MACHINE-LEARNING ONNX-RUNTIME WEBASSEMBLY BROWSER-AI FRONTEND-AI AI-ML WEB-DEVELOPMENT JAVASCRIPT PERFORMANCE INFERENCE ON-DEVICE-ML DEEP-LEARNING MODEL-DEPLOYMENT WEB-PERFORMANCE PRIVACY OFFLINE-FIRST

Membangun Inferensi Machine Learning di Browser dengan ONNX Runtime Web dan WebAssembly

⏱️ 15 menit baca
👨‍💻

Membangun Inferensi Machine Learning di Browser dengan ONNX Runtime Web dan WebAssembly

1. Pendahuluan

Di era digital yang serba cepat ini, aplikasi web terus berevolusi, tidak hanya menjadi lebih interaktif tetapi juga lebih cerdas. Salah satu tren paling menarik adalah kemampuan untuk menjalankan model Machine Learning (ML) langsung di browser. Bayangkan aplikasi yang bisa mengklasifikasikan gambar, mendeteksi objek, atau bahkan memproses suara tanpa perlu mengirim data ke server. Keren, kan?

Kemampuan ini membuka pintu bagi berbagai use case baru yang mengutamakan privasi, kecepatan, dan kemampuan offline. Namun, tantangan utamanya adalah performa dan ukuran model ML yang seringkali besar. Di sinilah ONNX Runtime Web dan WebAssembly (Wasm) masuk sebagai kombinasi yang sangat powerful.

Artikel ini akan memandu Anda memahami mengapa inferensi ML di browser itu penting, bagaimana ONNX Runtime Web dan WebAssembly bekerja sama, dan memberikan contoh praktis untuk membangun aplikasi cerdas Anda sendiri. Mari kita mulai! 🚀

2. Mengapa Inferensi ML di Browser Penting?

Membawa inferensi Machine Learning langsung ke sisi klien (browser) bukan sekadar gimmick, tetapi menawarkan beberapa keuntungan signifikan yang bisa menjadi game-changer untuk aplikasi web Anda:

✅ Privasi Data Pengguna

📌 Data Tetap di Tangan Pengguna: Ketika inferensi berjalan di browser, data sensitif pengguna (seperti gambar pribadi atau rekaman suara) tidak perlu dikirim ke server. Ini sangat krusial untuk aplikasi yang berurusan dengan data pribadi dan mematuhi regulasi seperti GDPR atau UU PDP. Privasi menjadi nilai jual utama aplikasi Anda.

✅ Latensi Rendah dan Respon Instan

🎯 Tanpa Perjalanan ke Server: Bayangkan Anda ingin mengklasifikasikan gambar. Jika harus mengirim gambar ke server, ada latensi jaringan yang tak terhindarkan. Dengan inferensi di browser, hasilnya bisa didapatkan dalam hitungan milidetik, memberikan pengalaman pengguna yang jauh lebih responsif dan instan. Ini seperti memiliki superkomputer mini di perangkat pengguna!

✅ Aplikasi Offline-First

💡 Berfungsi Tanpa Internet: Model yang di-cache di browser bisa berjalan sepenuhnya offline. Ini ideal untuk Progressive Web Apps (PWA) atau aplikasi yang digunakan di area dengan koneksi internet yang tidak stabil, seperti aplikasi di lapangan atau di perjalanan.

✅ Mengurangi Beban Server dan Biaya

💰 Hemat Sumber Daya Backend: Setiap inferensi yang dilakukan di browser berarti satu request komputasi lebih sedikit untuk server Anda. Ini dapat secara signifikan mengurangi beban server, biaya infrastruktur, dan kompleksitas backend, terutama untuk aplikasi dengan jutaan pengguna.

✅ Personalisasi yang Lebih Baik

Model yang Lebih Adaptif: Dengan inferensi di browser, Anda berpotensi menjalankan model yang lebih personal untuk setiap pengguna, atau bahkan melakukan fine-tuning model kecil secara lokal berdasarkan preferensi pengguna, tanpa perlu infrastruktur server yang kompleks.

3. Mengenal ONNX Runtime Web dan WebAssembly

Untuk memahami bagaimana kita bisa mencapai semua manfaat di atas, kita perlu menyelami dua teknologi kunci: ONNX Runtime Web dan WebAssembly.

Apa Itu ONNX (Open Neural Network Exchange)?

ONNX adalah format terbuka yang dirancang untuk merepresentasikan model machine learning. Analogi mudahnya, jika Anda membuat dokumen di Microsoft Word, Anda bisa membukanya di Google Docs atau LibreOffice karena ada standar format file (.docx). Begitu pula dengan ONNX.

Berbagai framework ML seperti TensorFlow, PyTorch, Scikit-learn, dan lainnya dapat mengkonversi model mereka ke format ONNX. Ini berarti Anda bisa melatih model di PyTorch, mengkonversinya ke ONNX, lalu menjalankannya di lingkungan yang berbeda, termasuk browser.

Apa Itu ONNX Runtime?

ONNX Runtime adalah mesin inferensi berkinerja tinggi yang dikembangkan oleh Microsoft untuk menjalankan model dalam format ONNX. Ia dirancang untuk efisiensi dan kompatibilitas di berbagai platform, dari server hingga perangkat edge.

ONNX Runtime Web: Kekuatan ONNX di Browser Anda

ONNX Runtime Web adalah versi ONNX Runtime yang dioptimalkan khusus untuk lingkungan browser. Ia dapat memanfaatkan berbagai backend komputasi yang tersedia di browser, termasuk:

Dengan ONNX Runtime Web, Anda bisa memuat model ONNX dan menjalankannya dengan performa mendekati native langsung di JavaScript.

WebAssembly (Wasm): Otot di Balik Performa

WebAssembly adalah format instruksi biner tingkat rendah yang dirancang untuk eksekusi yang sangat cepat di browser. Bayangkan Wasm sebagai “bahasa mesin” untuk web. Kode yang ditulis dalam bahasa seperti C, C++, Rust, atau Go dapat dikompilasi ke Wasm dan dijalankan di browser dengan performa yang jauh lebih tinggi daripada JavaScript murni untuk tugas-tugas komputasi berat.

Bagaimana Keduanya Bekerja Sama? ONNX Runtime Web secara cerdas memilih backend terbaik yang tersedia. Untuk komputasi CPU, ia akan menggunakan implementasi yang dikompilasi ke WebAssembly. Ini memungkinkan ONNX Runtime Web menjalankan operasi tensor dan perhitungan ML yang kompleks dengan kecepatan luar biasa, memanfaatkan kekuatan Wasm untuk kinerja mendekati native. Jika perangkat mendukung WebGL atau WebGPU, ONNX Runtime Web juga bisa mengalihkannya ke GPU untuk akselerasi lebih lanjut.

4. Persiapan dan Konfigurasi Awal

Mari kita siapkan lingkungan pengembangan Anda untuk menjalankan inferensi ML di browser.

1. Instalasi ONNX Runtime Web

Pertama, Anda perlu menginstal library onnxruntime-web ke proyek JavaScript/TypeScript Anda.

npm install onnxruntime-web
# atau
yarn add onnxruntime-web

2. Struktur Proyek Sederhana

Buat struktur file dasar seperti ini:

my-ml-app/
├── index.html
├── main.js
└── model/
    └── my_model.onnx # File model ONNX Anda

3. Mendapatkan Model ONNX

Anda bisa mendapatkan model ONNX dengan beberapa cara:

4. Pentingnya SharedArrayBuffer dan Cross-Origin Isolation

⚠️ Perhatian Penting: Untuk performa terbaik dan untuk memungkinkan ONNX Runtime Web memanfaatkan WebAssembly secara penuh (terutama fitur multithreading-nya), browser memerlukan akses ke SharedArrayBuffer. Ini adalah fitur JavaScript yang memungkinkan Web Workers berbagi memori.

Namun, SharedArrayBuffer memerlukan lingkungan Cross-Origin Isolated. Ini berarti server Anda harus mengirimkan HTTP headers tertentu:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

Anda bisa mengaktifkan ini di server development lokal Anda (misalnya menggunakan http-server dengan opsi CORS atau konfigurasi webpack-dev-server). Untuk produksi, pastikan server Anda menyertakan header ini.

Contoh index.html dengan header di <head> (ini hanya untuk development, untuk produksi harus dari server):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>ML Inferensi di Browser</title>
    <!-- Untuk development, bisa tambahkan header ini di meta, tapi lebih baik via server -->
    <meta http-equiv="Cross-Origin-Opener-Policy" content="same-origin">
    <meta http-equiv="Cross-Origin-Embedder-Policy" content="require-corp">
    <style>
        body { font-family: sans-serif; display: flex; flex-direction: column; align-items: center; margin-top: 20px; }
        #inputImage { max-width: 300px; margin-bottom: 20px; border: 1px solid #ccc; }
        #output { font-weight: bold; margin-top: 20px; }
        button { padding: 10px 20px; font-size: 16px; cursor: pointer; }
    </style>
</head>
<body>
    <h1>Klasifikasi Gambar di Browser</h1>
    <input type="file" id="imageUpload" accept="image/*">
    <img id="inputImage" src="" alt="Gambar Input" style="display: none;">
    <button id="classifyBtn" style="display: none;">Klasifikasikan Gambar</button>
    <p id="output">Hasil: </p>

    <script src="main.js"></script>
</body>
</html>

5. Contoh Praktis: Inferensi Klasifikasi Gambar Sederhana

Mari kita buat contoh sederhana di mana kita akan mengklasifikasikan gambar yang diunggah pengguna menggunakan model ONNX. Kita akan menggunakan model pre-trained yang sangat ringan untuk demonstrasi.

Asumsi Anda sudah punya model ONNX (misalnya mobilenetv2-7.onnx dari ONNX Model Zoo) di folder model/.

// main.js

import * as ort from 'onnxruntime-web';

const imageUpload = document.getElementById('imageUpload');
const inputImage = document.getElementById('inputImage');
const classifyBtn = document.getElementById('classifyBtn');
const outputElem = document.getElementById('output');

let session; // Variabel untuk menyimpan session inferensi ONNX

// Labels untuk model MobileNetV2 (contoh, sesuaikan dengan model Anda)
const labels = [
    "tench", "goldfish", "great white shark", "tiger shark", "hammerhead shark",
    // ... tambahkan label lain sesuai model Anda
    "zebra", "tiger", "bear", "dog", "cat", "apple", "banana", "car", "airplane"
];

// 1. Inisialisasi ONNX Runtime Session saat halaman dimuat
async function initializeORT() {
    outputElem.innerText = "Memuat model...";
    try {
        // Mode 'wasm' akan menggunakan WebAssembly
        // Untuk performa lebih baik, bisa coba 'webgl' atau 'webgpu' jika didukung
        ort.env.wasm.numThreads = 1; // Sesuaikan jumlah thread jika SharedArrayBuffer diaktifkan
        ort.env.wasm.simd = true; // Aktifkan SIMD untuk percepatan jika didukung

        session = await ort.InferenceSession.create('./model/mobilenetv2-7.onnx', {
            executionProviders: ['wasm'], // Pastikan menggunakan wasm
            graphOptimizationLevel: 'all' // Optimasi graph
        });
        outputElem.innerText = "Model berhasil dimuat!";
        console.log("ONNX Runtime session initialized successfully.");
    } catch (e) {
        outputElem.innerText = `Gagal memuat model: ${e.message}`;
        console.error("Error initializing ONNX Runtime:", e);
    }
}

// 2. Event Listener untuk mengunggah gambar
imageUpload.addEventListener('change', (event) => {
    const file = event.target.files[0];
    if (file) {
        const reader = new FileReader();
        reader.onload = (e) => {
            inputImage.src = e.target.result;
            inputImage.style.display = 'block';
            classifyBtn.style.display = 'block';
            outputElem.innerText = "Hasil: ";
        };
        reader.readAsDataURL(file);
    }
});

// 3. Event Listener untuk tombol klasifikasi
classifyBtn.addEventListener('click', async () => {
    if (!session) {
        outputElem.innerText = "Model belum dimuat. Mohon tunggu.";
        return;
    }
    if (!inputImage.src || inputImage.style.display === 'none') {
        outputElem.innerText = "Mohon unggah gambar terlebih dahulu.";
        return;
    }

    outputElem.innerText = "Mengklasifikasikan...";
    classifyBtn.disabled = true;

    try {
        const tensor = await preprocessImage(inputImage);
        const feeds = { 'input': tensor }; // 'input' harus sesuai dengan nama input model ONNX Anda
        const results = await session.run(feeds);

        const outputTensor = results[session.outputNames[0]]; // Ambil output pertama
        const predictions = Array.from(outputTensor.data);
        const topPrediction = getTopPrediction(predictions);

        outputElem.innerText = `Hasil: ${labels[topPrediction.index]} (Kepercayaan: ${(topPrediction.probability * 100).toFixed(2)}%)`;
    } catch (e) {
        outputElem.innerText = `Gagal mengklasifikasikan: ${e.message}`;
        console.error("Error during inference:", e);
    } finally {
        classifyBtn.disabled = false;
    }
});

// Fungsi Pre-processing Gambar
async function preprocessImage(imgElement) {
    const canvas = document.createElement('canvas');
    const ctx = canvas.getContext('2d');
    const width = 224; // Ukuran input model MobileNetV2
    const height = 224;

    canvas.width = width;
    canvas.height = height;

    // Gambar ke canvas dan resize
    ctx.drawImage(imgElement, 0, 0, width, height);

    // Dapatkan data piksel
    const imageData = ctx.getImageData(0, 0, width, height);
    const { data } = imageData;

    // Konversi ke format tensor yang diharapkan model (misal: [1, 3, H, W] atau [1, H, W, 3])
    // Normalisasi piksel (misal: dari 0-255 ke -1.0 - 1.0 atau 0.0 - 1.0)
    // Ini sangat tergantung pada bagaimana model Anda dilatih!
    const input = new Float32Array(3 * width * height); // Untuk RGB
    let pixelIndex = 0;
    for (let y = 0; y < height; y++) {
        for (let x = 0; x < width; x++) {
            const i = (y * width + x) * 4;
            // Normalisasi ke 0.0 - 1.0 dan urutan R, G, B
            // Beberapa model butuh normalisasi ke -1.0 - 1.0
            input[pixelIndex++] = data[i] / 255.0;     // R
            input[pixelIndex++] = data[i + 1] / 255.0; // G
            input[pixelIndex++] = data[i + 2] / 255.0; // B
        }
    }

    // Buat tensor ONNX Runtime
    // Shape harus sesuai dengan input model ONNX Anda (misal: [1, 3, 224, 224])
    return new ort.Tensor('float32', input, [1, 3, height, width]);
}

// Fungsi Post-processing: Mendapatkan prediksi teratas
function getTopPrediction(predictions) {
    let maxProb = -1;
    let maxIndex = -1;
    for (let i = 0; i < predictions.length; i++) {
        if (predictions[i] > maxProb) {
            maxProb = predictions[i];
            maxIndex = i;
        }
    }
    return { index: maxIndex, probability: maxProb };
}

// Mulai inisialisasi saat script dimuat
initializeORT();

Penjelasan Kode:

6. Tips Optimasi dan Best Practices

Meskipun ONNX Runtime Web dan WebAssembly sudah sangat cepat, ada beberapa hal yang bisa Anda lakukan untuk memaksimalkan performa dan pengalaman pengguna:

1. Pilih dan Optimasi Model yang Ringan

💡 Ukuran Itu Penting: Model ML yang besar akan memperlambat waktu loading dan mengkonsumsi lebih banyak memori. Pilih model yang dirancang untuk perangkat edge atau mobile (misalnya MobileNet, EfficientNet-lite). Anda juga bisa melakukan optimasi model seperti:

2. Manfaatkan Web Workers untuk Offloading

🎯 Jangan Blokir Main Thread: Inferensi ML, terutama untuk model yang lebih besar, bisa menjadi tugas komputasi berat. Jalankan inferensi di Web Workers agar main thread browser tetap responsif. ONNX Runtime Web mendukung ini dengan baik, dan ini sangat penting untuk mencegah UI Anda freeze saat model sedang bekerja.

// Contoh sederhana worker (worker.js)
import * as ort from 'onnxruntime-web';

self.onmessage = async (event) => {
    if (event.data.type === 'load_model') {
        // Load model di worker
        // ...
        self.postMessage({ type: 'model_loaded' });
    } else if (event.data.type === 'run_inference') {
        // Run inference di worker
        // ...
        self.postMessage({ type: 'inference_result', payload: results });
    }
};

3. Pastikan Cross-Origin Isolation Diaktifkan

Kunci Performa Wasm: Seperti yang disebutkan sebelumnya, pastikan server Anda menyajikan header Cross-Origin-Opener-Policy: same-origin dan Cross-Origin-Embedder-Policy: require-corp. Ini mengaktifkan SharedArrayBuffer yang krusial untuk multithreading WebAssembly dan kinerja optimal ONNX Runtime Web.

4. Caching Model dengan Service Worker

📌 Offline dan Cepat: Model ONNX bisa berukuran megabyte bahkan gigabyte. Gunakan Service Workers atau Cache API untuk menyimpan file model di browser. Ini memastikan model hanya diunduh sekali dan tersedia secara instan untuk penggunaan offline atau kunjungan berulang.

5. Fallback untuk Kompatibilitas

Siapkan Rencana B: Meskipun WebAssembly didukung luas, selalu ada kemungkinan browser lama atau lingkungan yang tidak kompatibel. Pertimbangkan untuk menyediakan fallback ringan, misalnya dengan mengirim data ke server untuk inferensi jika inferensi di browser gagal atau tidak didukung.

6. Profiling dan Monitoring

🛠️ Ukur dan Tingkatkan: Gunakan Chrome DevTools untuk memprofiling performa aplikasi Anda. Perhatikan penggunaan CPU, memori, dan waktu eksekusi skrip. Ini akan membantu Anda mengidentifikasi bottleneck dan mengoptimalkan kode pre-processing, post-processing, atau bahkan memilih model yang lebih efisien.

Kesimpulan

Membangun inferensi Machine Learning langsung di