@@ -48,6 +48,18 @@ tags: [hide-output]
4848!pip install quantecon jax
4949```
505051+```{admonition} GPU
52+:class: warning
53+54+This lecture is accelerated via [hardware](status:machine-details) that has access to a GPU and target JAX for GPU programming.
55+56+Free GPUs are available on Google Colab.
57+To use this option, please click on the play icon top right, select Colab, and set the runtime environment to include a GPU.
58+59+Alternatively, if you have your own GPU, you can follow the [instructions](https://github.com/google/jax) for installing JAX with GPU support.
60+If you would like to install JAX running on the `cpu` only you can use `pip install jax[cpu]`
61+```
62+5163We will use the following imports.
52645365```{code-cell} ipython3
@@ -317,7 +329,7 @@ with qe.Timer(precision=8):
317329 z_max = jnp.max(f(x_mesh, y_mesh)).block_until_ready()
318330```
319331320-Once compiled, JAX will be significantly faster than NumPy, especially if you are using a GPU.
332+Once compiled, JAX is significantly faster than NumPy due to GPU acceleration.
321333322334The compilation overhead is a one-time cost that pays off when the function is called repeatedly.
323335@@ -370,23 +382,29 @@ with qe.Timer(precision=8):
370382 z_max.block_until_ready()
371383```
372384373-The execution time is similar to the mesh operation but, by avoiding the large input arrays `x_mesh` and `y_mesh`,
374-we are using far less memory.
385+By avoiding the large input arrays `x_mesh` and `y_mesh`, this `vmap` version uses far less memory.
386+387+When run on a CPU, its runtime is similar to that of the meshgrid version.
375388376-In addition, `vmap` allows us to break vectorization up into stages, which is
377-often easier to comprehend than the traditional approach.
389+When run on a GPU, it is usually significantly faster.
378390379-This will become more obvious when we tackle larger problems.
391+In fact, using `vmap` has another advantage: It allows us to break vectorization up into stages.
392+393+This leads to code that is often easier to comprehend than traditional vectorized code.
394+395+We will investigate these ideas more when we tackle larger problems.
380396381397382398### vmap version 2
383399384400We can be still more memory efficient using vmap.
385401386-While we avoided large input arrays in the preceding version,
402+While we avoid large input arrays in the preceding version,
387403we still create the large output array `f(x,y)` before we compute the max.
388404389-Let's use a slightly different approach that takes the max to the inside.
405+Let's try a slightly different approach that takes the max to the inside.
406+407+Because of this change, we never compute the two-dimensional array `f(x,y)`.
390408391409```{code-cell} ipython3
392410@jax.jit
@@ -399,23 +417,28 @@ def compute_max_vmap_v2(grid):
399417 return jnp.max(f_vec_max(grid))
400418```
401419402-Let's try it
420+Here
421+422+* `f_vec_x_max` computes the max along any given row
423+* `f_vec_max` is a vectorized version that can compute the max of all rows in parallel.
424+425+We apply this function to all rows and then take the max of the row maxes.
426+427+Let's try it.
403428404429```{code-cell} ipython3
405430with qe.Timer(precision=8):
406431 z_max = compute_max_vmap_v2(grid).block_until_ready()
407432```
408433409-410434Let's run it again to eliminate compilation time:
411435412436```{code-cell} ipython3
413437with qe.Timer(precision=8):
414438 z_max = compute_max_vmap_v2(grid).block_until_ready()
415439```
416440417-We don't get much speed gain but we do save some memory.
418-441+If you are running this on a GPU, as we are, you should see another nontrivial speed gain.
419442420443421444### Summary
@@ -497,7 +520,9 @@ Now let's create a JAX version using `lax.scan`:
497520from jax import lax
498521from functools import partial
499522500-@partial(jax.jit, static_argnums=(1,))
523+cpu = jax.devices("cpu")[0]
524+525+@partial(jax.jit, static_argnums=(1,), device=cpu)
501526def qm_jax(x0, n, α=4.0):
502527 def update(x, t):
503528 x_new = α * x * (1 - x)
@@ -509,6 +534,16 @@ def qm_jax(x0, n, α=4.0):
509534510535This code is not easy to read but, in essence, `lax.scan` repeatedly calls `update` and accumulates the returns `x_new` into an array.
511536537+```{note}
538+Sharp readers will notice that we specify `device=cpu` in the `jax.jit` decorator.
539+540+The computation consists of many very small `lax.scan` iterations that must run sequentially, leaving little opportunity for the GPU to exploit parallelism.
541+542+As a result, kernel-launch overhead tends to dominate on the GPU, making the CPU a better fit for this workload.
543+544+Curious readers can try removing this option to see how performance changes.
545+```
546+512547Let's time it with the same parameters:
513548514549```{code-cell} ipython3