Text-to-Image
Diffusers
anima
lora
in-context
character-reference
ip-adapter-alternative
comfyui
anime
Instructions to use darask0/Anima-InContext-Character with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use darask0/Anima-InContext-Character with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("circlestone-labs/Anima", torch_dtype=torch.bfloat16, device_map="cuda") pipe.load_lora_weights("darask0/Anima-InContext-Character") prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k" image = pipe(prompt).images[0] - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- Draw Things
- DiffusionBee
| """ComfyUI nodes for the Anima style stream (decoupled cross-attention). | |
| Requires a trained AnimaStyleAdapter checkpoint (see style_adapter.py | |
| and the project docs); with an untrained adapter the gates are zero and | |
| the nodes are an exact no-op. | |
| The style stream composes freely with the in-context character stream: | |
| AnimaInContextApply patches self-attention (T-axis reference frames), | |
| AnimaStyleApply patches cross-attention — chain both Apply nodes and | |
| control char/style strength independently. | |
| """ | |
| import os | |
| import torch | |
| import comfy.utils | |
| import folder_paths | |
| from comfy.patcher_extension import WrappersMP | |
| from .style_adapter import AnimaStyleAdapter, StyleState | |
| STYLE_WRAPPER_KEY = "anima_style_ref" | |
| _ADAPTER_DIR = "anima_style_adapters" | |
| if _ADAPTER_DIR not in folder_paths.folder_names_and_paths: | |
| folder_paths.add_model_folder_path( | |
| _ADAPTER_DIR, os.path.join(folder_paths.models_dir, _ADAPTER_DIR) | |
| ) | |
| class AnimaStyleAdapterLoader: | |
| def INPUT_TYPES(cls): | |
| return { | |
| "required": { | |
| "adapter_name": (folder_paths.get_filename_list(_ADAPTER_DIR),), | |
| }, | |
| } | |
| RETURN_TYPES = ("ANIMA_STYLE_ADAPTER",) | |
| FUNCTION = "load" | |
| CATEGORY = "anima/style" | |
| def load(self, adapter_name): | |
| path = folder_paths.get_full_path_or_raise(_ADAPTER_DIR, adapter_name) | |
| sd = comfy.utils.load_torch_file(path, safe_load=True) | |
| adapter = AnimaStyleAdapter.from_state_dict(sd) | |
| adapter.eval() | |
| return (adapter,) | |
| class AnimaStyleEncode: | |
| """Encode style reference image(s) with a SigLIP CLIP_VISION model, | |
| keeping the hidden-state stack the adapter aggregates over. | |
| Multiple images are encoded independently and their patch tokens are | |
| concatenated at apply time (attention pools over all of them).""" | |
| def INPUT_TYPES(cls): | |
| return { | |
| "required": { | |
| "clip_vision": ("CLIP_VISION",), | |
| "image": ("IMAGE",), | |
| }, | |
| } | |
| RETURN_TYPES = ("ANIMA_STYLE_EMBEDS",) | |
| FUNCTION = "encode" | |
| CATEGORY = "anima/style" | |
| def encode(self, clip_vision, image): | |
| out = clip_vision.encode_image(image) | |
| hs = out["all_hidden_states"] # (B, L, N, D) | |
| if hs is None: | |
| raise RuntimeError( | |
| "this CLIP_VISION model does not expose all hidden states; " | |
| "use a SigLIP vision model" | |
| ) | |
| return ({"hidden_states": hs},) | |
| class AnimaStyleApply: | |
| """Attach the style stream to an Anima model. | |
| style_weight: global multiplier on the (learned, per-block) gates. | |
| cond_only: apply style only to the cond half of the CFG batch. | |
| start/end_percent: sampling window, same semantics as the | |
| in-context Apply node. | |
| """ | |
| def INPUT_TYPES(cls): | |
| return { | |
| "required": { | |
| "model": ("MODEL",), | |
| "style_adapter": ("ANIMA_STYLE_ADAPTER",), | |
| "style_embeds": ("ANIMA_STYLE_EMBEDS",), | |
| "style_weight": ("FLOAT", {"default": 1.0, "min": -2.0, "max": 5.0, "step": 0.05}), | |
| "start_percent": ("FLOAT", {"default": 0.0, "min": 0.0, "max": 1.0, "step": 0.001}), | |
| "end_percent": ("FLOAT", {"default": 1.0, "min": 0.0, "max": 1.0, "step": 0.001}), | |
| }, | |
| "optional": { | |
| "cond_only": ("BOOLEAN", {"default": True}), | |
| }, | |
| } | |
| RETURN_TYPES = ("MODEL",) | |
| FUNCTION = "apply" | |
| CATEGORY = "anima/style" | |
| def apply(self, model, style_adapter, style_embeds, style_weight, | |
| start_percent, end_percent, cond_only=True): | |
| m = model.clone() | |
| ms = m.get_model_object("model_sampling") | |
| sigma_start = ms.percent_to_sigma(start_percent) | |
| sigma_end = ms.percent_to_sigma(end_percent) | |
| dm = m.get_model_object("diffusion_model") | |
| n_layers = style_adapter.config["n_layers"] | |
| hidden_states = style_embeds["hidden_states"][:, -n_layers:] # (B, K, N, D) | |
| state = StyleState() | |
| # per-(device, dtype) cache of the per-block K/V — the style | |
| # tokens are constant across sampling steps | |
| kv_cache = {} | |
| def compute_kv(device, dtype): | |
| key = (device, dtype) | |
| if key not in kv_cache: | |
| adapter = style_adapter.to(device) | |
| hs = hidden_states.to(device=device, dtype=next(adapter.parameters()).dtype) | |
| with torch.no_grad(): | |
| tokens = adapter.aggregator(hs) # (B, N, style_dim) | |
| # multiple style images -> one long token sequence | |
| tokens = tokens.reshape(1, -1, tokens.shape[-1]) | |
| kv_cache[key] = [ | |
| tuple(t.to(dtype) for t in blk.kv(tokens)) for blk in adapter.blocks | |
| ] | |
| return kv_cache[key] | |
| def wrapper(executor, x, timesteps, context, fps=None, padding_mask=None, **kwargs): | |
| to = kwargs.get("transformer_options", {}) | |
| sigmas = to.get("sigmas", None) | |
| if sigmas is not None: | |
| s = float(sigmas.max()) | |
| if s > sigma_start or s < sigma_end: | |
| return executor(x, timesteps, context, fps, padding_mask, **kwargs) | |
| state.kv_per_block = compute_kv(x.device, x.dtype) | |
| state.weight = style_weight | |
| state.sample_scale_B = None | |
| cou = to.get("cond_or_uncond", None) | |
| if cond_only and cou and x.shape[0] % len(cou) == 0: | |
| chunk = x.shape[0] // len(cou) | |
| scale = torch.ones(x.shape[0], device=x.device) | |
| for i, kind in enumerate(cou): | |
| if kind == 1: | |
| scale[i * chunk:(i + 1) * chunk] = 0.0 | |
| state.sample_scale_B = scale | |
| state.active = True | |
| try: | |
| return executor(x, timesteps, context, fps, padding_mask, **kwargs) | |
| finally: | |
| state.active = False | |
| m.add_wrapper_with_key(WrappersMP.DIFFUSION_MODEL, STYLE_WRAPPER_KEY, wrapper) | |
| from .style_adapter import StyleCrossAttention | |
| for i, block in enumerate(dm.blocks): | |
| m.add_object_patch( | |
| "diffusion_model.blocks.{}.cross_attn".format(i), | |
| StyleCrossAttention(block.cross_attn, style_adapter.blocks[i], state, i), | |
| ) | |
| return (m,) | |
| NODE_CLASS_MAPPINGS = { | |
| "AnimaStyleAdapterLoader": AnimaStyleAdapterLoader, | |
| "AnimaStyleEncode": AnimaStyleEncode, | |
| "AnimaStyleApply": AnimaStyleApply, | |
| } | |
| NODE_DISPLAY_NAME_MAPPINGS = { | |
| "AnimaStyleAdapterLoader": "Anima Style Adapter Loader", | |
| "AnimaStyleEncode": "Anima Style Encode (SigLIP)", | |
| "AnimaStyleApply": "Anima Style Apply", | |
| } | |