aiml
SearchMy learning
Menu

Lesson 16 of 20Module 5

Investigate inference tail latency with request records

Aggregate request records to investigate tail latency. Keep timing boundaries, preemptions and admission outcomes distinct when testing hypotheses.

Prerequisites

Python, basic causal attention, and the preceding inference lessons.

By the end, you can

  • Investigate inference tail latency with request records.
  • Explain the bottleneck, measurement boundary, and tradeoffs in this implementation.
  • Keep rejections and sample size visible when interpreting tail latency.
Runtime
Python 3.12+
Dependencies
Standard library only

Lab code, tests and fixtures (ZIP, 104 KB)Quiz check your understanding

Progress is saved in this browser.

When you need this. A code assistant feels normal most of the time, but some requests pause for seconds. An average hides these outliers. Build a report that relates per-request latency to queue wait, context, cache pressure, and preemption records.

What you run. Executable telemetry aggregation over controlled log fixtures; diagnosis labels are test hypotheses, not a recorded GPU incident.

This lesson builds on compare eager and compiled normalization on real tensors.

Relate request timing to server pressure

A latency percentile describes the sampled requests and measurement window. A high p99 may come from queueing, a long prompt, output length, recomputation, transport, or another dependency. Correlation suggests an investigation; it does not prove a root cause.

KV preemption can require recomputing earlier work in a serving engine. To diagnose it, correlate engine counters with request-level timing and load. The lab includes controlled log windows with known labels, so its diagnosis rules can be inspected. The labels are hypotheses for real incidents, not an automatic explanation of the cause.

Request logsTail aggregationDiagnosticsummarytimingsstatisticsRequest logsTail aggregationDiagnostic summarytimingsstatistics
Follow the named data through the downloadable program. The article identifies whether the result is measured execution, a controlled fixture, or a planning estimate.

diagnose_p99_spike() aggregates request records and returns a rule-based interpretation. The controlled windows test how outliers and queueing affect the report. The real HTTP benchmark from the earlier lesson provides a separate small request record with both successes and overloads.

Run the telemetry report

Download the lab from this page, extract it, and run these commands from its directory. The default command reads saved run records or runs the explicitly described local algorithm; it never downloads a model or calls a paid API.

Run the local analysis and checksTerminal · bash
python3 lab.py
python3 -m unittest -v test_lab.py
Read lab.py
lab.pySource · pythonDownload
"""Aggregate controlled request logs and report tail-latency diagnostic hypotheses."""

from __future__ import annotations

from dataclasses import asdict, dataclass
import json
import math


@dataclass(frozen=True)
class RequestLog:
    request_id: str
    prompt_tokens: int
    output_tokens: int
    queue_wait_ms: float
    prefill_ms: float
    decode_tpot_ms: float
    preempted: bool
    recompute_penalty_ms: float
    prefix_cached_tokens: int
    kv_occupancy_pct_at_arrival: float

    def __post_init__(self):
        if (
            self.prompt_tokens < 1
            or self.output_tokens < 1
            or not 0 <= self.prefix_cached_tokens <= self.prompt_tokens
        ):
            raise ValueError("Invalid completed request token counts")
        values = [
            self.queue_wait_ms,
            self.prefill_ms,
            self.decode_tpot_ms,
            self.recompute_penalty_ms,
            self.kv_occupancy_pct_at_arrival,
        ]
        if (
            any(not math.isfinite(v) or v < 0 for v in values)
            or self.kv_occupancy_pct_at_arrival > 100
        ):
            raise ValueError("Invalid completed request measurements")

    @property
    def ttft_ms(self) -> float:
        return self.queue_wait_ms + self.prefill_ms

    @property
    def e2e_ms(self) -> float:
        return (
            self.ttft_ms
            + (max(0, self.output_tokens - 1) * self.decode_tpot_ms)
            + self.recompute_penalty_ms
        )


@dataclass(frozen=True)
class TelemetrySnapshot:
    window_name: str
    request_count: int
    mean_e2e_ms: float
    p50_e2e_ms: float
    p95_e2e_ms: float
    p99_e2e_ms: float
    p99_to_p50_ratio: float | None
    p50_ttft_ms: float
    p99_ttft_ms: float
    p50_tpot_ms: float | None
    p99_tpot_ms: float | None
    p99_queue_wait_ms: float
    peak_kv_occupancy_pct: float
    preemption_count: int
    estimated_recompute_tokens: int
    prefix_cache_hit_rate_pct: float
    modeled_cost_per_1k_requests_usd: float
    diagnostic_hypothesis: str


def percentile(values: list[float], q: float) -> float:
    if not values:
        raise ValueError("Cannot compute percentile of empty list")
    ordered = sorted(values)
    idx = min(len(ordered) - 1, max(0, math.ceil((q / 100.0) * len(ordered)) - 1))
    return ordered[idx]


def diagnose_p99_spike(
    window_name: str,
    logs: list[RequestLog],
    gpu_hourly_cost_usd: float = 4.00,
    effective_concurrency: float = 32.0,
) -> TelemetrySnapshot:
    if not logs:
        raise ValueError("logs must not be empty")

    e2e_vals = [r.e2e_ms for r in logs]
    ttft_vals = [r.ttft_ms for r in logs]
    tpot_vals = [
        r.decode_tpot_ms + (r.recompute_penalty_ms / (r.output_tokens - 1))
        for r in logs if r.output_tokens > 1
    ]
    queue_vals = [r.queue_wait_ms for r in logs]

    mean_e2e = sum(e2e_vals) / len(e2e_vals)
    p50_e2e = percentile(e2e_vals, 50.0)
    p95_e2e = percentile(e2e_vals, 95.0)
    p99_e2e = percentile(e2e_vals, 99.0)
    ratio = p99_e2e / p50_e2e if p50_e2e else None

    p50_ttft = percentile(ttft_vals, 50.0)
    p99_ttft = percentile(ttft_vals, 99.0)
    p50_tpot = percentile(tpot_vals, 50.0) if tpot_vals else None
    p99_tpot = percentile(tpot_vals, 99.0) if tpot_vals else None
    p99_queue = percentile(queue_vals, 99.0)

    peak_kv = max(r.kv_occupancy_pct_at_arrival for r in logs)
    preemptions = sum(1 for r in logs if r.preempted)
    recompute_tokens = sum(
        (r.prompt_tokens + r.output_tokens // 2) for r in logs if r.preempted
    )

    total_prompt = sum(r.prompt_tokens for r in logs)
    cached_prompt = sum(r.prefix_cached_tokens for r in logs)
    prefix_hit_pct = (cached_prompt / total_prompt) * 100.0 if total_prompt else 0.0

    # Illustrative allocation proxy includes waiting; it is not measured GPU usage or a bill.
    avg_gpu_s_per_req = (mean_e2e / 1000.0) / effective_concurrency
    cost_per_1k = avg_gpu_s_per_req * 1000.0 * (gpu_hourly_cost_usd / 3600.0)

    if preemptions > 0 and peak_kv >= 95.0:
        diagnostic_hypothesis = "kv_cache_preemption_and_recompute_thrashing"
    elif p99_ttft > p50_ttft * 5.0 and preemptions == 0:
        diagnostic_hypothesis = "head_of_line_prefill_queue_blocking"
    elif prefix_hit_pct < 20.0 and p99_ttft > 1000.0:
        diagnostic_hypothesis = "cold_prefix_cache_miss_burst"
    else:
        diagnostic_hypothesis = "no_rule_matched"

    return TelemetrySnapshot(
        window_name=window_name,
        request_count=len(logs),
        mean_e2e_ms=round(mean_e2e, 1),
        p50_e2e_ms=round(p50_e2e, 1),
        p95_e2e_ms=round(p95_e2e, 1),
        p99_e2e_ms=round(p99_e2e, 1),
        p99_to_p50_ratio=round(ratio, 2) if ratio is not None else None,
        p50_ttft_ms=round(p50_ttft, 1),
        p99_ttft_ms=round(p99_ttft, 1),
        p50_tpot_ms=round(p50_tpot, 2) if p50_tpot is not None else None,
        p99_tpot_ms=round(p99_tpot, 2) if p99_tpot is not None else None,
        p99_queue_wait_ms=round(p99_queue, 1),
        peak_kv_occupancy_pct=round(peak_kv, 1),
        preemption_count=preemptions,
        estimated_recompute_tokens=recompute_tokens,
        prefix_cache_hit_rate_pct=round(prefix_hit_pct, 1),
        modeled_cost_per_1k_requests_usd=round(cost_per_1k, 4),
        diagnostic_hypothesis=diagnostic_hypothesis,
    )


def simulate_workload_logs(
    admit_watermark_pct: float = 98.5, chunked_prefill: bool = False
) -> list[RequestLog]:
    logs: list[RequestLog] = []
    for i in range(100):
        is_tail_burst = i >= 95
        if not is_tail_burst:
            logs.append(
                RequestLog(
                    request_id=f"req-{i:03d}",
                    prompt_tokens=1024,
                    output_tokens=128,
                    queue_wait_ms=14.0 + (i % 6) * 2.0,
                    prefill_ms=38.0 if chunked_prefill else 42.0,
                    decode_tpot_ms=14.5,
                    preempted=False,
                    recompute_penalty_ms=0.0,
                    prefix_cached_tokens=640,
                    kv_occupancy_pct_at_arrival=76.0 + (i % 9),
                )
            )
        else:
            burst_idx = i - 95
            if admit_watermark_pct > 90.0:
                # Over-admitting requests drives KV occupancy to 98.4% and triggers decode eviction + full recompute cascade
                cascade_queue_ms = 3200.0 + burst_idx * 850.0
                recompute_ms = 11200.0 + burst_idx * 1300.0
                logs.append(
                    RequestLog(
                        request_id=f"req-{i:03d}",
                        prompt_tokens=4096,
                        output_tokens=192,
                        queue_wait_ms=cascade_queue_ms,
                        prefill_ms=310.0,
                        decode_tpot_ms=22.0,
                        preempted=True,
                        recompute_penalty_ms=recompute_ms,
                        prefix_cached_tokens=1024,
                        kv_occupancy_pct_at_arrival=96.0 + burst_idx * 0.6,
                    )
                )
            else:
                # Admission watermark holds burst requests briefly at the gate; chunked prefill prevents HoL blocking
                gated_queue_ms = 85.0 + burst_idx * 18.0
                logs.append(
                    RequestLog(
                        request_id=f"req-{i:03d}",
                        prompt_tokens=4096,
                        output_tokens=192,
                        queue_wait_ms=gated_queue_ms,
                        prefill_ms=75.0 if chunked_prefill else 260.0,
                        decode_tpot_ms=15.0,
                        preempted=False,
                        recompute_penalty_ms=0.0,
                        prefix_cached_tokens=2048,
                        kv_occupancy_pct_at_arrival=84.5,
                    )
                )
    return logs


def main() -> None:
    unbounded_logs = simulate_workload_logs(
        admit_watermark_pct=98.5, chunked_prefill=False
    )
    gated_logs = simulate_workload_logs(admit_watermark_pct=85.0, chunked_prefill=True)

    before = diagnose_p99_spike("unbounded_98pct_kv_watermark", unbounded_logs)
    after = diagnose_p99_spike("gated_85pct_kv_plus_chunked_prefill", gated_logs)

    report = {
        "before_remediation": asdict(before),
        "after_remediation": asdict(after),
        "p99_reduction_x": round(before.p99_e2e_ms / after.p99_e2e_ms, 2),
        "recompute_tokens_eliminated": before.estimated_recompute_tokens
        - after.estimated_recompute_tokens,
    }
    print(
        json.dumps(
            {
                "execution_mode": "CPU algorithm with controlled inputs; any latency/capacity coefficients are assumptions",
                **report,
            },
            indent=2,
        )
    )


if __name__ == "__main__":
    main()

The controlled logs exercise diagnostic rules. They do not prove that a production latency spike has the same cause. A real investigation needs engine counters, request correlation, comparable workloads, and rejected-request counts.

QuantityRecorded value
Controlled before-window requests100
Controlled before p99 (ms)25362
Controlled after p99 (ms)3079
Inspect the complete local result
Recorded local resultRecorded output · jsonDownload
{
  "execution_mode": "CPU algorithm with controlled inputs; any latency/capacity coefficients are assumptions",
  "before_remediation": {
    "window_name": "unbounded_98pct_kv_watermark",
    "request_count": 100,
    "mean_e2e_ms": 2967.9,
    "p50_e2e_ms": 1903.5,
    "p95_e2e_ms": 1907.5,
    "p99_e2e_ms": 25362.0,
    "p99_to_p50_ratio": 13.32,
    "p50_ttft_ms": 62.0,
    "p99_ttft_ms": 6060.0,
    "p50_tpot_ms": 14.5,
    "p99_tpot_ms": 101.06,
    "p99_queue_wait_ms": 5750.0,
    "peak_kv_occupancy_pct": 98.4,
    "preemption_count": 5,
    "estimated_recompute_tokens": 20960,
    "prefix_cache_hit_rate_pct": 56.0,
    "modeled_cost_per_1k_requests_usd": 0.1031,
    "diagnostic_hypothesis": "kv_cache_preemption_and_recompute_thrashing"
  },
  "after_remediation": {
    "window_name": "gated_85pct_kv_plus_chunked_prefill",
    "request_count": 100,
    "mean_e2e_ms": 1956.6,
    "p50_e2e_ms": 1899.5,
    "p95_e2e_ms": 1903.5,
    "p99_e2e_ms": 3079.0,
    "p99_to_p50_ratio": 1.62,
    "p50_ttft_ms": 58.0,
    "p99_ttft_ms": 214.0,
    "p50_tpot_ms": 14.5,
    "p99_tpot_ms": 15.0,
    "p99_queue_wait_ms": 139.0,
    "peak_kv_occupancy_pct": 84.5,
    "preemption_count": 0,
    "estimated_recompute_tokens": 0,
    "prefix_cache_hit_rate_pct": 60.3,
    "modeled_cost_per_1k_requests_usd": 0.0679,
    "diagnostic_hypothesis": "no_rule_matched"
  },
  "p99_reduction_x": 8.24,
  "recompute_tokens_eliminated": 20960
}
Saved output report for investigate inference tail latency with request records; execution mode and values come from the linked raw result.

This image is an application-generated report of the saved program result, not a new inference run. Open the full-resolution report or read the raw result.

Keep tails, rejections, and denominators together

A tight admission limit can lower latency among accepted requests while rejecting more users. Report offered, accepted, failed, and rejected counts together. A small sample’s maximum is not a stable estimate of production p99, and a single watermark cannot fit every model and workload.

Averages hide outliers. A controlled window has a moderate mean alongside large tail values. The report retains both rather than using the average as a latency guarantee.

Preemption records. The supplied log window contains preemption flags and penalties that support a diagnostic hypothesis. The recompute token quantity and cost are explicitly estimates, not engine counters or a bill.

Read the executable checks
Executable checksSource · pythonDownload
"""Unit and failure-boundary tests for inference telemetry and 10x p99 spike diagnostics."""

from __future__ import annotations

import unittest

from lab import (
    RequestLog,
    diagnose_p99_spike,
    simulate_workload_logs,
)


class TestInferenceMetricsAndP99Spikes(unittest.TestCase):
    def test_single_token_and_zero_duration_metrics_are_undefined(self):
        row = RequestLog("one", 1, 1, 0, 0, 9, False, 0, 0, 0)
        result = diagnose_p99_spike("one", [row])
        self.assertIsNone(result.p50_tpot_ms)
        self.assertIsNone(result.p99_tpot_ms)
        self.assertIsNone(result.p99_to_p50_ratio)

    def test_kv_headroom_gating_and_chunked_prefill_collapse_p99_spike(self) -> None:
        before = diagnose_p99_spike(
            "unbounded",
            simulate_workload_logs(admit_watermark_pct=98.5, chunked_prefill=False),
        )
        after = diagnose_p99_spike(
            "gated",
            simulate_workload_logs(admit_watermark_pct=85.0, chunked_prefill=True),
        )
        self.assertGreater(before.p99_to_p50_ratio, 10.0)
        self.assertEqual(
            before.diagnostic_hypothesis, "kv_cache_preemption_and_recompute_thrashing"
        )
        self.assertLessEqual(after.p99_to_p50_ratio, 2.1)
        self.assertEqual(after.preemption_count, 0)
        self.assertEqual(after.diagnostic_hypothesis, "no_rule_matched")
        self.assertLess(
            after.modeled_cost_per_1k_requests_usd,
            before.modeled_cost_per_1k_requests_usd,
        )

    def test_averaging_latency_hides_10x_p99_preemption_spike(self) -> None:
        logs = simulate_workload_logs(admit_watermark_pct=98.5, chunked_prefill=False)
        snap = diagnose_p99_spike("unbounded", logs)
        # p50 and mean look deceptively low (< 3.1s), while p99 exceeds 23s (>12x p50)
        self.assertLess(snap.p50_e2e_ms, 2000.0)
        self.assertLess(snap.mean_e2e_ms, 3100.0)
        self.assertGreater(snap.p99_e2e_ms, snap.p50_e2e_ms * 10.0)
        self.assertGreater(snap.p99_e2e_ms, snap.mean_e2e_ms * 7.0)

    def test_kv_thrashing_cascades_recompute_queue_delay(self) -> None:
        logs = simulate_workload_logs(admit_watermark_pct=98.5, chunked_prefill=False)
        preempted_reqs = [r for r in logs if r.preempted]
        self.assertEqual(len(preempted_reqs), 5)
        # Each subsequent preempted request suffers strictly worse queue wait and recompute delay
        for prev, curr in zip(preempted_reqs, preempted_reqs[1:]):
            self.assertGreater(curr.queue_wait_ms, prev.queue_wait_ms)
            self.assertGreater(curr.recompute_penalty_ms, prev.recompute_penalty_ms)


class TelemetryBoundaryTests(unittest.TestCase):
    def test_no_matching_rule_does_not_claim_slo_success(self):
        from lab import RequestLog

        record = RequestLog("slow", 32, 2, 60000, 100, 10, False, 0, 32, 50)
        result = diagnose_p99_spike("slow", [record])
        self.assertEqual(result.diagnostic_hypothesis, "no_rule_matched")
        self.assertEqual(result.p99_ttft_ms, 60100)

    def test_first_token_is_not_counted_as_an_extra_decode(self):
        from lab import RequestLog

        self.assertEqual(RequestLog("one", 32, 1, 4, 6, 10, False, 0, 0, 50).e2e_ms, 10)


if __name__ == "__main__":
    unittest.main()


class GuardBoundaryTests(unittest.TestCase):
    def test_rejects_invalid_boundary_inputs_with_specific_reason(self):
        import lab
        cases = [('percentile([],50)', 'empty list'), ('diagnose_p99_spike("empty",[])', 'logs must'), ('RequestLog("x",0,1,0,0,1,False,0,0,0)', 'token counts'), ('RequestLog("x",1,1,-1,0,1,False,0,0,0)', 'measurements')]
        for code, reason in cases:
            with self.subTest(code=code):
                with self.assertRaisesRegex((ValueError, MemoryError, KeyError, RuntimeError), reason):
                    exec(code, vars(lab).copy())

Try a change and explain the result

Compare the average with the tail in the controlled windows. Identify which fields support the preemption hypothesis and which measurements are still missing. In the actual HTTP run, inspect failures rather than silently dropping them.

Tail latency falls after tighter admission, but rejections rise. What changed?

Choose one answer, then check your reasoning.

Read the explanation

Evaluate offered and completed work together. Accepted-only tail latency does not describe the whole service.

Investigate rather than infer causality

Collect bounded content-free metrics and retain request correlation IDs without logging prompts. Use engine metric names from the installed version. Compare equivalent workload windows and denominators before attributing improvement to one setting.

References

Sources checked October 24, 2026. Runtime-specific commands and results apply to the recorded versions; linked rolling documentation can change. The application scenarios are teaching examples, not claims about an author’s production incident.

Written by Arjun Prabhulal.

Module 5

Debug slow GPUs and tail latency