mirror of
https://github.com/huggingface/diffusers.git
synced 2026-10-24 20:59:27 +03:00
* refactor c* pipeline tests. Migrate the remaining `tests/pipelines/` directories starting with C to the config class + composable pytest mixins introduced in #14113: chroma, chronoedit, cogview3, consisid, consistency_models, controlnet, controlnet_flux, controlnet_hunyuandit, controlnet_sd3 and cosmos. cogvideo and cogview4 were already migrated. Notable points beyond the mechanical conversion: - Expected slices switch from the `"np"` layout (`[0, -3:, -3:, -1]`) to the `"pt"` one (`[0, -1, -3:, -3:]`); the values themselves are unchanged. - The Cosmos `safety_checker` overrides (save/load, variants, dtype dict, encode_prompt) were duplicated across five files and now live in a shared `CosmosSafetyCheckerTesterMixin` in `tests/pipelines/cosmos/testing_utils.py`. - chroma and controlnet_flux import `FluxIPAdapterTesterMixin` from `..flux.testing_utils` rather than the old `..test_pipelines_common` one. - `PipelineTesterMixin.test_save_load_float16` now passes the non-deprecated `dtype=` to `from_pretrained` instead of `torch_dtype=`, which the Cosmos2.5 `from_pretrained` wrappers read when casting the safety checker they inject. - Dropped along the way, matching #14619: `PipelineLatentTesterMixin`, `PipelineKarrasSchedulerTesterMixin`, the attention-slicing and xformers tests, and the `test_ip_adapter` overrides that only supplied an `expected_pipe_slice`. `pytest tests/pipelines/{chroma,chronoedit,cogview3,consisid,consistency_models,controlnet,controlnet_flux,controlnet_hunyuandit,controlnet_sd3,cosmos}` gives 14 failed, 1014 passed, 82 skipped. All 14 failures are in cosmos and reproduce unchanged on main: `DummyCosmosSafetyChecker` is a parameter-less `ModelMixin` with a single non-persistent buffer, so accelerate does not hook it for sequential offload and a reloaded pipeline gets a fresh fp32 one. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
96 lines
4.5 KiB
Python
96 lines
4.5 KiB
Python
# Copyright 2026 The HuggingFace Team.
|
|
#
|
|
# 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 json
|
|
import os
|
|
|
|
import pytest
|
|
import torch
|
|
|
|
from .cosmos_guardrail import DummyCosmosSafetyChecker
|
|
|
|
|
|
class CosmosSafetyCheckerTesterMixin:
|
|
"""Overrides of the shared pipeline tests that the Cosmos `safety_checker` component gets in the way of.
|
|
|
|
Every Cosmos pipeline takes a `safety_checker`, and the fast tests substitute `DummyCosmosSafetyChecker` for the
|
|
real Cosmos Guardrail, which is far too large to build on CI. The dummy is not serialized like the other model
|
|
components and a pipeline constructed without one falls back to the real guardrail, so the tests that save,
|
|
reload or enumerate components need the component handled explicitly.
|
|
|
|
Compose it *before* `PipelineTesterMixin` so these overrides win, e.g.
|
|
`class TestCosmosXPipeline(CosmosXPipelineTesterConfig, CosmosSafetyCheckerTesterMixin, PipelineTesterMixin)`.
|
|
"""
|
|
|
|
def test_save_load_optional_components(self, tmp_path, expected_max_difference=1e-4):
|
|
# `safety_checker` is listed as optional, but a pipeline built without one falls back to the real Cosmos
|
|
# Guardrail — so keep it out of the components the base test nulls.
|
|
self.pipeline_class._optional_components.remove("safety_checker")
|
|
try:
|
|
super().test_save_load_optional_components(tmp_path, expected_max_difference=expected_max_difference)
|
|
finally:
|
|
self.pipeline_class._optional_components.append("safety_checker")
|
|
|
|
def test_serialization_with_variants(self, tmp_path):
|
|
# Same as the base test, except `safety_checker` is not serialized like the other model components.
|
|
pipe = self.get_pipeline()
|
|
model_components = [
|
|
component_name
|
|
for component_name, component in pipe.components.items()
|
|
if isinstance(component, torch.nn.Module)
|
|
]
|
|
model_components.remove("safety_checker")
|
|
variant = "fp16"
|
|
|
|
pipe.save_pretrained(tmp_path, variant=variant, safe_serialization=False)
|
|
|
|
with open(f"{tmp_path}/model_index.json", "r") as f:
|
|
config = json.load(f)
|
|
|
|
for subfolder in os.listdir(tmp_path):
|
|
if not os.path.isfile(subfolder) and subfolder in model_components:
|
|
folder_path = os.path.join(tmp_path, subfolder)
|
|
is_folder = os.path.isdir(folder_path) and subfolder in config
|
|
assert is_folder and any(p.split(".")[1].startswith(variant) for p in os.listdir(folder_path))
|
|
|
|
def test_torch_dtype_dict(self, tmp_path):
|
|
# Same as the base test, except the safety checker has to be passed back in on load and is left out of the
|
|
# dtype check (the dummy tracks its dtype through a non-persistent buffer).
|
|
components = self.get_dummy_components()
|
|
pipe = self.get_pipeline(**components)
|
|
specified_key = next(iter(components.keys()))
|
|
|
|
pipe.save_pretrained(str(tmp_path), safe_serialization=False)
|
|
torch_dtype_dict = {specified_key: torch.bfloat16, "default": torch.float16}
|
|
loaded_pipe = self.pipeline_class.from_pretrained(
|
|
str(tmp_path), safety_checker=DummyCosmosSafetyChecker(), dtype=torch_dtype_dict
|
|
)
|
|
|
|
for name, component in loaded_pipe.components.items():
|
|
if name == "safety_checker":
|
|
continue
|
|
if isinstance(component, torch.nn.Module) and hasattr(component, "dtype"):
|
|
expected_dtype = torch_dtype_dict.get(name, torch_dtype_dict.get("default", torch.float32))
|
|
assert component.dtype == expected_dtype, (
|
|
f"Component '{name}' has dtype {component.dtype} but expected {expected_dtype}"
|
|
)
|
|
|
|
@pytest.mark.skip(
|
|
"The pipeline should not be runnable without a safety checker. The test creates a pipeline without passing in "
|
|
"a safety checker, which makes the pipeline default to the actual Cosmos Guardrail. The Cosmos Guardrail is "
|
|
"too large and slow to run on CI."
|
|
)
|
|
def test_encode_prompt_works_in_isolation(self):
|
|
pass
|