Skip to content

Commit d3eef6a

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 d3801bb commit d3eef6a

8 files changed

Lines changed: 306 additions & 0 deletions

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
@@ -385,6 +386,12 @@ def train(self,
385386
logger.info("Dry-run validation passed. No job submitted.")
386387
return None
387388

389+
# If running within a PipelineSession, intercept the request and store
390+
# step arguments instead of launching a training job.
391+
if isinstance(sagemaker_session, PipelineSession):
392+
sagemaker_session._intercept_create_request(create_args, None, "train")
393+
return sagemaker_session.context
394+
388395
try:
389396
training_job = TrainingJob.create(**create_args)
390397
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
@@ -351,6 +352,12 @@ def train(self, training_dataset: Optional[Union[str, DataSet]] = None, validati
351352
logger.info("Dry-run validation passed. No job submitted.")
352353
return None
353354

355+
# If running within a PipelineSession, intercept the request and store
356+
# step arguments instead of launching a training job.
357+
if isinstance(sagemaker_session, PipelineSession):
358+
sagemaker_session._intercept_create_request(create_args, None, "train")
359+
return sagemaker_session.context
360+
354361
try:
355362
training_job = TrainingJob.create(**create_args)
356363
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
@@ -571,6 +572,12 @@ def train(self, training_dataset: Optional[Union[str, DataSet]] = None,
571572
logger.info("Dry-run validation passed. No job submitted.")
572573
return None
573574

575+
# If running within a PipelineSession, intercept the request and store
576+
# step arguments instead of launching a training job.
577+
if isinstance(sagemaker_session, PipelineSession):
578+
sagemaker_session._intercept_create_request(create_args, None, "train")
579+
return sagemaker_session.context
580+
574581
try:
575582
training_job = TrainingJob.create(**create_args)
576583
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
@@ -453,6 +454,12 @@ def train(self, training_dataset: Optional[Union[str, DataSet]] = None, validati
453454
logger.info("Dry-run validation passed. No job submitted.")
454455
return None
455456

457+
# If running within a PipelineSession, intercept the request and store
458+
# step arguments instead of launching a training job.
459+
if isinstance(sagemaker_session, PipelineSession):
460+
sagemaker_session._intercept_create_request(create_args, None, "train")
461+
return sagemaker_session.context
462+
456463
try:
457464
training_job = TrainingJob.create(**create_args)
458465
except Exception as e:

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

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -750,3 +750,72 @@ def test_list_supported_models(self, mock_list):
750750
mock_list.assert_called_once_with(
751751
recipe_type="FineTuning", technique="DPO", session=None
752752
)
753+
754+
class TestDPOTrainerPipelineSession:
755+
"""Test DPOTrainer behavior when PipelineSession is used.
756+
757+
Ref: https://github.com/aws/sagemaker-python-sdk/issues/6163
758+
"""
759+
760+
@patch('sagemaker.train.dpo_trainer._create_model_package_config')
761+
@patch('sagemaker.train.dpo_trainer._create_mlflow_config')
762+
@patch('sagemaker.train.dpo_trainer._create_output_config')
763+
@patch('sagemaker.train.dpo_trainer._create_serverless_config')
764+
@patch('sagemaker.train.dpo_trainer._convert_input_data_to_channels')
765+
@patch('sagemaker.train.dpo_trainer._create_input_data_config')
766+
@patch('sagemaker.train.dpo_trainer._get_unique_name')
767+
@patch('sagemaker.train.dpo_trainer.TrainDefaults.get_role')
768+
@patch('sagemaker.train.dpo_trainer.TrainDefaults.get_sagemaker_session')
769+
@patch('sagemaker.train.dpo_trainer._validate_and_resolve_model_package_group')
770+
@patch('sagemaker.train.dpo_trainer._get_fine_tuning_options_and_model_arn')
771+
@patch('sagemaker.train.dpo_trainer._resolve_model_and_name')
772+
@patch('sagemaker.train.common_utils.finetune_utils._get_beta_session')
773+
@patch('sagemaker.core.resources.TrainingJob.create')
774+
def test_train_with_pipeline_session_does_not_launch_job(
775+
self, mock_training_job_create, mock_beta_session, mock_resolve_model,
776+
mock_finetuning_options, mock_validate_group, mock_get_session, mock_get_role,
777+
mock_unique_name, mock_input_config, mock_convert_channels,
778+
mock_serverless_config, mock_output_config, mock_mlflow_config, mock_model_package_config,
779+
):
780+
"""When PipelineSession is passed, _intercept_create_request traps the args."""
781+
from sagemaker.train.dpo_trainer import DPOTrainer
782+
from sagemaker.core.workflow.pipeline_context import PipelineSession, _JobStepArguments
783+
784+
pipeline_session = Mock(spec=PipelineSession)
785+
pipeline_session.boto_session = Mock()
786+
pipeline_session.boto_session.region_name = "us-west-2"
787+
788+
step_args = _JobStepArguments("train", {"training_job_name": "test-dpo-job-001"})
789+
pipeline_session._intercept_create_request.return_value = None
790+
pipeline_session.context = step_args
791+
mock_get_session.return_value = pipeline_session
792+
793+
mock_resolve_model.return_value = ("test-model", "resolved-model-name")
794+
mock_hyperparams = Mock()
795+
mock_hyperparams.to_dict.return_value = {"param1": "value1"}
796+
mock_hyperparams._specs = {"param1": {"type": "string"}}
797+
mock_hyperparams._user_set = set()
798+
mock_finetuning_options.return_value = (mock_hyperparams, "arn:aws:sagemaker:us-west-2:123456789012:model/test", False)
799+
mock_validate_group.return_value = "test-group"
800+
mock_get_role.return_value = "arn:aws:iam::123456789012:role/Role"
801+
mock_unique_name.return_value = "test-dpo-job-001"
802+
mock_input_config.return_value = {"train": "s3://bucket/data"}
803+
mock_convert_channels.return_value = [{"ChannelName": "train"}]
804+
mock_serverless_config.return_value = {"BaseModelArn": "arn:model"}
805+
mock_output_config.return_value = {"S3OutputPath": "s3://bucket/output"}
806+
mock_mlflow_config.return_value = None
807+
mock_model_package_config.return_value = None
808+
mock_beta_session.return_value = pipeline_session
809+
810+
trainer = DPOTrainer(model="test-model", training_dataset="s3://bucket/data", model_package_group="test-group", sagemaker_session=pipeline_session)
811+
trainer._model_arn = "arn:aws:sagemaker:us-west-2:123456789012:model/test"
812+
trainer._model_name = "test-model"
813+
trainer.accept_eula = True
814+
trainer.hyperparameters = mock_hyperparams
815+
816+
result = trainer.train()
817+
818+
mock_training_job_create.assert_not_called()
819+
pipeline_session._intercept_create_request.assert_called_once()
820+
assert pipeline_session._intercept_create_request.call_args[0][2] == "train"
821+
assert result == step_args

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

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -872,3 +872,72 @@ def test_list_supported_models(self, mock_list):
872872
mock_list.assert_called_once_with(
873873
recipe_type="FineTuning", technique="RLAIF", session=None
874874
)
875+
876+
class TestRLAIFTrainerPipelineSession:
877+
"""Test RLAIFTrainer behavior when PipelineSession is used.
878+
879+
Ref: https://github.com/aws/sagemaker-python-sdk/issues/6163
880+
"""
881+
882+
@patch('sagemaker.train.rlaif_trainer._create_model_package_config')
883+
@patch('sagemaker.train.rlaif_trainer._create_mlflow_config')
884+
@patch('sagemaker.train.rlaif_trainer._create_output_config')
885+
@patch('sagemaker.train.rlaif_trainer._create_serverless_config')
886+
@patch('sagemaker.train.rlaif_trainer._convert_input_data_to_channels')
887+
@patch('sagemaker.train.rlaif_trainer._create_input_data_config')
888+
@patch('sagemaker.train.rlaif_trainer._get_unique_name')
889+
@patch('sagemaker.train.rlaif_trainer.TrainDefaults.get_role')
890+
@patch('sagemaker.train.rlaif_trainer.TrainDefaults.get_sagemaker_session')
891+
@patch('sagemaker.train.rlaif_trainer._validate_and_resolve_model_package_group')
892+
@patch('sagemaker.train.rlaif_trainer._get_fine_tuning_options_and_model_arn')
893+
@patch('sagemaker.train.rlaif_trainer._resolve_model_and_name')
894+
@patch('sagemaker.train.common_utils.finetune_utils._get_beta_session')
895+
@patch('sagemaker.core.resources.TrainingJob.create')
896+
def test_train_with_pipeline_session_does_not_launch_job(
897+
self, mock_training_job_create, mock_beta_session, mock_resolve_model,
898+
mock_finetuning_options, mock_validate_group, mock_get_session, mock_get_role,
899+
mock_unique_name, mock_input_config, mock_convert_channels,
900+
mock_serverless_config, mock_output_config, mock_mlflow_config, mock_model_package_config,
901+
):
902+
"""When PipelineSession is passed, _intercept_create_request traps the args."""
903+
from sagemaker.train.rlaif_trainer import RLAIFTrainer
904+
from sagemaker.core.workflow.pipeline_context import PipelineSession, _JobStepArguments
905+
906+
pipeline_session = Mock(spec=PipelineSession)
907+
pipeline_session.boto_session = Mock()
908+
pipeline_session.boto_session.region_name = "us-west-2"
909+
910+
step_args = _JobStepArguments("train", {"training_job_name": "test-rlaif-job-001"})
911+
pipeline_session._intercept_create_request.return_value = None
912+
pipeline_session.context = step_args
913+
mock_get_session.return_value = pipeline_session
914+
915+
mock_resolve_model.return_value = ("test-model", "resolved-model-name")
916+
mock_hyperparams = Mock()
917+
mock_hyperparams.to_dict.return_value = {"param1": "value1"}
918+
mock_hyperparams._specs = {"param1": {"type": "string"}}
919+
mock_hyperparams._user_set = set()
920+
mock_finetuning_options.return_value = (mock_hyperparams, "arn:aws:sagemaker:us-west-2:123456789012:model/test", False)
921+
mock_validate_group.return_value = "test-group"
922+
mock_get_role.return_value = "arn:aws:iam::123456789012:role/Role"
923+
mock_unique_name.return_value = "test-rlaif-job-001"
924+
mock_input_config.return_value = {"train": "s3://bucket/data"}
925+
mock_convert_channels.return_value = [{"ChannelName": "train"}]
926+
mock_serverless_config.return_value = {"BaseModelArn": "arn:model"}
927+
mock_output_config.return_value = {"S3OutputPath": "s3://bucket/output"}
928+
mock_mlflow_config.return_value = None
929+
mock_model_package_config.return_value = None
930+
mock_beta_session.return_value = pipeline_session
931+
932+
trainer = RLAIFTrainer(model="test-model", training_dataset="s3://bucket/data", model_package_group="test-group", sagemaker_session=pipeline_session)
933+
trainer._model_arn = "arn:aws:sagemaker:us-west-2:123456789012:model/test"
934+
trainer._model_name = "test-model"
935+
trainer.accept_eula = True
936+
trainer.hyperparameters = mock_hyperparams
937+
938+
result = trainer.train()
939+
940+
mock_training_job_create.assert_not_called()
941+
pipeline_session._intercept_create_request.assert_called_once()
942+
assert pipeline_session._intercept_create_request.call_args[0][2] == "train"
943+
assert result == step_args

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

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -775,3 +775,71 @@ def test_list_supported_models(self, mock_list):
775775
mock_list.assert_called_once_with(
776776
recipe_type="FineTuning", technique="RLVR", session=None
777777
)
778+
779+
class TestRLVRTrainerPipelineSession:
780+
"""Test RLVRTrainer behavior when PipelineSession is used.
781+
782+
Ref: https://github.com/aws/sagemaker-python-sdk/issues/6163
783+
"""
784+
785+
@patch('sagemaker.train.rlvr_trainer._create_model_package_config')
786+
@patch('sagemaker.train.rlvr_trainer._create_mlflow_config')
787+
@patch('sagemaker.train.rlvr_trainer._create_output_config')
788+
@patch('sagemaker.train.rlvr_trainer._convert_input_data_to_channels')
789+
@patch('sagemaker.train.rlvr_trainer._create_input_data_config')
790+
@patch('sagemaker.train.rlvr_trainer._get_unique_name')
791+
@patch('sagemaker.train.rlvr_trainer.TrainDefaults.get_role')
792+
@patch('sagemaker.train.rlvr_trainer.TrainDefaults.get_sagemaker_session')
793+
@patch('sagemaker.train.rlvr_trainer._validate_and_resolve_model_package_group')
794+
@patch('sagemaker.train.rlvr_trainer._get_fine_tuning_options_and_model_arn')
795+
@patch('sagemaker.train.rlvr_trainer._resolve_model_and_name')
796+
@patch('sagemaker.train.common_utils.finetune_utils._get_beta_session')
797+
@patch('sagemaker.core.resources.TrainingJob.create')
798+
def test_train_with_pipeline_session_does_not_launch_job(
799+
self, mock_training_job_create, mock_beta_session, mock_resolve_model,
800+
mock_finetuning_options, mock_validate_group, mock_get_session, mock_get_role,
801+
mock_unique_name, mock_input_config, mock_convert_channels,
802+
mock_output_config, mock_mlflow_config, mock_model_package_config,
803+
):
804+
"""When PipelineSession is passed, _intercept_create_request traps the args."""
805+
from sagemaker.train.rlvr_trainer import RLVRTrainer
806+
from sagemaker.core.workflow.pipeline_context import PipelineSession, _JobStepArguments
807+
808+
pipeline_session = Mock(spec=PipelineSession)
809+
pipeline_session.boto_session = Mock()
810+
pipeline_session.boto_session.region_name = "us-west-2"
811+
812+
step_args = _JobStepArguments("train", {"training_job_name": "test-rlvr-job-001"})
813+
pipeline_session._intercept_create_request.return_value = None
814+
pipeline_session.context = step_args
815+
mock_get_session.return_value = pipeline_session
816+
817+
mock_resolve_model.return_value = ("test-model", "resolved-model-name")
818+
mock_hyperparams = Mock()
819+
mock_hyperparams.to_dict.return_value = {"param1": "value1"}
820+
mock_hyperparams._specs = {"param1": {"type": "string"}}
821+
mock_hyperparams._user_set = set()
822+
mock_finetuning_options.return_value = (mock_hyperparams, "arn:aws:sagemaker:us-west-2:123456789012:model/test", False)
823+
mock_validate_group.return_value = "test-group"
824+
mock_get_role.return_value = "arn:aws:iam::123456789012:role/Role"
825+
mock_unique_name.return_value = "test-rlvr-job-001"
826+
mock_input_config.return_value = {"train": "s3://bucket/data"}
827+
mock_convert_channels.return_value = [{"ChannelName": "train"}]
828+
mock_output_config.return_value = {"S3OutputPath": "s3://bucket/output"}
829+
mock_mlflow_config.return_value = None
830+
mock_model_package_config.return_value = None
831+
mock_beta_session.return_value = pipeline_session
832+
833+
trainer = RLVRTrainer(model="test-model", training_dataset="s3://bucket/data", model_package_group="test-group", sagemaker_session=pipeline_session)
834+
trainer._model_arn = "arn:aws:sagemaker:us-west-2:123456789012:model/test"
835+
trainer._model_name = "test-model"
836+
trainer.accept_eula = True
837+
trainer.hyperparameters = mock_hyperparams
838+
trainer.custom_reward_function = None
839+
840+
result = trainer.train()
841+
842+
mock_training_job_create.assert_not_called()
843+
pipeline_session._intercept_create_request.assert_called_once()
844+
assert pipeline_session._intercept_create_request.call_args[0][2] == "train"
845+
assert result == step_args

0 commit comments

Comments
 (0)