Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
2caf376
CollectiveX: make roundtrip mean dispatch->combine in every row
Oseltamivir Aug 3, 2026
807e13b
CollectiveX: mark the roundtrip contract change and correct its docs
Oseltamivir Aug 3, 2026
8dde2ec
CollectiveX: publish the entry-skew bracket beside the headline
Oseltamivir Aug 3, 2026
a7e8884
CollectiveX: size nccl-ep HT combine to its receive count, align MoRI…
Oseltamivir Aug 3, 2026
1c50e47
CollectiveX: state why nccl-ep excludes routing and what MoRI's confi…
Oseltamivir Aug 3, 2026
20960ad
CollectiveX: cache nccl-ep's low-latency gate wrapper per handle
Oseltamivir Aug 3, 2026
f17d37b
CollectiveX: match MoRI's production warps, and surface what makes ro…
Oseltamivir Aug 3, 2026
029c357
CollectiveX: charge the fp8 quantize production pays, as one fused ke…
Oseltamivir Aug 3, 2026
f78330a
CollectiveX: add FP8 dispatch to flashinfer-ep
Oseltamivir Aug 3, 2026
8668338
CollectiveX: fix contradictory methodology claims and three review fi…
Oseltamivir Aug 3, 2026
cf4b1fa
CollectiveX: run MoRI in the buffer mode its pinned warp count belong…
Oseltamivir Aug 3, 2026
ff71649
CollectiveX: put the mode in the artifact filename / CollectiveX:将 mo…
Oseltamivir Aug 3, 2026
7ff4533
CollectiveX: stop the per-case hang guard from killing slow-but-healt…
Oseltamivir Aug 3, 2026
71a8700
CollectiveX: state the BF16 stage shape precisely / CollectiveX:准确表述 …
Oseltamivir Aug 3, 2026
d1165a2
CollectiveX: blank-line spacing after the wheel-version helper / Coll…
Oseltamivir Aug 3, 2026
a8c0411
CollectiveX: say that the FP8 quantize charge is fixed per call, not …
Oseltamivir Aug 3, 2026
c4b034c
CollectiveX: trim my own comments back to the ambient density / Colle…
Oseltamivir Aug 3, 2026
b4f4e39
CollectiveX: record what the warps-and-buffer pairing actually measur…
Oseltamivir Aug 3, 2026
7314f9f
CollectiveX: exclude the B200 node with a thermally-clamped GPU / Col…
Oseltamivir Aug 3, 2026
161c319
CollectiveX: call FP8 staging what it is, and stop rehearsing it in w…
Oseltamivir Aug 3, 2026
151b007
CollectiveX: reject an allocation holding a throttled GPU / Collectiv…
Oseltamivir Aug 3, 2026
bb11716
CollectiveX: record the library version the combine model was chosen …
Oseltamivir Aug 3, 2026
2fd9130
CollectiveX: exclude the h100 node with a thermally clamped GPU / Col…
Oseltamivir Aug 3, 2026
a6328fe
CollectiveX: fix four defects adversarial review found in my own work…
Oseltamivir Aug 3, 2026
cda28f8
CollectiveX: record the temperature spread the gate cannot act on / C…
Oseltamivir Aug 3, 2026
fc07823
CollectiveX: correct eight claims a third review found wrong / Collec…
Oseltamivir Aug 3, 2026
c59956b
CollectiveX: correct the README's FP8 coverage and state the roundtri…
Oseltamivir Aug 4, 2026
a7fc2ef
CollectiveX: keep the sweep at version 1
Oseltamivir Aug 4, 2026
7e8c229
CollectiveX: let low-latency use MNNVL, cover it on GB, and measure s…
Oseltamivir Aug 4, 2026
b368eae
CollectiveX: stop the deepep-v2 LL ladder below its receive cap
Oseltamivir Aug 5, 2026
90b16f7
CollectiveX: record the measured and clamped ladder in the artifact
Oseltamivir Aug 5, 2026
88e1481
CollectiveX: pin DeepEP to main for the #642 low-latency fix, and unc…
Oseltamivir Aug 5, 2026
ca72509
CollectiveX: exclude b300-018, which has two dead RDMA rails
Oseltamivir Aug 5, 2026
1b3d223
CollectiveX: raise the per-case hang guard to 5400s for the post-#715…
Oseltamivir Aug 5, 2026
48ecbe8
CollectiveX: retract the #715 attribution for the EP16 slowdown
Oseltamivir Aug 5, 2026
be5d12b
CollectiveX: publish the chained pair period and per-op floors for ev…
Oseltamivir Aug 6, 2026
b21094b
CollectiveX: gate the chained regime with the oracle and demote the b…
Oseltamivir Aug 6, 2026
06cd50e
CollectiveX: stop charging the chain's own event records into the pai…
Oseltamivir Aug 6, 2026
a0983be
CollectiveX: fix a stale count in the chained-family pool comment
Oseltamivir Aug 6, 2026
a6b67dd
CollectiveX: move the B200 pool from the dgxc VMs to nscale bare metal
Oseltamivir Aug 6, 2026
564a79e
CollectiveX: let prepare_cache bootstrap a fresh pool's squash directory
Oseltamivir Aug 6, 2026
e60f12d
CollectiveX: sync docs and comments with the nscale pool and the two-…
Oseltamivir Aug 6, 2026
a522190
CollectiveX: document the sibling-chain settling offset in floor-vs-p…
Oseltamivir Aug 6, 2026
19f9a68
CollectiveX: release the chained-headline hold
Oseltamivir Aug 6, 2026
c1b2f00
CollectiveX: trim the chain-period prose and fold the duplicated stag…
Oseltamivir Aug 6, 2026
f2fd21a
CollectiveX: compress comments, docstrings, and doc prose to their fa…
Oseltamivir Aug 6, 2026
e29c955
CollectiveX: drop the opt-in period path and the chain_barrier valve
Oseltamivir Aug 6, 2026
6a69d47
CollectiveX: retire the spent headline hold and fold the staging gate…
Oseltamivir Aug 6, 2026
5e232f9
CollectiveX: retry a failed container import instead of losing the leg
Oseltamivir Aug 7, 2026
e262dac
CollectiveX: share the matrix rollout query and fold a duplicated cha…
Oseltamivir Aug 7, 2026
f336ab9
CollectiveX: consolidate ten test modules into five by subject
Oseltamivir Aug 7, 2026
a45643e
CollectiveX: delete MoRI's unreachable registered-input path and the …
Oseltamivir Aug 7, 2026
476eb9a
CollectiveX: split receive layout out of combine weighting, and colla…
Oseltamivir Aug 7, 2026
0469e79
CollectiveX: validate the chain's own output, and stop the gate claim…
Oseltamivir Aug 7, 2026
8100562
CollectiveX: quarantine the gb300 tray with the broken lock path, and…
Oseltamivir Aug 7, 2026
19303f6
CollectiveX: correct what each sign of period minus floor-sum means
Oseltamivir Aug 7, 2026
272a9f0
CollectiveX: name results by case_id, and stop summarize parsing the …
Oseltamivir Aug 7, 2026
3a12a40
CollectiveX: delete the combine_input_attr indirection
Oseltamivir Aug 7, 2026
b526816
CollectiveX: collapse _encode_dispatch into semantic_payload plus a v…
Oseltamivir Aug 7, 2026
8068c41
CollectiveX: give off-path precisions a field of their own, and fail …
Oseltamivir Aug 7, 2026
c85e8d7
CollectiveX: fail closed on a backend with no declared precisions
Oseltamivir Aug 7, 2026
bdd5f18
CollectiveX: make the timing profile an object, with one migration fu…
Oseltamivir Aug 7, 2026
b14a719
CollectiveX: hold staging constant in the chain-vs-drained comparison
Oseltamivir Aug 7, 2026
e1a05d9
CollectiveX: quarantine gb300 c005 too, and correct the claim that th…
Oseltamivir Aug 7, 2026
9041f33
CollectiveX: make the chain-output check report how far apart the out…
Oseltamivir Aug 7, 2026
114cce5
CollectiveX: report the chain-output check instead of gating on it
Oseltamivir Aug 7, 2026
1a706bd
CollectiveX: re-arm the chain-output gate — the magnitude says the di…
Oseltamivir Aug 7, 2026
3a7455a
CollectiveX: expose the fp8_consume hatch as a dispatch input, to loc…
Oseltamivir Aug 7, 2026
37fafcc
CollectiveX: scope the chain-output check to where the chain stages p…
Oseltamivir Aug 7, 2026
70425de
CollectiveX: order the h200 RDMA selector by GPU affinity, not by dev…
Oseltamivir Aug 7, 2026
bb7695d
CollectiveX: state what the chain-output null does not cover, and nam…
Oseltamivir Aug 7, 2026
bfd6b4b
CollectiveX: name the generations the version tag cannot separate, an…
Oseltamivir Aug 7, 2026
92d412b
CollectiveX: clamp the nccl-ep low-latency ladder around an unfixed u…
Oseltamivir Aug 7, 2026
39b96c9
CollectiveX: record the nccl-ep low-latency ladder clamp in the backe…
Oseltamivir Aug 7, 2026
6f1bb99
Revert "CollectiveX: order the h200 RDMA selector by GPU affinity, no…
Oseltamivir Aug 7, 2026
ee11e4e
CollectiveX: keep off-path precisions out of the default matrix
Oseltamivir Aug 8, 2026
133d723
CollectiveX: carry one sample object per ladder point
Oseltamivir Aug 8, 2026
57759e8
CollectiveX: drive the cap and import-retry tests through the code in…
Oseltamivir Aug 8, 2026
83a5b35
CollectiveX: move one measurement report to the docs, and close an op…
Oseltamivir Aug 8, 2026
4c2704d
CollectiveX: remove 749 lines of duplicated test classes I introduced
Oseltamivir Aug 8, 2026
de87b1e
CollectiveX: fold same-invariant tests into parameterized cases
Oseltamivir Aug 8, 2026
7f60974
CollectiveX: drop the tests the sweeps already prove
Oseltamivir Aug 8, 2026
653a8a5
CollectiveX: drop path_status, and fix the claims a review pass falsi…
Oseltamivir Aug 8, 2026
bfe8301
Merge remote-tracking branch 'origin/main' into cx-queries-chaingate
Oseltamivir Aug 10, 2026
afff66a
CollectiveX: install packaging in the test job
Oseltamivir Aug 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions .github/workflows/collectivex-sweep.yml
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@ on:
description: "Keep only shards in this comma-list of modes (normal, low-latency); low-latency is a decode-only EP8 addition; blank = all"
type: string
default: ''
fp8_consume:
description: "DIAGNOSTIC ONLY. 'dequant' puts the FP8 dequant back inside the chain, so staging is no longer hoisted; changes what is measured. Blank = native (the production model)."
type: choice
default: ''
options: ['', dequant]
concurrency:
group: cx-${{ github.ref }}-${{ inputs.backend }}-${{ inputs.only_sku }}
cancel-in-progress: false
Expand Down Expand Up @@ -132,6 +137,12 @@ jobs:
COLLX_SHARD_SKU: ${{ matrix.sku }}
COLLECTIVEX_CANONICAL_GHA: '1'
COLLECTIVEX_SOURCE_SHA: ${{ github.sha }}
# Diagnostic hatch, "native" on every normal run. Reaches the ranks because the container
# srun exports the submitting environment; EPBackend reads it at class-body evaluation.
# Defaulted here rather than passed through empty: the harness fails closed on an
# unrecognised value, and a blank input would set the variable to "" -- which is not
# "unset", so os.environ.get's default never applies and every leg would die at import.
CX_FP8_CONSUME: ${{ inputs.fp8_consume || 'native' }}
# Consolidated shards run one bounded build-group in one Slurm allocation;
# every production pool accepts 300 minutes. Allocations release as soon as
# the shard finishes.
Expand Down
9 changes: 7 additions & 2 deletions .github/workflows/test-collectivex.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,11 +38,16 @@ jobs:
run: |
python -m pip install --upgrade pip
pip install --index-url https://download.pytorch.org/whl/cpu torch
# ep_flashinfer's combine-model switch compares wheel versions with
# packaging.version; the benchmark image ships it, this runner does not.
pip install packaging

