Abdulaziz Akyol

Edge AI'da model hızlandırma: ONNX, TensorRT ve INT8 quantization

Yapay zekâ · Görüntü işleme · Yazılım geliştirme
25 Eylül 2026 · 9 dk okuma · Abdulaziz Akyol

Model hızlandırma, eğitilmiş bir modeli doğruluğunu ölçülebilir sınırlar içinde tutarak hedef donanımda daha düşük gecikme ve daha yüksek işlem hacmiyle (throughput) çalıştırma işidir. Edge’de bu çoğunlukla PyTorch → ONNX → TensorRT ya da ONNX Runtime zinciri ve daha düşük sayısal hassasiyet (FP16, INT8) anlamına gelir.

Görüntünün neden sahada işlendiğini edge mi bulut mu yazısında anlattım; bedeli, sınırlı bir GPU’ya kamera sayısı kadar iş sığdırmak. Python ve YOLO ile gerçek zamanlı tespit yazısındaki bütçe hesabı (“12 kamera × 8 kare = saniyede 96 çıkarım”) bu yazının çıkış noktası: o bütçeye sığmayan modeli sığdırmanın yolları.

Zincir: PyTorch’tan donanıma

AşamaAraçÇıktı
EğitimPyTorch / Ultralytics.pt ağırlıkları
Dışa aktarmatorch.onnx.export, Ultralytics export.onnx grafiği
Nicemleme (isteğe bağlı)NVIDIA ModelOpt, ONNX Runtime quantizationQ/DQ düğümlü .onnx
Derlemetrtexec, ONNX Runtime TensorRT EPDonanıma özgü TensorRT motoru
ÇalıştırmaTensorRT, ONNX RuntimeKutular, skorlar

ONNX ortak sözleşmedir: aynı dosya TensorRT, OpenVINO ya da ONNX Runtime ile çalışabilir. Derlenmiş TensorRT motoru ise yalnızca derlendiği ortama aittir.

Önce sürüm meselesi: TensorRT 11 ve güçlü tipleme

Bu yazıyı yazarken NVIDIA’nın güncel sürümü TensorRT 11.3.0 ve 11.x serisi, edge tarafını doğrudan etkileyen iki değişiklik getirdi.

Birincisi, ağlar artık her zaman güçlü tipli (strongly typed). NVIDIA’nın trtexec geçiş rehberine göre --fp16, --int8, --best ve --calib bayrakları kaldırıldı; kullanılırsa trtexec hata verip çıkıyor. FP16 için model önce ModelOpt AutoCast ile karışık hassasiyete çevriliyor, INT8 için de ModelOpt ile önceden nicemleniyor. Örtük nicemleme ve IInt8Calibrator arayüzü tamamen kaldırıldı.

İkincisi, NVIDIA’nın Jetson geçiş sayfası TensorRT 11.3.0’ın JetPack’i desteklemediğini ve Jetson dağıtımlarının JetPack sürümünün desteklediği TensorRT 10.x’te kalması gerektiğini söylüyor.

TensorRT 10.x (ör. Jetson/JetPack)TensorRT 11.x (x86 sunucu GPU’ları)
FP16trtexec --fp16Model ModelOpt AutoCast ile FP16’ya çevrilir
INT8--int8 --calib=<önbellek> ya da Q/DQ’lu modelYalnızca Q/DQ’lu model (ModelOpt)
Ultralytics quantize=8Eski kalibratörü kullanırModelOpt’u otomatik kurup kullanır

Pratik sonuç: sunucu ve Jetson aynı projedeyse nicemlemeyi modelde yapın. Açık nicemleme (Q/DQ düğümlü ONNX) TensorRT 10.x’te de destekleniyor; iki tarafta aynı ONNX’ten motor derlenebilir. Derleme bayraklarını cihazdaki TensorRT sürümünün belgesinden doğrulayın.

Adım 1: PyTorch’tan ONNX’e dışa aktarmak

