@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 1aa427fdc1
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 14, 05:30 UTC

[Kernels] Add Flash Linear Attention Kernels (#24518)

Signed-off-by: youkaichao <youkaichao@gmail.com>

youkaichao committed Sep 10, 2025 at 00:04 UTC 1aa427fdc1d4ddb7a0c966f7c46badd1c3952c14
17 files changed +2671 -2
tools/mypy.sh
+1 -1
@@ -29,7 +29,7 @@ run_mypy vllm/engine
29 run_mypy vllm/executor
30 run_mypy vllm/inputs
31 run_mypy vllm/lora
32 -run_mypy vllm/model_executor
32 +run_mypy --exclude 'vllm/model_executor/layers/fla/ops' vllm/model_executor
33 run_mypy vllm/plugins
34 run_mypy vllm/worker
35 run_mypy vllm/v1
vllm/model_executor/layers/fla/__init__.py new
+8
@@ -0,0 +1,8 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +# SPDX-FileCopyrightText: Songlin Yang, Yu Zhang
4 +#
5 +# This file contains code copied from the flash-linear-attention project.
6 +# The original source code was licensed under the MIT license and included
7 +# the following copyright notice:
8 +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang
vllm/model_executor/layers/fla/ops/__init__.py new
+17
@@ -0,0 +1,17 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +# SPDX-FileCopyrightText: Songlin Yang, Yu Zhang
4 +#
5 +# This file contains code copied from the flash-linear-attention project.
6 +# The original source code was licensed under the MIT license and included
7 +# the following copyright notice:
8 +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang
9 +from .chunk import chunk_gated_delta_rule
10 +from .fused_recurrent import fused_recurrent_gated_delta_rule
11 +from .layernorm_guard import RMSNormGated
12 +
13 +__all__ = [
14 + "RMSNormGated",
15 + "chunk_gated_delta_rule",
16 + "fused_recurrent_gated_delta_rule",
17 +]
vllm/model_executor/layers/fla/ops/chunk.py new
+225
@@ -0,0 +1,225 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +# SPDX-FileCopyrightText: Songlin Yang, Yu Zhang
4 +#
5 +# This file contains code copied from the flash-linear-attention project.
6 +# The original source code was licensed under the MIT license and included
7 +# the following copyright notice:
8 +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang
9 +# ruff: noqa: E501
10 +import warnings
11 +from typing import Optional
12 +
13 +import torch
14 +from einops import rearrange
15 +
16 +from .chunk_delta_h import chunk_gated_delta_rule_fwd_h
17 +from .chunk_o import chunk_fwd_o
18 +from .chunk_scaled_dot_kkt import chunk_scaled_dot_kkt_fwd
19 +from .cumsum import chunk_local_cumsum
20 +from .l2norm import l2norm_fwd
21 +from .solve_tril import solve_tril
22 +from .utils import SUPPRESS_LEVEL, input_guard
23 +from .wy_fast import recompute_w_u_fwd
24 +
25 +
26 +def chunk_gated_delta_rule_fwd(q: torch.Tensor,
27 + k: torch.Tensor,
28 + v: torch.Tensor,
29 + g: torch.Tensor,
30 + beta: torch.Tensor,
31 + scale: float,
32 + initial_state: torch.Tensor,
33 + output_final_state: bool,
34 + cu_seqlens: Optional[torch.LongTensor] = None):
35 + g = chunk_local_cumsum(g, chunk_size=64, cu_seqlens=cu_seqlens)
36 + # obtain WY representation. u is actually the new v.
37 + A = chunk_scaled_dot_kkt_fwd(k=k,
38 + beta=beta,
39 + g_cumsum=g,
40 + cu_seqlens=cu_seqlens,
41 + output_dtype=torch.float32)
42 + A = solve_tril(A=A, cu_seqlens=cu_seqlens, output_dtype=k.dtype)
43 + w, u = recompute_w_u_fwd(
44 + k=k,
45 + v=v,
46 + beta=beta,
47 + A=A,
48 + g_cumsum=g,
49 + cu_seqlens=cu_seqlens,
50 + )
51 + h, v_new, final_state = chunk_gated_delta_rule_fwd_h(
52 + k=k,
53 + w=w,
54 + u=u,
55 + g=g,
56 + initial_state=initial_state,
57 + output_final_state=output_final_state,
58 + cu_seqlens=cu_seqlens,
59 + )
60 + o = chunk_fwd_o(
61 + q=q,
62 + k=k,
63 + v=v_new,
64 + h=h,
65 + g=g,
66 + scale=scale,
67 + cu_seqlens=cu_seqlens,
68 + )
69 + if SUPPRESS_LEVEL < 3:
70 + return g, o, A, final_state, None, None, None
71 + elif SUPPRESS_LEVEL >= 3:
72 + return g, o, A, final_state, w, h, v_new
73 +
74 +
75 +class ChunkGatedDeltaRuleFunction(torch.autograd.Function):
76 +
77 + @staticmethod
78 + @input_guard
79 + @torch.amp.custom_fwd(device_type='cuda')
80 + def forward(ctx,
81 + q: torch.Tensor,
82 + k: torch.Tensor,
83 + v: torch.Tensor,
84 + g: torch.Tensor,
85 + beta: torch.Tensor,
86 + scale: float,
87 + initial_state: torch.Tensor,
88 + output_final_state: bool,
89 + cu_seqlens: Optional[torch.LongTensor] = None,
90 + use_qk_l2norm_in_kernel: bool = False):
91 + if use_qk_l2norm_in_kernel:
92 + q = l2norm_fwd(q)
93 + k = l2norm_fwd(k)
94 +
95 + g, o, A, final_state, w, h, v_new = chunk_gated_delta_rule_fwd(
96 + q=q,
97 + k=k,
98 + v=v,
99 + g=g,
100 + beta=beta,
101 + scale=scale,
102 + initial_state=initial_state,
103 + output_final_state=output_final_state,
104 + cu_seqlens=cu_seqlens,
105 + )
106 + ctx.scale = scale
107 + ctx.use_qk_l2norm_in_kernel = use_qk_l2norm_in_kernel
108 + return o.to(q.dtype), final_state
109 +
110 +
111 +@torch.compiler.disable
112 +def chunk_gated_delta_rule(q: torch.Tensor,
113 + k: torch.Tensor,
114 + v: torch.Tensor,
115 + g: torch.Tensor,
116 + beta: torch.Tensor,
117 + scale: float = None,
118 + initial_state: torch.Tensor = None,
119 + output_final_state: bool = False,
120 + cu_seqlens: Optional[torch.LongTensor] = None,
121 + head_first: bool = False,
122 + use_qk_l2norm_in_kernel: bool = False):
123 + r"""
124 + Args:
125 + q (torch.Tensor):
126 + queries of shape `[B, T, H, K]` if `head_first=False` else `[B, H, T, K]`.
127 + k (torch.Tensor):
128 + keys of shape `[B, T, H, K]` if `head_first=False` else `[B, H, T, K]`.
129 + v (torch.Tensor):
130 + values of shape `[B, T, H, V]` if `head_first=False` else `[B, H, T, V]`.
131 + g (torch.Tensor):
132 + (forget) gating tensor (in log space!) of shape `[B, T, H]` if `head_first=False` else `[B, H, T]`.
133 + beta (torch.Tensor):
134 + betas of shape `[B, T, H]` if `head_first=False` else `[B, H, T]`.
135 + scale (Optional[int]):
136 + Scale factor for the RetNet attention scores.
137 + If not provided, it will default to `1 / sqrt(K)`. Default: `None`.
138 + initial_state (Optional[torch.Tensor]):
139 + Initial state of shape `[N, H, K, V]` for `N` input sequences.
140 + For equal-length input sequences, `N` equals the batch size `B`.
141 + Default: `None`.
142 + output_final_state (Optional[bool]):
143 + Whether to output the final state of shape `[N, H, K, V]`. Default: `False`.
144 + cu_seqlens (torch.LongTensor):
145 + Cumulative sequence lengths of shape `[N+1]` used for variable-length training,
146 + consistent with the FlashAttention API.
147 + head_first (Optional[bool]):
148 + Whether the inputs are in the head-first format, which is not supported for variable-length inputs.
149 + Default: `False`.
150 +
151 + Returns:
152 + o (torch.Tensor):
153 + Outputs of shape `[B, T, H, V]` if `head_first=False` else `[B, H, T, V]`.
154 + final_state (torch.Tensor):
155 + Final state of shape `[N, H, K, V]` if `output_final_state=True` else `None`.
156 +
157 + Examples::
158 + >>> import torch
159 + >>> import torch.nn.functional as F
160 + >>> from einops import rearrange
161 + >>> from fla.ops.gated_delta_rule import chunk_gated_delta_rule
162 + # inputs with equal lengths
163 + >>> B, T, H, K, V = 4, 2048, 4, 512, 512
164 + >>> q = torch.randn(B, T, H, K, dtype=torch.bfloat16, device='cuda')
165 + >>> k = F.normalize(torch.randn(B, T, H, K, dtype=torch.bfloat16, device='cuda'), p=2, dim=-1)
166 + >>> v = torch.randn(B, T, H, V, dtype=torch.bfloat16, device='cuda')
167 + >>> beta = torch.rand(B, T, H, dtype=torch.bfloat16, device='cuda').sigmoid()
168 + >>> g = F.logsigmoid(torch.rand(B, T, H, dtype=torch.bfloat16, device='cuda'))
169 + >>> h0 = torch.randn(B, H, K, V, dtype=torch.bfloat16, device='cuda')
170 + >>> o, ht = chunk_gated_delta_rule(
171 + q, k, v, g, beta,
172 + initial_state=h0,
173 + output_final_state=True
174 + )
175 + # for variable-length inputs, the batch size `B` is expected to be 1 and `cu_seqlens` is required
176 + >>> q, k, v, beta, g = map(lambda x: rearrange(x, 'b t ... -> 1 (b t) ...'), (q, k, v, beta, g))
177 + # for a batch with 4 sequences, `cu_seqlens` with 5 start/end positions are expected
178 + >>> cu_seqlens = q.new_tensor([0, 2048, 4096, 6144, 8192], dtype=torch.long)
179 + >>> o_var, ht_var = chunk_gated_delta_rule(
180 + q, k, v, g, beta,
181 + initial_state=h0,
182 + output_final_state=True,
183 + cu_seqlens=cu_seqlens
184 + )
185 + """
186 + assert q.dtype == k.dtype == v.dtype
187 + assert q.dtype != torch.float32, "ChunkGatedDeltaRuleFunction does not support float32. Please use bfloat16."
188 + assert len(
189 + beta.shape
190 + ) == 3, "beta must be of shape [B, T, H] if head_first=False, or [B, H, T] otherwise."
191 +
192 + if head_first:
193 + raise DeprecationWarning(
194 + "head_first is deprecated and will be removed in a future version. "
195 + "Please use head_first=False for now instead.",
196 + stacklevel=2)
197 + q, k, v, beta, g = map(
198 + lambda x: rearrange(x, 'b h t ... -> b t h ...'),
199 + (q, k, v, beta, g))
200 + if not head_first and q.shape[1] < q.shape[2]:
201 + warnings.warn(
202 + f"Input tensor shape suggests potential format mismatch: seq_len ({q.shape[1]}) < num_heads ({q.shape[2]}). "
203 + "This may indicate the inputs were passed in head-first format [B, H, T, ...] "
204 + "when head_first=False was specified. "
205 + "Please verify your input tensor format matches the expected shape [B, T, H, ...].",
206 + stacklevel=2)
207 + if cu_seqlens is not None:
208 + if q.shape[0] != 1:
209 + raise ValueError(
210 + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`."
211 + f"Please flatten variable-length inputs before processing.")
212 + if initial_state is not None and initial_state.shape[0] != len(
213 + cu_seqlens) - 1:
214 + raise ValueError(
215 + f"The number of initial states is expected to be equal to the number of input sequences, "
216 + f"i.e., {len(cu_seqlens) - 1} rather than {initial_state.shape[0]}."
217 + )
218 + if scale is None:
219 + scale = k.shape[-1]**-0.5
220 + o, final_state = ChunkGatedDeltaRuleFunction.apply(
221 + q, k, v, g, beta, scale, initial_state, output_final_state, cu_seqlens,
222 + use_qk_l2norm_in_kernel)
223 + if head_first:
224 + o = rearrange(o, 'b t h ... -> b h t ...')
225 + return o, final_state
vllm/model_executor/layers/fla/ops/chunk_delta_h.py new
+289
@@ -0,0 +1,289 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +# SPDX-FileCopyrightText: Songlin Yang, Yu Zhang
4 +#
5 +# This file contains code copied from the flash-linear-attention project.
6 +# The original source code was licensed under the MIT license and included
7 +# the following copyright notice:
8 +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang
9 +# ruff: noqa: E501
10 +from typing import Optional
11 +
12 +import torch
13 +
14 +from vllm.triton_utils import tl, triton
15 +
16 +from .index import prepare_chunk_indices, prepare_chunk_offsets
17 +from .op import exp, safe_exp
18 +from .utils import is_nvidia_hopper, use_cuda_graph
19 +
20 +NUM_WARPS = [2, 4] if is_nvidia_hopper else [2, 4, 8, 16]
21 +
22 +
23 +@triton.heuristics({
24 + 'USE_G': lambda args: args['g'] is not None,
25 + 'USE_INITIAL_STATE': lambda args: args['h0'] is not None,
26 + 'STORE_FINAL_STATE': lambda args: args['ht'] is not None,
27 + 'SAVE_NEW_VALUE': lambda args: args['v_new'] is not None,
28 + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None,
29 +})
30 +@triton.autotune(
31 + configs=[
32 + triton.Config({'BV': BV}, num_warps=num_warps, num_stages=num_stages)
33 + for num_warps in [2, 4] for num_stages in [2, 3, 4] for BV in [32, 64]
34 + ],
35 + key=['H', 'K', 'V', 'BT', 'USE_G'],
36 + use_cuda_graph=use_cuda_graph,
37 +)
38 +@triton.jit(do_not_specialize=['T'])
39 +def chunk_gated_delta_rule_fwd_kernel_h_blockdim64(
40 + k,
41 + v,
42 + w,
43 + v_new,
44 + g,
45 + h,
46 + h0,
47 + ht,
48 + cu_seqlens,
49 + chunk_offsets,
50 + T,
51 + H: tl.constexpr,
52 + Hg: tl.constexpr,
53 + K: tl.constexpr,
54 + V: tl.constexpr,
55 + BT: tl.constexpr,
56 + BV: tl.constexpr,
57 + USE_G: tl.constexpr,
58 + USE_INITIAL_STATE: tl.constexpr,
59 + STORE_FINAL_STATE: tl.constexpr,
60 + SAVE_NEW_VALUE: tl.constexpr,
61 + IS_VARLEN: tl.constexpr,
62 +):
63 + i_v, i_nh = tl.program_id(0), tl.program_id(1)
64 + i_n, i_h = i_nh // H, i_nh % H
65 + if IS_VARLEN:
66 + bos, eos = tl.load(cu_seqlens + i_n).to(
67 + tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32)
68 + T = eos - bos
69 + NT = tl.cdiv(T, BT)
70 + boh = tl.load(chunk_offsets + i_n).to(tl.int32)
71 + else:
72 + bos, eos = i_n * T, i_n * T + T
73 + NT = tl.cdiv(T, BT)
74 + boh = i_n * NT
75 +
76 + # [BK, BV]
77 + b_h1 = tl.zeros([64, BV], dtype=tl.float32)
78 + if K > 64:
79 + b_h2 = tl.zeros([64, BV], dtype=tl.float32)
80 + if K > 128:
81 + b_h3 = tl.zeros([64, BV], dtype=tl.float32)
82 + if K > 192:
83 + b_h4 = tl.zeros([64, BV], dtype=tl.float32)
84 +
85 + # calculate offset
86 + h += (boh * H + i_h) * K * V
87 + v += (bos * H + i_h) * V
88 + k += (bos * Hg + i_h // (H // Hg)) * K
89 + w += (bos * H + i_h) * K
90 + if SAVE_NEW_VALUE:
91 + v_new += (bos * H + i_h) * V
92 + stride_v = H * V
93 + stride_h = H * K * V
94 + stride_k = Hg * K
95 + stride_w = H * K
96 + if USE_INITIAL_STATE:
97 + h0 = h0 + i_nh * K * V
98 + if STORE_FINAL_STATE:
99 + ht = ht + i_nh * K * V
100 +
101 + # load initial state
102 + if USE_INITIAL_STATE:
103 + p_h0_1 = tl.make_block_ptr(h0, (K, V), (V, 1), (0, i_v * BV), (64, BV),
104 + (1, 0))
105 + b_h1 += tl.load(p_h0_1, boundary_check=(0, 1)).to(tl.float32)
106 + if K > 64:
107 + p_h0_2 = tl.make_block_ptr(h0, (K, V), (V, 1), (64, i_v * BV),
108 + (64, BV), (1, 0))
109 + b_h2 += tl.load(p_h0_2, boundary_check=(0, 1)).to(tl.float32)
110 + if K > 128:
111 + p_h0_3 = tl.make_block_ptr(h0, (K, V), (V, 1), (128, i_v * BV),
112 + (64, BV), (1, 0))
113 + b_h3 += tl.load(p_h0_3, boundary_check=(0, 1)).to(tl.float32)
114 + if K > 192:
115 + p_h0_4 = tl.make_block_ptr(h0, (K, V), (V, 1), (192, i_v * BV),
116 + (64, BV), (1, 0))
117 + b_h4 += tl.load(p_h0_4, boundary_check=(0, 1)).to(tl.float32)
118 +
119 + # main recurrence
120 + for i_t in range(NT):
121 + p_h1 = tl.make_block_ptr(h + i_t * stride_h, (K, V), (V, 1),
122 + (0, i_v * BV), (64, BV), (1, 0))
123 + tl.store(p_h1, b_h1.to(p_h1.dtype.element_ty), boundary_check=(0, 1))
124 + if K > 64:
125 + p_h2 = tl.make_block_ptr(h + i_t * stride_h, (K, V), (V, 1),
126 + (64, i_v * BV), (64, BV), (1, 0))
127 + tl.store(p_h2,
128 + b_h2.to(p_h2.dtype.element_ty),
129 + boundary_check=(0, 1))
130 + if K > 128:
131 + p_h3 = tl.make_block_ptr(h + i_t * stride_h, (K, V), (V, 1),
132 + (128, i_v * BV), (64, BV), (1, 0))
133 + tl.store(p_h3,
134 + b_h3.to(p_h3.dtype.element_ty),
135 + boundary_check=(0, 1))
136 + if K > 192:
137 + p_h4 = tl.make_block_ptr(h + i_t * stride_h, (K, V), (V, 1),
138 + (192, i_v * BV), (64, BV), (1, 0))
139 + tl.store(p_h4,
140 + b_h4.to(p_h4.dtype.element_ty),
141 + boundary_check=(0, 1))
142 +
143 + p_v = tl.make_block_ptr(v, (T, V), (stride_v, 1), (i_t * BT, i_v * BV),
144 + (BT, BV), (1, 0))
145 + p_v_new = tl.make_block_ptr(v_new, (T, V), (stride_v, 1),
146 + (i_t * BT, i_v * BV), (BT, BV),
147 + (1, 0)) if SAVE_NEW_VALUE else None
148 + b_v_new = tl.zeros([BT, BV], dtype=tl.float32)
149 + p_w = tl.make_block_ptr(w, (T, K), (stride_w, 1), (i_t * BT, 0),
150 + (BT, 64), (1, 0))
151 + b_w = tl.load(p_w, boundary_check=(0, 1))
152 + b_v_new += tl.dot(b_w, b_h1.to(b_w.dtype))
153 + if K > 64:
154 + p_w = tl.make_block_ptr(w, (T, K), (stride_w, 1), (i_t * BT, 64),
155 + (BT, 64), (1, 0))
156 + b_w = tl.load(p_w, boundary_check=(0, 1))
157 + b_v_new += tl.dot(b_w, b_h2.to(b_w.dtype))
158 + if K > 128:
159 + p_w = tl.make_block_ptr(w, (T, K), (stride_w, 1), (i_t * BT, 128),
160 + (BT, 64), (1, 0))
161 + b_w = tl.load(p_w, boundary_check=(0, 1))
162 + b_v_new += tl.dot(b_w, b_h3.to(b_w.dtype))
163 + if K > 192:
164 + p_w = tl.make_block_ptr(w, (T, K), (stride_w, 1), (i_t * BT, 192),
165 + (BT, 64), (1, 0))
166 + b_w = tl.load(p_w, boundary_check=(0, 1))
167 + b_v_new += tl.dot(b_w, b_h4.to(b_w.dtype))
168 + b_v_new = -b_v_new + tl.load(p_v, boundary_check=(0, 1))
169 +
170 + if SAVE_NEW_VALUE:
171 + p_v_new = tl.make_block_ptr(v_new, (T, V), (stride_v, 1),
172 + (i_t * BT, i_v * BV), (BT, BV), (1, 0))
173 + tl.store(p_v_new,
174 + b_v_new.to(p_v_new.dtype.element_ty),
175 + boundary_check=(0, 1))
176 +
177 + if USE_G:
178 + last_idx = min((i_t + 1) * BT, T) - 1
179 + b_g_last = tl.load(g + bos * H + last_idx * H + i_h)
180 + p_g = tl.make_block_ptr(g + bos * H + i_h, (T, ), (H, ),
181 + (i_t * BT, ), (BT, ), (0, ))
182 + b_g = tl.load(p_g, boundary_check=(0, ))
183 + b_v_new = b_v_new * safe_exp(b_g_last - b_g)[:, None]
184 + b_g_last = exp(b_g_last)
185 + b_h1 = b_h1 * b_g_last
186 + if K > 64:
187 + b_h2 = b_h2 * b_g_last
188 + if K > 128:
189 + b_h3 = b_h3 * b_g_last
190 + if K > 192:
191 + b_h4 = b_h4 * b_g_last
192 + b_v_new = b_v_new.to(k.dtype.element_ty)
193 + p_k = tl.make_block_ptr(k, (K, T), (1, stride_k), (0, i_t * BT),
194 + (64, BT), (0, 1))
195 + b_k = tl.load(p_k, boundary_check=(0, 1))
196 + b_h1 += tl.dot(b_k, b_v_new)
197 + if K > 64:
198 + p_k = tl.make_block_ptr(k, (K, T), (1, stride_k), (64, i_t * BT),
199 + (64, BT), (0, 1))
200 + b_k = tl.load(p_k, boundary_check=(0, 1))
201 + b_h2 += tl.dot(b_k, b_v_new)
202 + if K > 128:
203 + p_k = tl.make_block_ptr(k, (K, T), (1, stride_k), (128, i_t * BT),
204 + (64, BT), (0, 1))
205 + b_k = tl.load(p_k, boundary_check=(0, 1))
206 + b_h3 += tl.dot(b_k, b_v_new)
207 + if K > 192:
208 + p_k = tl.make_block_ptr(k, (K, T), (1, stride_k), (192, i_t * BT),
209 + (64, BT), (0, 1))
210 + b_k = tl.load(p_k, boundary_check=(0, 1))
211 + b_h4 += tl.dot(b_k, b_v_new)
212 +
213 + # epilogue
214 + if STORE_FINAL_STATE:
215 + p_ht = tl.make_block_ptr(ht, (K, V), (V, 1), (0, i_v * BV), (64, BV),
216 + (1, 0))
217 + tl.store(p_ht, b_h1.to(p_ht.dtype.element_ty), boundary_check=(0, 1))
218 + if K > 64:
219 + p_ht = tl.make_block_ptr(ht, (K, V), (V, 1), (64, i_v * BV),
220 + (64, BV), (1, 0))
221 + tl.store(p_ht,
222 + b_h2.to(p_ht.dtype.element_ty),
223 + boundary_check=(0, 1))
224 + if K > 128:
225 + p_ht = tl.make_block_ptr(ht, (K, V), (V, 1), (128, i_v * BV),
226 + (64, BV), (1, 0))
227 + tl.store(p_ht,
228 + b_h3.to(p_ht.dtype.element_ty),
229 + boundary_check=(0, 1))
230 + if K > 192:
231 + p_ht = tl.make_block_ptr(ht, (K, V), (V, 1), (192, i_v * BV),
232 + (64, BV), (1, 0))
233 + tl.store(p_ht,
234 + b_h4.to(p_ht.dtype.element_ty),
235 + boundary_check=(0, 1))
236 +
237 +
238 +def chunk_gated_delta_rule_fwd_h(
239 + k: torch.Tensor,
240 + w: torch.Tensor,
241 + u: torch.Tensor,
242 + g: Optional[torch.Tensor] = None,
243 + initial_state: Optional[torch.Tensor] = None,
244 + output_final_state: bool = False,
245 + chunk_size: int = 64, # SY: remove this argument and force chunk size 64?
246 + save_new_value: bool = True,
247 + cu_seqlens: Optional[torch.LongTensor] = None,
248 +) -> tuple[torch.Tensor, torch.Tensor]:
249 + B, T, Hg, K, V = *k.shape, u.shape[-1]
250 + H = u.shape[-2]
251 + BT = chunk_size
252 +
253 + chunk_indices = prepare_chunk_indices(
254 + cu_seqlens, chunk_size) if cu_seqlens is not None else None
255 + # N: the actual number of sequences in the batch with either equal or variable lengths
256 + if cu_seqlens is None:
257 + N, NT, chunk_offsets = B, triton.cdiv(T, BT), None
258 + else:
259 + N, NT, chunk_offsets = len(cu_seqlens) - 1, len(
260 + chunk_indices), prepare_chunk_offsets(cu_seqlens, BT)
261 + assert K <= 256, "current kernel does not support head dimension larger than 256."
262 +
263 + h = k.new_empty(B, NT, H, K, V)
264 + final_state = k.new_empty(
265 + N, H, K, V, dtype=torch.float32) if output_final_state else None
266 +
267 + v_new = torch.empty_like(u) if save_new_value else None
268 +
269 + def grid(meta):
270 + return (triton.cdiv(V, meta['BV']), N * H)
271 +
272 + chunk_gated_delta_rule_fwd_kernel_h_blockdim64[grid](
273 + k=k,
274 + v=u,
275 + w=w,
276 + v_new=v_new,
277 + g=g,
278 + h=h,
279 + h0=initial_state,
280 + ht=final_state,
281 + cu_seqlens=cu_seqlens,
282 + chunk_offsets=chunk_offsets,
283 + T=T,
284 + H=H,
285 + Hg=Hg,
286 + K=K,
287 + V=V,
288 + BT=BT)
289 + return h, v_new, final_state
vllm/model_executor/layers/fla/ops/chunk_o.py new
+176
@@ -0,0 +1,176 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +# SPDX-FileCopyrightText: Songlin Yang, Yu Zhang
4 +#
5 +# This file contains code copied from the flash-linear-attention project.
6 +# The original source code was licensed under the MIT license and included
7 +# the following copyright notice:
8 +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang
9 +
10 +# ruff: noqa: E501
11 +
12 +from typing import Optional
13 +
14 +import torch
15 +
16 +from vllm.triton_utils import tl, triton
17 +
18 +from .index import prepare_chunk_indices
19 +from .op import exp, safe_exp
20 +from .utils import FLA_GDN_FIX_BT, check_shared_mem, is_nvidia_hopper
21 +
22 +BKV_LIST = [64, 128] if check_shared_mem() else [32, 64]
23 +NUM_WARPS = [2, 4] if is_nvidia_hopper else [2, 4, 8]
24 +
25 +
26 +@triton.heuristics({
27 + 'USE_G': lambda args: args['g'] is not None,
28 + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None
29 +})
30 +@triton.autotune(
31 + configs=[
32 + triton.Config({
33 + 'BK': BK,
34 + 'BV': BV
35 + },
36 + num_warps=num_warps,
37 + num_stages=num_stages) for BK in BKV_LIST
38 + for BV in BKV_LIST for num_warps in NUM_WARPS
39 + for num_stages in [2, 3, 4]
40 + ],
41 + key=['H', 'K', 'V', 'BT'],
42 +)
43 +@triton.jit(do_not_specialize=['T'])
44 +def chunk_fwd_kernel_o(
45 + q,
46 + k,
47 + v,
48 + h,
49 + g,
50 + o,
51 + cu_seqlens,
52 + chunk_indices,
53 + scale,
54 + T,
55 + H: tl.constexpr,
56 + Hg: tl.constexpr,
57 + K: tl.constexpr,
58 + V: tl.constexpr,
59 + BT: tl.constexpr,
60 + BK: tl.constexpr,
61 + BV: tl.constexpr,
62 + USE_G: tl.constexpr,
63 + IS_VARLEN: tl.constexpr,
64 +):
65 + i_v, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2)
66 + i_b, i_h = i_bh // H, i_bh % H
67 +
68 + if IS_VARLEN:
69 + i_tg = i_t
70 + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(
71 + tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32)
72 + bos, eos = tl.load(cu_seqlens + i_n).to(
73 + tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32)
74 + T = eos - bos
75 + NT = tl.cdiv(T, BT)
76 + else:
77 + NT = tl.cdiv(T, BT)
78 + i_tg = i_b * NT + i_t
79 + bos, eos = i_b * T, i_b * T + T
80 +
81 + # offset calculation
82 + q += (bos * Hg + i_h // (H // Hg)) * K
83 + k += (bos * Hg + i_h // (H // Hg)) * K
84 + v += (bos * H + i_h) * V
85 + o += (bos * H + i_h) * V
86 + h += (i_tg * H + i_h).to(tl.int64) * K * V
87 +
88 + b_o = tl.zeros([BT, BV], dtype=tl.float32)
89 + b_A = tl.zeros([BT, BT], dtype=tl.float32)
90 +
91 + for i_k in range(tl.cdiv(K, BK)):
92 + p_q = tl.make_block_ptr(q, (T, K), (Hg * K, 1), (i_t * BT, i_k * BK),
93 + (BT, BK), (1, 0))
94 + p_k = tl.make_block_ptr(k, (K, T), (1, Hg * K), (i_k * BK, i_t * BT),
95 + (BK, BT), (0, 1))
96 + p_h = tl.make_block_ptr(h, (K, V), (V, 1), (i_k * BK, i_v * BV),
97 + (BK, BV), (1, 0))
98 + # [BT, BK]
99 + b_q = tl.load(p_q, boundary_check=(0, 1))
100 + # [BK, BT]
101 + b_k = tl.load(p_k, boundary_check=(0, 1))
102 + # [BK, BV]
103 + b_h = tl.load(p_h, boundary_check=(0, 1))
104 +
105 + # [BT, BK] @ [BK, BV] -> [BT, BV]
106 + b_o += tl.dot(b_q, b_h)
107 + # [BT, BK] @ [BK, BT] -> [BT, BT]
108 + b_A += tl.dot(b_q, b_k)
109 +
110 + if USE_G:
111 + g += bos * H + i_h
112 + p_g = tl.make_block_ptr(g, (T, ), (H, ), (i_t * BT, ), (BT, ), (0, ))
113 + b_g = tl.load(p_g, boundary_check=(0, ))
114 + b_o = b_o * exp(b_g)[:, None]
115 + b_A = b_A * safe_exp(b_g[:, None] - b_g[None, :])
116 +
117 + o_i = tl.arange(0, BT)
118 + m_A = o_i[:, None] >= o_i[None, :]
119 + b_A = tl.where(m_A, b_A, 0)
120 +
121 + p_v = tl.make_block_ptr(v, (T, V), (H * V, 1), (i_t * BT, i_v * BV),
122 + (BT, BV), (1, 0))
123 + p_o = tl.make_block_ptr(o, (T, V), (H * V, 1), (i_t * BT, i_v * BV),
124 + (BT, BV), (1, 0))
125 + b_v = tl.load(p_v, boundary_check=(0, 1))
126 +
127 + # to fix mma -> mma layout conversion
128 + # already solved by triton v3.2 or higher
129 + b_o = b_o * scale + tl.dot(b_A.to(b_v.dtype), b_v) * scale
130 + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1))
131 +
132 +
133 +def chunk_fwd_o(
134 + q: torch.Tensor,
135 + k: torch.Tensor,
136 + v: torch.Tensor,
137 + h: torch.Tensor,
138 + g: Optional[torch.Tensor] = None, # cumsum of log decay
139 + scale: Optional[float] = None,
140 + cu_seqlens: Optional[torch.LongTensor] = None,
141 + chunk_size: int = 64) -> torch.Tensor:
142 + B, T, Hg, K, V = *q.shape, v.shape[-1]
143 + H = v.shape[-2]
144 + if FLA_GDN_FIX_BT:
145 + BT = 64
146 + else:
147 + BT = min(chunk_size, max(16, triton.next_power_of_2(T)))
148 + chunk_indices = prepare_chunk_indices(
149 + cu_seqlens, BT) if cu_seqlens is not None else None
150 + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices)
151 + if scale is None:
152 + scale = k.shape[-1]**-0.5
153 +
154 + o = torch.empty_like(v)
155 +
156 + def grid(meta):
157 + return (triton.cdiv(V, meta['BV']), NT, B * H)
158 +
159 + chunk_fwd_kernel_o[grid](
160 + q,
161 + k,
162 + v,
163 + h,
164 + g,
165 + o,
166 + cu_seqlens,
167 + chunk_indices,
168 + scale,
169 + T=T,
170 + H=H,
171 + Hg=Hg,
172 + K=K,
173 + V=V,
174 + BT=BT,
175 + )
176 + return o
vllm/model_executor/layers/fla/ops/chunk_scaled_dot_kkt.py new
+138
@@ -0,0 +1,138 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +# SPDX-FileCopyrightText: Songlin Yang, Yu Zhang
4 +#
5 +# This file contains code copied from the flash-linear-attention project.
6 +# The original source code was licensed under the MIT license and included
7 +# the following copyright notice:
8 +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang
9 +# ruff: noqa: E501
10 +from typing import Optional
11 +
12 +import torch
13 +
14 +from vllm.triton_utils import tl, triton
15 +
16 +from .index import prepare_chunk_indices
17 +from .op import safe_exp
18 +
19 +
20 +@triton.heuristics({
21 + 'IS_VARLEN': lambda args: args['cu_seqlens'] is not None,
22 + 'USE_G': lambda args: args['g_cumsum'] is not None
23 +})
24 +@triton.autotune(
25 + configs=[
26 + triton.Config({'BK': BK}, num_warps=num_warps, num_stages=num_stages)
27 + for BK in [32, 64, 128] for num_warps in [2, 4, 8]
28 + for num_stages in [2, 3, 4]
29 + ],
30 + key=['H', 'K', 'BT', 'IS_VARLEN'],
31 +)
32 +@triton.jit(do_not_specialize=['T'])
33 +def chunk_scaled_dot_kkt_fwd_kernel(
34 + k,
35 + beta,
36 + g_cumsum,
37 + A,
38 + cu_seqlens,
39 + chunk_indices,
40 + T,
41 + H: tl.constexpr,
42 + Hg: tl.constexpr,
43 + K: tl.constexpr,
44 + BT: tl.constexpr,
45 + BK: tl.constexpr,
46 + IS_VARLEN: tl.constexpr,
47 + USE_G: tl.constexpr,
48 +):
49 + i_t, i_bh = tl.program_id(0), tl.program_id(1)
50 + i_b, i_h = i_bh // H, i_bh % H
51 + if IS_VARLEN:
52 + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(
53 + tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32)
54 + bos, eos = tl.load(cu_seqlens + i_n).to(
55 + tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32)
56 + T = eos - bos
57 + else:
58 + bos, eos = i_b * T, i_b * T + T
59 + o_t = tl.arange(0, BT)
60 +
61 + p_beta = tl.make_block_ptr(beta + bos * H + i_h, (T, ), (H, ),
62 + (i_t * BT, ), (BT, ), (0, ))
63 + b_beta = tl.load(p_beta, boundary_check=(0, ))
64 +
65 + b_A = tl.zeros([BT, BT], dtype=tl.float32)
66 + for i_k in range(tl.cdiv(K, BK)):
67 + p_k = tl.make_block_ptr(k + (bos * Hg + i_h // (H // Hg)) * K, (T, K),
68 + (Hg * K, 1), (i_t * BT, i_k * BK), (BT, BK),
69 + (1, 0))
70 + b_k = tl.load(p_k, boundary_check=(0, 1))
71 + b_kb = b_k * b_beta[:, None]
72 + b_A += tl.dot(b_kb.to(b_k.dtype), tl.trans(b_k))
73 +
74 + if USE_G:
75 + p_g = tl.make_block_ptr(g_cumsum + bos * H + i_h, (T, ), (H, ),
76 + (i_t * BT, ), (BT, ), (0, ))
77 + b_g = tl.load(p_g, boundary_check=(0, ))
78 + b_g_diff = b_g[:, None] - b_g[None, :]
79 + b_A = b_A * safe_exp(b_g_diff)
80 +
81 + b_A = tl.where(o_t[:, None] > o_t[None, :], b_A, 0)
82 + p_A = tl.make_block_ptr(A + (bos * H + i_h) * BT, (T, BT), (BT * H, 1),
83 + (i_t * BT, 0), (BT, BT), (1, 0))
84 + tl.store(p_A, b_A.to(p_A.dtype.element_ty), boundary_check=(0, 1))
85 +
86 +
87 +def chunk_scaled_dot_kkt_fwd(
88 + k: torch.Tensor,
89 + beta: torch.Tensor,
90 + g_cumsum: Optional[torch.Tensor] = None,
91 + cu_seqlens: Optional[torch.LongTensor] = None,
92 + chunk_size: int = 64,
93 + output_dtype: torch.dtype = torch.float32) -> torch.Tensor:
94 + r"""
95 + Compute beta * K * K^T.
96 +
97 + Args:
98 + k (torch.Tensor):
99 + The key tensor of shape `[B, T, H, K]`.
100 + beta (torch.Tensor):
101 + The beta tensor of shape `[B, T, H]`.
102 + g_cumsum (torch.Tensor):
103 + The cumulative sum of the gate tensor of shape `[B, T, H]`.
104 + Default: None
105 + cu_seqlens (torch.LongTensor):
106 + The cumulative sequence lengths of the input tensor.
107 + Default: None
108 + chunk_size (int):
109 + The chunk size. Default: 64.
110 + output_dtype (torch.dtype):
111 + The dtype of the output tensor. Default: `torch.float32`
112 +
113 + Returns:
114 + beta * K * K^T of shape `[B, T, H, BT]` where `BT` is the chunk size.
115 + """
116 +
117 + B, T, Hg, K = k.shape
118 +
119 + H = beta.shape[-1]
120 + BT = chunk_size
121 + chunk_indices = prepare_chunk_indices(
122 + cu_seqlens, BT) if cu_seqlens is not None else None
123 + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices)
124 + A = torch.empty(B, T, H, BT, device=k.device, dtype=output_dtype)
125 + chunk_scaled_dot_kkt_fwd_kernel[(NT, B * H)](
126 + k=k,
127 + beta=beta,
128 + g_cumsum=g_cumsum,
129 + A=A,
130 + cu_seqlens=cu_seqlens,
131 + chunk_indices=chunk_indices,
132 + T=T,
133 + H=H,
134 + Hg=Hg,
135 + K=K,
136 + BT=BT,
137 + )
138 + return A
vllm/model_executor/layers/fla/ops/cumsum.py new
+226
@@ -0,0 +1,226 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +# SPDX-FileCopyrightText: Songlin Yang, Yu Zhang
4 +#
5 +# This file contains code copied from the flash-linear-attention project.
6 +# The original source code was licensed under the MIT license and included
7 +# the following copyright notice:
8 +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang
9 +# ruff: noqa: E501
10 +import warnings
11 +from typing import Optional
12 +
13 +import torch
14 +
15 +from vllm.triton_utils import tl, triton
16 +
17 +from .index import prepare_chunk_indices
18 +from .utils import check_shared_mem, input_guard
19 +
20 +BS_LIST = [32, 64] if check_shared_mem() else [16, 32]
21 +
22 +
23 +@triton.heuristics({'IS_VARLEN': lambda args: args['cu_seqlens'] is not None})
24 +@triton.autotune(configs=[
25 + triton.Config({}, num_warps=num_warps) for num_warps in [1, 2, 4, 8]
26 +],
27 + key=['B', 'H', 'BT', 'IS_VARLEN', 'REVERSE'])
28 +@triton.jit(do_not_specialize=['T'])
29 +def chunk_local_cumsum_scalar_kernel(
30 + s,
31 + o,
32 + cu_seqlens,
33 + chunk_indices,
34 + T,
35 + B: tl.constexpr,
36 + H: tl.constexpr,
37 + BT: tl.constexpr,
38 + REVERSE: tl.constexpr,
39 + IS_VARLEN: tl.constexpr,
40 + HEAD_FIRST: tl.constexpr,
41 +):
42 + i_t, i_bh = tl.program_id(0), tl.program_id(1)
43 + i_b, i_h = i_bh // H, i_bh % H
44 + if IS_VARLEN:
45 + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(
46 + tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32)
47 + bos, eos = tl.load(cu_seqlens + i_n).to(
48 + tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32)
49 + T = eos - bos
50 + else:
51 + bos, eos = i_b * T, i_b * T + T
52 +
53 + if HEAD_FIRST:
54 + p_s = tl.make_block_ptr(s + bos * H + i_h * T, (T, ), (1, ),
55 + (i_t * BT, ), (BT, ), (0, ))
56 + p_o = tl.make_block_ptr(o + bos * H + i_h * T, (T, ), (1, ),
57 + (i_t * BT, ), (BT, ), (0, ))
58 + else:
59 + p_s = tl.make_block_ptr(s + bos * H + i_h, (T, ), (H, ), (i_t * BT, ),
60 + (BT, ), (0, ))
61 + p_o = tl.make_block_ptr(o + bos * H + i_h, (T, ), (H, ), (i_t * BT, ),
62 + (BT, ), (0, ))
63 + # [BT]
64 + b_s = tl.load(p_s, boundary_check=(0, )).to(tl.float32)
65 + b_o = tl.cumsum(b_s, axis=0)
66 + if REVERSE:
67 + b_z = tl.sum(b_s, axis=0)
68 + b_o = -b_o + b_z[None] + b_s
69 + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, ))
70 +
71 +
72 +@triton.heuristics({'IS_VARLEN': lambda args: args['cu_seqlens'] is not None})
73 +@triton.autotune(configs=[
74 + triton.Config({'BS': BS}, num_warps=num_warps) for BS in BS_LIST
75 + for num_warps in [2, 4, 8]
76 +],
77 + key=['B', 'H', 'S', 'BT', 'IS_VARLEN', 'REVERSE'])
78 +@triton.jit(do_not_specialize=['T'])
79 +def chunk_local_cumsum_vector_kernel(
80 + s,
81 + o,
82 + cu_seqlens,
83 + chunk_indices,
84 + T,
85 + B: tl.constexpr,
86 + H: tl.constexpr,
87 + S: tl.constexpr,
88 + BT: tl.constexpr,
89 + BS: tl.constexpr,
90 + REVERSE: tl.constexpr,
91 + IS_VARLEN: tl.constexpr,
92 + HEAD_FIRST: tl.constexpr,
93 +):
94 + i_s, i_t, i_bh = tl.program_id(0), tl.program_id(1), tl.program_id(2)
95 + i_b, i_h = i_bh // H, i_bh % H
96 + if IS_VARLEN:
97 + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(
98 + tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32)
99 + bos, eos = tl.load(cu_seqlens + i_n).to(
100 + tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32)
101 + T = eos - bos
102 + else:
103 + bos, eos = i_b * T, i_b * T + T
104 +
105 + o_i = tl.arange(0, BT)
106 + if REVERSE:
107 + m_s = tl.where(o_i[:, None] <= o_i[None, :], 1., 0.)
108 + else:
109 + m_s = tl.where(o_i[:, None] >= o_i[None, :], 1., 0.)
110 +
111 + if HEAD_FIRST:
112 + p_s = tl.make_block_ptr(s + (bos * H + i_h * T) * S, (T, S), (S, 1),
113 + (i_t * BT, i_s * BS), (BT, BS), (1, 0))
114 + p_o = tl.make_block_ptr(o + (bos * H + i_h * T) * S, (T, S), (S, 1),
115 + (i_t * BT, i_s * BS), (BT, BS), (1, 0))
116 + else:
117 + p_s = tl.make_block_ptr(s + (bos * H + i_h) * S, (T, S), (H * S, 1),
118 + (i_t * BT, i_s * BS), (BT, BS), (1, 0))
119 + p_o = tl.make_block_ptr(o + (bos * H + i_h) * S, (T, S), (H * S, 1),
120 + (i_t * BT, i_s * BS), (BT, BS), (1, 0))
121 + # [BT, BS]
122 + b_s = tl.load(p_s, boundary_check=(0, 1)).to(tl.float32)
123 + b_o = tl.dot(m_s, b_s, allow_tf32=False)
124 + tl.store(p_o, b_o.to(p_o.dtype.element_ty), boundary_check=(0, 1))
125 +
126 +
127 +def chunk_local_cumsum_scalar(
128 + g: torch.Tensor,
129 + chunk_size: int,
130 + reverse: bool = False,
131 + cu_seqlens: Optional[torch.Tensor] = None,
132 + head_first: bool = False,
133 + output_dtype: Optional[torch.dtype] = torch.float) -> torch.Tensor:
134 + if head_first:
135 + B, H, T = g.shape
136 + else:
137 + B, T, H = g.shape
138 + assert chunk_size == 2**(chunk_size.bit_length() -
139 + 1), "chunk_size must be a power of 2"
140 + BT = chunk_size
141 + chunk_indices = prepare_chunk_indices(
142 + cu_seqlens, BT) if cu_seqlens is not None else None
143 + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices)
144 + g_org, g = g, torch.empty_like(g, dtype=output_dtype or g.dtype)
145 + grid = (NT, B * H)
146 + chunk_local_cumsum_scalar_kernel[grid](g_org,
147 + g,
148 + cu_seqlens,
149 + chunk_indices,
150 + T=T,
151 + B=B,
152 + H=H,
153 + BT=BT,
154 + HEAD_FIRST=head_first,
155 + REVERSE=reverse)
156 + return g
157 +
158 +
159 +def chunk_local_cumsum_vector(
160 + g: torch.Tensor,
161 + chunk_size: int,
162 + reverse: bool = False,
163 + cu_seqlens: Optional[torch.Tensor] = None,
164 + head_first: bool = False,
165 + output_dtype: Optional[torch.dtype] = torch.float) -> torch.Tensor:
166 + if head_first:
167 + B, H, T, S = g.shape
168 + else:
169 + B, T, H, S = g.shape
170 + BT = chunk_size
171 + chunk_indices = prepare_chunk_indices(
172 + cu_seqlens, chunk_size) if cu_seqlens is not None else None
173 + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices)
174 + assert chunk_size == 2**(chunk_size.bit_length() -
175 + 1), "chunk_size must be a power of 2"
176 +
177 + g_org, g = g, torch.empty_like(g, dtype=output_dtype or g.dtype)
178 +
179 + def grid(meta):
180 + return (triton.cdiv(meta['S'], meta['BS']), NT, B * H)
181 +
182 + # keep cummulative normalizer in fp32
183 + # this kernel is equivalent to
184 + # g = g.view(B, H, NT, BT, -1).cumsum(-2).view(B, H, T, -1)
185 + chunk_local_cumsum_vector_kernel[grid](g_org,
186 + g,
187 + cu_seqlens,
188 + chunk_indices,
189 + T=T,
190 + B=B,
191 + H=H,
192 + S=S,
193 + BT=BT,
194 + HEAD_FIRST=head_first,
195 + REVERSE=reverse)
196 + return g
197 +
198 +
199 +@input_guard
200 +def chunk_local_cumsum(g: torch.Tensor,
201 + chunk_size: int,
202 + reverse: bool = False,
203 + cu_seqlens: Optional[torch.Tensor] = None,
204 + head_first: bool = False,
205 + output_dtype: Optional[torch.dtype] = torch.float,
206 + **kwargs) -> torch.Tensor:
207 + if not head_first and g.shape[1] < g.shape[2]:
208 + warnings.warn(
209 + f"Input tensor shape suggests potential format mismatch: seq_len ({g.shape[1]}) < num_heads ({g.shape[2]}). "
210 + "This may indicate the inputs were passed in head-first format [B, H, T, ...] "
211 + "when head_first=False was specified. "
212 + "Please verify your input tensor format matches the expected shape [B, T, H, ...].",
213 + stacklevel=2)
214 + if cu_seqlens is not None:
215 + assert g.shape[
216 + 0] == 1, "Only batch size 1 is supported when cu_seqlens are provided"
217 + if len(g.shape) == 3:
218 + return chunk_local_cumsum_scalar(g, chunk_size, reverse, cu_seqlens,
219 + head_first, output_dtype)
220 + elif len(g.shape) == 4:
221 + return chunk_local_cumsum_vector(g, chunk_size, reverse, cu_seqlens,
222 + head_first, output_dtype)
223 + else:
224 + raise ValueError(f"Unsupported input shape {g.shape}. "
225 + f"which should be (B, T, H, D) if `head_first=False` "
226 + f"or (B, H, T, D) otherwise")
vllm/model_executor/layers/fla/ops/fused_recurrent.py new
+366
@@ -0,0 +1,366 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +# SPDX-FileCopyrightText: Songlin Yang, Yu Zhang
4 +#
5 +# This file contains code copied from the flash-linear-attention project.
6 +# The original source code was licensed under the MIT license and included
7 +# the following copyright notice:
8 +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang
9 +# ruff: noqa: E501
10 +from typing import Optional
11 +
12 +import torch
13 +
14 +from vllm.triton_utils import tl, triton
15 +
16 +from .op import exp
17 +
18 +
19 +@triton.heuristics({
20 + 'USE_INITIAL_STATE':
21 + lambda args: args['h0'] is not None,
22 + 'IS_VARLEN':
23 + lambda args: args['cu_seqlens'] is not None,
24 + "IS_CONTINUOUS_BATCHING":
25 + lambda args: args['ssm_state_indices'] is not None,
26 + "IS_SPEC_DECODING":
27 + lambda args: args['num_accepted_tokens'] is not None,
28 +})
29 +@triton.jit(do_not_specialize=['N', 'T'])
30 +def fused_recurrent_gated_delta_rule_fwd_kernel(
31 + q,
32 + k,
33 + v,
34 + g,
35 + beta,
36 + o,
37 + h0,
38 + ht,
39 + cu_seqlens,
40 + ssm_state_indices,
41 + num_accepted_tokens,
42 + scale,
43 + N: tl.constexpr, # num of sequences
44 + T: tl.constexpr, # num of tokens
45 + B: tl.constexpr,
46 + H: tl.constexpr,
47 + HV: tl.constexpr,
48 + K: tl.constexpr,
49 + V: tl.constexpr,
50 + BK: tl.constexpr,
51 + BV: tl.constexpr,
52 + stride_init_state_token: tl.constexpr,
53 + stride_final_state_token: tl.constexpr,
54 + stride_indices_seq: tl.constexpr,
55 + stride_indices_tok: tl.constexpr,
56 + USE_INITIAL_STATE: tl.constexpr, # whether to use initial state
57 + INPLACE_FINAL_STATE: tl.constexpr, # whether to store final state inplace
58 + IS_BETA_HEADWISE: tl.
59 + constexpr, # whether beta is headwise vector or scalar,
60 + USE_QK_L2NORM_IN_KERNEL: tl.constexpr,
61 + IS_VARLEN: tl.constexpr,
62 + IS_CONTINUOUS_BATCHING: tl.constexpr,
63 + IS_SPEC_DECODING: tl.constexpr,
64 +):
65 + i_k, i_v, i_nh = tl.program_id(0), tl.program_id(1), tl.program_id(2)
66 + i_n, i_hv = i_nh // HV, i_nh % HV
67 + i_h = i_hv // (HV // H)
68 + if IS_VARLEN:
69 + bos, eos = tl.load(cu_seqlens + i_n).to(
70 + tl.int64), tl.load(cu_seqlens + i_n + 1).to(tl.int64)
71 + all = T
72 + T = eos - bos
73 + else:
74 + bos, eos = i_n * T, i_n * T + T
75 + all = B * T
76 +
77 + if T == 0:
78 + # no tokens to process for this sequence
79 + return
80 +
81 + o_k = i_k * BK + tl.arange(0, BK)
82 + o_v = i_v * BV + tl.arange(0, BV)
83 +
84 + p_q = q + (bos * H + i_h) * K + o_k
85 + p_k = k + (bos * H + i_h) * K + o_k
86 + p_v = v + (bos * HV + i_hv) * V + o_v
87 + if IS_BETA_HEADWISE:
88 + p_beta = beta + (bos * HV + i_hv) * V + o_v
89 + else:
90 + p_beta = beta + bos * HV + i_hv
91 + p_g = g + bos * HV + i_hv
92 + p_o = o + ((i_k * all + bos) * HV + i_hv) * V + o_v
93 +
94 + mask_k = o_k < K
95 + mask_v = o_v < V
96 + mask_h = mask_k[:, None] & mask_v[None, :]
97 +
98 + b_h = tl.zeros([BK, BV], dtype=tl.float32)
99 + if USE_INITIAL_STATE:
100 + if IS_CONTINUOUS_BATCHING:
101 + if IS_SPEC_DECODING:
102 + i_t = tl.load(num_accepted_tokens + i_n).to(tl.int64) - 1
103 + else:
104 + i_t = 0
105 + p_h0 = h0 + tl.load(ssm_state_indices + i_n * stride_indices_seq +
106 + i_t).to(tl.int64) * stride_init_state_token
107 + else:
108 + p_h0 = h0 + bos * HV * K * V
109 + p_h0 = p_h0 + i_hv * K * V + o_k[:, None] * V + o_v[None, :]
110 + b_h += tl.load(p_h0, mask=mask_h, other=0).to(tl.float32)
111 +
112 + for i_t in range(0, T):
113 + b_q = tl.load(p_q, mask=mask_k, other=0).to(tl.float32)
114 + b_k = tl.load(p_k, mask=mask_k, other=0).to(tl.float32)
115 + b_v = tl.load(p_v, mask=mask_v, other=0).to(tl.float32)
116 + b_g = tl.load(p_g).to(tl.float32)
117 +
118 + if USE_QK_L2NORM_IN_KERNEL:
119 + b_q = b_q / (tl.sqrt(tl.sum(b_q * b_q)) + 1e-6)
120 + b_k = b_k / (tl.sqrt(tl.sum(b_k * b_k)) + 1e-6)
121 + b_q = b_q * scale
122 + # [BK, BV]
123 + b_h *= exp(b_g)
124 + # [BV]
125 + b_v -= tl.sum(b_h * b_k[:, None], 0)
126 + if IS_BETA_HEADWISE:
127 + b_beta = tl.load(p_beta, mask=mask_v, other=0).to(tl.float32)
128 + else:
129 + b_beta = tl.load(p_beta).to(tl.float32)
130 + b_v *= b_beta
131 + # [BK, BV]
132 + b_h += b_k[:, None] * b_v[None, :]
133 + # [BV]
134 + b_o = tl.sum(b_h * b_q[:, None], 0)
135 + tl.store(p_o, b_o.to(p_o.dtype.element_ty), mask=mask_v)
136 +
137 + # keep the states for multi-query tokens
138 + if INPLACE_FINAL_STATE:
139 + p_ht = ht + tl.load(ssm_state_indices + i_n * stride_indices_seq +
140 + i_t).to(tl.int64) * stride_final_state_token
141 + else:
142 + p_ht = ht + (bos + i_t) * stride_final_state_token
143 + p_ht = p_ht + i_hv * K * V + o_k[:, None] * V + o_v[None, :]
144 + tl.store(p_ht, b_h.to(p_ht.dtype.element_ty), mask=mask_h)
145 +
146 + p_q += H * K
147 + p_k += H * K
148 + p_o += HV * V
149 + p_v += HV * V
150 + p_g += HV
151 + p_beta += HV * (V if IS_BETA_HEADWISE else 1)
152 +
153 +
154 +def fused_recurrent_gated_delta_rule_fwd(
155 + q: torch.Tensor,
156 + k: torch.Tensor,
157 + v: torch.Tensor,
158 + g: torch.Tensor,
159 + beta: torch.Tensor,
160 + scale: float,
161 + initial_state: torch.Tensor,
162 + inplace_final_state: bool = True,
163 + cu_seqlens: Optional[torch.LongTensor] = None,
164 + ssm_state_indices: Optional[torch.Tensor] = None,
165 + num_accepted_tokens: Optional[torch.Tensor] = None,
166 + use_qk_l2norm_in_kernel: bool = False,
167 +) -> tuple[torch.Tensor, torch.Tensor]:
168 + B, T, H, K, V = *k.shape, v.shape[-1]
169 + HV = v.shape[2]
170 + N = B if cu_seqlens is None else len(cu_seqlens) - 1
171 + BK, BV = triton.next_power_of_2(K), min(triton.next_power_of_2(V), 8)
172 + NK, NV = triton.cdiv(K, BK), triton.cdiv(V, BV)
173 + assert NK == 1, "NK > 1 is not supported yet"
174 + num_stages = 3
175 + num_warps = 1
176 +
177 + o = q.new_empty(NK, *v.shape)
178 + if inplace_final_state:
179 + final_state = initial_state
180 + else:
181 + final_state = q.new_empty(T, HV, K, V, dtype=initial_state.dtype)
182 +
183 + stride_init_state_token = initial_state.stride(0)
184 + stride_final_state_token = final_state.stride(0)
185 +
186 + if ssm_state_indices is None:
187 + stride_indices_seq, stride_indices_tok = 1, 1
188 + elif ssm_state_indices.ndim == 1:
189 + stride_indices_seq, stride_indices_tok = ssm_state_indices.stride(0), 1
190 + else:
191 + stride_indices_seq, stride_indices_tok = ssm_state_indices.stride()
192 +
193 + grid = (NK, NV, N * HV)
194 + fused_recurrent_gated_delta_rule_fwd_kernel[grid](
195 + q=q,
196 + k=k,
197 + v=v,
198 + g=g,
199 + beta=beta,
200 + o=o,
201 + h0=initial_state,
202 + ht=final_state,
203 + cu_seqlens=cu_seqlens,
204 + ssm_state_indices=ssm_state_indices,
205 + num_accepted_tokens=num_accepted_tokens,
206 + scale=scale,
207 + N=N,
208 + T=T,
209 + B=B,
210 + H=H,
211 + HV=HV,
212 + K=K,
213 + V=V,
214 + BK=BK,
215 + BV=BV,
216 + stride_init_state_token=stride_init_state_token,
217 + stride_final_state_token=stride_final_state_token,
218 + stride_indices_seq=stride_indices_seq,
219 + stride_indices_tok=stride_indices_tok,
220 + IS_BETA_HEADWISE=beta.ndim == v.ndim,
221 + USE_QK_L2NORM_IN_KERNEL=use_qk_l2norm_in_kernel,
222 + INPLACE_FINAL_STATE=inplace_final_state,
223 + num_warps=num_warps,
224 + num_stages=num_stages,
225 + )
226 + o = o.squeeze(0)
227 + return o, final_state
228 +
229 +
230 +class FusedRecurrentFunction(torch.autograd.Function):
231 +
232 + @staticmethod
233 + def forward(ctx,
234 + q: torch.Tensor,
235 + k: torch.Tensor,
236 + v: torch.Tensor,
237 + g: torch.Tensor,
238 + beta: torch.Tensor,
239 + scale: float,
240 + initial_state: torch.Tensor,
241 + inplace_final_state: bool = True,
242 + cu_seqlens: Optional[torch.LongTensor] = None,
243 + ssm_state_indices: Optional[torch.Tensor] = None,
244 + num_accepted_tokens: Optional[torch.Tensor] = None,
245 + use_qk_l2norm_in_kernel: bool = False):
246 + o, final_state = fused_recurrent_gated_delta_rule_fwd(
247 + q=q.contiguous(),
248 + k=k.contiguous(),
249 + v=v.contiguous(),
250 + g=g.contiguous(),
251 + beta=beta.contiguous(),
252 + scale=scale,
253 + initial_state=initial_state,
254 + inplace_final_state=inplace_final_state,
255 + cu_seqlens=cu_seqlens,
256 + ssm_state_indices=ssm_state_indices,
257 + num_accepted_tokens=num_accepted_tokens,
258 + use_qk_l2norm_in_kernel=use_qk_l2norm_in_kernel,
259 + )
260 +
261 + return o, final_state
262 +
263 +
264 +def fused_recurrent_gated_delta_rule(
265 + q: torch.Tensor,
266 + k: torch.Tensor,
267 + v: torch.Tensor,
268 + g: torch.Tensor,
269 + beta: torch.Tensor = None,
270 + scale: float = None,
271 + initial_state: torch.Tensor = None,
272 + inplace_final_state: bool = True,
273 + cu_seqlens: Optional[torch.LongTensor] = None,
274 + ssm_state_indices: Optional[torch.Tensor] = None,
275 + num_accepted_tokens: Optional[torch.Tensor] = None,
276 + use_qk_l2norm_in_kernel: bool = False,
277 +) -> tuple[torch.Tensor, torch.Tensor]:
278 + r"""
279 + Args:
280 + q (torch.Tensor):
281 + queries of shape `[B, T, H, K]`.
282 + k (torch.Tensor):
283 + keys of shape `[B, T, H, K]`.
284 + v (torch.Tensor):
285 + values of shape `[B, T, HV, V]`.
286 + GVA is applied if `HV > H`.
287 + g (torch.Tensor):
288 + g (decays) of shape `[B, T, HV]`.
289 + beta (torch.Tensor):
290 + betas of shape `[B, T, HV]`.
291 + scale (Optional[int]):
292 + Scale factor for the RetNet attention scores.
293 + If not provided, it will default to `1 / sqrt(K)`. Default: `None`.
294 + initial_state (Optional[torch.Tensor]):
295 + Initial state of shape `[N, HV, K, V]` for `N` input sequences.
296 + For equal-length input sequences, `N` equals the batch size `B`.
297 + Default: `None`.
298 + inplace_final_state: bool:
299 + Whether to store the final state in-place to save memory.
300 + Default: `True`.
301 + cu_seqlens (torch.LongTensor):
302 + Cumulative sequence lengths of shape `[N+1]` used for variable-length training,
303 + consistent with the FlashAttention API.
304 + ssm_state_indices (Optional[torch.Tensor]):
305 + Indices to map the input sequences to the initial/final states.
306 + num_accepted_tokens (Optional[torch.Tensor]):
307 + Number of accepted tokens for each sequence during decoding.
308 +
309 + Returns:
310 + o (torch.Tensor):
311 + Outputs of shape `[B, T, HV, V]`.
312 + final_state (torch.Tensor):
313 + Final state of shape `[N, HV, K, V]`.
314 +
315 + Examples::
316 + >>> import torch
317 + >>> import torch.nn.functional as F
318 + >>> from einops import rearrange
319 + >>> from fla.ops.gated_delta_rule import fused_recurrent_gated_delta_rule
320 + # inputs with equal lengths
321 + >>> B, T, H, HV, K, V = 4, 2048, 4, 8, 512, 512
322 + >>> q = torch.randn(B, T, H, K, device='cuda')
323 + >>> k = F.normalize(torch.randn(B, T, H, K, device='cuda'), p=2, dim=-1)
324 + >>> v = torch.randn(B, T, HV, V, device='cuda')
325 + >>> g = F.logsigmoid(torch.rand(B, T, HV, device='cuda'))
326 + >>> beta = torch.rand(B, T, HV, device='cuda').sigmoid()
327 + >>> h0 = torch.randn(B, HV, K, V, device='cuda')
328 + >>> o, ht = fused_gated_recurrent_delta_rule(
329 + q, k, v, g, beta,
330 + initial_state=h0,
331 + )
332 + # for variable-length inputs, the batch size `B` is expected to be 1 and `cu_seqlens` is required
333 + >>> q, k, v, g, beta = map(lambda x: rearrange(x, 'b t ... -> 1 (b t) ...'), (q, k, v, g, beta))
334 + # for a batch with 4 sequences, `cu_seqlens` with 5 start/end positions are expected
335 + >>> cu_seqlens = q.new_tensor([0, 2048, 4096, 6144, 8192], dtype=torch.long)
336 + >>> o_var, ht_var = fused_gated_recurrent_delta_rule(
337 + q, k, v, g, beta,
338 + initial_state=h0,
339 + cu_seqlens=cu_seqlens
340 + )
341 + """
342 + if cu_seqlens is not None and q.shape[0] != 1:
343 + raise ValueError(
344 + f"The batch size is expected to be 1 rather than {q.shape[0]} when using `cu_seqlens`."
345 + f"Please flatten variable-length inputs before processing.")
346 + if scale is None:
347 + scale = k.shape[-1]**-0.5
348 + else:
349 + assert scale > 0, "scale must be positive"
350 + if beta is None:
351 + beta = torch.ones_like(q[..., 0])
352 + o, final_state = FusedRecurrentFunction.apply(
353 + q,
354 + k,
355 + v,
356 + g,
357 + beta,
358 + scale,
359 + initial_state,
360 + inplace_final_state,
361 + cu_seqlens,
362 + ssm_state_indices,
363 + num_accepted_tokens,
364 + use_qk_l2norm_in_kernel,
365 + )
366 + return o, final_state
vllm/model_executor/layers/fla/ops/index.py new
+39
@@ -0,0 +1,39 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +# SPDX-FileCopyrightText: Songlin Yang, Yu Zhang
4 +#
5 +# This file contains code copied from the flash-linear-attention project.
6 +# The original source code was licensed under the MIT license and included
7 +# the following copyright notice:
8 +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang
9 +# ruff: noqa: E501
10 +import torch
11 +
12 +from vllm.triton_utils import triton
13 +
14 +from .utils import tensor_cache
15 +
16 +
17 +@tensor_cache
18 +def prepare_lens(cu_seqlens: torch.LongTensor) -> torch.LongTensor:
19 + return cu_seqlens[1:] - cu_seqlens[:-1]
20 +
21 +
22 +@tensor_cache
23 +def prepare_chunk_indices(cu_seqlens: torch.LongTensor,
24 + chunk_size: int) -> torch.LongTensor:
25 + indices = torch.cat([
26 + torch.arange(n)
27 + for n in triton.cdiv(prepare_lens(cu_seqlens), chunk_size).tolist()
28 + ])
29 + return torch.stack([indices.eq(0).cumsum(0) - 1, indices],
30 + 1).to(cu_seqlens)
31 +
32 +
33 +@tensor_cache
34 +def prepare_chunk_offsets(cu_seqlens: torch.LongTensor,
35 + chunk_size: int) -> torch.LongTensor:
36 + return torch.cat([
37 + cu_seqlens.new_tensor([0]),
38 + triton.cdiv(prepare_lens(cu_seqlens), chunk_size)
39 + ]).cumsum(-1)
vllm/model_executor/layers/fla/ops/l2norm.py new
+143
@@ -0,0 +1,143 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +# SPDX-FileCopyrightText: Songlin Yang, Yu Zhang
4 +#
5 +# This file contains code copied from the flash-linear-attention project.
6 +# The original source code was licensed under the MIT license and included
7 +# the following copyright notice:
8 +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang
9 +
10 +import os
11 +from typing import Optional
12 +
13 +import torch
14 +
15 +from vllm.triton_utils import tl, triton
16 +
17 +BT_LIST = [8, 16, 32, 64, 128]
18 +
19 +USE_DEFAULT_FLA_NORM = int(os.getenv("USE_DEFAULT_FLA_NORM", "0"))
20 +
21 +
22 +@triton.autotune(configs=[
23 + triton.Config({}, num_warps=num_warps)
24 + for num_warps in [1, 2, 4, 8, 16, 32]
25 +],
26 + key=['D'])
27 +@triton.jit
28 +def l2norm_fwd_kernel1(
29 + x,
30 + y,
31 + D,
32 + BD: tl.constexpr,
33 + eps,
34 +):
35 + i_t = tl.program_id(0)
36 + x += i_t * D
37 + y += i_t * D
38 + # Compute mean and variance
39 + cols = tl.arange(0, BD)
40 + mask = cols < D
41 + b_x = tl.load(x + cols, mask=mask, other=0.0).to(tl.float32)
42 + b_var = tl.sum(b_x * b_x, axis=0)
43 + b_rstd = 1 / tl.sqrt(b_var + eps)
44 + # tl.store(Rstd + i_t, rstd)
45 + # Normalize and apply linear transformation
46 + b_y = b_x * b_rstd
47 + tl.store(y + cols, b_y, mask=mask)
48 +
49 +
50 +@triton.autotune(configs=[
51 + triton.Config({'BT': BT}, num_warps=num_warps)
52 + for num_warps in [1, 2, 4, 8, 16] for BT in BT_LIST
53 +],
54 + key=['D'])
55 +@triton.jit(do_not_specialize=["NB"])
56 +def l2norm_fwd_kernel(
57 + x,
58 + y,
59 + eps,
60 + NB,
61 + T,
62 + D: tl.constexpr,
63 + BT: tl.constexpr,
64 + BD: tl.constexpr,
65 +):
66 + i_t = tl.program_id(0)
67 + p_x = tl.make_block_ptr(x, (T, D), (D, 1), (i_t * BT, 0), (BT, BD), (1, 0))
68 + b_x = tl.load(p_x, boundary_check=(0, 1)).to(tl.float32)
69 + b_var = tl.sum(b_x * b_x, axis=1)
70 + b_y = b_x / tl.sqrt(b_var + eps)[:, None]
71 + p_y = tl.make_block_ptr(y, (T, D), (D, 1), (i_t * BT, 0), (BT, BD), (1, 0))
72 + tl.store(p_y, b_y.to(p_y.dtype.element_ty), boundary_check=(0, 1))
73 +
74 +
75 +@triton.jit
76 +def l2norm_fwd_kernel2(X, Y, eps, M, N: tl.constexpr, MBLOCK: tl.constexpr):
77 + xoffset = tl.program_id(0) * MBLOCK
78 + row_idx = xoffset + tl.arange(0, MBLOCK)[:, None]
79 + xmask = row_idx < M
80 + rindex = tl.arange(0, N)[None, :]
81 + xs = tl.load(X + (rindex + N * row_idx), None).to(tl.float32)
82 + square = tl.broadcast_to(xs * xs, [MBLOCK, N])
83 + square_sum = tl.sum(tl.where(xmask, square, 0), 1)[:, None]
84 + rsqrt = tl.rsqrt(square_sum + eps)
85 + tl.store(Y + (rindex + N * row_idx), xs * rsqrt, xmask)
86 +
87 +
88 +def l2norm_fwd(x: torch.Tensor,
89 + eps: float = 1e-6,
90 + output_dtype: Optional[torch.dtype] = None):
91 + x_shape_og = x.shape
92 + x = x.view(-1, x.shape[-1])
93 + # allocate output
94 + if output_dtype is None:
95 + y = torch.empty_like(x)
96 + else:
97 + y = torch.empty_like(x, dtype=output_dtype)
98 + assert y.stride(-1) == 1
99 + T, D = x.shape[0], x.shape[-1]
100 + # rstd = torch.empty((T,), dtype=torch.float32, device=x.device)
101 + # Less than 64KB per feature: enqueue fused kernel
102 + MAX_FUSED_SIZE = 65536 // x.element_size()
103 + BD = min(MAX_FUSED_SIZE, triton.next_power_of_2(D))
104 + if D > BD:
105 + raise RuntimeError("This layer doesn't support feature dim >= 64KB.")
106 +
107 + if not USE_DEFAULT_FLA_NORM:
108 + MBLOCK = 32
109 + # M, N = x.shape
110 + l2norm_fwd_kernel2[(triton.cdiv(T, MBLOCK), )](
111 + x,
112 + y,
113 + eps,
114 + T,
115 + D,
116 + MBLOCK,
117 + )
118 + else:
119 + if D <= 512:
120 + NB = triton.cdiv(T, 2048)
121 +
122 + def grid(meta):
123 + return (triton.cdiv(T, meta['BT']), )
124 +
125 + l2norm_fwd_kernel[grid](
126 + x,
127 + y,
128 + eps,
129 + NB=NB,
130 + T=T,
131 + D=D,
132 + BD=BD,
133 + )
134 + else:
135 + l2norm_fwd_kernel1[(T, )](
136 + x,
137 + y,
138 + eps=eps,
139 + D=D,
140 + BD=BD,
141 + )
142 +
143 + return y.view(x_shape_og)
vllm/model_executor/layers/fla/ops/layernorm_guard.py new
+337
@@ -0,0 +1,337 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +# SPDX-FileCopyrightText: Tri Dao
4 +#
5 +# This file contains code copied from the flash-linear-attention project.
6 +# The original source code was licensed under the MIT license and included
7 +# the following copyright notice:
8 +# Copyright (c) 2024, Tri Dao.
9 +
10 +# ruff: noqa: E501
11 +# Based on the Triton LayerNorm tutorial: https://triton-lang.org/main/getting-started/tutorials/05-layer-norm.html
12 +# For the backward pass, we keep weight_grad and bias_grad in registers and accumulate.
13 +# This backward pass is faster for dimensions up to 8k, but after that it's much slower due to register spilling.
14 +# The models we train have hidden dim up to 8k anyway (e.g. Llama 70B), so this is fine.
15 +
16 +from typing import Optional
17 +
18 +import torch
19 +import torch.nn as nn
20 +import torch.nn.functional as F
21 +from einops import rearrange
22 +
23 +from vllm.triton_utils import tl, triton
24 +
25 +from .utils import input_guard
26 +
27 +
28 +def rms_norm_ref(x,
29 + weight,
30 + bias,
31 + z=None,
32 + eps=1e-6,
33 + group_size=None,
34 + norm_before_gate=True,
35 + upcast=True):
36 + dtype = x.dtype
37 + weight = weight.float()
38 + bias = bias.float() if bias is not None else None
39 + if upcast:
40 + x = x.float()
41 + z = z.float() if z is not None else z
42 + if z is not None and not norm_before_gate:
43 + x = x * F.silu(z)
44 + if group_size is None:
45 + rstd = 1 / torch.sqrt((x.square()).mean(dim=-1, keepdim=True) + eps)
46 + out = (x * rstd * weight) + bias if bias is not None else (x * rstd *
47 + weight)
48 + else:
49 + x_group = rearrange(x, "... (g d) -> ... g d", d=group_size)
50 + rstd = 1 / torch.sqrt((x_group.square()).mean(dim=-1, keepdim=True) +
51 + eps)
52 + out = rearrange(x_group * rstd, "... g d -> ... (g d)") * weight
53 + if bias is not None:
54 + out = out + bias
55 + if z is not None and norm_before_gate:
56 + out *= F.silu(z)
57 + return out.to(dtype)
58 +
59 +
60 +@triton.heuristics({
61 + "HAS_BIAS": lambda args: args["B"] is not None,
62 + "HAS_Z": lambda args: args["Z"] is not None,
63 +})
64 +@triton.jit
65 +def layer_norm_fwd_kernel(
66 + X, # pointer to the input
67 + Y, # pointer to the output
68 + W, # pointer to the weights
69 + B, # pointer to the biases
70 + Z, # pointer to the other branch
71 + Mean, # pointer to the mean
72 + Rstd, # pointer to the 1/std
73 + stride_x_row, # how much to increase the pointer when moving by 1 row
74 + stride_y_row,
75 + stride_z_row,
76 + M, # number of rows in X
77 + N, # number of columns in X
78 + eps, # epsilon to avoid division by zero
79 + BLOCK_N: tl.constexpr,
80 + HAS_BIAS: tl.constexpr,
81 + HAS_Z: tl.constexpr,
82 + NORM_BEFORE_GATE: tl.constexpr,
83 + IS_RMS_NORM: tl.constexpr,
84 +):
85 + # Map the program id to the row of X and Y it should compute.
86 + row = tl.program_id(0)
87 + group = tl.program_id(1)
88 + X += row * stride_x_row + group * N
89 + Y += row * stride_y_row + group * N
90 + if HAS_Z:
91 + Z += row * stride_z_row + group * N
92 + if not IS_RMS_NORM:
93 + Mean += group * M
94 + Rstd += group * M
95 + W += group * N
96 + if HAS_BIAS:
97 + B += group * N
98 + # Compute mean and variance
99 + cols = tl.arange(0, BLOCK_N)
100 + x = tl.load(X + cols, mask=cols < N, other=0.).to(tl.float32)
101 + if HAS_Z and not NORM_BEFORE_GATE:
102 + z = tl.load(Z + cols, mask=cols < N).to(tl.float32)
103 + x *= z * tl.sigmoid(z)
104 + if not IS_RMS_NORM:
105 + mean = tl.sum(x, axis=0) / N
106 + tl.store(Mean + row, mean)
107 + xbar = tl.where(cols < N, x - mean, 0.)
108 + var = tl.sum(xbar * xbar, axis=0) / N
109 + else:
110 + xbar = tl.where(cols < N, x, 0.)
111 + var = tl.sum(xbar * xbar, axis=0) / N
112 + rstd = 1 / tl.sqrt(var + eps)
113 + tl.store(Rstd + row, rstd)
114 + # Normalize and apply linear transformation
115 + mask = cols < N
116 + w = tl.load(W + cols, mask=mask).to(tl.float32)
117 + if HAS_BIAS:
118 + b = tl.load(B + cols, mask=mask).to(tl.float32)
119 + x_hat = (x - mean) * rstd if not IS_RMS_NORM else x * rstd
120 + y = x_hat * w + b if HAS_BIAS else x_hat * w
121 + if HAS_Z and NORM_BEFORE_GATE:
122 + z = tl.load(Z + cols, mask=mask).to(tl.float32)
123 + y *= z * tl.sigmoid(z)
124 + # Write output
125 + tl.store(Y + cols, y, mask=mask)
126 +
127 +
128 +def layer_norm_fwd(
129 + x: torch.Tensor,
130 + weight: torch.Tensor,
131 + bias: torch.Tensor,
132 + eps: float,
133 + z: torch.Tensor = None,
134 + out: torch.Tensor = None,
135 + group_size: int = None,
136 + norm_before_gate: bool = True,
137 + is_rms_norm: bool = False,
138 +):
139 + M, N = x.shape
140 + if group_size is None:
141 + group_size = N
142 + assert N % group_size == 0
143 + ngroups = N // group_size
144 + assert x.stride(-1) == 1
145 + if z is not None:
146 + assert z.stride(-1) == 1
147 + assert z.shape == (M, N)
148 + assert weight.shape == (N, )
149 + assert weight.stride(-1) == 1
150 + if bias is not None:
151 + assert bias.stride(-1) == 1
152 + assert bias.shape == (N, )
153 + # allocate output
154 + if out is not None:
155 + assert out.shape == x.shape
156 + else:
157 + out = torch.empty_like(x)
158 + assert out.stride(-1) == 1
159 + mean = torch.empty((ngroups * M, ), dtype=torch.float32,
160 + device=x.device) if not is_rms_norm else None
161 + rstd = torch.empty((ngroups * M, ), dtype=torch.float32, device=x.device)
162 + # Less than 64KB per feature: enqueue fused kernel
163 + MAX_FUSED_SIZE = 65536 // x.element_size()
164 + BLOCK_N = min(MAX_FUSED_SIZE, triton.next_power_of_2(group_size))
165 + if group_size > BLOCK_N:
166 + raise RuntimeError(
167 + "This layer norm doesn't support feature dim >= 64KB.")
168 + # heuristics for number of warps
169 + num_warps = min(max(BLOCK_N // 256, 1), 8)
170 + grid = (M, ngroups)
171 + layer_norm_fwd_kernel[grid](x,
172 + out,
173 + weight,
174 + bias,
175 + z,
176 + mean,
177 + rstd,
178 + x.stride(0),
179 + out.stride(0),
180 + z.stride(0) if z is not None else 0,
181 + M,
182 + group_size,
183 + eps,
184 + BLOCK_N=BLOCK_N,
185 + NORM_BEFORE_GATE=norm_before_gate,
186 + IS_RMS_NORM=is_rms_norm,
187 + num_warps=num_warps)
188 + return out, mean, rstd
189 +
190 +
191 +class LayerNormFn(torch.autograd.Function):
192 +
193 + @input_guard
194 + @staticmethod
195 + def forward(ctx,
196 + x,
197 + weight,
198 + bias,
199 + z=None,
200 + eps=1e-6,
201 + group_size=None,
202 + norm_before_gate=True,
203 + is_rms_norm=False):
204 + """If z is not None, we do norm(x) * silu(z) if norm_before_gate, else norm(x * silu(z))
205 + """
206 +
207 + x_shape_og = x.shape
208 + # reshape input data into 2D tensor
209 + x = x.reshape(-1, x.shape[-1])
210 + if x.stride(-1) != 1:
211 + x = x.contiguous()
212 + if z is not None:
213 + assert z.shape == x_shape_og
214 + z = z.reshape(-1, z.shape[-1])
215 + if z.stride(-1) != 1:
216 + z = z.contiguous()
217 + weight = weight.contiguous()
218 + if bias is not None:
219 + bias = bias.contiguous()
220 + y, mean, rstd = layer_norm_fwd(
221 + x,
222 + weight,
223 + bias,
224 + eps,
225 + z=z,
226 + group_size=group_size,
227 + norm_before_gate=norm_before_gate,
228 + is_rms_norm=is_rms_norm,
229 + )
230 + ctx.save_for_backward(x, weight, bias, mean, rstd, z)
231 + ctx.x_shape_og = x_shape_og
232 + ctx.eps = eps
233 + ctx.group_size = group_size
234 + ctx.norm_before_gate = norm_before_gate
235 + ctx.is_rms_norm = is_rms_norm
236 + return y.reshape(x_shape_og)
237 +
238 +
239 +def layernorm_fn(x,
240 + weight,
241 + bias,
242 + z=None,
243 + eps=1e-6,
244 + group_size=None,
245 + norm_before_gate=True,
246 + is_rms_norm=False):
247 + return LayerNormFn.apply(x, weight, bias, z, eps, group_size,
248 + norm_before_gate, is_rms_norm)
249 +
250 +
251 +def rmsnorm_fn(x,
252 + weight,
253 + bias,
254 + z=None,
255 + eps=1e-6,
256 + group_size=None,
257 + norm_before_gate=True):
258 + return LayerNormFn.apply(x, weight, bias, z, eps, group_size,
259 + norm_before_gate, True)
260 +
261 +
262 +class LayerNormGated(nn.Module):
263 +
264 + def __init__(
265 + self,
266 + hidden_size,
267 + eps: float = 1e-5,
268 + group_size: Optional[int] = None,
269 + norm_before_gate: bool = True,
270 + device: Optional[torch.device] = None,
271 + dtype: Optional[torch.dtype] = None,
272 + ):
273 + """If group_size is not None, we do GroupNorm with each group having group_size elements.
274 + group_size=None is equivalent to group_size=hidden_size (i.e. there's only 1 group).
275 + """
276 +
277 + factory_kwargs = {"device": device, "dtype": dtype}
278 + super().__init__()
279 + self.eps = eps
280 + self.weight = nn.Parameter(torch.empty(hidden_size, **factory_kwargs))
281 + self.bias = nn.Parameter(torch.empty(hidden_size, **factory_kwargs))
282 + self.group_size = group_size
283 + self.norm_before_gate = norm_before_gate
284 + self.reset_parameters()
285 +
286 + def reset_parameters(self):
287 + torch.nn.init.ones_(self.weight)
288 + torch.nn.init.zeros_(self.bias)
289 +
290 + def forward(self, x, z=None):
291 + """If z is not None, we do norm(x) * silu(z) if norm_before_gate, else norm(x * silu(z))
292 + """
293 + return layernorm_fn(x,
294 + self.weight,
295 + self.bias,
296 + z=z,
297 + group_size=self.group_size,
298 + eps=self.eps,
299 + norm_before_gate=self.norm_before_gate)
300 +
301 +
302 +class RMSNormGated(nn.Module):
303 +
304 + def __init__(
305 + self,
306 + hidden_size,
307 + eps: float = 1e-5,
308 + group_size: Optional[int] = None,
309 + norm_before_gate: bool = False,
310 + device: Optional[torch.device] = None,
311 + dtype: Optional[torch.dtype] = None,
312 + ):
313 + """If group_size is not None, we do GroupNorm with each group having group_size elements.
314 + group_size=None is equivalent to group_size=hidden_size (i.e. there's only 1 group).
315 + """
316 + factory_kwargs = {"device": device, "dtype": dtype}
317 + super().__init__()
318 + self.eps = eps
319 + self.weight = nn.Parameter(torch.empty(hidden_size, **factory_kwargs))
320 + self.register_parameter("bias", None)
321 + self.group_size = group_size
322 + self.norm_before_gate = norm_before_gate
323 + self.reset_parameters()
324 +
325 + def reset_parameters(self):
326 + torch.nn.init.ones_(self.weight)
327 +
328 + def forward(self, x, z=None):
329 + """If z is not None, we do norm(x) * silu(z) if norm_before_gate, else norm(x * silu(z))
330 + """
331 + return rmsnorm_fn(x,
332 + self.weight,
333 + self.bias,
334 + z=z,
335 + eps=self.eps,
336 + group_size=self.group_size,
337 + norm_before_gate=self.norm_before_gate)
vllm/model_executor/layers/fla/ops/op.py new
+44
@@ -0,0 +1,44 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +# SPDX-FileCopyrightText: Songlin Yang, Yu Zhang
4 +#
5 +# This file contains code copied from the flash-linear-attention project.
6 +# The original source code was licensed under the MIT license and included
7 +# the following copyright notice:
8 +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang
9 +
10 +import os
11 +
12 +from vllm.triton_utils import tl, tldevice, triton
13 +
14 +if os.environ.get('FLA_USE_FAST_OPS', '0') == '1':
15 + div = tldevice.fast_dividef
16 + exp = tldevice.fast_expf
17 + log = tldevice.fast_logf
18 + log2 = tldevice.fast_log2f
19 +else:
20 +
21 + @triton.jit
22 + def div_normal(x, y):
23 + return x / y
24 +
25 + div = div_normal
26 + exp = tl.exp
27 + log = tl.log
28 + log2 = tl.log2
29 +
30 +
31 +@triton.jit
32 +def safe_exp(x):
33 + return exp(tl.where(x <= 0, x, float('-inf')))
34 +
35 +
36 +if not hasattr(tl, 'gather'):
37 +
38 + @triton.jit
39 + def gather(src, index, axis, _builder=None):
40 + # This is a fallback implementation when tl.gather is not supported
41 + # In order to pass triton compiler, there is no actual gather operation
42 + return src
43 +else:
44 + gather = tl.gather
vllm/model_executor/layers/fla/ops/solve_tril.py new
+365
@@ -0,0 +1,365 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +# SPDX-FileCopyrightText: Songlin Yang, Yu Zhang
4 +#
5 +# This file contains code copied from the flash-linear-attention project.
6 +# The original source code was licensed under the MIT license and included
7 +# the following copyright notice:
8 +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang
9 +# ruff: noqa: E501
10 +from typing import Optional
11 +
12 +import torch
13 +
14 +from vllm.triton_utils import tl, triton
15 +
16 +from .index import prepare_chunk_indices
17 +from .utils import input_guard
18 +
19 +
20 +@triton.heuristics({'IS_VARLEN': lambda args: args['cu_seqlens'] is not None})
21 +@triton.autotune(
22 + configs=[
23 + triton.Config({}, num_warps=num_warps, num_stages=num_stages)
24 + for num_warps in [1, 2, 4, 8] for num_stages in [2, 3, 4, 5]
25 + ],
26 + key=['BT'],
27 +)
28 +@triton.jit(do_not_specialize=['T'])
29 +def solve_tril_16x16_kernel(
30 + A,
31 + Ad,
32 + cu_seqlens,
33 + chunk_indices,
34 + T,
35 + H: tl.constexpr,
36 + BT: tl.constexpr,
37 + IS_VARLEN: tl.constexpr,
38 +):
39 + i_t, i_bh = tl.program_id(0), tl.program_id(1)
40 + i_b, i_h = i_bh // H, i_bh % H
41 + if IS_VARLEN:
42 + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(
43 + tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32)
44 + bos, eos = tl.load(cu_seqlens + i_n).to(
45 + tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32)
46 + T = eos - bos
47 + else:
48 + bos, eos = i_b * T, i_b * T + T
49 +
50 + A = A + (bos * H + i_h) * BT
51 + Ad = Ad + (bos * H + i_h) * 16
52 +
53 + offset = (i_t * 16) % BT
54 + p_A = tl.make_block_ptr(A, (T, BT), (H * BT, 1), (i_t * 16, offset),
55 + (16, 16), (1, 0))
56 + p_Ai = tl.make_block_ptr(Ad, (T, 16), (H * 16, 1), (i_t * 16, 0), (16, 16),
57 + (1, 0))
58 + b_A = tl.load(p_A, boundary_check=(0, 1)).to(tl.float32)
59 + b_A = -tl.where(
60 + tl.arange(0, 16)[:, None] > tl.arange(0, 16)[None, :], b_A, 0)
61 +
62 + o_i = tl.arange(0, 16)
63 + for i in range(1, min(16, T - i_t * 16)):
64 + b_a = -tl.load(A + (i_t * 16 + i) * H * BT + o_i + offset)
65 + b_a = b_a + tl.sum(b_a[:, None] * b_A, 0)
66 + mask = o_i == i
67 + b_A = tl.where(mask[:, None], b_a, b_A)
68 + b_A += o_i[:, None] == o_i[None, :]
69 + tl.store(p_Ai,
70 + b_A.to(p_Ai.dtype.element_ty, fp_downcast_rounding="rtne"),
71 + boundary_check=(0, 1))
72 +
73 +
74 +@triton.heuristics({'IS_VARLEN': lambda args: args['cu_seqlens'] is not None})
75 +@triton.autotune(
76 + configs=[
77 + triton.Config({}, num_warps=num_warps, num_stages=num_stages)
78 + for num_warps in [1, 2, 4, 8] for num_stages in [2, 3, 4, 5]
79 + ],
80 + key=['H', 'BT', 'IS_VARLEN'],
81 +)
82 +@triton.jit(do_not_specialize=['T'])
83 +def merge_16x16_to_32x32_inverse_kernel(A, Ad, Ai, cu_seqlens, chunk_indices,
84 + T, H: tl.constexpr, BT: tl.constexpr,
85 + IS_VARLEN: tl.constexpr):
86 + i_t, i_bh = tl.program_id(0), tl.program_id(1)
87 + i_b, i_h = i_bh // H, i_bh % H
88 + if IS_VARLEN:
89 + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(
90 + tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32)
91 + bos, eos = tl.load(cu_seqlens + i_n).to(
92 + tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32)
93 + T = eos - bos
94 + else:
95 + bos, eos = i_b * T, i_b * T + T
96 +
97 + A += (bos * H + i_h) * 32
98 + Ad += (bos * H + i_h) * 16
99 + Ai += (bos * H + i_h) * 32
100 +
101 + p_A_21 = tl.make_block_ptr(A, (T, 32), (H * 32, 1), (i_t * 32 + 16, 0),
102 + (16, 16), (1, 0))
103 + p_Ad_11 = tl.make_block_ptr(Ad, (T, 16), (H * 16, 1), (i_t * 32, 0),
104 + (16, 16), (1, 0))
105 + p_Ad_22 = tl.make_block_ptr(Ad, (T, 16), (H * 16, 1), (i_t * 32 + 16, 0),
106 + (16, 16), (1, 0))
107 + p_Ai_11 = tl.make_block_ptr(Ai, (T, 32), (H * 32, 1), (i_t * 32, 0),
108 + (16, 16), (1, 0))
109 + p_Ai_22 = tl.make_block_ptr(Ai, (T, 32), (H * 32, 1), (i_t * 32 + 16, 16),
110 + (16, 16), (1, 0))
111 + p_Ai_21 = tl.make_block_ptr(Ai, (T, 32), (H * 32, 1), (i_t * 32 + 16, 0),
112 + (16, 16), (1, 0))
113 +
114 + A_21 = tl.load(p_A_21, boundary_check=(0, 1)).to(tl.float32)
115 + Ai_11 = tl.load(p_Ad_11, boundary_check=(0, 1)).to(tl.float32)
116 + Ai_22 = tl.load(p_Ad_22, boundary_check=(0, 1)).to(tl.float32)
117 + Ai_21 = -tl.dot(tl.dot(Ai_22, A_21, input_precision='ieee'),
118 + Ai_11,
119 + input_precision='ieee')
120 + tl.store(p_Ai_11,
121 + Ai_11.to(p_Ai_11.dtype.element_ty, fp_downcast_rounding="rtne"),
122 + boundary_check=(0, 1))
123 + tl.store(p_Ai_22,
124 + Ai_22.to(p_Ai_22.dtype.element_ty, fp_downcast_rounding="rtne"),
125 + boundary_check=(0, 1))
126 + tl.store(p_Ai_21,
127 + Ai_21.to(p_Ai_21.dtype.element_ty, fp_downcast_rounding="rtne"),
128 + boundary_check=(0, 1))
129 +
130 +
131 +@triton.heuristics({'IS_VARLEN': lambda args: args['cu_seqlens'] is not None})
132 +@triton.autotune(
133 + configs=[
134 + triton.Config({}, num_warps=num_warps, num_stages=num_stages)
135 + for num_warps in [2, 4, 8] for num_stages in [2, 3, 4, 5]
136 + ],
137 + key=['H', 'BT', 'IS_VARLEN'],
138 +)
139 +@triton.jit(do_not_specialize=['T'])
140 +def merge_16x16_to_64x64_inverse_kernel(A, Ad, Ai, cu_seqlens, chunk_indices,
141 + T, H: tl.constexpr, BT: tl.constexpr,
142 + IS_VARLEN: tl.constexpr):
143 + i_t, i_bh = tl.program_id(0), tl.program_id(1)
144 + i_b, i_h = i_bh // H, i_bh % H
145 + if IS_VARLEN:
146 + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(
147 + tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32)
148 + bos, eos = tl.load(cu_seqlens + i_n).to(
149 + tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32)
150 + T = eos - bos
151 + else:
152 + bos, eos = i_b * T, i_b * T + T
153 +
154 + A += (bos * H + i_h) * 64
155 + Ad += (bos * H + i_h) * 16
156 + Ai += (bos * H + i_h) * 64
157 +
158 + p_A_21 = tl.make_block_ptr(A, (T, 64), (H * 64, 1), (i_t * 64 + 16, 0),
159 + (16, 16), (1, 0))
160 + p_A_32 = tl.make_block_ptr(A, (T, 64), (H * 64, 1), (i_t * 64 + 32, 16),
161 + (16, 16), (1, 0))
162 + p_A_31 = tl.make_block_ptr(A, (T, 64), (H * 64, 1), (i_t * 64 + 32, 0),
163 + (16, 16), (1, 0))
164 + p_A_43 = tl.make_block_ptr(A, (T, 64), (H * 64, 1), (i_t * 64 + 48, 32),
165 + (16, 16), (1, 0))
166 + p_A_42 = tl.make_block_ptr(A, (T, 64), (H * 64, 1), (i_t * 64 + 48, 16),
167 + (16, 16), (1, 0))
168 + p_A_41 = tl.make_block_ptr(A, (T, 64), (H * 64, 1), (i_t * 64 + 48, 0),
169 + (16, 16), (1, 0))
170 + p_Ad_11 = tl.make_block_ptr(Ad, (T, 16), (H * 16, 1), (i_t * 64, 0),
171 + (16, 16), (1, 0))
172 + p_Ad_22 = tl.make_block_ptr(Ad, (T, 16), (H * 16, 1), (i_t * 64 + 16, 0),
173 + (16, 16), (1, 0))
174 + p_Ad_33 = tl.make_block_ptr(Ad, (T, 16), (H * 16, 1), (i_t * 64 + 32, 0),
175 + (16, 16), (1, 0))
176 + p_Ad_44 = tl.make_block_ptr(Ad, (T, 16), (H * 16, 1), (i_t * 64 + 48, 0),
177 + (16, 16), (1, 0))
178 +
179 + A_21 = tl.load(p_A_21, boundary_check=(0, 1)).to(tl.float32)
180 + A_32 = tl.load(p_A_32, boundary_check=(0, 1)).to(tl.float32)
181 + A_31 = tl.load(p_A_31, boundary_check=(0, 1)).to(tl.float32)
182 + A_43 = tl.load(p_A_43, boundary_check=(0, 1)).to(tl.float32)
183 + A_42 = tl.load(p_A_42, boundary_check=(0, 1)).to(tl.float32)
184 + A_41 = tl.load(p_A_41, boundary_check=(0, 1)).to(tl.float32)
185 +
186 + Ai_11 = tl.load(p_Ad_11, boundary_check=(0, 1)).to(tl.float32)
187 + Ai_22 = tl.load(p_Ad_22, boundary_check=(0, 1)).to(tl.float32)
188 + Ai_33 = tl.load(p_Ad_33, boundary_check=(0, 1)).to(tl.float32)
189 + Ai_44 = tl.load(p_Ad_44, boundary_check=(0, 1)).to(tl.float32)
190 +
191 + Ai_21 = -tl.dot(tl.dot(Ai_22, A_21, input_precision='ieee'),
192 + Ai_11,
193 + input_precision='ieee')
194 + Ai_32 = -tl.dot(tl.dot(Ai_33, A_32, input_precision='ieee'),
195 + Ai_22,
196 + input_precision='ieee')
197 + Ai_43 = -tl.dot(tl.dot(Ai_44, A_43, input_precision='ieee'),
198 + Ai_33,
199 + input_precision='ieee')
200 +
201 + Ai_31 = -tl.dot(Ai_33,
202 + tl.dot(A_31, Ai_11, input_precision='ieee') +
203 + tl.dot(A_32, Ai_21, input_precision='ieee'),
204 + input_precision='ieee')
205 + Ai_42 = -tl.dot(Ai_44,
206 + tl.dot(A_42, Ai_22, input_precision='ieee') +
207 + tl.dot(A_43, Ai_32, input_precision='ieee'),
208 + input_precision='ieee')
209 + Ai_41 = -tl.dot(Ai_44,
210 + tl.dot(A_41, Ai_11, input_precision='ieee') +
211 + tl.dot(A_42, Ai_21, input_precision='ieee') +
212 + tl.dot(A_43, Ai_31, input_precision='ieee'),
213 + input_precision='ieee')
214 +
215 + p_Ai_11 = tl.make_block_ptr(Ai, (T, 64), (H * 64, 1), (i_t * 64, 0),
216 + (16, 16), (1, 0))
217 + p_Ai_22 = tl.make_block_ptr(Ai, (T, 64), (H * 64, 1), (i_t * 64 + 16, 16),
218 + (16, 16), (1, 0))
219 + p_Ai_33 = tl.make_block_ptr(Ai, (T, 64), (H * 64, 1), (i_t * 64 + 32, 32),
220 + (16, 16), (1, 0))
221 + p_Ai_44 = tl.make_block_ptr(Ai, (T, 64), (H * 64, 1), (i_t * 64 + 48, 48),
222 + (16, 16), (1, 0))
223 + p_Ai_21 = tl.make_block_ptr(Ai, (T, 64), (H * 64, 1), (i_t * 64 + 16, 0),
224 + (16, 16), (1, 0))
225 + p_Ai_31 = tl.make_block_ptr(Ai, (T, 64), (H * 64, 1), (i_t * 64 + 32, 0),
226 + (16, 16), (1, 0))
227 + p_Ai_32 = tl.make_block_ptr(Ai, (T, 64), (H * 64, 1), (i_t * 64 + 32, 16),
228 + (16, 16), (1, 0))
229 + p_Ai_41 = tl.make_block_ptr(Ai, (T, 64), (H * 64, 1), (i_t * 64 + 48, 0),
230 + (16, 16), (1, 0))
231 + p_Ai_42 = tl.make_block_ptr(Ai, (T, 64), (H * 64, 1), (i_t * 64 + 48, 16),
232 + (16, 16), (1, 0))
233 + p_Ai_43 = tl.make_block_ptr(Ai, (T, 64), (H * 64, 1), (i_t * 64 + 48, 32),
234 + (16, 16), (1, 0))
235 + tl.store(p_Ai_11,
236 + Ai_11.to(p_Ai_11.dtype.element_ty, fp_downcast_rounding="rtne"),
237 + boundary_check=(0, 1))
238 + tl.store(p_Ai_22,
239 + Ai_22.to(p_Ai_22.dtype.element_ty, fp_downcast_rounding="rtne"),
240 + boundary_check=(0, 1))
241 + tl.store(p_Ai_33,
242 + Ai_33.to(p_Ai_33.dtype.element_ty, fp_downcast_rounding="rtne"),
243 + boundary_check=(0, 1))
244 + tl.store(p_Ai_44,
245 + Ai_44.to(p_Ai_44.dtype.element_ty, fp_downcast_rounding="rtne"),
246 + boundary_check=(0, 1))
247 + tl.store(p_Ai_21,
248 + Ai_21.to(p_Ai_21.dtype.element_ty, fp_downcast_rounding="rtne"),
249 + boundary_check=(0, 1))
250 + tl.store(p_Ai_31,
251 + Ai_31.to(p_Ai_31.dtype.element_ty, fp_downcast_rounding="rtne"),
252 + boundary_check=(0, 1))
253 + tl.store(p_Ai_32,
254 + Ai_32.to(p_Ai_32.dtype.element_ty, fp_downcast_rounding="rtne"),
255 + boundary_check=(0, 1))
256 + tl.store(p_Ai_41,
257 + Ai_41.to(p_Ai_41.dtype.element_ty, fp_downcast_rounding="rtne"),
258 + boundary_check=(0, 1))
259 + tl.store(p_Ai_42,
260 + Ai_42.to(p_Ai_42.dtype.element_ty, fp_downcast_rounding="rtne"),
261 + boundary_check=(0, 1))
262 + tl.store(p_Ai_43,
263 + Ai_43.to(p_Ai_43.dtype.element_ty, fp_downcast_rounding="rtne"),
264 + boundary_check=(0, 1))
265 +
266 + fill_zeros = tl.zeros((16, 16), dtype=tl.float32)
267 + p_Ai_12 = tl.make_block_ptr(Ai, (T, 64), (H * 64, 1), (i_t * 64, 16),
268 + (16, 16), (1, 0))
269 + p_Ai_13 = tl.make_block_ptr(Ai, (T, 64), (H * 64, 1), (i_t * 64, 32),
270 + (16, 16), (1, 0))
271 + p_Ai_14 = tl.make_block_ptr(Ai, (T, 64), (H * 64, 1), (i_t * 64, 48),
272 + (16, 16), (1, 0))
273 + p_Ai_23 = tl.make_block_ptr(Ai, (T, 64), (H * 64, 1), (i_t * 64 + 16, 32),
274 + (16, 16), (1, 0))
275 + p_Ai_24 = tl.make_block_ptr(Ai, (T, 64), (H * 64, 1), (i_t * 64 + 16, 48),
276 + (16, 16), (1, 0))
277 + p_Ai_34 = tl.make_block_ptr(Ai, (T, 64), (H * 64, 1), (i_t * 64 + 32, 48),
278 + (16, 16), (1, 0))
279 + tl.store(p_Ai_12,
280 + fill_zeros.to(p_Ai_12.dtype.element_ty,
281 + fp_downcast_rounding="rtne"),
282 + boundary_check=(0, 1))
283 + tl.store(p_Ai_13,
284 + fill_zeros.to(p_Ai_13.dtype.element_ty,
285 + fp_downcast_rounding="rtne"),
286 + boundary_check=(0, 1))
287 + tl.store(p_Ai_14,
288 + fill_zeros.to(p_Ai_14.dtype.element_ty,
289 + fp_downcast_rounding="rtne"),
290 + boundary_check=(0, 1))
291 + tl.store(p_Ai_23,
292 + fill_zeros.to(p_Ai_23.dtype.element_ty,
293 + fp_downcast_rounding="rtne"),
294 + boundary_check=(0, 1))
295 + tl.store(p_Ai_24,
296 + fill_zeros.to(p_Ai_24.dtype.element_ty,
297 + fp_downcast_rounding="rtne"),
298 + boundary_check=(0, 1))
299 + tl.store(p_Ai_34,
300 + fill_zeros.to(p_Ai_34.dtype.element_ty,
301 + fp_downcast_rounding="rtne"),
302 + boundary_check=(0, 1))
303 +
304 +
305 +@input_guard
306 +def solve_tril(A: torch.Tensor,
307 + cu_seqlens: Optional[torch.Tensor] = None,
308 + output_dtype: torch.dtype = torch.float) -> torch.Tensor:
309 + """
310 + Compute the inverse of the lower triangular matrix
311 + A should be strictly lower triangular, i.e., A.triu() == 0.
312 +
313 + Args:
314 + A (torch.Tensor):
315 + [B, T, H, K]
316 + cu_seqlens (torch.Tensor):
317 + The cumulative sequence lengths of the input tensor.
318 + Default: None.
319 + output_dtype (torch.dtype):
320 + The dtype of the output tensor. Default: `torch.float`
321 +
322 + Returns:
323 + (I + A)^-1 with the same shape as A
324 + """
325 + assert A.shape[-1] in [16, 32, 64]
326 +
327 + B, T, H, BT = A.shape
328 + Ad = torch.empty(B,
329 + T,
330 + H,
331 + 16,
332 + device=A.device,
333 + dtype=torch.float if BT != 16 else output_dtype)
334 +
335 + chunk_indices = prepare_chunk_indices(
336 + cu_seqlens, 16) if cu_seqlens is not None else None
337 + NT = len(chunk_indices) if cu_seqlens is not None else triton.cdiv(T, 16)
338 + solve_tril_16x16_kernel[NT, B * H](
339 + A=A,
340 + Ad=Ad,
341 + cu_seqlens=cu_seqlens,
342 + chunk_indices=chunk_indices,
343 + T=T,
344 + H=H,
345 + BT=BT,
346 + )
347 + if BT == 16:
348 + return Ad
349 +
350 + Ai = torch.empty(B, T, H, BT, device=A.device, dtype=output_dtype)
351 + merge_fn = merge_16x16_to_32x32_inverse_kernel if BT == 32 else merge_16x16_to_64x64_inverse_kernel
352 + chunk_indices = prepare_chunk_indices(
353 + cu_seqlens, BT) if cu_seqlens is not None else None
354 + NT = len(chunk_indices) if cu_seqlens is not None else triton.cdiv(T, BT)
355 + merge_fn[NT, B * H](
356 + A=A,
357 + Ad=Ad,
358 + Ai=Ai,
359 + cu_seqlens=cu_seqlens,
360 + chunk_indices=chunk_indices,
361 + T=T,
362 + H=H,
363 + BT=BT,
364 + )
365 + return Ai
vllm/model_executor/layers/fla/ops/utils.py new
+180
@@ -0,0 +1,180 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +# SPDX-FileCopyrightText: Songlin Yang, Yu Zhang
4 +#
5 +# This file contains code copied from the flash-linear-attention project.
6 +# The original source code was licensed under the MIT license and included
7 +# the following copyright notice:
8 +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang
9 +# ruff: noqa: E501
10 +import contextlib
11 +import functools
12 +import logging
13 +import os
14 +from enum import Enum
15 +from typing import Any, Callable, Literal, Optional
16 +
17 +import torch
18 +
19 +from vllm.triton_utils import triton
20 +
21 +logger = logging.getLogger(__name__)
22 +
23 +COMPILER_MODE = os.getenv("FLA_COMPILER_MODE") == "1"
24 +FLA_CI_ENV = os.getenv("FLA_CI_ENV") == "1"
25 +FLA_GDN_FIX_BT = os.getenv("FLA_GDN_FIX_BT", "0") == "1"
26 +
27 +SUPPRESS_LEVEL = int(os.getenv("GDN_RECOMPUTE_SUPPRESS_LEVEL", "0"))
28 +
29 +
30 +def tensor_cache(
31 + fn: Callable[..., torch.Tensor]) -> Callable[..., torch.Tensor]:
32 + """
33 + A decorator that caches the most recent results of a function with tensor inputs.
34 +
35 + This decorator will store the output of the decorated function for the most recent set of input tensors.
36 + The cache is limited to a fixed size (default is 4). When the cache is full, the oldest entry will be removed.
37 +
38 + Args:
39 + fn (Callable[..., torch.Tensor]):
40 + The function to be decorated. It should take tensor inputs and return tensor outputs.
41 +
42 + Returns:
43 + Callable[..., torch.Tensor]:
44 + A wrapped version of the input function with single-entry caching.
45 + """
46 +
47 + cache_entries: tuple[Optional[tuple], Optional[dict], Any] = []
48 + cache_size = 4
49 +
50 + @functools.wraps(fn)
51 + def wrapper(*args: Any, **kwargs: Any) -> Any:
52 + nonlocal cache_entries, cache_size
53 + for i, entry in enumerate(cache_entries):
54 + last_args, last_kwargs, last_result = entry
55 + if len(args) == len(last_args) and len(kwargs) == len(last_kwargs) \
56 + and all(a is b for a, b in zip(args, last_args)) \
57 + and all(k in last_kwargs and v is last_kwargs[k] for k, v in kwargs.items()):
58 + cache_entries = cache_entries[:i] + cache_entries[i + 1:] + [
59 + (args, kwargs, last_result)
60 + ]
61 + return last_result
62 +
63 + result = fn(*args, **kwargs)
64 +
65 + if len(cache_entries) >= cache_size:
66 + cache_entries = cache_entries[1:]
67 + cache_entries.append((args, kwargs, result))
68 + return result
69 +
70 + return wrapper
71 +
72 +
73 +def input_guard(
74 + fn: Callable[..., torch.Tensor]) -> Callable[..., torch.Tensor]:
75 + """
76 + A decorator to make sure all input tensors are contiguous and set the device based on input tensors.
77 + """
78 +
79 + @functools.wraps(fn)
80 + def wrapper(*args, **kwargs):
81 + contiguous_args = (i if not isinstance(i, torch.Tensor) else
82 + i.contiguous() for i in args)
83 + contiguous_kwargs = {
84 + k: (v if not isinstance(v, torch.Tensor) else v.contiguous())
85 + for k, v in kwargs.items()
86 + }
87 +
88 + tensor = None
89 + for arg in args:
90 + if isinstance(arg, torch.Tensor):
91 + tensor = arg
92 + break
93 + if tensor is None:
94 + for value in kwargs.values():
95 + if isinstance(value, torch.Tensor):
96 + tensor = value
97 + break
98 +
99 + if tensor is not None:
100 + ctx = torch.cuda.device(tensor.device.index)
101 + else:
102 + ctx = contextlib.nullcontext()
103 +
104 + with ctx:
105 + return fn(*contiguous_args, **contiguous_kwargs)
106 +
107 + return wrapper
108 +
109 +
110 +@functools.cache
111 +def get_available_device() -> str:
112 + try:
113 + return triton.runtime.driver.active.get_current_target().backend
114 + except BaseException:
115 + return 'cpu'
116 +
117 +
118 +@functools.cache
119 +def _check_platform() -> Literal['nvidia', 'amd', 'intel', 'musa']:
120 + device = get_available_device()
121 + mapping = {
122 + "cuda": "nvidia",
123 + "hip": "amd",
124 + "xpu": "intel",
125 + }
126 + # return the mapped value, or the original if not found
127 + return mapping.get(device, device)
128 +
129 +
130 +# For AMD GPUs, the triton backend is 'hip', while for Nvidia GPUs, the triton backend is 'cuda'.
131 +# However, the torch backend is 'cuda' for both Nvidia and AMD GPUs.
132 +# Therefore, we need to check the triton backend to determine the actual GPU vendor.
133 +device = get_available_device() if get_available_device() != 'hip' else 'cuda'
134 +device_torch_lib = getattr(torch, device)
135 +device_platform = _check_platform()
136 +
137 +is_amd = (device_platform == 'amd')
138 +is_intel = (device_platform == 'intel')
139 +is_nvidia = (device_platform == 'nvidia')
140 +is_intel_alchemist = (is_intel
141 + and 'Intel(R) Arc(TM) A' in torch.xpu.get_device_name(0))
142 +is_nvidia_hopper = (is_nvidia
143 + and ('NVIDIA H' in torch.cuda.get_device_name(0)
144 + or torch.cuda.get_device_capability()[0] >= 9))
145 +use_cuda_graph = (is_nvidia
146 + and os.environ.get('FLA_USE_CUDA_GRAPH', '0') == '1')
147 +
148 +
149 +def get_all_max_shared_mem():
150 + try:
151 + return [
152 + triton.runtime.driver.active.utils.get_device_properties(i)
153 + ['max_shared_mem'] for i in range(device_torch_lib.device_count())
154 + ]
155 + except BaseException:
156 + return [-1]
157 +
158 +
159 +class Backend(Enum):
160 + ADA = 101376 # RTX 4090
161 + AMPERE = 166912 # A100
162 + HOPPER = 232448 # H100
163 + DEFAULT = 102400 # Default
164 +
165 + @classmethod
166 + def get_shared_memory(cls, arch: str) -> int:
167 + try:
168 + return cls[arch.upper()].value
169 + except KeyError:
170 + return cls.DEFAULT.value
171 +
172 +
173 +@functools.cache
174 +def check_shared_mem(arch: str = "none", tensor_idx: int = 0) -> bool:
175 + try:
176 + device_shared_mem_list = get_all_max_shared_mem()
177 + max_shared_memory = device_shared_mem_list[tensor_idx]
178 + return max_shared_memory >= Backend.get_shared_memory(arch)
179 + except Exception:
180 + return False
vllm/model_executor/layers/fla/ops/wy_fast.py new
+114
@@ -0,0 +1,114 @@
1 +# SPDX-License-Identifier: Apache-2.0
2 +# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 +# SPDX-FileCopyrightText: Songlin Yang, Yu Zhang
4 +#
5 +# This file contains code copied from the flash-linear-attention project.
6 +# The original source code was licensed under the MIT license and included
7 +# the following copyright notice:
8 +# Copyright (c) 2023-2025, Songlin Yang, Yu Zhang
9 +
10 +# ruff: noqa: E501
11 +from typing import Optional
12 +
13 +import torch
14 +
15 +from vllm.triton_utils import tl, triton
16 +
17 +from .index import prepare_chunk_indices
18 +
19 +
20 +@triton.heuristics({'IS_VARLEN': lambda args: args['cu_seqlens'] is not None})
21 +@triton.autotune(
22 + configs=[
23 + triton.Config({}, num_warps=num_warps, num_stages=num_stages)
24 + for num_warps in [2, 4, 8] for num_stages in [2, 3, 4]
25 + ],
26 + key=['H', 'K', 'V', 'BT', 'BK', 'BV', 'IS_VARLEN'],
27 +)
28 +@triton.jit(do_not_specialize=['T'])
29 +def recompute_w_u_fwd_kernel(k, v, beta, w, u, A, g, cu_seqlens, chunk_indices,
30 + T, H: tl.constexpr, Hg: tl.constexpr,
31 + K: tl.constexpr, V: tl.constexpr,
32 + BT: tl.constexpr, BK: tl.constexpr,
33 + BV: tl.constexpr, IS_VARLEN: tl.constexpr):
34 + i_t, i_bh = tl.program_id(0), tl.program_id(1)
35 + i_b, i_h = i_bh // H, i_bh % H
36 + if IS_VARLEN:
37 + i_n, i_t = tl.load(chunk_indices + i_t * 2).to(
38 + tl.int32), tl.load(chunk_indices + i_t * 2 + 1).to(tl.int32)
39 + bos, eos = tl.load(cu_seqlens + i_n).to(
40 + tl.int32), tl.load(cu_seqlens + i_n + 1).to(tl.int32)
41 + T = eos - bos
42 + else:
43 + bos, eos = i_b * T, i_b * T + T
44 + p_beta = tl.make_block_ptr(beta + bos * H + i_h, (T, ), (H, ),
45 + (i_t * BT, ), (BT, ), (0, ))
46 + p_g = tl.make_block_ptr(g + (bos * H + i_h), (T, ), (H, ), (i_t * BT, ),
47 + (BT, ), (0, ))
48 + p_A = tl.make_block_ptr(A + (bos * H + i_h) * BT, (T, BT), (H * BT, 1),
49 + (i_t * BT, 0), (BT, BT), (1, 0))
50 + b_beta = tl.load(p_beta, boundary_check=(0, ))
51 + b_A = tl.load(p_A, boundary_check=(0, 1))
52 + b_g = tl.exp(tl.load(p_g, boundary_check=(0, )))
53 +
54 + for i_v in range(tl.cdiv(V, BV)):
55 + p_v = tl.make_block_ptr(v + (bos * H + i_h) * V, (T, V), (H * V, 1),
56 + (i_t * BT, i_v * BV), (BT, BV), (1, 0))
57 + p_u = tl.make_block_ptr(u + (bos * H + i_h) * V, (T, V), (H * V, 1),
58 + (i_t * BT, i_v * BV), (BT, BV), (1, 0))
59 + b_v = tl.load(p_v, boundary_check=(0, 1))
60 + b_vb = (b_v * b_beta[:, None]).to(b_v.dtype)
61 + b_u = tl.dot(b_A, b_vb, allow_tf32=False)
62 + tl.store(p_u, b_u.to(p_u.dtype.element_ty), boundary_check=(0, 1))
63 +
64 + for i_k in range(tl.cdiv(K, BK)):
65 + p_k = tl.make_block_ptr(k + (bos * Hg + i_h // (H // Hg)) * K, (T, K),
66 + (Hg * K, 1), (i_t * BT, i_k * BK), (BT, BK),
67 + (1, 0))
68 + p_w = tl.make_block_ptr(w + (bos * H + i_h) * K, (T, K), (H * K, 1),
69 + (i_t * BT, i_k * BK), (BT, BK), (1, 0))
70 + b_k = tl.load(p_k, boundary_check=(0, 1))
71 + b_kb = (b_k * b_beta[:, None] * b_g[:, None]).to(b_k.dtype)
72 + b_w = tl.dot(b_A, b_kb)
73 + tl.store(p_w, b_w.to(p_w.dtype.element_ty), boundary_check=(0, 1))
74 +
75 +
76 +def recompute_w_u_fwd(
77 + k: torch.Tensor,
78 + v: torch.Tensor,
79 + beta: torch.Tensor,
80 + g_cumsum: torch.Tensor,
81 + A: torch.Tensor,
82 + cu_seqlens: Optional[torch.LongTensor],
83 +) -> tuple[torch.Tensor, torch.Tensor]:
84 + B, T, Hg, K, V = *k.shape, v.shape[-1]
85 + H = v.shape[-2]
86 + BT = A.shape[-1]
87 +
88 + chunk_indices = prepare_chunk_indices(
89 + cu_seqlens, BT) if cu_seqlens is not None else None
90 + NT = triton.cdiv(T, BT) if cu_seqlens is None else len(chunk_indices)
91 + BK = 64
92 + BV = 64
93 + u = torch.empty_like(v)
94 + w = k.new_empty(B, T, H, K)
95 + recompute_w_u_fwd_kernel[(NT, B * H)](
96 + k=k,
97 + v=v,
98 + beta=beta,
99 + w=w,
100 + u=u,
101 + A=A,
102 + g=g_cumsum,
103 + cu_seqlens=cu_seqlens,
104 + chunk_indices=chunk_indices,
105 + T=T,
106 + H=H,
107 + Hg=Hg,
108 + K=K,
109 + V=V,
110 + BT=BT,
111 + BK=BK,
112 + BV=BV,
113 + )
114 + return w, u
vllm/triton_utils/__init__.py
+3 -1
@@ -7,8 +7,10 @@ from vllm.triton_utils.importing import (HAS_TRITON, TritonLanguagePlaceholder,
7 if HAS_TRITON:
8 import triton
9 import triton.language as tl
10 + import triton.language.extra.libdevice as tldevice
11 else:
12 triton = TritonPlaceholder()
13 tl = TritonLanguagePlaceholder()
14 + tldevice = TritonLanguagePlaceholder()
15
14 -__all__ = ["HAS_TRITON", "triton", "tl"]
16 +__all__ = ["HAS_TRITON", "triton", "tl", "tldevice"]