From 970accfad170958b257c7f293b13294a23e6c5a9 Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Mon, 13 Apr 2026 13:49:44 +0100 Subject: [PATCH 1/4] Update translation: lectures/jax_intro.md --- lectures/jax_intro.md | 58 +++++++++++++++++++++++++------------------ 1 file changed, 34 insertions(+), 24 deletions(-) diff --git a/lectures/jax_intro.md b/lectures/jax_intro.md index fdd359b..46b6757 100644 --- a/lectures/jax_intro.md +++ b/lectures/jax_intro.md @@ -18,9 +18,10 @@ translation: JAX as a NumPy Replacement::Differences::Speed!: سرعت! JAX as a NumPy Replacement::Differences::Speed!::With NumPy: با NumPy JAX as a NumPy Replacement::Differences::Speed!::With JAX: با JAX + JAX as a NumPy Replacement::Differences::Size Experiment: آزمایش اندازه JAX as a NumPy Replacement::Differences::Precision: دقت JAX as a NumPy Replacement::Differences::Immutability: تغییرناپذیری - JAX as a NumPy Replacement::Differences::A workaround: راه‌حل جایگزین + JAX as a NumPy Replacement::Differences::A Workaround: راه‌حل جایگزین Functional Programming: برنامه‌نویسی تابعی Functional Programming::Pure functions: توابع خالص Functional Programming::Examples: مثال‌ها @@ -76,18 +77,18 @@ import numpy as np import quantecon as qe ``` -توجه کنید که `jax.numpy as jnp` را import می‌کنیم که یک رابط شبیه NumPy فراهم می‌کند. - ## JAX به عنوان جایگزین NumPy -یکی از ویژگی‌های جذاب JAX این است که، هر زمان که امکان‌پذیر باشد، عملیات پردازش آرایه‌های آن با API NumPy مطابقت دارد. - -این بدان معناست که در بسیاری از موارد، می‌توانیم از JAX به عنوان جایگزین مستقیم NumPy استفاده کنیم. - بیایید به شباهت‌ها و تفاوت‌های بین JAX و NumPy نگاه کنیم. ### شباهت‌ها +در بالا `jax.numpy as jnp` را وارد کردیم که یک رابط شبیه به NumPy برای عملیات آرایه فراهم می‌کند. + +یکی از ویژگی‌های جذاب JAX این است که، هر زمان که امکان‌پذیر باشد، این رابط با API NumPy مطابقت دارد. + +در نتیجه، اغلب می‌توانیم از JAX به عنوان جایگزین مستقیم NumPy استفاده کنیم. + در اینجا برخی عملیات استاندارد آرایه با استفاده از `jnp` آمده است: ```{code-cell} ipython3 @@ -106,7 +107,7 @@ print(jnp.sum(a)) print(jnp.dot(a, a)) ``` -با این حال، شیء آرایه `a` یک آرایه NumPy نیست: +با این حال، باید به خاطر داشت که شیء آرایه `a` یک آرایه NumPy نیست: ```{code-cell} ipython3 a @@ -129,11 +130,13 @@ jnp.sum(a) (jax_speed)= #### سرعت! -فرض کنیم می‌خواهیم تابع کسینوس را در نقاط بسیاری ارزیابی کنیم. +یکی از تفاوت‌های عمده این است که JAX سریع‌تر است --- و گاهی بسیار سریع‌تر. + +برای نشان دادن این موضوع، فرض کنیم می‌خواهیم تابع کسینوس را در نقاط بسیاری ارزیابی کنیم. ```{code-cell} n = 50_000_000 -x = np.linspace(0, 10, n) +x = np.linspace(0, 10, n) # NumPy array ``` ##### با NumPy @@ -174,27 +177,23 @@ with qe.Timer(): # First run y = jnp.cos(x) # Hold the interpreter until the array operation finishes - jax.block_until_ready(y); + y.block_until_ready() ``` ```{note} -در اینجا، برای اندازه‌گیری سرعت واقعی، از متد `block_until_ready` استفاده می‌کنیم -تا مفسر را تا زمانی که نتایج محاسبات بازگردانده شوند نگه داریم. - -این ضروری است زیرا JAX از ارسال ناهمزمان استفاده می‌کند که +در بالا، متد `block_until_ready` مفسر را تا زمانی که نتایج محاسبات بازگردانده شوند نگه می‌دارد. +این برای زمان‌بندی اجرا ضروری است زیرا JAX از ارسال ناهمزمان استفاده می‌کند که به مفسر Python اجازه می‌دهد جلوتر از محاسبات عددی حرکت کند. - -برای کدهایی که زمان‌بندی نمی‌شوند، می‌توانید خط حاوی `block_until_ready` را حذف کنید. ``` -و بیایید دوباره زمان‌بندی کنیم. +اکنون بیایید دوباره زمان‌بندی کنیم. ```{code-cell} with qe.Timer(): # Second run y = jnp.cos(x) # Hold interpreter - jax.block_until_ready(y); + y.block_until_ready() ``` روی GPU، این کد بسیار سریع‌تر از معادل NumPy خود اجرا می‌شود. @@ -209,6 +208,8 @@ with qe.Timer(): اندازه برای تولید کد بهینه اهمیت دارد زیرا موازی‌سازی کارآمد نیازمند تطابق اندازه کار با سخت‌افزار موجود است. +#### آزمایش اندازه + می‌توانیم ادعا که JAX بر اندازه آرایه تخصص پیدا می‌کند را با تغییر اندازه ورودی و مشاهده زمان‌های اجرا تأیید کنیم. ```{code-cell} @@ -220,7 +221,7 @@ with qe.Timer(): # First run y = jnp.cos(x) # Hold interpreter - jax.block_until_ready(y); + y.block_until_ready() ``` ```{code-cell} @@ -228,7 +229,7 @@ with qe.Timer(): # Second run y = jnp.cos(x) # Hold interpreter - jax.block_until_ready(y); + y.block_until_ready() ``` زمان اجرا افزایش می‌یابد و سپس دوباره کاهش می‌یابد (این روی GPU واضح‌تر خواهد بود). @@ -277,7 +278,7 @@ a[0] = 1 a ``` -در JAX این کار شکست می‌خورد! +در JAX این کار شکست می‌خورد 😱. ```{code-cell} ipython3 a = jnp.linspace(0, 1, 3) @@ -292,11 +293,18 @@ except Exception as e: ``` -طراحان JAX تصمیم گرفتند آرایه‌ها را تغییرناپذیر کنند زیرا JAX از سبک برنامه‌نویسی تابعی استفاده می‌کند که در ادامه آن را بررسی می‌کنیم. +طراحان JAX تصمیم گرفتند آرایه‌ها را تغییرناپذیر کنند زیرا +1. JAX از *سبک برنامه‌نویسی تابعی* استفاده می‌کند و +2. برنامه‌نویسی تابعی معمولاً از داده‌های قابل تغییر اجتناب می‌کند + +این ایده‌ها را {ref}`در ادامه ` بررسی می‌کنیم. + + +(jax_at_workaround)= #### راه‌حل جایگزین -توجه می‌کنیم که JAX یک جایگزین برای تغییر درجای آرایه با استفاده از [متد `at`](https://docs.jax.dev/en/latest/_autosummary/jax.numpy.ndarray.at.html) فراهم می‌کند. +JAX یک جایگزین مستقیم برای تغییر درجای آرایه از طریق [متد `at`](https://docs.jax.dev/en/latest/_autosummary/jax.numpy.ndarray.at.html) فراهم می‌کند. ```{code-cell} ipython3 a = jnp.linspace(0, 1, 3) @@ -318,6 +326,8 @@ a (اگرچه در واقع می‌تواند داخل توابع کامپایل‌شده JIT کارآمد باشد -- اما بیایید این را فعلاً کنار بگذاریم.) + +(jax_func)= ## برنامه‌نویسی تابعی از مستندات JAX: From d5205d6d5c209858a896e333e310f587dc52a92b Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Mon, 13 Apr 2026 13:49:44 +0100 Subject: [PATCH 2/4] Update translation: .translate/state/jax_intro.md.yml --- .translate/state/jax_intro.md.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.translate/state/jax_intro.md.yml b/.translate/state/jax_intro.md.yml index e8f1756..414535f 100644 --- a/.translate/state/jax_intro.md.yml +++ b/.translate/state/jax_intro.md.yml @@ -1,4 +1,4 @@ -source-sha: 95378b8382b4dbd1cd3e0ffe0e152811894c357f +source-sha: 11e7d823f7f355f5025d40cab40bf801b3262e56 synced-at: "2026-04-13" model: claude-sonnet-4-6 mode: UPDATE From 777c279dec50c81544bddeb7b166a92e42617698 Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Mon, 13 Apr 2026 13:49:45 +0100 Subject: [PATCH 3/4] Update translation: lectures/numpy_vs_numba_vs_jax.md --- lectures/numpy_vs_numba_vs_jax.md | 81 +++++++++++++++++++++++-------- 1 file changed, 60 insertions(+), 21 deletions(-) diff --git a/lectures/numpy_vs_numba_vs_jax.md b/lectures/numpy_vs_numba_vs_jax.md index 8578b55..a5ee332 100644 --- a/lectures/numpy_vs_numba_vs_jax.md +++ b/lectures/numpy_vs_numba_vs_jax.md @@ -36,7 +36,7 @@ translation: # NumPy در مقابل Numba در مقابل JAX -در سخنرانی‌های قبلی، سه کتابخانه اصلی برای محاسبات علمی و عددی را بحث کردیم: +در درس‌های قبلی، سه کتابخانه اصلی برای محاسبات علمی و عددی را بحث کردیم: * [NumPy](numpy) * [Numba](numba) @@ -44,7 +44,7 @@ translation: کدام یک را باید در هر موقعیت استفاده کنیم؟ -این سخنرانی به آن سؤال پاسخ می‌دهد، حداقل تا حدی، با بحث در مورد برخی موارد استفاده. +این درس به آن سؤال پاسخ می‌دهد، حداقل تا حدی، با بحث در مورد برخی موارد استفاده. قبل از شروع، توجه می‌کنیم که دو مورد اول یک جفت طبیعی هستند: NumPy و Numba به خوبی با هم کار می‌کنند. @@ -52,7 +52,7 @@ JAX، از سوی دیگر، به تنهایی می‌ایستد. هنگام بررسی هر رویکرد، نه تنها کارایی و رد پای حافظه، بلکه وضوح و سهولت استفاده را نیز در نظر خواهیم گرفت. -علاوه بر آنچه در Anaconda موجود است، این سخنرانی به کتابخانه‌های زیر نیاز دارد: +علاوه بر آنچه در Anaconda موجود است، این درس به کتابخانه‌های زیر نیاز دارد: ```{code-cell} ipython3 --- @@ -67,7 +67,6 @@ tags: [hide-output] ما از import های زیر استفاده خواهیم کرد. ```{code-cell} ipython3 -import random from functools import partial import numpy as np @@ -455,15 +454,60 @@ Numba این عملیات ترتیبی را به طور بسیار کارآمد ### نسخه JAX -حالا بیایید یک نسخه JAX با استفاده از `lax.scan` ایجاد کنیم: +حالا بیایید یک نسخه JAX با استفاده از سینتکس `at[t].set` ایجاد کنیم که، همان‌طور که {ref}`در درس JAX بحث شد `، راه‌حلی برای آرایه‌های تغییرناپذیر فراهم می‌کند. -(ما `n` را ایستا نگه می‌داریم زیرا بر اندازه آرایه تأثیر می‌گذارد و از این رو JAX می‌خواهد روی مقدار آن در کد کامپایل شده تخصصی شود.) +ما از `lax.fori_loop` استفاده می‌کنیم که نسخه‌ای از حلقه for است که می‌تواند توسط XLA کامپایل شود. ```{code-cell} ipython3 cpu = jax.devices("cpu")[0] -@partial(jax.jit, static_argnames=('n',), device=cpu) -def qm_jax(x0, n, α=4.0): +@partial(jax.jit, static_argnames=("n",), device=cpu) +def qm_jax_fori(x0, n, α=4.0): + + x = jnp.empty(n + 1).at[0].set(x0) + + def update(t, x): + return x.at[t + 1].set(α * x[t] * (1 - x[t])) + + x = lax.fori_loop(0, n, update, x) + return x + +``` + +* ما `n` را ایستا نگه می‌داریم زیرا بر اندازه آرایه تأثیر می‌گذارد و از این رو JAX می‌خواهد روی مقدار آن در کد کامپایل شده تخصصی شود. +* ما به CPU از طریق `device=cpu` متصل می‌مانیم زیرا این بار کاری ترتیبی از بسیاری عملیات کوچک تشکیل شده است که فرصت کمی برای موازی‌سازی GPU باقی می‌گذارد. + +اگرچه `at[t].set` در هر مرحله ظاهراً یک آرایه جدید ایجاد می‌کند، در داخل یک تابع کامپایل‌شده با JIT، کامپایلر تشخیص می‌دهد که آرایه قدیمی دیگر مورد نیاز نیست و به‌روزرسانی را در جا انجام می‌دهد. + +بیایید آن را با همان پارامترها زمان‌بندی کنیم: + +```{code-cell} ipython3 +with qe.Timer(): + # First run + x_jax = qm_jax_fori(0.1, n) + # Hold interpreter + x_jax.block_until_ready() +``` + +بیایید دوباره اجرا کنیم تا سربار کامپایل حذف شود: + +```{code-cell} ipython3 +with qe.Timer(): + # Second run + x_jax = qm_jax_fori(0.1, n) + # Hold interpreter + x_jax.block_until_ready() +``` + +JAX نیز برای این عملیات ترتیبی کاملاً کارآمد است. + +روش دیگری برای پیاده‌سازی حلقه وجود دارد که از `lax.scan` استفاده می‌کند. + +این روش جایگزین، به طور قابل بحث، بیشتر با رویکرد تابعی JAX همسو است --- اگرچه سینتکس آن به خاطر سپردن دشواری دارد. + +```{code-cell} ipython3 +@partial(jax.jit, static_argnames=("n",), device=cpu) +def qm_jax_scan(x0, n, α=4.0): def update(x, t): x_new = α * x * (1 - x) return x_new, x_new @@ -474,16 +518,12 @@ def qm_jax(x0, n, α=4.0): این کد خواندن آسانی ندارد اما، در اصل، `lax.scan` به طور مکرر `update` را فراخوانی می‌کند و بازگشت‌های `x_new` را در یک آرایه جمع می‌کند. -```{note} -ما `device=cpu` را در decorator `jax.jit` مشخص می‌کنیم زیرا این محاسبه از بسیاری عملیات ترتیبی کوچک تشکیل شده است که فرصت کمی برای بهره‌برداری GPU از موازی‌سازی باقی می‌گذارد. در نتیجه، سربار راه‌اندازی kernel تمایل دارد روی GPU غالب شود و CPU را متناسب‌تر برای این بار کاری می‌کند. -``` - بیایید آن را با همان پارامترها زمان‌بندی کنیم: ```{code-cell} ipython3 with qe.Timer(): # First run - x_jax = qm_jax(0.1, n) + x_jax = qm_jax_scan(0.1, n) # Hold interpreter x_jax.block_until_ready() ``` @@ -493,13 +533,11 @@ with qe.Timer(): ```{code-cell} ipython3 with qe.Timer(): # Second run - x_jax = qm_jax(0.1, n) + x_jax = qm_jax_scan(0.1, n) # Hold interpreter x_jax.block_until_ready() ``` -JAX نیز برای این عملیات ترتیبی کاملاً کارآمد است. - هم JAX و هم Numba عملکرد قوی پس از کامپایل ارائه می‌دهند. ### خلاصه @@ -510,9 +548,9 @@ JAX نیز برای این عملیات ترتیبی کاملاً کارآمد این دقیقاً نحوه تفکر اکثر برنامه‌نویسان در مورد الگوریتم است. -نسخه JAX، از سوی دیگر، نیاز به استفاده از `lax.scan` دارد که به طور قابل توجهی کمتر شهودی است. +نسخه‌های JAX، از سوی دیگر، نیاز به استفاده از `lax.fori_loop` یا `lax.scan` دارند که هر دو کمتر شهودی از یک حلقه استاندارد Python هستند. -علاوه بر این، آرایه‌های تغییرناپذیر JAX به این معنی است که نمی‌توانیم به سادگی عناصر آرایه را در جا به‌روزرسانی کنیم و تکرار مستقیم الگوریتم مورد استفاده توسط Numba را سخت می‌کند. +در حالی که سینتکس `at[t].set` در JAX به‌روزرسانی عنصر به عنصر را ممکن می‌سازد، کد کلی همچنان سخت‌تر از معادل Numba برای خواندن است. برای این نوع عملیات ترتیبی، Numba برنده واضح از نظر وضوح کد و سهولت پیاده‌سازی است. @@ -532,11 +570,12 @@ JAX نیز برای این عملیات ترتیبی کاملاً کارآمد کد طبیعی و خوانا است --- صرفاً یک حلقه پایتون با یک decorator --- و کارایی آن عالی است. -JAX می‌تواند مسائل ترتیبی را از طریق `lax.scan` مدیریت کند، اما نحو آن کمتر شهودی است و برای کارهای کاملاً ترتیبی، بهره‌وری اضافی ناچیز است. - -با این حال، `lax.scan` یک مزیت مهم دارد: از مشتق‌گیری خودکار در طول حلقه پشتیبانی می‌کند، که Numba قادر به انجام آن نیست. +JAX می‌تواند مسائل ترتیبی را از طریق `lax.fori_loop` یا `lax.scan` مدیریت کند، اما نحو آن کمتر شهودی است. +```{note} +یک مزیت مهم `lax.fori_loop` و `lax.scan` این است که از مشتق‌گیری خودکار در طول حلقه پشتیبانی می‌کنند، که Numba قادر به انجام آن نیست. اگر نیاز دارید از طریق یک محاسبه ترتیبی مشتق بگیرید (مثلاً محاسبه حساسیت‌های یک مسیر نسبت به پارامترهای مدل)، JAX علی‌رغم نحو کمتر طبیعی‌اش، انتخاب بهتری است. +``` در عمل، بسیاری از مسائل ترکیبی از هر دو الگو هستند. From 3137f28282741c66cf38d3b2a2f96eb58df6658a Mon Sep 17 00:00:00 2001 From: Matt McKay Date: Mon, 13 Apr 2026 13:49:45 +0100 Subject: [PATCH 4/4] Update translation: .translate/state/numpy_vs_numba_vs_jax.md.yml --- .translate/state/numpy_vs_numba_vs_jax.md.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.translate/state/numpy_vs_numba_vs_jax.md.yml b/.translate/state/numpy_vs_numba_vs_jax.md.yml index 34ec88f..3f071e6 100644 --- a/.translate/state/numpy_vs_numba_vs_jax.md.yml +++ b/.translate/state/numpy_vs_numba_vs_jax.md.yml @@ -1,4 +1,4 @@ -source-sha: 95378b8382b4dbd1cd3e0ffe0e152811894c357f +source-sha: 11e7d823f7f355f5025d40cab40bf801b3262e56 synced-at: "2026-04-13" model: claude-sonnet-4-6 mode: UPDATE