Skip to content

Commit 471e1fb

Browse files
committed
fix: handle unrecognized JumpStart container images in ModelBuilder
Collapse endpoint mode routing into a single code path since all JumpStart builders (DJL, TGI, MMS) perform identical operations for endpoint deployment. Unknown container images (e.g. vLLM) now pass through instead of raising ValueError. For local container mode, unrecognized images raise ValueError with a message directing users to use SAGEMAKER_ENDPOINT mode.
1 parent ed4b183 commit 471e1fb

3 files changed

Lines changed: 122 additions & 27 deletions

File tree

‎sagemaker-serve/src/sagemaker/serve/model_builder_servers.py‎

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -930,25 +930,28 @@ def _build_for_jumpstart(self) -> Model:
930930
)
931931
return self._build_for_mms_jumpstart(init_kwargs)
932932
else:
933-
raise ValueError(f"Unsupported JumpStart image URI: {self.image_uri}")
933+
raise ValueError(
934+
f"Local container mode is not yet supported for JumpStart image: {self.image_uri}. "
935+
f"Use Mode.SAGEMAKER_ENDPOINT for deployment."
936+
)
934937

935938
else:
936-
# SAGEMAKER_ENDPOINT mode - prepare artifacts if needed
939+
# SAGEMAKER_ENDPOINT mode — all JumpStart containers follow the same
940+
# pattern: JumpStart provides the full image URI, env vars, and S3
941+
# model artifacts. No framework-specific prep is needed.
937942
if not self._optimizing:
938943
self._prepare_for_mode()
939-
940-
# Route to appropriate model server based on image URI
944+
941945
if "djl-inference" in self.image_uri:
942946
self.model_server = ModelServer.DJL_SERVING
943-
return self._build_for_djl_jumpstart(init_kwargs)
944947
elif "tgi-inference" in self.image_uri:
945948
self.model_server = ModelServer.TGI
946-
return self._build_for_tgi_jumpstart(init_kwargs)
947949
elif "huggingface-pytorch-inference" in self.image_uri:
948950
self.model_server = ModelServer.MMS
949-
return self._build_for_mms_jumpstart(init_kwargs)
950-
else:
951-
raise ValueError(f"Unsupported JumpStart image URI: {self.image_uri}")
951+
952+
if not hasattr(self, "s3_upload_path") or not self.s3_upload_path:
953+
self.s3_upload_path = init_kwargs.model_data
954+
return self._create_model()
952955

953956

954957

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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 __future__ import absolute_import
14+
15+
import uuid
16+
import pytest
17+
import logging
18+
19+
from sagemaker.serve.model_builder import ModelBuilder
20+
from sagemaker.core.jumpstart.configs import JumpStartConfig
21+
from sagemaker.train.configs import Compute
22+
23+
logger = logging.getLogger(__name__)
24+
25+
MODEL_ID = "huggingface-vlm-qwen3-5-4b"
26+
INSTANCE_TYPE = "ml.g6.12xlarge"
27+
MODEL_NAME_PREFIX = "js-vllm-test-model"
28+
29+
30+
@pytest.mark.slow_test
31+
def test_jumpstart_vllm_build():
32+
"""Integration test for JumpStart model using vLLM container image.
33+
34+
Validates that ModelBuilder correctly handles vLLM container images
35+
which do not match the legacy djl-inference/tgi-inference/huggingface-pytorch-inference
36+
patterns in the endpoint mode routing.
37+
"""
38+
logger.info("Starting JumpStart vLLM build test...")
39+
40+
compute = Compute(instance_type=INSTANCE_TYPE)
41+
jumpstart_config = JumpStartConfig(model_id=MODEL_ID)
42+
model_builder = ModelBuilder.from_jumpstart_config(
43+
jumpstart_config=jumpstart_config, compute=compute
44+
)
45+
unique_id = str(uuid.uuid4())[:8]
46+
47+
core_model = model_builder.build(model_name=f"{MODEL_NAME_PREFIX}-{unique_id}")
48+
logger.info(f"Model Successfully Created: {core_model.model_name}")
49+
50+
try:
51+
assert "vllm" in model_builder.image_uri, (
52+
f"Expected vLLM image URI, got: {model_builder.image_uri}"
53+
)
54+
assert model_builder.s3_upload_path is not None, "s3_upload_path should be set"
55+
logger.info("JumpStart vLLM build test completed successfully")
56+
finally:
57+
core_model.delete()
58+
logger.info("Model Successfully Deleted!")

‎sagemaker-serve/tests/unit/test_model_builder_servers_coverage.py‎

