@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 3f52fa5aa2
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 10, 20:15 UTC

[Model] Add support for openPangu moe model (#28775)

Signed-off-by: yuantao <2422264527@qq.com> Signed-off-by: yt0428 <51468697+yt0428@users.noreply.github.com> Co-authored-by: Lucas Wilkinson <LucasWilkinson@users.noreply.github.com> Co-authored-by: Cyrus Leung <tlleungac@connect.ust.hk>

yt0428 committed Dec 31, 2025 at 00:11 UTC 3f52fa5aa26d65c12ea81488986e1b8ecc4dcdc6
11 files changed +1136 -7
docs/models/supported_models.md
+1
@@ -433,6 +433,7 @@ th {
433 | `OrionForCausalLM` | Orion | `OrionStarAI/Orion-14B-Base`, `OrionStarAI/Orion-14B-Chat`, etc. | | ✅︎ |
434 | `OuroForCausalLM` | ouro | `ByteDance/Ouro-1.4B`, `ByteDance/Ouro-2.6B`, etc. | ✅︎ | |
435 | `PanguEmbeddedForCausalLM` |openPangu-Embedded-7B | `FreedomIntelligence/openPangu-Embedded-7B-V1.1` | ✅︎ | ✅︎ |
436 +| `PanguProMoEV2ForCausalLM` |openpangu-pro-moe-v2 | | ✅︎ | ✅︎ |
437 | `PanguUltraMoEForCausalLM` |openpangu-ultra-moe-718b-model | `FreedomIntelligence/openPangu-Ultra-MoE-718B-V1.1` | ✅︎ | ✅︎ |
438 | `PhiForCausalLM` | Phi | `microsoft/phi-1_5`, `microsoft/phi-2`, etc. | ✅︎ | ✅︎ |
439 | `Phi3ForCausalLM` | Phi-4, Phi-3 | `microsoft/Phi-4-mini-instruct`, `microsoft/Phi-4`, `microsoft/Phi-3-mini-4k-instruct`, `microsoft/Phi-3-mini-128k-instruct`, `microsoft/Phi-3-medium-128k-instruct`, etc. | ✅︎ | ✅︎ |
tests/models/registry.py
+5
@@ -402,6 +402,11 @@ _TEXT_GENERATION_EXAMPLE_MODELS = {
402 "PanguEmbeddedForCausalLM": _HfExamplesInfo(
403 "FreedomIntelligence/openPangu-Embedded-7B-V1.1", trust_remote_code=True
404 ),
405 + "PanguProMoEV2ForCausalLM": _HfExamplesInfo(
406 + "",
407 + trust_remote_code=True,
408 + is_available_online=False,
409 + ),
410 "PanguUltraMoEForCausalLM": _HfExamplesInfo(
411 "FreedomIntelligence/openPangu-Ultra-MoE-718B-V1.1",
412 trust_remote_code=True,
vllm/attention/backends/registry.py
+3
@@ -42,6 +42,9 @@ class AttentionBackendEnum(Enum, metaclass=_AttentionBackendEnumMeta):
42 """
43
44 FLASH_ATTN = "vllm.v1.attention.backends.flash_attn.FlashAttentionBackend"
45 + FLASH_ATTN_DIFFKV = (
46 + "vllm.v1.attention.backends.flash_attn_diffkv.FlashAttentionDiffKVBackend"
47 + )
48 TRITON_ATTN = "vllm.v1.attention.backends.triton_attn.TritonAttentionBackend"
49 ROCM_ATTN = "vllm.v1.attention.backends.rocm_attn.RocmAttentionBackend"
50 ROCM_AITER_MLA = "vllm.v1.attention.backends.mla.rocm_aiter_mla.AiterMLABackend"
vllm/attention/layer.py
+11 -4
@@ -170,6 +170,7 @@ class Attention(nn.Module, AttentionLayerBase):
170 attn_type: str = AttentionType.DECODER,
171 kv_sharing_target_layer_name: str | None = None,
172 attn_backend: type[AttentionBackend] | None = None,
173 + head_size_v: int | None = None,
174 **extra_impl_args,
175 ) -> None:
176 """
@@ -217,6 +218,7 @@ class Attention(nn.Module, AttentionLayerBase):
218
219 self.num_heads = num_heads
220 self.head_size = head_size
221 + self.head_size_v = self.head_size if head_size_v is None else head_size_v
222 self.num_kv_heads = num_kv_heads
223 self.sliding_window = sliding_window
224 self.has_sink = extra_impl_args.get("sinks") is not None
@@ -274,8 +276,7 @@ class Attention(nn.Module, AttentionLayerBase):
276 kv_sharing_target_layer_name,
277 **extra_impl_args,
278 )
277 - backend_name = self.attn_backend.get_name()
278 - self.backend = AttentionBackendEnum.__members__.get(backend_name)
279 + self.backend = AttentionBackendEnum[self.attn_backend.get_name()]
280 self.dtype = dtype
281
282 # For cuda-alike (CUDA and ROCM) and cpu platforms, we control how
@@ -355,6 +356,10 @@ class Attention(nn.Module, AttentionLayerBase):
356 query, _ = self.query_quant(query, self._q_scale)
357
358 if self.use_output:
359 + if output_shape is None:
360 + output_shape = torch.Size(
361 + (*query.shape[:-1], self.num_heads * self.head_size_v)
362 + )
363 output_shape = output_shape if output_shape is not None else query.shape
364 output = torch.empty(output_shape, dtype=output_dtype, device=query.device)
365 hidden_size = output_shape[-1]
@@ -362,11 +367,11 @@ class Attention(nn.Module, AttentionLayerBase):
367 # NOTE(woosuk): We do this outside the custom op to minimize the
368 # CPU overheads from the non-CUDA-graph regions.
369 query = query.view(-1, self.num_heads, self.head_size)
365 - output = output.view(-1, self.num_heads, self.head_size)
370 + output = output.view(-1, self.num_heads, self.head_size_v)
371 if key is not None:
372 key = key.view(-1, self.num_kv_heads, self.head_size)
373 if value is not None:
369 - value = value.view(-1, self.num_kv_heads, self.head_size)
374 + value = value.view(-1, self.num_kv_heads, self.head_size_v)
375 if self.use_direct_call:
376 forward_context: ForwardContext = get_forward_context()
377 attn_metadata = forward_context.attn_metadata
@@ -452,6 +457,7 @@ class Attention(nn.Module, AttentionLayerBase):
457 block_size=block_size,
458 num_kv_heads=self.num_kv_heads,
459 head_size=self.head_size,
460 + head_size_v=self.head_size_v,
461 dtype=self.kv_cache_torch_dtype,
462 )
463
@@ -794,6 +800,7 @@ def unified_attention_with_output(
800 output_block_scale: torch.Tensor | None = None,
801 ) -> None:
802 attn_metadata, self, kv_cache = get_attention_context(layer_name)
803 +
804 self.impl.forward(
805 self,
806 query,
vllm/attention/layers/static_sink_attention.py new
+254
@@ -0,0 +1,254 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +import functools
4 +
5 +import torch
6 +
7 +from vllm.attention.backends.abstract import (
8 + AttentionBackend,
9 + AttentionMetadata,
10 + AttentionType,
11 +)
12 +from vllm.attention.layer import Attention
13 +from vllm.attention.ops.triton_reshape_and_cache_flash import (
14 + triton_reshape_and_cache_flash_diffkv,
15 +)
16 +from vllm.attention.selector import get_attn_backend
17 +from vllm.config import CacheConfig, VllmConfig
18 +from vllm.forward_context import ForwardContext, get_forward_context
19 +from vllm.logger import init_logger
20 +from vllm.model_executor.custom_op import CustomOp
21 +from vllm.utils.math_utils import cdiv
22 +from vllm.utils.torch_utils import direct_register_custom_op
23 +from vllm.v1.attention.backends.utils import (
24 + CommonAttentionMetadata,
25 + subclass_attention_backend,
26 +)
27 +from vllm.v1.kv_cache_interface import (
28 + AttentionSpec,
29 + KVCacheSpec,
30 + SinkFullAttentionSpec,
31 +)
32 +
33 +logger = init_logger(__name__)
34 +
35 +
36 +@functools.lru_cache
37 +def create_static_sink_attention_backend(
38 + underlying_attn_backend: type[AttentionBackend],
39 + sink_len: int = 0,
40 +) -> type[AttentionBackend]:
41 + prefix = "StaticSink_"
42 + underlying_builder = underlying_attn_backend.get_builder_cls()
43 +
44 + class StaticSinkAttentionBuilder(underlying_builder): # type: ignore
45 + def __init__(
46 + self,
47 + kv_cache_spec: AttentionSpec,
48 + layer_names: list[str],
49 + vllm_config: VllmConfig,
50 + device: torch.device,
51 + ):
52 + super().__init__(kv_cache_spec, layer_names, vllm_config, device)
53 + model_config = vllm_config.model_config
54 + scheduler_config = vllm_config.scheduler_config
55 + self.sink_len = sink_len
56 + self.block_size = vllm_config.cache_config.block_size
57 + self.num_sink_blocks = self.sink_len // vllm_config.cache_config.block_size
58 + self.max_num_blocks = cdiv(
59 + model_config.max_model_len, vllm_config.cache_config.block_size
60 + )
61 + self.block_table_with_sink = torch.zeros(
62 + (
63 + scheduler_config.max_num_seqs,
64 + self.max_num_blocks + self.num_sink_blocks,
65 + ),
66 + device=device,
67 + dtype=torch.int32,
68 + )
69 + self.block_table_with_sink[:, : self.num_sink_blocks] = torch.arange(
70 + 1,
71 + self.num_sink_blocks + 1,
72 + device=device,
73 + dtype=torch.int32,
74 + )
75 +
76 + def build(
77 + self,
78 + common_prefix_len: int,
79 + common_attn_metadata: CommonAttentionMetadata,
80 + fast_build: bool = False,
81 + ) -> AttentionMetadata:
82 + common_attn_metadata.seq_lens[:] = (
83 + common_attn_metadata.seq_lens + self.sink_len
84 + )
85 + common_attn_metadata.seq_lens[
86 + common_attn_metadata.seq_lens == self.sink_len
87 + ] = 0
88 + common_attn_metadata.max_seq_len = (
89 + common_attn_metadata.max_seq_len + self.sink_len
90 + )
91 + max_num_blocks = cdiv(common_attn_metadata.max_seq_len, self.block_size)
92 + num_reqs = common_attn_metadata.num_reqs
93 + self.block_table_with_sink[
94 + :num_reqs, self.num_sink_blocks : self.num_sink_blocks + max_num_blocks
95 + ] = common_attn_metadata.block_table_tensor[:, :max_num_blocks]
96 + common_attn_metadata.block_table_tensor = self.block_table_with_sink[
97 + :num_reqs
98 + ]
99 +
100 + return super().build(common_prefix_len, common_attn_metadata, fast_build)
101 +
102 + attn_backend = subclass_attention_backend(
103 + name_prefix=prefix,
104 + attention_backend_cls=underlying_attn_backend,
105 + builder_cls=StaticSinkAttentionBuilder,
106 + )
107 +
108 + return attn_backend
109 +
110 +
111 +@CustomOp.register("static_sink_attention")
112 +class StaticSinkAttention(Attention, CustomOp):
113 + """
114 + Attention with static sink tokens
115 + """
116 +
117 + def __init__(
118 + self,
119 + num_heads: int,
120 + head_size: int,
121 + scale: float,
122 + sink_len: int,
123 + attn_backend: type[AttentionBackend] | None = None,
124 + cache_config: CacheConfig | None = None,
125 + **kwargs,
126 + ):
127 + dtype = torch.get_default_dtype()
128 +
129 + if cache_config is not None:
130 + kv_cache_dtype = cache_config.cache_dtype
131 + block_size = cache_config.block_size
132 + else:
133 + kv_cache_dtype = "auto"
134 + block_size = 16
135 +
136 + if attn_backend is not None:
137 + underlying_attn_backend = attn_backend
138 + else:
139 + underlying_attn_backend = get_attn_backend(
140 + head_size, dtype, kv_cache_dtype, block_size
141 + )
142 + attn_backend = create_static_sink_attention_backend(
143 + underlying_attn_backend,
144 + sink_len=sink_len,
145 + )
146 + Attention.__init__(
147 + self=self,
148 + num_heads=num_heads,
149 + head_size=head_size,
150 + scale=scale,
151 + cache_config=cache_config,
152 + attn_backend=attn_backend,
153 + **kwargs,
154 + )
155 + CustomOp.__init__(self)
156 +
157 + self.sink_len = sink_len
158 + self.block_size = block_size
159 + self.sink_populated = False
160 + self.sink_key = None
161 + self.sink_value = None
162 +
163 + def update_sink_kv(self, sink_key, sink_value) -> None:
164 + self.sink_key = sink_key
165 + self.sink_value = sink_value
166 +
167 + def forward_native(
168 + self,
169 + query: torch.Tensor,
170 + key: torch.Tensor,
171 + value: torch.Tensor,
172 + output_shape: torch.Size | None = None,
173 + ) -> torch.Tensor:
174 + assert self.sink_key is not None and self.sink_value is not None, (
175 + "sink_key and sink_value have not been prepared"
176 + )
177 + if not self.sink_populated:
178 + forward_context: ForwardContext = get_forward_context()
179 + self_kv_cache = self.kv_cache[forward_context.virtual_engine]
180 + torch.ops.vllm.maybe_populate_sink(self_kv_cache, self.layer_name)
181 +
182 + return super().forward(query, key, value, output_shape)
183 +
184 + def forward_cuda(
185 + self,
186 + query: torch.Tensor,
187 + key: torch.Tensor,
188 + value: torch.Tensor,
189 + output_shape: torch.Size | None = None,
190 + ) -> torch.Tensor:
191 + return self.forward_native(query, key, value, output_shape)
192 +
193 + def forward(self, *args, **kwargs):
194 + return self._forward_method(*args, **kwargs)
195 +
196 + def populate_sink_kv(self, self_kv_cache):
197 + sink_kv_slot_mapping = torch.arange(
198 + self.block_size,
199 + self.sink_len + self.block_size,
200 + device=torch.cuda.current_device(),
201 + dtype=torch.long,
202 + )
203 + triton_reshape_and_cache_flash_diffkv(
204 + self.sink_key,
205 + self.sink_value,
206 + self_kv_cache,
207 + sink_kv_slot_mapping,
208 + self.kv_cache_dtype,
209 + self._k_scale,
210 + self._v_scale,
211 + )
212 + # We only populate the sink_key and sink_value once
213 + self.sink_populated = True
214 +
215 + def get_kv_cache_spec(self, vllm_config: VllmConfig) -> KVCacheSpec:
216 + # Block size may get updated after model loading, refresh it
217 + block_size = vllm_config.cache_config.block_size
218 + # Should not be called for enc-dec or encoder-only attention.
219 + assert self.attn_type == AttentionType.DECODER
220 +
221 + return SinkFullAttentionSpec(
222 + block_size=block_size,
223 + num_kv_heads=self.num_kv_heads,
224 + head_size=self.head_size,
225 + head_size_v=self.head_size_v,
226 + sink_len=self.sink_len,
227 + dtype=self.kv_cache_torch_dtype,
228 + )
229 +
230 +
231 +def maybe_populate_sink(
232 + self_kv_cache: torch.Tensor,
233 + layer_name: str,
234 +) -> None:
235 + forward_context: ForwardContext = get_forward_context()
236 + self = forward_context.no_compile_layers[layer_name]
237 + if self.sink_populated or self_kv_cache.numel() == 0:
238 + return
239 + self.populate_sink_kv(self_kv_cache)
240 +
241 +
242 +def maybe_populate_sink_fake(
243 + self_kv_cache: torch.Tensor,
244 + layer_name: str,
245 +) -> None:
246 + return
247 +
248 +
249 +direct_register_custom_op(
250 + op_name="maybe_populate_sink",
251 + op_func=maybe_populate_sink,
252 + mutates_args=["self_kv_cache"],
253 + fake_impl=maybe_populate_sink_fake,
254 +)
vllm/attention/ops/triton_reshape_and_cache_flash.py
+171
@@ -182,3 +182,174 @@ def triton_reshape_and_cache_flash(
182 num_warps=num_warps,
183 num_stages=num_stages,
184 )
185 +
186 +
187 +@triton.jit
188 +def reshape_and_cache_kernel_flash_diffkv(
189 + key_ptr, # [num_tokens, num_heads, head_size]
190 + value_ptr, # [num_tokens, num_heads, head_size_v]
191 + kv_cache_ptr, # [num_blocks, block_size, num_heads, head_size + head_size_v]
192 + slot_mapping_ptr, # [num_tokens]
193 + k_scale, # float32
194 + v_scale, # float32
195 + # strides
196 + key_stride: tl.int64,
197 + value_stride: tl.int64,
198 + block_stride: tl.int64,
199 + page_stride: tl.int64,
200 + num_heads: tl.constexpr,
201 + head_size_k: tl.constexpr,
202 + head_size_v: tl.constexpr,
203 + block_size: tl.constexpr,
204 + # FP8 flags
205 + FP8_KV_CACHE: tl.constexpr,
206 + # tune parameters
207 + TILE_SIZE: tl.constexpr,
208 +):
209 + token_idx = tl.program_id(axis=0)
210 + slot_idx = tl.load(slot_mapping_ptr + token_idx).to(tl.int64)
211 + if slot_idx < 0:
212 + # Padding token that should be ignored.
213 + return
214 +
215 + tile_i = tl.program_id(axis=1)
216 + tile_offs = tl.arange(0, TILE_SIZE)
217 +
218 + block_idx = slot_idx // block_size
219 + block_offset = slot_idx % block_size
220 +
221 + src_key_idx = token_idx * key_stride + tile_i * head_size_k
222 + src_value_idx = token_idx * value_stride + tile_i * head_size_v
223 +
224 + tgt_idx = (
225 + block_idx * block_stride
226 + + block_offset * page_stride
227 + + tile_i * (head_size_k + head_size_v)
228 + )
229 +
230 + # [TILE_SIZE]
231 + key_load = tl.load(key_ptr + src_key_idx + tile_offs, mask=tile_offs < head_size_k)
232 + if FP8_KV_CACHE:
233 + # tl.store will do the correct implicit cast to fp8,
234 + # based on the key_cache_ptr.dtype.element_ty
235 + key_tile = key_load if key_load.dtype.is_fp8() else key_load / tl.load(k_scale)
236 + else:
237 + key_tile = key_load
238 +
239 + # [TILE_SIZE]
240 + value_load = tl.load(
241 + value_ptr + src_value_idx + tile_offs, mask=tile_offs < head_size_v
242 + )
243 + if FP8_KV_CACHE:
244 + if value_load.dtype.is_fp8():
245 + value_tile = value_load
246 + else:
247 + # tl.store will do the correct implicit cast to fp8,
248 + # based on the value_cache_ptr.dtype.element_ty
249 + value_tile = value_load / tl.load(v_scale)
250 + else:
251 + value_tile = value_load
252 +
253 + tl.store(
254 + kv_cache_ptr + tgt_idx + tile_offs,
255 + key_tile,
256 + mask=tile_offs < head_size_k,
257 + )
258 + tl.store(
259 + kv_cache_ptr + tgt_idx + head_size_k + tile_offs,
260 + value_tile,
261 + mask=tile_offs < head_size_v,
262 + )
263 + return
264 +
265 +
266 +def triton_reshape_and_cache_flash_diffkv(
267 + key: torch.Tensor, # [num_tokens, num_heads, head_size]
268 + value: torch.Tensor, # [num_tokens, num_heads, head_size_v]
269 + # [num_blocks, block_size, num_heads, head_size + head_size_v]
270 + kv_cache: torch.Tensor,
271 + slot_mapping: torch.Tensor, # [num_tokens]
272 + kv_cache_dtype: str, # "auto", "fp8"
273 + k_scale: torch.Tensor, # float32
274 + v_scale: torch.Tensor, # float32
275 +):
276 + num_heads = key.shape[1]
277 + head_size_k = key.shape[2]
278 + head_size_v = value.shape[2]
279 + block_size = kv_cache.shape[1]
280 +
281 + k_stride = key.stride()[0]
282 + v_stride = value.stride()[0]
283 + block_stride = kv_cache.stride()[0]
284 + page_stride = kv_cache.stride()[1]
285 +
286 + assert kv_cache_dtype == "auto" or kv_cache_dtype.startswith("fp8"), (
287 + f"unsupported kv_cache_dtype (str), got {kv_cache_dtype}."
288 + )
289 + kv_cache_torch_dtype = (
290 + current_platform.fp8_dtype()
291 + if kv_cache_dtype.startswith("fp8")
292 + else kv_cache.dtype
293 + )
294 +
295 + if kv_cache.dtype != kv_cache_torch_dtype and kv_cache_dtype.startswith("fp8"):
296 + # to avoid erounous implicit cast in triton kernel (tl.store to uint8)
297 + # (e.g. explicit cast to fp8e4m3fnuz is not supported in triton 3.4)
298 + kv_cache = kv_cache.view(kv_cache_torch_dtype)
299 + assert kv_cache_dtype != torch.uint8, (
300 + "explicit fp8 cast and store to "
301 + "uint8 is not supported by triton reshape_and_cache_flash_diffkv"
302 + )
303 +
304 + FP8_KV_CACHE = kv_cache_dtype.startswith("fp8")
305 + assert (not FP8_KV_CACHE) or kv_cache_torch_dtype in [
306 + torch.float8_e4m3fn,
307 + torch.float8_e5m2,
308 + torch.uint8,
309 + torch.float8_e4m3fnuz,
310 + ], (
311 + "unsupported dtype of KV cache tensor, got "
312 + "{kv_cache_torch_dtype}. Supported kv cache dtypes: fp8e4m3fn, "
313 + "fp8e5m2, uint8, bfloat16, float16, float32, fp8e4m3fnuz."
314 + )
315 +
316 + # heuristics instead of autotuning
317 + TILE_SIZE = max(head_size_k, head_size_v)
318 + TILE_SIZE = triton.next_power_of_2(TILE_SIZE)
319 + if current_platform.is_rocm() or current_platform.is_xpu():
320 + num_stages = 4
321 + num_warps = 8
322 + else: # cuda
323 + num_stages = 10
324 + num_warps = 16
325 +
326 + # TODO(ngl): maybe replace with static launch grid to avoid overhead if
327 + # using cudagraphs
328 + grid = lambda meta: (
329 + slot_mapping.shape[0],
330 + num_heads,
331 + )
332 +
333 + reshape_and_cache_kernel_flash_diffkv[grid](
334 + key_ptr=key,
335 + value_ptr=value,
336 + kv_cache_ptr=kv_cache,
337 + slot_mapping_ptr=slot_mapping,
338 + k_scale=k_scale,
339 + v_scale=v_scale,
340 + # strides
341 + key_stride=k_stride,
342 + value_stride=v_stride,
343 + block_stride=block_stride,
344 + page_stride=page_stride,
345 + num_heads=num_heads,
346 + head_size_k=head_size_k,
347 + head_size_v=head_size_v,
348 + block_size=block_size,
349 + # FP8 flags
350 + FP8_KV_CACHE=FP8_KV_CACHE,
351 + # autotune parameters
352 + TILE_SIZE=TILE_SIZE,
353 + num_warps=num_warps,
354 + num_stages=num_stages,
355 + )
vllm/model_executor/models/openpangu.py
+329 -3
@@ -29,13 +29,14 @@ import torch
29 from torch import nn
30 from transformers import PretrainedConfig
31
32 -from vllm.attention.backends.abstract import AttentionType
33 -from vllm.attention.layer import Attention
32 +from vllm.attention.layer import Attention, AttentionType
33 +from vllm.attention.layers.static_sink_attention import StaticSinkAttention
34 from vllm.compilation.decorators import support_torch_compile
35 from vllm.config import CacheConfig, ParallelConfig, VllmConfig
36 from vllm.distributed import (
37 get_ep_group,
38 get_pp_group,
39 + get_tensor_model_parallel_rank,
40 get_tensor_model_parallel_world_size,
41 get_tp_group,
42 tensor_model_parallel_all_gather,
@@ -77,8 +78,11 @@ from vllm.model_executor.models.utils import (
78 maybe_prefix,
79 sequence_parallel_chunk,
80 )
81 +from vllm.model_executor.utils import set_weight_attrs
82 +from vllm.platforms import current_platform
83 from vllm.sequence import IntermediateTensors
84 from vllm.transformers_utils.config import set_default_rope_theta
85 +from vllm.v1.attention.backends.flash_attn_diffkv import FlashAttentionDiffKVBackend
86
87
88 def check_ffn_act_fn(act_fn: str):
@@ -155,7 +159,15 @@ class OpenPanguMoE(nn.Module):
159 quant_config=None,
160 prefix=f"{prefix}.gate",
161 )
158 - self.gate.e_score_correction_bias = None
162 + if (
163 + hasattr(config, "router_enable_expert_bias")
164 + and config.router_enable_expert_bias
165 + ):
166 + self.gate.e_score_correction_bias = nn.Parameter(
167 + torch.empty(self.n_routed_experts, dtype=torch.float32)
168 + )
169 + else:
170 + self.gate.e_score_correction_bias = None
171
172 # Load balancing settings.
173 eplb_config = parallel_config.eplb_config
@@ -530,6 +542,264 @@ class OpenPanguEmbeddedAttention(nn.Module):
542 )
543
544
545 +class OpenPanguSinkAttention(nn.Module):
546 + def __init__(
547 + self,
548 + config: PretrainedConfig,
549 + hidden_size: int,
550 + num_heads: int,
551 + num_kv_heads: int,
552 + rope_parameters: dict[str, Any] | None = None,
553 + max_position_embeddings: int = 8192,
554 + quant_config: QuantizationConfig | None = None,
555 + bias: bool = False,
556 + bias_o_proj: bool = False,
557 + cache_config: CacheConfig | None = None,
558 + prefix: str = "",
559 + attn_type: str = AttentionType.DECODER,
560 + ) -> None:
561 + super().__init__()
562 + layer_idx = extract_layer_index(prefix)
563 + self.hidden_size = hidden_size
564 + self.tp_size = get_tensor_model_parallel_world_size()
565 + self.tp_rank = get_tensor_model_parallel_rank()
566 + self.total_num_heads = num_heads
567 + if self.total_num_heads % self.tp_size != 0:
568 + raise ValueError(
569 + f"total_num_heads {self.total_num_heads} "
570 + f"is not divisible by tp_size {self.tp_size}."
571 + )
572 + self.num_heads = self.total_num_heads // self.tp_size
573 + self.total_num_kv_heads = num_kv_heads
574 + if (
575 + self.total_num_kv_heads > self.tp_size
576 + and self.total_num_kv_heads % self.tp_size != 0
577 + ):
578 + # Number of KV heads is greater than TP size, so we partition
579 + # the KV heads across multiple tensor parallel ranks.
580 + raise ValueError(
581 + "Number of KV heads is greater than TP size, "
582 + f"but total_num_kv_heads {self.total_num_kv_heads} "
583 + f"is not divisible by tp_size {self.tp_size}."
584 + )
585 + elif self.total_num_kv_heads < self.tp_size:
586 + # TODO: Number of KV heads is less than TP size, so we replicate
587 + # the KV heads across multiple tensor parallel ranks.
588 + raise ValueError(
589 + f"Number of KV heads {self.total_num_kv_heads} is less than "
590 + f"TP size {self.tp_size}, KV heads replication is not support yet."
591 + )
592 + self.num_kv_heads = max(1, self.total_num_kv_heads // self.tp_size)
593 + self.qk_nope_dim = getattr(config, "qk_nope_dim", None)
594 + self.qk_rope_dim = getattr(config, "qk_rope_dim", None)
595 + self.v_channels = getattr(config, "v_channels", None)
596 + self.head_dim = self.qk_rope_dim + self.qk_nope_dim
597 + self.q_size = self.num_heads * self.head_dim
598 + self.k_size = self.num_kv_heads * self.head_dim
599 + self.v_size = self.num_kv_heads * self.v_channels
600 + self.scaling = self.head_dim**-0.5
601 + self.max_position_embeddings = max_position_embeddings
602 +
603 + self.param_sink_number = getattr(config, "param_sink_number", 0)
604 + self.param_sink_with_value = getattr(config, "param_sink_with_value", False)
605 + self.param_sink_scalar = getattr(config, "param_sink_scalar", None)
606 + self.param_sink_of_head_num = getattr(config, "param_sink_of_head_dim", False)
607 +
608 + self.qkv_proj = MergedColumnParallelLinear(
609 + input_size=hidden_size,
610 + output_sizes=[
611 + self.q_size * self.tp_size,
612 + self.k_size * self.tp_size,
613 + self.v_size * self.tp_size,
614 + ],
615 + bias=bias,
616 + quant_config=quant_config,
617 + prefix=f"{prefix}.qkv_proj",
618 + )
619 +
620 + self.o_proj = RowParallelLinear(
621 + input_size=self.total_num_heads * self.v_channels,
622 + output_size=hidden_size,
623 + bias=bias_o_proj,
624 + quant_config=quant_config,
625 + prefix=f"{prefix}.o_proj",
626 + )
627 +
628 + self.k_layernorm = RMSNorm(self.head_dim, eps=config.rms_norm_eps)
629 +
630 + self._init_rotary_emb(
631 + config, rope_parameters=rope_parameters, quant_config=quant_config
632 + )
633 +
634 + if hasattr(config, "interleaved_sliding_window"):
635 + interleaved_sliding_window = config.interleaved_sliding_window
636 + if isinstance(interleaved_sliding_window, int):
637 + sliding_window = interleaved_sliding_window
638 + elif isinstance(interleaved_sliding_window, list):
639 + sw_idx = layer_idx % len(interleaved_sliding_window)
640 + sliding_window = interleaved_sliding_window[sw_idx]
641 + else:
642 + raise ValueError(
643 + f"{type(interleaved_sliding_window)} "
644 + "for interleaved_sliding_window is not supported."
645 + )
646 + else:
647 + sliding_window = None
648 +
649 + FlashAttentionDiffKVBackend.set_head_size_v(self.v_channels)
650 + self.attn = StaticSinkAttention(
651 + self.num_heads,
652 + self.head_dim,
653 + self.scaling,
654 + sink_len=self.param_sink_number,
655 + num_kv_heads=self.num_kv_heads,
656 + cache_config=cache_config,
657 + quant_config=quant_config,
658 + per_layer_sliding_window=sliding_window,
659 + attn_type=attn_type,
660 + prefix=f"{prefix}.attn",
661 + attn_backend=FlashAttentionDiffKVBackend,
662 + head_size_v=self.v_channels,
663 + )
664 +
665 + if self.param_sink_number > 0:
666 + self.param_sink_key = torch.nn.Parameter(
667 + torch.empty(
668 + (
669 + self.param_sink_number,
670 + self.num_kv_heads,
671 + self.head_dim,
672 + ),
673 + device=current_platform.current_device(),
674 + dtype=config.torch_dtype,
675 + )
676 + )
677 + set_weight_attrs(
678 + self.param_sink_key,
679 + {
680 + "output_dim": 1,
681 + "weight_loader": self.weight_loader,
682 + },
683 + )
684 +
685 + if self.param_sink_with_value:
686 + self.param_sink_value = torch.nn.Parameter(
687 + torch.empty(
688 + (
689 + self.param_sink_number,
690 + self.num_kv_heads,
691 + self.v_channels,
692 + ),
693 + device=current_platform.current_device(),
694 + dtype=config.torch_dtype,
695 + )
696 + )
697 + set_weight_attrs(
698 + self.param_sink_value,
699 + {
700 + "output_dim": 1,
701 + "weight_loader": self.weight_loader,
702 + },
703 + )
704 + else:
705 + self.param_sink_value = torch.zeros(
706 + (
707 + self.param_sink_number,
708 + self.num_kv_heads,
709 + self.v_channels,
710 + ),
711 + device=current_platform.current_device(),
712 + dtype=config.torch_dtype,
713 + )
714 + # To enable dummy run with out weight
715 + self.post_weight_load()
716 +
717 + def weight_loader(self, param: nn.Parameter, loaded_weight: torch.Tensor):
718 + output_dim = getattr(param, "output_dim", None)
719 +
720 + is_sharded_weight = getattr(param, "is_sharded_weight", False)
721 + use_bitsandbytes_4bit = getattr(param, "use_bitsandbytes_4bit", False)
722 + # bitsandbytes loads the weights of the specific portion
723 + # no need to narrow
724 + is_sharded_weight = is_sharded_weight or use_bitsandbytes_4bit
725 +
726 + # Special case for GGUF
727 + is_gguf_weight = getattr(param, "is_gguf_weight", False)
728 + is_gguf_weight_type = getattr(param, "is_gguf_weight_type", False)
729 + if is_gguf_weight_type:
730 + param.weight_type = loaded_weight.item()
731 +
732 + # Materialize GGUF UninitializedParameter
733 + if is_gguf_weight and isinstance(param, nn.UninitializedParameter):
734 + final_shape = list(loaded_weight.shape)
735 + if output_dim is not None:
736 + assert final_shape[output_dim] % self.tp_size == 0
737 + final_shape[output_dim] = final_shape[output_dim] // self.tp_size
738 + param.materialize(final_shape, dtype=loaded_weight.dtype)
739 +
740 + param_data = param.data
741 + if output_dim is not None and not is_sharded_weight:
742 + shard_size = param_data.shape[output_dim]
743 + start_idx = self.tp_rank * shard_size
744 + loaded_weight = loaded_weight.narrow(output_dim, start_idx, shard_size)
745 +
746 + # Special case for loading scales off disk, which often do not
747 + # have a shape (such as in the case of AutoFP8).
748 + if len(loaded_weight.shape) == 0:
749 + loaded_weight = loaded_weight.reshape(1)
750 +
751 + assert param_data.shape == loaded_weight.shape
752 + param_data.copy_(loaded_weight)
753 +
754 + def forward(
755 + self,
756 + positions: torch.Tensor,
757 + hidden_states: torch.Tensor,
758 + ) -> torch.Tensor:
759 + qkv, _ = self.qkv_proj(hidden_states)
760 + q, k, v = qkv.split([self.q_size, self.k_size, self.v_size], dim=-1)
761 + k = self.k_layernorm(k.view(-1, self.num_kv_heads, self.head_dim))
762 + q, k = self.rotary_emb(positions, q, k)
763 +
764 + q = q.view(-1, self.q_size)
765 + k = k.view(-1, self.k_size)
766 +
767 + attn_output = self.attn(
768 + q,
769 + k,
770 + v,
771 + output_shape=torch.Size(
772 + [q.shape[0], q.shape[1] // self.head_dim * self.v_channels]
773 + ),
774 + )
775 + output, _ = self.o_proj(attn_output)
776 + return output
777 +
778 + def _init_rotary_emb(
779 + self,
780 + config: PretrainedConfig,
781 + rope_parameters: dict[str, Any] | None,
782 + quant_config: QuantizationConfig | None,
783 + ) -> None:
784 + is_neox_style = False
785 + rope_parameters = {"partial_rotary_factor": self.qk_rope_dim / self.head_dim}
786 +
787 + self.rotary_emb = get_rope(
788 + self.head_dim,
789 + max_position=self.max_position_embeddings,
790 + rope_parameters=rope_parameters,
791 + is_neox_style=is_neox_style,
792 + )
793 +
794 + def post_weight_load(self) -> None:
795 + if hasattr(self, "k_layernorm") and self.k_layernorm is not None:
796 + param_sink_key = self.k_layernorm(self.param_sink_key)
797 + else:
798 + param_sink_key = self.param_sink_key
799 +
800 + self.attn.update_sink_kv(param_sink_key, self.param_sink_value)
801 +
802 +
803 class OpenPanguDecoderLayer(nn.Module):
804 def __init__(
805 self,
@@ -557,6 +827,9 @@ class OpenPanguDecoderLayer(nn.Module):
827 and hasattr(config, "v_head_dim")
828 and hasattr(config, "kv_lora_rank")
829 )
830 + self.use_sink_attention = (
831 + hasattr(config, "param_sink_number") and config.param_sink_number > 0
832 + )
833 if self.use_mla:
834 self.self_attn = OpenPanguMLAAttention(
835 config=config,
@@ -574,6 +847,42 @@ class OpenPanguDecoderLayer(nn.Module):
847 quant_config=quant_config,
848 prefix=f"{prefix}.self_attn",
849 )
850 + elif self.use_sink_attention:
851 + attention_bias = getattr(config, "attention_bias", False) or getattr(
852 + config, "bias", False
853 + )
854 + bias_o_proj = attention_bias
855 + if hasattr(config, "qkv_bias"):
856 + attention_bias = config.qkv_bias
857 + if getattr(config, "is_causal", True):
858 + attn_type = AttentionType.DECODER
859 + else:
860 + raise ValueError(
861 + f"is_causal={config.is_causal} is not support "
862 + "for attention with sink"
863 + )
864 + rope_parameters = getattr(config, "rope_scaling", None)
865 + if rope_parameters is None:
866 + rope_parameters = {
867 + "rope_type": "default",
868 + "rope_theta": config.rope_theta,
869 + }
870 + self.self_attn = OpenPanguSinkAttention(
871 + config=config,
872 + hidden_size=self.hidden_size,
873 + num_heads=config.num_attention_heads,
874 + num_kv_heads=getattr(
875 + config, "num_key_value_heads", config.num_attention_heads
876 + ),
877 + rope_parameters=rope_parameters,
878 + max_position_embeddings=max_position_embeddings,
879 + quant_config=quant_config,
880 + bias=attention_bias,
881 + bias_o_proj=bias_o_proj,
882 + cache_config=cache_config,
883 + prefix=f"{prefix}.self_attn",
884 + attn_type=attn_type,
885 + )
886 else:
887 attention_bias = getattr(config, "attention_bias", False) or getattr(
888 config, "bias", False
@@ -903,6 +1212,10 @@ class OpenPanguModel(nn.Module):
1212 if name.endswith(".bias") and name not in params_dict:
1213 continue
1214 name = maybe_remap_kv_scale_name(name, params_dict)
1215 + if name.endswith("e_score_correction_bias"):
1216 + name = name.replace(
1217 + "e_score_correction_bias", "gate.e_score_correction_bias"
1218 + )
1219 if name is None:
1220 continue
1221 if is_pp_missing_parameter(name, self):
@@ -912,8 +1225,17 @@ class OpenPanguModel(nn.Module):
1225 weight_loader = getattr(param, "weight_loader", default_weight_loader)
1226 weight_loader(param, loaded_weight)
1227 loaded_params.add(name)
1228 +
1229 + self.post_weight_load()
1230 return loaded_params
1231
1232 + def post_weight_load(self) -> None:
1233 + for name, module in self.named_modules():
1234 + if module is self:
1235 + continue
1236 + if hasattr(module, "post_weight_load"):
1237 + module.post_weight_load()
1238 +
1239
1240 class OpenPanguModelBase(nn.Module, SupportsPP, SupportsLoRA):
1241 packed_modules_mapping = {
@@ -1047,3 +1369,7 @@ class PanguEmbeddedForCausalLM(OpenPanguEmbeddedModel):
1369
1370 class PanguUltraMoEForCausalLM(OpenPanguMoEModel):
1371 pass
1372 +
1373 +
1374 +class PanguProMoEV2ForCausalLM(OpenPanguMoEModel):
1375 + pass
vllm/model_executor/models/registry.py
+1
@@ -164,6 +164,7 @@ _TEXT_GENERATION_MODELS = {
164 "OrionForCausalLM": ("orion", "OrionForCausalLM"),
165 "OuroForCausalLM": ("ouro", "OuroForCausalLM"),
166 "PanguEmbeddedForCausalLM": ("openpangu", "PanguEmbeddedForCausalLM"),
167 + "PanguProMoEV2ForCausalLM": ("openpangu", "PanguProMoEV2ForCausalLM"),
168 "PanguUltraMoEForCausalLM": ("openpangu", "PanguUltraMoEForCausalLM"),
169 "PersimmonForCausalLM": ("persimmon", "PersimmonForCausalLM"),
170 "PhiForCausalLM": ("phi", "PhiForCausalLM"),
vllm/v1/attention/backends/flash_attn_diffkv.py new
+269
@@ -0,0 +1,269 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +"""Attention layer with FlashAttention."""
4 +
5 +import torch
6 +
7 +from vllm.attention.backends.abstract import AttentionType
8 +from vllm.attention.ops.triton_reshape_and_cache_flash import (
9 + triton_reshape_and_cache_flash_diffkv,
10 +)
11 +from vllm.attention.utils.fa_utils import is_flash_attn_varlen_func_available
12 +
13 +if is_flash_attn_varlen_func_available():
14 + from vllm.attention.utils.fa_utils import flash_attn_varlen_func
15 +from vllm.logger import init_logger
16 +from vllm.v1.attention.backends.utils import get_kv_cache_layout
17 +
18 +from .flash_attn import (
19 + FlashAttentionBackend,
20 + FlashAttentionImpl,
21 + FlashAttentionMetadata,
22 + cascade_attention,
23 +)
24 +
25 +logger = init_logger(__name__)
26 +
27 +
28 +class FlashAttentionDiffKVBackend(FlashAttentionBackend):
29 + # Default to 128 for this backend
30 + head_size_v: int = 128
31 +
32 + @classmethod
33 + def set_head_size_v(cls, head_size_v: int) -> None:
34 + cls.head_size_v = head_size_v
35 +
36 + @staticmethod
37 + def get_name() -> str:
38 + return "FLASH_ATTN_DIFFKV"
39 +
40 + @staticmethod
41 + def get_impl_cls() -> type["FlashAttentionImpl"]:
42 + return FlashAttentionDiffKVImpl
43 +
44 + # Do not modify the interface of get_kv_cache_shape,
45 + # but consider head_size_v when returning result.
46 + @staticmethod
47 + def get_kv_cache_shape(
48 + num_blocks: int,
49 + block_size: int,
50 + num_kv_heads: int,
51 + head_size: int,
52 + cache_dtype_str: str = "auto",
53 + ) -> tuple[int, ...]:
54 + if block_size % 16 != 0:
55 + raise ValueError("Block size must be a multiple of 16.")
56 + return (
57 + num_blocks,
58 + block_size,
59 + num_kv_heads,
60 + head_size + FlashAttentionDiffKVBackend.head_size_v,
61 + )
62 +
63 + @staticmethod
64 + def get_kv_cache_stride_order(
65 + include_num_layers_dimension: bool = False,
66 + ) -> tuple[int, ...]:
67 + # `stride_order` indicates the permutation that gets
68 + # us from `get_kv_cache_shape` to the actual memory layout we want.
69 + cache_layout = get_kv_cache_layout()
70 + if cache_layout == "NHD" and include_num_layers_dimension:
71 + # (num_blocks, num_layers, block_size,
72 + # num_kv_heads, head_size + head_size_v)
73 + return (1, 0, 2, 3, 4)
74 + elif cache_layout == "NHD":
75 + stride_order = (0, 1, 2, 3)
76 + elif cache_layout == "HND" and include_num_layers_dimension:
77 + # (num_blocks, num_kv_heads, num_layers,
78 + # block_size, head_size + head_size_v)
79 + return (1, 3, 0, 2, 4)
80 + elif cache_layout == "HND":
81 + stride_order = (0, 2, 1, 3)
82 + else:
83 + raise ValueError(f"Unknown cache layout format {cache_layout}.")
84 + return stride_order
85 +
86 +
87 +class FlashAttentionDiffKVImpl(FlashAttentionImpl):
88 + def forward(
89 + self,
90 + layer: torch.nn.Module,
91 + query: torch.Tensor,
92 + key: torch.Tensor,
93 + value: torch.Tensor,
94 + kv_cache: torch.Tensor,
95 + attn_metadata: FlashAttentionMetadata,
96 + output: torch.Tensor | None = None,
97 + output_scale: torch.Tensor | None = None,
98 + output_block_scale: torch.Tensor | None = None,
99 + ) -> torch.Tensor:
100 + """Forward pass with FlashAttention.
101 +
102 + Args:
103 + query: shape = [num_tokens, num_heads, head_size]
104 + key: shape = [num_tokens, num_kv_heads, head_size]
105 + value: shape = [num_tokens, num_kv_heads, head_size_v]
106 + kv_cache: shape =
107 + [num_blocks, block_size, num_kv_heads, head_size + head_size_v]
108 + attn_metadata: Metadata for attention.
109 + Returns:
110 + shape = [num_tokens, num_heads * head_size_v]
111 + NOTE: FP8 quantization, flash-attn expect the size of
112 + {q,k,v}_descale to be (num_sequences, num_kv_heads).
113 + We use torch's .expand() to avoid duplicating values
114 + """
115 + assert output is not None, "Output tensor must be provided."
116 +
117 + if output_scale is not None or output_block_scale is not None:
118 + raise NotImplementedError(
119 + "fused output quantization is not yet supported for FlashAttentionImpl"
120 + )
121 +
122 + if attn_metadata is None:
123 + # Profiling run.
124 + return output.fill_(0)
125 +
126 + attn_type = self.attn_type
127 +
128 + # IMPORTANT!
129 + # NOTE(woosuk): With piece-wise CUDA graphs, this method is executed in
130 + # eager-mode PyTorch. Thus, we need to be careful about any CPU overhead
131 + # in this method. For example, `view` and `slice` (or `[:n]`) operations
132 + # are surprisingly slow even in the case they do not invoke any GPU ops.
133 + # Minimize the PyTorch ops in this method as much as possible.
134 + # Whenever making a change in this method, please benchmark the
135 + # performance to make sure it does not introduce any overhead.
136 +
137 + num_actual_tokens = attn_metadata.num_actual_tokens
138 +
139 + # Handle encoder attention differently - no KV cache needed
140 + if attn_type in (AttentionType.ENCODER_ONLY, AttentionType.ENCODER):
141 + # For encoder attention,
142 + # we use direct Q, K, V tensors without caching
143 + return self._forward_encoder_attention(
144 + query[:num_actual_tokens],
145 + key[:num_actual_tokens],
146 + value[:num_actual_tokens],
147 + output[:num_actual_tokens],
148 + attn_metadata,
149 + layer,
150 + )
151 +
152 + # For decoder and cross-attention, use KV cache as before
153 + # Different head_size for K and V
154 + key_cache = kv_cache[..., : self.head_size]
155 + value_cache = kv_cache[..., self.head_size :]
156 +
157 + # key and value may be None in the case of cross attention. They are
158 + # calculated once based on the output from the encoder and then cached
159 + # in KV cache.
160 + if (
161 + self.kv_sharing_target_layer_name is None
162 + and key is not None
163 + and value is not None
164 + ):
165 + # Reshape the input keys and values and store them in the cache.
166 + # Skip this if sharing KV cache with an earlier attention layer.
167 + # NOTE(woosuk): Here, key and value are padded while slot_mapping is
168 + # not padded. However, we don't need to do key[:num_actual_tokens]
169 + # and value[:num_actual_tokens] because the reshape_and_cache_flash
170 + # op uses the slot_mapping's shape to determine the number of
171 + # actual tokens.
172 +
173 + # kv_cache update for different head_size K and V
174 + triton_reshape_and_cache_flash_diffkv(
175 + key,
176 + value,
177 + kv_cache,
178 + attn_metadata.slot_mapping,
179 + self.kv_cache_dtype,
180 + layer._k_scale,
181 + layer._v_scale,
182 + )
183 +
184 + if self.kv_cache_dtype.startswith("fp8"):
185 + # queries are quantized in the attention layer
186 + dtype = FlashAttentionBackend.get_fp8_dtype_for_flashattn(
187 + self.kv_cache_dtype
188 + )
189 + key_cache = key_cache.view(dtype)
190 + value_cache = value_cache.view(dtype)
191 +
192 + if not attn_metadata.use_cascade:
193 + cu_seqlens_q = attn_metadata.query_start_loc
194 + seqused_k = attn_metadata.seq_lens
195 + max_seqlen_q = attn_metadata.max_query_len
196 + max_seqlen_k = attn_metadata.max_seq_len
197 + block_table = attn_metadata.block_table
198 + scheduler_metadata = attn_metadata.scheduler_metadata
199 +
200 + descale_shape = (cu_seqlens_q.shape[0] - 1, self.num_kv_heads)
201 +
202 + if self.dcp_world_size > 1:
203 + self._forward_with_dcp(
204 + query[:num_actual_tokens],
205 + key[:num_actual_tokens],
206 + value[:num_actual_tokens],
207 + key_cache,
208 + value_cache,
209 + output[:num_actual_tokens],
210 + attn_metadata,
211 + q_descale=layer._q_scale.expand(descale_shape),
212 + k_descale=layer._k_scale.expand(descale_shape),
213 + v_descale=layer._v_scale.expand(descale_shape),
214 + )
215 + return output
216 + else:
217 + flash_attn_varlen_func(
218 + q=query[:num_actual_tokens],
219 + k=key_cache,
220 + v=value_cache,
221 + out=output[:num_actual_tokens],
222 + cu_seqlens_q=cu_seqlens_q,
223 + max_seqlen_q=max_seqlen_q,
224 + seqused_k=seqused_k,
225 + max_seqlen_k=max_seqlen_k,
226 + softmax_scale=self.scale,
227 + causal=attn_metadata.causal,
228 + alibi_slopes=self.alibi_slopes,
229 + window_size=self.sliding_window,
230 + block_table=block_table,
231 + softcap=self.logits_soft_cap,
232 + scheduler_metadata=scheduler_metadata,
233 + fa_version=self.vllm_flash_attn_version,
234 + q_descale=layer._q_scale.expand(descale_shape),
235 + k_descale=layer._k_scale.expand(descale_shape),
236 + v_descale=layer._v_scale.expand(descale_shape),
237 + num_splits=attn_metadata.max_num_splits,
238 + s_aux=self.sinks,
239 + )
240 + return output
241 +
242 + # Cascade attention (rare case).
243 + cascade_attention(
244 + output[:num_actual_tokens],
245 + query[:num_actual_tokens],
246 + key_cache,
247 + value_cache,
248 + cu_query_lens=attn_metadata.query_start_loc,
249 + max_query_len=attn_metadata.max_query_len,
250 + cu_prefix_query_lens=attn_metadata.cu_prefix_query_lens,
251 + prefix_kv_lens=attn_metadata.prefix_kv_lens,
252 + suffix_kv_lens=attn_metadata.suffix_kv_lens,
253 + max_kv_len=attn_metadata.max_seq_len,
254 + softmax_scale=self.scale,
255 + alibi_slopes=self.alibi_slopes,
256 + sliding_window=self.sliding_window,
257 + logits_soft_cap=self.logits_soft_cap,
258 + block_table=attn_metadata.block_table,
259 + common_prefix_len=attn_metadata.common_prefix_len,
260 + max_num_splits=attn_metadata.max_num_splits,
261 + fa_version=self.vllm_flash_attn_version,
262 + prefix_scheduler_metadata=attn_metadata.prefix_scheduler_metadata,
263 + suffix_scheduler_metadata=attn_metadata.scheduler_metadata,
264 + q_descale=layer._q_scale,
265 + k_descale=layer._k_scale,
266 + v_descale=layer._v_scale,
267 + s_aux=self.sinks,
268 + )
269 + return output
vllm/v1/core/single_type_kv_cache_manager.py
+26
@@ -15,6 +15,7 @@ from vllm.v1.kv_cache_interface import (
15 KVCacheSpec,
16 MambaSpec,
17 MLAAttentionSpec,
18 + SinkFullAttentionSpec,
19 SlidingWindowSpec,
20 )
21 from vllm.v1.request import Request
@@ -882,6 +883,30 @@ class CrossAttentionManager(SingleTypeKVCacheManager):
883 raise NotImplementedError("CrossAttentionManager does not support caching")
884
885
886 +class SinkFullAttentionManager(FullAttentionManager):
887 + def __init__(
888 + self,
889 + kv_cache_spec: SinkFullAttentionSpec,
890 + block_pool: BlockPool,
891 + enable_caching: bool,
892 + kv_cache_group_id: int,
893 + dcp_world_size: int = 1,
894 + pcp_world_size: int = 1,
895 + ):
896 + super().__init__(
897 + kv_cache_spec,
898 + block_pool,
899 + enable_caching,
900 + kv_cache_group_id,
901 + dcp_world_size,
902 + pcp_world_size,
903 + )
904 + sink_len = kv_cache_spec.sink_len
905 + assert sink_len is not None and sink_len > 0 and sink_len % self.block_size == 0
906 + num_sink_block = sink_len // self.block_size
907 + self.sink_blocks = self.block_pool.free_block_queue.popleft_n(num_sink_block)
908 +
909 +
910 spec_manager_map: dict[type[KVCacheSpec], type[SingleTypeKVCacheManager]] = {
911 FullAttentionSpec: FullAttentionManager,
912 MLAAttentionSpec: FullAttentionManager,
@@ -889,6 +914,7 @@ spec_manager_map: dict[type[KVCacheSpec], type[SingleTypeKVCacheManager]] = {
914 ChunkedLocalAttentionSpec: ChunkedLocalAttentionManager,
915 MambaSpec: MambaManager,
916 CrossAttentionSpec: CrossAttentionManager,
917 + SinkFullAttentionSpec: SinkFullAttentionManager,
918 }
919
920
vllm/v1/kv_cache_interface.py
+66
@@ -89,12 +89,18 @@ class FullAttentionSpec(AttentionSpec):
89 In this case, we use FullAttentionSpec and record the sliding window size.
90 """
91
92 + head_size_v: int | None = None
93 +
94 sliding_window: int | None = None
95 """
96 Default to None for not using sliding window attention.
97 """
98 attention_chunk_size: int | None = None
99
100 + def __post_init__(self):
101 + if self.head_size_v is None:
102 + object.__setattr__(self, "head_size_v", self.head_size)
103 +
104 def max_memory_usage_bytes(self, vllm_config: VllmConfig) -> int:
105 max_model_len = vllm_config.model_config.max_model_len
106 dcp_world_size = vllm_config.parallel_config.decode_context_parallel_size
@@ -142,6 +148,7 @@ class FullAttentionSpec(AttentionSpec):
148 block_size=specs[0].block_size,
149 num_kv_heads=specs[0].num_kv_heads,
150 head_size=specs[0].head_size,
151 + head_size_v=specs[0].head_size_v,
152 dtype=specs[0].dtype,
153 sliding_window=cls.merge_window_sizes(sliding_window),
154 attention_chunk_size=cls.merge_window_sizes(attention_chunk_size),
@@ -160,6 +167,15 @@ class FullAttentionSpec(AttentionSpec):
167 )
168 return merged_spec
169
170 + @property
171 + def page_size_bytes(self) -> int:
172 + return (
173 + self.block_size
174 + * self.num_kv_heads
175 + * (self.head_size + self.head_size_v)
176 + * get_dtype_size(self.dtype)
177 + )
178 +
179
180 @dataclass(frozen=True)
181 class MLAAttentionSpec(FullAttentionSpec):
@@ -287,6 +303,56 @@ class CrossAttentionSpec(AttentionSpec):
303 return cdiv(max_encoder_len, self.block_size) * self.page_size_bytes
304
305
306 +@dataclass(frozen=True)
307 +class SinkFullAttentionSpec(FullAttentionSpec):
308 + sink_len: int | None = None
309 +
310 + @classmethod
311 + def merge(cls, specs: list[Self]) -> Self:
312 + """
313 + Merge a list of FullAttentionSpec objects into a single
314 + FullAttentionSpec object.
315 + """
316 + assert all(isinstance(spec, FullAttentionSpec) for spec in specs), (
317 + "All attention layers in the same KV cache group must be FullAttentionSpec."
318 + )
319 +
320 + sliding_window = set(
321 + spec.sliding_window for spec in specs if spec.sliding_window is not None
322 + )
323 + attention_chunk_size = set(
324 + spec.attention_chunk_size
325 + for spec in specs
326 + if spec.attention_chunk_size is not None
327 + )
328 + assert not any(isinstance(spec, MLAAttentionSpec) for spec in specs), (
329 + "MLAAttentionSpec should be merged in MLAAttentionSpec.merge"
330 + )
331 + merged_spec = cls(
332 + block_size=specs[0].block_size,
333 + num_kv_heads=specs[0].num_kv_heads,
334 + head_size=specs[0].head_size,
335 + head_size_v=specs[0].head_size_v,
336 + sink_len=specs[0].sink_len,
337 + dtype=specs[0].dtype,
338 + sliding_window=cls.merge_window_sizes(sliding_window),
339 + attention_chunk_size=cls.merge_window_sizes(attention_chunk_size),
340 + )
341 + for spec in specs:
342 + for f in fields(AttentionSpec):
343 + assert getattr(spec, f.name) == getattr(merged_spec, f.name), (
344 + "All attention layers in the same KV cache group must have "
345 + "the same attention spec."
346 + )
347 + assert (merged_spec.sliding_window is not None) + (
348 + merged_spec.attention_chunk_size is not None
349 + ) <= 1, (
350 + "Model with both sliding window layers and chunked local attention "
351 + "layers is not supported."
352 + )
353 + return merged_spec
354 +
355 +
356 @dataclass(frozen=True)
357 class UniformTypeKVCacheSpecs(KVCacheSpec):
358 """