Files
vllm-omni/tests/diffusion/attention/test_rainfusion_plan.py
T
2026-08-11 05:10:50 +08:00

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"