最近几年,端侧AI越来越火,从手机上的NPU到浏览器的WebGPU,都在努力把AI能力带到离用户更近的地方。但你有没有想过:在App内嵌的WebView里,也能直接跑大语言模型(LLM)吗?答案是肯定的。本文将带你实操,在Android/iOS的WebView中运行本地LLM推理,无需服务器,完全离线。


一、为什么要在WebView里跑LLM?

在移动端App中,Hybrid架构依旧流行,很多页面直接内嵌WebView展示。如果我们能直接在WebView里完成AI推理,将带来几点好处:

  • 完全离线:不依赖网络,用户隐私数据不会离开设备。
  • 降低服务器成本:推理发生在客户端,无需维护昂贵的GPU集群。
  • 跨平台复用:一套Web代码,同时跑在Android、iOS甚至桌面端的WebView中。
  • 快速迭代:模型和UI更新无需发版,走Web发布流程即可。

当然,挑战也很明显:WebView的算力、内存受限,JS单线程容易阻塞UI。但随着WebGPU的普及和WebAssembly的成熟,浏览器内运行小型LLM(如LLaMA-2-7B的量化版本)已成为可能。
我这里只是简单介绍下,我的尝试体验,乐意和大家做进一步的交流,欢迎评论区了见。


二、技术选型:让浏览器跑起LLM的三驾马车

要在WebView里跑LLM,当前主流技术栈有三个:

1. WebGPU + ONNX Runtime Web

ONNX Runtime Web 支持通过WebGPU后端执行ONNX模型,能利用GPU进行加速。你需要将LLM转换为ONNX格式,借助其提供的JavaScript API加载并推理。优点是成熟度高,支持多种模型;缺点是需要自行处理分词器(Tokenizer)逻辑。

2. Transformers.js

Hugging Face推出的Transformers.js 让你直接在浏览器里使用和Python版接口几乎一致的库。底层基于ONNX Runtime Web,并内置了Tokenizer、Pipeline等组件。一行代码即可加载模型并开始生成文本,极大降低了门槛。

3. WebLLM / MLC-LLM

这类方案专门针对浏览器内LLM推理优化,比如WebLLM直接通过WebGPU运行LLaMA、Mistral等模型,内部使用Apache TVM编译,性能很强。但生态相对封闭,需要模型按照其格式转换。

综合考量:对于大多数开发者,推荐从Transformers.js入手,它隐藏了复杂的转换细节,上手最快。本文实战也将基于此库。


三、实战:在WebView里运行Gemma-2B模型

我们选择Google的轻量模型 gemma-2-2b-it 的量化版本(约1.3GB),目标是:用户在WebView的输入框提问,模型实时流式输出回答,就像和服务端通信一样,但全程在本地运行。

3.1 环境准备

确保你的App内嵌的WebView支持WebGPU。以Android为例,需要系统WebView版本 ≥ 121,并开启相关flag。iOS同理,需要15.0+,WKWebView默认支持。

首先,创建一个简单的HTML页面,引入Transformers.js:

<!DOCTYPE html>
<html lang="zh">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
  <title>WebView LLM Demo</title>
  <script type="module">
    import { pipeline } from 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.0.2';
    // 你的代码将写在这里
  </script>
</head>
<body>
  <textarea id="input" placeholder="输入你的问题..."></textarea>
  <button id="submit">发送</button>
  <div id="output"></div>
</body>
</html>

3.2 加载模型并创建生成器

Transformers.js提供了 pipeline API,类似Python版的体验。我们使用 text-generation pipeline,并指定模型为 Xenova/gemma-2-2b-it(一个ONNX量化版本)。同时,为了能在WebView的有限内存中跑起来,需要做一些配置:

const generator = await pipeline(
  'text-generation',
  'Xenova/gemma-2-2b-it',
  {
    // 使用WebGPU加速,若不可用则回退到CPU(极慢)
    device: 'webgpu',
    // 模型缓存到浏览器IndexedDB,下次加载更快
    cache_dir: './models',
    // 量化类型,进一步减少内存
    dtype: 'q4f16',
  }
);

首次加载会下载约1.3GB的模型文件,需要给用户一个进度提示:

