Files
hao-ai-lab-FastVideo/fastvideo/layers/fp4linear.py
T

117 lines
3.5 KiB
Python

from typing import Any
import torch
try:
import flashinfer
except ImportError:
flashinfer = None
def _require_flashinfer() -> Any:
if flashinfer is None:
raise ImportError("flashinfer is required for FP4 linear layers. "
"Please install flashinfer to use this path.")
return flashinfer
@torch.compile
def _global_sf(t: torch.Tensor) -> torch.Tensor:
maxabs = t.float().abs().nan_to_num().max()
maxabs = maxabs.clamp(min=1e-12)
return (448.0 * 6.0) / maxabs
class _LinearFWD4BWD16Fn(torch.autograd.Function):
@staticmethod
def forward(ctx, x, weight, bias, backend="cutlass", block_size=16, use_128x4_sf_layout=True):
flashinfer_mod = _require_flashinfer()
# assert activation dtype
if x.dtype not in (torch.float16, torch.bfloat16):
x = x.to(dtype=torch.bfloat16)
# cast params (can be fp32) to activation dtype for quantization
weight_cast = weight.to(dtype=x.dtype)
bias_cast = bias.to(dtype=x.dtype) if bias is not None else None
# shapes
orig_shape = x.shape
k = weight_cast.shape[1]
n = weight_cast.shape[0]
x2d = x.reshape(-1, k).contiguous()
M = x2d.shape[0]
out2d = torch.empty((M, n), device=x.device, dtype=x.dtype)
a_sf_layout = (flashinfer_mod.SfLayout.layout_128x4
if use_128x4_sf_layout else flashinfer_mod.SfLayout.layout_8x4)
global_sf_a = _global_sf(x2d)
global_sf_b = _global_sf(weight_cast)
a_fp4, a_inv_s = flashinfer_mod.nvfp4_quantize(
x2d,
global_sf_a,
sfLayout=a_sf_layout,
do_shuffle=False,
)
b_fp4, b_inv_s = flashinfer_mod.nvfp4_quantize(
weight_cast,
global_sf_b,
sfLayout=flashinfer_mod.SfLayout.layout_128x4,
do_shuffle=False,
)
alpha = 1.0 / (global_sf_a * global_sf_b)
flashinfer_mod.mm_fp4(
a_fp4,
b_fp4.T,
a_inv_s,
b_inv_s.T,
alpha,
x.dtype,
out2d,
block_size=block_size,
use_8x4_sf_layout=(not use_128x4_sf_layout),
backend=backend,
)
if bias_cast is not None:
out2d.add_(bias_cast)
# save tensors for backward (keep original dtypes)
ctx.save_for_backward(x2d, weight, bias)
ctx.k = k
ctx.n = n
ctx.orig_shape = orig_shape
return out2d.reshape(*orig_shape[:-1], n)
@staticmethod
def backward(ctx, grad_out):
x2d, weight, bias = ctx.saved_tensors
M = x2d.shape[0]
n = ctx.n
grad_out_2d = grad_out.reshape(M, n).contiguous()
# cast to grad dtype for matmuls
weight_cast = weight.to(dtype=grad_out.dtype)
x_cast = x2d.to(dtype=grad_out.dtype)
grad_x = grad_out_2d.matmul(weight_cast).reshape(*ctx.orig_shape)
grad_w = grad_out_2d.t().matmul(x_cast)
grad_b = grad_out_2d.sum(dim=0) if bias is not None else None
# None for the three extra forward args
return grad_x, grad_w, grad_b, None, None, None
def fp4_linear_forward(self, x: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor | None]:
# pass config **positionally**; autograd.Function.apply ignores kwargs
bias = self.bias if not self.skip_bias_add else None
output = _LinearFWD4BWD16Fn.apply(x, self.weight, bias, "cutlass", 16, True)
output_bias = self.bias if self.skip_bias_add else None
return output, output_bias