Start studying
Module 14 · MLOps / LLMOps Module contents
Module 14  ·  Card 16 / 22intermediate

Rate Limits

Rate limits cap how many requests or tokens you may use per period.

Providers enforce them to keep capacity fair across customers.

Exceeding one returns a 429 response.

Retry with exponential backoff and jitter rather than immediately.

You should apply your own limits to your users too, protecting cost and capacity.

Learn in depth ↓
Module 14 · MLOps / LLMOps · 22 concepts
In depth  /  15 lines

Rate Limits, in more detail

Rate limits cap how much you may use in a period.

Providers enforce them to keep capacity fair and to protect their systems.

They are usually expressed in requests per minute and tokens per minute.

Tokens per minute is the one that bites first for large prompts.

Exceeding a limit returns a 429 response.

Retrying immediately makes it worse, since your retries are also requests.

Exponential backoff waits longer after each failure.

Jitter adds randomness so many clients do not retry in unison.

Without jitter, a burst of retries arrives together and fails together.

Cap the total retries and fail cleanly rather than retrying forever.

Respect the retry-after header when one is provided, since it beats guessing.

Queue work rather than firing it all at once, especially in batch jobs.

Apply your own limits to your own users, or one caller can consume the whole quota.

That protects both cost and availability for everyone else.

Monitor how close you run to the limit, because growth arrives without warning.

Want the full technical article? Wikipedia ↗
Deep dive

What is underneath Rate Limits

The token bucket

tokens←min⁡(B,  tokens+rΔt)\text{tokens} \leftarrow \min\big( B, \; \text{tokens} + r \Delta t \big)
  • BB: the bucket size, which is the burst allowance.
  • rr: the refill rate, which is the sustained limit.

A request takes one token and is allowed if one is available. The bucket refills continuously, so idle time accumulates into permission to burst.

burst=B,sustained=r\text{burst} = B, \qquad \text{sustained} = r

Those two numbers are the whole policy, and they are independent: a bucket of 100 refilling at 10 per second allows a burst of 100 and then 10 per second forever.

A fixed window counter cannot express that, and it also allows twice the limit across a window boundary, which is why the bucket is the standard.

The two limits that bind on a model API

Model APIs limit requests per minute (RPM) and tokens per minute (TPM).

Token limits are usually the binding one, and they are charged on input plus expected output, which means the limiter must estimate the completion before it runs.

reserve nin+max⁡_tokens, refund the difference\text{reserve } n_{\mathrm{in}} + \max\_\mathrm{tokens}, \text{ refund the difference}

Setting a large maximum output therefore reduces throughput even when the responses are short, because the reservation is taken up front.

Reading the headers instead of guessing

Responses carry X-RateLimit-Remaining, X-RateLimit-Reset and Retry-After headers.

The server publishes its own state, and a client that reads those headers can slow down before it is refused rather than after.

A 429 means back off, not retry at once.

A retry that ignores the reset time makes the overload worse and can extend the penalty.

One client, in order

  1. Keep a local bucket sized slightly below the published limit.
  2. Take tokens before issuing the request, and wait rather than fail when empty.
  3. Update the local state from the response headers after every call.
  4. On a 429, honour Retry-After, or back off exponentially with jitter if absent.
  5. Log rejections separately from errors, because they mean capacity rather than failure.

Queueing, and what it costs

W=LλW = \frac{L}{\lambda}

Little's law says the wait is the queue length over the arrival rate, so a queue absorbs bursts by converting rejections into latency.

λ>r  ⟹  L→∞\lambda > r \implies L \to \infty

If the sustained arrival rate exceeds the refill rate, the queue grows without bound and every request eventually times out.

A bounded queue with a rejection at the tail is therefore correct, and an unbounded one is a way of failing later and worse.

Priorities help when they reflect a real difference. An interactive request and a batch job in the same queue means the user waits behind work that nobody is watching.

More keys do not mean more capacity.

Splitting traffic across keys usually shares one account-level limit, so it adds complexity and no throughput.

Higher limits come from the provider, and the code's job is to use what exists without wasting it.

Test yourself

One question on this card. Every option explains itself once you pick, right or wrong.

Question 1
You get a 429 response from an API. What should your code do?
MLOps / LLMOps

22 concepts in this module, then a graded test with every answer explained.

Study this module →
↑↓ move↵ openesc close