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
Remove the two low-latency logfmt10 precision profiles (d-bf16.c-logfmt10-dynamic64 and d-fp8-e4m3fn-b128-f32-fused.c-logfmt10-dynamic64) atomically across the identity registry, capability ledger, schema enums, byte-provenance maps, and the DeepEP/UCCL supported-profile sets. The logfmt10 wire format is no longer scheduled or publishable; the inert use_logfmt native-API keyword wiring is kept because it pins the DeepEP/UCCL low_latency_combine signature independently of any logfmt profile. Regenerate the frozen digests and counts whose inputs shrank with the two profiles gone: precision targets 94/59/35 → 62/33/29, the full-matrix case tuple, shard-count Counter, low-latency case count 80 → 48, unsupported terminal-outcome count 173 → 167, and the execution-plan / full-matrix / case-catalog SHA-256 anchors. The publisher precision-cohort fixture moves from the (now bf16-only) low-latency profiles to the normal-mode dispatch×combine 2×2, preserving its cohort topology. 移除两个低延迟 logfmt10 精度档位,在身份注册表、能力账本、schema 枚举、 字节溯源映射以及 DeepEP/UCCL 支持档位集合中原子化删除。logfmt10 线格式不再 被排程或发布;保留 use_logfmt 原生 API 关键字接线(它独立于任何 logfmt 档位, 用于固定 DeepEP/UCCL low_latency_combine 签名)。重新生成随之收缩的冻结摘要与 计数:精度目标 94/59/35 → 62/33/29、全矩阵用例元组、分片计数 Counter、低延迟 用例数 80 → 48、不支持终态数 173 → 167,以及执行计划/全矩阵/用例目录 SHA-256。 发布器精度队列夹具从(现仅 bf16 的)低延迟档位改为普通模式 dispatch×combine 2×2,保持其队列拓扑不变。
Reduce ep-precision-normal-v1 from six scheduled profiles to two (d-fp8-e4m3fn-b128-f32-prequantized.c-bf16 and d-fp8-e4m3fnuz-b128-f32-prequantized.c-fp8-e4m3fnuz-direct-cast-noscale), spanning one bf16-combine and one fp8-combine codec across the e4m3fn and e4m3fnuz families. The four dropped profiles stay defined in the V1_PRECISION_PROFILES registry (defined-but-unscheduled) so their schema enums and capability targets survive for republication of already collected evidence. - identity.V1_NORMAL_PRECISION_PROFILE_IDS trimmed to the two kept ids (order-preserving); the frozen suite contract follows automatically. - configs/suites.yaml ep-precision-normal-v1.precision_profiles matched order-for-order to satisfy the frozen-catalog equality gate. - test_precision_scheduling registry-equality relaxed to a subset check plus an explicit defined-but-unscheduled set; expected-case counters filtered to the scheduled profile ids. - regenerated the execution-plan, full-matrix and case-catalog digests, the unsupported terminal-outcome count (167 to 159) and the matrix case/point tuples. 221 tests green. 将 ep-precision-normal-v1 的常规精度扫描从六个受调度配置削减为两个 (d-fp8-e4m3fn-b128-f32-prequantized.c-bf16 与 d-fp8-e4m3fnuz-b128-f32-prequantized.c-fp8-e4m3fnuz-direct-cast-noscale), 覆盖 e4m3fn 与 e4m3fnuz 两族各一个 bf16-combine 与 fp8-combine 编解码。 被移除的四个配置仍保留在 V1_PRECISION_PROFILES 注册表中(已定义但不调度), 使其 schema 枚举与能力目标得以留存,以便对已采集证据进行再发布。 - identity.V1_NORMAL_PRECISION_PROFILE_IDS 保序削减为保留的两个 id; 冻结的套件契约自动跟随。 - configs/suites.yaml 中对应字段按序对齐以通过冻结目录相等性校验。 - 放宽 test_precision_scheduling 的注册表相等断言为子集校验加显式的 已定义但不调度集合;预期用例计数按受调度配置 id 过滤。 - 重新生成执行计划、完整矩阵与用例目录摘要、不支持终态计数(167 至 159) 以及矩阵用例/采样点元组。 221 项测试通过。
Introduce tests/ep_backend.py holding the EPBackend abstract base class plus the WorkloadSpec / RankInputs dataclasses. This is the target shape for the EP dispatch/combine benchmark: the base owns input generation (make_inputs), problem encoding (make_problem), and the two Pass-2 timing branch rules (benchmark_dispatch/benchmark_combine), while subclasses supply only the transport (create_buffer/dispatch/stage/combine). make_inputs runs before create_buffer, resolving the historical inversion where buffers were sized in __init__ before any input existed: buffer sizing needs the ladder numbers, not the input tensors. The module has zero consumers yet — nothing imports it at runtime, so the 221-test baseline is unchanged. torch and routing are imported lazily inside device-touching methods (mirroring ep_harness) so the module imports under the CPU-only test environment. 新增惰性的 EPBackend 抽象基类 引入 tests/ep_backend.py,包含 EPBackend 抽象基类以及 WorkloadSpec / RankInputs 数据类。这是 EP dispatch/combine 基准测试的目标结构:基类负责 输入生成(make_inputs)、问题编码(make_problem)以及两条 Pass-2 计时分支 规则(benchmark_dispatch/benchmark_combine),子类只需提供传输实现 (create_buffer/dispatch/stage/combine)。 make_inputs 在 create_buffer 之前运行,纠正了以往在任何输入存在之前就于 __init__ 中构建缓冲区的顺序倒置:缓冲区大小只需要阶梯数值,而非输入张量。 该模块目前没有任何调用方——运行时不会被导入,因此 221 项测试基线保持不变。 torch 与 routing 在触及设备的方法内惰性导入(与 ep_harness 一致),使该模块 可在仅 CPU 的测试环境中导入。
Restructure the EP dispatch/combine benchmark so every backend subclasses the EPBackend ABC and the driver drives a real lifecycle instead of a duck-typed protocol. Buffer construction moves out of __init__ into create_buffer(spec), which consumes what make_inputs(args) produces — resolving the historical inversion where buffers were sized before inputs existed. run_sweep now calls make_inputs -> create_buffer and reads all provenance/contract flags off the backend after the buffer is built. All six adapters (nccl, deepep, deepep-v2, mori, deepep-hybrid, uccl) subclass EPBackend via super().__init__, which sets self.mode and validates SUPPORTED_MODES. Buffer bodies are moved verbatim behind a local-alias line with an `assert spec.max_tokens_per_rank <= cap` guard. probe_precision, the second protocol consumer, calls create_buffer(make_inputs(args)) after construction. Drop the unused abstract supported_profiles hook — each adapter resolves its own profile set inline via ep_precision.resolve_precision. Scrape-anchor tests updated in lockstep: the ElasticBuffer/JIT/cache-dir and HybridEPBuffer ordering pins now walk create_buffer, and the combine-timing and post=finish_dispatch fragments target ep_backend.py. 将 EP dispatch/combine 基准重构为每个后端都继承 EPBackend 抽象基类,驱动器执行 真正的生命周期,而非鸭子类型协议。缓冲区构造从 __init__ 移到 create_buffer(spec), 由 make_inputs(args) 的产物驱动,纠正了过去"先建缓冲区后有输入"的顺序倒置。 run_sweep 现按 make_inputs -> create_buffer 顺序调用,并在缓冲区建成后从后端读取 全部 provenance/契约标志。 六个适配器(nccl、deepep、deepep-v2、mori、deepep-hybrid、uccl)均通过 super().__init__ 继承 EPBackend,由基类设置 self.mode 并校验 SUPPORTED_MODES。 缓冲区代码体在局部别名行后原样移动,并加 `assert spec.max_tokens_per_rank <= cap` 守卫。第二个协议消费方 probe_precision 在构造后调用 create_buffer(make_inputs(args))。 移除未实现的抽象 supported_profiles 钩子——各适配器在 __init__ 中经 ep_precision.resolve_precision 内联解析各自的 profile 集合。 同步更新源码抓取测试:ElasticBuffer/JIT/缓存目录 及 HybridEPBuffer 的顺序断言 现遍历 create_buffer;combine 计时与 post=finish_dispatch 片段改指向 ep_backend.py。
Pure dedup after the ABC cutover: delete per-adapter methods whose bodies the base class now reproduces exactly. - oracle_dispatch_payload: deleted from deepep, deepep-v2, mori, hybrid, uccl (all were the base's encode_dispatch(...).semantic). nccl keeps its identity override (the wire payload is what the oracle built). - precision_evidence: deleted from every adapter. The base threads uint8_storage=self._uint8_scale_storage, which is False by default and True for hybrid (via its class flag) — matching each adapter's former literal. - make_problem: deleted from nccl, deepep, deepep-v2, uccl (base reproduces them; deepep-v2 keeps its int-dtype via a new _topk_idx_dtype override returning deep_ep.topk_idx_t). mori/hybrid keep make_problem (extra scales/indices fields and conditional dispatch_x). - buffer_cap/finalize: deleted from nccl (both matched the base). deepep/uccl keep buffer_cap (low-latency cap) and finalize (LL teardown / os._exit); mori keeps finalize (os._exit); deepep-v2/hybrid keep finalize (rc=1 / JIT teardown). No behavioral change: every deletion was verified equal to the base, and the dead problem.layout field (never read) is dropped with nccl.make_problem. ABC 切换后的纯去重:删除那些基类已逐字复现的各适配器方法。 - oracle_dispatch_payload:从 deepep、deepep-v2、mori、hybrid、uccl 删除(均为基类的 encode_dispatch(...).semantic)。nccl 保留其恒等重写(线上载荷即 oracle 所构造)。 - precision_evidence:从所有适配器删除。基类透传 uint8_storage=self._uint8_scale_storage,默认 False、hybrid 经类标志为 True, 与各适配器原有字面量一致。 - make_problem:从 nccl、deepep、deepep-v2、uccl 删除(基类可复现;deepep-v2 通过新增 _topk_idx_dtype 重写返回 deep_ep.topk_idx_t 保留其整型 dtype)。mori/hybrid 保留 make_problem(含额外 scales/indices 字段与条件式 dispatch_x)。 - buffer_cap/finalize:从 nccl 删除(均与基类一致)。deepep/uccl 保留 buffer_cap (低延迟容量)与 finalize(LL 拆除 / os._exit);mori 保留 finalize(os._exit); deepep-v2/hybrid 保留 finalize(rc=1 / JIT 拆除)。 无行为变化:每处删除均已核验与基类等价;随 nccl.make_problem 一并移除从未被读取的 无用 problem.layout 字段。
Add torch-free unit tests for the shared base-class plumbing the driver relies on, using a minimal in-process fake backend: - make_inputs: rc=2 early-return when the buffer cap can't serve the conditioning ladder and when the token ladder is empty; on success, ladder/conditioning sizing, max-tokens-per-rank span, and canonical-measured workload registration (conditioning shapes never register). _build_rank_inputs is overridden so the path needs no routing/activation tensors. - timed_components: the roundtrip / +dispatch,combine / +stage / roundtrip-only matrix, including roundtrip_only suppressing the stage flag. - benchmark_dispatch/combine branch selection: post=cleanup only when dispatch_needs_combine_cleanup; combine supplies a per-iter re-dispatch `pre` when combine_needs_redispatch, else dispatches once and reuses the handle. time_us is stubbed to capture pre/post without running a timing loop. - __init__ mode validation (SUPPORTED_MODES) and __init_subclass__ empty-name enforcement. 为驱动器所依赖的基类公共管线新增无需 torch 的单元测试,使用进程内最小假后端: - make_inputs:当缓冲区容量无法承载 conditioning 阶梯、以及 token 阶梯为空时的 rc=2 提前返回;成功路径下的阶梯/conditioning 定尺、max-tokens-per-rank 跨度, 以及仅对 canonical 实测点登记 workload(conditioning 形状不登记)。重写 _build_rank_inputs 使该路径无需 routing/activation 张量。 - timed_components:roundtrip / +dispatch,combine / +stage / 仅 roundtrip 矩阵, 含 roundtrip_only 抑制 stage 标志。 - benchmark_dispatch/combine 分支选择:仅当 dispatch_needs_combine_cleanup 时 post=清理;combine 在 combine_needs_redispatch 时提供逐迭代重派发的 pre, 否则派发一次并复用句柄。time_us 被打桩以捕获 pre/post 而不运行计时循环。 - __init__ 模式校验(SUPPORTED_MODES)与 __init_subclass__ 空 name 强制。
UCCL's uccl_deepep.Buffer is a DeepEP-API clone, so ep_deepep.py and ep_uccl.py carried a byte-identical ~250-line operational surface (mode handling, dispatch/combine, expert-packed inspection, fp8 dequantization). Extract it into a new deep_ep-free DeepEPFamilyBackend base; each concrete adapter keeps only its native buffer import, create_buffer provenance, and teardown. The base imports no vendor module, so importing ep_uccl never transitively requires deep_ep (absent on the UCCL image). Re-target the three adapter-genuineness scrape-tests to follow the moved code into the base file rather than deleting them, preserving all CPU-side coverage of the un-runnable native low-latency calls. Suite stays 239 green. 重构:通过 family 基类共享 DeepEP/UCCL 适配器公共代码 UCCL 的 uccl_deepep.Buffer 是 DeepEP API 的克隆,因此 ep_deepep.py 与 ep_uccl.py 存在约 250 行逐字节相同的操作代码(模式处理、dispatch/combine、 expert-packed 检查、fp8 反量化)。将其提取到不依赖 deep_ep 的 DeepEPFamilyBackend 基类;各具体适配器仅保留各自的原生 buffer 导入、 create_buffer 溯源与清理逻辑。基类不导入任何厂商模块,因此导入 ep_uccl 不会间接引入 deep_ep(UCCL 镜像中并不存在)。 将三个适配器真实性源码扫描测试重定向到基类文件以跟随迁移后的代码,而非删除, 从而保留对不可运行的原生 low-latency 调用的全部 CPU 侧覆盖。测试保持 239 通过。
Lift the B300 EP16 topology wall now that cross-node dispatch/combine is proven over the 400G RoCE GPU-fabric rails (world=16 all-reduce PASS on adjacent B300 nodes); the prior wall tested only the storage-IB rails. - capability.py: clear TOPOLOGY_CELL_OVERRIDES (B300 EP16 -> runnable for deepep, deepep-v2, deepep-hybrid, nccl-ep; uccl/mori stay unsupported) - collectivex-sweep.yml: add B300 network overlay slot (RoCE selector kept in the write-only per-SKU secret, never in-tree) - publisher.py: recompute CANONICAL_FULL_V1 matrix + case-catalog digests - tests: update matrix count contract (49->53 shards, 172 runnable) and B300 EP16 disposition expectations 启用 B300 EP16 跨节点(经 RoCE 网络) 已验证跨节点 dispatch/combine 可经 400G RoCE GPU 网络链路运行(相邻 B300 节点 world=16 all-reduce 通过);此前的限制仅测试了存储 IB 链路。 - capability.py:清空 TOPOLOGY_CELL_OVERRIDES(B300 EP16 对 deepep、 deepep-v2、deepep-hybrid、nccl-ep 变为可运行;uccl/mori 仍不支持) - collectivex-sweep.yml:新增 B300 网络覆盖槽位(RoCE 选择器仅存于只写的 分 SKU secret,绝不入库) - publisher.py:重新计算 CANONICAL_FULL_V1 矩阵与用例目录摘要 - tests:更新矩阵计数契约(49->53 个分片,172 个可运行)及 B300 EP16 处置预期
B300 EP16 runs over the RoCE GPU-fabric: nccl-ep and the DeepEP Hybrid control path (native DOCA transport) execute there and stay runnable. DeepEP V1/V2 cannot -- their internode path is NVSHMEM-IBGDA, which needs GDRCopy /dev/gdrdrv, and that char device is unprovisioned on B300 hosts (module loaded, no device node; gdr_open returns NULL). Wall (b300, deepep|deepep-v2, 16) at the topology layer and keep their precision cells unsupported. Cascade the frozen digests/counts: full V1 matrix is now 51 shards / 322 cases / 167 runnable / 155 unsupported; refresh the publisher canonical matrix + case-catalog digests, the qualification execution-plan digests, and the pinned CPU-test expectations. B300 EP16 在 RoCE GPU 光纤上运行:nccl-ep 与 DeepEP Hybrid 控制路径 (原生 DOCA 传输)可执行,保持可运行。DeepEP V1/V2 不行——其跨节点 路径为 NVSHMEM-IBGDA,需要 GDRCopy /dev/gdrdrv,而该字符设备在 B300 主机上未配置(模块已加载但无设备节点;gdr_open 返回 NULL)。在拓扑层 封禁 (b300, deepep|deepep-v2, 16),并将其精度单元保持为 unsupported。 级联更新冻结摘要与计数:完整 V1 矩阵现为 51 分片 / 322 用例 / 167 可运行 / 155 不支持;刷新 publisher 规范矩阵与用例目录摘要、 资格执行计划摘要,以及固定的 CPU 测试期望值。
Collapse the publication requirement from three independent allocations to one. Set REQUIRED_ALLOCATIONS=1 so the count-based eligibility, promotion-coverage, promote, and bundle-id gates all repoint to a single qualified run at qualification index 1. Drop the cross-allocation stability apparatus entirely (it cannot be measured from one allocation): remove the per-point stability block, the stable_p50/stable_p99/ p50_max_min_ratio/p99_max_min_ratio eligibility fields, the unstable-p50/ unstable-p99 reasons, the _ratio helper, and the pointStability schema def. Keep within-run trial drift/outlier diagnostics and cross-backend cohort ranking, collapsing their run dimension to REQUIRED_ALLOCATIONS. The qualification-index machinery (indices 1..3) is untouched: it defines deterministic execution orderings, not a publication count. v1 publication pins to index 1. Full unittest battery: 239 OK. 从三次独立分配收敛为单次发布 v1。将 REQUIRED_ALLOCATIONS 置为 1,使 基于计数的资格、晋升覆盖、晋升与 bundle-id 门槛统一指向资格索引 1 的 单次合格运行。彻底移除跨分配稳定性机制(单次分配无法测量):删除 每点 stability 块、stable_p50/stable_p99/p50_max_min_ratio/ p99_max_min_ratio 资格字段、unstable-p50/unstable-p99 原因、_ratio 辅助函数以及 pointStability schema 定义。保留运行内 trial 漂移/离群 诊断与跨后端 cohort 排名,将其运行维度收敛为 REQUIRED_ALLOCATIONS。 资格索引机制(索引 1..3)保持不变:它定义确定性执行顺序,而非发布 计数。v1 发布固定使用索引 1。
Collapse the publish-v1 job to a single source run. Replace the
publish_run_ids input (three comma-separated IDs) with publish_run_id,
drop the count-of-three and uniqueness checks and the shared-source-SHA
loop, pin the per-run release marker to qualification_index == 1, and
require the aggregate qualification-index set to be exactly {1}. The
setup job's qualification-index input and per-index release-marker
digest check are unchanged (layer 2). Update test_sampling_contract.py
publish-job string assertions to the single-run messages.
Full unittest battery: 239 OK.
将 publish-v1 作业收敛为单一来源运行。以 publish_run_id 替换
publish_run_ids(三个逗号分隔的 ID),移除计数为三与唯一性检查以及
共享来源 SHA 的循环,将每次运行的发布标记固定为 qualification_index
== 1,并要求聚合资格索引集合恰为 {1}。setup 作业的资格索引输入与
按索引的发布标记摘要校验保持不变(第二层)。同步更新
test_sampling_contract.py 中 publish 作业的字符串断言为单次运行消息。
Update the tracked docs to match the single-allocation publication: README, methodology, and README_zh now describe accepting one successful first-attempt run ID at qualification index 1 (not three run IDs at indices 1, 2, 3). Replace the cross-allocation p50/p99 repeat-stability language in the decision-grade contract with the retained within-run trial drift/outlier diagnostics. 更新受版本管理的文档以匹配单次分配发布:README、methodology 与 README_zh 现描述接受一个位于 qualification index 1 的成功首次尝试 run ID(而非索引 1、2、3 的三个 run ID)。将 decision-grade 契约中 的跨分配 p50/p99 重复稳定性表述替换为保留的运行内 trial 漂移/离群 诊断。
Relax v1 promotion so a v1-tagged run can publish any subset of the canonical matrix, not only the full 8-SKU matrix. A partial promotion carries coverage_scope="partial" + covered_skus and is validated by canonical membership (every case is a real canonical case with the canonical disposition, proper subset) rather than the full catalog-sha / matrix-id pin. Quality gates are unchanged: partial cohorts must still be decision-grade and runnable cases must still succeed. Full promotions keep the exact catalog pin, fixed cohort counts, and non-empty rankings. 放宽 v1 发布门槛:带 v1 标签且成功的运行可发布规范矩阵的任意子集, 而不再要求覆盖全部 8 种 SKU。部分发布携带 coverage_scope="partial" 与 covered_skus,通过规范成员校验(每个用例均为真实规范用例、判定一致、 且为真子集)而非完整目录哈希/矩阵 ID 绑定。质量门槛不变:部分队列仍须 达到 decision-grade,可运行用例仍须成功;完整发布保留精确目录绑定、 固定队列数量与非空排名要求。
Model the release version as a first-class numeric field N (1, 2, 3, ...),
not a hardcoded "v1" tag. The release_tag workflow input becomes a string
validated as 'unversioned' or a positive integer; the release marker carries
"version": N plus a coverage_scope ("full"/"partial"); publish and refresh
discover the version version-agnostically from the marker/artifact name via
regex and re-emit it in cxrelease-<N>-* / cxpublication-<N>-* names. A new
autopublish job self-publishes a successful first-attempt versioned sweep so
the frontend can discover many runs of a version JIT. The frozen data-format
literals (collectivex.release-tag.v1, collectivex.public.v1, the
collectivex_public_v1_<digest>.ndjson filename) are schema-version, shared
across releases, and stay unchanged.
将发布版本建模为一等的数值字段 N(1、2、3……),而非硬编码的 “v1” 标签。
release_tag 工作流输入改为字符串,校验为 'unversioned' 或正整数;发布标记
携带 "version": N 及 coverage_scope("full"/"partial");publish 与 refresh
通过正则从标记/产物名中与版本无关地发现版本号,并在 cxrelease-<N>-* /
cxpublication-<N>-* 名称中重新输出。新增 autopublish 作业让一次成功的
首次尝试带版本 sweep 自行发布,便于前端即时发现某版本的多个运行。冻结的
数据格式字面量(collectivex.release-tag.v1、collectivex.public.v1、
collectivex_public_v1_<digest>.ndjson 文件名)属于 schema 版本,跨发布共享,
保持不变。
Add an --exclude-skus comma-list to sweep_matrix.resolve_matrix (and the sweep_matrix.py CLI + collectivex-sweep.yml exclude_skus dispatch input) so a versioned "tagged + success" run can drop whole runner pools whose clusters are unavailable and still publish the SKUs it can exercise. Excluding a pool only omits its cells; every surviving requested case stays byte-identical to the full matrix, so the publisher's canonical-catalog binding still forbids inventing or reclassifying cases. Disjoint from --only-sku; unknown pools are rejected. The release marker already records coverage_scope=partial for any non-canonical matrix digest and autopublish already accepts full OR partial, so this completes the incomplete-matrix gate end to end. 为部分版本化 sweep 增加 --exclude-skus:允许在集群不可用时剔除整个 runner 池,仍以"打标签 + 成功"发布可运行的 SKU。剔除只省略对应单元格,存活的请求用例 与完整矩阵逐字节一致,发布器的规范目录绑定仍禁止伪造或重新分类用例。与 --only-sku 互斥,未知池会被拒绝。
Drop the first-attempt-only gate on promotion/autopublish so a re-run whose legs re-emit at one consistent attempt (e.g. attempt 2) can still publish. Provenance stays single-attempt: every included shard must share the same run and attempt; only the "attempt must be 1" restriction is removed. - publisher: remove the run_attempt != 1 term from the promote gate; message now "qualification index 1 from a single versioned run" - workflow: drop github.run_attempt == '1' from the autopublish `if`; manual publish accepts any positive attempt - tests: cover promotion of a non-first-attempt run; update workflow string assertions 放开仅限首次尝试的发布门槛:重跑(各腿在同一次一致的尝试号,如第 2 次) 也可发布。来源仍为单次尝试——所有分片须同属一次运行且尝试号一致,仅移除 "尝试号必须为 1"的限制。
Promotion no longer aborts a whole run because some series or cohorts are non-decision-grade. The publisher computes each series' and cohort's quality verdict and attaches it to every one of them in the published dataset, then publishes everything: diagnostic series/cohorts ship labelled (status:"diagnostic" / decision_grade:false) with their eligibility.reasons, never dropped. The consumer (frontend) decides what to surface and may reveal flagged series. This splits selection (what to show) from validation (is the number real): validation stays on the backend, the display decision moves out. Honesty floor kept: _require_runnable_promotion_success (every runnable case physically produced clean data) and at least one decision-grade series are still required for a promoted "v1" to mean something. _require_promotion_cohorts full scope keeps only structural checks (required kinds present, exact counts, derived-chip count); the partial scope no longer requires surfaced cohorts to be decision-grade. Also fixes three latent bugs unmasked once the veto stopped aborting first: - schema trial_count const 192 -> 64 (single-run migration leftover; real data emits 64), plus the same stale value in a test fixture. - zero-logical-byte measured components (UCCL host-staging "stage": real latency, total_logical_bytes:0 -> 0.0 GB/s) could not satisfy the rate schema (percentiles, exclusiveMinimum:0) yet the validator requires a rate. Add ratePercentiles (minimum:0) for the two component rate fields; latency stays strict. - rankings-coverage validator now skips precision-pair cohorts (display-only paired contrasts) to match the ranking generator, which already skips them. Tests: the three promote-gate tests that asserted abort-on-non-decision-grade now assert mixed bundles promote and ship both verdicts, plus the retained all-diagnostic -> floor rejection; new schema contract test locks zero-byte rates and trial_count=64. Full CPU battery 243 green. Verified end-to-end on a real green run: promote -> schema-valid dataset shipping decision-grade + diagnostic series each with its verdict. 发布不再因为部分 series 或 cohort 非 decision-grade 就整体中止。publisher 计算 每个 series 和 cohort 的质量判定并将其附加到已发布数据集中的每一项上,然后发布 全部内容:诊断类 series/cohort 带标签发布(status:"diagnostic" / decision_grade:false)并附带 eligibility.reasons,绝不丢弃。由消费端(前端) 决定展示哪些,并可显示被标记的 series。这将“选择展示什么”与“校验数值是否真实” 分离:校验留在后端,展示决策移出。 保留诚实底线:_require_runnable_promotion_success(每个可运行 case 确实产出了 干净数据)以及至少一个 decision-grade series 仍是已发布“v1”的必要条件。同时 修复 veto 移除后暴露的三个潜藏缺陷:schema trial_count 常量 192->64、零逻辑 字节测量分量的 0.0 GB/s 速率(新增 ratePercentiles,minimum:0)、rankings 覆盖校验跳过 precision-pair cohort。全量 CPU 测试 243 通过。
… runs Mirror --max-nodes with an --ep-sizes / ep_sizes filter in sweep_matrix.resolve_matrix and the sweep workflow: a dispatch-time narrower that keeps only shards whose expert-parallel degree is in the given comma-list. Passing "8" drops EP16 so a single comprehensive run can co-schedule the 8-GPU SKUs' single-node EP8 with the GB200/GB300 two-node EP8, without dispatching any EP16 leg (EP16 walls on H100's zero-RDMA topology and would block all-green autopublish). Blank keeps every degree, so the canonical matrix stays byte-identical and its frozen digest does not drift; a filtered run publishes as a partial subset, exactly like --exclude-skus / --max-nodes.
…on probe Front-load the deletions that would otherwise break the CPU battery on every subsequent refactor commit, leaving a stable keep-set. - delete publisher.py (pure CLI leaf; only test_publisher imported it) - delete test_publisher, test_sampling_contract, test_deepep_v2_contract, test_precision_scheduling (AST/source-text/frozen-digest suites) - delete probe_precision.py (swept precision-dimension probe) - trim test_ep_precision_adapters to the behavioral resolution tests Battery: 48 tests OK.
…rtifacts Reset scope to the MVP: schedule benchmarks, execute, validate emissions, and upload neutral artifacts. Remove promotion, ranking, recommendation, channels, bundles, and the qualification-repeat machinery. The frontend owns all display and coverage decisions from the neutral matrix/result/summary artifacts. This deliberately breaks the current frontend publication reader; that migration is a separate effort. Schemas: delete the channel, private-bundle, and public-dataset schemas. Strip the precision, qualification-index, and allocation-stratum dimensions from raw-case and terminal-outcome. Keep raw-case, samples, and terminal-outcome as the neutral contract. Contracts: validate document shape with jsonschema and keep only the cross-file Python checks (scheduled-case-ID equality, detached-sample path/hash, one terminal per scheduled case, complete shard delivery). Fold the artifact-safety privacy boundary into validate_delivery and terminal emission, then delete artifact_safety.py. Matrix/capability/identity: drop the precision dimension and min/max_nodes; schedule EP8/EP16 via ep_degrees plus an ep-sizes filter. Relocate the FP8 precision catalog from identity to ep_precision, where it stays callable but dormant (default sweep is BF16 only). Remove required_publication from harness emission so its case identity matches the matrix. Tests trimmed to the neutral contract (four files, 731 LOC). CPU battery green; matrix gen -> extract -> validate-control round-trips clean.
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.
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通过。