WebLLM Subgroups 能力路由实战:在 Web 应用中按 WebGPU 子组特性动态切换 WASM 模型库
【免费下载链接】web-llmHigh-performance In-browser LLM Inference Engine项目地址: https://gitcode.com/GitHub_Trending/we/web-llm
导读:本文基于 WebLLM 仓库中的examples/subgroups-usage示例,讲解如何在 Web 应用里实现"能力路由"(capability-based routing):运行时探测 WebGPU adapter 是否支持subgroups特性,并据此在 baseline 与 subgroup(SG32)两种 WebGPU WASM 构建之间动态切换模型库。读完本文,你将掌握 WebGPU 子组特性的探测逻辑、model_lib路径改写方法,以及如何把该示例改造成指向自己的模型与模型库。
示例背景:为什么要按能力路由
WebLLM 是高性能的浏览器端 LLM 推理引擎,模型权重与编译产物以 WebGPU WASM 形式分发。为了让推理更快,MLC 团队会针对支持 WebGPU 子组(subgroup)特性的设备提供优化后的 WASM 构建;而不支持该特性的设备则继续使用 baseline 构建。子组特性(WGSLSubgroups对应的subgroups功能)允许一个工作组内的线程以硬件原生方式协作,从而降低同步与内存开销。
问题在于:同一份应用无法预先知道用户设备的 WebGPU 能力。examples/subgroups-usage正是为此提供一个最小可运行 demo——启动时探测 adapter 能力,再决定加载哪种 WASM,避免在不支持的设备上加载 subgroup 构建导致运行失败,也避免在支持的设备上错过性能优化。仓库根目录的 examples/README.md 将其概括为 "capability-based routing between baseline and subgroup WebGPU WASM builds"。
快速运行示例
示例目录自带独立的package.json,使用 Parcel 作为开发服务器与打包器,依赖@mlc-ai/web-llm(示例当前锁定^0.2.84),见 examples/subgroups-usage/package.json。在示例目录下执行:
npm install npm startnpm start实际执行的是parcel src/subgroups_usage.html --port 8888,随后浏览器打开http://localhost:8888即可。页面本身非常精简(见 examples/subgroups-usage/src/subgroups_usage.html),只有一个初始化进度标签init-label,其余输出(模型加载日志、对话回复、usage统计)都在浏览器控制台查看。
运行前提:浏览器需支持 WebGPU 并处于启用状态(如 Chrome/Edge 的 WebGPU 支持);示例运行时需要联网下载模型权重与 WASM 模型库。
核心机制一:探测 WebGPU 子组能力
示例的探测逻辑位于 examples/subgroups-usage/src/subgroups_usage.ts 的main()中。它通过navigator.gpu.requestAdapter()请求一个优先"高性能"的 adapter,然后读取其特性与限制:
const adapter = await (navigator as any).gpu?.requestAdapter({ powerPreference: "high-performance", }); if (adapter == null) { throw Error("Unable to request a WebGPU adapter."); } const adapterInfo = adapter.info || (await (adapter as any).requestAdapterInfo()); const subgroupMinSize = adapterInfo.subgroupMinSize; const subgroupMaxSize = adapterInfo.subgroupMaxSize; const supportsSubgroups = adapter.features.has("subgroups") && subgroupMinSize !== undefined && subgroupMinSize <= 32 && subgroupMaxSize !== undefined && 32 <= subgroupMaxSize && adapter.limits.maxComputeInvocationsPerWorkgroup >= 1024;这段代码揭示了 SG32 构建的硬件适配条件,全部满足才算支持子组路由:
adapter.features.has("subgroups"):adapter 暴露了subgroups功能;- 子组大小范围必须覆盖 32:
subgroupMinSize <= 32且subgroupMaxSize >= 32,即设备能以 32 线程为单位的子组运行计算; adapter.limits.maxComputeInvocationsPerWorkgroup >= 1024:单工作组最多可容纳 1024 个调用,这是运行对应 compute shader 的必要上限。
从源码结构看,"SG32" 即 32 线程子组的优化构建:只有当硬件子组大小允许 32(SG32)时才切换,否则停留在 baseline。开发者若想支持其他子组大小(如 SG64),可以在此基础上扩展判断条件与路径改写规则。
核心机制二:动态改写 model_lib 路径
toSg32ModelLib()是路由的核心工具函数,它把 baseline 的 WASM 路径改写为 subgroup 变体:
function toSg32ModelLib(modelLib: string): string { const modelLibUrl = new URL(modelLib); const pathParts = modelLibUrl.pathname.split("/"); const wasmFileIndex = pathParts.length - 1; const variantDirIndex = wasmFileIndex - 1; if (variantDirIndex < 0 || pathParts[variantDirIndex] !== "base") { throw Error( `Expected model_lib path variant directory to be "base": ${modelLib}`, ); } pathParts[variantDirIndex] = "sg32"; modelLibUrl.pathname = pathParts.join("/"); return modelLibUrl.toString(); }其约定如下:model_libURL 的目录结构中必须存在一个名为base的变体目录,且它紧邻 WASM 文件名。改写时仅把base替换为sg32,其余路径保持不变。例如:
- baseline:
.../v0_2_84/base/Llama-3_1-8B-Instruct-q4f32_1-ctx4k_cs1k-webgpu.wasm - 改写后:
.../v0_2_84/sg32/Llama-3_1-8B-Instruct-q4f32_1-ctx4k_cs1k-webgpu.wasm
如果目录名不是base(例如指向了自定义路径),函数会抛出明确错误,防止静默加载错误构建。
核心机制三:构造带路由的 appConfig
WebLLM 的模型配置由AppConfig描述,其中model_list是ModelRecord数组(定义见 src/config.ts)。ModelRecord的关键字段包括:
model:模型权重仓库地址(Hugging Face 风格 URL 或本地路径);model_id:模型的唯一标识,供CreateMLCEngine()引用;model_lib:该模型使用的 WASM 模型库地址;overrides:可选的ChatConfig覆盖项,例如调整 KV Cache 设置(context_window_size等,见 src/config.ts);vram_required_MB、low_resource_required、required_features等辅助字段,用于资源预估与特性校验。
示例首选从 WebLLM 内置的prebuiltAppConfig(位于 src/config.ts)中取出目标模型记录,再按能力探测结果决定是否替换model_lib:
const selectedModel = "Llama-3.1-8B-Instruct-q4f32_1-MLC"; const modelRecord = webllm.prebuiltAppConfig.model_list.find( (entry: webllm.ModelRecord) => entry.model_id === selectedModel, ); const appConfig = supportsSubgroups && modelRecord !== undefined ? { model_list: [ { ...modelRecord, model_lib: toSg32ModelLib(modelRecord.model_lib), }, ], } : undefined;这段代码体现了"默认安全、按能力升级"的设计:不支持子组时appConfig保持undefined,引擎自动使用prebuiltAppConfig(baseline 构建);支持时才注入替换过model_lib的配置。...modelRecord展开保留了model、overrides等其余字段,仅覆盖model_lib一项。
若想指向自己的模型,示例注释给出了 Option 2 的完整模板:手工构造model_lib,使用webllm.modelLibURLPrefix + webllm.modelVersion + "/..."拼接预构建库地址。其中modelVersion表示当前 npm 包兼容的预构建模型库版本(示例对应v0_2_84/base),modelLibURLPrefix指向模型库发布前缀,二者定义见 src/config.ts,拼接结果形如.../v0_2_84/base/<模型名>-webgpu.wasm。
引擎创建与 KV Cache 定制
探测与配置就绪后,通过CreateMLCEngine()创建引擎(API 定义见 src/engine.ts):
const engine: webllm.MLCEngineInterface = await webllm.CreateMLCEngine( selectedModel, { appConfig: appConfig, 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, }, );三个参数分别对应:
selectedModel:要加载的model_id;MLCEngineConfig:appConfig传入路由后的配置;initProgressCallback把加载进度report.text实时写到页面标签;logLevel控制控制台日志级别;- 可选的 KV Cache 定制参数:
context_window_size: 2048限定上下文窗口,或改用sliding_window_size+attention_sink_size(滑窗 + 注意力锚点)方案。这些字段对应ChatConfig中的 KV Cache 设置(见 src/config.ts),最终会覆盖模型的mlc-chat-config.json默认值。示例注释还给出了第三种用法:先new webllm.MLCEngine({...})再单独调用engine.reload(selectedModel)。
路由效果的验证方式
加载完成后,示例发起一次带logit_bias的生成请求,用于验证模型工作正常:
const reply0 = await engine.chat.completions.create({ messages: [{ role: "user", content: "List three US states." }], n: 3, temperature: 1.5, max_tokens: 256, logit_bias: { "46510": -100, "7188": -100, "8421": 5, "51325": 5, }, logprobs: true, top_logprobs: 2, }); console.log(reply0); console.log(reply0.usage);其中logit_bias特意把 "California" 的两个 token(46510、7188)压到 -100、把 "Texas" 的两个 token(8421、51325)抬到 +5,从而让输出更倾向 Texas 而绝不出现 California。这是验证路由构建可正常推理的巧妙手段,同时展示了logprobs/top_logprobs、n、temperature等 OpenAI 兼容参数的用法。
验证路由是否生效的关键观察点:打开浏览器控制台,查看supportsSubgroups的日志值,并确认 DevTools Network 面板中实际下载的 WASM 文件名。若设备支持子组,应为.../sg32/....wasm;否则为.../base/....wasm。两种情况下模型都应正常完成对话生成,说明路由切换未破坏推理链路。
改造指南:指向自己的模型与构建
README 明确指出:编辑 examples/subgroups-usage/src/subgroups_usage.ts 即可指向自己的模型路径与 baselinemodel_lib。改造要点:
- 更换模型:把
selectedModel改为自己的model_id,并确保该模型存在于prebuiltAppConfig.model_list(或改用 Option 2 手工配置model_list); - 更换 model_lib:将
model_lib指向自己托管的 WASM 地址,注意保持目录结构满足.../<variant>/<name>-webgpu.wasm且变体目录名为base,否则toSg32ModelLib()会抛错——这是-subgroups后缀路由机制的前提约定; - 注意
modelVersion兼容性:若手工拼接预构建库,须保证路径中的版本与当前 npm 包的modelVersion匹配(见 src/config.ts),否则可能加载到不兼容的 WASM。
进阶:hack WebLLM 核心包
README 特别提示:如果你希望修改 WebLLM 核心包本身,可以将示例的依赖改为本地路径,并跟随仓库源码构建:
"dependencies": { "@mlc-ai/web-llm": "file:../.." }即把examples/subgroups-usage/package.json中的依赖替换为"file:../.."(指向仓库根目录),再按照仓库的从源码构建说明(docs/developer/building_from_source.rst)本地构建 webllm。构建完成后重新npm install即可让示例使用本地核心包。README 强调该方式仅推荐给需要修改 WebLLM 核心包的开发者;仅使用示例本身时,保持 npm 依赖即可。
小结
subgroups-usage示例用约百行 TypeScript 演示了完整的 WebGPU 能力路由链路:探测subgroups特性 → 校验 SG32 硬件条件 → 改写model_lib变体目录 → 按结果构造appConfig→ 创建引擎并验证推理。这一模式可推广到其他能力差异化分发场景(如 shader-f16 特性、不同 KV Cache 策略),是构建"一套代码、多端适配"的浏览器端 LLM 应用的实用参考模板。
【免费下载链接】web-llmHigh-performance In-browser LLM Inference Engine项目地址: https://gitcode.com/GitHub_Trending/we/web-llm
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考