Throughput-aware scheduling + ring-plan service + signed model registry & generic ModelRuntime adapter#6
Throughput-aware scheduling + ring-plan service + signed model registry & generic ModelRuntime adapter#6onlyzhynx wants to merge 10 commits into
Conversation
|
Hey — went through the prod-improvements branch, solid work. The throughput-aware ring selection is the right call and it's model-agnostic — the order-independence insight (reuse the latency loop, let compute drive selection) is clean, and your 3090/4090 priors match the fleet you're targeting, so cold-start should be decent until the perf store learns real numbers. One thing on the drafting side, for the M2.5 target: the MTP drafter you scaffolded is GLM-5.2-specific — it leans on GLM's native NEXTN head (layer 78), which only exists because 5.2 was trained with it. You can't add an MTP head to a model that wasn't trained with one, so it won't port to MiniMax M2.5 unless M2.5 actually exposes a NEXTN/num_mtp head — which your If you're keeping a parallel GLM-5.2 path (since you pulled the ring_mtp work), one heads-up: you nailed the single-step convention (post- Smaller and model-agnostic: acceptance isn't really a per-model scalar — it climbs with context — so the tok/s estimate drifts long. Non-blocking. Nice branch overall. |
…after scaffold shard/research: - glm_swarm_nvfp4_kv.py: full replace from shardv2 (MLA-latent cache, tree verify par/dep, GraphVerify, GLM_MAXPOS) - mla_latent.py: offline MLA absorb equivalence proof (1e-13 errors) - ring_mtp.py, ring_long.py, draft_accept_bench.py: MTP + long-ctx research shard/phase0: - confidence.py: ConfidenceScheduler (EMA-based depth/K adaptation, DSpark-inspired), self-test passes - mtp_draft.py: MtpDrafter scaffold matching NgramDrafter interface, post-norm convention, lazy-loads Layer 78, degrades without torch - specpipe.py: confidence-scheduled depth in coordinate_pipe (K stays fixed for CUDA graph, depth adapts from acceptance EMA)
the orchestrator owns ring assembly + payments but must not re-implement the
VRAM-fit + min-latency topology solver (tested python in shard/scheduler.py).
POST /plan {model,total_layers,gb_per_layer,nodes:[{node_id,vram_gb,rtt_ms}]}
-> {coordinator, ring_order, stages:[{stage,node_id,lo,hi,n_layers}]}.
coordinator is co-located on the head stage (matches launch_libp2p.py): ring =
[coord] + topology(coord), blocks re-tiled contiguous in ring order so each
stage's --lo/--hi is a clean local 0-based window. auto-picks lowest-mean-rtt
depot when unpinned. stdlib only, no deps. boundary law: knows layers/vram/rtt,
nothing about accounts/pay.
6 offline tests ($0): even/heterogeneous fit, fat-node-first, coord pin+auto,
insufficient-vram raise, 0-layer node dropped. all pass + live HTTP smoke.
the working demo is hand-tuned: operator guesses --stages order + --layers split. plan_ring queries each box's REAL vram (nvidia-smi) + the measured rtt mesh (reuses launch_swarm.mesh_rtt), calls the scheduler, prints the exact launch_libp2p.py invocation (--stages in ring order, --layers as the vram fit). non-invasive: launcher untouched, operator pastes the printed command. fleet imports (launch_oss/launch_swarm, which read ~/.shard_psk + pull vastai) are lazy so the pure planning logic stays importable + testable offline. 6 offline tests ($0): vram parse (single/multi-gpu/units/garbage), node assembly, full pipeline with stubbed vram (fat-node-biggest, [0:78] coverage), coord pin. all pass.
the shard side of the c0mpute receipts bridge. on a --pipe --receipts coordinator
run, after the post-gen ring sweep verifies every signature + full layer coverage,
dump {ok, receipts:[...]} to this path. the c0mpute shard-worker reads it and
forwards the receipts to the orchestrator on job:complete, which re-verifies
(signer-bound) and pays each stage. complements --dump (output) with the receipts.
…learn from real runs
The latency-only planner (topology.py) can't see GPU compute — it'd hand a slow
fat-VRAM card a big layer block and tank throughput. Score the whole speculative
round instead (WAN loop + per-GPU layers*ms/layer + accept-rate) and choose the
subset+order that maximizes predicted tok/s.
- shard/throughput.py: best_ring searches feasible node subsets, fits+orders each
(reusing optimal_loop — order is still the min-latency loop for a fixed set), scores
est_tok_s, returns the winner. round_ms/est_tok_s with accept-cap + zero guards.
- shard/perf_store.py: EWMA store of per-GPU ms/layer, edge RTT, per-model accept_rate,
learned from completed runs; GPU-class priors when cold; atomic JSON persistence.
- scheduler_svc: POST /plan {objective:tok_s} -> throughput plan (+est_tok_s/round_ms);
POST /telemetry folds a completed run into the store; --perf-store persists it.
Default /plan unchanged (latency-only) — fully backward-compatible.
- specpipe --dump: add rounds (mean_accept/K already present) so accept_rate is derivable.
- throughput_test.py: 29 tests incl. the headline 'slow fat GPU loses to fast lean pair',
the learn-then-replan loop (cold 77 tok/s single-fat -> warm 100 tok/s fast-pair), and
backward-compat. 41 Python tests green.
S1: gpt-oss layer count reconciled to 36 everywhere - plan_ring.py: MODEL_LAYERS dict, no global DEFAULT_TOTAL_LAYERS=78 - launch_swarm.py: NLAYERS 78->36 (matches default model gpt-oss-120B) S2: FIXME flag in receipt.py verify_coverage (unknown-signer hole) leyten's self-check, not payment boundary. Flagged for him to fix. Python syntax verified.
a model is defined once in registry/models.json (shard-models/1), signed with the publisher ed25519 key (reuses manifest.py canonical+sign path). both repos read it. deletes plan_ring MODEL_LAYERS — layer count / bytes-per-layer / quant now resolve from the verified registry, --total-layers stays an override only. - shard/registry.py: load/verify/sign + row validation (known adapter, unique ids, positive layerCount), fail closed on bad sig / pinned-pubkey mismatch. - phase0/publish_registry.py: sign a source registry, or --verify a signed one. - registry_test.py: 11 proofs incl tampered-layerCount rejection (the gpt-oss 120-vs-36 bug, now caught by the signature). - tests/registry_cross_repo_test.py: drives BOTH repos, asserts field-for-field agreement on every model — the test that would have caught the drift.
implements upstream's ModelRuntime (shard/node.py, MODEL_RUNTIME.md) over the proven pipeline.load_stage / run_block path: GenericHFRuntime is the universal HF/Transformers fallback — any arch transformers can load (incl gpt-oss) serves with no new engine code. make_runtime(adapter,...) maps the signed-registry adapter field to the impl; generic-vllm falls back here until VllmRuntime lands (MODEL_RUNTIME.md step 3). NOT a new interface — one adapter behind the in-house firewall, so coordinate_pipe rides on it unchanged. registry KNOWN_ADAPTERS gains generic-hf/hf (kept identical to c0mpute's set, asserted by the cross-repo test). 22 $0 proofs (no torch): subclass+contract, adapter selection fail-closed, head/tail boundary law, [0:N] tiling. the real load_shard/forward is the metered GPU spike (MODEL_RUNTIME.md step 2). supersedes the earlier StageRuntime sketch, which duplicated ModelRuntime; this conforms to the already-merged upstream interface instead.
add_model.py turns onboarding into data: read config.architectures + dims, compute gbPerLayer + kvGbPerLayer for the scheduler fit, emit + merge + sign the registry row (version bumped so consumers refetch). prints the operator steps it can't do (quantize + host + manifest CID, tokenizer round-trip, 3-stage smoke). - fit math handles dense AND MoE layers (num_experts*expert_mlp + shared + router), conservative bytes/param per quant so the vram fit never over-commits. - add_model_test.py: 16 $0 proofs incl the acceptance offline half — a new MoE model goes nothing -> signed verifiable registry row with ZERO engine code, post-sign tamper rejected. - docs/runbooks/add-a-model.md: one-page runbook (TL;DR, quant recipe, fit math, tokenizer silent-failure guard, $0 verify list). adapter values reference the ModelRuntime impls (generic-hf/generic-vllm/glm-nvfp4).
9bdc950 to
f603786
Compare
Rebased onto current
master(the ModelRuntime / one-engine-every-model commit).origin/masteris an ancestor — conflict-free; the only file overlap isphase0/confidence.py, byte-identical on both sides.Moat layer (in-house; orthogonal to the ModelRuntime sprint)
shard/throughput.py,shard/perf_store.py): picks the ring maximizing estimated tok/s, not the first feasible one (WAN + per-layer compute + draft + accept; exact subset enumeration for small rings).PerfStorelearns fleet behavior via EWMA, cold-starts on 3090/4090 priors; atomic writes; largest-remainder conserves exactly. Default plan stays latency-only for byte-compat;objective:"tok_s"opts in.phase0/scheduler_svc.py): the bridge c0mpute calls for a plan (subset + order + per-stage blocks) from live VRAM+RTT, plus/telemetryto learn. Stdlib only.phase0/plan_ring.py): plans the libp2p ring from live VRAM+RTT.phase0/specpipe.py --receipts-out): writes verified receipts as JSON for the c0mpute payout path. Only fires under--pipe + --receipts.phase0/specpipe.py,confidence.py): adapts pipeline depth from the acceptance EMA so a bad draft streak doesn't waste WAN traversals. K stays fixed (CUDA-graph shape K+1); only depth changes → lossless (output bit-identical). Result dict gainsmean_conf_depth/final_confidencefor A/B.plan_ring.py,launch_swarm.py): gpt-oss reconciled to its real 36 layers;NLAYERSno longer a GLM-78 holdover applied globally.Model-onboarding layer (needs your ack — see heads-up)
shard-models/1(shard/registry.py,phase0/publish_registry.py,registry/models.json): a model defined once, both repos read it; layer count / bytes-per-layer / quant / engine path / adapter / tokenizer stop living in three places that drift (the gpt-oss 120-vs-36 bug). Signed with the publisher Ed25519 key (reusesmanifest.py's canonical+sign path); consumers pin the pubkey and fail closed on bad sig, pinned-pubkey mismatch, unknown adapter, duplicate id, non-positive layerCount.plan_ring.pyresolves from the registry; the oldMODEL_LAYERSdict is deleted.ModelRuntimeadapter (phase0/hf_runtime.py):GenericHFRuntimeimplements yourshard/node.pyover the provenpipeline.load_stage/run_block— the universal HF fallback, any arch transformers can load (incl. gpt-oss) with no new engine code.make_runtime(adapter, …)maps the registry adapter field to an impl;generic-vllmfalls back here untilVllmRuntimelands. One adapter behind the firewall —coordinate_piperides on it unchanged. Not a competing interface; implements yours.add-modeltooling + runbook (phase0/add_model.py,docs/runbooks/add-a-model.md): config → fit math (gbPerLayer/kvGbPerLayer, dense and MoE) → emit+merge+sign the registry row (version bumped). Prints the operator steps it can't do — quantize+host+CID, tokenizer round-trip, the 3-stage smoke.Tests (all offline, no GPU)
scheduler_svc (6), plan_ring (6), throughput (29), registry (11, incl. a post-sign layerCount tamper rejected by the signature — the gpt-oss-drift guard), hf_runtime (22, conformance via a torch-free fake), add_model (16, incl. a new MoE model → signed verifiable row with zero engine code), cross-repo agreement (29, drives both repos + asserts identical KNOWN_ADAPTERS), plus confidence.py / mtp_draft.py self-tests.
What "green" does NOT cover (stated plainly)
GenericHFRuntime— a sliced layer block running on hardware and matching a full-model reference is your step-2 spike, not done here. This PR is the torch-only reference for it.Out of scope (follow-up)
VllmRuntimebehindModelRuntime+ the GPU spike that gates it (MODEL_RUNTIME.md 2-3).weight_mapinshard/fetch.py(step 4) — flagged, not grabbed.plan_ringsizing now that the engine serves B streams.