| from __future__ import annotations |
| import os,sys |
| from pathlib import Path |
| import torch |
|
|
| class StudentFlowRuntime: |
| """Unified evaluation/runtime wrapper for v7 students before ONNX/TRT promotion. |
| |
| Loads the original AniGen flow once, merges a chain of compact LoRA stage adapters, |
| then optionally applies velocity symmetry guidance and time-aware residual caching. |
| The wrapper preserves the original AniGen model call signature, so existing samplers |
| and pipelines can use it without changes. |
| """ |
| def __init__(self,model,steps:int,cache_enabled:bool=True,cache_threshold=.985,cache_max_reuse=2,symmetry_group:str='none',symmetry_weight:float=0.0,symmetry_every:int=1,sparse_resolution:int=64): |
| self.model=model;self.steps=int(steps);self.symmetry_group=symmetry_group;self.symmetry_weight=float(symmetry_weight);self.symmetry_every=max(1,int(symmetry_every));self.sparse_resolution=int(sparse_resolution);self.calls=0 |
| self.cached=None |
| if cache_enabled and self.steps>=4: |
| from .timestep_cache import CachedFlowModel |
| self.cached=CachedFlowModel(model,threshold=cache_threshold,max_reuse=cache_max_reuse,max_timestep_gap=max(1.0,1100.0/self.steps)) |
| def reset(self): |
| self.calls=0 |
| if self.cached:self.cached.reset() |
| def _base(self,x,xs,t,cond,*args,**kwargs): |
| return self.cached(x,xs,t,cond,*args,**kwargs) if self.cached else self.model(x,xs,t,cond,*args,**kwargs) |
| def __call__(self,x,xs,t,cond,*args,**kwargs): |
| self.calls+=1 |
| if self.symmetry_weight<=0 or self.symmetry_group in ('','none') or (self.calls-1)%self.symmetry_every: |
| return self._base(x,xs,t,cond,*args,**kwargs) |
| |
| |
| from .symmetry import symmetrized_velocity |
| v0,s0=self._base(x,xs,t,cond,*args,**kwargs) |
| vg,sg=symmetrized_velocity(self.model,x,xs,t,cond,group=self.symmetry_group,res=self.sparse_resolution,*args,**kwargs) |
| w=self.symmetry_weight |
| if hasattr(v0,'replace'): |
| return v0.replace(feats=v0.feats*(1-w)+vg.feats*w),s0.replace(feats=s0.feats*(1-w)+sg.feats*w) |
| return v0*(1-w)+vg*w,s0*(1-w)+sg*w |
| @property |
| def metrics(self): |
| return {'steps':self.steps,'calls':self.calls,'cache_hit_rate':self.cached.hit_rate if self.cached else 0.0,'symmetry_group':self.symmetry_group,'symmetry_weight':self.symmetry_weight} |
|
|
|
|
| def load_student(component:str,adapter_paths:list[str]|None=None,students_repo='patdev/Companion-Forge-v7-Students',token=None,app_root='/home/user/app',**runtime_kwargs): |
| """Load base AniGen and merge an ordered adapter chain (10→4, 4→2, 2→1).""" |
| |
| runtime_repo_root=Path(__file__).resolve().parents[1] |
| if str(runtime_repo_root.parent) not in sys.path:sys.path.insert(0,str(runtime_repo_root.parent)) |
| from v7.training.distill_flow import load_anigen_model |
| from v7.training.lora import apply_adapter |
| from huggingface_hub import hf_hub_download |
| model,_=load_anigen_model(component,root=app_root);model.eval() |
| merged=[] |
| for path in adapter_paths or []: |
| local=hf_hub_download(students_repo,path,repo_type='model',token=token or os.environ.get('HF_TOKEN')) |
| state=torch.load(local,map_location='cpu',weights_only=False);merged+=apply_adapter(model,state) |
| steps=int(runtime_kwargs.pop('steps',1 if adapter_paths and any('2to1' in p for p in adapter_paths) else (2 if adapter_paths and any('4to2' in p for p in adapter_paths) else 4))) |
| rt=StudentFlowRuntime(model,steps=steps,**runtime_kwargs);rt.merged_adapter_modules=merged;return rt |
|
|
|
|
| def aggregate_multiview_conditions(cond_dicts:list[dict],mode='mean'): |
| """Shape-preserving multi-view fusion usable immediately with existing 1374-token engines. |
| |
| Mean fusion requires no learned weights and is therefore the safe fallback. A trained |
| pose-aware MultiViewAggregator can replace it later while keeping the same output shape. |
| """ |
| if not cond_dicts:raise ValueError('At least one view conditioning dict is required') |
| if len(cond_dicts)==1:return cond_dicts[0] |
| if mode!='mean':raise ValueError('Only weight-free mean fusion is production-safe before aggregator training') |
| out={} |
| keys=set().union(*(d.keys() for d in cond_dicts)) |
| for k in keys: |
| vals=[d[k] for d in cond_dicts if k in d] |
| if vals and torch.is_tensor(vals[0]) and vals[0].is_floating_point() and all(v.shape==vals[0].shape for v in vals):out[k]=torch.stack(vals).mean(0) |
| else:out[k]=vals[0] |
| return out |
|
|