Hub Python Library documentation

沙盒

You are viewing main version, which requires installation from source. If you'd like regular pip install, checkout the latest stable version (v2.4.0).
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