Repository navigation
fix(vllm): bind cross-layer KV sharing layers to their target's cache - #472
Conversation
vLLM registers KV-sharing layers (e.g. gemma E2B) by appending their names to kv_cache_groups[*].layer_names and runner_only_attn_layers without adding them to any kv_cache_tensors[*].shared_by. The kvcached allocation path direct-indexed every group layer name against the layer-to-tensor map and raised KeyError at startup. Mirror vanilla vLLM's initialize_kv_cache_tensors: skip runner-only layers wherever layers are resolved against kv_cache_tensors or mapped to pool indices (allocation validation, representative layer selection, pool count, per-group views, reshape binding), then alias each sharing layer to its target layer's cache. The pool count fix also keeps the worker-side pool count equal to the scheduler-side num_layers, whose config never contains the appended names. Fixes ovg-project#417 Signed-off-by: Rishabh Sinha <rsinha17@terpmail.umd.edu>
|
@shipiyouniao can you help me to review this PR? |
shipiyouniao
left a comment
There was a problem hiding this comment.
@RixinLiu, I went through f3b8a00. I haven't found a new functional bug in this change. Filtering the runner-only layers before counting/binding pools, then aliasing them to their targets, matches vLLM's handling of cross-layer KV sharing.
The 14 new tests pass. I also exercised the actual patched allocation/reshape entry points with a small review harness: 12 cases passed on CPU and the same 12 passed with T4 CUDA tensors. These cover homogeneous/heterogeneous groups, both layouts, exact target aliasing and readback, a missing target followed by recovery, and delegation when kvcached is disabled. The harness substitutes the allocator/backend; it is not a native VMM or Gemma serving test.
Two follow-ups would make this much easier to sign off:
- Could you include tests that call the patched allocation/reshape methods? The current tests rebuild the binding loop around the helpers, so they would still pass if a helper stopped being used at a call site. In particular, assert that the pool count matches the scheduler's pre-append group size and that a sharing layer gets its target's exact tensor/view in the heterogeneous path.
- Could you add the Gemma output comparison you offered? The reference can be vanilla vLLM with kvcached disabled; main's startup failure doesn't prevent that comparison. A small fixed greedy prompt set, including repeated-prefix requests and more than one decode step, would check that fixing the startup exception hasn't left incorrect KV bindings. Please record the versions/settings and token outputs, and investigate any mismatch rather than requiring only successful generation.
These are validation gaps, not a reproduced correctness defect. I'm leaving this as a comment pending that final model-level check; no code redesign requested.
|
LGTM. I run the two layouts on my A100, the output matches exactly with vanilla vLLM. |
Summary
Fixes #417. Models with cross-layer KV sharing (gemma E2B) crashed at startup with
KeyError: '...self_attn.attn'in kvcached's layer-to-tensor binding. Skip the sharing layers wherever a layer is resolved againstkv_cache_tensors, then alias each one to its target layer's cache, mirroring vanilla vLLM'sinitialize_kv_cache_tensors.Root cause
vLLM's
maybe_add_kv_sharing_layers_to_kv_cache_groupsappends sharing-layer names tokv_cache_groups[*].layer_namesand records them in the runner'srunner_only_attn_layersWITHOUT adding them to anykv_cache_tensors[*].shared_by(verified identical on 0.22.1/0.23.0/0.24.0,v1/worker/utils.py). Vanilla vLLM copes by skippingrunner_only_attn_layersin_allocate_kv_cache_tensors/_reshape_kv_cache_tensorsand aliasingkv_caches[layer] = kv_caches[target]after allocation (gpu_model_runner.py:7064/:7115/:7266at v0.24.0). kvcached's_allocate_kv_cache_from_kvcacheddid neither and direct-indexed everygrp.layer_namesentry, which is the KeyError site.The appended names also inflated
_get_group_size, so the worker would have allocated more pools than the scheduler-sidenum_layers(the scheduler's config never contains the appended names, the append is runner-side only).Changes
kvcached/integration/vllm/patches.py:_get_runner_only_attn_layers,_tensor_backed_layer_names,_alias_shared_kv_layers;_get_group_sizetakes an optional runner-only set (default keeps the old behavior; the scheduler-side call is unchanged)._allocate_kv_cache_from_kvcached: resolve only tensor-backed layer names in the validation loop, representative-layer selection, pool count, and the per-group hetero views._reshape_kv_cache_tensors_from_kvcached: bind pools to tensor-backed names only, then alias sharing layers to their target's cache (fail-loud if the target has no cache). On vLLM releases whose vanillainitialize_kv_cache_tensorsalso aliases after the patched reshape returns, the second pass re-assigns the same objects.kv_cache_config.tensorsschema) predates cross-layer sharing and is untouched.tests/test_shared_kv_layer_binding.py(added totests/manifests/cpu.txt): 14 CPU tests over the helpers, including a scenario shaped like the #417 report that reproduces the KeyError without the filter and a full binding+aliasing pass with it.Validation
python3.11 -m pytest tests/test_shared_kv_layer_binding.py: 14 passed.tools/check_test_classification.py: valid (cpu=23).mypy --python-version 3.10introduces no new errors vs main (error set identical).git merge-tree --write-tree: merges clean against open PRs fix: make KV tensor VMM batches transactional #418/fix: coordinate distributed physical KV mapping outcomes #455/fix: split page create from map to avoid a remap race on Volta #465 (2026-08-26).sharing_active=True num_shared=20,group_layer_names=35 tensor_backed(shared_by)=15, 20 layer names in groups but absent from everyshared_by, then startup dies withKeyError: 'language_model.model.layers.15.self_attn.attn'in EngineCore.