1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

HuggingFace Space のデモをローカルに移行してみた。コード改修はたった1行だった

1
Last updated at Posted at 2026-07-05

image.png

初めに

最近、HuggingFace Space で公開されているデモを触る機会がかなり増えました。ブラウザで試せるのは便利なのですが、ZeroGPU の割り当て待ちや GPU 時間制限(数十秒〜数分)があり、本格的に検証しようとすると正直しんどいです。

そこで今回は、NVIDIA が公開している物体検出デモ LocateAnything(自然言語で画像・動画内のオブジェクトを検出する Vision-Language モデル)を、手元の GPU マシンに移行してみました。

結論から言うと、コードの改修は requirements.txt への1行追加だけで動きました。ただし、そこに辿り着くまでに「Space 固有の仕組み」をいくつか理解する必要があったので、実際の手順とハマりどころを整理して共有します。

同じように「Space のデモをローカルで動かしたい」という方の参考になれば幸いです。

1. 検証環境

項目 内容
GPU NVIDIA GeForce RTX 5080 Laptop(16GB VRAM、Blackwell 世代)
OS Windows 11 + WSL2(Ubuntu)
ドライバ CUDA 13.2 対応版
パッケージ管理 uv 0.11.3
対象 Space nvidia/LocateAnything(Gradio + 3B モデル)

ポイントは RTX 5080 が Blackwell(sm_120)世代であることです。後述しますが、ここが PyTorch のホイール選びに効いてきます。

2. まず Space を clone する

HuggingFace Space は実体がただの git リポジトリなので、そのまま clone できます。

git clone https://huggingface.co/spaces/nvidia/LocateAnything
cd LocateAnything

中身はこれだけです。

app.py            # メインアプリケーション(Gradio Server)
index.html        # カスタムフロントエンド
requirements.txt  # 依存関係
assets/           # サンプル画像・フォント
README.md         # ← 実はこれが「設定ファイル」

3. README.md は飾りではなく「設定ファイル」

ローカル移行で最初に読むべきは、コードではなく README.md の frontmatter です。Space の実行環境はここで定義されています。

---
title: LocateAnything
sdk: gradio
sdk_version: 6.14.0
python_version: "3.10.13"
app_file: app.py
---

ここから読み取れる重要な情報が2つあります。

  • sdk_version: 6.14.0 — Space 側では Gradio がプラットフォームから供給されるため、requirements.txt に gradio が書かれていません。ローカルでは自分でインストールする必要があります。
  • python_version: "3.10.13" — Python バージョンの指定です。「3.13 でも動くだろう」と無視すると、後で依存関係の解決に失敗します(後述)。

実際、今回の唯一のコード改修はこれでした。

requirements.txt
 huggingface_hub
 spaces
+gradio==6.14.0

バージョンは README の sdk_version に合わせます。このプロジェクトは from gradio import Server@app.api といった Gradio 6 系の API を使っているので、適当に最新版を入れると動かない可能性があります。

4. Python バージョンは README に従う(uv で仮想環境を作る)

手元の Python は 3.13 でしたが、requirements.txt を見ると numpy==1.25.0 が固定されています。numpy 1.25 は Python 3.9〜3.11 までしか対応していないため、3.13 ではそもそもインストールできません。

こういうとき uv だと、Python 本体ごとバージョン指定して仮想環境を作れるので楽です。

# README の python_version に合わせて 3.10 を指定
# (未インストールなら uv が自動でダウンロードしてくれます)
uv venv --python 3.10

# 依存関係のインストール
uv pip install -r requirements.txt

pyenv でビルドしたり deadsnakes PPA を探したりする必要はありません。個人的には、この「Python 本体の調達まで含めて1コマンド」が uv に移行して一番ありがたかった部分です。

5. Blackwell 世代 GPU と PyTorch ホイールの確認

RTX 5080 は Compute Capability sm_120(12.0) です。古い CUDA ビルドの PyTorch だと「sm_120 is not compatible」というエラーで動きません。

requirements.txt では torch==2.8.0 が固定されていました。torch 2.8.0 の PyPI 標準ホイールは cu128(CUDA 12.8)ビルドなので、実は追加作業なしで Blackwell に対応しています。ただし思い込みは禁物なので、必ず実際に演算を流して確認します。

.venv/bin/python -c "
import torch
print(torch.__version__, 'cuda:', torch.version.cuda)
print(torch.cuda.get_device_name(0))
print('capability:', torch.cuda.get_device_capability(0))
x = torch.randn(8, device='cuda')
print('compute ok:', (x * 2).sum().item())
"

実行結果です。

2.8.0+cu128 cuda: 12.8
NVIDIA GeForce RTX 5080 Laptop GPU
capability: (12, 0)
compute ok: 9.896278381347656

torch.cuda.is_available() だけで判断せず、実際に randn → 演算まで流すのがポイントです。カーネルが対応していない場合、演算を実行して初めてエラーになるケースがあるためです。

もし手元の環境で古い CUDA ビルドが入ってしまった場合は、明示的に cu128 の index を指定して入れ直せば OK です。

uv pip install torch==2.8.0 torchvision==0.23.0 --index-url https://download.pytorch.org/whl/cu128

なお、ドライバ側の CUDA バージョン(今回 13.2)とホイールの CUDA バージョン(12.8)は一致していなくても、ドライバが新しい分には後方互換で動きます。

6. @spaces.GPU デコレータは消さなくていい

app.py を開くと、いかにも「Space 専用」なコードが目に入ります。

app.py
import spaces  # MUST BE THE ABSOLUTE FIRST IMPORT FOR ZEROGPU EMULATION

# ...(中略)...

@spaces.GPU(duration=120, size="xlarge")
def run_image_gpu_api(
    image_path: str, category: str, model_mode: str, temp: float, top_p: float, top_k: int,
    short_size: int | None, question_override: str | None
):

最初は「これを全部削除しないと動かないのでは」と思ったのですが、削除不要でした。spaces ライブラリは Space の外(SPACE_ID 環境変数が無い環境)で動いていることを検知すると、@spaces.GPU単なるパススルー(no-op)として扱うように作られています。

つまり、ローカルではデコレータが「無かったこと」になるだけで、副作用はありません。ここを理解しておくと、diff をゼロに保ったまま Space 版との互換性を維持できます。upstream の更新を git pull で取り込み続けたい場合、この「改修しない」判断はかなり効いてきます。

同様に、Space 固有のロギング機構(HuggingFace Dataset への利用ログ送信)も確認したところ、トークンが無ければ自動で無効になる作りでした。

app.py
if LOG_DATASET_REPO and LOG_HF_TOKEN:
    _log_scheduler = CommitScheduler(...)
else:
    print("[LOG] Dataset logging disabled (LOG_HF_TOKEN not set)")

ローカルでは LOG_HF_TOKEN を設定していないので、何もせずログ送信は止まります。「Space 専用に見えるコード」は、消す前に「ローカルで実行されたら何が起きるか」を追うのがおすすめです。よくできたデモアプリは、大抵ローカル実行も考慮して書かれています。

7. ハマりどころ:assets が git-lfs のポインタファイルだった

一番地味にハマったのがここです。起動してサンプル画像で推論したら、こんなエラーが出ました。

PIL.UnidentifiedImageError: cannot identify image file
'/u01/workspace/LocateAnything/assets/book.jpg'

file コマンドで正体を確認すると、JPEG のはずが ASCII テキストです。

$ file assets/book.jpg
assets/book.jpg: ASCII text

$ head -c 130 assets/book.jpg
version https://git-lfs.github.com/spec/v1
oid sha256:fc0a3d0fde90c19697ea7901d92213ecd6de3dce4e2024af8ce579dd4cee99f3
size 47930

HuggingFace のリポジトリは画像・フォントなどのバイナリを git-lfs で管理しています。clone したマシンに git-lfs が入っていないと、実体ではなくポインタファイルが落ちてきます。しかもエラーになるのは「そのファイルを開いた瞬間」なので、起動時には気づけません。

対処は2通りあります。

# 正攻法:git-lfs を入れて実体を取得
apt install git-lfs
git lfs pull

# git-lfs を入れたくない場合:resolve URL から直接ダウンロード
for f in book.jpg ocr.jpg person.jpg sweet.jpg LXGWWenKai-Bold.ttf; do
  curl -sL "https://huggingface.co/spaces/nvidia/LocateAnything/resolve/main/assets/$f" -o "assets/$f"
done

https://huggingface.co/<repo>/resolve/main/<path> という URL 形式を覚えておくと、LFS ファイルを個別に実体で取得できて便利です。

8. 起動と動作確認

準備ができたので起動します。モデル(nvidia/LocateAnything-3B、約7.2GB)は初回推論時に遅延ロードされる作りなので、起動自体は10秒程度で完了します。

.venv/bin/python app.py
# → http://localhost:7860

ブラウザで確認してもよいのですが、せっかくなので API 経由でも検証します。Gradio 6 系は POST /gradio_api/call/<api名>GET のイベントストリームという2段構えの REST API を持っています。

# 推論リクエストを投げて event_id を取得
EVENT_ID=$(curl -s -X POST http://localhost:7860/gradio_api/call/run_inference \
  -H "Content-Type: application/json" \
  -d '{"data": ["Image", "assets/book.jpg", null, "Detection", "book",
       "hybrid", 0.7, 0.9, 20, null, null, 4]}' | jq -r .event_id)

# 結果をストリームで受け取る
curl -s -N "http://localhost:7860/gradio_api/call/run_inference/$EVENT_ID"

結果は "success": true で、サンプル画像から book が22件、バウンディングボックス付きで検出されました。OCR タスク(slow モード=標準の自己回帰デコード)も同様に成功です。ZeroGPU の時間制限を気にせず、何度でも回せます。

9. VRAM 実測

3B パラメータ・bfloat16 のモデルなので、重みだけで約6〜7GB を見込んでいました。実測はこうです。

$ nvidia-smi --query-gpu=memory.used,memory.total --format=csv
memory.used [MiB], memory.total [MiB]
10094 MiB, 16303 MiB

このマシンでは別プロセス(llama-server)が同居している状態でこの数字なので、16GB クラスの GPU なら 3B の VLM は余裕を持って同居可能という感触です。KV キャッシュは max_new_tokens=4096 の設定でもこの範囲に収まりました。

10. 移行チェックリスト

今回の経験を、他の Space にも使える形で整理しておきます。

確認項目 見る場所 今回のケース
SDK とバージョン README.md の sdk / sdk_version gradio 6.14.0 を requirements に追加(唯一の改修)
Python バージョン README.md の python_version 3.10 指定。numpy 1.25 が 3.13 非対応のため必須
@spaces.GPU の扱い app.py ローカルでは no-op。削除不要
Space 固有のロギング等 app.py トークン未設定で自動無効。削除不要
バイナリファイル file assets/* git-lfs ポインタだった → 実体を取得
GPU 世代とホイール torch.cuda.get_device_capability() sm_120 → torch 2.8.0 標準の cu128 で対応済み
モデルの取得可否 HF の model ページ public。gated モデルなら HF_TOKEN が必要
VRAM モデルサイズ × dtype + α 3B bf16 → 実測 10GB 弱(同居プロセス込み)

まとめ

「Space のデモをローカルに移行する」と聞くと大掛かりな改修を想像しますが、実際にやってみると、

  • コード改修は requirements.txt に gradio==6.14.0 を足した1行だけ
  • @spaces.GPU などの Space 固有コードは触らない方が正解(no-op になる設計)
  • 本当のハマりどころはコードではなく、README frontmatter・Python バージョン・git-lfs という「リポジトリの周辺」

でした。個人的には、「改修を最小に保つ=upstream への追従性を保つ」という観点で、消したくなるコードをあえて消さない判断が一番の学びでした。

手元に GPU がある方は、お気に入りの Space で是非お試しください!

参考

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?