Files
wehub-resource-sync cddb07a176
docs / deploy (push) Has been cancelled
docs / changes (push) Has been cancelled
docs / check-and-build (push) Has been cancelled
build container image / cpu (push) Has been cancelled
build container image / cuda (push) Has been cancelled
build container image / rocm (push) Has been cancelled
frontend checks / frontend-checks (push) Has been cancelled
frontend tests / frontend-tests (push) Has been cancelled
lfs checks / lfs-check (push) Has been cancelled
python checks / python-checks (push) Has been cancelled
python tests / py3.12: macos-default (push) Has been cancelled
python tests / py3.11: windows-cpu (push) Has been cancelled
python tests / py3.12: windows-cpu (push) Has been cancelled
python tests / py3.11: linux-cpu (push) Has been cancelled
typegen checks / typegen-checks (push) Has been cancelled
uv lock checks / uv-lock-checks (push) Has been cancelled
openapi checks / openapi-checks (push) Has been cancelled
python tests / py3.11: macos-default (push) Has been cancelled
python tests / py3.12: linux-cpu (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:22:06 +08:00

154 lines
5.8 KiB
Python

from contextlib import contextmanager, nullcontext
from types import SimpleNamespace
from unittest.mock import MagicMock
import torch
from invokeai.app.invocations.sd3_text_encoder import Sd3TextEncoderInvocation
from invokeai.backend.model_manager.taxonomy import ModelFormat
class FakeSd3ClipTextEncoder(torch.nn.Module):
def __init__(self, effective_device: torch.device):
super().__init__()
self.register_parameter("cpu_param", torch.nn.Parameter(torch.ones(1)))
self.register_buffer("active_buffer", torch.ones(1, device=effective_device))
self.dtype = torch.float32
self.forward_input_device: torch.device | None = None
@property
def device(self) -> torch.device:
return torch.device("cpu")
def forward(self, input_ids: torch.Tensor, output_hidden_states: bool = False):
assert output_hidden_states
self.forward_input_device = input_ids.device
hidden = input_ids.unsqueeze(-1).float()
return SimpleNamespace(hidden_states=[hidden, hidden + 1], __getitem__=lambda self, idx: hidden)
class FakeClipOutput(SimpleNamespace):
def __getitem__(self, idx):
del idx
return self.hidden_states[-1]
class FakeClipTokenizer:
def __call__(self, prompt, padding, max_length=None, truncation=None, return_tensors=None):
del prompt, padding, max_length, truncation, return_tensors
return SimpleNamespace(input_ids=torch.tensor([[1, 2, 3]], dtype=torch.long))
def batch_decode(self, input_ids):
del input_ids
return ["decoded"]
class FakeSd3T5Encoder(torch.nn.Module):
def __init__(self, effective_device: torch.device):
super().__init__()
self.register_parameter("cpu_param", torch.nn.Parameter(torch.ones(1)))
self.register_buffer("active_buffer", torch.ones(1, device=effective_device))
self.forward_input_device: torch.device | None = None
@property
def device(self) -> torch.device:
return torch.device("cpu")
def forward(self, input_ids: torch.Tensor):
self.forward_input_device = input_ids.device
hidden = input_ids.unsqueeze(-1).float()
return (hidden,)
class FakeT5Tokenizer:
def __call__(self, prompt, padding, max_length=None, truncation=None, add_special_tokens=None, return_tensors=None):
del prompt, padding, max_length, truncation, add_special_tokens, return_tensors
return SimpleNamespace(input_ids=torch.tensor([[1, 2, 3]], dtype=torch.long))
def batch_decode(self, input_ids):
del input_ids
return ["decoded"]
class FakeLoadedModel:
def __init__(self, model, config=None):
self._model = model
self.config = config
@contextmanager
def model_on_device(self):
yield (None, self._model)
def __enter__(self):
return self._model
def __exit__(self, exc_type, exc, tb):
return False
def test_sd3_clip_encode_uses_effective_device(monkeypatch):
module_path = "invokeai.app.invocations.sd3_text_encoder"
effective_device = torch.device("meta")
text_encoder = FakeSd3ClipTextEncoder(effective_device)
tokenizer = FakeClipTokenizer()
def forward(input_ids: torch.Tensor, output_hidden_states: bool = False):
assert output_hidden_states
text_encoder.forward_input_device = input_ids.device
hidden = input_ids.unsqueeze(-1).float()
return FakeClipOutput(hidden_states=[hidden, hidden + 1])
text_encoder.forward = forward # type: ignore[method-assign]
mock_context = MagicMock()
mock_context.models.load.side_effect = [
FakeLoadedModel(text_encoder, config=SimpleNamespace(format=ModelFormat.Diffusers)),
FakeLoadedModel(tokenizer),
]
mock_context.util.signal_progress = MagicMock()
monkeypatch.setattr(f"{module_path}.CLIPTextModel", FakeSd3ClipTextEncoder)
monkeypatch.setattr(f"{module_path}.CLIPTextModelWithProjection", FakeSd3ClipTextEncoder)
monkeypatch.setattr(f"{module_path}.CLIPTokenizer", FakeClipTokenizer)
monkeypatch.setattr(f"{module_path}.LayerPatcher.apply_smart_model_patches", lambda **kwargs: nullcontext())
invocation = Sd3TextEncoderInvocation.model_construct(
clip_l=SimpleNamespace(text_encoder=SimpleNamespace(), tokenizer=SimpleNamespace(), loras=[]),
clip_g=SimpleNamespace(text_encoder=SimpleNamespace(), tokenizer=SimpleNamespace(), loras=[]),
t5_encoder=None,
prompt="test prompt",
)
invocation._clip_encode(
context=mock_context,
clip_model=SimpleNamespace(text_encoder=SimpleNamespace(), tokenizer=SimpleNamespace(), loras=[]),
)
assert text_encoder.forward_input_device == effective_device
def test_sd3_t5_encode_uses_effective_device(monkeypatch):
module_path = "invokeai.app.invocations.sd3_text_encoder"
effective_device = torch.device("meta")
text_encoder = FakeSd3T5Encoder(effective_device)
tokenizer = FakeT5Tokenizer()
mock_context = MagicMock()
mock_context.models.load.side_effect = [FakeLoadedModel(text_encoder), FakeLoadedModel(tokenizer)]
mock_context.util.signal_progress = MagicMock()
mock_context.logger.warning = MagicMock()
monkeypatch.setattr(f"{module_path}.T5EncoderModel", FakeSd3T5Encoder)
monkeypatch.setattr(f"{module_path}.T5Tokenizer", FakeT5Tokenizer)
invocation = Sd3TextEncoderInvocation.model_construct(
clip_l=SimpleNamespace(text_encoder=SimpleNamespace(), tokenizer=SimpleNamespace(), loras=[]),
clip_g=SimpleNamespace(text_encoder=SimpleNamespace(), tokenizer=SimpleNamespace(), loras=[]),
t5_encoder=SimpleNamespace(text_encoder=SimpleNamespace(), tokenizer=SimpleNamespace()),
prompt="test prompt",
)
invocation._t5_encode(mock_context, max_seq_len=16)
assert text_encoder.forward_input_device == effective_device