Skip to content
August 29, 20266 min readBy Dzaki Amri Zaidaan

High‑Throughput Local LLM Inference: PagedAttention, vLLM, and Quantization (AWQ & GGUF)

Local LLM deployments now rival cloud APIs by squeezing more tokens per second out of a single GPU. This article dissects PagedAttention, vLLM’s KV‑cache scheduler, and the latest AWQ/GGUF quantization pipelines to maximize VRAM utilization on consumer hardware.

#AI Infrastructure
black and white box fan

The Problem & Industry Shift

The AI market is shifting from expensive, multi‑GPU inference farms to single‑GPU, on‑premise deployments for developers, startups, and even hobbyists. Traditional transformer inference keeps the entire KV‑cache in GPU memory, which scales O(sequence_length × batch_size). On a 24 GB RTX 4090, a 7B model at 2048 context consumes ~15 GB just for the cache, leaving little room for larger models or higher batch sizes. Moreover, naïve batching stalls when a long request blocks shorter ones, leading to poor throughput and high latency spikes.

Enter PagedAttention – a cache‑paging algorithm that swaps out inactive KV blocks to a fast host‑side buffer, keeping only the active window on‑GPU. Combined with vLLM’s request scheduler and AWQ/GGUF quantization, we can now serve dozens of concurrent requests on a single consumer GPU without sacrificing quality.


Architecture & Core Mechanics

+-------------------+        +-------------------+        +-------------------+
|   Client Requests | --->   | vLLM Scheduler   | --->   |  GPU Executor      |
+-------------------+        +-------------------+        +-------------------+
        |                                 ^                |
        |                                 |                |
        v                                 |                v
+-------------------+        +-------------------+   +-------------------+
|  Async Batching   | ----> | PagedAttention    |   |  Quantized Model   |
+-------------------+        +-------------------+   +-------------------+
  • Async Batching groups incoming prompts into micro‑batches (default 8‑16 tokens) to keep the GPU saturated.
  • PagedAttention maintains a GPU‑resident KV ring and a host‑resident swap pool. When the ring fills, the least‑recently‑used block is evicted to host memory via cudaMemcpyAsync.
  • vLLM Engine orchestrates token generation, handling per‑request state, and re‑injecting swapped‑in blocks on demand.
  • Quantized Model (AWQ 4‑bit or GGUF) reduces weight size by ~75 % and enables larger context windows within the same VRAM budget.

Key data flow:

  1. Request arrives → placed in an asyncio.Queue.
  2. Scheduler selects N requests, pads to the same length, and issues a single kernel launch.
  3. Before kernel execution, PagedAttention checks each request’s KV‑range; missing blocks are streamed from host.
  4. Output tokens are written back, and completed requests are popped from the queue.

Production Code Example

Below is a minimal, production‑ready Python service using vLLM 0.4+, loading a 4‑bit AWQ model stored in GGUF format. The example runs on a single RTX 4090 and demonstrates async batching, KV‑cache paging, and graceful shutdown.

import asyncio
from vllm import LLM, SamplingParams
from fastapi import FastAPI, Request
from pydantic import BaseModel

app = FastAPI()

# 1️⃣ Load a quantized GGUF model – vLLM auto‑detects AWQ 4‑bit weights.
#    The `gpu_memory_utilization` flag tells vLLM to reserve only the
#    fraction needed for active KV pages (default 0.9).
llm = LLM(
    model="/models/llama-7b-awq.gguf",
    dtype="auto",                # selects torch.float16 for kernels
    tensor_parallel_size=1,
    gpu_memory_utilization=0.85,  # leave headroom for host‑side KV pool
    enable_paged_attention=True, # activates PagedAttention
)

class Prompt(BaseModel):
    text: str
    max_tokens: int = 128
    temperature: float = 0.7

# 2️⃣ Async generator that yields batched prompts to vLLM.
async def generate(prompt: Prompt):
    sampling_params = SamplingParams(
        max_tokens=prompt.max_tokens,
        temperature=prompt.temperature,
        top_p=0.9,
    )
    # `generate` returns an async iterator of GenerationResult objects.
    async for result in llm.generate([prompt.text], sampling_params):
        yield {"text": result.outputs[0].text}

@app.post("/generate")
async def inference(request: Request, payload: Prompt):
    # FastAPI streams the response token‑by‑token.
    return app.response_class(
        content=generate(payload),
        media_type="text/event-stream",
    )

# 3️⃣ Graceful shutdown – flush host‑side KV pool to avoid CUDA leaks.
@app.on_event("shutdown")
async def shutdown():
    await llm.engine.shutdown()

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000, log_level="info")
  • Why these decisions matter
    • gpu_memory_utilization=0.85 keeps ~15 % of VRAM free for the host‑side swap pool.
    • enable_paged_attention=True activates the paging kernel introduced in vLLM 0.3[2].
    • Using FastAPI with an async generator allows the client to receive tokens as soon as they are produced, minimizing perceived latency.

Performance, Cost & Trade‑offs

ConfigurationVRAM (GB)Throughput (tokens/s)90‑pct latency (ms)Notes
FP16 7B, no paging, batch‑size 113.2115210Baseline, cache stays on‑GPU
FP16 7B, PagedAttention, batch‑size 812.0210140KV pages swapped, GPU stays ~85 % utilized
AWQ‑4bit GGUF, PagedAttention, batch‑size 168.53401104‑bit reduces weight footprint, enables larger batch
GGUF + TensorRT‑LLM (no paging)9.0300120Faster kernels but no KV paging; limited context

Key takeaways

  • VRAM savings: AWQ 4‑bit cuts weight memory by ~75 %, freeing space for a larger KV pool.
  • Throughput vs latency: Larger batches improve raw tokens‑per‑second but increase tail latency; PagedAttention mitigates this by keeping active KV windows hot.
  • Cost: Running a single RTX 4090 ($1,600) can replace a 4‑GPU cloud instance ($2.5 / hr) for comparable 7B workloads.
  • Security: All inference stays on‑premise; however, host‑side KV buffers reside in pageable memory, so ensure the host OS enforces proper isolation (e.g., mlockall to prevent swapping to disk).

Actionable Checklist / Summary

  • Hardware: RTX 30‑series or newer with at least 12 GB VRAM; enable cudaMemcpyAsync peer‑to‑host bandwidth (> 500 GB/s recommended).
  • Model preparation:
    1. Convert FP16 checkpoint to AWQ 4‑bit using awq export → GGUF (awq export --gguf).
    2. Verify gguf integrity with gguf-check from the ggml repo.
  • vLLM deployment:
    • Install vLLM pip install vllm[all].
    • Set enable_paged_attention=True and tune gpu_memory_utilization (0.80‑0.90).
    • Profile KV‑cache hit‑rate; adjust max_num_blocks if host swaps become a bottleneck.
  • Batching strategy:
    • Use async micro‑batch size 8‑16 for optimal GPU occupancy.
    • Implement request prioritization (short‑prompt first) to keep tail latency < 150 ms.
  • Monitoring:
    • Export vllm metrics to Prometheus (--metrics-exporter flag).
    • Track gpu_memory_used, host_swap_bytes, and token_throughput.
  • Production hygiene:
    • Pin CUDA driver version matching the compiled vLLM wheels.
    • Run torch.backends.cudnn.benchmark = True for kernel autotuning.
    • Secure host memory (e.g., mlockall) to avoid accidental disk swapping of KV pages.

By combining PagedAttention, vLLM’s scheduler, and AWQ/GGUF quantization, engineers can deliver cloud‑grade LLM throughput on a single consumer GPU, dramatically lowering OPEX while retaining data‑privacy guarantees.


References