Signed-off-by: Fan <fan@FandeMacBook-Air.local> Co-authored-by: Fan <fan@FandeMacBook-Air.local>
185 lines
7.0 KiB
Python
185 lines
7.0 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
|
|
"""Geometry contract RAINFUSION_ATTN enforces before handing a forward to rf_v2.
|
|
|
|
The grids here are the ones MiniMax-H3 FL2VA actually produces for an 8.7s clip:
|
|
1280x768 gives a 62x24x40 latent grid whose 59520 video rows land on the 128-row
|
|
kernel block, and 1344x768 gives 62x24x42 whose 62496 rows do not. Both are
|
|
handed to MindIE-SD: its rf_v2 preprocessing promotes an irregular video tail
|
|
to the always-kept prefix segment before generating the block mask.
|
|
"""
|
|
|
|
import dataclasses
|
|
|
|
import pytest
|
|
import torch
|
|
|
|
from vllm_omni.diffusion.attention.backends.abstract import AttentionMetadata, VideoTokenLayout
|
|
from vllm_omni.diffusion.attention.backends.rainfusion_attn import (
|
|
_BLOCK_SIZE,
|
|
RainFusionAttentionImpl,
|
|
RainFusionPlan,
|
|
)
|
|
from vllm_omni.platforms import current_omni_platform
|
|
|
|
PREFIX_ROWS = 710 # 14 text rows + 696 audio rows
|
|
ALIGNED_GRID = (62, 24, 40) # 1280x768 -> 59520 video rows, 465 blocks
|
|
MISALIGNED_GRID = (62, 24, 42) # 1344x768 -> 62496 video rows, 488 blocks + 32 rows
|
|
|
|
|
|
def make_impl(**backend_kwargs):
|
|
return RainFusionAttentionImpl(
|
|
num_heads=8,
|
|
head_size=128,
|
|
softmax_scale=128**-0.5,
|
|
prefix="transformer_blocks.0.attn",
|
|
qkv_layout="BSND",
|
|
backend_kwargs={"sparsity": 0.8, **backend_kwargs},
|
|
)
|
|
|
|
|
|
def make_metadata(grid, prefix_len=PREFIX_ROWS, max_seqlen_q=None):
|
|
video_rows = grid[0] * grid[1] * grid[2]
|
|
return AttentionMetadata(
|
|
extra={"max_seqlen_q": prefix_len + video_rows if max_seqlen_q is None else max_seqlen_q},
|
|
video_layout=VideoTokenLayout(prefix_len=prefix_len, latent_grid=grid),
|
|
)
|
|
|
|
|
|
def test_block_aligned_video_segment_runs_sparse():
|
|
plan = make_impl()._resolve_plan(make_metadata(ALIGNED_GRID))
|
|
|
|
assert plan is not None
|
|
assert plan.prefix_len == PREFIX_ROWS
|
|
assert plan.used_len == PREFIX_ROWS + 59520
|
|
assert plan.latent_shape == list(ALIGNED_GRID)
|
|
|
|
|
|
def test_misaligned_video_segment_runs_sparse_with_updated_mindiesd():
|
|
plan = make_impl()._resolve_plan(make_metadata(MISALIGNED_GRID))
|
|
|
|
assert plan is not None
|
|
assert plan.prefix_len == PREFIX_ROWS
|
|
assert plan.used_len == PREFIX_ROWS + 62496
|
|
assert plan.latent_shape == list(MISALIGNED_GRID)
|
|
|
|
|
|
@pytest.mark.parametrize("prefix_len", [1, 127, _BLOCK_SIZE, 710, 900])
|
|
def test_prefix_length_does_not_affect_alignment(prefix_len):
|
|
# Only the video segment has to land on a block boundary; rf_v2 pools the
|
|
# prefix separately and keeps every one of its blocks.
|
|
plan = make_impl()._resolve_plan(make_metadata(ALIGNED_GRID, prefix_len=prefix_len))
|
|
|
|
assert plan is not None
|
|
assert plan.prefix_len == prefix_len
|
|
|
|
|
|
def test_sparsity_zero_never_resolves_a_plan():
|
|
assert make_impl(sparsity=0.0)._resolve_plan(make_metadata(ALIGNED_GRID)) is None
|
|
|
|
|
|
def test_missing_video_layout_falls_back_to_dense():
|
|
assert make_impl()._resolve_plan(AttentionMetadata(extra={"max_seqlen_q": 60230})) is None
|
|
|
|
|
|
def test_missing_max_seqlen_falls_back_to_dense():
|
|
metadata = AttentionMetadata(
|
|
video_layout=VideoTokenLayout(prefix_len=PREFIX_ROWS, latent_grid=ALIGNED_GRID),
|
|
)
|
|
|
|
assert make_impl()._resolve_plan(metadata) is None
|
|
|
|
|
|
def test_video_segment_must_be_the_tail_of_packed_document_zero():
|
|
metadata = make_metadata(ALIGNED_GRID, max_seqlen_q=PREFIX_ROWS + 59520 + 128)
|
|
|
|
assert make_impl()._resolve_plan(metadata) is None
|
|
|
|
|
|
@pytest.mark.parametrize("grid", [(4, 24, 40), (1, 24, 40)])
|
|
def test_short_video_stays_dense(grid):
|
|
assert make_impl()._resolve_plan(make_metadata(grid)) is None
|
|
|
|
|
|
@pytest.mark.parametrize("qkv_layout", ["BNSD", "BSH"])
|
|
def test_explicitly_wrong_layout_is_rejected(qkv_layout):
|
|
with pytest.raises(ValueError, match="BSND"):
|
|
RainFusionAttentionImpl(
|
|
num_heads=8,
|
|
head_size=128,
|
|
softmax_scale=128**-0.5,
|
|
qkv_layout=qkv_layout,
|
|
backend_kwargs={"sparsity": 0.8},
|
|
)
|
|
|
|
|
|
def test_undeclared_layout_stays_dense():
|
|
# An undeclared layout is not an error -- the layer keeps working, just
|
|
# densely, and the fallback sees the same absent layout plain FLASH_ATTN would.
|
|
impl = RainFusionAttentionImpl(
|
|
num_heads=8,
|
|
head_size=128,
|
|
softmax_scale=128**-0.5,
|
|
prefix="transformer_blocks.0.attn",
|
|
backend_kwargs={"sparsity": 0.8},
|
|
)
|
|
|
|
assert impl._resolve_plan(make_metadata(ALIGNED_GRID)) is None
|
|
assert impl.dense_fallback.qkv_layout is None
|
|
|
|
|
|
@pytest.mark.skipif(not current_omni_platform.is_npu(), reason="rf_v2 runs on Ascend NPU only.")
|
|
def test_fully_populated_mask_reproduces_dense_attention():
|
|
"""At sparsity=0 every key block is kept, so rf_v2 must match dense attention.
|
|
|
|
This is the guard on the geometry the backend hands the kernel: a wrong
|
|
prefix length or latent grid still produces plausible output under sparse
|
|
selection, but shows up here as a mismatch against SDPA.
|
|
"""
|
|
torch.manual_seed(0)
|
|
prefix_len, grid = 384, (4, 16, 64) # 4096 video rows = 32 blocks
|
|
video_len = grid[0] * grid[1] * grid[2]
|
|
used = prefix_len + video_len
|
|
heads, head_dim = 4, 128
|
|
q, k, v = (torch.randn(1, used, heads, head_dim, dtype=torch.bfloat16, device="npu") for _ in range(3))
|
|
|
|
impl = make_impl()
|
|
impl.rainfusion = dataclasses.replace(impl.rainfusion, sparsity=0.0)
|
|
plan = RainFusionPlan(prefix_len=prefix_len, used_len=used, latent_shape=list(grid))
|
|
out = impl._forward_sparse_npu(q, k, v, plan)
|
|
|
|
reference = torch.nn.functional.scaled_dot_product_attention(
|
|
q.transpose(1, 2),
|
|
k.transpose(1, 2),
|
|
v.transpose(1, 2),
|
|
scale=impl.softmax_scale,
|
|
).transpose(1, 2)
|
|
error = (out.float() - reference.float()).abs().mean() / reference.float().abs().mean()
|
|
assert error < 2e-3, f"mean relative error {error:.4%} against dense attention"
|
|
|
|
|
|
@pytest.mark.skipif(not current_omni_platform.is_npu(), reason="rf_v2 runs on Ascend NPU only.")
|
|
def test_irregular_video_tail_reproduces_dense_attention_with_updated_mindiesd():
|
|
"""vLLM forwards the full irregular grid; MindIE-SD owns its tail handling."""
|
|
torch.manual_seed(1)
|
|
prefix_len, grid = 384, (4, 16, 10)
|
|
video_len = grid[0] * grid[1] * grid[2]
|
|
used = prefix_len + video_len
|
|
heads, head_dim = 4, 128
|
|
q, k, v = (torch.randn(1, used, heads, head_dim, dtype=torch.bfloat16, device="npu") for _ in range(3))
|
|
|
|
impl = make_impl()
|
|
impl.rainfusion = dataclasses.replace(impl.rainfusion, sparsity=0.0)
|
|
plan = RainFusionPlan(prefix_len=prefix_len, used_len=used, latent_shape=list(grid))
|
|
out = impl._forward_sparse_npu(q, k, v, plan)
|
|
|
|
reference = torch.nn.functional.scaled_dot_product_attention(
|
|
q.transpose(1, 2),
|
|
k.transpose(1, 2),
|
|
v.transpose(1, 2),
|
|
scale=impl.softmax_scale,
|
|
).transpose(1, 2)
|
|
error = (out.float() - reference.float()).abs().mean() / reference.float().abs().mean()
|
|
assert error < 2e-3, f"mean relative error {error:.4%} against dense attention"
|