Executar inferência ONNX em grafos de fluxo de dados WASM (WebAssembly)

Este artigo mostra como incorporar e executar pequenos modelos Open Neural Network Exchange (ONNX) em seus módulos WebAssembly para realizar inferência em banda como parte dos gráficos de fluxo de dados do Operações do Azure IoT. Use essa abordagem para enriquecimento de baixa latência e classificação diretamente em dados de streaming sem chamar serviços de previsão externa.

Pré-requisitos

Antes de começar, verifique se você tem os seguintes itens:

  • Uma implantação do Operações do Azure IoT que possui a funcionalidade de grafos de fluxo de dados.
  • Um registro de contêineres, como o Registro de Contêiner do Azure, e um ponto de extremidade desse registro configurado. Para saber mais, confira Configurar pontos de extremidade do Registro.
  • Um ambiente de desenvolvimento configurado para o desenvolvimento do módulo WebAssembly. Para obter opções e instruções detalhadas de configuração de ambiente, consulte Desenvolver módulos WebAssembly.

Importante

Atualmente, os grafos de fluxo de dados dão suporte apenas a MQTT (Protocolo de Telemetria de Filas de Mensagens), Kafka e endpoints do OpenTelemetry. Não há suporte para outros tipos de ponto de extremidade, como Azure Data Lake, Microsoft Fabric OneLake, Azure Data Explorer e armazenamento local.

Benefícios da inferência ONNX na banda

Com os grafos de fluxo de dados do Operações do Azure IoT, você pode incorporar diretamente uma inferência de modelo ONNX pequena no pipeline, em vez de chamar um serviço de previsão externo. Essa abordagem oferece os seguintes benefícios:

  • Baixa latência: execute o enriquecimento ou a classificação em tempo real na mesma via do operador por onde os dados chegam. Cada mensagem requer apenas inferência de CPU local, evitando viagens de ida e volta de rede.
  • Integrado ao processamento de transmissões: execute a inferência junto com o processamento de transmissões de várias fontes, em que os atributos já estão co-localizados no grafo, e alinhe-a à semântica de tempo de evento para que a inferência use as mesmas marcas de tempo que os outros operadores.
  • Atualizações simples: envie um novo módulo com WASM e modelo inserido e atualize a referência de definição de grafo. Você não precisa de um registro de modelos separado nem de uma mudança em endpoint externo.
  • Dimensionamento horizontal: a inferência é dimensionada conforme o grafo de fluxo de dados é dimensionado. Quando o runtime adiciona mais trabalhadores para aumentar a taxa de transferência, cada trabalhador carrega o modelo incorporado e participa do balanceamento de carga.

A inferência é executada em uma CPU por meio da interface WASI (Interface do Sistema WebAssembly) wasi-nn . Para formatos de modelo e restrições de hardware com suporte, consulte Limitações da inferência ONNX em grafos de fluxo de dados WASM.

Quando você deve usar a inferência ONNX em banda?

Use a inferência ONNX em banda em grafos de fluxo de dados quando precisar dos seguintes recursos:

  • Baixa latência para enriquecer ou classificar mensagens em linha durante a ingestão.
  • Modelos pequenos e eficientes, como modelos de visão do tipo MobileNet.
  • Inferência que se alinha ao processamento em tempo de evento e usa as mesmas marcas de tempo dos outros operadores.
  • Atualizações de modelo simples enviando uma nova versão do módulo.

Não use inferência ONNX em banda quando precisar dos seguintes recursos:

  • Grandes modelos de transformadores, aceleração por GPU ou TPU, ou implantações A/B sofisticadas.
  • Os modelos que exigem várias entradas de tensores, cache de chave-valor ou operadores ONNX sem suporte.

Observação

Mantenha os módulos e os modelos incorporados pequenos. Não há suporte para modelos grandes e tarefas com uso intensivo de memória. Use arquiteturas compactas e tamanhos de entrada pequenos, como 224×224 para classificação de imagem.

Padrão de arquitetura para inferência ONNX em grafos de fluxo de dados

O padrão comum para inferência ONNX em grafos de fluxo de dados inclui os seguintes estágios:

  1. Dados de pré-processamento: transforme dados de entrada brutos para corresponder ao formato esperado do modelo. Para modelos de imagem, esse processo normalmente envolve:
    • Decodificação de bytes de imagem.
    • Redimensionando para uma dimensão de destino (por exemplo, 224×224).
    • Convertendo o espaço de cores (por exemplo, RGB em BGR).
    • Normalizando valores de pixel para o intervalo esperado (0 a 1 ou -1 a 1).
    • Organizando dados no layout de tensor correto: NCHW (lote, canais, altura, largura) ou NHWC (lote, altura, largura, canais).
  2. Executar inferência: converta dados pré-processados em tensores usando a interface wasi-nn, carregue o modelo ONNX incorporado com o back-end da CPU, defina tensores de entrada no contexto de execução, invoque o processamento para frente do modelo e recupere tensores de saída que contêm previsões brutas.
  3. Saídas de pós-processamento: transformar saídas de modelo bruto em resultados significativos. Operações comuns:
    • Aplique softmax para produzir probabilidades de classificação.
    • Selecione as previsões top-K.
    • Aplique um limite de confiança para filtrar os resultados de baixa confiança.
    • Mapear os índices de previsão para rótulos compreensíveis por humanos.
    • Formatar resultados para consumo posterior.

Nos exemplos IoT para operadores RUST WASM você pode encontrar dois exemplos que seguem este padrão:

Configurar a definição do grafo

Para habilitar a inferência ONNX no grafo de fluxo de dados, configure a estrutura do grafo e os parâmetros do módulo. A definição do grafo especifica o fluxo de pipeline, enquanto as configurações dos módulos permitem a personalização em tempo de execução do comportamento de pré-processamento e inferência.

Habilitar o recurso wasi-nn

Para habilitar a interface WebAssembly Neural Network (wasi-nn) para inferência com ONNX, adicione o recurso wasi-nn à definição do seu grafo:

moduleRequirements:
  apiVersion: "1.1.0"
  runtimeVersion: "1.1.0"
  features:
    - name: "wasi-nn"

Definir operações para o pipeline de inferência

Configure as operações que formam o pipeline de inferência do ONNX. Este exemplo mostra um fluxo de trabalho de classificação de imagem típico:

operations:
  - operationType: "source"
    name: "camera-input"
  - operationType: "map"
    name: "module-format/map"
    module: "format:1.0.0"
  - operationType: "map"
    name: "module-snapshot/map"
    module: "snapshot:1.0.0"
  - operationType: "sink"
    name: "results-output"

connections:
  - from: { name: "camera-input" }
    to: { name: "module-format/map" }
  - from: { name: "module-format/map" }
    to: { name: "module-snapshot/map" }
  - from: { name: "module-snapshot/map" }
    to: { name: "results-output" }

Essa configuração cria um pipeline em que:

  • camera-input recebe dados brutos de imagem de uma fonte
  • module-format/map pré-processa imagens (decodificar, redimensionar, formatar conversão)
  • module-snapshot/map executa a inferência e o pós-processamento do ONNX
  • results-output emite resultados de classificação para um coletor

Configurar parâmetros de módulo

Defina parâmetros de runtime para personalizar o comportamento do módulo WASM sem recompilar. Esses parâmetros passam para os módulos WASM na inicialização.

Para obter detalhes, consulte os parâmetros de configuração do módulo.

Empacotar o modelo

A incorporação de modelos ONNX diretamente em seu componente WASM garante a implantação atômica e a consistência da versão. Essa abordagem simplifica a distribuição e remove as dependências de runtime em arquivos ou registros de modelo externos.

Dica

A inserção mantém o modelo e a lógica do operador versionados juntos. Para atualizar um modelo, publique uma nova versão do módulo e atualize sua definição de grafo para referenciá-lo. Essa abordagem elimina o descompasso do modelo e garante implantações reproduzíveis.

Requisitos de preparação do modelo ONNX

Antes de inserir seu modelo, verifique se ele atende aos requisitos de implantação do WASM:

  • Mantenha modelos abaixo de 50 MB para tempos práticos de carregamento de WASM e restrições de memória.
  • Verifique se o modelo aceita uma única entrada tensor em um formato comum (float32 ou uint8).
  • Verifique se o back-end de runtime WASM ONNX dá suporte a todos os operadores usados pelo modelo.
  • Use ferramentas de otimização ONNX para reduzir o tamanho do modelo e melhorar a velocidade de inferência.

Etapas para inserir um modelo ONNX em um módulo WASM

Siga estas etapas para inserir seu modelo ONNX e recursos associados em um módulo WASM:

  1. Organizar ativos de modelo: coloque o arquivo de modelo .onnx e os labels.txt opcionais em sua árvore de origem. Use uma estrutura de diretório dedicada, como src/fixture/models/ e src/fixture/labels/ para uma organização clara.
  2. Incorpore em tempo de compilação: use ferramentas específicas da linguagem para incluir os bytes do modelo no binário. No Rust, use include_bytes! para dados binários e include_str! para arquivos de texto.
  3. Inicializar o grafo wasi-nn: Na função init do seu operador, crie um grafo wasi-nn a partir dos bytes incorporados, especificando a codificação ONNX e o alvo de execução em CPU.
  4. Implementar o loop de inferência: para cada mensagem de entrada, pré-processe entradas para corresponder aos requisitos do modelo, defina tensores de entrada, execute inferência, recupere saídas e aplique o pós-processamento.
  5. Lidar com erros normalmente: implemente o tratamento de erros adequado para falhas de carregamento de modelo, operadores sem suporte e erros de inferência de runtime.

Para obter um padrão de implementação completo, consulte o exemplo "snapshot.

Organize seu projeto de módulo WASM com clara separação de preocupações:

src/
├── lib.rs                 # Main module implementation
├── model/
│   ├── mod.rs            # Model management module
│   └── inference.rs      # Inference logic
└── fixture/
    ├── models/
    │   ├── mobilenet.onnx      # Primary model
    │   └── mobilenet_opt.onnx  # Optimized variant
    └── labels/
        ├── imagenet.txt        # ImageNet class labels
        └── custom.txt          # Custom label mappings

Exemplo de estrutura de arquivos da amostra de instantâneo

Use o seguinte layout de arquivo do exemplo de "instantâneo" como referência:

  • diretório Labels - Contém vários arquivos de mapeamento de rótulo
  • DiretórioModels - Contém arquivos de modelo ONNX e metadados

Exemplo mínimo do Rust para inserir um modelo ONNX

O exemplo do Rust a seguir mostra o código mínimo necessário para inserir um modelo ONNX em um módulo WASM. Os caminhos são relativos ao arquivo de origem que contém a macro:

// src/lib.rs (example)
// Embed ONNX model and label map into the component
static MODEL: &[u8] = include_bytes!("fixture/models/mobilenet.onnx");
static LABEL_MAP: &[u8] = include_bytes!("fixture/labels/synset.txt");

fn init_model() -> Result<(), anyhow::Error> {
  // Create wasi-nn graph from embedded ONNX bytes using the CPU backend
  // Pseudocode – refer to the snapshot sample for the full implementation
  // use wasi_nn::{graph::{load, GraphEncoding, ExecutionTarget}, Graph};
  // let graph = load(&[MODEL.to_vec()], GraphEncoding::Onnx, ExecutionTarget::Cpu)?;
  // let exec_ctx = Graph::init_execution_context(&graph)?;
  Ok(())
}

Reutilizar o grafo ONNX e o contexto de execução entre mensagens

Para evitar recriar o grafo ONNX e o contexto de execução para cada mensagem, inicialize-os uma vez e reutilize-os. O exemplo público de instantâneo usa um LazyLock estático para inicializar o grafo e o contexto de execução uma vez por worker:

use crate::wasi::nn::{
    graph::{load, ExecutionTarget, Graph, GraphEncoding, GraphExecutionContext},
    tensor::{Tensor, TensorData, TensorDimensions, TensorType},
};

static mut CONTEXT: LazyLock<GraphExecutionContext> = LazyLock::new(|| {
    let graph = load(&[MODEL.to_vec()], GraphEncoding::Onnx, ExecutionTarget::Cpu).unwrap();
    Graph::init_execution_context(&graph).unwrap()
});

fn run_inference(/* input tensors, etc. */) {
    unsafe {
        // (*CONTEXT).compute()?;
    }
}

Depurar e testar seu módulo ONNX localmente

Antes de implantar no Operações do Azure IoT, teste o módulo de inferência ONNX localmente para validar a funcionalidade e o desempenho. Para obter mais informações, consulte:

Configurar e implantar o módulo ONNX

Quando estiver pronto para usar seu módulo de inferência ONNX em um fluxo de dados ou conector do Operações do Azure IoT, siga estas etapas:

Exemplo: classificação de imagem da MobileNet

As amostras públicas de IoT fornecem duas amostras conectadas em um grafo para classificação de imagens:

  • O exemplo "format" fornece a funcionalidade de decodificação e redimensionamento da imagem.
  • O exemplo "snapshot" fornece inferência ONNX e processamento softmax.

Para saber mais sobre e executar o exemplo que usa esses módulos, consulte Exemplo 2: Implantar um grafo complexo.

Limitações da inferência ONNX em grafos de fluxo de dados WASM

A inferência em grafos de fluxo de dados WASM tem as seguintes limitações:

  • Formato de modelo: somente ONNX. Os grafos de fluxo de dados não dão suporte a outros formatos, como o TFLite.
  • Hardware: somente CPU. Não há suporte para aceleração de GPU e TPU.
  • Tamanho do modelo: não há suporte para modelos grandes e inferência com uso intensivo de memória. Use modelos pequenos, como arquiteturas de classe MobileNet.
  • Entradas de modelo: há suporte apenas para modelos de entrada de tensor único. Não há suporte para modelos com várias entradas, armazenamento em cache de chave-valor e cenários avançados de sequência ou gerativos.
  • Suporte a operadores: o backend ONNX no runtime WASM deve oferecer suporte a todos os operadores que seu modelo usa. Se não houver suporte para um operador, a inferência falhará no tempo de carregamento ou execução.