Hub Python Library documentation

Các môi trường cách ly

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

Các môi trường cách ly

Sandboxes là một tính năng thực nghiệm. API, các giá trị mặc định và hành vi có thể thay đổi mà không cần thông báo trước. Các sandbox dùng chung được thiết kế cho các tác vụ nằm trong cùng một ranh giới tin cậy; sự cách ly của chúng không đảm bảo bảo vệ khỏi mọi cuộc tấn công xuyên sandbox. Hãy sử dụng các sandbox riêng biệt cho các tác vụ không tin tưởng lẫn nhau, và tránh cung cấp các thông tin xác thực có thời hạn dài hoặc phạm vi rộng cho các tác vụ trong sandbox.

Sandbox là một máy chủ cloud được cách ly, có thể khởi tạo trong vài giây, cho phép chạy các lệnh với đầu ra được phát trực tiếp, và di chuyển tệp vào ra — tất cả đều từ Python hoặc CLI. Sandbox được xây dựng dựa trên Jobs: bên trong, một sandbox đơn giản chỉ là một Job chạy một server nhỏ, cung cấp khả năng thực thi lệnh và truyền tệp qua HTTP.

Chúng phù hợp tốt khi bạn cần chạy mã ở nơi khác ngoài máy của mình:

  • Chạy mã không đáng tin cậy hoặc mã do AI tạo ra — cho phép tác nhân thực thi mã tùy ý mà không cấp cho nó quyền truy cập vào hệ thống tệp của bạn. Hãy sử dụng một sandbox riêng biệt cho mục đích này: nó là một máy ảo độc lập.
  • Xây dựng và thử nghiệm có thể tái lập — chạy trên một image sạch, được định nghĩa rõ ràng, trên CPU hoặc GPU.
  • Phân tán công việc — khởi tạo hàng trăm môi trường song song (RL rollouts, đánh giá, thực thi công cụ theo lô) với chi phí thấp.

Bất kỳ hình ảnh Docker nào có /bin/sh đều hoạt động — không cần cài đặt sẵn Python, pip hay agent (một binary máy chủ tĩnh nhỏ sẽ được tiêm vào khi khởi động).

Bạn tò mò về cách hoạt động bên trong — máy chủ trong Job, xác thực không trạng thái, hay cách một Job duy nhất có thể chứa nhiều sandbox cách ly? Xem hướng dẫn khái niệm về Sandboxes.

Hai loại sandbox

Có hai cách để tạo một sandbox. Cả hai đều trả về cùng một đối tượng Sandbox (cùng run, files, connect, kill); chúng chỉ khác nhau ở cách phân bổ máy chủ cơ sở:

Sandbox.create() — riêng biệtSandboxPool — chia sẻ / pool
mappingmột Job = một sandbox (một VM hoàn chỉnh)một Job = nhiều sandbox (một VM, đóng gói)
cách lyVM đầy đủuid + Landlock (tin cậy cùng người dùng)
khởi động lạnh~6 giây cho mỗi sandbox~6s cho host đầu tiên, sau đó ~1 lượt đi-về cho mỗi lần
chi phímột VM cho mỗi sandboxmột VM cho mỗi host, được phân bổ cho nhiều sandbox
GPU✅❌ (chỉ CPU)
phù hợp nhất chomột sandbox duy nhất, các tác vụ GPU, mã không đáng tinnhiều sandbox CPU giá rẻ (RL rollouts, fan-out)

Quy tắc chung: cần GPU hoặc chạy mã không tin cậy lẫn nhau → dùng riêng. Cần hàng trăm sandbox CPU giá rẻ → dùng pool.

Bắt đầu nhanh

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

Chọn bất kỳ hình ảnh và phần cứng flavor nào:

>>> sbx = Sandbox.create(image="alpine:3.20")
>>> sbx = Sandbox.create(image="pytorch/pytorch:2.6.0-cuda12.4-cudnn9-devel", flavor="a10g-small")

