Skip to main content

K3sでエッジAI推論基盤を組んだら、1台のGPUに2つのモデルを乗せられなかった。fractional GPUという壁

1. 1台のJetsonに2つ目の推論モデルを乗せようとしたら、Podがスケジュールされなかった

section01

K3sでエッジAI推論基盤を組んでいる現場でよく起きる話がある。1台のエッジノードに画像認識モデルをデプロイして無事に動いた。次に、同じノードにもう1つ別の推論モデルを追加でデプロイしようとしたところ、新しいPodがPendingのまま動かなくなった。

ノードのリソースを確認すると、CPUにもメモリにも余裕はある。それなのにスケジューラは「GPUが足りない」と判断して新しいPodを配置できない。これは設定ミスではなく、Kubernetesの標準的なGPUリソース管理の仕様そのものが原因だ。

標準Kubernetesであれ、K3sであれ、この制約は同じ土俵の上にある。むしろK3sのような軽量ディストリビューションでエッジに1ノード1GPUの構成を組むときほど、この壁に真っ先にぶつかる。K3sはサーバーで2GB、エージェントで512MBという最小要件で動作し(K3s公式ドキュメント)、シングルバイナリで素早く展開できる軽量さが魅力だが、その省リソース性と、GPUというハードウェア資源の扱いにくさは別の問題として向き合う必要がある。

本記事では、なぜこの壁にぶつかるのか、Kubernetesの設計思想から掘り下げ、K3s環境でエッジAI推論基盤を組む際にどう回避すべきかを解説する。

2. 原因はKubernetesの設計思想。GPUは「割当の有無」だけを見ていて「使用率」を見ていない

section02

まず理解すべきは、KubernetesがGPUをCPUやメモリとは根本的に異なる扱いをしているという事実だ。

CPUやメモリはLinuxカーネルのcgroupsによって細かく制御できる。500m(0.5コア)のような小数単位のリクエストが可能で、使われていない分は他のPodに融通される。しかしGPUは事情が違う。Kubernetes公式ドキュメントははっきりとこう定義している。

Extended resources must be advertised in integer amounts. For example, a Node can advertise four dongles, but not 4.5 dongles.

GPUはKubernetesにおいて「拡張リソース(Extended Resource)」として扱われ、整数単位でしか広告・割当ができない。0.5個のGPUをリクエストすることはできず、1個の物理GPU全体を占有するか、まったく使わないかの二択になる。

この背景には、GPUがcgroupsの管理対象外であるという技術的な事情がある。ScaleOpsの解説によれば、CPUやメモリはカーネルプリミティブでメータリング・制限できるのに対し、GPUは「opaque(不透明)」で、NVIDIAドライバが独立して制御する領域だという。そのため、Kubernetesのスケジューラから見えるのは「GPUが割り当てられているかどうか」だけであり、

The resource count tracks allocation, not use.

実際の使用率はまったく見えない。GPU使用率0%のPodも99%のPodも、スケジューラにとっては同じ「GPU 1個を占有している」状態として扱われる。これが、1台のGPUに2つ目の推論モデルを素直に同居させられない根本原因だ。

3. NVIDIA device pluginとtime-slicingの仕組み。1個のGPUを「見かけ上」複数に増やす

section03

そもそもKubernetesは、GPUの存在すら標準では認識できない。GPUをPodに割り当てられるようにするには、NVIDIA device pluginをDaemonSetとして各ノードにデプロイする必要がある。NVIDIA公式のdevice pluginリポジトリによれば、このプラグインは各ノードのGPU数を自動検出し、nvidia.com/gpuというリソース名でkubeletに登録する。Pod側は以下のようにリソースを要求する。

yaml
resources:
  limits:
    nvidia.com/gpu: 1

ここで効いてくるのがtime-slicingという仕組みだ。NVIDIA公式ドキュメントによると、time-slicingはConfigMapでresources.replicasを指定することで、1個の物理GPUを複数の「レプリカ」として広告できる。たとえば1GPUに4レプリカを設定すれば、物理GPUが2個のノードでは計8個のnvidia.com/gpuリソースが広告される計算になる。これにより、Kubernetesのスケジューラから見ると「GPUが8個ある」ように見え、1つの物理GPUに複数のPodを同居させられるようになる。

ただし注意点もある。同ドキュメントは「複数のtime-sliced GPUへのリクエストは、GPU計算パワーの比例配分を保証しない」とも明記している。time-slicingはあくまで時分割でワークロードを多重化しているだけで、各Podが同時にフルパワーを使えるわけではない。推論のレイテンシが読める形でSLAに関わる場合は、この性質を踏まえた設計が必要になる。

4. エッジ特有の罠: MIGが使えない、Jetsonは統合メモリ

section04

GPU共有の選択肢として、time-slicingのほかに**MIG(Multi-Instance GPU)**というハードウェアレベルの隔離機能もある。MIGを使えば、物理GPUを複数の独立した区画に分割し、メモリもフォルト(障害)も完全に隔離できる。

しかしMIGには大きな制約がある。vClusterの解説によれば、

MIG's only supported on select NVIDIA GPUs, starting from the Ampere generation introduced in 2020.

