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, значения по умолчанию и поведение могут измениться без предварительного уведомления. Общие песочницы предназначены для рабочих нагрузок в пределах одной границы доверия; их изоляция не гарантирует защиту от всех атак между песочницами. Используйте выделенные песочницы для рабочих нагрузок, которые не доверяют друг другу, и избегайте предоставления рабочим нагрузкам в песочницах долгосрочных или широко охваченных учётных данных.

Песочница — это изолированная облачная машина, которую можно запустить за секунды, выполнять в ней команды с выводом в реальном времени и передавать файлы внутрь и наружу — всё это из Python или через CLI. Песочницы построены на базе Jobs: под капотом песочница — это просто Job, запускающий небольшой сервер, который предоставляет выполнение команд и передачу файлов по HTTP.

Они хорошо подходят в тех случаях, когда нужно выполнять код где-либо, кроме вашей собственной машины:

  • Запуск недоверенного или сгенерированного ИИ кода — разрешите агенту выполнять произвольный код, не предоставляя ему доступ к вашей файловой системе. Используйте для этого выделенную песочницу: это отдельная виртуальная машина.
  • Воспроизводимые сборки и эксперименты — запуск в чистом, четко определенном образе, на CPU или GPU.
  • Распределение нагрузки — запуск сотен параллельных сред (RL-роллауты, оценка, пакетное выполнение инструментов) с минимальными затратами.

Подойдёт любой Docker-образ с /bin/sh — Python, pip или агент не требуют предварительной установки (небольшой статический бинарный файл сервера внедряется при запуске).

Хотите узнать, как это работает под капотом — сервер внутри задания, stateless-аутентификация или то, как одно задание (Job) размещает множество изолированных песочниц? См. концептуальное руководство по песочницам.

Два вида песочниц

Существует два способа получить песочницу. Оба возвращают один и тот же объект Sandbox (с теми же методами run, files, connect, kill); различие состоит лишь в том, как выделяется базовая машина:

Sandbox.create() — выделенныйSandboxPool — общий / пул
mappingодна задача = одна песочница (целая ВМ)одна Job = множество песочниц (одна ВМ, упакованы)
изоляцияполная ВМuid + Landlock (доверие в рамках одного пользователя)
холодный старт~6 с на песочницу~6 с для первого хоста, затем ~1 обмен данными для каждого
стоимостьодин ВМ на песочницуодин ВМ на хост, распределённый между множеством песочниц
GPU✅❌ (только CPU)
лучше всего подходит дляодин песочный контейнер, GPU-нагрузки, недоверенный кодмногие дешёвые CPU-песочницы (RL-роллауты, fan-out)

Ориентир: нужен 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-клиенте (follow_redirects=False для httpx или allow_redirects=False для requests). Приложение может перенаправлять запросы на другой источник, и эти пользовательские заголовки аутентификации не должны передаваться туда.

Пул песочниц требует сервер, возвращающий токены возможностей для каждой песочницы. Отсутствие токена является ошибкой; вместо использования токена управления хоста необходимо обновить и перезапустить хост.

То, как внутренний сервер должен слушать, зависит от типа песочницы:

  • Выделенный (Sandbox.create()): привязать обычный TCP-порт к 127.0.0.1:<port>.
  • Пул / общий (SandboxPool): песочницы из пула не могут привязать TCP-порт (Landlock), поэтому слушайте на unix-сокет по адресу $SBX_PROXY_DIR/<port>.sock (эта переменная окружения задана в каждой песочнице), например 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 now
  • idle_timeout (по умолчанию 10 минут) — ключевой параметр: он останавливает песочницу, если не выполняется ни один вызов API и не запущен ни один процесс, поэтому неиспользуемые песочницы перестают тарифицироваться. Установите его при создании (Sandbox.create(idle_timeout="30m")) или передайте None, чтобы отключить.
  • У задания также есть фиксированный максимальный срок жизни 24 часа в качестве жёсткой меры предосторожности (не настраивается).
  • forward_hf_token=True явно передаёт ваш HF-токен в код, выполняющийся в песочнице (в виде HF_TOKEN). Даже при forward_hf_token=False не рассматривайте песочницу как жёсткий барьер для ваших учётных данных — см. предупреждение в начале этой страницы.

Многие песочницы одновременно: SandboxPool

Когда требуется много песочниц (параллельные RL-роллауты, fan-out оценка, пакетное выполнение инструментов), назначение отдельного Job на каждую песочницу неэффективно: каждая из них несёт полную стоимость холодного запуска ВМ и занимает целый узел для нагрузки, требующей лишь несколько МБ ОЗУ. SandboxPool размещает множество лёгких песочниц в нескольких общих хост-Job — одна тарифицируемая ВМ обслуживает десятки песочниц, поэтому стоимость на песочницу снижается в соответствующем倍数, а холодный запуск каждой песочницы занимает около одного сетевого round-trip.

>>> 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; для масштабирования вызывайте его многократно. Пул запускает хостовые Jobs по мере необходимости, размещает 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на песочницуна песочницу

Пул-песочницы разделяют долгоживущую хост-задачу, поэтому у них нет канала зашифрованных секретов задачи, как у выделенных песочниц. Однако env пул-песочницы передаётся на хост-сервер при создании песочницы (а не при запуске задачи), поэтому он никогда не сохраняется в метаданных какой-либо задачи — передавайте предполагаемые секреты как обычные 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).

Повторное использование пулов между процессами и машинами

Тёплые хосты обнаруживаются по меткам задач, поэтому повторное использование работает между процессами: совершенно новый SandboxPool с теми же значениями image/flavor/name подключается к хостам, оставшимся запущенными после предыдущего запуска, вместо того чтобы запускать собственные. Передайте name=, чтобы отдельные пулы не делили хосты.

Чтобы переподключиться с другого компьютера, не имеющего локального состояния, используйте SandboxPool.connect() с указанием pool id — он находит работающий хост, восстанавливает конфигурацию пула (образ, flavor, плотность упаковки) из задания этого хоста и готов 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 создаёт общий sandbox; его id имеет вид <host_job_id>.<local_id> и работает везде, где используется выделенный id (exec, cp, kill). Пул не имеет локального состояния — это просто его работающие хостовые ВМ, которые находятся по id пула, — поэтому он работает с любой машины и перестаёт существовать, как только все его хосты исчезают (завершены или истёк таймаут простоя).

Update on GitHub