Giriş
WebGPU, tarayıcıda modern grafik ve hesaplama iş yüklerini donanıma yakın performansla çalıştırmaya imkan tanıyor. Bu makalede, ONNX Runtime Web kullanarak WebGPU üzerinde GPU hızlandırmalı bir görüntü sınıflandırma örneğini adım adım kuracağız. Amaç, ek bir sunucuya gerek kalmadan, tamamen istemci tarafında çalışan ve gerçek zamanlıya yakın tepki veren bir makine öğrenmesi deneyimi elde etmek.
Neden WebGPU ve ONNX Runtime Web?
WebGL veya WASM ile kıyaslandığında WebGPU, paralel iş yüklerini daha verimli planlayarak tensör işlemlerini hızlandırır. ONNX Runtime Web ise aynı model dosyasını hem WebGPU hem WASM üzerinde çalıştırabilen esnek bir yürütme altyapısı sunar. Bu sayede destekli cihazlarda GPU, diğerlerinde otomatik olarak CPU kullanılabilir; uygulama tek kod tabanıyla geniş bir cihaz yelpazesini kapsar.
Ön Koşullar ve Ortam
- Chrome 121+ veya Edge 121+ gibi WebGPU destekli bir tarayıcı kullanın. Firefox’ta Nightly sürümünde dom.webgpu.enabled bayrağıyla çalışır. Safari’de ise güncel sürümler kısmi destek verir. Uygulamayı güvenli kökenden (https veya localhost) barındırın.
- Node.js 18+ ve npm gereklidir. Örnek, vanilla JS ve Vite ile hazırlanacaktır.
Proje Kurulumu
1) Yeni bir proje oluşturun: npm create vite@latest webgpu-ml -- --template vanilla
2) Klasöre girin ve bağımlılıkları ekleyin: cd webgpu-ml && npm i onnxruntime-web
3) Basit bir dizin yapısı oluşturun: /public/model/mobilenetv3.onnx ve etiketler için /public/model/labels.json. ONNX modelini ONNX Model Zoo’dan veya dönüştürdüğünüz bir MobileNetV3 modelinden alabilirsiniz.
Temel Kod: Oturum, Ön İşleme, Çıkarım
Aşağıdaki kod, WebGPU destekli bir tarayıcıda modeli başlatır, bir görüntüyü 224x224’e ölçekler, normalleştirir, tensöre çevirir ve çıkarım yapar. Destek yoksa WASM’e otomatik düşer.
import * as ort from 'onnxruntime-web';
// 1) Oturum oluşturma (GPU + CPU yedekli)
const session = await ort.InferenceSession.create('/model/mobilenetv3.onnx', {
executionProviders: ['webgpu', 'wasm'],
graphOptimizationLevel: 'all'
});
async function loadImageToTensor(imgEl) {
const size = 224;
const canvas = document.createElement('canvas');
canvas.width = size; canvas.height = size;
const ctx = canvas.getContext('2d');
ctx.drawImage(imgEl, 0, 0, size, size);
const { data } = ctx.getImageData(0, 0, size, size);
const mean = [0.485, 0.456, 0.406];
const std = [0.229, 0.224, 0.225];
const chw = new Float32Array(1 * 3 * size * size);
for (let i = 0; i < size * size; i++) {
const r = data[i * 4] / 255, g = data[i * 4 + 1] / 255, b = data[i * 4 + 2] / 255;
chw[i] = (r - mean[0]) / std[0];
chw[i + size * size] = (g - mean[1]) / std[1];
chw[i + 2 * size * size] = (b - mean[2]) / std[2];
}
return new ort.Tensor('float32', chw, [1, 3, size, size]);
}
async function classify(imgEl) {
const input = await loadImageToTensor(imgEl);
const feeds = { [session.inputNames[0]]: input };
const outputMap = await session.run(feeds);
const logits = outputMap[session.outputNames[0]].data;
// Softmax ve top-k
const exps = logits.map(x => Math.exp(x - Math.max(...logits)));
const sum = exps.reduce((a, b) => a + b, 0);
const probs = exps.map(e => e / sum);
const topK = [...probs].map((p, i) => ({ i, p })).sort((a, b) => b.p - a.p).slice(0, 5);
const labels = await fetch('/model/labels.json').then(r => r.json());
return topK.map(({ i, p }) => ({ label: labels[i], prob: p }));
}
Performans İpuçları
- FP16 kullanımı: Bazı modelleri yarım hassasiyetle (Float16) çalıştırmak bellek ve bant genişliği kullanımını düşürerek hız kazandırır. Modeliniz FP16’ya dönüştürülmüşse WebGPU’da belirgin fark görebilirsiniz.
- Girdi boyutu: 224x224 yerine 192x192 gibi daha küçük boyutlar deneyerek gecikmeyi azaltın. Bu değişim doğruluk kaybı ile hız kazanımı arasında bir denge gerektirir.
- Ortak önbellek: Modeli uygulama yaşam döngüsü boyunca bir kez yükleyip aynı oturumu tekrar kullanın. Tekrarlı session.create maliyetlidir.
- Grafik optimizasyon seviyesi: graphOptimizationLevel: 'all' çoğu senaryoda iyi sonuç verir. Bazı modellerde extended veya basic de test edilmeli.
- Çalışma zamanında veri kopyalarını azaltın: Canvas’tan aldığınız ImageData yerine WebCodecs API ile VideoFrame kullanmak (destekliyse) dönüşümleri hızlandırabilir.
Uyumluluk ve Geriye Dönük Çalışma
WebGPU her cihazda aynı düzeyde mevcut olmayabilir. executionProviders: ['webgpu', 'wasm'] sıralaması sayesinde GPU destekli tarayıcılarda hızlanır, diğerlerinde WASM’e düşer. Bu yaklaşım üretim ortamında kesintisiz deneyim sağlar. Ayrıca HTTPS ve güvenli bağlam zorunluluğuna dikkat edin; yerel geliştirmede localhost istisnadır.
Hata Ayıklama
- Model giriş/çıkış adları: session.inputNames ve session.outputNames ile doğrulayın; ONNX ihracında katman adları değişmiş olabilir.
- Bellek taşmaları: Büyük modeller mobil cihazlarda bellek sınırlarına takılabilir. Modeli quantize ederek (INT8/UINT8) veya daha hafif mimarilere geçerek çözebilirsiniz.
- WebGPU hataları: Tarayıcı konsolunda validation error mesajlarını kontrol edin. Sürücü veya adaptör sınırlamaları için navigator.gpu.requestAdapter() ile dönen limitleri inceleyin.
Gerçek Hayat Senaryoları
- Ürün arama: Kullanıcı yüklediği fotoğrafla eşleşen kategoriye gerçek zamanlı yönlendirme.
- İçerik güvenliği: Tarayıcıda ön filtreleme yaparak sunucu maliyetini ve gecikmeyi azaltma.
- Eğitim: Etkileşimli ML demoları ve ders materyalleri, laboratuvar kurulumları olmadan, yalnızca bir URL ile erişilebilir.
Sonuç
WebGPU ve ONNX Runtime Web birlikteliği, tarayıcıda makine öğrenmesi için yeni bir performans eşiği oluşturuyor. Doğru model seçimi, hafif ön/son işleme ve dikkatli optimizasyonlarla, mobil dahi pek çok cihazda tatmin edici hızlara ulaşmak mümkün. Bu temel iskeleti kullanarak; nesne tespiti, metinden görüntü üretimi veya konuşma tanıma gibi daha karmaşık senaryolara ilerleyebilirsiniz. Üstelik tümü, kullanıcı verisini cihazdan çıkarmadan, gizlilik dostu bir şekilde.