Skip to content

[KV Connector][NIXL] Register packed MLA KV rows as a single region - #61074

Merged
ivanium merged 3 commits into
vllm-project:mainfrom
NickLucche:nixl-packed-row-regions
Oct 15, 2026
Merged

ivanium merged 3 commits into
vllm-project:mainfrom
NickLucche:nixl-packed-row-regions

Conversation

@NickLucche

@NickLucche NickLucche commented Oct 11, 2026 •

Copy link
Copy Markdown
Member

Purpose

With a block-outermost KV cache layout (VLLM_KV_CACHE_LAYOUT=BLHNC), all layers' pages of a block sit in one contiguous row.
NIXL still registers one region per layer, so each block moves as one descriptor per layer (effectively rendering BL* useless).

This PR registers the row once, fixing registration back to its intended use.

Example: GLM-5.3-NVFP4, TP4, BLHNC. Input to register_kv_caches on rank 0 is 99 views over one 43.8 GB storage:

model.layers.0.self_attn.indexer.k_cache  (14323, 1, 64, 132)  stride(0)=3059712  offset 0
model.layers.0.self_attn.attn             (14323, 1, 64, 576)  stride(0)=3059712  offset 177408
...

row b: [ idx L0 | idx L1 | ... | mla L0 | mla L1 | ... | mla L77 ]   3059712 B

Before (elif storage_is_block_major: branch), each view is its own region:

region 0:  base S + 177408, block_len 36864, stride 3059712   # layers.0.attn
region 1:  base S + 0,      block_len 8448,  stride 3059712   # layers.0.indexer
...
-> 99 regions, 99 x 14323 = 1.42M descriptors; a 2-block request = 198 descriptors

After (the existing whole-row branch, previously push-only), every view emits the same region and dedup folds them into one:

region 0:  base S, block_len 3059712, stride 3059712           # whole row, all layers
-> 1 region, 14323 descriptors; a 2-block request = 2 descriptors

HiSparse

A HiSparse decoder can't register rows: its MLA cache is in host DRAM and its indexer on GPU, so it keeps per-layer regions LB*.
P can still advertise each layer's (offset, page_size) within the row (BL*).
When the peer is per-layer, _split_remote_packed_rows (added with this PR) maps P's row onto the peer's per-layer regions.

BLHNC P + HiSparse D therefore works as before, with the same descriptor count as main.
cc @LucasWilkinson the alternative is to enforce P also uses LB* here, not sure if there's disadvantages (considering .

Out of scope: GQA spec-decode drafts under an MLA target, deferred to #60564.

Test Plan

  • Unit: pytest tests/v1/kv_connector/unit/test_nixl_desc_geometry.py test_nixl_connector.py test_nixl_connector_hma.py test_nixl_push_connector.py
    • test_packed_mla_pp1_transfers_whole_rows now covers pull and push and asserts 1 region / 1 descriptor per block, guarding against falling back to per-layer regions again.
    • New tests for the row split and for the mismatched-rows error.
  • E2E, GLM-5.3-NVFP4, P TP4 -> D TP4 on 8xB200, NixlConnector pull, tests/evals/gsm8k/gsm8k_eval.py --num-questions 500 (5-shot) through toy_proxy_server.py.

Test Result

  • Unit: 593 passed across the four suites. On main, the new pull-mode cases of test_packed_mla_pp1_transfers_whole_rows fail (per-layer regions); with this PR they pass.
P -> D regions P / D gsm8k NIXL transfers avg descriptors / transfer
BLHNC -> BLHNC 1 / 1 (99 / 99 on main) 0.912 1040, 0 failed 1.2
BLHNC -> HiSparse D (default layout) 1 / 99 0.918 685, 0 failed 45.9

Related PRs

#60851 and #60564 target the same descriptor blow-up. #60564 puts whole rows behind an opt-in whole_row_regions flag that must be set on both P and D, and rejects HiSparse. Here it is the default for BLHNC, needs no flag, and HiSparse decoders keep working.

#60564 also handles GQA draft head-sharding, which this PR leaves to it.

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

@NickLucche

Copy link
Copy Markdown
Member Author

/ci run --allow-stale

@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #94324 for commit 8d739f2a1cbe.

⚠️ This PR is 82 commits behind upstream main. Running CI at your own risk because --allow-stale was requested; outdated CI configuration may cause failures. Before merging, merge or rebase onto the latest main, then rerun /ci run on the latest PR commit.

@vllm-agent

Copy link
Copy Markdown
Collaborator

CI selector (shadow): 13 test steps (17 jobs) instead of 19 (25 jobs)

Shadow mode: this changes nothing about what CI runs. It shows what the evidence-based selector would pick for this PR, next to today's rules. How it works.

Feedback welcome: reply here if it would skip a step this change needs, or runs something unrelated.

steps (jobs) Today's rules Selector Would skip Would add
NVIDIA, CPU and others 19 (25) 13 (17) 6 (8) 0 (0)
AMD mirrors 27 (30) 12 (15) 15 (15) 0 (0)
Selector would run (13)
  • crosslayer-kv-layout-distributed-nixlconnector-pd-accuracy-tests-4-gpus
  • distributed-flashinfer-nixlconnector-pd-accuracy-4-gpus
  • distributed-nixlconnector-pd-accuracy-4-gpus
  • dp-ep-distributed-nixlconnector-pd-accuracy-tests-4-gpus
  • hybrid-ssm-nixlconnector-pd-accuracy-tests-4-gpus
  • hybrid-ssm-nixlconnector-pd-prefix-cache-2-gpus
  • kimi-k3-prefix-cache-4xb200 ×2
  • multiconnector-nixl-offloading-pd-accuracy-2-gpus
  • multiconnector-nixl-offloading-pd-edge-cases-2-gpus
  • nixlconnector-pd-edge-cases-2-gpus
  • push-nixlconnector-pp-prefill-pd-accuracy-4-gpus
  • v1-kv-connectors ×4
  • v1-others-cpu
