Signed-off-by: lishunyang12 <lishunyang12@users.noreply.github.com> Co-authored-by: lishunyang12 <lishunyang12@users.noreply.github.com>
127 lines
5.8 KiB
Python
127 lines
5.8 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
|
|
|
|
import sys
|
|
import types
|
|
|
|
import pytest
|
|
import torch
|
|
from torch._subclasses.fake_tensor import FakeTensorMode
|
|
|
|
from vllm_omni.diffusion.attention.ops.block_sparse import (
|
|
build_prefix_dense_block_map,
|
|
fastvideo_block_sparse_attn_bshd,
|
|
mean_pool_tiles,
|
|
)
|
|
|
|
pytestmark = [pytest.mark.core_model, pytest.mark.diffusion, pytest.mark.cpu]
|
|
|
|
|
|
@pytest.mark.parametrize("prefix_blocks,topk", [(0, 1), (2, 0), (2, 1), (2, 3), (2, 5)])
|
|
def test_block_map_keeps_prefix_dense_and_selects_non_prefix_keys(prefix_blocks, topk):
|
|
sparse_blocks = 3
|
|
blocks = prefix_blocks + sparse_blocks
|
|
scores = torch.arange(2 * blocks * blocks, dtype=torch.float32).reshape(1, 2, blocks, blocks)
|
|
block_map = build_prefix_dense_block_map(scores, prefix_blocks, sparse_blocks, topk)
|
|
expected = torch.zeros_like(scores, dtype=torch.bool)
|
|
# Scores increase with key index, so top-k always selects the last keys.
|
|
for query_block in range(blocks):
|
|
for key_block in range(blocks):
|
|
if (
|
|
query_block < prefix_blocks
|
|
or key_block < prefix_blocks
|
|
or key_block >= blocks - min(topk, sparse_blocks)
|
|
):
|
|
expected[..., query_block, key_block] = True
|
|
assert torch.equal(block_map, expected)
|
|
|
|
|
|
@pytest.mark.parametrize("block_size", [4, 32, 64])
|
|
@pytest.mark.parametrize("dtype", [torch.float32, torch.bfloat16])
|
|
def test_pooling_uses_valid_tile_sizes_and_fp32_accumulation(block_size, dtype):
|
|
sizes = torch.tensor([block_size, block_size - 1, 0], dtype=torch.int32)
|
|
tiles = torch.arange(2 * 3 * block_size * 2 * 3).reshape(2, 3, block_size, 2, 3).to(dtype)
|
|
tiles[:, 1, -1] = 0
|
|
tiles[:, 2] = 0
|
|
expected = torch.stack(
|
|
[tiles[:, 0].float().mean(dim=1), tiles[:, 1, :-1].float().mean(dim=1), torch.zeros(2, 2, 3)], dim=2
|
|
)
|
|
result = mean_pool_tiles(tiles.flatten(1, 2), sizes, block_size)
|
|
assert result.dtype == torch.float32
|
|
torch.testing.assert_close(result, expected, rtol=0, atol=0)
|
|
|
|
|
|
def test_provider_wrapper_restores_transport_rows_and_bshd_layout(monkeypatch):
|
|
monkeypatch.delenv("FASTVIDEO_VSA_SM100A", raising=False)
|
|
calls = []
|
|
|
|
def block_sparse_attn(q, k, v, block_map, sizes):
|
|
calls.append((q.shape, block_map.shape, sizes.tolist()))
|
|
assert q.is_contiguous() and k.is_contiguous() and v.is_contiguous()
|
|
assert sizes.dtype == torch.int32
|
|
return q + k + v, None
|
|
|
|
module = types.ModuleType("fastvideo_kernel.block_sparse_attn")
|
|
setattr(module, "block_sparse_attn", block_sparse_attn)
|
|
monkeypatch.setitem(sys.modules, "fastvideo_kernel", types.ModuleType("fastvideo_kernel"))
|
|
monkeypatch.setitem(sys.modules, "fastvideo_kernel.block_sparse_attn", module)
|
|
query = torch.randn(1, 4 * 64, 2, 8)
|
|
block_map = torch.ones(1, 2, 4, 4, dtype=torch.bool)
|
|
sizes = torch.tensor([64, 17, 64, 0])
|
|
output = fastvideo_block_sparse_attn_bshd(query, query, query, block_map, sizes, 3)
|
|
expected = torch.zeros_like(query)
|
|
expected[:, : 3 * 64] = query[:, : 3 * 64] * 3
|
|
torch.testing.assert_close(output, expected)
|
|
assert calls == [(torch.Size([1, 2, 3 * 64, 8]), torch.Size([1, 2, 3, 3]), [64, 17, 64])]
|
|
|
|
|
|
def test_custom_op_fake_preserves_metadata_without_loading_provider(monkeypatch):
|
|
monkeypatch.setitem(sys.modules, "fastvideo_kernel", None)
|
|
with FakeTensorMode():
|
|
query = torch.empty(1, 4 * 64, 2, 8, dtype=torch.bfloat16)
|
|
block_map = torch.empty(1, 2, 4, 4, dtype=torch.bool)
|
|
sizes = torch.empty(4, dtype=torch.int32)
|
|
output = fastvideo_block_sparse_attn_bshd(query, query, query, block_map, sizes, 3)
|
|
assert output.shape == query.shape
|
|
assert output.dtype == query.dtype
|
|
assert output.device == query.device
|
|
|
|
|
|
@pytest.mark.parametrize("error_type", [ImportError, RuntimeError, torch.AcceleratorError])
|
|
def test_native_provider_failure_only_recovers_without_accelerator_fault(monkeypatch, mocker, error_type):
|
|
monkeypatch.setenv("FASTVIDEO_VSA_SM100A", "1")
|
|
error = error_type("native provider failure")
|
|
native = mocker.Mock()
|
|
native.is_supported.return_value = True
|
|
native.block_sparse_attn_sm100a.side_effect = error
|
|
provider = types.ModuleType("fastvideo_kernel")
|
|
setattr(provider, "block_sparse_attn_sm100a", native)
|
|
index = types.ModuleType("fastvideo_kernel.triton_kernels.index")
|
|
setattr(
|
|
index,
|
|
"map_to_index",
|
|
mocker.Mock(return_value=(torch.zeros(1, dtype=torch.int32), torch.ones(1, dtype=torch.int32))),
|
|
)
|
|
triton = types.ModuleType("fastvideo_kernel.block_sparse_attn")
|
|
fallback = mocker.Mock(side_effect=lambda q, *args: (q.clone(), None))
|
|
setattr(triton, "block_sparse_attn", fallback)
|
|
monkeypatch.setitem(sys.modules, "fastvideo_kernel", provider)
|
|
monkeypatch.setitem(
|
|
sys.modules, "fastvideo_kernel.triton_kernels", types.ModuleType("fastvideo_kernel.triton_kernels")
|
|
)
|
|
monkeypatch.setitem(sys.modules, "fastvideo_kernel.triton_kernels.index", index)
|
|
monkeypatch.setitem(sys.modules, "fastvideo_kernel.block_sparse_attn", triton)
|
|
query = torch.arange(128 * 2 * 8, dtype=torch.float32).reshape(1, 128, 2, 8)
|
|
block_map = torch.ones(1, 2, 2, 2, dtype=torch.bool)
|
|
sizes = torch.tensor([64, 64], dtype=torch.int32)
|
|
|
|
if error_type is torch.AcceleratorError:
|
|
with pytest.raises(torch.AcceleratorError, match="native provider failure") as raised:
|
|
fastvideo_block_sparse_attn_bshd(query, query, query, block_map, sizes, 2)
|
|
assert raised.value is error
|
|
fallback.assert_not_called()
|
|
else:
|
|
output = fastvideo_block_sparse_attn_bshd(query, query, query, block_map, sizes, 2)
|
|
torch.testing.assert_close(output, query, rtol=0, atol=0)
|
|
fallback.assert_called_once()
|