Hub Python Library documentation
沙盒
沙盒
沙箱是一项实验性功能。其 API、默认设置和行为可能会在不另行通知的情况下发生变化。共享沙箱旨在用于同一信任边界内的工作负载;其隔离机制并不能保证防范所有跨沙箱攻击。对于互不信任的工作负载,请使用专用沙箱,并避免向沙箱工作负载提供长期有效或权限范围过大的凭据。
沙箱(Sandbox)是一台可在数秒内启动的隔离云机器,您可以在其中运行命令并实时流式查看输出,也可以在其中传输文件——所有操作均可通过 Python 或 CLI 完成。沙箱构建于 Jobs 之上:在底层,沙箱只是一个运行着小型服务器的 Job,该服务器通过 HTTP 暴露命令执行和文件传输功能。
当您需要在本机以外的环境中运行代码时,它们是一个很好的选择:
- 运行不受信任或 AI 生成的代码 — 允许智能体执行任意代码,但不授予其访问文件系统的权限。请为此使用一个专用沙箱:它是一个独立的虚拟机。
- 可复现的构建与实验 — 在干净、定义明确的镜像上运行,支持 CPU 或 GPU。
- 分发任务 — 以低成本启动数百个并行环境(RL 推演、评估、批量工具执行)。
任何包含 /bin/sh 的 Docker 镜像均可使用——无需预装 Python、pip 或 agent(启动时会注入一个小型静态服务器二进制文件)。
想知道其底层工作原理——任务内服务器、无状态身份验证,或单个 Job 如何托管多个隔离的沙箱?请参阅 沙箱概念指南。
两种沙盒
获取沙箱有两种方式。两种方式都会返回相同的 Sandbox 对象(具有相同的 run、files、connect、kill);区别仅在于底层机器的分配方式:
| Sandbox.create() — 专用 | SandboxPool — 共享 / 池 | |
|---|---|---|
| 映射 | 一个 Job = 一个沙箱(一台完整的虚拟机) | 一个 Job = 多个沙箱(一个 VM,打包) |
| 隔离 | 完整虚拟机 | uid + Landlock(同一用户信任) |
| 冷启动 | 每个沙箱约 6 秒 | 第一个主机约需 6 秒,之后每次约需 1 个往返时间 |
| cost | 每个沙箱一个虚拟机 | 每个 host 一个 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() 会执行命令并等待其完成。请传入 shell 字符串或 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 执行)。传入 shell= 可以显式指定模式——这有助于避免一个常见的陷阱:像 ["echo hi"] 这样的单元素列表会被当作一个名为 "echo hi" 的程序来执行:
>>> 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 会立即返回一个 SandboxProcess,而不是 SandboxCommandResult:
>>> 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)列出沙箱中的进程,并在完成后停止其中一个。已完成的进程会保留在列表中(带有 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 token 的服务器。缺失 token 视为错误;应升级并回收该主机,而不是使用其管理 token。
内部服务器必须如何监听取决于沙箱类型:
- 专用(Sandbox.create()):在
127.0.0.1:<port>上绑定一个普通的 TCP 端口。 - 池化 / 共享(SandboxPool):池化沙箱无法绑定 TCP 端口(Landlock),因此请在
$SBX_PROXY_DIR/<port>.sock处监听 unix socket(该环境变量在每个沙箱中均已设置),例如uvicorn app:app --uds $SBX_PROXY_DIR/8000.sock。客户端(proxy_url_for/proxy_headers)在两种情况下完全相同。
生命周期
沙箱的生命周期长于创建它的进程——您可以现在创建它,之后从任何持有相同 HF token 的机器重新连接,无需复制任何状态:
>>> 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 token 显式暴露给在沙箱中运行的代码(作为HF_TOKEN)。即使设置了forward_hf_token=False,也不要将沙箱视为凭据的硬性边界——请参阅本页顶部的警告。
同时使用多个沙箱:SandboxPool
当您需要大量沙箱(例如并行 RL 推演、扇出评估、批量工具执行)时,为每个沙箱单独创建一个 Job 会造成资源浪费:每个沙箱都需要经历完整的虚拟机冷启动,并且会占用整台机器,而该工作负载实际上仅需几 MB 的内存。SandboxPool 将许多轻量级沙箱打包到少数共享的主机 Job 中——一台计费的虚拟机即可服务数十个沙箱,因此每个沙箱的成本按此比例降低,且每个沙箱的冷启动时间约为一次网络往返。
>>> 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;可以反复调用它以实现扇出。该池会按需启动主机上的 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(),而非池),因此同一个池中的沙箱可以拥有不同的环境:
>>> 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 share a long-lived host job, so there’s no encrypted-Job-secrets channel like dedicated sandboxes get. But a pooled sandbox’s env is delivered to the host server at sandbox creation (not at job start), so it’s never stored in any job’s metadata — pass would-be secrets as plain env.
按需增长和预热
pool.create() 每次创建一个沙箱,在启动新主机之前会优先复用仍有空闲容量的主机——因此您可以在工作到达时随时创建沙箱,它们会自动打包到已预热的主机上:
>>> 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)。
跨进程和跨机器复用池
Warm 主机通过作业标签发现,因此复用可以跨进程生效:一个全新的 SandboxPool,只要具有相同的 image/flavor/name,就会连接到先前运行遗留下来的主机,而不是自行启动新的主机。传入 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()(每个沙箱对应一个独立的虚拟机)。相关权衡详见概念指南:隔离机制的工作原理,以及——如果您正在两种模式之间做选择——已知限制的完整列表。
从 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)。池没有本地状态——它只是其正在运行的主机 VM,通过池 id 查找——因此它可以从任何机器访问,并且一旦其所有主机都不复存在(被终止或因空闲超时),它便不再存在。