Two commands, real output

Know what a GPU job needs before it takes a reservation.

This is the second of the pre-check's four answers, what it needs, applied to jobs you already ran. The other three (what changed, whether the layout is legal and balanced, whether the config matches the reference) are on the home page.

One command reads a month of jobs and puts each request next to what the job needed. The other reads a CUDA out-of-memory error and says why. Below is what each printed, unedited, on a sample CSV and on a real public report.

1 · Requested vs needed, over past jobs

Six past jobs, card by card

$ npx neurarch rightsize jobs.sample.csv# GPU right-sizing: requested vs needed **6 jobs, 174 GPU-hours, $507.85 spent; $328.53 of that was reserved capacity no job needed, claiming nothing on 1 row marked under or unknown.** 4 over, 1 right-sized, 1 under, 0 unknown. | job | model | requested | batch | hours | needed (GB) | smallest card | verdict | right-sized request | spent | reserved, not needed | note ||---|---|---|---:|---:|---:|---|---|---|---:|---:|---|| ft-qwen-small | Qwen/Qwen3-0.6B | 8xH100 | 8 | 6 | 211 | none single | over (borderline) | 2xH200 | $189.60 | $129.60 | seq 2048; 282 GB holds the 210.5 GB the job needed; 8 cards to 2: the same work on fewer cards takes longer, so read the figure as capacity reserved and not needed; borderline: the upper bound (301.1 GB with headroom, 95% of held-out runs) does not fit 282 GB; measure once, so the not-needed figure is claimed only down to 4xA100-80, which holds the upper bound || lora-qwen-8b | Qwen/Qwen3-8B | A100-80 | 4 | 12 | 78 | A100-80 | right-sized (borderline) | as requested | $30.00 | - | seq 2048, LoRA r=16; borderline: the upper bound (112.0 GB with headroom, 95% of held-out runs) does not fit 80 GB; measure once || bert-classifier | bert-base-uncased | A100-80 | 32 | 3 | 8.4 | T4 | over | T4 | $7.50 | $5.73 | seq 512; 16 GB holds the 8.4 GB the job needed || tinyllama-lora | TinyLlama/TinyLlama-1.1B-Chat-v1.0 | H100 | 8 | 5 | 35 | A100-40 | over (borderline) | L40S | $19.75 | - | seq 2048, LoRA r=8; 48 GB holds the 34.7 GB the job needed; borderline: the upper bound (49.6 GB with headroom, 95% of held-out runs) does not fit 48 GB; measure once, so the not-needed figure is claimed only down to the request itself, which holds the upper bound || phi3-sft | microsoft/Phi-3-mini-4k-instruct | A100-40 | 8 | 10 | 230 | none single | under |  | $21.00 | - | seq 4096; predicted peak 200.3 GB x 1.15 does not fit 40 GB: the job OOMed or relied on something the graph does not declare || gpt2-pretrain | openai-community/gpt2 | 4xA100-80 | 16 | 24 | 29 | A100-40 | over | L40S | $240.00 | $193.20 | seq 1024; 48 GB holds the 29.2 GB the job needed; 4 cards to 1: the same work on fewer cards takes longer, so read the figure as capacity reserved and not needed |

Printed up to its Method section, which is folded at the end of this card.

6 jobs, 174 GPU-hours, $507.85 spent; $328.53 of that was reserved capacity no job needed, claiming nothing on 1 row marked under or unknown.

reserved$507.856 jobs, 174 GPU-hours at list price
needed$179.32the smallest request that holds each peak, same hours
reserved, not needed$328.53capacity held that no job used
  1. ft-qwen-small 8xH100 · 640 GB reserved · needed 211 GB · fits on 2xH200 (282 GB)
  2. lora-qwen-8b A100-80 · 80 GB reserved · needed 78 GB · right-sized as requested
  3. bert-classifier A100-80 · 80 GB reserved · needed 8.4 GB · fits on T4 (16 GB)
  4. tinyllama-lora H100 · 80 GB reserved · needed 35 GB · fits on L40S (48 GB)
  5. phi3-sft A100-40 · 40 GB reserved · needed 230 GB · did not fit what it asked for
  6. gpt2-pretrain 4xA100-80 · 320 GB reserved · needed 29 GB · fits on L40S (48 GB)
reserved (card GB x count) needed (predicted training peak x 1.15) needed more than it reserved GB, log scale
jobrequestedneeded GBverdictright-sized requestreservedreserved, not needed
ft-qwen-smallQwen/Qwen3-0.6B 8xH100batch 8 · 6 h · seq 2048 211 over 2xH200 $189.60 $129.60
282 GB holds the 210.5 GB the job needed; 8 cards to 2: the same work on fewer cards takes longer, so read the figure as capacity reserved and not needed; borderline: the upper bound (301.1 GB with headroom, 95% of held-out runs) does not fit 282 GB; measure once, so the not-needed figure is claimed only down to 4xA100-80, which holds the upper bound
lora-qwen-8bQwen/Qwen3-8B A100-80batch 4 · 12 h · seq 2048, LoRA r=16 78 right-sized as requested $30.00 -
borderline: the upper bound (112.0 GB with headroom, 95% of held-out runs) does not fit 80 GB; measure once
bert-classifierbert-base-uncased A100-80batch 32 · 3 h · seq 512 8.4 over T4 $7.50 $5.73
16 GB holds the 8.4 GB the job needed
tinyllama-loraTinyLlama/TinyLlama-1.1B-Chat-v1.0 H100batch 8 · 5 h · seq 2048, LoRA r=8 35 over L40S $19.75 -
48 GB holds the 34.7 GB the job needed; borderline: the upper bound (49.6 GB with headroom, 95% of held-out runs) does not fit 48 GB; measure once, so the not-needed figure is claimed only down to the request itself, which holds the upper bound
phi3-sftmicrosoft/Phi-3-mini-4k-instruct A100-40batch 8 · 10 h · seq 4096 230 under none claimed $21.00 -
predicted peak 200.3 GB x 1.15 does not fit 40 GB: the job OOMed or relied on something the graph does not declare
gpt2-pretrainopenai-community/gpt2 4xA100-80batch 16 · 24 h · seq 1024 29 over L40S $240.00 $193.20
48 GB holds the 29.2 GB the job needed; 4 cards to 1: the same work on fewer cards takes longer, so read the figure as capacity reserved and not needed

phi3-sft needed more than it reserved, so it is counted under "needed" at what it reserved and nothing is claimed on it. The not-needed figure assumes the same wall hours: a smaller card can run longer, so it is reserved capacity, not a promise of a cheaper run.

Method, prices, and what it does not know

