sagemaker-hyperpod 3.0.2__tar.gz → 3.1.0__tar.gz
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_hyperpod-3.0.2/src/sagemaker_hyperpod.egg-info → sagemaker_hyperpod-3.1.0}/PKG-INFO +1 -1
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/pyproject.toml +1 -1
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/setup.py +1 -1
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/commands/training.py +8 -9
- sagemaker_hyperpod-3.1.0/src/sagemaker/hyperpod/cli/common_utils.py +71 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/inference_utils.py +7 -11
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/training_utils.py +8 -31
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/common/config/metadata.py +4 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/common/utils.py +168 -3
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/inference/hp_endpoint_base.py +8 -4
- sagemaker_hyperpod-3.1.0/src/sagemaker/hyperpod/inference/jumpstart_public_hub_visualization_utils.py +301 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/training/hyperpod_pytorch_job.py +12 -9
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0/src/sagemaker_hyperpod.egg-info}/PKG-INFO +1 -1
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker_hyperpod.egg-info/SOURCES.txt +2 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/LICENSE +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/NOTICE +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/README.md +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/setup.cfg +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/__init__.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/__init__.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/__init__.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/clients/__init__.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/clients/kubernetes_client.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/commands/__init__.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/commands/cluster.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/commands/inference.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/constants/__init__.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/constants/command_constants.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/constants/exception_constants.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/constants/hyperpod_instance_types.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/constants/kueue_constants.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/constants/pytorch_constants.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/hyp_cli.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/service/__init__.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/service/cancel_training_job.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/service/discover_namespaces.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/service/exec_command.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/service/get_logs.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/service/get_namespaces.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/service/get_training_job.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/service/list_pods.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/service/list_training_jobs.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/service/self_subject_access_review.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/templates/__init__.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/templates/k8s_pytorch_job_template.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/utils.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/validators/__init__.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/validators/cluster_validator.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/validators/job_validator.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/validators/validator.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/common/__init__.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/common/config/__init__.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/common/telemetry/__init__.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/common/telemetry/constants.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/common/telemetry/telemetry_logging.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/common/telemetry/user_agent.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/inference/__init__.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/inference/config/__init__.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/inference/config/constants.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/inference/config/hp_endpoint_config.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/inference/config/hp_jumpstart_endpoint_config.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/inference/hp_endpoint.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/inference/hp_jumpstart_endpoint.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/observability/MonitoringConfig.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/observability/__init__.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/observability/constants.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/observability/utils.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/training/__init__.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/training/config/__init__.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/training/config/hyperpod_pytorch_job_unified_config.py +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker_hyperpod.egg-info/dependency_links.txt +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker_hyperpod.egg-info/entry_points.txt +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker_hyperpod.egg-info/requires.txt +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker_hyperpod.egg-info/top_level.txt +0 -0
- {sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker_hyperpod.egg-info/zip-safe +0 -0
|
@@ -47,7 +47,7 @@ for root, dirs, files in os.walk(
|
|
|
47
47
|
setup(
|
|
48
48
|
data_files=sagemaker_hyperpod_recipes,
|
|
49
49
|
name="sagemaker-hyperpod",
|
|
50
|
-
version="3.0
|
|
50
|
+
version="3.1.0",
|
|
51
51
|
description="Amazon SageMaker HyperPod SDK and CLI",
|
|
52
52
|
long_description=open("README.md").read(),
|
|
53
53
|
long_description_content_type="text/markdown",
|
|
@@ -1,16 +1,7 @@
|
|
|
1
1
|
import click
|
|
2
|
-
import logging
|
|
3
|
-
import os
|
|
4
|
-
import yaml
|
|
5
|
-
import shutil
|
|
6
|
-
import subprocess
|
|
7
|
-
from pathlib import Path
|
|
8
2
|
from sagemaker.hyperpod.training.hyperpod_pytorch_job import HyperPodPytorchJob
|
|
9
3
|
from sagemaker.hyperpod.common.config import Metadata
|
|
10
|
-
import tempfile
|
|
11
|
-
from typing import List, Dict, Any, Optional, Callable, get_args, get_origin, Literal
|
|
12
4
|
from sagemaker.hyperpod.cli.training_utils import generate_click_command
|
|
13
|
-
from importlib.metadata import entry_points
|
|
14
5
|
from hyperpod_pytorch_job_template.registry import SCHEMA_REGISTRY
|
|
15
6
|
from sagemaker.hyperpod.common.telemetry.telemetry_logging import (
|
|
16
7
|
_hyperpod_telemetry_emitter,
|
|
@@ -33,11 +24,17 @@ def pytorch_create(version, debug, config):
|
|
|
33
24
|
job_name = config.get("name")
|
|
34
25
|
namespace = config.get("namespace")
|
|
35
26
|
spec = config.get("spec")
|
|
27
|
+
metadata_labels = config.get("labels")
|
|
28
|
+
annotations = config.get("annotations")
|
|
36
29
|
|
|
37
30
|
# Prepare metadata
|
|
38
31
|
metadata_kwargs = {"name": job_name}
|
|
39
32
|
if namespace:
|
|
40
33
|
metadata_kwargs["namespace"] = namespace
|
|
34
|
+
if metadata_labels:
|
|
35
|
+
metadata_kwargs["labels"] = metadata_labels
|
|
36
|
+
if annotations:
|
|
37
|
+
metadata_kwargs["annotations"] = annotations
|
|
41
38
|
|
|
42
39
|
# Prepare job kwargs
|
|
43
40
|
job_kwargs = {
|
|
@@ -163,6 +160,8 @@ def pytorch_describe(job_name: str, namespace: str):
|
|
|
163
160
|
click.echo("=" * 80)
|
|
164
161
|
click.echo(f"Name: {job.metadata.name}")
|
|
165
162
|
click.echo(f"Namespace: {job.metadata.namespace}")
|
|
163
|
+
click.echo(f"Labels: {job.metadata.labels}")
|
|
164
|
+
click.echo(f"Annotations: {job.metadata.annotations}")
|
|
166
165
|
|
|
167
166
|
# Print Spec details
|
|
168
167
|
click.echo("\nSpec:")
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from typing import Mapping, Type
|
|
3
|
+
import click
|
|
4
|
+
import pkgutil
|
|
5
|
+
import json
|
|
6
|
+
|
|
7
|
+
JUMPSTART_SCHEMA = "hyperpod_jumpstart_inference_template"
|
|
8
|
+
CUSTOM_SCHEMA = "hyperpod_custom_inference_template"
|
|
9
|
+
JUMPSTART_COMMAND = "hyp-jumpstart-endpoint"
|
|
10
|
+
CUSTOM_COMMAND = "hyp-custom-endpoint"
|
|
11
|
+
PYTORCH_SCHEMA="hyperpod_pytorch_job_template"
|
|
12
|
+
PYTORCH_COMMAND="hyp-pytorch-job"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def extract_version_from_args(registry: Mapping[str, Type], schema_pkg: str, default: str) -> str:
|
|
16
|
+
if "--version" not in sys.argv:
|
|
17
|
+
return default
|
|
18
|
+
|
|
19
|
+
idx = sys.argv.index("--version")
|
|
20
|
+
if idx + 1 >= len(sys.argv):
|
|
21
|
+
return default
|
|
22
|
+
|
|
23
|
+
requested_version = sys.argv[idx + 1]
|
|
24
|
+
invoked_command = next(
|
|
25
|
+
(arg for arg in sys.argv if arg.startswith('hyp-')),
|
|
26
|
+
None
|
|
27
|
+
)
|
|
28
|
+
|
|
29
|
+
# Check if schema validation is needed
|
|
30
|
+
needs_validation = (
|
|
31
|
+
(schema_pkg == JUMPSTART_SCHEMA and invoked_command == JUMPSTART_COMMAND) or
|
|
32
|
+
(schema_pkg == CUSTOM_SCHEMA and invoked_command == CUSTOM_COMMAND) or
|
|
33
|
+
(schema_pkg == PYTORCH_SCHEMA and invoked_command == PYTORCH_COMMAND)
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
if registry is not None and requested_version not in registry:
|
|
37
|
+
if needs_validation:
|
|
38
|
+
raise click.ClickException(f"Unsupported schema version: {requested_version}")
|
|
39
|
+
else:
|
|
40
|
+
return default
|
|
41
|
+
|
|
42
|
+
return requested_version
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def get_latest_version(registry: Mapping[str, Type]) -> str:
|
|
46
|
+
"""
|
|
47
|
+
Get the latest version from the schema registry.
|
|
48
|
+
"""
|
|
49
|
+
if not registry:
|
|
50
|
+
raise ValueError("Schema registry is empty")
|
|
51
|
+
|
|
52
|
+
# Sort versions and return the last (highest) one
|
|
53
|
+
sorted_versions = sorted(registry.keys(), key=lambda v: [int(x) for x in v.split('.')])
|
|
54
|
+
return sorted_versions[-1]
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def load_schema_for_version(
|
|
58
|
+
version: str,
|
|
59
|
+
base_package: str,
|
|
60
|
+
) -> dict:
|
|
61
|
+
"""
|
|
62
|
+
Load schema.json from the top-level <base_package>.vX_Y_Z package.
|
|
63
|
+
"""
|
|
64
|
+
ver_pkg = f"{base_package}.v{version.replace('.', '_')}"
|
|
65
|
+
raw = pkgutil.get_data(ver_pkg, "schema.json")
|
|
66
|
+
if raw is None:
|
|
67
|
+
raise click.ClickException(
|
|
68
|
+
f"Could not load schema.json for version {version} "
|
|
69
|
+
f"(looked in package {ver_pkg})"
|
|
70
|
+
)
|
|
71
|
+
return json.loads(raw)
|
{sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/inference_utils.py
RENAMED
|
@@ -2,25 +2,21 @@ import json
|
|
|
2
2
|
import pkgutil
|
|
3
3
|
import click
|
|
4
4
|
from typing import Callable, Optional, Mapping, Type
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
def load_schema_for_version(version: str, schema_pkg: str) -> dict:
|
|
8
|
-
ver_pkg = f"{schema_pkg}.v{version.replace('.', '_')}"
|
|
9
|
-
raw = pkgutil.get_data(ver_pkg, "schema.json")
|
|
10
|
-
if raw is None:
|
|
11
|
-
raise click.ClickException(f"Could not load schema.json for version {version}")
|
|
12
|
-
return json.loads(raw)
|
|
5
|
+
import sys
|
|
6
|
+
from sagemaker.hyperpod.cli.common_utils import extract_version_from_args, get_latest_version, load_schema_for_version
|
|
13
7
|
|
|
14
8
|
|
|
15
9
|
def generate_click_command(
|
|
16
10
|
*,
|
|
17
|
-
version_key: Optional[str] = None,
|
|
18
11
|
schema_pkg: str = "hyperpod_jumpstart_inference_template",
|
|
19
12
|
registry: Mapping[str, Type] = None,
|
|
20
13
|
) -> Callable:
|
|
21
14
|
if registry is None:
|
|
22
15
|
raise ValueError("You must pass a registry mapping version→Model")
|
|
23
16
|
|
|
17
|
+
default_version = get_latest_version(registry)
|
|
18
|
+
version = extract_version_from_args(registry, schema_pkg, default_version)
|
|
19
|
+
|
|
24
20
|
def decorator(func: Callable) -> Callable:
|
|
25
21
|
# Parser for the single JSON‐dict env var flag
|
|
26
22
|
def _parse_json_flag(ctx, param, value):
|
|
@@ -34,7 +30,7 @@ def generate_click_command(
|
|
|
34
30
|
# 1) the wrapper click actually invokes
|
|
35
31
|
def wrapped_func(*args, **kwargs):
|
|
36
32
|
namespace = kwargs.pop("namespace", None)
|
|
37
|
-
|
|
33
|
+
pop_version = kwargs.pop("version", "1.0")
|
|
38
34
|
|
|
39
35
|
Model = registry.get(version)
|
|
40
36
|
if Model is None:
|
|
@@ -81,7 +77,7 @@ def generate_click_command(
|
|
|
81
77
|
)(wrapped_func)
|
|
82
78
|
|
|
83
79
|
# 3) auto-inject all schema.json fields
|
|
84
|
-
schema = load_schema_for_version(
|
|
80
|
+
schema = load_schema_for_version(version, schema_pkg)
|
|
85
81
|
props = schema.get("properties", {})
|
|
86
82
|
reqs = set(schema.get("required", []))
|
|
87
83
|
|
{sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/training_utils.py
RENAMED
|
@@ -3,29 +3,13 @@ import pkgutil
|
|
|
3
3
|
import click
|
|
4
4
|
from typing import Callable, Optional, Mapping, Type, Dict, Any
|
|
5
5
|
from pydantic import ValidationError
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def load_schema_for_version(
|
|
9
|
-
version: str,
|
|
10
|
-
base_package: str,
|
|
11
|
-
) -> dict:
|
|
12
|
-
"""
|
|
13
|
-
Load schema.json from the top-level <base_package>.vX_Y_Z package.
|
|
14
|
-
"""
|
|
15
|
-
ver_pkg = f"{base_package}.v{version.replace('.', '_')}"
|
|
16
|
-
raw = pkgutil.get_data(ver_pkg, "schema.json")
|
|
17
|
-
if raw is None:
|
|
18
|
-
raise click.ClickException(
|
|
19
|
-
f"Could not load schema.json for version {version} "
|
|
20
|
-
f"(looked in package {ver_pkg})"
|
|
21
|
-
)
|
|
22
|
-
return json.loads(raw)
|
|
6
|
+
import sys
|
|
7
|
+
from sagemaker.hyperpod.cli.common_utils import extract_version_from_args, get_latest_version, load_schema_for_version
|
|
23
8
|
|
|
24
9
|
|
|
25
10
|
def generate_click_command(
|
|
26
11
|
*,
|
|
27
|
-
|
|
28
|
-
schema_pkg: str,
|
|
12
|
+
schema_pkg: str = "hyperpod_pytorch_job_template",
|
|
29
13
|
registry: Mapping[str, Type] = None,
|
|
30
14
|
) -> Callable:
|
|
31
15
|
"""
|
|
@@ -33,13 +17,15 @@ def generate_click_command(
|
|
|
33
17
|
1) Injects click.options from the JSON Schema under `schema_pkg`
|
|
34
18
|
2) At runtime, pops `version`, builds the flat model from `registry`, calls .to_domain()
|
|
35
19
|
3) Finally invokes your handler as `func(version, domain_config)`
|
|
36
|
-
- `version_key`: if given, hard-codes the version (no --version flag injected)
|
|
37
20
|
- `schema_pkg`: the importable package root to read schema.json from
|
|
38
21
|
- `registry`: a dict mapping version → flat‐model class, e.g. hyperpod_pytorch_job_template.registry.SCHEMA_REGISTRY
|
|
39
22
|
"""
|
|
40
23
|
if registry is None:
|
|
41
24
|
raise ValueError("You must pass a registry mapping version→Model")
|
|
42
25
|
|
|
26
|
+
default_version = get_latest_version(registry)
|
|
27
|
+
version = extract_version_from_args(registry, schema_pkg, default_version)
|
|
28
|
+
|
|
43
29
|
def decorator(func: Callable) -> Callable:
|
|
44
30
|
# Parser for the single JSON‐dict env var flag
|
|
45
31
|
def _parse_json_flag(ctx, param, value):
|
|
@@ -81,7 +67,7 @@ def generate_click_command(
|
|
|
81
67
|
# 1) the wrapper click will call
|
|
82
68
|
def wrapped_func(*args, **kwargs):
|
|
83
69
|
# extract version
|
|
84
|
-
|
|
70
|
+
pop_version = kwargs.pop("version", default_version)
|
|
85
71
|
debug = kwargs.pop("debug", False)
|
|
86
72
|
|
|
87
73
|
# look up the model class
|
|
@@ -165,7 +151,7 @@ def generate_click_command(
|
|
|
165
151
|
]
|
|
166
152
|
)
|
|
167
153
|
|
|
168
|
-
schema = load_schema_for_version(
|
|
154
|
+
schema = load_schema_for_version(version, schema_pkg)
|
|
169
155
|
props = schema.get("properties", {})
|
|
170
156
|
reqs = set(schema.get("required", []))
|
|
171
157
|
|
|
@@ -195,15 +181,6 @@ def generate_click_command(
|
|
|
195
181
|
help=spec.get("description", ""),
|
|
196
182
|
)(wrapped_func)
|
|
197
183
|
|
|
198
|
-
# 3) if no hard-coded version_key, inject the top-level --version flag
|
|
199
|
-
if version_key is None:
|
|
200
|
-
wrapped_func = click.option(
|
|
201
|
-
"--version",
|
|
202
|
-
default="1.0",
|
|
203
|
-
show_default=True,
|
|
204
|
-
help="Schema version to use",
|
|
205
|
-
)(wrapped_func)
|
|
206
|
-
|
|
207
184
|
return wrapped_func
|
|
208
185
|
|
|
209
186
|
return decorator
|
|
@@ -16,3 +16,7 @@ class Metadata(BaseModel):
|
|
|
16
16
|
default=None,
|
|
17
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.",
|
|
18
18
|
)
|
|
19
|
+
annotations: Optional[Dict[str, str]] = Field(
|
|
20
|
+
default=None,
|
|
21
|
+
description="Annotations are key-value pairs that can be used to attach arbitrary non-identifying metadata to objects.",
|
|
22
|
+
)
|
{sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/common/utils.py
RENAMED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
from kubernetes import client
|
|
1
|
+
from kubernetes import client, __version__ as kubernetes_client_version
|
|
2
2
|
from pydantic import ValidationError
|
|
3
3
|
from kubernetes.client.exceptions import ApiException
|
|
4
4
|
from kubernetes import config
|
|
5
5
|
import re
|
|
6
6
|
import boto3
|
|
7
7
|
import json
|
|
8
|
-
from typing import List
|
|
8
|
+
from typing import List, Tuple, Optional
|
|
9
9
|
import logging
|
|
10
10
|
import os
|
|
11
11
|
import subprocess
|
|
12
12
|
import yaml
|
|
13
|
-
from typing import Optional
|
|
14
13
|
from kubernetes.config import (
|
|
15
14
|
KUBE_CONFIG_DEFAULT_LOCATION,
|
|
16
15
|
)
|
|
17
16
|
|
|
18
17
|
EKS_ARN_PATTERN = r"arn:aws:eks:([\w-]+):\d+:cluster/([\w-]+)"
|
|
18
|
+
CLIENT_VERSION_PATTERN = r'^\d+\.\d+\.\d+$'
|
|
19
19
|
|
|
20
20
|
KUBE_CONFIG_PATH = os.path.expanduser(KUBE_CONFIG_DEFAULT_LOCATION)
|
|
21
21
|
|
|
@@ -297,3 +297,168 @@ def get_current_region():
|
|
|
297
297
|
return get_region_from_eks_arn(eks_arn)
|
|
298
298
|
except:
|
|
299
299
|
return boto3.session.Session().region_name
|
|
300
|
+
|
|
301
|
+
|
|
302
|
+
def parse_client_kubernetes_version(version_str: str) -> Tuple[int, int]:
|
|
303
|
+
"""Parse major and minor version from client library version string.
|
|
304
|
+
|
|
305
|
+
Handles both old versioning scheme (v12 and before) and new homogenized scheme.
|
|
306
|
+
Old scheme: v12.0.0 corresponds to Kubernetes v1.16
|
|
307
|
+
New scheme: v17.0.0 corresponds to Kubernetes v1.17
|
|
308
|
+
|
|
309
|
+
Args:
|
|
310
|
+
version_str (str): Client library version string (e.g., '12.0.0', '17.0.0', 'v12.0.0')
|
|
311
|
+
|
|
312
|
+
Returns:
|
|
313
|
+
Tuple[int, int]: Major and minor version numbers as (1, minor)
|
|
314
|
+
"""
|
|
315
|
+
if not version_str:
|
|
316
|
+
logger = logging.getLogger(__name__)
|
|
317
|
+
logger.debug(f"Empty version string provided, Using default version 0.0")
|
|
318
|
+
return 0, 0
|
|
319
|
+
|
|
320
|
+
# Remove suffix (like '+snapshot') if present
|
|
321
|
+
version_str = version_str.split('+')[0]
|
|
322
|
+
|
|
323
|
+
# Remove 'v' prefix if present
|
|
324
|
+
if version_str.startswith('v'):
|
|
325
|
+
version_str = version_str[1:]
|
|
326
|
+
|
|
327
|
+
# Client library version format (x.y.z)
|
|
328
|
+
if re.match(CLIENT_VERSION_PATTERN, version_str):
|
|
329
|
+
major = int(version_str.split('.')[0])
|
|
330
|
+
|
|
331
|
+
# Old client versioning scheme (v12 and before)
|
|
332
|
+
if major <= 12:
|
|
333
|
+
# Currently maps to Kubernetes v1.x
|
|
334
|
+
# This mapping assumes Kubernetes major version is 1
|
|
335
|
+
# If Kubernetes moves to v2.x in the future, this mapping would need to be updated
|
|
336
|
+
return 1, major + 4
|
|
337
|
+
|
|
338
|
+
# New homogenized scheme (v17 and above)
|
|
339
|
+
# Currently maps to Kubernetes v1.x
|
|
340
|
+
# This mapping assumes Kubernetes major version is 1
|
|
341
|
+
# If Kubernetes moves to v2.x in the future, this mapping would need to be updated
|
|
342
|
+
return 1, major
|
|
343
|
+
|
|
344
|
+
# If we get here, parsing failed
|
|
345
|
+
logger = logging.getLogger(__name__)
|
|
346
|
+
logger.warning(f"Failed to parse client version from string: '{version_str}'. Using default version 0.0.")
|
|
347
|
+
return 0, 0
|
|
348
|
+
|
|
349
|
+
|
|
350
|
+
|
|
351
|
+
def is_kubernetes_version_compatible(client_version: Tuple[int, int], server_version: Tuple[int, int]) -> bool:
|
|
352
|
+
"""
|
|
353
|
+
Check if Kubernetes client and server versions are compatible.
|
|
354
|
+
|
|
355
|
+
Args:
|
|
356
|
+
client_version (Tuple[int, int]): Client major and minor version
|
|
357
|
+
server_version (Tuple[int, int]): Server major and minor version
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
bool: True if versions are compatible, False otherwise
|
|
361
|
+
"""
|
|
362
|
+
# Check for default versions (0.0) which indicate parsing failures
|
|
363
|
+
if client_version == (0, 0) or server_version == (0, 0):
|
|
364
|
+
logger = logging.getLogger(__name__)
|
|
365
|
+
logger.warning(
|
|
366
|
+
f"Version compatibility check using default version(s): client={client_version}, server={server_version}. "
|
|
367
|
+
f"\nThis may indicate a version parsing issue. Please check your Kubernetes configuration."
|
|
368
|
+
)
|
|
369
|
+
return True
|
|
370
|
+
|
|
371
|
+
if client_version[0] != server_version[0]:
|
|
372
|
+
return False
|
|
373
|
+
|
|
374
|
+
"""
|
|
375
|
+
Client version should not be more than 3 minor versions behind the server and not more than
|
|
376
|
+
1 minor version ahead of the server
|
|
377
|
+
"""
|
|
378
|
+
client_minor = client_version[1]
|
|
379
|
+
server_minor = server_version[1]
|
|
380
|
+
|
|
381
|
+
if server_minor - client_minor > 3:
|
|
382
|
+
return False
|
|
383
|
+
|
|
384
|
+
if client_minor - server_minor > 1:
|
|
385
|
+
return False
|
|
386
|
+
|
|
387
|
+
return True
|
|
388
|
+
|
|
389
|
+
|
|
390
|
+
def verify_kubernetes_version_compatibility(logger) -> bool:
|
|
391
|
+
"""
|
|
392
|
+
Verify compatibility between Kubernetes client and server versions.
|
|
393
|
+
|
|
394
|
+
This function checks if the current Kubernetes client version is compatible with
|
|
395
|
+
the server version. It handles both minimum compatibility versions specified by
|
|
396
|
+
the server and the standard Kubernetes support policy (within 3 minor versions behind
|
|
397
|
+
and not more than 1 minor version ahead).
|
|
398
|
+
|
|
399
|
+
Ref link: https://github.com/kubernetes-client/python#compatibility
|
|
400
|
+
|
|
401
|
+
Args:
|
|
402
|
+
logger: Logger instance for outputting messages.
|
|
403
|
+
|
|
404
|
+
Returns:
|
|
405
|
+
bool: True if versions are compatible, False otherwise
|
|
406
|
+
"""
|
|
407
|
+
|
|
408
|
+
try:
|
|
409
|
+
version_api = client.VersionApi()
|
|
410
|
+
server_version_info = version_api.get_code()
|
|
411
|
+
|
|
412
|
+
server_version_str = f"{server_version_info.major}.{server_version_info.minor}"
|
|
413
|
+
client_version = parse_client_kubernetes_version(kubernetes_client_version)
|
|
414
|
+
client_version_str = f"{client_version[0]}.{client_version[1]}"
|
|
415
|
+
|
|
416
|
+
# Debug output of server version info
|
|
417
|
+
logger.debug(f"Server version info: {server_version_info}")
|
|
418
|
+
logger.debug(f"Client version: {kubernetes_client_version}, parsed as {client_version_str}")
|
|
419
|
+
|
|
420
|
+
# Check if server provides minimum compatibility versions (these are optional strings)
|
|
421
|
+
has_min_compatibility = False
|
|
422
|
+
is_compatible = True
|
|
423
|
+
|
|
424
|
+
try:
|
|
425
|
+
if hasattr(server_version_info, 'min_compatibility_major') and server_version_info.min_compatibility_major is not None and \
|
|
426
|
+
hasattr(server_version_info, 'min_compatibility_minor') and server_version_info.min_compatibility_minor is not None:
|
|
427
|
+
min_major = int(server_version_info.min_compatibility_major)
|
|
428
|
+
min_minor = int(server_version_info.min_compatibility_minor)
|
|
429
|
+
has_min_compatibility = True
|
|
430
|
+
|
|
431
|
+
# Check if client version is below minimum compatibility
|
|
432
|
+
if client_version[0] < min_major or (client_version[0] == min_major and client_version[1] < min_minor):
|
|
433
|
+
logger.warning(
|
|
434
|
+
f"Kubernetes version incompatibility detected! Your client version {client_version_str} "
|
|
435
|
+
f"(package: {kubernetes_client_version}) is below the minimum compatible version {min_major}.{min_minor} "
|
|
436
|
+
f"required by server {server_version_str}. The server explicitly requires a minimum client version."
|
|
437
|
+
)
|
|
438
|
+
logger.warning(
|
|
439
|
+
f"To resolve this issue, please update your kubernetes Python client to meet the minimum requirement."
|
|
440
|
+
)
|
|
441
|
+
is_compatible = False
|
|
442
|
+
except (ValueError, TypeError, AttributeError) as e:
|
|
443
|
+
logger.debug(f"Could not parse minimum compatibility version: {e}")
|
|
444
|
+
has_min_compatibility = False
|
|
445
|
+
|
|
446
|
+
if not has_min_compatibility:
|
|
447
|
+
# Fall back to standard compatibility check if min versions not provided
|
|
448
|
+
server_version_parsed = (int(server_version_info.major), int(server_version_info.minor))
|
|
449
|
+
if not is_kubernetes_version_compatible(client_version, server_version_parsed):
|
|
450
|
+
logger.warning(
|
|
451
|
+
f"Kubernetes version incompatibility detected! Your client version {client_version_str} "
|
|
452
|
+
f"(package: {kubernetes_client_version}) is not compatible with server version {server_version_str}. "
|
|
453
|
+
f"According to Kubernetes support policy, client should be within 3 minor versions behind "
|
|
454
|
+
f"and not more than 1 minor version ahead of the server."
|
|
455
|
+
)
|
|
456
|
+
logger.warning(
|
|
457
|
+
f"To resolve this issue, please update your kubernetes Python client to a compatible version."
|
|
458
|
+
)
|
|
459
|
+
is_compatible = False
|
|
460
|
+
|
|
461
|
+
return is_compatible
|
|
462
|
+
except Exception as e:
|
|
463
|
+
logger.warning(f"Failed to verify Kubernetes version compatibility: {e}")
|
|
464
|
+
return True # Be lenient if we can't check compatibility
|
|
@@ -14,6 +14,7 @@ from sagemaker.hyperpod.common.utils import (
|
|
|
14
14
|
handle_exception,
|
|
15
15
|
setup_logging,
|
|
16
16
|
get_default_namespace,
|
|
17
|
+
verify_kubernetes_version_compatibility,
|
|
17
18
|
)
|
|
18
19
|
from sagemaker.hyperpod.common.telemetry.telemetry_logging import (
|
|
19
20
|
_hyperpod_telemetry_emitter,
|
|
@@ -24,15 +25,18 @@ from sagemaker.hyperpod.common.telemetry.constants import Feature
|
|
|
24
25
|
class HPEndpointBase:
|
|
25
26
|
is_kubeconfig_loaded = False
|
|
26
27
|
|
|
28
|
+
@classmethod
|
|
29
|
+
def get_logger(cls):
|
|
30
|
+
return logging.getLogger(__name__)
|
|
31
|
+
|
|
27
32
|
@classmethod
|
|
28
33
|
def verify_kube_config(cls):
|
|
29
34
|
if not cls.is_kubeconfig_loaded:
|
|
30
35
|
config.load_kube_config()
|
|
31
36
|
cls.is_kubeconfig_loaded = True
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
return logging.getLogger(__name__)
|
|
37
|
+
|
|
38
|
+
# Verify Kubernetes version compatibility
|
|
39
|
+
verify_kubernetes_version_compatibility(cls.get_logger())
|
|
36
40
|
|
|
37
41
|
@classmethod
|
|
38
42
|
def call_create_api(
|
|
@@ -0,0 +1,301 @@
|
|
|
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
|
+
|
|
14
|
+
"""Notebook visualization utilities for Models in JumpStart Public Hub."""
|
|
15
|
+
from __future__ import absolute_import
|
|
16
|
+
|
|
17
|
+
import time
|
|
18
|
+
import boto3
|
|
19
|
+
import itables
|
|
20
|
+
import pandas
|
|
21
|
+
import logging
|
|
22
|
+
import json
|
|
23
|
+
from botocore.config import Config
|
|
24
|
+
from ipywidgets import Button, Output
|
|
25
|
+
from IPython.display import display
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class ModelDataLoader:
|
|
29
|
+
"""Handles progressive loading of model data from SageMaker Hub."""
|
|
30
|
+
|
|
31
|
+
TIMEOUT_SECONDS = 30
|
|
32
|
+
MAX_RESULTS_PER_CALL = 100
|
|
33
|
+
|
|
34
|
+
def __init__(self, region: str, hub_name: str = "SageMakerPublicHub"):
|
|
35
|
+
config = Config(region_name=region, retries={"max_attempts": 10, "mode": "adaptive"})
|
|
36
|
+
self.client = boto3.client("sagemaker", config=config)
|
|
37
|
+
self.hub_name = hub_name
|
|
38
|
+
self.all_data = []
|
|
39
|
+
self.next_token = None
|
|
40
|
+
self.table_output = None
|
|
41
|
+
self.load_button = None
|
|
42
|
+
|
|
43
|
+
def load_initial_data(self, limit: int = 100):
|
|
44
|
+
"""Load first batch of models and display progressive table."""
|
|
45
|
+
batch_data = self._get_batch(limit)
|
|
46
|
+
self.all_data = batch_data["data"]
|
|
47
|
+
self.next_token = batch_data["next_token"]
|
|
48
|
+
self._display_progressive_table()
|
|
49
|
+
|
|
50
|
+
def _get_batch(self, limit: int = 100):
|
|
51
|
+
"""Get a batch of model data with pagination and timeout."""
|
|
52
|
+
model_summaries = []
|
|
53
|
+
current_token = self.next_token
|
|
54
|
+
start_time = time.time()
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
while (len(model_summaries) < limit and
|
|
58
|
+
(time.time() - start_time) < self.TIMEOUT_SECONDS):
|
|
59
|
+
|
|
60
|
+
params = {
|
|
61
|
+
"HubName": self.hub_name,
|
|
62
|
+
"HubContentType": "Model",
|
|
63
|
+
"MaxResults": min(self.MAX_RESULTS_PER_CALL, limit - len(model_summaries))
|
|
64
|
+
}
|
|
65
|
+
if current_token:
|
|
66
|
+
params["NextToken"] = current_token
|
|
67
|
+
|
|
68
|
+
response = self.client.list_hub_contents(**params)
|
|
69
|
+
batch_summaries = [
|
|
70
|
+
self._get_model_summary(summary)
|
|
71
|
+
for summary in response["HubContentSummaries"]
|
|
72
|
+
]
|
|
73
|
+
model_summaries.extend(batch_summaries)
|
|
74
|
+
|
|
75
|
+
current_token = response.get("NextToken")
|
|
76
|
+
if not current_token:
|
|
77
|
+
break
|
|
78
|
+
|
|
79
|
+
if (time.time() - start_time) >= self.TIMEOUT_SECONDS:
|
|
80
|
+
logging.debug(f"Retrieved {len(model_summaries)} models in {self.TIMEOUT_SECONDS} seconds. There may be more models to list, press Load More Models button to continue loading.")
|
|
81
|
+
|
|
82
|
+
return {"data": model_summaries, "next_token": current_token}
|
|
83
|
+
|
|
84
|
+
except Exception as e:
|
|
85
|
+
print(f"Error fetching models: {e}")
|
|
86
|
+
return {"data": model_summaries, "next_token": current_token}
|
|
87
|
+
|
|
88
|
+
def _display_progressive_table(self):
|
|
89
|
+
"""Display table with optional load more button."""
|
|
90
|
+
if not self.all_data:
|
|
91
|
+
print("No models to display")
|
|
92
|
+
return
|
|
93
|
+
|
|
94
|
+
self._create_and_display_table()
|
|
95
|
+
self._create_load_button()
|
|
96
|
+
|
|
97
|
+
def _create_load_button(self):
|
|
98
|
+
"""Create and display load more button if needed."""
|
|
99
|
+
if self.next_token:
|
|
100
|
+
self.load_button = Button(description="Load More Models")
|
|
101
|
+
self.load_button.on_click(lambda b: self._load_more())
|
|
102
|
+
display(self.load_button)
|
|
103
|
+
|
|
104
|
+
def _create_and_display_table(self):
|
|
105
|
+
"""Create and display the data table."""
|
|
106
|
+
self.table_output = Output()
|
|
107
|
+
display(self.table_output)
|
|
108
|
+
|
|
109
|
+
try:
|
|
110
|
+
with self.table_output:
|
|
111
|
+
self.table_output.clear_output(wait=True)
|
|
112
|
+
interactive_view(self.all_data)
|
|
113
|
+
except Exception as e:
|
|
114
|
+
print(f"Error displaying table: {e}")
|
|
115
|
+
|
|
116
|
+
def _load_more(self):
|
|
117
|
+
"""Load next batch and update consolidated table."""
|
|
118
|
+
if not self.next_token:
|
|
119
|
+
print("No more models to load")
|
|
120
|
+
return
|
|
121
|
+
|
|
122
|
+
self._set_loading_state()
|
|
123
|
+
self._fetch_and_update_data()
|
|
124
|
+
self._update_button_state()
|
|
125
|
+
|
|
126
|
+
def _set_loading_state(self):
|
|
127
|
+
"""Set button to loading state."""
|
|
128
|
+
if self.load_button:
|
|
129
|
+
self.load_button.disabled = True
|
|
130
|
+
self.load_button.description = "Loading..."
|
|
131
|
+
|
|
132
|
+
def _fetch_and_update_data(self):
|
|
133
|
+
"""Fetch new data and update table."""
|
|
134
|
+
batch_data = self._get_batch()
|
|
135
|
+
self.next_token = batch_data["next_token"]
|
|
136
|
+
self.all_data.extend(batch_data["data"])
|
|
137
|
+
|
|
138
|
+
with self.table_output:
|
|
139
|
+
self.table_output.clear_output(wait=True)
|
|
140
|
+
interactive_view(self.all_data)
|
|
141
|
+
|
|
142
|
+
def _update_button_state(self):
|
|
143
|
+
"""Update button state based on data availability."""
|
|
144
|
+
if not self.load_button:
|
|
145
|
+
return
|
|
146
|
+
|
|
147
|
+
if self.next_token:
|
|
148
|
+
self.load_button.disabled = False
|
|
149
|
+
self.load_button.description = "Load More Models"
|
|
150
|
+
else:
|
|
151
|
+
self.load_button.description = "All Models Loaded"
|
|
152
|
+
|
|
153
|
+
def _get_model_summary(self, full_summary):
|
|
154
|
+
"""Extract relevant model information."""
|
|
155
|
+
keywords = full_summary["HubContentSearchKeywords"]
|
|
156
|
+
model_type = self._determine_model_type(keywords, full_summary["HubContentName"])
|
|
157
|
+
|
|
158
|
+
return {
|
|
159
|
+
"Model Id": full_summary["HubContentName"],
|
|
160
|
+
"Model Display Name": full_summary["HubContentDisplayName"],
|
|
161
|
+
"Model Type": model_type,
|
|
162
|
+
"Model Description": full_summary["HubContentDescription"],
|
|
163
|
+
"Search Keywords": keywords,
|
|
164
|
+
"Deployment Configs": self._create_config_link(full_summary["HubContentName"]),
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
def _determine_model_type(self, keywords, model_id):
|
|
168
|
+
"""Determine model type from keywords or hub document."""
|
|
169
|
+
if "@model-type:proprietary" in keywords:
|
|
170
|
+
return "Proprietary"
|
|
171
|
+
elif "@model-type:open_weights" in keywords:
|
|
172
|
+
return "Open"
|
|
173
|
+
else:
|
|
174
|
+
# Check if gated via hub content document
|
|
175
|
+
hub_doc = self._get_hub_document(model_id)
|
|
176
|
+
return "Gated" if '"GatedBucket": true' in hub_doc else "Open"
|
|
177
|
+
|
|
178
|
+
def _get_hub_document(self, model_id):
|
|
179
|
+
"""Get hub content document for model."""
|
|
180
|
+
return self.client.describe_hub_content(
|
|
181
|
+
HubName=self.hub_name,
|
|
182
|
+
HubContentType="Model",
|
|
183
|
+
HubContentName=model_id
|
|
184
|
+
)["HubContentDocument"]
|
|
185
|
+
|
|
186
|
+
def _get_supported_instance_types(self, model_id):
|
|
187
|
+
"""Extract supported instance types from hub document."""
|
|
188
|
+
try:
|
|
189
|
+
hub_doc = self._get_hub_document(model_id)
|
|
190
|
+
doc_data = json.loads(hub_doc)
|
|
191
|
+
|
|
192
|
+
supported_types = doc_data.get("SupportedInferenceInstanceTypes", [])
|
|
193
|
+
default_type = doc_data.get("DefaultInferenceInstanceType")
|
|
194
|
+
|
|
195
|
+
if default_type and default_type in supported_types:
|
|
196
|
+
supported_types = [default_type] + [t for t in supported_types if t != default_type]
|
|
197
|
+
|
|
198
|
+
return {"types": supported_types, "default": default_type, "error": None}
|
|
199
|
+
except Exception as e:
|
|
200
|
+
return {"types": [], "default": None, "error": str(e)}
|
|
201
|
+
|
|
202
|
+
def _create_config_link(self, model_id):
|
|
203
|
+
"""Create deployment config display using collapsible details for all environments."""
|
|
204
|
+
return f'<details><summary style="color: #007bff; cursor: pointer;">View SDK Config</summary><pre style="font-size: 10px; background: #f5f5f5; padding: 5px; margin: 5px 0;">{self._generate_deployment_config(model_id)}</pre></details>'
|
|
205
|
+
|
|
206
|
+
def _generate_deployment_config(self, model_id):
|
|
207
|
+
"""Generate deployment configuration code for a model."""
|
|
208
|
+
instance_data = self._get_supported_instance_types(model_id)
|
|
209
|
+
supported_types = instance_data["types"]
|
|
210
|
+
default_type = instance_data["default"]
|
|
211
|
+
error = instance_data["error"]
|
|
212
|
+
|
|
213
|
+
if error:
|
|
214
|
+
instance_type = '<ENTER-INSTANCE-TYPE>'
|
|
215
|
+
types_comment = ""
|
|
216
|
+
else:
|
|
217
|
+
instance_type = default_type if default_type else '\<ENTER-INSTANCE-TYPE\>'
|
|
218
|
+
types_comment = self._format_instance_types_comment(supported_types)
|
|
219
|
+
|
|
220
|
+
config_code = f'''# Deployment configuration for {model_id}
|
|
221
|
+
from sagemaker.hyperpod.inference.config.hp_jumpstart_endpoint_config import (
|
|
222
|
+
Model, Server, SageMakerEndpoint
|
|
223
|
+
)
|
|
224
|
+
from sagemaker.hyperpod.inference.hp_jumpstart_endpoint import HPJumpStartEndpoint
|
|
225
|
+
|
|
226
|
+
{types_comment}
|
|
227
|
+
|
|
228
|
+
# Create configs
|
|
229
|
+
model = Model(
|
|
230
|
+
model_id='{model_id}',
|
|
231
|
+
)
|
|
232
|
+
server = Server(
|
|
233
|
+
instance_type='{instance_type}',
|
|
234
|
+
)
|
|
235
|
+
|
|
236
|
+
# Default endpoint name using model_id, modify as desired
|
|
237
|
+
endpoint_name = SageMakerEndpoint(name='{model_id}')
|
|
238
|
+
|
|
239
|
+
# Create endpoint spec
|
|
240
|
+
js_endpoint = HPJumpStartEndpoint(
|
|
241
|
+
model=model,
|
|
242
|
+
server=server,
|
|
243
|
+
sage_maker_endpoint=endpoint_name,
|
|
244
|
+
)
|
|
245
|
+
|
|
246
|
+
# Deploy the endpoint
|
|
247
|
+
js_endpoint.create()'''
|
|
248
|
+
return config_code
|
|
249
|
+
|
|
250
|
+
def _format_instance_types_comment(self, supported_types):
|
|
251
|
+
"""Format instance types comment with line breaks for better readability."""
|
|
252
|
+
if not supported_types:
|
|
253
|
+
return "# No supported instance types found"
|
|
254
|
+
|
|
255
|
+
if len(supported_types) <= 5:
|
|
256
|
+
return f"# Supported instance types: {', '.join(supported_types)}"
|
|
257
|
+
|
|
258
|
+
# For more than 5 instance types, format with newlines every 5 types
|
|
259
|
+
comment_lines = ["# Supported instance types:"]
|
|
260
|
+
for i in range(0, len(supported_types), 5):
|
|
261
|
+
batch = supported_types[i:i+5]
|
|
262
|
+
comment_lines.append(f"# {', '.join(batch)}")
|
|
263
|
+
|
|
264
|
+
return '\n'.join(comment_lines)
|
|
265
|
+
|
|
266
|
+
|
|
267
|
+
def get_all_public_hub_model_data(region: str):
|
|
268
|
+
"""Load and display SageMaker public hub models with progressive loading."""
|
|
269
|
+
loader = ModelDataLoader(region)
|
|
270
|
+
loader.load_initial_data()
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
def interactive_view(tabular_data: list):
|
|
274
|
+
"""Display interactive table in Jupyter notebook."""
|
|
275
|
+
if not tabular_data:
|
|
276
|
+
return
|
|
277
|
+
|
|
278
|
+
_configure_itables()
|
|
279
|
+
df = pandas.DataFrame(tabular_data)
|
|
280
|
+
styled_df = _style_dataframe(df)
|
|
281
|
+
layout = _get_table_layout(len(tabular_data))
|
|
282
|
+
|
|
283
|
+
itables.show(styled_df, layout=layout, allow_html=True)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
def _configure_itables():
|
|
287
|
+
"""Configure itables for notebook display."""
|
|
288
|
+
itables.init_notebook_mode(all_interactive=True)
|
|
289
|
+
itables.options.allow_html = True
|
|
290
|
+
|
|
291
|
+
|
|
292
|
+
def _style_dataframe(df):
|
|
293
|
+
"""Apply styling to dataframe."""
|
|
294
|
+
return df.style.set_properties(**{"text-align": "left"}).set_table_styles(
|
|
295
|
+
[{"selector": "th", "props": [("text-align", "left")]}]
|
|
296
|
+
)
|
|
297
|
+
|
|
298
|
+
|
|
299
|
+
def _get_table_layout(data_length):
|
|
300
|
+
"""Get appropriate table layout based on data size."""
|
|
301
|
+
return {} if data_length > 10 else {"topStart": None, "topEnd": "search"}
|
|
@@ -9,6 +9,7 @@ from sagemaker.hyperpod.common.utils import (
|
|
|
9
9
|
handle_exception,
|
|
10
10
|
get_default_namespace,
|
|
11
11
|
setup_logging,
|
|
12
|
+
verify_kubernetes_version_compatibility
|
|
12
13
|
)
|
|
13
14
|
from sagemaker.hyperpod.common.telemetry.telemetry_logging import (
|
|
14
15
|
_hyperpod_telemetry_emitter,
|
|
@@ -17,6 +18,7 @@ from sagemaker.hyperpod.common.telemetry.constants import Feature
|
|
|
17
18
|
import yaml
|
|
18
19
|
import logging
|
|
19
20
|
|
|
21
|
+
|
|
20
22
|
TRAINING_GROUP = "sagemaker.amazonaws.com"
|
|
21
23
|
API_VERSION = "v1"
|
|
22
24
|
PLURAL = "hyperpodpytorchjobs"
|
|
@@ -36,15 +38,18 @@ class HyperPodPytorchJob(_HyperPodPytorchJob):
|
|
|
36
38
|
default=None, description="The status of the HyperPodPytorchJob"
|
|
37
39
|
)
|
|
38
40
|
|
|
41
|
+
@classmethod
|
|
42
|
+
def get_logger(cls):
|
|
43
|
+
return logging.getLogger(__name__)
|
|
44
|
+
|
|
39
45
|
@classmethod
|
|
40
46
|
def verify_kube_config(cls):
|
|
41
47
|
if not cls.is_kubeconfig_loaded:
|
|
42
48
|
config.load_kube_config()
|
|
43
49
|
cls.is_kubeconfig_loaded = True
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
return logging.getLogger(__name__)
|
|
50
|
+
|
|
51
|
+
# Verify Kubernetes version compatibility
|
|
52
|
+
verify_kubernetes_version_compatibility(cls.get_logger())
|
|
48
53
|
|
|
49
54
|
@_hyperpod_telemetry_emitter(Feature.HYPERPOD, "create_pytorchjob")
|
|
50
55
|
def create(self, debug=False):
|
|
@@ -79,7 +84,7 @@ class HyperPodPytorchJob(_HyperPodPytorchJob):
|
|
|
79
84
|
plural=PLURAL,
|
|
80
85
|
body=config,
|
|
81
86
|
)
|
|
82
|
-
logger.info("Successfully submitted HyperPodPytorchJob!")
|
|
87
|
+
logger.info(f"Successfully submitted HyperPodPytorchJob '{self.metadata.name}'!")
|
|
83
88
|
except Exception as e:
|
|
84
89
|
logger.error(f"Failed to create HyperPodPytorchJob {self.metadata.name}!")
|
|
85
90
|
handle_exception(e, self.metadata.name, self.metadata.namespace)
|
|
@@ -126,7 +131,7 @@ class HyperPodPytorchJob(_HyperPodPytorchJob):
|
|
|
126
131
|
plural=PLURAL,
|
|
127
132
|
name=self.metadata.name,
|
|
128
133
|
)
|
|
129
|
-
logger.info(f"Successful deleted HyperPodPytorchJob!")
|
|
134
|
+
logger.info(f"Successful deleted HyperPodPytorchJob '{self.metadata.name}'!")
|
|
130
135
|
except Exception as e:
|
|
131
136
|
logger.error(f"Failed to delete HyperPodPytorchJob {self.metadata.name}!")
|
|
132
137
|
handle_exception(e, self.metadata.name, self.metadata.namespace)
|
|
@@ -230,11 +235,9 @@ class HyperPodPytorchJob(_HyperPodPytorchJob):
|
|
|
230
235
|
|
|
231
236
|
|
|
232
237
|
def _load_hp_job(response: dict) -> HyperPodPytorchJob:
|
|
233
|
-
name = response["metadata"]["name"]
|
|
234
|
-
namespace = response["metadata"]["namespace"]
|
|
235
238
|
|
|
236
239
|
spec = _HyperPodPytorchJob.model_validate(response["spec"], by_name=True)
|
|
237
|
-
metadata = Metadata(
|
|
240
|
+
metadata = Metadata(**response["metadata"])
|
|
238
241
|
|
|
239
242
|
if "status" in response:
|
|
240
243
|
status = HyperPodPytorchJobStatus.model_validate(
|
{sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker_hyperpod.egg-info/SOURCES.txt
RENAMED
|
@@ -7,6 +7,7 @@ setup.py
|
|
|
7
7
|
src/sagemaker/__init__.py
|
|
8
8
|
src/sagemaker/hyperpod/__init__.py
|
|
9
9
|
src/sagemaker/hyperpod/cli/__init__.py
|
|
10
|
+
src/sagemaker/hyperpod/cli/common_utils.py
|
|
10
11
|
src/sagemaker/hyperpod/cli/hyp_cli.py
|
|
11
12
|
src/sagemaker/hyperpod/cli/inference_utils.py
|
|
12
13
|
src/sagemaker/hyperpod/cli/training_utils.py
|
|
@@ -51,6 +52,7 @@ src/sagemaker/hyperpod/inference/__init__.py
|
|
|
51
52
|
src/sagemaker/hyperpod/inference/hp_endpoint.py
|
|
52
53
|
src/sagemaker/hyperpod/inference/hp_endpoint_base.py
|
|
53
54
|
src/sagemaker/hyperpod/inference/hp_jumpstart_endpoint.py
|
|
55
|
+
src/sagemaker/hyperpod/inference/jumpstart_public_hub_visualization_utils.py
|
|
54
56
|
src/sagemaker/hyperpod/inference/config/__init__.py
|
|
55
57
|
src/sagemaker/hyperpod/inference/config/constants.py
|
|
56
58
|
src/sagemaker/hyperpod/inference/config/hp_endpoint_config.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/__init__.py
RENAMED
|
File without changes
|
{sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/clients/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/commands/cluster.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/service/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/cli/service/get_logs.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/common/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/inference/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/observability/utils.py
RENAMED
|
File without changes
|
{sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker/hyperpod/training/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker_hyperpod.egg-info/requires.txt
RENAMED
|
File without changes
|
{sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker_hyperpod.egg-info/top_level.txt
RENAMED
|
File without changes
|
{sagemaker_hyperpod-3.0.2 → sagemaker_hyperpod-3.1.0}/src/sagemaker_hyperpod.egg-info/zip-safe
RENAMED
|
File without changes
|