Intel Arc 向けの LLM 推論エンジン XeStrata を、Intel Arc Pro B60(VRAM 24 GB)を積んだ PC で動かそうとしたところ、エラーで起動が止まってしまいました。今回はその話を書きます。
調べてみると、B60 が原因ではなく、基本的な表示用に残した CPU 内蔵 GPU(UHD Graphics 630) にありました。 この記事では、原因を突き止めるまでの流れと、ソースコードをどう直したかをまとめます。
ClaudeのOpus 5.5とともに修正したので、↑のまとめ画像もつくってもらいました。
問題となった症状について
起動すると、エンジンのログにこう出ていました。
strata generate: 0 MiB of VRAM free at the start
strata generate: expert cache auto: 0.00 GiB free, 700 MiB reserved (...) -> 0 slots24 GB の VRAM がほぼ空いているのに、エンジンは「空き 0 MiB」と判断していました。 そのため、MoE モデルのエキスパートを VRAM に置くキャッシュの枠が 0 になり、正常な状態まで起動できませんでした。セットアップ時のウォームアップがこの「0」を測定値として設定ファイルに保存していました。 保存された値は --vram-free-mib 0 という起動引数として次回以降も渡されるため、何度起動し直しても同じ状態になります。
環境情報
今回の問題が起こっていた環境は次の通りです。
| 項目 | 内容 |
|---|---|
| CPU | Intel Core i7-9700K(内蔵 GPU: UHD Graphics 630) |
| GPU | Intel Arc Pro B60(24 GB、xe ドライバ) |
| OS | Ubuntu(Linux カーネル 7.0) |
| GPU ランタイム | Intel compute-runtime 26.05.37020.3(Level Zero)、IGC 2.28.4 |
| XeStrata | ソースからビルド(free モードと contrib-llvm モード) |
ポイントはGPU が 2 つ見えています。
- Arc Pro B60: PCI
0000:03:00.0(xe ドライバ)。推論に使う GPU です。 - UHD Graphics 630: PCI
0000:00:02.0(i915 ドライバ)。CPU の内蔵 GPU で、推論には使いません。
調査のながれ
Intel Arc B60 は別の同等環境で使えていたこと、メモリもストレージにも差が無く、コンテキストサイズの指定も同じとなれば、動くはずという確信はありました。結局、環境問題になりそうではあったので、切り分けなどは Claude Opus 5.5で進めてもらいました。
空き VRAM はどこから読んでいるか
XeStrata は SYCL の拡張 ext_intel_free_memory で GPU の空きメモリを読んでいます。 SYCL ランタイムは、この値を Level Zero の管理 API(sysman)から取得します。
そこで、sysman の初期化関数 zesInit を直接呼んで確かめると、失敗していました。
zesInit -> ZE_RESULT_ERROR_UNINITIALIZED (0x78000001)sysman が初期化できないと、SYCL は ext_intel_free_memory を「対応していない」と報告します。 XeStrata はこれを空き 0 として扱っていました。
原因: ドライバが内蔵 GPU を知らない
compute-runtime 26.05 は、UHD Graphics 630(Coffee Lake 世代、デバイス ID 8086:3e98)に対応していません。 そして zesInit は PC 上のすべての GPU をまとめて初期化しようとし、1 台でも失敗すると、全体が失敗扱いになります。
つまり、
- 対応外の内蔵 GPU が 1 台ある
- sysman の初期化が全 GPU で失敗する
- 対応しているはずの B60 の空き VRAM も読めない
- エンジンが「空き 0 MiB」と判断する
という巻き添えが起きていました。
対策と修正について
結局設定でどうにかできないか、と思ったのですが、AI使って調査までさせたこともあり、コードを修正する(パッチを当てる)というのが面白そうと思ったので、そちらで進めました。環境変数などの設定でも、一応できることはできました。NEOReadDebugKeys や FilterBdfPath の環境変数を使って、見えるGPUを縛るという方法です。
さて、ソースコードの変更ですが、AIに進めてもらいます。ここから先は AI が修正方針を立ててどう修正したかを記載します。
方針
Linux の xe ドライバには、GPU のメモリ領域を問い合わせる ioctl(DRM_XE_DEVICE_QUERY_MEM_REGIONS)があります。 これはそのカード 1 台の render ノード(/dev/dri/renderD*)に対して発行するので、他の GPU の影響を受けません。 試したところ、カーネル 7.0 では一般ユーザーでも B60 の VRAM 使用量が読めました(アイドル時 24480 MiB 中 40 MiB 使用)。
そこで、空き VRAM の読み取りを次の順にしました。
- まず、従来どおり SYCL(Level Zero sysman)に聞く。
- 読めなければ、使っている GPU の PCI アドレスから render ノードを探し、xe ドライバに直接聞く。
- どちらでも読めなければ「不明」とする(0 とはみなさない)。
エンジン側(C++)
src/core/device.cpp に、2 つの関数を足しました。
kernel_vram() は、PCI アドレスから /sys/bus/pci/devices/<pci>/drm/renderD* を探します。 見つけた render ノードを開いてドライバ名が xe であることを確かめ、DRM_XE_DEVICE_QUERY_MEM_REGIONS で VRAM の総量と使用量を読みます。 開いたファイルはプロセスの間、使い回します。
drm_xe_device_query q{};
q.query = DRM_XE_DEVICE_QUERY_MEM_REGIONS;
// 1 回目でサイズを聞き、2 回目で中身を読む
...
for (uint32_t i = 0; i < regions->num_mem_regions; ++i) {
const drm_xe_mem_region& r = regions->mem_regions[i];
if (r.mem_class != DRM_XE_MEM_REGION_CLASS_VRAM) continue;
total += r.total_size;
used += r.used;
}
// カーネルによっては、使用量を CAP_PERFMON 権限のあるプロセスにしか返さず、
// 他には 0 を返す。VRAM にはドライバ自身のバッファが必ずあるので、0 は「不明」とみなす
if (total == 0 || used == 0) return false;
free = total - used;device_free_memory() は、SYCL で読めればその値を使い、読めなければ kernel_vram() の値を使います。
bool device_free_memory(const sycl::device& d, uint64_t& free) {
if (d.has(sycl::aspect::ext_intel_free_memory)) {
try {
free = d.get_info<sycl::ext::intel::info::device::free_memory>();
return true;
} catch (const sycl::exception&) { /* カーネルドライバに聞く */ }
}
uint64_t total = 0;
if (!kernel_vram(pci_of(d), free, total)) return false;
// SYCL の値は global_mem_size を超えない。ドライバの数える VRAM はそれより大きいことがある
// (B60: 23256 MiB に対して 24480 MiB)ので、同じ上限をかける
free = std::min<uint64_t>(free, d.get_info<sycl::info::device::global_mem_size>());
return true;
}最後の min は細かいところですが、大事です。 SYCL が報告する全体のメモリ(global_mem_size)は 23256 MiB で、ドライバが数える VRAM の総量(24480 MiB)より小さくなっています。 エンジンは前者を基準にメモリを割り振るので、空き容量もそれに合わせて上限をかけています。
エンジン内で空き VRAM を読む箇所(デバイス情報、メモリ情報、メモリ不足時の判定)は、すべてこの関数を通すように変えました。 Windows や xe 以外のドライバでは、kernel_vram() は「読めない」と返すだけなので、従来と同じ動作になります。
Web サーバー側(Python)
XeStrata の Web サーバーにも、GPU のメモリ使用量を /metrics に出す機能があります。 こちらも同じく sysman で読めない場合に備えて、serve/telemetry.py に同じ ioctl を fcntl.ioctl で発行するフォールバックを足しました。 構造体は struct で組み立てています。 Windows には fcntl がないので、その場合は何もしません。
単体テストとして、ioctl をモックして次を確かめるテストも追加しました。
- VRAM 領域だけを合計し、システムメモリの領域は数えない。
- 使用量 0、VRAM 領域なし(内蔵 GPU)、ioctl のエラーは、どれも「不明」になる。
- sysman で読めるときは sysman の値を使い、i915 ドライバではフォールバックしない。
さて、このように作業が進んだところで、実行しながら確認して、うまく XeStrata が起動するようになりました。
まとめ
内蔵GPUが問題の引き金とは思っていなかったので、調査させて良かったと思います。確かに以前に動かしていた環境は iGPUなしだったので、環境差分を見ていたらどこかで気付いていたかもしれません。とはいえ、調査を任せて動作確認まで1時間半程度で終わったため、自分で進めていたら1日仕事だったでしょう。
今回の修正パッチについては、PRを作ってフィードバックできるほどのものではないと考え、作っていません。また動作確認環境も複数用意できないため、動作の保証すらとれず。ただ、もし同じように躓いている人がいたら、助けになるかもと思い、ここに記載しておきます。
おまけ
動くようになったXeStrataで、定番ボクセルアートを作らせてみました。数回のやり取りをしましたが、たったそれだけで出てきたのでさすがの精度でした。

コメント