前回の記事でベアメタルに落ち着きました。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/v1kind: ResourceClaimmetadata: name: amd-gpuspec: 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: gpulemonade も 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 を追加することで解決しました。
apiVersion: nfd.k8s-sigs.io/v1alpha1kind: NodeFeatureRulemetadata: name: amd-gpu-strix-halo namespace: kube-amd-gpuspec: 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.go に normalizeToSemver を追加して 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 ですが統合メモリの考え方は同じです)。
This blog post introduces the MI300 APU hardware, how it differs from other discrete systems, and how to leverage its GPU programming
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"| GPU2 つの 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-plugin | DRA |
|---|---|---|
| GPU 共有 | 不可(1 Pod が独占) | 可(複数 Pod が ResourceClaim を参照) |
| k8s バージョン要件 | 古くから対応 | 1.32+ (beta) |
| 設定の複雑さ | シンプル | やや複雑 |
| ホームラボ 1 枚運用 | 実質使えない | 向いている |
Strix Halo 固有の対応をまとめるとこうなります。
- NFD カスタムルール — PCI ID
0x1586を手動登録(v1.5.0 時点) - driverVersion 正規化パッチ —
"1"→"1.0.0"(フォークv0.2.0に含む) - 環境変数 —
HSA_OVERRIDE_GFX_VERSION=11.5.1、HSA_XNACK=1
device-plugin で 1 枚の GPU を複数サービスに使い回そうとして詰まっている方の参考になれば。試したい場合はフォークの v0.2.0 をお使いください。
K8s DRA driver for AMD GPUs. Contribute to tamara1031/k8s-gpu-dra-driver development by creating an account on GitHub.
K8s DRA driver for AMD GPUs. Contribute to ROCm/k8s-gpu-dra-driver development by creating an account on GitHub.
Contribute to ROCm/gpu-operator development by creating an account on GitHub.









