Skip to content

Commit 36e1625

Browse files
authored
[bugfix] Split SFA patch by vllm-ascend version and fix patch runtime issues (#975)
## Purpose bugfix in sfa patch ## Modifications Fix UCM SFA forward patch compatibility across vllm-ascend `0.18.0rc1` and `0.18.0`, and resolve runtime issues introduced by method-level monkey patching. - Split the single `sfa_v1.py` patch into version-specific implementations: - `sfa_v1_rc1.py` for `0.18.0rc1` - `sfa_v1_0180.py` for `0.18.0` (drops the removed `enable_dsa_cp_strict_accuracy` branch) - Add `get_vllm_ascend_version_full()` to preserve `rc`/`post` suffixes for patch selection - Fix missing `get_tp_group` import in both SFA patch files - Fix `o_proj_full_pool` reference to use `type(self)` instead of the patch module stub class ## Test
1 parent 9800c5c commit 36e1625

4 files changed

Lines changed: 330 additions & 18 deletions

File tree

ucm/integration/vllm/patch/apply_patch.py

Lines changed: 25 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -43,27 +43,19 @@
4343
ENABLE_UCM_PATCH = os.environ.get("ENABLE_UCM_PATCH", "").lower() in ("1", "true")
4444

4545

46-
def get_vllm_ascend_version() -> Optional[str]:
47-
"""Detect vllm_ascend version if installed.
48-
49-
Note: keep it simple and robust (no hard import required).
50-
"""
46+
def _read_vllm_ascend_version_raw() -> Optional[str]:
47+
"""Read vllm_ascend version string, stripping only build metadata (+xxx)."""
5148

52-
def _norm(v: Optional[str]) -> Optional[str]:
49+
def _strip_build(v: Optional[str]) -> Optional[str]:
5350
if not v:
5451
return None
55-
v = str(v).strip()
56-
# common suffixes: 0.11.0+xxx / 0.11.0.post1 / 0.11.0rc1
57-
v = v.split("+", 1)[0]
58-
v = v.split(".post", 1)[0]
59-
v = v.split("rc", 1)[0]
60-
return v
52+
return str(v).strip().split("+", 1)[0]
6153

6254
try:
6355
from importlib.metadata import PackageNotFoundError, version
6456

6557
try:
66-
return _norm(version("vllm-ascend"))
58+
return _strip_build(version("vllm-ascend"))
6759
except PackageNotFoundError:
6860
return None
6961
except Exception:
@@ -73,11 +65,30 @@ def _norm(v: Optional[str]) -> Optional[str]:
7365
import importlib
7466

7567
mod = importlib.import_module("vllm_ascend")
76-
return _norm(getattr(mod, "__version__", None))
68+
return _strip_build(getattr(mod, "__version__", None))
7769
except Exception:
7870
return None
7971

8072

73+
def _norm_vllm_ascend_version(v: Optional[str]) -> Optional[str]:
74+
if not v:
75+
return None
76+
# common suffixes: 0.11.0.post1 / 0.11.0rc1
77+
v = v.split(".post", 1)[0]
78+
v = v.split("rc", 1)[0]
79+
return v
80+
81+
82+
def get_vllm_ascend_version_full() -> Optional[str]:
83+
"""Detect vllm_ascend version preserving rc/post suffixes (e.g. 0.18.0rc1)."""
84+
return _read_vllm_ascend_version_raw()
85+
86+
87+
def get_vllm_ascend_version() -> Optional[str]:
88+
"""Detect normalized vllm_ascend version (e.g. 0.18.0rc1 -> 0.18.0)."""
89+
return _norm_vllm_ascend_version(_read_vllm_ascend_version_raw())
90+
91+
8192
_vllm_version: Optional[str] = None
8293

8394

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
import torch
2+
import torch_npu
3+
from vllm.distributed import get_tp_group
4+
from vllm_ascend.ascend_forward_context import _EXTRA_CTX
5+
from vllm_ascend.attention.attention_v1 import AscendAttentionState
6+
from vllm_ascend.attention.mla_v1 import (
7+
MAX_O_PROJ_PREFETCH_SIZE,
8+
MLAPO_MAX_SUPPORTED_TOKENS,
9+
)
10+
from vllm_ascend.attention.utils import (
11+
maybe_save_kv_layer_to_connector,
12+
wait_for_kv_layer_from_connector,
13+
)
14+
from vllm_ascend.device.device_op import DeviceOperator
15+
from vllm_ascend.distributed.utils import all_gather_async
16+
from vllm_ascend.ops.layer_shard_linear import (
17+
is_hidden_layer,
18+
reach_layer_for_shard_weight_series,
19+
)
20+
from vllm_ascend.utils import get_weight_prefetch_method
21+
22+
23+
class AscendSFAImpl:
24+
def forward(
25+
self,
26+
layer_name,
27+
hidden_states: torch.Tensor, # query in unified attn
28+
kv_cache: tuple[torch.Tensor, torch.Tensor, torch.Tensor],
29+
attn_metadata,
30+
need_gather_q_kv: bool = False,
31+
output: torch.Tensor | None = None,
32+
) -> torch.Tensor:
33+
assert output is not None, "Output tensor must be provided."
34+
if attn_metadata is None:
35+
# Profiling run.
36+
if self.enable_dsa_cp_with_layer_shard and not _EXTRA_CTX.in_profile_run:
37+
for layer in self.layer_sharding_kwargs or []:
38+
if is_hidden_layer(layer):
39+
reach_layer_for_shard_weight_series(layer)
40+
return output.fill_(0)
41+
42+
cos = attn_metadata.cos
43+
sin = attn_metadata.sin
44+
slot_mapping = attn_metadata.slot_mapping
45+
slot_mapping_cp = None
46+
if self.enable_dsa_cp:
47+
assert attn_metadata.dsa_cp_context is not None
48+
slot_mapping_cp = attn_metadata.dsa_cp_context.slot_mapping_cp
49+
actual_seq_lengths_query = (
50+
attn_metadata.dsa_cp_context.actual_seq_lengths_query
51+
)
52+
actual_seq_lengths_key = attn_metadata.dsa_cp_context.actual_seq_lengths_key
53+
else:
54+
actual_seq_lengths_query = attn_metadata.cum_query_lens
55+
actual_seq_lengths_key = attn_metadata.seq_lens
56+
57+
# Inputs and outputs may be padded for CUDA graphs
58+
num_input_tokens = attn_metadata.num_input_tokens
59+
output_padded = output
60+
61+
# all-gather o_proj weight for prefill stage of PD mix node
62+
o_proj_full_handle = None
63+
# if is PD mix stage, using original TP o_proj weight, and also need to full gather for o_proj
64+
# weight for prefill stage.
65+
full_gather_o_proj_enabled = (
66+
self.enable_dsa_cp_with_o_proj_tp
67+
and attn_metadata.attn_state
68+
not in {
69+
AscendAttentionState.DecodeOnly,
70+
AscendAttentionState.SpecDecoding,
71+
}
72+
)
73+
74+
# run mlapo ops when dsa-cp is disabled, and ensure that num_tokens satisfies the count limitation
75+
if self.enable_mlapo and num_input_tokens <= MLAPO_MAX_SUPPORTED_TOKENS:
76+
hidden_states, ql_nope, q_pe, q_c = self._sfa_preprocess_with_mlapo(
77+
hidden_states=hidden_states,
78+
kv_cache=kv_cache,
79+
cos=cos,
80+
sin=sin,
81+
slot_mapping=slot_mapping,
82+
num_input_tokens=num_input_tokens,
83+
)
84+
k_li, k_li_scale = self.indexer_select_pre_process(
85+
x=hidden_states, cos=cos, sin=sin
86+
)
87+
# [patch] Add 'wait_for_kv_layer_from_connector' call for mlapo path
88+
wait_for_kv_layer_from_connector(layer_name)
89+
# native
90+
else:
91+
assert self.fused_qkv_a_proj is not None, "q lora is required for DSA."
92+
weight_prefetch_method = get_weight_prefetch_method()
93+
weight_prefetch_method.maybe_prefetch_mla_or_sla_weight_in_current_stream(
94+
inputs=self.fused_qkv_a_proj.weight, dependency=hidden_states
95+
)
96+
qkv_lora = self.fused_qkv_a_proj(hidden_states)[0]
97+
q_c, kv_no_split = qkv_lora.split(
98+
[self.q_lora_rank, self.kv_lora_rank + self.qk_rope_head_dim],
99+
dim=-1,
100+
)
101+
assert self.q_a_layernorm is not None, "q_a_layernorm must be initialized"
102+
q_c = self.q_a_layernorm(q_c)
103+
104+
k_li, k_li_scale = self.indexer_select_pre_process(
105+
x=hidden_states, cos=cos, sin=sin
106+
)
107+
108+
wait_for_kv_layer_from_connector(layer_name)
109+
110+
if self.enable_dsa_cp:
111+
assert slot_mapping_cp is not None
112+
k_pe, k_nope = self.exec_kv(
113+
kv_no_split, cos, sin, kv_cache, slot_mapping_cp, attn_metadata
114+
)
115+
else:
116+
k_pe, k_nope = self.exec_kv(
117+
kv_no_split, cos, sin, kv_cache, slot_mapping, attn_metadata
118+
)
119+
120+
if self.enable_dsa_cp:
121+
assert k_pe is not None
122+
assert k_nope is not None
123+
assert k_li is not None
124+
async_op = (
125+
self.enable_dsa_cp_with_layer_shard or full_gather_o_proj_enabled
126+
)
127+
# support all_gather kv async for communication calculation overlap
128+
if not self.use_sparse_c8_indexer:
129+
fused_kv_no_split, kv_ag_handle = all_gather_async(
130+
torch.cat(
131+
[
132+
k_pe.view(-1, k_pe.shape[-1]),
133+
k_nope.view(-1, k_nope.shape[-1]),
134+
k_li.view(-1, k_li.shape[-1]),
135+
],
136+
dim=1,
137+
),
138+
get_tp_group(),
139+
async_op=async_op,
140+
)
141+
else:
142+
# due to different dtypes, we have to split commu pass
143+
assert k_li_scale is not None
144+
fused_kv_no_split, _ = all_gather_async(
145+
torch.cat(
146+
[
147+
k_pe.view(-1, k_pe.shape[-1]),
148+
k_nope.view(-1, k_nope.shape[-1]),
149+
],
150+
dim=1,
151+
),
152+
get_tp_group(),
153+
async_op=async_op,
154+
)
155+
k_li, _ = all_gather_async(
156+
k_li,
157+
get_tp_group(),
158+
async_op=async_op,
159+
)
160+
k_li_scale, kv_ag_handle = all_gather_async(
161+
k_li_scale,
162+
get_tp_group(),
163+
async_op=async_op,
164+
)
165+
166+
ql_nope, q_pe = self._q_proj_and_k_up_proj(q_c)
167+
q_pe = self.rope_single(q_pe, cos, sin)
168+
169+
if self.enable_dsa_cp:
170+
if kv_ag_handle is not None:
171+
kv_ag_handle.wait()
172+
173+
if self.enable_dsa_cp_with_layer_shard:
174+
for layer in self.layer_sharding_kwargs or []:
175+
if is_hidden_layer(layer):
176+
reach_layer_for_shard_weight_series(layer)
177+
elif full_gather_o_proj_enabled:
178+
_, o_proj_full_handle = all_gather_async(
179+
self.o_proj_tp_weight,
180+
get_tp_group(),
181+
output=type(self).o_proj_full_pool,
182+
)
183+
184+
if kv_cache is not None:
185+
assert fused_kv_no_split is not None
186+
if not self.use_sparse_c8_indexer:
187+
k_pe, k_nope, k_li = fused_kv_no_split.split(
188+
[self.qk_rope_head_dim, self.kv_lora_rank, self.head_dim],
189+
dim=-1,
190+
)
191+
else:
192+
k_pe, k_nope = fused_kv_no_split.split(
193+
[self.qk_rope_head_dim, self.kv_lora_rank], dim=-1
194+
)
195+
k_nope = k_nope.view(k_nope.shape[0], 1, -1)
196+
k_pe = k_pe.view(k_pe.shape[0], 1, -1)
197+
DeviceOperator.reshape_and_cache(
198+
key=k_nope[: attn_metadata.num_actual_tokens],
199+
value=k_pe[: attn_metadata.num_actual_tokens],
200+
key_cache=kv_cache[0],
201+
value_cache=kv_cache[1],
202+
slot_mapping=slot_mapping[: attn_metadata.num_actual_tokens],
203+
)
204+
205+
k_li = self._get_full_kv(k_li, attn_metadata)
206+
207+
if kv_cache is not None:
208+
if self.is_kv_producer:
209+
attn_metadata.reshape_cache_event = torch.npu.Event()
210+
torch_npu.npu_scatter_nd_update_(
211+
kv_cache[2].view(-1, k_li.shape[-1]),
212+
slot_mapping.view(-1, 1),
213+
k_li.view(-1, k_li.shape[-1]),
214+
) # b, s, n, d
215+
if self.use_sparse_c8_indexer:
216+
assert len(kv_cache) == 4
217+
assert k_li_scale is not None
218+
torch_npu.npu_scatter_nd_update_(
219+
kv_cache[3].view(-1, k_li_scale.shape[-1]),
220+
slot_mapping.view(-1, 1),
221+
k_li_scale.view(-1, k_li_scale.shape[-1]),
222+
)
223+
if self.is_kv_producer:
224+
attn_metadata.reshape_cache_event.record()
225+
226+
topk_indices = self.indexer_select_post_process(
227+
x=hidden_states,
228+
q_c=q_c,
229+
kv_cache=kv_cache,
230+
attn_metadata=attn_metadata,
231+
cos=cos,
232+
sin=sin,
233+
actual_seq_lengths_query=actual_seq_lengths_query,
234+
actual_seq_lengths_key=actual_seq_lengths_key,
235+
)
236+
237+
attn_output = self._execute_sparse_flash_attention_process(
238+
ql_nope,
239+
q_pe,
240+
kv_cache,
241+
topk_indices,
242+
attn_metadata,
243+
actual_seq_lengths_query,
244+
actual_seq_lengths_key,
245+
)
246+
247+
attn_output = self._v_up_proj(attn_output)
248+
weight_prefetch_method = get_weight_prefetch_method()
249+
weight_prefetch_method.maybe_prefetch_mla_or_sla_weight_in_current_stream(
250+
inputs=self.o_proj.weight,
251+
dependency=attn_output,
252+
max_size=MAX_O_PROJ_PREFETCH_SIZE,
253+
linear_layer=self.o_proj,
254+
)
255+
256+
if self.enable_dsa_cp_with_o_proj_tp:
257+
# When using SFA-CP with pd mixed, o_proj has two cases:
258+
# 1. prefill: o_proj is a TP weight, we need to all-gather o_proj weight to switch TP=1.
259+
# 2. decode: all-to-all the hidden_state before the o_proj forward.
260+
result, require_o_proj_forward = (
261+
self._handle_o_proj_weight_switch_and_forward(
262+
attn_output=attn_output,
263+
output=output,
264+
o_proj_full_handle=o_proj_full_handle,
265+
should_shard_weight=full_gather_o_proj_enabled,
266+
)
267+
)
268+
if not require_o_proj_forward:
269+
return result
270+
attn_output = result
271+
272+
output[...] = self.o_proj(attn_output)[0]
273+
274+
maybe_save_kv_layer_to_connector(layer_name, list(kv_cache))
275+
276+
return output_padded

ucm/integration/vllm/patch/v0180/vllm_ascend/pc/attention/sfa_v1.py renamed to ucm/integration/vllm/patch/v0180/vllm_ascend/pc/attention/sfa_v1_rc1.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import torch
22
import torch_npu
3+
from vllm.distributed import get_tp_group
34
from vllm_ascend.ascend_forward_context import _EXTRA_CTX
45
from vllm_ascend.attention.attention_v1 import AscendAttentionState
56
from vllm_ascend.attention.mla_v1 import (
@@ -177,7 +178,7 @@ def forward(
177178
_, o_proj_full_handle = all_gather_async(
178179
self.o_proj_tp_weight,
179180
get_tp_group(),
180-
output=AscendSFAImpl.o_proj_full_pool,
181+
output=type(self).o_proj_full_pool,
181182
)
182183

183184
if kv_cache is not None:

0 commit comments

Comments
 (0)