[Feat] Add native Mooncake KV transfer from AR to DiT (#7166)

Signed-off-by: asukaqaq-s <1311722138@qq.com>
Signed-off-by: Acerak01-fy <wfy2003324@163.com>
Co-authored-by: Acerak01-fy <wfy2003324@163.com>
Co-authored-by: amy-why-3459 <wuhaiyan17@huawei.com>
This commit is contained in:
authored and GitHub committed 2026-09-20 14:44:55 +00:00
1 parent 2d0b03f717
commit 102ba716bf
53 files changed
+2564 -204

No files matched your search

@@ -1,5 +1,5 @@
# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
from types import SimpleNamespace
@@ -362,6 +362,20 @@ def test_prepared_model_inputs_match_local_tokenization(
local_inputs = pipeline.prepare_model_inputs(**common_kwargs)
prepared_inputs = pipeline.prepare_model_inputs(**common_kwargs, prepared_layout=prepared_layout)
if with_reference_image:
# Preparation runs without a forward context. Coverage must come from
# the scheduler metadata copied onto this request, including every CFG row.
reused = pipeline.prepare_model_inputs(
**common_kwargs, prepared_layout=prepared_layout, kv_computed_tokens=(5,) * len(prefix_lens)
)
assert reused["cond_vae_images"] is None and reused["cond_vit_images"] is None
partial = pipeline.prepare_model_inputs(
**common_kwargs,
prepared_layout=prepared_layout,
kv_computed_tokens=(5,) * (len(prefix_lens) - 1) + (4,),
)
assert partial["cond_vae_images"] is not None and partial["cond_vit_images"] is not None
comparable_fields = (
"input_ids",
"position_ids",
@@ -474,3 +488,33 @@ def test_paged_preprocess_attaches_layout_and_scheduler_kv_requests(monkeypatch)
(12, 17, 32)
]
assert len(tokenizer.calls) == 1
@pytest.mark.parametrize("transfer_tokens, expected", [(9, [7, 5]), (4, [4, 4])])
def test_cfg_rows_preserve_reusable_token_ids_and_native_transfer_boundary(transfer_tokens, expected):
from vllm_omni.diffusion.diffusion_kv.kv_connector import prepare_kv_requests
tokenizer, image_processor = _components([12, 14])
request = _request(guidance_scale=5.0)
layout = _prepare(request, tokenizer, image_processor)
layout.tokenizer_output.think_recaption_end_pos = [[7], [5]]
layout.tokenizer_output.uncond_cfg_start_pos = [[None], [5]]
rows = build_hunyuan_diffusion_kv_requests(request, layout)
assert [row.prompt_token_ids for row in rows] == [
layout.tokenizer_output.tokens[0, :7].tolist(),
layout.tokenizer_output.tokens[1, :5].tolist(),
]
prepare_kv_requests(rows, {"num_transfer_tokens": transfer_tokens})
assert [row.num_prompt_tokens for row in rows] == expected
assert all(row.kv_transfer_params["num_transfer_tokens"] == transfer_tokens for row in rows)
layout.tokenizer_output.uncond_cfg_start_pos = [[5]]
with pytest.raises(ValueError, match="one boundary per CFG row"):
build_hunyuan_diffusion_kv_requests(request, layout)
@pytest.mark.parametrize("computed, expected", [((8, 8), True), ((8, 5), False), ((0, 0), False), ((), False)])
def test_native_image_reuse_requires_coverage_in_every_cfg_row(computed, expected):
from vllm_omni.diffusion.models.hunyuan_image3.request_layout import native_kv_covers_cond_images
output = TokenizerEncodeOutput(joint_image_slices=[[slice(2, 8)], [slice(2, 6)]])
assert native_kv_covers_cond_images(output, computed) is expected
@@ -9,6 +9,7 @@ from __future__ import annotations
import math
from contextlib import ExitStack, contextmanager
from types import SimpleNamespace
from unittest.mock import patch
import pytest
@@ -143,6 +144,104 @@ def test_cache_manager_registers_attention_without_adding_dense_state() -> None:
assert mgr.state_dict() == {}
def test_cache_manager_initializes_with_quantization_skip_layers(monkeypatch):
import vllm_omni.diffusion.attention.layer as layer
from vllm_omni.diffusion.config import set_current_diffusion_config
from vllm_omni.diffusion.models.hunyuan_image3.hunyuan_image3_transformer import ImageKVCacheManager
config = SimpleNamespace(
diffusion_attention_config=None,
parallel_config=SimpleNamespace(ring_degree=1),
diffusion_kv_cache_dtype="auto",
diffusion_kv_cache_skip_layer_indices={3},
)
monkeypatch.setattr(layer, "get_attn_backend_for_role", lambda **_: (layer.SDPABackend, None))
monkeypatch.setattr(layer, "build_parallel_attention_strategy", lambda **_: object())
with patched_mgr_env(), set_current_diffusion_config(config):
# Keep the real Attention constructor and its skip-layer validation.
with patch(f"{_TRANSFORMER_MODULE}.Attention", layer.Attention):
mgr = ImageKVCacheManager(
num_heads=NUM_HEADS,
num_kv_heads=NUM_KV_HEADS,
head_dim=HEAD_DIM,
scaling=SCALING,
prefix="model.layers.3.self_attn",
)
assert mgr.attn.layer_idx == 3
assert not mgr.attn._should_apply_kv_cache_quant()
@pytest.mark.parametrize("first_step", [True, False])
def test_native_connector_keeps_imported_prefix_in_paged_attention(first_step) -> None:
from vllm_omni.diffusion.forward_context import override_paged_kv_adapter, set_forward_context
mgr = _make_cache_mgr()
mgr.attn.paged_kv_active = True
query_len, prefix_len = IMAGE_TOKEN_LEN, 19
key, value = _make_known_kv(2 * query_len)
spans = [[(prefix_len, prefix_len + query_len)]] * 2
with (
set_forward_context(),
override_paged_kv_adapter(object()),
patch.object(mgr, "_forward_dense_legacy", side_effect=AssertionError("dense KV path")),
patch(f"{_TRANSFORMER_MODULE}.repeat_kv", side_effect=AssertionError("KV head expansion")),
patch(f"{_TRANSFORMER_MODULE}.get_paged_kv_computed_tokens", return_value=(prefix_len, prefix_len)),
):
_call_mgr(
mgr,
2,
query_len,
prefix_len + query_len,
key,
value,
first_step=first_step,
full_attn_spans=spans,
)
assert len(mgr.attn.paged_calls) == 1
assert mgr.attn.paged_calls[0][1].shape == (2, query_len, NUM_KV_HEADS, HEAD_DIM)
assert mgr.attn.paged_metadata[0].attn_mask is None
assert mgr.attn.paged_metadata[0].full_attn_spans == spans
assert mgr.image_kv_cache_map is None
assert not hasattr(mgr, "_materialize_paged_prefix")
def test_legacy_tensor_prefix_still_uses_dense_cache_and_reuse() -> None:
mgr = _make_cache_mgr()
prefix_len, prompt_len = 3, 2
imported = _make_known_kv(prefix_len, base=10.0)
mgr._injected_ar_kv = [imported]
first_len = prompt_len + IMAGE_TOKEN_LEN
key, value = _make_known_kv(first_len, base=20.0)
with (
patch.object(mgr, "_forward_paged", side_effect=AssertionError("legacy entered paged path")),
):
_call_mgr(
mgr,
1,
first_len,
prefix_len + first_len,
key,
value,
first_step=True,
gen_timestep_scatter_index=_gen_timestep_index(1, prompt_len),
)
assert mgr.image_kv_cache_map is not None
assert mgr.image_kv_cache_map[0].shape[1] == prefix_len + prompt_len
next_key, next_value = _make_known_kv(IMAGE_TOKEN_LEN, base=30.0)
_call_mgr(
mgr,
1,
IMAGE_TOKEN_LEN,
prefix_len + first_len,
next_key,
next_value,
position_ids=torch.arange(prefix_len + prompt_len, prefix_len + first_len).unsqueeze(0),
)
assert len(mgr.attn.calls) == 2
assert not mgr.attn.paged_calls
assert mgr.attn.calls[-1][1].shape == (1, prefix_len + first_len, NUM_HEADS, HEAD_DIM)
def test_scheduler_paged_kv_runs_piecewise_for_first_and_later_steps() -> None:
from vllm_omni.diffusion.forward_context import override_paged_kv_adapter, set_forward_context