Skip to content

feat: add out-of-tree Gemma4 KV connector#674

Open
Beichen-Ma wants to merge 5 commits into
vllm-project:mainfrom
Beichen-Ma:feat/gemma4-kv-connector
Open

feat: add out-of-tree Gemma4 KV connector#674
Beichen-Ma wants to merge 5 commits into
vllm-project:mainfrom
Beichen-Ma:feat/gemma4-kv-connector

Conversation

@Beichen-Ma

Copy link
Copy Markdown

Purpose

RFC. Added Gemma4KVConnector, an out-of-tree vLLM KV connector that produces training data for finetuning Gemma4 MTP draft models.

Load it via:

  kv_transfer_config={
      "kv_connector": "Gemma4KVConnector",
      "kv_connector_module_path": "speculators.data_generation.gemma4_kv_connector",
      "kv_role": "kv_producer",
      "kv_connector_extra_config": {"shared_storage_path": "<dir>"},
  }

Tests

  • Added e2e test tests/e2e/run_gemma4_kv_extraction.py
  • run_gemma4_kv_extraction helper in tests/e2e/utils.py
  • Validated against google/gemma-4-E4B-it:
    • tp =1 and tp>1 passed. Asserts saved tensors carry the full unsharded head count, exercising the all-gather + GQA dedup

Checklist

I have filled in:

  • The purpose of the PR, such as "Fix some issue (link existing issues this PR will resolve)".
  • The test plan/results, such as providing test command and pasting the results.
  • (Optional) The necessary documentation update.
  • I (a human) have written or reviewed the code in this pr to the best of my ability.

@coderabbitai

coderabbitai Bot commented Jun 27, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 337a56be-f003-48cd-a441-78de1b7c5a8a

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds Gemma4KVConnector, a vLLM out-of-tree KV transfer connector that extracts local-sliding and global-full verifier KV tensors from Gemma4 paged caches during inference, gathering heads across TP ranks before writing to disk. Includes a subprocess test driver and e2e smoke tests for TP=1 and TP=2.

Changes

Gemma4 KV Connector and E2E Tests

Layer / File(s) Summary
KV extraction utilities and connector implementation
src/speculators/data_generation/gemma4_kv_connector.py
Defines module constants, extract_real_kv_from_cache (5-D paged cache slicing), Gemma4PendingSave (adds verifier block ID fields), and Gemma4KVConnector with init, _resolve_layer, register_kv_caches, request_finished_all_groups, _slot_mapping_from_blocks, _gather_kv_heads, _submit_async_write, and _pin.
Subprocess test driver
tests/e2e/run_gemma4_kv_extraction.py
Runs LLM generation in a subprocess, loads extracted hidden states, collects per-request token alignment and per-KV-key presence/shape/finite flags, and writes JSON results for parent-side assertions.
E2E test utility, constants, and smoke tests
tests/e2e/utils.py, tests/e2e/smoke/test_gemma4_kv_connector.py
Adds GEMMA4_KV_KEYS, run_gemma4_kv_extraction (subprocess invocation plus assertions on shape, finiteness, TP head counts, and local vs global KV dimension differences), and TP=1/TP=2 pytest smoke tests.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: adding an out-of-tree Gemma4 KV connector.
Description check ✅ Passed The description matches the changeset and explains the new connector, tests, and validation results.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

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

Actionable comments posted: 4

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/speculators/data_generation/gemma4_kv_connector.py`:
- Around line 72-76: The KV extraction logic indexes all entries from
slot_mapping before truncating, so update the slot_mapping handling in this
kv_cache block to validate the length against num_tokens and trim it first. In
the data generation path that computes block_idx and block_off, ensure any extra
mapped slots are ignored and any short mapping is rejected or handled explicitly
before using kv_cache[:, 0] and kv_cache[:, 1]. Keep the fix localized to the
hidden-state/KV save path in gemma4_kv_connector.py so the extracted k and v
rows always align with num_tokens.
- Around line 70-71: The KV layout checks in gemma4_kv_connector should not use
assert because they can be skipped when Python runs with optimizations. Replace
the assertions in the KV validation logic with explicit exception raising in the
same spot, using the existing _bad_layout_msg and the surrounding KV cache
validation path so unsupported pipeline_parallel_size, quantized cache settings,
or malformed kv_cache tensors fail immediately with a clear error.
- Around line 252-260: The current head-stride logic in _gather_kv_heads is
already covered for standard TP cases, so move this from correctness to coverage
by adding a test that exercises total_kv_heads < tensor_parallel_size. The test
should verify the gathered KV head count path in gemma4_kv_connector.py and
assert that a non-divisible gather shape is rejected, while keeping the existing
behavior of deduping contiguous GQA replicas intact.

In `@tests/e2e/utils.py`:
- Around line 508-513: The speculative-decoding helper test is not verifying
that any request actually exceeds the 512-token sliding window, so the overflow
path may never be exercised. Update the test logic around the prompts batch in
the relevant helper(s) to assert via results["per_request"] that at least one
request token count crosses 512, using the existing prompt-generation setup and
the per-request output to confirm the long prompt truly triggers the
sliding-window overflow case.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 3f2cf2b8-34de-4808-9736-673a89209b2c

📥 Commits

Reviewing files that changed from the base of the PR and between 5aa4da9 and 9865b0f.

📒 Files selected for processing (4)
  • src/speculators/data_generation/gemma4_kv_connector.py
  • tests/e2e/run_gemma4_kv_extraction.py
  • tests/e2e/smoke/test_gemma4_kv_connector.py
  • tests/e2e/utils.py

Comment thread src/speculators/data_generation/gemma4_kv_connector.py Outdated
Comment thread src/speculators/data_generation/gemma4_kv_connector.py Outdated
Comment on lines +252 to +260
gathered = tensor_model_parallel_gather(x, dst=0, dim=1)
if gathered is None:
return None

stride = gathered.shape[1] // total_kv_heads
if stride > 1:
gathered = gathered[:, ::stride, :]

return gathered.contiguous()

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Inspect current gather/dedup coverage and configs mentioning KV head counts.
rg -n "tensor_parallel_size|num_key_value_heads|total_kv_heads|_gather_kv_heads|GQA|dedup" src tests

Repository: vllm-project/speculators

Length of output: 4898


Shift this to coverage, not correctness. The code already dedups contiguous GQA replicas in _gather_kv_heads, and the smoke tests cover both TP=1 and TP=2. Add a case with total_kv_heads < tensor_parallel_size to lock down the head-count path and reject any non-divisible gather shape.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/speculators/data_generation/gemma4_kv_connector.py` around lines 252 -
260, The current head-stride logic in _gather_kv_heads is already covered for
standard TP cases, so move this from correctness to coverage by adding a test
that exercises total_kv_heads < tensor_parallel_size. The test should verify the
gathered KV head count path in gemma4_kv_connector.py and assert that a
non-divisible gather shape is rejected, while keeping the existing behavior of
deduping contiguous GQA replicas intact.

