Skip to content

[Core] Add is_eagle in AttentionSpec - #41640

Closed
gcanlin wants to merge 2 commits into
vllm-project:mainfrom
gcanlin:is_eagle
Closed

gcanlin wants to merge 2 commits into
vllm-project:mainfrom
gcanlin:is_eagle

Conversation

@gcanlin

@gcanlin gcanlin commented May 4, 2026 •

Copy link
Copy Markdown
Contributor

Purpose

Follow #39690.

Currently, _annotate_eagle_groups_deepseek_v4 is model-specific method. Other models would all go into the fallback logic inside. This PR is introducing is_eagle in AttentionSpec to make _annotate_eagle_groups general for all models.

Test Plan

Gemma4

vllm serve google/gemma-4-31b-it \
  --tensor-parallel-size 2 \
  --speculative-config '{
    "model": "RedHatAI/gemma-4-31B-it-speculator.eagle3",
    "num_speculative_tokens": 3,
    "method": "eagle3"
  }' \
  --max-num-seqs 64 \
  --enable-prefix-caching
vllm bench serve \
  --host 127.0.0.1 --port 8000 \
  --model google/gemma-4-31b-it \
  --dataset-name prefix_repetition \
  --prefix-repetition-prefix-len 512 \
  --prefix-repetition-suffix-len 256 \
  --prefix-repetition-num-prefixes 8 \
  --prefix-repetition-output-len 128 \
  --request-rate 8 \
  --num-prompts 256 \
  --save-result \
  --result-dir ./bench_results_with_prefix_cache \
  --result-filename result.json

Test Result

(APIServer pid=1447322) INFO:     127.0.0.1:58752 - "POST /v1/completions HTTP/1.1" 200 OK
(APIServer pid=1447322) INFO:     127.0.0.1:58754 - "POST /v1/completions HTTP/1.1" 200 OK
(APIServer pid=1447322) INFO 05-04 15:29:49 [loggers.py:271] Engine 000: Avg prompt throughput: 2975.4 tokens/s, Avg generation throughput: 867.4 tokens/s, Running: 64 reqs, Waiting: 79 reqs, GPU KV cache usage: 35.1%, Prefix cache hit rate: 53.1%
(APIServer pid=1447322) INFO 05-04 15:29:49 [metrics.py:101] SpecDecoding metrics: Mean acceptance length: 1.30, Accepted throughput: 196.87 tokens/s, Drafted throughput: 1994.65 tokens/s, Accepted: 1969 tokens, Drafted: 19950 tokens, Per-position acceptance rate: 0.136, 0.089, 0.071, Avg Draft acceptance rate: 9.9%

...

(APIServer pid=1447322) INFO 05-04 15:29:59 [loggers.py:271] Engine 000: Avg prompt throughput: 2306.9 tokens/s, Avg generation throughput: 709.2 tokens/s, Running: 64 reqs, Waiting: 79 reqs, GPU KV cache usage: 32.4%, Prefix cache hit rate: 55.4%
(APIServer pid=1447322) INFO 05-04 15:29:59 [metrics.py:101] SpecDecoding metrics: Mean acceptance length: 1.33, Accepted throughput: 175.37 tokens/s, Drafted throughput: 1588.24 tokens/s, Accepted: 1754 tokens, Drafted: 15885 tokens, Per-position acceptance rate: 0.147, 0.102, 0.083, Avg Draft acceptance rate: 11.0%
(APIServer pid=1447322) INFO:     127.0.0.1:58620 - "POST /v1/completions HTTP/1.1" 200 OK
(APIServer pid=1447322) INFO:     127.0.0.1:58448 - "POST /v1/completions HTTP/1.1" 200 OK
(APIServer pid=1447322) INFO:     127.0.0.1:58622 - "POST /v1/completions HTTP/1.1" 200 OK
(APIServer pid=1447322) INFO:     127.0.0.1:58626 - "POST /v1/completions HTTP/1.1" 200 OK
(APIServer pid=1447322) INFO:     127.0.0.1:58724 - "POST /v1/completions HTTP/1.1" 200 OK
(APIServer pid=1447322) INFO:     127.0.0.1:58632 - "POST /v1/completions HTTP/1.1" 200 OK
(APIServer pid=1447322) INFO 05-04 15:30:09 [loggers.py:271] Engine 000: Avg prompt throughput: 1631.7 tokens/s, Avg generation throughput: 763.2 tokens/s, Running: 64 reqs, Waiting: 43 reqs, GPU KV cache usage: 33.6%, Prefix cache hit rate: 56.1%
(APIServer pid=1447322) INFO 05-04 15:30:09 [metrics.py:101] SpecDecoding metrics: Mean acceptance length: 1.22, Accepted throughput: 136.88 tokens/s, Drafted throughput: 1869.95 tokens/s, Accepted: 1369 tokens, Drafted: 18702 tokens, Per-position acceptance rate: 0.104, 0.066, 0.050, Avg Draft acceptance rate: 7.3%
(APIServer pid=1447322) INFO 05-04 15:30:19 [loggers.py:271] Engine 000: Avg prompt throughput: 1407.2 tokens/s, Avg generation throughput: 745.9 tokens/s, Running: 41 reqs, Waiting: 0 reqs, GPU KV cache usage: 23.7%, Prefix cache hit rate: 56.5%
(APIServer pid=1447322) INFO 05-04 15:30:19 [metrics.py:101] SpecDecoding metrics: Mean acceptance length: 1.16, Accepted throughput: 103.45 tokens/s, Drafted throughput: 1921.20 tokens/s, Accepted: 1035 tokens, Drafted: 19221 tokens, Per-position acceptance rate: 0.084, 0.044, 0.033, Avg Draft acceptance rate: 5.4%
(APIServer pid=1447322) INFO:     127.0.0.1:58768 - "GET /metrics HTTP/1.1" 200 OK
(APIServer pid=1447322) INFO 05-04 15:30:29 [loggers.py:271] Engine 000: Avg prompt throughput: 0.0 tokens/s, Avg generation throughput: 180.5 tokens/s, Running: 0 reqs, Waiting: 0 reqs, GPU KV cache usage: 0.0%, Prefix cache hit rate: 56.5%
(APIServer pid=1447322) INFO 05-04 15:30:29 [metrics.py:101] SpecDecoding metrics: Mean acceptance length: 1.06, Accepted throughput: 9.50 tokens/s, Drafted throughput: 514.42 tokens/s, Accepted: 95 tokens, Drafted: 5145 tokens, Per-position acceptance rate: 0.029, 0.015, 0.011, Avg Draft acceptance rate: 1.8%
 python -m pytest tests/v1/core/test_prefix_caching.py
========================================================== test session starts ==========================================================
platform linux -- Python 3.12.3, pytest-9.0.3, pluggy-1.6.0
rootdir: /root/vllm-workspace/vllm
configfile: pyproject.toml
plugins: anyio-4.13.0
collected 58 items

tests/v1/core/test_prefix_caching.py ..........................................................                                   [100%]

=========================================================== warnings summary ============================================================
<frozen importlib._bootstrap>:488
  <frozen importlib._bootstrap>:488: DeprecationWarning: builtin type SwigPyPacked has no __module__ attribute

<frozen importlib._bootstrap>:488
  <frozen importlib._bootstrap>:488: DeprecationWarning: builtin type SwigPyObject has no __module__ attribute

