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

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