SmolCpp-MoE-360M — SmolLM2-360M Mixture-of-Experts for Code (C++-focused)

Español más abajo / Spanish below

AI disclosure

This project was built with heavy assistance from Claude (Anthropic's AI assistant): the training pipeline code, architecture decisions (Branch- Train-MiX, mergekit-moe, QAT integration), dataset research, and debugging were largely done in collaboration with/by the AI, in a conversational, iterative process. The human author drove the project's goals, made the key product decisions (domain focus, expert split), ran every step on real hardware (Google Colab, then an Android phone via Termux), and debugged real-world failures as they came up — but does not claim independent expertise in the ML internals this project touches. If you have questions about why a particular technical choice was made, the honest answer is often "the AI suggested it and it seemed to work," not deep prior knowledge. This disclosure exists because the author would rather be transparent than imply skills they don't have.

What this is

A small (~360M dense-equivalent) Mixture-of-Experts language model built from HuggingFaceTB/SmolLM2-360M using a Branch-Train-MiX approach: four separate LoRA fine-tunes, each specialized on a distinct coding facet (with an emphasis on C++), merged into a single real Mixture-of-Experts model using mergekit-moe (Mixtral-style architecture), then quantized to GGUF for local/mobile inference via llama.cpp.

The main thing this project actually contributes is not the model's raw capability (it's a 360M model — don't expect it to compete with 1B+ coder models). It's a working, documented pipeline for producing a GGUF file with real MoE routing for small custom models, as opposed to the "dense-equivalent" workaround that most GGUF conversions of custom MoE architectures currently fall back to (llama.cpp has no native converter for arbitrary MoE archs, but it does understand Mixtral natively — so building on the Mixtral arch via mergekit-moe sidesteps that gap entirely).

Architecture

  • Base model: SmolLM2-360M (LLaMA-style architecture)
  • Experts: 4 total, one per facet below
  • Routing: top-2 (experts_per_token: 2), Mixtral-style
  • Gate initialization: gate_mode: hidden — the router is initialized from the base model's hidden states on facet-representative prompts, rather than trained from scratch. This means the router works reasonably out of the box without a separate training loop, though it has not been further fine-tuned.
  • Format: GGUF, quantized to Q4_K_M

Experts and training data

Each expert started as SmolLM2-360M, fine-tuned with LoRA (r=16, alpha=32) on one facet, then merged back into the dense weights, then given a short post-merge Quantization-Aware Training (QAT) pass (int4 fake-quantization via torchao, group_size=32) intended to make the weights more tolerant of the eventual 4-bit quantization. Note this QAT scheme does not exactly match llama.cpp's K-quant block structure, so treat it as a general-purpose robustness pass, not a guarantee tuned specifically for Q4_K_M.

Expert Facet Dataset
cpp_instrucciones C++ instruction/reasoning → code nvidia/OpenCodeReasoning-2 (train/cpp split)
cpp_crudo C++ raw code / autocomplete style bigcode/the-stack-smol (data/c++)
algoritmos Multi-language competitive programming deepmind/code_contests
python_referencia Python instruction → code (cross-reference) iamtarun/python_code_instructions_18k_alpaca

Honest evaluation notes

This was tested manually on a small number of prompts (not a formal benchmark suite), on-device via llama.cpp on an Android phone. Findings:

  • cpp_instrucciones: produced syntactically correct, functionally working C++ (a factorial function) with a minor edge-case bug at n=0.
  • cpp_crudo: continues raw C++ code plausibly and correctly, in the autocomplete style it was trained on (not instruction-following).
  • python_referencia: produced a mostly-correct Python factorial using functools.reduce (the classic idiom), but was missing the from functools import reduce import and had a minor logic bug in its edge-case validation.
  • Generation quality was noticeably worse at default/high sampling temperature; results above used --temp 0.2 --repeat-penalty 1.3. At higher temperature the model can hallucinate (e.g., calling a nonexistent module) or get stuck in repetition loops.
  • Not evaluated: how well the router actually discriminates between facets on realistic mixed prompts (this used hand-picked prompts aligned to each expert's training format, not blind routing tests).

Bottom line: this is a proof of concept, not a production-ready coding assistant. It demonstrates the pipeline works end-to-end; it does not demonstrate strong coding ability at this model size.

Performance

Tested on a Redmi Note 14 4G (MediaTek Helio G99 Ultra, 8GB RAM) via llama.cpp in Termux, CPU-only, -t 2 (using only the 2 Cortex-A76 cores):

  • ~15–18 tokens/second generation
  • ~24–25 tokens/second prompt processing

Known llama.cpp CLI quirk (important if you hit it)

On at least one recent Termux-packaged llama-cli build, the interactive/ completion CLI applies an output-validation layer (peg-native / "Content-only format" parser, originally meant for tool-calling) that can incorrectly reject plain code completions from this model, aborting generation with an error. Neither -no-cnv nor --no-jinja reliably fixed this in testing. Workaround: use llama-server instead of llama-cli, and query the raw /completion HTTP endpoint directly — this bypasses the CLI's output parser entirely and worked reliably in testing.

./llama-server -m SmolCpp-MoE-360M-Q4_K_M.gguf -c 2048 -t 2 --port 8080 &

python3 -c "
import json, urllib.request
prompt = '### Problema:\nEscribe una funcion en C++ que calcule el factorial de un numero.\n\n### Solucion en C++:\n'
payload = json.dumps({'prompt': prompt, 'n_predict': 200, 'temperature': 0.2, 'repeat_penalty': 1.3}).encode()
req = urllib.request.Request('http://localhost:8080/completion', data=payload, headers={'Content-Type': 'application/json'})
print(json.loads(urllib.request.urlopen(req).read())['content'])
"

Prompt format

This is a base model (not instruction-tuned by the base model provider), so it has no chat template. Each expert learned its own plain-text format from its training data:

  • cpp_instrucciones: ### Problema:\n{problem}\n\n### Solucion en C++:\n
  • python_referencia: ### Instruccion:\n{instruction}\n\n### Codigo:\n
  • cpp_crudo: no special format — just feed it partial C++ code to continue

License

  • This model's own weights/code: Apache 2.0 (inherited from the SmolLM2-360M base model).
  • Training data licenses vary by expert and are NOT unified — please review each dataset's own terms before redistributing derivatives:
    • nvidia/OpenCodeReasoning-2: CC-BY-4.0
    • bigcode/the-stack-smol: no single license — each file retains the license of its original source repository; you must comply with those individual licenses (including attribution where required) when using or redistributing content derived from this expert. See the dataset's terms of use.
    • deepmind/code_contests: check the dataset page for current license terms before redistribution.
    • iamtarun/python_code_instructions_18k_alpaca: check the dataset page for current license terms.

Reproducing this

The full training/merge/quantization notebook (Google Colab, T4 GPU) is included in this repository. It covers: LoRA fine-tuning per facet, post-merge QAT with torchao, embedding untying (required for mergekit-moe to work with SmolLM2's tied embeddings), mergekit-moe assembly, and GGUF conversion + quantization.

Acknowledgements


SmolCpp-MoE-360M — SmolLM2-360M, mezcla de expertos para código (enfoque en C++)

Aviso sobre uso de IA

Este proyecto se construyó con asistencia intensiva de Claude (el asistente de IA de Anthropic): el código del pipeline de entrenamiento, las decisiones de arquitectura (Branch-Train-MiX, mergekit-moe, integración de QAT), la investigación de datasets, y la depuración se hicieron en gran parte en colaboración con/por la IA, en un proceso conversacional e iterativo. El autor humano llevó los objetivos del proyecto, tomó las decisiones clave de producto (enfoque de dominio, división de expertos), corrió cada paso en hardware real (Google Colab, luego un celular Android vía Termux), y depuró los fallos reales conforme fueron apareciendo — pero no reclama experiencia independiente en los aspectos internos de ML que toca este proyecto. Si tienes preguntas sobre por qué se tomó alguna decisión técnica en particular, la respuesta honesta muchas veces es "la IA lo sugirió y parecía funcionar", no conocimiento previo profundo. Este aviso existe porque el autor prefiere ser transparente antes que dar a entender habilidades que no tiene.

Qué es esto

Un modelo de lenguaje pequeño (~360M, equivalente denso) de Mezcla de Expertos (MoE), construido a partir de HuggingFaceTB/SmolLM2-360M usando un enfoque Branch-Train-MiX: cuatro fine-tunings LoRA separados, cada uno especializado en una faceta distinta de programación (con énfasis en C++), fusionados en un solo modelo MoE real usando mergekit-moe (arquitectura estilo Mixtral), y luego cuantizado a GGUF para inferencia local/móvil vía llama.cpp.

Lo que este proyecto realmente aporta no es la capacidad bruta del modelo (es un modelo de 360M — no esperes que compita con modelos de código de 1B+). Es un pipeline funcional y documentado para producir un GGUF con routing MoE real, en vez del truco "dense-equivalent" al que recurren la mayoría de las conversiones GGUF de arquitecturas MoE custom actualmente (llama.cpp no tiene un conversor nativo para arquitecturas MoE arbitrarias, pero sí entiende Mixtral de forma nativa — así que construir sobre la arquitectura Mixtral vía mergekit-moe evita ese hueco por completo).

Arquitectura

  • Modelo base: SmolLM2-360M (arquitectura estilo LLaMA)
  • Expertos: 4 en total, uno por cada faceta listada abajo
  • Routing: top-2 (experts_per_token: 2), estilo Mixtral
  • Inicialización del router: gate_mode: hidden — el router se inicializa a partir de los estados ocultos del modelo base sobre prompts representativos de cada faceta, en vez de entrenarse desde cero. Esto significa que el router funciona razonablemente sin un loop de entrenamiento aparte, aunque no se afinó más allá de eso.
  • Formato: GGUF, cuantizado a Q4_K_M

Expertos y datos de entrenamiento

Cada experto arrancó como SmolLM2-360M, se afinó con LoRA (r=16, alpha=32) sobre una faceta, se fusionó de vuelta a los pesos densos, y luego recibió una pasada corta de Quantization-Aware Training (QAT) post-fusión (fake-quantización int4 vía torchao, group_size=32), pensada para que los pesos toleren mejor la cuantización a 4 bits. Nota: este esquema de QAT no coincide exactamente con la estructura de bloques K-quant de llama.cpp, así que trátalo como una pasada general de robustez, no una garantía afinada específicamente para Q4_K_M.

Experto Faceta Dataset
cpp_instrucciones Instrucción/razonamiento en C++ → código nvidia/OpenCodeReasoning-2 (split train/cpp)
cpp_crudo Código C++ crudo / estilo autocompletado bigcode/the-stack-smol (data/c++)
algoritmos Programación competitiva multi-lenguaje deepmind/code_contests
python_referencia Instrucción en Python → código (referencia cruzada) iamtarun/python_code_instructions_18k_alpaca

Notas honestas de evaluación

Esto se probó manualmente con un número pequeño de prompts (no es una batería de benchmarks formal), en el dispositivo vía llama.cpp en un celular Android. Hallazgos:

  • cpp_instrucciones: produjo C++ sintácticamente correcto y funcionalmente correcto (una función de factorial), con un bug menor de caso extremo en n=0.
  • cpp_crudo: continúa código C++ crudo de forma plausible y correcta, en el estilo de autocompletado con el que se entrenó (no sigue instrucciones).
  • python_referencia: produjo un factorial en Python mayormente correcto usando functools.reduce (el idioma clásico), pero le faltaba el import from functools import reduce y tenía un bug menor de lógica en su validación de caso extremo.
  • La calidad de generación fue notablemente peor con temperatura de muestreo alta/por defecto; los resultados de arriba usaron --temp 0.2 --repeat-penalty 1.3. Con temperatura más alta el modelo puede alucinar (por ejemplo, llamar a un módulo inexistente) o quedarse atascado en bucles de repetición.
  • No evaluado: qué tan bien discrimina el router entre facetas con prompts mixtos realistas (se usaron prompts elegidos a mano, alineados al formato de entrenamiento de cada experto, no pruebas ciegas de routing).

En resumen: esto es una prueba de concepto, no un asistente de código listo para producción. Demuestra que el pipeline funciona de punta a punta; no demuestra una capacidad de código fuerte en este tamaño de modelo.

Rendimiento

Probado en un Redmi Note 14 4G (MediaTek Helio G99 Ultra, 8GB RAM) vía llama.cpp en Termux, solo CPU, -t 2 (usando solo los 2 núcleos Cortex-A76):

  • ~15–18 tokens/segundo en generación
  • ~24–25 tokens/segundo en procesamiento del prompt

Detalle conocido del CLI de llama.cpp (importante si te pasa)

En al menos una build reciente de llama-cli empaquetada para Termux, el CLI interactivo/de completado aplica una capa de validación de salida (parser peg-native / "Content-only format", pensado originalmente para llamadas a herramientas) que puede rechazar incorrectamente completados de código plano de este modelo, abortando la generación con un error. Ni -no-cnv ni --no-jinja lo arreglaron de forma confiable en las pruebas. Solución: usa llama-server en vez de llama-cli, y consulta directamente el endpoint HTTP /completion — esto evita por completo el parser de salida del CLI y funcionó de forma confiable en las pruebas (ver el bloque de código en la sección en inglés arriba).

Formato de prompt

Este es un modelo base (no instruction-tuned por el proveedor del modelo base), así que no tiene plantilla de chat. Cada experto aprendió su propio formato de texto plano a partir de sus datos de entrenamiento:

  • cpp_instrucciones: ### Problema:\n{problema}\n\n### Solucion en C++:\n
  • python_referencia: ### Instruccion:\n{instruccion}\n\n### Codigo:\n
  • cpp_crudo: sin formato especial — solo dale código C++ parcial para que lo continúe

Licencia

  • Los pesos/código de este modelo: Apache 2.0 (heredado del modelo base SmolLM2-360M).
  • Las licencias de los datos de entrenamiento varían por experto y NO están unificadas — por favor revisa los términos de cada dataset antes de redistribuir derivados:
    • nvidia/OpenCodeReasoning-2: CC-BY-4.0
    • bigcode/the-stack-smol: sin licencia única — cada archivo conserva la licencia de su repositorio de origen; debes cumplir esas licencias individuales (incluyendo atribución cuando se requiera) al usar o redistribuir contenido derivado de este experto. Ver los términos de uso del dataset.
    • deepmind/code_contests: revisa la página del dataset para los términos de licencia vigentes antes de redistribuir.
    • iamtarun/python_code_instructions_18k_alpaca: revisa la página del dataset para los términos de licencia vigentes.

Cómo reproducir esto

El notebook completo de entrenamiento/fusión/cuantización (Google Colab, GPU T4) está incluido en este repositorio. Cubre: fine-tuning LoRA por faceta, QAT post-fusión con torchao, "destar" los embeddings (necesario para que mergekit-moe funcione con los embeddings atados de SmolLM2), ensamblaje con mergekit-moe, y conversión + cuantización a GGUF.

Agradecimientos

Downloads last month
51
GGUF
Model size
1B params
Architecture
llama
Hardware compatibility
Log In to add your hardware

4-bit

16-bit

Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Model tree for Edgeai-gio/SmolCpp-MoE-360M

Quantized
(43)
this model