Hub Python Library documentation

沙盒

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

沙盒

沙箱是一项实验性功能。其 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 now
  • idle_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 687f911eaea852de79c4a50a

hf 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 查找——因此它可以从任何机器访问,并且一旦其所有主机都不复存在(被终止或因空闲超时),它便不再存在。

Update on GitHub