| from __future__ import annotations |
|
|
| import argparse |
| import json |
| import os |
| import sys |
| from pathlib import Path |
|
|
| import torch |
| import torch.nn as nn |
| import torch.nn.functional as F |
|
|
| APP_ROOT = Path(os.environ.get('ANIGEN_APP_ROOT','/home/user/app')) |
| if str(APP_ROOT) not in sys.path: |
| sys.path.insert(0,str(APP_ROOT)) |
|
|
| DOMAIN='com.companionforge' |
| PLUGIN_NAMESPACE='companionforge' |
|
|
|
|
| |
| _lib = torch.library.Library("companionforge", "DEF") |
| try: |
| _lib.define("sparse_window_attention(Tensor qkv, Tensor coords, int window_size, int shift_x, int shift_y, int shift_z) -> Tensor") |
| except Exception: |
| pass |
|
|
| def _window_cuda(qkv, coords, window_size:int, shift_x:int, shift_y:int, shift_z:int): |
| from anigen.modules.sparse import SparseTensor |
| from anigen.modules.sparse.attention.windowed_attn import sparse_windowed_scaled_dot_product_self_attention |
| st=SparseTensor(qkv,coords) |
| return sparse_windowed_scaled_dot_product_self_attention(st,int(window_size),(int(shift_x),int(shift_y),int(shift_z))).feats |
| try: |
| _lib.impl("sparse_window_attention", _window_cuda, "CUDA") |
| except Exception: |
| pass |
|
|
| def _window_symbolic(g,qkv,coords,window_size,shift_x,shift_y,shift_z): |
| from torch.onnx.symbolic_helper import _get_const |
| ws=int(_get_const(window_size,'i','window_size'));sx=int(_get_const(shift_x,'i','shift_x'));sy=int(_get_const(shift_y,'i','shift_y'));sz=int(_get_const(shift_z,'i','shift_z')) |
| return g.op(f'{DOMAIN}::SparseWindowAttention',qkv,coords,window_size_i=ws,shift_x_i=sx,shift_y_i=sy,shift_z_i=sz,plugin_namespace_s=PLUGIN_NAMESPACE,plugin_version_s='1') |
| torch.onnx.register_custom_op_symbolic('companionforge::sparse_window_attention',_window_symbolic,18) |
|
|
|
|
| class Branch(nn.Module): |
| def __init__(self, decoder, branch:str): |
| super().__init__(); self.branch=branch |
| if branch=='geo': |
| self.input_layer=decoder.input_layer; self.pos=decoder.pos_embedder; self.blocks=decoder.blocks |
| self.channels=decoder.model_channels; self.heads=decoder.num_heads |
| self.kind='plain' |
| elif branch=='skin': |
| self.input_layer=decoder.input_layer_skin; self.pos=decoder.pos_embedder_skin; self.blocks=decoder.blocks_skin |
| self.channels=decoder.model_channels_skin; self.heads=self.blocks[0].self_attn.num_heads |
| self.kind='multi' |
| elif branch=='skl': |
| self.input_layer=decoder.input_layer_skl; self.pos=decoder.pos_embedder_skl; self.blocks=decoder.blocks_skl |
| self.channels=decoder.model_channels_skl; self.heads=self.blocks[0].self_attn.num_heads |
| self.kind='multi' |
| else: raise ValueError(branch) |
| self.register_buffer('freqs',self.pos.freqs.detach().clone(),persistent=True) |
| self.eps=1e-6; self.window_size=int(decoder.window_size) |
|
|
| def position(self,coords): |
| xyz=coords[:,1:].float(); flat=xyz.reshape(-1) |
| emb=torch.outer(flat,self.freqs) |
| emb=torch.cat([torch.sin(emb),torch.cos(emb)],dim=-1) |
| emb=emb.reshape(xyz.shape[0],-1) |
| if emb.shape[-1] < self.channels: |
| emb=torch.cat([emb,torch.zeros((emb.shape[0],self.channels-emb.shape[-1]),device=emb.device,dtype=emb.dtype)],dim=-1) |
| return emb |
|
|
| def _ln(self,x,norm): |
| w=norm.weight if getattr(norm,'elementwise_affine',False) else None |
| b=norm.bias if getattr(norm,'elementwise_affine',False) else None |
| return F.layer_norm(x.float(),tuple(norm.normalized_shape),w,b,self.eps).to(x.dtype) |
|
|
| def _attn(self,attn,x,coords,shift): |
| qkv=F.linear(x,attn.to_qkv.weight,attn.to_qkv.bias) |
| n=qkv.shape[0]; d=self.channels//attn.num_heads |
| qkv=qkv.reshape(n,3,attn.num_heads,d) |
| if getattr(attn,'qk_rms_norm',False): |
| q,k,v=qkv.unbind(1) |
| |
| q=(F.normalize(q.float(),dim=-1)*attn.q_rms_norm.gamma.float()*float(d**0.5)).to(q.dtype) |
| k=(F.normalize(k.float(),dim=-1)*attn.k_rms_norm.gamma.float()*float(d**0.5)).to(k.dtype) |
| qkv=torch.stack([q,k,v],dim=1) |
| y=torch.ops.companionforge.sparse_window_attention(qkv,coords,self.window_size,shift,shift,shift) |
| y=y.reshape(n,self.channels) |
| return F.linear(y,attn.to_out.weight,attn.to_out.bias) |
|
|
| def _mlp(self,mlp,x): |
| |
| a=mlp.mlp[0]; b=mlp.mlp[2] |
| x=F.linear(x,a.weight,a.bias) |
| x=F.gelu(x,approximate='tanh') |
| return F.linear(x,b.weight,b.bias) |
|
|
| def forward(self,feats,coords): |
| x=F.linear(feats.float(),self.input_layer.weight.float(),self.input_layer.bias.float() if self.input_layer.bias is not None else None) |
| x=(x+self.position(coords).to(x.dtype)).to(torch.float16) |
| for i,blk in enumerate(self.blocks): |
| shift=(self.window_size//2)*(i%2) |
| if self.kind=='plain': |
| h=self._ln(x,blk.norm1); h=self._attn(blk.attn,h,coords,shift); x=x+h |
| h=self._ln(x,blk.norm2); h=self._mlp(blk.mlp,h); x=x+h |
| else: |
| |
| h=self._ln(x,blk.norm1); h=self._attn(blk.self_attn,h,coords,shift); x=x+h |
| h=self._ln(x,blk.norm3); h=self._mlp(blk.mlp,h); x=x+h |
| return x |
|
|
|
|
| def export_one(decoder,branch,out:Path,sample_n:int=128): |
| m=Branch(decoder,branch).cuda().eval() |
| cin={'geo':decoder.latent_channels,'skin':decoder.latent_channels_vertskin,'skl':decoder.latent_channels_skl}[branch] |
| feats=torch.randn(sample_n,cin,device='cuda',dtype=torch.float16) |
| xyz=torch.randint(0,64,(sample_n,3),device='cuda',dtype=torch.int32) |
| coords=torch.cat([torch.zeros((sample_n,1),device='cuda',dtype=torch.int32),xyz],dim=1) |
| path=out/f'{branch}.onnx'; path.parent.mkdir(parents=True,exist_ok=True) |
| try: |
| torch.onnx.export( |
| m,(feats,coords),str(path), |
| input_names=['feats','coords'],output_names=['out_feats'], |
| dynamic_axes={'feats':{0:'N'},'coords':{0:'N'},'out_feats':{0:'N'}}, |
| opset_version=18,do_constant_folding=True,external_data=True,dynamo=False, |
| ) |
| except Exception as exc: |
| print('EXPORT_ERROR',branch,type(exc).__name__,str(exc)[:3000].replace('\n',' | '),flush=True) |
| raise SystemExit(31) |
| import onnx |
| model=onnx.load(str(path),load_external_data=True) |
| |
| if not any(x.domain==DOMAIN for x in model.opset_import): model.opset_import.append(onnx.helper.make_opsetid(DOMAIN,1)) |
| model.producer_name='Companion-Forge';model.producer_version='6.5-slat-dae-custom' |
| data_name=f'{branch}.onnx.data' |
| data_path=out/data_name |
| if data_path.exists(): data_path.unlink() |
| onnx.save_model(model,str(path),save_as_external_data=True,all_tensors_to_one_file=True,location=data_name,size_threshold=1024) |
| meta={'branch':branch,'input_channels':cin,'model_channels':m.channels,'heads':m.heads,'blocks':len(m.blocks),'window_size':m.window_size,'custom_op':f'{DOMAIN}::SparseWindowAttention'} |
| (out/f'{branch}.json').write_text(json.dumps(meta,indent=2)) |
| print('EXPORTED',branch,path,path.stat().st_size,flush=True) |
| del m; torch.cuda.empty_cache() |
|
|
|
|
| def main(): |
| ap=argparse.ArgumentParser();ap.add_argument('--model-root',default='/tmp/anigen-model');ap.add_argument('--out',default='/tmp/slat-dae-branches');args=ap.parse_args() |
| from huggingface_hub import snapshot_download |
| root=Path(args.model_root) |
| snapshot_download('VAST-AI/AniGen',token=os.environ.get('HF_TOKEN'),local_dir=root,allow_patterns=['ckpts/anigen/slat_dae/config.json','ckpts/anigen/slat_dae/ckpts/decoder_final.pt']) |
| os.chdir(root) |
| from anigen.utils.model_utils import load_decoder |
| decoder=load_decoder('ckpts/anigen/slat_dae','final','cuda') |
| out=Path(args.out) |
| for b in ('geo','skin','skl'): export_one(decoder,b,out) |
|
|
| if __name__=='__main__': main() |
|
|