fix(core): keep environment variable names intact when parsing private hub model documents - #6204
Open
Om-singhaI wants to merge 3 commits into
Open
fix(core): keep environment variable names intact when parsing private hub model documents#6204Om-singhaI wants to merge 3 commits into
Om-singhaI wants to merge 3 commits into
Conversation
…e hub model documents Private hub model documents are converted between UpperCamelCase and snake_case several times while a JumpStartModelSpecs object is built. The hub document parser snake cases the instance type variants, the hub parsers turn the spec back into UpperCamelCase, and JumpStartModelSpecs snake cases it again, twice, when is_hub_content is set. walk_and_apply_json rewrote every key on every pass, including the environment variable names that are stored as keys under Variants.Properties.EnvironmentVariables, so SM_VLLM_MAX_MODEL_LEN came out as s_m__v_l_l_m__m_a_x__m_o_d_e_l__l_e_n and instance specific overrides never merged over the base defaults. The public hub path performs no key conversion, which is why the same model deploys correctly from the public catalog. walk_and_apply_json already has a stop list so that metric definitions are left alone, but it compared only the converted key, which meant a single list could not stop both conversion directions. Compare the original key as well and add environment_variables to the default stop list. The helper exists twice, in common_utils and in jumpstart.hub.parser_utils, and JumpStartInstanceTypeVariants uses the common_utils copy, so both copies receive the same change. Fixes aws#6191
Om-singhaI
had a problem deploying
to
manual-approval
August 23, 2026 00:00 — with
GitHub Actions
Error
Om-singhaI
had a problem deploying
to
manual-approval
August 23, 2026 00:00 — with
GitHub Actions
Error
Om-singhaI
had a problem deploying
to
manual-approval
August 23, 2026 00:00 — with
GitHub Actions
Error
Om-singhaI
requested a deployment
to
manual-approval
August 27, 2026 00:12 — with
GitHub Actions
Waiting
Om-singhaI
requested a deployment
to
manual-approval
August 27, 2026 00:12 — with
GitHub Actions
Waiting
Om-singhaI
requested a deployment
to
manual-approval
August 27, 2026 00:12 — with
GitHub Actions
Waiting
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
fix(core): keep environment variable names intact when parsing private hub model documents
Issue
Fixes #6191
Description of changes
When a JumpStart model is resolved through a private hub (
hub_name=...), the environment variable names stored underHostingInstanceTypeVariants.Variants.<instance>.Properties.EnvironmentVariablesarrive mangled.SM_VLLM_MAX_MODEL_LENbecomess_m__v_l_l_m__m_a_x__m_o_d_e_l__l_e_n, so the instance specific override no longer collides with the base default and both end up in the container environment. reported on 2.245.0 and confirmed by AWS Premium Support on 2.257.6 and 3.20.0 as well and noted that the public hubfrom_jsonpath performs no key conversion, which is why the same model deploys correctly from the public catalog.Root cause
Private hub documents are converted between UpperCamelCase and snake_case several times while the
JumpStartModelSpecsobject is built. For a document with a top levelHostingInstanceTypeVariantsblock the variants go through four passes:HubModelDocument.from_jsonconstructsJumpStartInstanceTypeVariants(..., is_hub_content=True), whosefrom_describe_hub_content_responserunswalk_and_apply_json(response, camel_to_snake)(types.py, the copy of the helper imported fromcommon_utils).hub/parsers.py_to_jsonserialises every data holder in the spec and walks it back withsnake_to_upper_camel(the copy of the helper inhub/parser_utils.py).JumpStartModelSpecs(..., is_hub_content=True)walks the whole spec withcamel_to_snakeagain inJumpStartMetadataBaseFields.from_json(the base class thatJumpStartModelSpecsinherits).JumpStartInstanceTypeVariants(..., is_hub_content=True), which walks them withcamel_to_snakeonce more.Variants that live inside
InferenceConfigComponentsonly seecamel_to_snakepasses, which is why the issue reports the double underscore form (s_m__v_l_l_m__...); the top level block additionally round trips through UpperCamelCase and ends up ass_m_v_l_l_m_m_a_x_m_o_d_e_l_l_e_n. Either way the names are destroyed.walk_and_apply_jsonalready has astop_keyslist (default["metrics"]) so that metric definitions are left untouched, but it compared only the converted key against the list. That means one list cannot stop both conversion directions: thesnake_to_upper_camelpass would needEnvironmentVariableswhile thecamel_to_snakepasses needenvironment_variables. Adding a stop key at a single call site is therefore not enough; I verified that patching only the call atJumpStartInstanceTypeVariants.from_describe_hub_content_responsestill produces mangled names after the remaining passes.Fix
In
walk_and_apply_json, treat a key as a stop key if either its original or its converted form is instop_keys, and addenvironment_variablesto the default stop list. With that, every pass in both directions leaves the children ofEnvironmentVariables/environment_variablesverbatim, while sibling keys such asImageUriandResourceRequirementsare still converted as before.Metric definitions behave as they did for real documents: their children were already kept verbatim by the first
camel_to_snakepass, and the only keys that appear under a metrics block areNameandRegex, for which the UpperCamelCase conversion is the identity. Stopping on the originalmetricskey during thesnake_to_upper_camelpass additionally protects any multiword child key there (the old code would have turned a hypotheticalMetricNameintoMetricname), which is the intended meaning of the stop list anyway.The helper exists twice, verbatim, in
sagemaker/core/common_utils.pyand insagemaker/core/jumpstart/hub/parser_utils.py.types.pyimports thecommon_utilscopy whilehub/interfaces.pyandhub/parsers.pyimport theparser_utilscopy, so both copies receive the identical change. I did not make one delegate to the other:common_utilsimporting fromsagemaker.core.jumpstart.hubwould pull in thejumpstartpackage init, which importstypes.py, which importscommon_utils. Consolidating the two helpers would be a reasonable follow up but is out of scope here.The
stop_keysparameter keeps its signature, andstop_keys=Nonestill converts everything. Two edge cases of an explicitly passed list do change: because the original key is now compared as well, a list that happens to name an unconverted key form now stops there too, and an empty list now converts every key (the old condition was falsy for an empty list, so it converted the top level keys but left nested children untouched). No code insagemaker-core,sagemaker-serve,sagemaker-trainorsagemaker-mlopspassesstop_keys; every caller relies on the default.Reproduction on master
Building a
DescribeHubContentResponsefromsagemaker-core/tests/unit/jumpstart/hub_content_document.json(withSM_VLLM_MAX_MODEL_LENadded to theml.g5.12xlargevariant) and callingmake_model_specs_from_describe_hub_content_responseon master gives:With this change the same call returns
{'SM_NUM_GPUS': '4', 'SM_VLLM_MAX_MODEL_LEN': '4096'}.The same fixture also reproduces the user facing symptom without any modification: it declares a default
SM_NUM_GPUS=4inInferenceEnvironmentVariablesand an overrideSM_NUM_GPUS=8on theml.g5.48xlargevariant. Feeding the parsed spec to_retrieve_default_environment_variables(instance_type="ml.g5.48xlarge")on master yields bothSM_NUM_GPUS: '4'ands_m__n_u_m__g_p_u_s: '8'; with this change it yields a singleSM_NUM_GPUS: '8'.Testing done
New unit tests:
sagemaker-core/tests/unit/jumpstart/hub/test_parser_utils.py(new file, 5 tests):camel_to_snakepass,snake_to_upper_camelpass and a four pass round trip all keep environment variable names verbatim while still converting sibling keys; explicitstop_keysandstop_keys=Nonekeep their existing behaviour.sagemaker-core/tests/unit/jumpstart/hub/test_parsers.py(2 tests added toTestParsers): parse the real fixture throughDescribeHubContentResponseandmake_model_specs_from_describe_hub_content_response, once via theInferenceConfigComponentspath that the fixture already exercises and once via a top levelHostingInstanceTypeVariantsblock, and assert the names under the variants and fromget_instance_specific_environment_variablessurvive verbatim.sagemaker-core/tests/unit/jumpstart/artifacts/test_environment_variables.py(new file, 1 test): mirrors the issue end to end. It parses the fixture the way the JumpStart cache does for hub content, returns that spec from a patchedverify_model_region_and_return_specs, and asserts that_retrieve_default_environment_variablesforml.g5.48xlargecontains exactly oneSM_NUM_GPUSkey holding the instance override8. On master this assertion fails with['SM_NUM_GPUS', 's_m__n_u_m__g_p_u_s'].With the two source files checked out from the parent commit, the three test modules give
6 failed, 20 passed(the six preservation tests fail with the mangled keys; the two stop key behaviour tests pass on both versions). With the fix they give26 passed.Surrounding suites (
sagemaker-core/tests/unit/jumpstart,test_jumpstart_types.py,test_jumpstart_types_coverage.py,test_jumpstart_types_extended.py,test_jumpstart_utils.py,test_common_utils.py):952 passed, 4 skipped, 1 failed. The single failure,tests/unit/jumpstart/test_search_unit.py::test_search_public_hub_models, fails identically with the pristine source files (ValueError: Must setup local AWS configuration with a region supported by SageMaker.); it needs a configured AWS region and is unrelated to this change.black(24.10.0, line length 100 fromsagemaker-core/pyproject.toml) leavesparser_utils.pyand the three test files unchanged.common_utils.pywas already not black clean on master; the changed hunk in it is not part of what black would reformat.flake8withsagemaker-core/tox.inireports only pre existing findings in untouched lines (common_utils.py:472 F841,common_utils.py:714 W293, and the unusedpatchimport thattest_parsers.pyalready had on master).No CHANGELOG entry was added because
sagemaker-core/CHANGELOG.mdis updated by the release commits.Notes for reviewers
While reproducing I noticed a separate defect in the same top level variants path: the
snake_to_upper_camelround trip is not lossless for instance keys containing digits.g4dnbecomesG4Dnand theng4_dn, andml.g5.12xlargebecomesml._g5.12_xlarge, so family and instance level lookups on top levelHostingInstanceTypeVariants/TrainingInstanceTypeVariantsblocks from a private hub miss for those keys (the fixture's training variants show['g4_dn', 'g5', 'g6', 'g6_e', ..., 'p3_dn', 'p4_d', 'p4_de', ..., 'ml._g4_dn.12_xlarge']after parsing). That is independent of environment variable names and needs a different fix (the variant keys must stay verbatim while theirPropertieschildren are still converted), so it is left for a follow up rather than widening this change.