Skip to content

Commit 51765a6

Browse files
committed
refactor(recipes): isolate Qwen-VL null tokenizer
Signed-off-by: Chen Cui <chcui@nvidia.com>
1 parent b128da7 commit 51765a6

8 files changed

Lines changed: 38 additions & 41 deletions

File tree

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

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,21 @@
3232
from megatron.bridge.training.config import ConfigContainer
3333

3434

35+
def _use_model_vocab_null_tokenizer(cfg: ConfigContainer) -> None:
36+
"""Use a model-sized synthetic tokenizer for Qwen-VL performance runs."""
37+
if cfg.model.vocab_size is None:
38+
raise ValueError("Qwen-VL performance recipes require a model vocabulary size.")
39+
cfg.tokenizer.tokenizer_type = "NullTokenizer"
40+
cfg.tokenizer.tokenizer_model = None
41+
cfg.tokenizer.vocab_size = cfg.model.vocab_size
42+
43+
3544
def _qwen35_vl_common(cfg: ConfigContainer) -> None:
3645
"""Apply VLM-specific performance benchmark settings for Qwen3.5-VL.
3746
3847
Must be called before ``_benchmark_common`` and after setting precision.
3948
"""
49+
_use_model_vocab_null_tokenizer(cfg)
4050
cfg.model.bias_activation_fusion = True
4151
cfg.model.recompute_granularity = None
4252
cfg.model.recompute_method = None
@@ -82,6 +92,7 @@ def _qwen35_vl_post_clear_scope_with_overlap(cfg: ConfigContainer) -> None:
8292

8393
def _finalize_qwen3_vl(cfg: ConfigContainer) -> None:
8494
"""Apply Qwen3-VL perf defaults that must override generic benchmark defaults."""
95+
_use_model_vocab_null_tokenizer(cfg)
8596
# _benchmark_common sets apply_rope_fusion=True; Qwen3-VL asserts it must be False
8697
# (per-token absolute positional frequencies are incompatible with TE's fused RoPE).
8798
cfg.model.apply_rope_fusion = False

