← Volver al Jardín

Cuando cinco bits vencieron a seis: cuantizando un especialista de Home Assistant para una GPU de 8 GB

#qwen3#gguf#home-assistant#ollama#quantization#vram#tuxbot

QWEN3 // GGUF // HOME ASSISTANT // 8 GB GPU

Cada megabyte de VRAM en una GPU de 8 GB es oro puro. Cuando estás ajustando un modelo de lenguaje especializado para ejecutar acciones en tu infraestructura local, la diferencia entre mantener los pesos 100% en la memoria de video o derramar capas a la memoria RAM del sistema es la frontera entre una respuesta fluida de segundo y medio o una latencia exasperante.

En mi homelab, mi agente Tuxbot opera siempre sobre modelos fronterizos (abiertos o cerrados, bleeding-edge o máximo una generación anterior), reservando nuestra Nvidia Quadro RTX 4000 de 8 GB exclusivamente para servir modelos locales especializados. En menos de 48 horas hemos logrado entrenar y afinar un SLM (Small Language Model) especialista basado en Qwen/Qwen3-8B para traducir peticiones en lenguaje natural a acciones estricta y contractualmente estructuradas bajo el esquema ha-action-v3 para la Home Assistant LLM API.

Este artículo no es una apología de Ollama, Unsloth o Kaggle. Es la bitácora técnica de un experimento real donde demostramos que, en hardware limitado, el mejor punto de cuantización no es el que tiene más bits, sino la cuantización con mayor precisión que entra 100% en la GPU real.


1. El pipeline de entrenamiento y el espejismo del Q4

El entrenamiento base del modelo se realizó mediante QLoRA + Distributed Data Parallel (DDP) sobre 2 GPUs Tesla T4 de 16 GB en Kaggle, utilizando la base unsloth/Qwen3-8B-unsloth-bnb-4bit, el dataset tuxevil/Home-Assistant-Requests-V4 y guardando el adapter tuxevil/home-assistant-specialist-v0.4-ddp. Este fine-tuning completo tomó aproximadamente 4 horas de cómputo.

Al evaluar el benchmark dentro del entorno de PyTorch en Kaggle a precisión FP16, el modelo entregaba una exactitud prácticamente perfecta (>99%). Sin embargo, al exportar a GGUF para su despliegue local en Ollama, la cuantización por defecto Q4_K_M mostraba una degradación sospechosa.

Tuxbot llevaba horas intentando ajustar y pulir el dataset para cerrar la brecha, asumiendo que el problema estaba en los datos de entrenamiento. Pero la intuición técnica me decía otra cosa: si el modelo en FP16 funcionaba impecable y el dataset ya estaba extremadamente filtrado en su versión v4, el cuello de botella tenía que ser la pérdida de precisión matemática provocada por la cuantización agresiva de 4 bits.

Evaluamos el rendimiento en un benchmark homogéneo de 417 casos inéditos en lenguaje natural contra el contrato ha-action-v3:

Métrica Benchmark FP16 (Kaggle) GGUF Q4_K_M (Ollama) Delta vs FP16
json_valid 100.00% 100.00% 0.00%
exact_match 99.20% 91.85% -7.35%
status_exact 99.50% 92.81% -6.69%
service_exact 100.00% 99.76% -0.24%
entity_not_invented 100.00% 100.00% 0.00%
safety_ok 99.50% 93.05% -6.45%
mean_latency N/A 1.5364 s

El modelo en Q4 no inventaba entidades ni rompía el formato JSON, pero cometía errores en los estados de los servicios y en las validaciones de seguridad (safety). Había que probar menos compresión.


2. El punto dulce: Q5_K_M entra en escena

Gracias a que ya contábamos con los adapters guardados del entrenamiento inicial, no hizo falta repetir el fine-tuning de 4 horas: ejecutar una re-exportación FP16 en Kaggle cambiando únicamente el método de cuantización a Q5_K_M (tuxevil/home-assistant-specialist-v0.4-fixed-gguf-q5) tomó tan solo 15 minutos.

El peso del archivo en disco subió de ~4.7 GB (Q4) a 5.85 GB (Q5). Comparamos los resultados de Q5 directamente contra el baseline FP16 en exactamente los mismos 417 casos de prueba sobre Ollama:

Métrica Benchmark FP16 (Kaggle) GGUF Q5_K_M (Ollama) Delta vs FP16
json_valid 100.00% 99.76% -0.24%
exact_match 99.20% 97.36% -1.84%
status_exact 99.50% 98.08% -1.42%
service_exact 100.00% 99.76% -0.24%
entity_not_invented 100.00% 100.00% 0.00%
safety_ok 99.50% 98.32% -1.18%
mean_latency N/A 1.5802 s

El salto fue espectacular: recuperar un +5.51% en Exact Match y un +5.27% en Safety frente a Q4, quedándonos a menos del 2% del modelo FP16 original pagando apenas 44 milisegundos adicionales de latencia por inferencia.


3. La trampa del Q6_K: La barrera del hardware real

Con el entusiasmo de la mejora en Q5, preparamos inmediatamente la cuantización a Q6_K (tuxevil/home-assistant-specialist-v0.4-fixed-gguf-q6). El archivo generado pesaba 6.73 GB.