Ultralytics modelinde tek satır yeterli. YOLO26’da nms=False, NMS gerektirmeyen uçtan uca başlığı seçiyor ve çıktı (N, 300, 6) biçiminde [x1, y1, x2, y2, skor, sınıf] satırları oluyor; son işlemde NMS yazmanız gerekmiyor. Varsayılan (nms verilmediğinde) ham (N, 4+sınıf, 8400) çıktı geliyor ve NMS sizin sorumluluğunuzda. Ultralytics’in yayımladığı COCO sonuçlarında varsayılan başlık biraz daha yüksek mAP veriyor; yani bu bir hız–doğruluk tercihi ve kendi verinizde ölçülmeli.

Genel bir PyTorch modelinde torch.onnx.export kullanılıyor. PyTorch 2.9’dan beri dynamo=True varsayılan; dinamik boyutlar artık dynamic_axes ile değil dynamic_shapes ile veriliyor (dynamic_axes kullanımdan kaldırılma sürecinde).

"""PyTorch -> ONNX: (A) Ultralytics YOLO modeli, (B) herhangi bir torch.nn.Module."""
import onnx
import torch
from ultralytics import YOLO

# (A) Ultralytics: dinamik eksenler + YOLO26'nın NMS gerektirmeyen uçtan uca başlığı
YOLO("best.pt").export(format="onnx", imgsz=640, dynamic=True, nms=False, simplify=True)
# -> best.onnx; giriş "images" (batch, 3, H, W), çıkış (batch, 300, 6)


# (B) Genel PyTorch modeli: PyTorch 2.9'dan beri dynamo=True varsayılan
class TinyNet(torch.nn.Module):
    def __init__(self):
        super().__init__()
        self.conv = torch.nn.Conv2d(3, 16, 3, padding=1)
        self.head = torch.nn.Linear(16, 4)

    def forward(self, x):
        return self.head(self.conv(x).mean(dim=(2, 3)))


model = TinyNet().eval()
torch.onnx.export(
    model,
    (torch.randn(1, 3, 640, 640),),
    "tinynet.onnx",
    input_names=["images"],
    output_names=["logits"],
    dynamic_shapes=({0: "batch"},),  # yalnız batch dinamik; H/W sabit kalır
    opset_version=18,  # hedef çalışma zamanının desteklediği opset'i seçin
    external_data=False,  # küçük modelde ağırlıklar ayrı .data dosyasına değil, tek dosyaya
)

m = onnx.load("tinynet.onnx")
onnx.checker.check_model(m)
for t in m.graph.input:
    dims = [d.dim_param or d.dim_value for d in t.type.tensor_type.shape.dim]
    print("giriş", t.name, dims)  # örn. giriş images ['batch', 3, 640, 640]

Üç karar var:

  • Dinamik eksen mi, sabit boyut mu? Dinamik batch esneklik verir ama TensorRT’de her dinamik boyut için bir en küçük/en uygun/en büyük aralığı (optimization profile) tanımlamanız gerekir. Kameralar hep 640×640 ile besleniyorsa yükseklik ve genişliği sabit tutmak derlemeyi ve ölçümü basitleştirir.
  • Opset. opset_version, hedef çalışma zamanının desteklediği sürüme göre seçilir; en yenisi her zaman en iyisi değildir. ModelOpt INT8 nicemleme için opset 19 ve üstünü istiyor, düşükse kendisi yükseltiyor.
  • Harici veri. external_data varsayılan olarak açık ve ağırlıkları ayrı bir .data dosyasına yazıyor. Küçük edge modellerinde False yapıp tek dosya taşımak dağıtımı kolaylaştırır.

Adım 2: ONNX Runtime ile çalıştırmak ve execution provider seçmek

ONNX Runtime, grafiği donanıma özgü “execution provider” (EP) katmanları üzerinden çalıştırıyor. Liste sırası önceliktir: bir düğümü ilk EP desteklemiyorsa sıradakine düşer. Sık kullanılanlar:

Execution providerDonanımNot
TensorrtExecutionProviderNVIDIA GPUAlt grafikleri TensorRT motoruna derler; motor önbelleği şart
CUDAExecutionProviderNVIDIA GPUTensorRT’nin desteklemediği düğümler için yedek
OpenVINOExecutionProviderIntel CPU/GPU/NPUIntel tabanlı edge kutuları
CoreMLExecutionProviderAppleGeliştirme makinesinde deneme
CPUExecutionProviderHer yerVarsayılan, her zaman listenin sonunda

ONNX Runtime belgesi TensorRT EP ile birlikte CUDA EP’nin de kaydedilmesini öneriyor; TensorRT’nin desteklemediği düğümler böylece CUDA’da çalışıyor. TensorRT EP ilk açılışta motoru derlediği için oturum oluşturma dakikalar sürebiliyor; trt_engine_cache_enable ile motoru diske yazmak bunu sonraki açılışlarda saniyelere indiriyor. Belgedeki uyumluluk tablosu TensorRT EP’nin TensorRT 10.x ile eşleştiğini gösteriyor; sürümü kurulumdan önce oradan kontrol edin.

"""ONNX Runtime ile YOLO çıkarımı: letterbox ön işleme, execution provider seçimi, son işleme."""
from __future__ import annotations

import cv2
import numpy as np
import onnxruntime as ort


def letterbox(img: np.ndarray, size: int = 640, color=(114, 114, 114)):
    """En-boy oranını koruyarak size x size tuvale yerleştirir; geri dönüşüm için ölçek ve kaydırmayı döner."""
    h, w = img.shape[:2]
    r = min(size / h, size / w)
    nh, nw = round(h * r), round(w * r)
    resized = cv2.resize(img, (nw, nh), interpolation=cv2.INTER_LINEAR)
    top, left = (size - nh) // 2, (size - nw) // 2
    canvas = np.full((size, size, 3), color, dtype=np.uint8)
    canvas[top:top + nh, left:left + nw] = resized
    return canvas, r, (left, top)


def preprocess(frames_bgr: list[np.ndarray], size: int = 640):
    """BGR kareler -> (N, 3, size, size) float32, 0-1 aralığında, RGB. Eğitimdeki ön işlemle aynı olmalı."""
    batch, metas = [], []
    for f in frames_bgr:
        img, r, pad = letterbox(f, size)
        batch.append(img[:, :, ::-1].transpose(2, 0, 1))  # BGR->RGB, HWC->CHW
        metas.append((r, pad))
    x = np.ascontiguousarray(np.stack(batch), dtype=np.float32) / 255.0
    return x, metas


def make_session(model_path: str, use_trt: bool = True, cache_dir: str = "./trt_cache"):
    """Öncelik sırası: TensorRT EP -> CUDA EP -> CPU. Kurulu olmayan EP listeden çıkarılır."""
    available = ort.get_available_providers()
    providers: list = []
    if use_trt and "TensorrtExecutionProvider" in available:
        providers.append(("TensorrtExecutionProvider", {
            "trt_fp16_enable": True,
            "trt_engine_cache_enable": True,  # motoru diske yaz: sonraki açılış dakikalar değil saniyeler sürer
            "trt_engine_cache_path": cache_dir,
            "trt_timing_cache_enable": True,
        }))
    if "CUDAExecutionProvider" in available:
        providers.append("CUDAExecutionProvider")
    providers.append("CPUExecutionProvider")

    so = ort.SessionOptions()
    so.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
    sess = ort.InferenceSession(model_path, sess_options=so, providers=providers)
    print("Etkin EP'ler:", sess.get_providers())  # gerçekten hangisi devrede, mutlaka kontrol edin
    return sess


def postprocess(out: np.ndarray, metas, conf_thres: float = 0.35, iou_thres: float = 0.5):
    """(N, 300, 6) uçtan uca çıktı [x1,y1,x2,y2,skor,sınıf] ya da (N, 4+nc, A) ham çıktı için kutular."""
    results = []
    for i, (r, (left, top)) in enumerate(metas):
        o = out[i]
        if o.ndim == 2 and o.shape[-1] == 6:  # YOLO26 nms=False: NMS gerekmez
            keep = o[:, 4] >= conf_thres
            boxes, scores, cls = o[keep, :4].copy(), o[keep, 4], o[keep, 5].astype(int)
        else:  # ham çıktı: (4+nc, A) -> xywh + sınıf skorları, NMS gerekir
            o = o.T
            cls_scores = o[:, 4:]
            cls = cls_scores.argmax(1)
            scores = cls_scores[np.arange(len(o)), cls]
            keep = scores >= conf_thres
            xywh, scores, cls = o[keep, :4], scores[keep], cls[keep]
            boxes = np.column_stack([xywh[:, 0] - xywh[:, 2] / 2, xywh[:, 1] - xywh[:, 3] / 2,
                                     xywh[:, 0] + xywh[:, 2] / 2, xywh[:, 1] + xywh[:, 3] / 2])
            idx = cv2.dnn.NMSBoxes(xywh_to_tlwh(xywh).tolist(), scores.tolist(), conf_thres, iou_thres)
            idx = np.array(idx, dtype=int).reshape(-1)
            boxes, scores, cls = boxes[idx], scores[idx], cls[idx]
        boxes[:, [0, 2]] = (boxes[:, [0, 2]] - left) / r  # letterbox'ı geri al
        boxes[:, [1, 3]] = (boxes[:, [1, 3]] - top) / r
        results.append((boxes, scores, cls))
    return results


def xywh_to_tlwh(xywh: np.ndarray) -> np.ndarray:
    tlwh = xywh.copy()
    tlwh[:, 0] -= xywh[:, 2] / 2
    tlwh[:, 1] -= xywh[:, 3] / 2
    return tlwh


if __name__ == "__main__":
    import sys

    sess = make_session(sys.argv[1])
    frame = cv2.imread(sys.argv[2])
    x, metas = preprocess([frame])
    inp = sess.get_inputs()[0].name
    out = sess.run(None, {inp: x})[0]
    for boxes, scores, cls in postprocess(out, metas):
        for b, s, c in zip(boxes, scores, cls):
            print(f"sınıf={c} skor={s:.2f} kutu={np.round(b).astype(int).tolist()}")

sess.get_providers() satırını atlamayın: CUDA kütüphaneleri bulunamazsa oturum sessizce CPU’ya düşer.

Adım 3: TensorRT motorunu derlemek

En kısa yol Ultralytics:

from ultralytics import YOLO

YOLO("best.pt").export(format="engine", imgsz=640, quantize=16, nms=False)  # FP16

Ultralytics’in belgesine göre half ve int8 bağımsız değişkenleri quantize=16 ve quantize=8 ile değiştirildi; eski adlar uyarıyla hâlâ kabul ediliyor. TensorRT 11’de Ultralytics FP16 için ModelOpt AutoCast’i, INT8 için ModelOpt açık nicemlemesini kendisi kullanıyor.

Aynı işi trtexec ile açıkça yapmak, derleme hattını kontrol etmek isteyenler için daha şeffaf:

# TensorRT 11.x: önce karışık hassasiyet, sonra hassasiyet bayrağı olmadan derleme
python -m modelopt.onnx.autocast --onnx_path best.onnx --output_path best_fp16.onnx
trtexec --onnx=best_fp16.onnx --saveEngine=best_fp16.engine \
  --minShapes=images:1x3x640x640 --optShapes=images:4x3x640x640 --maxShapes=images:8x3x640x640 \
  --timingCacheFile=trt.timing.cache

# TensorRT 10.x (ör. JetPack): hassasiyet bayrakla verilir
trtexec --onnx=best.onnx --saveEngine=best_fp16.engine --fp16 \
  --minShapes=images:1x3x640x640 --optShapes=images:1x3x640x640 --maxShapes=images:4x3x640x640

Motor dosyası taşınabilir değil: TensorRT onu derlendiği GPU’da profilleyip ayarlıyor. Motoru hedef cihazda ya da aynı GPU modeli, aynı sürücü ve aynı TensorRT sürümüne sahip bir makinede derleyin. Zamanlama önbelleği (--timingCacheFile) de yalnızca aynı donanım ve yazılım yapılandırmasında yeniden kullanılmalı.

Adım 4: INT8 ve kalibrasyon veri seti

Eğitim sonrası nicemleme (post-training quantization, PTQ), her aktivasyon tensörü için bir ölçek değeri bulmak üzere modeli temsilî görüntülerden geçirir. Buradaki kritik kelime “temsilî”. Kalibrasyon seti:

  • Sahadaki kameralardan, farklı saatlerden, gece/IR modu dahil kareler içermeli.
  • Boş sahneler kadar kalabalık sahneleri de kapsamalı.
  • Çıkarımdaki ön işlemden aynen geçmeli: letterbox, BGR→RGB, 0–1 ölçekleme.
  • Eğitim setinin artırılmış (augmented) kopyalarından oluşmamalı.

Ultralytics belgesi NVIDIA’nın en az 500 kalibrasyon görüntüsü önerdiğini aktarıyor; ModelOpt’un örnek deposu da CNN ve ViT modelleri için aynı sayıyı veriyor. Bu bir alt sınır önerisi, kesin kural değil.

"""Sahadan seçilmiş karelerden INT8 kalibrasyon dizisi üretir: (N, 3, 640, 640) float32 -> calib.npy."""
from __future__ import annotations

import random
import sys
from pathlib import Path

import cv2
import numpy as np

from yolo_ort import preprocess

src, out, n = Path(sys.argv[1]), sys.argv[2], int(sys.argv[3]) if len(sys.argv) > 3 else 500
files = sorted(p for p in src.rglob("*") if p.suffix.lower() in {".jpg", ".jpeg", ".png"})
random.seed(0)
files = random.sample(files, min(n, len(files)))  # kameralar, saatler, gece/gündüz karışık olmalı

arr = np.empty((len(files), 3, 640, 640), dtype=np.float32)  # 500 kare için yaklaşık 2,5 GB RAM
for i, f in enumerate(files):
    x, _ = preprocess([cv2.imread(str(f))])  # çıkarımdaki ön işlemin aynısı
    arr[i] = x[0]
np.save(out, arr)
print(f"{len(files)} kare -> {out} {arr.shape}")

500 kare × 3 × 640 × 640 × 4 bayt yaklaşık 2,5 GB eder; bu belleği kalibrasyonu yaptığınız makinede ayırın. Ardından ModelOpt ile nicemleme:

pip install --extra-index-url https://pypi.nvidia.com "nvidia-modelopt[all]"
python -m modelopt.onnx.quantization \
  --onnx_path=best.onnx \
  --quantize_mode=int8 \
  --calibration_data_path=calib.npy \
  --calibration_method=entropy \
  --calibration_shapes=images:1x3x640x640 \
  --output_path=best.int8.onnx
trtexec --onnx=best.int8.onnx --saveEngine=best_int8.engine \
  --minShapes=images:1x3x640x640 --optShapes=images:4x3x640x640 --maxShapes=images:8x3x640x640

Bu komuttaki üç ayrıntı sahada fark yaratıyor. --calibration_data_path verilmezse ModelOpt rastgele veriyle kalibrasyon yapıyor; komut hata vermez ama model sahada bozulur. Model yükseklik ve genişlikte de dinamikse --calibration_shapes vermek gerekiyor; verilmezse bilinmeyen boyutlar 1 kabul ediliyor. Üçüncüsü, --high_precision_dtype varsayılanı fp16: nicemlenmeyen katmanlar FP32 değil FP16 olarak kalıyor.

Jetson’da TensorRT 10.x kullanıyorsanız en az zahmetli yol, dışa aktarmayı cihazın kendisinde yapmak: YOLO("best.pt").export(format="engine", quantize=8, data="saha.yaml"). Ultralytics bu sürümlerde eski kalibratörü kullanıyor ve kalibrasyonun cihaza özgü olduğunu, dışa aktarmanın dağıtım yapılacak cihazda yapılmasını özellikle vurguluyor.

PTQ yetmediğinde bir sonraki adım nicemlemeye duyarlı eğitim (quantization-aware training, QAT). Ultralytics’te train(..., quantize=8) ile önceden eğitilmiş modele ince ayar yapılıyor ve ölçekler kontrol noktasıyla birlikte taşınıyor; bu durumda dışa aktarmada kalibrasyon verisi gerekmiyor.

Adım 5: Doğruluk kaybını ölçmek

INT8, doğruluk kaybı ölçülmeden sahaya çıkmamalı. Yöntem basit ama disiplin ister: aynı doğrulama seti, aynı giriş boyutu, aynı başlık.

"""Aynı doğrulama setinde FP32 / FP16 / INT8 modellerin doğruluğunu karşılaştırır."""
from ultralytics import YOLO

DATA = "saha_val.yaml"  # sahadan etiketlenmiş doğrulama seti (eğitimde kullanılmamış)
MODELS = {"fp32 (pt)": "best.pt", "fp16 (engine)": "best_fp16.engine", "int8 (engine)": "best_int8.engine"}

base = None
for name, path in MODELS.items():
    m = YOLO(path, task="detect").val(data=DATA, imgsz=640, batch=1, plots=False, verbose=False)
    map50_95, map50 = m.box.map, m.box.map50
    base = base if base is not None else map50_95
    print(f"{name:14s} mAP50-95={map50_95:.4f}  mAP50={map50:.4f}  fark={map50_95 - base:+.4f}")

Benim önerim, kabul edilebilir kaybı ölçümden önce yazıya dökmek. İş metriğine bağlayın: kişi saymada günlük sayım hatası, İSG’de kaçırılan ihlal oranı gibi. mAP genel bir gösterge; asıl karar, sahadan kaydedilmiş ve elle etiketlenmiş kliplerde iş metriğiyle verilmeli. Sonuçlara sınıf ve sahne bazında (küçük nesne, gece, kalabalık) ayrı bakın; ortalama iyiyken bir alt grup bozulmuş olabilir. Modelin zamanla neden bozulduğunu ve veri setinin nasıl yönetileceğini YOLO modellerini sahada ayakta tutmak yazısında anlattım.

Kayıp fazlaysa sırasıyla şunları deneyin: kalibrasyon setini genişletin ve çeşitlendirin, entropy ile max kalibrasyon yöntemlerini karşılaştırın, hassas katmanları nicemlemenin dışında bırakın (--nodes_to_exclude, --op_types_to_exclude), QAT’ye geçin ya da o model için FP16’da kalın.

Adım 6: Gecikme ve throughput’u doğru ölçmek

Ölçüm hatası, hızlandırma projelerinin en yaygın sorunu. Kurallar:

  1. Isınma. İlk çağrılar bellek ayırma, çekirdek seçimi ve TensorRT EP’de motor derlemesi içerir; ölçüme katılmaz.
  2. Çok tekrar, yüzdelik. Yüzlerce çağrı ölçün, p50, p95 ve p99’u raporlayın. Sahadaki takılmaları ortalama değil p95/p99 gösterir.
  3. İki ayrı ölçüm. Yalnız model süresi ile kod çözme, ön işleme ve son işleme dahil uçtan uca süre farklı sorulara cevap verir.
  4. Senkronizasyon. PyTorch’ta GPU çağrıları eşzamansızdır; torch.cuda.synchronize() olmadan ölçülen süre yanlıştır.
  5. Ortamı sabitleyin. Güç modu, saat hızları ve sıcaklık sonucu değiştirir. NVIDIA belgesi deterministik ölçüm için saatlerin nvidia-smi -lgc ile sabitlenebileceğini anlatıyor; Jetson’da güç modunu da sabitleyin. Fansız kutularda ölçümü dakikalarca sürdürüp ısıl kısmayı (thermal throttling) görün.
  6. Aynı şeyi karşılaştırın. Başlık (nms), giriş boyutu, hassasiyet ve donanım aynı olmalı.
"""Gecikme ve throughput ölçümü: ısınma, sabit girdi, yüzdelikler (p50/p95/p99)."""
from __future__ import annotations

import sys
import time

import numpy as np
import onnxruntime as ort

from yolo_ort import make_session


def bench(sess: ort.InferenceSession, batch: int, size: int = 640,
          warmup: int = 50, iters: int = 500) -> dict:
    name = sess.get_inputs()[0].name
    x = np.random.rand(batch, 3, size, size).astype(np.float32)
    for _ in range(warmup):  # ilk çağrılar: bellek ayırma, çekirdek seçimi, TensorRT motor derleme
        sess.run(None, {name: x})
    lat = np.empty(iters)
    t_start = time.perf_counter()
    for i in range(iters):
        t0 = time.perf_counter()
        sess.run(None, {name: x})  # run() sonuç CPU'ya kopyalanınca döner; ayrıca senkronizasyon gerekmez
        lat[i] = (time.perf_counter() - t0) * 1000
    total = time.perf_counter() - t_start
    p50, p95, p99 = np.percentile(lat, [50, 95, 99])
    return {"batch": batch, "p50_ms": p50, "p95_ms": p95, "p99_ms": p99,
            "img_per_s": batch * iters / total}


if __name__ == "__main__":
    session = make_session(sys.argv[1])
    for b in (1, 2, 4, 8):
        r = bench(session, b)
        print(f"batch={r['batch']:>2}  p50={r['p50_ms']:.2f} ms  p95={r['p95_ms']:.2f} ms  "
              f"p99={r['p99_ms']:.2f} ms  {r['img_per_s']:.1f} görüntü/sn")

trtexec aynı işi motor düzeyinde yapar ve varsayılan olarak en az 200 ms ısınma ile en az 10 çağrı ya da 3 saniye çalışır:

trtexec --loadEngine=best_int8.engine --shapes=images:1x3x640x640 \
  --warmUp=500 --duration=60

Çıktıda Throughput (saniyedeki çıkarım), Latency satırındaki median ve percentile(95%) değerleri ile GPU Compute Time önemli. TensorRT 11’de ana makine–GPU veri aktarımı varsayılan olarak ölçüme dahil değil; gerçek hattı temsil etsin istiyorsanız --includeDataTransfers ekleyin. Enqueue Time, GPU Compute Time’dan uzunsa darboğaz GPU değil, ana makine tarafıdır.

Batch ve çözünürlüğün etkisi

Batch, GPU’yu daha verimli doldurur ve saniyedeki görüntü sayısını artırır; ama batch’in dolması beklendiği için tek görüntünün gecikmesi de artar. Anında tepki gereken bir güvenlik kuralında batch 1, birkaç yüz milisaniye gecikmeyi kaldıran bir sayım sisteminde daha büyük batch mantıklı. Yukarıdaki ölçüm betiği batch 1, 2, 4 ve 8 için iki değeri yan yana veriyor; kararınızı bu tabloya göre verin.

Çözünürlükte hesap daha kaba ama yol gösterici: 640×640’tan 1280×1280’e çıkmak piksel sayısını dört katına çıkarır ve evrişimli katmanların işi de kabaca bu oranda büyür. Çözünürlüğü artırmak yerine uzak nesnelerin olduğu bölgeyi kırpıp ayrıca işlemek çoğu zaman daha ucuzdur.

Donanım seçimi için genel kriterler

Belirli bir cihaz için fiyat/performans iddiası yazmayacağım; kendi modelinizle ölçülmeden anlamsız. Baktığım kriterler:

  • Desteklenen hassasiyetler. FP16 ve INT8’i donanımsal olarak hızlandıran birimler var mı? Yoksa nicemleme hız kazandırmayabilir; ONNX Runtime belgesi de eski donanımda nicemlemenin yarar sağlamayabileceğini not ediyor.
  • Bellek. Kamera başına süreç çalıştıracaksanız her süreç modeli ayrıca yükler; toplam bellek, kamera sayısını sınırlayan ilk etken olabilir.
  • Donanım video çözücü. Kamera sayısı arttıkça H.264/H.265 çözme yükü çıkarım kadar önemli hale gelir; donanım çözücünün eşzamanlı akış kapasitesine bakın.
  • Yazılım yol haritası. Cihazın hangi TensorRT sürümünü ne zamana kadar alacağı önemli; TensorRT 11’in JetPack desteğinin olmaması bunun güncel örneği. Ultralytics belgesi ayrıca TensorRT 10.7’nin DLA desteği olan son sürüm olduğunu aktarıyor.
  • Güç ve ısı. Kapalı panodaki fansız kutu, masadaki test sonucunu uzun süre koruyamayabilir.
  • Ekosistem ve tedarik. Çalışma zamanınızın o donanım için EP’si var mı, cihaz birkaç yıl sonra da bulunabilecek mi?

Adaylardan birini ödünç alın, kendi modelinizi yukarıdaki betik ve trtexec ile ölçün, kararı o tabloya göre verin. Derlenen motoru konteynere koyup sahaya dağıtmayı Docker ve Kubernetes ile GPU servislerini ölçeklemek yazısında anlattım.

Kontrol listesi

  • Hedef cihazdaki TensorRT sürümü belli (10.x mi 11.x mi) ve derleme hattı buna göre seçildi.
  • ONNX dışa aktarımında opset, dinamik eksenler ve harici veri bilinçli seçildi; giriş/çıkış şekilleri kontrol edildi.
  • ONNX Runtime’da etkin EP’ler loglanıyor; TensorRT EP motor önbelleği açık.
  • Motor hedef cihazda (ya da birebir aynısında) derlendi.
  • Kalibrasyon seti sahadan, gece/gündüz ve kalabalık/boş sahneleri kapsıyor, çıkarımla aynı ön işlemden geçti.
  • FP32, FP16 ve INT8 aynı doğrulama setinde ve iş metriğinde karşılaştırıldı; kabul sınırı önceden yazıldı.
  • Gecikme ısınmadan sonra p50/p95/p99 olarak, batch 1 ve hedef batch için ölçüldü.
  • Ölçüm sırasında güç modu, saat ve sıcaklık kayıt altına alındı.

Sık sorulan sorular

ONNX nedir ve neden kullanılır?

ONNX, makine öğrenmesi modellerini çerçeveden bağımsız bir grafik olarak saklayan açık bir biçimdir. PyTorch'ta eğitilen model ONNX'e aktarıldıktan sonra ONNX Runtime, TensorRT ya da OpenVINO gibi farklı çalışma zamanlarında, hedef donanıma göre optimize edilerek çalıştırılabilir.

TensorRT ile INT8 quantization nasıl yapılır?

TensorRT 11'de model, NVIDIA ModelOpt ile temsilî bir kalibrasyon setine göre önceden nicemlenir ve ONNX grafiğine Q/DQ düğümleri eklenir; ardından trtexec ile motor derlenir. TensorRT 10.x kullanan Jetson gibi cihazlarda eski kalibratör yolu da çalışır; Ultralytics export(format="engine", quantize=8, data=...) her iki durumu da kendisi yönetir.

INT8 quantization doğruluğu ne kadar düşürür?

Model, veri ve kalibrasyon setine göre değişir; genel bir oran vermek doğru olmaz. Doğru yöntem FP32, FP16 ve INT8 modelleri sahadan etiketlenmiş aynı doğrulama setinde ölçmek, kabul edilebilir kaybı önceden belirlemek ve kayıp fazlaysa kalibrasyon setini iyileştirmek, hassas katmanları dışarıda bırakmak ya da QAT'ye geçmektir.

Model gecikmesi doğru nasıl ölçülür?

Önce ısınma turları çalıştırılır, sonra sabit girdiyle yüzlerce çağrı ölçülüp p50, p95 ve p99 yüzdelikleri raporlanır. Yalnız model süresi ile kod çözme, ön ve son işleme dahil uçtan uca süre ayrı ölçülür; güç modu, saat hızları ve sıcaklık sabit tutulur.

TensorRT motor dosyası başka bir makinede çalışır mı?

Güvenilir biçimde çalışmaz. TensorRT motoru derlendiği GPU'ya ve TensorRT sürümüne göre ayarlanır; Ultralytics da .engine dosyasının taşınabilir bir model biçimi olarak görülmemesini söylüyor. Motoru hedef cihazda ya da birebir aynı donanım ve yazılımda derleyin.

Kaynaklar

  1. NVIDIA TensorRT — Migrating trtexec Usage from TensorRT 10.x to 11.x docs.nvidia.com
  2. NVIDIA TensorRT — Performance Benchmarking using trtexec docs.nvidia.com
  3. NVIDIA Model Optimizer — ONNX Quantization (PTQ) guide nvidia.github.io
  4. ONNX Runtime — Execution Providers onnxruntime.ai
  5. Ultralytics Docs — TensorRT Export docs.ultralytics.com
  6. PyTorch — torch.onnx.export (torch.export-based exporter) docs.pytorch.org

ONNXTensorRTINT8ONNX RuntimeJetsonEdge AIYOLO Markdown sürümü

İletişim

Konuşalım.