基于 BrowserStack 的多设备 TensorFlow.js 推理性能基准测试工具实战指南
2026/9/20 21:22:06 网站建设 项目流程

基于 BrowserStack 的多设备 TensorFlow.js 推理性能基准测试工具实战指南

【免费下载链接】tfjsA WebGL accelerated JavaScript library for training and deploying ML models.项目地址: https://gitcode.com/gh_mirrors/tf/tfjs

本指南以 browserstack-benchmark 工具文档 为核心骨架,结合 e2e/benchmarks/browserstack-benchmark 目录下的真实源码(app.js、benchmark_models.js、karma.conf.js、preconfigured_browser.json 等)进行深度展开,帮助你完整掌握:如何在数十台真实远程设备上横向评测 TFJS 模型的推理耗时与内存峰值,如何用预配置文件批量跑基准,以及如何用 12 个命令行参数控制整个基准流程。

工具是什么:一次跑遍几十台真机的 TFJS 性能评测

在浏览器端部署 TensorFlow.js 模型时,开发者最关心的问题往往是:这个模型在不同操作系统、不同浏览器、不同档次的手机上,推理到底有多快、吃多少内存?单机自测只能回答"我这台机器上跑得怎样",无法回答"用户的 iPhone 13、Pixel 6、三星 S22 上跑得怎样"。

browserstack-benchmark正是 TFJS 官方仓库(tfjs/e2e/benchmarks/目录)提供的多设备基准测试工具。它借助BrowserStack Automate云真机服务,在一批远程浏览器/真机上统一执行模型推理基准,收集推理时间内存占用两类指标。使用该工具你可以:

  • 基于OS、OS 版本、浏览器、浏览器版本、设备型号五个字段,挑选一批目标设备(桌面浏览器 + iOS/Android 真机);
  • 选择推理后端:WASM / WebGL / CPU
  • 设置模型推理轮数(numRuns);
  • 选择要基准的模型(官方内置模型、远程 URL 自定义模型,或任意代码片段 codeSnippet)。

需要提醒的是,使用该工具前你必须注册 BrowserStack 的 Automate 付费/试用服务,并准备好账号的用户名与 Access Key;工具本身无法脱离 BrowserStack 云设备运行(除非只用local模式跑本机 Chrome)。

工具的三段式架构:Karma 测试运行器 + Node 服务端 + Web 页面

从仓库源码看,工具由三个部分协同工作(见 About this tool 章节):

  1. Karma 测试运行器:benchmark_models.js 把所有基准逻辑封装成一个 Jasmine spec(describe('BrowserStack benchmark')),由 Karma 在目标浏览器中执行;browser_list.json 罗列了官方支持的 BrowserStack 设备组合清单。
  2. Node 服务端:app.js 负责拉起 Karma 测试运行器,并通过 socket.io 把基准结果回传网页。
  3. Web 页面:index.html + index.js 提供可视化操作界面与结果展示。

关键调用链在源码中清晰可见:

  • app.jsrunServer()用 Node 原生http模块起静态文件服务(默认端口 8001,可通过环境变量PORT覆盖),并挂载 socket.io;浏览器连接后服务端立即下发availableBrowsers(即browser_list.json内容),随后监听前端发来的run事件触发benchmark()
  • benchmark()调用setupBenchmarkEnv()生成两份供 Karma 读取的临时配置:./browsers.json(tabId → 浏览器配置映射)与./benchmark_parameters.json(模型、backend、numRuns 等基准参数)。这里有一个值得注意的实现细节:当目标 OS 是iosandroid时,源码会自动追加real_mobile = true,即移动端一律使用真机而非模拟器
  • 对每个浏览器实例,runBrowserStackBenchmark()实际执行yarn test --browserstack --browsers=<tabId>(必要时追加--localBuild/--npmVersion),然后在 Karma 的 stdout 中用正则解析<tfjs_benchmark>...</tfjs_benchmark><tfjs_error>...</tfjs_error>标记,得到结构化结果或错误信息(这两个标记由 benchmark_models.js 在 Jasmine spec 内console.log输出)。

Karma 端如何装配依赖

