Anima-Role-Ref-KV / runtime.py
AnticPan's picture
Support BF16 v2.0 and v1.0 adapter switching
b04cb99 verified
Raw History Blame Contribute Delete
3.46 kB
"""One resident pipeline; original backbone tensors are never cumulatively merged."""
from pathlib import Path
import torch
from anima_role_ref_kv.components import load_transformer, PromptEncoder, VAE, NEGATIVE
from anima_role_ref_kv.pipeline import sample, resize_reference
from anima_role_ref_kv.weights import load, read_state, swap
from checkpoints import native_plan, diffusers_plan, copy_plan
class Runtime:
def __init__(self, base_dir, adapter_file, device="cuda", adapter_key="v2.0"):
self.base_dir = str(base_dir)
self.net = load_transformer(base_dir, device)
self.text = PromptEncoder(base_dir, device)
self.vae = VAE(base_dir, device)
# Capture references before LoRALinear wraps linears. The adapter keeps
# its frozen base outside its registered parameters; net.state_dict()
# after injection would omit those base weights.
self.targets = {("transformer", k): v for k, v in self.net.state_dict(keep_vars=True).items()}
self.targets.update({("conditioner", k): v for k, v in
self.text.components.text_conditioner.state_dict(keep_vars=True).items()})
self.base_plan = diffusers_plan(base_dir, self.targets)
self.adapter_states = {adapter_key: read_state(self.net, adapter_file)}
self.adapter = load(self.net, adapter_file)
self.current = "base-v1.0"
self.current_adapter = adapter_key
def select(self, model_key, filename=None):
if self.current == model_key:
return
plan = (self.base_plan if model_key == "base-v1.0" else
(diffusers_plan(filename, self.targets) if Path(filename).is_dir() else
native_plan(filename, self.targets)))
self.current = None # A failed copy must force a complete reload next time.
copy_plan(plan, self.targets)
self.current = model_key
def select_adapter(self, adapter_key, filename=None):
if self.current_adapter == adapter_key:
return
state = self.adapter_states.get(adapter_key)
if state is None:
if not filename:
raise ValueError(f"Adapter file required for {adapter_key}")
state = read_state(self.net, filename)
self.adapter_states[adapter_key] = state
self.current_adapter = None # A failed copy must force a complete reload next time.
swap(self.adapter, state)
self.current_adapter = adapter_key
@torch.no_grad()
def generate(self, model_key, filename, reference, prompt, *, adapter_key=None,
adapter_file=None, strength=1.0, negative_prompt=NEGATIVE,
progress_callback=None, **settings):
self.select(model_key, filename)
if adapter_key is not None:
self.select_adapter(adapter_key, adapter_file)
for layer in self.adapter.layers.values():
layer.multiplier = strength
cfg = settings.get("cfg", 4.)
positive = self.text([prompt])
negative = self.text([negative_prompt]) if cfg != 1 else None
ref = self.vae.encode(resize_reference(reference))
latent = sample(self.net, positive, negative, ref,
progress_callback=progress_callback, **settings)
if not torch.isfinite(latent).all():
raise FloatingPointError("The model produced non-finite latents")
return self.vae.decode(latent)