尧图网站建设 尧图网络
  • 首页
  • 关于我们
  • 服务项目
  • 案例展示
  • 建站流程
  • 资讯中心
  • 联系我们
首页/资讯中心/详情

DeepSeek-R1 WebGPU (1):在浏览器里跑大模型

DeepSeek-R1 WebGPU (1):在浏览器里跑大模型
📅 发布时间:2026/7/21 11:54:27

文章目录

    • 一、端侧模型:AI 不再只活在云端
      • 1.1 什么是端侧模型?
      • 1.2 为什么端侧模型突然火了?
      • 1.3 本项目的模型选择
    • 二、React + TypeScript:为什么是 AI 时代的首选?
      • 2.1 React vs Vue:选型的底层逻辑
      • 2.2 新建项目:React + TS + ESLint 一步到位
      • 2.3 Vite 配置:让 Tailwind 跑起来
    • 三、TailwindCSS:告别手写 CSS 的原子化方案
      • 3.1 传统 CSS 的痛点
      • 3.2 Tailwind 的思路:原子类
      • 3.3 Tailwind 运行原理
      • 3.4 为什么是 `className` 而不是 `class`?
    • 四、React 组件:函数就是积木
      • 4.1 Vue 组件 vs React 组件
      • 4.2 入口文件:React 是怎么启动的?
    • 五、代码详解:App.tsx 逐段解析
      • 5.1 导入 Hooks
      • 5.2 数据状态:响应式的核心
      • 5.3 WebGPU 检测:一行代码判断浏览器能力
      • 5.4 组件生命周期:useEffect 的执行时机
      • 5.5 JSX:在 JavaScript 里写 HTML
      • 5.6 Tailwind 原子类实战解读
      • 5.7 模型信息展示区解析
      • 5.8 错误处理状态
    • 六、全文总结
    • 七、核心知识点复盘
    • 八、常见问题 / 避坑指南

一份保姆级技术复盘,覆盖端侧模型、React + TypeScript、TailwindCSS、JSX 等核心技能点,适合学习复盘和技术分享。


一、端侧模型:AI 不再只活在云端

1.1 什么是端侧模型?

平时我们使用 ChatGPT、DeepSeek、Kimi 等 AI 助手,流程是这样的:

用户输入 → 网络请求 → 远程服务器(GPU集群) → 推理计算 → 返回结果

这种方式叫云端推理,模型跑在厂商的服务器上。它有两个绕不开的问题:

  • 贵:厂商需要采购大量 GPU,成本最终转嫁给你(API 按 token 计费)。
  • 不安全:你的输入内容(context)会随着请求发送到远端服务器,数据隐私无法完全掌控。

而端侧模型(On-Device Model)指的是模型直接运行在你的设备上——手机、电脑、汽车、甚至浏览器。数据不出设备,推理在本地完成。

1.2 为什么端侧模型突然火了?

关键推动力来自两点:

推动因素说明
开源小参数模型成熟Llama、Qwen、Gemma 等 1B~7B 参数模型,在特定任务上表现已经不输大模型
WebGPU 的到来浏览器可以直接调用 GPU 做并行计算,不再依赖 WebGL 的"曲线救国"

Ollama 就是典型的端侧方案——你下载模型到本地,通过命令行或 API 调用。而本项目的更进一步:模型直接在浏览器里下载、加载、推理,用户打开网页就能用,用完即走,不占用磁盘。

1.3 本项目的模型选择

项目使用的是DeepSeek-R1-Distill-Qwen-1.5B:

  • 这是 DeepSeek-R1(推理模型)的蒸馏版,参数量压缩到 15 亿。
  • 基于Qwen架构,专为本地轻量推理优化。
  • 模型格式为ONNX(Open Neural Network Exchange,开放神经网络交换格式),跨平台跨框架。
  • 托管在HuggingFace(全球最大开源模型社区),通过Transformers.js加载。

关键理解:蒸馏 = 用大模型"教"小模型。大模型生成高质量答案 → 小模型模仿学习 → 保留大部分推理能力但体积小很多。


二、React + TypeScript:为什么是 AI 时代的首选?

2.1 React vs Vue:选型的底层逻辑

