When p95 latency slips or the GPU bill climbs, the first instinct is usually to reach for a faster model. In production inference, that instinct is wrong far more often than it is right. Most of the latency and most of the cost live in the serving path — the batching strategy, the request router, the cache, the runtime, the precision the kernels run at — long before the model architecture itself is the limiting factor. That is what “performance tuning” actually means once a system is in production: not picking a different model, but doing measured work on the path between an incoming request and a returned response. Each lever — batching, caching, routing, quantisation, runtime selection, kernel-level work — has a distinct mechanism, a distinct cost, and a distinct failure mode. The discipline is knowing which one is your constraint before you touch any of them. What Performance Tuning Means in Practice The naive reading treats tuning as a model problem. The system is slow, so the model must be too big or too slow, so swap it. Sometimes that is the right move. Most of the time it is the most expensive move you can make, because you have replaced a known-good model and inherited a fresh accuracy-validation effort to solve a problem that lived two layers up. The expert reading treats tuning as work on the deployed serving path, sequenced from a profiled bottleneck. The divergence between the two approaches is entirely about sequencing. Tuning that starts from a profile targets the layer that actually costs latency. Tuning that starts from a guess optimises a layer that was never the constraint — and “the change worked” (the kernel really did get faster) tells you nothing about whether the end-to-end latency moved. A useful way to hold this: performance tuning is the set of reversible, measurable changes you make to a serving path, each ranked by latency or cost returned per unit of engineering effort. If a change is not measured against a baseline, it is not tuning — it is hope. The Serving-Path Levers and How Each Behaves The levers are not interchangeable. They sit at different points in the request lifecycle and they trade different things. Here is how they behave in practice. Lever What it changes What it returns What it costs / risks Touches the model? Batching Groups concurrent requests into one forward pass Throughput, GPU utilisation Adds queueing latency; tail latency under bursty load No Request routing Sends requests to the right replica / model tier Throughput at fixed cost; tail control Routing logic complexity; cold replicas No Caching Reuses prior results or KV state Latency on hits; effective throughput Memory; staleness; low hit rates waste it No Quantisation Lowers numeric precision (e.g. FP16, INT8) Latency, memory, cost-per-request Accuracy drift; needs validation Weights, not architecture Runtime selection Swaps the execution engine (TensorRT, ONNX Runtime) Latency via graph optimisation, kernel fusion Porting effort; operator coverage gaps No Kernel-level work Custom or fused kernels (FlashAttention, etc.) Latency on the hot path High effort; narrow applicability No Read top to bottom, the table also encodes a rough effort-to-reward ordering. Batching and routing are usually the cheapest gains because they touch infrastructure, not the model. A serving stack running every request as a batch of one is leaving throughput on the table that the GPU was built to deliver — and dynamic batching in a server like Triton Inference Server recovers it without anyone retraining anything. Caching is high-return when the workload has reuse (repeated prompts, shared prefixes for KV caching in transformer inference) and near-worthless when it does not, which is exactly why you measure the hit rate before you build it. Quantisation is where the serving path starts to touch the model, and where the trade-off becomes explicit. Moving from FP32 or FP16 down to INT8 typically cuts memory and improves throughput meaningfully, but precision is a first-class economic lever, not a free win — it trades a measured amount of accuracy for a measured amount of cost and latency. We treat that trade-off the way LynxBench AI frames precision as an economic lever in inference systems: the question is never “is INT8 fine,” it is “how much accuracy does this specific model lose at this precision, and is that loss worth the cost reduction for this workload.” That is a measurement, not an assumption. Runtime selection and kernel work sit at the bottom because they cost the most engineering and apply most narrowly. Choosing TensorRT over a generic runtime, or fusing attention kernels, can deliver real latency reductions — but only on a hot path you have already profiled, and only after the cheaper levers are exhausted. The pattern we see across engagements is that teams reach for the bottom of the table first because it feels like “real” optimisation, and skip the top of the table where most of the gain actually lives. How Do You Know Whether to Tune the Serving Path or the Model? This is the decision that determines whether the next two weeks of engineering are well spent. The answer comes from a profile, not a hunch. Profile the end-to-end request path and attribute latency to its stages: pre-processing, queueing, the model forward pass, post-processing, serialisation, network. If the forward pass is a small fraction of wall-clock latency, the model is not your constraint and a faster model will barely move p95 — the gain is upstream or downstream. If the forward pass dominates, and batching and runtime work have already been applied, then quantisation or a model change starts to make sense. Our guide to profiling AI inference walks through how that attribution works and what the numbers actually mean. The cost dimension follows the same logic. The unit that makes tuning levers comparable is cost-per-request, because it lets you rank “this caching change saves X per request” against “this quantisation change saves Y per request” on the same axis. We argue the case for that target in why cost-per-request is the right production AI optimisation target — it is the KPI that turns a pile of possible tuning changes into a ranked list. How Do You Establish a Baseline Before Tuning? Without a baseline, every tuning change is unfalsifiable. You need a fixed, repeatable measurement of the system as it is today before you change anything. A serviceable baseline captures, under a representative load: p50 and p95 latency, end-to-end, not just the model forward pass throughput (requests/second) at a fixed concurrency GPU utilisation and memory footprint cost-per-request at the current throughput The load matters as much as the metrics. A baseline taken under a trickle of traffic tells you nothing about behaviour under the bursty, concurrent load that production actually sees — and batching and queueing effects only appear under load. This is where the throughput-versus-latency relationship becomes the central tension: pushing batch size up improves throughput but adds queueing latency, and the right operating point depends entirely on your latency budget. LynxBench AI’s treatment of the throughput-vs-latency trade-off is the measurement reasoning we lean on here rather than re-deriving it — the point being that “throughput” and “latency” are not one dial, and a baseline has to capture both at a known concurrency to mean anything. Once the baseline exists, every change is evaluated the same way: re-run the identical load, compare the same metrics, keep the change only if the end-to-end number moved. A kernel that got 30% faster (benchmark-class, measured on the isolated kernel) but left p95 unchanged is a kernel that was never on the critical path. That gap — local speedup, no global gain — is the single most common way tuning effort gets wasted. Why Tuning the Wrong Layer Wastes Time Even When the Change Works This deserves its own treatment because it is so counterintuitive. Engineers measure the thing they changed, see it improve, and conclude the tuning succeeded. The change did work. It just worked on a layer that was not the constraint. If pre-processing and serialisation account for the majority of latency and you spend a sprint fusing attention kernels, the kernels will genuinely run faster and p95 will barely move. The work was real, the measurement was honest, and the outcome was nearly zero — because Amdahl’s law is unforgiving about optimising a component that owns a small share of the total. Across the inference engagements we have worked on, this is the dominant failure mode, more common than any single technical mistake: effort directed at the wrong layer because nobody profiled the whole path first. This is exactly the bottleneck-attribution problem that an AI inference cost audit is built to solve — it profiles the serving path end-to-end and ranks the tuning levers by return before any engineering time is spent, so the work goes where the latency and cost actually are. FAQ How does performance tuning work, and what does it mean in practice? In practice, performance tuning for AI inference is measured work on the deployed serving path — batching, caching, request routing, quantisation, runtime selection, and kernel-level work — rather than swapping the model. Each change is made against a fixed baseline and kept only if the end-to-end latency or cost metric actually moves. The discipline is sequencing the work from a profiled bottleneck instead of a guess. Which serving-path levers — batching, caching, routing, quantisation, runtime — give the biggest latency or cost gain? It depends on where your bottleneck sits, which is why you profile first. As a rough effort-to-reward ordering, batching and request routing are usually the cheapest gains because they touch infrastructure not the model; caching is high-return only when the workload has reuse; quantisation trades accuracy for cost and needs validation; runtime selection and kernel work cost the most engineering and apply most narrowly. How do I know whether to tune the serving path or the model itself? Profile the end-to-end request path and attribute latency to each stage. If the model forward pass is a small fraction of wall-clock latency, the model is not your constraint and a faster model will barely move p95 — the gain is in the serving path. Only when the forward pass dominates, and the cheaper serving-path levers are exhausted, does quantisation or a model change make sense. How do I establish a baseline before I start tuning? Capture, under a representative production-like load, your p50 and p95 end-to-end latency, throughput at a fixed concurrency, GPU utilisation and memory, and cost-per-request. The load matters as much as the metrics, because batching and queueing effects only appear under concurrent traffic. Every later change is then judged by re-running the identical load and comparing the same numbers. Why does tuning the wrong layer waste engineering time even when the change ‘works’? Because a local speedup on a component that owns a small share of total latency barely moves the end-to-end number — Amdahl’s law is unforgiving. A kernel can genuinely run 30% faster while p95 stays flat, because that kernel was never on the critical path. The work was real and the measurement honest, but the layer was not the constraint, which is why profiling the whole path first is non-negotiable. How does quantisation reduce inference latency and cost, and what accuracy trade-offs should I expect? Lowering numeric precision (for example FP16 or INT8) reduces memory footprint and improves throughput, which lowers cost-per-request and can cut latency. The trade-off is accuracy: precision is a first-class economic lever, so you measure how much accuracy a specific model loses at a given precision and decide whether that loss is worth the cost reduction for that workload. It is a measured trade-off, never a free win. How does runtime selection compare with kernel-level work as a tuning lever? Both target latency, but runtime selection — choosing an optimised engine like TensorRT or ONNX Runtime — delivers graph optimisation and kernel fusion with porting effort rather than bespoke code, so it is usually the better first move. Kernel-level work (custom or fused kernels such as FlashAttention) returns latency only on an already-profiled hot path and costs the most engineering for the narrowest applicability, so it belongs after the cheaper levers are exhausted. The honest closing question is not “which lever is best” — there is no context-free answer. It is: have you profiled the full serving path and ranked the levers by latency and cost returned per unit of effort, or are you about to optimise a layer you only assume is the constraint? Performance tuning that starts from that ranked profile is what the inference cost-cut audit produces, and it is the difference between engineering time spent on the bottleneck and engineering time spent on a guess. If you want that profiling and ranking done before committing a sprint, that is the kind of work our engineering services are scoped around.