OpenCV di Chapter sebelumnya bahkan punya modul dnn untuk menjalankan model neural network — tetapi ketika inferensi machine learning menjadi jantung produk, tim biasanya naik ke engine yang didesain khusus untuk itu: ONNX Runtime. Polanya yang dipakai industri terlihat seperti ini: model dilatih di Python dengan PyTorch atau TensorFlow, diekspor ke format ONNX (Open Neural Network Exchange — format terbuka yang menyimpan model sebagai graf operasi tensor), lalu dijalankan di aplikasi C++ produksi dengan ONNX Runtime sebagai engine-nya. C++ menjadi pilihan alami untuk sisi deployment: kontrol memori yang ketat, latensi yang bisa diprediksi, dan tidak ada interpreter yang perlu diawasi. Pada Chapter ini kita bedah konsep intinya — environment, session, input/output tensor — memandang pola C API-nya, lalu menjalankan pipeline inferensi mini yang strukturnya identik.

Konsep Inti: Environment, Session, Tensor #

Tiga object menopang semuanya. OrtEnv adalah lingkungan logging yang dibuat sekali per proses; OrtSession adalah model yang sudah dimuat dari file .onnx beserta graf operasinya — dibuat sekali, lalu dipanggil ribuan kali; dan tensor (di C API berwujud OrtValue) adalah data berbentuk multidimensi — misalnya {1, 4} berarti satu sampel dengan empat fitur. Alur inferensinya selalu sama: isi tensor input dengan angka, panggil Run dengan nama input dan output (nama-nama ini ditulis di dalam model, dan bisa diintrogasi lewat GetInputNameAllocated), lalu baca tensor output. Biaya pembuatan session mahal — parsing model dan optimasi graf — sehingga aplikasi nyata membuatnya sekali saat startup dan menggunakannya untuk seluruh umur proses.

Peran lain ONNX Runtime adalah Execution Provider (EP): backend yang benar-benar mengeksekusi operasi. EP CPU tersedia di semua platform; EP CUDA mengalihkan perhitungan ke GPU NVIDIA; EP TensorRT, OpenVINO, dan DirectML mengoptimalkan untuk perangkat masing-masing. Kode pemanggil tidak berubah — yang berubah hanyalah konfigurasi session.

C API dalam Bentuk Paling Padat #

C API ONNX Runtime bekerja dengan handle dan struct fungsi — pola yang sudah familier dari Chapter C ABI dan C++ ABI:

cpp Pola C API ONNX Runtime (ilustrasi)
#include <onnxruntime/core/session/onnxruntime_c_api.h>

const OrtApi* ort = OrtGetApiBase()->GetApi(ORT_API_VERSION);

OrtEnv* env = nullptr;
ort->CreateEnv(ORT_LOGGING_LEVEL_WARNING, "aplikasi", &env);  // 1) sekali per proses

OrtSessionOptions* opsi = nullptr;
ort->CreateSessionOptions(&opsi);
OrtSession* sesi = nullptr;
ort->CreateSession(env, "model.onnx", opsi, &sesi);           // 2) muat model sekali

// 3) siapkan tensor input (OrtValue) + nama input/output milik model
const char* namaInput[]  = {"input"};
const char* namaOutput[] = {"logits"};

OrtValue* keluaran = nullptr;
ort->Run(sesi, nullptr, namaInput, &input, 1,
         namaOutput, 1, &keluaran);                           // 4) inferensi
// baca isi keluaran; setiap object yang dibuat wajib di-Release()

C API ini tidak terinstal di lingkungan course, sehingga blok di atas kita tampilkan tanpa klaim output. Dua hal yang paling sering melukit pemula: lupa memanggil Release() pada setiap object yang dibuat (C API tidak kenal RAII — bungkus sendiri dengan smart pointer bila perlu), dan salah nama input/output — kesalahan keduanya langsung menjadi exception/OrtStatus yang perlu diperiksa.

Pipeline Inferensi Mini yang Teruji #

Sekarang kita tiru seluruh alur itu dengan “model” buatan sendiri — satu layer dense dengan bobot tetap — sehingga pipeline-nya benar-benar berjalan dan output-nya deterministik:

cpp
// inferensi-dummy.cpp — pipeline inferensi machine learning versi mini.
// Strukturnya mengikuti alur C API ONNX Runtime: siapkan environment ->
// buat session dari "model" -> isi input tensor -> jalankan -> baca output.
// Modelnya kita buat sendiri: satu layer dense dengan bobot tetap, sehingga
// hasilnya deterministik tanpa file .onnx dan tanpa ONNX Runtime terinstal.
#include <cstdint>
#include <cstdio>
#include <vector>

struct Tensor {
    std::vector<int64_t> bentuk;   // mis. {1, 4} berarti batch 1, 4 fitur
    std::vector<float> data;

    size_t totalElemen() const {
        size_t hasil = 1;
        for (int64_t d : bentuk) hasil *= static_cast<size_t>(d);
        return hasil;
    }
};

// Analogi OrtSession: model yang sudah dimuat dan siap dieksekusi.
class SesiModel {
public:
    SesiModel() {
        // "Membaca model": di ONNX Runtime asli, bobot dibaca dari file .onnx.
        // Di sini bobot layer dense 4->3 ditulis langsung agar self-contained.
        bobot_ = {
            { 0.6f, -0.2f, 0.4f},   // baris 0: bobot dari fitur 0 ke 3 output
            { 0.1f,  0.5f, -0.3f},  // baris 1
            {-0.4f,  0.2f, 0.7f},   // baris 2
            { 0.3f,  0.8f, 0.1f},   // baris 3
        };
        bias_ = {0.05f, -0.10f, 0.20f};
    }

