Skip to content

CollectiveX MVP v0.1#2004

Open
Oseltamivir wants to merge 213 commits into
mainfrom
collectivex
Open

CollectiveX MVP v0.1#2004
Oseltamivir wants to merge 213 commits into
mainfrom
collectivex

Conversation

@Oseltamivir

@Oseltamivir Oseltamivir commented Jul 3, 2026

Copy link
Copy Markdown
Collaborator

Summary

CollectiveX v1 expert-parallel communication benchmark and its isolated GitHub Actions publication path.

Benchmark and publication contract

  • Covers eight SKUs, six backend families, EP8/EP16, normal and genuine low-latency semantics.
  • Locks 58 shards, 608 requested cases / 1,600 points, including 364 runnable and 244 explicit unsupported cases.
  • Standardizes every measured component at 32 synchronized full-roundtrip warmups and 64 x 8 timed iterations: 512 cross-rank-max observations.
  • Defaults every sweep to release_tag=unversioned; only an explicit v1 tag with the locked full-matrix digest emits a release marker.
  • Uses no results server, Vercel storage, GCP, Neon, managed database, or managed object store.

Validation

  • 151 Python contract/unit tests pass.
  • Matrix SHA-256: f1ca85f9689922b90edd5767b9ff2a902f6b896f32f68b2ca086dde3fd2157d0.
  • Case catalog SHA-256: 8e262178f770b0cdde12b7ec71604afd87251fa55685d4594f29717153ad6bbd.
  • Regeneration confirms 58 shards, 364 runnable cases, 244 unsupported cases, and 1,600 points.
  • Actionlint, ShellCheck, and git diff --check pass.

中文说明

本 PR 完成 CollectiveX v1 专家并行通信基准测试及隔离式 GitHub Actions 发布链路。代码已准备执行三轮完整、无 canary 的 V1 运行;本 PR 目前不声称已有完成 promotion 的实测结果。

基准测试与发布约定

  • 覆盖八种 SKU、六类后端、EP8/EP16,以及常规与真正低延迟语义。
  • 固定为 58 个 shard、608 个请求 case / 1,600 个点位,其中包含 364 个可运行 case 和 244 个显式 unsupported case。
  • 每个被测组件统一执行 32 次同步完整往返预热和 64 x 8 次计时迭代,共 512 个跨 rank 最大值观测。
  • 所有扫描默认使用 release_tag=unversioned;只有显式选择 v1 且匹配固定完整矩阵摘要时才生成 release marker。
  • 发布必须使用来自同一 source SHA 的三个成功且带 tag 的运行。手动 publisher 会重新校验运行 metadata 与精确 marker,在 runner 临时存储中完成 promotion,并仅保留清理后的 cxpublication-v1-* NDJSON。
  • 不使用 results server、Vercel storage、GCP、Neon、managed database 或 managed object store。

验证

  • 151 个 Python 约定/单元测试通过。
  • Matrix SHA-256:f1ca85f9689922b90edd5767b9ff2a902f6b896f32f68b2ca086dde3fd2157d0
  • Case catalog SHA-256:8e262178f770b0cdde12b7ec71604afd87251fa55685d4594f29717153ad6bbd
  • 重生成确认 58 个 shard、364 个可运行 case、244 个 unsupported case 和 1,600 个点位。
  • Actionlint、ShellCheck 与 git diff --check 通过。

Comment on lines +145 to +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/" >/dev/null 2>&1 \
|| cx_die "staging CollectiveX failed"

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 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)

  1. Setup job resolves matrix; writes experimental/CollectiveX/results/.shard_b300-deepep.json on the checkout with e.g. 24 cases (varied phase/dtype/routing/eplb across ep-core-v1 + ep-routing-v1).
  2. Sweep job on the b300 runner exports CX_SHARD_FILE=results/.shard_b300-deepep.json, checks out the repo, and calls launch_b300.sh.
  3. launch_b300.sh:34 -> cx_stage_repo rsyncs to $CX_STAGE_DIR/job_<id>/experimental/CollectiveX/ with --exclude='results/' --delete-excluded. The shard file is not copied.
  4. srun --container-workdir=$MOUNT_DIR/experimental/CollectiveX ... run_in_container.sh. cwd inside container = /ix/experimental/CollectiveX.
  5. run_in_container.sh:458 tests [ -f "results/.shard_b300-deepep.json" ] -> that resolves to /ix/experimental/CollectiveX/results/.shard_b300-deepep.json -> missing.
  6. Execution falls into the else branch at line 556+. It dispatches ${CX_BENCH} once with CX_MODE=normal, CX_PHASE=decode, CX_ROUTING=uniform, CX_DISPATCH_DTYPE=bf16, empty CX_CASE_ID, empty CX_SUITE, empty CX_WORKLOAD_NAME, empty CX_REQUIRED_PUBLICATION.
  7. One result JSON is produced with no case_id and mismatched identity; the other 23 scheduled cases never run.
  8. Aggregate job's make_bundle.py validate_expected_coverage computes missing_identity + missing + identity_mismatch against matrix_full.json and raise 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:

  1. 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/' ...
  2. 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"
  3. 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.

