Files
ModelTC-ComfyUI-Lightx2vWra…/tests/test_runner_memory.py
T

177 lines
8.2 KiB
Python

"""Regression checks for switching cached LightX2V model outputs."""
import importlib.util
import sys
import threading
import types
import unittest
from pathlib import Path
from unittest.mock import Mock
class RunnerMemoryTests(unittest.TestCase):
def setUp(self):
package = "_runner_memory_test_package"
nodes_package = package + ".nodes"
self.build_runner = Mock(side_effect=lambda config: {"model_cls": config["model_cls"]})
self.release_modular = Mock()
self.empty_cache = Mock()
self.unload_native = Mock()
self.soft_empty_cache = Mock()
self.native_free_memory = Mock(return_value=["native-unloaded"])
self.modular_node = types.SimpleNamespace(_current_runner=None, _release_runner=self.release_modular)
modules = {
package: types.ModuleType(package),
nodes_package: types.ModuleType(nodes_package),
nodes_package + "._lightx2v_runner": types.ModuleType(nodes_package + "._lightx2v_runner"),
nodes_package + ".inference": types.ModuleType(nodes_package + ".inference"),
"torch": types.ModuleType("torch"),
"comfy": types.ModuleType("comfy"),
"comfy.model_management": types.ModuleType("comfy.model_management"),
}
modules[nodes_package + "._lightx2v_runner"].build_runner = self.build_runner
modules[nodes_package + ".inference"].LightX2VModularInferenceV2 = self.modular_node
modules["torch"].cuda = types.SimpleNamespace(empty_cache=self.empty_cache)
modules["comfy"].model_management = modules["comfy.model_management"]
modules["comfy.model_management"].unload_all_models = self.unload_native
modules["comfy.model_management"].soft_empty_cache = self.soft_empty_cache
modules["comfy.model_management"].free_memory = self.native_free_memory
self.saved_modules = {name: sys.modules.get(name) for name in modules}
sys.modules.update(modules)
path = Path(__file__).resolve().parents[1] / "nodes" / "_runner_memory.py"
name = nodes_package + "._runner_memory"
spec = importlib.util.spec_from_file_location(name, path)
module = importlib.util.module_from_spec(spec)
sys.modules[name] = module
spec.loader.exec_module(module)
self.module = module
self.module_name = name
def tearDown(self):
sys.modules.pop(self.module_name, None)
for name, previous in self.saved_modules.items():
if previous is None:
sys.modules.pop(name, None)
else:
sys.modules[name] = previous
def test_switch_evicts_runner_from_cached_loader_output(self):
first = self.module.make_runner_handle({"model_cls": "seedvr2"})
second = self.module.make_runner_handle({"model_cls": "swiftvr"})
self.assertIsNone(first["runner"])
self.assertEqual(second["runner"]["model_cls"], "swiftvr")
self.assertEqual(self.unload_native.call_count, 2)
self.assertGreaterEqual(self.empty_cache.call_count, 2)
restored = self.module.get_runner(first)
self.assertEqual(restored["model_cls"], "seedvr2")
self.assertIsNone(second["runner"])
self.assertEqual(self.build_runner.call_count, 3)
self.assertEqual(self.unload_native.call_count, 3)
def test_native_memory_request_evicts_lightx2v_runner(self):
handle = self.module.make_runner_handle({"model_cls": "swiftvr"})
self.module.install_comfyui_memory_hook()
self.module.install_comfyui_memory_hook()
result = sys.modules["comfy.model_management"].free_memory(100, types.SimpleNamespace(type="cuda"))
self.assertEqual(result, ["native-unloaded"])
self.assertIsNone(handle["runner"])
self.native_free_memory.assert_called_once()
def test_active_lightx2v_request_cannot_be_evicted(self):
handle = self.module.make_runner_handle({"model_cls": "seedvr2"})
self.module.install_comfyui_memory_hook()
with self.module.active_lightx2v_request():
sys.modules["comfy.model_management"].free_memory(100, types.SimpleNamespace(type="cuda"))
self.assertIsNotNone(handle["runner"])
sys.modules["comfy.model_management"].free_memory(100, types.SimpleNamespace(type="cuda"))
self.assertIsNone(handle["runner"])
def test_native_memory_request_evicts_modular_runner(self):
self.modular_node._current_runner = types.SimpleNamespace(model=object())
self.module.install_comfyui_memory_hook()
sys.modules["comfy.model_management"].free_memory(100, types.SimpleNamespace(type="cuda"))
self.release_modular.assert_called_once_with()
def test_native_memory_hook_does_not_reenter_during_teardown(self):
self.modular_node._current_runner = types.SimpleNamespace(model=object())
self.module.install_comfyui_memory_hook()
model_management = sys.modules["comfy.model_management"]
self.release_modular.side_effect = lambda: model_management.free_memory(100, types.SimpleNamespace(type="cuda"))
model_management.free_memory(100, types.SimpleNamespace(type="cuda"))
self.release_modular.assert_called_once_with()
def test_concurrent_native_load_waits_for_lightx2v_request(self):
handle = self.module.make_runner_handle({"model_cls": "swiftvr"})
self.module.install_comfyui_memory_hook()
started = threading.Event()
finished = threading.Event()
def load_native():
started.set()
sys.modules["comfy.model_management"].free_memory(100, types.SimpleNamespace(type="cuda"))
finished.set()
with self.module.active_lightx2v_request():
thread = threading.Thread(target=load_native)
thread.start()
self.assertTrue(started.wait(1))
self.assertFalse(finished.wait(0.05))
self.assertIsNotNone(handle["runner"])
thread.join(timeout=1)
self.assertTrue(finished.is_set())
self.assertIsNone(handle["runner"])
def test_cpu_memory_request_keeps_lightx2v_runner(self):
handle = self.module.make_runner_handle({"model_cls": "swiftvr"})
self.module.install_comfyui_memory_hook()
sys.modules["comfy.model_management"].free_memory(100, types.SimpleNamespace(type="cpu"))
self.assertIsNotNone(handle["runner"])
def test_modular_switch_releases_restoration_runner(self):
cached_output = self.module.make_runner_handle({"model_cls": "swiftvr"})
self.module.release_restoration_runners()
self.assertIsNone(cached_output["runner"])
self.assertEqual(cached_output.startup_config, {"model_cls": "swiftvr"})
def test_seedvr_switch_clears_bound_method_cache(self):
clear = Mock()
name = "lightx2v.models.networks.seedvr.utils.rope"
previous = sys.modules.get(name)
rope_module = types.ModuleType(name)
rope_module.RotaryEmbeddingBase = types.SimpleNamespace(get_axial_freqs=types.SimpleNamespace(cache_clear=clear))
sys.modules[name] = rope_module
try:
cached_output = self.module.make_runner_handle({"model_cls": "seedvr2"})
self.module.make_runner_handle({"model_cls": "swiftvr"})
self.assertIsNone(cached_output["runner"])
clear.assert_called_once_with()
finally:
if previous is None:
sys.modules.pop(name, None)
else:
sys.modules[name] = previous
def test_dispose_runner_drops_swiftvr_restorer(self):
runner = types.SimpleNamespace(model=object(), restorer=object(), vae_decoder=object())
self.module.dispose_runner(runner)
self.assertFalse(hasattr(runner, "model"))
self.assertFalse(hasattr(runner, "restorer"))
self.assertFalse(hasattr(runner, "vae_decoder"))
def test_dispose_runner_recurses_into_rs2v_clips(self):
child = types.SimpleNamespace(model=object(), vae_decoder=object())
shot = types.SimpleNamespace(clip_generators={"rs2v_clip": child})
self.module.dispose_runner(shot)
self.assertFalse(hasattr(shot, "clip_generators"))
self.assertFalse(hasattr(child, "model"))
if __name__ == "__main__":
unittest.main()