AgentesModelosGPUBenchmarks

Puse el mismo agente a resolver bugs reales con tres motores locales: 284B, 27B y 27B en dos bits

El DeepSeek Harness solo se ha probado con la API oficial. Lo conecté a tres motores locales y les di instancias reales de SWE-bench Verified. Dos empatan en aciertos, uno es once veces más rápido, y el que tiene 284 mil millones de parámetros no llega ni a la salida.

Efrain Garay 15 de agosto de 2026

Toda la cobertura del DeepSeek Harness que encontré lo describe con la API oficial de DeepSeek. Ninguna lo corre con un modelo local. Y esa es justo la pregunta que me interesa, porque tengo una tarjeta de 16 GB y un enjambre de máquinas donde las cosas se prueban de verdad.

Así que lo conecté a tres motores muy distintos y les di el mismo trabajo: arreglar fallos reales de proyectos reales, verificados con sus propios tests.

En 22 segundos: tres bots subiendo una colina, cada uno avanzando al ritmo que midió de verdad. El gordo de 284 mil millones apenas se despega de la salida, el musculoso empuja a media cuesta y el flaco de dos bits corona primero. Sin sonido por defecto: actívalo en los controles.

Qué es el harness y por qué importa

Un harness de agente es el andamiaje que rodea al modelo: el bucle que decide cuándo llamarlo, qué herramientas ofrecerle, cómo registrar la sesión y cuándo parar. DeepSeek liberó el suyo bajo licencia MIT, con una idea de diseño clara: todo es un plugin, incluido el adaptador del modelo, el registro de herramientas y el propio bucle del agente.

Eso último es lo que hace posible este artículo. Si el adaptador es reemplazable, el modelo también.

Lo instalé desde el repositorio en 28 segundos y compiló en 147. Tiene 2 587 archivos de TypeScript y JavaScript, así que no es precisamente pequeño.

Y aquí el primer hallazgo: no trae adaptador para modelos locales. De fábrica vienen dos, el de DeepSeek y el de su pasarela. Pero su ruta de “proveedor personalizado” habla el protocolo de OpenAI, que es exactamente el que exponen ollama, llama.cpp y Colibrì. No hay que escribir una línea de código, solo declararlo:

llm-pi-ai:
  providers:
    local:
      api: openai-completions
      baseURL: http://127.0.0.1:8090/v1
      apiKeyEnv: API_KEY
      models:
        - id: mi-modelo

agent-default-model:
  provider: local
  model: mi-modelo

Cómo medí

Usé SWE-bench Verified, que es el estándar que reportan los laboratorios: 500 fallos reales extraídos de repositorios grandes, cada uno con su entorno preparado y sus tests.

El protocolo, que importa más que el resultado:

El agente ve solo el texto del reporte de fallo. Nunca el parche de referencia, nunca los tests con que se lo evalúa. Trabaja sobre el repositorio en el commit anterior al arreglo. Después extraigo el diff que dejó, aplico el parche de tests oficial y ejecuto los tests que deben pasar. Resuelto significa que pasan, no que el agente diga que lo logró.

Elegí tres instancias de la categoría más fácil, de repositorios distintos: astropy, django y matplotlib. Tres no son una medición estadística y las fáciles son el escenario favorable. Es un termómetro, no un ranking, y lo digo antes de dar ningún número.

Motor 1: Qwen3.8-27B, el que no cabe

Empecé con el modelo del que ya medí que no entra en 16 GB: 17 GB de archivo contra 16 de tarjeta, con un tercio del modelo en CPU.

Antes de que funcionara nada, me topé con el fallo más instructivo del día.

El agente respondía sobre una escena de Three.js con un cubo rotando, algo sin ninguna relación con la tarea. Parecía un modelo alucinando sin control. La causa real estaba en el registro del servidor:

truncating input prompt  limit=2050  prompt=8195  keep=4  new=2050

ollama cortaba el prompt de 8 195 tokens a 2 050 en silencio, conservando cuatro tokens del principio. Descartaba el 75%, incluida la tarea. El modelo respondía coherentemente a lo poco que le llegaba, y el aviso solo aparece en un registro que nadie mira.

