GitHub

@@ -18,7 +18,7 @@ In addition to what's in Anaconda, this lecture will need the following librarie

1818

```{code-cell} ipython3

1919

:tags: [hide-output]

202021-

!pip install jax

21+

!pip install jax quantecon

2222

```

23232424

This lecture provides a short introduction to [Google JAX](https://github.com/jax-ml/jax).

@@ -52,6 +52,7 @@ The following import is standard, replacing `import numpy as np`:

5252

```{code-cell} ipython3

5353

import jax

5454

import jax.numpy as jnp

55+

import quantecon as qe

5556

```

56575758

Now we can use `jnp` in place of `np` for the usual array operations:

@@ -304,7 +305,8 @@ x = jnp.ones(n)

304305

How long does the function take to execute?

305306306307

```{code-cell} ipython3

307-

%time f(x).block_until_ready()

308+

with qe.Timer():

309+

f(x).block_until_ready()

308310

```

309311310312

```{note}

@@ -318,7 +320,8 @@ allows the Python interpreter to run ahead of numerical computations.

318320

If we run it a second time it becomes faster again:

319321320322

```{code-cell} ipython3

321-

%time f(x).block_until_ready()

323+

with qe.Timer():

324+

f(x).block_until_ready()

322325

```

323326324327

This is because the built in functions like `jnp.cos` are JIT compiled and the

@@ -341,7 +344,8 @@ y = jnp.ones(m)

341344

```

342345343346

```{code-cell} ipython3

344-

%time f(y).block_until_ready()

347+

with qe.Timer():

348+

f(y).block_until_ready()

345349

```

346350347351

Notice that the execution time increases, because now new versions of

@@ -352,14 +356,16 @@ If we run again, the code is dispatched to the correct compiled version and we

352356

get faster execution.

353357354358

```{code-cell} ipython3

355-

%time f(y).block_until_ready()

359+

with qe.Timer():

360+

f(y).block_until_ready()

356361

```

357362358363

The compiled versions for the previous array size are still available in memory

359364

too, and the following call is dispatched to the correct compiled code.

360365361366

```{code-cell} ipython3

362-

%time f(x).block_until_ready()

367+

with qe.Timer():

368+

f(x).block_until_ready()

363369

```

364370365371

### Compiling the outer function

@@ -379,7 +385,8 @@ f_jit(x)

379385

And now let's time it.

380386381387

```{code-cell} ipython3

382-

%time f_jit(x).block_until_ready()

388+

with qe.Timer():

389+

f_jit(x).block_until_ready()

383390

```

384391385392

Note the speed gain.

@@ -534,10 +541,10 @@ z_loops = np.empty((n, n))

534541

```

535542536543

```{code-cell} ipython3

537-

%%time

538-

for i in range(n):

539-

for j in range(n):

540-

z_loops[i, j] = f(x[i], y[j])

544+

with qe.Timer():

545+

for i in range(n):

546+

for j in range(n):

547+

z_loops[i, j] = f(x[i], y[j])

541548

```

542549543550

Even for this very small grid, the run time is extremely slow.

@@ -575,15 +582,15 @@ x_mesh, y_mesh = jnp.meshgrid(x, y)

575582

Now we get what we want and the execution time is very fast.

576583577584

```{code-cell} ipython3

578-

%%time

579-

z_mesh = f(x_mesh, y_mesh).block_until_ready()

585+

with qe.Timer():

586+

z_mesh = f(x_mesh, y_mesh).block_until_ready()

580587

```

581588582589

Let's run again to eliminate compile time.

583590584591

```{code-cell} ipython3

585-

%%time

586-

z_mesh = f(x_mesh, y_mesh).block_until_ready()

592+

with qe.Timer():

593+

z_mesh = f(x_mesh, y_mesh).block_until_ready()

587594

```

588595589596

Let's confirm that we got the right answer.

@@ -602,8 +609,8 @@ x_mesh, y_mesh = jnp.meshgrid(x, y)

602609

```

603610604611

```{code-cell} ipython3

605-

%%time

606-

z_mesh = f(x_mesh, y_mesh).block_until_ready()

612+

with qe.Timer():

613+

z_mesh = f(x_mesh, y_mesh).block_until_ready()

607614

```

608615609616

But there is one problem here: the mesh grids use a lot of memory.

@@ -641,8 +648,8 @@ f_vec = jax.vmap(f_vec_y, in_axes=(0, None))

641648

With this construction, we can now call the function $f$ on flat (low memory) arrays.

642649643650

```{code-cell} ipython3

644-

%%time

645-

z_vmap = f_vec(x, y).block_until_ready()

651+

with qe.Timer():

652+

z_vmap = f_vec(x, y).block_until_ready()

646653

```

647654648655

The execution time is essentially the same as the mesh operation but we are using much less memory.

@@ -711,15 +718,15 @@ def compute_call_price_jax(β=β,

711718

Let's run it once to compile it:

712719713720

```{code-cell} ipython3

714-

%%time

715-

compute_call_price_jax().block_until_ready()

721+

with qe.Timer():

722+

compute_call_price_jax().block_until_ready()

716723

```

717724718725

And now let's time it:

719726720727

```{code-cell} ipython3

721-

%%time

722-

compute_call_price_jax().block_until_ready()

728+

with qe.Timer():

729+

compute_call_price_jax().block_until_ready()

723730

```

724731725732

```{solution-end}

Read the original on github.com ↗