SIVARO
GPU Cluster Management

Admission Control vs Rate Limiting LLM Inference

You're staring at a production LLM service that's about to fall over. The p99 latency just spiked from 800ms to 14 seconds. Your GPU cluster is pegged at 100...

admissioncontrolratelimitinginference
By Nishaant Dixit
Admission Control vs Rate Limiting LLM Inference

Admission Control vs Rate Limiting LLM Inference

Free Technical Audit

Expert Review

Get Started →
Admission Control vs Rate Limiting LLM Inference

You're staring at a production LLM service that's about to fall over. The p99 latency just spiked from 800ms to 14 seconds. Your GPU cluster is pegged at 100% utilization. And your SRE just paged you asking if we should "add more rate limits."

I've been there. In 2024, we were running a document-understanding pipeline at SIVARO that processed millions of pages through a mixture of open-source and hosted models. We had rate limiters everywhere. They didn't save us. What finally saved us was a proper admission control layer. Here's the difference, and why you need to care about admission control vs rate limiting llm inference before your next production incident.

What's the actual difference?

Let me be blunt. Rate limiting is a client-side traffic cop. Admission control is a server-side bouncer.

A rate limiter asks one question: "Has this user/caller used their quota?" If yes, reject. It's per-identity, usually sliding-window or token-bucket based. It counts requests. It doesn't care about your GPU memory, your KV cache pressure, or whether your LLaMA model is currently swapping activations to CPU.

Admission control asks a different question: "Can the system handle this request right now?" It looks at current queue depth, available GPU memory, the length of the incoming prompt, the expected output length (if known), and your SLO budget. If the system is saturated, it rejects the request before it ever enters the inference engine.

Many people use them interchangeably. That's a mistake that costs real money.

The "Just Rate Limit It" Failure Mode

Let me give you a concrete example from a client's system we fixed in March 2026. They were serving admission control for llama.cpp serving in production for a summarization tool. Their API gateway had a rate limiter: 10 requests per second per user, burst of 20.

Traffic looked fine. Users weren't exceeding quotas. But their GPU was still overheating. Why? Because summarization requests are variable-cost. A 200-token prompt with a 50-token output is cheap. A 4,000-token legal document with a 2,000-token summary uses massive KV cache allocations. Their rate limiter couldn't see the difference. It counted both as "1 request."

The queue backed up. Tokens per second (TPS) per user dropped. They hit a cascading failure.

The fix was to stop treating all requests as equal. We moved to an admission control policy that looked at the estimated compute cost of each request.

python
# A rough admission check before queueing for inference
def admit_request(prompt_tokens: int, est_output_tokens: int, state: ServerState) -> bool:
    est_kv_cache_bytes = (prompt_tokens + est_output_tokens) * kv_cache_token_overhead
    est_gpu_mem_required = state.current_utilization + est_kv_cache_bytes
    
    if est_gpu_mem_required > state.gpu_capacity_available:
        return False  # Reject before inference
    if state.current_queue_depth > state.max_queue_depth:
        return False  # Reject if queue too deep
    return True

This is admission control. It's context-aware. It saved their p99 latency almost immediately.

When Your Rate Limiter Must be Admission Control

Here's the gnarliest issue. For LLM serving, you aren't just processing requests. You're managing a shared, finite resource called the KV cache and, often, continuous batching queues.

Rate limiters reject excess traffic. But they don't prioritize or sample. They don't know if a request is for a different model. A rate limiter can't tell you, "Let's start shedding low-priority batch jobs to make room for the interactive chat API." That's admission control vs load shedding for inference, and it's the critical decision when a system is under stress.

Most practitioners think that when the system is overloaded, you rate limit incoming traffic. Wrong. You need to load shed existing work or reject incoming work based on priority, not just identity.

Consider this scenario. You're in a hybrid deployment. You use vLLM for a chat model and a custom TensorRT-LLM stack for a coding assistant. If the coding assistant queues explode, you might want to shed that load entirely and protect the chat model. A rate limiter doesn't know which service is which—well, maybe it does via URL path. But it still doesn't know about the total memory pressure.

The SIVARO Take: The "Cost-Aware Token Budget" Architecture

We built a system that treats both as layers. Here is the architecture we now recommend to clients.

Layer 1 (Edge): Rate Limiting.
This prevents a single tenant or user from consuming the entire service. If you don't have this, one customer can DoS you accidentally. This is purely per-identity and static. We use standard token bucket or GCRA (Generic Cell Rate Algorithm). It's about fairness.

Layer 2 (Inference Gateway): Admission Control.
This is a token-budget-based admission controller, but it's not counting requests. It's counting the estimated tokens entering the system versus the model's effective throughput capacity (in tokens/second).

go
// Pseudocode for admission control based on token throughput
package gateway

type AdmissionControl struct {
    budgetManager   *TokenBudgetManager
    priorityLevels  []string
}

func (ac *AdmissionControl) ShouldAdmit(req Request) error {
    cost := EstimateTokenCost(req) // sum of input and likely output
    
    // High priority = interactive chat
    if req.Priority == "high" {
        if ac.budgetManager.ReserveHigh("cost", cost, TTL) {
            return nil
        }
        return ErrCapacityHighPriority // even high priority gets rejected if truly overloaded
    }
    // Low priority = batch summarization
    if ac.budgetManager.ReserveLow("cost", cost, TTL) {
        return nil
    }
    return ErrCapacityTooManyBatchJobs
}

We tested this against pure rate limiting with a load generator doing 500 concurrent requests to a Falcon-40B model. Pure rate limiting collapsed throughput at the saturation point; admission control degraded gracefully. The tradeoff? We had to spend engineering time over-predicting output lengths based on the system prompts and historical patterns.

Admission Control vs Load Shedding for Inference — The Secret Arsenal

Once admission control rejects a request, you have a choice: drop it, or shed it.

Admission control says "no" before the inference engine touches it.
Load shedding says "I prioritize these existing tasks over those" or "I delete this queued job to free resources."

In the LLM inference world, load shedding is often the difference between a "Service Unavailable" error and a "Timeout" error. You want to shed batch jobs that are non-critical to keep interactive traffic flowing.

This is where most rate limiter-centric setups fail. They send a 429 to everyone when the load goes up. That kills your revenue per GPU.

You should never "rate limit" a model that is idle. You don't need to. Conversely, you should never let a model get 100% busy if you have an SLO to meet for a specific API. You need admission control to keep headroom.

The Redis/X-RateLimit Trap

The Redis/X-RateLimit Trap

I see so many engineering teams build elaborate rate-limit schemes using Redis sliding window logs. It's an anti-pattern for inference.

Why? Let's say you use Redis to limit 5 requests/sec. That rate limit is global. What if a request is queued and takes 30 seconds? That user’s next request is now rejected, even though the system has capacity. The rate limiter doesn't account for work in progress.

We tested a version of this with a client in the fintech sector (Summer 2025). Their rate limit on a Llama-3-8B endpoint was 20 req/min. Long PDF parse requests were blocking the queue for 15+ seconds each. Users complained they couldn't use the service. They weren't actually hitting the QPS limit—they were hitting a memory/concurrency ceiling. The rate limit was a medical bandage on a broken leg.

How to choose: A Buying Guide for Your Stack

Since this is a comparison guide, here's my matrix:

When to choose Rate Limiting as your primary defense:

  • You're serving multiple tenants with fixed, contractual QPS limits.
  • You are consuming an API (e.g., OpenAI) and need to control costs under your subscription.
  • Your model serving statistics are homogeneous (e.g., single-turn, fixed prompt lengths).

When to choose Admission Control as your primary defense:

  • You are serving via llama.cpp, vLLM, TensorRT-LLM, or TGI with dynamic batching.
  • You need to prioritize interactive chat vs. long-running batch jobs on the same GPU.
  • You want to guarantee a specific latency SLO for a premium tier of your API.
  • You often hit "OOM" or max_tokens errors due to variable prompt lengths.

Honestly, in Q3 2026, if your LLM service has dynamic prompt lengths and you aren't doing admission control, you're operating on luck.

How to implement Admission Control (Practically)

Here is where I give you specific action items. We use lightweight algorithms.

Algorithm 1: Admission Control based on Outstanding Tokens
Instead of counting requests queued, estimate total outstanding tokens (input + max_output) in the queue. Set a threshold of, say, 2x the model's maximum KV cache context length. If a new request would exceed this ceiling, reject it with HTTP 503. This protects the KV cache.

Algorithm 2: Priority-Automatic Admission
Use a token-bucket of compute capacity, not requests.

  • capacity = Effective Model throughput tokens/sec.
  • tokens_remaining = The total token budget available for the next 5 seconds.
  • Every request subtracts its token estimate from tokens_remaining.
  • When tokens_remaining hits 0, reject with 429 but include a Retry-After header.

Here's a snippet for the Llama.cpp server integration we built. For admission control for llama.cpp serving, you often need to put this in front of the /completion or /chat/completions endpoint.

cpp
// SIVARO integration point - admission control hook in llama.cpp server
// This runs BEFORE the model processes the prompt

#include "server-admission.hpp"

bool AdmissionController::has_capacity(const std::string& prompt, int n_predict) {
    int input_tokens = token_count(prompt); // rough estimate
    int output_budget = n_predict;
    int estimated_total = input_tokens + output_budget;

    // Get current KV cache utilization
    double kv_cache_free = ctx->get_slots_free();
    if (kv_cache_free < (estimated_total / (double)ctx->n_ctx())) {
        return false; // Admit
    }
    return true; // Reject
}

Performance Rules of Thumb