karma.conf.js 揭示了三种依赖加载模式(由 CLI 参数切换):

  • 默认走CDNhttps://unpkg.com/@tensorflow/<package>@latest/...
  • 指定--npmVersion时,改为https://unpkg.com/@tensorflow/<package>@<version>/...
  • 指定--localBuild时,从../../../dist/bin/加载本地构建产物(tf-core.jstf-backend-cpu.jstf-backend-webgl.jstf-layers.jstf-converter.jstf-backend-wasm.jstf-automl.js等 7 个文件按需选取)。

同时 Karma 通过karma-browserstack-launcher建立本地隧道(hostname: 'bs-local.com',随机tunnelIdentifier),并把./browsers.json的内容直接注入customLaunchersbenchmark_models.js在 Jasmine 的beforeAll中通过fetch('./base/benchmark_parameters.json')拉取基准参数,并执行tf.setBackend(targetBackend)完成后端切换。

环境准备与快速上手

第一步:导出 BrowserStack 凭据

app.jscheckBrowserStackAccount()会在启动时强校验两个环境变量,缺失会直接抛错并提示导出命令:

export BROWSERSTACK_USERNAME=YOUR_USERNAME export BROWSERSTACK_ACCESS_KEY=YOUR_ACCESS_KEY

第二步:下载依赖并启动

git clone https://github.com/tensorflow/tfjs.git cd tfjs/e2e/benchmarks/browserstack-benchmark yarn install node app.js

启动成功后终端会输出> Running socket on: 127.0.0.1:8001(注意:源码日志写的是127.0.0.1:${port},README 中描述为Running socket on port: 8001,实际端口由环境变量PORT决定,默认 8001)。

第三步:打开网页开始基准

