fix: ModelBuilder with source_code + DJL LMI: /opt/ml/model becomes read-only, breaki (5698) - #5733

Merged
aviruthen merged 2 commits into
aws:masterfrom
aviruthen:fix/modelbuilder-with-source-code-djl-lmi-opt-ml-5698
Apr 17, 2026
Merged

fix: ModelBuilder with source_code + DJL LMI: /opt/ml/model becomes read-only, breaki (5698)#5733
aviruthen merged 2 commits into
aws:masterfrom
aviruthen:fix/modelbuilder-with-source-code-djl-lmi-opt-ml-5698

Conversation

@aviruthen

Copy link
Copy Markdown
Collaborator

Description

The issue has two root causes in _build_for_djl() in model_builder_servers.py:

  1. Missing HF cache redirection: Unlike _build_for_tgi() and _build_for_tei() which set HF_HOME=/tmp and HUGGINGFACE_HUB_CACHE=/tmp, the DJL builder never sets these environment variables. When source_code is provided, the model artifacts (requirements.txt etc.) get packaged as model.tar.gz and mounted read-only at /opt/ml/model/. The DJL container then tries to download HF models to /opt/ml/model/ (the default cache location) and fails with EROFS.

  2. HF_MODEL_ID override: _build_for_djl() unconditionally calls self.env_vars.update({'HF_MODEL_ID': self.model}), which overwrites any user-provided HF_MODEL_ID value. This prevents users from setting HF_MODEL_ID to a local path (e.g., /opt/ml/model) when they want to use pre-downloaded model artifacts.

The fix adds HF cache environment variables (HF_HOME, HUGGINGFACE_HUB_CACHE) pointing to /tmp for the DJL builder (matching TGI/TEI behavior), and changes HF_MODEL_ID to use setdefault() so user-provided values are preserved.

Related Issue

Related issue: 5698

Changes Made

  • sagemaker-serve/src/sagemaker/serve/model_builder_servers.py
  • sagemaker-serve/tests/unit/servers/__init__.py
  • sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py

AI-Generated PR

This PR was automatically generated by the PySDK Issue Agent.

  • Confidence score: 85%
  • Classification: bug
  • SDK version target: V3

Merge Checklist

  • Changes are backward compatible
  • Commit message follows prefix: description format
  • Unit tests added/updated
  • Integration tests added (if applicable)
  • Documentation updated (if applicable)

@sagemaker-botsagemaker-bot left a comment

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.

🤖 AI Code Review

This PR fixes a real bug where DJL builder lacks HF cache redirection env vars (unlike TGI/TEI), causing read-only filesystem errors when source_code is provided. The fix is sound, but there are issues with duplicate env var setting, test style (unittest instead of pytest), and significant test code duplication that should be addressed.

"OPTION_MODEL_LOADING_TIMEOUT": "240",
"OPTION_PREDICT_TIMEOUT": "60",
"TENSOR_PARALLEL_DEGREE": "1" # Default, will be overridden below
"TENSOR_PARALLEL_DEGREE": "1", # Default, will be overridden below

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.

Potential duplicate/conflicting env var setting: HF_HOME and HUGGINGFACE_HUB_CACHE are set here (inside the not self._is_jumpstart_model_id() branch, line 348-349), and then again in the else branch at lines 375-376 (non-local mode). This means for non-local, non-JumpStart models, these values get set twice (which is harmless but redundant). However, for JumpStart models or local modes, the behavior differs:

  • Local mode: The if self.mode in LOCAL_MODES branch sets HF_HUB_OFFLINE but does NOT set HF_HOME/HUGGINGFACE_HUB_CACHE. If the model is not a JumpStart model, these were already set at line 348. But if it IS a JumpStart model, they won't be set at all in local mode. Is that intentional?
  • Non-local JumpStart models: They'll get the env vars from lines 375-376 but not from 348-349.

Consider consolidating the HF cache env var setting to a single location (e.g., always set them regardless of JumpStart status and mode) to make the logic clearer and avoid subtle gaps.

# Cache management based on mode
if self.mode in LOCAL_MODES:
self.env_vars.update({"HF_HUB_OFFLINE": "1"})
else:

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.

Consider using setdefault here too: For consistency with the HF_MODEL_ID change, consider using self.env_vars.setdefault("HF_HOME", "/tmp") and self.env_vars.setdefault("HUGGINGFACE_HUB_CACHE", "/tmp") so that if a user explicitly provides these env vars (e.g., pointing to a different writable directory), their values are preserved. The same applies to lines 348-349.

