在浏览器中运行大模型:基于 WebGPU 的本地 LLM 应用深度解析
所有代码均使用 Antigravity IDE 开发,模型为 Gemini 3 Pro
摘要
本文深入剖析了一个完全运行在浏览器中的本地大语言模型应用。该项目使用 Vue 3 + TypeScript 构建,基于 @mlc-ai/web-llm 实现 WebGPU 加速推理,让用户无需服务器即可在浏览器中与 AI 进行隐私保护的对话。项目展示了现代 Web 技术在机器学习领域的强大能力,以及如何通过合理的架构设计实现流畅的用户体验。
一、项目概述
1.1 背景与动机
随着大语言模型的普及,隐私和数据安全成为用户的核心关切。传统的 AI 应用需要将用户数据上传到服务器,存在潜在的隐私风险。Local LLM 项目旨在解决这一痛点,通过 WebGPU 技术将完整的语言模型加载到浏览器中,实现:
- 🔒 完全本地化:所有对话数据不离开用户设备
- ⚡ GPU 加速:利用 WebGPU 提供接近原生的推理性能
- 💬 多轮对话:支持上下文记忆的连续对话
- 🌐 跨平台:任何支持 WebGPU 的现代浏览器均可使用
1.2 技术栈
| 技术 | 版本 | 用途 |
|---|---|---|
| Vue 3 | ^3.5.24 | 前端框架 |
| TypeScript | ~5.9.3 | 类型安全 |
| Vite | ^7.2.4 | 构建工具 |
| @mlc-ai/web-llm | ^0.2.80 | WebGPU LLM 引擎 |
| marked | ^17.0.1 | Markdown 渲染 |
1.3 应用界面


