技術 約11分で読めます

SwiftLMはTurboQuantとSSDストリーミングをMetalシェーダーに統合したSwift製LLM推論サーバー

TurboQuantのKVキャッシュ量子化アルゴリズムを以前紹介したが、あれはGoogle Researchの論文の理論解説だった。SwiftLMはそのTurboQuantをネイティブのC++/Metalシェーダーに実装し、SSDエキスパートストリーミングと組み合わせた実動するSwift製推論サーバーだ。Pythonランタイムなし、GILなし、Apple SiliconのMLXフレームワーク上で直接動く。iOSアプリまで付いている。

SwiftLMの全体像

SwiftLMは100%ネイティブSwiftで書かれたLLM推論サーバーで、OpenAI互換API(/v1/chat/completions)を提供する。内部ではAppleのMLXフレームワーク(mlx-swift)を使い、HTTPサーバーにはSwift製のHummingbirdを採用している。

主要な特徴は2つある。

  • TurboQuant V2+V3ハイブリッドによるKVキャッシュのオンザフライ圧縮(約3.5倍)
  • MoEモデルのエキスパート層をNVMe SSDからGPUコマンドバッファへゼロコピーストリーミング

ターゲットハードウェアはMacBook Pro M5 Pro(64GB統合メモリ)で、Qwen3.5-122B-A10B-4bitを動かすことを前提に設計されている。iOSアプリではiPhone 13 Pro(6GB)上でQwen3 1.7Bが動作する。

graph TD
    A[HuggingFaceモデル<br/>Safetensors] --> B[SwiftLM Server<br/>macOS]
    B --> C[MLX Swift<br/>Metal GPU]
    C --> D[TurboQuant V2+V3<br/>KVキャッシュ圧縮]
    C --> E[SSD Expert Streaming<br/>NVMeゼロコピー]
    B --> F[OpenAI互換API<br/>localhost:5413]
    
    G[SwiftLM Chat<br/>iOS App] --> H[MLX Swift<br/>オンデバイス推論]
    H --> I[iPhone/iPad<br/>Metal GPU]
    
    style D fill:#e8f5e9,stroke:#4caf50
    style E fill:#e3f2fd,stroke:#2196f3

TurboQuant V2+V3ハイブリッド実装

前回の記事ではTurboQuantの理論(PolarQuantの極座標変換 + QJLの1ビット符号補正)を解説した。SwiftLMが面白いのは、理論をMetal GPUで実用的な速度で動かすために「V2の速度とV3の品質」を融合させた点だ。

TurboQuantのオープンソース実装は大きく2つの路線に分かれていた。

路線量子化方式速度品質
V2(ハードウェア加速)線形アフィン量子化高速(MLXのquantized_matmul活用)3ビットで劣化が目立つ
V3(論文準拠)非線形Lloyd-Maxコードブック低速(ソフトウェア逆量子化)高品質

V2はturboquant-mlxなどの既存実装で採用されていた方式で、MLXのハードウェア加速された行列演算を使えるため速いが、3ビットまで落とすとアフィン(線形)量子化の限界が出る。V3はLloyd-Maxアルゴリズムで最適な非線形コードブックを事前計算するため品質は高いが、逆量子化(圧縮データから元の値を復元する処理)をソフトウェアで行う必要があり遅かった。

SwiftLMはV3のLloyd-Maxコードブックをネイティブの C++エンコーディングパスに移植し、逆量子化を融合Metalシェーダー(bggml-metal)で処理する。つまりV3品質のコードブックをGPUのハードウェアで高速に逆量子化する。

Lloyd-Maxコードブックとは

Lloyd-Maxは1960年代に提案されたスカラー量子化の最適化アルゴリズムで、入力データの確率分布に対して平均二乗誤差(MSE)を最小化する量子化レベルを反復計算する。通常のアフィン量子化が等間隔のグリッドで値を丸めるのに対し、Lloyd-Maxはデータの密度が高い区間に細かくレベルを配置し、密度が低い区間は粗くする。

TurboQuantではPolarQuantの角度変換後の座標が標準正規分布N(0, 1/d)に従うことが理論的に示されている。この分布は事前に既知なので、Lloyd-Maxのコードブック(8レベル、3ビット)を推論開始前に1回だけ計算しておける。データ依存のキャリブレーションが不要なのがTurboQuantの強みだが、そのコードブック自体が非線形であるため、GPU上での逆量子化にはカスタムシェーダーが必要になる。

KVキャッシュ圧縮の内訳

SwiftLMの実装では、K(Key)キャッシュとV(Value)キャッシュで異なる戦略を取る。

Kキャッシュは4.25ビット/次元で量子化する。

  1. L2ノルムを抽出して正規化
  2. Fast Walsh-Hadamard Transform(WHT)で回転し、外れ値を均等に分散
  3. 3ビット非線形Lloyd-Maxセントロイドで量子化
  4. 元ベクトルと量子化近似の残差を計算
  5. ランダムJohnson-Lindenstrauss(QJL)行列で残差を射影し、1ビット符号を保持

Vキャッシュは3.125ビット/次元。内積によるアテンションスコアリングに使われないため、QJLの誤差補正が効かない。SwiftLMはVキャッシュのQJLを無効化することで、品質を落とさず25%のメモリ削減を追加で得ている。

全体で約3.6ビット/次元、FP16比で約3.5倍の圧縮になる。前回の記事で解説したとおり、PolarQuantの角度集中性によって量子化定数(スケール/ゼロポイント)のオーバーヘッドがゼロになるため、実効ビット幅が公称値とほぼ一致する。KIVIのINT2が実効約2.25ビットに膨らむのとは対照的だ。

Walsh-Hadamard Transform(WHT)の役割

WHTはKキャッシュの量子化前に適用される回転操作で、ベクトル中の外れ値(他の要素より極端に大きい/小さい値)を全次元に均等に分散させる。LLMのアテンションKVベクトルは一部のチャネルに値が集中する傾向があり、そのまま量子化すると外れ値チャネルの精度が大きく劣化する。

WHTは加減算だけで構成される直交変換で、FFT(高速フーリエ変換)に似た分割統治構造を持つ。乗算が不要なため「Fast」の名に恥じない速度で動く。SwiftLMではturbo-wht.hとしてCカーネルに実装されており、TheTom/llama-cpp-turboquantからの移植だ。

SSD Expert Streaming

SwiftLMの2つ目のコア機能は、MoEモデルのエキスパート層をNVMe SSDからGPUコマンドバッファへゼロコピーでストリーミングする仕組みだ。--stream-expertsフラグで有効化する。

なぜ必要か

Qwen3.5-122B-A10Bは総パラメータ1220億のMoEモデルで、4ビット量子化でもモデル重み全体はおよそ60GBになる。64GBの統合メモリに載せると、KVキャッシュやOSの動作領域を含めてメモリが逼迫する。macOSの統合メモリが物理限界を超えると仮想メモリスワッピングが発生し、最悪の場合Watchdog(macOSがシステム応答を監視するカーネルプロセス)がGPUハングを検知してカーネルパニックを引き起こす。

従来のアプローチでは、macOSの仮想メモリシステムに任せてスワップさせていた。しかしこれはSSD I/Oのパターンが推論パイプラインに最適化されていないため、ランダムなページフォルトが頻発する。Flash-MoEの記事でmmapが5倍の性能悪化を起こした事例と同じ問題だ。

ゼロコピーストリーミングの仕組み

SwiftLMのSSD Expert Streamingは、MoEモデルの疎性を利用する。Qwen3.5-122B-A10Bは各層に複数のエキスパートを持ち、1トークンの処理で活性化されるのはそのうちK=4個だけだ。非エキスパート部分(Attention、正規化、ルーター等)はGPUメモリに常駐させ、エキスパートの重みだけをNVMeから必要に応じて読み込む。

graph TD
    A[推論リクエスト] --> B[非エキスパート層<br/>GPU常駐]
    B --> C[MoEルーター<br/>上位K=4を選択]
    C --> D[NVMe SSD<br/>エキスパート重みファイル]
    D -->|ゼロコピー読み込み| E[GPUコマンドバッファ<br/>直接転送]
    E --> F[Metal GPU<br/>エキスパート順伝播]
    F --> G[出力トークン]
    
    style D fill:#fff3e0,stroke:#ff9800
    style E fill:#e3f2fd,stroke:#2196f3

「ゼロコピー」とは、SSDから読み込んだデータがユーザー空間のバッファを経由せずにGPUコマンドバッファへ直接渡されることを意味する。通常のファイルI/OではSSD→OSカーネルバッファ→ユーザー空間バッファ→GPU転送という経路を辿るが、SwiftLMはこの中間コピーを省略する。Apple SiliconのUnified Memory Architecture(CPU/GPU/NPUが同じ物理メモリを共有する設計)がこれを可能にしている。

Flash-MoE、Hypuraとの設計比較

Flash-MoEHypuraに続いて、Apple SiliconのNVMe帯域をLLM推論に活用するプロジェクトの3例目になる。

Flash-MoEHypuraSwiftLM
言語C + 手書きMetalシェーダーRust + llama.cppフォークSwift + MLX
モデル形式SafetensorsGGUFHuggingFace Safetensors
対象モデルMoE専用MoE + DenseMoE専用
KVキャッシュ圧縮なしなしTurboQuant V2+V3
SSD I/O方式pread()並列pread() + F_NOCACHEゼロコピーGPUバッファ転送
キャッシュOSページキャッシュ(71%)自前LRU(99.5%)記述なし
APIなしOllama互換OpenAI互換
Python依存なしなしなし
iOS対応なしなしあり

Flash-MoEは397Bパラメータのモデルを48GBのM3 Maxで4.36 tok/sで動かした実績がある。Hypuraは99.5%のキャッシュヒット率とDenseモデル対応が強みだった。SwiftLMの独自性はTurboQuantによるKVキャッシュ圧縮との統合だ。エキスパートのストリーミングでメモリを節約しつつ、KVキャッシュも3.5倍に圧縮することで、長いコンテキストでの推論でもメモリ逼迫を防ぐ。

