Repository navigation
Conversation
…on for a layer split by the serve scan's own arithmetic (auto ceiling or the explicit chunk, the 128-slot floor, the auto percentage cap), Niko1221#340's own-buffer rule deciding which parts lend nothing - and hand them to the resident copy before it pins resident split: a test that the RAM copy keeps each stage's lend region (red: FileExpertSource has no stage_lend_regions, and no append_stage_lend_regions) resident split: pin_cache_complement keeps each stage's lend region in the RAM copy too (stage_lend_regions, append_stage_lend_regions) - the experts the prompt path borrows under a split streamed from the mapped files through page faults (3.7 GiB read for one 73K prompt, 1.5 s of a 3.9 s chunk on host staging, 2026-10-06 rig); one budget caps complement and regions, experts past it keep the file fallback generate: size every part's lend region for a layer split by the serve scan's own arithmetic (auto ceiling or the explicit chunk, the 128-slot floor, the auto percentage cap), Niko1221#340's own-buffer rule deciding which parts lend nothing - and hand them to the resident copy before it pins Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0157fjixQD2nRxvCvtXq66DD
|
Thanks for this, it fixes a gap we also ran into: we run a layer split with While measuring on top of this branch we found a related ordering issue and put a fix on top of your three commits: #1600. The lend regions are sized with #1600 is branched from this PR's head, so it can only be merged after this one. This PR currently conflicts with main in |
…s, always-on list, newer upstream note, PatchAudit facts per patch
…orrowed vs RAM-kept slots Builds on Niko1221#1190. The layer split sized its lend regions (the RAM copies of the experts the prompt path borrows) with Prefill::bytes_needed before the split's ring override was applied (Prefill::set_ring_override says it must be set before any bytes_needed), so with STRATA_SPLIT_RING=384 the prompt path borrowed 5396 slots and the copy kept 4662. The override is now decided first (decide_split_ring); include/strata/program/split_ring.hpp holds the pure helpers (split_ring_slots, split_lend_slots - the scan's own tail-slot arithmetic, now one function for the region and the borrow), src/program/split_ring_test.cpp covers them on the CPU. A start line reports borrowed vs kept slots and warns when kept < borrowed. Docs: MULTI_GPU.md.
…s, always-on list, newer upstream note, PatchAudit facts per patch
…on (red: FileExpertSource has no stage_lend_regions, and no append_stage_lend_regions)
…n the RAM copy too (stage_lend_regions, append_stage_lend_regions) - the experts the prompt path borrows under a split streamed from the mapped files through page faults (3.7 GiB read for one 73K prompt, 1.5 s of a 3.9 s chunk on host staging, 2026-10-06 rig); one budget caps complement and regions, experts past it keep the file fallback
…e scan's own arithmetic (auto ceiling or the explicit chunk, the 128-slot floor, the auto percentage cap), Niko1221#340's own-buffer rule deciding which parts lend nothing - and hand them to the resident copy before it pins
6b6277a to
6d8f179
Compare
|
Thanks for #1600 and for measuring on top of this branch. I rebased this PR on main ( The only conflict was in |
…orrowed vs RAM-kept slots Builds on #1190. The layer split sized its lend regions (the RAM copies of the experts the prompt path borrows) with Prefill::bytes_needed before the split's ring override was applied (Prefill::set_ring_override says it must be set before any bytes_needed), so with STRATA_SPLIT_RING=384 the prompt path borrowed 5396 slots and the copy kept 4662. The override is now decided first (decide_split_ring); include/strata/program/split_ring.hpp holds the pure helpers (split_ring_slots, split_lend_slots - the scan's own tail-slot arithmetic, now one function for the region and the borrow), src/program/split_ring_test.cpp covers them on the CPU. A start line reports borrowed vs kept slots and warns when kept < borrowed. Docs: MULTI_GPU.md.
…-support Upstream now carries these of our picks, in its own versions: Niko1221#1237 (pinned stage buffers, now with fences and the STRATA_STAGE_PIN switch), Niko1221#1332, Niko1221#1359, Niko1221#1372, Niko1221#1370, Niko1221#1251, Niko1221#1190 and Niko1221#1283. CPU help for short prompts: Strata's own CPU prefill share (0.1.41: on by default below 1,024 tokens on one NVIDIA GPU; -20..-34% prompt time at 512-1,000 tokens on an RTX 3060 in upstream's measurement) replaces architectds' CPU assist, which measured +11% at ~560 tokens on our desktop. prefill.cpp is upstream's, with the pool's hooks (remote_next to the workers, a headless worker stage) and Niko1221#1368's quantize-once put back. A pool PC runs its own layers alone (no local stages), so the share's default applies on every PC. Pipelined decode: upstream's N-stage loop (front stages + the last) with the pool's remote stage as the last (pl_remote via stage_ver_p, pl_ns = 2 on a pool coordinator), and architectds' verified-path-first yield, the lookup carried across windows and the pool head's penalty rows ported onto it. Batch: upstream's batch_step (the adaptive tier in batch windows, per-stage timing, Niko1221#879 non-finite logits) with Niko1221#1290's per-slot row width and the pool head's slot histories; the pool's batch groups keep their PoolLink stage. Also: the conversation cache's eviction keeps both the pool-borrowed entry and pinned prefixes (evict_oldest); the verifier's adaptive-tier gate (STRATA_ADAPT_OVERLAP) also guards the overlapped split's prelaunch (Niko1221#1120); the server's read-once body takes upstream's size limit; replicas and the pool roles both start engines; serve/pool.py keeps the engine's newer spawn tuple; the worker's parking reads available_host_bytes(); setup tests that need a ready-made engine are skipped (StrataPool compiles its own). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0157fjixQD2nRxvCvtXq66DD
…orrowed vs RAM-kept slots Builds on Niko1221#1190. The layer split sized its lend regions (the RAM copies of the experts the prompt path borrows) with Prefill::bytes_needed before the split's ring override was applied (Prefill::set_ring_override says it must be set before any bytes_needed), so with STRATA_SPLIT_RING=384 the prompt path borrowed 5396 slots and the copy kept 4662. The override is now decided first (decide_split_ring); include/strata/program/split_ring.hpp holds the pure helpers (split_ring_slots, split_lend_slots - the scan's own tail-slot arithmetic, now one function for the region and the borrow), src/program/split_ring_test.cpp covers them on the CPU. A start line reports borrowed vs kept slots and warns when kept < borrowed. Docs: MULTI_GPU.md.
The problem
#848 gave
--resident-expertswith a layer split a RAM copy of only the experts no stage's cache holds.That left out the experts the prompt path borrows:
generate.cpphandspin_cache_complementthe laterstages' cache pairs as
additional_gpu_pairs, and with that list non-empty the function switches thesingle lend region off (
expert_source.cpp:1752,lend = ... && additional_gpu_pairs.empty()— the commentat
generate.cpp:5820: "with them the copy keeps no lend region: pin_cache_complement turns the loan off").So under a split, the experts in the tail slots of every card's cache — CUDA0's part included — are in
neither VRAM nor the RAM copy during a prompt, and the prompt reads them from the model files through page
faults.
Measured 2026-10-06 (v0.1.40 build, config: 17/48 layer split, RTX 2000 Ada
stage 0 = layers 0-16 + RTX 5060 Ti stage 1 = 17-47, IQ3_S native pack without experts.bin so
--mmap-expertsmaps the GGUF shards,--prefill auto= 8192-token chunks; each card's prompt path borrows4.31 GiB of its expert-cache slots):
--resident-experts(before)STRATA_PREFILL_TIMING=1, one 8192-token chunk (GPU wall 3132 ms arena vs 3865 ms resident): dequant 232 vs678 ms, host grouping 33 vs 187 ms, host staging 365 vs 1504 ms. The expensive experts were the file-read
ones: with the borrowed slots' bytes already in RAM, grouping/staging/dequant all stream the same
bytes the arena streams, and the walk collapses to
memcpy+cudaMemcpyinstead of aper-blob page-fault trail through the mmap'd GGUF. (
--no-prefill-borrowrestored the shortprompts but cost the 73K prefill — 1428 t/s — and decode: not the fix.)
The change
FileExpertSource::stage_lend_regions(...)(header): each stage's (layer, expert) pairs, listed fromthe cache's highest slot down — the order the borrowing takes them.
detail::append_stage_lend_regions(expert_source.cpp:186): adds the regions to the compactplan after the complement; a pair already in the copy is passed over, a stage's walk stops at the first
pair that does not fit, and the next stage's walk still runs. One budget caps the whole copy — the same
available-minus-headroom reading as the complement (
--resident-budget-gib: a budget clamped to available RAM then fails the safety check (0.1.31) #403), or an explicit--resident-budget-gib.Experts past it keep the mapped-file fallback they have today.
pin_cache_complementfolds the regions in after its own lend walk (expert_source.cpp:1833) and sayshow many of the split's lent slots stayed in RAM (
the split stages lend N slots to the prompt path: M keep their experts in RAM too).generate.cpp:5827: sizes each part's region at the largest chunk the serve scan can ever pick — auto'sceiling
auto_ceiling, or an explicit--prefill, which the scan only walks down — with the scan's owntail-slot arithmetic (
cache_slots_for, the 128-slot floor, the autokAutoLendPctcap), and honoursPrefill regression on sm_86 (2x RTX A5000) in 0.1.30: ~-35% on ~2K-token prompts, not recovered by STRATA_PREFILL_STREAM_MIN / GDN_PIPELINE=0 / SCORES_MULTI=0 #340's
own_okrule: a part whose card can fund the bound chunk's buffers outright gets no region and noRAM is spent on it.
RAM arithmetic on the rig above: 25.13 GiB (complement) + ~8.6 GiB (two regions of 4.31 GiB) ≈ 33.7 GiB —
~13.5 GiB less pinned than the arena, with none of the 2.6 GiB extra NVMe reads the one prompt cost.
Answers are unchanged: the copy holds the same file bytes the fallback would read.
Untouched: the arena default, single-GPU behaviour (no regions set;
pin_cache_complement's own lend walkstays the only one), disk/keyless parking, and the expert-order/lookup semantics — resident pairs
resolve through the existing
cache_complement_blob_or_fallback.Tests
split_lend_complement_test(new, CPU-runnable): a two-stage split built the waygeneratebuildsthe call (CUDA0's cache as the primary, the later stage's pairs as
additional_gpu_pairs, emptycomplement). The device calls are wrapped at the link —
conversation_transfer_test's--wrappattern — and
pin = falsekeeps the copy pageable. It checks the copy holds exactly the two stages'lend-region pairs (
has_resident,resident_lent_slots), that the borrowed expertsare served from the RAM copy (
blob(l,e) == resident_blob(l,e), matching bytes, andfile_reads() == 0— not one read reaches the file source), that a--resident-budget-gibcap splits thetwo regions at the first pair that does not fit while the pair past it keeps its file fallback
(
file_reads() == 1), and the old Resident RAM mode with a layer split, --vram-reserve-later-mib (#642) #848 shape with no regions set (copy keeps nothing).Red on HEAD:
'class strata::core::FileExpertSource' has no member named 'stage_lend_regions'.file_expert_source_test.cpp: test_split_lend_regions(extended): the plan-level walk — bothregions compacted after an existing complement entry (passed over, not counted twice), whole-copy cap,
a refused walk leaves the plan untouched, out-of-range pair rejected before anything changes.
Red on HEAD:
'append_stage_lend_regions' was not declared in this scope.Measured after the change
Same rig and config as above,
--pcie-frac 0.20for all three columns (2026-10-06; interleaved A B C C runs,5 samples per run, medians):
--resident-expertsbefore--resident-expertswith this changeThe 2.1 GB that remains in every column is the PLE n-gram table (
--ple-io direct, unbuffered reads of a26.8 GiB shard, off the critical path), not experts.
conversation_cache_parity.pyreuse / disk / mixedpass byte-exact with this change on the split. Decode is unchanged by it (the regions only matter while a
prompt borrows; with resident vs arena decode differs by the cache-miss path, not by this change).
🤖 Generated with Claude Code