hayriyigit's picture
decider LoRA fine-tune
8fe5d78 verified
Raw History Blame Contribute Delete
2.59 kB
"""FP8 (e4m3) linear layers for Hopper via torch._scaled_mm.
Weights: per-output-channel scales, quantised once. Activations: per-token dynamic scales.
Under torch.compile the quantisation ops fuse into the surrounding elementwise work."""
import torch, torch.nn as nn
E4M3_MAX = 448.0
def _quant_rowwise(x):
s = x.abs().amax(dim=-1, keepdim=True).float().clamp(min=1e-12) / E4M3_MAX
return (x.float() / s).clamp(-E4M3_MAX, E4M3_MAX).to(torch.float8_e4m3fn), s
class FP8Linear(nn.Module):
def __init__(self, lin: nn.Linear):
super().__init__()
wq, sw = _quant_rowwise(lin.weight.detach()) # [N,K] fp8, [N,1]
self.register_buffer("wq", wq.contiguous()) # [N,K]; passed as wq.t() -> [K,N] column-major, as _scaled_mm wants
self.register_buffer("sw_t", sw.t().contiguous()) # [1,N]
self.bias = None if lin.bias is None else nn.Parameter(lin.bias.detach().clone(), requires_grad=False)
self.in_features, self.out_features = lin.in_features, lin.out_features
self.out_dtype = lin.weight.dtype
def forward(self, x):
shp = x.shape[:-1]
x2 = x.reshape(-1, self.in_features)
xq, sx = _quant_rowwise(x2)
y = torch._scaled_mm(xq, self.wq.t(), scale_a=sx, scale_b=self.sw_t, bias=self.bias, out_dtype=self.out_dtype)
return y.reshape(*shp, self.out_features)
def convert_to_fp8(model, skip=("lm_head",), min_dim=1024):
"""Replace nn.Linear (with in/out >= min_dim) by FP8Linear in place. Returns count."""
n = 0
for name, mod in list(model.named_modules()):
for cname, child in list(mod.named_children()):
full = f"{name}.{cname}" if name else cname
if isinstance(child, nn.Linear) and not any(s in full for s in skip) and min(child.in_features, child.out_features) >= min_dim:
setattr(mod, cname, FP8Linear(child)); n += 1
return n
if __name__ == "__main__":
import time
lin = nn.Linear(2048, 6144, bias=False).cuda().to(torch.bfloat16)
f8 = FP8Linear(lin)
x = torch.randn(8192, 2048, device="cuda", dtype=torch.bfloat16)
ref = lin(x); got = f8(x)
print("rel err", ((ref.float() - got.float()).abs().mean() / ref.float().abs().mean()).item())
for f, name in [(lin, "bf16 linear"), (f8, "fp8 linear (eager)"), (torch.compile(f8), "fp8 linear (compiled)")]:
for _ in range(3): f(x)
torch.cuda.synchronize(); t = time.time()
for _ in range(20): f(x)
torch.cuda.synchronize(); print(f"{name:24s} {(time.time()-t)/20*1000:.3f} ms")