Image-2.1-Calibrated-NVFP4 / source /probe_dynamic.py
ProCreations's picture
Release calibrated Image2.1 NVFP4 transformer with dynamic scaling and BF16 rank correction, native SM120 runtime, quality evidence and real-time demo
1961af5 verified
Raw History Blame Contribute Delete
3.48 kB
"""Calibrate from original BF16 and measure actual native W4A4 + BF16 correction."""
import os,json,time,math
from pathlib import Path
import torch
from safetensors import safe_open
from safetensors.torch import load_file
from flashinfer import nvfp4_quantize,mm_fp4
from flashinfer.gemm import nvfp4_quantize_smooth,mm_nvfp4_svdquant
BASE=Path(os.environ['IMAGE21_BASE'])/'transformer'
OLD=Path(os.environ['IMAGE21_WORK_DIR'])
INDEX=json.loads((BASE/'diffusion_pytorch_model.safetensors.index.json').read_text())['weight_map']
def weight(name):
with safe_open(BASE/INDEX[name+'.weight'],framework='pt',device='cuda') as f:return f.get_tensor(name+'.weight')
def split(x):
g=torch.arange(len(x)//4,device=x.device)
return x[g*4+g%4].contiguous(),x[g*4+(g+1)%4].contiguous()
def error(y,ref):return ((y.float()-ref.float()).square().mean()/ref.float().square().mean()).sqrt().item()
def prepare(w,x,smoothing,rank,method="svd",mse=False):
amax=x.float().abs().amax(0).clamp_min(1e-5);wmax=w.float().abs().amax(0).clamp_min(1e-5)
smooth=torch.ones_like(amax) if smoothing==0 else (amax**smoothing/wmax**(1-smoothing)).clamp(.01,100)
smooth/=smooth.log().mean().exp()
pre=(1/smooth).to(torch.bfloat16)
ws=w.float()/pre.float()[None,:]
if rank:
torch.manual_seed(1234)
if method=='svd':
u,s,v=torch.svd_lowrank(ws,q=rank+32,niter=3)
up=u[:,:rank]*s[:rank];down=v[:,:rank]
elif method=='output':
u,s,v=torch.svd_lowrank(ws@(x.float()*pre.float()).T,q=rank+32,niter=3)
up=u[:,:rank];down=ws.T@up
else:
u,s,v=torch.svd_lowrank(x.float()*pre.float(),q=rank+32,niter=3)
down=v[:,:rank];up=ws@down
residual=ws-up@down.T
else:residual=ws
gx=(2688/((x.float()*pre.float()).abs().max()*1.2)).reshape(1)
gw=(2688/residual.abs().max()).reshape(1)
if mse:
from weight_quant import quantize_weight
h=(x.float()*pre.float()).square().mean(0)
h=h+.01*h.mean()
wq,wsc=quantize_weight(residual,gw,h)
else:wq,wsc=nvfp4_quantize(residual.to(torch.bfloat16),gw,backend='cute-dsl')
alpha=(1/(gx*gw)).reshape(1)
result={'weight':wq.view(torch.uint8),'sf':wsc.view(torch.uint8).reshape(-1),'pre':pre,'gx':gx,'alpha':alpha}
if rank:result.update(down=(pre.float()[:,None]*down).to(torch.bfloat16).contiguous(),up=(up/alpha).to(torch.bfloat16).contiguous())
return result
def run(x,p):
from dynamic_scale import scale
gx,alpha,up=scale(x,p['pre'],p['gx'],p['alpha'],p['up']) if 'up' in p else (p['gx'],p['alpha'],None)
q,sf=nvfp4_quantize_smooth(x,p['pre'],gx,backend='cute-dsl')
if 'down' in p:
d=x@p['down']
return mm_nvfp4_svdquant(q,p['weight'],sf,p['sf'],alpha,d,up,backend='cute-dsl')
return mm_fp4(q,p['weight'].T,sf,p['sf'],p['alpha'],backend='b12x')
@torch.inference_mode()
def main():
rows=[]
for block,leaf in [(0,'attn.to_q'),(0,'img_mlp.out'),(8,'attn.to_v'),(16,'img_mlp.proj'),(16,'img_mlp.gate_layer'),(24,'attn.to_out.0'),(31,'attn.to_k')]:
name=f'transformer_blocks.{block}.{leaf}';w=weight(name)
x=load_file(str(OLD/'calibration'/f'{name}.safetensors'))['activations'].cuda();fit,check=split(x)
rf=fit@w.T;rc=check@w.T
for smooth in [0.,.5,1.]:
for rank in [0,128]:
start=time.time();p=prepare(w,fit,smooth,rank);y=run(fit,p);yc=run(check,p)
row={'name':name,'smooth':smooth,'rank':rank,'fit_nrmse':error(y,rf),'check_nrmse':error(yc,rc),'seconds':time.time()-start};rows.append(row);print(json.dumps(row),flush=True)
Path('probe.json').write_text(json.dumps(rows,indent=2))
if __name__=='__main__':main()