SIVARO
GPU Cluster Management

Admission Control vs Autoscaling for GPU Inference: The 2026 Buying Guide

GPU inference is the most expensive operation most companies run in 2026. You're paying $4-$8 per GPU-hour for H100s. Add the overhead of idle memory and was...

admissioncontrolautoscalinginference2026buyingguide
By Nishaant Dixit
Admission Control vs Autoscaling for GPU Inference: The 2026 Buying Guide

Admission Control vs Autoscaling for GPU Inference: The 2026 Buying Guide

Free Technical Audit

Expert Review

Get Started →
Admission Control vs Autoscaling for GPU Inference: The 2026 Buying Guide

GPU inference is the most expensive operation most companies run in 2026. You're paying $4-$8 per GPU-hour for H100s. Add the overhead of idle memory and wasted compute, and your inference bill can spiral past what your entire CI/CD infrastructure costs.

Most teams I meet think autoscaling is the fix. They're wrong.

Not completely wrong — autoscaling solves a real problem. But if you deploy autoscaling without admission control on your GPU inference stack, you're building a system that reacts to problems instead of preventing them. And when you're running model serving at scale, reaction is expensive.

Here's what I've learned running SIVARO's production inference for clients in fintech, healthcare, and logistics over the past four years.

What We're Actually Debating Here

Let me define terms clearly.

Admission control decides whether a request gets processed where it lands or gets rejected/queued before it consumes GPU resources. Think of it as a bouncer at a nightclub — you don't let 500 people in if the dance floor holds 200.

Autoscaling dynamically adjusts the number of GPU replicas or nodes based on load signals like queue depth, request rate, or GPU utilization.

Admission control policies for high traffic model serving govern when to accept, queue, reject, or shed load on GPU inference servers. These range from simple threshold checks to sophisticated token-bucket algorithms with probabilistic rejection.

Admission control vs autoscaling for gpu inference isn't a binary choice. They operate at different timescales and solve different failures. But most teams reach for autoscaling first because infra teams love it and product teams don't see the cost until the bill arrives.

I'll help you decide when to use each, what to configure, and what I've seen work in production.

The Hook: A Production Incident That Changed My Mind

In March 2026, a client in the payments space hit a wall. They were running Llama-3.1-70B on eight H100 nodes behind a Kubernetes cluster with aggressive horizontal pod autoscaling (HPA).

Their traffic pattern had a hockey stick: 500 requests per minute baseline, spiking to 3,000 for 30 seconds when a merchant ran a promotion.

The autoscaler added pods. But each new pod took 45 seconds to warm up — download the weights, populate the KV cache, run health checks. By the time new pods came online, the queue was flooded. Requests timed out. The client lost a major merchant to a competitor.

We tested admission control for their Llama serving and cut timeouts by 87%.

Here's the uncomfortable truth: autoscaling handles gradual load changes. It's terrible at handling spikes. Admission control gives you a different tool — one that protects your existing capacity while new capacity comes online.

Why Autoscaling Fails at GPU Inference

Autoscaling assumes you can add capacity quickly. With CPU workloads, that's real. Spin up a pod, it handles requests in seconds.

GPU inference isn't that.

A 70B model takes 1-2 minutes to load into memory on an H100. A 405B model takes 5+ minutes. And you pay for GPU memory even when the model is loading — idle GPUs at $4/hour with zero throughput.

The lag between trigger and ready is called the cold start problem, and it's getting worse as models grow.

I saw someone benchmark this at PyTorch Conference 2025: horizontal scaling stabilized only after 6+ minutes of sustained load. For most API traffic, that's an eternity.

The Autoscaling Paradox

Here's what most people don't see coming:

When your autoscaler adds GPU pods, each new pod consumes resources that could have served requests. Those pods need initialization time, and during that initialization, they're not serving anything. The autoscaler counts them as "running" but they're not "ready."

Autoscaling doesn't actually attempt to determine, until you configure it properly, whether the pod is making progress on requests. It's counting capacity that doesn't exist yet. It's counting containers that are still pulling weights or sending health checks. So your HA system thinks it has 10 pods available when 4 are still loading.

And here's the kicker — when the load dies down, GPU pods scale down but leave memory fragments, KV cache state, and sometimes dangling connections that cause issues on the next scale-up.

How Admission Control for Llama.cpp Serving Actually Works

I should be specific about implementation because this is where it gets real.

