Skip to content

gpu, gpu-gates: on a pinned arena the card computes a decode step's NVMe tier experts from their slots (P4 card leg) - #325

Merged
juliankang4 merged 5 commits into
midagedev:mainfrom
juliankang4:dgx-spark-card-leg
Oct 16, 2026
Merged

juliankang4 merged 5 commits into
midagedev:mainfrom
juliankang4:dgx-spark-card-leg

Conversation

@juliankang4

@juliankang4 juliankang4 commented Oct 16, 2026 •

Copy link
Copy Markdown
Collaborator

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.md 8.2):

  1. At the go the host holds the step's arena ids filled and pinned (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.
  2. After the row's wait the card runs six launches a layer: the activation's q8_1, the gate/up arena entry, the slots' q8_1, the down arena entry, the weighted sum (CardAcc), and its add to the row's host sum.
  3. The hold drops at the row's next go, which the card enqueues after the leg's launches.

The commits:

  • 56da88fc2 check-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.
  • 3f20ec452 gpu: four arena entries, kq_gate_up_act_q4k_at, kq_gate_up_act_q5k_at, q5k_gemv_sel_at and q6k_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! in crates/gpu/src/q6k_sel.rs, which q6k_gemv_sel, q6k_gemv_sel_prefetch and q6k_gemv_sel_at expand 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 changed q6k_gemv_sel_prefetch's PTX; upstream's own precedent is neox_append_k192_body in crates/gpu/src/rope_neox.rs. The PTX section below shows the existing entries unchanged.
  • 3cddd999c the 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.
  • 9d1e8843c the nvtier record's card_leg, card_ids and card_overflow keys.
  • 069723030 docs: README, models.md, serving.md, tierengine.md and dgx-spark.md, and docs/cards/t4-glm-card-leg.card.

