TuBrief
구독 채널
비디오
커뮤니티

Tiga Ladang Ranjau Saat Beralih dari TensorFlow.js ke LiteRT.js

TuBrief 편집팀
2026년 7월 18일
0
Computing/Software

원본 영상을 바탕으로 AI의 도움을 받아 작성했습니다. 원본 영상이 기준입니다.

Bahasa Indonesia한국어EnglishEspañol中文العربيةहिन्दीDeutschFrançaisPortuguêsРусский日本語

관련 영상

Google Baru Saja Membuat TensorFlow.js Menjadi Usang (LiteRT.js)8:10

Google Baru Saja Membuat TensorFlow.js Menjadi Usang (LiteRT.js)

Better Stack

커뮤니티의 다른 글

사내 시스템에 llm api 붙일 때 마주하는 현실적인 한계와 대응법

2026년 9월 13일

레거시 백엔드에 GPT-6 Astra 붙일 때 예산 승인과 보안 통과를 먼저 끝내는 법이 있습니다

2026년 9월 13일

에이전트끼리 대화하다 6천만 원 청구서가 나오는 이유

2026년 9월 13일

사내 RAG 벡터 검색에 Okta 권한 필터를 직접 거는 방법

2026년 9월 13일

브라우저 에이전트에게 내 구글 계정을 통째로 넘기면 안 되는 이유

2026년 9월 12일

Apple Won the AI Race

2026년 9월 12일

댓글 (0)

Log in to leave a comment

아직 작성된 글이 없습니다

© 2026 . All rights reserved.

TuBrief
구독 채널
비디오
커뮤니티
로그인

Tiga Ladang Ranjau Saat Beralih dari TensorFlow.js ke LiteRT.js

Menjalankan model AI di dalam peramban web memang mengasyikkan, tetapi Anda akan segera menemui batasannya. Saat memproses gambar beresolusi tinggi atau memulai operasi berat, layar akan menjadi patah-patah. TensorFlow.js (TF.js) menggunakan backend WebGL dan binding kernel JavaScript, yang menciptakan beban berlebih (overhead) serius pada operasi matriks skala besar.

Di sisi lain, LiteRT.js mengompilasi runtime asli C++ menjadi WebAssembly (Wasm) untuk diimplan ke dalam peramban. Ini adalah struktur yang menyelesaikan hambatan secara mendasar. Terlebih lagi, proses membawa model PyTorch ke web menjadi lebih sederhana. Dulu, kita harus mengubah dari PyTorch ke ONNX, lalu melalui TensorFlow ke format TF.js, yang sering menyebabkan kompatibilitas operator rusak dan hilangnya presisi. Sekarang, cukup gunakan satu pustaka (ai-edge-torch) untuk menghasilkan file standar .tflite dan selesai.

Jika Anda sayang untuk membuang aset yang ada, Anda bisa menggunakan paket @litertjs/tfjs-interop yang disediakan oleh Google. Caranya adalah dengan tetap mempertahankan pipeline prapemrosesan atau pascapemrosesan data milik TF.js, namun hanya mengganti bagian eksekusi prediksi model inti ke LiteRT.js.

Mengatasi Ketidakcocokan NCHWNCHWNCHW dan NHWCNHWCNHWC

Model TFLite yang dikonversi dari PyTorch biasanya memerlukan struktur channel-first yaitu NCHWNCHWNCHW (channel, height, width). Namun, array ImageData pada Canvas peramban menggunakan format NHWCNHWCNHWC (height, width, channel) di mana piksel berjajar. Diperlukan kode prapemrosesan yang melakukan pemrosesan sinkron tanpa membebani main thread untuk menjembatani kesenjangan ini.

Pertama, kalikan jumlah total piksel ImageData dengan 3 untuk membuat Float32Array. Kemudian, tentukan posisi offset perataan untuk masing-masing channel merah, hijau, dan biru masing-masing di 0, jumlah total piksel, dan dua kali jumlah total piksel. Terakhir, normalisasikan nilai piksel antara 0-255 dengan membaginya dengan 255.0, lalu tetapkan ke setiap offset channel. Ini adalah proses penataan ulang data input menjadi buffer tensor rata NCHWNCHWNCHW.

