1 مقدمه

اشتراک منابع محاسباتی در یک سامانه محاسبات سریع یا ابررایانه توسط یک نرم افزار مدیریت منابع (resource manager) یا زمانبند کار (job scheduler) سازماندهی می شود. کاربران، کارها (Jobs) را به نرم افزار مدیریت منابع ارسال کرده و این نرم افزار منابع (زمان استفاده از پردازنده ، مقدار حافظه و غیره) را به آنها اختصاص داده می دهد.

نرم افزارSlurm  یک نرم افزار مدیریت منابع و زمانبند کار است که برای مدیریت کارها و منابع طراحی شده است.

2 جمع آوری اطلاعات

نرم افزار SLURM دستورات زیادی را برای تعامل کاربر با سیستم ارائه می دهد. به عنوان مثال، دستور  sinfo  منابع ارائه شده توسط سامانه را نمایش می‌دهد، در حالی که دستور  squeue  نشان می دهد که در حال حاضر به چه کارهایی منابع اختصاص داده شده است.

به طور پیش فرض  sinfo  پارتیشن (partition) های موجود را لیست می کند. یک پارتیشن (partition) مجموعه ای از گره های محاسباتی (رایانه های اختصاص داده شده به یک کار محاسباتی) است که بنا به کاربرد و نوع منابع گروه بندی می شوند. نمونه این پارتیشن ها شامل پارتیشن های اختصاصی به پردازش دسته‌ای ، اشکال زدایی ، پس پردازش (Post Proceess) یا نمایش نتایج (Visualization) می‌باشد.

$ sinfo
PARTITION AVAIL TIMELIMIT NODES STATE  NODELIST
batch     up     infinite     2 alloc  node[8-9]
batch     up     infinite     6 idle   node[10-15]
debug*    up        30:00     8 idle   node[0-7]

در مثال فوق دو پارتیشن با نامهای batch و debug را می بینیم. پارتیشن debug به عنوان پارتیشن پیش فرض انتخاب و با ستاره مشخص شده است. همه گره های پارتیشن debug بیکار هستند ، در حالی که دو گره در پارتیشن batch در حال استفاده می باشند.

دستور   sinfo  همچنین محدودیت زمانی (ستون TIMELIMIT) که کارها برای اجرا در آن پارتیشن دارند، را نمایش می دهد. در هر پارتیشن، کارها به زمان اجرای ارائه شده در ستون TIMELIMIT محدود می شوند. محدود کردن زمان اجرا این امکان را به کاربران دیگر می دهد تا کارهای خود به مرحله اجرا برسانند. معمولاً هرچه سامانه محاسبات سریع بزرگتر باشد ، حداکثر زمان مجاز کوچکتر است.

دستور   sinfo  می‌تواند اطلاعات را به صورت گره محور و افزودن پارامتر   -N  نمایش دهد.

$ sinfo -N -l
NODELIST    NODES PARTITION STATE  CPUS MEMORY TMP_DISK WEIGHT FEATURES REASON
node[0-1]       2 debug*    idle      2   3448    38536     16 (null)   (null)
node[2,4-7]     5 debug*    idle      2   3384    38536     16 (null)   (null)
node3           1 debug*    idle      2   3394    38536     16 (null)   (null)
node[8-9]       2 batch     allocated 2    246    82306     16 (null)   (null)
node[10-15]     6 batch     idle      2    246    82306     16 (null)   (null)

توجه داشته باشید که با افزودن پارامتر   -l  اطلاعات بیشتری در مورد گره‌ها ارائه داده می شود: تعداد پردازنده‌ها، حافظه، دیسک موقتی (همچنین به آن فضای scratch گفته می شود)، وزن گره (در هنگام وجود گزینه‌های مختلف، این پارامتر داخلی، اجرای کار را به گره‌های مشخصی اختصاص می‌دهد) ، ویژگی‌های گره‌ها (مانند نمونه پردازنده) و دلیل عدم کارکرد گره.

کاربر در واقع می تواند با استفاده از پارامتر   -format ، دقیقاً مشخص کند که چه اطلاعاتی را دستور   sinfo  نمایش دهد. برای دریافت اطلاعات بیشتر ، به صفحه کمک این دستور با اجرای فرمان   man sinfo  رجوع کنید.

