WebLLM 逐 Token 延迟分解(Latency Breakdown)实战:用 get-started-latency-breakdown 示例量化浏览器端 LLM 推理耗时 WebLLM 逐 Token 延迟分解Latency Breakdown实战用 get-started-latency-breakdown 示例量化浏览器端 LLM 推理耗时【免费下载链接】web-llmHigh-performance In-browser LLM Inference Engine项目地址: https://gitcode.com/GitHub_Trending/we/web-llm导读本文围绕 WebLLM 仓库中的 get-started-latency-breakdown 示例 展开讲解如何在浏览器 Web 应用中以最小化的代码量调用 WebLLM 的 OpenAI 兼容 API并采集单个 token 采样阶段的延迟分解数据latency breakdown。读完本文你将掌握示例的安装与运行方式、CreateMLCEngine的初始化参数、一次生成请求中n / temperature / top_p / logprobs / frequency_penalty / repetition_penalty等采样参数的完整含义以及如何从usage.extra.latencyBreakdown中读取 logit 处理、惩罚、采样、grammar bitmask 等各阶段的耗时并用均值/最小值/最大值/P99 进行统计分析从而精确评估和调优模型在端侧WebGPU的推理性能。示例定位一个带延迟统计的最小 Web 应用该示例是 WebLLM 官方示例集中最精简的入门变体之一。与 get-started 示例 展示“如何跑通一次对话”不同本示例的差异化价值在于它在标准生成流程之外额外收集了每个 token 采样步骤的延迟统计并把结果输出到浏览器控制台便于开发者观察推理引擎的耗时构成。从仓库结构看该示例仅由三个文件组成examples/get-started-latency-breakdown/src/get_started_latency_breakdown.ts示例主逻辑负责初始化引擎、循环发起生成请求、汇总延迟数据examples/get-started-latency-breakdown/src/get_started_latency_breakdown.html极简页面骨架仅包含状态标签与模块脚本入口examples/get-started-latency-breakdown/package.json依赖与构建脚本声明Parcel TypeScript mlc-ai/web-llm。页面 HTML 中只有init-label一个用于回显初始化进度的标签所有统计结果通过console.log输出——示例定位是面向开发者的测量工具而非交互式聊天界面。环境准备与运行示例基于 npm 工作流运行前需要 Node.js 环境。在示例目录下依次执行npm install npm startnpm install安装依赖。核心依赖为mlc-ai/web-llm该示例锁定的版本为^0.2.84见 package.json构建工具使用 Parcel 2parcel: ^2.8.3配合 TypeScript、buffer、process、url等浏览器 polyfill 依赖npm start启动开发服务器实际命令为parcel src/get_started_latency_breakdown.html --port 8888即通过 Parcel 在8888 端口提供页面并热编译 TypeScript。打开http://localhost:8888后页面会显示 “WebLLM Test Page”模型加载进度显示在init-label标签中随后自动发起多轮生成请求延迟统计输出在开发者工具Console中。如需打包生产版本示例还提供了npm run buildparcel build src/get_started_latency_breakdown.html --dist-dir lib。调试 WebLLM 核心包高级选项原文档特别指出如果想深入修改 WebLLM 核心包本身可以将 package.json 中的mlc-ai/web-llm依赖改为file:../..即指向仓库根目录随后按项目中的“从源码构建”说明见 docs/developer/building_from_source.rst本地构建 webllm。原文档明确提醒该选项仅推荐给需要 hack WebLLM 核心包的开发者普通使用者应保持使用 npm 发布版本。引擎初始化模型选择与 KV Cache 定制示例通过CreateMLCEngine创建推理引擎见 get_started_latency_breakdown.ts 第 43-61 行const initProgressCallback (report: webllm.InitProgressReport) { setLabel(init-label, report.text); }; // Option 1: If we do not specify appConfig, we use prebuiltAppConfig defined in config.ts const selectedModel Qwen3-0.6B-q0f32-MLC; const engine: webllm.MLCEngineInterface await webllm.CreateMLCEngine( selectedModel, { initProgressCallback: initProgressCallback, logLevel: INFO, // specify the log level }, // customize kv cache, use either context_window_size or sliding_window_size (with attention sink) { context_window_size: 2048, // sliding_window_size: 1024, // attention_sink_size: 4, }, );这里有三个层次值得展开模型选择示例默认使用Qwen3-0.6B-q0f32-MLC。当不显式传入appConfig时引擎会使用prebuiltAppConfig——该配置在 src/config.ts 中定义集中登记了各预构建模型的model_id、model_lib、KV cache 默认窗口context_window_size大多为 4096等元信息。示例代码中的注释 “Option 1” 指的就是这一默认路径。初始化回调与日志initProgressCallback接收InitProgressReport包含下载/加载进度文本用于更新页面标签logLevel: INFO控制引擎日志级别。WebLLM 定义了TRACE/DEBUG/INFO/WARN/ERROR/SILENT六级日志见 src/types.ts 第 245-253 行。KV Cache 定制第三个参数示例显式传入context_window_size: 2048将上下文窗口收敛到 2K同时以注释形式展示了另一组方案——sliding_window_size滑动窗口配合attention_sink_size注意力锚点。在实际部署中context_window_size直接决定 KV cache 的内存占用与可容纳的上下文长度滑动窗口方案则通过固定窗口 attention sink 控制长序列下的显存增长。选择哪个方案需要在内存上限与可用上下文长度之间权衡。发起生成请求一次覆盖全量采样参数的调用示例在每次试验中调用engine.chat.completions.create一次性演示了 WebLLM 支持的大多数采样控制参数见 get_started_latency_breakdown.ts 第 80-100 行const reply0 await engine.chat.completions.create({ messages: [{ role: user, content: List twenty US states. }], // below configurations are all optional n: 1, temperature: 0, max_tokens: 2048, // logit_bias 示例注释中说明46510/7188 对应 Llama-3.1-8B-Instruct 词表中的 California // 8421/51325 对应 Texas。通过对前者施加 -100、对后者施加 5可让答案中几乎必然出现 Texas 而绝不出现 California // logit_bias: { // 46510: -100, // 7188: -100, // 8421: 5, // 41325: 5, // }, top_p: 0.8, logprobs: true, top_logprobs: 2, frequency_penalty: 1.2, presence_penalty: 1.0, repetition_penalty: 1.1, });各参数含义与取值说明如下参数示例值作用n1一次请求生成的候选回复数量1 时多个候选的耗时会被合计/平均temperature0采样温度0 表示贪心解码输出更确定max_tokens2048单次生成的最大 token 数上限logit_bias注释形式按 token id 调整 logit 值负值抑制如-100几乎强制屏蔽、正值增强top_p0.8核采样阈值只从累计概率达到 0.8 的最小 token 集合中采样logprobs/top_logprobstrue/2返回每个位置的 logprobs以及前 2 个最高概率 token 的 logprobfrequency_penalty1.2按 token 已出现频次施加惩罚抑制重复presence_penalty1.0按 token 是否出现过施加惩罚鼓励引入新话题repetition_penalty1.1对已出现 token 的 logit 进行缩放1 时惩罚重复提示词固定为 “List twenty US states.”该任务输出长度适中便于在 20 次试验中积累足够多的逐 token 耗时样本。这些请求参数在 OpenAI 兼容协议层均有对应类型定义例如logprobs、top_logprobs、stream_options与 WebLLM 专有的extra_body字段定义于 src/openai_api_protocols/chat_completion.ts。核心机制Latency Breakdown 从哪来数据结构定义逐 token 延迟分解的数据结构在 src/types.ts 第 255-262 行 定义export type LatencyBreakdown { logitProcessorTime: number[]; // 自定义 logit processor 处理耗时秒 logitBiasTime: number[]; // logit bias 应用耗时秒 penaltyTime: number[]; // 各类 penalty 计算耗时秒 sampleTime: number[]; // token 采样耗时秒 totalTime: number[]; // 单个输出 token 的端到端耗时秒 grammarBitmaskTime: number[]; // grammar/结构化输出 bitmask 构建耗时秒 };每个字段都是一个数值数组——数组长度等于该轮生成中被测量的输出 token 数即每个 token 各有一个耗时样本。这正是“逐 token 延迟分解”的含义所在。如何开启extra_body.enable_latency_breakdown关键点在于latencyBreakdown字段默认不会返回必须通过请求的extra_body.enable_latency_breakdown: true显式开启。该字段定义于 src/openai_api_protocols/chat_completion.ts 第 272-286 行是 WebLLM 相对 OpenAI 协议新增的扩展字段同结构内还有 Qwen3 专用的enable_thinking。在引擎实现中usage 的组装逻辑为latencyBreakdown: request.extra_body?.enable_latency_breakdown ? latencyBreakdown : undefined,见 src/engine.ts 第 721-729 行。也就是说只有传入该标志usage.extra.latencyBreakdown才会被填充。在真实使用本示例时若发现各阶段数组均为空应先检查是否在请求中传入extra_body: { enable_latency_breakdown: true }——示例代码出于最小化演示的目的未显式传入该标志而是用|| []兜底读取见 get_started_latency_breakdown.ts 第 102-116 行。各阶段的源码采集点在流水线层 src/llm_chat.ts 中LLMChatPipeline维护了一个curRoundLatencyBreakdown成员第 135-142 行并在每个输出 token 的采样过程中用performance.now()计时、按阶段 push 耗时样本单位为秒除以1e3换算grammarBitmaskTimegrammar matcher 构建下一个合法 token 的 bitmask 耗时第 1740-1747 行仅在启用语法约束且开启 breakdown 时记录logitProcessorTime自定义 logit processor 在 CPU 上处理 logits 的耗时第 1758-1768 行附近logitBiasTime应用 logit bias 的耗时第 1822 行附近penaltyTimefrequency/presence/repetition 惩罚计算耗时第 1887 行附近sampleTimeGPU 采样及结果回读耗时第 1959-1963 行totalTime从该 token 的 logits 处理开始到采样完成的整体耗时第 1982-1986 行。由此可知totalTime大致等于前五个阶段的合计通过对比各阶段与totalTime的比值即可定位耗时瓶颈——例如启用logprobs/top_logprobs后sampleTime占比上升、启用response_format结构化输出后grammarBitmaskTime与 grammar 初始化耗时上升。usage.extra 中的其他性能指标除了latencyBreakdown一次生成请求的 usage 中还带有 WebLLM 独有的整体性能指标字段注释见 src/openai_api_protocols/chat_completion.ts 第 981-1026 行e2e_latency_s整个请求从接收到响应完成的总耗时秒prefill_tokens_per_s预填充prefill阶段的 token 吞吐decode_tokens_per_s自回归解码阶段的 token 吞吐time_to_first_token_s首 token 延迟主要包含 prefill 开销time_per_output_token_s相邻输出 token 的平均间隔主要包含 decode 开销grammar_init_s/grammar_per_token_s结构化输出场景下 grammar matcher 初始化与逐 token 处理耗时。示例在第 118-121 行将这些指标逐轮收集到decodeTokensPerS、e2eLatencyS、timePerOutputTokenS、completionTokens数组中作为与逐 token breakdown 互补的整体视图。数据聚合均值 / 最小值 / 最大值 / P99 统计示例定义了computeStats函数见 get_started_latency_breakdown.ts 第 19-41 行对任意数值数组计算四项统计量function _computeStats(arr: number[]) { if (!arr.length) return undefined; const sorted [...arr].sort((a, b) a - b); const sum arr.reduce((a, b) a b, 0); const avg sum / arr.length; const min sorted[0]; const max sorted[sorted.length - 1]; const p99 sorted[Math.floor(0.99 * (sorted.length - 1))]; return { avg, min, max, p99 }; }它遍历LatencyBreakdown的每个字段只对非空数组计算{avg, min, max, p99}。在性能测量中avg反映该阶段的平均开销用于估算单 token 延迟的典型值min接近该阶段的理论下限无调度抖动max暴露偶发的长尾延迟p99第 99 百分位是衡量长尾风险的关键指标比max更稳健对评估交互流畅性如流式输出时的卡顿感尤其重要。多轮试验与结果输出为获得稳定的统计样本示例将整个生成-采集流程放入 20 次循环numTrials 20第 77-122 行每次试验调用chat.completions.create生成回复从reply0.usage?.extra.latencyBreakdown中读取 6 个阶段数组用展开运算符push(...)合并进累积数组收集decode_tokens_per_s、e2e_latency_s、time_per_output_token_s、completion_tokens等整体指标通过console.log打印每个 trial 的序号。循环结束后将全部样本交给computeStats聚合并依次输出console.log(Latency stats: , latencyStats); console.log(Decode tokens per second: , decodeTokensPerS); console.log(Completion tokens: , completionTokens); console.log(E2E latency (s): , e2eLatencyS); console.log(Time per output token (s): , timePerOutputTokenS);多轮累积的意义在于单次生成的 token 数量有限示例中max_tokens: 2048是上限而非实际值20 次试验可将样本量提升一个量级使avg与p99更具统计意义。所有结果打印在浏览器 Console 中配合logLevel: INFO的引擎日志即可完成一次完整的端侧推理性能剖面。更换模型与扩展思路示例结尾注释提供了两种换模型的方式再次调用CreateMLCEngine()创建新引擎或调用engine.reload(modelId)热切换模型见 get_started_latency_breakdown.ts 第 132 行。可选的模型 id 以 src/config.ts 中prebuiltAppConfig登记的模型为准。常见的扩展方向包括修改selectedModel对比不同规模/量化如 q0f32 全精度与低位量化模型的逐 token 耗时与decode_tokens_per_s调整context_window_size观察 KV cache 变化对吞吐的影响在请求中开启extra_body: { enable_latency_breakdown: true }获取完整的逐 token 分解数据引入response_format结构化输出后观察grammarBitmaskTime与grammar_per_token_s的开销将 Console 输出改为页面渲染或上报形成持续的性能监控。仓库中另有 tests 目录下的引擎集成测试与 API 协议测试如 tests/engine_integration.test.ts、tests/openai_chat_completion.test.ts可作为理解协议行为与断言结构的参考。小结get-started-latency-breakdown 示例用不到 100 行 TypeScript 代码把 WebLLM 从引擎初始化、采样参数配置到逐 token 延迟采集的完整链路串了起来。其核心价值在于usage.extra.latencyBreakdown提供了logitProcessorTime / logitBiasTime / penaltyTime / sampleTime / totalTime / grammarBitmaskTime六个维度的逐 token 耗时明细配合e2e_latency_s、decode_tokens_per_s等整体指标与 P99 统计开发者可以在浏览器内直接量化模型推理的耗时构成为选型、调参与优化提供第一手数据。掌握这一测量方法是深入使用 WebLLM 构建高性能端侧 LLM 应用的第一步。【免费下载链接】web-llmHigh-performance In-browser LLM Inference Engine项目地址: https://gitcode.com/GitHub_Trending/we/web-llm创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考