Skip to content

Commit d07c756

Browse files
author
nayan3107
committed
fix(train): add PipelineSession support to V3 trainers (SFT/DPO/RLAIF/RLVR)
When a PipelineSession is passed as sagemaker_session, the serverless training path in SFTTrainer, DPOTrainer, RLAIFTrainer, and RLVRTrainer now intercepts the CreateTrainingJob request and returns step arguments instead of immediately launching a training job. This enables V3 trainers to be used with SageMaker Pipelines TrainingStep, matching the existing behavior of ModelTrainer, Processor, Transformer, and HyperparameterTuner. The fix follows the established SDK pattern: isinstance check for PipelineSession, call _intercept_create_request with the request args, and return session.context (the captured step arguments). Fixes: #6163
1 parent 17b2679 commit d07c756

8 files changed

Lines changed: 311 additions & 1 deletion

File tree

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
from sagemaker.train.common import TrainingType, CustomizationTechnique, JOB_TYPE
66
from sagemaker.core.resources import TrainingJob, ModelPackageGroup, ModelPackage
77
from sagemaker.core.shapes import VpcConfig
8+
from sagemaker.core.workflow.pipeline_context import PipelineSession
89
from sagemaker.train.defaults import TrainDefaults
910
from sagemaker.train.utils import _get_unique_name, _get_jumpstart_tags
1011
from sagemaker.train.configs import StoppingCondition
@@ -347,6 +348,12 @@ def train(self,
347348
if self.stopping_condition is not None:
348349
create_args["stopping_condition"] = self.stopping_condition
349350

351+
# If running within a PipelineSession, intercept the request and store
352+
# step arguments instead of launching a training job.
353+
if isinstance(sagemaker_session, PipelineSession):
354+
sagemaker_session._intercept_create_request(create_args, None, "train")
355+
return sagemaker_session.context
356+
350357
try:
351358
training_job = TrainingJob.create(**create_args)
352359
except Exception as e:

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from sagemaker.train.common import TrainingType, CustomizationTechnique, JOB_TYPE
55
from sagemaker.core.resources import TrainingJob, ModelPackageGroup, MlflowTrackingServer, ModelPackage
66
from sagemaker.core.shapes import VpcConfig
7+
from sagemaker.core.workflow.pipeline_context import PipelineSession
78
from sagemaker.train.defaults import TrainDefaults
89
from sagemaker.train.utils import _get_unique_name, _get_jumpstart_tags
910
from sagemaker.train.common_utils.recipe_utils import _get_hub_content_metadata
@@ -310,6 +311,12 @@ def train(self, training_dataset: Optional[Union[str, DataSet]] = None, validati
310311
if self.stopping_condition is not None:
311312
create_args["stopping_condition"] = self.stopping_condition
312313

314+
# If running within a PipelineSession, intercept the request and store
315+
# step arguments instead of launching a training job.
316+
if isinstance(sagemaker_session, PipelineSession):
317+
sagemaker_session._intercept_create_request(create_args, None, "train")
318+
return sagemaker_session.context
319+
313320
try:
314321
training_job = TrainingJob.create(**create_args)
315322
except Exception as e:

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
from sagemaker.train.common import TrainingType, CustomizationTechnique, JOB_TYPE
77
from sagemaker.core.resources import TrainingJob, ModelPackageGroup, MlflowTrackingServer, ModelPackage
88
from sagemaker.core.shapes import VpcConfig
9+
from sagemaker.core.workflow.pipeline_context import PipelineSession
910
from sagemaker.train.defaults import TrainDefaults
1011
from sagemaker.train.utils import _get_unique_name, _get_jumpstart_tags
1112
from sagemaker.ai_registry.dataset import DataSet
@@ -518,6 +519,12 @@ def train(self, training_dataset: Optional[Union[str, DataSet]] = None,
518519
if self.stopping_condition is not None:
519520
create_args["stopping_condition"] = self.stopping_condition
520521

522+
# If running within a PipelineSession, intercept the request and store
523+
# step arguments instead of launching a training job.
524+
if isinstance(sagemaker_session, PipelineSession):
525+
sagemaker_session._intercept_create_request(create_args, None, "train")
526+
return sagemaker_session.context
527+
521528
try:
522529
training_job = TrainingJob.create(**create_args)
523530
except Exception as e:

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
from sagemaker.train.common import TrainingType, CustomizationTechnique, JOB_TYPE
55
from sagemaker.core.resources import TrainingJob, ModelPackageGroup, ModelPackage
66
from sagemaker.core.shapes import VpcConfig
7+
from sagemaker.core.workflow.pipeline_context import PipelineSession
78
from sagemaker.train.defaults import TrainDefaults
89
from sagemaker.train.utils import _get_unique_name, _get_jumpstart_tags
910
from sagemaker.ai_registry.dataset import DataSet
@@ -414,6 +415,12 @@ def train(self, training_dataset: Optional[Union[str, DataSet]] = None, validati
414415
if self.stopping_condition is not None:
415416
create_args["stopping_condition"] = self.stopping_condition
416417

418+
# If running within a PipelineSession, intercept the request and store
419+
# step arguments instead of launching a training job.
420+
if isinstance(sagemaker_session, PipelineSession):
421+
sagemaker_session._intercept_create_request(create_args, None, "train")
422+
return sagemaker_session.context
423+
417424
try:
418425
training_job = TrainingJob.create(**create_args)
419426
except Exception as e:

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

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -621,3 +621,73 @@ def test_s3_model_without_base_model_name_raises(self, mock_resolve, mock_valida
621621
compute=HyperPodCompute(cluster_name="my-cluster", node_count=4),
622622
training_dataset="s3://bucket/train.jsonl",
623623
)
624+
625+
626+
class TestDPOTrainerPipelineSession:
627+
"""Test DPOTrainer behavior when PipelineSession is used.
628+
629+
Ref: https://github.com/aws/sagemaker-python-sdk/issues/6163
630+
"""
631+
632+
@patch('sagemaker.train.dpo_trainer._create_model_package_config')
633+
@patch('sagemaker.train.dpo_trainer._create_mlflow_config')
634+
@patch('sagemaker.train.dpo_trainer._create_output_config')
635+
@patch('sagemaker.train.dpo_trainer._create_serverless_config')
636+
@patch('sagemaker.train.dpo_trainer._convert_input_data_to_channels')
637+
@patch('sagemaker.train.dpo_trainer._create_input_data_config')
638+
@patch('sagemaker.train.dpo_trainer._get_unique_name')
639+
@patch('sagemaker.train.dpo_trainer.TrainDefaults.get_role')
640+
@patch('sagemaker.train.dpo_trainer.TrainDefaults.get_sagemaker_session')
641+
@patch('sagemaker.train.dpo_trainer._validate_and_resolve_model_package_group')
642+
@patch('sagemaker.train.dpo_trainer._get_fine_tuning_options_and_model_arn')
643+
@patch('sagemaker.train.dpo_trainer._resolve_model_and_name')
644+
@patch('sagemaker.train.common_utils.finetune_utils._get_beta_session')
645+
@patch('sagemaker.core.resources.TrainingJob.create')
646+
def test_train_with_pipeline_session_does_not_launch_job(
647+
self, mock_training_job_create, mock_beta_session, mock_resolve_model,
648+
mock_finetuning_options, mock_validate_group, mock_get_session, mock_get_role,
649+
mock_unique_name, mock_input_config, mock_convert_channels,
650+
mock_serverless_config, mock_output_config, mock_mlflow_config, mock_model_package_config,
651+
):
652+
"""When PipelineSession is passed, _intercept_create_request traps the args."""
653+
from sagemaker.train.dpo_trainer import DPOTrainer
654+
from sagemaker.core.workflow.pipeline_context import PipelineSession, _JobStepArguments
655+
656+
pipeline_session = Mock(spec=PipelineSession)
657+
pipeline_session.boto_session = Mock()
658+
pipeline_session.boto_session.region_name = "us-west-2"
659+
660+
step_args = _JobStepArguments("train", {"training_job_name": "test-dpo-job-001"})
661+
pipeline_session._intercept_create_request.return_value = None
662+
pipeline_session.context = step_args
663+
mock_get_session.return_value = pipeline_session
664+
665+
mock_resolve_model.return_value = ("test-model", "resolved-model-name")
666+
mock_hyperparams = Mock()
667+
mock_hyperparams.to_dict.return_value = {"param1": "value1"}
668+
mock_hyperparams._specs = {"param1": {"type": "string"}}
669+
mock_hyperparams._user_set = set()
670+
mock_finetuning_options.return_value = (mock_hyperparams, "arn:aws:sagemaker:us-west-2:123456789012:model/test", False)
671+
mock_validate_group.return_value = "test-group"
672+
mock_get_role.return_value = "arn:aws:iam::123456789012:role/Role"
673+
mock_unique_name.return_value = "test-dpo-job-001"
674+
mock_input_config.return_value = {"train": "s3://bucket/data"}
675+
mock_convert_channels.return_value = [{"ChannelName": "train"}]
676+
mock_serverless_config.return_value = {"BaseModelArn": "arn:model"}
677+
mock_output_config.return_value = {"S3OutputPath": "s3://bucket/output"}
678+
mock_mlflow_config.return_value = None
679+
mock_model_package_config.return_value = None
680+
mock_beta_session.return_value = pipeline_session
681+
682+
trainer = DPOTrainer(model="test-model", training_dataset="s3://bucket/data", model_package_group="test-group", sagemaker_session=pipeline_session)
683+
trainer._model_arn = "arn:aws:sagemaker:us-west-2:123456789012:model/test"
684+
trainer._model_name = "test-model"
685+
trainer.accept_eula = True
686+
trainer.hyperparameters = mock_hyperparams
687+
688+
result = trainer.train()
689+
690+
mock_training_job_create.assert_not_called()
691+
pipeline_session._intercept_create_request.assert_called_once()
692+
assert pipeline_session._intercept_create_request.call_args[0][2] == "train"
693+
assert result == step_args

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

Lines changed: 71 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -682,4 +682,74 @@ def test_train_wait_false_skips_wait(self, mock_training_job_create, mock_model_
682682
trainer = RLAIFTrainer(model="test-model", model_package_group="test-group", training_dataset="s3://bucket/train")
683683
trainer.train(wait=False, wait_timeout=600)
684684

685-
mock_wait.assert_not_called()
685+
mock_wait.assert_not_called()
686+
687+
688+
class TestRLAIFTrainerPipelineSession:
689+
"""Test RLAIFTrainer behavior when PipelineSession is used.
690+
691+
Ref: https://github.com/aws/sagemaker-python-sdk/issues/6163
692+
"""
693+
694+
@patch('sagemaker.train.rlaif_trainer._create_model_package_config')
695+
@patch('sagemaker.train.rlaif_trainer._create_mlflow_config')
696+
@patch('sagemaker.train.rlaif_trainer._create_output_config')
697+
@patch('sagemaker.train.rlaif_trainer._create_serverless_config')
698+
@patch('sagemaker.train.rlaif_trainer._convert_input_data_to_channels')
699+
@patch('sagemaker.train.rlaif_trainer._create_input_data_config')
700+
@patch('sagemaker.train.rlaif_trainer._get_unique_name')
701+
@patch('sagemaker.train.rlaif_trainer.TrainDefaults.get_role')
702+
@patch('sagemaker.train.rlaif_trainer.TrainDefaults.get_sagemaker_session')
703+
@patch('sagemaker.train.rlaif_trainer._validate_and_resolve_model_package_group')
704+
@patch('sagemaker.train.rlaif_trainer._get_fine_tuning_options_and_model_arn')
705+
@patch('sagemaker.train.rlaif_trainer._resolve_model_and_name')
706+
@patch('sagemaker.train.common_utils.finetune_utils._get_beta_session')
707+
@patch('sagemaker.core.resources.TrainingJob.create')
708+
def test_train_with_pipeline_session_does_not_launch_job(
709+
self, mock_training_job_create, mock_beta_session, mock_resolve_model,
710+
mock_finetuning_options, mock_validate_group, mock_get_session, mock_get_role,
711+
mock_unique_name, mock_input_config, mock_convert_channels,
712+
mock_serverless_config, mock_output_config, mock_mlflow_config, mock_model_package_config,
713+
):
714+
"""When PipelineSession is passed, _intercept_create_request traps the args."""
715+
from sagemaker.train.rlaif_trainer import RLAIFTrainer
716+
from sagemaker.core.workflow.pipeline_context import PipelineSession, _JobStepArguments
717+
718+
pipeline_session = Mock(spec=PipelineSession)
719+
pipeline_session.boto_session = Mock()
720+
pipeline_session.boto_session.region_name = "us-west-2"
721+
722+
step_args = _JobStepArguments("train", {"training_job_name": "test-rlaif-job-001"})
723+
pipeline_session._intercept_create_request.return_value = None
724+
pipeline_session.context = step_args
725+
mock_get_session.return_value = pipeline_session
726+
727+
mock_resolve_model.return_value = ("test-model", "resolved-model-name")
728+
mock_hyperparams = Mock()
729+
mock_hyperparams.to_dict.return_value = {"param1": "value1"}
730+
mock_hyperparams._specs = {"param1": {"type": "string"}}
731+
mock_hyperparams._user_set = set()
732+
mock_finetuning_options.return_value = (mock_hyperparams, "arn:aws:sagemaker:us-west-2:123456789012:model/test", False)
733+
mock_validate_group.return_value = "test-group"
734+
mock_get_role.return_value = "arn:aws:iam::123456789012:role/Role"
735+
mock_unique_name.return_value = "test-rlaif-job-001"
736+
mock_input_config.return_value = {"train": "s3://bucket/data"}
737+
mock_convert_channels.return_value = [{"ChannelName": "train"}]
738+
mock_serverless_config.return_value = {"BaseModelArn": "arn:model"}
739+
mock_output_config.return_value = {"S3OutputPath": "s3://bucket/output"}
740+
mock_mlflow_config.return_value = None
741+
mock_model_package_config.return_value = None
742+
mock_beta_session.return_value = pipeline_session
743+
744+
trainer = RLAIFTrainer(model="test-model", training_dataset="s3://bucket/data", model_package_group="test-group", sagemaker_session=pipeline_session)
745+
trainer._model_arn = "arn:aws:sagemaker:us-west-2:123456789012:model/test"
746+
trainer._model_name = "test-model"
747+
trainer.accept_eula = True
748+
trainer.hyperparameters = mock_hyperparams
749+
750+
result = trainer.train()
751+
752+
mock_training_job_create.assert_not_called()
753+
pipeline_session._intercept_create_request.assert_called_once()
754+
assert pipeline_session._intercept_create_request.call_args[0][2] == "train"
755+
assert result == step_args

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

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -620,3 +620,72 @@ def test_s3_model_without_base_model_name_raises(self, mock_resolve, mock_valida
620620
compute=HyperPodCompute(cluster_name="my-cluster", node_count=4),
621621
training_dataset="s3://bucket/train.jsonl",
622622
)
623+
624+
625+
class TestRLVRTrainerPipelineSession:
626+
"""Test RLVRTrainer behavior when PipelineSession is used.
627+
628+
Ref: https://github.com/aws/sagemaker-python-sdk/issues/6163
629+
"""
630+
631+
@patch('sagemaker.train.rlvr_trainer._create_model_package_config')
632+
@patch('sagemaker.train.rlvr_trainer._create_mlflow_config')
633+
@patch('sagemaker.train.rlvr_trainer._create_output_config')
634+
@patch('sagemaker.train.rlvr_trainer._convert_input_data_to_channels')
635+
@patch('sagemaker.train.rlvr_trainer._create_input_data_config')
636+
@patch('sagemaker.train.rlvr_trainer._get_unique_name')
637+
@patch('sagemaker.train.rlvr_trainer.TrainDefaults.get_role')
638+
@patch('sagemaker.train.rlvr_trainer.TrainDefaults.get_sagemaker_session')
639+
@patch('sagemaker.train.rlvr_trainer._validate_and_resolve_model_package_group')
640+
@patch('sagemaker.train.rlvr_trainer._get_fine_tuning_options_and_model_arn')
641+
@patch('sagemaker.train.rlvr_trainer._resolve_model_and_name')
642+
@patch('sagemaker.train.common_utils.finetune_utils._get_beta_session')
643+
@patch('sagemaker.core.resources.TrainingJob.create')
644+
def test_train_with_pipeline_session_does_not_launch_job(
645+
self, mock_training_job_create, mock_beta_session, mock_resolve_model,
646+
mock_finetuning_options, mock_validate_group, mock_get_session, mock_get_role,
647+
mock_unique_name, mock_input_config, mock_convert_channels,
648+
mock_output_config, mock_mlflow_config, mock_model_package_config,
649+
):
650+
"""When PipelineSession is passed, _intercept_create_request traps the args."""
651+
from sagemaker.train.rlvr_trainer import RLVRTrainer
652+
from sagemaker.core.workflow.pipeline_context import PipelineSession, _JobStepArguments
653+
654+
pipeline_session = Mock(spec=PipelineSession)
655+
pipeline_session.boto_session = Mock()
656+
pipeline_session.boto_session.region_name = "us-west-2"
657+
658+
step_args = _JobStepArguments("train", {"training_job_name": "test-rlvr-job-001"})
659+
pipeline_session._intercept_create_request.return_value = None
660+
pipeline_session.context = step_args
661+
mock_get_session.return_value = pipeline_session
662+
663+
mock_resolve_model.return_value = ("test-model", "resolved-model-name")
664+
mock_hyperparams = Mock()
665+
mock_hyperparams.to_dict.return_value = {"param1": "value1"}
666+
mock_hyperparams._specs = {"param1": {"type": "string"}}
667+
mock_hyperparams._user_set = set()
668+
mock_finetuning_options.return_value = (mock_hyperparams, "arn:aws:sagemaker:us-west-2:123456789012:model/test", False)
669+
mock_validate_group.return_value = "test-group"
670+
mock_get_role.return_value = "arn:aws:iam::123456789012:role/Role"
671+
mock_unique_name.return_value = "test-rlvr-job-001"
672+
mock_input_config.return_value = {"train": "s3://bucket/data"}
673+
mock_convert_channels.return_value = [{"ChannelName": "train"}]
674+
mock_output_config.return_value = {"S3OutputPath": "s3://bucket/output"}
675+
mock_mlflow_config.return_value = None
676+
mock_model_package_config.return_value = None
677+
mock_beta_session.return_value = pipeline_session
678+
679+
trainer = RLVRTrainer(model="test-model", training_dataset="s3://bucket/data", model_package_group="test-group", sagemaker_session=pipeline_session)
680+
trainer._model_arn = "arn:aws:sagemaker:us-west-2:123456789012:model/test"
681+
trainer._model_name = "test-model"
682+
trainer.accept_eula = True
683+
trainer.hyperparameters = mock_hyperparams
684+
trainer.custom_reward_function = None
685+
686+
result = trainer.train()
687+
688+
mock_training_job_create.assert_not_called()
689+
pipeline_session._intercept_create_request.assert_called_once()
690+
assert pipeline_session._intercept_create_request.call_args[0][2] == "train"
691+
assert result == step_args

0 commit comments

Comments
 (0)