Pásale un CV a un modelo de lenguaje, pídele una nota del 1 al 10 y te devolverá un número con una justificación muy segura. Pregúntale otra vez y el número puede cambiar. El consejo habitual es "pon la temperatura a 0", y es correcto, pero deja dos preguntas sin responder: ¿cuánto se mueve de verdad la nota con la configuración por defecto? ¿Y qué es lo que temperature 0 no arregla? Las dos cosas se pueden medir en un portátil en unos minutos, porque la nota es un solo token y el modelo te da su probabilidad directamente.
Para las pruebas usé Qwen2.5-3B-Instruct (4 bits, MLX) con mlx-lm 0.32.2 en un Mac M1 de 8 GB. Los prompts son los de una demo de evaluación de CVs en Models → Playground de la app de escritorio osFoundry: un CV de seis líneas para un puesto de analista de datos junior, seguido de "Score this CV from 1 to 10 for a junior data analyst role. Start with the score, then one reason." La app ejecuta los modelos locales con llama-server de llama.cpp, así que la configuración del muestreo sale de ahí.
Los valores por defecto cuando no eliges nada
Si una petición no lleva temperatura, llama-server muestrea con sus propios valores por defecto. La compilación que incluye la app (versión 10358) los muestra en --help:
--temp, --temperature N temperature (default: 0.80) --top-k N top-k sampling (default: 40, 0 = disabled) --top-p N top-p sampling (default: 0.95, 1.0 = disabled) --min-p N min-p sampling (default: 0.05, 0.0 = disabled) -s, --seed SEED RNG seed (default: -1, use random seed for -1) Se aplican a cualquier petición que no fije sus propios valores. Cuando la app arranca llama-server, solo añade --temp si se ha configurado una temperatura para el servidor (local_inference.rs), así que una petición sin temperatura se muestrea a 0.8 con una semilla aleatoria, que es también lo que hacen de serie la mayoría de interfaces basadas en llama.cpp.
Hacer que la nota sea el siguiente token
Una respuesta libre puede empezar con "Score: 7", "7/10" o "I would rate this CV a 7", y así cuesta localizar la nota. La solución es rellenar de antemano el inicio de la respuesta, para que el siguiente token que genere el modelo sea directamente el dígito:
import mlx.core as mx from mlx_lm import load, generate from mlx_lm.sample_utils import make_sampler model, tok = load("mlx-community/Qwen2.5-3B-Instruct-4bit") DIGITS = {d: tok.encode(str(d))[0] for d in range(1, 10)} def ids_for(cv, question, prefill="Score: "): msgs = [{"role": "user", "content": cv + "\n\n" + question}] text = tok.apply_chat_template(msgs, add_generation_prompt=True, tokenize=False) return tok.encode(text + prefill) def digit_probs(ids, T=1.0): logits = model(mx.array([ids]))[0, -1].astype(mx.float32) p = mx.softmax(logits / T) return {d: float(p[t]) for d, t in DIGITS.items()} El tokenizador de Qwen separa los números en dígitos sueltos, así que "7" es un token y "10" empezaría por el token "1". Una sola pasada hacia delante sobre el prompt de 173 tokens da la distribución completa de la nota.
La nota es una distribución, no un número
Para el CV en inglés, con temperatura 1 (las probabilidades en bruto del modelo):
| Nota | Probabilidad |
|---|---|
| 7 | 74.2% |
| 6 | 23.3% |
| 8 | 1.3% |
| 5 | 1.2% |
El modelo no "piensa" que el CV es un 7. Piensa que probablemente es un 7, con una posibilidad entre cuatro de que sea un 6. La temperatura divide los logits antes del softmax, lo que afila o aplana esta distribución:
| Temperatura | P(7) | P(6) | P(8) |
|---|---|---|---|
| 1.0 | 74.2% | 23.3% | 1.3% |
| 0.8 | 80.2% | 18.9% | 0.5% |
| 0.5 | 91.0% | 9.0% | 0.0% |
| 0.2 | 99.7% | 0.3% | 0.0% |
llama-server no aplica solo la temperatura. Su cadena por defecto ejecuta top-k 40, luego top-p 0.95, luego min-p 0.05 y la temperatura al final. Con estos logits, top-p ya elimina todo lo que viene después del 7 y el 6 (entre los dos superan el 95%), así que el muestreador por defecto elige entre exactamente dos notas:
def llamacpp_default(logits, temp=0.8, top_k=40, top_p=0.95, min_p=0.05): order = mx.argsort(-logits)[:top_k] kept = logits[order] p = mx.softmax(kept) n = int(mx.sum(mx.cumsum(p) < top_p).item()) + 1 idx = [i for i in range(n) if p[i].item() >= min_p * p[0].item()] q = mx.softmax(kept[mx.array(idx)] / temp) return {tok.decode([int(order[i])]): float(q[j]) for j, i in enumerate(idx)} # -> {'7': 0.8093, '6': 0.1907} Después lo muestreé de verdad: 200 generaciones con el mismo prompt y el muestreador de mlx-lm configurado con esos mismos cuatro valores (make_sampler(temp=0.8, top_p=0.95, min_p=0.05, top_k=40)).
| 7 | 6 | |
|---|---|---|
| Previsto por la cadena | 80.9% | 19.1% |
| Observado, 200 ejecuciones | 162 (81%) | 38 (19%) |
Es decir, con la configuración por defecto, más o menos una de cada cinco ejecuciones le pone un 6 a este CV en lugar de un 7. Si evalúas a dos candidatos una vez cada uno y uno saca un 6 y el otro un 7, hay una posibilidad real de que la diferencia sea puro sorteo.
Un caso más plano: el mismo CV en japonés
El mismo CV y la misma pregunta en japonés (222 tokens, con 「点数:」 como relleno inicial) dieron un modelo mucho menos decidido:
| Nota | Probabilidad con T=1 | Observado, 200 ejecuciones con los valores por defecto |
|---|---|---|
| 8 | 39.8% | 108 |
| 9 | 23.8% | 44 |
| 7 | 19.4% | 40 |
| 10 | (empieza por "1": 4.1%) | 8 |
Aquí la nota más probable gana solo la mitad de las veces, más o menos, y en 200 ejecuciones salieron cuatro notas distintas. Cuánto baila una nota no es una propiedad fija del modelo. Depende de lo cerca que estén los primeros candidatos para esa entrada concreta, y eso solo se ve mirando las probabilidades.
Lo que temperature 0 sí arregla
Con temperatura 0 (decodificación greedy: siempre el token más probable), generé la respuesta completa de 40 tokens 20 veces en cada idioma. Salió 1 texto distinto por idioma, idéntico carácter a carácter. Para el CV en inglés es "7" seguido siempre de la misma justificación, y para el japonés 「8点」.
Una semilla fija consigue lo mismo al muestrear: 10 ejecuciones reiniciando mx.random.seed(42) antes de cada una dieron 1 texto distinto. Es útil cuando quieres variedad pero necesitas reproducir una ejecución concreta. Pero no sirve para ser justo. Con una semilla, un CV distinto recibe una secuencia de sorteos distinta, así que dos candidatos siguen evaluándose con suertes diferentes.
Todo esto tiene una condición: cada ejecución fue una única petición sobre un modelo sin otra carga. Los servidores que agrupan varias peticiones en lotes pueden producir diferencias mínimas de coma flotante entre ejecuciones, que solo importan cuando dos tokens están casi empatados. Eso no se midió aquí.
Lo que temperature 0 no arregla
La decodificación greedy convierte la salida en una función de la entrada. Lo que no garantiza es que sea una función estable. Hice cuatro cambios que no deberían alterar la nota de un CV y anoté cada vez la nota greedy y las probabilidades:
| Cambio (inglés) | Nota greedy | P(primera) | Segunda |
|---|---|---|---|
| ninguno | 7 | 74.2% | 6 con 23.3% |
| Línea de habilidades movida arriba | 7 | 69.6% | 6 con 28.1% |
| Nombre cambiado (Mia Chen → James Miller) | 7 | 68.5% | 6 con 28.5% |
| Una línea en blanco extra | 7 | 71.0% | 6 con 26.1% |
| Pregunta reformulada | 6 | 67.5% | 5 con 19.3% |
La pregunta reformulada era "Rate this CV for a junior data analyst role on a 1 to 10 scale. Give the score first, then one reason." Misma tarea, mismo CV, y la nota greedy baja de 7 a 6. En japonés, las cinco versiones se quedaron en 8 con temperatura 0, pero la probabilidad de la primera nunca pasó del 44%, y la segunda alternaba entre 9 y 7 según el cambio.
Así que temperature 0 elimina el azar entre ejecuciones, no la sensibilidad al prompt. Para comparar CVs, eso significa:
- Usa temperatura 0 y exactamente la misma pregunta para todo lo que compares, carácter a carácter. Si cambias la pregunta, vuelve a puntuarlo todo.
- Guarda la probabilidad, no solo la nota. El endpoint
/completionde llama-server devuelve las probabilidades de los tokens si le pasasn_probs, y con el truco del relleno inicial el primer token es la nota. Un 7 al 74% y un 8 al 40% no son el mismo tipo de respuesta. - Trata como empate una diferencia de un punto con poca confianza, y lee la justificación del modelo antes de fiarte de cualquiera de los dos números.
Hacerlo en la app
En la app de escritorio osFoundry, el mismo experimento son tres paneles con un mismo modelo local en Models → Playground. Ejecuta el prompt una vez con los valores por defecto para ver la dispersión, luego abre la fila de ajustes y pon Temp a 0.
Las entradas del glosario sobre temperatura y top-p explican los dos ajustes con más detalle. La app es gratuita en la página de descarga de osFoundry.



