NVIDIA DeepStream
O NVIDIA DeepStream roda a inferência pelo seu elemento nvinfer, que precisa de um grafo ONNX, de um arquivo de configuração correspondente e de um parser de bounding boxes. Colocar deepstream=True na exportação para ONNX escreve os dois primeiros e os liga ao terceiro.
- Flag
export(format="onnx", deepstream=True)- Escreve
- Um grafo ONNX, config_infer_primary_<stem>.txt e <stem>_labels.txt
- Cobertura
- 43 combinações de família e tarefa distribuídas em nove tarefas
- Parser
- NvDsInferParseYolo, do projeto DeepStream-Yolo de Marcos Luciano, licenciado sob MIT. Compilado uma vez por dispositivo. github.com/marcoslucianops/DeepStream-Yolo
- Disponibilidade
- Chega na v1.5.0. Mesclado no dev em 2026-08-08 na pull request 728. pull request 728, issue 648
- Validado em execução
- DeepStream 8.0.0 em uma RTX 5070 Ti, só detecção, 2026-08-08
Disponibilidade
A exportação para DeepStream chega na v1.5.0. Foi mesclada no dev em 2026-08-08
na pull request 728, então uma instalação atual já a tem e não é preciso fixar
nenhum branch.
pip install "libreyolo[onnx]"Se você clonou o branch deepstream-export antes de 2026-08-08, substitua. Esse
branch foi rebaseado e enviado com force-push, e o histórico antigo não tem o
conserto que faz essas exportações rodarem em uma máquina com CUDA.
O que a exportação escreve
model.export(format="onnx", deepstream=True) escreve três arquivos lado a lado.
Para libreyolo9s.pt:
libreyolo9s.onnx, o grafo de detecção, um tensor de saída de forma(batch, num_detections, 6), cada linha[x1, y1, x2, y2, score, class_id]em coordenadas de pixel da entrada da rede.config_infer_primary_libreyolo9s.txt, uma configuração donvinferque carrega as constantes de pré-processamento da família, a contagem de classes, os limiares e a ligação com o parser.libreyolo9s_labels.txt, um nome de classe por linha.
Um arquivo de rótulos aparece sempre que o checkpoint carrega nomes de classe. Os
modelos de profundidade não têm nenhum, então não recebem nem o arquivo nem uma
chave labelfile-path.
A LibreYOLO não emite nenhum .so. O .so que o DeepStream carrega é o parser de
bounding boxes de marcoslucianops/DeepStream-Yolo, compilado uma vez por
dispositivo, e é o mesmo binário para qualquer detector da LibreYOLO que você
apontar para ele. O modelo é o ONNX. Classificação e segmentação semântica não
precisam de parser nenhum, porque o nvinfer faz esse pós-processamento sozinho.
Exportar o modelo
from libreyolo import LibreYOLO9, LibreDFINE # Escreve libreyolo9s.onnx, config_infer_primary_libreyolo9s.txt# e libreyolo9s_labels.txt no diretório de trabalho.LibreYOLO9("libreyolo9s.pt", size="s").export(format="onnx", deepstream=True) # Mantenha cada modelo de detecção em seu próprio diretório: todo config# de detecção nomeia o mesmo arquivo de cache do engine. Veja "Armadilhas conhecidas".LibreDFINE("LibreDFINEs.pt", size="s").export(format="onnx", deepstream=True)model.export( format="onnx", # deepstream=True é rejeitado em todos os outros formatos deepstream=True, conf=0.25, # alimenta pre-cluster-threshold (e classifier-threshold, # segmentation-threshold nessas tarefas) iou=0.45, # alimenta nms-iou-threshold, omitido em cluster-mode=4 batch=1, # alimenta batch-size e o nome do arquivo de cache do engine half=False, # True marca o config como network-mode=2 (build fp16) int8=False, # True marca o config como network-mode=1 dynamic=True, # eixo de batch dinâmico no grafo ONNX imgsz=640, # alimenta infer-dims=3;H;W) # deepstream=True e nms=True são mutuamente exclusivos: o DeepStream roda a# supressão na sua etapa de clustering, então nada é embutido no grafo.curl -L -o LibreDFINEs.pt \ https://huggingface.co/LibreYOLO/LibreDFINEs/resolve/main/LibreDFINEs.ptLibreDFINE._load_weights levanta FileNotFoundError quando o arquivo ainda não
está em disco, sem tentar baixá-lo, então baixe LibreDFINEs.pt você mesmo antes.
Essa lacuna está registrada no
issue #727. Os pesos do YOLO9
são baixados no primeiro uso.
A flag é só de Python. libreyolo export neste branch não tem opção deepstream,
e a CLI monta seus argumentos de exportação a partir de uma lista fixa em vez de
repassar chaves desconhecidas.
Compilar o parser de bounding boxes
A detecção precisa da biblioteca do parser, a segmentação de instâncias precisa de outra, e as demais tarefas não precisam de nenhuma. Duas coisas na imagem do DeepStream 8.0 quebram o comando de build documentado, e ambas são problemas de ambiente, não da LibreYOLO.
A imagem traz cuda, cuda-12, cuda-12.5, cuda-12.8 e cuda-12.9 em
/usr/local. Só cuda-12.5 tem um toolkit completo. Ela também traz
libcublas.so.12 e libcublas.so.12.8.4.1, mas não o libcublas.so sem versão
contra o qual -lcublas resolve. O script abaixo contorna as duas coisas.
set -egit clone --depth 1 https://github.com/marcoslucianops/DeepStream-Yolo.git # /usr/local/cuda-12 nesta imagem é um stub e o build morre nele com# "fatal error: crt/host_defines.h: No such file or directory". Resolva um# toolkit que realmente traga o header; na imagem 8.0 esse é o cuda-12.5.CUDA_DIR=$(readlink -f /usr/local/cuda)[ -f "$CUDA_DIR/include/crt/host_defines.h" ] || \ CUDA_DIR=$(ls -d /usr/local/cuda-*.* | sort -Vr | \ while read d; do [ -f "$d/include/crt/host_defines.h" ] && echo "$d" && break; done) # A imagem traz libcublas.so.12 e libcublas.so.12.8.4.1, mas não o# libcublas.so sem versão de que -lcublas precisa, então a etapa de link falha# com "/usr/bin/ld: cannot find -lcublas". Dê ao linker os nomes que ele quer.mkdir -p /tmp/cudalibsfor lib in cublas cublasLt cudart; do real=$(find /usr/local -name "lib${lib}.so.1*" | grep -v stubs | sort -V | tail -1) ln -sf "$real" "/tmp/cudalibs/lib${lib}.so"doneexport LIBRARY_PATH="/tmp/cudalibs:$LIBRARY_PATH" make -C DeepStream-Yolo/nvdsinfer_custom_impl_Yolo CUDA_VER="${CUDA_DIR##*/cuda-}"git clone --depth 1 https://github.com/marcoslucianops/DeepStream-Yolo-Seg.gitmake -C DeepStream-Yolo-Seg/nvdsinfer_custom_impl_Yolo_seg \ CUDA_VER="${CUDA_DIR##*/cuda-}"Depois aponte custom-lib-path no config gerado para o
libnvdsinfer_custom_impl_Yolo.so compilado. O valor gerado é o caminho relativo
nvdsinfer_custom_impl_Yolo/libnvdsinfer_custom_impl_Yolo.so, que resolve quando
o deepstream-app roda a partir do checkout do DeepStream-Yolo e precisa ser
editado nos outros casos.
Rodar o pipeline
Confira que o container enxerga a GPU antes de gastar tempo com qualquer outra coisa. Essa foi a primeira checagem que a execução de validação fez, em uma placa Blackwell sob WSL2.
docker run --rm --gpus all nvcr.io/nvidia/tritonserver:26.04-py3 \ nvidia-smi --query-gpu=name,driver_version,compute_cap --format=csvOutput
name, driver_version, compute_cap NVIDIA GeForce RTX 5070 Ti, 591.86, 12.0
A execução de validação tocou o deepstream-app com uma fonte de arquivo, sem
sink de display, com o on-screen display ligado e com gie-kitti-output-dir
definido para que as detecções de cada frame caíssem em disco como texto KITTI.
Um config com esses ajustes:
[application]enable-perf-measurement=1perf-measurement-interval-sec=5gie-kitti-output-dir=kitti [tiled-display]enable=0 [source0]enable=1type=3uri=file:///opt/nvidia/deepstream/deepstream/samples/streams/sample_1080p_h264.mp4num-sources=1gpu-id=0 [streammux]gpu-id=0batch-size=1batched-push-timeout=40000width=1920height=1080live-source=0 [primary-gie]enable=1gpu-id=0gie-unique-id=1config-file=config_infer_primary_libreyolo9s.txt [osd]enable=1border-width=2text-size=15 [sink0]enable=1type=1sync=0 [tests]file-loop=0deepstream-app -c deepstream_app_config.txtOutput
App run successful
docker run --rm --gpus all -v "$PWD:/work" -w /work \ nvcr.io/nvidia/deepstream:8.0-samples-multiarch \ bash -c "bash build_parser.sh && deepstream-app -c deepstream_app_config.txt"O nvinfer constrói o engine do TensorRT a partir do ONNX na primeira execução e
o guarda em cache ao lado do modelo, então a primeira execução paga o build do
engine e as seguintes carregam o cache.
O config gerado
Os dois configs abaixo foram escritos pelo exportador para a execução de validação, sem edição depois.
| Chave | YOLO9-s | D-FINE-s |
|---|---|---|
net-scale-factor | 0.003921568627 | 0.003921568627 |
model-color-format | 0 | 0 |
infer-dims | 3;640;640 | 3;640;640 |
maintain-aspect-ratio | 1 | 0 |
symmetric-padding | 0 | 0 |
network-type | 0 | 0 |
num-detected-classes | 80 | 80 |
cluster-mode | 2 | 4 |
parse-bbox-func-name | NvDsInferParseYolo | NvDsInferParseYolo |
pre-cluster-threshold | 0.25 | 0.25 |
nms-iou-threshold | 0.45 | |
topk | 300 | 300 |
Os dois configs diferem em três pontos: maintain-aspect-ratio, cluster-mode, e
se nms-iou-threshold está presente ou não. O config do D-FINE omite essa chave
por completo, que é o que o cluster-mode=4 pede.
Cabeças que emitem no máximo uma predição por objeto recebem cluster-mode=4,
então o DeepStream não roda clustering sobre elas; o clustering fundiria detecções
genuinamente distintas. Isso cobre rfdetr, dfine, deim, deimv2, ec,
rtdetr, rtdetrv2, rtdetrv4 e yolo9_e2e. Cabeças de grid e com âncoras
recebem cluster-mode=2 mais nms-iou-threshold.
Os configs de detecção também carregam
engine-create-func-name=NvDsInferYoloCudaEngineGet, que entrega a construção do
engine à biblioteca do parser. É isso que fixa o nome do arquivo de cache do
engine, e é a origem da colisão descrita nas armadilhas conhecidas.
Tarefas e famílias suportadas
Quarenta e três combinações de família e tarefa exportam.
deepstream_supported_tasks() e deepstream_supported_families(task) em
libreyolo/export/deepstream.py devolvem as mesmas listas em tempo de execução.
| Tarefa | network-type | Biblioteca do parser | Famílias |
|---|---|---|---|
| Detecção | 0 | DeepStream-Yolo | yolo9, yolo9_p2, yolo9_e2e, yolo1, yolo2, yolo3, yolo4, yolo7, yolox, yolonas, rtmdet, picodet, rfdetr, dfine, deim, deimv2, ec, rtdetr, rtdetrv2, rtdetrv4 |
| Classificação | 1 | Nenhuma | mobilenetv4, convnext, efficientnetv2, resnet, dinov2 |
| Segmentação semântica | 2 | Nenhuma | pidnet, eomt, dinov2, lingbotvision |
| Segmentação de instâncias | 3 | DeepStream-Yolo-Seg | rfdetr, dfine, ec |
| Pose | 100 | Nenhuma | yolo9, yolonas, rfdetr, ec |
| Profundidade | 100 | Nenhuma | depth_anything, zipdepth |
| Restauração | 100 | Nenhuma | nafnet, realesrgan, swinir |
| Matting | 100 | Nenhuma | birefnet |
| Gaze | 100 | Nenhuma | l2cs |
network-type=100 quer dizer que o DeepStream não tem pós-processador para a
tarefa. Esses configs definem output-tensor-meta=1, as saídas nativas do grafo
passam sem alteração, e a aplicação as decodifica a partir dos metadados do tensor.
Grafos com várias saídas não dão problema ali: toda camada de saída chega aos
metadados com os mesmos nomes de saída e os mesmos eixos dinâmicos de uma
exportação ONNX comum.
As linhas de segmentação de instâncias são a linha de detecção seguida da máscara
daquela instância, achatada em (netH / 4, netW / 4), que é a resolução que o
parser de segmentação tem fixada no código, como probabilidades para
segmentation-threshold.
Classificação e gaze rodam como inferência secundária. Defina process-mode=2 e
operate-on-gie-id no config gerado para colocar um classificador atrás de um
detector. O gaze é um contrato só de cabeça, um recorte de rosto por entrada,
então precisa de um detector de rostos na frente.
Três famílias estão ausentes de propósito. segformer não está ligada ao contrato
compartilhado de exportação semântica e não consegue exportar para ONNX em formato
nenhum. RTMDet-Ins e YOLO9 têm a exportação de segmentação de instâncias bloqueada
dentro da própria LibreYOLO. depth_anything3 não tem implementação de exportação.
Duas linhas da tabela têm lacunas de checkpoint por trás. Só o checkpoint semântico
l do EoMT está publicado, e a classificação com DINOv2 não tem checkpoint
publicado nenhum, então essa combinação precisa de pesos ajustados por você.
Diferenças de pré-processamento
O nvinfer calcula net-scale-factor * (x - offsets) por canal com uma escala
escalar, que não consegue expressar desvio padrão por canal. As famílias que
precisam de um (rfdetr, ec, os tamanhos de deimv2 com backbone DINO,
rtmdet, picodet e todas as famílias de classificação) têm a normalização
embutida no grafo exportado, e o config gerado alimenta o grafo com o espaço de
entrada bruto correspondente.
A geometria é onde os pipelines em Python da própria LibreYOLO e o nvinfer ainda
divergem:
- Famílias com letterbox (
yolo9,yolox,yolonas,rtmdet,yolo2,yolo3,yolo4,yolo7) preenchem com cinza nativamente. Onvinferpreenche com preto. - A detecção com
yolonasnativamente redimensiona o lado mais longo para 636 dentro do seu canvas de 640. Omaintain-aspect-ratiodonvinferusa os 640 inteiros. - A classificação nativamente redimensiona o lado mais curto e depois recorta pelo
centro. O
nvinferestica o frame ou a ROI do objeto até a entrada da rede, então sujeitos com recorte apertado saem diferentes. - O EoMT nativamente roda tiles de janela deslizante para segmentação semântica. O grafo exportado é um único canvas esticado, o que é mais rápido e menos preciso.
pidnetemite um mapa de classes a 1/8 da resolução de entrada elingbotvisiona 1/16. O DeepStream faz upsampling do mapa de classes para exibição.
A checagem de paridade do ONNX alimenta tensores já pré-processados, então ela confere as saídas do grafo e não pega uma ordem de cor ou uma política de padding erradas no config. Valide com os seus próprios dados antes de fazer deploy de uma carga que exija paridade exata.
Armadilhas conhecidas
Dois modelos de detecção em um diretório carregam o engine um do outro
Todo config de detecção carrega a mesma linha:
model-engine-file=model_b1_gpu0_fp32.engineO construtor de engines do parser exige esse nome base e ele não varia por modelo. Exporte um segundo modelo de detecção para o mesmo diretório e a segunda execução carrega o engine em cache do primeiro modelo. Nada quebra; os boxes é que saem errados. Dê a cada modelo de detecção o seu próprio diretório. A execução de validação teve que isolar o D-FINE em um antes de conseguir testá-lo.
Um box só pode carregar uma classe
O formato de linha do nvinfer é [x1, y1, x2, y2, score, class_id], uma classe
por box, então a exportação colapsa as pontuações de classe no argmax delas. Um box
que o predict reporta sob duas classes sobrevive sob uma. Caso medido: a LibreYOLO
reporta vase 0.773 e bottle 0.383 no mesmo box, e o grafo do DeepStream fica com
vase. Isso decorre do formato de linha do parser e não dá para mudar sem sair
desse contrato, então é comportamento esperado, não uma regressão.
Validado
O deepstream-app rodou até o EOS com App run successful nos dois tipos de
cabeça de detector, sobre o sample_1080p_h264.mp4 que a NVIDIA traz (1443
frames), com os dumps KITTI por frame ligados.
| YOLO9-s | D-FINE-s | |
|---|---|---|
| Tipo de cabeça | grid | um para um |
cluster-mode | 2 | 4 |
maintain-aspect-ratio | 1 | 0 |
| Frames com detecções | 1443 | 1443 |
| Total de detecções | 18031 | 71105 |
Os histogramas de classe sobre os 1443 frames colocam carros em primeiro e pessoas
em segundo nos dois modelos, o que está certo para uma cena de rua. A diferença de
quatro vezes na contagem de detecções é a diferença de cluster-mode fazendo o seu
trabalho: o D-FINE em cluster-mode=4 não roda clustering, então toda query acima
do limiar sobrevive, quase duplicadas incluídas.
Dois modelos treinados de forma independente colocam o objeto dominante no mesmo lugar:
YOLO9 bus [706.72, 0.82, 1916.34, 1062.97] conf 0.965
D-FINE bus [702.73, 2.93, 1916.24, 1069.32] conf 0.965Essa execução estabelece cinco coisas: o TensorRT constrói um engine a partir do
ONNX exportado em sm_120, o nvinfer aceita toda chave do config gerado, o
NvDsInferParseYolo lê o layout do tensor corretamente, os boxes caem em
coordenadas 1920x1080 da resolução de origem, e os rótulos resolvem contra o
arquivo de rótulos gerado.
O ambiente em que ela rodou:
| Componente | Valor |
|---|---|
| SO do host | Windows 11 Pro 26200 |
| GPU | NVIDIA GeForce RTX 5070 Ti, 16 GB |
| Driver | 591.86 |
| Capacidade de computação | 12.0 (Blackwell, sm_120) |
| Runtime de container | Docker Desktop 29.4.3, backend WSL2 |
| Imagem do DeepStream | nvcr.io/nvidia/deepstream:8.0-samples-multiarch |
| Versão do DeepStream | 8.0.0 |
| CUDA do container | 12.8.1 |
| Parser | marcoslucianops/DeepStream-Yolo no HEAD |
Junto com a execução do pipeline, tests/unit/test_deepstream_export.py cobre os
adaptadores de grafo e as chaves do config gerado, e seus 35 testes passam neste
commit.
Não validado
Dito para que o escopo acima não seja lido como mais amplo do que é.
- Jetson e aarch64. O contrato de exportação não depende da arquitetura, mas o pipeline só foi rodado em uma GPU discreta x86.
- Quarenta e uma das 43 combinações. Só detecção com
yolo9e detecção comdfinepassaram pelo DeepStream. Classificação, segmentação semântica, segmentação de instâncias e as tarefas de tensor bruto estão cobertas por testes unitários e checagens de paridade ONNX, não por uma execução do pipeline. - FP16 e INT8. Só
network-mode=0foi exercitado. - Multi-stream e batching. Uma fonte,
batch-size=1. - Acurácia contra um dataset de ground truth. As detecções foram conferidas por plausibilidade semântica e por concordância entre modelos, não pontuadas como mAP através do DeepStream.