Lines changed: 52 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ def setUp(self):
319319
@patch('sagemaker.core.jumpstart.factory.utils.get_init_kwargs')
320320
@patch('sagemaker.serve.model_builder.ModelBuilder._prepare_for_mode')
321321
def test_build_for_jumpstart_unsupported_image_raises(self, mock_prepare, mock_get_kwargs):
322-
"""Test that unsupported JumpStart image raises error."""
322+
"""Test that unsupported JumpStart image in LOCAL_CONTAINER mode raises error."""
323323
mock_init_kwargs = Mock()
324324
mock_init_kwargs.image_uri = "unsupported-image:latest"
325325
mock_init_kwargs.env = {}
@@ -329,27 +329,58 @@ def test_build_for_jumpstart_unsupported_image_raises(self, mock_prepare, mock_g
329329
model="some-model-id",
330330
role_arn=MOCK_ROLE_ARN,
331331
sagemaker_session=self.mock_session,
332-
mode=Mode.SAGEMAKER_ENDPOINT
332+
mode=Mode.LOCAL_CONTAINER
333333
)
334334
builder._optimizing = False
335335

336336
with self.assertRaises(ValueError) as context:
337337
builder._build_for_jumpstart()
338338

339-
self.assertIn("Unsupported JumpStart image URI", str(context.exception))
339+
self.assertIn("Local container mode is not yet supported", str(context.exception))
340+
self.assertIn("Mode.SAGEMAKER_ENDPOINT", str(context.exception))
341+
342+
@patch('sagemaker.core.jumpstart.factory.utils.get_init_kwargs')
343+
@patch('sagemaker.serve.model_builder.ModelBuilder._create_model')
344+
@patch('sagemaker.serve.model_builder.ModelBuilder._prepare_for_mode')
345+
def test_build_for_jumpstart_unknown_image_endpoint_uses_default(self, mock_prepare, mock_create, mock_get_kwargs):
346+
"""Test that unknown image in endpoint mode passes through without error."""
347+
mock_init_kwargs = Mock()
348+
mock_init_kwargs.image_uri = "763104351884.dkr.ecr.us-east-1.amazonaws.com/vllm:0.17-gpu-py312-cu129-ubuntu22.04-sagemaker-v1"
349+
mock_init_kwargs.env = {}
350+
mock_init_kwargs.model_data = "s3://jumpstart-cache/models/vllm/model.tar.gz"
351+
mock_get_kwargs.return_value = mock_init_kwargs
352+
353+
mock_model = Mock(spec=Model)
354+
mock_create.return_value = mock_model
355+
356+
builder = ModelBuilder(
357+
model="huggingface-vlm-qwen3-5-4b",
358+
role_arn=MOCK_ROLE_ARN,
359+
sagemaker_session=self.mock_session,
360+
mode=Mode.SAGEMAKER_ENDPOINT
361+
)
362+
builder._optimizing = False
363+
364+
result = builder._build_for_jumpstart()
365+
366+
self.assertEqual(result, mock_model)
367+
self.assertIsNone(builder.model_server)
368+
self.assertEqual(builder.s3_upload_path, "s3://jumpstart-cache/models/vllm/model.tar.gz")
369+
mock_create.assert_called_once()
340370

341371
@patch('sagemaker.core.jumpstart.factory.utils.get_init_kwargs')
342-
@patch('sagemaker.serve.model_builder.ModelBuilder._build_for_djl_jumpstart')
372+
@patch('sagemaker.serve.model_builder.ModelBuilder._create_model')
343373
@patch('sagemaker.serve.model_builder.ModelBuilder._prepare_for_mode')
344-
def test_build_for_jumpstart_routes_to_djl(self, mock_prepare, mock_build_djl, mock_get_kwargs):
374+
def test_build_for_jumpstart_routes_to_djl(self, mock_prepare, mock_create, mock_get_kwargs):
345375
"""Test JumpStart routing to DJL builder."""
346376
mock_init_kwargs = Mock()
347377
mock_init_kwargs.image_uri = "763104351884.dkr.ecr.us-west-2.amazonaws.com/djl-inference:0.21.0-deepspeed0.8.0-cu117"
348378
mock_init_kwargs.env = {}
379+
mock_init_kwargs.model_data = "s3://jumpstart-cache/models/djl/model.tar.gz"
349380
mock_get_kwargs.return_value = mock_init_kwargs
350381

351382
mock_model = Mock(spec=Model)
352-
mock_build_djl.return_value = mock_model
383+
mock_create.return_value = mock_model
353384

