vllm-project--vllm-omni
165 行
5.5 KiB
Python
165 行
5.5 KiB
Python
"""
|
|
Tests for hook registry.
|
|
|
|
NOTE: The hook registry is also tested indirectly through a lot of
|
|
other tests, e.g., tests/diffusion/distributed/test_sp_plan_hooks.py
|
|
"""
|
|
|
|
from typing import Any
|
|
|
|
import pytest
|
|
from torch import nn
|
|
|
|
from vllm_omni.diffusion.hooks.base import HookRegistry, ModelHook
|
|
|
|
DEFAULT_OUT = "ECHO"
|
|
OVERRIDE_OUT = "OVERRIDE"
|
|
INPUT_KWARG = "inp"
|
|
|
|
|
|
class EchoModule(nn.Module):
|
|
"""Just echo the input."""
|
|
|
|
def __init__(self, *args: Any, **kwargs: Any) -> None:
|
|
super().__init__(*args, **kwargs)
|
|
|
|
def forward(self, *args, **kwargs):
|
|
input_val = kwargs[INPUT_KWARG]
|
|
return input_val + DEFAULT_OUT
|
|
|
|
|
|
class AppendHook(ModelHook):
|
|
"""Append an echo value to the input string on pre / post forward."""
|
|
|
|
def __init__(self, echo_val: str):
|
|
self.echo_val = echo_val
|
|
|
|
def pre_forward(self, module: nn.Module, *args, **kwargs):
|
|
input_val = kwargs[INPUT_KWARG]
|
|
return (), {INPUT_KWARG: input_val + self.echo_val}
|
|
|
|
def post_forward(self, module: nn.Module, output):
|
|
return output + self.echo_val
|
|
|
|
|
|
class OverrideAppendHook(AppendHook):
|
|
"""Same as AppendHook, but replace the forward call with a different string."""
|
|
|
|
def new_forward(self, module: nn.Module, *args, **kwargs):
|
|
return kwargs[INPUT_KWARG] + OVERRIDE_OUT
|
|
|
|
|
|
def test_register_no_fwd_override_hooks():
|
|
"""Ensure registration is correct with no forward hooks."""
|
|
mod = EchoModule()
|
|
registry = HookRegistry.get_or_create(mod)
|
|
first_hook = AppendHook("1")
|
|
second_hook = AppendHook("2")
|
|
sorted_no_fwd_hooks = [first_hook, second_hook]
|
|
|
|
# Will add and sort the hook by key
|
|
registry.register_hook(name="b", hook=second_hook)
|
|
registry.register_hook(name="a", hook=first_hook)
|
|
|
|
assert len(registry._hooks) == 2
|
|
assert len(registry._sorted_hooks) == 2
|
|
assert registry._new_fwd_impl_hook is None
|
|
# Ensure registering a new hook sorting alphabetically
|
|
for actual_hook, expected_hook in zip(registry._sorted_hooks, sorted_no_fwd_hooks):
|
|
assert actual_hook is expected_hook
|
|
|
|
|
|
def test_register_with_forward_hooks():
|
|
"""Ensure registration is correct with a forward hooks."""
|
|
mod = EchoModule()
|
|
registry = HookRegistry.get_or_create(mod)
|
|
first_hook = AppendHook("1")
|
|
second_hook = AppendHook("2")
|
|
exec_hook = OverrideAppendHook("3")
|
|
sorted_no_fwd_hooks = [first_hook, second_hook]
|
|
|
|
# Will add and sort the hook by key
|
|
registry.register_hook(name="b", hook=second_hook)
|
|
registry.register_hook(name="a", hook=first_hook)
|
|
registry.register_hook(name="c", hook=exec_hook)
|
|
|
|
assert len(registry._hooks) == 3
|
|
assert len(registry._sorted_hooks) == 3
|
|
assert registry._new_fwd_impl_hook is exec_hook
|
|
# Ensure registering a new hook sorting alphabetically
|
|
for actual_hook, expected_hook in zip(registry._sorted_hooks, sorted_no_fwd_hooks):
|
|
assert actual_hook is expected_hook
|
|
|
|
|
|
def test_register_fails_with_multiple_forward_hooks():
|
|
"""Ensure registration only allows one hook overriding new_forward"""
|
|
mod = EchoModule()
|
|
registry = HookRegistry.get_or_create(mod)
|
|
|
|
registry.register_hook(name="foo", hook=OverrideAppendHook("1"))
|
|
with pytest.raises(RuntimeError):
|
|
registry.register_hook(name="bar", hook=OverrideAppendHook("2"))
|
|
|
|
|
|
def test_remove_hooks():
|
|
"""Ensure removal sorts hooks."""
|
|
mod = EchoModule()
|
|
registry = HookRegistry.get_or_create(mod)
|
|
|
|
first_hook = AppendHook("1")
|
|
second_hook = AppendHook("2")
|
|
exec_hook = OverrideAppendHook("3")
|
|
|
|
registry.register_hook(name="b", hook=second_hook)
|
|
registry.register_hook(name="a", hook=first_hook)
|
|
registry.register_hook(name="c", hook=exec_hook)
|
|
# Explicitly reorder our hooks to be in the wrong order, since register
|
|
# forces them to be sorted too. Ensure that remove the hook will also
|
|
# enforce the sorted order.
|
|
registry._sorted_hooks = [second_hook, first_hook]
|
|
|
|
assert registry._new_fwd_impl_hook is exec_hook
|
|
registry.remove_hook("c")
|
|
assert registry._new_fwd_impl_hook is None
|
|
|
|
sorted_no_fwd_hooks = [first_hook, second_hook]
|
|
for actual_hook, expected_hook in zip(registry._sorted_hooks, sorted_no_fwd_hooks):
|
|
assert actual_hook is expected_hook
|
|
|
|
|
|
def test_dispatch_no_fwd_override_hooks():
|
|
"""Ensure dispatch runs hooks in deterministic sorted order."""
|
|
mod = EchoModule()
|
|
registry = HookRegistry.get_or_create(mod)
|
|
|
|
first_hook = AppendHook("1")
|
|
second_hook = AppendHook("2")
|
|
|
|
# Register will sort the hooks, so hook 1 will run first
|
|
# on preprocess and last in post process
|
|
registry.register_hook(name="2", hook=second_hook)
|
|
registry.register_hook(name="1", hook=first_hook)
|
|
res = registry.dispatch(inp="")
|
|
assert isinstance(res, str)
|
|
assert res == f"12{DEFAULT_OUT}21"
|
|
|
|
|
|
def test_dispatch_with_fwd_hooks():
|
|
"""Ensure dispatch runs hooks in deterministic sorted order."""
|
|
mod = EchoModule()
|
|
registry = HookRegistry.get_or_create(mod)
|
|
|
|
first_hook = AppendHook("1")
|
|
second_hook = AppendHook("2")
|
|
exec_hook = OverrideAppendHook("3")
|
|
|
|
# Register will sort the hooks, so hook 1 will run first on preprocess and last in
|
|
# post process. Since the override hook mutates forward, it will run last even
|
|
# though the name of the exec_hook is alphabetically before the second hook.
|
|
registry.register_hook(name="c", hook=second_hook)
|
|
registry.register_hook(name="a", hook=first_hook)
|
|
registry.register_hook(name="b", hook=exec_hook)
|
|
res = registry.dispatch(inp="")
|
|
assert isinstance(res, str)
|
|
assert res == f"123{OVERRIDE_OUT}321"
|