Ground Truth

Your AI Is Memory-Bound, Not Compute-Bound (So Tuning the Model Won't Fix Latency)

Mostafa DhouibMostafa Dhouib··11 min read
The short answer

On fixed hardware, inference latency is set by moving bytes, not by doing math. Most models sit in the memory-bound region with the compute units idle, so the real cost is moving weights and activations across a fixed memory bandwidth. Accuracy, latency, memory, and power are not four goals, they are four views of one thing, the bus, and the worst-case tail is set by contention, not by your model.

Your AI Is Memory-Bound, Not Compute-Bound (So Tuning the Model Won't Fix Latency)

The short answer. On fixed hardware the cost of running a model is not the math, it is moving the weights and activations across a fixed memory bandwidth. Most models sit in the memory-bound region with the compute units idle, so you cannot fix latency by adding compute. Accuracy, latency, memory footprint, and power are not four independent goals, they are four views of one quantity, the bus, and the worst-case tail is set by what else is contending for that bus, which is a system problem, not a model problem.

A team misses its latency budget on an edge device. The reflex is to reach for computation. Quantize harder, fuse the operators, find a faster kernel, argue for a chip with more arithmetic throughput. Some of that helps a little. Most of it does not, and the team burns a quarter finding out.

The reason is that the intuition behind all of it is wrong. A model looks like it is doing a lot of math, so it feels like a math problem. On fixed hardware, the math is almost never the bottleneck.

The math is nearly free, moving the bytes is not

Every accelerator has two ceilings.

The first is how fast it can do arithmetic. On a modern part this number is enormous, and it has been growing fast for years. The second is how fast it can fetch numbers from memory. This number is much smaller, and it has been growing far more slowly.

Plot those two ceilings against each other and you get the shape every accelerator has. There is a sloped region on the left where your speed is capped by how fast data arrives, and a flat region on the right where your speed is capped by how fast the chip does math. Which region you land in depends on how much arithmetic you do per byte you fetch.

Most models land on the left. Well down on the left.

The chip can do arithmetic far faster than it can fetch data, so most models sit down in the memory-bound region with the compute units idle. The gap up to the ceiling is silicon you already paid for, waiting.The chip can do arithmetic far faster than it can fetch data, so most models sit down in the memory-bound region with the compute units idle. The gap up to the ceiling is silicon you already paid for, waiting.memory bound0halfpeaklowthe ridgehighARITHMETIC PER BYTE FETCHEDACHIEVED THROUGHPUTpeak arithmetic, idlemost models operate hereMemory ceiling, how fast data arrivesCompute ceiling, how fast it does math
FigureThe chip can do arithmetic far faster than it can fetch data, so most models sit down in the memory-bound region with the compute units idle. The gap up to the ceiling is silicon you already paid for, waiting.

That has a specific and uncomfortable meaning. The compute units at the top of the chart are sitting idle. The gap between where you are operating and that upper ceiling is not headroom you are saving for later, it is silicon you have already paid for that is doing nothing, waiting for data to arrive.

So the real cost of running your model is not the arithmetic. The arithmetic is mostly free and mostly idle. The real cost is moving the weights and the activations across a fixed amount of memory bandwidth. You are memory-bound, not compute-bound.

Once that lands, a whole category of optimization work reveals itself as misdirected. Adding computation to a memory-bound system does nothing, because computation was never what you were short of. You can double the arithmetic throughput of the part and watch your latency stay exactly where it was.

How to tell which side you are on

You do not have to take this on faith, and you should not. There are three checks, in increasing order of effort.

The first is a back of the envelope. Take the total bytes that must move for one inference, which for a weight-dominated model is roughly the size of the weights in whatever precision you actually run them, plus the activations. Divide by the memory bandwidth the part is specified for. If that number is already close to your measured latency, memory movement is your latency, and there is nothing left for the math to explain.

The second is an experiment that costs an afternoon. Hold the model fixed and lower the clock on the compute units. If latency barely moves, you were not compute-bound. Then hold the compute clock fixed and lower the memory clock. If latency moves roughly in proportion, you are memory-bound, and now you know it rather than believe it.

The third is the profiler on the real part, looking at achieved bandwidth as a fraction of peak rather than at utilization percentages, which tend to report that something was busy without saying what it was busy waiting for.

  1. Back of the envelope
    bytes that must move for one inference, divided by the part's memory bandwidth. If that is close to your measured latency, memory movement is your latency
  2. Lower the compute clock
    if latency barely moves, you were not compute bound
  3. Lower the memory clock
    if latency moves roughly in proportion, you are memory bound, and now you know rather than believe
  4. Profile achieved bandwidth as a fraction of peak
    not utilisation percentages, which report that something was busy without saying what it was waiting for
Most teams run these after a quarter of failed optimisation, which is the same information at a much worse price.
FigureThree checks, in increasing order of effort, to find out which side of the roofline you are on. Run them before you optimise anything.

Run these before you optimize anything. Most teams run them after a quarter of failed optimization, which is the same information at a much worse price.

The four goals were always one goal

Here is where this stops being a performance detail and becomes an architecture problem.

Almost every edge program carries four targets, usually written by four different people in four different documents. Accuracy has to clear some bar. Latency has to fit a budget. The model has to fit in a memory footprint. Power has to stay under a ceiling, because there is a battery or a thermal limit or both.

They are tracked as four independent goals, in four rows of a spreadsheet, and traded against each other in meetings as though they were separate quantities.

They are not separate.

More accurateusually a bigger model, so more bytes to move
Lower latencyfewer bytes per inference
Smaller footprintthe footprint is literally the bytes
Lower powerdominated by moving bytes, not by the arithmetic
The busthe one quantity all four are views of, set by the part you choose
Written as four goals, spent as one budget. The bill arrives at integration, all at once.
FigureAccuracy, latency, memory, and power are not four independent targets. On fixed hardware they are four views of one quantity, the traffic crossing the bus.

A more accurate model is usually a bigger model, and a bigger model is more bytes to move. Lower latency means fewer bytes per inference, because bytes are what the latency is made of. The memory footprint is not merely related to the bytes, it is literally the bytes. And the power draw of an inference is dominated by moving those bytes, not by the arithmetic performed on them, because on modern parts moving a number a few millimeters costs far more energy than multiplying it.

So the four targets are four views of a single quantity that most programs never name: traffic across the bus.

This is why integration ambushes people. Four goals written separately look like a four dimensional trade space where you can give a little here to gain a little there. In reality they move together, because they are the same number wearing four different labels. You hit the accuracy target by growing the model, and you have simultaneously spent latency, footprint, and power, without anyone recording that you spent them. The bill arrives at integration, all at once, and it looks like a surprise.

It also means the single decision that moves all four at once is the part you choose, specifically its memory bandwidth and its memory hierarchy. That decision typically gets made early, by people optimizing for unit cost or availability, and it silently sets the ceiling for every one of the four targets the software team will later be held to.

If you take one thing into your next architecture review, take this: put the four targets on one line as one budget, in bytes, and make the chip decision against that line rather than against a price list.

The tail is contention, and it lives somewhere else

Everything above is about the average. Real-time systems do not fail on the average.

Your inference does not take one fixed amount of time. It takes a spread. Run it ten thousand times and you get a distribution, with a bulk of runs clustered somewhere comfortable and a tail extending to the right. The average can sit well inside your budget while the tail crosses your deadline, and in a system with a hard deadline, everything past that line is a miss. A control loop that misses is a control loop that was not there.

An inference takes a spread, not a fixed time. The average sits comfortably inside the budget while the tail crosses the deadline, and everything past that line is a miss.An inference takes a spread, not a fixed time. The average sits comfortably inside the budget while the tail crosses the deadline, and everything past that line is a miss.deadline. everything right of here is a missLATENCY, ONE INFERENCEHOW OFTEN
FigureAn inference takes a spread, not a fixed time. The average sits comfortably inside the budget while the tail crosses the deadline, and everything past that line is a miss.

The reason there is a tail at all is that the bus is shared. Your model is not the only thing that wants to move bytes. The camera or sensor front end is writing frames. A radio is moving packets. The display is being refreshed. Another process is doing its own work. Some of these are periodic, some are bursty, and occasionally several of them want the bus in the same window your inference needed it. When they contend, you wait, and that wait is your tail.

Now the part that changes how you plan the work.

You can pull the average down by making the model smaller. Fewer bytes per inference, less time moving them, the whole distribution shifts left. This is real and it is worth doing.

But the tail is not set by your model. The tail is set by what else is fighting for the bus at that instant. Shrinking your model does not make the radio quieter or the camera less periodic. You can halve your model, watch your average improve nicely, and watch your worst case stay exactly where it was, because the worst case was never about you.

The tail lives on a different layer than the model. It is a system problem, not a model problem. That single sentence explains why teams that only tune the model never fix their worst case, no matter how many quarters they spend, and it explains why the fix, when it finally arrives, comes from somewhere the model team was not looking.

Controlling the tail means controlling what shares the bus. That is scheduling, arbitration, DMA priority, buffering strategy, and sometimes the physical decision to give a critical path its own memory or its own path to it. It means specifying the worst case rather than the average in the requirement, because a budget written as an average is a budget that will be met and still fail. And it means measuring under realistic contention, with the radio actually transmitting and the camera actually streaming, rather than on a quiet bench where the tail politely does not appear.

Measure the whole chain, on the real part

One more trap sits at the edges of the pipeline.

The model is not the only thing moving bytes. Preprocessing moves bytes. Postprocessing moves bytes. Format conversions, resizing, normalization, copies between buffers that exist only because two libraries disagreed about layout, all of it crosses the same bus and spends the same budget.

It is common for a program to have a carefully optimized model surrounded by preprocessing and postprocessing nobody profiled, and for the unmeasured ends to cost more than the measured middle.

Which is why the only honest measurement is the whole chain, on the real hardware, under realistic load. A model benchmarked in isolation on a workstation reports a number that has almost no relationship to what will happen at the edge. Different memory system, different contention, different everything. An accuracy figure and a latency figure produced on a laptop are not evidence about a device.

Does this expire as chips get better?

The reasonable objection is that this is a description of today's hardware, and hardware improves. Parts get faster every year, memory gets wider, models get smaller and better at the same size. Does the whole argument dissolve on a long enough timeline?

Partly, and less than you would hope.

The bandwidth ceiling does rise every year, so the specific numbers move and some problems that are hard now become easy later. That much is real.

But the shape does not change. Arithmetic throughput has been growing faster than memory bandwidth for a long time, which means the gap that puts you in the memory-bound region has been widening, not closing. And contention does not go to zero because the bus got faster, because the other things sharing it get hungrier at the same time. A faster bus with more traffic on it still has a tail.

So the engineering gets easier. The structure of the problem does not move. Budget in bytes, decide the part against that budget, and control what shares the path.

FAQ

Why doesn't a faster processor fix my model's latency? Because most models are memory-bound, not compute-bound, so the processor spends its time waiting for weights and activations to arrive across a fixed memory bandwidth. The compute units are already idle, so adding more compute does not help. Latency is set by moving bytes, and the fix is the memory bandwidth of the chip you choose.

How do I know whether my model is memory-bound or compute-bound? Divide the bytes that must move for one inference by the part's memory bandwidth. If that number is close to your measured latency, you are memory-bound. Confirm it by lowering the compute clock, which should barely change latency, then lowering the memory clock, which should change it roughly in proportion.

Why are accuracy, latency, memory, and power not independent? Because on fixed hardware they are four views of one quantity, the bytes moving across the bus. A bigger model is more bytes, lower latency is fewer bytes per inference, memory footprint is the bytes, and power is dominated by moving them. Optimizing one moves the others, which is why you cannot treat them as separate targets.

How do I fix worst-case (tail) latency in a real-time AI system? Not by shrinking the model, which only lowers the average. The tail is set by contention for the shared bus, so you control the tail by controlling what else shares that bus, through scheduling, arbitration, DMA priority, and sometimes a dedicated path for the critical work. Specify the worst case rather than the average, and measure under realistic contention.

Why is my model fast on my laptop and slow on the device? Different memory system, different bandwidth, and different contention. A benchmark on a workstation measures a machine that has bandwidth to spare and nothing else competing for it. Measure the whole pipeline, including preprocessing and postprocessing, on the real part while the rest of the system is actually running.

Carrying a program like this one?

Tell us the system, the stakes, and the date that matters. You get a straight technical reply from the person who would lead the work, within 24 hours.

Bring us the program