@tduncan / Nvidia-Optimization-Stack-VLLM Mirror · read-only / commits / 289cee0473
Fork 0

A high-throughput and memory-efficient inference and serving engine for LLMs

Mirrored from https://github.com/tduncan-lum/Nvidia-Optimization-Stack---VLLM.git

Synced Nov 3, 20:15 UTC

[vLLM IR] Minor improvements (#39362) (#39558)

Signed-off-by: Avishek Goswami <avishek.goswami@ibm.com> Co-authored-by: Avishek Goswami <avishek.goswami@ibm.com>

Avishek Goswami committed May 12, 2026 at 20:28 UTC 289cee047390ffec8c61600e887232fc8b339e2c
3 files changed +328 -89
tests/conftest.py
+41
@@ -1686,3 +1686,44 @@ def disable_log_dedup(monkeypatch):
1686 logger._print_warning_once = original_print_warning_once
1687 logger._print_info_once = original_print_info_once
1688 logger._print_debug_once = original_print_debug_once
1689 +
1690 +
1691 +@pytest.fixture(scope="function")
1692 +def fake_vllm_ir(monkeypatch):
1693 + """
1694 + Pytest fixture to allow isolated IR op registration in tests.
1695 +
1696 + Replaces IrOp.registry with an empty dict and swaps ``vllm_ir_torch_lib`` for a
1697 + fresh ``Library`` with a unique namespace per test (see ``Library.ns``).
1698 +
1699 + Torch keeps registrations for the process lifetime; reusing the fragment
1700 + name ``vllm_ir`` and defining the same op string again can segfault. A
1701 + random library name keeps each fixture run on a disjoint namespace.
1702 +
1703 + The test Library is kept alive until after monkeypatch teardown so PyTorch's
1704 + C++ state is not freed while references may still exist.
1705 +
1706 + Usage:
1707 + def test_my_ir_op(fake_vllm_ir):
1708 + @vllm.ir.register_op
1709 + def my_test_op(x: torch.Tensor) -> torch.Tensor:
1710 + return x * 2
1711 +
1712 + result = my_test_op(torch.tensor([1, 2, 3]))
1713 + # Registry and library cleaned up automatically after the test
1714 + """
1715 + import secrets
1716 +
1717 + from torch.library import Library
1718 + from vllm.ir.op import IrOp
1719 +
1720 + monkeypatch.setattr(IrOp, "registry", {})
1721 +
1722 + # Keep a local reference so the Library is not GC'd before monkeypatch
1723 + # teardown restores the original reference.
1724 + test_lib = Library(f"vllm_ir_{secrets.token_hex(8)}", "FRAGMENT")
1725 + monkeypatch.setattr("vllm.ir.op.vllm_ir_torch_lib", test_lib)
1726 +
1727 + yield
1728 +
1729 + del test_lib
tests/ir/test_op.py
+214 -66
@@ -13,20 +13,40 @@ from torch.fx.experimental.proxy_tensor import make_fx
13 import vllm.ir.op
14 from vllm.ir.op import RESERVED_PROVIDERS, IrOp, IrOpImpl
15
16 -# This should not exist
17 -assert "_custom_add" not in IrOp.registry
18 -
16
17 class CustomError(Exception):
18 pass
19
20
24 -@vllm.ir.register_op(allow_inplace=True)
25 -def _custom_add(x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
26 - return x + y
21 +@pytest.fixture
22 +def custom_add_op(fake_vllm_ir):
23 + """Register ``_custom_add`` plus impl_a, impl_b, impl_even for this test."""
24 +
25 + @vllm.ir.register_op(allow_inplace=True)
26 + def _custom_add(x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
27 + return x + y
28 +
29 + @_custom_add.register_impl("impl_a")
30 + def impl_a(x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
31 + return x + y + 10
32 +
33 + @_custom_add.register_impl("impl_b", inplace=True)
34 + def impl_b(x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
35 + """Computes x+y+20"""
36 + x.add_(y)
37 + x.add_(20)
38 + return x
39 +
40 + @_custom_add.register_impl(
41 + "impl_even", supports_args=lambda x, y: x.size(1) % 2 == 0
42 + )
43 + def impl_even(x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
44 + return x + y + 50
45
46 + return _custom_add
47
29 -def test_registration_overloads():
48 +
49 +def test_registration_overloads(fake_vllm_ir):
50 assert all(
51 n not in IrOp.registry for n in ["_custom_sub", "_custom_mul", "_custom_div"]
52 )
@@ -63,7 +83,7 @@ def test_registration_overloads():
83 return x * y - 100
84
85
66 -def test_no_kw_only_args():
86 +def test_no_kw_only_args(fake_vllm_ir):
87 # kw-only args not supported
88 with pytest.raises(ValueError, match="keyword-only arguments"):
89
@@ -78,18 +98,23 @@ def test_no_kw_only_args():
98
99 class TestIrOpCustomAdd:
100 # Registration invariants
81 - def test_decorated_object(self):
101 + def test_decorated_object(self, custom_add_op):
102 """Make sure that referring directly to an op is correct"""
103 + _custom_add = custom_add_op
104 assert isinstance(_custom_add, IrOp)
105 assert "_custom_add" in IrOp.registry
106 assert _custom_add is IrOp.registry["_custom_add"]
107
87 - def test_torch_op_is_registered(self):
88 - assert hasattr(torch.ops.vllm_ir, "_custom_add")
89 - assert callable(torch.ops.vllm_ir._custom_add.default)
108 + def test_torch_op_is_registered(self, custom_add_op):
109 + _custom_add = custom_add_op
110 + torch_ops = getattr(torch.ops, vllm.ir.op.vllm_ir_torch_lib.ns)
111 + assert hasattr(torch_ops, "_custom_add")
112 + assert callable(torch_ops._custom_add.default)
113 + assert _custom_add.torch_op is torch_ops._custom_add.default
114
115 # Semantic correctness
92 - def test_semantics_match_native(self):
116 + def test_semantics_match_native(self, custom_add_op):
117 + _custom_add = custom_add_op
118 x = torch.randn(4, 5)
119 y = torch.randn(4, 5)
120
@@ -103,7 +128,9 @@ class TestIrOpCustomAdd:
128 # Implementation registration
129 # -------------------------
130
106 - def test_register_impl_is_non_intrusive(self):
131 + def test_register_impl_is_non_intrusive(self, custom_add_op):
132 + _custom_add = custom_add_op
133 +
134 @_custom_add.register_impl("dummy_provider")
135 def dummy_impl(x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
136 return x + y + 123
@@ -117,7 +144,8 @@ class TestIrOpCustomAdd:
144 # Native semantics must still hold
145 torch.testing.assert_close(_custom_add(x, y), x + y)
146
120 - def test_schema_contains_tensor_signature(self):
147 + def test_schema_contains_tensor_signature(self, custom_add_op):
148 + _custom_add = custom_add_op
149 schema = _custom_add._schema_str
150
151 assert "Tensor" in schema
@@ -131,10 +159,19 @@ class TestIrOpCustomAdd:
159 @pytest.mark.parametrize("symbolic_trace", [True, False])
160 @pytest.mark.parametrize("overload", ["default", "maybe_inplace"])
161 def test_trace_sees_single_custom_op(
134 - self, symbolic_trace: bool, enable_torch_wrap: bool, overload: str
162 + self,
163 + custom_add_op,
164 + symbolic_trace: bool,
165 + enable_torch_wrap: bool,
166 + overload: str,
167 ):
168 + _custom_add = custom_add_op
169 op_fn = _custom_add if overload == "default" else _custom_add.maybe_inplace
137 - torch_op = getattr(torch.ops.vllm_ir._custom_add, overload)
170 + torch_op = (
171 + _custom_add.torch_op
172 + if overload == "default"
173 + else _custom_add.maybe_inplace.torch_op
174 + )
175
176 def fn(x, y):
177 return op_fn(x, y)
@@ -175,30 +212,13 @@ class TestIrOpCustomAdd:
212 assert len(ir_nodes) == 1, gm.code
213
214
178 -@_custom_add.register_impl("impl_a")
179 -def impl_a(x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
180 - return x + y + 10
181 -
182 -
183 -@_custom_add.register_impl("impl_b", inplace=True)
184 -def impl_b(x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
185 - """Computes x+y+20"""
186 - x.add_(y)
187 - x.add_(20)
188 - return x
189 -
190 -
191 -@_custom_add.register_impl("impl_even", supports_args=lambda x, y: x.size(1) % 2 == 0)
192 -def impl_even(x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
193 - return x + y + 50
194 -
195 -
215 class TestIrOpImplDispatch:
197 - def test_register_impl(self):
216 + def test_register_impl(self, custom_add_op):
217 + _custom_add = custom_add_op
218 assert "impl_a" in _custom_add.impls
219 impl = _custom_add.impls["impl_a"]
220
201 - assert impl is impl_a
221 + assert impl is _custom_add.impls["impl_a"]
222 assert impl.op is _custom_add
223 assert impl.provider == "impl_a"
224 assert callable(impl.impl_fn)
@@ -211,14 +231,15 @@ class TestIrOpImplDispatch:
231 return x + y + 30
232
233 # Check the original impl is still intact
214 - assert _custom_add.impls["impl_a"] is impl_a
234 + assert _custom_add.impls["impl_a"] is impl
235
236 # Check support all args
217 - assert impl_a.supports_all_args
218 - assert impl_b.supports_all_args
219 - assert not impl_even.supports_all_args
237 + assert _custom_add.impls["impl_a"].supports_all_args
238 + assert _custom_add.impls["impl_b"].supports_all_args
239 + assert not _custom_add.impls["impl_even"].supports_all_args
240
221 - def test_reserved_provider_rejected(self):
241 + def test_reserved_provider_rejected(self, custom_add_op):
242 + _custom_add = custom_add_op
243 for provider in RESERVED_PROVIDERS:
244 with pytest.raises(AssertionError):
245
@@ -226,7 +247,8 @@ class TestIrOpImplDispatch:
247 def bad_impl(x, y):
248 return x + y
249
229 - def test_set_priority_scoped(self):
250 + def test_set_priority_scoped(self, custom_add_op):
251 + _custom_add = custom_add_op
252 assert _custom_add.get_priority() == []
253
254 with _custom_add.set_priority(["impl_even", "impl_b"]):
@@ -251,20 +273,25 @@ class TestIrOpImplDispatch:
273 assert _custom_add.get_priority() == []
274
275 @pytest.mark.parametrize("overload", ["default", "maybe_inplace"])
254 - def test_dispatch_priority_order(self, overload: str):
276 + def test_dispatch_priority_order(self, custom_add_op, overload: str):
277 + _custom_add = custom_add_op
278 op_fn = _custom_add if overload == "default" else _custom_add.maybe_inplace
256 - torch_op = getattr(torch.ops.vllm_ir._custom_add, overload)
279 + torch_op = (
280 + _custom_add.torch_op
281 + if overload == "default"
282 + else _custom_add.maybe_inplace.torch_op
283 + )
284
285 x = torch.tensor(1, dtype=torch.int32)
286 y = torch.tensor(2, dtype=torch.int32)
287
288 with _custom_add.set_priority(["impl_b", "impl_a"]):
262 - assert _custom_add.dispatch(x, y) is impl_b
289 + assert _custom_add.dispatch(x, y) is _custom_add.impls["impl_b"]
290 out1 = op_fn(x.clone(), y)
291 out2 = torch_op(x.clone(), y)
292
293 with _custom_add.set_priority(["impl_a"]):
267 - assert _custom_add.dispatch(x, y) is impl_a
294 + assert _custom_add.dispatch(x, y) is _custom_add.impls["impl_a"]
295 out3 = op_fn(x.clone(), y)
296 out4 = torch_op(x.clone(), y)
297
@@ -275,7 +302,9 @@ class TestIrOpImplDispatch:
302 assert out3.item() == 1 + 2 + 10
303 assert out4.item() == 1 + 2 + 10
304
278 - def test_unsupported_impl_filtered(self):
305 + def test_unsupported_impl_filtered(self, custom_add_op):
306 + _custom_add = custom_add_op
307 +
308 @_custom_add.register_impl("impl_unsupported", supported=False)
309 def impl_unsupported(x: torch.Tensor, y: torch.Tensor) -> torch.Tensor:
310 return x + y + 999
@@ -291,8 +320,9 @@ class TestIrOpImplDispatch:
320 assert out.item() == 1 + 2 + 10
321
322 def test_supports_args_runtime_dispatch_and_warning(
294 - self, caplog_vllm: pytest.LogCaptureFixture
323 + self, custom_add_op, caplog_vllm: pytest.LogCaptureFixture
324 ):
325 + _custom_add = custom_add_op
326 x1 = torch.ones((2, 2), dtype=torch.int32)
327 y1 = torch.full((2, 2), 2, dtype=torch.int32)
328
@@ -312,7 +342,7 @@ class TestIrOpImplDispatch:
342
343 # Check dispatching
344 assert _custom_add.get_priority() == ["impl_even", "native"]
315 - assert _custom_add.dispatch(x1, y1) is impl_even
345 + assert _custom_add.dispatch(x1, y1) is _custom_add.impls["impl_even"]
346 assert _custom_add.dispatch(x2, y2) is _custom_add.impls["native"]
347
348 out1 = _custom_add(x1, y1) # size(1) == 2 → impl_even
@@ -324,8 +354,12 @@ class TestIrOpImplDispatch:
354 assert torch.all(out2 == 1 + 2)
355
356 def test_default_priority(
327 - self, caplog_vllm: pytest.LogCaptureFixture, disable_log_dedup
357 + self,
358 + custom_add_op,
359 + caplog_vllm: pytest.LogCaptureFixture,
360 + disable_log_dedup,
361 ):
362 + _custom_add = custom_add_op
363 # Make sure logs are not deduplicated to properly test the warning
364 x = torch.tensor([3], dtype=torch.int32)
365 y = torch.tensor([4], dtype=torch.int32)
@@ -347,15 +381,23 @@ class TestIrOpImplDispatch:
381 assert "priority not set" in message
382
383
350 -@vllm.ir.register_op
351 -def _custom_mm(
352 - x: torch.Tensor, y: torch.Tensor, bias: torch.Tensor | None = None
353 -) -> torch.Tensor:
354 - tmp = x @ y
355 - return tmp if bias is None else tmp + bias
384 +@pytest.fixture
385 +def custom_mm_op(fake_vllm_ir):
386 + """Fixture that registers ``_custom_mm`` (isolated by ``fake_vllm_ir``)."""
387
388 + @vllm.ir.register_op
389 + def _custom_mm(
390 + x: torch.Tensor, y: torch.Tensor, bias: torch.Tensor | None = None
391 + ) -> torch.Tensor:
392 + tmp = x @ y
393 + return tmp if bias is None else tmp + bias
394 +
395 + return _custom_mm
396 +
397 +
398 +def test_default_args(custom_mm_op):
399 + _custom_mm = custom_mm_op
400
358 -def test_default_args():
401 # Test that default args are properly applied when dispatching and calling
402 @_custom_mm.register_impl("impl_mm", supports_args=lambda x, y, bias=None: True)
403 def impl_mm(
@@ -373,7 +415,8 @@ def test_default_args():
415 assert _custom_mm.dispatch(x1, x2) is impl_mm
416
417
376 -def test_bad_impl_registrations():
418 +def test_bad_impl_registrations(custom_mm_op):
419 + _custom_mm = custom_mm_op
420 # Check bad schema
421 with pytest.raises(ValueError, match="does not match native schema"):
422
@@ -446,7 +489,8 @@ def test_bad_impl_registrations():
489 ) -> torch.Tensor:
490 return x @ y + 40
491
449 - assert set(_custom_mm.impls.keys()) == {"impl_mm", "native"}
492 + # With fixture, each test gets a fresh op with only "native" impl
493 + assert set(_custom_mm.impls.keys()) == {"native"}
494
495
496 IMPL_OOT_SRC = """
@@ -460,14 +504,14 @@ def impl_mm_oot(
504 """
505
506
463 -def load_custom_mm_module(file_path: Path):
507 +def load_custom_mm_module(file_path: Path, custom_mm_op):
508 spec = importlib.util.spec_from_file_location("_custom_mm_oot", file_path)
509 assert spec is not None
510 module = importlib.util.module_from_spec(spec)
511
512 # Inject the variable into the module's global namespace
513 # This allows the @_custom_mm.register_impl decorator to work
470 - module._custom_mm = _custom_mm # type: ignore[attr-defined]
514 + module._custom_mm = custom_mm_op # type: ignore[attr-defined]
515
516 # Execute the file; this triggers the decorator
517 assert spec.loader is not None
@@ -475,12 +519,13 @@ def load_custom_mm_module(file_path: Path):
519 return module
520
521
478 -def test_uuid_and_oot(tmp_path: Path):
522 +def test_uuid_and_oot(custom_mm_op, tmp_path: Path):
523 + _custom_mm = custom_mm_op
524 file_path = tmp_path / "_custom_mm_oot.py"
525 file_path.write_text(IMPL_OOT_SRC)
526
527 assert "impl_mm_oot" not in _custom_mm.impls
483 - _ = load_custom_mm_module(file_path)
528 + _ = load_custom_mm_module(file_path, _custom_mm)
529 assert "impl_mm_oot" in _custom_mm.impls
530
531 uuid = _custom_mm.impls["impl_mm_oot"].uuid()
@@ -489,7 +534,7 @@ def test_uuid_and_oot(tmp_path: Path):
534 # Replace file source
535 file_path.write_text(IMPL_OOT_SRC + " # added file source")
536 assert "impl_mm_oot" not in _custom_mm.impls
492 - _ = load_custom_mm_module(file_path)
537 + _ = load_custom_mm_module(file_path, _custom_mm)
538 assert "impl_mm_oot" in _custom_mm.impls
539
540 uuid1 = _custom_mm.impls["impl_mm_oot"].uuid()
@@ -499,7 +544,7 @@ def test_uuid_and_oot(tmp_path: Path):
544 # Back to original
545 file_path.write_text(IMPL_OOT_SRC)
546 assert "impl_mm_oot" not in _custom_mm.impls
502 - _ = load_custom_mm_module(file_path)
547 + _ = load_custom_mm_module(file_path, _custom_mm)
548 assert "impl_mm_oot" in _custom_mm.impls
549
550 uuid2 = _custom_mm.impls["impl_mm_oot"].uuid()
@@ -571,3 +616,106 @@ class TestTolerance:
616 op = IrOp("_tol_test_unknown", _test_native)
617 with pytest.raises(ValueError, match="No tolerance defined"):
618 op.get_tolerance(torch.complex64)
619 +
620 +
621 +def test_naming_validation(fake_vllm_ir):
622 + """Test that op and provider names are validated ([a-z_][a-z_0-9]*)."""
623 +
624 + # Valid op and provider names
625 + @vllm.ir.register_op
626 + def _valid_name_123(x: torch.Tensor) -> torch.Tensor:
627 + return x
628 +
629 + @_valid_name_123.register_impl("valid_provider_123")
630 + def valid_impl(x: torch.Tensor) -> torch.Tensor:
631 + return x + 1
632 +
633 + # Invalid op names should fail
634 + with pytest.raises(ValueError, match="name.*invalid"):
635 +
636 + @vllm.ir.register_op
637 + def InvalidName(x: torch.Tensor) -> torch.Tensor:
638 + return x
639 +
640 + with pytest.raises(ValueError, match="name.*invalid"):
641 +
642 + @vllm.ir.register_op(name="123invalid")
643 + def some_func(x: torch.Tensor) -> torch.Tensor:
644 + return x
645 +
646 + # Invalid provider names should fail
647 + with pytest.raises(ValueError, match="name.*invalid"):
648 +
649 + @_valid_name_123.register_impl("Invalid-Provider")
650 + def invalid_impl(x: torch.Tensor) -> torch.Tensor:
651 + return x + 1
652 +
653 +
654 +def test_registration_stack_traces(fake_vllm_ir):
655 + """Test that stack traces are captured for ops and impls."""
656 +
657 + @vllm.ir.register_op
658 + def _test_stack(x: torch.Tensor) -> torch.Tensor:
659 + return x
660 +
661 + @_test_stack.register_impl("test_provider")
662 + def test_impl(x: torch.Tensor) -> torch.Tensor:
663 + return x + 1
664 +
665 + # Verify op stack trace
666 + assert hasattr(_test_stack, "_registration_stack")
667 + assert len(_test_stack._registration_stack) > 0
668 + op_stack_str = "".join(_test_stack._registration_stack)
669 + assert "test_op.py" in op_stack_str
670 + # Last frame should be the decorator in user code, not internal decorator logic
671 + assert "@vllm.ir.register_op" in _test_stack._registration_stack[-1]
672 + assert "return decorator(f)" not in op_stack_str
673 +
674 + # Verify impl stack trace
675 + impl = _test_stack.impls["test_provider"]
676 + assert hasattr(impl, "_registration_stack")
677 + assert len(impl._registration_stack) > 0
678 + impl_stack_str = "".join(impl._registration_stack)
679 + assert "test_op.py" in impl_stack_str
680 + # Last frame should be the decorator in user code
681 + assert '@_test_stack.register_impl("test_provider")' in impl._registration_stack[-1]
682 +
683 +
684 +def test_op_repr_uses_docstring(fake_vllm_ir):
685 + """Test that __str__ uses the function's docstring and __repr__ is simple."""
686 +
687 + @vllm.ir.register_op
688 + def _test_repr_with_doc(x: torch.Tensor) -> torch.Tensor:
689 + """First line of docstring.
690 +
691 + Additional details here.
692 + """
693 + return x
694 +
695 + @vllm.ir.register_op
696 + def _test_repr_no_doc(x: torch.Tensor) -> torch.Tensor:
697 + return x
698 +
699 + # __str__ with docstring: uses first line only
700 + str_with = str(_test_repr_with_doc)
701 + assert "IrOp('_test_repr_with_doc')" in str_with
702 + assert "First line of docstring." in str_with
703 + assert "Additional details" not in str_with
704 +
705 + # __str__ without docstring: simple format
706 + assert str(_test_repr_no_doc) == "IrOp('_test_repr_no_doc')"
707 +
708 + # __repr__ should be simple for both
709 + assert repr(_test_repr_with_doc) == "IrOp('_test_repr_with_doc')"
710 + assert repr(_test_repr_no_doc) == "IrOp('_test_repr_no_doc')"
711 +
712 +
713 +def test_vllm_ir_fixture(fake_vllm_ir):
714 + """Test that the fake_vllm_ir fixture provides test isolation."""
715 +
716 + @vllm.ir.register_op
717 + def _test_fixture(x: torch.Tensor) -> torch.Tensor:
718 + return x
719 +
720 + assert "_test_fixture" in IrOp.registry
721 + # Fixture will automatically clean up after test
vllm/ir/op.py
+73 -23
@@ -2,10 +2,12 @@
2 # SPDX-FileCopyrightText: Copyright contributors to the vLLM project
3 import contextlib
4 import inspect
5 +import traceback
6 from collections.abc import Callable
7 from pathlib import Path
8 from typing import Any, ClassVar, Literal, overload
9
10 +import regex as re
11 import torch
12 from torch.library import Library, infer_schema
13
@@ -16,13 +18,35 @@ from vllm.logging_utils import lazy, tensors_str_no_data
18
19 InputGenerator = Callable[..., tuple[Any, ...]]
20
19 -vllm_ir_lib = Library("vllm_ir", "FRAGMENT")
21 +vllm_ir_torch_lib = Library("vllm_ir", "FRAGMENT") # IR op lib; monkeypatch in tests.
22
23 logger = init_logger(__name__)
24
25 +
26 +def _torch_ops_subtree(lib: Any) -> Any:
27 + """``torch.ops`` subtree for ``lib.ns``; fall back if doc mocks replace ``ns``."""
28 + ns = getattr(lib, "ns", None)
29 + if isinstance(ns, str):
30 + return getattr(torch.ops, ns)
31 + return torch.ops.vllm_ir
32 +
33 +
34 +_NAME_PATTERN = re.compile(r"^[a-z_][a-z_0-9]*$")
35 +
36 RESERVED_PROVIDERS = ["native", "unfused"]
37 """Providers that are reserved and cannot be used for custom implementations."""
38
39 +
40 +def _validate_name(name: str, entity_type: str) -> None:
41 + """Validate that a name matches the required pattern `[a-z_][a-z_0-9]*`."""
42 + if not _NAME_PATTERN.match(name):
43 + raise ValueError(
44 + f"{entity_type} name '{name}' is invalid. "
45 + f"Names must start with a letter or underscore, "
46 + f"followed by lowercase letters, underscores, or digits only."
47 + )
48 +
49 +
50 _ENABLE_TORCH_WRAP: bool = True
51 """Global override flag to control torch op layer wrapping."""
52
@@ -100,11 +124,14 @@ def register_op(
124
125 def decorator(_f: Callable):
126 op_name: str = _f.__name__ if name is None else name
103 - assert op_name not in IrOp.registry
127 + _validate_name(op_name, "Op")
128 + assert op_name not in IrOp.registry, f"Op '{op_name}' is already registered."
129 + # Slice out the decorator function frames from the stack
130 + stack = traceback.format_stack()[:-2]
131 if allow_inplace:
105 - op: IrOp = IrOpInplace(op_name, _f, activations)
132 + op: IrOp = IrOpInplace(op_name, _f, activations, stack)
133 else:
107 - op = IrOp(op_name, _f, activations)
134 + op = IrOp(op_name, _f, activations, stack)
135 IrOp.registry[op_name] = op
136 return op
137
@@ -126,6 +153,7 @@ class IrOp:
153 name: str,
154 native_impl: Callable,
155 activations: list[str] | None = None,
156 + registration_stack: list[str] | None = None,
157 ):
158 self._py_signature = inspect.signature(native_impl)
159 if any(
@@ -146,6 +174,8 @@ class IrOp:
174 ]
175
176 self.name = name
177 + self._docstring = inspect.getdoc(native_impl) or ""
178 + self._registration_stack = registration_stack or []
179 self.impls: dict[str, IrOpImpl] = {}
180 self.activations = activations
181 self.activation_indices = [
@@ -166,22 +196,23 @@ class IrOp:
196 # always supported
197 supported=True,
198 supports_args=None,
199 + registration_stack=self._registration_stack,
200 )
201
202 # By default, fake routes directly to native,
203 # can be overridden by register_fake
204 self._fake_fn = native_impl
205
175 - # torch registration
176 - vllm_ir_lib.define(self.name + self._schema_str)
206 + # torch registration (resolve ``torch.ops`` subtree from ``lib.ns``)
207 + lib = vllm_ir_torch_lib
208 + lib.define(self.name + self._schema_str)
209 # CompositeExplicitAutograd is not decomposed
210 # by ATen IR normalization in AOTAutograd
179 - vllm_ir_lib.impl(
180 - self.name, self._inner_call, dispatch_key="CompositeExplicitAutograd"
181 - )
182 - vllm_ir_lib._register_fake(self.name, self._fake_call)
183 - assert hasattr(torch.ops.vllm_ir, name)
184 - self.torch_op: torch._ops.OpOverload = getattr(torch.ops.vllm_ir, name).default
211 + lib.impl(self.name, self._inner_call, dispatch_key="CompositeExplicitAutograd")
212 + lib._register_fake(self.name, self._fake_call)
213 + torch_ops = _torch_ops_subtree(lib)
214 + assert hasattr(torch_ops, name)
215 + self.torch_op: torch._ops.OpOverload = getattr(torch_ops, name).default
216
217 def register_fake(self, fn: Callable) -> Callable:
218 """
@@ -237,9 +268,12 @@ class IrOp:
268 assert provider not in RESERVED_PROVIDERS, (
269 f"Provider name {provider} is reserved."
270 )
271 + _validate_name(provider, "Provider")
272
273 def _register_impl(f: Callable):
242 - impl = IrOpImpl(self, provider, f, supported, supports_args, inplace)
274 + # Slice out the decorator function from the stack
275 + stack = traceback.format_stack()[:-1]
276 + impl = IrOpImpl(self, provider, f, supported, supports_args, inplace, stack)
277 self.impls[provider] = impl
278
279 if self.get_priority():
@@ -323,6 +357,17 @@ class IrOp:
357
358 return self.torch_op(*args, **kwargs)
359
360 + def __repr__(self) -> str:
361 + """Return unambiguous string representation."""
362 + return f"IrOp('{self.name}')"
363 +
364 + def __str__(self) -> str:
365 + """Return human-readable string representation using docstring."""
366 + if not self._docstring:
367 + return f"IrOp('{self.name}')"
368 + first_line = self._docstring.split("\n")[0].strip()
369 + return f"IrOp('{self.name}') - {first_line}"
370 +
371 def get_priority(self) -> list[str]:
372 """Get the current dispatch priority for implementations for this op."""
373 return [p.provider for p in self._priority_impls]
@@ -419,8 +464,9 @@ class IrOpInplace(IrOp):
464 name: str,
465 native_impl: Callable,
466 activations: list[str] | None = None,
467 + registration_stack: list[str] | None = None,
468 ):
423 - super().__init__(name, native_impl, activations)
469 + super().__init__(name, native_impl, activations, registration_stack)
470
471 # Create the inplace overload
472 self.maybe_inplace = IrOpInplaceOverload(self)
@@ -445,16 +491,16 @@ class IrOpInplaceOverload:
491 op.impls["native"].impl_fn, mutates_args=op.activations
492 )
493
448 - # torch registration
449 - vllm_ir_lib.define(self.name + self._schema_str)
450 - vllm_ir_lib.impl(
451 - self.name, self._inner_call, dispatch_key="CompositeExplicitAutograd"
452 - )
494 + # torch registration (resolve ``torch.ops`` subtree from ``lib.ns``)
495 + lib = vllm_ir_torch_lib
496 + lib.define(self.name + self._schema_str)
497 + lib.impl(self.name, self._inner_call, dispatch_key="CompositeExplicitAutograd")
498 # fake goes to default overload for now
454 - vllm_ir_lib._register_fake(self.name, self.op._fake_call)
499 + lib._register_fake(self.name, self.op._fake_call)
500
456 - assert hasattr(getattr(torch.ops.vllm_ir, self.op.name), "maybe_inplace")
457 - self.torch_op = getattr(torch.ops.vllm_ir, self.op.name).maybe_inplace
501 + torch_ops = _torch_ops_subtree(lib)
502 + assert hasattr(getattr(torch_ops, self.op.name), "maybe_inplace")
503 + self.torch_op = getattr(torch_ops, self.op.name).maybe_inplace
504
505 def __call__(self, *args, **kwargs) -> Any:
506 if not _ENABLE_TORCH_WRAP:
@@ -477,12 +523,15 @@ class IrOpImpl:
523 supported: bool,
524 supports_args: Callable[..., bool] | None,
525 inplace: bool = False,
526 + registration_stack: list[str] | None = None,
527 ):
528 assert provider not in op.impls, (
529 f"Implementation for provider {provider} already registered."
530 )
531 # Native also uses this path, so we allow it here.
485 - assert provider == "native" or provider not in RESERVED_PROVIDERS
532 + assert provider == "native" or provider not in RESERVED_PROVIDERS, (
533 + f"Provider name {provider} is reserved."
534 + )
535
536 # Enforce the exact same schema as the native implementation.
537 # This takes care of names, types, and defaults.
@@ -547,6 +596,7 @@ class IrOpImpl:
596 self.supported = supported
597 self._supports_args = supports_args
598 self.inplace = inplace
599 + self._registration_stack = registration_stack or []
600
601 @property
602 def supports_all_args(self) -> bool: