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:
- WebAssembly (Wasm): Untuk eksekusi CPU yang cepat.
- WebGL: Untuk memanfaatkan GPU perangkat dalam komputasi paralel.
- WebGPU: API grafis dan komputasi next-gen yang lebih modern dan berperforma tinggi (masih dalam pengembangan).
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:
- ONNX Model Zoo: Kumpulan model pre-trained yang siap digunakan (misalnya untuk klasifikasi gambar, deteksi objek).
- Konversi Model: Jika Anda memiliki model TensorFlow, PyTorch, atau framework lain, Anda bisa mengkonversinya ke format ONNX. Banyak tools dan library yang tersedia untuk ini (misalnya
tf2onnxuntuk TensorFlow,torch.onnx.exportuntuk PyTorch). Pastikan model Anda sudah dikonversi dan diletakkan di foldermodel/.
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:
ort.InferenceSession.create(): Ini adalah inti dari ONNX Runtime Web. Ia memuat model ONNX Anda dan menyiapkan session inferensi. Kita secara eksplisit memintaexecutionProviders: ['wasm']untuk memastikan WebAssembly digunakan.preprocessImage(): Fungsi ini mengambil elemen<img>, menggambarnya ke<canvas>, mengubah ukurannya ke dimensi yang diharapkan model (misalnya 224x224 untuk MobileNetV2), lalu menormalisasi nilai piksel dan mengubahnya menjadiFloat32Array. Ini adalah langkah krusial karena setiap model ML memiliki persyaratan input yang spesifik.session.run(): Fungsi ini menjalankan inferensi dengan tensor input yang sudah diproses. Outputnya adalah hasil prediksi dari model.getTopPrediction(): Fungsi sederhana untuk menemukan label dengan probabilitas tertinggi dari hasil inferensi.
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:
- Quantization: Mengurangi presisi angka (misalnya dari Float32 ke Int8) untuk membuat model lebih kecil dan lebih cepat, seringkali dengan sedikit penurunan akurasi.
- Pruning: Menghilangkan koneksi atau neuron yang tidak terlalu penting dalam jaringan.
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