Skip to content

Latest commit

 

History

History
509 lines (318 loc) · 28.4 KB

File metadata and controls

509 lines (318 loc) · 28.4 KB
jupytext
text_representation
extension format_name
.md
myst
kernelspec
display_name language name
Python 3
python
python3
translation
title headings
پایتون برای محاسبات علمی
Overview Major Scientific Libraries Major Scientific Libraries::Why do we need them? Major Scientific Libraries::Python's Scientific Ecosystem Pure Python is slow Pure Python is slow::High vs low level code Pure Python is slow::Where are the bottlenecks? Pure Python is slow::Where are the bottlenecks?::Dynamic typing Pure Python is slow::Where are the bottlenecks?::Static types Pure Python is slow::Data Access Pure Python is slow::Data Access::Summing with Compiled Code Pure Python is slow::Data Access::Summing in Pure Python Pure Python is slow::Summary Accelerating Python Accelerating Python::Vectorization Accelerating Python::Vectorization vs for pure Python loops Accelerating Python::JIT compilers Parallelization Parallelization::Parallelization on CPUs Parallelization::Parallelization on CPUs::Multiprocessing Parallelization::Parallelization on CPUs::Multithreading Parallelization::Parallelization on CPUs::Advantages and Disadvantages Parallelization::Hardware Accelerators Parallelization::Hardware Accelerators::GPUs and TPUs Parallelization::Hardware Accelerators::Why TPUs/GPUs Matter Parallelization::Single GPUs vs GPU Servers Parallelization::Single GPUs vs GPU Servers::Single GPU Systems Parallelization::Single GPUs vs GPU Servers::Multi-GPU Servers Parallelization::Summary
مرور کلی
کتابخانه‌های علمی اصلی
چرا به آنها نیاز داریم؟
اکوسیستم علمی پایتون
پایتون خالص کند است
کد سطح بالا در مقابل سطح پایین
گلوگاه‌ها کجا هستند؟
تایپ کردن پویا
انواع ایستا
دسترسی به داده
جمع کردن با کد کامپایل شده
جمع کردن در پایتون خالص
خلاصه
تسریع پایتون
برداری‌سازی
برداری‌سازی در مقابل حلقه‌های پایتون خالص
کامپایلرهای JIT
موازی‌سازی
موازی‌سازی بر روی CPUها
چندپردازشی
چندرشته‌ای
مزایا و معایب
شتاب‌دهنده‌های سخت‌افزاری
GPUها و TPUها
چرا TPU/GPU مهم هستند
GPUهای تکی در مقابل سرورهای GPU
سیستم‌های GPU تکی
سرورهای چند GPU
خلاصه

(speed)=

<div id="qe-notebook-header" align="right" style="text-align:right;">
        <a href="https://quantecon.org/" title="quantecon.org">
                <img style="width:250px;display:inline;" width="250px" src="https://assets.quantecon.org/img/qe-menubar-logo.svg" alt="QuantEcon">
        </a>
</div>

پایتون برای محاسبات علمی

"ما باید کارایی‌های کوچک را فراموش کنیم، بگوییم حدود 97 درصد از زمان:
بهینه‌سازی زودرس ریشه همه بدی‌هاست." -- دونالد کنوت

مرور کلی

پایتون محبوب‌ترین زبان برای بسیاری از جنبه‌های محاسبات علمی است.

این به دلیل موارد زیر است:

  • ماهیت قابل دسترس و گویا بودن خود زبان،
  • دامنه عظیم کتابخانه‌های علمی با کیفیت بالا،
  • این واقعیت که زبان و کتابخانه‌ها متن‌باز هستند،
  • نقش محوری که پایتون در علم داده، یادگیری ماشین و هوش مصنوعی ایفا می‌کند.

در سخنرانی‌های قبلی، از برخی کتابخانه‌های علمی پایتون، از جمله NumPy و Matplotlib استفاده کردیم.

با این حال، تمرکز اصلی ما بر زبان پایتون بود، نه کتابخانه‌ها.

اکنون به کتابخانه‌های علمی می‌پردازیم و تمام توجه خود را به آنها معطوف می‌کنیم.

در این سخنرانی مقدماتی، موضوعات زیر را مورد بحث قرار خواهیم داد:

  1. عناصر اصلی اکوسیستم علمی پایتون چیست؟
  2. آنها چگونه با هم جور می‌شوند؟
  3. وضعیت در طول زمان چگونه در حال تغییر است؟

علاوه بر آنچه در Anaconda موجود است، این سخنرانی به موارد زیر نیاز دارد:

---
tags: [hide-output]
---
!pip install quantecon

بیایید با برخی import ها شروع کنیم:

import numpy as np
import quantecon as qe
import matplotlib.pyplot as plt
import random

کتابخانه‌های علمی اصلی

بیایید به طور خلاصه کتابخانه‌های علمی پایتون را مرور کنیم.

چرا به آنها نیاز داریم؟

ما به کتابخانه‌های علمی پایتون به دو دلیل نیاز داریم:

  1. پایتون کوچک است
  2. پایتون کند است

پایتون کوچک است

هسته پایتون به طور عمدی کوچک طراحی شده است -- این به بهینه‌سازی، دسترسی‌پذیری و نگهداری کمک می‌کند.

کتابخانه‌های علمی روال‌هایی را فراهم می‌کنند که نمی‌خواهیم -- و احتمالاً نباید -- خودمان بنویسیم.

  • انتگرال‌گیری عددی، درونیابی، جبر خطی، یافتن ریشه و غیره.

پایتون کند است

دلیل دیگری که به کتابخانه‌های علمی نیاز داریم این است که پایتون خالص نسبتاً کند است.

کتابخانه‌های علمی اجرا را با استفاده از سه استراتژی اصلی تسریع می‌کنند:

  1. برداری‌سازی: فراهم کردن کد ماشین کامپایل‌شده و رابط‌هایی که این کد را قابل دسترس می‌کنند
  2. کامپایل JIT: کامپایلرهایی که دستورات شبیه پایتون را در زمان اجرا به کد ماشین سریع تبدیل می‌کنند
  3. موازی‌سازی: توزیع وظایف در چندین thread / CPU / GPU / TPU

ما این ایده‌ها را در ادامه به تفصیل بحث خواهیم کرد.

اکوسیستم علمی پایتون

در QuantEcon، کتابخانه‌های علمی که بیشترین استفاده را از آنها می‌کنیم عبارتند از:

اینگونه با هم جور می‌شوند:

  • NumPy با ارائه یک نوع داده آرایه‌ای پایه (به بردارها و ماتریس‌ها فکر کنید) و توابعی برای عمل بر روی این آرایه‌ها (به عنوان مثال، ضرب ماتریسی)، پایه‌ها را تشکیل می‌دهد.
  • SciPy بر روی NumPy با افزودن روش‌های عددی که به طور معمول در علم استفاده می‌شوند (درونیابی، بهینه‌سازی، یافتن ریشه و غیره) ساخته می‌شود.
  • Matplotlib برای تولید شکل‌ها، با تمرکز بر رسم داده‌های ذخیره شده در آرایه‌های NumPy استفاده می‌شود.
  • JAX شامل عملیات پردازش آرایه مشابه NumPy، مشتق‌گیری خودکار، یک کامپایلر just-in-time با محوریت موازی‌سازی، و یکپارچه‌سازی خودکار با شتاب‌دهنده‌های سخت‌افزاری مانند GPUها است.
  • Pandas انواع و توابعی را برای دستکاری داده‌ها فراهم می‌کند.
  • Numba یک کامپایلر just-in-time فراهم می‌کند که با NumPy به خوبی کار می‌کند و به تسریع کد پایتون کمک می‌کند.

ما همه این کتابخانه‌ها را به تفصیل در این مجموعه درس‌ها مورد بحث قرار خواهیم داد.

پایتون خالص کند است

همانطور که در بالا ذکر شد، یکی از جاذبه‌های اصلی کتابخانه‌های علمی، سرعت اجرای بیشتر است.

ما بحث خواهیم کرد که چگونه کتابخانه‌های علمی می‌توانند به ما در تسریع کد کمک کنند.

برای این موضوع، مفید خواهد بود اگر درک کنیم که چه چیزی باعث سرعت اجرای کند می‌شود.

کد سطح بالا در مقابل سطح پایین

زبان‌های سطح بالاتر مانند پایتون برای انسان‌ها بهینه شده‌اند.

این بدان معنی است که برنامه‌نویس می‌تواند بسیاری از جزئیات را به محیط زمان اجرا واگذار کند:

  • مشخص کردن انواع متغیرها
  • تخصیص/آزادسازی حافظه
  • و غیره.

علاوه بر این، پایتون خالص توسط یک مفسر اجرا می‌شود که کد را دستور به دستور اجرا می‌کند.

این پایتون را انعطاف‌پذیر، تعاملی، آسان برای نوشتن، آسان برای خواندن و نسبتاً آسان برای اشکال‌زدایی می‌کند.

از طرف دیگر، پیاده‌سازی استاندارد پایتون (به نام CPython) نمی‌تواند با سرعت زبان‌های کامپایل شده مانند C یا Fortran برابری کند.

گلوگاه‌ها کجا هستند؟

چرا اینطور است؟

تایپ کردن پویا

این عملیات پایتون را در نظر بگیرید:

a, b = 10, 10
a + b

حتی برای این عملیات ساده، مفسر پایتون کار زیادی برای انجام دادن دارد.

به عنوان مثال، در دستور a + b، مفسر باید بداند کدام عملیات را فراخوانی کند.

اگر a و b رشته باشند، آنگاه a + b نیاز به الحاق رشته دارد:

a, b = 'foo', 'bar'
a + b

اگر a و b لیست باشند، آنگاه a + b نیاز به الحاق لیست دارد:

a, b = ['foo'], ['bar']
a + b

در نتیجه، هنگام اجرای a + b، پایتون ابتدا باید نوع اشیا را بررسی کند و سپس عملیات صحیح را فراخوانی کند.

این شامل سربار می‌شود.

اگر ما بارها و بارها این عبارت را در یک حلقه تنگ اجرا کنیم، سربار بزرگ می‌شود.

انواع ایستا

زبان‌های کامپایل شده از این سربارها با انواع صریح و ایستا اجتناب می‌کنند.

به عنوان مثال، کد C زیر را در نظر بگیرید که اعداد صحیح از 1 تا 10 را جمع می‌کند:

:class: no-execute

#include <stdio.h>

int main(void) {
    int i;
    int sum = 0;
    for (i = 1; i <= 10; i++) {
        sum = sum + i;
    }
    printf("sum = %d\n", sum);
    return 0;
}

متغیرهای i و sum به صراحت به عنوان اعداد صحیح اعلام شده‌اند.

علاوه بر این، وقتی یک دستور مانند int i را می‌دهیم، ما به کامپایلر وعده می‌دهیم که i همیشه یک عدد صحیح خواهد بود، در طول اجرای برنامه.

به این ترتیب، معنای جمع در عبارت sum + i کاملاً بدون ابهام است.

نیازی به بررسی نوع نیست و بنابراین سربار وجود ندارد.

دسترسی به داده

یکی دیگر از موانع سرعت برای زبان‌های سطح بالا، دسترسی به داده است.

برای توضیح، بیایید مسئله جمع کردن برخی داده‌ها را در نظر بگیریم --- بگویید، مجموعه‌ای از اعداد صحیح.

جمع کردن با کد کامپایل شده

در C یا Fortran، یک آرایه از اعداد صحیح در یک بلوک پیوسته واحد از حافظه ذخیره می‌شود:

  • به عنوان مثال، یک عدد صحیح 64 بیتی در 8 بایت حافظه ذخیره می‌شود.
  • یک آرایه از $n$ چنین اعداد صحیحی $8n$ شکاف حافظه متوالی اشغال می‌کند.

علاوه بر این، نوع داده در زمان کامپایل مشخص است.

از این رو، هر نقطه داده متوالی می‌تواند با جابجایی رو به جلو در فضای حافظه به میزان مشخص و ثابتی دسترسی پیدا کند.

جمع کردن در پایتون خالص

پایتون سعی می‌کند این ایده‌ها را تا حدی تکرار کند.

به عنوان مثال، در پیاده‌سازی استاندارد پایتون (CPython)، عناصر لیست در مکان‌های حافظه قرار می‌گیرند که به نوعی پیوسته هستند.

با این حال، این عناصر لیست بیشتر شبیه اشاره‌گرها به داده‌ها هستند تا خود داده‌های واقعی.

