[perf] Add mask-free TRTLLM packed-padding path (#6542)
Signed-off-by: Bo Li <22713281+bobboli@users.noreply.github.com>
This commit is contained in:
1 parent
b454e34966
commit
51b7565f33
7 files changed
+314
-123
No files matched your search
@@ -1,14 +1,18 @@
|
||||
# SPDX-License-Identifier: Apache-2.0
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||||
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
||||
|
||||
import math
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
|
||||
from vllm_omni.diffusion.attention.backends import trtllm_attn as tg
|
||||
from vllm_omni.diffusion.attention.backends.abstract import AttentionMetadata
|
||||
from vllm_omni.diffusion.attention.backends.abstract import (
|
||||
AttentionMetadata,
|
||||
PackedPaddingMetadata,
|
||||
)
|
||||
from vllm_omni.diffusion.attention.backends.trtllm_attn import TrtllmAttentionImpl
|
||||
|
||||
pytestmark = [pytest.mark.core_model, pytest.mark.diffusion, pytest.mark.cuda]
|
||||
@@ -35,6 +39,15 @@ def _impl(**bk):
|
||||
return TrtllmAttentionImpl(8, 128, 1.0 / math.sqrt(128), causal=False, num_kv_heads=8, backend_kwargs=bk)
|
||||
|
||||
|
||||
def _packed_padding(cu_seqlens_q, cu_seqlens_k, q_length, kv_length):
|
||||
return PackedPaddingMetadata(
|
||||
q_length=q_length,
|
||||
kv_length=kv_length,
|
||||
cu_seqlens_q=cu_seqlens_q[:2],
|
||||
cu_seqlens_k=cu_seqlens_k[:2],
|
||||
)
|
||||
|
||||
|
||||
def test_skip_config_pure_resolution():
|
||||
from vllm_omni.diffusion.attention.backends.trtllm_attn import SkipSoftmaxConfig
|
||||
|
||||
@@ -75,7 +88,7 @@ def test_quant_quantize_requires_flashinfer_routine(monkeypatch):
|
||||
def test_quant_quantize_calls_routine_and_shapes_sfs():
|
||||
from vllm_omni.diffusion.attention.backends.trtllm_attn import QuantConfig
|
||||
|
||||
captured = {}
|
||||
captured: dict[str, object] = {}
|
||||
|
||||
def fake_quantize(q, k, v, q_block_size, k_block_size, qk_quant_dtype):
|
||||
captured.update(q_block_size=q_block_size, k_block_size=k_block_size, qk_quant_dtype=qk_quant_dtype)
|
||||
@@ -209,62 +222,80 @@ def test_masked_layer_raises():
|
||||
_impl().forward_cuda(q, k, v, _Meta())
|
||||
|
||||
|
||||
def test_packed_metadata_trims_padding_mask(monkeypatch):
|
||||
b, s, h, d = 1, 8, 8, 128
|
||||
q, k, v = (torch.zeros(b, s, h, d) for _ in range(3))
|
||||
mask = torch.tensor([[True, True, True, True, True, True, False, False]])
|
||||
cu_seqlens = torch.tensor([0, 6, 8], dtype=torch.int32)
|
||||
def test_generic_packed_metadata_preserves_ragged_batch(monkeypatch):
|
||||
b, h, d = 1, 8, 128
|
||||
q = torch.zeros(b, 5, h, d)
|
||||
k, v = (torch.zeros(b, 7, h, d) for _ in range(2))
|
||||
metadata = AttentionMetadata(
|
||||
attn_mask=mask,
|
||||
extra={
|
||||
"cu_seqlens_q": cu_seqlens,
|
||||
"cu_seqlens_k": cu_seqlens,
|
||||
"max_seqlen_q": 6,
|
||||
"max_seqlen_k": 6,
|
||||
"cu_seqlens_q": torch.tensor([0, 2, 5], dtype=torch.int32),
|
||||
"cu_seqlens_k": torch.tensor([0, 3, 7], dtype=torch.int32),
|
||||
"max_seqlen_q": 3,
|
||||
"max_seqlen_k": 4,
|
||||
}
|
||||
)
|
||||
fake_attention = Mock(return_value=torch.zeros_like(q.reshape(5, h, d)))
|
||||
|
||||
monkeypatch.setattr(tg, "HAS_FLASHINFER", True)
|
||||
monkeypatch.setattr(tg, "trtllm_ragged_attention_deepseek", fake_attention)
|
||||
monkeypatch.setattr(TrtllmAttentionImpl, "_get_workspace", classmethod(lambda cls, device: torch.empty(0)))
|
||||
|
||||
_impl().forward_cuda(q, k, v, metadata)
|
||||
captured = fake_attention.call_args.kwargs
|
||||
|
||||
assert captured["query"].shape[0] == 5
|
||||
assert captured["key"].shape[0] == 7
|
||||
assert captured["value"].shape[0] == 7
|
||||
assert captured["batch_size"] == 2
|
||||
assert captured["seq_lens"].tolist() == [3, 4]
|
||||
assert tg.TrtllmAttentionBackend.supports_multi_doc_packed_varlen()
|
||||
|
||||
|
||||
def test_packed_padding_metadata_trims_without_mask(monkeypatch):
|
||||
q_len, kv_len = 8, 9
|
||||
valid_q_tokens, valid_kv_tokens = 5, 6
|
||||
b, h, d = 1, 8, 128
|
||||
q = torch.zeros(b, q_len, h, d)
|
||||
k, v = (torch.zeros(b, kv_len, h, d) for _ in range(2))
|
||||
cu_seqlens_q = torch.tensor([0, valid_q_tokens, q_len], dtype=torch.int32)
|
||||
cu_seqlens_k = torch.tensor([0, valid_kv_tokens, kv_len], dtype=torch.int32)
|
||||
metadata = AttentionMetadata(
|
||||
packed_padding=_packed_padding(
|
||||
cu_seqlens_q,
|
||||
cu_seqlens_k,
|
||||
valid_q_tokens,
|
||||
valid_kv_tokens,
|
||||
),
|
||||
extra={
|
||||
"cu_seqlens_q": cu_seqlens_q,
|
||||
"cu_seqlens_k": cu_seqlens_k,
|
||||
"max_seqlen_q": valid_q_tokens,
|
||||
"max_seqlen_k": valid_kv_tokens,
|
||||
"valid_kv_length": valid_kv_tokens,
|
||||
},
|
||||
)
|
||||
captured = {}
|
||||
|
||||
def fake_attention(**kwargs):
|
||||
captured.update(kwargs)
|
||||
return torch.zeros_like(kwargs["query"])
|
||||
fake_attention = Mock(return_value=torch.ones(valid_q_tokens, h, d))
|
||||
|
||||
monkeypatch.setattr(tg, "HAS_FLASHINFER", True)
|
||||
monkeypatch.setattr(tg, "trtllm_ragged_attention_deepseek", fake_attention)
|
||||
monkeypatch.setattr(TrtllmAttentionImpl, "_get_workspace", classmethod(lambda cls, device: torch.empty(0)))
|
||||
|
||||
out = _impl().forward_cuda(q, k, v, metadata)
|
||||
captured = fake_attention.call_args.kwargs
|
||||
|
||||
assert tg.TrtllmAttentionBackend.supports_packed_mask_free()
|
||||
assert out.shape == q.shape
|
||||
assert captured["query"].shape[0] == 6
|
||||
assert captured["key"].shape[0] == 6
|
||||
assert captured["value"].shape[0] == 6
|
||||
assert captured["query"].shape[0] == valid_q_tokens
|
||||
assert captured["key"].shape[0] == valid_kv_tokens
|
||||
assert captured["value"].shape[0] == valid_kv_tokens
|
||||
assert captured["batch_size"] == 1
|
||||
assert captured["seq_lens"].tolist() == [6]
|
||||
assert captured["cum_seq_lens_q"].tolist() == [0, 6]
|
||||
assert captured["cum_seq_lens_kv"].tolist() == [0, 6]
|
||||
assert captured["max_q_len"] == 6
|
||||
assert captured["max_kv_len"] == 6
|
||||
assert torch.count_nonzero(out[:, 6:]) == 0
|
||||
|
||||
|
||||
def test_packed_metadata_rejects_non_prefix_mask(monkeypatch):
|
||||
b, s, h, d = 1, 8, 8, 128
|
||||
q, k, v = (torch.zeros(b, s, h, d) for _ in range(3))
|
||||
cu_seqlens = torch.tensor([0, 6, 8], dtype=torch.int32)
|
||||
metadata = AttentionMetadata(
|
||||
attn_mask=torch.tensor([[True, True, False, True, True, True, False, False]]),
|
||||
extra={
|
||||
"cu_seqlens_q": cu_seqlens,
|
||||
"cu_seqlens_k": cu_seqlens,
|
||||
"max_seqlen_q": 6,
|
||||
"max_seqlen_k": 6,
|
||||
},
|
||||
)
|
||||
monkeypatch.setattr(tg, "HAS_FLASHINFER", True)
|
||||
|
||||
with pytest.raises(ValueError, match="prefix-valid"):
|
||||
_impl().forward_cuda(q, k, v, metadata)
|
||||
assert captured["seq_lens"].tolist() == [valid_kv_tokens]
|
||||
assert captured["cum_seq_lens_q"].tolist() == [0, valid_q_tokens]
|
||||
assert captured["cum_seq_lens_kv"].tolist() == [0, valid_kv_tokens]
|
||||
assert captured["max_q_len"] == valid_q_tokens
|
||||
assert captured["max_kv_len"] == valid_kv_tokens
|
||||
assert torch.count_nonzero(out[:, :valid_q_tokens] != 1) == 0
|
||||
assert torch.count_nonzero(out[:, valid_q_tokens:]) == 0
|
||||
|
||||
|
||||
def test_packed_metadata_must_be_complete():
|
||||
@@ -393,12 +424,13 @@ def test_bf16_packed_padding_matches_sdpa():
|
||||
q, k, v = (torch.randn(b, s, h, d, device="cuda", dtype=torch.bfloat16) for _ in range(3))
|
||||
cu_seqlens = torch.tensor([0, used, s], dtype=torch.int32, device="cuda")
|
||||
metadata = AttentionMetadata(
|
||||
attn_mask=torch.arange(s, device="cuda")[None] < used,
|
||||
packed_padding=_packed_padding(cu_seqlens, cu_seqlens, used, used),
|
||||
extra={
|
||||
"cu_seqlens_q": cu_seqlens,
|
||||
"cu_seqlens_k": cu_seqlens,
|
||||
"max_seqlen_q": used,
|
||||
"max_seqlen_k": used,
|
||||
"valid_kv_length": used,
|
||||
},
|
||||
)
|
||||
|
||||
@@ -443,12 +475,13 @@ def test_sage_packed_non_aligned_length_matches_sdpa():
|
||||
q, k, v = (torch.randn(b, s, h, d, device="cuda", dtype=torch.bfloat16) for _ in range(3))
|
||||
cu_seqlens = torch.tensor([0, used, s], dtype=torch.int32, device="cuda")
|
||||
metadata = AttentionMetadata(
|
||||
attn_mask=torch.arange(s, device="cuda")[None] < used,
|
||||
packed_padding=_packed_padding(cu_seqlens, cu_seqlens, used, used),
|
||||
extra={
|
||||
"cu_seqlens_q": cu_seqlens,
|
||||
"cu_seqlens_k": cu_seqlens,
|
||||
"max_seqlen_q": used,
|
||||
"max_seqlen_k": used,
|
||||
"valid_kv_length": used,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@@ -774,6 +774,12 @@ def test_packed_attention_skips_mask_for_packed_mask_free_backend():
|
||||
assert metadata.attn_mask is None
|
||||
assert metadata.extra["valid_kv_length"] == 5
|
||||
assert metadata.extra["npu_attn_varlen"] is True
|
||||
packed_padding = metadata.packed_padding
|
||||
assert packed_padding is not None
|
||||
assert packed_padding.q_length == 5
|
||||
assert packed_padding.kv_length == 5
|
||||
assert packed_padding.cu_seqlens_q.tolist() == [0, 5]
|
||||
assert packed_padding.cu_seqlens_k.tolist() == [0, 5]
|
||||
|
||||
|
||||
def test_packed_attention_keeps_padding_mask_for_other_backends():
|
||||
|
||||
@@ -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
|
||||
"""MiniMax H3 step-wise execution (continuous batching) contract tests.
|
||||
|
||||
These run on CPU against a stand-in DiT, so they cover the packing, the
|
||||
@@ -31,6 +31,8 @@ class _SegmentMeanModel:
|
||||
x = kwargs["x"][0]
|
||||
audio_x = kwargs["audio_x"][0]
|
||||
bounds = kwargs["packed_seq_params"]["cu_seqlens_q"].tolist()
|
||||
if kwargs["packed_seq_params"].get("num_requests", 1) > 1:
|
||||
assert all(start < stop for start, stop in zip(bounds[:-1], bounds[1:]))
|
||||
img_pos = kwargs["img_pos_info"]["position_ids"]
|
||||
audio_pos = kwargs["audio_pos_info"]["position_ids"]
|
||||
|
||||
@@ -158,9 +160,9 @@ def test_batched_step_execution_matches_independent_requests():
|
||||
|
||||
model = _SegmentMeanModel()
|
||||
specs = [
|
||||
dict(text_len=9, latent_t=2, latent_h=4, latent_w=6, audio_t=3, seed=6),
|
||||
# Exactly 64 packed rows exercises the no-padding-tail boundary case.
|
||||
dict(text_len=46, latent_t=7, latent_h=2, latent_w=4, audio_t=2, seed=7),
|
||||
dict(text_len=9, latent_t=2, latent_h=4, latent_w=6, audio_t=3, seed=6),
|
||||
]
|
||||
# Different step counts, so the batch composition changes mid-flight.
|
||||
schedules = [(_sigmas(6, 12.0), _sigmas(6, 3.0)), (_sigmas(4, 12.0), _sigmas(4, 3.0))]
|
||||
@@ -178,7 +180,7 @@ def test_batched_step_execution_matches_independent_requests():
|
||||
states = []
|
||||
for index, (spec, (sigmas_video, sigmas_audio)) in enumerate(zip(specs, schedules)):
|
||||
branch, video_rows, audio_rows = _make_branch(**spec)
|
||||
if index == 1:
|
||||
if index == 0:
|
||||
assert branch.used_len == branch.seq_len
|
||||
states.append(_make_state(f"req-{index}", model, branch, video_rows, audio_rows, sigmas_video, sigmas_audio))
|
||||
|
||||
|
||||
Reference in new issue
Block a user