Subiendo la ventana a 32 768, las mismas tareas se ejecutaron correctamente. Ahí está buena parte de la explicación de por qué tanta gente concluye que “los modelos locales no sirven para agentes”.

Con eso resuelto, el termómetro:

instanciatiempodiffresultado
astropy-143093 581 s0 líneasagotó el límite
django-132973 377 s57 líneasresuelta
matplotlib-139892 101 s13 líneasresuelta

Dos de tres, en dos horas y media.

Lo de matplotlib merece detenerse: el agente encontró la misma línea que los mantenedores del proyecto y aplicó la misma corrección con otra sintaxis. Donde el arreglo oficial escribe hist_kwargs['density'] = density, él escribió hist_kwargs.update(density=density). Semánticamente idéntico, y nunca vio el parche.

Astropy no falló por incapacidad. Se quedó sin tiempo: su última salida era “The checkout imports now. Reproduce the bug:”, o sea el camino correcto. Treinta llamadas al modelo en una hora, con la GPU al 15% porque pasaba la mayor parte esperando a la CPU.

Motor 2: DeepSeek V4 Flash, 284 mil millones de parámetros desde el disco

El segundo motor es el más espectacular y el que peor resultado da.

Colibrì es un motor de inferencia en C puro, sin dependencias, que trata disco, memoria y tarjeta como una sola jerarquía. Su promesa es correr modelos enormes en hardware normal transmitiendo los expertos desde el disco en vez de cargarlos.

Compilé el motor en 7.3 segundos y bajé DeepSeek V4 Flash: 284 mil millones de parámetros, 167 GB en disco, 48 fragmentos. La documentación advierte que una descarga puede terminar con un fragmento truncado y reportar éxito igual, así que comparé el tamaño de los 74 archivos contra el repositorio antes de tocar nada. Los 74 exactos.

Y funciona. Le pregunté cuánto es 17 por 23 y respondió correctamente, con estos números:

Residente en memoria6.27 GB de 167 en disco
Peticiones de expertos6 708 para 9 tokens
Aciertos en caché61.4%
Leído del disco30.3 GB
Primer token35.0 s

Seis gigas de memoria para un modelo de 167. No lo carga en memoria: lo transmite desde el disco. Y ese 61% de aciertos en caché es lo único que evita que sea inutilizable.

Pero como motor de un agente, el veredicto es claro: 0.27 tokens por segundo. Una llamada trivial de 16 tokens tardó 60 segundos. Una segunda llamada, algo mayor, murió con error 500 del servidor. Y el servidor atiende una petición a la vez: no hay paralelismo posible, cada paso del razonamiento espera al anterior.

Con la instancia de astropy necesitando treinta llamadas, esto son días, no horas. No lo llevé a SWE-bench porque el resultado ya estaba decidido.

Un apunte técnico: este motor no usa la tarjeta. Su archivo fuente tiene cero enganches con CUDA o Vulkan, mientras que el motor principal del proyecto tiene 112 y 42. Así que los 35 segundos y los 30 GB leídos se consiguieron solo con procesador y disco.

Motor 3: Bonsai-27B en dos bits, el que sí cabe

El tercero es la respuesta directa al problema del primero.

Bonsai es un derivado de Qwen3.6-27B con cuantización ternaria: el mismo tamaño de modelo comprimido a 7.06 GB, nueve veces menos que en precisión completa. Sus autores afirman conservar el 95% de la inteligencia y, textualmente, el comportamiento agéntico por debajo de los cuatro bits.

Eso es exactamente lo que se puede verificar.

Compilé llama.cpp con Vulkan y busqué por medición el techo de la tarjeta, subiendo la ventana hasta que reventara:

contextoVRAMmargen
16 3848 687 MiB7 689
32 7689 716 MiB6 660
65 53611 772 MiB4 604
131 07215 884 MiB492
262 144no levanta

El techo real son 131 072, pero con 492 MiB de margen es demasiado fino para dejarlo fijo. Me quedé en 65 536, que ya es cuatro veces lo que soportaba Qwen3.8 y con todo el modelo dentro de la tarjeta.

Y aquí me tropecé con el segundo fallo silencioso del día, hermano del primero.

