注意書き

本サイトでは、アフィリエイト広告およびGoogleアドセンスを利用しています。

内蔵GPUのせいでXeStrata起動失敗に遭遇

ディープラーニング・AI関連

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 slots

24 GB の VRAM がほぼ空いているのに、エンジンは「空き 0 MiB」と判断していました。 そのため、MoE モデルのエキスパートを VRAM に置くキャッシュの枠が 0 になり、正常な状態まで起動できませんでした。セットアップ時のウォームアップがこの「0」を測定値として設定ファイルに保存していました。 保存された値は --vram-free-mib 0 という起動引数として次回以降も渡されるため、何度起動し直しても同じ状態になります。

環境情報

今回の問題が起こっていた環境は次の通りです。

項目内容
CPUIntel Core i7-9700K(内蔵 GPU: UHD Graphics 630)
GPUIntel Arc Pro B60(24 GB、xe ドライバ)
OSUbuntu(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 台でも失敗すると、全体が失敗扱いになります。

つまり、

  1. 対応外の内蔵 GPU が 1 台ある
  2. sysman の初期化が全 GPU で失敗する
  3. 対応しているはずの B60 の空き VRAM も読めない
  4. エンジンが「空き 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 の読み取りを次の順にしました。

  1. まず、従来どおり SYCL(Level Zero sysman)に聞く。
  2. 読めなければ、使っている GPU の PCI アドレスから render ノードを探し、xe ドライバに直接聞く。
  3. どちらでも読めなければ「不明」とする(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で、定番ボクセルアートを作らせてみました。数回のやり取りをしましたが、たったそれだけで出てきたのでさすがの精度でした。

コメント

タイトルとURLをコピーしました