Skip to content

Commit 415419d

Browse files
alexzmsSolitaryThinkerloaydatrainH1yori233
committed
[kernel]: Add modified SageAttention3 FP4 inference kernels (7/12)
Add the attn_qat_infer Blackwell FP4 attention + quantization CUDA kernels (modified SageAttention3) into fastvideo-kernel, gated behind a build flag and landed as deadcode (not yet wired into a backend). - fastvideo-kernel/attn_qat_infer/: Blackwell FP4 attention (blackwell/api.cu) and 4D FP4 quantization (quantization/fp4_quantization_4d.cu) CUDA kernels, plus their Python wrappers (api.py) and microbenchmarks. - CMake: new FASTVIDEO_KERNEL_BUILD_ATTN_QAT_INFER option (AUTO/ON/OFF). AUTO only builds on CUDA Toolkit 12.8+ with Blackwell sm_120a; otherwise the kernels are skipped, so the change is inert on existing CI/GPUs. - pyproject/MANIFEST: ship the attn_qat_infer module in the wheel. This is the modified-SageAttention3-kernel item from the #1225 tracker. The attn_qat_infer / attn_qat_train attention backends already on main import these modules lazily, so they stay dormant until a follow-up PR wires the backend in. Part of #1225. Co-authored-by: William Lin <SolitaryThinker@users.noreply.github.com> Co-authored-by: Loay Rashid <42599591+loaydatrain@users.noreply.github.com> Co-authored-by: Kaiqin Kong <k1kong@ucsd.edu>
1 parent 633d393 commit 415419d

29 files changed

Lines changed: 5296 additions & 1 deletion