Would skip (today's rules run them) (6)
  • ascend-npu-test
  • cpu-language-generation-and-pooling-model-tests ×3
  • v1-core
  • v1-executor-worker
  • v1-kv-offload
  • v1-metrics-lmeval
Would add (today's rules do not run them) (0)

none

AMD mirrors: would skip (15)
  • extract-hidden-states-integration-2-gpus
  • fault-tolerance-e2e-2xh100
  • fusion-and-compile-unit-tests-2xb200
  • fusion-e2e-tp2-ar-rms-config-sweep-h100
  • fusion-e2e-tp2-b200
  • fusion-e2e-tp2-quick-h100
  • gemm-rs-ar-2xb200
  • kernels-fusedmoe-layer-test-2-b200s
  • kernels-fusedmoe-layer-test-2-h100s
  • kernels-minimax-reduce-rms-test-2-gpus
  • platform-tests
  • v1-core
  • v1-executor-worker
  • v1-kv-offload
  • v1-metrics-lmeval
AMD mirrors: would add (0)

none

3 changed files · base 1789f09588 · head 8d739f2a1c · Python record: build 93039 at 1e5d0ea888 · kernel record: table 74f4404 (build 94125), map 74f4404 · not counted: 11 build steps, 5 A100 steps the generator no longer emits, 11 optional steps the selector would also run

@NickLucche

Copy link
Copy Markdown
Member Author

/ci run

@github-actions

Copy link
Copy Markdown

❌ This PR is 131 commits behind upstream main. Your branch must contain every commit currently on upstream main. No new CI build was started. Merge or rebase onto the latest main, then rerun /ci run. To test this branch at your own risk, use /ci run --allow-stale.

@NickLucche

Copy link
Copy Markdown
Member Author

/ci run

@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #94492 for commit 6ee70a15a7c9.

@mergify

mergify Bot commented Oct 14, 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, @NickLucche.

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 14, 2026

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

LGTM. One minor comment but I think it's okay to merge with or without it.

Comment on lines +2270 to +2273
# With HiSparse, P may be on BL* while D is LB*, as only indexer is on HBM.
# TODO (NickLucche) we may consider not allowing this case at all (P BL*)
# and just clear this function.
self._split_remote_packed_rows(nixl_agent_meta)

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.

I kinda agree and would prefer to reject this case directly.

@ivanium ivanium added the ready ONLY add when PR is ready to merge/full CI is needed label Oct 15, 2026
NickLucche and others added 3 commits October 15, 2026 04:58
With a block-outermost layout (e.g. VLLM_KV_CACHE_LAYOUT=BLHNC) every
layer's page of a block lives in one contiguous row. Register that row
as one NIXL region in pull mode too, so a block is a single descriptor
instead of one per layer. Peers with per-layer regions (HiSparse decode,
layer-outer layouts) address pages inside the row via the advertised
packed_member_layouts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: NickLucche <nicolo.lucchesi@mistral.ai>
… rows

Explain _split_remote_packed_rows with a small diagram, and run the
whole-row registration test for the pull worker too, so packed BLHNC rows
cannot silently fall back to one region per layer again.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: NickLucche <nicolo.lucchesi@mistral.ai>
DeepSeek-V4.1 registers a compressor-state CircularBufferSpec ring, and
_is_csa_linear then forced packed_storage off. Its seven transfer groups
alias each block row from byte 0, so it fell back to overlapping per-layer
regions: about 11k NIXL descriptors per 100k-token request.

Gate whole-row transfer on what it needs instead: every page in the row
must be TP-replicated, which holds for MLA without SSM state.
Qwen3.8-Flash-Next (Mamba) and non-MLA models keep per-layer regions.

Cherry-picked from vllm-project#61121 (c673ff0).

Co-authored-by: Yifan Qiao <yifanqiao@inferact.ai>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Yifan Qiao <yifanqiao@inferact.ai>
Signed-off-by: NickLucche <nicolo.lucchesi@mistral.ai>
Co-authored-by: Codex <noreply@openai.com>
@ivanium
ivanium force-pushed the nixl-packed-row-regions branch from 6ee70a1 to 5f18e79 Compare October 15, 2026 05:08
@mergify mergify Bot removed the needs-rebase label Oct 15, 2026
@ivanium

ivanium commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

/ci run

@ivanium
ivanium enabled auto-merge (squash) October 15, 2026 05:09
@github-actions

Copy link
Copy Markdown

✅ Triggered Buildkite CI #95032 for commit 5f18e79480ca.

@ivanium
ivanium merged commit 7020600 into vllm-project:main Oct 15, 2026
75 checks passed
michael-gpu pushed a commit to practical-inference/vllm-ampere-extended that referenced this pull request Oct 20, 2026
…llm-project#61074)

Signed-off-by: NickLucche <nicolo.lucchesi@mistral.ai>
Signed-off-by: Yifan Qiao <yifanqiao@inferact.ai>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Co-authored-by: Yifan Qiao <yifanqiao@inferact.ai>
Co-authored-by: Codex <noreply@openai.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kv-connector ready ONLY add when PR is ready to merge/full CI is needed

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants