Gömülü Sistemler··7 dk okuma
TensorRT ile Model Hızlandırma: FP16 Engine ve Jetson Optimizasyonu
Aynı model, aynı kart, iki kat hız. TensorRT'nin ne yaptığını ve .engine dosyasının neden bavula konup taşınamayacağını anlatıyoruz.
Uçuş öncesi son testte kameradan gelen kare ile ekrandaki kutu arasında gözle görülür bir gecikme varsa, sorun genelde modelin doğruluğunda değil çalıştırma biçimindedir. Bizim durumumuzda YOLO11m, Jetson Orin NX 16GB üzerinde 640 piksel tam-kare çıkarımda 15-20 FPS bandında çalışıyor. Bu, 20 m irtifadan hedef tespiti için yeterli; ama aynı hat üzerinde SIYI A8 mini’den gelen 1080p RTSP akışının çözülmesi, FastMosaic’in kare biriktirmesi ve pymavlink telemetrisi de var. GPU’yu boşa harcadığımız her milisaniye başka bir yerden kesiliyor.
PyTorch ile eğitilmiş bir .pt dosyası aslında bir tarif: her katman ayrı ayrı, Python yorumlayıcısının gözetiminde, genel amaçlı çekirdeklerle çalışıyor. Masaüstü RTX kartında bunun bedeli fark edilmiyor. 10-25 W güç zarfındaki bir modülde ise doğrudan FPS olarak geri dönüyor.
Bu yazıda modeli değiştirmeden hız kazanmanın en doğrudan yolunu, yani NVIDIA TensorRT ile derlenmiş .engine dosyalarını anlatıyoruz. Özellikle de bu konuda en sık kırılan kurala: engine dosyası taşınabilir bir model formatı değildir.
TensorRT tam olarak ne yapıyor
TensorRT bir çıkarım (inference) derleyicisi. Eğitilmiş bir ağ grafiğini alıp hedef GPU için özelleştirilmiş bir çalıştırma planı üretiyor. NVIDIA’nın saydığı temel teknikler şunlar:
- Katman ve tensör kaynaştırma (layer/tensor fusion): Conv + BatchNorm + aktivasyon gibi ardışık işlemler tek bir GPU çekirdeğinde birleştiriliyor. Her birleşme, bir çekirdek başlatma maliyeti ve bir bellek gidiş-dönüşü daha az demek.
- Hassasiyet kalibrasyonu (precision calibration): Ağırlıkların ve ara sonuçların FP16 veya INT8 gibi daha dar sayı biçimlerinde tutulması. Tensor Core’lar bu biçimlerde belirgin biçimde daha hızlı.
- Çekirdek otomatik ayarı (kernel auto-tuning): Her katman için, o karttaki gerçek ölçümlere bakarak aday CUDA çekirdekleri arasından en hızlısı seçiliyor.
- Dinamik tensör belleği: Ara tensörler için bellek, ömürleri çakışmayan katmanlar arasında yeniden kullanılıyor.
Üçüncü madde, yazının ilerleyen kısmındaki kritik kuralın kaynağı. TensorRT tahmin yürütmüyor, hedef GPU üzerinde ölçüm yapıp karar veriyor.
FP32, FP16, INT8: hız ile doğruluğun takası
Ultralytics’in Jetson kılavuzundaki Orin NX 16GB ölçümleri bu takası net gösteriyor. Aşağıdaki tablo, dokümantasyonun referans modeli olan YOLO26n için, 640 piksel girdiyle alınmış değerler — bizim YOLO11m’imiz değil, ama formatlar arası oranlar aynı mantıkla okunur (2026 itibarıyla):
| Format | Çıkarım (ms) | mAP50-95 | Ne zaman tercih edilir |
|---|---|---|---|
PyTorch (.pt) |
13.90 | 0.4799 | Geliştirme, hata ayıklama, hızlı deneme |
| TorchScript | 11.60 | 0.4787 | Python bağımlılığını azaltmak isteyip TensorRT kuramadığınızda |
| ONNX | 14.18 | 0.4763 | Taşınabilirlik gerektiğinde; Jetson’da hız için değil |
| TensorRT FP32 | 7.01 | 0.4770 | Doğrulukta sıfır risk isteyen doğrulama koşuları |
| TensorRT FP16 | 4.13 | 0.4789 | Uçuş yapılandırması — varsayılan tercihimiz |
| TensorRT INT8 | 3.49 | 0.4489 | Kare bütçesi gerçekten sıkışıksa ve doğruluk kaybı ölçüldüyse |
Okunması gereken üç oran var. TensorRT FP32 bile PyTorch’a göre yaklaşık 2 kat hızlı — yani daha ilk adımda, hiçbir hassasiyet düşürmeden kazanç geliyor. FP16’ya geçmek bunun üzerine yaklaşık 1,7 kat daha ekliyor ve bu tabloda mAP’ye ölçülebilir bir zarar vermiyor. INT8 ise FP16 üzerine sadece ~1,2 kat kazandırırken mAP50-95’i 0,479’dan 0,449’a düşürüyor; yaklaşık 3 puanlık bir kayıp.
Bizim tercihimiz FP16. Küçük hedefleri 20 m’den yakalamaya çalışan bir tespit hattında 3 puanlık mAP kaybı, 0,6 ms uğruna verilecek bir taviz değil.
.pt → ONNX → .engine akışı
Ultralytics dışa aktarımı ONNX’i ara grafik olarak kullanıyor; opset argümanı da bu ara adımı ilgilendiriyor. Tek komutla halledilebiliyor:
# Jetson'ın ÜZERİNDE çalıştırılır
yolo export model=best.pt format=engine imgsz=640 quantize=16 batch=1 workspace=4
Python tarafında aynısı:
from ultralytics import YOLO
model = YOLO("best.pt")
model.export(format="engine", imgsz=640, quantize=16, batch=1)
# Çıkarım tarafı hiç değişmiyor
trt = YOLO("best.engine")
results = trt("frame.jpg", conf=0.25)
Önemli bir API değişikliği: eski half=True ve int8=True bayrakları artık quantize=16 ve quantize=8 biçimine taşındı. Eskileri hâlâ kabul ediliyor ama kullanımdan kaldırma uyarısı veriyor (2026 itibarıyla). Sık kullanılan argümanlar:
| Argüman | Varsayılan | İşlevi |
|---|---|---|
quantize |
None |
Hassasiyet: 32, 16 veya 8 |
imgsz |
640 |
Engine’e gömülen giriş boyutu |
batch |
1 |
İzin verilen azami yığın boyutu |
dynamic |
False |
Değişken giriş boyutu profili |
workspace |
None |
Derleme sırasında GiB cinsinden azami çalışma alanı |
data |
None |
INT8 kalibrasyonu için veri seti yapılandırması |
fraction |
1.0 |
Kalibrasyonda kullanılacak veri seti oranı |
Kritik kural: engine dosyası hedef cihazda derlenmeli
Ultralytics dokümantasyonu bunu açıkça yazıyor: TensorRT engine’i derlediği GPU üzerinde profilleyip ayarlıyor, dolayısıyla .engine taşınabilir bir model formatı sayılmamalı. NVIDIA tarafındaki gerekçe daha da nettir — serileştirilmiş plan dosyası varsayılan olarak üç şeye kilitlidir:
- Derlendiği TensorRT sürümüne. Plan içine kaydedilir ve yüklemede kontrol edilir.
- GPU’nun hesaplama yeteneğine (compute capability). Major ve minor sürüm plana yazılır, yükleme anında karta karşı doğrulanır.
- Platforma. İşletim sistemi ve CPU mimarisi de sabittir; x86 masaüstünde derlenen bir plan ARM tabanlı Jetson’da açılmaz.
NVIDIA bu kısıtları gevşetmek için bayraklar sunuyor: VERSION_COMPATIBLE, kAMPERE_PLUS (Ampere ve sonrası tüm mimariler) ve kSAME_COMPUTE_CAPABILITY. Hepsi cihaza özel engine’e kıyasla performans kaybettirebiliyor. Pratik sonuç değişmiyor: derlemeyi uçacak karta yaptırın, .engine dosyasını sürüm kontrolüne koymayın, CI’da üretip sahaya taşımaya çalışmayın.
Sabit mi dinamik mi giriş boyutu
Varsayılan olarak engine sabit girişle derlenir: imgsz ve batch plana gömülür. dynamic=True ile değişken boyut profili açılabilir; INT8 dışa aktarımında bu zaten varsayılan olarak etkinleşiyor.
| Seçenek | Ne zaman | Bedeli |
|---|---|---|
Sabit (dynamic=False) |
Tek ve değişmeyen çıkarım boyutu | Farklı boyut denemek için yeniden derleme |
Dinamik (dynamic=True) |
Tam kare + kırpma/tiling gibi karışık boyutlar | Optimizasyon profili dışındaki boyutlarda daha düşük hız |
batch argümanının azami değeri belirttiğini unutmayın: daha küçük yığınlar kabul edilir, daha büyükleri hata verir.
imgsz çıkarım hattıyla eşleşmeli
Burası bizim de ders çıkardığımız yer. Modeli imgsz=1280 ile eğitiyoruz ama uçuşta tam kareyi 640’ta işliyoruz. Engine’i eğitim değerine bakarak 1280’de derlerseniz, çıkarım hattı 640 gönderdiğinde ya boyut uyuşmazlığı hatası alırsınız ya da sessizce yeniden ölçekleme yapılır ve ölçtüğünüz hız gerçek hız olmaz.
Kural basit: engine’in imgsz değeri, uçuşta gerçekten beslediğiniz tensörün boyutu olmalı. Eğitim çözünürlüğü ile karıştırmayın. Farklı çözünürlükte tiling de yapıyorsanız ya her boyut için ayrı engine derleyin ya da dynamic=True kullanıp profil aralığını bilinçli seçin.
INT8’e geçecekseniz kalibrasyon verisi
INT8, eğitim sonrası nicemlemeyle (post-training quantization) çalışır ve aktivasyon aralıklarını öğrenmek için gerçek veriye ihtiyaç duyar. NVIDIA en az 500 temsili kalibrasyon görüntüsü öneriyor. Ultralytics tarafında bu, data argümanıyla veri seti yapılandırmasını göstererek yapılıyor; fraction ile alt küme seçilebiliyor.
yolo export model=best.pt format=engine imgsz=640 quantize=8 \
data=suas_aerial.yaml fraction=0.5 batch=1
“Temsili” kelimesi burada belirleyici. Kalibrasyon setiniz web’den toplanmış yakın çekim fotoğraflardan oluşuyorsa, 20 m’den çekilmiş küçük hedeflerin aktivasyon aralıkları hiç görülmez ve saha performansı doğrulama metriğinizden kötü çıkar. Kalibrasyon bir .cache dosyası üretir; veri seti veya yığın boyutu belirgin biçimde değiştiğinde bu dosyayı silin, yoksa eski aralıklar yeniden kullanılır.
Sık düşülen tuzaklar
- Masaüstünde derleyip Jetson’a kopyalamak. En yaygın hata. Sonuç ya yükleme hatası ya da mimari uyuşmazlığı; ikisi de uçuş sabahı istemeyeceğiniz sürprizler.
- JetPack yükselttikten sonra engine’i yeniden derlememek. JetPack, TensorRT ve CUDA sürümünü birlikte getirir; eski plan yeni çalışma zamanında açılmayabilir.
- Eğitim
imgsz’si ile dışa aktarımimgsz’sini karıştırmak. Ölçtüğünüz FPS’in hangi çözünürlüğe ait olduğunu bilmeden optimizasyon yapmış olursunuz. - Ölçümü güç modu ayarlamadan almak.
sudo nvpmodel -m 0vesudo jetson_clocksçalıştırılmadan alınan sayılar, kartın gerçek tavanını göstermez.jtopile sıcaklık ve saat frekansını izlemek de faydalı. - DLA’yı otomatik hız kazancı sanmak. Orin NX 16GB’de 614 MHz’de çalışan 2 DLA çekirdeği var, ama DLA öncelikli olarak verim ve enerji verimliliği hedefliyor; GPU’ya kıyasla gecikmeyi düşürmek zorunda değil. Ayrıca TensorRT 11.0’da DLA desteklenmiyor, DLA dışa aktarımı TensorRT 10.x gerektiriyor (2026 itibarıyla).
- INT8’e doğrulama koşusu yapmadan geçmek. Nicemlenmiş engine’i mutlaka kendi doğrulama setinizde tekrar ölçün. Bizim aerial-only doğrulama setimizdeki mAP50 0.950 değeri FP32 modele ait; INT8 sonrası aynı sayıyı varsaymak yanlış olur.
Pratik özet
- Engine’i her zaman uçacak Jetson’ın üzerinde derleyin.
.enginedosyasını depoya koymayın, derleme adımını kurulum betiğine koyun. - Varsayılanınız FP16 (
quantize=16) olsun. Referans ölçümlerde PyTorch’a göre ~3 kat hız, ölçülebilir doğruluk kaybı olmadan geliyor. - Dışa aktarımdaki
imgszvebatchdeğerlerini çıkarım hattınızın gerçekten gönderdiği tensörle birebir eşleyin; eğitim çözünürlüğünü değil. - INT8’i ancak kare bütçeniz gerçekten sıkışıksa değerlendirin, en az 500 temsili görüntüyle kalibre edin ve doğrulama setinde mAP kaybını ölçerek karar verin.
- Her hız ölçümünü
nvpmodel -m 0+jetson_clockssonrası, kart ısındıktan sonra alın. JetPack veya Ultralytics sürümü değiştiğinde engine’i yeniden derleyip ölçümü tekrarlayın.
- tensorrt
- jetson
- yolo11
- fp16
- kenar-bilisim