Machine Learning em Rust: O Cenário
Machine learning em Rust não é uma única biblioteca como PyTorch é para Python. É uma federação solta de crates que concordam em um modelo de execução compartilhado: computação expressa como tensores fluindo através de um grafo explícito de operações, apoiado por uma abstração de dispositivo plugável para CPU, CUDA, Metal ou WGPU. Entender esse modelo compartilhado é mais importante do que memorizar a API de qualquer crate individual, pois explica por que candle, burn, ort e os crates de tokenização e serviço nesta seção se encaixam da maneira que o fazem.
Esta página é a âncora conceitual para o restante da seção ml-in-rust. Onde a página de Fundamentos mostra código funcional para dez tarefas comuns, esta página explica o modelo de execução por trás dessas tarefas e por que as ferramentas de ML de Rust otimizam para objetivos diferentes dos do Python. O ecossistema do Python é construído para iteração rápida em uma ideia de pesquisa. O de Rust é construído para incorporar um modelo treinado onde um runtime Python não pode ir, servi-lo com latência previsível e capturar erros de forma antes que cheguem à produção.
Resumo
- O ecossistema de ML de Rust é um conjunto de crates que compartilham um modelo de execução de tensor/grafo de computação e se especializam em inferência, embedding e segurança, em vez de velocidade de iteração de pesquisa.
- Por que Importa: Conhecer o modelo compartilhado permite prever como um crate desconhecido se comportará e escolher o correto para treinamento versus inferência versus serviço sem reaprender tudo em sua documentação.
- Conceitos Chave: tensor, grafo de computação, abstração de dispositivo, execução eager vs. graph, autodiff, formato de intercâmbio (ONNX, safetensors, GGUF).
- Quando Usar: utilize ML em Rust quando precisar incorporar um modelo em um único binário, servir inferência com baixa latência sem um runtime Python, ou impor garantias de forma e tipo em tempo de compilação em torno de uma fronteira de modelo.
- Limitações / Trade-offs: o lado de treinamento do ecossistema, significando carregadores de dataset, pesquisa de otimizadores e arquiteturas publicadas, está anos atrás do Python, então a maioria das equipes ainda treina em Python e traz pesos para Rust para as partes que precisam de velocidade ou segurança.
- Tópicos Relacionados: Modelos de execução PyTorch e JAX, intercâmbio ONNX, implantação embarcada e de borda, backends de computação GPU.
Fundamentos
Um tensor é um array n-dimensional com forma e dtype fixos, o mesmo bloco de construção que todo framework de ML usa, de arrays NumPy a tensores PyTorch a Tensor do candle. O que difere entre os ecossistemas é como as operações em tensores se montam em um grafo de computação, a rede de nós (operações) e arestas (tensores) descrevendo como as saídas derivam das entradas. Alguns frameworks constroem esse grafo implicitamente à medida que o código é executado, um estilo chamado define-by-run ou execução eager. Outros consomem um grafo já construído e congelado em outro lugar, mais próximo de define-and-run.
Os crates de ML de Rust se dividem claramente ao longo dessa linha, e essa divisão é a primeira coisa a internalizar sobre o cenário. candle e burn constroem grafos de forma eager, chamada por chamada, muito parecido com o que PyTorch faz, e ambos podem diferenciar através desse grafo para treinamento. ort faz algo diferente: carrega um grafo exportado como ONNX de PyTorch, scikit-learn ou TensorFlow, e apenas o reproduz para frente. Não há história de treinamento em ort, por design.
Uma analogia útil é a diferença entre uma cozinha completa e ferramentas elétricas modulares aparafusadas a uma bancada. O ecossistema do Python é a cozinha, um ambiente coerente onde cada aparelho já sabe como falar com todos os outros, ao custo de carregar toda a cozinha com você. Os crates de ML de Rust são as ferramentas elétricas: escolha uma biblioteca de tensores, um tokenizador e uma camada de serviço separadamente, e monte-os em torno da tarefa.
É por isso também que Rust não tem um framework dominante. candle existe porque Hugging Face queria executar pesos de transformadores sem um runtime Python. burn existe porque pesquisadores queriam treinamento agnóstico de backend com segurança de tensor em tempo de compilação. ort existe porque equipes de produção precisavam executar grafos treinados em outro lugar, o mais rápido e seguro possível. Cada crate resolveu um problema mais restrito do que "ser PyTorch", e o cenário é a soma dessas soluções.
Mecânicas e Interações
Rastreie uma solicitação típica através do modelo compartilhado e o design se torna concreto. Texto bruto ou bytes são tokenizados em IDs inteiros, esses IDs se tornam um tensor de entrada dimensionado [batch, sequence], e esse tensor entra em um grafo de matmuls, ativações e normalizações construído no momento da chamada (candle, burn) ou reproduzido de um grafo ONNX congelado (ort). O tensor de saída sai do outro lado e decodifica de volta para rótulos, tokens ou embeddings.
Duas mecânicas distinguem a versão de Rust desse fluxo da do Python. Primeiro, propriedade. Em Python, um tensor é um objeto com contagem de referência que qualquer código com uma referência pode mutar. Em Rust, o buffer subjacente de um tensor tem um único proprietário (ou é explicitamente compartilhado via Arc), então a maioria das operações consome sua entrada e devolve um novo tensor em vez de mutar no local. É por isso que o idioma em candle, burn e código baseado em ndarray é "chame esta op, receba um novo tensor de volta", uma consequência direta do borrow checker em vez de uma escolha de estilo.
Segundo, a abstração de dispositivo. Todo framework aqui representa CPU, CUDA, Metal e WGPU por trás de um único enum ou trait Device, e as operações de tensor são despachadas para qualquer backend que o dispositivo aponte. O próprio grafo é agnóstico de backend; apenas a execução de cada nó é específica do backend. É isso que permite que burn treine em NdArray em CI e implante em CUDA em produção sem tocar no código do modelo, e é a mesma razão pela qual candle executa uma função forward idêntica em um laptop CPU e em uma GPU de data center.
use candle_core::{Device, Tensor};
// `x` é proprietário de seu buffer. Esta função não muta `x` no local;
// ela retorna um novo nó de tensor, então chamá-la duas vezes compõe dois
// nós de grafo em vez de sobrescrever estado compartilhado - a mesma
// disciplina que todo crate neste cenário segue.
fn scale(x: &Tensor, factor: f64) -> candle_core::Result<Tensor> {
x.affine(factor, 0.0)
}Autodiff é onde os crates eager-graph e o crate apenas de replay divergem acentuadamente. candle e burn retêm (ou podem reconstruir) o grafo para trás para acumular gradientes durante o treinamento, que é por que eles expõem otimizadores e loops de treinamento. ort nunca precisa disso, porque um grafo ONNX congelado não tem gradientes para computar; ele apenas executa para frente. Uma falha de raciocínio comum segue diretamente disso: como a API do candle se parece com PyTorch, é fácil assumir que ela carrega o ecossistema de treinamento e a maturidade do PyTorch com ela. Na realidade, o vocabulário da API é familiar, mas as ferramentas circundantes, significando agendadores, receitas de treinamento publicadas e casos extremos depurados pela comunidade, são muito mais finas.
Considerações Avançadas e Aplicações
O manuseio de formas é onde o modelo na prática fica mais complicado do que o modelo na teoria. Grafos ONNX frequentemente incorporam formas estáticas no momento da exportação, então um serviço que recebe entrada de comprimento variável precisa preencher ou truncar para uma forma fixa antes que o grafo a aceite, ou reexportar com eixos dinâmicos. candle e burn são mais tolerantes no momento da chamada, pois o grafo é reconstruído a cada chamada, mas essa flexibilidade custa uma pequena quantidade de sobrecarga de construção de grafo por solicitação que um grafo ONNX congelado não paga.
A concorrência de serviço é uma segunda área onde a história de Rust difere da do Python. Um padrão típico combina Axum com tokio, mas a inferência em si é trabalho limitado por CPU ou GPU, não I/O, então ele tem que rodar fora das threads do reator do runtime assíncrono (via spawn_blocking ou um pool dedicado) para evitar a fome de outras solicitações. Os pesos do modelo são carregados uma vez na inicialização e compartilhados somente leitura entre os manipuladores através de Arc, então chamadas de inferência concorrentes nunca disputam um lock, apenas o dispositivo de computação em si.
A segurança de memória de Rust é real, mas limitada. Ela cobre o código do lado de Rust que constrói e move tensores através do grafo, mas não se estende além da fronteira FFI para um driver CUDA, um kernel de GPU ou uma biblioteca compartilhada nativa do ONNX Runtime. Travamentos e comportamento indefinido ainda podem se originar lá, e trazer binários nativos pré-compilados (o recurso download-binaries do ort, bibliotecas CUDA empacotadas) é uma fronteira de confiança de cadeia de suprimentos diferente de um grafo de crate puramente Rust.
O sinal mais claro de que todo o cenário é organizado em torno de "um grafo é executado" em vez de "uma maneira canônica de entrar" é o quanto das ferramentas são formatos de intercâmbio. safetensors serializa pesos de tensor, ONNX serializa um grafo de computação inteiro, e GGUF serializa um checkpoint quantizado para inferência local de LLM. Cada formato existe para entregar um grafo ou seus pesos de onde foi treinado para onde precisa ser executado, quase sempre Python para Rust em ambientes de produção hoje.
| Abordagem | Força | Fraqueza | Melhor Ajuste |
|---|---|---|---|
| Treinar e inferir nativamente (burn, candle) | Cadeia de ferramentas única, segurança de forma em tempo de compilação | Menor "zoo" de modelos, ferramentas de treinamento menos maduras em escala | Modelos pequenos/médios, treinamento de borda, equipes que evitam Python completamente |
| Treinar em Python, inferir via ONNX (ort) | Reutiliza todo o ecossistema de pesquisa Python, grafo congelado é rápido de executar | Sem treinamento ou ajuste fino na borda, suposições de forma estática | Serviços de inferência de produção que utilizam modelos treinados em Python |
| Treinar em Python, inferir via GGUF (crates estilo llama.cpp) | Quantização primeiro, pegada de memória mínima | Restrito a arquiteturas LLM/transformador | Inferência LLM local e de borda |
| Pilha Python completa de ponta a ponta (PyTorch, JAX) | Velocidade máxima de iteração, maior ecossistema | Dependência de runtime, GIL, difícil de enviar como um único binário | Pesquisa, iteração rápida, implantação não embarcada |
Conceitos Equivocados Comuns
- "ML em Rust significa treinar modelos grandes em Rust." A maior parte do ML em Rust de produção é apenas inferência sobre pesos treinados em Python; o treinamento nativo é comum para modelos pequenos e pesquisa, não para os de escala de fronteira.
- "candle se parece com PyTorch, então tem o ecossistema do PyTorch." O vocabulário da API é deliberadamente familiar para que os desenvolvedores PyTorch se sintam em casa, mas as ferramentas por trás dele, significando arquiteturas publicadas, casos extremos depurados e integrações de terceiros, levaram uma década para serem acumuladas e ainda não são equivalentes.
- "Qualquer modelo que treina em Python exportará limpo para ONNX." Existem lacunas de operações e camadas exóticas podem falhar na importação, muitas vezes exigindo um grafo de exportação simplificado ou um caminho alternativo.
- "A segurança de memória de Rust torna toda a pilha de inferência segura." As garantias de segurança param na fronteira FFI; drivers de GPU e bibliotecas de runtime nativas ainda são uma fonte de travamentos.
- "Escolher um crate de ML de Rust é apenas uma preferência de sintaxe em relação ao Python." Muda a forma operacional do sistema, a pegada de implantação, o perfil de latência e se o treinamento é sequer possível, não apenas em qual linguagem você escreve.
- "Você precisa de uma GPU para obter valor do ML em Rust." Uma grande parte do uso real de ML em Rust é inferência limitada por CPU e implantação de borda, uma área onde a sobrecarga por solicitação do Python é comparativamente fraca.
FAQs
O que "modelo de execução compartilhado de grafo de computação/tensor" realmente significa aqui?
Significa que cada crate neste cenário representa trabalho de ML da mesma forma por baixo: tensores como os dados, e um grafo de operações conectando-os. candle e burn constroem esse grafo à medida que seu código é executado; ort carrega um que já foi construído e congelado como ONNX. Tempo de construção diferente, mesma forma subjacente.
Por que Rust não tem um framework de ML dominante como PyTorch?
Cada crate principal surgiu de um problema mais restrito do que "substituir PyTorch": candle para executar pesos Hugging Face sem Python, burn para querer treinamento agnóstico de backend com ranks de tensor seguros por tipo, ort para precisar executar grafos treinados em outro lugar o mais rápido possível. O cenário é a soma dessas soluções mais restritas em vez de um framework de propósito geral.
Como um crate de ML de Rust realmente constrói e executa um grafo de computação?
- Crates eager (candle, burn) constroem nós de grafo à medida que cada op de tensor é chamado, semelhante ao estilo define-by-run do PyTorch.
- Crates apenas de replay (ort) carregam um grafo serializado em outro lugar (ONNX) e o executam para frente sem reconstruí-lo.
- Ambos despacham a computação de cada nó para um
Device(CPU, CUDA, Metal, WGPU) escolhido em tempo de execução.
Como o autodiff funciona sem o grafo de objetos dinâmicos do Python?
candle e burn rastreiam a sequência de operações de tensor o suficiente para percorrê-la para trás e acumular gradientes, o mesmo mecanismo conceitual do autograd do PyTorch, implementado sobre regras de propriedade em vez de objetos com contagem de referência. ort não tem autodiff algum, pois ele apenas reproduz um grafo forward congelado.
ML em Rust é principalmente para treinamento ou para inferência?
Predominantemente para inferência, embedding e serviço em produção. Treinar nativamente em Rust (via candle ou burn) é real e útil para modelos menores e casos de borda, mas a maioria das equipes ainda faz treinamento em larga escala em Python e traz os pesos resultantes para Rust.
Quando não devo usar Rust para uma tarefa de machine learning?
- Pesquisa exploratória onde você ainda está iterando em ideias de arquitetura diariamente.
- Qualquer coisa que dependa de uma receita de treinamento de biblioteca Python sem equivalente em Rust.
- Treinamento distribuído em larga escala em arquiteturas que os crates de Rust ainda não suportam bem.
A API semelhante ao PyTorch do candle não significa que ele herda o ecossistema do PyTorch também?
Não. A superfície da API é intencionalmente familiar para que os desenvolvedores PyTorch se sintam em casa, mas as ferramentas por trás dela, significando arquiteturas publicadas, casos extremos depurados e integrações de terceiros, são muito menores. Trate a similaridade como um auxílio de aprendizado, não como uma garantia de paridade.
Como os backends de GPU se encaixam neste cenário?
Todo framework representa CPU, CUDA, Metal e WGPU por trás de uma abstração de dispositivo, então o mesmo código de construção de grafo pode ter como alvo hardware diferente apenas trocando o dispositivo em que os tensores são alocados. Veja GPU & Aceleração para a configuração prática.
Qual é o papel dos formatos de intercâmbio como ONNX, safetensors e GGUF?
Eles são a ponte normal do treinamento em Python para a implantação em Rust, não um caso de borda. safetensors carrega pesos brutos, ONNX carrega um grafo completo, e GGUF carrega um checkpoint quantizado ajustado para inferência local de LLM.
A segurança de memória de Rust me protege de todos os travamentos relacionados a ML?
Não. Ela cobre o código do lado de Rust que constrói e move tensores através do grafo, mas drivers de GPU e bibliotecas de runtime nativas chamadas através de FFI ficam fora dessa garantia.
Como decido entre burn, candle e ort para um novo projeto?
- Precisa executar o grafo exportado de outra pessoa o mais rápido possível sem treinamento? Use ort.
- Precisa de pesos de transformador compatíveis com Hugging Face e uma API de tensor familiar? Use candle.
- Precisa treinar do zero em Rust com flexibilidade de backend e segurança de forma em tempo de compilação? Use burn.
A tipagem de tensor em tempo de compilação realmente captura bugs reais?
Sim, mais comumente erros de rank, como passar um tensor [batch, seq, hidden] onde um tensor [batch, hidden] é esperado. burn codifica o rank em seu tipo Tensor<B, D> para que esses falhem na compilação; candle e ort capturam os erros equivalentes em tempo de execução através de um erro Result, ainda mais cedo e mais explicitamente do que um bug de broadcast silencioso faria em código eager Python.
Relacionados
- Fundamentos de ML em Rust - um guia prático que coloca este modelo em prática em dez exemplos.
- candle - o framework de tensor e transformador eager, focado em HF.
- burn - o framework de treinamento agnóstico de backend com ranks de tensor em tempo de compilação.
- ONNX Runtime (ort) - o caminho de replay para grafos treinados em outro lugar.
- Inferência e Serviço de LLM - como o modelo de execução aparece em uma arquitetura de serviço.
Versões da Stack: Esta página é conceitual e não está vinculada a uma versão específica da stack.