Skip to content

Commit 0495265

Browse files
fix(train): set input_data_config to None (#6154)
* fix(model_trainer): set input_data_config to None when no sources are provided When no input channels are provided, input_data_config must be None rather than an empty list to avoid SageMaker API validation errors. * fix(train): preserve empty input channels in local mode --------- Co-authored-by: Mohamed Zeidan <81834882+mohamedzeidan2021@users.noreply.github.com>
1 parent e98739b commit 0495265

2 files changed

Lines changed: 60 additions & 3 deletions

File tree

‎sagemaker-train/src/sagemaker/train/model_trainer.py‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -851,7 +851,7 @@ def _create_training_job_args(
851851
"training_job_name": current_training_job_name,
852852
"algorithm_specification": algorithm_specification,
853853
"hyper_parameters": string_hyper_parameters,
854-
"input_data_config": final_input_data_config,
854+
"input_data_config": (final_input_data_config if final_input_data_config else None),
855855
"resource_config": resource_config,
856856
"vpc_config": vpc_config,
857857
"role_arn": self.role,
@@ -995,7 +995,7 @@ def train(
995995
"algorithm_specification"
996996
].container_entrypoint,
997997
container_arguments=training_request["algorithm_specification"].container_arguments,
998-
input_data_config=training_request["input_data_config"],
998+
input_data_config=training_request["input_data_config"] or [],
999999
hyper_parameters=training_request["hyper_parameters"],
10001000
environment=training_request["environment"],
10011001
)

‎sagemaker-train/tests/unit/train/test_model_trainer.py‎

Lines changed: 58 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -394,7 +394,7 @@ def test_train_with_intelligent_defaults_training_job_space(
394394
training_job_name=ANY,
395395
algorithm_specification=ANY,
396396
hyper_parameters={},
397-
input_data_config=[],
397+
input_data_config=None,
398398
resource_config=ResourceConfig(
399399
volume_size_in_gb=30,
400400
instance_type="ml.m5.xlarge",
@@ -434,6 +434,63 @@ def test_train_with_intelligent_defaults_training_job_space(
434434
training_job_instance.wait.assert_called_once_with(logs=True)
435435

436436

437+
@pytest.mark.parametrize(
438+
"train_kwargs",
439+
[{}, {"input_data_config": None}, {"input_data_config": []}],
440+
ids=["omitted", "none", "empty-list"],
441+
)
442+
@patch("sagemaker.train.local.local_container.check_for_studio", return_value=False)
443+
@patch("sagemaker.train.model_trainer._LocalContainer.train", autospec=True)
444+
def test_train_local_without_input_data(mock_train, mock_check_for_studio, train_kwargs, tmp_path):
445+
trainer = ModelTrainer(
446+
training_image=DEFAULT_IMAGE,
447+
training_mode=Mode.LOCAL_CONTAINER,
448+
compute=DEFAULT_COMPUTE_CONFIG,
449+
local_container_root=str(tmp_path),
450+
)
451+
452+
trainer.train(**train_kwargs)
453+
454+
mock_train.assert_called_once_with(ANY, True)
455+
local_container = mock_train.call_args.args[0]
456+
assert local_container.input_data_config == []
457+
assert local_container.input_from_s3 is False
458+
459+
460+
@pytest.mark.parametrize(
461+
"train_kwargs",
462+
[{}, {"input_data_config": None}, {"input_data_config": []}],
463+
ids=["omitted", "none", "empty-list"],
464+
)
465+
@patch("sagemaker.train.model_trainer.TrainingJob")
466+
def test_train_managed_without_input_data(mock_training_job, train_kwargs, model_trainer):
467+
model_trainer.train(**train_kwargs)
468+
469+
mock_training_job.create.assert_called_once()
470+
assert mock_training_job.create.call_args.kwargs["input_data_config"] is None
471+
472+
473+
@patch("sagemaker.train.local.local_container.check_for_studio", return_value=False)
474+
@patch("sagemaker.train.model_trainer._LocalContainer.train", autospec=True)
475+
def test_train_local_preserves_input_data(mock_train, mock_check_for_studio, tmp_path):
476+
trainer = ModelTrainer(
477+
training_image=DEFAULT_IMAGE,
478+
training_mode=Mode.LOCAL_CONTAINER,
479+
compute=DEFAULT_COMPUTE_CONFIG,
480+
local_container_root=str(tmp_path),
481+
)
482+
input_data = InputData(channel_name="train", data_source=str(tmp_path))
483+
484+
trainer.train(input_data_config=[input_data])
485+
486+
mock_train.assert_called_once_with(ANY, True)
487+
local_container = mock_train.call_args.args[0]
488+
assert len(local_container.input_data_config) == 1
489+
channel = local_container.input_data_config[0]
490+
assert channel.channel_name == "train"
491+
assert channel.data_source.file_system_data_source.directory_path == str(tmp_path)
492+
493+
437494
@patch("sagemaker.train.model_trainer.TrainingJob")
438495
@patch.object(ModelTrainer, "_get_input_data_config")
439496
def test_train_with_input_data_channels(mock_get_input_config, mock_training_job, model_trainer):

0 commit comments

Comments
 (0)