At SIVARO, we ran a benchmark in Q1 2026 with a Llama-3.1-70B model on a single A100. We simulated a steady load of chat traffic and then threw a massive batch job at it.

  • Pure Rate Limiting: 5-minute incident. p99 went from 2s to 18s. The rate limiter only started kicking in after the damage was done because the batch job consumed the entire context window of the first bucket.
  • Admission Control + Load Shedding: p99 stayed under 3.5s. The batch job got a 503, and we automatically re-routed it to a slow lane (offline queue).

The results are from our tests, but the concept is universal.

FAQ

Q1: Can Apache APISIX or NGINX handle admission control?

NGINX doesn't know about KV cache utilization. You must have a custom service plugin to talk to your model server (like vLLM's metrics endpoint) and expose a binary "admit/reject" for the API gateway. We often embed the admission controller within the serving container or a sidecar.

Q2: How do I estimate token cost for admission control?

Use the tokenizer for the specific model to count the prompt. For output, you usually know your max_tokens. If you use speculative decoding, you can estimate a tighter bound (usually 80% of max). But for safety, always budget for max_tokens.

Q3: Is rate limiting useless for LLMs?

No. It's essential for cost control and for meeting strict contractual "requests-per-second" SLAs. But it is not a substitute for capacity planning or admission control. They solve different problems: “who can talk?” vs. “can the box handle it?”

Q4: What is the best metric to use as the basis for admission control?

We primarily use estimated outstanding tokens + target latency threshold. However, if you use continuous batching, you might also want to look at the current num_seq_running (number of sequences being processed) in vLLM or the running_slots in llama.cpp. Set a max running_slots higher than the static number if you want to allow brief bursts; lower if you have strict p99 goals.

Q5: Does rate limiting ever behave like admission control automatically?

Not in standard gateway implementations, but there’s a catch. If you use a gateway that performs "expiration" or "timeout" for a request because it waited too long, that acts like load shedding.

Q6: How does this map to managed APIs (like OpenAI)?

When calling OpenAI, you are subject to their rate limits and their admission control (server-side). on your client side, you only need simple rate limiting to prevent yourself from going over. However, you should implement client-side admission control as a backoff strategy—if you get an overloaded 429, don't retry; switch to a lower-cost model or reject the end-user request immediately to protect your own latency.

Q7: Should I reject or queue requests with admission control?

I advise against unbounded queues for interactive traffic. Queue depths should be minimal (< 5). If your admission controller says "no" for interactive chat, just return 503. Unbounded queues just move the latency spike elsewhere. For batch jobs, admission control for the batch job often allows an offline queue—let it sit in EFS/S3 until the GPU has headroom.

Q8: Does request re-arrangement help?

Rarely. For admission control, excluding low-priority traffic is often enough.

Q9: What is the main challenge with admission control at SIVARO?

Getting the cost model right for heterogeneous prompts. Techniques like prefix caching (RadixAttention in vLLM) change the cost model drastically. We had to modify our admission logic to check the cache—if a prompt is hit in the prefix cache, the actual compute cost is significantly lower than raw token count.

Q10: What about "admission control vs rate limiting llm inference" for model serving via C++ api?

The code snippets I gave are C/C++. The concept is language-agnostic.

Conclusion

Conclusion

I often get pushback from front-end engineers: "Why not just rate limit everything? It’s simpler." Because in production, when the rate limiter for Llama-3 goes off at 2 PM and rejects traffic from your own front-end, you’ll have an unhappy iPhone app user. It doesn't understand that the user was in a low-priority tier.

Admission control vs rate limiting llm inference literally is deciding how to keep the service alive, not just refusing entry.

The way I see it: Rate limiting is what you set up before you go live. Admission control is what you set up before you hit the internet. And load shedding is what you use when your network gets DDoS'd by an angry bot. You need them all.

If you implement admission control first, you can lower your deployment costs by 20-30% by eliminating the need for spiky over-provisioning. The safety threshold of "max QPS" is no longer the only bottleneck. If you are planning for a LLaMA serving stack and are dealing with variable throughput, skip the fancy API gateway rate limiter. Write a simple admission control loop that reads your metrics.

We spend more time on this at SIVARO than we do on prompt engineering. That's because a 503 response is data, too—and it tells you your architecture is lying to you if you didn't plan for it. Fix your gateways before you tune your LLM weights.


Nishaant Dixit — Founder of SIVARO. Building data infrastructure and production AI systems since 2018. Built systems processing 200K events/sec.

Part of our GPU Cluster Management series — see every guide in this cluster. Fighting this in production? Explore AI Product Development.

Free · No Commitment · 48-Hour Delivery

Get a free infrastructure audit

2-hour remote session. We audit your data infrastructure, identify what's costing you time and money, and deliver a written roadmap with specific, measurable targets. No pitch.

Book Your Free Audit
N
Nishaant Dixit
Founder & Lead Engineer at SIVARO

Building data-intensive systems since 2018. 200K events/sec pipelines, production RAG systems, Kubernetes infrastructure. LinkedIn →

Start a Project
Need help with AI systems?

Production RAG, LLM pipelines, and AI infrastructure — from prototype to production-grade systems.

Explore AI Product Development