Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 9 additions & 2 deletions sagemaker-serve/src/sagemaker/serve/model_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -2931,7 +2931,12 @@ def _build_single_modelbuilder(
"Cannot deploy LORA adapter without base model artifacts."
)
accept_eula = getattr(self, "accept_eula", None)
if not accept_eula:
# Only models that declare a hosting EULA (gated models such as
# Meta Llama) require explicit acceptance. Ungated models
# (Apache-2.0, MIT, etc.) carry no HostingEulaUri and must deploy

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: there's a bit too many comments here! Maybe we can make them shorter so that the code is a bit cleaner...?

# without the flag. See HubContentDocument.HostingEulaUri.
requires_eula = bool(hub_document.get("HostingEulaUri"))
if requires_eula and not accept_eula:
raise ValueError(
"accept_eula must be set to True to deploy this model. "
"Please set accept_eula=True on the ModelBuilder instance to confirm "
Expand All @@ -2945,7 +2950,9 @@ def _build_single_modelbuilder(
"s3_uri": hosting_artifact_uri,
"s3_data_type": "S3Prefix",
"compression_type": "None",
"model_access_config": {"accept_eula": accept_eula},
# accept_eula may be None (default); coerce so the
# config never carries null for an ungated model.
"model_access_config": {"accept_eula": bool(accept_eula)},
}
},
)
Expand Down
64 changes: 55 additions & 9 deletions sagemaker-serve/tests/unit/test_model_builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -831,8 +831,17 @@ def _make_mb(self, accept_eula=None):
mb.mode = None
return mb

def _patch_lora_deps(self, mb, hosting_uri="s3://bucket/hosting/"):
"""Patch all dependencies needed to reach the LoRA ContainerDefinition block."""
def _patch_lora_deps(self, mb, hosting_uri="s3://bucket/hosting/",
hosting_eula_uri=None):
"""Patch all dependencies needed to reach the LoRA ContainerDefinition block.

Pass ``hosting_eula_uri`` to simulate a gated model whose hub content
document declares a ``HostingEulaUri``; omit it to simulate an ungated
(e.g. Apache-2.0) model.
"""
hub_document = {"HostingArtifactUri": hosting_uri}
if hosting_eula_uri is not None:
hub_document["HostingEulaUri"] = hosting_eula_uri
patches = [
patch.object(mb, "_get_serve_setting", return_value=MagicMock()),
patch.object(mb, "_is_model_customization", return_value=True),
Expand All @@ -841,13 +850,15 @@ def _patch_lora_deps(self, mb, hosting_uri="s3://bucket/hosting/"):
patch.object(mb, "_is_nova_model", return_value=False),
patch.object(mb, "_fetch_peft", return_value="LORA"),
patch.object(mb, "_fetch_hub_document_for_custom_model",
return_value={"HostingArtifactUri": hosting_uri}),
return_value=hub_document),
]
return patches

def test_lora_build_raises_when_accept_eula_false(self):
_GATED_EULA_URI = "s3://jumpstart-cache-prod/eula/llama_eula.txt"

def test_lora_gated_build_raises_when_accept_eula_false(self):
mb = self._make_mb(accept_eula=False)
patches = self._patch_lora_deps(mb)
patches = self._patch_lora_deps(mb, hosting_eula_uri=self._GATED_EULA_URI)
for p in patches:
p.start()
try:
Expand All @@ -858,9 +869,9 @@ def test_lora_build_raises_when_accept_eula_false(self):
for p in patches:
p.stop()

def test_lora_build_raises_when_accept_eula_not_set(self):
def test_lora_gated_build_raises_when_accept_eula_not_set(self):
mb = self._make_mb(accept_eula=None)
patches = self._patch_lora_deps(mb)
patches = self._patch_lora_deps(mb, hosting_eula_uri=self._GATED_EULA_URI)
for p in patches:
p.start()
try:
Expand All @@ -873,10 +884,10 @@ def test_lora_build_raises_when_accept_eula_not_set(self):

@patch("sagemaker.serve.model_builder.ContainerDefinition")
@patch("sagemaker.serve.model_builder.Model")
def test_lora_build_passes_accept_eula_true(self, mock_model, mock_container_def):
def test_lora_gated_build_passes_accept_eula_true(self, mock_model, mock_container_def):
mb = self._make_mb(accept_eula=True)
mock_model.create.return_value = MagicMock()
patches = self._patch_lora_deps(mb)
patches = self._patch_lora_deps(mb, hosting_eula_uri=self._GATED_EULA_URI)
for p in patches:
p.start()
try:
Expand All @@ -889,3 +900,38 @@ def test_lora_build_passes_accept_eula_true(self, mock_model, mock_container_def
finally:
for p in patches:
p.stop()

@patch("sagemaker.serve.model_builder.ContainerDefinition")
@patch("sagemaker.serve.model_builder.Model")
def test_lora_ungated_build_succeeds_without_accept_eula(self, mock_model, mock_container_def):
"""Ungated model (no HostingEulaUri) must deploy without accept_eula."""
mb = self._make_mb(accept_eula=None)
mock_model.create.return_value = MagicMock()
patches = self._patch_lora_deps(mb) # no hosting_eula_uri -> ungated
for p in patches:
p.start()
try:
mb._build_single_modelbuilder() # must not raise
call_kwargs = mock_container_def.call_args[1]
eula_val = (
call_kwargs["model_data_source"]["s3_data_source"]["model_access_config"]["accept_eula"]
)
self.assertFalse(eula_val)
finally:
for p in patches:
p.stop()

@patch("sagemaker.serve.model_builder.ContainerDefinition")
@patch("sagemaker.serve.model_builder.Model")
def test_lora_ungated_build_succeeds_with_accept_eula_false(self, mock_model, mock_container_def):
"""Explicit accept_eula=False on an ungated model must still deploy."""
mb = self._make_mb(accept_eula=False)
mock_model.create.return_value = MagicMock()
patches = self._patch_lora_deps(mb) # ungated
for p in patches:
p.start()
try:
mb._build_single_modelbuilder() # must not raise
finally:
for p in patches:
p.stop()
Loading