const generator = await pipeline(..., {
  progress_callback: (progress) => {
    console.log(`加载中: ${progress.loaded}/${progress.total}`);
    // 更新UI进度条
  }
});

3.3 流式生成(关键)

为了做到打字机效果,我们需要开启流式输出。Transformers.js支持在 generate 时传入回调函数,不断获取新token:

document.getElementById('submit').addEventListener('click', async () => {
  const inputText = document.getElementById('input').value;
  const outputDiv = document.getElementById('output');
  outputDiv.textContent = '';

  // 构建对话模板(Gemma-2-it格式)
  const messages = [
    { role: 'user', content: inputText }
  ];
  
  const stream = await generator(messages, {
    max_new_tokens: 512,
    temperature: 0.7,
    do_sample: true,
    // 关键:流式回调
    callback_function: (token, tokens, generationComplete) => {
      // token是当前生成的文本片段
      outputDiv.textContent += token;
      // 自动滚动到底部
      window.scrollTo(0, document.body.scrollHeight);
    }
  });

  // 完成后,stream是完整的生成文本
  console.log('生成完成:', stream[0].generated_text);
});

注意:不同模型的对话模板不同,这里的Gemma-2-it模型需要 messages 数组格式,库内部会自动拼接成Prompt。

3.4 优化体验:Service Worker缓存模型

模型文件较大,每次打开页面都下载是不可接受的。我们可以注册Service Worker,拦截模型请求并缓存到CacheStorage,实现“一次下载,永久离线”。Transformers.js内部使用fetch加载模型,所以SW可以透明接管:

// sw.js
const CACHE_NAME = 'llm-models-v1';
self.addEventListener('fetch', (event) => {
  const url = event.request.url;
  if (url.includes('huggingface') || url.includes('onnx')) {
    event.respondWith(
      caches.open(CACHE_NAME).then(cache => 
        cache.match(event.request).then(res => 
          res || fetch(event.request).then(response => {
            cache.put(event.request, response.clone());
            return response;
          })
        )
      )
    );
  }
});

这样,即使App进入后台或被杀死,再次打开WebView时模型依然从本地缓存加载,速度飞快。


四、WebView特定适配与性能调优

在原生App的WebView中,我们还能做一些特殊处理来提升体验:

  • Android WebView 可开启硬件加速,并在 onPageFinished 中注入JavaScript调优参数,比如增大GPU内存限制(尝试 --enable-unsafe-webgpu flag)。
  • iOS WKWebView 默认开启WebGPU,但建议将模型放在 IndexedDB 中持久化,而非内存缓存,避免被系统清理。
  • 温度控制:移动端容易发热,可适当降低生成速度(如调整 max_new_tokensdo_sample),或在检测到温度升高时暂停推理。

实测:在骁龙8Gen2手机上,Gemma-2B-q4的推理速度约10-15 token/s,完全满足对话交互。


五、避坑指南

  1. 不支持WebGPU怎么办?
    可回退到WebAssembly(CPU)模式,但速度慢5-10倍。建议检测支持性,给用户明确提示。

  2. 内存溢出
    2B量化的模型占用内存约1.5GB,加上WebView本身开销,低端机可能闪退。可在加载前评估可用内存,或提供更小的模型(如Phi-3-mini)。

  3. Token生成乱码
    很可能是Tokenizer配置不匹配,确保使用模型卡上 config.jsontokenizer.json 对应的版本。

  4. 跨域问题
    本地开发时,WebView加载本地HTML文件可能遇到跨域限制,建议通过原生代码将HTML注入或使用本地服务器(如 http://localhost:8080)。


六、总结与展望

通过 Transformers.js,我们轻松在 App 的 WebView 里跑起了 LLM,实现了真正的离线智能。这种方案尤其适用于隐私敏感的问答助手、本地知识库检索等场景。未来,随着 WebGPU 规范的进一步成熟和更小更强的模型涌现(如 MobileLLM、Phi 系列),浏览器端 AI 将不再只是玩具,而会成为混合架构的关键一环。

趁现在,赶紧在你的App里尝试吧!如果觉得有帮助,欢迎点赞收藏~ 🚀


本文首发于CSDN,转载请注明出处。

Logo

AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。

更多推荐