Repository navigation
Conversation
LAST_EXITED keeps one KV plane per recurrence depth and reserves every layer's slot in every plane. For a model whose prelude and coda layers run once per token (Huginn), only plane 0 ever holds those layers: of Huginn-0125's 8 layers x 32 planes = 256 layer slots per token, 124 are never written. The pool fills with dead slots, so few requests fit and throughput stops growing with concurrency. With CacheConfig.pack_recurrent_planes (default on) each plane maps only the layer slots it can hold: plane 0 all layers, deeper planes the recurrent ones. A page is a run of blocks of num_layers slots, entry = block * num_layers + slot in a flattened view of the pool, so a Huginn page needs 17 blocks instead of 32. Writes, attention, early-exit plane copies and the async finalize kernel take an entry stride and first-layer offset; PD sends each plane's live slot range (plane_slots, checked on connect) and the coordinator prices requests in blocks per page. Values never change, only their addresses. Fully recurrent models (Ouro) keep the old layout. --no-pack-recurrent-planes restores the unpacked layout. GH200, Huginn-0125 (32 loops, CUDA graphs, sync), same KV pool of 24,165 blocks, 512-token prompts, 64 output tokens, one run per level: tokens in the pool: 12,080 unpacked -> 22,736 packed (1.88x) throughput: 0.99x / 0.99x / 1.14x / 1.16x / 1.06x at 1 / 8 / 32 / 64 / 128 median TTFT at 64 requests: 15.7 -> 10.9 s 2048-token prompts: 1.13x / 1.11x at 8 / 32 token identity: 233/233 requests PD: 2.79 MB per prompt token sent instead of 5.41 MB (only live slots). Tests: CPU unit tests for the packed layout, routing, PD segments and slot checks; GPU test comparing packed and unpacked Huginn (tiny model, CUDA graphs, early-exit copies) token for token. Signed-off-by: 0z5a <dezhen.lu@student.uni-tuebingen.de> Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This was referenced Oct 25, 2026
Closed
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.
Summary
LAST_EXITED keeps one KV plane per recurrence depth and reserves every layer's slot in every plane. For a model whose prelude and coda layers run once per token (Huginn), only plane 0 ever holds those layers: of Huginn-0125's 8 layers × 32 planes = 256 layer slots per token, 124 are never written. The pool fills with dead slots, so few requests fit and throughput stops growing with concurrency (on RTX 5090 the pool held 1,328 tokens, two of our requests; on GH200
mainplateaus near 65 tok/s from 32 requests on).With
CacheConfig.pack_recurrent_planes(default on) each plane maps only the layer slots it can hold: plane 0 all layers, deeper planes the recurrent ones. A page becomes a run of blocks ofnum_layersslots (entry = block × num_layers + slot in a flattened view of the pool), so a Huginn page needs 17 blocks instead of 32. Writes, attention, early-exit plane copies and the async finalize kernel take an entry stride and first-layer offset; PD sends each plane's live slot range (plane_slots, checked on connect) and the coordinator prices requests in blocks per page. Values never change, only their addresses. Fully recurrent models (Ouro) keep the old layout.--no-pack-recurrent-planesrestores it.Measurements
GH200, Huginn-0125 (32 loops, sync, CUDA graphs), same KV pool of 24,165 blocks, 512-token prompts, 64 output tokens, one run per level:
mainacross 1–128 requests, 231/233 identical.Platforms. GH200: torch 2.14.0+cu130, Triton 3.8.0, NIXL 1.4.1. RTX 5090: torch 2.13.0+cu130, Triton 3.7.1. Models: Ouro-1.4B (24 layers, 4 loops, fixed depth) and Huginn-0125 (2 prelude + 4 recurrent + 2 coda layers, 32 loops). Burst workloads issue C requests at once with random-token prompts; session workloads replay multi-turn agent traces with the prefix cache on.
Tests
pytest --run-gpu tests/test_huginn.py tests/test_kv_cache.py tests/test_async_state.py tests/test_prefix_growth.py tests/test_cdb_runtime.py): 143 passed, 1 failed:test_cuda_memory_plan_reclaims_allocator_residue_on_recreation, whose KV budget moved by 50 MiB against a 16 MiB tolerance. It fails the same way onmain(66920da) on this host (46 MiB), so this branch adds no failure. Without FlashAttention installed, the FlashAttention tests failed at import on this branch and onmain(66920da) alike.Notes
The GPU numbers come from a development branch that contains all changes of this series; most cells are single runs, and on the shared GH200 host two runs of
mainat 32 requests differed by up to 16%, so smaller differences are ties. This branch alone was checked with the CPU suite, ruff and the GPU tests above.Series. Draft PRs from one study of serving looped models. They are independent except #152, which is stacked on #147; several touch the same files, so later ones may need a rebase after earlier ones merge.