Skip to content

English · Español

05 — Un presupuesto de latencia trabajado para el mini-GPT en i5-8250U

🇪🇸 Un presupuesto de latencia es una hoja de cálculo, no una intuición. Para servir el mini-GPT del §A13 sobre un i5-8250U vía FastAPI, sumamos cada componente y vemos quién manda. La respuesta para este modelo a esta escala: la gestión del KV-cache no es el cuello de botella; lo es la falta de batching cuando llegan varias peticiones a la vez. Cross-ref: Fase 22 (KV-cache), Fase 41 (el portal es el ejemplo vivo).


El presupuesto

Máquina de Borja: Intel i5-8250U, 4C/8T Kaby Lake R, 62 GiB RAM, sin CUDA. El modelo tutor de gramática del §A13 es microscópico: ~500k parámetros, vocabulario de ~600 formas, ventana de contexto 64. Una sola petición del tutor codifica una frase corta en inglés, ejecuta el forward pass sobre el prefijo y luego decodifica auto-regresivamente la corrección (típicamente ≤ 16 tokens) más una glosa en español (típicamente ≤ 8 tokens).

Nomenclatura, todo por petición:

  • t_parse — parseo JSON + validación Pydantic de la petición entrante.
  • t_tokenize — codificación BPE de la frase de entrada (tokenizer de la Fase 11).
  • t_prefill — primer forward pass sobre el prefijo completo (N tokens de entrada, una sola pasada).
  • t_decode_step — un paso de decode auto-regresivo (hit en el KV-cache para todos los tokens previos).
  • t_detok — destokenizar los ids de salida de vuelta a una cadena UTF-8.
  • t_serialize — serializar la respuesta a JSON.
  • K — número de tokens de decode que generamos.
  • t_total = t_parse + t_tokenize + t_prefill + K · t_decode_step + t_detok + t_serialize.

Números medidos en i5-8250U (NumPy + attention hecha a mano, un solo hilo)

Estas son las cifras de orden de magnitud que produjo el lab del KV-cache de la Fase 22. Son deliberadamente conservadoras — tu wall-clock caerá dentro de un factor 2× en cualquier dirección dependiendo del proveedor de BLAS y el estado térmico.

Componente Coste (ms) Notas
t_parse 0.2 FastAPI + Pydantic, dict único, sin modelos anidados.
t_tokenize 0.8 BPE de 600 merges, pre-tokenize por regex, ≤ 32 tokens de entrada.
t_prefill (24 toks entrada) 18 Una matmul por capa × 4 capas, attention es O(N²) pero N=24.
t_decode_step (KV hit) 6.5 Lineal en la longitud cacheada pero el cache es pequeño (≤ 64).
K (tokens de decode) 20 Corrección + " / " + glosa en español, p50.
t_detok 0.3 Merge inverso.
t_serialize 0.2 Volcado JSON.
t_total (petición única) ~150 0.2 + 0.8 + 18 + 20·6.5 + 0.3 + 0.2 ≈ 149.5.

p50 = ~150 ms. p95 con frases más largas y glosas en español que estiran K=28: ~190 ms.

¿Dónde está el tiempo?

El decode domina. 20 · 6.5 = 130 ms de los 150 ms totales — el 87% del wall-clock está en el bucle de decode. El prefill es un blip único de 18 ms; todo lo que lo rodea (parse, tokenize, serialize) suma ≤ 2 ms combinados.

Esta es la imagen correcta para un modelo microscópico en CPU. No es la imagen para un modelo de 7B en GPU, donde el prefill crece a segundos y el decode se amortiza por token de salida. La forma del presupuesto cambia con el modelo, pero la disciplina — medir, atribuir, luego optimizar el término dominante — no.

¿Entonces es el KV-cache el cuello de botella?

Para una sola petición: no. El KV-cache (Fase 22) está haciendo su trabajo — sin él, cada paso de decode tendría que recomputar attention sobre el prefijo completo y t_decode_step crecería de 6.5 ms a ~18 ms (el coste del prefill). El cache nos ahorra aproximadamente \(20 \cdot 11.5 \approx 230\) ms por petición, dividiendo la p50 a la mitad. El cache es la diferencia entre un tutor que se siente instantáneo y uno que se siente atascado.