354385
builder = ModelBuilder(
355386
model="huggingface-llm-falcon-7b",
@@ -363,20 +394,21 @@ def test_build_for_jumpstart_routes_to_djl(self, mock_prepare, mock_build_djl, m
363394

364395
self.assertEqual(result, mock_model)
365396
self.assertEqual(builder.model_server, ModelServer.DJL_SERVING)
366-
mock_build_djl.assert_called_once()
397+
mock_create.assert_called_once()
367398

368399
@patch('sagemaker.core.jumpstart.factory.utils.get_init_kwargs')
369-
@patch('sagemaker.serve.model_builder.ModelBuilder._build_for_djl_jumpstart')
400+
@patch('sagemaker.serve.model_builder.ModelBuilder._create_model')
370401
@patch('sagemaker.serve.model_builder.ModelBuilder._prepare_for_mode')
371-
def test_build_for_jumpstart_passes_config_name(self, mock_prepare, mock_build_djl, mock_get_kwargs):
402+
def test_build_for_jumpstart_passes_config_name(self, mock_prepare, mock_create, mock_get_kwargs):
372403
"""Test that config_name is forwarded to get_init_kwargs."""
373404
mock_init_kwargs = Mock()
374405
mock_init_kwargs.image_uri = "763104351884.dkr.ecr.us-west-2.amazonaws.com/djl-inference:0.21.0-deepspeed0.8.0-cu117"
375406
mock_init_kwargs.env = {}
407+
mock_init_kwargs.model_data = "s3://jumpstart-cache/models/djl/model.tar.gz"
376408
mock_get_kwargs.return_value = mock_init_kwargs
377409

378410
mock_model = Mock(spec=Model)
379-
mock_build_djl.return_value = mock_model
411+
mock_create.return_value = mock_model
380412

381413
builder = ModelBuilder(
382414
model="meta-textgeneration-llama-3-3-70b-instruct",
@@ -394,17 +426,18 @@ def test_build_for_jumpstart_passes_config_name(self, mock_prepare, mock_build_d
394426
self.assertEqual(call_kwargs.kwargs.get("config_name") or call_kwargs[1].get("config_name"), "lmi-optimized")
395427

396428
@patch('sagemaker.core.jumpstart.factory.utils.get_init_kwargs')
397-
@patch('sagemaker.serve.model_builder.ModelBuilder._build_for_tgi_jumpstart')
429+
@patch('sagemaker.serve.model_builder.ModelBuilder._create_model')
398430
@patch('sagemaker.serve.model_builder.ModelBuilder._prepare_for_mode')
399-
def test_build_for_jumpstart_routes_to_tgi(self, mock_prepare, mock_build_tgi, mock_get_kwargs):
431+
def test_build_for_jumpstart_routes_to_tgi(self, mock_prepare, mock_create, mock_get_kwargs):
400432
"""Test JumpStart routing to TGI builder."""
401433
mock_init_kwargs = Mock()
402434
mock_init_kwargs.image_uri = "763104351884.dkr.ecr.us-west-2.amazonaws.com/huggingface-pytorch-tgi-inference:2.0.1-tgi0.9.3-gpu-py39-cu118-ubuntu20.04"
403435
mock_init_kwargs.env = {}
436+
mock_init_kwargs.model_data = "s3://jumpstart-cache/models/tgi/model.tar.gz"
404437
mock_get_kwargs.return_value = mock_init_kwargs
405438

406439
mock_model = Mock(spec=Model)
407-
mock_build_tgi.return_value = mock_model
440+
mock_create.return_value = mock_model
408441

409442
builder = ModelBuilder(
410443
model="huggingface-llm-mistral-7b",
@@ -418,20 +451,21 @@ def test_build_for_jumpstart_routes_to_tgi(self, mock_prepare, mock_build_tgi, m
418451

419452
self.assertEqual(result, mock_model)
420453
self.assertEqual(builder.model_server, ModelServer.TGI)
421-
mock_build_tgi.assert_called_once()
454+
mock_create.assert_called_once()
422455

423456
@patch('sagemaker.core.jumpstart.factory.utils.get_init_kwargs')
424-
@patch('sagemaker.serve.model_builder.ModelBuilder._build_for_mms_jumpstart')
457+
@patch('sagemaker.serve.model_builder.ModelBuilder._create_model')
425458
@patch('sagemaker.serve.model_builder.ModelBuilder._prepare_for_mode')
426-
def test_build_for_jumpstart_routes_to_mms(self, mock_prepare, mock_build_mms, mock_get_kwargs):
459+
def test_build_for_jumpstart_routes_to_mms(self, mock_prepare, mock_create, mock_get_kwargs):
427460
"""Test JumpStart routing to MMS builder."""
428461
mock_init_kwargs = Mock()
429462
mock_init_kwargs.image_uri = "763104351884.dkr.ecr.us-west-2.amazonaws.com/huggingface-pytorch-inference:1.13.1-transformers4.26.0-gpu-py39-cu117-ubuntu20.04"
430463
mock_init_kwargs.env = {}
464+
mock_init_kwargs.model_data = "s3://jumpstart-cache/models/mms/model.tar.gz"
431465
mock_get_kwargs.return_value = mock_init_kwargs
432466

433467
mock_model = Mock(spec=Model)
434-
mock_build_mms.return_value = mock_model
468+
mock_create.return_value = mock_model
435469

436470
builder = ModelBuilder(
437471
model="pytorch-ic-mobilenet-v2",
@@ -445,7 +479,7 @@ def test_build_for_jumpstart_routes_to_mms(self, mock_prepare, mock_build_mms, m
445479

446480
self.assertEqual(result, mock_model)
447481
self.assertEqual(builder.model_server, ModelServer.MMS)
448-
mock_build_mms.assert_called_once()
482+
mock_create.assert_called_once()
449483

450484

451485
class TestDeployWrappers(unittest.TestCase):

0 commit comments

Comments
 (0)