Repository navigation
Conversation
…e room holds come before the cache kv_pool.plan funded one window of pages, a page a stream, then the prompt cache, then the rest. Where the room holds more than one window, that gave the cache rows that a window a stream would have given the streams: at TP=2, a 128k window and four streams, 260 pages against four windows. Now, with engine_paged, the pages of as many windows as the room would hold as windows come before the cache, so the pool never holds fewer rows than windows in the same room. holds() counts the same way. Nemotron (engine_paged false) is unchanged. The engine's-own test keeps its checks at a room under two windows; a new test covers TP=2 at 128k and 1M and TP=3 at 1M.
With the plan funding the windows the room holds before the cache, pages at two ranks hold at least the rows a window a stream would, and serve --parallel 8 at a 1M window where windows are refused. One GPU and --kv-cache fp8 keep their windows.
Owner
|
Landed on main (3f1d669..0d24ecb), with both commits under your authorship. Thank you! On two GB10s at TP=2 with the NVFP4 checkpoint, against main in alternating runs (base, new, new, base):
The startup line confirms pages from the core pool at two ranks. This closes #1313. - Synthema, Ash's AI Assistant. |
This was referenced Oct 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What this changes
GLM-5.3-Flash on CUDA at TP=2 now serves several streams that share a long window. Before,
--parallel 8at a 1M,512k or 256k window was refused at start. Two commits:
75218c46corekv_pool: when the engine's own sequence is on pages (Budget.engine_paged, GLM's),plansetsaside, before the prompt cache, the pages of as many windows as the room would hold as a window a stream:
min(lanes, 1 + (room - window pages) / (window pages + fixed)), and at least the one window glm/cuda: the engine's own sequence on KV pages too, one window of them planned first (stacked on #1163) #1211 already setsaside. The cache gets what those leave, so the pool never holds fewer rows than windows would in the same room.
holdscounts the same way. Nemotron (engine_pagedfalse) is unchanged.dbd073dcglm/cuda:TF_KV_PAGES=autopages from two ranks (world >= 2), not three. One GPU and--kv-cache fp8keep their windows, as dooffand0.Refs #1313 (step 1 of its proposal; #1163's question 1, open since #1211).
What is wrong on main (67cc8fb)
autokeeps a window per stream at one and two ranks (zig/src/families/glm/cuda_start.zig:22). At TP=2 with anEXL3 checkpoint, a 1M window is 12.93 GiB a stream, so the startup refuses
--parallel 8(verbatim in GLM-5.3-Flash CUDA at TP=2:--parallel 8at a 1M window is refused (90.48 GiB of windows, 2.33 GiB left), though KV pages would serve it #1313:"needs 90.48 GiB for its streams …, and the CUDA memory budget leaves 2.33 GiB").
TF_KV_PAGES=onwould serve it, butautocould not simply page at two ranks.kv_pool.planfunds the promptcache before every page past one window and a page a stream (
kv_pool.zig:79), and the default cache is 16 GiB.At TP=2's usual
--context 131072 --parallel 4, with glm/cuda: the engine's own sequence on KV pages too, one window of them planned first (stacked on #1163) #1211's 9.5 GiB of room, that is 260 pages (133,120 tokens)for four streams, where windows hold four of 131,072. That is the reason glm/cuda: KV pages from the core pool, kept states sized by their position (#1142) #1163 gave for keeping windows there.
The change
held(l, b, fit): the pages the cache leaves the other streams, the larger of a page and a run for each stream (asbefore) and
(windows - 1)windows' pages.windows(l, b)is 1 unless the engine's own sequence pages. Thenplangives the cache
min(cache, left -| held);holdschecksleft -| (fit x fixed + held) >= cache. The page count andits bound (
most) are as before. Withautoat two ranks, by the plan's arithmetic (computed; the receipt has themeasured lines):
--parallelautoauto--parallel auto: 4 windows)The last row is the one default this moves at three ranks (question 1 on #1313). The engine's-own test in
kv_pool_test.zigkept its checks but at a room under two windows (24 GiB and an 8 GiB cache instead of 40 and 16).At 40 GiB the plan now sets two more windows aside before the cache, which the new test checks. The same goes for
cuda_kv_pool.zig'sfitsthreshold: it is now four windows' pages, which its 8,192-token room holds.Tests
kv_pool_test"the engine's own sequence on pages: the windows the room would hold come before the cache" coversthree cases. TP=2 at 128k with four streams in a 9.5 GiB room gives 4 x 257 pages, and the cache gets the rest,
whole for
holdsand not one byte more. TP=2 at 1M with eight streams in a room of one window is unchanged: thewindow, a page each, the cache. TP=3 at 1M with eight streams in 38 GiB gives 2 x 2,049 pages before the cache.
cuda_native"TF_KV_PAGES: auto pages from two ranks, …":autoand unset page at 2, 3 and 16 ranks and keepwindows at one. The FP8 test is unchanged (windows at every rank count,
onandallrefused).On main (the tests alone,
zig build test-kv-pool/test-cuda-host; trimmed, paths relative):Both pass on this branch.
Receipt
Host, base
67cc8fbceand this branch (dbd073dc), fresh caches each, Zig 0.17.0, Apple M4 Mac, macOS 27:test-kv-pool21/21 on base and 22/22 on the branch (the new test),test-glm-startup-host5/5 on both,test-kv-safety-host21/21 on both,test-kv-merge-host19/19 on both,test-cuda-host3,246/3,262 on base (14 skipped, 2 failed) and 3,249/3,263 on the branch (14 skipped, 0 failed). The base's two
failures are timing tests outside this change (
cortheonfuzz and html "linear time"); they passed in thebranch's run.
zig fmt --checkis clean on the changed files.lean_check.py: 434problems on base and on the branch, none new.
GPU: base
67cc8fbceand this branch (dbd073dc2), bothtensorfold-nativebuilt alike (Zig 0.17.0,aarch64-linux-gnu,-Doptimize=fast) with the same 97 embedded CUDA kernel images (one fatbin set, nvcc 13.0V13.0.88, sm_121). Two NVIDIA DGX Spark (GB10, 121 GiB unified memory) at TP=2 over a direct RoCE link, driver
580.178.04 (CUDA 13.0), cuBLASLt 13.1.1.3, NCCL 2.30.7, glibc 2.39, Ubuntu 24.04.5 LTS, kernel 6.17.0-1032-nvidia,
aarch64; one model process on each. Checkpoint
Mia-AiLab/GLM-5.3-Flash-EXL3-4bpw-TensorFold@078455ff, MTP drafts.Every serve:
serve <checkpoint> --backend cuda --parallel P --context C --no-update-checkwithTENSORFOLD_CUDA_KERNELS=native TF_HEAT_HIGH=92 TF_HEAT_LOW=88, and the env named per row.Starts. Each line is rank 0's start-up line, cut to the part that differs (all print
of X GiB left after a 12.2 GiB reserveandprompts in passes of up to 2048 rows):TF_KV_PAGES--context,--parallel--parallel 8 needs 90.48 GiB for its streams at a 1048576-token window, and the CUDA memory budget leaves 3.89 GiBonthe engine's own and 7 more taking 2056 pages of 512 rows (12.80 GiB) as they need them (enough for one at a 1048576-token window), 180 MiB fixed eachontaking 520 pages of 512 rows (3.24 GiB) ... (enough for one at a 262144-token window)taking 2052 pages of 512 rows (12.77 GiB): 1,050,624 rows, four windows'4 streams at once, 1.77 GiB each at a 131072-token window: 524,288 rowsTF_KV_POISON=1the engine's own and 3 more taking 1028 pages of 512 rows (6.40 GiB): 526,336 rowsoff4 streams at once, 1.77 GiB each at a 131072-token window1c equals 1b and 1h equals 1f apart from 0.2 GiB of
leftbetween starts. The room was 14.5 to 16.2 GiB, more thanthe 9.5 GiB the table in "The change" assumes, so the measured caches are larger than that table's. Main's refusal
here leaves 3.89 GiB, where GLM-5.3-Flash CUDA at TP=2:
--parallel 8at a 1M window is refused (90.48 GiB of windows, 2.33 GiB left), though KV pages would serve it #1313 quotes 2.33 GiB from an earlier start. After every branch serve with traffic theidle census is even (
kv pool idle: promised 0, free N + store 0 == total N, N = 2056 on 1c, 1028 on 1g and thespeed serves; no
UNEVEN).Exactness at 131072,
--parallel 4, default prompt cache: pages = 1g (TF_KV_POISON=1), windows = 1h, main = 1f.tools/exact.py, recorded on pages, then on windows and on main:comparepages against windows andpages against main,
IDENTICAL (0 problem(s), 31 cases)both.bench_concurrent.py --levels 1,2,4,8 --alone --serialon pages: alone 60/60 equal and serial 72/72 equal(0 unequal, 0 failed).
reuse_served.py record --planned --min-prompt 2048on pages: 44 requests, 0 not cached as planned; its 20drafted /
"draft": falsepairs give equal tokens.replayon a main serve (--prompt-cache-gib 0) andcompare:43/43 replies equal and as cached as planned.Speed at 131072,
--parallel 4,--prompt-cache-gib 0, no poison, serves alternated main, branch, main, branch.Decode:
bench_openai.py --tokens 256 --reps 3 --temperatures 0, median. Prefill:reuse_served.py coldandbench_cache.py --require-cold(the 32k chat), seconds. The last column is the branch's better serve over main'sworse one (time, so under 1 is faster):
fibonacci-raw, tok/sgpu-chat-no-think, tok/sEvery serve gave the same tokens for each decode prompt and for the 32k chat. No measure is over 1.002. The second
branch serve is the slowest on 2k-64k by up to 0.7% against main's best; main's own two serves differ by up to 2%
(1k).
A ~1M prompt with seven short ones beside it, on the branch at 1048576 x 8, unset (1c). The prompt:
tf-receipt-long-v1/chat/seed=1/words=854334/turns=8(long_prompt.pyin the recipe repo; 18 messages, bodysha256
b849e8e45a16ddb1aeb6a91525b5e100239cd870e5cbabb58fe9dc6edf445c2a), 1,020,560 prompt tokens, 16 outputtokens: cached 0, prefill 1,276.2 s, time to first token 1,276.5 s, wall 1,277.4 s,
token_sha9e0c38a5d727.Seven short prompts (1,177 to 1,406 tokens, 64 output tokens each) sent 30 s into its fill were all admitted beside
it: time to first token 2.45 to 11.47 s (alone afterwards on the same serve: 1.27 to 1.42 s), none waited for the
long one to end, and each equals its alone run. Rank 0's
device_memory_peak_bytes101.1 GiB;nvidia-smi103,898 MiB on each rank at ready; the lowest
MemAvailablesampled during the serve 11.7 GiB (rank 0) and 12.3 GiB(rank 1). The idle census after it:
free 2056 + store 0 == total 2056.Not run: TP=3 or more (no ring of three here; the table's TP=3 row is the plan's arithmetic, checked by the host
test).
--kv-cache fp8on a device (its windows are checked by the host test). Exactness at--parallel 8and awindows serve in the speed alternation. Also not run: one GPU, Metal and MLX (unchanged paths).
Checklist
"draft": false, a resumed prompt equals a fresh one, and concurrent equals solo.(On pages: 20/20 drafted /
"draft": falsepairs, 43/43 resumed replies equal on main,bench_concurrentalone60/60 and serial 72/72.)
default that picks pages.
cold 1k-64k and the 32k chat within 0.2%, best against worst.)
Apple M4 without the Metal toolchain; served CUDA at TP=2 on two DGX Spark (GB10). No Metal device, no TP=3.)
main and pass here; host tests with nothing to skip on.)
By Robert Wojciechowski (wojo on GitHub, @rwojo on X)