Skip to content

Commit ac1dfa7

Browse files
authored
docs(instance-preferences): resolve example images from a candidate type; make the PipeVar override test tox-safe (#6257)
* docs(instance-preferences): resolve the example image from a candidate type The training notebook and the training guide resolved the PyTorch image with instance_type="ml.m5.xlarge", which selects the CPU image, and then paired it with GPU candidates (ml.p5.48xlarge, ml.p4d.24xlarge). A reader copying either example would run a GPU job on the CPU image. Both now resolve the image from one of the listed candidates (all three GPU candidates map to the same pytorch-training:2.0.0-gpu-py310 image), and state the rule the feature implies: the image is fixed at submission time while the instance type is not, so every candidate must be able to run it. The processing notebook and guide are updated the same way for consistency; the scikit-learn image is CPU-only and all candidates are m5 types, so their behaviour was already correct. * test(instance-preferences): decouple the PipeVar override test from the packaged service model TestInstancePreferencesPipeVarOverrides built a ShapesExtractor from the real service JSON, which lives under sample/ in the source tree and is not part of the installed package. Under tox the extractor is imported from site-packages, the file is absent, and all six cases errored at setup with FileNotFoundError. The test now injects a minimal in-memory model that mirrors the real member -> shape wiring for the six count members, so it exercises exactly the PIPE_VAR_OVERRIDES lookup and nothing else. A control shape with the same integer type and no override asserts the widening is targeted rather than blanket. Removing any one override still fails exactly that member's case. * docs(instance-preferences): list the shallow test in the suite README; define processing_image in the guide The shallow suite README maps each shallow file to its deep counterpart; the instance-preferences pair was missing. The processing guide's instance-preferences snippet referenced processing_image without defining it on the page; it now resolves the image from one of the candidates, matching the training guide.
1 parent 07b7741 commit ac1dfa7

6 files changed

Lines changed: 83 additions & 7 deletions

File tree

‎docs/ml_ops/index.rst‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -321,8 +321,14 @@ Run data preprocessing with ``ScriptProcessor`` (sklearn) or ``FrameworkProcesso
321321

322322
.. code-block:: python
323323
324+
from sagemaker.core import image_uris
324325
from sagemaker.core.processing import Processor
325326
327+
# Resolve the image from one of the candidates; every candidate must be able to run it.
328+
processing_image = image_uris.retrieve(
329+
framework="sklearn", region=region, version="1.2-1", instance_type="ml.m5.4xlarge"
330+
)
331+
326332
processor = Processor(
327333
role=role, image_uri=processing_image, volume_size_in_gb=100,
328334
instance_preferences=[
@@ -333,7 +339,7 @@ Run data preprocessing with ``ScriptProcessor`` (sklearn) or ``FrameworkProcesso
333339
334340
processor.run(job_name="instance-prefs-processing")
335341
336-
Up to 5 candidates are allowed, each instance type at most once, and exactly one is selected; the list is mutually exclusive with ``instance_type``. Counts use exactly one of two modes — a top-level ``instance_count`` shared by whichever candidate wins, or an ``InstanceCount`` on every candidate — and mixed, partial, or omitted counts are rejected. Selection is based on capacity, not on workload fit, so list only types the job can genuinely run on. The winner is reported as ``SelectedInstanceType`` / ``SelectedInstanceCount`` on the job's ``ClusterConfig``, and billing is for that type and count. Supported on ``Processor``, ``ScriptProcessor``, ``PySparkProcessor``, and ``SparkJarProcessor``; training plans are training-only and do not apply to processing.
342+
Up to 5 candidates are allowed, each instance type at most once, and exactly one is selected; the list is mutually exclusive with ``instance_type``. Counts use exactly one of two modes — a top-level ``instance_count`` shared by whichever candidate wins, or an ``InstanceCount`` on every candidate — and mixed, partial, or omitted counts are rejected. Selection is based on capacity, not on workload fit, so list only types that can run the job's ``image_uri`` (the image is fixed at submission time; the instance type is not). The winner is reported as ``SelectedInstanceType`` / ``SelectedInstanceCount`` on the job's ``ClusterConfig``, and billing is for that type and count. Supported on ``Processor``, ``ScriptProcessor``, ``PySparkProcessor``, and ``SparkJarProcessor``; training plans are training-only and do not apply to processing.
337343

338344
:doc:`Instance Preferences example <../v3-examples/ml-ops-examples/v3-processing-instance-preferences>`
339345

‎docs/training/index.rst‎

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,10 +398,22 @@ Provide an ordered list of candidate instance types and the platform launches th
398398

399399
.. code-block:: python
400400
401+
from sagemaker.core import image_uris
401402
from sagemaker.train.model_trainer import ModelTrainer
402403
from sagemaker.core.training.configs import Compute, SourceCode
403404
from sagemaker.core.shapes import InstancePreference
404405
406+
# The image is fixed at submission time while the instance type is not:
407+
# resolve it from one of the candidates and list only types that can run it.
408+
gpu_training_image = image_uris.retrieve(
409+
framework="pytorch",
410+
region=region,
411+
version="2.0.0",
412+
py_version="py310",
413+
instance_type="ml.p5.48xlarge",
414+
image_scope="training",
415+
)
416+
405417
compute = Compute(
406418
instance_preferences=[
407419
InstancePreference(instance_type="ml.p5.48xlarge"),
@@ -412,7 +424,7 @@ Provide an ordered list of candidate instance types and the platform launches th
412424
)
413425
414426
model_trainer = ModelTrainer(
415-
training_image=training_image,
427+
training_image=gpu_training_image,
416428
source_code=SourceCode(source_dir="./source", entry_script="train.py"),
417429
compute=compute,
418430
base_job_name="instance-preferences-training",

‎sagemaker-core/tests/unit/tools/test_shapes_extractor.py‎

Lines changed: 54 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,54 @@ class TestInstancePreferencesPipeVarOverrides:
350350
"""The IntPipeVar annotations on instance-preferences count members come from
351351
PIPE_VAR_OVERRIDES, not the service model. If an override is dropped, codegen
352352
silently narrows the member back to int and pipeline variables stop being
353-
accepted -- so assert the generated type directly."""
353+
accepted -- so assert the generated type directly.
354+
355+
Uses a minimal in-memory model mirroring the real member -> shape wiring;
356+
the packaged service JSON is not available in every test environment."""
357+
358+
_COUNT = {"type": "integer", "min": 1}
359+
_STRING = {"type": "string"}
360+
_MODEL = {
361+
"TrainingInstanceCount": _COUNT,
362+
"ProcessingInstanceCount": _COUNT,
363+
"TrainingInstanceType": _STRING,
364+
"ProcessingInstanceType": _STRING,
365+
"ResourceConfig": {
366+
"type": "structure",
367+
"members": {
368+
"InstanceType": {"shape": "TrainingInstanceType"},
369+
"InstanceCount": {"shape": "TrainingInstanceCount"},
370+
"SelectedInstanceCount": {"shape": "TrainingInstanceCount"},
371+
},
372+
},
373+
"InstancePreference": {
374+
"type": "structure",
375+
"members": {
376+
"InstanceType": {"shape": "TrainingInstanceType"},
377+
"InstanceCount": {"shape": "TrainingInstanceCount"},
378+
},
379+
},
380+
"ProcessingClusterConfig": {
381+
"type": "structure",
382+
"members": {
383+
"InstanceType": {"shape": "ProcessingInstanceType"},
384+
"InstanceCount": {"shape": "ProcessingInstanceCount"},
385+
"SelectedInstanceCount": {"shape": "ProcessingInstanceCount"},
386+
},
387+
},
388+
"ProcessingInstancePreference": {
389+
"type": "structure",
390+
"members": {
391+
"InstanceType": {"shape": "ProcessingInstanceType"},
392+
"InstanceCount": {"shape": "ProcessingInstanceCount"},
393+
},
394+
},
395+
# Control: same integer shape, no override registered -> must stay int.
396+
"UnrelatedConfig": {
397+
"type": "structure",
398+
"members": {"InstanceCount": {"shape": "TrainingInstanceCount"}},
399+
},
400+
}
354401

355402
@pytest.fixture
356403
def extractor(self, tmp_path):
@@ -363,7 +410,7 @@ def extractor(self, tmp_path):
363410
str(tmp_path / "shape_dag.py"),
364411
),
365412
):
366-
return ShapesExtractor()
413+
return ShapesExtractor(combined_shapes=self._MODEL)
367414

368415
@pytest.mark.parametrize(
369416
"shape, member",
@@ -383,3 +430,8 @@ def test_count_members_generate_as_int_pipe_var(self, extractor, shape, member):
383430
f"{shape}.{member} generated as {members[member]!r}; "
384431
"expected IntPipeVar via PIPE_VAR_OVERRIDES"
385432
)
433+
434+
def test_override_is_targeted_not_blanket(self, extractor):
435+
members = extractor.generate_shape_members("UnrelatedConfig")
436+
assert "IntPipeVar" not in members["instance_count"]
437+
assert "int" in members["instance_count"]

‎sagemaker-train/tests/integ/train/shallow/README.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ of any deep test is easy to find:
7575
| Shallow file | Deep counterpart |
7676
|---|---|
7777
| `test_model_trainer.py` | `test_model_trainer.py` |
78+
| `test_instance_preferences.py` | `test_instance_preferences.py` |
7879
| `test_sft_trainer.py` | `test_sft_trainer_integration.py` |
7980
| `test_dpo_trainer.py` | `test_dpo_trainer_integration.py` |
8081
| `test_rlvr_trainer.py` | `test_rlvr_trainer_integration.py` |
@@ -108,6 +109,7 @@ below accounts for all of them.
108109
| Deep test | Shallow equivalent |
109110
|---|---|
110111
| `test_model_trainer.py` — 8 tests (tar source, py/sh entry, MPI, torchrun, HP json/yaml, custom driver) | `test_model_trainer.py` — `TestSourceCodePackaging`, `TestPayloadShaping`, `TestComputeConfiguration` |
112+
| `test_instance_preferences.py::test_instance_preferences_select_a_winner_and_complete` (runs to a selected winner and completion) | `test_instance_preferences.py` — `TestInstancePreferencesAccepted` (submit, Describe echo, stop) + `TestInstancePreferencesRejected` |
111113
| `test_sft_trainer_integration.py::test_sft_trainer_lora_complete_workflow` | `test_minimal_request_is_accepted` + `test_mlflow_resource_arn` |
112114
| `::test_sft_trainer_with_validation_dataset` | `test_with_validation_dataset` |
113115
| `::test_sft_trainer_lora_with_sequence_length` | `test_sft_trainer.py::test_sequence_length_is_accepted` |

‎v3-examples/ml-ops-examples/v3-processing-instance-preferences.ipynb‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@
3939
" framework=\"sklearn\",\n",
4040
" region=region,\n",
4141
" version=\"1.2-1\",\n",
42-
" instance_type=\"ml.m5.xlarge\",\n",
42+
" instance_type=\"ml.m5.4xlarge\", # a Step 2 candidate; every candidate must be able to run the image\n",
4343
" image_scope=\"training\",\n",
4444
")\n"
4545
]

‎v3-examples/training-examples/instance-preferences-example.ipynb‎

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,11 @@
1717
"source": [
1818
"## Step 1: Setup Session\n",
1919
"\n",
20-
"Initialize the SageMaker session, execution role, and a training image.\n"
20+
"Initialize the SageMaker session, execution role, and a training image.\n",
21+
"\n",
22+
"The image is fixed at submission time while the instance type is not, so resolve it\n",
23+
"from one of the candidate types you will list in Step 2 and make sure every candidate\n",
24+
"can run it. Here both candidates are GPU types and share the same GPU image.\n"
2125
]
2226
},
2327
{
@@ -42,7 +46,7 @@
4246
" region=region,\n",
4347
" version=\"2.0.0\",\n",
4448
" py_version=\"py310\",\n",
45-
" instance_type=\"ml.m5.xlarge\",\n",
49+
" instance_type=\"ml.p5.48xlarge\", # a Step 2 candidate; ml.p4d.24xlarge resolves to the same image\n",
4650
" image_scope=\"training\",\n",
4751
")\n"
4852
]

0 commit comments

Comments
 (0)