|
22 | 22 |
|
23 | 23 | import importlib |
24 | 24 | import inspect |
| 25 | +from types import SimpleNamespace |
25 | 26 | from typing import Callable |
26 | 27 |
|
27 | 28 | import pytest |
|
108 | 109 | _QWEN35_VL_GB200_FUNCS = [ |
109 | 110 | _qwen35_vl_gb200_module.qwen35_vl_27b_pretrain_16gpu_gb200_bf16_mock_config, |
110 | 111 | _qwen35_vl_gb200_module.qwen35_vl_35b_a3b_sft_8gpu_gb200_bf16_functional_config, |
| 112 | + _qwen35_vl_gb200_module.qwen35_vl_35b_a3b_sft_long_context_32gpu_gb200_bf16_config, |
| 113 | + _qwen35_vl_gb200_module.qwen35_vl_35b_a3b_sft_long_context_32gpu_gb200_fp8mx_config, |
111 | 114 | _qwen35_vl_gb200_module.qwen35_vl_35b_a3b_peft_8gpu_gb200_bf16_functional_config, |
112 | 115 | ] |
113 | 116 |
|
@@ -148,7 +151,7 @@ class _FakeAutoBridge: |
148 | 151 | """Fake AutoBridge for testing.""" |
149 | 152 |
|
150 | 153 | @staticmethod |
151 | | - def from_hf_pretrained(hf_path: str): |
| 154 | + def from_hf_pretrained(hf_path: str, *, revision: str | None = None): |
152 | 155 | return _FakeAutoBridge() |
153 | 156 |
|
154 | 157 | def to_megatron_provider(self, load_weights: bool = False): |
@@ -441,7 +444,7 @@ def test_qwen35_vl_27b_peft_lora_defaults(monkeypatch: pytest.MonkeyPatch): |
441 | 444 |
|
442 | 445 | def test_qwen35_vl_397b_a17b_pretrain_64gpu_gb300_defaults(monkeypatch: pytest.MonkeyPatch): |
443 | 446 | """The 64-GB300 library pretrain recipe should own the measured execution policy.""" |
444 | | - patch_recipe_module_global(monkeypatch, _qwen35_vl_gb300_module, "AutoBridge", _FakeAutoBridge) |
| 447 | + patch_recipe_module_global(monkeypatch, _qwen35_vl_h100_module, "AutoBridge", _FakeAutoBridge) |
445 | 448 |
|
446 | 449 | cfg = _qwen35_vl_gb300_module.qwen35_vl_397b_a17b_pretrain_config() |
447 | 450 |
|
@@ -472,7 +475,7 @@ def test_qwen35_vl_397b_a17b_pretrain_64gpu_gb300_defaults(monkeypatch: pytest.M |
472 | 475 |
|
473 | 476 | def test_qwen35_vl_35b_a3b_pretrain_16gpu_gb300_defaults(monkeypatch: pytest.MonkeyPatch): |
474 | 477 | """The 16-GB300 library pretrain recipe should own the measured execution policy.""" |
475 | | - patch_recipe_module_global(monkeypatch, _qwen35_vl_gb300_module, "AutoBridge", _FakeAutoBridge) |
| 478 | + patch_recipe_module_global(monkeypatch, _qwen35_vl_h100_module, "AutoBridge", _FakeAutoBridge) |
476 | 479 |
|
477 | 480 | cfg = _qwen35_vl_gb300_module.qwen35_vl_35b_a3b_pretrain_16gpu_gb300_bf16_config() |
478 | 481 |
|
@@ -659,6 +662,83 @@ def test_qwen35_vl_35b_a3b_long_context_sft_defaults(monkeypatch: pytest.MonkeyP |
659 | 662 | assert cfg.ddp.average_in_collective is False |
660 | 663 |
|
661 | 664 |
|
| 665 | +def test_qwen35_vl_35b_a3b_gb200_long_context_precision_pair(monkeypatch: pytest.MonkeyPatch): |
| 666 | + """The GB200 BF16 and MXFP8 recipes should share one 128K execution topology.""" |
| 667 | + from megatron.bridge.data.builders import EnergonDatasetConfig, QwenVLEnergonTaskEncoderConfig |
| 668 | + from megatron.bridge.models.qwen_vl.qwen35_vl_provider import Qwen35VLMoEModelProvider |
| 669 | + |
| 670 | + monkeypatch.setattr( |
| 671 | + _FakeAutoBridge, |
| 672 | + "to_megatron_provider", |
| 673 | + lambda self, load_weights=False: Qwen35VLMoEModelProvider( |
| 674 | + num_layers=40, hidden_size=2048, num_attention_heads=16, bias_activation_fusion=False |
| 675 | + ), |
| 676 | + ) |
| 677 | + patch_recipe_module_global(monkeypatch, _qwen35_vl_gb200_module, "AutoBridge", _FakeAutoBridge) |
| 678 | + |
| 679 | + bf16_cfg = _qwen35_vl_gb200_module.qwen35_vl_35b_a3b_sft_long_context_32gpu_gb200_bf16_config() |
| 680 | + fp8mx_cfg = _qwen35_vl_gb200_module.qwen35_vl_35b_a3b_sft_long_context_32gpu_gb200_fp8mx_config() |
| 681 | + |
| 682 | + for cfg in (bf16_cfg, fp8mx_cfg): |
| 683 | + _assert_basic_config(cfg) |
| 684 | + assert cfg.model.seq_length == 131072 |
| 685 | + assert cfg.model.bias_activation_fusion is True |
| 686 | + assert cfg.model.tensor_model_parallel_size == 2 |
| 687 | + assert cfg.model.pipeline_model_parallel_size == 1 |
| 688 | + assert cfg.model.pipeline_dtype is None |
| 689 | + assert cfg.model.virtual_pipeline_model_parallel_size is None |
| 690 | + assert cfg.model.context_parallel_size == 8 |
| 691 | + assert cfg.model.expert_model_parallel_size == 16 |
| 692 | + assert cfg.model.expert_tensor_parallel_size == 1 |
| 693 | + assert cfg.model.sequence_parallel is True |
| 694 | + assert cfg.model.moe_token_dispatcher_type == "flex" |
| 695 | + assert cfg.model.moe_flex_dispatcher_backend == "hybridep" |
| 696 | + assert cfg.model.moe_flex_dispatcher_num_sms == 32 |
| 697 | + assert cfg.model.moe_hybridep_pad_uneven_dispatch_inputs is True |
| 698 | + assert cfg.model.gdn_pre_gated_delta_rule_fusion is False |
| 699 | + assert cfg.model.cross_entropy_fusion_impl == "te" |
| 700 | + assert cfg.model.recompute_granularity == "selective" |
| 701 | + assert cfg.model.recompute_modules == ["gdn_norm_out", "moe"] |
| 702 | + assert cfg.model.recompute_method is None |
| 703 | + assert cfg.model.recompute_num_layers is None |
| 704 | + assert cfg.model.vision_recompute_granularity == "full" |
| 705 | + assert cfg.model.vision_recompute_method == "uniform" |
| 706 | + assert cfg.model.vision_recompute_num_layers == 1 |
| 707 | + assert cfg.model.vision_recompute_modules is None |
| 708 | + assert cfg.model.attention_backend.name == "auto" |
| 709 | + for variable in ("NVTE_FUSED_ATTN", "NVTE_FLASH_ATTN", "NVTE_UNFUSED_ATTN"): |
| 710 | + assert cfg.env_vars[variable] == 1 |
| 711 | + assert cfg.train.global_batch_size == 32 |
| 712 | + assert cfg.train.micro_batch_size == 1 |
| 713 | + assert cfg.dataset.seq_length == 131072 |
| 714 | + assert cfg.dataset.enable_in_batch_packing is False |
| 715 | + assert cfg.dataset.defer_in_batch_packing_to_step is False |
| 716 | + assert isinstance(cfg.dataset, EnergonDatasetConfig) |
| 717 | + assert cfg.dataset.path is None # Caller supplies prepared CLEVR2 shards. |
| 718 | + assert cfg.dataset.micro_batch_size == 1 |
| 719 | + assert cfg.dataset.num_workers == 1 |
| 720 | + assert cfg.dataset.shuffle_buffer_size == 2 |
| 721 | + assert cfg.dataset.packing_buffer_size == 8 |
| 722 | + assert cfg.dataset.dataset_kwargs == {"image_decode_spec": "pilrgb"} |
| 723 | + encoder = cfg.dataset.task_encoder |
| 724 | + assert isinstance(encoder, QwenVLEnergonTaskEncoderConfig) |
| 725 | + assert encoder.hf_processor_path == "Qwen/Qwen3.5-35B-A3B" |
| 726 | + assert encoder.hf_processor_revision == _qwen35_vl_gb200_module._QWEN35_35B_A3B_REVISION |
| 727 | + assert encoder.min_pixels == 200704 |
| 728 | + assert encoder.max_pixels == 401408 |
| 729 | + assert encoder.max_num_images == 4 |
| 730 | + assert encoder.max_visual_tokens == 2048 |
| 731 | + assert cfg.mixed_precision.grad_reduce_in_fp32 is True |
| 732 | + assert cfg.ddp.grad_reduce_in_fp32 is True |
| 733 | + assert cfg.env_vars["CUDA_DEVICE_MAX_CONNECTIONS"] == 1 |
| 734 | + assert cfg.env_vars["NUM_OF_HYBRID_EP_RANKS_PER_NVLINK_DOMAIN"] == 16 |
| 735 | + |
| 736 | + assert bf16_cfg.mixed_precision.fp8 is None |
| 737 | + assert fp8mx_cfg.mixed_precision.fp8_recipe == "mxfp8" |
| 738 | + assert fp8mx_cfg.mixed_precision.fp8_param_gather is False |
| 739 | + assert fp8mx_cfg.mixed_precision.reuse_grad_buf_for_mxfp8_param_ag is False |
| 740 | + |
| 741 | + |
662 | 742 | def test_qwen35_vl_35b_a3b_fsdp_sft_defaults(monkeypatch: pytest.MonkeyPatch): |
663 | 743 | """35B-A3B FSDP SFT should have FSDP-specific parallelism and settings.""" |
664 | 744 | patch_recipe_module_global(monkeypatch, _qwen35_vl_module, "AutoBridge", _FakeAutoBridge) |
@@ -1453,3 +1533,55 @@ def test_qwen35_vl_gdn_conv_fusion_skipped_on_older_core(monkeypatch: pytest.Mon |
1453 | 1533 | cfg = _qwen35_vl_module.qwen35_vl_9b_pretrain_mock_config() |
1454 | 1534 |
|
1455 | 1535 | assert not hasattr(cfg.model, "gdn_pre_gated_delta_rule_fusion") |
| 1536 | + |
| 1537 | + |
| 1538 | +@pytest.mark.parametrize( |
| 1539 | + "recipe_func", |
| 1540 | + [ |
| 1541 | + _qwen35_vl_gb200_module.qwen35_vl_35b_a3b_sft_long_context_32gpu_gb200_bf16_config, |
| 1542 | + _qwen35_vl_gb200_module.qwen35_vl_35b_a3b_sft_long_context_32gpu_gb200_fp8mx_config, |
| 1543 | + ], |
| 1544 | +) |
| 1545 | +def test_gb200_long_context_validates_with_real_provider(monkeypatch, recipe_func): |
| 1546 | + from megatron.bridge.models.qwen_vl.qwen35_vl_provider import Qwen35VLMoEModelProvider |
| 1547 | + |
| 1548 | + monkeypatch.setattr( |
| 1549 | + torch.cuda, "get_device_properties", lambda index: SimpleNamespace(major=10, name="NVIDIA GB200") |
| 1550 | + ) |
| 1551 | + |
| 1552 | + config_module = importlib.import_module("megatron.bridge.training.config") |
| 1553 | + monkeypatch.setattr(config_module, "get_world_size_safe", lambda: 32) |
| 1554 | + monkeypatch.setattr( |
| 1555 | + _FakeAutoBridge, |
| 1556 | + "to_megatron_provider", |
| 1557 | + lambda self, load_weights=False: Qwen35VLMoEModelProvider( |
| 1558 | + num_layers=40, hidden_size=2048, num_attention_heads=16 |
| 1559 | + ), |
| 1560 | + ) |
| 1561 | + patch_recipe_module_global(monkeypatch, _qwen35_vl_gb200_module, "AutoBridge", _FakeAutoBridge) |
| 1562 | + config = recipe_func() |
| 1563 | + |
| 1564 | + config.dataset.path = "prepared-clevr2-energon" |
| 1565 | + config.dataset.validate() |
| 1566 | + config.validate() |
| 1567 | + assert config.dataset.pad_to_max_length is True # Derived for HybridEP. |
| 1568 | + |
| 1569 | + assert config.train.global_batch_size % (2 * config.train.micro_batch_size) == 0 |
| 1570 | + config.dataset.enable_in_batch_packing = True |
| 1571 | + with pytest.raises(ValueError, match="mutually exclusive"): |
| 1572 | + config.dataset.validate() |
| 1573 | + |
| 1574 | + |
| 1575 | +def test_gb200_long_context_pins_model_and_processor_together(monkeypatch): |
| 1576 | + calls = [] |
| 1577 | + |
| 1578 | + def from_hf_pretrained(hf_path, **kwargs): |
| 1579 | + calls.append((hf_path, kwargs)) |
| 1580 | + return _FakeAutoBridge() |
| 1581 | + |
| 1582 | + monkeypatch.setattr(_FakeAutoBridge, "from_hf_pretrained", from_hf_pretrained) |
| 1583 | + patch_recipe_module_global(monkeypatch, _qwen35_vl_gb200_module, "AutoBridge", _FakeAutoBridge) |
| 1584 | + recipe = _qwen35_vl_gb200_module.qwen35_vl_35b_a3b_sft_long_context_32gpu_gb200_bf16_config |
| 1585 | + cfg = recipe() |
| 1586 | + encoder = cfg.dataset.task_encoder |
| 1587 | + assert calls == [(encoder.hf_processor_path, {"revision": encoder.hf_processor_revision})] |
0 commit comments