fix: resolve private hub Models and aliased references for modelTrainer - #6201
fix: resolve private hub Models and aliased references for modelTrainer#6201tanvikab4 wants to merge 2 commits into
Conversation
| deadline = time.time() + timeout | ||
| while time.time() < deadline: | ||
| try: | ||
| resp = sm.list_hub_contents(HubName=hub_name, HubContentType=content_type) |
There was a problem hiding this comment.
can we use describe_hub_content directly?
There was a problem hiding this comment.
switched _wait_for_content to use describe_hub_content directly
|
|
||
| def _default_training_dataset(region, model_id): | ||
| """Resolve the model's default training dataset S3 URI from JS metadata.""" | ||
| from sagemaker.core.jumpstart.accessors import JumpStartModelsAccessor |
There was a problem hiding this comment.
let's avoid the lazy imports, claude loves to add them for some reason lol
| compute=Compute(instance_type="ml.m5.xlarge"), | ||
| sagemaker_session=sagemaker_session, | ||
| ) | ||
| model_trainer.train(input_data_config=[InputData(channel_name="training", data_source=dataset)]) |
There was a problem hiding this comment.
is there anything to assert or just validating nothing is thrown?
There was a problem hiding this comment.
This test's purpose is to verify the aliased-reference resolution path so when hub_content_name differs from the public model_id, from_jumpstart_config resolves the reference by its alias. I added these assertions to strengthen the test:
- _jumpstart_config.hub_content_name == ALIASED_REFERENCE_NAME (the alias was threaded through resolution)
- training_image is set
- the model channel's S3 source carries a HubAccessConfig with a hub_content_arn
| model_trainer.train() | ||
|
|
||
|
|
||
| def test_jumpstart_train_from_private_hub_reference(private_hub, sagemaker_session): |
There was a problem hiding this comment.
can you add a unit test for training a model reference to a gated model? there should have been a similar test in v2 so you can use that same model. There's some ModelAccessConfig/accept_eula stuff that we should verify works
There was a problem hiding this comment.
Added a unit test class TestJumpStartTrainDefaultsGatedModelReferenceEula in test_defaults.py using the same gated model as v2 (mocks the resolver seam and verifies the
ModelAccessConfig/accept_eula behavior)
There was a problem hiding this comment.
Sorry, meant integ test. My bad
There was a problem hiding this comment.
might have missed it but do we have this as an integ test?
There was a problem hiding this comment.
I looked into the v2 tests and there exists a gated private hub test on the ModelBuilder/deploy side, but not one for the training path. I'll add a corresponding integ test for ModelTrainer rn
| HubDescription="SDK integ test JumpStart training private hub", | ||
| ) | ||
| except ClientError as e: | ||
| pytest.skip(f"Cannot create private hub (missing permissions?): {e}") |
There was a problem hiding this comment.
Skipping gracefully without hub permissions is the right intent, but the implementation is wider than that: every setup step here is a bare except ClientError → skip (create_hub, create_hub_content_reference, import_hub_content, describe_hub_content, and both _wait_for_content timeouts).
The assertion itself fails loudly — from_jumpstart_config isn't wrapped. But if import_hub_content for a private-hub Model breaks service-side, test_jumpstart_train_from_private_owned_model skips and CI stays green, so the test for the core case in this fix silently stops running.
Suggest matching only the specific authorization codes you expect in a restricted account and letting everything else fail; _wait_for_content returning False should be a pytest.fail once the hub was creatable.
There was a problem hiding this comment.
Added a _skip_if_unauthorized helper that skips only on an authorization allowlist (AccessDeniedException/AccessForbiddenException/UnauthorizedOperation) and re-raises everything else
| compute=Compute(instance_type="ml.m5.xlarge"), | ||
| sagemaker_session=sagemaker_session, | ||
| ) | ||
| model_trainer.train(input_data_config=[InputData(channel_name="training", data_source=dataset)]) |
There was a problem hiding this comment.
This explicit training channel bypasses the hub-aware channel construction the fix enables. _create_training_job_args merges with "method parameter taking precedence" (existing_channels[new_input.channel_name] = new_input), so this bare InputData replaces the channel built by JumpStartTrainDefaults.get_training_dataset_input — the only place ModelAccessConfig and HubAccessConfig(hub_content_arn=...) are attached (defaults.py, hub_content_type == "ModelReference" branch).
So the reference and alias tests never assert that a HubAccessConfig was derived from the resolved hub content, and with a non-gated model like catboost-regression-model they'd pass even if that plumbing were wrong.
The existing test_jumpstart_train calls train() with no arguments and relies on SDK-resolved channels — suggest the same here and dropping _default_training_dataset, which also removes the hardcoded jumpstart-cache-prod-{region} bucket.
There was a problem hiding this comment.
Dropped explicit channel and _default_training_dataset. Train() now uses SDK-resolved channels like test_jumpstart_train. Also added assertions on the resolved channels so the plumbing is guarded
b96a38b to
b0a2b7b
Compare
b0a2b7b to
f66b654
Compare
Description of changes
Summary
ModelTrainer.from_jumpstart_config(...) could not resolve models that live in a private hub — only public
JumpStart models and (by a fragile assumption) plain private-hub references worked. This change fixes hub-content
resolution so ModelTrainer reaches parity with ModelBuilder, supporting:
Root cause
sagemaker.core.jumpstart.document.get_hub_content_and_document() guessed the hub content type from the hub name:
hub_content_type = "Model" if hub_name == SAGEMAKER_PUBLIC_HUB else "ModelReference"
A private hub can hold either a Model or a ModelReference. This guess meant:
Fix (sagemaker-core/src/sagemaker/core/jumpstart/document.py)
public hub uses Model only. This mirrors ModelBuilder's resolution in accessors.py.
No changes were needed elsewhere: defaults.py already attaches HubAccessConfig based on hub_content_type, and
model_trainer.py / JumpStartConfig already support hub_name/hub_content_name — they become correct automatically
once the content type is resolved honestly.
Testing
Unit (sagemaker-core/tests/unit/jumpstart/test_document.py) — 5 new tests, all passing:
Integration (sagemaker-train/tests/integ/jumpstart/test_jumpstart_train.py) — 3 new tests, each creates a
temporary private hub, runs a real training job, and tears down (skips gracefully without hub permissions). All
verified passing end-to-end against AWS: