Rust で GPU 計算と機械学習を扱うための実験的なリポジトリを公開しました。
このリポジトリは、大きく分けると CommonCL と Rusty Lantern の2つで構成されています。
CommonCL は、自作の GPU kernel 用言語で書いたコードを CUDA / ROCm / Metal / WGSL / Vulkan / OneAPI / CPU 向けの native source に変換し、同じ runtime から実行するための基盤です。
Rusty Lantern は、CommonCLを使用した Rust 製の機械学習ライブラリです。この記事ではCommonCL/Rusty Lantern の概要と sample_llm_fp16 で Qwen2.5 のサンプルを動かすところまでを紹介します。
この記事で分かること
- CommonCL と Rusty Lantern の目的・設計の概要
- CommonCL が移植性と実行速度を両立する仕組み
- Rusty Lantern ベースのLLM推論デモ(Qwen2.5)を動かす手順
rust、kernel、thread、fp16 などの言葉を見たことがあると読みやすいと思いますが、コマンドのコピペだけで体験できる構成です。
今回のゴールは、手元のマシンで Qwen2.5 0.5B に こんにちは を投げて、generated_text= まで表示することです。実行にはrustの環境が必要です。
CommonCLとRusty Lantern
何を目指しているのか
CommonCL/Rusty Lantern で扱いたいのは、Python系の既存スタックでGPU 計算や ML 実装を扱うときに出てくる次のような問題です。
- 移植性が低い
- 別マシンで動かない
- 去年のコードが動かない
- Dockerを持ち出すとマシになるが本質ではない手間と学習が増える
- 独自カーネルを書きにくい
- 独自カーネルを書くとその言語が使えるGPUにロックイン
- かといってOpenCLを書くとなんだか遅い
- Tritonとかはあるが対応backendが少ない
これらはだいたい以下が原因です。
- Pythonのゆるふわさ
- GPU関連のハードウェア依存
- GPU向け汎用規格の最適化が往々にして劣る
なのでこうします。
- Rustでしっかりする
- GPU関連のハードウェア依存を隠蔽する
- vendor 独自規格の上にのせて高速に実行する
CommonCL/Rusty Lanternは、この構成を使用して機械学習スタックを1から構築する アホの プロジェクトです。
大雑把に言うと対応関係はこうなっています。
- CommonCL: CUDA、OpenCLなどのGPU計算APIに相当
- Rusty Lantern: Pytorchなどの機械学習ライブラリに相当
現行のPython系エコシステムは、現実的により重視すべき他のメリットを優先した結果であり、確かに王道の正解です。しかし、その反対側からのアプローチが可能な選択肢が存在することは、私たちが直面する開発課題に対して、少なくとも一定の意味があるはずです。
実装
CommonCL + Rusty Lanternの呼び出し関係を整理すると以下の図のようになります。
CommonCLは、CommonCL言語で書かれたカーネルを7種のbackend(CUDA / ROCm / Metal / WGSL / Vulkan / OneAPI / CPU)で実行するための基盤です。
Rusty Lanternは、CommonCL をbackendにした Rust 製の ML ライブラリです。拡張性を重視していてCommonCL言語で書いた独自カーネルを使用できます。
まだ alpha 段階なので、API も実装も変わりうる点に注意してください。
Qwen2.5 0.5Bを動かす
ここからはコピペで体感するCommonCL/Rusty Lanternのコーナーです。
Rusty Lanternを使用して実装されたLLM推論サンプルのsample_llm_fp16 を動かします。モデルはちょっと古いですが Qwen2.5 系モデルで、実行はFP16(半精度)です。
対応しているモデルサイズは次の4つです。
0.5b1.5b3.0b7.0b
最初は 0.5b が無難です。モデルは量子化なしのFP16なので、パラメータあたり2 Byte消費し、VRAMはだいたい(モデルサイズ × 2 + 1GB)くらいあれば実行可能です。
必要なものは次のとおりです。
- Rust / Cargo
- git
- 初回モデル取得用のネットワーク
- 使いたい backend の runtime / driver / device
CommonCL/Rusty Lanternは移植性重視なので非常にシンプルです。
- まずclone します。
git clone https://github.com/MochiPiyo/commoncl_and_lantern.git
cd commoncl_and_lantern
- cloneしたら
--backendを指定して実行します。以上!
cargo run --release --manifest-path sample_llm_fp16/Cargo.toml -- \
--model 0.5b \
--prompt "こんにちは" \
--max-new-tokens 8 \
--backend metal
sample_llm_fp16 では、backend を次のように指定します。
| 環境 | 指定 |
|---|---|
| Apple Silicon / macOS | --backend metal |
| NVIDIA GPU | --backend cuda |
| AMD GPU / ROCm | --backend rocm |
| WebGPU 系 | --backend wgsl |
| Vulkan | --backend vulkan |
| OneAPI | --backend oneapi |
| CPUで小さく確認 | --backend cpu |
LinuxやWindowsの場合、CUDA / ROCm が使えるなら、まずはそこから試すのが高速です。追加の GPU 開発環境を何も入れていない場合の動作確認には wgsl が試しやすいです。wgsl は WebGPU 系の backend で、グラフィック向け API を使うため、GUI のある OS なら動く可能性が高いです。WGSL 自体は Web ブラウザ向けの shader 言語ですが、CommonCL では Firefox などでも使われている wgpu を内蔵しているため、ブラウザ外でも使えます。
macOSならMetalが最初から使えるはずです。
使えるbackendがわからないときはこのコマンドで確認してください。このコマンドは、CommonCL Runtime から見える backend や device 情報を表示しています。
cargo run -p commoncl_runtime -- caps
実行例
Runtime Capability Check
----------------------------------------
Backends
backend runtime reason
--------------------------------------------------------
cpu available cpu is always available
wgsl available wgpu found a compatible adapter through Metal
metal available Metal device creation succeeded
cuda unavailable runtime_unavailable: nvcc is not available in PATH
rocm unavailable runtime_unavailable: hipcc is not available in PATH
vulkan unavailable runtime_unavailable: glslangValidator is not available in PATH
oneapi unavailable runtime_unavailable: oneapi compiler (dpcpp/icpx) is not available in PATH
...(略)...
Apple Silicon / macOS なら、次のコマンドで試せます。
cargo run --release --manifest-path sample_llm_fp16/Cargo.toml -- \
--model 0.5b \
--prompt "こんにちは" \
--max-new-tokens 8 \
--backend metal
CUDA なら --backend cuda にします。
cargo run --release --manifest-path sample_llm_fp16/Cargo.toml -- \
--model 0.5b \
--prompt "こんにちは" \
--max-new-tokens 8 \
--backend cuda
ROCm なら --backend rocm です。
cargo run --release --manifest-path sample_llm_fp16/Cargo.toml -- \
--model 0.5b \
--prompt "こんにちは" \
--max-new-tokens 8 \
--backend rocm
初回実行時に必要なモデルファイルが無ければ、Hugging Face Hub から取得します。保存先は sample_llm_fp16/model/Qwen_Qwen2.5-0.5B-Instruct/ のようなモデル別ディレクトリです。モデル取得と初回 compile があるので、最初だけ少し時間がかかります。
成功すると、設定値、生成 token id、生成テキストが表示されます。当方の環境では生成文はこんにちは!どのようなお手伝いが必要(生成上限の8トークンで停止)でした。
backend=Metal
model=Qwen/Qwen2.5-0.5B-Instruct
max_new_tokens=8
prompt=こんにちは
generated_token_ids=[151644, 8948, ...(略)...]
generated_text=こんにちは!どのようなお手伝いが必要
まずは generated_text=... まで出れば成功です。生成内容は sampling 設定や backend によって変わることがあります。
0.5Bの次
0.5B が動いたら、--model を変えるだけで大きいモデルも試せます。また、promptやmax-new-tokensを変更することで色々遊べます。
backend指定を変えてみるのも良いでしょう。cpu, wgslは大体の環境で動作するはずです。oneapiやvulkanはコンパイルができる環境が必要なので現状では少し面倒です。
1.5Bで長い生成をする例
cargo run --release --manifest-path sample_llm_fp16/Cargo.toml -- \
--model 1.5b \
--prompt "GPUについて説明してください" \
--max-new-tokens 64 \
--backend metal
このプロンプトではこういう結果が得られました。
generated_text=GPU(Graphics Processing Unit)とは、グラフィックス処理ユニットのことを指します。主にゲーム機や高機能なパソコンで使用されるハードウェアアクセサリーです。
GPUは、通常、PC(Personal Computer)やゲーム機、デジタルレンダリングなどの高
3B / 7B も指定できます。
cargo run --release --manifest-path sample_llm_fp16/Cargo.toml -- \
--model 3.0b \
--prompt "こんにちは" \
--max-new-tokens 8 \
--backend metal
cargo run --release --manifest-path sample_llm_fp16/Cargo.toml -- \
--model 7.0b \
--prompt "こんにちは" \
--max-new-tokens 8 \
--backend metal
中では何が起きているのか
ここまでのサンプルでは、Rust 側から Tensor や LLM の処理を呼び出しています。その下では、CommonCL Runtime が backend を選び、必要な kernel や native library 呼び出しを実行します。
CommonCL の特徴は、CCL で書いた kernel を backend ごとの通常の GPU source に変換するところです。独自 VM で解釈しているわけではありません。
たとえば、単純なベクトル加算は CCL言語 だとこう書きます。ここでは説明を短くするため、dispatch 範囲と配列長の調整などは省いています。
@versions(commoncl = "0.1.0-alpha.1")
@group(0) @binding(0)
var<storage> lhs: Array<f32>;
@group(0) @binding(1)
var<storage> rhs: Array<f32>;
@group(0) @binding(2)
var<storage> mut out: Array<f32>;
@workgroup_size(64, 1, 1)
fn main() {
let i: u32 = workgroup_size.x * workgroup_id.x + thread_id.x;
out[i] = lhs[i] + rhs[i];
}
lhs と rhs が入力、out が出力です。workgroup_id と thread_id から、各 thread が担当する配列 index を作っています。
このCCL言語をCommonCL Compilerに通してCUDAカーネルに変換すると、およそ次のような kernel になります。
extern "C" __global__ void vector_add_kernel(
const float* __restrict__ lhs,
const float* __restrict__ rhs,
float* __restrict__ out
) {
uint32_t i = blockDim.x * blockIdx.x + threadIdx.x;
out[i] = lhs[i] + rhs[i];
}
CCL の workgroup_id / thread_id が、CUDA の blockIdx / threadIdx に対応しています。Metal 向けなら Metal の kernel、WGSL 向けなら WGSL の shader になります。
おまけ1: 量子化Llama
⚠️まだ実験段階なので遅いし、動作するのはたぶん metal / cuda / rocm くらいのbackendだけです。
sample_llama_quant では、GGUF 形式の量子化 Llama を読み込んで推論できます。
デフォルトでは unsloth/Llama-3.2-1B-Instruct-GGUF の Q4_K_M モデルを使います。--gguf を指定しない場合は、必要な .gguf ファイルと tokenizer を Hugging Face Hub から取得し、sample_llama_quant/model/ 以下に保存します。
量子化モデルなので fp16 のモデルよりメモリ使用量は小さく、手元では 2GB 程度のメモリがあれば動く想定です。ただし、まだ最適化は十分ではないため、速度を見るサンプルというより「GGUF を読んで CommonCL backend 上で Llama 推論まで通る」ことを確認するためのものです。
cargo run --release -p sample_llama_quant -- \
--prompt "hello world" \
--max-new-tokens 8 \
--backend metal
おまけ2: SDXL
⚠️まだ実験段階なので遅いし、動作するのはたぶん metal / cuda / rocm くらいのbackendだけです。
sample_stable_diffusion では SDXL の画像生成も試せます。
ROCm 環境で 384px 画像を生成する例です。VRAMは 12GB 以上が必要です。
cargo run --release --manifest-path sample_stable_diffusion/Cargo.toml -- \
--backend rocm \
--steps 20 \
--width 384 \
--height 384 \
--output sample_stable_diffusion/out_384_blue_sphere.png \
--prompt "a blue glass sphere on a wooden table, studio lighting, sharp focus" \
--negative-prompt "red cube, low quality, blurry" \
--guidance-scale 5.0 \
--seed 44 \
--pool-capacity-gb 12
生成例です。一応「青いガラスの球」らしきものは見えます...。
現時点の制限
現時点では、実際の ML workload を通して設計と実装を検証している段階です。
- API はまだ変わる可能性があります
- backend ごとに対応状況や性能差があります
- fp16、workgroup、barrier などは環境差があります
- LLM や SDXL の品質・速度はまだ改善中です
- サンプルによっては初回のモデル取得や kernel compile に時間がかかります
エラーが出る場合
CommonCL runtime/compiler の基本動作は各backendの実機で確認していますが、環境依存のバグはまだ残っていると思われます。
動かしてみてエラーが出た場合は、GitHub Issues で報告していただけるとたいへん助かります。
おわりに
CommonCL と Rusty Lantern は、Rust で GPU 可搬な ML 実行基盤を作るための実験的なプロジェクトです。
まだ alpha 版で、API も性能も実装もこれから変わっていくと思います。それでも、Rust だけで GPU kernel から Tensor、LLM、画像生成までつながる形が見えてきたので、いったん公開することにしました。
手元の環境で試していただいて、動いた backend、動かなかった backend、変なエラーなどがあれば GitHub Issues に投げてもらえると助かります。
機能詳細や設計の話は、別の記事で少しずつ書いていく予定です。
