LLM + RoboticsModule 09 · Problems 11–12

Quantisation and choosing GPUs

Two problems that are really one: how many bytes each weight takes, and how fast a GPU can move those bytes. Pick a number format, prove it didn't break the model, then pick the hardware with arithmetic instead of brand loyalty.

Problems covered11 (FP8 vs INT8 vs INT4 vs NVFP4, and measuring the loss), 12 (H100 vs H200 vs B200 vs L40S)
Interview levelMid to senior. Senior candidates are expected to bring an evaluation protocol and a cost model.
Key ideasBytes per weight · scale granularity · outliers · weight-only vs weights-and-activations · eval noise · bandwidth per dollar · memory fit and max batch
Time to prep3–4 h: 1 h formats, 1 h evaluation, 1–2 h GPU arithmetic and the demo

Source: Vishal Singh's post listing 25 inference-engineering interview problems · saved in sources/09-vishal-inference-25.md

Start here (no maths needed)

A model is billions of numbers. Writing each number with fewer digits makes the model smaller and faster, as long as you check it still gives good answers. Then you pick the computer whose memory is big enough and fast enough for the job.

Think of it like this

Imagine moving house with a delivery van. Your stuff is the model. You can pack each item in a big padded box (16-bit numbers), a smaller box (8-bit) or squash it into a tiny pouch (4-bit). Smaller packing means fewer trips and a smaller van, but squash a lamp too hard and it breaks. Clever packers put a label on every small bundle saying how big the things inside really are, so they unpack correctly. Choosing the van is the second question: a bigger van holds more, a faster van finishes sooner, and a cheap van might be fine if your stuff is small.

How it works

  1. Each weight is normally stored as a 16-bit number. Quantisation stores it in 8 or 4 bits instead.
  2. A "scale" number stored next to a group of weights says how to stretch the small numbers back to their real size.
  3. Smaller groups, each with its own scale, make fewer mistakes but cost a little extra memory.
  4. You test the squashed model against the original on the same questions, and accept it only if the drop is smaller than the normal noise.
  5. Then you check which GPU can hold the model plus its notes (the KV cache) and read it fast enough.

Why robots care

A robot's computer has a small memory and a slow memory bus compared with a data-centre GPU. A model that answers in 10 ms in the cloud can take ten times longer on the robot. Quantising to 4 or 8 bits is often the difference between a model that fits on board and reacts in time, and one that doesn't.

Try it

Words to know

Quantisation
Storing numbers with fewer bits, plus scales to recover their size.
Scale (block scale)
A multiplier shared by a group of values.
HBM
High-bandwidth memory: the fast memory stacked next to a data-centre GPU.
Memory bandwidth
How many bytes per second the GPU can read from its memory.
SXM / PCIe
Two ways a GPU plugs into a server. SXM boards allow more power and fast NVLink between GPUs; PCIe cards fit ordinary slots.

Going deeper: everything below is the full version, with some maths. On a first read, skim the headings and the "Why it matters for robotics" box, and come back for the rest later.

Problem 11 · FP8 vs INT8 vs INT4 vs NVFP4: pick one and measure the loss

What the interviewer is testing: whether you know what each format actually stores, why outliers decide what works, which hardware runs which format natively, and whether you can design an evaluation that tells a real quality drop from noise.

The concepts

Integer formats. Map a real value x to an integer with a scale s, and back:

symmetric INT-b:   q = clamp(round(x / s), −(2^(b−1) − 1), 2^(b−1) − 1)
                   x̂ = s × q,     s = max|x| / (2^(b−1) − 1)
INT8: q ∈ [−127, 127]      INT4: q ∈ [−7, 7]

granularity of s:  per tensor  →  per channel (row)  →  per group (e.g. 128 values)

Integer steps are evenly spaced, so one large value forces a large scale and crushes the small ones. Example: a group holding 0.05, 0.3, −0.2 and one outlier of 20, in INT8 per tensor. s = 20/127 = 0.157, so 0.05 rounds to 0 and 0.3 becomes 0.31. Activations in large LLMs have exactly this pattern: a few channels with values far larger than the rest. LLM.int8() (Dettmers et al., 2022) keeps those channels in 16 bits. SmoothQuant (Xiao et al., 2023) moves the difficulty from activations into weights with a per-channel rescaling, so both can use INT8.

