Repository navigation
Conversation
…Card,Placed,Host}) The Nemotron-H families' common owner gains `Experts`: where a plan puts the routed experts. `Card` is today's plan and keeps its refusal of any host expert byte for byte; `Placed` keeps each routed layer's id prefix [0, n_l) on the card as its budget holds and the rest on the host tier; `Host` keeps none on the card. `PlanInputs::plan` is `plan_with(.., Experts::Card)`. The context, tier and card-count refusals hold under every arm. The load still refuses host experts until the body has a host tier. Gates: the fixture's C1 to C4 in nemotron_h_moe::spec (the whole-card Placed plan is the Card plan; a small card keeps each layer's prefix and hosts the rest; the Host arm keeps no routed expert on the card; the refusals loop over the three arms), the no-routed-stacks case in nemotron_h::place, and hw_nemotron_h_moe_plans_the_host_tier on the real header. The split's clauses have one owner, tests/common/host_split.rs. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
A change to hybrid.rs found no row, so `just affected --narrow` kept the full list. The row is the 80 gates `recipes.py reach` names for the file; the gates whose own target is the module (gate-nemotron-meta, gate-nemotron-h-moe-meta and the rest) stay outside it, as the table's rule says. FAIL-first, the same three identical scan pairs, origin/main..HEAD of the plan change: before: narrow: full list — crates/model/src/arch/hybrid.rs: ... no row of tools/gate-paths.tsv maps it (167 recipes) after: narrow: file crates/model/src/arch/hybrid.rs: own target of 12; rows 111 -> 80 recipes (102 recipes) `recipes.py check`: 134 gate rows held to the derivation, rc 0. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
tools/gate-paths.tsv: main's rows, and the hybrid.rs row recomputed on the merged tree (`recipes.py reach --row`): main added gate-gpu-qwen38-nodes-remote and gate-qwen3-plan to the gates that reach the file, so the row is 82 recipes, not 80. `recipes.py check` rc 0. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…de it
Main's Card::set_aside_bytes returns None past u64 bytes. The two new budget
sums (the fixture's small card and the real header's budget B) take the value
with the same `expect("bounded set-aside")` the spill gate beside them uses.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
There was a problem hiding this comment.
Verdict: MUST_FIX: Two new test owners leave the same fixture setup and expert-count pin duplicated.
Must fix
1. Use the fixture reader in the existing fixture tests
crates/model/src/arch/nemotron_h_moe/spec.rs:608 repeats the setup at crates/model/src/arch/nemotron_h_moe/spec.rs:489 and crates/model/src/arch/nemotron_h_moe/spec.rs:564: the same architecture, keys, tokenizer, tensors and read sequence (R14).
Reusing the helper now removes two setup blocks before the fixture changes.
Replace each header_typed-through-place::read setup with the following line, in test order:
let inputs = fixture_inputs("nhm-fixture");let inputs = fixture_inputs("nhm-spill");2. Use ROUTED in the existing header pin
crates/model/tests/nemotron_h_moe_meta.rs:325 introduces an owner for the Bolt file's total expert count, but crates/model/tests/nemotron_h_moe_meta.rs:263 keeps the same literal as a separate pin (R14).
Sharing it now prevents the header and placement gates from disagreeing after a fixture update.
Replace the existing condition with:
if u64::from(routed) != ROUTED || hp.n_expert != 496 {Non-blocking
⚠ Check the aggregate host byte total: crates/model/tests/common/host_split.rs:132, a static trace shows that replacing expert_bytes: host_experts at crates/placement/src/placement.rs:3770 with expert_bytes: 0 escapes the new assertions. The real-file gate prints the total, and HostNeed::of uses it for sizing.
Add before the return: assert_eq!(plan.host.expert_bytes, layers.iter().map(|l| l.host_bytes).sum::<u64>());
Checked
Duplicates: both setup blocks match fixture_inputs; the header literal and ROUTED pin the same total. Both fixes reuse existing owners and add no elements.
Complexity: plan_with adds an enum dispatch without model-specific branches.
Silent failure: ignored fixture cleanup supplies no plan value; a missing expert list fails the segment comparison; show's discarded failures are checked immediately afterward; the Card-error fallback retains a failing pin.
Tests: static mutation traces at hybrid.rs:602 (Placed→Host) fail the whole-fit and small-card tests; :611 (Host→plan_routed) fails the Host and real-file tests; :589 (tier guard Card-only) fails the changed refusal test; a Host refusal at :610 fails the no-routed-stacks test.
Gates: no existing pin, case or gate was relaxed or removed.
Escape catalog: no C1–C4 pattern matches the diff.
Behavior: zero context, tier cards and wrong card counts are refused before dispatch; final invariants check card and host room. No decode or prefill work changes.
Notes
Static review only; gates were not rerun. GPU evidence omits the nvidia-smi output and exact just gate-* invocations.
🔨 Shingler, bloomery's review bot: approves a clean pull request and requests changes on a must-fix (how it works)
There was a problem hiding this comment.
Since the last review: the three earlier items fall outside the incremental review scope.
Verdict: CLEAN: No in-scope must-fix items remain.
Checked
Evidence: read all three findings' cited code. The two duplication reports are kind B; the host-byte assertion proposal identifies a coverage gap without a reachable behavior failure.
Scope: none of the three finding paths appears in the supplied incremental diff. The re-review rule excludes these non-kind-A findings.
Diff: all 19 files in the incremental patch are byte-identical to BASE_TREE.
Validation: static inspection only; gates were not rerun.
🔨 Shingler, bloomery's review bot: approves a clean pull request and requests changes on a must-fix (how it works)
|
Superseded by #393 (merged). Written by a bot (Claude agent) for the maintainer. |
What and why
Bolt's (
nemotron_h_moe) plan can now put routed experts on the host tier. This is B2 of the offload row of #328. The common planner already makes that plan per layer. Only the family wrapper refused it.hybrid.rsgainsExperts::{Card, Placed, Host}andPlanInputs::plan_with.plan()isplan_with(.., Experts::Card): same plan, same error text.Placedkeeps each routed layer's id prefix[0, n_l)on the card as its budget holds and the rest on the host.Hostkeeps none on the card. The context, tier and card-count refusals hold under every arm. The load still refuses host experts: the body has no host tier yet (B3).Release notes: internal — the plan half of Bolt's
--n-cpu-moeequivalent; no load uses it yet.API for B3
PlanInputs::plan_with(&machine, ctx, &levers, Experts::{Card, Placed, Host}), re-exported asnemotron_h_moe::place::Experts.workstation::placed_on(RTX_3090, 88, workstation::SCRATCH + <the body's own scratch>). The body's scratch is the GPU side's to size. This PR counts none.plan.n_l[l]; each routed row isCard(0)ids[0, n_l)thenHost/HostFileids[n_l, E_l);plan.host_experts(l);plan.host.expert_bytes. Sum only over layers that have routed stacks:layer_experts(l)of any other layer is the model's largest count.n_lfollows from the bytes:BLOOMERY_CARD_BUDGETand--place gatealready reachplanthroughPlanLevers.Pinned facts (the real 24 GB header, ctx 4096, body scratch not counted)
plan_gate(88)andplaced_on(RTX_3090, 88, SCRATCH)both.Placedthere is Debug-equal toCard.Gates and FAIL-first
Fixture clauses in
nemotron_h_moe::spec(C1 to C4),nemotron_h::place(C5), andhw_nemotron_h_moe_plans_the_host_tieron the real header. The clauses of a split have one owner,tests/common/host_split.rs. Each mutant ran throughtools/mutant-run.shwith--expecton the intended clause, then a clean run.placement.rshost experts counted at the model's largest layerplan_routedplacement.rsexpert budget without the margin9 of 9 killed, clean runs green. M3, M5 and M8, M9 are temporary mutants of
crates/placement; none is committed. Clause (d), the stacks' lists equal, is implied by (c): no mutant isolates it.tools/gate-paths.tsvgets a row forcrates/model/src/arch/hybrid.rs(second commit): before,narrowkept the full list (167 recipes, "no row maps it"); after,rows 111 -> 80 recipes(102 in all; the row holds 82 recipes since the merge of main).recipes.py check: 134 gate rows, rc 0.GPU Evidence
Box: RTX 3090 and RTX A6000, driver 615.71.09, nvcc cuda_13.3, Linux 6.8.0-142. All jobs through the box queue.
Two trees. The jobs below ran at head
9b630364con base962401358(origin/main then), except the lines marked [tip]. After them, origin/main moved toe195c9e6a; this PR merged it (e263e7f59) and fixed two call sites for main'sOption<u64>set-aside (766b36916). The merge moved thehybrid.rsrow oftools/gate-paths.tsvfrom 80 to 82 recipes. The 80-gate batch below ran on the pre-merge tree; the [tip] lines re-ran the four gates the merge could change.[tip] at
766b36916,tools/gate-batch.sh --tier fixture --round-ledger,DONE total=4 red=2 wall=608s:gate-nemotron-h-moe-metajob s2246 rc 0: lib15 passed, hw2 passed.gate-nemotron-metajob s2251 rc 0: lib36 passed, hw1 passed.gate-qwen3-planrc 101, not from this PR:error[E0308]: mismatched typesatcrates/gpu-gates/src/bin/shared/qwen3moe_place.rs:660and:1364(reserve_bytes()isOption<u64>sincebd378cd6e; that file'sreserves:line came withea4b6228c). Both files are byte-identical to origin/maine195c9e6a.gate-gpu-qwen38-nodes-remoterc 101, not from this PR:Error: "BLOOMERY_TIER_NODES names 0 addresses, the gate one". The batch sets no remote node.Second merge: origin/main
402f9abc6(85410e2b4, plain push) brings #372 (686da9f16), which fixes theqwen3moe_place.rscompile error above.just checkon the box at85410e2b4: rc 0.gate-qwen3-planand the two meta gates were not re-run at85410e2b4; the row is still 82 recipes (recipes.py checkrc 0).gate-nemotron-h-moe-metajob s2221 rc 0: lib15 passed, hw2 passed(hw_nemotron_h_moe_header,hw_nemotron_h_moe_plans_the_host_tier).gate-nemotron-metajob s2222 rc 0: lib36 passed, hw1 passed.tools/gate-batch.sh --list <the row> --tier fixture --round-ledger→DONE total=80 red=5 tier=fixture skipped=0 wall=5478s laneA=3243s laneB=3403s laneX=2073s deferred=120 real_only=20 real_file=16.gate-gpu-nemotron-h-moe-e2ejob s2065, A6000, rc 0:PASSED: gate_nemotron_h_moe_e2e the captured step holds the entries its layer kinds derive; eager and replay give bit-identical logits; a reset rewinds the ids and the logits bits; the scan state carries; the fault word stays clean; …gate-gpu-nemotron-h-moe-servejob s2067, A6000, rc 0:PASSED: gate_nemotron_serve the seat plans its default and says so, refuses a second slot, loads at the context it named, …gate-gpu-nemotron-e2ejob s2218, 3090, rc 0,PASSED: gate_nemotron_e2e …;gate-gpu-nemotron-servejob s2220, A6000, rc 0,PASSED: gate_nemotron_serve ….gate-gpu-qwen4exp-e2e(s2063) andgate-gpu-qwen4exp-mtp(s2070), rc 1:card leg plan: … a layer's count Some(255)..Some(256) (want 256 …) FAIL. The same two lines fail on origin/maine195c9e6awith none of our commits (jobs s2219 and s2223).gate-gpu-gemma-embedding2-e2e(s2073), rc 1:/models/embeddinggemma-2/embeddinggemma-2-Q8_0.gguf: No such file or directory. The box has no such file.real-only, 16real-file; list in the batch'sdeferred.list): gate-gpu-p6, gate-gpu-e2e, gate-gpu-hybrid, gate-gpu-p8, gate-gpu-moe, gate-gpu-ds41-moe, gate-gpu-ds41-chain-ffn, gate-gpu-p10, gate-placement, gate-gpu-qwen3moe-router, gate-gpu-qwen35moe-verify, gate-gpu-qwen35moe-mtp, gate-gpu-qwen35-dspark, gate-gpu-minicpm-text, gate-gpu-clef-hidden, gate-gpu-qwen35-media, gate-gpu-qwen35moe-media, gate-gpu-clef-serve, gate-gpu-lev-serve, gate-gpu-qwen4exp-iqleg, gate-gpu-gemm, gate-gpu-gemma-embedding2-serve, gate-gpu-qwen3moe-e2e, gate-fixture, gate-gpu-head, gate-gpu-ds41-long, gate-gpu-ds41-draft, gate-gpu-ds41-chat, gate-gpu-nvtier, gate-gpu-qwen38-residency, gate-gpu-qwen3-serve, gate-gpu-p8b, gate-gpu-dspark-experts, gate-gpu-dspark-kv, gate-gpu-dspark-graph, gate-gpu-glm5next-serve.ptx-scan --no-jitofgate_glm5next_e2e,gate_kquant,gate_swapidentical to the base (just narrow): no kernel moved.cargo check -p bloomery-model --all-targets0 warnings; clippy 12 warnings, all in other files;fmt-checkand everytools/check-*.shrc 0 exceptcheck-recipes.sh, wherenarrow-scan.sh --self-test(16 FAIL) fails the same way on origin/main'stools/.Code shape
tests/common/host_split.rs) read by the fixture gate and the real-file gate. The placed machine has no wrapper here; qwen3moe'smachineandcounted_arena_bytesare a separate lift.hybrid.rsgains an enum and one function, no per-model branch. A file with no routed stacks plans the same under every arm (C5).Cardkeeps its named refusal of host experts. No comment carries a date or an issue number.This PR was written by a bot (a Claude Sonnet 5.5 agent in the Claude Code harness) under the lead's direction.
🤖 Generated with Claude Code