From dbda3de2c5dc75a26b89f078e06ee6fb70f9be54 Mon Sep 17 00:00:00 2001 From: reginaldalfret Date: Fri, 9 Oct 2026 14:58:34 +0530 Subject: [PATCH] fix(train): omit input_data_config when no input channels are provided When a ModelTrainer has no input channels specified, _create_training_job_args serialized an empty list ('input_data_config: []'). Downstream CreatePipeline rejects an empty InputDataConfig array with ValidationException because the botocore service model enforces min=1 on InputDataConfig when present. Omit input_data_config from training_request when final_input_data_config is empty, allowing the SageMaker API and CreatePipeline to succeed as in v2. Fixes #6156 Signed-off-by: reginaldalfret --- .../src/sagemaker/train/model_trainer.py | 6 ++- .../tests/unit/train/test_model_trainer.py | 40 +++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/sagemaker-train/src/sagemaker/train/model_trainer.py b/sagemaker-train/src/sagemaker/train/model_trainer.py index 8816ea68f9..53b70ec515 100644 --- a/sagemaker-train/src/sagemaker/train/model_trainer.py +++ b/sagemaker-train/src/sagemaker/train/model_trainer.py @@ -851,7 +851,6 @@ def _create_training_job_args( "training_job_name": current_training_job_name, "algorithm_specification": algorithm_specification, "hyper_parameters": string_hyper_parameters, - "input_data_config": final_input_data_config, "resource_config": resource_config, "vpc_config": vpc_config, "role_arn": self.role, @@ -876,6 +875,9 @@ def _create_training_job_args( "session_chaining_config": self._session_chaining_config, } + if final_input_data_config: + training_request["input_data_config"] = final_input_data_config + if self.model_package_config: training_request["model_package_config"] = self.model_package_config @@ -995,7 +997,7 @@ def train( "algorithm_specification" ].container_entrypoint, container_arguments=training_request["algorithm_specification"].container_arguments, - input_data_config=training_request["input_data_config"], + input_data_config=training_request.get("input_data_config"), hyper_parameters=training_request["hyper_parameters"], environment=training_request["environment"], ) diff --git a/sagemaker-train/tests/unit/train/test_model_trainer.py b/sagemaker-train/tests/unit/train/test_model_trainer.py index f990b7ab88..2a3a4a965b 100644 --- a/sagemaker-train/tests/unit/train/test_model_trainer.py +++ b/sagemaker-train/tests/unit/train/test_model_trainer.py @@ -663,6 +663,46 @@ def _echo(path, bucket, key_prefix, extra_args=None): assert f"/{DEFAULT_BASE_NAME}/input" not in uris["sm_drivers"] +def test_create_training_job_args_omits_empty_input_data_config(): + """ModelTrainer with no input channels omits input_data_config to avoid ValidationException (issue #6156).""" + trainer = ModelTrainer( + training_image=DEFAULT_IMAGE, + role=DEFAULT_ROLE, + compute=DEFAULT_COMPUTE_CONFIG, + stopping_condition=DEFAULT_STOPPING_CONDITION, + output_data_config=DEFAULT_OUTPUT_DATA_CONFIG, + ) + args = trainer._create_training_job_args() + assert "input_data_config" not in args + + +def test_create_training_job_args_includes_provided_input_data_config(): + """ModelTrainer with input channels includes input_data_config in training args.""" + channel = Channel( + channel_name="train", + data_source=DataSource( + s3_data_source=S3DataSource( + s3_data_type="S3Prefix", + s3_uri=f"s3://{DEFAULT_BUCKET}/data", + s3_data_distribution_type="FullyReplicated", + ) + ), + ) + trainer = ModelTrainer( + training_image=DEFAULT_IMAGE, + role=DEFAULT_ROLE, + compute=DEFAULT_COMPUTE_CONFIG, + stopping_condition=DEFAULT_STOPPING_CONDITION, + output_data_config=DEFAULT_OUTPUT_DATA_CONFIG, + input_data_config=[channel], + ) + args = trainer._create_training_job_args() + assert "input_data_config" in args + assert len(args["input_data_config"]) == 1 + assert args["input_data_config"][0].channel_name == "train" + + + HETEROGENEOUS_INSTANCE_GROUPS = [ InstanceGroup( instance_type="ml.t3.large", instance_count=1, instance_group_name="head-instance-group"