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

샌드박스

Sandboxes는 실험적 기능입니다. API, 기본값 및 동작은 사전 통보 없이 변경될 수 있습니다. 공유 샌드박스는 동일한 신뢰 경계 내 워크로드를 위해 설계되었으며, 그 격리는 모든 샌드박스 간 공격으로부터의 보호를 보장하지 않습니다. 서로를 신뢰하지 않는 워크로드에는 전용 샌드박스를 사용하고, 샌드박스 워크로드에 장기 유효하거나 범위가 넓은 자격 증명을 제공하지 않도록 하십시오.

Sandbox는 몇 초 만에 시작할 수 있는 격리된 클라우드 머신으로, 실시간 스트리밍되는 출력을 확인하면서 명령을 실행하고 파일을 입출력할 수 있으며, Python 또는 CLI에서 모두 사용할 수 있습니다. Sandbox는 Jobs 위에 구축되어 있습니다. 내부적으로 Sandbox는 HTTP를 통해 명령 실행 및 파일 전송을 노출하는 작은 서버를 실행하는 Job일 뿐입니다.

자신의 머신 이외의 다른 곳에서 코드를 실행해야 하는 경우, 다음이 좋은 선택지입니다:

  • 신뢰할 수 없거나 AI가 생성한 코드 실행 — 에이전트가 파일 시스템에 접근할 수 없도록 설정하고 임의의 코드를 실행하게 합니다. 이를 위해 전용 샌드박스를 사용하세요. 이는 자체적인 VM입니다.
  • 재현 가능한 빌드 및 실험 — CPU 또는 GPU에서 깨끗하고 명확하게 정의된 이미지로 실행합니다.
  • 작업 분산 — 수백 개의 병렬 환경(RL 롤아웃, 평가, 배치 도구 실행)을 저렴하게 시작합니다.

/bin/sh가 포함된 Docker 이미지라면 모두 작동합니다. Python, pip, 에이전트를 사전에 설치할 필요가 없으며(시작 시 작은 정적 서버 바이너리가 주입됩니다).

이것이 내부적으로 어떻게 작동하는지 궁금하신가요 — 인-잡 서버, 무상태 인증, 또는 단일 Job이 여러 격리된 샌드박스를 호스팅하는 방식? 샌드박스 개념 가이드를 참고하세요.

두 가지 유형의 샌드박스

샌드박스를 생성하는 방법은 두 가지입니다. 두 방법 모두 동일한 Sandbox 객체(동일한 run, files, connect, kill)를 반환하며, 차이점은 기반 머신의 할당 방식에만 있습니다:

Sandbox.create() — 전용SandboxPool — 공유 / 풀
매핑Job 1개 = 샌드박스 1개(VM 전체)하나의 Job = 여러 샌드박스 (하나의 VM, 패킹됨)
격리전체 VMuid + Landlock (동일 사용자 신뢰)
콜드 스타트샌드박스당 약 6초첫 번째 호스트에 약 6초, 이후 각 라운드 트립에 약 1초
비용샌드박스당 VM 1개호스트당 VM 1개, 여러 샌드박스에 걸쳐 분산
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()은 명령을 실행하고 완료까지 대기합니다. 셸 문자열 또는 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)

기본적으로 mode는 타입에서 추론됩니다(문자열은 /bin/sh -c를 통해 실행되고, 리스트는 argv로 직접 exec됩니다). shell=를 전달하여 명시적으로 지정할 수 있습니다. ["echo hi"]와 같은 1개 요소 리스트가 "echo hi"라는 이름의 단일 프로그램으로 exec되는 전형적인 오류를 방지하는 데 유용합니다:

>>> 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가 발생합니다.

0이 아닌 값으로 종료되는 명령은 SandboxCommandError를 발생시킵니다(stdout, stderr, exit_code가 포함됩니다). 예외를 발생시키지 않고 SandboxCommandResult를 반환하려면 check=False를 전달하세요:

>>> result = sbx.run("test -f /tmp/missing", check=False)
>>> result.exit_code
1

백그라운드 프로세스

background=True를 전달하면 서버, 워처, 학습 실행과 같은 장시간 실행되는 프로세스를 대기하지 않고 시작할 수 있습니다. run은 SandboxCommandResult 대신 SandboxProcess를 즉시 반환합니다:

>>> 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이 누락된 경우 오류로 처리되며, 해당 host의 management token을 사용하는 대신 host를 업그레이드하고 재활용해야 합니다.

샌드박스의 종류에 따라 내부 서버가 경청해야 하는 방식이 달라집니다:

  • Dedicated (Sandbox.create()): 127.0.0.1:<port>에 일반 TCP 포트를 바인딩합니다.
  • Pool / shared (SandboxPool): pooled sandbox는 TCP 포트 바인딩이 불가능하므로(Landlock), $SBX_PROXY_DIR/<port>.sock에 있는 unix socket에서 리스닝해야 합니다(해당 환경 변수는 모든 sandbox에 설정되어 있음). 예: 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를 사용하더라도 샌드박스를 자격 증명의 경계로 간주하지 마세요. 이 페이지 상단의 경고 사항을 참조하세요.

Many sandboxes at once: SandboxPool

많은 샌드박스가 필요한 경우(병렬 RL 롤아웃, 팬아웃 평가, 배치 도구 실행) 샌드박스당 하나의 Job을 사용하는 것은 비효율적입니다. 각 Job마다 전체 VM 콜드 스타트 비용이 발생하며, RAM 몇 MB만 필요한 워크로드에 머신 전체를 점유하게 됩니다. SandboxPool은 여러 경량 샌드박스를 소수의 공유 호스트 Job에 패키징합니다. 즉, 과금되는 VM 한 대가 수십 개의 샌드박스를 서비스하므로 샌드박스당 비용이 해당 비율만큼 감소하고, 샌드박스당 콜드 스타트 시간은 네트워크 왕복 시간 한 번 수준으로 줄어듭니다.

>>> 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를 반환합니다. 반복 호출하여 fan-out을 수행할 수 있습니다. 풀은 필요에 따라 호스트 Job을 시작하고, 호스트당 sandboxes_per_host개의 샌드박스를 배치하며, close() 호출 시(또는 호스트가 유휴 상태가 될 때, 과금 방지용 백스톱으로) 모든 것을 종료합니다. 일반적인 fan-out 패턴은 다음과 같습니다:

>>> 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)

풀된 샌드박스(Pooled sandbox)는 완전한 Sandbox이지만, 일부 입력값은 샌드박스별로 설정되는 대신 호스트에 의해 고정됩니다. 따라서 SandboxPool.create()는 Sandbox.create()보다 적은 수의 인수를 받습니다:

입력Sandbox.create (전용)SandboxPool.create (풀링)
image, flavor샌드박스별풀에 의해 고정됨(호스트에서 한 번만 설정)
volumes샌드박스별사용 불가 (호스트 부팅 시 마운트됨)
env샌드박스별샌드박스별
idle_timeout샌드박스별샌드박스별
forward_hf_token샌드박스별샌드박스별

Pooled sandboxes는 장기 실행되는 host job을 공유하므로, dedicated sandboxes와 같은 암호화된 Job-secrets 채널이 없습니다. 그러나 pooled sandbox의 env는 job 시작 시가 아니라 sandbox 생성 시점에 host server로 전달되므로, 어떤 job의 메타데이터에도 저장되지 않습니다. 따라서 비밀 정보는 일반 env로 전달하세요.

요청에 따라 성장 및 사전 워밍

pool.create()는 한 번에 하나의 샌드박스를 생성하며, 새 호스트를 부팅하기 전에 아직 여유 용량이 있는 호스트를 재사용합니다. 따라서 작업이 도착할 때마다 샌드박스를 생성할 수 있으며, 샌드박스는 이미 준비된(warm) 호스트에 자동으로 배치됩니다:

>>> 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 host는 job label을 통해 발견되므로 재사용은 프로세스 간에 작동합니다: 이전 실행에서 실행을 유지한 host에 연결하고 자체적으로 부팅하지 않습니다. 동일한 image/flavor/name를 가진 완전히 새로운 SandboxPool이 이를 수행합니다. 서로 다른 pool이 host를 공유하지 않도록 하려면 name=를 전달하세요.

로컬 상태가 없는 다른 머신에서 다시 연결하려면 SandboxPool.connect()로 풀 ID를 지정하여 재연결하십시오. 실행 중인 호스트를 찾아 해당 호스트 작업에서 풀 구성(이미지, 플레버, 패킹 밀도)을 재구성한 후 create()를 통해 추가 생성할 준비가 완료됩니다:

>>> pool = SandboxPool.connect("pool-ae9f7efe0bc7")   # from anywhere, no config needed
>>> sbx = pool.create()

connect()된 pool은 공유된 호스트를 소유하지 않습니다(다른 클라이언트가 해당 호스트를 사용 중일 수 있음). 따라서 Sandbox.connect()와 마찬가지로, with 블록을 벗어나거나 close()를 호출해도 로컬 HTTP 클라이언트만 해제되고 호스트는 계속 실행됩니다. pool의 호스트를 명시적으로 종료하려면 pool delete / hf sandbox pool delete <id>를 사용하세요.

호스트 내 샌드박스는 고유한 uid와 샌드박스별 Landlock ruleset으로 분리됩니다. 공유 샌드박스는 사용자 자신의 병렬 워크로드를 위해 설계되었으며, 모든 샌드박스 간 공격으로부터의 격리는 보장되지 않습니다. 상호 신뢰할 수 없는 코드나 GPU를 사용하는 경우 Sandbox.create()(샌드박스당 개별 VM)를 사용하십시오. 트레이드오프는 개념 가이드에 자세히 설명되어 있습니다: 격리가 작동하는 방식 및 두 모드 중 하나를 선택하는 경우 더 중요한 알려진 제한 사항 전체 목록입니다.

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를 사용하여 백그라운드에서 장시간 실행되는 프로세스를 시작합니다(프로세스 ID를 출력합니다). 이후 프로세스를 나열하거나 중지할 수 있습니다. 목록에는 각 프로세스의 상태(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)에서 동일하게 동작합니다. 풀에는 로컬 상태가 없으며, 풀 id로 식별되는 실행 중인 호스트 VM만 존재합니다. 따라서 어떤 머신에서도 사용할 수 있으며, 모든 호스트가 제거되거나(kill 또는 idle-timeout) 사라지면 더 이상 존재하지 않습니다.

Update on GitHub