src/megatron/bridge/recipes/gpt/h100/vanilla_gpt.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,6 @@ def vanilla_gpt_pretrain_1gpu_h100_bf16_config() -> ConfigContainer:
112112
tokenizer=TokenizerConfig(
113113
tokenizer_type="NullTokenizer",
114114
vocab_size=DEFAULT_NULL_TOKENIZER_VOCAB_SIZE,
115-
use_tokenizer_vocab_size=True,
116115
),
117116
checkpoint=CheckpointConfig(
118117
save_interval=500,

src/megatron/bridge/recipes/qwen_vl/h100/qwen35_vl.py

Lines changed: 5 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,12 @@
3030
from megatron.bridge.recipes.utils.dataset_utils import default_peft_config
3131
from megatron.bridge.recipes.utils.environment_utils import COMMON_RECIPE_ENV_VARS
3232
from megatron.bridge.recipes.utils.optimizer_utils import distributed_fused_adam_with_cosine_annealing
33-
from megatron.bridge.recipes.utils.tokenizer_utils import DEFAULT_NULL_TOKENIZER_VOCAB_SIZE
3433
from megatron.bridge.training.config import ConfigContainer
3534

3635

3736
# =============================================================================
3837
# Qwen3.5-VL Pretrain Configurations (mock dataset)
3938
# =============================================================================
40-
# The mock VLM dataset gets language token IDs from the HF processor. Its
41-
# NullTokenizer is only a runtime placeholder and must not resize the model.
4239
def qwen35_vl_9b_pretrain_4gpu_h100_bf16_mock_config() -> ConfigContainer:
4340
"""Return a pre-training config for Qwen3.5-VL 9B (dense)."""
4441
cfg = _pretrain_common()
@@ -74,9 +71,7 @@ def qwen35_vl_9b_pretrain_4gpu_h100_bf16_mock_config() -> ConfigContainer:
7471
persistent_workers=False,
7572
pad_to_max_length=True,
7673
)
77-
cfg.tokenizer.tokenizer_type = "NullTokenizer"
78-
cfg.tokenizer.vocab_size = DEFAULT_NULL_TOKENIZER_VOCAB_SIZE
79-
cfg.tokenizer.use_tokenizer_vocab_size = False
74+
cfg.tokenizer.tokenizer_model = hf_path
8075
cfg.train.eval_interval = 500
8176
cfg.train.eval_iters = 32
8277
cfg.ddp.overlap_grad_reduce = False
@@ -124,9 +119,7 @@ def qwen35_vl_27b_pretrain_16gpu_h100_bf16_mock_config() -> ConfigContainer:
124119
persistent_workers=False,
125120
pad_to_max_length=True,
126121
)
127-
cfg.tokenizer.tokenizer_type = "NullTokenizer"
128-
cfg.tokenizer.vocab_size = DEFAULT_NULL_TOKENIZER_VOCAB_SIZE
129-
cfg.tokenizer.use_tokenizer_vocab_size = False
122+
cfg.tokenizer.tokenizer_model = hf_path
130123
cfg.train.eval_interval = 500
131124
cfg.train.eval_iters = 32
132125
cfg.ddp.overlap_grad_reduce = False
@@ -175,9 +168,7 @@ def qwen35_vl_35b_a3b_pretrain_8gpu_h100_bf16_mock_config() -> ConfigContainer:
175168
persistent_workers=False,
176169
pad_to_max_length=True,
177170
)
178-
cfg.tokenizer.tokenizer_type = "NullTokenizer"
179-
cfg.tokenizer.vocab_size = DEFAULT_NULL_TOKENIZER_VOCAB_SIZE
180-
cfg.tokenizer.use_tokenizer_vocab_size = False
171+
cfg.tokenizer.tokenizer_model = hf_path
181172
cfg.train.eval_interval = 500
182173
cfg.train.eval_iters = 32
183174
cfg.ddp.overlap_grad_reduce = False
@@ -227,9 +218,7 @@ def qwen35_vl_122b_a10b_pretrain_128gpu_h100_bf16_mock_config() -> ConfigContain
227218
persistent_workers=False,
228219
pad_to_max_length=True,
229220
)
230-
cfg.tokenizer.tokenizer_type = "NullTokenizer"
231-
cfg.tokenizer.vocab_size = DEFAULT_NULL_TOKENIZER_VOCAB_SIZE
232-
cfg.tokenizer.use_tokenizer_vocab_size = False
221+
cfg.tokenizer.tokenizer_model = hf_path
233222
cfg.train.eval_interval = 500
234223
cfg.train.eval_iters = 32
235224
cfg.ddp.overlap_grad_reduce = False
@@ -280,9 +269,7 @@ def qwen35_vl_397b_a17b_pretrain_512gpu_h100_bf16_mock_config() -> ConfigContain
280269
persistent_workers=False,
281270
pad_to_max_length=True,
282271
)
283-
cfg.tokenizer.tokenizer_type = "NullTokenizer"
284-
cfg.tokenizer.vocab_size = DEFAULT_NULL_TOKENIZER_VOCAB_SIZE
285-
cfg.tokenizer.use_tokenizer_vocab_size = False
272+
cfg.tokenizer.tokenizer_model = hf_path
286273
cfg.train.eval_interval = 500
287274
cfg.train.eval_iters = 32
288275
cfg.ddp.overlap_grad_reduce = False

src/megatron/bridge/recipes/qwen_vl/h100/qwen3_vl.py

Lines changed: 3 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -32,16 +32,13 @@
3232
from megatron.bridge.recipes.utils.dataset_utils import default_peft_config
3333
from megatron.bridge.recipes.utils.environment_utils import COMMON_RECIPE_ENV_VARS
3434
from megatron.bridge.recipes.utils.optimizer_utils import distributed_fused_adam_with_cosine_annealing
35-
from megatron.bridge.recipes.utils.tokenizer_utils import DEFAULT_NULL_TOKENIZER_VOCAB_SIZE
3635
from megatron.bridge.training.config import ConfigContainer
3736
from megatron.bridge.training.flex_dispatcher_backend import apply_flex_dispatcher_backend
3837

3938

4039
# =============================================================================
4140
# Qwen3-VL Pretrain Configurations (mock dataset)
4241
# =============================================================================
43-
# The mock VLM dataset gets language token IDs from the HF processor. Its
44-
# NullTokenizer is only a runtime placeholder and must not resize the model.
4542

4643

4744
def qwen3_vl_8b_pretrain_4gpu_h100_bf16_mock_config() -> ConfigContainer:
@@ -79,9 +76,7 @@ def qwen3_vl_8b_pretrain_4gpu_h100_bf16_mock_config() -> ConfigContainer:
7976
persistent_workers=False,
8077
pad_to_max_length=True,
8178
)
82-
cfg.tokenizer.tokenizer_type = "NullTokenizer"
83-
cfg.tokenizer.vocab_size = DEFAULT_NULL_TOKENIZER_VOCAB_SIZE
84-
cfg.tokenizer.use_tokenizer_vocab_size = False
79+
cfg.tokenizer.tokenizer_model = hf_path
8580
cfg.train.eval_interval = 500
8681
cfg.train.eval_iters = 32
8782
cfg.ddp.overlap_grad_reduce = False
@@ -130,9 +125,7 @@ def qwen3_vl_30b_a3b_pretrain_8gpu_h100_bf16_mock_config() -> ConfigContainer:
130125
persistent_workers=False,
131126
pad_to_max_length=True,
132127
)
133-
cfg.tokenizer.tokenizer_type = "NullTokenizer"
134-
cfg.tokenizer.vocab_size = DEFAULT_NULL_TOKENIZER_VOCAB_SIZE
135-
cfg.tokenizer.use_tokenizer_vocab_size = False
128+
cfg.tokenizer.tokenizer_model = hf_path
136129
cfg.train.eval_interval = 500
137130
cfg.train.eval_iters = 32
138131
cfg.ddp.overlap_grad_reduce = False
@@ -182,9 +175,7 @@ def qwen3_vl_235b_a22b_pretrain_256gpu_h100_bf16_mock_config() -> ConfigContaine
182175
persistent_workers=False,
183176
pad_to_max_length=True,
184177
)
185-
cfg.tokenizer.tokenizer_type = "NullTokenizer"
186-
cfg.tokenizer.vocab_size = DEFAULT_NULL_TOKENIZER_VOCAB_SIZE
187-
cfg.tokenizer.use_tokenizer_vocab_size = False
178+
cfg.tokenizer.tokenizer_model = hf_path
188179
cfg.train.eval_interval = 500
189180
cfg.train.eval_iters = 32
190181
cfg.ddp.overlap_grad_reduce = False

