Skip to content

[Core][KV Connector] Keep the connector's head order when its KV layout is unsupported - #59594

Open
lucifer1004 wants to merge 1 commit into
vllm-project:mainfrom
lucifer1004:kv-layout-connector-fallback
Open

lucifer1004 wants to merge 1 commit into
vllm-project:mainfrom
lucifer1004:kv-layout-connector-fallback

Conversation

@lucifer1004

@lucifer1004 lucifer1004 commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Overview

When the KV connector's requested layout is not supported by every backend, resolve_kv_cache_layout now falls back to the first candidate that keeps the requested head/token order, instead of the backends' first choice.

Claims

  • Mooncake (and NIXL) request LBHNC so heterogeneous-TP transfers can slice pages by KV head. On a hybrid model whose backends only accept block-outer layouts, the fallback now picks BLHNC (head-major) instead of BLNHC, so a page can still be cut by head.
  • With that layout, prefill TP1 → decode TP2 over Mooncake works for Qwen3.8-Flash-Next. With BLNHC the handshake fails.
  • No throughput or latency cost on that model at equal TP (tables below).
  • Connectors whose requested layout is supported, and runs with VLLM_KV_CACHE_LAYOUT set, are unchanged.

Validation

  • New tests in tests/v1/core/test_kv_cache_utils.py: test_connector_layout_fallback_keeps_head_order (an LBHNC request against [BLNHC, BLHNC] resolves to BLHNC, and against [BLNHC] still falls back to BLNHC), and test_kv_cache_layout_is_head_major. The first case fails without the change.

  • pytest tests/v1/core/test_kv_cache_utils.py tests/distributed/test_kvlayout.py tests/v1/kv_connector/unit/test_mooncake_connector.py tests/v1/kv_connector/unit/test_mooncake_connector_hybrid_mamba.py: 285 passed.

  • pre-commit hooks pass on the changed files.

  • End to end, on RTX PRO 6000 with Mooncake over RDMA, Qwen3.8-Flash-Next (QSA attention + GDN, 2 KV heads), prefill TP1 → decode TP2. These runs set VLLM_KV_CACHE_LAYOUT=BLHNC, the layout this change now selects, and used the connector with [Bugfix][KV Connector] Mooncake: heterogeneous-TP support for hybrid GDN/Mamba models #53078 applied for the GDN state.

    • BLNHC (today's fallback): every pull fails the handshake, local=39739392 (TP1 page registered as one opaque row) vs remote=1605632.
    • BLHNC: GSM8K (200 questions, 32 in flight) 0.970, 0 errors. A TP2 → TP2 pair scores 0.985 on the same set; the extra misses are coherent reasoning, not garbled output.
  • Performance at equal TP2 (single instance, no connector, VLLM_KV_CACHE_LAYOUT set, 8K in / 1K out random prompts, output tok/s):

    BLNHC vs BLHNC

    TP2, MTP3 speculative decoding:

    C BLNHC BLHNC
    1 236.1 235.6 (-0%)
    4 653.9 643.8 (-2%)
    16 1282.8 1293.8 (+1%)
    32 1628.3 1640.9 (+1%)
    64 1493.9 1491.0 (-0%)

    TP2, no speculative decoding:

    C BLNHC BLHNC
    1 110.2 110.2 (+0%)
    4 348.5 348.6 (+0%)
    16 831.4 836.4 (+1%)
    32 1176.1 1175.5 (-0%)
    64 1214.0 1215.2 (+0%)

    p50 TPOT is the same within noise at every concurrency.

Details

Mooncake and NIXL request LBHNC because a heterogeneous-TP transfer splits each page by KV head, which needs each head's tokens to be contiguous. QSA declares (BLNHC, BLHNC), so LBHNC is not a candidate, and the fallback took BLNHC, where heads are interleaved per token. At TP2 a page holds one head, so it is still contiguous and equal-TP transfers work. At TP1 it holds two, cannot be sliced, and registers as one row the size of the whole block.

The fallback now picks the first candidate whose head/token order (KVCacheLayout.is_head_major, H outside N) matches the request, and otherwise the backends' first choice as before. The other layout requests follow the same rule: offloading and HiSparse ask for head-major layouts, and the example hidden-states connector asks for LBNHC to keep each token's data together, so an unsupported request now falls back to a token-major candidate.

Related: #53078 (Mooncake heterogeneous TP for hybrid GDN models) and #59592 (Mooncake block-size handshake), from the same TP1/TP2/TP4 testing. #58380 changes the layout order the worker reports for mixed page sizes and keeps connector requests taking precedence; it does not touch this fallback.


Pull Request Checklist
  • I used vLLM's /pr-checklist skill. (Mandatory for agents, optional for humans).

  • AI assistance was used during the creation of this PR.

  • Design Fit: Minimizes impact on core components, reuses existing functionality, and justifies added complexity.

  • Testing and Validation: Validates the change and ensures any added tests are meaningful and reliable, with CI coverage or documented CI resource constraints and validation performed outside CI.

  • Code Quality and Style: Keeps code and comments clear and concise, and updates relevant documentation and examples.

  • Pull Request Contents: Includes a brief summary and relevant links, supports claims with evidence, explains root causes and implementation trade-offs, and follows the contributing guide.

AI assistance (Claude Code) was used to write the change, tests, and this description; I reviewed every changed line and ran the tests above.

🤖 Generated with Claude Code

@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.

lucifer1004 added a commit to lucifer1004/vllm that referenced this pull request Oct 3, 2026
…order when its KV layout is unsupported
@property
def is_head_major(self) -> bool:
"""True when H is outside N, so each head's tokens are contiguous."""
return self.value.index(_DIM_H) < self.value.index(_DIM_N)

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.

should it be self.value[_DIM_H] < self.value[_DIM_N] instead?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

No, I believe index() is right here. Each member's value lists the logical dims in physical order, so a dim's physical position is value.index(dim), not value[dim]. For example, BHLNC = (1, 2, 0, 3, 4) is physically [B, H, L, N, C]. The neighboring properties read value the same way: is_block_outermost is value[0] == _DIM_B, is_block_contiguous compares value[-3:], and layer_view_order iterates over value.

value[_DIM_H] is the logical dim stored at physical position 2. For the six layouts defined today, the two expressions happen to agree: five of the permutations are their own inverse, and BHLNC gives the same answer either way. They diverge for a layout like LHNBC = (0, 2, 3, 1, 4), physically [L, H, N, B, C]. Each head's tokens are contiguous there, and value.index(2) = 1 < value.index(3) = 2 says so, but value[2] = 3 > value[3] = 1 would report it as not head-major. Happy to add a comment on the convention next to _DIM_* if that would help.

lucifer1004 added a commit to lucifer1004/vllm that referenced this pull request Oct 7, 2026
…order when its KV layout is unsupported
lucifer1004 added a commit to lucifer1004/vllm that referenced this pull request Oct 13, 2026
…order when its KV layout is unsupported
@mergify

mergify Bot commented Oct 16, 2026

Copy link
Copy Markdown
Contributor

This pull request has merge conflicts that must be resolved before it can be
merged. Please rebase the PR, @lucifer1004.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@mergify mergify Bot added the needs-rebase label Oct 16, 2026
…ut is unsupported

When the KV connector's requested layout is not supported by every
backend, resolve_kv_cache_layout fell back to the backends' first
choice. Mooncake and NIXL request LBHNC so a heterogeneous-TP transfer
can slice each page by KV head. A hybrid model whose backends only
support block-outer layouts (QSA lists BLNHC, BLHNC) then got BLNHC,
where heads are interleaved per token, and a page can no longer be cut
by head: Mooncake registers it as one opaque row and the TP1 -> TP2
handshake fails on region lengths.

Fall back to the first candidate with the same head/token order as the
requested layout, here BLHNC, and only then to the backends' first
choice. Every connector that requests a layout does so for that order
(head-major for transfer and offloading, token-major for the example
hidden-states connector), so the rule matches each request's intent.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Signed-off-by: Zihua Wu <13583761+lucifer1004@users.noreply.github.com>
@lucifer1004
lucifer1004 force-pushed the kv-layout-connector-fallback branch from c18d185 to bca75c9 Compare October 16, 2026 15:21
@lucifer1004
lucifer1004 requested a review from ZJY0516 as a code owner October 16, 2026 15:21
@mergify mergify Bot removed the needs-rebase label Oct 16, 2026
lucifer1004 added a commit to lucifer1004/vllm that referenced this pull request Oct 17, 2026
…when its KV layout is unsupported

Signed-off-by: Zihua Wu <13583761+lucifer1004@users.noreply.github.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants