Skip to content

Commit 71d787b

Browse files
committed
feat(hygon): support FL INT4 inference for Qwen3.6-27B, Qwen3.6-35B-A3B, and GLM-5.2
1 parent db9afd6 commit 71d787b

12 files changed

Lines changed: 3184 additions & 5 deletions

File tree

vllm_fl/__init__.py

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,22 @@ def register_router():
139139

140140
def register_model():
141141
"""Register FL-specific models not yet upstream."""
142+
143+
# Apply Hygon-specific GLM-5.2 compatibility patches only on Hygon.
144+
from vllm.platforms import current_platform
145+
146+
if getattr(current_platform, "vendor_name", None) == "hygon":
147+
from vllm_fl.patches.hygon_glm_kv_cache import apply_hygon_glm_kv_cache_patch
148+
from vllm_fl.patches.glm_index_share import apply_glm_index_share_patches
149+
150+
try:
151+
apply_hygon_glm_kv_cache_patch()
152+
apply_glm_index_share_patches()
153+
except Exception as e:
154+
logger.exception(f"Apply GLM IndexShare patches failed: {str(e)}")
155+
raise
156+
157+
142158
from vllm import ModelRegistry
143159

144160
_register_flagcx_connector()
Lines changed: 238 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,238 @@
1+
# Copyright 2026 BAAI. All rights reserved.
2+
3+
"""FlagGems-backed sparse MLA integration for vLLM Plugin FL.
4+
5+
This module adapts vLLM's ``FlashMLASparseBackend`` to the Hygon/BW1000
6+
runtime used by GLM-5.2. It has three deliberately narrow responsibilities:
7+
8+
1. expose sparse MLA through FL's ``CachedOp`` dispatch layer;
9+
2. bridge the GLM full/shared Indexer semantics from vLLM PR #45895 to the
10+
vLLM 0.20.x constructor contract; and
11+
3. replace FlagGems' default sparse FlashMLA Triton candidates with a tile
12+
that fits BW1000's per-workgroup shared-memory limit.
13+
14+
The GLM sparse Indexer runs before this backend. By the time
15+
``_bf16_flash_mla_kernel`` is called, ``topk_indices`` has already been
16+
converted from per-request logical token positions to physical KV-cache
17+
slots. The kernel consumes BF16 query/KV tensors and returns the latent-value
18+
attention output; this module does not calculate Indexer logits or TopK.
19+
20+
The FlagGems autotuner configuration is process-global. It is changed lazily,
21+
only on Hygon, and only once per worker process. Other platforms retain their
22+
existing FlagGems candidate list.
23+
"""
24+
25+
from __future__ import annotations
26+
27+
from threading import Lock
28+
from typing import ClassVar
29+
30+
import torch
31+
import triton
32+
33+
from vllm.config.cache import CacheDType
34+
from vllm.platforms.interface import DeviceCapability
35+
from vllm.v1.attention.backends.mla.flashmla_sparse import (
36+
FlashMLASparseBackend,
37+
FlashMLASparseImpl,
38+
)
39+
40+
from vllm_fl.dispatch import CachedOp
41+
42+
43+
_flash_mla_sparse_fwd = CachedOp(
44+
"flash_mla_sparse_fwd"
45+
)
46+
47+
_hygon_sparse_config_lock = Lock()
48+
_hygon_sparse_configured = False
49+
50+
51+
def _configure_hygon_flashmla_sparse() -> None:
52+
"""Install the BW1000-safe sparse FlashMLA Triton configuration.
53+
54+
FlagGems currently provides BK=64/BH=64 candidates. For GLM-5.2's BF16
55+
DQK=576 path, that configuration requests 81,920 bytes of shared memory,
56+
exceeding BW1000's 65,536-byte limit.
57+
58+
The platform guard prevents a Hygon-specific tuning decision from changing
59+
sparse MLA behavior on other vendors.
60+
61+
The fast path avoids locking after initialization; the second check prevents
62+
two threads from replacing the global candidate list concurrently during the
63+
first invocation.
64+
"""
65+
66+
from vllm.platforms import current_platform
67+
68+
if getattr(current_platform, "vendor_name", None) != "hygon":
69+
return
70+
71+
global _hygon_sparse_configured
72+
if _hygon_sparse_configured:
73+
return
74+
75+
with _hygon_sparse_config_lock:
76+
if _hygon_sparse_configured:
77+
return
78+
79+
from flag_gems.fused import flashmla_sparse
80+
81+
flashmla_sparse.triton_flash_mla_sparse_fwd.configs = [
82+
triton.Config(
83+
{"BK": 32, "BH": 32},
84+
num_warps=8,
85+
num_stages=2,
86+
)
87+
]
88+
_hygon_sparse_configured = True
89+
90+
91+
class _TopKBufferRef:
92+
"""Construction-only adapter for vLLM 0.20.x.
93+
94+
vLLM PR #45895 makes FlashMLASparseImpl accept topk_indices_buffer directly
95+
when indexer is None.
96+
97+
vLLM 0.20.x still requires indexer.topk_indices_buffer.
98+
This object exists only while calling the upstream constructor;
99+
it is not installed into the model and owns no parameters/cache.
100+
"""
101+
102+
__slots__ = ("topk_indices_buffer",)
103+
104+
def __init__(
105+
self,
106+
topk_indices_buffer: torch.Tensor,
107+
) -> None:
108+
self.topk_indices_buffer = topk_indices_buffer
109+
110+
111+
class SparseMLAFLImpl(FlashMLASparseImpl):
112+
"""Sparse MLA implementation with GLM sharing and BW1000 kernel support.
113+
114+
All metadata construction, logical-to-physical index conversion, and
115+
higher-level sparse-attention control flow remain in the upstream
116+
``FlashMLASparseImpl``. This subclass changes only the constructor bridge
117+
needed by shared Indexer layers and the final BF16 kernel invocation.
118+
"""
119+
def __init__(
120+
self,
121+
*args,
122+
topk_indices_buffer: torch.Tensor | None = None,
123+
indexer=None,
124+
**kwargs,
125+
) -> None:
126+
# Backport the semantic change from vLLM PR #45895:
127+
#
128+
# indexer.topk_indices_buffer
129+
# if indexer is not None
130+
# else topk_indices_buffer
131+
#
132+
# Reuse the complete vLLM 0.20.x constructor instead of
133+
# copying its implementation.
134+
if indexer is None:
135+
if topk_indices_buffer is None:
136+
raise RuntimeError(
137+
"Sparse MLA requires either a physical "
138+
"Indexer or topk_indices_buffer."
139+
)
140+
141+
upstream_indexer_arg = _TopKBufferRef(
142+
topk_indices_buffer
143+
)
144+
else:
145+
upstream_indexer_arg = indexer
146+
147+
super().__init__(
148+
*args,
149+
topk_indices_buffer=topk_indices_buffer,
150+
indexer=upstream_indexer_arg,
151+
**kwargs,
152+
)
153+
154+
def _bf16_flash_mla_kernel(
155+
self,
156+
q: torch.Tensor,
157+
kv_c_and_k_pe_cache: torch.Tensor,
158+
topk_indices: torch.Tensor,
159+
) -> torch.Tensor:
160+
_configure_hygon_flashmla_sparse()
161+
num_tokens = q.shape[0]
162+
163+
kv = kv_c_and_k_pe_cache.view(
164+
-1,
165+
1,
166+
kv_c_and_k_pe_cache.shape[-1],
167+
)
168+
169+
if self.num_heads % self.prefill_padding != 0:
170+
assert (
171+
self.prefill_padding % self.num_heads == 0
172+
)
173+
174+
q_padded = q.new_zeros(
175+
(
176+
q.shape[0],
177+
self.prefill_padding,
178+
q.shape[2],
179+
)
180+
)
181+
182+
q_padded[
183+
:, : self.num_heads, :
184+
] = q
185+
186+
q = q_padded
187+
188+
topk_indices = topk_indices.view(
189+
num_tokens,
190+
1,
191+
-1,
192+
)
193+
194+
out = torch.empty(
195+
(
196+
num_tokens,
197+
q.shape[1],
198+
self.kv_lora_rank,
199+
),
200+
dtype=q.dtype,
201+
device=q.device,
202+
)
203+
204+
out, _, _ = _flash_mla_sparse_fwd(
205+
q=q,
206+
kv=kv,
207+
indices=topk_indices,
208+
sm_scale=self.softmax_scale,
209+
attn_sink=None,
210+
topk_length=None,
211+
out=out,
212+
)
213+
214+
return out[:, : self.num_heads, :]
215+
216+
217+
class SparseMLAFLBackend(FlashMLASparseBackend):
218+
supported_kv_cache_dtypes: ClassVar[
219+
list[CacheDType]
220+
] = [
221+
"auto",
222+
"bfloat16",
223+
]
224+
225+
@staticmethod
226+
def get_name() -> str:
227+
return "FL_SPARSE_MLA"
228+
229+
@staticmethod
230+
def get_impl_cls():
231+
return SparseMLAFLImpl
232+
233+
@classmethod
234+
def supports_compute_capability(
235+
cls,
236+
capability: DeviceCapability,
237+
) -> bool:
238+
return True

