- Para reduzir as grandes dependências e a carga de implantação de uma stack de inferência centrada em Python, um app Llama2 em Rust+Wasm foi empacotado em um binário de 2 MB para rodar em vários dispositivos
- A implementação é uma adaptação do llama.cpp para Wasm e usa arquivos de modelo GGUF, o plugin GGML do WasmEdge e a API WASI NN para aproveitar a aceleração local de hardware
- O exemplo usa o modelo GGUF com quantização de 5 bits do Llama-2-7B-Chat e mostra desempenho de cerca de 25 tokens/s em um MacBook M2 básico e cerca de 50 tokens/s em uma Nvidia A10G
- O runtime WasmEdge pode ser usado com ferramentas de contêiner, facilitando implantar o mesmo binário Wasm em dispositivos de edge, on-premises e nuvem
- As ferramentas GGML já estão disponíveis, mas ainda estão em estágio inicial; faltam mais plugins para hardware e SO, suporte a configurações do llama.cpp e suporte à API WASI NN em várias linguagens compatíveis com Wasm
Executando inferência com Llama2 em Rust+Wasm
- A stack Rust+Wasm pode ser usada como alternativa ao Python para inferência de IA
- Apps Rust+Wasm podem ter 1/100 do tamanho e ser 100 vezes mais rápidos que Python, além de usar aceleração de hardware em vários ambientes com o mesmo binário, sem mudanças no código
- A Second State criou um simples programa em Rust que executa inferência do modelo Llama2 em velocidade nativa
- O app binário compilado para Wasm tem 2 MB
- Esse app Wasm é portátil entre dispositivos com aceleradores de hardware heterogêneos
- O runtime WasmEdge fornece um ambiente de execução seguro em ambientes de nuvem
- O WasmEdge pode ser usado com ferramentas de contêiner para orquestrar e executar apps portáteis em vários dispositivos
Baseado em llama.cpp e GGUF
Procedimento de execução
- Instale o WasmEdge e o plugin GGML em um dispositivo Linux ou Mac
curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/… | bash -s -- --plugins wasmedge_rustls wasi_nn-ggml
- Baixe o app Wasm pré-compilado
curl -LO https://github.com/LlamaEdge/LlamaEdge/…
- O exemplo usa um arquivo GGUF do modelo Llama2 7B ajustado para chat, quantizado com pesos de 5 bits
curl -LO https://huggingface.co/second-state/Llama-2-7B-Chat-GGUF/…
- Execute o app de inferência Wasm com o WasmEdge e passe o modelo GGUF para inserir perguntas de forma interativa
wasmedge --dir .:. --nn-preload default:GGML:AUTO:Llama-2-7b-chat-hf-Q5_K_M.gguf llama-chat.wasm
Configuração do modelo e exemplos de desempenho
llama-chat.wasm configura a forma de interação com o modelo por opções de linha de comando
--ctx-size: tamanho do contexto do prompt, padrão 512
--n-predict: número de tokens a prever, padrão 1024
--n-gpu-layers: número de camadas a executar na GPU, padrão 100
--batch-size: tamanho do lote para processamento do prompt, padrão 512
--temp: temperatura de amostragem, padrão 0.8
--repeat-penalty: penalidade para tokens repetidos, padrão 1.1
--prompt-template: suporta llama-2-chat, codellama-instruct, mistral-instruct-v0.1, chatml, deepseek-chat, deepseek-coder etc.
--log-stat: exibe estatísticas
- Um exemplo de execução com comprimento de contexto 2048, resposta máxima de 512 tokens e saída de estatísticas é o seguinte
wasmedge --dir .:. --nn-preload default:GGML:AUTO:Llama-2-7b-chat-hf-Q5_K_M.gguf llama-chat.wasm -c 2048 -n 512 --log-stat
- Em um MacBook M2 básico, a resposta do LLM é transmitida por streaming por padrão e é gerada a cerca de 25 tokens/s
- Um exemplo de log registra 25.64 tokens/s em 82 execuções com base em
eval time
kv self size aparece como 1024.00MB, e compute buffer total size como 630.14MB
- Em uma máquina com Nvidia A10G, ele roda a cerca de 50 tokens/s
- Usa aceleração de GPU com CUDA e faz offload de 35/35 camadas para a GPU
- O uso de VRAM aparece como 4474.93MB para o modelo, 1648.02MB para o contexto, totalizando 6122.95MB
- Um exemplo de log registra 50.55 tokens/s em 83 execuções com base em
eval time
Servidor de API compatível com OpenAI e apps de LLM
- Também é fornecido um servidor de API compatível com OpenAI feito com Rust e WasmEdge
- Com esse servidor, é possível criar agentes e apps de LLM usando ferramentas de desenvolvimento compatíveis com OpenAI, como flows.network
- A explicação relacionada está em Build a super lightweight AI agent
Limitações da stack de inferência em Python
- LLMs como o Llama2 normalmente são treinados com frameworks baseados em Python, como PyTorch, Tensorflow e JAX
- Mas, para aplicações de inferência, que representam cerca de 95% da computação de IA, considera-se que o Python está em desvantagem
- Pacotes Python podem se tornar difíceis de configurar e usar por causa de dependências complexas
- Imagens Docker com Python ou PyTorch costumam chegar a vários GB ou até dezenas de GB, o que pesa bastante para inferência de IA em servidores ou dispositivos de edge
- Python é muito mais lento que linguagens compiladas como C, C++ e Rust, podendo ser até 35.000 vezes mais lento
- A maior parte das cargas reais acaba sendo delegada a bibliotecas compartilhadas nativas por baixo dos wrappers em Python
- Essa estrutura é boa para demos, mas é considerada difícil de modificar internamente para atender exigências específicas de cada negócio
- A forte dependência de bibliotecas nativas e a gestão complexa de dependências reduzem a portabilidade de programas de IA em Python que tentam aproveitar recursos de hardware específicos de cada dispositivo
- Há também casos em que pacotes Python comuns no toolchain de LLM entram em conflito por requisitos de versão do
pydantic
llama-cpp-python exige pydantic 2.0.1 e não funciona com <=2.0
fastapi e chromadb exigem pydantic 1.9.2 e não funcionam com >=2.0
Vantagens de Rust+Wasm
- A stack Rust+Wasm pode servir como uma infraestrutura unificada de computação em nuvem abrangendo dispositivos, edge cloud, servidores on-premises e nuvem pública
- Em aplicações de inferência de IA, ela pode ser uma alternativa forte à stack Python
-
Extremamente leve
- A aplicação de inferência tem 2 MB incluindo todas as dependências
- Isso representa menos de 1% do tamanho de um contêiner típico com PyTorch
-
Execução rápida
- Pode entregar velocidade nativa de C/Rust em pré-processamento, cálculo de tensores e pós-processamento
-
Portabilidade
- O mesmo app em bytecode Wasm roda nas principais plataformas de computação
- Também suporta aceleração de hardware heterogênea
-
Facilidade de configuração, desenvolvimento e implantação
- Reduz dependências complexas
- É possível compilar um único arquivo Wasm no notebook com ferramentas padrão e implantá-lo em vários ambientes
-
Segurança e prontidão para a nuvem
- O runtime Wasm foi projetado para isolar código de usuário não confiável
- Pode ser gerenciado com ferramentas de contêiner e implantado em plataformas cloud-native
Estrutura do programa de inferência em Rust
- O programa de inferência de demonstração foi escrito em Rust e compilado para Wasm
- O código-fonte principal em Rust tem cerca de 40 linhas
- O programa Rust é responsável por:
- gerenciar a entrada do usuário
- rastrear o histórico da conversa
- converter o texto para o template de chat do Llama2
- executar a inferência usando a API WASI NN
- Para fazer o build diretamente, instale o compilador Rust e o alvo de compilação
wasm32-wasi
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add wasm32-wasi
- Em seguida, baixe o projeto-fonte e gere o arquivo Wasm com
cargo
git clone https://github.com/second-state/llama-utils
cd llama-utils/chat/
cargo build --target wasm32-wasi --release
cp target/wasm32-wasi/release/llama-chat.wasm .
Implantação em nuvem e edge
- Com o arquivo de bytecode Wasm em mãos, ele pode ser implantado em qualquer dispositivo que suporte o runtime WasmEdge
- Atualmente, o plugin GGML oferece suporte a Linux genérico e Ubuntu Linux
- CPUs x86 e ARM
- GPUs Nvidia
- Apple M1/M2/M3
- O plugin GGML do WasmEdge aproveita automaticamente a aceleração de hardware do dispositivo com base no llama.cpp
- Se houver uma GPU Nvidia, o instalador instala automaticamente o plugin GGML otimizado para CUDA
- O plugin GGML para Mac OS usa a API Metal para executar a carga de inferência no neural processing engine integrado dos M1/M2/M3
- O build para CPU em Linux usa OpenBLAS para detectar e aproveitar automaticamente recursos modernos de computação da CPU, como AVX e SIMD
- Essa abordagem busca garantir portabilidade entre hardwares e plataformas heterogêneos de IA sem sacrificar desempenho
Próximos trabalhos
- As ferramentas WasmEdge GGML já estão disponíveis e vêm sendo usadas por clientes cloud-native, mas ainda estão em estágio inicial
- As áreas em que se pode contribuir são as seguintes
- adicionar plugins GGML para mais hardwares e plataformas de SO
- há interesse em TPU, ARM NPU e chips de IA especializados em Linux e Windows
- ampliar o suporte a mais configurações do llama.cpp
- atualmente, apenas algumas opções de configuração podem ser passadas do Wasm para o plugin GGML
- o objetivo é suportar todas as opções oferecidas pelo GGML
- dar suporte à API WASI NN em outras linguagens compatíveis com Wasm
- há interesse em Go, Zig, Kotlin, JavaScript, C e C++
Suporte a modelos além de LLM
- O WasmEdge e o WASI NN também podem ser usados para criar apps de inferência baseados em modelos populares de IA além de LLM, como uma alternativa ao Python leve, rápida, portátil e segura
- O mediapipe-rs fornece uma API Rust+Wasm para a família de modelos Tensorflow mediapipe do Google
- WasmEdge YOLO é um projeto de API Rust+Wasm para trabalhar com o modelo PyTorch YOLOv8
- O WasmEdge ADAS demo é um exemplo que faz segmentação de estrada para carros autônomos com modelos Intel OpenVINO
- WasmEdge Document AI deverá fornecer uma API Rust+Wasm para uma família de modelos de OCR e processamento de documentos
- Discussões e contribuições relacionadas ao WasmEdge podem ser feitas no WasmEdge Discord
1 comentários
Comentários no Hacker News
Rust e WASM são legais, mas, olhando o código, são só 150 linhas de um script básico de linha de comando em Rust
O trabalho pesado é feito por uma única linha que entrega o modelo ao backend WASI-NN, que aqui é fornecido pelo runtime WasmEdge
Só que o WasmEdge é C++, não Rust, e neste caso quase não há benefício trazido pelo Rust; o backend também pode ser chamado de outras linguagens, como Python
Empacotar Python e PyTorch num formato que o usuário final possa executar com duplo clique ainda hoje é quase uma bagunça total, e embora o código real de alto desempenho seja C++ nos dois casos, se em vez de distribuir mais de 2 GB e dezenas de milhares de arquivos só para enviar alguns comandos a esse C++, for possível resolver tudo com um único executável de 2 MB, isso tem valor
Trabalho impressionante. Mas quem quiser usar ainda vai precisar baixar arquivos de pesos grandes
Na prática, isso equivale a transformar o llama.cpp, que já é totalmente portátil e sem dependências, em 2 MB
Para desenvolvedores de apps, pode ser a forma mais fácil de empacotar o mecanismo de inferência dentro de um arquivo distribuível. Os pesos já são portáveis e podem ser baixados quando necessário, então a parte que realmente faz sentido fixar é o mecanismo de inferência
Como foi dito, os pesos dominam completamente esse tamanho
maingerado pelo llama.cpp tem 1,2 MBO tamanho de 2 MB em si não é particularmente impressionante; o ponto principal é ser mais portátil por mirar wasm, não por ser especialmente mais compacto
No fim, isso não é só um wrapper em volta do llama.cpp? Sinceramente, já estou bem cansado de projetos que embrulham um
x.cppNos últimos 6 meses venho desenvolvendo um framework de machine learning em Rust + WebGPU, e rapidamente percebi o quão impressionante é o trabalho do GG
Ainda está em estágio inicial, mas dá para ver aqui:
https://www.ratchet.sh/
https://github.com/FL33TW00D/whisper-turbo
O wasm-nn do qual isso depende, ou seja, https://github.com/WebAssembly/wasi-nn, é uma proposta baseada em enviar chunks arbitrários para implementações de fornecedores. A API na prática também é algo como configurar entradas, computar e configurar saídas
Então isso não é portátil de forma alguma
Isso funciona porque depende da abstração já implementada no llama.cpp. Dá para ver aqui o WasmEdge pegando exatamente esse código que mapeia modelos gguf para vários alvos de hardware: https://github.com/WasmEdge/WasmEdge/tree/master/plugins/was...
Portanto, descrições do tipo “desenvolvedores podem usar bindings para escrever apps de machine learning em linguagens de alto nível, compilar para WebAssembly e depois executar em runtimes com suporte a wasi-nn como o WasmEdge” estão completamente erradas. Na prática, isso não funciona assim
Isso não é portátil, não é sandbox e nem uma camada de abstração de hardware
Mesmo com um binário wasm, ele só vai rodar se a versão do runtime em uso por acaso implementar o backend ggml específico de que você precisa, e não há exigência alguma para isso, então na maioria das vezes provavelmente não vai
E, mesmo que funcione, no fim ele está apenas chamando o código ggml do llama.cpp, então a segurança depende do quanto essa biblioteca é segura
O texto enfatiza demais “portabilidade” e “Rust”, mas na prática as vantagens de nenhum dos dois ficam muito evidentes
Supondo que exista um runtime WASI para um novo hardware, será que dá para rodar o modelo? Há suporte a GPU? A resposta vira “veja se o llama.cpp compila com suporte a GPU nessa plataforma, se o runtime que você usa tem o plugin ggml e se dentro dele foi incluída a versão correta do ggml. Caso contrário, não”
Nesse caso, para que usar WASI afinal?
É verdade que suporte multiplataforma a GPU é difícil, mas isso aqui parece bastante absurdo
Imagine se o WebGPU fosse algo como “jogue um chunk binário na GPU e talvez ela desenhe alguma coisa se aquele chunk combinar com o hardware atual”; é basicamente essa a estrutura aqui
Dá para rodar isso offline no iPhone? Se sim, daria para usar como uma espécie de busca básica na internet independentemente de sinal, o que seria útil em acampamentos
Mas para carregar o modelo em si ainda são necessários alguns GB de memória
[0] https://github.com/mlc-ai/mlc-llm
No começo eu o fiz com ggml e depois encontrei o mlc-llm e fiz a portabilidade com base nele
[0]: https://apps.apple.com/us/app/private-llm/id6448106860
Ao longo do tempo devem surgir formas ainda mais eficientes e rápidas de executar arquiteturas Transformer no edge, mas a exigência de VRAM não pode ser resolvida com Rust, então isso parece estar chegando perto do limite.
Ao carregar modelos suficientemente grandes, esse é justamente o principal gargalo.
Dá para dizer “modelos pequenos estão melhorando, veja Mistral e Llama 2”, mas até os modelos pequenos estão se aproximando do limite de capacidade. Há um limite para a quantidade de informação que cabe em 7 bilhões de parâmetros.
Não parece que essa abordagem de IA vá levar à AGI. Parece ineficiente demais.
A explicação de que “o plugin GGML para Mac OS usa a API Metal para executar tarefas de inferência no mecanismo neural integrado do M1/M2/M3” não parece correta.
Dizer que o GGML usa a API Metal significa que ele roda na GPU do M1/2/3, não que roda no Neural Engine.
Claro, isso por si só já é bom, mas, em termos estritos, é isso.
Não gosto desse marketing caça-cliques que faz parecer que, só porque o wrapper em torno da biblioteca principal usa outra linguagem, o projeto reduziu o tamanho para 1/100 de outras soluções ou aumentou a velocidade em 100~35000 vezes.
Isso também ignora completamente as ferramentas e a especialização da comunidade acumuladas em torno de outras soluções.
Antes de mais nada, este projeto é baseado em llama.cpp[1], e o trabalho pesado de carregar e executar arquivos de modelo de vários GB em GPU/CPU é feito pelo llama.cpp.
A velocidade de inferência não é limitada pela escolha do wrapper, e há wrappers em Go, Python, Node e Rust, além da opção de usar o llama.cpp diretamente.
O tamanho do binário também não é tão importante assim. Arquivos de modelos quantizados comuns ficam na faixa de 5GB~40GB, e é preciso uma máquina com GPU forte ou 16~64GB de RAM.
[1] https://github.com/ggerganov/llama.cpp
Se uma grande parte do tamanho acaba sendo os pesos treinados do modelo, como é possível reduzir o tamanho em várias ordens de magnitude sem perder precisão?
Esses 2MB parecem se referir ao tamanho do programa, excluindo o modelo. Parece ser uma forma de executar o llama.cpp com wasm e com um servidor Rust que roda o llama.cpp.
Gosto do pequeno
llama.cpp/examples/server, então o embuto no FreeChat, mas é sempre bom ter mais opções de ferramentas.Fui conferir, e o executável arm64/x86 que eu embuto atualmente tem 4,2MB. O FreeChat tem 12,1MB, mas como o modelo padrão tem cerca de 3GB, não ligo muito para uma diferença de 2MB.
[0]: https://github.com/ggerganov/llama.cpp/tree/master/examples/...
Este texto trata de reduzir em 100 vezes o tamanho do app de inferência.
Não vejo motivo para usar isso em vez de usar o llama.cpp diretamente.
O texto também explica que ele aproveita aceleração por hardware em dispositivos com aceleradores heterogêneos.
Isso significa que um programa compilado em wasm pode usar de forma eficiente diversos recursos de hardware, como GPU ou chips dedicados de IA, em vários dispositivos.
Se isso fosse implementado diretamente em C++, talvez fossem necessárias otimizações ou versões separadas para cada tipo de hardware para alcançar desempenho semelhante.