Het draaien van large language models op een telefoon is niet langer een wetenschappelijk experiment. Het is een realiteit voor commerciële producten. Maar zodra je de stap zet van een demo naar een echt product, keert de latentie onvermijdelijk terug. Je hebt het model verkleind, de gewichten gekwantiseerd, en toch sleept de prefill-fase voort. De tokens haperen. De UI bevriest. De schuld wordt zelden bij de juiste oorzaak gelegd.
Op Android-apparaten is rekenkracht bijna nooit de bottleneck tijdens LLM-prefill. Het is de geheugenbandbreedte. Moderne flagship SoC's worden geleverd met krachtige GPU- en NPU-kernen die rekenwerk veel sneller kunnen verwerken dan het geheugensysteem kan aanleveren. Wanneer je een naïeve attention-implementatie profileert, zijn de execution units niet verzadigd. Ze wachten. Ze wachten op DRAM.
Waarom je gekwantiseerde model nog steeds traag aanvoelt
Kwantisatie is de standaard eerste stap geworden voor on-device inferentie. Het verkleinen van gewichten van FP16 naar INT8 halveert de modelgrootte en vermindert de opslagruimte. Dat helpt. Maar het lost de latentie van de attention-lagen niet op. De reden is simpel: kwantisatie vermindert de hoeveelheid data die je opslaat, maar het vermindert niet het aantal geheugentransacties dat het attention-mechanisme uitvoert.
Een standaard multi-head attention-laag, geïmplementeerd volgens de tekstboekmethode, maakt voor elke laag drie volledige rondreizen naar het DRAM. Query-, Key- en Value-matrices worden uit het hoofdgeheugen gelezen, scores worden berekend en tussenresultaten worden teruggeschreven. De rekenkunde is triviaal. De databeweging is meedogenloos. Op Android, waar het stroom- en thermische budget beperkt is, belast dit patroon de geheugenbus zwaar. De processor betaalt effectief drie keer tol om dezelfde brug over te steken.
Als je INT8-modellen hebt uitgebracht en je afvraagt waarom de prefill-stap nog steeds kwadratisch schaalt met de promptlengte, dan is dit het antwoord. De gewichten zijn kleiner, maar het activatieverkeer blijft enorm.
De bottleneck is geheugen, niet rekenkracht
Om de oplossing te begrijpen, moet je naar de roofline kijken. Mobiele GPU's en NPU's op chips zoals de Snapdragon 8 Gen 3 en Dimensity 9300 hebben een theoretische reken-throughput die veel hoger ligt dan wat hun LPDDR5X-interfaces kunnen ondersteunen. In een naïeve attention-kernel berekent elke head de softmax over het product van Q en K, en vermenigvuldigt dit vervolgens met V. Elke tussenliggende scorematrix wordt gematerialiseerd in het globale geheugen. Dat betekent dat je piek-DRAM-leesacties schalen met het kwadraat van de sequentielengte, oftewel O(n²). Voor een prompt van 1024 tokens is het geheugenverkeer al groot genoeg om de executietijd te domineren.
De kernen worden onderbenut omdat ze de latentie niet kunnen verbergen. Moderne processors vertrouwen op caches om pipelines gevuld te houden. Wanneer een algoritme constant een cache-miss heeft en gegevens uit het DRAM moet ophalen, blijven de execution units onbenut. Geen enkele hoeveelheid kwantisatie lost deze structurele mismatch tussen rekencapaciteit en geheugenvoorziening op.
Hoe tiling bandbreedte terugwint
De oplossing is een tiling-strategie die tussenliggende scores in on-chip SRAM houdt in plaats van ze naar het DRAM te sturen. Dit is hetzelfde inzicht dat Flash Attention aandrijft, aangepast voor de compute-stack van Android. In plaats van een volledige n × n scorematrix in het geheugen te materialiseren, breek je de berekening op in kleine tiles die in de L1-cache passen. Je berekent lokale softmax-statistieken, cumuleert lopende max-waarden en normalisatiesommen, en schrijft alleen de uiteindelijke gewogen outputs terug naar het geheugen.
Dit verandert de bandbreedtecomplexiteit. De piek-DRAM-leesacties dalen van O(n²) naar O(n), omdat je geen volledige scorematrices meer door het hoofdgeheugen hoeft te sluizen. Het zware werk gebeurt binnen de SRAM, vlak naast de execution units.
Neem als concreet voorbeeld een tile-grootte van 64 en een head-dimensie van 128. De score-tile neemt 16 KB in beslag. Deze footprint past gemakkelijk in de L1-cache van huidige flagship SoC's zoals de Snapdragon 8 Gen 3 en Dimensity 9300. De rekenkunde blijft lokaal. De geheugenbus krijgt weer ademruimte.
Dit implementeren op Android
De algoritmische schets is eenvoudig, hoewel het cruciaal is om de details goed te krijgen.
Splits je Query, Key