Floating-point formats. FP8 comes in two variants (Micikevicius et al., 2022): E4M3 (4 exponent bits, 3 mantissa bits, max ±448), used for weights and activations in inference, and E5M2 (more range, max ±57,344), mostly for gradients. Floating-point steps are fine near zero and coarse for big values, which suits bell-shaped weight distributions and tolerates outliers better than INT8 at the same bit count.

INT4 weight-only. GPTQ (Frantar et al., 2022) quantises weights one column at a time and uses second-order information to compensate for the error in the remaining columns. AWQ (Lin et al., 2023) protects the small share of weight channels that see large activations by scaling them before rounding. Both usually use groups of 128 with a 16-bit scale, so the real cost is 4 + 16/128 = 4.125 bits per weight. Activations stay in 16 bits, so the matrix multiply still runs at 16-bit speed. The gain is in bytes moved, which is what limits decode.

NVFP4. According to NVIDIA's Blackwell documentation, NVFP4 stores each value as E2M1 (1 sign, 2 exponent, 1 mantissa bit, so values from about −6 to 6). Every block of 16 values shares an FP8 E4M3 scale, and each tensor also has one FP32 scale. That's about 4.5 bits per value. NVIDIA describes it as roughly 3.5× smaller than FP16 and 1.8× smaller than FP8. MXFP4, the OCP format, uses 32-value blocks with a power-of-two scale. NVFP4's smaller blocks and fractional scales are meant to cut the rounding error. Blackwell tensor cores run it natively.

NVFP4:   x̂ = s_tensor (FP32) × s_block (E4M3, one per 16 values) × q (E2M1)
bits per value ≈ 4 + 8/16 = 4.5   (plus one FP32 per tensor)
FormatBits per weight (incl. scales)What's quantisedNative tensor-core support (per NVIDIA spec pages)Typical quality risk
BF1616BaselineAll four GPUsNone (reference)
FP8 (E4M3)≈8Weights + activations (W8A8), often KV cache tooH100, H200, B200, L40SLow with per-channel weight and per-token activation scales
INT8≈8W8A8 (needs outlier handling) or weight-onlyAll fourLow for weight-only; W8A8 needs SmoothQuant-style tricks
INT4 (GPTQ/AWQ, g128)≈4.125Weights only; matmul in 16-bitRuns via dequantising kernels; the gain is bandwidthModerate; larger models tolerate it better; check reasoning and long-context tasks
NVFP4≈4.5Weights, and optionally activationsB200 (Blackwell); H100, H200 and L40S tables list no FP4 rateNVIDIA reports ≤1% drop vs FP8 on DeepSeek-R1-0528; verify on your model

Worked answer with real numbers

Assumptions: a Llama-3-70B-shaped model (nominal 70B parameters, 80 layers, 8 KV heads, head dim 128), batch 1, decode bandwidth floor = weight bytes ÷ HBM bandwidth (ignores KV and overhead). Bandwidths from NVIDIA's spec tables: H100 SXM 3.35 TB/s, H200 SXM 4.8 TB/s.

format     weight bytes            fits on one…            floor/token H100 SXM   H200 SXM
BF16       70e9 × 2     = 140 GB   nothing below B200      (needs 2+ GPUs)        29.2 ms*
FP8/INT8   70e9 × 1     =  70 GB   H100 80 GB (barely)     20.9 ms                14.6 ms
NVFP4      70e9 × 4.5/8 =  39 GB   any of them             (no native FP4)        (no native FP4)
INT4 g128  70e9 × 4.125/8 = 36 GB  L40S 48 GB (barely)     10.8 ms                 7.5 ms

* 140 GB on a 141 GB H200 leaves almost nothing for the KV cache: not deployable.

Going from BF16 to FP8 halves the bytes and therefore roughly halves the decode floor. It also frees memory for a bigger batch, so throughput gains are often larger than 2×. Going to 4 bits halves the bytes again.

My pick, and the measurement plan. On H100 or H200, pick FP8 W8A8 (per-channel weight scales, dynamic per-token activation scales), with FP8 KV cache as a separate, later step. It is natively supported, well supported in vLLM, SGLang and TensorRT-LLM, and usually close to lossless. On B200, evaluate NVFP4 for weights, because it buys another ~1.8× in memory over FP8. Measure the loss like this:

  1. Freeze the baseline: same engine, same prompts, greedy decoding (temperature 0) for deterministic tasks.
  2. Cheap signal first: perplexity on held-out text, and the KL divergence between baseline and quantised next-token distributions. These catch broken quantisation fast, but small perplexity changes don't map cleanly to task quality.
  3. Task evals: knowledge (MMLU-style), maths (GSM8K-style), code, long-context retrieval, and your own production eval set. Quantisation damage often shows up first in maths, code and long contexts.
  4. Know the noise: standard error of an accuracy is √(p(1−p)/n). MMLU has 14,042 test questions: at 70% accuracy the SE is 0.39 points, so a 95% interval is about ±0.8 points. GSM8K has 1,319: at 80% the SE is 1.1 points, about ±2.2. A 1-point drop on GSM8K is inside the noise. Use paired comparisons on the same questions, or a bootstrap, before you claim a difference.
  5. Set the gate in advance, for example "no task drops more than 1 point absolute beyond noise, and the production eval's win rate against baseline is at least 48%". Then measure the payoff: tokens/s, ITL at p99, max batch, memory.

Trade-offs

ChoiceGainCost
FP8 W8A8≈2× fewer bytes, 2× the dense tensor rate of BF16 on HopperNeeds Hopper, Ada or Blackwell; calibration of activation scales
INT8 weight-only≈2× fewer bytes, runs on any recent GPUNo compute speedup; matmul in 16-bit
INT4 weight-only≈3.9× fewer bytes; fits big models on small GPUsHigher quality risk; kernel quality varies; can be compute-bound at large batch because of dequantisation
NVFP4≈3.5× fewer bytes than FP16; native FP4 math on BlackwellBlackwell only; newer tooling; verify per model; quantisation-aware training may be needed for small models
Finer groups (16 vs 128)Lower errorMore scale overhead (0.5 vs 0.125 bits per value)

How to say it in the interview

Decode is bandwidth-bound, so bits per weight translate almost directly into tokens per second and into how many users fit. On Hopper I'd pick FP8 W8A8 with per-channel weight and per-token activation scales: it's native, about 2× fewer bytes and usually near-lossless. INT4 weight-only and NVFP4 roughly halve the bytes again, and NVFP4 runs natively on Blackwell, but the risk is higher, so they need stronger evidence. I'd measure the loss with perplexity and KL as smoke tests, then paired task evals including maths, code, long-context and our own traffic, with confidence intervals so I don't mistake noise for loss. I'd set the acceptance gate before looking at results, then report the speed and memory gains alongside.

Common mistakes

Problem 12 · Pick GPUs for a workload: H100 vs H200 vs B200 vs L40S

What the interviewer is testing: whether you start from the workload (model size, precision, context, latency target, traffic) and turn specs into memory fit, max batch, latency floor and cost per token, rather than quoting peak FLOPS.

The specs, with the form factor behind each number

All figures are from NVIDIA's product pages (fetched October 2026). NVIDIA quotes tensor-core rates with sparsity; the dense rate, which is what LLM inference normally gets, is half. SXM and PCIe versions of the same chip differ, so the form factor matters.

GPU (form factor)MemoryBandwidthFP8 dense (sparse)FP4GPU-to-GPU linkMax power
H100 SXM (HGX/DGX boards)80 GB3.35 TB/s1,979 (3,958) TFLOPSNot listedNVLink 900 GB/sUp to 700 W
H100 NVL (PCIe, dual-slot)94 GB3.9 TB/s≈1,671 (3,341) TFLOPSNot listedNVLink bridge 600 GB/s350–400 W
H200 SXM (HGX boards)141 GB HBM3e4.8 TB/s1,979 (3,958) TFLOPSNot listedNVLink 900 GB/sUp to 700 W
H200 NVL (PCIe, dual-slot)141 GB HBM3e4.8 TB/s≈1,671 (3,341) TFLOPSNot listed2- or 4-way NVLink bridge, 900 GB/sUp to 600 W
B200 (HGX/DGX B200: 8× Blackwell SXM), per GPU*180 GB HBM3e8 TB/s4,500 (9,000) TFLOPS9 PFLOPS denseNVLink 1.8 TB/sSystem ~14.3 kW (DGX)
L40S (PCIe Gen4, dual-slot, passive)48 GB GDDR6864 GB/s733 (1,466) TFLOPSNot listedNone (no NVLink, no MIG)350 W

*NVIDIA publishes B200 figures per 8-GPU system: DGX B200 lists 1,440 GB total and 64 TB/s of HBM3e bandwidth; HGX B200 lists 72 PFLOPS FP8 (sparse) and 72 PFLOPS FP4 (dense). The per-GPU numbers above are those totals divided by 8. NVIDIA's NVFP4 blog gives the same 9 PFLOPS dense for the B200's smallest format. Sources: H100, H200, DGX B200, HGX, L40S.

