详情

首页手游攻略 大模型真能在浏览器里跑 WebGPU 一招搞定

大模型真能在浏览器里跑 WebGPU 一招搞定

佚名 2026-07-21 17:36:18

你有没有想过,有一天打开一个网页就能直接跑通一个 1.5B 参数的大语言模型?不用买显卡、不用配环境、不用注册账号,甚至断网了都能用。

最近我在做一个叫 DeepSeek-R1 WebGPU 的项目,把 DeepSeek-R1 的 1.5B 蒸馏版模型搬到了浏览器里,全程用 WebGPU 加速,前端用 React + TypeScript + Tailwind CSS。这篇文章我会把整个项目的技术细节掰开揉碎讲清楚。

一、为什么要在大模型这件事上折腾浏览器?

你可能已经用过 ChatGPT 或者 DeepSeek 的网页版。输入问题,等待几秒,答案就出来了。体验不错对吧?但这里面有几个不太容易被注意到的问题。

第一个问题是成本。 大模型 API 是按 token 计费的——token 可以理解成模型处理文字时的最小单位,一个汉字大概对应 1 到 2 个 token。你问得越多、聊得越长,费用就越高。

第二个问题是隐私。 每一次你向 API 发送请求,你的问题和上下文都会被传送到远端的服务器。如果你问的是公司内部的代码、客户的敏感信息、或者任何不方便被第三方看到的内容,这个模式就有点令人担忧了。

第三个问题是网络依赖。 没网就彻底歇菜。

那有没有办法让模型跑在本地、不联网、还不用花钱?有的。这就是端侧模型的思路——把模型直接部署在用户的设备上(手机、电脑、汽车的中控系统),推理计算全部在本地完成。

浏览器是端侧模型最轻量的分发平台。用户点开一个网页,模型自动下载到本地,推理全程在浏览器里跑,数据不出设备,用完即走。

但问题来了:浏览器的性能够跑大模型吗?

二、WebGPU:浏览器调用显卡的新方式

大模型推理的本质就是大量的矩阵乘法运算——你可以把它想象成无数个数字表格在不停地做乘法和加法。这种运算恰恰是 GPU(图形处理器)最擅长的事情。

过去浏览器想用 GPU 算力,主要靠 WebGL。但 WebGL 本来是为画 3D 图形设计的,用来做通用计算属于“跨界打工”,效率并不高。

WebGPU是新一代的 Web 标准,专门为图形渲染和通用计算设计。它让网页开发者能直接调用系统 GPU 进行高性能计算。简单说,WebGPU 就是浏览器里的 GPU 计算接口,类似 CUDA 的 Web 版。

有了 WebGPU,浏览器里也能做矩阵乘法 → 也就能跑 Transformer 模型 → 也就能跑大语言模型。

截至 2024 年 10 月,WebGPU 的全球浏览器支持率大约在 70% 左右。Chrome 113+、Edge 113+ 都已经支持,Firefox 和 Safari 需要通过实验性开关开启。

在代码里检测浏览器是否支持 WebGPU,只需要一行:

typescript

const IS_WEBGPU_AVAILABLE = !!navigator.gpu;

navigator.gpu是浏览器提供的 API 入口点。如果浏览器支持 WebGPU,这个属性会返回一个GPU对象;如果不支持,返回undefined!!的作用是把任何值都转成布尔值——undefined变成false,有值变成true

三、技术栈为什么选 React + TypeScript?

项目搭好架子之后,下一步就是选技术栈。我选了三个核心工具:React、TypeScript、Tailwind CSS。

3.1 React:组件化思想

传统网页开发里,HTML 管结构、CSS 管样式、JavaScript 管逻辑——三者是分离的。这种模式在简单页面里很好用,但页面一复杂,改一个按钮可能要同时修改三个文件,维护起来很头疼。

React 换了一种思路:把 HTML、CSS、JavaScript 打包成一个独立的功能单元,叫做“组件” 。一个按钮是一个组件,一个输入框是一个组件,整个聊天界面也是一个组件。组件可以嵌套、可以复用,像搭积木一样拼出完整的页面。

在 React 里,组件就是一个 JavaScript 函数。这个函数返回一段叫做 JSX 的代码——JSX 看起来像 HTML,但实际上是 JavaScript 的语法扩展。

下面是一个最简单的 React 组件:

tsx

function Welcome() {return <h1>你好,世界</h1>;}

这个函数返回了一个<h1>标签,当你在页面上使用<Welcome />时,浏览器就会渲染出“你好,世界”这个标题。

3.2 useState:让界面“活”起来

普通的 JavaScript 变量改了之后,页面不会自动更新。你需要手动操作 DOM(文档对象模型)去修改页面内容——比如document.getElementById('xxx').innerText = '新内容'

React 的useState解决了这个问题。它创建了一个“响应式”的数据状态:状态变了,界面会自动跟着变。

tsx

import { useState } from 'react';function Counter() {// 声明一个叫 count 的状态,初始值是 0// setCount 是修改这个状态的函数const [count, setCount] = useState(0);return (<p><p>你点击了 {count} 次</p><button onClick={() => setCount(count + 1)}>点我</button></p>);}

这段代码做了什么?useState(0)创建了一个初始值为 0 的状态。点击按钮时调用setCount(count + 1)count变成 1,页面上的{count}自动更新为 1。你不需要写任何 DOM 操作代码。

在 DeepSeek-R1 项目里,我用了多个状态来管理模型加载的不同阶段:

tsx

const [status, setStatus] = useState(null);// 模型状态:null | 'loading' | 'ready'const [error, setError] = useState(null);// 错误信息const [loadingMessage, setLoadingMessage] = useState("");// 加载进度提示

status一开始是null,表示模型还没开始加载。开始加载后变成'loading',加载完成变成'ready'。界面根据status的不同值显示不同的内容——这就是“数据驱动界面”的核心思想。

3.3 useEffect:在合适的时机做事情

useEffect是 React 的另一个核心 Hook(钩子函数),用来处理“副作用”。什么是副作用?任何不属于“渲染界面”本身的事情——比如发请求、操作浏览器 API、读写本地存储等。

useEffect接收两个参数:一个函数(你要做的事情),和一个依赖数组(控制什么时候做)。

tsx

import { useEffect } from 'react';function App() {useEffect(() => {// 这个函数会在组件第一次渲染到屏幕上之后执行console.log('组件已经挂载完成');// 这里可以做一些初始化的事情,比如加载模型}, []);// 空数组表示只在组件挂载时执行一次}

在 DeepSeek-R1 项目里,useEffect用来在页面加载后自动开始下载和初始化模型。[]这个空数组保证了初始化逻辑只在组件第一次出现时执行一次,不会重复运行。

3.4 TypeScript:给 JavaScript 加上类型约束

JavaScript 是动态类型语言——变量可以是数字,也可以是字符串,还可以随时改变类型。灵活是灵活了,但大型项目里这种灵活性往往会变成隐患。

TypeScript给 JavaScript 加上了类型系统。你可以明确告诉编译器:这个变量是数字、那个参数是字符串、这个函数返回布尔值。

typescript

// 变量名后面跟 : 类型 就是类型注解let name: string = "张三";// name 只能是字符串let age: number = 25; // age 只能是数字let isReady: boolean = true;// isReady 只能是 true 或 false// 函数参数和返回值也可以加类型function greet(person: string): string {return "你好," + person;}

如果你试图把数字赋值给name,TypeScript 编译器会报错。这听起来有点啰嗦,但在大型项目里,类型系统能帮你提前发现很多低级错误,也让代码的意图更清晰。

3.5 Tailwind CSS:原子类让样式更高效

传统 CSS 开发里,你要给每个元素起一个类名,然后在单独的 CSS 文件里写样式。类名一多,维护就变得困难。

Tailwind CSS换了一种方式:它提供了大量细粒度的工具类(也叫原子类),每个类只做一件事。你想让元素有内边距,就用p-4;想让它居中,就用text-center;想让它水平排列,就用flex

tsx

// 不用单独写 CSS 文件,直接在 className 里组合原子类<p className="flex flex-col items-center max-w-[400px] text-center"><h1 className="text-4xl font-bold mb-1">DeepSeek-R1 WebGPU</h1></p>

flex flex-col让子元素纵向排列,items-center让它们在交叉轴方向居中,text-4xl设置大号字体。你不需要离开 JSX 文件就能完成所有样式工作。

3.6 ESLint:代码风格的“守门员”

团队协作时,每个人写代码的习惯不一样——有人用单引号有人用双引号,有人加分号有人不加。这些小差异累积起来会让代码库变得杂乱。

ESLint是一个代码检查工具,它可以根据你配置的规则自动检查代码风格,甚至自动修复。大公司的前端项目几乎都会配置 ESLint,保证代码风格统一。

四、模型是怎么在浏览器里跑起来的?

聊完前端技术栈,终于到了最核心的问题:模型到底怎么在浏览器里跑?

4.1 Transformers.js:Hugging Face 的浏览器版

Transformers.js是 Hugging Face 推出的 JavaScript 库,让你能在浏览器或 Node.js 环境里直接运行预训练的 AI 模型。

它在设计上尽量对标 Python 版的 Transformers 库,但底层实现不同——Transformers.js 使用ONNX Runtime来执行模型推理。

4.2 ONNX:模型的“通用语言”

不同 AI 框架(PyTorch、TensorFlow 等)训练出来的模型格式各不相同。ONNX(Open Neural Network Exchange,开放神经网络交换格式)是一个开放标准,让模型可以在不同框架和平台之间迁移。

ONNX Runtime 是专门用来执行 ONNX 模型的高性能推理引擎。ONNX Runtime Web 则是它的浏览器版本。

Transformers.js 只支持 ONNX 格式的模型。好消息是,Hugging Face 的模型库里已经有大量转换好的 ONNX 模型可以直接使用。如果你想用自己的模型,也可以用 Optimum 工具把 PyTorch 或 TensorFlow 模型转成 ONNX 格式。

4.3 项目里用的具体模型

这个项目加载的是DeepSeek-R1-Distill-Qwen-1.5B-ONNX。

来拆解一下这个名字:

  • DeepSeek-R1:DeepSeek 公司推出的推理模型
  • Distill:蒸馏版——用大模型(老师)的知识去训练小模型(学生),让小模型在更小的参数量下达到接近大模型的效果
  • Qwen:通义千问的模型架构
  • 1.5B:15 亿参数
  • ONNX:模型是 ONNX 格式

15 亿参数听起来很多,但在大模型世界里其实算“轻量级”。经过蒸馏和量化之后,这个模型已经能在浏览器里跑起来了。

在代码里加载模型大致是这样的(简化版):

typescript

import { pipeline } from '@huggingface/transformers';// 创建文本生成管道// pipeline 函数会下载模型、加载到内存、准备推理const generator = await pipeline('text-generation',// 任务类型:文本生成'onnx-community/DeepSeek-R1-Distill-Qwen-1.5B-ONNX', // 模型名称{ device: 'webgpu' }// 使用 WebGPU 加速);// 执行推理const result = await generator('你好,请介绍一下自己');console.log(result);

pipeline是 Transformers.js 提供的最简单的使用方式——它把预处理、模型推理、后处理全部打包在一起。你只需要告诉它“我要做什么任务”和“用哪个模型”,剩下的它帮你搞定。

{ device: 'webgpu' }这个配置告诉 Transformers.js 用 WebGPU 来加速计算。如果没有这个配置,模型会默认用 CPU 运行,速度会慢很多。

五、完整示例:从零开始看 App 组件

现在我们把所有知识点串起来,看看 DeepSeek-R1 项目的App.tsx组件完整长什么样,逐行拆解。

tsx

import { useState, useEffect } from 'react';function App() {// 1. 声明三个响应式状态const [status, setStatus] = useState(null);const [error, setError] = useState(null);const [loadingMessage, setLoadingMessage] = useState("");// 2. 检测浏览器是否支持 WebGPUconst IS_WEBGPU_AVAILABLE = !!navigator.gpu;// 3. 组件挂载后执行初始化useEffect(() => {console.log('组件已经挂载完成');// 这里会触发模型的下载和加载}, []);// 4. 根据 WebGPU 支持情况渲染不同内容return IS_WEBGPU_AVAILABLE ? (<p className="flex flex-col h-screen mx-auto items-center justify-end"><p className="flex flex-col items-center"><h1 className="text-4xl font-bold">DeepSeek-R1 WebGPU</h1><h2>一个在浏览器本地运行的推理模型</h2></p>{/* 状态和错误信息的展示 */}{error && (<p className="text-red-500"><p>加载模型失败:</p><p>{error}</p></p>)}</p>) : (<p>您的浏览器还不支持 WebGPU</p>);}export default App;

逐行来看:

第 1 行:从 React 库中导入useStateuseEffect两个 Hook。

第 4-9 行:App组件是一个函数。函数内部用useState声明了三个状态——status追踪模型加载进度,error记录错误信息,loadingMessage显示加载提示。

第 12 行:IS_WEBGPU_AVAILABLE是一个常量,通过!!navigator.gpu检测浏览器是否支持 WebGPU。

第 15-18 行:useEffect在组件挂载后执行。这里会发起模型加载的请求(实际代码中会更复杂,涉及异步加载和进度更新)。

第 21-39 行:return语句返回 JSX。三元运算符? :根据IS_WEBGPU_AVAILABLE的值决定渲染哪个界面——支持 WebGPU 就显示主界面,不支持就显示提示信息。

className里的flexflex-colitems-centertext-4xlfont-bold都是 Tailwind CSS 的原子类,分别控制布局方向、对齐方式、字体大小和粗细。

六、总结

这个项目把几个关键技术串在了一起:

  1. 端侧模型的思路——模型跑在用户本地,数据不出设备,不依赖网络
  2. WebGPU提供 GPU 加速能力,让浏览器能跑得动大模型
  3. Transformers.js + ONNX Runtime Web负责模型的加载和推理
  4. React的组件化和响应式状态管理让 UI 开发更高效
  5. TypeScript的类型系统保证代码质量
  6. Tailwind CSS的原子类让样式管理更轻松
  7. ESLint统一代码风格

打开网页就能跑大模型——这已经不是什么未来概念了。随着 WebGPU 浏览器支持率的提升和端侧模型性能的持续优化,浏览器正在变成一个越来越强大的 AI 计算平台

相关资讯
点击查看更多
游戏推荐
推荐专题
热门阅读
推荐下载