CollectiveX MVP v0.1#2004
Conversation
| rsync -a --delete --delete-excluded \ | ||
| --exclude='__pycache__/' --exclude='results/' --exclude='.cx_workloads/' \ | ||
| --exclude='configs/platforms.yaml' --exclude='private-infra.md' \ | ||
| --exclude='goal.md' --exclude='notes.md' \ | ||
| "$repo_root/experimental/CollectiveX" "$stage_dir/experimental/" >/dev/null 2>&1 \ | ||
| || cx_die "staging CollectiveX failed" |
There was a problem hiding this comment.
🔴 The setup step writes the shard JSON to experimental/CollectiveX/results/.shard_${matrix.id}.json and sets CX_SHARD_FILE=results/.shard_${matrix.id}.json (relative), but cx_stage_repo (runtime/common.sh:145-150) rsyncs the CollectiveX tree with --exclude='results/' --delete-excluded and drops the shard file — so for every staged single-tray SKU (b300 always; gb200/gb300 with EP4 via CX_NODES<=1), the [ -f "$CX_SHARD_FILE" ] guard at run_in_container.sh:458 fails and execution falls into the single-bench else branch (line 556+), silently running one wrong-config default (uniform/decode/bf16, empty case_id) instead of the shard's N scheduled cases. Downstream make_bundle will catch this via missing_identity/coverage but only after GPU allocation was spent on the wrong workload. Cheap fix: allow-list the shard file through the rsync (--include='experimental/CollectiveX/results/' --include='experimental/CollectiveX/results/.shard_*.json' before the results/ exclude), copy the shard file into the stage dir after the rsync, or resolve CX_SHARD_FILE against the original repo root in run_in_container.sh's SHARD guard the way the rack (EP8) launchers already do (see launch_gb300-nv.sh:92-93 / launch_gb200-nv.sh cx_ep_cases).
Extended reasoning...
The bug
The sweep workflow's shard-fanout step writes the resolved case list to experimental/CollectiveX/results/.shard_${matrix.id}.json:
# .github/workflows/collectivex-sweep.yml
env:
CX_SHARD_FILE: results/.shard_${{ matrix.id }}.json # RELATIVE path
...
- name: Extract shard from matrix artifact
working-directory: experimental/CollectiveX
run: |
...
json.dump({...,'cases':s['cases']}, open('results/.shard_${{ matrix.id }}.json','w'))The physical file therefore lands at $REPO/experimental/CollectiveX/results/.shard_<id>.json, and CX_SHARD_FILE=results/.shard_<id>.json is interpreted relative to the container's cwd, which is /ix/experimental/CollectiveX.
For every SKU that requires CX_STAGE_DIR (b300 always; gb200/gb300 with EP4 via the CX_NODES<=1 delegate path in launch_gb200-nv.sh:57 / launch_gb300-nv.sh:47), the launcher calls:
# launch_b300.sh:34, launch_gb200-nv.sh:52, launch_gb300-nv.sh:24
MOUNT_SRC="$(cx_stage_repo "$REPO_ROOT" "$CX_STAGE_DIR")"which rsyncs the tree with an exclude that drops results/:
# experimental/CollectiveX/runtime/common.sh:145-150
rsync -a --delete --delete-excluded \
--exclude='__pycache__/' --exclude='results/' --exclude='.cx_workloads/' \
--exclude='configs/platforms.yaml' --exclude='private-infra.md' \
--exclude='goal.md' --exclude='notes.md' \
"$repo_root/experimental/CollectiveX" "$stage_dir/experimental/"Both --exclude='results/' and --delete-excluded guarantee that the shard file the workflow just wrote is missing from the stage dir.
The consequence at runtime
The container mounts $MOUNT_SRC:/ix, cwd=/ix/experimental/CollectiveX. Inside run_in_container.sh, the SHARD guard resolves CX_SHARD_FILE relative to that cwd:
# runtime/run_in_container.sh:458
if [ -n "${CX_SHARD_FILE:-}" ] && [ -f "${CX_SHARD_FILE:-/nonexistent}" ]; then
# SHARD mode — sweep every scheduled case
...
else
# Single-bench (workflow_dispatch) path
# uses ${CX_MODE:-normal}, ${CX_PHASE:-decode}, ${CX_ROUTING:-uniform},
# ${CX_DISPATCH_DTYPE:-bf16}, empty CX_CASE_ID/CX_SUITE/CX_WORKLOAD_NAME, ...The file resolves to /ix/experimental/CollectiveX/results/.shard_<id>.json — which is missing because rsync excluded it — so the test fails and the else branch runs a single default case with none of the shard's identity, N times cheaper than the intended N-case sweep.
Why the rack (EP8) paths escape
The rack-scale launchers iterate cases themselves in the launcher on the SUBMIT host (not inside the container). Their case-list helpers explicitly resolve the shard file against the original checkout when the relative path misses:
# launch_gb300-nv.sh cx_ep8_cases (and launch_gb200-nv.sh cx_ep_cases)
local sf="${CX_SHARD_FILE:-}"
[ -n "$sf" ] && [ ! -f "$sf" ] && [ -f "$CX_DIR/$sf" ] && sf="$CX_DIR/$sf"The same workaround is absent from run_in_container.sh:458, so the EP4 single-tray path — which shares the b300/gb200-EP4/gb300-EP4 launchers with the staged mount — hits the missing file.
Affected sweeps
Every single-tray staged shard in the v1 promoted matrix, per sweep_matrix.py + configs/suites.yaml platforms:
- b300 (all shards; launch_b300.sh is single-node)
- gb200 EP4 (CX_NODES<=1 -> run_in_container.sh)
- gb300 EP4 (CX_NODES<=1 -> run_in_container.sh)
The h100-dgxc/h200-dgxc/b200-dgxc/mi325x/mi355x paths do not set CX_STAGE_DIR in this workflow (cx_stage_repo becomes a no-op) and are unaffected.
Concrete walk-through (b300 shard)
- Setup job resolves matrix; writes
experimental/CollectiveX/results/.shard_b300-deepep.jsonon the checkout with e.g. 24 cases (varied phase/dtype/routing/eplb across ep-core-v1 + ep-routing-v1). - Sweep job on the b300 runner exports
CX_SHARD_FILE=results/.shard_b300-deepep.json, checks out the repo, and callslaunch_b300.sh. launch_b300.sh:34->cx_stage_reporsyncs to$CX_STAGE_DIR/job_<id>/experimental/CollectiveX/with--exclude='results/' --delete-excluded. The shard file is not copied.srun --container-workdir=$MOUNT_DIR/experimental/CollectiveX ... run_in_container.sh. cwd inside container =/ix/experimental/CollectiveX.run_in_container.sh:458tests[ -f "results/.shard_b300-deepep.json" ]-> that resolves to/ix/experimental/CollectiveX/results/.shard_b300-deepep.json-> missing.- Execution falls into the else branch at line 556+. It dispatches
${CX_BENCH}once withCX_MODE=normal,CX_PHASE=decode,CX_ROUTING=uniform,CX_DISPATCH_DTYPE=bf16, emptyCX_CASE_ID, emptyCX_SUITE, emptyCX_WORKLOAD_NAME, emptyCX_REQUIRED_PUBLICATION. - One result JSON is produced with no case_id and mismatched identity; the other 23 scheduled cases never run.
- Aggregate job's
make_bundle.py validate_expected_coveragecomputesmissing_identity + missing + identity_mismatchagainstmatrix_full.jsonandraise SystemExit(...)— the whole aggregate fails, after b300 GPU-time was spent on the wrong workload.
Impact
For every b300/gb200-EP4/gb300-EP4 shard promoted through v1 (three of the eight SKUs in ep-core-v1 + ep-routing-v1), the sweep silently runs one wrong-config default point instead of the scheduled N-case sweep. Bundle validation catches the divergence but only post-hoc, so the failure is loud yet wasteful: GPU allocations spent, aggregate job red, invalidating the v1 dataset this PR is producing.
Fix
Any one of:
-
Allow the shard file through the rsync in
cx_stage_repo(runtime/common.sh:146):rsync -a --delete --delete-excluded \ --include='experimental/CollectiveX/results/' \ --include='experimental/CollectiveX/results/.shard_*.json' \ --exclude='__pycache__/' --exclude='results/' ...
-
Copy the shard file into the stage dir after the rsync completes:
[ -n "${CX_SHARD_FILE:-}" ] && [ -f "$repo_root/experimental/CollectiveX/$CX_SHARD_FILE" ] \ && cp -a "$repo_root/experimental/CollectiveX/$CX_SHARD_FILE" \ "$stage_dir/experimental/CollectiveX/$CX_SHARD_FILE"
-
Mirror the rack (EP8) launcher workaround in
run_in_container.sh:458:sf="${CX_SHARD_FILE:-}" # $CX_DIR is not set inside the container; use the fixed workdir instead. [ -n "$sf" ] && [ ! -f "$sf" ] && [ -f "/ix/experimental/CollectiveX/$sf" ] \ && sf="/ix/experimental/CollectiveX/$sf" if [ -n "$sf" ] && [ -f "$sf" ]; then ...
Approach (1) or (2) is the smallest change with the least surface area.
| elif _run(["ibstat", "-l"]): | ||
| devices = [d.strip() for d in _run(["ibstat", "-l"]).splitlines() if d.strip()] | ||
| return { |
There was a problem hiding this comment.
🟡 _rdma() calls _run(["ibstat", "-l"]) twice at env_capture.py:178-179 — once in the elif condition and once in the comprehension body. If the second invocation returns None (which _run does on shutil.which miss, TimeoutExpired/OSError, or nonzero exit), .splitlines() raises AttributeError and takes down env_capture.py under run_in_container.sh's set -euo pipefail. The trigger is genuinely rare (both calls are microseconds apart on a stable IB stack, and this branch runs only when ibv_devinfo is absent), so nit — but the fix is a one-line refactor mirroring the ibv_devinfo branch just above.
Extended reasoning...
The defect. env_capture._rdma() has an asymmetry between its two RDMA-listing branches:
listing = _run(["ibv_devinfo", "-l"]) # assigned once, iterated once
if listing:
for line in listing.splitlines()[1:]:
...
elif _run(["ibstat", "-l"]): # called once (as a truthiness check)
devices = [d.strip() for d in _run(["ibstat", "-l"]).splitlines() if d.strip()] # called AGAINThe ibv_devinfo branch just above does the right thing: assign once, reuse. The ibstat branch does not.
Why the crash is theoretical but real. _run() returns None on any of: shutil.which(cmd[0]) failing (line 51), subprocess.TimeoutExpired/OSError (line 57), or out.returncode != 0 (line 59). If the first call returns a truthy string but the second returns None — a transient OS timer glitch, an OOM-killed helper, a stray nonzero exit under load — then None.splitlines() raises AttributeError. Under run_in_container.sh's set -euo pipefail (line 33), that aborts the whole shard step before any GPU benchmark runs.
Step-by-step proof of the theoretical crash path:
- Node has
ibstatin$PATHbut noibv_devinfo(a real config: MI355X-style stacks withibstatonly). - First call:
_run(["ibstat", "-l"])succeeds → returns"mlx5_0\nmlx5_1\n"→ elif condition is truthy. - Second call: a transient nonzero exit (e.g.
ibstatracing an IB-driver reload, timer wraparound, PID-namespace hiccup) →out.returncode != 0→_runreturnsNone. None.splitlines()→AttributeError: 'NoneType' object has no attribute 'splitlines'→ Python exits nonzero →set -eabortsrun_in_container.sh→ the shard step fails before GPU work.
Why this is nit, not normal. Every verifier converged on the same practical assessment: ibstat -l is a fast local device listing with no network/filesystem dependency, so a transient failure between two back-to-back calls (microseconds apart) is extremely improbable. The elif branch itself only runs when ibv_devinfo is absent, which is uncommon on the target runners since both binaries come from the same InfiniBand userspace stack. And env_capture.py produces a diagnostic/provenance artifact — even a genuine crash here would break provenance capture, not the benchmark measurement. The defect exists but doesn't justify blocking merge.
The fix. One-line refactor to mirror the ibv_devinfo branch:
else:
listing = _run(["ibstat", "-l"])
if listing:
devices = [d.strip() for d in listing.splitlines() if d.strip()]Same idiom the file uses immediately above. Eliminates the wasted subprocess call and the theoretical None-deref in one change. Worth doing as a follow-up cleanup, but the PR does not need to block for it.
| "required_publication": env("CX_REQUIRED_PUBLICATION") or None, | ||
| "backend": backend, | ||
| "phase": phase, | ||
| "ep": integer("CX_EP", integer("CX_NGPUS", 1)), | ||
| "gpus_per_node": integer("CX_GPUS_PER_NODE", integer("CX_NGPUS", 1)), | ||
| "scale_up_domain": integer("CX_SCALE_UP_DOMAIN", integer("CX_NGPUS", 1)), | ||
| "dispatch_dtype": env("CX_DISPATCH_DTYPE", "bf16"), | ||
| "mode": env("CX_MODE", "normal"), | ||
| "contract": env("CX_MEASUREMENT_CONTRACT", "layout-and-dispatch-v1"), | ||
| "routing": env("CX_ROUTING", "uniform"), | ||
| "eplb": enabled("CX_EPLB"), | ||
| "combine_quant_mode": env("CX_COMBINE_QUANT_MODE", "none"), | ||
| "resource_mode": env("CX_RESOURCE_MODE", "tuned"), | ||
| "activation_profile": env("CX_ACTIVATION_PROFILE", "normal"), | ||
| "placement": env("CX_PLACEMENT", "packed"), | ||
| "routing_step": env("CX_ROUTING_STEP", "0"), | ||
| "uneven_tokens": env("CX_UNEVEN_TOKENS", "none"), | ||
| "tokens_ladder": env("CX_TOKENS_LADDER"), | ||
| "canonical": enabled("CX_CANONICAL"), | ||
| "sampling_contract": "fixed-512-v1", | ||
| "samples_per_point": integer("CX_SAMPLES_PER_POINT", 512), | ||
| "iters": integer("CX_ITERS", 8), | ||
| "trials": integer("CX_TRIALS", 64), | ||
| "warmup": integer("CX_WARMUP", 32), | ||
| "warmup_semantics": env( | ||
| "CX_WARMUP_SEMANTICS", "full-roundtrip-per-trial-point-v1" | ||
| ), |
There was a problem hiding this comment.
🟡 cx_emit_ep_failed_case (runtime/common.sh:256-287) builds failure.case without the hidden/topk/experts/nodes keys, but every matrix case emitted by sweep_matrix.py always carries all four. On the first sweep where any case exhausts its retries (flashinfer intermittent MNNVL, HybridEP/UCCL empty-rank, any deterministic rc=5), make_bundle's _identity_differences reports the same case_id four times as hidden=None!=7168,topk=None!=8,experts=None!=256,nodes=None!=1, and validate_expected_coverage piles on by re-listing that case in missing, so the aggregate job aborts with a dual-report that hides the real signal (the case failed all retries — the intended fail-closed behavior). Fix in either place is fine: add the four fields to cx_emit_ep_failed_case from CX_HIDDEN/CX_TOPK/CX_EXPERTS (defaults 7168/8/256) and CX_NGPUS/SLURM_NNODES, or make _identity_differences skip these fields when the actual doc is a failed-case.
Extended reasoning...
The observed behavior
With the PR merged and any sweep that produces a failed-case record for a scheduled case, the aggregate job will fail with a message like:
bundle: expected-matrix coverage failed (
missing_identity=0 missing=['cxv1-...'] extra=[] duplicates=[]
identity_mismatch=['cxv1-...:hidden=None!=7168,topk=None!=8,experts=None!=256,nodes=None!=1'])
The same case_id appears in both missing and identity_mismatch, and the mismatch string names four fields that have nothing to do with why the case actually failed.
Step-by-step proof
Take a concrete promoted case, say h100-dgxc/deepep/decode under ep-core-v1 (uniform, canonical, deepseek-v3-v1 defaults). sweep_matrix.py:181-186 builds the matrix entry with:
{
...,
"hidden": "", # h==7168 -> "" sentinel
"topk": "", # t==8 -> ""
"experts": "", # e==256 -> ""
"nodes": "1", # always str
...
}When every one of the 4 flashinfer attempts wedges on the intermittent MNNVL completion-flag deadlock (documented in run_in_container.sh around line 526), the last attempt's cx_emit_ep_failed_case writes a failed_*.json whose failure.case dict is missing the four keys entirely — the emitter reads CX_DISPATCH_DTYPE/CX_MODE/etc. but has no CX_HIDDEN/CX_TOPK/CX_EXPERTS/SLURM_NNODES reads.
aggregate_results.py keeps that failed-case doc as the newest for that case_id. Then make_bundle.py runs validate_expected_coverage:
_expected_case_identity(matrix_case)—"hidden" in caseis true (value""), soidentity["hidden"] = int("" or 7168) = 7168. Same for topk/experts (8/256)."nodes" in caseis true,identity["nodes"] = int("1") = 1. Expected identity contains{hidden: 7168, topk: 8, experts: 256, nodes: 1, ...}._actual_case_identity(failed_doc)(the failed-case branch, line 184-195) copiesfailure.caseverbatim, calls_expected_case_identity. None ofhidden/topk/experts/nodesare in that dict, so theif field in case:guard skips all four. Actual identity contains everything except the four scheduled shape fields._identity_differencesiterates the expected identity's items;actual_identity.get("hidden")isNone,None != 7168->hidden=None!=7168. Same for the other three.validate_expected_coverage(line 294-298) hits thedifferencesbranch, appends the case_id toidentity_mismatch, and does not add it toactual{}. Thenmissing = set(expected) - set(actual)(line 301) also contains that case_id. Line 319 raises the dual-reportSystemExit.
validate_results.py:validate_doc's failed-case schema (v5, ~lines 234-243) requires a different, smaller field set that happens to match what the emitter writes, so it stays silent about this desync. Only make_bundle notices, and only in a way that obscures the real cause.
Why this fires in practice
The PR explicitly builds in retry logic — CX_FLASHINFER_RETRIES defaults to 3 attempts, and both the container and rack launchers loop attempts and preserve a failed_*.json when all attempts fail. Retry-exhaustion is expected behavior for known intermittents, but the aggregate step will now report those as identity_mismatch + missing for hidden/topk/experts/nodes — the least informative signal possible.
Impact
Bundle validation still correctly rejects the incomplete run (the intended fail-closed behavior), and no incorrect data ships, so this is a diagnostic-clarity regression rather than a correctness bug. It will, however, cost real triage time in CI: an operator staring at hidden=None!=7168,topk=None!=8,experts=None!=256,nodes=None!=1 will not obviously infer "one flashinfer case exhausted its retries."
Fix
Either add the four fields to cx_emit_ep_failed_case (read CX_HIDDEN/CX_TOPK/CX_EXPERTS with defaults 7168/8/256, and CX_NGPUS/SLURM_NNODES for nodes), or teach _identity_differences/_actual_case_identity to drop these fields when the actual doc is a failed-case. Either way the two validators stay in sync.
758fa52 to
1c5b901
Compare
7e5f80a to
28cbac4
Compare
4ff5841 to
bffd8d6
Compare
Drop the probe-summary, autopublish, publish, and refresh jobs together with the release-marker/digest logic and the 11 publication-oriented inputs. The workflow now only resolves the shard matrix and executes each cell, uploading neutral matrix and per-shard result artifacts. Display, promotion, ranking, and dataset building move out of this repo.
Relocate the 15 runnable EP modules (adapters, harness, routing, workload, eplb, precision codec, run_ep/make_workloads entrypoints) from tests/ into a dedicated bench/ directory so tests/ holds only unit tests. Retarget sweep_matrix's importer, the two unit tests' path setup, the runtime shell launch paths, and run_ep's stamped reproduction command. Sibling and top-level imports resolve via file-relative sys.path, so the move is mechanical; probe_precision paths stay pending removal in the runtime prune.
…rovenance.py The emitter's implementation-provenance evidence builders (DeepEP MNNVL resolution, NCCL/RCCL lineage, resource projection, backend-provenance issue detection, hybrid/deepep-v2 JIT evidence checks, content-manifest hashing) are cross-field facts a JSON Schema cannot express, but they are an emitter concern, not part of the neutral document/delivery validation boundary. Relocate the twelve-function cluster beside the bench modules that call it, so contracts.py holds only neutral validation. Rewire the five emitter modules (ep_deepep, ep_deepep_hybrid, ep_harness, ep_nccl, ep_uccl) to ep_provenance. Drop two dead helpers (backend_version, hybrid_communication_domains) with no callers. contracts.py now holds only neutral validation (1009 LOC).
The publication-subsystem strip (b09a270) removed four emit-side projection helpers from contracts.py on the premise that their only consumers were the precision validator and publisher.py. run_sweep's emit tail still calls all of them, so the real (GPU) emit path raised AttributeError at runtime; the FakeBackend unit smoke never reaches that tail, so nothing surfaced it. Relocate the emit-side projection helpers next to the other backend-provenance projections in bench/ep_provenance.py and rewire the ep_harness call sites: - series_provenance (privacy projection: drops host path/jit_cache_key/ jit_shared_objects/sm_fraction, projects source-built libs + jit cubins) - public_series_config / public_series_config_sha256 - routing_implementation_control_sha256 - backend_version (+ a local canonical _sha256_json) series_id stays load-bearing: validate_samples_document derives the detached sample point path from it (identity.point_id(series=...)), and identity + the schemas still require it. This is completed relocation, not removal. Also remove the emitter's publication-disposition logic: _derive_publication_status and the outcome.publication_status field. The raw-case schema's outcome object is additionalProperties:false over {reasons,status,validity}, so any real emission carrying publication_status would have failed validation; deciding publication disposition is out of scope for the sweep backend.
… shell The MVP sweep schedules, executes, validates, and uploads neutral artifacts only; it does not run the swept precision-probe path and no longer carries a required-publication signal. Remove the precision-probe dispatch, control-field parsing, manifest validation, and failure-class branches from runtime/common.sh and runtime/run_in_container.sh, and drop the CX_REQUIRED_PUBLICATION / --required-publication plumbing (the arg no longer exists on the harness). Keep the BF16-default --precision-profile and --qualification-index paths (FP8 stays callable-but-dormant), and preserve every cluster execution fix untouched.
CollectiveX now schedules, executes, validates, and uploads neutral artifacts and no longer promotes, ranks, recommends, or decides display. Rewrite README.md and docs/methodology.md to describe the setup+sweep workflow, BF16-default measurement (FP8 codec callable but not swept), per-dispatch matrix (no frozen digest/qualification), and neutral validate-delivery + artifact upload. Drop all publication/promotion/ dev-latest/publisher/cohort/recommendation content; preserve the measurement, correctness, execution-isolation, and image-pinning contracts unchanged. Remove the language switcher from the English docs (existing _zh files left untouched per the English-only override).
The P1 schema strip removed publication/precision/qualification fields from raw-case-v1, but the emitter in bench/ep_harness.py still wrote them, so contracts.validate_raw_document would reject every emitted case (including the default BF16 sweep) on the cluster. Remove the fields the stripped schema no longer accepts: - case.shape: precision_profile, dispatch_precision, combine_precision - identity.series_factors: implementation_contract_sha256, public_config_sha256, routing_control_sha256, runtime_fingerprint_sha256 - identity.allocation_factors / measurement / samples: qualification_index - measurement.execution_order_sha256 (+ dead observed_component_orders) - provenance.allocation_stratum_sha256 - provenance.git_run.qualification_index (bench/run_ep.py) Add tests/test_raw_document_roundtrip.py: a hermetic regression test that builds a full raw + samples document from the real identity module and drives contracts.validate_raw_document, asserting the BF16 fixed facts and rejecting each removed drift field. Closes the FakeBackend coverage gap where no test exercised the emitter->validate contract on CPU.
sweep_matrix.py imports contracts unconditionally, and its --validate-control / --extract-from paths are documented (and relied on by cx_validate_shard_control in runtime/common.sh) to run on a minimal GPU-runner python3 with only the standard library. The P2 rewrite added a module-level `from jsonschema import Draft202012Validator` to contracts.py, which made importing contracts (hence sweep_matrix) fail on any host without jsonschema, breaking host-side shard validation before allocation. Defer the jsonschema import into contracts._validator(), which is the only runtime user of the symbol (all annotations are strings under `from __future__ import annotations`). Importing contracts no longer requires jsonschema; document validation still imports it on first use.
…hema The schema/publication trim removed the precision-evidence key and the allocation_stratum_sha256 provenance parameter, but the raw-document emitter still wrote correctness.precision (rejected by raw-case schema additionalProperties) and still passed allocation_stratum_sha256 to provenance_complete() (now a TypeError). It also left two orphaned probe_stage branches in cx_fail_stage() that abort the launcher under set -u. These desyncs are invisible to the CPU unit battery (run_sweep imports torch and never executes off-GPU); a live 8xH100 nccl-ep sweep surfaced all three. Fix: drop the dead precision gather + correctness.precision emit, drop the allocation_stratum_sha256 kwarg, and remove the probe_stage branches. Full offline schema reconciliation confirms zero remaining forbidden keys. Verified green on 8xH100 (nccl-ep, EP8, BF16): both prefill and decode emit status=success case-attempt docs + detached samples; summarize produces the neutral table.
contracts.py now depends on jsonschema (P2 moved document validation onto it). The setup job and self-hosted sweep runners ship a bare python3 with no system pip, so validate-delivery / emit-terminal / summarize invocations of bare python3 could not resolve jsonschema. Add jsonschema to the matrix-deps install and build a host venv (venv+ensurepip, available where system pip is not) on PATH for every sweep step.
…nd (MVP cleanup) Backend now only schedules, executes, validates, and uploads neutral artifacts; it never promotes, ranks, recommends, selects, hides, or decides frontend display. - B1: iterable benchmark `version` in one config (configs/suites.yaml), threaded through matrix, shard control, raw+terminal docs, and the summary; mismatches and missing/non-int versions are rejected across Python + JSON Schema + shell. - B2: drop frontend-facing `v1` suffixes from suite/workload names (ep-core, ep-low-latency, deepseek-v3); keep schema_version, format strings, internal names. - B3: remove precision profiles (BF16-only); delete ep_precision.py. - B4: readable case IDs with minimal hashing in identity.py. - B5/B11: trim contracts.py to its neutral public surface, de-duplicate the allocation-factors projection and the double delivery-validation pass; privacy boundary and all cross-file checks preserved. - B6: delete frontend projection/ranking from sweep_matrix.py. - B7: remove max_nodes coupling; clean capability.py. - B9: consolidate tests into test_contracts.py + test_matrix.py; mechanical summarize.py. - B8: confirm common.sh maps cleanly to its six responsibilities (no dead code). CPU battery: 13 passed / 6 subtests. All modules compile; runtime shell bash -n clean.
- identity.py: remove no-op component_order_contract override in V1_LOW_LATENCY_CASE_PROFILE (identical to the value inherited from V1_NORMAL_CASE_PROFILE via spread). - capability.py: remove unused DEEPEP_V1_IBGDA_NIC_HANDLERS constant; the b200 CPU-proxied IBGDA handler is set live in runtime/common.sh. - runtime/common.sh: remove uncalled cx_require_single_node and cx_gha_workspace_stage_root helpers. No behavior change; CPU battery green (13 passed, 6 subtests).
…code only Remove contracts.py and schemas/*.json and rewire every dependent to run without document validation, terminal-outcome emission, or delivery gating. A case now counts as run on the benchmark's return code alone; failed or unsupported cells emit no synthetic record. - bench: drop `import contracts`; move ContractError/GIT_RUN_FIELDS into ep_provenance.py; inline the frozen v1 conditioning ladders; remove the validate_raw_document emit-time call. - runtime: strip result-doc probes, terminal-outcome/setup-failure emission, and delivery gating from common.sh and run_in_container.sh. - summarize.py: drop the no-longer-emitted terminal document format. - CI: remove both validate-delivery steps, emit-unsupported, and jsonschema; re-gate summary/stage/upload/cleanup off delivery_contracts. - requirements: drop jsonschema. - docs/tests: README, methodology, and matrix tests updated to the return-code-only reality; delete test_contracts.py.
…empty results cx_fail_stage now prints the failing stage's captured log tail (bounded by CX_LOG_TAIL_LINES, default 100) verbatim to stdout after the diagnostic line, so a failed leg surfaces the underlying tool output instead of just a failure-class label. Stage shard artifact now no-ops cleanly when a leg produced no result JSON (nullglob + staged output flag), so a failed leg shows one red step instead of two.
…rdown The cluster runs pyxis with container_scope=global, so the distributed path's named --container-writable container (cxep_<jobid>) survives job teardown. Its unpacked rootfs is ~60-70 GB per node, and it was never removed, so every EP16 leg left one behind on each allocated node's local image store (/mnt/image-storage, 915 GB). After enough legs the store filled and the next writable extraction failed at ~2 s with "No space left on device" — classified as backend-setup/storage-capacity. That is exactly why every H200 2-node (EP16) leg failed while every single-node (EP8) leg, which mounts the squash read-only and creates no named container, succeeded. cx_launcher_cleanup now runs `enroot remove -f pyxis_cxep_<jobid>` across the allocation's nodes before releasing it (best-effort, bounded, NODES>1 only). Single-node legs use an unnamed ephemeral container that pyxis reclaims itself.
| [ -n "$INPUT_ONLY_SKU" ] && args+=(--only-sku "$INPUT_ONLY_SKU") | ||
| [ -n "$INPUT_EXCLUDE_SKUS" ] && args+=(--exclude-skus "$INPUT_EXCLUDE_SKUS") | ||
| [ -n "$INPUT_EP_SIZES" ] && args+=(--ep-sizes "$INPUT_EP_SIZES") | ||
| python3 sweep_matrix.py "${args[@]}" --out matrix_full.json >/dev/null |
There was a problem hiding this comment.
Matrix with dimensions benchmark suites, SKUs, EP degree, backends
…d archive Replace the setup-job tar seed pipeline (build seed -> upload cxbackend-sources -> download -> extract via source_archive.py) with a per-shard git fetch of the pinned DeepEP-v2/hybrid source before allocation, using the checkout path the shard already had. Removes source_archive.py and the seed-copy branch in _cx_prepare_backend_source, reduces the isolated-parent setup and operator-config overlay merge to shell, and updates README/methodology to match.
| rm -f -- "$CX_JOB_ROOT/cleanup-safe" | ||
| cd "$CX_SOURCE_ROOT" 2>/dev/null \ | ||
| || { echo "CollectiveX source is unavailable" >&2; exit 1; } | ||
| bash "experimental/CollectiveX/launchers/launch_${{ matrix.launcher }}.sh" |
There was a problem hiding this comment.
Runs script here
| [ "${CX_JOB_ROOT%/*}" = "$CX_JOB_PARENT" ] \ | ||
| && [[ "${CX_JOB_ROOT##*/}" =~ ^inferencex-collectivex-[0-9]+-[0-9]+-[A-Za-z0-9._-]+$ ]] \ | ||
| || { echo "CollectiveX isolated root is invalid" >&2; exit 1; } | ||
| [ "$CX_SOURCE_ROOT" = "$CX_JOB_ROOT/source" ] \ |
There was a problem hiding this comment.
To ensure rm -rf later is safe
| || { echo "CollectiveX isolated root already exists" >&2; exit 1; } | ||
| mkdir -m 700 "$CX_JOB_ROOT" | ||
| trap 'rc=$?; if [ "$rc" != 0 ]; then rm -rf -- "$CX_JOB_ROOT"; [ "$CX_JOB_PARENT" = /tmp ] || rm -f -- "$CX_JOB_PARENT"; fi; exit "$rc"' EXIT | ||
| mkdir -m 700 "$HOME" "$CX_JOB_ROOT/control" "$CX_JOB_ROOT/artifact" "$CX_SOURCE_ROOT" |
There was a problem hiding this comment.
Clean working dir
| [ "$CX_SOURCE_ROOT" = "$CX_JOB_ROOT/source" ] \ | ||
| || { echo "CollectiveX source root is invalid" >&2; exit 1; } | ||
| if [ "$CX_JOB_PARENT" != /tmp ]; then | ||
| [[ "$CX_JOB_PARENT" =~ ^/tmp/inferencex-collectivex-parent-[0-9]+-[0-9]+-[A-Za-z0-9._-]+$ ]] \ |
There was a problem hiding this comment.
AMD shared-filesystem workaround, mi300x /tmp near full
| strategy: | ||
| fail-fast: false | ||
| max-parallel: 10 # don't saturate the ~20-runner fleet; cells queue as slots free | ||
| max-parallel: ${{ fromJSON(needs.setup.outputs.max_parallel) }} |
There was a problem hiding this comment.
Prevents too many jobs -> prevent others from running
Prevents too little jobs -> serialization is slow
Migrate mi355x onto the same mi35x-tagged 0701 image (sglang 0.5.14) that mi300x/mi325x already run green, with the asyncll kernel and the XGMI/SDMA env. The prior 0227 image (sglang 0.5.9) hung MoRI EpDispatchCombineOp construction on gfx950; the 0227 image/commit constants are now dead and removed. Drops the MI355X/mori/EP8 capability wall so the cell is runnable (EP16 wall retained pending separate validation).
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
Commit 07b81ab ("cleanup sweep.yml") dropped the block that unset the COLLECTIVEX_*_CONFIG_CONTENT and _REQUIRED environment variables after the merge step writes $CX_JOB_ROOT/operator-config.json. With those still set, the launcher's cx_load_operator_config re-enters the ephemeral-config branch and tries to create the same file with O_EXCL/noclobber, which the merge step already created — failing every leg at setup with "cannot create ephemeral runner configuration". Restore the unset so the launcher consumes the written path instead.
The (mi355x, mori, 16) wall recorded a construction hang under the old 0227 image, which has since been replaced by the mi35x-tagged 0701 image now proven green for MI355X EP8. Remove the wall so the EP16 InterNodeV1 scale-out cell is dispatched and validated on the current image; restore it with an accurate reason if device-initiated cross-node RDMA does not complete.
Enablement attempt (removed the wall, dispatched the 2-node EP16 leg) confirmed the MoRI InterNodeV1 scale-out path does not complete on MI355X: the leg failed early with no result emitted. InterNodeV1 EP16 needs device-initiated cross-node RDMA over ROCm SHMEM, which hits the same GPUDirect-RDMA wall seen for other custom-RDMA backends on this fleet. EP8 scale-up (AsyncLL/intranode) stays runnable and green.
The B300 RoCE fabric selector (gid 3, RoCE rails) is validated for NCCL (nccl-ep) and verbs/DOCA (deepep-hybrid) cross-node transport, but the single-node base-deepep leg forced NVSHMEM IBGDA loopback over those same HCAs and failed connection setup (nvshmem init.cu status 7). All 8 b300 GPUs share one NVLink domain, so the single-node deepep leg has no cross-node fabric to use. Route it to NVSHMEM_DISABLE_IB=1 (intranode NVLink P2P), matching the deepep-v2 single-node path that has always been green. Drop the deepep-low-latency single-node-RDMA carve-out in both cx_apply_network_profile and cx_validate_network_profile_on_job; normal-mode b300 deepep already used the disable-IB path, so this only aligns the low-latency cases with it.
Summary
CollectiveX v1 expert-parallel communication benchmark and its isolated GitHub Actions publication path.
Benchmark and publication contract
release_tag=unversioned; only an explicitv1tag with the locked full-matrix digest emits a release marker.Validation
f1ca85f9689922b90edd5767b9ff2a902f6b896f32f68b2ca086dde3fd2157d0.8e262178f770b0cdde12b7ec71604afd87251fa55685d4594f29717153ad6bbd.git diff --checkpass.中文说明
本 PR 完成 CollectiveX v1 专家并行通信基准测试及隔离式 GitHub Actions 发布链路。代码已准备执行三轮完整、无 canary 的 V1 运行;本 PR 目前不声称已有完成 promotion 的实测结果。
基准测试与发布约定
release_tag=unversioned;只有显式选择v1且匹配固定完整矩阵摘要时才生成 release marker。cxpublication-v1-*NDJSON。验证
f1ca85f9689922b90edd5767b9ff2a902f6b896f32f68b2ca086dde3fd2157d0。8e262178f770b0cdde12b7ec71604afd87251fa55685d4594f29717153ad6bbd。git diff --check通过。