tests/unit_tests/recipes/qwen_vl/test_qwen35_vl_recipes.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -715,7 +715,9 @@ def test_each_qwen35_vl_pretrain_mock_recipe_builds_config(recipe_func: Callable
715715

716716
_assert_basic_config(cfg)
717717

718-
assert cfg.tokenizer.tokenizer_type == "NullTokenizer"
718+
assert cfg.tokenizer.tokenizer_type == "HuggingFaceTokenizer"
719+
assert cfg.tokenizer.tokenizer_model == cfg.dataset.hf_processor_path
720+
assert cfg.tokenizer.use_tokenizer_vocab_size is True
719721
assert getattr(cfg.model, "tensor_model_parallel_size", 1) >= 1
720722
assert getattr(cfg.model, "pipeline_model_parallel_size", 1) >= 1
721723

tests/unit_tests/recipes/qwen_vl/test_qwen3_vl_recipes.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -600,7 +600,9 @@ def test_each_qwen3_vl_pretrain_mock_recipe_builds_config(recipe_func: Callable,
600600

601601
_assert_basic_config(cfg)
602602

603-
assert cfg.tokenizer.tokenizer_type == "NullTokenizer"
603+
assert cfg.tokenizer.tokenizer_type == "HuggingFaceTokenizer"
604+
assert cfg.tokenizer.tokenizer_model == cfg.dataset.hf_processor_path
605+
assert cfg.tokenizer.use_tokenizer_vocab_size is True
604606
assert getattr(cfg.model, "tensor_model_parallel_size", 1) >= 1
605607
assert getattr(cfg.model, "pipeline_model_parallel_size", 1) >= 1
606608

tests/unit_tests/recipes/test_all_perf_recipe_factories.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@
2121

2222
import pytest
2323

24-
from megatron.bridge.training.config import ConfigContainer, GPTDatasetConfig, MockVLMSFTDatasetConfig
24+
from megatron.bridge.training.config import ConfigContainer, MockVLMSFTDatasetConfig
2525
from tests.unit_tests.recipes.recipe_test_utils import (
2626
discover_recipe_factories,
2727
exported_recipe_factory_keys,
@@ -85,7 +85,6 @@ def test_perf_recipe_factory_builds_config(recipe_factory: Callable[..., object]
8585
):
8686
assert getattr(cfg, section) is not None
8787

88-
if "pretrain" in recipe_factory.__name__ and isinstance(cfg.dataset, GPTDatasetConfig):
89-
assert cfg.tokenizer.use_tokenizer_vocab_size is True
9088
if "pretrain" in recipe_factory.__name__ and isinstance(cfg.dataset, MockVLMSFTDatasetConfig):
91-
assert cfg.tokenizer.use_tokenizer_vocab_size is False
89+
assert cfg.tokenizer.tokenizer_type == "NullTokenizer"
90+
assert cfg.tokenizer.vocab_size == cfg.model.vocab_size

tests/unit_tests/recipes/test_all_recipe_factories.py

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,8 @@
2020

2121
import pytest
2222

23-
from megatron.bridge.training.config import ConfigContainer, GPTDatasetConfig, MockVLMSFTDatasetConfig
23+
from megatron.bridge.recipes.common import _pretrain_common
24+
from megatron.bridge.training.config import ConfigContainer, MockVLMSFTDatasetConfig
2425
from tests.unit_tests.recipes.recipe_test_utils import (
2526
discover_recipe_factories,
2627
exported_recipe_factory_keys,
@@ -65,6 +66,11 @@ def test_all_recipe_factories_are_exported() -> None:
6566
assert getattr(_RECIPES_PACKAGE, factory.__name__) is factory
6667

6768

69+
def test_common_pretrain_uses_runtime_tokenizer_vocabulary() -> None:
70+
"""From-scratch pretraining derives the model vocabulary from its tokenizer."""
71+
assert _pretrain_common().tokenizer.use_tokenizer_vocab_size is True
72+
73+
6874
@pytest.mark.parametrize("recipe_factory", _RUNNABLE_RECIPE_FACTORIES, ids=recipe_factory_id)
6975
def test_recipe_factory_builds_config(recipe_factory: Callable[..., object]) -> None:
7076
"""Every supported recipe can be called with defaults without GPU or network access."""
@@ -88,10 +94,10 @@ def test_recipe_factory_builds_config(recipe_factory: Callable[..., object]) ->
8894
):
8995
assert getattr(cfg, section) is not None
9096

91-
if "pretrain" in recipe_factory.__name__ and isinstance(cfg.dataset, GPTDatasetConfig):
92-
assert cfg.tokenizer.use_tokenizer_vocab_size is True
9397
if "pretrain" in recipe_factory.__name__ and isinstance(cfg.dataset, MockVLMSFTDatasetConfig):
94-
assert cfg.tokenizer.use_tokenizer_vocab_size is False
98+
assert cfg.tokenizer.tokenizer_type == "HuggingFaceTokenizer"
99+
assert cfg.tokenizer.tokenizer_model == cfg.dataset.hf_processor_path
100+
assert cfg.tokenizer.use_tokenizer_vocab_size is True
95101

96102

97103
@pytest.mark.parametrize("recipe_factory", _UNSUPPORTED_RECIPE_FACTORIES, ids=recipe_factory_id)

0 commit comments

Comments
 (0)