11.3. التوازي وإدارة الموارد والتهيئة#
11.3.1. التوازي#
يقوم بعض المقدرين والمرافقين في scikit-learn بتوازي العمليات المكلفة باستخدام عدة وحدات معالجة مركزية.
اعتمادًا على نوع المقدر وأحيانًا على قيم معلمات الباني، يتم ذلك إما:
مع التوازي عالي المستوى عبر joblib.
مع التوازي منخفض المستوى عبر OpenMP، المستخدم في كود C أو Cython.
مع التوازي منخفض المستوى عبر BLAS، المستخدم بواسطة NumPy وSciPy للعمليات العامة على المصفوفات.
معلمات n_jobs
للمقدرين تتحكم دائمًا في مقدار التوازي
الذي يديره joblib (العمليات أو الخيوط اعتمادًا على backend joblib).
التوازي على مستوى الخيوط الذي يديره OpenMP في كود Cython الخاص بـ scikit-learn
أو بواسطة BLAS & LAPACK الذي تستخدمه مكتبات NumPy وSciPy المستخدمة في scikit-learn
يتم التحكم فيه دائمًا بواسطة متغيرات البيئة أو threadpoolctl
كما هو موضح أدناه.
لاحظ أن بعض المقدرين يمكنهم الاستفادة من جميع أنواع التوازي الثلاثة في نقاط مختلفة
من طرق التدريب والتنبؤ الخاصة بهم.
نصف هذه الأنواع الثلاثة من التوازي في الفقرات الفرعية التالية بمزيد من التفاصيل.
11.3.1.1. التوازي عالي المستوى مع joblib#
عندما يستخدم التنفيذ joblib، يمكن التحكم في عدد العمال
(الخيوط أو العمليات) التي يتم إنشاؤها بالتوازي عبر
المعلمة n_jobs
.
ملاحظة
أين (وكيف) يحدث التوازي في المقدرين الذين يستخدمون joblib عن طريق
تحديد n_jobs
موثق حاليًا بشكل سيئ.
يرجى مساعدتنا من خلال تحسين وثائقنا والتعامل مع issue 14228!
يمكن لـ joblib دعم كل من المعالجة المتعددة والتعدد الخيطي. ما إذا كان joblib يختار إنشاء خيط أو عملية يعتمد على backend الذي يستخدمه.
يعتمد scikit-learn بشكل عام على backend loky
، وهو backend الافتراضي لـ joblib. Loky هو backend متعدد العمليات. عند القيام
بالمعالجة المتعددة، من أجل تجنب تكرار الذاكرة في كل عملية
(الذي ليس معقولًا مع مجموعات البيانات الكبيرة)، سيقوم joblib بإنشاء memmap
يمكن لجميع العمليات مشاركته، عندما تكون البيانات أكبر من 1MB.
في بعض الحالات المحددة (عندما يطلق الكود الذي يتم تشغيله بالتوازي GIL)، سيشير scikit-learn إلى joblib
بأن backend متعدد الخيوط
يفضل.
كمستخدم، يمكنك التحكم في backend الذي سيستخدمه joblib (بغض النظر عن ما يوصي به scikit-learn) باستخدام context manager:
from joblib import parallel_backend
with parallel_backend('threading', n_jobs=2):
# Your scikit-learn code here
يرجى الرجوع إلى joblib's docs لمزيد من التفاصيل.
في الممارسة العملية، ما إذا كان التوازي مفيدًا في تحسين وقت التشغيل يعتمد على العديد من العوامل. عادة ما تكون فكرة جيدة للتجربة بدلاً من افتراض أن زيادة عدد العمال هي دائمًا أمر جيد. في بعض الحالات يمكن أن يكون ضارًا للغاية بالأداء لتشغيل عدة نسخ من بعض المقدرين أو الوظائف بالتوازي (انظر الإفراط في الاشتراك أدناه).
11.3.1.2. التوازي منخفض المستوى مع OpenMP#
يستخدم OpenMP لتوازي الكود المكتوب في Cython أو C، معتمدًا على التعدد الخيطي حصريًا. بشكل افتراضي، ستستخدم التنفيذات التي تستخدم OpenMP أكبر عدد ممكن من الخيوط، أي عدد الخيوط مثل وحدات المعالجة المركزية المنطقية.
يمكنك التحكم في العدد الدقيق للخيوط المستخدمة إما:
عبر متغير البيئة
OMP_NUM_THREADS
، على سبيل المثال عند: تشغيل نص برمجي بايثون:OMP_NUM_THREADS=4 python my_script.py
أو عبر
threadpoolctl
كما هو موضح بواسطة هذه القطعة من الوثائق.
11.3.1.3. الروتينات الموازية NumPy وSciPy من المكتبات العددية#
يعتمد scikit-learn بشكل كبير على NumPy وSciPy، والذي يستدعي داخليًا الروتينات الخطية المتوازية (BLAS & LAPACK) المنفذة في المكتبات مثل MKL وOpenBLAS أو BLIS.
يمكنك التحكم في العدد الدقيق للخيوط التي تستخدمها BLAS لكل مكتبة باستخدام متغيرات البيئة، على وجه التحديد:
MKL_NUM_THREADS
يحدد عدد الخيوط التي يستخدمها MKL،OPENBLAS_NUM_THREADS
يحدد عدد الخيوط التي يستخدمها OpenBLASBLIS_NUM_THREADS
يحدد عدد الخيوط التي يستخدمها BLIS
لاحظ أن BLAS & LAPACK يمكن أن تتأثر أيضًا بـ
OMP_NUM_THREADS
. للتحقق مما إذا كان هذا هو الحال في بيئتك،
يمكنك فحص كيفية تأثير عدد الخيوط المستخدمة بشكل فعال بواسطة هذه المكتبات
عند تشغيل الأمر التالي في محيط bash أو zsh
للقيم المختلفة لـ OMP_NUM_THREADS
:
OMP_NUM_THREADS=2 python -m threadpoolctl -i numpy scipy
ملاحظة
في وقت الكتابة (2022)، حزم NumPy وSciPy التي يتم توزيعها على pypi.org (أي تلك المثبتة عبر pip install
)
وعلى قناة conda-forge (أي تلك المثبتة عبر
conda install --channel conda-forge
) مرتبطة بـ OpenBLAS، بينما
حزم NumPy وSciPy packages الموزعة على قناة conda الافتراضية من Anaconda.org (أي تلك المثبتة عبر conda install
)
مرتبطة افتراضيًا بـ MKL.
11.3.1.4. الإفراط في الاشتراك: إنشاء الكثير من الخيوط#
من المستحسن عمومًا تجنب استخدام عدد أكبر بكثير من العمليات أو الخيوط من عدد وحدات المعالجة المركزية على الجهاز. يحدث الإفراط في الاشتراك عندما يقوم برنامج بتشغيل الكثير من الخيوط في نفس الوقت.
لنفترض أن لديك جهازًا به 8 وحدات معالجة مركزية. ضع في اعتبارك حالة تقوم فيها بتشغيل
GridSearchCV
(موازاة مع joblib)
مع n_jobs=8
على
HistGradientBoostingClassifier
(موازاة مع
OpenMP). ستقوم كل مثيل من
HistGradientBoostingClassifier
بإنشاء 8 خيوط
(نظرًا لأن لديك 8 وحدات معالجة مركزية). هذا ما مجموعه 8 * 8 = 64
خيوط، مما
يؤدي إلى الإفراط في الاشتراك في الموارد المادية لوحدات المعالجة المركزية وبالتالي
إلى زيادة وقت الجدولة.
يمكن أن يحدث الإفراط في الاشتراك بنفس الطريقة بالضبط مع الروتينات الموازية من MKL أو OpenBLAS أو BLIS المضمنة في مكالمات joblib.
بدءًا من joblib >= 0.14
، عندما يتم استخدام backend loky
(الذي
هو الافتراضي)، سيخبر joblib عملياته الفرعية بتحديد عدد الخيوط التي يمكنهم استخدامها، وذلك لتجنب الإفراط في الاشتراك. في الممارسة
الخوارزمية التي يستخدمها joblib هي إخبار العمليات باستخدام max_threads
= n_cpus // n_jobs
، عبر متغير البيئة الخاص بهم. عودة إلى
مثالنا من الأعلى، نظرًا لأن backend joblib من
GridSearchCV
هو loky
، فإن كل عملية ستتمكن
فقط من استخدام خيط واحد بدلاً من 8، وبالتالي التخفيف من مشكلة الإفراط في الاشتراك.
لاحظ أنه:
تعيين أحد متغيرات البيئة (
OMP_NUM_THREADS
،MKL_NUM_THREADS
،OPENBLAS_NUM_THREADS
، أوBLIS_NUM_THREADS
) سيكون له الأسبقية على ما يحاول joblib القيام به. سيكون العدد الإجمالي للخيوطn_jobs * <LIB>_NUM_THREADS
. لاحظ أن تعيين هذا الحد سيؤثر أيضًا على حساباتك في العملية الرئيسية، والتي ستستخدم فقط<LIB>_NUM_THREADS
. يعرض joblib context manager للتحكم الدقيق في عدد الخيوط في عماله (انظر وثائق joblib المرتبطة أدناه).عندما يتم تكوين joblib لاستخدام backend
threading
، لا توجد
آلية لتجنب الإفراط في الاشتراك عند استدعاء المكتبات الأصلية الموازية في الخيوط التي يديرها joblib. - جميع المقدرين في scikit-learn الذين يعتمدون صراحة على OpenMP في كود Cython الخاص بهم
يستخدمون
threadpoolctl
داخليًا لتكييف أعداد الخيوط المستخدمة بواسطة OpenMP وربما BLAS المضمنة بحيث يتم تجنب الإفراط في الاشتراك.
ستجد تفاصيل إضافية حول تخفيف joblib للإفراط في الاشتراك في وثائق joblib.
ستجد تفاصيل إضافية حول التوازي في المكتبات بايثون العددية في هذه الوثيقة من Thomas J. Fan.
11.3.2. مفاتيح التهيئة#
11.3.2.1. Python API#
يمكن استخدام sklearn.set_config
و sklearn.config_context
لتغيير
معلمات التهيئة التي تتحكم في جانب التوازي.
11.3.2.2. متغيرات البيئة#
يجب تعيين متغيرات البيئة هذه قبل استيراد scikit-learn.
11.3.2.2.1. SKLEARN_ASSUME_FINITE
#
يحدد القيمة الافتراضية لحجة assume_finite
لـ
sklearn.set_config
.
11.3.2.2.2. SKLEARN_WORKING_MEMORY
#
يحدد القيمة الافتراضية لحجة working_memory
لـ
sklearn.set_config
.
11.3.2.2.3. SKLEARN_SEED
#
يحدد البذرة للمولد العشوائي العالمي عند تشغيل الاختبارات، من أجل القابلية للتكرار.
لاحظ أنه من المتوقع أن تعمل اختبارات scikit-learn بشكل حتمي مع الاستخدام الصريح للبذور المستقلة الخاصة بها بدلاً من الاعتماد على مولدات الأرقام العشوائية للبايثون أو مكتبة المعايير القياسية للبايثون للتأكد من أن نتائج الاختبار مستقلة عن ترتيب تنفيذ الاختبار. ومع ذلك، قد تنسى بعض الاختبارات استخدام البذر الصريح وهذه المتغير هي وسيلة للتحكم في الحالة الأولية للمولدات المذكورة أعلاه.
11.3.2.2.4. SKLEARN_TESTS_GLOBAL_RANDOM_SEED
#
يتحكم في بذر مولد الأرقام العشوائية المستخدم في الاختبارات التي تعتمد على
الـ global_random_seed
fixture.
تعتمد جميع الاختبارات التي تستخدم هذا الـ fixture على العقد بأن تمر بشكل حتمي لأي قيمة بذرة من 0 إلى 99.
في عمليات بناء CI الليلية، يتم رسم SKLEARN_TESTS_GLOBAL_RANDOM_SEED
بيئة متغير عشوائيًا في النطاق المذكور أعلاه وتعمل جميع الاختبارات
التي تستخدم هذا الـ fixture على تلك البذرة المحددة. الهدف هو التأكد من أنه، مع مرور الوقت، سيقوم CI الخاص بنا بتشغيل
جميع الاختبارات مع بذور مختلفة مع الحفاظ على مدة الاختبار لتشغيل واحد
من مجموعة الاختبارات الكاملة محدودة. هذا سيتحقق من أن تأكيدات الاختبارات
التي تم كتابتها لاستخدام هذا الـ fixture ليست معتمدة على قيمة بذرة محددة.
يقتصر نطاق قيم البذور المقبولة على [0، 99] لأنه غالبًا ما يكون من غير الممكن كتابة اختبار يمكن أن يعمل لأي بذرة ممكنة ونريد تجنب وجود اختبارات تفشل عشوائيًا في CI.
القيم الصالحة لـ SKLEARN_TESTS_GLOBAL_RANDOM_SEED
:
SKLEARN_TESTS_GLOBAL_RANDOM_SEED="42"
: تشغيل الاختبارات مع بذرة ثابتة من 42SKLEARN_TESTS_GLOBAL_RANDOM_SEED="40-42"
: تشغيل الاختبارات مع جميع البذور بين 40 و 42.SKLEARN_TESTS_GLOBAL_RANDOM_SEED="all"
: تشغيل الاختبارات مع جميع البذور بين 0 و 99. يمكن أن يستغرق هذا وقتًا طويلاً: استخدمه فقط لاختبارات فردية، وليس لمجموعة الاختبارات الكاملة!
إذا لم يتم تعيين المتغير، فسيتم استخدام 42 كبذرة عالمية بطريقة حتمية. هذا يضمن أن مجموعة اختبارات scikit-learn تكون حتمية قدر الإمكان لتجنب إزعاج مُحسني الطرف الثالث الودودين لدينا. وبالمثل، لا ينبغي تعيين هذا المتغير في تكوين CI لسحب الطلبات للتأكد من أن مُحسنينا الودودين ليسوا أول من يواجه مشكلة حساسية البذور في اختبار غير مرتبط بتغييرات PR الخاصة بهم. فقط مُحسنو scikit-learn الذين يشاهدون نتائج عمليات البناء الليلية يتوقع أن يزعجهم هذا.
عند كتابة اختبار جديد يستخدم هذا الـ fixture، يرجى استخدام الأمر التالي للتأكد من أنه يمر بشكل حتمي لجميع البذور المقبولة على جهازك المحلي:
SKLEARN_TESTS_GLOBAL_RANDOM_SEED="all" pytest -v -k test_your_test_name
11.3.2.2.5. SKLEARN_SKIP_NETWORK_TESTS
#
عندما يتم تعيين هذه البيئة إلى قيمة غير صفرية، يتم تخطي الاختبارات التي تحتاج إلى الوصول إلى الشبكة. عندما لا يتم تعيين هذه البيئة، يتم تخطي اختبارات الشبكة.
11.3.2.2.6. SKLEARN_RUN_FLOAT32_TESTS
#
عندما يتم تعيين هذه البيئة إلى '1'، يتم أيضًا تشغيل الاختبارات التي تستخدم
global_dtype
fixture على بيانات float32.
عندما لا يتم تعيين هذه البيئة، يتم تشغيل الاختبارات فقط على
بيانات float64.
11.3.2.2.7. SKLEARN_ENABLE_DEBUG_CYTHON_DIRECTIVES
#
عندما يتم تعيين هذه البيئة إلى قيمة غير صفرية، يتم تعيين المشتق Cython
،
boundscheck
إلى True
. هذا مفيد للعثور على
segfaults.
11.3.2.2.8. SKLEARN_BUILD_ENABLE_DEBUG_SYMBOLS
#
عندما يتم تعيين هذه البيئة إلى قيمة غير صفرية، سيتم تضمين رموز التصحيح في الإضافات C المجمّعة. يتم تكوين رموز التصحيح فقط لأنظمة POSIX.
11.3.2.2.9. SKLEARN_PAIRWISE_DIST_CHUNK_SIZE
#
يحدد هذا حجم الجزء الذي سيتم استخدامه بواسطة التطبيقات الأساسية لـ PairwiseDistancesReductions
. القيمة الافتراضية هي 256
والتي ثبت أنها كافية على
معظم الأجهزة.
قد يرغب المستخدمون الذين يبحثون عن أفضل أداء في ضبط هذا المتغير باستخدام أسس 2 بحيث يحصلون على أفضل سلوك للتوازي لمعداتهم، خاصة فيما يتعلق بحجم ذاكرتهم المخبئية.
11.3.2.2.10. SKLEARN_WARNINGS_AS_ERRORS
#
يتم استخدام متغير البيئة هذا لتحويل التحذيرات إلى أخطاء في الاختبارات وبناء الوثائق.
يحدد بعض عمليات CI (Continuous Integration) SKLEARN_WARNINGS_AS_ERRORS=1
، على سبيل المثال للتأكد من أننا نلتقط تحذيرات الإلغاء التدريجي من التبعيات الخاصة بنا وأننا نكيف كودنا.
لتشغيل مع نفس إعداد "التحذيرات كأخطاء" كما في عمليات بناء CI هذه
يمكنك تعيين SKLEARN_WARNINGS_AS_ERRORS=1
.
بشكل افتراضي، لا يتم تحويل التحذيرات إلى أخطاء. هذا هو الحال إذا
تم إلغاء تعيين SKLEARN_WARNINGS_AS_ERRORS
، أو SKLEARN_WARNINGS_AS_ERRORS=0
.
يستخدم هذا متغير البيئة مرشحات تحذير محددة لتجاهل بعض التحذيرات،
نظرًا لأنه في بعض الأحيان تنشأ التحذيرات من مكتبات تابعة ولا يمكننا فعل الكثير حيالها. يمكنك الاطلاع على مرشحات التحذير في
الدالة _get_warnings_filters_info_list
في sklearn/utils/_testing.py
.
لاحظ أنه بالنسبة لبناء الوثائق، يتحقق SKLEARN_WARNING_AS_ERRORS=1
من أن بناء الوثائق، خاصة تشغيل الأمثلة، لا ينتج أي تحذيرات. هذا يختلف عن -W
sphinx-build
الذي يلتقط تحذيرات بناء الجملة في ملفات rst.