AI/ML Uç Bilişim
TEKNİK REHBER AI/ML UÇ BİLİŞİM ONNX Runtime 2026

ONNX Runtime —
cross-platform inference.

Open Neural Network Exchange ile platform bağımsız model çıkarımı. Execution provider mimarisi, graph optimization, Python ve C API. Raspberry Pi CM4'te YOLOv8 nano ile <50ms nesne tespiti.

00 ONNX nedir? — format ve ekosistem

ONNX (Open Neural Network Exchange), farklı derin öğrenme çerçeveleri arasında model taşınabilirliğini sağlayan açık bir format standardıdır. Microsoft, Facebook ve AWS öncülüğünde geliştirilmiştir.

Ekosistem haritası

  ┌──────────┐  ┌──────────┐  ┌──────────┐  ┌──────────┐
  │ PyTorch  │  │TensorFlow│  │  scikit  │  │  MXNet   │
  │ .pt/.pth │  │ saved_   │  │  -learn  │  │  .params │
  └────┬─────┘  └────┬─────┘  └────┬─────┘  └────┬─────┘
       │              │              │              │
       ▼              ▼              ▼              ▼
  torch.onnx     tf2onnx        sklearn-onnx     mxnet
  .export()      convert        convert          export
       │              │              │              │
       └──────────────┴──────────────┴──────────────┘
                              ↓
                      model.onnx  (FlatBuffer/ProtoBuf)
                              ↓
                    ONNX Runtime (libonnxruntime)
                              ↓
             ┌────────┬───────────┬──────────┬───────┐
             │ CPU EP │ CUDA EP   │ TensorRT │ NNAPI │
             │ x86_64 │ NVIDIA    │  EP      │Android│
             │ ARM    │ GPU       │          │       │
             └────────┴───────────┴──────────┴───────┘
    
ONNX Format
ProtoBuf tabanlı ikili format (.onnx). Hesaplama grafiği (nodes), tensör şekilleri, ağırlık verileri ve op tanımlamalarını içerir. opset sürümü (1-19+) op uyumluluğunu belirler.
ONNX Runtime
Microsoft'un yüksek performanslı çıkarım motoru. C++ çekirdeği üzerine Python, C, C#, Java, JavaScript binding'leri. Hem eğitim hem çıkarım modu desteklenir.
ONNX Model Zoo
github.com/onnx/models adresinde 50+ önceden dönüştürülmüş model: ResNet, VGG, BERT, YOLO serileri, GPT-2. Doğrudan ORT ile kullanılabilir.
opset Versiyonu
Her ONNX sürümü yeni op'lar ekler veya mevcutları değiştirir. ORT, opset 7-19 aralığını destekler. Export sırasında hedef cihazın desteklediği opset'i belirle.
Format Karşılaştırması

ONNX, TFLite'a kıyasla daha zengin op kümesine sahiptir (transformer, LSTM, custom ops). Ancak MCU deployment'ta TFLite Micro daha uygun seçenektir. ONNX/ORT, Linux tabanlı gömülü sistemlerde (Raspberry Pi, i.MX8, RK3588) tercih edilir.

01 ONNX Runtime mimarisi

ORT'un execution provider (EP) mimarisi, aynı ONNX modelini farklı donanım hızlandırıcılarında çalıştırmayı sağlar. Graph optimizer, session oluşturulurken modeli analiz edip dönüştürür.

