初めに
最近、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 でも動くだろう」と無視すると、後で依存関係の解決に失敗します(後述)。
実際、今回の唯一のコード改修はこれでした。
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 専用」なコードが目に入ります。
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 への利用ログ送信)も確認したところ、トークンが無ければ自動で無効になる作りでした。
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 で是非お試しください!
