mirror of
https://github.com/huggingface/diffusers.git
synced 2026-01-29 07:22:12 +03:00
* update
* update
* update
* update
* update
* merge main
* Revert "merge main"
This reverts commit 65efbcead5.
327 lines
13 KiB
Python
327 lines
13 KiB
Python
# coding=utf-8
|
|
# Copyright 2025 HuggingFace Inc.
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
import unittest
|
|
|
|
import numpy as np
|
|
import torch
|
|
from torch import nn
|
|
|
|
from diffusers import ControlNetXSAdapter, UNet2DConditionModel, UNetControlNetXSModel
|
|
from diffusers.utils import logging
|
|
|
|
from ...testing_utils import enable_full_determinism, floats_tensor, is_flaky, torch_device
|
|
from ..test_modeling_common import ModelTesterMixin, UNetTesterMixin
|
|
|
|
|
|
logger = logging.get_logger(__name__)
|
|
|
|
enable_full_determinism()
|
|
|
|
|
|
class UNetControlNetXSModelTests(ModelTesterMixin, UNetTesterMixin, unittest.TestCase):
|
|
model_class = UNetControlNetXSModel
|
|
main_input_name = "sample"
|
|
|
|
@property
|
|
def dummy_input(self):
|
|
batch_size = 4
|
|
num_channels = 4
|
|
sizes = (16, 16)
|
|
conditioning_image_size = (3, 32, 32) # size of additional, unprocessed image for control-conditioning
|
|
|
|
noise = floats_tensor((batch_size, num_channels) + sizes).to(torch_device)
|
|
time_step = torch.tensor([10]).to(torch_device)
|
|
encoder_hidden_states = floats_tensor((batch_size, 4, 8)).to(torch_device)
|
|
controlnet_cond = floats_tensor((batch_size, *conditioning_image_size)).to(torch_device)
|
|
conditioning_scale = 1
|
|
|
|
return {
|
|
"sample": noise,
|
|
"timestep": time_step,
|
|
"encoder_hidden_states": encoder_hidden_states,
|
|
"controlnet_cond": controlnet_cond,
|
|
"conditioning_scale": conditioning_scale,
|
|
}
|
|
|
|
@property
|
|
def input_shape(self):
|
|
return (4, 16, 16)
|
|
|
|
@property
|
|
def output_shape(self):
|
|
return (4, 16, 16)
|
|
|
|
def prepare_init_args_and_inputs_for_common(self):
|
|
init_dict = {
|
|
"sample_size": 16,
|
|
"down_block_types": ("DownBlock2D", "CrossAttnDownBlock2D"),
|
|
"up_block_types": ("CrossAttnUpBlock2D", "UpBlock2D"),
|
|
"block_out_channels": (4, 8),
|
|
"cross_attention_dim": 8,
|
|
"transformer_layers_per_block": 1,
|
|
"num_attention_heads": 2,
|
|
"norm_num_groups": 4,
|
|
"upcast_attention": False,
|
|
"ctrl_block_out_channels": [2, 4],
|
|
"ctrl_num_attention_heads": 4,
|
|
"ctrl_max_norm_num_groups": 2,
|
|
"ctrl_conditioning_embedding_out_channels": (2, 2),
|
|
}
|
|
inputs_dict = self.dummy_input
|
|
return init_dict, inputs_dict
|
|
|
|
def get_dummy_unet(self):
|
|
"""For some tests we also need the underlying UNet. For these, we'll build the UNetControlNetXSModel from the UNet and ControlNetXS-Adapter"""
|
|
return UNet2DConditionModel(
|
|
block_out_channels=(4, 8),
|
|
layers_per_block=2,
|
|
sample_size=16,
|
|
in_channels=4,
|
|
out_channels=4,
|
|
down_block_types=("DownBlock2D", "CrossAttnDownBlock2D"),
|
|
up_block_types=("CrossAttnUpBlock2D", "UpBlock2D"),
|
|
cross_attention_dim=8,
|
|
norm_num_groups=4,
|
|
use_linear_projection=True,
|
|
)
|
|
|
|
def get_dummy_controlnet_from_unet(self, unet, **kwargs):
|
|
"""For some tests we also need the underlying ControlNetXS-Adapter. For these, we'll build the UNetControlNetXSModel from the UNet and ControlNetXS-Adapter"""
|
|
# size_ratio and conditioning_embedding_out_channels chosen to keep model small
|
|
return ControlNetXSAdapter.from_unet(unet, size_ratio=1, conditioning_embedding_out_channels=(2, 2), **kwargs)
|
|
|
|
def test_from_unet(self):
|
|
unet = self.get_dummy_unet()
|
|
controlnet = self.get_dummy_controlnet_from_unet(unet)
|
|
|
|
model = UNetControlNetXSModel.from_unet(unet, controlnet)
|
|
model_state_dict = model.state_dict()
|
|
|
|
def assert_equal_weights(module, weight_dict_prefix):
|
|
for param_name, param_value in module.named_parameters():
|
|
assert torch.equal(model_state_dict[weight_dict_prefix + "." + param_name], param_value)
|
|
|
|
# # check unet
|
|
# everything expect down,mid,up blocks
|
|
modules_from_unet = [
|
|
"time_embedding",
|
|
"conv_in",
|
|
"conv_norm_out",
|
|
"conv_out",
|
|
]
|
|
for p in modules_from_unet:
|
|
assert_equal_weights(getattr(unet, p), "base_" + p)
|
|
optional_modules_from_unet = [
|
|
"class_embedding",
|
|
"add_time_proj",
|
|
"add_embedding",
|
|
]
|
|
for p in optional_modules_from_unet:
|
|
if hasattr(unet, p) and getattr(unet, p) is not None:
|
|
assert_equal_weights(getattr(unet, p), "base_" + p)
|
|
# down blocks
|
|
assert len(unet.down_blocks) == len(model.down_blocks)
|
|
for i, d in enumerate(unet.down_blocks):
|
|
assert_equal_weights(d.resnets, f"down_blocks.{i}.base_resnets")
|
|
if hasattr(d, "attentions"):
|
|
assert_equal_weights(d.attentions, f"down_blocks.{i}.base_attentions")
|
|
if hasattr(d, "downsamplers") and getattr(d, "downsamplers") is not None:
|
|
assert_equal_weights(d.downsamplers[0], f"down_blocks.{i}.base_downsamplers")
|
|
# mid block
|
|
assert_equal_weights(unet.mid_block, "mid_block.base_midblock")
|
|
# up blocks
|
|
assert len(unet.up_blocks) == len(model.up_blocks)
|
|
for i, u in enumerate(unet.up_blocks):
|
|
assert_equal_weights(u.resnets, f"up_blocks.{i}.resnets")
|
|
if hasattr(u, "attentions"):
|
|
assert_equal_weights(u.attentions, f"up_blocks.{i}.attentions")
|
|
if hasattr(u, "upsamplers") and getattr(u, "upsamplers") is not None:
|
|
assert_equal_weights(u.upsamplers[0], f"up_blocks.{i}.upsamplers")
|
|
|
|
# # check controlnet
|
|
# everything expect down,mid,up blocks
|
|
modules_from_controlnet = {
|
|
"controlnet_cond_embedding": "controlnet_cond_embedding",
|
|
"conv_in": "ctrl_conv_in",
|
|
"control_to_base_for_conv_in": "control_to_base_for_conv_in",
|
|
}
|
|
optional_modules_from_controlnet = {"time_embedding": "ctrl_time_embedding"}
|
|
for name_in_controlnet, name_in_unetcnxs in modules_from_controlnet.items():
|
|
assert_equal_weights(getattr(controlnet, name_in_controlnet), name_in_unetcnxs)
|
|
|
|
for name_in_controlnet, name_in_unetcnxs in optional_modules_from_controlnet.items():
|
|
if hasattr(controlnet, name_in_controlnet) and getattr(controlnet, name_in_controlnet) is not None:
|
|
assert_equal_weights(getattr(controlnet, name_in_controlnet), name_in_unetcnxs)
|
|
# down blocks
|
|
assert len(controlnet.down_blocks) == len(model.down_blocks)
|
|
for i, d in enumerate(controlnet.down_blocks):
|
|
assert_equal_weights(d.resnets, f"down_blocks.{i}.ctrl_resnets")
|
|
assert_equal_weights(d.base_to_ctrl, f"down_blocks.{i}.base_to_ctrl")
|
|
assert_equal_weights(d.ctrl_to_base, f"down_blocks.{i}.ctrl_to_base")
|
|
if d.attentions is not None:
|
|
assert_equal_weights(d.attentions, f"down_blocks.{i}.ctrl_attentions")
|
|
if d.downsamplers is not None:
|
|
assert_equal_weights(d.downsamplers, f"down_blocks.{i}.ctrl_downsamplers")
|
|
# mid block
|
|
assert_equal_weights(controlnet.mid_block.base_to_ctrl, "mid_block.base_to_ctrl")
|
|
assert_equal_weights(controlnet.mid_block.midblock, "mid_block.ctrl_midblock")
|
|
assert_equal_weights(controlnet.mid_block.ctrl_to_base, "mid_block.ctrl_to_base")
|
|
# up blocks
|
|
assert len(controlnet.up_connections) == len(model.up_blocks)
|
|
for i, u in enumerate(controlnet.up_connections):
|
|
assert_equal_weights(u.ctrl_to_base, f"up_blocks.{i}.ctrl_to_base")
|
|
|
|
def test_freeze_unet(self):
|
|
def assert_frozen(module):
|
|
for p in module.parameters():
|
|
assert not p.requires_grad
|
|
|
|
def assert_unfrozen(module):
|
|
for p in module.parameters():
|
|
assert p.requires_grad
|
|
|
|
init_dict, _ = self.prepare_init_args_and_inputs_for_common()
|
|
model = UNetControlNetXSModel(**init_dict)
|
|
model.freeze_unet_params()
|
|
|
|
# # check unet
|
|
# everything expect down,mid,up blocks
|
|
modules_from_unet = [
|
|
model.base_time_embedding,
|
|
model.base_conv_in,
|
|
model.base_conv_norm_out,
|
|
model.base_conv_out,
|
|
]
|
|
for m in modules_from_unet:
|
|
assert_frozen(m)
|
|
|
|
optional_modules_from_unet = [
|
|
model.base_add_time_proj,
|
|
model.base_add_embedding,
|
|
]
|
|
for m in optional_modules_from_unet:
|
|
if m is not None:
|
|
assert_frozen(m)
|
|
|
|
# down blocks
|
|
for i, d in enumerate(model.down_blocks):
|
|
assert_frozen(d.base_resnets)
|
|
if isinstance(d.base_attentions, nn.ModuleList): # attentions can be list of Nones
|
|
assert_frozen(d.base_attentions)
|
|
if d.base_downsamplers is not None:
|
|
assert_frozen(d.base_downsamplers)
|
|
|
|
# mid block
|
|
assert_frozen(model.mid_block.base_midblock)
|
|
|
|
# up blocks
|
|
for i, u in enumerate(model.up_blocks):
|
|
assert_frozen(u.resnets)
|
|
if isinstance(u.attentions, nn.ModuleList): # attentions can be list of Nones
|
|
assert_frozen(u.attentions)
|
|
if u.upsamplers is not None:
|
|
assert_frozen(u.upsamplers)
|
|
|
|
# # check controlnet
|
|
# everything expect down,mid,up blocks
|
|
modules_from_controlnet = [
|
|
model.controlnet_cond_embedding,
|
|
model.ctrl_conv_in,
|
|
model.control_to_base_for_conv_in,
|
|
]
|
|
optional_modules_from_controlnet = [model.ctrl_time_embedding]
|
|
|
|
for m in modules_from_controlnet:
|
|
assert_unfrozen(m)
|
|
for m in optional_modules_from_controlnet:
|
|
if m is not None:
|
|
assert_unfrozen(m)
|
|
|
|
# down blocks
|
|
for d in model.down_blocks:
|
|
assert_unfrozen(d.ctrl_resnets)
|
|
assert_unfrozen(d.base_to_ctrl)
|
|
assert_unfrozen(d.ctrl_to_base)
|
|
if isinstance(d.ctrl_attentions, nn.ModuleList): # attentions can be list of Nones
|
|
assert_unfrozen(d.ctrl_attentions)
|
|
if d.ctrl_downsamplers is not None:
|
|
assert_unfrozen(d.ctrl_downsamplers)
|
|
# mid block
|
|
assert_unfrozen(model.mid_block.base_to_ctrl)
|
|
assert_unfrozen(model.mid_block.ctrl_midblock)
|
|
assert_unfrozen(model.mid_block.ctrl_to_base)
|
|
# up blocks
|
|
for u in model.up_blocks:
|
|
assert_unfrozen(u.ctrl_to_base)
|
|
|
|
def test_gradient_checkpointing_is_applied(self):
|
|
expected_set = {
|
|
"Transformer2DModel",
|
|
"UNetMidBlock2DCrossAttn",
|
|
"ControlNetXSCrossAttnDownBlock2D",
|
|
"ControlNetXSCrossAttnMidBlock2D",
|
|
"ControlNetXSCrossAttnUpBlock2D",
|
|
}
|
|
super().test_gradient_checkpointing_is_applied(expected_set=expected_set)
|
|
|
|
@is_flaky
|
|
def test_forward_no_control(self):
|
|
unet = self.get_dummy_unet()
|
|
controlnet = self.get_dummy_controlnet_from_unet(unet)
|
|
|
|
model = UNetControlNetXSModel.from_unet(unet, controlnet)
|
|
|
|
unet = unet.to(torch_device)
|
|
model = model.to(torch_device)
|
|
|
|
input_ = self.dummy_input
|
|
|
|
control_specific_input = ["controlnet_cond", "conditioning_scale"]
|
|
input_for_unet = {k: v for k, v in input_.items() if k not in control_specific_input}
|
|
|
|
with torch.no_grad():
|
|
unet_output = unet(**input_for_unet).sample.cpu()
|
|
unet_controlnet_output = model(**input_, apply_control=False).sample.cpu()
|
|
|
|
assert np.abs(unet_output.flatten() - unet_controlnet_output.flatten()).max() < 3e-4
|
|
|
|
def test_time_embedding_mixing(self):
|
|
unet = self.get_dummy_unet()
|
|
controlnet = self.get_dummy_controlnet_from_unet(unet)
|
|
controlnet_mix_time = self.get_dummy_controlnet_from_unet(
|
|
unet, time_embedding_mix=0.5, learn_time_embedding=True
|
|
)
|
|
|
|
model = UNetControlNetXSModel.from_unet(unet, controlnet)
|
|
model_mix_time = UNetControlNetXSModel.from_unet(unet, controlnet_mix_time)
|
|
|
|
unet = unet.to(torch_device)
|
|
model = model.to(torch_device)
|
|
model_mix_time = model_mix_time.to(torch_device)
|
|
|
|
input_ = self.dummy_input
|
|
|
|
with torch.no_grad():
|
|
output = model(**input_).sample
|
|
output_mix_time = model_mix_time(**input_).sample
|
|
|
|
assert output.shape == output_mix_time.shape
|
|
|
|
@unittest.skip("Test not supported.")
|
|
def test_forward_with_norm_groups(self):
|
|
# UNetControlNetXSModel currently only supports StableDiffusion and StableDiffusion-XL, both of which have norm_num_groups fixed at 32. So we don't need to test different values for norm_num_groups.
|
|
pass
|