Repository navigation
gpu, gpu-gates: on a pinned arena the card computes a decode step's NVMe tier experts from their slots (P4 card leg) - #325
Conversation
There was a problem hiding this comment.
Verdict: MUST_FIX: Two proven duplicates violate the single-owner rule (kind B, R14).
Must fix
1. Reuse the existing Rust lexer
tools/check-defaults.py:370 repeats the comment, raw-string, escape and character-literal handling already owned by tools/recipes.py:5178. Both decide whether source text is code.
Reusing the owner now removes a lexer before its edge cases diverge. Delete comments_and_strings and replace includers with:
def includers(root):
from recipes import rust_lex
out = {}
parts = ("crates", "gpu-gates", "src", "bin")
base = os.path.join(root, *parts)
decl = re.compile(
r'#\[path\s*=\s*"([^"]+)"\]\s*(?:#\[[^\]]*\]\s*)*'
r'(?:pub(?:\([^)]*\))?\s+)?mod\s+[A-Za-z_]\w*\s*[;{]'
)
files = next(walk(root, *parts), (None, None, []))[2]
for f in sorted(files):
if not f.endswith(".rs"):
continue
text = open(os.path.join(base, f), encoding="utf-8").read()
_, shape = rust_lex(text)
for m in decl.finditer(text):
if shape[m.start()] != "#":
continue
rel = os.path.relpath(
os.path.normpath(os.path.join(base, m.group(1))), root
)
out.setdefault(rel, []).append(os.path.splitext(f)[0])
return out2. Give the minimal GGUF test fixture one owner
crates/gpu/src/host/nvtier.rs:4899 and crates/gpu/src/host/nvtier.rs:5159 copy the same one-tensor GGUF builder; another copy starts at line 5485. The existing copy starts at line 4702. These encode the same fixture, independently of each test's expert data.
Extracting it now replaces four builders with one helper and four calls, removing more lines and names than it adds. Add this inside mod tests:
fn split_in(scratch: &Scratch) -> std::sync::Arc<gguf::Split> {
use gguf::write::{Layout, TensorDecl, Writer};
let path = scratch.0.join("model.gguf");
let decl = TensorDecl {
name: "x".into(),
dims: vec![4],
type_id: 0,
nbytes: 16,
};
let layout = Layout::new(&[], vec![decl]).unwrap();
let mut writer = Writer::new(std::fs::File::create(&path).unwrap(), layout).unwrap();
writer.tensor("x", &[0; 16]).unwrap();
writer.finish().unwrap();
std::sync::Arc::new(gguf::Split::open(&path).unwrap())
}Replace each complete builder, from let model through let split, with let split = split_in(&scratch);. Remove the imports those blocks alone used.
Non-blocking
⚠ Supply the dispatch timing proof: crates/gpu/src/host/nvtier.rs:2528 enters synchronous claim/fill. For one-row decode, a layer with a miss allocates at least the picks and reads buffers; eviction adds another buffer. The leg adds six launches per covered layer per token [derived]. The float precision/sum-order class requires a same-lease A/B on the contributor's card; the body lists none. Use the existing timing runner to establish the total effect.
⚠ Pin the card arm's accuracy: crates/gpu-gates/src/bin/shared/nvtier_family.rs:778 compares graph with eager, both using the new precision; host/card argmax agreement is only printed. The stack comparisons also use q8_1. Resolve the body's reference-set question with a GLM count pin and show that a precision mutant makes it fail.
Optimization headroom
- B. Overlap waiting: crates/gpu/src/host/step.rs:1128 waits for arena fills before computing the remaining file experts. On steps with file experts, reuse the existing step ticket to overlap their compute with the fill. Expected effect: hide up to one fill wait per missed layer per token [derived].
Checked
Duplicates: lexer pair and all four identical GGUF builders confirmed. Both proposed fixes remove more elements than they add.
Complexity: diagnostics flag ArenaLeg::build, nvtier_family::gate, StepPort::serve_one and Body::load_placed. No finding rests on complexity alone.
Silent failure: hold identity and offset alignment refusals propagate through StepPort::serve_one.
Tests: proposed lexer replacement matches all 52 includer paths in the PR tree and passes eight focused in-memory cases. CUDA gates were not run during validation.
Gates: bits-clause and unsafe-ratchet changes have dated PIN reasons.
Escape catalog: no surviving finding matches C1–C4.
Notes
The body reports card and driver without nvidia-smi output. Its reported GPU runs precede the current head.
Card check: needs-gpu. This pull request changes crates/gpu-deepseek41/src/chain/ffn.rs, crates/gpu-gates/src/bin/gate_ds41_nvtier.rs, crates/gpu-gates/src/bin/gate_glm5next_nvtier.rs … and carries no gate run on a card (CONTRIBUTING's evidence list); a maintainer adds gpu-verified after one.
🔨 Shingler, bloomery's review bot: approves a clean pull request and requests changes on a must-fix (how it works)
386d3b5 to
d0d31a5
Compare
There was a problem hiding this comment.
Verdict: CLEAN: The two supported findings concern missing measurement and precision evidence; neither establishes a must-fix defect.
Non-blocking
⚠ Measure the changed decode schedule: crates/gpu/src/host/step.rs:1131 waits for arena fills before the remaining file-backed experts run at line 1158. With pending misses, the previous resident path could compute hits during that wait. This puts one fill join before those host reads per affected layer per token; the card leg also adds six launches per covered layer per token. The body reports family gate runs but explicitly leaves the same-lease base/head A/B unrun. Supply that dispatch/precision measurement on the contributor's card to assess the overlap loss against the predicted saving.
⚠ Complete the precision proof: crates/gpu/src/host/arena_leg.rs:523 quantizes held experts' activations as q8_1 and partitions their sum from the host's. The existing checks establish stack and graph/eager consistency. The precision change class also requires an error-model diagnostic and an independent count pin. The body explains that existing GLM reference sets supply no suitable count pin for this path; establish the required reference through crates/refset and add its family pin when available.
Checked
Validation: both cited lines are added by the diff. The base resident path (HostRun::experts_into → serve_column_resident → serve_step_split) computes the early group before waiting for pending fills.
Precision: the card clause gates graph/eager equality and traffic counts; card/host argmax agreement is printed as a diagnostic. The base contract requires an error-model diagnostic, count pin and timed A/B for precision changes.
Scope and classification: full review; no revert or no-merge mirror. Both items remain advisory evidence requests. No ledger lessons are in force, and escape patterns C1–C4 do not apply.
Notes
CUDA tests were not executed during validation. The reported card runs lack a nvidia-smi output line, so gpu.json records no qualifying GPU evidence.
Card check: needs-gpu. This pull request changes crates/gpu-deepseek41/src/chain/ffn.rs, crates/gpu-gates/src/bin/gate_ds41_nvtier.rs, crates/gpu-gates/src/bin/gate_glm5next_nvtier.rs … and carries no gate run on a card (CONTRIBUTING's evidence list); a maintainer adds gpu-verified after one.
🔨 Shingler, bloomery's review bot: approves a clean pull request and requests changes on a must-fix (how it works)
d0d31a5 to
e5fae14
Compare
There was a problem hiding this comment.
Verdict: CLEAN: Neither finding establishes a reachable behavior failure or a mechanical quality defect.
Non-blocking
⚠ Complete the timing evidence: crates/gpu/src/host/arena_leg.rs:523. The card leg adds six launches per covered layer per decode token, plus arena reads, replacing host computation. With all 42 GLM routed layers covered, that is 252 launches/token [derived]. The dispatch path and precision change require a same-lease timed head/base A/B on the contributor's card. Provide that result.
⚠ Establish the precision path's accuracy pin: crates/gpu-gates/src/bin/shared/nvtier_family.rs:760. The card clause checks counts and graph/eager equality; host/card argmax agreement is only printed. It does not bound divergence from an independent reference. The PR supplies no count-pinned reference for this arena path. Add a count pin to the existing GLM NVMe gate when a suitable reference is available, defining acceptable divergence before expanding the path.
Checked
Both cited lines are added by the diff. Boundary::enqueue_back_of calls the leg after the row's wait; the six enqueue operations each select one kernel launch. The card gate asserts count identity and replay equality, while cross-leg argmax agreement is diagnostic. The bits-clause scope change carries a dated PIN and reason. This is a full review of a feature PR; no ledger lessons or escape-catalog exceptions change the findings' classification.
Notes
The supplied GPU reports name earlier revisions. The PR omits the required nvidia-smi output line, so gpu.json records incomplete GPU evidence. CUDA gates and timings were not run during validation.
Card check: needs-gpu. This pull request changes crates/gpu-deepseek41/src/chain/ffn.rs, crates/gpu-gates/src/bin/gate_ds41_nvtier.rs, crates/gpu-gates/src/bin/gate_glm5next_nvtier.rs … and carries no gate run on a card (CONTRIBUTING's evidence list); a maintainer adds gpu-verified after one.
🔨 Shingler, bloomery's review bot: approves a clean pull request and requests changes on a must-fix (how it works)
… the bins that declare it as a module A pin in a file under crates/gpu-gates/src/bin that no recipe names was a named error. A shared harness file (shared/*.rs) is such a file: it runs inside every bin that declares it as a module, so its pins are now attributed to the recipes of those bins. A declaration is a `#[path = "..."]` attribute on a `mod` item outside any comment or string literal; an attribute in a comment, in a string or with no `mod` item after it links nothing, and the file's pin stays a named error. The pins of such a file are every including bin's, so a value given as an identifier the scanner cannot resolve (a parameter, a field) is refused by name there instead of being recorded as the identifier's text. Files a recipe names keep their rule. The self-test adds the shared file linked by a module declaration, the same attribute in a line comment, a block comment, a raw string and with no module after it (each refused as no recipe's), and the shared pin to an unresolved identifier (refused by name).
…K down read an expert's parts at word offsets of the NVMe tier's arena, bit for bit the stack _sel launches kq_gate_up_act_q4k_at, kq_gate_up_act_q5k_at, q5k_gemv_sel_at and q6k_gemv_sel_at take the arena as one word slice and a table of three word offsets a slot (gate, up, down; u64::MAX skips the slot) in place of a stack and its ids. A part is the expert's rows as its file holds them, so each entry runs its stack entry's row walk on the part (row_dot_1col for the K-quants, q6k_gemv_sel's body for Q6_K) and the same bytes give the same bits. A part that does not lie inside the arena raises ExpertId: the gate/up writes NaN, the down leaves its slot alone. The four are the types GLM-5.3-Flash UD-Q4_K_XL's routed stacks use. q6k_sel_row_dot expands one shared row walk inside q6k_gemv_sel, q6k_gemv_sel_prefetch and q6k_gemv_sel_at. It takes the weight words, the row's first byte and the activation column. Expansion avoids the extra inline function boundary that reorders the prefetched stack entry's unsigned super-block comparison, without keeping another copy of the row walk. hw tests: card_leg's arena test holds the four entries to the stack launches over parts placed out of order at odd word skews in pinned host memory the card maps, and the past-the-arena fault; nvtier's refill test holds the gate/up and down arena entries, eager and replayed, to each round's expert after the tier's own fill rewrote the slot (O_DIRECT and through the page cache). ptx-shapes.tsv pins the four entries in generate_ds41 and gate_e2e at 0 spill and 0 jit_local, the spill column ptxas 13.3.73's at sm_86 and the jit_local column that ptxas's stack frame at sm_86 (`ptxas -arch=sm_86 -v`), which over these two binaries equals the RTX 3090 pin of every other row.
… pinned arena the step port's card leg computes a service's arena ids on the card from their slots, held until the row's next go A service's routed ids that the NVMe tier serves are held filled and pinned for the card (NvTier::hold_for_card, an owned CardHold under Claim::Step, which names them in the order asked: the claim pins its hits before its misses). The service checks that the hold holds exactly its ids, reads every held id's part word offsets (NvTier::card_offsets) into a list made at load, and only then writes them into ArenaLeg's mapped tables with place 0, so a refused offset leaves the row's slots skipped as the service's start left them. The host computes the row's other host slots as before. After the row's wait the card runs six launches on the stream: the activation's q8_1, the gate/up arena entry, the slots' q8_1, the down arena entry, their weighted sum (CardAcc) and its add to the row's sum. A row's hold drops at its next go, which the card enqueues after the leg's launches, and at every point where the stream has run past the steps (a reset's rest, an idle settle, a prompt's batch service). The card quantizes the activation as q8_1 (an f32 scale per 128 values) where the host leg's K-quant dots take Q8_2X4 (a bf16 scale per 32), and it sums the held ids in its own order: a step's logits on the leg are not the host leg's bit for bit. The leg runs where the arena gives the card an address and the page carries one column a row. It is off, with the reason noted once on the tier (NvTier::card_leg, an Option<LegOff>, unattached until a load notes it), for an anonymous arena, a load with expert tier cards, a load whose residency machine runs (it frees arena slots a hold keeps), a covered layer whose stacks no arena entry takes, a covered layer with a part off a 4-byte word (NvTier::parts_on_words), and a covered layer with fewer slots than the boundary's rows times n_used, the ids a pass holds at once. GLM-5.3-Flash attaches it after its tier cards and passes whether its residency glue runs a machine; HostTier::start_swap refuses by name a load that has a leg. The other families keep the host leg. ArenaLeg holds its windows into the arena, its tables and the boundary's page in one type whose drop frees nothing, each made in its own unsafe block with its SAFETY line; the scratch the launches write sits behind a lock, with its reason, since the launches are enqueued through the boundary's shared borrow. Boundary::enqueue_back and enqueue_back_of take the layer, since the leg's entries follow each layer's types; the eight back call sites pass theirs. PIN_BUFFERS is 3 + MAX_ROWS: a row's card hold pins one more id list on a layer. TierCensus::card counts the arena ids the leg read; NvTierStats counts the held ids and the overflow. Tests. nvtier's card hold test holds the hold's ids and offsets to the slots' bytes and addresses, a claim refused while both slots are held, a miss and a hit held in the order asked, the overflow left unheld and counted, the slots free again after the drop, an off-word part refused by name and its layer's parts reported off a word, no card address on an anonymous arena, and the leg's state unattached until noted once. The pool test takes the most pins a layer holds at once (MAX_ROWS card holds, a step's pin and two union batches) from pooled buffers only. hw_the_card_leg_sums_its_held_ids_as_the_stack_launches_do holds two ids of a pinned tier in slots 0 and 3 of six on row 1 of a two-row boundary whose sum starts nonzero, and compares the sum after ArenaLeg::enqueue bit for bit with the stack _sel launches, CardAcc and the add over the same experts. The NVMe family gate's clauses are six: bits holds the host leg's paged arm and a new card clause holds the card leg's. The gate runs the paged arm once more in a child of its own binary on the other arena memory (BLOOMERY_NVTIER_MEM): anon where the process's arena gave the leg its slots, pinned where it did not and the family attaches the leg, so an integrated device and a discrete card each run both arms. card asks of the leg's arm that the steps' claims on the step port are the leg's ids plus its overflow, that the leg held some and the tier filled some, and that the steps run again eagerly from a cleared session give every position's logits bit for bit; it prints the argmax beside the host leg's arm's and does not judge it. gate_glm5next_nvtier attaches the leg and steps eagerly; gate_ds41_nvtier holds its tier unattached and takes Family::eager's default refusal. The two child words are consts of the shared harness, each a row of defaults-twins.tsv whose twin is the gate's own paged arm at the unset memory. NVTIER_MEM's doc names the card leg as the reader of a pinned arena's step ids.
…tate the step port's card leg as built, and docs/cards holds its DGX Spark profile README and models.md say where tokens differ from the all-RAM load: GLM-5.3-Flash's decode on a pinned arena (the default on an integrated GPU) computes the arena's experts on the card, which quantizes the activation as q8_1 and sums in its own order. serving.md explains BLOOMERY_NVTIER_MEM's two memories, the q8_1 against the host's Q8_2X4 and the nvtier record's card_leg key. tierengine.md's (h) and its S7 row describe ArenaLeg, NvTier::hold_for_card, card_offsets, the four arena entries and when a load keeps the host leg, as landed. dgx-spark.md moves the card leg to Closed with a section of its own (8.2: the six launches, when the leg runs, the off words, the gates and tests that hold it, and that no gate pins an exact-token count on this path yet), sets P4's state and leaves V4.1's attach as the open item. t4-glm-card-leg.card is the profile card for the card leg against the host leg on one DGX Spark, as leased. Its premise line read the day-1 serve probe's plan (4137 card experts, 115.9 GB paged); that is struck through and the four arms' own plan lines at --ctx 768 follow it: 4193 card experts, 108,877,512,704 B paged in three arms and 109,806,092,288 B in anon-a, which read a lower room. The fills term is restated on that plan.
e5fae14 to
0a99547
Compare
There was a problem hiding this comment.
Verdict: CLEAN: The remaining finding is advisory.
Non-blocking
⚠ Fill/host overlap: crates/gpu/src/host/step.rs:1131
On a step with mapped host IDs and an arena miss, the hold waits before computing the remaining host IDs. This puts one fill-ticket wait ahead of mapped-host work per affected layer/token. The base's resident split overlaps this work with the fill when its plan fits. Include the lost overlap in the prediction and timed A/B; if material, compute mapped IDs while the fill is pending.
Checked
Confirmed the changed service calls hold_for_card before experts_into; hold_for_card waits on the fill ticket. Compared the base's HostRun resident path and serve_step_split, which computes its early group before waiting. The warning names a reachable scheduling change, without establishing a net slowdown or a kind B defect. No duplicate finding, inline suggestion, issue candidate, or optimization-headroom item remains.
Notes
Static validation only; CUDA gates were not rerun.
Card check: needs-gpu. This pull request changes crates/gpu-deepseek41/src/chain/ffn.rs, crates/gpu-gates/src/bin/gate_ds41_nvtier.rs, crates/gpu-gates/src/bin/gate_glm5next_nvtier.rs … and carries no gate run on a card (CONTRIBUTING's evidence list); a maintainer adds gpu-verified after one.
🔨 Shingler, bloomery's review bot: approves a clean pull request and requests changes on a must-fix (how it works)
|
The review is done and the contribution check passed, so I'm merging this. |
Part of #13, P4 of
docs/contrib/dgx-spark.md(#13): the step port's card leg. On a pinned NVMe tier arena, GLM-5.3-Flash's decode steps compute the step's arena experts on the card from their slots, and the host computes none of them.What and why
A model larger than the pool answers on one DGX Spark with the card computing the NVMe tier's experts from their slots. S7 (#120) gave the arena a device address; this PR adds the leg that reads it. The host still serves the plan's host segment through the file mapping and every expert of a prompt's batch.
For the held ids the change moves precision as well as sum order. The card quantizes the activation as q8_1 with an f32 scale per 128 values. The host leg's K-quant dots take Q8_2X4 with a bf16 scale per 32 (
crates/gguf/src/quant.rs). The card's own experts already run at q8_1, so the held ids take a precision the product already uses. The tokens can part from the all-RAM load's after some steps. The open question below is about where a count pin for that belongs.How a step runs (
crates/gpu/src/host/arena_leg.rs,docs/contrib/dgx-spark.md8.2):NvTier::hold_for_card) and writes each part's word offset in the arena beside the handoff (NvTier::card_offsets). The offsets are computed first and written after; a failure skips the row.CardAcc), and its add to the row's host sum.The commits:
56da88fc2check-defaults: a harness file shared through#[path]has its pins checked under the recipes of the bins that declare it. The scanner reads each file once, reads only real#[path]module attributes (comments, strings and removed lines do not count, with a negative self-test case each), refuses by name an identifier it cannot resolve in a shared file, and refuses by name a directory it cannot list.3f20ec452gpu: four arena entries,kq_gate_up_act_q4k_at,kq_gate_up_act_q5k_at,q5k_gemv_sel_atandq6k_gemv_sel_at, the types GLM-5.3-Flash UD-Q4_K_XL's routed stacks use. Each runs its stack entry's row walk on a part at word offsets of the arena, so the same bytes give the same bits. The Q6_K row walk is one macro,q6k_sel_row_dot!incrates/gpu/src/q6k_sel.rs, whichq6k_gemv_sel,q6k_gemv_sel_prefetchandq6k_gemv_sel_atexpand inside their own bodies. It replaces the stack entry's separate copy of the walk. It is a macro, not an inline function, because moving the walk behind a function boundary changedq6k_gemv_sel_prefetch's PTX; upstream's own precedent isneox_append_k192_bodyincrates/gpu/src/rope_neox.rs. The PTX section below shows the existing entries unchanged.3cddd999cthe card leg (ArenaLeg), its attach (HostTier::attach_arena_leg, called by GLM-5.3-Flash's body), the step port's hold and offsets, and the gate's card leg arm.9d1e8843cthenvtierrecord'scard_leg,card_idsandcard_overflowkeys.069723030docs: README, models.md, serving.md, tierengine.md and dgx-spark.md, anddocs/cards/t4-glm-card-leg.card.When the leg runs. The arena is pinned (
BLOOMERY_NVTIER_MEMispinned, orautoon an integrated device), the family attaches it (GLM-5.3-Flash today), and the page has one column a row. The load has no expert tier card and starts no residency machine. Every covered layer's types have entries, its parts start on 4-byte words, and it has a slot for each id a pass's rows hold at once. Otherwise the record'scard_legnames why it is off:off_anon,off_expert_tier,off_cols,off_types,off_words,off_slots,off_residencyorunattached.The slot check asks for
boundary.rows() * n_usedslots a layer. A row's old hold drops before its new one is taken, and held ids leave the host call's pin, so the rows' holds are the most a layer holds at once.Record keys.
card_leg,card_idsandcard_overflowfollow #135'sstep_run_nsand come beforeprompt_run_ns, inrecord.rs, the five schema files and the Qwen3.8 start screen fixture.Open question
Where should the count pin for this path live? The change class table puts a precision change under a
gate-gpu-e2ecount pin againstexact-forced-32.tsv. That file and that gate belong to the 27-layer reference model. No GLM-5.3-Flash reference set with a count pin exists incrates/refsetor in the tree, andgate-glm5next-e2eholds ratio bands with no NVMe tier load. So no count pin for this path can be set from what the tree has.Our default: this PR holds the contracts a GB10 can check without a reference set. They are the card leg against the host leg's bits, graph against eager, and the tier's counts, all run on the base too. A GLM-5.3-Flash count pin follows once a reference set carries one. The options I see are a count pin on the real file with the leg on (a new refset family row and its writer), a pin in
gate-gpu-glm5next-nvtier's card arm on the fixture, or a pin on another model that the leg can serve. I will do whichever the maintainer prefers.Code shape
crates/gpu/src/host/arena_leg.rs), with its attach inHostTier. GLM-5.3-Flash's body only calls the attach. The Q6_K row walk is one macro for the stack, prefetch and arena entries.gpu-glm5next/src/body.rsgains the attach call.gpu-deepseek41andgpu-mimo2change only whereenqueue_back_ofnow takes the layer.start_swap's take branch for a leg is removed, and so isgate_ds41_nvtier's eager impl (Family::eagerhas a default that refuses by name).LegOff. A second note of the leg's state, a hold that is not exactly the service's arena ids, a part outside the arena (ExpertIdfault), and a row or slot the arena writer is given that does not exist each fail by name.tools/unsafe-ratchet.txtmoves thegpurow from1873 203 10to1896 206 10; the other columns and every other row stay.tools/defaults-twins.tsvgains two rows,anonandpinned. The twotools/ref/ptx-shapestables gain rows for the new entries and no existing row changes.Duplication found or removed:
crates/gpu/src/q6k_sel.rs, the row walk ofq6k_gemv_sel,q6k_gemv_sel_prefetchandq6k_gemv_sel_atis oneq6k_sel_row_dot!, in place of the stack entry's own copy.What I ran
After the review, the branch changed as follows. The child's reply to the gate parent is now one
record::NVTIER_ARMrecord that the parent reads withLog::one, so a missing, repeated or malformed reply is refused by name. Thenvtier_familymodule is a registered record group, whichtools/check-records.pyrequires of a module that prints records. The defaults scanner reads each file once. The step port no longer re-checks the hold's ids, becauseNvTier::hold_for_cardalready returns exactly the requested ids in the requested order. The arena writer refuses a row or slot that does not exist with a named error, where it used to skip it.The tables below are from head
2fbb68abon based66ef090, before the rebases and the review changes, and each row names its head. After the review the GPU checks ran on a DGX Spark (CUDA 13.3.1, driver 595.84, JIT capability 12.1) at head679fc59e: the two NVMe family gates build, and the shared child reply test passes in both. Thehost::nvtiertests (41 passed, and the 4 card tests with--ignored) and thecheck-defaultsself-test ran at the commit before it,50071d64. At head4c041e91(before the rebase) the same machine builds both gates,--records-schemaof each prints its five kinds, andrecord::tests::checked_in_schemas_are_currentpasses;tools/check-records.py check, its self-test andtools/check-records.shpass on a Linux machine. The head is now06972303. A full t0 ran on it, and a later two-line change to thejustfilelinks the shared arm child reply test,the_arm_child_reply_is_one_whole_record, into thegate-gpu-ds41-nvtierandgate-gpu-glm5next-nvtierrecipes. The first full t0 at54f32eddfound that test unowned by any recipe (check-recipesred, rc 1), so the recipes now calltools/gate.sh --oxidefor it asgate-gpu-qwen38-nodesalready does. The other numbers below carry from the older heads.git range-diffof54f32eddagainst06972303shows four of the five commits unchanged and the fifth changed only by those twojustfilelines, and no runtime path differs between54f32eddand06972303. No full GPU or t1 run was repeated on06972303. The card runs used a DGX Spark in the project's dev container (CUDA 13.3.1, ptxas 13.3.73, driver 595.84, JIT capability 12.1). Thesm_86PTX runs are that target's PTX JIT-compiled on the GB10, not an RTX 3090 run.2fbb68abcargo fmtLC_ALL=C cargo fmt --checkjust contrib-check --tier t0DONE tier=t0 total=84 red=0 wall=319s06972303just contrib-check --tier t0DONE tier=t0 total=85 red=0 wall=403sjust contrib-check --tier t1DONE tier=t1 total=104 red=0 wall=317sbloomery-gpulibBLOOMERY_BOX_LOCAL=1 just gate-gpu-libbloomery-gpu-gateslibBLOOMERY_BOX_LOCAL=1 just gate-gpu-gates-libff4dd034BLOOMERY_BOX_LOCAL=1 just gate-leversff4dd034python3 tools/check-defaults.py --self-testcheck-defaults: self-test ok-synthetic, headjust gate-gpu-glm5next-nvtierwithBLOOMERY_TIER=fixture BLOOMERY_FIXTURE_VARIANT=q6kdown-synthetic, based66ef090nvtier-parent-death.pyongate_glm5next_nvtierPASS: stopped nvtier arm child died with gate parenttools/mutant-run.shovergate-gpu-lib's arena teststools/mutant-run.shover the sum test-synthetictools/mutant-run.shover the gatesm_86tools/recipes.py ptx-compareforgenerate_ds41andgate_e2eptx-compare: added-only (rc 0)for bothsm_121ptx-compare: added-only (rc 0)for bothsm_86andsm_121just gate-ptx-spilljust mac-staticon a Macjust mac-staticjust affectedgatesBLOOMERY_BOX_LOCAL=1 just affectedDetails of the rows that need them.
off_anon) equals the all-RAM arm bit for bit, 9 positions against 9.glm5next/corpus-prose.ids, are not public (docs/contrib/local-check.md:314). The two-syntheticrows run on a synthetic corpus: one English sentence repeated 96 times, tokenized with the fixture's tokenizer into 1536 ids, of which the gate reads the first 512. The base and the head read the same ids. The gate compares the head with the all-RAM arm, the card graph with eager and the tier's counts, so it holds those contracts on this input. It is not a reference set accuracy result.q6kdownvariant of the glm5next fixture has Q6_K routed downs, as the real file's layers have: the gate prints routed layers with a Q6_K down = [2, 3, 4, 5], where the real file's are [11, 12, 44].bloomery-gpurebuilt: no final add, a zeroCardAccweight, an activation window one f32 off, and no leg call in the boundary's graph path. Each fails on "the card leg's sum at". The sum test now runs the realenqueue_back_ofof a boundary that holds the leg and compares with an independent stack launch, so removing the leg's call from the boundary fails it. Before that change the last mutant survived.-synthetic) are no attach, no hold, held ids left to the host, and no parent guard. Each is killed with a clean run green after it.Comparison with the reference
The reference is the all-RAM arm and the eager arm of
gate-gpu-glm5next-nvtier, run on the GLM5Nextq6kdownfixture with the synthetic corpus described above. It checks the internal paths against each other and is not an independent accuracy reference. The oracle is "GLM5Next nvtier eager card-leg and all-RAM host-leg reference arms, q6kdown fixture with synthetic corpus", at revision "54f32eddb4c9e8f4e5f90c86db7ac3ff2497f3b3 (nvtier_family.rs blob b6bc20eece30b8dc443e4346973902a55c3ee2a0)". It ran at54f32eddand carries to06972303because the range-diff above shows only the twojustfilelines changed.Card-leg graph replay matches eager at all 9 positions; the anonymous host leg matches all-RAM at all 9 positions. Card-leg and host-leg argmax agree at 7 of 9 positions; this cross-leg diagnostic is not a gate.
PTX
The PR adds four entries and changes no existing one. In both binaries,
generate_ds41andgate_e2e, onsm_86and onsm_121, the head has exactly the four arena entries more than the base and removes none; the compare lists no existing entry as changed. The new entries' rows are in the twotools/ref/ptx-shapestables with spill 0 and jit_local 0, all 16 of them measured on the GB10. The Q6_K row walk is expanded inside each entry by a macro, so the existingq6k_gemv_selandq6k_gemv_sel_prefetchkeep their PTX.What I ran into
None of this blocks the PR.
just gate-ptx-spillreturns rc 1 at the base and at the head, onsm_86and onsm_121. Its unpinned rows are upstream's own and exist on the base too; the rows this PR adds are pinned.just mac-statichas one red step on the base too. The step ischeck-recipes: theptx-compareself-test case "cold run" fails on the Mac because the base side runs in a temp archive away from the tree's git. The same case fails on the base, so it is not from this PR.timeoutis uutils. Thecheck-recipesself-test expects the stall-term case to return 143 after TERM, and uutilstimeoutreturns 124. The t1 row above ran with GNUtimeoutfirst onPATH.check-defaultsself-test failed for a non-root user on a directory it could not read, because the new scanner listed directories itself instead of through the existingwalk; it now goes throughwalkand refuses by name. The mutant that removed the leg call from the boundary survived the graph against eager clause, since both paths lose the leg together; the strengthened sum test above catches it.NvTier::hold_for_cardnow returns the ids in the order asked, and the card hold test has a case of a miss before a hit.MAX_ROWS + 3pins at once and checks that each came from the pool.Not run, and why. A maintainer box run would cover them:
gate-gpu-glm5next-nvtier. A GB10 is integrated, so the gate runs no child arm there.tools/ref/depth-serve.shunder akind: abcard).depth-serve.shhas no single-card mode, which is a change of its own. This PR makes no speed claim.gate-gpu-ds41-nvtierandgate-gpu-nvtier: their plans open the A6000 or the 3090 by name, so on a GB10 they stop before any clause.gate-gpu-e2eand a same-lease A/B on the reference card.Hardware (if the change is for a machine)
DGX Spark (GB10, 128 GB unified memory), driver 595.84, the runs in a container with CUDA 13.3.1 and ptxas 13.3.73. The t1 row ran on an x86_64 RTX 5090 machine (driver 595.99.02), and the static checks on an Apple M-series Mac.
Release notes: Added: the step port's card leg, which computes a decode step's NVMe tier experts on the card from their arena slots on a pinned arena (GLM-5.3-Flash).
AI help
AI 코딩 에이전트가 코드를 작성한 후 설명을 썼고, 테스트를 마쳤습니다. diff 리뷰와 PR 초안 리뷰는 제가 직접 했습니다.