# Every skip in this suite is torch-gated, so a missing torch turns the oracle
# checks into silent passes. Fail here instead, where the cause is obvious.
- name: Verify torch is importable
run: python -c "import torch; print(torch.__version__)"
# packaging is not skip-gated at all — without it the FlashInfer combine-model
# tests ERROR, so name it here too rather than debugging it from a traceback.
- name: Verify torch and packaging are importable
run: python -c "import torch, packaging; print(torch.__version__)"

- name: Run unit tests
run: |
Expand Down
100 changes: 78 additions & 22 deletions experimental/CollectiveX/README.md

Large diffs are not rendered by default.

359 changes: 270 additions & 89 deletions experimental/CollectiveX/bench/ep_backend.py

Large diffs are not rendered by default.

99 changes: 70 additions & 29 deletions experimental/CollectiveX/bench/ep_deepep_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,20 @@
raise


# The source pin in runtime/common.sh is PR #605 head at the #630 fix; #640 is NOT in
# the fetched tree — runtime/stage.py applies it as a local rewrite before the build. This adapter no longer
# verifies the wheel's commit tag against the pin — it checks only that the loaded
# deep_ep exposes ElasticBuffer (the from-source PR #605 capability).
# The source pin in runtime/common.sh is upstream main, which carries #630 and #640. This
# adapter does not check the wheel's commit tag, only that the loaded deep_ep exposes
# ElasticBuffer.

