Fine-tuning episode: cost, capacity and setup
Last updated on 2026-10-21 | Edit this page
Planning notes for running the Fine-tuning a Vision-Language
Model with Training Jobs episode (Badger Scribe, CHURRO-3B LoRA)
with a class of about 50 in a 2-hour slot (3 with the bonus exercises)
(quotas below are sized for 100). Prices are on-demand SageMaker rates
from the AWS price list published 2026-10-16 (us-east-1 and
the class account’s us-east-2 are the same for these
instance types); re-check them before the term, since
ml.g5.xlarge has already moved from $1.21 to
$1.41 since the RAG episodes were written.
The in-class design
In class, every student runs one LoRA fine-tuning job of about 33
minutes: it scores 15 held-out German-letter (kade_letters)
pages with the base model, trains on all 251 training pages (295 minus a
44-page holdout) for 2 passes, scores 8 holdout pages after each pass
for a curve, scores the same 15 pages again, and saves the adapter. The
lesson ships only the German letters (295 train, 141 test pages) because
every label is human-transcribed (the competition’s survey and
account-book labels are mostly machine drafts), because they are most of
the hidden test set, and because the scans are smaller. Before/after
lives inside that one job, so there are no separate inference jobs
during class. The full run, the Processing jobs over the test set and
the optional Kaggle submission are homework.
Launches use wait=True and an explicit job name with the
launch time in it
(badger-<user>-churro-lora-2ep-<MMDD-HHMM>), so
a rerun gets a new name instead of failing with
ResourceInUse; wait=True is the double-launch
guard, since the cell blocks until the job ends. Interrupting the cell
does not stop the job; the episode says so in a callout, and you should
say it out loud.
What one student spends
| Item | Instance | Billable time | Cost |
|---|---|---|---|
| Controller notebook (CPU) |
ml.t3.medium ($0.05/h) |
3 h | $0.15 |
| In-class LoRA fine-tune (251 German-letter pages × 2 passes, 15 pages scored before and after, 8-page curve) |
ml.g5.xlarge ($1.41/h) |
~32 min | $0.75 |
| In class | ~0.5 GPU-hours | ~$0.90 |
|
| Bonus Exercise 1, if anyone does it: the class job at 2 megapixels | ml.g5.xlarge |
~60 min | $1.40 |
| Homework: four-pass LoRA run (251 pages × 4 passes, 40 pages scored before and after) | ml.g5.xlarge |
~65 min | $1.50 |
| Homework: base and LoRA fine-tuned over holdout + test (~185 pages, two Processing jobs) | ml.g5.xlarge |
~15 min each | $0.70 |
| In class + bonus + homework | ~3.1 GPU-hours | ~$4.50 |
Assumptions behind the training lines: CHURRO-3B in bf16 with LoRA
rank 16, gradient checkpointing, one page per step,
max_pixels of 1280 × 28 × 28 (about one megapixel, ~1,300
visual tokens plus ~300 text tokens per page), and, measured on the
class job on 2026-10-21, 1.45 s per page-pass of training, 17–21 s per
page for greedy generation on an A10G, about 5 minutes of billed startup
(container pull, ~130 MB of images, 7.5 GB of weights copied from the
shared bucket as a model channel) and under a minute of
unbilled provisioning. The 2-megapixel and homework rows are scaled from
those rates. Every job is capped (max_run of 65 min on the
class job, 130 min on the homework run, 45 min on each Processing job),
so the worst case from the scripted steps is about $7 per
student.
What the class spends
| Scenario | Per student | 50 students (double for 100) |
|---|---|---|
| In class, one clean pass each | $0.90 |
$45 |
| In class, realistic (one extra relaunch each) | ~$1.65
|
~$85
|
In class, two launches each at the max_run cap |
~$3.20
|
~$160
|
| Homework, if everyone does it | ~$2.20
|
~$110
|
| Budget to request (realistic class + homework + 50%) | $300 |
For comparison, teaching the same material on
ml.g5.xlarge notebook instances costs about the
same during class ($1.41 × 3 h × 50 = $212)
but adds the forgotten-instance risk: each one left running costs
$34/day, $237/week, $1,029/month.
Two forgotten instances over a long weekend exceed the entire jobs-based
budget above. That difference is the point of the episode, and it is
worth saying out loud to the class.
Suggested timeline (3 h)
| Time | What |
|---|---|
| 0:00–0:30 | Why fine-tune; the cost table; setup cells (CSV download, token counting, guard rails) |
| 0:30–0:40 | Everyone launches the class job (wait=True) |
| 0:40–1:15 | While it runs (~33 min), in the console: job page,
max_run, tags, CloudWatch log, the checkpoint folder in S3;
forgotten-instance arithmetic; quotas |
| 1:15–1:45 | Step 2: bill vs. estimate, before/after CER, the curves, a page side by side; the holdout callouts |
| 1:45–2:00 | Step 3 totals; walk through the homework section; cleanup |
The bonus exercises are not scheduled. With a third hour, Exercise 1 (the class job at 2 megapixels) is about an hour of GPU time, so launch it first and discuss Exercise 2 while it runs.
If the jobs are still queueing at 0:50 (capacity), teach the console and the cost arithmetic from your own job’s page, run before class.
Things that will go wrong unless arranged ahead of time
Service quotas. This is the item most likely to sink the session. In Service Quotas → Amazon SageMaker, per region:
-
ml.g5.xlarge for training job usage: at least the number of students launching at once. Request 100. -
ml.g5.xlarge for processing job usage: only needed for the homework, where each student launches two jobs at once. Request 200. -
Maximum number of instances across all training jobs: check it is not lower than the above. - If you want
ml.g6e.xlargefor Exercise 2, request 100 of those too.
New accounts often have these at 0 to 4. Quota increases for GPU instances can take days and sometimes a back-and-forth with support, so file them three to four weeks before class.
Capacity. Fifty ml.g5.xlarge launches
within the same minute in one region can fail with
InsufficientCapacity even when the quota allows it.
Mitigations, in order: launch the class jobs in waves (table by table),
and have ml.g6.xlarge (L4, 24 GB, $1.13/h;
somewhat slower on this workload, with half the memory bandwidth, but
still well within max_run) ready as a documented fallback
instance type.
Model download. The base weights
(stanford-oval/churro-3B, ~7.5 GB) are not pulled
from Hugging Face by the jobs. They are staged once in the shared bucket
under badger-scribe/model/churro-3B/ (step on the
Shared data bucket page) and every estimator passes that prefix
as a model channel; the Processing jobs mount the same
prefix and pass it as --model_id. SageMaker copies it from
S3 in ~1–2 minutes per job, and a hundred jobs starting in the same
minute hit S3, which is built for that, rather than one Hugging Face
repository. The scripts fall back to the Hub only when no
model channel is given (self-paced learners who skipped the
staging step), in which case the instance needs internet access (default
outside a VPC). The CHURRO weights carry the Qwen research license
(non-commercial), which is fine for a class.
Warm pools.
keep_alive_period_in_seconds on the estimator keeps the
instance for the next job, which would let a relaunch skip the startup
overhead. It needs its own quota
(ml.g5.xlarge for training warm pool usage) and it bills
for the idle time at the full rate, so for a class it adds a way to
leave meters running. Leave it off.
Before class
1. Stage the data in the shared read-only bucket
Students do not create a bucket or upload anything for this episode.
The data lives as a release on the lesson repository
(badger-scribe-data-v1, ~130 MB) and is staged once per
term under badger-scribe/ in the shared bucket, next to the
WattBot data. The CHURRO-3B weights (~7.5 GB) are staged once under
badger-scribe/model/churro-3B/ in the same bucket, from a
laptop or a notebook instance rather than CloudShell. The bucket policy,
layout and exact commands are on the Shared data bucket: staging
before each term page.
2. The rest
-
Before anything launches, check that the container the episode asks for exists in the class region (a wrong version combination fails at
fit()with a confusing error): Run the mini job once too, so you have seen the log students will see and know where
max_runlands.Set a Budgets alert on the class account at the planned total, and a Cost Explorer filter on the
Projecttag students use inmake_job_tags.Have the fallback instance type on a slide:
instance_type = "ml.g6.xlarge".Tell students to stop the controller notebook at the end. It is five cents an hour, which is why nobody notices it.