chore: import upstream snapshot with attribution
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
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
This commit is contained in:
+143
@@ -0,0 +1,143 @@
|
||||
import torch
|
||||
|
||||
from invokeai.backend.model_manager.load.model_cache.cached_model.cached_model_only_full_load import (
|
||||
CachedModelOnlyFullLoad,
|
||||
)
|
||||
from tests.backend.model_manager.load.model_cache.cached_model.utils import (
|
||||
DummyModule,
|
||||
parameterize_keep_ram_copy,
|
||||
parameterize_mps_and_cuda,
|
||||
)
|
||||
|
||||
|
||||
class NonTorchModel:
|
||||
"""A model that does not sub-class torch.nn.Module."""
|
||||
|
||||
def __init__(self):
|
||||
self.linear = torch.nn.Linear(10, 32)
|
||||
|
||||
def run_inference(self, x: torch.Tensor) -> torch.Tensor:
|
||||
return self.linear(x)
|
||||
|
||||
|
||||
@parameterize_mps_and_cuda
|
||||
@parameterize_keep_ram_copy
|
||||
def test_cached_model_total_bytes(device: str, keep_ram_copy: bool):
|
||||
model = DummyModule()
|
||||
cached_model = CachedModelOnlyFullLoad(
|
||||
model=model, compute_device=torch.device(device), total_bytes=100, keep_ram_copy=keep_ram_copy
|
||||
)
|
||||
assert cached_model.total_bytes() == 100
|
||||
|
||||
|
||||
@parameterize_mps_and_cuda
|
||||
@parameterize_keep_ram_copy
|
||||
def test_cached_model_is_in_vram(device: str, keep_ram_copy: bool):
|
||||
model = DummyModule()
|
||||
cached_model = CachedModelOnlyFullLoad(
|
||||
model=model, compute_device=torch.device(device), total_bytes=100, keep_ram_copy=keep_ram_copy
|
||||
)
|
||||
assert not cached_model.is_in_vram()
|
||||
assert cached_model.cur_vram_bytes() == 0
|
||||
|
||||
cached_model.full_load_to_vram()
|
||||
assert cached_model.is_in_vram()
|
||||
assert cached_model.cur_vram_bytes() == 100
|
||||
|
||||
cached_model.full_unload_from_vram()
|
||||
assert not cached_model.is_in_vram()
|
||||
assert cached_model.cur_vram_bytes() == 0
|
||||
|
||||
|
||||
@parameterize_mps_and_cuda
|
||||
@parameterize_keep_ram_copy
|
||||
def test_cached_model_full_load_and_unload(device: str, keep_ram_copy: bool):
|
||||
model = DummyModule()
|
||||
cached_model = CachedModelOnlyFullLoad(
|
||||
model=model, compute_device=torch.device(device), total_bytes=100, keep_ram_copy=keep_ram_copy
|
||||
)
|
||||
assert cached_model.full_load_to_vram() == 100
|
||||
assert cached_model.is_in_vram()
|
||||
assert all(p.device.type == device for p in cached_model.model.parameters())
|
||||
|
||||
assert cached_model.full_unload_from_vram() == 100
|
||||
assert not cached_model.is_in_vram()
|
||||
assert all(p.device.type == "cpu" for p in cached_model.model.parameters())
|
||||
|
||||
|
||||
@parameterize_mps_and_cuda
|
||||
def test_cached_model_get_cpu_state_dict(device: str):
|
||||
model = DummyModule()
|
||||
cached_model = CachedModelOnlyFullLoad(
|
||||
model=model, compute_device=torch.device(device), total_bytes=100, keep_ram_copy=True
|
||||
)
|
||||
assert not cached_model.is_in_vram()
|
||||
|
||||
# The CPU state dict can be accessed and has the expected properties.
|
||||
cpu_state_dict = cached_model.get_cpu_state_dict()
|
||||
assert cpu_state_dict is not None
|
||||
assert len(cpu_state_dict) == len(model.state_dict())
|
||||
assert all(p.device.type == "cpu" for p in cpu_state_dict.values())
|
||||
|
||||
# Full load the model into VRAM.
|
||||
cached_model.full_load_to_vram()
|
||||
assert cached_model.is_in_vram()
|
||||
|
||||
# The CPU state dict is still available, and still on the CPU.
|
||||
cpu_state_dict = cached_model.get_cpu_state_dict()
|
||||
assert cpu_state_dict is not None
|
||||
assert len(cpu_state_dict) == len(model.state_dict())
|
||||
assert all(p.device.type == "cpu" for p in cpu_state_dict.values())
|
||||
|
||||
|
||||
@parameterize_mps_and_cuda
|
||||
@parameterize_keep_ram_copy
|
||||
def test_cached_model_full_load_and_inference(device: str, keep_ram_copy: bool):
|
||||
model = DummyModule()
|
||||
cached_model = CachedModelOnlyFullLoad(
|
||||
model=model, compute_device=torch.device(device), total_bytes=100, keep_ram_copy=keep_ram_copy
|
||||
)
|
||||
assert not cached_model.is_in_vram()
|
||||
|
||||
# Run inference on the CPU.
|
||||
x = torch.randn(1, 10)
|
||||
output1 = model(x)
|
||||
assert output1.device.type == "cpu"
|
||||
|
||||
# Full load the model into VRAM.
|
||||
cached_model.full_load_to_vram()
|
||||
assert cached_model.is_in_vram()
|
||||
|
||||
# Run inference on the GPU.
|
||||
output2 = model(x.to(device))
|
||||
assert output2.device.type == device
|
||||
|
||||
# The outputs should be the same for both runs.
|
||||
assert torch.allclose(output1, output2.to("cpu"))
|
||||
|
||||
|
||||
@parameterize_mps_and_cuda
|
||||
@parameterize_keep_ram_copy
|
||||
def test_non_torch_model(device: str, keep_ram_copy: bool):
|
||||
model = NonTorchModel()
|
||||
cached_model = CachedModelOnlyFullLoad(
|
||||
model=model, compute_device=torch.device(device), total_bytes=100, keep_ram_copy=keep_ram_copy
|
||||
)
|
||||
assert not cached_model.is_in_vram()
|
||||
|
||||
# The model does not have a CPU state dict.
|
||||
assert cached_model.get_cpu_state_dict() is None
|
||||
|
||||
# Attempting to load the model into VRAM should have no effect.
|
||||
cached_model.full_load_to_vram()
|
||||
assert not cached_model.is_in_vram()
|
||||
assert cached_model.cur_vram_bytes() == 0
|
||||
|
||||
# Attempting to unload the model from VRAM should have no effect.
|
||||
cached_model.full_unload_from_vram()
|
||||
assert not cached_model.is_in_vram()
|
||||
assert cached_model.cur_vram_bytes() == 0
|
||||
|
||||
# Running inference on the CPU should work.
|
||||
output1 = model.run_inference(torch.randn(1, 10))
|
||||
assert output1.device.type == "cpu"
|
||||
+341
@@ -0,0 +1,341 @@
|
||||
import itertools
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
from invokeai.backend.model_manager.load.model_cache.cached_model.cached_model_with_partial_load import (
|
||||
CachedModelWithPartialLoad,
|
||||
)
|
||||
from invokeai.backend.model_manager.load.model_cache.torch_module_autocast.torch_module_autocast import (
|
||||
apply_custom_layers_to_model,
|
||||
)
|
||||
from invokeai.backend.util.calc_tensor_size import calc_tensor_size
|
||||
from tests.backend.model_manager.load.model_cache.cached_model.utils import (
|
||||
DummyModule,
|
||||
parameterize_keep_ram_copy,
|
||||
parameterize_mps_and_cuda,
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def model():
|
||||
model = DummyModule()
|
||||
apply_custom_layers_to_model(model)
|
||||
return model
|
||||
|
||||
|
||||
@parameterize_mps_and_cuda
|
||||
@parameterize_keep_ram_copy
|
||||
def test_cached_model_total_bytes(device: str, model: DummyModule, keep_ram_copy: bool):
|
||||
cached_model = CachedModelWithPartialLoad(
|
||||
model=model, compute_device=torch.device(device), keep_ram_copy=keep_ram_copy
|
||||
)
|
||||
linear1_numel = 10 * 32 + 32
|
||||
linear2_numel = 32 * 64 + 64
|
||||
buffer1_numel = 64
|
||||
# Note that the non-persistent buffer (buffer2) is not included in .total_bytes() calculation.
|
||||
assert cached_model.total_bytes() == (linear1_numel + linear2_numel + buffer1_numel) * 4
|
||||
|
||||
|
||||
@parameterize_mps_and_cuda
|
||||
@parameterize_keep_ram_copy
|
||||
def test_cached_model_cur_vram_bytes(device: str, model: DummyModule, keep_ram_copy: bool):
|
||||
# Model starts in CPU memory.
|
||||
cached_model = CachedModelWithPartialLoad(
|
||||
model=model, compute_device=torch.device(device), keep_ram_copy=keep_ram_copy
|
||||
)
|
||||
assert cached_model.cur_vram_bytes() == 0
|
||||
|
||||
# Full load the model into VRAM.
|
||||
cached_model.full_load_to_vram()
|
||||
assert cached_model.cur_vram_bytes() > 0
|
||||
assert cached_model.cur_vram_bytes() == cached_model.total_bytes()
|
||||
assert all(p.device.type == device for p in model.parameters())
|
||||
assert all(p.device.type == device for p in model.buffers())
|
||||
|
||||
|
||||
@parameterize_mps_and_cuda
|
||||
@parameterize_keep_ram_copy
|
||||
def test_cached_model_partial_load(device: str, model: DummyModule, keep_ram_copy: bool):
|
||||
# Model starts in CPU memory.
|
||||
cached_model = CachedModelWithPartialLoad(
|
||||
model=model, compute_device=torch.device(device), keep_ram_copy=keep_ram_copy
|
||||
)
|
||||
model_total_bytes = cached_model.total_bytes()
|
||||
assert cached_model.cur_vram_bytes() == 0
|
||||
|
||||
# Partially load the model into VRAM.
|
||||
target_vram_bytes = int(model_total_bytes * 0.6)
|
||||
loaded_bytes = cached_model.partial_load_to_vram(target_vram_bytes)
|
||||
|
||||
# Check that the model is partially loaded into VRAM.
|
||||
assert loaded_bytes > 0
|
||||
assert loaded_bytes < model_total_bytes
|
||||
assert loaded_bytes == cached_model.cur_vram_bytes()
|
||||
assert loaded_bytes == sum(
|
||||
calc_tensor_size(p)
|
||||
for n, p in itertools.chain(model.named_parameters(), model.named_buffers())
|
||||
if p.device.type == device and n != "buffer2"
|
||||
)
|
||||
|
||||
# Check that the model's modules have device autocasting enabled.
|
||||
assert model.linear1.is_device_autocasting_enabled()
|
||||
assert model.linear2.is_device_autocasting_enabled()
|
||||
|
||||
|
||||
@parameterize_mps_and_cuda
|
||||
@parameterize_keep_ram_copy
|
||||
def test_cached_model_partial_unload(device: str, model: DummyModule, keep_ram_copy: bool):
|
||||
# Model starts in CPU memory.
|
||||
cached_model = CachedModelWithPartialLoad(
|
||||
model=model, compute_device=torch.device(device), keep_ram_copy=keep_ram_copy
|
||||
)
|
||||
model_total_bytes = cached_model.total_bytes()
|
||||
assert cached_model.cur_vram_bytes() == 0
|
||||
|
||||
# Full load the model into VRAM.
|
||||
cached_model.full_load_to_vram()
|
||||
assert cached_model.cur_vram_bytes() == model_total_bytes
|
||||
|
||||
# Partially unload the model from VRAM.
|
||||
bytes_to_free = int(model_total_bytes * 0.4)
|
||||
freed_bytes = cached_model.partial_unload_from_vram(bytes_to_free)
|
||||
|
||||
# Check that the model is partially unloaded from VRAM.
|
||||
assert freed_bytes >= bytes_to_free
|
||||
assert freed_bytes < model_total_bytes
|
||||
assert freed_bytes == model_total_bytes - cached_model.cur_vram_bytes()
|
||||
assert freed_bytes == sum(
|
||||
calc_tensor_size(p) for p in itertools.chain(model.parameters(), model.buffers()) if p.device.type == "cpu"
|
||||
)
|
||||
|
||||
# Check that the model's modules still have device autocasting enabled.
|
||||
assert model.linear1.is_device_autocasting_enabled()
|
||||
assert model.linear2.is_device_autocasting_enabled()
|
||||
|
||||
|
||||
@parameterize_mps_and_cuda
|
||||
@parameterize_keep_ram_copy
|
||||
def test_cached_model_partial_unload_keep_required_weights_in_vram(
|
||||
device: str, model: DummyModule, keep_ram_copy: bool
|
||||
):
|
||||
# Model starts in CPU memory.
|
||||
cached_model = CachedModelWithPartialLoad(
|
||||
model=model, compute_device=torch.device(device), keep_ram_copy=keep_ram_copy
|
||||
)
|
||||
model_total_bytes = cached_model.total_bytes()
|
||||
assert cached_model.cur_vram_bytes() == 0
|
||||
|
||||
# Full load the model into VRAM.
|
||||
cached_model.full_load_to_vram()
|
||||
assert cached_model.cur_vram_bytes() == model_total_bytes
|
||||
|
||||
# Partially unload the model from VRAM, but request the required weights to be kept in VRAM.
|
||||
bytes_to_free = int(model_total_bytes)
|
||||
freed_bytes = cached_model.partial_unload_from_vram(bytes_to_free, keep_required_weights_in_vram=True)
|
||||
|
||||
# Check that the model is partially unloaded from VRAM.
|
||||
assert freed_bytes < model_total_bytes
|
||||
assert freed_bytes == model_total_bytes - cached_model.cur_vram_bytes()
|
||||
assert freed_bytes == sum(
|
||||
calc_tensor_size(p) for p in itertools.chain(model.parameters(), model.buffers()) if p.device.type == "cpu"
|
||||
)
|
||||
# The parameters should be offloaded to the CPU, because they are in Linear layers.
|
||||
assert all(p.device.type == "cpu" for p in model.parameters())
|
||||
# The buffer should still be on the device, because it is in a layer that does not support autocast.
|
||||
assert all(p.device.type == device for p in model.buffers())
|
||||
|
||||
# Check that the model's modules still have device autocasting enabled.
|
||||
assert model.linear1.is_device_autocasting_enabled()
|
||||
assert model.linear2.is_device_autocasting_enabled()
|
||||
|
||||
|
||||
@parameterize_mps_and_cuda
|
||||
@parameterize_keep_ram_copy
|
||||
def test_cached_model_full_load_and_unload(device: str, model: DummyModule, keep_ram_copy: bool):
|
||||
cached_model = CachedModelWithPartialLoad(
|
||||
model=model, compute_device=torch.device(device), keep_ram_copy=keep_ram_copy
|
||||
)
|
||||
|
||||
# Model starts in CPU memory.
|
||||
model_total_bytes = cached_model.total_bytes()
|
||||
assert cached_model.cur_vram_bytes() == 0
|
||||
|
||||
# Full load the model into VRAM.
|
||||
loaded_bytes = cached_model.full_load_to_vram()
|
||||
assert loaded_bytes > 0
|
||||
assert loaded_bytes == model_total_bytes
|
||||
assert loaded_bytes == cached_model.cur_vram_bytes()
|
||||
assert all(p.device.type == device for p in itertools.chain(model.parameters(), model.buffers()))
|
||||
assert not model.linear1.is_device_autocasting_enabled()
|
||||
assert not model.linear2.is_device_autocasting_enabled()
|
||||
|
||||
# Full unload the model from VRAM.
|
||||
unloaded_bytes = cached_model.full_unload_from_vram()
|
||||
|
||||
# Check that the model is fully unloaded from VRAM.
|
||||
assert unloaded_bytes > 0
|
||||
assert unloaded_bytes == model_total_bytes
|
||||
assert cached_model.cur_vram_bytes() == 0
|
||||
# Note that the non-persistent buffer (buffer2) is not required to be unloaded from VRAM.
|
||||
assert all(
|
||||
p.device.type == "cpu"
|
||||
for n, p in itertools.chain(model.named_parameters(), model.named_buffers())
|
||||
if n != "buffer2"
|
||||
)
|
||||
|
||||
|
||||
@parameterize_mps_and_cuda
|
||||
@parameterize_keep_ram_copy
|
||||
def test_cached_model_full_load_from_partial(device: str, model: DummyModule, keep_ram_copy: bool):
|
||||
cached_model = CachedModelWithPartialLoad(
|
||||
model=model, compute_device=torch.device(device), keep_ram_copy=keep_ram_copy
|
||||
)
|
||||
|
||||
# Model starts in CPU memory.
|
||||
model_total_bytes = cached_model.total_bytes()
|
||||
assert cached_model.cur_vram_bytes() == 0
|
||||
|
||||
# Partially load the model into VRAM.
|
||||
target_vram_bytes = int(model_total_bytes * 0.6)
|
||||
loaded_bytes = cached_model.partial_load_to_vram(target_vram_bytes)
|
||||
assert loaded_bytes > 0
|
||||
assert loaded_bytes < model_total_bytes
|
||||
assert loaded_bytes == cached_model.cur_vram_bytes()
|
||||
assert model.linear1.is_device_autocasting_enabled()
|
||||
assert model.linear2.is_device_autocasting_enabled()
|
||||
|
||||
# Full load the rest of the model into VRAM.
|
||||
loaded_bytes_2 = cached_model.full_load_to_vram()
|
||||
assert loaded_bytes_2 > 0
|
||||
assert loaded_bytes_2 < model_total_bytes
|
||||
assert loaded_bytes + loaded_bytes_2 == cached_model.cur_vram_bytes()
|
||||
assert loaded_bytes + loaded_bytes_2 == model_total_bytes
|
||||
assert all(p.device.type == device for p in itertools.chain(model.parameters(), model.buffers()))
|
||||
assert not model.linear1.is_device_autocasting_enabled()
|
||||
assert not model.linear2.is_device_autocasting_enabled()
|
||||
|
||||
|
||||
@parameterize_mps_and_cuda
|
||||
@parameterize_keep_ram_copy
|
||||
def test_cached_model_full_unload_from_partial(device: str, model: DummyModule, keep_ram_copy: bool):
|
||||
cached_model = CachedModelWithPartialLoad(
|
||||
model=model, compute_device=torch.device(device), keep_ram_copy=keep_ram_copy
|
||||
)
|
||||
|
||||
# Model starts in CPU memory.
|
||||
model_total_bytes = cached_model.total_bytes()
|
||||
assert cached_model.cur_vram_bytes() == 0
|
||||
|
||||
# Partially load the model into VRAM.
|
||||
target_vram_bytes = int(model_total_bytes * 0.6)
|
||||
loaded_bytes = cached_model.partial_load_to_vram(target_vram_bytes)
|
||||
assert loaded_bytes > 0
|
||||
assert loaded_bytes < model_total_bytes
|
||||
assert loaded_bytes == cached_model.cur_vram_bytes()
|
||||
|
||||
# Full unload the model from VRAM.
|
||||
unloaded_bytes = cached_model.full_unload_from_vram()
|
||||
assert unloaded_bytes > 0
|
||||
assert unloaded_bytes == loaded_bytes
|
||||
assert cached_model.cur_vram_bytes() == 0
|
||||
# Note that the non-persistent buffer (buffer2) is not required to be unloaded from VRAM.
|
||||
assert all(
|
||||
p.device.type == "cpu"
|
||||
for n, p in itertools.chain(model.named_parameters(), model.named_buffers())
|
||||
if n != "buffer2"
|
||||
)
|
||||
|
||||
|
||||
@parameterize_mps_and_cuda
|
||||
def test_cached_model_get_cpu_state_dict(device: str, model: DummyModule):
|
||||
cached_model = CachedModelWithPartialLoad(model=model, compute_device=torch.device(device), keep_ram_copy=True)
|
||||
|
||||
# Model starts in CPU memory.
|
||||
assert cached_model.cur_vram_bytes() == 0
|
||||
|
||||
# The CPU state dict can be accessed and has the expected properties.
|
||||
cpu_state_dict = cached_model.get_cpu_state_dict()
|
||||
assert cpu_state_dict is not None
|
||||
assert len(cpu_state_dict) == len(model.state_dict())
|
||||
assert all(p.device.type == "cpu" for p in cpu_state_dict.values())
|
||||
|
||||
# Full load the model into VRAM.
|
||||
cached_model.full_load_to_vram()
|
||||
assert cached_model.cur_vram_bytes() == cached_model.total_bytes()
|
||||
|
||||
# The CPU state dict is still available, and still on the CPU.
|
||||
cpu_state_dict = cached_model.get_cpu_state_dict()
|
||||
assert cpu_state_dict is not None
|
||||
assert len(cpu_state_dict) == len(model.state_dict())
|
||||
assert all(p.device.type == "cpu" for p in cpu_state_dict.values())
|
||||
|
||||
|
||||
@parameterize_mps_and_cuda
|
||||
@parameterize_keep_ram_copy
|
||||
def test_cached_model_full_load_and_inference(device: str, model: DummyModule, keep_ram_copy: bool):
|
||||
cached_model = CachedModelWithPartialLoad(
|
||||
model=model, compute_device=torch.device(device), keep_ram_copy=keep_ram_copy
|
||||
)
|
||||
# Model starts in CPU memory.
|
||||
model_total_bytes = cached_model.total_bytes()
|
||||
assert cached_model.cur_vram_bytes() == 0
|
||||
|
||||
# Run inference on the CPU.
|
||||
x = torch.randn(1, 10)
|
||||
output1 = model(x)
|
||||
assert output1.device.type == "cpu"
|
||||
|
||||
# Full load the model into VRAM.
|
||||
loaded_bytes = cached_model.full_load_to_vram()
|
||||
assert loaded_bytes > 0
|
||||
assert loaded_bytes == model_total_bytes
|
||||
assert loaded_bytes == cached_model.cur_vram_bytes()
|
||||
assert all(p.device.type == device for p in itertools.chain(model.parameters(), model.buffers()))
|
||||
|
||||
# Run inference on the GPU.
|
||||
output2 = model(x.to(device))
|
||||
assert output2.device.type == device
|
||||
|
||||
# The outputs should be the same for both runs.
|
||||
assert torch.allclose(output1, output2.to("cpu"))
|
||||
|
||||
|
||||
@parameterize_mps_and_cuda
|
||||
@parameterize_keep_ram_copy
|
||||
def test_cached_model_partial_load_and_inference(device: str, model: DummyModule, keep_ram_copy: bool):
|
||||
# Model starts in CPU memory.
|
||||
cached_model = CachedModelWithPartialLoad(
|
||||
model=model, compute_device=torch.device(device), keep_ram_copy=keep_ram_copy
|
||||
)
|
||||
model_total_bytes = cached_model.total_bytes()
|
||||
assert cached_model.cur_vram_bytes() == 0
|
||||
|
||||
# Run inference on the CPU.
|
||||
x = torch.randn(1, 10)
|
||||
output1 = model(x)
|
||||
assert output1.device.type == "cpu"
|
||||
|
||||
# Partially load the model into VRAM.
|
||||
target_vram_bytes = int(model_total_bytes * 0.6)
|
||||
loaded_bytes = cached_model.partial_load_to_vram(target_vram_bytes)
|
||||
|
||||
# Check that the model is partially loaded into VRAM.
|
||||
assert loaded_bytes > 0
|
||||
assert loaded_bytes < model_total_bytes
|
||||
assert loaded_bytes == cached_model.cur_vram_bytes()
|
||||
assert loaded_bytes == sum(
|
||||
calc_tensor_size(p)
|
||||
for n, p in itertools.chain(model.named_parameters(), model.named_buffers())
|
||||
if p.device.type == device and n != "buffer2"
|
||||
)
|
||||
# Check that the model's modules have device autocasting enabled.
|
||||
assert model.linear1.is_device_autocasting_enabled()
|
||||
assert model.linear2.is_device_autocasting_enabled()
|
||||
|
||||
# Run inference on the GPU.
|
||||
output2 = model(x.to(device))
|
||||
assert output2.device.type == device
|
||||
|
||||
# The output should be the same as the output from the CPU.
|
||||
assert torch.allclose(output1, output2.to("cpu"))
|
||||
+47
@@ -0,0 +1,47 @@
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
from invokeai.backend.model_manager.load.model_cache.cached_model.cached_model_with_partial_load import (
|
||||
CachedModelWithPartialLoad,
|
||||
)
|
||||
from invokeai.backend.model_manager.load.model_cache.torch_module_autocast.torch_module_autocast import (
|
||||
apply_custom_layers_to_model,
|
||||
)
|
||||
|
||||
|
||||
class ModelWithRequiredScale(torch.nn.Module):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.linear = torch.nn.Linear(4, 4)
|
||||
self.scale = torch.nn.Parameter(torch.ones(4))
|
||||
|
||||
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
||||
return self.linear(x) * self.scale
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"device",
|
||||
[
|
||||
pytest.param(
|
||||
torch.device("cuda"), marks=pytest.mark.skipif(not torch.cuda.is_available(), reason="requires CUDA device")
|
||||
),
|
||||
pytest.param(
|
||||
torch.device("mps"),
|
||||
marks=pytest.mark.skipif(not torch.backends.mps.is_available(), reason="requires MPS device"),
|
||||
),
|
||||
],
|
||||
)
|
||||
@pytest.mark.parametrize("keep_ram_copy", [True, False])
|
||||
@torch.no_grad()
|
||||
def test_repair_required_tensors_on_compute_device(device: torch.device, keep_ram_copy: bool):
|
||||
model = ModelWithRequiredScale()
|
||||
apply_custom_layers_to_model(model, device_autocasting_enabled=True)
|
||||
cached_model = CachedModelWithPartialLoad(model=model, compute_device=device, keep_ram_copy=keep_ram_copy)
|
||||
|
||||
cached_model._cur_vram_bytes = 0
|
||||
repaired_tensors = cached_model.repair_required_tensors_on_compute_device()
|
||||
|
||||
assert repaired_tensors == 1
|
||||
assert cached_model._cur_vram_bytes is None
|
||||
assert model.scale.device.type == device.type
|
||||
assert all(param.device.type == "cpu" for param in model.linear.parameters())
|
||||
@@ -0,0 +1,41 @@
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import torch
|
||||
|
||||
|
||||
class DummyModule(torch.nn.Module):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.linear1 = torch.nn.Linear(10, 32)
|
||||
self.linear2 = torch.nn.Linear(32, 64)
|
||||
self.register_buffer("buffer1", torch.ones(64))
|
||||
# Non-persistent buffers are not included in the state dict. We need to make sure that this case is handled
|
||||
# correctly by the partial loading code.
|
||||
self.register_buffer("buffer2", torch.ones(64), persistent=False)
|
||||
|
||||
def forward(self, x: torch.Tensor) -> torch.Tensor:
|
||||
x = self.linear1(x)
|
||||
x = self.linear2(x)
|
||||
x = x + self.buffer1
|
||||
x = x + self.buffer2
|
||||
return x
|
||||
|
||||
|
||||
is_github_ci = os.getenv("GITHUB_ACTIONS") == "true"
|
||||
|
||||
parameterize_mps_and_cuda = pytest.mark.parametrize(
|
||||
("device"),
|
||||
[
|
||||
pytest.param(
|
||||
"mps",
|
||||
marks=pytest.mark.skipif(
|
||||
is_github_ci or not torch.backends.mps.is_available(),
|
||||
reason="MPS is very flaky in CI" if is_github_ci else "MPS is not available.",
|
||||
),
|
||||
),
|
||||
pytest.param("cuda", marks=pytest.mark.skipif(not torch.cuda.is_available(), reason="CUDA is not available.")),
|
||||
],
|
||||
)
|
||||
|
||||
parameterize_keep_ram_copy = pytest.mark.parametrize("keep_ram_copy", [True, False])
|
||||
Reference in New Issue
Block a user