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

> [!NOTE]
> 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 chuyên dụng 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](./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).

> [!TIP]
> 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](../concepts/sandbox).

## 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](/docs/huggingface_hub/v2.4.0/vi/package_reference/sandbox#huggingface_hub.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()](/docs/huggingface_hub/v2.4.0/vi/package_reference/sandbox#huggingface_hub.Sandbox.create) — **riêng biệt**              | [SandboxPool](/docs/huggingface_hub/v2.4.0/vi/package_reference/sandbox#huggingface_hub.SandboxPool) — **chia sẻ / pool**                                                     |
| ---------- | ----------------------------------------------- | --------------------------------------------------------------------------------------- |
| mapping    | mộ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 ly  | VM đầy đủ                                         | uid + [Landlock](https://docs.kernel.org/userspace-api/landlock.html) (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 sandbox                              | một VM cho mỗi **host**, được phân bổ cho nhiều sandbox                                    |
| GPU        | ✅                                               | ❌ (chỉ CPU)                                                                            |
| phù hợp nhất cho   | một sandbox duy nhất, các tác vụ GPU, mã không đáng tin | nhiề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

```python
>>> 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](./jobs#select-the-hardware) nào:

```python
>>> 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()](/docs/huggingface_hub/v2.4.0/vi/package_reference/sandbox#huggingface_hub.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:

```python
>>> 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"`:

```python
>>> 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](/docs/huggingface_hub/v2.4.0/vi/package_reference/sandbox#huggingface_hub.SandboxCommandResult) thay vì gây ra lỗi:

```python
>>> 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](/docs/huggingface_hub/v2.4.0/vi/package_reference/sandbox#huggingface_hub.SandboxProcess) ngay lập tức thay vì một [SandboxCommandResult](/docs/huggingface_hub/v2.4.0/vi/package_reference/sandbox#huggingface_hub.SandboxCommandResult):

```python
>>> 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, vì vậy bạn có thể biết được một tiến trình vẫn đang chạy hay đã thoát:

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

```python
>>> 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()](/docs/huggingface_hub/v2.4.0/vi/package_reference/sandbox#huggingface_hub.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 chúng):

```python
>>> 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 là một lỗi; hãy nâng cấp và tái tạo 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()](/docs/huggingface_hub/v2.4.0/vi/package_reference/sandbox#huggingface_hub.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](/docs/huggingface_hub/v2.4.0/vi/package_reference/sandbox#huggingface_hub.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:

```python
>>> 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](/docs/huggingface_hub/v2.4.0/vi/package_reference/sandbox#huggingface_hub.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.

```python
>>> 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](/docs/huggingface_hub/v2.4.0/vi/package_reference/sandbox#huggingface_hub.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:

```python
>>> 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:

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

Một sandbox được gom nhóm là một [Sandbox](/docs/huggingface_hub/v2.4.0/vi/package_reference/sandbox#huggingface_hub.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()](/docs/huggingface_hub/v2.4.0/vi/package_reference/sandbox#huggingface_hub.SandboxPool.create) chấp nhận một tập hợp các đối số nhỏ hơn so với [Sandbox.create()](/docs/huggingface_hub/v2.4.0/vi/package_reference/sandbox#huggingface_hub.Sandbox.create):

| Đầu vào              | `Sandbox.create` (riêng biệt) | `SandboxPool.create` (dùng pool)            |
| ------------------ | ---------------------------- | ---------------------------------------- |
| `image`, `flavor`  | cho từng sandbox                  | được cố định bởi pool (được thiết lập một lần trên host) |
| `volumes`          | cho từng sandbox                  | **không khả dụng** (được gắn khi khởi động máy chủ) |
| `env`              | cho từng sandbox                  | cho từng sandbox                              |
| `idle_timeout`     | cho từng sandbox                  | cho từng sandbox                              |
| `forward_hf_token` | cho từng sandbox                  | cho 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:

```python
>>> 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()](/docs/huggingface_hub/v2.4.0/vi/package_reference/sandbox#huggingface_hub.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:

```python
>>> 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), vì vậy — giống như [Sandbox.connect()](/docs/huggingface_hub/v2.4.0/vi/package_reference/sandbox#huggingface_hub.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>`.

> [!NOTE]
> 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()](/docs/huggingface_hub/v2.4.0/vi/package_reference/sandbox#huggingface_hub.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](../concepts/sandbox#isolation-in-a-pool-uid--landlock) 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](../concepts/sandbox#known-limitations).

## Từ CLI

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

```bash
>>> 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:

```bash
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>)`):

```bash
>>> 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:

```bash
# 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).

