Skip to content

Commit 784f6bb

Browse files
author
Xichao Wang
committed
fix(training-session): discover base models by Training Sessions capability
list_base_models() and base_model resolution keep a Hub model when it carries @capability:training_session, or, until the Public Hub publishes that keyword, when it is in the client Region's static list of models served by Training Sessions. Replaces the MTRL recipe keyword filter, which listed only models that also have a managed MTRL recipe.
1 parent 61213e9 commit 784f6bb

3 files changed

Lines changed: 90 additions & 28 deletions

File tree

‎sagemaker-train/src/sagemaker/train/training_session/_utils.py‎

Lines changed: 44 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@
2020
import logging
2121
import warnings
2222
import weakref
23-
from typing import Any, Dict, List, Optional
23+
from typing import Any, Dict, FrozenSet, List, Optional
2424

2525
from sagemaker.train.training_session.types import (
2626
Datum,
@@ -34,12 +34,34 @@
3434

3535
_PUBLIC_HUB_NAME = "SageMakerPublicHub"
3636

37-
#: Hub content search-keyword prefix identifying a base model this client can
38-
#: fine-tune. Each model's recipes are tagged
39-
#: ``@recipe:{type}_{technique}_{strategy}`` (lowercase) on the Public Hub;
40-
#: matching on the prefix selects models carrying a compatible fine-tuning
41-
#: recipe regardless of the parameter-efficient strategy it uses.
42-
_TRAINABLE_RECIPE_KEYWORD_PREFIX = "@recipe:finetuning_mtrl_"
37+
#: Hub content search keyword identifying a base model this client can train.
38+
#: The Public Hub publishes each model's ``Capabilities`` as
39+
#: ``@capability:{capability}`` (lowercase) keywords; ``TRAINING_SESSION`` is
40+
#: set per region, so the keyword is present only where the model is served.
41+
_TRAINABLE_CAPABILITY_KEYWORD = "@capability:training_session"
42+
43+
#: Hub content names served by Training Sessions, by Region, for the interval
44+
#: before the Public Hub publishes the capability keyword above. Mirrors the
45+
#: service's model configuration; a listed model that is absent from a
46+
#: Region's Hub is simply not reported there. Remove when the keyword is live.
47+
_TRAINABLE_MODELS_BY_REGION: Dict[str, FrozenSet[str]] = {
48+
"us-west-2": frozenset(
49+
{
50+
"huggingface-vlm-gemma-4-31b-it",
51+
"huggingface-vlm-qwen3-6-27b",
52+
"huggingface-vlm-qwen3-8-27b",
53+
"openai-reasoning-gpt-oss-20b",
54+
}
55+
),
56+
"us-east-1": frozenset({"openai-reasoning-gpt-oss-20b"}),
57+
"ap-south-1": frozenset(
58+
{
59+
"huggingface-vlm-gemma-4-31b-it",
60+
"huggingface-vlm-qwen3-6-27b",
61+
"openai-reasoning-gpt-oss-20b",
62+
}
63+
),
64+
}
4365

4466
#: Hub content search-keyword prefix carrying a model's published id, spelled
4567
#: ``@huggingface-id:{provider}/{model}``. Free on the list summary, but keyword
@@ -206,17 +228,29 @@ def _public_model_id(hub_content_name: str, keywords: List[str], hub_client: Any
206228
return model_id
207229

208230

231+
def _is_trainable(hub_content_name: str, keywords: List[str], region: Optional[str]) -> bool:
232+
"""Return True if the Hub entry is served by Training Sessions in this Region.
233+
234+
A model qualifies when its keywords carry the training-session capability
235+
or when it is in the Region's static list (:data:`_TRAINABLE_MODELS_BY_REGION`).
236+
"""
237+
if any(kw.lower() == _TRAINABLE_CAPABILITY_KEYWORD for kw in keywords):
238+
return True
239+
return hub_content_name in _TRAINABLE_MODELS_BY_REGION.get(region or "", frozenset())
240+
241+
209242
def _scan_trainable_hub_models(hub_client: Any) -> Dict[str, str]:
210243
"""Return ``{model id: Hub content name}`` for the region's fine-tunable models.
211244
212-
Queries the SageMaker Public Hub and keeps the models that advertise a
213-
compatible fine-tuning recipe (via their ``HubContentSearchKeywords``), so
245+
Queries the SageMaker Public Hub and keeps the models that advertise the
246+
training-session capability (via their ``HubContentSearchKeywords``), so
214247
the set is discovered live rather than hardcoded and a newly enabled model
215248
appears with no SDK change.
216249
217250
Args:
218251
hub_client: A boto3 ``sagemaker`` client (its region determines results).
219252
"""
253+
region = getattr(getattr(hub_client, "meta", None), "region_name", None)
220254
trainable: List["tuple[str, List[str]]"] = []
221255
next_token = None
222256
while True:
@@ -231,7 +265,7 @@ def _scan_trainable_hub_models(hub_client: Any) -> Dict[str, str]:
231265
if _is_excluded_base_model(hub_content_name):
232266
continue
233267
keywords = item.get("HubContentSearchKeywords", []) or []
234-
if not any(kw.lower().startswith(_TRAINABLE_RECIPE_KEYWORD_PREFIX) for kw in keywords):
268+
if not _is_trainable(hub_content_name, keywords, region):
235269
continue
236270
trainable.append((hub_content_name, keywords))
237271
next_token = resp.get("NextToken")

‎sagemaker-train/tests/unit/train/training_session/test_service_client.py‎

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -530,8 +530,8 @@ def test_list_keeps_a_status_this_version_does_not_know(patch_clients):
530530
class FakeHubClient:
531531
"""Stubs DescribeHubContent + ListHubContents for base-model resolution."""
532532

533-
#: Recipe keyword marking a model as fine-tunable (what the SDK filters on).
534-
_TRAINABLE_KEYWORD = "@recipe:finetuning_mtrl_lora"
533+
#: Capability keyword marking a model as trainable (what the SDK filters on).
534+
_TRAINABLE_KEYWORD = "@capability:training_session"
535535

536536
def __init__(
537537
self,
@@ -547,7 +547,7 @@ def __init__(
547547
else ["hubcontent-vlm-model-b", "hubcontent-text-model-a"]
548548
)
549549
# Search keywords per content name. Default: every listed model carries
550-
# the fine-tuning recipe keyword, i.e. is trainable.
550+
# the training-session capability keyword, i.e. is trainable.
551551
self._keywords_by_name = (
552552
keywords_by_name
553553
if keywords_by_name is not None
@@ -1092,8 +1092,8 @@ def test_create_rejects_unresolvable_base_model(patch_clients):
10921092
svc.create_lora_training_client("not-a-real-model", **kwargs)
10931093

10941094

1095-
def test_list_base_models_filters_by_recipe_keyword(patch_clients):
1096-
# Only models carrying a fine-tuning recipe keyword are trainable; a model
1095+
def test_list_base_models_filters_by_capability_keyword(patch_clients):
1096+
# Only models carrying the training-session capability keyword are trainable; a model
10971097
# present in the hub without one must be excluded. Each trainable model is
10981098
# reported by the model id its Hub entry advertises.
10991099
install, _ = patch_clients
@@ -1108,11 +1108,11 @@ def test_list_base_models_filters_by_recipe_keyword(patch_clients):
11081108
keywords_by_name={
11091109
"hubcontent-vlm-model-b": [
11101110
"@recipe:finetuning_sft_lora",
1111-
"@recipe:finetuning_mtrl_lora",
1111+
"@capability:training_session",
11121112
"@huggingface-id:provider-b/model-b",
11131113
],
11141114
"hubcontent-vlm-model-c": [
1115-
"@recipe:finetuning_mtrl_lora",
1115+
"@capability:training_session",
11161116
"@huggingface-id:provider-c/model-c",
11171117
],
11181118
"some-eval-only-model": ["@recipe:evaluation_mtrlevaluation"],
@@ -1126,7 +1126,7 @@ def test_list_base_models_filters_by_recipe_keyword(patch_clients):
11261126

11271127

11281128
def test_list_base_models_excludes_unsupported_names(patch_clients):
1129-
# The recipe keyword alone does not make a model usable through this client.
1129+
# The capability keyword alone does not make a model usable through this client.
11301130
# A name the client does not support end to end is excluded even when the
11311131
# Hub tags it as fine-tunable.
11321132
install, _ = patch_clients
@@ -1140,11 +1140,11 @@ def test_list_base_models_excludes_unsupported_names(patch_clients):
11401140
],
11411141
keywords_by_name={
11421142
"hubcontent-vlm-model-b": [
1143-
"@recipe:finetuning_mtrl_lora",
1143+
"@capability:training_session",
11441144
"@huggingface-id:provider-b/model-b",
11451145
],
11461146
"hubcontent-nova-model-x": [
1147-
"@recipe:finetuning_mtrl_lora",
1147+
"@capability:training_session",
11481148
"@huggingface-id:provider-x/model-x",
11491149
],
11501150
},

‎sagemaker-train/tests/unit/train/training_session/test_utils.py‎

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,7 @@ class _FakeCatalogHub:
8989
Args:
9090
models: ``{hub_content_name: (arn, hf_id_or_None)}``.
9191
advertise_hf_id: Put ``@huggingface-id:`` on the list summary, as the
92-
real Hub does. False leaves only the recipe keyword, so the
92+
real Hub does. False leaves only the capability keyword, so the
9393
Describe fallback is what has to supply the id.
9494
region: Reported as ``meta.region_name``, as a real client does. The
9595
catalogue is cached per client rather than per Region, so this
@@ -98,7 +98,7 @@ class _FakeCatalogHub:
9898
that are not fine-tunable at all.
9999
"""
100100

101-
_RECIPE_KW = "@recipe:finetuning_mtrl_lora"
101+
_CAPABILITY_KW = "@capability:training_session"
102102

103103
def __init__(self, models, *, advertise_hf_id=False, region=None, keywords_by_name=None):
104104
self._models = models
@@ -112,7 +112,7 @@ def __init__(self, models, *, advertise_hf_id=False, region=None, keywords_by_na
112112
def _keywords(self, name):
113113
if name in self._keywords_by_name:
114114
return self._keywords_by_name[name]
115-
keywords = [self._RECIPE_KW]
115+
keywords = [self._CAPABILITY_KW]
116116
hf_id = self._models[name][1]
117117
if self._advertise_hf_id and hf_id:
118118
keywords.append(f"@huggingface-id:{hf_id.lower()}")
@@ -157,7 +157,7 @@ def list_hub_contents(self, **kwargs):
157157
{
158158
"HubContentName": "hubcontent-text-model-a",
159159
"HubContentSearchKeywords": [
160-
"@recipe:finetuning_mtrl_lora",
160+
"@capability:training_session",
161161
"@huggingface-id:provider-a/model-a",
162162
],
163163
}
@@ -246,6 +246,34 @@ def test_excludes_models_without_a_finetuning_recipe(self):
246246
)
247247
assert list_base_models_in_region(hub) == ["provider-a/model-a"]
248248

249+
def test_static_region_list_surfaces_a_model_without_the_keyword(self):
250+
# Until the Hub publishes the capability, a Region's static list stands in.
251+
hub = _FakeCatalogHub(
252+
{"huggingface-vlm-qwen3-8-27b": (_MODEL_A_ARN, "Qwen/Qwen3.8-27B")},
253+
advertise_hf_id=True,
254+
region="us-west-2",
255+
keywords_by_name={"huggingface-vlm-qwen3-8-27b": ["@huggingface-id:qwen/qwen3.8-27b"]},
256+
)
257+
assert list_base_models_in_region(hub) == ["Qwen/Qwen3.8-27B"]
258+
assert resolve_base_model("Qwen/Qwen3.8-27B", hub) == (_MODEL_A_ARN, "Qwen/Qwen3.8-27B")
259+
260+
def test_static_region_list_is_per_region(self):
261+
hub = _FakeCatalogHub(
262+
{"huggingface-vlm-qwen3-6-27b": (_MODEL_A_ARN, "Qwen/Qwen3.6-27B")},
263+
advertise_hf_id=True,
264+
region="us-east-1",
265+
keywords_by_name={"huggingface-vlm-qwen3-6-27b": ["@huggingface-id:qwen/qwen3.6-27b"]},
266+
)
267+
assert list_base_models_in_region(hub) == []
268+
269+
def test_static_region_list_needs_a_region(self):
270+
hub = _FakeCatalogHub(
271+
{"huggingface-vlm-qwen3-8-27b": (_MODEL_A_ARN, "Qwen/Qwen3.8-27B")},
272+
advertise_hf_id=True,
273+
keywords_by_name={"huggingface-vlm-qwen3-8-27b": ["@huggingface-id:qwen/qwen3.8-27b"]},
274+
)
275+
assert list_base_models_in_region(hub) == []
276+
249277
def test_paginates(self):
250278
hub = _FakeCatalogHub(self._MODELS, advertise_hf_id=True)
251279
pages = [
@@ -254,7 +282,7 @@ def test_paginates(self):
254282
{
255283
"HubContentName": "hubcontent-text-model-a",
256284
"HubContentSearchKeywords": [
257-
"@recipe:finetuning_mtrl_lora",
285+
"@capability:training_session",
258286
"@huggingface-id:provider-a/model-a",
259287
],
260288
}
@@ -266,7 +294,7 @@ def test_paginates(self):
266294
{
267295
"HubContentName": "hubcontent-vlm-model-b",
268296
"HubContentSearchKeywords": [
269-
"@recipe:finetuning_mtrl_lora",
297+
"@capability:training_session",
270298
"@huggingface-id:provider-b/model-b",
271299
],
272300
}
@@ -303,7 +331,7 @@ def test_ignores_a_keyword_that_is_not_a_model_id(self):
303331
# in the very error that rejects it; the content name is listed instead.
304332
hub = _FakeCatalogHub({"hubcontent-single": (_MODEL_A_ARN, None)})
305333
hub._keywords_by_name = {
306-
"hubcontent-single": ["@recipe:finetuning_mtrl_lora", "@huggingface-id:singleseg"]
334+
"hubcontent-single": ["@capability:training_session", "@huggingface-id:singleseg"]
307335
}
308336

309337
listed = list_base_models_in_region(hub)
@@ -410,7 +438,7 @@ class _KeywordOnlyHub(_FakeCatalogHub):
410438
"""Advertises the canonical id; its Hub document carries none."""
411439

412440
def _keywords(self, name):
413-
return ["@recipe:finetuning_mtrl_lora", "@huggingface-id:Provider-A/Model-A"]
441+
return ["@capability:training_session", "@huggingface-id:Provider-A/Model-A"]
414442

415443
def describe_hub_content(self, **kwargs):
416444
self.describe_calls.append(kwargs["HubContentName"])

0 commit comments

Comments
 (0)