The concepts

fits?        W/n + KV_seq × B/n + overhead  ≤  HBM per GPU
max batch    B_max = ⌊(usable HBM × n − W) / KV_seq⌋
ITL floor    t_step = (W + B × KV_seq) / (n × BW)          (decode, ignores comms)
prefill      t_prefill ≈ 2 × params × prompt_tokens / (achieved FLOPS × n)
cost         $ per M tokens = ($/GPU-h × n) / (tokens/s × 3,600) × 10⁶

decode-heavy work: compare $/h ÷ TB/s       prefill-heavy work: compare $/h ÷ dense TFLOPS

Worked answer with real numbers

Workload: a 70B-shaped model in FP8 (70 GB of weights), FP8 KV cache, 4,096 tokens of context per sequence, batch 64. KV per sequence = 2 × 80 × 8 × 128 × 4,096 × 1 B = 671 MB. Assumptions: 90% of HBM is usable (the rest goes to activations and the runtime), the bandwidth floor is reached exactly, and tensor-parallel communication is free. All three flatter the GPUs, the last one especially for PCIe cards. Hourly prices are assumptions for illustration only (H100 $2.50, H200 $3.50, B200 $6.00, L40S $1.20 per GPU-hour). Real prices vary widely by provider, contract and month.

config          usable−W    max batch   ITL floor @B=64   tokens/s (all GPUs)   $/M tokens (assumed price)
1× H100 SXM       2.0 GB          2     doesn't fit B=64          –                    –
2× H100 SXM      74.0 GB        110     16.9 ms                 3,796                0.37
1× H200 SXM      56.9 GB         84     23.5 ms                 2,720                0.36
1× B200 FP8      92.0 GB        137     14.1 ms                 4,533                0.37
1× B200 NVFP4   122.6 GB        182     10.3 ms                 6,219                0.27
4× L40S          102.8 GB       153     32.7 ms + PCIe comms    1,958                0.68

example row, 1× H200: (70 + 64 × 0.671) GB / 4.8 TB/s = 112.9 / 4.8 = 23.5 ms
                      64 / 0.0235 s = 2,720 tokens/s
                      $3.50 / (2,720 × 3,600) × 10⁶ = $0.36 per M tokens

What this shows:

Second workload, to show the other side: an 8B model in FP8 with 2K context. One L40S fits it with room for 262 sequences. At batch 64 the floor is 19.2 ms per token and 3,333 tokens/s, about $0.10 per M tokens at the assumed $1.20/h. That's acceptable for a 50 ms ITL target, and it runs in ordinary PCIe servers. An H100 is faster (5.0 ms, about $0.05 per M at the assumed price), so the L40S wins only if H100s are scarce, the price gap is wider, or power and rack limits rule.

Trade-offs

GPUPick it whenAvoid it when
H100 SXMMature software, wide availability, models that fit in 80 GB per GPU or split well over NVLinkLong contexts or big batches on 70B+, where 80 GB forces extra GPUs
H200 SXMSame software as H100 but 141 GB and 4.8 TB/s: 70B on one GPU, long contexts, bigger batchesCompute-bound prefill-heavy work, where it is no faster than an H100
B200 (HGX)Largest models, highest throughput per GPU, FP4, 1.8 TB/s NVLink for big tensor-parallel or MoE groupsSmall models that can't use it fully; power or cooling limits; tooling not yet tested with your stack
L40S (PCIe)Models up to ~13B in FP8, vision and graphics alongside AI, PCIe-only racks, tight power budgets70B+ models, multi-GPU tensor parallelism, long contexts

How to say it in the interview

I start from the workload: model size and precision, context length, traffic shape, and a latency target such as 50 ms ITL at p99. From that I compute whether weights plus cache fit, the max batch, the decode floor of bytes per step over bandwidth, and the prefill time from dense FLOPS. Then I turn throughput into cost per million tokens with real price quotes. For a 70B FP8 model, one H200 replaces two H100s because of its 141 GB, B200 wins on throughput and on FP4 if quality holds, and L40S suits small models in PCIe servers. Finally I'd benchmark the top two candidates on our actual traffic, because the floor ignores communication, kernels and scheduling.

Common mistakes

Interactive: does it fit, and how fast can it decode?

Decode time per token (ms, the bandwidth floor) against batch size for your setup. The dashed line is total tokens/s, on the right axis. The red vertical line is the largest batch whose KV cache fits in memory. The marker is your batch: green if it fits, red if not.

