2026/8/11 1:24:36

【Bug已解决】[Anima] Add img2img capability 解决方案

【Bug已解决】[Anima] Add img2img capability 解决方案 【Bug已解决】[Anima] Add img2img capability 解决方案一、现象长什么样Anima 是一个动漫风格的图像生成模型diffusers 接入。它的 pipeline 最初只支持文生图txt2img用户想做「图生图」拿一张草图/参考图生成同构图时from diffusers import AnimaPipeline pipe AnimaPipeline.from_pretrained(user/Anima) out pipe(promptanime girl with blue hair, imagesketch, strength0.7)报错TypeError __call__() got an unexpected keyword argument image或者强行塞了image但被忽略# 没报错但 image 参数根本没用上输出和纯 txt2img 一样没参考输入图又或编码器不认图像输入AttributeError AnimaPipeline object has no attribute image_encoder现象总结Anima pipeline 只实现了 txt2img 主路径没有 img2img 分支——__call__不接受image/strength也没有把输入图编码成初始潜变量并按时序加噪的逻辑于是图生图要么 TypeError要么静默忽略输入图。二、背景图生图img2img相对 txt2img 多三步编码输入图把image用 VAE 编码成初始潜变量init_latents或 Anima 用自己的图像编码器按 strength 加噪latents scheduler.add_noise(init_latents, noise, t_start)其中t_start由strength决定strength 越大初始噪声越多越偏离原图去噪从加噪后的latents开始跑正常的去噪循环。Anima 的 txt2img pipeline 只做了第 3 步从纯随机噪声开始。要支持 img2img必须在__call__里加image/strength分支并接上图像编码路径。Anima 的特殊点在于它可能用一个专门的image_encoder而不是单纯 VAE把参考图编码成条件潜变量因此还要保证这个编码器被加载和接线。三、根因根因三点__call__无 img2img 分支签名里没有image/strength参数传了就 TypeError。缺图像编码路径img2img 需要把输入图编码成init_latents或 Anima 的image_encoder条件pipeline 没接这个组件 →AttributeError或忽略。初始潜变量构造方式缺失没做「VAE 编码 →add_noise(strength)」这一步于是即使传了图也无法变成去噪起点。本质Anima 只实现了 txt2img 单路径图生图所需的「image 入参 图像编码 strength 加噪」三件套缺失。四、最小可运行复现用标准库复现「__call__不认 image 参数」class AnimaPipeline: def __init__(self): self.vae object() def __call__(self, prompt, num_inference_steps30): # 只有 txt2img 路径没有 image/strength return ftxt2img: {prompt} pipe AnimaPipeline() try: pipe(anime girl, imagesketch.png, strength0.7) except TypeError as e: print(TypeError, e) # unexpected keyword argument image复现「image 被忽略」把__call__改成def __call__(self, prompt, imageNone, strength0.7, **kw)但内部完全不处理image输出和纯 txt2img 一样等于静默失效。五、解决方案第一层最小直接修复最小修复在__call__加 img2img 分支编码输入图并按 strength 加噪import torch from diffusers import DiffusionPipeline, ConfigMixin, ModelMixin, register_to_safetensors register_to_safetensors class AnimaPipeline(DiffusionPipeline, ConfigMixin): def __init__(self, vae, text_encoder, tokenizer, transformer, scheduler, image_encoderNone): super().__init__() self.register_modules( vaevae, text_encodertext_encoder, tokenizertokenizer, transformertransformer, schedulerscheduler, image_encoderimage_encoder, # Anima 的图像编码器img2img 用 ) torch.no_grad() def __call__(self, prompt, imageNone, strength0.7, num_inference_steps30, generatorNone, **kw): device self._execution_device # 文本条件 tok self.tokenizer(prompt, return_tensorspt, paddingmax_length, max_lengthself.tokenizer.model_max_length, truncationTrue).to(device) txt self.text_encoder(**tok).last_hidden_state # 1) 决定初始 latents有 image 走 img2img否则纯噪声 if image is not None: px self.image_processor.preprocess(image, ...).to(device, self.vae.dtype) init_latents self.vae.encode(px).latent_dist.mode() * self.vae.config.scaling_factor # 2) 按 strength 选起始 timestep 并加噪 t_start int(num_inference_steps * (1 - strength)) timesteps, _ retrieve_timesteps(self.scheduler, num_inference_steps, device, None) t timesteps[t_start: t_start 1] noise torch.randn(init_latents.shape, generatorgenerator, devicedevice, dtypeinit_latents.dtype) latents self.scheduler.add_noise(init_latents, noise, t) else: latents torch.randn((1, 4, 64, 64), generatorgenerator, devicedevice) # 3) 去噪与 txt2img 共用 for i, t in enumerate(timesteps[t_start:] if image is not None else timesteps): noise_pred self.transformer(latents, t, encoder_hidden_statestxt).sample latents self.scheduler.step(noise_pred, t, latents, generatorgenerator).prev_sample out self.vae.decode((1 / self.vae.config.scaling_factor) * latents).sample return self.image_processor.postprocess(out, output_typepil)这样image/strength被正确处理初始潜变量来自输入图并按 strength 加噪。六、解决方案第二层结构性改进把「Anima img2img 约定image 入参、strength 加噪、图像编码组件」收敛成一个 dataclass 单一真源from dataclasses import dataclass, field from typing import List, Optional dataclass(frozenTrue) class AnimaImg2ImgPolicy: Anima img2img 接入的单一真源。 # __call__ 必须接受的 img2img 参数 required_args: tuple (image, strength) # strength 取值范围 strength_range: tuple (0.0, 1.0) # 默认 strength default_strength: float 0.7 # 图像编码组件名Anima 专用 image_encoder 或 vae image_encoder_attr: Optional[str] image_encoder # 初始潜变量构造方式 init_method: str vae_encode_then_add_noise # t_start 计算int(num_inference_steps * (1 - strength)) t_start_formula: str int(n * (1 - strength)) def validate_call_signature(self, sig_params: set) - List[str]: problems [] for arg in self.required_args: if arg not in sig_params: problems.append(f__call__ 缺少 img2img 参数: {arg}) return problems def compute_t_start(self, num_inference_steps: int, strength: float) - int: if not self.strength_range[0] strength self.strength_range[1]: raise ValueError(fstrength 必须在 {self.strength_range}) return int(num_inference_steps * (1 - strength)) def has_image_encoder(self, pipeline) - bool: return getattr(pipeline, self.image_encoder_attr, None) is not None \ or hasattr(pipeline, vae)落库时__call__按required_args暴露参数compute_t_start统一算起始步has_image_encoder校验编码组件存在。七、解决方案第三层断言 / CI 守护用 pytest 把「签名含 image/strength 加噪正确 输出参考输入图」固化成回归import torch import pytest from diffusers import DiffusionPipeline from mylib.anima_img2img import AnimaImg2ImgPolicy POLICY AnimaImg2ImgPolicy() def test_call_signature_has_img2img(): import inspect sig inspect.signature(DiffusionPipeline.from_pretrained(user/Anima).__call__) problems POLICY.validate_call_signature(set(sig.parameters)) assert problems [], img2img 签名缺失:\n \n.join(problems) def test_t_start_formula(): assert POLICY.compute_t_start(30, 0.7) int(30 * 0.3) 9 assert POLICY.compute_t_start(30, 1.0) 0 # strength1 - 纯文生图 assert POLICY.compute_t_start(30, 0.0) 30 # strength0 - 不改图 def test_img2img_uses_input_image(): pipe DiffusionPipeline.from_pretrained(user/Anima, torch_dtypebf16) img __import__(PIL.Image).Image.new(RGB, (64, 64), color(120, 80, 40)) out pipe(promptanime girl, imageimg, strength0.3, num_inference_steps4) # 低 strength 下输出应接近输入图结构 assert abs(float(out.images[0].convert(RGB).getpixel((32, 32))[0]) - 120) 70 def test_image_encoder_present(): pipe DiffusionPipeline.from_pretrained(user/Anima, torch_dtypebf16) assert POLICY.has_image_encoder(pipe) def test_strength_out_of_range_rejected(): with pytest.raises(ValueError, matchstrength): POLICY.compute_t_start(30, 1.5)CI 把test_call_signature_has_img2img与test_img2img_uses_input_image作为 Anima img2img 的必过项要求「任何新增 img2img 能力必须暴露 image/strength 且低 strength 保留输入结构」。八、排查清单Anima 图生图失败按顺序查TypeError: unexpected keyword image__call__没 img2img 分支需加image/strength参数。传了 image 但输出和 txt2img 一样内部没编码输入图image 被忽略静默失效。是否有图像编码组件image_encoder / vae没有则无法把输入图变初始潜变量。初始潜变量是否vae.encode - add_noise(strength)直接用随机噪声等于 txt2img。strength是否在 (0,1]为 0 时 t_startn循环不执行输出接近输入图 VAE round-trip为 1 时等于文生图。低 strength 下输出是否接近输入图不接近说明加噪/编码路径错。九、小结「[Anima] Add img2img capability」本质是Anima pipeline 只实现了 txt2img 单路径图生图所需的「image 入参 图像编码 strength 加噪」三件套缺失导致传 image 时 TypeError 或静默忽略输入图。第一层在__call__加 img2img 分支编码输入图并按strength用add_noise构造初始潜变量第二层把 img2img 约定参数、strength 范围、t_start 公式、编码组件收敛到AnimaImg2ImgPolicy单一真源第三层用 pytest 守住「签名含 image/strength、低 strength 保留结构、strength 越界拒绝」。通用教训**给一个只做文生图的模型加图生图必须显式实现「输入图编码 strength 加噪」两步并把它们作为一等公民接入__call__否则 image 参数要么报错、要么被静默丢弃。