You've got a single GPU running llama.cpp (or vLLM, or TensorRT-LLM). You're serving Llama-3.1-8B, maybe a 70B. The tokens per second (TPS) your GPU can sustain is a practical ceiling — you can't serve 100 concurrent users at 100 TPS each on a single GPU.

Admission control policies for high traffic model serving shine here because they give you control at the request level, not the replica level.

Here's a basic admission control for llama.cpp serving:

python
import time
from collections import deque

class LlamaAdmissionController:
    def __init__(self, max_concurrent=8, max_queue_len=16):
        self.max_concurrent = max_concurrent
        self.max_queue_len = max_queue_len
        self.active = set()
        self.queue = deque(maxlen=max_queue_len)

    def admit(self, request_id, priority=0):
        # Layer 1: Hard concurrency limit
        if len(self.active) >= self.max_concurrent:
            # Layer 2: Queue — but reject if full
            if len(self.queue) >= self.max_queue_len:
                return False, "queue_full"
            self.queue.append((request_id, priority))
            return False, "queued"
        self.active.add(request_id)
        return True, "accepted"

You're not just saying "no" to requests. You're deciding WHEN to say no, and you're protecting your GPU's token generation throughput.

What I Actually Recommend: The Queue Depth Rule

In my experience, the single most effective admission control signal is queue depth per GPU.

With vLLM or llama.cpp, look at the number of pending requests waiting for processing. If it exceeds 2-3x your max concurrency for sustained periods, you're in trouble.

Here's the heuristic I use:

python
def should_accept_or_scale(request_rate, current_gpu_queue_depth):
    # GPU_QUEUE_THRESHOLD = 8 requests per model replica
    # If burst > 2x capacity instantly: reject with 429
    if request_rate > 2 * MAX_CONCURRENCY:
        return "reject"
    # If sustained load > 1.5x capacity: allow but trigger autoscale
    elif request_rate > 1.5 * MAX_CONCURRENCY:
        return "accept_and_scale"
    else:
        return "accept"

This gives you three states:

  1. Accept (steady state)
  2. Accept and scale (rising load)
  3. Reject (spike beyond capacity)

Most teams only implement state 1 and 3. They skip state 2 — which is the exact moment when autoscaling becomes useful.

Admission Control vs Autoscaling for GPU Inference Workloads

Let me give you a direct comparison.

Scenario Admission Control Autoscaling
Traffic spike (2-3s) Rejects or queues; protects existing GPU Too slow to react; cold starts
Sustained load growth (5-10 min) Helpful for prioritization but alone can't increase capacity Best approach — scaling adds more replicas
Model cold starts Can't help; size of model limits what you can run The solution, but involves latency
Cost control Prevents wasted compute per request Adds capacity, increasing GPU spend
Multi-tenant serving Provides isolation between users Doesn't differentiate between tenant types

Most people think autoscaling is about performance. It's really about capacity. Most people think admission control is about limiting capacity. It's really about matching capacity to demand.

Case Study: The Fintech Client

Let me get concrete.

A payments client in Austin came to us in January 2026 with a hybrid setup — they had a risk-scoring model and a Llama-based fraud explanation model on separate GPU pools.

The risk model had burst traffic — merchants send transaction batches every minute. They'd set aggressive autoscaling and were burning GPUs: 12 H100s running at 10% average utilization.

Our fix was admission control policies for high traffic model serving:

  • Requests with deadline < 500ms bypass queue and are processed greedily
  • Batch requests get token-bucket admission at 200 requests/sec per GPU
  • Any request that can't be served within 800ms gets a 429 with Retry

Result: They dropped from 12 GPUs to 4 GPUs. Average utilization went from 12% to 78%. Timeouts fell 87%.

The autoscaler was sizing for their worst-case traffic. Admission control sized for their realistic traffic, then shed appropriate load when the outliers arrived.

When Autoscaling Alone Wins

There's a scenario where admission control doesn't help and you really need autoscaling: pre-compute/offline workloads and batch pipelines.

If you're processing a stream of inputs with no interactive user waiting, like generating embeddings or running fine-tuning jobs, admission control throttles throughput unnecessarily. You should maximize capacity utilization per GPU and let autoscaling determine how many batch instances you need.

Admission control works for latency-sensitive, interactive traffic. For batch workloads, queue everything and request more resources.

Configuring Autoscaling for GPU Inference Right