Source: Path instructions

Comment thread tests/e2e/utils.py
Comment on lines +508 to +513
# Short, and a long prompt past the 512 sliding window, in one batch.
prompts = [
"Hello world",
"Test prompt with several tokens",
" ".join(["word"] * 600),
]

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Assert that one request really crosses the 512-token sliding window.

Right now the test assumes the repeated "word" prompt tokenizes past 512, but nothing checks that in results["per_request"]. If tokenization ever stays at or below 512, this helper still passes without exercising the overflow case it is supposed to cover.

Suggested fix
     assert results["num_outputs"] == len(prompts)
     assert len(results["per_request"]) == len(prompts)
+    assert any(req["num_tokens"] > 512 for req in results["per_request"]), (
+        "expected at least one prompt to exceed the 512-token sliding window"
+    )
     expected_heads = {
         "kv_last_local_k": results["local_total_heads"],
         "kv_last_local_v": results["local_total_heads"],
         "kv_last_global_k": results["global_total_heads"],
         "kv_last_global_v": results["global_total_heads"],

As per path instructions, "Ensure PyTest tests are clear, comprehensive, and cover edge cases specific to speculative decoding..."

Also applies to: 551-583

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/e2e/utils.py` around lines 508 - 513, The speculative-decoding helper
test is not verifying that any request actually exceeds the 512-token sliding
window, so the overflow path may never be exercised. Update the test logic
around the prompts batch in the relevant helper(s) to assert via
results["per_request"] that at least one request token count crosses 512, using
the existing prompt-generation setup and the per-request output to confirm the
long prompt truly triggers the sliding-window overflow case.

Source: Path instructions

@orestis-z orestis-z left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

LGTM. Clean Phase 1 implementation of the vLLM RFC. The TP gather/dedup path, CUDA stream synchronization, and layer resolution logic all look correct. Good test coverage with both tp=1 and tp=2 smoke tests. Recommend approving.

🤖 Generated with Claude Code using the /pr-review skill

Comment thread tests/e2e/utils.py
Comment on lines +35 to +41
GEMMA4_KV_KEYS = (
"kv_last_local_k",
"kv_last_local_v",
"kv_last_global_k",
"kv_last_global_v",
)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Marked for
#758
@shotsan can we make keys match?

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

LGTM. Clean out-of-tree KV connector implementation matching Phase 1 of the vLLM RFC. One non-blocking note: all commits are missing Signed-off-by lines (the DCO CI check is also failing). The author will need to amend or rebase with -s before merge.

🤖 Generated with Claude Code using the /pr-review skill

@mergify

mergify Bot commented Jul 24, 2026

Copy link
Copy Markdown

Merge Protections

🔴 1 of 1 protections blocking · waiting on 👀 reviews

Protection Waiting on
🔴 Require approval from approved reviewers list 👀 reviews

🔴 Require approval from approved reviewers list

Waiting for any of

  • approved-reviews-by = dsikka
  • approved-reviews-by = fynnsu
  • approved-reviews-by = orestis-z
  • approved-reviews-by = rahul-tuli
  • approved-reviews-by = shanjiaz
This rule is failing.

All pull requests must have at least one approving review from a member of the approved reviewers list before merging.

  • any of:
    • approved-reviews-by = dsikka
    • approved-reviews-by = fynnsu
    • approved-reviews-by = orestis-z
    • approved-reviews-by = rahul-tuli
    • approved-reviews-by = shanjiaz

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants