Files
vllm-omni/tests/diffusion/models/bagel/test_combine_cfg.py
T
Ding Zuhaoand汪志鹏 128e6f4b97 [Feature][Bagel] Add CFG parallel mode (#1578)
Signed-off-by: Ding Zuhao <e1583181@u.nus.edu>
Signed-off-by: 汪志鹏 <wangzhipeng628@gmail.com>
Co-authored-by: 汪志鹏 <wangzhipeng628@gmail.com>
2026-03-06 09:55:54 +08:00

317 lines
11 KiB
Python

# SPDX-License-Identifier: Apache-2.0
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
"""Unit tests for Bagel._combine_cfg logic."""
import pytest
import torch
from vllm_omni.diffusion.models.bagel.bagel_transformer import Bagel
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
class TestCombineCfg:
"""Tests for the _combine_cfg static method."""
def _make_tensors(self, shape=(10, 64), seed=42):
"""Create deterministic test tensors."""
gen = torch.Generator().manual_seed(seed)
v_t = torch.randn(shape, generator=gen)
cfg_text_v_t = torch.randn(shape, generator=gen)
cfg_img_v_t = torch.randn(shape, generator=gen)
return v_t, cfg_text_v_t, cfg_img_v_t
def test_text_channel_renorm_preserves_direction(self):
"""text_channel renorm should change direction but constrain magnitude."""
v_t, cfg_text_v_t, _ = self._make_tensors()
result = Bagel._combine_cfg(
v_t,
cfg_text_v_t,
None,
cfg_text_scale=4.0,
cfg_img_scale=1.0,
cfg_renorm_type="text_channel",
cfg_renorm_min=0.0,
)
# Result norm per token should be <= original v_t norm (clamp max=1.0)
result_norm = torch.norm(result, dim=-1)
v_t_norm = torch.norm(v_t, dim=-1)
assert torch.all(result_norm <= v_t_norm + 1e-6), "text_channel renorm should not increase per-token norm"
def test_scale_1_returns_v_t(self):
"""cfg_text_scale=1.0 means no CFG: result should equal v_t."""
v_t, cfg_text_v_t, _ = self._make_tensors()
result = Bagel._combine_cfg(
v_t,
cfg_text_v_t,
None,
cfg_text_scale=1.0,
cfg_img_scale=1.0,
cfg_renorm_type="text_channel",
cfg_renorm_min=0.0,
)
# scale=1 → v_t_text_ = cfg_text + 1*(v_t - cfg_text) = v_t
# renorm scale = norm(v_t)/norm(v_t) = 1.0, so result = v_t
assert torch.allclose(result, v_t, atol=1e-6)
def test_img_cfg_applied_when_scale_gt_1(self):
"""When cfg_img_scale > 1.0, result should differ from text-only CFG."""
v_t, cfg_text_v_t, cfg_img_v_t = self._make_tensors()
text_only = Bagel._combine_cfg(
v_t,
cfg_text_v_t,
None,
cfg_text_scale=4.0,
cfg_img_scale=1.0,
cfg_renorm_type="text_channel",
cfg_renorm_min=0.0,
)
with_img = Bagel._combine_cfg(
v_t,
cfg_text_v_t,
cfg_img_v_t,
cfg_text_scale=4.0,
cfg_img_scale=1.5,
cfg_renorm_type="text_channel",
cfg_renorm_min=0.0,
)
assert not torch.allclose(text_only, with_img, atol=1e-6), (
"Image CFG should produce different result from text-only CFG"
)
def test_img_cfg_none_ignored(self):
"""cfg_img_v_t=None should be equivalent to cfg_img_scale <= 1.0."""
v_t, cfg_text_v_t, cfg_img_v_t = self._make_tensors()
result_none = Bagel._combine_cfg(
v_t,
cfg_text_v_t,
None,
cfg_text_scale=4.0,
cfg_img_scale=1.5,
cfg_renorm_type="text_channel",
cfg_renorm_min=0.0,
)
result_low_scale = Bagel._combine_cfg(
v_t,
cfg_text_v_t,
cfg_img_v_t,
cfg_text_scale=4.0,
cfg_img_scale=0.5,
cfg_renorm_type="text_channel",
cfg_renorm_min=0.0,
)
assert torch.allclose(result_none, result_low_scale, atol=1e-6), (
"cfg_img_v_t=None and cfg_img_scale<=1.0 should give same result"
)
def test_global_renorm(self):
"""global renorm should produce valid output without error."""
v_t, cfg_text_v_t, cfg_img_v_t = self._make_tensors()
result = Bagel._combine_cfg(
v_t,
cfg_text_v_t,
cfg_img_v_t,
cfg_text_scale=4.0,
cfg_img_scale=1.5,
cfg_renorm_type="global",
cfg_renorm_min=0.0,
)
assert result.shape == v_t.shape
assert not torch.any(torch.isnan(result))
def test_channel_renorm(self):
"""channel renorm should produce valid output without error."""
v_t, cfg_text_v_t, cfg_img_v_t = self._make_tensors()
result = Bagel._combine_cfg(
v_t,
cfg_text_v_t,
cfg_img_v_t,
cfg_text_scale=4.0,
cfg_img_scale=1.5,
cfg_renorm_type="channel",
cfg_renorm_min=0.0,
)
assert result.shape == v_t.shape
assert not torch.any(torch.isnan(result))
def test_invalid_renorm_type_raises(self):
"""Unknown renorm type should raise NotImplementedError."""
v_t, cfg_text_v_t, _ = self._make_tensors()
with pytest.raises(NotImplementedError):
Bagel._combine_cfg(
v_t,
cfg_text_v_t,
None,
cfg_text_scale=4.0,
cfg_img_scale=1.0,
cfg_renorm_type="unknown",
cfg_renorm_min=0.0,
)
def test_renorm_min_clamps_scale(self):
"""cfg_renorm_min should prevent scale from going too low."""
v_t = torch.ones(10, 64)
# Make cfg_text_v_t very different so CFG amplifies heavily
cfg_text_v_t = torch.zeros(10, 64)
result_no_min = Bagel._combine_cfg(
v_t,
cfg_text_v_t,
None,
cfg_text_scale=100.0,
cfg_img_scale=1.0,
cfg_renorm_type="text_channel",
cfg_renorm_min=0.0,
)
result_with_min = Bagel._combine_cfg(
v_t,
cfg_text_v_t,
None,
cfg_text_scale=100.0,
cfg_img_scale=1.0,
cfg_renorm_type="text_channel",
cfg_renorm_min=0.5,
)
# With higher renorm_min, result magnitude should be larger
# (scale is clamped to at least 0.5 instead of going near 0)
norm_no_min = torch.norm(result_no_min)
norm_with_min = torch.norm(result_with_min)
assert norm_with_min >= norm_no_min - 1e-6, "Higher cfg_renorm_min should preserve more magnitude"
def test_global_renorm_with_img_cfg(self):
"""global renorm + img CFG should produce valid, different output."""
v_t, cfg_text_v_t, cfg_img_v_t = self._make_tensors()
text_only = Bagel._combine_cfg(
v_t.clone(),
cfg_text_v_t.clone(),
None,
cfg_text_scale=4.0,
cfg_img_scale=1.0,
cfg_renorm_type="global",
cfg_renorm_min=0.0,
)
with_img = Bagel._combine_cfg(
v_t.clone(),
cfg_text_v_t.clone(),
cfg_img_v_t.clone(),
cfg_text_scale=4.0,
cfg_img_scale=1.5,
cfg_renorm_type="global",
cfg_renorm_min=0.0,
)
assert not torch.allclose(text_only, with_img, atol=1e-6), (
"global renorm + img CFG should differ from text-only"
)
assert not torch.any(torch.isnan(with_img))
def test_channel_renorm_with_img_cfg(self):
"""channel renorm + img CFG should produce valid, different output."""
v_t, cfg_text_v_t, cfg_img_v_t = self._make_tensors()
text_only = Bagel._combine_cfg(
v_t.clone(),
cfg_text_v_t.clone(),
None,
cfg_text_scale=4.0,
cfg_img_scale=1.0,
cfg_renorm_type="channel",
cfg_renorm_min=0.0,
)
with_img = Bagel._combine_cfg(
v_t.clone(),
cfg_text_v_t.clone(),
cfg_img_v_t.clone(),
cfg_text_scale=4.0,
cfg_img_scale=1.5,
cfg_renorm_type="channel",
cfg_renorm_min=0.0,
)
assert not torch.allclose(text_only, with_img, atol=1e-6), (
"channel renorm + img CFG should differ from text-only"
)
assert not torch.any(torch.isnan(with_img))
def test_global_channel_renorm_constrains_norm(self):
"""global and channel renorm should not increase overall norm."""
v_t, cfg_text_v_t, cfg_img_v_t = self._make_tensors()
for renorm_type in ["global", "channel"]:
result = Bagel._combine_cfg(
v_t.clone(),
cfg_text_v_t.clone(),
cfg_img_v_t.clone(),
cfg_text_scale=4.0,
cfg_img_scale=1.5,
cfg_renorm_type=renorm_type,
cfg_renorm_min=0.0,
)
# Global norm of result should be <= global norm of v_t (clamp max=1.0)
assert torch.norm(result) <= torch.norm(v_t) + 1e-5, f"{renorm_type} renorm should not increase global norm"
def test_text_channel_img_cfg_no_second_renorm(self):
"""text_channel mode: img CFG is applied AFTER renorm, without a second renorm.
So the result norm can exceed v_t norm when img_scale > 1."""
v_t, cfg_text_v_t, cfg_img_v_t = self._make_tensors()
result = Bagel._combine_cfg(
v_t,
cfg_text_v_t,
cfg_img_v_t,
cfg_text_scale=4.0,
cfg_img_scale=2.0,
cfg_renorm_type="text_channel",
cfg_renorm_min=0.0,
)
# text_channel renorms after text CFG, then applies img CFG without renorm
# So result norm CAN exceed v_t norm — this is expected behavior
assert result.shape == v_t.shape
assert not torch.any(torch.isnan(result))
def test_all_renorm_types_consistent_direction(self):
"""All renorm types should guide in the same general direction."""
v_t, cfg_text_v_t, _ = self._make_tensors()
results = {}
for renorm_type in ["text_channel", "global", "channel"]:
results[renorm_type] = Bagel._combine_cfg(
v_t.clone(),
cfg_text_v_t.clone(),
None,
cfg_text_scale=4.0,
cfg_img_scale=1.0,
cfg_renorm_type=renorm_type,
cfg_renorm_min=0.0,
)
# All results should have positive cosine similarity with each other
for a_name, a in results.items():
for b_name, b in results.items():
cos_sim = torch.nn.functional.cosine_similarity(a.flatten().unsqueeze(0), b.flatten().unsqueeze(0))
assert cos_sim > 0.5, (
f"{a_name} and {b_name} should point in similar direction, "
f"but cosine similarity = {cos_sim.item():.4f}"
)