Needed memory is the training peak buildPlan predicts from the graph: weights, gradients and optimizer state (1.2 x 16 bytes per parameter) plus activations (fitted factors on every layer's output, the intermediates inside each layer, recurrent gate states, the largest tensor's backward copy, and stored attention scores), with 1.15x headroom for allocator fragmentation and the CUDA context. It estimates the training peak under AdamW with bf16 autocast; inference jobs and other optimizers will differ. Six factors fitted on 178 measured runs across 41 architectures, out-of-memory runs included, with the attention-score term only where the run stored scores; held out one architecture at a time, the median error is 7% above 1 GB and it flagged 113 of the 131 times a run did not fit a card; on 25 runs at the edge of a 24 GB card measured after the fit, the median miss is 14%. (every run: https://www.neurarch.com/memory-calibration.html). The score term counts only the attention score matrices eager attention stores for backward; PyTorch SDPA (the Hugging Face default) and flash attention do not keep them, so the plan, like every other plan, leaves that term out unless the CSV's attention column says eager. Token models are sized at the CSV's seq_len, or 2048 tokens (the model's context if shorter) when it is blank. A job with lora_rank is sized with the base frozen in bf16 (2 bytes per parameter) and adapters on every Linear but the head carrying AdamW state. A multi-card job is sized as one model sharded across its cards (FSDP / ZeRO-3 style): the peak at the global batch (per-device batch x cards) against the summed capacity. Plain DDP keeps a full copy on every card, so for a DDP job read the single-card peak at the per-device batch instead. A request is right-sized when it is the smallest capacity that holds the peak or one size up; the right-sized request is the cheapest single card that holds it, and several cards only when no single card does. The not-needed figure assumes the same wall hours: a slower card can lengthen a run, so the figure is reservation capacity paid for and not needed, not a promise about a cheaper run. Every verdict is also checked against the conservative peak, the estimate x 1.43 (held out by architecture, 95% of measured runs of 1 GB or more peaked at or under it): a row whose capacity holds the estimate and not the conservative peak is marked borderline ("measure once"), and an over row that is borderline claims as not needed only what the conservative peak would free.

Prices: on-demand list price per GPU-hour from Modal (modal.com/pricing, verified 2026-08-19), the same table the Neurarch app prices training with. Pass --prices prices.json to use your own rates.

cardGB$/GPU-hour
T4160.59
L4240.80
A10G241.10
A100-40402.10
L40S481.95
A100-80802.50
H100803.95
H2001414.54

What it does not know

  • Data loader memory: pinned host buffers, prefetch queues and worker processes on the GPU host.
  • Custom kernels beyond the attention column: fused optimizers, fused losses and other custom ops can add or remove memory it does not see.
  • Activation checkpointing, unless the graph declares it: a checkpointed job needs less than this predicts.
  • Multi-node communication buffers (NCCL, all-gather and reduce-scatter staging) and pipeline bubbles.
  • Throughput: whether the job asked for more cards to finish sooner. This is a memory verdict, not a speed one.
  • Inference, evaluation and non-AdamW optimizers: the peak here is training under AdamW.
  • Output logits upcast to fp32, and per-token entropy or log-probabilities over a large vocabulary (as TRL computes them): batch x tokens x vocabulary x 4 bytes, which on a 150k-token vocabulary can be several GB on its own.

Derived, not measured: nothing in this report was run. Input: packages/neurarch-cli/examples/jobs.sample.csv, captured 2026-10-18 with neurarch 0.2.0.

2 · Why a job ran out of memory

A real report: flairNLP/flair#2364

"How much CUDA memory is needed to train a new NER tagger?" A NER tagger fine-tuning xlm-roberta-large on a NVIDIA GeForce GTX 1050 Ti (4 GB). The error, verbatim from the issue:

RuntimeError: CUDA out of memory. Tried to allocate 16.00 MiB (GPU 0; 4.00 GiB total capacity; 3.08 GiB already allocated; 0 bytes free; 3.14 GiB reserved in total by PyTorch)

Stated in the issue

  • model xlm-roberta-large, fine_tune=True (read as FacebookAI/xlm-roberta-large)
  • optimizer torch.optim.AdamW
  • batch mini_batch_size=4, mini_batch_chunk_size=1, so 1 sentence per backward pass: --batch 1
  • card GTX 1050 Ti, 4.00 GiB (also in the message)

Assumed by us

  • sequence length 128 tokens, and 512 (the model maximum) as a check; the issue does not state it
$ npx neurarch oom error.txt --model FacebookAI/xlm-roberta-large --batch 1 --seq-len 128cause    too-big-for-card The job is bigger than this card: the predicted peak is 10.8 GB (12.5 GB with headroom) on a 4.29 GB card. read from  - the failing allocation asked for 0.02 GB  - the card has 4.29 GB  - 3.31 GB was held by live tensors  - 0.06 GB was reserved by PyTorch but held no tensor  - the plan predicts a 10.8 GB training peak at this batch fixes, least invasive first  1. shard the optimizer state and gradients across cards (FSDP or ZeRO), or use an 8-bit optimizer     weights, gradients and optimizer state alone are 10.7 GB, more than half the card  2. train adapters (LoRA) on a frozen base instead of every weight  (changes what is trained)     the optimizer state then covers the adapters only

too-big-for-card The job is bigger than this card: the predicted peak is 10.8 GB (12.5 GB with headroom) on a 4.29 GB card.

weights, gradients, AdamW state: 10.7 GB activations at batch 1: 0.11 GB 1.15x headroom: 12.5 GB the card: 4.29 GB (4.00 GiB) held by live tensors when it failed: 3.31 GB GB

Evidence, read from the message and the plan

  • the failing allocation asked for 0.02 GB
  • the card has 4.29 GB
  • 3.31 GB was held by live tensors
  • 0.06 GB was reserved by PyTorch but held no tensor
  • the plan predicts a 10.8 GB training peak at this batch

Fixes, least invasive first

  1. shard the optimizer state and gradients across cards (FSDP or ZeRO), or use an 8-bit optimizerweights, gradients and optimizer state alone are 10.7 GB, more than half the card
  2. train adapters (LoRA) on a frozen base instead of every weight changes what is trainedthe optimizer state then covers the adapters only

The sequence length is the one input we assumed. At 512 tokens the predicted peak is 11.2 GB and the cause is the same (too-big-for-card). Weights, gradients and optimizer state alone are 10.7 GB on a 4.29 GB card, so no batch size or sequence length makes this fit, and the answer to "do I have to purchase a better one?" is a card with at least 12.5 GB, or one of the fixes above. Captured 2026-10-18 with neurarch 0.2.0.

3 · Run it on your own jobs

One CSV row per past job

job_id,model,gpu,batch,hours,seq_len,lora_rank
$ npx neurarch rightsize jobs.csv
$ npx neurarch oom error.txt --model <hf-id> --batch N --seq-len T

No account, no weights downloaded, no GPU: each model is read from its Hugging Face config.json. Nothing is uploaded.

Running a GPU cluster? A 30-day report: send us your last month of jobs and we show what each one asked for against what it needed, card by card, for a flat $3,000 credited toward a subscription.

Email us