Con lo que el cache no ayuda es con el throughput bajo carga concurrente. Cada cliente conectado tiene su propio KV-cache; los bytes son baratos (el modelo es diminuto) pero la CPU es compartida. Dos clientes pegándole a /correct a la vez sin batching simplemente serializan en la matmul de NumPy bound por el GIL. La p50 se duplica. La p95 se cuadruplica.

El cuello de botella real: falta de batching

Ejecuta el barrido de throughput de lab 02 + lab 03. La forma:

Clientes concurrentes Sin batching p50 Static batch p50 Continuous batch p50
1 150 ms 150 ms 150 ms
2 295 ms 175 ms 165 ms
4 590 ms 220 ms 195 ms
8 1180 ms 320 ms 240 ms

Sin batching, la p50 crece linealmente con la concurrencia — cada petición espera a que la anterior termine un token. Con static batching (recoger 4 peticiones, ejecutarlas como una matmul), el coste de la matmul sube sub-linealmente porque BLAS amortiza el overhead del FMA; la p50 crece aproximadamente de forma logarítmica. Con continuous batching (lab 03 de la Fase 33), las peticiones cortas salen pronto y no se quedan atascadas detrás de las largas — la p95 también colapsa.

El tutor de gramática del §A13 bajo carga de 8 clientes está limitado por batching, no por cache. El KV-cache es necesario pero no suficiente. La Fase 22 + Fase 33 están diseñadas para enseñar exactamente esta lección: el cache hace tratable la latencia de una sola petición; el batching hace tratable el throughput multi-petición.

Referencia cruzada a la Fase 41 (el portal)

El portal del aprendiz de la Fase 41 es el ejemplo vivo del sistema servido. Llama al tutor de gramática de la Fase 32 como uno de varios endpoints (las superficies "quiz me" y "exam"). Re-lee docs/phase-41-learner-portal/theory/01-architecture.md (no lo modifiques) — el portal es dueño de la historia de lifespan / Depends / middleware; la Fase 33 es dueña de la historia de latencia del forward pass. Se componen:

  • El handler /quiz/submit del portal es de scope por petición (sesión por estudiante, CSRF, audit log) y añade ~3 ms de overhead encima de la llamada de 150 ms al modelo. Despreciable.
  • El modelo de carga del portal está acotado: incluso con 30 aprendices concurrentes, las RPS al tutor están acotadas por el think time entre envíos (los humanos leen explicaciones durante segundos). Estado estacionario esperado ≈ 1-2 RPS. Cómodo dentro del presupuesto de batching.

El portal no cambia el presupuesto de latencia; lo consume.

Regla de bolsillo de ingeniería (CPU, modelo microscópico)

Síntoma Causa probable Fase a revisitar
p50 demasiado alta incluso en C=1 KV-cache deshabilitado o mal indexado 22
p50 crece linealmente con C Sin batching 33 lab 02/03
p95 ≫ p50 Cola del static batch; o tamaños de input long-tail 33 lab 03
OOM bajo carga sostenida Buffers del KV-cache por petición nunca liberados 22 + 33 admission
p50 bien, throughput se estanca Contención single-thread en BLAS de NumPy 23 (X4)

Lo que este capítulo NO cubre

  • Presupuestos de latencia en GPU — territorio de la Fase 23+.
  • Speculative decoding para comprimir K — solo survey en la Fase 36.
  • Latencia cross-region, CDN, handshake TLS. Fuera del scope del §A13.

Referencias

  • Patel et al., "Splitwise: Efficient Generative LLM Inference Using Phase Splitting" (ISCA 2024). La separación prefill-vs-decode que nuestro presupuesto hace explícita.
  • Kwon et al., "Efficient Memory Management for Large Language Model Serving with PagedAttention" (SOSP 2023). El modelo de memoria del KV-cache del que la Fase 22 toma prestado.

Siguiente: ../lab/00-minimal-fastapi.md o vuelve a 03-littles-law-and-capacity.md para el lado del throughput.