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.
- sagemaker/__init__.py +0 -0
- sagemaker/hyperpod/__init__.py +2 -0
- sagemaker/hyperpod/cli/__init__.py +0 -0
- sagemaker/hyperpod/cli/clients/__init__.py +12 -0
- sagemaker/hyperpod/cli/clients/kubernetes_client.py +365 -0
- sagemaker/hyperpod/cli/commands/__init__.py +0 -0
- sagemaker/hyperpod/cli/commands/cluster.py +627 -0
- sagemaker/hyperpod/cli/commands/inference.py +659 -0
- sagemaker/hyperpod/cli/commands/training.py +347 -0
- sagemaker/hyperpod/cli/constants/__init__.py +0 -0
- sagemaker/hyperpod/cli/constants/command_constants.py +105 -0
- sagemaker/hyperpod/cli/constants/exception_constants.py +13 -0
- sagemaker/hyperpod/cli/constants/hyperpod_instance_types.py +100 -0
- sagemaker/hyperpod/cli/constants/kueue_constants.py +17 -0
- sagemaker/hyperpod/cli/constants/pytorch_constants.py +15 -0
- sagemaker/hyperpod/cli/hyp_cli.py +132 -0
- sagemaker/hyperpod/cli/inference_utils.py +121 -0
- sagemaker/hyperpod/cli/service/__init__.py +12 -0
- sagemaker/hyperpod/cli/service/cancel_training_job.py +70 -0
- sagemaker/hyperpod/cli/service/discover_namespaces.py +108 -0
- sagemaker/hyperpod/cli/service/exec_command.py +75 -0
- sagemaker/hyperpod/cli/service/get_logs.py +136 -0
- sagemaker/hyperpod/cli/service/get_namespaces.py +59 -0
- sagemaker/hyperpod/cli/service/get_training_job.py +110 -0
- sagemaker/hyperpod/cli/service/list_pods.py +138 -0
- sagemaker/hyperpod/cli/service/list_training_jobs.py +178 -0
- sagemaker/hyperpod/cli/service/self_subject_access_review.py +47 -0
- sagemaker/hyperpod/cli/telemetry/__init__.py +12 -0
- sagemaker/hyperpod/cli/telemetry/user_agent.py +36 -0
- sagemaker/hyperpod/cli/templates/__init__.py +12 -0
- sagemaker/hyperpod/cli/templates/k8s_pytorch_job_template.py +74 -0
- sagemaker/hyperpod/cli/training_utils.py +200 -0
- sagemaker/hyperpod/cli/utils.py +183 -0
- sagemaker/hyperpod/cli/validators/__init__.py +12 -0
- sagemaker/hyperpod/cli/validators/cluster_validator.py +52 -0
- sagemaker/hyperpod/cli/validators/job_validator.py +346 -0
- sagemaker/hyperpod/cli/validators/validator.py +75 -0
- sagemaker/hyperpod/common/__init__.py +0 -0
- sagemaker/hyperpod/common/config/__init__.py +1 -0
- sagemaker/hyperpod/common/config/metadata.py +18 -0
- sagemaker/hyperpod/common/utils.py +299 -0
- sagemaker/hyperpod/inference/__init__.py +11 -0
- sagemaker/hyperpod/inference/config/__init__.py +0 -0
- sagemaker/hyperpod/inference/config/constants.py +18 -0
- sagemaker/hyperpod/inference/config/hp_endpoint_config.py +767 -0
- sagemaker/hyperpod/inference/config/hp_jumpstart_endpoint_config.py +572 -0
- sagemaker/hyperpod/inference/hp_endpoint.py +203 -0
- sagemaker/hyperpod/inference/hp_endpoint_base.py +223 -0
- sagemaker/hyperpod/inference/hp_jumpstart_endpoint.py +232 -0
- sagemaker/hyperpod/observability/MonitoringConfig.py +10 -0
- sagemaker/hyperpod/observability/__init__.py +0 -0
- sagemaker/hyperpod/observability/constants.py +2 -0
- sagemaker/hyperpod/observability/utils.py +64 -0
- sagemaker/hyperpod/training/__init__.py +9 -0
- sagemaker/hyperpod/training/config/__init__.py +1 -0
- sagemaker/hyperpod/training/config/hyperpod_pytorch_job_config.py +2977 -0
- sagemaker/hyperpod/training/config/hyperpod_pytorch_job_status.py +3083 -0
- sagemaker/hyperpod/training/hyperpod_pytorch_job.py +253 -0
- sagemaker_hyperpod-3.0.0.dist-info/METADATA +534 -0
- sagemaker_hyperpod-3.0.0.dist-info/RECORD +66 -0
- sagemaker_hyperpod-3.0.0.dist-info/WHEEL +5 -0
- sagemaker_hyperpod-3.0.0.dist-info/entry_points.txt +2 -0
- sagemaker_hyperpod-3.0.0.dist-info/licenses/LICENSE +175 -0
- sagemaker_hyperpod-3.0.0.dist-info/licenses/NOTICE +1 -0
- sagemaker_hyperpod-3.0.0.dist-info/top_level.txt +1 -0
- sagemaker_hyperpod-3.0.0.dist-info/zip-safe +1 -0
|
@@ -0,0 +1,59 @@
|
|
|
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 sagemaker.hyperpod.cli.clients.kubernetes_client import (
|
|
14
|
+
KubernetesClient,
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
from sagemaker.hyperpod.cli.constants.command_constants import SAGEMAKER_MANAGED_QUEUE_LABEL
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
LIMIT_PER_REQUEST = 200
|
|
21
|
+
|
|
22
|
+
class GetNamespaces:
|
|
23
|
+
|
|
24
|
+
def __init__(self):
|
|
25
|
+
return
|
|
26
|
+
|
|
27
|
+
def get_namespaces(self, label_selector=None):
|
|
28
|
+
"""
|
|
29
|
+
Get namespaces in the cluster
|
|
30
|
+
"""
|
|
31
|
+
core_v1_api = KubernetesClient().get_core_v1_api()
|
|
32
|
+
all_namespaces = list()
|
|
33
|
+
continue_token = None
|
|
34
|
+
|
|
35
|
+
while True:
|
|
36
|
+
response = None
|
|
37
|
+
if continue_token:
|
|
38
|
+
response = core_v1_api.list_namespace(
|
|
39
|
+
limit=LIMIT_PER_REQUEST, label_selector=label_selector, _continue=continue_token
|
|
40
|
+
)
|
|
41
|
+
else:
|
|
42
|
+
response = core_v1_api.list_namespace(
|
|
43
|
+
limit=LIMIT_PER_REQUEST, label_selector=label_selector
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
all_namespaces.extend([ns.metadata.name for ns in response.items])
|
|
47
|
+
continue_token = response.metadata._continue
|
|
48
|
+
|
|
49
|
+
if not continue_token:
|
|
50
|
+
break
|
|
51
|
+
|
|
52
|
+
return all_namespaces
|
|
53
|
+
|
|
54
|
+
def get_sagemaker_managed_namespaces(self):
|
|
55
|
+
"""
|
|
56
|
+
Get sagemaker managed namespaces in the cluster
|
|
57
|
+
"""
|
|
58
|
+
|
|
59
|
+
return self.get_namespaces(SAGEMAKER_MANAGED_QUEUE_LABEL + "=true")
|
|
@@ -0,0 +1,110 @@
|
|
|
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 json
|
|
16
|
+
|
|
17
|
+
from sagemaker.hyperpod.cli.clients.kubernetes_client import (
|
|
18
|
+
KubernetesClient,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
from sagemaker.hyperpod.cli import utils
|
|
22
|
+
from kubernetes.client.rest import ApiException
|
|
23
|
+
from kubernetes.client import (
|
|
24
|
+
V1ResourceAttributes
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
from sagemaker.hyperpod.cli.constants.pytorch_constants import PYTORCH_CUSTOM_OBJECT_GROUP, PYTORCH_CUSTOM_OBJECT_PLURAL
|
|
28
|
+
from sagemaker.hyperpod.cli.service.discover_namespaces import DiscoverNamespaces
|
|
29
|
+
|
|
30
|
+
class GetTrainingJob:
|
|
31
|
+
def __init__(self):
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
def get_training_job(
|
|
35
|
+
self,
|
|
36
|
+
job_name: str,
|
|
37
|
+
namespace: Optional[str],
|
|
38
|
+
verbose: Optional[bool],
|
|
39
|
+
):
|
|
40
|
+
"""
|
|
41
|
+
Describe training job provided by the user in the specified namespace.
|
|
42
|
+
If namespace is not provided job is described from the default namespace in user context
|
|
43
|
+
"""
|
|
44
|
+
|
|
45
|
+
k8s_client = KubernetesClient()
|
|
46
|
+
if not namespace:
|
|
47
|
+
resource_attributes_template = V1ResourceAttributes(
|
|
48
|
+
verb="get",
|
|
49
|
+
group=PYTORCH_CUSTOM_OBJECT_GROUP,
|
|
50
|
+
resource=PYTORCH_CUSTOM_OBJECT_PLURAL,
|
|
51
|
+
)
|
|
52
|
+
namespace = DiscoverNamespaces().discover_accessible_namespace(
|
|
53
|
+
resource_attributes_template
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
result = k8s_client.get_job(
|
|
58
|
+
job_name=job_name,
|
|
59
|
+
namespace=namespace,
|
|
60
|
+
)
|
|
61
|
+
except ApiException as e:
|
|
62
|
+
raise RuntimeError(f"Unexpected API error: {e.reason} ({e.status})")
|
|
63
|
+
|
|
64
|
+
if not verbose:
|
|
65
|
+
return self._format_output_to_keep_needed_fields(result)
|
|
66
|
+
else:
|
|
67
|
+
return self._format_verbose_output(result)
|
|
68
|
+
|
|
69
|
+
def _format_output_to_keep_needed_fields(self, output):
|
|
70
|
+
result = {}
|
|
71
|
+
if output:
|
|
72
|
+
if output.get("metadata"):
|
|
73
|
+
result = {
|
|
74
|
+
"Name": output.get("metadata").get("name"),
|
|
75
|
+
"Namespace": output.get("metadata").get("namespace"),
|
|
76
|
+
"Label": output.get("metadata").get("labels"),
|
|
77
|
+
"CreationTimestamp": output.get("metadata").get(
|
|
78
|
+
"creationTimestamp"
|
|
79
|
+
),
|
|
80
|
+
}
|
|
81
|
+
result.update({"Status": output.get("status")})
|
|
82
|
+
result.update({"ConsoleURL": utils.get_cluster_console_url()})
|
|
83
|
+
return json.dumps(result, indent=1, sort_keys=False)
|
|
84
|
+
|
|
85
|
+
def _format_verbose_output(self, output):
|
|
86
|
+
result = {}
|
|
87
|
+
if output:
|
|
88
|
+
if output.get("metadata"):
|
|
89
|
+
result = {
|
|
90
|
+
"Name": output.get("metadata").get("name"),
|
|
91
|
+
"Namespace": output.get("metadata").get("namespace"),
|
|
92
|
+
"Label": output.get("metadata").get("labels"),
|
|
93
|
+
"Annotations": output.get("metadata").get("annotations"),
|
|
94
|
+
"Metadata": {
|
|
95
|
+
"CreationTimestamp": output.get("metadata").get(
|
|
96
|
+
"creationTimestamp"
|
|
97
|
+
),
|
|
98
|
+
"Generation": output.get("metadata").get("generation"),
|
|
99
|
+
"ResourceVersion": output.get("metadata").get(
|
|
100
|
+
"resourceVersion"
|
|
101
|
+
),
|
|
102
|
+
"UID": output.get("metadata").get("uid"),
|
|
103
|
+
},
|
|
104
|
+
}
|
|
105
|
+
result.update({"Kind": output.get("kind")})
|
|
106
|
+
result.update({"ApiVersion": output.get("apiVersion")})
|
|
107
|
+
result.update({"Spec": output.get("spec")})
|
|
108
|
+
result.update({"Status": output.get("status")})
|
|
109
|
+
result.update({"ConsoleURL": utils.get_cluster_console_url()})
|
|
110
|
+
return json.dumps(result, indent=1, sort_keys=False)
|
|
@@ -0,0 +1,138 @@
|
|
|
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 collections import defaultdict
|
|
14
|
+
from typing import List, Optional
|
|
15
|
+
import json
|
|
16
|
+
|
|
17
|
+
from kubernetes.client import V1Pod, V1PodList
|
|
18
|
+
|
|
19
|
+
from sagemaker.hyperpod.cli.clients.kubernetes_client import (
|
|
20
|
+
KubernetesClient,
|
|
21
|
+
)
|
|
22
|
+
from kubernetes.client.rest import ApiException
|
|
23
|
+
from kubernetes.client import (
|
|
24
|
+
V1ResourceAttributes
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
from sagemaker.hyperpod.cli.constants.command_constants import NVIDIA_GPU_RESOURCE_LIMIT_KEY
|
|
28
|
+
from sagemaker.hyperpod.cli.service.discover_namespaces import DiscoverNamespaces
|
|
29
|
+
|
|
30
|
+
class ListPods:
|
|
31
|
+
def __init__(self):
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
def list_pods_for_training_job(
|
|
35
|
+
self,
|
|
36
|
+
job_name: str,
|
|
37
|
+
namespace: Optional[str],
|
|
38
|
+
pretty: Optional[bool],
|
|
39
|
+
):
|
|
40
|
+
"""
|
|
41
|
+
List pods associated with a training job
|
|
42
|
+
"""
|
|
43
|
+
k8s_client = KubernetesClient()
|
|
44
|
+
|
|
45
|
+
if not namespace:
|
|
46
|
+
resource_attributes_template = V1ResourceAttributes(
|
|
47
|
+
verb="list",
|
|
48
|
+
group="",
|
|
49
|
+
resource="pods",
|
|
50
|
+
)
|
|
51
|
+
namespace = DiscoverNamespaces().discover_accessible_namespace(
|
|
52
|
+
resource_attributes_template
|
|
53
|
+
)
|
|
54
|
+
|
|
55
|
+
label_filter = f"training.kubeflow.org/job-name={job_name}"
|
|
56
|
+
|
|
57
|
+
try:
|
|
58
|
+
_pods: V1PodList = k8s_client.list_pods_with_labels(namespace, label_filter)
|
|
59
|
+
except ApiException as e:
|
|
60
|
+
raise RuntimeError(f"Unexpected API error: {e.reason} ({e.status})")
|
|
61
|
+
|
|
62
|
+
if pretty:
|
|
63
|
+
return self._generate_list_pods_output(_pods)
|
|
64
|
+
else:
|
|
65
|
+
return self._generate_pods_list(_pods)
|
|
66
|
+
|
|
67
|
+
def list_pods_and_get_requested_resources_group_by_node_name(
|
|
68
|
+
self,
|
|
69
|
+
):
|
|
70
|
+
"""
|
|
71
|
+
List pods for all namespaces and initialized by kubeflow.
|
|
72
|
+
Group by the node_name of the pod and the value is the
|
|
73
|
+
accelerator devices resources requested
|
|
74
|
+
"""
|
|
75
|
+
k8s_client = KubernetesClient()
|
|
76
|
+
|
|
77
|
+
label_filter = f"training.kubeflow.org/job-name"
|
|
78
|
+
pods = k8s_client.list_pods_in_all_namespaces_with_labels(label_filter)
|
|
79
|
+
|
|
80
|
+
# Dictionary to hold total GPU/Neuron requests per node
|
|
81
|
+
accelerator_devices_requests_by_node = defaultdict(int)
|
|
82
|
+
|
|
83
|
+
# Loop through each pod and aggregate the GPU resource requests per node
|
|
84
|
+
for pod in pods:
|
|
85
|
+
node_name = pod.spec.node_name
|
|
86
|
+
# Check if the pod has a node_name assigned, if pod is not scheduled, it may not have node_name
|
|
87
|
+
if node_name:
|
|
88
|
+
for container in pod.spec.containers:
|
|
89
|
+
if container.resources and container.resources.requests:
|
|
90
|
+
gpu_request = container.resources.requests.get(NVIDIA_GPU_RESOURCE_LIMIT_KEY)
|
|
91
|
+
neuron_request = container.resources.requests.get(
|
|
92
|
+
"aws.amazon.com/neurondevice"
|
|
93
|
+
)
|
|
94
|
+
if gpu_request:
|
|
95
|
+
accelerator_devices_requests_by_node[node_name] += int(
|
|
96
|
+
gpu_request
|
|
97
|
+
)
|
|
98
|
+
if neuron_request:
|
|
99
|
+
accelerator_devices_requests_by_node[node_name] += int(
|
|
100
|
+
neuron_request
|
|
101
|
+
)
|
|
102
|
+
|
|
103
|
+
return accelerator_devices_requests_by_node
|
|
104
|
+
|
|
105
|
+
def _generate_list_pods_output(self, pods: V1PodList) -> Optional[str]:
|
|
106
|
+
output_pods = {"pods": []}
|
|
107
|
+
if pods.items and len(pods.items) > 0:
|
|
108
|
+
_pod: V1Pod
|
|
109
|
+
for _pod in pods.items:
|
|
110
|
+
if _pod.metadata and _pod.metadata.name and _pod.metadata.namespace:
|
|
111
|
+
name = _pod.metadata.name
|
|
112
|
+
namespace = _pod.metadata.namespace
|
|
113
|
+
status = None
|
|
114
|
+
creation_timestamp = None
|
|
115
|
+
if _pod.status and _pod.status.phase:
|
|
116
|
+
status = _pod.status.phase
|
|
117
|
+
if _pod.metadata.creation_timestamp:
|
|
118
|
+
creation_timestamp = str(_pod.metadata.creation_timestamp)
|
|
119
|
+
output_pods["pods"].append(
|
|
120
|
+
{
|
|
121
|
+
"PodName": name,
|
|
122
|
+
"Namespace": namespace,
|
|
123
|
+
"Status": status,
|
|
124
|
+
"CreationTime": creation_timestamp,
|
|
125
|
+
}
|
|
126
|
+
)
|
|
127
|
+
|
|
128
|
+
return json.dumps(output_pods, indent=1, sort_keys=False)
|
|
129
|
+
|
|
130
|
+
def _generate_pods_list(self, pods: V1PodList) -> List:
|
|
131
|
+
output_pods = []
|
|
132
|
+
if pods.items and len(pods.items) > 0:
|
|
133
|
+
_pod: V1Pod
|
|
134
|
+
for _pod in pods.items:
|
|
135
|
+
if _pod.metadata and _pod.metadata.name:
|
|
136
|
+
name = _pod.metadata.name
|
|
137
|
+
output_pods.append(name)
|
|
138
|
+
return output_pods
|
|
@@ -0,0 +1,178 @@
|
|
|
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 List, Optional
|
|
14
|
+
|
|
15
|
+
import json
|
|
16
|
+
from datetime import datetime
|
|
17
|
+
|
|
18
|
+
from sagemaker.hyperpod.cli.clients.kubernetes_client import (
|
|
19
|
+
KubernetesClient,
|
|
20
|
+
)
|
|
21
|
+
from sagemaker.hyperpod.cli.utils import setup_logger
|
|
22
|
+
from kubernetes.client.rest import ApiException
|
|
23
|
+
from kubernetes.client import (
|
|
24
|
+
V1ResourceAttributes
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
from sagemaker.hyperpod.cli.constants.command_constants import KUEUE_WORKLOAD_PRIORITY_CLASS_LABEL_KEY, OutputFormat
|
|
28
|
+
from sagemaker.hyperpod.cli.constants.pytorch_constants import PYTORCH_CUSTOM_OBJECT_GROUP, PYTORCH_CUSTOM_OBJECT_PLURAL
|
|
29
|
+
from sagemaker.hyperpod.cli.service.discover_namespaces import DiscoverNamespaces
|
|
30
|
+
from tabulate import tabulate
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class ListTrainingJobs:
|
|
34
|
+
def __init__(self):
|
|
35
|
+
return
|
|
36
|
+
|
|
37
|
+
def list_training_jobs(
|
|
38
|
+
self,
|
|
39
|
+
namespace: Optional[str],
|
|
40
|
+
all_namespaces: Optional[bool],
|
|
41
|
+
selector: Optional[str],
|
|
42
|
+
output: Optional[str],
|
|
43
|
+
) -> str:
|
|
44
|
+
"""
|
|
45
|
+
List training job provided by the user in the specified namespace.
|
|
46
|
+
If namespace is not provided job are listed the default namespace in user context
|
|
47
|
+
If all_namespace is true we will list training job from all namespaces that user has access
|
|
48
|
+
Selector when specified will filter list of job addisional based on labels filter provided
|
|
49
|
+
"""
|
|
50
|
+
k8s_client = KubernetesClient()
|
|
51
|
+
|
|
52
|
+
jobs: List = []
|
|
53
|
+
logger = setup_logger(__name__)
|
|
54
|
+
logger.debug(namespace)
|
|
55
|
+
try:
|
|
56
|
+
if all_namespaces:
|
|
57
|
+
namespaces: List[str] = k8s_client.list_namespaces()
|
|
58
|
+
for _namespace in namespaces:
|
|
59
|
+
_jobs = k8s_client.list_training_jobs(
|
|
60
|
+
namespace=_namespace,
|
|
61
|
+
label_selector=selector,
|
|
62
|
+
)
|
|
63
|
+
if _jobs.get("items") and len(_jobs.get("items")) > 0:
|
|
64
|
+
for _job in _jobs.get("items"):
|
|
65
|
+
jobs.append(_job)
|
|
66
|
+
else:
|
|
67
|
+
if not namespace:
|
|
68
|
+
resource_attributes_template = V1ResourceAttributes(
|
|
69
|
+
verb="list",
|
|
70
|
+
group=PYTORCH_CUSTOM_OBJECT_GROUP,
|
|
71
|
+
resource=PYTORCH_CUSTOM_OBJECT_PLURAL,
|
|
72
|
+
)
|
|
73
|
+
namespace = DiscoverNamespaces().discover_accessible_namespace(
|
|
74
|
+
resource_attributes_template
|
|
75
|
+
)
|
|
76
|
+
else:
|
|
77
|
+
if not k8s_client.check_if_namespace_exists(namespace):
|
|
78
|
+
raise ValueError(f"Namespace {namespace} does not exist!")
|
|
79
|
+
|
|
80
|
+
namespace_jobs = k8s_client.list_training_jobs(
|
|
81
|
+
namespace=namespace,
|
|
82
|
+
label_selector=selector,
|
|
83
|
+
)
|
|
84
|
+
if namespace_jobs.get("items") and len(namespace_jobs.get("items")) > 0:
|
|
85
|
+
for namespace_job in namespace_jobs.get("items"):
|
|
86
|
+
jobs.append(namespace_job)
|
|
87
|
+
except ApiException as e:
|
|
88
|
+
raise RuntimeError(f"Unexpected API error: {e.reason} ({e.status})")
|
|
89
|
+
|
|
90
|
+
return self._generate_list_training_job_output(jobs, output)
|
|
91
|
+
|
|
92
|
+
def _generate_list_training_job_output(self, jobs: List, output: Optional[str]):
|
|
93
|
+
output_jobs = {"jobs": []}
|
|
94
|
+
priority_header_required = False
|
|
95
|
+
|
|
96
|
+
for job in jobs:
|
|
97
|
+
if job.get("metadata"):
|
|
98
|
+
name = job.get("metadata").get("name")
|
|
99
|
+
namespace = job.get("metadata").get("namespace")
|
|
100
|
+
creation_time = None
|
|
101
|
+
state = None
|
|
102
|
+
priority = self._get_job_priority(job)
|
|
103
|
+
if job.get("status"):
|
|
104
|
+
creation_time = job.get("status").get("startTime")
|
|
105
|
+
state = self._get_job_status(job.get("status").get("conditions"))
|
|
106
|
+
|
|
107
|
+
job_summary = {
|
|
108
|
+
"Name": name,
|
|
109
|
+
"Namespace": namespace,
|
|
110
|
+
"CreationTime": creation_time,
|
|
111
|
+
"State": state,
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if priority is not None:
|
|
115
|
+
job_summary["priority"] = priority
|
|
116
|
+
priority_header_required = True
|
|
117
|
+
|
|
118
|
+
output_jobs["jobs"].append(job_summary)
|
|
119
|
+
|
|
120
|
+
if output == OutputFormat.TABLE.value:
|
|
121
|
+
return self._generate_table(output_jobs, priority_header_required)
|
|
122
|
+
return json.dumps(output_jobs, indent=4, sort_keys=False)
|
|
123
|
+
|
|
124
|
+
def _generate_table(self, output_jobs, priority_header_required):
|
|
125
|
+
headers = [
|
|
126
|
+
"Name",
|
|
127
|
+
"Namespace",
|
|
128
|
+
"CreationTime",
|
|
129
|
+
"State"
|
|
130
|
+
]
|
|
131
|
+
|
|
132
|
+
if priority_header_required:
|
|
133
|
+
headers.append("Priority")
|
|
134
|
+
|
|
135
|
+
jobs = []
|
|
136
|
+
if "jobs" in output_jobs and isinstance(output_jobs["jobs"], list):
|
|
137
|
+
for job in output_jobs["jobs"]:
|
|
138
|
+
job_values = list(job.values())
|
|
139
|
+
if priority_header_required and len(job_values) == 4:
|
|
140
|
+
job_values.append("NA")
|
|
141
|
+
jobs.append(job_values)
|
|
142
|
+
|
|
143
|
+
return tabulate(jobs, headers=headers, tablefmt="presto")
|
|
144
|
+
|
|
145
|
+
def _get_job_status(self, status: List) -> Optional[str]:
|
|
146
|
+
current_status = None
|
|
147
|
+
last_date_time = datetime.strptime(
|
|
148
|
+
"2001-08-27T22:47:57Z",
|
|
149
|
+
"%Y-%m-%dT%H:%M:%SZ",
|
|
150
|
+
)
|
|
151
|
+
for state in status:
|
|
152
|
+
state_date_time = datetime.strptime(
|
|
153
|
+
state.get("lastTransitionTime"),
|
|
154
|
+
"%Y-%m-%dT%H:%M:%SZ",
|
|
155
|
+
)
|
|
156
|
+
if state_date_time >= last_date_time:
|
|
157
|
+
last_date_time = state_date_time
|
|
158
|
+
current_status = state.get("type")
|
|
159
|
+
return current_status
|
|
160
|
+
|
|
161
|
+
def _get_job_priority(self, job) -> Optional[str]:
|
|
162
|
+
worker_template = job.get("spec", {}).get("pytorchReplicaSpecs", {}).get("Worker", {}).get("template", {})
|
|
163
|
+
wl_priority_cls = worker_template.get("metadata", {}).get("labels", {}).get(KUEUE_WORKLOAD_PRIORITY_CLASS_LABEL_KEY, None)
|
|
164
|
+
spec_priority_cls = worker_template.get("spec", {}).get("priorityClassName", None)
|
|
165
|
+
|
|
166
|
+
# For reference: https://kueue.sigs.k8s.io/docs/concepts/workload_priority_class/
|
|
167
|
+
# The workload priority class takes precedence over the k8s priority class.
|
|
168
|
+
# Because the cli focuses on the job level which means workload priority is more essential.
|
|
169
|
+
# There is possibility that these two priorities coexist at the same time. In this case,
|
|
170
|
+
# the k8s priority class will be used as pod priority. cli should still take workload
|
|
171
|
+
# priority in this scenario.
|
|
172
|
+
|
|
173
|
+
if wl_priority_cls is not None:
|
|
174
|
+
return wl_priority_cls
|
|
175
|
+
elif spec_priority_cls is not None:
|
|
176
|
+
return spec_priority_cls
|
|
177
|
+
|
|
178
|
+
return None
|
|
@@ -0,0 +1,47 @@
|
|
|
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 sagemaker.hyperpod.cli.clients.kubernetes_client import (
|
|
14
|
+
KubernetesClient,
|
|
15
|
+
)
|
|
16
|
+
from kubernetes.client import (
|
|
17
|
+
V1SelfSubjectAccessReview,
|
|
18
|
+
V1SelfSubjectAccessReviewSpec,
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class SelfSubjectAccessReview:
|
|
23
|
+
def __init__(self):
|
|
24
|
+
return
|
|
25
|
+
|
|
26
|
+
def self_subject_access_review(
|
|
27
|
+
self,
|
|
28
|
+
resource_attributes=None,
|
|
29
|
+
non_resource_attributes=None,
|
|
30
|
+
local_vars_configuration=None,
|
|
31
|
+
):
|
|
32
|
+
"""
|
|
33
|
+
Submit self subject access review
|
|
34
|
+
"""
|
|
35
|
+
auth_v1_api = KubernetesClient().get_auth_v1_api()
|
|
36
|
+
|
|
37
|
+
access_review = V1SelfSubjectAccessReview(
|
|
38
|
+
spec=V1SelfSubjectAccessReviewSpec(
|
|
39
|
+
resource_attributes=resource_attributes,
|
|
40
|
+
non_resource_attributes=non_resource_attributes,
|
|
41
|
+
local_vars_configuration=local_vars_configuration,
|
|
42
|
+
)
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
response = auth_v1_api.create_self_subject_access_review(body=access_review)
|
|
46
|
+
|
|
47
|
+
return response
|
|
@@ -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,36 @@
|
|
|
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
|
+
"""Placeholder docstring"""
|
|
14
|
+
|
|
15
|
+
from __future__ import absolute_import
|
|
16
|
+
|
|
17
|
+
import importlib.metadata
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
CLI_PREFIX = "AWS-SageMaker-Hyperpod-CLI"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def get_user_agent_extra_suffix():
|
|
24
|
+
"""Get the user agent extra suffix string specific to SageMaker Hyperpod CLI
|
|
25
|
+
|
|
26
|
+
Adhers to new boto recommended User-Agent 2.0 header format
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
str: The user agent extra suffix string to be appended
|
|
30
|
+
"""
|
|
31
|
+
suffix = "cli/{}#{}".format(
|
|
32
|
+
CLI_PREFIX,
|
|
33
|
+
importlib.metadata.version("sagemaker-hyperpod"),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
return suffix
|
|
@@ -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,74 @@
|
|
|
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
|
+
KUBERNETES_PYTORCH_JOB_TEMPLATE = """### Please keep template file unchanged ###
|
|
14
|
+
defaults:
|
|
15
|
+
- override hydra/job_logging: stdout
|
|
16
|
+
|
|
17
|
+
hydra:
|
|
18
|
+
run:
|
|
19
|
+
dir: .
|
|
20
|
+
output_subdir: null
|
|
21
|
+
|
|
22
|
+
training_cfg:
|
|
23
|
+
entry_script: ??? # Path to the entry script of training/fine-tuning. This path should be inside container or relative path in git repo
|
|
24
|
+
script_args: ??? # Entry script arguments
|
|
25
|
+
run:
|
|
26
|
+
nodes: ??? # Number of nodes to use for current training
|
|
27
|
+
ntasks_per_node: ??? # Number of tasks per node
|
|
28
|
+
cluster:
|
|
29
|
+
cluster_type: k8s # currently k8s only
|
|
30
|
+
instance_type: ???
|
|
31
|
+
cluster_config:
|
|
32
|
+
namespace: ??? # the namespace to submit job
|
|
33
|
+
custom_labels: ???
|
|
34
|
+
service_account_name: null
|
|
35
|
+
annotations: ???
|
|
36
|
+
priority_class_name: ???
|
|
37
|
+
# Create k8s NodeAffinity to select nodes to deploy jobs which matches required and preferred labels
|
|
38
|
+
# Structure:
|
|
39
|
+
# label_selector:
|
|
40
|
+
# required: <required label key-values pair>
|
|
41
|
+
# preferred: <preferred label key-values pair>
|
|
42
|
+
# weights: <weights list used by preferred labels to get nodes priority>
|
|
43
|
+
# Example:
|
|
44
|
+
# label_selector:
|
|
45
|
+
# required:
|
|
46
|
+
# example-label-key:
|
|
47
|
+
# - expected-label-value-1
|
|
48
|
+
# - expected-label-value-2
|
|
49
|
+
# preferred:
|
|
50
|
+
# preferred-label-key:
|
|
51
|
+
# - preferred-label-value-1
|
|
52
|
+
# - preferred-label-value-2
|
|
53
|
+
# weights:
|
|
54
|
+
# - 100
|
|
55
|
+
label_selector: ???
|
|
56
|
+
# persistent volume, usually used to mount FSx
|
|
57
|
+
persistent_volume_claims: null
|
|
58
|
+
pullPolicy: ??? # policy to pull container, can be Always, IfNotPresent and Never
|
|
59
|
+
restartPolicy: ??? # PyTorchJob restart policy
|
|
60
|
+
# temp volume, usually used to mount temp directory
|
|
61
|
+
# volumes, used to mount temp path to container
|
|
62
|
+
# example:
|
|
63
|
+
# volumes:
|
|
64
|
+
# - volumeName: data1
|
|
65
|
+
# hostPath: "/data"
|
|
66
|
+
# mountPath: "/data"
|
|
67
|
+
volumes: null
|
|
68
|
+
|
|
69
|
+
base_results_dir: ??? # Location to store the results, checkpoints and logs.
|
|
70
|
+
container: ??? # container to use
|
|
71
|
+
|
|
72
|
+
env_vars:
|
|
73
|
+
NCCL_DEBUG: INFO # Logging level for NCCL. Set to "INFO" for debug information
|
|
74
|
+
"""
|