你可能会问:Vue 上手更简单,为什么 AI 项目偏爱 React?

维度ReactVue
学习曲线较陡(需要理解 JSX、Hooks、函数式编程)平缓(模板语法接近 HTML)
大型项目函数式编程天然适合抽象和复用,生态更成熟中小项目效率极高
AI/ML 生态Transformers.js、LangChain.js、Vercel AI SDK 都优先支持 React社区也在跟进,但目前示例偏少
招聘市场大厂、AI Startup 的首选国内中小企业用得更多

一句话总结:React 的上限更高,Vue 的下限更低。做 AI 相关的复杂交互,React 的函数式思想更适合。

2.2 新建项目:React + TS + ESLint 一步到位

# 使用 Vite 创建项目(最快的构建工具)npmcreate vite@latest webgpu-demo ----templatereact-tscdwebgpu-demonpminstall

创建完成后,你会得到以下关键文件:

webgpu-demo/ ├── src/ │ ├── App.tsx # 主组件(你写代码的地方) │ ├── App.css # 组件样式 │ ├── main.tsx # 入口文件(挂载 React 到页面) │ └── index.css # 全局样式 + Tailwind 导入 ├── eslint.config.js # ESLint 代码约束配置 ├── vite.config.ts # Vite 构建配置 ├── tsconfig.json # TypeScript 配置 └── package.json # 依赖管理

package.json 的核心依赖解读:

{"dependencies":{"@tailwindcss/vite":"^4.3.3",// TailwindCSS Vite 插件"react":"^19.2.6",// React 核心库"react-dom":"^19.2.6",// React DOM 渲染(浏览器端)"tailwindcss":"^4.3.3"// TailwindCSS 框架本体},"devDependencies":{"typescript":"~6.0.2",// TypeScript 编译器"eslint":"^10.3.0",// 代码规范检查"vite":"^8.0.12"// 构建工具}}

ESLint 的作用是什么?

ESLint 是代码"纪律委员"——约束团队写出一致风格的代码。比如用单引号还是双引号?结尾要不要分号?这些规则在eslint.config.js中统一配置。大公司必备,否则代码合并时就是灾难。

// eslint.config.js 关键配置exportdefaultdefineConfig([globalIgnores(['dist']),// 忽略构建产物{files:['**/*.{ts,tsx}'],// 对 TS 和 TSX 文件生效extends:[js.configs.recommended,// JS 基础规则tseslint.configs.recommended,// TypeScript 规则reactHooks.configs.flat.recommended,// React Hooks 规则],},])

2.3 Vite 配置:让 Tailwind 跑起来

// vite.config.tsimport{defineConfig}from'vite'importreactfrom'@vitejs/plugin-react'importtailwindcssfrom'@tailwindcss/vite'exportdefaultdefineConfig({plugins:[react(),// 让 Vite 支持 React JSXtailwindcss(),// 让 Vite 处理 Tailwind 原子类],})

Vite 插件机制很简单:每个插件负责一块能力,像搭积木一样拼起来。react()负责编译 JSX,tailwindcss()负责扫描和注入 CSS。


三、TailwindCSS:告别手写 CSS 的原子化方案

3.1 传统 CSS 的痛点

回想一下你写 CSS 的流程:

  1. 想一个 class 名(.my-cool-button)
  2. 找到对应文件(或<style>块)
  3. 写选择器 + 规则(color: red; font-size: 16px;)
  4. 反复调试样式冲突和优先级

这个过程太低效了——你在两个文件之间来回切换,还要想命名、管优先级。

3.2 Tailwind 的思路:原子类

Tailwind 的做法是:不写 CSS 规则,直接写类名。

<!-- 传统方式 --><buttonclass="my-button">点击</button><style>.my-button{background:blue;color:white;padding:8px 16px;border-radius:4px;}</style><!-- Tailwind 方式 --><buttonclassName="bg-blue-500 text-white px-4 py-2 rounded">点击</button>

每一个 class 名 = 一条 CSS 规则。bg-blue-500就是background-color: blue,px-4就是padding-left: 1rem; padding-right: 1rem;。

为什么这更好?

  • 不用命名:不用再纠结 class 叫btn-primary还是btn-main
  • 所见即所得:看到类名就知道样式,不用跳转到 CSS 文件
  • 自然语言友好:类名是用英文单词组合的,和 AI 编程(Vibe Coding)天然契合
  • 按需生成:Vite 插件只提取你用到的类名,打包体积很小

3.3 Tailwind 运行原理

Tailwind 不是原生 CSS——浏览器不认识bg-blue-500。它的工作流程是:

1. 你写 className="bg-blue-500 text-white" ↓ 2. Tailwind Vite 插件扫描所有 .tsx/.jsx 文件 ↓ 3. 识别到 bg-blue-500 → 找到对应 CSS: background-color: #3b82f6; ↓ 4. 把这条 CSS 注入到最终构建的样式文件中 ↓ 5. 浏览器正确渲染蓝色背景

核心原理一句话:Tailwind 是一个"类名到 CSS 规则"的映射字典。插件在构建时扫描代码 → 查字典 → 生成最小化的 CSS 文件。你没有用到的类名不会出现在最终产物中。

在项目中的体现:

/* src/index.css — 只需要一行! */@import"tailwindcss";/* 下面是项目自定义的 CSS 变量和全局样式 */:root{--text:#6b6375;--bg:#fff;/* ... */}

@import "tailwindcss"这一行就是 Tailwind 的"入口",插件会从这里开始注入扫描到的所有原子类。

3.4 为什么是className而不是class?

这是一个非常经典的困惑。答案很简单:

JSX 中写<div class="xxx">会出问题,因为class是 JavaScript 的关键字(用于定义类/面向对象编程)。

React 团队为了避免语法冲突,用className替代了class:

// ❌ 错误:class 是 JS 关键字 <div class="container"> // ✅ 正确:使用 className <div className="container">

编译后<div className="container">→ 原生 DOM 的<div class="container">,效果一模一样。


四、React 组件:函数就是积木

4.1 Vue 组件 vs React 组件

Vue 组件是"三件套"——HTML、CSS、JS 分块写在一个.vue文件里:

<template> <div>{{ message }}</div> </template> <script setup> const message = 'Hello' </script> <style scoped> div { color: red; } </style>

React 组件就是一个函数,返回 HTML(JSX):

function MyComponent() { const message = 'Hello' // JS 逻辑 // CSS 通过 import 或 Tailwind 引入 return <div>{message}</div> // 返回 HTML }

两者的本质区别:

VueReact
组件形态.vue单文件(模板+逻辑+样式)函数(JS + JSX)
入门难度低(模板接近原生 HTML)中(需要理解 JSX 和函数式编程)
抽象能力指令体系(v-if, v-for)JavaScript 原生能力(&&, map)

React 的理念:组件就是函数,函数就是组件。所有 JavaScript 的能力(条件判断、循环、解构)都能直接在"模板"里用。

4.2 入口文件:React 是怎么启动的?

// src/main.tsx — React 应用的"点火开关" import { StrictMode } from 'react' import { createRoot } from 'react-dom/client' import './index.css' // 全局样式(含 Tailwind) import App from './App.tsx' // 导入根组件 createRoot(document.getElementById('root')!).render( <StrictMode> <App /> </StrictMode>, )

执行流程:

  1. createRoot(...)— 找到index.html中的<div id="root">,把它变成 React 的"根容器"
  2. .render(...)— 把<App />组件渲染到这个容器里
  3. <StrictMode>— 开发模式下的"严格检查",会帮你发现潜在问题(比如不安全的生命周期),生产环境自动失效

五、代码详解:App.tsx 逐段解析

下面逐一解析App.tsx的每一部分代码,确保你完全理解。

5.1 导入 Hooks

import { useState, useEffect } from 'react'
  • useState:React 的"状态钩子"。让你在函数组件中创建响应式数据——数据变了,界面自动更新。
  • useEffect:React 的"副作用钩子"。组件渲染完成后自动执行指定代码(比如发请求、设置定时器)。
  • 这两个函数都以use开头,这是 React 的约定——所有 Hooks 都遵循useXxx命名模式。

5.2 数据状态:响应式的核心

function App() { // status: 当前加载状态 // null = 初始 / 'loading' = 加载中 / 'ready' = 模型就绪 const [status, setStatus] = useState(null) // error: 错误信息(演示用 "出错了" 作为初始值) const [error, setError] = useState("出错了") // loadingMessage: 加载提示文本 const [loadingMessage, setLoadingMessage] = useState("") // progressItems: 模型文件下载进度 const [progressItems, setProgressItems] = useState([{ file: 'model.onnx', // 模型文件名 progress: 0, // 当前已下载字节数 total: 5465458632 // 模型总大小(约 5.5GB) }])

useState语法详解:

const [值, 修改值的函数] = useState(初始值)

这是数组解构语法——useState返回一个长度为 2 的数组:

  • 第一个元素是当前状态值(只读,不要直接修改)
  • 第二个元素是更新函数(想改状态?调它!)
// ❌ 错误:直接修改不会触发界面更新 status = 'ready' // ✅ 正确:调用更新函数 setStatus('ready') // 状态变了 → React 自动重新渲染组件

为什么叫"响应式"?数据(状态)和界面是绑定的。就像川剧变脸——你切换一张脸谱(改状态),观众看到的脸就变了(界面更新)。你不需要手动操作 DOM,React 帮你做好了。

5.3 WebGPU 检测:一行代码判断浏览器能力

const IS_WEBGPU_AVAILABLE = !!navigator.gpu

这行代码值得拆开理解:

表达式含义
navigator.gpu浏览器是否暴露 GPU 接口。支持 WebGPU → 返回对象;不支持 →undefined
!navigator.gpu取反。支持 →false;不支持 →true
!!navigator.gpu再取反(双重否定等于肯定)。支持 →true;不支持 →false

!!是一种将任意值强转为布尔值的 JS 技巧:

!!{}// true!!undefined// false!!null// false!!0// false!!'hello'// true

5.4 组件生命周期:useEffect 的执行时机

useEffect(() => { console.log('组件已经挂载完成') setTimeout(() => { // setStatus('ready') // 1 秒后将状态改为 ready }, 1000) }, []) // ← 空数组,只执行一次

useEffect的第二个参数是关键:

第二个参数执行时机
[](空数组)组件首次渲染后执行一次
[status]首次渲染后 +status变化后执行
不传每次渲染后都执行

这里的[]意味着"组件挂载完成时执行,只此一次"——非常适合做初始化操作(加载模型、请求数据等)。

5.5 JSX:在 JavaScript 里写 HTML

return ( IS_WEBGPU_AVAILABLE ? ( <div className="flex flex-col h-screen ..."> <h1 className="text-4xl font-bold mb-1">DeepSeek-R1 WebGPU</h1> {/* ... */} </div> ) : ( <div>您的浏览器还不支持WebGPU</div> ) )

JSX(JavaScript XML)是 React 最骄傲的特性之一——在 JS 代码中直接写 HTML 标签。

几个 JSX 核心规则:

① 条件渲染:三目运算符

{condition ? <ComponentA /> : <ComponentB />}

② 列表渲染:.map()

{items.map(item => <li key={item.id}>{item.name}</li>)}

③ 嵌入 JS 表达式:{}大括号

<p>计算结果:{1 + 1}</p> // → 计算结果:2 <p>用户名:{user.name}</p> // → 用户名:张三

④ 注释:大括号包裹

{/* 这是 JSX 注释,和 JS 多行注释一样的写法 */}

⑤ 条件显示:&&短路

{error && ( <div className="text-red-500"> <p>Unable to load model due to the following error:</p> <p className="text-sm">{error}</p> </div> )}

当error为空字符串或null时,&&右边不执行,整个<div>不渲染。这是 React 中极常用的条件渲染模式。

5.6 Tailwind 原子类实战解读

来看看项目中用到的关键原子类:

<div className="flex flex-col h-screen mx-auto items-center justify-end text-gray-800 bg-white">
类名对应 CSS含义
flexdisplay: flex开启弹性布局
flex-colflex-direction: column主轴方向为垂直(从上到下)
h-screenheight: 100vh高度 = 整个屏幕高度
mx-automargin-left: auto; margin-right: auto水平居中
items-centeralign-items: center子元素垂直居中
justify-endjustify-content: flex-end子元素靠底部对齐
text-gray-800color: #1f2937文字颜色
bg-whitebackground-color: white背景色

自定义值的语法:

<div className="max-w-[400px]"> {/* 方括号内是自定义值 */}

[]允许你使用 Tailwind 预设之外的任意值。这里max-w-[400px]等价于max-width: 400px。

1rem = 4是 Tailwind 的默认尺寸单位映射:p-1= 4px,p-4= 16px,以此类推。

5.7 模型信息展示区解析

<p className="mx-w-[510px] mb-4"> You are about to load <a href="https://huggingface.co/onnx-community/DeepSeek-R1-Distill-Qwen-1.5B-ONNX" target="_blank" rel="noreferrer" className="font-medium underline" > DeepSeek-R1-Distill-Qwen-1.5B </a> , a 1.5B parameter reasoning LLM optimized for in-browser inference. Everything runs entirely in your browser with <a href="https://huggingface.co/docs/transformers.js" target="_blank" rel="noreferrer" className="underline" > 🤗&nbsp;Transformers.js </a> and ONNX Runtime Web, meaning no data is sent to a server. </p>

两个关键链接指向的技术:

  • DeepSeek-R1-Distill-Qwen-1.5B-ONNX:模型托管在 HuggingFace。HuggingFace 是全球最大的开源模型社区,被称为 AI 界的 GitHub。
  • Transformers.js:HuggingFace 推出的 JavaScript 库,让你在浏览器中加载和推理 Transformer 模型,无需后端服务。
  • ONNX Runtime Web:微软的 ONNX 运行时浏览器版,负责在 WebGPU 上高效执行模型推理。

这两个库配合 WebGPU,让"浏览器跑大模型"从不可能变成了现实。

5.8 错误处理状态

{error && ( <div className="text-red-500 text-center mb-2"> <p className="mb-1">Unable to load model due to the following error:</p> <p className="text-sm">{error}</p> </div> )}

当error有值时(非空字符串),显示红色错误提示。当错误被清除(setError(null)或setError('')),错误提示自动消失。这就是"响应式条件渲染"——你只需要改数据,界面自己会跟着变。


六、全文总结

本文从一个真实的浏览器端 AI 推理项目出发,系统梳理了以下技术链路:

  1. 端侧模型:模型从云端走向本地,从服务器走向浏览器。核心理念是"数据不出设备",WebGPU 是浏览器端 AI 的关键基础设施。
  2. React + TypeScript:AI 时代大型前端项目的首选技术栈。函数式组件 + Hooks 模式提供了强大的抽象能力。
  3. TailwindCSS:原子化 CSS 框架,用"堆类名"替代"写 CSS",开发效率翻倍。Vite 插件在构建时按需注入样式。
  4. React 组件化:函数 = 组件,JSX = 模板。所有 JavaScript 能力直接用于 UI 表达。
  5. 状态驱动:useState+useEffect实现响应式数据绑定,数据变化自动驱动界面更新。

七、核心知识点复盘

序号知识点一句话总结
1端侧模型LLM 运行在用户设备上,数据不出设备,隐私安全
2ONNX开放神经网络交换格式,跨框架跨平台的模型标准
3HuggingFace全球最大开源模型社区,AI 界的 GitHub
4WebGPU浏览器原生 GPU API,替代 WebGL 做高性能计算
5useStateReact 状态钩子,创建响应式数据[值, 更新函数]
6useEffectReact 副作用钩子,组件渲染后执行,第二个参数控制执行时机
7!!双重否定强转布尔值,!!undefined=false,!!{}=true
8JSXJavaScript XML,在 JS 中写 HTML,React 的核心语法
9classNameJSX 中替代class(因为 class 是 JS 关键字)
10Tailwind原子化 CSS 框架,类名即样式,按需生成,不写 CSS 文件
11Vite 插件扩展 Vite 能力(处理 JSX、Tailwind 等),像搭积木
12ESLint代码约束工具,确保团队代码风格一致
13条件渲染{condition && <Component />}或三目运算符
14响应式数据变化 → 界面自动更新,无需手动操作 DOM

八、常见问题 / 避坑指南

Q1:!!navigator.gpu和Boolean(navigator.gpu)有区别吗?

没有本质区别,效果一样。!!更简洁,是 JS 社区的惯用写法。不推荐new Boolean()。

Q2:useEffect第二个参数传空数组[]时,函数什么时候执行?

组件首次挂载到 DOM 后执行一次。类比 Vue 的mounted()生命周期钩子。

Q3:为什么不直接在useState里写useState(null) => useState("出错了")会怎样?

不会怎样,初始值只是"第一次渲染时"的状态。后续通过setError更新。这里给"出错了"是为了演示错误状态 UI。

Q4:Tailwind@import "tailwindcss"报错怎么办?

检查vite.config.ts中是否添加了tailwindcss()插件。Tailwind v4 通过 Vite 插件工作,不需要手动安装 PostCSS。

Q5:为什么组件函数里console.log会执行多次?

React 在开发模式(StrictMode)下会故意渲染两次来帮你发现副作用问题。生产环境不会。这是正常的,不用担心。

Q6:模型文件 5.5GB,浏览器怎么存得下?

模型通过Transformers.js分片下载后会缓存在浏览器的 Cache Storage 中。第二次访问时直接从缓存加载,不需要重新下载。离线也能用。


项目地址:github.com/onnx-community/DeepSeek-R1-Distill-Qwen-1.5B-ONNX

技术栈:React 19 + TypeScript 6 + Vite 8 + TailwindCSS 4 + WebGPU + Transformers.js + ONNX Runtime Web

相关新闻

  • 深入解析TI处理器L4总线:寄存器映射、地址保护与系统调试实战
  • BIOS密码遗忘的五大解决方案全解析
  • 3个IO口驱动256个LED的硬件设计与动态扫描技术

最新新闻

  • 干货分享✨论文绘图不用熬!Paperxie智能科研绘图,小白也能出期刊级图表
  • 止损回测差了一笔交易:收盘触发与盘中触发的口径实验
  • 2026漯河第三方验房检测排名 TOP5 CMA 资质提供房屋质量检测、水电验收、墙面地面检测一站式服务 联系方式推荐 - 科信检测
  • 重庆音响升级难题破解:正信汽车音响的核心优势,问界原车音响升级/原车音响升级/路虎音响改装,音响升级门店找哪家 - 音响改装门店分享
  • 厦门各区黄金回收收的顶 免费验金极速打款 - 日常比对手册
  • 计算机毕业设计之基于SpringBoot的乡村贫困帮扶App的设计与实现

日新闻

  • Python开发内部工具:7大核心库实战解析
  • 合肥雷达官方2026年7月最新信息:客户服务网点地址与售后热线权威公示 - 亨得利官方服务中心
  • PCA实战指南:从变量纠缠诊断到主成分业务解读

周新闻

  • SaaS软件行业GEO实践:AI搜索时代的品牌可见性与获客新路径
  • 什么是PCTFE?医药高端包装的“防潮王牌“材料
  • 【JVM调优实战】16-可视化利器-JConsole-VisualVM-JMC

月新闻

  • 2026年6月公司网站搭建最新热门渠道测评:四大低成本/零代码平台对比+避坑
  • 【Linux】Linux arm 编译QT程序,出现expected “}“报错
  • 【MATLAB例程】四基站二维AOA定位与距离辅助增强对比仿真。基于角度观测和测距修正的固定目标平面定位精度分析

关于尧图

  • 公司简介
  • 团队介绍
  • 企业文化
  • 荣誉资质

服务项目

  • 定制开发
  • 电商建站
  • UI 设计
  • 运维服务

快速链接

  • 案例展示
  • 建站流程
  • 常见问题
  • 资讯中心

联系方式

  • 📍北京市朝阳区互联网产业园 A 座 10 层
  • 📞400-888-8888
  • ✉️contact@rkmt.cn
  • 🕐周一至周日 9:00-21:00

© 2024 北京尧图网络科技有限公司 版权所有 | 京 ICP 备 XXXXXXXX 号