|
| 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 |
0 commit comments