Chạy các lệnh

Sandbox.run() thực thi một lệnh và chờ nó hoàn tất. Truyền vào một chuỗi shell hoặc một danh sách 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)

Mặc định, chế độ được suy ra từ kiểu dữ liệu (một chuỗi sẽ chạy qua /bin/sh -c, một danh sách sẽ được thực thi trực tiếp như argv). Truyền shell= để làm rõ điều này — hữu ích để tránh bẫy kinh điển khi một danh sách một phần tử như ["echo hi"] bị thực thi như một chương trình duy nhất có tên là "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 yêu cầu một chuỗi và shell=False yêu cầu một danh sách; truyền sai kiểu sẽ gây ra ValueError.

Lệnh thoát với mã khác không sẽ gây ra SandboxCommandError (kèm theo stdout, stderr và exit_code). Truyền check=False để nhận lại SandboxCommandResult thay vì gây ra lỗi:

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

Quá trình nền

Truyền background=True để khởi động một tiến trình chạy dài (một máy chủ, một trình theo dõi, một phiên huấn luyện) mà không cần chờ đợi. run sẽ trả về một SandboxProcess ngay lập tức thay vì một 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)

Liệt kê các tiến trình của một sandbox và dừng một tiến trình khi bạn hoàn tất. Các tiến trình đã hoàn thành vẫn được liệt kê (với running=False và exit_code của chúng) cho đến khi sandbox bị xóa, giúp bạn biết được một tiến trình vẫn đang chạy hay đã thoát:

>>> sbx.processes()
[SandboxProcess(pid=1234, cmd='python -m http.server 8000', tag=None, started_at_ms=1700000000000, running=True, exit_code=None)]
>>> proc.kill()

Các tùy chọn streaming/chỉ chờ (timeout, stdin, on_stdout, on_stderr, check) không áp dụng ở chế độ nền — chỉ có env, cwd và shell được tuân thủ.

Tệp

>>> 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, ...)]

Các trình trợ giúp khác: stat, exists, mkdir, delete.

Truy cập máy chủ bên trong môi trường sandbox

Khởi động một server trong sandbox (chạy nền), sau đó truy cập nó từ bên ngoài bằng Sandbox.proxy_url_for() — yêu cầu sẽ được chuyển tiếp bởi server sandbox trong job đến server bên trong của bạn, do đó không cần mở thêm cổng công khai nào. Cách này hoạt động với HTTP thông thường, Server-Sent Events và WebSocket. Kết hợp URL với Sandbox.proxy_headers để xác thực (client WebSocket/HTTP của bạn phải gửi các header này):

>>> 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://")

Khi sử dụng proxy_headers, hãy tắt tính năng chuyển hướng trong client HTTP của bạn (follow_redirects=False với httpx, hoặc allow_redirects=False với requests). Một ứng dụng có thể chuyển hướng đến một origin khác, và các header xác thực tùy chỉnh này không được chuyển tiếp đến đó.

Các sandbox dạng pooled yêu cầu một server trả về các token năng lực cho từng sandbox. Việc thiếu token được coi là lỗi; hãy nâng cấp và khởi tạo lại host thay vì sử dụng token quản lý của nó.

Cách máy chủ bên trong phải lắng nghe phụ thuộc vào loại sandbox:

  • Dedicated (Sandbox.create()): gắn một cổng TCP thông thường trên 127.0.0.1:<port>.
  • Pool / dùng chung (SandboxPool): các sandbox dạng pool không thể bind cổng TCP (Landlock), vì vậy hãy listen trên một unix socket tại $SBX_PROXY_DIR/<port>.sock (biến môi trường này được đặt trong mọi sandbox), ví dụ uvicorn app:app --uds $SBX_PROXY_DIR/8000.sock. Phía client (proxy_url_for / proxy_headers) giống hệt nhau trong cả hai trường hợp.

Chu kỳ sống