vllm_fl/dispatch/backends/vendor/hygon/hygon.py

Lines changed: 119 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,26 @@ def attention_backend(self, use_mla: bool = False, use_sparse: bool = False) ->
129129

130130
if use_mla:
131131
if use_sparse:
132-
return AttentionBackendEnum.ROCM_AITER_MLA_SPARSE.get_path()
132+
sparse_backend = os.environ.get(
133+
"VLLM_FL_HYGON_SPARSE_MLA_BACKEND",
134+
"aiter",
135+
).strip().lower()
136+
137+
if sparse_backend == "aiter":
138+
return AttentionBackendEnum.ROCM_AITER_MLA_SPARSE.get_path()
139+
# Keep AITER as the default for backward compatibility with existing
140+
# Hygon deployments. GLM-5.2 on BW1000 can explicitly select the
141+
# validated FL/FlagGems path through the environment variable.
142+
if sparse_backend == "flag_gems":
143+
return (
144+
"vllm_fl.dispatch.backends.flaggems.impl.sparse_mla."
145+
"SparseMLAFLBackend"
146+
)
147+
148+
raise ValueError(
149+
"Unsupported VLLM_FL_HYGON_SPARSE_MLA_BACKEND value: "
150+
f"{sparse_backend!r}. Expected 'aiter' or 'flag_gems'."
151+
)
133152

