Hub Python Library documentation

صناديق الرمل

Hugging Face's logo
Join the Hugging Face community

and get access to the augmented documentation experience

to get started

صناديق الرمل

تُعدّ البيئات المعزولة (Sandboxes) ميزة تجريبية. قد تتغير واجهة البرمجة (API) والقيم الافتراضية والسلوك دون إشعار مسبق. صُممت البيئات المعزولة المشتركة للأحمال التشغيلية ضمن نطاق الثقة نفسه؛ ولا تضمن عزلتها الحماية من كل هجوم بين البيئات المعزولة. استخدم بيئات معزولة مخصصة للأحمال التشغيلية التي لا تثق ببعضها البعض، وتجنّب إتاحة بيانات الاعتماد طويلة الأمد أو واسعة النطاق لأحمال البيئات المعزولة.

البيئة المعزولة (Sandbox) هي آلة سحابية معزولة يمكنك تشغيلها في ثوانٍ، وتنفيذ أوامر فيها مع بث مباشر للمخرجات، ونقل الملفات إليها ومن خلالها — كل ذلك من بايثون أو سطر الأوامر. تُبنى البيئات المعزولة فوق Jobs: ففي الخلفية، البيئة المعزولة ليست سوى مهمة (Job) تشغّل خادمًا صغيرًا يعرّض تنفيذ الأوامر ونقل الملفات عبر HTTP.

تُعدّ خيارًا مناسبًا في أي وقت تحتاج فيه إلى تشغيل الأكواد في مكان آخر غير جهازك الخاص:

  • تشغيل أكواد غير موثوقة أو أكواد مولّدة بالذكاء الاصطناعي — اسمح للوكيل بتنفيذ أكواد عشوائية دون منحها وصولاً إلى نظام الملفات لديك. استخدم بيئة معزولة مخصصة لهذا الغرض: فهي آلة افتراضية مستقلة بذاتها.
  • بناءات وتجارب قابلة للتكرار — تُنفَّذ على صورة نظيفة ومعرّفة بوضوح، على وحدة المعالجة المركزية أو وحدة معالجة الرسوميات.
  • توزيع المهام — إطلاق مئات البيئات المتوازية (تشغيلات التعلم المعزز، التقييم، تنفيذ الأدوات الدفعية) بتكلفة منخفضة.

تعمل أي صورة Docker تحتوي على /bin/sh — لا حاجة لتثبيت Python أو pip أو الوكيل مسبقًا (يتم حقن ملف تنفيذي صغير لخادم ثابت عند بدء التشغيل).

هل تتساءل عن آلية العمل الداخلية — الخادم داخل المهمة، أو المصادقة غير الحالة، أو كيف تستضيف مهمة واحدة العديد من البيئات المعزولة؟ راجد الدليل المفاهيمي للبيئات المعزولة.

النوعان من بيئة العزل

هناك طريقتان للحصول على بيئة معزولة. تمنحك كلتاهما كائن Sandbox نفسه (نفس run، files، connect، kill)؛ وتختلفان فقط في كيفية تخصيص الآلة الأساسية:

Sandbox.create() — مخصصSandboxPool — مشترك / مجمّع
الربطوظيفة واحدة = بيئة معزولة واحدة (آلة افتراضية كاملة)وظيفة واحدة = عدة بيئات معزولة (آلة افتراضية واحدة، مكدّسة)
العزلآلة افتراضية كاملةuid + Landlock (ثقة المستخدم نفسه)
البدء البارد~6 ثوانٍ لكل بيئة معزولة~6 ثوانٍ للمضيف الأول، ثم ~1 رحلة ذهاب وإياب لكل مضيف
التكلفةآلة افتراضية واحدة لكل بيئة معزولةVM واحدة لكل host، موزعة على العديد من البيئات المعزولة
وحدة معالجة الرسوميات✅❌ (وحدة المعالجة المركزية فقط)
الأفضل لـبيئة معزولة واحدة، أحمال عمل على وحدات معالجة الرسوميات، أكواد غير موثوقةكثير من بيئات العزل الرخيصة المعتمدة على المعالج (تنفيذات التعلم المعزز، التفرع)

قاعدة عامة: إذا كنت بحاجة إلى وحدة معالجة رسومية أو لتشغيل أكواد غير موثوق بها بشكل متبادل → استخدم بيئة مخصصة. إذا كنت بحاجة إلى مئات بيئات العزل الرخيصة المعتمدة على وحدة المعالجة المركزية → استخدم مجمّعًا.

البدء السريع

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

اختر أي صورة ونوع flavor من العتاد:

>>> 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. اجمع بين الرابط و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 socket عند $SBX_PROXY_DIR/<port>.sock (يتم تعيين متغير البيئة هذا في كل بيئة عزل)، على سبيل المثال uvicorn app:app --uds $SBX_PROXY_DIR/8000.sock. جانب العميل (proxy_url_for / proxy_headers) متطابق في الحالتين.

دورة الحياة

تبقى بيئة العزل (Sandbox) قائمة بعد انتهاء العملية التي أنشأتها — يمكنك إنشاؤها الآن وإعادة الاتصال بها لاحقًا، من أي جهاز يحمل رمز HF نفسه، دون الحاجة إلى نسخ أي حالة:

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

# Later, from anywhere:
>>> sbx = Sandbox.connect("687f911eaea852de79c4a50a")
>>> sbx.kill()           # terminate now
  • idle_timeout (الافتراضي 10 دقائق) هو الحارس الحقيقي: فهو يُغلق بيئة العزل عندما لا يتم تنفيذ أي استدعاء لواجهة برمجة التطبيقات ولا يعمل أي عملية، مما يوقف الفوترة عن بيئات العزل المهجورة. اضبطه عند الإنشاء (Sandbox.create(idle_timeout="30m")) أو مرّر None لتعطيله.
  • يتمتع هذا العمل أيضًا بعمر أقصى ثابت قدره 24 ساعة كإجراء احتياطي صارم (غير قابل للتهيئة).
  • يُظهر forward_hf_token=True بشكل صريح رمز HF الخاص بك إلى الكود الذي يعمل داخل بيئة العزل (بصفتها HF_TOKEN). حتى مع استخدام forward_hf_token=False، لا تعامل بيئة العزل كحدٍّ صارم لحماية بيانات الاعتماد الخاصة بك — راجع التحذير في أعلى هذه الصفحة.

العديد من بيئات العزل في وقت واحد: SandboxPool

عندما تحتاج إلى العديد من البيئات المعزولة (تنفيذات تعزيز تعلمي متوازية، تقييمات موزعة، تنفيذ أدوات على دفعات)، فإن تخصيص مهمة واحدة لكل بيئة معزولة يكون مضيعةً للموارد: إذ تتكبّد كل مهمة بدء تشغيل بارد كامل للآلة الافتراضية وتحتجز آلة كاملة بالكامل لحِمل عمل يحتاج إلى بضعة ميغابايتات من ذاكرة الوصول العشوائي. بدلاً من ذلك، يجمع SandboxPool العديد من البيئات المعزولة الخفيفة في عدد قليل من مهام مضيف مشتركة — فتخدم آلة افتراضية واحدة محسوبة الدفع عشرات البيئات المعزولة، مما يُخفض تكلفة كل بيئة معزولة بهذا العامل، ويصبح بدء التشغيل البارد لكل بيئة معزولة تقريباً ذهاباً وإياباً واحداً عبر الشبكة.

>>> 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 كامل واحد؛ يمكنك استدعاؤه بشكل متكرر لتوزيع المهام. يقوم الحوض بتشغيل مهام المضيف عند الحاجة، ويحزم 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 خاصة بكل رمل (sandbox) على حدة (فهما تابعان لـcreate() وليس للمجموعة pool)، لذا يمكن أن تختلف البيئات بين الرمال داخل مجموعة واحدة:

>>> 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) مسبقاً.

إعادة استخدام المجموعات عبر العمليات والأجهزة

تُكتشف مضيفات الدفء (Warm hosts) من خلال وسوم المهام، لذا يعمل إعادة الاستخدام عبر العمليات: فـSandboxPool جديد تمامًا يحمل نفس image/flavor/name يتصل بالمضيفات التي تركها تشغيل سابق قيد العمل بدلاً من تشغيل مضيفاته الخاصة. مرّر name= لمنع المجموعات المنفصلة من مشاركة المضيفات.

لإعادة الاتصال من جهاز آخر لا يحتوي على حالة محلية، أعد الاتصال عبر معرّف المجموعة باستخدام SandboxPool.connect() — حيث يجد مضيفًا قيد التشغيل، ويعيد بناء إعدادات المجموعة (الصورة، النوع، كثافة التعبئة) من مهمة ذلك المضيف، ويكون جاهزًا لإنشاء المزيد عبر 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>.

تُفصل الصناديق الرملية داخل مضيف واحد بواسطة معرّفات مستخدمين (uids) مميزة وقواعد Landlock خاصة بكل صندوق رملي. الصناديق الرملية المشتركة مخصصة لأحمال العمل المتوازية للمستخدم نفسه، ولا يُضمن العزل من كل هجوم بين الصناديق الرملية. بالنسبة للأكواد غير الموثوقة المتبادلة، أو لاستخدام GPU، استخدم Sandbox.create() (آلة افتراضية منفصلة لكل صندوق رملي). تفاصيل المفاضلات موضحة في الدليل المفاهيمي: كيف يعمل العزل و— وهو أكثر أهمية إذا كنت تختار بين الوضعين — القائمة الكاملة بـ القيود المعروفة.

من سطر الأوامر

يعكس الأمر hf sandbox واجهة برمجة التطبيقات الخاصة بلغة بايثون. صندوق رمل مخصص:

>>> 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 (يطبع المعرف الخاص بها)، ثم اذكر أو أوقف العمليات. يعرض القائمة حالة كل عملية (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 يُنشئ بيئة رملية مشتركة؛ يبدو معرّفها على النحو التالي <host_job_id>.<local_id> ويعمل في أي مكان يعمل فيه معرّف مخصص (exec، cp، kill). لا تحتوي المجموعة على حالة محلية — فهي تتألف فقط من أجهزة الاستضافة الافتراضية العاملة، والتي يتم العثور عليها عبر معرّف المجموعة — لذا تعمل من أي جهاز وتُصبح غير موجودة بمجرد اختفاء جميع أجهزة الاستضافة الخاصة بها (سواءً بسبب الإيقاف أو انتهاء مهلة الخمول).

Update on GitHub