Repository navigation
bench: add reproducible InferSwarm Phase 0 baseline harness - #1
Merged
Merged
Conversation
Tooling and instrumentation only for InferSwarm issue #2 (Phase 0 baseline). No distributed execution, and no benchmark results: this branch was developed without the target RTX 3060, so it claims no measurement. Harness (benchmarks/inferswarm_phase0/, entry point benchmarks/phase0_baseline.py) - Executes the exact B1-B5 sweep declared in the InferSwarm Phase-1 success criteria, passing every value the criteria fix explicitly: --nvfp4-backend is always stated (EngineConfig defaults it to "triton", not "auto"), and --moe-cache-auto is always stated (the CLI, not the dataclass, applies it). - CORRECTNESS_REFERENCE is a separate subcommand: fixed configuration, explicit resolved NVFP4 backend, fixed --moe-cache-size, --moe-cpu-layers 0, greedy, full output text retained. Never selected by speed, never a comparator. - Frozen workload manifest (JSON + schema) pinning per class the fixture, its sha256, output-token count, sampling, ignore_eos and chat-template settings. W1/W3/W4 fixtures come from issue #3; only a clearly-labelled smoke-test example ships here. - Precommitted protocol: 2 warmups + 10 measured generations per (arm, class), recorded execution order, distinct session ids, reversed traversal for a second session. Overrides require --dev-smoke and stamp the run NON-CANONICAL. - Provenance capture with explicit nulls-plus-reason, a refusal to start a canonical run on missing provenance, and a refusal of any model revision that is not a 40-hex commit SHA. - Raw artifacts: one JSONL line per generation (warmups tagged, never dropped) with full inter-token timings; run status derived from expected-vs-observed counts so a summary cannot hide a missing repetition. No ratio is computed and no baseline is selected by the runner. - Hardware profile subcommand (GPU identity, PCIe link gen/width, topology, ft bench bw) plus a single-expert NVFP4 decode-GEMV microbenchmark, marked diagnostic-only. Runtime instrumentation (the narrowest additions the above needs) - freetoken/engine/runtime_report.py: the engine's RESOLVED configuration, read back off the live engine rather than re-derived - resolved MoE backend, resolved NVFP4 backend and whether the flag was inert for the executing expert path, whether _auto_cpu_layers locked layers, resolved cache slots/bytes, and whether the Marlin 992-slot cap applies and whether it bound. Shipped on the readiness ack and served by a new read-only GET /v1/instrumentation. - Prefill is measured where prefill happens: CUDA events bracket the prefill model forward in Engine.forward_batch and are summed per request across chunked prefill, so prefill throughput is not prompt_tokens/TTFT. Off by default, behind FREETOKEN_INSTRUMENT_PREFILL; a multi-request prefill batch is marked shared rather than split. Tests: 156 new hardware-independent tests (subprocess/server/GPU mocked). Canonical issue: Zutfen-LLC/inferswarm#2 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UPpHsotcG7hXxggzHKoYb9
The format is only knowable once an engine has loaded the banks, so the provenance document said "server not started yet" for the life of the artifact. Backfill it from what the arms actually reported, and record per-arm disagreement explicitly: criteria section 3 rule 4 holds the weight format constant across arms, so a mismatch invalidates the campaign rather than being smoothed into one value. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UPpHsotcG7hXxggzHKoYb9
Review found that the harness could still produce an apparently canonical, COMPLETE, MEASURED artifact while a precommitted requirement had failed. The invariant it now enforces: a canonical-looking Phase-0 artifact exists only when every prerequisite, held-constant rule, workload-shape rule, instrumentation requirement, provenance requirement and repetition requirement was satisfied. Anything else is explicitly INVALID, INCOMPLETE or NON-CANONICAL. Campaign validity is now a first-class answer, separate from completeness (inferswarm_phase0/validity.py). run.json carries execution_status (COMPLETE / INCOMPLETE) alongside validity (VALID / INVALID / NON_CANONICAL) and a structured campaign_invalidations list with stable reason codes; the bare `canonical: true` boolean is gone, because it was the field a reader would mistake for a verdict. MEASURED now labels an observation, never the campaign. SUMMARY.md and run.json both lead with one of VALID CANONICAL CAMPAIGN / INVALID CANONICAL ATTEMPT / NON-CANONICAL DEVELOPER RUN / INCOMPLETE RUN, keyed off the overall campaign state rather than the repetition protocol alone -- --allow-missing-provenance leaves the protocol untouched and still makes a run non-canonical. `ft bench bw` is a session-level prerequisite, not a B2-local side effect (inferswarm_phase0/bench_bw.py). B2 resolves its fetch split from the profile AND B3's --moe-backend auto reads the same profile, so the refresh runs once before the sweep traversal in either direction -- a reversed session runs B3 first and would otherwise consume a stale profile. --no-bench-bw is refused for a canonical sweep; a failed command, an unreadable profile, or a profile benched on another card aborts before any server starts. The record pins the command, both timestamps, the return code, the GPU UUID, the resolved path, the profile contents and its sha256. Physical-GPU provenance is proven, not asserted (inferswarm_phase0/gpu.py). --gpu is required for a canonical run and resolved to a stable UUID through freetoken.gpu_select -- no second selector policy -- with both the UUID and the nvidia-smi index recorded; the resolved UUID is what every child process gets. After startup the UUID the engine reports for itself is compared with it, and a mismatch invalidates. The nvidia-smi query now asks for the index, without which a numeric selector cannot be correlated with a row. Workload shape and output length are contract, not commentary. A prompt outside its frozen class rule and a completion length that is not the requested one invalidate the canonical block; the observation is preserved and the prompt is never rewritten. The "~16,000" / "~128" tolerances the criteria leave informal are frozen in version control and reproduced in every artifact. Prefill instrumentation is a validity gate. Records are attributed by request uid (chatcmpl-<uid> against the record's own uid) rather than "newest above a sequence floor"; where the id shape is unrecognized, more than one candidate is ambiguous rather than resolved by guessing. Unavailable, disabled, missing, ambiguous, shared-batch and unusable timings each carry a stable code and each invalidates a canonical measured repetition. Warmups are exempt: they are discarded by construction. CORRECTNESS_REFERENCE forces greedy request sampling on the same frozen prompt fixture and records the override. --sampling-defaults none was not sufficient: the manifest states sampling in every request body and a request-level value beats a server default, so a realistic performance sampling would have made the correctness reference sampled -- with no seed to make it reproducible. The performance sweep keeps the manifest's frozen sampling. The single-expert microbenchmark now measures a single expert: top_k=1, timed directly. The grouped top-k step is retained as a separately named diagnostic and is never divided by top_k, because experts inside a grouped call execute concurrently. Both it and the new device-memory-bandwidth benchmark bind the process through FreeToken's own gpu_select path and record the UUID they actually bound, refusing rather than misattributing. Device (VRAM) bandwidth is measured, which issue #2 asks for and `ft bench bw` does not provide -- its ceilings are host DRAM and the PCIe link. A device-resident D2D copy over a working set far beyond L2, CUDA-event timed after warmup, every repetition reported, byte accounting stated both ways, labelled MEASURED and diagnostic-only. Provenance refusals: a dirty FreeToken checkout cannot be reproduced from its commit SHA, so a canonical run refuses it and names the modified paths; --inferswarm-commit must be a full 40-hex SHA; a Hugging Face snapshots/<sha> path and its models--<org>--<name> cache entry are reconciled with --model-revision / --model-repository and a disagreement is a refusal, while a non-snapshot path records "cannot cross-check" rather than guessing. Resolved configuration is audited against criteria section 2.3, and the report gained the fields it was missing: the resolved --max-prefill-length and --cache-type (both on SchedulerConfig, and --cache-type is rewritten by _adjust_config) and the resolved hybrid fetch fraction. A missing or null required field invalidates, as does a held-constant value differing across arms or B3 resolving to a MoE backend -- or an NVFP4 expert path -- that coincides with neither B1 nor B2. --dry-run now reports what would refuse the run, so a plan cannot read "CANONICAL" while it would abort. Tests: 169 new hardware-independent tests (294 in tests/benchmarks/, up from 125), covering every refusal and every invalidating path above. Canonical issue: Zutfen-LLC/inferswarm#2 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EbyUuzs7m7peuxDwfjALco
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Canonical issue: Zutfen-LLC/inferswarm#2 — ROADMAP Phase 0, "Establish reproducible RTX 3060 FreeToken baseline".
Rules implemented here are fixed in advance by InferSwarm's Phase-1 success criteria (§1.1, §2, §3, §9, §10, §13) and its benchmark contract. Neither document is duplicated into this repo;
benchmarks/README.mdlinks to them and covers usage only.No results are claimed by this PR
This PR claims no benchmark numbers of any kind. It was developed without the target RTX 3060, so nothing here was measured on the declared hardware, and no result directory is created — in this repo or in InferSwarm. The only committed text fixture anywhere in the diff is a smoke-test prompt explicitly labelled as not a workload.
This is tooling and instrumentation. No distributed execution, no second-GPU expert path, no Phase-1 candidate.
Review fixes (commit
50a91b9)Review found that the harness could still produce an apparently canonical,
COMPLETE,MEASUREDartifact while a precommitted requirement had failed. The invariant it now enforces:Campaign validity is now a first-class answer, separate from execution completeness (
inferswarm_phase0/validity.py).run.jsoncarriesexecution_status(COMPLETE/INCOMPLETE) alongsidevalidity(VALID/INVALID/NON_CANONICAL) and a structuredcampaign_invalidationslist with stable reason codes (bench_bw.failed,workload.prompt_shape_violation,prefill.ambiguous_records,gpu.mismatch,held_constant.mismatch, …). The barecanonical: trueboolean is gone — it was the field a reader would mistake for a verdict.MEASUREDnow labels an observation: repetitions that really happened keep it whatever the campaign verdict is, and that never promotes the campaign to a valid baseline. Bothrun.jsonand the first screen ofSUMMARY.mdlead with one ofVALID CANONICAL CAMPAIGN/INVALID CANONICAL ATTEMPT/NON-CANONICAL DEVELOPER RUN/INCOMPLETE RUN, with the reasons immediately below — keyed off the overall campaign state, not the repetition protocol alone (--allow-missing-provenanceleaves the protocol untouched and still makes a run non-canonical).ft bench bwis a session-level prerequisite, not a B2-local side effect (inferswarm_phase0/bench_bw.py). B2 resolves its per-step fetch split from the profile and B3's--moe-backend autoreads the same profile (engine._adjust_config→load_backend_recommendation), so the refresh runs once, before the sweep traversal, in either direction — a reversed session runs B3 first and would otherwise consume a stale profile.--no-bench-bwis refused for a canonical sweep; a failed command, an unreadable profile, or a profile benched on another card aborts before any server starts. The record pins the command, both timestamps, the return code, the selected GPU UUID, the resolved profile path, the profile's contents and its sha256, and checks the profile's owngpu.uuidagainst the campaign's card.Physical-GPU provenance is proven, not asserted (
inferswarm_phase0/gpu.py).--gpuis required for a canonical run and is resolved to a stable UUID throughfreetoken.gpu_select— the same selectorft serveandft bench bwuse, so no second policy exists — with the UUID and the nvidia-smi index recorded. The resolved UUID is what every child process receives: the servers, the bandwidth refresh, and both microbenchmarks. After each server is ready, the UUID the engine reports for itself (/v1/statsgpus, filled from its own bound device) is compared with it, and a mismatch invalidates. The nvidia-smi provenance query now asks forindex, without which a numeric selector cannot be correlated with a row.Workload shape and output length are contract, not commentary. A prompt outside its frozen class rule and a
completion_tokensthat is not the requestedmax_tokenseach invalidate the canonical block; the observation is preserved in full and the prompt is never rewritten or truncated. The criteria state W1/W2 as bounds and W3/W4 as ""; "" is not machine-checkable, so the tolerance is frozen in version control (±15 % / ±25 %) and reproduced verbatim in every artifact rather than chosen after seeing a fixture's token count.Prefill instrumentation is a validity gate. Records are attributed by request uid — the response id is
chatcmpl-<uid>and every instrumentation record is stamped with the same uid — rather than "newest record above a sequence floor". Where the id shape is unrecognized the sequence rule still applies, but more than one candidate is reported ambiguous instead of resolved by taking the newest, which would attribute another request's interval to this one. Unavailable endpoint, disabled instrumentation, no fresh record, ambiguity, shared batch and unusable timing each carry a stable code and each invalidates a canonical measured repetition. Warmups are exempt: §10 discards them by construction. For--dev-smoke, an explicit null plus reason remains acceptable and the run staysNON_CANONICAL.CORRECTNESS_REFERENCEforces greedy request sampling on the same frozen prompt fixture, and records the override alongside the manifest's own values.--sampling-defaults nonewas not sufficient: the manifest statestemperature/top_p/top_kexplicitly in every request body and a request-level value beats a server default, so a manifest whose frozen performance sampling is deliberately realistic would have made the correctness reference sampled — with no seed available to make that reproducible (§5.3). The performance sweep keeps the manifest's frozen sampling untouched.The single-expert microbenchmark now measures a single expert.
top_k = 1, one routed expert per call, timed directly. The groupedtop_kstep is retained as a separately named grouped/batched diagnostic and is never divided bytop_k: experts inside a grouped call execute concurrently, sostep_ms / top_kis an amortized throughput-like quantity the hardware never produced. Both it and the bandwidth benchmark bind the process through FreeToken's owngpu_selectpath and record the UUID they actually bound, refusing rather than misattributing — the old code builttorch.device("cuda")and merely recorded the requested selector.Device (VRAM) memory bandwidth is measured (
--device-bandwidth), which issue #2 asks for andft bench bwdoes not provide — its ceilings are host DRAM and the PCIe link. A device-resident D2D copy over a working set far beyond L2 (512 MiB per buffer), CUDA-event timed after warmup, every repetition reported individually, byte accounting stated both ways (read+write and read-only), labelledMEASUREDand diagnostic-only.Dirty-tree and model-pin safeguards. A canonical run refuses a dirty FreeToken checkout and names the modified paths — a dirty tree cannot be reproduced from its commit SHA, and recording only the filenames does not make it reproducible.
--inferswarm-commitmust be a full 40-hex SHA. If the checkpoint path is a Hugging Face.../snapshots/<sha>layout, that SHA and themodels--<org>--<name>cache entry are reconciled against--model-revision/--model-repositoryand a disagreement is a refusal; a non-snapshot path records "cannot cross-check" rather than guessing.Held-constant and resolved-configuration audit. The runtime report gained the §2.3 fields it was missing — the resolved
--max-prefill-lengthand--cache-type(both live onSchedulerConfig, and--cache-typeis rewritten by_adjust_configfor SWA/GDN models) and the resolved hybrid fetch fraction. A missing or null required resolved field invalidates, as does a held-constant value differing across arms, or B3 resolving to a MoE backend — or an NVFP4 expert path — that coincides with neither B1 nor B2. The resolved cache slots and KV page count are deliberately not held constant: §3 rule 2 asks for them to be reported, not equalized.--dry-runnow reports what would refuse the run, so a plan cannot printCANONICALwhile it would abort on a dirty tree or an unresolvable--gpu.What is now mechanically reproducible
--nvfp4-backendis stated on every arm becauseEngineConfig.nvfp4_backenddefaults to"triton", notauto— a row that only said "auto" in prose would silently run Triton and duplicate B4.--moe-cache-autois stated for the same reason: the CLI applies it when no sizing flag is given, the dataclass does not.autoselected, whether--nvfp4-backendwas inert for the executing expert path (decode_target="cpu"undercpu/hybrid, and under an offload run whose layers_auto_cpu_layerslocked), whether_auto_cpu_layersfired, resolved cache slots/bytes and coverage, the resolved hybrid fetch fraction, the resolved--max-prefill-lengthand--cache-type, and whether the Marlin 992-slot cap applies and whether it bound (established by re-solving the same pure plan uncapped, not by guessing from equality with 992). Served by a new read-onlyGET /v1/instrumentation.--nvfp4-backend; ifautoresolves totritonon the rig, the two are recorded as observations of one resolved configuration. Nothing installs or forces Marlin to make them distinct.Engine.forward_batchand are summed per request across chunked prefill (Scheduler._accumulate_prefill).prompt_tokens / TTFTis never computed — TTFT also contains transport, tokenization, template rendering, queueing, sampling, detokenization and the SSE hop. A multi-request prefill batch is markedshared_batchwith token counts withheld rather than split by an invented model.--dev-smokeand stamp the run NON-CANONICAL inrun.json, inSUMMARY.md, and in the--dry-runbanner.--gpu, a--model-revisionthat is not a 40-hex commit SHA, an--inferswarm-committhat is not one, or a local snapshot that contradicts either. Credential-shaped env names are excluded by name; the local model path stays out of the runtime report. The resolved expert weight format is backfilled from what the arms actually reported, and per-arm disagreement invalidates the campaign (§3 rule 4) rather than being smoothed into one value.execution_statusis derived from expected-vs-observed repetition counts plus the failure list, andvalidityis derived independently from the invalidation list, so neither a lost repetition nor a broken prerequisite can read as success. The runner computes no cross-configuration ratio and selects no baseline —CANONICAL_PERFORMANCE_BASELINEis chosen by a human from the completed campaign (§2.2), and mid-campaign ratios are prohibited (§10).CORRECTNESS_REFERENCEas a separate subcommand, so it can never be conflated with the performance baseline: fixed--moe-cache-size, explicit resolved--nvfp4-backend,--moe-cpu-layers 0,--sampling-defaults none, forced-greedy request sampling, full output text retained. Its self-consistency check is two runs under different session ids compared on the recordedoutput_sha256.nvidia-smi topo -m, CPU/RAM/OS, theft bench bwhost-DRAM/PCIe measurements, an optional device (VRAM) memory bandwidth benchmark, and an optional true single-expert NVFP4 decode-GEMV latency measurement plus its grouped-step diagnostic. Every microbenchmark block is marked diagnostic-only and is never mixed into a throughput claim.What still requires the RTX 3060
Everything that is a number. Specifically: pinning the exact
nvidia/Qwen3.6-35B-A3B-NVFP4upstream revision (§1.1 — inventing a SHA is a fabricated provenance record, so none is guessed here); the B1–B5 sweep itself and therefore the selection ofCANONICAL_PERFORMANCE_BASELINE; theCORRECTNESS_REFERENCEcapture and its greedy self-consistency check; the hardware profile, the VRAM-bandwidth and expert microbenchmarks; and the resolved-configuration facts that only exist at runtime — whatautopicks on sm_86, whether vLLM's Marlin donor symbols are usable, whether the 992-slot cap binds, whether_auto_cpu_layersfires on the host, and whatft bench bwactually recommends for NVFP4 experts on this card.W1–W4 fixtures
Coordinated with Zutfen-LLC/inferswarm#3, which supplies the realistic routing fixtures. This PR ships the container: the manifest schema, the validator, and the freezing helper (
phase0_baseline.py hash). It deliberately invents no W1/W3/W4 content — the only example manifest declarescanonical: false, holds one synthetic prompt labelled "SMOKE TEST FIXTURE — NOT a Phase-0 workload", and is refused by a canonical run.Tests run
New, hardware-independent (subprocess / server / GPU / HTTP all mocked):
tests/benchmarks/grew from 125 to 294 tests. Regression coverage was added for every review finding, including: canonical--no-bench-bwis impossible; a failed bench-bw prevents any server from starting; the refresh precedes B3 in a reversed session; a stale/unreadable/wrong-GPU profile cannot yield a canonical run; a dirty tree is rejected;--inferswarm-commitmust be a full SHA; an HF snapshot/revision mismatch is rejected; the GPU UUID is required, resolved and verified against the engine; a runtime GPU mismatch is rejected; the microbenchmark binds the requested GPU; the single-expert benchmark usestop_k=1; the grouped result is never called single-expert; a non-greedy manifest cannot makeCORRECTNESS_REFERENCEsampled; prompt-shape and completion-length violations invalidate; stale/missing/ambiguous/shared prefill invalidates; a missing resolved-configuration field invalidates (parametrized over the whole required list); expert-quant and held-constant disagreements invalidate; an unexpected B3 resolution invalidates; the summary uses overall campaign validity; a canonical campaign cannot finishVALIDwhile any invalidation exists; and the device-bandwidth output is correctly labelled and structured.The full suite could not be run in the container this commit was produced in.
pytest tests/ --ignore=tests/e2eaborts at collection there with 77 collection errors, allModuleNotFoundErrorfortorch,huggingface_hubandfastapi: the container has no CUDA toolchain andtorchis not installable through its proxy (download.pytorch.orgis blocked, and the PyPI wheel installs but cannot loadlibcublasLt.so). Onlytests/benchmarks/(294) andtests/daemon/(31) collect and run there, and both are green. Consequently the previously reported "3flashinfer-environment failures, identical on the base branch" could not be re-verified in this session — that comparison stands from the earlier run but is not re-attested here, and the full suite should be re-run on a machine with torch before merge. Thetests/engine/,tests/scheduler/andtests/server/additions in this commit are hardware-independent by construction (the runtime report is built from a fake engine's attributes, as its module docstring explains) but were not executed here.Remaining gaps, stated rather than papered over
A/B/A/Binterleaving governs the Phase-1 candidate-vs-baseline comparison, where both arms are alive at once. Phase 0's five arms are five server configurations, so the sweep is config-major with oneft serveper arm; interleaving would mean a model reload per generation. The realized order is recorded, and--reverse-orderprovides the reversed second-session traversal. Called out inprotocol.py.R_c/R_agg, and the §10 noise-floor guard (baseline CV > 5 %) belong after the campaign completes; the runner emits per-block min/median/max/IQR/CV over the preserved raw repetitions and nothing comparative. A separate analysis step is still needed before a Phase-1 verdict.ignore_eos, output hashes, full text). It is not the C1/C2/C3 Phase-1 instrumentation — per-layer MoE outputs, router selections at decode step 0, step-0 logit vectors — which is not reachable over HTTP and is not attempted here. The CLI says so on every reference run.--moe-cache-autoand the harness offers no way to lower the cache below it;--memory-ratio/--kv-reserve-tokensare held constant across arms and that agreement is now checked against the resolved configuration rather than assumed from the flags. §3 rule 2's supplementary--num-tokens-pinned baseline run is a Phase-1 concern and is not implemented.--moe-cache-sizethe report says "unknown, with reason" rather than guessing, because the cap rejects an over-large size instead of clamping it.FREETOKEN_INSTRUMENT_PREFILLchanges the served process (two extra CUDA events per prefill batch). It is off by default, the harness enables it identically on every arm, and each arm now confirms from the server that it actually took effect — so it cannot advantage one configuration. It is recorded in the run's provenance, and a campaign comparing against numbers taken without it would not be like-for-like.--allow-missing-provenanceexists as an escape hatch and forces the run non-canonical; it is not a way to publish a baseline.Follow-up benchmark-integrity fixes (
0b3f88e)--bench-bw-dtype nvfp4; alternate formats remain available only to explicitlyNON_CANONICALdeveloper smoke runs.0.0fixed-cap fallback instead of a positive resolved fraction./phase0-runs/is ignored, and the documented hardware profile now writes tophase0-runs/hardware-profile.json, so session 1's own artifacts cannot dirty session 2's clean-tree preflight.Verification:
tests/benchmarks/310 passed;tests/daemon/31 passed (FastAPI supplied to the lightweight test environment). No Phase-0 measurements or result directories were produced. The remaining work is the real RTX 3060 hardware profile and B1-B5 validation after discovering the GPU UUID and pinning the actual upstream Qwen checkpoint revision.