[Model] Add online FP8 support for Boogu-Image MLLM (#7342)

Signed-off-by: Dmaner <2663515256@qq.com>
This commit is contained in:
DMAN authored and GitHub committed 2026-09-21 05:13:37 +00:00
1 parent 1b87115c9e
commit d139c600c7
4 files changed
+366 -68

No files matched your search

@@ -13,11 +13,13 @@ Two groups:
CFG handling, and reshape logic can be verified numerically on CPU.
"""
import os
from types import SimpleNamespace
import pytest
import torch
from torch import nn
from transformers import Qwen3VLConfig
from vllm_omni.diffusion.data import DiffusionParallelConfig, OmniDiffusionConfig, TransformerConfig
@@ -56,9 +58,17 @@ def mock_dependencies(mocker, monkeypatch):
f"{_MODULE}.FlowMatchEulerDiscreteScheduler.from_pretrained",
lambda *a, **k: mock_scheduler,
)
monkeypatch.setattr(
mllm_loader = mocker.patch(
f"{_MODULE}.Qwen3VLForConditionalGeneration.from_pretrained",
lambda *a, **k: mllm_wrapper,
return_value=mllm_wrapper,
)
mllm_config_loader = mocker.patch(
f"{_MODULE}.Qwen3VLConfig.from_pretrained",
return_value=Qwen3VLConfig(),
)
mllm_builder = mocker.patch(
f"{_MODULE}.create_transformers_model_with_vllm_linears",
return_value=nn.Linear(1, 1),
)
monkeypatch.setattr(
f"{_MODULE}.Qwen3VLProcessor.from_pretrained",
@@ -74,12 +84,17 @@ def mock_dependencies(mocker, monkeypatch):
mock_transformer_cls.return_value = mock_transformer_instance
monkeypatch.setattr(f"{_MODULE}.BooguImageTransformer2DModel", mock_transformer_cls)
# Treat the dummy model id as a local path: skips hub prefetch.
mocker.patch("os.path.exists", return_value=True)
# Treat only the dummy model id as local. Other filesystem checks (for
# example lazy imports in the quantization registry) must remain real.
path_exists = os.path.exists
mocker.patch("os.path.exists", side_effect=lambda path: str(path).startswith("dummy-boogu") or path_exists(path))
return {
"inner_encoder": inner_encoder,
"mllm_wrapper": mllm_wrapper,
"mllm_loader": mllm_loader,
"mllm_config_loader": mllm_config_loader,
"mllm_builder": mllm_builder,
"processor": mock_processor,
"vae": mock_vae,
"scheduler": mock_scheduler,
@@ -128,6 +143,9 @@ def test_constructor_wires_components(boogu_pipeline, mock_dependencies):
assert boogu_pipeline.vae_scale_factor == 8
assert boogu_pipeline.default_sample_size == 128
assert hasattr(boogu_pipeline, "load_weights")
assert "quantization_config" not in mock_dependencies["mllm_loader"].call_args.kwargs
mock_dependencies["mllm_config_loader"].assert_not_called()
mock_dependencies["mllm_builder"].assert_not_called()
def test_constructor_strips_mllm_lm_head(boogu_pipeline, mock_dependencies):
@@ -188,7 +206,7 @@ def test_constructor_weights_sources(boogu_pipeline):
assert source.fall_back_to_pt is True
def test_constructor_routes_only_transformer_config(mock_dependencies, mocker):
def test_constructor_rejects_unsupported_mllm_quantization(mock_dependencies, mocker):
from vllm.model_executor.layers.quantization.base_config import QuantizationConfig
from vllm_omni.diffusion.models.boogu_image.pipeline_boogu_image import BooguImagePipeline
@@ -204,10 +222,143 @@ def test_constructor_routes_only_transformer_config(mock_dependencies, mocker):
{"transformer": transformer_config, "mllm": encoder_config, "vae": None}
),
)
with pytest.raises(ValueError, match="Boogu MLLM only supports FP8 quantization"):
BooguImagePipeline(od_config=od_config)
mock_dependencies["mllm_loader"].assert_not_called()
mock_dependencies["mllm_config_loader"].assert_not_called()
@pytest.mark.parametrize(
("quantization_config", "quantize_mllm"),
[
pytest.param("fp8", True, id="global-fp8"),
pytest.param({"mllm": {"method": "fp8"}, "transformer": {"method": "fp8"}}, True, id="mllm-and-dit-fp8"),
pytest.param({"mllm": None, "transformer": "fp8"}, False, id="dit-only-fp8"),
],
)
def test_constructor_routes_mllm_quantization(mock_dependencies, quantization_config, quantize_mllm):
from transformers import AutoModel
from vllm.model_executor.layers.quantization.fp8 import Fp8Config
from vllm.model_executor.layers.quantization.online.base import OnlineQuantizationConfig
from vllm.model_executor.layers.quantization.utils.quant_utils import kFp8Static128BlockSym
from vllm_omni.diffusion.models.boogu_image.pipeline_boogu_image import BooguImagePipeline
od_config = OmniDiffusionConfig(
model="dummy-boogu",
revision="test-revision",
tf_model_config=TransformerConfig(params={}),
dtype=torch.bfloat16,
quantization_config=quantization_config,
)
pipeline = BooguImagePipeline(od_config=od_config)
if quantize_mllm:
mock_dependencies["mllm_loader"].assert_not_called()
mock_dependencies["mllm_config_loader"].assert_called_once_with(
"dummy-boogu", subfolder="mllm", local_files_only=True, revision="test-revision"
)
builder = mock_dependencies["mllm_builder"]
builder.assert_called_once()
auto_cls, hf_config, online_quant_config = builder.call_args.args
assert auto_cls is AutoModel
assert hf_config is mock_dependencies["mllm_config_loader"].return_value
assert isinstance(online_quant_config, OnlineQuantizationConfig)
assert online_quant_config.args.linear.weight == kFp8Static128BlockSym
assert builder.call_args.kwargs == {
"dtype": torch.bfloat16,
"device": pipeline._execution_device,
"prefix": "mllm",
"skip_modules": ("mllm.visual",),
}
assert pipeline.mllm is builder.return_value
assert not pipeline.mllm.training
assert all(not parameter.requires_grad for parameter in pipeline.mllm.parameters())
transformer_source, mllm_source = pipeline.weights_sources
assert transformer_source.subfolder == "transformer"
assert mllm_source.model_or_path == "dummy-boogu"
assert mllm_source.subfolder == "mllm"
assert mllm_source.prefix == "mllm."
assert mllm_source.revision == "test-revision"
else:
assert "quantization_config" not in mock_dependencies["mllm_loader"].call_args.kwargs
assert pipeline.mllm is mock_dependencies["inner_encoder"]
mock_dependencies["mllm_config_loader"].assert_not_called()
mock_dependencies["mllm_builder"].assert_not_called()
assert [source.subfolder for source in pipeline.weights_sources] == ["transformer"]
assert isinstance(mock_dependencies["transformer_cls"].call_args.kwargs["quant_config"], Fp8Config)
def test_constructor_preserves_mllm_ignored_layers(mock_dependencies):
from vllm.model_executor.layers.quantization.fp8 import Fp8Config
from vllm_omni.diffusion.models.boogu_image.pipeline_boogu_image import BooguImagePipeline
quant_config = Fp8Config(
ignored_layers=["mllm.language_model.layers.0.self_attn.q_proj", "transformer.blocks.0.attn.to_q"]
)
od_config = OmniDiffusionConfig(
model="dummy-boogu",
tf_model_config=TransformerConfig(params={}),
dtype=torch.bfloat16,
quantization_config=quant_config,
)
BooguImagePipeline(od_config=od_config)
kwargs = mock_dependencies["transformer_cls"].call_args.kwargs
assert kwargs["quant_config"] is transformer_config
assert kwargs["prefix"] == "transformer"
online_quant_config = mock_dependencies["mllm_builder"].call_args.args[2]
assert online_quant_config.ignored_layers == quant_config.ignored_layers
def test_constructor_preserves_serialized_mllm_quantization(mock_dependencies):
from vllm_omni.diffusion.models.boogu_image.pipeline_boogu_image import BooguImagePipeline
mock_dependencies["mllm_config_loader"].return_value = Qwen3VLConfig(
quantization_config={"quant_method": "fp8", "modules_to_not_convert": ["model.visual"]}
)
od_config = OmniDiffusionConfig(
model="dummy-boogu-fp8",
tf_model_config=TransformerConfig(params={}),
dtype=torch.bfloat16,
quantization_config="fp8",
)
pipeline = BooguImagePipeline(od_config=od_config)
# No override: HF reads the serialized checkpoint's scales and skip list.
assert "quantization_config" not in mock_dependencies["mllm_loader"].call_args.kwargs
assert pipeline.mllm is mock_dependencies["inner_encoder"]
mock_dependencies["mllm_builder"].assert_not_called()
assert [source.subfolder for source in pipeline.weights_sources] == ["transformer"]
def test_load_weights_maps_mllm_and_preserves_transformer():
from vllm_omni.diffusion.models.boogu_image.pipeline_boogu_image import BooguImagePipeline
pipeline = object.__new__(BooguImagePipeline)
nn.Module.__init__(pipeline)
pipeline.mllm = nn.ModuleDict(
{
"language_model": nn.Linear(2, 2, bias=False),
"visual": nn.Linear(2, 2, bias=False),
}
)
pipeline.transformer = nn.Linear(2, 2, bias=False)
language_weight = torch.full((2, 2), 1.0)
visual_weight = torch.full((2, 2), 2.0)
transformer_weight = torch.full((2, 2), 3.0)
loaded = pipeline.load_weights(
[
("mllm.lm_head.weight", torch.full((2, 2), 9.0)),
("mllm.model.language_model.weight", language_weight),
("mllm.model.visual.weight", visual_weight),
("transformer.weight", transformer_weight),
]
)
assert loaded == {"mllm.language_model.weight", "mllm.visual.weight", "transformer.weight"}
torch.testing.assert_close(pipeline.mllm["language_model"].weight, language_weight)
torch.testing.assert_close(pipeline.mllm["visual"].weight, visual_weight)
torch.testing.assert_close(pipeline.transformer.weight, transformer_weight)
@pytest.mark.parametrize(