char-fork/llama.cpp

git clone https://git.t4t.associates/char-fork/llama.cpp

scutler-nvFix for issue #22974. Cast intermediate results to float before adding and casting the result to the destination type. Avoids half+half operator ambiguity. (#22994)7f3f843c3

char/aliceai-t5-moe
44.2 KiB971 linesraw
1#include "allreduce.cuh"
2
3#if !defined(GGML_USE_HIP) && !defined(GGML_USE_MUSA)
4
5#include "convert.cuh"
6#include "ggml-impl.h"
7
8#include <algorithm>
9#include <cstdlib>
10#include <cstring>
11#include <limits>
12
13// ---------------------------------------------------------------------------
14// CUDA AllReduce for tensor-parallel inference across two GPUs.
15//
16// Provides an in-place sum reduction over matching tensors on two CUDA
17// devices in the same process.  Used by the tensor-split path alongside
18// NCCL; targets setups without NVLink, where data is exchanged between the
19// GPUs by staging it through pinned host memory over PCIe.
20//
21// Two reduction strategies are selected per call by tensor size:
22//
23//   * Chunked kernel path (small reductions): a single CUDA kernel both
24//     stages data through pinned host memory and performs the local sum.
25//     Cross-GPU synchronization happens *inside the kernel* (busy-wait on
26//     a host-memory flag), which keeps launch overhead low for the
27//     latency-sensitive token-generation case.
28//
29//   * Copy-engine path (large reductions): the transfer is split into
30//     D2H + H2D cudaMemcpyAsync chunks driven by the GPU's copy engine,
31//     followed by a small device-side add kernel.  Cross-GPU
32//     synchronization happens *outside the kernel*, via CUDA events
33//     between streams.  This keeps the compute engine free while large
34//     transfers are in flight, which matters for prefill-sized tensors.
35//     Reductions larger than the per-call inner cap are processed by an
36//     outer chunker that issues sequential inner calls.
37// ---------------------------------------------------------------------------
38
39// ---------------------------------------------------------------------------
40// Cross-GPU signal mechanism
41//
42// One int per (slot, rank) pair in pinned host memory.  Each AR call writes a
43// strictly increasing token (= the AR call number) into its own arrival int.
44// The peer spins until its read of the other's arrival int equals the token
45// it expects for this call -- a mismatch means the peer hasn't arrived yet.
46// Tokens never repeat over realistic call rates (32-bit int wraps in tens of
47// days at thousands of ARs/sec), so arrival ints don't need to be reset
48// between calls; we initialize once at pipeline init and let the values
49// accumulate.
50//
51// There is exactly one writer (the owning GPU) and one reader (the peer), so
52// we don't need atomics.  A volatile store paired with __threadfence_system()
53// provides the release ordering that makes the D2H writes visible system-wide
54// before the arrival token is observed.
55//
56// atomicAdd_system() requires hostNativeAtomicSupported, which is unavailable
57// on PCIe-attached consumer GPUs without NVLink, so the volatile path is the
58// portable choice.
59// ---------------------------------------------------------------------------
60
61static __device__ __forceinline__ void ggml_cuda_ar_signal_set(int * p, int token) {
62    *(volatile int *)p = token;
63}
64static __device__ __forceinline__ int ggml_cuda_ar_signal_get(const int * p) {
65    return *(const volatile int *)p;
66}
67
68// Byte spacing between adjacent arrival ints.  64 bytes (one cache line)
69// ensures each GPU/block's arrival slot lives on its own line, preventing
70// false-sharing stalls on the polling GPU.
71static constexpr size_t GGML_CUDA_AR_ARRIVAL_STRIDE = 64;
72
73// Number of blocks the chunked kernel launches with.  Each block stripes a
74// disjoint slice of the data and synchronizes through its own arrival-token
75// slot so multiple SMs can pump PCIe stores in parallel.
76static constexpr int GGML_CUDA_AR_KERNEL_BLOCKS = 8;
77
78// ---------------------------------------------------------------------------
79// Chunked kernel AllReduce -- 2 GPUs, supports float, half, and bfloat16.
80//
81// Both GPUs run this kernel simultaneously on independent streams.  sendbuf
82// and recvbuf live in T_dst (the caller's tensor type); host_mine / host_other
83// carry data in T_wire (the on-wire type, possibly narrower than T_dst -- e.g.
84// T_dst=F32 with T_wire=BF16 halves the bytes pushed across PCIe).  When
85// T_dst == T_wire the casts below are no-ops.
86//
87// Each GPU runs three phases:
88//
89//   Phase 1 (all threads): cast sendbuf (T_dst) -> T_wire and store as
90//                          single-instruction-width vectors into host_mine.
91//                          __threadfence_system() commits these writes to host
92//                          memory.
93//   Phase 2 (thread 0):    write token to arrival_mine; spin until
94//                          arrival_other == token.
95//   Phase 3 (all threads): read T_wire vectors from host_other, cast
96//                          each element to T_dst, and sum with the local
97//                          sendbuf value (also rounded through T_wire so that
98//                          both GPUs truncate identically -- this guarantees
99//                          bit-equivalent results across the two devices).
100//
101// Multi-block: blocks stripe vectors across (gridDim.x * blockDim.x) global
102// threads to keep multiple SMs issuing PCIe stores in parallel.  Each block
103// has its own arrival-token slot (offset by blockIdx.x * ARRIVAL_STRIDE);
104// thread 0 of each block signals/spins on that slot independently of other
105// blocks.  Tail elements (the leftover < ELEMS_PER_VEC at the end) are
106// handled only by block 0 to avoid cross-block writes to the same slots.
107// ---------------------------------------------------------------------------
108template <typename T_dst, typename T_wire>
109static __global__ void ggml_cuda_ar_kernel(
110        const T_dst  *              sendbuf,
111        T_dst        *              recvbuf,
112        T_wire       * __restrict__ host_mine,
113        const T_wire * __restrict__ host_other,
114        int                         count,
115        int *                       arrival_mine,
116        int *                       arrival_other,
117        int                         token) {
118
119    // Vector unit for the wire type, sized to the arch's widest single-instruction
120    // copy (16 B on Volta+).  Each phase-1 iter writes one vector to host memory;
121    // each phase-3 iter reads one and produces ELEMS_PER_VEC sums.
122    constexpr int ELEMS_PER_VEC = ggml_cuda_get_max_cpy_bytes() / sizeof(T_wire);
123    constexpr int ARRIVAL_INTS  = (int)(GGML_CUDA_AR_ARRIVAL_STRIDE / sizeof(int));
124
125    const int tid       = threadIdx.x;
126    const int nt        = blockDim.x;
127    const int bid       = blockIdx.x;
128    const int gtid      = bid * nt + tid;
129    const int gnt       = gridDim.x * nt;
130    const int count_vec = count / ELEMS_PER_VEC;
131    const int tail      = count_vec * ELEMS_PER_VEC;
132
133    // Phase 1: cast sendbuf (T_dst) -> host_mine (T_wire) and store as vectors.
134    {
135        for (int i = gtid; i < count_vec; i += gnt) {
136            const int off = i * ELEMS_PER_VEC;
137            T_wire wire[ELEMS_PER_VEC];
138            #pragma unroll
139            for (int k = 0; k < ELEMS_PER_VEC; ++k) {
140                wire[k] = ggml_cuda_cast<T_wire>(sendbuf[off + k]);
141            }
142            ggml_cuda_memcpy_1<sizeof(wire)>(&host_mine[off], wire);
143        }
144        if (bid == 0 && tid < count - tail) {
145            host_mine[tail + tid] = ggml_cuda_cast<T_wire>(sendbuf[tail + tid]);
146        }
147    }
148
149    // Commit this block's host writes before signalling.
150    __threadfence_system();
151    __syncthreads();
152
153    // Phase 2: thread 0 of each block signals on its own arrival slot, then
154    // spins for the matching slot from peer.  Per-block tokens mean blocks
155    // proceed independently -- no inter-block barrier needed.
156    if (tid == 0) {
157        int       * my_slot    = arrival_mine  + bid * ARRIVAL_INTS;
158        const int * other_slot = arrival_other + bid * ARRIVAL_INTS;
159
160        ggml_cuda_ar_signal_set(my_slot, token);
161        __threadfence_system(); // make our signal visible system-wide
162
163        while (ggml_cuda_ar_signal_get(other_slot) != token) {
164#if __CUDA_ARCH__ >= GGML_CUDA_CC_VOLTA
165            __nanosleep(100);
166#else
167            NO_DEVICE_CODE;
168#endif // __CUDA_ARCH__ >= GGML_CUDA_CC_VOLTA
169        }
170    }
171
172    __syncthreads();
173
174    // Acquire peer's host_other writes (this block's stripe of them).
175    __threadfence_system();
176
177    // Phase 3: read peer's T_wire vector, cast both sides through T_wire for
178    // bit-equivalence, sum in T_dst precision, and write back to recvbuf.
179    {
180        for (int i = gtid; i < count_vec; i += gnt) {
181            const int off = i * ELEMS_PER_VEC;
182            T_wire wire[ELEMS_PER_VEC];
183            ggml_cuda_memcpy_1<sizeof(wire)>(wire, &host_other[off]);
184            #pragma unroll
185            for (int k = 0; k < ELEMS_PER_VEC; ++k) {
186                const T_wire d_low = ggml_cuda_cast<T_wire>(sendbuf[off + k]);
187                recvbuf[off + k] = ggml_cuda_cast<T_dst>(
188                    ggml_cuda_cast<float>(d_low) + ggml_cuda_cast<float>(wire[k]));
189            }
190        }
191        if (bid == 0 && tid < count - tail) {
192            const T_wire d_low = ggml_cuda_cast<T_wire>(sendbuf[tail + tid]);
193            recvbuf[tail + tid] = ggml_cuda_cast<T_dst>(
194                ggml_cuda_cast<float>(d_low) +
195                ggml_cuda_cast<float>(host_other[tail + tid]));
196        }
197    }
198}
199
200// Combined load-convert-add kernel.  The peer's contribution arrives as T_src
201// (which may be a lower-precision type than T_dst when the BF16 round-trip is
202// active).  For bit-equivalence between the two GPUs, dst is first rounded
203// through T_src's precision via ggml_cuda_cast -- peer already truncated its
204// own value the same way before sending -- so both sides perform identical
205// arithmetic.  When T_dst == T_src the round-trip cast is a no-op.
206template <typename T_dst, typename T_src>
207static __global__ void ggml_cuda_ar_add_kernel(
208        T_dst       * __restrict__ dst,
209        const T_src * __restrict__ src,
210        int count) {
211    const int tid = blockIdx.x * blockDim.x + threadIdx.x;
212    const int nt  = gridDim.x * blockDim.x;
213    for (int i = tid; i < count; i += nt) {
214        const T_src d_low = ggml_cuda_cast<T_src>(dst[i]);
215        dst[i] = ggml_cuda_cast<T_dst>(
216            ggml_cuda_cast<float>(d_low) + ggml_cuda_cast<float>(src[i]));
217    }
218}
219
220// ---------------------------------------------------------------------------
221// Pipeline structure
222// ---------------------------------------------------------------------------
223
224// Number of slots in the event / arrival ring.  Two slots is sufficient:
225// lockstep guarantees the two GPUs are at most one AR (or chunk) apart, so
226// slot[N%2] is always safe to reuse -- peer has already consumed slot[N%2]
227// from AR N-2 by the time we get to AR N.  acquire_slot's
228// cudaEventSynchronize on ev.ker for both devices makes that consumption
229// explicit before we overwrite host_buf[slot] for the new AR.
230static constexpr int GGML_CUDA_AR_POOL_SIZE = 2;
231
232// Maximum chunk size (bytes per GPU) handled by one chunked kernel launch.
233// Larger tensors are reduced by issuing multiple chunked launches.
234static constexpr size_t GGML_CUDA_AR_MAX_BYTES = 1024 * 1024; // 1 MB
235
236// Copy-engine path: largest tensor accepted on this path; sets host_large /
237// dev_tmp allocation size.
238static constexpr size_t GGML_CUDA_AR_COPY_MAX_BYTES = 32 * 1024 * 1024; // 32 MB
239
240// AR wire size at which the copy-engine path takes over from the chunked-
241// kernel path.  Override via GGML_CUDA_AR_COPY_THRESHOLD.
242static constexpr size_t GGML_CUDA_AR_COPY_THRESHOLD_DEFAULT = 1024 * 1024; // 1 MB
243// Per-call CE chunk-size heuristic: chunk_bytes = clamp(nbytes / 4, MIN, MAX).
244// The /4 keeps ~4 chunks in flight at any moment (good D2H/H2D overlap with
245// the peer); the clamps cover the cases where nbytes/4 is too small (per-
246// memcpy fixed cost dominates) or too large (chunk-level pipelining stalls).
247// Env var GGML_CUDA_AR_COPY_CHUNK_BYTES can override with a fixed value.
248static constexpr size_t GGML_CUDA_AR_COPY_CHUNK_BYTES_HEURISTIC_MIN = 512 * 1024;       // 512 KB
249static constexpr size_t GGML_CUDA_AR_COPY_CHUNK_BYTES_HEURISTIC_MAX = 2 * 1024 * 1024;  // 2 MB
250// Absolute floor that an env-var override is allowed to set; this caps the
251// per-slot copy-event array.  256 KB -> up to 128 chunks per 32 MB tensor.
252static constexpr size_t GGML_CUDA_AR_COPY_CHUNK_BYTES_MIN = 256 * 1024;
253static constexpr int GGML_CUDA_AR_COPY_MAX_CHUNKS =
254    static_cast<int>((GGML_CUDA_AR_COPY_MAX_BYTES + GGML_CUDA_AR_COPY_CHUNK_BYTES_MIN - 1) /
255                    GGML_CUDA_AR_COPY_CHUNK_BYTES_MIN);
256
257struct ggml_cuda_ar_event_slot {
258    cudaEvent_t app = nullptr;  // upstream computation complete
259    cudaEvent_t cpy[GGML_CUDA_AR_COPY_MAX_CHUNKS] = {};  // copy-engine D2H chunks complete
260    cudaEvent_t h2d = nullptr;  // copy-engine H2Ds complete (handoff AR stream -> compute stream)
261    cudaEvent_t ker = nullptr;  // AllReduce kernel complete
262};
263
264// Mapped pinned host allocation: cudaHostAlloc + cudaHostGetDevicePointer
265// in one place, with the host handle preserved for cudaFreeHost.  Used where
266// the CPU never touches the buffer -- only the device reads/writes via the
267// mapped device pointer.  Required on systems where cudaDevAttrCanUseHost-
268// PointerForRegisteredMem is 0 and the host pointer can't be used as a
269// device pointer.
270struct ggml_cuda_ar_host_mapping {
271    uint8_t * host = nullptr;   // cudaFreeHost handle; also the H-side ptr for cudaMemcpyAsync
272    uint8_t * dev  = nullptr;   // device-side pointer for kernels / cudaMemset
273
274    cudaError_t alloc(size_t bytes) {
275        cudaError_t rc = cudaHostAlloc(reinterpret_cast<void **>(&host), bytes,
276                                       cudaHostAllocPortable | cudaHostAllocMapped);
277        if (rc != cudaSuccess) {
278            host = nullptr;
279            return rc;
280        }
281        rc = cudaHostGetDevicePointer(reinterpret_cast<void **>(&dev), host, 0);
282        if (rc != cudaSuccess) {
283            cudaFreeHost(host);
284            host = nullptr;
285            dev  = nullptr;
286        }
287        return rc;
288    }
289
290    void free() {
291        if (host) {
292            cudaFreeHost(host);
293            host = nullptr;
294            dev  = nullptr;
295        }
296    }
297};
298
299struct ggml_cuda_ar_pipeline {
300    int      n_devices;
301    int      devices[GGML_CUDA_MAX_DEVICES];
302    size_t   buf_bytes;    // bytes per device in host_buf[]
303    size_t   copy_bytes;   // bytes per device in host_large[] / dev_tmp[]
304    size_t   copy_threshold;
305    size_t   copy_chunk_bytes;
306    size_t   bf16_threshold; // tensors >= this size (bytes) are reduced via FP32->BF16 round-trip; 0 disables
307    uint64_t call_count;
308
309    // Per-device resources.
310    ggml_cuda_ar_host_mapping host_buf[GGML_CUDA_MAX_DEVICES];   // pinned staging (chunked kernel)
311    ggml_cuda_ar_host_mapping host_large[GGML_CUDA_MAX_DEVICES]; // pinned staging (copy-engine)
312    char *                    dev_tmp[GGML_CUDA_MAX_DEVICES];    // device scratch for copy-engine path
313    cudaStream_t             streams[GGML_CUDA_MAX_DEVICES];   // non-blocking
314    ggml_cuda_ar_event_slot  ev_pool[GGML_CUDA_MAX_DEVICES][GGML_CUDA_AR_POOL_SIZE];
315
316    // Copy-engine: per-device "I finished reading my peer's host_large"
317    // event.  Indexed by RECORDER device.  Recorded same-device on streams[i]
318    // after stage 2's last H2D from host_large[peer].  Waited cross-device
319    // by peer's stage-1 stream before the next AR overwrites host_large[peer].
320    cudaEvent_t              host_large_read_done[GGML_CUDA_MAX_DEVICES];
321    bool                     host_large_read_done_valid;
322
323    // Copy-engine: per-device "my add_kernel is done with dev_tmp" event.
324    // Recorded on the compute stream after each add_kernel; the AR stream
325    // waits on it before the next copy_impl's H2D overwrites dev_tmp.  Lets us
326    // single-buffer dev_tmp despite add_kernel running on a separate stream.
327    cudaEvent_t              dev_tmp_kernel_done[GGML_CUDA_MAX_DEVICES];
328    bool                     dev_tmp_kernel_done_valid;
329
330    // Arrival ring: ARRIVAL_STRIDE bytes between adjacent ints.  Mapped pinned
331    // memory; CPU never reads/writes -- only the kernel and cudaMemset.
332    // Use ggml_cuda_ar_arrival_ptr() to index.
333    ggml_cuda_ar_host_mapping arrival;
334};
335
336// Base pointer for the (slot, rank) per-block token block.  The kernel adds
337// blockIdx.x * (ARRIVAL_STRIDE/sizeof(int)) internally to land on its own slot.
338static int * ggml_cuda_ar_arrival_ptr(const ggml_cuda_ar_pipeline * p, int slot, int rank) {
339    const size_t offset = ((size_t)slot * p->n_devices + rank) *
340                          GGML_CUDA_AR_KERNEL_BLOCKS * GGML_CUDA_AR_ARRIVAL_STRIDE;
341    return reinterpret_cast<int *>(p->arrival.dev + offset);
342}
343
344static uint64_t ggml_cuda_ar_env_u64(const char * name, uint64_t default_value) {
345    const char * value = getenv(name);
346    if (value == nullptr || value[0] == '\0') {
347        return default_value;
348    }
349
350    char * end = nullptr;
351    const unsigned long long parsed = strtoull(value, &end, 10);
352    return end != value ? (uint64_t) parsed : default_value;
353}
354
355struct ggml_cuda_ar_slot_info {
356    int slot;
357    int token;
358};
359
360static ggml_cuda_ar_slot_info ggml_cuda_ar_acquire_slot(ggml_cuda_ar_pipeline * p) {
361    const int  slot        = static_cast<int>(p->call_count % GGML_CUDA_AR_POOL_SIZE);
362    const bool pool_lapped = p->call_count >= GGML_CUDA_AR_POOL_SIZE;
363    p->call_count++;
364
365    if (pool_lapped) {
366        for (int i = 0; i < p->n_devices; ++i) {
367            ggml_cuda_set_device(p->devices[i]);
368            CUDA_CHECK(cudaEventSynchronize(p->ev_pool[i][slot].ker));
369        }
370    }
371
372    return { slot, (int) p->call_count };
373}
374
375// Per-AR copy-engine chunk size: env-var override if set, else heuristic
376// (clamp(nbytes/4, HEURISTIC_MIN, HEURISTIC_MAX)).
377static size_t ggml_cuda_ar_chunk_bytes(const ggml_cuda_ar_pipeline * p, size_t nbytes) {
378    if (p->copy_chunk_bytes > 0) {
379        return p->copy_chunk_bytes;
380    }
381    return std::min(GGML_CUDA_AR_COPY_CHUNK_BYTES_HEURISTIC_MAX,
382                    std::max(GGML_CUDA_AR_COPY_CHUNK_BYTES_HEURISTIC_MIN, nbytes / 4));
383}
384
385static void ggml_cuda_ar_wait_for_compute(
386        ggml_cuda_ar_pipeline * p, ggml_backend_cuda_context * cuda_ctx, int rank, int slot) {
387    ggml_cuda_ar_event_slot & ev = p->ev_pool[rank][slot];
388    CUDA_CHECK(cudaEventRecord(ev.app, cuda_ctx->stream()));
389    CUDA_CHECK(cudaStreamWaitEvent(p->streams[rank], ev.app));
390}
391
392// ---------------------------------------------------------------------------
393// Init / free
394// ---------------------------------------------------------------------------
395
396ggml_cuda_ar_pipeline * ggml_cuda_ar_pipeline_init(const int * devices, size_t n_devices) {
397
398    if (n_devices != 2) {
399        GGML_LOG_DEBUG("%s: internal AllReduce only supports n_devices=2 (got %zu); "
400                       "falling back\n", __func__, n_devices);
401        return nullptr;
402    }
403
404    // The chunked kernel uses __nanosleep, which is sm70+ (Volta+).
405    for (size_t i = 0; i < n_devices; ++i) {
406        const int cc = ggml_cuda_info().devices[devices[i]].cc;
407        if (cc < GGML_CUDA_CC_VOLTA) {
408            GGML_LOG_DEBUG("%s: internal AllReduce requires compute capability >= %d "
409                           "(device %d has cc=%d); falling back\n",
410                           __func__, GGML_CUDA_CC_VOLTA, devices[i], cc);
411            return nullptr;
412        }
413    }
414
415    auto * p = new ggml_cuda_ar_pipeline{};
416    p->n_devices        = n_devices;
417    p->copy_bytes       = GGML_CUDA_AR_COPY_MAX_BYTES;
418    p->copy_threshold   = ggml_cuda_ar_env_u64("GGML_CUDA_AR_COPY_THRESHOLD", GGML_CUDA_AR_COPY_THRESHOLD_DEFAULT);
419    // 0 = use the per-call heuristic (default).  Non-zero env value forces a
420    // fixed chunk size for diagnostics, with a floor at COPY_CHUNK_BYTES_MIN.
421    p->copy_chunk_bytes = ggml_cuda_ar_env_u64("GGML_CUDA_AR_COPY_CHUNK_BYTES", 0);
422    if (p->copy_chunk_bytes > 0 && p->copy_chunk_bytes < GGML_CUDA_AR_COPY_CHUNK_BYTES_MIN) {
423        GGML_LOG_WARN("%s: GGML_CUDA_AR_COPY_CHUNK_BYTES=%zu below minimum %zu; clamping\n",
424                      __func__, p->copy_chunk_bytes, GGML_CUDA_AR_COPY_CHUNK_BYTES_MIN);
425        p->copy_chunk_bytes = GGML_CUDA_AR_COPY_CHUNK_BYTES_MIN;
426    }
427    // Default 1: BF16 round-trip is always on for F32 inputs (any non-zero
428    // ne).  Set GGML_CUDA_AR_BF16_THRESHOLD=0 to disable, or to a larger
429    // byte threshold to opt out for small tensors.
430    p->bf16_threshold   = ggml_cuda_ar_env_u64("GGML_CUDA_AR_BF16_THRESHOLD", 1);
431    for (size_t i = 0; i < n_devices; ++i) {
432        p->devices[i] = devices[i];
433    }
434
435    // Per-device streams and event pools.
436    for (size_t i = 0; i < n_devices; ++i) {
437        ggml_cuda_set_device(p->devices[i]);
438
439        cudaStream_t stream = nullptr;
440        if (cudaStreamCreateWithFlags(&stream, cudaStreamNonBlocking) != cudaSuccess) {
441            GGML_LOG_ERROR("%s: cudaStreamCreateWithFlags failed for device %d\n",
442                           __func__, p->devices[i]);
443            ggml_cuda_ar_pipeline_free(p);
444            return nullptr;
445        }
446        p->streams[i] = stream;
447
448        for (int s = 0; s < GGML_CUDA_AR_POOL_SIZE; ++s) {
449            bool ok =
450                cudaEventCreateWithFlags(&p->ev_pool[i][s].app, cudaEventDisableTiming) == cudaSuccess &&
451                cudaEventCreateWithFlags(&p->ev_pool[i][s].h2d, cudaEventDisableTiming) == cudaSuccess &&
452                cudaEventCreateWithFlags(&p->ev_pool[i][s].ker, cudaEventDisableTiming) == cudaSuccess;
453            for (int c = 0; ok && c < GGML_CUDA_AR_COPY_MAX_CHUNKS; ++c) {
454                ok = cudaEventCreateWithFlags(&p->ev_pool[i][s].cpy[c], cudaEventDisableTiming) == cudaSuccess;
455            }
456            if (!ok) {
457                GGML_LOG_ERROR("%s: cudaEventCreate failed for device %d slot %d\n",
458                               __func__, p->devices[i], s);
459                ggml_cuda_ar_pipeline_free(p);
460                return nullptr;
461            }
462        }
463
464        if (cudaEventCreateWithFlags(&p->host_large_read_done[i], cudaEventDisableTiming) != cudaSuccess) {
465            GGML_LOG_ERROR("%s: cudaEventCreate for host_large_read_done failed for device %d\n",
466                           __func__, p->devices[i]);
467            ggml_cuda_ar_pipeline_free(p);
468            return nullptr;
469        }
470        if (cudaEventCreateWithFlags(&p->dev_tmp_kernel_done[i], cudaEventDisableTiming) != cudaSuccess) {
471            GGML_LOG_ERROR("%s: cudaEventCreate for dev_tmp_kernel_done failed for device %d\n",
472                           __func__, p->devices[i]);
473            ggml_cuda_ar_pipeline_free(p);
474            return nullptr;
475        }
476    }
477
478    // Arrival ring: cache-line padded so each GPU's int is on its own line.
479    const size_t arrival_bytes =
480        (size_t)GGML_CUDA_AR_POOL_SIZE * n_devices *
481        GGML_CUDA_AR_KERNEL_BLOCKS * GGML_CUDA_AR_ARRIVAL_STRIDE;
482    if (p->arrival.alloc(arrival_bytes) != cudaSuccess) {
483        GGML_LOG_ERROR("%s: alloc for arrival ring failed (%zu bytes)\n",
484                       __func__, arrival_bytes);
485        ggml_cuda_ar_pipeline_free(p);
486        return nullptr;
487    }
488    ggml_cuda_set_device(p->devices[0]);
489    if (cudaMemset(p->arrival.dev, 0, arrival_bytes) != cudaSuccess) {
490        GGML_LOG_ERROR("%s: cudaMemset for arrival ring failed (%zu bytes)\n",
491                       __func__, arrival_bytes);
492        ggml_cuda_ar_pipeline_free(p);
493        return nullptr;
494    }
495
496    // Per-device pinned staging buffers -- POOL_SIZE-deep ring so the chunked-
497    // kernel can write the next slot's data while the peer is still reading
498    // the previous slot's. Indexed by (slot * buf_bytes) at the call site.
499    p->buf_bytes = GGML_CUDA_AR_MAX_BYTES;
500    const size_t host_buf_total = (size_t) GGML_CUDA_AR_POOL_SIZE * p->buf_bytes;
501    for (size_t i = 0; i < n_devices; ++i) {
502        if (p->host_buf[i].alloc(host_buf_total) != cudaSuccess) {
503            GGML_LOG_ERROR("%s: alloc for staging failed (%zu bytes)\n",
504                           __func__, host_buf_total);
505            ggml_cuda_ar_pipeline_free(p);
506            return nullptr;
507        }
508    }
509
510    // Copy-engine path: pinned host staging + device scratch, sized for the
511    // largest tensor we accept on this path (GGML_CUDA_AR_COPY_MAX_BYTES).
512    // dev_tmp is single-buffered; cross-AR safety is enforced by an explicit
513    // cross-stream wait in copy_impl on the prior AR's add_kernel-done event.
514    for (size_t i = 0; i < n_devices; ++i) {
515        ggml_cuda_set_device(p->devices[i]);
516        if (p->host_large[i].alloc(p->copy_bytes) != cudaSuccess) {
517            GGML_LOG_ERROR("%s: alloc for large staging failed (%zu bytes)\n",
518                           __func__, p->copy_bytes);
519            ggml_cuda_ar_pipeline_free(p);
520            return nullptr;
521        }
522        if (cudaMalloc(reinterpret_cast<void **>(&p->dev_tmp[i]), p->copy_bytes) != cudaSuccess) {
523            GGML_LOG_ERROR("%s: cudaMalloc for copy scratch failed (%zu bytes) on device %d\n",
524                           __func__, p->copy_bytes, p->devices[i]);
525            ggml_cuda_ar_pipeline_free(p);
526            return nullptr;
527        }
528    }
529
530    GGML_LOG_INFO("%s: initialized AllReduce pipeline: %zu GPUs, "
531                  "%zu KB chunked kernel staging + %zu MB copy-engine staging per GPU\n",
532                  __func__, n_devices, p->buf_bytes >> 10, p->copy_bytes >> 20);
533
534    return p;
535}
536
537void ggml_cuda_ar_pipeline_free(ggml_cuda_ar_pipeline * p) {
538    if (!p) {
539        return;
540    }
541
542    // Drain all in-flight kernels before tearing down resources.
543    for (int i = 0; i < p->n_devices; ++i) {
544        if (p->streams[i]) {
545            ggml_cuda_set_device(p->devices[i]);
546            cudaStreamSynchronize(p->streams[i]);
547        }
548    }
549
550    for (int i = 0; i < p->n_devices; ++i) {
551        p->host_buf[i].free();
552        p->host_large[i].free();
553        if (p->dev_tmp[i]) {
554            ggml_cuda_set_device(p->devices[i]);
555            cudaFree(p->dev_tmp[i]);
556        }
557        ggml_cuda_set_device(p->devices[i]);
558        for (int s = 0; s < GGML_CUDA_AR_POOL_SIZE; ++s) {
559            if (p->ev_pool[i][s].app) { cudaEventDestroy(p->ev_pool[i][s].app); }
560            for (int c = 0; c < GGML_CUDA_AR_COPY_MAX_CHUNKS; ++c) {
561                if (p->ev_pool[i][s].cpy[c]) { cudaEventDestroy(p->ev_pool[i][s].cpy[c]); }
562            }
563            if (p->ev_pool[i][s].h2d) { cudaEventDestroy(p->ev_pool[i][s].h2d); }
564            if (p->ev_pool[i][s].ker) { cudaEventDestroy(p->ev_pool[i][s].ker); }
565        }
566        if (p->host_large_read_done[i]) {
567            ggml_cuda_set_device(p->devices[i]);
568            cudaEventDestroy(p->host_large_read_done[i]);
569        }
570        if (p->dev_tmp_kernel_done[i]) {
571            ggml_cuda_set_device(p->devices[i]);
572            cudaEventDestroy(p->dev_tmp_kernel_done[i]);
573        }
574        if (p->streams[i]) {
575            ggml_cuda_set_device(p->devices[i]);
576            cudaStreamDestroy(p->streams[i]);
577        }
578    }
579    p->arrival.free();
580    delete p;
581}
582
583// ---------------------------------------------------------------------------
584// Dispatch
585// ---------------------------------------------------------------------------
586
587// Asymmetric copy_impl: data sent over PCIe in T_src precision (one element of
588// nbytes per ne element); accumulated locally into a T_dst buffer.  When
589// T_src == T_dst this is the original homogeneous reduction.  When they differ
590// (e.g. BF16 wire / F32 accumulator) the add kernel rounds dst through T_src
591// for bit-equivalence between GPUs and we skip the otherwise-needed
592// post-conversion entirely.
593template <typename T_src, typename T_dst>
594static bool ggml_cuda_ar_allreduce_copy_impl(
595        ggml_cuda_ar_pipeline * p,
596        ggml_backend_t        * backends,
597        T_src * const           src_buf[GGML_CUDA_MAX_DEVICES],
598        T_dst * const           dst_buf[GGML_CUDA_MAX_DEVICES],
599        const bool              compute[GGML_CUDA_MAX_DEVICES],
600        int64_t                 ne,
601        size_t                  nbytes) {
602    GGML_ASSERT(p->n_devices == 2);
603    GGML_ASSERT(nbytes <= p->copy_bytes);
604    GGML_ASSERT(ne <= std::numeric_limits<int>::max());
605
606    const size_t chunk_bytes = ggml_cuda_ar_chunk_bytes(p, nbytes);
607    GGML_ASSERT(chunk_bytes > 0);
608
609    const int slot = ggml_cuda_ar_acquire_slot(p).slot;
610    const size_t copy_chunks = (nbytes + chunk_bytes - 1) / chunk_bytes;
611    GGML_ASSERT(copy_chunks <= GGML_CUDA_AR_COPY_MAX_CHUNKS);
612
613    ggml_backend_cuda_context * cuda_ctx[2] = {};
614
615    // Stage 1: both GPUs copy their local contribution to pinned host memory.
616    for (int i = 0; i < 2; ++i) {
617        ggml_cuda_set_device(p->devices[i]);
618        cuda_ctx[i] = static_cast<ggml_backend_cuda_context *>(backends[i]->context);
619        GGML_ASSERT(cuda_ctx[i]->device == p->devices[i]);
620
621        ggml_cuda_ar_wait_for_compute(p, cuda_ctx[i], i, slot);
622
623        // Wait for peer's H2D from our host_large[i] (recorded in the
624        // previous AR's stage 2) to complete before we overwrite host_large[i].
625        // host_large_read_done[peer] = peer finished reading host_large[i].
626        // No-op on the first AR -- no prior record exists.
627        if (p->host_large_read_done_valid) {
628            const int peer = 1 - i;
629            CUDA_CHECK(cudaStreamWaitEvent(p->streams[i], p->host_large_read_done[peer]));
630        }
631
632        if (!compute[i]) {
633            CUDA_CHECK(cudaMemsetAsync(src_buf[i], 0, nbytes, p->streams[i]));
634        }
635
636        for (size_t c = 0; c < copy_chunks; ++c) {
637            const size_t offset = c * chunk_bytes;
638            const size_t this_bytes = (nbytes - offset) < chunk_bytes ?
639                (nbytes - offset) : chunk_bytes;
640
641            CUDA_CHECK(cudaMemcpyAsync(
642                p->host_large[i].host + offset, reinterpret_cast<char *>(src_buf[i]) + offset, this_bytes,
643                cudaMemcpyDeviceToHost, p->streams[i]));
644            CUDA_CHECK(cudaEventRecord(p->ev_pool[i][slot].cpy[c], p->streams[i]));
645        }
646    }
647
648    // Stage 2: each GPU waits for each peer D2H chunk, pulls that chunk back to
649    // local device scratch (dev_tmp), then performs one device-local add over
650    // the assembled peer tensor.  The H2Ds run on the AR stream (copy engine)
651    // and the add_kernel runs on the caller's compute stream, so the AR stream
652    // stays pure-copy and avoids an in-stream copy->compute engine switch every
653    // AR.  dev_tmp is single-buffered: the AR stream waits cross-stream on the
654    // prior AR's add_kernel-done event before overwriting it.
655    for (int i = 0; i < 2; ++i) {
656        const int peer = 1 - i;
657        ggml_cuda_set_device(p->devices[i]);
658
659        // Wait for the previous AR's add_kernel (on the compute stream) to
660        // finish reading dev_tmp before our H2D overwrites it.  No-op on the
661        // first copy_impl call.
662        if (p->dev_tmp_kernel_done_valid) {
663            CUDA_CHECK(cudaStreamWaitEvent(p->streams[i], p->dev_tmp_kernel_done[i]));
664        }
665
666        for (size_t c = 0; c < copy_chunks; ++c) {
667            const size_t offset = c * chunk_bytes;
668            const size_t this_bytes = (nbytes - offset) < chunk_bytes ?
669                (nbytes - offset) : chunk_bytes;
670
671            CUDA_CHECK(cudaStreamWaitEvent(p->streams[i], p->ev_pool[peer][slot].cpy[c]));
672            CUDA_CHECK(cudaMemcpyAsync(
673                p->dev_tmp[i] + offset, p->host_large[peer].host + offset, this_bytes,
674                cudaMemcpyHostToDevice, p->streams[i]));
675        }
676
677        // Mark our reads of host_large[peer] complete so peer's next AR can
678        // safely overwrite it.
679        CUDA_CHECK(cudaEventRecord(p->host_large_read_done[i], p->streams[i]));
680
681        // Hand off from AR stream (copy engine) to compute stream: compute
682        // stream waits for all H2Ds to finish, then runs the add_kernel.
683        CUDA_CHECK(cudaEventRecord(p->ev_pool[i][slot].h2d, p->streams[i]));
684        CUDA_CHECK(cudaStreamWaitEvent(cuda_ctx[i]->stream(), p->ev_pool[i][slot].h2d));
685
686        const int block_size = 256;
687        int n_blocks = (int) ((ne + block_size - 1) / block_size);
688        if (n_blocks > 1024) {
689            n_blocks = 1024;
690        }
691        ggml_cuda_ar_add_kernel<T_dst, T_src><<<n_blocks, block_size, 0, cuda_ctx[i]->stream()>>>(
692            dst_buf[i],
693            reinterpret_cast<const T_src *>(p->dev_tmp[i]),
694            (int) ne);
695        CUDA_CHECK(cudaGetLastError());
696
697        // Record dev_tmp-released on the compute stream so the next copy_impl
698        // can wait for the kernel to finish before overwriting dev_tmp.  Also
699        // record AR-done as ev.ker for acquire_slot's pool-wraparound sync.
700        CUDA_CHECK(cudaEventRecord(p->dev_tmp_kernel_done[i], cuda_ctx[i]->stream()));
701        CUDA_CHECK(cudaEventRecord(p->ev_pool[i][slot].ker, cuda_ctx[i]->stream()));
702    }
703    p->host_large_read_done_valid = true;
704    p->dev_tmp_kernel_done_valid = true;
705
706    return true;
707}
708
709// Outer-level chunker: copy_impl handles up to copy_bytes per call (limited by
710// the host_large / dev_tmp allocation size).  When the full AR exceeds that,
711// slice the tensor into copy_bytes-sized pieces and call copy_impl repeatedly.
712// Each slice goes through its own stage 1 -> stage 2 cycle and acquires its own
713// slot, so cross-AR fences and pool wraparound work the same way as for any
714// other sequence of small ARs.
715template <typename T_src, typename T_dst>
716static bool ggml_cuda_ar_allreduce_copy_outer(
717        ggml_cuda_ar_pipeline * p,
718        ggml_backend_t        * backends,
719        T_src * const           src_buf[GGML_CUDA_MAX_DEVICES],
720        T_dst * const           dst_buf[GGML_CUDA_MAX_DEVICES],
721        const bool              compute[GGML_CUDA_MAX_DEVICES],
722        int64_t                 ne) {
723    const int64_t outer_max_elems = (int64_t) (p->copy_bytes / sizeof(T_src));
724    GGML_ASSERT(outer_max_elems > 0);
725
726    bool ok = true;
727    for (int64_t outer_start = 0; outer_start < ne && ok; outer_start += outer_max_elems) {
728        const int64_t outer_ne     = std::min(outer_max_elems, ne - outer_start);
729        const size_t  outer_nbytes = (size_t) outer_ne * sizeof(T_src);
730
731        T_src * src[GGML_CUDA_MAX_DEVICES] = {};
732        T_dst * dst[GGML_CUDA_MAX_DEVICES] = {};
733        for (int i = 0; i < p->n_devices; ++i) {
734            src[i] = src_buf[i] + outer_start;
735            dst[i] = dst_buf[i] + outer_start;
736        }
737        ok = ggml_cuda_ar_allreduce_copy_impl<T_src, T_dst>(
738            p, backends, src, dst, compute, outer_ne, outer_nbytes);
739    }
740    return ok;
741}
742
743bool ggml_cuda_ar_allreduce(
744        ggml_cuda_ar_pipeline * p,
745        ggml_backend_t        * backends,
746        ggml_tensor           ** tensors) {
747    GGML_ASSERT(p != nullptr);
748
749    const int n = p->n_devices;
750    GGML_ASSERT(n == 2);
751
752    const ggml_type input_type = tensors[0]->type;
753    GGML_ASSERT(input_type == GGML_TYPE_F32 || input_type == GGML_TYPE_F16 || input_type == GGML_TYPE_BF16);
754
755    const int64_t ne = ggml_nelements(tensors[0]);
756    GGML_ASSERT(ne > 0);
757
758    const size_t   input_nbytes = ggml_nbytes(tensors[0]);
759
760    // BF16 round-trip: F32 inputs >= bf16_threshold are converted to BF16 for
761    // the reduction (chunked or copy-engine), halving on-wire bytes. Matches
762    // NCCL's behaviour. The pre-conversion zeroes inactive shards so the
763    // inner paths see them as already-prepared compute tensors.
764    const bool use_bf16 =
765        input_type == GGML_TYPE_F32 &&
766        p->bf16_threshold > 0 &&
767        input_nbytes >= p->bf16_threshold;
768
769    const ggml_type kernel_type = use_bf16 ? GGML_TYPE_BF16 : input_type;
770    const size_t    type_size   = ggml_type_size(kernel_type);
771    GGML_ASSERT(p->buf_bytes >= type_size);
772    const size_t    nbytes      = (size_t) ne * type_size;
773
774    bool compute_flag[GGML_CUDA_MAX_DEVICES] = {};
775    for (int i = 0; i < n; ++i) {
776        compute_flag[i] = (tensors[i]->flags & GGML_TENSOR_FLAG_COMPUTE) != 0;
777    }
778
779    // Decide between copy-engine and chunked kernel paths based on the working
780    // type's actual byte count.  No upper bound: copy_outer slices reductions
781    // larger than copy_bytes into copy_bytes-sized pieces.
782    const bool use_copy_engine =
783        p->copy_threshold > 0 &&
784        nbytes >= p->copy_threshold;
785
786    // BF16 inactive-shard zeroing: when use_bf16 is on, the combined kernel
787    // (chunked kernel path) and the combined add kernel (copy_engine path)
788    // both accumulate into the F32 tensor data directly, so an inactive
789    // shard's accumulator must start at zero.
790    if (use_bf16) {
791        for (int i = 0; i < n; ++i) {
792            if (!compute_flag[i]) {
793                auto * cuda_ctx = static_cast<ggml_backend_cuda_context *>(backends[i]->context);
794                GGML_ASSERT(cuda_ctx->device == p->devices[i]);
795                ggml_cuda_set_device(p->devices[i]);
796                CUDA_CHECK(cudaMemsetAsync(tensors[i]->data, 0, (size_t) ne * sizeof(float), cuda_ctx->stream()));
797            }
798        }
799    }
800
801    // Pre-convert F32 -> BF16 into bf16_tmp ONLY for the copy_engine + use_bf16
802    // path; the chunked kernel path's combined kernel does the conversion
803    // inline as it writes to host_buf.
804    ggml_cuda_pool_alloc<nv_bfloat16> bf16_tmp[GGML_CUDA_MAX_DEVICES];
805    void * copy_src_ptr[GGML_CUDA_MAX_DEVICES] = {};
806
807    if (use_copy_engine && use_bf16) {
808        to_bf16_cuda_t to_bf16 = ggml_get_to_bf16_cuda(GGML_TYPE_F32);
809        for (int i = 0; i < n; ++i) {
810            auto * cuda_ctx = static_cast<ggml_backend_cuda_context *>(backends[i]->context);
811            GGML_ASSERT(cuda_ctx->device == p->devices[i]);
812            bf16_tmp[i].pool = &cuda_ctx->pool();
813            bf16_tmp[i].alloc(ne);
814            ggml_cuda_set_device(p->devices[i]);
815            if (compute_flag[i]) {
816                to_bf16(tensors[i]->data, bf16_tmp[i].get(), ne, cuda_ctx->stream());
817                CUDA_CHECK(cudaGetLastError());
818            } else {
819                CUDA_CHECK(cudaMemsetAsync(bf16_tmp[i].get(), 0, nbytes, cuda_ctx->stream()));
820            }
821            copy_src_ptr[i] = bf16_tmp[i].get();
822        }
823    }
824
825    bool ok = true;
826    if (use_copy_engine) {
827        // After up-front BF16 conversion, the tmp buffers already hold the
828        // (possibly zeroed-for-inactive) data, so the inner path can treat
829        // every shard as compute.
830        bool inner_compute[GGML_CUDA_MAX_DEVICES];
831        for (int i = 0; i < n; ++i) {
832            inner_compute[i] = use_bf16 ? true : compute_flag[i];
833        }
834
835        // Dispatch into copy_impl with explicit src/dst types.  When use_bf16
836        // is on, the wire type is BF16 (src = bf16_tmp) and the accumulator
837        // is F32 (dst = tensors[i]->data); the combined add kernel rounds dst
838        // through BF16 for bit-equivalence and writes F32 directly, so no
839        // post-conversion is needed.  Otherwise src == dst (same native type).
840        if (use_bf16) {
841            GGML_ASSERT(kernel_type == GGML_TYPE_BF16);
842            nv_bfloat16 * src[GGML_CUDA_MAX_DEVICES] = {};
843            float       * dst[GGML_CUDA_MAX_DEVICES] = {};
844            for (int i = 0; i < n; ++i) {
845                src[i] = static_cast<nv_bfloat16 *>(copy_src_ptr[i]);
846                dst[i] = static_cast<float *>(tensors[i]->data);
847            }
848            ok = ggml_cuda_ar_allreduce_copy_outer<nv_bfloat16, float>(
849                p, backends, src, dst, inner_compute, ne);
850        } else {
851            switch (kernel_type) {
852                case GGML_TYPE_F32: {
853                    float * buf[GGML_CUDA_MAX_DEVICES] = {};
854                    for (int i = 0; i < n; ++i) {
855                        buf[i] = static_cast<float *>(tensors[i]->data);
856                    }
857                    ok = ggml_cuda_ar_allreduce_copy_outer<float, float>(
858                        p, backends, buf, buf, inner_compute, ne);
859                    break;
860                }
861                case GGML_TYPE_BF16: {
862                    nv_bfloat16 * buf[GGML_CUDA_MAX_DEVICES] = {};
863                    for (int i = 0; i < n; ++i) {
864                        buf[i] = static_cast<nv_bfloat16 *>(tensors[i]->data);
865                    }
866                    ok = ggml_cuda_ar_allreduce_copy_outer<nv_bfloat16, nv_bfloat16>(
867                        p, backends, buf, buf, inner_compute, ne);
868                    break;
869                }
870                case GGML_TYPE_F16: {
871                    half * buf[GGML_CUDA_MAX_DEVICES] = {};
872                    for (int i = 0; i < n; ++i) {
873                        buf[i] = static_cast<half *>(tensors[i]->data);
874                    }
875                    ok = ggml_cuda_ar_allreduce_copy_outer<half, half>(
876                        p, backends, buf, buf, inner_compute, ne);
877                    break;
878                }
879                default:
880                    GGML_ASSERT(false);
881            }
882        }
883    } else {
884        // host_buf carries T_wire-typed data; max_chunk_elems is the count that
885        // fits in one host_buf at the wire size.
886        const size_t max_chunk_elems = p->buf_bytes / type_size;
887        const size_t input_type_size = ggml_type_size(input_type);
888
889        // Chunked kernel path runs entirely on the caller's compute stream:
890        // since AR is a barrier here, same-stream ordering subsumes any
891        // cross-stream event handshake that the copy-engine path needs, and
892        // skips the cross-stream scheduling overhead that was hurting the
893        // small-tensor (tg) latency on the AR-stream variant.  Only ev.ker is
894        // still recorded at end-of-AR for acquire_slot's pool-wraparound check.
895        for (int64_t chunk_start = 0; chunk_start < ne; chunk_start += (int64_t) max_chunk_elems) {
896            const size_t remaining_elems = (size_t) (ne - chunk_start);
897            const size_t chunk_elems = remaining_elems < max_chunk_elems ? remaining_elems : max_chunk_elems;
898            const size_t chunk_dst_bytes  = chunk_elems * input_type_size;
899
900            const auto [slot, token] = ggml_cuda_ar_acquire_slot(p);
901            const bool last_chunk = chunk_start + (int64_t) chunk_elems == ne;
902
903            for (int i = 0; i < n; ++i) {
904                const int peer = 1 - i;  // valid for n == 2 only
905                ggml_cuda_set_device(p->devices[i]);
906                auto * cuda_ctx = static_cast<ggml_backend_cuda_context *>(backends[i]->context);
907                GGML_ASSERT(cuda_ctx->device == p->devices[i]);
908                cudaStream_t stream = cuda_ctx->stream();
909
910                char * data = static_cast<char *>(tensors[i]->data) + chunk_start * (int64_t) input_type_size;
911
912                // Match NCCL/meta-backend semantics: inactive shards contribute
913                // zeros.  On the BF16 path the F32 tensor data was already
914                // zeroed up-front (above), so per-chunk zeroing isn't needed.
915                if (!compute_flag[i] && !use_bf16) {
916                    CUDA_CHECK(cudaMemsetAsync(data, 0, chunk_dst_bytes, stream));
917                }
918
919#define LAUNCH_AR_KERNEL(T_dst, T_wire) \
920                ggml_cuda_ar_kernel<T_dst, T_wire><<<dim3(GGML_CUDA_AR_KERNEL_BLOCKS), dim3(256), 0, stream>>>( \
921                    reinterpret_cast<const T_dst *>(data), \
922                    reinterpret_cast<T_dst *>(data), \
923                    reinterpret_cast<T_wire *>(p->host_buf[i].dev + (size_t) slot * p->buf_bytes), \
924                    reinterpret_cast<const T_wire *>(p->host_buf[peer].dev + (size_t) slot * p->buf_bytes), \
925                    static_cast<int>(chunk_elems), \
926                    ggml_cuda_ar_arrival_ptr(p, slot, i), \
927                    ggml_cuda_ar_arrival_ptr(p, slot, peer), \
928                    token)
929
930                if (use_bf16) {
931                    GGML_ASSERT(input_type == GGML_TYPE_F32);
932                    LAUNCH_AR_KERNEL(float, nv_bfloat16);
933                } else {
934                    switch (input_type) {
935                        case GGML_TYPE_F32:  LAUNCH_AR_KERNEL(float,       float);       break;
936                        case GGML_TYPE_F16:  LAUNCH_AR_KERNEL(half,        half);        break;
937                        case GGML_TYPE_BF16: LAUNCH_AR_KERNEL(nv_bfloat16, nv_bfloat16); break;
938                        default: GGML_ASSERT(false);
939                    }
940                }
941
942#undef LAUNCH_AR_KERNEL
943                CUDA_CHECK(cudaGetLastError());
944
945                if (last_chunk) {
946                    CUDA_CHECK(cudaEventRecord(p->ev_pool[i][slot].ker, stream));
947                }
948            }
949        }
950    }
951
952    return ok;
953}
954
955#else // defined(GGML_USE_HIP) || defined(GGML_USE_MUSA)
956
957// HIP and MUSA lack the host-mapped pinned-memory APIs (cudaHostAllocPortable
958// / cudaHostAllocMapped / cudaHostGetDevicePointer) and __nanosleep that this
959// implementation relies on, so the internal AllReduce is a CUDA-only feature.
960// The dispatcher in ggml-cuda.cu treats a nullptr pipeline as "init failed"
961// and silently falls back to the meta backend's generic AllReduce.
962ggml_cuda_ar_pipeline * ggml_cuda_ar_pipeline_init(const int *, size_t) {
963    return nullptr;
964}
965void ggml_cuda_ar_pipeline_free(ggml_cuda_ar_pipeline *) {
966}
967bool ggml_cuda_ar_allreduce(ggml_cuda_ar_pipeline *, ggml_backend_t *, ggml_tensor **) {
968    return false;
969}
970
971#endif // !defined(GGML_USE_HIP) && !defined(GGML_USE_MUSA)