When the leg runs. The arena is pinned (BLOOMERY_NVTIER_MEM is pinned, or auto on 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's card_leg names why it is off: off_anon, off_expert_tier, off_cols, off_types, off_words, off_slots, off_residency or unattached.

The slot check asks for boundary.rows() * n_used slots 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_ids and card_overflow follow #135's step_run_ns and come before prompt_run_ns, in record.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-e2e count pin against exact-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 in crates/refset or in the tree, and gate-glm5next-e2e holds 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

  • No second copy. The leg lives in the common host (crates/gpu/src/host/arena_leg.rs), with its attach in HostTier. 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.
  • Model code holds only architecture facts. gpu-glm5next/src/body.rs gains the attach call. gpu-deepseek41 and gpu-mimo2 change only where enqueue_back_of now takes the layer.
  • Dead code is gone. start_swap's take branch for a leg is removed, and so is gate_ds41_nvtier's eager impl (Family::eager has a default that refuses by name).
  • No silent failure. Each off condition is a named 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 (ExpertId fault), and a row or slot the arena writer is given that does not exist each fail by name.
  • Comments state what is true now.
  • No relaxed gate. tools/unsafe-ratchet.txt moves the gpu row from 1873 203 10 to 1896 206 10; the other columns and every other row stay. tools/defaults-twins.tsv gains two rows, anon and pinned. The two tools/ref/ptx-shapes tables gain rows for the new entries and no existing row changes.
  • Lint count: still running on the card when this was posted; the result goes into this description when it ends.

Duplication found or removed: crates/gpu/src/q6k_sel.rs, the row walk of q6k_gemv_sel, q6k_gemv_sel_prefetch and q6k_gemv_sel_at is one q6k_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_ARM record that the parent reads with Log::one, so a missing, repeated or malformed reply is refused by name. The nvtier_family module is a registered record group, which tools/check-records.py requires of a module that prints records. The defaults scanner reads each file once. The step port no longer re-checks the hold's ids, because NvTier::hold_for_card already 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 2fbb68ab on base d66ef090, 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 head 679fc59e: the two NVMe family gates build, and the shared child reply test passes in both. The host::nvtier tests (41 passed, and the 4 card tests with --ignored) and the check-defaults self-test ran at the commit before it, 50071d64. At head 4c041e91 (before the rebase) the same machine builds both gates, --records-schema of each prints its five kinds, and record::tests::checked_in_schemas_are_current passes; tools/check-records.py check, its self-test and tools/check-records.sh pass on a Linux machine. The head is now 06972303. A full t0 ran on it, and a later two-line change to the justfile links the shared arm child reply test, the_arm_child_reply_is_one_whole_record, into the gate-gpu-ds41-nvtier and gate-gpu-glm5next-nvtier recipes. The first full t0 at 54f32edd found that test unowned by any recipe (check-recipes red, rc 1), so the recipes now call tools/gate.sh --oxide for it as gate-gpu-qwen38-nodes already does. The other numbers below carry from the older heads. git range-diff of 54f32edd against 06972303 shows four of the five commits unchanged and the fifth changed only by those two justfile lines, and no runtime path differs between 54f32edd and 06972303. No full GPU or t1 run was repeated on 06972303. 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). The sm_86 PTX runs are that target's PTX JIT-compiled on the GB10, not an RTX 3090 run.

Check Command On head 2fbb68ab
cargo fmt LC_ALL=C cargo fmt --check exit 0
contrib-check t0 just contrib-check --tier t0 DONE tier=t0 total=84 red=0 wall=319s
contrib-check t0, on head 06972303 just contrib-check --tier t0 DONE tier=t0 total=85 red=0 wall=403s
contrib-check t1, on an x86_64 RTX 5090 machine just contrib-check --tier t1 DONE tier=t1 total=104 red=0 wall=317s
bloomery-gpu lib BLOOMERY_BOX_LOCAL=1 just gate-gpu-lib 268 passed; 0 failed, and 4 passed; 0 failed in the held test run
bloomery-gpu-gates lib BLOOMERY_BOX_LOCAL=1 just gate-gpu-gates-lib 116 passed and 5 passed; 0 failed
levers, at ff4dd034 BLOOMERY_BOX_LOCAL=1 just gate-levers 40 passed; 0 failed
check-defaults self-test, at ff4dd034 python3 tools/check-defaults.py --self-test check-defaults: self-test ok
GLM-5.3-Flash NVMe tier gate, -synthetic, head just gate-gpu-glm5next-nvtier with BLOOMERY_TIER=fixture BLOOMERY_FIXTURE_VARIANT=q6kdown 6 clauses ran, every clause passed
the same gate, -synthetic, base d66ef090 the same command in the base tree 5 clauses ran, every clause passed
arm child dies with the gate parent nvtier-parent-death.py on gate_glm5next_nvtier PASS: stopped nvtier arm child died with gate parent
arena mutants tools/mutant-run.sh over gate-gpu-lib's arena tests 7 mutants, 7 killed, 0 survived
card leg sum mutants tools/mutant-run.sh over the sum test 4 mutants, 4 killed, 0 survived
card leg gate mutants, -synthetic tools/mutant-run.sh over the gate 4 mutants in three runs, all killed, 0 survived
ptx-compare, sm_86 tools/recipes.py ptx-compare for generate_ds41 and gate_e2e ptx-compare: added-only (rc 0) for both
ptx-compare, sm_121 the same ptx-compare: added-only (rc 0) for both
full spill gate, sm_86 and sm_121 just gate-ptx-spill rc 1 on the base and on the head alike (see "What I ran into")
just mac-static on a Mac just mac-static combos 0 red, fmt-check rc 0, lint 41 = the ratchet 41; one red step, see "What I ran into"
just affected gates BLOOMERY_BOX_LOCAL=1 just affected still running on the card when this was posted; the result goes into this description when it ends

Details of the rows that need them.

  • The tier gate's card clause on the head: card leg on; the steps' claims 195 = the leg's ids 195 + its overflow 0; fills 581; graph against eager 9 positions against 9, first difference None. The host leg's arm (off_anon) equals the all-RAM arm bit for bit, 9 positions against 9.
  • The corpus ids the gate normally reads, glm5next/corpus-prose.ids, are not public (docs/contrib/local-check.md:314). The two -synthetic rows 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.
  • The q6kdown variant 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].
  • The card leg sum mutants are four edits of the card leg's launches, each applied alone with bloomery-gpu rebuilt: no final add, a zero CardAcc weight, 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 real enqueue_back_of of 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.
  • The gate mutants (-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 GLM5Next q6kdown fixture 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 at 54f32edd and carries to 06972303 because the range-diff above shows only the two justfile lines changed.

Case Exact comparisons Cross-leg diagnostic Result
GLM5Next q6kdown, 9 positions Graph/eager: 0/9 differences; host/all-RAM: 0/9 differences Card/host argmax: 7/9 agree (diagnostic) PASS

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_ds41 and gate_e2e, on sm_86 and on sm_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 two tools/ref/ptx-shapes tables 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 existing q6k_gemv_sel and q6k_gemv_sel_prefetch keep their PTX.

What I ran into

None of this blocks the PR.

  • The full spill gate is red on the base. just gate-ptx-spill returns rc 1 at the base and at the head, on sm_86 and on sm_121. Its unpinned rows are upstream's own and exist on the base too; the rows this PR adds are pinned.
  • just mac-static has one red step on the base too. The step is check-recipes: the ptx-compare self-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.
  • The RTX 5090 machine's timeout is uutils. The check-recipes self-test expects the stall-term case to return 143 after TERM, and uutils timeout returns 124. The t1 row above ran with GNU timeout first on PATH.
  • Two gaps the first version of the tests had. The check-defaults self-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 existing walk; it now goes through walk and 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.
  • The hold's order. A claim pins the ids it finds filled before the ones it fills, so a hold of a hit and a miss named them out of the order the step port asked for. The step port pairs the hold's ids with its slots by position. NvTier::hold_for_card now returns the ids in the order asked, and the card hold test has a case of a miss before a hit.
  • The pin pool. The most pins a layer holds at once are the rows' holds, a step's pin and two union batches. The pool test takes MAX_ROWS + 3 pins at once and checks that each came from the pool.

Not run, and why. A maintainer box run would cover them:

  • The discrete card child arm of gate-gpu-glm5next-nvtier. A GB10 is integrated, so the gate runs no child arm there.
  • A same-lease A/B of the head against the base on a GB10 (tools/ref/depth-serve.sh under a kind: ab card). depth-serve.sh has no single-card mode, which is a change of its own. This PR makes no speed claim.
  • gate-gpu-ds41-nvtier and gate-gpu-nvtier: their plans open the A6000 or the 3090 by name, so on a GB10 they stop before any clause.
  • gate-gpu-e2e and a same-lease A/B on the reference card.
  • GLM-5.3-Flash UD-Q4_K_XL on the real file at this head. No run of it is part of this description.

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 초안 리뷰는 제가 직접 했습니다.

@github-actions github-actions Bot added dgx-spark The DGX Spark (aarch64, GB10) port documentation Improvements or additions to documentation gpu The card engine: crates/gpu*, crates/app host Host crates: qdot, threads, engram, engram-lab, runtime, sampler, levers, decision, vision, refset needs-gpu Changes code a card must check, and carries no gate run on a card serve The HTTP server: crates/serve tools Tools, recipes, CI and the toolchain: tools/, justfile, .github/, Cargo labels Oct 16, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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 out

2. 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)

github-actions[bot]
github-actions Bot previously approved these changes Oct 16, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

github-actions[bot]
github-actions Bot previously approved these changes Oct 16, 2026

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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)

@juliankang4

Copy link
Copy Markdown
Collaborator Author

The review is done and the contribution check passed, so I'm merging this.

@juliankang4
juliankang4 merged commit 75afdfe into midagedev:main Oct 16, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

dgx-spark The DGX Spark (aarch64, GB10) port documentation Improvements or additions to documentation gpu The card engine: crates/gpu*, crates/app gpu-verified host Host crates: qdot, threads, engram, engram-lab, runtime, sampler, levers, decision, vision, refset needs-gpu Changes code a card must check, and carries no gate run on a card serve The HTTP server: crates/serve tools Tools, recipes, CI and the toolchain: tools/, justfile, .github/, Cargo

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants