IoT e IA

Como Implantar Modelos de ML em Dispositivos de Borda: Empacotamento, Versionamento e Rollouts via MQTT

Um guia prático para entregar modelos de machine learning a frotas de dispositivos de borda, usando mensagens retidas do MQTT para anunciar novas versões e coletar o status do rollout de cada dispositivo.

Diagrama de como implantar modelos de ML em dispositivos de borda, com um broker MQTT anunciando versões de modelo e dispositivos informando o status do rollout
Rode isso no seu próprio broker MQTTTenha um broker MQTT privado e gerenciado no plano Free e comece com exemplos de código prontos.
Teste grátis Exemplos de código Sem cartão de crédito

Treinar um modelo é só metade do trabalho: para gerar valor, você precisa implantar modelos de ML em dispositivos de borda, mantê-los atualizados e saber a qualquer momento qual versão cada dispositivo está executando. Este guia mostra um padrão comprovado para empacotar, versionar e distribuir modelos em uma frota, usando o MQTT para sinalizar atualizações e coletar status, enquanto os próprios arquivos de modelo trafegam via HTTPS.

Por que a implantação de modelos na borda é difícil

Entregar um modelo a um servidor é uma única implantação. Entregá-lo a centenas de gateways, câmeras ou microcontroladores, espalhados por fazendas, frigoríficos ou plantas em Manaus e Joinville, é um problema de sistemas distribuídos:

  • Os dispositivos ficam offline em momentos aleatórios e precisam se atualizar quando se reconectam.
  • O hardware varia, então um único artefato de modelo pode não servir para todos os dispositivos.
  • Um modelo ruim pode degradar as previsões silenciosamente em vez de travar, então você precisa de sinais de saúde além de "o processo está rodando".
  • A largura de banda e o armazenamento são limitados, e alguns locais ficam atrás de firewalls que só permitem conexões de saída.

O MQTT se encaixa bem nessas restrições. Os dispositivos mantêm uma única conexão de saída com um broker, as mensagens retidas entregam imediatamente a instrução mais recente a quem se conecta depois e uma mensagem de Last Will avisa quando um dispositivo cai. Se você é novo nessa divisão de responsabilidades, nossa visão geral sobre o que faz um broker MQTT é uma boa introdução.

Passo 1: empacote o modelo como um artefato versionado

Trate cada modelo como uma release imutável, nunca como um arquivo que é sobrescrito. Um pacote de modelo deve conter:

Arquivo Finalidade
model.tflite (ou .onnx, _edgetpu.tflite, .engine) O modelo compilado para um runtime específico
labels.txt ou classes.json Nomes das classes de saída na ordem correta
manifest.json Versão, runtime, formato da entrada, pré-processamento, hardware-alvo, checksum
preprocess.json (opcional) Constantes de normalização, modo de redimensionamento, ordem dos canais

Incompatibilidades de pré-processamento são a causa mais frequente de "o modelo funciona no meu notebook, mas não no dispositivo". Registrar no manifesto o tamanho de entrada esperado, a ordem das cores e a normalização, e fazer o dispositivo verificá-los no carregamento, evita toda uma classe de erros silenciosos.

Escolha o formato de acordo com o alvo:

Hardware-alvo Formato típico Observações
Microcontrolador (ESP32-S3, Cortex-M) TensorFlow Lite Micro, int8 Geralmente compilado no firmware; atualizado via OTA de firmware
Raspberry Pi / gateway ARM TFLite ou ONNX Runtime Pode ser trocado em tempo de execução como arquivo
Coral Edge TPU _edgetpu.tflite Precisa ser compilado com o Edge TPU compiler
NVIDIA Jetson Engine TensorRT ou ONNX Engines ficam atreladas à GPU e à versão do TensorRT, então gere uma por tipo de dispositivo

Em microcontroladores, o modelo muitas vezes fica dentro da imagem do firmware, então uma atualização de modelo é uma atualização de firmware. Nosso tutorial de TensorFlow Lite Micro cobre esse caso; o restante deste guia se concentra em dispositivos da classe Linux que carregam modelos a partir do disco.

Passo 2: versione semanticamente e assine cada release

Use uma string de versão que diga algo aos operadores, como defect-classifier 2.3.0, em que o número principal (major) muda quando entradas ou saídas mudam de forma incompatível. Junto com a versão, calcule um checksum SHA-256 do artefato e publique-o no manifesto. Os dispositivos devem recusar um arquivo cujo checksum não confira. Para maior garantia, assine o manifesto com uma chave privada mantida pelo seu sistema de CI e distribua aos dispositivos apenas a chave pública.

Armazene os artefatos em um object storage via HTTPS ou em um servidor de arquivos interno. O MQTT é o canal errado para binários de vários megabytes: o broker teria de manter payloads grandes na memória, e muitos planos limitam o tamanho das mensagens. O plano gratuito Serverless do ConnectMQ, por exemplo, limita as mensagens a 256 KB, o que é mais do que suficiente para um manifesto, mas não para um modelo de visão.

Passo 3: sinalize as atualizações via MQTT

O plano de controle usa três topics por dispositivo (ou por grupo de dispositivos):

fleet/<site>/<device>/model/desired    retido, escrito pelo serviço de implantação
fleet/<site>/<device>/model/reported   retido, escrito pelo dispositivo
fleet/<site>/<device>/model/events     não retido, progresso e erros

O serviço de implantação publica uma mensagem de estado desejado, retida para que um dispositivo offline a receba assim que se conectar:

{
  "model": "defect-classifier",
  "version": "2.3.0",
  "url": "https://models.example.com/defect-classifier/2.3.0/model.tflite",
  "sha256": "9f2c4c1d5e8b0a7f3d6e1b2c4a5f6e7d8c9b0a1f2e3d4c5b6a7f8e9d0c1b2a3f",
  "runtime": "tflite",
  "rollout": "canary",
  "issuedAt": "2026-10-16T08:00:00Z"
}

O dispositivo compara o estado desejado com o que está executando, baixa, verifica, troca e informa de volta:

{
  "model": "defect-classifier",
  "version": "2.3.0",
  "state": "active",
  "previous": "2.2.1",
  "loadedAt": "2026-10-16T08:03:12Z",
  "selfTest": "passed"
}

Esse padrão desired/reported é a mesma ideia usada pelos device shadows e funciona com qualquer broker compatível com o padrão.

Passo 4: um agente de atualização mínimo para o dispositivo

O agente abaixo roda em um dispositivo de borda Linux. Ele assina o seu topic de estado desejado, baixa o modelo, verifica o checksum, executa um smoke test, troca o modelo ativo de forma atômica e mantém a versão anterior para rollback.

import hashlib
import json
import os
import shutil
import time
import urllib.request
from pathlib import Path

import paho.mqtt.client as mqtt

DEVICE = "gw-07"
BASE = f"connectmq/demo/a91e4d/fleet/plant1/{DEVICE}/model"
MODELS = Path("/opt/models")
ACTIVE = MODELS / "active"           # symlink para o diretório da versão ativa
STATE_FILE = MODELS / "state.json"


def current_version():
    try:
        return json.loads(STATE_FILE.read_text())["version"]
    except (FileNotFoundError, KeyError, json.JSONDecodeError):
        return None


def report(client, state, **extra):
    body = {"model": extra.pop("model", None), "state": state,
            "version": extra.pop("version", current_version()),
            "ts": int(time.time() * 1000), **extra}
    client.publish(f"{BASE}/reported", json.dumps(body), qos=1, retain=True)


def event(client, msg):
    client.publish(f"{BASE}/events", json.dumps({"msg": msg, "ts": int(time.time() * 1000)}), qos=0)


def sha256_of(path):
    h = hashlib.sha256()
    with open(path, "rb") as f:
        for chunk in iter(lambda: f.read(1 << 20), b""):
            h.update(chunk)
    return h.hexdigest()


