Skip to content

Commit b3f9ffc

Browse files
committed
2 parents bef4429 + dbfe3be commit b3f9ffc

16 files changed

Lines changed: 332 additions & 218 deletions

File tree

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ In theory, vllm-plugin-FL can support all models available in vLLM, as long as n
5555
# or editble install
5656
pip install --no-build-isolation -e .
5757
```
58-
58+
5959
For CUDA-like devices, including CUDA and HIP/ROCm environments that use
6060
PyTorch's CUDA dispatch key, build the plugin native extension by setting
6161
`VLLM_VENDOR=cuda` during installation:

tests/unit_tests/ops/test_activation.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,24 @@ class TestSiluAndMulFL:
1414
"""Test SiluAndMulFL class behavior."""
1515

1616
@pytest.fixture
17-
def mock_call_op(self):
18-
with patch("vllm_fl.ops.activation.call_op") as mock:
17+
def mock_cached_op(self):
18+
with patch("vllm_fl.ops.activation._silu_and_mul") as mock:
1919
yield mock
2020

2121
@pytest.fixture
2222
def mock_parent_init(self):
2323
with patch("vllm_fl.ops.activation.SiluAndMul.__init__", return_value=None):
2424
yield
2525

26-
def test_forward_oot_dispatches_correctly(self, mock_parent_init, mock_call_op):
26+
def test_forward_oot_dispatches_correctly(self, mock_parent_init, mock_cached_op):
2727
"""Test forward_oot calls dispatch system with correct op name and input."""
2828
from vllm_fl.ops.activation import SiluAndMulFL
2929

30-
mock_call_op.return_value = torch.randn(2, 4)
30+
mock_cached_op.return_value = torch.randn(2, 4)
3131
layer = SiluAndMulFL()
3232
x = torch.randn(2, 8)
3333

3434
result = layer.forward_oot(x)
3535

36-
mock_call_op.assert_called_once_with("silu_and_mul", layer, x)
36+
mock_cached_op.assert_called_once_with(layer, x)
3737
assert result.shape == (2, 4)

tests/unit_tests/ops/test_layernorm.py

Lines changed: 16 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ def __init__(self):
1919
set_current_vllm_config(VllmConfig())
2020

2121
@pytest.fixture
22-
def mock_call_op(self):
23-
with patch("vllm_fl.ops.layernorm.call_op") as mock:
22+
def mock_cached_op(self):
23+
with patch("vllm_fl.ops.layernorm._rms_norm") as mock:
2424
yield mock
2525

2626
def test_init_creates_weight_parameter(self):
@@ -34,31 +34,30 @@ def test_init_creates_weight_parameter(self):
3434
assert layer.variance_epsilon == eps
3535
assert layer.weight.shape == (hidden_size,)
3636

37-
def test_forward_oot_dispatches_without_residual(self, mock_call_op):
37+
def test_forward_oot_dispatches_without_residual(self, mock_cached_op):
3838
"""Test forward_oot calls dispatch system correctly without residual."""
3939
from vllm_fl.ops.layernorm import RMSNormFL
4040

4141
hidden_size = 128
42-
mock_call_op.return_value = torch.randn(2, hidden_size)
42+
mock_cached_op.return_value = torch.randn(2, hidden_size)
4343

4444
layer = RMSNormFL(hidden_size=hidden_size)
4545
x = torch.randn(2, hidden_size)
4646

4747
layer.forward_oot(x)
4848

49-
mock_call_op.assert_called_once()
50-
call_args = mock_call_op.call_args
51-
assert call_args[0][0] == "rms_norm"
52-
assert call_args[0][1] is layer # self
53-
assert torch.equal(call_args[0][2], x)
54-
assert call_args[0][3] is None # residual should be None
49+
mock_cached_op.assert_called_once()
50+
call_args = mock_cached_op.call_args
51+
assert call_args[0][0] is layer # self
52+
assert torch.equal(call_args[0][1], x)
53+
assert call_args[0][2] is None # residual should be None
5554

56-
def test_forward_oot_dispatches_with_residual(self, mock_call_op):
55+
def test_forward_oot_dispatches_with_residual(self, mock_cached_op):
5756
"""Test forward_oot passes residual to dispatch system."""
5857
from vllm_fl.ops.layernorm import RMSNormFL
5958

6059
hidden_size = 128
61-
mock_call_op.return_value = (
60+
mock_cached_op.return_value = (
6261
torch.randn(2, hidden_size),
6362
torch.randn(2, hidden_size),
6463
)
@@ -69,9 +68,8 @@ def test_forward_oot_dispatches_with_residual(self, mock_call_op):
6968

7069
layer.forward_oot(x, residual=residual)
7170

72-
mock_call_op.assert_called_once()
73-
call_args = mock_call_op.call_args
74-
assert call_args[0][0] == "rms_norm"
75-
assert call_args[0][1] is layer # self
76-
assert torch.equal(call_args[0][2], x)
77-
assert torch.equal(call_args[0][3], residual)
71+
mock_cached_op.assert_called_once()
72+
call_args = mock_cached_op.call_args
73+
assert call_args[0][0] is layer # self
74+
assert torch.equal(call_args[0][1], x)
75+
assert torch.equal(call_args[0][2], residual)

tests/unit_tests/ops/test_rotary_embedding.py

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,8 @@ class TestRotaryEmbeddingFL:
1414
"""Test RotaryEmbeddingFL class behavior."""
1515

1616
@pytest.fixture
17-
def mock_call_op(self):
18-
with patch("vllm_fl.ops.rotary_embedding.call_op") as mock:
17+
def mock_cached_op(self):
18+
with patch("vllm_fl.ops.rotary_embedding._rotary_embedding") as mock:
1919
yield mock
2020

2121
@pytest.fixture
@@ -25,7 +25,7 @@ def mock_parent_init(self):
2525
):
2626
yield
2727

28-
def test_forward_oot_dispatches_correctly(self, mock_parent_init, mock_call_op):
28+
def test_forward_oot_dispatches_correctly(self, mock_parent_init, mock_cached_op):
2929
"""Test forward_oot calls dispatch system with correct arguments."""
3030
from vllm_fl.ops.rotary_embedding import RotaryEmbeddingFL
3131

@@ -44,14 +44,17 @@ def test_forward_oot_dispatches_correctly(self, mock_parent_init, mock_call_op):
4444
layer.is_neox_style = True
4545
layer.cos_sin_cache = torch.randn(2048, 64)
4646

47-
mock_call_op.return_value = (torch.randn(4, 8, 32), torch.randn(4, 8, 32))
47+
mock_cached_op.return_value = (
48+
torch.randn(4, 8, 32),
49+
torch.randn(4, 8, 32),
50+
)
4851

4952
positions = torch.tensor([0, 1, 2, 3])
5053
query = torch.randn(4, 8, 64)
5154
key = torch.randn(4, 8, 64)
5255

5356
layer.forward_oot(positions, query, key)
5457

55-
mock_call_op.assert_called_once()
56-
call_args = mock_call_op.call_args
57-
assert call_args[0][0] == "rotary_embedding"
58+
mock_cached_op.assert_called_once()
59+
call_args = mock_cached_op.call_args
60+
assert call_args[0][0] is layer

vllm_fl/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,9 @@ def register_router():
128128
# fused_moe import chain triggers cutlass_scaled_mm_supports_fp8 on MUSA
129129
if current_platform.device_type == "musa":
130130
return
131+
from vllm_fl.utils import is_oot_enabled
132+
if not is_oot_enabled():
133+
return
131134
from vllm_fl.ops.fused_moe.router import replace_router_with_fl
132135
replace_router_with_fl()
133136

vllm_fl/dispatch/__init__.py

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,12 +76,15 @@
7676
- reference
7777
"""
7878

79+
import os
80+
7981
from .types import OpImpl, BackendImplKind, BackendPriority, match_token
8082
from .registry import OpRegistry, OpRegistrySnapshot
8183
from .policy import (
8284
SelectionPolicy,
8385
PolicyManager,
8486
get_policy,
87+
get_policy_epoch,
8588
set_global_policy,
8689
reset_global_policy,
8790
policy_context,
@@ -108,6 +111,7 @@
108111
enable_io_dump,
109112
disable_io_dump,
110113
io_dump_step,
114+
is_dump_enabled,
111115
)
112116
from .io_common import list_model_layers, register_tensor_stat, tensor_stats
113117

@@ -139,6 +143,94 @@ def resolve_op(op_name: str):
139143
return get_default_manager().resolve(op_name)
140144

141145

146+
# Fast-path opt-out: set VLLM_FL_OP_FAST_PATH=0 to disable per-op fn caching
147+
# in hot OOT layers and route every call back through OpManager.call.
148+
_OP_FAST_PATH_ENABLED = os.environ.get("VLLM_FL_OP_FAST_PATH", "1") == "1"
149+
150+
151+
class CachedOp:
152+
"""Resolve an op once at the call site and refresh on policy changes.
153+
154+
OpManager.call preserves fallback and IO-dump hooks, but it also pays the
155+
manager/fallback path on every invocation. Hot layer paths can use CachedOp
156+
to call the resolved implementation directly after the first lookup.
157+
158+
The cache is invalidated by both OpManager.policy_epoch and
159+
PolicyManager.policy_epoch. The latter matters for policy_context() and
160+
set_global_policy(), which can change the effective backend without
161+
touching the OpManager instance.
162+
163+
Cache refresh is best-effort under concurrent calls. If another thread
164+
changes policy at the same time, a call may observe the previous impl once
165+
before the next epoch check refreshes it.
166+
"""
167+
168+
__slots__ = (
169+
"_op_name",
170+
"_impl",
171+
"_use_manager_call",
172+
"_manager_id",
173+
"_manager_epoch",
174+
"_policy_epoch",
175+
)
176+
177+
def __init__(self, op_name: str) -> None:
178+
self._op_name = op_name
179+
self._impl = None
180+
self._use_manager_call = False
181+
self._manager_id = -1
182+
self._manager_epoch = -1
183+
self._policy_epoch = -1
184+
185+
def __call__(self, *args, **kwargs):
186+
mgr = get_default_manager()
187+
188+
if not _OP_FAST_PATH_ENABLED:
189+
return mgr.call(self._op_name, *args, **kwargs)
190+
191+
if is_dump_enabled():
192+
return mgr.call(self._op_name, *args, **kwargs)
193+
194+
manager_epoch = mgr.policy_epoch
195+
manager_id = id(mgr)
196+
policy_epoch = get_policy_epoch()
197+
if (
198+
self._manager_id != manager_id
199+
or self._manager_epoch != manager_epoch
200+
or self._policy_epoch != policy_epoch
201+
):
202+
self._impl = None
203+
self._use_manager_call = False
204+
205+
if self._use_manager_call:
206+
return mgr.call(self._op_name, *args, **kwargs)
207+
208+
impl = self._impl
209+
if (
210+
impl is None
211+
or self._manager_id != manager_id
212+
or self._manager_epoch != manager_epoch
213+
or self._policy_epoch != policy_epoch
214+
):
215+
impl = mgr._resolve_impl(self._op_name)
216+
mgr._record_first_use(self._op_name, impl)
217+
self._impl = impl
218+
# resolve() can initialize the manager and bump its epoch.
219+
self._manager_id = manager_id
220+
self._manager_epoch = mgr.policy_epoch
221+
self._policy_epoch = get_policy_epoch()
222+
223+
try:
224+
return impl.fn(*args, **kwargs)
225+
except Exception:
226+
self._impl = None
227+
if get_policy().strict:
228+
raise
229+
mgr._mark_failed_impl(self._op_name, impl.impl_id)
230+
self._use_manager_call = True
231+
return mgr.call(self._op_name, *args, **kwargs)
232+
233+
142234
__all__ = [
143235
# Types
144236
"OpImpl",
@@ -152,6 +244,7 @@ def resolve_op(op_name: str):
152244
"SelectionPolicy",
153245
"PolicyManager",
154246
"get_policy",
247+
"get_policy_epoch",
155248
"set_global_policy",
156249
"reset_global_policy",
157250
"policy_context",
@@ -188,4 +281,5 @@ def resolve_op(op_name: str):
188281
# Convenience functions
189282
"call_op",
190283
"resolve_op",
284+
"CachedOp",
191285
]

vllm_fl/dispatch/backends/vendor/metax/patches/gdn_linear_attn.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,4 +112,3 @@ def forward(
112112
core_attn_out = core_attn_out.reshape(z_shape_og)
113113
core_attn_out = rearrange(core_attn_out, "... h d -> ... (h d)")
114114
output[:num_tokens], _ = self.out_proj(core_attn_out)
115-

0 commit comments

Comments
 (0)