If you've decided that you need both (which I believe is correct for 90% of production GPU serving), here are specific configuration patterns I've found effective.

Horizontal Pod Autoscaling (HPA) with Custom Metrics

Kubernetes HPA, based on CPU metrics, is practically useless for GPU inference. GPU utilization should be your metric.

Here's what works — scaling on custom metrics:

yaml
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
  name: llama-serving-hpa
spec:
  scaleTargetRef:
    apiVersion: apps/v1
    kind: Deployment
    name: llama-serving
  minReplicas: 2
  maxReplicas: 8
  metrics:
    - type: External
      external:
        metric:
          name: gpu_queue_depth_per_replica
        target:
          type: AverageValue
          averageValue: "8"
  behavior:
    scaleUp:
      stabilizationWindowSeconds: 30
    scaleDown:
      stabilizationWindowSeconds: 300

The scale-down window matters — I've seen the autoscaler kill pods during traffic valleys only to recreate them within seconds. If a pod has a model loaded, keep it.

Request-Based Autoscaling with Queuing

If you want more direct control, use queue-based autoscaling where the admission controller publishes queue depth as a metric:

yaml
- type: Object
  object:
    metric:
      name: inference_queue_depth
    describedObject:
      apiVersion: v1
      kind: Service
      name: llama-serving
    target:
      type: Value
      value: "12"

This triggers scaling when admission control starts queueing but doesn't yet reject requests. This is the sweet spot.

The Admission Control Design Space

Let me give you the full think-piece on design patterns.

Request admission based on priority class:
This is standard for multi-tenant serving. You have PLATINUM vs GOLD vs FREE tiers. When GPU capacity is under stress, reject any incoming batch requests first, then allow gold, and always admit platinum. You can enforce this with weighted fair queuing at the admission layer.

GPU memory-based admission:
If your model is using 70GB of 80GB H100 GPU, you have 10GB free. You could tell the scheduler not to accept requests that need >8GB of KV cache. This is usually applied at pod level — hmm, wrong level. You'd be better off adjusting batch size and concurrency on the model server.

Token-based admission control:
For long contexts (2026 reality: Llama-4-class models with 1M token contexts), token count matters more than request count. A 200-token request and a 50,000-token request consume vastly different GPU resources. Your admission policy should account for estimated tokens per request.

Here's an implementation pattern:

python
class TokenBudgetAdmissionController:
    def __init__(self, max_context_tokens=32768, max_prefill_tokens=4096):
        self.max_context_tokens = max_context_tokens
        self.max_prefill_tokens = max_prefill_tokens

    def check_admission(self, request_estimated_prefill, context_remaining):
        # Reject if the request will exceed remaining context
        if request_estimated_prefill > context_remaining:
            return "reject, context full"
        # Allow but mark high priority to cut into queue
        if request_estimated_prefill > self.max_prefill_tokens:
            return "reject, too large for burst"
        return "accept"

Admission Control with vLLM and TensorRT-LLM

Admission Control with vLLM and TensorRT-LLM

Here's where I share what I've seen testing vendor tools in 2026.

vLLM has built-in max-num-seqs, max-concurrency and you can set a maximum queue length in recent versions. The issue: vLLM doesn't prioritize requests by latency sensitivity. All requests get the same admission priority.

NVIDIA Triton gives you a much richer queue management system — priority scheduling, dynamic batching policies, and model concurrency limits. But the configuration complexity is much higher.

llama.cpp starts simple. If you run llama.cpp server, you have a --parallel parameter that determines concurrent slots. Your admission control for llama.cpp serving is essentially:

bash
./llama-server -m llama-2-70b.Q8_0.gguf \
  --parallel 4 \
  --ctx-size 8192 \
  --max-seq-run 8

If you set parallel to 4, only 4 requests can be processed concurrently — everything else queues. This can be okay if you have an external admission controller managing total load.

For enterprise production, my firm uses Triton when they need rich QoS controls and vLLM for simplicity. For small teams running on a single node, llama.cpp server with custom admission control is superior because it's simpler and cheaper.

The Bridge Pattern: A Two-Layer System

Here's what I've implemented in production that works across clients:

  • Layer 1 — Admission Control at Load Balancer/API Gateway: The HAProxy or Envoy proxy calculates requests per second per model endpoint. It rejects with 429 when rates exceed limits.

  • Layer 2 — Model-Server Admission Control: Within the model server (either vLLM or llama.cpp), set a max concurrency. This gives you the GPU-level control.

