567 lines
20 KiB
Python
567 lines
20 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
|
"""Bit-exact kernel equivalence for MLA decode/write kernels on the
|
|
cross-layer (block-major) KV cache layout.
|
|
|
|
The cross-layer layout carves each layer's per-block page out of a single
|
|
unified slot, so the per-layer view has an inflated ``stride(0)`` (the full
|
|
unified slot) and a non-zero storage offset. These tests confirm the MLA
|
|
kernels behind the backends that opt in to the layout (FlashMLA dense,
|
|
FlashInfer MLA dense, FlashMLA fp8 sparse, plus the ``concat_and_cache_mla``
|
|
write) honor that strided view bit-identically to a contiguous per-layer
|
|
cache, and that writes do not bleed into neighbouring layers' segments.
|
|
"""
|
|
|
|
import pytest
|
|
import torch
|
|
|
|
pytestmark = pytest.mark.skipif(
|
|
not torch.cuda.is_available(), reason="MLA cache kernels require CUDA"
|
|
)
|
|
|
|
|
|
def test_concat_and_cache_mla_into_unified_slot_view():
|
|
"""concat_and_cache_mla must write correctly into a per-layer view whose
|
|
block stride is the full unified slot (block-major), with zero bleed into
|
|
the other layers' segments of the same slot."""
|
|
from vllm import _custom_ops as ops
|
|
|
|
torch.manual_seed(0)
|
|
dev = "cuda"
|
|
kv_lora_rank = 512
|
|
pe = 64
|
|
entry = kv_lora_rank + pe
|
|
page = 64
|
|
num_blocks = 32
|
|
ntok = 200
|
|
|
|
kv_c = torch.randn(ntok, kv_lora_rank, device=dev, dtype=torch.bfloat16)
|
|
k_pe = torch.randn(ntok, pe, device=dev, dtype=torch.bfloat16)
|
|
slot = torch.randperm(num_blocks * page, device=dev, dtype=torch.int64)[:ntok]
|
|
scale = torch.tensor(1.0, device=dev)
|
|
|
|
def write(cache):
|
|
ops.concat_and_cache_mla(kv_c, k_pe, cache, slot, "auto", scale)
|
|
|
|
# Contiguous per-layer reference: (num_blocks, page, entry).
|
|
ref = torch.zeros(num_blocks, page, entry, device=dev, dtype=torch.bfloat16)
|
|
write(ref)
|
|
|
|
# Unified slot holding three layer pages per block. Carve the middle
|
|
# layer's view (non-zero offset, block stride == full unified slot).
|
|
layer_page_elems = page * entry
|
|
n_layers = 3
|
|
unified_slot_elems = n_layers * layer_page_elems
|
|
big = torch.zeros(num_blocks, unified_slot_elems, device=dev, dtype=torch.bfloat16)
|
|
flat = big.view(-1)
|
|
offset = layer_page_elems # middle layer
|
|
view = torch.as_strided(
|
|
flat,
|
|
size=(num_blocks, page, entry),
|
|
stride=(unified_slot_elems, entry, 1),
|
|
storage_offset=offset,
|
|
)
|
|
assert not view.is_contiguous()
|
|
assert view.stride(0) == unified_slot_elems
|
|
write(view)
|
|
|
|
# Bit-exact equivalence and zero bleed into the neighbour segments.
|
|
max_diff = (ref.float() - view.float()).abs().max().item()
|
|
assert max_diff == 0.0, f"max|Δ| = {max_diff}"
|
|
|
|
neighbour_lo = torch.as_strided(
|
|
flat, (num_blocks, layer_page_elems), (unified_slot_elems, 1), 0
|
|
)
|
|
neighbour_hi = torch.as_strided(
|
|
flat,
|
|
(num_blocks, layer_page_elems),
|
|
(unified_slot_elems, 1),
|
|
2 * layer_page_elems,
|
|
)
|
|
assert neighbour_lo.abs().max().item() == 0.0
|
|
assert neighbour_hi.abs().max().item() == 0.0
|
|
|
|
|
|
def test_flashmla_dense_decode_unified_slot_view():
|
|
"""FlashMLA dense decode (FLASHMLA backend, e.g. Kimi-K2-style dense MLA
|
|
on Hopper) must read a unified-slot block-major view bit-identically to a
|
|
contiguous per-layer cache."""
|
|
import vllm.v1.attention.ops.flashmla as fm
|
|
|
|
ok, reason = fm.is_flashmla_dense_supported()
|
|
if not ok:
|
|
pytest.skip(reason)
|
|
|
|
torch.manual_seed(0)
|
|
dev = "cuda"
|
|
dt = torch.bfloat16
|
|
head_dim = 576
|
|
hdv = 512
|
|
h_q = 128
|
|
page = 64
|
|
num_blocks = 64
|
|
bs = 4
|
|
n_layers = 3
|
|
layer = 1
|
|
|
|
q = torch.randn(bs, 1, h_q, head_dim, device=dev, dtype=dt) * 0.1
|
|
kv_data = torch.randn(num_blocks, page, 1, head_dim, device=dev, dtype=dt) * 0.1
|
|
|
|
# (A) contiguous per-layer reference.
|
|
cache_contiguous = kv_data.clone().contiguous()
|
|
|
|
# (B) unified slot: view one layer -> inflated stride(0), non-zero offset.
|
|
unified = (
|
|
torch.randn(num_blocks, n_layers, page, 1, head_dim, device=dev, dtype=dt) * 0.1
|
|
)
|
|
unified[:, layer].copy_(kv_data)
|
|
cache_view = unified[:, layer]
|
|
assert not cache_view.is_contiguous()
|
|
assert cache_view.stride(0) == n_layers * page * 1 * head_dim
|
|
|
|
max_blk = num_blocks // bs
|
|
block_table = torch.arange(num_blocks, device=dev, dtype=torch.int32).view(
|
|
bs, max_blk
|
|
)
|
|
cache_seqlens = torch.full((bs,), max_blk * page, device=dev, dtype=torch.int32)
|
|
|
|
def run(kc):
|
|
meta, num_splits = fm.get_mla_metadata()
|
|
out, _ = fm.flash_mla_with_kvcache(
|
|
q=q,
|
|
k_cache=kc,
|
|
block_table=block_table,
|
|
cache_seqlens=cache_seqlens,
|
|
head_dim_v=hdv,
|
|
tile_scheduler_metadata=meta,
|
|
num_splits=num_splits,
|
|
softmax_scale=head_dim**-0.5,
|
|
causal=True,
|
|
)
|
|
return out.clone().float()
|
|
|
|
out_ref = run(cache_contiguous)
|
|
out_view = run(cache_view)
|
|
assert torch.isfinite(out_ref).all()
|
|
assert out_ref.abs().max().item() > 0.0
|
|
assert (out_ref - out_view).abs().max().item() == 0.0
|
|
|
|
|
|
def test_flashinfer_mla_dense_decode_unified_slot_view():
|
|
"""FlashInfer MLA dense decode must read a unified-slot block-major view
|
|
(inflated stride(0), non-zero storage offset) bit-identically to a
|
|
contiguous per-layer cache."""
|
|
try:
|
|
from flashinfer.decode import trtllm_batch_decode_with_kv_cache_mla
|
|
except ImportError:
|
|
pytest.skip("flashinfer is not available")
|
|
from vllm.platforms import current_platform
|
|
|
|
if not current_platform.is_device_capability_family(100):
|
|
pytest.skip("FlashInfer trtllm-gen MLA requires sm100")
|
|
|
|
torch.manual_seed(0)
|
|
dev = "cuda"
|
|
dt = torch.bfloat16
|
|
kv_lora_rank = 512
|
|
qk_rope_head_dim = 64
|
|
qk_nope_head_dim = 128
|
|
head_dim = kv_lora_rank + qk_rope_head_dim # 576
|
|
num_qo_heads = 128
|
|
page = 64
|
|
num_blocks = 64
|
|
bs = 4
|
|
n_layers = 3 # >1 so the per-layer view's block stride is inflated.
|
|
layer = 1
|
|
|
|
q = torch.randn(bs, 1, num_qo_heads, head_dim, device=dev, dtype=dt)
|
|
kv_data = torch.randn(num_blocks, 1, page, head_dim, device=dev, dtype=dt)
|
|
|
|
# (A) contiguous per-layer reference.
|
|
kv_contiguous = kv_data.clone().contiguous()
|
|
|
|
# (B) unified slot: block b of every layer packed together; view one layer
|
|
# -> stride(0) is n_layers x larger and storage offset is non-zero.
|
|
unified = torch.randn(num_blocks, n_layers, 1, page, head_dim, device=dev, dtype=dt)
|
|
unified[:, layer].copy_(kv_data)
|
|
kv_view = unified[:, layer]
|
|
assert not kv_view.is_contiguous()
|
|
assert kv_view.stride(0) == n_layers * 1 * page * head_dim
|
|
|
|
max_blk = num_blocks // bs
|
|
block_tables = torch.arange(num_blocks, device=dev, dtype=torch.int32).view(
|
|
bs, max_blk
|
|
)
|
|
seq_lens = torch.full((bs,), max_blk * page, device=dev, dtype=torch.int32)
|
|
ws = torch.empty(128 * 1024 * 1024, dtype=torch.int8, device=dev)
|
|
scale = head_dim**-0.5
|
|
|
|
def run(kv):
|
|
return trtllm_batch_decode_with_kv_cache_mla(
|
|
query=q,
|
|
kv_cache=kv,
|
|
workspace_buffer=ws,
|
|
qk_nope_head_dim=qk_nope_head_dim,
|
|
kv_lora_rank=kv_lora_rank,
|
|
qk_rope_head_dim=qk_rope_head_dim,
|
|
block_tables=block_tables,
|
|
seq_lens=seq_lens,
|
|
max_seq_len=int(seq_lens.max().item()),
|
|
bmm1_scale=scale,
|
|
bmm2_scale=1.0,
|
|
).clone()
|
|
|
|
out_ref = run(kv_contiguous).float()
|
|
out_view = run(kv_view).float()
|
|
assert torch.isfinite(out_ref).all()
|
|
assert (out_ref - out_view).abs().max().item() == 0.0
|
|
|
|
|
|
def test_flashmla_fp8_sparse_decode_unified_slot_view():
|
|
"""FlashMLA fp8 sparse decode (DeepSeek V3.2/V4 DSA path) must read a
|
|
unified-slot block-major view bit-identically to a contiguous fp8_ds_mla
|
|
cache, with finite nonzero output."""
|
|
import vllm.v1.attention.ops.flashmla as fm
|
|
|
|
ok, reason = fm.is_flashmla_sparse_supported()
|
|
if not ok:
|
|
pytest.skip(reason)
|
|
|
|
torch.manual_seed(0)
|
|
dev = "cuda"
|
|
entry = 656 # fp8_ds_mla bytes per token
|
|
page = 64
|
|
num_blocks = 32
|
|
h_q = 128
|
|
head_dim = 576
|
|
hdv = 512
|
|
batch = 2
|
|
topk = 128
|
|
n_layers = 3
|
|
layer = 1
|
|
|
|
q = torch.randn(batch, 1, h_q, head_dim, device=dev, dtype=torch.bfloat16) * 0.1
|
|
|
|
# Structurally valid fp8 ds_mla payload: 512B fp8 + 16B f32 scales + 128B
|
|
# bf16 rope (random bytes corrupt the scale region and yield NaNs).
|
|
nope = (torch.randn(num_blocks, page, 1, 512, device=dev) * 0.1).to(
|
|
torch.float8_e4m3fn
|
|
)
|
|
scales = torch.ones(num_blocks, page, 1, 4, device=dev, dtype=torch.float32)
|
|
rope = (torch.randn(num_blocks, page, 1, 64, device=dev) * 0.1).to(torch.bfloat16)
|
|
payload = torch.cat(
|
|
[
|
|
nope.view(torch.uint8).view(num_blocks, page, 1, 512),
|
|
scales.view(torch.uint8).view(num_blocks, page, 1, 16),
|
|
rope.view(torch.uint8).view(num_blocks, page, 1, 128),
|
|
],
|
|
dim=-1,
|
|
).contiguous()
|
|
assert payload.shape[-1] == entry and payload.dtype == torch.uint8
|
|
|
|
# (A) contiguous reference.
|
|
cache_contiguous = payload.clone().contiguous()
|
|
|
|
# (B) unified slot: view one layer -> inflated stride(0), non-zero offset.
|
|
unified = torch.randint(
|
|
0, 256, (num_blocks, n_layers, page, 1, entry), device=dev, dtype=torch.uint8
|
|
)
|
|
unified[:, layer].copy_(payload)
|
|
cache_view = unified[:, layer]
|
|
assert not cache_view.is_contiguous()
|
|
assert cache_view.stride(0) == n_layers * page * 1 * entry
|
|
|
|
# Sparse indices: each batch uses its own disjoint blocks.
|
|
blocks_per_batch = num_blocks // batch
|
|
idx = torch.full((batch, 1, topk), -1, device=dev, dtype=torch.int32)
|
|
for b in range(batch):
|
|
slots: list[int] = []
|
|
for blk in range(b * blocks_per_batch, (b + 1) * blocks_per_batch):
|
|
slots.extend(blk * page + off for off in range(page))
|
|
slots_t = torch.tensor(slots[:topk], device=dev, dtype=torch.int32)
|
|
idx[b, 0, : slots_t.numel()] = slots_t
|
|
|
|
def run(kc):
|
|
meta, num_splits = fm.get_mla_metadata()
|
|
out, _ = fm.flash_mla_with_kvcache(
|
|
q=q,
|
|
k_cache=kc,
|
|
block_table=None,
|
|
cache_seqlens=None,
|
|
head_dim_v=hdv,
|
|
tile_scheduler_metadata=meta,
|
|
is_fp8_kvcache=True,
|
|
indices=idx,
|
|
softmax_scale=head_dim**-0.5,
|
|
)
|
|
return out.clone().float()
|
|
|
|
out_ref = run(cache_contiguous)
|
|
out_view = run(cache_view)
|
|
assert torch.isfinite(out_ref).all()
|
|
assert out_ref.abs().max().item() > 0.0
|
|
assert (out_ref - out_view).abs().max().item() == 0.0
|
|
|
|
|
|
def test_indexer_k_quant_and_cache_into_unified_slot_view():
|
|
"""indexer_k_quant_and_cache (DeepSeek V3.2/V4 DSA indexer K write) must
|
|
write correctly into a per-layer view whose block stride is the full
|
|
unified slot, with zero bleed into the other layers' segments."""
|
|
from vllm import _custom_ops as ops
|
|
|
|
torch.manual_seed(0)
|
|
dev = "cuda"
|
|
head_dim = 128
|
|
quant_block_size = 128
|
|
block_size = 64
|
|
num_blocks = 16
|
|
ntok = 100
|
|
# Indexer cache layout per token: head_dim fp8 bytes followed by
|
|
# head_dim * 4 / quant_block_size scale bytes.
|
|
cache_stride = head_dim + head_dim * 4 // quant_block_size
|
|
|
|
k = torch.randn(ntok, head_dim, device=dev, dtype=torch.bfloat16)
|
|
slot = torch.randperm(num_blocks * block_size, device=dev, dtype=torch.int64)[:ntok]
|
|
|
|
def write(cache):
|
|
ops.indexer_k_quant_and_cache(k, cache, slot, quant_block_size, "ue8m0")
|
|
|
|
# Contiguous per-layer reference.
|
|
ref = torch.zeros(
|
|
num_blocks, block_size, cache_stride, device=dev, dtype=torch.uint8
|
|
)
|
|
write(ref)
|
|
|
|
# Unified slot holding three layer pages per block; carve the middle one.
|
|
n_layers = 3
|
|
layer = 1
|
|
unified = torch.zeros(
|
|
num_blocks, n_layers, block_size, cache_stride, device=dev, dtype=torch.uint8
|
|
)
|
|
view = unified[:, layer]
|
|
assert not view.is_contiguous()
|
|
assert view.stride(0) == n_layers * block_size * cache_stride
|
|
write(view)
|
|
|
|
assert torch.equal(ref, view.contiguous())
|
|
# Zero bleed into the neighbour layers' segments.
|
|
assert unified[:, 0].abs().max().item() == 0
|
|
assert unified[:, 2].abs().max().item() == 0
|
|
|
|
|
|
def test_flashattn_mla_dense_decode_unified_slot_view():
|
|
"""FA3 decode (FLASH_ATTN_MLA backend) must read a unified-slot
|
|
block-major view bit-identically to a contiguous per-layer cache."""
|
|
try:
|
|
from vllm.vllm_flash_attn import flash_attn_varlen_func
|
|
except ImportError:
|
|
pytest.skip("vllm_flash_attn is not available")
|
|
from vllm.v1.attention.backends.fa_utils import flash_attn_supports_mla
|
|
|
|
if not flash_attn_supports_mla():
|
|
pytest.skip("FA3 MLA requires a Hopper device")
|
|
|
|
torch.manual_seed(0)
|
|
dev = "cuda"
|
|
dt = torch.bfloat16
|
|
kv_lora_rank = 512
|
|
rope_dim = 64
|
|
entry = kv_lora_rank + rope_dim # 576
|
|
h_q = 16
|
|
page = 64
|
|
num_blocks = 64
|
|
bs = 4
|
|
n_layers = 3
|
|
layer = 1
|
|
|
|
q_pe = torch.randn(bs, h_q, rope_dim, device=dev, dtype=dt) * 0.1
|
|
q_nope = torch.randn(bs, h_q, kv_lora_rank, device=dev, dtype=dt) * 0.1
|
|
kv_data = torch.randn(num_blocks, page, entry, device=dev, dtype=dt) * 0.1
|
|
|
|
# (A) contiguous per-layer reference.
|
|
cache_contiguous = kv_data.clone().contiguous()
|
|
|
|
# (B) unified slot: view one layer -> inflated stride(0), non-zero offset.
|
|
unified = torch.randn(num_blocks, n_layers, page, entry, device=dev, dtype=dt) * 0.1
|
|
unified[:, layer].copy_(kv_data)
|
|
cache_view = unified[:, layer]
|
|
assert not cache_view.is_contiguous()
|
|
assert cache_view.stride(0) == n_layers * page * entry
|
|
|
|
max_blk = num_blocks // bs
|
|
block_table = torch.arange(num_blocks, device=dev, dtype=torch.int32).view(
|
|
bs, max_blk
|
|
)
|
|
seq_lens = torch.full((bs,), max_blk * page, device=dev, dtype=torch.int32)
|
|
cu_seqlens_q = torch.arange(bs + 1, device=dev, dtype=torch.int32)
|
|
|
|
def run(cache):
|
|
kv_c_cache = cache[..., :kv_lora_rank]
|
|
k_pe_cache = cache[..., kv_lora_rank:]
|
|
out = flash_attn_varlen_func(
|
|
q=q_pe,
|
|
k=k_pe_cache.unsqueeze(-2), # Add head dim of 1
|
|
v=kv_c_cache.unsqueeze(-2), # Add head dim of 1
|
|
q_v=q_nope,
|
|
max_seqlen_q=1,
|
|
cu_seqlens_q=cu_seqlens_q,
|
|
max_seqlen_k=int(seq_lens.max().item()),
|
|
seqused_k=seq_lens,
|
|
block_table=block_table,
|
|
softmax_scale=entry**-0.5,
|
|
causal=True,
|
|
fa_version=3,
|
|
)
|
|
return out.clone().float()
|
|
|
|
out_ref = run(cache_contiguous)
|
|
out_view = run(cache_view)
|
|
assert torch.isfinite(out_ref).all()
|
|
assert out_ref.abs().max().item() > 0.0
|
|
assert (out_ref - out_view).abs().max().item() == 0.0
|
|
|
|
|
|
def test_flashmla_dense_fp8_decode_unified_slot_view():
|
|
"""FlashMLA dense fp8 decode (FLASHMLA backend with quantized KV cache)
|
|
must read a unified-slot block-major view bit-identically to a contiguous
|
|
per-layer fp8 cache."""
|
|
import vllm.v1.attention.ops.flashmla as fm
|
|
|
|
ok, reason = fm.is_flashmla_dense_supported()
|
|
if not ok:
|
|
pytest.skip(reason)
|
|
|
|
torch.manual_seed(0)
|
|
dev = "cuda"
|
|
head_dim = 576
|
|
hdv = 512
|
|
h_q = 128
|
|
page = 64
|
|
num_blocks = 64
|
|
bs = 4
|
|
n_layers = 3
|
|
layer = 1
|
|
|
|
q = torch.randn(bs, 1, h_q, head_dim, device=dev, dtype=torch.bfloat16) * 0.1
|
|
kv_data = (torch.randn(num_blocks, page, head_dim, device=dev) * 0.1).to(
|
|
torch.float8_e4m3fn
|
|
)
|
|
|
|
# (A) contiguous per-layer reference.
|
|
cache_contiguous = kv_data.clone().contiguous()
|
|
|
|
# (B) unified slot: view one layer -> inflated stride(0), non-zero offset.
|
|
unified = (torch.randn(num_blocks, n_layers, page, head_dim, device=dev) * 0.1).to(
|
|
torch.float8_e4m3fn
|
|
)
|
|
unified[:, layer].copy_(kv_data)
|
|
cache_view = unified[:, layer]
|
|
assert not cache_view.is_contiguous()
|
|
assert cache_view.stride(0) == n_layers * page * head_dim
|
|
|
|
max_blk = num_blocks // bs
|
|
block_table = torch.arange(num_blocks, device=dev, dtype=torch.int32).view(
|
|
bs, max_blk
|
|
)
|
|
cache_seqlens = torch.full((bs,), max_blk * page, device=dev, dtype=torch.int32)
|
|
descale = torch.ones(1, device=dev, dtype=torch.float32)
|
|
|
|
def run(kc):
|
|
tile_md, num_splits = fm.get_mla_metadata_dense_fp8(cache_seqlens, h_q, 1)
|
|
out, _ = fm.flash_mla_with_kvcache_fp8(
|
|
q=q,
|
|
k_cache=kc.unsqueeze(-2), # Add head dim of 1
|
|
block_table=block_table,
|
|
cache_seqlens=cache_seqlens,
|
|
head_dim_v=hdv,
|
|
tile_scheduler_metadata=tile_md,
|
|
num_splits=num_splits,
|
|
softmax_scale=head_dim**-0.5,
|
|
causal=True,
|
|
descale_q=descale,
|
|
descale_k=descale,
|
|
)
|
|
return out.clone().float()
|
|
|
|
out_ref = run(cache_contiguous)
|
|
out_view = run(cache_view)
|
|
assert torch.isfinite(out_ref).all()
|
|
assert out_ref.abs().max().item() > 0.0
|
|
assert (out_ref - out_view).abs().max().item() == 0.0
|
|
|
|
|
|
def test_flashinfer_mla_dense_fp8_decode_unified_slot_view():
|
|
"""FlashInfer MLA dense decode with an fp8 KV cache must read a
|
|
unified-slot block-major view bit-identically to a contiguous per-layer
|
|
cache."""
|
|
try:
|
|
from flashinfer.decode import trtllm_batch_decode_with_kv_cache_mla
|
|
except ImportError:
|
|
pytest.skip("flashinfer is not available")
|
|
from vllm.platforms import current_platform
|
|
|
|
if not current_platform.is_device_capability_family(100):
|
|
pytest.skip("FlashInfer trtllm-gen MLA requires sm100")
|
|
|
|
torch.manual_seed(0)
|
|
dev = "cuda"
|
|
kv_lora_rank = 512
|
|
qk_rope_head_dim = 64
|
|
qk_nope_head_dim = 128
|
|
head_dim = kv_lora_rank + qk_rope_head_dim # 576
|
|
num_qo_heads = 128
|
|
page = 64
|
|
num_blocks = 64
|
|
bs = 4
|
|
n_layers = 3
|
|
layer = 1
|
|
|
|
# With a quantized KV cache the decode query is quantized to fp8 as well
|
|
# (trtllm-gen has no bf16-query x fp8-cache decode kernel).
|
|
q = (torch.randn(bs, 1, num_qo_heads, head_dim, device=dev) * 0.1).to(
|
|
torch.float8_e4m3fn
|
|
)
|
|
kv_data = (torch.randn(num_blocks, 1, page, head_dim, device=dev) * 0.1).to(
|
|
torch.float8_e4m3fn
|
|
)
|
|
|
|
# (A) contiguous per-layer reference.
|
|
kv_contiguous = kv_data.clone().contiguous()
|
|
|
|
# (B) unified slot: view one layer -> inflated stride(0), non-zero offset.
|
|
unified = (
|
|
torch.randn(num_blocks, n_layers, 1, page, head_dim, device=dev) * 0.1
|
|
).to(torch.float8_e4m3fn)
|
|
unified[:, layer].copy_(kv_data)
|
|
kv_view = unified[:, layer]
|
|
assert not kv_view.is_contiguous()
|
|
assert kv_view.stride(0) == n_layers * 1 * page * head_dim
|
|
|
|
max_blk = num_blocks // bs
|
|
block_tables = torch.arange(num_blocks, device=dev, dtype=torch.int32).view(
|
|
bs, max_blk
|
|
)
|
|
seq_lens = torch.full((bs,), max_blk * page, device=dev, dtype=torch.int32)
|
|
ws = torch.empty(128 * 1024 * 1024, dtype=torch.int8, device=dev)
|
|
scale = head_dim**-0.5
|
|
|
|
def run(kv):
|
|
return trtllm_batch_decode_with_kv_cache_mla(
|
|
query=q,
|
|
kv_cache=kv,
|
|
workspace_buffer=ws,
|
|
qk_nope_head_dim=qk_nope_head_dim,
|
|
kv_lora_rank=kv_lora_rank,
|
|
qk_rope_head_dim=qk_rope_head_dim,
|
|
block_tables=block_tables,
|
|
seq_lens=seq_lens,
|
|
max_seq_len=int(seq_lens.max().item()),
|
|
bmm1_scale=scale,
|
|
bmm2_scale=1.0,
|
|
).clone()
|
|
|
|
out_ref = run(kv_contiguous).float()
|
|
out_view = run(kv_view).float()
|
|
assert torch.isfinite(out_ref).all()
|
|
assert (out_ref - out_view).abs().max().item() == 0.0
|