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
sagemaker/__init__.py ADDED
File without changes
@@ -0,0 +1,2 @@
1
+ from .common.utils import *
2
+ from .observability.MonitoringConfig import MonitoringConfig
File without changes
@@ -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,365 @@
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 os
14
+ from typing import List, Optional
15
+
16
+ import yaml
17
+ from kubernetes import client, config, stream
18
+ from kubernetes.client import (
19
+ V1Namespace,
20
+ V1NamespaceList,
21
+ )
22
+ from kubernetes.config import (
23
+ KUBE_CONFIG_DEFAULT_LOCATION,
24
+ )
25
+ from kubernetes.client.rest import ApiException
26
+ from sagemaker.hyperpod.cli.constants.command_constants import (
27
+ SAGEMAKER_MANAGED_QUEUE_LABEL,
28
+ TEMP_KUBE_CONFIG_FILE,
29
+ )
30
+ from sagemaker.hyperpod.cli.constants.exception_constants import RESOURCE_NOT_FOUND_CODE
31
+ from sagemaker.hyperpod.cli.constants.kueue_constants import (
32
+ CLUSTER_QUEUE_PRIORITY_CLASS_CUSTOM_OBJECT_PLURAL,
33
+ KUEUE_CUSTOM_OBJECT_GROUP,
34
+ KUEUE_CUSTOM_OBJECT_VERSION,
35
+ WORKLOAD_CUSTOM_OBJECT_PLURAL,
36
+ WORKLOAD_PRIORITY_CLASS_CUSTOM_OBJECT_PLURAL,
37
+ )
38
+ from sagemaker.hyperpod.cli.constants.pytorch_constants import (
39
+ PYTORCH_CUSTOM_OBJECT_GROUP,
40
+ PYTORCH_CUSTOM_OBJECT_PLURAL,
41
+ PYTORCH_CUSTOM_OBJECT_VERSION,
42
+ )
43
+ from sagemaker.hyperpod.cli.utils import setup_logger
44
+
45
+ logger = setup_logger(__name__)
46
+
47
+ KUBE_CONFIG_PATH = os.path.expanduser(KUBE_CONFIG_DEFAULT_LOCATION)
48
+
49
+
50
+ class KubernetesClient:
51
+ _instance = None
52
+ _kube_client = None
53
+
54
+ def __new__(cls, is_get_capacity: bool = False) -> "KubernetesClient":
55
+ if cls._instance is None:
56
+ cls._instance = super(KubernetesClient, cls).__new__(cls)
57
+ config.load_kube_config(
58
+ config_file=KUBE_CONFIG_PATH
59
+ if not is_get_capacity
60
+ else TEMP_KUBE_CONFIG_FILE
61
+ ) # or config.load_incluster_config() for in-cluster config
62
+ cls._instance._kube_client = client.ApiClient()
63
+ return cls._instance
64
+
65
+ def set_context(
66
+ self,
67
+ context_name: str,
68
+ namespace: Optional[str],
69
+ ) -> None:
70
+ """
71
+ Set the current context in the kubeconfig file.
72
+
73
+ Args:
74
+ context_name (str): The name of the context to set as current.
75
+ namespace (str): The name of the namespace to use.
76
+ """
77
+ with open(KUBE_CONFIG_PATH, "r") as file:
78
+ kubeconfig = yaml.safe_load(file)
79
+
80
+ # Check if the context exists in the kubeconfig and update the context namespace
81
+ # when namespace is specified in command line
82
+ exist = False
83
+ for context in kubeconfig["contexts"]:
84
+ if context["name"] == context_name:
85
+ if namespace is not None:
86
+ context["context"]["namespace"] = namespace
87
+ logger.debug(f"updated the namespace to {namespace}")
88
+ else:
89
+ context["context"].pop("namespace", None)
90
+ exist = True
91
+
92
+ if not exist:
93
+ raise ValueError(f"Context '{context_name}' not found in kubeconfig file")
94
+
95
+ # Set the current context
96
+ kubeconfig["current-context"] = context_name
97
+
98
+ # Write the updated kubeconfig back to the file
99
+ with open(KUBE_CONFIG_PATH, "w") as file:
100
+ yaml.safe_dump(kubeconfig, file)
101
+
102
+ # Load the updated kubeconfig
103
+ config.load_kube_config(config_file=KUBE_CONFIG_PATH)
104
+ logger.debug(f"Current context set to '{context_name}'")
105
+
106
+ def get_core_v1_api(self) -> client.CoreV1Api:
107
+ """
108
+ Get the CoreV1Api client.
109
+
110
+ Returns:
111
+ client.CoreV1Api: The CoreV1Api client.
112
+ """
113
+ if self._kube_client is None:
114
+ raise RuntimeError(
115
+ "Kubernetes client is not initialized. Call set_context() first."
116
+ )
117
+ return client.CoreV1Api(self._kube_client)
118
+
119
+ def get_apps_v1_api(self) -> client.AppsV1Api:
120
+ """
121
+ Get the AppsV1Api client.
122
+
123
+ Returns:
124
+ client.AppsV1Api: The AppsV1Api client.
125
+ """
126
+ if self._kube_client is None:
127
+ raise RuntimeError(
128
+ "Kubernetes client is not initialized. Call set_context() first."
129
+ )
130
+ return client.AppsV1Api(self._kube_client)
131
+
132
+ def get_auth_v1_api(self) -> client.AuthorizationV1Api:
133
+ """
134
+ Get the AuthorizationV1Api client.
135
+
136
+ Returns:
137
+ client.AuthorizationV1Api: The AuthorizationV1Api client.
138
+ """
139
+
140
+ if self._kube_client is None:
141
+ raise RuntimeError(
142
+ "Kubernetes client is not initialized. Call set_context() first."
143
+ )
144
+ return client.AuthorizationV1Api(self._kube_client)
145
+
146
+ def context_exists(self, context: str) -> bool:
147
+ """
148
+ Check if the specified context exists in the kubeconfig.
149
+
150
+ Args:
151
+ context (str): The name of the context to check.
152
+
153
+ Returns:
154
+ bool: True if the context exists, False otherwise.
155
+ """
156
+ try:
157
+ contexts, _ = config.list_kube_config_contexts()
158
+ return any(ctx["name"] == context for ctx in contexts)
159
+ except Exception as e:
160
+ raise RuntimeError(f"Failed to check Kubernetes context: {e}")
161
+
162
+ def list_node_with_temp_config(
163
+ self,
164
+ file: str,
165
+ selector_str: str,
166
+ ) -> List:
167
+ """
168
+ Load kubeconfig from a local file
169
+
170
+ Args:
171
+ file (str): The path to the kubeconfig file
172
+ selector_str (str): Selector to filter
173
+ """
174
+ config.load_kube_config(config_file=file)
175
+ v1Client = client.CoreV1Api()
176
+ _continue = None
177
+
178
+ nodes = []
179
+ while True:
180
+ response = v1Client.list_node(
181
+ label_selector=selector_str,
182
+ limit=200, # Set a reasonable limit
183
+ _continue=_continue,
184
+ )
185
+ nodes.extend(response.items)
186
+
187
+ _continue = response._metadata._continue # Get the continue token
188
+
189
+ if not _continue: # If there's no more data, break the loop
190
+ break
191
+ return nodes
192
+
193
+ def get_current_context_namespace(
194
+ self,
195
+ ) -> str:
196
+ """
197
+ Returns current user namespace context
198
+ """
199
+ contexts, active_context = config.list_kube_config_contexts()
200
+ return active_context["context"]["namespace"] if "namespace" in active_context["context"] else None
201
+
202
+ def list_namespaces(self) -> List[str]:
203
+ """
204
+ returns all namespaces
205
+ """
206
+ result: List[str] = []
207
+ namespaces: V1NamespaceList = client.CoreV1Api().list_namespace()
208
+ namespace: V1Namespace
209
+ for namespace in namespaces.items:
210
+ if namespace.metadata and namespace.metadata.name:
211
+ result.append(namespace.metadata.name)
212
+ return result
213
+
214
+ def get_sagemaker_managed_namespace(self, namespace: Optional[str]):
215
+ """
216
+ Verify if a namespace is sagemaker managed. SageMaker created namespaces have custom label
217
+ attached, indicating that the namespace is SageMaker managed.
218
+
219
+ Args:
220
+ namespace (Optional[str]): The input namespace that will be checked.
221
+
222
+ Returns:
223
+ Optional[V1Namespace]: V1Namespace response if namespace exists and it is verified being managed by SageMaker. Otherwise return None.
224
+ """
225
+ if namespace is None:
226
+ return None
227
+ try:
228
+ response = client.CoreV1Api().read_namespace(name=namespace)
229
+ labels = response.metadata.labels
230
+ if labels and SAGEMAKER_MANAGED_QUEUE_LABEL in labels and labels[SAGEMAKER_MANAGED_QUEUE_LABEL] == "true":
231
+ return response
232
+ except ApiException as e:
233
+ if e.status == RESOURCE_NOT_FOUND_CODE:
234
+ return None
235
+ else:
236
+ raise e
237
+ return None
238
+
239
+ def list_pods_with_labels(self, namespace: str, label_selector: str):
240
+ return client.CoreV1Api().list_namespaced_pod(
241
+ namespace=namespace,
242
+ label_selector=label_selector,
243
+ )
244
+
245
+ def list_pods_in_all_namespaces_with_labels(self, label_selector: str):
246
+ v1Client = client.CoreV1Api()
247
+ pods = []
248
+ _continue = None
249
+
250
+ while True:
251
+ response = v1Client.list_pod_for_all_namespaces(
252
+ label_selector=label_selector,
253
+ limit=200, # Set a reasonable limit
254
+ _continue=_continue,
255
+ )
256
+ pods.extend(response.items)
257
+
258
+ _continue = response._metadata._continue # Get the continue token
259
+
260
+ if not _continue: # If there's no more data, break the loop
261
+ break
262
+
263
+ return pods
264
+
265
+ def get_logs_for_pod(self, pod_name: str, namespace: str):
266
+ return client.CoreV1Api().read_namespaced_pod_log(
267
+ name=pod_name, namespace=namespace
268
+ )
269
+
270
+ def get_job(self, job_name: str, namespace: str):
271
+ return client.CustomObjectsApi().get_namespaced_custom_object(
272
+ group=PYTORCH_CUSTOM_OBJECT_GROUP,
273
+ version=PYTORCH_CUSTOM_OBJECT_VERSION,
274
+ namespace=namespace,
275
+ plural=PYTORCH_CUSTOM_OBJECT_PLURAL,
276
+ name=job_name,
277
+ )
278
+
279
+ def get_pod_details(self, pod_name: str, namespace: str):
280
+ return client.CoreV1Api().read_namespaced_pod(
281
+ name=pod_name, namespace=namespace
282
+ )
283
+
284
+ def delete_training_job(self, job_name: str, namespace: str):
285
+ return client.CustomObjectsApi().delete_namespaced_custom_object(
286
+ group=PYTORCH_CUSTOM_OBJECT_GROUP,
287
+ version=PYTORCH_CUSTOM_OBJECT_VERSION,
288
+ namespace=namespace,
289
+ plural=PYTORCH_CUSTOM_OBJECT_PLURAL,
290
+ name=job_name,
291
+ )
292
+
293
+ def list_training_jobs(
294
+ self,
295
+ namespace: str,
296
+ label_selector: Optional[str],
297
+ ):
298
+ return client.CustomObjectsApi().list_namespaced_custom_object(
299
+ group=PYTORCH_CUSTOM_OBJECT_GROUP,
300
+ version=PYTORCH_CUSTOM_OBJECT_VERSION,
301
+ namespace=namespace,
302
+ plural=PYTORCH_CUSTOM_OBJECT_PLURAL,
303
+ label_selector=label_selector,
304
+ )
305
+
306
+ def check_if_namespace_exists(self, namespace: str):
307
+ try:
308
+ client.CoreV1Api().read_namespace(name=namespace)
309
+ return True
310
+ except client.rest.ApiException as e:
311
+ if e.status == 404:
312
+ return False
313
+ else:
314
+ print(f"Exception when calling read_namespace: {e}")
315
+ raise e
316
+
317
+ def exec_command_on_pod(
318
+ self,
319
+ pod: str,
320
+ namespace: str,
321
+ bash_command: str,
322
+ ):
323
+ return stream.stream(
324
+ client.CoreV1Api().connect_get_namespaced_pod_exec,
325
+ stderr=True,
326
+ stdout=True,
327
+ name=pod,
328
+ namespace=namespace,
329
+ command=bash_command,
330
+ )
331
+
332
+ def patch_workload(self, workload_name: str, namespace: str, patch_body: str):
333
+ return client.CustomObjectsApi().patch_namespaced_custom_object(
334
+ group=KUEUE_CUSTOM_OBJECT_GROUP,
335
+ version=KUEUE_CUSTOM_OBJECT_VERSION,
336
+ namespace=namespace,
337
+ plural=WORKLOAD_CUSTOM_OBJECT_PLURAL,
338
+ name=workload_name,
339
+ body=patch_body
340
+ )
341
+
342
+ def get_workload_by_label(self, label_selector: str, namespace: str):
343
+ return client.CustomObjectsApi().list_namespaced_custom_object(
344
+ group=KUEUE_CUSTOM_OBJECT_GROUP,
345
+ version=KUEUE_CUSTOM_OBJECT_VERSION,
346
+ namespace=namespace,
347
+ plural=WORKLOAD_CUSTOM_OBJECT_PLURAL,
348
+ label_selector=label_selector,
349
+ )
350
+
351
+ def list_workload_priority_classes(self):
352
+ return client.CustomObjectsApi().list_cluster_custom_object(
353
+ group=KUEUE_CUSTOM_OBJECT_GROUP,
354
+ version=KUEUE_CUSTOM_OBJECT_VERSION,
355
+ plural=WORKLOAD_PRIORITY_CLASS_CUSTOM_OBJECT_PLURAL,
356
+ )
357
+
358
+ def get_cluster_queue(self, cluster_queue_name: str):
359
+ return client.CustomObjectsApi().get_cluster_custom_object(
360
+ group=KUEUE_CUSTOM_OBJECT_GROUP,
361
+ version=KUEUE_CUSTOM_OBJECT_VERSION,
362
+ plural=CLUSTER_QUEUE_PRIORITY_CLASS_CUSTOM_OBJECT_PLURAL,
363
+ name=cluster_queue_name
364
+ )
365
+ # Add more methods to access other APIs as needed
File without changes