Repository navigation
operatorx: gemm + moe on NVIDIA and AMD - #3452
Conversation
77ece66 to
e3aa591
Compare
There was a problem hiding this comment.
Beyond the inline findings, I also checked the AMD torch GEMM backend's fp8 unsupported-case handling (runners/amd/backends/torch.py) against the actual testlist data: none of the e4m3 entries in gemm.json, gemm_perf.json, or the gemm_serving_*_min.json lists use the static per-tensor scale format the backend supports — they're all block-scaled (group [128,128] etc.), which the backend correctly rejects as unsupported. So this is the backend's documented per-tensor-only scope matching the current data, not a regression introduced by this change.
Extended reasoning...
This PR rewrites the operatorx microbenchmark tool end to end (CI workflow, core op/backend framework, gemm/moe op specs, and every vendor runner backend for NVIDIA/AMD/TPU/Trainium); it touches CI dispatch/scheduling but no auth or crypto surface. Four confirmed findings are already queued as inline comments (a Trainium NEFF-selection fallback that silently degrades to noisy host timing, testlist entries that bypass schema validation entirely, a telemetry polling-thread leak on kernel fault, and an eager registry-digest lookup in ci.py's plan() that can fail a whole plan on one transient pruned-image error). I additionally verified, by grepping the regenerated gemm testlists for the static per-tensor fp8 scale shape the AMD torch backend accepts, that no current test case actually hits that accept path with a genuine per-tensor scheme — the backend's narrow fp8 support matches the shipped data rather than silently dropping real coverage.
Findings marked 🟡 are optional suggestions and need no follow-up push.
|
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
…hodology 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>
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>
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>
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>
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>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ping - 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>
…ounters 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>
…zation 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>
…ole 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>
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>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
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>
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>
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>
…ackend 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>
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>
…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>
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>
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>
…r 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>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… 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>
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>
…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>
…3; 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>
…duler, 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>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…er reads it) Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ker does Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…'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>
… current config Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…endent of linear Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…g, 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/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>
…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>
…gistry prunes its tag Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
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>
… / 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>
5afbbd9 to
4652976
Compare
…pruned Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
… 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>
…ith 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>
…ectives.vec Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…ceX 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>
OperatorX measures single ops (a GEMM, one MoE layer) on NVIDIA and AMD. It runs them through the same framework layers InferenceX serves with, so the kernel it times is the kernel real inference uses. This PR replaces the prototype under
experimental/operatorx/with that version, adds the collection methodology, and connects the sweep to the OperatorX database.Scope
Ops: only
gemmandmoefor now.Attention, collectives,
moe_gemm,moe_forward,moe_layer, the other NVIDIA backends, the TPU and Trainium runners, and thescripts/inferencex_testlistgenerator are removed. Ops and platforms will come back one at a time.gemm(ops/gemm.py): activationa[M, K] and weightb[N, K]. Each is an operand descriptor{"dtype", "scale"?, "scale2"?, "symmetric"?}.scaleis{"dtype", "static", "group": [rows, cols]}, and-1spans a dimension.[-1,-1]is per-tensor,[1,-1]per-token,[-1,1]per-channel,[1,g]groups of g along K, and[r,c]2-D blocks.moe(ops/moe.py): one MoE layer from the router GEMM to the combined output. It has:tokensandhidden;experts: count, top-k, intermediate size, optional bias / latent / zero experts, and the operand descriptorsa1(input),w1(fused gate/up weight),w2(down weight) anda2(intermediate activation), named after vLLM'sFusedMoEQuantConfigslots;router: gate dtype, scoring, selection, bias, renormalize, scale;activation, optionalsharedexperts, and aroutingdistribution (natural / balanced / zipf / single_hot).Kernels, dispatch, fusion and streams are the backend's choice.
Every case says where it comes from:
sourceslists one<checkpoint>/<role>per layer that runs it, e.g.deepseek-ai/DeepSeek-V4-Pro/attn.wq_borzai-org/GLM-5-FP8/mlp./.head(lm_head) shapes.Backends:
vllmgemm (NVIDIA, AMD;runners/common/vllm/linear.py).ReplicatedLinearunder the quant config the descriptors imply, loads synthetic checkpoint-format weights, and runsprocess_weights_after_loading. vLLM then does its own activation quantization, repacking and kernel selection.vllmmoe (runners/common/vllm/moe.py): vLLM'sGateLinearrouter,FusedMoEFactoryexperts and shared-expert MLP, wired as vLLM's model blocks wire them. Covers DeepSeek-V4 FP4 experts and hash routing, Kimi-K3 latent experts, Qwen's shared-expert gate, and forced expert loads.torch(AMD): hipBLASLt bf16 and per-tensor fp8.Testlists:
gemm.jsongemm_serving_{8k1k,all}_min.jsonsourcesis DSv4-Pro where the shape is one of its projections, else[]gemm_perf.jsonmoe_small.jsongpt-oss is left out of
moe_smallfor now. Its biased MXFP4 experts don't go through vLLM's mxfp4 method cleanly here yet.Measurement
runners/common/telemetry.py):runners/common/profiling.py,runners/common/trace.py):metrics.profile).trace.py) with a small adapter per platform (CUDA/ROCm today).results/counters/, and latencies are perturbed.CI (
operatorx-sweep.yml)runner: acluster:label (cluster:h100-dgxcdefault, thenh200-dgxc,b200-nscale,b300-dsxe,gb200-nv,gb300-nv,mi300x-amd,mi325x-amds,mi355x-amds);backends,testlists,mode(timing | counters),world_sizes,chunk_size.trigger-ingest: sends InferenceX-app aningest-operatorxrepository_dispatch with the run id, asrun-sweep.ymldoes for benchmark results. The app stores the run in the OperatorX database (SemiAnalysisAI/InferenceX-app, branchhbarclay/operatorx). A run with failed shards is stored too; its unrun cases read as missing.hbarclay/operatorx:gemmtiming, 5,416 cases each: H200, B200, B300, MI300X, MI325X, MI355X.moe_small: H200, B300, MI300X, MI325X, MI355X.moe_smallreports a failure only because one shard is all-unsupported under--strict; its data is complete.tests/test_ci.pypasses (32).🤖 Generated with Claude Code