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,346 @@
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 json
14
+ import os
15
+ import yaml
16
+ from yaml.loader import SafeLoader
17
+ from typing import Optional
18
+
19
+ from sagemaker.hyperpod.cli.clients.kubernetes_client import KubernetesClient
20
+ from sagemaker.hyperpod.cli.constants.command_constants import (
21
+ KUEUE_WORKLOAD_PRIORITY_CLASS_LABEL_KEY,
22
+ SAGEMAKER_TRAINING_LAUNCHER_DIR,
23
+ RestartPolicy,
24
+ KUEUE_QUEUE_NAME_LABEL_KEY,
25
+ HYPERPOD_AUTO_RESUME_ANNOTATION_KEY,
26
+ HYPERPOD_MAX_RETRY_ANNOTATION_KEY,
27
+ INSTANCE_TYPE_LABEL,
28
+ SchedulerType
29
+ )
30
+ from sagemaker.hyperpod.cli.constants.hyperpod_instance_types import (
31
+ HyperpodInstanceType,
32
+ )
33
+ from sagemaker.hyperpod.cli.utils import setup_logger
34
+ from sagemaker.hyperpod.cli.validators.validator import (
35
+ Validator,
36
+ )
37
+
38
+ logger = setup_logger(__name__)
39
+
40
+ RECIPES_DIR = os.path.join(SAGEMAKER_TRAINING_LAUNCHER_DIR, "recipes_collection/recipes")
41
+
42
+ class JobValidator(Validator):
43
+ def __init__(self):
44
+ super().__init__()
45
+
46
+ def validate_start_job_args(
47
+ self,
48
+ config_file: Optional[str],
49
+ job_name: Optional[str],
50
+ node_count: Optional[int],
51
+ instance_type: Optional[str],
52
+ image: Optional[str],
53
+ job_kind: Optional[str],
54
+ command: Optional[str],
55
+ label_selector: Optional[str],
56
+ scheduler_type: Optional[str],
57
+ queue_name: Optional[str],
58
+ priority: Optional[str],
59
+ auto_resume: bool,
60
+ restart_policy: Optional[RestartPolicy],
61
+ max_retry: Optional[int],
62
+ namespace: Optional[str],
63
+ entry_script: Optional[str],
64
+ recipe: Optional[str] = None,
65
+ ):
66
+ if job_kind is not None and job_kind != "kubeflow/PyTorchJob":
67
+ logger.error("The only supported 'job-kind' is 'kubeflow/PyTorchJob'.")
68
+ return False
69
+
70
+ if command is not None and command != "torchrun":
71
+ logger.error("The only supported 'command' is 'torchrun'.")
72
+ return False
73
+
74
+ if scheduler_type is not None and scheduler_type not in SchedulerType.get_values():
75
+ logger.error(f"The only supported 'scheduler_type' are {SchedulerType.get_values()}.")
76
+ return False
77
+
78
+ if config_file is not None and job_name is not None:
79
+ logger.error(
80
+ "Please provide only 'config-file' to submit job using custom script or 'job-name' to submit job via CLI arguments"
81
+ )
82
+ return False
83
+
84
+ if config_file is not None and recipe is not None:
85
+ logger.error(
86
+ "Please provide only 'config-file' to submit job using custom script or 'recipe' for recipe-based jobs"
87
+ )
88
+ return False
89
+
90
+ if config_file is None and job_name is None and recipe is None:
91
+ logger.error(
92
+ "Please provide either 'recipe' for recipe-based jobs or 'config-file' to submit job using config file or 'job-name' to submit job via CLI arguments"
93
+ )
94
+ return False
95
+
96
+ if job_name is not None:
97
+ if entry_script is None:
98
+ logger.error("Please provide 'entry-script' for the training job")
99
+ return False
100
+
101
+ if node_count is None:
102
+ logger.error(
103
+ "Please provide 'node-count' to specify number of nodes used for training job"
104
+ )
105
+ return False
106
+
107
+ if image is None:
108
+ logger.error(
109
+ "Please provide 'image' to specify the training image for training job"
110
+ )
111
+ return False
112
+
113
+ if label_selector is not None:
114
+ if not _validate_json_str(label_selector):
115
+ logger.error("Please provide valid 'label-selector' JSON string")
116
+ return False
117
+ if not is_dict_str_list_str(json.loads(label_selector)):
118
+ logger.error(
119
+ "Please ensure 'label-selector' keys are string type and values are string or list of string type"
120
+ )
121
+ return False
122
+
123
+ if not validate_scheduler_related_fields(scheduler_type, namespace, priority):
124
+ return False
125
+
126
+ return validate_hyperpod_related_fields(
127
+ instance_type,
128
+ queue_name,
129
+ priority,
130
+ auto_resume,
131
+ restart_policy,
132
+ max_retry,
133
+ namespace
134
+ )
135
+ if recipe is not None:
136
+ return validate_recipe_file(recipe)
137
+
138
+ return True
139
+
140
+
141
+ def verify_and_load_yaml(file_path: str):
142
+ if not os.path.exists(file_path):
143
+ logger.error(f"Configuration file {file_path} does not exist.")
144
+ return None
145
+ try:
146
+ with open(file_path, "r") as file:
147
+ # Attempt to load the YAML file
148
+ data = yaml.load(file, Loader=SafeLoader)
149
+ return data
150
+ except Exception as e:
151
+ logger.error(
152
+ f"The config file {file_path} is not a valid YAML file. Error: {e}"
153
+ )
154
+ return None
155
+
156
+
157
+ def validate_yaml_content(data):
158
+ cluster_fields = data.get("cluster")
159
+ if cluster_fields is None:
160
+ logger.error("Please ensure 'cluster' field provided in the config file")
161
+ return False
162
+ cluster_type = cluster_fields.get("cluster_type")
163
+ if cluster_type is None or cluster_type != "k8s":
164
+ logger.error("Only support 'k8s' cluster type currently.")
165
+ return False
166
+ cluster_config_fields = cluster_fields.get("cluster_config")
167
+ if cluster_config_fields is None:
168
+ logger.error(
169
+ "Please ensure 'cluster' contains 'cluster_config' field in the config file"
170
+ )
171
+ return False
172
+
173
+ custom_labels = cluster_config_fields.get("custom_labels")
174
+ annotations = cluster_config_fields.get("annotations")
175
+ namespace = cluster_config_fields.get("namespace")
176
+ scheduler_type = cluster_config_fields.get("scheduler_type", SchedulerType.get_default().value)
177
+
178
+ if scheduler_type not in SchedulerType.get_values():
179
+ logger.error(
180
+ f"Unsupported scheduler type '{scheduler_type}', only {SchedulerType.get_values()} are allowed."
181
+ )
182
+ return False
183
+ instance_type = cluster_fields.get("instance_type", None)
184
+ queue_name = None
185
+ if custom_labels is not None:
186
+ queue_name = custom_labels.get(KUEUE_QUEUE_NAME_LABEL_KEY, None)
187
+
188
+ label_selector = cluster_config_fields.setdefault("label_selector",{})
189
+ required_labels = label_selector.get("required", {})
190
+ preferred_labels = label_selector.get("preferred", {})
191
+
192
+ if (
193
+ not required_labels.get(INSTANCE_TYPE_LABEL) and
194
+ not preferred_labels.get(INSTANCE_TYPE_LABEL)
195
+ ):
196
+ if "required" not in label_selector:
197
+ label_selector["required"] = {}
198
+ label_selector["required"][INSTANCE_TYPE_LABEL] = (
199
+ [str(instance_type)]
200
+ )
201
+
202
+ auto_resume = False
203
+ max_retry = None
204
+ if annotations is not None:
205
+ auto_resume = annotations.get(HYPERPOD_AUTO_RESUME_ANNOTATION_KEY, False)
206
+ max_retry = annotations.get(HYPERPOD_MAX_RETRY_ANNOTATION_KEY, None)
207
+ if auto_resume and (annotations.get(HYPERPOD_MAX_RETRY_ANNOTATION_KEY) is None):
208
+ logger.error(
209
+ f"Please provide both '{HYPERPOD_AUTO_RESUME_ANNOTATION_KEY}' "
210
+ f"and '{HYPERPOD_MAX_RETRY_ANNOTATION_KEY}' "
211
+ f"annotations to use Auto Resume feature"
212
+ )
213
+ return False
214
+
215
+ priority = cluster_config_fields.get("priority_class_name", None)
216
+ restart_policy = cluster_config_fields.get("restartPolicy", None)
217
+ workload_priority = None
218
+
219
+ if custom_labels is not None:
220
+ workload_priority = custom_labels.get(KUEUE_WORKLOAD_PRIORITY_CLASS_LABEL_KEY, None)
221
+
222
+ if not validate_scheduler_related_fields(scheduler_type, namespace, workload_priority):
223
+ return False
224
+
225
+ if not validate_hyperpod_related_fields(
226
+ instance_type,
227
+ queue_name,
228
+ priority,
229
+ auto_resume,
230
+ restart_policy,
231
+ max_retry,
232
+ namespace,
233
+ ):
234
+ return False
235
+
236
+ return True
237
+
238
+
239
+ def validate_hyperpod_related_fields(
240
+ instance_type: Optional[str],
241
+ queue_name: Optional[str],
242
+ priority: Optional[str],
243
+ auto_resume: bool,
244
+ restart_policy: Optional[RestartPolicy],
245
+ max_retry: Optional[int],
246
+ namespace: Optional[str]
247
+ ):
248
+ logger.info(f"instance_type: {instance_type}, queue_name: {queue_name}, priority: {priority}, auto_resume: {auto_resume}, restart_policy: {restart_policy}, max_retry: {max_retry}, namespace: {namespace}")
249
+ if instance_type is None:
250
+ logger.error(
251
+ "Please provide 'instance-type' to specify instance type for training job"
252
+ )
253
+ return False
254
+ else:
255
+ if instance_type not in [member.value for member in HyperpodInstanceType]:
256
+ logger.error("Please provide SageMaker HyperPod supported 'instance-type'")
257
+ return False
258
+
259
+ if max_retry and not auto_resume:
260
+ logger.error("Please enable 'auto_resume' with 'max_retry' option.")
261
+ return False
262
+
263
+ if auto_resume and restart_policy != RestartPolicy.ON_FAILURE.value:
264
+ logger.error(
265
+ "To enable 'auto_resume', please ensure the 'restart-policy' is 'OnFailure'. "
266
+ )
267
+ return False
268
+
269
+ return True
270
+
271
+
272
+ def validate_scheduler_related_fields(
273
+ scheduler_type: SchedulerType,
274
+ namespace: Optional[str],
275
+ priority: Optional[str],
276
+ ):
277
+ if scheduler_type == SchedulerType.SAGEMAKER.value:
278
+ k8s_client = KubernetesClient()
279
+ sm_managed_namespace = k8s_client.get_sagemaker_managed_namespace(namespace)
280
+ if namespace and not sm_managed_namespace:
281
+ logger.error(
282
+ f"Scheduler type is '{SchedulerType.SAGEMAKER.value}' however cannot find namespace '{namespace}' managed by SageMaker. Please ensure namespace exists and you have 'get' access to it."
283
+ )
284
+ return False
285
+
286
+ if priority is not None :
287
+ priority_classes = [item['metadata']['name'] for item in k8s_client.list_workload_priority_classes()['items']]
288
+ if priority not in priority_classes:
289
+ logger.error(
290
+ f"Workload priority class '{priority}' is not found. Please ensure the priority exists and you have 'get' access to 'WorkloadPriorityClass'. Valid priority values are {priority_classes}."
291
+ )
292
+ return False
293
+ return True
294
+
295
+ def validate_recipe_file(recipe: str):
296
+ full_recipe_path = os.path.join(RECIPES_DIR, f"{recipe}.yaml")
297
+
298
+ if os.path.exists(full_recipe_path) and os.path.isfile(full_recipe_path):
299
+ logger.info(f"Recipe file found: {full_recipe_path}")
300
+ return True
301
+
302
+ logger.error(f"Recipe file '{recipe}.yaml' not found in {RECIPES_DIR}")
303
+ return False
304
+
305
+ def is_dict_str_list_str(data: dict) -> bool:
306
+ """
307
+ Check if the given dictionary is of type Dict[str, List[str]].
308
+
309
+ Parameters:
310
+ data (dict): The dictionary to check.
311
+
312
+ Returns:
313
+ bool: True if the dictionary is of type Dict[str, List[str]], False otherwise.
314
+ """
315
+ for key, value in data.items():
316
+ if not isinstance(value, list) and not isinstance(value, str):
317
+ return False
318
+ elif isinstance(value, list) and not all(
319
+ isinstance(item, str) for item in value
320
+ ):
321
+ return False
322
+ return True
323
+ def _validate_json_str(
324
+ json_str: str,
325
+ ):
326
+ """
327
+ Convert a JSON string to a dictionary.
328
+
329
+ Parameters:
330
+ json_string (str): A string in JSON format.
331
+
332
+ Returns:
333
+ dict: A dictionary representation of the JSON string.
334
+ """
335
+ try:
336
+ # Attempt to convert the JSON string to a dictionary
337
+ json.loads(json_str)
338
+ return True
339
+ except json.JSONDecodeError as e:
340
+ # Handle JSON decoding errors
341
+ logger.error(f"JSON decoding failed: {e}")
342
+ return False
343
+ except Exception as e:
344
+ # Catch any other exceptions
345
+ logger.error(f"An unexpected error occurred: {e}")
346
+ return False
@@ -0,0 +1,75 @@
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 boto3
14
+ from botocore.exceptions import (
15
+ ClientError,
16
+ NoCredentialsError,
17
+ PartialCredentialsError,
18
+ )
19
+
20
+ from sagemaker.hyperpod.cli.utils import setup_logger
21
+
22
+ logger = setup_logger(__name__)
23
+
24
+
25
+ class Validator:
26
+ def __init__(self):
27
+ return
28
+
29
+ def validate(self):
30
+ """
31
+ Abstract validate method to be implemented in subclasses.
32
+ """
33
+ return NotImplementedError()
34
+
35
+ def validate_aws_credential(self, session: boto3.Session) -> bool:
36
+ """
37
+ Validate AWS credentials to ensure AWS credentials configured
38
+ a valida credential exist in current session
39
+
40
+ Returns:
41
+ bool: True aws credentials are valid, False otherwise.
42
+ """
43
+ try:
44
+ # Check if credentials are available
45
+ credentials = session.get_credentials()
46
+ if not credentials:
47
+ logger.error(
48
+ "No AWS credentials found. Please configure your AWS credentials."
49
+ )
50
+ return False
51
+
52
+ # Get an STS client to check the credentials
53
+ sts = session.client("sts")
54
+
55
+ # Call get_caller_identity to validate credentials
56
+ sts.get_caller_identity()
57
+
58
+ logger.debug("AWS credentials are valid.")
59
+ return True
60
+ except (NoCredentialsError, PartialCredentialsError) as e:
61
+ logger.error(f"No AWS credentials or partial AWS credentials provided: {e}")
62
+ return False
63
+ except ClientError as e:
64
+ error_code = e.response["Error"]["Code"]
65
+ if error_code == "ExpiredToken":
66
+ logger.error(
67
+ "AWS credentials have expired. Please refresh your AWS "
68
+ "credentials"
69
+ )
70
+ else:
71
+ logger.error(f"Get credentials AWS client error: {e}")
72
+ return False
73
+ except Exception as e:
74
+ logger.error(f"Unexpected error to get AWS credentials: {e}")
75
+ return False
File without changes
@@ -0,0 +1 @@
1
+ from sagemaker.hyperpod.common.config.metadata import Metadata
@@ -0,0 +1,18 @@
1
+ from typing import Dict, Optional
2
+ from pydantic import Field, BaseModel
3
+
4
+
5
+ class Metadata(BaseModel):
6
+ """Metadata class"""
7
+
8
+ name: str = Field(
9
+ description="Name must match the name of one entry in pod.spec.resourceClaims of the Pod where this field is used. It makes that resource available inside a container."
10
+ )
11
+ namespace: Optional[str] = Field(
12
+ default=None,
13
+ description="Name must match the name of one entry in pod.spec.resourceClaims of the Pod where this field is used. It makes that resource available inside a container.",
14
+ )
15
+ labels: Optional[Dict[str, str]] = Field(
16
+ default=None,
17
+ description="Labels are key value pairs that are attached to objects, such as Pod. Labels are intended to be used to specify identifying attributes of objects. The system ignores labels that are not in the service's selector. Labels can only be added to objects during creation. More info: XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX",
18
+ )