Files
2026-10-02 20:05:04 +08:00

778 lines
29 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM-Omni project
"""CPU contracts for capability-driven AR-Diffusion sessions."""
from __future__ import annotations
from collections import OrderedDict
from contextlib import contextmanager
from types import SimpleNamespace
import pytest
import torch
from vllm_omni.diffusion.sched.interface import CachedRequestData, DiffusionSchedulerOutput
from vllm_omni.diffusion.worker.diffusion_model_runner import DiffusionModelRunner
from vllm_omni.experimental.ar_diffusion.capability import (
ARDiffusionCrossAttentionKVSpec,
ARDiffusionKVBranchSpec,
ARDiffusionKVCacheSpec,
)
from vllm_omni.experimental.ar_diffusion.kv_cache import ARDiffusionKVConfig
from vllm_omni.experimental.ar_diffusion.runner import ARDiffusionModelRunner
from vllm_omni.experimental.ar_diffusion.tick_protocol import ARDiffusionTickRequest
BLOCK = 16
POS = "positive"
NEG = "negative"
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
@pytest.fixture(autouse=True)
def platform_synchronize_calls(monkeypatch) -> list[None]:
"""Stub the platform barrier the runner takes at every chunk boundary.
These tests run on a CPU host, where the resolved platform is
``UnspecifiedOmniPlatform`` and ``synchronize()`` raises. Tests that need
a failing barrier override this with their own fake.
"""
calls: list[None] = []
monkeypatch.setattr(
"vllm_omni.experimental.ar_diffusion.runner.current_omni_platform",
SimpleNamespace(synchronize=lambda: calls.append(None)),
)
return calls
def lingbot_like_spec(*, capacity: int = 2) -> ARDiffusionKVCacheSpec:
"""Single-kv_branch causal DMD: 3 latent frames/block + sink/window + text KV."""
return ARDiffusionKVCacheSpec(
num_layers=2,
num_kv_heads=4, # already TP-local
head_size=64,
tokens_per_frame=BLOCK,
frames_per_block=3,
window_frames=5,
sink_frames=1,
kv_branches=(ARDiffusionKVBranchSpec("main", 0),),
session_capacity=capacity,
cross_attention=(ARDiffusionCrossAttentionKVSpec("text", 8),),
)
def dreamzero_like_spec(*, capacity: int = 2) -> ARDiffusionKVCacheSpec:
return ARDiffusionKVCacheSpec(
num_layers=2,
num_kv_heads=4,
head_size=64,
tokens_per_frame=BLOCK,
frames_per_block=4,
window_frames=6,
kv_branches=(ARDiffusionKVBranchSpec(POS, 0), ARDiffusionKVBranchSpec(NEG, 1)),
session_capacity=capacity,
cross_attention=(ARDiffusionCrossAttentionKVSpec("text", 8),),
)
def tiny_spec(*, capacity: int = 2) -> ARDiffusionKVCacheSpec:
return ARDiffusionKVCacheSpec(
num_layers=1,
num_kv_heads=1,
head_size=1,
tokens_per_frame=1,
frames_per_block=3,
window_frames=3,
sink_frames=3,
kv_branches=(ARDiffusionKVBranchSpec("main", 0),),
session_capacity=capacity,
cross_attention=(ARDiffusionCrossAttentionKVSpec("text", 2),),
)
class CapablePipeline:
def __init__(self, spec: ARDiffusionKVCacheSpec) -> None:
self.spec = spec
self.bound_state = None
self.binds: list[str] = []
self.resets: list[str] = []
self.closes: list[str] = []
def ar_diffusion_kv_cache_spec(self) -> ARDiffusionKVCacheSpec:
return self.spec
@contextmanager
def bind_ar_diffusion_state(self, session_id: str, state):
assert self.bound_state is None
self.bound_state = state
self.binds.append(session_id)
try:
yield
finally:
self.bound_state = None
def reset_ar_diffusion_session(self, session_id: str) -> None:
self.resets.append(session_id)
def close_ar_diffusion_session(self, session_id: str) -> None:
self.closes.append(session_id)
class WarmupPipeline(CapablePipeline):
def __init__(self, spec: ARDiffusionKVCacheSpec, requests: list[object]) -> None:
super().__init__(spec)
self.requests = requests
def ar_diffusion_warmup_requests(self, session_id: str):
assert session_id == ARDiffusionModelRunner._WARMUP_SID
return iter(self.requests)
class BatchCapablePipeline(CapablePipeline):
supports_request_batch = True
class StepCapablePipeline(CapablePipeline):
supports_step_execution = True
def prepare_encode(self, state, **kwargs):
del kwargs
return state
def denoise_step(self, input_batch, *, states=None, **kwargs):
del input_batch, states, kwargs
return None
def step_scheduler(self, state, noise_pred, **kwargs):
del state, noise_pred, kwargs
def post_decode(self, state, **kwargs):
del state, kwargs
from vllm_omni.diffusion.data import DiffusionOutput
return DiffusionOutput()
def scheduler_output(
request_ids: tuple[str, ...] = ("req-1",),
*,
finished: tuple[str, ...] = (),
) -> DiffusionSchedulerOutput:
"""Build the real scheduler payload; stepwise requests arrive as cached rows."""
return DiffusionSchedulerOutput(
step_id=0,
scheduled_new_reqs=[],
scheduled_cached_reqs=CachedRequestData(request_ids=list(request_ids)),
finished_req_ids=set(finished),
num_running_reqs=len(request_ids),
num_waiting_reqs=0,
)
def make_runner(
pipeline: object,
*,
available_bytes: int = 1 << 28,
step_execution: bool = False,
gpu_memory_fraction: float = 0.1,
) -> ARDiffusionModelRunner:
runner = object.__new__(ARDiffusionModelRunner)
runner.od_config = SimpleNamespace(
max_num_seqs=1,
dtype=torch.float32,
enforce_eager=True,
step_execution=step_execution,
)
runner.device = torch.device("cpu")
runner.pipeline = pipeline
runner.ar_diffusion_kv_config = ARDiffusionKVConfig(
enable=True,
gpu_memory_fraction=gpu_memory_fraction,
)
runner.kv_cache = None
runner._ar_diffusion_capability = None
runner._ar_diffusion_kv_cache_spec = None
runner._sessions = OrderedDict()
runner._session_capacity = 0
runner._perf_e2e_times = []
runner._stepwise_chunk_started = {}
runner._preallocate_kv_cache(available_bytes=available_bytes)
return runner
def commit_one_frame(runner: ARDiffusionModelRunner, session_id: str, kv_branch: str):
state = runner._get_or_create_session(session_id)
ctx = state.get_kv_caches(kv_branch, seq_len=BLOCK, commit_current=True)[0].forward_ctx
ctx.ensure_video_slots(torch.device("cpu"))
state.commit_paged_context(kv_branch)
return state
def commit_one_block(runner: ARDiffusionModelRunner, session_id: str, kv_branch: str):
state = runner._get_or_create_session(session_id)
assert runner.kv_cache is not None
seq_len = BLOCK * runner.kv_cache.frames_per_block
ctx = state.get_kv_caches(kv_branch, seq_len=seq_len, commit_current=True)[0].forward_ctx
ctx.ensure_video_slots(torch.device("cpu"))
state.commit_paged_context(kv_branch)
return state
def test_ar_runner_rejects_pipeline_without_capability():
runner = object.__new__(ARDiffusionModelRunner)
runner.od_config = SimpleNamespace(max_num_seqs=1)
runner.pipeline = object()
with pytest.raises(TypeError, match="SupportsARDiffusionPipeline"):
runner._preallocate_kv_cache(available_bytes=1 << 20)
def test_runner_uses_typed_tick_as_authoritative_session_contract():
tick = ARDiffusionTickRequest(
session_id="world-7",
request_id="request-3",
chunk_index=3,
reset=True,
)
req = SimpleNamespace(
request_id="request-3",
sampling_params=SimpleNamespace(extra_args=tick.to_extra_args()),
)
session_id, extra_args, parsed = ARDiffusionModelRunner._request_session(req)
assert session_id == "world-7"
assert extra_args == tick.to_extra_args()
assert parsed == tick
def test_runner_keeps_engine_request_id_separate_from_tick_correlation_id():
tick = ARDiffusionTickRequest(
session_id="world-7",
request_id="client-request-3",
chunk_index=3,
)
req = SimpleNamespace(
request_id="engine-request-uuid",
sampling_params=SimpleNamespace(extra_args=tick.to_extra_args()),
)
session_id, _, parsed = ARDiffusionModelRunner._request_session(req)
assert session_id == "world-7"
assert req.request_id == "engine-request-uuid"
assert parsed.request_id == "client-request-3"
def test_lingbot_like_single_branch_session_reuse_reset_and_close():
pipeline = CapablePipeline(lingbot_like_spec())
runner = make_runner(pipeline)
kv = runner.kv_cache
assert kv is not None
assert kv.num_local_kv_branches == 1
assert kv.frames_per_block == 3
assert kv.spec.window_chunks == 5
assert kv.spec.sink_chunks == 1
assert kv.cross_attention_lengths == {"text": 8}
first = commit_one_frame(runner, "s1", "main")
assert runner._get_or_create_session("s1") is first
k = torch.randn(1, 8, 4, 64)
v = torch.randn(1, 8, 4, 64)
first.populate_cross_attention("main", "text", [(k, v)] * first.num_layers)
assert first.get_cross_attention_kv("main", "text")[0]["k"].shape == k.shape
runner.reset_session("s1")
assert "s1" not in runner._sessions
assert "s1" not in kv._cross_sessions
assert pipeline.resets == ["s1"]
second = runner._get_or_create_session("s1")
assert second is not first
runner.close_session("s1")
assert "s1" not in runner._sessions
assert pipeline.closes == ["s1"]
def test_lingbot_like_interleaved_sessions_keep_independent_kv_partitions():
runner = make_runner(CapablePipeline(lingbot_like_spec(capacity=2)))
kv = runner.kv_cache
assert kv is not None
session_a = commit_one_block(runner, "world-a", "main")
a0_blocks = kv.window_block_ids(session_a.adapter("main"))
session_b = commit_one_block(runner, "world-b", "main")
b0_blocks = kv.window_block_ids(session_b.adapter("main"))
session_a_again = commit_one_block(runner, "world-a", "main")
a1_blocks = kv.window_block_ids(session_a_again.adapter("main"))
assert session_a_again is session_a
assert session_a.adapter("main").request_id == "ar::world-a::main"
assert session_b.adapter("main").request_id == "ar::world-b::main"
assert session_a.adapter("main").completed_chunks == 6
assert session_b.adapter("main").completed_chunks == 3
assert len(a0_blocks) == 3
assert len(b0_blocks) == 3
assert len(a1_blocks) == 6
assert set(a0_blocks) <= set(a1_blocks)
assert set(a1_blocks).isdisjoint(b0_blocks)
assert tuple(runner._sessions) == ("world-b", "world-a")
def test_lingbot_like_sink_survives_sliding_window_eviction():
runner = make_runner(CapablePipeline(lingbot_like_spec()))
kv = runner.kv_cache
assert kv is not None
state = commit_one_frame(runner, "s1", "main")
sink_block = kv.block_table(state.adapter("main"))[0]
for _ in range(7):
state = commit_one_frame(runner, "s1", "main")
adapter = state.adapter("main")
table = kv.block_table(adapter)
assert len(table) == 6 # one sink + five recent frames
assert table[0] == sink_block
assert kv.null_block_id not in table
assert adapter.absolute_num_computed_tokens == 8 * BLOCK
assert adapter.num_computed_tokens == 6 * BLOCK
ctx = state.get_kv_caches("main", seq_len=BLOCK, commit_current=False)[0].forward_ctx
visible, _ = ctx.video_block_table(torch.device("cpu"))
assert visible[0] == table[0]
assert len(visible) == 6 # sink + recent window, including current scratch
def test_dreamzero_like_two_branches_are_independent():
runner = make_runner(CapablePipeline(dreamzero_like_spec()))
kv = runner.kv_cache
assert kv is not None and kv.num_local_kv_branches == 2
state = commit_one_frame(runner, "s1", POS)
assert len(kv.window_block_ids(state.adapter(POS))) == 1
assert kv.window_block_ids(state.adapter(NEG)) == []
def test_lru_eviction_releases_blocks_and_notifies_pipeline():
pipeline = CapablePipeline(lingbot_like_spec(capacity=2))
runner = make_runner(pipeline)
kv = runner.kv_cache
assert kv is not None
assert runner._session_capacity == 2
assert kv.session_capacity == 2
free_total = kv.manager.block_pool.get_num_free_blocks()
old = commit_one_frame(runner, "old", "main")
k = torch.randn(1, 8, 4, 64)
old.populate_cross_attention("main", "text", [(k, k)] * old.num_layers)
assert kv.manager.block_pool.get_num_free_blocks() < free_total
runner._get_or_create_session("new")
assert tuple(runner._sessions) == ("old", "new")
runner._get_or_create_session("newest")
assert tuple(runner._sessions) == ("new", "newest")
assert pipeline.closes == ["old"]
assert "old" not in kv._cross_sessions
assert kv.manager.block_pool.get_num_free_blocks() == free_total
def test_budget_reduced_capacity_drives_runner_lru():
pipeline = CapablePipeline(tiny_spec(capacity=2))
# One tiny LingBot-like session requires 1,808 bytes; two require 2,592.
#
# These read 128 and 192 while the paging unit was the frame. tiny_spec
# declares one token per frame, so it used to page a single token at a
# time -- a block size no attention kernel accepts. Paging at 16 makes
# each page sixteen times larger. The block *counts* are unchanged, and
# so is what this test is about: a budget that fits fewer sessions than
# the pipeline asked for must drive the runner's LRU.
runner = make_runner(
pipeline,
available_bytes=2048,
gpu_memory_fraction=1.0,
)
kv = runner.kv_cache
assert kv is not None
assert kv.requested_session_capacity == 2
assert kv.session_capacity == 1
assert runner._session_capacity == 1
runner._get_or_create_session("old")
runner._get_or_create_session("new")
assert tuple(runner._sessions) == ("new",)
assert pipeline.closes == ["old"]
def test_dreamzero_like_requested_capacity_is_capped_by_budget():
spec = dreamzero_like_spec(capacity=64)
# Per all-layer self-KV page: 65,536 bytes. Two resident sessions need:
# managed=(2 * (2 * 6 + 4) + 2)=34 pages, scratch=8 pages,
# cross-attention=1 page/session, for 44 pages total.
page_bytes = 65_536
pipeline = CapablePipeline(spec)
runner = make_runner(
pipeline,
available_bytes=44 * page_bytes,
gpu_memory_fraction=1.0,
)
kv = runner.kv_cache
assert kv is not None
assert kv.requested_session_capacity == 64
assert kv.session_capacity == 2
assert runner._session_capacity == 2
assert kv.cross_attention_reserved_bytes == 2 * page_bytes
def test_forward_exception_releases_pending_allocation_and_model_state(monkeypatch):
pipeline = CapablePipeline(lingbot_like_spec())
runner = make_runner(pipeline)
kv = runner.kv_cache
assert kv is not None
free_total = kv.manager.block_pool.get_num_free_blocks()
def boom(self, req, kv_prefetch_job=None):
state = pipeline.bound_state
ctx = state.get_kv_caches("main", seq_len=BLOCK, commit_current=True)[0].forward_ctx
ctx.ensure_video_slots(torch.device("cpu"))
raise RuntimeError("layer exploded")
monkeypatch.setattr(DiffusionModelRunner, "execute_model", boom)
request = SimpleNamespace(
request_id="broken-request",
sampling_params=SimpleNamespace(extra_args={"session_id": "broken"}),
)
with pytest.raises(RuntimeError, match="layer exploded"):
runner.execute_model(request)
assert pipeline.bound_state is None
assert pipeline.closes == ["broken"]
assert not runner._sessions
assert not kv._adapters
assert kv.manager.block_pool.get_num_free_blocks() == free_total
def test_synchronize_exception_uses_forward_cleanup_path(monkeypatch):
pipeline = CapablePipeline(lingbot_like_spec())
runner = make_runner(pipeline)
kv = runner.kv_cache
assert kv is not None
free_total = kv.manager.block_pool.get_num_free_blocks()
def return_after_allocation(self, req, kv_prefetch_job=None):
state = pipeline.bound_state
ctx = state.get_kv_caches("main", seq_len=BLOCK, commit_current=True)[0].forward_ctx
ctx.ensure_video_slots(torch.device("cpu"))
return object()
def synchronize_boom():
raise RuntimeError("asynchronous kernel failed")
monkeypatch.setattr(DiffusionModelRunner, "execute_model", return_after_allocation)
# The runner defers the device barrier to the platform, so the failure is
# injected there rather than on a device-specific torch entry point.
monkeypatch.setattr(
"vllm_omni.experimental.ar_diffusion.runner.current_omni_platform",
SimpleNamespace(synchronize=synchronize_boom),
)
request = SimpleNamespace(
request_id="broken-request",
sampling_params=SimpleNamespace(extra_args={"session_id": "broken"}),
)
with pytest.raises(RuntimeError, match="asynchronous kernel failed"):
runner.execute_model(request)
assert pipeline.bound_state is None
assert pipeline.closes == ["broken"]
assert not runner._sessions
assert not kv._adapters
assert not runner._perf_e2e_times
assert kv.manager.block_pool.get_num_free_blocks() == free_total
def test_ar_runner_rejects_step_and_request_batch_modes():
with pytest.raises(ValueError, match="step_execution=True"):
make_runner(CapablePipeline(lingbot_like_spec()), step_execution=True)
with pytest.raises(ValueError, match="request-batch execution"):
make_runner(BatchCapablePipeline(lingbot_like_spec()))
def test_ar_runner_allows_step_execution_when_pipeline_implements_the_contract():
runner = make_runner(StepCapablePipeline(lingbot_like_spec()), step_execution=True)
assert runner.kv_cache is not None
def test_ar_runner_defensively_rejects_inherited_batch_and_step_entrypoints():
runner = object.__new__(ARDiffusionModelRunner)
with pytest.raises(RuntimeError, match="request-batch execution"):
runner.execute_model_batch(None, None)
with pytest.raises(RuntimeError, match="step execution"):
runner.execute_stepwise(None)
def test_execute_stepwise_binds_request_id_session_and_unbinds_after_call(monkeypatch):
from vllm_omni.diffusion.worker.utils import BatchRunnerOutput, RunnerOutput
pipeline = StepCapablePipeline(lingbot_like_spec())
runner = make_runner(pipeline, step_execution=True)
bound_during = []
def fake_execute(self, scheduler_output):
del self, scheduler_output
bound_during.append(pipeline.bound_state is not None)
return BatchRunnerOutput.from_list([RunnerOutput(request_id="req-1", finished=False)])
monkeypatch.setattr(DiffusionModelRunner, "execute_stepwise", fake_execute)
output = runner.execute_stepwise(scheduler_output())
assert bound_during == [True]
assert pipeline.bound_state is None
assert pipeline.binds == ["req-1"]
assert "req-1" in runner._sessions
assert output.get_request_output("req-1") is not None
assert not pipeline.closes
def test_execute_stepwise_closes_session_when_request_finishes(monkeypatch):
from vllm_omni.diffusion.worker.utils import BatchRunnerOutput, RunnerOutput
pipeline = StepCapablePipeline(lingbot_like_spec())
runner = make_runner(pipeline, step_execution=True)
def fake_execute(self, scheduler_output):
del self, scheduler_output
return BatchRunnerOutput.from_list([RunnerOutput(request_id="req-1", finished=True)])
monkeypatch.setattr(DiffusionModelRunner, "execute_stepwise", fake_execute)
runner.execute_stepwise(scheduler_output())
assert pipeline.bound_state is None
assert pipeline.closes == ["req-1"]
assert "req-1" not in runner._sessions
def test_execute_stepwise_exception_releases_kv_and_does_not_resume_pages(monkeypatch):
pipeline = StepCapablePipeline(lingbot_like_spec())
runner = make_runner(pipeline, step_execution=True)
first = runner._get_or_create_session("req-1")
def boom(self, scheduler_output):
del self, scheduler_output
raise RuntimeError("step failed")
monkeypatch.setattr(DiffusionModelRunner, "execute_stepwise", boom)
with pytest.raises(RuntimeError, match="step failed"):
runner.execute_stepwise(scheduler_output())
assert pipeline.bound_state is None
assert pipeline.closes == ["req-1"]
assert "req-1" not in runner._sessions
second = runner._get_or_create_session("req-1")
assert second is not first
def test_execute_stepwise_closes_session_for_scheduler_aborted_request(monkeypatch):
from vllm_omni.diffusion.worker.utils import BatchRunnerOutput
pipeline = StepCapablePipeline(lingbot_like_spec())
runner = make_runner(pipeline, step_execution=True)
runner._get_or_create_session("req-1")
def fake_execute(self, scheduler_output):
del self, scheduler_output
return BatchRunnerOutput.from_list([])
monkeypatch.setattr(DiffusionModelRunner, "execute_stepwise", fake_execute)
# An aborted request is never scheduled again and never reports finished,
# so the retire path is the only thing that can free its KV.
runner.execute_stepwise(scheduler_output(request_ids=(), finished=("req-1",)))
assert "req-1" not in runner._sessions
assert pipeline.closes == ["req-1"]
def test_execute_stepwise_times_once_per_chunk_not_once_per_step(monkeypatch, platform_synchronize_calls):
from vllm_omni.diffusion.data import DiffusionOutput
from vllm_omni.diffusion.worker.utils import BatchRunnerOutput, RunnerOutput
pipeline = StepCapablePipeline(lingbot_like_spec())
runner = make_runner(pipeline, step_execution=True)
results = [None, None, None, DiffusionOutput()]
def fake_execute(self, scheduler_output):
del self, scheduler_output
return BatchRunnerOutput.from_list([RunnerOutput(request_id="req-1", finished=False, result=results.pop(0))])
monkeypatch.setattr(DiffusionModelRunner, "execute_stepwise", fake_execute)
for _ in range(4):
runner.execute_stepwise(scheduler_output())
# Four denoise steps, one emitted AR block: one timing sample, matching the
# one-sample-per-block meaning request mode already has.
assert len(runner._perf_e2e_times) == 1
assert len(platform_synchronize_calls) == 1
assert "req-1" not in runner._stepwise_chunk_started
def test_execute_stepwise_synchronize_exception_fails_closed(monkeypatch):
"""The chunk barrier is where an asynchronous device error surfaces. It must
clean up like an in-step failure instead of leaving the session, the cached
step state and the timing entry to the scheduler's next cycle."""
from vllm_omni.diffusion.worker.utils import BatchRunnerOutput, RunnerOutput
pipeline = StepCapablePipeline(lingbot_like_spec())
runner = make_runner(pipeline, step_execution=True)
runner.state_cache = {"req-1": object()}
def fake_execute(self, scheduler_output):
del self, scheduler_output
return BatchRunnerOutput.from_list([RunnerOutput(request_id="req-1", finished=False, result=object())])
def synchronize_boom():
raise RuntimeError("asynchronous kernel failed")
monkeypatch.setattr(DiffusionModelRunner, "execute_stepwise", fake_execute)
monkeypatch.setattr(
"vllm_omni.experimental.ar_diffusion.runner.current_omni_platform",
SimpleNamespace(synchronize=synchronize_boom),
)
with pytest.raises(RuntimeError, match="asynchronous kernel failed"):
runner.execute_stepwise(scheduler_output())
assert pipeline.bound_state is None
assert pipeline.closes == ["req-1"]
assert "req-1" not in runner._sessions
assert "req-1" not in runner.state_cache
assert "req-1" not in runner._stepwise_chunk_started
assert not runner._perf_e2e_times
def test_model_specific_warmup_provider_is_consumed(monkeypatch):
requests = [object(), object()]
pipeline = WarmupPipeline(lingbot_like_spec(), requests)
runner = make_runner(pipeline)
seen: list[object] = []
monkeypatch.setattr(runner, "execute_model", seen.append)
runner._warmup_ar_rollout()
assert seen == requests
assert pipeline.closes == [runner._WARMUP_SID]
def test_pipeline_without_warmup_provider_is_safely_skipped(monkeypatch):
pipeline = CapablePipeline(lingbot_like_spec())
runner = make_runner(pipeline)
execute = SimpleNamespace(called=False)
def fail_if_called(request):
execute.called = True
monkeypatch.setattr(runner, "execute_model", fail_if_called)
runner._warmup_ar_rollout()
assert execute.called is False
# ── paging unit vs eviction unit ────────────────────────────────────────────
def test_a_frame_that_is_already_a_legal_block_keeps_its_paging():
"""Every resolution that runs today must page exactly as it does today."""
from vllm_omni.experimental.ar_diffusion.runner import paging_block_size
for tokens_per_frame in (16, 320, 1440, 4096):
assert tokens_per_frame % 16 == 0
assert paging_block_size(tokens_per_frame) == tokens_per_frame
def test_a_frame_the_kernel_would_reject_pages_at_the_finest_legal_unit():
"""832x480 is the checkpoint's own default and the kernel rejects it.
(480/16) x (832/16) = 1560 tokens per frame, and 1560 % 16 == 8, so a
frame-sized block is not something FlashAttention's paged kernel accepts.
"""
from vllm_omni.experimental.ar_diffusion.runner import paging_block_size
assert 1560 % 16 == 8
assert paging_block_size(1560) == 16
# And whatever it returns is always something the kernel takes.
for tokens_per_frame in range(1, 200):
assert paging_block_size(tokens_per_frame) % 16 == 0
# ── the kernel's constraint is data, not a constant ─────────────────────────
def _multiple_of(base):
from vllm.v1.attention.backend import MultipleOf
return MultipleOf(base)
def test_the_dispatching_module_answers_in_vllms_own_vocabulary():
"""AR-Diffusion calls flash_attn itself, so no backend can be asked.
The answer has to come from the module that picks the kernel, and it has to
have the shape vLLM uses, so the policy that consumes it does not care
where it came from.
"""
from vllm.v1.attention.backend import MultipleOf
from vllm_omni.experimental.ar_diffusion.kv_cache.paged_attention import supported_kernel_block_sizes
advertised = supported_kernel_block_sizes()
assert advertised, "a kernel that accepts nothing cannot be paged for"
for entry in advertised:
assert isinstance(entry, int | MultipleOf)
def test_a_kernel_that_takes_one_fixed_size_gets_that_size():
"""hpc_attn advertises [64] -- not a multiple, a single legal value.
A frame of 1560 tokens is not 64, and 1560 % 64 == 24, so the frame cannot
be the block and the only thing left to page at is 64. Assuming multiples
of 16 here would hand the kernel a block size it rejects.
"""
from vllm_omni.experimental.ar_diffusion.runner import paging_block_size
assert 1560 % 64 == 24
assert paging_block_size(1560, [64]) == 64
assert paging_block_size(64, [64]) == 64
assert paging_block_size(128, [64]) == 64 # a legal multiple is still not the advertised size
def test_a_kernel_with_large_pages_still_keeps_a_whole_frame_when_it_fits():
"""FlashInfer offers 128-and-up pages on Blackwell and not on Hopper.
The same checkpoint on the two cards must therefore page differently, which
is exactly what a constant cannot express.
"""
from vllm_omni.experimental.ar_diffusion.runner import paging_block_size
hopper = [_multiple_of(16)]
blackwell = [_multiple_of(16), 128, 256]
assert paging_block_size(1440, hopper) == 1440 # a legal multiple of 16
assert paging_block_size(1440, blackwell) == 1440
assert paging_block_size(1560, hopper) == 16
assert paging_block_size(1560, blackwell) == 16 # finest legal unit, not the largest
def test_whatever_comes_back_is_always_something_the_kernel_accepts():
from vllm_omni.experimental.ar_diffusion.runner import paging_block_size
for advertised, check in (
([_multiple_of(16)], lambda n: n % 16 == 0),
([64], lambda n: n == 64),
([_multiple_of(32), 48], lambda n: n % 32 == 0 or n == 48),
):
for tokens_per_frame in range(1, 300):
assert check(paging_block_size(tokens_per_frame, advertised))
def test_a_kernel_that_advertises_nothing_is_an_error_not_a_guess():
from vllm_omni.experimental.ar_diffusion.runner import paging_block_size
with pytest.raises(ValueError, match="no legal block size"):
paging_block_size(1560, [])