Comment threadsagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py
@patch('sagemaker.serve.model_builder_servers._get_nb_instance')
def test_build_for_djl_sets_hf_home_to_tmp(
self, mock_nb, mock_djl_config, mock_hf_config, mock_is_js,
mock_validate, mock_auto_detect, mock_prepare, mock_create,

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.

Massive test duplication: Nearly every test method has the same ~30 lines of mock setup and builder construction copied verbatim. Extract the common mock setup and builder creation into a pytest.fixture (or at minimum a helper method). This would reduce the file by ~60% and make it much easier to maintain. For example:

@pytest.fixturedefdjl_builder(mock_session, temp_dir):
# common mock patches and builder setup
...
returnbuilder

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Agreed! Only a few tests would suffice. We should strive for test quality over quantity!

@patch('sagemaker.serve.model_builder_servers._get_nb_instance')
def test_build_for_djl_sets_hf_home_to_tmp(
self, mock_nb, mock_djl_config, mock_hf_config, mock_is_js,
mock_validate, mock_auto_detect, mock_prepare, mock_create,

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.

Tests test_build_for_djl_sets_hf_home_to_tmp and test_build_for_djl_sets_huggingface_hub_cache_to_tmp are redundant with test_build_for_djl_with_source_code_and_hf_model_id: The last test already asserts both HF_HOME and HUGGINGFACE_HUB_CACHE. Consider consolidating these three tests into one that checks both env vars, following the "one logical assertion per test" guideline (checking two related env vars from the same operation is one logical assertion).


import unittest
from unittest.mock import Mock, patch, MagicMock
import tempfile

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.

Unused imports: MagicMock, os, and shutil are imported but MagicMock is never used. os and shutil are only used for temp dir cleanup which pytest's tmp_path fixture handles automatically. Clean up unused imports.

MOCK_ROLE_ARN = "arn:aws:iam::123456789012:role/SageMakerRole"
MOCK_IMAGE_URI = "763104351884.dkr.ecr.us-east-1.amazonaws.com/djl-inference:0.36.0-lmi22.0.0-cu129"
MOCK_HF_MODEL_CONFIG = {"model_type": "gpt2", "architectures": ["GPT2LMHeadModel"]}

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.

Hardcoded AWS account ID in mock: MOCK_ROLE_ARN contains 123456789012 and MOCK_IMAGE_URI contains a real ECR registry ID (763104351884). While these are mocks, using a clearly fake ECR URI (e.g., 000000000000.dkr.ecr.us-east-1.amazonaws.com/djl-inference:latest) would be more consistent with test standards that avoid real account/region references.

Comment threadsagemaker-serve/src/sagemaker/serve/model_builder_servers.py
@aviruthen

Copy link
Copy Markdown
CollaboratorAuthor

🤖 Iteration #1 — Review Comments Addressed

Description

Fix ModelBuilder with source_code + DJL LMI: /opt/ml/model becomes read-only, breaking HF Hub model downloads.

The issue has two root causes in _build_for_djl() in model_builder_servers.py:

  1. Missing HF cache redirection: Unlike _build_for_tgi() and _build_for_tei() which set HF_HOME=/tmp and HUGGINGFACE_HUB_CACHE=/tmp, the DJL builder never set these environment variables. When source_code is provided, the model artifacts (requirements.txt etc.) get packaged as model.tar.gz and mounted read-only at /opt/ml/model/. The DJL container then tries to download HF models to /opt/ml/model/ (the default cache location) and fails with EROFS.

  2. HF_MODEL_ID override: _build_for_djl() unconditionally called self.env_vars.update({'HF_MODEL_ID': self.model}), which overwrote any user-provided HF_MODEL_ID value. This prevented users from setting HF_MODEL_ID to a local path (e.g., /opt/ml/model) when they want to use pre-downloaded model artifacts.

Changes Made

  • sagemaker-serve/src/sagemaker/serve/model_builder_servers.py:

    • Added HF_HOME and HUGGINGFACE_HUB_CACHE env vars pointing to /tmp for the DJL builder, consolidated to a single location using setdefault() so user-provided values are preserved
    • Changed HF_MODEL_ID to use setdefault() so user-provided values are not overridden
  • sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py:

    • Added pytest-style tests verifying HF cache env vars, HF_MODEL_ID preservation, and local mode offline behavior
    • Uses fixtures and helper functions to minimize duplication

Comments reviewed: 9
Files modified:sagemaker-serve/src/sagemaker/serve/model_builder_servers.py, sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py

  • sagemaker-serve/src/sagemaker/serve/model_builder_servers.py: Fix DJL builder to use setdefault for HF_MODEL_ID (preserving user values), consolidate HF cache env vars to a single location using setdefault, and remove trailing whitespace
  • sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py: Rewrite tests using pytest style with fixtures, consolidating redundant tests and cleaning up imports

@jam-jeejam-jee left a comment

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.

LGTM !

@aviruthen
aviruthen marked this pull request as ready for review April 17, 2026 17:57
@aviruthen
aviruthen merged commit f20a7e2 into aws:masterApr 17, 2026
25 of 35 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@aviruthen@sagemaker-bot@nargokul@jam-jee
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

fix: ModelBuilder with source_code + DJL LMI: /opt/ml/model becomes read-only, breaki (5698) - #5733

Merged
aviruthen merged 2 commits into
aws:masterfrom
aviruthen:fix/modelbuilder-with-source-code-djl-lmi-opt-ml-5698
Apr 17, 2026
Merged

fix: ModelBuilder with source_code + DJL LMI: /opt/ml/model becomes read-only, breaki (5698)#5733
aviruthen merged 2 commits into
aws:masterfrom
aviruthen:fix/modelbuilder-with-source-code-djl-lmi-opt-ml-5698

Conversation

@aviruthen

Copy link
Copy Markdown
Collaborator

Description

The issue has two root causes in _build_for_djl() in model_builder_servers.py:

  1. Missing HF cache redirection: Unlike _build_for_tgi() and _build_for_tei() which set HF_HOME=/tmp and HUGGINGFACE_HUB_CACHE=/tmp, the DJL builder never sets these environment variables. When source_code is provided, the model artifacts (requirements.txt etc.) get packaged as model.tar.gz and mounted read-only at /opt/ml/model/. The DJL container then tries to download HF models to /opt/ml/model/ (the default cache location) and fails with EROFS.

  2. HF_MODEL_ID override: _build_for_djl() unconditionally calls self.env_vars.update({'HF_MODEL_ID': self.model}), which overwrites any user-provided HF_MODEL_ID value. This prevents users from setting HF_MODEL_ID to a local path (e.g., /opt/ml/model) when they want to use pre-downloaded model artifacts.

The fix adds HF cache environment variables (HF_HOME, HUGGINGFACE_HUB_CACHE) pointing to /tmp for the DJL builder (matching TGI/TEI behavior), and changes HF_MODEL_ID to use setdefault() so user-provided values are preserved.

Related Issue

Related issue: 5698

Changes Made

  • sagemaker-serve/src/sagemaker/serve/model_builder_servers.py
  • sagemaker-serve/tests/unit/servers/__init__.py
  • sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py

AI-Generated PR

This PR was automatically generated by the PySDK Issue Agent.

  • Confidence score: 85%
  • Classification: bug
  • SDK version target: V3

Merge Checklist

  • Changes are backward compatible
  • Commit message follows prefix: description format
  • Unit tests added/updated
  • Integration tests added (if applicable)
  • Documentation updated (if applicable)

@sagemaker-botsagemaker-bot left a comment

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.

🤖 AI Code Review

This PR fixes a real bug where DJL builder lacks HF cache redirection env vars (unlike TGI/TEI), causing read-only filesystem errors when source_code is provided. The fix is sound, but there are issues with duplicate env var setting, test style (unittest instead of pytest), and significant test code duplication that should be addressed.

"OPTION_MODEL_LOADING_TIMEOUT": "240",
"OPTION_PREDICT_TIMEOUT": "60",
"TENSOR_PARALLEL_DEGREE": "1" # Default, will be overridden below
"TENSOR_PARALLEL_DEGREE": "1", # Default, will be overridden below

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.

Potential duplicate/conflicting env var setting: HF_HOME and HUGGINGFACE_HUB_CACHE are set here (inside the not self._is_jumpstart_model_id() branch, line 348-349), and then again in the else branch at lines 375-376 (non-local mode). This means for non-local, non-JumpStart models, these values get set twice (which is harmless but redundant). However, for JumpStart models or local modes, the behavior differs:

  • Local mode: The if self.mode in LOCAL_MODES branch sets HF_HUB_OFFLINE but does NOT set HF_HOME/HUGGINGFACE_HUB_CACHE. If the model is not a JumpStart model, these were already set at line 348. But if it IS a JumpStart model, they won't be set at all in local mode. Is that intentional?
  • Non-local JumpStart models: They'll get the env vars from lines 375-376 but not from 348-349.

Consider consolidating the HF cache env var setting to a single location (e.g., always set them regardless of JumpStart status and mode) to make the logic clearer and avoid subtle gaps.

# Cache management based on mode
if self.mode in LOCAL_MODES:
self.env_vars.update({"HF_HUB_OFFLINE": "1"})
else:

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.

Consider using setdefault here too: For consistency with the HF_MODEL_ID change, consider using self.env_vars.setdefault("HF_HOME", "/tmp") and self.env_vars.setdefault("HUGGINGFACE_HUB_CACHE", "/tmp") so that if a user explicitly provides these env vars (e.g., pointing to a different writable directory), their values are preserved. The same applies to lines 348-349.

Comment threadsagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py
@patch('sagemaker.serve.model_builder_servers._get_nb_instance')
def test_build_for_djl_sets_hf_home_to_tmp(
self, mock_nb, mock_djl_config, mock_hf_config, mock_is_js,
mock_validate, mock_auto_detect, mock_prepare, mock_create,

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.

Massive test duplication: Nearly every test method has the same ~30 lines of mock setup and builder construction copied verbatim. Extract the common mock setup and builder creation into a pytest.fixture (or at minimum a helper method). This would reduce the file by ~60% and make it much easier to maintain. For example:

@pytest.fixturedefdjl_builder(mock_session, temp_dir):
# common mock patches and builder setup
...
returnbuilder

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Agreed! Only a few tests would suffice. We should strive for test quality over quantity!

@patch('sagemaker.serve.model_builder_servers._get_nb_instance')
def test_build_for_djl_sets_hf_home_to_tmp(
self, mock_nb, mock_djl_config, mock_hf_config, mock_is_js,
mock_validate, mock_auto_detect, mock_prepare, mock_create,

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.

Tests test_build_for_djl_sets_hf_home_to_tmp and test_build_for_djl_sets_huggingface_hub_cache_to_tmp are redundant with test_build_for_djl_with_source_code_and_hf_model_id: The last test already asserts both HF_HOME and HUGGINGFACE_HUB_CACHE. Consider consolidating these three tests into one that checks both env vars, following the "one logical assertion per test" guideline (checking two related env vars from the same operation is one logical assertion).


import unittest
from unittest.mock import Mock, patch, MagicMock
import tempfile

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.

Unused imports: MagicMock, os, and shutil are imported but MagicMock is never used. os and shutil are only used for temp dir cleanup which pytest's tmp_path fixture handles automatically. Clean up unused imports.

MOCK_ROLE_ARN = "arn:aws:iam::123456789012:role/SageMakerRole"
MOCK_IMAGE_URI = "763104351884.dkr.ecr.us-east-1.amazonaws.com/djl-inference:0.36.0-lmi22.0.0-cu129"
MOCK_HF_MODEL_CONFIG = {"model_type": "gpt2", "architectures": ["GPT2LMHeadModel"]}

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.

Hardcoded AWS account ID in mock: MOCK_ROLE_ARN contains 123456789012 and MOCK_IMAGE_URI contains a real ECR registry ID (763104351884). While these are mocks, using a clearly fake ECR URI (e.g., 000000000000.dkr.ecr.us-east-1.amazonaws.com/djl-inference:latest) would be more consistent with test standards that avoid real account/region references.

Comment threadsagemaker-serve/src/sagemaker/serve/model_builder_servers.py
@aviruthen

Copy link
Copy Markdown
CollaboratorAuthor

🤖 Iteration #1 — Review Comments Addressed

Description

Fix ModelBuilder with source_code + DJL LMI: /opt/ml/model becomes read-only, breaking HF Hub model downloads.

The issue has two root causes in _build_for_djl() in model_builder_servers.py:

  1. Missing HF cache redirection: Unlike _build_for_tgi() and _build_for_tei() which set HF_HOME=/tmp and HUGGINGFACE_HUB_CACHE=/tmp, the DJL builder never set these environment variables. When source_code is provided, the model artifacts (requirements.txt etc.) get packaged as model.tar.gz and mounted read-only at /opt/ml/model/. The DJL container then tries to download HF models to /opt/ml/model/ (the default cache location) and fails with EROFS.

  2. HF_MODEL_ID override: _build_for_djl() unconditionally called self.env_vars.update({'HF_MODEL_ID': self.model}), which overwrote any user-provided HF_MODEL_ID value. This prevented users from setting HF_MODEL_ID to a local path (e.g., /opt/ml/model) when they want to use pre-downloaded model artifacts.

Changes Made

  • sagemaker-serve/src/sagemaker/serve/model_builder_servers.py:

    • Added HF_HOME and HUGGINGFACE_HUB_CACHE env vars pointing to /tmp for the DJL builder, consolidated to a single location using setdefault() so user-provided values are preserved
    • Changed HF_MODEL_ID to use setdefault() so user-provided values are not overridden
  • sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py:

    • Added pytest-style tests verifying HF cache env vars, HF_MODEL_ID preservation, and local mode offline behavior
    • Uses fixtures and helper functions to minimize duplication

Comments reviewed: 9
Files modified:sagemaker-serve/src/sagemaker/serve/model_builder_servers.py, sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py

  • sagemaker-serve/src/sagemaker/serve/model_builder_servers.py: Fix DJL builder to use setdefault for HF_MODEL_ID (preserving user values), consolidate HF cache env vars to a single location using setdefault, and remove trailing whitespace
  • sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py: Rewrite tests using pytest style with fixtures, consolidating redundant tests and cleaning up imports

@jam-jeejam-jee left a comment

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.

LGTM !

@aviruthen
aviruthen marked this pull request as ready for review April 17, 2026 17:57
@aviruthen
aviruthen merged commit f20a7e2 into aws:masterApr 17, 2026
25 of 35 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@aviruthen@sagemaker-bot@nargokul@jam-jee
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: ModelBuilder with source_code + DJL LMI: /opt/ml/model becomes read-only, breaki (5698) - #5733

Merged
aviruthen merged 2 commits into
aws:masterfrom
aviruthen:fix/modelbuilder-with-source-code-djl-lmi-opt-ml-5698
Apr 17, 2026
Merged

fix: ModelBuilder with source_code + DJL LMI: /opt/ml/model becomes read-only, breaki (5698)#5733
aviruthen merged 2 commits into
aws:masterfrom
aviruthen:fix/modelbuilder-with-source-code-djl-lmi-opt-ml-5698

Conversation

@aviruthen

Copy link
Copy Markdown
Collaborator

Description

The issue has two root causes in _build_for_djl() in model_builder_servers.py:

  1. Missing HF cache redirection: Unlike _build_for_tgi() and _build_for_tei() which set HF_HOME=/tmp and HUGGINGFACE_HUB_CACHE=/tmp, the DJL builder never sets these environment variables. When source_code is provided, the model artifacts (requirements.txt etc.) get packaged as model.tar.gz and mounted read-only at /opt/ml/model/. The DJL container then tries to download HF models to /opt/ml/model/ (the default cache location) and fails with EROFS.

  2. HF_MODEL_ID override: _build_for_djl() unconditionally calls self.env_vars.update({'HF_MODEL_ID': self.model}), which overwrites any user-provided HF_MODEL_ID value. This prevents users from setting HF_MODEL_ID to a local path (e.g., /opt/ml/model) when they want to use pre-downloaded model artifacts.

The fix adds HF cache environment variables (HF_HOME, HUGGINGFACE_HUB_CACHE) pointing to /tmp for the DJL builder (matching TGI/TEI behavior), and changes HF_MODEL_ID to use setdefault() so user-provided values are preserved.

Related Issue

Related issue: 5698

Changes Made

  • sagemaker-serve/src/sagemaker/serve/model_builder_servers.py
  • sagemaker-serve/tests/unit/servers/__init__.py
  • sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py

AI-Generated PR

This PR was automatically generated by the PySDK Issue Agent.

  • Confidence score: 85%
  • Classification: bug
  • SDK version target: V3

Merge Checklist

  • Changes are backward compatible
  • Commit message follows prefix: description format
  • Unit tests added/updated
  • Integration tests added (if applicable)
  • Documentation updated (if applicable)

@sagemaker-botsagemaker-bot left a comment

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.

🤖 AI Code Review

This PR fixes a real bug where DJL builder lacks HF cache redirection env vars (unlike TGI/TEI), causing read-only filesystem errors when source_code is provided. The fix is sound, but there are issues with duplicate env var setting, test style (unittest instead of pytest), and significant test code duplication that should be addressed.

"OPTION_MODEL_LOADING_TIMEOUT": "240",
"OPTION_PREDICT_TIMEOUT": "60",
"TENSOR_PARALLEL_DEGREE": "1" # Default, will be overridden below
"TENSOR_PARALLEL_DEGREE": "1", # Default, will be overridden below

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.

Potential duplicate/conflicting env var setting: HF_HOME and HUGGINGFACE_HUB_CACHE are set here (inside the not self._is_jumpstart_model_id() branch, line 348-349), and then again in the else branch at lines 375-376 (non-local mode). This means for non-local, non-JumpStart models, these values get set twice (which is harmless but redundant). However, for JumpStart models or local modes, the behavior differs:

  • Local mode: The if self.mode in LOCAL_MODES branch sets HF_HUB_OFFLINE but does NOT set HF_HOME/HUGGINGFACE_HUB_CACHE. If the model is not a JumpStart model, these were already set at line 348. But if it IS a JumpStart model, they won't be set at all in local mode. Is that intentional?
  • Non-local JumpStart models: They'll get the env vars from lines 375-376 but not from 348-349.

Consider consolidating the HF cache env var setting to a single location (e.g., always set them regardless of JumpStart status and mode) to make the logic clearer and avoid subtle gaps.

# Cache management based on mode
if self.mode in LOCAL_MODES:
self.env_vars.update({"HF_HUB_OFFLINE": "1"})
else:

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.

Consider using setdefault here too: For consistency with the HF_MODEL_ID change, consider using self.env_vars.setdefault("HF_HOME", "/tmp") and self.env_vars.setdefault("HUGGINGFACE_HUB_CACHE", "/tmp") so that if a user explicitly provides these env vars (e.g., pointing to a different writable directory), their values are preserved. The same applies to lines 348-349.

Comment threadsagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py
@patch('sagemaker.serve.model_builder_servers._get_nb_instance')
def test_build_for_djl_sets_hf_home_to_tmp(
self, mock_nb, mock_djl_config, mock_hf_config, mock_is_js,
mock_validate, mock_auto_detect, mock_prepare, mock_create,

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.

Massive test duplication: Nearly every test method has the same ~30 lines of mock setup and builder construction copied verbatim. Extract the common mock setup and builder creation into a pytest.fixture (or at minimum a helper method). This would reduce the file by ~60% and make it much easier to maintain. For example:

@pytest.fixturedefdjl_builder(mock_session, temp_dir):
# common mock patches and builder setup
...
returnbuilder

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Agreed! Only a few tests would suffice. We should strive for test quality over quantity!

@patch('sagemaker.serve.model_builder_servers._get_nb_instance')
def test_build_for_djl_sets_hf_home_to_tmp(
self, mock_nb, mock_djl_config, mock_hf_config, mock_is_js,
mock_validate, mock_auto_detect, mock_prepare, mock_create,

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.

Tests test_build_for_djl_sets_hf_home_to_tmp and test_build_for_djl_sets_huggingface_hub_cache_to_tmp are redundant with test_build_for_djl_with_source_code_and_hf_model_id: The last test already asserts both HF_HOME and HUGGINGFACE_HUB_CACHE. Consider consolidating these three tests into one that checks both env vars, following the "one logical assertion per test" guideline (checking two related env vars from the same operation is one logical assertion).


import unittest
from unittest.mock import Mock, patch, MagicMock
import tempfile

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.

Unused imports: MagicMock, os, and shutil are imported but MagicMock is never used. os and shutil are only used for temp dir cleanup which pytest's tmp_path fixture handles automatically. Clean up unused imports.

MOCK_ROLE_ARN = "arn:aws:iam::123456789012:role/SageMakerRole"
MOCK_IMAGE_URI = "763104351884.dkr.ecr.us-east-1.amazonaws.com/djl-inference:0.36.0-lmi22.0.0-cu129"
MOCK_HF_MODEL_CONFIG = {"model_type": "gpt2", "architectures": ["GPT2LMHeadModel"]}

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.

Hardcoded AWS account ID in mock: MOCK_ROLE_ARN contains 123456789012 and MOCK_IMAGE_URI contains a real ECR registry ID (763104351884). While these are mocks, using a clearly fake ECR URI (e.g., 000000000000.dkr.ecr.us-east-1.amazonaws.com/djl-inference:latest) would be more consistent with test standards that avoid real account/region references.

Comment threadsagemaker-serve/src/sagemaker/serve/model_builder_servers.py
@aviruthen

Copy link
Copy Markdown
CollaboratorAuthor

🤖 Iteration #1 — Review Comments Addressed

Description

Fix ModelBuilder with source_code + DJL LMI: /opt/ml/model becomes read-only, breaking HF Hub model downloads.

The issue has two root causes in _build_for_djl() in model_builder_servers.py:

  1. Missing HF cache redirection: Unlike _build_for_tgi() and _build_for_tei() which set HF_HOME=/tmp and HUGGINGFACE_HUB_CACHE=/tmp, the DJL builder never set these environment variables. When source_code is provided, the model artifacts (requirements.txt etc.) get packaged as model.tar.gz and mounted read-only at /opt/ml/model/. The DJL container then tries to download HF models to /opt/ml/model/ (the default cache location) and fails with EROFS.

  2. HF_MODEL_ID override: _build_for_djl() unconditionally called self.env_vars.update({'HF_MODEL_ID': self.model}), which overwrote any user-provided HF_MODEL_ID value. This prevented users from setting HF_MODEL_ID to a local path (e.g., /opt/ml/model) when they want to use pre-downloaded model artifacts.

Changes Made

  • sagemaker-serve/src/sagemaker/serve/model_builder_servers.py:

    • Added HF_HOME and HUGGINGFACE_HUB_CACHE env vars pointing to /tmp for the DJL builder, consolidated to a single location using setdefault() so user-provided values are preserved
    • Changed HF_MODEL_ID to use setdefault() so user-provided values are not overridden
  • sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py:

    • Added pytest-style tests verifying HF cache env vars, HF_MODEL_ID preservation, and local mode offline behavior
    • Uses fixtures and helper functions to minimize duplication

Comments reviewed: 9
Files modified:sagemaker-serve/src/sagemaker/serve/model_builder_servers.py, sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py

  • sagemaker-serve/src/sagemaker/serve/model_builder_servers.py: Fix DJL builder to use setdefault for HF_MODEL_ID (preserving user values), consolidate HF cache env vars to a single location using setdefault, and remove trailing whitespace
  • sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py: Rewrite tests using pytest style with fixtures, consolidating redundant tests and cleaning up imports

@jam-jeejam-jee left a comment

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.

LGTM !

@aviruthen
aviruthen marked this pull request as ready for review April 17, 2026 17:57
@aviruthen
aviruthen merged commit f20a7e2 into aws:masterApr 17, 2026
25 of 35 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@aviruthen@sagemaker-bot@nargokul@jam-jee
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: ModelBuilder with source_code + DJL LMI: /opt/ml/model becomes read-only, breaki (5698) - #5733

Merged
aviruthen merged 2 commits into
aws:masterfrom
aviruthen:fix/modelbuilder-with-source-code-djl-lmi-opt-ml-5698
Apr 17, 2026
Merged

fix: ModelBuilder with source_code + DJL LMI: /opt/ml/model becomes read-only, breaki (5698)#5733
aviruthen merged 2 commits into
aws:masterfrom
aviruthen:fix/modelbuilder-with-source-code-djl-lmi-opt-ml-5698

Conversation

@aviruthen

Copy link
Copy Markdown
Collaborator

Description

The issue has two root causes in _build_for_djl() in model_builder_servers.py:

  1. Missing HF cache redirection: Unlike _build_for_tgi() and _build_for_tei() which set HF_HOME=/tmp and HUGGINGFACE_HUB_CACHE=/tmp, the DJL builder never sets these environment variables. When source_code is provided, the model artifacts (requirements.txt etc.) get packaged as model.tar.gz and mounted read-only at /opt/ml/model/. The DJL container then tries to download HF models to /opt/ml/model/ (the default cache location) and fails with EROFS.

  2. HF_MODEL_ID override: _build_for_djl() unconditionally calls self.env_vars.update({'HF_MODEL_ID': self.model}), which overwrites any user-provided HF_MODEL_ID value. This prevents users from setting HF_MODEL_ID to a local path (e.g., /opt/ml/model) when they want to use pre-downloaded model artifacts.

The fix adds HF cache environment variables (HF_HOME, HUGGINGFACE_HUB_CACHE) pointing to /tmp for the DJL builder (matching TGI/TEI behavior), and changes HF_MODEL_ID to use setdefault() so user-provided values are preserved.

Related Issue

Related issue: 5698

Changes Made

  • sagemaker-serve/src/sagemaker/serve/model_builder_servers.py
  • sagemaker-serve/tests/unit/servers/__init__.py
  • sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py

AI-Generated PR

This PR was automatically generated by the PySDK Issue Agent.

  • Confidence score: 85%
  • Classification: bug
  • SDK version target: V3

Merge Checklist

  • Changes are backward compatible
  • Commit message follows prefix: description format
  • Unit tests added/updated
  • Integration tests added (if applicable)
  • Documentation updated (if applicable)

@sagemaker-botsagemaker-bot left a comment

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.

🤖 AI Code Review

This PR fixes a real bug where DJL builder lacks HF cache redirection env vars (unlike TGI/TEI), causing read-only filesystem errors when source_code is provided. The fix is sound, but there are issues with duplicate env var setting, test style (unittest instead of pytest), and significant test code duplication that should be addressed.

"OPTION_MODEL_LOADING_TIMEOUT": "240",
"OPTION_PREDICT_TIMEOUT": "60",
"TENSOR_PARALLEL_DEGREE": "1" # Default, will be overridden below
"TENSOR_PARALLEL_DEGREE": "1", # Default, will be overridden below

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.

Potential duplicate/conflicting env var setting: HF_HOME and HUGGINGFACE_HUB_CACHE are set here (inside the not self._is_jumpstart_model_id() branch, line 348-349), and then again in the else branch at lines 375-376 (non-local mode). This means for non-local, non-JumpStart models, these values get set twice (which is harmless but redundant). However, for JumpStart models or local modes, the behavior differs:

  • Local mode: The if self.mode in LOCAL_MODES branch sets HF_HUB_OFFLINE but does NOT set HF_HOME/HUGGINGFACE_HUB_CACHE. If the model is not a JumpStart model, these were already set at line 348. But if it IS a JumpStart model, they won't be set at all in local mode. Is that intentional?
  • Non-local JumpStart models: They'll get the env vars from lines 375-376 but not from 348-349.

Consider consolidating the HF cache env var setting to a single location (e.g., always set them regardless of JumpStart status and mode) to make the logic clearer and avoid subtle gaps.

# Cache management based on mode
if self.mode in LOCAL_MODES:
self.env_vars.update({"HF_HUB_OFFLINE": "1"})
else:

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.

Consider using setdefault here too: For consistency with the HF_MODEL_ID change, consider using self.env_vars.setdefault("HF_HOME", "/tmp") and self.env_vars.setdefault("HUGGINGFACE_HUB_CACHE", "/tmp") so that if a user explicitly provides these env vars (e.g., pointing to a different writable directory), their values are preserved. The same applies to lines 348-349.

Comment threadsagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py
@patch('sagemaker.serve.model_builder_servers._get_nb_instance')
def test_build_for_djl_sets_hf_home_to_tmp(
self, mock_nb, mock_djl_config, mock_hf_config, mock_is_js,
mock_validate, mock_auto_detect, mock_prepare, mock_create,

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.

Massive test duplication: Nearly every test method has the same ~30 lines of mock setup and builder construction copied verbatim. Extract the common mock setup and builder creation into a pytest.fixture (or at minimum a helper method). This would reduce the file by ~60% and make it much easier to maintain. For example:

@pytest.fixturedefdjl_builder(mock_session, temp_dir):
# common mock patches and builder setup
...
returnbuilder

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Agreed! Only a few tests would suffice. We should strive for test quality over quantity!

@patch('sagemaker.serve.model_builder_servers._get_nb_instance')
def test_build_for_djl_sets_hf_home_to_tmp(
self, mock_nb, mock_djl_config, mock_hf_config, mock_is_js,
mock_validate, mock_auto_detect, mock_prepare, mock_create,

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.

Tests test_build_for_djl_sets_hf_home_to_tmp and test_build_for_djl_sets_huggingface_hub_cache_to_tmp are redundant with test_build_for_djl_with_source_code_and_hf_model_id: The last test already asserts both HF_HOME and HUGGINGFACE_HUB_CACHE. Consider consolidating these three tests into one that checks both env vars, following the "one logical assertion per test" guideline (checking two related env vars from the same operation is one logical assertion).


import unittest
from unittest.mock import Mock, patch, MagicMock
import tempfile

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.

Unused imports: MagicMock, os, and shutil are imported but MagicMock is never used. os and shutil are only used for temp dir cleanup which pytest's tmp_path fixture handles automatically. Clean up unused imports.

MOCK_ROLE_ARN = "arn:aws:iam::123456789012:role/SageMakerRole"
MOCK_IMAGE_URI = "763104351884.dkr.ecr.us-east-1.amazonaws.com/djl-inference:0.36.0-lmi22.0.0-cu129"
MOCK_HF_MODEL_CONFIG = {"model_type": "gpt2", "architectures": ["GPT2LMHeadModel"]}

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.

Hardcoded AWS account ID in mock: MOCK_ROLE_ARN contains 123456789012 and MOCK_IMAGE_URI contains a real ECR registry ID (763104351884). While these are mocks, using a clearly fake ECR URI (e.g., 000000000000.dkr.ecr.us-east-1.amazonaws.com/djl-inference:latest) would be more consistent with test standards that avoid real account/region references.

Comment threadsagemaker-serve/src/sagemaker/serve/model_builder_servers.py
@aviruthen

Copy link
Copy Markdown
CollaboratorAuthor

🤖 Iteration #1 — Review Comments Addressed

Description

Fix ModelBuilder with source_code + DJL LMI: /opt/ml/model becomes read-only, breaking HF Hub model downloads.

The issue has two root causes in _build_for_djl() in model_builder_servers.py:

  1. Missing HF cache redirection: Unlike _build_for_tgi() and _build_for_tei() which set HF_HOME=/tmp and HUGGINGFACE_HUB_CACHE=/tmp, the DJL builder never set these environment variables. When source_code is provided, the model artifacts (requirements.txt etc.) get packaged as model.tar.gz and mounted read-only at /opt/ml/model/. The DJL container then tries to download HF models to /opt/ml/model/ (the default cache location) and fails with EROFS.

  2. HF_MODEL_ID override: _build_for_djl() unconditionally called self.env_vars.update({'HF_MODEL_ID': self.model}), which overwrote any user-provided HF_MODEL_ID value. This prevented users from setting HF_MODEL_ID to a local path (e.g., /opt/ml/model) when they want to use pre-downloaded model artifacts.

Changes Made

  • sagemaker-serve/src/sagemaker/serve/model_builder_servers.py:

    • Added HF_HOME and HUGGINGFACE_HUB_CACHE env vars pointing to /tmp for the DJL builder, consolidated to a single location using setdefault() so user-provided values are preserved
    • Changed HF_MODEL_ID to use setdefault() so user-provided values are not overridden
  • sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py:

    • Added pytest-style tests verifying HF cache env vars, HF_MODEL_ID preservation, and local mode offline behavior
    • Uses fixtures and helper functions to minimize duplication

Comments reviewed: 9
Files modified:sagemaker-serve/src/sagemaker/serve/model_builder_servers.py, sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py

  • sagemaker-serve/src/sagemaker/serve/model_builder_servers.py: Fix DJL builder to use setdefault for HF_MODEL_ID (preserving user values), consolidate HF cache env vars to a single location using setdefault, and remove trailing whitespace
  • sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py: Rewrite tests using pytest style with fixtures, consolidating redundant tests and cleaning up imports

@jam-jeejam-jee left a comment

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.

LGTM !

@aviruthen
aviruthen marked this pull request as ready for review April 17, 2026 17:57
@aviruthen
aviruthen merged commit f20a7e2 into aws:masterApr 17, 2026
25 of 35 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@aviruthen@sagemaker-bot@nargokul@jam-jee
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

fix: ModelBuilder with source_code + DJL LMI: /opt/ml/model becomes read-only, breaki (5698) - #5733

Merged
aviruthen merged 2 commits into
aws:masterfrom
aviruthen:fix/modelbuilder-with-source-code-djl-lmi-opt-ml-5698
Apr 17, 2026
Merged

fix: ModelBuilder with source_code + DJL LMI: /opt/ml/model becomes read-only, breaki (5698)#5733
aviruthen merged 2 commits into
aws:masterfrom
aviruthen:fix/modelbuilder-with-source-code-djl-lmi-opt-ml-5698

Conversation

@aviruthen

Copy link
Copy Markdown
Collaborator

Description

The issue has two root causes in _build_for_djl() in model_builder_servers.py:

  1. Missing HF cache redirection: Unlike _build_for_tgi() and _build_for_tei() which set HF_HOME=/tmp and HUGGINGFACE_HUB_CACHE=/tmp, the DJL builder never sets these environment variables. When source_code is provided, the model artifacts (requirements.txt etc.) get packaged as model.tar.gz and mounted read-only at /opt/ml/model/. The DJL container then tries to download HF models to /opt/ml/model/ (the default cache location) and fails with EROFS.

  2. HF_MODEL_ID override: _build_for_djl() unconditionally calls self.env_vars.update({'HF_MODEL_ID': self.model}), which overwrites any user-provided HF_MODEL_ID value. This prevents users from setting HF_MODEL_ID to a local path (e.g., /opt/ml/model) when they want to use pre-downloaded model artifacts.

The fix adds HF cache environment variables (HF_HOME, HUGGINGFACE_HUB_CACHE) pointing to /tmp for the DJL builder (matching TGI/TEI behavior), and changes HF_MODEL_ID to use setdefault() so user-provided values are preserved.

Related Issue

Related issue: 5698

Changes Made

  • sagemaker-serve/src/sagemaker/serve/model_builder_servers.py
  • sagemaker-serve/tests/unit/servers/__init__.py
  • sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py

AI-Generated PR

This PR was automatically generated by the PySDK Issue Agent.

  • Confidence score: 85%
  • Classification: bug
  • SDK version target: V3

Merge Checklist

  • Changes are backward compatible
  • Commit message follows prefix: description format
  • Unit tests added/updated
  • Integration tests added (if applicable)
  • Documentation updated (if applicable)

@sagemaker-botsagemaker-bot left a comment

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.

🤖 AI Code Review

This PR fixes a real bug where DJL builder lacks HF cache redirection env vars (unlike TGI/TEI), causing read-only filesystem errors when source_code is provided. The fix is sound, but there are issues with duplicate env var setting, test style (unittest instead of pytest), and significant test code duplication that should be addressed.

"OPTION_MODEL_LOADING_TIMEOUT": "240",
"OPTION_PREDICT_TIMEOUT": "60",
"TENSOR_PARALLEL_DEGREE": "1" # Default, will be overridden below
"TENSOR_PARALLEL_DEGREE": "1", # Default, will be overridden below

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.

Potential duplicate/conflicting env var setting: HF_HOME and HUGGINGFACE_HUB_CACHE are set here (inside the not self._is_jumpstart_model_id() branch, line 348-349), and then again in the else branch at lines 375-376 (non-local mode). This means for non-local, non-JumpStart models, these values get set twice (which is harmless but redundant). However, for JumpStart models or local modes, the behavior differs:

  • Local mode: The if self.mode in LOCAL_MODES branch sets HF_HUB_OFFLINE but does NOT set HF_HOME/HUGGINGFACE_HUB_CACHE. If the model is not a JumpStart model, these were already set at line 348. But if it IS a JumpStart model, they won't be set at all in local mode. Is that intentional?
  • Non-local JumpStart models: They'll get the env vars from lines 375-376 but not from 348-349.

Consider consolidating the HF cache env var setting to a single location (e.g., always set them regardless of JumpStart status and mode) to make the logic clearer and avoid subtle gaps.

# Cache management based on mode
if self.mode in LOCAL_MODES:
self.env_vars.update({"HF_HUB_OFFLINE": "1"})
else:

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.

Consider using setdefault here too: For consistency with the HF_MODEL_ID change, consider using self.env_vars.setdefault("HF_HOME", "/tmp") and self.env_vars.setdefault("HUGGINGFACE_HUB_CACHE", "/tmp") so that if a user explicitly provides these env vars (e.g., pointing to a different writable directory), their values are preserved. The same applies to lines 348-349.

Comment threadsagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py
@patch('sagemaker.serve.model_builder_servers._get_nb_instance')
def test_build_for_djl_sets_hf_home_to_tmp(
self, mock_nb, mock_djl_config, mock_hf_config, mock_is_js,
mock_validate, mock_auto_detect, mock_prepare, mock_create,

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.

Massive test duplication: Nearly every test method has the same ~30 lines of mock setup and builder construction copied verbatim. Extract the common mock setup and builder creation into a pytest.fixture (or at minimum a helper method). This would reduce the file by ~60% and make it much easier to maintain. For example:

@pytest.fixturedefdjl_builder(mock_session, temp_dir):
# common mock patches and builder setup
...
returnbuilder

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Agreed! Only a few tests would suffice. We should strive for test quality over quantity!

@patch('sagemaker.serve.model_builder_servers._get_nb_instance')
def test_build_for_djl_sets_hf_home_to_tmp(
self, mock_nb, mock_djl_config, mock_hf_config, mock_is_js,
mock_validate, mock_auto_detect, mock_prepare, mock_create,

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.

Tests test_build_for_djl_sets_hf_home_to_tmp and test_build_for_djl_sets_huggingface_hub_cache_to_tmp are redundant with test_build_for_djl_with_source_code_and_hf_model_id: The last test already asserts both HF_HOME and HUGGINGFACE_HUB_CACHE. Consider consolidating these three tests into one that checks both env vars, following the "one logical assertion per test" guideline (checking two related env vars from the same operation is one logical assertion).


import unittest
from unittest.mock import Mock, patch, MagicMock
import tempfile

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.

Unused imports: MagicMock, os, and shutil are imported but MagicMock is never used. os and shutil are only used for temp dir cleanup which pytest's tmp_path fixture handles automatically. Clean up unused imports.

MOCK_ROLE_ARN = "arn:aws:iam::123456789012:role/SageMakerRole"
MOCK_IMAGE_URI = "763104351884.dkr.ecr.us-east-1.amazonaws.com/djl-inference:0.36.0-lmi22.0.0-cu129"
MOCK_HF_MODEL_CONFIG = {"model_type": "gpt2", "architectures": ["GPT2LMHeadModel"]}

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.

Hardcoded AWS account ID in mock: MOCK_ROLE_ARN contains 123456789012 and MOCK_IMAGE_URI contains a real ECR registry ID (763104351884). While these are mocks, using a clearly fake ECR URI (e.g., 000000000000.dkr.ecr.us-east-1.amazonaws.com/djl-inference:latest) would be more consistent with test standards that avoid real account/region references.

Comment threadsagemaker-serve/src/sagemaker/serve/model_builder_servers.py
@aviruthen

Copy link
Copy Markdown
CollaboratorAuthor

🤖 Iteration #1 — Review Comments Addressed

Description

Fix ModelBuilder with source_code + DJL LMI: /opt/ml/model becomes read-only, breaking HF Hub model downloads.

The issue has two root causes in _build_for_djl() in model_builder_servers.py:

  1. Missing HF cache redirection: Unlike _build_for_tgi() and _build_for_tei() which set HF_HOME=/tmp and HUGGINGFACE_HUB_CACHE=/tmp, the DJL builder never set these environment variables. When source_code is provided, the model artifacts (requirements.txt etc.) get packaged as model.tar.gz and mounted read-only at /opt/ml/model/. The DJL container then tries to download HF models to /opt/ml/model/ (the default cache location) and fails with EROFS.

  2. HF_MODEL_ID override: _build_for_djl() unconditionally called self.env_vars.update({'HF_MODEL_ID': self.model}), which overwrote any user-provided HF_MODEL_ID value. This prevented users from setting HF_MODEL_ID to a local path (e.g., /opt/ml/model) when they want to use pre-downloaded model artifacts.

Changes Made

  • sagemaker-serve/src/sagemaker/serve/model_builder_servers.py:

    • Added HF_HOME and HUGGINGFACE_HUB_CACHE env vars pointing to /tmp for the DJL builder, consolidated to a single location using setdefault() so user-provided values are preserved
    • Changed HF_MODEL_ID to use setdefault() so user-provided values are not overridden
  • sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py:

    • Added pytest-style tests verifying HF cache env vars, HF_MODEL_ID preservation, and local mode offline behavior
    • Uses fixtures and helper functions to minimize duplication

Comments reviewed: 9
Files modified:sagemaker-serve/src/sagemaker/serve/model_builder_servers.py, sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py

  • sagemaker-serve/src/sagemaker/serve/model_builder_servers.py: Fix DJL builder to use setdefault for HF_MODEL_ID (preserving user values), consolidate HF cache env vars to a single location using setdefault, and remove trailing whitespace
  • sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py: Rewrite tests using pytest style with fixtures, consolidating redundant tests and cleaning up imports

@jam-jeejam-jee left a comment

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.

LGTM !

@aviruthen
aviruthen marked this pull request as ready for review April 17, 2026 17:57
@aviruthen
aviruthen merged commit f20a7e2 into aws:masterApr 17, 2026
25 of 35 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@aviruthen@sagemaker-bot@nargokul@jam-jee
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: ModelBuilder with source_code + DJL LMI: /opt/ml/model becomes read-only, breaki (5698) - #5733

Merged
aviruthen merged 2 commits into
aws:masterfrom
aviruthen:fix/modelbuilder-with-source-code-djl-lmi-opt-ml-5698
Apr 17, 2026
Merged

fix: ModelBuilder with source_code + DJL LMI: /opt/ml/model becomes read-only, breaki (5698)#5733
aviruthen merged 2 commits into
aws:masterfrom
aviruthen:fix/modelbuilder-with-source-code-djl-lmi-opt-ml-5698

Conversation

@aviruthen

Copy link
Copy Markdown
Collaborator

Description

The issue has two root causes in _build_for_djl() in model_builder_servers.py:

  1. Missing HF cache redirection: Unlike _build_for_tgi() and _build_for_tei() which set HF_HOME=/tmp and HUGGINGFACE_HUB_CACHE=/tmp, the DJL builder never sets these environment variables. When source_code is provided, the model artifacts (requirements.txt etc.) get packaged as model.tar.gz and mounted read-only at /opt/ml/model/. The DJL container then tries to download HF models to /opt/ml/model/ (the default cache location) and fails with EROFS.

  2. HF_MODEL_ID override: _build_for_djl() unconditionally calls self.env_vars.update({'HF_MODEL_ID': self.model}), which overwrites any user-provided HF_MODEL_ID value. This prevents users from setting HF_MODEL_ID to a local path (e.g., /opt/ml/model) when they want to use pre-downloaded model artifacts.

The fix adds HF cache environment variables (HF_HOME, HUGGINGFACE_HUB_CACHE) pointing to /tmp for the DJL builder (matching TGI/TEI behavior), and changes HF_MODEL_ID to use setdefault() so user-provided values are preserved.

Related Issue

Related issue: 5698

Changes Made

  • sagemaker-serve/src/sagemaker/serve/model_builder_servers.py
  • sagemaker-serve/tests/unit/servers/__init__.py
  • sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py

AI-Generated PR

This PR was automatically generated by the PySDK Issue Agent.

  • Confidence score: 85%
  • Classification: bug
  • SDK version target: V3

Merge Checklist

  • Changes are backward compatible
  • Commit message follows prefix: description format
  • Unit tests added/updated
  • Integration tests added (if applicable)
  • Documentation updated (if applicable)

@sagemaker-botsagemaker-bot left a comment

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.

🤖 AI Code Review

This PR fixes a real bug where DJL builder lacks HF cache redirection env vars (unlike TGI/TEI), causing read-only filesystem errors when source_code is provided. The fix is sound, but there are issues with duplicate env var setting, test style (unittest instead of pytest), and significant test code duplication that should be addressed.

"OPTION_MODEL_LOADING_TIMEOUT": "240",
"OPTION_PREDICT_TIMEOUT": "60",
"TENSOR_PARALLEL_DEGREE": "1" # Default, will be overridden below
"TENSOR_PARALLEL_DEGREE": "1", # Default, will be overridden below

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.

Potential duplicate/conflicting env var setting: HF_HOME and HUGGINGFACE_HUB_CACHE are set here (inside the not self._is_jumpstart_model_id() branch, line 348-349), and then again in the else branch at lines 375-376 (non-local mode). This means for non-local, non-JumpStart models, these values get set twice (which is harmless but redundant). However, for JumpStart models or local modes, the behavior differs:

  • Local mode: The if self.mode in LOCAL_MODES branch sets HF_HUB_OFFLINE but does NOT set HF_HOME/HUGGINGFACE_HUB_CACHE. If the model is not a JumpStart model, these were already set at line 348. But if it IS a JumpStart model, they won't be set at all in local mode. Is that intentional?
  • Non-local JumpStart models: They'll get the env vars from lines 375-376 but not from 348-349.

Consider consolidating the HF cache env var setting to a single location (e.g., always set them regardless of JumpStart status and mode) to make the logic clearer and avoid subtle gaps.

# Cache management based on mode
if self.mode in LOCAL_MODES:
self.env_vars.update({"HF_HUB_OFFLINE": "1"})
else:

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.

Consider using setdefault here too: For consistency with the HF_MODEL_ID change, consider using self.env_vars.setdefault("HF_HOME", "/tmp") and self.env_vars.setdefault("HUGGINGFACE_HUB_CACHE", "/tmp") so that if a user explicitly provides these env vars (e.g., pointing to a different writable directory), their values are preserved. The same applies to lines 348-349.

Comment threadsagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py
@patch('sagemaker.serve.model_builder_servers._get_nb_instance')
def test_build_for_djl_sets_hf_home_to_tmp(
self, mock_nb, mock_djl_config, mock_hf_config, mock_is_js,
mock_validate, mock_auto_detect, mock_prepare, mock_create,

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.

Massive test duplication: Nearly every test method has the same ~30 lines of mock setup and builder construction copied verbatim. Extract the common mock setup and builder creation into a pytest.fixture (or at minimum a helper method). This would reduce the file by ~60% and make it much easier to maintain. For example:

@pytest.fixturedefdjl_builder(mock_session, temp_dir):
# common mock patches and builder setup
...
returnbuilder

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Agreed! Only a few tests would suffice. We should strive for test quality over quantity!

@patch('sagemaker.serve.model_builder_servers._get_nb_instance')
def test_build_for_djl_sets_hf_home_to_tmp(
self, mock_nb, mock_djl_config, mock_hf_config, mock_is_js,
mock_validate, mock_auto_detect, mock_prepare, mock_create,

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.

Tests test_build_for_djl_sets_hf_home_to_tmp and test_build_for_djl_sets_huggingface_hub_cache_to_tmp are redundant with test_build_for_djl_with_source_code_and_hf_model_id: The last test already asserts both HF_HOME and HUGGINGFACE_HUB_CACHE. Consider consolidating these three tests into one that checks both env vars, following the "one logical assertion per test" guideline (checking two related env vars from the same operation is one logical assertion).


import unittest
from unittest.mock import Mock, patch, MagicMock
import tempfile

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.

Unused imports: MagicMock, os, and shutil are imported but MagicMock is never used. os and shutil are only used for temp dir cleanup which pytest's tmp_path fixture handles automatically. Clean up unused imports.

MOCK_ROLE_ARN = "arn:aws:iam::123456789012:role/SageMakerRole"
MOCK_IMAGE_URI = "763104351884.dkr.ecr.us-east-1.amazonaws.com/djl-inference:0.36.0-lmi22.0.0-cu129"
MOCK_HF_MODEL_CONFIG = {"model_type": "gpt2", "architectures": ["GPT2LMHeadModel"]}

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.

Hardcoded AWS account ID in mock: MOCK_ROLE_ARN contains 123456789012 and MOCK_IMAGE_URI contains a real ECR registry ID (763104351884). While these are mocks, using a clearly fake ECR URI (e.g., 000000000000.dkr.ecr.us-east-1.amazonaws.com/djl-inference:latest) would be more consistent with test standards that avoid real account/region references.

Comment threadsagemaker-serve/src/sagemaker/serve/model_builder_servers.py
@aviruthen

Copy link
Copy Markdown
CollaboratorAuthor

🤖 Iteration #1 — Review Comments Addressed

Description

Fix ModelBuilder with source_code + DJL LMI: /opt/ml/model becomes read-only, breaking HF Hub model downloads.

The issue has two root causes in _build_for_djl() in model_builder_servers.py:

  1. Missing HF cache redirection: Unlike _build_for_tgi() and _build_for_tei() which set HF_HOME=/tmp and HUGGINGFACE_HUB_CACHE=/tmp, the DJL builder never set these environment variables. When source_code is provided, the model artifacts (requirements.txt etc.) get packaged as model.tar.gz and mounted read-only at /opt/ml/model/. The DJL container then tries to download HF models to /opt/ml/model/ (the default cache location) and fails with EROFS.

  2. HF_MODEL_ID override: _build_for_djl() unconditionally called self.env_vars.update({'HF_MODEL_ID': self.model}), which overwrote any user-provided HF_MODEL_ID value. This prevented users from setting HF_MODEL_ID to a local path (e.g., /opt/ml/model) when they want to use pre-downloaded model artifacts.

Changes Made

  • sagemaker-serve/src/sagemaker/serve/model_builder_servers.py:

    • Added HF_HOME and HUGGINGFACE_HUB_CACHE env vars pointing to /tmp for the DJL builder, consolidated to a single location using setdefault() so user-provided values are preserved
    • Changed HF_MODEL_ID to use setdefault() so user-provided values are not overridden
  • sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py:

    • Added pytest-style tests verifying HF cache env vars, HF_MODEL_ID preservation, and local mode offline behavior
    • Uses fixtures and helper functions to minimize duplication

Comments reviewed: 9
Files modified:sagemaker-serve/src/sagemaker/serve/model_builder_servers.py, sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py

  • sagemaker-serve/src/sagemaker/serve/model_builder_servers.py: Fix DJL builder to use setdefault for HF_MODEL_ID (preserving user values), consolidate HF cache env vars to a single location using setdefault, and remove trailing whitespace
  • sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py: Rewrite tests using pytest style with fixtures, consolidating redundant tests and cleaning up imports

@jam-jeejam-jee left a comment

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.

LGTM !

@aviruthen
aviruthen marked this pull request as ready for review April 17, 2026 17:57
@aviruthen
aviruthen merged commit f20a7e2 into aws:masterApr 17, 2026
25 of 35 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@aviruthen@sagemaker-bot@nargokul@jam-jee
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

fix: ModelBuilder with source_code + DJL LMI: /opt/ml/model becomes read-only, breaki (5698) - #5733

Merged
aviruthen merged 2 commits into
aws:masterfrom
aviruthen:fix/modelbuilder-with-source-code-djl-lmi-opt-ml-5698
Apr 17, 2026
Merged

fix: ModelBuilder with source_code + DJL LMI: /opt/ml/model becomes read-only, breaki (5698)#5733
aviruthen merged 2 commits into
aws:masterfrom
aviruthen:fix/modelbuilder-with-source-code-djl-lmi-opt-ml-5698

Conversation

@aviruthen

Copy link
Copy Markdown
Collaborator

Description

The issue has two root causes in _build_for_djl() in model_builder_servers.py:

  1. Missing HF cache redirection: Unlike _build_for_tgi() and _build_for_tei() which set HF_HOME=/tmp and HUGGINGFACE_HUB_CACHE=/tmp, the DJL builder never sets these environment variables. When source_code is provided, the model artifacts (requirements.txt etc.) get packaged as model.tar.gz and mounted read-only at /opt/ml/model/. The DJL container then tries to download HF models to /opt/ml/model/ (the default cache location) and fails with EROFS.

  2. HF_MODEL_ID override: _build_for_djl() unconditionally calls self.env_vars.update({'HF_MODEL_ID': self.model}), which overwrites any user-provided HF_MODEL_ID value. This prevents users from setting HF_MODEL_ID to a local path (e.g., /opt/ml/model) when they want to use pre-downloaded model artifacts.

The fix adds HF cache environment variables (HF_HOME, HUGGINGFACE_HUB_CACHE) pointing to /tmp for the DJL builder (matching TGI/TEI behavior), and changes HF_MODEL_ID to use setdefault() so user-provided values are preserved.

Related Issue

Related issue: 5698

Changes Made

  • sagemaker-serve/src/sagemaker/serve/model_builder_servers.py
  • sagemaker-serve/tests/unit/servers/__init__.py
  • sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py

AI-Generated PR

This PR was automatically generated by the PySDK Issue Agent.

  • Confidence score: 85%
  • Classification: bug
  • SDK version target: V3

Merge Checklist

  • Changes are backward compatible
  • Commit message follows prefix: description format
  • Unit tests added/updated
  • Integration tests added (if applicable)
  • Documentation updated (if applicable)

@sagemaker-botsagemaker-bot left a comment

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.

🤖 AI Code Review

This PR fixes a real bug where DJL builder lacks HF cache redirection env vars (unlike TGI/TEI), causing read-only filesystem errors when source_code is provided. The fix is sound, but there are issues with duplicate env var setting, test style (unittest instead of pytest), and significant test code duplication that should be addressed.

"OPTION_MODEL_LOADING_TIMEOUT": "240",
"OPTION_PREDICT_TIMEOUT": "60",
"TENSOR_PARALLEL_DEGREE": "1" # Default, will be overridden below
"TENSOR_PARALLEL_DEGREE": "1", # Default, will be overridden below

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.

Potential duplicate/conflicting env var setting: HF_HOME and HUGGINGFACE_HUB_CACHE are set here (inside the not self._is_jumpstart_model_id() branch, line 348-349), and then again in the else branch at lines 375-376 (non-local mode). This means for non-local, non-JumpStart models, these values get set twice (which is harmless but redundant). However, for JumpStart models or local modes, the behavior differs:

  • Local mode: The if self.mode in LOCAL_MODES branch sets HF_HUB_OFFLINE but does NOT set HF_HOME/HUGGINGFACE_HUB_CACHE. If the model is not a JumpStart model, these were already set at line 348. But if it IS a JumpStart model, they won't be set at all in local mode. Is that intentional?
  • Non-local JumpStart models: They'll get the env vars from lines 375-376 but not from 348-349.

Consider consolidating the HF cache env var setting to a single location (e.g., always set them regardless of JumpStart status and mode) to make the logic clearer and avoid subtle gaps.

# Cache management based on mode
if self.mode in LOCAL_MODES:
self.env_vars.update({"HF_HUB_OFFLINE": "1"})
else:

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.

Consider using setdefault here too: For consistency with the HF_MODEL_ID change, consider using self.env_vars.setdefault("HF_HOME", "/tmp") and self.env_vars.setdefault("HUGGINGFACE_HUB_CACHE", "/tmp") so that if a user explicitly provides these env vars (e.g., pointing to a different writable directory), their values are preserved. The same applies to lines 348-349.

Comment threadsagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py
@patch('sagemaker.serve.model_builder_servers._get_nb_instance')
def test_build_for_djl_sets_hf_home_to_tmp(
self, mock_nb, mock_djl_config, mock_hf_config, mock_is_js,
mock_validate, mock_auto_detect, mock_prepare, mock_create,

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.

Massive test duplication: Nearly every test method has the same ~30 lines of mock setup and builder construction copied verbatim. Extract the common mock setup and builder creation into a pytest.fixture (or at minimum a helper method). This would reduce the file by ~60% and make it much easier to maintain. For example:

@pytest.fixturedefdjl_builder(mock_session, temp_dir):
# common mock patches and builder setup
...
returnbuilder

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Agreed! Only a few tests would suffice. We should strive for test quality over quantity!

@patch('sagemaker.serve.model_builder_servers._get_nb_instance')
def test_build_for_djl_sets_hf_home_to_tmp(
self, mock_nb, mock_djl_config, mock_hf_config, mock_is_js,
mock_validate, mock_auto_detect, mock_prepare, mock_create,

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.

Tests test_build_for_djl_sets_hf_home_to_tmp and test_build_for_djl_sets_huggingface_hub_cache_to_tmp are redundant with test_build_for_djl_with_source_code_and_hf_model_id: The last test already asserts both HF_HOME and HUGGINGFACE_HUB_CACHE. Consider consolidating these three tests into one that checks both env vars, following the "one logical assertion per test" guideline (checking two related env vars from the same operation is one logical assertion).


import unittest
from unittest.mock import Mock, patch, MagicMock
import tempfile

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.

Unused imports: MagicMock, os, and shutil are imported but MagicMock is never used. os and shutil are only used for temp dir cleanup which pytest's tmp_path fixture handles automatically. Clean up unused imports.

MOCK_ROLE_ARN = "arn:aws:iam::123456789012:role/SageMakerRole"
MOCK_IMAGE_URI = "763104351884.dkr.ecr.us-east-1.amazonaws.com/djl-inference:0.36.0-lmi22.0.0-cu129"
MOCK_HF_MODEL_CONFIG = {"model_type": "gpt2", "architectures": ["GPT2LMHeadModel"]}

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.

Hardcoded AWS account ID in mock: MOCK_ROLE_ARN contains 123456789012 and MOCK_IMAGE_URI contains a real ECR registry ID (763104351884). While these are mocks, using a clearly fake ECR URI (e.g., 000000000000.dkr.ecr.us-east-1.amazonaws.com/djl-inference:latest) would be more consistent with test standards that avoid real account/region references.

Comment threadsagemaker-serve/src/sagemaker/serve/model_builder_servers.py
@aviruthen

Copy link
Copy Markdown
CollaboratorAuthor

🤖 Iteration #1 — Review Comments Addressed

Description

Fix ModelBuilder with source_code + DJL LMI: /opt/ml/model becomes read-only, breaking HF Hub model downloads.

The issue has two root causes in _build_for_djl() in model_builder_servers.py:

  1. Missing HF cache redirection: Unlike _build_for_tgi() and _build_for_tei() which set HF_HOME=/tmp and HUGGINGFACE_HUB_CACHE=/tmp, the DJL builder never set these environment variables. When source_code is provided, the model artifacts (requirements.txt etc.) get packaged as model.tar.gz and mounted read-only at /opt/ml/model/. The DJL container then tries to download HF models to /opt/ml/model/ (the default cache location) and fails with EROFS.

  2. HF_MODEL_ID override: _build_for_djl() unconditionally called self.env_vars.update({'HF_MODEL_ID': self.model}), which overwrote any user-provided HF_MODEL_ID value. This prevented users from setting HF_MODEL_ID to a local path (e.g., /opt/ml/model) when they want to use pre-downloaded model artifacts.

Changes Made

  • sagemaker-serve/src/sagemaker/serve/model_builder_servers.py:

    • Added HF_HOME and HUGGINGFACE_HUB_CACHE env vars pointing to /tmp for the DJL builder, consolidated to a single location using setdefault() so user-provided values are preserved
    • Changed HF_MODEL_ID to use setdefault() so user-provided values are not overridden
  • sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py:

    • Added pytest-style tests verifying HF cache env vars, HF_MODEL_ID preservation, and local mode offline behavior
    • Uses fixtures and helper functions to minimize duplication

Comments reviewed: 9
Files modified:sagemaker-serve/src/sagemaker/serve/model_builder_servers.py, sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py

  • sagemaker-serve/src/sagemaker/serve/model_builder_servers.py: Fix DJL builder to use setdefault for HF_MODEL_ID (preserving user values), consolidate HF cache env vars to a single location using setdefault, and remove trailing whitespace
  • sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py: Rewrite tests using pytest style with fixtures, consolidating redundant tests and cleaning up imports

@jam-jeejam-jee left a comment

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.

LGTM !

@aviruthen
aviruthen marked this pull request as ready for review April 17, 2026 17:57
@aviruthen
aviruthen merged commit f20a7e2 into aws:masterApr 17, 2026
25 of 35 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@aviruthen@sagemaker-bot@nargokul@jam-jee
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

fix: ModelBuilder with source_code + DJL LMI: /opt/ml/model becomes read-only, breaki (5698) - #5733

Merged
aviruthen merged 2 commits into
aws:masterfrom
aviruthen:fix/modelbuilder-with-source-code-djl-lmi-opt-ml-5698
Apr 17, 2026
Merged

fix: ModelBuilder with source_code + DJL LMI: /opt/ml/model becomes read-only, breaki (5698)#5733
aviruthen merged 2 commits into
aws:masterfrom
aviruthen:fix/modelbuilder-with-source-code-djl-lmi-opt-ml-5698

Conversation

@aviruthen

Copy link
Copy Markdown
Collaborator

Description

The issue has two root causes in _build_for_djl() in model_builder_servers.py:

  1. Missing HF cache redirection: Unlike _build_for_tgi() and _build_for_tei() which set HF_HOME=/tmp and HUGGINGFACE_HUB_CACHE=/tmp, the DJL builder never sets these environment variables. When source_code is provided, the model artifacts (requirements.txt etc.) get packaged as model.tar.gz and mounted read-only at /opt/ml/model/. The DJL container then tries to download HF models to /opt/ml/model/ (the default cache location) and fails with EROFS.

  2. HF_MODEL_ID override: _build_for_djl() unconditionally calls self.env_vars.update({'HF_MODEL_ID': self.model}), which overwrites any user-provided HF_MODEL_ID value. This prevents users from setting HF_MODEL_ID to a local path (e.g., /opt/ml/model) when they want to use pre-downloaded model artifacts.

The fix adds HF cache environment variables (HF_HOME, HUGGINGFACE_HUB_CACHE) pointing to /tmp for the DJL builder (matching TGI/TEI behavior), and changes HF_MODEL_ID to use setdefault() so user-provided values are preserved.

Related Issue

Related issue: 5698

Changes Made

  • sagemaker-serve/src/sagemaker/serve/model_builder_servers.py
  • sagemaker-serve/tests/unit/servers/__init__.py
  • sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py

AI-Generated PR

This PR was automatically generated by the PySDK Issue Agent.

  • Confidence score: 85%
  • Classification: bug
  • SDK version target: V3

Merge Checklist

  • Changes are backward compatible
  • Commit message follows prefix: description format
  • Unit tests added/updated
  • Integration tests added (if applicable)
  • Documentation updated (if applicable)

@sagemaker-botsagemaker-bot left a comment

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.

🤖 AI Code Review

This PR fixes a real bug where DJL builder lacks HF cache redirection env vars (unlike TGI/TEI), causing read-only filesystem errors when source_code is provided. The fix is sound, but there are issues with duplicate env var setting, test style (unittest instead of pytest), and significant test code duplication that should be addressed.

"OPTION_MODEL_LOADING_TIMEOUT": "240",
"OPTION_PREDICT_TIMEOUT": "60",
"TENSOR_PARALLEL_DEGREE": "1" # Default, will be overridden below
"TENSOR_PARALLEL_DEGREE": "1", # Default, will be overridden below

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.

Potential duplicate/conflicting env var setting: HF_HOME and HUGGINGFACE_HUB_CACHE are set here (inside the not self._is_jumpstart_model_id() branch, line 348-349), and then again in the else branch at lines 375-376 (non-local mode). This means for non-local, non-JumpStart models, these values get set twice (which is harmless but redundant). However, for JumpStart models or local modes, the behavior differs:

  • Local mode: The if self.mode in LOCAL_MODES branch sets HF_HUB_OFFLINE but does NOT set HF_HOME/HUGGINGFACE_HUB_CACHE. If the model is not a JumpStart model, these were already set at line 348. But if it IS a JumpStart model, they won't be set at all in local mode. Is that intentional?
  • Non-local JumpStart models: They'll get the env vars from lines 375-376 but not from 348-349.

Consider consolidating the HF cache env var setting to a single location (e.g., always set them regardless of JumpStart status and mode) to make the logic clearer and avoid subtle gaps.

# Cache management based on mode
if self.mode in LOCAL_MODES:
self.env_vars.update({"HF_HUB_OFFLINE": "1"})
else:

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.

Consider using setdefault here too: For consistency with the HF_MODEL_ID change, consider using self.env_vars.setdefault("HF_HOME", "/tmp") and self.env_vars.setdefault("HUGGINGFACE_HUB_CACHE", "/tmp") so that if a user explicitly provides these env vars (e.g., pointing to a different writable directory), their values are preserved. The same applies to lines 348-349.

Comment threadsagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py
@patch('sagemaker.serve.model_builder_servers._get_nb_instance')
def test_build_for_djl_sets_hf_home_to_tmp(
self, mock_nb, mock_djl_config, mock_hf_config, mock_is_js,
mock_validate, mock_auto_detect, mock_prepare, mock_create,

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.

Massive test duplication: Nearly every test method has the same ~30 lines of mock setup and builder construction copied verbatim. Extract the common mock setup and builder creation into a pytest.fixture (or at minimum a helper method). This would reduce the file by ~60% and make it much easier to maintain. For example:

@pytest.fixturedefdjl_builder(mock_session, temp_dir):
# common mock patches and builder setup
...
returnbuilder

Copy link
Copy Markdown
CollaboratorAuthor

Choose a reason for hiding this comment

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

Agreed! Only a few tests would suffice. We should strive for test quality over quantity!

@patch('sagemaker.serve.model_builder_servers._get_nb_instance')
def test_build_for_djl_sets_hf_home_to_tmp(
self, mock_nb, mock_djl_config, mock_hf_config, mock_is_js,
mock_validate, mock_auto_detect, mock_prepare, mock_create,

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.

Tests test_build_for_djl_sets_hf_home_to_tmp and test_build_for_djl_sets_huggingface_hub_cache_to_tmp are redundant with test_build_for_djl_with_source_code_and_hf_model_id: The last test already asserts both HF_HOME and HUGGINGFACE_HUB_CACHE. Consider consolidating these three tests into one that checks both env vars, following the "one logical assertion per test" guideline (checking two related env vars from the same operation is one logical assertion).


import unittest
from unittest.mock import Mock, patch, MagicMock
import tempfile

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.

Unused imports: MagicMock, os, and shutil are imported but MagicMock is never used. os and shutil are only used for temp dir cleanup which pytest's tmp_path fixture handles automatically. Clean up unused imports.

MOCK_ROLE_ARN = "arn:aws:iam::123456789012:role/SageMakerRole"
MOCK_IMAGE_URI = "763104351884.dkr.ecr.us-east-1.amazonaws.com/djl-inference:0.36.0-lmi22.0.0-cu129"
MOCK_HF_MODEL_CONFIG = {"model_type": "gpt2", "architectures": ["GPT2LMHeadModel"]}

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.

Hardcoded AWS account ID in mock: MOCK_ROLE_ARN contains 123456789012 and MOCK_IMAGE_URI contains a real ECR registry ID (763104351884). While these are mocks, using a clearly fake ECR URI (e.g., 000000000000.dkr.ecr.us-east-1.amazonaws.com/djl-inference:latest) would be more consistent with test standards that avoid real account/region references.

Comment threadsagemaker-serve/src/sagemaker/serve/model_builder_servers.py
@aviruthen

Copy link
Copy Markdown
CollaboratorAuthor

🤖 Iteration #1 — Review Comments Addressed

Description

Fix ModelBuilder with source_code + DJL LMI: /opt/ml/model becomes read-only, breaking HF Hub model downloads.

The issue has two root causes in _build_for_djl() in model_builder_servers.py:

  1. Missing HF cache redirection: Unlike _build_for_tgi() and _build_for_tei() which set HF_HOME=/tmp and HUGGINGFACE_HUB_CACHE=/tmp, the DJL builder never set these environment variables. When source_code is provided, the model artifacts (requirements.txt etc.) get packaged as model.tar.gz and mounted read-only at /opt/ml/model/. The DJL container then tries to download HF models to /opt/ml/model/ (the default cache location) and fails with EROFS.

  2. HF_MODEL_ID override: _build_for_djl() unconditionally called self.env_vars.update({'HF_MODEL_ID': self.model}), which overwrote any user-provided HF_MODEL_ID value. This prevented users from setting HF_MODEL_ID to a local path (e.g., /opt/ml/model) when they want to use pre-downloaded model artifacts.

Changes Made

  • sagemaker-serve/src/sagemaker/serve/model_builder_servers.py:

    • Added HF_HOME and HUGGINGFACE_HUB_CACHE env vars pointing to /tmp for the DJL builder, consolidated to a single location using setdefault() so user-provided values are preserved
    • Changed HF_MODEL_ID to use setdefault() so user-provided values are not overridden
  • sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py:

    • Added pytest-style tests verifying HF cache env vars, HF_MODEL_ID preservation, and local mode offline behavior
    • Uses fixtures and helper functions to minimize duplication

Comments reviewed: 9
Files modified:sagemaker-serve/src/sagemaker/serve/model_builder_servers.py, sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py

  • sagemaker-serve/src/sagemaker/serve/model_builder_servers.py: Fix DJL builder to use setdefault for HF_MODEL_ID (preserving user values), consolidate HF cache env vars to a single location using setdefault, and remove trailing whitespace
  • sagemaker-serve/tests/unit/servers/test_djl_hf_cache_env.py: Rewrite tests using pytest style with fixtures, consolidating redundant tests and cleaning up imports

@jam-jeejam-jee left a comment

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.

LGTM !

@aviruthen
aviruthen marked this pull request as ready for review April 17, 2026 17:57
@aviruthen
aviruthen merged commit f20a7e2 into aws:masterApr 17, 2026
25 of 35 checks passed
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@aviruthen@sagemaker-bot@nargokul@jam-jee