The situation
A recommender-system team trains nightly on a single ml.p4d.24xlarge (8 × A100). On-demand price in eu-west-1 is around $33/hour, so a four-hour job is ~$132 per night, ~$3,960/month.
Characteristics that make this job a Spot candidate:
- Idempotent training: the same seed and hyperparameters produce the same ModelA trained set of weights plus the architecture that makes them useful – the thing you load up and run inference against. .
- Checkpointing every 30 min: the training script writes full model + optimizer state to
/opt/ml/checkpoints/every 30 minutes. - Deadline is morning standup: if the job finishes by 08:00 UTC, nobody cares if it took four hours or eight.
- One job, not many concurrent: no fleet-diversification tricks needed.
Characteristics that make it tricky:
- p4d.24xlarge capacity is variable on Spot. Some nights it’s easily available; other nights it’s not, and the job waits or fails.
- Interruption mid-training loses up to 30 minutes of work (since the last checkpoint).
- Checkpoint resume logic has to actually work. If the team hasn’t tested it, the first interruption exposes whichever bug has been hiding.
The question is whether Spot is the correct tool for this job, how to wire it up so the risks are manageable, and what the “ideal scenario” discount looks like against the “bad night” fallback cost.
What actually matters
Before picking a pricing model, it’s worth being precise about the properties that govern whether a training job is a candidate for opportunistic capacity at all.
The first thing worth thinking about is interruption tolerance. The deepest discounts come from capacity the cloud provider can reclaim with little notice. A training job can take advantage of that pricing only if “stop the job halfway through and start it again later” is acceptable in the worst case. That hinges on two sub-properties: whether the job’s progress is persistable (model + optimizer state can be serialised to durable storage at intervals) and whether the resume path actually works (the same code that wrote the checkpoint can read it back and pick up training without restarting from scratch). Without both, the discount isn’t on offer; with both, it is.
The second is the deadline shape. There’s a difference between “must finish by 07:00 sharp” and “done by the time the team looks at it in the morning.” Discounted capacity is variable: some nights it’s there immediately, some nights the job waits. The job’s tolerance for that variability is what determines whether the discount is real or whether it’s papered over by frantic on-demand retries. A loose deadline (“by standup”) is a Spot candidate; an SLA-bound model serve at a fixed hour isn’t.
The third is the depth of the capacity pool. Not all instance shapes have equally deep Spot pools. The newest, most-fought-over GPU types are exactly the ones whose Spot availability fluctuates the most night to night. The discount only materialises if capacity is available; if it isn’t, the job either waits or fails. The right way to think about this is “the chosen instance type has a probability distribution of capacity, and the cost model has to account for the bad tail”, not “Spot is 70% off.” Diversifying across instance types, or having a fallback to on-demand, is how the bad tail gets handled.
The fourth is the cost of a failure. If tonight’s training fails because Spot never came, what’s the consequence? For a nightly model that retrains incrementally, the consequence is “yesterday’s model is still in production”, mild, recoverable, the next night’s job catches up. For a one-shot training run that’s a deadline-driven hand-off, the consequence is “the deliverable is late”, expensive, not recoverable by the same retry. The size of the failure-cost determines how much fallback engineering is worth.
The fifth is the absolute size of the bill. A 70% discount on a $4,000/month workload is worth a week of engineering on checkpointing and orchestration. A 70% discount on a $50/month workload isn’t. The discount percentage is the same; the dollars saved are different by two orders of magnitude. “Could this be cheaper?” is the wrong frame; what matters is whether the saving is worth the engineering effort to capture it, and to maintain it as the workload evolves.
The sixth is the checkpoint-interval trade-off. Checkpoints are themselves expensive: writing tens of GB to S3 pauses training and costs both time and money. Checkpoint too rarely and interruptions lose a lot of work; checkpoint too often and steady-state throughput drops. The sweet spot is “worst-case loss is tolerable, and the checkpoint overhead is well under the discount.” For a four-hour job, every 30 minutes is a common starting point; the right value depends on the cost of writing the checkpoint and the cost of redoing the lost work.
The seventh is who manages the lifecycle. Spot capacity acquisition, interruption notice handling, and retry logic are non-trivial pieces of infrastructure. Some managed services wrap all of that and let the team focus on training code; others leave the team to wire it together themselves. The trade-off is flexibility versus engineering load: a managed offering takes choice away (instance types, retry strategy) in exchange for less code to maintain.
Two risks worth thinking about explicitly
Risk one: capacity isn’t there. Some instance types have deep, stable pools of interruptible capacity; the newest, most-fought-over GPU types don’t. If capacity isn’t there for the wait window the job is configured to tolerate, the job fails. Fallback strategies:
- Broaden the instance family list. Allow more than one instance type so the platform can fulfil against whichever is available. (Training jobs don’t natively support instance-type lists the way fleets do; this is usually handled at orchestration level, a state machine that retries across types.)
- Fall back to on-demand after N retries. Orchestrator watches for capacity failures and retries the same training configuration without interruptible capacity after a wait threshold.
Risk two: interruption loses progress. Interruption is part of the deal. The cost is the work between last-checkpoint and interruption time, up to the checkpoint interval. The fix is making that interval small enough that the loss is acceptable. For a four-hour job, checkpointing every 30 minutes means worst-case loss is one epoch; checkpointing every 10 minutes means worst-case loss is 20 minutes of compute. Trade-off: checkpointing is itself expensive (object-store writes of tens of GB) and pauses training.
What we’ll filter on
Five filters for “is Spot correct for this training job”:
- Can the training be interrupted and resumed?, checkpointing is the prerequisite; without it, Spot is not viable.
- What’s the deadline?, if the job must finish in exactly 4 hours, Spot’s variability breaks that.
- How deep is the Spot pool for the instance type?, some GPU instances are nearly always available; others aren’t.
- What’s the cost of a failure?, can the pipeline retry the next night if capacity is bad tonight?
- How much discount justifies the extra engineering?, 70% on a $3,960/month bill is worth the wiring; 70% on a $50/month bill isn’t.
The Spot-training landscape
1. SageMaker managed Spot training. EnableManagedSpotTraining: true on the training job; CheckpointConfig pointing at S3. SageMaker handles capacity acquisition, interruption, retry. The primary answer.
2. SageMaker on-demand (status quo). The full price; full guarantee. The fallback for jobs that can’t tolerate Spot.
3. EC2 Spot + self-managed training. Orchestrate Spot instances yourself via ASG or EC2 Fleet; run training on them without SageMaker. More flexible; more engineering; loses SageMaker’s resume-from-checkpoint automation.
4. SageMaker HyperPod. A newer SageMaker offering: reserved, persistent compute clusters for distributed training, with auto-healing and checkpoint restoration. For jobs that run constantly (foundation model pre-training), not nightly four-hour runs. Mentioned for completeness.
5. Batch with Spot. AWS Batch can schedule training jobs on Spot-backed compute environments. Relevant when the team’s workflow is already in Batch; SageMaker’s integration is smoother for pure ML workloads.
6. Savings Plans + on-demand. A commitment-based discount path orthogonal to Spot. Compute Savings Plans give roughly a 30-50% discount on SageMaker training with no interruption risk, in exchange for a dollar-per-hour commitment. A complement to Spot, not a replacement.
Side by side
| Option | Discount | Interruption risk | Capacity availability | Extra engineering |
|---|---|---|---|---|
| Managed Spot training | Up to ~70% | Yes, managed retry | Depends on type | Checkpoint + resume logic |
| On-demand | 0% | None | Guaranteed | None |
| EC2 Spot + self-managed | Up to ~90% | Yes, you manage | Depends on type | Full orchestration |
| HyperPod | Reserved capacity | None | Reserved | Cluster provisioning |
| Batch on Spot | Up to ~70% | Yes, Batch handles | Depends on type | Batch job definition |
| Savings Plans | ~30-50% | None | On-demand backed | Commitment management |
Reading the table against the recommender scenario: managed Spot training is the fit. The team doesn’t want to write its own Spot orchestration (EC2 Spot + self-managed); it doesn’t have constant workloads (HyperPod); it already uses SageMaker training (not Batch). Savings Plans can layer underneath, commit to a lower dollar-per-hour, use Spot on top for the extra discount.
How a Spot night actually plays out
The pick in depth
Managed Spot training via the SDK:
from sagemaker.pytorch import PyTorch
estimator = PyTorch(
entry_point="train.py",
source_dir="src",
framework_version="2.3",
py_version="py310",
instance_type="ml.p4d.24xlarge",
instance_count=1,
role=role,
# Spot configuration
use_spot_instances=True,
max_run=4 * 60 * 60, # 4h max training time
max_wait=8 * 60 * 60, # up to 8h total including Spot waits
checkpoint_s3_uri="s3://bucket/training-checkpoints/recommender/",
checkpoint_local_path="/opt/ml/checkpoints/",
# Hyperparameters, etc.
hyperparameters={"epochs": 20, "batch-size": 512, "lr": 1e-4},
)
estimator.fit({"train": "s3://data/train", "val": "s3://data/val"})
Key knobs:
use_spot_instances=Trueturns on managed Spot.max_runbounds training time. If training takes longer than this post-start, the job fails (not Spot-specific; helps catch runaway training).max_waitis the outer bound including Spot-capacity waits. Must be ≥max_run.checkpoint_s3_uriis where SageMaker syncs the contents of/opt/ml/checkpoints/at intervals while training runs, and where it restores from at restart. The training script writes tocheckpoint_local_path; SageMaker handles the S3 sync.
Training code (pseudocode):
import os, glob, torch
CHECKPOINT_DIR = "/opt/ml/checkpoints"
def find_latest_checkpoint():
checkpoints = sorted(glob.glob(f"{CHECKPOINT_DIR}/ckpt-*.pt"))
return checkpoints[-1] if checkpoints else None
def save_checkpoint(epoch, model, optimizer):
torch.save({
"epoch": epoch,
"model_state": model.state_dict(),
"optimizer_state": optimizer.state_dict(),
}, f"{CHECKPOINT_DIR}/ckpt-{epoch:04d}.pt")
model = build_model()
optimizer = torch.optim.AdamW(model.parameters(), lr=args.lr)
start_epoch = 0
latest = find_latest_checkpoint()
if latest:
ckpt = torch.load(latest)
model.load_state_dict(ckpt["model_state"])
optimizer.load_state_dict(ckpt["optimizer_state"])
start_epoch = ckpt["epoch"] + 1
print(f"Resumed from {latest}, starting epoch {start_epoch}")
for epoch in range(start_epoch, args.epochs):
train_one_epoch(model, optimizer, train_loader)
validate(model, val_loader)
save_checkpoint(epoch, model, optimizer)
Three lines doing the work:
find_latest_checkpoint()at startup, without this, a restart starts from scratch.save_checkpointevery epoch (or more often), without this, restart loses a lot.- Loading
start_epoch = ckpt["epoch"] + 1, without this, restart repeats completed epochs.
Test the resume path before shipping. Interruption testing is the bit teams skip. A simple check: stop the job mid-training, restart with the same checkpoint S3 path, verify training picks up at the correct epoch and the loss trajectory matches what it would have been.
Fallback orchestration. A Step Functions state machine (or an EventBridge-plus-Lambda) that wraps CreateTrainingJob can implement a “Spot, else on-demand” retry:
Try:
CreateTrainingJob(... EnableManagedSpotTraining=True, max_wait=4h ...)
Catch TrainingJobFailed (capacity):
CreateTrainingJob(... EnableManagedSpotTraining=False ...)
Runs Spot by default; if after 4 hours of waiting the capacity hasn’t come, retries on-demand. Worst-case cost is on-demand; average case is Spot’s 70% discount.
When Spot is not the correct answer. When the training has a hard deadline (a production model must be ready at 07:00 sharp). When the Spot pool for the target instance type is thin (rare GPU types, niche Regions). When the training job is short (a 20-minute job’s Spot savings don’t justify the infrastructure setup). When the team has no checkpoint-and-resume logic and doesn’t want to write it.
A worked month
Baseline (on-demand): 30 nights × 4h × $33/h = ~$3,960.
Spot (ideal, no interruptions): 30 nights × 4h × $10/h = ~$1,200.
Spot (realistic, ~3 interruption nights with 45min retry + 20min redo each): 27 × 4h × $10 + 3 × 4h 20min × $10 = $1,080 + $130 = ~$1,210.
Spot (with 2 nights falling back to on-demand because capacity never came): 28 × 4h × $10 + 2 × 4h × $33 = $1,120 + $264 = ~$1,384.
Even in the pessimistic case, the monthly bill is roughly a third of the on-demand baseline, ~$2,500 of savings per month, for a few days of engineering on the checkpoint logic.
What’s worth remembering
- Managed Spot training is a training-job flag.
EnableManagedSpotTraining: trueplusCheckpointConfig. SageMaker handles capacity, interruption, and retry; you handle the resume-from-checkpoint logic in training code. - Discount is ~70% off on-demand. Comparable to EC2 Spot; no extra discount, no extra penalty.
- Checkpointing is the prerequisite. Write model + optimizer state to
/opt/ml/checkpoints/at an interval where worst-case loss is acceptable. 30 minutes is a common default for multi-hour jobs. - Training code must read the checkpoint at startup. The code that saves checkpoints is easy; the code that resumes from them is the bit that breaks in production.
max_waitbounds total job duration including wait time. Larger thanmax_run; if capacity never comes withinmax_wait, the job fails.- Build a fallback to on-demand. Orchestrator retries with
EnableManagedSpotTraining: falsewhen Spot capacity is persistently unavailable. Keeps the nightly training SLA intact on bad nights. - Test the resume path. Deliberately stop a job mid-training; verify restart picks up at the correct place. Interruption testing is the part teams skip and the part that bites.
- Spot compounds with Savings Plans. Commit a dollar-per-hour via a Compute Savings Plan for the on-demand fallback; run Spot on top. Belts and braces.
Running training “on somebody else’s schedule”, when AWS decides the instance is available, and on its terms if it has to reclaim the box, is the deal Spot offers. For jobs that don’t need exact timing and can checkpoint cleanly, the deal is extraordinary value. For jobs that need the opposite of both, on-demand (or a Savings Plan) is the honest answer. Pairing each training job with the pricing model that matches its rhythm is the same work that happens everywhere in AWS pricing, and the ML training specifically rewards it because the jobs are expensive and the discount is deep.