از این رو، هنوز سربار در دسترسی به خود مقادیر داده وجود دارد.

چنین سرباری یک مجرم اصلی است وقتی صحبت از اجرای کند می‌شود.

خلاصه

آیا بحث بالا به این معنی است که ما باید برای همه چیز به C یا Fortran تغییر مکان دهیم؟

پاسخ این است: قطعاً نه!

برای هر برنامه داده شده، خطوط نسبتاً کمی همیشه بحرانی از نظر زمان خواهند بود.

از این رو بسیار کارآمدتر است که بیشتر کد خود را در یک زبان با بهره‌وری بالا مانند پایتون بنویسیم.

علاوه بر این، حتی برای خطوط کدی که هستند بحرانی از نظر زمان، اکنون می‌توانیم با استفاده از کتابخانه‌های علمی پایتون با فایل‌های باینری کامپایل شده از C یا Fortran برابری یا از آنها پیشی بگیریم.

در این زمینه، ما تأکید می‌کنیم که، در چند سال گذشته، تسریع کد اساساً مترادف با موازی‌سازی شده است.

این کار بهترین است که به کامپایلرهای تخصصی واگذار شود!

تسریع پایتون

در این بخش به سه تکنیک مرتبط برای تسریع کد پایتون نگاه می‌کنیم.

در اینجا بر ایده‌های بنیادی تمرکز خواهیم کرد.

بعداً به کتابخانه‌های خاص و نحوه پیاده‌سازی این ایده‌ها توسط آنها نگاه خواهیم کرد.

{index}برداری‌سازی <single: Vectorization>

یکی از روش‌ها برای اجتناب از ترافیک حافظه و بررسی نوع، برنامه‌نویسی آرایه‌ای است.

بسیاری از اقتصاددانان معمولاً به برنامه‌نویسی آرایه‌ای به عنوان "برداری‌سازی" اشاره می‌کنند.