tests/v1/core/test_prefix_caching.py: 14 warnings
  /root/vllm-workspace/.venv/lib/python3.12/site-packages/torch/jit/_script.py:365: DeprecationWarning: `torch.jit.script_method` is deprecated. Please switch to `torch.compile` or `torch.export`.
    warnings.warn(

-- Docs: https://docs.pytest.org/en/stable/how-to/capture-warnings.html
=================================================== 58 passed, 16 warnings in 11.26s ====================================================
sys:1: DeprecationWarning: builtin type swigvarlink has no __module__ attribute

Essential Elements of an Effective PR Description Checklist
  • The purpose of the PR, such as "Fix some issue (link existing issues this PR will resolve)".
  • The test plan, such as providing test command.
  • The test results, such as pasting the results comparison before and after, or e2e results
  • (Optional) The necessary documentation update, such as updating supported_models.md and examples for a new model.

Signed-off-by: gcanlin <canlinguosdu@gmail.com>

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

@mergify mergify Bot added deepseek Related to DeepSeek models v1 labels May 4, 2026

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request generalizes the identification of EAGLE and MTP draft attention layers by introducing an is_eagle flag in the attention specification. This replaces model-specific heuristics in the KV cache grouping logic, specifically for DeepSeek-V4, with a more robust mechanism that propagates the flag from the model layers to the KVCacheCoordinator. I have no feedback to provide as there were no review comments to assess.

@gcanlin

gcanlin commented May 4, 2026

Copy link
Copy Markdown
Contributor Author

@ivanium @heheda12345 Would you mind taking a look? Thanks!

Comment thread vllm/v1/core/kv_cache_utils.py Outdated


def _annotate_eagle_groups_deepseek_v4(
def _annotate_eagle_groups(

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.

Ideally we can annotate kv_cache_group.is_eagle when building KVCacheGroupSpec and avoid this function. Would that be too intrusive?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I think it would be a bit intrusive. And it also introduced some duplicate code to judge is_eagle_group. get_kv_cache_groups playing a role as orchestrator to do it once looks reasonable. I created a commit 14d5684 here.

@gcanlin gcanlin May 5, 2026 •

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

We can simplify them to 6 lines inline code in get_kv_cache_groups.

    spec_config = vllm_config.speculative_config
    if spec_config is not None and spec_config.use_eagle():
        eagle_layer_names = {n for n, s in kv_cache_spec.items() if s.is_eagle}
        for group in kv_cache_groups:
            if eagle_layer_names & set(group.layer_names):
                group.is_eagle_group = True

@ivanium

ivanium commented May 4, 2026

Copy link
Copy Markdown
Contributor

Thanks for the effort! I left one comment, and some more general ones:

  1. Why not put is_eagle into KVCacheSpec?
  2. Will be good to run test_prefix_caching.py too

Signed-off-by: gcanlin <canlinguosdu@gmail.com>
@gcanlin

gcanlin commented May 5, 2026 •

Copy link
Copy Markdown
Contributor Author

Thanks for the effort! I left one comment, and some more general ones:

  1. Why not put is_eagle into KVCacheSpec?
  2. Will be good to run test_prefix_caching.py too

Thanks for reviewing! I have moved is_eagle into KVCacheSpec to make it more general now. And ran test_prefix_caching.py and all passed on my side.(Test results have been pasted in the PR description)


return [KVCacheGroupSpec(list(spec.kv_cache_specs.keys()), spec)]
layer_names = list(spec.kv_cache_specs.keys())
is_eagle_group = bool(eagle_layer_names and eagle_layer_names & set(layer_names))

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 prefer a more intuitive code like this:

Suggested change
is_eagle_group = bool(eagle_layer_names and eagle_layer_names & set(layer_names))
is_eagle_group = bool(eagle_layer_names) and bool(
eagle_layer_names.intersection(current_group_layer_names)
)

full_mla_group = KVCacheGroupSpec(
layer_names=list(full_mla_spec.kv_cache_specs.keys()),
kv_cache_spec=full_mla_spec,
is_eagle_group=bool(

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.

ditto.

layer_names=group_layer_names,
kv_cache_spec=sub_sm_spec,
is_eagle_group=bool(
eagle_layer_names and eagle_layer_names & set(group_layer_names)

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.

ditto. and let's extract a function for determining whether it is a eagle group.

# time instead of through a separate post-hoc pass.
spec_config = vllm_config.speculative_config
if spec_config is not None and spec_config.use_eagle():
eagle_layer_names = {n for n, s in kv_cache_spec.items() if s.is_eagle}

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.

Let's clarify the meaning of the name more clearly.

Suggested change
eagle_layer_names = {n for n, s in kv_cache_spec.items() if s.is_eagle}
eagle_layer_names = {layer_name for layer_name, spec in kv_cache_spec.items() if spec.is_eagle}

cache_dtype_str=self.cache_config.cache_dtype,
alignment=576, # NOTE: FlashMLA requires 576B alignment
model_version="deepseek_v4",
is_eagle=self.is_eagle,

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.

ditto

dtype=torch.uint8,
prefix=f"{prefix}.swa_cache",
cache_config=cache_config,
is_eagle=is_eagle,

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.

Instead of passing by is_eagle through a long link, we can get the info about whether it is eagle layer in this way:

is_eagle= True if extract_layer_index(prefix) > vllm_config.model_config.hf_config.num_hidden_layers else False

@mergify

mergify Bot commented May 23, 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, @gcanlin.

https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/working-with-forks/syncing-a-fork

@github-actions

Copy link
Copy Markdown

This pull request has been automatically marked as stale because it has not had any activity within 90 days. It will be automatically closed if no further activity occurs within 30 days. Leave a comment if you feel this pull request should remain open. Thank you!

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

Labels

deepseek Related to DeepSeek models needs-rebase stale Over 90 days of inactivity v1

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants