sagemaker-hyperpod 3.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (66) hide show
  1. sagemaker/__init__.py +0 -0
  2. sagemaker/hyperpod/__init__.py +2 -0
  3. sagemaker/hyperpod/cli/__init__.py +0 -0
  4. sagemaker/hyperpod/cli/clients/__init__.py +12 -0
  5. sagemaker/hyperpod/cli/clients/kubernetes_client.py +365 -0
  6. sagemaker/hyperpod/cli/commands/__init__.py +0 -0
  7. sagemaker/hyperpod/cli/commands/cluster.py +627 -0
  8. sagemaker/hyperpod/cli/commands/inference.py +659 -0
  9. sagemaker/hyperpod/cli/commands/training.py +347 -0
  10. sagemaker/hyperpod/cli/constants/__init__.py +0 -0
  11. sagemaker/hyperpod/cli/constants/command_constants.py +105 -0
  12. sagemaker/hyperpod/cli/constants/exception_constants.py +13 -0
  13. sagemaker/hyperpod/cli/constants/hyperpod_instance_types.py +100 -0
  14. sagemaker/hyperpod/cli/constants/kueue_constants.py +17 -0
  15. sagemaker/hyperpod/cli/constants/pytorch_constants.py +15 -0
  16. sagemaker/hyperpod/cli/hyp_cli.py +132 -0
  17. sagemaker/hyperpod/cli/inference_utils.py +121 -0
  18. sagemaker/hyperpod/cli/service/__init__.py +12 -0
  19. sagemaker/hyperpod/cli/service/cancel_training_job.py +70 -0
  20. sagemaker/hyperpod/cli/service/discover_namespaces.py +108 -0
  21. sagemaker/hyperpod/cli/service/exec_command.py +75 -0
  22. sagemaker/hyperpod/cli/service/get_logs.py +136 -0
  23. sagemaker/hyperpod/cli/service/get_namespaces.py +59 -0
  24. sagemaker/hyperpod/cli/service/get_training_job.py +110 -0
  25. sagemaker/hyperpod/cli/service/list_pods.py +138 -0
  26. sagemaker/hyperpod/cli/service/list_training_jobs.py +178 -0
  27. sagemaker/hyperpod/cli/service/self_subject_access_review.py +47 -0
  28. sagemaker/hyperpod/cli/telemetry/__init__.py +12 -0
  29. sagemaker/hyperpod/cli/telemetry/user_agent.py +36 -0
  30. sagemaker/hyperpod/cli/templates/__init__.py +12 -0
  31. sagemaker/hyperpod/cli/templates/k8s_pytorch_job_template.py +74 -0
  32. sagemaker/hyperpod/cli/training_utils.py +200 -0
  33. sagemaker/hyperpod/cli/utils.py +183 -0
  34. sagemaker/hyperpod/cli/validators/__init__.py +12 -0
  35. sagemaker/hyperpod/cli/validators/cluster_validator.py +52 -0
  36. sagemaker/hyperpod/cli/validators/job_validator.py +346 -0
  37. sagemaker/hyperpod/cli/validators/validator.py +75 -0
  38. sagemaker/hyperpod/common/__init__.py +0 -0
  39. sagemaker/hyperpod/common/config/__init__.py +1 -0
  40. sagemaker/hyperpod/common/config/metadata.py +18 -0
  41. sagemaker/hyperpod/common/utils.py +299 -0
  42. sagemaker/hyperpod/inference/__init__.py +11 -0
  43. sagemaker/hyperpod/inference/config/__init__.py +0 -0
  44. sagemaker/hyperpod/inference/config/constants.py +18 -0
  45. sagemaker/hyperpod/inference/config/hp_endpoint_config.py +767 -0
  46. sagemaker/hyperpod/inference/config/hp_jumpstart_endpoint_config.py +572 -0
  47. sagemaker/hyperpod/inference/hp_endpoint.py +203 -0
  48. sagemaker/hyperpod/inference/hp_endpoint_base.py +223 -0
  49. sagemaker/hyperpod/inference/hp_jumpstart_endpoint.py +232 -0
  50. sagemaker/hyperpod/observability/MonitoringConfig.py +10 -0
  51. sagemaker/hyperpod/observability/__init__.py +0 -0
  52. sagemaker/hyperpod/observability/constants.py +2 -0
  53. sagemaker/hyperpod/observability/utils.py +64 -0
  54. sagemaker/hyperpod/training/__init__.py +9 -0
  55. sagemaker/hyperpod/training/config/__init__.py +1 -0
  56. sagemaker/hyperpod/training/config/hyperpod_pytorch_job_config.py +2977 -0
  57. sagemaker/hyperpod/training/config/hyperpod_pytorch_job_status.py +3083 -0
  58. sagemaker/hyperpod/training/hyperpod_pytorch_job.py +253 -0
  59. sagemaker_hyperpod-3.0.0.dist-info/METADATA +534 -0
  60. sagemaker_hyperpod-3.0.0.dist-info/RECORD +66 -0
  61. sagemaker_hyperpod-3.0.0.dist-info/WHEEL +5 -0
  62. sagemaker_hyperpod-3.0.0.dist-info/entry_points.txt +2 -0
  63. sagemaker_hyperpod-3.0.0.dist-info/licenses/LICENSE +175 -0
  64. sagemaker_hyperpod-3.0.0.dist-info/licenses/NOTICE +1 -0
  65. sagemaker_hyperpod-3.0.0.dist-info/top_level.txt +1 -0
  66. sagemaker_hyperpod-3.0.0.dist-info/zip-safe +1 -0
@@ -0,0 +1,200 @@
1
+ import json
2
+ import pkgutil
3
+ import click
4
+ from typing import Callable, Optional, Mapping, Type
5
+
6
+
7
+ def load_schema_for_version(
8
+ version: str,
9
+ base_package: str,
10
+ ) -> dict:
11
+ """
12
+ Load schema.json from the top-level <base_package>.vX_Y_Z package.
13
+ """
14
+ ver_pkg = f"{base_package}.v{version.replace('.', '_')}"
15
+ raw = pkgutil.get_data(ver_pkg, "schema.json")
16
+ if raw is None:
17
+ raise click.ClickException(
18
+ f"Could not load schema.json for version {version} "
19
+ f"(looked in package {ver_pkg})"
20
+ )
21
+ return json.loads(raw)
22
+
23
+
24
+ def generate_click_command(
25
+ *,
26
+ version_key: Optional[str] = None,
27
+ schema_pkg: str = "hyperpod_jumpstart_inference_template",
28
+ registry: Mapping[str, Type] = None,
29
+ ) -> Callable:
30
+ """
31
+ Decorator factory that:
32
+ 1) Injects click.options from the JSON Schema under `schema_pkg`
33
+ 2) At runtime, pops `version`, builds the flat model from `registry`, calls .to_domain()
34
+ 3) Finally invokes your handler as `func(version, domain_config)`
35
+ - `version_key`: if given, hard-codes the version (no --version flag injected)
36
+ - `schema_pkg`: the importable package root to read schema.json from
37
+ - `registry`: a dict mapping version → flat‐model class, e.g. hyperpod_pytorch_job_template.registry.SCHEMA_REGISTRY
38
+ """
39
+ if registry is None:
40
+ raise ValueError("You must pass a registry mapping version→Model")
41
+
42
+ def decorator(func: Callable) -> Callable:
43
+ # Parser for the single JSON‐dict env var flag
44
+ def _parse_json_flag(ctx, param, value):
45
+ if value is None:
46
+ return None
47
+ try:
48
+ return json.loads(value)
49
+ except json.JSONDecodeError as e:
50
+ raise click.BadParameter(f"{param.name!r} must be valid JSON: {e}")
51
+
52
+ # Parser for list flags
53
+ def _parse_list_flag(ctx, param, value):
54
+ if value is None:
55
+ return None
56
+ # Remove brackets and split by comma
57
+ value = value.strip("[]")
58
+ return [item.strip() for item in value.split(",") if item.strip()]
59
+
60
+ # 1) the wrapper click will call
61
+ def wrapped_func(*args, **kwargs):
62
+ # extract version
63
+ version = version_key or kwargs.pop("version", "1.0")
64
+ debug = kwargs.pop("debug", False)
65
+
66
+ # look up the model class
67
+ Model = registry.get(version)
68
+ if Model is None:
69
+ raise click.ClickException(f"Unsupported schema version: {version}")
70
+
71
+ # validate & to_domain
72
+ flat = Model(**kwargs)
73
+ domain_config = flat.to_domain()
74
+
75
+ # call your handler
76
+ return func(version, debug, domain_config)
77
+
78
+ # 2) inject click options from JSON Schema
79
+ excluded_props = set(["version"])
80
+ if schema_pkg == "hyperpod_jumpstart_inference_template":
81
+ wrapped_func = click.option(
82
+ "--env",
83
+ callback=_parse_json_flag,
84
+ type=str,
85
+ default=None,
86
+ help=(
87
+ "JSON object of environment variables, e.g. "
88
+ '\'{"VAR1":"foo","VAR2":"bar"}\''
89
+ ),
90
+ metavar="JSON",
91
+ )(wrapped_func)
92
+ wrapped_func = click.option(
93
+ "--resources-limits",
94
+ callback=_parse_json_flag,
95
+ help='JSON object of resource limits, e.g. \'{"cpu":"2","memory":"4Gi"}\'',
96
+ metavar="JSON",
97
+ )(wrapped_func)
98
+
99
+ wrapped_func = click.option(
100
+ "--resources-requests",
101
+ callback=_parse_json_flag,
102
+ help='JSON object of resource requests, e.g. \'{"cpu":"1","memory":"2Gi"}\'',
103
+ metavar="JSON",
104
+ )(wrapped_func)
105
+
106
+ excluded_props = set(
107
+ ["version", "env", "resources_limits", "resources_requests"]
108
+ )
109
+
110
+ elif schema_pkg == "hyperpod_pytorch_job_template":
111
+ wrapped_func = click.option(
112
+ "--environment",
113
+ callback=_parse_json_flag,
114
+ type=str,
115
+ default=None,
116
+ help=(
117
+ "JSON object of environment variables, e.g. "
118
+ '\'{"VAR1":"foo","VAR2":"bar"}\''
119
+ ),
120
+ metavar="JSON",
121
+ )(wrapped_func)
122
+ wrapped_func = click.option(
123
+ "--label_selector",
124
+ callback=_parse_json_flag,
125
+ help='JSON object of resource limits, e.g. \'{"cpu":"2","memory":"4Gi"}\'',
126
+ metavar="JSON",
127
+ )(wrapped_func)
128
+
129
+ # Add list options
130
+ list_params = {
131
+ "command": "List of command arguments",
132
+ "args": "List of script arguments, e.g. '[--batch-size, 32, --learning-rate, 0.001]'",
133
+ "volumes": "List of volumes, e.g. '[vol1, vol2, vol3]'",
134
+ "persistent_volume_claims": "List of persistent volume claims, e.g. '[pvc1, pvc2]'",
135
+ }
136
+
137
+ for param_name, help_text in list_params.items():
138
+ wrapped_func = click.option(
139
+ f"--{param_name}",
140
+ callback=_parse_list_flag,
141
+ type=str,
142
+ default=None,
143
+ help=help_text,
144
+ metavar="LIST",
145
+ )(wrapped_func)
146
+
147
+ excluded_props = set(
148
+ [
149
+ "version",
150
+ "environment",
151
+ "label_selector",
152
+ "command",
153
+ "args",
154
+ "volumes",
155
+ "persistent_volume_claims",
156
+ ]
157
+ )
158
+
159
+ schema = load_schema_for_version(version_key or "1.0", schema_pkg)
160
+ props = schema.get("properties", {})
161
+ reqs = set(schema.get("required", []))
162
+
163
+ # reverse so flags appear in the same order as in schema.json
164
+ for name, spec in reversed(list(props.items())):
165
+ if name in excluded_props:
166
+ continue
167
+
168
+ # type inference
169
+ if "enum" in spec:
170
+ ctype = click.Choice(spec["enum"])
171
+ elif spec.get("type") == "integer":
172
+ ctype = int
173
+ elif spec.get("type") == "number":
174
+ ctype = float
175
+ elif spec.get("type") == "boolean":
176
+ ctype = bool
177
+ else:
178
+ ctype = str
179
+
180
+ wrapped_func = click.option(
181
+ f"--{name.replace('_','-')}",
182
+ required=(name in reqs),
183
+ default=spec.get("default", None),
184
+ show_default=("default" in spec),
185
+ type=ctype,
186
+ help=spec.get("description", ""),
187
+ )(wrapped_func)
188
+
189
+ # 3) if no hard-coded version_key, inject the top-level --version flag
190
+ if version_key is None:
191
+ wrapped_func = click.option(
192
+ "--version",
193
+ default="1.0",
194
+ show_default=True,
195
+ help="Schema version to use",
196
+ )(wrapped_func)
197
+
198
+ return wrapped_func
199
+
200
+ return decorator
@@ -0,0 +1,183 @@
1
+ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You
4
+ # may not use this file except in compliance with the License. A copy of
5
+ # the License is located at
6
+ #
7
+ # http://aws.amazon.com/apache2.0/
8
+ #
9
+ # or in the "license" file accompanying this file. This file is
10
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
+ # ANY KIND, either express or implied. See the License for the specific
12
+ # language governing permissions and limitations under the License.
13
+ import logging
14
+ import re
15
+ import json
16
+
17
+ import boto3
18
+ import botocore
19
+ from botocore.config import Config
20
+
21
+ from sagemaker.hyperpod.cli.constants.command_constants import (
22
+ GENERATED_LAUNCHER_CONFIG_FILE_PATH,
23
+ HYPERPOD_CLUSTER_CONTEXT_FILE_NAME,
24
+ )
25
+
26
+
27
+ def get_name_from_arn(arn: str) -> str:
28
+ """
29
+ Parse the EKS cluster name from an EKS ARN.
30
+
31
+ Args:
32
+ arn (str): The ARN of the EKS cluster.
33
+
34
+ Returns: str: The name of the EKS cluster if parsing is
35
+ successful, otherwise raise RuntimeError.
36
+ """
37
+ # Define the regex pattern to match the EKS ARN and capture the cluster name
38
+ pattern = r"arn:aws:eks:[\w-]+:\d+:cluster/([\w-]+)"
39
+ match = re.match(pattern, arn)
40
+
41
+ if match:
42
+ return match.group(1)
43
+ else:
44
+ raise RuntimeError("cannot get EKS cluster name")
45
+
46
+
47
+ def setup_logger(
48
+ name: str,
49
+ logging_level: int = logging.ERROR,
50
+ ) -> logging.Logger:
51
+ """
52
+ Set up a logger with a console handler and a formatter.
53
+
54
+ Args:
55
+ name (str): The name of the logger.
56
+ logging_level (int): The logging level.
57
+
58
+ Returns:
59
+ logging.Logger: The configured logger instance.
60
+ """
61
+ # Create a logger
62
+ logger = logging.getLogger(name)
63
+ logger.setLevel(logging_level) # Set the log level to ERROR
64
+
65
+ # Create a console handler
66
+ console_handler = logging.StreamHandler()
67
+ console_handler.setLevel(logging_level) # Set the log level for the handler
68
+
69
+ # Create a formatter and set it for the handler
70
+ formatter = logging.Formatter(
71
+ "%(asctime)s - %(name)s - %(levelname)s - %(message)s",
72
+ datefmt="%Y-%m-%d %H:%M:%S",
73
+ )
74
+ console_handler.setFormatter(formatter)
75
+
76
+ # Add the handler to the logger
77
+ logger.addHandler(console_handler)
78
+
79
+ return logger
80
+
81
+
82
+ def set_logging_level(
83
+ logger: logging.Logger,
84
+ logging_level: int,
85
+ ):
86
+ logger.setLevel(logging_level)
87
+ logger.handlers[0].setLevel(logging_level)
88
+
89
+
90
+ def get_sagemaker_client(
91
+ session: boto3.Session, config: Config = None
92
+ ) -> botocore.client.BaseClient:
93
+ return session.client(
94
+ service_name="sagemaker",
95
+ config=config,
96
+ )
97
+
98
+
99
+ def store_current_hyperpod_context(data):
100
+ with open(
101
+ GENERATED_LAUNCHER_CONFIG_FILE_PATH + HYPERPOD_CLUSTER_CONTEXT_FILE_NAME,
102
+ "w",
103
+ ) as hyperpod_current_context:
104
+ hyperpod_current_context.write(json.dumps(data, indent=4, default=str))
105
+
106
+
107
+ def _retrieve_current_hyperpod_context():
108
+ with open(
109
+ GENERATED_LAUNCHER_CONFIG_FILE_PATH + HYPERPOD_CLUSTER_CONTEXT_FILE_NAME,
110
+ "r",
111
+ ) as file:
112
+ return json.load(file)
113
+
114
+
115
+ def _validate_link(console_url):
116
+ pattern = "https:\/\/([a-z0-9-]+).console.aws.amazon.com\/sagemaker\/home\?region=([a-z0-9-]+)#\/cluster-management\/([a-zA-Z0-9-]+)"
117
+ match = re.match(pattern, console_url)
118
+ if match:
119
+ return True
120
+ else:
121
+ return False
122
+
123
+
124
+ def validate_region_and_cluster_name(region, cluster_name):
125
+ output = False
126
+ region_char_list = region.split("-")
127
+
128
+ if len(region_char_list) != 3:
129
+ return False
130
+
131
+ region_prefix_match = re.match("[a-z]+", region_char_list[0])
132
+ region_match = re.match("[a-z]+", region_char_list[1])
133
+ region_suffix_match = re.match("[0-9]+", region_char_list[2])
134
+
135
+ region_prefix_length = len(region_char_list[0])
136
+ region_length = len(region_char_list[1])
137
+ region_suffix_length = len(region_char_list[2])
138
+
139
+ cluster_name_match = re.match("[a-zA-Z0-9-]+", cluster_name)
140
+
141
+ if (
142
+ region_prefix_match
143
+ and region_match
144
+ and region_suffix_match
145
+ and region_prefix_length == 2
146
+ and region_suffix_length == 1
147
+ and region_length >= 4
148
+ and region_length < 10
149
+ and cluster_name_match
150
+ and len(cluster_name) >= 1
151
+ and len(cluster_name) <= 63
152
+ ):
153
+ output = True
154
+ return output
155
+
156
+
157
+ def get_cluster_console_url():
158
+ hyperpod_context_cluster = _retrieve_current_hyperpod_context()
159
+ console_url = None
160
+ if (
161
+ hyperpod_context_cluster
162
+ and hyperpod_context_cluster.get("ClusterArn")
163
+ and hyperpod_context_cluster.get("ClusterName")
164
+ ):
165
+ region = hyperpod_context_cluster.get("ClusterArn").split(":")[3]
166
+ cluster_name = hyperpod_context_cluster.get("ClusterName")
167
+
168
+ console_url = (
169
+ f"https://{region}.console.aws.amazon.com/sagemaker/"
170
+ f"home?region={region}#/cluster-management/{cluster_name}"
171
+ )
172
+ if _validate_link(console_url) and validate_region_and_cluster_name(region, cluster_name):
173
+ return console_url
174
+ return None
175
+
176
+ def get_eks_cluster_name():
177
+ hyperpod_context_cluster = _retrieve_current_hyperpod_context()
178
+ eks_cluster_arn = hyperpod_context_cluster.get("Orchestrator", {}).get("Eks", {}).get("ClusterArn", '')
179
+ return eks_cluster_arn.split('cluster/')[-1]
180
+
181
+ def get_hyperpod_cluster_region():
182
+ hyperpod_context_cluster = _retrieve_current_hyperpod_context()
183
+ return hyperpod_context_cluster.get("ClusterArn").split(":")[3]
@@ -0,0 +1,12 @@
1
+ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You
4
+ # may not use this file except in compliance with the License. A copy of
5
+ # the License is located at
6
+ #
7
+ # http://aws.amazon.com/apache2.0/
8
+ #
9
+ # or in the "license" file accompanying this file. This file is
10
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
+ # ANY KIND, either express or implied. See the License for the specific
12
+ # language governing permissions and limitations under the License.
@@ -0,0 +1,52 @@
1
+ # Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License"). You
4
+ # may not use this file except in compliance with the License. A copy of
5
+ # the License is located at
6
+ #
7
+ # http://aws.amazon.com/apache2.0/
8
+ #
9
+ # or in the "license" file accompanying this file. This file is
10
+ # distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF
11
+ # ANY KIND, either express or implied. See the License for the specific
12
+ # language governing permissions and limitations under the License.
13
+ from typing import Optional
14
+
15
+ import boto3
16
+ from botocore.exceptions import ClientError
17
+
18
+ from sagemaker.hyperpod.cli.utils import setup_logger
19
+ from sagemaker.hyperpod.cli.validators.validator import (
20
+ Validator,
21
+ )
22
+
23
+ logger = setup_logger(__name__)
24
+
25
+
26
+ class ClusterValidator(Validator):
27
+ def __init__(self):
28
+ super().__init__()
29
+
30
+ def validate_cluster_and_get_eks_arn(
31
+ self, cluster_name: str, sm_client: boto3.client
32
+ ) -> Optional[str]:
33
+ try:
34
+ hp_cluster_details = sm_client.describe_cluster(ClusterName=cluster_name)
35
+ if (
36
+ "Orchestrator" not in hp_cluster_details
37
+ or "Eks" not in hp_cluster_details["Orchestrator"]
38
+ ):
39
+ logger.warning(
40
+ f"HyperPod cluster {cluster_name} Orchestrator not exist or is not Eks."
41
+ )
42
+ return None
43
+ return hp_cluster_details["Orchestrator"]["Eks"]["ClusterArn"]
44
+ except ClientError as e:
45
+ if e.response["Error"]["Code"] == "ResourceNotFoundException":
46
+ logger.error(f"HyperPod cluster {cluster_name} not found.")
47
+ else:
48
+ logger.error(f"Validate HyperPod cluster {cluster_name} error: {e}")
49
+ return None
50
+ except Exception as e:
51
+ logger.error(f"Validate HyperPod cluster {cluster_name} error: {e}")
52
+ return None