Skip to content

Commit 81f3694

Browse files
authored
Merge DeepSeek V4 WNA16 SM86 runtime
Merge the experimentally validated runtime history onto the pinned SM8x incubation branch. The original base remains preserved as tag deepseek-v4-sm8x-base-12810046; the validated source head remains 9a2ffbb.
2 parents 1281004 + 9a2ffbb commit 81f3694

23 files changed

Lines changed: 1431 additions & 116 deletions

File tree

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3+
4+
import pytest
5+
6+
7+
@pytest.fixture
8+
def should_do_global_cleanup_after_test() -> bool:
9+
return False
10+
11+
12+
def test_cuda_split_k_decode_requires_sufficient_shared_memory() -> None:
13+
from vllm.v1.attention.ops import rocm_aiter_mla_sparse as sparse_mla
14+
15+
assert not sparse_mla.cuda_split_k_decode_supported(101_376)
16+
assert sparse_mla.cuda_split_k_decode_supported(166_912)
17+
18+
19+
def test_cuda_split_k_decode_dispatches_by_shared_memory(monkeypatch) -> None:
20+
from vllm.v1.attention.ops import rocm_aiter_mla_sparse as sparse_mla
21+
22+
monkeypatch.setattr(sparse_mla.current_platform, "is_cuda", lambda: True)
23+
sparse_mla._use_split_k_decode.cache_clear()
24+
25+
monkeypatch.setattr(sparse_mla, "get_max_shared_memory_bytes", lambda: 101_376)
26+
assert not sparse_mla._use_split_k_decode()
27+
28+
sparse_mla._use_split_k_decode.cache_clear()
29+
monkeypatch.setattr(sparse_mla, "get_max_shared_memory_bytes", lambda: 166_912)
30+
assert sparse_mla._use_split_k_decode()
31+
32+
sparse_mla._use_split_k_decode.cache_clear()

tests/kernels/moe/test_moe.py

