Signed-off-by: Rahul Steiger <rsteiger@aws-cmh-slurm-1-vscode-04.cm.cluster> Signed-off-by: Rahul Steiger <rsteiger@nvidia.com> Co-authored-by: Rahul Steiger <rsteiger@aws-cmh-slurm-1-vscode-04.cm.cluster>
265 lines
10 KiB
Python
265 lines
10 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
|
|
|
import functools
|
|
from dataclasses import replace
|
|
from types import SimpleNamespace
|
|
|
|
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, PackedPaddingMetadata
|
|
from vllm_omni.diffusion.attention.capabilities import (
|
|
CompilationMode,
|
|
ExecutionContext,
|
|
OuterBoundary,
|
|
ParallelStrategy,
|
|
SupportStatus,
|
|
)
|
|
|
|
pytestmark = [pytest.mark.core_model, pytest.mark.diffusion, pytest.mark.cpu]
|
|
|
|
|
|
def _impl(**kwargs):
|
|
return tg.TrtllmAttentionImpl(2, 128, 128**-0.5, **kwargs)
|
|
|
|
|
|
def _inputs(q_len=8, kv_len=12, dtype=torch.bfloat16):
|
|
return (
|
|
torch.randn(1, q_len, 2, 128, dtype=dtype),
|
|
torch.randn(1, kv_len, 2, 128, dtype=dtype),
|
|
torch.randn(1, kv_len, 2, 128, dtype=dtype),
|
|
)
|
|
|
|
|
|
@pytest.fixture
|
|
def dispatcher(monkeypatch, tmp_path):
|
|
marker = tmp_path / "kernel"
|
|
marker.write_text("loaded")
|
|
|
|
@functools.cache
|
|
def load_kernel():
|
|
return marker.read_text()
|
|
|
|
def fake_attention(query, key, value, workspace_buffer, batch_size, bmm1_scale, **kwargs):
|
|
assert load_kernel() == "loaded"
|
|
assert kwargs["skip_all_rows_active_check"] is True
|
|
workspace_buffer.add_(1)
|
|
q, k, v = (t.reshape(batch_size, -1, t.shape[-2], t.shape[-1]).transpose(1, 2) for t in (query, key, value))
|
|
return (
|
|
F.scaled_dot_product_attention(q, k, v, scale=bmm1_scale)
|
|
.transpose(1, 2)
|
|
.reshape(query.shape[0], query.shape[1], value.shape[-1])
|
|
.contiguous()
|
|
)
|
|
|
|
def quantize(q, k, v, **kwargs):
|
|
scale = torch.ones(1)
|
|
return q, k, v, scale, scale, scale, None
|
|
|
|
# CPU tests substitute device selection; the real kernel tests use the GPU.
|
|
monkeypatch.setattr(tg, "_is_trtllm_blackwell_device", lambda query: True)
|
|
monkeypatch.setattr(tg, "HAS_FLASHINFER", True)
|
|
monkeypatch.setattr(tg, "trtllm_ragged_attention_deepseek", fake_attention, raising=False)
|
|
monkeypatch.setattr(
|
|
tg, "flashinfer", SimpleNamespace(__version__="0.6.18", trtllm_sage_attention_quantize=quantize), raising=False
|
|
)
|
|
workspace = torch.zeros(16, dtype=torch.uint8)
|
|
monkeypatch.setattr(tg.TrtllmAttentionImpl, "_get_workspace", classmethod(lambda cls, device: workspace))
|
|
torch.compiler.reset()
|
|
yield workspace
|
|
torch.compiler.reset()
|
|
|
|
|
|
def test_dense_contract_uses_initialized_state(dispatcher):
|
|
context = ExecutionContext(platform="cuda", kernel_variant="caller_sage", causal=True, require_fullgraph=True)
|
|
result = _impl().resolve_execution_path(context, *_inputs(), None)
|
|
assert result.path == "trtllm_dense" and result.kernel_variant == "trtllm"
|
|
assert result.support.status is SupportStatus.SUPPORTED
|
|
assert result.compilation_mode is CompilationMode.CUSTOM_OP
|
|
assert result.requested_support(context).status is SupportStatus.SUPPORTED
|
|
pre = tg.TrtllmAttentionBackend.resolve_capabilities(context)
|
|
assert pre.kernel_variant is None and pre.support.status is SupportStatus.UNMIGRATED
|
|
assert pre.requested_support(context).status is SupportStatus.UNSUPPORTED
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"change",
|
|
[
|
|
{"platform": "rocm"},
|
|
{"paged_kv": True},
|
|
{"parallel_strategy": ParallelStrategy.ULYSSES},
|
|
{"parallel_strategy": ParallelStrategy.RING},
|
|
{"parallel_strategy": ParallelStrategy.HYBRID_ULYSSES_RING},
|
|
{"parallel_strategy": ParallelStrategy.ALLGATHER_KV},
|
|
{"outer_boundaries": frozenset({OuterBoundary.HSDP})},
|
|
],
|
|
)
|
|
def test_outer_paths_are_unmigrated(dispatcher, change):
|
|
context = replace(ExecutionContext(platform="cuda", require_fullgraph=True), **change)
|
|
result = _impl().resolve_execution_path(context, *_inputs(), None)
|
|
assert result.support.status is SupportStatus.UNMIGRATED
|
|
assert result.requested_support(context).status is SupportStatus.UNSUPPORTED
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"kwargs,path",
|
|
[
|
|
({"causal": True}, "trtllm_dense"),
|
|
({"backend_kwargs": {"quant": {"dtype_qk": "int8"}}}, "trtllm_sage"),
|
|
({"backend_kwargs": {"skip_softmax_threshold": 0.01}}, "trtllm_skip_softmax"),
|
|
({"backend_kwargs": {"target_sparsity": 0.5}}, "trtllm_skip_softmax"),
|
|
],
|
|
)
|
|
def test_unverified_backend_settings(dispatcher, kwargs, path):
|
|
result = _impl(**kwargs).resolve_execution_path(ExecutionContext(platform="cuda"), *_inputs(), None)
|
|
assert result.path == path and result.support.status is SupportStatus.UNMIGRATED
|
|
|
|
|
|
@pytest.mark.parametrize("padding", [False, True])
|
|
def test_packed_paths_are_not_dense(dispatcher, padding):
|
|
q, k, v = _inputs()
|
|
cuq, cuk = torch.tensor([0, 8], dtype=torch.int32), torch.tensor([0, 12], dtype=torch.int32)
|
|
metadata = AttentionMetadata(
|
|
extra={
|
|
"cu_seqlens_q": cuq,
|
|
"cu_seqlens_k": cuk,
|
|
"max_seqlen_q": 8,
|
|
"max_seqlen_k": 12,
|
|
},
|
|
packed_padding=PackedPaddingMetadata(8, 12, cuq, cuk) if padding else None,
|
|
)
|
|
impl = _impl()
|
|
result = impl.resolve_execution_path(ExecutionContext(platform="cuda"), q, k, v, metadata)
|
|
assert result.path == ("trtllm_packed_padding" if padding else "trtllm_packed_varlen")
|
|
assert result.support.status is SupportStatus.UNMIGRATED
|
|
assert (
|
|
impl.resolve_execution_path(ExecutionContext(platform="cuda"), q, k, v, None).support.status
|
|
is SupportStatus.SUPPORTED
|
|
)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"metadata,message",
|
|
[
|
|
(AttentionMetadata(attn_mask=torch.ones(1, 12)), "does not support attn_mask"),
|
|
(AttentionMetadata(extra={"cu_seqlens_q": torch.tensor([0, 8])}), "Incomplete packed"),
|
|
(AttentionMetadata(packed_padding="invalid"), "must be PackedPaddingMetadata"),
|
|
],
|
|
)
|
|
def test_resolution_and_execution_share_metadata_validation(dispatcher, metadata, message):
|
|
impl = _impl()
|
|
inputs = _inputs()
|
|
with pytest.raises(ValueError, match=message):
|
|
impl.resolve_execution_path(ExecutionContext(platform="cuda"), *inputs, metadata)
|
|
with pytest.raises(ValueError, match=message):
|
|
impl.forward_cuda(*inputs, metadata)
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"metadata",
|
|
[
|
|
AttentionMetadata(full_attn_spans=[[(0, 4)]]),
|
|
AttentionMetadata(extra={"kv_cache_dtype": "fp8"}),
|
|
],
|
|
)
|
|
def test_special_metadata_is_unmigrated(dispatcher, metadata):
|
|
result = _impl().resolve_execution_path(ExecutionContext(platform="cuda"), *_inputs(), metadata)
|
|
assert result.support.status is SupportStatus.UNMIGRATED
|
|
|
|
|
|
def test_invalid_geometry_and_missing_dependency(dispatcher, monkeypatch):
|
|
q, k, v = _inputs()
|
|
context = ExecutionContext(platform="cuda")
|
|
for inputs in ((q[0], k, v), (q, k.float(), v), (q, k, v[:, :-1])):
|
|
result = _impl().resolve_execution_path(context, *inputs, None)
|
|
assert result.support.status is SupportStatus.UNSUPPORTED and result.support.reason
|
|
for inputs in (
|
|
_inputs(dtype=torch.float32),
|
|
(q[..., :64], k[..., :64], v[..., :64]),
|
|
(q, k[:, :, :1], v[:, :, :1]),
|
|
):
|
|
assert _impl().resolve_execution_path(context, *inputs, None).support.status is SupportStatus.UNMIGRATED
|
|
monkeypatch.setattr(tg, "HAS_FLASHINFER", False)
|
|
result = _impl().resolve_execution_path(context, q, k, v, None)
|
|
assert result.support.status is SupportStatus.UNSUPPORTED and "FlashInfer" in result.support.reason
|
|
|
|
|
|
@pytest.mark.parametrize("use_sage", [False, True], ids=["dense", "sage"])
|
|
def test_trtllm_dispatcher_is_opaque_to_torch_compile(dispatcher, use_sage):
|
|
impl = _impl(backend_kwargs={"quant": {"dtype_qk": "int8"}} if use_sage else None)
|
|
compiled = torch.compile(impl.forward_cuda, fullgraph=True, dynamic=True)
|
|
for q_len, kv_len in ((8, 12), (11, 17), (8, 12)):
|
|
q, k, v = _inputs(q_len, kv_len)
|
|
before = [t.clone() for t in (q, k, v)]
|
|
eager = impl.forward_cuda(q, k, v)
|
|
out = compiled(q, k, v)
|
|
torch.testing.assert_close(out, eager)
|
|
for actual, original in zip((q, k, v), before):
|
|
torch.testing.assert_close(actual, original, rtol=0, atol=0)
|
|
assert torch.all(dispatcher == 6)
|
|
|
|
|
|
def test_custom_op_schema_fake_and_noncontiguous_output(dispatcher):
|
|
q = torch.randn(2, 7, 128).transpose(0, 1)
|
|
k, v = torch.randn(9, 2, 128), torch.randn(9, 2, 64)
|
|
args = (
|
|
q,
|
|
k,
|
|
v,
|
|
dispatcher,
|
|
torch.tensor([9], dtype=torch.int32),
|
|
torch.tensor([0, 7], dtype=torch.int32),
|
|
torch.tensor([0, 9], dtype=torch.int32),
|
|
None,
|
|
None,
|
|
None,
|
|
7,
|
|
9,
|
|
1,
|
|
128**-0.5,
|
|
1.0,
|
|
-1.0,
|
|
0,
|
|
0,
|
|
False,
|
|
)
|
|
result = torch.library.opcheck(tg._trtllm_ragged_attention_op, args, test_utils=("test_schema", "test_faketensor"))
|
|
assert all(v == "SUCCESS" for v in result.values())
|
|
out = tg._trtllm_ragged_attention_op(*args)
|
|
assert out.shape == (7, 2, 64) and out.is_contiguous()
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
"capability,expected",
|
|
[
|
|
((9, 0), False),
|
|
((10, 0), True),
|
|
((10, 3), True),
|
|
((10, 7), False),
|
|
((12, 0), False),
|
|
(None, False),
|
|
],
|
|
)
|
|
def test_trtllm_device_capability_is_conservative(monkeypatch, capability, expected):
|
|
monkeypatch.setattr(
|
|
tg,
|
|
"current_omni_platform",
|
|
SimpleNamespace(
|
|
is_cuda=lambda: True,
|
|
get_device_capability=lambda device_id: capability,
|
|
),
|
|
)
|
|
assert tg._is_trtllm_blackwell_device(SimpleNamespace(device=torch.device("cuda:0"))) is expected
|
|
assert not tg._is_trtllm_blackwell_device(SimpleNamespace(device=torch.device("cpu")))
|
|
|
|
|
|
def test_non_blackwell_cannot_claim_fullgraph(dispatcher, monkeypatch):
|
|
monkeypatch.setattr(tg, "_is_trtllm_blackwell_device", lambda query: False)
|
|
context = ExecutionContext(platform="cuda", require_fullgraph=True)
|
|
result = _impl().resolve_execution_path(context, *_inputs(), None)
|
|
assert result.support.status is SupportStatus.UNMIGRATED
|
|
assert result.requested_support(context).status is SupportStatus.UNSUPPORTED
|