Por qué mi LLM da resultados inconsistentes

Cinco peticiones idénticas, cinco respuestas distintas — una dijo “not Python” y cuatro dijeron Python. Fijamos la seed y el lote volteó un token a temp=0.

HUMANpor @msvram7h8 min de lectura
mismo prompt × 5 · temp 0.7 · sin seed · Qwen3-4B Q4_K_M UN PROMPT“The most popular programminglanguage in the world is” run 1 · “ Python. Python is widely used in data…” run 2 · “not Python, Java, or C++. It’s actually…” run 3 · “ Python. But there are other languages…” run 4 · “ Python. But why? Python is a high-level…” run 5 · “ Python. Python has a lot of applications…”

Fig 1 — cinco ejecuciones, cinco respuestas, una autocontradicción. La run 2 (apagada) es el recibo: mismo archivo, misma máquina, mismo prompt, apertura opuesta.

¿Por qué mi LLM da una respuesta distinta cada vez?

seed — una tirada de dados guardada. Cada decisión de muestreo arranca de un número; ponlo y toda la baraja se repite. Omítelo y el motor saca uno nuevo por petición.
temperature — lo desatada que es la puja. Cada token siguiente es una subasta: el modelo sostiene una lista de probabilidades, la temperature la remodela y un token gana la tirada. 0.7 son dados corrientes; 0 significa “quedarse con el favorito siempre” (greedy).
top_p — un segundo filtro, más estricto. Recorta la lista de candidatos antes de la tirada. Afina los dados; no los fija.

El modelo nunca “tiene” una respuesta. Para cada hueco de token sostiene una lista de probabilidades, la temperature la remodela y un token gana la tirada. El ganador se añade y la siguiente subasta abre con otra lista — porque el texto que continúa ya es otro. Una divergencia temprana se compone: la run 2 dijo “not” donde las runs 1, 3, 4 y 5 dijeron “Python”, y desde ese único carácter las cinco respuestas viven en universos distintos. Cinco ejecuciones son cinco tiradas independientes, no una respuesta impresa cinco veces.

¿Cómo consigo que mis respuestas de LLM se repitan exactas?

Nombra la tirada. Repetimos la petición idéntica tres veces con seed: 42 a temperature 0.7 — el ajuste nada determinista — y salieron 3/3 respuestas byte-idénticas (la velocidad de generación osciló 6.18 → 6.74 → 7.63 tok/s; las palabras no oscilaron ni un pelo). La seed fija los dados incluso mientras se lanzan.

curl http://localhost:8080/v1/chat/completions -d '{
  "model": "qwen3:4b",
  "messages": [{"role": "user", "content": "Explain GGUF quantization"}],
  "temperature": 0.7,
  "seed": 42
}'

verify: mismo prompt + misma seed → respuesta byte-idéntica, 3/3 en el banco (temp 0.7, no solo greedy) · det-results-a.json

El mismo control existe donde ya trabajas: options.seed en el /api/generate de ollama, seed en la vía chat compatible con OpenAI, --seed en la línea de comandos de llama.cpp. El runner de ollama es llama-server con chaqueta, así que el recibo bajo el capó es el mismo — la seed llega al mismo sampler.

¿Por qué temperature 0 sigue sin ser determinista?

Porque el decoding greedy quita los dados. No quita el redondeo. Dentro de la red cada puntaje de token son miles de sumas, y la suma en coma flotante no es asociativa: suma una columna larga de izquierda a derecha, luego súmala en bloques — mismos números, tal vez un último dígito distinto. Cuando los dos primeros candidatos quedan lo bastante cerca, el redondeo es la decisión. Y el orden de suma depende de cuánto trabajo hace el lote de una vez.

Así que lo fijamos todo — temp=0, seed=42, mismo servidor, mismo prompt — y cambiamos exactamente una bandera, el ancho del micro-lote:

petición-ubtempseedla salida abre convs -ub 512
1–2512042“ Python. It is used in many…”línea base, 2/2 idénticas
18192042“ Python. It’s used in many…”SE VOLTEÓ — un token
2–38192042“ Python. It is used in many…”volvió a la línea base, 2/2

El dado cargado, en vivo: todos los controles decían determinista y la forma del lote movió un token igual — y solo en la primera petición tras el arranque.

Lee la tercera fila dos veces. El volteo apareció una vez, en la primera petición del servidor frío, y las dos peticiones siguientes con ajustes idénticos devolvieron el texto de la línea base. El estado del servidor caliente — la caché de prompts, el reparto de slots — se añade al orden de suma en la lista de cosas de las que depende una respuesta “determinista”. Cazamos el volteo; no vamos a fingir que cazamos al culpable. Los dados están cargados. La mano sigue oculta.

¿Cuánto cuesta la varianza entre ejecuciones?

La factura, por cinco. Pedimos un hola en exactamente tres palabras, cinco veces, seed libre, presupuesto de 256 tokens. El modelo respondió cero de cinco — cada ejecución gastó el presupuesto entero pensando antes de que el tope la cortara, y el thinking mismo osciló: 884 · 954 · 986 · 915 · 885 caracteres para la petición idéntica.

caracteres de thinking · “Say hello in exactly three words” × 5 · tope 256 tokens, comido ×5 884run 1 954run 2 986run 3 915run 4 885run 5

Fig 2 — la oscilación es el punto: un rango de 102 caracteres sobre una petición fija. La factura del thinking del post de LFM3 de octubre, cinco veces seguidas.

Pedimos hola en tres palabras y nos llegaron de 884 a 986 caracteres de introspección y ningún hola. El modelo no es indeciso con su respuesta — es indeciso con todo lo que va antes de la respuesta, y eso se paga llegue o no la respuesta. Toda petición de “respuesta exacta” es un presupuesto: limita el thinking (think: false, o el interruptor propio del modelo) o el tope come primero.

La lista de arreglos

síntomaqué está pasandoel arreglo
respuesta distinta en cada ejecuciónsin seed; temp > 0 remodela la lista en cada tiradafija la seed; la respuesta se repite byte a byte
respuestas distintas a temp=0la forma del lote / el estado del servidor caliente cambió el orden de sumamismo -ub/-b, misma compilación, calienta antes de medir
la respuesta nunca llegael thinking se comió el presupuesto de tokens (0/5 aquí)limita o desactiva el thinking; lee completion_tokens, no vibras
receipt · la run 2 niega a las runs 1, 3, 4, 5 (mismo prompt, misma máquina, sin seed)
run 1: “ Python. Python is widely used in data science, web…”
run 2: “not Python, Java, or C++. It’s actually a language that’s
       not even a progr…”
run 3: “ Python.  But there are other languages that are…”
run 4: “ Python. But why? Python is a high-level, interpreted…”
run 5: “ Python. Python has a lot of applications in web…”

Cuatro ejecuciones dijeron Python. Una abrió discutiendo la premisa. Mismos pesos, misma máquina, misma cadena de entrada. Una de las cinco se equivoca al creerse una máquina determinista; las otras cuatro solo tienen suerte.

última prueba 2026-11-11

Qwen3-4B Q4_K_M · 2,497,280,256 B · sha256 7485fe6f…df5 · llama-server 0.9.0-dev (b12110) · una VM Arch recién instalada, 4 hilos — banco en CPU, sin pretensiones de VRAM