k3s で AMD GPU を複数 Pod に共有する — DRA と Strix Halo の詰まりポイント

Progress 18 / 18
目次

前回の記事でベアメタルに落ち着きました。GPU の dirty state から回復できないという VFIO の壁を越えられず、k3s エージェントを直接 Ubuntu 26.04 上で動かす構成にした話でした。

今回はその上に乗っている GPU 管理レイヤーの話です。

GPUを複数Podで共有したい

k8s で AMD GPU を使う一般的な方法は device-plugin です。設定はシンプルで、Pod に amd.com/gpu: "1" をリクエストすれば動きます。

# device-plugin の場合
resources:
limits:
amd.com/gpu: "1"

ただ、これだと GPU は Pod が独占します。ホームラボに GPU が 1 枚しかない場合、他の Pod は全部 Pending になる。

おうちk8sには lemonade(llama.cpp ベースの推論サーバー)と ComfyUI(画像生成)の 2 つが動いています。エージェントが推論中に画像生成を呼び出すこともあるため、両者が同時に GPU を使うケースがあります。1 枚の GPU を 2 つのサービスで使いたいのに、device-plugin では片方しか動かせない。

DRA(Dynamic Resource Allocation)を選んだ理由

DRA は Kubernetes 1.26 で alpha に入り、1.32 で beta になった機能です。GPU などのリソースを ResourceClaim というオブジェクトで表現し、同じ ResourceClaim を複数の Pod が参照できます。

apiVersion: resource.k8s.io/v1
kind: ResourceClaim
metadata:
name: amd-gpu
spec:
devices:
requests:
- name: gpu
exactly:
deviceClassName: gpu.amd.com
allocationMode: ExactCount
count: 1

resource.k8s.io/v1 は Kubernetes 1.34 以降。それより古い k3s を使っている場合は v1beta1 / v1beta2 への読み替えが必要です。

Pod 側は resourceClaimName でこの ResourceClaim を参照します。

spec:
resourceClaims:
- name: gpu
resourceClaimName: amd-gpu
containers:
- name: lemonade
resources:
claims:
- name: gpu

lemonade も ComfyUI も同じ amd-gpu ResourceClaim を参照することで、1 枚の物理 GPU に両方からアクセスできます。VRAM の管理は各アプリが担当し、Kubernetes はその排他制御には関与しません。

gpu-operator での切り替えは values.yaml の 2 行だけです。

deviceConfig:
spec:
devicePlugin:
enableDevicePlugin: false
draDriver:
enable: true
image: ghcr.io/tamara1031/k8s-gpu-dra-driver:v0.2.0

この image: は公式ではなく私がフォークしたものです。その理由が次の話です。

Strix Halo で詰まった 3 つのポイント

NFD が GPU を検出しない

gpu-operator v1.5.0 の NFD(Node Feature Discovery)ルールには、Strix Halo の PCI デバイス ID 0x1586 が含まれていませんでした。NFD がラベルを付けないので、DRA ドライバーの DaemonSet が一切起動しません。GPU を積んでいるのに Kubernetes からは存在しないのと同じ状態です。

Helm チャートに NodeFeatureRule を追加することで解決しました。

charts/amd/gpu-operator/templates/strix-halo-nfd-rule.yaml
apiVersion: nfd.k8s-sigs.io/v1alpha1
kind: NodeFeatureRule
metadata:
name: amd-gpu-strix-halo
namespace: kube-amd-gpu
spec:
rules:
- name: amd-gpu-strix-halo
labels:
feature.node.kubernetes.io/amd-gpu: "true"
matchAny:
- matchFeatures:
- feature: pci.device
matchExpressions:
device:
op: In
value:
- "1586" # Strix Halo (gfx1151) — gpu-operator v1.5.0 未収録
vendor:
op: In
value:
- "1002"

gpu-operator の最新バージョンでこの ID が追加されている可能性はあるので、試す前にリリースノートを確認してみてください。

driverVersion が semver として無効

DRA ドライバーは GPU の情報を ResourceSlice に書き込んで kubelet に公開します。このとき driverVersion フィールドに amdgpu カーネルモジュールのバージョンを入れるのですが、Strix Halo の iGPU は sysfs から "1" という文字列を返します。空文字になるケースもあります。

Kubernetes の ResourceSlice API は semver(X.Y.Z 形式)を要求するため、"1" も空文字もバリデーションで弾かれて ResourceSlice の登録が失敗します。ResourceSlice が登録できなければ GPU は存在しないことになり、すべての ResourceClaim が Pending のままです。

フォーク(tamara1031/k8s-gpu-dra-driver、Apache 2.0)で修正したのは 2 ファイルです。

まず pkg/amdgpu/amdgpu.gonormalizeToSemver を追加して GetDriverVersion() に適用します。

// pkg/amdgpu/amdgpu.go — Copyright (c) Advanced Micro Devices, Inc. (Apache 2.0)
// normalizeToSemver pads a version string to semver 2.0.0 format (X.Y.Z).
// The amdgpu kernel module on some hardware (e.g. Strix iGPU) reports "1" instead of "1.0.0",
// which is rejected by the Kubernetes ResourceSlice API.
func normalizeToSemver(v string) string {
parts := strings.Split(v, ".")
for len(parts) < 3 {
parts = append(parts, "0")
}
return strings.Join(parts[:3], ".")
}

