blob: 1875adf147cac3d6c6bd0cee19dfef6ee10374bb [file]
{
"nbformat": 4,
"nbformat_minor": 0,
"metadata": {},
"cells": [
{
"cell_type": "markdown",
"source": [
"##### Copyright 2020 The IREE Authors"
],
"metadata": {}
},
{
"cell_type": "code",
"source": [
"#@title Licensed under the Apache License v2.0 with LLVM Exceptions.\n",
"# See https://llvm.org/LICENSE.txt for license information.\n",
"# SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception"
],
"metadata": {},
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Training and Executing an MNIST Model with IREE"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Overview\n",
"\n",
"This notebook covers installing IREE and using it to train a simple neural network on the MNIST dataset."
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 1. Install dependencies"
]
},
{
"cell_type": "code",
"source": [
"%%capture\n",
"!python -m pip install --upgrade tensorflow"
],
"metadata": {},
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"metadata": {},
"source": [
"%%capture\n",
"!python -m pip install --pre iree-base-compiler iree-base-runtime iree-tools-tf -f https://iree.dev/pip-release-links.html"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"metadata": {},
"source": [
"import iree.compiler.tf\n",
"import iree.runtime\n",
"import tensorflow as tf\n",
"\n",
"# Print version information for future notebook users to reference.\n",
"!iree-compile --version\n",
"print(\"TensorFlow version: \", tf.__version__)"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 2. Import TensorFlow and Other Dependencies"
]
},
{
"cell_type": "code",
"metadata": {},
"source": [
"from matplotlib import pyplot as plt\n",
"import numpy as np\n",
"import tensorflow as tf\n",
"\n",
"tf.random.set_seed(91)\n",
"np.random.seed(91)\n",
"\n",
"plt.rcParams[\"font.family\"] = \"monospace\"\n",
"plt.rcParams[\"figure.figsize\"] = [8, 4.5]\n",
"plt.rcParams[\"figure.dpi\"] = 150\n",
"\n",
"# Print version information for future notebook users to reference.\n",
"print(\"TensorFlow version: \", tf.__version__)\n",
"print(\"Numpy version: \", np.__version__)"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 3. Load the MNIST Dataset"
]
},
{
"cell_type": "code",
"metadata": {},
"source": [
"# Keras datasets don't provide metadata.\n",
"NUM_CLASSES = 10\n",
"NUM_ROWS, NUM_COLS = 28, 28\n",
"\n",
"(x_train, y_train), (x_test, y_test) = tf.keras.datasets.mnist.load_data()\n",
"\n",
"# Reshape into grayscale images:\n",
"x_train = np.reshape(x_train, (-1, NUM_ROWS, NUM_COLS, 1))\n",
"x_test = np.reshape(x_test, (-1, NUM_ROWS, NUM_COLS, 1))\n",
"\n",
"# Rescale uint8 pixel values into float32 values between 0 and 1:\n",
"x_train = x_train.astype(np.float32) / 255\n",
"x_test = x_test.astype(np.float32) / 255\n",
"\n",
"# IREE doesn't currently support int8 tensors, so we cast them to int32:\n",
"y_train = y_train.astype(np.int32)\n",
"y_test = y_test.astype(np.int32)"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"metadata": {},
"source": [
"print(\"Sample image from the dataset:\")\n",
"sample_index = np.random.randint(x_train.shape[0])\n",
"plt.figure(figsize=(5, 5))\n",
"plt.imshow(x_train[sample_index].reshape(NUM_ROWS, NUM_COLS), cmap=\"gray\")\n",
"plt.title(f\"Sample #{sample_index}, label: {y_train[sample_index]}\")\n",
"plt.axis(\"off\")\n",
"plt.tight_layout()"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 4. Create a Simple DNN"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"MLIR-HLO (the MLIR dialect we use to convert TensorFlow models into assembly that IREE can compile) does not currently support training with a dynamic number of examples, so we compile the model with a fixed batch size (by specifying the batch size in the `tf.TensorSpec`s)."
]
},
{
"cell_type": "code",
"metadata": {},
"source": [
"BATCH_SIZE = 32"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"metadata": {},
"source": [
"class TrainableDNN(tf.Module):\n",
"\n",
" def __init__(self):\n",
" super().__init__()\n",
"\n",
" # Create a Keras model to train.\n",
" inputs = tf.keras.layers.Input((NUM_COLS, NUM_ROWS, 1))\n",
" x = tf.keras.layers.Flatten()(inputs)\n",
" x = tf.keras.layers.Dense(128)(x)\n",
" x = tf.keras.layers.Activation(\"relu\")(x)\n",
" x = tf.keras.layers.Dense(10)(x)\n",
" outputs = tf.keras.layers.Softmax()(x)\n",
" self.model = tf.keras.Model(inputs, outputs)\n",
"\n",
" # Create a loss function and optimizer to use during training.\n",
" self.loss = tf.keras.losses.SparseCategoricalCrossentropy()\n",
" self.optimizer = tf.keras.optimizers.SGD(learning_rate=1e-2)\n",
"\n",
" @tf.function(input_signature=[\n",
" tf.TensorSpec([BATCH_SIZE, NUM_ROWS, NUM_COLS, 1]) # inputs\n",
" ])\n",
" def predict(self, inputs):\n",
" return self.model(inputs, training=False)\n",
"\n",
" # We compile the entire training step by making it a method on the model.\n",
" @tf.function(input_signature=[\n",
" tf.TensorSpec([BATCH_SIZE, NUM_ROWS, NUM_COLS, 1]), # inputs\n",
" tf.TensorSpec([BATCH_SIZE], tf.int32) # labels\n",
" ])\n",
" def learn(self, inputs, labels):\n",
" # Capture the gradients from forward prop...\n",
" with tf.GradientTape() as tape:\n",
" probs = self.model(inputs, training=True)\n",
" loss = self.loss(labels, probs)\n",
"\n",
" # ...and use them to update the model's weights.\n",
" variables = self.model.trainable_variables\n",
" gradients = tape.gradient(loss, variables)\n",
" self.optimizer.apply_gradients(zip(gradients, variables))\n",
" return loss"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 5. Compile the Model with IREE"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"tf.keras adds a large number of methods to TrainableDNN, and most of them\n",
"cannot be compiled with IREE. To get around this we tell IREE exactly which\n",
"methods we would like it to compile."
]
},
{
"cell_type": "code",
"metadata": {},
"source": [
"exported_names = [\"predict\", \"learn\"]"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Choose one of IREE's three backends to compile to. (*Note: Using Vulkan requires installing additional drivers.*)"
]
},
{
"cell_type": "code",
"metadata": {},
"source": [
"backend_choice = \"llvm-cpu (CPU)\" #@param [ \"vmvx (CPU)\", \"llvm-cpu (CPU)\", \"vulkan-spirv (GPU/SwiftShader – requires additional drivers) \" ]\n",
"backend_choice = backend_choice.split(' ')[0]"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"metadata": {},
"source": [
"# Compile the TrainableDNN module\n",
"vm_flatbuffer = iree.compiler.tf.compile_module(\n",
" TrainableDNN(),\n",
" target_backends=[backend_choice],\n",
" exported_names=exported_names)\n",
"compiled_model = iree.runtime.load_vm_flatbuffer(\n",
" vm_flatbuffer,\n",
" backend=backend_choice)"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 6. Train the Compiled Model on MNIST"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"This compiled model is portable, demonstrating that IREE can be used for training on a mobile device. On mobile, IREE has a ~1000 fold binary size advantage over the current TensorFlow solution (which is to use the now-deprecated TF Mobile, as TFLite does not support training at this time)."
]
},
{
"cell_type": "code",
"metadata": {},
"source": [
"#@title Benchmark inference and training\n",
"print(\"Inference latency:\\n \", end=\"\")\n",
"%timeit -n 100 compiled_model.predict(x_train[:BATCH_SIZE])\n",
"print(\"Training latancy:\\n \", end=\"\")\n",
"%timeit -n 100 compiled_model.learn(x_train[:BATCH_SIZE], y_train[:BATCH_SIZE])"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"metadata": {},
"source": [
"# Run the core training loop.\n",
"losses = []\n",
"\n",
"step = 0\n",
"max_steps = x_train.shape[0] // BATCH_SIZE\n",
"\n",
"for batch_start in range(0, x_train.shape[0], BATCH_SIZE):\n",
" if batch_start + BATCH_SIZE > x_train.shape[0]:\n",
" continue\n",
"\n",
" inputs = x_train[batch_start:batch_start + BATCH_SIZE]\n",
" labels = y_train[batch_start:batch_start + BATCH_SIZE]\n",
"\n",
" loss = compiled_model.learn(inputs, labels).to_host()\n",
" losses.append(loss)\n",
"\n",
" step += 1\n",
" print(f\"\\rStep {step:4d}/{max_steps}: loss = {loss:.4f}\", end=\"\")"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"metadata": {},
"source": [
"#@title Plot the training results\n",
"!python -m pip install bottleneck\n",
"import bottleneck as bn\n",
"smoothed_losses = bn.move_mean(losses, 32)\n",
"x = np.arange(len(losses))\n",
"\n",
"plt.plot(x, smoothed_losses, linewidth=2, label='loss (moving average)')\n",
"plt.scatter(x, losses, s=16, alpha=0.2, label='loss (per training step)')\n",
"\n",
"plt.ylim(0)\n",
"plt.legend(frameon=True)\n",
"plt.xlabel(\"training step\")\n",
"plt.ylabel(\"cross-entropy\")\n",
"plt.title(\"training loss\");"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## 7. Evaluate on Heldout Test Examples"
]
},
{
"cell_type": "code",
"metadata": {},
"source": [
"#@title Evaluate the network on the test data.\n",
"accuracies = []\n",
"\n",
"step = 0\n",
"max_steps = x_test.shape[0] // BATCH_SIZE\n",
"\n",
"for batch_start in range(0, x_test.shape[0], BATCH_SIZE):\n",
" if batch_start + BATCH_SIZE > x_test.shape[0]:\n",
" continue\n",
"\n",
" inputs = x_test[batch_start:batch_start + BATCH_SIZE]\n",
" labels = y_test[batch_start:batch_start + BATCH_SIZE]\n",
"\n",
" prediction = compiled_model.predict(inputs).to_host()\n",
" prediction = np.argmax(prediction, -1)\n",
" accuracies.append(np.sum(prediction == labels) / BATCH_SIZE)\n",
"\n",
" step += 1\n",
" print(f\"\\rStep {step:4d}/{max_steps}\", end=\"\")\n",
"print()\n",
"\n",
"accuracy = np.mean(accuracies)\n",
"print(f\"Test accuracy: {accuracy:.3f}\")"
],
"execution_count": null,
"outputs": []
},
{
"cell_type": "code",
"metadata": {},
"source": [
"#@title Display inference predictions on a random selection of heldout data\n",
"rows = 4\n",
"columns = 4\n",
"images_to_display = rows * columns\n",
"assert BATCH_SIZE >= images_to_display\n",
"\n",
"random_index = np.arange(x_test.shape[0])\n",
"np.random.shuffle(random_index)\n",
"x_test = x_test[random_index]\n",
"y_test = y_test[random_index]\n",
"\n",
"predictions = compiled_model.predict(x_test[:BATCH_SIZE]).to_host()\n",
"predictions = np.argmax(predictions, -1)\n",
"\n",
"fig, axs = plt.subplots(rows, columns)\n",
"\n",
"for i, ax in enumerate(np.ndarray.flatten(axs)):\n",
" ax.imshow(x_test[i, :, :, 0])\n",
" color = \"#000000\" if predictions[i] == y_test[i] else \"#ff7f0e\"\n",
" ax.set_xlabel(f\"prediction={predictions[i]}\", color=color)\n",
" ax.grid(False)\n",
" ax.set_yticks([])\n",
" ax.set_xticks([])\n",
"\n",
"fig.tight_layout()"
],
"execution_count": null,
"outputs": []
}
]
}