Một sandbox tồn tại lâu hơn tiến trình đã tạo ra nó — bạn có thể tạo nó ngay bây giờ và kết nối lại sau, từ bất kỳ máy nào giữ cùng một HF token, mà không cần sao chép trạng thái:

>>> sbx = Sandbox.create()
>>> sbx.id
'687f911eaea852de79c4a50a'

# Later, from anywhere:
>>> sbx = Sandbox.connect("687f911eaea852de79c4a50a")
>>> sbx.kill()           # terminate now
  • idle_timeout (mặc định 10 phút) là yếu tố then chốt: nó tắt sandbox khi không có cuộc gọi API nào và không có tiến trình nào đang chạy, nhờ đó các sandbox bị bỏ quên sẽ ngừng tính phí. Đặt giá trị này khi tạo (Sandbox.create(idle_timeout="30m")) hoặc truyền None để tắt tính năng này.
  • Công việc cũng có thời gian tồn tại tối đa cố định là 24 giờ như một biện pháp dự phòng cuối cùng (không thể cấu hình).
  • forward_hf_token=True hiển thị rõ ràng token HF của bạn cho mã đang chạy trong sandbox (dưới dạng HF_TOKEN). Ngay cả khi forward_hf_token=False, đừng coi sandbox là ranh giới cứng cho các thông tin xác thực của bạn — xem cảnh báo ở đầu trang này.

Nhiều sandbox cùng lúc: SandboxPool

Khi bạn cần nhiều sandbox (các lượt rollout RL song song, đánh giá fan-out, thực thi công cụ theo lô), việc tạo một Job cho mỗi sandbox là lãng phí: mỗi sandbox phải chịu chi phí khởi động lạnh đầy đủ của một VM và chiếm dụng cả một máy cho một khối lượng công việc chỉ cần vài MB RAM. SandboxPool đóng gói nhiều sandbox nhẹ vào một vài Job host dùng chung — một VM được tính phí phục vụ hàng chục sandbox, do đó chi phí cho mỗi sandbox giảm đi theo hệ số đó và thời gian khởi động lạnh cho mỗi sandbox chỉ còn khoảng một lượt đi-về mạng.

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

Mỗi lần gọi create() sẽ trả về một Sandbox hoàn chỉnh; hãy gọi lặp lại để phân tán. Pool sẽ khởi động các Job trên host khi cần, đóng gói sandboxes_per_host sandbox trên mỗi host, và kết thúc tất cả khi gọi close() (hoặc khi một host trở nên nhàn rỗi, như một biện pháp dự phòng về hóa đơn). Mẫu phân tán điển hình:

>>> 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 và idle_timeout là thuộc tính của từng sandbox (chúng thuộc về create(), không phải pool), do đó các sandbox trong cùng một pool có thể có môi trường khác nhau:

>>> sbx = pool.create(env={"SEED": "42"}, idle_timeout="5m", forward_hf_token=True)

Một sandbox được gom nhóm là một Sandbox đầy đủ, nhưng một số đầu vào được cố định bởi host thay vì theo từng sandbox — do đó SandboxPool.create() chấp nhận một tập hợp các đối số nhỏ hơn so với Sandbox.create():

Đầu vàoSandbox.create (riêng biệt)SandboxPool.create (dùng pool)
image, flavorcho từng sandboxđược cố định bởi pool (được thiết lập một lần trên host)
volumescho từng sandboxkhông khả dụng (được gắn khi khởi động máy chủ)
envcho từng sandboxcho từng sandbox
idle_timeoutcho từng sandboxcho từng sandbox
forward_hf_tokencho từng sandboxcho từng sandbox

Các sandbox dạng pooled chia sẻ một host job có vòng đời dài, do đó không có kênh Job-secrets mã hóa như các sandbox dedicated. Tuy nhiên, env của một sandbox pooled được chuyển đến host server tại thời điểm tạo sandbox (không phải khi job bắt đầu), nên nó không bao giờ được lưu trong metadata của bất kỳ job nào — hãy truyền các bí mật dự kiến dưới dạng env thông thường.

Mở rộng theo nhu cầu và làm nóng trước

pool.create() tạo một sandbox một lúc, tái sử dụng một host vẫn còn dung lượng trống trước khi khởi động một host mới — vì vậy bạn có thể tạo các sandbox khi công việc đến và chúng tự động được xếp lên các host đang ấm:

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

Để tránh khởi động lạnh trên host ở vài lần gọi đầu tiên, hãy cung cấp trước các host bằng cách đặt warm_up=N (được khởi động ở lần gọi create() đầu tiên) hoặc bằng cách gọi pool.warm(N) trước.

Tái sử dụng các pool giữa các tiến trình và máy

Các host ấm được phát hiện thông qua nhãn của job, do đó việc tái sử dụng hoạt động xuyên suốt các tiến trình: một SandboxPool hoàn toàn mới với cùng image/flavor/name sẽ gắn vào các host mà một lần chạy trước đó đã để chạy thay vì khởi động các host riêng. Truyền một name= để ngăn các pool riêng biệt chia sẻ host.

Để gắn lại từ một máy khác không có trạng thái cục bộ, hãy kết nối lại bằng pool id với SandboxPool.connect() — nó sẽ tìm một host đang chạy, xây dựng lại cấu hình của pool (image, flavor, packing density) từ job của host đó, và sẵn sàng create() thêm:

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

Một pool đã được connect() không sở hữu các host dùng chung (các client khác có thể đang sử dụng chúng), do đó — giống như Sandbox.connect() — việc thoát khỏi khối with (hoặc gọi close()) chỉ giải phóng các client HTTP cục bộ và để các host tiếp tục chạy. Hãy kết thúc các host của pool một cách tường minh bằng pool delete / hf sandbox pool delete <id>.

Các sandbox trong một host được tách biệt bằng các uid riêng biệt và các bộ quy tắc Landlock cho từng sandbox. Các sandbox dùng chung được thiết kế cho các tác vụ song song của chính người dùng đó, và không đảm bảo cách ly khỏi mọi cuộc tấn công xuyên sandbox. Đối với mã không tin cậy lẫn nhau, hoặc cho GPU, hãy sử dụng Sandbox.create() (một VM riêng cho mỗi sandbox). Các đánh đổi được mô tả chi tiết trong hướng dẫn khái niệm: cách cách ly hoạt động và — quan trọng hơn nếu bạn đang chọn giữa hai chế độ — danh sách đầy đủ các hạn chế đã biết.

Từ CLI

Lệnh hf sandbox mô phỏng API Python. Một sandbox riêng:

>>> 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 truyền trực tiếp kết quả đầu ra và thoát với mã thoát của lệnh, do đó có thể kết hợp trong các script:

hf sandbox exec $ID -- pytest && echo "tests passed"

Bắt đầu một quy trình chạy dài hạn ở chế độ nền bằng hf sandbox spawn (in ra pid của nó), sau đó liệt kê hoặc dừng các quy trình. Danh sách hiển thị trạng thái của từng quy trình (running hoặc 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

Đối với nhiều sandbox chia sẻ giá rẻ, hãy khởi động một pool một lần rồi tạo vào đó theo yêu cầu:

# 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 tạo ra một sandbox dùng chung; id của nó có dạng <host_job_id>.<local_id> và hoạt động ở mọi nơi mà một id chuyên dụng có thể dùng được (exec, cp, kill). Một pool không có trạng thái cục bộ — nó chỉ bao gồm các host VM đang chạy, được tìm thấy bằng pool id — vì vậy nó hoạt động từ bất kỳ máy nào và sẽ không còn tồn tại khi tất cả các host của nó biến mất (bị kill hoặc hết thời gian chờ do nhàn rỗi).

Update on GitHub