- Motor de inferência baseado em C/Metal que executa um modelo Mixture-of-Experts de 397B parâmetros em um MacBook Pro (48 GB de RAM) a mais de 4,4 tokens por segundo
- Faz streaming do modelo completo de 209 GB a partir do SSD e foi implementado apenas com C e shaders Metal, sem Python nem frameworks
- Maximiza a eficiência paralela entre GPU, SSD e CPU com técnicas como SSD Expert Streaming, kernels otimizados com FMA e Deferred GPU Compute
- A configuração com quantização em 4 bits equilibra qualidade e velocidade, produzindo saída em nível de produção, incluindo recurso de chamada de ferramentas
- Um caso de leveza e otimização que torna possível a inferência em tempo real de modelos MoE gigantescos mesmo em um notebook
Resultados de desempenho
- Na configuração com especialistas em 4 bits (kernel FMA), alcança 4,36 tok/s, com boa qualidade, usando 209 GB de disco no total
- A configuração básica em 4 bits atinge 3,90 tok/s, etapa anterior à otimização com FMA
- A configuração com especialistas em 2 bits (trust the OS) chega a 5,74 tok/s e é mais rápida, mas erros na saída JSON impedem a chamada de ferramentas
- O pico em token único com 2 bits chega a 7,05 tok/s, mas não é adequado para uso real
- A quantização em 4 bits é a configuração mais apropriada para operação prática
Ambiente de hardware
- MacBook Pro (Apple M3 Max), CPU de 16 núcleos (12P+4E), GPU de 40 núcleos, ANE de 16 núcleos
- 48 GB de memória unificada, com largura de banda de cerca de 400 GB/s
- O SSD é um Apple Fabric de 1 TB, com 17,5 GB/s de leitura sequencial
- Ambiente macOS 26.2 (Darwin 25.2.0)
Arquitetura do modelo
- Total de 60 camadas Transformer: 45 de GatedDeltaNet (atenção linear) + 15 de atenção completa
- Cada camada tem 512 especialistas, e K=4 são ativados por token (incluindo 1 especialista compartilhado)
- Dimensão oculta 4096
-
Tecnologias principais
-
SSD Expert Streaming
- Carrega os pesos dos especialistas (209 GB em 4 bits) sob demanda a partir de um SSD NVMe com pread() paralelo
- Em cada camada, carrega apenas os 4 especialistas ativados (cerca de 6,75 MB cada)
- O cache de páginas do sistema operacional gerencia o cache automaticamente, sem necessidade de cache separado
- Estrutura inspirada no artigo da Apple “LLM in a Flash”
-
Kernel de dequantização otimizado com FMA
- Reorganiza a operação
(nibble * scale + bias) * x para a forma fma(nibble, scale*x, bias*x)
scale*x e bias*x são pré-calculados para que as unidades FMA da GPU executem tudo em uma única instrução
- 12% de ganho de velocidade em relação a uma implementação simples
-
Shaders de computação Metal
- Implementa com kernels Metal escritos manualmente operações como multiplicação matriz-vetor com dequantização de 4 bits/2 bits, ativação SwiGLU, normalização RMS, atenção na GPU (Q@Kᵀ, softmax, scores@V), RoPE e combinação de MoE + residual + gate
-
Deferred GPU Expert Compute
- Envia de forma assíncrona o comando CMD3 (forward pass dos especialistas) para que a CPU prepare a próxima camada enquanto a GPU executa
- As operações de combinação + normalização + residual também são executadas na GPU e passadas diretamente para a próxima camada
-
Uso do Accelerate BLAS
- Usa
cblas_sscal, cblas_sgemv, cblas_sger nos cálculos recorrentes do GatedDeltaNet
- Desempenho 64% mais rápido que código escalar
-
Trust the OS
- Remove o cache customizado, deixando o cache de páginas do sistema operacional (baseado em LRU, cerca de 35 GB) cuidar do cache dos dados dos especialistas
- Foi mais rápido do que Metal LRU próprio, cache com
malloc e cache comprimido com LZ4
- Alcança uma taxa natural de acerto de cache de 71%
-
Limitações da memória unificada
- No Apple Silicon, DMA do SSD e computação da GPU compartilham o mesmo controlador de memória
- Em execução paralela, a saturação da largura de banda da GPU causa forte aumento de latência
- Um pipeline sequencial GPU → SSD → GPU é a forma ideal para esse hardware
1 comentários
Comentários do Hacker News
Há outra forma de rodar o Qwen 3.5 397B até em dispositivos de consumo
Existe uma versão com quantização de cerca de 2.5 BPW (quant), então mesmo um dispositivo com 128 GB de memória dá conta
Eu rodei bem em um M1 Ultra a cerca de 20 tok/s, mantendo contexto de 256k
Os resultados do lm-evaluation-harness ficaram em algo como mmlu 87.86%, gpqa diamond 82.32%, gsm8k 86.43% e ifeval 75.90%
Descrevi a experiência em mais detalhes na discussão 1 do Hugging Face e na discussão 2
É um excelente modelo para inferência offline
Reduzir o número de especialistas por token de 10 para 4 degrada ainda mais a qualidade
Para prompts curtos pode até servir, mas em sessões longas não presta
Também há o problema de gerar
"name"como\name\, o que torna a chamada de ferramentas instávelTambém queria saber se ele continua funcionando bem com contexto longo
E foi legal ver seu nick de novo depois de tanto tempo — você é a pessoa que criou o Neovim, um sucesso incrível
Eu também uso todo dia
Talvez dê para estimar com o CoconutBattery
Pelos detalhes, parece que eles usaram quantização de 2 bits e reduziram os especialistas de 10 para 4 para conseguir algo em torno de 5 tok/s
É uma prova de conceito interessante, mas está longe da qualidade do modelo 397B original
Esse tipo de otimização extrema provoca perda de inteligência no modelo
Também há o problema de ele gerar
"name"como\name\, então não é adequado para uso realReconheço o mérito de mostrar que esse tipo de experimento é tecnicamente possível, mas ainda não está em um nível realmente utilizável
Ultimamente tenho me sentido cansado de tantos artigos escritos por IA
Mas ouvi dizer que, na prática, até LLMs comerciais têm baixa precisão em chamadas de ferramentas
Então imagino que seja algo difícil de implementar, ou estruturalmente impossível em alguma medida
Também houve discussão relacionada no tópico do r/LocalLLaMA
Segundo a página no GitHub, o acesso simples via mmap vira gargalo por causa do overhead por página
Fiquei pensando se configurar huge pages no macOS ou usar
posix_fadvisepara prefetch poderia melhorar issoA perda de qualidade com quantização de 2 bits é realmente um problema sério
Na prática, já vi modelos 30B em 4 bits bem ajustados se saírem melhor do que modelos 70B+ em 2 bits
Ao reduzir o número de especialistas, ele praticamente vira outro modelo
Ainda assim, é interessante ver gente testando os limites do hardware de consumo
É cansativo que o título “rodar em um laptop” quase sempre queira dizer um MacBook de US$ 3000
A tecnologia de compressão é impressionante, mas não é uma opção realista para o usuário comum
Mas ao ler o título não espero que vá rodar em qualquer laptop
Prefiro curtir esse tipo de experimento em vez de cair num cinismo excessivo
Muita gente já tem um MacBook potente assim por causa de edição de vídeo e coisas do tipo
A vantagem é poder experimentar no laptop que a pessoa já tem, sem equipamento extra
Ficou em cerca de 20 tok/s, e me pareceu algo bem acessível até para uso pessoal
Para trabalho também valeu o investimento
“No Python. No frameworks. Just C, Objective-C, and hand-tuned Metal shaders.”
Quando li essa frase, imediatamente entendi de onde vinham os tokens
Dizem “Hand-written Metal kernels”, mas será que não foram escritos pelo próprio GPT? 😉
Resultado realmente impressionante
Fiquei curioso se algo parecido seria possível no Linux usando acesso baseado em memória do sistema em vez de SSD
Ou até a ideia de armazenar os pesos em forma de ROM parece interessante
Só que este projeto usa Metal, então fica restrito ao macOS
Mas modelos MoE ainda continuam muito limitados por largura de banda
Só que, na etapa de decode, o overhead de transferência pela CPU costuma ser maior do que o ganho em relação à GPU
É mais eficiente usar a GPU apenas para acelerar a parte compartilhada
Mas, quando o modelo transborda para RAM do sistema ou disco, o desempenho despenca
Havia a explicação de que o SSD é o gargalo, mas o autor diz que conseguiu 15GB/s
Só que, pelo que eu sabia, a largura de banda máxima era algo perto de 8GB/s. O que estou deixando passar?
Quando sai o resultado do roteador, os especialistas são carregados do SSD, e nesse instante o SSD satura
O IO médio fica em torno de 2970MB/s, bem abaixo do limite do SSD
Talvez desse para melhorar paralelizando a leitura de alguns tensores com leitura assíncrona
Quando experimentei com io_uring no Linux, cerca de 20% das leituras terminavam em paralelo com a computação