はじめに
WSL2 上の Chrome で、Google のオンデバイス AI Gemini Nano(window.LanguageModel などの Prompt API で叩けるやつ)を動かしたかった。
が、普通にやると GPU がソフトウェアレンダリングに落ちて完全に詰む。色々調べて格闘した末、最終的に Mesa のドライバにパッチを当てて自前ビルドするという結論に至り、最後は本当に動いた。
この記事は、その過程で見つけた GitHub リポジトリと、そこから得たヒントをもとに、自分の環境(NVIDIA GeForce RTX 3060 Laptop)でローカル Gemini を動かすまでの記録。
結論を先に置いておくと、ハマりどころは「WSL で Vulkan が実 GPU を引けない」ことと、「Mesa Dozen が
fullDrawIndexUint32をfalseと報告して Dawn に弾かれる」ことの二段構えだった。
環境
- Windows 11 + WSL2(Ubuntu 24.04, x86_64)
- NVIDIA GeForce RTX 3060 Laptop GPU(VRAM 6GB)
- Windows 側に NVIDIA ドライバ導入済み(WSL の GPU サポート有効)
1. まず詰まる:SwiftShader で死ぬ
chrome://on-device-internals からオンデバイス機能を有効化しようとすると、モデルサービスがこう吐いて落ちる。
Selected adapter: SwiftShader Device (Subzero), backend=Vulkan, adapterType=CPU / Software
Terminating On-Device Model Service: Failed creation: Failed to create device:
Required limit (14336) is greater than the supported limit (8192).
- While validating maxTextureDimension2D
オンデバイスモデルは maxTextureDimension2D = 14336 を要求するが、SwiftShader(CPU ソフトウェア Vulkan)の上限は 8192。14336 > 8192 でデバイス生成に失敗している。
なぜ SwiftShader なのか。Vulkan が実 GPU を引けていないからだ。
vulkaninfo | grep deviceName
# deviceName = llvmpipe (LLVM ...)
llvmpipe。これも CPU ソフト実装。CUDA は通る(nvidia-smi も動く)のに、グラフィックス系の Vulkan には実 GPU が出てこない。 WSL では計算(CUDA)とグラフィックス(Vulkan)が別レイヤーで、Chrome のオンデバイスモデル(内部で Dawn/WebGPU を使う)が要求するのは後者なので、CUDA が動いていても無関係に詰む。
2. 真因その1:WSL に入れてはいけないドライバ
調べると、システムにネイティブ Linux 版の NVIDIA ドライバがフルセットで入っていた。
dpkg -l | grep nvidia
# nvidia-driver-580, nvidia-dkms-580, libnvidia-gl-580, xserver-xorg-video-nvidia-580 ...
これらは物理 Linux マシン用で、/dev/nvidia0 やカーネルモジュールの存在を前提にする。WSL にはそれが無いため初期化に失敗し、Vulkan が llvmpipe に落ちていた。
ls /dev/nvidia* # No such file or directory
lsmod | grep nvidia # 何も出ない
WSL で GPU を使う正しい仕組みは /usr/lib/wsl/lib/ 配下のドライバ(DXGI 経由で Windows 側 GPU を叩く)だけ。ネイティブドライバは害なので除去する。
sudo apt-get remove --purge '^nvidia-driver-.*' '^nvidia-dkms-.*' \
'^xserver-xorg-video-nvidia.*' '^libnvidia-gl-.*' \
'^nvidia-kernel-.*' '^libnvidia-cfg.*' nvidia-settings nvidia-prime
nvidia-smi は /usr/lib/wsl/lib/nvidia-smi(dpkg 管理外の WSL 正規版)が残るので CUDA 経路は無傷。
3. Dozn(D3D12 → Vulkan)を導入する
WSL には libd3d12.so があり D3D12 は通る。Mesa の Dozn (Dozen / dzn) は D3D12 の上に Vulkan を実装するドライバなので、これ経由なら実 GPU を Vulkan に見せられる。
sudo add-apt-repository ppa:kisak/kisak-mesa -y
sudo apt update
sudo apt install -y mesa-vulkan-drivers
VK_ICD_FILENAMES=/usr/share/vulkan/icd.d/dzn_icd.json \
LD_LIBRARY_PATH=/usr/lib/wsl/lib \
vulkaninfo | grep -A2 deviceName
deviceName = Microsoft Direct3D12 (NVIDIA GeForce RTX 3060 Laptop GPU)
maxImageDimension2D = 16384
実 GPU が Vulkan に現れ、maxImageDimension2D = 16384 で 14336 の要件も満たす。勝ったと思った。
4. 真因その2:fullDrawIndexUint32 で弾かれる
ところが Chrome のモデルサービスに Dozn を渡しても同じ場所で死ぬ。
Warning: Vulkan fullDrawIndexUint32 feature required.
- While initializing adapter (backend=BackendType::Vulkan)
at PhysicalDeviceVk.cpp:253
Selected adapter: SwiftShader Device (Subzero)
Required limit (14336) > supported limit (8192)
モデルサービスは内部で Dawn(WebGPU 実装) を使い、Dawn は初期化時に fullDrawIndexUint32 を必須要件として要求する。
vulkaninfo | grep -E "fullDrawIndexUint32|driverName"
# driverName = Dozen
# fullDrawIndexUint32 = false
Dozn がこれを false で返すため、Dawn は Dozn を却下し SwiftShader にフォールバック、8192 の壁で死ぬ。
Dozn(NVIDIA, 16384) → fullDrawIndexUint32 が無い → Dawn が却下
SwiftShader(8192) → 機能はあるが上限8192 → サイズ要件で却下
→ どちらも通らず詰み
5. 同じ壁にぶつかった人たち
調べると、この fullDrawIndexUint32 の問題は自分だけのものではなかった。
Gemini CLI の公式リポジトリに、まさに同じ症状の Issue が上がっていた(google-gemini/gemini-cli #27182)。NVIDIA 搭載の WSL2 で gemini gemma を動かそうとすると、Dawn が fullDrawIndexUint32 を要求し、Dozen がそれを満たせず Found 0 adapters で落ちる、という内容。ログも自分が見たものとほぼ同一だった。
Warning: Vulkan fullDrawIndexUint32 feature required.
- While initializing adapter (backend=BackendType::Vulkan)
at PhysicalDeviceVk.cpp:253
Found 0 adapters
Failed to create engine: INTERNAL: No adapters found
この Issue は kind/bug / priority/p2 でオープンのまま(執筆時点)。「Dawn がグラフィックス機能 fullDrawIndexUint32 を必須化していて、仮想化環境の Dozen には無い」ことが根本原因と整理されていた。つまり設定で回避できる類ではない、既知の構造的な問題ということ。
次に wsl2 dozn fullDrawIndexUint32 で検索を続けて、Qualcomm Adreno(Snapdragon X Elite)を WSL2 で動かすためのリポジトリ(Mycomembranes/adreno-wsl2-gpu)に辿り着いた。Mesa Dozen にパッチを当てるもので、パッチノートにこうあった。
fullDrawIndexUint32 = trueに変更。
D3D12 は常に 32bit インデックスをサポートしている。このフラグは不必要に保守的だった。
つまり、false は嘘というか、過度に保守的な値だった。D3D12 は本来 32bit インデックス描画に対応しているのに、Dozn が控えめに無効化していただけ。
このリポジトリ自体は ARM (aarch64) / Adreno 向けだが、パッチが触る src/microsoft/vulkan/dzn_device.c の変更点は Adreno 固有の処理ではない。D3D12 抽象を見ているだけなので、x86_64 + NVIDIA でもそのまま当たる(実際に当たった)。
- .fullDrawIndexUint32 = false,
+ .fullDrawIndexUint32 = true, # D3D12 は 32bit インデックスをサポート
- .logicOp = false,
+ .logicOp = true, # D3D12 FL11.0+ は logic op をサポート
- .major = 0, .minor = 0, # conformance 0.0 → 1.2 でローダー却下を回避
+ .major = 1, .minor = 2,
- vk_warn_non_conformant_implementation("dzn");
+ /* コメントアウト */
6. Mesa Dozen を自前ビルドする
ビルドツールを入れる(libllvmspirvlib-dev はパッケージ名が変わっているので除外)。
sudo apt install -y meson ninja-build gcc g++ pkg-config git glslang-tools \
llvm-dev libdrm-dev libx11-dev libxcb1-dev libxrandr-dev \
zlib1g-dev libexpat1-dev python3 python3-mako python3-yaml \
libwayland-dev wayland-protocols libxext-dev bison flex cython3 libxcb-randr0-dev
パッチが想定するバージョンの Mesa を取得して適用、Dozn だけ最小構成でビルドする。
git clone --depth=1 --branch mesa-25.0.5 \
https://gitlab.freedesktop.org/mesa/mesa.git ~/mesa
cd ~/mesa
# パッチ適用(リポジトリの .patch を git apply、または該当行を直接書き換え)
meson setup builddir \
--prefix=$HOME/mesa-dzn \
-Dvulkan-drivers=microsoft-experimental,swrast \
-Dgallium-drivers=swrast \
-Dplatforms=[] \
-Dglx=disabled -Dopengl=false \
-Dgles1=disabled -Dgles2=disabled \
-Degl=disabled -Dgbm=disabled \
-Dllvm=enabled \
-Dbuildtype=release -Db_ndebug=true
ninja -C builddir
ninja -C builddir install
ハマりどころメモ:
-
-Dcpp_rtti=falseは Ubuntu の LLVM(RTTI 有効)と衝突する。Remove cpp_rtti disable switchと言われたら外す -
-Dplatforms=x11は X11 dev パッケージを芋づるで要求する。計算用途なら-Dplatforms=[]でよい - ただし
platforms=[]のときは GLX も明示的にdisabledにしないと矛盾でこける
確認:
VK_ICD_FILENAMES=$HOME/mesa-dzn/share/vulkan/icd.d/dzn_icd.x86_64.json \
LD_LIBRARY_PATH=/usr/lib/wsl/lib:$HOME/mesa-dzn/lib/x86_64-linux-gnu \
vulkaninfo | grep -E "deviceName|fullDrawIndexUint32"
deviceName = Microsoft Direct3D12 (NVIDIA GeForce RTX 3060 Laptop GPU)
fullDrawIndexUint32 = true
false だった値が true になった。
7. Chrome に自前 Dozn を食わせる
VK_ICD_FILENAMES=$HOME/mesa-dzn/share/vulkan/icd.d/dzn_icd.x86_64.json \
LD_LIBRARY_PATH=/usr/lib/wsl/lib:$HOME/mesa-dzn/lib/x86_64-linux-gnu \
google-chrome --enable-features=OptimizationGuideOnDeviceModel
ログから SwiftShader が消え、実 GPU が選択される。
Selected adapter: Microsoft Direct3D12 (NVIDIA GeForce RTX 3060 Laptop GPU),
arch=ampere, vendor=nvidia, backend=Vulkan, adapterType=Discrete GPU
14336 > 8192 も fullDrawIndexUint32 required も消えた。chrome://components の "Optimization Guide On Device Model" を Update するとモデル本体がダウンロードされ、ステータスが「最新」になる。
chrome://flags で以下を有効化:
-
#optimization-guide-on-device-model→ Enabled (BypassPerfRequirement) -
#prompt-api-for-gemini-nano→ Enabled
8. Prompt API で動作確認
DevTools のコンソールで確認する。貼り付け保護がかかったら allow pasting(日本語 UI なら「貼り付けを許可」)を入力。WSL に日本語 IME が無い場合は DevTools の言語を英語にすると英字だけで抜けられる。
await LanguageModel.availability();
// "downloadable" or "available"
const session = await LanguageModel.create({ outputLanguage: "ja" });
const result = await session.prompt("やあ。日本語で自己紹介して。");
console.log(result);
応答が返り、別ターミナルの watch -n 1 /usr/lib/wsl/lib/nvidia-smi で GPU メモリ使用量が動けば、ローカルの GPU 上で推論が走っている。
まとめ
WSL2 でローカル Gemini を動かす障壁は二段構えだった。
- ネイティブ Linux 版 GPU ドライバが WSL に混入し、Vulkan が llvmpipe に落ちていた → 除去して WSL 正規ドライバに任せる
- Dozn が
fullDrawIndexUint32を保守的にfalse報告し、Dawn に弾かれていた → ソースをtrueに書き換えて自前ビルド
押さえておきたいポイント:
- WSL の「GPU 計算(CUDA)」と「グラフィックス(Vulkan)」は別レイヤー
- ドライバが報告する機能フラグは、必ずしもハードウェアの真の能力ではない
- OSS の強さは、別ハードウェア向けのパッチが自分の環境でもそのまま効くこと
検索でたまたま見つけた Adreno 向けのパッチが、まったく別の NVIDIA 環境を救ってくれた。先人に感謝。
参考
-
google-gemini/gemini-cli #27182 — 同じ症状(
fullDrawIndexUint32欠如で Dawn がFound 0 adapters)の公式 Issue -
Mycomembranes/adreno-wsl2-gpu — Adreno 向けだが、
fullDrawIndexUint32 = true化の Mesa Dozen パッチが流用できる(Apache-2.0、リポジトリ表記による) - Mesa Dozen ドライバ(
src/microsoft/vulkan/dzn_device.c) - 検索のきっかけになったキーワード:
wsl2 dozn fullDrawIndexUint32 - 一連の手順をまとめた自前スクリプトも別途用意した(環境変数
SKIP_GEMINI=1で「パッチ済み Dozn の汎用 Vulkan 環境」までで止められる)
クレジット:
fullDrawIndexUint32 = true化のアイデアと具体的なパッチは Mycomembranes/adreno-wsl2-gpu に基づく。ARM/Adreno 向けに書かれたものが、x86_64/NVIDIA の環境を救ってくれた。先人に感謝。