Hub Python Library documentation
サンドボックス
サンドボックス
サンドボックスは実験的な機能です。API、デフォルト設定、および動作は、事前の通知なしに変更される場合があります。共有サンドボックスは、同じ信頼境界内のワークロード向けに設計されており、その分離はすべてのサンドボックス間攻撃からの保護を保証するものではありません。相互に信頼し合わないワークロードには専用サンドボックスを使用し、サンドボックス内のワークロードに対して、長期間有効な資格情報や広範なスコープの資格情報を提供しないようにしてください。
サンドボックスは、数秒で起動できる隔離されたクラウドマシンです。ライブストリーミングされた出力付きでコマンドを実行したり、ファイルを出入力したりできます。すべてPythonまたはCLIから操作可能です。サンドボックスはJobsの上に構築されています。内部では、サンドボックスはHTTP経由でコマンド実行とファイル転送を公開する小さなサーバーを実行しているJobに過ぎません。
自分のマシン以外の場所でコードを実行する必要がある場合、これらは適しています。
- 信頼できないコードまたはAI生成コードの実行 — エージェントにファイルシステムへのアクセス権を与えずに、任意のコードを実行させます。この用途には専用のサンドボックスを使用してください。これは独立したVMです。
- 再現可能なビルドと実験 — クリーンで明確に定義されたイメージ上で、CPUまたはGPUで実行します。
- 作業の分散 — 数百の並列環境(RLロールアウト、評価、バッチツール実行)を低コストで起動します。
/bin/sh を備えた任意の Docker イメージで動作します。Python、pip、エージェントの事前インストールは不要です(起動時に小さな静的サーバーバイナリが注入されます)。
この仕組みの内部構造、すなわちジョブ内サーバー、ステートレスな認証、あるいは単一のJobがどのように多数の分離されたサンドボックスをホストしているのかについて詳しく知りたい場合は、Sandboxesの概念ガイドを参照してください。
2種類のサンドボックス
サンドボックスを取得する方法は2つあります。どちらの方法でも同じSandboxオブジェクト(同じrun、files、connect、kill)が返されます。違いは、基盤となるマシンの割り当て方法だけです。
| Sandbox.create() — 専用 | SandboxPool — 共有 / プール | |
|---|---|---|
| マッピング | 1つのJob = 1つのサンドボックス(VM全体) | 1つのJob = 多数のサンドボックス(1つのVM、パッキング済み) |
| 分離 | 完全なVM | uid + Landlock(同一ユーザー間の信頼) |
| コールドスタート | サンドボックスあたり約6秒 | 最初のホストで約6秒、その後1ラウンドトリップごとに約1秒 |
| コスト | サンドボックスごとに1つのVM | ホストごとに1つのVMを割り当て、多数のサンドボックスに分散して使用します |
| GPU | ✅ | ❌ (CPU のみ) |
| 最適 | 単一のサンドボックス、GPUワークロード、信頼できないコード | 多数の低コストなCPUサンドボックス(RLロールアウト、ファンアウト) |
目安: GPUが必要、または相互に信頼できないコードを実行する必要がある場合は、専用インスタンスを使用します。安価なCPUサンドボックスを数百個必要とする場合は、プールを使用します。
クイックスタート
>>> from huggingface_hub import Sandbox
>>> with Sandbox.create() as sbx: # ready in ~6s
... result = sbx.run("python -c 'print(40 + 2)'") # ~100ms per command
... print(result.stdout)
42任意のイメージとハードウェアの構成を選択してください:
>>> sbx = Sandbox.create(image="alpine:3.20")
>>> sbx = Sandbox.create(image="pytorch/pytorch:2.6.0-cuda12.4-cudnn9-devel", flavor="a10g-small")コマンドの実行
Sandbox.run() はコマンドを実行し、完了を待機します。シェル文字列または argv リストを渡します:
>>> sbx.run("pip install -q numpy") # string → runs through /bin/sh -c
>>> sbx.run(["python", "-c", "import numpy; print(numpy.__version__)"]) # list → exec'd directly
# Live output streaming, plus env, cwd, timeout, stdin
>>> sbx.run("make -j4", cwd="/app", env={"CC": "gcc"}, timeout=600, on_stdout=print, on_stderr=print)デフォルトでは、モードは型から推測されます(文字列は/bin/sh -cで実行され、リストはargvとして直接execされます)。明示的に指定するにはshell=を渡します。これは、["echo hi"]のような1要素のリストが、"echo hi"という単一のプログラム名としてexecされてしまうという典型的な落とし穴を避けるのに便利です。
>>> sbx.run("echo $HOME && ls | wc -l", shell=True) # force the shell (pipes, globs, $VARS)
>>> sbx.run(["git", "commit", "-m", msg], shell=False) # force argv (no quoting surprises)shell=True の場合は文字列を、shell=False の場合はリストを指定する必要があります。型が異なると ValueError が発生します。
ゼロ以外の終了コードで終了するコマンドは、SandboxCommandError(stdout、stderr、exit_code が付随)を発生させます。check=False を渡すと、例外を発生させる代わりに SandboxCommandResult が返されます。
>>> result = sbx.run("test -f /tmp/missing", check=False)
>>> result.exit_code
1バックグラウンドプロセス
長時間実行されるプロセス(サーバー、ウォッチャー、トレーニング実行)を待たずに開始するには、background=True を渡します。run は SandboxCommandResult ではなく、SandboxProcess を即座に返します:
>>> proc = sbx.run("python -m http.server 8000", background=True)
>>> proc
SandboxProcess(pid=1234, cmd='python -m http.server 8000', tag=None, started_at_ms=1700000000000, running=True, exit_code=None)サンドボックスのプロセスをリスト表示し、使用が完了したら1つを停止します。完了したプロセスは、サンドボックスが削除されるまでリストに表示され続けます(running=Falseおよびexit_codeが設定された状態)。これにより、プロセスがまだ実行中か、それとも既に終了したかを判断できます。
>>> sbx.processes()
[SandboxProcess(pid=1234, cmd='python -m http.server 8000', tag=None, started_at_ms=1700000000000, running=True, exit_code=None)]
>>> proc.kill()ストリーミング/待機のみオプション(timeout、stdin、on_stdout、on_stderr、check)はバックグラウンドモードでは適用されません。env、cwd、shellのみが反映されます。
ファイル
>>> sbx.files.write("/app/script.py", "print('hi')") # str | bytes | file-like
>>> sbx.files.read_text("/app/script.py")
"print('hi')"
>>> sbx.files.upload("local_data.csv", "/data/data.csv") # local -> sandbox
>>> sbx.files.download("/data/results.bin", "results.bin") # sandbox -> local
>>> sbx.files.list("/data")
[FileEntry(name='data.csv', path='/data/data.csv', type='file', size=5324, ...)]その他のヘルパー: stat、exists、mkdir、delete。
サンドボックス内のサーバーに到達する
サンドボックス内でサーバーを(バックグラウンドで)起動し、Sandbox.proxy_url_for() を使用して外部からアクセスします。リクエストはジョブ内のサンドボックスサーバーによって内部サーバーへ転送されるため、追加の公開ポートを公開する必要はありません。プレーンな HTTP、Server-Sent Events、WebSocket で動作します。認証には、この URL を Sandbox.proxy_headers と組み合わせて使用します(WebSocket/HTTP クライアントからこれらを送信する必要があります):
>>> import httpx2
>>> with Sandbox.create() as sbx:
... sbx.files.write("app.py", "...") # a server exposing e.g. /hello and /ws
... sbx.run("uvicorn app:app --host 127.0.0.1 --port 8000", background=True)
... # plain HTTP
... r = httpx2.get(sbx.proxy_url_for(8000, "/hello"), headers=sbx.proxy_headers)
... # WebSocket: ask for a wss:// URL
... ws_url = sbx.proxy_url_for(8000, "/ws", scheme="wss://")proxy_headersを使用する場合は、HTTPクライアントでのリダイレクトを無効にしてください(httpxの場合はfollow_redirects=False、requestsの場合はallow_redirects=False)。アプリケーションが別のオリジンへリダイレクトされる可能性があり、これらのカスタム認証ヘッダーはそこに転送されてはなりません。
Pooled sandboxesには、サンドボックスごとにcapabilityトークンを返すサーバーが必要です。トークンの欠落はエラーとみなされます。ホストの管理トークンを使用するのではなく、アップグレードしてホストをリサイクルしてください。
内部サーバーがどのようにリッスンする必要があるかは、サンドボックスの種類によって異なります。
- Dedicated(Sandbox.create()):
127.0.0.1:<port>に通常の TCP ポートをバインドします。 - プール / 共有(SandboxPool): プールされたサンドボックスは TCP ポートをバインドできないため(Landlock)、
$SBX_PROXY_DIR/<port>.sockにある unix ソケット でリッスンします(この環境変数はすべてのサンドボックスで設定されています)。例:uvicorn app:app --uds $SBX_PROXY_DIR/8000.sock。クライアント側(proxy_url_for/proxy_headers)はどちらの場合も同一です。
ライフサイクル
サンドボックスは、それを生成したプロセスよりも長く存続します。同じ HF トークンを保持している任意のマシンから、後で再接続できます。状態をコピーする必要はありません:
>>> sbx = Sandbox.create()
>>> sbx.id
'687f911eaea852de79c4a50a'
# Later, from anywhere:
>>> sbx = Sandbox.connect("687f911eaea852de79c4a50a")
>>> sbx.kill() # terminate nowidle_timeout(デフォルトは10分)が実際の鍵を握っています。API呼び出しがなく、プロセスも実行されていない状態になるとサンドボックスをシャットダウンするため、放置されたサンドボックスの課金が停止します。作成時に設定(Sandbox.create(idle_timeout="30m"))するか、Noneを渡して無効にできます。- ジョブには、ハードバックストップとして固定の24時間という最大生存時間が設定されています(設定不可)。
forward_hf_token=Trueを設定すると、サンドボックス内で実行されるコードに HF トークンが明示的に公開されます(HF_TOKENとして)。forward_hf_token=Falseの場合でも、サンドボックスを認証情報のための堅牢な境界として扱わないでください。このページの上部にある警告を参照してください。
同時に多数のサンドボックス: SandboxPool
多数のサンドボックスが必要な場合(並列RLロールアウト、ファンアウト評価、バッチツール実行など)、サンドボックスごとに1つのJobを割り当てるのは非効率です。各Jobは完全なVMコールドスタートのコストを発生させ、数MBのRAMで済むワークロードのためにマシン全体を占有してしまいます。SandboxPoolでは、多数の軽量サンドボックスを少数の共有ホストJobに集約します。課金対象のVM1台で数十のサンドボックスを処理できるため、サンドボックスあたりのコストはその倍数だけ低減し、サンドボックスあたりのコールドスタートはネットワークの往復1回程度になります。
>>> from huggingface_hub import SandboxPool
>>> with SandboxPool(image="python:3.12", flavor="cpu-basic", warm_up=2) as pool:
... boxes = [pool.create() for _ in range(100)] # packed across the 2 warm host VMs
... print(boxes[0].run("echo hi").stdout) # each box is a normal Sandbox
hi各 create() は完全な Sandbox を1つ返します。繰り返し呼び出すことで、ファンアウトを行うことができます。プールは必要に応じてホストの Job を起動し、ホストごとに sandboxes_per_host 個のサンドボックスを詰め込み、close() が呼ばれた場合(またはホストがアイドル状態になった場合、請求のバックストップとして)にすべてを終了します。一般的なファンアウトパターンは以下の通りです。
>>> from concurrent.futures import ThreadPoolExecutor
>>> with SandboxPool(image="python:3.12", warm_up=4) as pool:
... boxes = [pool.create() for _ in tasks]
... with ThreadPoolExecutor(32) as ex:
... outputs = list(ex.map(lambda b, t: b.run(t.cmd).stdout, boxes, tasks))Env と idle_timeout はサンドボックス単位で設定されます(create() に属し、プールには属さないため)、そのため1つのプール内のサンドボックス間で異なる環境を設定できます。
>>> sbx = pool.create(env={"SEED": "42"}, idle_timeout="5m", forward_hf_token=True)プールされたサンドボックスは完全なSandboxですが、いくつかの入力がサンドボックスごとに設定されるのではなく、ホストによって固定されます。そのため、SandboxPool.create()はSandbox.create()よりも少ない数の引数を受け取ります。
| 入力 | Sandbox.create(専用) | SandboxPool.create(プール方式) |
|---|---|---|
image, flavor | サンドボックスごと | プールによって固定される(ホストで一度だけ設定される) |
volumes | サンドボックスごと | 利用不可(ホスト起動時にマウント済み) |
env | サンドボックスごと | サンドボックスごと |
idle_timeout | サンドボックスごと | サンドボックスごと |
forward_hf_token | サンドボックスごと | サンドボックスごと |
Pooled sandboxes は長期間稼働するホストジョブを共有するため、dedicated sandboxes のような暗号化された Job-secrets チャネルはありません。ただし、pooled sandbox の env はジョブの開始時ではなく、sandbox の作成時にホストサーバーへ配信されるため、どのジョブのメタデータにも保存されません。秘密情報とされる値は、プレーンな env として渡してください。
オンデマンドでの拡張と事前ウォームアップ
pool.create() は1回につき1つのサンドボックスを作成し、新しいホストを起動する前に、まだ空き容量があるホストを再利用します。これにより、作業が到着するたびにサンドボックスを生成でき、それらはウォームなホストに自動的に詰め込まれます。
>>> pool = SandboxPool(image="python:3.12", flavor="cpu-basic")
>>> sbx = pool.create() # boots the first host (~6s)
>>> sbx = pool.create() # packs onto the same warm host (~one round-trip, no new VM)最初の数回の呼び出しにおけるホストのコールドスタートを回避するには、warm_up=N(最初のcreate()時に起動)でホストを事前プロビジョニングするか、事前にpool.warm(N)を呼び出してください。
プロセスおよびマシン間でプールを再利用する
ウォームホストはジョブのラベルを通じて検出されるため、プロセスをまたいで再利用できます。同じimage/flavor/nameを持つ、まったく新しいSandboxPoolは、独自のホストを起動するのではなく、以前の実行で稼働しっぱなしだったホストにアタッチされます。異なるプールがホストを共有しないようにするには、name=を指定してください。
ローカル状態のない別のマシンから再接続するには、プールIDを指定してSandboxPool.connect()を呼び出します。これにより、実行中のホストが特定され、そのホストジョブからプールの設定(イメージ、フレーバー、パッキング密度)が再構築され、create()による追加の作成が可能な状態になります。
>>> pool = SandboxPool.connect("pool-ae9f7efe0bc7") # from anywhere, no config needed
>>> sbx = pool.create()connect() されたプールは共有ホストを所有していません(他のクライアントが使用している可能性があるため)、Sandbox.connect() と同様に、その with ブロックを終了しても(または close() を呼び出しても)、ローカルの HTTP クライアントを解放するだけで、ホストは実行されたままになります。プールのホストを明示的に終了するには、pool delete / hf sandbox pool delete <id> を使用してください。
ホスト内のサンドボックスは、個別の uid とサンドボックスごとの Landlock ルールセットによって分離されます。共有サンドボックスは、ユーザー自身の並列ワークロード用に設計されており、サンドボックス間のすべての攻撃からの完全な分離は保証されません。相互に信頼できないコードや GPU を使用する場合、Sandbox.create()(サンドボックスごとに独立した VM)を使用してください。トレードオフの詳細は、コンセプトガイドの分離の仕組みおよび、2 つのモードの選択に際してはより重要となる既知の制限事項の完全なリストに記載されています。
CLIから
hf sandbox コマンドは Python API を反映しています。専用のサンドボックス:
>>> hf sandbox create
✓ Sandbox ready id=687f911eaea852de79c4a50a image=python:3.12 elapsed=6.0s
# Attach labels to the underlying Job
>>> hf sandbox create --label controller-run=run-42 --label team=data-infra
>>> hf sandbox exec 687f911eaea852de79c4a50a -- python -c "print('hi')"
hi
>>> hf sandbox cp data.csv 687f911eaea852de79c4a50a:/data/data.csv
>>> hf sandbox kill 687f911eaea852de79c4a50ahf sandbox exec は出力をライブでストリーミングし、コマンドの終了コードで終了するため、スクリプト内で組み合わせることができます。
hf sandbox exec $ID -- pytest && echo "tests passed"hf sandbox spawn を使用してバックグラウンドで長時間実行されるプロセスを開始します(pid が出力されます)。その後、プロセスの一覧表示や停止を行います。一覧には、各プロセスのステータス(running または exited (<code>))が表示されます。
>>> hf sandbox spawn $ID -- python -m http.server 8000
✓ Process started sandbox=687f... pid=1234
>>> hf sandbox process ls $ID
pid status cmd
1234 running python -m http.server 8000
>>> hf sandbox process kill $ID 1234多くの安価な共有サンドボックスでは、プールを一度ウォームアップしてから、必要に応じてプール内に作成します。
# Warm a pool -> prints a pool id (billing starts: a host VM is now running)
>>> hf sandbox pool create python:3.12 --flavor cpu-basic
✓ Pool created id=pool-ae9f7efe0bc7 image=python:3.12 flavor=cpu-basic host=687f... elapsed=5.7s
# Each create packs onto a host with room (found by the pool id, from any machine);
# only when every host is full does it boot a duplicate. Env is per-sandbox (pooled
# sandboxes share a host, so there's no encrypted-secrets channel — use --env).
>>> hf sandbox create --pool pool-ae9f7efe0bc7 --env LOG_LEVEL=debug
>>> hf sandbox create --pool pool-ae9f7efe0bc7
>>> hf sandbox pool ls
>>> hf sandbox pool delete pool-ae9f7efe0bc7 # terminate the pool's hosts (and their sandboxes)hf sandbox create --pool は共有サンドボックスを生成します。その ID は <host_job_id>.<local_id> の形式で、専用 ID が使用できる場所(exec、cp、kill)でどこでも機能します。プールにはローカル状態がなく、プール ID で特定される実行中のホスト VM だけで構成されるため、どのマシンからでも使用でき、すべてのホストが消失(kill されたか、アイドルタイムアウトした)すると存在しなくなります。