164 lines
5.7 KiB
Python
164 lines
5.7 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|||
|
|
# SPDX-FileCopyrightText: Copyright contributors to the vLLM project
|
||
|
|
"""Tests for T5EncoderModel prefix handling and weight loading fix."""
|
||
|
|
|
||
|
|
import pytest
|
||
|
|
import torch
|
||
|
|
from transformers import T5Config
|
||
|
|
from vllm.config import DeviceConfig, VllmConfig, set_current_vllm_config
|
||
|
|
|
||
|
|
from vllm_omni.diffusion.models.t5_encoder.t5_encoder import T5EncoderModel
|
||
|
|
|
||
|
|
pytestmark = [pytest.mark.core_model, pytest.mark.cpu]
|
||
|
|
|
||
|
|
|
||
|
|
_SMALL_T5_CONFIG = dict(
|
||
|
|
d_model=64,
|
||
|
|
d_kv=8,
|
||
|
|
d_ff=128,
|
||
|
|
num_heads=8,
|
||
|
|
num_layers=2,
|
||
|
|
vocab_size=256,
|
||
|
|
relative_attention_num_buckets=32,
|
||
|
|
relative_attention_max_distance=128,
|
||
|
|
is_gated_act=True,
|
||
|
|
dense_act_fn="gelu_new",
|
||
|
|
layer_norm_epsilon=1e-6,
|
||
|
|
feed_forward_proj="gated-gelu",
|
||
|
|
)
|
||
|
|
|
||
|
|
_T5_MODULE = "vllm_omni.diffusion.models.t5_encoder.t5_encoder"
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.fixture
|
||
|
|
def t5_config() -> T5Config:
|
||
|
|
return T5Config(**_SMALL_T5_CONFIG)
|
||
|
|
|
||
|
|
|
||
|
|
@pytest.fixture(scope="function", autouse=True)
|
||
|
|
def setup_vllm_config(monkeypatch, mocker):
|
||
|
|
"""Set up VllmConfig and TP=2 mocks for tests."""
|
||
|
|
device_config = DeviceConfig(device="cpu")
|
||
|
|
|
||
|
|
monkeypatch.setattr("vllm.model_executor.layers.linear.get_tensor_model_parallel_world_size", lambda: 2)
|
||
|
|
monkeypatch.setattr(f"{_T5_MODULE}.get_tensor_model_parallel_world_size", lambda: 2)
|
||
|
|
monkeypatch.setattr(
|
||
|
|
"vllm.model_executor.layers.vocab_parallel_embedding.get_tensor_model_parallel_world_size",
|
||
|
|
lambda: 2,
|
||
|
|
)
|
||
|
|
|
||
|
|
monkeypatch.setattr(f"{_T5_MODULE}.get_tensor_model_parallel_rank", lambda: 0)
|
||
|
|
monkeypatch.setattr(
|
||
|
|
"vllm.model_executor.layers.vocab_parallel_embedding.get_tensor_model_parallel_rank",
|
||
|
|
lambda: 0,
|
||
|
|
)
|
||
|
|
|
||
|
|
mock_tp_group = mocker.MagicMock()
|
||
|
|
mock_tp_group.world_size = 2
|
||
|
|
mocker.patch("vllm.distributed.parallel_state.get_tp_group", return_value=mock_tp_group)
|
||
|
|
|
||
|
|
monkeypatch.setattr(f"{_T5_MODULE}.get_act_fn", lambda _: torch.nn.GELU())
|
||
|
|
|
||
|
|
with set_current_vllm_config(VllmConfig(device_config=device_config)):
|
||
|
|
yield
|
||
|
|
|
||
|
|
|
||
|
|
class TestT5EncoderModelPrefixHandling:
|
||
|
|
"""Test that T5EncoderModel correctly handles prefix attribute."""
|
||
|
|
|
||
|
|
def test_prefix_stored_in_model(self, t5_config):
|
||
|
|
"""Test that prefix is stored in the model when provided."""
|
||
|
|
prefix = "text_encoder"
|
||
|
|
model = T5EncoderModel(t5_config, prefix=prefix)
|
||
|
|
assert hasattr(model, "prefix")
|
||
|
|
assert model.prefix == prefix
|
||
|
|
|
||
|
|
def test_prefix_empty_by_default(self, t5_config):
|
||
|
|
"""Test that prefix defaults to empty string when not provided."""
|
||
|
|
model = T5EncoderModel(t5_config)
|
||
|
|
assert hasattr(model, "prefix")
|
||
|
|
assert model.prefix == ""
|
||
|
|
|
||
|
|
|
||
|
|
class TestT5EncoderModelWeightLoadingWithPrefix:
|
||
|
|
"""Test weight loading with prefix handling."""
|
||
|
|
|
||
|
|
def test_load_weights_with_prefix(self, t5_config):
|
||
|
|
"""Test that weights without prefix are loaded when model has prefix."""
|
||
|
|
config = T5Config(**{**_SMALL_T5_CONFIG, "num_layers": 1})
|
||
|
|
model = T5EncoderModel(config, prefix="text_encoder")
|
||
|
|
|
||
|
|
inner_dim = config.num_heads * config.d_kv
|
||
|
|
|
||
|
|
weights = [
|
||
|
|
("encoder.block.0.layer.0.SelfAttention.q.weight", torch.randn(inner_dim, config.d_model)),
|
||
|
|
("encoder.block.0.layer.0.SelfAttention.k.weight", torch.randn(inner_dim, config.d_model)),
|
||
|
|
("encoder.block.0.layer.0.SelfAttention.v.weight", torch.randn(inner_dim, config.d_model)),
|
||
|
|
]
|
||
|
|
|
||
|
|
loaded = model.load_weights(weights)
|
||
|
|
assert len(loaded) > 0
|
||
|
|
|
||
|
|
def test_load_weights_embed_tokens_shared_sync(self, t5_config):
|
||
|
|
"""Test that embed_tokens and shared weights are synced."""
|
||
|
|
model = T5EncoderModel(t5_config, prefix="text_encoder")
|
||
|
|
|
||
|
|
d_model = t5_config.d_model
|
||
|
|
vocab_size = t5_config.vocab_size
|
||
|
|
|
||
|
|
embed_weight = torch.randn(vocab_size, d_model)
|
||
|
|
weights = [
|
||
|
|
("encoder.embed_tokens.weight", embed_weight.clone()),
|
||
|
|
]
|
||
|
|
|
||
|
|
model.load_weights(weights)
|
||
|
|
|
||
|
|
shared_param = model.shared.weight
|
||
|
|
embed_param = model.encoder.embed_tokens.weight
|
||
|
|
|
||
|
|
assert torch.allclose(shared_param, embed_param), (
|
||
|
|
"shared and embed_tokens should have the same weights after loading"
|
||
|
|
)
|
||
|
|
|
||
|
|
def test_load_weights_shared_without_prefix(self, t5_config):
|
||
|
|
"""Test shared.weight is recognized without relying on dot context."""
|
||
|
|
model = T5EncoderModel(t5_config, prefix="text_encoder")
|
||
|
|
|
||
|
|
shared_weight = torch.randn(t5_config.vocab_size, t5_config.d_model)
|
||
|
|
loaded = model.load_weights([("shared.weight", shared_weight)])
|
||
|
|
|
||
|
|
assert "shared.weight" in loaded
|
||
|
|
assert torch.allclose(model.shared.weight, model.encoder.embed_tokens.weight)
|
||
|
|
|
||
|
|
def test_unmatched_weights_are_not_reported_loaded(self, t5_config):
|
||
|
|
"""Test that skipped checkpoint weights are not added to loaded_params."""
|
||
|
|
model = T5EncoderModel(t5_config, prefix="text_encoder")
|
||
|
|
|
||
|
|
loaded = model.load_weights(
|
||
|
|
[
|
||
|
|
(
|
||
|
|
"text_encoder.encoder.block.0.layer.0.SelfAttention.missing.weight",
|
||
|
|
torch.randn(t5_config.d_model, t5_config.d_model),
|
||
|
|
),
|
||
|
|
]
|
||
|
|
)
|
||
|
|
|
||
|
|
assert loaded == set()
|
||
|
|
|
||
|
|
|
||
|
|
class TestT5EncoderModelWeightLoadingWithoutPrefix:
|
||
|
|
"""Test weight loading without prefix."""
|
||
|
|
|
||
|
|
def test_load_weights_without_prefix(self, t5_config):
|
||
|
|
"""Test that weights without prefix are loaded correctly."""
|
||
|
|
config = T5Config(**{**_SMALL_T5_CONFIG, "num_layers": 1})
|
||
|
|
model = T5EncoderModel(config)
|
||
|
|
|
||
|
|
inner_dim = config.num_heads * config.d_kv
|
||
|
|
|
||
|
|
weights = [
|
||
|
|
("encoder.block.0.layer.0.SelfAttention.q.weight", torch.randn(inner_dim, config.d_model)),
|
||
|
|
]
|
||
|
|
|
||
|
|
loaded = model.load_weights(weights)
|
||
|
|
assert len(loaded) > 0
|