134153
from vllm._aiter_ops import rocm_aiter_ops
135154

@@ -256,3 +275,102 @@ def grouped_topk(
256275
bias,
257276
scoring_func,
258277
)
278+
279+
def glm_hygon_indexer_fp8_mqa_logits(
280+
self,
281+
q,
282+
kv,
283+
weights,
284+
cu_seqlen_ks,
285+
cu_seqlen_ke,
286+
clean_logits,
287+
):
288+
"""Compute GLM Indexer logits for the prefill phase.
289+
290+
"""
291+
from .impl.indexer_mqa import glm_hygon_indexer_fp8_mqa_logits
292+
293+
return glm_hygon_indexer_fp8_mqa_logits(
294+
q,
295+
kv,
296+
weights,
297+
cu_seqlen_ks,
298+
cu_seqlen_ke,
299+
clean_logits,
300+
)
301+
302+
def glm_hygon_indexer_fp8_paged_mqa_logits(
303+
self,
304+
q,
305+
kv_cache,
306+
weights,
307+
context_lens,
308+
block_tables,
309+
max_model_len,
310+
head_dim,
311+
quant_block_size,
312+
):
313+
from .impl.indexer_mqa import (
314+
glm_hygon_indexer_fp8_paged_mqa_logits,
315+
)
316+
317+
return glm_hygon_indexer_fp8_paged_mqa_logits(
318+
q,
319+
kv_cache,
320+
weights,
321+
context_lens,
322+
block_tables,
323+
max_model_len,
324+
head_dim,
325+
quant_block_size,
326+
)
327+
328+
def glm_hygon_top_k_per_row_prefill(
329+
self,
330+
logits,
331+
row_starts,
332+
row_ends,
333+
indices,
334+
num_rows,
335+
stride0,
336+
stride1,
337+
topk_tokens,
338+
):
339+
from .impl.top_k_per_row import glm_hygon_top_k_per_row_prefill
340+
341+
return glm_hygon_top_k_per_row_prefill(
342+
logits,
343+
row_starts,
344+
row_ends,
345+
indices,
346+
num_rows,
347+
stride0,
348+
stride1,
349+
topk_tokens,
350+
)
351+
352+
def glm_hygon_top_k_per_row_decode(
353+
self,
354+
logits,
355+
next_n,
356+
seq_lens,
357+
indices,
358+
num_rows,
359+
stride0,
360+
stride1,
361+
topk_tokens,
362+
max_seq_len,
363+
):
364+
from .impl.top_k_per_row import glm_hygon_top_k_per_row_decode
365+
366+
return glm_hygon_top_k_per_row_decode(
367+
logits,
368+
next_n,
369+
seq_lens,
370+
indices,
371+
num_rows,
372+
stride0,
373+
stride1,
374+
topk_tokens,
375+
max_seq_len,
376+
)

0 commit comments

Comments
 (0)