浏览器访问http://localhost:8001/。页面(由 index.js 驱动)提供以下交互能力:

  • 从服务端下发的设备清单中选择浏览器/设备,通过Add browser加入待测列表(默认状态为 OS X Monterey + Chrome 103.0);
  • 选择模型(model)、推理轮数(numRuns,默认 10)、后端(backend,默认 webgl);
  • 点击Run benchmark后,服务端为每个 tabId 创建独立 Karma 实例并行开跑,结果通过 socket.io分批回传(谁先跑完谁先展示),并在页面表格中实时标记状态:等待(灰#AAAAAA)、完成(蓝#357edd)、出错(红#e8564b)。

基准一个自定义代码片段(codeSnippet)

页面内置的代码片段基准功能,默认值在 index.js 中可以看到,例如一个随机输入上的tf.conv2d卷积:

setupCodeSnippetEnv: 'const img = tf.randomUniform([1, 240, 240, 3], 0, 1000); ' + 'const filter = tf.randomUniform([3, 3, 3, 3], 0, 1000);', codeSnippet: 'predict = () => { return tf.conv2d(img, filter, 2, \'same\');};'

注意:通过网页基准 codeSnippet 时,需要在运行node app.js之前更新 index.js 中state.benchmarkcodeSnippet/setupCodeSnippetEnv字段,然后在页面的模型下拉框中选择codeSnippet。benchmark_models.js 中的benchmarkCodeSnippet()eval拼接后的代码,要求其中必须定义名为predict的函数,随后先做 1 次预热,再正式计时numRuns轮并做内存 profile。

预配置文件:一条命令批量跑完"模型 × 后端 × 设备"矩阵

对于 CI 或周期化回归,官方推荐使用--benchmarks参数从预配置 JSON 文件驱动:

node app.js --benchmarks=relative_file_path.json

仓库自带的 preconfigured_browser.json 就是一份可直接参考的完整示例。配置文件的格式如下:

{ "benchmark": { "model": ["model_name"], "numRuns": positive_integer, "backend": ["backend_name"] }, "browsers": { "local": {}, "unique_identifier_laptop_or_desktop": { "base": "BrowserStack", "browser": "browser_name", "browser_version": "browser_version", "os": "os_name", "os_version": "os_version", "device": null }, "unique_identifier_mobile_device": { "base": "BrowserStack", "browser": "iphone_or_android", "browser_version": null, "os": "os_name", "os_version": "os_version", "device": "device_name" } } }

字段说明:

  • benchmark.model:一个或多个要基准的模型名(官方内置模型名或custom),见下文"Custom model";
  • benchmark.numRuns:每个模型的推理轮数,必须为正整数;
  • benchmark.backend:一个或多个后端名,如["webgl"]["wasm", "cpu"]
  • browsers:以唯一标识符为键的浏览器配置映射,local键代表本机执行(无 BrowserStack 依赖),其余每个键代表一台远程设备。桌面设备用browser+browser_version+os+os_version+device: null描述;移动设备用browser: "iphone"|"android"+device描述真实机型(browser_version: null)。配置文件中的base: "BrowserStack"会在运行时被 app.js 的 setupBenchmarkEnv() 再次补全,并自动为 iOS/Android 设备加上real_mobile: true

执行语义(见 benchmarkAll() 的嵌套循环):模型列表中的每个模型 × 后端列表中的每个后端,组合成一组基准;每组基准会在每一个浏览器配置上执行。也就是说最终的基准次数 = 模型数 × 后端数 × 浏览器数。如果你想针对"特定模型 × 特定后端"做组合,README 明确建议:拆分成多个配置文件分别执行

仓库内相关文件:

  • 官方支持的 BrowserStack 设备/浏览器组合清单:browser_list.json(涵盖 Windows/OS X 桌面端 Chrome/Edge/Firefox/Safari/IE,以及 iOS(iPhone/iPad)与 Android 真机,如 iPhone 13 Pro Max、Google Pixel 6 Pro、Samsung Galaxy S22 Ultra、Xiaomi Redmi Note 8 等);
  • 官方示例预配置:preconfigured_browser.json(内置 24 个模型的基准列表 + 17 台真机配置,numRuns: 10backend: ["webgl"])。

命令行参数全解(12 个选项)

工具使用argparse解析参数(定义见 setupHelpMessage()),在仓库目录下运行node app.js --h(或--help)可在终端查看完整帮助菜单。全部参数如下:

参数作用示例
--benchmarks从用户指定的预配置 JSON 文件运行基准(文件路径需相对仓库目录)node app.js --benchmarks=relative_file_path.json
--cloud以 GCP 兼容模式运行:不启动本地服务器(不监听页面、不回传 socket),适合云上 CInode app.js --cloud
--firestore把成功的基准结果推送到 Firestore 数据库node app.js --firestore
--h/--help在终端显示帮助菜单与全部可选参数node app.js --h
--period--benchmarks文件中的模型列表按每月日期循环切分为若干桶,只跑其中一桶;取值 1~31。仅在设置了--benchmarks时生效node app.js --period=15
--date手动指定用于选择模型的日期(1~31),仅在设置了--period时生效;未声明则使用运行当天日期node app.js --period=15 --date=1
--maxBenchmarks并行执行的基准数量上限,正整数,默认 5node app.js --maxBenchmarks=positive_integer
--maxTries单个基准失败后的最大重试次数,正整数,默认 3node app.js --maxTries=positive_integer
--outfile把结果写入外部文件,期望值为htmljson。设为html生成benchmark_results.js,可打开benchmark_result.html离线查看node app.js --outfile=js
--v/--version显示当前使用的 node 版本node app.js --v
--localBuild使用本地构建产物替代公共 CDN。使用前务必先构建对应目标,例如yarn build-individual-link-package tfjs-backend-webglnode app.js --localBuild=core,webgl,wasm,cpu,layers,converter,automl
--npmVersion指定要基准的 TFJS npm 版本;默认基准最新版node app.js --npmVersion=4.4.0

关键参数的源码级解读

  • --maxBenchmarks(默认 5)与--maxTries(默认 3)benchmark()内部使用仓库自实现的 promise_queue.js 控制并发;每个 tabId 的基准都会进入队列,最多maxBenchmarks个同时运行。重试逻辑在 getOneBenchmarkResult():失败后递归重试直到maxTries用完。源码还处理了一个工程细节——多个 Karma 进程同时拉起会触发spawn ETXTBSY竞争错误,因此每次启动基准前会按tabIndex * (3 ** numFailed) * 1000毫秒指数退避地错峰启动 runner。
  • --period/--date的调度算法:见 scheduleModels()。模型列表被均匀切成period个桶(bucketSize = Math.ceil(models.length / period)),按(date - 1) % period选中其中一个桶。参数越界(perioddate不在 1~31)会直接抛出异常。这非常适合"每天只跑 1/15 的模型,半月轮转一遍"的持续回归场景——仓库 package.json 中的run-cloud-benchmarks-half-month-cycle脚本正是这么用的。
  • --outfile:启动时若指定html,会先写const benchmarkResults =前缀到benchmark_results.js,再通过JSONStream把每个基准结果流式追加;指定json则写入benchmark_results.json。结果可通过 benchmark_results.html 离线渲染成表格(按"Model/Code Snippet(backend)"分表,展示OS(版本) 设备与目标名称等字段)。
  • --firestoreinitializeWriting()中通过 firestore.js 初始化 Firebase,pushToFirestore()只把status === 'fulfilled'的结果写入数据库,被拒绝的 Promise 只计数并打印(见 app.js)。
  • --localBuild的命名规则:源码注释明确,参数值是包名的短形式——一般去掉tfjs-backend-前缀,例如webgl对应tfjs-backend-webglcore对应tfjs-core。仓库 package.json 提供了配套构建脚本:yarn build-all-deps(构建全部 link 包 + tfjs 本体)、yarn build-individual-link-package(构建单个依赖)。

内置模型与自定义模型(Custom model)

e2e/benchmarks目录下的 model_config.js 以benchmarks对象集中登记了所有官方内置模型。每个模型条目包含typeGraphModel/LayersModel)、load()(加载函数,含默认输入分辨率与架构参数,如 MobileNetV3 的small_075/small_100/large_075/large_100)、predictFunc()(返回带固定随机/零值输入的预测函数)。从源码可见,内置模型覆盖了图像分类(MobileNetV3、MobileNetV2)、姿态估计(MoveNet-SinglePose、MoveNet-MultiPose、posenet)、手势(HandPoseDetector、HandPoseLandmark)、检测(Coco-SSD、BlazePose)、分割(DeepLabV3、BodyPix、SelfieSegmentation)、文本(Universal Sentence Encoder、TextToxicity、MobileBert)、语音(speech-commands)以及 AutoML 系列等 20 余个模型。

如果你想基准自定义模型,需要满足以下约束:

  • 必须提供模型的 URL 路径,不支持本地文件系统模型。例如:
    • TF Hub:https://tfhub.dev/google/tfjs-model/imagenet/resnet_v2_50/feature_vector/1/default/1
    • 对象存储:https://storage.googleapis.com/tfjs-models/savedmodel/mobilenet_v2_1.0_224/model.json
  • 目前仅支持tf.GraphModeltf.LayersModel两种模型类型。

如果你的模型需要更复杂的输入预处理逻辑,就需要把模型连同loadpredictFunc两个方法注册进 e2e/benchmarks/model_config.js。加载与推断的接线逻辑在 benchmark_models.js 的 benchmarkModel():优先使用模型自带的predictFunc;否则调用 benchmark_util.js 的 generateInput() 依据model.inputs的 shape/dtype 自动生成随机输入(shape 中的-1/null会被替换为 1,float32/int32 输入按 [0, 1000] 区间、近似正态分布生成并裁剪),再走timeModelInference/profileModelInference。输入生成失败时,所有已创建的 tensor 会被统一dispose()防止内存泄漏。

指标口径:工具到底在测什么

结果由 benchmark_util.js 统一计算,两条主线分别对应 README 强调的time(时间)memory(内存)

时间指标(timeInference(),timeInfo):

  • times:每一轮推理的耗时数组;
  • averageTime:全部轮次平均耗时;
  • averageTimeExclFirst剔除第一轮后的平均耗时——第一轮通常包含 WebGL shader 编译等冷启动开销,因此这个值更能反映稳态性能;
  • minTime/maxTime:最慢/最快耗时。

计时范围包含predict()与对结果张量调用data()/dataSync()的完整链路,因此是"端到端可用结果"的耗时;对 WebGL 后端使用同步dataSync()强制 GPU 计算完成。此外 timeFirstInference() 支持通过ENGINE_COMPILE_ONLY标志测量带并行编译的首轮推理耗时(WebGL/WebGPU 后端)。

内存指标(profileInference(),基于tf.profile):

  • newBytes:新增分配的字节数;
  • newTensors:新增创建的 tensor 数量;
  • peakBytes:内存分配峰值;
  • kernels:每个 kernel 的输入/输出 shape、字节数、耗时明细(按耗时降序);
  • aggregatedKernels:按 kernel 名称聚合后的耗时({name, timeMs},降序)。

环境信息benchmark_models.jsgetEnvSummary()会附加后端版本信息——WebGL 后端带WEBGL_VERSION,WASM 后端标注是否启用 SIMD(通过WASM_HAS_SIMD_SUPPORT);WebGL 后端还会通过WEBGL_debug_renderer_info扩展抓取真实的 GPU 渲染器字符串(见 getRendererInfo())。最终 getBenchmarkSummary() 汇总成:"1st inference time、Subsequent average inference time(N runs)、Best inference time、Peak memory"四段摘要,这与 README 中"benchmark the time and memory"的目标一一对应。

完整基准流程回放

结合源码,一次完整的 BrowserStack 多设备基准可以总结为以下链路:

  1. node app.js [options]checkBrowserStackAccount()校验凭据 → 按需初始化 Firestore/结果文件;
  2. --cloud模式下启动静态服务 + socket.io(端口 8001),页面从服务端拿到browser_list.json设备清单;
  3. 用户在页面配置浏览器/模型/后端/轮数,或通过--benchmarks加载预配置文件;
  4. 服务端为每台设备生成browsers.jsonbenchmark_parameters.json
  5. 通过PromiseQueue控制并发,为每个 tabId 依次/并行拉起yarn test --browserstack --browsers=<tabId>(Karma);
  6. Karma 在远端浏览器中由benchmark_models.js执行 Jasmine spec:tf.setBackend(后端)→ 加载模型/执行代码片段 →timeInference+profileInference→ 以<tfjs_benchmark>{JSON}</tfjs_benchmark>输出;
  7. 服务端解析结果,失败则按maxTries指数退避重试;成功则通过 socket 回传页面实时展示,并按需写入--outfile文件或推送 Firestore。

这套链路使得"多模型 × 多后端 × 多设备"的矩阵基准可以全自动化运行——仓库 package.json 中现成的 CI 脚本即为最佳实践:

yarn run-cloud-benchmarks # node app.js --benchmark='./preconfigured_browser.json' --cloud --maxBenchmarks=12 --firestore yarn run-cloud-benchmarks-half-month-cycle # 加上 --period=15,按日期轮转模型

使用前提与注意事项

  • 必须拥有 BrowserStack Automate 账号并正确导出BROWSERSTACK_USERNAME/BROWSERSTACK_ACCESS_KEY,否则node app.js启动即报错;
  • 移动端设备为真机(real_mobile: true),会真实消耗 BrowserStack 额度,注意--maxBenchmarks并发数与--period切分以控制成本;
  • 依赖清单见 package.json:karma、karma-browserstack-launcher、karma-jasmine、socket.io、argparse、firebase-admin(Firestore)、JSONStream 等;需使用 yarn(engines.yarn >= 1.0.0);
  • 使用--localBuild前必须先构建对应本地包,否则会加载失败;
  • --period/--date依赖--benchmarks,两者单独使用不生效;--date依赖--period
  • 本工具针对 WebGL/WASM/CPU 三类传统浏览器后端设计(benchmark_models.js通过tf.setBackend切换),TFLite 等特殊后端仅通过特定模型配置与代码路径支持。

现在,你已经可以动手构建属于自己的多设备基准矩阵:从browser_list.json挑设备、在preconfigured_browser.json里配模型与后端,再把它接进--period的月度轮转,让 TFJS 在真实终端上的性能表现持续处于你的监控之下。

【免费下载链接】tfjsA WebGL accelerated JavaScript library for training and deploying ML models.项目地址: https://gitcode.com/gh_mirrors/tf/tfjs

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询