arxbig.cu 39 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848
  1. // arxbig: prefill-sized all-gather and reduce-scatter over RoCE for GB10, no NCCL.
  2. //
  3. // Same transport as arx_vllm.cu (pinned host buffers are GPU buffers at full
  4. // speed on GB10, and the ConnectX DMAs them without GPUDirect; a CPU proxy
  5. // posts RDMA writes when the GPU publishes a sequence number), sized for
  6. // sequence-parallel prefill: tens of MB per rank per call.
  7. //
  8. // all_gather: the GPU copies this rank's slice into its slot of out[seq % 3];
  9. // the proxy writes that slot into the same place in every peer's out[seq % 3],
  10. // then a flag; the GPU waits for every peer's flags. The result is a view of
  11. // out[seq % 3], valid until three more all-gathers have run.
  12. // reduce_scatter: the GPU copies its full input into send[seq & 1]; the proxy
  13. // writes chunk j into peer j's recv[seq & 1][rank], then a flag; the GPU waits
  14. // and sums the world chunks for this rank in rank order (fp32).
  15. //
  16. // Every write is split in half, one half per ConnectX root, each followed by
  17. // its own flag on that root's QP. Rank r sends to r+1, r+2, ... in turn.
  18. // Reuse is safe for the same reason as in arx: a peer can only reach seq + 2
  19. // (or + 3) after this rank posted seq + 1, which it does after its stream
  20. // finished with seq.
  21. //
  22. // Ring mode (see arx_vllm.cu): QPs go only to prev and next, one channel
  23. // each. Data for the rank two away goes half each way round the ring. Of each
  24. // root's share of a piece, the first half travels next-ward and the second
  25. // prev-ward. The rank in between forwards each piece as soon as its flag
  26. // arrives.
  27. // all_gather: a rank sends its whole slice to prev and to next. The rank
  28. // forwards the next-ward half of prev's slice to next and the prev-ward half
  29. // of next's to prev, from and to the same place in out[seq % 3]. Each port
  30. // carries 1.5 slices out per call. Relaying whole slices one way would put 2
  31. // on one port.
  32. // reduce_scatter: chunk r+1 goes to next, chunk r-1 to prev, and chunk r+2
  33. // half to each. Each neighbour stages its half in its own recv[seq & 1][self],
  34. // a slot a rank never otherwise uses, and forwards it into recv[seq & 1][r]
  35. // at r+2. Each port carries 2 slices out, the least possible when relays carry
  36. // raw data. The two relays through a rank carry opposite halves of each piece,
  37. // so they share the staging slot.
  38. // World 2 sends half of everything each way and relays nothing.
  39. // Ring flags are per (source, arrival channel, root), because data from rank
  40. // r+2 arrives on both channels. Dev::mask names the flags the GPU waits on.
  41. // Staged chunks have their own flags for the proxy. A relay takes the piece's
  42. // bounds from this rank's own publish of the same seq.
  43. // Reuse with relays: rank m reads out[seq % 3] or its staging slot to forward
  44. // seq. The sender writes there again at seq + 3 or seq + 2, after finishing
  45. // seq + 1. Finishing seq + 1 needs data from m's other neighbour. That
  46. // neighbour published it after finishing seq, and finishing seq needed m's
  47. // relay. The same chain holds m's own seq + 2 publish, and its new piece
  48. // bounds, behind the relay. At the final destination the mesh argument holds
  49. // as is. Relayed data for seq + 2 left its source after the source finished
  50. // seq + 1, and that needed the destination's seq + 1 data.
  51. #include <cuda_bf16.h>
  52. #include <cuda_fp8.h>
  53. #include <cuda_runtime.h>
  54. #include <infiniband/verbs.h>
  55. #include <algorithm>
  56. #include <atomic>
  57. #include <chrono>
  58. #include <cstdint>
  59. #include <cstdlib>
  60. #include <cstdio>
  61. #include <cstring>
  62. #include <thread>
  63. #include <vector>
  64. #include <ATen/cuda/CUDAContext.h>
  65. #include <torch/extension.h>
  66. #define CK(x) do { cudaError_t e_ = (x); if (e_ != cudaSuccess) { fprintf(stderr, "%s:%d %s\n", __FILE__, __LINE__, cudaGetErrorString(e_)); exit(1); } } while (0)
  67. #define IBCK(x) do { if (!(x)) { fprintf(stderr, "%s:%d %s failed: %s\n", __FILE__, __LINE__, #x, strerror(errno)); exit(1); } } while (0)
  68. namespace {
  69. constexpr int kMaxWorld = 8, kAgSlots = 3, kChunks = 8, kRing = 256;
  70. constexpr int kMaxDev = 4; // ring mode: 2 ports x 2 roots
  71. // Mesh: [op][src][root]. Ring: [op][src][channel][root], then [channel][root]
  72. // for staged reduce-scatter chunks.
  73. constexpr int kFlagWords = 2 * kMaxWorld * 4 + 4;
  74. // kRsPiece: one row range of one destination's chunk, published by a producer
  75. // kernel as soon as it has written those rows (see finalize_rs_kernel).
  76. enum Op : uint64_t { kAllGather = 1, kReduceScatter = 2, kRsPiece = 3 };
  77. // Each op goes out as kChunks pieces so the network starts on piece 0 while
  78. // the GPU still copies piece 1. A peer's flag for op seq, piece c, is
  79. // seq * 16 + c + 1: monotonic, and it covers every earlier piece on that QP.
  80. // Producers publish in any order, so each ring entry carries its own index + 1
  81. // (tag), written last; the proxy consumes entries strictly in index order.
  82. struct Work {
  83. uint64_t op, seq, chunk, slice, lo, hi;
  84. volatile uint64_t tag;
  85. };
  86. struct Ctl { // pinned; written by the GPU, read by the proxy
  87. Work ring[kRing];
  88. volatile uint64_t stop;
  89. };
  90. struct PeerInfo {
  91. uint32_t qpn[kMaxWorld][2]; // per channel (see State), per root
  92. uint8_t gid[kMaxDev][16];
  93. uint32_t mtu[kMaxDev]; // per device: the port's active MTU (enum ibv_mtu)
  94. uint64_t ag_addr, rs_addr, flag_addr;
  95. uint32_t ag_rkey[kMaxDev], rs_rkey[kMaxDev], flag_rkey[kMaxDev];
  96. };
  97. struct Dev {
  98. uint8_t* ag; // [kAgSlots][slot_bytes]
  99. uint8_t* rs_send; // [2][slot_bytes]
  100. uint8_t* rs_recv; // [2][world][slot_bytes / world]
  101. const volatile uint64_t* flag; // [2 ops][nslot], see kFlagWords
  102. Ctl* ctl;
  103. unsigned* blocks; // [kChunks * kMaxWorld] blocks done with each piece
  104. unsigned long long* work_next; // next ring index to hand out
  105. unsigned long long* ready; // highest flag_of(seq, piece) whose data has fully arrived
  106. int rank, world;
  107. int nslot; // flag words per op
  108. uint32_t mask; // the words of an op that carry data for this rank
  109. size_t slot_bytes;
  110. };
  111. __device__ __forceinline__ uint64_t ld_acquire_sys(const volatile uint64_t* p) {
  112. uint64_t v;
  113. asm volatile("ld.acquire.sys.global.u64 %0, [%1];" : "=l"(v) : "l"(p) : "memory");
  114. return v;
  115. }
  116. __device__ __forceinline__ uint4 ld_cv(const void* p) {
  117. uint4 v;
  118. asm volatile("ld.global.cv.v4.u32 {%0,%1,%2,%3}, [%4];" : "=r"(v.x), "=r"(v.y), "=r"(v.z), "=r"(v.w) : "l"(p));
  119. return v;
  120. }
  121. #ifndef RS_CACHED
  122. #define RS_CACHED 1
  123. #endif
  124. __device__ __forceinline__ uint4 ld_plain(const void* p) { return *reinterpret_cast<const uint4*>(p); }
  125. #if RS_CACHED
  126. #define RS_LOAD ld_plain
  127. #else
  128. #define RS_LOAD ld_cv
  129. #endif
  130. __device__ __forceinline__ uint64_t flag_of(uint64_t seq, int c) { return seq * 16 + c + 1; }
  131. // Piece c of one rank's slice: [lo, hi) bytes, 16-aligned.
  132. __device__ __forceinline__ void piece(size_t slice, int c, size_t& lo, size_t& hi) {
  133. const size_t step = (slice / kChunks + 15) & ~size_t(15);
  134. lo = min(slice, step * c);
  135. hi = c == kChunks - 1 ? slice : min(slice, step * (c + 1));
  136. }
  137. __device__ void publish(const Dev& d, uint64_t op, uint64_t seq, uint64_t chunk, uint64_t slice, uint64_t lo,
  138. uint64_t hi) {
  139. const unsigned long long idx = atomicAdd(d.work_next, 1ull);
  140. Work& it = d.ctl->ring[idx % kRing];
  141. it.op = op; it.seq = seq; it.chunk = chunk; it.slice = slice; it.lo = lo; it.hi = hi;
  142. __threadfence_system();
  143. it.tag = idx + 1;
  144. }
  145. // Copy piece c of every slice in `slices` (count of them, stride `slice`) from
  146. // src to dst; the last block to finish a piece publishes it.
  147. __device__ void copy_publish(const Dev& d, const uint8_t* src, uint8_t* dst, size_t slice, int slices, uint64_t op,
  148. uint64_t seq) {
  149. for (int c = 0; c < kChunks; ++c) {
  150. size_t lo, hi;
  151. piece(slice, c, lo, hi);
  152. const size_t len = hi - lo, total = len * slices;
  153. const size_t stride = (size_t)gridDim.x * blockDim.x * 16;
  154. if (src != dst)
  155. for (size_t i = ((size_t)blockIdx.x * blockDim.x + threadIdx.x) * 16; i < total; i += stride) {
  156. const size_t s_ = i / len, o = s_ * slice + lo + i % len;
  157. *reinterpret_cast<uint4*>(dst + o) = *reinterpret_cast<const uint4*>(src + o);
  158. }
  159. __threadfence_system();
  160. __syncthreads();
  161. // One counter per piece: blocks do not wait for each other between pieces.
  162. if (threadIdx.x == 0 && atomicAdd(&d.blocks[c], 1) == gridDim.x - 1) {
  163. d.blocks[c] = 0;
  164. publish(d, op, seq, c, slice, lo, hi);
  165. }
  166. }
  167. }
  168. // Wait until every flag in d.mask for op reaches seq, piece c. Only
  169. // block 0 reads the pinned flags the NIC writes; it relays progress through a
  170. // device word, since many blocks polling those lines slows the NIC's writes.
  171. __device__ void wait_piece(const Dev& d, uint64_t op, uint64_t seq, int c) {
  172. const uint64_t want = flag_of(seq, c);
  173. if (blockIdx.x == 0) {
  174. const volatile uint64_t* f = d.flag + (op == kAllGather ? 0 : d.nslot);
  175. if (threadIdx.x < d.nslot && (d.mask >> threadIdx.x & 1))
  176. while (ld_acquire_sys(f + threadIdx.x) < want) {}
  177. __syncthreads();
  178. if (threadIdx.x == 0) { __threadfence(); atomicMax(d.ready, (unsigned long long)want); }
  179. } else {
  180. if (threadIdx.x == 0)
  181. while (true) {
  182. unsigned long long g;
  183. asm volatile("ld.acquire.gpu.global.u64 %0, [%1];" : "=l"(g) : "l"(d.ready) : "memory");
  184. if (g >= want) break;
  185. }
  186. __syncthreads();
  187. }
  188. }
  189. __global__ void ag_kernel(Dev d, const uint8_t* __restrict__ in, size_t slice, uint64_t seq) {
  190. uint8_t* out = d.ag + (seq % kAgSlots) * d.slot_bytes;
  191. copy_publish(d, in, out + d.rank * slice, slice, 1, kAllGather, seq);
  192. wait_piece(d, kAllGather, seq, kChunks - 1);
  193. }
  194. // out [slice / 2] bf16 = sum over ranks of their chunk `rank` of in.
  195. __global__ void rs_kernel(Dev d, const uint8_t* __restrict__ in, size_t slice, __nv_bfloat16* __restrict__ out,
  196. uint64_t seq) {
  197. const int par = seq & 1;
  198. uint8_t* send = d.rs_send + par * d.slot_bytes;
  199. copy_publish(d, in, send, slice, d.world, kReduceScatter, seq);
  200. const uint8_t* recv = d.rs_recv + (size_t)par * d.slot_bytes;
  201. const size_t stride = (size_t)gridDim.x * blockDim.x * 16;
  202. for (int c = 0; c < kChunks; ++c) {
  203. size_t lo, hi;
  204. piece(slice, c, lo, hi);
  205. wait_piece(d, kReduceScatter, seq, c);
  206. for (size_t i = lo + ((size_t)blockIdx.x * blockDim.x + threadIdx.x) * 16; i < hi; i += stride) {
  207. float acc[8] = {};
  208. for (int src = 0; src < d.world; ++src) { // rank order: every rank rounds the same way
  209. const uint4 v = src == d.rank ? *reinterpret_cast<const uint4*>(send + (size_t)d.rank * slice + i)
  210. : RS_LOAD(recv + (size_t)src * slice + i);
  211. const __nv_bfloat16* b = reinterpret_cast<const __nv_bfloat16*>(&v);
  212. #pragma unroll
  213. for (int k = 0; k < 8; ++k) acc[k] += __bfloat162float(b[k]);
  214. }
  215. __align__(16) __nv_bfloat16 o[8];
  216. #pragma unroll
  217. for (int k = 0; k < 8; ++k) o[k] = __float2bfloat16(acc[k]);
  218. *reinterpret_cast<uint4*>(reinterpret_cast<uint8_t*>(out) + i) = *reinterpret_cast<const uint4*>(o);
  219. }
  220. }
  221. }
  222. // Rows [lo, hi) of piece k of a destination's Rr-row chunk.
  223. __device__ __host__ __forceinline__ void row_piece(int64_t Rr, int k, int64_t& lo, int64_t& hi) {
  224. lo = Rr * k / kChunks;
  225. hi = Rr * (k + 1) / kChunks;
  226. }
  227. // MoE output straight into the reduce-scatter send buffer:
  228. // row t = shared[t] + sum_k w[t, k] * y[pos[t, k]] (fp32, rounded once),
  229. // zero for padding rows t >= T. Blocks take rows piece-major (piece 0 of
  230. // every destination first) and the last block of a piece publishes it, so
  231. // the network starts while later rows are still being computed.
  232. // y8s != nullptr: y holds e4m3 bytes with one fp32 scale per (row, 128 columns).
  233. __global__ void finalize_rs_kernel(Dev d, const void* __restrict__ yv, const float* __restrict__ y8s,
  234. const int* __restrict__ pos, const float* __restrict__ w,
  235. const __nv_bfloat16* __restrict__ shared, int64_t T, int64_t Tpad, int H, int topk,
  236. uint64_t seq) {
  237. const __nv_bfloat16* y = reinterpret_cast<const __nv_bfloat16*>(yv);
  238. const uint8_t* y8 = reinterpret_cast<const uint8_t*>(yv);
  239. const int64_t Rr = Tpad / d.world;
  240. int64_t b = blockIdx.x, lo = 0, hi = 0;
  241. int k = 0;
  242. for (; k < kChunks; ++k) {
  243. row_piece(Rr, k, lo, hi);
  244. const int64_t n = (hi - lo) * d.world;
  245. if (b < n) break;
  246. b -= n;
  247. }
  248. const int j = (int)(b / (hi - lo));
  249. const int64_t t = j * Rr + lo + b % (hi - lo);
  250. __nv_bfloat16* dst = reinterpret_cast<__nv_bfloat16*>(d.rs_send + (seq & 1) * d.slot_bytes) + t * H;
  251. __shared__ int ps[16];
  252. __shared__ float ws[16];
  253. if (t < T && threadIdx.x < topk) {
  254. ps[threadIdx.x] = pos[t * topk + threadIdx.x];
  255. ws[threadIdx.x] = w[t * topk + threadIdx.x];
  256. }
  257. __syncthreads();
  258. for (int c = threadIdx.x * 8; c < H; c += blockDim.x * 8) {
  259. float acc[8] = {};
  260. if (t < T) {
  261. const uint4 sv = *reinterpret_cast<const uint4*>(shared + t * H + c);
  262. const __nv_bfloat162* sb = reinterpret_cast<const __nv_bfloat162*>(&sv);
  263. #pragma unroll
  264. for (int i = 0; i < 4; ++i) {
  265. const float2 f = __bfloat1622float2(sb[i]);
  266. acc[2 * i] = f.x; acc[2 * i + 1] = f.y;
  267. }
  268. for (int q = 0; q < topk; ++q) {
  269. if (y8s != nullptr) {
  270. const uint2 v = *reinterpret_cast<const uint2*>(y8 + (size_t)ps[q] * H + c);
  271. const float f = ws[q] * y8s[(size_t)ps[q] * (H / 128) + c / 128];
  272. const __nv_fp8x4_e4m3* q4 = reinterpret_cast<const __nv_fp8x4_e4m3*>(&v);
  273. #pragma unroll
  274. for (int i = 0; i < 2; ++i) {
  275. const float4 x4 = static_cast<float4>(q4[i]);
  276. acc[4 * i] += f * x4.x; acc[4 * i + 1] += f * x4.y; acc[4 * i + 2] += f * x4.z; acc[4 * i + 3] += f * x4.w;
  277. }
  278. continue;
  279. }
  280. const uint4 v = *reinterpret_cast<const uint4*>(y + (size_t)ps[q] * H + c);
  281. const __nv_bfloat162* b2 = reinterpret_cast<const __nv_bfloat162*>(&v);
  282. #pragma unroll
  283. for (int i = 0; i < 4; ++i) {
  284. const float2 f = __bfloat1622float2(b2[i]);
  285. acc[2 * i] += ws[q] * f.x;
  286. acc[2 * i + 1] += ws[q] * f.y;
  287. }
  288. }
  289. }
  290. uint4 o;
  291. __nv_bfloat162* ob = reinterpret_cast<__nv_bfloat162*>(&o);
  292. #pragma unroll
  293. for (int i = 0; i < 4; ++i) ob[i] = __floats2bfloat162_rn(acc[2 * i], acc[2 * i + 1]);
  294. *reinterpret_cast<uint4*>(dst + c) = o;
  295. }
  296. __threadfence_system();
  297. __syncthreads();
  298. const int idx = k * d.world + j;
  299. if (threadIdx.x == 0 && atomicAdd(&d.blocks[idx], 1) == (unsigned)(hi - lo) - 1) {
  300. d.blocks[idx] = 0;
  301. const uint64_t row = (uint64_t)H * 2;
  302. publish(d, kRsPiece, seq, (uint64_t)j * kChunks + k, Rr * row, lo * row, hi * row);
  303. }
  304. }
  305. // out [Rr, H] = sum over ranks, in rank order, of their rows for this rank,
  306. // piece by piece as the pieces arrive.
  307. __global__ void rs_finish_kernel(Dev d, __nv_bfloat16* __restrict__ out, int64_t Rr, int H, uint64_t seq) {
  308. const int par = seq & 1;
  309. const size_t row = (size_t)H * 2, slice = Rr * row;
  310. const uint8_t* send = d.rs_send + par * d.slot_bytes + d.rank * slice;
  311. const uint8_t* recv = d.rs_recv + (size_t)par * d.slot_bytes;
  312. const size_t stride = (size_t)gridDim.x * blockDim.x * 16;
  313. for (int k = 0; k < kChunks; ++k) {
  314. int64_t lo, hi;
  315. row_piece(Rr, k, lo, hi);
  316. wait_piece(d, kReduceScatter, seq, k);
  317. for (size_t i = lo * row + ((size_t)blockIdx.x * blockDim.x + threadIdx.x) * 16; i < hi * row; i += stride) {
  318. float acc[8] = {};
  319. for (int src = 0; src < d.world; ++src) {
  320. const uint4 v = src == d.rank ? *reinterpret_cast<const uint4*>(send + i)
  321. : RS_LOAD(recv + (size_t)src * slice + i);
  322. const __nv_bfloat16* b = reinterpret_cast<const __nv_bfloat16*>(&v);
  323. #pragma unroll
  324. for (int q = 0; q < 8; ++q) acc[q] += __bfloat162float(b[q]);
  325. }
  326. __align__(16) __nv_bfloat16 o[8];
  327. #pragma unroll
  328. for (int q = 0; q < 8; ++q) o[q] = __float2bfloat16(acc[q]);
  329. *reinterpret_cast<uint4*>(reinterpret_cast<uint8_t*>(out) + i) = *reinterpret_cast<const uint4*>(o);
  330. }
  331. }
  332. }
  333. // A channel is one QP per root to one peer. Mesh: channel j goes to rank j
  334. // over device r. Ring: channel 0 goes to prev over devices 0 and 1, channel 1
  335. // to next over devices 2 and 3, and meets the peer's channel on the other side.
  336. struct State {
  337. int rank = -1, world = 0, ndev = 0;
  338. bool ring = false;
  339. int prev = 0, next = 0, nslot = 0;
  340. int gid_idx[kMaxDev] = {};
  341. size_t slot_bytes = 0, rs_slot_bytes = 0;
  342. uint8_t *ag = nullptr, *rs_send = nullptr, *rs_recv = nullptr;
  343. uint64_t* flag = nullptr;
  344. Ctl* ctl = nullptr;
  345. ibv_context* ctx[kMaxDev] = {};
  346. ibv_pd* pd[kMaxDev] = {};
  347. ibv_mr *ag_mr[kMaxDev] = {}, *rs_send_mr[kMaxDev] = {}, *rs_recv_mr[kMaxDev] = {}, *flag_mr[kMaxDev] = {};
  348. ibv_cq* cq[kMaxDev] = {};
  349. ibv_qp* qp[kMaxWorld][2] = {};
  350. PeerInfo mine{};
  351. std::vector<PeerInfo> all;
  352. Dev dev{};
  353. bool connected = false;
  354. };
  355. State S;
  356. std::atomic<bool> g_err{false};
  357. int chan_peer(int c) { return S.ring ? (c ? S.next : S.prev) : c; }
  358. int chan_dev(int c, int r) { return S.ring ? c * 2 + r : r; } // device here
  359. int chan_rdev(int c, int r) { return S.ring ? (1 - c) * 2 + r : r; } // device at the peer
  360. bool post(int c, int r, uint64_t laddr, uint32_t lkey, uint64_t len, uint64_t raddr, uint32_t rkey, uint64_t flag_off,
  361. uint64_t* seq_word) {
  362. const int j = chan_peer(c);
  363. ibv_sge sg{laddr, (uint32_t)len, lkey};
  364. ibv_sge fs{(uint64_t)seq_word, 8, 0};
  365. ibv_send_wr w{}, f{}, *bad;
  366. w.opcode = IBV_WR_RDMA_WRITE; w.sg_list = &sg; w.num_sge = 1;
  367. w.wr.rdma.remote_addr = raddr; w.wr.rdma.rkey = rkey;
  368. f.opcode = IBV_WR_RDMA_WRITE; f.sg_list = &fs; f.num_sge = 1;
  369. f.send_flags = IBV_SEND_INLINE | IBV_SEND_SIGNALED;
  370. f.wr_id = (uint64_t)j;
  371. f.wr.rdma.remote_addr = S.all[j].flag_addr + flag_off; f.wr.rdma.rkey = S.all[j].flag_rkey[chan_rdev(c, r)];
  372. w.next = len ? &f : nullptr;
  373. ibv_send_wr* head = len ? &w : &f;
  374. if (int e = ibv_post_send(S.qp[c][r], head, &bad)) {
  375. fprintf(stderr, "arxbig rank %d: post to %d root %d failed: %s\n", S.rank, j, r, strerror(e));
  376. return false;
  377. }
  378. return true;
  379. }
  380. // Sends one piece [lo, hi) of op `it` to peer j, split over both roots, each
  381. // half followed by a flag write of `flag` on that root.
  382. bool send_piece(const Work& it, int j, uint64_t* flag) {
  383. const int rank = S.rank, world = S.world;
  384. const uint64_t len = it.hi - it.lo, half = (len / 2 + 15) & ~15ull;
  385. for (int r = 0; r < 2; ++r) {
  386. const uint64_t off = it.lo + (r ? half : 0), n = r ? len - half : half;
  387. bool ok;
  388. if (it.op == kAllGather) {
  389. const uint64_t at = (it.seq % kAgSlots) * S.slot_bytes + rank * it.slice + off;
  390. ok = post(j, r, (uint64_t)S.ag + at, S.ag_mr[r]->lkey, n, S.all[j].ag_addr + at, S.all[j].ag_rkey[r],
  391. (rank * 2 + r) * 8, flag);
  392. } else {
  393. const uint64_t par = it.seq & 1;
  394. ok = post(j, r, (uint64_t)S.rs_send + par * S.rs_slot_bytes + j * it.slice + off, S.rs_send_mr[r]->lkey, n,
  395. S.all[j].rs_addr + par * S.rs_slot_bytes + rank * it.slice + off, S.all[j].rs_rkey[r],
  396. (world * 2 + rank * 2 + r) * 8, flag);
  397. }
  398. if (!ok) return false;
  399. }
  400. return true;
  401. }
  402. // ---- ring mode ----------------------------------------------------------------
  403. // Byte offsets of a ring flag: data from src that arrived on channel ch, and
  404. // a staged reduce-scatter chunk that arrived on channel ch.
  405. uint64_t ring_slot(int op, int src, int ch, int r) { return ((uint64_t)op * S.nslot + (src * 2 + ch) * 2 + r) * 8; }
  406. uint64_t stage_slot(int ch, int r) { return ((uint64_t)2 * S.nslot + ch * 2 + r) * 8; }
  407. // Narrows [lo, hi) to half q. send_piece splits roots at the same point.
  408. void halve(uint64_t& lo, uint64_t& hi, int q) {
  409. const uint64_t h = ((hi - lo) / 2 + 15) & ~15ull;
  410. if (q) lo += h; else hi = lo + h;
  411. }
  412. // Piece [lo, hi) of this rank's all-gather slice, to both neighbours: whole at
  413. // world 4, half each way at world 2.
  414. bool ring_send_ag(const Work& it, uint64_t* flag) {
  415. const uint64_t at = (it.seq % kAgSlots) * S.slot_bytes + S.rank * it.slice;
  416. for (int c = 1; c >= 0; --c)
  417. for (int r = 0; r < 2; ++r) {
  418. uint64_t lo = it.lo, hi = it.hi;
  419. halve(lo, hi, r);
  420. if (S.world == 2) halve(lo, hi, 1 - c);
  421. const PeerInfo& p = S.all[chan_peer(c)];
  422. if (!post(c, r, (uint64_t)S.ag + at + lo, S.ag_mr[chan_dev(c, r)]->lkey, hi - lo, p.ag_addr + at + lo,
  423. p.ag_rkey[chan_rdev(c, r)], ring_slot(0, S.rank, 1 - c, r), flag))
  424. return false;
  425. }
  426. return true;
  427. }
  428. // Piece [lo, hi) of chunk j of this rank's reduce-scatter input. The chunk for
  429. // the rank two away goes half each way into the neighbours' staging slots.
  430. bool ring_send_chunk(const Work& it, int j, uint64_t* flag) {
  431. const int world = S.world, f = (j - S.rank + world) % world;
  432. const bool split = world == 2 || f == 2, stage = world == 4 && f == 2;
  433. const uint64_t base = (it.seq & 1) * S.rs_slot_bytes;
  434. for (int c = 1; c >= 0; --c) {
  435. if (!split && (c == 1) != (f == 1)) continue;
  436. const int peer = chan_peer(c);
  437. const PeerInfo& p = S.all[peer];
  438. for (int r = 0; r < 2; ++r) {
  439. uint64_t lo = it.lo, hi = it.hi;
  440. halve(lo, hi, r);
  441. if (split) halve(lo, hi, 1 - c);
  442. if (!post(c, r, (uint64_t)S.rs_send + base + j * it.slice + lo, S.rs_send_mr[chan_dev(c, r)]->lkey, hi - lo,
  443. p.rs_addr + base + (stage ? peer : S.rank) * it.slice + lo, p.rs_rkey[chan_rdev(c, r)],
  444. stage ? stage_slot(1 - c, r) : ring_slot(1, S.rank, 1 - c, r), flag))
  445. return false;
  446. }
  447. }
  448. return true;
  449. }
  450. // The proxy's relay state at world 4. Every rank runs the same
  451. // collectives in the same order, so this rank's own publishes say which seqs
  452. // are all-gathers or reduce-scatters and where each piece starts and ends.
  453. constexpr int kQ = 8;
  454. struct Bound { uint64_t seq, slice, lo, hi; };
  455. struct Relays {
  456. Bound bnd[2][kChunks] = {}; // by seq parity, piece
  457. uint64_t seqs[2][kQ] = {}, nseq[2] = {}; // per kind (0 all-gather, 1 reduce-scatter), in order
  458. struct { uint64_t i; int c; } at[2][2][2] = {}; // per kind, out channel, root: next seq index and piece
  459. };
  460. bool relay_note(Relays& R, const Work& it) {
  461. const int kind = it.op == kAllGather ? 0 : 1;
  462. const int c = (int)(it.op == kRsPiece ? it.chunk % kChunks : it.chunk);
  463. if (R.nseq[kind] == 0 || R.seqs[kind][(R.nseq[kind] - 1) % kQ] != it.seq) {
  464. for (int o = 0; o < 2; ++o)
  465. for (int r = 0; r < 2; ++r)
  466. if (R.nseq[kind] - R.at[kind][o][r].i >= kQ) {
  467. fprintf(stderr, "arxbig rank %d: relays fell %d collectives behind\n", S.rank, kQ);
  468. return false;
  469. }
  470. R.seqs[kind][R.nseq[kind]++ % kQ] = it.seq;
  471. }
  472. R.bnd[it.seq & 1][c] = {it.seq, it.slice, it.lo, it.hi};
  473. return true;
  474. }
  475. // Idle backoff (#55): keep the exact busy spin while there is work and for ARX_IDLE_SPIN_MS after it (default
  476. // 200 ms), then nap ARX_IDLE_NAP_US (default 200 us) per loop turn. ARX_IDLE_NAP_US=0 keeps the old spin.
  477. struct IdleNap {
  478. std::chrono::steady_clock::duration spin;
  479. std::chrono::microseconds nap;
  480. std::chrono::steady_clock::time_point last;
  481. IdleNap() {
  482. const char* s = std::getenv("ARX_IDLE_SPIN_MS");
  483. const char* n = std::getenv("ARX_IDLE_NAP_US");
  484. spin = std::chrono::milliseconds(s ? std::atol(s) : 200);
  485. nap = std::chrono::microseconds(n ? std::atol(n) : 200);
  486. last = std::chrono::steady_clock::now();
  487. fprintf(stderr, "arx idle nap: spin %ld ms, nap %ld us%s\n", (long)(s ? std::atol(s) : 200), (long)nap.count(),
  488. nap.count() <= 0 ? " (disabled = old spin)" : "");
  489. }
  490. void busy() { last = std::chrono::steady_clock::now(); }
  491. void idle() {
  492. if (nap.count() <= 0) return;
  493. if (std::chrono::steady_clock::now() - last > spin) std::this_thread::sleep_for(nap);
  494. }
  495. };
  496. bool relay_pending(const Relays& R) { // a relay this rank noted but has not forwarded yet
  497. for (int kind = 0; kind < 2; ++kind)
  498. for (int o = 0; o < 2; ++o)
  499. for (int r = 0; r < 2; ++r)
  500. if (R.at[kind][o][r].i < R.nseq[kind]) return true;
  501. return false;
  502. }
  503. // Forwards every piece whose flag has arrived, in order per stream. Out
  504. // channel 1 carries prev's data on to next (first halves), channel 0 next's
  505. // on to prev (second halves).
  506. bool relay_step(Relays& R) {
  507. const volatile uint64_t* flags = S.flag;
  508. for (int kind = 0; kind < 2; ++kind)
  509. for (int o = 0; o < 2; ++o)
  510. for (int r = 0; r < 2; ++r) {
  511. auto& st = R.at[kind][o][r];
  512. const int src = o ? S.prev : S.next, peer = chan_peer(o);
  513. const uint64_t in = (kind ? stage_slot(1 - o, r) : ring_slot(0, src, 1 - o, r)) / 8;
  514. while (st.i < R.nseq[kind]) {
  515. const uint64_t s = R.seqs[kind][st.i % kQ];
  516. const Bound& B = R.bnd[s & 1][st.c];
  517. uint64_t flag = s * 16 + st.c + 1;
  518. if (B.seq != s || flags[in] < flag) break;
  519. std::atomic_thread_fence(std::memory_order_acquire);
  520. uint64_t lo = B.lo, hi = B.hi;
  521. halve(lo, hi, r);
  522. halve(lo, hi, 1 - o);
  523. const PeerInfo& p = S.all[peer];
  524. const int d = chan_dev(o, r), rd = chan_rdev(o, r);
  525. bool ok;
  526. if (kind == 0) {
  527. const uint64_t at = (s % kAgSlots) * S.slot_bytes + src * B.slice;
  528. ok = post(o, r, (uint64_t)S.ag + at + lo, S.ag_mr[d]->lkey, hi - lo, p.ag_addr + at + lo, p.ag_rkey[rd],
  529. ring_slot(0, src, 1 - o, r), &flag);
  530. } else {
  531. const uint64_t base = (s & 1) * S.rs_slot_bytes;
  532. ok = post(o, r, (uint64_t)S.rs_recv + base + S.rank * B.slice + lo, S.rs_recv_mr[d]->lkey, hi - lo,
  533. p.rs_addr + base + src * B.slice + lo, p.rs_rkey[rd], ring_slot(1, src, 1 - o, r), &flag);
  534. }
  535. if (!ok) return false;
  536. if (++st.c == kChunks) { st.c = 0; ++st.i; }
  537. }
  538. }
  539. return true;
  540. }
  541. void proxy_loop() {
  542. uint64_t done = 0;
  543. static Work pend[kMaxWorld][kChunks]; // kRsPiece items that arrived early
  544. uint32_t pend_mask[kMaxWorld] = {}, next_k[kMaxWorld] = {};
  545. uint64_t cur_seq[kMaxWorld] = {};
  546. const int rank = S.rank, world = S.world;
  547. const bool relay = S.ring && world == 4;
  548. static Relays R;
  549. auto send = S.ring ? ring_send_chunk : send_piece;
  550. IdleNap nap;
  551. while (!S.ctl->stop) {
  552. bool moved = false;
  553. for (int d = 0; d < S.ndev; ++d) {
  554. ibv_wc wc[32];
  555. const int n = ibv_poll_cq(S.cq[d], 32, wc);
  556. if (n > 0) moved = true;
  557. for (int i = 0; i < n; ++i)
  558. if (wc[i].status != IBV_WC_SUCCESS) {
  559. fprintf(stderr, "arxbig rank %d: completion error %d\n", rank, wc[i].status);
  560. g_err = true;
  561. return;
  562. }
  563. }
  564. if (relay && !relay_step(R)) { g_err = true; return; }
  565. Work& slot = S.ctl->ring[done % kRing];
  566. if (slot.tag != done + 1) {
  567. if (moved || (relay && relay_pending(R))) nap.busy(); else nap.idle();
  568. continue;
  569. }
  570. nap.busy();
  571. std::atomic_thread_fence(std::memory_order_acquire);
  572. Work it;
  573. it.op = slot.op; it.seq = slot.seq; it.chunk = slot.chunk; it.slice = slot.slice; it.lo = slot.lo; it.hi = slot.hi;
  574. ++done;
  575. if (relay && !relay_note(R, it)) { g_err = true; return; }
  576. if (it.op == kAllGather && S.ring) {
  577. uint64_t flag = it.seq * 16 + it.chunk + 1;
  578. if (!ring_send_ag(it, &flag)) { g_err = true; return; }
  579. continue;
  580. }
  581. if (it.op != kRsPiece) { // one piece for every peer
  582. uint64_t flag = it.seq * 16 + it.chunk + 1; // inline: copied at post time
  583. for (int k = 1; k < world; ++k)
  584. if (!send(it, (rank + k) % world, &flag)) { g_err = true; return; }
  585. continue;
  586. }
  587. // Row piece `sub` of destination j's chunk. Peers read the flag as "all
  588. // pieces up to sub arrived", so pieces go out in order per destination.
  589. const int j = (int)(it.chunk / kChunks), sub = (int)(it.chunk % kChunks);
  590. if (j == rank) continue; // this rank's own rows never leave
  591. if (cur_seq[j] != it.seq) { cur_seq[j] = it.seq; next_k[j] = 0; pend_mask[j] = 0; }
  592. pend[j][sub] = it;
  593. pend_mask[j] |= 1u << sub;
  594. while (next_k[j] < kChunks && (pend_mask[j] >> next_k[j] & 1)) {
  595. const int k = next_k[j]++;
  596. uint64_t flag = it.seq * 16 + k + 1;
  597. if (!send(pend[j][k], j, &flag)) { g_err = true; return; }
  598. }
  599. }
  600. }
  601. Dev make_dev() {
  602. Dev d{};
  603. d.ag = S.ag; d.rs_send = S.rs_send; d.rs_recv = S.rs_recv; d.flag = S.flag; d.ctl = S.ctl;
  604. CK(cudaMalloc(&d.blocks, kChunks * kMaxWorld * 4)); CK(cudaMalloc(&d.ready, 8)); CK(cudaMalloc(&d.work_next, 8));
  605. CK(cudaMemset(d.blocks, 0, kChunks * kMaxWorld * 4)); CK(cudaMemset(d.ready, 0, 8)); CK(cudaMemset(d.work_next, 0, 8));
  606. d.rank = S.rank; d.world = S.world; d.slot_bytes = S.slot_bytes;
  607. d.nslot = S.nslot;
  608. for (int src = 0; src < S.world; ++src) {
  609. if (src == S.rank) continue;
  610. if (!S.ring) { d.mask |= 3u << (src * 2); continue; }
  611. // f hops from src to this rank going next-ward, which arrives on channel 0.
  612. const int f = (S.rank - src + S.world) % S.world;
  613. if (2 * f <= S.world) d.mask |= 3u << ((src * 2 + 0) * 2);
  614. if (2 * f >= S.world) d.mask |= 3u << ((src * 2 + 1) * 2);
  615. }
  616. return d;
  617. }
  618. } // namespace
  619. // Mesh: devs are root 0 and root 1. Ring: the port facing prev (root 0, root
  620. // 1), then the port facing next. gids: each device's GID index.
  621. // rs_slot_bytes: 0 leaves reduce_scatter unavailable and saves its buffers.
  622. py::bytes prepare(int64_t rank, int64_t world, std::vector<std::string> devs, std::vector<int64_t> gids, bool ring,
  623. int64_t slot_bytes, int64_t rs_slot_bytes) {
  624. TORCH_CHECK(S.rank < 0 && world >= 2 && world <= kMaxWorld && slot_bytes % (world * 16) == 0);
  625. TORCH_CHECK(rs_slot_bytes == 0 || rs_slot_bytes == slot_bytes, "arxbig: reduce_scatter uses the same slot size");
  626. TORCH_CHECK(!ring || world == 2 || world == 4, "arxbig ring mode supports world 2 or 4, not ", world,
  627. ": the ring reduce-scatter sends the rank two away half each way. Set VLLM_ARXBIG=0 to run arx alone");
  628. S.ndev = ring ? 4 : 2;
  629. TORCH_CHECK((int)devs.size() == S.ndev && (int)gids.size() == S.ndev, "arxbig: expected ", S.ndev, " devices");
  630. S.rank = rank; S.world = world; S.ring = ring; S.slot_bytes = slot_bytes; S.rs_slot_bytes = rs_slot_bytes;
  631. S.prev = (rank + world - 1) % world; S.next = (rank + 1) % world;
  632. S.nslot = (ring ? 4 : 2) * world;
  633. const size_t rs_alloc = rs_slot_bytes ? 2 * rs_slot_bytes : 4096;
  634. CK(cudaHostAlloc(&S.ag, kAgSlots * slot_bytes, cudaHostAllocMapped));
  635. CK(cudaHostAlloc(&S.rs_send, rs_alloc, cudaHostAllocMapped));
  636. CK(cudaHostAlloc(&S.rs_recv, rs_alloc, cudaHostAllocMapped));
  637. CK(cudaHostAlloc(&S.flag, kFlagWords * sizeof(uint64_t), cudaHostAllocMapped));
  638. CK(cudaHostAlloc(&S.ctl, sizeof(Ctl), cudaHostAllocMapped));
  639. memset(S.flag, 0, kFlagWords * sizeof(uint64_t));
  640. memset((void*)S.ctl, 0, sizeof(Ctl));
  641. int ndev;
  642. ibv_device** list = ibv_get_device_list(&ndev);
  643. const int acc = IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_WRITE;
  644. for (int d = 0; d < S.ndev; ++d) {
  645. S.gid_idx[d] = gids[d];
  646. for (int i = 0; i < ndev; ++i)
  647. if (devs[d] == ibv_get_device_name(list[i])) S.ctx[d] = ibv_open_device(list[i]);
  648. TORCH_CHECK(S.ctx[d], "arxbig: no RDMA device ", devs[d]);
  649. S.pd[d] = ibv_alloc_pd(S.ctx[d]); IBCK(S.pd[d]);
  650. S.ag_mr[d] = ibv_reg_mr(S.pd[d], S.ag, kAgSlots * slot_bytes, acc); IBCK(S.ag_mr[d]);
  651. S.rs_send_mr[d] = ibv_reg_mr(S.pd[d], S.rs_send, rs_alloc, acc); IBCK(S.rs_send_mr[d]);
  652. S.rs_recv_mr[d] = ibv_reg_mr(S.pd[d], S.rs_recv, rs_alloc, acc); IBCK(S.rs_recv_mr[d]);
  653. S.flag_mr[d] = ibv_reg_mr(S.pd[d], S.flag, kFlagWords * sizeof(uint64_t), acc); IBCK(S.flag_mr[d]);
  654. S.cq[d] = ibv_create_cq(S.ctx[d], 4096, nullptr, nullptr, 0); IBCK(S.cq[d]);
  655. ibv_gid gid;
  656. IBCK(ibv_query_gid(S.ctx[d], 1, S.gid_idx[d], &gid) == 0);
  657. memcpy(S.mine.gid[d], gid.raw, 16);
  658. ibv_port_attr port{};
  659. IBCK(ibv_query_port(S.ctx[d], 1, &port) == 0);
  660. S.mine.mtu[d] = port.active_mtu;
  661. S.mine.ag_rkey[d] = S.ag_mr[d]->rkey;
  662. S.mine.rs_rkey[d] = S.rs_recv_mr[d]->rkey;
  663. S.mine.flag_rkey[d] = S.flag_mr[d]->rkey;
  664. }
  665. ibv_free_device_list(list);
  666. for (int c = 0; c < (ring ? 2 : world); ++c)
  667. for (int r = 0; r < 2; ++r) {
  668. if (!ring && c == rank) continue;
  669. const int d = chan_dev(c, r);
  670. ibv_qp_init_attr ia{};
  671. ia.send_cq = S.cq[d]; ia.recv_cq = S.cq[d]; ia.qp_type = IBV_QPT_RC;
  672. ia.cap.max_send_wr = 1024; ia.cap.max_recv_wr = 1; ia.cap.max_send_sge = 1; ia.cap.max_recv_sge = 1;
  673. ia.cap.max_inline_data = 64;
  674. S.qp[c][r] = ibv_create_qp(S.pd[d], &ia); IBCK(S.qp[c][r]);
  675. ibv_qp_attr a{};
  676. a.qp_state = IBV_QPS_INIT; a.pkey_index = 0; a.port_num = 1; a.qp_access_flags = acc;
  677. IBCK(ibv_modify_qp(S.qp[c][r], &a, IBV_QP_STATE | IBV_QP_PKEY_INDEX | IBV_QP_PORT | IBV_QP_ACCESS_FLAGS) == 0);
  678. S.mine.qpn[c][r] = S.qp[c][r]->qp_num;
  679. }
  680. S.mine.ag_addr = (uint64_t)S.ag;
  681. S.mine.rs_addr = (uint64_t)S.rs_recv;
  682. S.mine.flag_addr = (uint64_t)S.flag;
  683. return py::bytes(reinterpret_cast<const char*>(&S.mine), sizeof(PeerInfo));
  684. }
  685. void connect(std::vector<std::string> infos) {
  686. TORCH_CHECK(S.rank >= 0 && !S.connected && (int)infos.size() == S.world);
  687. S.all.resize(S.world);
  688. for (int j = 0; j < S.world; ++j) {
  689. TORCH_CHECK(infos[j].size() == sizeof(PeerInfo), "arxbig: PeerInfo size mismatch");
  690. memcpy(&S.all[j], infos[j].data(), sizeof(PeerInfo));
  691. }
  692. for (int c = 0; c < (S.ring ? 2 : S.world); ++c)
  693. for (int r = 0; r < 2; ++r) {
  694. if (!S.ring && c == S.rank) continue;
  695. const PeerInfo& p = S.all[chan_peer(c)];
  696. const int d = chan_dev(c, r), rd = chan_rdev(c, r);
  697. ibv_qp_attr a{};
  698. a.qp_state = IBV_QPS_RTR; a.path_mtu = (ibv_mtu)std::min(S.mine.mtu[d], p.mtu[rd]);
  699. a.dest_qp_num = S.ring ? p.qpn[1 - c][r] : p.qpn[S.rank][r]; a.rq_psn = 0;
  700. a.max_dest_rd_atomic = 1; a.min_rnr_timer = 12;
  701. a.ah_attr.is_global = 1; a.ah_attr.port_num = 1; a.ah_attr.grh.hop_limit = 1;
  702. a.ah_attr.grh.sgid_index = S.gid_idx[d];
  703. memcpy(a.ah_attr.grh.dgid.raw, p.gid[rd], 16);
  704. IBCK(ibv_modify_qp(S.qp[c][r], &a, IBV_QP_STATE | IBV_QP_AV | IBV_QP_PATH_MTU | IBV_QP_DEST_QPN |
  705. IBV_QP_RQ_PSN | IBV_QP_MAX_DEST_RD_ATOMIC | IBV_QP_MIN_RNR_TIMER) == 0);
  706. if (S.ring || c == (S.rank + 1) % S.world)
  707. fprintf(stderr, "arxbig rank %d%s root %d: path MTU %d bytes (ours %d, peer %d)\n", S.rank,
  708. S.ring ? (c ? " next" : " prev") : "", r, 128 << a.path_mtu, 128 << S.mine.mtu[d], 128 << p.mtu[rd]);
  709. a.qp_state = IBV_QPS_RTS; a.timeout = 14; a.retry_cnt = 7; a.rnr_retry = 7; a.sq_psn = 0; a.max_rd_atomic = 1;
  710. IBCK(ibv_modify_qp(S.qp[c][r], &a, IBV_QP_STATE | IBV_QP_TIMEOUT | IBV_QP_RETRY_CNT | IBV_QP_RNR_RETRY |
  711. IBV_QP_SQ_PSN | IBV_QP_MAX_QP_RD_ATOMIC) == 0);
  712. }
  713. S.dev = make_dev();
  714. CK(cudaDeviceSynchronize());
  715. std::thread(proxy_loop).detach();
  716. S.connected = true;
  717. }
  718. static int blocks_for(size_t bytes) { return (int)std::max<size_t>(1, std::min<size_t>(48, bytes / (256 * 16 * 8))); }
  719. static uint64_t g_seq = 0; // collectives run in stream order, so the host numbers them
  720. // in: this rank's slice (any dtype, contiguous). Returns the gathered data as a
  721. // view of the pinned output slot, shaped [world * in.size(0), ...]; it stays
  722. // valid until three more all-gathers have run.
  723. torch::Tensor all_gather(torch::Tensor in) {
  724. TORCH_CHECK(S.connected && !g_err, "arxbig is not connected or its proxy failed");
  725. TORCH_CHECK(in.is_contiguous() && in.is_cuda() && in.dim() >= 1);
  726. const size_t slice = in.numel() * in.element_size();
  727. TORCH_CHECK(slice % 16 == 0 && slice * S.world <= S.slot_bytes, "arxbig: all_gather slice size");
  728. const uint64_t seq = ++g_seq;
  729. ag_kernel<<<blocks_for(slice), 256, 0, at::cuda::getCurrentCUDAStream()>>>(S.dev, (const uint8_t*)in.data_ptr(),
  730. slice, seq);
  731. std::vector<int64_t> shape(in.sizes().begin(), in.sizes().end());
  732. shape[0] *= S.world;
  733. return torch::from_blob(S.ag + (seq % kAgSlots) * S.slot_bytes, shape, in.options());
  734. }
  735. // in: bf16 [world * m, ...] contiguous. Returns bf16 [m, ...]: the sum over
  736. // ranks of each rank's chunk `rank`.
  737. torch::Tensor reduce_scatter(torch::Tensor in) {
  738. TORCH_CHECK(S.connected && !g_err, "arxbig is not connected or its proxy failed");
  739. TORCH_CHECK(S.rs_slot_bytes, "arxbig: prepared without reduce_scatter buffers");
  740. TORCH_CHECK(in.is_contiguous() && in.is_cuda() && in.scalar_type() == at::kBFloat16 && in.size(0) % S.world == 0);
  741. const size_t slice = in.numel() * 2 / S.world;
  742. TORCH_CHECK(slice % 16 == 0 && slice * S.world <= S.slot_bytes, "arxbig: reduce_scatter size");
  743. std::vector<int64_t> shape(in.sizes().begin(), in.sizes().end());
  744. shape[0] /= S.world;
  745. auto out = torch::empty(shape, in.options());
  746. const uint64_t seq = ++g_seq;
  747. rs_kernel<<<blocks_for(slice * S.world), 256, 0, at::cuda::getCurrentCUDAStream()>>>(
  748. S.dev, (const uint8_t*)in.data_ptr(), slice, (__nv_bfloat16*)out.data_ptr(), seq);
  749. return out;
  750. }
  751. // The pinned buffer the next reduce_scatter sends from, as a bf16 tensor of
  752. // `shape`. A producer that writes its output here saves reduce_scatter's copy;
  753. // it stays valid until that reduce_scatter.
  754. torch::Tensor rs_input(std::vector<int64_t> shape) {
  755. TORCH_CHECK(S.connected && S.rs_slot_bytes);
  756. int64_t n = 1;
  757. for (auto v : shape) n *= v;
  758. TORCH_CHECK(n * 2 <= (int64_t)S.slot_bytes, "arxbig: rs_input too large");
  759. const uint64_t par = (g_seq + 1) & 1;
  760. return torch::from_blob(S.rs_send + par * S.slot_bytes, shape,
  761. torch::TensorOptions().dtype(at::kBFloat16).device(torch::kCUDA));
  762. }
  763. // The MoE's final combine, reduce-scattered: finalize_rs_kernel writes and
  764. // publishes rows; rs_finish (called next on the same stream) returns this
  765. // rank's [Tpad / world, H] sum. y bf16 [R, H], pos int32 [T * topk],
  766. // w fp32 [T, topk] (routing weights, scale folded in), shared bf16 [T, H].
  767. int64_t moe_finalize_rs(torch::Tensor y, torch::Tensor pos, torch::Tensor w, torch::Tensor shared, int64_t T,
  768. int64_t Tpad, c10::optional<torch::Tensor> y8s) {
  769. TORCH_CHECK(S.connected && !g_err && S.rs_slot_bytes, "arxbig: reduce_scatter unavailable");
  770. const int H = y.size(1), topk = w.size(1);
  771. TORCH_CHECK(Tpad % S.world == 0 && Tpad / S.world >= kChunks && T <= Tpad && topk <= 16 && H % 8 == 0);
  772. TORCH_CHECK((size_t)Tpad * H * 2 <= S.slot_bytes, "arxbig: MoE output larger than a slot");
  773. TORCH_CHECK(y.is_contiguous() && pos.is_contiguous() && w.is_contiguous() && shared.is_contiguous());
  774. TORCH_CHECK(!y8s.has_value() || (y.scalar_type() == at::kByte && H % 128 == 0), "arxbig: y8s needs e4m3 y");
  775. const uint64_t seq = ++g_seq;
  776. finalize_rs_kernel<<<Tpad, 128, 0, at::cuda::getCurrentCUDAStream()>>>(
  777. S.dev, y.data_ptr(), y8s.has_value() ? y8s->data_ptr<float>() : nullptr, pos.data_ptr<int>(),
  778. w.data_ptr<float>(), (const __nv_bfloat16*)shared.data_ptr(), T, Tpad, H, topk, seq);
  779. return (int64_t)seq;
  780. }
  781. torch::Tensor rs_finish(int64_t seq, int64_t rows, int64_t H) {
  782. TORCH_CHECK((uint64_t)seq == g_seq, "arxbig: rs_finish must follow its producer directly");
  783. auto out = torch::empty({rows, H}, torch::TensorOptions().dtype(at::kBFloat16).device(torch::kCUDA));
  784. rs_finish_kernel<<<blocks_for(rows * H * 2 * S.world), 256, 0, at::cuda::getCurrentCUDAStream()>>>(
  785. S.dev, (__nv_bfloat16*)out.data_ptr(), rows, (int)H, (uint64_t)seq);
  786. return out;
  787. }
  788. bool failed() { return g_err; }
  789. PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
  790. m.def("prepare", &prepare);
  791. m.def("connect", &connect);
  792. m.def("all_gather", &all_gather);
  793. m.def("reduce_scatter", &reduce_scatter);
  794. m.def("rs_input", &rs_input);
  795. m.def("moe_finalize_rs", &moe_finalize_rs, py::arg("y"), py::arg("pos"), py::arg("w"), py::arg("shared"), py::arg("T"), py::arg("Tpad"), py::arg("y8s") = py::none());
  796. m.def("rs_finish", &rs_finish);
  797. m.def("failed", &failed);
  798. }