A KiTS segmentation model takes an abdominal CT scan and returns a mask of each kidney and any tumor. It is accurate, but running one is not cheap: a single scan takes a few minutes of mixed CPU and GPU work, which at any real volume of patients adds up to meaningful energy and carbon. This is a walk through where that cost actually goes for one case, and then how far a sequence of inference‑side changes — none of which retrain the model — can bring it down. Unless noted, every figure is a mean per case, measured on an RTX 4090 over the 189 held‑out cases (00400–00588).
A case moves CPU → GPU → CPU, and the colour of each box marks which device does the work. This is the stock configuration — call it B0 — with the mean time each step takes.
Read + gunzip the .nii.gz, parse the NIfTI header & voxels into RAM.
Crop to non‑zero → resample to 0.78 mm isotropic (order‑3 spline, separate‑Z) → normalize (CT clip + z‑score). Single‑threaded scipy.
Sliding window: tile into fixed patches → forward pass (fp32) → Gaussian‑weighted accumulate softmax → ×8 TTA mirror flips (8 passes per patch).
Softmax GPU→CPU → argmax to labels → resample back to original spacing → write .nii.gz (SimpleITK gzip).
End to end the baseline takes about 223 seconds per case, and the GPU prediction step is the largest single slice. The rest of this piece follows what happens to that budget — in both time and energy — as the configuration changes.
Each rung is a configuration change, noted on the left. The bars are the mean per‑case stage split, scaled to B0’s 223 s; at the right of each is that config’s mean seconds per case and, below it, its mean prediction energy (kJ per case, from the GPU‑locked NVML counter). The prediction segment is the cleanest signal to follow — it falls from 138 s to about 5 s down the ladder, and the energy falls with it. (CPU preprocess and export times vary from one pod to another, since each config ran on its own machine, so the prediction segment is the more reliable trend. All figures are means over the 189 cases.)
argmax picks the winning label per voxel, so a tiny interpolation difference almost never flips the result. (Doing the same on the input side is not free — the resampled image feeds the whole network, and torch’s trilinear ≠ the scipy cubic‑spline nnU‑Net was trained on, so the masks change. That’s the separate “research” rung.)
B0 is GPU‑bound
of B0’s 223 s is the predict stage (fp32 + 8‑pass TTA).
O6 is CPU‑bound
once predict shrinks, preprocess dominates the 47 s.
Predict energy (mean)
B0 45.1 → O6 3.07 kJ/case; carbon 4.93 → 0.34 gCO₂. 4090, n=189.
Parallel producers measured
3 workers preprocess at once: −30 % total energy, Dice‑identical. L40S, 10 cases.
torch.compile — make little difference here, because they optimise a step that is no longer the bottleneck.
The ladder ends at O6. These are the next rungs and what each one changes.
cudnn.benchmark + TF32. Free but marginal (~4 %). torch.compile was dropped — 2× slower on the sliding window.Mean per case over 189 held‑out cases (00400–00588), RTX 4090, NVML energy. Bars are stage split (preprocess / predict / export); totals are sum‑of‑stages. The O6 + parallel lever is measured (Modal L40S, 10 cases: −30 % total energy, 2.1× throughput, Dice‑identical); its dashed bar projects that effect onto this 4090 ladder. Preprocess is a single‑thread scipy resample.