Repository navigation
operatorx: multi-device gemm/moe (tp/dp/ep) under InferenceX recipes - #3526
Conversation
|
Sorry, over the weekend, there was 2 major refactors to clean up the technical debt accumalated over the past 11 months of moving at the speed of light. We don't see any major refactors in the forthseeable future besides cleaning up AMD multinode AgentX pile of bash. As much, due to the refactors, u would need to ask your agent to rebase from remote main@latest. Thank you in advance for ur understanding |
72e4ae3 to
a0ee5b8
Compare
82f35b9 to
894251b
Compare
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
f3e1e77 to
b39576f
Compare
…ion config vLLM rejects max_cudagraph_capture_size given both as its own argument and in the compilation config, which a non-eager engine now receives. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Attention sets enable_jit_warmup only where the image's KernelConfig has it; a MoE case on an image whose vLLM lacks the API the generic block uses is unsupported, not an error (the DeepSeek-V4 DEP recipe image runs vLLM 0.17). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…don't split along it An attention case on the DeepSeek-V4 DEP recipe (DP attention, MegaMoE experts) kept the recipe's MoE backend but lost its expert parallelism, which MegaMoE requires. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…point's width Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
|
Claude finished @hbarclay's task in 2m 30s —— View job Review of PR #3526: multi-device GEMM/MoE (TP/DP/EP)
Most branches that launch collectives now check that every rank agrees before continuing: build and run go through 1. 🟡 2. 🟡 3. 🟡 This PR is already merged into |
| @@ -328,7 +312,7 @@ def measure(op, time_once) -> tuple[float, dict]: | |||
| _dump(op, attempt, summary, samples) | |||
| if best is None or median_us < best[0]: | |||
| best = (median_us, summary, sleep_s) | |||
There was a problem hiding this comment.
🟡 WARNING: The ranks block can describe a different attempt than latency_us.
Why it matters: Each time_once call goes through ranks.iterations(), and that overwrites ranks.LAST. This loop keeps the best attempt's median, but ranks.summary() (called later in runners/*/runner.py) reads LAST, which holds the last attempt. So after a throttle retry where the last attempt isn't the best one, ranks.latency_us_min, skew_us and per_rank[].latency_us come from another run than the reported latency_us. For example, latency_us_min can end up larger than latency_us.
Fix: Save LAST together with the best attempt and restore it after the loop:
if best is None or median_us < best[0]:
best = (median_us, summary, sleep_s, dict(ranks.LAST))
...
median_us, summary, sleep_s, last = best
ranks.LAST.clear()
ranks.LAST.update(last)There was a problem hiding this comment.
Beyond the inline findings, I also checked the recipe-selection fallback in recipes.py (find(), lines 76-84): when no variant matches a case's actual tp/dp/ep split, it does fall back to the first variant at any topology for that checkpoint, but this is not silent — ci.py records the outcome via the recipe_match field (ci.py:233), so a mismatched recipe is surfaced rather than hidden.
Extended reasoning...
This operatorx PR reworks multi-rank GEMM/MoE execution (CUDA-graph capture, rank-agreement barriers in ranks.py, vLLM engine integration) with a confirmed inline finding that a fault during CUDA-graph capture on one rank (linear.py _launcher, called outside any ranks.together block) can hang sibling ranks instead of failing the run cleanly. I additionally traced the recipes.py checkpoint-fallback path that another reviewer flagged as a candidate silent-mismatch bug, and confirmed ci.py surfaces the match quality via recipe_match rather than swallowing it, so that path is not an independent bug. No security-sensitive surface (auth/crypto/permissions) is touched; the main risk is the confirmed distributed-hang finding, which already warrants a human look.
| except Exception as e: | ||
| if _is_fault(e): | ||
| raise |
There was a problem hiding this comment.
🔴 In multi-rank runs, a CUDA/device fault during CUDA-graph capture on one rank hangs every other rank instead of failing the run. _launcher (called directly from run() in nvidia/runner.py:50, outside any ranks.together block) only calls ranks.agree(err is None) on the non-fault branch; when _is_fault(e) is true at line 312-313 it re-raises immediately without ever calling ranks.agree. Other ranks that succeeded capture then block forever inside their own ranks.agree() collective, which needs every rank to participate. Fix: make the fault path also participate in a rank-agreement collective (or wrap the whole launcher in ranks.together) before re-raising, for both call sites: linear.py:312-313 (shared by gemm and moe via moe.py's import) and attention.py:562-563.
Why this was flagged
Trigger: on an OOM or other device fault (torch.OutOfMemoryError, "CUDA error", "illegal memory", "device-side assert") during torch.cuda.graph() capture on one rank of a multi-rank TP/DP run, while sibling ranks capture successfully - plausible at the large batch/token sizes this PR's testlists exercise (gemm_parallel/moe_parallel at TP8/DP8). Entry: nvidia/runner.py:50 impl.launcher(ctx), called directly, not inside ranks.together. Bug: linear.py:312-313 if _is_fault(e): raise propagates the exception without calling ranks.agree, while other ranks proceed to linear.py:316 ranks.agree(err is None), a collective all_reduce that now never completes. Base branch had no multi-rank path at all, so this hang is new. ranks.py's own docstring states "Every rank must take the same branch wherever a collective launches, or the others hang" - this path violates that invariant. Same pattern in attention.py's _launcher (line ~562-566).
Verification: Severity: normal. The candidate is real. In _launcher (operatorx/runners/common/vllm/linear.py:311-316), the fault branch if _is_fault(e): raise (line 312-313) re-raises WITHOUT any cross-rank collective, whereas the normal branch reaches if not ranks.agree(err is None) (line 316), a blocking all_reduce (ranks.py:24-31). _launcher is called at nvidia/runner.py:50 from run(), which is…
…3526) * operatorx: multi-device gemm, moe and attention under InferenceX recipes Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * operatorx engine: the recipe's CUDA-graph cap moves into its compilation config vLLM rejects max_cudagraph_capture_size given both as its own argument and in the compilation config, which a non-eager engine now receives. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * operatorx: smoke_parallel, one case per op family and parallel split Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * operatorx vllm: images older than the pinned vLLM Attention sets enable_jit_warmup only where the image's KernelConfig has it; a MoE case on an image whose vLLM lacks the API the generic block uses is unsupported, not an error (the DeepSeek-V4 DEP recipe image runs vLLM 0.17). Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * operatorx engine: expert parallelism stays the recipe's for ops that don't split along it An attention case on the DeepSeek-V4 DEP recipe (DP attention, MegaMoE experts) kept the recipe's MoE backend but lost its expert parallelism, which MegaMoE requires. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> * operatorx attention: DeepSeek-V4-Pro's untimed experts keep the checkpoint's width Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: dense GEMM serving benchmark with integrated profiling methodology
Dense GEMM only (grouped GEMM staged separately): serving/agentic/sim
testlists, vLLM dense backend (fp8 W8A8 cutlass, bf16, bf16xfp8
weight-only with per-call activation quant in the timed region, nvfp4
with unsupported guards), sglang dense fp8, cluster registration.
Measurement methodology (replaces the previous timing loop and the
experimental opt-in profiler): event timing with wall-clock warmup
floor, pre-enqueued launch-gap shield, full L2 flush before every timed
iteration, median of timed iters; per-op kernel decomposition via
torch.profiler attached to every result by default (OPERATORX_PROFILE=0
opts out); nvtx/roctx marker mode + optional cache flush for external
hardware-counter collection (counter-run latencies are never timing
ground truth).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* operatorx: reduce to gemm-only baseline (NVIDIA + AMD)
Keep exactly what the current collections validated: the dense gemm op,
the NVIDIA vLLM backend, the AMD torch/hipBLASLt backend with the same
timing methodology as NVIDIA (warmup floor, launch-gap shield, per-iter
cache flush), shared profiling module, and one small serving testlist
(gemm_serving_8k1k_min). Everything else (attention, moe, collectives,
other backends/platforms/testlists) is removed here and will be re-added
incrementally on this structure.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* operatorx: fix trace retention for OPERATORX_PROFILE_TRACE_EVERY=1
The keep condition compared against a literal 1, which modulo 1 never
matches, so keep-every-trace runs silently kept none.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* operatorx: cold-cache profiling replays by default
Flush the full cache hierarchy before every profiling replay
(OPERATORX_PROFILE_FLUSH_MB now defaults to 512; 0 restores warm
back-to-back replays) and exclude the flush kernel from the per-kernel
decomposition by identity, reporting flush_kernels_excluded.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* operatorx: per-op GPU telemetry with power-throttle retry
Generic TelemetryProvider interface sampling fine-grained clocks, power
and throttle events on a background thread during the timed loop; NVML
implementation for NVIDIA (poll + driver sample-ring drain) and amdsmi
for AMD (gpu_metrics table with hardware throttle_status), plus a no-op
fallback. Each result carries metrics.telemetry (clock/power min/p50/max,
throttle reasons, attempts). If actual capping is observed (throttle
reason active while SM clock sits below rated boost - the power-cap
governor bit at boost clock is normal DVFS and does not trigger), the op
is re-measured with exponentially increased inter-kernel sleeps, at most
OPERATORX_THROTTLE_RETRIES (default 3) retries, keeping the best-median
attempt. Also sizes the AMD flush buffer to the full cache hierarchy
(ROCm reports only the per-XCD L2 slice).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* operatorx: FIXME(Harrison) - migrate NVML telemetry to DCGM later
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* operatorx: FIXME owner tag hbarclay
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* operatorx: fix AMD backend import, telemetry windowing and device mapping
- amd/backends/torch.py: drop the import of the removed attention runner
(the module failed to import) and its attention impls.
- telemetry: read NVML driver history only from the attempt's start, not
from provider creation; match the NVML/amdsmi device to the torch device
by UUID/PCI address (both ignore *_VISIBLE_DEVICES); record op identity
and attempt per sidecar line; move the throttle-retry loop into
telemetry.measure() so both runners share it.
- profiling: remove the OPERATORX_PROFILE_METRICS counter pass (inert in
the vLLM container); trim docstrings and comments across the runners.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: record all NVML/amdsmi clocks, temperatures and throttle counters
Each telemetry sample is now a dict: SM/graphics/memory/video clocks,
average and instantaneous power, GPU and HBM temperature, pstate and
utilization on NVIDIA (plus the driver's 20 ms power history); gfx,
memory and SoC clocks, hotspot/HBM temperature and activity on AMD.
NVIDIA windows also record deltas of the cumulative per-reason throttle
and perf-limit time counters and energy; the driver advances those in
100 ms steps, so average power is derived only for windows >= 1 s.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: GEMM through vLLM's own linear layers with explicit quantization schema
The gemm op now names storage dtypes (bf16/e4m3/e2m1/int4) and quantization
(scale_a, scale_b, scale dtypes). The vllm backend (NVIDIA and AMD) builds a
ReplicatedLinear under the matching checkpoint quant config, runs
process_weights_after_loading and times layer(x), so vLLM selects the kernel;
the chosen kernels, parameter dtypes and vLLM env are recorded per op.
Images follow InferenceX's vLLM recipes; AMD enables AITER.
Serving testlists are rewritten to the DeepSeek-V4-Pro checkpoint schemes
(FP8 128x128 ue8m0 blocks; compressor/indexer projections bf16), same shapes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: testlist generator takes GEMM dtypes from per-checkpoint role schemes
scripts/inferencex_testlist/schemes.py maps each InferenceX checkpoint and
layer role to the scheme vLLM runs (from config/quant configs and safetensors
headers). gemm.json is regenerated with the same (m, n, k) shapes; shapes the
current enumerator no longer reproduces take the schemes of the models whose
projections match them.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: GEMM operands as vLLM-style quant descriptors
a / b are {dtype, scale: {dtype, static, group: [rows, cols]}, scale2?,
symmetric?}, mirroring vLLM's QuantKey / ScaleDesc / GroupShape, with groups
over A [M, K] and B [N, K]. This replaces the flat scale strings: arbitrary
block shapes (e.g. DeepSeek-V4.1-Flash 32x32), second-level scales (NVFP4)
and asymmetric formats are now expressible, and the vLLM backend derives the
checkpoint quant config from the descriptors instead of a lookup table.
Testlists are converted entry for entry; op hashing is JSON-based so nested
args hash.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: plain descriptions of the GEMM operand schema
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: move shared runner modules into runners/common
telemetry, profiling and the vLLM linear backend are used by more than one
platform runner; runners/ now holds only the platform packages and common/.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: drop scripts/ from the tree; run docs use python -m operatorx
scripts/ (submit_run, pull_containers, consolidate_results, the testlist
generators) stays local for now. README and CLUSTERS.md describe launching
the entry point directly; references to tooling outside this project are
removed.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: restore upstream scripts/ and its docs
scripts/ (submit_run.py and the rest) is upstream code used by the CI
workflow; it goes back unchanged, along with the README / CLUSTERS.md text
describing it. This PR's generator changes stay out of the tree.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: moe_layer op schema
Describes one MoE layer as math and data only: shape, routed experts (with
gemm operand descriptors for x / w13 / w2 / a2, optional biases, latent width,
zero experts), router (gate dtype, scoring, top-k / grouped / hash selection,
bias, renormalize, scale, weight-on-input), activation, optional shared
experts, and the routing distribution. Kernels, dispatch, shared-expert
execution and graph use are left to the backend. No backend yet.
Also: tests/conftest.py so each test file imports operatorx on its own.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: backend-chosen CUDA graphs, per-op kernel timeline, vLLM backend fixes
- BackendImpl.launcher(ctx) returns what the runner times and profiles. The
vLLM backend replays a full CUDA graph captured at vLLM's next capture size
(batch padded as vLLM pads it) up to its max capture size and runs larger
batches eagerly, recording the choice in backend_meta.graph.
- profile gains per-replay span/busy/gap/overlap/streams and an op-relative
kernel timeline (stream, start, duration), so gaps and concurrency are
explicit.
- vLLM backend: enable the CUDA quant_fp8 op for block-scaled weights as
VllmConfig does in serving; MXFP4 W4A4 through Quark OCP-MX (the config AMD
MXFP4 checkpoints ship); allocation / device faults are errors, not
'unsupported'; set input/output_size_per_partition like the parallel
linears real models use.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: gemm operand 'input' dtype for pre-quantized operands
An operand's optional 'input' (default bf16) is the dtype it arrives in. When
it differs from 'dtype' the op includes the quantization; when equal the
operand is pre-quantized and the op starts at the matmul. The AMD torch FP8
path multiplies pre-quantized operands and now says so; the vLLM backend takes
bf16 input only.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: GPU spin before each profiled replay; vLLM context builds the EP group
The profiler replay now enqueues a spin after each flush (as the timing loop
does), so an eager op's timeline shows device time rather than host launch
gaps; the spin kernel is excluded from the breakdown. The vLLM context also
creates the size-1 expert-parallel group MoE layers need.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: record CUDA-graph use as metrics.cuda_graph
The backend launcher returns (callable, cuda_graph) and the runner stores the
boolean; the graph decision is no longer a backend_meta dict. The capture cap
defaults to vLLM's and can be set per run with OPERATORX_CUDA_GRAPH_MAX_TOKENS
(0 = never), since deployments set their own.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: ops/moe_layer.py -> ops/moe.py
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: drop the tests this PR added
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: make CI match the backends and testlists on this branch
- workflow and docs default to backends=vllm (there is no NVIDIA torch backend)
- containers.toml drops images for removed backends; the planner rejects a
backend with no module for the pool's vendor before any node is allocated
- AMD planner allowances drop aiter and attention
- restore gemm_perf (11 BF16 cases) in the operand-descriptor schema for the
documented smoke check
- CI docs: current case/shard counts, drop the removed attention and Kimi K3
MoE sections and stale backend lists
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: moe_layer_small testlist (one full-size layer per InferenceX MoE scheme)
20 single-device cases at tokens=1: DeepSeek-R1, DeepSeek-V4-Pro (incl. a
hash-routed layer), DeepSeek-V4.1-Flash, Qwen3.5-397B, Qwen3.8-Flash-Next,
GLM-5.2, Kimi-K3 and MiniMax-M3, each in its InferenceX checkpoints'
FP8/NVFP4/MXFP4/MXFP8 schemes, with values from the checkpoint configs and
safetensors headers. The AMD planner accepts moe_layer; the schema documents
the swigluoai and situ activation parameters.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx CI: counters mode (ncu / rocprofv3); launch ranks by path
- workflow input mode=timing|counters, recorded in each shard cell. Counters
runs do one marked profiler replay per op under ncu (the host's Nsight
Compute, mounted from /opt/nvidia) or rocprofv3 (12 counter passes on AMD);
raw counter files land in results/counters/. Latencies from these runs are
perturbed.
- ranks start as `python3 .../ci.py rank` and prepend the source dir to
PYTHONPATH: the ROCm vLLM nightly image sets its own PYTHONPATH, which
replaced the launcher's, so every MI355X shard failed to import operatorx.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: moe_layer vLLM backend
Builds a GateLinear router, FusedMoEFactory routed experts under the quant
config the expert descriptors imply, and a shared-expert MLP under its own
(AITER fused shared experts where vLLM enables them), then times the block on
bf16 hidden states, as a CUDA graph where vLLM would capture one. Reports each
submodule's quant method and the experts / prepare-finalize classes vLLM chose;
emulation paths are unsupported. Latent experts, hash routing, zero experts and
forced expert-load distributions are not wired yet.
Scheme mapping adds MXFP8, FP8 blocks with bf16 scales, and MXFP4 weight-only
(compressed-tensors). moe_layer router gains an optional logits dtype, set in
moe_layer_small from how vLLM routes each model.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: document CI counters mode and the moe_layer backend
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx CI: MI300X Slurm partition is compute-0
Matches runners/launch_mi300x-amd.sh; salloc on 'compute' is rejected there.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx CI: find the compute node's Nsight Compute for counters runs
Search /opt/nvidia/nsight-compute, /usr/local/cuda*/nsight-compute* and PATH on
the allocated node and mount the newest install; B200 nodes have none under
/opt/nvidia. The shard records what was found in ncu.log.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: moe_layer latent experts, hash routing, DSv4 FP4 experts, forced expert load
- latent experts (Kimi-K3): bf16 H->L down projection overlapped with the router
on an aux stream, vLLM's LatentMoERunner and KimiRoutedOutputTransform
(optional latent RMSNorm, new experts.latent_norm)
- hash routing (DeepSeek-V4 hash layers): token-id -> expert table fed to
FusedMoEFactory's hash_indices_table, seeded input ids
- DeepSeek-V4 FP4 experts: the checkpoint's fp8/ue8m0 config, which vLLM maps
to its MXFP4 MoE method
- routing.distribution balanced / zipf / single_hot through a custom routing
function (forced expert choice, the router's weights)
- activation.interleaved selects swigluoai vs swigluoai_uninterleave
- a vLLM custom op missing from this build is unsupported, not an error
- moe_layer_small: GLM-5.2 / Kimi-K3 route with one-group grouped top-k as their
configs do, MiniMax-M3 swigluoai beta, and 8 expert-load cases at 256 tokens
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: free the previous MoE layer before building the next
vLLM registers each MoE layer by name in the forward context, which kept every
built layer alive; H200 ran out of memory by the 256-token cases.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx CI: mount the node's Nsight Compute tree at its own path
The ncu wrapper looks for its install next to itself, so mounting only the
version directory broke it on H200.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx CI: pick the newest real Nsight Compute install over a cuda/bin wrapper
On H200 nodes /usr/local/cuda-12.9/bin/ncu sorted last but only points at
/opt/nvidia, which the container does not have.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: Qwen shared-expert gate applied inside the shared MLP, as vLLM's Qwen block does
Handing it to the MoE runner appends it to the router GEMM (E+1 logits), which
Blackwell's monolithic TRT-LLM experts reject; the runner takes it only with
fused shared experts.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx profiling: split replays at the spin kernel, not at any fill kernel
The profile loop issues flush (a FillFunctor zero_), spin, replay. Replays were split
at every FillFunctor, so an op that launches its own fill (vLLM MoE, FlashInfer MXFP4
GEMM) was cut into fragments: span/busy/overlap/timeline came from a fragment and the
op's own fills were dropped from the kernel breakdown. The flush is now the last fill
before each spin, and replays start at the spins. latency_us was not affected.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: runners/common/vllm/{linear,moe}.py; InferenceX recipe images and env per testlist entry
- vLLM-specific shared code moves under runners/common/vllm/.
- A testlist entry can name InferenceX recipes (configs/*-master.yaml keys). The CI
planner runs such an entry, on a pool matching a recipe's hardware and framework,
in its own shard with that recipe's image and the unconditional exports of its
single-node launch script (OPERATORX_RECIPE records which).
- moe_layer_small: Kimi-K3 names kimik3-fp4-mi355x-vllm-agentic-mtp.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx CI: fall back to the backend image when a recipe's image tag is gone
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx profiling: a platform-independent trace core with a per-platform adapter
The profiler splits into a platform-independent core (runners/common/trace.py:
interval math, per-kernel totals, the median-span replay) and a per-platform
adapter for capture and replay boundaries. The CUDA/ROCm path is unchanged;
checked event for event against the old code on 300 synthetic traces.
Also on the way:
- a layer whose quantized checkpoint came out with an unquantized method is
rejected: vLLM's mxfp4 has no linear kernel and was returning bf16
weights under an mxfp4 row.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* operatorx: rename op moe_layer -> moe
The "layer" suffix only distinguished it from the moe_gemm / moe_forward ops,
which are gone from this backend set. MoeLayerArgs -> MoeArgs, MOE_LAYER -> MOE,
testlists/moe_layer_small.json -> moe_small.json. The AMD planner still admits
moe_gemm, which the upstream planner test exercises.
The op type is part of the op identity, so MoE rows from earlier runs keep
"moe_layer" and do not match these; none are in the correlation DB.
CI.md's MoE section is brought up to date on the way: 28 cases, and forced
expert-load distributions, latent experts and hash routing are wired - only
zero experts remain unsupported.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: remove moe_gemm
The op is no longer registered, so the AMD planner allowlist is {"gemm", "moe"}
and the planner test that fed moe_gemm shapes through the AMD pool is deleted.
CLUSTERS.md names moe for the single-chip tests.
perf-changelog.yaml keeps its moe_gemm entry: it records what PR #3213 shipped.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: only gemm and moe - remove the other MoE variants
moe_forward, dispatch and combine had no registered op spec any more; their
backend implementations were dead code. With them goes
everything built around moe_forward's expert/tensor-parallel triple: the
planner's per-triple shard grouping, the OPERATORX_MOE_PARALLELISM filter in
main.py, and submit_run.py's per-combo fan-out, which now submits one job per
world size. Parallelism for the moe op will be designed with its schema.
The planner test keeps its chunking, world-size exclusion and shared-image
coverage with a world-size-4 gemm where the moe_forward shape was.
scripts/profile_sglang_moe.py imported an NVIDIA sglang backend that no longer
exists and is deleted; the MaxText notes in README/CLUSTERS served only
moe_forward.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx CI: request runners by their cluster: label
The sweep asked for the bare pool name. GB200 and GB300 runners carry only
cluster:gb200-nv / cluster:gb300-nv, so those shards could never be scheduled,
and the bare mi300x label also matches a site OperatorX is not set up for. The
planner now emits each shard's runner label beside its pool - the labels
InferenceX's benchmark workflows use - and runs-on requests that.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx CI: runners are named by their cluster: label only
The pool names (h100-dgxc, b300, mi300x, ...) are gone from the sweep. The
workflow's runner input offers the self-hosted labels themselves, the planner,
manifest, recovery and execution key on the label, and a shard id is the label
without its "cluster:" prefix. Vendor and per-hardware Slurm handling follow
the label's hardware family (cluster:b300-dsxe -> b300) instead of pool lists.
The one translation left is to the CollectiveX platform profile a label draws
its Slurm settings from: that shared registry is keyed by its own names, and
RUNNERS carries the mapping beside each label's cluster id. platforms.json's
AMD overlays are keyed by label.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: drop the InferenceX-matrix testlist generator
scripts/inferencex_testlist came over in the OperatorX import (#2236) and has
never run in CI; it reads .github/configs/*-master.yaml, which no longer exist,
and emitted op types operatorx no longer has. The checked-in testlists are the
working set for now.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: every case says where it comes from - sources and roles
Each testlist entry carries `sources`, the checkpoint ids whose layers run it,
and `name`, its roles in them - both lists, both carried into every result's op
and kept out of the op's identity, so a shape several models share stays one
case. The loader rejects an entry without them; they are empty for a shape from
no model.
gemm.json: 5,394 of 5,416 cases attributed by matching (n, k, operand scheme,
bias) against every projection of the checkpoints in InferenceX's matrix and of
the ones it has dropped (gpt-oss, Kimi-K2.5, MiniMax-M2.5), including the
unfused q/k/v an older enumeration emitted. M is the batch and is not matched.
The 22 left are 18 fp8/NVFP4 shapes whose dims fit DeepSeek-V4-Pro's shared
expert at TP8/16 but whose scales are not its checkpoint's, and 4 synthetic
8192^2 squares.
gemm_serving_*: deepseek-ai/DeepSeek-V4-Pro where a shape is one of its
projections or spans its hidden size (or the 4x-hidden mHC input); none for the
hidden-10240/12288 proxies. gemm_perf is synthetic. moe_small: the model id
moves from name to sources, and name is ["mlp"].
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: widen the MoE list to 34 checkpoints and sweep the batch
Coverage was 19 checkpoints at tokens=1 plus one 256-token distribution study.
It now spans 34 and six batch sizes.
New layer families, from each checkpoint's config.json:
- gpt-oss-120b: 128 experts, top-4, no shared expert, biased experts and the
clamped swiglu. AMD serves the same MXFP4 weights with static per-tensor
FP8 activations, which no single vLLM quantization config expresses; that
row is expected to come back unsupported and say so.
- MiniMax-M2.5: 256 experts, top-8, no shared expert, no routed scaling.
- Kimi-K2.5 / K2.6 in their NVFP4 and MXFP4 variants. The base K2.5
checkpoint is INT4 weight-only (config type "int"), which the operand
descriptors have no dtype for, so it is left out rather than approximated.
- GLM-5, 5.1 and 5.3, and Qwen3.5-397B-A17B unquantized.
GLM-5/5.1/5.2/5.3 and Kimi-K2.5/K2.6 are the same layer, so they fold into one
case each carrying every checkpoint in sources - the dashboard groups by model
without measuring the same shape four times.
tokens now covers 1, 8, 64, 512 and 2048 for one layer per architecture family,
beside the existing 256-token expert-load distributions: decode, small and
large continuous batches, and a prefill-sized chunk. 28 cases -> 68.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* operatorx MoE: biased MXFP4 experts go to vLLM's own mxfp4 method
gpt-oss ships MXFP4 experts with a per-expert bias, and vLLM serves those
through its 'mxfp4' quantization method. The same operand descriptors on a
checkpoint without bias (Kimi-K3) mean the compressed-tensors
mxfp4-pack-quantized config instead, and the descriptors cannot tell the two
apart, so the expert bias picks between them.
Routing gpt-oss to the compressed-tensors config gave "shape '[5760, 90]' is
invalid for input of size 529920" on Blackwell - the packed scale layout the
two configs expect differs - and on ROCm it fell through to Marlin, which is
not built there.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* operatorx: a gpt-oss-only MoE testlist
The six gpt-oss cases from moe_small, so the mxfp4-method fix can be measured
without re-running the other 62 on every pool.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
* operatorx: drop gpt-oss from the MoE lists for now
Removes the six gpt-oss cases from moe_small (68 -> 62), the gpt-oss-only
moe_gptoss testlist, and the bias-keyed switch to vLLM's mxfp4 method. The
H200/B300 rerun still failed (Marlin pads hidden to 3072 ahead of the 2880-wide
router) and ROCm has only an emulation kernel, so gpt-oss waits for a later pass.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx sweep: dispatch the OperatorX database ingest when a run finishes
The last job sends InferenceX-app an ingest-operatorx repository_dispatch with
the run id, as run-sweep.yml does for benchmark results. Runs with failed
shards are ingested too; their unrun cases read as missing.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: NVIDIA and AMD only
The runners, the shared profiling and vLLM layers, the cluster table, the
container manifest and the docs cover the NVIDIA and AMD platforms.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: validate every case against its op schema before it runs
GemmArgs/MoeArgs check operands, scales, experts, router and routing, but only
op_registry.validate() instantiates them and nothing called it, so a misspelled
or out-of-schema key reached the backend, which read it with dict.get and ran a
default instead. main() now validates each case first; one the schema rejects
is an error row, never a measurement. All 15,093 checked-in cases validate.
The strict-run test's gemm case carries full operands to match.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx telemetry: stop the sampler even when the timed replay raises
A kernel that faulted inside time_once() skipped provider.stop(), leaving the
5 ms sampling thread running into the next op's samples.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx CI: resolve a recipe's fallback image digest once
setdefault() evaluated the lookup for every shard of a recipe whose image was
pruned, and kept a first transient empty result forever, failing the plan.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx moe: operands a1/w1/w2/a2 directly on experts and shared
The MoE layer's operand descriptors sat in a 'quant' block under x/w13/w2/a2.
They describe the operand tensors, of which quantization is one attribute (an
unquantized layer still had a 'quant' block of bf16 operands), so they now sit
beside the dimensions, as gemm's a/b do. The names are the slots of vLLM's
FusedMoEQuantConfig: a1 the experts' input, w1 the fused gate/up weight, w2 the
down weight, a2 the intermediate activation. moe_small is regenerated, identical
apart from the keys.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: sources are <checkpoint>/<role> pairs; name is gone
A case's checkpoints and its roles were two separate lists, so a shape that is a
q_proj in one model and an o_proj in another could not say which was which. Each
entry of sources is now one layer that runs the case, e.g.
deepseek-ai/DeepSeek-V4-Pro/attn.wq_b; the role follows the last '/'. The loader
checks the org/model/role form and rejects the old name field.
The GEMM lists are re-annotated from the exact (model, role) pairs. DeepSeek-V4
roles now come from the checkpoints' own weight maps: the enumerator had modeled
DeepSeek-V4 as DeepSeek-V3 MLA (kv_a_proj_with_mqa, ...), which is not its
architecture. They are the real projections under the TP sharding and fusions of
vLLM's deepseek_v4.py (wq_a+wkv, shared w1+w3, compressor wkv+wgate), and they name
the indexer, compressor, router gate, mHC and lm_head (head) shapes that had no role.
Consequences:
- gemm: 106 cases lose their DeepSeek-V4 source; those shapes exist only in the MLA
model. 5,300 of 5,416 cases have sources.
- gemm_serving_8k1k_min: 812 of 1,505 have sources (proxy-model shapes have none).
98 DeepSeek-V4-sized shapes stay unlabeled rather than guessed: 320x7168 bf16,
14336x7168 fp8, and 896/1792-wide ones that look like MTP e_proj/h_proj shards.
- moe_small: each case names the MoE module of each checkpoint: mlp, ffn
(DeepSeek-V4) or block_sparse_moe (MiniMax, Kimi-K3).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx CI: import H100 DGXC images with the node's own enroot paths
The importer gives every enroot import private temp/data/runtime paths under
/tmp (B300 homes are node-local). On the H100 DGXC nodes /tmp cannot hold
overlay whiteouts, so the first operatorx import there failed
('failed to create opaque ovlfs whiteout ... Not supported'). A platform entry
can now set enroot_defaults to keep the node's configured paths, as InferenceX's
h100-dgxc launcher imports; cluster:h100-dgxc does.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: DeepSeek-V4, Kimi-K3 and MiniMax-M3 gemm/moe testlists over 1..64k tokens
GEMMs come from each checkpoint's weight map (every InferenceX precision variant,
every framework fusion, lm_head and router/mixer gates), 32 token counts from 1 to
65536. fp32-output gates run the matmul vLLM runs for them: torch.mm with an fp32
epilogue, or an fp32 matmul on an fp32 weight. The MoE input buffers now cover 64k tokens.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx CI: ingest input; test runs dispatch with ingest=false
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: attention module op schemas (mla, mla_dsa, dsv4_attn, gqa, qsa, gdn, kda)
One op type per attention module, timed whole from the post-norm hidden states to
the module output. Args are the module's sizes, cache/state dtypes, projection
operands, on/off steps and a (count, q, ctx) request batch; no backend yet.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: attention modules through vLLM's model runner (mla, gqa, gdn)
Each op builds a one- or two-layer model of the checkpoint family that has the
module (real config.json, the op's sizes, a shrunken MLP) with dummy weights; vLLM
allocates the KV cache and picks the attention backend. The batch is scheduled
through vLLM's model runner as requests with ctx tokens already computed (random,
format-valid cache contents), and the timed call is the module forward as the
decoder layer makes it, replayed as a CUDA graph where vLLM would use one.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: vLLM attention for Kimi-K3, GLM-5.x, Qwen3.8 and DeepSeek-V4 / V4.1
The cut-down models keep each family's layer arrangement where the KV cache layout or a
module's inputs depend on it: DeepSeek-V4-Pro C4A beside C128A, V4.1's six layer roles
(window, ratio-2 source and consumer, ratio-1 candidate source, consumer and shared
indexer), GLM's indexer layer before its top-k reuser, K3's KDA beside MLA. The timed
module is resolved per op; a reusing layer's capture sees its group's top-k already
converted, as it is in the full graph. vLLM rejecting a config is unsupported.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: attention testlists for DeepSeek-V4, Kimi-K3 and MiniMax-M3; checkpoint quantization
attn_deepseek_v4 (V4-Pro C4A with fp8 / mxfp4 indexer cache, C128A; V4.1's six layer
roles), attn_kimi_k3 (gated NoPE MLA, KDA with fp32 / bf16 state) and attn_minimax_m3
(dense layers) sweep decode (1-256 requests x 1k-128k ctx), prefill up to 32k tokens,
chunked prefill on 8k-128k ctx and a mixed batch. proj lists the checkpoint's quantized
projections; the backend applies that checkpoint's own quantization_config. The KV
cache is half the device; vLLM rejecting a batch (or a K3 fp8-cache prefill without an
fp8-query prefill backend) is unsupported.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx attention: replay in inference mode, allocate like the scheduler, lazy JIT
The module replay runs in inference mode like the runner's step (in-place ops on the
step's tensors failed outside it). Blocks are allocated for a request whose ctx tokens
are computed, so sliding-window caches keep only the window. vLLM compiles kernels on
first use instead of every shape at startup. vLLM failing to build or warm up a module
config is unsupported.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx CI: AMD pools also run the attention modules
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx attention: replay under vLLM's current config too (FlashInfer reads it)
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx attention: schedule under vLLM's current config, as the worker does
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: paths for the top-level layout; drop the removed generator's test
Rank launch and PYTHONPATH use /opx/source/operatorx, the test command runs
operatorx/tests. test_inferencex_generator.py tested scripts/inferencex_testlist,
which this branch removes.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx attention: ask backends for CUDA-graph support under vLLM's current config
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx attention: vLLM's default CUDA-graph capture ceiling, independent of linear
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* Revert "operatorx attention: vLLM's default CUDA-graph capture ceiling, independent of linear"
This reverts commit dbbb2f2: adapting attention to the multi-device work belongs
on that branch.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: cases run under InferenceX's srt-slurm recipes
operatorx/recipes.py finds the recipe InferenceX serves a case's source checkpoint with on
the runner's hardware (expanded by infx.srt_slurm's own selected_recipes); the planner
gives that shard the recipe's image, launch env and server arguments
(OPERATORX_ENGINE_ARGS). The attention engine takes the arguments through vLLM's
EngineArgs parser (the op's own KV and indexer dtypes on top) and the recipe's CUDA-graph
mode and sizes, replacing per-model settings. Replaces the configs/*-master.yaml
recipe lookup that the top-level layout broke. The plan job initializes srt-slurm and
srtctl's dependencies.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: multi-device gemm/moe (tp/dp/ep) under InferenceX recipes (#3526)
* operatorx: multi-device gemm, moe and attention under InferenceX recipes
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx engine: the recipe's CUDA-graph cap moves into its compilation config
vLLM rejects max_cudagraph_capture_size given both as its own argument and in the
compilation config, which a non-eager engine now receives.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: smoke_parallel, one case per op family and parallel split
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx vllm: images older than the pinned vLLM
Attention sets enable_jit_warmup only where the image's KernelConfig has it; a MoE case
on an image whose vLLM lacks the API the generic block uses is unsupported, not an error
(the DeepSeek-V4 DEP recipe image runs vLLM 0.17).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx engine: expert parallelism stays the recipe's for ops that don't split along it
An attention case on the DeepSeek-V4 DEP recipe (DP attention, MegaMoE experts) kept
the recipe's MoE backend but lost its expert parallelism, which MegaMoE requires.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx attention: DeepSeek-V4-Pro's untimed experts keep the checkpoint's width
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx CI: plan with a backend image's recorded digest once the registry prunes its tag
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: main's changes since the last rebase
h100-dgxc moved to the AWS dsxe Slurm (as b300): --mem=0, --container-remap-root and
--mpi=none, and the old DGXC enroot-paths workaround goes. Workflow steps source
check_env.sh; CI_zh.md is gone.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: find recipes through configs/*-master.yaml (srt-recipe-dir / srt-recipe)
InferenceX now names a single-node configuration's model, image and runner in the master
config, whose search space points at the srt-slurm recipe files; recipes no longer carry
model.path / model.container. Lookup walks those entries and expands each referenced
recipe with infx's selected_recipes as before.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx CI: record the AMD vLLM image's digest; its nightly tag is pruned
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: run on InferenceX's images; digest-pinned images import by digest
A backend's image is the one InferenceX serves that framework with on the runner's
hardware (configs/*-master.yaml); containers.toml is only the fallback where it serves
none. InferenceX pins images as tag@sha256: the planner takes the pinned digest and the
import URI from InferenceX's own enroot_uri (by digest, since enroot 3.x cannot parse
tag@digest). H100 runs on InferenceX's cluster:h100-dsxe runners.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx CI: CollectiveX's current AMD partitions; load ep_harness with its siblings
The AMD overlay replaced CollectiveX's whole operator block, pinning partitions the
clusters no longer have (compute-0); it now adds to it, so the partition and squash
directory are CollectiveX's. ep_harness now imports its sibling ep_oracle: load it with
its directory on sys.path.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx: cross-rank iteration reduction through CollectiveX's _Collectives.vec
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
* operatorx CI: fall back to the backend's image for any pruned InferenceX image
The hardware's default image is InferenceX's too, and can be a nightly tag the registry
has pruned (H100's ddd6fbca): swap it like a pruned recipe image instead of failing the
plan.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Multi-device (single-node) GEMM and MoE for OperatorX, stacked on #3452 (base
hbarclay/operatorx).Schema
parallel: {"tp", "dp", "ep"}on gemm and moe args (core/parallel.py); args keep the full layer shape; noparallel= one device (same code path).tponly — the weight is split over K, the all-reduce is part of the op.dp) and experts (tpsharded /eppartitioned) are divided, not how it's computed. vLLM mapping:tensor_parallel_size=tp,data_parallel_size=dp,enable_expert_parallel = ep > 1(ep ∈ {1, tp×dp}).tokensis per DP group.How a case runs (as InferenceX serves it)
recipes.py: planner matches each case to the InferenceX srt-slurm recipe for its source checkpoint on the runner's hardware (expanded withinfx.srt_slurm/ srtctl) → image, launch env,vllm serveargs (+ the checkpoint'sconfig.json, no weights). Per-framework topology mapping is one table entry (SPLITS), so SGLang can slot in beside vLLM.runners/common/vllm/engine.py: each rank runs the recipe args through vLLM'sEngineArgs/create_engine_configwithexternal_launcher, then the worker'sinit_worker_distributed_environment. vLLM decides collectives, MoE dispatch (prepare/finalize), kernels, CUDA-graph sizes. Replaces the hand-built single-rank context.RowParallelLinear; the MoE block under vLLM's parallel config (shared experts TP-sharded likeDeepseekV2MLP, sequence-parallel chunking likeDeepseekV2MoE,reduce_results=True); DP token counts fromcoordinate_batch_across_dp; graph capture insidegraph_capture().runners/common/ranks.py: ranks agree at every branch that launches collectives (build/trial failures, graph-capture fallback, throttle retries, warmup rounds) so one rank can't strand the others; timing reduces each iteration across ranks with CollectiveX'sep_harness._reduce_vec(MAX).ci.py: one shard per (split, recipe); OPERATORX_PARALLEL / OPERATORX_ENGINE_ARGS / OPERATORX_MODEL_CONFIG; CollectiveX's network-env scrub +NCCL_CUMEM_ENABLE=1; per-step MASTER_PORT;timeout -k 30around the rank srun; AMD no longer limited to one GPU (torch backend stays single-device). Oldagentic/*.shexport-scraping recipe loader removed.utils/srt-slurmand runs with srtctl's light deps.Testlists
gemm_parallel(50) /moe_parallel(46): every split InferenceX serves DSV4-Pro, DSV4.1-Flash, Kimi-K3, MiniMax-M3 with today — TP2/4/8, DP4+EP4, DP8+EP8, DP8, and SGLang's TP+EP (2/4/8) — at 8 and 256 tokens.Status (tailscale-h200 dev box, no CI yet)
cluster:h200-dgxcwithci.plan+ main's recipes (14 cells) and run on 8×H200: 94 ok, 2 unsupported, 0 error. The 2 are AMD's MXFP4 MiniMax MoE (no NVIDIA kernel, same as single-device). Covers TP2/4/8, TP+EP 2/4/8, DP4+EP4, DP8, DP8+EP8, incl. cells under the DSV4.1-Flash and MiniMax-M3 H200 recipes (their images; recipe caps such as CUDA graphs ≤ 64 tokens honored).NoDPEPfor TP(+EP) andNaiveDPEP(AG/RS) for DP.ranks: per-rank latency, profiled timeline and telemetry, cross-rank floor and skew (TP8 GEMM: 17.0 µs slowest-rank, 16.2 µs floor, ~1 µs skew).Open
benchmarks/single_node/srt-slurm-recipes+infx/srt_slurm.moe-backend deep_gemm_amxf4_mega_moe) is chosen in DSV4's model code, not FusedMoE; the recipe arg is passed but this harness builds FusedMoE, so DEP runs vLLM's generic EP path._scheme), not yet from the checkpoint'squantization_config.-f ingest=false.🤖 Generated with Claude Code