feat(data): extract verifier KV caches for Gemma4 MTP#758
Conversation
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
🧹 Nitpick comments (1)
src/speculators/train/data.py (1)
73-91: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueUpdate docstring to document new KV cache fields.
The data structure comment at lines 73-80 doesn't mention
verifier_kv_last_localorverifier_kv_last_global. Similarly, the comments at lines 188-193 and 211-218 in__getitem__omit these new fields. Adding them will help future readers understand the full sample schema.📝 Proposed docstring update
# data structure: { # "hidden_states": [seq_len, num_target_layers * hidden_size], # "input_ids": [seq_len], # "verifier_last_hidden_states": [seq_len, hidden_size], + # "verifier_kv_last_local": [...], + # "verifier_kv_last_global": [...], # "loss_mask": [seq_len], # "lengths": [1], # "position_ids": [seq_len], # }🤖 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/train/data.py` around lines 73 - 91, Update the sample schema comments in data.py to include the new KV cache fields: the top-level data structure docstring near the fallback return in the data-loading helper should list verifier_kv_last_local and verifier_kv_last_global alongside the existing tensors, and the __getitem__ comments should also mention these fields so the documented sample shape matches the actual dict returned by the data pipeline.
🤖 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.
Nitpick comments:
In `@src/speculators/train/data.py`:
- Around line 73-91: Update the sample schema comments in data.py to include the
new KV cache fields: the top-level data structure docstring near the fallback
return in the data-loading helper should list verifier_kv_last_local and
verifier_kv_last_global alongside the existing tensors, and the __getitem__
comments should also mention these fields so the documented sample shape matches
the actual dict returned by the data pipeline.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
Run ID: a8fbffaa-9a48-4de1-a9d1-0005f0a30c68
📒 Files selected for processing (1)
src/speculators/train/data.py
orestis-z
left a comment
There was a problem hiding this comment.
The passthrough plumbing looks correct — optional fields are conditionally propagated and dtype-converted alongside other hidden states.
Two questions before this can be reviewed properly:
-
Missing context: The PR description is empty (just a signed-off-by). What is the motivation? Which data generation flow produces
verifier_kv_last_local/verifier_kv_last_global, and which model forward consumes them?grep -rn 'verifier_kv_last' src/returns zero hits outside this PR, so the producer and consumer don't appear to exist in the codebase yet. Is this meant to land alongside another PR? -
No tests: Can you add at least a unit test that round-trips a sample with these optional fields through
standardize_data_v1and verifies they survive collation?
🤖 Generated with Claude Code using the /pr-review skill
|
Hi @orestis-z, thanks for the review! To answer your first question: This PR is part 1 of a 3-part rollout to introduce Gemma4 MTP support (Issue #586).
By splitting this into three PRs, we wanted to keep the review size manageable and isolate the data plumbing from the model math. I'll push the unit tests and docstring fixes shortly! |
|
This pull request has merge conflicts that must be resolved before it can be |
orestis-z
left a comment
There was a problem hiding this comment.
Thanks for the follow-up commit. The test covers the standardize_data_v1 → collate_fn round-trip for the optional KV fields, which was the main gap. The conditional propagation, dtype conversion, and backward compatibility with non-Gemma4 datasets all look correct. LGTM. Recommend approving once the merge conflict is resolved.
🤖 Generated with Claude Code using the /pr-review skill
orestis-z
left a comment
There was a problem hiding this comment.
Data pipeline changes (KV cache extraction, dtype conversion, backward compatibility, tests) are unchanged from the previous round and still correct. Merge conflict is resolved.
The merge commit adds two new config fields to MTPSpeculatorConfig that weren't in the prior rounds — see inline comment.
🤖 Generated with Claude Code using the /pr-review skill
| num_centroids: int | None = Field( | ||
| default=None, | ||
| description=( | ||
| "Number of centroids for a centroid-masked multi-level LM head. " | ||
| "If None, standard flat LM head is used." | ||
| ), | ||
| ) | ||
|
|
||
| top_k_centroids: int = Field( | ||
| default=32, | ||
| description="Number of top centroids to select during multi-level LM head decoding.", | ||
| ) |
There was a problem hiding this comment.
These two fields (num_centroids, top_k_centroids) are new in this merge commit and weren't in the previous rounds. Per your rollout plan, centroid-masked LM head support is part 2, while this PR is part 1 (data pipeline). No code in this PR or on main references these fields.
Consider deferring them to the multi-level LM head PR where they'll have a consumer and can be reviewed alongside the code that uses them. If there's a reason to land them here (e.g., checkpoint format compatibility), please note it in the PR description.
…dels This commit introduces the architecture modifications required for Gemma4 MTP support. Unlike Qwen-style MTP which maintains its own full attention layers and KV cache, Gemma4 MTP borrows the last local and global KV caches from the verifier base model. - Registered Gemma2 components in base_components.py - Implemented QueryOnlyGemma2Attention to bypass K/V projections and route external KV caches - Updated MTPDraftModel.forward to pass verifier_kv_last_local/global to layers - Added unit tests for KV cache routing in Gemma4 MTP Relates to vllm-project#586, vllm-project#758 Signed-off-by: santosh-agebold <12378904+shotsan@users.noreply.github.com>
|
Good catch @orestis-z! I accidentally leaked those two config fields from the subsequent ML Head PR when resolving merge conflicts. I just pushed a fix to remove them entirely from this PR so it remains strictly focused on the data pipeline. |
orestis-z
left a comment
There was a problem hiding this comment.
The leaked config fields from the previous round have been removed. Data pipeline changes (conditional KV cache propagation, dtype conversion, backward compatibility with non-Gemma4 datasets, tests) remain correct and unchanged. LGTM. Recommend approving.
🤖 Generated with Claude Code using the /pr-review skill
|
The quality checks have failed. Please run |
| } | ||
| if "verifier_kv_last_local" in data: | ||
| res["verifier_kv_last_local"] = data["verifier_kv_last_local"] | ||
| if "verifier_kv_last_global" in data: | ||
| res["verifier_kv_last_global"] = data["verifier_kv_last_global"] | ||
| return res |
There was a problem hiding this comment.
Can we modify these keys to match this pr:
#674
Left a comment
|
Good catch! Updated |
speculatorsbot
left a comment
There was a problem hiding this comment.
The data pipeline plumbing for SampleFileDataset (via standardize_data_v1) correctly reads PR #674's key names (kv_last_local_k/_v) and stacks them. However, the ArrowDataset path — which is the primary consumer of Gemma4KVConnector's safetensors output — has a key name mismatch that will silently drop the KV caches. One inline comment below.
🤖 Generated with Claude Code using the /pr-review skill
| if "verifier_kv_last_local" in loaded_hs: | ||
| res["verifier_kv_last_local"] = loaded_hs["verifier_kv_last_local"] | ||
| if "verifier_kv_last_global" in loaded_hs: | ||
| res["verifier_kv_last_global"] = loaded_hs["verifier_kv_last_global"] |
There was a problem hiding this comment.
ArrowDataset._get_raw_data looks for verifier_kv_last_local / verifier_kv_last_global in the loaded safetensors, but PR #674's Gemma4KVConnector writes the KV caches as four separate tensors: kv_last_local_k, kv_last_local_v, kv_last_global_k, kv_last_global_v (see GEMMA4_KV_KEYS and the _submit_async_write save path). These keys will never match verifier_kv_last_local, so the condition is always False and the KV caches are silently dropped.
This was already fixed in standardize_data_v1 (line 119) after @Roderick-Wu's comment, but the same transformation needs to be applied here. Something like:
if "kv_last_local_k" in loaded_hs and "kv_last_local_v" in loaded_hs:
res["verifier_kv_last_local"] = torch.stack(
[loaded_hs["kv_last_local_k"], loaded_hs["kv_last_local_v"]], dim=1
)
if "kv_last_global_k" in loaded_hs and "kv_last_global_v" in loaded_hs:
res["verifier_kv_last_global"] = torch.stack(
[loaded_hs["kv_last_global_k"], loaded_hs["kv_last_global_v"]], dim=1
)(Or extract the stacking into a shared helper to avoid duplicating it.)
|
I have pushed a fix to apply the same |
speculatorsbot
left a comment
There was a problem hiding this comment.
The ArrowDataset._get_raw_data key name mismatch I flagged last round is fixed — it now uses the PR #674 key names (kv_last_local_k/_v) with torch.stack, matching standardize_data_v1. LGTM. Recommend approving.
🤖 Generated with Claude Code using the /pr-review skill
|
This pull request has merge conflicts that must be resolved before it can be |
Signed-off-by: santosh-agebold <12378904+shotsan@users.noreply.github.com> Signed-off-by: shotsan <shotsan@users.noreply.github.com>
Signed-off-by: santosh-agebold <12378904+shotsan@users.noreply.github.com> Signed-off-by: shotsan <shotsan@users.noreply.github.com>
Signed-off-by: shotsan <shotsan@users.noreply.github.com>
Signed-off-by: shotsan <shotsan@users.noreply.github.com>
Signed-off-by: shotsan <shotsan@users.noreply.github.com>
Signed-off-by: shotsan <shotsan@users.noreply.github.com>
97c7018 to
5bf4793
Compare
…dels This commit introduces the architecture modifications required for Gemma4 MTP support. Unlike Qwen-style MTP which maintains its own full attention layers and KV cache, Gemma4 MTP borrows the last local and global KV caches from the verifier base model. - Registered Gemma2 components in base_components.py - Implemented QueryOnlyGemma2Attention to bypass K/V projections and route external KV caches - Updated MTPDraftModel.forward to pass verifier_kv_last_local/global to layers - Added unit tests for KV cache routing in Gemma4 MTP Relates to vllm-project#586, vllm-project#758 Signed-off-by: santosh-agebold <12378904+shotsan@users.noreply.github.com> Signed-off-by: shotsan <shotsan@users.noreply.github.com>
|
@orestis-z @Roderick-Wu I just rebased this PR to resolve the merge conflicts with the latest |
|
Hi @shotsan, sorry for the wait. We'll have to wait for other reviewers. In the meantime, I have been trying to test all 3 prs + this earlier one: I've put them under this branch There are a few small issues here and there that come up when we actually try running the whole thing end to end, so I'll keep you updated. |
|
@shotsan Thanks for breaking up the PRs into smaller pieces! It's very helpful. Could you please add a PR description to help reviewers understand which issue you're trying to resolve and attach test results as well? Thanks! |
|
@shanjiaz Thanks for checking in! Since this PR has been open for a bit and already went through extensive review (we addressed all feedback and CodeRabbit has fully approved it), could I kindly ask you to read through the earlier comments for the full context? We are just trying to keep the comment flow clean without repeating the full picture, as this is part of a 3-PR stack (#758, #767, #768) that we are eager to land. @Roderick-Wu Just checking in—did you have any updates on your end-to-end testing with the combined branch? We managed to track down a small unit test bug (a missing |
|
I've been able to train a full gemma4 mtp speculator fully on the merged branch. However, it has some compatibility issues with actually testing it on vllm. The native vllm gemma4 supported architecture is found in vllm/vllm/model_executor/models/gemma4_mtp.py. This one is kind of based off of the Qwen3 version? Is this correct? |
|
@Roderick-Wu Yes, that is exactly correct. The current implementation is based on the Qwen3 version. The reason for this design is that the core MTP training pipeline ( However, to support testing on vLLM using the native architecture, a separate, new PR will be opened to add support for the native vLLM Gemma 4 layout (with explicit |
|
Hi @shotsan I got an agent to put some bandaid fixes for mapping the model to the gemma4 mtp version accepted by vllm. This version is able to train a speculator e2e and run it in vllm. Is this desired behavior? |
|
@Roderick-Wu The draft PR looks excellent. The agent did a fantastic job implementing the More importantly, the agent correctly added this under a new After a thorough diff against the current branches, only one minor issue was identified: the agent appears to have worked off a slightly older commit and accidentally reverted a recent fix applied to A quick patch to Overall, these changes represent the exact desired behavior. The commits from PR 845 can be pulled into the existing branches, and once the minor test fix is reapplied, the comprehensive Gemma 4 MTP support will be ready. |
Merge Protections🔴 1 of 1 protections blocking · waiting on 👀 reviews
🔴 Require approval from approved reviewers listWaiting for any of
This rule is failing.All pull requests must have at least one approving review from a member of the approved reviewers list before merging.
|
speculatorsbot
left a comment
There was a problem hiding this comment.
Data pipeline changes for the KV cache passthrough are correct on the happy path — conditional propagation, key name alignment with PR #674, dtype conversion in collate_fn, and test coverage all look solid. The utils import cleanup is fine.
One shape issue in the empty-sample fallback — see inline comment.
🤖 Generated with Claude Code using the /pr-review skill
| "verifier_kv_last_local": torch.empty(0, dtype=dtype), | ||
| "verifier_kv_last_global": torch.empty(0, dtype=dtype), |
There was a problem hiding this comment.
The other empty tensors in create_empty_sample preserve their inner dimensions — e.g. torch.empty(0, hidden_size) for verifier_last_hidden_states, torch.empty(0, num_target_layers * hidden_size) for hidden_states. These KV tensors are 1-dimensional (0,) instead.
When the empty fallback is used in collate_fn (all samples in a batch failed), the collation infers the output shape from first.shape[1:]. For these 1D tensors that yields (), so the padded output becomes [max_len] instead of the expected [max_len, 2, head_dim]. Downstream model code that indexes into the KV tensors by dimension will get a shape error.
Since these fields are optional (non-Gemma4 datasets never produce them), the simplest fix is to remove them from create_empty_sample entirely. The collation iterates for key in batch[0], so when the empty sample is batch[0] without KV keys, those keys are simply absent from the output — which is consistent with how non-KV datasets already behave. Adding them here also has the side effect of injecting unexpected KV keys into the fallback output for non-Gemma4 datasets that never produce them.
|
@shanjiaz I have updated the PR description with the detailed breakdown of the issue being resolved, the specific code changes, and the test results as requested! Please let me know if there is anything else needed for your approval. |
Purpose
Resolves the offline data ingestion requirements for Gemma 4 MTP training by properly extracting and batching the verifier's KV caches.
The Gemma 4 MTP architecture uses a query-only attention mechanism that borrows the KV cache from the verifier model. To train this offline, the dataset must provide the extracted KV caches for every token. This PR updates the data loading and batching pipeline to handle these new 5-D KV tensors.
Code Changes:
ArrowDataset._get_raw_dataandstandardize_data_v1to dynamically detectkv_last_local_kand_vkeys. It usestorch.stackto combine them into the unifiedverifier_kv_last_localtensor that the downstream model expects.BaseDataset.__getitem__, the KV caches are correctly cast to thehidden_states_dtype(bfloat16) to prevent downstream mixed-precision crashes.shift_batch_mtpinsrc/speculators/models/mtp/data.pyto correctly forward the 5-D KV cache tensors through the training loop without sequence misalignment.Tests
tests/unit/models/test_mtp_data.py. The unit tests pass locally and verify that the 5-D KV tensors(batch_size, seq_len, 2, num_kv_heads, head_dim)are correctly preserved and aligned.