diff --git a/sagemaker-core/src/sagemaker/core/image_uri_config/sglang.json b/sagemaker-core/src/sagemaker/core/image_uri_config/sglang.json new file mode 100644 index 0000000000..d8d3bffc8d --- /dev/null +++ b/sagemaker-core/src/sagemaker/core/image_uri_config/sglang.json @@ -0,0 +1,62 @@ +{ + "inference": { + "processors": [ + "gpu" + ], + "version_aliases": { + "0.5": "0.5.18" + }, + "versions": { + "0.5.18": { + "py_versions": [ + "py312" + ], + "registries": { + "af-south-1": "626614931356", + "ap-east-1": "871362719292", + "ap-east-2": "975050140332", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-northeast-3": "364406365360", + "ap-south-1": "763104351884", + "ap-south-2": "772153158452", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ap-southeast-3": "907027046896", + "ap-southeast-4": "457447274322", + "ap-southeast-5": "550225433462", + "ap-southeast-6": "633930458069", + "ap-southeast-7": "590183813437", + "ca-central-1": "763104351884", + "ca-west-1": "204538143572", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-central-2": "380420809688", + "eu-north-1": "763104351884", + "eu-south-1": "692866216735", + "eu-south-2": "503227376785", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "il-central-1": "780543022126", + "me-central-1": "914824155844", + "me-south-1": "217643126080", + "mx-central-1": "637423239942", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-east-1": "446045086412", + "us-gov-west-1": "442386744353", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "tag_prefix": "0.5.18", + "repository": "sglang", + "container_version": { + "gpu": "cu130-ubuntu24.04-sagemaker" + } + } + } + } +} diff --git a/sagemaker-core/src/sagemaker/core/image_uri_config/vllm.json b/sagemaker-core/src/sagemaker/core/image_uri_config/vllm.json new file mode 100644 index 0000000000..3630161df9 --- /dev/null +++ b/sagemaker-core/src/sagemaker/core/image_uri_config/vllm.json @@ -0,0 +1,62 @@ +{ + "inference": { + "processors": [ + "gpu" + ], + "version_aliases": { + "0.28": "0.28.0" + }, + "versions": { + "0.28.0": { + "py_versions": [ + "py312" + ], + "registries": { + "af-south-1": "626614931356", + "ap-east-1": "871362719292", + "ap-east-2": "975050140332", + "ap-northeast-1": "763104351884", + "ap-northeast-2": "763104351884", + "ap-northeast-3": "364406365360", + "ap-south-1": "763104351884", + "ap-south-2": "772153158452", + "ap-southeast-1": "763104351884", + "ap-southeast-2": "763104351884", + "ap-southeast-3": "907027046896", + "ap-southeast-4": "457447274322", + "ap-southeast-5": "550225433462", + "ap-southeast-6": "633930458069", + "ap-southeast-7": "590183813437", + "ca-central-1": "763104351884", + "ca-west-1": "204538143572", + "cn-north-1": "727897471807", + "cn-northwest-1": "727897471807", + "eu-central-1": "763104351884", + "eu-central-2": "380420809688", + "eu-north-1": "763104351884", + "eu-south-1": "692866216735", + "eu-south-2": "503227376785", + "eu-west-1": "763104351884", + "eu-west-2": "763104351884", + "eu-west-3": "763104351884", + "il-central-1": "780543022126", + "me-central-1": "914824155844", + "me-south-1": "217643126080", + "mx-central-1": "637423239942", + "sa-east-1": "763104351884", + "us-east-1": "763104351884", + "us-east-2": "763104351884", + "us-gov-east-1": "446045086412", + "us-gov-west-1": "442386744353", + "us-west-1": "763104351884", + "us-west-2": "763104351884" + }, + "tag_prefix": "0.28.0", + "repository": "vllm", + "container_version": { + "gpu": "cu130-ubuntu24.04-sagemaker" + } + } + } + } +} diff --git a/sagemaker-core/tests/unit/image_uris/test_sglang.py b/sagemaker-core/tests/unit/image_uris/test_sglang.py new file mode 100644 index 0000000000..bcc52bbad5 --- /dev/null +++ b/sagemaker-core/tests/unit/image_uris/test_sglang.py @@ -0,0 +1,94 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +from __future__ import absolute_import + +import pytest + +from sagemaker.core import image_uris +from sagemaker.core.common_utils import ALTERNATE_DOMAINS + +# SGLang images are GPU-only; a GPU instance type selects the "gpu" processor. +INSTANCE_TYPE = "ml.g5.2xlarge" +DEFAULT_DOMAIN = "amazonaws.com" + +# Regions whose ECR host suffix is stable across botocore versions (commercial, +# China, GovCloud). Exact-host assertions are limited to these; every other region +# is still covered by the account/region/repository/tag checks in test_sglang_uris. +FULL_URI_REGIONS = ["us-east-1", "us-west-2", "eu-west-1", "cn-north-1", "us-gov-west-1"] + + +@pytest.mark.parametrize("load_config", ["sglang.json"], indirect=True) +def test_sglang_uris(load_config): + """Every (version, region) resolves to the sglang repo with the expected account + tag.""" + config = load_config + assert config["inference"]["processors"] == ["gpu"] + versions = config["inference"]["versions"] + for version, version_config in versions.items(): + py_version = version_config["py_versions"][0] + container_version = version_config["container_version"]["gpu"] + expected_tag = f"{version}-gpu-{py_version}-{container_version}" + for region, account in version_config["registries"].items(): + uri = image_uris.retrieve( + framework="sglang", + region=region, + version=version, + image_scope="inference", + instance_type=INSTANCE_TYPE, + ) + # account (registry), region, repository and tag are config-controlled; + # the domain suffix is resolved by botocore and asserted separately below. + assert uri.startswith(f"{account}.dkr.ecr.{region}."), uri + assert uri.endswith(f"/sglang:{expected_tag}"), uri + + +@pytest.mark.parametrize("load_config", ["sglang.json"], indirect=True) +def test_sglang_full_uri_for_representative_regions(load_config): + """Exact URI (including domain) for representative commercial/China/GovCloud regions.""" + config = load_config + versions = config["inference"]["versions"] + for version, version_config in versions.items(): + py_version = version_config["py_versions"][0] + container_version = version_config["container_version"]["gpu"] + for region in FULL_URI_REGIONS: + if region not in version_config["registries"]: + continue + account = version_config["registries"][region] + domain = ALTERNATE_DOMAINS.get(region, DEFAULT_DOMAIN) + expected = ( + f"{account}.dkr.ecr.{region}.{domain}" + f"/sglang:{version}-gpu-{py_version}-{container_version}" + ) + uri = image_uris.retrieve( + framework="sglang", + region=region, + version=version, + image_scope="inference", + instance_type=INSTANCE_TYPE, + ) + assert uri == expected + + +@pytest.mark.parametrize("load_config", ["sglang.json"], indirect=True) +def test_sglang_version_aliases_resolve_to_newest_patch(load_config): + """Each minor alias resolves to its newest patch version.""" + config = load_config + aliases = config["inference"]["version_aliases"] + for alias, target_version in aliases.items(): + uri = image_uris.retrieve( + framework="sglang", + region="us-west-2", + version=alias, + image_scope="inference", + instance_type=INSTANCE_TYPE, + ) + assert f"/sglang:{target_version}-gpu-" in uri, uri diff --git a/sagemaker-core/tests/unit/image_uris/test_vllm.py b/sagemaker-core/tests/unit/image_uris/test_vllm.py new file mode 100644 index 0000000000..2934a7324b --- /dev/null +++ b/sagemaker-core/tests/unit/image_uris/test_vllm.py @@ -0,0 +1,95 @@ +# Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved. +# +# Licensed under the Apache License, Version 2.0 (the "License"). You +# may not use this file except in compliance with the License. A copy of +# the License is located at +# +# http://aws.amazon.com/apache2.0/ +# +# or in the "license" file accompanying this file. This file is +# distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF +# ANY KIND, either express or implied. See the License for the specific +# language governing permissions and limitations under the License. +from __future__ import absolute_import + +import pytest + +from sagemaker.core import image_uris +from sagemaker.core.common_utils import ALTERNATE_DOMAINS + +# vLLM images are GPU-only; a GPU instance type selects the "gpu" processor. +INSTANCE_TYPE = "ml.g5.2xlarge" +DEFAULT_DOMAIN = "amazonaws.com" + +# Regions whose ECR host suffix is stable across botocore versions (commercial, +# China, GovCloud). Exact-host assertions are limited to these; every other region +# is still covered by the account/region/repository/tag checks in test_vllm_uris, +# which avoids depending on botocore endpoint data for newer ISO partitions. +FULL_URI_REGIONS = ["us-east-1", "us-west-2", "eu-west-1", "cn-north-1", "us-gov-west-1"] + + +@pytest.mark.parametrize("load_config", ["vllm.json"], indirect=True) +def test_vllm_uris(load_config): + """Every (version, region) resolves to the vllm repo with the expected account + tag.""" + config = load_config + assert config["inference"]["processors"] == ["gpu"] + versions = config["inference"]["versions"] + for version, version_config in versions.items(): + py_version = version_config["py_versions"][0] + container_version = version_config["container_version"]["gpu"] + expected_tag = f"{version}-gpu-{py_version}-{container_version}" + for region, account in version_config["registries"].items(): + uri = image_uris.retrieve( + framework="vllm", + region=region, + version=version, + image_scope="inference", + instance_type=INSTANCE_TYPE, + ) + # account (registry), region, repository and tag are config-controlled; + # the domain suffix is resolved by botocore and asserted separately below. + assert uri.startswith(f"{account}.dkr.ecr.{region}."), uri + assert uri.endswith(f"/vllm:{expected_tag}"), uri + + +@pytest.mark.parametrize("load_config", ["vllm.json"], indirect=True) +def test_vllm_full_uri_for_representative_regions(load_config): + """Exact URI (including domain) for representative commercial/China/GovCloud regions.""" + config = load_config + versions = config["inference"]["versions"] + for version, version_config in versions.items(): + py_version = version_config["py_versions"][0] + container_version = version_config["container_version"]["gpu"] + for region in FULL_URI_REGIONS: + if region not in version_config["registries"]: + continue + account = version_config["registries"][region] + domain = ALTERNATE_DOMAINS.get(region, DEFAULT_DOMAIN) + expected = ( + f"{account}.dkr.ecr.{region}.{domain}" + f"/vllm:{version}-gpu-{py_version}-{container_version}" + ) + uri = image_uris.retrieve( + framework="vllm", + region=region, + version=version, + image_scope="inference", + instance_type=INSTANCE_TYPE, + ) + assert uri == expected + + +@pytest.mark.parametrize("load_config", ["vllm.json"], indirect=True) +def test_vllm_version_aliases_resolve_to_newest_patch(load_config): + """Each minor alias resolves to its newest patch version.""" + config = load_config + aliases = config["inference"]["version_aliases"] + for alias, target_version in aliases.items(): + uri = image_uris.retrieve( + framework="vllm", + region="us-west-2", + version=alias, + image_scope="inference", + instance_type=INSTANCE_TYPE, + ) + assert f"/vllm:{target_version}-gpu-" in uri, uri