def smoke_test(model_path):
    # Carrega o modelo e executa uma inferência com uma entrada de exemplo armazenada.
    # Substitua pelo seu runtime, p. ex. tflite_runtime.interpreter.Interpreter.
    return model_path.stat().st_size > 0


def apply_update(client, desired):
    version = desired["version"]
    if version == current_version():
        report(client, "active", model=desired["model"], version=version)
        return
    target = MODELS / version
    target.mkdir(parents=True, exist_ok=True)
    tmp = target / "model.tmp"
    event(client, f"downloading {version}")
    urllib.request.urlretrieve(desired["url"], tmp)
    if sha256_of(tmp) != desired["sha256"]:
        tmp.unlink(missing_ok=True)
        report(client, "failed", model=desired["model"], reason="checksum mismatch")
        return
    final = target / "model.tflite"
    tmp.replace(final)
    if not smoke_test(final):
        report(client, "failed", model=desired["model"], reason="smoke test failed")
        return
    previous = current_version()
    link_tmp = MODELS / "active.tmp"
    if link_tmp.exists() or link_tmp.is_symlink():
        link_tmp.unlink()
    link_tmp.symlink_to(target)
    os.replace(link_tmp, ACTIVE)  # troca atômica do symlink
    STATE_FILE.write_text(json.dumps({"version": version, "previous": previous}))
    report(client, "active", model=desired["model"], version=version, previous=previous)
    # Mantém em disco apenas a versão atual e a anterior
    for d in MODELS.iterdir():
        if d.is_dir() and not d.is_symlink() and d.name not in (version, previous):
            shutil.rmtree(d, ignore_errors=True)


def on_connect(client, userdata, flags, reason_code, properties):
    client.subscribe(f"{BASE}/desired", qos=1)
    report(client, "online")


def on_message(client, userdata, msg):
    try:
        apply_update(client, json.loads(msg.payload))
    except Exception as exc:  # informa cada falha em vez de morrer em silêncio
        report(client, "failed", reason=str(exc))


MODELS.mkdir(parents=True, exist_ok=True)
client = mqtt.Client(mqtt.CallbackAPIVersion.VERSION2, client_id=f"{DEVICE}-a91e4d")
client.on_connect = on_connect
client.on_message = on_message
client.will_set(f"{BASE}/reported", json.dumps({"state": "offline"}), qos=1, retain=True)
client.connect("broker.connectmq.com", 1883, keepalive=60)
client.loop_forever()

Observe que o download roda dentro do callback de mensagem, o que bloqueia o loop de rede. Isso é aceitável para modelos pequenos e para um ambiente de testes; em produção, passe o trabalho para uma thread de worker para que os keepalives continuem durante downloads longos. O seu processo de inferência deve abrir o modelo pelo symlink active e recarregá-lo quando ele mudar, por exemplo observando o arquivo de estado.

Para disparar um rollout durante os testes, publique uma mensagem de estado desejado retida:

mosquitto_pub -h broker.connectmq.com -p 1883 -i deployer-a91e4d -r -q 1 \
  -t "connectmq/demo/a91e4d/fleet/plant1/gw-07/model/desired" \
  -f desired.json

O servidor MQTT público serve para experimentar o fluxo, mas lembre-se de que qualquer pessoa pode lê-lo; portanto, nunca coloque ali URLs reais de modelos nem tokens assinados.

Passo 5: faça o rollout em etapas

Nunca envie um modelo novo para a frota inteira de uma só vez. Um rollout em etapas fica assim:

  1. Modo sombra. O novo modelo roda ao lado do antigo em alguns dispositivos e as duas saídas são publicadas, mas apenas o antigo aciona ações. Compare-as offline.
  2. Canary. Publique o estado desejado para um pequeno grupo de dispositivos. Acompanhe o estado informado, a latência de inferência e a distribuição das previsões.
  3. Ondas progressivas. Expanda para grupos maiores, fazendo pausas entre as ondas. Um script de implantação simplesmente escreve o topic de estado desejado para o próximo lote de dispositivos.
  4. Rollout completo. Quando a última onda estiver saudável, torne a nova versão o padrão para os dispositivos provisionados daí em diante.
  5. Rollback. Se as métricas piorarem, publique a versão anterior como estado desejado. Como o dispositivo mantém a última versão em disco, o rollback nem precisa de download.

A associação aos grupos pode ficar no seu serviço de implantação, ou você pode codificá-la nos topics, por exemplo fleet/plant1/canary/model/desired, que um dispositivo do grupo canary assina além do seu próprio topic.

Monitorando o que cada dispositivo executa

Como cada dispositivo publica uma mensagem reported retida, uma única assinatura com wildcard em fleet/+/+/model/reported fornece um inventário completo da frota no momento em que se conecta. Combine isso com a telemetria de previsões para detectar desvios: se a distribuição de classes ou os índices de confiança mudarem bruscamente após uma atualização, provavelmente o modelo está vendo entradas com as quais não foi treinado. Nosso artigo sobre MLOps para IoT aprofunda o monitoramento e os ciclos de retreinamento.

Checklist de produção

  1. Os artefatos são imutáveis, versionados e armazenados via HTTPS.
  2. Todo manifesto traz um checksum SHA-256 e, de preferência, uma assinatura.
  3. Os dispositivos verificam, executam smoke test e fazem a troca de forma atômica, mantendo uma versão anterior.
  4. O estado desejado e o informado ficam em topics MQTT retidos, com Last Will para detecção de dispositivos offline.
  5. Os rollouts seguem a sequência sombra, canary, ondas e completo, com rollback por uma única mensagem.
  6. Os brokers usam TLS na porta 8883 e credenciais por dispositivo.

Para frotas de produção, um broker MQTT gerenciado cuida de certificados, escalabilidade e autenticação, para que a sua equipe possa se concentrar nos modelos. No ConnectMQ, cada dispositivo recebe credenciais com o client ID vinculado a ele, e o explorador de topics em tempo real do portal mostra as mensagens desired e reported à medida que um rollout avança. Para o contexto mais amplo de como combinar modelos e dispositivos conectados, veja nosso guia completo de IA e IoT.

Comece a usar o ConnectMQ

O ConnectMQ é um broker MQTT gerenciado com suporte a MQTT 3.1.1 e 5.0 sobre TCP, TLS e WebSockets, pronto para transportar os sinais de atualização dos seus modelos. Cadastre-se no plano gratuito Serverless, sem cartão de crédito, e configure hoje mesmo o seu primeiro rollout.

Perguntas frequentes

Posso enviar o próprio arquivo do modelo via MQTT?

Você pode dividir arquivos pequenos em pedaços, mas raramente vale a pena. Use o MQTT para o sinal de atualização e para o status, e baixe o artefato via HTTPS, onde downloads retomáveis e cache são padrão.

Como um dispositivo que estava offline recebe a atualização?

Como a mensagem de estado desejado é retida, o broker a entrega imediatamente quando o dispositivo se reconecta e faz a assinatura. O dispositivo então compara as versões e se atualiza, se necessário.

Como faço o rollback de um modelo ruim?

Publique a versão anterior como estado desejado. Um dispositivo que mantém sua última versão em disco pode voltar a ela instantaneamente, sem baixar nada.

Microcontroladores usam a mesma abordagem?

A sinalização é a mesma, mas o modelo geralmente é compilado no firmware, então o dispositivo executa uma atualização OTA de firmware em vez de trocar um arquivo de modelo.

Artigos relacionados

Todos os artigos →

Seu broker MQTT privado está a poucos cliques de distância

Crie uma conta, receba as credenciais do seu broker e conecte seu primeiro dispositivo — sem servidores para instalar, atualizar ou escalar.