GRATÍCULAinstrumento de maestría
BancoRTX 5090 · GB202
Rev2026.06
Entrar
N1 · Serving world-class/L2

Tuning de vLLM: cerrar el gap conscientemente

Objetivo de maestría

conocer las palancas reales de vLLM, qué hace cada una al roofline/VRAM, y ajustarlas con una hipótesis (no por superstición). Cada flag aquí se justifica con la teoría de L1.


2.1Las palancas y qué tocan

FlagQué controlaEfecto
--gpu-memory-utilizationfracción de VRAM para pesos+KV+activaciónsube KV-cache disponible → más concurrencia; no bajar de 0.7 (prefix cache necesita ~30%)
--max-model-lenlongitud máxima de contextoreduce reserva de KV → primera palanca contra OOM
--max-num-seqsnº máximo de secuencias concurrentestecho del batch → throughput vs latencia
--max-num-batched-tokenstokens por iteración (prefill+decode)tamaño del trabajo por paso
--enable-chunked-prefilltrocea prefills largosmejora TPOT cuando hay prefills grandes mezclados con decodes
--enable-prefix-cachingreutiliza KV de prefijosenorme en workloads con prompts repetidos
--kv-cache-dtype fp8precisión del KV-cache~2× contexto/concurrencia, leve coste de calidad

2.2Memoria y concurrencia (el equilibrio base)

--gpu-memory-utilization reparte tu VRAM. Con 0.90 en una 5090, ~28.8 GB quedan para pesos + KV-cache. Si el modelo pesa 16 GB (8B BF16), te quedan ~12.8 GB de KV-cache → muchas peticiones concurrentes. Si subes el modelo a 14B (28 GB), casi no queda KV → OOM o concurrencia ínfima.

Orden correcto para resolver OOM (apréndetelo):

  1. Baja --max-model-len (la mayoría de cargas no necesitan 128K).
  2. Cuantiza el modelo (L4) para que los pesos ocupen menos y quede más KV.
  3. Solo en último caso toca --gpu-memory-utilization (y nunca <0.7).

2.3Chunked prefill: arreglar el TTFT con prefills largos

Problema: un prefill largo (un prompt de 30K tokens) monopoliza una iteración entera → todas las peticiones en decode se quedan esperando → su TPOT se dispara (jitter).

--enable-chunked-prefill parte ese prefill en trozos e intercala trozos de prefill con pasos de decode de otras peticiones. Resultado: TPOT más estable a costa de un TTFT ligeramente mayor para la petición de prompt largo. Imprescindible si mezclas prompts largos y cortos.


2.4Prefix caching determinista

Si muchas peticiones comparten prefijo (un system prompt, un preámbulo de herramientas, un few-shot), vLLM puede cachear el KV de ese prefijo y reutilizarlo entre peticiones (gracias a PagedAttention, L1.2). El prefijo solo se computa una vez.

bash
1vllm serve Qwen/Qwen3-8B --enable-prefix-caching \
2  --prefix-caching-hash-algo sha256_cbor   # hashing determinista y reproducible entre procesos

Por qué sha256_cbor y no el default: el hashing por defecto puede variar entre ejecuciones/entornos; sha256_cbor es estable y serializable, lo que hace el comportamiento del cache reproducible (clave para medir hit-rate de forma fiable y para entornos multi-proceso). Lo notarás cuando midas el caso prefix-heavy en L5/L6.


2.5KV-cache FP8: duplicar el contexto casi gratis

bash
1vllm serve Qwen/Qwen3-8B --kv-cache-dtype fp8

El KV-cache pasa de 2 bytes/elemento (FP16) a 1 (FP8) → cabe ~2× más contexto o ~2× más peticiones concurrentes en la misma VRAM. El coste de calidad suele ser despreciable para chat/tool-calling; mídelo tú (L4 te enseña a evaluar degradación).


2.6Laboratorio L2.1 — Barrido de configuración con prefijo compartido

Diseña una workload que comparta prefijo (para que prefix caching brille) y compara configuraciones.

python
1# lab_n1l2_prefix.py — mide el efecto de prefix caching con un system prompt compartido
2import asyncio, time, httpx
3
4URL = "http://localhost:8000/v1/chat/completions"
5MODEL = "Qwen/Qwen3-8B"
6# System prompt largo y COMPARTIDO por todas las peticiones (caso típico de agentes/RAG)
7SYSTEM = "Eres un asistente experto. " * 400   # ~prefijo grande y repetido
8QUERIES = [f"Pregunta {i}: resume el concepto número {i}." for i in range(64)]
9
10async def ask(client, q):
11    t0 = time.perf_counter()
12    r = await client.post(URL, json={
13        "model": MODEL,
14        "messages": [{"role": "system", "content": SYSTEM},
15                     {"role": "user", "content": q}],
16        "max_tokens": 128, "temperature": 0.0,
17    }, timeout=120)
18    return time.perf_counter() - t0
19
20async def main():
21    async with httpx.AsyncClient() as client:
22        # primera ronda: llena la prefix cache
23        await asyncio.gather(*[ask(client, q) for q in QUERIES])
24        # segunda ronda: debería beneficiarse del cache (mismo SYSTEM)
25        t0 = time.perf_counter()
26        lats = await asyncio.gather(*[ask(client, q) for q in QUERIES])
27        wall = time.perf_counter() - t0
28    print(f"2ª ronda: wall={wall:.2f}s  TTFT_medio≈{sum(lats)/len(lats):.3f}s")
29
30asyncio.run(main())

Corre el script dos veces: una con el servidor lanzado sin --enable-prefix-caching y otra con él (+ sha256_cbor). Compara el wall-time de la 2ª ronda. El servidor con prefix caching debería ser claramente más rápido porque no recomputa el SYSTEM de 400 repeticiones cada vez.

Líneas no triviales:

  • SYSTEM enorme y repetido: maximiza el ahorro del cache; en producción es tu system prompt + definiciones de tools.
  • Dos rondas: la primera llena el cache, la segunda mide el hit. Comparar entre servidores (con/sin flag) aísla el efecto.
  • vLLM expone métricas Prometheus (/metrics) con gpu_prefix_cache_hit_rate; consúltalas para el número exacto de hit-rate.

2.7Ejercicios

E1. Mide el hit-rate de prefix cache real vía /metrics (curl localhost:8000/metrics | grep prefix). Relaciónalo con el speedup de la 2ª ronda.

E2. Sube --kv-cache-dtype fp8 y mide cuánto más contexto (--max-model-len) puedes pedir sin OOM. ¿Coincide con el ~2× teórico?

E3. Con --enable-chunked-prefill, mezcla 1 petición de prompt de 20K tokens con 16 peticiones cortas. Mide el TPOT de las cortas con y sin chunked prefill. Documenta el jitter.

2.8Trampas comunes

  • Bajar --gpu-memory-utilization por debajo de 0.7 para "arreglar" OOM → mata el prefix cache; baja --max-model-len primero.
  • Medir prefix caching con una sola ronda (no hay hit que medir).
  • Asumir que KV FP8 degrada mucho sin medirlo.

2.9Referencias

  • Docs vLLM: "Optimization and Tuning", "Automatic Prefix Caching", "Chunked Prefill".