Writing / 6 min / on SkinGuard AI

Fitting 589 million parameters into seven hours

SkinGuard's training run had one H100 and a hard stop. How the budget shaped the config.

SkinGuard AI is an ensemble of three vision backbones for classifying skin lesions on ISIC 2019: EVA-02-Large at 304M parameters, ConvNeXt-V2-Large at 198M and Swin-V2-Base at 87M. Everything had to happen inside one seven-hour session on a single H100: download, training, evaluation, test-time augmentation, Grad-CAM and ONNX export. The budget decided more of the config than the architecture did.

Start with the arithmetic

The config asks for 40 epochs at 384×384 over a dataset of 25,331 images. If all 40 ran and all seven hours went to training, each epoch would get about 630 seconds, which means roughly 40 images per second through all three backbones. That is a rough ceiling, not a measurement, since it ignores validation and the images held out of training. It does show that nothing could be wasted.

image_size: 384
epochs: 40
batch_size: 16
gradient_accumulation_steps: 16   # effective batch = 256
num_workers: 16
prefetch_factor: 4
time_budget_hours: 7.0
From configs/config.yaml

Spending the GPU well

  • BF16 autocast, which is lighter on memory than full precision and does not need the loss scaling FP16 does.
  • torch.compile in reduce-overhead mode, TF32 matmuls, channels-last memory format and cuDNN benchmark mode.
  • A physical batch of 16 with 16 accumulation steps, for an effective batch of 256 without holding 256 images at 384×384 in memory.
  • Sixteen dataloader workers with pinned memory and a prefetch factor of four, so the GPU is not left waiting on JPEG decoding.

Freeze first

The first five epochs train only the fusion layer and classifier head, with all three backbones frozen. After that the backbones unfreeze at a tenth of the head's learning rate (1e-4 for the head). This helps the budget twice: frozen epochs are cheap, and a randomly initialised head cannot send large gradients into pretrained weights while it is still finding its feet.

The stop condition is the safety decision

Melanocytic nevi outnumber melanoma by roughly fifteen to one, so a model can score well on accuracy by learning to say benign. Focal loss with gamma 2.0, inverse-frequency class weights, MixUp and CutMix all push against that. The setting that matters most is smaller: early stopping watches validation AUROC with a patience of eight epochs, not accuracy. In a run with a hard budget, the stopping metric decides which checkpoint you keep. Accuracy would have kept the wrong one.

Assume the run will be killed

The trainer has a time watchdog that saves and exits before the seven-hour limit, plus a checkpoint every five epochs. On rented hardware, a run that gets cut off without saving costs you the whole session. Stopping early with a saved model costs a few epochs.

What this post cannot tell you

The documentation has drifted as well: the README says four accumulation steps while the config says sixteen. Both reach an effective batch of 256 and the config is what actually runs, but it is the kind of small mismatch that makes a reader trust everything else a bit less. Publishing a measured evaluation and fixing that line are the next two things this project needs.

SkinGuard AI case study

Search only: every answer quotes this site and links its source. Nothing is generated.