Weights per GPU
–
Max batch that fits
–
ITL floor
–
Tokens/s per GPU
–
$ per M tokens
–

Try this: the defaults (1× H200 SXM, 70B, FP8 weights, FP8 KV, 4,096 tokens, batch 64, $3.50/h) give max batch 84, 23.5 ms, 2,720 tokens/s and $0.36 per M. Switch to H100 SXM: it doesn't fit until you set 2 GPUs per replica. Switch to B200 and NVFP4: 10.3 ms. Specs come from NVIDIA's pages (B200 per-GPU numbers are 8-GPU system totals ÷ 8). Assumptions: 90% of memory usable, peak bandwidth reached, free inter-GPU communication. Real throughput is lower. The price is yours to set.

Practise it

(a) Fake-quantise a small model and measure the loss (laptop or free Colab). Round-trip the weights of every linear layer through INT8 or INT4 with different group sizes, then compare perplexity. This needs no special hardware, because the maths runs in 16 or 32 bits.

import torch, math
from transformers import AutoModelForCausalLM, AutoTokenizer
from datasets import load_dataset

def fake_quant(w, bits=4, group=128):
    qmax = 2 ** (bits - 1) - 1
    g = w.reshape(-1, group)
    s = g.abs().amax(1, keepdim=True).clamp(min=1e-8) / qmax
    return (torch.clamp(torch.round(g / s), -qmax, qmax) * s).reshape(w.shape)

def ppl(model, tok, text, n=256, steps=40):
    ids = tok(text, return_tensors="pt").input_ids[:, : n * steps]
    nll = 0.0
    with torch.no_grad():
        for i in range(steps):
            x = ids[:, i * n : (i + 1) * n]
            nll += model(x, labels=x).loss.item()
    return math.exp(nll / steps)

name = "Qwen/Qwen2.5-0.5B"
tok = AutoTokenizer.from_pretrained(name)
text = "\n\n".join(load_dataset("wikitext", "wikitext-2-raw-v1", split="test")["text"])
for bits, group in [(16, 0), (8, 128), (4, 128), (4, 32)]:
    m = AutoModelForCausalLM.from_pretrained(name, torch_dtype=torch.float32).eval()
    if bits < 16:
        for mod in m.modules():
            if isinstance(mod, torch.nn.Linear) and mod.weight.numel() % group == 0:
                mod.weight.data = fake_quant(mod.weight.data, bits, group)
    print(bits, group, round(ppl(m, tok, text), 3))

(b) The real thing (one Hopper or newer GPU). Make an FP8 checkpoint of an 8B model with TensorRT Model Optimizer or vLLM's quantisation tooling, serve both versions in vLLM, and compare tokens/s, ITL at p99 and the KV-cache capacity vLLM reports at startup. Then run the same evals on both. (c) Re-create the worked GPU table in a spreadsheet with your own price quotes and an ITL target, and see which GPU wins.

In this course

Why it matters for robotics

Self-check

How many bits per weight does INT4 with group size 64 and an FP16 scale really cost?

4 + 16/64 = 4.25 bits per weight.

Why does a single activation outlier hurt per-tensor INT8 more than FP8?

INT8 steps are evenly spaced, so the outlier sets a large scale and small values round to zero. FP8 has finer steps near zero and coarser steps for big values, so it keeps small values better at the same bit count.

A quantised model scores 79.0% on GSM8K (1,319 questions) against 80.0% for BF16. Is that a real loss?

Not on its own. The SE at 80% is about 1.1 points, so a 1-point gap is within noise. Use a paired comparison on the same questions or a bootstrap, and look at other tasks too.

Why is an H200 not faster than an H100 for a compute-bound prefill?

NVIDIA lists the same tensor-core rates for H100 SXM and H200 SXM. The H200 gains memory capacity (141 vs 80 GB) and bandwidth (4.8 vs 3.35 TB/s), which help decode and batch size, not raw FLOPS.

A 70B FP8 model with 671 MB of KV per sequence runs on one H200 with 90% usable memory. What's the max batch?

Usable = 0.9 × 141 = 126.9 GB. Minus 70 GB of weights = 56.9 GB. 56.9 / 0.671 ≈ 84 sequences.

NVIDIA's table says "FP8 3,958 TFLOPS" for H100 SXM. What number do you use for dense inference?

Half: 1,979 TFLOPS. The headline figure assumes 2:4 structured sparsity.

Resources