Esfuerzo de razonamiento en un LLM local: coste real
Descubre qué cambia entre niveles de esfuerzo en un LLM local: tokens de razonamiento, tiempo de CPU o GPU, uso de contexto y cómo medir el coste real.
Qué cambia con el esfuerzo de razonamiento en un LLM local
El esfuerzo de razonamiento es un ajuste que indica al modelo cuánto tiempo debe pensar antes de responder. Cambia la longitud del segmento de razonamiento y nada más. Los pesos almacenados en disco son idénticos en todos los niveles, la cuantización también es idéntica y la respuesta se genera mediante el mismo paso hacia delante. Lo que cambia es cuántos tokens dedica primero el modelo a su bloc de notas interno.
Esta distinción importa por el lugar donde se contabilizan esos tokens. En una API alojada, los tokens de razonamiento aparecen en la factura. En un VPS propio, se pagan con tiempo de generación en la CPU o GPU propias y con espacio dentro de la ventana de contexto. Un modelo configurado con el nivel de esfuerzo máximo puede dedicar la mayor parte de su salida al razonamiento antes de mostrar la primera palabra de la respuesta. En hardware autoadministrado, eso puede marcar la diferencia entre una respuesta en dos segundos y otra en dos minutos.
Dónde se define el nivel: en la plantilla de chat, no en los pesos
Un modelo de razonamiento está entrenado para emitir un segmento de razonamiento, normalmente delimitado por las etiquetas <think> y </think>, antes de su respuesta final. El nivel de esfuerzo es una instrucción que la plantilla de chat del modelo inserta en el prompt. Esa plantilla es un archivo Jinja incluido con el modelo. Lee una variable como reasoning_effort y genera una línea de nivel de sistema diferente para cada valor. El modelo se entrenó para acortar o alargar su bloque de razonamiento en respuesta a esa línea.
De esto se desprenden dos consecuencias. Los nombres de los niveles pertenecen al modelo, no al entorno de ejecución. Por tanto, un nombre de la ficha de un modelo puede no significar nada para otro. Además, si cualquier componente de la cadena sustituye la plantilla de chat del modelo por una genérica, la variable nunca se genera y la configuración no tiene ningún efecto, sin mostrar errores.
Comprobada el 2026-08-20, la ficha del modelo Qwen3.8-27B documenta tres niveles de esfuerzo: low, medium y xhigh, con xhigh como valor predeterminado. No existe high. El razonamiento se activa o desactiva con enable_thinking, que está activado de forma predeterminada. La ficha también documenta preserve_thinking, activado de forma predeterminada, que conserva el razonamiento de los turnos anteriores en el historial de la conversación. gpt-oss usa low, medium y high en su lugar. Muchas otras familias aceptan sólo un valor booleano. Consulte la ficha de la versión exacta que descargó, porque estos nombres no son un estándar. Poner en ejecución un modelo 27B en un VPS es el primer paso. Esta página explica qué debe configurar una vez que el modelo responda.
Por qué un esfuerzo de razonamiento alto cuesta más en un VPS
Tokens de salida. Los tokens de razonamiento se generan y pasan por el mismo ciclo de decodificación que la respuesta, a la velocidad que permita el hardware. Suponga que una tarea produce 200 tokens de respuesta y 4,000 tokens de razonamiento. Se generan 4,200 tokens, pero el lector sólo ve 200. La velocidad de decodificación depende del ancho de banda de memoria y de la cuantización elegida, por lo que el único factor que queda es el número de tokens.
Tiempo de espera. El usuario espera el primer token de la respuesta, porque todo lo anterior aparece como una pantalla en blanco o un indicador de carga contraído. El razonamiento se emite primero, por lo que la espera es aproximadamente el número de tokens de razonamiento dividido por la velocidad de decodificación, más el procesamiento del prompt. Si duplica la longitud del razonamiento, duplica esa espera.
Contexto. Los tokens de razonamiento ocupan espacio en la ventana de contexto como cualquier otro token. Con preserve_thinking activado, el borrador del primer turno sigue incluido en el prompt durante el quinto turno. Por ello, el procesamiento del prompt se vuelve más lento en cada turno mientras la ventana se llena por ambos extremos. Aumentar num_ctx para conservarlo consume memoria de caché KV, que en un VPS sin GPU corresponde a RAM del sistema que quizá no esté disponible.
Cuándo aumentar el nivel y cuándo mantenerlo bajo
Auméntelo para tareas en las que un paso intermedio incorrecto arruina el resultado: aritmética y conversión de unidades en varios pasos, planificación de una edición en varios archivos, código que debe compilar y problemas con restricciones en los que una respuesta debe cumplir varias condiciones a la vez. En estos casos, el borrador de razonamiento realiza un trabajo real, y uno más extenso es una forma económica de detectar un error que, de otro modo, el modelo daría por válido.
Manténgalo bajo cuando la respuesta ya está en la entrada y la tarea consiste en trasladarla. La extracción, clasificación, etiquetado, traducción, reescritura, síntesis y aplicación de formato pertenecen a esta categoría. El segmento de razonamiento se limita en gran medida a repetir la tarea y da al modelo margen para descartar una primera intuición correcta.
Manténgalo bajo también para cualquier tarea interactiva. En un cuadro de chat o un editor, usted participa en el proceso, por lo que una respuesta rápida que pueda corregir es preferible a una respuesta lenta que tenga que esperar. Esta es la verdadera contrapartida de dirigir un agente de programación a un modelo local: un agente realiza muchas llamadas pequeñas y el coste de razonamiento se aplica a cada una de ellas.
Cómo establecer el nivel en llama.cpp
llama.cpp escribe la variable directamente en la plantilla. Por eso, este es el punto de ejecución donde puede confirmar que el nivel llegó correctamente. Apunte -m al archivo GGUF que ya tiene.
llama-server -m ./qwen3.8-27b-Q4_K_M.gguf \
--jinja \
--reasoning-effort medium \
--reasoning-format deepseek \
-c 32768 \
--host 127.0.0.1 --port 8080--jinja usa la propia plantilla de chat del modelo y está habilitado de forma predeterminada en las compilaciones actuales. --reasoning-effort acepta default, minimal, low, medium, high, xhigh o max, donde default significa mantener el valor predeterminado de la plantilla. Esta lista pertenece al vocabulario de llama.cpp, no al del modelo. Por tanto, use sólo un nombre que aparezca en la ficha del modelo: un nivel que la plantilla no defina puede provocar un error de plantilla al procesar la petición. --reasoning-format deepseek saca el razonamiento de message.content y lo coloca en message.reasoning_content. Esto permite medir la separación en la sección siguiente.
Para desactivar el razonamiento en lugar de acortarlo, establezca usted mismo la variable de la plantilla:
llama-server -m ./qwen3.8-27b-Q4_K_M.gguf --jinja \
--chat-template-kwargs '{"enable_thinking": false}'--reasoning-budget es un mecanismo diferente. Limita el segmento de razonamiento en tokens. 0 lo termina inmediatamente y -1 lo deja sin restricciones. No solicita al modelo que planifique un segmento más corto. Ambos indicadores se aplican a todo el servidor. llama-server no acepta reasoning_effort como campo por petición. Por tanto, para servir dos niveles de esfuerzo al mismo tiempo se necesitan dos procesos en dos puertos.
vLLM expone la misma variable por petición, dentro del cuerpo compatible con OpenAI:
{"model": "Qwen/Qwen3.8-27B",
"messages": [{"role": "user", "content": "Summarise this changelog in two lines."}],
"chat_template_kwargs": {"reasoning_effort": "medium"}}Cómo establecer el nivel en Ollama
Ollama tiene su propio campo, think, en /api/chat y /api/generate. Acepta true, false o uno de low, medium, high y max, donde max solicita el nivel más alto que ofrece el modelo. El razonamiento está activado de forma predeterminada en los modelos que lo admiten.
ollama run qwen3.8:27b --think=low "Draft a one line commit message for a README typo fix"{"model": "qwen3.8:27b",
"messages": [{"role": "user", "content": "Which HTTP status code means the request body was too large?"}],
"think": "low",
"stream": false}El razonamiento se devuelve en message.thinking y la respuesta en message.content, ya separados. Dentro de una sesión interactiva de ollama run, /set think y /set nothink lo activan o desactivan sin reiniciar.
Ahora observe la discrepancia. El vocabulario de Ollama es low, medium, high y max. La plantilla de Qwen3.8 define low, medium y xhigh. Es necesario asignar unos valores a otros. Además, un modelo de Ollama incluye una plantilla empaquetada dentro de su tag, en lugar del archivo Jinja del repositorio original. Por tanto, que el nivel llegue al modelo depende de esa plantilla empaquetada. No dé por hecho que funcionó. Medirlo lleva aproximadamente un minuto.
Cómo medir si el nivel se aplicó realmente
Envíe el mismo prompt con más de un nivel y establezca temperature en 0. Después, compare el número de tokens. Aquí jq construye el cuerpo para que no tenga que escapar las comillas manualmente.
for level in low medium max; do
body=$(jq -n --arg lvl "$level" '{
model: "qwen3.8:27b",
messages: [{role: "user", content: "A pump fills a 4500 litre tank in 25 minutes. A second pump is 40 percent slower. How long do both together take? Answer in minutes."}],
think: $lvl,
stream: false,
options: {temperature: 0, num_ctx: 8192}
}')
echo "== $level"
curl -s http://localhost:11434/api/chat -d "$body" | jq '{
thinking_chars: (.message.thinking // "" | length),
answer_chars: (.message.content | length),
eval_count: .eval_count,
seconds: (.total_duration / 1e9),
tok_per_sec: (.eval_count / (.eval_duration / 1e9))
}'
doneeval_count representa todos los tokens generados, incluido el razonamiento. Por tanto, la diferencia entre dos niveles corresponde casi por completo al razonamiento. thinking_chars proporciona la separación directamente. Deben cumplirse dos condiciones: los números cambian entre niveles y la respuesta sigue siendo correcta con el nivel inferior. Si eval_count se mantiene dentro del ruido en las tres ejecuciones, el nivel se está ignorando. La solución es usar un runtime que lo transmita, no cambiar el nombre del nivel.
El tiempo total sólo cuenta una parte. Mida también el intervalo hasta el primer token de la respuesta mediante streaming y deténgase en el primer fragmento content no vacío. Para esto necesita jq y bc.
start=$(date +%s.%N)
curl -sN http://localhost:11434/api/chat -d '{
"model": "qwen3.8:27b",
"messages": [{"role": "user", "content": "Explain what a reverse proxy does, in three sentences."}],
"think": "low",
"stream": true
}' |
while IFS= read -r line; do
if [ -n "$(printf '%s' "$line" | jq -r '.message.content // ""')" ]; then
echo "first answer token after $(echo "$(date +%s.%N) - $start" | bc)s"
break
fi
doneEjecútelo con low y vuelva a ejecutarlo con max. La diferencia es la espera adicional que está aceptando. En llama.cpp, los mismos números aparecen dentro de la respuesta, sin necesidad de hacer cálculos aritméticos en el shell:
curl -s http://localhost:8080/v1/chat/completions \
-H 'Content-Type: application/json' \
-d '{"model": "local", "temperature": 0,
"messages": [{"role": "user", "content": "A pump fills a 4500 litre tank in 25 minutes. A second pump is 40 percent slower. How long do both together take?"}]}' | jq '{
reasoning_chars: (.choices[0].message.reasoning_content // "" | length),
answer_chars: (.choices[0].message.content | length),
predicted_n: .timings.predicted_n,
tok_per_sec: .timings.predicted_per_second
}'Hágalo en su propio equipo. Una comparación publicada del esfuerzo se midió en hardware distinto del suyo, y su velocidad de decodificación es el factor que convierte un número de tokens en segundos. Medir tokens por segundo en su propio servidor proporciona ese factor: los tokens de razonamiento divididos por su velocidad de decodificación representan la espera adicional que acaba de introducir.
Qué puede fallar
La respuesta se corta o content está vacío mientras thinking contiene datos. El límite de generación se consumió durante el razonamiento. num_predict de Ollama limita toda la generación, incluido el razonamiento. Como el razonamiento se genera primero, un límite de 512 tokens con un nivel de esfuerzo alto puede finalizar la respuesta antes de que empiece. Ollama informa de "done_reason": "length" en esa respuesta. Aumente el límite o reduzca el nivel de esfuerzo. Cómo cuenta tokens num_predict explica esta interacción en detalle.
El nivel no cambia nada. El número de tokens es idéntico en todos los niveles. Es posible que el runtime no esté pasando la variable o que la plantilla no la lea. Compruebe la plantilla que usa realmente el runtime, no la plantilla del repositorio original. llama.cpp, con --jinja y --chat-template-kwargs, escribe la variable manualmente, por lo que sirve como control. Si el nivel funciona allí y en ningún otro sitio, el modelo es correcto y el otro runtime está descartando la variable.
Se rechaza el nombre de un nivel. Un error de plantilla durante la solicitud, o un fallo en el primer mensaje con un servidor que por lo demás funciona correctamente, suele indicar que se pasó un nivel que la plantilla no define, como high a un modelo cuya ficha sólo incluye low, medium y xhigh.
Las conversaciones con varios turnos se ralentizan en cada turno. El historial conserva el razonamiento anterior. Establezca preserve_thinking en false si el modelo lo admite, o elimine el campo thinking de los mensajes que vuelva a enviar. De lo contrario, el procesamiento del prompt aumenta en cada turno, mientras las respuestas mantienen la misma longitud.
La calidad disminuye con un nivel de esfuerzo bajo en una tarea que consideraba sencilla. Algunas tareas de extracción no son realmente extracción. Si la entrada requiere convertir unidades o aplicar reglas en un orden determinado, es una tarea de razonamiento con una salida breve. Aumente el nivel para esa llamada concreta, no para todo el servidor.
Ejecutar dos niveles a la vez
llama.cpp fija el nivel al iniciar, por lo que un equipo que atiende tanto a un editor como a un trabajo por lotes nocturno necesita dos procesos en dos puertos, cada uno con su propio --reasoning-effort. Dos procesos también implican dos copias de los pesos en memoria, a menos que se separen los trabajos en el tiempo. En un VPS, la opción más económica suele ser un servidor de bajo esfuerzo para cualquier tarea cuyo resultado esté esperando una persona, además de una ejecución programada con mayor esfuerzo para trabajos que nadie está supervisando. Qué ocurre cuando varios usuarios comparten un modelo local también se aplica aquí: los tokens de razonamiento son trabajo de decodificación, por lo que aumentar el esfuerzo reduce la concurrencia efectiva aproximadamente en el mismo factor en que aumenta el número de tokens.
FAQ
¿Qué nivel de esfuerzo de razonamiento debo usar de forma predeterminada?
Empiece por el nivel más bajo que ofrezca el modelo y súbalo sólo para las tareas en las que haya observado fallos. Varios modelos de razonamiento se distribuyen con un nivel predeterminado alto, y Qwen3.8-27B usa xhigh, su nivel máximo, de forma predeterminada a fecha de agosto de 2026. Ese valor se elige para obtener buenos resultados en las tablas de referencia, pero una tabla de referencia no cobra por el tiempo. En su propio hardware, el coste se mide en segundos. Por tanto, el nivel más alto debe ser una opción para cada tarea, no el ajuste que hereda cada solicitud.
¿Los tokens de razonamiento cuentan para el límite de contexto?
Sí. Son tokens normales de la salida y ocupan espacio en la ventana de contexto junto con todo lo demás. Que permanezcan en el siguiente turno depende del runtime y del modelo. La tarjeta de Qwen3.8 documenta preserve_thinking, activado de forma predeterminada, que conserva el razonamiento anterior en el historial. Por eso, una conversación larga incluye cada bloque de trabajo intermedio que el modelo haya producido. Establézcalo en false o elimine el campo thinking de los mensajes que vuelva a enviar. Así, el procesamiento del prompt deja de crecer.
¿Por qué cambiar el nivel de razonamiento no modifica mis recuentos de tokens?
El ajuste no está llegando a la plantilla de chat. El nivel es una variable de la plantilla. Por tanto, sólo funciona si el runtime la transmite y la plantilla incluida la lee. Algunos runtimes incluyen su propia plantilla con el modelo en lugar del archivo Jinja del repositorio original. En ese caso, la variable se descarta sin mostrar ningún error. Compruébelo enviando el mismo prompt con el nivel más bajo y el más alto, con temperature en 0, y compare eval_count. Si los recuentos coinciden dentro del margen de variación, el nivel se está ignorando.
¿Un esfuerzo de razonamiento menor hace que el modelo sea menos preciso?
Depende de la tarea. Conviene medirlo en lugar de darlo por supuesto. Cuando la respuesta ya está presente en la entrada, como en la extracción o la reescritura, un bloque de trabajo intermedio más corto normalmente no cambia nada. Cuando un paso intermedio debe ser correcto antes de poder completar el resultado final, como en la aritmética de varios pasos o en código que debe compilar, la precisión disminuye con un bloque de trabajo intermedio más corto. Prepare un conjunto de veinte prompts de su carga de trabajo real, ejecútelos con dos niveles y temperature en 0, y cuente las respuestas incorrectas. Ese número es específico de su carga de trabajo. Ninguna tabla publicada puede proporcionárselo.