Skip to main content

The Memory Wall

Why 3.2× more FLOPS gives only 1.6× speedup, and how to know in advance.

node
beginner
Compare A100 and H100 GPUs to discover that for memory-bound workloads, bandwidth, not compute, determines performance. The most important fallacy in ML systems.

The Question

NVIDIA’s H100 has 3.2× more FLOP/s than the A100. Does upgrading deliver a 3.2× speedup?

It does not. For the workloads that matter most in production (LLM inference, recommendation models, any memory-bound task), the speedup is closer to 1.6× (tracking the 1.6× HBM bandwidth ratio). This tutorial shows why, and how to predict the actual speedup before spending a dollar on hardware.

NotePrerequisites

Complete Tutorial 0: Hello, Roofline. This tutorial assumes familiarity with memory-bound vs. compute-bound regimes and the ridge point concept.

NoteLearning Objectives
  • Calculate the actual speedup between two GPUs for a given workload
  • Explain why the binding constraint determines which spec matters
  • Predict whether a hardware upgrade will help a specific model
  • Apply the roofline model to hardware procurement decisions
TipBackground: The Two Specs That Matter

GPU vendors advertise peak FLOP/s prominently, yet every GPU also has a memory bandwidth spec (in TB/s) that is equally important. Which spec determines actual performance depends entirely on which regime the workload is in:

Regime Binding Constraint Speedup Scales With
Memory-bound HBM bandwidth (TB/s) Bandwidth ratio between GPUs
Compute-bound Peak arithmetic (FLOP/s) FLOP/s ratio between GPUs

The key numbers for this tutorial:

Spec A100 H100 Ratio
Peak FP16 312 TFLOP/s 989 TFLOP/s 3.2×
HBM Bandwidth 2.04 TB/s 3.35 TB/s 1.6×

For a memory-bound workload, the speedup ceiling is 1.6×, regardless of the 3.2× compute improvement.


1. Setup

import mlsysim
from mlsysim import Engine

2. Side-by-Side Hardware Comparison

Load both GPUs from the Silicon Zoo and confirm the specs:

from mlsysim.show import table

a100 = mlsysim.Hardware.Cloud.A100
h100 = mlsysim.Hardware.Cloud.H100

flops_a = a100.compute.peak_flops.to("TFLOPs/s").magnitude
flops_h = h100.compute.peak_flops.to("TFLOPs/s").magnitude
bw_a = a100.memory.bandwidth.to("TB/s").magnitude
bw_h = h100.memory.bandwidth.to("TB/s").magnitude

table(
    ["Spec", "A100", "H100", "Ratio"],
    [
        ["Peak FP16 (TFLOP/s)", flops_a, flops_h, f"{flops_h/flops_a:.1f}x"],
        ["HBM BW (TB/s)", bw_a, bw_h, f"{bw_h/bw_a:.1f}x"],
        ["Ridge (FLOP/byte)", f"{ridge_a:.1f}", f"{ridge_h:.1f}", f"{ridge_h/ridge_a:.1f}x"]
    ]
)
Spec                  A100   H100  Ratio
────────────────────────────────────────
Peak FP16 (TFLOP/s)  312.0  989.0   3.2x
HBM BW (TB/s)         2.04   3.35   1.6x
Ridge (FLOP/byte)    153.0  295.2   1.9x

The FLOP/s ratio is 3.2× (989 vs. 312 TFLOP/s), whereas the bandwidth ratio is only 1.6× (3.35 vs. 2.04 TB/s). The theoretical ridge point also shifts from 153.0 to 295.2 FLOP/byte: the H100 has a higher ridge, meaning more workloads fall into the memory-bound regime on the H100 than on the A100.


3. The Fallacy: LLM Inference Speedup

The “3.2× speedup” claim can be tested with a workload that dominates production today, Llama-3 8B inference at batch size 1:

model = mlsysim.Models.Language.Llama3_8B

# Solve on both GPUs
prof_a100 = Engine.solve(model=model, hardware=a100, batch_size=1, precision="fp16")
prof_h100 = Engine.solve(model=model, hardware=h100, batch_size=1, precision="fp16")

lat_a = prof_a100.latency.to("ms").magnitude
lat_h = prof_h100.latency.to("ms").magnitude
speedup = lat_a / lat_h

table(
    ["", "A100", "H100", "Speedup"],
    [
        ["Bottleneck", prof_a100.bottleneck, prof_h100.bottleneck, ""],
        ["Latency", prof_a100.latency.to("ms"), prof_h100.latency.to("ms"), f"{speedup:.1f}x"],
        ["Throughput", prof_a100.throughput, prof_h100.throughput, ""]
    ]
)
                 A100       H100  Speedup
─────────────────────────────────────────
Bottleneck     Memory     Memory         
Latency       9.00 ms    5.60 ms     1.6x
Throughput  111.1 1/s  178.5 1/s         

Both GPUs report memory-bound. The actual speedup is approximately 1.6× (9.00 ms vs. 5.60 ms), close to the 1.6× bandwidth ratio rather than the 3.2× FLOP/s ratio. The remaining 2.0× of the H100’s compute advantage goes unused for this workload.

Sanity check: Llama-3 8B at FP16 has 8.03B parameters × 2 bytes = 16.06 GB of weights. On the A100 (2.04 TB/s), pure weight-streaming latency is 16.06 GB ÷ 2.04 TB/s = 7.9 ms (9.0 ms in Engine.solve after activation traffic and kernel launch tax). On the H100 (3.35 TB/s), pure weight-streaming latency is 16.06 GB ÷ 3.35 TB/s = 4.8 ms (5.6 ms in Engine.solve). The weight-streaming speedup is 7.9 / 4.8 = 1.64×, matching the bandwidth ratio, as expected for a memory-bound workload.


4. When 3.2× Matters: The Batch Size Crossover

The FLOP/s advantage takes effect only once the workload crosses into the compute-bound regime. Sweeping batch size on both GPUs locates the crossover:

rows = []
for batch in [1, 4, 16, 32, 64, 128, 256]:
    pa = Engine.solve(model=model, hardware=a100, batch_size=batch, precision="fp16")
    ph = Engine.solve(model=model, hardware=h100, batch_size=batch, precision="fp16")

    la = pa.latency.to("ms").magnitude
    lh = ph.latency.to("ms").magnitude
    sp = la / lh if lh > 0 else 0
    ai = pa.arithmetic_intensity.to("flop/byte")

    rows.append([batch, f"{ai:~.1f}", pa.bottleneck, ph.bottleneck, f"{sp:.1f}x"])

table(["Batch Size", "Arithmetic Intensity", "A100 Bottleneck", "H100 Bottleneck", "Actual Speedup"], rows)
Batch Size  Arithmetic Intensity  A100 Bottleneck  H100 Bottleneck  Actual Speedup
──────────────────────────────────────────────────────────────────────────────────
1                   0.9 flop / B           Memory           Memory            1.6x
4                   2.9 flop / B           Memory           Memory            1.6x
16                  6.2 flop / B           Memory           Memory            1.6x
32                  7.6 flop / B           Memory           Memory            1.6x
64                  8.6 flop / B           Memory           Memory            1.6x
128                 9.3 flop / B           Memory           Memory            1.6x
256                 9.6 flop / B           Memory           Memory            1.6x

At batch size 256, where both GPUs are compute-bound, the speedup reaches 1.6×, approaching the 3.2× compute ratio. The roofline plot below shows where Llama-3 8B at batch size 1 sits on the H100 relative to its 295.2 FLOP/byte ridge point:

from mlsysim.viz.plots import plot_roofline

# Plot the H100 roofline and see where Llama-3 8B (batch 1) falls
fig, ax = plot_roofline(h100, workloads=[model])

ImportantKey Insight

The binding constraint determines which hardware spec matters. For a memory-bound workload, speedup scales with the bandwidth ratio (1.6×). For a compute-bound workload, speedup scales with the FLOP/s ratio (up to 3.2×). The transition happens at different batch sizes on each GPU because the H100’s higher ridge point (295.2 vs. 153.0 FLOP/byte) means it stays memory-bound longer. In a procurement decision, the first question is not “how many FLOP/s?” but “which regime will the production workload operate in?”


5. The Procurement Table: Three Generations

Extending the analysis across three GPU generations shows the trend:

gpus = [
    ("V100", mlsysim.Hardware.Cloud.V100),
    ("A100", mlsysim.Hardware.Cloud.A100),
    ("H100", mlsysim.Hardware.Cloud.H100),
]

rows = []
for name, hw in gpus:
    p = Engine.solve(model=model, hardware=hw, batch_size=1, precision="fp16")
    flops = hw.compute.peak_flops.to("TFLOPs/s").magnitude
    bw = hw.memory.bandwidth.to("TB/s").magnitude
    ridge = (hw.compute.peak_flops / hw.memory.bandwidth).to("flop/byte").magnitude
    rows.append([name, flops, bw, f"{ridge:.1f}", p.latency.to("ms"), p.bottleneck])

table(["GPU", "TFLOP/s", "BW (TB/s)", "Ridge", "Latency", "Bottleneck"], rows)
GPU   TFLOP/s  BW (TB/s)  Ridge   Latency  Bottleneck
─────────────────────────────────────────────────────
V100    125.0     0.9000  138.9  19.97 ms      Memory
A100    312.0       2.04  153.0   9.00 ms      Memory
H100    989.0       3.35  295.2   5.60 ms      Memory

Across three generations (from 125 TFLOP/s and 0.9 TB/s on the V100 to 989 TFLOP/s and 3.35 TB/s on the H100), compute has grown 7.9× while bandwidth has grown 3.7×. The theoretical ridge point rises from 138.9 to 153.0 to 295.2 FLOP/byte, which means more workloads are memory-bound on newer hardware. This is the memory wall, and it is getting steeper with each generation.


Next Steps

Back to top