Kernels
danieldk HF Staff commited on
Commit
7061931
·
verified ·
1 Parent(s): 37a6646

Build uploaded using `kernels`.

Browse files
.gitattributes CHANGED
@@ -48,3 +48,4 @@ build/torch210-cxx11-cu130-x86_64-linux/_sage_attention_6e51d70.abi3.so filter=l
48
  build/torch29-cxx11-cu126-x86_64-linux/_sage_attention_6e51d70.abi3.so filter=lfs diff=lfs merge=lfs -text
49
  build/torch29-cxx11-cu128-x86_64-linux/_sage_attention_6e51d70.abi3.so filter=lfs diff=lfs merge=lfs -text
50
  build/torch29-cxx11-cu130-x86_64-linux/_sage_attention_6e51d70.abi3.so filter=lfs diff=lfs merge=lfs -text
 
 
48
  build/torch29-cxx11-cu126-x86_64-linux/_sage_attention_6e51d70.abi3.so filter=lfs diff=lfs merge=lfs -text
49
  build/torch29-cxx11-cu128-x86_64-linux/_sage_attention_6e51d70.abi3.so filter=lfs diff=lfs merge=lfs -text
50
  build/torch29-cxx11-cu130-x86_64-linux/_sage_attention_6e51d70.abi3.so filter=lfs diff=lfs merge=lfs -text
51
+ build/torch210-cu128-x86_64-windows/sage_attention/_sage_attention_ac695bf.pyd filter=lfs diff=lfs merge=lfs -text
build/torch210-cu128-x86_64-windows/metadata.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "version": 1,
3
+ "python-depends": []
4
+ }
build/torch210-cu128-x86_64-windows/sage_attention/__init__.py ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from .quant import per_block_int8, per_warp_int8, sub_mean, per_channel_fp8
2
+ from .core import sageattn
3
+
4
+
5
+ __all__ = [
6
+ "per_block_int8",
7
+ "per_warp_int8",
8
+ "sub_mean",
9
+ "per_channel_fp8",
10
+ "sageattn",
11
+ ]
build/torch210-cu128-x86_64-windows/sage_attention/_ops.py ADDED
@@ -0,0 +1,9 @@
 
 
 
 
 
 
 
 
 
 
1
+ import torch
2
+ from . import _sage_attention_ac695bf
3
+ ops = torch.ops._sage_attention_ac695bf
4
+
5
+ def add_op_namespace_prefix(op_name: str):
6
+ """
7
+ Prefix op by namespace.
8
+ """
9
+ return f"_sage_attention_ac695bf::{op_name}"
build/torch210-cu128-x86_64-windows/sage_attention/_sage_attention_ac695bf.pyd ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7ee5ee9b9eb4564de25d92ddefdda1ea6312a2bca08ad55919c907351b9bdec3
3
+ size 24102400
build/torch210-cu128-x86_64-windows/sage_attention/core.py ADDED
@@ -0,0 +1,979 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Copyright (c) 2024 by SageAttention team.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ """
16
+
17
+ import torch
18
+ import torch.nn.functional as F
19
+
20
+ from ._ops import ops
21
+
22
+
23
+ from .quant import per_warp_int8 as per_warp_int8_cuda
24
+ from .quant import sub_mean
25
+ from .quant import per_channel_fp8
26
+ from .quant_per_thread import per_thread_int8 as per_thread_int8_triton
27
+
28
+ try:
29
+ from . import sm80_compile
30
+ SM80_ENABLED = True
31
+ except:
32
+ SM80_ENABLED = False
33
+
34
+ try:
35
+ from . import sm89_compile
36
+ SM89_ENABLED = True
37
+ except:
38
+ SM89_ENABLED = False
39
+
40
+ try:
41
+ from . import sm90_compile
42
+ SM90_ENABLED = True
43
+ except:
44
+ SM90_ENABLED = False
45
+
46
+ from typing import Any, List, Literal, Optional, Tuple, Union
47
+ import warnings
48
+
49
+
50
+ import subprocess
51
+ import re
52
+
53
+
54
+ def get_cuda_version():
55
+ try:
56
+ output = subprocess.check_output(["nvcc", "--version"]).decode()
57
+ match = re.search(r"release (\d+)\.(\d+)", output)
58
+ if match:
59
+ major, minor = int(match.group(1)), int(match.group(2))
60
+ return major, minor
61
+ except Exception as e:
62
+ print("Failed to get CUDA version:", e)
63
+ return None, None
64
+
65
+
66
+ def get_cuda_arch_versions():
67
+ cuda_archs = []
68
+ for i in range(torch.cuda.device_count()):
69
+ major, minor = torch.cuda.get_device_capability(i)
70
+ cuda_archs.append(f"sm{major}{minor}")
71
+ return cuda_archs
72
+
73
+
74
+ def sageattn(
75
+ q: torch.Tensor,
76
+ k: torch.Tensor,
77
+ v: torch.Tensor,
78
+ tensor_layout: str = "HND",
79
+ is_causal: bool = False,
80
+ sm_scale: Optional[float] = None,
81
+ return_lse: bool = False,
82
+ **kwargs: Any,
83
+ ):
84
+ """
85
+ Automatically selects the appropriate implementation of the SageAttention kernel based on the GPU compute capability.
86
+
87
+ Parameters
88
+ ----------
89
+ q : torch.Tensor
90
+ The query tensor. Shape:
91
+ - If `tensor_layout` is "HND": ``[batch_size, num_qo_heads, qo_len, head_dim]``.
92
+ - If `tensor_layout` is "NHD": ``[batch_size, qo_len, num_qo_heads, head_dim]``.
93
+
94
+ k : torch.Tensor
95
+ The key tensor. Shape:
96
+ - If `tensor_layout` is "HND": ``[batch_size, num_kv_heads, kv_len, head_dim]``.
97
+ - If `tensor_layout` is "NHD": ``[batch_size, kv_len, num_kv_heads, head_dim]``.
98
+
99
+ v : torch.Tensor
100
+ The value tensor. Shape:
101
+ - If `tensor_layout` is "HND": ``[batch_size, num_kv_heads, kv_len, head_dim]``.
102
+ - If `tensor_layout` is "NHD": ``[batch_size, kv_len, num_kv_heads, head_dim]``.
103
+
104
+ tensor_layout : str
105
+ The tensor layout, either "HND" or "NHD".
106
+ Default: "HND".
107
+
108
+ is_causal : bool
109
+ Whether to apply causal mask to the attention matrix. Only applicable when qo_len == kv_len.
110
+ Default: False.
111
+
112
+ sm_scale : Optional[float]
113
+ The scale used in softmax, if not provided, will be set to ``1.0 / sqrt(head_dim)``.
114
+
115
+ return_lse : bool
116
+ Whether to return the log sum of the exponentiated attention weights. Used for cases like Ring Attention.
117
+ Default: False.
118
+
119
+ Returns
120
+ -------
121
+ torch.Tensor
122
+ The output tensor. Shape:
123
+ - If `tensor_layout` is "HND": ``[batch_size, num_qo_heads, qo_len, head_dim]``.
124
+ - If `tensor_layout` is "NHD": ``[batch_size, qo_len, num_qo_heads, head_dim]``.
125
+
126
+ torch.Tensor
127
+ The logsumexp of each row of the matrix QK^T * scaling (e.g., log of the softmax normalization factor).
128
+ Shape: ``[batch_size, num_qo_heads, qo_len]``.
129
+ Only returned if `return_lse` is True.
130
+
131
+ Note
132
+ ----
133
+ - ``num_qo_heads`` must be divisible by ``num_kv_heads``.
134
+ - The tensors `q`, `k`, and `v` must have the dtype ``torch.float16`` or ``torch.bfloat16``
135
+ - All tensors must be on the same cuda device.
136
+ """
137
+ arch = get_cuda_arch_versions()[q.device.index]
138
+ if arch == "sm80":
139
+ return sageattn_qk_int8_pv_fp16_cuda(
140
+ q,
141
+ k,
142
+ v,
143
+ tensor_layout=tensor_layout,
144
+ is_causal=is_causal,
145
+ sm_scale=sm_scale,
146
+ return_lse=return_lse,
147
+ pv_accum_dtype="fp32",
148
+ )
149
+ elif arch == "sm89":
150
+ return sageattn_qk_int8_pv_fp8_cuda(
151
+ q,
152
+ k,
153
+ v,
154
+ tensor_layout=tensor_layout,
155
+ is_causal=is_causal,
156
+ sm_scale=sm_scale,
157
+ return_lse=return_lse,
158
+ pv_accum_dtype="fp32+fp16",
159
+ )
160
+ elif arch == "sm90":
161
+ return sageattn_qk_int8_pv_fp8_cuda_sm90(
162
+ q,
163
+ k,
164
+ v,
165
+ tensor_layout=tensor_layout,
166
+ is_causal=is_causal,
167
+ sm_scale=sm_scale,
168
+ return_lse=return_lse,
169
+ pv_accum_dtype="fp32+fp32",
170
+ )
171
+ elif arch == "sm120":
172
+ return sageattn_qk_int8_pv_fp8_cuda(
173
+ q,
174
+ k,
175
+ v,
176
+ tensor_layout=tensor_layout,
177
+ is_causal=is_causal,
178
+ qk_quant_gran="per_warp",
179
+ sm_scale=sm_scale,
180
+ return_lse=return_lse,
181
+ pv_accum_dtype="fp32+fp16",
182
+ ) # sm120 has accurate fp32 accumulator for fp8 mma and triton kernel is currently not usable on sm120.
183
+ else:
184
+ raise ValueError(f"Unsupported CUDA architecture: {arch}")
185
+
186
+ def sageattn_qk_int8_pv_fp16_cuda(
187
+ q: torch.Tensor,
188
+ k: torch.Tensor,
189
+ v: torch.Tensor,
190
+ tensor_layout: str = "HND",
191
+ is_causal: bool = False,
192
+ qk_quant_gran: str = "per_thread",
193
+ sm_scale: Optional[float] = None,
194
+ pv_accum_dtype: str = "fp32",
195
+ smooth_k: bool = True,
196
+ smooth_v: bool = False,
197
+ return_lse: bool = False,
198
+ **kwargs: Any,
199
+ ) -> torch.Tensor:
200
+ """
201
+ SageAttention with INT8 quantization for Q and K, FP16 PV with FP16/FP32 accumulation, implemented using CUDA.
202
+
203
+ Parameters
204
+ ----------
205
+ q : torch.Tensor
206
+ The query tensor. Shape:
207
+ - If `tensor_layout` is "HND": ``[batch_size, num_qo_heads, qo_len, head_dim]``.
208
+ - If `tensor_layout` is "NHD": ``[batch_size, qo_len, num_qo_heads, head_dim]``.
209
+
210
+ k : torch.Tensor
211
+ The key tensor. Shape:
212
+ - If `tensor_layout` is "HND": ``[batch_size, num_kv_heads, kv_len, head_dim]``.
213
+ - If `tensor_layout` is "NHD": ``[batch_size, kv_len, num_kv_heads, head_dim]``.
214
+
215
+ v : torch.Tensor
216
+ The value tensor. Shape:
217
+ - If `tensor_layout` is "HND": ``[batch_size, num_kv_heads, kv_len, head_dim]``.
218
+ - If `tensor_layout` is "NHD": ``[batch_size, kv_len, num_kv_heads, head_dim]``.
219
+
220
+ tensor_layout : str
221
+ The tensor layout, either "HND" or "NHD".
222
+ Default: "HND".
223
+
224
+ is_causal : bool
225
+ Whether to apply causal mask to the attention matrix. Only applicable when qo_len == kv_len.
226
+ Default: False.
227
+
228
+ qk_quant_gran : str
229
+ The granularity of quantization for Q and K, either "per_warp" or "per_thread".
230
+ Default: "per_thread".
231
+
232
+ sm_scale : Optional[float]
233
+ The scale used in softmax, if not provided, will be set to ``1.0 / sqrt(head_dim)``.
234
+
235
+ pv_accum_dtype : str
236
+ The dtype of the accumulation of the product of the value tensor and the attention weights, either "fp16", "fp16+fp32" or "fp32".
237
+ - "fp16": PV accumulation is done in fully in FP16. This is the fastest option but may lead to numerical instability. `smooth_v` option will increase the accuracy in cases when the value tensor has a large bias (like in CogVideoX-2b).
238
+ - "fp32": PV accumulation is done in FP32. This is the most accurate option but may be slower than "fp16" due to CUDA core overhead.
239
+ - "fp16+fp32": PV accumulation is done in FP16, but added to a FP32 buffer every few iterations. This offers a balance between speed and accuracy.
240
+ Default: "fp32".
241
+
242
+ smooth_k : bool
243
+ Whether to smooth the key tensor by subtracting the mean along the sequence dimension.
244
+ Default: True.
245
+
246
+ smooth_v : bool
247
+ Whether to smooth the value tensor by subtracting the mean along the sequence dimension.
248
+ smooth_v will be ignored if pv_accum_dtype is "fp32" or "fp16+fp32".
249
+ Default: False.
250
+
251
+ return_lse : bool
252
+ Whether to return the log sum of the exponentiated attention weights. Used for cases like Ring Attention.
253
+ Default: False.
254
+
255
+ Returns
256
+ -------
257
+ torch.Tensor
258
+ The output tensor. Shape:
259
+ - If `tensor_layout` is "HND": ``[batch_size, num_qo_heads, qo_len, head_dim]``.
260
+ - If `tensor_layout` is "NHD": ``[batch_size, qo_len, num_qo_heads, head_dim]``.
261
+
262
+ torch.Tensor
263
+ The logsumexp of each row of the matrix QK^T * scaling (e.g., log of the softmax normalization factor).
264
+ Shape: ``[batch_size, num_qo_heads, qo_len]``.
265
+ Only returned if `return_lse` is True.
266
+
267
+ Note
268
+ ----
269
+ - ``num_qo_heads`` must be divisible by ``num_kv_heads``.
270
+ - The tensors `q`, `k`, and `v` must have the dtype ``torch.float16`` or ``torch.bfloat16``
271
+ - All tensors must be on the same cuda device.
272
+ - `smooth_k` will introduce slight overhead but will improve the accuracy under most circumstances.
273
+ """
274
+
275
+ dtype = q.dtype
276
+ assert q.is_cuda, "Input tensors must be on cuda."
277
+ assert dtype in [torch.float16, torch.bfloat16], (
278
+ "Input tensors must be in dtype of torch.float16 or torch.bfloat16"
279
+ )
280
+ assert qk_quant_gran in ["per_warp", "per_thread"], (
281
+ "qk_quant_gran must be either 'per_warp' or 'per_thread'."
282
+ )
283
+ assert q.device == k.device == v.device, "All tensors must be on the same device."
284
+ assert q.dtype == k.dtype == v.dtype, "All tensors must have the same dtype."
285
+
286
+ # FIXME(DefTruth): make sage attention work compatible with distributed
287
+ # env, for example, xDiT which launch by torchrun. Without this workaround,
288
+ # sage attention will run into illegal memory access error after first
289
+ # inference step in distributed env for multi gpus inference. This small
290
+ # workaround also make sage attention work compatible with torch.compile
291
+ # through non-fullgraph compile mode.
292
+ torch.cuda.set_device(v.device)
293
+
294
+ _tensor_layout = 0 if tensor_layout == "NHD" else 1
295
+ _is_caual = 1 if is_causal else 0
296
+ _qk_quant_gran = 3 if qk_quant_gran == "per_thread" else 2
297
+ _return_lse = 1 if return_lse else 0
298
+
299
+ head_dim_og = q.size(-1)
300
+
301
+ if head_dim_og < 64:
302
+ q = torch.nn.functional.pad(q, (0, 64 - head_dim_og))
303
+ k = torch.nn.functional.pad(k, (0, 64 - head_dim_og))
304
+ v = torch.nn.functional.pad(v, (0, 64 - head_dim_og))
305
+ elif head_dim_og > 64 and head_dim_og < 128:
306
+ q = torch.nn.functional.pad(q, (0, 128 - head_dim_og))
307
+ k = torch.nn.functional.pad(k, (0, 128 - head_dim_og))
308
+ v = torch.nn.functional.pad(v, (0, 128 - head_dim_og))
309
+ elif head_dim_og > 128:
310
+ raise ValueError(f"Unsupported head_dim: {head_dim_og}")
311
+
312
+ # assert last dim is contiguous
313
+ assert q.stride(-1) == 1 and k.stride(-1) == 1 and v.stride(-1) == 1, (
314
+ "Last dim of qkv must be contiguous."
315
+ )
316
+
317
+ if sm_scale is None:
318
+ sm_scale = head_dim_og**-0.5
319
+
320
+ seq_dim = 1 if _tensor_layout == 0 else 2
321
+ nh_dim = 2 if _tensor_layout == 0 else 1
322
+
323
+ if smooth_k:
324
+ km = k.mean(dim=seq_dim, keepdim=True)
325
+ nqheads = q.size(2)
326
+ nkheads = k.size(2)
327
+ q_per_kv_heads = nqheads // nkheads
328
+ if q_per_kv_heads > 1:
329
+ # nheads_k => nheads_q
330
+ km_broadcast = torch.repeat_interleave(km, q_per_kv_heads, dim=nh_dim)
331
+ else:
332
+ km_broadcast = km
333
+ if return_lse:
334
+ if tensor_layout == "NHD":
335
+ lse_correction = (
336
+ torch.matmul(
337
+ q.transpose(1, 2), km_broadcast.transpose(1, 2).transpose(2, 3)
338
+ )
339
+ .squeeze(-1)
340
+ .to(torch.float32)
341
+ )
342
+ else:
343
+ lse_correction = (
344
+ torch.matmul(q, km_broadcast.transpose(2, 3))
345
+ .squeeze(-1)
346
+ .to(torch.float32)
347
+ )
348
+ else:
349
+ km = None
350
+
351
+ if qk_quant_gran == "per_warp":
352
+ q_int8, q_scale, k_int8, k_scale = per_warp_int8_cuda(
353
+ q,
354
+ k,
355
+ km,
356
+ tensor_layout=tensor_layout,
357
+ BLKQ=128,
358
+ WARPQ=(16 if (q.size(-1) == 128 and pv_accum_dtype == "fp16+fp32") else 32),
359
+ BLKK=64,
360
+ )
361
+ elif qk_quant_gran == "per_thread":
362
+ q_int8, q_scale, k_int8, k_scale = per_thread_int8_triton(
363
+ q,
364
+ k,
365
+ km,
366
+ tensor_layout=tensor_layout,
367
+ BLKQ=128,
368
+ WARPQ=(16 if (q.size(-1) == 128 and pv_accum_dtype == "fp16+fp32") else 32),
369
+ BLKK=64,
370
+ WARPK=64,
371
+ )
372
+
373
+ o = torch.empty(q.size(), dtype=dtype, device=q.device)
374
+
375
+ if pv_accum_dtype in ["fp32", "fp16+fp32"] and smooth_v:
376
+ warnings.warn(f"pv_accum_dtype is {pv_accum_dtype}, smooth_v will be ignored.")
377
+ smooth_v = False
378
+
379
+ if pv_accum_dtype == "fp32":
380
+ v = v.to(torch.float16)
381
+ lse = sm80_compile.qk_int8_sv_f16_accum_f32_attn(
382
+ q_int8,
383
+ k_int8,
384
+ v,
385
+ o,
386
+ q_scale,
387
+ k_scale,
388
+ _tensor_layout,
389
+ _is_caual,
390
+ _qk_quant_gran,
391
+ sm_scale,
392
+ _return_lse,
393
+ )
394
+ elif pv_accum_dtype == "fp16":
395
+ if smooth_v:
396
+ smoothed_v, vm = sub_mean(v, tensor_layout=tensor_layout)
397
+ lse = sm80_compile.qk_int8_sv_f16_accum_f16_fuse_v_mean_attn(
398
+ q_int8,
399
+ k_int8,
400
+ smoothed_v,
401
+ o,
402
+ q_scale,
403
+ k_scale,
404
+ vm,
405
+ _tensor_layout,
406
+ _is_caual,
407
+ _qk_quant_gran,
408
+ sm_scale,
409
+ _return_lse,
410
+ )
411
+ else:
412
+ v = v.to(torch.float16)
413
+ lse = sm80_compile.qk_int8_sv_f16_accum_f16_attn(
414
+ q_int8,
415
+ k_int8,
416
+ v,
417
+ o,
418
+ q_scale,
419
+ k_scale,
420
+ _tensor_layout,
421
+ _is_caual,
422
+ _qk_quant_gran,
423
+ sm_scale,
424
+ _return_lse,
425
+ )
426
+ elif pv_accum_dtype == "fp16+fp32":
427
+ v = v.to(torch.float16)
428
+ lse = sm80_compile.qk_int8_sv_f16_accum_f16_attn_inst_buf(
429
+ q_int8,
430
+ k_int8,
431
+ v,
432
+ o,
433
+ q_scale,
434
+ k_scale,
435
+ _tensor_layout,
436
+ _is_caual,
437
+ _qk_quant_gran,
438
+ sm_scale,
439
+ _return_lse,
440
+ )
441
+ else:
442
+ raise ValueError(f"Unsupported pv_accum_dtype: {pv_accum_dtype}")
443
+
444
+ o = o[..., :head_dim_og]
445
+
446
+ if return_lse:
447
+ return (
448
+ o,
449
+ lse / 1.44269504 + lse_correction * sm_scale
450
+ if smooth_k
451
+ else lse / 1.44269504,
452
+ )
453
+ else:
454
+ return o
455
+
456
+ def sageattn_qk_int8_pv_fp8_cuda(
457
+ q: torch.Tensor,
458
+ k: torch.Tensor,
459
+ v: torch.Tensor,
460
+ tensor_layout: str = "HND",
461
+ is_causal: bool = False,
462
+ qk_quant_gran: str = "per_thread",
463
+ sm_scale: Optional[float] = None,
464
+ pv_accum_dtype: str = "fp32+fp16",
465
+ smooth_k: bool = True,
466
+ smooth_v: bool = False,
467
+ return_lse: bool = False,
468
+ **kwargs: Any,
469
+ ) -> torch.Tensor:
470
+ """
471
+ SageAttention with INT8 quantization for Q and K, FP8 PV with FP32 accumulation, implemented using CUDA.
472
+
473
+ Parameters
474
+ ----------
475
+ q : torch.Tensor
476
+ The query tensor. Shape:
477
+ - If `tensor_layout` is "HND": ``[batch_size, num_qo_heads, qo_len, head_dim]``.
478
+ - If `tensor_layout` is "NHD": ``[batch_size, qo_len, num_qo_heads, head_dim]``.
479
+
480
+ k : torch.Tensor
481
+ The key tensor. Shape:
482
+ - If `tensor_layout` is "HND": ``[batch_size, num_kv_heads, kv_len, head_dim]``.
483
+ - If `tensor_layout` is "NHD": ``[batch_size, kv_len, num_kv_heads, head_dim]``.
484
+
485
+ v : torch.Tensor
486
+ The value tensor. Shape:
487
+ - If `tensor_layout` is "HND": ``[batch_size, num_kv_heads, kv_len, head_dim]``.
488
+ - If `tensor_layout` is "NHD": ``[batch_size, kv_len, num_kv_heads, head_dim]``.
489
+
490
+ tensor_layout : str
491
+ The tensor layout, either "HND" or "NHD".
492
+ Default: "HND".
493
+
494
+ is_causal : bool
495
+ Whether to apply causal mask to the attention matrix. Only applicable when qo_len == kv_len.
496
+ Default: False.
497
+
498
+ qk_quant_gran : str
499
+ The granularity of quantization for Q and K, either "per_warp" or "per_thread".
500
+ Default: "per_thread".
501
+
502
+ sm_scale : Optional[float]
503
+ The scale used in softmax, if not provided, will be set to ``1.0 / sqrt(head_dim)``.
504
+
505
+ pv_accum_dtype : str
506
+ The dtype of the accumulation of the product of the value tensor and the attention weights, either "fp32" or "fp32+fp32".
507
+ - "fp32": PV accumulation is done in fully in FP32. However, due to the hardware issue, there are only 22 valid bits in the FP32 accumulator.
508
+ - "fp32+fp32": PV accumulation is done in FP32 (actually FP22), but added to a FP32 buffer every few iterations. This offers a balance between speed and accuracy.
509
+ Default: "fp32+fp32".
510
+
511
+ smooth_k : bool
512
+ Whether to smooth the key tensor by subtracting the mean along the sequence dimension.
513
+ Default: True.
514
+
515
+ smooth_v : bool
516
+ Whether to smooth the value tensor by subtracting the mean along the sequence dimension.
517
+ smooth_v will be ignored if pv_accum_dtype is "fp32+fp32".
518
+ Default: False.
519
+
520
+ return_lse : bool
521
+ Whether to return the log sum of the exponentiated attention weights. Used for cases like Ring Attention.
522
+ Default: False.
523
+
524
+ Returns
525
+ -------
526
+ torch.Tensor
527
+ The output tensor. Shape:
528
+ - If `tensor_layout` is "HND": ``[batch_size, num_qo_heads, qo_len, head_dim]``.
529
+ - If `tensor_layout` is "NHD": ``[batch_size, qo_len, num_qo_heads, head_dim]``.
530
+
531
+ torch.Tensor
532
+ The logsumexp of each row of the matrix QK^T * scaling (e.g., log of the softmax normalization factor).
533
+ Shape: ``[batch_size, num_qo_heads, qo_len]``.
534
+ Only returned if `return_lse` is True.
535
+
536
+ Note
537
+ ----
538
+ - ``num_qo_heads`` must be divisible by ``num_kv_heads``.
539
+ - The tensors `q`, `k`, and `v` must have the dtype ``torch.float16`` or ``torch.bfloat16``
540
+ - All tensors must be on the same cuda device.
541
+ - `smooth_k` will introduce slight overhead but will improve the accuracy under most circumstances.
542
+ """
543
+
544
+ dtype = q.dtype
545
+ assert q.is_cuda, "Input tensors must be on cuda."
546
+ assert dtype in [torch.float16, torch.bfloat16], (
547
+ "Input tensors must be in dtype of torch.float16 or torch.bfloat16"
548
+ )
549
+ assert qk_quant_gran in ["per_warp", "per_thread"], (
550
+ "qk_quant_gran must be either 'per_warp' or 'per_thread'."
551
+ )
552
+ assert q.device == k.device == v.device, "All tensors must be on the same device."
553
+ assert q.dtype == k.dtype == v.dtype, "All tensors must have the same dtype."
554
+
555
+ # cuda_major_version, cuda_minor_version = get_cuda_version()
556
+ # if(cuda_major_version, cuda_minor_version) < (12, 8) and pv_accum_dtype == 'fp32+fp16':
557
+ # warnings.warn("cuda version < 12.8, change pv_accum_dtype to 'fp32+fp32'")
558
+ # pv_accum_dtype = 'fp32+fp32'
559
+
560
+ # FIXME(DefTruth): make sage attention work compatible with distributed
561
+ # env, for example, xDiT which launch by torchrun. Without this workaround,
562
+ # sage attention will run into illegal memory access error after first
563
+ # inference step in distributed env for multi gpus inference. This small
564
+ # workaround also make sage attention work compatible with torch.compile
565
+ # through non-fullgraph compile mode.
566
+ torch.cuda.set_device(v.device)
567
+
568
+ _tensor_layout = 0 if tensor_layout == "NHD" else 1
569
+ _is_caual = 1 if is_causal else 0
570
+ _qk_quant_gran = 3 if qk_quant_gran == "per_thread" else 2
571
+ _return_lse = 1 if return_lse else 0
572
+
573
+ head_dim_og = q.size(-1)
574
+
575
+ if head_dim_og < 64:
576
+ q = torch.nn.functional.pad(q, (0, 64 - head_dim_og))
577
+ k = torch.nn.functional.pad(k, (0, 64 - head_dim_og))
578
+ v = torch.nn.functional.pad(v, (0, 64 - head_dim_og))
579
+ elif head_dim_og > 64 and head_dim_og < 128:
580
+ q = torch.nn.functional.pad(q, (0, 128 - head_dim_og))
581
+ k = torch.nn.functional.pad(k, (0, 128 - head_dim_og))
582
+ v = torch.nn.functional.pad(v, (0, 128 - head_dim_og))
583
+ elif head_dim_og > 128:
584
+ raise ValueError(f"Unsupported head_dim: {head_dim_og}")
585
+
586
+ # assert last dim is contiguous
587
+ assert q.stride(-1) == 1 and k.stride(-1) == 1 and v.stride(-1) == 1, (
588
+ "Last dim of qkv must be contiguous."
589
+ )
590
+
591
+ if sm_scale is None:
592
+ sm_scale = head_dim_og**-0.5
593
+
594
+ seq_dim = 1 if _tensor_layout == 0 else 2
595
+ nh_dim = 2 if _tensor_layout == 0 else 1
596
+
597
+ if smooth_k:
598
+ km = k.mean(dim=seq_dim, keepdim=True)
599
+ nqheads = q.size(2)
600
+ nkheads = k.size(2)
601
+ q_per_kv_heads = nqheads // nkheads
602
+ if q_per_kv_heads > 1:
603
+ # nheads_k => nheads_q
604
+ km_broadcast = torch.repeat_interleave(km, q_per_kv_heads, dim=nh_dim)
605
+ else:
606
+ km_broadcast = km
607
+ if return_lse:
608
+ if tensor_layout == "NHD":
609
+ lse_correction = (
610
+ torch.matmul(
611
+ q.transpose(1, 2), km_broadcast.transpose(1, 2).transpose(2, 3)
612
+ )
613
+ .squeeze(-1)
614
+ .to(torch.float32)
615
+ )
616
+ else:
617
+ lse_correction = (
618
+ torch.matmul(q, km_broadcast.transpose(2, 3))
619
+ .squeeze(-1)
620
+ .to(torch.float32)
621
+ )
622
+ else:
623
+ km = None
624
+
625
+ if qk_quant_gran == "per_warp":
626
+ q_int8, q_scale, k_int8, k_scale = per_warp_int8_cuda(
627
+ q, k, km, tensor_layout=tensor_layout, BLKQ=128, WARPQ=32, BLKK=64
628
+ )
629
+ elif qk_quant_gran == "per_thread":
630
+ q_int8, q_scale, k_int8, k_scale = per_thread_int8_triton(
631
+ q, k, km, tensor_layout=tensor_layout, BLKQ=128, WARPQ=32, BLKK=64, WARPK=64
632
+ )
633
+
634
+ o = torch.empty(q.size(), dtype=dtype, device=q.device)
635
+
636
+ if pv_accum_dtype == "fp32+fp32" and smooth_v:
637
+ warnings.warn("pv_accum_dtype is 'fp32+fp32', smooth_v will be ignored.")
638
+ smooth_v = False
639
+
640
+ if pv_accum_dtype == "fp32+fp16" and smooth_v:
641
+ warnings.warn("pv_accum_dtype is 'fp32+fp16', smooth_v will be ignored.")
642
+ smooth_v = False
643
+
644
+ quant_v_scale_max = 448.0
645
+ if pv_accum_dtype == "fp32+fp16":
646
+ quant_v_scale_max = 2.25
647
+
648
+ v_fp8, v_scale, vm = per_channel_fp8(
649
+ v, tensor_layout=tensor_layout, scale_max=quant_v_scale_max, smooth_v=smooth_v
650
+ )
651
+ if pv_accum_dtype == "fp32":
652
+ if smooth_v:
653
+ lse = sm89_compile.qk_int8_sv_f8_accum_f32_fuse_v_scale_fuse_v_mean_attn(
654
+ q_int8,
655
+ k_int8,
656
+ v_fp8,
657
+ o,
658
+ q_scale,
659
+ k_scale,
660
+ v_scale,
661
+ vm,
662
+ _tensor_layout,
663
+ _is_caual,
664
+ _qk_quant_gran,
665
+ sm_scale,
666
+ _return_lse,
667
+ )
668
+ torch.cuda.synchronize()
669
+ else:
670
+ lse = sm89_compile.qk_int8_sv_f8_accum_f32_fuse_v_scale_attn(
671
+ q_int8,
672
+ k_int8,
673
+ v_fp8,
674
+ o,
675
+ q_scale,
676
+ k_scale,
677
+ v_scale,
678
+ _tensor_layout,
679
+ _is_caual,
680
+ _qk_quant_gran,
681
+ sm_scale,
682
+ _return_lse,
683
+ )
684
+ torch.cuda.synchronize()
685
+ elif pv_accum_dtype == "fp32+fp32":
686
+ lse = sm89_compile.qk_int8_sv_f8_accum_f32_fuse_v_scale_attn_inst_buf(
687
+ q_int8,
688
+ k_int8,
689
+ v_fp8,
690
+ o,
691
+ q_scale,
692
+ k_scale,
693
+ v_scale,
694
+ _tensor_layout,
695
+ _is_caual,
696
+ _qk_quant_gran,
697
+ sm_scale,
698
+ _return_lse,
699
+ )
700
+ torch.cuda.synchronize()
701
+ elif pv_accum_dtype == "fp32+fp16":
702
+ lse = sm89_compile.qk_int8_sv_f8_accum_f16_fuse_v_scale_attn_inst_buf(
703
+ q_int8,
704
+ k_int8,
705
+ v_fp8,
706
+ o,
707
+ q_scale,
708
+ k_scale,
709
+ v_scale,
710
+ _tensor_layout,
711
+ _is_caual,
712
+ _qk_quant_gran,
713
+ sm_scale,
714
+ _return_lse,
715
+ )
716
+ torch.cuda.synchronize()
717
+ o = o[..., :head_dim_og]
718
+ if return_lse:
719
+ return (
720
+ o,
721
+ lse / 1.44269504 + lse_correction * sm_scale
722
+ if smooth_k
723
+ else lse / 1.44269504,
724
+ )
725
+ else:
726
+ return o
727
+
728
+
729
+ def sageattn_qk_int8_pv_fp8_cuda_sm90(
730
+ q: torch.Tensor,
731
+ k: torch.Tensor,
732
+ v: torch.Tensor,
733
+ tensor_layout: str = "HND",
734
+ is_causal: bool = False,
735
+ qk_quant_gran: str = "per_thread",
736
+ sm_scale: Optional[float] = None,
737
+ pv_accum_dtype: str = "fp32+fp32",
738
+ smooth_k: bool = True,
739
+ return_lse: bool = False,
740
+ **kwargs: Any,
741
+ ) -> torch.Tensor:
742
+ """
743
+ SageAttention with INT8 quantization for Q and K, FP8 PV with FP32 accumulation, implemented using CUDA.
744
+
745
+ Parameters
746
+ ----------
747
+ q : torch.Tensor
748
+ The query tensor. Shape:
749
+ - If `tensor_layout` is "HND": ``[batch_size, num_qo_heads, qo_len, head_dim]``.
750
+ - If `tensor_layout` is "NHD": ``[batch_size, qo_len, num_qo_heads, head_dim]``.
751
+
752
+ k : torch.Tensor
753
+ The key tensor. Shape:
754
+ - If `tensor_layout` is "HND": ``[batch_size, num_kv_heads, kv_len, head_dim]``.
755
+ - If `tensor_layout` is "NHD": ``[batch_size, kv_len, num_kv_heads, head_dim]``.
756
+
757
+ v : torch.Tensor
758
+ The value tensor. Shape:
759
+ - If `tensor_layout` is "HND": ``[batch_size, num_kv_heads, kv_len, head_dim]``.
760
+ - If `tensor_layout` is "NHD": ``[batch_size, kv_len, num_kv_heads, head_dim]``.
761
+
762
+ tensor_layout : str
763
+ The tensor layout, either "HND" or "NHD".
764
+ Default: "HND".
765
+
766
+ is_causal : bool
767
+ Whether to apply causal mask to the attention matrix. Only applicable when qo_len == kv_len.
768
+ Default: False.
769
+
770
+ qk_quant_gran : str
771
+ The granularity of quantization for Q and K, either "per_warp" or "per_thread".
772
+ Default: "per_thread".
773
+
774
+ sm_scale : Optional[float]
775
+ The scale used in softmax, if not provided, will be set to ``1.0 / sqrt(head_dim)``.
776
+
777
+ pv_accum_dtype : str
778
+ The dtype of the accumulation of the product of the value tensor and the attention weights, either "fp32" or "fp32+fp32".
779
+ - "fp32": PV accumulation is done in fully in FP32. However, due to the hardware issue, there are only 22 valid bits in the FP32 accumulator.
780
+ - "fp32+fp32": PV accumulation is done in FP32 (actually FP22), but added to a FP32 buffer every few iterations. This offers a balance between speed and accuracy.
781
+ Default: "fp32+fp32".
782
+
783
+ smooth_k : bool
784
+ Whether to smooth the key tensor by subtracting the mean along the sequence dimension.
785
+ Default: True.
786
+
787
+ return_lse : bool
788
+ Whether to return the log sum of the exponentiated attention weights. Used for cases like Ring Attention.
789
+ Default: False.
790
+
791
+ Returns
792
+ -------
793
+ torch.Tensor
794
+ The output tensor. Shape:
795
+ - If `tensor_layout` is "HND": ``[batch_size, num_qo_heads, qo_len, head_dim]``.
796
+ - If `tensor_layout` is "NHD": ``[batch_size, qo_len, num_qo_heads, head_dim]``.
797
+
798
+ torch.Tensor
799
+ The logsumexp of each row of the matrix QK^T * scaling (e.g., log of the softmax normalization factor).
800
+ Shape: ``[batch_size, num_qo_heads, qo_len]``.
801
+ Only returned if `return_lse` is True.
802
+
803
+ Note
804
+ ----
805
+ - ``num_qo_heads`` must be divisible by ``num_kv_heads``.
806
+ - The tensors `q`, `k`, and `v` must have the dtype ``torch.float16`` or ``torch.bfloat16``
807
+ - All tensors must be on the same cuda device.
808
+ - `smooth_k` will introduce slight overhead but will improve the accuracy under most circumstances.
809
+ """
810
+
811
+ dtype = q.dtype
812
+ assert q.is_cuda, "Input tensors must be on cuda."
813
+ assert dtype in [torch.float16, torch.bfloat16], (
814
+ "Input tensors must be in dtype of torch.float16 or torch.bfloat16"
815
+ )
816
+ assert qk_quant_gran in ["per_warp", "per_thread"], (
817
+ "qk_quant_gran must be either 'per_warp' or 'per_thread'."
818
+ )
819
+ assert q.device == k.device == v.device, "All tensors must be on the same device."
820
+ assert q.dtype == k.dtype == v.dtype, "All tensors must have the same dtype."
821
+
822
+ torch.cuda.set_device(v.device)
823
+
824
+ _tensor_layout = 0 if tensor_layout == "NHD" else 1
825
+ _is_caual = 1 if is_causal else 0
826
+ _qk_quant_gran = 3 if qk_quant_gran == "per_thread" else 2
827
+ _return_lse = 1 if return_lse else 0
828
+
829
+ head_dim_og = q.size(-1)
830
+
831
+ if head_dim_og < 64:
832
+ q = torch.nn.functional.pad(q, (0, 64 - head_dim_og))
833
+ k = torch.nn.functional.pad(k, (0, 64 - head_dim_og))
834
+ v = torch.nn.functional.pad(v, (0, 64 - head_dim_og))
835
+ elif head_dim_og > 64 and head_dim_og < 128:
836
+ q = torch.nn.functional.pad(q, (0, 128 - head_dim_og))
837
+ k = torch.nn.functional.pad(k, (0, 128 - head_dim_og))
838
+ v = torch.nn.functional.pad(v, (0, 128 - head_dim_og))
839
+ elif head_dim_og > 128:
840
+ raise ValueError(f"Unsupported head_dim: {head_dim_og}")
841
+
842
+ # assert last dim is contiguous
843
+ assert q.stride(-1) == 1 and k.stride(-1) == 1 and v.stride(-1) == 1, (
844
+ "Last dim of qkv must be contiguous."
845
+ )
846
+
847
+ if sm_scale is None:
848
+ sm_scale = head_dim_og**-0.5
849
+
850
+ seq_dim = 1 if _tensor_layout == 0 else 2
851
+ nh_dim = 2 if _tensor_layout == 0 else 1
852
+
853
+ if smooth_k:
854
+ km = k.mean(dim=seq_dim, keepdim=True)
855
+ nqheads = q.size(2)
856
+ nkheads = k.size(2)
857
+ q_per_kv_heads = nqheads // nkheads
858
+ if q_per_kv_heads > 1:
859
+ # nheads_k => nheads_q
860
+ km_broadcast = torch.repeat_interleave(km, q_per_kv_heads, dim=nh_dim)
861
+ else:
862
+ km_broadcast = km
863
+ if return_lse:
864
+ if tensor_layout == "NHD":
865
+ lse_correction = (
866
+ torch.matmul(
867
+ q.transpose(1, 2), km_broadcast.transpose(1, 2).transpose(2, 3)
868
+ )
869
+ .squeeze(-1)
870
+ .to(torch.float32)
871
+ )
872
+ else:
873
+ lse_correction = (
874
+ torch.matmul(q, km_broadcast.transpose(2, 3))
875
+ .squeeze(-1)
876
+ .to(torch.float32)
877
+ )
878
+ else:
879
+ km = None
880
+
881
+ if qk_quant_gran == "per_warp":
882
+ q_int8, q_scale, k_int8, k_scale = per_warp_int8_cuda(
883
+ q, k, km, tensor_layout=tensor_layout, BLKQ=64, WARPQ=16, BLKK=128
884
+ )
885
+ elif qk_quant_gran == "per_thread":
886
+ q_int8, q_scale, k_int8, k_scale = per_thread_int8_triton(
887
+ q,
888
+ k,
889
+ km,
890
+ tensor_layout=tensor_layout,
891
+ BLKQ=64,
892
+ WARPQ=16,
893
+ BLKK=128,
894
+ WARPK=128,
895
+ )
896
+
897
+ o = torch.empty(q.size(), dtype=dtype, device=q.device)
898
+
899
+ # pad v to multiple of 128
900
+ # TODO: modify per_channel_fp8 kernel to handle this
901
+ kv_len = k.size(seq_dim)
902
+ v_pad_len = 128 - (kv_len % 128) if kv_len % 128 != 0 else 0
903
+ if v_pad_len > 0:
904
+ if tensor_layout == "HND":
905
+ v = torch.cat(
906
+ [
907
+ v,
908
+ torch.zeros(
909
+ v.size(0),
910
+ v.size(1),
911
+ v_pad_len,
912
+ v.size(3),
913
+ dtype=v.dtype,
914
+ device=v.device,
915
+ ),
916
+ ],
917
+ dim=2,
918
+ )
919
+ else:
920
+ v = torch.cat(
921
+ [
922
+ v,
923
+ torch.zeros(
924
+ v.size(0),
925
+ v_pad_len,
926
+ v.size(2),
927
+ v.size(3),
928
+ dtype=v.dtype,
929
+ device=v.device,
930
+ ),
931
+ ],
932
+ dim=1,
933
+ )
934
+
935
+ v_fp8, v_scale, _ = per_channel_fp8(v, tensor_layout=tensor_layout, smooth_v=False)
936
+
937
+ if pv_accum_dtype == "fp32":
938
+ raise NotImplementedError("Please use pv_accum_dtype='fp32+fp32' for sm90.")
939
+ lse = sm90_compile.qk_int8_sv_f8_accum_f32_fuse_v_scale_attn(
940
+ q_int8,
941
+ k_int8,
942
+ v_fp8,
943
+ o,
944
+ q_scale,
945
+ k_scale,
946
+ v_scale,
947
+ _tensor_layout,
948
+ _is_caual,
949
+ _qk_quant_gran,
950
+ sm_scale,
951
+ _return_lse,
952
+ )
953
+ elif pv_accum_dtype == "fp32+fp32":
954
+ lse = sm90_compile.qk_int8_sv_f8_accum_f32_fuse_v_scale_attn_inst_buf_sm90(
955
+ q_int8,
956
+ k_int8,
957
+ v_fp8,
958
+ o,
959
+ q_scale,
960
+ k_scale,
961
+ v_scale,
962
+ _tensor_layout,
963
+ _is_caual,
964
+ _qk_quant_gran,
965
+ sm_scale,
966
+ _return_lse,
967
+ )
968
+
969
+ o = o[..., :head_dim_og]
970
+
971
+ if return_lse:
972
+ return (
973
+ o,
974
+ lse / 1.44269504 + lse_correction * sm_scale
975
+ if smooth_k
976
+ else lse / 1.44269504,
977
+ )
978
+ else:
979
+ return o
build/torch210-cu128-x86_64-windows/sage_attention/quant.py ADDED
@@ -0,0 +1,326 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Copyright (c) 2024 by SageAttention team.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ """
16
+
17
+ import torch
18
+ from typing import Optional
19
+
20
+ from ._ops import ops
21
+
22
+
23
+ def per_block_int8(
24
+ q: torch.Tensor,
25
+ k: torch.Tensor,
26
+ km: Optional[torch.Tensor] = None,
27
+ BLKQ: int = 128,
28
+ BLKK: int = 64,
29
+ sm_scale: Optional[float] = None,
30
+ tensor_layout: str = "HND",
31
+ ):
32
+ """
33
+ Quantize the query tensor `q` and the key tensor `k` with per block quantization.
34
+
35
+ Parameters
36
+ ----------
37
+ q : torch.Tensor
38
+ The query tensor. Shape:
39
+ - If `tensor_layout` is "HND": ``[batch_size, num_qo_heads, qo_len, head_dim]``.
40
+ - If `tensor_layout` is "NHD": ``[batch_size, qo_len, num_qo_heads, head_dim]``.
41
+
42
+ k : torch.Tensor
43
+ The key tensor. Shape:
44
+ - If `tensor_layout` is "HND": ``[batch_size, num_kv_heads, kv_len, head_dim]``.
45
+ - If `tensor_layout` is "NHD": ``[batch_size, kv_len, num_kv_heads, head_dim]``.
46
+
47
+ km : Optional[torch.Tensor]
48
+ The mean tensor of `k` along the sequence length dimension. Shape: ``[batch_size, num_kv_heads, head_dim]``.
49
+ Should be of the same dtype as `k` if provided. Default is None.
50
+
51
+ sm_scale : Optional[float]
52
+ The scale factor for the softmax operation. Default is ``head_dim**-0.5``.
53
+ It will be multiplied by ``1.44269504`` to work together with the triton attention kernel.
54
+
55
+ tensor_layout : str
56
+ The tensor layout, either "HND" or "NHD".
57
+ Default: "HND".
58
+
59
+ Returns
60
+ -------
61
+ Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]
62
+ A tuple containing:
63
+ - The quantized query tensor. Shape: Same as `q` but with `int8` dtype.
64
+ - The scale tensor of the query tensor. Shape: ``[batch_size, num_qo_heads, (qo_len + BLKQ - 1) // BLKQ]`` with `float32` dtype.
65
+ - The quantized key tensor. Shape: Same as `k` but with `int8` dtype.
66
+ - The scale tensor of the key tensor. Shape: ``[batch_size, num_kv_heads, (kv_len + BLKK - 1) // BLKK]`` with `float32` dtype.
67
+
68
+ Note
69
+ ----
70
+ - The tensors `q` and `k` must have the dtype ``torch.float16`` or ``torch.bfloat16``
71
+ """
72
+
73
+ q_int8 = torch.empty(q.shape, dtype=torch.int8, device=q.device)
74
+ k_int8 = torch.empty(k.shape, dtype=torch.int8, device=k.device)
75
+
76
+ if tensor_layout == "HND":
77
+ b, h_qo, qo_len, head_dim = q.shape
78
+ _, h_kv, kv_len, _ = k.shape
79
+
80
+ elif tensor_layout == "NHD":
81
+ b, qo_len, h_qo, head_dim = q.shape
82
+ _, kv_len, h_kv, _ = k.shape
83
+
84
+ else:
85
+ raise ValueError(f"Unknown tensor layout: {tensor_layout}")
86
+
87
+ _tensor_layout = 0 if tensor_layout == "NHD" else 1
88
+
89
+ q_scale = torch.empty(
90
+ (b, h_qo, (qo_len + BLKQ - 1) // BLKQ), device=q.device, dtype=torch.float32
91
+ )
92
+ k_scale = torch.empty(
93
+ (b, h_kv, (kv_len + BLKK - 1) // BLKK), device=q.device, dtype=torch.float32
94
+ )
95
+
96
+ if sm_scale is None:
97
+ sm_scale = head_dim**-0.5
98
+
99
+ sm_scale *= 1.44269504
100
+
101
+ ops.quant_per_block_int8_cuda(q, q_int8, q_scale, sm_scale, BLKQ, _tensor_layout)
102
+ if km is not None:
103
+ km = km.squeeze(1) if _tensor_layout == 0 else km.squeeze(2)
104
+ ops.quant_per_block_int8_fuse_sub_mean_cuda(
105
+ k, km, k_int8, k_scale, BLKK, _tensor_layout
106
+ )
107
+ else:
108
+ # The bound CUDA op expects an sm_scale argument; use 1.0 for K to avoid scaling
109
+ ops.quant_per_block_int8_cuda(k, k_int8, k_scale, 1.0, BLKK, _tensor_layout)
110
+
111
+ return q_int8, q_scale, k_int8, k_scale
112
+
113
+
114
+ def per_warp_int8(
115
+ q: torch.Tensor,
116
+ k: torch.Tensor,
117
+ km: Optional[torch.Tensor] = None,
118
+ BLKQ: int = 128,
119
+ WARPQ: int = 32,
120
+ BLKK: int = 64,
121
+ tensor_layout: str = "HND",
122
+ ):
123
+ """
124
+ Quantize the query tensor `q` with per warp quantization and the key tensor `k` with per block quantization.
125
+ Warp size of quantizing `q` is 16 or 32, with a block size of 64 or 128.
126
+ Block size of quantizing `k` is 64 or 128.
127
+
128
+ Parameters
129
+ ----------
130
+ q : torch.Tensor
131
+ The query tensor. Shape:
132
+ - If `tensor_layout` is "HND": ``[batch_size, num_qo_heads, qo_len, head_dim]``.
133
+ - If `tensor_layout` is "NHD": ``[batch_size, qo_len, num_qo_heads, head_dim]``.
134
+
135
+ k : torch.Tensor
136
+ The key tensor. Shape:
137
+ - If `tensor_layout` is "HND": ``[batch_size, num_kv_heads, kv_len, head_dim]``.
138
+ - If `tensor_layout` is "NHD": ``[batch_size, kv_len, num_kv_heads, head_dim]``.
139
+
140
+ km : Optional[torch.Tensor]
141
+ The mean tensor of `k` along the sequence length dimension. Shape: ``[batch_size, num_kv_heads, head_dim]``.
142
+ Should be of the same dtype as `k` if provided. Default is None.
143
+
144
+ tensor_layout : str
145
+ The tensor layout, either "HND" or "NHD".
146
+ Default: "HND".
147
+
148
+ Returns
149
+ -------
150
+ Tuple[torch.Tensor, torch.Tensor, torch.Tensor, torch.Tensor]
151
+ A tuple containing:
152
+ - The quantized query tensor. Shape: Same as `q` but with `int8` dtype.
153
+ - The scale tensor of the query tensor. Shape: ``[batch_size, num_qo_heads, (qo_len + BLKQ - 1) // BLKQ * (BLKQ // WARPQ)]`` with `float32` dtype.
154
+ - The quantized key tensor. Shape: Same as `k` but with `int8` dtype.
155
+ - The scale tensor of the key tensor. Shape: ``[batch_size, num_kv_heads, (kv_len + BLKK - 1) // BLKK]`` with `float32` dtype.
156
+
157
+ Note
158
+ ----
159
+ - The tensors `q` and `k` must have the dtype ``torch.float16`` or ``torch.bfloat16``
160
+ """
161
+
162
+ q_int8 = torch.empty(q.shape, dtype=torch.int8, device=q.device)
163
+ k_int8 = torch.empty(k.shape, dtype=torch.int8, device=k.device)
164
+
165
+ if tensor_layout == "HND":
166
+ b, h_qo, qo_len, head_dim = q.shape
167
+ _, h_kv, kv_len, _ = k.shape
168
+
169
+ elif tensor_layout == "NHD":
170
+ b, qo_len, h_qo, head_dim = q.shape
171
+ _, kv_len, h_kv, _ = k.shape
172
+
173
+ else:
174
+ raise ValueError(f"Unknown tensor layout: {tensor_layout}")
175
+
176
+ _tensor_layout = 0 if tensor_layout == "NHD" else 1
177
+
178
+ q_scale = torch.empty(
179
+ (b, h_qo, ((qo_len + BLKQ - 1) // BLKQ) * (BLKQ // WARPQ)),
180
+ device=q.device,
181
+ dtype=torch.float32,
182
+ )
183
+ k_scale = torch.empty(
184
+ (b, h_kv, (kv_len + BLKK - 1) // BLKK), device=q.device, dtype=torch.float32
185
+ )
186
+
187
+ ops.quant_per_warp_int8_cuda(q, q_int8, q_scale, BLKQ, WARPQ, _tensor_layout)
188
+
189
+ if km is not None:
190
+ km = km.squeeze(1) if _tensor_layout == 0 else km.squeeze(2)
191
+ ops.quant_per_block_int8_fuse_sub_mean_cuda(
192
+ k, km, k_int8, k_scale, BLKK, _tensor_layout
193
+ )
194
+ else:
195
+ # The bound CUDA op expects an sm_scale argument; use 1.0 for K to avoid scaling
196
+ ops.quant_per_block_int8_cuda(k, k_int8, k_scale, 1.0, BLKK, _tensor_layout)
197
+
198
+ return q_int8, q_scale, k_int8, k_scale
199
+
200
+
201
+ def sub_mean(v: torch.Tensor, tensor_layout: str = "HND"):
202
+ """
203
+ Calculate the mean of the tensor `v` along the sequence length dimension and subtract it from `v`. Result is stored as fp16.
204
+
205
+ Parameters
206
+ ----------
207
+ v : torch.Tensor
208
+ The input tensor. Shape:
209
+ - If `tensor_layout` is "HND": ``[batch_size, num_kv_heads, kv_len, head_dim]``.
210
+ - If `tensor_layout` is "NHD": ``[batch_size, kv_len, num_kv_heads, head_dim]``.
211
+
212
+ tensor_layout : str
213
+ The tensor layout, either "HND" or "NHD".
214
+ Default: "HND".
215
+
216
+ Returns
217
+ -------
218
+ Tuple[torch.Tensor, torch.Tensor]
219
+ A tuple containing:
220
+ - The tensor `v_smoothed` with the mean subtracted and stored as fp16. Shape: Same as `v` with `float16` dtype.
221
+ - The mean tensor of `v` along the sequence length dimension. Shape: ``[batch_size, num_kv_heads, head_dim]`` with dtype same as `v`.
222
+
223
+ Note
224
+ ----
225
+ - The tensors `v` must have the dtype ``torch.float16`` or ``torch.bfloat16``
226
+ - The returned tensor `v_smoothed` will have dtype ``torch.float16`` regardless of the input dtype.
227
+ - The returned mean tensor will have the same dtype as the input tensor.
228
+ """
229
+
230
+ _tensor_layout = 0 if tensor_layout == "NHD" else 1
231
+ vm = v.mean(dim=1 if _tensor_layout == 0 else 2)
232
+
233
+ v_smoothed = torch.empty(v.shape, dtype=torch.float16, device=v.device)
234
+
235
+ # subtract mean and store the result as fp16
236
+ ops.sub_mean_cuda(v, vm, v_smoothed, _tensor_layout)
237
+
238
+ return v_smoothed, vm
239
+
240
+
241
+ def per_channel_fp8(
242
+ v: torch.Tensor,
243
+ tensor_layout: str = "HND",
244
+ scale_max: float = 448.0,
245
+ smooth_v: bool = True,
246
+ ):
247
+ """
248
+ Transpose, pad and permute the tensor `v` and quantize it to fp8 with per channel quantization.
249
+ `v` is first transposed along the head dimension and the sequence length dimension, then padded to a multiple of 64.
250
+ After that, the tensor is permuted along the sequence length dimension by ``[0, 1, 8, 9, 2, 3, 10, 11, 4, 5, 12, 13, 6, 7, 14, 15]``.
251
+ The quantization is done per channel, with the scale value and smooth factor calculated per channel.
252
+
253
+ Parameters
254
+ ----------
255
+ v : torch.Tensor
256
+ The input tensor. Shape:
257
+ - If `tensor_layout` is "HND": ``[batch_size, num_kv_heads, kv_len, head_dim]``.
258
+ - If `tensor_layout` is "NHD": ``[batch_size, kv_len, num_kv_heads, head_dim]``.
259
+
260
+ tensor_layout : str
261
+ The tensor layout, either "HND" or "NHD".
262
+ Default: "HND".
263
+
264
+ scale_max : float
265
+ The maximum scale value for the quantization. Default is 448.0 (upper bound of E4M3 data format).
266
+
267
+ smooth_v : bool
268
+ Whether to smooth the quantized tensor. Default is True.
269
+
270
+ Returns
271
+ -------
272
+ Tuple[torch.Tensor, torch.Tensor, Optional[torch.Tensor]]
273
+ A tuple containing:
274
+ - The quantized tensor `v_fp8`. Shape:
275
+ - If `tensor_layout` is "HND": ``[batch_size, num_kv_heads, head_dim, (kv_len + 63) // 64 * 64]``, with `float8_e4m3fn` dtype.
276
+ - If `tensor_layout` is "NHD": ``[batch_size, head_dim, num_kv_heads, (kv_len + 63) // 64 * 64]``, with `float8_e4m3fn` dtype.
277
+ - The scale tensor of `v`. Shape: ``[batch_size, num_kv_heads, head_dim]`` with `float32` dtype.
278
+ - The mean tensor of `v` along the sequence length dimension. Shape: ``[batch_size, num_kv_heads, head_dim]`` with `float32` dtype.
279
+
280
+ Note
281
+ ----
282
+ - The tensors `v` must have the dtype ``torch.float16`` or ``torch.bfloat16``
283
+ - The returned mean tensor will be None if `smooth_v` is False. Otherwise it will have dtype ``torch.float32``.
284
+ """
285
+
286
+ _tensor_layout = 0 if tensor_layout == "NHD" else 1
287
+
288
+ if tensor_layout == "HND":
289
+ b, h_kv, kv_len, head_dim = v.shape
290
+ padded_len = (kv_len + 63) // 64 * 64
291
+ v_transposed_permutted = torch.empty(
292
+ (b, h_kv, head_dim, padded_len), dtype=v.dtype, device=v.device
293
+ )
294
+
295
+ elif tensor_layout == "NHD":
296
+ b, kv_len, h_kv, head_dim = v.shape
297
+ padded_len = (kv_len + 63) // 64 * 64
298
+ v_transposed_permutted = torch.empty(
299
+ (b, head_dim, h_kv, padded_len), dtype=v.dtype, device=v.device
300
+ )
301
+
302
+ ops.transpose_pad_permute_cuda(v, v_transposed_permutted, _tensor_layout)
303
+
304
+ v_fp8 = torch.empty(
305
+ v_transposed_permutted.shape, dtype=torch.float8_e4m3fn, device=v.device
306
+ )
307
+
308
+ v_scale = torch.empty((b, h_kv, head_dim), dtype=torch.float32, device=v.device)
309
+ vm = torch.empty((b, h_kv, head_dim), dtype=torch.float32, device=v.device)
310
+
311
+ if smooth_v:
312
+ ops.mean_scale_fuse_quant_cuda(
313
+ v_transposed_permutted,
314
+ v_fp8,
315
+ vm,
316
+ v_scale,
317
+ kv_len,
318
+ scale_max,
319
+ _tensor_layout,
320
+ )
321
+ return v_fp8, v_scale, vm
322
+ else:
323
+ ops.scale_fuse_quant_cuda(
324
+ v_transposed_permutted, v_fp8, v_scale, kv_len, scale_max, _tensor_layout
325
+ )
326
+ return v_fp8, v_scale, None
build/torch210-cu128-x86_64-windows/sage_attention/quant_per_thread.py ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Copyright (c) 2024 by SageAttention team.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ """
16
+
17
+ import torch
18
+ import triton
19
+ import triton.language as tl
20
+
21
+ @triton.jit
22
+ def quant_query_per_thread_int8_kernel(Input, Output, Scale, L,
23
+ stride_iz, stride_ih, stride_in,
24
+ stride_oz, stride_oh, stride_on,
25
+ stride_sz, stride_sh,
26
+ C: tl.constexpr, BLK: tl.constexpr):
27
+ off_blk = tl.program_id(0) // 8
28
+ off_tld = tl.program_id(0) % 8
29
+ off_h = tl.program_id(1)
30
+ off_b = tl.program_id(2)
31
+
32
+ offs_n = off_blk * BLK + tl.arange(0, BLK // 8) * 8 + off_tld
33
+ offs_k = tl.arange(0, C)
34
+
35
+ input_ptrs = Input + off_b * stride_iz + off_h * stride_ih + offs_n[:, None] * stride_in + offs_k[None, :]
36
+ output_ptrs = Output + off_b * stride_oz + off_h * stride_oh + offs_n[:, None] * stride_on + offs_k[None, :]
37
+ scale_ptrs = Scale + off_b * stride_sz + off_h * stride_sh + off_blk * 8 + off_tld
38
+
39
+ x = tl.load(input_ptrs, mask=offs_n[:, None] < L)
40
+ x = x.to(tl.float32)
41
+ scale = tl.max(tl.abs(x)) / 127. + 0.0000001
42
+ x_int8 = x / scale
43
+ x_int8 += 0.5 * tl.where(x_int8 >= 0, 1, -1)
44
+ x_int8 = x_int8.to(tl.int8)
45
+ tl.store(output_ptrs, x_int8, mask=offs_n[:, None] < L)
46
+ tl.store(scale_ptrs, scale)
47
+
48
+ @triton.jit
49
+ def quant_key_per_thread_int8_kernel(Input, Output, Scale, L,
50
+ stride_iz, stride_ih, stride_in,
51
+ stride_oz, stride_oh, stride_on,
52
+ stride_sz, stride_sh,
53
+ C: tl.constexpr, BLK: tl.constexpr):
54
+ off_blk = tl.program_id(0) // 4
55
+ off_tld = tl.program_id(0) % 4
56
+ off_h = tl.program_id(1)
57
+ off_b = tl.program_id(2)
58
+
59
+ # offs_n = off_blk * BLK + tl.cat(tl.arange(0, BLK // 8) * 8, tl.arange(0, BLK // 8) * 8 + 1, True) + off_tld * 2
60
+ # offs_k = tl.arange(0, C)
61
+
62
+ # input_ptrs = Input + off_b * stride_iz + off_h * stride_ih + offs_n[:, None] * stride_in + offs_k[None, :]
63
+ # output_ptrs = Output + off_b * stride_oz + off_h * stride_oh + offs_n[:, None] * stride_on + offs_k[None, :]
64
+ # scale_ptrs = Scale + off_b * stride_sz + off_h * stride_sh + off_blk * 4 + off_tld
65
+
66
+ # x = tl.load(input_ptrs, mask=offs_n[:, None] < L)
67
+ # x = x.to(tl.float32)
68
+ # scale = tl.max(tl.abs(x)) / 127. + 0.0000001
69
+ # x_int8 = x / scale
70
+ # x_int8 += 0.5 * tl.where(x_int8 >= 0, 1, -1)
71
+ # x_int8 = x_int8.to(tl.int8)
72
+ # tl.store(output_ptrs, x_int8, mask=offs_n[:, None] < L)
73
+ # tl.store(scale_ptrs, scale)
74
+
75
+ offs_n0 = off_blk * BLK + tl.arange(0, BLK // 8) * 8 + off_tld * 2
76
+ offs_n1 = off_blk * BLK + tl.arange(0, BLK // 8) * 8 + off_tld * 2 + 1
77
+ offs_k = tl.arange(0, C)
78
+
79
+ input_ptrs0 = Input + off_b * stride_iz + off_h * stride_ih + offs_n0[:, None] * stride_in + offs_k[None, :]
80
+ input_ptrs1 = Input + off_b * stride_iz + off_h * stride_ih + offs_n1[:, None] * stride_in + offs_k[None, :]
81
+ output_ptrs0 = Output + off_b * stride_oz + off_h * stride_oh + offs_n0[:, None] * stride_on + offs_k[None, :]
82
+ output_ptrs1 = Output + off_b * stride_oz + off_h * stride_oh + offs_n1[:, None] * stride_on + offs_k[None, :]
83
+ scale_ptrs = Scale + off_b * stride_sz + off_h * stride_sh + off_blk * 4 + off_tld
84
+
85
+ x0 = tl.load(input_ptrs0, mask=offs_n0[:, None] < L)
86
+ x1 = tl.load(input_ptrs1, mask=offs_n1[:, None] < L)
87
+ x0 = x0.to(tl.float32)
88
+ x1 = x1.to(tl.float32)
89
+ scale = max(tl.max(tl.abs(x0)), tl.max(tl.abs(x1))) / 127. + 0.0000001
90
+ x0_int8 = x0 / scale
91
+ x1_int8 = x1 / scale
92
+ x0_int8 += 0.5 * tl.where(x0_int8 >= 0, 1, -1)
93
+ x1_int8 += 0.5 * tl.where(x1_int8 >= 0, 1, -1)
94
+ x0_int8 = x0_int8.to(tl.int8)
95
+ x1_int8 = x1_int8.to(tl.int8)
96
+ tl.store(output_ptrs0, x0_int8, mask=offs_n0[:, None] < L)
97
+ tl.store(output_ptrs1, x1_int8, mask=offs_n1[:, None] < L)
98
+ tl.store(scale_ptrs, scale)
99
+
100
+ @triton.jit
101
+ def quant_query_per_thread_int4_kernel(Input, Output, Scale, L,
102
+ stride_iz, stride_ih, stride_in,
103
+ stride_oz, stride_oh, stride_on,
104
+ stride_sz, stride_sh,
105
+ C: tl.constexpr, BLK: tl.constexpr):
106
+ off_blk = tl.program_id(0) // 8
107
+ off_tld = tl.program_id(0) % 8
108
+ off_h = tl.program_id(1)
109
+ off_b = tl.program_id(2)
110
+
111
+ offs_n = off_blk * BLK + tl.arange(0, BLK // 8) * 8 + off_tld
112
+ offs_k = tl.arange(0, C)
113
+
114
+ input_ptrs = Input + off_b * stride_iz + off_h * stride_ih + offs_n[:, None] * stride_in + offs_k[None, :]
115
+ output_ptrs = Output + off_b * stride_oz + off_h * stride_oh + offs_n[:, None] * stride_on + offs_k[None, :]
116
+ scale_ptrs = Scale + off_b * stride_sz + off_h * stride_sh + off_blk * 8 + off_tld
117
+
118
+ x = tl.load(input_ptrs, mask=offs_n[:, None] < L)
119
+ x = x.to(tl.float32)
120
+ scale = tl.max(tl.abs(x)) / 7. + 0.0000001
121
+ x_int8 = x / scale
122
+ x_int8 += 0.5 * tl.where(x_int8 >= 0, 1, -1)
123
+ x_int8 = x_int8.to(tl.int8)
124
+ tl.store(output_ptrs, x_int8, mask=offs_n[:, None] < L)
125
+ tl.store(scale_ptrs, scale)
126
+
127
+ @triton.jit
128
+ def quant_key_per_thread_int4_kernel(Input, Output, Scale, L,
129
+ stride_iz, stride_ih, stride_in,
130
+ stride_oz, stride_oh, stride_on,
131
+ stride_sz, stride_sh,
132
+ C: tl.constexpr, BLK: tl.constexpr):
133
+ off_blk = tl.program_id(0) // 4
134
+ off_tld = tl.program_id(0) % 4
135
+ off_h = tl.program_id(1)
136
+ off_b = tl.program_id(2)
137
+
138
+ offs_n = off_blk * BLK + tl.cat(tl.arange(0, BLK // 8) * 8, tl.arange(0, BLK // 8) * 8 + 1, True) + off_tld * 2
139
+ offs_k = tl.arange(0, C)
140
+
141
+ input_ptrs = Input + off_b * stride_iz + off_h * stride_ih + offs_n[:, None] * stride_in + offs_k[None, :]
142
+ output_ptrs = Output + off_b * stride_oz + off_h * stride_oh + offs_n[:, None] * stride_on + offs_k[None, :]
143
+ scale_ptrs = Scale + off_b * stride_sz + off_h * stride_sh + off_blk * 4 + off_tld
144
+
145
+ x = tl.load(input_ptrs, mask=offs_n[:, None] < L)
146
+ x = x.to(tl.float32)
147
+ scale = tl.max(tl.abs(x)) / 7. + 0.0000001
148
+ x_int8 = x / scale
149
+ x_int8 += 0.5 * tl.where(x_int8 >= 0, 1, -1)
150
+ x_int8 = x_int8.to(tl.int8)
151
+ tl.store(output_ptrs, x_int8, mask=offs_n[:, None] < L)
152
+ tl.store(scale_ptrs, scale)
153
+
154
+ def per_thread_int8(q, k, km=None, BLKQ=128, WARPQ=32, BLKK=64, WARPK=64, sm_scale=None, tensor_layout="HND"):
155
+ q_int8 = torch.empty(q.shape, dtype=torch.int8, device=q.device)
156
+ k_int8 = torch.empty(k.shape, dtype=torch.int8, device=k.device)
157
+
158
+ if km is not None:
159
+ k = k - km
160
+
161
+ if tensor_layout == "HND":
162
+ b, h_qo, qo_len, head_dim = q.shape
163
+ _, h_kv, kv_len, _ = k.shape
164
+
165
+ stride_bz_q, stride_h_q, stride_seq_q = q.stride(0), q.stride(1), q.stride(2)
166
+ stride_bz_qo, stride_h_qo, stride_seq_qo = q_int8.stride(0), q_int8.stride(1), q_int8.stride(2)
167
+ stride_bz_k, stride_h_k, stride_seq_k = k.stride(0), k.stride(1), k.stride(2)
168
+ stride_bz_ko, stride_h_ko, stride_seq_ko = k_int8.stride(0), k_int8.stride(1), k_int8.stride(2)
169
+ elif tensor_layout == "NHD":
170
+ b, qo_len, h_qo, head_dim = q.shape
171
+ _, kv_len, h_kv, _ = k.shape
172
+
173
+ stride_bz_q, stride_h_q, stride_seq_q = q.stride(0), q.stride(2), q.stride(1)
174
+ stride_bz_qo, stride_h_qo, stride_seq_qo = q_int8.stride(0), q_int8.stride(2), q_int8.stride(1)
175
+ stride_bz_k, stride_h_k, stride_seq_k = k.stride(0), k.stride(2), k.stride(1)
176
+ stride_bz_ko, stride_h_ko, stride_seq_ko = k_int8.stride(0), k_int8.stride(2), k_int8.stride(1)
177
+ else:
178
+ raise ValueError(f"Unknown tensor layout: {tensor_layout}")
179
+
180
+ q_scale = torch.empty((b, h_qo, (qo_len + BLKQ - 1) // BLKQ * (BLKQ // WARPQ) * 8), device=q.device, dtype=torch.float32)
181
+ k_scale = torch.empty((b, h_kv, (kv_len + BLKK - 1) // BLKK * (BLKK // WARPK) * 4), device=q.device, dtype=torch.float32)
182
+
183
+ if sm_scale is None:
184
+ sm_scale = head_dim**-0.5
185
+
186
+ grid = ((qo_len + BLKQ - 1) // BLKQ * (BLKQ // WARPQ) * 8, h_qo, b)
187
+ quant_query_per_thread_int8_kernel[grid](
188
+ q, q_int8, q_scale, qo_len,
189
+ stride_bz_q, stride_h_q, stride_seq_q,
190
+ stride_bz_qo, stride_h_qo, stride_seq_qo,
191
+ q_scale.stride(0), q_scale.stride(1),
192
+ C=head_dim, BLK=WARPQ
193
+ )
194
+
195
+ grid = ((kv_len + BLKK - 1) // BLKK * (BLKK // WARPK) * 4, h_kv, b)
196
+ quant_key_per_thread_int8_kernel[grid](
197
+ k, k_int8, k_scale, kv_len,
198
+ stride_bz_k, stride_h_k, stride_seq_k,
199
+ stride_bz_ko, stride_h_ko, stride_seq_ko,
200
+ k_scale.stride(0), k_scale.stride(1),
201
+ C=head_dim, BLK=WARPK
202
+ )
203
+
204
+ return q_int8, q_scale, k_int8, k_scale
build/torch210-cu128-x86_64-windows/sage_attention/sm80_compile.py ADDED
@@ -0,0 +1,149 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from ._ops import ops
2
+ import torch
3
+ from ._ops import add_op_namespace_prefix
4
+
5
+ @torch.library.custom_op(add_op_namespace_prefix("qk_int8_sv_f16_accum_f16_attn"), mutates_args=("output",), device_types="cuda")
6
+ def qk_int8_sv_f16_accum_f16_attn(
7
+ query: torch.Tensor,
8
+ key: torch.Tensor,
9
+ value: torch.Tensor,
10
+ output: torch.Tensor,
11
+ query_scale: torch.Tensor,
12
+ key_scale: torch.Tensor,
13
+ tensor_layout: int,
14
+ is_causal: int,
15
+ qk_quant_gran: int,
16
+ sm_scale: float,
17
+ return_lse: int,
18
+ ) -> torch.Tensor:
19
+ """
20
+ Custom CUDA kernel for SageAttention with INT8 quantization for Q and K, FP16 PV with FP16 accumulation.
21
+ """
22
+ return ops.qk_int8_sv_f16_accum_f16_attn(
23
+ query, key, value, output, query_scale, key_scale, tensor_layout,
24
+ is_causal, qk_quant_gran, sm_scale, return_lse
25
+ )
26
+
27
+
28
+ @torch.library.custom_op(add_op_namespace_prefix("qk_int8_sv_f16_accum_f32_attn"), mutates_args=("output",), device_types="cuda")
29
+ def qk_int8_sv_f16_accum_f32_attn(
30
+ query: torch.Tensor,
31
+ key: torch.Tensor,
32
+ value: torch.Tensor,
33
+ output: torch.Tensor,
34
+ query_scale: torch.Tensor,
35
+ key_scale: torch.Tensor,
36
+ tensor_layout: int,
37
+ is_causal: int,
38
+ qk_quant_gran: int,
39
+ sm_scale: float,
40
+ return_lse: int,
41
+ ) -> torch.Tensor:
42
+ """
43
+ Custom CUDA kernel for SageAttention with INT8 quantization for Q and K, FP16 PV with FP32 accumulation.
44
+ """
45
+ return ops.qk_int8_sv_f16_accum_f32_attn(
46
+ query, key, value, output, query_scale, key_scale, tensor_layout,
47
+ is_causal, qk_quant_gran, sm_scale, return_lse
48
+ )
49
+
50
+
51
+ @torch.library.custom_op(add_op_namespace_prefix("qk_int8_sv_f16_accum_f16_attn_inst_buf"), mutates_args=("output",), device_types="cuda")
52
+ def qk_int8_sv_f16_accum_f16_attn_inst_buf(
53
+ query: torch.Tensor,
54
+ key: torch.Tensor,
55
+ value: torch.Tensor,
56
+ output: torch.Tensor,
57
+ query_scale: torch.Tensor,
58
+ key_scale: torch.Tensor,
59
+ tensor_layout: int,
60
+ is_causal: int,
61
+ qk_quant_gran: int,
62
+ sm_scale: float,
63
+ return_lse: int,
64
+ ) -> torch.Tensor:
65
+ """
66
+ Custom CUDA kernel for SageAttention with INT8 quantization for Q and K, FP16 PV with FP16 accumulation.
67
+ """
68
+ return ops.qk_int8_sv_f16_accum_f16_attn_inst_buf(
69
+ query, key, value, output, query_scale, key_scale, tensor_layout,
70
+ is_causal, qk_quant_gran, sm_scale, return_lse
71
+ )
72
+
73
+
74
+ @torch.library.custom_op(add_op_namespace_prefix("qk_int8_sv_f16_accum_f16_fuse_v_mean_attn"), mutates_args=("output",), device_types="cuda")
75
+ def qk_int8_sv_f16_accum_f16_fuse_v_mean_attn(
76
+ query: torch.Tensor,
77
+ key: torch.Tensor,
78
+ value: torch.Tensor,
79
+ output: torch.Tensor,
80
+ query_scale: torch.Tensor,
81
+ key_scale: torch.Tensor,
82
+ value_mean: torch.Tensor,
83
+ tensor_layout: int,
84
+ is_causal: int,
85
+ qk_quant_gran: int,
86
+ sm_scale: float,
87
+ return_lse: int,
88
+ ) -> torch.Tensor:
89
+ """
90
+ Custom CUDA kernel for SageAttention with INT8 quantization for Q and K, FP16 PV with FP16 accumulation.
91
+ """
92
+ return ops.qk_int8_sv_f16_accum_f16_fuse_v_mean_attn(
93
+ query, key, value, output, query_scale, key_scale, value_mean,
94
+ tensor_layout, is_causal, qk_quant_gran, sm_scale, return_lse
95
+ )
96
+
97
+
98
+ def sm80_qk_fake_impl(
99
+ query: torch.Tensor,
100
+ key: torch.Tensor,
101
+ value: torch.Tensor,
102
+ output: torch.Tensor,
103
+ query_scale: torch.Tensor,
104
+ key_scale: torch.Tensor,
105
+ tensor_layout: int,
106
+ is_causal: int,
107
+ qk_quant_gran: int,
108
+ sm_scale: float,
109
+ return_lse: int,
110
+ ) -> torch.Tensor:
111
+ batch_size = query.size(0)
112
+
113
+ if tensor_layout == 0:
114
+ num_qo_heads = query.size(2)
115
+ qo_len = query.size(1)
116
+ else:
117
+ num_qo_heads = query.size(1)
118
+ qo_len = query.size(2)
119
+
120
+ if return_lse:
121
+ lse = torch.empty((batch_size, num_qo_heads, qo_len), dtype=torch.float32, device=query.device)
122
+ else:
123
+ lse = torch.empty((0))
124
+ return lse
125
+
126
+ torch.library.register_fake(add_op_namespace_prefix("qk_int8_sv_f16_accum_f16_attn"))(sm80_qk_fake_impl)
127
+ torch.library.register_fake(add_op_namespace_prefix("qk_int8_sv_f16_accum_f32_attn"))(sm80_qk_fake_impl)
128
+ torch.library.register_fake(add_op_namespace_prefix("qk_int8_sv_f16_accum_f16_attn_inst_buf"))(sm80_qk_fake_impl)
129
+
130
+
131
+ @torch.library.register_fake(add_op_namespace_prefix("qk_int8_sv_f16_accum_f16_fuse_v_mean_attn"))
132
+ def qk_int8_sv_f16_accum_f16_fuse_v_mean_attn_fake_impl(
133
+ query: torch.Tensor,
134
+ key: torch.Tensor,
135
+ value: torch.Tensor,
136
+ output: torch.Tensor,
137
+ query_scale: torch.Tensor,
138
+ key_scale: torch.Tensor,
139
+ value_mean: torch.Tensor,
140
+ tensor_layout: int,
141
+ is_causal: int,
142
+ qk_quant_gran: int,
143
+ sm_scale: float,
144
+ return_lse: int,
145
+ ) -> torch.Tensor:
146
+ return sm80_qk_fake_impl(
147
+ query, key, value, output, query_scale, key_scale, tensor_layout,
148
+ is_causal, qk_quant_gran, sm_scale, return_lse
149
+ )
build/torch210-cu128-x86_64-windows/sage_attention/sm89_compile.py ADDED
@@ -0,0 +1,146 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from ._ops import ops
2
+ import torch
3
+ from ._ops import add_op_namespace_prefix
4
+
5
+ @torch.library.custom_op(add_op_namespace_prefix("qk_int8_sv_f8_accum_f32_fuse_v_scale_attn"), mutates_args=("output",), device_types="cuda")
6
+ def qk_int8_sv_f8_accum_f32_fuse_v_scale_attn(
7
+ query: torch.Tensor,
8
+ key: torch.Tensor,
9
+ value: torch.Tensor,
10
+ output: torch.Tensor,
11
+ query_scale: torch.Tensor,
12
+ key_scale: torch.Tensor,
13
+ value_scale: torch.Tensor,
14
+ tensor_layout: int,
15
+ is_causal: int,
16
+ qk_quant_gran: int,
17
+ sm_scale: float,
18
+ return_lse: int,
19
+ ) -> torch.Tensor:
20
+ return ops.qk_int8_sv_f8_accum_f32_fuse_v_scale_attn(
21
+ query, key, value, output, query_scale, key_scale, value_scale,
22
+ tensor_layout, is_causal, qk_quant_gran, sm_scale, return_lse
23
+ )
24
+
25
+
26
+
27
+ @torch.library.custom_op(add_op_namespace_prefix("qk_int8_sv_f8_accum_f32_fuse_v_scale_attn_inst_buf"), mutates_args=("output",), device_types="cuda")
28
+ def qk_int8_sv_f8_accum_f32_fuse_v_scale_attn_inst_buf(
29
+ query: torch.Tensor,
30
+ key: torch.Tensor,
31
+ value: torch.Tensor,
32
+ output: torch.Tensor,
33
+ query_scale: torch.Tensor,
34
+ key_scale: torch.Tensor,
35
+ value_scale: torch.Tensor,
36
+ tensor_layout: int,
37
+ is_causal: int,
38
+ qk_quant_gran: int,
39
+ sm_scale: float,
40
+ return_lse: int,
41
+ ) -> torch.Tensor:
42
+ return ops.qk_int8_sv_f8_accum_f32_fuse_v_scale_attn_inst_buf(
43
+ query, key, value, output, query_scale, key_scale, value_scale,
44
+ tensor_layout, is_causal, qk_quant_gran, sm_scale, return_lse
45
+ )
46
+
47
+
48
+ @torch.library.custom_op(add_op_namespace_prefix("qk_int8_sv_f8_accum_f16_fuse_v_scale_attn_inst_buf"), mutates_args=("output",), device_types="cuda")
49
+ def qk_int8_sv_f8_accum_f16_fuse_v_scale_attn_inst_buf(
50
+ query: torch.Tensor,
51
+ key: torch.Tensor,
52
+ value: torch.Tensor,
53
+ output: torch.Tensor,
54
+ query_scale: torch.Tensor,
55
+ key_scale: torch.Tensor,
56
+ value_scale: torch.Tensor,
57
+ tensor_layout: int,
58
+ is_causal: int,
59
+ qk_quant_gran: int,
60
+ sm_scale: float,
61
+ return_lse: int,
62
+ ) -> torch.Tensor:
63
+ return ops.qk_int8_sv_f8_accum_f16_fuse_v_scale_attn_inst_buf(
64
+ query, key, value, output, query_scale, key_scale, value_scale,
65
+ tensor_layout, is_causal, qk_quant_gran, sm_scale, return_lse
66
+ )
67
+
68
+
69
+ def sm89_qk_with_key_value(
70
+ query: torch.Tensor,
71
+ key: torch.Tensor,
72
+ value: torch.Tensor,
73
+ output: torch.Tensor,
74
+ query_scale: torch.Tensor,
75
+ key_scale: torch.Tensor,
76
+ value_scale: torch.Tensor,
77
+ tensor_layout: int,
78
+ is_causal: int,
79
+ qk_quant_gran: int,
80
+ sm_scale: float,
81
+ return_lse: int,
82
+ ) -> torch.Tensor:
83
+ batch_size = query.size(0)
84
+
85
+ if tensor_layout == 0:
86
+ num_qo_heads = query.size(2)
87
+ qo_len = query.size(1)
88
+ else:
89
+ num_qo_heads = query.size(1)
90
+ qo_len = query.size(2)
91
+
92
+ if return_lse:
93
+ lse = torch.empty((batch_size, num_qo_heads, qo_len), dtype=torch.float32, device=query.device)
94
+ else:
95
+ lse = torch.empty((0))
96
+ return lse
97
+
98
+
99
+ torch.library.register_fake(add_op_namespace_prefix("qk_int8_sv_f8_accum_f32_fuse_v_scale_attn_inst_buf"))(sm89_qk_with_key_value)
100
+ torch.library.register_fake(add_op_namespace_prefix("qk_int8_sv_f8_accum_f16_fuse_v_scale_attn_inst_buf"))(sm89_qk_with_key_value)
101
+ torch.library.register_fake(add_op_namespace_prefix("qk_int8_sv_f8_accum_f32_fuse_v_scale_attn"))(sm89_qk_with_key_value)
102
+
103
+
104
+ @torch.library.custom_op(add_op_namespace_prefix("qk_int8_sv_f8_accum_f32_fuse_v_scale_fuse_v_mean_attn"), mutates_args=("output",), device_types="cuda")
105
+ def qk_int8_sv_f8_accum_f32_fuse_v_scale_fuse_v_mean_attn(
106
+ query: torch.Tensor,
107
+ key: torch.Tensor,
108
+ value: torch.Tensor,
109
+ output: torch.Tensor,
110
+ query_scale: torch.Tensor,
111
+ key_scale: torch.Tensor,
112
+ value_scale: torch.Tensor,
113
+ value_mean: torch.Tensor,
114
+ tensor_layout: int,
115
+ is_causal: int,
116
+ qk_quant_gran: int,
117
+ sm_scale: float,
118
+ return_lse: int,
119
+ ) -> torch.Tensor:
120
+ return ops.qk_int8_sv_f8_accum_f32_fuse_v_scale_fuse_v_mean_attn(
121
+ query, key, value, output, query_scale, key_scale, value_scale,
122
+ value_mean, tensor_layout, is_causal, qk_quant_gran, sm_scale,
123
+ return_lse
124
+ )
125
+
126
+
127
+ @torch.library.register_fake(add_op_namespace_prefix("qk_int8_sv_f8_accum_f32_fuse_v_scale_fuse_v_mean_attn"))
128
+ def sm89_qk_with_key_value_mean(
129
+ query: torch.Tensor,
130
+ key: torch.Tensor,
131
+ value: torch.Tensor,
132
+ output: torch.Tensor,
133
+ query_scale: torch.Tensor,
134
+ key_scale: torch.Tensor,
135
+ value_scale: torch.Tensor,
136
+ value_mean: torch.Tensor,
137
+ tensor_layout: int,
138
+ is_causal: int,
139
+ qk_quant_gran: int,
140
+ sm_scale: float,
141
+ return_lse: int,
142
+ ) -> torch.Tensor:
143
+ return sm89_qk_with_key_value(
144
+ query, key, value, output, query_scale, key_scale, value_scale,
145
+ tensor_layout, is_causal, qk_quant_gran, sm_scale, return_lse
146
+ )
build/torch210-cu128-x86_64-windows/sage_attention/sm90_compile.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from ._ops import ops
2
+ import torch
3
+ from ._ops import add_op_namespace_prefix
4
+
5
+ @torch.library.custom_op(add_op_namespace_prefix("qk_int8_sv_f8_accum_f32_attn_inst_buf"), mutates_args=("output",), device_types="cuda")
6
+ def qk_int8_sv_f8_accum_f32_attn_inst_buf(
7
+ query: torch.Tensor,
8
+ key: torch.Tensor,
9
+ value: torch.Tensor,
10
+ output: torch.Tensor,
11
+ query_scale: torch.Tensor,
12
+ key_scale: torch.Tensor,
13
+ tensor_layout: int,
14
+ is_causal: int,
15
+ qk_quant_gran: int,
16
+ sm_scale: float,
17
+ return_lse: int,
18
+ ) -> torch.Tensor:
19
+ return ops.qk_int8_sv_f8_accum_f32_attn_inst_buf(
20
+ query, key, value, output, query_scale, key_scale, tensor_layout,
21
+ is_causal, qk_quant_gran, sm_scale, return_lse
22
+ )
23
+
24
+
25
+ @torch.library.register_fake(add_op_namespace_prefix("qk_int8_sv_f8_accum_f32_attn_inst_buf"))
26
+ def qk_int8_sv_f8_accum_f32_attn_inst_buf_fake_impl(
27
+ query: torch.Tensor,
28
+ key: torch.Tensor,
29
+ value: torch.Tensor,
30
+ output: torch.Tensor,
31
+ query_scale: torch.Tensor,
32
+ key_scale: torch.Tensor,
33
+ tensor_layout: int,
34
+ is_causal: int,
35
+ qk_quant_gran: int,
36
+ sm_scale: float,
37
+ return_lse: int,
38
+ ) -> torch.Tensor:
39
+ batch_size = query.size(0)
40
+
41
+ if tensor_layout == 0:
42
+ num_qo_heads = query.size(2)
43
+ qo_len = query.size(1)
44
+ else:
45
+ num_qo_heads = query.size(1)
46
+ qo_len = query.size(2)
47
+
48
+ if return_lse:
49
+ lse = torch.empty((batch_size, num_qo_heads, qo_len), dtype=torch.float32, device=query.device)
50
+ else:
51
+ lse = torch.empty((0))
52
+ return lse
53
+
54
+
55
+ @torch.library.custom_op(add_op_namespace_prefix("qk_int8_sv_f8_accum_f32_fuse_v_scale_attn_inst_buf_sm90"), mutates_args=("output",), device_types="cuda")
56
+ def qk_int8_sv_f8_accum_f32_fuse_v_scale_attn_inst_buf_sm90(
57
+ query: torch.Tensor,
58
+ key: torch.Tensor,
59
+ value: torch.Tensor,
60
+ output: torch.Tensor,
61
+ query_scale: torch.Tensor,
62
+ key_scale: torch.Tensor,
63
+ value_scale: torch.Tensor,
64
+ tensor_layout: int,
65
+ is_causal: int,
66
+ qk_quant_gran: int,
67
+ sm_scale: float,
68
+ return_lse: int,
69
+ ) -> torch.Tensor:
70
+ return ops.qk_int8_sv_f8_accum_f32_fuse_v_scale_attn_inst_buf_sm90(
71
+ query, key, value, output, query_scale, key_scale, value_scale,
72
+ tensor_layout, is_causal, qk_quant_gran, sm_scale, return_lse
73
+ )
74
+
75
+
76
+ @torch.library.register_fake(add_op_namespace_prefix("qk_int8_sv_f8_accum_f32_fuse_v_scale_attn_inst_buf_sm90"))
77
+ def qk_int8_sv_f8_accum_f32_fuse_v_scale_attn_inst_buf_sm90_fake_impl(
78
+ query: torch.Tensor,
79
+ key: torch.Tensor,
80
+ value: torch.Tensor,
81
+ output: torch.Tensor,
82
+ query_scale: torch.Tensor,
83
+ key_scale: torch.Tensor,
84
+ value_scale: torch.Tensor,
85
+ tensor_layout: int,
86
+ is_causal: int,
87
+ qk_quant_gran: int,
88
+ sm_scale: float,
89
+ return_lse: int,
90
+ ) -> torch.Tensor:
91
+ return qk_int8_sv_f8_accum_f32_attn_inst_buf_fake_impl(
92
+ query, key, value, output, query_scale, key_scale, tensor_layout,
93
+ is_causal, qk_quant_gran, sm_scale, return_lse
94
+ )