Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 10 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,16 @@ In theory, vllm-plugin-FL can support all models available in vLLM, as long as n
pip install --no-build-isolation -e .
```

### Runtime compatibility hooks

The plugin installs runtime compatibility hooks through vLLM's plugin entry
points without modifying the installed vLLM package. Model-specific config and
model registrations are loaded only for their corresponding architectures.

Operator adapters use the plugin dispatch manager, so backend selection,
fallback, per-op policy, operator-list recording, and I/O diagnostics continue
to follow the common FlagOS controls.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

provide a general readme instead model-level readme

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated. I replaced the model-specific section with a general Runtime compatibility hooks section describing plugin-owned config/model registration and OpManager-based operator adapters. Model-specific implementation details remain in the code and PR description.

4. (Optional) Install [FlagCX](https://github.com/flagos-ai/FlagCX/blob/main/docs/getting_started.md#build-and-installation)

4.1 Clone the repository:
Expand Down
24 changes: 24 additions & 0 deletions tests/unit_tests/dispatch/test_cuda_moe_sum.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import sys
from types import ModuleType

from vllm_fl.dispatch.backends.vendor.cuda.impl.fused_moe import moe_sum_cuda


def test_cuda_moe_sum_unwraps_dispatch_adapter(monkeypatch):
calls = []
custom_ops = ModuleType("vllm._custom_ops")

def native_moe_sum(inp, out):
calls.append(("native", inp, out))

def dispatch_adapter(inp, out):
calls.append(("dispatch", inp, out))

dispatch_adapter._vllm_fl_original = native_moe_sum
custom_ops.moe_sum = dispatch_adapter
monkeypatch.setitem(sys.modules, "vllm._custom_ops", custom_ops)

inp, out = object(), object()
moe_sum_cuda(inp, out)

assert calls == [("native", inp, out)]
56 changes: 56 additions & 0 deletions tests/unit_tests/ops/test_fused_moe_layer.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
# Copyright (c) 2026 BAAI. All rights reserved.

from unittest.mock import MagicMock, patch

from vllm_fl.ops.fused_moe import layer


def _runner_with_quant_method(quant_method):
runner = MagicMock()
runner._quant_method = quant_method
runner.moe_config = MagicMock()
return runner


def test_fused_moe_fl_replaces_unquantized_method():
quant_method = MagicMock(spec=layer.UnquantizedFusedMoEMethod)
runner = _runner_with_quant_method(quant_method)
replacement = MagicMock()

with (
patch.object(layer, "_OrigFusedMoE", return_value=runner),
patch.object(
layer,
"UnquantizedFusedMoEMethodFL",
return_value=replacement,
) as replacement_cls,
patch.object(layer, "replace_router_with_fl") as replace_router,
):
result = layer.FusedMoEFL(test_arg=True)

assert result is runner
replacement_cls.assert_called_once_with(runner.moe_config)
runner._replace_quant_method.assert_called_once_with(replacement)
replace_router.assert_called_once_with()


def test_fused_moe_fl_preserves_quantized_method():
quant_method = object()
runner = _runner_with_quant_method(quant_method)

with (
patch.object(layer, "_OrigFusedMoE", return_value=runner),
patch.object(layer, "UnquantizedFusedMoEMethodFL") as replacement_cls,
patch.object(layer, "replace_router_with_fl") as replace_router,
patch.object(layer.logger, "info_once") as info_once,
):
result = layer.FusedMoEFL()

assert result is runner
replacement_cls.assert_not_called()
runner._replace_quant_method.assert_not_called()
replace_router.assert_called_once_with()
info_once.assert_called_once_with(
"Preserving upstream quantized MoE method %s in FusedMoEFL.",
"object",
)
Empty file.
73 changes: 73 additions & 0 deletions tests/unit_tests/patches/test_moe_sum.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
from types import ModuleType

from vllm_fl.patches import moe_sum


class _FakeTensor:
def __init__(self, numel, hidden_stride=1):
self._numel = numel
self._hidden_stride = hidden_stride

def numel(self):
return self._numel

def stride(self, dim):
assert dim == -1
return self._hidden_stride


def test_moe_sum_patch_routes_nonempty_and_guards_empty(monkeypatch):
calls = []
ops = ModuleType("fake_vllm_custom_ops")
ops.moe_sum = lambda input, output: calls.append(("original", input, output))

monkeypatch.setattr(
moe_sum, "use_flaggems_op", lambda op_name: op_name == "moe_sum"
)
monkeypatch.setattr(
moe_sum,
"_dispatch_moe_sum",
lambda input, output: calls.append(("dispatch", input, output)),
)

assert moe_sum.patch_vllm_moe_sum(ops) is True
assert moe_sum.patch_vllm_moe_sum(ops) is False

nonempty_input = _FakeTensor(24)
nonempty_output = _FakeTensor(8)
ops.moe_sum(nonempty_input, nonempty_output)
ops.moe_sum(_FakeTensor(0), nonempty_output)

assert calls == [("dispatch", nonempty_input, nonempty_output)]
assert ops.moe_sum._vllm_fl_original is not ops.moe_sum


def test_moe_sum_patch_uses_stride_safe_fallback(monkeypatch):
calls = []
ops = ModuleType("fake_vllm_custom_ops")
ops.moe_sum = lambda input, output: None

monkeypatch.setattr(moe_sum, "use_flaggems_op", lambda op_name: True)
monkeypatch.setattr(
moe_sum,
"_torch_moe_sum",
lambda input, output: calls.append((input, output)),
)

assert moe_sum.patch_vllm_moe_sum(ops) is True
noncontiguous_input = _FakeTensor(24, hidden_stride=2)
output = _FakeTensor(8)
ops.moe_sum(noncontiguous_input, output)

assert calls == [(noncontiguous_input, output)]


def test_moe_sum_patch_respects_flaggems_disable(monkeypatch):
ops = ModuleType("fake_vllm_custom_ops")
original = lambda input, output: None
ops.moe_sum = original

monkeypatch.setattr(moe_sum, "use_flaggems_op", lambda op_name: False)

assert moe_sum.patch_vllm_moe_sum(ops) is False
assert ops.moe_sum is original
116 changes: 116 additions & 0 deletions tests/unit_tests/patches/test_qwen3_5_text.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
from types import SimpleNamespace

from vllm_fl.patches import qwen3_5_text as compat


def test_apply_registers_only_plugin_owned_lazy_models(monkeypatch):
from vllm.model_executor.models import (
config as model_config,
registry as model_registry,
)
from vllm.transformers_utils import config as transformers_config

registered = {}
fake_registry = SimpleNamespace(
register_model=lambda architecture, model: registered.__setitem__(
architecture, model
)
)
monkeypatch.setattr(transformers_config, "_CONFIG_REGISTRY", {})
monkeypatch.setattr(model_config, "MODELS_CONFIG_MAP", {})
monkeypatch.setattr(model_registry, "_TEXT_GENERATION_MODELS", {})
monkeypatch.setattr(model_registry, "_VLLM_MODELS", {})
monkeypatch.setattr(model_registry, "ModelRegistry", fake_registry)

assert compat.apply_qwen3_5_text_patches() is True

assert registered == {
"Qwen3_5ForCausalLM": ("vllm_fl.models.qwen3_5:Qwen3_5ForCausalLM"),
"Qwen3_5MoeForCausalLM": ("vllm_fl.models.qwen3_5:Qwen3_5MoeForCausalLM"),
}
assert transformers_config._CONFIG_REGISTRY == {
"qwen3_5_text": "Qwen3_5TextConfig",
"qwen3_5_moe_text": "Qwen3_5MoeTextConfig",
}
assert {
"Qwen3_5ForCausalLM": compat.Qwen3_5ForConditionalGenerationConfig,
"Qwen3_5MoeForCausalLM": compat.Qwen3_5ForConditionalGenerationConfig,
} == model_config.MODELS_CONFIG_MAP


def test_lazy_model_shim_marks_upstream_classes_hybrid():
from vllm_fl.models.qwen3_5 import (
Qwen3_5ForCausalLM,
Qwen3_5MoeForCausalLM,
)

for model_cls in (Qwen3_5ForCausalLM, Qwen3_5MoeForCausalLM):
assert model_cls.is_hybrid is True
assert hasattr(model_cls, "get_mamba_state_dtype_from_config")
assert hasattr(model_cls, "get_mamba_state_shape_from_config")
assert hasattr(model_cls, "get_mamba_state_copy_func")


def test_lazy_model_shim_remaps_vl_checkpoint_weights(monkeypatch):
from vllm_fl.models import qwen3_5

calls = {}

class FakeLoader:
def __init__(self, model, **kwargs):
calls["init"] = (model, kwargs)

def load_weights(self, weights, **kwargs):
calls["load"] = (weights, kwargs)
return {"loaded"}

monkeypatch.setattr(qwen3_5, "AutoWeightsLoader", FakeLoader)
model = object()
weights = [("model.language_model.proj.weight", object())]

assert qwen3_5._load_weights(model, weights) == {"loaded"}
assert calls["init"] == (
model,
{
"skip_prefixes": ["mtp."],
"ignore_unexpected_prefixes": ["model.visual."],
},
)
assert calls["load"][0] is weights
assert calls["load"][1]["mapper"] is qwen3_5._WEIGHTS_MAPPER
assert qwen3_5._WEIGHTS_MAPPER.orig_to_new_prefix == {
"model.language_model.": "model."
}


def test_lazy_model_shim_declares_hf_to_vllm_mapper():
"""configure_quant_config needs the class attribute, not just load_weights.

Rebinding load_weights fixes weight names only. The FP8 ignored_layers
rewrite goes through this attribute, and its absence fails silently.
"""
from vllm.model_executor.models import qwen3_5 as upstream

from vllm_fl.models import qwen3_5 # noqa: F401 - import applies the shim

mapper = upstream.Qwen3_5ForCausalLMBase.hf_to_vllm_mapper
assert mapper is not None
assert mapper.orig_to_new_prefix == {"model.language_model.": "model."}

for model_cls in (upstream.Qwen3_5ForCausalLM, upstream.Qwen3_5MoeForCausalLM):
assert model_cls.hf_to_vllm_mapper is mapper


def test_lazy_model_shim_keeps_existing_hf_to_vllm_mapper(monkeypatch):
"""Compose with the vLLM-side source patch instead of overwriting it."""
from vllm.model_executor.models import qwen3_5 as upstream

from vllm_fl.models import qwen3_5

sentinel = object()
monkeypatch.setattr(
upstream.Qwen3_5ForCausalLMBase, "hf_to_vllm_mapper", sentinel, raising=False
)
qwen3_5._patch_upstream_base()

assert upstream.Qwen3_5ForCausalLMBase.hf_to_vllm_mapper is sentinel
8 changes: 8 additions & 0 deletions vllm_fl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,6 +138,14 @@ def register_router():

def register_model():
"""Register FL-specific models not yet upstream."""
# General plugins are loaded independently in spawned model-inspection and
# worker processes, so all runtime compatibility hooks must be idempotent.
from vllm_fl.patches.moe_sum import patch_vllm_moe_sum
from vllm_fl.patches.qwen3_5_text import apply_qwen3_5_text_patches

apply_qwen3_5_text_patches()
patch_vllm_moe_sum()

_register_flagcx_connector()

# Register OOT quant kernels so kernel selection can find them
Expand Down
9 changes: 7 additions & 2 deletions vllm_fl/dispatch/backends/vendor/cuda/impl/fused_moe.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
"""

import torch

from vllm.triton_utils import triton
from vllm.utils.math_utils import round_up

Expand Down Expand Up @@ -67,9 +68,13 @@ def topk_softmax_cuda(


def moe_sum_cuda(inp, out):
from vllm._custom_ops import moe_sum
from vllm._custom_ops import moe_sum as vllm_moe_sum

moe_sum(inp, out)
# The general-plugin adapter routes vllm._custom_ops.moe_sum back through
# OpManager. Unwrap it here so selecting/falling back to vendor.cuda does
# not recursively re-enter dispatch.
native_moe_sum = getattr(vllm_moe_sum, "_vllm_fl_original", vllm_moe_sum)
native_moe_sum(inp, out)


def grouped_topk_cuda(
Expand Down
Loading
Loading