fastvideo-kernel/CMakeLists.txt

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,13 +50,29 @@ include_directories(
5050
set(FASTVIDEO_KERNEL_BUILD_TK "AUTO" CACHE STRING "Build ThunderKittens kernels: AUTO/ON/OFF")
5151
set_property(CACHE FASTVIDEO_KERNEL_BUILD_TK PROPERTY STRINGS AUTO ON OFF)
5252

53+
set(_FASTVIDEO_KERNEL_BUILD_ATTN_QAT_INFER_DEFAULT "AUTO")
54+
if(DEFINED FASTVIDEO_KERNEL_BUILD_MODIFIED_SAGE3 AND NOT DEFINED CACHE{FASTVIDEO_KERNEL_BUILD_ATTN_QAT_INFER})
55+
set(_FASTVIDEO_KERNEL_BUILD_ATTN_QAT_INFER_DEFAULT "${FASTVIDEO_KERNEL_BUILD_MODIFIED_SAGE3}")
56+
endif()
57+
58+
set(FASTVIDEO_KERNEL_BUILD_ATTN_QAT_INFER "${_FASTVIDEO_KERNEL_BUILD_ATTN_QAT_INFER_DEFAULT}" CACHE STRING
59+
"Build attn_qat_infer Blackwell inference kernels: AUTO/ON/OFF")
60+
set_property(CACHE FASTVIDEO_KERNEL_BUILD_ATTN_QAT_INFER PROPERTY STRINGS AUTO ON OFF)
61+
62+
if(DEFINED FASTVIDEO_KERNEL_BUILD_MODIFIED_SAGE3)
63+
message(DEPRECATION
64+
"FASTVIDEO_KERNEL_BUILD_MODIFIED_SAGE3 is deprecated. "
65+
"Use FASTVIDEO_KERNEL_BUILD_ATTN_QAT_INFER instead.")
66+
endif()
67+
5368
# Prefer environment variable (used by CI) if CMake var is not explicitly set.
5469
if(NOT DEFINED TORCH_CUDA_ARCH_LIST AND DEFINED ENV{TORCH_CUDA_ARCH_LIST})
5570
set(TORCH_CUDA_ARCH_LIST "$ENV{TORCH_CUDA_ARCH_LIST}")
5671
endif()
5772

5873
message(STATUS "TORCH_CUDA_ARCH_LIST (cmake/env): ${TORCH_CUDA_ARCH_LIST}")
5974
message(STATUS "FASTVIDEO_KERNEL_BUILD_TK: ${FASTVIDEO_KERNEL_BUILD_TK}")
75+
message(STATUS "FASTVIDEO_KERNEL_BUILD_ATTN_QAT_INFER: ${FASTVIDEO_KERNEL_BUILD_ATTN_QAT_INFER}")
6076

6177
set(ENABLE_TK_KERNELS OFF)
6278
if(FASTVIDEO_KERNEL_BUILD_TK STREQUAL "ON")
@@ -91,6 +107,54 @@ else()
91107
message(STATUS "ThunderKittens kernels: DISABLED (will use Triton fallbacks at runtime)")
92108
endif()
93109

110+
set(ENABLE_ATTN_QAT_INFER OFF)
111+
if(GPU_BACKEND STREQUAL "ROCM")
112+
message(STATUS "attn_qat_infer kernels: DISABLED (ROCm build)")
113+
else()
114+
set(_WANTS_ATTN_QAT_INFER OFF)
115+
if(FASTVIDEO_KERNEL_BUILD_ATTN_QAT_INFER STREQUAL "ON")
116+
set(_WANTS_ATTN_QAT_INFER ON)
117+
elseif(FASTVIDEO_KERNEL_BUILD_ATTN_QAT_INFER STREQUAL "AUTO")
118+
if(TORCH_CUDA_ARCH_LIST)
119+
string(REGEX MATCH
120+
"(^|[; ,])((12\\.0a)|(120a)|(sm_120a))([; ,]|$)"
121+
_HAS_120A "${TORCH_CUDA_ARCH_LIST}")
122+
if(_HAS_120A)
123+
set(_WANTS_ATTN_QAT_INFER ON)
124+
endif()
125+
else()
126+
execute_process(
127+
COMMAND "${Python_EXECUTABLE}" -c
128+
"import torch; print('1' if (torch.cuda.is_available() and torch.version.cuda and torch.cuda.get_device_capability()[0] >= 12) else '0')"
129+
OUTPUT_VARIABLE _LOCAL_HAS_BLACKWELL
130+
OUTPUT_STRIP_TRAILING_WHITESPACE
131+
ERROR_QUIET
132+
)
133+
if(_LOCAL_HAS_BLACKWELL STREQUAL "1")
134+
set(_WANTS_ATTN_QAT_INFER ON)
135+
endif()
136+
endif()
137+
endif()
138+
139+
if(_WANTS_ATTN_QAT_INFER)
140+
if(CUDAToolkit_VERSION VERSION_LESS 12.8)
141+
message(WARNING
142+
"attn_qat_infer kernels require CUDA Toolkit 12.8+. "
143+
"Skipping because CUDAToolkit_VERSION=${CUDAToolkit_VERSION}.")
144+
else()
145+
set(ENABLE_ATTN_QAT_INFER ON)
146+
endif()
147+
endif()
148+
149+
if(ENABLE_ATTN_QAT_INFER)
150+
message(STATUS "attn_qat_infer kernels: ENABLED")
151+
else()
152+
message(STATUS
153+
"attn_qat_infer kernels: DISABLED "
154+
"(requires CUDA 12.8+ and Blackwell sm_120a)")
155+
endif()
156+
endif()
157+
94158
# Always try to build the extension if CUDA is available, but conditionally add sources/flags
95159
set(BUILD_CXX_KERNELS ON)
96160

@@ -183,3 +247,74 @@ if(BUILD_CXX_KERNELS)
183247
install(TARGETS fastvideo_kernel_ops LIBRARY DESTINATION fastvideo_kernel/_C)
184248
endif()
185249

250+
if(ENABLE_ATTN_QAT_INFER)
251+
set(ATTN_QAT_INFER_DIR ${CMAKE_SOURCE_DIR}/attn_qat_infer)
252+
set(ATTN_QAT_INFER_INCLUDE_DIRS
253+
${ATTN_QAT_INFER_DIR}
254+
${CMAKE_SOURCE_DIR}/include/cutlass/include
255+
${CMAKE_SOURCE_DIR}/include/cutlass/tools/util/include
256+
${TORCH_INCLUDE_DIRS}
257+
)
258+
set(ATTN_QAT_INFER_CUDA_FLAGS
259+
"-O3"
260+
"-std=c++17"
261+
"-U__CUDA_NO_HALF_OPERATORS__"
262+
"-U__CUDA_NO_HALF_CONVERSIONS__"
263+
"-U__CUDA_NO_BFLOAT16_OPERATORS__"
264+
"-U__CUDA_NO_BFLOAT16_CONVERSIONS__"
265+
"-U__CUDA_NO_BFLOAT162_OPERATORS__"
266+
"-U__CUDA_NO_BFLOAT162_CONVERSIONS__"
267+
"--expt-relaxed-constexpr"
268+
"--expt-extended-lambda"
269+
"--use_fast_math"
270+
"--ptxas-options=--verbose,--warn-on-local-memory-usage"
271+
"-lineinfo"
272+
"-DCUTLASS_DEBUG_TRACE_LEVEL=0"
273+
"-DNDEBUG"
274+
"-DQBLKSIZE=128"
275+
"-DKBLKSIZE=128"
276+
"-DCTA256"
277+
"-DDQINRMEM"
278+
)
279+
280+
Python_add_library(fp4attn_cuda MODULE WITH_SOABI
281+
attn_qat_infer/blackwell/api.cu
282+
)
283+
target_include_directories(fp4attn_cuda PRIVATE ${ATTN_QAT_INFER_INCLUDE_DIRS})
284+
target_compile_definitions(fp4attn_cuda PRIVATE TORCH_EXTENSION_NAME=fp4attn_cuda)
285+
target_compile_options(fp4attn_cuda PRIVATE
286+
$<$<COMPILE_LANGUAGE:CXX>:-O3 -std=c++17>
287+
$<$<COMPILE_LANGUAGE:CUDA>:${ATTN_QAT_INFER_CUDA_FLAGS}>
288+
)
289+
set_target_properties(fp4attn_cuda PROPERTIES
290+
CUDA_ARCHITECTURES "120a"
291+
CXX_STANDARD 17
292+
CUDA_STANDARD 17
293+
)
294+
target_link_libraries(fp4attn_cuda PRIVATE ${TORCH_LIBRARIES} CUDA::cudart CUDA::cuda_driver)
295+
296+
Python_add_library(fp4quant_cuda MODULE WITH_SOABI
297+
attn_qat_infer/quantization/fp4_quantization_4d.cu
298+
)
299+
target_include_directories(fp4quant_cuda PRIVATE ${ATTN_QAT_INFER_INCLUDE_DIRS})
300+
target_compile_definitions(fp4quant_cuda PRIVATE TORCH_EXTENSION_NAME=fp4quant_cuda)
301+
target_compile_options(fp4quant_cuda PRIVATE
302+
$<$<COMPILE_LANGUAGE:CXX>:-O3 -std=c++17>
303+
$<$<COMPILE_LANGUAGE:CUDA>:${ATTN_QAT_INFER_CUDA_FLAGS}>
304+
)
305+
set_target_properties(fp4quant_cuda PROPERTIES
306+
CUDA_ARCHITECTURES "120a"
307+
CXX_STANDARD 17
308+
CUDA_STANDARD 17
309+
)
310+
target_link_libraries(fp4quant_cuda PRIVATE ${TORCH_LIBRARIES} CUDA::cudart CUDA::cuda_driver)
311+
312+
if(TORCH_PYTHON_LIBRARY_PATH)
313+
target_link_libraries(fp4attn_cuda PRIVATE "${TORCH_PYTHON_LIBRARY_PATH}")
314+
target_link_libraries(fp4quant_cuda PRIVATE "${TORCH_PYTHON_LIBRARY_PATH}")
315+
endif()
316+
317+
install(TARGETS fp4attn_cuda LIBRARY DESTINATION .)
318+
install(TARGETS fp4quant_cuda LIBRARY DESTINATION .)
319+
endif()
320+

fastvideo-kernel/MANIFEST.in

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,6 @@ include LICENSE
22
include README.md
33
include pyproject.toml
44
recursive-include python/fastvideo_kernel *.py
5+
recursive-include attn_qat_infer *.py *.cu *.cuh *.cpp *.h
56
recursive-include csrc *.cu *.cuh *.cpp *.h
67
recursive-include include/tk *.cu *.cuh *.cpp *.h *.src
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
"""
2+
Copyright (c) 2025 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+
from .api import sageattn_blackwell
Lines changed: 185 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
# Modified from the original SageATtention3 code
2+
"""
3+
Copyright (c) 2025 by SageAttention team.
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
"""
17+
import torch
18+
import triton
19+
import triton.language as tl
20+
import torch.nn.functional as F
21+
from typing import Tuple
22+
from torch.nn.functional import scaled_dot_product_attention as sdpa
23+
import fp4attn_cuda
24+
import fp4quant_cuda
25+
26+
# Centralized block size configuration for sageattn_blackwell kernels
27+
# These should match the values in fastvideo/attention/backends/sageattn/blackwell/block_config.h
28+
BLOCK_M = 128 # Block size for M dimension (query sequence length)
29+
BLOCK_N = 128 # Block size for N dimension (key/value sequence length)
30+
31+
32+
@triton.jit
33+
def group_mean_kernel(
34+
q_ptr,
35+
q_out_ptr,
36+
qm_out_ptr,
37+
B, H, L, D: tl.constexpr,
38+
stride_qb, stride_qh, stride_ql, stride_qd,
39+
stride_qmb, stride_qmh, stride_qml, stride_qmd,
40+
GROUP_SIZE: tl.constexpr
41+
):
42+
pid_b = tl.program_id(0)
43+
pid_h = tl.program_id(1)
44+
pid_group = tl.program_id(2)
45+
46+
group_start = pid_group * GROUP_SIZE
47+
offsets = group_start + tl.arange(0, GROUP_SIZE)
48+
49+
q_offsets = pid_b * stride_qb + pid_h * stride_qh + offsets[:, None] * stride_ql + tl.arange(0, D)[None, :] * stride_qd
50+
q_group = tl.load(q_ptr + q_offsets)
51+
52+
qm_group = tl.sum(q_group, axis=0) / GROUP_SIZE
53+
54+
q_group = q_group - qm_group
55+
tl.store(q_out_ptr + q_offsets, q_group)
56+
57+
qm_offset = pid_b * stride_qmb + pid_h * stride_qmh + pid_group * stride_qml + tl.arange(0, D) * stride_qmd
58+
tl.store(qm_out_ptr + qm_offset, qm_group)
59+
60+
61+
def triton_group_mean(q: torch.Tensor):
62+
B, H, L, D = q.shape
63+
GROUP_SIZE = BLOCK_M
64+
num_groups = L // GROUP_SIZE
65+
66+
q_out = torch.empty_like(q) # [B, H, L, D]
67+
qm = torch.empty(B, H, num_groups, D, device=q.device, dtype=q.dtype)
68+
69+
grid = (B, H, num_groups)
70+
71+
group_mean_kernel[grid](
72+
q, q_out, qm,
73+
B, H, L, D,
74+
q.stride(0), q.stride(1), q.stride(2), q.stride(3),
75+
qm.stride(0), qm.stride(1), qm.stride(2), qm.stride(3),
76+
GROUP_SIZE=GROUP_SIZE
77+
)
78+
return q_out, qm
79+
80+
81+
def preprocess_qkv(q: torch.Tensor, k: torch.Tensor, v: torch.Tensor, per_block_mean: bool = True, enable_smoothing_q: bool = False, enable_smoothing_k: bool = False):
82+
83+
def pad_to_block_size(x):
84+
L = x.size(2)
85+
pad_len = (BLOCK_M - L % BLOCK_M) % BLOCK_M
86+
if pad_len == 0:
87+
return x.contiguous()
88+
return F.pad(x, (0, 0, 0, pad_len), value=0).contiguous()
89+
90+
if enable_smoothing_k:
91+
k -= k.mean(dim=-2, keepdim=True)
92+
q, k, v = map(lambda x: pad_to_block_size(x), [q, k, v])
93+
if per_block_mean and enable_smoothing_q:
94+
q, qm = triton_group_mean(q)
95+
elif enable_smoothing_q:
96+
qm = q.mean(dim=-2, keepdim=True)
97+
q = q - qm
98+
if enable_smoothing_q:
99+
delta_s = torch.matmul(qm, k.transpose(-2, -1)).to(torch.float32).contiguous()
100+
else: # used to disable q smoothing
101+
B, H, L, D = q.shape
102+
delta_s = torch.zeros((B, H, L // BLOCK_M, k.shape[2]), device=q.device, dtype=torch.float32)
103+
104+
return q, k, v, delta_s
105+
106+
def scale_and_quant_fp4(x: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
107+
assert x.ndim == 4
108+
B, H, N, D = x.shape
109+
packed_fp4 = torch.empty((B, H, N, D // 2), device=x.device, dtype=torch.uint8)
110+
fp8_scale = torch.empty((B, H, N, D // 16), device=x.device, dtype=torch.float8_e4m3fn)
111+
fp4quant_cuda.scaled_fp4_quant(x, packed_fp4, fp8_scale, 1)
112+
return packed_fp4, fp8_scale
113+
114+
def scale_and_quant_fp4_permute(x: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
115+
assert x.ndim == 4
116+
B, H, N, D = x.shape
117+
packed_fp4 = torch.empty((B, H, N, D // 2), device=x.device, dtype=torch.uint8)
118+
fp8_scale = torch.empty((B, H, N, D // 16), device=x.device, dtype=torch.float8_e4m3fn)
119+
fp4quant_cuda.scaled_fp4_quant_permute(x, packed_fp4, fp8_scale, 1)
120+
return packed_fp4, fp8_scale
121+
122+
def scale_and_quant_fp4_transpose(x: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
123+
assert x.ndim == 4
124+
B, H, N, D = x.shape
125+
packed_fp4 = torch.empty((B, H, D, N // 2), device=x.device, dtype=torch.uint8)
126+
fp8_scale = torch.empty((B, H, D, N // 16), device=x.device, dtype=torch.float8_e4m3fn)
127+
fp4quant_cuda.scaled_fp4_quant_trans(x, packed_fp4, fp8_scale, 1)
128+
return packed_fp4, fp8_scale
129+
130+
def blockscaled_fp4_attn(qlist: Tuple,
131+
klist: Tuple,
132+
vlist: Tuple,
133+
delta_s: torch.Tensor,
134+
KL: int,
135+
is_causal: bool = False,
136+
per_block_mean: bool = True,
137+
is_bf16: bool = True,
138+
single_level_p_quant: bool = False
139+
):
140+
softmax_scale = (qlist[0].shape[-1] * 2) ** (-0.5)
141+
return fp4attn_cuda.fwd(qlist[0], klist[0], vlist[0], qlist[1], klist[1], vlist[1], delta_s, KL, None, softmax_scale, is_causal, per_block_mean, is_bf16, single_level_p_quant)
142+
143+
144+
def sageattn_blackwell(q, k, v, attn_mask = None, is_causal = False, per_block_mean = True, single_level_p_quant = True, **kwargs):
145+
"""
146+
SageAttention3 Blackwell kernel for FP4 attention.
147+
148+
Args:
149+
q: Query tensor [B, H, L, D]
150+
k: Key tensor [B, H, L, D]
151+
v: Value tensor [B, H, L, D]
152+
attn_mask: Attention mask (not used)
153+
is_causal: Whether to use causal masking
154+
per_block_mean: Whether to use per-block mean for Q smoothing
155+
single_level_p_quant: If True, use single-level quantization: s_P2, P̂_2 = φ(P̃) directly
156+
(standard per-block FP4 quantization like V, no s_P1).
157+
If False (default), use two-level quantization:
158+
s_P1 = rowmax(P̃)/(448×6), then s_P2, P̂_2 = φ(P̃/s_P1).
159+
**kwargs: Additional arguments (ignored)
160+
161+
Returns:
162+
Output tensor [B, H, L, D]
163+
"""
164+
if q.size(-1) >= 256:
165+
print(f"Unsupported Headdim {q.size(-1)}")
166+
return sdpa(q, k, v, is_causal = is_causal)
167+
QL = q.size(2)
168+
KL = k.size(2)
169+
is_bf16 = q.dtype == torch.bfloat16
170+
q, k, v, delta_s = preprocess_qkv(q, k, v, per_block_mean)
171+
qlist_from_cuda = scale_and_quant_fp4(q)
172+
klist_from_cuda = scale_and_quant_fp4_permute(k)
173+
vlist_from_cuda = scale_and_quant_fp4_transpose(v)
174+
o_fp4 = blockscaled_fp4_attn(
175+
qlist_from_cuda,
176+
klist_from_cuda,
177+
vlist_from_cuda,
178+
delta_s,
179+
KL,
180+
is_causal,
181+
per_block_mean,
182+
is_bf16,
183+
single_level_p_quant
184+
)[0][:, :, :QL, :].contiguous()
185+
return o_fp4
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
__version__ = "3.0.0.b1"

0 commit comments

Comments
 (0)