Ajustes de muestreo
El muestreo es la última pulgada de la inferencia: el modelo puntúa
cada token del vocabulario, y tus ajustes deciden cómo esa distribución
se convierte en un token. Los controles interactúan, así que el orden de
operaciones importa más que cualquier valor suelto.
La tubería, en orden
La mayoría de los motores aplica los samplers más o menos en esta
secuencia:
- Logits — puntuaciones crudas, una por token del vocabulario (50k–150k
entradas). - Penalizaciones de repetición — los tokens vistos recientemente ven
sus logits empujados hacia abajo. - Temperatura — logits divididos entre T, afilando o aplanando todo.
- top_k — tope duro: solo sobreviven los k mejores tokens.
- top_p — núcleo: el conjunto top más pequeño que cubre p de la masa
de probabilidad. - min_p — suelo relativo: descarta tokens por debajo de min_p ×
P(token top). - Muestreo — elige un token de lo que queda, lo añade, repite.
Un filtro aplicado temprano cambia lo que ve todo filtro posterior.
Tenlo presente al apilar.
Temperatura
La temperatura T divide los logits antes del softmax. Una T baja afila la
distribución hacia el token superior; una T alta la aplana hacia lo
uniforme. Concreto: logits [5.0, 3.0, 1.0] sobre tres tokens.
| T | P(top) | P(segundo) | P(tercero) |
|---|---|---|---|
| 0.5 | 98.1% | 1.8% | 0.03% |
| 1.0 | 86.7% | 11.7% | 1.6% |
| 1.5 | 75.0% | 19.8% | 5.2% |
Con T → 0 esto colapsa a voraz: siempre argmax, totalmente determinista.
Usa temperatura 0 (o 0.000001 donde un motor no permita 0) para
extracción, clasificación y cualquier cosa que quieras reproducible entre
ejecuciones. El chat quiere 0.6–0.8; la escritura creativa tolera
0.9–1.2.
top_k: un tope duro
Conserva solo los k tokens de mayor probabilidad y muestrea entre ellos.top_k 40 vuelve imposibles al token 41 y de abajo, pase lo que pase.
Es un instrumento contundente. k=40 es holgado tras «The capital of
France is» y absurdamente despilfarrador en posiciones de tokens raros
donde solo importan dos candidatos. Valor por omisión común: 40.
Desactívalo (0 o -1, según el motor) cuando uses min_p.
top_p: el núcleo
Ordena por probabilidad, conserva el conjunto más pequeño cuya
probabilidad acumulada alcanza p, renormaliza, muestrea.
Resuelto: probabilidades [0.867, 0.117, 0.016] con top_p 0.9. El
token top por sí solo cubre 0.867 < 0.9; añadir el segundo alcanza 0.984
≥ 0.9. Muestreas de dos tokens, renormalizados a [0.88, 0.12].
El conjunto de candidatos se encoge automáticamente cuando el modelo está
seguro y crece cuando duda. Rango típico: 0.9–0.95.
min_p: el favorito local
Conserva solo tokens cuya probabilidad sea al menos min_p × P(token top).
El suelo es relativo a la escala:
- Modelo 99% seguro → suelo = 0.05 × 0.99 ≈ 0.05: solo sobreviven los
contendientes reales. - Token top al 30% → suelo = 0.05 × 0.30 = 0.015: la diversidad
sobrevive.
Eso mata los fallos clásicos de top_k/top_p — sobre-podar una
distribución confiada, sub-podar una plana — y min_p compone con
seguridad con temperatura alta. La receta «creativo pero coherente»:
temp 1.0–1.5, min_p 0.05–0.1. Ajuste local común: min_p 0.05 con
top_k y top_p apagados.
repeat_penalty (familia llama.cpp)
Antes de aplicar la temperatura, los logits de los tokens que aparecieron
en el contexto reciente se dividen entre la penalización. Los valores por
debajo de 1.0 potencian la repetición al revés. Rango sensato: 1.0–1.1.
Los efectos secundarios son reales, porque el código y los datos repiten
tokens a propósito:
- Los identificadores se repiten legítimamente:
countdentro decounter.count. - Las claves JSON y las llaves de cierre se repiten en cada objeto de un
array. - Una penalización de 1.2+ rompe código y JSON (llaves caídas, comillas
«corregidas») mucho antes de arreglar los bucles de prosa.
Los modelos instruct modernos rara vez necesitan más de 1.05. Para código
y salida estructurada, 1.0 es una elección legítima.
Penalizaciones de frecuencia y presencia (estilo API)
Las APIs compatibles con OpenAI exponen frequency_penalty (crece con
cada repetición) y presence_penalty (penalización plana por cualquier
token ya visto) donde llama.cpp usa repeat_penalty. Misma intención,
matemática distinta. No todo motor local honra ambas — comprueba qué
implementa el tuyo antes de apilarlas una sobre otra: parámetros del
motor.
Acorralar los samplers en una esquina
temp 0.1 + top_k 10 + top_p 0.5 + min_p 0.2 + repeat_penalty 1.3,
aplicados juntos, pueden enmascarar todos los tokens candidatos.
Síntomas: avisos de logprob NaN, generaciones vacías, abortos de
«sampling returned zero candidates».
Eso no es un modelo roto — es un conjunto de candidatos vacío. Afloja la
pila, un control cada vez. Triaje completo: resolución de
problemas.
Preajustes que funcionan
| Tarea | temp | top_p | min_p | repeat_penalty |
|---|---|---|---|---|
| Chat (general) | 0.7 | 0.90–0.95 | 0.05 | 1.0–1.05 |
| Generación de código | 0.1–0.2 | 0.9 | — | 1.0 |
| Extracción / clasificación | 0 (voraz) | — | — | 1.0 |
| Escritura creativa | 0.9–1.0 | 0.95 | 0.05–0.1 | 1.05–1.1 |
| Traducción | 0.2–0.4 | 0.9 | — | 1.0 |
La fila de chat como flags de llama.cpp:
llama-server -m model.gguf --temp 0.7 --top-p 0.95 --min-p 0.05 --repeat-penalty 1.05
Cambia una cosa a la vez
Trata los preajustes como puntos de partida, no leyes. Si mueves
temperatura y top_p juntos y la salida mejora, no has aprendido nada
sobre qué control importaba.
- Fija un conjunto de pruebas: 5–10 prompts que ejerzan lo que te
importa. - Guarda las salidas por configuración; difúndelas lado a lado.
- Cambia exactamente un control por ejecución.
- Guarda el ganador como preajuste con nombre — todo motor y UI los
almacena.
Para cómo mapean estos controles a flags de motor y campos de API:
parámetros del motor.
Relacionados: parámetros del motor · prompting · salida estructurada · resolución de problemas