Comment thread experimental/CollectiveX/env_capture.py Outdated
Comment on lines +178 to +180
elif _run(["ibstat", "-l"]):
devices = [d.strip() for d in _run(["ibstat", "-l"]).splitlines() if d.strip()]
return {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 _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 AGAIN

The 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:

  1. Node has ibstat in $PATH but no ibv_devinfo (a real config: MI355X-style stacks with ibstat only).
  2. First call: _run(["ibstat", "-l"]) succeeds → returns "mlx5_0\nmlx5_1\n" → elif condition is truthy.
  3. Second call: a transient nonzero exit (e.g. ibstat racing an IB-driver reload, timer wraparound, PID-namespace hiccup) → out.returncode != 0_run returns None.
  4. None.splitlines()AttributeError: 'NoneType' object has no attribute 'splitlines' → Python exits nonzero → set -e aborts run_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.

Comment on lines +260 to +286
"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"
),

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 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:

  1. _expected_case_identity(matrix_case)"hidden" in case is true (value ""), so identity["hidden"] = int("" or 7168) = 7168. Same for topk/experts (8/256). "nodes" in case is true, identity["nodes"] = int("1") = 1. Expected identity contains {hidden: 7168, topk: 8, experts: 256, nodes: 1, ...}.
  2. _actual_case_identity(failed_doc) (the failed-case branch, line 184-195) copies failure.case verbatim, calls _expected_case_identity. None of hidden/topk/experts/nodes are in that dict, so the if field in case: guard skips all four. Actual identity contains everything except the four scheduled shape fields.
  3. _identity_differences iterates the expected identity's items; actual_identity.get("hidden") is None, None != 7168 -> hidden=None!=7168. Same for the other three.
  4. validate_expected_coverage (line 294-298) hits the differences branch, appends the case_id to identity_mismatch, and does not add it to actual{}. Then missing = set(expected) - set(actual) (line 301) also contains that case_id. Line 319 raises the dual-report SystemExit.

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.

@Oseltamivir Oseltamivir force-pushed the collectivex branch 4 times, most recently from 758fa52 to 1c5b901 Compare July 4, 2026 01:11
Comment thread experimental/CollectiveX/tests/test_sampling_contract.py Fixed
@Oseltamivir Oseltamivir force-pushed the collectivex branch 3 times, most recently from 7e5f80a to 28cbac4 Compare July 4, 2026 03:21
@functionstackx functionstackx changed the title CollectiveX v1: cross-vendor EP benchmark suite CollectiveX v1: cross-vendor EP benchmark suite / CollectiveX v1:跨厂商 EP 基准测试套件 Jul 4, 2026
@functionstackx functionstackx changed the title CollectiveX v1: cross-vendor EP benchmark suite / CollectiveX v1:跨厂商 EP 基准测试套件 CollectiveX v1: cross-vendor EP benchmark suite / CollectiveX v1:跨厂商 EP 基准测试套件 / CollectiveX v1: 크로스 벤더 EP 벤치마크 스위트 Jul 4, 2026
@Oseltamivir Oseltamivir changed the title CollectiveX v1: cross-vendor EP benchmark suite / CollectiveX v1:跨厂商 EP 基准测试套件 / CollectiveX v1: 크로스 벤더 EP 벤치마크 스위트 Finalize CollectiveX v1 cross-vendor EP benchmark suite / 完成 CollectiveX v1 跨厂商 EP 基准测试套件 Jul 4, 2026
@Oseltamivir Oseltamivir force-pushed the collectivex branch 15 times, most recently from 4ff5841 to bffd8d6 Compare July 4, 2026 15:05
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

@Oseltamivir Oseltamivir Jul 8, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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" ] \

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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._-]+$ ]] \

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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) }}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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).
@socket-security

socket-security Bot commented Jul 8, 2026

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addedpyyaml@​6.0.2100100100100100

View full report

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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

2 participants