寫在最前
本故事主要介紹在網(wǎng)頁上部署模型的來龍去脈江锨,你想問的問題吃警,可能都可以在這里找到答案
在這個(gè) AI 內(nèi)容生成泛濫的時(shí)代,依然有一批人"傻傻"堅(jiān)持原創(chuàng)啄育,如果您能讀到最后酌心,還請(qǐng)點(diǎn)贊或收藏或關(guān)注支持下我唄,感謝 ( ̄︶ ̄)↗
能在網(wǎng)頁上跑模型嗎挑豌?
丹尼爾:嘿谒府,蛋兄,你這是要去哪兒遛彎呢浮毯?
蛋先生:剛吃完飯,準(zhǔn)備散下步消消食
丹尼爾:一起唄泰鸡。蛋兄债蓝,我最近對(duì) AI 有點(diǎn)著迷,突然冒出個(gè)念頭盛龄,你說咱們能不能在網(wǎng)頁上跑機(jī)器學(xué)習(xí)模型呢饰迹?
蛋先生:這個(gè)嘛芳誓,確實(shí)可以
為什么可以在網(wǎng)頁上跑模型?
丹尼爾:這我就納悶了啊鸭,一個(gè)專門看網(wǎng)頁的瀏覽器锹淌,怎么還能“兼職”跑模型呢?蛋兄赠制,快給我講講唄
蛋先生:你想啊赂摆,一顆種子能不能發(fā)芽,得看它有沒有適合生存的環(huán)境钟些。模型也一樣烟号,得有個(gè)能跑的“土壤”——runtime,還得有足夠的“陽光”和“水”——也就是算力和存儲(chǔ)
丹尼爾:哦政恍,我好像有點(diǎn)懂了汪拥。但我還是不明白,瀏覽器是怎么做到這一點(diǎn)的
蛋先生:自從瀏覽器有了 WebAssembly 之后篙耗,它的“胃口”可就大了去了迫筑!現(xiàn)在,很多用 C宗弯、C++脯燃、Rust 等編程語言寫的應(yīng)用,都能編譯成 WASM 格式罕伯,在瀏覽器里跑曲伊。這樣一來,瀏覽器就能處理更加復(fù)雜的計(jì)算任務(wù)了
丹尼爾:原來如此追他!也就是說坟募,原來用 C++ 等寫的模型 runtime,現(xiàn)在可以直接放進(jìn)瀏覽器里邑狸,成了模型的“土壤”了懈糯!
蛋先生:對(duì)頭!而且单雾,瀏覽器的 WebGL赚哗、WebGPU 這些技術(shù),還能讓你的應(yīng)用用上 GPU 資源硅堆,速度更上一層樓屿储!否則,能跑渐逃,但很慢够掠,也沒啥意義
丹尼爾:哈哈,我總結(jié)一下啊茄菊,WebAssembly 讓模型有了土壤疯潭,WebGL赊堪、WebGPU 讓算力提升成為可能!
蛋先生:不錯(cuò)不錯(cuò)竖哩,總結(jié)得挺到位哭廉!
為什么要跑在瀏覽器呢?
蛋先生:那我問你相叁,你為什么想把模型跑在瀏覽器上呢遵绰?
丹尼爾:額~,這~钝荡,就覺得挺酷的嘛街立!不過說實(shí)話,我還真沒認(rèn)真想過這個(gè)問題埠通。蛋兄赎离,你給說道說道?
蛋先生:來端辱,咱們從請(qǐng)求鏈路說起梁剔。模型部署在瀏覽器上,是不是就不用請(qǐng)求服務(wù)器了舞蔽?
丹尼爾:那是肯定的
蛋先生:對(duì)于客戶端荣病,請(qǐng)求沒有離開用戶設(shè)備,這樣是不是就可以更好地保護(hù)用戶隱私了渗柿?
丹尼爾:是哦
蛋先生:計(jì)算是在瀏覽器本地進(jìn)行的个盆,距離用戶更近,也沒有網(wǎng)絡(luò)請(qǐng)求的損耗朵栖,響應(yīng)速度通常更快颊亮,這樣是不是就可以提升用戶體驗(yàn)了?
丹尼爾:是哦
蛋先生:還有陨溅,模型已經(jīng)部署在瀏覽器了终惑,只要應(yīng)用本身支持離線訪問,那是不是就可以離線使用了门扇?
丹尼爾:是哦
蛋先生:對(duì)于服務(wù)端雹有,因?yàn)榘延?jì)算壓力分?jǐn)偝鋈チ耍遣皇蔷涂梢詼p輕服務(wù)器的計(jì)算壓力臼寄,降低運(yùn)營(yíng)成本呢霸奕?
丹尼爾:是哦
蛋先生:剩下的你自己琢磨琢磨吧
怎么跑在瀏覽器呢?
丹尼爾:好嘞吉拳,那具體要怎么實(shí)現(xiàn)呢质帅?
蛋先生:主流的機(jī)器學(xué)習(xí)框架除了訓(xùn)練模型外,還能部署和推理模型。比如大名鼎鼎的 Tensorflow 就有 tensorflow.js临梗,它可以將模型部署在瀏覽器端。不過今天我要給你說的是 onnxruntime-web
丹尼爾:onnxruntime-web稼跳?這名字聽著有點(diǎn)新鮮懊伺印!
蛋先生:onnxruntime-web汤善,可以把這個(gè)拆成 onnx什猖,onnxruntime 和 onnxruntime-web 來說
丹尼爾:您繼續(xù)
蛋先生:onnx 就是個(gè)模型格式,就像你存音樂用的 mp3 格式一樣红淡,但它存的是機(jī)器學(xué)習(xí)模型不狮;onnxruntime 呢,就是運(yùn)行這些模型的“播放器”在旱;而 onnxruntime-web摇零,則是讓這個(gè)“播放器”能在網(wǎng)頁上跑起來的神奇工具
丹尼爾:哦,那模型都有哪些格式呢桶蝎?用這個(gè) onnx 有什么優(yōu)勢(shì)呢驻仅?
蛋先生:正所謂合久必分,分久必合
丹尼爾:這是要講三國(guó)的節(jié)奏嗎
蛋先生:各個(gè)機(jī)器學(xué)習(xí)框架都有自己的模型格式登渣,在沒有 onnx 之前噪服,你得用 tf 來部署 tensorflow 的模型,用 pytorch 來部署 pytorch 的模型胜茧≌秤牛可用戶只想部署個(gè)模型而已,能不能把問題簡(jiǎn)單化呢呻顽?
丹尼爾:確實(shí)
蛋先生:于是就有了 onnx 這個(gè)開放標(biāo)準(zhǔn)雹顺。各家的模型格式都能轉(zhuǎn)換成這種標(biāo)準(zhǔn)格式,然后你就可以用一個(gè) onnxruntime 來部署和推理模型了芬位!
丹尼爾:那這個(gè) onnxruntime 是用什么實(shí)現(xiàn)的呢无拗?
蛋先生:它是用 C++ 實(shí)現(xiàn)的,在瀏覽器運(yùn)行時(shí)會(huì)被編譯成 WASM 格式昧碉。然后 onnxruntime-web 提供了 JS API 來與 WASM 進(jìn)行交互
丹尼爾:原來如此英染!那快給我看個(gè)代碼示例吧,我都迫不及待了
蛋先生:以下是一個(gè)數(shù)字圖像識(shí)別的簡(jiǎn)單例子被饿,不過接口有點(diǎn)底層哦四康,你得懂點(diǎn) tensor 之類的。希望以后有第三方庫能封裝個(gè)高級(jí)的接口狭握,比如手寫數(shù)字識(shí)別輸入是圖片闪金,輸出是數(shù)字;生成式 AI 輸入是 prompt,輸出是回答之類的哎垦。當(dāng)然你也可以自己嘗試嘗試
<!DOCTYPE html>
<html lang="en">
<head>
...
<script src="https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.min.js"></script>
...
</head>
<body>
...
<canvas id="canvas" width="28" height="28" style="display: none"></canvas>
<script>
let imageData;
...
async function runModel() {
// 提取圖像數(shù)據(jù)并規(guī)范化為模型的輸入格式
const input = new Float32Array(28 * 28);
for (let i = 0; i < input.length; i++) {
const r = imageData.data[i * 4];
const g = imageData.data[i * 4 + 1];
const b = imageData.data[i * 4 + 2];
const gray = 0.299 * r + 0.587 * g + 0.114 * b; // 轉(zhuǎn)換為灰度值
input[i] = gray / 255.0; // 規(guī)范化到 0~1
}
const inputTensor = new ort.Tensor("float32", input, [1, 1, 28, 28]);
// 使用 mnist-12.onnx 模型創(chuàng)建推理會(huì)話
const session = await ort.InferenceSession.create(
"https://media.githubusercontent.com/media/onnx/models/refs/heads/main/validated/vision/classification/mnist/model/mnist-12.onnx"
);
// 推理
const results = await session.run({ Input3: inputTensor });
const output = results.Plus214_Output_0.data;
// 找到最大值(概率最大)的索引囱嫩,即預(yù)測(cè)的數(shù)字
const predictedDigit = output.indexOf(Math.max(...output));
// 顯示結(jié)果
document.getElementById("output").innerText = predictedDigit;
}
...
</script>
</body>
</html>
有什么限制?
丹尼爾:除了數(shù)字識(shí)別漏设,還能玩點(diǎn)兒別的花樣不墨闲?
蛋先生:那當(dāng)然咯!語音識(shí)別郑口、圖像分類鸳碧、對(duì)象檢測(cè),甚至生成式 AI犬性,都不在話下瞻离!
丹尼爾:哇塞,連大語言模型都能搞定乒裆?
蛋先生:不過在瀏覽器上運(yùn)行有些小小的限制
丹尼爾:讓我來猜猜套利,是不是模型的大小有限制?
蛋先生:對(duì)頭
丹尼爾:具體能多大呢缸兔?
蛋先生:首先我們得加載遠(yuǎn)程模型
各大瀏覽器對(duì) ArrayBuffer 的大小都是有限制的日裙,比如 Chrome 就是2G。當(dāng)你用 fetch 去加載模型時(shí)惰蜜,需要用到 response.arrayBuffer()昂拂,如果模型超過2G,就 GG 了
還有啊抛猖,ONNX 模型是通過 protobuf 格式進(jìn)行傳輸?shù)母窈睿琾rotobuf 單個(gè)消息的大小限制也剛好是 2G
丹尼爾:所以最多只能加載2G的模型了?
蛋先生:那也不完全是财著。一次不行联四,我們可以分次嘛!我們可以將模型分成模型圖和權(quán)重撑教,權(quán)重信息作為外部數(shù)據(jù)另外加載即可朝墩。只要模型圖不超過2G,咱就可以突破這2G的限制了
丹尼爾:那豈不是可以加載超級(jí)大的模型了伟姐?
蛋先生:嘿嘿收苏,別高興得太早。模型最終是要加載到運(yùn)行時(shí)環(huán)境的愤兵,而我們的運(yùn)行時(shí)是在 WebAssembly 環(huán)境中鹿霸。根據(jù) WebAssembly 規(guī)范,Memory 對(duì)象的大小頂多4G秆乳。所以理論上4G就是天花板了
丹尼爾:哦……
蛋先生:而且啊懦鼠,當(dāng)模型太大時(shí)钻哩,對(duì)硬件的要求就更高了。這些大家伙就不推薦放在瀏覽器里折騰了
丹尼爾:明白了肛冶,那我去試一下咯
蛋先生:好的街氢,祝你好運(yùn)!
寫在最后
親們睦袖,都到這了阳仔,要不,點(diǎn)贊或收藏或關(guān)注支持下我唄 o( ̄▽ ̄)d