MIGが使えるのは2020年以降のAmpere世代以降の、データセンター向け高性能GPUに限られる。NVIDIA Jetsonのようなエッジ向けSoC(System on Chip)はこの対象外だ。つまりエッジ環境では、GPUを共有する手段は事実上time-slicingしかない。time-slicingはメモリもフォルトも隔離されないため、1つのPodが暴走すれば同居する他のPodの推論にも影響しうる。この信頼性の差を理解せずにエッジで複数モデル同居を設計すると、本番障害時に原因切り分けができなくなるリスクがある。

さらにJetsonのようなエッジデバイスには、もう1つの構造的な違いがある。データセンターのGPUサーバーでは、CPUのメモリとGPUのVRAM(ビデオメモリ)は物理的に独立しており、PCIe経由でテンソルをコピーする。一方iotdigitaltwinplmの解説によれば、Jetsonは**統合メモリ(Unified Memory)**アーキテクチャを採用しており、CPUとGPUが同じメモリプールを共有する。

Size your inference pod's memory request against the whole board, not against an imaginary separate GPU.

つまり、「GPUに80GBのVRAMがあるから大丈夫」という発想のクラウドGPUサーバー前提のリソース設計は、Jetsonではそのまま通用しない。モデル・アクティベーション・OS・K3s自体が1つのメモリプールを取り合う構造になっているため、ボード全体のメモリに対してPodのリソースをサイジングしなければ、簡単にメモリ不足を引き起こす。

5. K3sでの実装: time-slicing設定とリソース指定の具体例

section05

K3sでエッジAI推論基盤を構築する際、oneuptimeの実践ガイドを参考にすると、以下の設定が最低限必要になる。

  • runtimeClassName: nvidia の指定 — containerdがNVIDIAランタイムをデフォルトで使うよう、テンプレート化された設定が必要
  • nodeSelector でGPUノードを明示指定(例: ai-capable: "true") — CPUノードに誤ってスケジュールされるのを防ぐ
  • メモリのrequest/limitを現実的な値に — 同ガイドの例ではrequest 2〜3Gi、limit 4〜6Giという具体的な範囲が示されている。これをボード全体のメモリに対して余裕を持って設定する

GPUスケジューリングが期待通りに動かないときの典型的な失敗パターンは3つある。

  1. device plugin未導入: PodがPendingのまま。リソース不足エラーが出る
  2. 誤ったruntime設定: Podはスケジュールされるが、CUDAにアクセスできずCPUで実行される。性能が静かに劣化するため、ログだけでは気づきにくい
  3. RuntimeClassの欠如: リソースは割り当てられているのに、GPUデバイスがコンテナにマウントされない

これらはいずれも「Podは動いているように見えるのに、実際には性能が出ていない」という形で現れるため、推論のレイテンシやスループットを監視する仕組みとセットで運用することが欠かせない。GPUノードがどのPodにどれだけ割り当てられているかをダッシュボードで可視化できれば、こうしたサイレントな性能劣化にも早く気づける。KuboのCaptain UIは、こうしたクラスタのリソース割当状況をブラックボックスにせず確認できる管理画面を備えている。

複数モデルを同居させたい場合は、NVIDIA GPU OperatorのConfigMapでtime-slicingのreplicas数を設定し、各推論ワークロードのレイテンシ要件がtime-slicingの「比例配分を保証しない」特性と両立できるかを事前に検証しておくべきだ。SLAが厳しい推論と、バッチ的に処理できる推論を同じGPUに同居させない、といった切り分けも現実的な選択肢になる。

6. まとめ: エッジAI推論でK3sを選ぶなら、GPU共有の限界を前提に設計する

section06

K3sはサーバー2GB・エージェント512MBという最小要件で動く軽量さが魅力で、エッジへのKubernetes展開を現実的にしてきた。しかし、GPUという物理リソースの制約はディストリビューションを問わずKubernetes共通の仕様であり、K3sだからといって回避できるわけではない。

整理すると、エッジAI推論基盤の設計で押さえるべきポイントは次の3つだ。

  • GPUは整数単位・request=limit固定で、fractional(小数)割当は標準では不可能
  • 複数モデルを1GPUに同居させるにはtime-slicingが必要だが、メモリもフォルトも隔離されない。MIGのようなハードウェア隔離はデータセンター向けGPU(Ampere世代以降)限定でエッジでは使えない
  • Jetson等のエッジデバイスは統合メモリアーキテクチャのため、クラウドGPUサーバー前提のリソース設計ではなく、ボード全体に対してサイジングする必要がある

クラウドだけでなく、工場や店舗といったエッジにもKubernetesを展開する動きは広がっている。K3sの軽量性は、そうしたエッジでのAI推論を現実的なコストで実現する土台になる。ただし、GPU共有の限界を理解せずに「クラウドと同じ感覚」で設計すると、本番環境で見えない性能劣化や障害の切り分け困難に直面する。

こうしたエッジ・AI基盤特有の設計判断を、構築のたびにゼロから検証するのは負荷が大きい。KuboはK3sベースのマネージドKubernetesとして、GPUノードの設定やAI-Driven Deploymentにより、こうした構成をYAML地獄に陥らず構築できる環境を提供している。さらに、推論基盤の上でAIエージェントを運用したい場合は、Captain.AIがAIワークロードの実行基盤として連携する。

エッジAI推論基盤の設計を相談したい場合は、お問い合わせから気軽に相談してほしい。

Related articles

← Back to all posts