Lines changed: 199 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1182,16 +1182,25 @@ def test_fused_marlin_moe_non_gated(
11821182
torch.testing.assert_close(marlin_output, torch_output, atol=1e-1, rtol=0)
11831183

11841184

1185-
def _make_humming_indexed_experts(activation: MoEActivation):
1185+
def _make_humming_indexed_experts(
1186+
activation: MoEActivation,
1187+
*,
1188+
weight_schema: Any = None,
1189+
weight_schemas: dict[str, Any] | None = None,
1190+
tensor_factory: Callable[..., torch.Tensor] | None = None,
1191+
top_k: int = 6,
1192+
num_experts: int = 12,
1193+
hidden_size: int = 2688,
1194+
intermediate_size: int = 1856,
1195+
swiglu_limit: float | None = None,
1196+
):
11861197
pytest.importorskip("humming")
11871198
from vllm.model_executor.layers.fused_moe.experts.fused_humming_moe import (
11881199
HummingIndexedExperts,
11891200
)
11901201
from vllm.model_executor.layers.quantization.utils import humming_utils
11911202
from vllm.utils import humming
11921203

1193-
top_k, num_experts = 6, 12
1194-
hidden_size, intermediate_size = 2688, 1856
11951204
gate_up_size = intermediate_size * 2 if activation.is_gated else intermediate_size
11961205
num_w13_stacks = 2 if activation.is_gated else 1
11971206
moe_config = make_dummy_moe_config(
@@ -1205,35 +1214,51 @@ def _make_humming_indexed_experts(activation: MoEActivation):
12051214
layer = torch.nn.Module()
12061215
layer.moe_config = moe_config
12071216
layer.params_dtype = torch.bfloat16
1217+
layer.swiglu_limit = swiglu_limit
12081218

1209-
weight_schema = humming.ModeloptNvfp4WeightSchema()
1219+
weight_schema = weight_schema or humming.ModeloptNvfp4WeightSchema()
12101220
for sublayer_name, shape_n, shape_k, stack_size in (
12111221
("w13", gate_up_size, hidden_size, num_w13_stacks),
12121222
("w2", hidden_size, intermediate_size, 1),
12131223
):
1214-
tensor_attrs = weight_schema.get_tensors_attrs(
1224+
sublayer_weight_schema = (
1225+
weight_schemas.get(sublayer_name, weight_schema)
1226+
if weight_schemas is not None
1227+
else weight_schema
1228+
)
1229+
tensor_attrs = sublayer_weight_schema.get_tensors_attrs(
12151230
shape_n=shape_n,
12161231
shape_k=shape_k,
12171232
param_dtype=layer.params_dtype,
12181233
num_experts=num_experts,
12191234
stack_size=stack_size,
12201235
)
12211236
for tensor_name, attrs in tensor_attrs.items():
1237+
tensor = (
1238+
tensor_factory(
1239+
sublayer_name=sublayer_name,
1240+
tensor_name=tensor_name,
1241+
attrs=attrs,
1242+
shape_n=shape_n,
1243+
shape_k=shape_k,
1244+
num_experts=num_experts,
1245+
)
1246+
if tensor_factory is not None
1247+
else torch.ones(
1248+
attrs["shape"],
1249+
dtype=attrs["dtype"],
1250+
device="cuda",
1251+
)
1252+
)
12221253
layer.register_parameter(
12231254
f"{sublayer_name}_{tensor_name}",
1224-
Parameter(
1225-
torch.ones(
1226-
attrs["shape"],
1227-
dtype=attrs["dtype"],
1228-
device="cuda",
1229-
),
1230-
requires_grad=False,
1231-
),
1255+
Parameter(tensor, requires_grad=False),
12321256
)
12331257

12341258
humming_utils.convert_to_humming_moe_kernel_format(
12351259
layer,
12361260
weight_schema=weight_schema,
1261+
weight_schemas=weight_schemas,
12371262
input_schema=humming.HummingInputSchema(a_dtype=humming.dtypes.bfloat16),
12381263
)
12391264

@@ -1350,6 +1375,167 @@ def test_humming_indexed_writes_supplied_output_buffer():
13501375
assert torch.isfinite(output).all()
13511376

13521377

1378+
@pytest.mark.skipif(not current_platform.is_cuda(), reason="Requires CUDA")
1379+
@pytest.mark.parametrize(
1380+
("w13_group_size", "w2_group_size", "down_bits"),
1381+
[
1382+
(128, 128, 2),
1383+
(256, 128, 2),
1384+
(512, 256, 2),
1385+
(512, 512, 2),
1386+
(512, 128, 4),
1387+
(512, 256, 4),
1388+
(512, 512, 4),
1389+
],
1390+
ids=[
1391+
"w2-g128-g128",
1392+
"w2-g256-g128",
1393+
"w2-g512-g256",
1394+
"w2-g512-g512",
1395+
"w4-g512-g128",
1396+
"w4-g512-g256",
1397+
"w4-g512-g512",
1398+
],
1399+
)
1400+
def test_humming_wna16_grouped_indexed_numerical_oracle(
1401+
w13_group_size, w2_group_size, down_bits
1402+
):
1403+
from vllm.forward_context import set_forward_context
1404+
from vllm.utils import humming
1405+
1406+
weight_scale = 2**-5
1407+
swiglu_limit = 10.0
1408+
weight_schemas = {
1409+
sublayer_name: humming.CompressedTensorsWeightSchema(
1410+
format="pack-quantized",
1411+
type="int",
1412+
num_bits=bits,
1413+
strategy="group",
1414+
symmetric=True,
1415+
group_size=group_size,
1416+
)
1417+
for sublayer_name, bits, group_size in (
1418+
("w13", 2, w13_group_size),
1419+
("w2", down_bits, w2_group_size),
1420+
)
1421+
}
1422+
1423+
def make_positive_unit_weights(
1424+
*,
1425+
sublayer_name: str,
1426+
tensor_name: str,
1427+
attrs: dict[str, Any],
1428+
shape_n: int,
1429+
shape_k: int,
1430+
num_experts: int,
1431+
) -> torch.Tensor:
1432+
if tensor_name == "weight_packed":
1433+
# Compressed-tensors offsets signed codes into unsigned lanes.
1434+
# W2 +1 is lane 0b11; W4 +1 is lane 0b1001.
1435+
packed_positive_one = -1 if sublayer_name == "w13" else -1717986919
1436+
if sublayer_name == "w2" and down_bits == 2:
1437+
packed_positive_one = -1
1438+
return torch.full(
1439+
attrs["shape"],
1440+
packed_positive_one,
1441+
dtype=attrs["dtype"],
1442+
device="cuda",
1443+
)
1444+
if tensor_name == "weight_scale":
1445+
return torch.full(
1446+
attrs["shape"],
1447+
weight_scale,
1448+
dtype=attrs["dtype"],
1449+
device="cuda",
1450+
)
1451+
assert tensor_name == "weight_shape"
1452+
return torch.tensor(
1453+
[[shape_n, shape_k]] * num_experts,
1454+
dtype=attrs["dtype"],
1455+
device="cuda",
1456+
)
1457+
1458+
activation = MoEActivation.SILU
1459+
experts = _make_humming_indexed_experts(
1460+
activation,
1461+
weight_schemas=weight_schemas,
1462+
tensor_factory=make_positive_unit_weights,
1463+
top_k=2,
1464+
num_experts=4,
1465+
hidden_size=512,
1466+
intermediate_size=512,
1467+
swiglu_limit=swiglu_limit,
1468+
)
1469+
layer = experts.layer
1470+
for sublayer_name, expected_dtype, expected_group_size in (
1471+
("w13", humming.dtypes.uint2, w13_group_size),
1472+
(
1473+
"w2",
1474+
humming.dtypes.uint2 if down_bits == 2 else humming.dtypes.uint4,
1475+
w2_group_size,
1476+
),
1477+
):
1478+
meta = layer.humming_metas[sublayer_name]
1479+
assert meta.a_dtype == humming.dtypes.bfloat16
1480+
assert meta.b_dtype == expected_dtype
1481+
assert meta.weight_scale_group_size == expected_group_size
1482+
1483+
num_tokens = 4
1484+
top_k = experts.moe_config.experts_per_token
1485+
hidden_size = experts.moe_config.hidden_dim
1486+
intermediate_size = experts.moe_config.intermediate_size
1487+
num_experts = experts.moe_config.num_experts
1488+
workspace13_shape, workspace2_shape, _ = experts.workspace_shapes(
1489+
M=num_tokens,
1490+
N=intermediate_size,
1491+
K=hidden_size,
1492+
topk=top_k,
1493+
global_num_experts=num_experts,
1494+
local_num_experts=num_experts,
1495+
expert_tokens_meta=None,
1496+
activation=activation,
1497+
)
1498+
dtype = layer.params_dtype
1499+
workspace13 = torch.empty(workspace13_shape, dtype=dtype, device="cuda")
1500+
workspace2 = torch.empty(workspace2_shape, dtype=dtype, device="cuda")
1501+
hidden_states = torch.ones((num_tokens, hidden_size), dtype=dtype, device="cuda")
1502+
output = torch.full_like(hidden_states, torch.nan)
1503+
topk_weights = torch.full(
1504+
(num_tokens, top_k),
1505+
1 / top_k,
1506+
dtype=dtype,
1507+
device="cuda",
1508+
)
1509+
topk_ids = torch.zeros((num_tokens, top_k), dtype=torch.int32, device="cuda")
1510+
unused = torch.empty((num_experts, 0), device="cuda")
1511+
1512+
with set_forward_context(None, vllm_config, num_tokens=num_tokens):
1513+
experts.apply(
1514+
output=output,
1515+
hidden_states=hidden_states,
1516+
w1=unused,
1517+
w2=unused,
1518+
topk_weights=topk_weights,
1519+
topk_ids=topk_ids,
1520+
activation=activation,
1521+
global_num_experts=num_experts,
1522+
expert_map=None,
1523+
a1q_scale=None,
1524+
a2_scale=None,
1525+
workspace13=workspace13,
1526+
workspace2=workspace2,
1527+
expert_tokens_meta=None,
1528+
apply_router_weight_on_input=False,
1529+
)
1530+
1531+
gate_up = hidden_size * weight_scale
1532+
clamped_gate_up = min(gate_up, swiglu_limit)
1533+
expected_value = F.silu(torch.tensor(clamped_gate_up)) * clamped_gate_up
1534+
expected_value *= intermediate_size * weight_scale
1535+
expected = torch.full_like(output, expected_value.item())
1536+
torch.testing.assert_close(output, expected, atol=5e-3, rtol=2e-2)
1537+
1538+
13531539
@pytest.mark.parametrize("ep_size", [1, 2])
13541540
def test_moe_align_block_size_opcheck(ep_size):
13551541
num_experts = 4
Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
# SPDX-License-Identifier: Apache-2.0
2+
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3+
4+
from types import SimpleNamespace
5+
6+
import pytest
7+
import torch
8+
9+
import vllm.model_executor.custom_op as custom_op
10+
from vllm.model_executor.layers.rotary_embedding.deepseek_scaling_rope import (
11+
DeepseekV4ScalingRotaryEmbedding,
12+
)
13+
from vllm.models.deepseek_v4.common.rope import build_deepseek_v4_rope
14+
15+
16+
@pytest.fixture
17+
def should_do_global_cleanup_after_test() -> bool:
18+
return False
19+
20+
21+
def _deepseek_v4_rope_config() -> SimpleNamespace:
22+
return SimpleNamespace(
23+
rope_parameters={
24+
"rope_type": "yarn",
25+
"factor": 4,
26+
"original_max_position_embeddings": 16,
27+
"beta_fast": 32,
28+
"beta_slow": 1,
29+
},
30+
rope_theta=10_000,
31+
compress_rope_theta=160_000,
32+
)
33+
34+
35+
@pytest.fixture(autouse=True)
36+
def disable_optional_rope_backend_probe(monkeypatch: pytest.MonkeyPatch) -> None:
37+
compilation_config = SimpleNamespace(
38+
custom_ops=["none"],
39+
enabled_custom_ops=set(),
40+
disabled_custom_ops=set(),
41+
)
42+
monkeypatch.setattr(
43+
custom_op,
44+
"get_cached_compilation_config",
45+
lambda: compilation_config,
46+
)
47+
monkeypatch.setattr(
48+
DeepseekV4ScalingRotaryEmbedding,
49+
"enabled",
50+
classmethod(lambda cls: False),
51+
)
52+
53+
54+
def test_deepseek_v4_rope_cache_is_bounded_by_runtime_context() -> None:
55+
bounded = build_deepseek_v4_rope(
56+
_deepseek_v4_rope_config(),
57+
head_dim=8,
58+
rope_head_dim=4,
59+
max_position_embeddings=64,
60+
max_model_len=20,
61+
compress_ratio=1,
62+
)
63+
full = build_deepseek_v4_rope(
64+
_deepseek_v4_rope_config(),
65+
head_dim=8,
66+
rope_head_dim=4,
67+
max_position_embeddings=64,
68+
max_model_len=80,
69+
compress_ratio=1,
70+
)
71+
72+
assert bounded.cos_sin_cache.shape == (20, 4)
73+
assert full.cos_sin_cache.shape == (64, 4)
74+
torch.testing.assert_close(
75+
bounded.cos_sin_cache,
76+
full.cos_sin_cache[:20],
77+
rtol=0,
78+
atol=0,
79+
)
80+
assert bounded is not full
81+
82+
83+
def test_deepseek_v4_rope_cache_reuses_matching_runtime_context() -> None:
84+
first = build_deepseek_v4_rope(
85+
_deepseek_v4_rope_config(),
86+
head_dim=8,
87+
rope_head_dim=4,
88+
max_position_embeddings=64,
89+
max_model_len=20,
90+
compress_ratio=4,
91+
)
92+
second = build_deepseek_v4_rope(
93+
_deepseek_v4_rope_config(),
94+
head_dim=8,
95+
rope_head_dim=4,
96+
max_position_embeddings=64,
97+
max_model_len=20,
98+
compress_ratio=4,
99+
)
100+
101+
assert first is second

0 commit comments

Comments
 (0)