در علوم کامپیوتر، این اصطلاح [معنای کمی متفاوت](https://en.wikipedia.org/wiki/Automatic_vectorization) دارد.

ایده کلیدی این است که عملیات پردازش آرایه را به صورت دسته‌ای به کد ماشین بومی از پیش کامپایل شده و کارآمد ارسال کنیم.

خود کد ماشین معمولاً از C یا Fortran که به دقت بهینه شده‌اند کامپایل می‌شود.

به عنوان مثال، هنگام کار در یک زبان سطح بالا، عملیات معکوس کردن یک ماتریس بزرگ می‌تواند به کد ماشین کارآمد که از پیش برای این منظور کامپایل شده و به عنوان بخشی از یک بسته به کاربران ارائه شده است، پیمانکاری شود.

مزایای اصلی عبارتند از:

  1. بررسی نوع به ازای هر آرایه پرداخت می‌شود، نه به ازای هر عنصر، و
  2. آرایه‌های حاوی عناصر با یک نوع داده از نظر دسترسی به حافظه کارآمد هستند.

ایده برداری‌سازی به MATLAB برمی‌گردد که به طور گسترده از برداری‌سازی استفاده می‌کند.

NumPy از مدلی مشابه استفاده می‌کند که از MATLAB الهام گرفته است.

برداری‌سازی در مقابل حلقه‌های پایتون خالص

بیایید یک مقایسه سریع سرعت را امتحان کنیم تا نشان دهیم چگونه برداری‌سازی می‌تواند کد را تسریع کند.

در اینجا مقداری کد غیر برداری شده وجود دارد که از یک حلقه بومی پایتون برای تولید، مجذور کردن و سپس جمع کردن تعداد زیادی متغیر تصادفی استفاده می‌کند:

n = 1_000_000
with qe.Timer():
    y = 0      # Will accumulate and store sum
    for i in range(n):
        x = random.uniform(0, 1)
        y += x**2

کد برداری شده زیر از NumPy استفاده می‌کند که به زودی آن را به تفصیل بررسی خواهیم کرد، تا همان کار را انجام دهد.

with qe.Timer():
    x = np.random.uniform(0, 1, n)
    y = np.sum(x**2)

همانطور که می‌بینید، بلوک کد دوم بسیار سریعتر اجرا می‌شود.

این حلقه را به سه عملیات اساسی تقسیم می‌کند:

  1. n uniform را بکشید
  2. آنها را مجذور کنید
  3. آنها را جمع کنید

اینها به عنوان عملگرهای دسته‌ای به کد ماشین بهینه شده ارسال می‌شوند.

(numba-p_c_vectorization)=

کامپایلرهای JIT

در بهترین حالت، برداری‌سازی کد سریع و ساده ارائه می‌دهد.

با این حال، بدون معایب نیست.

یکی از مسائل این است که می‌تواند بسیار فشرده از نظر حافظه باشد.

این به این دلیل است که برداری‌سازی تمایل به ایجاد آرایه‌های میانی زیادی قبل از تولید محاسبه نهایی دارد.

مسئله دیگر این است که همه الگوریتم‌ها نمی‌توانند برداری شوند.

به دلیل این مسائل، بیشتر محاسبات با کارایی بالا از برداری‌سازی سنتی دور می‌شوند و به سمت استفاده از کامپایلرهای just-in-time حرکت می‌کنند.

در سخنرانی‌های بعدی در این مجموعه، در مورد چگونگی بهره‌برداری کتابخانه‌های مدرن پایتون از کامپایلرهای just-in-time برای تولید کد ماشین سریع، کارآمد و موازی یاد خواهیم گرفت.

موازی‌سازی

رشد سرعت کلاک CPU (یعنی سرعتی که یک زنجیره منفرد منطقی می‌تواند اجرا شود) در سال‌های اخیر به طور چشمگیری کند شده است.

طراحان تراشه و برنامه‌نویسان کامپیوتر با کندی با جستجوی مسیری متفاوت برای اجرای سریع پاسخ داده‌اند: موازی‌سازی.

این شامل موارد زیر می‌شود:

  1. افزایش تعداد CPUهای تعبیه شده در هر ماشین
  2. اتصال شتاب‌دهنده‌های سخت‌افزاری مانند GPUها و TPUها

برای برنامه‌نویسان، چالش این بوده است که از این سخت‌افزار با اجرای بسیاری از فرآیندها به صورت موازی بهره‌برداری کنند.

در زیر ما موازی‌سازی برای محاسبات علمی را با تمرکز بر موارد زیر بحث می‌کنیم:

  1. ابزارهای موازی‌سازی در پایتون و
  2. چگونه این ابزارها می‌توانند برای مسائل اقتصادی کمی به کار گرفته شوند.

موازی‌سازی بر روی CPUها

بیایید دو نوع اصلی موازی‌سازی مبتنی بر CPU که معمولاً در محاسبات علمی استفاده می‌شود را مرور کنیم و مزایا و معایب آنها را بحث کنیم.

چندپردازشی

چندپردازشی به معنای اجرای همزمان چندین رشته منطقی با استفاده از بیش از یک پردازنده است.

چندپردازشی می‌تواند روی یک ماشین با CPUهای چندگانه یا روی کلاستری از ماشین‌های متصل شده توسط یک شبکه انجام شود.

با چندپردازشی، هر فرآیند فضای حافظه خود را دارد، اگرچه تراشه حافظه فیزیکی ممکن است مشترک باشد.

چندرشته‌ای

چندرشته‌ای شبیه به چندپردازشی است، به جز اینکه، در طول اجرا، همه رشته‌ها فضای حافظه یکسانی را به اشتراک می‌گذارند.

پایتون بومی برای پیاده‌سازی چندرشته‌ای به دلیل برخی ویژگی‌های طراحی قدیمی مشکل دارد.

اما این یک محدودیت برای کتابخانه‌های علمی مانند NumPy و Numba نیست.

توابع وارد شده از این کتابخانه‌ها و کد JIT-compiled در محیط‌های اجرای سطح پایین اجرا می‌شوند که محدودیت‌های قدیمی پایتون اعمال نمی‌شود.

مزایا و معایب

چندرشته‌ای سبک‌تر است زیرا بیشتر منابع سیستم و حافظه توسط رشته‌ها به اشتراک گذاشته می‌شوند.

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

از طرف دیگر، چندپردازشی انعطاف‌پذیرتر است و می‌تواند در کلاسترها توزیع شود.

برای اکثریت قریب به اتفاق کاری که ما در این سخنرانی‌ها انجام می‌دهیم، چندرشته‌ای کافی خواهد بود.

شتاب‌دهنده‌های سخت‌افزاری

در حالی که CPUها با هسته‌های چندگانه برای محاسبات موازی استاندارد شده‌اند، یک تغییر چشمگیرتر با ظهور شتاب‌دهنده‌های سخت‌افزاری تخصصی رخ داده است.

این شتاب‌دهنده‌ها به طور خاص برای انواع محاسبات بسیار موازی که در محاسبات علمی، یادگیری ماشین و علم داده به وجود می‌آید طراحی شده‌اند.

GPUها و TPUها

دو نوع مهم‌ترین شتاب‌دهنده‌های سخت‌افزاری عبارتند از:

  • GPUها (واحدهای پردازش گرافیکی) و
  • TPUها (واحدهای پردازش تانسور).

GPUها در ابتدا برای رندرینگ گرافیک طراحی شدند که نیاز به انجام همزمان یک عملیات روی بسیاری از پیکسل‌ها دارد.

:scale: 40

دانشمندان و مهندسان متوجه شدند که همین معماری --- بسیاری از پردازنده‌های ساده که به صورت موازی کار می‌کنند --- برای وظایف محاسبات علمی ایده‌آل است.

TPUها یک توسعه اخیرتر هستند که توسط گوگل به طور خاص برای بارهای کاری یادگیری ماشین طراحی شده‌اند.

مانند GPUها، TPUها در انجام تعداد عظیمی از عملیات ماتریسی به صورت موازی عالی هستند.

چرا TPU/GPU مهم هستند

دستاوردهای عملکردی از استفاده از شتاب‌دهنده‌های سخت‌افزاری می‌تواند چشمگیر باشد.

به عنوان مثال، یک GPU مدرن می‌تواند شامل هزاران هسته پردازشی کوچک باشد، در مقایسه با 8-64 هسته معمولاً در CPUها یافت می‌شوند.

وقتی یک مسئله می‌تواند به عنوان بسیاری از عملیات مستقل بر روی آرایه‌های داده بیان شود، GPUها می‌توانند درجه‌های بزرگی سریعتر از CPUها باشند.

این امر به ویژه برای محاسبات علمی مرتبط است زیرا بسیاری از الگوریتم‌ها به طور طبیعی بر روی معماری موازی GPUها نگاشت می‌شوند.

GPUهای تکی در مقابل سرورهای GPU

دو روش رایج برای دسترسی به منابع GPU وجود دارد:

سیستم‌های GPU تکی

بسیاری از ایستگاه‌های کاری و لپ‌تاپ‌ها اکنون با GPUهای قابل استفاده ارائه می‌شوند، یا می‌توانند با آنها مجهز شوند.

یک GPU مدرن تکی می‌تواند بسیاری از وظایف محاسبات علمی را به طور چشمگیری تسریع بخشد.

برای محققان فردی و پروژه‌های کوچک، یک GPU تکی اغلب کافی است.

کتابخانه‌های مدرن پایتون مانند JAX که به طور گسترده در این مجموعه سخنرانی‌ها مورد بحث قرار می‌گیرند، به طور خودکار GPUهای موجود را با تغییرات حداقلی در کد تشخیص داده و استفاده می‌کنند.

سرورهای چند GPU

برای مسائل در مقیاس بزرگتر، سرورهای حاوی GPUهای متعدد (اغلب 4-8 GPU در هر سرور) به طور فزاینده‌ای رایج هستند.

:scale: 40

با نرم‌افزار مناسب، محاسبات می‌توانند در چندین GPU، یا در یک سرور واحد یا در چندین سرور، توزیع شوند.

این محققان را قادر می‌سازد مسائلی را که بر روی یک GPU یا CPU تکی غیرعملی هستند، مورد بررسی قرار دهند.

خلاصه

محاسبات GPU بسیار در دسترس‌تر می‌شود، به ویژه از داخل پایتون.

برخی از کتابخانه‌های علمی پایتون، مانند JAX، اکنون شتاب GPU را با تغییرات حداقلی در کد موجود پشتیبانی می‌کنند.

ما محاسبات GPU را با جزئیات بیشتری در سخنرانی‌های بعدی بررسی خواهیم کرد و آن را در طیف وسیعی از کاربردهای اقتصادی به کار خواهیم برد.