huggingface-zerogpu について
Hugging Face ZeroGPU 上で Gradio SDK Spaces を構築するためのコーディングスキルです。GPU の割り当て、クォータと実行時間、プロセス分離、並行処理の注意点を扱います。
主な機能
- 01ZeroGPU デコレーターと GPU ライフサイクルの指針
- 02実行時間とクォータの調整
- 03プロセス分離とシリアライズのルール
- 04並行処理の安全パターン
- 05CUDA 依存関係とビルド制約
活用シーン
- 01ZeroGPU 上で Gradio デモを実装
- 02GPU の実行時間とサイズを選択
- 03クォータやシリアライズの問題を調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ huggingface/ skills --skill huggingface-zerogpuインストール後、Claude Code で/huggingface-zerogpuを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/concurrency.mdcuda-and-deps.mdhow-quota-works.mdhow-zerogpu-works.md詳細
Hugging Face ZeroGPU
ZeroGPU ハードウェアを使用した Hugging Face Spaces 上の ML デモ向けのルールとパターンです。@spaces.GPU、実行時間とクォータの調整、プロセス分離、CUDA の可用性モデル、並行実行の安全性、CUDA のビルド制約について説明します。
対象範囲
このスキルの対象は、ZeroGPU ハードウェアを使用する Gradio SDK Spaces です。Docker Spaces と Static Spaces は ZeroGPU にスケジュールできず、Streamlit アプリは現在 Docker Spaces として実行されるため、このスキルは Gradio のみに適用されます。一般的な Gradio のコーディング(コンポーネント、レイアウト、イベントリスナー)については、このリポジトリの huggingface-gradio スキルを参照してください。ZeroGPU の正式なドキュメントは https://huggingface.co/docs/hub/spaces-zerogpu にあります。現在のバックエンド GPU、ランタイムのバージョン一覧、ティアのしきい値はいずれも随時変更されるため、これらについては公式ドキュメントを参照してください。
リファレンスファイル
| リファレンス | 読むタイミング |
|---|---|
references/concurrency.md | ZeroGPU コードを書くときは、常に SKILL.md と併せて読んでください。ハンドラーはデフォルトで並列実行されます |
references/how-zerogpu-works.md | コールドスタート、ワーカーの再利用、モジュールスコープでのウォームアップがリクエストに引き継がれない理由、CUDA テンソルを返すとハングする理由を検討するとき |
references/how-quota-works.md | duration の値を選ぶとき、illegal duration と quota exceeded のエラーをデバッグするとき、デフォルトの 60 秒が短いタスクを妨げる理由を説明するとき |
references/cuda-and-deps.md | CUDA 依存パッケージ(例:flash-attn)をインストールするとき、torch のサイドカーパッケージのバージョンを固定するとき、ホイールのファイル名タグを確認するとき |
ハードウェア
ZeroGPU では、バックエンドカードを分割した 2 種類の GPU サイズを利用できます。
size | バックエンド GPU の分割量 | クォータ消費量 |
|---|---|---|
large (デフォルト) | 半分 | 1x |
xlarge | 全体 | 2x |
デフォルトの large では物理 GPU の半分を使用するため、メモリ帯域幅と演算性能はフルカードの仕様を大きく下回ります。追加のメモリや演算性能が本当に必要な場合に限り、xlarge を使用してください。
バックエンド GPU は予告なく変更されます。 ZeroGPU はこれまでに GPU 世代を何度か移行しているため、A100 や H200 に言及した古い記事は情報が古くなっています。現在のバックエンド GPU とサイズごとの正確な VRAM を確認するには、ワークロードの規模を決める前に必ず ZeroGPU のドキュメントを確認してください。
基本パターン
import spaces
import torch
from transformers import pipeline
pipe = pipeline("text-generation", model="...", device="cuda")
@spaces.GPU
def generate(prompt: str) -> str:
return pipe(prompt, max_new_tokens=100)[0]["generated_text"]
主なルール:
- モデルはモジュールスコープでインスタンス化し、
.to("cuda")をすぐに呼び出してください。 ZeroGPU が実際のデバイスマッピングを透過的に処理します(下記の CUDA 可用性モデルを参照)。 - GPU 関数には
@spaces.GPUを付けてください。 ZeroGPU 以外ではデコレーターは何もしないため、すべての環境でそのまま使用できます。 - 現実的な最悪ケースのワークロードに合わせて
durationを設定してください(デフォルトは 60 秒です)。プラットフォームは実際の実行時間ではなく、requested durationとユーザーのremaining quotaを事前に照合します。そのため、10 秒のタスクでもデフォルトの 60 秒を指定していると、ユーザーの残りクォータが 60 秒を下回った時点でquota exceededエラーになります。宣言するdurationを短くすると、ノードレベルのキューでも優先度が上がります。下記の「実行時間とクォータ」を参照してください。 torch.compileはサポートされていません。 代わりに PyTorch の事前コンパイル(AoTI)(torch 2.8+)を使用してください。size="xlarge"の使用は控えてください。 バックエンド GPU 全体を割り当てますが、クォータ消費量は 2 倍になり、キューでの待ち時間も長くなる傾向があります。
@spaces.GPU(duration=120)
def generate_image(prompt: str):
return pipe(prompt).images[0]
CUDA の利用可能性モデル
実際の GPU アクセスが可能なのは、@spaces.GPU でデコレートされた関数の内部のみです。それ以外では、GPU はプロセスに接続されていません。
ただし、import spaces は torch をモンキーパッチするため、次のようになります。
torch.cuda.is_available()はグローバルにTrueを返します。- モジュールスコープでの
.to("cuda")/device="cuda"の呼び出しは、エラーなしで成功します。
これは意図した動作です。モジュールスコープでの model.to("cuda") 呼び出しは、テンソルを ZeroGPU バックエンドに登録します。バックエンドは起動時の「パック」手順でテンソルをディスクのオフロードディレクトリに書き込み、対応する RAM を解放します。@spaces.GPU の呼び出しが実行されると、フォークされた GPU ワーカープロセスが、ピン留めメモリのパイプラインを通じてディスクから VRAM に重みをストリームします。同じ GPU スロットでリクエストをまたいで再利用されるウォーム状態のワーカーは、重みを GPU 上に保持し、ディスクから VRAM への転送をスキップします。ユーザー向けのルールは、モジュールスコープで device="cuda" を指定すれば動作する、ということです。ライフサイクル全体については、references/how-zerogpu-works.md を参照してください。
| 操作 | 場所 | 理由 |
|---|---|---|
model.to("cuda") / pipe(..., device="cuda") | モジュールスコープ | ZeroGPU がテンソルを登録し、デバイス間の移行を管理します |
| 実際の CUDA 計算(推論など) | @spaces.GPU の内部 | 実際の GPU が接続されるのは、デコレートされた呼び出しの間のみです |
torch.cuda.is_available() による分岐 | 依存しないでください | モンキーパッチにより常に True を返します |
モジュールスコープで推論や CUDA カーネルを実行しないでください。実際の GPU は接続されていないため、処理は CPU で実行されるか、失敗します。
デバイス選択のイディオムは引き続き使用できます
ZeroGPU でも、標準的なイディオムは引き続き正しく機能します。
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = AutoModel.from_pretrained("...").to(device)
- ZeroGPU — モンキーパッチにより
is_available()はTrueとなるため、モデルは自動デバイス移行の対象として登録されます。 - 専用 GPU Spaces /ローカル GPU —
is_available()は実際にTrueです。 - CPU Spaces /ローカル CPU —
"cpu"に解決されます。
device="cuda" をハードコードしないでください。CPU のみの環境では動作しなくなります。
デフォルトでは eager loading が適切です
モデルは初回リクエスト時に遅延ロードするのではなく、モジュールスコープでロードしてください。Space のプロセスはユーザーがアクセスする前に起動するため、コールドスタートのコストは一度だけ発生します。遅延ロード(global model; if model is None; ...、@lru_cache ラッパー、初回呼び出し時にインスタンスを生成するファクトリー関数)は、そのコストを初回ユーザーに転嫁するだけです。
ローカル開発:spaces をインストールするだけ
ローカル実行用に import spaces を try/except で囲み、spaces.GPU を何もしないフォールバックとして再定義しないでください。ZeroGPU 以外では、spaces パッケージはすでに実際に何もしません。
- 重い動作(CUDA のモンキーパッチ、クライアントの初期化、起動時フック)は、ZeroGPU でのみ設定される
SPACES_ZERO_GPU環境変数がある場合に限り有効になります。 - ZeroGPU 以外では、
@spaces.GPUはデコレート前の関数をそのまま返します。 - トップレベルの
import spacesで実行されるのは、軽量なインポートだけです。
Gradio SDK のベースイメージは、すべてのハードウェア階層に spaces をインストールします。そのため、Space を専用 GPU(T4、L4、A10G など)や CPU basic に複製した後も、コードの変更は不要です。import spaces は引き続き成功し、@spaces.GPU は透過的なパススルーとして動作します。
アンチパターン
try:
import spaces
except ImportError:
class spaces: # type: ignore
@staticmethod
def GPU(func=None, **kwargs):
return func if func else (lambda f: f)
問題点:
- フォールバックでは、引数なしのデコレーター、
duration=...、size=...、ジェネレーター、aoti_*ヘルパーなど、@spaces.GPUのあらゆる呼び出し形式を再現する必要があり、spacesAPI の拡張に伴って実装がずれていきます。 - Space ではデプロイ時に
spacesが必要なのに、requirements.txtからspacesが見えなくなります。 - 実際には問題がありません。実際のパッケージはローカルですでに何もしないためです。
代わりにこちらを行ってください
依存関係に spaces を追加し、無条件にインポートします。
import spaces
@spaces.GPU
def generate(prompt: str) -> str:
...
実行時間とクォータ
@spaces.GPU(duration=N) を宣言すると、次の3つのことが起こります。
- ティア上限のチェック — 各ビジターティアには、呼び出しごとの
duration上限があります。上限を超えるdurationを宣言すると、残りのクォータに関係なく、直ちにZeroGPU illegal durationで失敗します。(ティアの数値は時間とともに変わります。ZeroGPU のドキュメントを参照してください。) - クォータの事前チェック — プラットフォームは、
requested durationとユーザーのremaining quotaを比較します。remaining < requestedの場合、実際の処理が収まる場合でも、呼び出しはZeroGPU quota exceededで失敗します。エラーメッセージには、たとえば"60s requested vs. 30s left"のように具体的な数値が表示されます。デフォルトの60秒が設定された10秒のタスクは、残りのクォータが60秒を下回ると、ユーザーの利用を妨げます。 - キューの優先順位 — キューはノード単位です(同じノード上のすべての Space からのリクエストが GPU スロットを取り合います)。宣言された
durationが短いほど、優先順位が高くなります。
この3つはいずれも、短いタスクも含め、現実的な範囲で最小の duration を宣言することを推奨しています。10秒のタスクに @spaces.GPU(duration=15) を明示的に設定すると、早すぎる quota exceeded による拒否を避けられ、キューでの優先順位も高くなります。
xlargeではリクエストが2倍になります。size="xlarge"の場合、ティア上限のチェックとクォータの事前チェックの両方で、requested = N * 2となります。そのため、@spaces.GPU(duration=60, size="xlarge")は内部的に120秒のリクエストとして扱われます。
ワークロードに応じて実行時間を動的に設定する
実行時間が入力によって変わるワークロードでは、リクエストごとに実行時間を推定する呼び出し可能オブジェクトを渡します。固定の大きな duration を設定すると、低いティアのユーザーは(ティア上限が固定値を下回る場合)利用できなくなり、軽い入力に対しても不必要にクォータを確保してしまいます。
def estimate_duration(prompt, steps):
return int(steps * 3.5)
@spaces.GPU(duration=estimate_duration)
def generate(prompt, steps):
return pipe(prompt, num_inference_steps=steps).images[0]
illegal duration と quota exceeded の違い、1日あたりの実行回数の制限、24時間のクォータ期間、および従量課金について詳しくは、references/how-quota-works.md を参照してください。
プロセスの分離と Pickle
@spaces.GPU でデコレートされた関数は、ZeroGPU スケジューラーが管理する別プロセスで実行されます。引数と戻り値は、pickle シリアライズを介してプロセス境界を越えます。
そのため、次の点に注意してください。
- pickle 化可能なオブジェクトのみを渡したり返したりできます。開いたファイルハンドル、データベース接続、ロック、ラムダ、および pickle 化できない状態をキャプチャするクロージャーを使用すると、
PicklingErrorが発生します。 - CUDA テンソルを直接返さないでください。 メインプロセスで CUDA テンソルをデシリアライズすると
torch.cuda._lazy_init()が呼び出されますが、ZeroGPU はこれをブロックします。先に CPU に変換してください。tensor.cpu()またはtensor.cpu().numpy()を返します。 - CPU テンソル、numpy 配列、PIL Images、および通常の Python オブジェクトは問題なく使用できます。
- 大きなオブジェクトはシリアライズのオーバーヘッドを伴います。複雑なオブジェクトグラフよりも、テンソル、配列、ファイルパス、base64 文字列など、軽量な戻り値を優先してください。
境界を越える gr.State のセマンティクス
ハンドラーは別プロセスで実行されるため、gr.State の値はyield のたびに pickle 化されます。参照によって共有されるわけではありません。
- ジェネレーターが受け取るのは状態のコピーです(呼び出し元とは
id()が異なります)。 - ジェネレーター内でのインプレース変更は、変更後の状態が明示的に yield されるまで、他のハンドラーからは見えません。
gr.Stateのスロットにgr.update()を yield しても、更新はスキップされます。他のハンドラーからは、yield 前の値が引き続き見えます。- 状態オブジェクトを返す各 yield で、pickle による新しいコピーが作成されます。
実践上の指針:
- ZeroGPU では、
gr.Stateが参照セマンティクスを持つと想定しないでください。ジェネレーター内で状態を変更し、別のハンドラーからその変更が見えると想定するコードでは、古いデータがそのまま使われることがあります。 gr.Stateの値を含むすべての yield で、pickle による完全な往復処理が発生します。大きな状態(モデルのセッション、フレームバッファー)の場合は、yield の回数を最小限にし、できれば最後に一度だけ返してください。中間の yield では、状態のスロットにgr.update()を使用します。- 状態内の CUDA テンソルは、yield する前に CPU に移動する必要があります。上記と同じ
torch.cuda._lazy_init()の問題が発生します。
並行処理
ZeroGPU では、ハンドラーはデフォルトで並行実行されます。これはオプトインではありません。単一ユーザーでのテストでは問題なく動作したコードでも、本番環境でデータを気付かないうちに破損させたり、漏えいさせたりする可能性があります。
ルールは 3 つです。例を含む詳しい説明については、references/concurrency.md をご覧ください。
- 変更可能なグローバル状態を使わないでください。 並行リクエストによって互いのデータが上書きされます。
- 出力に固定ファイルパスを使わないでください。 並行リクエストが同じファイルを上書きします。一意のパスには
tempfileを使ってください。 - 読み取り専用のグローバル変数は安全です。 起動時に一度だけ読み込まれ、リクエスト中に読み取りのみが行われるモデルオブジェクト、トークナイザー、設定は安全であり、推奨されます。
呼び出しの粒度
@spaces.GPU 関数を呼び出すたびに、プロセス境界をまたぐ pickle の往復、ワーカーのウォームアップ、CUDA の再アタッチ、ノードレベルのキューでの新たな待機など、無視できないコストがかかります。高頻度のループ内からデコレートされた関数を呼び出すと、これらのコストが増大し、新たな障害要因も加わります。後続の反復で GPU スロットを取得できず、ジョブ全体が途中で停止する可能性があります。
反復ごとのワーカーではなく、ループを管理する外側の関数にデコレーターを適用してください。
# Avoid — N GPU entries for N frames
def process_video(frames):
return [process_frame(f) for f in frames]
@spaces.GPU(duration=...)
def process_frame(frame):
...
# Prefer — one GPU entry for the whole video
@spaces.GPU(duration=...)
def process_video(frames):
return [process_frame(f) for f in frames]
def process_frame(frame):
...
ループ内で負荷の高い CPU 処理と GPU 処理が混在する場合、ループ全体をラップすると、その CPU 処理時間もユーザーのクォータに計上されます。このコストが無視できない場合は、CPU の前処理と後処理をデコレーターの外に置けるよう GPU 処理をバッチ化する方法もありますが、これは状況に応じた最適化であり、デフォルトではありません。
CUDA ビルドの制約
HF Spaces は CPU のみの環境で Docker イメージをビルドします。ZeroGPU では、ベースイメージが python:3.13 であるため、ビルドフェーズに nvcc はありません(専用 GPU の Spaces では nvidia/cuda:*-devel-* を使用し、ビルド時に nvcc を利用できます)。そのため、配布形式が sdist のみの CUDA 依存パッケージ(例:flash-attn 単体)は、ZeroGPU で requirements.txt からインストールできません。使用できるのはビルド済みの wheel のみです。
ZeroGPU のランタイムでは、2025-07 以降、CUDA devel イメージから /cuda-image にマウントされた nvcc を利用できます(AoTI のサポートのために当初追加されました)。これにより、@spaces.GPU の呼び出し内で torch.export / AoTI のワークフローを実行できます。
要点:CUDA 依存パッケージはすべて、ビルド済みの wheel からインストールしてください。PyPI に wheel がない場合は、外部でビルドして(例:HF Hub でホストして)、その URL を固定してください。flash-attn については、上流のリリースページで、ほとんどの Python × CUDA × torch の組み合わせを網羅する、かなり充実した wheel の一覧が公開されています。
wheel タグ(cxx11 ABI、cu12torch2.X、cp3XX)の読み方、torch ファミリーのサイドカーのバージョンずれ、kernels-community のフォールバックについては、references/cuda-and-deps.md をご覧ください。
サンプルのキャッシュ
gr.Examples の動作は環境に依存します。特に ZeroGPU では、次のようになります。
cache_examplesのデフォルトはTrueです(Spaces がGRADIO_CACHE_EXAMPLES=trueを設定します)。cache_modeのデフォルトは"lazy"です(Spaces は ZeroGPU でのみGRADIO_CACHE_MODE=lazyを設定します)。
ZeroGPU のデフォルトが lazy なのは、eager キャッシュではアプリの起動時にすべてのサンプルを事前実行する一方、ZeroGPU では起動時にGPU がアタッチされておらず、リクエスト処理中にのみ利用できるためです。GPU を使うサンプルを eager キャッシュすると、そこで失敗します。
cache_examples=True の場合、run_on_click / run_examples_on_click パラメーターは無視されますが、そのことは通知されません。アプリがクリック時のみサンプルを読み込む動作に依存している場合は、その動作を維持するため、明示的に cache_examples=False を設定してください。
ZeroGPU のサンプルキャッシュの動作をローカルで再現するには、次のようにします。
GRADIO_CACHE_EXAMPLES=true GRADIO_CACHE_MODE=lazy python app.py
依存関係の管理
README フロントマターの python_version の固定
ZeroGPU では、python_version の固定は実質的に必須です。現在、ランタイムのデフォルトは Python 3.10 のため、Python 3.11 以降を使うローカル環境では、明示的に固定しないと Space へのインストールに失敗します。ZeroGPU がサポートするバージョン(デフォルトとしては 3.12 が妥当です)を指定してください。サポート対象の正式な一覧はZeroGPU のドキュメントにあります。一覧全体をハードコードせず、ドキュメントを参照してください。
# README.md frontmatter
python_version: "3.12"
"3.12" と "3.12.12" のどちらの形式も使用できます。
requirements.txt で spaces を固定しない
Space プラットフォームでは、独自の spaces バージョンが固定されています。requirements.txt で競合するバージョンを固定すると、ビルド時に pip の依存関係の解決に失敗します。
ルール:
requirements.txtにspacesを含めないでください。
実現方法は、使用するツールによって異なります。
- 手書きの
requirements.txt:spacesを省略してください。 - uv(
pyproject.tomlで管理):推移的な制約(特にspacesがバージョンを固定するpsutil)を uv がまとめて解決できるよう、pyproject.tomlにspacesを宣言し、エクスポート時に除外してください。uv export --no-hashes --no-dev --no-emit-package spaces -o requirements.txtpyproject.tomlにspacesがないと、uv は推移的な制約を確認できず、ビルド時に互換性のないバージョンを解決する可能性があります。 - pip-tools(
pip-compile)/Poetry:同等の除外機能を使用してください。
wheel タグに合わせて torch を固定する
CUDA に依存する wheel を直接 URL からインストールする場合、wheel のファイル名には、ビルドに使用された torch のメジャー・マイナーバージョンが記載されています(例:cu12torch2.8)。一致するように requirements.txt で torch==X.Y.Z を固定してください。そうしないと、pip が別のバージョンの torch を解決し、Space で初回のインポート時に失敗する可能性があります。詳細と kernels-community の代替方法については、references/cuda-and-deps.md を参照してください。