Repository navigation
GLM paged attention and multi-sequence serving with prefix-aware scheduling - #642
ClintMoody wants to merge 6 commits into
Conversation
35edd9d to
6806e0f
Compare
|
Thanks for this substantial contribution, and especially for the honest reporting. What's goodThe overall design is sound: a refcounted page pool with LRU eviction, a content-hashed Blockers1. Stale baseThe branch is based on v1.1.33 while 2. Correctness bugs found in review
3. The core claim lacks test coverage
One more thing on this front: all four new tests are GPU-gated while CI is GPU-less, 4. CONTRIBUTING.md compliance
5. Process noise in the codeThe diff carries 136 internal "Task N.M" plan references, dated run transcripts used as Suggested path forward
None of this diminishes the amount of real work you put in. It's just that the last |
6806e0f to
ff7fc0d
Compare
|
Rebased, reworked, and pushed. The branch is rewritten onto Blockers
Evidence on this tree (engine stopped for the device window, then restarted):
Stated plainly, as you asked: CI does not build the engine and does not run Still open: the |
|
One addition to the evidence, since you asked for the CI claim stated exactly: I reproduced CI's Python job on this tree in a venv with CI's dependency list.
The workflow still does not build the engine and does not run |
|
Two-GPU (2x RTX 3090 Ti) run of this head, with the shared prompt sent as a real
The same head and the same settings, with the shared text in the user message instead of a system message: 8 streams at 102.6 s wall, 93.9 s worst first token, 21.3 tok/s. Why the second row is faster: the slot cut saved the 5,587-token system prompt ( On the 256-row clamp you noted: on this head, a read from token 0 goes in one 6,144-token chunk at 798 to 827 tok/s, against 286 tok/s on the v1.1.48 port. Still open: first token grows with stream count (2.5 s at 1, 4.8 s at 2, 17.9 s at 4, 39.2 s at 8). One serve thread reads prompts one at a time, so eight reads of 2.15 s are already 17 s of queue, and a 546-token read runs at 253 tok/s because of the stateful tile at the chunk end. Cutting that per-chunk overhead, and reading while other streams decode, would be the next step. |
ff7fc0d to
dcaa5df
Compare
|
Rebased onto |
|
Thanks, parity tests and a rebase on v1.1.56 are appreciated. Missing: (1) the GPUs, CPU, RAM, model+quant you tested on; (2) single-request prefill and decode before/after (must be unchanged for normal use), and multi-request throughput with its numbers; (3) a greedy-output match between this and main for the same prompts; (4) which behaviour is on by default - anything that changes answers should be opt-in. |
dcaa5df to
f397014
Compare
|
Rebased onto v1.1.57 (0065548). New head f397014 = e697ec2 (paged latent cache) + e67fd33-equivalent (dynamic multi-seq scheduler + MTP speculation + paged wiring). Content diff vs the old head dcaa5df is empty: the rebase changes parents only, the PR's 2 commits apply onto v1.1.57 with the same resolved content. |
|
Correction to my note above: the rebase is not content-identical this time. v1.1.57 already merged much of the scheduler work (glm-dsa B-series: GlmStateLayout, state_carve, mtp_carry_forget), so the rebased second commit (f397014) carries fewer own lines than dcaa5df did (3707 vs 3762 patch lines): HEAD's layout rewrite wins where the PR overlapped it, the PR keeps its includes/helpers and its own scheduler/speculation/paged wiring, and the overlap is resolved in upstream's favor. Verified by patch-text diff of dcaa5df vs f397014: 171 lines vanish, 116 appear, all accounted for by upstream's absorbed features plus context shifts. |
|
One more commit landed on the PR head during the upgrade window's test runs: a669456. The PR's glm_seq_dynamic_test exposed a real divergence in the rebased stack: upstream's B4.1 rewrite of Glm5Model::seq_layout recomputes the sequence layout with Fp16 + the fast ik ring + kv-resident latents unconditionally, while the load path (glm_model.cu) carves SlowF32 with the plain ring and full-context latents when fast_mode_ is off (the STRATA_GLM_SLOW legs), so the guard in seq_alloc_arenas rejected every pack it did not carve itself. The fix mirrors the load's mode pick in seq_layout; fast mode is byte-identical before and after, so production behavior does not change. Verified: 1116 checks, 0 failures after the fix (the port tree's pre-fix binary had passed window 2 only because it was still the Nov 1 build). |
…ore and the gather kernel The latent rows of the GLM fast path move into a paged pool. A logical row reaches its slab row through a page table, so a page can be freed, reused and reordered without moving the tokens that point at it. - glm_pages: the pool (bump-allocated pages, refcounts, LRU eviction, logical->physical table and the physical->logical mirror) - glm_prefix: the canonical prefix store on top of the pool - glm_batch: dsa_select reads the page table (paged_lat_table) - parity: pages (35 checks), prefix (13 boundaries), lat mapping (10), and a device-side gather over an identity, a shuffled and a corrupted table (8 checks); the gather test's host layer runs without a GPU The pool sizes from STRATA_GLM_PAGE_SIZE / STRATA_GLM_PAGE_POOL_GB and shrinks by halves when the device cannot hold the request.
…d path's wiring STRATA_GLM_SEQS lets several requests share the engine: a request is admitted only when its sequence state binds, a parked sequence is paged in or out, and the oldest idle page is evicted under pressure. - generate: admission, eviction and a terminal path for a request that waits too long (STRATA_GLM_MULTI_WAIT=0 means wait forever, as the docs say); a failed bind rejects the request instead of marking the slot busy while the model points at another sequence - glm_multi: sequence bind/release, the pause snapshot capture/restore (the table mode is written and read as a 4-byte host value, the width part_bytes counts) and the split-rows gate for n_parts > 1 - glm_prefill: the chunk/page clamp is opt-in (STRATA_GLM_PREFIX_PAGE_ALIGN) - glm_rows, glm_fast, glm_window, glm_model: the paged table reaches dsa_select and the rows arena - MTP: speculative decoding on the multi-sequence path - tests: glm_seq_dynamic_test (1116 checks on a real pack fixture) and tools/glm_synth_pack.py, the fixture generator the test uses
…recompute diverges in the slow legs (STRATA_GLM_SLOW=1: the load carves SlowF32 with the plain ring and full-ctx latents, the B4.1 recompute assumed Fp16 + the fast ring + kv-resident rows)
a669456 to
8abc270
Compare
v1.1.58 renamed the engine's env family STRATA_ -> MAYA_ and added a one-way alias (STRATA_X promotes to MAYA_X once at program start), so the paged knobs must be read as MAYA_ names: MAYA_GLM_PAGED=1 otherwise fell back to OFF and MAYA_GLM_SEQ_FLOOR to the full cap. Renames MAYA_GLM_PAGED, MAYA_GLM_PAGE_SIZE, MAYA_GLM_PAGE_POOL_GB, MAYA_GLM_SEQ_FLOOR, MAYA_GLM_SEQ_VRAM_FIT and the seq test's env names. The lifecycle test now passes (1116 checks, 0 failures).
The runtime setenv sites do not get the one-way STRATA_ -> MAYA_ alias (it runs once at program start).
v1.1.58 renamed the engine env family STRATA_ -> MAYA_ and made STRATA_X a one-way alias for MAYA_X. v1.1.61 code reads MAYA_X, so the branch's remaining STRATA_GLM_* reads ignored the config's MAYA_ names: paged attention, the page pool, the state floor and the pause knobs all silently stayed at their defaults. The reads now match the alias direction.
Summary
This contribution adapts GLM-5.3-Flash's latent cache to a page-pool and page-table layout, adds per-sequence state and prefix sharing, and schedules multiple requests with admission and pressure management. It includes CUDA gather routing, parity tests for pages, prefix and latent mapping, and a synthetic dynamic-sequence test.
The branch is rebased onto current
main(v1.1.56,26d53d8) and carries two commits: the paged latent cache (page table, page pool, prefix store, gather kernel) and the dynamic multi-sequence scheduler with MTP speculation. The abliteration harness (glm_ablate.cuh) is deliberately not in this PR; it belongs on its own branch.This PR does not port GLM paged attention into DeepSeek's distinct
src/ds4/engine.Author credit: Clint Moody contributed the paged-attention, multi-sequence serving, scheduler, and related integration work. Project Maya and Strata retain their existing author credits and copyright notices. The repository's MIT license and all upstream/vendor notices are unchanged. DeepSeek model weights and optional differently licensed drafter weights do not belong in the code PR.
What the branch contains
glm_pages; the canonical prefix store sits on top inglm_prefix.dsa_selecttakes an optional paged table.glm_paged_gather_testdrives the real kernel with an identity, a shuffled and a corrupted table and compares the device result to the host mapping. Its host mapping layer runs without a GPU.STRATA_GLM_SEQSshares the engine between requests. A request is admitted only when its sequence state binds; a parked sequence is paged in or out; the oldest idle page is evicted under pressure.STRATA_GLM_PAGE_SIZE/STRATA_GLM_PAGE_POOL_GBand shrinks by halves when the device cannot hold the request.STRATA_GLM_MULTI_WAITbounds a request's wait for a context;0means wait forever.Review fixes applied
SnapPacker::put_host,snap_unpack_host). Before, the pack wrote 4 bytes, the unpack read 8, andpart_bytescounted 4, so the bounds check failed at the last field and every resume silently fell back. Astatic_assertnow locks the width thatpart_bytescounts.seq_bindfailure propagation:seq_bindandseq_bind_allreturnbool. A failed bind rejects the request instead of marking the slot busy while the model still points at another sequence.STRATA_GLM_MULTI_WAIT=0: now unbounded, as the docs say. Before, it rejected the request immediately.evict_lru: one pass pluspartial_sort, same order (last use, then page index),O(N log N)instead ofO(N x n).--require-gpuso a busy GPU cannot look like a pass, and zeroes device memory withcudaMemset(a hostmemseton device memory faulted).glm_pages_paritySKIPs cleanly without a usable CUDA device.docs/SETTINGS.mdusesMAYA_*names, whichenv_alias.hppmaps toSTRATA_*at startup, so the rows work under either spelling.rows_allocgate:(n_parts_ == 1 || split_rows_setting() > 0) && fast_mode_, so the multi-GPU split-rows leg is no longer dropped whenSTRATA_GLM_SEQSis on.STRATA_GLM_PREFIX_PAGE_ALIGNis opt-in and off by default. When unset, a chunk keeps its full size and the stateful tile is captured at the natural chunk end, so the page clamp is not a tax on every prompt.GlmPagesstill relies on the caller's mutex; it holds no lock of its own.docs/SETTINGS.md.docs/SETTINGS.mdusesMAYA_*names, whichenv_alias.hppmaps toSTRATA_*at startup. No plan or task references, no run transcript, no ablation harness, noCUDA_LIBRARIESlink.bench/README.mdcarries nogenerate.cppline numbers.glm_multi.cubut is called only byglm_seq_dynamic_test.cpp; it is the only coverage of pause and restore.Test plan (performed evidence)
Build (this tree, upstream
main@26d53d8plus the 26 files):Full build, every target: EXIT 0, no errors (2m42s wall on 2x RTX 3090 Ti).
Host-runnable checks (no GPU needed):
glm_paged_gather_test --selftest(host layer)glm_prefix_parity --selftestglm_lat_paged_parity --selftestglm_pages_parity --selftestcudaMalloc)tests/glm_memory_test.cpptools/check_hip_compat.pyDevice checks (free-GPU window, engine stopped):
glm_pages_parity --selftestglm_prefix_parity --selftestglm_lat_paged_parity --selftestglm_paged_gather_test --selftest --require-gpuglm_seq_dynamic_test --selftest <pack>The fixture comes from
tools/glm_synth_pack.py, which packsdata/glm5-synth.ggufthroughtools/iq_pack.py.CI scope, stated exactly:
.github/workflows/tests.ymlruns the Python suites,check_hip_compat.py, and the CPU-only C++ tests. It does not build the engine and does not runctest, so the device tests above run on a machine with CUDA. The host layers of the gather test and the lat mapping test are the subset that can run without one.CI's job reproduced on this exact tree, in a venv with CI's dependency list (
numpy jinja2 regex pyyaml tqdm psutil jsonschema>=4.18,<5):tools/check_hip_compat.pyglm_memory_test(-Wall -Wextra -Werror)ds4_cpu_prepare_rows_testnative and stubunittest discover -s serveunittest discover -s toolsNot yet run: the
CONTRIBUTING.mdbefore/after benchmark pair (maya.py --benchon this build and onmain), which needs a GLM pack and two model loads.Earlier probe window (v1.1.33 base, not re-measured after this rebase): 1,536 of 1,775 prompt tokens were reused on the repeated-prefix probe, and the speculative path under concurrency was byte-identical to the serial MTP path.
Disclosed limitation: with paging enabled, one of three temperature-zero canaries diverged in wording from the stock answer. Sustained-load, long-context and error-path gates remain open; please do not label the paged path production-ready until they run.
Notes for maintainers
main@26d53d8.src/ds4/engine is deliberately untouched. No DeepSeek performance claim is made from the GLM tests.