Llama Crab: de ponte FFI a runtime local completo em Rust
Criei o Llama Crab para executar grandes modelos de linguagem localmente sem expor toda a complexidade de uma biblioteca C/C++ em evolução rápida, arquivos de vários gigabytes, recursos de CPU e GPU, estado autoregressivo, streaming e formatos de chat. Organizei o projeto como um runtime Rust sobre o llama.cpp: não reimplementei os kernels de inferência; compilei e vinculei o upstream, delimitei sua interface de baixo nível e ofereci fluxos de uso por Rust, HTTP e desktop [1][2]. O projeto também continua em GitHub com documentos, changelog e workflows para rastreio de mudanças.
Na release v0.1.8, publicada em 16 de junho de 2026, fixei uma versão do código nativo do llama.cpp, algo importante porque a ABI de libllama, os backends e os formatos de modelo mudam com frequência. No repositório mantive também os links de changelog, documentação e workflow de segurança/cobertura para facilitar rastreabilidade da versão publicada [1][2][3][4][6].
Objetivo e posição no ecossistema
O objetivo é preservar a abrangência e o desempenho do llama.cpp enquanto a aplicação trabalha, na maior parte do tempo, com tipos Rust, Result, RAII e builders, em vez de ponteiros crus. O upstream fornece inferência C/C++ portátil, quantização e execução híbrida CPU/GPU para muitas arquiteturas [7]. Embora o nome remeta à família LLaMA original [20], o runtime não é limitado a ela: a arquitetura efetivamente aceita depende do suporte presente na versão nativa incluída e dos metadados do GGUF. O Llama Crab acrescenta:
- carregamento local de GGUF e resolução opcional no Hugging Face Hub;
- completions, chat, infill, embeddings e reranking;
- cadeias de amostradores, logit bias, GBNF e conversão de JSON Schema;
- estado e cache de prefixos, além de um passo de decodificação especulativa;
- visão e áudio na camada Rust por meio de
mtmd; - servidor HTTP compatível com formas centrais da API OpenAI;
- plugin Tauri 2 e contratos/clientes TypeScript.
“Compatível” não significa substituição integral de toda a API OpenAI nem paridade automática com todo recurso do llama-server. Significa que requests, responses e streaming adotam formas conhecidas onde o projeto as implementa. O runtime continua síncrono no núcleo e orientado a um modelo/contexto por worker.
Workspace e dependências
Organizei o repositório como um monorepo duplo. No workspace Cargo, usei resolver = "2", edição 2021, MSRV 1.88 e crates/*; no workspace pnpm, incluí packages/* [3]. Publiquei quatro artefatos Rust e dois TypeScript:
llama-crab-sys: bindings gerados e lógica de compilação nativa;llama-crab: wrappers, tipos e APIs de alto nível;llama-crab-server: binário Axum/Clap para HTTP e SSE;tauri-plugin-llama-crab: comandos IPC, estado e workers do Tauri 2;@llama-crab/core: contratos OpenAI-like e mapeadores puros, sem cliente de transporte;@llama-crab/tauri: cliente que traduz esses contratos parainvokeeChanneldo Tauri.
Renderizando diagrama...
Essa divisão segue a convenção oficial do Cargo para crates *-sys: o pacote sys localiza ou compila a biblioteca nativa e expõe declarações; um pacote companheiro oferece abstrações de mais alto nível [12]. Também impede que Axum, Tauri e TypeScript contaminem a dependência mínima de quem só quer inferência dentro de um binário Rust.
Como a biblioteca nativa é construída
O build.rs de llama-crab-sys faz cinco trabalhos em ordem: detecta target e features, executa bindgen sobre wrapper.h, compila o submódulo com CMake, compila shims C++ opcionais com cc e emite diretivas de link para o Cargo [4]. O allowlist do bindgen restringe símbolos a prefixos llama_*, ggml_* e gguf_*; com mtmd, inclui também mtmd_* e bloqueia templates C++ que Rust não consegue representar diretamente.
O CMake desliga testes, exemplos, servidor, ferramentas, UI e curl do upstream. Isso reduz o artefato ao necessário para a biblioteca. O mtmd é uma exceção: como LLAMA_BUILD_TOOLS=OFF, suas fontes e vtables de modelos são compiladas diretamente pelo crate. Depois, o script descobre arquivos nativos e vincula llama, ggml, backends encontrados e runtimes de plataforma: frameworks Metal/Accelerate no macOS, stdc++ e possivelmente gomp no Linux, bibliotecas CUDA/ROCm/Vulkan/OpenCL quando habilitadas.
O Cargo recomenda que artefatos gerados fiquem em OUT_DIR, que mudanças sejam declaradas com rerun-if-changed e que a configuração do target venha de CARGO_CFG_*/TARGET, não do host [12]. O script observa headers, fontes C/C++/Metal e wrappers, grava bindings.rs em OUT_DIR e o inclui com include!. Portanto, o primeiro build requer CMake, compilador C/C++ e libclang compatíveis; não é um crate Rust puro.
A fronteira FFI entre Rust e C/C++
O mantenedor reserva ao llama-crab-sys a interface de baixo nível, marcada como insegura em sua referência publicada [9]: tipos opacos, ponteiros, callbacks e funções extern "C" não carregam, por si, provas de validade, aliasing ou thread safety. Em linha com a orientação oficial de Rust, a arquitetura declara ABI e layouts, converte strings para CString, valida ponteiros nulos, encapsula recursos estrangeiros e libera ownership por Drop [10][11]. Essa decisão motiva a separação em um segundo crate.
No llama-crab, handles retornados por C tornam-se NonNull; handles nulos viram LlamaError; modelos, contextos, batches e samplers liberam seus recursos em Drop; buffers C são expostos como slices somente após checagem de tamanho; strings atravessam a ABI como C strings. A API de alto nível centraliza backend, modelo e contexto em Llama.
No crate superior, encapsulei os blocos necessários para chamar a FFI e preservei escape hatches públicos para usos avançados: LlamaContext::raw_handle() devolve *mut llama_context; LlamaSampler::sample, o construtor de gramática e speculative_decode são unsafe. Minha decisão de segurança foi: o caminho comum é seguro na chamada; o controle FFI avançado transfere invariantes ao consumidor. O nome do crate descreve essa camada de encapsulamento, não uma prova formal de ausência de unsafe.
Uma chamada FFI tem custo, mas não há benchmark do projeto que justifique atribuir um número fixo. O orçamento pode ser descrito por:
Em inferência de LLM, T_modelo costuma dominar, mas fazer uma transição por token ainda justifica batching e streaming sem cópias desnecessárias. Essa equação é um modelo de medição, não um resultado do Llama Crab.
Ownership, lifetimes e ordem de destruição
O changelog de 0.1.7 registra uma correção importante: uma versão anterior guardava &'a LlamaModel no contexto e estendia sua vida para 'static com transmute. Quando Llama era movido ao retornar de load, o contexto podia manter um endereço de stack inválido, causando SIGSEGV [5]. A solução atual aloca LlamaModel em Box, cujo endereço do conteúdo permanece estável mesmo quando o Box é movido, e guarda no contexto um NonNull<LlamaModel>.
A ordem dos campos também é uma invariante, pois Rust destrói campos na ordem de declaração:
contextchamallama_freeenquanto o modelo ainda existe;model: Box<LlamaModel>chamallama_free_model;_backendchamallama_backend_free;- o marcador
PhantomData<*mut ()>mantém o orquestrador fora deSend/Syncautomáticos.
Essa arquitetura resolve a auto-referência móvel, mas o compilador não codifica a relação entre o NonNull do contexto e o Box; a correção depende do encapsulamento e da ordem de Drop. Por isso os testes de regressão atravessam limites de retorno e exercitam embeddings, reranking, infill e streaming. É um bom exemplo de como “RAII sobre C” exige mais do que implementar destrutores: exige provar endereço estável, exclusividade e ordem de teardown.
GGUF, quantização e carregamento
O llama.cpp exige GGUF para esses fluxos. GGUF é um contêiner binário extensível, alinhado e compatível com mmap: reúne magic/version, metadados tipados, descritores de tensores, pesos, vocabulário e, frequentemente, tokenizer.chat_template em um arquivo [8]. Ele não é uma arquitetura nem um algoritmo de quantização; é o formato que descreve tensores e como interpretá-los.
O limite inferior aproximado para pesos com P parâmetros e b bits por peso é:
Assim, 7 bilhões de parâmetros a 4 bits dão
cerca de 3,5 GB decimais ou 3,26 GiB. Isso não prevê o consumo total: blocos quantizados carregam escalas/metadados, alguns tensores ficam em precisão maior e ainda existem contexto, KV cache, buffers de computação e runtime do backend. O arquivo real e os logs de alocação são as fontes operacionais corretas.
Um uso mínimo e reproduzível escolhe explicitamente o backend:
[dependencies]
llama-crab = { version = "0.1.8", default-features = false, features = ["openmp"] }use llama_crab::high_level::completion::{CompletionOptions, SamplingOptions};
use llama_crab::{Llama, LlamaParams};
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut llama = Llama::load(
LlamaParams::new("models/model.gguf")
.with_n_ctx(4096)
.with_n_batch(512)
.with_n_threads(8)
.with_n_gpu_layers(0),
)?;
let options = CompletionOptions::sampled(128).with_sampling(
SamplingOptions::default()
.with_temperature(0.7)
.with_seed(42),
);
let completion = llama.create_completion_with_options(
"Explique ownership em Rust em um parágrafo:",
options,
)?;
println!("{} ({:?})", completion.text, completion.stop_reason);
Ok(())
}Llama::load inicializa o backend, resolve uma origem para um caminho local, carrega o GGUF, obtém o vocabulário e cria o contexto. use_mmap, offload de camadas, n_ctx, batches, threads, flash attention e offload KQV são decisões independentes; n_gpu_layers = 99 é apenas uma forma comum de pedir offload amplo, não garantia de que 99 camadas existam ou caibam na VRAM.
Hugging Face: resolução e cache
Com a feature hf-hub, LlamaParams::new("organizacao/repositorio") é interpretado como repo quando o caminho não existe localmente. Se houver um único .gguf, ele é escolhido; com vários, with_hf_filename é obrigatório. O downloader usa a API síncrona de hf-hub e devolve o caminho do snapshot local [5][6].
use llama_crab::{Llama, LlamaParams};
# fn main() -> Result<(), Box<dyn std::error::Error>> {
let llama = Llama::load(
LlamaParams::new("org/modelo-GGUF")
.with_hf_filename("modelo-Q4_K_M.gguf")
.with_n_ctx(2048),
)?;
println!("{} parâmetros", llama.model().n_params());
# Ok(())
# }O cache oficial usa por padrão ~/.cache/huggingface/hub, ou $HF_HOME/hub, com referências, blobs endereçados por conteúdo e snapshots versionados; isso permite compartilhar arquivos entre ferramentas sem baixar o mesmo blob novamente [13]. HF_TOKEN atende repositórios privados/gated e HF_ENDPOINT pode apontar para um endpoint compatível. Em produção, selecione uma versão explícita do modelo, verifique a integridade por um mecanismo confiável, controle espaço em disco e não registre tokens.
Há uma limitação específica da release v0.1.8: with_hf_revision, with_hf_token, with_hf_cache_dir e with_hf_endpoint preenchem campos de LlamaParams, mas Llama::load cria o downloader padrão sem aplicar esses quatro campos. Os equivalentes por ambiente funcionam no downloader real; with_hf_filename está conectado. Tratar os demais builders como plenamente ativos nessa versão seria incorreto.
O ciclo de inferência
Na completion de alto nível, a sequência 0 do KV cache é limpa para tornar chamadas independentes. O prompt é tokenizado com tokens especiais, colocado em LlamaBatch, decodificado em um prefill, e só a última posição solicita logits. A cada iteração, o sampler escolhe um token, EOS/EOT e stop strings são verificados, bytes são detokenizados, o chunk é emitido e o token volta em um batch unitário para atualizar contexto e KV.
Renderizando diagrama...
Streaming é síncrono no crate: o callback recebe CompletionChunk e devolve Continue ou Stop. HTTP adapta isso a SSE; Tauri adapta a canais IPC. Completion informa texto, número de tokens, logprobs opcionais e StopReason (Length, Eos, Stop ou ToolCalls). A taxa observada deve sempre declarar fase e hardware:
Prefill processa muitos tokens em paralelo e não deve ser misturado com geração token a token. Ainda não publiquei uma tabela de benchmarks controlados; por isso, não apresento aqui um número de desempenho sem medição reproduzível.
KV cache, estado e memória
O KV cache evita recalcular keys e values de todos os tokens anteriores. Para um Transformer com L camadas, T posições, H_kv heads KV, dimensão por head D e B bytes por elemento, uma aproximação é:
O fator 2 representa K e V. Em um exemplo puramente dimensional com L=32, T=4096, H_kv=8, D=128 e FP16 (B=2), o resultado é 536.870.912 bytes, ou 512 MiB. MHA teria mais heads KV; GQA/MQA reduz esse termo. Alinhamento, múltiplas sequências e buffers elevam o uso real.
LlamaContext expõe remoção, cópia, retenção e deslocamento de sequências (seq_rm, seq_cp, seq_keep, seq_add, seq_div), serialização do estado para bytes/arquivo e restauração. RamCache e DiskCache armazenam estados opacos por sequência exata de tokens e procuram o prefixo mais longo; o segundo depende de sled. Entretanto, a completion de alto nível não integra automaticamente esses caches: usá-los exige orquestração do estado pelo consumidor.
Sampling, chat e ferramentas
Sampling transforma logits em uma escolha. CompletionOptions::new é greedy (temperature = 0); sampled usa defaults probabilísticos. A cadeia disponível inclui penalidades de repetição/frequência/presença, top-k, top-p, min-p, locally typical, tail-free, temperatura, distribuição, Mirostat, XTC, top-n-sigma, DRY, adaptive-p e logit bias. Ordem importa: filtrar candidatos antes de temperatura e sorteio não é equivalente a aplicar os operadores em outra ordem.
Chat não é um estado mágico separado. Mensagens são renderizadas em texto e seguem pela mesma completion. Há 14 templates embutidos, incluindo ChatML, Mistral Instruct, Llama 3, Gemma, Phi, Command-R, DeepSeek, Granite e Plain, além de um interpretador de subconjunto Jinja. O GGUF pode carregar tokenizer.chat_template, mas a API simples create_chat_completion usa explicitamente Plain; para formato correto, escolha BuiltinTemplate ou renderize o template detectado. Formato errado degrada qualidade mesmo quando o modelo carrega sem erro.
Definições de ferramentas entram no prompt, e parsers reconhecem formatos ChatML, Mistral, Llama 3, Functionary e JSON. Isso estrutura a saída, mas não executa funções nem torna argumentos confiáveis: a aplicação deve validar nome, JSON, autorização e efeitos colaterais antes de chamar qualquer ferramenta.
GBNF e JSON Schema
GBNF restringe os próximos tokens à linguagem definida pela gramática. Isso aumenta validade sintática, mas não veracidade semântica. O upstream recomenda regras compactas, pois repetições opcionais mal construídas podem tornar sampling muito lento [19]. O Llama Crab fornece um conversor Rust para um subconjunto de JSON Schema 2020-12 e o sampler de gramática com a feature common.
use llama_crab::high_level::completion::json_schema_grammar;
use serde_json::json;
# fn main() -> Result<(), Box<dyn std::error::Error>> {
let schema = json!({
"type": "object",
"properties": {
"resposta": { "type": "string", "maxLength": 120 },
"confianca": { "type": "number" }
},
"required": ["resposta", "confianca"],
"additionalProperties": false
});
let gbnf = json_schema_grammar(&schema)?;
assert!(gbnf.contains("root"));
# Ok(())
# }Para gerar sob essa restrição, cria-se LlamaSampler::grammar, encadeia-se o sampler de gramática ao sampler base e chama-se create_completion_with_sampler. Esse construtor é unsafe nesta API, portanto o usuário avançado deve respeitar seu contrato de vida. O conversor cobre objetos, arrays, tipos primitivos, enum/const, formatos selecionados, uniões e $ref local, mas algumas implementações são aproximações: pattern não é validado como regex completa; limites numéricos são lidos sem produzir faixa exata; oneOf e anyOf viram alternativas; allOf é conservador; propriedades opcionais não têm semântica completa. Sempre valide o JSON final contra o schema original.
Embeddings e reranking
Embeddings exigem contexto criado com with_embeddings(true) e pooling compatível. embed(text, normalize) limpa a sequência, tokeniza, usa encode, lê o embedding da sequência e opcionalmente normaliza por L2. Vetores normalizados permitem similaridade cosseno por produto escalar.
use llama_crab::context::params::PoolingType;
use llama_crab::{Llama, LlamaParams};
# fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut model = Llama::load(
LlamaParams::new("models/embedder.gguf")
.with_embeddings(true)
.with_pooling_type(PoolingType::Mean),
)?;
let vector = model.embed("caranguejo em Rust", true)?;
println!("dimensão = {}", vector.len());
# Ok(())
# }Reranking é outro problema: um cross-encoder avalia cada par (consulta, documento) e retorna um score, geralmente melhor para ordenação final e mais caro que busca vetorial. Llama::rerank faz um forward pass separado por documento e lê o primeiro valor do embedding de sequência, esperando pooling Rank. A própria fonte reconhece que batching com as APIs seq_* seria mais eficiente; não se deve comparar score entre modelos diferentes nem interpretá-lo automaticamente como probabilidade calibrada.
Decodificação especulativa
Na decodificação especulativa, uma estratégia barata propõe k tokens e o modelo alvo valida vários de uma vez. O módulo oferece DraftModel, busca de n-gramas no próprio prompt (PromptLookupDecoding) e um passo unsafe que aceita o prefixo coincidente ou substitui a primeira rejeição. O artigo original mostra como preservar a distribuição com um procedimento de aceitação apropriado [21]; a rotina atual do projeto é uma primitiva de baixo nível, não uma pipeline completa plug-and-play nem uma reprodução formal de todos os detalhes do paper.
Uma estimativa de speedup, sem números inventados, é:
a é o número médio de propostas aceitas, C_alvo o custo de um passo normal, C_rascunho o custo por token do draft e C_alvo_batch(k) o custo da validação em lote. Se a aceitação for baixa ou o draft caro, S pode ser menor ou igual a 1. Só medição com o mesmo prompt, seed, backend e modelo pode demonstrar ganho.
Multimodal com mtmd
A feature mtmd compila a API multimodal do upstream. MtmdContext carrega um projetor mmproj GGUF associado ao modelo textual; MtmdBitmap representa mídia; MtmdInputText e MtmdInputChunks transformam prompt mais mídias em chunks avaliáveis. Cada mídia precisa corresponder a um marcador retornado por default_media_marker(), hoje normalmente <__media__>. A API consulta se o projetor suporta visão ou áudio, sample rate de áudio, M-RoPE e atenção não causal.
O suporte tem camadas diferentes. O crate Rust expõe capacidades de visão e áudio do mtmd; servidor e plugin constroem fluxos de chat multimodal; mas o adaptador Tauri analisado aceita imagens por data URL base64 ou caminho local e rejeita áudio explicitamente. Além disso, modelo textual e mmproj precisam ser compatíveis. Um projetor não adiciona visão a qualquer GGUF arbitrário.
Servidor HTTP
llama-crab-server usa Axum e mantém Llama em uma thread worker. Handlers assíncronos enviam jobs por std::sync::mpsc; respostas únicas voltam por oneshot, e chunks passam por canal Tokio para SSE. Esse desenho evita acesso concorrente ao contexto mutável, mas também serializa inferências naquele worker. As rotas implementadas são [14]:
GET /healtheGET /v1/models;POST /v1/completionsePOST /v1/chat/completions;POST /v1/embeddings;POST /v1/reranke aliases/v1/reranking,/rerank,/reranking;POST /extras/tokenize,/extras/tokenize/counte/extras/detokenize.
Completions suportam stop, sampling, logit bias, n, best_of, echo, suffix e logprobs; chat suporta mensagens, ferramentas, saída estruturada e SSE terminado por [DONE]. Embeddings podem retornar floats ou base64; rerank ordena por score e aplica top_n. O binário recebe modelo, host, porta, contexto, batch, threads, camadas GPU, pooling, modo embedding/rerank, preset mobile e mmproj por CLI ou LLAMA_CRAB_*.
O default 127.0.0.1:8080 é prudente. A aplicação configura CorsLayer::permissive() e não implementa autenticação, TLS, quotas ou rate limiting. Expor em 0.0.0.0 sem proxy de segurança transforma arquivos, memória e GPU locais em recursos acessíveis à rede. O worker único também torna limites de fila e cancelamento relevantes contra negação de serviço.
Tauri e TypeScript
O plugin Tauri mantém um registro de modelos e cria uma thread por modelo carregado. Comandos cobrem load/unload/list/retrieve, chat e completion com streaming, embedding, rerank, tokenização, detokenização e cancelamento [15]. init_with_config define defaults globais de contexto, batch, threads, GPU e nome; opções da request têm precedência onde presentes.
O streaming usa tauri::ipc::Channel; cancelamento usa Arc<AtomicBool>. A serialização dentro de cada worker protege o contexto, enquanto modelos distintos podem ter workers distintos. A permissão default autoriza todos os comandos do plugin, inclusive carregar modelo e acessar caminhos locais; aplicações distribuídas devem reduzir capabilities ao mínimo necessário, não aceitar o conjunto default por conveniência.
No frontend, @llama-crab/core mantém tipos e normalização isolados sob llama_crab, evitando misturar extensões com campos OpenAI. @llama-crab/tauri oferece recursos models, chat.completions, completions, embeddings, rerank e extras; overloads TypeScript retornam resposta comum ou AsyncIterable quando stream: true. Ambos geram ESM, CJS e declarações .d.ts com tsup, e são pacotes públicos separados.
Backends e decisões de desempenho
As features expostas são OpenMP/CPU, Metal, CUDA, CUDA sem VMM, Vulkan, ROCm/HIP, OpenCL e KleidiAI, além de link dinâmico, GGML de sistema e backends dinâmicos. Os defaults do crate são openmp e metal, mas o build.rs só ativa Metal quando o target é macOS. Para binários distribuíveis, prefira default-features = false e selecione uma matriz por plataforma.
Desempenho resulta do conjunto, não de uma feature isolada:
- quantização reduz tráfego e memória, mas pode afetar qualidade e kernels disponíveis;
n_gpu_layerscontrola offload e precisa caber junto de KV e buffers;n_batchlimita batch lógico;n_ubatch, o forward físico;n_threadsótimo depende de CPU, NUMA e contenção do processo;mmapreduz custo/cópia de carregamento, mas não elimina page faults;- flash attention é um algoritmo exato orientado a reduzir movimentação de memória [22]; disponibilidade e ganho dependem do backend;
- contexto maior aumenta KV aproximadamente de forma linear na fórmula anterior.
Perfis release, release-perf e release-size usam LTO, uma unidade de codegen e otimizações diferentes; o perfil de tamanho adiciona panic = "abort". Compare sempre a mesma versão do runtime, modelo/quantização, prompt, contexto, batch, temperatura/seed e energia/temperatura do hardware. Sem isso, tokens/s não são comparáveis.
Testes, CI e releases
A suíte combina testes unitários de lógica pura, testes de integração com modelos e testes dos adaptadores. Há regressões para o use-after-move, streaming e parada antecipada, infill reentrante, embeddings, reranker, downloads HF e visão Gemma/LFM; testes de rede são ignorados e exigem LLAMA_CRAB_RUN_HF_INTEGRATION=1. TypeScript usa Vitest para contratos e cliente. O changelog registra “136 testes” em 0.1.1, mas isso é um retrato histórico, não a contagem garantida de 0.1.8 [5].
O CI executa rustfmt; Clippy em Linux/macOS; matriz de features; testes com common,mtmd,disk-cache; servidor com/sem mtmd; rustdoc estrito em nightly; e cargo-llvm-cov com gate de 45% de linhas da biblioteca [16]. Não cobre no workflow principal todos os backends de GPU nem Windows/Android, portanto compilação no CI não me permite concluir portabilidade universal.
Tags v* acionam validação de versões e publicação ordenada: llama-crab-sys, llama-crab, servidor, plugin, depois os dois pacotes npm. O job espera dependências aparecerem no índice crates.io e suporta dry-run. Um workflow semanal encontra a tag bNNNN mais recente do llama.cpp, atualiza o submódulo e abre PR contra develop; isso automatiza descoberta, não compatibilidade, que ainda precisa passar pela matriz [16].
O changelog segue Keep a Changelog e SemVer, mas a cópia analisada começa em 0.1.7 mesmo com manifests/tag 0.1.8; consumidores devem conferir tag, Cargo.lock, submódulo e docs.rs, não depender apenas do topo desse arquivo.
Segurança e licenciamento
A política do Llama Crab cobre 0.1.x, solicita relato privado por e-mail ou GitHub Security Advisory e promete resposta em 72 horas [17]. Para operação segura:
- trate GGUF,
mmproj, imagens, áudio, templates, schemas e estados serializados como entradas não confiáveis; - fixe uma versão ou snapshot nomeado e verifique a integridade dos artefatos baixados;
- execute modelos desconhecidos em sandbox/container/VM com filesystem e rede mínimos;
- limite tamanho de upload, contexto, tokens, fila, tempo, RAM e VRAM;
- não exponha servidor sem autenticação, TLS, CORS restritivo e rate limiting;
- valide tool calls e nunca converta texto do modelo diretamente em comando, SQL ou caminho;
- proteja
HF_TOKENe logs; o downloader evita registrar token, endpoint e URL completa; - atualize tanto o wrapper quanto a versão nativa incluída.
O upstream alerta que modelos e entradas multimodais não confiáveis devem ser isolados, recomenda checksums, desaconselha servidor/RPC em redes hostis e exige separação forte em multi-tenancy [18]. IA local melhora privacidade por evitar um provedor remoto, mas não garante confidencialidade: prompts podem ir para logs, swap, caches, telemetria da aplicação ou outro tenant.
Publiquei o código do Llama Crab sob MIT; o llama.cpp também usa MIT [1][7]. Isso não relicencia pesos, tokenizer, dataset, marca ou conteúdo gerado. O campo general.license no GGUF é metadado de proveniência, não substitui a licença real do repositório do modelo. Distribuidores devem validar também dependências nativas, crates, pacotes npm e licença de cada modelo.
Escopo atual e roadmap
As limitações verificáveis de 0.1.8 são: API OpenAI parcial; um worker serial por modelo; cache de prefixo não conectado automaticamente à API alta; reranking não batched; completion alta limpa sequência 0; speculative decoding de baixo nível e unsafe; subconjuntos de Jinja e JSON Schema; áudio rejeitado pelo Tauri; builders HF parcialmente desconectados; cobertura CI limitada de GPU/Windows/mobile; e ponteiros/unsafe ainda presentes no crate superior. Compatibilidade de modelo continua determinada pelo llama.cpp fixado, não pelo nome GGUF sozinho.
Como ainda não publiquei um roadmap completo na branch, as direções abaixo conectam minhas decisões atuais a oportunidades indicadas por placeholders, comentários e informações que não registrei; não constituem promessas de release:
- conectar revisão, token, cache e endpoint HF de
LlamaParamsao downloader padrão; - transformar cache de prefixos e speculative decoding em fluxos seguros de alto nível;
- reduzir a superfície
unsafe, revisarSend/Synce testar teardown de múltiplas instâncias; - implementar batching/parallel slots para rerank e serving concorrente;
- completar áudio nos adaptadores e uniformizar contabilidade multimodal;
- ampliar fidelidade de Jinja/JSON Schema e validação dos artefatos gerados;
- adicionar matrizes reais de CUDA, ROCm, Vulkan, Windows e mobile;
- estabilizar uma trait pública para samplers customizados, marcada no código como alvo futuro de
v0.2.
Conclusão
Llama Crab é mais que um arquivo de bindings: é uma composição de build nativo, wrappers RAII, orquestração de inferência, contratos de serviço e integração desktop. Seu desenho mais valioso é a separação de responsabilidades: llama.cpp executa tensores; llama-crab-sys representa a ABI; llama-crab organiza ownership e fluxos; servidor, Tauri e TypeScript adaptam transportes.
Usado pelo caminho alto e com modelos confiáveis, ele reduz substancialmente o trabalho acidental de FFI. Usado pelas escape hatches, exige a mesma disciplina de uma integração C: lifetimes, aliasing, thread safety e teardown permanecem obrigações reais. A avaliação correta não é “Rust torna llama.cpp seguro”, mas “o runtime concentra e documenta invariantes para que a maioria das aplicações não precise reimplementá-las”.
Nos próximos passos do ecossistema, pretendo ampliar os fluxos seguros de alto nível, conectar cache e configuração do Hub, testar mais backends e aproximar runtime, servidor, Tauri, exemplos e documentação sem perder a fronteira explícita com o código nativo.
Referências
- Llama Crab, release
v0.1.8, publicada em 16 de junho de 2026: https://github.com/DominguesM/llama-crab/releases/tag/v0.1.8 - README do Llama Crab
v0.1.8: https://github.com/DominguesM/llama-crab/blob/v0.1.8/README.md - Workspace Cargo
v0.1.8: https://github.com/DominguesM/llama-crab/blob/v0.1.8/Cargo.toml llama-crab-sys/build.rsemv0.1.8: https://github.com/DominguesM/llama-crab/blob/v0.1.8/crates/llama-crab-sys/build.rs- Changelog do Llama Crab em
v0.1.8: https://github.com/DominguesM/llama-crab/blob/v0.1.8/CHANGELOG.md - API
llama-crab0.1.8 no docs.rs: https://docs.rs/llama-crab/0.1.8/llama_crab/ llama.cpp, documentação e código upstream: https://github.com/ggml-org/llama.cpp- Especificação GGUF: https://github.com/ggml-org/ggml/blob/master/docs/gguf.md
- API
llama-crab-sys0.1.8 no docs.rs: https://docs.rs/llama-crab-sys/0.1.8/llama_crab_sys/ - Rustonomicon, Foreign Function Interface: https://doc.rust-lang.org/nomicon/ffi.html
- Rust Reference, external blocks e ABI: https://doc.rust-lang.org/reference/items/external-blocks.html
- Cargo Book, build scripts e crates
*-sys: https://doc.rust-lang.org/cargo/reference/build-scripts.html - Hugging Face Hub, cache local: https://huggingface.co/docs/huggingface_hub/guides/manage-cache
- README do
llama-crab-serveremv0.1.8: https://github.com/DominguesM/llama-crab/blob/v0.1.8/crates/llama-crab-server/README.md - README do plugin Tauri em
v0.1.8: https://github.com/DominguesM/llama-crab/blob/v0.1.8/crates/tauri-plugin-llama-crab/README.md - Workflows de CI e release em
v0.1.8: https://github.com/DominguesM/llama-crab/tree/v0.1.8/.github/workflows - Política de segurança do Llama Crab em
v0.1.8: https://github.com/DominguesM/llama-crab/blob/v0.1.8/SECURITY.md - Política e recomendações de segurança do
llama.cpp: https://github.com/ggml-org/llama.cpp/blob/master/SECURITY.md - Guia oficial de GBNF do
llama.cpp: https://github.com/ggml-org/llama.cpp/blob/master/grammars/README.md - Touvron et al., LLaMA: Open and Efficient Foundation Language Models: https://arxiv.org/abs/2302.13971
- Leviathan, Kalman e Matias, Fast Inference from Transformers via Speculative Decoding: https://arxiv.org/abs/2211.17192
- Dao et al., FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness: https://arxiv.org/abs/2205.14135
BibTeX dos artigos
@article{touvron2023llama,
title={LLaMA: Open and Efficient Foundation Language Models},
author={Touvron, Hugo and Lavril, Thibaut and Izacard, Gautier and Martinet, Xavier and Lachaux, Marie-Anne and Lacroix, Timothée and Rozière, Baptiste and Goyal, Naman and Hambro, Eric and Azhar, Faisal and Rodriguez, Aurelien and Joulin, Armand and Grave, Edouard and Lample, Guillaume},
journal={arXiv preprint arXiv:2302.13971},
year={2023},
doi={10.48550/arXiv.2302.13971}
}
@inproceedings{leviathan2023speculative,
title={Fast Inference from Transformers via Speculative Decoding},
author={Leviathan, Yaniv and Kalman, Matan and Matias, Yossi},
booktitle={Proceedings of the 40th International Conference on Machine Learning},
year={2023},
url={https://arxiv.org/abs/2211.17192}
}
@inproceedings{dao2022flashattention,
title={FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness},
author={Dao, Tri and Fu, Daniel Y. and Ermon, Stefano and Rudra, Atri and Ré, Christopher},
booktitle={Advances in Neural Information Processing Systems},
volume={35},
year={2022},
url={https://arxiv.org/abs/2205.14135}
}