2020
2121import pytest
2222import torch
23+ import torch .distributed as dist
24+ from megatron .core import parallel_state
2325from megatron .core .transformer .pipeline_parallel_layer_layout import PipelineParallelLayerLayout
2426from megatron .training .models .base import ModelConfig
2527
@@ -238,7 +240,11 @@ def test_temporary_distributed_context_nccl(self, mock_seed, mock_os, mock_socke
238240 mock_socket_instance .getsockname .return_value = ("localhost" , 12345 )
239241 mock_socket .socket .return_value .__enter__ .return_value = mock_socket_instance
240242
241- with temporary_distributed_context (backend = "nccl" ):
243+ with (
244+ patch ("megatron.bridge.training.model_load_save.torch.cuda.is_available" , return_value = True ),
245+ patch ("megatron.bridge.training.model_load_save.torch.cuda.device_count" , return_value = 1 ),
246+ temporary_distributed_context (backend = "nccl" ),
247+ ):
242248 pass
243249
244250 mock_dist .init_process_group .assert_called_once_with (
@@ -250,6 +256,74 @@ def test_temporary_distributed_context_nccl(self, mock_seed, mock_os, mock_socke
250256 mock_dist .destroy_process_group .assert_called_once ()
251257
252258
259+ class TestGetOrInitializePgCollection :
260+ """Test shared model-parallel process-group setup for Bridge model configs."""
261+
262+ @patch ("megatron.core.tensor_parallel.model_parallel_cuda_manual_seed" )
263+ @patch ("megatron.bridge.training.model_load_save.torch.cuda.device_count" , return_value = 1 )
264+ @patch ("megatron.bridge.training.model_load_save.torch.cuda.is_available" , return_value = True )
265+ @patch ("megatron.bridge.training.model_load_save.ProcessGroupCollection" )
266+ @patch ("megatron.bridge.training.model_load_save.parallel_state" )
267+ def test_initializes_model_parallel_and_cuda_rng (
268+ self ,
269+ mock_parallel_state ,
270+ mock_pg_collection ,
271+ mock_cuda_available ,
272+ mock_cuda_device_count ,
273+ mock_seed ,
274+ ):
275+ """Initialize missing MPU state before returning its process groups."""
276+ model_cfg = SimpleNamespace (
277+ tensor_model_parallel_size = 2 ,
278+ pipeline_model_parallel_size = 4 ,
279+ virtual_pipeline_model_parallel_size = 2 ,
280+ context_parallel_size = 0 ,
281+ expert_model_parallel_size = None ,
282+ expert_tensor_parallel_size = 1 ,
283+ )
284+ expected_pg_collection = Mock ()
285+ mock_parallel_state .is_initialized .return_value = False
286+ mock_pg_collection .use_mpu_process_groups .return_value = expected_pg_collection
287+
288+ result = model_load_save ._get_or_initialize_pg_collection (model_cfg )
289+
290+ assert result is expected_pg_collection
291+ mock_parallel_state .initialize_model_parallel .assert_called_once_with (
292+ tensor_model_parallel_size = 2 ,
293+ pipeline_model_parallel_size = 4 ,
294+ virtual_pipeline_model_parallel_size = 2 ,
295+ context_parallel_size = 1 ,
296+ expert_model_parallel_size = 1 ,
297+ expert_tensor_parallel_size = 1 ,
298+ )
299+ mock_seed .assert_called_once_with (0 )
300+ mock_pg_collection .use_mpu_process_groups .assert_called_once_with ()
301+
302+ @patch ("megatron.core.tensor_parallel.model_parallel_cuda_manual_seed" )
303+ @patch ("megatron.bridge.training.model_load_save.torch.cuda.is_available" , return_value = True )
304+ @patch ("megatron.bridge.training.model_load_save.ProcessGroupCollection" )
305+ @patch ("megatron.bridge.training.model_load_save.parallel_state" )
306+ def test_reuses_initialized_model_parallel_state (
307+ self ,
308+ mock_parallel_state ,
309+ mock_pg_collection ,
310+ mock_cuda_available ,
311+ mock_seed ,
312+ ):
313+ """Do not reinitialize or reseed an existing MPU state."""
314+ model_cfg = Mock ()
315+ expected_pg_collection = Mock ()
316+ mock_parallel_state .is_initialized .return_value = True
317+ mock_pg_collection .use_mpu_process_groups .return_value = expected_pg_collection
318+
319+ result = model_load_save ._get_or_initialize_pg_collection (model_cfg )
320+
321+ assert result is expected_pg_collection
322+ mock_parallel_state .initialize_model_parallel .assert_not_called ()
323+ mock_seed .assert_not_called ()
324+ mock_pg_collection .use_mpu_process_groups .assert_called_once_with ()
325+
326+
253327class TestLoadMegatronModel :
254328 """Test load_megatron_model function."""
255329
@@ -361,6 +435,7 @@ def _finalized_layout(checkpoint_path, model_cfg, *args):
361435 else :
362436 assert result is None
363437
438+ @patch ("megatron.bridge.training.model_load_save._get_or_initialize_pg_collection" )
364439 @patch ("megatron.bridge.training.model_load_save.temporary_distributed_context" )
365440 @patch ("megatron.bridge.training.checkpointing._load_model_weights_from_checkpoint" )
366441 @patch ("megatron.bridge.utils.instantiate_utils.instantiate" )
@@ -377,6 +452,7 @@ def test_load_mbridge_saved_model(
377452 mock_instantiate ,
378453 mock_load_weights ,
379454 mock_temp_dist ,
455+ mock_get_pg_collection ,
380456 ):
381457 # Setup mocks
382458 mock_dist .is_available .return_value = False
@@ -394,6 +470,8 @@ def test_load_mbridge_saved_model(
394470 mock_model_cfg .use_cpu_initialization = False
395471
396472 mock_instantiate .return_value = mock_model_cfg
473+ expected_pg_collection = Mock ()
474+ mock_get_pg_collection .return_value = expected_pg_collection
397475 expected_result = {"layer.weight" : torch .randn (2 , 2 )}
398476 mock_load_weights .return_value = expected_result
399477
@@ -408,32 +486,37 @@ def test_load_mbridge_saved_model(
408486 mock_run_config .assert_called_once ()
409487 mock_instantiate .assert_called_once_with (mock_run_cfg_dict ["model" ])
410488 mock_cpu_context .assert_called_once ()
411- mock_model_cfg .provide_distributed_model .assert_called_once ()
489+ mock_get_pg_collection .assert_called_once_with (mock_model_cfg )
490+ mock_model_cfg .provide_distributed_model .assert_called_once_with (
491+ wrap_with_ddp = False ,
492+ use_cpu_initialization = True ,
493+ pg_collection = expected_pg_collection ,
494+ )
412495 mock_load_weights .assert_called_once_with (ckpt_path , [mock_model ], return_state_dict = True )
413496 assert mock_model_cfg .params_dtype == torch .bfloat16
414497
415498 result = load_megatron_model (ckpt_path , return_state_dict = False , use_cpu_init = True )
416499 assert result == [mock_model ]
417500 mock_load_weights .assert_called_with (ckpt_path , [mock_model ], return_state_dict = False )
418501
502+ @patch ("megatron.bridge.training.model_load_save._get_or_initialize_pg_collection" )
419503 @patch ("megatron.bridge.training.model_load_save.temporary_distributed_context" )
420504 @patch ("megatron.bridge.training.checkpointing._load_model_weights_from_checkpoint" )
421505 @patch ("megatron.bridge.training.checkpointing.read_run_config" )
422506 @patch ("megatron.bridge.training.checkpointing.get_checkpoint_run_config_filename" )
423507 @patch ("megatron.bridge.training.model_load_save.megatron_cpu_init_context" )
424508 @patch ("megatron.bridge.training.model_load_save.dist" )
425- @patch ("megatron.bridge.training.model_load_save.ProcessGroupCollection" )
426509 @patch ("megatron.bridge.training.model_load_save.ModelConfig.from_dict" )
427510 def test_load_mbridge_saved_model_config (
428511 self ,
429512 mock_from_dict ,
430- mock_pg_collection ,
431513 mock_dist ,
432514 mock_cpu_context ,
433515 mock_run_config_fname ,
434516 mock_run_config ,
435517 mock_load_weights ,
436518 mock_temp_dist ,
519+ mock_get_pg_collection ,
437520 ):
438521 """Test loading a model when config yaml contains a serialized ModelConfig instance."""
439522 # Setup mocks
@@ -464,7 +547,7 @@ def test_load_mbridge_saved_model_config(
464547 mock_from_dict .return_value = mock_model_cfg
465548
466549 mock_mpu_pgs = Mock ()
467- mock_pg_collection . use_mpu_process_groups .return_value = mock_mpu_pgs
550+ mock_get_pg_collection .return_value = mock_mpu_pgs
468551
469552 expected_result = {"layer.weight" : torch .randn (2 , 2 )}
470553 mock_load_weights .return_value = expected_result
@@ -482,6 +565,7 @@ def test_load_mbridge_saved_model_config(
482565 mock_cpu_context .assert_called_once ()
483566 mock_model_cfg .finalize .assert_called_once ()
484567 mock_model_cfg .get_builder_cls .assert_called_once ()
568+ mock_get_pg_collection .assert_called_once_with (mock_model_cfg )
485569 mock_builder_cls .assert_called_once_with (mock_model_cfg )
486570 mock_builder .build_distributed_models .assert_called_once_with (
487571 mock_mpu_pgs ,
@@ -494,6 +578,53 @@ def test_load_mbridge_saved_model_config(
494578 assert result == [mock_model ]
495579 mock_load_weights .assert_called_with (ckpt_path , [mock_model ], return_state_dict = False )
496580
581+ @patch ("megatron.bridge.training.checkpointing._load_model_weights_from_checkpoint" )
582+ @patch ("megatron.bridge.training.checkpointing.read_run_config" )
583+ @patch ("megatron.bridge.training.checkpointing.get_checkpoint_run_config_filename" )
584+ @patch ("megatron.bridge.training.model_load_save.ModelConfig.from_dict" )
585+ def test_load_builder_config_initializes_model_parallel_with_existing_default_group (
586+ self ,
587+ mock_from_dict ,
588+ mock_run_config_fname ,
589+ mock_run_config ,
590+ mock_load_weights ,
591+ tmp_path ,
592+ ):
593+ """Load a builder checkpoint when the caller owns only the default process group."""
594+ mock_run_config_fname .return_value = tmp_path / "run_config.yaml"
595+ mock_run_config .return_value = {
596+ "model" : {"tensor_model_parallel_size" : 1 , "_builder_" : "import.path.to.SomeModelBuilder" }
597+ }
598+
599+ mock_model = Mock ()
600+ mock_model_cfg = Mock (spec = GPTModelConfig )
601+ mock_model_cfg .params_dtype = torch .float32
602+ mock_model_cfg .bf16 = False
603+ mock_model_cfg .fp16 = False
604+ mock_model_cfg .use_cpu_initialization = False
605+ mock_model_cfg .finalize = Mock ()
606+ mock_builder = Mock ()
607+ mock_builder .build_distributed_models .return_value = [mock_model ]
608+ mock_model_cfg .get_builder_cls .return_value = Mock (return_value = mock_builder )
609+ mock_from_dict .return_value = mock_model_cfg
610+
611+ (tmp_path / "run_config.yaml" ).touch ()
612+ rendezvous = tmp_path / "gloo_rendezvous"
613+ dist .init_process_group ("gloo" , init_method = f"file://{ rendezvous } " , rank = 0 , world_size = 1 )
614+ try :
615+ assert not parallel_state .is_initialized ()
616+
617+ result = load_megatron_model (tmp_path , use_cpu_init = True )
618+
619+ assert result == [mock_model ]
620+ assert parallel_state .is_initialized ()
621+ mock_builder .build_distributed_models .assert_called_once ()
622+ mock_load_weights .assert_called_once_with (tmp_path , [mock_model ], return_state_dict = False )
623+ finally :
624+ if parallel_state .is_initialized ():
625+ parallel_state .destroy_model_parallel ()
626+ dist .destroy_process_group ()
627+
497628 @pytest .mark .parametrize ("model_type" , ["gpt" , "hybrid" , "mamba" , "resnet" ])
498629 @patch ("megatron.bridge.training.model_load_save.temporary_distributed_context" )
499630 @patch ("megatron.bridge.training.mlm_compat.model._hybrid_provider" )
@@ -582,6 +713,7 @@ def test_load_mlm_saved_model(
582713 with pytest .raises (AssertionError , match = f"model type { model_type } not supported." ):
583714 load_megatron_model (ckpt_path , model_type = model_type , return_state_dict = True , use_cpu_init = True )
584715
716+ @patch ("megatron.bridge.training.model_load_save._get_or_initialize_pg_collection" )
585717 @patch ("megatron.bridge.training.model_load_save.temporary_distributed_context" )
586718 @patch ("megatron.bridge.training.checkpointing._load_model_weights_from_checkpoint" )
587719 @patch ("megatron.bridge.utils.instantiate_utils.instantiate" )
@@ -598,6 +730,7 @@ def test_load_megatron_model_skip_temp_dist_context(
598730 mock_instantiate ,
599731 mock_load_weights ,
600732 mock_temp_dist ,
733+ mock_get_pg_collection ,
601734 ):
602735 """Test loading model when distributed is already initialized."""
603736
@@ -617,6 +750,7 @@ def test_load_megatron_model_skip_temp_dist_context(
617750 mock_model_cfg .use_cpu_initialization = False
618751
619752 mock_instantiate .return_value = mock_model_cfg
753+ mock_get_pg_collection .return_value = Mock ()
620754
621755 with tempfile .TemporaryDirectory () as ckpt_path :
622756 config_file = Path (ckpt_path ) / "run_config.yaml"
@@ -626,6 +760,7 @@ def test_load_megatron_model_skip_temp_dist_context(
626760 assert result == mock_model
627761 mock_temp_dist .assert_not_called ()
628762
763+ @patch ("megatron.bridge.training.model_load_save._get_or_initialize_pg_collection" )
629764 @patch ("megatron.bridge.training.model_load_save.temporary_distributed_context" )
630765 @patch ("megatron.bridge.training.post_training.checkpointing.load_modelopt_state" )
631766 @patch ("megatron.bridge.training.post_training.checkpointing.has_modelopt_state" )
@@ -646,6 +781,7 @@ def test_load_mbridge_saved_model_with_modelopt_state(
646781 mock_has_modelopt_state ,
647782 mock_load_modelopt_state ,
648783 mock_temp_dist ,
784+ mock_get_pg_collection ,
649785 ):
650786 """Test loading model when modelopt state exists and model supports it."""
651787 # Setup mocks
@@ -665,6 +801,7 @@ def test_load_mbridge_saved_model_with_modelopt_state(
665801 mock_model_cfg .restore_modelopt_state = False # Initially False
666802
667803 mock_instantiate .return_value = mock_model_cfg
804+ mock_get_pg_collection .return_value = Mock ()
668805 expected_result = {"layer.weight" : torch .randn (2 , 2 )}
669806 mock_load_weights .return_value = expected_result
670807
0 commit comments