Files
835c041c58 fix(specdec): resolve the eagle aux-layer preset in the vLLM hidden-state dump (#2410)
### What does this PR do?

Type of change: Bug fix

Fixes `nvbugs/6753684`, filed against #2080 by the ModelOpt QA Sentinel.

The vLLM offline hidden-state dump rejected `--aux-layers eagle` — **the
flag's own default** — so the documented invocation aborted before
writing any state:

```
File "collect_hidden_states/compute_hidden_states_vllm.py", line 76, in _resolve_aux_layers_standalone
    ids = sorted({int(t) for t in aux_layers.split(',') if t.strip()})
ValueError: invalid literal for int() with base 10: 'eagle'
```

**Root cause.** `compute_hidden_states_vllm.py` runs in a stock vLLM
container, where importing `modelopt.torch` fails (the full init chain
pulls in omegaconf and friends). It therefore carries
`_resolve_aux_layers_standalone`, a local copy of the preset logic in
`common.resolve_aux_layers`. That copy implemented the `dflash` preset
and explicit id lists, but never `eagle` — while `add_aux_layers_args`
defaults to `eagle`. The HF and TRT-LLM dumps call the shared helper and
were unaffected; only the vLLM path forked, and nothing compared the
fork against its source.

This PR resolves `eagle` inline, mirroring
`hf_eagle.default_eagle_aux_layer_ids`.

It also fixes a second defect the bug exposes: the function already had
a message naming the accepted values, but it was unreachable, because
`int()` raised first. An unrecognised preset now reports what it accepts
instead of surfacing the raw `int()` error — which is what made the
original failure opaque.

### Usage

The previously-broken documented invocation now works:

```bash
cd examples/speculative_decoding
python collect_hidden_states/compute_hidden_states_vllm.py \
    --model Qwen/Qwen2.5-0.5B-Instruct \
    --input-data ../dataset/synthetic_conversations_1k.jsonl \
    --output-dir /tmp/hs_vllm \
    --max-seq-len 512 --tp 1
```

`--aux-layers dflash` and explicit lists such as `--aux-layers 2,5,8`
are unchanged.

### Testing

Added `tests/unit/examples/test_vllm_hidden_states_aux_layers.py`, which
pins the standalone copy to the shared implementation it mirrors:

- `eagle` matches `hf_eagle.default_eagle_aux_layer_ids` across layer
counts 4, 6, 8, 12, 24, 28, 32, 36, 48, 52, 61, 80 — deliberately
including counts small enough that the `max(0, ...)` clamps collapse ids
together.
- A named regression case for `nvbugs/6753684`.
- `dflash` and explicit-list behaviour unchanged.
- Unknown specs (`bogus`, `EAGLE3`, `eagle3`, empty) raise the
actionable message.
- Out-of-range ids still rejected.

Divergence here is silent — the dump would write plausible-looking
hidden states from the *wrong* layers, surfacing much later as a poor
acceptance rate. Hence pinning to the reference rather than asserting
hardcoded lists alone.

All 20 assertions verified and every pre-commit hook passes (`ruff`,
`mypy`, `bandit`, RST lint, license headers).

One caveat worth stating plainly: **pytest could not be run locally.**
`tests/unit/conftest.py` imports `modelopt.torch.utils.distributed`,
which needs `CPUOffloadPolicy` from `torch.distributed.fsdp` — absent in
this machine's torch. Each assertion was executed directly against the
real module instead, but CI is the first genuine pytest run.

### Before your PR is "*Ready for review*"

- Is this change backward compatible?: ✅ — strictly widens accepted
input; `dflash` and explicit lists behave identically.
- If you copied code from any other sources or added a new PIP
dependency, did you follow guidance in `CONTRIBUTING.md`: N/A
- Did you write any new necessary tests?: ✅
- Did you update
[Changelog](https://github.com/NVIDIA/Model-Optimizer/blob/main/CHANGELOG.rst)?:
✅ — bug fix for a defect present in a previous release.
- Did you get Claude approval on this PR?: ❌ — not yet run.

### Additional Information

The underlying fragility is the duplicated implementation, not this one
missing branch. The function's own `TODO: drop this once
common.resolve_aux_layers is decoupled from the heavy modelopt.torch
import chain` is the real fix; the new test narrows the gap but does not
close it. Worth tracking separately if the vLLM dump is expected to keep
pace with new presets.

🤖 Generated with [Claude Code](https://claude.com/claude-code)


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **Bug Fixes**
  * Fixed `--aux-layers eagle` for vLLM offline hidden-state collection.
* Added support for the documented `eagle` preset alongside `dflash` and
explicit layer IDs.
  * Improved invalid-option errors to clearly list accepted formats.
* Rejects `dflash` configurations when the target model has too few
layers.
  * Continues rejecting layer IDs outside the model’s available range.
* **Documentation**
  * Added a v0.48.0 changelog entry for the fix.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->

---------

Signed-off-by: Ye Yu <yeyu@nvidia.com>
Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
Co-authored-by: Keval Morabia <28916987+kevalmorabia97@users.noreply.github.com>
2026-09-17 17:54:30 +00:00
..