Skip to content

解决 Texo-web 的部署问题

2025-10-25 · 1414字 · 5分钟

Texo 是我花了三个月完成的 LaTeXOCR 项目,该项目最终的目标在于发布一个可以完全运行在纯前端的 LaTeX 识别模型。关于识别模型的设计、训练和调试过程参见原仓库。本文记录的是我在把模型的 onnx 导出部署到纯前端中遇到的问题和解决方案。

事实上,transformers.js 已经是一套相当成熟的深度学习模型纯前端部署方案,美中不足之处是它的文档太过简略,只有接口说明但鲜有具体的使用案例,好在有 HF 论坛 John666 的慷慨帮助 [1] ,我还是成功实现了这个前端应用。不过由于 transformers.js 默认从 HF Hub 上加载模型,而中国大陆在 23 年就封禁了 HF Hub,因此为大陆用户提供服务不得不另辟蹊径。

起初我的想法是把同样的模型托管一套在 ModelScope 上,而在 transformers.js 的 from_pretrained(modelName) 向 MS 的 API 发送请求后,由于它的 response header 中缺少 Access-Control-Allow-Origin 一项,使得浏览器拒绝向 js 代码解包响应,报 Network Error,这个问题也被称为拒绝跨源请求,是浏览器为了避免前端代码可以直接访问其他站点从事恶意行为。(相反 HF Hub 的服务器的响应则是直接设置了 Access-Control-Allow-Origin:*,因而允许任意其他源的代码访问 API)。

这种设计比较有趣,在这里真正起审查作用的是浏览器,既不是客户端也不是服务器。服务器返回的响应头只是注明它希望浏览器放行哪些访问,而浏览器也只能限制纯前端的代码访问,因为实际上浏览器已经获取了响应信息,但却持有信息不向前端代码呈现。也就是说,如果直接在浏览器输入栏中访问同样的 API,确实能看到响应体,而离开浏览器环境,例如直接在(服务器)命令行中用 wget 访问也可以看到。

换言之,前端代码无法通过篡改 header 来绕过浏览器的审查,因为发送请求的前端代码只能改变 request 的 header,而无法改变 response 的 header,这是服务器给出的,并且浏览器先于前端代码获得 response,因而无法在浏览器掌握它以前篡改。为此一种解决方案是,让与前端同源的服务端代替发送请求并接受响应,因为服务端的请求响应不会受到审查,而中转后的响应变成了由同源代理服务端交给前端,因此是同源的从而不会触发浏览器的跨源请求审查,这种策略被称为反向代理。它可以被包装成 serverless function 使用,我使用的 Netlify 平台支持一定免费额度的 serverless function,但是其带宽有限,并不足以中介从 MS API 得到的响应。

于是我又转向第二种方案,即放弃远程加载模型,而是把模型放在网站的 public 文件夹下,作为本地资源一并打包到网页托管服务器上,这样自然是同源请求。不幸的是,即便在中国大陆可以访问 Netlify 上的 web 应用,然而其网速较慢,加载前端三件套的资源尚可,加载更大的模型文件就显得捉襟见肘了(这可能和缓存策略有关,前端资源的缓存较容易发送到边缘节点,因而加载速度快)。

第三种方案是,把模型文件托管到对中国大陆更友好的 CDN 平台,例如 Cloudfare,或者干脆是国内的 CDN 服务器上。但是国内的 CDN 平台注册比较复杂,其收费规则我又没看明白。于是就想用 Cloudfare 的服务。它家的 R2 是用于加速分发大文件的,对存储大小收费(所谓每月每 GB 多少钱,不仅对存储空间收费,还对存储时间计费),对操作次数收费,而不对出站流量收费。简而言之是说它只管你托管了多少、多久,管你托管的内容被访问了多少次而不管它分发的流量。在我的使用情况中,总共 80M 的模型文件远小于每月 10GB 的免费额度,因而只有 Texo-web 的用户加载模型次数有关。而同一个用户首次加载后总会使用保存到浏览器的缓存,再根据 claude 的说法,相邻地区的其他用户的首次加载也可能因为先前加载到边缘节点的缓存,从而避免了向源站发送请求,因而实际加载次数会被远远节省。

不过就在我决定采用上述方案时,我突然发现可以使用 github 的镜像站来加速分发,这样我就不需要自行维护一份托管在 CDN 上的模型,而只需要把 HF Hub 上的模型转移到 github 上,而镜像服务器也没有跨源请求的限制,因此就暂时决定这么做了。


  1. https://discuss.huggingface.co/t/load-model-from-platform-other-than-hf-hub-and-display-a-progress-bar-by-from-pretrained-in-transformers-js/169364/9 https://discuss.huggingface.co/t/how-to-make-my-customized-pipeline-consumable-for-transformers-js/169036/12 https://discuss.huggingface.co/t/how-to-build-a-tokenizer-from-a-vocab-subset-of-a-bpe-tokenizer/168698/10 ↩︎

返回

人同此心,心同此理;如风沐面,若水润心