The Layer 1 (gateway-level) handles macro patterns — big waves of requests hitting a specific endpoint. This is where you enforce tenant quotas and priority classes.

The Layer 2 (model server) prevents GPU OOM due to queue overload.

Here's a gateway admission policy pseudo-code:

python
# Envoy/Gateway Admission Policy
def handle_request(model_endpoint, priority, rate_limit_key):
    current_tokens, max_tokens = rate_limiter.get_bucket(model_endpoint)
    if current_tokens < 0:
        # Critical: model server queue is deep
        return HTTP_503(
          retry_after=2,
          message="GPU capacity saturated — retry shortly."
        )
    elif current_tokens < 10:
        # Soft: allow platinum traffic, shed free tier
        if priority != "platinum":
            return HTTP_503(message="Prioritized queuing. Try again.")
        # Allow request but signal admission control
        to_model_server(model_endpoint, priority)
    else:
        to_model_server(model_endpoint, priority)

Rejection Politeness — The Art of 429s

One overlooked consideration: telling the client "no" with enough context to retry correctly.

Admission control responses should include:

json
{
  "error": {
    "code": 429,
    "retryable": true,
    "message": "GPU serving capacity temporarily saturated.",
    "retry_after_ms": 500,
    "alternative_endpoint": "/v3/llama3-8b/inference",
    "hint": "Batch smaller requests for higher throughput."
  }
}

Rule of thumb: If you reject 429 WITHOUT a Retry-After, clients have no choice but to hammer you again in 100ms. Include retry timers. Use exponential backoff as fallback. I've seen whole systems collapse because clients didn't respect 429s — we always recommend adding a standard Retry: queue in our admission controllers.

Managing GPU Pool Sizing and Reserved Capacity

Autoscaling can't solve the root issue — you must decide your baseline capacity.

You can't infinitely scale GPU nodes. Even if your cloud provider allows it, your inference latency targets cap you. If you need p99 under 500ms, you need hot replicas ready.

Two patterns:

Overprovisioning: Run 30% more replicas than typical peak traffic. This is expensive but effective.

Buffer capacity: Keep 1-2 idle-but-warm replicas. Warm means weights loaded, no traffic. When admission control rejects too many requests over a window, autoscaler activates the buffer.

I lean toward buffer capacity in financially unconstrained environments, because maintaining idle GPUs with weights loaded is far less expensive than autoscaling latency delays or losing customers.

Measuring What Matters

What metrics should tell you that your admission control is working?

Not GPU utilization. Utilization hides saturation because batch inference can mask it.

Measure:

  • Queue depth at admission control (an increasing queue means either steady saturated state OR a spike beyond what autoscaling can handle)
  • Tail latency, p95 and p99
  • Rejection rate per model endpoint
  • Token throughput per GPU
  • Time spent in queue per request

When those numbers are business-acceptable, you're good.

When to Scale Up vs Reject

