尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

WebGPU + Transformers.js:把 DeepSeek-R1 塞进浏览器,真香!

WebGPU + Transformers.js:把 DeepSeek-R1 塞进浏览器,真香! 谁说前端只能写页面当浏览器遇上 GPU 加速1.5B 参数的大模型也能在你电脑上流畅推理而且全程数据不出设备。这篇实战笔记带你从零搭建一个纯浏览器端的 DeepSeek-R1 对话应用。1. 先看看我们要做什么先别急着写代码你会发现现在我们完全可以在浏览器里跑一个真正的推理模型而不只是玩具 Demo。这个项目基于 HuggingFace 上的DeepSeek-R1-Distill-Qwen-1.5B-ONNX配合 Transformers.js 和 WebGPU实现了模型全部在浏览器端下载、加载、推理利用 GPU 加速生成速度吊打纯 CPU使用 Web Worker 保证页面不卡顿Markdown 流式输出打字机效果说白了这就是一个无需后端、完全本地运行的大模型聊天应用。所有数据留在你的电脑上甚至可以离线使用。2. 环境准备该装的轮子一个都不能少2.1 核心依赖pnpm add huggingface/transformers marked pnpm add -D webgpu/types我们来拆解一下这三个包分别干了什么事huggingface/transformersTransformers.js 的本体相当于 HuggingFace Python 生态的 JavaScript 版本。它能直接从 HuggingFace Hub 下载 ONNX 格式的模型权重并在浏览器中完成分词、编码、推理的完整流程。没有它在浏览器里跑大模型几乎是不可能的事。marked为什么需要这个包因为几乎所有大模型的输出都是Markdown 格式。你想想看AI 回复经常包含代码块、加粗、列表、引用 —— 如果直接返回纯文本这些结构就很难表达。Markdown 是一种轻量级标记语言能让模型用最简单的符号表示富文本语义同时又保持文本可读性。把 Markdown 转成 HTML 展示给用户就是marked这个包的职责。用起来也极简marked.parse(markdownString)就能得到对应的 HTML。webgpu/types这是 WebGPU 的类型声明文件开发阶段用打包后是纯 JS所以安装为devDependency。它让 TypeScript 编译器认识navigator.gpu、GPUAdapter这些实验性 API避免你到处写as any。2.2 解决!!navigator.gpu报错的两种方法很多同学第一次写下!!navigator.gpu时编辑器会无情报错Property gpu does not exist on type Navigator。原因很简单TypeScript 的内置类型定义里还没有包含 WebGPU 的类型毕竟这还是个新鲜出炉的规范。这里有两种优雅的解决方法方法一推荐安装类型声明包pnpm add -D webgpu/types然后在tsconfig.app.json或你项目中的 tsconfig里加上{ compilerOptions: { /* ... */ }, types: [vite/client, webgpu/types] }重启编辑器后navigator.gpu就能被正确识别了。这种方式让代码保持类型安全不会留下后患。方法二类型断言临时方案如果只是快速验证不想动配置文件可以这样const IS_WEBGPU_AVAILABLE !!(navigator as any).gpu;as any告诉 TypeScript“别管了我知道自己在干什么”。但滥用any会让整个项目的类型防护形同虚设只在实验阶段或者确实无法安装类型包时使用。金句不要因为 TS 报错就滥用as any多数时候只是缺了类型声明文件 —— 装一个类型包让代码回归安全。3. 应用骨架主线程与 Web Worker 的分工直接在主线程跑模型那页面肯定会卡成 PPT。我们的架构很清晰主线程 (App.jsx)负责 UI、用户交互通过 Worker 发指令Worker 线程 (worker.js)负责模型加载、推理只通过消息与主线程通信为什么用 Worker因为模型下载和推理都是 CPU / GPU 密集操作放到 Worker 里不会阻塞 UI 渲染保证了丝滑体验。3.1 初始化 Workerconst worker useRef(null); useEffect(() { if (!worker.current) { worker.current new Worker( new URL(./worker.js, import.meta.url), { type: module } ); worker.current.addEventListener(message, onMessageReceived); worker.current.addEventListener(error, onErrorReceived); worker.current.postMessage({ type: check }); // 提前检测 WebGPU } }, []);这里有一行很关键的代码new Worker(new URL(./worker.js, import.meta.url), { type: module })我们逐个参数拆解一下new URL(./worker.js, import.meta.url)URL构造函数接收两个参数第一个是相对路径./worker.js第二个是基准 URLimport.meta.url当前 JS 模块的完整 URL比如http://localhost:5173/src/App.jsx。它会解析出一个新的绝对 URLhttp://localhost:5173/src/worker.js。这样做的好处是无论打包工具Vite、Webpack怎么处理模块路径都能准确定位 Worker 文件避免路径错误。{ type: module }Worker 构造函数的第二个参数表示这个 Worker 将作为 ES Module 执行。这意味着在 worker.js 里可以直接使用import语句比如import { AutoTokenizer } from ...而传统的 Web Worker 默认只支持importScripts()。这个配置项是现代前端工程化的必备选项。3.2 主线程消息处理我们约定一套消息状态码const onMessageReceived (e) { switch (e.data.status) { case loading: // 模型开始下载 case initiate: // 单个文件开始下载 case progress: // 下载进度 case done: // 单个文件完成 case ready: // 模型全部就绪 case start: // 推理开始 case update: // 流式生成的新 token case complete: // 推理完成 case error: // 出错了 } }这种设计让 UI 只用根据状态做展示而真正的重活都藏在 Worker 里。4. Worker 核心单例 流水线4.1 为什么用单例模式大模型的初始化非常昂贵 —— 下载模型文件、构建分词器、预热推理 pipeline。这个 pipeline 我们全局只需要一份每次对话复用即可。单例模式正好解决这个问题保证只有一个实例延迟初始化第一次调用才加载避免重复下载模型class TextGenerationPipeline { static model_id onnx-community/DeepSeek-R1-Distill-Qwen-1.5B-ONNX; static async getInstance(progress_callback null) { this.tokenizer ?? AutoTokenizer.from_pretrained(this.model_id, { progress_callback, }); return Promise.all([this.tokenizer]); } }我们把这段代码解剖一下static model_id静态属性存储 HuggingFace 上的模型仓库 ID。Transformers.js 会通过这个 ID 去远程拉取对应的分词器配置、tokenizer.json 等文件。static async getInstance(progress_callback null)静态方法负责创建并返回单例。参数progress_callback是一个可选的回调函数用于接收模型下载过程中的进度信息如文件名称、已下载百分比。调用方可以传入一个回调比如(x) self.postMessage(x)这样下载进度就能实时发送给主线程。this.tokenizer ?? ...这是空值合并赋值运算符等价于if (this.tokenizer null || this.tokenizer undefined) { this.tokenizer AutoTokenizer.from_pretrained(...) }它确保from_pretrained只会执行一次 —— 后续调用getInstance时this.tokenizer已经有值直接复用不会重复下载。AutoTokenizer.from_pretrained(this.model_id, { progress_callback })这是 Transformers.js 提供的一个智能工厂方法能根据模型 ID 自动匹配并下载对应的分词器。分词器的核心使命大模型内部处理的根本不是文字而是一串整数 IDtoken IDs。分词器就是“文字 ↔ 数字序列”的双向翻译器。文本 → Token IDs把用户输入的你好世界切成[你好, , 世界]再映射为模型词汇表中的编号比如[101, 102, 103]。Token IDs → 文本把模型推理输出的 token 序列逐个解码回人类可读的文字。特殊标记自动添加对话模板需要的s、/s、|user|、|assistant|等控制符。一句话没有分词器模型就是个听不懂人话、也说不出人话的哑巴。AutoTokenizer.from_pretrained()为什么能自动匹配你只需要传入 HuggingFace 上的模型仓库 ID它就会从远程仓库下载tokenizer.json、tokenizer_config.json等文件。根据配置文件自动选择正确的分词器类型BPE、WordPiece、Unigram 等。加载词汇表、合并规则、特殊 token 映射。返回一个可以直接调用encode()/decode()的实例。整个过程对开发者黑盒你不用关心模型内部的分词细节这也是“Auto”的含义。4.2 下载进度反馈from_pretrained的第二个参数里可以传入progress_callback它能收到每个文件的下载进度。我们把进度数据通过postMessage发回主线程页面就能展示“Loading model… 45%”这样友好的提示。async function load() { self.postMessage({ status: loading, data: Loading model... }); const [tokenizer] await TextGenerationPipeline.getInstance((x) { self.postMessage(x); }); // 下载完成后通知主线程 self.postMessage({ status: ready }); }不要让你的用户对着空白页猜进度一个进度条能极大提升等待体验。5. 让 WebGPU 飞起来模型推理的加速器WebGPU 不仅仅用来画三角形它对通用计算GPGPU的支持让浏览器里的 AI 推理成为可能。我们需要在 Worker 里检查设备是否支持 WebGPUasync function check() { try { const adapter await navigator.gpu.requestAdapter(); if (!adapter) throw new Error(No adapter found); // 可选检测 shader-f16 特性等 } catch (e) { self.postMessage({ status: error, data: e.toString() }); } }这行代码是整个 WebGPU 世界的入口const adapter await navigator.gpu.requestAdapter();它的执行流程如下浏览器向操作系统请求一个 GPU 适配器物理显卡的抽象。操作系统返回一个可用的 GPU 句柄如果存在。浏览器封装成GPUAdapter对象包含该 GPU 的特性、限制、队列族等信息。如果系统没有独立显卡比如虚拟机或者浏览器不支持 WebGPUrequestAdapter()会返回null。拿到 adapter 之后能干嘛调用adapter.requestDevice()创建一个GPUDevice这才是你真正干活的“虚拟 GPU 终端”。检查adapter.features看看是否支持shader-f16、timestamp-query等高级特性。查看adapter.limits了解最大绑定组数量、最大缓冲区大小等硬件限制。所以这行代码不是简单的一句“获取 GPU”而是浏览器与显卡握手的起点后续所有并行计算、着色器执行、显存分配都由这个 adapter 派生的 device 完成。如果用户浏览器不支持 WebGPU比如旧版 Firefox 或未开启相关 flag我们就友好地显示提示页面。这也是我们 App 里IS_WEBGPU_AVAILABLE的判断依据。6. 踩坑合集与性能优化建议6.1 TypeScript 报错navigator.gpu不存在安装webgpu/types并在 tsconfig 的types里加入webgpu/types或临时使用(navigator as any).gpu做运行时检测不推荐用于生产6.2 模型下载太慢模型文件存放在 HuggingFace首次加载会下载大约几个 GB 的数据1.5B 的量化版大约 1~2GB浏览器会缓存这些文件通过 Service Worker 或 HTTP 缓存第二次打开速度起飞可以考虑将模型托管到国内 CDN但要注意跨域策略6.3 推理速度与内存占用WebGPU 对显存的使用有限制大模型可能需要shader-f16等特性支持推理时 Worker 占用的内存可以通过navigator.deviceMemory做个粗略判断给低配设备降级提示7. 一点思考前端工程师的 AI 新使命过去我们说“前端搞 AI”总觉得离自己很远要么得学 Python要么得调云端 API。但现在 WebGPU Transformers.js 的组合让浏览器成为最好的 AI 应用运行环境之一隐私优先数据不离开设备适合企业内网、医疗、法律等场景零部署成本一个静态页面就能跑没有服务器开销离线可用模型一次加载后随时随地都能推理说白了以后的前端技能树里一定会多一条“端侧模型部署与优化”。现在开始接触 WebGPU 和 Transformers.js就是在为未来铺路。浏览器不再是内容的展示层它正在成为通用计算平台 —— 而 WebGPU 就是那把打开新世界大门的钥匙。
返回列表