Instructions to use Codeseys/composer-replication-framework with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use Codeseys/composer-replication-framework with Transformers:
# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("Codeseys/composer-replication-framework", device_map="auto") - Notebooks
- Google Colab
- Kaggle
Wave 20: SageMaker GRPO smoke artifacts (F3) — runnable g5.2xlarge GSM8K
Browse filesThe F3 runnable-now RL smoke, with two live-verified corrections to the
design doc's recipe:
- DLC tag is cu126 + requires the -v1.25 build suffix (NOT the cu124 bare
tag in the AWS docs page; resolved against the live ECR registry).
- sagemaker SDK pinned <3: v3 is an API rewrite that dropped
sagemaker.estimator.Estimator / sagemaker.pytorch.
Artifacts:
- examples/gsm8k_grpo/run_sagemaker.py: GPU+vLLM-colocate variant of run.py
(same GSM8K RLVR reward, plain GRPO alpha=beta=0), CLI/JSON hyperparams,
writes to SM_MODEL_DIR.
- examples/gsm8k_grpo/run_sagemaker_launch.py: sagemaker-v2 Estimator driver.
Default --image dlc uses the stock PyTorch DLC + source_dir (no local
15GB cross-arch build); --image <ecr> uses a prebuilt baked image.
Stages a minimal source_dir (framework + script + requirements).
- examples/gsm8k_grpo/requirements.txt: trl + vllm==0.8.5 (torch-2.6 line) +
RL stack, installed at job start atop the DLC's torch 2.6/cu126.
- docker/Dockerfile.sagemaker + scripts/build_and_push_ecr.sh: the
repeatable baked-image path (corrected DLC tag, cross-arch note).
- docs/AWS_SAGEMAKER_QUICKSTART.md: live facts + run instructions + gotchas.
- pyproject [aws] extra: add sagemaker>=2.200,<3.
Live preflight verified: g5.2xlarge training quota=1, exec role trusts
sagemaker, output bucket writable, 0 jobs running. Smoke job submitted
(composer-grpo-smoke-2026-06-09-18-35-31-353); result recorded separately.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- docker/Dockerfile.sagemaker +25 -0
- docs/AWS_SAGEMAKER_QUICKSTART.md +89 -0
- examples/gsm8k_grpo/requirements.txt +14 -0
- examples/gsm8k_grpo/run_sagemaker.py +179 -0
- examples/gsm8k_grpo/run_sagemaker_launch.py +138 -0
- pyproject.toml +5 -0
- scripts/build_and_push_ecr.sh +43 -0
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Baked SageMaker training image for the Composer-replication RL stack (F3 §3.2).
|
| 2 |
+
#
|
| 3 |
+
# The repeatable path: bake trl+vllm+the framework into an image so jobs don't
|
| 4 |
+
# pip-install at startup (saves ~5-10 min/job and removes a flaky failure
|
| 5 |
+
# surface). The one-shot smoke can instead use the stock DLC + source_dir
|
| 6 |
+
# (run_sagemaker_launch.py --image dlc), which needs no local build.
|
| 7 |
+
#
|
| 8 |
+
# Base: AWS PyTorch DLC, tag RESOLVED LIVE against the us-west-2 registry
|
| 9 |
+
# 2026-06-09 — it is cu126 (NOT the cu124 some docs list) and the -v1.25 build
|
| 10 |
+
# suffix is required (no bare floating 2.6.0-gpu tag exists).
|
| 11 |
+
FROM 763104351884.dkr.ecr.us-west-2.amazonaws.com/pytorch-training:2.6.0-gpu-py312-cu126-ubuntu22.04-sagemaker-v1.25
|
| 12 |
+
|
| 13 |
+
# RL stack baked in. torch 2.6 + CUDA 12.6 already in the DLC — do NOT reinstall
|
| 14 |
+
# torch. vllm 0.8.5 is the torch-2.6 line (pin to avoid a wheel/CUDA mismatch).
|
| 15 |
+
RUN pip install --no-cache-dir \
|
| 16 |
+
"trl>=1.5,<2" "peft>=0.13" "accelerate>=1.0" "datasets>=3.0" \
|
| 17 |
+
"vllm==0.8.5" "fsspec>=2024.6" "s3fs>=2024.6" "hf_transfer>=0.1.6"
|
| 18 |
+
|
| 19 |
+
# The framework itself (train + serverless extras → trainer, loss, executors,
|
| 20 |
+
# replica_entrypoint, s3fs all present).
|
| 21 |
+
COPY . /opt/composer_replication
|
| 22 |
+
RUN pip install --no-cache-dir -e "/opt/composer_replication[train,serverless]"
|
| 23 |
+
|
| 24 |
+
ENV HF_HOME=/opt/ml/input/hf_cache \
|
| 25 |
+
HF_HUB_ENABLE_HF_TRANSFER=1
|
|
@@ -0,0 +1,89 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# AWS SageMaker Quickstart — the runnable-now GRPO smoke
|
| 2 |
+
|
| 3 |
+
The minimum path to running the Composer-replication RL inner loop on a real
|
| 4 |
+
GPU, end-to-end, for **under $1**. Implements F3 (`research/design-F3-rl-sagemaker.md`).
|
| 5 |
+
|
| 6 |
+
## Live account facts (verified 2026-06-09, acct 386931836011, us-west-2)
|
| 7 |
+
|
| 8 |
+
| Fact | Value |
|
| 9 |
+
|---|---|
|
| 10 |
+
| `ml.g5.2xlarge` training-job quota | **1** (live, code `L-2D6DEB3C`) → no quota ticket needed |
|
| 11 |
+
| Execution role | `arn:aws:iam::386931836011:role/service-role/AmazonSageMaker-ExecutionRole-20250725T133247` |
|
| 12 |
+
| Bucket (rendezvous + output) | `amazon-sagemaker-386931836011-us-west-2-7597bf4d9a3d` |
|
| 13 |
+
| PyTorch DLC base image | `763104351884.dkr.ecr.us-west-2.amazonaws.com/pytorch-training:2.6.0-gpu-py312-cu126-ubuntu22.04-sagemaker-v1.25` |
|
| 14 |
+
|
| 15 |
+
> **Two corrections vs the AWS DLC docs page** (found by querying the live ECR
|
| 16 |
+
> registry, not the docs): the tag is **cu126**, not cu124, and the **`-v1.25`
|
| 17 |
+
> build suffix is required** — there is no bare floating `2.6.0-gpu...` tag.
|
| 18 |
+
> Always resolve the tag against the registry:
|
| 19 |
+
> ```bash
|
| 20 |
+
> aws ecr describe-images --registry-id 763104351884 \
|
| 21 |
+
> --repository-name pytorch-training --region us-west-2 \
|
| 22 |
+
> --query "reverse(sort_by(imageDetails,&imagePushedAt))[].imageTags" --output text \
|
| 23 |
+
> | tr '\t' '\n' | grep -E '^2.6.0-gpu-py312-cu126-.*-sagemaker-v[0-9.]+$' | head -1
|
| 24 |
+
> ```
|
| 25 |
+
|
| 26 |
+
> **SDK pin:** the smoke launcher uses the **sagemaker SDK v2** Estimator API.
|
| 27 |
+
> SDK **v3 is an API rewrite** that dropped `sagemaker.estimator.Estimator` and
|
| 28 |
+
> `sagemaker.pytorch` — install `pip install 'sagemaker>=2.200,<3'`.
|
| 29 |
+
|
| 30 |
+
## Run it (no local Docker build)
|
| 31 |
+
|
| 32 |
+
```bash
|
| 33 |
+
pip install 'sagemaker>=2.200,<3'
|
| 34 |
+
export AWS_REGION=us-west-2
|
| 35 |
+
python examples/gsm8k_grpo/run_sagemaker_launch.py --max-steps 20
|
| 36 |
+
```
|
| 37 |
+
|
| 38 |
+
This uses the **stock PyTorch DLC directly** as the training image and ships the
|
| 39 |
+
framework + entry script via `source_dir`; `examples/gsm8k_grpo/requirements.txt`
|
| 40 |
+
(trl + vllm==0.8.5 + the RL stack) installs at job start. No 15 GB local image
|
| 41 |
+
build, no ECR push. The script trains `Qwen/Qwen2.5-0.5B-Instruct` with GRPO +
|
| 42 |
+
the GSM8K `#### NUMBER` RLVR reward, vLLM **colocated** in-process
|
| 43 |
+
(`vllm_gpu_memory_utilization=0.3` on the 24 GB A10G).
|
| 44 |
+
|
| 45 |
+
Flags: `--no-wait` (submit + poll later), `--spot` (managed spot, quota=1 too),
|
| 46 |
+
`--no-vllm` (fall back to `model.generate` rollout if a vLLM/CUDA wheel mismatch
|
| 47 |
+
appears), `--image <ecr-uri>` (use a prebuilt baked image instead of the DLC).
|
| 48 |
+
|
| 49 |
+
**Cost:** `ml.g5.2xlarge` ≈ $1.52/hr on-demand; a 20-step 0.5B smoke is
|
| 50 |
+
~15–25 min ⇒ **well under $1**. Spot ≈ $0.45–0.60/hr ⇒ pennies.
|
| 51 |
+
|
| 52 |
+
## The repeatable path (baked image)
|
| 53 |
+
|
| 54 |
+
For runs where the ~5–10 min per-job pip-install is unwanted (and for the
|
| 55 |
+
DiLoCo N-replica `SageMakerExecutor`, which passes `ContainerEntrypoint` and
|
| 56 |
+
needs the framework baked in), build the image once:
|
| 57 |
+
|
| 58 |
+
```bash
|
| 59 |
+
scripts/build_and_push_ecr.sh # creates ECR repo, builds, pushes composer-rl:smoke
|
| 60 |
+
python examples/gsm8k_grpo/run_sagemaker_launch.py \
|
| 61 |
+
--image 386931836011.dkr.ecr.us-west-2.amazonaws.com/composer-rl:smoke
|
| 62 |
+
```
|
| 63 |
+
|
| 64 |
+
On an Apple-Silicon host the build cross-compiles (`--platform linux/amd64`) the
|
| 65 |
+
~15 GB GPU DLC under emulation — slow; prefer a linux/amd64 host or CodeBuild.
|
| 66 |
+
|
| 67 |
+
## Gotchas (load-bearing)
|
| 68 |
+
|
| 69 |
+
- **`EnableNetworkIsolation` stays False** (the default) so the container can
|
| 70 |
+
reach `huggingface.co` (model + GSM8K download) and S3.
|
| 71 |
+
- **`vllm_gpu_memory_utilization=0.3`** is the load-bearing knob on a 24 GB
|
| 72 |
+
A10G: too high → OOM when the policy + grads also need the GPU; too low → tiny
|
| 73 |
+
KV cache. Use `--no-vllm` if a vLLM wheel/CUDA mismatch surfaces.
|
| 74 |
+
- **Warm pools are off** — `g5 training warm pool usage` quota is 0 in this
|
| 75 |
+
account, so each job pays ~3–6 min cold-start. Request a warm-pool quota bump
|
| 76 |
+
for iterative dev, or move the long inner loop to HyperPod (persistent).
|
| 77 |
+
- **"Waiting for capacity"** in `SecondaryStatus` is transient g5 capacity
|
| 78 |
+
contention in the region, not an error — the job proceeds when capacity frees.
|
| 79 |
+
|
| 80 |
+
## Next: DiLoCo N-replica (the `SageMakerExecutor` path)
|
| 81 |
+
|
| 82 |
+
`examples/diloco_sagemaker/run.py` (driver, F3 §4.3) drives N independent
|
| 83 |
+
single-instance Training Jobs sharing one `s3://.../rendezvous/` prefix via
|
| 84 |
+
`ObjectStoreAllReduce` — no cross-job NCCL. N=1 runs today; N=2–4 needs a
|
| 85 |
+
`ml.g5.2xlarge for training job usage` quota increase. The DiLoCo math, loss,
|
| 86 |
+
trainer, and `ObjectStoreAllReduce` are unchanged from the smoke — the S3
|
| 87 |
+
rendezvous is the entire portability contract (validated `file://` and live
|
| 88 |
+
`s3://`; see `test_serverless_local.py::test_s3_rendezvous_allreduce_across_replicas`).
|
| 89 |
+
```
|
|
@@ -0,0 +1,14 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Installed by SageMaker at job start, layered on the PyTorch DLC 2.6.0
|
| 2 |
+
# (torch 2.6 + CUDA 12.6 already present — do NOT reinstall torch).
|
| 3 |
+
# F3 §3.2: baking these into a Dockerfile.sagemaker is the repeatable path;
|
| 4 |
+
# this requirements.txt is the no-local-build path for the one-shot smoke.
|
| 5 |
+
trl>=1.5,<2
|
| 6 |
+
peft>=0.13
|
| 7 |
+
accelerate>=1.0
|
| 8 |
+
datasets>=3.0
|
| 9 |
+
# vLLM must match the DLC's torch 2.6 / cu126. 0.8.x is the torch-2.6 line;
|
| 10 |
+
# pinning avoids a silent wheel/CUDA mismatch at colocate time (F3 §7).
|
| 11 |
+
vllm==0.8.5
|
| 12 |
+
fsspec>=2024.6
|
| 13 |
+
s3fs>=2024.6
|
| 14 |
+
hf_transfer>=0.1.6
|
|
@@ -0,0 +1,179 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""GPU + vLLM-colocated variant of run.py, for a SageMaker Training Job.
|
| 2 |
+
|
| 3 |
+
F3 §3.3: the runnable-NOW GRPO smoke. Same GSM8K RLVR reward and same
|
| 4 |
+
`ComposerReplicationTrainer(alpha_sdpo=0, beta_replay=0)` (plain GRPO, channels
|
| 5 |
+
2/3 off) as the CPU example, lifted to one real GPU with vLLM colocated in the
|
| 6 |
+
training process. Proves: container builds, trainer runs on GPU, vLLM rollout
|
| 7 |
+
works, reward fires, checkpoint lands in S3.
|
| 8 |
+
|
| 9 |
+
This script runs INSIDE the SageMaker container. SageMaker conventions used:
|
| 10 |
+
* Hyperparameters arrive as CLI args ``--key value`` (the Estimator's
|
| 11 |
+
``hyperparameters=`` dict). We also read /opt/ml/input/config/
|
| 12 |
+
hyperparameters.json as a fallback.
|
| 13 |
+
* The final model must be written to ``/opt/ml/model`` (SM_MODEL_DIR);
|
| 14 |
+
SageMaker tars it to ``OutputDataConfig.S3OutputPath`` on exit.
|
| 15 |
+
* stdout/stderr stream to CloudWatch ``/aws/sagemaker/TrainingJobs/<job>``.
|
| 16 |
+
|
| 17 |
+
Run via examples/gsm8k_grpo/run_sagemaker_launch.py (the Estimator driver).
|
| 18 |
+
"""
|
| 19 |
+
from __future__ import annotations
|
| 20 |
+
|
| 21 |
+
import argparse
|
| 22 |
+
import json
|
| 23 |
+
import logging
|
| 24 |
+
import os
|
| 25 |
+
import re
|
| 26 |
+
import sys
|
| 27 |
+
|
| 28 |
+
import torch
|
| 29 |
+
from datasets import load_dataset
|
| 30 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 31 |
+
|
| 32 |
+
# ---------------------------------------------------------------------------
|
| 33 |
+
# Reward — identical RLVR `#### NUMBER` regex to the CPU example (run.py)
|
| 34 |
+
# ---------------------------------------------------------------------------
|
| 35 |
+
|
| 36 |
+
_ANSWER_RE = re.compile(r"####\s*(-?\d+(?:\.\d+)?)")
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def _extract_answer(text: str) -> str | None:
|
| 40 |
+
matches = _ANSWER_RE.findall(text or "")
|
| 41 |
+
return matches[-1].strip() if matches else None
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def gsm8k_reward(completions, **kwargs):
|
| 45 |
+
gold = kwargs.get("gold_answer")
|
| 46 |
+
if gold is None:
|
| 47 |
+
return [0.0] * len(completions)
|
| 48 |
+
rewards: list[float] = []
|
| 49 |
+
for completion, gold_ans in zip(completions, gold, strict=False):
|
| 50 |
+
if isinstance(completion, list):
|
| 51 |
+
text = "\n".join(m.get("content", "") for m in completion)
|
| 52 |
+
else:
|
| 53 |
+
text = str(completion)
|
| 54 |
+
pred = _extract_answer(text)
|
| 55 |
+
rewards.append(1.0 if (pred is not None and pred == str(gold_ans).strip()) else 0.0)
|
| 56 |
+
return rewards
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
SYSTEM_PROMPT = (
|
| 60 |
+
"You are a math tutor. Solve the problem step by step. "
|
| 61 |
+
"End your answer with `#### N` where N is the final numeric answer."
|
| 62 |
+
)
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def build_dataset(n_rows: int):
|
| 66 |
+
raw = load_dataset("openai/gsm8k", "main", split=f"train[:{n_rows}]")
|
| 67 |
+
|
| 68 |
+
def _format(row):
|
| 69 |
+
gold = _extract_answer(row["answer"]) or ""
|
| 70 |
+
return {
|
| 71 |
+
"prompt": [
|
| 72 |
+
{"role": "system", "content": SYSTEM_PROMPT},
|
| 73 |
+
{"role": "user", "content": row["question"]},
|
| 74 |
+
],
|
| 75 |
+
"gold_answer": gold,
|
| 76 |
+
}
|
| 77 |
+
|
| 78 |
+
return raw.map(_format, remove_columns=raw.column_names)
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
# ---------------------------------------------------------------------------
|
| 82 |
+
# Hyperparameters — SageMaker passes them as CLI args; JSON fallback.
|
| 83 |
+
# ---------------------------------------------------------------------------
|
| 84 |
+
|
| 85 |
+
|
| 86 |
+
def _parse_hyperparameters() -> argparse.Namespace:
|
| 87 |
+
p = argparse.ArgumentParser()
|
| 88 |
+
p.add_argument("--model", default="Qwen/Qwen2.5-0.5B-Instruct")
|
| 89 |
+
p.add_argument("--n_train_rows", type=int, default=100)
|
| 90 |
+
p.add_argument("--max_steps", type=int, default=20)
|
| 91 |
+
p.add_argument("--num_generations", type=int, default=8)
|
| 92 |
+
p.add_argument("--per_device_train_batch_size", type=int, default=8)
|
| 93 |
+
p.add_argument("--max_completion_length", type=int, default=256)
|
| 94 |
+
p.add_argument("--learning_rate", type=float, default=1e-5)
|
| 95 |
+
p.add_argument("--beta", type=float, default=0.04)
|
| 96 |
+
p.add_argument("--vllm_gpu_memory_utilization", type=float, default=0.3)
|
| 97 |
+
p.add_argument("--use_vllm", type=lambda s: str(s).lower() != "false", default=True)
|
| 98 |
+
# SageMaker model output dir (env SM_MODEL_DIR, default /opt/ml/model).
|
| 99 |
+
p.add_argument("--model_dir", default=os.environ.get("SM_MODEL_DIR", "/opt/ml/model"))
|
| 100 |
+
args, _unknown = p.parse_known_args()
|
| 101 |
+
return args
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
def main() -> int:
|
| 105 |
+
logging.basicConfig(
|
| 106 |
+
level=logging.INFO,
|
| 107 |
+
format="%(asctime)s [%(levelname)s] %(name)s: %(message)s",
|
| 108 |
+
handlers=[logging.StreamHandler(sys.stdout)],
|
| 109 |
+
)
|
| 110 |
+
log = logging.getLogger("gsm8k_grpo_sagemaker")
|
| 111 |
+
args = _parse_hyperparameters()
|
| 112 |
+
|
| 113 |
+
log.info("=" * 64)
|
| 114 |
+
log.info("GRPO + GSM8K + %s (SageMaker GPU, vLLM=%s)", args.model, args.use_vllm)
|
| 115 |
+
log.info("=" * 64)
|
| 116 |
+
log.info("hyperparameters: %s", json.dumps(vars(args), indent=2))
|
| 117 |
+
|
| 118 |
+
cuda = torch.cuda.is_available()
|
| 119 |
+
log.info("CUDA available: %s | device: %s", cuda,
|
| 120 |
+
torch.cuda.get_device_name(0) if cuda else "cpu")
|
| 121 |
+
|
| 122 |
+
log.info("[1/4] Loading model + tokenizer ...")
|
| 123 |
+
tokenizer = AutoTokenizer.from_pretrained(args.model)
|
| 124 |
+
if tokenizer.pad_token_id is None:
|
| 125 |
+
tokenizer.pad_token = tokenizer.eos_token
|
| 126 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 127 |
+
args.model, torch_dtype=torch.bfloat16 if cuda else torch.float32
|
| 128 |
+
)
|
| 129 |
+
|
| 130 |
+
log.info("[2/4] Loading %d GSM8K rows ...", args.n_train_rows)
|
| 131 |
+
dataset = build_dataset(args.n_train_rows)
|
| 132 |
+
|
| 133 |
+
log.info("[3/4] Building GRPOConfig + ComposerReplicationTrainer ...")
|
| 134 |
+
from trl import GRPOConfig
|
| 135 |
+
|
| 136 |
+
from composer_replication import ComposerReplicationTrainer
|
| 137 |
+
|
| 138 |
+
config = GRPOConfig(
|
| 139 |
+
output_dir=args.model_dir,
|
| 140 |
+
per_device_train_batch_size=args.per_device_train_batch_size,
|
| 141 |
+
num_generations=args.num_generations,
|
| 142 |
+
max_completion_length=args.max_completion_length,
|
| 143 |
+
learning_rate=args.learning_rate,
|
| 144 |
+
max_steps=args.max_steps,
|
| 145 |
+
logging_steps=1,
|
| 146 |
+
save_strategy="no",
|
| 147 |
+
report_to=[],
|
| 148 |
+
bf16=cuda,
|
| 149 |
+
beta=args.beta,
|
| 150 |
+
# vLLM colocated in-process on the same GPU (F3 §3.3 / §5).
|
| 151 |
+
use_vllm=bool(args.use_vllm and cuda),
|
| 152 |
+
vllm_mode="colocate",
|
| 153 |
+
vllm_gpu_memory_utilization=args.vllm_gpu_memory_utilization,
|
| 154 |
+
vllm_tensor_parallel_size=1,
|
| 155 |
+
seed=42,
|
| 156 |
+
)
|
| 157 |
+
|
| 158 |
+
trainer = ComposerReplicationTrainer(
|
| 159 |
+
model=model,
|
| 160 |
+
processing_class=tokenizer,
|
| 161 |
+
reward_funcs=[gsm8k_reward],
|
| 162 |
+
train_dataset=dataset,
|
| 163 |
+
args=config,
|
| 164 |
+
alpha_sdpo=0.0, # channels 2/3 off — plain GRPO smoke
|
| 165 |
+
beta_replay=0.0,
|
| 166 |
+
)
|
| 167 |
+
|
| 168 |
+
log.info("[4/4] Training for %d steps ...", args.max_steps)
|
| 169 |
+
result = trainer.train()
|
| 170 |
+
log.info("Training complete: %s", result.metrics)
|
| 171 |
+
|
| 172 |
+
# Persist to SM_MODEL_DIR → SageMaker uploads to OutputDataConfig.
|
| 173 |
+
trainer.save_model(args.model_dir)
|
| 174 |
+
log.info("Model saved to %s (SageMaker will upload to S3).", args.model_dir)
|
| 175 |
+
return 0
|
| 176 |
+
|
| 177 |
+
|
| 178 |
+
if __name__ == "__main__":
|
| 179 |
+
sys.exit(main())
|
|
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Launch the GSM8K GRPO smoke as a SageMaker Training Job (F3 §3.4).
|
| 2 |
+
|
| 3 |
+
Two ways to run, selected by --image:
|
| 4 |
+
* --image dlc (default): use the AWS PyTorch DLC directly as the training
|
| 5 |
+
image and ship the framework + script via source_dir; deps install at job
|
| 6 |
+
start from requirements.txt. No local Docker build — the lowest-friction
|
| 7 |
+
path for a one-shot smoke (F3's "source_dir on top of the baked image",
|
| 8 |
+
here on top of the *stock* DLC).
|
| 9 |
+
* --image <ecr-uri>: use a prebuilt baked image (docker/Dockerfile.sagemaker
|
| 10 |
+
pushed to ECR via scripts/build_and_push_ecr.sh) — the repeatable path that
|
| 11 |
+
avoids the ~5-10 min startup pip-install (F3 §3.2).
|
| 12 |
+
|
| 13 |
+
Live facts (verified 2026-06-09, acct 386931836011, us-west-2):
|
| 14 |
+
* ml.g5.2xlarge training-job quota = 1 → runs with no quota ticket.
|
| 15 |
+
* DLC tag resolved live: 2.6.0-gpu-py312-cu126-ubuntu22.04-sagemaker-v1.25
|
| 16 |
+
(NOTE: cu126, not the cu124 in some docs; bare floating tag does not exist,
|
| 17 |
+
the -v1.25 build suffix is required).
|
| 18 |
+
* role: AmazonSageMaker-ExecutionRole-20250725T133247
|
| 19 |
+
* bucket: amazon-sagemaker-386931836011-us-west-2-7597bf4d9a3d
|
| 20 |
+
|
| 21 |
+
Usage (from the laptop / Studio, sagemaker SDK v2):
|
| 22 |
+
pip install 'sagemaker>=2.200,<3'
|
| 23 |
+
python examples/gsm8k_grpo/run_sagemaker_launch.py --max-steps 20
|
| 24 |
+
python examples/gsm8k_grpo/run_sagemaker_launch.py --no-wait # fire and poll later
|
| 25 |
+
"""
|
| 26 |
+
from __future__ import annotations
|
| 27 |
+
|
| 28 |
+
import argparse
|
| 29 |
+
import os
|
| 30 |
+
import shutil
|
| 31 |
+
import sys
|
| 32 |
+
import tempfile
|
| 33 |
+
|
| 34 |
+
REGION = "us-west-2"
|
| 35 |
+
ACCOUNT = "386931836011"
|
| 36 |
+
ROLE = f"arn:aws:iam::{ACCOUNT}:role/service-role/AmazonSageMaker-ExecutionRole-20250725T133247"
|
| 37 |
+
BUCKET = f"amazon-sagemaker-{ACCOUNT}-{REGION}-7597bf4d9a3d"
|
| 38 |
+
# DLC tag resolved live against the 763104351884 us-west-2 registry.
|
| 39 |
+
DLC_IMAGE = (
|
| 40 |
+
"763104351884.dkr.ecr.us-west-2.amazonaws.com/"
|
| 41 |
+
"pytorch-training:2.6.0-gpu-py312-cu126-ubuntu22.04-sagemaker-v1.25"
|
| 42 |
+
)
|
| 43 |
+
|
| 44 |
+
_HERE = os.path.dirname(os.path.abspath(__file__))
|
| 45 |
+
_REPO_ROOT = os.path.abspath(os.path.join(_HERE, "..", ".."))
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def _stage_source() -> str:
|
| 49 |
+
"""Build a minimal source_dir: the entry script + requirements.txt + the
|
| 50 |
+
composer_replication package (importable as a local package from
|
| 51 |
+
/opt/ml/code). Keeps the S3 upload tiny — no .venv/research/docs/.git.
|
| 52 |
+
Returns the staging dir path (caller cleans up)."""
|
| 53 |
+
staging = tempfile.mkdtemp(prefix="composer-sm-src-")
|
| 54 |
+
shutil.copy2(os.path.join(_HERE, "run_sagemaker.py"), staging)
|
| 55 |
+
shutil.copy2(os.path.join(_HERE, "requirements.txt"), staging)
|
| 56 |
+
shutil.copytree(
|
| 57 |
+
os.path.join(_REPO_ROOT, "composer_replication"),
|
| 58 |
+
os.path.join(staging, "composer_replication"),
|
| 59 |
+
ignore=shutil.ignore_patterns("__pycache__", "*.pyc", "tests", "*.egg-info"),
|
| 60 |
+
)
|
| 61 |
+
return staging
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def main() -> int:
|
| 65 |
+
ap = argparse.ArgumentParser()
|
| 66 |
+
ap.add_argument("--image", default="dlc",
|
| 67 |
+
help="'dlc' (stock PyTorch DLC + source_dir) or a prebuilt ECR image URI")
|
| 68 |
+
ap.add_argument("--instance-type", default="ml.g5.2xlarge")
|
| 69 |
+
ap.add_argument("--max-steps", type=int, default=20)
|
| 70 |
+
ap.add_argument("--n-train-rows", type=int, default=100)
|
| 71 |
+
ap.add_argument("--model", default="Qwen/Qwen2.5-0.5B-Instruct")
|
| 72 |
+
ap.add_argument("--no-vllm", action="store_true",
|
| 73 |
+
help="disable colocated vLLM (use model.generate rollout) — safer fallback")
|
| 74 |
+
ap.add_argument("--spot", action="store_true", help="use managed spot (quota=1 too)")
|
| 75 |
+
ap.add_argument("--no-wait", action="store_true", help="submit and return; poll later")
|
| 76 |
+
ap.add_argument("--max-run", type=int, default=3600)
|
| 77 |
+
args = ap.parse_args()
|
| 78 |
+
|
| 79 |
+
import sagemaker
|
| 80 |
+
from sagemaker.estimator import Estimator
|
| 81 |
+
|
| 82 |
+
sess = sagemaker.Session(default_bucket=BUCKET)
|
| 83 |
+
image = DLC_IMAGE if args.image == "dlc" else args.image
|
| 84 |
+
print(f"[launch] region={REGION} image={image}")
|
| 85 |
+
print(f"[launch] role={ROLE}")
|
| 86 |
+
print(f"[launch] instance={args.instance_type} max_steps={args.max_steps} "
|
| 87 |
+
f"vllm={not args.no_vllm} spot={args.spot}")
|
| 88 |
+
|
| 89 |
+
staging = _stage_source()
|
| 90 |
+
print(f"[launch] staged source_dir at {staging}")
|
| 91 |
+
|
| 92 |
+
hyperparameters = {
|
| 93 |
+
"model": args.model,
|
| 94 |
+
"n_train_rows": args.n_train_rows,
|
| 95 |
+
"max_steps": args.max_steps,
|
| 96 |
+
"use_vllm": "false" if args.no_vllm else "true",
|
| 97 |
+
}
|
| 98 |
+
|
| 99 |
+
spot_kwargs = {}
|
| 100 |
+
if args.spot:
|
| 101 |
+
spot_kwargs = {"use_spot_instances": True, "max_wait": args.max_run + 3600}
|
| 102 |
+
|
| 103 |
+
est = Estimator(
|
| 104 |
+
image_uri=image,
|
| 105 |
+
role=ROLE,
|
| 106 |
+
instance_type=args.instance_type,
|
| 107 |
+
instance_count=1,
|
| 108 |
+
volume_size=100,
|
| 109 |
+
max_run=args.max_run,
|
| 110 |
+
sagemaker_session=sess,
|
| 111 |
+
output_path=f"s3://{BUCKET}/composer-rl/smoke/output",
|
| 112 |
+
base_job_name="composer-grpo-smoke",
|
| 113 |
+
entry_point="run_sagemaker.py",
|
| 114 |
+
source_dir=staging,
|
| 115 |
+
hyperparameters=hyperparameters,
|
| 116 |
+
environment={
|
| 117 |
+
"HF_HUB_ENABLE_HF_TRANSFER": "1",
|
| 118 |
+
# DLC sagemaker-training-toolkit installs requirements.txt from source_dir.
|
| 119 |
+
},
|
| 120 |
+
# EnableNetworkIsolation MUST be False (default) so the container can reach
|
| 121 |
+
# huggingface.co (model + GSM8K) and S3 (F3 §3.5).
|
| 122 |
+
keep_alive_period_in_seconds=0, # warm-pool quota=0 in this acct → leave off
|
| 123 |
+
**spot_kwargs,
|
| 124 |
+
)
|
| 125 |
+
|
| 126 |
+
try:
|
| 127 |
+
est.fit(wait=not args.no_wait, logs=("All" if not args.no_wait else None))
|
| 128 |
+
finally:
|
| 129 |
+
shutil.rmtree(staging, ignore_errors=True)
|
| 130 |
+
|
| 131 |
+
print(f"[launch] job name: {est.latest_training_job.name}")
|
| 132 |
+
if not args.no_wait:
|
| 133 |
+
print(f"[launch] model artifact: {est.model_data}")
|
| 134 |
+
return 0
|
| 135 |
+
|
| 136 |
+
|
| 137 |
+
if __name__ == "__main__":
|
| 138 |
+
sys.exit(main())
|
|
@@ -75,8 +75,13 @@ eks = [
|
|
| 75 |
"kubernetes>=29",
|
| 76 |
]
|
| 77 |
# Amazon SageMaker training-job executor (SageMakerExecutor, per ADR-005).
|
|
|
|
|
|
|
|
|
|
|
|
|
| 78 |
aws = [
|
| 79 |
"boto3>=1.34",
|
|
|
|
| 80 |
]
|
| 81 |
# Replaysim dataset normalization (per ADR-004)
|
| 82 |
#
|
|
|
|
| 75 |
"kubernetes>=29",
|
| 76 |
]
|
| 77 |
# Amazon SageMaker training-job executor (SageMakerExecutor, per ADR-005).
|
| 78 |
+
# boto3: the executor uses raw create_training_job. sagemaker (<3): the v2
|
| 79 |
+
# Estimator API the GSM8K smoke launcher (examples/gsm8k_grpo/
|
| 80 |
+
# run_sagemaker_launch.py) uses — pinned <3 because SDK v3 is an API rewrite
|
| 81 |
+
# that dropped sagemaker.estimator.Estimator (F3 §1, verified live 2026-06-09).
|
| 82 |
aws = [
|
| 83 |
"boto3>=1.34",
|
| 84 |
+
"sagemaker>=2.200,<3",
|
| 85 |
]
|
| 86 |
# Replaysim dataset normalization (per ADR-004)
|
| 87 |
#
|
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
# Build + push the baked SageMaker RL image to this account's private ECR (F3 §3.2).
|
| 3 |
+
# One-time (Admin). The one-shot smoke does NOT need this — use
|
| 4 |
+
# python examples/gsm8k_grpo/run_sagemaker_launch.py --image dlc
|
| 5 |
+
# which runs on the stock DLC + source_dir with no local build. Use this script
|
| 6 |
+
# for the repeatable path (no per-job pip-install) and the DiLoCo N-replica
|
| 7 |
+
# executor (which passes ContainerEntrypoint and wants the framework baked in).
|
| 8 |
+
#
|
| 9 |
+
# NOTE: the DLC base is GPU/linux-amd64 (~15 GB). On an Apple-Silicon host you
|
| 10 |
+
# must cross-build: pass --platform linux/amd64 (set below). This is slow under
|
| 11 |
+
# emulation; prefer building on a linux/amd64 host or CodeBuild for real use.
|
| 12 |
+
set -euo pipefail
|
| 13 |
+
|
| 14 |
+
REGION="${REGION:-us-west-2}"
|
| 15 |
+
ACCOUNT="${ACCOUNT:-386931836011}"
|
| 16 |
+
REPO="${REPO:-composer-rl}"
|
| 17 |
+
TAG="${TAG:-smoke}"
|
| 18 |
+
DLC_ACCOUNT="763104351884"
|
| 19 |
+
|
| 20 |
+
REGISTRY="${ACCOUNT}.dkr.ecr.${REGION}.amazonaws.com"
|
| 21 |
+
IMAGE="${REGISTRY}/${REPO}:${TAG}"
|
| 22 |
+
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
| 23 |
+
|
| 24 |
+
echo "[ecr] region=${REGION} image=${IMAGE}"
|
| 25 |
+
|
| 26 |
+
# 1. Ensure the ECR repo exists (idempotent).
|
| 27 |
+
aws ecr describe-repositories --repository-names "${REPO}" --region "${REGION}" >/dev/null 2>&1 \
|
| 28 |
+
|| aws ecr create-repository --repository-name "${REPO}" --region "${REGION}" >/dev/null
|
| 29 |
+
|
| 30 |
+
# 2. Log in to BOTH the DLC registry (to pull the base) and our own (to push).
|
| 31 |
+
aws ecr get-login-password --region "${REGION}" \
|
| 32 |
+
| docker login --username AWS --password-stdin "${DLC_ACCOUNT}.dkr.ecr.${REGION}.amazonaws.com"
|
| 33 |
+
aws ecr get-login-password --region "${REGION}" \
|
| 34 |
+
| docker login --username AWS --password-stdin "${REGISTRY}"
|
| 35 |
+
|
| 36 |
+
# 3. Build (cross-arch on Apple Silicon) + push.
|
| 37 |
+
docker build --platform linux/amd64 \
|
| 38 |
+
-f "${REPO_ROOT}/docker/Dockerfile.sagemaker" \
|
| 39 |
+
-t "${IMAGE}" "${REPO_ROOT}"
|
| 40 |
+
docker push "${IMAGE}"
|
| 41 |
+
|
| 42 |
+
echo "[ecr] pushed ${IMAGE}"
|
| 43 |
+
echo "[ecr] launch with: python examples/gsm8k_grpo/run_sagemaker_launch.py --image ${IMAGE}"
|