- Python 中心の推論スタックにおける大きな依存関係とデプロイ負担を減らすため、Rust+Wasm Llama2 アプリを 2MB のバイナリとしてパッケージ化し、複数のデバイスで実行する
- 実装は llama.cpp を Wasm 向けに適合させたもので、GGUF モデルファイル、WasmEdge の GGML プラグイン、WASI NN API によりローカルのハードウェアアクセラレーションを活用する
- 例では Llama-2-7B-Chat GGUF の 5-bit 量子化モデルを使用し、低価格帯の M2 MacBook で約 25 tokens/s、Nvidia A10G で約 50 tokens/s の性能を示す
- WasmEdge ランタイムはコンテナツールと併用できるため、エッジデバイス、オンプレミス、クラウドに同じ Wasm バイナリをデプロイしやすい
- GGML ツールはすでに利用可能だが初期段階であり、より多くのハードウェア・OS プラグイン、llama.cpp 設定のサポート、複数の Wasm 互換言語での WASI NN API サポートが今後の課題
Rust+Wasm で Llama2 推論を実行
- Rust+Wasm スタックは、AI 推論において Python の代替として利用できる
- Rust+Wasm アプリは Python と比べてサイズが 1/100、速度が 100 倍になり得るほか、同一バイナリをコード変更なしで複数環境のハードウェアアクセラレーションに対応させられる
- Second State は、Llama2 モデル推論をネイティブ速度で実行するシンプルな Rust プログラムを作成した
- Wasm にコンパイルされたバイナリアプリは 2MB
- この Wasm アプリは、異種ハードウェアアクセラレータを備えたデバイス間で移植可能
- WasmEdge ランタイムは、クラウド環境で安全な実行環境を提供する
- WasmEdge はコンテナツールと併用することで、複数のデバイス上でポータブルアプリをオーケストレーションし実行できる
llama.cpp と GGUF ベース
実行手順
- Linux または Mac デバイスに WasmEdge と GGML プラグインをインストールする
curl -sSf https://raw.githubusercontent.com/WasmEdge/WasmEdge/… | bash -s -- --plugins wasmedge_rustls wasi_nn-ggml
- 事前ビルド済みの Wasm アプリをダウンロードする
curl -LO https://github.com/LlamaEdge/LlamaEdge/…
- 例では、Llama2 7B chat-tuned モデルを 5-bit weights で量子化した GGUF ファイルを使用する
curl -LO https://huggingface.co/second-state/Llama-2-7B-Chat-GGUF/…
- WasmEdge で Wasm 推論アプリを実行し、GGUF モデルを渡すと、対話形式で質問を入力できる
wasmedge --dir .:. --nn-preload default:GGML:AUTO:Llama-2-7b-chat-hf-Q5_K_M.gguf llama-chat.wasm
モデル設定と性能例
llama-chat.wasm はコマンドラインオプションでモデルとのやり取りの方式を設定する
--ctx-size: プロンプトのコンテキストサイズ、デフォルト 512
--n-predict: 予測するトークン数、デフォルト 1024
--n-gpu-layers: GPU で実行するレイヤー数、デフォルト 100
--batch-size: プロンプト処理のバッチサイズ、デフォルト 512
--temp: サンプリング温度、デフォルト 0.8
--repeat-penalty: 反復トークンのペナルティ、デフォルト 1.1
--prompt-template: llama-2-chat、codellama-instruct、mistral-instruct-v0.1、chatml、deepseek-chat、deepseek-coder などをサポート
--log-stat: 統計を出力
- コンテキスト長 2048、応答最大 512 トークン、統計出力を指定した実行例は次のとおり
wasmedge --dir .:. --nn-preload default:GGML:AUTO:Llama-2-7b-chat-hf-Q5_K_M.gguf llama-chat.wasm -c 2048 -n 512 --log-stat
- 低価格帯の M2 MacBook では、LLM の応答はデフォルトでストリーミング出力され、約 25 tokens/s で生成される
- ログ例では
eval time 基準で 82 回実行時に 25.64 tokens/s を記録した
kv self size は 1024.00MB、compute buffer total size は 630.14MB と表示される
- Nvidia A10G 搭載マシンでは約 50 tokens/s で動作する
- CUDA GPU アクセラレーションを使用し、35/35 レイヤーを GPU にオフロードする
- VRAM 使用量はモデル 4474.93MB、コンテキスト 1648.02MB、合計 6122.95MB と表示される
eval time 基準で 83 回実行時に 50.55 tokens/s を記録した
OpenAI 互換 API サーバーと LLM アプリ
Python 推論スタックの限界
- Llama2 のような LLM は通常、Python ベースのフレームワークである PyTorch、Tensorflow、JAX で学習される
- しかし AI コンピューティングの約 95% を占める推論アプリケーションでは、Python は不利だと見られている
- Python パッケージは複雑な依存関係により、設定や利用が難しくなることがある
- Python または PyTorch の Docker イメージは通常、数 GB から数十 GB にまで大きくなり得るため、エッジサーバーやデバイスでの AI 推論では負担が大きい
- Python は C、C++、Rust のようなコンパイル言語より非常に遅く、最大で 35,000 倍遅くなる可能性がある
- 実際のワークロードの多くは、Python ラッパー配下のネイティブ共有ライブラリに委譲される
- この構造はデモには適しているが、ビジネス固有の要件に合わせて内部を修正するのは難しいと見られている
- ネイティブライブラリへの高い依存と複雑な依存関係管理は、デバイス固有のハードウェア機能を活用する Python AI プログラムの移植性を低下させる
- LLM ツールチェーンでよく使われる Python パッケージ同士が、
pydantic のバージョン要件で衝突する例もある
llama-cpp-python は pydantic 2.0.1 を要求し、<=2.0 では動作しない
fastapi と chromadb は pydantic 1.9.2 を要求し、>=2.0 では動作しない
Rust+Wasm の利点
- Rust+Wasm スタックは、デバイス、エッジクラウド、オンプレミスサーバー、パブリッククラウドをまたぐ統合クラウドコンピューティングインフラとして利用できる
- AI 推論アプリケーションでは、Python スタックの強力な代替になり得る
-
超軽量
- 推論アプリケーションはすべての依存関係を含めて 2MB
- 一般的な PyTorch コンテナサイズの 1% 未満
-
高速実行
- 前処理、テンソル計算、後処理の全体でネイティブ C/Rust の速度を出せる
-
移植性
- 同じ Wasm バイトコードアプリケーションが主要なコンピューティングプラットフォームで動作する
- 異種ハードウェアアクセラレーションにも対応する
-
設定・開発・デプロイの容易さ
- 複雑な依存関係が減る
- ノート PC 上で標準ツールを使って単一の Wasm ファイルをビルドし、複数環境にデプロイできる
-
安全性とクラウド対応
- Wasm ランタイムは信頼できないユーザーコードを隔離するよう設計されている
- コンテナツールで管理し、クラウドネイティブプラットフォームにデプロイできる
Rust 推論プログラムの構造
- デモ推論プログラムは Rust で書かれ、Wasm にコンパイルされる
- 主要な Rust ソースコードは約 40 行
- Rust プログラムは次の役割を担う
- ユーザー入力の管理
- 会話履歴の追跡
- テキストを Llama2 chat template に変換
- WASI NN API を使って推論を実行
- 自分でビルドするには、Rust コンパイラと
wasm32-wasi コンパイルターゲットをインストールする
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh
rustup target add wasm32-wasi
- その後、ソースプロジェクトをダウンロードし、
cargo で Wasm ファイルをビルドする
git clone https://github.com/second-state/llama-utils
cd llama-utils/chat/
cargo build --target wasm32-wasi --release
cp target/wasm32-wasi/release/llama-chat.wasm .
クラウドとエッジへのデプロイ
- Wasm バイトコードファイルがあれば、WasmEdge ランタイムをサポートするあらゆるデバイスにデプロイできる
- 現在の GGML プラグインは generic Linux と Ubuntu Linux をサポートしている
- x86 および ARM CPU
- Nvidia GPU
- Apple M1/M2/M3
- WasmEdge GGML プラグインは llama.cpp をベースに、デバイスのハードウェアアクセラレーションを自動的に活用する
- Nvidia GPU がある場合、インストーラが CUDA 最適化済みの GGML プラグインを自動でインストールする
- Mac OS 向け GGML プラグインは Metal API を使い、M1/M2/M3 の内蔵 neural processing engine で推論ワークロードを実行する
- Linux CPU ビルドは OpenBLAS を使い、AVX や SIMD など最新 CPU の計算機能を自動検出して活用する
- この方式は、性能を犠牲にせずに異種 AI ハードウェアとプラットフォーム間の移植性を確保しようとするアプローチである
今後の作業
- WasmEdge GGML ツールは現在利用可能で、クラウドネイティブの顧客に使われているが、まだ初期段階にある
- 貢献できる領域は次のとおり
- より多くのハードウェアおよび OS プラットフォーム向けの GGML プラグイン追加
- TPU、ARM NPU、Linux および Windows の特殊な AI チップに関心がある
- より多くの llama.cpp 設定のサポート
- 現在は一部の設定オプションのみを Wasm から GGML プラグインへ渡せる
- 目標は GGML が提供するすべてのオプションをサポートすること
- 他の Wasm 互換言語での WASI NN API サポート
- Go、Zig、Kotlin、JavaScript、C、C++ に関心がある
LLM 以外のモデルサポート
1件のコメント
Hacker News のコメント
Rust と WASM は良いが、コードを見ると基本的な Rust のコマンドラインスクリプト 150行にすぎない
重い処理はモデルを WASI-NN バックエンドに渡す1行が担っており、ここでは WasmEdge ランタイムが提供している
ただし WasmEdge は Rust ではなく C++で、この場合 Rust がもたらす利点はほとんどなく、バックエンドは Python など他の言語からも呼び出せる
Python と PyTorch をエンドユーザーがダブルクリックして実行できる形にまとめるのは今でもひどく面倒で、実際の高性能コードはどちらも C++だが、その C++にいくつか命令を送るために 2GB 以上と数万個のファイルを配布する代わりに、2MB の実行ファイル1つで済むなら意味がある
すごい仕事だ。ただ、試したい人は依然としてサイズの大きい重みファイルをダウンロードしなければならない
本質的には、完全に移植可能で依存関係のない llama.cpp を 2MB にしたようなものだ
アプリ開発者なら、配布可能なファイルの中に推論エンジンをパッケージングする最も簡単な方法かもしれない。重みはすでに移植可能で、必要なときにダウンロードすればよいので、本当に固定したい部分は推論エンジンだ
言われている通り、重みがそのサイズを圧倒する
mainファイルは 1.2MBだ2MB というサイズ自体は特に印象的ではなく、wasm をターゲットにすることでより移植可能になる点が肝で、特別に圧縮されているわけではない
これは結局 llama.cpp をラップしているだけでは? 正直、
x.cppをラップするプロジェクトにはかなりうんざりしているこの6か月間、Rust + WebGPU の機械学習フレームワークを開発してきて、GG の仕事がどれほどすごいかすぐに分かった
まだ初期段階だが、ここで見られる:
https://www.ratchet.sh/
https://github.com/FL33TW00D/whisper-turbo
これが依存している wasm-nn、つまり https://github.com/WebAssembly/wasi-nn は、任意のチャンクをベンダー実装に送る方式の提案だ。API も実質的には入力設定、計算、出力設定という程度だ
だからまったく移植可能ではない
これが動作する理由は、llama.cpp にすでに実装されている抽象化に依存しているからだ。gguf モデルを複数のハードウェアターゲットへマッピングするそのコードを WasmEdge がそのまま取り込んでいる様子がここで見える: https://github.com/WasmEdge/WasmEdge/tree/master/plugins/was...
したがって「開発者はバインディングを使って高水準言語で機械学習アプリを書き、WebAssembly にコンパイルしてから WasmEdge のような wasi-nn 対応ランタイムで実行できる」といった説明は完全に間違っている。実際にはそうはできない
これは移植可能でもなく、サンドボックスでもなく、ハードウェア抽象化レイヤーでもない
wasm バイナリがあっても、使っているランタイムのバージョンが偶然にも必要な特定の ggml バックエンドを実装している場合にだけ実行可能で、そうする要件はまったくないので、たいていはそうではない可能性が高い
できたとしても、結局は llama.cpp の ggml コードを呼び出すことになるので、安全性はそのライブラリ次第だ
記事では「移植性」と「Rust」を強調しすぎているが、実際にはどちらの利点もあまり見えてこない
新しいハードウェアに WASI ランタイムがあると仮定して、モデルを実行できるのか? GPU サポートはあるのか? 答えは「そのプラットフォームで llama.cpp が GPU サポート付きでコンパイルされるか、使っているランタイムに ggml プラグインがあるか、その中に適合するバージョンの ggml がベンダリングされているかを確認してみて。そうでなければ無理」ということになる
それなら、いったいなぜ WASI を使うのか?
クロスプラットフォームの GPU サポートが難しいのは確かだが、これはかなりばかげて見える
WebGPU が「GPU にバイナリチャンクを投げると、それが現在のハードウェアに合ったチャンクだった場合に、もしかしたら何か描画してくれるかもしれない」というような方式だったと想像してみれば、まさにそういう構造だ
これを iPhone でオフライン実行できる? そうなら、受信状況に関係なく基本的なインターネット検索のように使えそうで、キャンプのときに便利そうだ
ただしモデル自体を載せるには、依然として数GBのメモリが必要だ
[0] https://github.com/mlc-ai/mlc-llm
最初は ggml で作ったが、その後 mlc-llm を見つけて、それをベースに移植した
[0]: https://apps.apple.com/us/app/private-llm/id6448106860
流れとして、エッジでTransformerアーキテクチャをより効率的かつ高速に実行する方法は今後も出てくるだろうが、VRAM要件はRustでは解決できないので、限界に近づいているように思う
十分に大きなモデルを載せるときの主なボトルネックはまさにこの部分
「小さなモデルは良くなっている。MistralやLlama 2を見よ」とは言えるが、小さなモデルも容量の限界に近づいている。70億パラメータに詰め込める情報には限界がある
こうしたAIのアプローチがAGIにつながるとは思えない。あまりに非効率に感じる
「Mac OS向けGGMLプラグインはMetal APIを使い、M1/M2/M3内蔵のニューラル処理エンジンで推論処理を実行する」という説明は正確ではないように思う
GGMLがMetal APIを使うというのは、M1/2/3のGPU上で実行されるという意味であって、Neural Engine上で実行されるという意味ではない
もちろんそれ自体でも良いことだが、厳密にはそういうこと
コアライブラリを包んだラッパーが別の言語を使っているという理由で、プロジェクトが他の解法よりサイズを1/100にしたり、速度を100〜35000倍にしたりしたかのように語るクリックベイト的マーケティングが嫌い
他の解法の周辺に積み上がったツールやコミュニティの専門性も完全に無視している
まず、このプロジェクトはllama.cpp[1]ベースであり、数GB規模のモデルファイルをGPU/CPUに載せて実行する重い処理はllama.cppが担っている
推論速度はラッパーの選択で制限されるものではなく、Go、Python、Node、Rustのラッパーもあるし、llama.cppを直接使ってもよい
バイナリサイズもそれほど重要ではない。一般的な量子化モデルファイルは5GB〜40GBの範囲で、強力なGPUや16〜64GB RAMを備えたマシンが必要になるから
[1] https://github.com/ggerganov/llama.cpp
サイズの大部分が結局、学習済みモデルの重みだとしたら、精度を失わずにどうやってサイズを数桁も削減できるのか?
ここで言う2MBは、モデルを除いたプログラムサイズを指しているように見える。wasmとllama.cppを実行するRustサーバーでllama.cppを動かす方式のようだ
小さな
llama.cpp/examples/serverが好きでFreeChatに埋め込んでいるが、ツールの選択肢が増えるのはいつでも良いこと確認してみると、私が埋め込んでいるarm64/x86実行ファイルは現在4.2MB。FreeChatは12.1MBだが、デフォルトモデルが約3GBなので、2MBの差はあまり気にしていない
[0]: https://github.com/ggerganov/llama.cpp/tree/master/examples/...
この記事は推論アプリのサイズを100分の1にする話を扱っている
これをllama.cppを直接使う代わりに使う理由が分からない
記事では、異種ハードウェアアクセラレータを搭載したデバイスでハードウェアアクセラレーションを活用するとも説明している
これは、wasmにコンパイルされたプログラムが、複数のデバイス上でGPUや専用AIチップのような多様なハードウェア資源を効率的に使えるという意味
C++で直接実装すると、同程度の性能を出すにはハードウェアの種類ごとに別々の最適化やバージョンが必要になる場合がある