[ROCm][Model][Bugfix] Enable GLM-5.2-MXFP4 on the deepseek_v32 path and fix sparse attention correctness (#51915)
Signed-off-by: Jack Hu <Jack.Hu@amd.com> Signed-off-by: Jack Hu <jack.hu@amd.com> Signed-off-by: Douglas Lehr <Doug.Lehr@amd.com> Co-authored-by: James E T Smith <jamesETsmith@users.noreply.github.com> Co-authored-by: Douglas Lehr <91553416+dllehr-amd@users.noreply.github.com> Co-authored-by: Douglas Lehr <Doug.Lehr@amd.com>
This commit is contained in:
co-authored by
James E T Smith
Douglas Lehr
Douglas Lehr
parent
9646f53064
commit
1c0eee919d
@@ -24,6 +24,8 @@ outputs are checked within 1 representable-step (ULP); bf16 norm/RoPE outputs us
|
||||
rtol/atol=1e-2 (the tolerance the sibling deepseek_v4 fused-kernel test uses).
|
||||
"""
|
||||
|
||||
from typing import NamedTuple
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
@@ -33,19 +35,30 @@ from vllm.platforms import current_platform
|
||||
FP8 = torch.float8_e4m3fn
|
||||
FP8_MAX = 448.0
|
||||
|
||||
# GLM-5.2 / DeepSeek-V3.2 shapes (TP8 local heads).
|
||||
Q_LORA = 2048
|
||||
KV_LORA = 512
|
||||
ROPE_DIM = 64
|
||||
NUM_HEADS = 8
|
||||
INDEX_HEADS = 32
|
||||
INDEX_HEAD_DIM = 128
|
||||
HIDDEN = 6144
|
||||
EPS = 1e-6
|
||||
|
||||
|
||||
class ModelConfig(NamedTuple):
|
||||
"""Per-model attention dims (num_heads is the TP8 local count)."""
|
||||
|
||||
q_lora: int
|
||||
kv_lora: int
|
||||
rope_dim: int
|
||||
num_heads: int
|
||||
index_heads: int
|
||||
index_head_dim: int
|
||||
hidden: int
|
||||
|
||||
|
||||
MODEL_CONFIGS = [
|
||||
ModelConfig(2048, 512, 64, 8, 32, 128, 6144), # GLM-5.2
|
||||
ModelConfig(1536, 512, 64, 16, 64, 128, 7168), # DeepSeek-V3.2
|
||||
]
|
||||
MODEL_IDS = ["glm5.2", "dsv3.2"]
|
||||
|
||||
pytestmark = pytest.mark.skipif(
|
||||
not current_platform.is_cuda() or not current_platform.has_device_capability(89),
|
||||
reason="deepseek_v32 fused kernels require CUDA with fp8 (SM89+)",
|
||||
not current_platform.supports_fp8(),
|
||||
reason="deepseek_v32 fused kernels require fp8 support",
|
||||
)
|
||||
|
||||
|
||||
@@ -144,29 +157,32 @@ def assert_fp8(got: torch.Tensor, ref: torch.Tensor, msg: str):
|
||||
# ── fused_norm_rope ──────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@pytest.mark.parametrize("cfg", MODEL_CONFIGS, ids=MODEL_IDS)
|
||||
@pytest.mark.parametrize("num_tokens", [1, 4, 17, 512, 4096])
|
||||
@pytest.mark.parametrize("index_interleave", [True, False])
|
||||
@pytest.mark.parametrize("mla_dtype", ["auto", "bfloat16", "fp8"])
|
||||
def test_fused_norm_rope(num_tokens: int, index_interleave: bool, mla_dtype: str):
|
||||
def test_fused_norm_rope(
|
||||
num_tokens: int, index_interleave: bool, mla_dtype: str, cfg: ModelConfig
|
||||
):
|
||||
torch.manual_seed(0)
|
||||
dev = "cuda"
|
||||
max_pos = 8192
|
||||
pos = torch.arange(num_tokens, device=dev, dtype=torch.int64) % max_pos
|
||||
|
||||
q_c = torch.randn(num_tokens, Q_LORA, device=dev, dtype=torch.bfloat16)
|
||||
kv_c = torch.randn(num_tokens, KV_LORA, device=dev, dtype=torch.bfloat16)
|
||||
k_pe = torch.randn(num_tokens, ROPE_DIM, device=dev, dtype=torch.bfloat16)
|
||||
qw = torch.randn(Q_LORA, device=dev, dtype=torch.bfloat16)
|
||||
kvw = torch.randn(KV_LORA, device=dev, dtype=torch.bfloat16)
|
||||
ik = torch.randn(num_tokens, INDEX_HEAD_DIM, device=dev, dtype=torch.bfloat16)
|
||||
ikw = torch.randn(INDEX_HEAD_DIM, device=dev, dtype=torch.float32)
|
||||
ikb = torch.randn(INDEX_HEAD_DIM, device=dev, dtype=torch.float32)
|
||||
q_c = torch.randn(num_tokens, cfg.q_lora, device=dev, dtype=torch.bfloat16)
|
||||
kv_c = torch.randn(num_tokens, cfg.kv_lora, device=dev, dtype=torch.bfloat16)
|
||||
k_pe = torch.randn(num_tokens, cfg.rope_dim, device=dev, dtype=torch.bfloat16)
|
||||
qw = torch.randn(cfg.q_lora, device=dev, dtype=torch.bfloat16)
|
||||
kvw = torch.randn(cfg.kv_lora, device=dev, dtype=torch.bfloat16)
|
||||
ik = torch.randn(num_tokens, cfg.index_head_dim, device=dev, dtype=torch.bfloat16)
|
||||
ikw = torch.randn(cfg.index_head_dim, device=dev, dtype=torch.float32)
|
||||
ikb = torch.randn(cfg.index_head_dim, device=dev, dtype=torch.float32)
|
||||
|
||||
mla_cos_sin = make_cos_sin(max_pos, ROPE_DIM, dev) # MLA k_pe: interleaved
|
||||
idx_cos_sin = make_cos_sin(max_pos, ROPE_DIM, dev)
|
||||
mla_cos_sin = make_cos_sin(max_pos, cfg.rope_dim, dev) # MLA k_pe: interleaved
|
||||
idx_cos_sin = make_cos_sin(max_pos, cfg.rope_dim, dev)
|
||||
|
||||
bs = max_pos # single block covering all tokens
|
||||
mla_dim = KV_LORA + ROPE_DIM
|
||||
mla_dim = cfg.kv_lora + cfg.rope_dim
|
||||
mla_fp8 = mla_dtype == "fp8"
|
||||
if mla_fp8:
|
||||
mla_cache = torch.zeros(1, bs, mla_dim, device=dev, dtype=torch.uint8)
|
||||
@@ -174,7 +190,7 @@ def test_fused_norm_rope(num_tokens: int, index_interleave: bool, mla_dtype: str
|
||||
else:
|
||||
mla_cache = torch.zeros(1, bs, mla_dim, device=dev, dtype=torch.bfloat16)
|
||||
mla_k_scale = None
|
||||
idx_row = INDEX_HEAD_DIM + INDEX_HEAD_DIM // 128 * 4 # 132
|
||||
idx_row = cfg.index_head_dim + cfg.index_head_dim // 128 * 4 # 132
|
||||
idx_cache = torch.zeros(1, bs, idx_row, device=dev, dtype=torch.uint8)
|
||||
slot = torch.arange(num_tokens, device=dev, dtype=torch.int64)
|
||||
topk = torch.full((num_tokens, 2048), 7, device=dev, dtype=torch.int32)
|
||||
@@ -213,20 +229,20 @@ def test_fused_norm_rope(num_tokens: int, index_interleave: bool, mla_dtype: str
|
||||
if mla_fp8:
|
||||
cache = mla_cache.view(FP8)[0, :num_tokens]
|
||||
s = mla_k_scale.item()
|
||||
assert_fp8(cache[:, :KV_LORA], (kv_ref / s).to(FP8), "MLA kv fp8")
|
||||
assert_fp8(cache[:, KV_LORA:], (kpe_ref / s).to(FP8), "MLA k_pe fp8")
|
||||
assert_fp8(cache[:, : cfg.kv_lora], (kv_ref / s).to(FP8), "MLA kv fp8")
|
||||
assert_fp8(cache[:, cfg.kv_lora :], (kpe_ref / s).to(FP8), "MLA k_pe fp8")
|
||||
else:
|
||||
cache = mla_cache[0, :num_tokens]
|
||||
assert_bf16(cache[:, :KV_LORA], kv_ref, "MLA kv bf16")
|
||||
assert_bf16(cache[:, KV_LORA:], kpe_ref, "MLA k_pe bf16")
|
||||
assert_bf16(cache[:, : cfg.kv_lora], kv_ref, "MLA kv bf16")
|
||||
assert_bf16(cache[:, cfg.kv_lora :], kpe_ref, "MLA k_pe bf16")
|
||||
|
||||
# Indexer-K cache (packed [bs*head_dim fp8 | bs*4 fp32 scale]).
|
||||
ik_ref = layer_norm(ik, ikw, ikb)
|
||||
ik_ref = rope(ik_ref, pos, idx_cos_sin, interleave=index_interleave)
|
||||
q_ref, s_ref = ue8m0_quant(ik_ref)
|
||||
flat = idx_cache[0].reshape(-1)
|
||||
vals = flat[: bs * INDEX_HEAD_DIM].view(FP8).reshape(bs, INDEX_HEAD_DIM)
|
||||
scales = flat[bs * INDEX_HEAD_DIM :].view(torch.float32)
|
||||
vals = flat[: bs * cfg.index_head_dim].view(FP8).reshape(bs, cfg.index_head_dim)
|
||||
scales = flat[bs * cfg.index_head_dim :].view(torch.float32)
|
||||
assert_fp8(vals[:num_tokens], q_ref, "indexer-K fp8")
|
||||
torch.testing.assert_close(scales[:num_tokens], s_ref, rtol=0, atol=0)
|
||||
|
||||
@@ -234,23 +250,24 @@ def test_fused_norm_rope(num_tokens: int, index_interleave: bool, mla_dtype: str
|
||||
assert (topk == -1).all(), "topk buffer not cleared on indexer layer"
|
||||
|
||||
|
||||
def test_fused_norm_rope_packed_indexer_block_stride():
|
||||
@pytest.mark.parametrize("cfg", MODEL_CONFIGS, ids=MODEL_IDS)
|
||||
def test_fused_norm_rope_packed_indexer_block_stride(cfg: ModelConfig):
|
||||
"""Indexer writes use their HMA block stride and independent slot map."""
|
||||
torch.manual_seed(7)
|
||||
dev = "cuda"
|
||||
num_tokens = block_size = 2
|
||||
pos = torch.arange(num_tokens, device=dev, dtype=torch.int64)
|
||||
q_c = torch.randn(num_tokens, Q_LORA, device=dev, dtype=torch.bfloat16)
|
||||
kv_c = torch.randn(num_tokens, KV_LORA, device=dev, dtype=torch.bfloat16)
|
||||
k_pe = torch.randn(num_tokens, ROPE_DIM, device=dev, dtype=torch.bfloat16)
|
||||
qw = torch.randn(Q_LORA, device=dev, dtype=torch.bfloat16)
|
||||
kvw = torch.randn(KV_LORA, device=dev, dtype=torch.bfloat16)
|
||||
ik = torch.randn(num_tokens, INDEX_HEAD_DIM, device=dev, dtype=torch.bfloat16)
|
||||
ikw = torch.randn(INDEX_HEAD_DIM, device=dev, dtype=torch.float32)
|
||||
ikb = torch.randn(INDEX_HEAD_DIM, device=dev, dtype=torch.float32)
|
||||
cos_sin = make_cos_sin(32, ROPE_DIM, dev)
|
||||
q_c = torch.randn(num_tokens, cfg.q_lora, device=dev, dtype=torch.bfloat16)
|
||||
kv_c = torch.randn(num_tokens, cfg.kv_lora, device=dev, dtype=torch.bfloat16)
|
||||
k_pe = torch.randn(num_tokens, cfg.rope_dim, device=dev, dtype=torch.bfloat16)
|
||||
qw = torch.randn(cfg.q_lora, device=dev, dtype=torch.bfloat16)
|
||||
kvw = torch.randn(cfg.kv_lora, device=dev, dtype=torch.bfloat16)
|
||||
ik = torch.randn(num_tokens, cfg.index_head_dim, device=dev, dtype=torch.bfloat16)
|
||||
ikw = torch.randn(cfg.index_head_dim, device=dev, dtype=torch.float32)
|
||||
ikb = torch.randn(cfg.index_head_dim, device=dev, dtype=torch.float32)
|
||||
cos_sin = make_cos_sin(32, cfg.rope_dim, dev)
|
||||
|
||||
idx_row = INDEX_HEAD_DIM + INDEX_HEAD_DIM // 128 * 4
|
||||
idx_row = cfg.index_head_dim + cfg.index_head_dim // 128 * 4
|
||||
packed_block_stride = block_size * idx_row + 64
|
||||
backing = torch.zeros(2 * packed_block_stride, device=dev, dtype=torch.uint8)
|
||||
idx_cache = torch.as_strided(
|
||||
@@ -261,7 +278,7 @@ def test_fused_norm_rope_packed_indexer_block_stride():
|
||||
mla_cache = torch.zeros(
|
||||
1,
|
||||
block_size,
|
||||
KV_LORA + ROPE_DIM,
|
||||
cfg.kv_lora + cfg.rope_dim,
|
||||
device=dev,
|
||||
dtype=torch.bfloat16,
|
||||
)
|
||||
@@ -296,15 +313,20 @@ def test_fused_norm_rope_packed_indexer_block_stride():
|
||||
q_ref, s_ref = ue8m0_quant(ik_ref)
|
||||
packed = idx_cache[1].reshape(-1)
|
||||
values = (
|
||||
packed[: block_size * INDEX_HEAD_DIM].view(FP8).view(block_size, INDEX_HEAD_DIM)
|
||||
packed[: block_size * cfg.index_head_dim]
|
||||
.view(FP8)
|
||||
.view(block_size, cfg.index_head_dim)
|
||||
)
|
||||
scales = packed[block_size * INDEX_HEAD_DIM :].view(torch.float32)
|
||||
scales = packed[block_size * cfg.index_head_dim :].view(torch.float32)
|
||||
assert_fp8(values, q_ref, "packed indexer-K fp8")
|
||||
torch.testing.assert_close(scales, s_ref, rtol=0, atol=0)
|
||||
assert (backing[block_size * idx_row : packed_block_stride] == 0).all()
|
||||
|
||||
|
||||
def test_fused_norm_rope_materializes_qk_without_local_cache_slots():
|
||||
@pytest.mark.parametrize("cfg", MODEL_CONFIGS, ids=MODEL_IDS)
|
||||
def test_fused_norm_rope_materializes_qk_without_local_cache_slots(
|
||||
cfg: ModelConfig,
|
||||
):
|
||||
"""DCP non-owner ranks still need valid query shards for query AllGather."""
|
||||
torch.manual_seed(7)
|
||||
dev = "cuda"
|
||||
@@ -312,20 +334,20 @@ def test_fused_norm_rope_materializes_qk_without_local_cache_slots():
|
||||
max_pos = 16
|
||||
pos = torch.arange(num_tokens, device=dev, dtype=torch.int64)
|
||||
|
||||
q_c = torch.randn(num_tokens, Q_LORA, device=dev, dtype=torch.bfloat16)
|
||||
kv_c = torch.randn(num_tokens, KV_LORA, device=dev, dtype=torch.bfloat16)
|
||||
k_pe = torch.randn(num_tokens, ROPE_DIM, device=dev, dtype=torch.bfloat16)
|
||||
qw = torch.randn(Q_LORA, device=dev, dtype=torch.bfloat16)
|
||||
kvw = torch.randn(KV_LORA, device=dev, dtype=torch.bfloat16)
|
||||
ik = torch.randn(num_tokens, INDEX_HEAD_DIM, device=dev, dtype=torch.bfloat16)
|
||||
ikw = torch.randn(INDEX_HEAD_DIM, device=dev, dtype=torch.float32)
|
||||
ikb = torch.randn(INDEX_HEAD_DIM, device=dev, dtype=torch.float32)
|
||||
cos_sin = make_cos_sin(max_pos, ROPE_DIM, dev)
|
||||
q_c = torch.randn(num_tokens, cfg.q_lora, device=dev, dtype=torch.bfloat16)
|
||||
kv_c = torch.randn(num_tokens, cfg.kv_lora, device=dev, dtype=torch.bfloat16)
|
||||
k_pe = torch.randn(num_tokens, cfg.rope_dim, device=dev, dtype=torch.bfloat16)
|
||||
qw = torch.randn(cfg.q_lora, device=dev, dtype=torch.bfloat16)
|
||||
kvw = torch.randn(cfg.kv_lora, device=dev, dtype=torch.bfloat16)
|
||||
ik = torch.randn(num_tokens, cfg.index_head_dim, device=dev, dtype=torch.bfloat16)
|
||||
ikw = torch.randn(cfg.index_head_dim, device=dev, dtype=torch.float32)
|
||||
ikb = torch.randn(cfg.index_head_dim, device=dev, dtype=torch.float32)
|
||||
cos_sin = make_cos_sin(max_pos, cfg.rope_dim, dev)
|
||||
|
||||
mla_cache = torch.zeros(
|
||||
1, max_pos, KV_LORA + ROPE_DIM, device=dev, dtype=torch.bfloat16
|
||||
1, max_pos, cfg.kv_lora + cfg.rope_dim, device=dev, dtype=torch.bfloat16
|
||||
)
|
||||
idx_row = INDEX_HEAD_DIM + INDEX_HEAD_DIM // 128 * 4
|
||||
idx_row = cfg.index_head_dim + cfg.index_head_dim // 128 * 4
|
||||
idx_cache = torch.zeros(1, max_pos, idx_row, device=dev, dtype=torch.uint8)
|
||||
no_local_slots = torch.full((num_tokens,), -1, device=dev, dtype=torch.int64)
|
||||
topk = torch.full((num_tokens, 2048), 7, device=dev, dtype=torch.int32)
|
||||
@@ -371,23 +393,26 @@ def test_fused_norm_rope_materializes_qk_without_local_cache_slots():
|
||||
assert (topk == -1).all(), "topk buffer not cleared on non-owner rank"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("cfg", MODEL_CONFIGS, ids=MODEL_IDS)
|
||||
@pytest.mark.parametrize("num_tokens", [1, 17, 512])
|
||||
def test_fused_norm_rope_no_indexer(num_tokens: int):
|
||||
def test_fused_norm_rope_no_indexer(num_tokens: int, cfg: ModelConfig):
|
||||
"""Shared (no-indexer) layer: q + kv/MLA only; top-k buffer untouched."""
|
||||
torch.manual_seed(1)
|
||||
dev = "cuda"
|
||||
max_pos = 8192
|
||||
pos = torch.arange(num_tokens, device=dev, dtype=torch.int64)
|
||||
|
||||
q_c = torch.randn(num_tokens, Q_LORA, device=dev, dtype=torch.bfloat16)
|
||||
kv_c = torch.randn(num_tokens, KV_LORA, device=dev, dtype=torch.bfloat16)
|
||||
k_pe = torch.randn(num_tokens, ROPE_DIM, device=dev, dtype=torch.bfloat16)
|
||||
qw = torch.randn(Q_LORA, device=dev, dtype=torch.bfloat16)
|
||||
kvw = torch.randn(KV_LORA, device=dev, dtype=torch.bfloat16)
|
||||
mla_cos_sin = make_cos_sin(max_pos, ROPE_DIM, dev)
|
||||
q_c = torch.randn(num_tokens, cfg.q_lora, device=dev, dtype=torch.bfloat16)
|
||||
kv_c = torch.randn(num_tokens, cfg.kv_lora, device=dev, dtype=torch.bfloat16)
|
||||
k_pe = torch.randn(num_tokens, cfg.rope_dim, device=dev, dtype=torch.bfloat16)
|
||||
qw = torch.randn(cfg.q_lora, device=dev, dtype=torch.bfloat16)
|
||||
kvw = torch.randn(cfg.kv_lora, device=dev, dtype=torch.bfloat16)
|
||||
mla_cos_sin = make_cos_sin(max_pos, cfg.rope_dim, dev)
|
||||
|
||||
bs = max_pos
|
||||
mla_cache = torch.zeros(1, bs, KV_LORA + ROPE_DIM, device=dev, dtype=torch.bfloat16)
|
||||
mla_cache = torch.zeros(
|
||||
1, bs, cfg.kv_lora + cfg.rope_dim, device=dev, dtype=torch.bfloat16
|
||||
)
|
||||
slot = torch.arange(num_tokens, device=dev, dtype=torch.int64)
|
||||
topk = torch.full((num_tokens, 2048), 7, device=dev, dtype=torch.int32)
|
||||
|
||||
@@ -418,9 +443,9 @@ def test_fused_norm_rope_no_indexer(num_tokens: int):
|
||||
|
||||
assert_bf16(q_out, rms_norm(q_c, qw), "q_c rmsnorm (no-indexer)")
|
||||
cache = mla_cache[0, :num_tokens]
|
||||
assert_bf16(cache[:, :KV_LORA], rms_norm(kv_c, kvw), "MLA kv (no-indexer)")
|
||||
assert_bf16(cache[:, : cfg.kv_lora], rms_norm(kv_c, kvw), "MLA kv (no-indexer)")
|
||||
assert_bf16(
|
||||
cache[:, KV_LORA:],
|
||||
cache[:, cfg.kv_lora :],
|
||||
rope(k_pe.float(), pos, mla_cos_sin, interleave=True),
|
||||
"MLA k_pe (no-indexer)",
|
||||
)
|
||||
@@ -428,36 +453,39 @@ def test_fused_norm_rope_no_indexer(num_tokens: int):
|
||||
assert (topk == 7).all(), "topk buffer should be untouched on shared layer"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("cfg", MODEL_CONFIGS, ids=MODEL_IDS)
|
||||
@pytest.mark.parametrize("has_indexer", [False, True])
|
||||
def test_fused_norm_rope_materializes_pcp_cache_inputs(has_indexer: bool):
|
||||
def test_fused_norm_rope_materializes_pcp_cache_inputs(
|
||||
has_indexer: bool, cfg: ModelConfig
|
||||
):
|
||||
"""PCP gets local normalized/rotated K rows without direct cache writes."""
|
||||
torch.manual_seed(6)
|
||||
dev = "cuda"
|
||||
num_tokens = 17
|
||||
max_pos = 8192
|
||||
pos = torch.arange(num_tokens, device=dev, dtype=torch.int64)
|
||||
q_c = torch.randn(num_tokens, Q_LORA, device=dev, dtype=torch.bfloat16)
|
||||
kv_c = torch.randn(num_tokens, KV_LORA, device=dev, dtype=torch.bfloat16)
|
||||
k_pe = torch.randn(num_tokens, ROPE_DIM, device=dev, dtype=torch.bfloat16)
|
||||
qw = torch.randn(Q_LORA, device=dev, dtype=torch.bfloat16)
|
||||
kvw = torch.randn(KV_LORA, device=dev, dtype=torch.bfloat16)
|
||||
q_c = torch.randn(num_tokens, cfg.q_lora, device=dev, dtype=torch.bfloat16)
|
||||
kv_c = torch.randn(num_tokens, cfg.kv_lora, device=dev, dtype=torch.bfloat16)
|
||||
k_pe = torch.randn(num_tokens, cfg.rope_dim, device=dev, dtype=torch.bfloat16)
|
||||
qw = torch.randn(cfg.q_lora, device=dev, dtype=torch.bfloat16)
|
||||
kvw = torch.randn(cfg.kv_lora, device=dev, dtype=torch.bfloat16)
|
||||
ik = (
|
||||
torch.randn(num_tokens, INDEX_HEAD_DIM, device=dev, dtype=torch.bfloat16)
|
||||
torch.randn(num_tokens, cfg.index_head_dim, device=dev, dtype=torch.bfloat16)
|
||||
if has_indexer
|
||||
else None
|
||||
)
|
||||
ikw = (
|
||||
torch.randn(INDEX_HEAD_DIM, device=dev, dtype=torch.float32)
|
||||
torch.randn(cfg.index_head_dim, device=dev, dtype=torch.float32)
|
||||
if has_indexer
|
||||
else None
|
||||
)
|
||||
ikb = (
|
||||
torch.randn(INDEX_HEAD_DIM, device=dev, dtype=torch.float32)
|
||||
torch.randn(cfg.index_head_dim, device=dev, dtype=torch.float32)
|
||||
if has_indexer
|
||||
else None
|
||||
)
|
||||
mla_cos_sin = make_cos_sin(max_pos, ROPE_DIM, dev)
|
||||
idx_cos_sin = make_cos_sin(max_pos, ROPE_DIM, dev) if has_indexer else None
|
||||
mla_cos_sin = make_cos_sin(max_pos, cfg.rope_dim, dev)
|
||||
idx_cos_sin = make_cos_sin(max_pos, cfg.rope_dim, dev) if has_indexer else None
|
||||
q_out = torch.empty_like(q_c)
|
||||
kv_out = torch.empty_like(kv_c)
|
||||
kpe_out = torch.empty_like(k_pe)
|
||||
@@ -503,8 +531,9 @@ def test_fused_norm_rope_materializes_pcp_cache_inputs(has_indexer: bool):
|
||||
assert_bf16(ik_out, ik_ref, "PCP indexer-K")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("cfg", MODEL_CONFIGS, ids=MODEL_IDS)
|
||||
@pytest.mark.parametrize("num_tokens", [1, 4, 17, 512])
|
||||
def test_fused_norm_rope_ds_mla(num_tokens: int):
|
||||
def test_fused_norm_rope_ds_mla(num_tokens: int, cfg: ModelConfig):
|
||||
"""fp8_ds_mla MLA cache layout (FlashMLA sparse, bf16-query path; SM90/SM100).
|
||||
|
||||
Per-token 656-byte entry: 512 fp8 NoPE (4 per-128 tiles, power-of-two scales
|
||||
@@ -515,12 +544,12 @@ def test_fused_norm_rope_ds_mla(num_tokens: int):
|
||||
max_pos = 8192
|
||||
pos = torch.arange(num_tokens, device=dev, dtype=torch.int64) % max_pos
|
||||
|
||||
q_c = torch.randn(num_tokens, Q_LORA, device=dev, dtype=torch.bfloat16)
|
||||
kv_c = torch.randn(num_tokens, KV_LORA, device=dev, dtype=torch.bfloat16)
|
||||
k_pe = torch.randn(num_tokens, ROPE_DIM, device=dev, dtype=torch.bfloat16)
|
||||
qw = torch.randn(Q_LORA, device=dev, dtype=torch.bfloat16)
|
||||
kvw = torch.randn(KV_LORA, device=dev, dtype=torch.bfloat16)
|
||||
mla_cos_sin = make_cos_sin(max_pos, ROPE_DIM, dev)
|
||||
q_c = torch.randn(num_tokens, cfg.q_lora, device=dev, dtype=torch.bfloat16)
|
||||
kv_c = torch.randn(num_tokens, cfg.kv_lora, device=dev, dtype=torch.bfloat16)
|
||||
k_pe = torch.randn(num_tokens, cfg.rope_dim, device=dev, dtype=torch.bfloat16)
|
||||
qw = torch.randn(cfg.q_lora, device=dev, dtype=torch.bfloat16)
|
||||
kvw = torch.randn(cfg.kv_lora, device=dev, dtype=torch.bfloat16)
|
||||
mla_cos_sin = make_cos_sin(max_pos, cfg.rope_dim, dev)
|
||||
|
||||
bs = max_pos
|
||||
mla_cache = torch.zeros(1, bs, 656, device=dev, dtype=torch.uint8)
|
||||
@@ -559,13 +588,13 @@ def test_fused_norm_rope_ds_mla(num_tokens: int):
|
||||
tiles = kv_ref.view(num_tokens, 4, 128)
|
||||
ref_scale = torch.clamp(tiles.abs().amax(dim=-1) / FP8_MAX, min=1e-4)
|
||||
ref_scale = torch.exp2(torch.ceil(torch.log2(ref_scale)))
|
||||
ref_nope = (tiles / ref_scale[..., None]).reshape(num_tokens, KV_LORA).to(FP8)
|
||||
ref_nope = (tiles / ref_scale[..., None]).reshape(num_tokens, cfg.kv_lora).to(FP8)
|
||||
|
||||
cache = mla_cache[0, :num_tokens] # [N, 656] uint8
|
||||
nope = cache[:, :KV_LORA].view(FP8)
|
||||
scales = cache.view(torch.float32)[:, KV_LORA // 4 : KV_LORA // 4 + 4]
|
||||
rope_off = KV_LORA // 2 + 8
|
||||
rope_vals = cache.view(torch.bfloat16)[:, rope_off : rope_off + ROPE_DIM]
|
||||
nope = cache[:, : cfg.kv_lora].view(FP8)
|
||||
scales = cache.view(torch.float32)[:, cfg.kv_lora // 4 : cfg.kv_lora // 4 + 4]
|
||||
rope_off = cfg.kv_lora // 2 + 8
|
||||
rope_vals = cache.view(torch.bfloat16)[:, rope_off : rope_off + cfg.rope_dim]
|
||||
|
||||
torch.testing.assert_close(scales, ref_scale, rtol=0, atol=0)
|
||||
assert_fp8(nope, ref_nope, "ds_mla NoPE fp8")
|
||||
@@ -598,8 +627,9 @@ def nvfp4_sf_byte(s: torch.Tensor) -> torch.Tensor:
|
||||
not current_platform.is_device_capability(100),
|
||||
reason="nvfp4_ds_mla requires SM100 (Blackwell)",
|
||||
)
|
||||
@pytest.mark.parametrize("cfg", MODEL_CONFIGS, ids=MODEL_IDS)
|
||||
@pytest.mark.parametrize("num_tokens", [1, 4, 17, 512])
|
||||
def test_fused_norm_rope_nvfp4_ds_mla(num_tokens: int):
|
||||
def test_fused_norm_rope_nvfp4_ds_mla(num_tokens: int, cfg: ModelConfig):
|
||||
"""nvfp4_ds_mla MLA cache layout (FlashMLA sparse, SM100 only).
|
||||
|
||||
Per-token 352-byte entry: 256 B of 512 e2m1 NoPE packed 2/byte (low nibble
|
||||
@@ -611,8 +641,8 @@ def test_fused_norm_rope_nvfp4_ds_mla(num_tokens: int):
|
||||
max_pos = 8192
|
||||
pos = torch.arange(num_tokens, device=dev, dtype=torch.int64) % max_pos
|
||||
|
||||
q_c = torch.randn(num_tokens, Q_LORA, device=dev, dtype=torch.bfloat16)
|
||||
kv_c = torch.randn(num_tokens, KV_LORA, device=dev, dtype=torch.bfloat16)
|
||||
q_c = torch.randn(num_tokens, cfg.q_lora, device=dev, dtype=torch.bfloat16)
|
||||
kv_c = torch.randn(num_tokens, cfg.kv_lora, device=dev, dtype=torch.bfloat16)
|
||||
# Tile 0 is deliberately low-magnitude: amax/6 lands between two e4m3
|
||||
# subnormals (spaced a flat 2^-9 there), so round-to-nearest picks a scale
|
||||
# BELOW amax/6 and the tile's peak saturates at +-6. Asserted below, so a
|
||||
@@ -622,10 +652,10 @@ def test_fused_norm_rope_nvfp4_ds_mla(num_tokens: int):
|
||||
kv_c[:, :16] = (
|
||||
7.5 * (2.0**-9) * torch.linspace(0.2, 1.0, 16, device=dev, dtype=torch.bfloat16)
|
||||
)
|
||||
k_pe = torch.randn(num_tokens, ROPE_DIM, device=dev, dtype=torch.bfloat16)
|
||||
qw = torch.randn(Q_LORA, device=dev, dtype=torch.bfloat16)
|
||||
kvw = torch.ones(KV_LORA, device=dev, dtype=torch.bfloat16)
|
||||
mla_cos_sin = make_cos_sin(max_pos, ROPE_DIM, dev)
|
||||
k_pe = torch.randn(num_tokens, cfg.rope_dim, device=dev, dtype=torch.bfloat16)
|
||||
qw = torch.randn(cfg.q_lora, device=dev, dtype=torch.bfloat16)
|
||||
kvw = torch.ones(cfg.kv_lora, device=dev, dtype=torch.bfloat16)
|
||||
mla_cos_sin = make_cos_sin(max_pos, cfg.rope_dim, dev)
|
||||
|
||||
bs = max_pos
|
||||
mla_cache = torch.zeros(1, bs, 352, device=dev, dtype=torch.uint8)
|
||||
@@ -675,19 +705,19 @@ def test_fused_norm_rope_nvfp4_ds_mla(num_tokens: int):
|
||||
)
|
||||
|
||||
ref_codes = quantize_to_e2m1(tiles / ref_scale.float().unsqueeze(-1))
|
||||
ref_codes = ref_codes.reshape(num_tokens, KV_LORA)
|
||||
ref_codes = ref_codes.reshape(num_tokens, cfg.kv_lora)
|
||||
|
||||
cache = mla_cache[0, :num_tokens] # [N, 352] uint8
|
||||
packed = cache[:, : KV_LORA // 2]
|
||||
packed = cache[:, : cfg.kv_lora // 2]
|
||||
got_codes = torch.stack([packed & 0xF, packed >> 4], dim=-1)
|
||||
got_codes = got_codes.reshape(num_tokens, KV_LORA) # low nibble = even elem
|
||||
got_codes = got_codes.reshape(num_tokens, cfg.kv_lora) # low nibble = even elem
|
||||
torch.testing.assert_close(got_codes, ref_codes, rtol=0, atol=0)
|
||||
|
||||
got_rope = cache[:, KV_LORA // 2 : KV_LORA // 2 + ROPE_DIM].view(FP8)
|
||||
got_rope = cache[:, cfg.kv_lora // 2 : cfg.kv_lora // 2 + cfg.rope_dim].view(FP8)
|
||||
assert_fp8(got_rope, kpe_ref.to(FP8), "nvfp4_ds_mla RoPE e4m3")
|
||||
|
||||
perm = nvfp4_sf_byte(torch.arange(32, device=dev))
|
||||
got_scale = cache[:, KV_LORA // 2 + ROPE_DIM :].view(FP8)[:, perm]
|
||||
got_scale = cache[:, cfg.kv_lora // 2 + cfg.rope_dim :].view(FP8)[:, perm]
|
||||
torch.testing.assert_close(got_scale.float(), ref_scale.float(), rtol=0, atol=0)
|
||||
|
||||
# No indexer on this call: top-k buffer must be untouched.
|
||||
@@ -737,39 +767,34 @@ def test_fused_norm_rope_supports_large_token_count():
|
||||
# ── fused_q ──────────────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
("num_tokens", "num_q_heads"),
|
||||
[
|
||||
(1, NUM_HEADS),
|
||||
(4, NUM_HEADS),
|
||||
(17, NUM_HEADS),
|
||||
(512, NUM_HEADS),
|
||||
(4096, NUM_HEADS),
|
||||
(17, 64),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize("cfg", MODEL_CONFIGS, ids=MODEL_IDS)
|
||||
@pytest.mark.parametrize("num_tokens", [1, 4, 17, 512, 4096])
|
||||
@pytest.mark.parametrize("index_interleave", [True, False])
|
||||
def test_fused_q(num_tokens: int, num_q_heads: int, index_interleave: bool):
|
||||
def test_fused_q(num_tokens: int, index_interleave: bool, cfg: ModelConfig):
|
||||
torch.manual_seed(2)
|
||||
dev = "cuda"
|
||||
max_pos = 8192
|
||||
pos = torch.arange(num_tokens, device=dev, dtype=torch.int64) % max_pos
|
||||
|
||||
q_pe = torch.randn(
|
||||
num_tokens, num_q_heads, ROPE_DIM, device=dev, dtype=torch.bfloat16
|
||||
num_tokens, cfg.num_heads, cfg.rope_dim, device=dev, dtype=torch.bfloat16
|
||||
)
|
||||
ql_nope = torch.randn(
|
||||
num_tokens, num_q_heads, KV_LORA, device=dev, dtype=torch.bfloat16
|
||||
num_tokens, cfg.num_heads, cfg.kv_lora, device=dev, dtype=torch.bfloat16
|
||||
)
|
||||
index_q = torch.randn(
|
||||
num_tokens, INDEX_HEADS, INDEX_HEAD_DIM, device=dev, dtype=torch.bfloat16
|
||||
num_tokens,
|
||||
cfg.index_heads,
|
||||
cfg.index_head_dim,
|
||||
device=dev,
|
||||
dtype=torch.bfloat16,
|
||||
)
|
||||
index_w = torch.randn(num_tokens, INDEX_HEADS, device=dev, dtype=torch.float32)
|
||||
index_w = torch.randn(num_tokens, cfg.index_heads, device=dev, dtype=torch.float32)
|
||||
q_scale = torch.tensor([0.37], device=dev, dtype=torch.float32)
|
||||
softmax_scale = INDEX_HEAD_DIM**-0.5
|
||||
head_scale = INDEX_HEADS**-0.5
|
||||
q_cos_sin = make_cos_sin(max_pos, ROPE_DIM, dev) # q_pe: interleaved
|
||||
idx_cos_sin = make_cos_sin(max_pos, ROPE_DIM, dev)
|
||||
softmax_scale = cfg.index_head_dim**-0.5
|
||||
head_scale = cfg.index_heads**-0.5
|
||||
q_cos_sin = make_cos_sin(max_pos, cfg.rope_dim, dev) # q_pe: interleaved
|
||||
idx_cos_sin = make_cos_sin(max_pos, cfg.rope_dim, dev)
|
||||
|
||||
iq_fp8, iw_out, mqa = K.fused_q(
|
||||
pos,
|
||||
@@ -791,18 +816,18 @@ def test_fused_q(num_tokens: int, num_q_heads: int, index_interleave: bool):
|
||||
mqa_nope_ref = (ql_nope.float() / s).to(FP8)
|
||||
qpe_ref = rope(
|
||||
q_pe.float(),
|
||||
pos.unsqueeze(-1).expand(num_tokens, num_q_heads),
|
||||
pos.unsqueeze(-1).expand(num_tokens, cfg.num_heads),
|
||||
q_cos_sin,
|
||||
interleave=True,
|
||||
)
|
||||
mqa_pe_ref = (qpe_ref / s).to(FP8)
|
||||
assert_fp8(mqa[:, :, :KV_LORA], mqa_nope_ref, "mqa ql_nope")
|
||||
assert_fp8(mqa[:, :, KV_LORA:], mqa_pe_ref, "mqa q_pe")
|
||||
assert_fp8(mqa[:, :, : cfg.kv_lora], mqa_nope_ref, "mqa ql_nope")
|
||||
assert_fp8(mqa[:, :, cfg.kv_lora :], mqa_pe_ref, "mqa q_pe")
|
||||
|
||||
# Indexer-Q: RoPE + UE8M0 fp8 quant; index weights fold in q-scale.
|
||||
iq_ref = rope(
|
||||
index_q.float(),
|
||||
pos.unsqueeze(-1).expand(num_tokens, INDEX_HEADS),
|
||||
pos.unsqueeze(-1).expand(num_tokens, cfg.index_heads),
|
||||
idx_cos_sin,
|
||||
interleave=index_interleave,
|
||||
)
|
||||
@@ -812,20 +837,21 @@ def test_fused_q(num_tokens: int, num_q_heads: int, index_interleave: bool):
|
||||
torch.testing.assert_close(iw_out, iw_ref, rtol=1e-3, atol=1e-3)
|
||||
|
||||
|
||||
@pytest.mark.parametrize("cfg", MODEL_CONFIGS, ids=MODEL_IDS)
|
||||
@pytest.mark.parametrize("num_tokens", [1, 17, 512])
|
||||
def test_fused_q_no_indexer(num_tokens: int):
|
||||
def test_fused_q_no_indexer(num_tokens: int, cfg: ModelConfig):
|
||||
torch.manual_seed(3)
|
||||
dev = "cuda"
|
||||
max_pos = 8192
|
||||
pos = torch.arange(num_tokens, device=dev, dtype=torch.int64)
|
||||
q_pe = torch.randn(
|
||||
num_tokens, NUM_HEADS, ROPE_DIM, device=dev, dtype=torch.bfloat16
|
||||
num_tokens, cfg.num_heads, cfg.rope_dim, device=dev, dtype=torch.bfloat16
|
||||
)
|
||||
ql_nope = torch.randn(
|
||||
num_tokens, NUM_HEADS, KV_LORA, device=dev, dtype=torch.bfloat16
|
||||
num_tokens, cfg.num_heads, cfg.kv_lora, device=dev, dtype=torch.bfloat16
|
||||
)
|
||||
q_scale = torch.tensor([0.5], device=dev, dtype=torch.float32)
|
||||
q_cos_sin = make_cos_sin(max_pos, ROPE_DIM, dev)
|
||||
q_cos_sin = make_cos_sin(max_pos, cfg.rope_dim, dev)
|
||||
_, _, mqa = K.fused_q(
|
||||
pos,
|
||||
q_pe,
|
||||
@@ -841,19 +867,20 @@ def test_fused_q_no_indexer(num_tokens: int):
|
||||
index_rope_interleave=False,
|
||||
)
|
||||
s = q_scale.item()
|
||||
assert_fp8(mqa[:, :, :KV_LORA], (ql_nope.float() / s).to(FP8), "mqa ql_nope")
|
||||
assert_fp8(mqa[:, :, : cfg.kv_lora], (ql_nope.float() / s).to(FP8), "mqa ql_nope")
|
||||
qpe_ref = rope(
|
||||
q_pe.float(),
|
||||
pos.unsqueeze(-1).expand(num_tokens, NUM_HEADS),
|
||||
pos.unsqueeze(-1).expand(num_tokens, cfg.num_heads),
|
||||
q_cos_sin,
|
||||
interleave=True,
|
||||
)
|
||||
assert_fp8(mqa[:, :, KV_LORA:], (qpe_ref / s).to(FP8), "mqa q_pe")
|
||||
assert_fp8(mqa[:, :, cfg.kv_lora :], (qpe_ref / s).to(FP8), "mqa q_pe")
|
||||
|
||||
|
||||
@pytest.mark.parametrize("cfg", MODEL_CONFIGS, ids=MODEL_IDS)
|
||||
@pytest.mark.parametrize("num_tokens", [1, 17, 512])
|
||||
@pytest.mark.parametrize("has_indexer", [True, False])
|
||||
def test_fused_q_bf16_query(num_tokens: int, has_indexer: bool):
|
||||
def test_fused_q_bf16_query(num_tokens: int, has_indexer: bool, cfg: ModelConfig):
|
||||
"""bf16-query path (FlashMLA sparse, SM90/SM100): only the RoPE'd q_pe is
|
||||
produced (bf16, unquantized); ql_nope is consumed directly by the caller."""
|
||||
torch.manual_seed(6)
|
||||
@@ -862,21 +889,27 @@ def test_fused_q_bf16_query(num_tokens: int, has_indexer: bool):
|
||||
pos = torch.arange(num_tokens, device=dev, dtype=torch.int64) % max_pos
|
||||
|
||||
q_pe = torch.randn(
|
||||
num_tokens, NUM_HEADS, ROPE_DIM, device=dev, dtype=torch.bfloat16
|
||||
num_tokens, cfg.num_heads, cfg.rope_dim, device=dev, dtype=torch.bfloat16
|
||||
)
|
||||
ql_nope = torch.randn(
|
||||
num_tokens, NUM_HEADS, KV_LORA, device=dev, dtype=torch.bfloat16
|
||||
num_tokens, cfg.num_heads, cfg.kv_lora, device=dev, dtype=torch.bfloat16
|
||||
)
|
||||
q_scale = torch.tensor([0.37], device=dev, dtype=torch.float32)
|
||||
q_cos_sin = make_cos_sin(max_pos, ROPE_DIM, dev)
|
||||
q_cos_sin = make_cos_sin(max_pos, cfg.rope_dim, dev)
|
||||
|
||||
index_q = index_w = idx_cos_sin = None
|
||||
if has_indexer:
|
||||
index_q = torch.randn(
|
||||
num_tokens, INDEX_HEADS, INDEX_HEAD_DIM, device=dev, dtype=torch.bfloat16
|
||||
num_tokens,
|
||||
cfg.index_heads,
|
||||
cfg.index_head_dim,
|
||||
device=dev,
|
||||
dtype=torch.bfloat16,
|
||||
)
|
||||
index_w = torch.randn(num_tokens, INDEX_HEADS, device=dev, dtype=torch.float32)
|
||||
idx_cos_sin = make_cos_sin(max_pos, ROPE_DIM, dev)
|
||||
index_w = torch.randn(
|
||||
num_tokens, cfg.index_heads, device=dev, dtype=torch.float32
|
||||
)
|
||||
idx_cos_sin = make_cos_sin(max_pos, cfg.rope_dim, dev)
|
||||
|
||||
iq_fp8, iw_out, q_pe_out = K.fused_q(
|
||||
pos,
|
||||
@@ -887,8 +920,8 @@ def test_fused_q_bf16_query(num_tokens: int, has_indexer: bool):
|
||||
ql_nope,
|
||||
q_scale,
|
||||
index_w,
|
||||
INDEX_HEAD_DIM**-0.5,
|
||||
INDEX_HEADS**-0.5,
|
||||
cfg.index_head_dim**-0.5,
|
||||
cfg.index_heads**-0.5,
|
||||
has_indexer=has_indexer,
|
||||
index_rope_interleave=False,
|
||||
quantize_mqa=False,
|
||||
@@ -896,10 +929,10 @@ def test_fused_q_bf16_query(num_tokens: int, has_indexer: bool):
|
||||
|
||||
# MQA query: only the RoPE'd q_pe, bf16, unquantized.
|
||||
assert q_pe_out.dtype == torch.bfloat16
|
||||
assert q_pe_out.shape == (num_tokens, NUM_HEADS, ROPE_DIM)
|
||||
assert q_pe_out.shape == (num_tokens, cfg.num_heads, cfg.rope_dim)
|
||||
qpe_ref = rope(
|
||||
q_pe.float(),
|
||||
pos.unsqueeze(-1).expand(num_tokens, NUM_HEADS),
|
||||
pos.unsqueeze(-1).expand(num_tokens, cfg.num_heads),
|
||||
q_cos_sin,
|
||||
interleave=True,
|
||||
)
|
||||
@@ -910,13 +943,15 @@ def test_fused_q_bf16_query(num_tokens: int, has_indexer: bool):
|
||||
assert index_q is not None
|
||||
iq_ref = rope(
|
||||
index_q.float(),
|
||||
pos.unsqueeze(-1).expand(num_tokens, INDEX_HEADS),
|
||||
pos.unsqueeze(-1).expand(num_tokens, cfg.index_heads),
|
||||
idx_cos_sin,
|
||||
interleave=False,
|
||||
)
|
||||
q_ref, scale_ref = ue8m0_quant(iq_ref)
|
||||
assert_fp8(iq_fp8, q_ref, "indexer-Q fp8 (bf16-query path)")
|
||||
iw_ref = index_w * scale_ref * (INDEX_HEAD_DIM**-0.5) * (INDEX_HEADS**-0.5)
|
||||
iw_ref = (
|
||||
index_w * scale_ref * (cfg.index_head_dim**-0.5) * (cfg.index_heads**-0.5)
|
||||
)
|
||||
torch.testing.assert_close(iw_out, iw_ref, rtol=1e-3, atol=1e-3)
|
||||
|
||||
|
||||
@@ -956,21 +991,22 @@ def test_fused_q_triton_supports_large_token_count():
|
||||
# ── fused_eh_norm (MTP) ──────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@pytest.mark.parametrize("cfg", MODEL_CONFIGS, ids=MODEL_IDS)
|
||||
@pytest.mark.parametrize("num_tokens", [1, 4, 17, 512])
|
||||
def test_fused_eh_norm(num_tokens: int):
|
||||
def test_fused_eh_norm(num_tokens: int, cfg: ModelConfig):
|
||||
torch.manual_seed(4)
|
||||
dev = "cuda"
|
||||
# Mix in a position-0 token to exercise the embeds-zeroing branch.
|
||||
pos = torch.arange(num_tokens, device=dev, dtype=torch.int64)
|
||||
pos[0] = 0
|
||||
embeds = torch.randn(num_tokens, HIDDEN, device=dev, dtype=torch.bfloat16)
|
||||
prev = torch.randn(num_tokens, HIDDEN, device=dev, dtype=torch.bfloat16)
|
||||
ew = torch.randn(HIDDEN, device=dev, dtype=torch.bfloat16)
|
||||
hw = torch.randn(HIDDEN, device=dev, dtype=torch.bfloat16)
|
||||
embeds = torch.randn(num_tokens, cfg.hidden, device=dev, dtype=torch.bfloat16)
|
||||
prev = torch.randn(num_tokens, cfg.hidden, device=dev, dtype=torch.bfloat16)
|
||||
ew = torch.randn(cfg.hidden, device=dev, dtype=torch.bfloat16)
|
||||
hw = torch.randn(cfg.hidden, device=dev, dtype=torch.bfloat16)
|
||||
|
||||
out = K.fused_eh_norm(pos, embeds, prev, ew, hw, EPS)
|
||||
|
||||
masked = torch.where(pos.unsqueeze(-1) == 0, torch.zeros_like(embeds), embeds)
|
||||
ref = torch.cat([rms_norm(masked, ew), rms_norm(prev, hw)], dim=-1)
|
||||
assert out.shape == (num_tokens, 2 * HIDDEN)
|
||||
assert out.shape == (num_tokens, 2 * cfg.hidden)
|
||||
assert_bf16(out, ref, "eh_norm")
|
||||
|
||||
+1
-1
@@ -3406,7 +3406,7 @@ class rocm_aiter_ops:
|
||||
X: torch.Tensor,
|
||||
W: torch.Tensor,
|
||||
w_scale: torch.Tensor,
|
||||
Y: torch.Tensor,
|
||||
Y: torch.Tensor | None = None,
|
||||
transpose_bm: bool | None = False,
|
||||
prequant: bool | None = False,
|
||||
y_scale: torch.Tensor | None = None,
|
||||
|
||||
@@ -1025,9 +1025,7 @@ class MLAAttention(nn.Module, AttentionLayerBase):
|
||||
mqa_q_pe = mqa_pe_padded
|
||||
|
||||
if self.is_aiter_triton_fp4_bmm_enabled:
|
||||
from aiter.ops.triton.batched_gemm_a16wfp4 import batched_gemm_a16wfp4
|
||||
|
||||
mqa_ql_nope = batched_gemm_a16wfp4(
|
||||
mqa_ql_nope = rocm_aiter_ops.batched_gemm_a16wfp4(
|
||||
mqa_q_nope,
|
||||
self.W_K,
|
||||
self.W_K_scale,
|
||||
|
||||
@@ -665,6 +665,11 @@ class DeepseekV32IndexerCache(torch.nn.Module, AttentionLayerBase):
|
||||
|
||||
def forward(self): ...
|
||||
|
||||
@property
|
||||
def uses_shuffled_layout(self) -> bool:
|
||||
"""Whether this cache's reader expects the shuffled value layout."""
|
||||
return False
|
||||
|
||||
def get_attn_backend(self) -> type[AttentionBackend]:
|
||||
return DeepseekV32IndexerBackend
|
||||
|
||||
|
||||
@@ -7,9 +7,9 @@ MLA + a "lightning indexer" that selects the top-k tokens for a sparse MLA
|
||||
attend. The same model code serves any DSA checkpoint, including GLM-5.2
|
||||
(``glm_moe_dsa``), which reuses this architecture.
|
||||
|
||||
The CUDA implementation selects capability-specific kernels internally and
|
||||
falls back when an optimization is unavailable. Other platforms use the
|
||||
generic implementation by default.
|
||||
The CUDA and ROCm implementations select capability-specific kernels
|
||||
internally and fall back when an optimization is unavailable. Other platforms
|
||||
use the generic implementation by default.
|
||||
"""
|
||||
|
||||
from vllm.platforms import current_platform
|
||||
@@ -20,8 +20,12 @@ if current_platform.is_cuda():
|
||||
from .nvidia.model import DeepseekV32ForCausalLM
|
||||
from .nvidia.model import DeepseekV32ForCausalLM as GlmMoeDsaForCausalLM
|
||||
from .nvidia.mtp import DeepseekV32MTP
|
||||
elif current_platform.is_rocm():
|
||||
from .amd.model import DeepseekV32ForCausalLM
|
||||
from .amd.model import DeepseekV32ForCausalLM as GlmMoeDsaForCausalLM
|
||||
from .amd.mtp import DeepseekV32MTP
|
||||
else:
|
||||
# ROCm, XPU, and CPU keep the generic implementation.
|
||||
# XPU and CPU keep the generic implementation.
|
||||
from vllm.model_executor.models.deepseek_mtp import DeepSeekMTP as DeepseekV32MTP
|
||||
from vllm.model_executor.models.deepseek_v2 import (
|
||||
DeepseekV3ForCausalLM as DeepseekV32ForCausalLM,
|
||||
|
||||
@@ -3,12 +3,14 @@
|
||||
|
||||
import torch
|
||||
|
||||
from vllm._aiter_ops import rocm_aiter_ops
|
||||
from vllm.compilation.breakable_cudagraph import eager_break_during_capture
|
||||
from vllm.forward_context import get_forward_context
|
||||
from vllm.model_executor.layers.sparse_attn_indexer import SparseAttnIndexer
|
||||
from vllm.model_executor.models.deepseek_v2 import DeepseekV32IndexerCache
|
||||
from vllm.models.deepseek_v32.attention import DeepseekV32Attention, DeepseekV32Indexer
|
||||
from vllm.models.deepseek_v32.common.kernels import fused_norm_rope, fused_q
|
||||
from vllm.platforms import current_platform
|
||||
from vllm.utils.torch_utils import is_quantized_kv_cache
|
||||
from vllm.v1.attention.backends.mla.indexer import DeepseekV32IndexerBackend
|
||||
from vllm.v1.attention.backends.mla.rocm_aiter_mla_sparse import (
|
||||
@@ -16,21 +18,15 @@ from vllm.v1.attention.backends.mla.rocm_aiter_mla_sparse import (
|
||||
)
|
||||
|
||||
|
||||
class DeepseekV32MLASparseBackend(ROCMAiterMLASparseBackend):
|
||||
@staticmethod
|
||||
def get_supported_kernel_block_sizes() -> list:
|
||||
return [16, 32]
|
||||
|
||||
|
||||
class DeepseekV32ROCmIndexerBackend(DeepseekV32IndexerBackend):
|
||||
@staticmethod
|
||||
def get_supported_kernel_block_sizes() -> list:
|
||||
return [16, 32]
|
||||
|
||||
|
||||
class DeepseekV32ROCmIndexerCache(DeepseekV32IndexerCache):
|
||||
def get_attn_backend(self):
|
||||
return DeepseekV32ROCmIndexerBackend
|
||||
return DeepseekV32IndexerBackend
|
||||
|
||||
@property
|
||||
def uses_shuffled_layout(self) -> bool:
|
||||
# aiter's gather/insert pair shuffles the cache above block size 1:
|
||||
# [n_blocks, blk/16, head_dim/16, 16, 16] instead of [n_blocks, blk, head_dim].
|
||||
return self.kv_cache.ndim == 3 and self.kv_cache.shape[1] != 1
|
||||
|
||||
|
||||
class DeepseekV32ROCmIndexer(DeepseekV32Indexer):
|
||||
@@ -46,7 +42,7 @@ class DeepseekV32MLAAttention(DeepseekV32Attention):
|
||||
config,
|
||||
prefix,
|
||||
topk_indices_buffer,
|
||||
attn_backend=DeepseekV32MLASparseBackend,
|
||||
attn_backend=ROCMAiterMLASparseBackend,
|
||||
)
|
||||
|
||||
self.indexer_op: SparseAttnIndexer | None = None
|
||||
@@ -100,14 +96,10 @@ class DeepseekV32MLAAttention(DeepseekV32Attention):
|
||||
q_nope = q_nope.transpose(0, 1) # (N, tokens, P)
|
||||
|
||||
if self.is_aiter_triton_fp4_bmm_enabled:
|
||||
from aiter.ops.triton.batched_gemm_a16wfp4 import batched_gemm_a16wfp4
|
||||
|
||||
ql_nope = batched_gemm_a16wfp4(
|
||||
ql_nope = rocm_aiter_ops.batched_gemm_a16wfp4(
|
||||
q_nope, self.W_K, self.W_K_scale, transpose_bm=True, prequant=True
|
||||
)
|
||||
elif self.is_aiter_triton_fp8_bmm_enabled:
|
||||
from vllm._aiter_ops import rocm_aiter_ops
|
||||
|
||||
ql_nope = rocm_aiter_ops.triton_fp8_bmm(
|
||||
q_nope, self.W_K, self.W_K_scale, group_size=128, transpose_bm=True
|
||||
)
|
||||
@@ -152,14 +144,10 @@ class DeepseekV32MLAAttention(DeepseekV32Attention):
|
||||
)
|
||||
|
||||
if self.is_aiter_triton_fp4_bmm_enabled:
|
||||
from aiter.ops.triton.batched_gemm_a16wfp4 import batched_gemm_a16wfp4
|
||||
|
||||
batched_gemm_a16wfp4(
|
||||
rocm_aiter_ops.batched_gemm_a16wfp4(
|
||||
x, self.W_V, self.W_V_scale, out_view, transpose_bm=True, prequant=True
|
||||
)
|
||||
elif self.is_aiter_triton_fp8_bmm_enabled:
|
||||
from vllm._aiter_ops import rocm_aiter_ops
|
||||
|
||||
rocm_aiter_ops.triton_fp8_bmm(
|
||||
x,
|
||||
self.W_V,
|
||||
@@ -202,6 +190,7 @@ class DeepseekV32MLAAttention(DeepseekV32Attention):
|
||||
indexer_k_norm_eps = self.indexer.k_norm.eps
|
||||
indexer_k_rope_cos_sin_cache = self.indexer_rope_emb.cos_sin_cache
|
||||
indexer_k_cache = self.indexer.k_cache.kv_cache
|
||||
indexer_cache_shuffled = self.indexer.k_cache.uses_shuffled_layout
|
||||
indexer_softmax_scale = self.indexer.softmax_scale
|
||||
indexer_n_head_scale = self.indexer.n_head**-0.5
|
||||
else:
|
||||
@@ -211,6 +200,7 @@ class DeepseekV32MLAAttention(DeepseekV32Attention):
|
||||
indexer_k_norm_eps = 1e-6
|
||||
indexer_k_rope_cos_sin_cache = None
|
||||
indexer_k_cache = None
|
||||
indexer_cache_shuffled = False
|
||||
indexer_softmax_scale = 0.0
|
||||
indexer_n_head_scale = 0.0
|
||||
|
||||
@@ -241,6 +231,7 @@ class DeepseekV32MLAAttention(DeepseekV32Attention):
|
||||
self.topk_indices_buffer,
|
||||
slot_mapping=mla_slot,
|
||||
indexer_k_cache=indexer_k_cache,
|
||||
indexer_cache_shuffled=indexer_cache_shuffled,
|
||||
mla_kv_cache=mla_kv_cache,
|
||||
mla_kv_cache_dtype=self.kv_cache_dtype,
|
||||
mla_k_scale=mla_k_scale,
|
||||
@@ -282,7 +273,7 @@ class DeepseekV32MLAAttention(DeepseekV32Attention):
|
||||
num_actual = attn_metadata.num_actual_tokens # type: ignore[attr-defined]
|
||||
kv_cache = self.kv_cache
|
||||
if self._fp8_kv_needs_view:
|
||||
kv_cache = kv_cache.view(torch.float8_e4m3fn)
|
||||
kv_cache = kv_cache.view(current_platform.fp8_dtype())
|
||||
|
||||
q_for_attn = self._build_q_for_attn(ql_nope, mqa_q, num_actual)
|
||||
attn_out, _ = self.impl.forward_mqa( # type: ignore[attr-defined]
|
||||
|
||||
@@ -327,6 +327,7 @@ class DeepseekV32Attention(MLAAttention):
|
||||
indexer_k_norm_bias = self.indexer.k_norm.bias
|
||||
indexer_k_norm_eps = self.indexer.k_norm.eps
|
||||
indexer_k_rope_cos_sin_cache = self.indexer_rope_emb.cos_sin_cache
|
||||
indexer_cache_shuffled = self.indexer.k_cache.uses_shuffled_layout
|
||||
indexer_k_cache = None if self.use_pcp else self.indexer.k_cache.kv_cache
|
||||
index_k_out = torch.empty_like(index_k) if self.use_pcp else None
|
||||
indexer_softmax_scale = self.indexer.softmax_scale
|
||||
@@ -338,6 +339,7 @@ class DeepseekV32Attention(MLAAttention):
|
||||
indexer_k_norm_eps = 1e-6
|
||||
indexer_k_rope_cos_sin_cache = None
|
||||
indexer_k_cache = None
|
||||
indexer_cache_shuffled = False
|
||||
index_k_out = None
|
||||
indexer_softmax_scale = 0.0
|
||||
indexer_n_head_scale = 0.0
|
||||
@@ -377,6 +379,7 @@ class DeepseekV32Attention(MLAAttention):
|
||||
slot_mapping=mla_slot,
|
||||
indexer_slot_mapping=indexer_slot,
|
||||
indexer_k_cache=indexer_k_cache,
|
||||
indexer_cache_shuffled=indexer_cache_shuffled,
|
||||
mla_kv_cache=mla_kv_cache,
|
||||
mla_kv_cache_dtype=self.kv_cache_dtype,
|
||||
mla_k_scale=mla_k_scale,
|
||||
@@ -544,7 +547,7 @@ class DeepseekV32Attention(MLAAttention):
|
||||
return
|
||||
|
||||
if self._fp8_kv_needs_view:
|
||||
kv_cache = kv_cache.view(torch.float8_e4m3fn)
|
||||
kv_cache = kv_cache.view(current_platform.fp8_dtype())
|
||||
if self._fp8_query:
|
||||
# FlashInfer sparse: single packed fp8 query.
|
||||
mqa_q_arg: torch.Tensor | tuple[torch.Tensor, torch.Tensor] = mqa_q[
|
||||
|
||||
@@ -12,6 +12,18 @@ from vllm.utils.torch_utils import is_quantized_kv_cache
|
||||
# has_indexer=False path so the indexer args don't allocate every call.
|
||||
_DUMMY_CACHE: dict[tuple, torch.Tensor] = {}
|
||||
|
||||
# Tile shape of the indexer-K cache's shuffled layout, used when the cache reports
|
||||
# uses_shuffled_layout. The block tile is a token count; the head tile is a byte
|
||||
# count, which the kernel converts to cache elements.
|
||||
_INDEXER_CACHE_BLOCK_TILE = 16
|
||||
_INDEXER_CACHE_HEAD_TILE_BYTES = 16
|
||||
|
||||
# FNUZ (e4m3fnuz) on gfx942, OCP (e4m3fn) elsewhere. 224.0 keeps the ue8m0
|
||||
# power-of-two scale inside the FNUZ range after its ceil.
|
||||
_FP8_DTYPE = current_platform.fp8_dtype()
|
||||
_USE_FNUZ = torch.float8_e4m3fnuz == _FP8_DTYPE
|
||||
_FP8_MAX = 224.0 if _USE_FNUZ else 448.0
|
||||
|
||||
|
||||
def _dummy(shape: tuple, dtype: torch.dtype, device: torch.device) -> torch.Tensor:
|
||||
key = (shape, dtype, device)
|
||||
@@ -47,16 +59,17 @@ def _get_cos_sin(
|
||||
|
||||
|
||||
@triton.jit
|
||||
def _fp8_ue8m0_quantize(vals):
|
||||
def _fp8_ue8m0_quantize(vals, FP8_MAX: tl.constexpr, USE_FNUZ: tl.constexpr):
|
||||
"""Quantize float32 values to FP8 E4M3 with a ue8m0 (power-of-2) scale.
|
||||
|
||||
Returns (fp8_vals, scale) so the caller can store them or reuse the scale.
|
||||
"""
|
||||
vals = vals.to(tl.float32)
|
||||
amax = tl.max(tl.abs(vals))
|
||||
scale = tl.div_rn(tl.maximum(amax, 1e-4), 448.0)
|
||||
scale = tl.div_rn(tl.maximum(amax, 1e-4), FP8_MAX)
|
||||
scale = tl.math.exp2(tl.math.ceil(tl.math.log2(scale)))
|
||||
fp8_vals = tl.div_rn(vals, scale).to(tl.float8e4nv)
|
||||
fp8_dtype = tl.float8e4b8 if USE_FNUZ else tl.float8e4nv
|
||||
fp8_vals = tl.div_rn(vals, scale).to(fp8_dtype)
|
||||
return fp8_vals, scale
|
||||
|
||||
|
||||
@@ -71,18 +84,32 @@ def _fp8_quant_and_cache_write(
|
||||
cache_block_stride,
|
||||
offsets,
|
||||
HEAD_DIM: tl.constexpr,
|
||||
SHUFFLE: tl.constexpr,
|
||||
BLOCK_TILE: tl.constexpr,
|
||||
HEAD_TILE: tl.constexpr,
|
||||
FP8_MAX: tl.constexpr,
|
||||
USE_FNUZ: tl.constexpr,
|
||||
):
|
||||
k_fp8, scale = _fp8_ue8m0_quantize(vals)
|
||||
k_fp8, scale = _fp8_ue8m0_quantize(vals, FP8_MAX, USE_FNUZ)
|
||||
|
||||
block_idx = slot_idx // cache_block_size
|
||||
block_offset = slot_idx % cache_block_size
|
||||
block_start = block_idx * cache_block_stride
|
||||
|
||||
tl.store(
|
||||
kv_cache_ptr + block_start + block_offset * HEAD_DIM + offsets,
|
||||
k_fp8,
|
||||
mask=mask,
|
||||
)
|
||||
# Shuffled layout: [blk/BLOCK_TILE, HEAD_DIM/HEAD_TILE, BLOCK_TILE, HEAD_TILE],
|
||||
# so one contiguous run holds BLOCK_TILE tokens x HEAD_TILE bytes and the
|
||||
# reader's coalesced loads land on the bytes we wrote.
|
||||
if SHUFFLE:
|
||||
value_off = (
|
||||
block_offset // BLOCK_TILE * BLOCK_TILE * HEAD_DIM
|
||||
+ block_offset % BLOCK_TILE * HEAD_TILE
|
||||
+ offsets // HEAD_TILE * BLOCK_TILE * HEAD_TILE
|
||||
+ offsets % HEAD_TILE
|
||||
)
|
||||
else:
|
||||
value_off = block_offset * HEAD_DIM + offsets
|
||||
|
||||
tl.store(kv_cache_ptr + block_start + value_off, k_fp8, mask=mask)
|
||||
scale_byte_off = block_start + cache_block_size * HEAD_DIM + block_offset * 4
|
||||
tl.store(kv_cache_scale_ptr + scale_byte_off // 4, scale)
|
||||
|
||||
@@ -137,6 +164,9 @@ def _fused_norm_rope_kernel(
|
||||
indexer_cache_scale_ptr,
|
||||
indexer_cache_block_size,
|
||||
indexer_cache_stride,
|
||||
INDEXER_CACHE_SHUFFLE: tl.constexpr,
|
||||
INDEXER_CACHE_BLOCK_TILE: tl.constexpr,
|
||||
INDEXER_CACHE_HEAD_TILE: tl.constexpr,
|
||||
# MLA KV cache (concat kv_c_normed + k_pe_roped, uses slot_mapping_ptr)
|
||||
mla_cache_ptr,
|
||||
mla_cache_block_stride,
|
||||
@@ -161,12 +191,15 @@ def _fused_norm_rope_kernel(
|
||||
HAS_INDEXER: tl.constexpr,
|
||||
INDEX_ROPE_INTERLEAVE: tl.constexpr,
|
||||
USE_PDL: tl.constexpr,
|
||||
FP8_MAX: tl.constexpr,
|
||||
USE_FNUZ: tl.constexpr,
|
||||
):
|
||||
tok_idx = tl.program_id(0).to(tl.int64)
|
||||
pid = tl.program_id(1)
|
||||
if USE_PDL:
|
||||
tl.extra.cuda.gdc_wait()
|
||||
tl.extra.cuda.gdc_launch_dependents()
|
||||
fp8_dtype = tl.float8e4b8 if USE_FNUZ else tl.float8e4nv
|
||||
if pid == 3:
|
||||
if not HAS_INDEXER:
|
||||
# Shared layer: reuse the previous indexer layer's top-k; do not
|
||||
@@ -327,9 +360,11 @@ def _fused_norm_rope_kernel(
|
||||
)
|
||||
kv_2d = tl.reshape(kv_c, (MLA_NUM_TILES, MLA_TILE_DIM))
|
||||
tile_amax = tl.max(tl.abs(kv_2d), axis=1, keep_dims=True)
|
||||
tile_scale = tl.maximum(tile_amax * (1.0 / 448.0), 1e-4)
|
||||
# scale = amax / FP8_MAX (fp8 e4m3 max), matching the reference
|
||||
# concat_and_cache_ds_mla kernel; floored to FLT_MIN.
|
||||
tile_scale = tl.maximum(tile_amax * (1.0 / FP8_MAX), 1e-4)
|
||||
tile_scale = tl.math.exp2(tl.math.ceil(tl.math.log2(tile_scale)))
|
||||
kv_c_fp8 = tl.reshape((kv_2d / tile_scale).to(tl.float8e4nv), (KV_DIM,))
|
||||
kv_c_fp8 = tl.reshape((kv_2d / tile_scale).to(fp8_dtype), (KV_DIM,))
|
||||
tl.store(mla_cache_ptr + byte_base + kv_block, kv_c_fp8)
|
||||
tile_off = tl.arange(0, MLA_NUM_TILES)
|
||||
tl.store(
|
||||
@@ -349,16 +384,16 @@ def _fused_norm_rope_kernel(
|
||||
# kv_c_normed (KV_DIM elements)
|
||||
if MLA_CACHE_FP8:
|
||||
scale = tl.load(mla_cache_scale_ptr)
|
||||
kv_c_fp8 = (kv_c.to(tl.float32) / scale).to(tl.float8e4nv)
|
||||
kv_c_fp8 = (kv_c.to(tl.float32) / scale).to(fp8_dtype)
|
||||
tl.store(dst + kv_block, kv_c_fp8)
|
||||
else:
|
||||
tl.store(dst + kv_block, kv_c)
|
||||
# k_pe_roped (from registers, interleaved layout)
|
||||
if MLA_CACHE_FP8:
|
||||
tl.store(dst + KV_DIM + dim_off * 2, (r1 / scale).to(tl.float8e4nv))
|
||||
tl.store(dst + KV_DIM + dim_off * 2, (r1 / scale).to(fp8_dtype))
|
||||
tl.store(
|
||||
dst + KV_DIM + dim_off * 2 + 1,
|
||||
(r2 / scale).to(tl.float8e4nv),
|
||||
(r2 / scale).to(fp8_dtype),
|
||||
)
|
||||
else:
|
||||
tl.store(dst + KV_DIM + dim_off * 2, r1)
|
||||
@@ -463,6 +498,11 @@ def _fused_norm_rope_kernel(
|
||||
indexer_cache_stride,
|
||||
index_k_block,
|
||||
INDEX_K_DIM,
|
||||
INDEXER_CACHE_SHUFFLE,
|
||||
INDEXER_CACHE_BLOCK_TILE,
|
||||
INDEXER_CACHE_HEAD_TILE,
|
||||
FP8_MAX,
|
||||
USE_FNUZ,
|
||||
)
|
||||
|
||||
|
||||
@@ -486,6 +526,7 @@ def fused_norm_rope(
|
||||
slot_mapping: torch.Tensor | None = None,
|
||||
indexer_slot_mapping: torch.Tensor | None = None,
|
||||
indexer_k_cache: torch.Tensor | None = None,
|
||||
indexer_cache_shuffled: bool = False,
|
||||
mla_kv_cache: torch.Tensor | None = None,
|
||||
mla_kv_cache_dtype: str = "auto",
|
||||
mla_k_scale: torch.Tensor | None = None,
|
||||
@@ -532,13 +573,28 @@ def fused_norm_rope(
|
||||
indexer_slot_mapping = slot_mapping
|
||||
idx_cache_scale_view = indexer_k_cache.view(torch.uint8).view(torch.float32)
|
||||
idx_cache_block_size = indexer_k_cache.shape[1]
|
||||
idx_cache_stride = indexer_k_cache.shape[2]
|
||||
# The caller's cache reports whether its reader expects the shuffled
|
||||
# layout; see DeepseekV32IndexerCache.uses_shuffled_layout.
|
||||
idx_cache_shuffle = indexer_cache_shuffled
|
||||
if idx_cache_shuffle:
|
||||
assert idx_cache_block_size % _INDEXER_CACHE_BLOCK_TILE == 0, (
|
||||
f"indexer K cache block size {idx_cache_block_size} must be a "
|
||||
f"multiple of {_INDEXER_CACHE_BLOCK_TILE} for the shuffled layout"
|
||||
)
|
||||
idx_cache_stride = indexer_k_cache.stride(0)
|
||||
if indexer_k_cache.dtype == torch.uint8:
|
||||
indexer_k_cache = indexer_k_cache.view(torch.float8_e4m3fn)
|
||||
indexer_k_cache = indexer_k_cache.view(_FP8_DTYPE)
|
||||
# The head tile is a byte count; the kernel indexes the cache in elements.
|
||||
idx_cache_head_tile = (
|
||||
_INDEXER_CACHE_HEAD_TILE_BYTES // indexer_k_cache.element_size()
|
||||
)
|
||||
else:
|
||||
idx_cache_scale_view = None
|
||||
idx_cache_block_size = 1
|
||||
idx_cache_stride = 0
|
||||
idx_cache_shuffle = False
|
||||
idx_cache_head_tile = _INDEXER_CACHE_HEAD_TILE_BYTES
|
||||
|
||||
# --- MLA KV cache setup ---
|
||||
mla_cache_nvfp4 = mla_kv_cache_dtype == "nvfp4_ds_mla"
|
||||
@@ -561,12 +617,12 @@ def fused_norm_rope(
|
||||
mla_entry_stride = u8_cache.stride(1)
|
||||
mla_ds_scale_view = u8_cache.view(torch.float32)
|
||||
mla_ds_rope_view = u8_cache.view(torch.bfloat16)
|
||||
mla_kv_cache = u8_cache.view(torch.float8_e4m3fn)
|
||||
mla_kv_cache = u8_cache.view(_FP8_DTYPE)
|
||||
else:
|
||||
mla_block_stride = mla_kv_cache.stride(0)
|
||||
mla_entry_stride = mla_kv_cache.stride(1)
|
||||
if mla_cache_fp8 and mla_kv_cache.dtype == torch.uint8:
|
||||
mla_kv_cache = mla_kv_cache.view(torch.float8_e4m3fn)
|
||||
mla_kv_cache = mla_kv_cache.view(_FP8_DTYPE)
|
||||
if mla_k_scale is None:
|
||||
mla_k_scale = torch.ones(1, dtype=torch.float32, device=device)
|
||||
else:
|
||||
@@ -592,6 +648,9 @@ def fused_norm_rope(
|
||||
assert index_k_out.shape == index_k.shape
|
||||
index_k_out_stride = index_k_out.stride(0)
|
||||
use_pdl = current_platform.is_arch_support_pdl()
|
||||
pdl_kwargs = (
|
||||
{"USE_PDL": use_pdl, "launch_pdl": True} if use_pdl else {"USE_PDL": use_pdl}
|
||||
)
|
||||
_fused_norm_rope_kernel[(num_tokens, 4)](
|
||||
positions,
|
||||
# Q RMS norm
|
||||
@@ -639,6 +698,9 @@ def fused_norm_rope(
|
||||
idx_cache_scale_view,
|
||||
idx_cache_block_size,
|
||||
idx_cache_stride,
|
||||
idx_cache_shuffle,
|
||||
_INDEXER_CACHE_BLOCK_TILE,
|
||||
idx_cache_head_tile,
|
||||
# MLA KV cache (uses same slot_mapping)
|
||||
mla_kv_cache,
|
||||
mla_block_stride,
|
||||
@@ -659,8 +721,9 @@ def fused_norm_rope(
|
||||
TOPK_BLOCK_SIZE=1024,
|
||||
HAS_INDEXER=has_indexer,
|
||||
INDEX_ROPE_INTERLEAVE=index_rope_interleave,
|
||||
USE_PDL=use_pdl,
|
||||
launch_pdl=use_pdl,
|
||||
FP8_MAX=_FP8_MAX,
|
||||
USE_FNUZ=_USE_FNUZ,
|
||||
**pdl_kwargs,
|
||||
)
|
||||
return q_c_out
|
||||
|
||||
@@ -715,6 +778,8 @@ def _fused_q_kernel(
|
||||
INDEX_ROPE_INTERLEAVE: tl.constexpr,
|
||||
QUANTIZE_MQA: tl.constexpr,
|
||||
USE_PDL: tl.constexpr,
|
||||
FP8_MAX: tl.constexpr,
|
||||
USE_FNUZ: tl.constexpr,
|
||||
):
|
||||
tok_idx = tl.program_id(0).to(tl.int64)
|
||||
pid = tl.program_id(1)
|
||||
@@ -722,6 +787,7 @@ def _fused_q_kernel(
|
||||
if USE_PDL:
|
||||
tl.extra.cuda.gdc_wait()
|
||||
tl.extra.cuda.gdc_launch_dependents()
|
||||
fp8_dtype = tl.float8e4b8 if USE_FNUZ else tl.float8e4nv
|
||||
|
||||
if pid == 2:
|
||||
# ql_nope quantize + pack into the front of mqa_q_fp8. On the bf16
|
||||
@@ -744,7 +810,7 @@ def _fused_q_kernel(
|
||||
+ ql_nope_off,
|
||||
mask=ql_nope_mask,
|
||||
).to(tl.float32)
|
||||
ql_nope_fp8 = (ql_nope / scale).to(tl.float8e4nv)
|
||||
ql_nope_fp8 = (ql_nope / scale).to(fp8_dtype)
|
||||
tl.store(
|
||||
mqa_q_fp8_ptr
|
||||
+ tok_idx * mqa_q_fp8_stride0
|
||||
@@ -794,7 +860,7 @@ def _fused_q_kernel(
|
||||
+ q_head_idx * mqa_q_fp8_stride1
|
||||
+ QL_NOPE_DIM
|
||||
+ rot_off * 2,
|
||||
(r1 / scale).to(tl.float8e4nv),
|
||||
(r1 / scale).to(fp8_dtype),
|
||||
)
|
||||
tl.store(
|
||||
mqa_q_fp8_ptr
|
||||
@@ -803,7 +869,7 @@ def _fused_q_kernel(
|
||||
+ QL_NOPE_DIM
|
||||
+ rot_off * 2
|
||||
+ 1,
|
||||
(r2 / scale).to(tl.float8e4nv),
|
||||
(r2 / scale).to(fp8_dtype),
|
||||
)
|
||||
else:
|
||||
# bf16 query: write the RoPE'd q_pe unquantized.
|
||||
@@ -861,7 +927,7 @@ def _fused_q_kernel(
|
||||
index_q = tl.where(in_rope, roped, index_q)
|
||||
|
||||
# Index Q Quantize (from registers)
|
||||
index_q_fp8, index_q_scale = _fp8_ue8m0_quantize(index_q)
|
||||
index_q_fp8, index_q_scale = _fp8_ue8m0_quantize(index_q, FP8_MAX, USE_FNUZ)
|
||||
tl.store(
|
||||
index_q_fp8_ptr
|
||||
+ tok_idx * index_q_fp8_stride0
|
||||
@@ -955,7 +1021,7 @@ def fused_q(
|
||||
q_pe.shape[0],
|
||||
q_pe.shape[1],
|
||||
ql_nope.shape[2] + q_pe.shape[2],
|
||||
dtype=torch.float8_e4m3fn,
|
||||
dtype=_FP8_DTYPE,
|
||||
device=q_pe.device,
|
||||
)
|
||||
# Placeholder; pid 0 packs q_pe into mqa_q_fp8 instead.
|
||||
@@ -967,7 +1033,7 @@ def fused_q(
|
||||
mqa_q_fp8 = q_pe_out # unused placeholder for the fp8 pack pointer
|
||||
mqa_q = q_pe_out
|
||||
|
||||
index_q_fp8 = torch.empty_like(index_q, dtype=torch.float8_e4m3fn)
|
||||
index_q_fp8 = torch.empty_like(index_q, dtype=_FP8_DTYPE)
|
||||
index_weights_out = torch.empty_like(index_weights, dtype=torch.float32)
|
||||
if cutedsl_kernel is not None:
|
||||
cutedsl_kernel(
|
||||
@@ -990,6 +1056,9 @@ def fused_q(
|
||||
return index_q_fp8, index_weights_out, mqa_q
|
||||
|
||||
use_pdl = current_platform.is_arch_support_pdl()
|
||||
pdl_kwargs = (
|
||||
{"USE_PDL": use_pdl, "launch_pdl": True} if use_pdl else {"USE_PDL": use_pdl}
|
||||
)
|
||||
_fused_q_kernel[(num_tokens, 3, grid_heads)](
|
||||
positions,
|
||||
q_pe,
|
||||
@@ -1031,8 +1100,9 @@ def fused_q(
|
||||
HAS_INDEXER=has_indexer,
|
||||
INDEX_ROPE_INTERLEAVE=index_rope_interleave,
|
||||
QUANTIZE_MQA=quantize_mqa,
|
||||
USE_PDL=use_pdl,
|
||||
launch_pdl=use_pdl,
|
||||
FP8_MAX=_FP8_MAX,
|
||||
USE_FNUZ=_USE_FNUZ,
|
||||
**pdl_kwargs,
|
||||
# num_warps=1 is optimal here: each program is a single 128-element
|
||||
# rope+quant, so the kernel is program-count/occupancy bound, not
|
||||
# per-program compute bound (swept 1/2/4/8 — 1 wins or ties everywhere).
|
||||
|
||||
Reference in New Issue
Block a user