次に cmd/gpu-kubeletplugin/deviceinfo.go で、DriverVersion が空だった場合のフォールバックを追加します。

// cmd/gpu-kubeletplugin/deviceinfo.go — Copyright (c) Advanced Micro Devices, Inc. (Apache 2.0)
func (d *AmdGpuInfo) GetDevice() resourceapi.Device {
driverVersion := d.DriverVersion
if driverVersion == "" {
driverVersion = "0.0.0"
}
attributes := map[resourceapi.QualifiedName]resourceapi.DeviceAttribute{
// ...
"driverVersion": {VersionValue: ptr.To(driverVersion)},
}
// ...
}

"1""1.0.0"(normalizeToSemver)、空文字 → "0.0.0"(フォールバック)の 2 段構えです。どちらかが欠けると ResourceSlice の登録に失敗します。このパッチはフォーク v0.2.0 に含まれています。

ROCm が gfx1151 を正式サポートしていない

GPU を使う各コンテナに環境変数が必要です。lemonade も ComfyUI も同じ設定が必要です。

env:
- name: HSA_OVERRIDE_GFX_VERSION
value: "11.5.1"
- name: HSA_XNACK
value: "1"

HSA_OVERRIDE_GFX_VERSION がないと ROCm HIP ランタイムが GPU を「サポート外のデバイス」として扱いロードを拒否します。

HSA_XNACK=1 は Strix Halo のような APU に必要な設定です。Strix Halo は独立した VRAM を持たず、CPU と GPU が同じ物理メモリを共有します(96GB の LPDDR5X がそのまま GPU からも見える)。このアーキテクチャでは GPU が CPU 管理下のページにアクセスしようとしてフォルトするケースがあり、XNACK はクラッシュせずページフォルト経由でリトライできる仕組みです。AMD の MI300A でも同じ理由で XNACK が必要になります(あちらは HBM ですが統合メモリの考え方は同じです)。

MI300A - Exploring the APU advantage

This blog post introduces the MI300 APU hardware, how it differs from other discrete systems, and how to leverage its GPU programming

rocm.blogs.amd.com

DRA でデバイスアクセスを渡せていても、この 2 つがないとコンテナ内から GPU を使えません。

実際の構成

最終的にこういう構造になっています。

graph TD
Client["クライアント"]
Client -->|"OpenAI API\nテキスト推論"| lemonade
Client -->|"ComfyUI API\n画像生成"| ComfyUI
lemonade["lemonade\nllama.cpp / ROCm"]
ComfyUI["ComfyUI\nPyTorch / ROCm"]
Claim["ResourceClaim: amd-gpu"]
lemonade -->|"resourceClaimName"| Claim
ComfyUI -->|"resourceClaimName"| Claim
GPU["Strix Halo GPU\n96GB 統合メモリ\namdgpu driver"]
Claim -->|"CDI inject"| GPU

2 つの Pod が同じ ResourceClaim を参照する構成です。DRA は CDI(Container Device Interface)経由で各コンテナに /dev/dri/renderD128 などのデバイスノードを inject するだけで、VRAM の管理には関与しません。同時アクセスの制御は amdgpu ドライバーが担います。amdgpu は複数プロセスからの同時オープンをネイティブにサポートしているため、2 つのコンテナが同じデバイスを開いていても OS レベルでは問題ありません。

VRAM の分離はありません。lemonade と ComfyUI が同時に大量 VRAM を要求すれば OOM します。エージェントが推論中に画像生成を呼び出すケースもあるので、同時アクセスは実際に発生します。それでもクラッシュしていないのは、ひとえに 96GB という大容量の統合メモリのおかげです。運用開始から約 1 週間、今のところ VRAM 競合でのクラッシュはありません。

まとめ

比較軸device-pluginDRA
GPU 共有不可(1 Pod が独占)可(複数 Pod が ResourceClaim を参照)
k8s バージョン要件古くから対応1.32+ (beta)
設定の複雑さシンプルやや複雑
ホームラボ 1 枚運用実質使えない向いている

Strix Halo 固有の対応をまとめるとこうなります。

  1. NFD カスタムルール — PCI ID 0x1586 を手動登録(v1.5.0 時点)
  2. driverVersion 正規化パッチ — "1""1.0.0"(フォーク v0.2.0 に含む)
  3. 環境変数 — HSA_OVERRIDE_GFX_VERSION=11.5.1HSA_XNACK=1

device-plugin で 1 枚の GPU を複数サービスに使い回そうとして詰まっている方の参考になれば。試したい場合はフォークの v0.2.0 をお使いください。

GitHub - tamara1031/k8s-gpu-dra-driver: K8s DRA driver for AMD GPUs

K8s DRA driver for AMD GPUs. Contribute to tamara1031/k8s-gpu-dra-driver development by creating an account on GitHub.

github.com
GitHub - ROCm/k8s-gpu-dra-driver: K8s DRA driver for AMD GPUs

K8s DRA driver for AMD GPUs. Contribute to ROCm/k8s-gpu-dra-driver development by creating an account on GitHub.

github.com
GitHub - ROCm/gpu-operator

Contribute to ROCm/gpu-operator development by creating an account on GitHub.

github.com