# Low-latency receive sizing, deliberately two numbers: _LL_BUFFER_CAP sizes the pre-allocated
# receive (and so the transport footprint and fp8 dequant volume), _LL_LADDER_CAP bounds which
# token counts are measured. Equal today, but kept separate so the ladder can be clamped around a
# kernel defect at one rung without moving the footprint (see `create_buffer`).
_LL_BUFFER_CAP = 256
_LL_LADDER_CAP = 256
assert _LL_LADDER_CAP <= _LL_BUFFER_CAP <= 511, (
"the LL receive cap must fit NVSHMEM_QP_DEPTH=1024 ((cap + 1) * 2 <= 1024 => cap <= 511) "
"and the measured ladder must fit inside the buffer"
)


def _fp8_cast_helpers():
Expand All @@ -47,8 +57,8 @@ def _ll_dequant_static(fp8, scales):
``[num_local_experts, cap*num_ranks, hidden]`` = (32, 2048, 7168) at EP8). The low-latency
padded shape is constant on every dispatch, so a static (``dynamic=False``) compile fuses
to one FP32 pass (~0.5 ms, 6.3x, bit-identical to the dynamic kernel on valid slots). The
dequant runs in every timed component's warmup and samples (~hundreds of thousands of
calls over the profile), so the dynamic kernel's per-call overhead overran the leg's
dequant runs in every timed `stage` sample and once per other component's warm-up, so the
call count is large enough that the dynamic kernel's per-call overhead overran the leg's
wall-clock budget (all ranks SIGKILLed ~22 min in, no result); the static form brings FP8
low-latency inside the budget BF16 already meets. Padding slots decode to NaN in both
forms (FP8 padding bytes) — harmless, because combine is handle-indexed and never reads
Expand Down Expand Up @@ -128,7 +138,8 @@ class DeepEPV2Backend(EPBackend):
SUPPORTED_MODES = ("normal", "low-latency")
SUPPORTED_PRECISIONS = ("bf16", "fp8")
stage_device_work = False
combine_needs_redispatch = False
requires_fresh_pair = False
receive_layout = "token-rank"
combine_weight_semantics = "unweighted-rank-sum"

