"""Unit tests for the --cpu-offload device-placement branch. ``WatermarkRemover._move_to_device_and_optimize`` chooses between a full ``pipeline.to("cuda")`` and ``enable_model_cpu_offload()``. The placement decision is exercised with a mock pipeline and an uninitialized remover, so the core CI matrix needs no diffusion dependency, model download, or GPU. """ from __future__ import annotations from unittest.mock import Mock import pytest from remove_ai_watermarks.noai.watermark_remover import WatermarkRemover def _remover(device: str, cpu_offload: bool) -> WatermarkRemover: remover = WatermarkRemover.__new__(WatermarkRemover) remover.device = device remover.cpu_offload = cpu_offload remover._progress_callback = None return remover class TestCpuOffloadPlacement: def test_offload_enabled_on_cuda_streams_instead_of_moving(self): remover = _remover("cuda", cpu_offload=True) pipeline = Mock() returned = remover._move_to_device_and_optimize(pipeline) pipeline.enable_model_cpu_offload.assert_called_once_with(device="cuda") pipeline.to.assert_not_called() # Offload leaves the pipeline object in place (accelerate hooks handle it). assert returned is pipeline def test_no_offload_moves_whole_pipeline_to_cuda(self): remover = _remover("cuda", cpu_offload=False) pipeline = Mock() remover._move_to_device_and_optimize(pipeline) pipeline.to.assert_called_once_with("cuda") pipeline.enable_model_cpu_offload.assert_not_called() def test_offload_flag_ignored_off_cuda(self): # The flag is CUDA-only: on cpu it must still be a plain .to("cpu"). remover = _remover("cpu", cpu_offload=True) pipeline = Mock() remover._move_to_device_and_optimize(pipeline) pipeline.to.assert_called_once_with("cpu") pipeline.enable_model_cpu_offload.assert_not_called() def test_offload_fails_loudly_when_pipeline_lacks_support(self): remover = _remover("cuda", cpu_offload=True) pipeline = Mock(spec=["to"]) with pytest.raises(RuntimeError, match="does not support"): remover._move_to_device_and_optimize(pipeline) pipeline.to.assert_not_called() def test_qwen_zimage_forces_face_stack_offload(self): remover = _remover("cuda", cpu_offload=True) remover.torch_dtype = object() remover.hf_token = None remover.controlnet_conditioning_scale = 1.0 remover._qwen_zimage_pipeline = None runtime = remover._load_qwen_zimage_pipeline() assert runtime.keep_face_models_on_device is False