Las tres instancias terminaban en menos de un minuto con el diff vacío. A primera vista parecía velocidad prodigiosa. Era el agente estrellándose:

CONTEXT_WINDOW_EXCEEDED: request (18180 tokens)
exceeds the available context size (16384 tokens)

En llama.cpp, el contexto es el total del servidor y se reparte entre las ranuras de atención paralela. Declarar 65 536 con cuatro ranuras no da una ventana de 65 536: da cuatro de 16 384. Para atender a varios usuarios tiene sentido; para un agente, que es una sola conversación que acumula contexto, es lo contrario de lo que necesitas. Con una sola ranura, la petición dispone de la ventana completa.

Si me hubiera quedado con los tiempos sin mirar los diffs, habría publicado que Bonsai es veinte veces más rápido. Y habría sido falso.

Ya con la ventana correcta:

instanciatiempodiffresultado
astropy-14309314 s15 líneasresuelta
django-13297342 s24 líneasno
matplotlib-13989132 s13 líneasresuelta

Dos de tres, en trece minutos.

La comparación que importa

Los dos modelos son del mismo tamaño y de la misma familia. Cambia una sola variable: si caben enteros en la tarjeta.

Qwen3.8-27BBonsai-27B ternario
Archivo17 GB7.06 GB
VRAM usada14 724 MiB11 371 MiB
Ventana32 76865 536
Reparto34% CPU / 66% GPU100% GPU
Velocidad28.9 tok/s52.3 tok/s
SWE-bench2 de 32 de 3
Tiempo total2 h 30 min13 min

Empatan en aciertos. Bonsai es once veces más rápido.

Y el detalle más interesante: resuelven instancias distintas. Bonsai arregló astropy, donde Qwen se quedó sin tiempo. Qwen arregló django, donde Bonsai eligió un parche defensivo, bien razonado pero no el que el test esperaba: entendió que los objetos perezosos rompen al usarse en consultas y decidió desenvolverlos, en vez de eliminar el mecanismo de deprecación completo como hizo Qwen.

No es que uno sea mejor. Tienen perfiles distintos.

Mi opinión

Lo que me llevo de todo esto no es un ranking, son tres cosas.

La primera: para agentes locales pesa más que el modelo quepa entero en la tarjeta que su precisión numérica. Un 27B a dos bits igualó a uno de la misma familia con el cuádruple de bits, porque el segundo pasaba la mayor parte del tiempo esperando a la CPU. La cuantización agresiva tiene un costo real en razonamiento fino, y se nota en el caso de django, pero el intercambio salió a favor.

La segunda, y es la más útil si vas a intentar esto: los dos fallos que más tiempo me costaron no eran del modelo ni del agente, sino de ventanas de contexto mal configuradas que fallan en silencio. Uno truncaba el prompt y solo lo decía en un registro del servidor; el otro repartía la ventana entre ranuras y devolvía un error que parecía del agente. En ambos casos el síntoma visible era “el modelo local no sirve para esto”. Sí sirve; lo que no servía era mi configuración.

La tercera: transmitir un modelo de 284 mil millones de parámetros desde el disco funciona de verdad y es técnicamente precioso, con 6.27 GB residentes para 167 en disco. Pero a 0.27 tokens por segundo pertenece a otra categoría de uso. Para preguntas sueltas donde la calidad importa y la espera no, tiene sentido. Para un agente, no.

Cuándo usaría cada uno

  • Bonsai ternario para agentes locales, sin discusión. Cabe, va rápido y deja ventana de sobra.
  • Qwen3.8 si la tarea depende de razonamiento fino sobre código y el tiempo no aprieta, o si tienes 24 GB de tarjeta y no hay derrame.
  • DeepSeek V4 por Colibrì para consultas puntuales donde quieras la capacidad de un modelo enorme y puedas esperar. No para agentes, al menos mientras el motor no use la tarjeta.

Y por encima de todo: antes de culpar al modelo, mira cuánta ventana está viendo realmente.


Los datos crudos de las tres pruebas, con tiempos, diffs y veredictos de cada test, están en el dataset de mediciones del blog.

Comentarios

Todavía no hay comentarios. El primero es tuyo.

Se revisa antes de publicarse. El correo no se guarda ni aparece en ninguna parte.