Repository navigation
Conversation
|
Two new commits since the receipt (no force-push):
Re-checked on the merged build (TP=2, 1M window, |
|
Checked locally:
|
|
Thanks @russellmm, the receipts are thorough and the trade-offs are stated plainly. One thing before it lands: could |
|
Checked locally:
|
|
Thanks @russellmm, the fingerprint is just what we asked:
The mbox still drops your merge of main, so we'll land from the branch. Then we'll run the unset path on our GB10s. |
|
CUDA SUMMARY (RTX PRO 6000, sm_120, nvcc 13.4 build): All nine kernels and their nine controls are in it, and their limits lines match the records (
The guard anyway: a row past the slots is now set back to -1 rather than left at -2 (before, Also checked: served on two RTX PRO 6000s at 1M context with |
|
Thanks @russellmm, commit 5's title is just what we asked for. Could you add the CUDA kernel-contract |
|
Thanks @ashhart. The refusal by name:
The SUMMARY line for the nine kernels (ten cases with the past-its-slots one) and their controls, on an RTX PRO 6000 (sm_120, nvcc 13.4 build): It comes from the same Layout: noted. When #1423 lands with 15 I'll merge main and take 16 (or leave it to your rebase, as you prefer). Also checked: served at 1M context with |
|
Thanks @russellmm, We went to land it, but it no longer rebases cleanly onto current main. Main's Could you rebase onto current main and carry the |
|
Thanks @ashhart. The branch already carries the tk change into So I've done the rebase here, with no force-push:
Main has been moving quickly under this PR, which is good to see: The tip's tree is byte-identical to this PR merged with current main ( Checked on the rebased branch:
|
|
Thanks @russellmm, that's even better than what I asked for, and thanks for checking every commit on its own. We've taken |
|
@russellmm, our two-GB10 run is in: GLM-5.3-Flash NVFP4 at TP=2, main against this head (
So with the switch unset, nothing changes, which is what we needed to see. It's gated and queued to land. Thanks! |
|
One more thing, @russellmm: since our run, main has moved under this, and it now conflicts in |
…st memory With TF_GLM_KV_HOST=1 (BF16 KV on pages) the pool's pages live in pinned host memory and only the pooled index keys stay on the device, on pages of their own. Each sparse DSA call gathers the rows it picked, once each, into device slots and attends there with the existing kernels; dense calls gather their visible rows the same way. Decode and verify calls keep each layer's last gathered rows on the device and fetch only the rest. The plan counts a page's device cost as its pooled keys; TF_GLM_KV_HOST_GIB sizes the host pages (one window unasked), and TF_GLM_KV_HOST_SHARED=1 gives the ranks of one host a single copy in /dev/shm.
…nks it The shared pages' file kept its name until rank 0 closed it, so a rank stopped by a signal or a crash left the whole window's pages in /dev/shm. A count past the pages now records each rank's mapping, and the rank that completes it unlinks the file: the mappings stay, and nothing outlives the ranks. Rank 0 still unlinks at close if a rank never mapped it.
The shared page file used POSIX open flags and DynLib.close, which the Windows test builds can't compile. Off Linux, SharedPages is now a stub that refuses with SharedMemoryUnavailable, so each rank keeps its own pages; Linux is unchanged.
A rank started without TF_GLM_KV_HOST would size a different pool from its peers, _SHARED needs every rank to map the same file, and each rank sizes its row marks from its own _GIB while rank 0 counts the pages. Settings now reads the three (refusing a bad value, _SHARED without the base, or a _GIB that isn't positive), and the fingerprint carries kv_host (0, 1 or 2) and kv_host_gib, so ranks that disagree stop at startup naming the setting.
…ll past its slots is refused contract_glm_kv_host.zig checks the nine tf_glm_kvh_* kernels exactly against a host replay, at decode, verify and 2,048-row chunk shapes with pages out of order, each with a planted control (a stale mark, a dropped row or swapped slots); glm_cuda_records.zig records their launches from nvcc 13.0. A call's distinct rows are bounded by the sequence's capacity, which Gather.cap equals, but mark no longer leaves a row past the slots at -2: it sets it unseen and raises a flag in mapped host memory, which Engine.checkGathers reads after its synchronize and refuses with error.GlmKvHostSlots.
…th a test Commit 5's evidence, here since the branch is under review and not force-pushed. The nine tf_glm_kvh_* kernels and their planted controls in the CUDA kernel contract (RTX PRO 6000, sm_120, nvcc 13.4; the kernels and limit records narrowed to tf_glm_kvh_* by a local name filter): SUMMARY 10 kernels: 10 pass gates 1-4, 0 fail, 0 known findings; 9 controls: 9 red as planted, 0 missed; 0 limit problems; gates 405 ms, limits 0 ms The refusal: Engine.checkGathers now calls kv_host.checkActive(), and `tf-cuda-test glm-kv-host-slots` (one GPU) runs the real Gather.gather with 256 distinct rows for 64 slots: it is refused as error.GlmKvHostSlots, no row is left marked, and the next call, 48 distinct rows, is accepted with each token's slot holding its row. The flag's read-and-clear is a host test, so CI runs that part.
d065689 to
c75fd0d
Compare
|
Thanks @ashhart, and thanks for the two-GB10 run: good to see the unset path equal in all 52 cells. Rebased onto current main ( Checked on the new head:
|
Part 3 of 3. This is the last of three changes that let GLM-5.3-Flash on CUDA use system memory for what the
device doesn't need resident, so two 96 GB cards serve the model's full 1,048,576-token context:
TF_GLM_HOST_CACHETF_GLM_VISION_HOSTTF_GLM_KV_HOSTWith all three on, the checkpoint's weights stay on the cards and the KV window goes from 512K (with a 1.4 GiB reserve,
1.5 GiB free) to 1M (with a 2.6 GiB reserve, 5.7 GiB free).
What this changes
GLM-5.3-Flash's KV pool costs every rank 13,056 bytes a token: for each of the 11 DSA layers and the MTP layer, a
1,024-byte latent row and 64 bytes of pooled index key. The sparse attention reads only the 2,048 tokens the indexer
picks per query row (512 pools of 4), and the indexer scans only the pooled keys. So the latent rows, 94% of the pool,
are read a few thousand at a time and can live off the card.
With
TF_GLM_KV_HOST=1(on every rank, with KV pages and BF16 KV):device pages of their own (
Caches.pks, their own page stride), so the indexer never leaves the card.tf_glm_kvh_mark,_copy) and rewritesits token lists to name the slots (
_remap, same order); the existing attention kernels then run on the slots withno page table. Dense calls (contexts under ~2K) gather their visible rows in order (
_rows).128 MiB in all) and copy only the rows they don't have. Rows the call itself writes (a rejected draft's) never hit,
and the cache is cleared whenever pages change hands.
window token, plus row marks) are made before the ranks read their room, so the budget counts them.
TF_GLM_KV_HOST_GIBsizes the host pages (one full window unasked).TF_GLM_KV_HOST_SHARED=1gives the ranks of onehost a single copy in
/dev/shm: their latent rows are bit-identical, both write them, and the first KV write of astep comes after the step's first three all-reduces, so no rank reads a page another has already reused.
--kv-cache fp8(which doesn't page), is refused at startup with the reason. Unset, nothing changes.Fixes #1794. The prompt caches work unchanged on top (#1736's host cache, and #1732's
TF_GLM_KV_SHARE, whose keptstates then sit in host memory as pool pages): the snapshot plan copies each page part with its own stride.
Overlap: #1779 moves
attend's cache writes intowriteKeys, where this PR gives the pooled-key write its own pagetable, so the two conflict in that one hunk. Happy to rebase whichever lands second. The others
atc overlapnames(#1411, #1423, #1545, #1576, #1577) merge cleanly.
Modules:
cuda_kv_host.zig(new): the setting, host page count, the gather and its row cache, the shared pages.glm_ops.cu: the gather's kernels (mark, copy, copy_cached, remap, rows, prev_mark, unmark, keep, reset).cuda_dsa_block.zig:attendgathers before the dense and sparse attention when the rows are on the host.cuda_pages.zig,cuda_state.zig,cuda_snapshot_layout.zig,cuda_native.zig: where the pages and the pooledkeys live, the plan's page cost, the refusal.
No attention kernel or arithmetic change.
Why this design
Measured first, on this checkpoint with a recording kernel captured in the CUDA graphs (not part of this PR):
44%). A recent-tokens window on the card alone would leave most reads on the host.
keeps those.
The gather copies each needed row across PCIe once a call (1 KiB rows, one warp each), rather than letting the
attention kernels read host memory directly; a first version that read the dense pass's rows directly was ~12 s slower
per prompt.
Benefits and trade-offs
Benefits:
with 5.7 GiB free on rank 0, where main fits 512K only with the reserve cut to 1.4 GiB (1.5 GiB free).
Trade-offs:
It grows with depth and with GPU clocks (the copy doesn't speed up with the GPU): at 400 W the same code measured
-3.7% at 205K before rebasing.
TF_GLM_KV_HOST_SHARED=1, else perrank), on top of the prompt cache's.
Receipt
Environment.
main1157ea5 with and without this commit, built with Zig 0.17.0 and nvcc 13.4.92 (-Dsm=120).The receipts' binary was built from this commit before its last amend, which changed two comments only.
cuBLASLt 13.4, driver 615.71.09, Linux 7.3. Host: Threadripper 9970X, 128 GB DDR5.
Mia-AiLab/GLM-5.3-Flash-EXL3-4bpw-TensorFold@ 76c0b517,--chat-template: the checkpoint's own withzai-org/GLM-5.3-Flash's media branch (as in glm cuda: TF_GLM_VISION_HOST keeps the vision tower's weights in pinned host memory #1760).
--vision --kv-cache bf16 --parallel 2,TF_GLM_HOST_CACHE=1 --prompt-cache-gib 24,TF_GLM_VISION_HOST=1, KV pages on (TP=2's default).--context 524288,TENSORFOLD_MEMORY_RESERVE_GIB=1.38(main's largest window with ≥1.5 GB free).TF_GLM_KV_HOST=1.--context 1048576, reserve 2.6,TF_GLM_KV_HOST=1 TF_GLM_KV_HOST_SHARED=1.Exactness (all three profiles):
tools/bench_concurrent.py --levels 1,2 --tokens 256 --temperatures 0 --alone --serial, code and chat: alone 18/18and serial 4/4 equal on each; 0 unequal, 0 failed.
three. The 61,558- and 204,206-token documents sent at once (two streams sharing the gather) reply as alone.
TF_GLM_KV_SHARE=8instead ofTF_GLM_HOST_CACHE(kept states as pool pages, so in host memory too): a fixedfour-turn conversation resumes 57,344 tokens a turn with replies identical to computing each turn fresh, a
100,063-token conversation resumes in 0.7 s, and the fixed documents reply as on
main.Memory (rank 0, idle after startup, driver
memory.free):A 1,007,082-token prompt with a 512-token reply served on candidate 1M: rank 0's free memory never fell below
5,785 MiB (sampled every second); 43 GB of host memory stayed available.
Speed (fresh server each run, main and candidates alternating, two rounds; medians):
bench_openai, 256 tokens)bench_concurrent2 streams, code / chatttft_s(bench_cache --require-cold)The same
token_shain everybench_*cell and the same reply SHA-256 for the 204,667-token document in all sixruns.
Tests run.
zig build test --test-timeout 10m: 5,941/5,968 passed, 27 skipped, 0 failed (includes the two new tests incuda_kv_host.zig: host page counts and the shared file's name).test-kernel-contract: 144/144.test-golden: pass.test-server-security test-pull-security: 28/28.fuzz-cortheon: 19/19. Plainzig build: pass.build_options_test.py: pass.git diff --check: clean.lean_new.pyagainstmain: 424 problems on the base, 424 on the head, 0 new.Not run, and why.
host memory already.
--kv-cache fp8(refused), sm_80-sm_89 cards: not available.Checklist
changes.
"draft": false, concurrent equals solo, and resumed prompts equal fresh (above).main(cold 32K/64K above); deep-context decode slower, disclosed above.