<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Smallthinker on lo0 — Blog Técnico</title><link>https://blog.lo0.es/tags/smallthinker/</link><description>Recent content in Smallthinker on lo0 — Blog Técnico</description><generator>Hugo -- gohugo.io</generator><language>es</language><lastBuildDate>Tue, 09 Jun 2026 01:50:00 +0000</lastBuildDate><atom:link href="https://blog.lo0.es/tags/smallthinker/index.xml" rel="self" type="application/rss+xml"/><item><title>Arquitecturas nativas para device: MoE de grano fino y pre-attention router</title><link>https://blog.lo0.es/posts/arquitecturas-nativas-device-moe-grano-fino/</link><pubDate>Tue, 09 Jun 2026 01:50:00 +0000</pubDate><guid>https://blog.lo0.es/posts/arquitecturas-nativas-device-moe-grano-fino/</guid><description>&lt;blockquote>
&lt;p>Este post es de la serie sobre rendimiento de inferencia en modelos pequeños. Es la cara arquitectónica de un problema que ya hemos mirado por el lado del régimen de cómputo (el roofline invertido del SLM) y por el lado de la carga de pesos en &lt;a href="https://blog.lo0.es/posts/del-disco-a-la-hbm-cold-start-carga-modelo/">Del disco a la HBM&lt;/a>. Aquí la pregunta es distinta: ¿y si en lugar de adaptar un modelo grande al device, diseñamos el modelo &lt;em>para&lt;/em> el device desde el primer commit?&lt;/p>
&lt;/blockquote>
&lt;h2 id="tldr">TL;DR&lt;/h2>
&lt;p>El gesto por defecto para llevar un LLM a un portátil, un móvil o un edge box es &lt;strong>coger un denso pensado para cloud y comprimirlo&lt;/strong>: destilación, poda, cuantización. Es un gesto de &lt;em>reducción&lt;/em>: partes de algo grande y le quitas. SmallThinker (arXiv:2507.20984, SJTU IPADS + Zenergize AI) defiende el gesto inverso —&lt;em>diseñar desde cero&lt;/em>— y lo articula en tres piezas. &lt;strong>Primera: MoE de grano fino&lt;/strong>, muchos expertos pequeños con muy pocos activados por token, de modo que los parámetros totales &lt;code>N&lt;/code> (la capacidad) se desacoplan de los parámetros activados &lt;code>A&lt;/code> (el coste de cómputo por token). &lt;strong>Segunda: sparse FFN&lt;/strong>, sparsity de activación tipo ReLU dentro de cada bloque, que añade un segundo nivel de dispersión sobre el primero. &lt;strong>Tercera: un pre-attention router&lt;/strong> que predice qué expertos hará falta &lt;em>antes&lt;/em> de ejecutar el bloque de atención y lanza el prefetch de esos pesos desde SSD/flash en paralelo con el cómputo de la atención, ocultando la latencia de almacenamiento —que es el cuello de botella real cuando el modelo no cabe entero en RAM. Los autores reportan SmallThinker-4B-A0.6B y SmallThinker-21B-A3B superando ~20 tok/s en CPU de consumo con Q4_0, consumiendo ~1 GB y ~8 GB de RAM. Los números son interesantes y la dirección es correcta; la metodología de evaluación y el coste de calidad de activar tan poco merecen escepticismo, y a eso dedicamos la última parte.&lt;/p>
&lt;h2 id="la-analogía-el-bibliotecario-que-se-adelanta-a-tu-pedido">La analogía: el bibliotecario que se adelanta a tu pedido&lt;/h2>
&lt;p>Imagina una biblioteca enorme con una sala de lectura pequeña. Tú estás sentado en la sala con un único pupitre: ahí caben pocos libros a la vez (eso es la RAM). El grueso del fondo está en la trastienda, en estanterías largas y lentas de recorrer (eso es el SSD/flash). Y hay un bibliotecario.&lt;/p>
&lt;p>El método ingenuo: tú lees, llegas a un punto donde necesitas un libro concreto, lo pides, y entonces el bibliotecario se levanta, va a la trastienda, lo busca y vuelve. Mientras tanto, tú esperas con la página abierta sin avanzar. Cada vez que necesitas un libro nuevo, pagas el viaje completo a la trastienda. La sala de lectura está la mayor parte del tiempo esperando, no leyendo.&lt;/p>
&lt;p>El método de SmallThinker: el bibliotecario es listo y se adelanta. Mientras tú todavía estás leyendo el &lt;strong>índice&lt;/strong> del capítulo —averiguando de qué va, relacionando ideas, lo que en el modelo es el bloque de &lt;strong>atención&lt;/strong>—, él ya ha mirado por encima de tu hombro, ha &lt;strong>predicho&lt;/strong> qué tres o cuatro libros vas a pedir y se ha ido a la trastienda a buscarlos. Para cuando terminas el índice y formulas el pedido, los libros ya están sobre tu pupitre. No has esperado: el viaje a la trastienda ocurrió &lt;em>en paralelo&lt;/em> con tu lectura del índice.&lt;/p>
&lt;p>La analogía se sostiene en cuatro detalles:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>El pupitre pequeño es la RAM&lt;/strong>; la trastienda lenta es el &lt;strong>SSD/flash&lt;/strong>.&lt;/li>
&lt;li>&lt;strong>Los libros son los expertos&lt;/strong> del MoE: solo unos pocos están sobre el pupitre en cada momento.&lt;/li>
&lt;li>&lt;strong>Leer el índice es el bloque de atención&lt;/strong>; pedir y usar los libros es el bloque FFN/expertos.&lt;/li>
&lt;li>&lt;strong>El bibliotecario que predice y se adelanta es el pre-attention router&lt;/strong>: la predicción se hace antes, y el viaje a buscar (el prefetch) se solapa con la lectura del índice (la atención).&lt;/li>
&lt;/ul>
&lt;p>La pregunta cuantitativa que recorre todo el post es: ¿llega el bibliotecario a tiempo? Solo se oculta la espera si el viaje a la trastienda dura menos que tu lectura del índice. Esa es la condición &lt;code>t_{\text{atención}} \ge t_{\text{prefetch}}&lt;/code>, y la haremos con números.&lt;/p>
&lt;h2 id="comprimir-un-denso-vs-diseñar-para-device">Comprimir un denso vs. diseñar para device&lt;/h2>
&lt;p>Conviene poner los dos enfoques en frío, porque no son grados de lo mismo: son filosofías distintas.&lt;/p>
&lt;p>&lt;strong>Enfoque A — comprimir un denso pensado para cloud.&lt;/strong> Partes de, digamos, un modelo denso de 7B–14B entrenado para correr en una RTX 4090 (24 GB, Ada Lovelace) o en un cluster genérico 4×H100 SXM (320 GB, NVLink, FP8 nativo). Para meterlo en un device aplicas tres palancas, cada una con su post propio: &lt;a href="https://blog.lo0.es/posts/knowledge-distillation-fundamentos/">destilación&lt;/a> (entrenas un student pequeño que imita al teacher), &lt;a href="https://blog.lo0.es/posts/poda-pruning-llm-fundamentos/">poda&lt;/a> (eliminas pesos o estructuras enteras) y cuantización agresiva (bajas a 4 bits o menos). El modelo resultante &lt;strong>sigue siendo denso&lt;/strong>: todos sus parámetros se activan en cada token. Has reducido el número de parámetros, pero el patrón de cómputo es el del cloud, solo que más pequeño.&lt;/p>
&lt;p>&lt;strong>Enfoque B — diseñar para device desde cero.&lt;/strong> Aquí las restricciones del device entran en la &lt;em>arquitectura&lt;/em>, no en una fase posterior de compresión. Las restricciones son tres y muy concretas:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Cómputo débil.&lt;/strong> Una CPU de portátil o un SoC móvil hace órdenes de magnitud menos FLOPs que una GPU de datacenter. Esto empuja a minimizar los parámetros &lt;strong>activados&lt;/strong> por token, no los totales.&lt;/li>
&lt;li>&lt;strong>Poca RAM.&lt;/strong> No caben decenas de GB. Esto empuja a tener residente solo lo imprescindible y a &lt;em>streamear&lt;/em> el resto.&lt;/li>
&lt;li>&lt;strong>Almacenamiento lento.&lt;/strong> El SSD o la flash a la que te ves obligado a streamear tiene un ancho de banda muy inferior al de la HBM de una GPU. Esto convierte la I/O de almacenamiento en el cuello de botella, y empuja a &lt;em>ocultarla&lt;/em>.&lt;/li>
&lt;/ol>
&lt;p>SmallThinker es el enfoque B llevado al detalle: cada una de esas tres restricciones tiene una respuesta arquitectónica. El cómputo débil se ataca con MoE de grano fino + sparse FFN (minimizar &lt;code>A&lt;/code>). La RAM escasa se ataca con streaming desde SSD (residente ≈ &lt;code>A&lt;/code> + caché, no &lt;code>N&lt;/code>). El almacenamiento lento se ataca con el pre-attention router (ocultar la I/O tras la atención). No es casual que las tres piezas encajen: cada una resuelve una restricción, y juntas se refuerzan.&lt;/p>
&lt;p>Un matiz importante, para no caer en el hype: el enfoque B &lt;strong>no es gratis ni universalmente superior&lt;/strong>. Requiere entrenar un modelo nuevo (no reutilizas pesos existentes), y el techo de calidad de un modelo con &lt;code>A&lt;/code> muy pequeño está intrínsecamente acotado, como veremos. El argumento no es &amp;ldquo;B gana siempre&amp;rdquo;, sino &amp;ldquo;para el régimen del device, B ataca los cuellos correctos, y A solo los ataca de refilón&amp;rdquo;.&lt;/p>
&lt;h2 id="dos-niveles-de-sparsity">Dos niveles de sparsity&lt;/h2>
&lt;p>La idea central de capacidad es vieja y bien entendida en &lt;a href="https://blog.lo0.es/posts/moe-inference-fundamentos/">MoE&lt;/a>: separar &lt;strong>capacidad&lt;/strong> de &lt;strong>coste de cómputo&lt;/strong>. En un MoE, el modelo tiene &lt;code>N&lt;/code> parámetros totales repartidos en expertos, pero para cada token solo se activan &lt;code>A&lt;/code> parámetros (los del top-k de expertos que el router elige). El coste de cómputo por token escala con &lt;code>A&lt;/code>; la capacidad de conocimiento escala con &lt;code>N&lt;/code>. SmallThinker aplica esta idea en &lt;strong>dos niveles superpuestos&lt;/strong>.&lt;/p>
&lt;p>&lt;strong>Nivel 1 — MoE de grano fino.&lt;/strong> &amp;ldquo;Grano fino&amp;rdquo; significa muchos expertos pequeños en vez de pocos expertos grandes, con muy pocos activados por token. En vez de, digamos, 8 expertos de los que activas 2, tienes decenas de expertos de los que activas un puñado. Con expertos más pequeños, el mismo &lt;code>A&lt;/code> se reparte entre más combinaciones posibles, lo que da granularidad fina al router y mantiene &lt;code>A&lt;/code> muy bajo respecto a &lt;code>N&lt;/code>. El resultado es un cociente &lt;code>N/A&lt;/code> agresivo: mucha capacidad, poquísimo cómputo por token.&lt;/p>
&lt;p>&lt;strong>Nivel 2 — sparse FFN (sparsity de activación tipo ReLU).&lt;/strong> Este nivel es ortogonal y opera &lt;em>dentro&lt;/em> de cada FFN. Con una no-linealidad tipo ReLU, una fracción grande de las neuronas de la capa intermedia produce exactamente cero para un token dado. Una neurona que sale a cero no contribuye nada a la salida: su multiplicación matriz-vector se puede saltar. Esto es &lt;em>sparsity de activación&lt;/em>: predecible token a token, y aprovechable para no cargar ni multiplicar las filas/columnas de peso correspondientes a neuronas inactivas. Es el mismo fenómeno que explotan trabajos como Deja Vu o PowerInfer; SmallThinker lo incorpora de fábrica eligiendo activaciones que lo favorecen.&lt;/p>
&lt;p>El efecto combinado, en una frase: &lt;strong>&lt;code>N&lt;/code> grande (capacidad), &lt;code>A&lt;/code> minúsculo (coste de cómputo por token ≈ proporcional a &lt;code>A&lt;/code>)&lt;/strong>, y además dentro de ese &lt;code>A&lt;/code> una fracción de las multiplicaciones se ahorra por la sparsity de activación. Es sparsity sobre sparsity.&lt;/p>
&lt;div class="diagram" style="max-width:780px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 780 300" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="MoE clásico vs MoE de grano fino con sparse FFN">
&lt;defs>&lt;marker id="ar1" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#666"/>&lt;/marker>&lt;/defs>
&lt;text x="195" y="22" text-anchor="middle" font-size="13" font-weight="600" fill="currentColor">MoE clásico (grano grueso)&lt;/text>
&lt;text x="585" y="22" text-anchor="middle" font-size="13" font-weight="600" fill="currentColor">MoE de grano fino + sparse FFN&lt;/text>
&lt;line x1="390" y1="35" x2="390" y2="285" stroke="currentColor" stroke-width="1" stroke-dasharray="4 3"/>
&lt;!-- clasico: 8 expertos grandes, 2 activos -->
&lt;p>&lt;text x="40" y="50" font-size="11" fill="currentColor">8 expertos grandes · activa 2&lt;/text>
&lt;rect x="40" y="60" width="64" height="40" fill="#1f5fa8" stroke="#13335c" stroke-width="1.4"/>
&lt;rect x="114" y="60" width="64" height="40" fill="#cfd8e3" stroke="#7b8794" stroke-width="1.2"/>
&lt;rect x="188" y="60" width="64" height="40" fill="#cfd8e3" stroke="#7b8794" stroke-width="1.2"/>
&lt;rect x="262" y="60" width="64" height="40" fill="#1f5fa8" stroke="#13335c" stroke-width="1.4"/>
&lt;rect x="40" y="106" width="64" height="40" fill="#cfd8e3" stroke="#7b8794" stroke-width="1.2"/>
&lt;rect x="114" y="106" width="64" height="40" fill="#cfd8e3" stroke="#7b8794" stroke-width="1.2"/>
&lt;rect x="188" y="106" width="64" height="40" fill="#cfd8e3" stroke="#7b8794" stroke-width="1.2"/>
&lt;rect x="262" y="106" width="64" height="40" fill="#cfd8e3" stroke="#7b8794" stroke-width="1.2"/>
&lt;text x="183" y="172" text-anchor="middle" font-size="11" fill="currentColor">A grande por experto · granularidad gruesa&lt;/text>&lt;/p>
&lt;!-- fino: muchos expertos pequeños, varios activos pero A total bajo -->
&lt;p>&lt;text x="410" y="50" font-size="11" fill="currentColor">muchos expertos pequeños · activa pocos&lt;/text>
&lt;g>
&lt;rect x="410" y="60" width="28" height="22" fill="#2a7a40" stroke="#1a4d29" stroke-width="1.2"/>
&lt;rect x="444" y="60" width="28" height="22" fill="#cfe3d5" stroke="#7b948a" stroke-width="1"/>
&lt;rect x="478" y="60" width="28" height="22" fill="#cfe3d5" stroke="#7b948a" stroke-width="1"/>
&lt;rect x="512" y="60" width="28" height="22" fill="#2a7a40" stroke="#1a4d29" stroke-width="1.2"/>
&lt;rect x="546" y="60" width="28" height="22" fill="#cfe3d5" stroke="#7b948a" stroke-width="1"/>
&lt;rect x="580" y="60" width="28" height="22" fill="#cfe3d5" stroke="#7b948a" stroke-width="1"/>
&lt;rect x="614" y="60" width="28" height="22" fill="#cfe3d5" stroke="#7b948a" stroke-width="1"/>
&lt;rect x="648" y="60" width="28" height="22" fill="#cfe3d5" stroke="#7b948a" stroke-width="1"/>
&lt;rect x="410" y="88" width="28" height="22" fill="#cfe3d5" stroke="#7b948a" stroke-width="1"/>
&lt;rect x="444" y="88" width="28" height="22" fill="#cfe3d5" stroke="#7b948a" stroke-width="1"/>
&lt;rect x="478" y="88" width="28" height="22" fill="#2a7a40" stroke="#1a4d29" stroke-width="1.2"/>
&lt;rect x="512" y="88" width="28" height="22" fill="#cfe3d5" stroke="#7b948a" stroke-width="1"/>
&lt;rect x="546" y="88" width="28" height="22" fill="#cfe3d5" stroke="#7b948a" stroke-width="1"/>
&lt;rect x="580" y="88" width="28" height="22" fill="#cfe3d5" stroke="#7b948a" stroke-width="1"/>
&lt;rect x="614" y="88" width="28" height="22" fill="#2a7a40" stroke="#1a4d29" stroke-width="1.2"/>
&lt;rect x="648" y="88" width="28" height="22" fill="#cfe3d5" stroke="#7b948a" stroke-width="1"/>
&lt;/g>
&lt;text x="543" y="128" text-anchor="middle" font-size="11" fill="currentColor">A total bajo · granularidad fina&lt;/text>&lt;/p>
&lt;!-- sparse FFN dentro -->
&lt;p>&lt;text x="410" y="158" font-size="11" font-weight="600" fill="currentColor">+ sparse FFN dentro de cada experto activo:&lt;/text>
&lt;g>
&lt;rect x="410" y="168" width="14" height="40" fill="#a48000"/>
&lt;rect x="428" y="168" width="14" height="40" fill="#f1e7c2" stroke="#b8a96a" stroke-width="0.8"/>
&lt;rect x="446" y="168" width="14" height="40" fill="#a48000"/>
&lt;rect x="464" y="168" width="14" height="40" fill="#f1e7c2" stroke="#b8a96a" stroke-width="0.8"/>
&lt;rect x="482" y="168" width="14" height="40" fill="#f1e7c2" stroke="#b8a96a" stroke-width="0.8"/>
&lt;rect x="500" y="168" width="14" height="40" fill="#a48000"/>
&lt;rect x="518" y="168" width="14" height="40" fill="#f1e7c2" stroke="#b8a96a" stroke-width="0.8"/>
&lt;rect x="536" y="168" width="14" height="40" fill="#f1e7c2" stroke="#b8a96a" stroke-width="0.8"/>
&lt;rect x="554" y="168" width="14" height="40" fill="#a48000"/>
&lt;rect x="572" y="168" width="14" height="40" fill="#f1e7c2" stroke="#b8a96a" stroke-width="0.8"/>
&lt;/g>
&lt;text x="600" y="192" font-size="11" fill="currentColor">neuronas a 0 (ReLU) →&lt;/text>
&lt;text x="600" y="206" font-size="11" fill="currentColor">se saltan en el cómputo&lt;/text>&lt;/p>
&lt;p>&lt;text x="40" y="245" font-size="11.5" font-weight="600" fill="currentColor">Capacidad = N (todos los expertos) · Coste/token ≈ A (activados) · y dentro de A, sparse FFN ahorra más&lt;/text>
&lt;text x="40" y="270" font-size="11" fill="currentColor">El truco: subir N sin subir A. La granularidad fina permite un cociente N/A mucho más agresivo.&lt;/text>
&lt;/svg>&lt;/p>
&lt;/div>
&lt;h2 id="el-pre-attention-router-predecir-y-prefetchar">El pre-attention router: predecir y prefetchar&lt;/h2>
&lt;p>Aquí está la pieza específica del paper, y la que da nombre al post. El problema que resuelve es de &lt;em>scheduling de I/O&lt;/em>, no de calidad.&lt;/p>
&lt;p>Cuando el modelo no cabe entero en RAM, los pesos de los expertos viven en SSD/flash y se cargan bajo demanda. El flujo ingenuo de una capa MoE es secuencial: ejecutas la atención, luego el router decide qué expertos tocan, luego &lt;strong>cargas esos expertos desde SSD&lt;/strong> (esperando), luego ejecutas la FFN de esos expertos. El paso de carga es una espera pura: la CPU está bloqueada esperando bytes del SSD. En el régimen del device, donde el SSD es lento, ese tiempo de espera domina el step de decode.&lt;/p>
&lt;p>El &lt;strong>pre-attention router&lt;/strong> rompe la secuencialidad invirtiendo el orden de la decisión. La observación es que el router no necesita la salida de la atención de &lt;em>esta&lt;/em> misma capa para hacer una predicción razonable de qué expertos harán falta: puede predecirlo a partir del estado que ya tiene &lt;em>antes&lt;/em> de ejecutar la atención. Así que:&lt;/p>
&lt;ol>
&lt;li>&lt;strong>Antes&lt;/strong> de ejecutar el bloque de atención de la capa, el router predice los expertos que se necesitarán.&lt;/li>
&lt;li>Lanza el &lt;strong>prefetch&lt;/strong> de esos expertos desde SSD/flash de forma asíncrona.&lt;/li>
&lt;li>&lt;strong>En paralelo&lt;/strong>, la CPU ejecuta el bloque de atención —que es cómputo puro, no necesita el SSD.&lt;/li>
&lt;li>Cuando la atención termina, los expertos prefetchados ya están (idealmente) en RAM, y la FFN procede sin esperar.&lt;/li>
&lt;/ol>
&lt;p>El I/O de almacenamiento se ha &lt;strong>solapado&lt;/strong> con el cómputo de atención. Es exactamente el bibliotecario que va a la trastienda mientras tú lees el índice.&lt;/p>
&lt;div class="diagram" style="max-width:780px;margin:1.5rem auto;">
&lt;svg viewBox="0 0 780 270" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="Flujo pre-attention router con prefetch solapado">
&lt;defs>&lt;marker id="ar2" viewBox="0 0 10 10" refX="9" refY="5" markerWidth="6" markerHeight="6" orient="auto">&lt;path d="M0,0 L10,5 L0,10 z" fill="#666"/>&lt;/marker>&lt;/defs>
&lt;p>&lt;text x="20" y="22" font-size="12.5" font-weight="600" fill="currentColor">Ingenuo (secuencial): la carga desde SSD bloquea&lt;/text>
&lt;rect x="20" y="32" width="110" height="34" fill="#d4ecff" stroke="#1f5fa8" stroke-width="1.4"/>
&lt;text x="75" y="53" text-anchor="middle" font-size="11" fill="#13335c">atención&lt;/text>
&lt;rect x="138" y="32" width="80" height="34" fill="#e6d0ff" stroke="#5a2db0" stroke-width="1.4"/>
&lt;text x="178" y="53" text-anchor="middle" font-size="11" fill="#3a1d70">router&lt;/text>
&lt;rect x="226" y="32" width="180" height="34" fill="#f6caca" stroke="#a52a2a" stroke-width="1.4"/>
&lt;text x="316" y="49" text-anchor="middle" font-size="11" fill="#6e1d1d">carga expertos desde SSD&lt;/text>
&lt;text x="316" y="61" text-anchor="middle" font-size="10" fill="#6e1d1d">(espera bloqueante)&lt;/text>
&lt;rect x="414" y="32" width="110" height="34" fill="#cdebd0" stroke="#2a7a40" stroke-width="1.4"/>
&lt;text x="469" y="53" text-anchor="middle" font-size="11" fill="#1a4d29">FFN expertos&lt;/text>
&lt;path d="M130,49 L138,49" stroke="#666" stroke-width="1.4" fill="none" marker-end="url(#ar2)"/>
&lt;path d="M218,49 L226,49" stroke="#666" stroke-width="1.4" fill="none" marker-end="url(#ar2)"/>
&lt;path d="M406,49 L414,49" stroke="#666" stroke-width="1.4" fill="none" marker-end="url(#ar2)"/>
&lt;text x="540" y="53" font-size="11" fill="currentColor">t_total = t_att + t_load + t_ffn&lt;/text>&lt;/p>
&lt;line x1="20" y1="92" x2="760" y2="92" stroke="currentColor" stroke-width="0.8" stroke-dasharray="3 3"/>
&lt;p>&lt;text x="20" y="118" font-size="12.5" font-weight="600" fill="currentColor">Pre-attention router: el prefetch se solapa con la atención&lt;/text>&lt;/p>
&lt;p>&lt;text x="20" y="140" font-size="11" font-weight="600" fill="currentColor">hilo de cómputo (CPU)&lt;/text>
&lt;rect x="170" y="130" width="80" height="30" fill="#e6d0ff" stroke="#5a2db0" stroke-width="1.4"/>
&lt;text x="210" y="149" text-anchor="middle" font-size="10.5" fill="#3a1d70">router (pre)&lt;/text>
&lt;rect x="258" y="130" width="150" height="30" fill="#d4ecff" stroke="#1f5fa8" stroke-width="1.4"/>
&lt;text x="333" y="149" text-anchor="middle" font-size="11" fill="#13335c">atención (t_att)&lt;/text>
&lt;rect x="416" y="130" width="120" height="30" fill="#cdebd0" stroke="#2a7a40" stroke-width="1.4"/>
&lt;text x="476" y="149" text-anchor="middle" font-size="11" fill="#1a4d29">FFN expertos&lt;/text>&lt;/p>
&lt;p>&lt;text x="20" y="190" font-size="11" font-weight="600" fill="currentColor">hilo de I/O (SSD)&lt;/text>
&lt;rect x="258" y="180" width="130" height="30" fill="#ffe0a8" stroke="#a48000" stroke-width="1.4"/>
&lt;text x="323" y="199" text-anchor="middle" font-size="10.5" fill="#6b5400">prefetch expertos (t_prefetch)&lt;/text>&lt;/p>
&lt;path d="M250,145 C254,145 254,150 258,150" stroke="#5a2db0" stroke-width="1.2" fill="none"/>
&lt;path d="M250,150 L256,150 L256,195 L258,195" stroke="#a48000" stroke-width="1.2" fill="none" marker-end="url(#ar2)" stroke-dasharray="3 2"/>
&lt;line x1="258" y1="122" x2="258" y2="218" stroke="currentColor" stroke-width="0.7" stroke-dasharray="2 2"/>
&lt;line x1="408" y1="122" x2="408" y2="218" stroke="currentColor" stroke-width="0.7" stroke-dasharray="2 2"/>
&lt;p>&lt;text x="20" y="245" font-size="11.5" fill="currentColor">El prefetch queda oculto si &lt;tspan font-weight="700">t_att ≥ t_prefetch&lt;/tspan>: para cuando la atención termina, los expertos ya están en RAM.&lt;/text>
&lt;text x="20" y="262" font-size="11" fill="currentColor">Si t_prefetch &amp;gt; t_att, asoma una burbuja de espera (t_prefetch − t_att) antes de la FFN. Ese es el caso a evitar.&lt;/text>
&lt;/svg>&lt;/p>
&lt;/div>
&lt;p>La &lt;strong>condición de ocultamiento&lt;/strong> es la desigualdad de arriba: el prefetch se oculta completamente si y solo si&lt;/p>
&lt;p>$$t_{\text{atención}} ;\ge; t_{\text{prefetch}}.$$&lt;/p>
&lt;p>Si la atención tarda más que cargar los expertos, la carga es gratis (ya estaba hecha). Si los expertos son demasiado grandes o el SSD demasiado lento, &lt;code>t_prefetch &amp;gt; t_att&lt;/code> y asoma una burbuja de espera igual a &lt;code>t_prefetch − t_att&lt;/code>. Por eso el diseño &lt;em>necesita&lt;/em> que &lt;code>A&lt;/code> sea pequeño (expertos pequeños → menos bytes a prefetchar → &lt;code>t_prefetch&lt;/code> bajo) y que el grano sea fino: las dos cosas que hace el nivel 1 de sparsity no son solo para ahorrar FLOPs, son para que el prefetch quepa debajo de la atención.&lt;/p>
&lt;h2 id="las-matemáticas-que-importan">Las matemáticas que importan&lt;/h2>
&lt;h3 id="footprint-de-memoria-n-residente-vs-a--caché">Footprint de memoria: N residente vs. A + caché&lt;/h3>
&lt;p>El parámetro que decide si el modelo cabe es cuánto tienes que tener &lt;strong>residente en RAM&lt;/strong> a la vez.&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Todo en RAM.&lt;/strong> Si exiges que todos los expertos estén cargados, el footprint es &lt;code>\approx N&lt;/code> (todos los parámetros, multiplicados por bytes/parámetro según la cuantización). Para un 21B esto es prohibitivo en un device.&lt;/li>
&lt;li>&lt;strong>Streaming desde SSD.&lt;/strong> Si solo mantienes residentes los expertos activos más una caché de los recientes/probables, el footprint cae a &lt;code>\approx A + \text{caché}&lt;/code>. Los pesos que no están en RAM viven en SSD y se prefetchan cuando toca. Aquí está el ahorro real: el residente escala con &lt;code>A&lt;/code>, no con &lt;code>N&lt;/code>.&lt;/li>
&lt;/ul>
&lt;p>La parte no-experta del modelo (embeddings, atención, router, layernorms) sí está siempre residente, pero en un MoE de grano fino el grueso de &lt;code>N&lt;/code> está en los expertos, así que la aproximación &lt;code>residente ≈ A + caché + parte_densa&lt;/code> es buena.&lt;/p>
&lt;h3 id="el-cálculo-de-prefetch-con-números">El cálculo de prefetch, con números&lt;/h3>
&lt;p>Pongamos los números de la analogía. Supón un SSD de consumo a &lt;strong>5 GB/s&lt;/strong> de lectura secuencial y un experto cuantizado de tamaño &lt;code>X&lt;/code> MB. El tiempo de cargar un experto es&lt;/p>
&lt;p>$$t_{\text{1 experto}} = \frac{X \text{ MB}}{5000 \text{ MB/s}} = \frac{X}{5000}\ \text{s} = \frac{X}{5}\ \text{ms}.$$&lt;/p>
&lt;p>Concretemos &lt;code>X&lt;/code>. En SmallThinker-4B-A0.6B con Q4_0 (~0.5 byte/param efectivo contando overhead de bloques), un experto pequeño de, digamos, 4M parámetros pesa &lt;code>\approx 4\text{M} \times 0.5 = 2&lt;/code> MB. Cargarlo cuesta &lt;code>t_{\text{1 experto}} = 2/5 = 0.4&lt;/code> ms.&lt;/p>
&lt;p>Ahora la pregunta de scheduling: si el bloque de atención de la capa toma &lt;code>Y&lt;/code> ms, &lt;strong>¿cuántos expertos puedo prefetchar mientras la atención corre?&lt;/strong> El número es&lt;/p>
&lt;p>$$n_{\text{prefetch}} = \left\lfloor \frac{Y}{t_{\text{1 experto}}} \right\rfloor = \left\lfloor \frac{Y \cdot 5}{X} \right\rfloor.$$&lt;/p>
&lt;p>Con &lt;code>Y = 2&lt;/code> ms de atención y &lt;code>X = 2&lt;/code> MB por experto: &lt;code>n_{\text{prefetch}} = \lfloor 2 \times 5 / 2 \rfloor = 5&lt;/code> expertos. Es decir, en la ventana de atención de esa capa el SSD alcanza a traer 5 expertos. Si el top-k de la capa activa ≤ 5 expertos, el prefetch los oculta todos y &lt;code>t_prefetch ≤ t_att&lt;/code>: latencia de carga cero. Si la capa necesitara 8 expertos, traerías 5 gratis y pagarías la carga de los 3 restantes como burbuja: &lt;code>(8-5) \times 0.4 = 1.2&lt;/code> ms de espera por capa. De ahí que el diseño quiera grano fino con top-k pequeño: para caber debajo de la ventana de atención.&lt;/p>
&lt;p>Dos observaciones críticas sobre este cálculo:&lt;/p>
&lt;ul>
&lt;li>Los 5 GB/s son &lt;strong>lectura secuencial idealizada&lt;/strong>. Los expertos están dispersos en disco; lecturas aleatorias 4K en un SSD de consumo van mucho más lentas. El ancho de banda efectivo puede ser una fracción del nominal, lo que reduce &lt;code>n_{\text{prefetch}}&lt;/code>. La metodología que reporte tok/s debería decir si mide con expertos pre-ordenados en disco o con acceso realista.&lt;/li>
&lt;li>La ventana &lt;code>Y&lt;/code> de atención &lt;strong>encoge con el contexto corto&lt;/strong> y al inicio de la generación. Con prompts cortos, la atención es barata y puede que &lt;em>no&lt;/em> cubra el prefetch; la ventaja del solapamiento crece con secuencias más largas. Otro detalle que un benchmark honesto debería desglosar.&lt;/li>
&lt;/ul>
&lt;h3 id="footprint-de-pesos-por-qué-reportan-1-gb-para-un-4b">Footprint de pesos: por qué reportan ~1 GB para un 4B&lt;/h3>
&lt;p>Hagamos la cuenta del 4B en Q4_0. Cuantización a 4 bits ≈ 0.5 byte/param, más un pequeño overhead de escalas por bloque (Q4_0 añade un FP16 de escala cada 32 pesos, ~0.56 byte/param efectivos). Entonces:&lt;/p>
&lt;p>$$4\text{B} \times 0.5\ \text{B/param} \approx 2\ \text{GB}.$$&lt;/p>
&lt;p>Es decir, &lt;strong>el modelo completo en Q4_0 ocupa ~2 GB en disco&lt;/strong>. Pero los autores reportan &lt;strong>~1 GB de RAM&lt;/strong>. ¿Contradicción? No, y entender por qué es entender el diseño:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>No todos los expertos están residentes.&lt;/strong> Solo los activados (&lt;code>A = 0.6B&lt;/code>) y una caché caben en RAM; el resto vive en SSD y se streamea. &lt;code>0.6\text{B} \times 0.5 \approx 0.3&lt;/code> GB de expertos activos, más la parte densa (atención, embeddings, router) y una caché de expertos calientes.&lt;/li>
&lt;li>&lt;strong>La sparse FFN reduce el trabajo y el residente útil.&lt;/strong> Las neuronas que salen a cero no necesitan estar materializadas para ese token.&lt;/li>
&lt;/ul>
&lt;p>Sumando expertos activos + parte densa + caché razonable, ~1 GB es plausible. Pero ojo con el matiz: ~1 GB es el &lt;strong>residente en RAM&lt;/strong>, no el footprint total en almacenamiento, que sigue siendo ~2 GB en SSD. Confundir ambos —reportar &amp;ldquo;1 GB&amp;rdquo; a secas— es engañoso si el lector entiende &amp;ldquo;el modelo ocupa 1 GB&amp;rdquo;. Ocupa 2 GB; &lt;em>mantiene&lt;/em> 1 GB en RAM. La distinción importa para un device con 2 GB de almacenamiento libre: ahí no entra.&lt;/p>
&lt;p>Análogamente, SmallThinker-21B-A3B: &lt;code>21\text{B} \times 0.5 \approx 10.5&lt;/code> GB en disco; &lt;code>3\text{B} \times 0.5 \approx 1.5&lt;/code> GB de expertos activos, y el ~8 GB de RAM reportado incluye expertos activos + caché generosa + parte densa. La caché grande es lo que sube de 1.5 a ~8 GB: mantienes muchos expertos calientes residentes para no golpear el SSD constantemente.&lt;/p>
&lt;h2 id="el-coste-de-calidad-el-escepticismo-necesario">El coste de calidad: el escepticismo necesario&lt;/h2>
&lt;p>Toda la maquinaria anterior reduce el cómputo por token a &lt;code>\approx A&lt;/code>. Pero &lt;code>A = 0.6B&lt;/code> activados es &lt;strong>muy&lt;/strong> poco. Aquí es donde hay que poner el freno al entusiasmo:&lt;/p>
&lt;ul>
&lt;li>&lt;strong>Capacidad de razonamiento acotada.&lt;/strong> Un modelo que activa 0.6B de parámetros por token tiene, por token, la potencia de cómputo de un modelo de 0.6B, no de 4B. La capacidad total &lt;code>N=4B&lt;/code> ayuda a &lt;em>almacenar&lt;/em> más conocimiento (más expertos especializados), pero el &lt;em>procesamiento&lt;/em> de cada token sigue limitado por &lt;code>A&lt;/code>. Para tareas que requieren composición y razonamiento multi-paso intensivo, esto es un techo real, no un detalle.&lt;/li>
&lt;li>&lt;strong>El router es un punto único de fallo de calidad.&lt;/strong> Si el router de grano fino elige mal los expertos —y con grano fino hay más decisiones que tomar—, la calidad cae sin que ninguna métrica de velocidad lo refleje. El pre-attention router agrava esto: predice los expertos &lt;em>antes&lt;/em> de ver la atención, con menos información que un router post-atención. Los autores deberían reportar cuánta calidad se pierde por predecir antes (mismatch entre experto prefetchado y experto que el router post-atención habría elegido).&lt;/li>
&lt;li>&lt;strong>Los ~20 tok/s necesitan letra pequeña.&lt;/strong> ¿En qué CPU exactamente? ¿Con qué longitud de contexto y de generación (la ventaja del solapamiento depende de &lt;code>Y&lt;/code>)? ¿Cold start incluido o steady state? ¿El SSD estaba con los expertos pre-ordenados secuencialmente? Un &amp;ldquo;supera 20 tok/s&amp;rdquo; sin esas condiciones es un número de marketing, no de metodología.&lt;/li>
&lt;li>&lt;strong>Comparación justa.&lt;/strong> La pregunta correcta no es &amp;ldquo;¿es rápido?&amp;rdquo;, sino &amp;ldquo;¿a igualdad de calidad en un benchmark independiente, es más rápido o más pequeño que un denso comprimido equivalente?&amp;rdquo;. Eso requiere evals que el lector pueda reproducir, no solo tok/s en la máquina de los autores.&lt;/li>
&lt;/ul>
&lt;p>Nada de esto invalida la dirección. Diseñar para device es, conceptualmente, el enfoque correcto: ataca los cuellos reales (cómputo, RAM, I/O) en la arquitectura en vez de paliar­los después. Pero &amp;ldquo;20 tok/s en ~1 GB&amp;rdquo; es una afirmación de &lt;em>eficiencia&lt;/em>, y la eficiencia solo significa algo anclada a un nivel de &lt;em>calidad&lt;/em> medido honestamente. Mientras esa ancla no esté clara, el número correcto de escepticismo es alto.&lt;/p>
&lt;h2 id="implicaciones-para-inferencia-on-premise-y-edge">Implicaciones para inferencia on-premise y edge&lt;/h2>
&lt;ul>
&lt;li>&lt;strong>El SSD pasa a ser parte de la jerarquía de inferencia.&lt;/strong> En cloud, la jerarquía es HBM → RAM. En device, el SSD/flash entra como un nivel más, y su ancho de banda y latencia de acceso aleatorio se vuelven parámetros de rendimiento de primer orden. Esto conecta con &lt;a href="https://blog.lo0.es/posts/del-disco-a-la-hbm-cold-start-carga-modelo/">Del disco a la HBM&lt;/a>: el cold start y el streaming de pesos dejan de ser solo un problema de arranque y pasan a ser parte del &lt;em>steady state&lt;/em>.&lt;/li>
&lt;li>&lt;strong>El edge box hetero­géneo gana sentido.&lt;/strong> En un patrón de &lt;a href="https://blog.lo0.es/posts/entornos-mixtos-nvidia-intel-servidores-nucs/">entornos mixtos&lt;/a>, un modelo nativo-device como SmallThinker corre en el NUC/edge con CPU y SSD, sirviendo localmente, mientras lo pesado se queda en el cluster central. El pre-attention router es lo que hace viable el edge box sin GPU.&lt;/li>
&lt;li>&lt;strong>El capacity planning cambia de ejes.&lt;/strong> Como discute &lt;a href="https://blog.lo0.es/posts/capacity-planning-inferencia-llm-on-premise/">Capacity planning de inferencia&lt;/a>, en device el recurso a planificar no es VRAM sino la terna RAM-residente / ancho-de-banda-SSD / FLOPs-de-CPU. Un modelo con &lt;code>A&lt;/code> pequeño y prefetch solapado mueve el cuello de botella de &amp;ldquo;¿cabe en RAM?&amp;rdquo; a &amp;ldquo;¿el SSD alimenta el prefetch a tiempo?&amp;rdquo;.&lt;/li>
&lt;/ul>
&lt;h2 id="conclusión">Conclusión&lt;/h2>
&lt;p>SmallThinker es, sobre todo, un cambio de pregunta. No &amp;ldquo;¿cómo encojo este modelo cloud para que quepa en el device?&amp;rdquo; sino &amp;ldquo;¿cómo sería el modelo si lo diseñara para el device desde el primer parámetro?&amp;rdquo;. La respuesta —MoE de grano fino para desacoplar &lt;code>N&lt;/code> de &lt;code>A&lt;/code>, sparse FFN para ahorrar dentro de &lt;code>A&lt;/code>, y un pre-attention router que oculta la I/O de almacenamiento bajo la atención— ataca las tres restricciones del device (cómputo, RAM, I/O) en la arquitectura, no en una fase de compresión posterior. La condición clave, &lt;code>t_att ≥ t_prefetch&lt;/code>, explica por qué las piezas encajan: el grano fino no solo ahorra FLOPs, hace que el prefetch quepa debajo de la atención. Los números reportados (~20 tok/s, ~1 GB / ~8 GB de RAM) son prometedores y la dirección es sólida; el coste de activar tan poco y la falta de detalle metodológico sobre calidad piden cautela. Diseñar para device es la apuesta correcta; medirlo honestamente es la asignatura pendiente.&lt;/p>
&lt;h2 id="ver-también">Ver también&lt;/h2>
&lt;ul>
&lt;li>&lt;a href="https://blog.lo0.es/posts/moe-inference-fundamentos/">MoE inference: el call center con 256 especialistas&lt;/a> — la base conceptual de este post: cómo un router enruta tokens a expertos y por qué &lt;code>N&lt;/code> y &lt;code>A&lt;/code> se desacoplan; léelo primero si MoE te suena lejano.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/del-disco-a-la-hbm-cold-start-carga-modelo/">Del disco a la HBM: cold start y carga de modelo&lt;/a> — el streaming de pesos desde almacenamiento lento, que aquí deja de ser problema de arranque y pasa a steady state vía prefetch.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/knowledge-distillation-fundamentos/">Knowledge distillation&lt;/a> — la palanca canónica del enfoque &amp;ldquo;comprimir un denso de cloud&amp;rdquo;, el contrapunto exacto del enfoque nativo-device.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/poda-pruning-llm-fundamentos/">Poda de modelos LLM&lt;/a> — la otra palanca de reducción; útil para comparar &amp;ldquo;quitar a un grande&amp;rdquo; frente a &amp;ldquo;diseñar pequeño desde cero&amp;rdquo;.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/entornos-mixtos-nvidia-intel-servidores-nucs/">Entornos mixtos NVIDIA + Intel&lt;/a> — dónde encaja un modelo nativo-device: el edge box con CPU y SSD que sirve localmente sin GPU.&lt;/li>
&lt;li>&lt;a href="https://blog.lo0.es/posts/capacity-planning-inferencia-llm-on-premise/">Capacity planning de inferencia LLM on-premise&lt;/a> — en device los ejes a planificar son RAM-residente, ancho de banda de SSD y FLOPs de CPU, no VRAM.&lt;/li>
&lt;li>&lt;strong>Roofline invertido en modelos pequeños&lt;/strong> (hermano de esta serie, próximamente) — el régimen de rendimiento del SLM que explica por qué &lt;code>A&lt;/code> pequeño mantiene el decode memory-bound y dónde está el techo real.&lt;/li>
&lt;li>&lt;strong>Self-speculative decoding con early-exit&lt;/strong> (hermano de esta serie, próximamente) — self-spec aplicado a MoE on-device: cómo acelerar el decode sin draft externo cuando el modelo ya es pequeño.&lt;/li>
&lt;li>&lt;strong>Cuantización agresiva sub-4-bit y ternaria&lt;/strong> (hermano de esta serie, próximamente) — Q4_0 y más allá en device: ternario y 2-bit para bajar aún más el footprint de expertos en SSD.&lt;/li>
&lt;/ul>
&lt;h2 id="referencias">Referencias&lt;/h2>
&lt;ul>
&lt;li>Equipo SmallThinker (SJTU IPADS + Zenergize AI). &lt;em>SmallThinker: A Family of Efficient Large Language Models Natively Trained for Local Deployment&lt;/em>. arXiv:2507.20984. &lt;a href="https://arxiv.org/abs/2507.20984">https://arxiv.org/abs/2507.20984&lt;/a>&lt;/li>
&lt;li>Repositorio oficial SmallThinker: &lt;a href="https://github.com/SJTU-IPADS/SmallThinker">https://github.com/SJTU-IPADS/SmallThinker&lt;/a>&lt;/li>
&lt;li>&lt;em>Self-Speculative Decoding for On-device MoE Acceleration&lt;/em>. ACM The Web Conference (WWW) 2026. doi:10.1145/3774904.3792218. &lt;a href="https://doi.org/10.1145/3774904.3792218">https://doi.org/10.1145/3774904.3792218&lt;/a>&lt;/li>
&lt;li>Liu, Z. et al. &lt;em>Deja Vu: Contextual Sparsity for Efficient LLMs at Inference Time&lt;/em>. ICML 2023. &lt;a href="https://arxiv.org/abs/2310.17157">https://arxiv.org/abs/2310.17157&lt;/a>&lt;/li>
&lt;li>Song, Y. et al. &lt;em>PowerInfer: Fast Large Language Model Serving with a Consumer-grade GPU&lt;/em> (sparse activation + hot/cold experts). SJTU IPADS, 2023. &lt;a href="https://arxiv.org/abs/2312.12456">https://arxiv.org/abs/2312.12456&lt;/a>&lt;/li>
&lt;/ul></description></item></channel></rss>