Dahili pipeline

  model.onnx
       ↓
  [Model Loader]
  ProtoBuf parse → InternalGraph
       ↓
  [Graph Optimizer]
  ├── Level 1 (Basic)    : constant folding, redundant elimination
  ├── Level 2 (Extended) : node fusion (Conv+BN+Relu)
  └── Level 3 (All)      : layout transformation, memory planning
       ↓
  [Execution Provider Dispatch]
  ├── CPU EP   : libomp, MLAS kernel (ARM NEON otomatik)
  ├── CUDA EP  : cuDNN, cuBLAS kernel dispatch
  └── fallback : CPU EP (desteklenmeyen op'lar için)
       ↓
  [Session Run]
  input tensors → kernel execution → output tensors
    
SessionOptions
Session oluşturma parametreleri: graph_optimization_level, inter/intra_op_num_threads, execution_mode (SEQUENTIAL / PARALLEL), log_severity_level.
RunOptions
Her run() çağrısı için: timeout_ms, terminate_flag, run_tag. Paralel async çıkarım için kullanılır.
MLAS (Microsoft Linear Algebra Subprograms)
ORT'un CPU EP'si. ARM NEON, AVX2, AVX-512 otomatik algılar. GEMM ve konvolüsyon için optimize edilmiş assembly kernelleri içerir.

02 Python API — InferenceSession, run, options

ORT Python API'si, prototipleme ve Linux gömülü sistemler için birincil arayüzdür. onnxruntime paketi pip ile kurulur.

Kurulum

bash
# CPU-only (arm64 RPi dahil)
pip install onnxruntime

# GPU destekli (CUDA 12.x)
pip install onnxruntime-gpu

# Gömülü / minimal (ORT Lite)
pip install onnxruntime-extensions  # custom op desteği

Temel çıkarım

ort_basic.py
import onnxruntime as ort
import numpy as np

# Session oluştur — Session options ile optimize et
so = ort.SessionOptions()
so.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
so.intra_op_num_threads = 4        # işlem içi paralellik
so.inter_op_num_threads = 1        # işlemler arası paralellik
so.enable_mem_pattern   = True     # bellek yeniden kullanımı
so.enable_cpu_mem_arena = True

session = ort.InferenceSession(
    "yolov8n.onnx",
    sess_options=so,
    providers=["CPUExecutionProvider"]
)

# Model giriş/çıkış bilgisi
for inp in session.get_inputs():
    print(f"Input : {inp.name}  shape={inp.shape}  type={inp.type}")
for out in session.get_outputs():
    print(f"Output: {out.name}  shape={out.shape}  type={out.type}")

# Çıkarım
img = np.random.rand(1, 3, 640, 640).astype(np.float32)  # NCHW
outputs = session.run(
    output_names=None,   # tümünü döndür
    input_feed={"images": img}
)
print("Çıkış şekli:", outputs[0].shape)  # (1, 84, 8400) YOLOv8 formatı

Gelişmiş: IO Binding ile sıfır kopyalama

ort_iobinding.py
import onnxruntime as ort
import numpy as np

session = ort.InferenceSession("yolov8n.onnx",
    providers=["CPUExecutionProvider"])

# IOBinding: giriş/çıkış tensörlerini önceden bağla
io_binding = session.io_binding()

img = np.random.rand(1, 3, 640, 640).astype(np.float32)

# Girişi ORT OrtValue olarak bağla (kopya yok)
io_binding.bind_input(
    name='images',
    device_type='cpu',
    device_id=0,
    element_type=np.float32,
    shape=img.shape,
    buffer_ptr=img.ctypes.data
)

# Çıkışı CPU'da oluştur
io_binding.bind_output('output0', 'cpu')

session.run_with_iobinding(io_binding)
output = io_binding.copy_outputs_to_cpu()[0]
print("output shape:", output.shape)

03 C API — OrtApi, OrtSession, OrtValue

ORT C API, C++ ortamı olmayan veya ABI uyumluluğu gerektiren gömülü sistemler için kullanılır. Tüm ORT işlemleri tek bir OrtApi struct üzerinden gerçekleşir.

Temel C çıkarım örneği

ort_c_infer.c
#include <onnxruntime_c_api.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#define ORT_ABORT_ON_ERROR(expr) \
  do { OrtStatus* ort_status = (expr); \
    if (ort_status != NULL) { \
      fprintf(stderr, "ORT Error: %s\n", \
              g_ort->GetErrorMessage(ort_status)); \
      g_ort->ReleaseStatus(ort_status); \
      abort(); \
    } \
  } while (0)

static const OrtApi* g_ort = NULL;

int main(void) {
    g_ort = OrtGetApiBase()->GetApi(ORT_API_VERSION);

    /* Environment oluştur */
    OrtEnv* env;
    ORT_ABORT_ON_ERROR(g_ort->CreateEnv(
        ORT_LOGGING_LEVEL_WARNING, "ort_embedded", &env));

    /* Session options */
    OrtSessionOptions* session_options;
    ORT_ABORT_ON_ERROR(g_ort->CreateSessionOptions(&session_options));
    ORT_ABORT_ON_ERROR(g_ort->SetIntraOpNumThreads(session_options, 4));
    ORT_ABORT_ON_ERROR(g_ort->SetSessionGraphOptimizationLevel(
        session_options, ORT_ENABLE_ALL));

    /* Session oluştur */
    OrtSession* session;
    ORT_ABORT_ON_ERROR(g_ort->CreateSession(
        env, "yolov8n.onnx", session_options, &session));

    /* Memory info */
    OrtMemoryInfo* memory_info;
    ORT_ABORT_ON_ERROR(g_ort->CreateCpuMemoryInfo(
        OrtArenaAllocator, OrtMemTypeDefault, &memory_info));

    /* Giriş tensörü oluştur */
    float input_data[1 * 3 * 640 * 640];
    memset(input_data, 0, sizeof(input_data));
    int64_t input_shape[] = {1, 3, 640, 640};
    size_t  input_shape_len = 4;

    OrtValue* input_tensor;
    ORT_ABORT_ON_ERROR(g_ort->CreateTensorWithDataAsOrtValue(
        memory_info,
        input_data, sizeof(input_data),
        input_shape, input_shape_len,
        ONNX_TENSOR_ELEMENT_DATA_TYPE_FLOAT,
        &input_tensor));

    /* Çıkarım */
    const char* input_names[]  = {"images"};
    const char* output_names[] = {"output0"};
    OrtValue*   output_tensor  = NULL;

    ORT_ABORT_ON_ERROR(g_ort->Run(
        session, NULL,
        input_names,  (const OrtValue* const*)&input_tensor, 1,
        output_names, 1, &output_tensor));

    /* Çıkış verisine eriş */
    float* output_data;
    ORT_ABORT_ON_ERROR(g_ort->GetTensorMutableData(
        output_tensor, (void**)&output_data));

    printf("İlk çıkış değeri: %f\n", output_data[0]);

    /* Temizlik */
    g_ort->ReleaseValue(output_tensor);
    g_ort->ReleaseValue(input_tensor);
    g_ort->ReleaseMemoryInfo(memory_info);
    g_ort->ReleaseSession(session);
    g_ort->ReleaseSessionOptions(session_options);
    g_ort->ReleaseEnv(env);
    return 0;
}
Derleme (arm64)
aarch64-linux-gnu-gcc -O2 -o ort_infer ort_c_infer.c \
  -I/opt/ort/include \
  -L/opt/ort/lib -lonnxruntime \
  -Wl,-rpath,/opt/ort/lib

04 Execution providers — CPU, CUDA, OpenVINO, NNAPI

Her Execution Provider (EP), belirli donanım üzerinde op'ları çalıştırır. ORT, desteklenmeyen op'ları otomatik olarak CPU EP'ye devreder (fallback).

CPU EP
Varsayılan EP. ARM NEON, x86 AVX2/AVX-512 otomatik. MLAS kernelleri ile INT8 QDQ modelleri hızlandırır. Her platformda kullanılabilir.
CUDA EP
NVIDIA GPU hızlandırması. cuDNN ve cuBLAS gerektirir. providers=["CUDAExecutionProvider", "CPUExecutionProvider"] ile aktif edilir. CUDA EP desteklemediği op'ları CPU'ya devreder.
TensorRT EP
NVIDIA TensorRT ile derleme zamanı kernel fusion. İlk çalışmada TRT engine derlenir (yavaş), sonraki çalışmalar hızlanır. Jetson platformlarda ideal.
CoreML EP
Apple Silicon ve iOS NPU/GPU hızlandırması. Raspberry Pi senaryosunda kullanılmaz; macOS/iOS gömülü ürünler için.
OpenVINO EP
Intel NPU ve iGPU hızlandırması. Intel UP Squared, NUC ve i.MX8 + eIQ gibi Intel tabanlı gömülü platformlarda kullanılır.
NNAPI EP
Android Neural Networks API. Android 8.1+ telefon ve gömülü Android (Qualcomm, MediaTek NPU). providers=["NnapiExecutionProvider"].

EP seçimi ve fallback akışı

ep_selection.py
import onnxruntime as ort

# Mevcut EP'leri listele
print(ort.get_available_providers())
# ['CPUExecutionProvider'] — RPi4 üzerinde tipik çıktı
# ['CUDAExecutionProvider','CPUExecutionProvider'] — CUDA kurulu

# RPi4 için optimal konfigürasyon
providers = [
    ("CPUExecutionProvider", {
        "arena_extend_strategy": "kNextPowerOfTwo",
        "cpu_memory_arena_cfg": "max_mem:536870912",  # 512 MB limit
    })
]

session = ort.InferenceSession("yolov8n.onnx", providers=providers)

# Hangi EP'nin aktif olduğunu doğrula
for i, node_provider in enumerate(session._provider_options):
    print(f"Node group {i}: {node_provider}")

05 Model optimizasyonu — graph opt, QDQ quantization

ORT'un üç seviyeli graph optimizer ve ONNX Runtime quantization araçları, çıkarım süresini ve bellek kullanımını önemli ölçüde azaltır.

Graph optimization seviyeleri

SeviyeEnumİçerikEtki
Devre dışıORT_DISABLE_ALLHam grafik
BasicORT_ENABLE_BASICConstant folding, redundant elim.%5-10
ExtendedORT_ENABLE_EXTENDEDOp fusion (Conv+BN, Gelu fused)%15-25
AllORT_ENABLE_ALLLayout dönüşümü, memory planner%20-40

QDQ (Quantize-DeQuantize) INT8 quantization

ort_quantize.py
from onnxruntime.quantization import quantize_static, CalibrationDataReader
from onnxruntime.quantization import QuantFormat, QuantType
import numpy as np

class ImageCalibrationReader(CalibrationDataReader):
    def __init__(self, model_path, image_folder, num_samples=100):
        import glob, cv2
        self.images = glob.glob(f"{image_folder}/*.jpg")[:num_samples]
        self.index  = 0

    def get_next(self):
        if self.index >= len(self.images):
            return None
        import cv2
        img = cv2.imread(self.images[self.index])
        img = cv2.resize(img, (640, 640))
        img = img.astype(np.float32) / 255.0
        img = img.transpose(2, 0, 1)[np.newaxis]  # NCHW
        self.index += 1
        return {"images": img}

# Statik INT8 quantization (QDQ format, CPU EP için optimal)
quantize_static(
    model_input="yolov8n.onnx",
    model_output="yolov8n_int8.onnx",
    calibration_data_reader=ImageCalibrationReader(
        "yolov8n.onnx", "./calib_images"),
    quant_format=QuantFormat.QDQ,
    activation_type=QuantType.QInt8,
    weight_type=QuantType.QInt8,
    per_channel=True
)
print("INT8 model oluşturuldu: yolov8n_int8.onnx")

Optimize edilmiş modeli kaydet

save_optimized.py
import onnxruntime as ort

so = ort.SessionOptions()
so.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
# Optimize edilmiş modeli diske kaydet (bir kez çalıştır, sonra yükle)
so.optimized_model_filepath = "yolov8n_optimized.onnx"

session = ort.InferenceSession("yolov8n.onnx", sess_options=so)
# Artık yolov8n_optimized.onnx dosyası mevcut
# Sonraki çalıştırmalarda doğrudan bu dosyayı yükle (session başlatma hızlanır)

06 Cross-compile ve gömülü deploy — minimal build, arm64

Gömülü Linux hedefler için ORT'un minimal build sistemi, gereksiz EP'leri ve op'ları çıkararak küçük bir shared library üretir.

Minimal build sistemi

bash — Host PC (Ubuntu 22.04)
# aarch64 cross-compile toolchain kur
sudo apt-get install -y gcc-aarch64-linux-gnu g++-aarch64-linux-gnu

# ORT kaynak kodu
git clone --depth 1 --branch v1.18.1 \
    https://github.com/microsoft/onnxruntime
cd onnxruntime

# Minimal build: yalnızca CPU EP, QDQ INT8, temel op'lar
python tools/ci_build/build.py \
    --build_dir ./build/arm64 \
    --config Release \
    --arm64 \
    --cmake_extra_defines \
        CMAKE_TOOLCHAIN_FILE=cmake/toolchains/aarch64-gcc.cmake \
        onnxruntime_BUILD_UNIT_TESTS=OFF \
        onnxruntime_MINIMAL_BUILD=ON \
        onnxruntime_DISABLE_EXCEPTIONS=ON \
    --parallel $(nproc) \
    --skip_tests

# Sonuç: build/arm64/Release/libonnxruntime.so (~8 MB minimal)
ls -lh build/arm64/Release/libonnxruntime*

Gerekli op listesi oluşturma

bash — hangi op'lar gerekli?
# Model için gerekli op listesini çıkar
python tools/ci_build/build.py \
    --build_dir ./build/required_ops \
    --config Release \
    --minimal_build extended \
    --enable_reduced_operator_type_support \
    --include_ops_by_model yolov8n.onnx

# Üretilen required_ops.config → minimal build'e dahil edilir

RPi hedefine kopyalama ve test

bash
# Kütüphane ve başlıkları RPi'ye kopyala
scp build/arm64/Release/libonnxruntime.so pi@raspberrypi:/usr/local/lib/
scp -r include/onnxruntime/ pi@raspberrypi:/usr/local/include/

# RPi'de ldconfig güncelle
ssh pi@raspberrypi "sudo ldconfig"

# Uygulama derle (RPi'de doğrudan)
g++ -O2 -o infer ort_c_infer.c \
    -I/usr/local/include/onnxruntime \
    -L/usr/local/lib -lonnxruntime \
    -Wl,-rpath,/usr/local/lib

./infer  # test et

07 PyTorch/TF → ONNX dönüşüm

Model dönüşümü, ONNX ekosisteminin temel adımıdır. torch.onnx.export ve tf2onnx, farklı çerçevelerden ONNX formatına geçişi sağlar.

PyTorch → ONNX

torch_to_onnx.py
import torch
from ultralytics import YOLO

# YOLOv8 nano modeli yükle
model = YOLO("yolov8n.pt")

# Yöntem 1: Ultralytics export (önerilen)
model.export(format="onnx", opset=17,
             simplify=True,
             dynamic=False,
             imgsz=640)
# → yolov8n.onnx

# Yöntem 2: torch.onnx.export (manuel)
dummy_input = torch.randn(1, 3, 640, 640)
torch.onnx.export(
    model.model,
    dummy_input,
    "yolov8n_manual.onnx",
    opset_version=17,
    input_names=["images"],
    output_names=["output0"],
    dynamic_axes={
        "images":  {0: "batch_size"},
        "output0": {0: "batch_size"}
    },
    export_params=True
)

TensorFlow/Keras → ONNX

tf_to_onnx.py
import tf2onnx
import tensorflow as tf

model = tf.keras.applications.MobileNetV2(weights="imagenet")

# tf2onnx ile dönüştür
input_signature = [tf.TensorSpec(
    shape=[None, 224, 224, 3],
    dtype=tf.float32,
    name="input")]

model_proto, _ = tf2onnx.convert.from_keras(
    model,
    input_signature=input_signature,
    opset=17,
    output_path="mobilenetv2.onnx"
)
print("Dönüşüm tamamlandı")

Dönüşüm doğrulama

validate_onnx.py
import onnx, onnxruntime as ort, numpy as np

# ONNX model doğrulama
model = onnx.load("yolov8n.onnx")
onnx.checker.check_model(model)
print("ONNX model geçerli")

# ORT ile çalıştırarak sayısal doğrulama
session = ort.InferenceSession("yolov8n.onnx")
inp = np.random.rand(1, 3, 640, 640).astype(np.float32)
out = session.run(None, {"images": inp})
print(f"Çıkış şekli: {out[0].shape}, min={out[0].min():.4f}")

08 Pratik — YOLOv8 nano RPi CM4 <50ms nesne tespiti

Raspberry Pi CM4 (4GB RAM, 4x Cortex-A72) üzerinde YOLOv8 nano ONNX modeli ile gerçek zamanlı nesne tespiti. Python ve C++ API karşılaştırması.

Pipeline mimarisi

  USB Kamera (V4L2)
       ↓ 640×480 BGR
  [cv2.VideoCapture]
       ↓
  [Ön işleme: resize 640×640, normalize /255, NCHW]
       ↓
  [ORT InferenceSession — CPU EP, 4 thread]
       ↓ (1, 84, 8400) float32
  [NMS post-processing — onnxruntime + numpy]
       ↓
  [Bounding box + label overlay]
       ↓
  [cv2.imshow veya RTSP stream]
    

Python implementasyonu

yolo_detect.py
import cv2, numpy as np, time
import onnxruntime as ort

CONF_THRESH = 0.5
IOU_THRESH  = 0.45
COCO_CLASSES = ["person","bicycle","car","motorbike","bus","truck"]  # kısaltılmış

# Session hazırlama
so = ort.SessionOptions()
so.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
so.intra_op_num_threads = 4
session = ort.InferenceSession("yolov8n.onnx", sess_options=so,
                                providers=["CPUExecutionProvider"])

def preprocess(frame):
    img = cv2.resize(frame, (640, 640))
    img = img[:, :, ::-1].astype(np.float32) / 255.0   # BGR→RGB, normalize
    return img.transpose(2, 0, 1)[np.newaxis]           # HWC→NCHW

def postprocess(output, orig_shape):
    """output: (1,84,8400) — YOLOv8 format"""
    pred = output[0].T  # (8400, 84)
    boxes    = pred[:, :4]
    scores   = pred[:, 4:].max(axis=1)
    class_id = pred[:, 4:].argmax(axis=1)

    mask = scores > CONF_THRESH
    boxes, scores, class_id = boxes[mask], scores[mask], class_id[mask]

    # xywh → xyxy, scale to original
    h, w = orig_shape[:2]
    cx, cy, bw, bh = boxes.T
    x1 = (cx - bw/2) * w / 640
    y1 = (cy - bh/2) * h / 480
    x2 = (cx + bw/2) * w / 640
    y2 = (cy + bh/2) * h / 480
    return np.stack([x1,y1,x2,y2], axis=1), scores, class_id

cap = cv2.VideoCapture(0)
while True:
    ret, frame = cap.read()
    if not ret: break

    inp = preprocess(frame)
    t0  = time.perf_counter()
    out = session.run(None, {"images": inp})
    dt  = (time.perf_counter() - t0) * 1000

    boxes, scores, cls = postprocess(out, frame.shape)
    for (x1,y1,x2,y2), s, c in zip(boxes, scores, cls):
        label = f"{COCO_CLASSES[c] if c < len(COCO_CLASSES) else c} {s:.2f}"
        cv2.rectangle(frame, (int(x1),int(y1)), (int(x2),int(y2)), (0,255,0), 2)
        cv2.putText(frame, label, (int(x1),int(y1)-5),
                    cv2.FONT_HERSHEY_SIMPLEX, 0.5, (0,255,0), 1)

    cv2.putText(frame, f"{dt:.1f}ms", (10,25),
                cv2.FONT_HERSHEY_SIMPLEX, 0.8, (0,0,255), 2)
    cv2.imshow("YOLOv8n ORT", frame)
    if cv2.waitKey(1) == ord('q'): break

Python vs C++ karşılaştırması (RPi CM4)

APISession initÇıkarım (ortalama)Post-processingToplam FPS
Python (4t)2.1 s38 ms8 ms~22 FPS
C++ (4t)1.8 s34 ms3 ms~27 FPS
C++ + INT81.9 s22 ms3 ms~40 FPS
50ms Hedefine Ulaşma

YOLOv8n float32 ile Python/C++ API her iki durumda da 50ms hedefini geçer. INT8 QDQ quantization ile 22ms çıkarım elde edilir ve 50ms hedefinin altına inilir. Daha küçük giriş boyutu (416×416) kullanmak da gecikmeyi %20 azaltır.