Serving APIs, dynamic batching and queueing latency
Before this lesson: lesson 04 (Pods, probes, replicas). From probability: the exponential and Poisson distributions and the geometric series (probability lesson 03); the proof in section 3.3 uses a Markov chain in continuous time, explained there in plain words (probability lesson 15 has more). Time: about 75 minutes, plus the notebook.
Why this matters #
A model is useful only when other programs can call it: a ranking model behind a search page, a fraud model inside a payment, a nowcast model that a weather app polls every minute. The serving layer has a latency target, written as an SLO (service level objective), for example "p99 latency below 100 ms". Two rates decide most of the design: the arrival rate and the service rate of one replica, both in requests per second. Queueing theory turns them into latency. It explains a fact that surprises many teams: at 90% load the mean latency is already 10 service times. Serving systems such as NVIDIA Triton, TorchServe, KServe and vLLM all expose the same few settings (replicas, max batch size, max queue delay), and this lesson shows how to choose them. "Design a model-serving endpoint" is also a standard interview question.
1. Predict first #
2. The picture #
HTTP in one paragraph. A client sends a request: a method (GET to read, POST to send
data), a path such as /predict, and often a JSON body such as
{"t2m_k": 285.1, "rh": 71}. The server answers with a status code and a body. Codes 200–299
mean success, 400–499 mean "the client sent something wrong", and 500–599 mean "the server
cannot do it now". The model server of this lesson is such a program.
One replica, one queue. Start with the simplest server: one replica that serves one request at a time, first in, first out (FIFO).
- A request arrives, waits in the queue, is served, and leaves. Its latency is the waiting time plus the service time.
- Throughput is the number of requests finished per second. A replica with mean service time finishes at most requests per second.
- The utilisation is the share of time the replica is busy.
Why does a queue form when ? Because arrivals are random. If requests came exactly every seconds and took exactly , no request would ever wait. Real requests come in bursts, and the replica clears the queue of a burst only with its spare time. At there is little spare time, so the queue of one burst is often still there when the next burst comes.
Watch the queue, then move . Orange dots are requests waiting in front of the replica; the chart shows the number of requests in the system over time, with the theory mean . At , () long queues build up in bursts. Set (): the queue is short and empties often. The table compares 400,000 simulated requests with the M/M/1 formulas of section 3.3.
Now put the same numbers on one curve. The figure below shows the mean latency and the p99 latency of one replica against . Both curves are flat at first and then rise without limit near : a wall.
Move the operating point and read the latency. The blue curve is the mean latency, the orange curve the p99 (for exponential service times, M/M/1), and the green line the SLO. Notice the factor in the readout: from to every latency doubles. Set (every request takes exactly the same time): the mean waiting time halves, but the wall at stays.
One replica has requests/s (20 ms per request on average) and behaves like an M/M/1 queue. Traffic grows from 25 to 45 requests/s. By what factor does the mean latency grow?
: from s ms to s ms, a factor of 5. Latency depends on the spare capacity , which shrinks from 25 to 5 per second. The mean number in the system goes from 1 to 9 (factor 9), but the arrival rate also grows, so by Little's law grows by .
Batching, as a picture. A GPU pays a large fixed cost per model call (reading the weights, launching kernels) and a small cost per request. A batch of requests takes
The throughput grows with towards , but every request in a batch waits for the whole batch. So batching trades a little latency per call for a lot of throughput, and under load the extra throughput makes the queue much shorter. Section 3.4 makes this exact.
3. Definitions and main results #
3.1 The serving API #
| Endpoint | Purpose | Answer |
|---|---|---|
GET /healthz |
liveness: the process runs and answers | 200 always; never depends on the model |
GET /readyz |
readiness: may this replica get traffic? | 200 when the model is loaded; 503 with a reason before |
GET /version |
which artifact answers | the model version (often also the code commit and the image digest) |
POST /predict |
one instance | 200 with the prediction; 422 invalid input; 503 not ready |
POST /predict_batch |
a list of instances | one vectorised model call; 413 if the batch is too large |
The status codes: 422 (unprocessable content) means "the body does not match the schema"; 413 (content too large) means "the batch is larger than the limit"; 503 (service unavailable) means "not ready, try another replica or later". Five rules make the server robust.
- Load the model once, at startup. In FastAPI this is the lifespan handler: the code
before
yieldruns before the first request, the code after it at shutdown. Not at import time (every test and tool that imports the module would load it), and not per request (seconds each). - Liveness never depends on the model (lesson 04, section 3.3). A liveness failure restarts the container; a readiness failure only removes the Pod from the Service. If loading takes 90 s and liveness checks the model, the container is restarted before loading finishes: a restart loop. For a slow load add a startup probe.
- Validate the input with a schema: required fields, types, ranges, and no unknown fields. Invalid input then never reaches the model, and the client gets a 422 with the reason. This is the API's data contract (lesson 01): a temperature sent in °C to a model trained in kelvin is a valid number, and only a range check catches it.
- One model call per batch, and a size limit (413), so that one client cannot take all the memory.
- State lives in the app object (for example
app.statein FastAPI), not in module-level global variables, so two apps in one process (two tests) stay independent.
A FastAPI sketch for the rain-nowcast model, as an illustration (other frameworks have the same parts; the notebook does not run web servers):
from contextlib import asynccontextmanager
from fastapi import FastAPI, HTTPException
from pydantic import BaseModel, ConfigDict, Field
class StationObs(BaseModel): # the input contract: FastAPI answers 422 on failure
model_config = ConfigDict(extra="forbid") # unknown fields are errors
t2m_k: float = Field(gt=180, lt=340) # 2 m temperature in kelvin
rh: float = Field(ge=0, le=100) # relative humidity in %
@asynccontextmanager
async def lifespan(app: FastAPI):
app.state.model = load_model("nowcast-v3") # once, before the first request (load_model: your loader)
yield # the app serves requests here
app = FastAPI(lifespan=lifespan)
@app.get("/healthz")
def healthz():
return {"status": "ok"} # liveness: never touches the model
@app.post("/predict_batch")
def predict_batch(items: list[StationObs]):
if len(items) > 64:
raise HTTPException(status_code=413, detail="at most 64 instances")
X = [[o.t2m_k, o.rh] for o in items]
return {"p_rain": app.state.model.predict_proba(X)[:, 1].tolist()} # one call for the batch
3.2 Latency, throughput and percentiles #
Latency is a distribution, not one number. Sort the measured latencies: . The -th percentile (nearest-rank definition) is : the smallest value that at least of the requests do not exceed. The p50 is the median; the p99 is the latency that 99% of requests stay below. SLOs use high percentiles because the mean hides the slow requests.
Example. 97 requests take 12 ms and 3 take 400 ms ().
- Mean: ms.
- p50: rank , so 12 ms. p95: rank 95, so 12 ms.
- p99: rank 99. The sorted list has 12 ms at ranks 1–97 and 400 ms at ranks 98–100, so 400 ms.
The mean (23.6 ms) describes no real request: almost every request is faster, and 3% are 17 times slower.
Percentiles cannot be averaged. Replica A serves 970 requests with p99 = 46 ms; replica B is slow and serves 30 requests with p99 = 636 ms (the "two replicas" sample in the figure below). The mean of the two p99 values is 341 ms, but the p99 of all 1,000 requests is 239 ms: the slow replica served only 3% of the traffic. The p99 over both depends on how many requests each replica served. Merge the histograms (or the raw latencies), then compute the percentile.
Result (fan-out amplifies the tail). A user request calls backends and waits for all of them. If each backend is slow with probability , independently, the user request is slow with probability
Proof. The request is fast only if all backends are fast. By independence this has probability . The complement is .
With : for and for (Dean and Barroso, "The Tail at Scale", 2013). For small , the value is close to .
Switch the sample, then move the fan-out. Left: the latency histogram with the p50, mean, p95 and p99. In the 97/3 example the mean sits between two groups and describes no request. "Two replicas" shows that the mean of two p99 values is not the p99 of all requests. Right: the curve ; notice how fast it rises for .
97 requests take 12 ms and 3 take 400 ms. Which statement is true?
With nearest rank, the p95 is the 95th of 100 sorted values (12 ms) and the p99 is the 99th (400 ms). The mean, 23.6 ms, is the latency of no request at all. A percentile is a value of the sorted sample, not a mean of a tail and not a scaled maximum.
Little's law. Latency is a time per request; throughput is a rate. Little's law links them in any stable system (one that does not grow without bound):
with the mean number of requests in the system, the arrival rate and the mean time a request spends in the system. It needs no assumption about the distributions. Example: a service gets 200 requests/s and has a mean latency of 50 ms, so on average requests are inside it. This is how you size thread pools, connection pools and GPU batch slots.
Why Little's law holds (sketch)
Watch the system for a long time and draw , the number of requests inside at time .
- The area under is . Each request adds 1 to for exactly the time it is inside, so the area also equals the sum of the times in the system of all requests (up to the few requests still inside at the end, which matter less and less as grows).
- Divide by : .
- As , the left side tends to , to , and the mean of the to . So .
Sigman's notes on Little's law (see section 11) give the full sample-path proof.
A GPU server receives 400 requests/s. On average, 12 requests are inside it (waiting or in a batch). What is the mean latency?
Little's law holds for any stable system, whatever the distributions. So s s ms. "4.8 s" multiplies instead of dividing; is a rate (per second), not a time.
3.3 The M/M/1 queue #
The notation M/M/1 means: arrivals form a Poisson process ("M" for memoryless, rate ), service times are exponential ("M", rate ), and there is 1 server. Requests are served FIFO, and the queue has no size limit.
Theorem (M/M/1 mean latency). If , then
The mean waiting time (before service) is , and the mean number in the system is .
Why this is true, in words. In steady state the system must move up from to requests as often as it moves down from to . This "balance" makes each extra request in the queue times as likely as one fewer: a geometric distribution. Its mean is , and Little's law turns that into a time.
Proof.
- Let be the number of requests in the system. Arrivals and services are memoryless, so the future of depends only on its present value: is a birth–death Markov chain. It jumps at rate (an arrival), and at rate for (a service ends).
- Let be the long-run share of time with . Draw a cut between the states and . The chain cannot cross this cut upwards twice without crossing it downwards in between, so in the long run it crosses up as often as down. Up-crossings happen at rate and down-crossings at rate , so .
- Hence , and by induction .
- The shares sum to 1: . The geometric series converges only for : this is the stability condition. So . The server is idle a share of the time, as the word "utilisation" says.
- The mean number in the system is , using .
- Little's law gives .
The tail. In the FIFO M/M/1 queue the latency of a request is itself exponential with rate : . So its -quantile is
Every percentile carries the factor . It grows without limit as , and faster the closer you get: from to , every latency doubles, because .
Why the M/M/1 latency is exponential
- An arriving request sees the system in its long-run state: a Poisson arrival finds requests with probability (PASTA: Poisson arrivals see time averages).
- It then stays for service times: the rest of the current service (again exponential with rate , by the memoryless property), full services of the others, and its own. So is a sum of independent exponential() times, where for : a geometric number of terms.
- A geometric() sum of exponential() times is exponential with rate . (Think of the service ends as a Poisson process with rate ; each end is "the last one" with probability , independently; thinning a Poisson process with probability gives a Poisson process with rate , and is the time to its first event.)
- .
Other service-time distributions: the Pollaczek–Khinchine formula
Model latency usually varies less than an exponential. For Poisson arrivals and any service-time distribution with mean (the M/G/1 queue), the mean waiting time is
where is the coefficient of variation of the service time (standard deviation divided by mean). Exponential service has and gives back . A constant service time (, the M/D/1 queue) halves the waiting time. But the factor stays for every : no service distribution removes the wall. So M/M/1 is a slightly pessimistic planning model for the service times. Arrivals that are burstier than Poisson (for example many clients that poll on the hour) make the real queue worse.
An M/M/1 replica has requests/s. At which arrival rate is its mean latency 25 ms, and what is its p95 latency there?
s gives /s, so /s (). The latency is exponential with rate 40/s, so s s . The first option confuses with . "41 ms" uses , the rule for a normal distribution; latency is not normal. And 175/s gives s ms, not 25 ms.
3.4 Dynamic batching #
A dynamic batcher keeps a queue in front of the model. It sends a batch when the replica is
free and either the queue holds max_batch_size requests or the oldest request has waited
the max queue delay. (Triton calls these max_batch_size and max_queue_delay_microseconds;
TorchServe calls them batch_size and max_batch_delay.) The delay acts differently at three
loads:
- Low load: the batch never fills; each request waits the full delay, then runs alone. The delay only adds latency.
- Medium load: the delay lets a few more requests join, so the throughput rises.
- High load: requests pile up while the replica is busy; batches form without waiting. The delay does not matter any more.
Result (the batch sizes itself under load). Suppose the replica is always busy and takes all waiting requests when it becomes free. Then the next batch holds the requests that arrived during the previous one: . If , the batch size converges to the fixed point
Proof.
- A fixed point satisfies , so and .
- Subtract the fixed-point equation from the recursion: . So the distance to shrinks by the factor at every batch, and .
If , the batches are capped at , and the replica's capacity is requests per second. Above that rate the queue grows without bound. So is a cap for bursts, and the largest throughput of one replica is .
Choosing the settings from an SLO.
- Measure for on the real hardware.
- Pick so that the capacity is well above the peak rate per replica (room for bursts) and is well below the latency budget.
- Pick a small max delay (a few ms, often below ). At low load it costs exactly the delay.
- Confirm the p99 at peak with a load test or a simulation. Add replicas instead of pushing one replica towards its capacity.
Use the presets, then change and the delay. The chart shows the p99 latency against the arrival rate, for your batcher and for no batching (one request per call). The grey dashed line is the capacity , the green line the SLO. Without batching the replica saturates near 95 requests/s; with it handles 400/s at a p99 under 30 ms. Compare the simulated mean batch size with the fixed point . At "night", set the delay to 0: the latency drops by about the delay.
A GPU model takes ms. Traffic is very low (one request every few seconds). You set the max queue delay to 2 ms. What happens to the latency?
At low load no second request arrives within 2 ms, so the batch never fills. Each request waits the full delay and then runs as a batch of 1: about ms instead of ms. A max delay helps only when several requests arrive within it.
Answers to section 1. (1) About 5. With /s, goes from s ms to s ms, while the traffic grows only 1.8 times (section 3.3). (2) Worse. The batch never fills, so each request waits the full 2 ms and then runs alone: about 12.5 ms instead of 10.5 ms (section 3.4). (3) About 18%: (section 3.2).
4. Worked example #
Part A: replicas for a CPU model. A quality-control model for weather-station data runs on CPU replicas with a mean service time of 20 ms (/s). Treat each replica as M/M/1.
| /s | /s | |
|---|---|---|
| 0.5 | 0.9 | |
| 40 ms | 200 ms | |
| 1 | 9 | |
| 20 ms | 180 ms | |
| 184 ms | 921 ms |
The SLO is p99 < 250 ms at a peak of 120 requests/s, spread evenly over the replicas.
- p99 < 0.25 s needs , so /s.
- So one replica may take at most /s ().
- Replicas: .
- Check: with 4 replicas each gets 30/s, , and s ms ✓. With 3 replicas each gets 40/s, and s ms ✗.
First pick your guess, then read the bars. Each bar is the p99 latency of one replica when the peak traffic is split over replicas: blue meets the SLO, magenta misses it. Your guess is the orange dot. Then change the SLO or the service time, predict again, and check. Notice how much the answer changes when the SLO comes close to .
Part B: batch settings for a GPU model. A precipitation-nowcasting model on one GPU needs ms. The peak traffic is 400 requests/s, and the SLO is p99 < 50 ms.
- Capacity : 95/s for , 571/s for , 889/s for , 1231/s for (limit /s). Without batching the replica is overloaded ().
- Self-sized batch at peak: per ms, so and ms. A request waits on average about half a batch time, then runs for one: about ms.
- Choice: (capacity 889/s, more than twice the peak; a full batch takes 18 ms) and a max queue delay of 2 ms (its cost at night: about 2 ms per request).
- A simulation of the batcher (Poisson arrivals, 200,000 requests) gives at 400/s: mean batch 5.0, mean latency 19.3 ms, p99 27.7 ms ✓. At 800/s the p99 is about 48–49 ms, just under the limit: time to add a replica. At 1000/s (above 889/s) the queue grows without bound. At 20/s the mean latency is 13.2 ms with the 2 ms delay and 11.6 ms without it.
5. In code #
Plain Python and NumPy check the schema rule, the sums of the proof, and the M/M/1 formulas against a simulated FIFO queue. The notebook has these cells and the exercises.
import math
import numpy as np
# 1. A plain-Python input contract: required fields, ranges, no extra fields
SCHEMA = {"t2m_k": (180.0, 340.0), # 2 m temperature in kelvin
"rh": (0.0, 100.0)} # relative humidity in %
def validate(body, schema=SCHEMA):
errors = [(k, "missing") for k in schema if k not in body]
errors += [(k, "extra_forbidden") for k in body if k not in schema]
for k, (lo, hi) in schema.items():
if k in body and not lo <= body[k] <= hi: # NaN fails every comparison
errors.append((k, "out_of_range"))
return errors # an API answers 422 if not empty
for body in ({"t2m_k": 285.1, "rh": 71},
{"t2m_k": 12.0, "rh": 71}, # sent in °C by mistake
{"t2m_k": float("nan"), "rh": 71}, # NaN
{"rh": 71}, # missing field
{"t2m_k": 285.1, "rh": 71, "wind": 3}): # unknown field
print(validate(body))
# []
# [('t2m_k', 'out_of_range')]
# [('t2m_k', 'out_of_range')]
# [('t2m_k', 'missing')]
# [('wind', 'extra_forbidden')]
# 2. Steps 4-5 of the proof at rho = 0.9: the pi_n sum to 1 and L = rho / (1 - rho) = 9
rho, n = 0.9, np.arange(2000)
print(round(((1 - rho) * rho**n).sum(), 6), round((n * (1 - rho) * rho**n).sum(), 6)) # 1.0 9.0
# 3. A FIFO queue against the M/M/1 formulas
def fifo_latencies(arrivals, services):
"""Latency of each request in a single-server FIFO queue."""
lat, finish = np.empty_like(arrivals), -np.inf
for i, (a, s) in enumerate(zip(arrivals, services)):
finish = max(finish, a) + s # start when both the request and the server are free
lat[i] = finish - a
return lat
rng, mu, N = np.random.default_rng(0), 50.0, 500_000
for lam in (25.0, 45.0):
arr = np.cumsum(rng.exponential(1 / lam, N))
lat = fifo_latencies(arr, rng.exponential(1 / mu, N))
print(f"lambda={lam:.0f}/s: mean {1e3 * lat.mean():.0f} ms (theory {1e3 / (mu - lam):.0f}), "
f"p99 {1e3 * np.percentile(lat, 99):.0f} ms (theory {1e3 * math.log(100) / (mu - lam):.0f})")
# lambda=25/s: mean 40 ms (theory 40), p99 185 ms (theory 184)
# lambda=45/s: mean 199 ms (theory 200), p99 943 ms (theory 921)
# 4. Constant service time (M/D/1) at lambda = 45/s: the mean wait halves, 180 ms -> 90 ms
wait = fifo_latencies(arr, np.full(N, 1 / mu)) - 1 / mu
print(f"M/D/1 mean wait {1e3 * wait.mean():.0f} ms (theory 90)") # M/D/1 mean wait 89 ms (theory 90)
The simulated values land within a few percent of the theory. Near one run is noisy,
because the queue length changes slowly and one long burst can move the p99 of a whole run: at
, runs of 200,000 requests with other seeds give p99 values 10–20% above or below
the theory. Load tests near full load need long runs for the same reason. np.percentile interpolates between ranks by default; with many samples it agrees
with the nearest-rank value.
6. Where it shows up #
- ML products. Ranking, fraud and search models are APIs with latency SLOs, often called with fan-out (one page calls many models). LLM servers such as vLLM do not wait for a batch to form; they use continuous batching: new requests join the running batch at each decoding step. The ML systems track covers this (ML systems lesson 12).
- ML for science. A nowcasting service gets bursts on the hour, when many clients poll at once: not Poisson, so plan for the peak. A surrogate called from a climate or ground-motion simulation runs inside the simulation, but the batch rule is the same: one vectorised call for all grid columns or scenarios. Moving inference from batch jobs to an always-on service is the step from throughput thinking to latency thinking.
- Practice. Kubernetes probes and autoscaling on request rate or latency (lesson 04); Triton, TorchServe and KServe expose a max batch size and a max delay; capacity plans use Little's law and a target utilisation well below 1.
7. Common mistakes #
- Liveness that checks the model or a database: restart loops during slow loads, and one dependency outage restarts every replica.
- Loading the model per request or at import time. Load it once, in the startup (lifespan) handler.
- CPU-heavy inference inside
async defin FastAPI: it blocks the event loop for every request of that worker. Use a plaindefendpoint (FastAPI runs it in a thread pool) or worker processes. - A Python loop over the batch: no batching gain. Make one vectorised model call.
- Planning for the mean. At the mean latency is 20 service times. Report the p99, and never average percentiles across replicas: merge the histograms.
- An unbounded queue. Near or above capacity it grows without limit, and every request in it will miss the SLO anyway. Bound the queue and reject early (429 or 503), so that clients retry elsewhere.
- A long max queue delay "for throughput" at low traffic: it only adds latency.
8. Check yourself #
- A service handles 300 requests/s with a mean latency of 80 ms. How many requests are inside it
on average?
Answer
By Little's law, requests. This holds for any stable system, without assumptions about the distributions. - A batch takes ms. What is the maximum throughput with , with ,
and the limit for large ?
Answer
/s; /s; the limit is /s. - A request fans out to 50 shards and waits for all of them. You want at most 5% of requests to
be slow. How rare must slowness be for one shard?
Answer
gives : about 0.1% per shard, 50 times rarer than the target for the whole request. - The model needs 90 s to load. What do
/healthzand/readyzreturn during that time, and what does Kubernetes do with each?Answer
`/healthz` returns 200, so the kubelet does not restart the container. `/readyz` returns 503, so the Pod stays out of the Service's endpoints and gets no traffic. A startup probe with failureThreshold × periodSeconds above 90 s (for example 30 × 10 s) holds back the other probes during the load. - In part B of section 4 the traffic doubles to 800/s. Estimate the batch size and the mean
latency with the fixed-point formula.
Answer
, ms, mean latency about ms. The simulation gives a mean batch of 13.3 and a mean latency of about 28 ms (random bursts hit the cap ), with a p99 of about 48–49 ms: at the SLO limit, so add a replica. - Why is a bad operating point even if the p99 is still inside the SLO today?
Answer
The latency factor is 20 there, and it changes fast: 5% more traffic () makes the queue unstable, and a small burst or one slow replica moves the p99 far above the SLO. Plan for a target utilisation with margin, and let the autoscaler add replicas before gets close to 1.
State Little's law and what each symbol means.
: mean number in the system = arrival rate × mean time in the system. It holds for any stable system.
Give the M/M/1 mean latency.
, or with .
In an M/M/1 queue, how does the p99 latency relate to the mean latency?
The latency is exponential, so .
What is the utilisation of a replica?
, the share of time the replica is busy. It must stay below 1 for the queue to be stable.
A request waits for 20 backends, each slow 1% of the time, independently. Which share of requests is slow?
.
A batch of requests takes . What is the throughput, and its limit?
, which grows towards as grows.
What batch size does a busy dynamic batcher settle at?
The fixed point of , valid while and .
What does a max queue delay cost at low traffic?
Exactly the delay: each request waits the full delay and still runs alone.
Why must a model server's liveness probe not check the model?
A liveness failure restarts the container. During a slow model load the check fails, so the container is killed before it can finish: a restart loop. Readiness should check the model.
Why is an unbounded request queue a bug?
Near or above capacity it grows without limit, so latency grows without limit. Bound it and reject early (429 or 503).
9. Interview angle #
- "Design the serving endpoint for this model." — Schema and 422; single and batch endpoints with a size limit; liveness, readiness and startup probes; a version endpoint; the model loaded once; dynamic batching on a GPU; replicas from the p99 SLO and a target utilisation; autoscaling; p99 monitoring from merged histograms.
- "The p99 doubled, the p50 did not move. Where do you look?" — Utilisation near 1, one slow replica, batches that are too large, garbage-collection pauses, cold starts, a slow dependency in a fan-out. Fixes: more replicas, a lower target utilisation, timeouts, hedged requests.
- "Why not run at 95% load?" — , and small changes in traffic move the latency a lot.
- "Derive the mean latency of an M/M/1 queue." — Cut equations , geometric , , Little's law.
10. Practice #
The notebook for this lesson runs in Colab. It has five exercises, each with a check cell and a folded solution:
latency_percentiles(samples, qs)with nearest rank; compare withnp.percentile.fifo_latencies(arrivals, services), then check and against M/M/1 at two loads, and the halving of the wait with constant service times.simulate_batching(arrivals, a, c, max_batch, max_delay)with the dispatch rule of section 3.4; find the fixed point numerically and the load at which the p99 crosses the SLO.replicas_for_slo(peak, mu, p99_slo)for M/M/1 replicas.validate_request(body, schema)in plain Python: required fields, types, ranges and extra fields, with the list of errors.
11. Watch and read #
- Karl Sigman, Notes on Little's Law (the law, its sample-path proof, and the M/M/1 queue), and Continuous-Time Markov Chains (birth–death processes and the balance equations).
- J. Dean and L. A. Barroso, "The Tail at Scale", Communications of the ACM 56(2), 2013: fan-out, tail latency and hedged requests.
- NVIDIA Triton, Batchers: "Dynamic Batcher" and "Recommended Configuration Process".
- FastAPI, Lifespan Events: loading a model once at startup.
Runs in your browser with nothing to install. The notebook repeats the key demos and has exercises with checks; solutions are inside, folded.
No Google account needed: Python runs inside your browser. The first start downloads about 40 MB; your changes are saved in this browser only.