Repository navigation
engram, gpu, gpu-gates, levers, model, placement: on an integrated device the NVMe tier's arena is one write-combined pinned allocation the card maps, and a pin yields a slot's device address - #120
Conversation
…vice the NVMe tier's arena is one write-combined pinned allocation the card maps, and a pin yields a slot's device address tierengine §5 S7. BLOOMERY_NVTIER_MEM picks the arena's memory: `anon` (today's anonymous mapping), `pinned` (one cuMemHostAlloc with PORTABLE and DEVICEMAP for the whole tier, and WRITECOMBINED on an integrated device alone, carved by layer_bases as before), and `auto` (unset), which is `pinned` on an integrated device and `anon` on a card with its own memory, as tierengine §8 Q5 answered. On a GB10 the pinned arena lowered decode p50 by 9.5 % at a load 1.4 s longer, on a cold arena (no eviction in the run), and on an RTX 5090 by 1.6 % at three arms at a load 2.6 s longer (the A/B below); `auto` stays `anon` on a discrete card, as tierengine §8 decided. The pinned arena is priced at load and never given back; HostNeed counts the arena's bytes whatever its kind, so a load plans the same room under either. `anon` and `pinned` are the same-binary arms (class A). - MappedHost::read_in_place: the opt-in constructor for bytes the card and the CPU both read, not zeroed. Write-combined on an integrated device alone (in_place_flags, from CU_DEVICE_ATTRIBUTE_INTEGRATED as row_prefetch reads it), where a kernel reads write-combined pages at the rate of its own memory (midagedev#13). Until the card leg, the host leg reads every slot on the CPU. On a GB10 that leg did not slow the pinned arena down: Qwen3.8 UD-Q4_K_XL, BLOOMERY_QWEN38_EXPERTS=host, BLOOMERY_HOST_ROOM=72G, a 16 GiB arena, -n 64, a discarded anon warm-up, then three arms of each kind in ABBA order, one token sequence over every arm: decode p50 anon 181.4 ms against pinned 164.1 (-9.5 %), mean step 184.7 against 170.5 (-7.7 %), load 8.7 s against 10.1 s (the means of three arms [derived]). Every arm filled 3068 slots and evicted none; the fills' wall was 4.40 s anon against 3.42 s pinned, about the 0.90 s the 63 steps saved, so the gain is mostly the first touch of anonymous pages, and load, prompt and steps took 0.45 s longer with pinned [derived]. Whether the driver maps those pages write-combined for the CPU on a GB10 was not checked. On an RTX 5090 the CPU reads write-combined pages uncached. In one session at the same settings with BLOOMERY_HOST_ROOM=40G, two arms each, decode p50: anon 86.6 and 87.1 ms, the arena allocated with WRITECOMBINED 681.1 and 686.6, without it 83.9 and 83.4 (a scratch build that dropped the flag). This change there, on f382b47, three arms each: anon 84.9 ms against pinned 83.5, load 4.0 s against 6.6 s. MappedHost keeps one cuMemHostAlloc site; new() keeps its flags and its zero fill. atomic_u32 checks the word's address for alignment: the driver does not promise a page-aligned allocation (midagedev#109). - engram: DirectFile::read_span_uninit reads into a buffer of MaybeUninit<u8>, so a pinned arena's unwritten pages are never viewed as initialized bytes. The one read loop is libc's pread over the buffer as it stands (std's read_at is the same pread on Linux), and read_span hands its bytes to the same loop. - levers: NvTierMem holds the lever's three words; NvTierMem::pinned(integrated) is the one place `auto` is resolved, and Levers::nvtier_mem reads the word. ArenaKind::of asks it with raw_device_integrated, the bit in_place_flags is given, so `auto` pins exactly the arenas that would be write-combined; nvme_tier calls it. accessors_read_their_rows holds each word's answer on both kinds of device; with Auto resolved to !integrated it fails on `auto`. - placement: HostNeed::arena's doc counts an arena of either kind. - nvtier: ArenaMem holds Anon(AnonMap) or Pinned(MappedHost) with its own Send/Sync argument, one bounds check for slice and read_into. read_into reads through read_span_uninit; slice lends only a part its fill wrote (a fill that placed fewer bytes than the part needs is refused before its slot reads filled). ArenaMem::pinned allocates DIRECT_ALIGN bytes more than the arena and starts it, and its device address, at the allocation's first DIRECT_ALIGN-aligned byte: cuMemHostAlloc promises no alignment, and on an RTX 5090 (driver 595.99.02) as on a GB10 (driver 595.84) a request that fits the free space of a 2 MiB chunk shared with earlier small allocations comes out of it at a 512 B step, with or without WRITECOMBINED (seen from 4 B to 1 MiB; 3 MiB and 64 MiB got mappings of their own). Its allocation error names BLOOMERY_NVTIER_MEM=anon. give_back is a no-op on Pinned: the allocation is the load's. On write-combined pages a MADV_DONTNEED leaves the range unbacked for the host and for a cuMemcpyDtoH from its device address (on a GB10, and on an RTX 5090 with WRITECOMBINED: the next fill's read fails with EFAULT, and a CPU read or that copy faults with SIGBUS). Without WRITECOMBINED on an RTX 5090 the allocation is a shared mapping, which keeps the pages and their bytes through the advice. NvTier::of_paged takes the kind (ArenaKind), the card's context with Pinned, and ends in NvTier::carve: the arena's memory, each layer's base and card address, the slot books, the reader and the dropper. LayerArena::span_of is the one reading of a part's place in the mapping: part_at, staged_slot and the audit call it. - model: TierSlots::slot names the pin its caller holds while it reads, which ArenaMem's SAFETY lines cite. PinRelease::device_slot (default None) and TierPin::device_slot(id, part): the part's first byte as the card addresses it, None for an id the pin does not hold and on an arena the card does not map. - devmap: two hw tests over one fixture, a tier carved by NvTier::carve over two paged layers (the tested layer's base past 0) in the arena ArenaKind::of gives the lever's `pinned`, allocated after a one-byte allocation of the same kind, whose flags (cuMemHostGetFlags) hold WRITECOMBINED on an integrated device alone; a file and a range per stack row, part_of [2, 0, 1] and an expert in the slot before. Three rounds fill expert 7's slot with bytes that differ at every offset from every other round's and row's, give the slot back between rounds, and want the card's DtoH copy through the pin's (NvTier::pin) device_slot, then the tier's TierSlots::slot, equal to the file's bytes for every part. Then an arena of the lever's `anon` gives no device address and the same host bytes, and `auto` is Pinned on an integrated device alone. One test fills through the page cache, the other O_DIRECT (the tier's default). Mutants (a GB10 at this tree): the carve leaving a layer's card address unset, ArenaKind::of resolving `auto` by the other bit, device_slot dropping the layer base, and ArenaMem::pinned starting the arena at the allocation's first byte, each red at both tests on its own message. Earlier versions held: give_back advising the pinned pages with MADV_DONTNEED (red at the refill, whose read fails with EFAULT, on a GB10; on an RTX 5090 the advice keeps the pages, and the mutant stays green, midagedev#109); part_at dropping the skew or the slot's offset, reading window 0, or taking the part for the row (each red on the card's read). in_place_flags's unit test holds both answers on any machine: write-combined on every device is red there (and on a discrete card at both devmap tests), write-combined on none is red there and, on an integrated device, at both devmap tests. - swap: PassReport::released_bytes is the bytes of the slots a landing freed, pages a pinned arena keeps. - unsafe ratchet: three blocks in gpu and two in engram, each with its SAFETY line. At this commit's tree on a DGX Spark (GB10, driver 595.84), tools/box.sh in local mode, --arch sm_86, BLOOMERY_NVTIER_MEM unset: the nvtier hw tests (42 passed, both devmap tests), just gate-gpu-lib (1 + 229 + 4 passed), just gate-levers (40 passed), bloomery-engram's lib (18 passed) and its direct-read hw test, and each of this tree's four mutants above red at both devmap tests on its own message, then green again reverted: the carve dropping the card address ("a pinned slot's address"), auto resolved by the other bit ("auto pins on an integrated device alone"), device_slot dropping the layer base ("round 0 part 0: the card's read of the slot"), the arena at the allocation's first byte ("the pinned arena's first byte 0x32f000400 is not 4096-aligned"). On an Apple M4 Pro Mac at this tree: cargo fmt --check, tools/mac-check.sh check (0 warnings), the lint (41 warnings, the ratchet's count), tools/check-comments.sh and tools/check-unsafe.sh green. The earlier mutants, the RTX 5090 runs and the A/B ran on earlier versions of this change: 31a73dd on b2080a5 (the GB10) and da88ce4 on f382b47 (the RTX 5090), whose A/B arms each set the lever; just mac-static and just mac-test on an Apple M5 and the GB10's probe loads (the arena mapped from /dev/nvidia* unset and with `pinned`, not with `anon`, the same tokens in all three) ran on 00b71de7 on d90151e. Not run: gate-gpu-nvtier, whose plan opens only an A6000 or a 3090 (gate_card).
…location for the tier, auto pinned on an integrated device and anon on a discrete card, and D1 is red on write-combined pages only tierengine.md: the opening says S7 has landed and the contributor holds its card leg. (d): nvme_tier picks the arena by BLOOMERY_NVTIER_MEM and the device's integrated bit: auto is Pinned on an integrated device and Anon on a card with its own memory, and the same bit makes a Pinned arena write-combined on an integrated device only. A MADV_DONTNEED on write-combined pinned pages leaves them unbacked (the next fill's read fails with EFAULT, a CPU read faults); on an RTX 5090 without WRITECOMBINED the shared mapping keeps its bytes, no stale alias was seen, and D1 is required red on an integrated device only (midagedev#109). Its line references point at this tree. (h) item 1 gives device_slot(id, part) -> Option<u64>; item 2 makes the arena one allocation for the tier, counted in HostNeed.arena until the card leg, and allocated without WRITECOMBINED on a discrete card. The S7 row takes the default (auto is Q5's answer, pinned on an integrated device only), the devmap hw tests and D1's verdict per device, and its status; §5's order line leaves the contributor S7's card leg. The §8 decision table, whose Q5 answer the default follows, and §7 stay as recorded. dgx-spark.md: §1's P4 row and file list, §8's opening and "The owner", §10's P4 item, and the state lines under §11's answers 12 and 19 say S7 is in, the arena on a Spark is pinned by default, and the card leg is left. The answers' own wording stays as recorded.
There was a problem hiding this comment.
Verdict: CLEAN: No must-fix finding remains; the two suggestions concern validation and load diagnostics.
Non-blocking
⚠ Validate the pinned default under memory pressure: crates/gpu/src/host/nvtier.rs:179.
The allocation pins the whole arena at load. On integrated devices, Gpu::alloc_room reads available host memory; crates/gpu-deepseek41/src/chain/attn/batch.rs:670 uses it to admit prompt buffers. Unfilled pinned pages therefore consume room anonymous pages leave available.
The PR reports seven-token GB10 runs with no evictions. Add P=512 and P=4096 runs and an arena that evicts; record token ids, prompt room and fill/eviction counts.
⚠ Preserve the alignment probe: crates/gpu/src/host/nvtier.rs:187.
The alignment fix and its fixture are present, but NvTier::stats() exposes neither allocation kind nor the offset from the driver's allocation to the aligned arena.
Keep a standalone probe under tools/, or expose those fields through stats or a record, so a load can report the driver's allocation behavior without editing the fixture.
Checked
Duplicates: MappedHost::alloc owns the allocation call; host and device slot offsets share LayerArena::span_of.
Complexity: bloomery_levers::read adds one lever-membership condition.
Silent failure: read_pick rejects incomplete part reads; missing addresses return None; pinned give-back retains pages. The ignored result removes test scratch files.
Tests, static mutation analysis: WC and Auto inversions fail flags/accessor assertions. Both devmap gates compare host/device reads with file bytes across three rounds; dropped skew, row, slot or layer offsets fail comparisons. Removing the pin-membership check fails the unheld-id assertion. GPU runs and FAIL-first results are reported by the PR.
Gates: unsafe ratchets carry PIN(2026-10-13) and new-block reasons; changed gate code retains its assertions.
Performance: the fill read loop uses pread, retrying short reads and EINTR.
Escape catalog: C1–C4 have no matching changed script code. No ledger lessons are in force.
Card check: gpu-evidence. This pull request changes crates/gpu-deepseek41/src/body.rs, crates/gpu-gates/src/bin/gate_nvtier.rs, crates/gpu-glm5next/src/body.rs … and carries a gate run on the author's card, which counts as verified.
🔨 Shingler, bloomery's review bot: approves a clean pull request and requests changes on a must-fix (how it works)
What and why
Part of #13: S7 of
docs/research/tierengine.md§5, which the maintainer left to contributors with itsdevmapclause, to be checked onany card (#13 (comment),
#13 (comment)). It follows the plan in my offer on #13
(#13 (comment)). Two things I found on the way, a pinned
allocation off a 4 KiB boundary and the D1 mutant's verdict per card, are in #109; this PR fixes the first and writes
the second into tierengine.md.
Closes #109.
A new lever,
BLOOMERY_NVTIER_MEM, picks the memory of the NVMe expert tier's RAM arena.anonis today's anonymousmapping.
pinnedis onecuMemHostAllocfor the whole tier withPORTABLE | DEVICEMAP, plusWRITECOMBINEDon anintegrated device only, carved by
layer_basesas before.auto(unset) ispinnedon an integrated device andanonon a card with its own memory, as tierengine.md §8 Q5 answered (point 1). On a pinned arena a pin gives thecard's address of each part of a filled slot (
TierPin::device_slot), which the card leg will read from. Until thenthe host leg reads the slots on the CPU, as it does today. Both arenas gave the same tokens in every run below.
What changed:
MappedHost::read_in_place(crates/gpu/src/graph.rs) is the constructor for bytes the card and the CPU both read.It does not zero the pages; the arena treats them as unwritten until a fill writes them (next item).
in_place_flagsaddsWRITECOMBINEDwhenCU_DEVICE_ATTRIBUTE_INTEGRATEDis set and nothing otherwise.MappedHostkeeps onecuMemHostAlloccall (MappedHost::alloc), andMappedHost::newkeeps its flags and itszero fill.
MappedHost::atomic_u32now checks the word's address for alignment instead of assuming a page-alignedallocation, which gpu: a small pinned arena from cuMemHostAlloc can start off a 4 KiB boundary, and S7's D1 mutant is red only on write-combined pages #109 showed the driver does not promise.
DirectFile::read_span_uninit(crates/engram/src/direct.rs) reads into a&mut [MaybeUninit<u8>], because a&mut [u8]over the pinned arena's unwritten pages would claim they are initialized. The one read loop now callslibc::preadon the buffer as it stands. std'sread_atis the samepreadon Linux, so a fill makes the samesystem call as before.
read_span(&mut [u8])hands its bytes to the same loop, and its callers are unchanged.NvTierMem(crates/levers/src/lib.rs) holds the lever's three words, andNvTierMem::pinned(integrated)is theone place
autois resolved.ArenaKind::of(crates/gpu/src/host/nvtier.rs) asks it with the device'sintegrated bit, read by the same
raw_device_integratedthatin_place_flagsis given, soautopins exactly thearenas that would be write-combined;
nvme_tier(crates/gpu/src/model.rs) calls it.accessors_read_their_rowsreads each word and holds its answer on both kinds of device.
ArenaMem(crates/gpu/src/host/nvtier.rs) holdsAnon(AnonMap)orPinned(MappedHost).sliceandread_intoshare one bounds check.
read_intoreads throughread_span_uninit, andslicelends only a part its fill wrote:a fill that placed fewer bytes than the part needs is refused before its slot reads filled.
ArenaMem::pinnedallocates
len + DIRECT_ALIGNbytes and starts the arena at the allocation's firstDIRECT_ALIGN-aligned byte, anddevice_starttakes the same offset, so the host's bytes and the card's address stay the same slot.cuMemHostAllocpromises no alignment: on an RTX 5090 (driver 595.99.02) and on a DGX Spark (GB10, driver 595.84),a request that fits the free space of a 2 MiB chunk shared with earlier small allocations comes out of it at a
512 B step, with or without
WRITECOMBINED(gpu: a small pinned arena from cuMemHostAlloc can start off a 4 KiB boundary, and S7's D1 mutant is red only on write-combined pages #109). If the allocation fails, the error namesBLOOMERY_NVTIER_MEM=anonas the way to keep the anonymous arena.give_backdoes nothing onPinned, because theallocation is the load's. On write-combined pages a
MADV_DONTNEEDleaves them unbacked: the next fill's read failswith
EFAULT, and a CPU read or acuMemcpyDtoHfrom the device address getsSIGBUS(on the DGX Spark, and onthe RTX 5090 with
WRITECOMBINED).NvTier::of_pagedtakes the kind (ArenaKind), which carries the card'scontext for
Pinned;host_runandnvme_tierpass the context down from each body. The carve at the end ofof_paged(the arena's memory, each layer's base and card address, the slot books, the reader and the dropper) isnow its own constructor,
NvTier::carve, whichof_pagedreturns through and thedevmaptests call.TierPin::device_slot(id, part)(crates/model/src/moe.rs) returns the part's first byte as the card addressesit. It returns
Nonefor an id the pin does not hold and on an anonymous arena.LayerArena::span_ofis the oneplace a part's position in the mapping is computed.
LayerArena::part_at(whichTierSlots::slotanddevice_slotcall),NvTier::staged_slotand the audit all call it.TierSlots::slot's doc now names the pin itscaller holds while it reads, which the
ArenaMemSAFETY lines cite.devmap: two hw tests innvtier.rsover one fixture, one filling through the page cache and oneO_DIRECT. Thefixture is a tier built by
NvTier::carveover two paged layers, so the tested layer's base comes fromlayer_basesand is past 0; its arena kind comes fromArenaKind::ofwith the lever'spinned. Callingof_pageditself would need a routed plan and a model file, so the fixture starts after the plan's checks. Eachtest runs three rounds of fill,
NvTier::pin, a DtoH copy through the pin'sdevice_slotand the tier'sTierSlots::slot, with a give-back between rounds, and checks every part against the file's bytes. The fixturefirst makes a one-byte allocation of the arena's kind and keeps it alive, so the arena is allocated where a shared
chunk can put it off a page boundary. It gives each stack row its own file and range, maps the leg's parts to other
rows (
[2, 0, 1]) and fills an expert in the slot before. It reads the arena's flags withcuMemHostGetFlagsandwants
WRITECOMBINEDexactly on an integrated device. Then it carves an arena with the lever'sanonand wantsNonefromdevice_slotand the file's bytes fromTierSlots::slot, and it wantsautoto givePinnedexactlyon an integrated device. A unit test holds
in_place_flags's two answers on any machine.PassReport::released_bytes' doc says a pinned arena keeps those pages, andHostNeed::arena's doc(
crates/placement/src/placement/workstation.rs) counts an arena of either kind. The unsafe ratchet takes threeblocks in
gpu(1743 to 1746:ArenaMem::sliceand thedevmaptests' DtoH copy and flags read) and two inengram(8 to 10: thepreadandread_span's view of its bytes), each with its SAFETY line.docs/research/tierengine.md, the opening: S7 has landed, and the contributor holds its card leg.nvme_tierpicks the arena by the lever and the device's integrated bit, the same bit that decideswrite-combining; what a
dontneeddoes on a pinned arena, per gpu: a small pinned arena from cuMemHostAlloc can start off a 4 KiB boundary, and S7's D1 mutant is red only on write-combined pages #109, and D1 required red on an integrated deviceonly. Its line references point at this head.
device_slot(id, part) -> Option<u64>. Item 2: one allocation for the tier, counted inHostNeed.arenauntil the card leg, allocated withoutWRITECOMBINEDon a discrete card.devmaphw tests with D1's verdict per device, and its status. §5's order line:the contributor holds S7's card leg.
docs/contrib/dgx-spark.md§1 (the P4 row and the file list), §8 (its opening and "The owner"), §10 (the P4item) and the state lines under §11's answers 12 and 19: S7 is in, the arena on a Spark is pinned by default, and
the card leg is left.
§7 (dated 2026-10-11), and the wording of dgx-spark.md §11's answers.
Points for review:
autoispinnedon an integrated device andanonon a discrete card. This is the answer tierengine.md §8records for Q5/Q7 (2026-10-11), "anonymous memory on a discrete card;
autois integrated only", and the S7 row's"default
auto= integrated only". Q5's recommendation waited for S7's clause and a measured registration time;the clause is now the two
devmaptests (point 4), and the allocation time is in point 3. On the DGX Spark thepinned arena lowered decode p50 by 9.5 % (181.39 ms to 164.15 ms) and added 1.37 s to the load at a 16 GiB arena.
That run read a cold arena: every arm filled 3068 slots and evicted none in its 64 tokens. The fills' wall
(
fill_ns) was 0.98 s lower withpinned(3.42 s against 4.40 s), close to the 0.90 s that the 63 timed decodesteps saved (10.74 s against 11.64 s) [derived, the means of three arms]. Most of the decode gain is therefore the
first touch of anonymous pages, which the pinned arena pays at load instead: the load, the prompt and the 63 steps
together took 0.45 s longer with
pinned(22.16 s against 21.71 s) [derived]. Once an arena has filled, aneviction hands its slot over with its pages, so I expect that term to go to zero on both kinds; I did not measure
an arena that evicts on the DGX Spark (see "Not run" below). On the RTX 5090 the pinned arena lowered decode p50 by
1.6 % (84.87 ms against 83.49 ms, three arms each, where the 95 % interval is about ±1.4 % [derived, at the 0.6 %
SD of
AGENTS.md]) and made the load 2.6 s longer (6.6 s against 4.0 s). I kept the recorded answer for a discretecard. If a discrete card should pin too, the switch is the
Autoarm ofNvTierMem::pinned, and a longer A/B on adiscrete card would show whether the 1.6 % holds. A pinned arena never gives pages back, but the plan already
counts them:
HostNeed::bytesadds the arena's bytes whatever its kind, so a load plans the same room undereither. The pinned allocation asks 4096 B (
DIRECT_ALIGN) more than the carve, for its alignment; when the carvefills the granted bytes exactly, those 4096 B are outside what the plan counts.
BLOOMERY_NVTIER_MEM=anonkeepstoday's arena on any device.
cuMemHostAllocperLayerArena. This PR keeps oneallocation for the tier, as
AnonMapdoes today, since §6.2 row 2's 64 GiB allocation succeeded, and keeps thearena in
HostNeed.arena. (h) item 2 counted it inHostNeed.cards/pool_card_bytes; I would move it therewith the card leg, as my offer said. The docs commit writes both into (h) item 2.
cuMemHostAlloccall alone. At a 16 GiB arenathe DGX Spark's load wall is 8.70 s for
anonand 10.07 s forpinned(the means of three arms), 1.37 s more[derived]. §6.2 row 2's linear cost, 1.59 s at 19.91 GiB, gives about 1.28 s at 16 GiB [derived].
devmaphw tests, not agate_nvtierclause.gate_nvtierplans withgate_card,which opens only an A6000 or a 3090, so a clause there could not run or be shown to fail on my cards.
just gate-gpu-libruns both tests on any card. On those two cardsautoisanon, so with the lever unsetgate_nvtierbuilds the same anonymous arena as before; its only change is theArenaKind::Anonargument inrefuse_clauses.Order: I would land this after #100. Today no serving default on a Spark builds an arena (Qwen3.8 loads whole, and
GLM and V4.1 are refused at the floor), so GLM's and V4.1's paged loads after #100 are the first to use the pinned
default.
Files that overlap the maintainer's train:
crates/gpu/src/host/nvtier.rs,crates/gpu/src/graph.rs,crates/gpu/src/model.rs,crates/engram/src/direct.rs,crates/gpu-gates/src/bin/gate_nvtier.rs(one line),crates/levers/src/{lib,registry,tests}.rs,crates/model/src/moe.rs,crates/placement/src/placement/workstation.rs(one doc comment),
tools/unsafe-ratchet.txt,docs/research/tierengine.md,docs/contrib/dgx-spark.md, and onecall line each in the four bodies that build a host run. #112 (the LES plan) rewrites the same fill path in
nvtier.rsin its T4 to T6 (read_pick,fill,PickRead,Inflight); this PR keeps their signatures, and itsArenaMem::read_intoandwindoware the door those fills write through.Code shape
The review reads these first. A box left unticked needs one line of why.
cuMemHostAllocsite (MappedHost::alloc) for every flag set; one reading of a part'splace in the mapping (
LayerArena::span_of) for the host's bytes, the card's address, the lane's slot read andthe audit; one read loop (
engram::direct::read_all) for initialized and unwritten buffers; one bounds check(
ArenaMem::window) for reads and fills; one integrated bit (raw_device_integrated) for both the default andwrite-combining.
gpu.context()to the commonhost_run/nvme_tier.MappedHost::newkeeps its zero fill on top ofMappedHost::alloc.ArenaMem::pinnedstarts the arena on aDIRECT_ALIGNboundary it computes, and anassert names the address if that ever fails; a failed pinned allocation names
BLOOMERY_NVTIER_MEM=anon;device_slotgivesNone, never an address, for an id the pin does not hold or on an anonymous arena; aBLOOMERY_NVTIER_MEMvalue outside its three words is refused by name.tools/check-comments.shis green.PIN(2026-10-13):lines;tools/check-unsafe.shis green at the new pins.cargo fmtandcargo clippyare clean on what I touched; the lint count did not go up (41 warnings,equal to
tools/lint-ratchet.txt).Duplication found or removed (
path:line), or "none": atbd29e9c9a part's place in the mapping was computedthree times, in
TierSlots::slotforNvTier(crates/gpu/src/host/nvtier.rs:2671-2673), inNvTier::staged_slot(
:1754-1755) and inNvTier::audit_slots(:2134-2135). All three, anddevice_slot, now callLayerArena::span_of.What I ran
At this head (
905e7ac5), on a DGX Spark intools/box.shlocal mode (BLOOMERY_BOX_LOCAL=1) in the CUDA 13.3.1 devcontainer,
--arch sm_86, withBLOOMERY_NVTIER_MEMunset:FAIL-first for the
devmapfixture at this head: I applied one mutant at a time, rebuiltbloomery-gpu, ran thenvtierhw tests, then reverted, rebuilt and saw them green again. Each mutant is red at bothdevmaptests:devmaptests (DGX Spark)NvTier::carveleaving a layer's card address unseta pinned slot's addressArenaKind::ofresolvingautoby the other bitauto pins on an integrated device alonedevice_slotdropping the layer baseround 0 part 0: the card's read of the slotArenaMem::pinnedstarting the arena at the allocation's first bytethe pinned arena's first byte 0x32f000400 is not 4096-alignedOn a Mac mini (Apple M4 Pro):
cargo fmt --check,tools/mac-check.sh check(0 warnings), the lint (41 warnings,equal to
tools/lint-ratchet.txt),tools/check-comments.shandtools/check-unsafe.share green.Not run on a GB10: a load whose arena evicts (every arm in the A/B below evicted 0), a prompt longer than 7 tokens,
ppat P = 512 and 4096, andbloomery-serve. The prompt path is the one to watch. After the load,Gpu::alloc_roomreads the host's available memory on an integrated device to size a prompt batch(
crates/gpu-deepseek41/src/chain/attn/batch.rs,crates/gpu-glm5next/src/prefill.rs,crates/gpu-mimo2/src/prefill.rs). A pinned arena takes its whole allocation at load, while an anonymous one leavesits unfilled pages in the available memory, so the same prompt can find less room under
pinned.Selected gates:
python3 tools/recipes.py affected upstream/main --no-boxselects 144 of 156 gate recipes and twoweekly recipes (
weekly-gpu-ds41-cand,weekly-gpu-glm5next-e2e-long), most of them becausecrates/gpuandcrates/leversare in their closure. I rangate-gpu-libandgate-leverson both cards. I did not rungate-gpu-nvtier(point 4), the other tier gates (gate-gpu-ds41-nvtier,gate-gpu-glm5next-nvtier,gate-gpu-qwen38-residency,gate-gpu-swap) or the rest of the list. With the lever unset they build the sameanonymous arena as before on a discrete card; on a GB10 they would build a pinned one, which the probe loads below
check on Qwen3.8.
The rest ran on earlier versions of this change. The mutants of
part_at, D1,in_place_flags, the RTX 5090 runsand the A/B ran on two versions I pushed as branches of my fork so the commits can be read:
31a73dd2onb2080a5a(the DGX Spark, and the RTX 5090's first
gate-gpu-lib) andda88ce4e, the same changeon
f382b474(the RTX 5090's mutants, repeated gates and A/B). The Mac'smac-staticandmac-testand the DGXSpark's probe loads ran on
00b71de7ond90151ed, the head before the review fixes above. Since31a73dd2thegate_nvtierclause that no card could run is taken out (point 4), and with it its unsafe block ingpu-gates, andautofollows the device (point 1); every A/B arm setBLOOMERY_NVTIER_MEMtoanonorpinned, so this does notchange what they measured. Since
00b71de7the fill reads into unwritten bytes throughread_span_uninit, a part'splace has one helper, the carve is its own constructor,
atomic_u32checks its address, and thedevmapfixturegoes through the tier's carve, pin and
TierSlots::slot; the mutant table above is thedevmaprun at this head.Of this PR's files, the main commits between
b2080a5aandbd29e9c9change two:crates/levers/src/registry.rs(
d24cb1a4, another row) andtools/unsafe-ratchet.txt(a49e1391, theqdotpin).On an Apple M5 MacBook, with the pinned nightly, on
00b71de7:just mac-static upstream/mainis green in all 14steps (rc 0, 651 s; fmt-check, lint with 41 warnings, combos with 27 shapes run and none red, then the eleven check
scripts including
check-levers,check-unsafeandcheck-comments).just mac-testis green too: 12 crates,1139 tests passed, 0 failed, 177 ignored, in 550 s. FAIL-first for the default: with
NvTierMem::Autoresolving to!integrated,cargo test -p bloomery-leverson a Mac mini (Apple M4 Pro) fails inaccessors_read_their_rowsonassertion left == right failed: auto(left: (false, true),right: (true, false)), and passes again reverted(40 passed).
On the DGX Spark, in
tools/box.shlocal mode (BLOOMERY_BOX_LOCAL=1) in a CUDA 13.3.1 dev container, built with--arch sm_86, on00b71de7withBLOOMERY_NVTIER_MEMunset:just gate-gpu-libgreen (1 + 229 + 4 passed, bothdevmaptests ok), then one load per lever state with the A/B's command below at-n 32, reading the process's/dev/nvidia*mappings from/proc/<pid>/mapsonce the load line printed (pinned host memory the card maps showsthere; an anonymous arena does not):
BLOOMERY_NVTIER_MEM/dev/nvidia*mapped (B)anonpinnedThe three loads printed the same token ids.
On
31a73dd2:On the RTX 5090, in local mode on the host, built with
--arch sm_86: on31a73dd2, the samenvtierrun (42passed, both
devmaptests ok) andjust gate-gpu-lib(1 + 229 + 4 passed); onda88ce4e,just gate-gpu-libthree times (1 + 229 + 4 each, base
f382b4741 + 226 + 4) andjust gate-levers(40 passed).FAIL-first on
31a73dd2(DGX Spark) andda88ce4e(RTX 5090), with thedevmapfixture of that version: each mutantapplied alone,
bloomery-gpurebuilt, thedevmaptests (and for the twoin_place_flagsmutants the flags unittest) run; then reverted, rebuilt and green again on both cards.
part_atthen computed the part's place itself;at this head that computation is
span_of.give_backadvising pinned pages withMADV_DONTNEEDdevmapred at the refill:round 1 slot 1: the slot's fill: NvTier::fill: .../row0: a direct read of 4096 B at 0: Bad address (os error 14)devmapok, the pages keep their bytes (#109)part_atdropping the skewdevmap:round 0 part 0: the card's read of the slotpart_atdropping the slot's offsetpart_atreading window 0part_attaking the part for the rowdevice_slotdropping the layer baseArenaMem::pinnedstarting the arena at the allocation's first bytedevmap:the pinned arena's first byte 0x32f000400 is not 4096-aligned(and0x32f00d400)devmap:0x70ab97200200and0x70ab9720d400in_place_flags: write-combined on every devicea card across PCIe(devmapgreen: integrated here)devmap:the arena is write-combined on an integrated device alonein_place_flags: write-combined on no devicedevmap:an integrated device,the arena is write-combined on an integrated device alonean integrated deviceD1 ran in two forms on the DGX Spark (a failed advice as a panic, and as the give-back's error) and in the second on
the RTX 5090. The advice itself returned 0 on both cards. On the RTX 5090 the arena is not write-combined and the
pages keep their bytes, so D1 cannot turn red there (#109). In the alignment mutant the arena lands inside the shared
chunk of the fixture's one-byte allocation, off a page boundary.
Timing.
generate_qwen3moebuilt from the same tree (cargo oxide build --arch sm_86 -- -p bloomery-gpu-gates --features gpu --release --bin generate_qwen3moe), run directly, one machine lock held for all arms, each arm startedonce load1 was under 1.00, a discarded
anonwarm-up, thenanon pinned pinned anon anon pinned. Command:Model:
Qwen3.8-Flash-Next-UD-Q4_K_XL-00001-of-00004.gguf(unsloth/Qwen3.8-Flash-Next-GGUF). Every arm granted the17179869184 B arena it asked for, and every arm of a run printed the same token ids. The prompt is 7 tokens.
DGX Spark (
NVIDIA GB10, 595.84, stock clocks, SM 208 MHz before each arm and 2411 MHz after it), room 72G, on31a73dd2:fill_nsper arm (s)anonpinnedDecode p50 9.5 % lower and mean step 7.7 % lower with
pinned; load 1.37 s longer [derived, from the means]. Point 1reads the
fill_nscolumn against the steps.RTX 5090 (
NVIDIA GeForce RTX 5090, 595.99.02, CPU governorperformance), room 40G, onda88ce4e:anonpinnedI did not run the A/B again at this head. The fill makes the same
preadas before, and the other fixes move nostep-time work.
What I ran into
This does not block this PR.
On an RTX 5090, a write-combined arena makes decode about 8 times slower. The first build of this change set
WRITECOMBINEDon every device, and the host leg reads every slot on the CPU. The CUDA driver API documentsWRITECOMBINEDmemory as "cannot be read efficiently by most CPUs". A scratch build that only dropped the flagbrought the pinned arm back to
anon's speed. In one session, at the settings above (40G, 16G,-n 64), the CPUgovernor at
performance, two arms each, interleaved, equal tokens over all of them:anonpinned,WRITECOMBINEDpinned, no extra flagThe 16.1 s load was the first arm of the scratch build after it was built; I did not find its cause, and the
second arm of that build loaded in 6.3 s.
So this PR sets
WRITECOMBINEDon an integrated device only, and thedevmaptests check the allocation's flagson both kinds of device. On the DGX Spark the same flag did not slow the host leg down (the table above). I did not
check whether the driver maps those pages write-combined for the CPU on a GB10, so "WC reads are fast there" and "WC
is not applied there" are not told apart.
To see it: on a discrete card, apply
in_place_flagsreturningCU_MEMHOSTALLOC_WRITECOMBINEDfor both answers,build
generate_qwen3moeand run the command above withBLOOMERY_NVTIER_MEM=anonand=pinnedon a pagedQwen3.8 plan with an arena.
A small pinned arena can start off a 4 KiB boundary, and D1 is red only on write-combined pages. Both are in
#109 with the probes behind them; the alignment fix and the fixture's one-byte allocation above come from the first,
and the tierengine.md lines from the second.
Hardware (if the change is for a machine)
NVIDIA GB10, 595.84, stock clocksnvtierhw tests,gate-gpu-lib,gate-levers,bloomery-engram's tests and its direct-read hw test, the fourdevmapmutants. On00b71de7:gate-gpu-liband the three probe loads. On31a73dd2:gate-gpu-lib(base once, the change three times), thenvtierhw tests,gate-levers, the mutants, the A/B. Built in a CUDA 13.3.1 container,--arch sm_86, cargo-oxide87bc9518, rustc 1.100.0-nightly (2026-08-27)NVIDIA GeForce RTX 5090, 595.99.0231a73dd2: thenvtierhw tests,gate-gpu-lib. Onda88ce4e:gate-gpu-lib(base once, the change three times),gate-levers, the mutants, the A/B. The write-combined runs in "What I ran into" on the first build. CUDA 13.3, rustc 1.100.0-nightly (2026-08-27)check-comments,check-unsafe. On00b71de7:just mac-static upstream/mainandjust mac-teston the M5, the levers FAIL-first on the Mac miniAI help
AI 코딩 에이전트가 코드를 작성한 후 설명을 썼고, 테스트를 마쳤습니다. diff 리뷰와 PR 초안 리뷰는 제가 직접 했습니다.