Servindo Modelos com Axum
Inferência de ML em produção via HTTP com Axum 0.8 - estado de modelo compartilhado, requisições validadas e execução não bloqueante.
Busque em todas as páginas da documentação
Inferência de ML em produção via HTTP com Axum 0.8 - estado de modelo compartilhado, requisições validadas e execução não bloqueante.
use axum::{extract::State, routing::post, Json, Router};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use tokio::task;
#[derive(Clone)]
struct AppState {
// session: Arc<ort::session::Session>,
}
#[derive(Deserialize)]
struct PredictRequest {
features: Vec<f32>,
}
#[derive(Serialize)]
struct PredictResponse {
score: f32,
}
async fn predict(
State(_state): State<AppState>,
Json(req): Json<PredictRequest>,
) -> Json<PredictResponse> {
let features = req.features;
let score = task::spawn_blocking(move || run_model(&features))
.await
.unwrap();
Json(PredictResponse { score })
}
fn run_model(features: &[f32]) -> f32 {
features.iter().sum::<f32>() / features.len() as f32
}Quando usar isso:
use axum::{extract::State, http::StatusCode, routing::post, Json, Router};
use serde::{Deserialize, Serialize};
use std::sync::Arc;
use std::time::Instant;
use tokio::task;
use tracing::info;
#[derive(Clone)]
struct AppState {
model_version: Arc<String>,
}
#[derive(Deserialize)]
struct PredictRequest {
features: Vec<f32>,
}
#[derive(Serialize)]
struct PredictResponse {
score: f32,
model_version: String,
}
async fn predict(
State(state): State<AppState>,
Json(req): Json<PredictRequest>,
) -> Result<Json<PredictResponse>, StatusCode> {
if req.features.is_empty() || req.features.len() > 10_000 {
return Err(StatusCode::BAD_REQUEST);
}
let version = state.model_version.clone();
let start = Instant::now();
let features = req.features;
let score = task::spawn_blocking(move || infer(&features))
.await
.map_err(|_| StatusCode::INTERNAL_SERVER_ERROR)?;
info!(elapsed_ms = start.elapsed().as_millis(), "inference");
Ok(Json(PredictResponse {
score,
model_version: (*version).clone(),
}))
}
fn infer(features: &[f32]) -> f32 {
features.iter().copied().fold(0.0f32, f32::max)
}
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
let state = AppState {
model_version: Arc::new("v1.0.0".into()),
};
let app = Router::new()
.route("/predict", post(predict))
.with_state(state);
let listener = tokio::net::TcpListener::bind("0.0.0.0:8080").await.unwrap();
axum::serve(listener, app).await.unwrap();
}O que isso demonstra:
spawn_blocking para inferência síncronatracing para medir o tempo por requisiçãoAppState contém Arc<Session> ou pesos de modelo carregados na inicialização.| Camada | Propósito |
|---|---|
TimeoutLayer | Encerrar inferências travadas |
RequestBodyLimitLayer | Limitar payloads JSON enormes |
TraceLayer | Spans em nível HTTP |
spawn_blocking ou um pool de threads dedicado.Arc a quente com um canal watch no SIGHUP ou um observador de arquivos./predict - queima de GPU aberta. Correção: chaves de API ou mTLS no gateway.| Alternativa | Usar Quando | Não Usar Quando |
|---|---|---|
| gRPC + tonic | Malha interna de alto QPS | Clientes de navegador precisam de JSON |
| BentoML / Ray Serve | Propriedade do ecossistema Python | Mandatos de operação apenas em Rust |
| Serverless GPU | Tráfego esporádico | Cold start inaceitável |
| Worker de fila em lote | Throughput acima de latência | SLAs de API interativa |
Router::with_state + extrator State<T> - clone apenas campos Arc internos baratos.
#[tokio::main] fn async: carregue em main, passe AppState para o router antes de serve.
Use handler SSE - veja Inferência e Servindo de LLM.
Fila em um worker de canal que agrupa requisições dentro de uma janela de N ms - padrão avançado para utilização de GPU.
Liveness: processo ativo. Readiness: modelo carregado e inferência de teste abaixo do limite de latência.
Use o crate validator ou verificações manuais no comprimento do vetor e nos intervalos de valores antes da alocação de tensor.
Contêiner mínimo com montagem de volume de modelo, limites de recursos de CPU/GPU e HPA na profundidade da fila ou latência.
Traces OpenTelemetry abrangendo HTTP + seção spawn_blocking com atributo de versão do modelo.
Rotas separadas ou roteamento de campo model_id para HashMap<String, Arc<Session>>.
Substitua o stub infer por uma chamada de sessão ort dentro da tarefa de bloqueio.
Versões da Stack: Esta página foi escrita para Rust 1.97.0 (edição 2024), Tokio 1.x, Axum 0.8, serde 1.0, sqlx 0.8, clap 4, e Polars 0.46+.
Revisado por Chris St. John·Última atualização: 16 de jul. de 2026