TuBrief
Subscribed Channels
Videos
Community

从 TensorFlow.js 迁移到 LiteRT.js 时会遇到的三个“雷区”

TuBrief Editorial
July 18, 2026
0
Computing/Software

Written with AI assistance from the source video. The video is the authority.

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

Related Video

Google 刚刚让 TensorFlow.js 变得过时了(LiteRT.js)8:10

Google 刚刚让 TensorFlow.js 变得过时了(LiteRT.js)

Better Stack

More from the community

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

September 13, 2026

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

September 13, 2026

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

September 13, 2026

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

September 13, 2026

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

September 12, 2026

Apple Won the AI Race

September 12, 2026

Comments (0)

Log in to leave a comment

No posts yet

© 2026 . All rights reserved.

TuBrief
Subscribed Channels
Videos
Community
Log in

从 TensorFlow.js 迁移到 LiteRT.js 时会遇到的三个“雷区”

在网页浏览器中运行 AI 模型是一件令人兴奋的事情,但很快就会遇到瓶颈。因为在处理高分辨率图像或进行繁重计算时,屏幕会变得卡顿。TensorFlow.js (TF.js) 使用 WebGL 后端和 JavaScript 内核绑定,这在大规模矩阵运算中会产生严重的开销。

相比之下,LiteRT.js 将 C++ 原生运行时编译为 WebAssembly (Wasm) 并移植到了浏览器中。这种结构从根本上解决了瓶颈问题。此外,将 PyTorch 模型引入 Web 的过程也变得更加简单。过去,我们需要从 PyTorch 转到 ONNX,再经过 TensorFlow 转换为 TF.js 格式,这导致了算子兼容性损坏和精度损失。而现在,只需一个库 (ai-edge-torch) 导出一个标准的 .tflite 文件就足够了。

如果你不想丢弃现有资产,可以使用 Google 提供的 @litertjs/tfjs-interop 包。其方式是保留数据预处理或后处理的 TF.js 流水线,仅将核心模型预测执行部分替换为 LiteRT.js。

解决 NCHWNCHWNCHW 与 NHWCNHWCNHWC 的不匹配问题

从 PyTorch 转换而来的 TFLite 模型通常要求通道优先结构 NCHWNCHWNCHW(通道、高度、宽度)。然而,浏览器 Canvas 的 ImageData 数组是像素排列的 NHWCNHWCNHWC(高度、宽度、通道)格式。我们需要一种预处理代码,在不增加主线程负担的情况下同步处理这种差异。

首先,将 ImageData 的总像素数乘以 3 来创建一个 Float32Array。然后,将红色、绿色和蓝色通道的扁平化偏移位置分别指定为 0、总像素数以及总像素数的两倍。最后,将 0~255 之间的像素值除以 255.0 进行归一化,并分配给各个通道偏移量。这是一个将输入数据重新排列为 NCHWNCHWNCHW 扁平化张量缓冲区的过程。

`javascript
/**

  • 将 NHWC 格式的 ImageData 缓冲区转换为高性能 NCHW Float32Array 的预处理工具
  • @param {ImageData} imageData - 从 HTML5 Canvas 获取的原始像素数据
  • @param {number} width - 目标模型要求的输入图像水平分辨率
  • @param {number} height - 目标模型要求的输入图像垂直分辨率
  • @returns {Float32Array} 重新排列为 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;
}

`

利用 Web Worker 和零拷贝防止画面卡顿

在前端运行深度学习时,最可怕的情况就是 UI 界面失去响应。为了让浏览器呈现每秒 60 帧的平滑动画,事件循环必须在 16.6ms 内处理同步任务。然而,张量运算很容易阻塞单线程的主渲染循环。

LiteRT.js 的默认执行速度比现有的基于 JavaScript 的工具快约 3 倍。如果加上 WebGPU 或 WebNN 加速硬件,速度比 CPU 模式快 5 到 60 倍。使用专用 NPU 的 WebNN 后端为了连接同步式 WebAssembly 内核调度程序与浏览器的异步硬件控制循环,必须启用 JavaScript Promise 集成 (JSPI) 功能。为了在使用这些加速资源的同时保持主线程畅通,必须将库初始化和整个推理流水线隔离在 Web Worker 内部。

此时,如果在线程间通信时直接传递缓冲区,会发生内部内存复制,导致 CPU 和堆内存中堆积开销。必须使用可转移对象 (Transferable Objects) 直接转移物理内存地址区域的所有权,这样才能消除延迟。所有权转移后的缓冲区会在发送端上下文立即失效,因此也能保证线程间的安全性。

`javascript
// litert-worker.js - 专门用于后台推理运算的 Web Worker 模块
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 - 用于主线程的 AI 协调器类
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]
);
});
}
}

`

不要迷信自动垃圾回收

在使用 TF.js 时,使用 tf.tidy() 清理同步调用作用域内的张量是标准模式。但如果涉及到异步代码或 Promise,就会出现张量在异步任务完成前就离开了作用域被销毁,或者漏掉回收的 Bug。

LiteRT.js 则更加严苛。它不在浏览器引擎的垃圾回收 (GC) 范围内。WebAssembly 线性虚拟内存空间和 WebGPU 缓冲区是 V8 等 JavaScript 引擎无法追踪堆状态的。如果不对使用完毕的张量实例显式调用 .delete(),浏览器内存将会无限膨胀。如果是每秒流式传输数十次高清视频帧的服务,标签页会在几分钟内崩溃。

必须创建一个作用域追踪器类,记录在整个异步流水线中创建的张量的生命周期并确保其批量销毁,这样管理起来才放心。

`javascript
/**

  • 用于手动追踪并确保销毁 WebAssembly 堆张量的异步内存范围管理器
    */
    export class LiteRtScopeTracker {
    constructor() {
    this.trackList = new Set();
    }

/**

  • 将生成或完成移动的张量加入生命周期管理列表
  • @param {Tensor} tensor - 需要追踪和销毁处理的 LiteRT.js 张量
  • @returns {Tensor} 返回传入的张量对象,以支持内联代码编写
    */
    register(tensor) {
    if (tensor && typeof tensor.delete === 'function') {
    this.trackList.add(tensor);
    }
    return tensor;
    }

/**

  • 强制管理异步执行块内的安全张量流水线
  • @param {Function} asyncCallable - 异步推理业务逻辑函数
  • @returns {Promise<*>} 任意执行块返回的最终原始数据结果
    */
    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();
    }
    }

/**

  • 将所有绑定在管理目标下且依然存活的原生 TFLite 张量从 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
// 使用内存作用域追踪器实现安全且稳健的多异步 AI 推理处理示例
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;
}
}

`

有条件地加载沉重的 Wasm 文件

资源下载量变大的问题也不容忽视。为了让构建打包工具发挥 Tree-shaking 的作用,必须消除 CommonJS 风格的静态引用,并基于 ES6 模块语法 (import/export) 编写源代码。还要注意构建工具的 sideEffects: false 设置,以减轻包体积。

LiteRT.js 核心运行时 @litertjs/core 会根据设备性能选择加载三种 WebAssembly 内核构建之一。在 Chrome 或 Edge 等现代浏览器中,会选择支持多线程和 SIMD 的模块 (litert_wasm_simd.wasm);在 Safari 等旧版环境中,则会获取基础回退模块 (litert_wasm.wasm)。如果 GPU 编译失败,XNNPACK 运行时会作为辅助装置启动,将所有硬件算子推送到 CPU Wasm 沙箱中执行。

为了防止初始加载延迟,应检查各规格加速器并动态获取模块。

`javascript
// litert-loader.js - 运行时设备探测及加速器动态绑定引擎
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
};
}

`

请将通过 Web Worker 转移内存所有权以及在 Wasm 区域进行显式对象销毁处理作为设计基调。一旦开始直接控制数据流,你就可以放心地将端侧 AI 服务投入生产,而不必担心浏览器崩溃。