def __init__(self, args, rank, world_size, local_rank, device):
Expand All @@ -151,26 +162,31 @@ def __init__(self, args, rank, world_size, local_rank, device):
# deep_ep.utils.math) so the timed stage() does no module lookup in the
# measured region.
self._to_fp8, self._cast_back = _fp8_cast_helpers()
# Normal/HT quantises inside the timed dispatch with the compiled form; low-latency
# keeps the eager helper, whose bits its in-kernel quantise matches. See fused_quantize.
self._quant = self.fused_quantize(self._to_fp8)
if self.mode == "low-latency":
# Legacy Buffer IBGDA decode path: a distinct kernel family whose combine
# multiplies by the gate at the source (weighted), not an unweighted rank sum.
self.kernel_generation = "legacy-buffer-ll"
self.receive_layout = "token-expert"
self.combine_weight_semantics = "weighted-kernel-sum"
# LL result tensors are double-buffered and single-use per dispatch (upstream:
# "you cannot hold more than 2 low-latency kernels' result tensors at a single
# moment"), so every timed combine needs a fresh dispatch and every timed
# dispatch must be drained by its combine.
self.combine_needs_redispatch = True
self.dispatch_needs_combine_cleanup = True
self.requires_fresh_pair = True

def buffer_cap(self, args):
if self.mode == "low-latency":
# LL pre-allocates a fixed [num_local_experts, cap * num_ranks, hidden] receive
# buffer, so cap is a hard per-rank dispatch-slot bound (the harness clamps the
# decode ladder to it and reports the dropped point). 256 sits well under the
# default NVSHMEM_QP_DEPTH ceiling ((cap + 1) * 2 <= 1024 => cap <= 511 with
# NVSHMEM_QP_DEPTH=1024) and is adjustable if the decode ladder needs more.
return 256
# buffer, so the cap is a hard per-rank dispatch-slot bound; the harness clamps the
# ladder to it and records any dropped point in the artifact. This was clamped to 128
# while DeepEP's low-latency combine stochastically corrupted the T=256 rung on
# Blackwell (issue #700, fixed upstream by #642); the pin now tracks main so the
# ladder runs full. If the top rung reds again, check the pin before assuming the
# defect returned -- clamping here is the containment lever either way.
return _LL_LADDER_CAP
return None