    Tensor run(const Tensor& input) const {
        Tensor keluaran;
        keluaran.bentuk = {1, 3};
        keluaran.data.assign(3, 0.0f);
        for (int j = 0; j < 3; ++j) {
            float jumlah = bias_[j];
            for (int i = 0; i < 4; ++i) {
                jumlah += input.data[i] * bobot_[i][j];
            }
            keluaran.data[j] = jumlah;  // logits, tanpa aktivasi
        }
        return keluaran;
    }

private:
    std::vector<std::vector<float>> bobot_;
    std::vector<float> bias_;
};

int main() {
    // 1) Input tensor: satu sampel dengan 4 fitur numerik.
    Tensor input;
    input.bentuk = {1, 4};
    input.data = {2.0f, 1.0f, 0.5f, -1.0f};

    // 2) Session dibuat sekali, dipakai berkali-kali (pola ONNX Runtime).
    SesiModel sesi;

    // 3) Eksekusi: satu panggilan run() dari input ke output.
    Tensor keluaran = sesi.run(input);

    // 4) Baca output tensor: logits + prediksi kelas (argmax).
    std::printf("input   : [% .2f, % .2f, % .2f, % .2f] (bentuk 1x4)\n",
                input.data[0], input.data[1], input.data[2], input.data[3]);
    std::printf("logits  : [% .2f, % .2f, % .2f] (bentuk 1x3)\n",
                keluaran.data[0], keluaran.data[1], keluaran.data[2]);

    int prediksi = 0;
    for (int j = 1; j < 3; ++j) {
        if (keluaran.data[j] > keluaran.data[prediksi]) prediksi = j;
    }
    std::printf("prediksi: kelas %d (skor tertinggi %.2f)\n",
                prediksi, keluaran.data[prediksi]);
    return 0;
}

Output yang Dihasilkan:

text
input   : [ 2.00,  1.00,  0.50, -1.00] (bentuk 1x4)
logits  : [ 0.85, -0.70,  0.95] (bentuk 1x3)
prediksi: kelas 2 (skor tertinggi 0.95)

Bedah bagian pentingnya:

  1. Baris 10-19 (struct Tensor) — pasangan bentuk (dimensi, persis semantik OrtValue di ONNX Runtime) dan data yang diratakan; totalElemen() menghitung ukuran buffer dari dimensi.
  2. Baris 22-53 (class SesiModel) — analogi OrtSession: model “dimuat” sekali di constructor (di dunia nyata: parsing file .onnx), lalu run() bisa dipanggil berkali-kali tanpa membaca ulang bobot.
  3. Baris 36-48 (run) — forward pass layer dense: untuk tiap output, jumlahkan input[i] * bobot[i][j] plus bias. Ini satu node Gemm di graf ONNX — pekerjaan yang di EP GPU dijalankan ribuan kali lebih cepat, tetapi semantiknya sama persis.
  4. Baris 57-59 (input tensor) — dimensi {1, 4} dan empat nilai fitur; di aplikasi nyata, nilai ini biasanya keluar dari pipeline praproses (mis. konversi gambar menjadi array float, tepat seperti Chapter OpenCV).
  5. Baris 73-76 (argmax) — postprocessing klasik: pilih indeks dengan skor tertinggi. Nilai logits itu sendiri belum probabilitas; bila perlu, softmax mengubahnya — dan itupun juga postprocessing di sisi pemanggil.

Perhatikan strukturnya, bukan modelnya: tensor input dibentuk, session lama dipanggil, tensor output dibaca, lalu keputusan diambil dari angkanya. Ganti SesiModel dengan session ONNX Runtime asli dan sisa kode pipeline Anda tidak berubah.

Deployment dan Alternatifnya #

Mengirim aplikasi ber-ONNX Runtime ke pengguna membawa dua beban yang perlu diperhitungkan: file libonnxruntime.so (atau .dll/.dylib) yang harus ikut terdistribusi per platform — tema penuhnya menanti di Section Deployment dan Distribusi — dan ukuran file model yang bisa mencapai ratusan MB. Keluarga engine sejenis memberi pilihan sesuai perangkat:

EngineTitik kuatCocok untuk
ONNX RuntimeLintas platform, banyak EP, format terbukaDefault deployment umum
TensorRTOptimasi maksimal di GPU NVIDIAServer inferensi ber-GPU
libtorchEkosistem PyTorch asli, model belum dieksporTim yang seluruh stack-nya PyTorch
OpenVINOOptimasi di CPU/GPU Intel, termasuk edgePerangkat Intel

Setelah melintasi Python, SQLite, socket, JSON, OpenCV, dan engine machine learning — semuanya library C dengan gaya API-nya masing-masing — satu kebutuhan mengemuka pada semua chapter ini: kode kita harus berjalan di Linux, Windows, dan macOS dengan compiler yang berbeda-beda. Chapter penutup section ini merangkum disiplin yang membuat program C++ bertahan lintas platform.

Referensi: ONNX Runtime Documentation , ONNX — Open Neural Network Exchange .