Skip to content

Commit 07aedaa

Browse files
[Bugfix][Feature] Adapt DSA-CP for sparse C8 MXFP8 dynamic quant in SFA (vllm-project#10060)
### What this PR does / why we need it? This PR adapts the sparse C8 DSA-CP attention path for MXFP8 dynamic quantization on A5. It updates the SFA implementation so the DSA-CP sparse C8 path can handle MXFP8 dynamic quantized execution with the expected tensor layouts and cached projection parameters. ### Does this PR introduce _any_ user-facing change? No. ### How was this patch tested? Not run locally. The change is scoped to the sparse C8 DSA-CP MXFP8 dynamic quantized attention path and should be covered by CI/NPU validation for the affected model path. - vLLM version: v0.22.1 - vLLM main: vllm-project/vllm@967c5c3 --------- Signed-off-by: yyt <yangyit139@gmail.com> Signed-off-by: TmacAaron <yangyit139@gmail.com> Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
1 parent 21582fe commit 07aedaa

1 file changed

Lines changed: 101 additions & 11 deletions

File tree

vllm_ascend/attention/sfa_v1.py

Lines changed: 101 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -484,6 +484,7 @@ def __init__(
484484

485485
# dsa c8
486486
self.use_sparse_c8_indexer = ascend_config.is_sparse_c8_layer(self.layer_name)
487+
self.use_a5_sparse_c8_indexer = self.use_sparse_c8_indexer and (get_ascend_device_type() == AscendDeviceType.A5)
487488
if self.use_sparse_c8_indexer:
488489
if get_ascend_device_type() == AscendDeviceType.A5:
489490
self.c8_k_cache_dtype = torch.float8_e4m3fn
@@ -961,6 +962,21 @@ def exec_kv(
961962
cache_mode = "PA"
962963

963964
if self.enable_dsa_cp:
965+
if self.use_a5_sparse_c8_indexer:
966+
k_pe, k_nope, knope_scale = custom_kv_rmsnorm_rope(
967+
kv_no_split,
968+
self.kv_a_layernorm.weight, # type: ignore[union-attr]
969+
cos,
970+
sin,
971+
slots.to(torch.int64),
972+
kv_cache[1],
973+
kv_cache[0],
974+
epsilon=self.kv_a_layernorm.variance_epsilon, # type: ignore[union-attr]
975+
cache_mode=cache_mode,
976+
is_output_kv=True,
977+
)
978+
return k_pe, k_nope, knope_scale
979+
964980
_, _, k_pe, k_nope = torch_npu.npu_kv_rmsnorm_rope_cache(
965981
kv_no_split,
966982
self.kv_a_layernorm.weight, # type: ignore[union-attr]
@@ -973,7 +989,8 @@ def exec_kv(
973989
cache_mode=cache_mode,
974990
is_output_kv=True,
975991
)
976-
return k_pe, k_nope
992+
knope_scale = None
993+
return k_pe, k_nope, knope_scale
977994
else:
978995
torch_npu.npu_kv_rmsnorm_rope_cache(
979996
kv_no_split,
@@ -1283,7 +1300,9 @@ def forward(
12831300

12841301
if self.enable_dsa_cp:
12851302
assert slot_mapping_cp is not None
1286-
k_pe, k_nope = self.exec_kv(kv_no_split, cos, sin, kv_cache, slot_mapping_cp, attn_metadata)
1303+
k_pe, k_nope, knope_scale = self.exec_kv(
1304+
kv_no_split, cos, sin, kv_cache, slot_mapping_cp, attn_metadata
1305+
)
12871306
else:
12881307
k_pe, k_nope = self.exec_kv(kv_no_split, cos, sin, kv_cache, slot_mapping, attn_metadata)
12891308

@@ -1306,6 +1325,32 @@ def forward(
13061325
get_tp_group(),
13071326
async_op=async_op,
13081327
)
1328+
elif self.use_a5_sparse_c8_indexer:
1329+
# due to different dtypes, we have to split commu pass
1330+
assert knope_scale is not None
1331+
assert k_li_scale is not None
1332+
fused_kv_no_split, _ = all_gather_async(
1333+
torch.cat(
1334+
[
1335+
k_nope.view(-1, k_nope.shape[-1]),
1336+
k_pe.view(-1, k_pe.shape[-1]),
1337+
knope_scale.view(-1, knope_scale.shape[-1]),
1338+
],
1339+
dim=1,
1340+
),
1341+
get_tp_group(),
1342+
async_op=async_op,
1343+
)
1344+
k_li, _ = all_gather_async(
1345+
k_li,
1346+
get_tp_group(),
1347+
async_op=async_op,
1348+
)
1349+
k_li_scale, kv_ag_handle = all_gather_async(
1350+
k_li_scale,
1351+
get_tp_group(),
1352+
async_op=async_op,
1353+
)
13091354
else:
13101355
# due to different dtypes, we have to split commu pass
13111356
assert k_li_scale is not None
@@ -1359,17 +1404,26 @@ def forward(
13591404
k_pe, k_nope, k_li = fused_kv_no_split.split(
13601405
[self.qk_rope_head_dim, self.kv_lora_rank, self.head_dim], dim=-1
13611406
)
1407+
elif self.use_a5_sparse_c8_indexer:
1408+
torch_npu.npu_scatter_nd_update_(
1409+
kv_cache[0].view(-1, fused_kv_no_split.shape[-1]),
1410+
slot_mapping[: attn_metadata.num_actual_tokens].view(-1, 1),
1411+
fused_kv_no_split[: attn_metadata.num_actual_tokens],
1412+
)
1413+
k_pe = None
1414+
k_nope = None
13621415
else:
13631416
k_pe, k_nope = fused_kv_no_split.split([self.qk_rope_head_dim, self.kv_lora_rank], dim=-1)
1364-
k_nope = k_nope.view(k_nope.shape[0], 1, -1)
1365-
k_pe = k_pe.view(k_pe.shape[0], 1, -1)
1366-
DeviceOperator.reshape_and_cache(
1367-
key=k_nope[: attn_metadata.num_actual_tokens],
1368-
value=k_pe[: attn_metadata.num_actual_tokens],
1369-
key_cache=kv_cache[0],
1370-
value_cache=kv_cache[1],
1371-
slot_mapping=slot_mapping[: attn_metadata.num_actual_tokens],
1372-
)
1417+
if not self.use_a5_sparse_c8_indexer:
1418+
k_nope = k_nope.view(k_nope.shape[0], 1, -1)
1419+
k_pe = k_pe.view(k_pe.shape[0], 1, -1)
1420+
DeviceOperator.reshape_and_cache(
1421+
key=k_nope[: attn_metadata.num_actual_tokens],
1422+
value=k_pe[: attn_metadata.num_actual_tokens],
1423+
key_cache=kv_cache[0],
1424+
value_cache=kv_cache[1],
1425+
slot_mapping=slot_mapping[: attn_metadata.num_actual_tokens],
1426+
)
13731427

13741428
k_li = self._get_full_kv(k_li, attn_metadata)
13751429

@@ -1480,3 +1534,39 @@ def forward(
14801534
maybe_save_kv_layer_to_connector(layer_name, list(kv_cache))
14811535

14821536
return output_padded
1537+
1538+
1539+
def custom_kv_rmsnorm_rope(
1540+
kv,
1541+
gamma,
1542+
cos,
1543+
sin,
1544+
index,
1545+
k_cache,
1546+
ckv_cache,
1547+
k_rope_scale=None,
1548+
c_kv_scale=None,
1549+
k_rope_offset=None,
1550+
c_kv_offset=None,
1551+
v=None,
1552+
epsilon=1e-05,
1553+
cache_mode="Norm",
1554+
is_output_kv=False,
1555+
):
1556+
# Split KV into RMSNorm and RoPE parts for sparse C8 cache preparation.
1557+
rms_in, rope_in = kv.split([512, 64], dim=-1)
1558+
k_nope, _ = torch_npu.npu_rms_norm(rms_in, gamma, epsilon=epsilon)
1559+
k_rope = torch_npu.npu_interleave_rope(rope_in, cos, sin)
1560+
1561+
# Store k_nope with block FP8 scales for the sparse C8 cache layout.
1562+
k_nope, knope_scale = torch_npu.npu_dynamic_block_quant(
1563+
k_nope.view(-1, 1, k_nope.shape[-1]),
1564+
dst_type=torch.float8_e4m3fn,
1565+
row_block_size=1,
1566+
col_block_size=128,
1567+
)
1568+
return (
1569+
k_rope.view(torch.float8_e4m3fn),
1570+
k_nope,
1571+
knope_scale.view(knope_scale.shape[0], -1).view(torch.float8_e4m3fn),
1572+
)

0 commit comments

Comments
 (0)