Skip to content

[Bugfix][KV Offload] Skip non-prefix-cacheable groups in SimpleCPUOffload (GLM-5.3-Flash kpool tail) - #56810

Open
JaredforReal wants to merge 3 commits into
vllm-project:mainfrom
JaredforReal:fix/glm53-flash-simple-cpu-offload
Open

JaredforReal wants to merge 3 commits into
vllm-project:mainfrom
JaredforReal:fix/glm53-flash-simple-cpu-offload

Conversation

@JaredforReal

@JaredforReal JaredforReal commented Sep 14, 2026

Copy link
Copy Markdown
Contributor

Purpose

GLM-5.3-Flash carries a kpool-tail scratch group (KpoolTailSpec, prefix_cacheable=False, block_size=index_kpool=4) next to prefix-cacheable groups that hash at 1152 tokens on this box (MLA 1152, 4× Mamba 1152 in align mode). Core (resolve_kv_cache_block_sizes, HybridKVCacheCoordinator, KVCacheManager) and the MooncakeStore connector already skip such groups. SimpleCPUOffloadScheduler did not, and treated every KV cache group as offloadable:

  • _select_eager_blocks_to_store views the request hashes at each group's block size to build BlockStored metadata. For the tail group that is resolve_block_hashes(hashes, hash_block_size=1152, block_size=4), which trips the divisibility assert as soon as --kv-events-config enables KV cache events. The engine dies on the first prefill store.
  • update_state_after_alloc maps external (CPU-hit) tokens onto the scratch group and asserts alignment against its block size, although the coordinator never returns hit blocks for it.

This PR records which groups take part in offloading (prefix_cacheable) and skips the rest in eager store selection and in load pairing. Scratch blocks never carry a hash, so nothing is stored or loaded for them either way; the scratch slot in the per-group hit tuple stays empty, exactly as the coordinator leaves it.

Duplication check

Two open PRs touch the same code path: #55033 (draft since 2026-09-02, Qwen3.8-Flash-Next QSA ring, also adjusts _estimate_lazy_target_blocks) and #56404 (DeepSeek-V4.1-Flash, gates resolve_block_hashes in the store path only, currently conflicting). This PR is scoped to the GLM-5.3-Flash kpool-tail layout and adds what neither of them covers: a regression test for the actual crash trigger (enable_kv_cache_events=true, BlockStored emitted for cacheable groups only) and an end-to-end store/load + accuracy check on GLM-5.3-Flash. Happy to fold the test into #55033 instead if the maintainers prefer to land that one. #54743 applies the same scoping to the OffloadingConnector (build_offloading_config) and does not touch vllm/v1/simple_kv_offload; the two are complementary.

Test Plan

Unit:

PYTHONPATH=. .venv/bin/python -m pytest tests/v1/simple_kv_offload/test_scheduler.py \
    tests/v1/simple_kv_offload/test_kv_events.py \
    tests/v1/kv_connector/unit/test_simple_cpu_offload_connector.py -q

E2E: GLM-5.3-Flash, 4× GB300, TP4 + EP, --enforce-eager, --max-model-len 32768, SimpleCPUOffloadConnector with 16 GB pinned host memory per rank, --kv-events-config '{"enable_kv_cache_events":true,"publisher":"zmq"}', dev endpoint /reset_prefix_cache used to evict the GPU prefix cache between runs (?reset_external=true also clears the CPU pool).

Test Result

E2E store/load (10,277-token prompt, greedy, 32 new tokens):

step GPU prefix hit connector hit
fresh 0 / 10277 0 / 10277
same prompt again 9216 / 10277 0 / 1061
after /reset_prefix_cache 0 / 10277 9216 / 10277
after /reset_prefix_cache again 0 / 10277 9216 / 10277

Hits are 1152-aligned (the Mamba/MLA block); the kpool tail block is a fresh per-request scratch block at every resume, as with a GPU hit.

Accuracy (4 natural-text prompts built from docs/, 3.4k–24k tokens, max_tokens=16, greedy, logprobs=20 at the first generated position; every case compared against the first fresh run of the same prompt):