فرمان   squeue  لیستی از کارهای موجود در سامانه پردازش سریع را نشان می دهد (اینکه کارها در حالت RUNNING قرار دارند ، با نماد “R” نمایش داده شده است یا منتظر آزاد سازی منابع هستند (نماد “PD” ، مختص PENDING نمایش داده شده است).

$  squeue
JOBID PARTITION NAME USER ST  TIME  NODES NODELIST(REASON)
12345     debug job1 dave  R   0:21     4 node[9-12]
12346     debug job2 dave PD   0:00     8 (Resources)
12348     debug job3 ed   PD   0:00     4 (Priority)

خروجی فوق نشان می دهد که یک کار در حال اجرا است ، نام آن job1  است و شناسه کار آن 12345 است. jobid یک شناسه منحصر به فرد است که در هنگام انجام اقدامات مربوط به یک کار خاص ، توسط بسیاری از دستورات Slurm مورد استفاده قرار می گیرد. به عنوان مثال ، برای لغو کار job1 ، از فرمان   scancel 12345  استفاده می شود. مفهوم هر یک از ستون های خروجی در ادامه آورده شده است:

  • TIMEزمانی اجرای کار تاکنون است.
  • NODES تعداد گره هایی است که به آن کار اختصاص داده شده است.
  • NODELISTگره های اختصاص داده شده برای اجرای آن کار. در مورد کارهای در حال تعلیق ، این ستون دلیل تعلیق کار را نشان می دهد.

در مثال فوق، کار  12346در حال تعلیق است زیرا منابع درخواستی (CPU یا موارد دیگر) به اندازه کافی در دسترس نیست، در حالی که کار 12348 منتظر کار 12346 است، چون اولویت پایین تری دارد. در واقع هر کار بسته به پارامترهای مختلفی در اولویت قرار می گیرد. توجه داشته باشید که اولویت برای کارهای در حال تعلیق با دستور   spiro  قابل دستیابی است.

پارامترهای بسیاری وجود دارد که می توانید برای فیلتر کردن خروجی از آنها استفاده کنید و خروجی را بر اساس کاربر   --user  ، بر اساس پارتیشن    --partition ، بر اساس وضعیت    --state  و غیره دسته بندی کنید. همچنین همانطور که در دستور   sinfo  خروجی را با   --format  می توانید شکل دهید، در دستور   spiro   هم میتوانید از این پارامتر استفاده کنید.

3 نحوه ایجاد کار

حال سوال اینجاست: چگونه یک کار به سامانه ارسال کنیم؟

اجرای یک کار شامل دو بخش است: اول درخواست منابع و دوم اجرای مراحل کار. درخواست منابع، شامل تعیین تعداد هسته ها، محاسبه مدت زمان مورد انتظار ، مقدار حافظه رم یا فضای دیسک مورد نیاز، و سایر موارد دیگر می‌باشد. مراحل اجرای کار نیز شامل اجرای taskها و اجرای نرم‌افزارها می‌باشد.

روش معمول ایجاد کار  نوشتن یک اسکریپت (script) است. این اسکریپت یک shell script  لینوکسی است. به عنوان مثال می‌توان از اسکریپت Bash استفاده کرد که کامنت های (comments) آن در صورت شروع با عبارت SBATCH ، توسط Slurm به عنوان پارامترهای درخواست منابع قابل درک است. شما می توانید لیست کاملی از این پارامترها را با اجرای دستور   man sbatch  مشاهده کنید.

پارامترها و دستورالعمل‌های SBATCH می بایست در ابتدای فایل اسکریپت، قبل از هر دستور دیگری قرار گیرند. البته همیشه اولین خط یک فایل اسکریپت شامل عبارت   #!/bin/bash  می‌باشد.

خود اسکریپت یک مرحله از اجرا کار (job step) می‌باشد و مراحل دیگر اجرای کار با دستور   srun  اجرا می‌شوند.

به عنوان مثال، اسکریپت زیر به نام فایل submit.sh ذخیره شده است:

#!/bin/bash
#
#SBATCH --job-name=test
#SBATCH --output=res.txt
#
#SBATCH --ntasks=1
#SBATCH --time=10:00
#SBATCH --mem-per-cpu=100

srun hostname
srun sleep 60

این اسکریپت، یک کار جدید به صف پیش فرض ارسال می کند که یک پردازنده را به مدت 10 دقیقه به همراه 100 مگابایت رم درخواست کرده است. با شروع کار ، در اولین مرحله   srun hostname  اجرا می شود، که دستور   hostname  را در گره‌ای که منابع درخواستی در آن اختصاص داده شده است اجرا می کند. سپس ، مرحله دوم کار اجرا می‌شود، دستور   sleep  را شروع می کند. توجه داشته باشید که پارامتر   --job-name  به شما امکان اسم گذاری کار را می‌دهد و پارامتر   --output  فایلی را که خروجی باید در آن قرار گیرد تعریف می کند.

هنگامی که اسکریپت به درستی نوشته شده باشد، آنرا از طریق دستور   sbatch  به slurm ارسال می‌کنیم. در صورت اجرا درست دستور   sbatch  ، بوسیله پارامتر jobid پاسخ می دهد.

$ sbatch submit.sh
sbatch: Submitted batch job 99999999

سپس این کار در حالت PENDING وارد صف می شود. هنگامی که منابع در دسترس قرار گرفت و کار به بالاترین اولویت در صف رسید، تخصیصی منابع برای آن کار انجام می‌شود و کار در حالت RUNNING قرار می‌گیرد. اگر کار به درستی اجرا شود، در نهایت به حالت COMPLETED قرار خواهد گرفت، در غیر این صورت ، به حالت FAILED تغییر وضعیت خواهد داد.

نکته قابل توجه آن است که شما می توانید با اجرای دستور   sstat –j jobid  ، اطلاعات مربوط به کار در حال اجرا (تعداد هسته، حافظه و غیره) را مشاهده کنید. با پارامتر   --format  می توانید خروجی دستور   sstat  را شخصی سازی کنید. برای اطلاعات بیشتر دستور   man sstat  را اجرا کنید.

پس از اتمام کار، فایل خروجی حاوی نتیجه دستوراتی است که در فایل اسکریپت قرار گرفته است. در مثال بالا می توانید فایل مورد نظر را با دستور   cat res.txt  مشاهده کنید.

مثال فوق یک کار که بصورت سریال (serial) اجرا شده را توضیح داده است. در واقع کار را روی یک CPU بروی یک گره محاسباتی اجرا گردیده است. از گره‌های چند پردازنده یا تعداد بیشتری گره محاسباتی استفاده نشده است. در بخش بعدی نحوه ایجاد کارهای موازی (parallel) توضیح داده شده است.

4 اجراهای موازی کارها

چندین روش مختلف وجود دارد که می توان یک کار موازی را ایجاد کرد:

  • اجرای یک برنامه چند پروسه‌ای (multi-process) (الگوی SPMD ، به عنوان مثال با MPI)
  • اجرای یک برنامه چند رشته ای (multi-thread) (الگوی حافظه مشترک ، به عنوان مثال با OpenMP یا pthreads)
  • اجرای چندین نمونه از یک برنامه تک‌رشته‌ای (single-threaded) (به اصطلاح embarrassingly parallel و job array)
  • اجرای یک برنامه اصلی که چندین برنامه دیگر را در کنترل خود دارد (master/slave)

در Slurm ، یک task باید به عنوان یک پروسه process در نظر گرفته شود. بنابراین یک برنامه چند پروسه‌ای (multi-process) از چندین task ساخته شده است. در مقابل، یک برنامه چند رشته ای (multi-thread) تنها از یک task تشکیل شده است، و آن task از چندین CPU استفاده می کند.

Taskها بوسیله پارامتر   --ntasks  درخواست و یا ایجاد می‌شوند، در حالی که CPU ها، دربرنامه‌های چندرشته‌ای (multi-thread)، با گزینه   --cpu-per-task  درخواست می شود. Task ها را نمی‌توان بین چند گره محاسباتی تقسیم کرد، بنابراین درخواست چندین CPU با گزینه   --cpu-per-task  این امکان را می‌دهد تا تمامی CPU ها در یک گره محاسباتی اختصاص داده شوند. در مقابل، درخواست همان مقدار CPU با گزینه   --ntasks  ممکن است منجر به اختصاص چندین CPU در چندین گره محاسباتی مجزا شود.

5 نمونه های اسکریپت‌های قابل ارسال

در اینجا چندین نمونه اسکریپت قابل ارسال به سامانه را مورد بررسی قرار می دهیم. برای کسب اطلاعات دقیق تر ، حتماً به Slurm FAQ مراجعه کنید.

5-1 مثال MPI

#!/bin/bash
#
#SBATCH --job-name=test_mpi
#SBATCH --output=res_mpi.txt
#
#SBATCH --ntasks=4
#SBATCH --time=10:00
#SBATCH --mem-per-cpu=100

module load OpenMPI
srun hello.mpi

این اسکریپت اجرا به مدت 10 دقیقه روی چهار هسته را از سامانه درخواست می‌کند و از 100 مگابایت رم در هر هسته استفاده کنید. با فرض اینکه فایل   hello.mpi  با پشتیبانی MPI کامپایل شده باشد، دستور   srun  چهار instance از این برنامه را بر روی گره‌های اختصاص داده شده توسط Slurm ایجاد می کند.

می توانید مثال بالا را با دانلود برنامه hello world از ویکی پدیا امتحان کنید (نام آن را به wiki_mpi_example.c تغییر دهید) و آن را بصورت زیر کامپایل کنید:

module load openmpi
mpicc wiki_mpi_example.c -o hello.mpi

فایل res_mpi.txt باید بصورت زیر باشد:

0: We have 4 processors
0: Hello 1! Processor 1 reporting for duty

0: Hello 2! Processor 2 reporting for duty

0: Hello 3! Processor 3 reporting for duty

5-2 مثال حافظه مشترک (OpenMP)

#!/bin/bash
#
#SBATCH --job-name=test_omp
#SBATCH --output=res_omp.txt
#
#SBATCH --ntasks=1
#SBATCH --cpus-per-task=4
#SBATCH --time=10:00
#SBATCH --mem-per-cpu=100

export OMP_NUM_THREADS=$SLURM_CPUS_PER_TASK
./hello.omp

در این اسکریپت چهار هسته در یک گره محاسباتی برای اجرای کار درخواست شده است.

می توانید مثال بالا را با دانلود برنامه hello world از ویکی پدیا امتحان کنید و آن را بصورت زیر کامپایل کنید:

gcc -fopenmp wiki_omp_example.c -o hello.omp

فایل res_mpi.txt باید بصورت زیر باشد:

Hello World from thread 0
Hello World from thread 3
Hello World from thread 1
Hello World from thread 2
There are 4 threads

5-3 مثال Embarrassingly parallel workload

این روش برای حل مسائل مبتنی بر قرعه کشی های تصادفی random draws (به عنوان مثال شبیه سازی مونت کارلو) مفید است. به عنوان مثال می توان چهار برنامه برای ترسیم 1000 نمونه تصادفی داشته باشید و پس از پایان اجرا خروجی ها را با هم ترکیب کنید.

یکی دیگر از کاربردهای این روش حل مسئله، parameter sweep است. در این حالت ، یک محاسبه چندین بار توسط یک کد مشخص انجام می شود و فقط در پارامتر های مقدار اولیه و یا پارامترهای خاص برای هر اجرا متفاوت است. به عنوان مثال از این روش برای بهینه سازی یک integer-value parameter از طریق range scanning باشد:

#!/bin/bash
#
#SBATCH --job-name=test_emb_arr
#SBATCH --output=res_emb_arr.txt
#
#SBATCH --ntasks=1
#SBATCH --time=10:00
#SBATCH --mem-per-cpu=100
#
#SBATCH --array=1-8

srun ./my_program.exe $SLURM_ARRAY_TASK_ID

در آن پیکربندی ، دستور   myprogram.exe  هشت بار اجرا می شود و هشت کار مجزا ایجاد می کند ، هر بار با یک پارامتر متفاوت که همراه با متغیر محیط به آن کار منتقل می شود و توسط slurm SLURM_ARRAY_TASK_ID از 1 تا 8 اجرا می شود.

همین روش را می توان برای پردازش چندین فایل داده استفاده کرد. به instance های مختلف برنامه، باید فایل متفاوتی ارجاع داده شود. که این کار بر اساس پارامتر محیطی   $SLURM_ *  انجام می‌گیرد. به عنوان مثال، با فرض اینکه دقیقاً هشت فایل در   /path/to/data  وجود داشته باشد، می توانیم اسکریپت زیر را ایجاد کنیم:

#!/bin/bash
#
#SBATCH --job-name=test_emb_arr
#SBATCH --output=res_emb_arr.txt
#
#SBATCH --ntasks=1
#SBATCH --time=10:00
#SBATCH --mem-per-cpu=100
#
#SBATCH --array=0-7

FILES=(/path/to/data/*)

srun ./my_program.exe ${FILES[$SLURM_ARRAY_TASK_ID]}

در این حالت ، هشت کار به slurm ارجاع داده می شود ، که هر یک از کارها نام متفاوت دارند و به عنوان پارامتر که در آرایه   FILES[]  تعیین شده است به برنامه   myprogram.exe  ارسال شده‌اند. از آنجا که آرایه   FILES[]  با ایندکس صفر است ، شناسه های کار Slurm نیز باید از 0 شروع شوند بنابراین پارامتر   --aray=0-7  به پارامتر‌ها اضافه می‌شود. یک نکته مهم آن است که تعداد فایلهای موجود در پوشه باید با تعداد کارها موجود در آرایه مطابقت داشته باشد.

توجه داشته باشید برای پارامتر های عددی که integer پشت سر هم نیستند نیز از همین روش و با تعریف   ARGS[]  که حاوی مقادیر مورد نظر است می توان استفاده کرد.

ARGS=(0.05 0.25 0.5 1 2 5 100)

srun ./my_program.exe ${ARGS[$SLURM_ARRAY_TASK_ID]}

در اینجا نیز، شماره گذاری آرایه کار Slurm باید از 0 شروع شود تا اطمینان حاصل شود که تمام موارد در آرایه   ARGS[] پردازش شده اند.