feat(peagle): add random anchor subsampling (max_anchors) - #687
Conversation
|
Important Review skippedAuto incremental reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the ⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: You can disable this status message by setting the Use the checkbox below for a quick retry:
📝 WalkthroughB{max_anchors set and candidates exceed cap?}B -- Yes --> C[Random permutation subset of all_valid_indices to max_anchors] |
There was a problem hiding this comment.
Actionable comments posted: 2
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
src/speculators/models/dflash/core.py (1)
158-163: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick winDefault on
None, not on any falsy value.
kwargs.get("max_anchors") or 3072makes an explicit0indistinguishable from an omitted value. That means--max-anchors 0silently becomes3072for DFlash, while the same value is rejected by the new P-EAGLE config. Only fall back when the argument is actuallyNone.💡 Suggested fix
- max_anchors=kwargs.get("max_anchors") or 3072, + max_anchors=( + 3072 + if kwargs.get("max_anchors") is None + else kwargs["max_anchors"] + ),🤖 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/models/dflash/core.py` around lines 158 - 163, The max_anchors defaulting in DFlashSpeculatorConfig is using a falsy check, so an explicit 0 gets replaced with 3072 instead of being preserved or handled consistently. Update the logic in the DFlash config निर्माण path to default only when kwargs.get("max_anchors") is None, matching the behavior of the related config handling and keeping 0 distinct from a missing value.
🧹 Nitpick comments (2)
tests/unit/models/test_peagle_data.py (2)
26-38: 🧹 Nitpick | 🔵 Trivial | 💤 Low valueConsider extending chain cap verification to depth 2+.
The test only asserts
depth1_count <= max_anchors, but the docstring says "depth-1+ chains." Depth 2+ counts derive from depth 1's already-capped pool, so they're transitively bounded, but explicit assertions would strengthen the contract. Not a blocker.The
anchor_posunused variable is acceptable test clarity; ignore RUF059.🤖 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/unit/models/test_peagle_data.py` around lines 26 - 38, The max_anchors test only checks depth 1, but the contract covers depth-1+ chains; update test_max_anchors_caps_chains in test_peagle_data.py to explicitly verify that depth 2 and deeper counts are also bounded by the capped depth-1 pool, using generate_cod_sample_indices and the depth tensor. Keep anchor_pos unused as-is and add assertions for the deeper depths so the chain-cap behavior is tested end-to-end.
49-68: 📐 Maintainability & Code Quality | 🔵 TrivialRemove unnecessary
torch.manual_seedcalls.With
max_anchors=None,generate_cod_sample_indicesdoes not invoke any random sampling (notorch.randpermpath is taken), so the manual seeding has no effect on the output. The equality assertion holds deterministically without it. The seed calls may mislead readers into thinking the default path is stochastic.def test_max_anchors_none_uses_all(self): """max_anchors=None should use all valid positions (default behavior).""" seq_len = 32 - torch.manual_seed(42) anchor_pos_none, depth_none = generate_cod_sample_indices( seq_length=seq_len, loss_mask=_loss_mask(seq_len), num_depths=4, max_anchors=None, ) - torch.manual_seed(42) anchor_pos_default, depth_default = generate_cod_sample_indices( seq_length=seq_len, loss_mask=_loss_mask(seq_len), num_depths=4, ) assert torch.equal(anchor_pos_none, anchor_pos_default) assert torch.equal(depth_none, depth_default)🤖 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/unit/models/test_peagle_data.py` around lines 49 - 68, Remove the unnecessary torch.manual_seed calls from test_max_anchors_none_uses_all in test_peagle_data.py, since generate_cod_sample_indices with max_anchors=None follows a deterministic path and does not use random sampling. Keep the assertions comparing anchor_pos_none/depth_none to anchor_pos_default/depth_default, and leave the test focused on verifying the default behavior of generate_cod_sample_indices without implying stochasticity.
🤖 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/models/peagle/data.py`:
- Around line 40-43: The capped sampling in the anchor-selection logic is
mutating all_valid_indices so it no longer represents the full loss-mask-valid
set, which later breaks the torch.isin(next_candidates, all_valid_indices) chain
check. Update the data flow in the relevant function in data.py so the full
valid-position collection stays separate from the capped seed subset, and use
distinct symbols for the complete valid indices versus the max_anchors-limited
anchors when building and extending chains.
In `@tests/unit/models/test_peagle_data.py`:
- Around line 69-81: The test in test_max_anchors_fewer_valid_than_cap is too
weak because anchor_pos.shape[0] > 0 does not prove the below-cap behavior in
generate_cod_sample_indices. Strengthen the assertion by checking that the full
set of valid positions from loss_mask is represented in the sampled indices (or
that the unique sampled valid indices across depths matches the valid count,
accounting for depth shifts), so the test verifies max_anchors is not truncating
when valid positions are fewer than the cap.
---
Outside diff comments:
In `@src/speculators/models/dflash/core.py`:
- Around line 158-163: The max_anchors defaulting in DFlashSpeculatorConfig is
using a falsy check, so an explicit 0 gets replaced with 3072 instead of being
preserved or handled consistently. Update the logic in the DFlash config निर्माण
path to default only when kwargs.get("max_anchors") is None, matching the
behavior of the related config handling and keeping 0 distinct from a missing
value.
---
Nitpick comments:
In `@tests/unit/models/test_peagle_data.py`:
- Around line 26-38: The max_anchors test only checks depth 1, but the contract
covers depth-1+ chains; update test_max_anchors_caps_chains in
test_peagle_data.py to explicitly verify that depth 2 and deeper counts are also
bounded by the capped depth-1 pool, using generate_cod_sample_indices and the
depth tensor. Keep anchor_pos unused as-is and add assertions for the deeper
depths so the chain-cap behavior is tested end-to-end.
- Around line 49-68: Remove the unnecessary torch.manual_seed calls from
test_max_anchors_none_uses_all in test_peagle_data.py, since
generate_cod_sample_indices with max_anchors=None follows a deterministic path
and does not use random sampling. Keep the assertions comparing
anchor_pos_none/depth_none to anchor_pos_default/depth_default, and leave the
test focused on verifying the default behavior of generate_cod_sample_indices
without implying stochasticity.
🪄 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: 20db3352-98f6-47bb-98dd-5f9e6428e227
📒 Files selected for processing (6)
scripts/train.pysrc/speculators/models/dflash/core.pysrc/speculators/models/peagle/config.pysrc/speculators/models/peagle/core.pysrc/speculators/models/peagle/data.pytests/unit/models/test_peagle_data.py
|
This pull request has merge conflicts that must be resolved before it can be |
e4062e1 to
8b555a3
Compare
|
The quality checks have failed. Please run |
081ed57 to
5f06f8d
Compare
5f06f8d to
31ab0b8
Compare
Randomly subsamples COD chain starting points when max_anchors is set, reducing depth-1+ token count while keeping depth-0 as the full sequence. This bounds VRAM for long-context training without degrading d0 accuracy. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Orestis Zambounis <orestis.zambounis@gmail.com>
31ab0b8 to
7a3d9cc
Compare
fynnsu
left a comment
There was a problem hiding this comment.
Looks good, but please update the max_anchor piping to not use the model's config.
- Remove max_anchors from PEagleSpeculatorConfig (training hyperparam, not model config); pipe through get_trainer_kwargs instead - Set --max-anchors CLI default to 3072 for both algorithms - Fix DFlash falsy check to use `is None` for max_anchors defaulting - Strengthen test assertions: check all depths, verify no-cap equality Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Orestis Zambounis <orestis.zambounis@gmail.com>
|
The quality checks have failed. Please run |
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Orestis Zambounis <orestis.zambounis@gmail.com>
3d041bc to
0d92463
Compare
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Orestis Zambounis <orestis.zambounis@gmail.com>
shanjiaz
left a comment
There was a problem hiding this comment.
Thanks for the detailed ablation. Glad this works out!
fynnsu
left a comment
There was a problem hiding this comment.
Looks good! One optional nit
Move max_anchors from **kwargs to an explicit keyword argument in PEagleDraftModel.forward() for clarity. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Orestis Zambounis <orestis.zambounis@gmail.com>
max_anchors is a training hyperparameter, not a model architecture parameter, so it should not be persisted in the model config. This follows the same pattern applied to peagle in PR #687. - Remove max_anchors field from DFlashSpeculatorConfig - Pass max_anchors through get_trainer_kwargs → forward for both DFlash and DSpark - Update tests to pass max_anchors as a forward kwarg Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Orestis Zambounis <orestis.zambounis@gmail.com>
max_anchors is a training hyperparameter, not a model architecture parameter, so it should not be persisted in the model config. This follows the same pattern applied to peagle in PR #687. - Remove max_anchors field from DFlashSpeculatorConfig - Pass max_anchors through get_trainer_kwargs → forward for both DFlash and DSpark - Update tests to pass max_anchors as a forward kwarg Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Orestis Zambounis <orestis.zambounis@gmail.com>
max_anchors is a training hyperparameter, not a model architecture parameter, so it should not be persisted in the model config. This follows the same pattern applied to peagle in PR #687. - Remove max_anchors field from DFlashSpeculatorConfig - Pass max_anchors through get_trainer_kwargs → forward for both DFlash and DSpark - Update tests to pass max_anchors as a forward kwarg Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Orestis Zambounis <orestis.zambounis@gmail.com>
max_anchors is a training hyperparameter, not a model architecture parameter, so it should not be persisted in the model config. This follows the same pattern applied to peagle in PR #687. - Remove max_anchors field from DFlashSpeculatorConfig - Pass max_anchors through get_trainer_kwargs → forward for both DFlash and DSpark - Update tests to pass max_anchors as a forward kwarg Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Orestis Zambounis <orestis.zambounis@gmail.com>
… kwargs (#707) ## Summary Follow-up to #687 as discussed in [review comments](#687 (comment)): Move training-only hyperparameters out of model configs into `get_trainer_kwargs` → `forward()`. These fields don't affect model architecture or inference behavior and shouldn't be persisted in `config.json`. **DFlash/DSpark:** - `max_anchors` — anchor sampling count, training-only - `sliding_window_non_causal` — training attention mask flag (vLLM uses its own attention path) **PEagle:** - `num_depths`, `down_sample_ratio`, `down_sample_ratio_min` — COD sampling params, training-only - Inference-time speculation count remains in `SpeculatorsConfig.proposal_methods[0].speculative_tokens` Not breaking: `SpeculatorModelConfig` uses `extra="allow"`, so old checkpoints with these fields load fine. ## Test plan - [ ] `pytest tests/integration/models/test_model_forward.py -k dflash` — DFlash param tests - [ ] `pytest tests/integration/models/test_model_forward.py -k peagle` — PEagle param tests - [ ] `pytest tests/integration/models/test_model_forward.py` — all model forward tests (no regressions) - [ ] Existing checkpoints load without error (removed fields silently accepted by `extra="allow"`) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Orestis Zambounis <orestis.zambounis@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…ct#687) Closes vllm-project#567. ## Summary - Add `max_anchors` config to P-EAGLE's COD sampling that randomly subsamples chain starting points at depth 1+, capping attention mask size for longer sequences while keeping depth 0 as the full sequence - Change `--max-anchors` CLI default from 256 to None so P-EAGLE uses all positions by default (DFlash retains its 3072 hardcoded fallback) - Add unit tests verifying depth-0 preservation, chain capping, sorted order, and backward compatibility ## Motivation P-EAGLE's COD sampling starts a chain from every `loss_mask=1` position, producing attention masks that OOM at 8K+ sequences. The ablation study (vllm-project#567 (comment)) showed that randomly subsampling with `max_anchors=1024` actually **improves** d0 accuracy by +3.1pp while dramatically reducing memory, enabling training at longer sequence lengths. Supersedes vllm-project#590 (contiguous window approach, which degraded quality) and vllm-project#683. ## Test plan - [x] 6 new unit tests in `tests/unit/models/test_peagle_data.py` — all pass - [x] 38 existing P-EAGLE integration tests — all pass - [x] `ruff check` clean on all changed files - [ ] CI green 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Orestis Zambounis <orestis.zambounis@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
… kwargs (vllm-project#707) Follow-up to vllm-project#687 as discussed in [review comments](vllm-project#687 (comment)): Move training-only hyperparameters out of model configs into `get_trainer_kwargs` → `forward()`. These fields don't affect model architecture or inference behavior and shouldn't be persisted in `config.json`. **DFlash/DSpark:** - `max_anchors` — anchor sampling count, training-only - `sliding_window_non_causal` — training attention mask flag (vLLM uses its own attention path) **PEagle:** - `num_depths`, `down_sample_ratio`, `down_sample_ratio_min` — COD sampling params, training-only - Inference-time speculation count remains in `SpeculatorsConfig.proposal_methods[0].speculative_tokens` Not breaking: `SpeculatorModelConfig` uses `extra="allow"`, so old checkpoints with these fields load fine. - [ ] `pytest tests/integration/models/test_model_forward.py -k dflash` — DFlash param tests - [ ] `pytest tests/integration/models/test_model_forward.py -k peagle` — PEagle param tests - [ ] `pytest tests/integration/models/test_model_forward.py` — all model forward tests (no regressions) - [ ] Existing checkpoints load without error (removed fields silently accepted by `extra="allow"`) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Orestis Zambounis <orestis.zambounis@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
…ct#687) Closes vllm-project#567. ## Summary - Add `max_anchors` config to P-EAGLE's COD sampling that randomly subsamples chain starting points at depth 1+, capping attention mask size for longer sequences while keeping depth 0 as the full sequence - Change `--max-anchors` CLI default from 256 to None so P-EAGLE uses all positions by default (DFlash retains its 3072 hardcoded fallback) - Add unit tests verifying depth-0 preservation, chain capping, sorted order, and backward compatibility ## Motivation P-EAGLE's COD sampling starts a chain from every `loss_mask=1` position, producing attention masks that OOM at 8K+ sequences. The ablation study (vllm-project#567 (comment)) showed that randomly subsampling with `max_anchors=1024` actually **improves** d0 accuracy by +3.1pp while dramatically reducing memory, enabling training at longer sequence lengths. Supersedes vllm-project#590 (contiguous window approach, which degraded quality) and vllm-project#683. ## Test plan - [x] 6 new unit tests in `tests/unit/models/test_peagle_data.py` — all pass - [x] 38 existing P-EAGLE integration tests — all pass - [x] `ruff check` clean on all changed files - [ ] CI green 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Orestis Zambounis <orestis.zambounis@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Eros483 <arnabmandal2912@gmail.com>
… kwargs (vllm-project#707) Follow-up to vllm-project#687 as discussed in [review comments](vllm-project#687 (comment)): Move training-only hyperparameters out of model configs into `get_trainer_kwargs` → `forward()`. These fields don't affect model architecture or inference behavior and shouldn't be persisted in `config.json`. **DFlash/DSpark:** - `max_anchors` — anchor sampling count, training-only - `sliding_window_non_causal` — training attention mask flag (vLLM uses its own attention path) **PEagle:** - `num_depths`, `down_sample_ratio`, `down_sample_ratio_min` — COD sampling params, training-only - Inference-time speculation count remains in `SpeculatorsConfig.proposal_methods[0].speculative_tokens` Not breaking: `SpeculatorModelConfig` uses `extra="allow"`, so old checkpoints with these fields load fine. - [ ] `pytest tests/integration/models/test_model_forward.py -k dflash` — DFlash param tests - [ ] `pytest tests/integration/models/test_model_forward.py -k peagle` — PEagle param tests - [ ] `pytest tests/integration/models/test_model_forward.py` — all model forward tests (no regressions) - [ ] Existing checkpoints load without error (removed fields silently accepted by `extra="allow"`) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Orestis Zambounis <orestis.zambounis@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Eros483 <arnabmandal2912@gmail.com>
… kwargs (vllm-project#707) Follow-up to vllm-project#687 as discussed in [review comments](vllm-project#687 (comment)): Move training-only hyperparameters out of model configs into `get_trainer_kwargs` → `forward()`. These fields don't affect model architecture or inference behavior and shouldn't be persisted in `config.json`. **DFlash/DSpark:** - `max_anchors` — anchor sampling count, training-only - `sliding_window_non_causal` — training attention mask flag (vLLM uses its own attention path) **PEagle:** - `num_depths`, `down_sample_ratio`, `down_sample_ratio_min` — COD sampling params, training-only - Inference-time speculation count remains in `SpeculatorsConfig.proposal_methods[0].speculative_tokens` Not breaking: `SpeculatorModelConfig` uses `extra="allow"`, so old checkpoints with these fields load fine. - [ ] `pytest tests/integration/models/test_model_forward.py -k dflash` — DFlash param tests - [ ] `pytest tests/integration/models/test_model_forward.py -k peagle` — PEagle param tests - [ ] `pytest tests/integration/models/test_model_forward.py` — all model forward tests (no regressions) - [ ] Existing checkpoints load without error (removed fields silently accepted by `extra="allow"`) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Orestis Zambounis <orestis.zambounis@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Eros483 <arnabmandal2912@gmail.com>
… kwargs (vllm-project#707) Follow-up to vllm-project#687 as discussed in [review comments](vllm-project#687 (comment)): Move training-only hyperparameters out of model configs into `get_trainer_kwargs` → `forward()`. These fields don't affect model architecture or inference behavior and shouldn't be persisted in `config.json`. **DFlash/DSpark:** - `max_anchors` — anchor sampling count, training-only - `sliding_window_non_causal` — training attention mask flag (vLLM uses its own attention path) **PEagle:** - `num_depths`, `down_sample_ratio`, `down_sample_ratio_min` — COD sampling params, training-only - Inference-time speculation count remains in `SpeculatorsConfig.proposal_methods[0].speculative_tokens` Not breaking: `SpeculatorModelConfig` uses `extra="allow"`, so old checkpoints with these fields load fine. - [ ] `pytest tests/integration/models/test_model_forward.py -k dflash` — DFlash param tests - [ ] `pytest tests/integration/models/test_model_forward.py -k peagle` — PEagle param tests - [ ] `pytest tests/integration/models/test_model_forward.py` — all model forward tests (no regressions) - [ ] Existing checkpoints load without error (removed fields silently accepted by `extra="allow"`) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Orestis Zambounis <orestis.zambounis@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Eros483 <arnabmandal2912@gmail.com>
… kwargs (vllm-project#707) Follow-up to vllm-project#687 as discussed in [review comments](vllm-project#687 (comment)): Move training-only hyperparameters out of model configs into `get_trainer_kwargs` → `forward()`. These fields don't affect model architecture or inference behavior and shouldn't be persisted in `config.json`. **DFlash/DSpark:** - `max_anchors` — anchor sampling count, training-only - `sliding_window_non_causal` — training attention mask flag (vLLM uses its own attention path) **PEagle:** - `num_depths`, `down_sample_ratio`, `down_sample_ratio_min` — COD sampling params, training-only - Inference-time speculation count remains in `SpeculatorsConfig.proposal_methods[0].speculative_tokens` Not breaking: `SpeculatorModelConfig` uses `extra="allow"`, so old checkpoints with these fields load fine. - [ ] `pytest tests/integration/models/test_model_forward.py -k dflash` — DFlash param tests - [ ] `pytest tests/integration/models/test_model_forward.py -k peagle` — PEagle param tests - [ ] `pytest tests/integration/models/test_model_forward.py` — all model forward tests (no regressions) - [ ] Existing checkpoints load without error (removed fields silently accepted by `extra="allow"`) 🤖 Generated with [Claude Code](https://claude.com/claude-code) --------- Signed-off-by: Orestis Zambounis <orestis.zambounis@gmail.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com> Signed-off-by: Eros483 <arnabmandal2912@gmail.com>
Closes #567.
Summary
max_anchorsconfig to P-EAGLE's COD sampling that randomly subsamples chain starting points at depth 1+, capping attention mask size for longer sequences while keeping depth 0 as the full sequence--max-anchorsCLI default from 256 to None so P-EAGLE uses all positions by default (DFlash retains its 3072 hardcoded fallback)Motivation
P-EAGLE's COD sampling starts a chain from every
loss_mask=1position, producing attention masks that OOM at 8K+ sequences. The ablation study (#567 (comment)) showed that randomly subsampling withmax_anchors=1024actually improves d0 accuracy by +3.1pp while dramatically reducing memory, enabling training at longer sequence lengths.Supersedes #590 (contiguous window approach, which degraded quality) and #683.
Test plan
tests/unit/models/test_peagle_data.py— all passruff checkclean on all changed files🤖 Generated with Claude Code