Skip to content

Commit c5617d2

Browse files
committed
fix(qwen-vl): pad tokenizer-derived vocabularies
Signed-off-by: Chen Cui <chcui@nvidia.com>
1 parent 51765a6 commit c5617d2

5 files changed

Lines changed: 68 additions & 1 deletion

File tree

src/megatron/bridge/models/qwen_vl/modelling_qwen3_vl/model.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
)
5353
from megatron.bridge.models.qwen_vl.modelling_qwen3_vl.vision_model import Qwen3VLVisionModel
5454
from megatron.bridge.training.utils.packed_seq_utils import get_packed_seq_cp_partition_indices
55+
from megatron.bridge.utils.vocab_utils import calculate_padded_vocab_size
5556

5657

5758
def _is_mrope_position_ids(position_ids: torch.Tensor | None) -> bool:
@@ -204,10 +205,22 @@ def __init__(
204205
pg_collection=pg_collection,
205206
)
206207
if self.add_decoder:
208+
assert language_transformer_config.vocab_size is not None, (
209+
"vocab_size must be configured before constructing the Qwen3-VL language model"
210+
)
211+
if language_transformer_config.should_pad_vocab:
212+
language_model_vocab_size = calculate_padded_vocab_size(
213+
language_transformer_config.vocab_size,
214+
language_transformer_config.make_vocab_size_divisible_by,
215+
language_transformer_config.tensor_model_parallel_size,
216+
)
217+
else:
218+
language_model_vocab_size = language_transformer_config.vocab_size
219+
207220
self.language_model = Qwen3VLGPTModel(
208221
config=language_transformer_config,
209222
transformer_layer_spec=language_transformer_layer_spec,
210-
vocab_size=language_transformer_config.vocab_size,
223+
vocab_size=language_model_vocab_size,
211224
max_sequence_length=language_transformer_config.language_max_sequence_length,
212225
parallel_output=parallel_output,
213226
position_embedding_type="mrope",

src/megatron/bridge/models/qwen_vl/modelling_qwen3_vl/transformer_config.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,8 @@ class Qwen3VLTransformerConfig(TransformerConfig):
2828
"""Configuration for Qwen3-VL transformer with vision and language components."""
2929

3030
vocab_size: int = 64000
31+
make_vocab_size_divisible_by: int = 128
32+
should_pad_vocab: bool = False
3133
language_max_sequence_length: int = 4096
3234

3335
patch_size: int = 16

src/megatron/bridge/perf_recipes/qwen_vl/common.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ def _use_model_vocab_null_tokenizer(cfg: ConfigContainer) -> None:
3939
cfg.tokenizer.tokenizer_type = "NullTokenizer"
4040
cfg.tokenizer.tokenizer_model = None
4141
cfg.tokenizer.vocab_size = cfg.model.vocab_size
42+
# The synthetic tokenizer mirrors the fixed benchmark model shape; it does
43+
# not define a new tokenizer-derived vocabulary for from-scratch training.
44+
cfg.tokenizer.use_tokenizer_vocab_size = False
4245

4346

4447
def _qwen35_vl_common(cfg: ConfigContainer) -> None:

tests/unit_tests/models/qwen_vl/modelling_qwen3_vl/test_model.py

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
import os
2424
from dataclasses import replace
2525
from types import SimpleNamespace
26+
from unittest.mock import Mock
2627

2728
import pytest
2829
import torch
@@ -326,6 +327,53 @@ def test_shared_embedding_or_output_weight(self, hf_config):
326327
weight_no_decoder = model_no_decoder.shared_embedding_or_output_weight()
327328
assert weight_no_decoder is None
328329

330+
@pytest.mark.parametrize(
331+
("vocab_size", "should_pad_vocab", "expected_vocab_size"),
332+
[
333+
(151669, True, 152064),
334+
(248077, True, 248320),
335+
(151936, False, 151936),
336+
],
337+
)
338+
def test_language_model_honors_vocab_padding_policy(
339+
self,
340+
hf_config,
341+
monkeypatch,
342+
vocab_size,
343+
should_pad_vocab,
344+
expected_vocab_size,
345+
):
346+
"""Apply tokenizer-derived padding before constructing the Qwen language model."""
347+
self._setup_parallel_state(tp_size=1, ep_size=1, pp_size=1)
348+
pg_collection = ProcessGroupCollection.use_mpu_process_groups()
349+
language_transformer_config = self.get_language_transformer_config(hf_config)
350+
language_transformer_config.vocab_size = vocab_size
351+
language_transformer_config.should_pad_vocab = should_pad_vocab
352+
language_transformer_config.make_vocab_size_divisible_by = 128
353+
language_transformer_config.tensor_model_parallel_size = 4
354+
355+
language_model = Mock()
356+
language_model.config.cuda_graph_impl = "none"
357+
language_model.share_embeddings_and_output_weights = False
358+
language_model_constructor = Mock(return_value=language_model)
359+
monkeypatch.setattr(
360+
"megatron.bridge.models.qwen_vl.modelling_qwen3_vl.model.Qwen3VLGPTModel",
361+
language_model_constructor,
362+
)
363+
364+
Qwen3VLModel(
365+
vision_transformer_config=self.get_vision_transformer_config(hf_config),
366+
language_transformer_config=language_transformer_config,
367+
language_transformer_layer_spec=self.get_language_model_layer_spec(),
368+
pre_process=False,
369+
post_process=True,
370+
add_encoder=False,
371+
add_decoder=True,
372+
pg_collection=pg_collection,
373+
)
374+
375+
assert language_model_constructor.call_args.kwargs["vocab_size"] == expected_vocab_size
376+
329377
@pytest.mark.timeout(50)
330378
def test_set_input_tensor(self, hf_config):
331379
"""Test set_input_tensor method."""

tests/unit_tests/recipes/test_all_perf_recipe_factories.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,4 @@ def test_perf_recipe_factory_builds_config(recipe_factory: Callable[..., object]
8888
if "pretrain" in recipe_factory.__name__ and isinstance(cfg.dataset, MockVLMSFTDatasetConfig):
8989
assert cfg.tokenizer.tokenizer_type == "NullTokenizer"
9090
assert cfg.tokenizer.vocab_size == cfg.model.vocab_size
91+
assert cfg.tokenizer.use_tokenizer_vocab_size is False

0 commit comments

Comments
 (0)