refactor(inputs): build the Omni renderer as a subclass instead of a proxy
OmniRenderer wrapped the upstream renderer, forwarded unknown attributes via
__getattr__, and re-bound eight BaseRenderer methods (render_cmpl, render_chat,
process_for_engine, _process_enc_dec and their async twins) onto the wrapper
so that they would reach Omni's _process_singleton. That allow-list hid any
concrete-renderer override of those methods and would silently skip the
Omni extras for any future BaseRenderer entry point not on the list.
Make OmniRenderer a mixin and create the renderer as a real subclass of the
upstream class instead:
- omni_renderer_cls(base_cls) builds (and caches) Omni<Base> = (OmniRenderer,
base_cls); only _process_singleton / _process_singleton_async are extended,
everything else is the upstream renderer unchanged.
- build_omni_renderer(vllm_config) resolves the class the way upstream
renderer_from_config does (cached_tokenizer_from_config,
tokenizer_args_from_config, RENDERER_REGISTRY.load_renderer_cls) and
instantiates the Omni subclass; renderer_cls/tokenizer can be passed to
skip the resolution.
- build_stage0_input_processor passes the renderer to InputProcessor up
front instead of replacing it afterwards; the skip-tokenizer branch keeps
_build_token_only_renderer and never resolves a tokenizer.
Rebinding an existing instance's __class__ was rejected: CPython refuses it
for HfRenderer ("object layout differs"), so the subclass is constructed.
Tests: tests/inputs/test_preprocess.py builds the renderer with
omni_renderer_cls and adds checks that the subclass is created once, that a
concrete renderer's render_cmpl override is no longer suppressed, and that
build_omni_renderer resolves like upstream or honours an explicit class;
tests/engine/test_async_omni_engine_stage_init.py asserts the stage-0
processor receives the built subclass instance and that the skip-tokenizer
branch never calls build_omni_renderer. Two pre-existing mypy complaints in
test_preprocess.py are fixed with annotations.
Validation: ruff, pre-commit (incl. mypy) on the four files; the modules
import and omni_renderer_cls(HfRenderer) builds; tests/inputs (32),
tests/engine/test_async_omni_engine_stage_init.py (55) and tests/entrypoints
(548) pass on CPU.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MpXe2LeRrUQGjZfKFaocMZ
Signed-off-by: tzhouam <tzhouam@connect.ust.hk>
This commit is contained in:
1 parent
a58825ed6c
commit
fb94b87d4b
4 files changed
+184
-53
No files matched your search
@@ -1593,22 +1593,34 @@ def test_model_path_resolver_is_generic_and_model_owned(tmp_path):
|
||||
assert "model_path_resolver" not in engine_args
|
||||
|
||||
|
||||
def test_build_stage0_input_processor_wraps_active_renderer(monkeypatch):
|
||||
import vllm_omni.engine.stage_init_utils as init_mod
|
||||
from vllm_omni.inputs.preprocess import OmniRenderer
|
||||
def test_build_stage0_input_processor_uses_omni_renderer_subclass(monkeypatch):
|
||||
from vllm.renderers import BaseRenderer
|
||||
|
||||
original = object()
|
||||
import vllm_omni.engine.stage_init_utils as init_mod
|
||||
from vllm_omni.inputs.preprocess import OmniRenderer, omni_renderer_cls
|
||||
|
||||
class _Base(BaseRenderer):
|
||||
def __init__(self, config, tokenizer):
|
||||
self.config, self.tokenizer = config, tokenizer
|
||||
|
||||
def render_messages(self, messages, params): # pragma: no cover - abstract stub
|
||||
raise NotImplementedError
|
||||
|
||||
config = types.SimpleNamespace(model_config=types.SimpleNamespace(try_get_generation_config=lambda: {}))
|
||||
built = omni_renderer_cls(_Base)(config, "tok")
|
||||
seen = {}
|
||||
|
||||
class DummyInputProcessor:
|
||||
def __init__(self, vllm_config, renderer=None):
|
||||
self.renderer = renderer or original
|
||||
seen["renderer"] = renderer
|
||||
self.renderer = renderer
|
||||
|
||||
monkeypatch.setattr(init_mod, "InputProcessor", DummyInputProcessor)
|
||||
processor = build_stage0_input_processor(
|
||||
types.SimpleNamespace(model_config=types.SimpleNamespace(try_get_generation_config=lambda: {}))
|
||||
)
|
||||
monkeypatch.setattr(init_mod, "build_omni_renderer", lambda cfg: built if cfg is config else None)
|
||||
processor = build_stage0_input_processor(config)
|
||||
assert seen["renderer"] is built
|
||||
assert isinstance(processor.renderer, OmniRenderer)
|
||||
assert processor.renderer._renderer is original
|
||||
assert isinstance(processor.renderer, _Base)
|
||||
assert not hasattr(processor, "input_preprocessor")
|
||||
|
||||
|
||||
@@ -1622,13 +1634,17 @@ def test_build_stage0_input_processor_does_not_resolve_tokenizer_when_skipped(mo
|
||||
assert renderer is original
|
||||
self.renderer = renderer
|
||||
|
||||
def _must_not_resolve(_cfg):
|
||||
raise AssertionError("tokenizer must not be resolved when skip_tokenizer_init=True")
|
||||
|
||||
monkeypatch.setattr(init_mod, "InputProcessor", DummyInputProcessor)
|
||||
monkeypatch.setattr(init_mod, "_build_token_only_renderer", lambda _: original)
|
||||
monkeypatch.setattr(init_mod, "build_omni_renderer", _must_not_resolve)
|
||||
config = types.SimpleNamespace(
|
||||
model_config=types.SimpleNamespace(skip_tokenizer_init=True, try_get_generation_config=lambda: {})
|
||||
)
|
||||
processor = build_stage0_input_processor(config)
|
||||
assert processor.renderer._renderer is original
|
||||
assert processor.renderer is original
|
||||
|
||||
|
||||
def test_inject_kv_stage_info_infers_sender_tp_topology():
|
||||
|
||||
Reference in new issue
Block a user