Un MoE de 35B a más de 45 tok/s en una GPU de $170: mi experiencia con FreeToken y Qwen3.6
Por Sebastián Real
Resumen: Crónica de un día empujando una NVIDIA Quadro RTX 4000 de 8 GB con FreeToken y Qwen3.6-35B-A3B: parches para Turing, pruebas de quants, trampas del harness y la decisión pragmática final.
FREETOKEN // QWEN3.6_35B // RTX4000_8GB // OFFGRID_LAB
Existe una creencia extendida de que si tienes una GPU modesta de 8 GB de VRAM estás condenado a conformarte con modelos densos de 7B u 8B parámetros si quieres mantener una velocidad de respuesta decente.
En mi homelab, donde vivo 100% off-grid generando y almacenando mi propia energía solar, cada watt y cada dólar invertido en hardware cuentan. Mi tarjeta principal para servir modelos locales es una vieja NVIDIA Quadro RTX 4000 de 8 GB con arquitectura Turing (sm_75), que conseguí de segunda mano por apenas $170.
Hace poco intenté correr Qwen/Qwen3.6-35B-A3B en Ollama. El resultado fue decepcionante: 11 tok/s cuando hacía offload completo a CPU y 16 tok/s cuando dividía 74% CPU y 26% GPU. Ante esa lentitud, ya había descartado por completo cualquier modelo que no cupiera 100% dentro de mi VRAM.
Hasta que apareció FlashML-org/FreeToken, un motor de inferencia edge-native diseñado específicamente para modelos Mixture-of-Experts (MoE) que mantiene una caché activa de expertos en la VRAM de la GPU y transfiere dinámicamente los pesos restantes desde la memoria RAM del sistema a través del bus PCIe.
Esta es la crónica de lo que pasó cuando me propuse hacer funcionar un monstruo de 35B parámetros en mi GPU de $170.
1. El primer muro: Turing no es Ampere
Qwen3.6-35B-A3B tiene 35 mil millones de parámetros totales, pero solo activa cerca de 3 mil millones por token generado. Esa arquitectura MoE es el terreno ideal para el caching dinámico de FreeToken.
El problema apareció nada más clonar el repositorio: la rama con soporte para GGUF genérico asumía que estabas corriendo sobre hardware moderno (sm_80+ como Ampere, Ada o Hopper). Mi RTX 4000 es Turing (compute capability 7.5). Al compilar las extensiones de C++ y CUDA, el servidor reventaba con errores en tiempo de ejecución:
RuntimeError: no kernel image is available for execution on the device
Yo no soy un desarrollador de kernels CUDA de bajo nivel ni pretendo serlo; soy un curioso que busca sacarle el máximo partido a su hardware. Me apoyé en ChatGPT como copiloto técnico. En lugar de sugerirme abandonar la idea, ChatGPT se puso a escarbar en el código del repositorio y en los Pull Requests abiertos.
Cruzando el PR #131 (feat/generic-gguf) con el PR #24 (que añadía soporte específico para sm_75), identificamos los bloqueos:
- Añadimos un gating manual en
python/freetoken/kernel/backend.pypara deshabilitarflashinfer,sgl_kernely kernels de Triton incompatibles con Turing, forzando a FreeToken a usar Triton para el mecanismo de atención. - Corregimos una referencia rota en los headers de PyTorch dentro de
ATen/List_inl.h.
Con esos parches aplicados, la extensión GGUF compiló limpiamente para sm_75.
2. El salto de velocidad: 45.44 tok/s en IQ3_S
El primer quant que puse a prueba fue Qwen3.6-35B-A3B-IQ3_S.gguf (cuantización agresiva de 3 bits de jimbothigpen).
En FreeToken, este quant solo puede correrse en modo offload (GPU + caché de expertos + streaming vía PCIe desde la RAM). Ajustando el parámetro memory-ratio a .90, lancé la primera generación de texto.
El resultado me dejó boquiabierto:
Throughput decode: 45.44 tokens/segundo
Prefill (~2K tokens): ~143 tokens/segundo
TTFT (con prefix cache): ~1.21 segundos
Pasar de los 16 tok/s arrastrados de Ollama a más de 45 tokens por segundo con un modelo de 35B en una GPU de $170 fue un antes y un después. La inferencia era completamente fluida, interactiva e instantánea.
3. La comparativa: Q4_K, NVFP4 y la trampa de Q5
Con la euforia del primer éxito, decidí probar otras cuantizaciones para entender el balance entre velocidad y formato:
A. Q4_K (FastFlow)
El quant Qwen3.6-35B-A3B-q4_k.gguf tenía bancos de expertos homogéneos (gate_up=Q4_K, down=Q4_K).
- En decode puro cayó frente a IQ3, logrando 39.45 tok/s.
- Pero en prefill nuevo (~2K tokens) saltó a ~194 tok/s, procesando contexto un 35% más rápido que IQ3.
B. NVFP4 nativo (ModelOpt)
Probamos el checkpoint nativo Qwen3.6-35B-A3B-NVFP4 usando --nvfp4-backend triton (ya que Turing no cuenta con aceleración por hardware para FP4).
- En decode entregó 37.21 tok/s.
- En prefill se hundió estrepitosamente a ~57 tok/s (3.4 veces más lento que Q4), con un cold-start de más de 30 segundos. Quedó claro que sobre Turing, NVFP4 no rinde.
C. Q5_K_S
Pensé que subir a 5 bits ofrecería un salto gigantesco de precisión que justificaría el peso extra.
- El decode cayó a 34.53 tok/s.
- El prefill quedó en ~172 tok/s.
- En las pruebas semánticas posteriores obtuvo un 92.86%, exactamente el mismo score que IQ3. Q5 quedó atrapado en el peor escenario: más pesado, más lento y sin ventaja real palpable en el día a día.
| Quant / Formato | Decode (tok/s) | Prefill @ 2K (tok/s) | Calidad Semántica | Calidad Estricta | Veredicto en Turing |
|---|---|---|---|---|---|
| IQ3_S | 45.44 | 143 | 92.86% | 60.71% | Ganador absoluto en velocidad y ligereza |
| Q4_K | 39.45 | 194 | 89.29% | 71.43% | Excelente ingiriendo contexto nuevo |
| NVFP4 | 37.21 | 57 | 89.29% | 78.57% | Prefill muy lento por emulación Triton |
| Q5_K_S | 34.53 | 172 | 92.86% | 64.29% | Penalización de velocidad sin ganancia semántica |
4. Off-Grid y el mito del modo Hybrid
Mi máquina monta un procesador AMD Ryzen 9 7945HX (16 núcleos, 32 hilos) y 96 GB de memoria RAM DDR5-5600. Como vivo con energía solar, tengo el CPU configurado en la BIOS con límites estrictos de consumo: modo Eco con boost deshabilitado, governor powersave y frecuencia fijada en ~2.5 GHz.
FreeToken incluye un modo hybrid que reparte la ejecución de los expertos MoE entre los núcleos del CPU y la GPU. Al probarlo en modo Eco, el rendimiento colapsó a unos tristes 16 - 23 tok/s.
Nos preguntamos si el modo híbrido estaba sufriendo porque el CPU estaba capado a 2.5 GHz. Para salir de dudas, desbloqueamos la potencia total del Ryzen: activamos boost=1 y governor performance.
El resultado fue revelador: el cambio en la velocidad de inferencia fue mínimo e imperceptible. El cuello de botella no era la frecuencia del procesador, sino la latencia de sincronización y el ancho de banda del bus.
Para mi configuración solar, esto fue una gran victoria: puedo dejar el procesador en modo Eco consumiendo el mínimo de energía y delegar todo el trabajo pesado a la RTX 4000 en modo offload sin sacrificar un solo token por segundo.
5. El falso 25/100: cuando el problema es el harness y no el modelo
Para no quedarnos únicamente con impresiones subjetivas, armamos una batería de pruebas de 28 tareas (razonamiento, extracción, JSON, llamadas a herramientas, código y contexto largo).
Cuando corrimos la primera evaluación sobre IQ3_S, el resultado fue un catastrófico 25/100. En extracción, herramientas y código marcaba cero absoluto.
Cualquiera habría concluido que bajar a 3 bits había destrozado el cerebro del modelo. Pero en lugar de darlo por muerto, nos pusimos a inspeccionar las respuestas crudas que devolvía el backend.
Descubrimos dos problemas garrafales en el entorno de prueba:
- Consumo de tokens en razonamiento interno: En muchas tareas el modelo resolvía el problema a la perfección dentro del bloque
reasoning_content, pero se le agotaba elmax_tokensantes de escribir la respuesta final, devolviendofinish_reason=length. - Parser de herramientas incorrecto: FreeToken venía configurado por defecto con el parser
qwen25, mientras que el modelo generaba llamadas en el formato nativo de Qwen3. Al cambiar el parser aqwen3_coder, las herramientas funcionaron de inmediato. - Permisos en el sandbox: El código generado se ejecutaba de forma segura bajo el usuario
nobody, pero el script intentaba invocar el intérprete de Python dentro de/root, provocando errores de permisosPermissionError.
Al corregir el harness, separar las tareas que requerían razonamiento profundo de las que solo necesitaban extracción directa y evaluar la calidad semántica por separado de la adherencia estricta de formato, la realidad cambió por completo:
- IQ3_S alcanzó un 92.86% de calidad semántica.
- Logró un 4/4 perfecto (100%) en llamadas a herramientas (tool calling).
- Clavó el 100% en las pruebas de código y razonamiento.
El modelo no había perdido su capacidad de razonar. Lo único que le costaba en 3 bits era ceñirse a formatos rígidos (a veces metía bloques de Markdown o explicaciones de más cuando se pedía un string plano).
6. Conclusión: simplicidad y pragmatismo
Al final del día, la teoría sugería un esquema complejo: rutear prompts pesados a Q4_K y respuestas largas a IQ3_S.
Pero yo prefiero la simplicidad práctica:
- Mantener dos o tres versiones cuantizadas del mismo modelo de 35B multiplica el uso de almacenamiento en disco innecesariamente.
- Me quedé con
IQ3_Scomo mi modelo diario. Es el más compacto, es el más rápido generando texto (>45 tok/s) y los pequeños desvíos de formato se corrigen fácilmente a nivel de software mediante un buen harness, system prompts claros y herramientas estructuradas.
Hacer correr un modelo MoE de 35B parámetros a más de 45 tok/s en una GPU de $170 y 8 GB de VRAM, alimentada por paneles solares, demuestra que no hace falta gastar miles de dólares para disfrutar de la inteligencia artificial local de última generación.