Skip to content

Commit 460f6e3

Browse files
SolitaryThinkerjzhang38RandNMR73
authored
[feat]: Add NVFP4QAT linear layer (Attn-QAT 3/12) (hao-ai-lab#1350)
Co-authored-by: jzhang38 <42993249+jzhang38@users.noreply.github.com> Co-authored-by: RandNMR73 <99706358+RandNMR73@users.noreply.github.com>
1 parent d2ffec5 commit 460f6e3

1 file changed

Lines changed: 116 additions & 0 deletions

File tree

fastvideo/layers/fp4linear.py

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
from typing import Any
2+
3+
import torch
4+
5+
try:
6+
import flashinfer
7+
except ImportError:
8+
flashinfer = None
9+
10+
11+
def _require_flashinfer() -> Any:
12+
if flashinfer is None:
13+
raise ImportError("flashinfer is required for FP4 linear layers. "
14+
"Please install flashinfer to use this path.")
15+
return flashinfer
16+
17+
18+
@torch.compile
19+
def _global_sf(t: torch.Tensor) -> torch.Tensor:
20+
maxabs = t.float().abs().nan_to_num().max()
21+
maxabs = maxabs.clamp(min=1e-12)
22+
return (448.0 * 6.0) / maxabs
23+
24+
25+
class _LinearFWD4BWD16Fn(torch.autograd.Function):
26+
27+
@staticmethod
28+
def forward(ctx, x, weight, bias, backend="cutlass", block_size=16, use_128x4_sf_layout=True):
29+
flashinfer_mod = _require_flashinfer()
30+
31+
# assert activation dtype
32+
if x.dtype not in (torch.float16, torch.bfloat16):
33+
x = x.to(dtype=torch.bfloat16)
34+
35+
# cast params (can be fp32) to activation dtype for quantization
36+
weight_cast = weight.to(dtype=x.dtype)
37+
bias_cast = bias.to(dtype=x.dtype) if bias is not None else None
38+
39+
# shapes
40+
orig_shape = x.shape
41+
k = weight_cast.shape[1]
42+
n = weight_cast.shape[0]
43+
x2d = x.reshape(-1, k).contiguous()
44+
M = x2d.shape[0]
45+
46+
out2d = torch.empty((M, n), device=x.device, dtype=x.dtype)
47+
48+
a_sf_layout = (flashinfer_mod.SfLayout.layout_128x4
49+
if use_128x4_sf_layout else flashinfer_mod.SfLayout.layout_8x4)
50+
global_sf_a = _global_sf(x2d)
51+
global_sf_b = _global_sf(weight_cast)
52+
53+
a_fp4, a_inv_s = flashinfer_mod.nvfp4_quantize(
54+
x2d,
55+
global_sf_a,
56+
sfLayout=a_sf_layout,
57+
do_shuffle=False,
58+
)
59+
b_fp4, b_inv_s = flashinfer_mod.nvfp4_quantize(
60+
weight_cast,
61+
global_sf_b,
62+
sfLayout=flashinfer_mod.SfLayout.layout_128x4,
63+
do_shuffle=False,
64+
)
65+
66+
alpha = 1.0 / (global_sf_a * global_sf_b)
67+
68+
flashinfer_mod.mm_fp4(
69+
a_fp4,
70+
b_fp4.T,
71+
a_inv_s,
72+
b_inv_s.T,
73+
alpha,
74+
x.dtype,
75+
out2d,
76+
block_size=block_size,
77+
use_8x4_sf_layout=(not use_128x4_sf_layout),
78+
backend=backend,
79+
)
80+
81+
if bias_cast is not None:
82+
out2d.add_(bias_cast)
83+
84+
# save tensors for backward (keep original dtypes)
85+
ctx.save_for_backward(x2d, weight, bias)
86+
ctx.k = k
87+
ctx.n = n
88+
ctx.orig_shape = orig_shape
89+
return out2d.reshape(*orig_shape[:-1], n)
90+
91+
@staticmethod
92+
def backward(ctx, grad_out):
93+
x2d, weight, bias = ctx.saved_tensors
94+
M = x2d.shape[0]
95+
n = ctx.n
96+
97+
grad_out_2d = grad_out.reshape(M, n).contiguous()
98+
99+
# cast to grad dtype for matmuls
100+
weight_cast = weight.to(dtype=grad_out.dtype)
101+
x_cast = x2d.to(dtype=grad_out.dtype)
102+
103+
grad_x = grad_out_2d.matmul(weight_cast).reshape(*ctx.orig_shape)
104+
grad_w = grad_out_2d.t().matmul(x_cast)
105+
grad_b = grad_out_2d.sum(dim=0) if bias is not None else None
106+
107+
# None for the three extra forward args
108+
return grad_x, grad_w, grad_b, None, None, None
109+
110+
111+
def fp4_linear_forward(self, x: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor | None]:
112+
# pass config **positionally**; autograd.Function.apply ignores kwargs
113+
bias = self.bias if not self.skip_bias_add else None
114+
output = _LinearFWD4BWD16Fn.apply(x, self.weight, bias, "cutlass", 16, True)
115+
output_bias = self.bias if self.skip_bias_add else None
116+
return output, output_bias

0 commit comments

Comments
 (0)