case top-1 |Δlogprob| mean / max mean |Δlogprob| over shared top-20 max |Δlogprob| identical 16-token greedy output
fresh again (no cache at all) 0.10 / 0.25 0.42 2.06 3 / 4
GPU prefix hit 0.13 / 0.31 0.27 1.03 2 / 4
CPU (connector) hit 0.11 / 0.16 0.28 0.91 3 / 4
CPU hit, second time 0.11 / 0.12 0.32 1.88 3 / 4

The CPU-hit deltas sit inside the band that two fresh recomputations of the same prompt already show on this model/backend (the fresh-vs-fresh spread is not caused by this PR; it is present with the connector disabled as well, see the fresh again row), so the offload round trip adds no measurable error over a plain GPU prefix hit.

AI assistance: this change was developed with Claude Code (analysis, patch, tests and the reproduction scripts); the human submitter reviewed every changed line and ran the tests and evaluations above.

…load

GLM-5.3-Flash carries a kpool-tail scratch group (KpoolTailSpec,
prefix_cacheable=False) whose block_size is index_kpool=4 while the
prefix-cacheable groups hash at 1152 tokens. Core, HybridKVCacheCoordinator
and the MooncakeStore connector already skip such groups, but
SimpleCPUOffloadScheduler treated every KV cache group as offloadable:

- _select_eager_blocks_to_store viewed the request hashes at the scratch
  group's block size for BlockStored metadata, which trips
  resolve_block_hashes' divisibility assert as soon as kv cache events are
  enabled (engine dies on the first prefill store).
- update_state_after_alloc mapped external tokens onto the scratch group
  and asserted alignment against its block size.

Record which groups take part in offloading and skip the rest in store
selection and load pairing. Their blocks never carry a hash, so nothing is
stored or loaded for them either way; the scratch slot in the per-group
hit tuple stays empty, as the coordinator already leaves it.

Signed-off-by: Jared Wen <jaredwen@inferact.ai>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@mergify mergify Bot added glm bug Something isn't working labels Sep 14, 2026
@JaredforReal
JaredforReal marked this pull request as ready for review September 14, 2026 10:28
Copilot AI lite review requested due to automatic review settings September 14, 2026 10:28

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Claude Code Review

This pull request is from a fork — automated review is disabled. A repository maintainer can comment @claude review to run a one-time review.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Lazy target sizing still counts non-prefix-cacheable scratch groups.

Get a fresh assessment by requesting another Copilot review.

Pull request overview

Updates SimpleCPUOffloadScheduler to exclude non-prefix-cacheable scratch KV groups from CPU offload handling.

Changes:

  • Filters scratch groups from eager store/load operations and KV events.
  • Adds scheduler and KV-event regression tests.
  • Lazy target sizing still requires adjustment to exclude scratch groups.
File summaries
File Description
vllm/v1/simple_kv_offload/manager.py Filters non-cacheable groups from offload paths.
tests/v1/simple_kv_offload/test_scheduler.py Tests scratch-group store/load exclusion.
tests/v1/simple_kv_offload/test_kv_events.py Verifies events contain cacheable groups only.
Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 1
  • Review effort level: Lite

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread vllm/v1/simple_kv_offload/manager.py Outdated
Comment on lines +172 to +175
self._group_offloadable = [
g.kv_cache_spec.prefix_cacheable
for g in self.cpu_kv_cache_config.kv_cache_groups
]
JaredforReal and others added 2 commits September 14, 2026 16:40
…llm-project#55033

Fold the SimpleCPUOffloadScheduler changes of vllm-project#55033 into this branch so
one PR covers both non-prefix-cacheable scratch layouts (GLM-5.3-Flash
kpool tail and Qwen3.8-Flash-Next QSA ring):

- Use the core projection KVCacheConfig.prefix_cacheable_group_ids
  instead of a private per-group list.
- Skip non-prefix-cacheable groups in _estimate_lazy_target_blocks, so
  the ring's small block size no longer inflates the lazy-mode free-block
  target.
- Add the QSA-shaped regression tests (store/load exclusion, lazy target
  sizing) alongside the kpool-tail ones.

Co-authored-by: Zhewen Li <zhewenli@inferact.ai>
Signed-off-by: Jared Wen <jaredwen@inferact.ai>
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working glm

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants