Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# Changelog

## v3.1.0 (2025-12-03)

### Features

* Fine-tuning SDK: SFT, RLVR, and RLAIF techniques with standardized parameter design
* AIRegistry Integration: Added CRUD operations for datasets and evaluators
* Enhanced Training Experience: Implemented MLFlow metrics tracking and deployment workflows

## v3.0.1 (2025-11-19)

* Update project dependencies to include submodules: sagemaker-core, sagemaker-train, sagemaker-serve, sagemaker-mlops
Expand Down
2 changes: 1 addition & 1 deletion VERSION
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.0.1
3.1.0
10 changes: 5 additions & 5 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,10 +33,10 @@ classifiers = [
"Programming Language :: Python :: 3.12",
]
dependencies = [
"sagemaker-core>=2.0.0,<3.0.0",
"sagemaker-train<2.0.0",
"sagemaker-serve<2.0.0",
"sagemaker-mlops<2.0.0",
"sagemaker-core>=2.1.0,<3.0.0",
"sagemaker-train>=1.1.0,<2.0.0",
"sagemaker-serve>=1.1.0,<2.0.0",
"sagemaker-mlops>=1.1.0,<2.0.0",
]

[project.optional-dependencies]
Expand All @@ -59,4 +59,4 @@ addopts = ["-vv"]
testpaths = ["tests"]

[tool.black]
line-length = 100
line-length = 100
3 changes: 2 additions & 1 deletion sagemaker-core/VERSION
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
2.0.1
2.1.0

2 changes: 1 addition & 1 deletion sagemaker-core/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ authors = [
readme = "README.rst"
dependencies = [
# Add your dependencies here (Include lower and upper bounds as applicable)
"boto3>=1.35.75,<2.0.0",
"boto3>=1.42.2,<2.0.0",
"pydantic>=2.0.0,<3.0.0",
"PyYAML>=6.0, <7.0",
"jsonschema<5.0.0",
Expand Down
63 changes: 49 additions & 14 deletions sagemaker-core/resource_plan.csv

Large diffs are not rendered by default.

15,507 changes: 14,973 additions & 534 deletions sagemaker-core/sample/sagemaker/2017-07-24/service-2.json

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions sagemaker-core/src/sagemaker/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,6 @@
FrameworkProcessor,
)
from sagemaker.core.transformer import Transformer # noqa: F401

# Note: HyperparameterTuner and WarmStartTypes are in sagemaker.train.tuner
# They are not re-exported from core to avoid circular dependencies
2 changes: 1 addition & 1 deletion sagemaker-core/src/sagemaker/core/_studio.py
Original file line number Diff line number Diff line change
Expand Up @@ -113,4 +113,4 @@ def _parse_tags(config):
{"Key": "sagemaker:project-name", "Value": config["sagemakerProjectName"]},
]
except Exception as e: # pylint: disable=W0703
logger.debug("Could not parse project config. %s", e)
logger.debug("Could not parse project config. %s", e)
2 changes: 1 addition & 1 deletion sagemaker-core/src/sagemaker/core/apiutils/_base_types.py
Original file line number Diff line number Diff line change
Expand Up @@ -225,4 +225,4 @@ def _invoke_api(self, boto_method, boto_method_members):
api_kwargs = self.to_boto(api_values)
api_method = getattr(self.sagemaker_session.sagemaker_client, boto_method)
api_boto_response = api_method(**api_kwargs)
return self.with_boto(api_boto_response)
return self.with_boto(api_boto_response)
4 changes: 2 additions & 2 deletions sagemaker-core/src/sagemaker/core/apiutils/_boto_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ def to_pascal_case(snake_case):
Returns:
str: String converted to PascalCase.
"""
return ''.join(word.capitalize() for word in snake_case.split('_'))
return "".join(word.capitalize() for word in snake_case.split("_"))


def to_snake_case(name):
Expand Down Expand Up @@ -127,4 +127,4 @@ def to_boto(member_vars, member_name_to_boto_name, member_name_to_type):
else:
boto_value = api_type.to_boto(member_value) if api_type else member_value
to_boto_values[boto_name] = boto_value
return to_boto_values
return to_boto_values
2 changes: 1 addition & 1 deletion sagemaker-core/src/sagemaker/core/base_deserializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@
"Importing from sagemaker.core.base_deserializers is deprecated. "
"Use sagemaker.core.deserializers instead.",
DeprecationWarning,
stacklevel=2
stacklevel=2,
)
2 changes: 1 addition & 1 deletion sagemaker-core/src/sagemaker/core/base_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,5 +31,5 @@
"Importing from sagemaker.core.base_serializers is deprecated. "
"Use sagemaker.core.serializers instead.",
DeprecationWarning,
stacklevel=2
stacklevel=2,
)
20 changes: 7 additions & 13 deletions sagemaker-core/src/sagemaker/core/clarify/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2004,7 +2004,7 @@ def _run(
kms_key,
)
from sagemaker.core.shapes import ProcessingS3Input, ProcessingS3Output

config_input = ProcessingInput(
input_name="analysis_config",
s3_input=ProcessingS3Input(
Expand All @@ -2013,7 +2013,7 @@ def _run(
s3_data_type="S3Prefix",
s3_input_mode="File",
s3_compression_type="None",
)
),
)
data_input = ProcessingInput(
input_name="dataset",
Expand All @@ -2024,15 +2024,15 @@ def _run(
s3_input_mode="File",
s3_data_distribution_type=data_config.s3_data_distribution_type,
s3_compression_type=data_config.s3_compression_type,
)
),
)
result_output = ProcessingOutput(
output_name="analysis_result",
s3_output=ProcessingS3Output(
s3_uri=data_config.s3_output_path,
local_path=self._CLARIFY_OUTPUT,
s3_upload_mode=ProcessingOutputHandler.get_s3_upload_mode(analysis_config),
)
),
)

return super().run(
Expand Down Expand Up @@ -2101,9 +2101,7 @@ def run_pre_training_bias(
data_config, data_bias_config, methods
)
# when name is either not provided (is None) or an empty string ("")
job_name = job_name or name_from_base(
self.job_name_prefix or "Clarify-Pretraining-Bias"
)
job_name = job_name or name_from_base(self.job_name_prefix or "Clarify-Pretraining-Bias")
return self._run(
data_config,
analysis_config,
Expand Down Expand Up @@ -2187,9 +2185,7 @@ def run_post_training_bias(
model_config,
)
# when name is either not provided (is None) or an empty string ("")
job_name = job_name or name_from_base(
self.job_name_prefix or "Clarify-Posttraining-Bias"
)
job_name = job_name or name_from_base(self.job_name_prefix or "Clarify-Posttraining-Bias")
return self._run(
data_config,
analysis_config,
Expand Down Expand Up @@ -2373,9 +2369,7 @@ def run_explainability(
data_config, model_config, model_scores, explainability_config
)
# when name is either not provided (is None) or an empty string ("")
job_name = job_name or name_from_base(
self.job_name_prefix or "Clarify-Explainability"
)
job_name = job_name or name_from_base(self.job_name_prefix or "Clarify-Explainability")
return self._run(
data_config,
analysis_config,
Expand Down
23 changes: 21 additions & 2 deletions sagemaker-core/src/sagemaker/core/common_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,7 @@ class ModelApprovalStatusEnum(str, Enum):
REJECTED = "Rejected"
PENDING_MANUAL_APPROVAL = "PendingManualApproval"


# Use the base name of the image as the job name if the user doesn't give us one
def name_from_image(image, max_length=63):
"""Create a training job name based on the image name and a timestamp.
Expand Down Expand Up @@ -1136,6 +1137,7 @@ def resolve_value_from_config(
else None
)
from sagemaker.core.config.config_utils import _log_sagemaker_config_single_substitution

_log_sagemaker_config_single_substitution(direct_input, config_value, config_path)

if direct_input is not None:
Expand Down Expand Up @@ -1179,6 +1181,7 @@ def get_sagemaker_config_value(sagemaker_session, key, sagemaker_config: dict =
# Copy the value so any modifications to the output will not modify the source config
return copy.deepcopy(config_value)


def get_resource_name_from_arn(arn):
"""Extract the resource name from an ARN string.

Expand All @@ -1190,6 +1193,7 @@ def get_resource_name_from_arn(arn):
"""
return arn.split(":", 5)[5].split("/", 1)[1]


def list_tags(sagemaker_session, resource_arn, max_results=50):
"""List the tags given an Amazon Resource Name.

Expand Down Expand Up @@ -1223,6 +1227,7 @@ def list_tags(sagemaker_session, resource_arn, max_results=50):
logger.error("Error retrieving tags. resource_arn: %s", resource_arn)
raise error


def resolve_class_attribute_from_config(
clazz: Optional[type],
instance: Optional[object],
Expand Down Expand Up @@ -1290,6 +1295,7 @@ def resolve_class_attribute_from_config(
setattr(instance, attribute, default_value)

from sagemaker.core.config.config_utils import _log_sagemaker_config_single_substitution

_log_sagemaker_config_single_substitution(current_value, config_value, config_path)

return instance
Expand Down Expand Up @@ -1344,6 +1350,7 @@ def resolve_nested_dict_value_from_config(
dictionary = set_nested_value(dictionary, nested_keys, default_value)

from sagemaker.core.config.config_utils import _log_sagemaker_config_single_substitution

_log_sagemaker_config_single_substitution(current_nested_value, config_value, config_path)

return dictionary
Expand Down Expand Up @@ -1410,6 +1417,7 @@ def update_list_of_dicts_with_values_from_config(
input_list[i] = dict_from_config

from sagemaker.core.config.config_utils import _log_sagemaker_config_merge

_log_sagemaker_config_merge(
source_value=inputs_copy,
config_value=unmodified_inputs_from_config,
Expand Down Expand Up @@ -1477,6 +1485,7 @@ def update_nested_dictionary_with_values_from_config(
return source_dict

from sagemaker.core.config.config_utils import _log_sagemaker_config_merge

_log_sagemaker_config_merge(
source_value=source_dict,
config_value=original_config_dict_value,
Expand Down Expand Up @@ -2023,6 +2032,7 @@ def _walk_and_apply_json(json_obj, new):

return _walk_and_apply_json(json_obj, new={})


def _wait_until(callable_fn, poll=5):
"""Placeholder docstring"""
elapsed_time = 0
Expand All @@ -2048,6 +2058,7 @@ def _wait_until(callable_fn, poll=5):
raise err
return result


def _flush_log_streams(
stream_names, instance_count, client, log_group, job_name, positions, dot, color_wrap
):
Expand Down Expand Up @@ -2098,7 +2109,9 @@ def _flush_log_streams(
color_wrap(idx, event["message"])
ts, count = positions[stream_names[idx]]
if event["timestamp"] == ts:
positions[stream_names[idx]] = sagemaker.core.logs.Position(timestamp=ts, skip=count + 1)
positions[stream_names[idx]] = sagemaker.core.logs.Position(
timestamp=ts, skip=count + 1
)
else:
positions[stream_names[idx]] = sagemaker.core.logs.Position(
timestamp=event["timestamp"], skip=1
Expand All @@ -2108,6 +2121,7 @@ def _flush_log_streams(
print(".", end="")
sys.stdout.flush()


class LogState(object):
"""Placeholder docstring"""

Expand All @@ -2117,6 +2131,7 @@ class LogState(object):
JOB_COMPLETE = 4
COMPLETE = 5


_STATUS_CODE_TABLE = {
"COMPLETED": "Completed",
"INPROGRESS": "InProgress",
Expand All @@ -2128,12 +2143,14 @@ class LogState(object):
"PENDING": "Pending",
}


def _get_initial_job_state(description, status_key, wait):
"""Placeholder docstring"""
status = description[status_key]
job_already_completed = status in ("Completed", "Failed", "Stopped")
return LogState.TAILING if wait and not job_already_completed else LogState.COMPLETE


def _logs_init(boto_session, description, job):
"""Placeholder docstring"""
if job == "Training":
Expand Down Expand Up @@ -2165,6 +2182,7 @@ def _logs_init(boto_session, description, job):

return instance_count, stream_names, positions, client, log_group, dot, color_wrap


def _check_job_status(job, desc, status_key_name):
"""Check to see if the job completed successfully.

Expand Down Expand Up @@ -2218,6 +2236,7 @@ def _check_job_status(job, desc, status_key_name):
actual_status=status,
)


def _create_resource(create_fn):
"""Call create function and accepts/pass when resource already exists.

Expand Down Expand Up @@ -2259,4 +2278,4 @@ def _is_s3_uri(s3_uri: Optional[str]) -> bool:
if s3_uri is None:
return False

return re.match("^s3://([^/]+)/?(.*)$", s3_uri) is not None
return re.match("^s3://([^/]+)/?(.*)$", s3_uri) is not None
2 changes: 1 addition & 1 deletion sagemaker-core/src/sagemaker/core/config/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -178,4 +178,4 @@
LOCAL,
LOCAL_CODE,
CONTAINER_CONFIG,
)
)
5 changes: 4 additions & 1 deletion sagemaker-core/src/sagemaker/core/config/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,10 @@
from botocore.utils import merge_dicts
from six.moves.urllib.parse import urlparse
from sagemaker.core.config.config_schema import SAGEMAKER_PYTHON_SDK_CONFIG_SCHEMA
from sagemaker.core.config.config_utils import non_repeating_log_factory, get_sagemaker_config_logger
from sagemaker.core.config.config_utils import (
non_repeating_log_factory,
get_sagemaker_config_logger,
)

logger = get_sagemaker_config_logger()
log_info_function = non_repeating_log_factory(logger, "info")
Expand Down
3 changes: 1 addition & 2 deletions sagemaker-core/src/sagemaker/core/config/config_schema.py
Original file line number Diff line number Diff line change
Expand Up @@ -733,7 +733,6 @@ def _simple_path(*args: str):
},
},
MODEL_TRAINER: {TYPE: OBJECT, ADDITIONAL_PROPERTIES: True},

REMOTE_FUNCTION: {
TYPE: OBJECT,
ADDITIONAL_PROPERTIES: False,
Expand Down Expand Up @@ -1218,4 +1217,4 @@ def _simple_path(*args: str):
},
},
"required": [LOCAL],
}
}
Loading
Loading