`javascript
/**

  • Utilitas prapemrosesan untuk konversi berkecepatan tinggi dari buffer ImageData format NHWC ke Float32Array NCHW
  • @param {ImageData} imageData - Data piksel mentah yang diperoleh dari HTML5 Canvas
  • @param {number} width - Resolusi lebar gambar input yang diperlukan oleh model target
  • @param {number} height - Resolusi tinggi gambar input yang diperlukan oleh model target
  • @returns {Float32Array} Buffer tensor rata yang disusun ulang dalam format array NCHW
    */
    export function preprocessNHWCToNCHW(imageData, width, height) {
    const { data } = imageData;
    const totalPixels = width * height;
    const nchwBuffer = new Float32Array(totalPixels * 3);

const rChannelOffset = 0;
const gChannelOffset = totalPixels;
const bChannelOffset = totalPixels * 2;

for (let i = 0; i < totalPixels; i++) {
const srcIndex = i * 4;
nchwBuffer[rChannelOffset + i] = data[srcIndex] / 255.0;
nchwBuffer[gChannelOffset + i] = data[srcIndex + 1] / 255.0;
nchwBuffer[bChannelOffset + i] = data[srcIndex + 2] / 255.0;
}
return nchwBuffer;
}

`

Mencegah Layar Patah dengan Web Worker dan Zero-Copy

Situasi paling buruk saat menjalankan deep learning di frontend adalah ketika UI menjadi macet (freeze). Agar peramban dapat menampilkan animasi yang mulus pada 60 frame per detik, event loop harus menyelesaikan tugas sinkron dalam waktu 16,6ms. Namun, operasi tensor sering kali menghambat main renderer loop yang bersifat single-threaded.

LiteRT.js memiliki kecepatan eksekusi dasar sekitar 3 kali lebih cepat daripada alat berbasis JavaScript konvensional. Jika ditambahkan akselerasi WebGPU atau WebNN, kecepatannya meningkat antara 5 hingga 60 kali lipat dibandingkan mode CPU. Backend WebNN yang menggunakan NPU khusus harus mengaktifkan fitur JavaScript Promise Integration (JSPI) untuk menghubungkan penjadwal kernel WebAssembly sinkron dengan loop kontrol perangkat keras asinkron peramban. Untuk menggunakan sumber daya akselerasi ini sambil tetap menjaga main thread tetap hidup, sangat disarankan untuk mengisolasi inisialisasi pustaka dan seluruh pipeline inferensi di dalam Web Worker.

Pada saat ini, jika Anda mengirim buffer secara langsung dalam komunikasi data antar thread, akan terjadi penyalinan memori internal yang mengakibatkan penumpukan overhead pada memori CPU dan heap. Gunakan Transferable Objects untuk memindahkan kepemilikan area alamat memori fisik guna menghilangkan latensi. Buffer yang kepemilikannya telah dipindahkan akan langsung tidak valid di konteks pengirim, sehingga keamanan antar-thread tetap terjaga.

`javascript
// litert-worker.js - Modul Web Worker khusus untuk operasi inferensi latar belakang
import { loadLiteRt, loadAndCompile, Tensor } from '@litertjs/core';

let compiledModel = null;
let isLoaded = false;

self.onmessage = async (event) => {
const { type, payload } = event.data;

switch (type) {
case 'LOAD_MODEL':
try {
await loadLiteRt(payload.wasmDirectory, { jspi: payload.enableJspi || false });
compiledModel = await loadAndCompile(payload.modelUrl, {
accelerator: payload.accelerator || 'webgpu'
});
isLoaded = true;
self.postMessage({ type: 'MODEL_READY' });
} catch (err) {
self.postMessage({ type: 'ERROR', error: Initialization failed: ${err.message} });
}
break;

case 'RUN_INFERENCE':
  if (!isLoaded || !compiledModel) {
    self.postMessage({ type: 'ERROR', error: 'Model has not been loaded' });
    return;
  }
  try {
    const rawInputData = payload.bufferData;
    const inputShape = payload.shape;
    const inputTensor = new Tensor(rawInputData, inputShape);
    const results = await compiledModel.run(inputTensor);
    const cpuOutputTensor = await results[0].moveTo('wasm');
    const outputBuffer = cpuOutputTensor.toTypedArray();

    inputTensor.delete();
    cpuOutputTensor.delete();
    results[0].delete();

    self.postMessage(
      {
        type: 'INFERENCE_COMPLETE',
        payload: {
          data: outputBuffer,
          shape: results[0].shape
        }
      },
      [outputBuffer.buffer]
    );
  } catch (err) {
    self.postMessage({ type: 'ERROR', error: `Inference failed: ${err.message}` });
  }
  break;

default:
  self.postMessage({ type: 'UNKNOWN_OP' });

}
};

`

`javascript
// litert-bridge.js - Kelas orkestrator AI untuk main thread
export class LiteRtBridge {
constructor(workerPath) {
this.worker = new Worker(workerPath);
this.promiseMap = new Map();
this.tokenCounter = 0;

this.worker.onmessage = (event) => {
  const { type, payload, error } = event.data;

  if (type === 'MODEL_READY') {
    if (this.initResolve) this.initResolve();
  } else if (type === 'INFERENCE_COMPLETE') {
    const currentToken = this.tokenCounter;
    const promiseHandler = this.promiseMap.get(currentToken);
    if (promiseHandler) {
      promiseHandler.resolve(payload);
      this.promiseMap.delete(currentToken);
    }
  } else if (type === 'ERROR') {
    const currentToken = this.tokenCounter;
    const promiseHandler = this.promiseMap.get(currentToken);
    if (promiseHandler) {
      promiseHandler.reject(new Error(error));
      this.promiseMap.delete(currentToken);
    } else if (this.initReject) {
      this.initReject(new Error(error));
    }
  }
};

}

bootstrap(wasmDirectory, modelUrl, accelerator = 'webgpu') {
return new Promise((resolve, reject) => {
this.initResolve = resolve;
this.initReject = reject;
this.worker.postMessage({
type: 'LOAD_MODEL',
payload: { wasmDirectory, modelUrl, accelerator, enableJspi: true }
});
});
}

execute(inputFloat32Array, inputShape) {
return new Promise((resolve, reject) => {
this.tokenCounter++;
this.promiseMap.set(this.tokenCounter, { resolve, reject });
this.worker.postMessage(
{
type: 'RUN_INFERENCE',
payload: {
bufferData: inputFloat32Array,
shape: inputShape
}
},
[inputFloat32Array.buffer]
);
});
}
}

`

Jangan Percaya pada Garbage Collection Otomatis

Saat menggunakan TF.js, pola standarnya adalah membersihkan tensor dalam cakupan panggilan sinkron menggunakan tf.tidy(). Namun, jika kode asinkron atau promise saling terkait, hal ini sering menciptakan bug di mana tensor dihapus di luar cakupan sebelum tugas asinkron selesai atau justru terlewat untuk dibersihkan.

LiteRT.js jauh lebih kejam. Ia bukan target Garbage Collection (GC) mesin peramban. Ruang memori virtual linear WebAssembly dan buffer WebGPU tidak dapat dilacak status heap-nya oleh mesin JavaScript seperti V8. Jika Anda tidak memanggil .delete() secara eksplisit pada instans tensor yang sudah tidak digunakan, memori peramban akan terus membengkak tanpa batas. Jika Anda menjalankan layanan yang melakukan streaming frame video kualitas tinggi puluhan kali per detik, tab akan mati dalam hitungan menit.

Anda harus merasa tenang dengan cara membuat kelas scope tracker yang mencatat masa pakai tensor yang dibuat di seluruh pipeline asinkron dan menjamin pemusnahan secara batch.

`javascript
/**

  • Manajer cakupan memori asinkron yang memfasilitasi pelacakan manual dan pemusnahan pasti dari tensor heap WebAssembly
    */
    export class LiteRtScopeTracker {
    constructor() {
    this.trackList = new Set();
    }

/**

  • Memasukkan tensor yang dibuat atau selesai dipindahkan ke dalam daftar manajemen siklus hidup
  • @param {Tensor} tensor - Tensor LiteRT.js yang akan dilacak dan dimusnahkan
  • @returns {Tensor} Mengembalikan objek tensor yang dimasukkan untuk mendukung penulisan kode inline
    */
    register(tensor) {
    if (tensor && typeof tensor.delete === 'function') {
    this.trackList.add(tensor);
    }
    return tensor;
    }

/**

  • Penegakan struktur manajemen pipeline tensor yang aman di dalam blok eksekusi asinkron
  • @param {Function} asyncCallable - Fungsi logika bisnis inferensi asinkron
  • @returns {Promise<*>} Hasil data mentah akhir yang dikembalikan oleh blok eksekusi arbitrer
    */
    async enforceScope(asyncCallable) {
    try {
    const outputResult = await asyncCallable(this);
    if (Array.isArray(outputResult)) {
    outputResult.forEach((item) => this.trackList.delete(item));
    } else {
    this.trackList.delete(outputResult);
    }
    return outputResult;
    } finally {
    this.disposeAll();
    }
    }

/**

  • Melepaskan isolasi secara permanen dari semua tensor TFLite asli yang terikat pada target pengelolaan dan masih bertahan di area Wasm
    */
    disposeAll() {
    for (const tensor of this.trackList) {
    try {
    tensor.delete();
    } catch (err) {
    console.error('An error occurred while cleaning the native Wasm tensor memory:', err);
    }
    }
    this.trackList.clear();
    }
    }

`

`javascript
// Contoh implementasi pemrosesan inferensi AI asinkron ganda yang aman dan tangguh menggunakan pengelola cakupan memori
export async function runRobustVisionInference(rawPixelArray, compiledModel) {
const scopeTracker = new LiteRtScopeTracker();
try {
return await scopeTracker.enforceScope(async (scope) => {
const inputTensor = scope.register(new Tensor(rawPixelArray, [1, 3, 224, 224]));
const predictionResults = await compiledModel.run(inputTensor);
predictionResults.forEach((tensor) => scope.register(tensor));

  const firstOutputTensor = predictionResults[0];
  const wasmTransferTensor = scope.register(await firstOutputTensor.moveTo('wasm'));
  const targetJsArray = wasmTransferTensor.toTypedArray();

  return targetJsArray;
});

} catch (err) {
console.error('Fatal crash occurred during the model pipeline execution:', err);
throw err;
}
}

`

Memuat File Wasm Berat Secara Kondisional

Masalah ukuran unduhan sumber daya juga tidak bisa diabaikan. Agar tree-shaking berjalan di build bundler, hapus referensi statis gaya CommonJS dan tulis kode sumber berdasarkan sintaks modul ES6 (import/export). Pastikan juga pengaturan sideEffects: false pada alat build agar bundle tetap ringan.

@litertjs/core, yang merupakan runtime inti LiteRT.js, memuat secara selektif tiga build kernel WebAssembly sesuai dengan performa perangkat. Pada peramban modern seperti Chrome atau Edge, modul yang mendukung multi-threading dan SIMD (litert_wasm_simd.wasm) akan dipilih, sedangkan pada lingkungan legacy seperti Safari, modul fallback dasar (litert_wasm.wasm) akan diambil. Jika kompilasi GPU gagal, runtime XNNPACK akan berjalan sebagai perangkat pendukung untuk mendorong seluruh operator perangkat keras ke dalam sandbox Wasm CPU.

Untuk mencegah keterlambatan pemuatan awal (initial loading delay), Anda harus memeriksa akselerator berdasarkan spesifikasi dan memuat modul secara dinamis.

`javascript
// litert-loader.js - Mesin penjelajahan perangkat runtime dan penggabungan akselerator dinamis
export async function bootstrapHighPerformanceInferenceEngine() {
const supportsWebGpu = 'gpu' in navigator;
let chosenAccelerator = 'wasm';

if (supportsWebGpu) {
try {
const gpuAdapter = await navigator.gpu.requestAdapter();
if (gpuAdapter) {
const info = await gpuAdapter.requestDevice();
if (info) {
chosenAccelerator = 'webgpu';
}
}
} catch (e) {
console.warn("GPU profile probe failed, resolving execution chain to fallback WASM.");
}
}

const { loadLiteRt, loadAndCompile } = await import('@litertjs/core');
const cdnWasmHostPath = 'https://cdn.jsdelivr.net/npm/@litertjs/core/wasm/';

await loadLiteRt(cdnWasmHostPath, {
jspi: chosenAccelerator === 'webnn'
});

return {
loadAndCompile,
chosenAccelerator
};
}

`

Mari kita tetapkan pemindahan kepemilikan memori melalui Web Worker dan pemusnahan objek eksplisit di area Wasm sebagai dasar desain Anda. Begitu Anda mulai mengendalikan aliran data secara langsung, Anda dapat meluncurkan layanan AI on-device ke produksi tanpa perlu mengkhawatirkan peramban yang meledak.