def create_buffer(self, spec):
Expand All @@ -180,6 +196,16 @@ def create_buffer(self, spec):
args, world_size = self.args, self.world_size
self.max_tokens = spec.max_tokens_per_rank
if self.mode == "low-latency":
# Size the LL buffer from the fixed cap, not from the clamped ladder: the receive
# footprint sets both the transport's memory traffic and the fp8 dequant volume
# (`_ll_recv_bf16` converts the whole padded receive), so following the ladder would
# shift every retained rung and break comparability with the published series.
if spec.max_tokens_per_rank > _LL_BUFFER_CAP:
raise RuntimeError(
f"low-latency ladder maximum {spec.max_tokens_per_rank} exceeds the LL "
f"buffer cap {_LL_BUFFER_CAP}"
)
self.max_tokens = _LL_BUFFER_CAP
self._create_ll_buffer(spec)
return
_require_runtime()
Expand Down Expand Up @@ -242,21 +268,37 @@ def _create_ll_buffer(self, spec):
raise RuntimeError(
"invalid DeepEP LL runtime: deep_ep.Buffer.low_latency_dispatch is absent"
)
# Verified pinned signatures (commit fa8a9b16, deep_ep/buffers/legacy.py):
# Verified pinned signatures (commit 01dc3aaa, deep_ep/buffers/legacy.py):
# Buffer.get_low_latency_rdma_size_hint(num_max_dispatch_tokens_per_rank,
# hidden, num_ranks, num_experts) -> int (staticmethod, line 175)
# hidden, num_ranks, num_experts) -> int (staticmethod, line 176)
# Buffer(group, num_nvl_bytes=0, num_rdma_bytes=0, low_latency_mode=False,
# num_qps_per_rank=24, allow_nvlink_for_low_latency_mode=True, ...) (line 33)
# num_qps_per_rank=24, allow_nvlink_for_low_latency_mode=True,
# allow_mnnvl=False, explicitly_destroy=False, ...) (line 33)
num_rdma_bytes = deep_ep.Buffer.get_low_latency_rdma_size_hint(
self.max_tokens, args.hidden, world_size, args.experts
)
kwargs = {}
# On an MNNVL rack the scale-up fabric is NVLink across trays, but the legacy Buffer
# defaults `allow_mnnvl=False` and a False there self-sets NVSHMEM_DISABLE_MNNVL, so
# leaving it unset runs the low-latency kernels over IBGDA on exactly the systems whose
# fast path is MNNVL. Keyed on the reported topology, not the SKU name.
if str(getattr(args, "scale_up_transport", "")) == "mnnvl":
import inspect
if "allow_mnnvl" in inspect.signature(deep_ep.Buffer.__init__).parameters:
kwargs["allow_mnnvl"] = True
else:
raise RuntimeError(
"MNNVL scale-up needs deep_ep.Buffer(allow_mnnvl=...); this wheel lacks it, "
"so the low-latency path would silently run over IBGDA"
)
self.buffer = deep_ep.Buffer(
self.group,
num_rdma_bytes=num_rdma_bytes,
low_latency_mode=True,
num_qps_per_rank=num_qps_per_rank,
allow_nvlink_for_low_latency_mode=True,
explicitly_destroy=True,
**kwargs,
)