二、核心架构设计
2.1 整体架构
项目采用典型的组件化 Vue 应用架构,核心分为三层:
┌─────────────────────────────────────┐
│ ChatTerminal.vue │ ← UI 层
│ (主界面、消息展示、输入处理) │
├─────────────────────────────────────┤
│ useLocalLLM.ts │ ← 逻辑层
│ (状态管理、引擎通信、配置管理) │
├─────────────────────────────────────┤
│ llm.worker.ts │ ← 计算层
│ (WebGPU 引擎、模型推理) │
└─────────────────────────────────────┘
2.2 目录结构
src/
├── components/ # UI 组件
│ ├── ChatTerminal.vue # 主聊天界面
│ ├── MessageItem.vue # 单条消息组件
│ ├── SettingsPanel.vue # 设置面板
│ └── ExportDialog.vue # 导出对话功能
├── composables/ # 组合式 API
│ └── useLocalLLM.ts # 核心业务逻辑
├── workers/ # Web Worker
│ └── llm.worker.ts # LLM 引擎线程
├── utils/ # 工具函数
│ └── storage.ts # 本地存储
├── types.ts # 类型定义
└── App.vue # 根组件
三、关键技术实现
3.1 WebGPU 引擎初始化
项目的核心是 @mlc-ai/web-llm,一个支持在浏览器中运行量化大模型的库。初始化过程分为以下步骤:
3.1.1 Web Worker 创建
为避免阻塞主线程,模型加载和推理在独立 Worker 中运行:
typescript
// src/workers/llm.worker.ts
import { WebWorkerMLCEngineHandler, MLCEngine } from "@mlc-ai/web-llm";
console.log("【Worker线程】脚本加载成功,准备初始化...");
// 创建引擎实例
const engine = new MLCEngine();
// 创建消息处理器
const handler = new WebWorkerMLCEngineHandler(engine);
onmessage = (msg) => {
handler.onmessage(msg);
};
3.1.2 主线程引擎初始化
在组合式 API 中封装引擎创建逻辑:
typescript
// src/composables/useLocalLLM.ts (简化版)
const initEngine = async () => {
try {
// 检查 WebGPU 支持
if (!navigator.gpu) {
throw new Error("您的浏览器不支持 WebGPU");
}
// 创建 Worker 实例
const worker = new LLMWorker();
// 创建跨线程的 MLC 引擎
engine = await CreateWebWorkerMLCEngine(
worker,
config.value.modelConfig.modelName,
{
initProgressCallback: (report) => {
loadProgress.value = Math.floor(report.progress * 100);
loadStatus.value = report.text;
},
}
);
isLoaded.value = true;
console.log("✅ 模型加载成功");
} catch (err) {
console.error("❌ 模型加载失败:", err);
loadError.value = err.message;
}
};
!IMPORTANT\] \> **关键配置**:WebGPU 需要特定的 HTTP 响应头才能正常工作。
3.1.3 Vite 配置
在 vite.config.ts 中添加必要的响应头:
typescript
// vite.config.ts
export default defineConfig({
plugins: [vue()],
// 预构建 web-llm 依赖
optimizeDeps: {
include: ["@mlc-ai/web-llm"],
},
server: {
// 🔑 核心:配置 COOP/COEP 响应头
headers: {
"Cross-Origin-Opener-Policy": "same-origin",
"Cross-Origin-Embedder-Policy": "require-corp",
},
},
worker: {
format: "es",
},
});
!WARNING
缺少
Cross-Origin-Opener-Policy和Cross-Origin-Embedder-Policy响应头会导致 SharedArrayBuffer 不可用,WebGPU Worker 无法正常运行。
3.2 流式对话生成
为了提供更好的用户体验,应用采用了流式响应(Streaming)方式,即 AI 的回复逐字显示:
typescript
// src/composables/useLocalLLM.ts
const sendMessage = async (content: string) => {
if (!engine || isGenerating.value || !content.trim()) return;
// 1. 添加用户消息
const userMessage: Message = {
id: generateId(),
role: "user",
content: content.trim(),
timestamp: Date.now(),
};
messages.value.push(userMessage);
// 2. 创建 AI 消息占位符
const aiMessageIndex = messages.value.length;
messages.value.push({
id: generateId(),
role: "assistant",
content: "",
timestamp: Date.now(),
});
isGenerating.value = true;
try {
// 3. 构建完整对话上下文
const chatMessages = [];
// 添加系统提示词
if (config.value.systemPrompt.trim()) {
chatMessages.push({
role: "system",
content: config.value.systemPrompt,
});
}
// 添加历史对话
messages.value.slice(0, -1).forEach((msg) => {
if (msg.role !== "system" && msg.content.trim()) {
chatMessages.push({
role: msg.role,
content: msg.content,
});
}
});
// 4. 流式生成
const chunks = await engine.chat.completions.create({
messages: chatMessages,
stream: true,
temperature: config.value.modelConfig.temperature,
top_p: config.value.modelConfig.topP,
max_tokens: config.value.modelConfig.maxTokens,
});
// 5. 逐块接收并更新 UI
for await (const chunk of chunks) {
const delta = chunk.choices[0]?.delta?.content || "";
messages.value[aiMessageIndex].content += delta;
}
} catch (err) {
console.error("❌ 生成失败:", err);
messages.value[aiMessageIndex].content = `⚠️ 生成失败: ${err.message}`;
} finally {
isGenerating.value = false;
saveChatHistory(messages.value);
}
};
流式生成的优势:
- ✅ 用户无需等待完整响应,立即看到输出
- ✅ 提供类似打字机效果,改善交互体验
- ✅ 可随时中断生成过程
3.3 多模型支持
应用内置了三种不同规模的模型,用户可根据性能需求切换:
typescript
// src/types.ts
export const AVAILABLE_MODELS = [
{
name: "Qwen2.5-1.5B-Instruct-q4f32_1-MLC",
displayName: "Qwen 2.5 1.5B (快速)",
description: "轻量级模型,加载快速,适合日常对话",
},
{
name: "Llama-3-8B-Instruct-q4f32_1-MLC",
displayName: "Llama 3 8B (强大)",
description: "性能强大,响应质量高,需要更多资源",
},
{
name: "Phi-3.5-mini-instruct-q4f16_1-MLC",
displayName: "Phi 3.5 Mini (均衡)",
description: "微软出品,性能与速度均衡",
},
] as const;
模型切换逻辑:
typescript
const switchModel = async (modelName: string) => {
if (isGenerating.value) {
throw new Error("正在生成回复,无法切换模型");
}
// 更新配置
config.value.modelConfig.modelName = modelName;
saveConfig(config.value);
// 重置引擎状态
isLoaded.value = false;
loadProgress.value = 0;
engine = null;
// 重新初始化
await initEngine();
};
3.4 本地存储与状态持久化
为了提供良好的用户体验,应用会自动保存:
- 📝 对话历史
- ⚙️ 用户配置(模型选择、温度等参数)
typescript
// src/utils/storage.ts (示例)
const STORAGE_KEYS = {
CHAT_HISTORY: "local-llm-chat-history",
CONFIG: "local-llm-config",
};
export function saveChatHistory(messages: Message[]) {
localStorage.setItem(STORAGE_KEYS.CHAT_HISTORY, JSON.stringify(messages));
}
export function loadChatHistory(): Message[] {
const data = localStorage.getItem(STORAGE_KEYS.CHAT_HISTORY);
return data ? JSON.parse(data) : [];
}
export function saveConfig(config: AppConfig) {
localStorage.setItem(STORAGE_KEYS.CONFIG, JSON.stringify(config));
}
export function loadConfig(): AppConfig | null {
const data = localStorage.getItem(STORAGE_KEYS.CONFIG);
return data ? JSON.parse(data) : null;
}
四、用户体验优化
4.1 响应式设计
应用采用现代 CSS 技术实现精美的界面:
- 渐变背景:多层次紫色渐变营造沉浸感
- 毛玻璃效果 :
backdrop-filter: blur(10px)实现半透明模糊 - 动画效果:加载动画、悬停效果、状态指示器脉冲
css
/* src/components/ChatTerminal.vue */
.chat-terminal {
background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%);
backdrop-filter: blur(10px);
}
.status-dot {
animation: pulse 2s infinite;
}
@keyframes pulse {
0%,
100% {
opacity: 1;
}
50% {
opacity: 0.5;
}
}
4.2 自动滚动
使用 Vue 的 watch 监听消息变化,自动滚动到底部:
typescript
// 监听消息数量变化
watch(
() => messages.value.length,
async () => {
await nextTick();
if (viewport.value) {
viewport.value.scrollTop = viewport.value.scrollHeight;
}
}
);
// 监听流式生成时的内容变化
watch(
() => messages.value[messages.value.length - 1]?.content,
async () => {
await nextTick();
if (viewport.value && isGenerating.value) {
viewport.value.scrollTop = viewport.value.scrollHeight;
}
}
);
4.3 加载进度反馈
模型加载是个耗时过程(通常 1-5 分钟),精心设计的加载界面至关重要:
vue
<div v-if="!isLoaded" class="loading-screen">
<div class="loading-content">
<div class="loading-icon">⚡</div>
<h2 class="loading-title">
{{ loadError ? "加载失败" : "正在加载模型" }}
</h2>
<div v-if="!loadError" class="progress-container">
<div class="progress-bar">
<div class="progress-fill" :style="{ width: loadProgress + '%' }"></div>
</div>
<div class="progress-info">
<span class="progress-percent">{{ loadProgress }}%</span>
<span class="progress-status">{{ loadStatus }}</span>
</div>
</div>
<div v-else class="error-message">
<p>❌ {{ loadError }}</p>
<p class="error-hint">
请确保使用支持 WebGPU 的浏览器(Chrome/Edge 最新版)
</p>
</div>
</div>
</div>
五、技术亮点与创新
5.1 完全离线运行
一旦模型加载完成,应用可以完全离线使用。所有计算均在本地完成,无需任何网络请求(除首次加载模型文件)。
5.2 隐私保护
- ✅ 对话内容不上传任何服务器
- ✅ 本地存储采用浏览器
localStorage - ✅ 用户拥有数据的完全控制权
5.3 高性能推理
通过 WebGPU:
- ⚡ 利用 GPU 并行计算能力
- ⚡ 量化模型(q4f32_1)减少内存占用
- ⚡ 推理速度可达 10-30 tokens/秒(取决于硬件)
5.4 灵活的配置
用户可调整:
- 🎛️ Temperature:控制输出的随机性
- 🎛️ Top P:核采样参数
- 🎛️ Max Tokens:最大生成长度
- 🎛️ System Prompt:自定义 AI 角色
六、挑战与解决方案
6.1 挑战:浏览器兼容性
问题:WebGPU 是较新的技术,仅在最新版本的 Chrome 和 Edge 中完全支持。
解决方案:
- 启动时检测
navigator.gpu是否可用 - 提供清晰的错误提示和浏览器升级建议
6.2 挑战:首次加载时间
问题:模型文件体积较大(1.5B 模型约 1GB),首次加载耗时长。
解决方案:
- 实时显示加载进度(百分比 + 状态文本)
- 模型文件缓存在浏览器中(IndexedDB),后续访问无需重新下载
- 提供多种规模的模型供用户选择
6.3 挑战:内存管理
问题:长时间对话可能导致内存占用过高。
解决方案:
- 提供清空对话功能释放内存
- 限制
max_tokens避免生成过长文本
七、未来展望
- 更多模型支持:集成更多开源模型(如 Mistral、Gemma)
- 多模态能力:支持图像理解(视觉语言模型)
- RAG 增强:本地知识库检索增强生成
- 语音交互:集成 Web Speech API 实现语音对话
- PWA 支持:安装为桌面应用
八、总结
Local LLM 项目展示了现代 Web 技术的惊人能力:通过 WebGPU,我们可以在浏览器中运行真正的大语言模型,提供接近原生应用的性能。这不仅是对隐私保护的承诺,更是对未来 AI 应用形态的探索。
随着 WebGPU 生态的成熟和浏览器性能的持续提升,本地化 AI 应用将成为重要趋势,为用户提供既强大又安全的智能体验。
附录:快速开始
安装依赖
bash
npm install
开发运行
bash
npm run dev
构建部署
bash
npm run build
系统要求
- Chrome/Edge 113+ (支持 WebGPU)
- 至少 8GB RAM
- 支持 WebGPU 的独立/集成显卡
参考资源
💡 提示:首次运行需要下载模型文件,请耐心等待。建议在 Wi-Fi 环境下使用。