ただしSwiftLMのREADMEにはトークン/秒の明確なベンチマーク数値が記載されていない。GPUのアクティブVRAM使用量が2,694MBという報告はHacker Newsのスレッドに出ているものの、推論速度の比較データはFlash-MoEやHypuraに比べて薄い。

4ビット量子化の制約

SwiftLMのREADMEでは、4ビット量子化がMoEモデルの本番標準と明記されている。2ビット量子化ではJSON文法が不安定になり、"name"\name\ に変化してOpenAI互換のツール呼び出しが壊れるという報告がある。

これはFlash-MoEの実験結果と完全に一致する。Flash-MoEの記事でも2ビット量子化で同じ "name"\name\ の症状が報告されており、4ビット構成が推奨されていた。2つの独立したプロジェクトが同じ症状を報告しているのは、MoEモデルの2ビット量子化に構造的な限界があることを示唆している。

iOS対応: SwiftLM Chat

SwiftLM ChatはiPhone/iPad用のネイティブアプリで、HuggingFaceからMLXモデルを直接ダウンロードしてオンデバイスで推論する。iPhone 13 Pro(6GB)でQwen3 1.7Bが動作する。

サーバーサイドのSwiftLMと同じmlx-swiftを使うが、SSD Expert StreamingやTurboQuantはiOS版には含まれていない(iOSのストレージI/OとメモリモデルはmacOSとは異なるため)。モデルカタログにはQwen3、Phi-3.5、Mistral、Llamaが並び、デバイスのRAMに収まるかどうかのインジケーターが表示される。

Pythonもサーバーもなく、Metal GPUだけで推論が完結するというのは、iOSでのLLM実行としてはかなりクリーンな構成だ。

metallib依存の罠

SwiftLMのセットアップで注意すべき点がある。Metal GPUカーネルはdefault.metallibというプリコンパイル済みバイナリに格納されており、これはmlx-swiftサブモジュール内にバージョン固定で同梱されている。

READMEにも太字で警告されているが、Pythonのpipパッケージとしてインストールできるmlx-metalからmlx.metallibを取ってくると、サーバーは起動するものの推論中にGPUカーネルのABI不整合でfreed pointer was not the last allocationクラッシュが発生する。Pythonのmlx-metalとSwiftのmlx-swiftは同じMLXファミリーだが、ビルドされたバージョンが異なるため、Metalカーネルのバイナリインターフェースが一致しない。

# 正しい方法: サブモジュール内のmetallibを使う
cp LocalPackages/mlx-swift/Source/Cmlx/mlx/mlx/backend/metal/kernels/default.metallib \
   .build/release/

# 間違った方法: pip mlx-metalから取ってくる(クラッシュする)
# uv run --with mlx-metal python -c "...shutil.copy(metallib, ...)"

MLXエコシステムはPython(mlx)、Swift(mlx-swift)、C/C++ APIと複数の言語バインディングがあり、それぞれのリリースサイクルが独立している。OllamaのMLXバックエンド移行でもMLXのバージョン管理が課題として挙がっていたが、SwiftLMではMetalカーネルのバイナリレベルで互換性が崩れるため、影響がより深刻だ。

実行方法

# ソースからビルド
git clone --recursive https://github.com/SharpAI/SwiftLM
cd SwiftLM
swift build -c release

# metallibをバイナリと同じディレクトリにコピー
cp LocalPackages/mlx-swift/Source/Cmlx/mlx/mlx/backend/metal/kernels/default.metallib \
   .build/release/

# 122B MoEモデルをSSDストリーミングで実行
.build/release/SwiftLM \
  --model mlx-community/Qwen3.5-122B-A10B-4bit \
  --stream-experts \
  --port 5413

--stream-expertsが肝で、これなしでは122Bモデルが統合メモリを食い尽くしてカーネルパニックのリスクがある。--gpu-layersオプションでGPUに割り当てるレイヤー数を制限することも可能だ。

OpenAI互換APIなので、既存のSDKやツールからそのまま接続できる。

curl http://localhost:5413/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{
    "model": "Qwen3.5-122B-A10B-4bit",
    "stream": true,
    "messages": [
      {"role": "user", "content": "Hello"}
    ]
  }'

macOS 14.0以上、Apple Silicon(M1以降)、Xcode Command Line Toolsが必要。Metal Toolchainはxcodebuild -downloadComponent MetalToolchainでインストールする。

成熟度と今後

SwiftLMはシャープAIのローカル映像セキュリティ製品(SharpAI Aegis)のML推論バックエンドとして1〜2週間で開発されたプロジェクトだ。Hacker Newsでは「vibe coding」の指摘や、包括的なベンチマークが不足しているという批判もある。Flash-MoEが58回の実験ログを公開していたのと比べると、検証の厚みには差がある。

それでも、TurboQuantの理論をネイティブMetalで実動させ、SSDストリーミングとOpenAI互換APIまで統合したのは、プロトタイプとしてよくできている。あとはベンチマーク次第だ。