{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# JAX on IPU: stateful linear regression\n",
    "\n",
    "Stateful computations in JAX on IPU, following the official tutorial: https://jax.readthedocs.io/en/latest/jax-101/07-state.html\n",
    "\n",
    "The original example will run as-is on the IPU. Nevertheless, to make full advantage of the IPU hardware and improve user experience, the following modifications were made:\n",
    "\n",
    "* `jax_platforms = \"cpu,ipu\"`: use CPU as the default platform for initialization of parameters and dataset.\n",
    "* `donate_argnums = (0,)` in `update`: buffer donation to keep model parameters on the IPU SRAM between iterations."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 1,
   "metadata": {},
   "outputs": [
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "Found existing installation: jax 0.3.16+ipu\r\n",
      "Not uninstalling jax at /nethome/paulb/github/jax-experimental-internal, outside environment /nethome/paulb/venvs/3.1.0+1205/3.1.0+1205_poptorch\r\n",
      "Can't uninstall 'jax'. No files were found to uninstall.\r\n",
      "Found existing installation: jaxlib 0.3.15+ipu.sdk310\r\n",
      "Uninstalling jaxlib-0.3.15+ipu.sdk310:\r\n",
      "  Successfully uninstalled jaxlib-0.3.15+ipu.sdk310\r\n"
     ]
    }
   ],
   "source": [
    "# Install experimental JAX for IPUs (SDK 3.1) from Github releases.\n",
    "import sys\n",
    "!{sys.executable} -m pip uninstall -y jax jaxlib\n",
    "!{sys.executable} -m pip install -q jax==0.3.16+ipu jaxlib==0.3.15+ipu.sdk310 -f https://graphcore-research.github.io/jax-experimental/wheels.html\n",
    "!{sys.executable} -m pip install -q matplotlib"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "metadata": {},
   "outputs": [],
   "source": [
    "from jax.config import config\n",
    "\n",
    "# Use CPU as default backend for initialization.\n",
    "config.FLAGS.jax_platforms = \"cpu,ipu\"\n",
    "# Uncomment to use IPU model emulator.\n",
    "# config.FLAGS.jax_ipu_use_model = True\n",
    "# config.FLAGS.jax_ipu_model_num_tiles = 8\n",
    "\n",
    "# Select how many IPUs will be visible.\n",
    "config.FLAGS.jax_ipu_device_count = 1"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "metadata": {},
   "outputs": [
    {
     "name": "stderr",
     "output_type": "stream",
     "text": [
      "WARNING:absl:No GPU/TPU found, falling back to CPU. (Set TF_CPP_MIN_LOG_LEVEL=0 and rerun for more info.)\n"
     ]
    },
    {
     "name": "stdout",
     "output_type": "stream",
     "text": [
      "IPU devices: [IpuDevice(id=0, num_tiles=1472, version=ipu2)]\n"
     ]
    }
   ],
   "source": [
    "import jax\n",
    "import jax.numpy as jnp\n",
    "print(\"IPU devices:\", jax.devices(\"ipu\"))"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 5,
   "metadata": {},
   "outputs": [],
   "source": [
    "from typing import NamedTuple\n",
    "\n",
    "class Params(NamedTuple):\n",
    "  \"\"\"Linear regression state.\"\"\"\n",
    "  weight: jnp.ndarray\n",
    "  bias: jnp.ndarray"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 6,
   "metadata": {},
   "outputs": [],
   "source": [
    "def init(rng) -> Params:\n",
    "  \"\"\"Returns the initial model params.\"\"\"\n",
    "  weights_key, bias_key = jax.random.split(rng)\n",
    "  weight = jax.random.normal(weights_key, ())\n",
    "  bias = jax.random.normal(bias_key, ())\n",
    "  return Params(weight, bias)\n",
    "\n",
    "\n",
    "def loss(params: Params, x: jnp.ndarray, y: jnp.ndarray) -> jnp.ndarray:\n",
    "  \"\"\"Computes the least squares error of the model's predictions on x against y.\"\"\"\n",
    "  pred = params.weight * x + params.bias\n",
    "  return jnp.mean((pred - y) ** 2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "from functools import partial\n",
    "\n",
    "LEARNING_RATE = 0.005\n",
    "\n",
    "# Explicit jitting for IPU backend.\n",
    "# Donate `params`` to keep variables on IPU SRAM. \n",
    "@partial(jax.jit, backend=\"ipu\", donate_argnums=(0,))\n",
    "def update(params: Params, x: jnp.ndarray, y: jnp.ndarray) -> Params:\n",
    "  \"\"\"Performs one SGD update step on params using the given data.\"\"\"\n",
    "  grad = jax.grad(loss)(params, x, y)\n",
    "\n",
    "  # If we were using Adam or another stateful optimizer,\n",
    "  # we would also do something like\n",
    "  # ```\n",
    "  # updates, new_optimizer_state = optimizer(grad, optimizer_state)\n",
    "  # ```\n",
    "  # and then use `updates` instead of `grad` to actually update the params.\n",
    "  # (And we'd include `new_optimizer_state` in the output, naturally.)\n",
    "\n",
    "  new_params = jax.tree_map(\n",
    "      lambda param, g: param - g * LEARNING_RATE, params, grad)\n",
    "\n",
    "  return new_params"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "rng = jax.random.PRNGKey(42)\n",
    "\n",
    "# Generate true data from y = w*x + b + noise\n",
    "true_w, true_b = 2, -1\n",
    "x_rng, noise_rng = jax.random.split(rng)\n",
    "xs = jax.random.normal(x_rng, (128, 1))\n",
    "noise = jax.random.normal(noise_rng, (128, 1)) * 0.5\n",
    "ys = xs * true_w + true_b + noise\n",
    "\n",
    "# Fit regression\n",
    "params = init(rng)\n",
    "for _ in range(1000):\n",
    "  params = update(params, xs, ys)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "metadata": {},
   "outputs": [
    {
     "data": {
      "image/png": "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\n",
      "text/plain": [
       "<Figure size 640x480 with 1 Axes>"
      ]
     },
     "metadata": {},
     "output_type": "display_data"
    }
   ],
   "source": [
    "import matplotlib.pyplot as plt\n",
    "\n",
    "params = jax.device_get(params)\n",
    "\n",
    "plt.scatter(xs, ys)\n",
    "plt.plot(xs, params.weight * xs + params.bias, c='red', label='Model Prediction')\n",
    "plt.legend();"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.8.10"
  },
  "vscode": {
   "interpreter": {
    "hash": "31f2aee4e71d21fbe5cf8b01ff0e069b9275f58929596ceb00d14d90e3e16cd6"
   }
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
