Skip to main content

API contract

Buffer(group=..., config=Config(...)) owns one NCCL communicator and one vendored DeepEP V2 ElasticBuffer. Initialization and close() are collective, outside CUDA capture. The caller owns a CPU Gloo process group, sets the current CUDA device first, and keeps the group alive until close.

Roles and tensors​

FFN ranks come first; each owns num_experts / ffn_ranks consecutive real experts. Attention ranks follow. Every rank uses identical configuration, exchange order, bucket, and read_host_counts policy. Only one unmatched dispatch per Buffer is allowed. IDs must be unique per token, in [0, num_experts), or -1 to skip. Use buffer.topk_idx_dtype; invalid real-role routes assert on the GPU, including on graph replay. All payload tensors are on the selected CUDA device.

OperationContract
source_dispatch(x, ids, weights, x_scale=None, *, bucket, read_host_counts=False)Attention: contiguous x [T,H], IDs/FP32 weights [T,K], T ≤ bucket ≤ capacity
worker_receive(*, bucket, read_host_counts=False)FFN: expert-major rows, weights, device expert prefix sums and opaque receipt
worker_reply(rows, receipt)FFN: contiguous BF16 [receive_rows,H], routing weight applied exactly once
source_collect(*, view=False)Attention: BF16 [T,H] summed over selected experts, in original token order

BF16 dispatch uses BF16 activations. FP8 uses float8_e4m3fn activations and FP32 block-128 scales [T,H/128]; strided scale views are supported. The library does not quantize or run GEMMs, except the opt-in lossy reply transport AFDEP_HYBRID_FP8_COMBINE below. H is a multiple of 256, K ≤ min(32,E), and real experts divide evenly over FFN ranks.

Receive prefix sum ends[e] is a device tensor: expert e occupies [align_up(ends[e-1], expert_alignment), ends[e]), with ends[-1]=0 for e=0. Padding rows are unspecified and must not be treated as live tokens. Return the entire receive shape to worker_reply, with valid expert rows in place. recv_counts_host, when requested, reports aligned per-expert counts, not raw live counts. Keep the receipt unchanged through the matching reply.

Graphs, streams and lifetime​

Capture complete dispatch/reply pairs with read_host_counts=False after warming up the kernels on all ranks. Keep captured tensors, Buffer and graph storage alive through every replay. Source tensor shapes and bucket specialize the capture. Change payloads and IDs in place; encode inactive choices as -1. This is capacity-bounded graph execution, not arbitrary-shape graph replay. Graph capture/instantiation and replay are different operations; replay still has a launch cost.

read_host_counts=True performs host synchronization for exact eager receive allocation on FFN. Otherwise receive capacity is conservative and device counts identify live rows. Attention has zero receive payload rows. FFN bounds count Attention sources only. Registered transport buffers are distinct from receive tensors, except for source_collect(view=True). With one FFN rank in one NVLink domain (and no more ranks than top-k), each token's only reply row is already its final output. That call then skips the reduce kernel and returns a strided [T,H] view of the registered combine inbox. Treat the view as read-only. It is valid until the next source_dispatch on the same Buffer is enqueued; copy it if the caller needs it longer. Other layouts return the ordinary result. In one NVLink domain registered buffers are sized by role: FFN dispatch inboxes for the Attention sources and, when ranks do not exceed top-k, Attention combine inboxes for the FFN ranks; every rank registers the same size. AFDEP_UNION_BUFFERS=1 keeps DeepEP's symmetric sizing as a measurement control.

Operations order their internal communication stream with the current CUDA stream using DeepEP events. Use caller stream dependencies before passing data produced on another stream. Outputs/receipts and user inputs must remain alive until dependent GPU work completes. A completed Python pair allows the next pair to be enqueued; device-side protocol and stream ordering protect reuse. Independent Buffers do not share ordering. close() synchronizes outstanding work and frees registered storage; destroy graphs before closing the Buffer.

Transport and tuning​

afdEP-comm keeps DeepEP's uniform logical expert numbering (Attention ranks own empty expert blocks, never legal routing targets) and its collective participation. The low-level connector interface is afdep.deepep.ElasticBuffer: pass ffn_ranks=M for afdEP-comm's transport, as Buffer does from Config. Without ffn_ranks it runs DeepEP V2 as upstream ships it, over a symmetric group; the benchmarks use that for the baseline, FastAFD's DeepEP M2N (benchmarks/fastafd.py).

allow_hybrid_mode, num_sms, num_qps, timeouts and RDMA traffic class pass through to the pinned transport. num_sms=0 selects approximately 24/132 of the smallest device's SM count; choose/tune explicitly for a workload. Both hybrid settings are validated in one NVLink domain and across NVLink domains joined by GIN. Across domains, role specialization runs the directional hybrid when every domain holds one role, and the union hybrid otherwise (design, sections 6 and 7). The directional hybrid always signals only the channels that carry tokens (the sparse directional hybrid, section 17 and sparse-hybrid.md); there is nothing to set. The opt-in mixed-role path (AFDEP_MIXED_HYBRID, section 18) changes the protocol when FFN ranks share a domain with Attention ranks. Set it, and any AFDEP_SHARED_* ablation control, alike on every rank before constructing the Buffer and keep them; construction gathers every rank's resolved values once and rejects a mismatch.

AFDEP_HYBRID_FP8_COMBINE=1 (section 19; validated on H200; GSM8K accuracy unchanged, no per-layer or next-token KL check yet) sends the directional hybrid's combine replies across NVLink domains as FP8: set it alike before construction, which latches it (later changes have no effect), checks it on every rank and reports it as Buffer.fp8_combine; it is off outside the directional hybrid. It is lossy: each FFN domain's BF16 partial is rounded to E4M3 with a power-of-two scale per 128 elements (error at most 2^-4 of the group's largest magnitude; tiny elements flush to zero; NaN stays in place; an infinity zeroes its group's finite elements unless they are near the BF16 range limit) before the source sums the domains in FP32. source_collect still returns BF16. AFDEP_HYBRID_FP8_COMBINE_MIN_BUCKET=b, latched and checked with it and reported as Buffer.fp8_combine_min_bucket, keeps BF16 replies for exchanges whose bucket is below b tokens (every rank passes the same bucket, so both roles agree on the row format). The default 0 means every bucket, negative values clamp to 0, and it reports 0 when FP8 is off.

Native compilation is lazy and process-locked. AFDEP_CACHE_DIR controls the extension cache; EP_JIT_CACHE_DIR controls DeepEP kernel JIT cache. Both must be writable. Use matching NCCL headers/runtime before starting Python; already loaded libraries cannot be replaced by changing environment variables later.