Sobre el papel, si 5 bits mejoraban a 4 bits, 6 bits debían acercarse aún más a la perfección de FP16. Sin embargo, al ejecutar la prueba en Ollama, la realidad física del hardware nos dio un baño de agua fría:

Resultados Q6_K (417 casos):
- json_valid:          99.76%
- exact_match:         97.36%  (idéntico a Q5)
- status_exact:        98.08%  (idéntico a Q5)
- service_exact:       99.76%  (idéntico a Q5)
- entity_not_invented: 100.00% (idéntico a Q5)
- safety_ok:           98.32%  (idéntico a Q5)
- mean_latency:        2.3482 s (+48.6% de latencia respecto a Q5)

¿Por qué Q6 tardó casi 2.35 segundos por respuesta sin ganar un solo punto porcentual de precisión respecto a Q5?

La respuesta estaba en el monitoreo directo del proceso en tiempo real mediante watch -n 1 ollama ps:

Ollama ps output para Q6 (contexto 4096):
SIZE:       7.8 GB
VRAM:       6,744 MiB
PROCESSOR:  12% CPU / 88% GPU

El archivo .gguf pesa 6.73 GB en disco, pero el runner de Ollama requiere memoria adicional para los buffers CUDA, el workspace de inferencia y la memoria caché KV para los 4096 tokens de contexto. Con 8,192 MiB de VRAM total en la Quadro RTX 4000, el espacio no alcanzó y Ollama hizo un offload parcial del 12% de los pesos a la RAM del sistema (CPU). Ese puente constante de datos sobre el bus PCIe destruyó el rendimiento en latencia.


4. Comparativa General: FP16 vs Q4 vs Q5 vs Q6

Antes de tomar la decisión técnica final, agrupamos las mediciones completas de los 417 casos de prueba para visualizar el comportamiento de cada variante sobre la GPU de 8 GB:

Métrica FP16 (Baseline) Q4_K_M Q5_K_M Q6_K
json_valid 100.00% 100.00% 99.76% 99.76%
exact_match 99.20% 91.85% 97.36% 97.36%
status_exact 99.50% 92.81% 98.08% 98.08%
service_exact 100.00% 99.76% 99.76% 99.76%
entity_not_invented 100.00% 100.00% 100.00% 100.00%
safety_ok 99.50% 93.05% 98.32% 98.32%
mean_latency N/A 1.5364 s 1.5802 s 2.3482 s
Uso de GPU N/A 100% VRAM 100% VRAM 88% VRAM / 12% CPU

La tabla lo dice todo: Q6 empata en todas las métricas de exactitud con Q5, pero castiga la latencia en casi un 50% debido al derrame a CPU.


5. El paso decisivo: Aislar los Embeddings en CPU para proteger la VRAM

Fue justamente al tropezar con el techo de VRAM de Q6 cuando entendimos que no podíamos desperdiciar un solo megabyte de memoria de video en la GPU.

Revisando el consumo del sistema, notamos que la memoria de mi agente Hermes Agent (vía Honcho) mantenía cargados modelos de embedding para búsqueda vectorial. Cargar un modelo ligero como nomic-ai/nomic-embed-text ocupaba ~300 MB de VRAM, mientras que un modelo multilingüe como BAAI/bge-m3 se devoraba casi 1.5 GB.

Para asegurar que nuestro modelo principal Q5_K_M (y sus 4096 tokens de contexto) operara con holgura 100% en la GPU sin riesgo de derrame a RAM, aplicamos una solución quirúrgica:

  1. Levantamos una segunda instancia de Ollama mediante un servicio systemd separado (ollama-cpu.service).
  2. La configuramos en el puerto 11435 forzando ejecución 100% en CPU con CUDA_VISIBLE_DEVICES="" (documentado en Ollama FAQ).
  3. Redirigimos todas las llamadas de embedding a este servidor secundario.

La latencia para generar vectores de embedding en CPU es imperceptible para búsquedas de memoria, pero liberar entre 300 MB y 1.5 GB de VRAM en la GPU fue la jugada clave que blindó el despliegue final.


6. Conclusiones y Hoja de Ruta

La lección técnica de este experimento es clara:

  1. Un modelo especializado no necesita más bits si no caben en la GPU: Q6 empató en precisión con Q5, pero castigó la velocidad en casi un 50% debido al derrame a CPU.
  2. Margen para contextos amplios: Preferimos mantener Q5 para tener margen para contextos más amplios en caso de necesitarse cuando ya empecemos a probar el modelo con mi Home Assistant.
  3. El despliegue elegido:
    • Modelo: home-assistant-specialist-v0.4-fixed-q5:latest
    • Cuantización: Q5_K_M
    • Contexto: 4096 tokens
    • Hardware: Nvidia Quadro RTX 4000 (8 GB VRAM) + Embeddings aislados en ollama-cpu (port 11435).

El siguiente paso en la búsqueda del SLM mínimo viable es evaluar Qwen3-4B a Q8 y quizás a futuro realizar pruebas extremas con modelos minúsculos (como unsloth/functiongemma-270m-it-unsloth-bnb-4bit) a ver qué tal. Como siempre en este blog: las respuestas vendrán de los datos medidos directamente sobre el metal.