ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

React + WebGPU 在浏览器运行 DeepSeek:从 Worker 通信到流式生成

React + WebGPU 在浏览器运行 DeepSeek:从 Worker 通信到流式生成 React WebGPU 在浏览器运行 DeepSeek从 Worker 通信到流式生成本文基于webgpu-deepseek项目源码整理重点解释模型如何在浏览器中加载、推理和返回结果。源码静态阅读运行未验证实际 WebGPU 兼容性、模型下载情况和生成速度需要在目标环境单独确认。你会得到什么这个项目不是简单地在页面里调用一个模型而是拆成了三层React 主线程负责输入框、聊天列表和加载进度。Web Worker负责下载模型、初始化 WebGPU 和执行推理。Transformers.js负责 tokenizer、模型加载和文本生成。核心判断是模型生命周期和页面交互要分开管理缓存和流式消息是浏览器端运行大模型的关键。1. 先看完整调用链main.tsx ↓ 挂载 App App.tsx ↓ 创建 Worker发送 check/load/generate worker.js ↓ 检测 WebGPU ↓ 加载 tokenizer 和 model ↓ TextStreamer 流式生成 ↓ postMessage 返回状态和文本 App.tsx ↓ 更新 React state Chat.jsx ↓ Markdown、HTML 安全清理、数学公式渲染主线程和 Worker 之间不是直接调用函数而是约定消息格式消息类型Worker 行为页面用途check检查 WebGPU 适配器判断能力load下载并初始化模型显示加载进度generate生成回答显示流式文本interrupt中断生成响应停止按钮reset清理缓存和中断状态开始新的状态2. 为什么模型放进 Web WorkerApp.tsx创建了一个 module Workerworker.currentnewWorker(newURL(./worker.js,import.meta.url),{type:module,});worker.current.postMessage({type:check});页面主线程擅长处理 DOM 和用户交互但模型下载、WebGPU 初始化和推理都可能是耗时任务。Worker 可以把这些工作放到后台线程主线程只接收结果并更新 UI。Worker 中不能直接使用window、document操作页面因此它通过self.postMessage({status:update,output,});把结果发送给 React。这里有一个需要重点记住的地方postMessage不是普通函数调用。主线程发送的是一份消息数据Worker 再根据type判断要做什么。3. WebGPU 检查分两步页面中有快速判断constIS_WEBGPU_AVAILABLE!!navigator.gpu;它只说明浏览器是否提供了navigator.gpu属性。Worker 中还会继续请求适配器constadapterawaitnavigator.gpu.requestAdapter();if(!adapter){thrownewError(WebGPU is not supported (no adapter found));}可以把两者理解为!!navigator.gpu有没有 WebGPU 入口。requestAdapter()能不能找到实际可用的 GPU 适配器。所以第一个判断为true并不代表后续模型推理一定成功。浏览器版本、显卡驱动、模型格式和显存都可能影响结果。4.TextGenerationPipeline如何避免重复加载项目用一个类统一管理 tokenizer 和模型classTextGenerationPipeline{staticmodel_idonnx-community/DeepSeek-R1-Distill-Qwen-1.5B-ONNX;staticasyncgetInstance(progress_callbacknull){this.tokenizer??AutoTokenizer.from_pretrained(this.model_id,{progress_callback,});this.model??AutoModelForCausalLM.from_pretrained(this.model_id,{dtype:q4f16,device:webgpu,progress_callback,});returnPromise.all([this.tokenizer,this.model]);}}static做了什么static让getInstance属于类本身因此可以直接调用TextGenerationPipeline.getInstance();??做了什么this.model??loadModel();只有this.model为null或undefined时才加载。第一次调用会下载和初始化后续调用复用原来的 Promise 或模型对象。这体现了“单例式缓存”思想模型初始化成本高生成多次回答时不应该反复加载。两个参数的含义dtype:q4f16,device:webgpu,源码意图是使用量化数据类型降低资源压力并让模型运行在 WebGPU 设备上。具体兼容性和性能不能只靠静态代码判断本文不把它们描述成已验证结果。5. 从聊天消息到模型输入用户消息最终通过constinputstokenizer.apply_chat_template(messages,{add_generation_prompt:true,return_dict:true,});转换为模型需要的输入。messages是聊天结构例如[{role:user,content:请解释 Web Worker},]模型真正处理的不是这段普通字符串而是 tokenizer 转换后的 token 数据。add_generation_prompt: true的作用是补充生成提示让模型知道接下来应该由 assistant 回答。6.TextStreamer为什么能实现流式输出模型生成不是一次性返回全部文本而是不断生成 token。项目配置了conststreamernewTextStreamer(tokenizer,{skip_prompt:true,skip_special_tokens:true,callback_function,token_callback_function,});其中callback_function获得已经转换好的文本片段并发送给主线程。token_callback_function每生成 token 时统计数量和速度。skip_prompt不重复显示输入提示词。skip_special_tokens隐藏特殊 token。发送给页面的消息大致是self.postMessage({status:update,output,tps,numTokens,state,});React 收到update后把output追加到最后一条 assistant 消息因此用户能看到逐步生成的回答。7. 思考过程和答案如何区分代码通过编码think/think拿到开始和结束 tokenconst[START_THINKING_TOKEN_ID,END_THINKING_TOKEN_ID]tokenizer.encode(think/think,{add_special_tokens:false,});当生成到结束思考 token 时if(tokens[0]END_THINKING_TOKEN_ID){stateanswering;}前端根据answerIndex把内容拆成 thinking 和 answer并允许用户展开或收起思考过程。8. 页面渲染为什么需要 DOMPurifyChat.jsx的渲染链是模型 Markdown 文本 ↓ marked.parse HTML 字符串 ↓ DOMPurify.sanitize 安全一些的 HTML ↓ dangerouslySetInnerHTML 插入 React 页面关键代码constresultDOMPurify.sanitize(marked.parse(text,{async:false,breaks:true,}),);Markdown 转 HTML 后如果直接使用dangerouslySetInnerHTML就需要考虑危险 HTML 内容。项目先使用 DOMPurify 清理这是一个重要的安全边界。另外MathJax负责数学公式显示适合模型回答方程、代码解释等内容。9. 模型加载与生成的两个阶段加载阶段发送 load ↓ 发送 loading ↓ getInstance 下载 tokenizer 和 model ↓ 发送下载进度 ↓ 用简单输入生成 1 个 token 进行预热 ↓ 发送 ready预热的目的是提前触发模型和 WebGPU 的初始化工作让正式提问时少承担一部分首次初始化成本。实际耗时和效果需要运行验证。生成阶段发送 generate ↓ reset stopping_criteria ↓ 准备 chat template ↓ model.generate ↓ TextStreamer 持续发送 update ↓ 发送 complete用户点击停止时发送interruptWorker 调用stopping_criteria.interrupt();这是一种由生成过程主动检查停止条件的中断设计。10. 排错清单现象优先检查navigator.gpu类型警告是否安装并配置webgpu/types不要长期依赖as anyWorker 无法加载new URL引用的文件名是否和src中实际文件一致Failed to resolve importpackage.json是否声明对应依赖包管理器是否混用页面一直不能输入Worker 是否发送ready主线程是否正确设置status只有完整结果没有实时输出TextStreamer是否传入streamer是否处理updateMarkdown 渲染异常marked输入、反斜杠处理和 MathJax 配置HTML 安全风险是否先调用DOMPurify.sanitize停止按钮无效stopping_criteria是否传给model.generate结语这个项目最值得迁移的设计不是某一个 API而是职责划分React 处理交互Worker 管理重任务模型类负责资源生命周期消息状态负责跨线程反馈。理解这条调用链后再学习 WebGPU、tokenizer 或流式生成都会更容易定位问题。建议下一步按以下顺序实践先单独完成 Worker 的消息往返再接入 tokenizer最后接入模型和流式 UI。本文代码和项目运行结果均未验证部署前应补做依赖安装、构建、浏览器 WebGPU 能力和模型加载检查。标签: React, WebGPU, Transformers.js, Web Worker
返回列表