@@ -582,6 +582,87 @@ def test_create_input_data_channel_with_instance_group_names(model_trainer):
582582 ]
583583
584584
585+ @patch ("sagemaker.train.model_trainer.ModelTrainer._resolve_staging_bucket" )
586+ @patch ("sagemaker.train.model_trainer.Session.upload_data" )
587+ @patch ("sagemaker.train.model_trainer.Session.default_bucket" )
588+ def test_create_input_data_channel_default_prefix_uses_base_job_name (
589+ mock_default_bucket , mock_upload_data , mock_staging , model_trainer
590+ ):
591+ """Without input_s3_key_prefix, the upload key prefix is derived from base_job_name (unchanged)."""
592+ mock_default_bucket .return_value = DEFAULT_BUCKET
593+ mock_staging .return_value = (DEFAULT_BUCKET , None )
594+ mock_upload_data .return_value = f"s3://{ DEFAULT_BUCKET } /code"
595+
596+ assert model_trainer .input_s3_key_prefix is None
597+ model_trainer .create_input_data_channel ("code" , DEFAULT_SOURCE_DIR )
598+
599+ # Leading segment is derived from base_job_name when input_s3_key_prefix is unset.
600+ assert mock_upload_data .call_args .kwargs ["key_prefix" ] == f"{ DEFAULT_BASE_NAME } /input/code"
601+
602+
603+ @patch ("sagemaker.train.model_trainer.ModelTrainer._resolve_staging_bucket" )
604+ @patch ("sagemaker.train.model_trainer.Session.upload_data" )
605+ @patch ("sagemaker.train.model_trainer.Session.default_bucket" )
606+ def test_create_input_data_channel_custom_input_s3_key_prefix (
607+ mock_default_bucket , mock_upload_data , mock_staging
608+ ):
609+ """input_s3_key_prefix replaces base_job_name as the leading key prefix (issue #5638)."""
610+ mock_default_bucket .return_value = DEFAULT_BUCKET
611+ mock_staging .return_value = (DEFAULT_BUCKET , None )
612+ mock_upload_data .return_value = f"s3://{ DEFAULT_BUCKET } /code"
613+
614+ trainer = ModelTrainer (
615+ training_image = DEFAULT_IMAGE ,
616+ role = DEFAULT_ROLE ,
617+ compute = DEFAULT_COMPUTE_CONFIG ,
618+ stopping_condition = DEFAULT_STOPPING_CONDITION ,
619+ output_data_config = DEFAULT_OUTPUT_DATA_CONFIG ,
620+ input_s3_key_prefix = "my-pipeline/my-step" ,
621+ )
622+ trainer .create_input_data_channel ("code" , DEFAULT_SOURCE_DIR )
623+
624+ # input_s3_key_prefix replaces base_job_name as the leading segment.
625+ assert mock_upload_data .call_args .kwargs ["key_prefix" ] == "my-pipeline/my-step/input/code"
626+ assert f"{ DEFAULT_BASE_NAME } /input/code" not in mock_upload_data .call_args .kwargs ["key_prefix" ]
627+
628+
629+ @patch ("sagemaker.train.model_trainer.ModelTrainer._resolve_staging_bucket" )
630+ @patch ("sagemaker.train.model_trainer.Session.upload_data" )
631+ @patch ("sagemaker.train.model_trainer.Session.default_bucket" )
632+ def test_create_training_job_args_input_s3_key_prefix (
633+ mock_default_bucket , mock_upload_data , mock_staging
634+ ):
635+ """Managed sm_drivers/code channels use input_s3_key_prefix in their S3 URIs (issue #5638)."""
636+ mock_default_bucket .return_value = DEFAULT_BUCKET
637+ mock_staging .return_value = (DEFAULT_BUCKET , None )
638+
639+ def _echo (path , bucket , key_prefix , extra_args = None ):
640+ return f"s3://{ bucket } /{ key_prefix } "
641+
642+ mock_upload_data .side_effect = _echo
643+
644+ trainer = ModelTrainer (
645+ training_image = DEFAULT_IMAGE ,
646+ role = DEFAULT_ROLE ,
647+ source_code = DEFAULT_SOURCE_CODE ,
648+ compute = DEFAULT_COMPUTE_CONFIG ,
649+ stopping_condition = DEFAULT_STOPPING_CONDITION ,
650+ output_data_config = DEFAULT_OUTPUT_DATA_CONFIG ,
651+ input_s3_key_prefix = "my-pipeline" ,
652+ )
653+ args = trainer ._create_training_job_args ()
654+ uris = {
655+ channel .channel_name : channel .data_source .s3_data_source .s3_uri
656+ for channel in args ["input_data_config" ]
657+ if channel .data_source and channel .data_source .s3_data_source
658+ }
659+
660+ # The SDK-managed sm_drivers channel is staged from a local temp dir, so its S3 URI
661+ # is built from the input key prefix -> it must lead with the custom pipeline prefix.
662+ assert "/my-pipeline/" in uris ["sm_drivers" ]
663+ assert f"/{ DEFAULT_BASE_NAME } /input" not in uris ["sm_drivers" ]
664+
665+
585666HETEROGENEOUS_INSTANCE_GROUPS = [
586667 InstanceGroup (
587668 instance_type = "ml.t3.large" , instance_count = 1 , instance_group_name = "head-instance-group"
0 commit comments