摘要:不调远程API,不装Ollama,打开浏览器就能跑1.5B参数大模型?本文用ReactHooks管理加载状态,TailwindCSS零行写样式,Transformers.js加载ONNX模型,WebGPU做推理加速,手写一个端侧AI推理页面。无数据离开你的电脑,断网也能用。
目录
- [端侧模型的革命:比 API 更便宜、更安全](#端侧模型的革命:比 API 更便宜、更安全 "#%E4%B8%80%E7%AB%AF%E4%BE%A7%E6%A8%A1%E5%9E%8B%E7%9A%84%E9%9D%A9%E5%91%BD%E6%AF%94-api-%E6%9B%B4%E4%BE%BF%E5%AE%9C%E6%9B%B4%E5%AE%89%E5%85%A8")
- [React 组件骨架:从数据状态到界面状态](#React 组件骨架:从数据状态到界面状态 "#%E4%BA%8Creact-%E7%BB%84%E4%BB%B6%E9%AA%A8%E6%9E%B6%E4%BB%8E%E6%95%B0%E6%8D%AE%E7%8A%B6%E6%80%81%E5%88%B0%E7%95%8C%E9%9D%A2%E7%8A%B6%E6%80%81")
- [TailwindCSS:不写一行 CSS 也能做出专业 UI](#TailwindCSS:不写一行 CSS 也能做出专业 UI "#%E4%B8%89tailwindcss%E4%B8%8D%E5%86%99%E4%B8%80%E8%A1%8C-css-%E4%B9%9F%E8%83%BD%E5%81%9A%E5%87%BA%E4%B8%93%E4%B8%9A-ui")
- [WebGPU 检测与界面分支](#WebGPU 检测与界面分支 "#%E5%9B%9Bwebgpu-%E6%A3%80%E6%B5%8B%E4%B8%8E%E7%95%8C%E9%9D%A2%E5%88%86%E6%94%AF")
- [模型加载:Transformers.js + ONNX Runtime Web](#模型加载:Transformers.js + ONNX Runtime Web "#%E4%BA%94%E6%A8%A1%E5%9E%8B%E5%8A%A0%E8%BD%BDtransformersjs--onnx-runtime-web")
- 总结
一、端侧模型的革命:比 API 更便宜、更安全
调用 OpenAI 或 DeepSeek 的 API 是主流做法,但它有两个绕不开的短板:贵 和不安全------你的上下文数据会随着 HTTP 请求离开本地,发送到远程服务器。
端侧模型(On-Device Model)是另一条路。Ollama 可以本地部署开源模型,手机端、汽车端、浏览器端都能跑小参数模型完成特定任务。在浏览器里,这一切依靠 WebGPU------一个让网页直接调用 GPU 算力的现代浏览器 API。
这个项目的完整技术栈:
markdown
React + TypeScript 前端框架 + 类型安全
↕
TailwindCSS 零 CSS 样式的原子类系统
↕
Transformers.js 在浏览器中加载和运行 HuggingFace 模型
↕
ONNX Runtime Web 跨平台模型推理引擎
↕
WebGPU 调用本地 GPU 做推理加速
所有计算都在浏览器本地完成------没有数据离开你的电脑,模型下载后断网也能用。
二、React 组件骨架:从数据状态到界面状态
React 的核心哲学是"数据驱动视图"------只要修改数据状态,界面自动更新,不需要手动操作 DOM。这个理念通过 Hooks 来实现。
状态驱动的页面结构
一个 LLM 推理页面需要跟踪四种数据状态:
typescript
import { useState, useEffect } from "react";
function App() {
const [status, setStatus] = useState(null); // 模型加载状态
const [error, setError] = useState('出错了'); // 错误信息
const [loadingMessage, setLoadingMessage] = useState(""); // 加载提示
const [progressItems, setProgressItems] = useState([{ // 下载进度
file: 'preinitModule.onnx',
progress: 0,
total: 34353543453
}]);
return (/* JSX 渲染 */);
}
| 状态变量 | 初始值 | 对应的界面状态 |
|---|---|---|
status |
null |
未开始 / loading / ready |
error |
'出错了' |
错误提示区域 |
loadingMessage |
"" |
加载中文案 |
progressItems |
[{file, progress, total}] |
模型文件下载进度条 |
useEffect:组件挂载时自动执行
useEffect 是 React 的"副作用钩子"------组件在页面上渲染完成后自动执行的回调:
typescript
useEffect(() => {
console.log('组件已经挂载完成');
// 在这里触发模型下载、WebGPU 初始化等
}, []); // 空依赖数组 = 只在首次挂载时执行一次
组件 = 函数
React 组件本质上就是一个返回 JSX 的函数。函数体里写 JS 逻辑(声明状态、处理事件),返回值是 UI 描述。一个组件就是一个自包含的功能单元------JS 逻辑 + HTML 模板 + CSS 引用------全部封装在一个函数里。
三、TailwindCSS:不写一行 CSS 也能做出专业 UI
传统前端开发中最耗时的环节之一是写 CSS------选择器、样式规则、响应式断点、命名规范。TailwindCSS 用一套原子类系统彻底省掉了这个过程。
原子类的组合哲学
tsx
<div className="flex flex-col h-screen mx-auto items-center justify-end text-gray-800 bg-white">
这行 className 干了什么?
| 原子类 | 对应的 CSS | 效果 |
|---|---|---|
flex |
display: flex |
弹性布局 |
flex-col |
flex-direction: column |
主轴方向为垂直 |
h-screen |
height: 100vh |
高度占满整个视口 |
mx-auto |
margin-left/right: auto |
水平居中 |
items-center |
align-items: center |
子元素沿交叉轴居中 |
justify-end |
justify-content: flex-end |
子元素靠主轴末端对齐 |
text-gray-800 |
color: #1f2937 |
深灰色文字 |
bg-white |
background-color: white |
白色背景 |
没有写一行 CSS,没有命名任何一个选择器,一个完整的布局就完成了。
为什么是 className 而不是 class?
因为在 JSX 中,class 是 JavaScript 的保留关键字(用于声明类)。React 使用 className 来代替 HTML 原生的 class 属性,避免语法冲突。
TailwindCSS 已经成为 Vibe Coding 时代的事实标准------原子类名天然适合自然语言描述,Agent 生成 UI 时写几个类名远比手写完整 CSS 规则高效。
四、WebGPU 检测与界面分支
WebGPU 不是所有浏览器都支持。打开页面的第一步是检测,然后根据检测结果走不同的渲染分支。
一行双重否定搞定检测
typescript
const IS_WEBGPU_AVAILABLE = !!navigator.gpu;
navigator.gpu------浏览器支持 WebGPU 时返回 GPU 对象,不支持时返回 undefined。!! 双重否定将任意值转换为布尔:undefined → false,GPU 对象 → true。
条件渲染:两种界面两种命运
tsx
return (
IS_WEBGPU_AVAILABLE ? (
<div className="flex flex-col h-screen ...">
{/* 主界面:模型介绍 + 加载进度 + 推理输入 */}
</div>
) : (
<div>您的浏览器还不支持 WebGPU</div>
)
);
除了 WebGPU 检测分支,还有 错误状态的条件渲染:
tsx
{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 && 是 JS 的短路表达式------error 有值时渲染错误提示,为空时不渲染。同一组件支持四种界面状态:未加载 / 加载中 / 就绪 / 出错。
五、模型加载:Transformers.js + ONNX Runtime Web
在页面 UI 就绪后,真正的重头戏开始------从 HuggingFace 下载模型并在浏览器中运行。
模型选择
项目使用的是 DeepSeek-R1-Distill-Qwen-1.5B------15 亿参数量的蒸馏推理模型。为什么选这个尺寸?1.5B 的参数规模在浏览器端刚好找到了能力与速度的平衡点:足够小到可以下载到浏览器内存中(约 3GB),又足够大到能完成有意义的推理任务。
加载链路
| 组件 | 角色 |
|---|---|
| HuggingFace | 全球最大开源模型社区,托管模型权重文件 |
| Transformers.js | HuggingFace 官方 JS 库,在浏览器中加载和运行 Transformer 模型 |
| ONNX Runtime Web | 微软的跨平台推理引擎,ONNX 格式让同一个模型文件可以在任何平台运行 |
| WebGPU | 调用本地 GPU 做矩阵乘法和 attention 计算,比 CPU 推理快 10-50 倍 |
进度追踪
progressItems 状态数组跟踪模型文件的下载进度------每个文件(如 preinitModule.onnx)有独立的 progress 和 total。用户在等待时能看到具体的下载百分比,而不是一个空白的加载页面。
六、总结
一个浏览器端的 LLM 推理项目,基础骨架就这五个模块:
- React Hooks 做状态管理 :
useState声明四种状态(加载/错误/进度/就绪),useEffect在组件挂载时触发模型下载。数据改 → 视图变,不需要任何document.getElementById。 - TailwindCSS 做零 CSS 样式 :原子类名组合表达完整布局,没有选择器命名没有样式文件。为什么
className不叫class?因为 JSX 中class是 JS 关键字。 !!navigator.gpu做 WebGPU 检测 :双重否定转布尔,决定渲染主界面还是兜底提示。error &&短路表达式做错误状态的条件显示。- Transformers.js + ONNX Runtime Web:从 HuggingFace 下载模型权重,ONNX 引擎做跨平台推理,WebGPU 做硬件加速。
- 端侧模型 = 便宜 + 安全 + 离线可用:数据不离开浏览器,没有 API 费用,模型下载一次后断网也能跑。
这个项目放在简历上是"超燃"的一笔------它覆盖了 React/TypeScript/WebGPU/ONNX/Transformers.js 五项前沿技能,而实现的核心代码不过一百多行。技术栈的深度不在代码行数,在架构的组合能力。
------ 浏览器里跑大模型,不是遥远的未来。