
Un modelo de lenguaje ejecutándose en el ESP32-S3
Un transformer hace inferencia dentro del microcontrolador, sin nube: por qué la memoria es la barrera, de dónde viene la velocidad y cómo reproducirlo.
Requisitos de hardware:
- ESP32-S3 com PSRAM: 2 MB para o modelo de 260 mil parâmetros, 8 MB para o de 28,9 milhões
- Flash de 16 MB para o modelo maior
- Cabo USB com transmissão de dados
- ESP-IDF instalado (os projetos não usam Arduino nem PlatformIO)
Descarga el material de este tutorial:
- Kit de apoio (ZIP)12 KBsdkconfig.defaults comentado, tabela de partições para flash de 16 MB, checklist e o manual em PDF. Não contém o firmware: o port do llama2.c é dos autores citados, e o README leva aos repositórios originais.
- Manual (PDF)12 KBAs seis páginas do artigo em formato de leitura offline, com as tabelas de referência, solução de problemas e créditos.
¿Quieres recibir nuevos manuales y esquemas industriales? Únete a nuestro canal de Telegram / WhatsApp
Qué es este artículo y qué no es
Aquí un transformer real ejecuta inferencia dentro de un ESP32-S3: sin nube, sin Wi-Fi y sin servidor al otro lado. Antes de cualquier comando, una advertencia para evitar frustraciones: el modelo que cabe en el chip no es un asistente.
Los pesos utilizados en estos proyectos proceden de TinyStories, historias cortas en inglés con vocabulario reducido, creadas para investigar qué tan pequeño puede ser un modelo y seguir produciendo texto coherente. No responde sobre tu planta, no lee manuales ni sustituye al modelo que usarías en la capa de supervisión.
Lo que demuestra es otra cosa, y es importante: el silicio que ya está en tu panel puede procesar las matemáticas de un transformer.
Hay dos referencias publicadas. En 2024, Dave Bennett ejecutó un Llama de 260 mil parámetros en un ESP32-S3FH4R2 y midió 19,13 tokens por segundo. En 2026, el proyecto esp32-ai llegó a 28,9 millones de parámetros en el mismo chip a 9,88 tokens por segundo, o 94,9 milisegundos por token.
Esas cifras pertenecen a los autores de ambos proyectos y no se midieron aquí. El procedimiento descrito sigue sus repositorios, y el número que midas en tu propia placa es el que vale para ti.
El límite es la memoria, no el procesador
La trampa está en comprar la placa y atrapa a casi todos en el primer intento.
La PSRAM no es opcional. Incluso el modelo de 260 mil parámetros necesita cerca de 1 MB de RAM, mientras que el ESP32-S3 tiene 512 KB de SRAM interna. Sin PSRAM no funciona, y muchos anuncios genéricos de ESP32-S3 DevKit no indican cuánta incluyen o no incluyen ninguna.
Bennett utilizó el ESP32-S3FH4R2, con 2 MB de PSRAM en el encapsulado. esp32-ai pide mucho más: 8 MB de PSRAM y 16 MB de flash, porque el modelo ocupa 14,9 MB con cuantización de 4 bits.
El ESP32 clásico y el ESP32-C3 quedan fuera de este camino. El C3 tiene un solo núcleo y las rutinas vectorizadas que dan velocidad aquí son específicas del S3, como muestra la sección de código.
La jerarquía que permite 28,9 millones de parámetros
El salto de 260 mil a 28,9 millones no vino de un chip mejor. Vino de decidir dónde vive cada parte del modelo.
Hay tres niveles. La SRAM, rápida y pequeña, guarda las activaciones y los pesos de normalización, que se tocan muchas veces por token. La PSRAM, intermedia, guarda el núcleo del modelo y la cabeza de salida, leídos una vez por posición. La flash, grande y lenta, guarda la tabla de 25 millones de parámetros.
El truco que lo hace posible se llama per-layer embeddings y procede de la familia Gemma. La tabla de embeddings suele ser el bloque único más grande de un modelo y precisamente esa parte se mueve a la flash. En cada token solo se buscan allí unos 450 bytes. La flash es lenta, pero 450 bytes cada vez son manejables.
Conviene registrar la inversión: la pieza mayor fue a la memoria más lenta y funcionó porque no importa el tamaño del bloque, sino cuántas veces se lee.
Dónde se gasta el tiempo
En un transformer, la mayor parte del tiempo se consume en multiplicaciones de matrices. El llama2.c original lo hace con un bucle escalar, portátil pero lento.
El ESP32-S3 tiene un camino más rápido: la biblioteca ESP-DSP ofrece rutinas de producto escalar escritas para las instrucciones SIMD del núcleo LX7. La función utilizada es dsps_dotprod_f32_aes3 y el sufijo aes3 identifica la variante del S3. Ella proporciona la velocidad y por eso el C3 no llega al mismo resultado.
Este es el núcleo de la función, en main/llm.c del repositorio de Dave Bennett:
| 1 | void matmul(v4sf *xout, v4sf *x, v4sf *w, int n, int d) |
| 2 | { |
| 3 | *matmul_params = (MatMulTaskParams){xout, x, w, d / 2, d, n, d, TASK_1_BIT}; |
| 4 | xSemaphoreGive(semaDataReady); |
| 5 | for (int i = 0; i < d / 2; i++) |
| 6 | { |
| 7 | v4sf val = 0.0f; |
| 8 | v4sf *row = &w[i * n]; |
| 9 | dsps_dotprod_f32_aes3(row, x, &val, n); |
| 10 | xout[i] = val; |
| 11 | } |
Los dos núcleos en la práctica
Observa qué hace la función antes de empezar a contar. Rellena matmul_params y libera un semáforo; después entra en el bucle.
Ese semáforo despierta una tarea fijada en el otro núcleo, que procesa la mitad inferior de las filas mientras el núcleo que llama procesa la mitad superior. El corte es literal: d es el número de filas y cada lado recibe d / 2.
Esa es la diferencia entre decir que el proyecto usa los dos núcleos y mostrar dónde sucede. Sin el semáforo, el segundo núcleo quedaría parado durante la parte más costosa de la inferencia.
Mide; no confíes en la impresión
La sensación de rapidez engaña, especialmente cuando el texto aparece palabra a palabra en la pantalla. El port ya trae el cálculo al final de la generación:
| 1 | float tks = (pos - 1) / (double)(end - start) * 1000; |
| 2 | fprintf(stderr, "achieved tok/s: %f", tks); |
El tiempo se mide en milisegundos, de ahí la multiplicación por mil. Anota el número de tu placa antes de cambiar cualquier configuración: sin una medida anterior no puedes saber si el cambio siguiente ayudó o empeoró el resultado.
Paso a paso
El camino siguiente es el de los repositorios originales. Ambos usan ESP-IDF, no Arduino ni PlatformIO. Conviene saberlo antes de empezar porque la estructura del proyecto es otra.
1. Confirma la placa antes de comprar
Busca la especificación de PSRAM en el anuncio, no solo el nombre del chip. Para reproducir el proyecto de 260 mil parámetros, el mínimo es 2 MB de PSRAM. Para 28,9 millones necesitas 8 MB de PSRAM y 16 MB de flash.
Si el vendedor no informa la PSRAM, considera que no tiene. Es la causa más común de que el proyecto no arranque.
2. Instala ESP-IDF
Sigue la guía oficial de Espressif para tu sistema y confirma que el entorno está activo antes de continuar. Todo lo demás depende de que esto funcione.
3. Compila
Con el repositorio clonado y el entorno de ESP-IDF cargado, compila con el comando estándar:
| 1 | idf.py build |
La primera compilación tarda porque descarga y construye las dependencias, incluida ESP-DSP.
4. Graba y monitoriza
Sustituye el puerto por el de tu placa. En Linux suele ser algo como ttyUSB0 o ttyACM0 y en Windows es un puerto COM:
| 1 | idf.py -p /dev/ttyUSB0 flash monitor |
Deberías ver que el monitor se abre, la placa se reinicia y el texto comienza a salir token a token en inglés, con estilo de cuento infantil. Al final aparece la línea con el valor de tokens por segundo.
Si sale texto pero a un ritmo mucho menor del esperado, el problema suele estar en la configuración y no en el código. Comprueba que la CPU esté a 240 MHz, la PSRAM a 80 MHz y que se haya ampliado la caché de instrucciones: son los tres ajustes que el autor señala como responsables de la mejora.
5. Cuando no cabe
Un fallo de asignación durante el arranque o un error de memoria al cargar el modelo casi siempre apunta al mismo sitio: PSRAM desactivada en menuconfig o una placa sin PSRAM.
Comprueba también el tamaño de la partición. Para el modelo de 14,9 MB, una tabla de particiones estándar no es suficiente y el firmware necesita espacio para la tabla de embeddings en la flash.
Dónde conecta con la automatización
Seamos claros sobre el estado actual: hoy es una demostración, no un producto. Un modelo TinyStories no tiene sitio en un panel eléctrico.
Lo que anticipa, sin embargo, es concreto. Las mismas rutinas de ESP-DSP y ESP-NN que aceleran este transformer sostienen inferencia útil en el borde: detección de anomalías en vibración de motores, clasificación de sonido y conteo mediante visión simple. Entender aquí los productos escalares vectorizados y la división entre núcleos te deja a un paso de aplicarlos allí.
También hay una lección de arquitectura independiente del modelo: poner el bloque mayor en la memoria más lenta puede ser la decisión correcta si se lee pocas veces. Eso vale para embeddings en flash y para el histórico de proceso en una tarjeta de tu datalogger.