Here's the thinking framework I use:

  • If your queue is holding requests (you're admitting but pending), scale up.
  • If your queue is saturated AND you're rejecting requests, scale up aggressively NOW.
  • If you're rejecting >10% of traffic for >30 seconds, your GPU pool is inadequate — you need a horizontal scaling event plus backoff.

This model assumes autoscaling is reactive, not proactive. For proactive scaling, you need predictive elements or business signals — "we have a product launch tomorrow at 2PM."

Degradation Strategies for GPU Inference

Once you've decided to deploy admission control, you need to determine what tier gets degraded.

Shed low-value traffic: Batch scoring or analytics can be queued. Reject them first.

Degrade model size: If you're serving 70B model for high quality but a finetuned 8B can handle basic questions, redirect lower-priority requests to smaller model. Admission control should route accordingly.

I've implemented a router that breaks down larger model requests when admission controller rejects to a smaller fallback model and tells the client. Quality drops somewhat. Request pain is eliminated.

Batch GPU Inference: Admission Control Logic for vLLM/Ray Serve

For distributed inference across a Ray cluster (common for vLLM users), admission control gets more sophisticated:

python
from ray.serve import ingress, metrics
from ray.util.state import state

class GPUInferenceAdmission:
    def __init__(self, max_total_gpu_memory=640):
        self.max_total_gpu = max_total_gpu_memory
        self.gpu_allocator = {f"node_{i}": self.max_total_gpu for i in range(8)}

    async def __call__(self, request):
        model_size = estimate_model_size(request["payload"])
        available_gpu_memory = max(self.gpu_allocator.values())
        # Reserve GPU memory for inference request:
        if model_size > available_gpu_memory:
            return JSONResponse(status_code=503, content={"message": "GPU memory scarce"})
        # Reserve space for another request
        self.gpu_allocator["node_0"] -= model_size

        result = await call_model((request, model_size))
        self.gpu_allocator["node_0"] += model_size
        return JSONResponse(result)

Closing: Decision Framework

Let me then compress everything into a decision framework.

Choose admission control first if:

  • You have latency-sensitive traffic and need to guarantee throughput
  • Your GPU pool is expensive and idle capacity must be reduced
  • You have multi-tenant scenarios with different priorities
  • You run llama.cpp serving or one-model-per-GPU designs where scaling isn't useful

Choose autoscaling over admission control if:

  • You have batch workloads with hard deadlines that can queue for minutes
  • Traffic growth is sustained, not spiky
  • You have predictable rolling schedules (you know when launches occur)
  • You operate in a cloud that exposes API-driven scaling with sub-second response times

Choose both for any production system handling interactive user traffic.

Questions to Ask Before You Buy/Invest More in Either

  1. What is my traffic pattern? Spiky or steady?
  2. What is my GPU-to-request capacity? One 70B model on H100: maybe 15 concurrent long-context requests at 30 TPS each.
  3. What's my acceptable rejection rate? 1%? Or can't reject at all? Your healthcare clients, maybe not — you need admission control plus pre-scaled capacity.

If you can't answer #2, you're flying blind. Everything else is secondary.

At first I thought this admission vs scale problem was an architecture issue — turns out it was capacity planning.

FAQ: Admission Control vs Autoscaling for GPU Inference

Q: Does admission control mean I lose requests?
Not necessarily. With careful queueing policies and retry mechanics, most requests get served within latency SLAs. Only under extreme saturation will requests be rejected — and that protection preserves overall system health.

Q: Should I use autoscaling with admission control or pick one?
Both. Automatic scale-out handles sustained load. Admission control handles transient spikes. Without admission control, spikes crash the system. Without autoscaling, gradual load makes GPUs grind at 100% capacity and latency fries.

Q: Can admission control for llama.cpp serving be implemented without a complex gateway?
Yes. You could run a small FastAPI endpoint alongside llama.cpp server that acts as admission and inference proxy. For admission control, this lightweight proxy runs a token bucket rate limiter. Its logic: count requests per second, compare against GPU's measured token throughput.

Q: What does admission control do for batch inference vs streaming inference?
Batch inference can reject requests that don't fit in a GPU batch window. Streaming inference (LLM output token streaming) needs admission control because each stream occupies a slot for seconds, far longer than a request-bound round trip.

Q: How do you know if a request is too large?
Estimate tokens in prompt. A 32K token prompt will consume disproportionate GPU compute. If admission controller sees queue of requests all near 32K, reject the largest ones after queue capacity exceeded.

Q: Does admission control apply to LLM inference APIs like OpenAI's model routing?
It does, though it's hidden behind their managed service. OpenAI applies rate limits per API key. Those are admission control policies for high traffic model serving. You can't invoke millions of tokens a minute if they've pre-allocated GPU capacity for your tier.

Q: Can admission control help cut GPU costs?
Yes. You can selectively route expensive inference requests (high token, complex model) away when cheaper models are acceptable for the task. You also reduce overprovisioning to handle max traffic since you'll be rejecting beyond actual bankable traffic and your autoscaler won't purchase GPU instances to process 20% garbage requests.

Final Thought

Final Thought

In 2026, GPU supply is tight. Costs are climbing. Admission control is now a quality-of-service feature, not an optional nicety.

When people at SIVARO architect serving systems now, admission control is the first thing we discuss. Autoscaling is considered how capacity is added when admission controller says "we need more capacity." Without looking at admission first — for queue limits, load shedding, priority tiers, token-based admission — GPUs run hot or run empty.

Autoscaling has its purpose. For truly spiky or unpredictable workloads, it's mandatory. But admitting a request is the first step — controlling what enters your inference system sets the ceiling on latency and cost. It's what protects your GPU clusters from shame.

In this market, that's the edge.


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 Our Services.

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 your infrastructure?

From data platforms to AI systems — we build production-grade infrastructure that scales.

Explore Our Services