def _ll_recv_bf16(self, recv_x):
Expand Down Expand Up @@ -286,18 +328,14 @@ def _topk_idx_dtype(self):
def semantic_payload(self, x):
if not self._fp8:
return x
return self._cast_back(*self._to_fp8(x))
# Same callable the wire uses, so oracle and sender cannot disagree by construction.
return self._cast_back(*self._quant(x))

def _encode_dispatch(self, x):
if not self._fp8:
return x, None
if self.mode == "low-latency":
# low_latency_dispatch takes BF16 x and casts to e4m3fn inside the kernel, so
# send x unquantized; expose the host round-trip as the oracle semantic so the
# combine expectation models the FP8 transport (same as semantic_payload).
return x, self._cast_back(*self._to_fp8(x))
quantized = self._to_fp8(x)
return quantized, self._cast_back(*quantized)
def _validate_quantizer(self, x):
# Low-latency keeps the eager quantize (fused_quantize returns it unchanged), so
# _quant IS _to_fp8 there and there is nothing to cross-check.
if self._fp8 and self.mode != "low-latency":
self.assert_quantize_identity(self._to_fp8, self._quant, x)

def _ll_dispatch(self, p):
# Verified pinned signature (legacy.py:553):
Expand All @@ -322,8 +360,11 @@ def _ll_dispatch(self, p):
def dispatch(self, p):
if self.mode == "low-latency":
return self._ll_dispatch(p)
# Quantise here, not in make_problem: production runs one fused bf16->fp8 kernel per
# forward pass immediately before this collective, so the timed window must contain it.
dispatch_x = self._quant(p.dispatch_x) if self._fp8 else p.dispatch_x
recv_x, recv_topk_idx, recv_topk_weights, handle, _ = self.buffer.dispatch(
p.dispatch_x,
dispatch_x,
topk_idx=p.topk_idx,
topk_weights=p.topk_weights,
num_experts=self.args.experts,
Expand Down
Loading