blob: 2c7eb6eda68fb875a4da81149e209fcf32589d0e [file] [log] [blame]
{
"cells": [
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "PZtRtMMUZHJS"
},
"source": [
"##### Copyright 2020 Google LLC.\n",
"\n",
"Licensed under the Apache License, Version 2.0 (the \"License\");"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {
"cellView": "form",
"colab": {},
"colab_type": "code",
"id": "TouZL3JZZSQe"
},
"outputs": [],
"source": [
"#@title License header\n",
"# Copyright 2020 Google LLC\n",
"#\n",
"# Licensed under the Apache License, Version 2.0 (the \"License\");\n",
"# you may not use this file except in compliance with the License.\n",
"# You may obtain a copy of the License at\n",
"#\n",
"# https://www.apache.org/licenses/LICENSE-2.0\n",
"#\n",
"# Unless required by applicable law or agreed to in writing, software\n",
"# distributed under the License is distributed on an \"AS IS\" BASIS,\n",
"# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n",
"# See the License for the specific language governing permissions and\n",
"# limitations under the License."
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "O6c3qfq5Zv57"
},
"source": [
"# MNIST Model TensorFlow Training, IREE Execution\n",
"\n",
"## Overview\n",
"\n",
"This notebook creates and trains a TensorFlow 2.0 model for recognizing handwritten digits using the [MNIST dataset](https://en.wikipedia.org/wiki/MNIST_database), then compiles and executes that trained model using IREE.\n",
"\n",
"## Running Locally\n",
"\n",
"* Refer to [using_colab.md](https://google.github.io/iree/using-iree/using-colab) for general information\n",
"* Ensure that you have a recent version of TensorFlow 2.0 [installed on your system](https://www.tensorflow.org/install)\n",
"* Enable IREE/TF integration by adding to your user.bazelrc: `build --define=iree_tensorflow=true`\n",
"* Start colab by running `python colab/start_colab_kernel.py` (see that file for additional instructions)\n",
"* Note: you may need to restart your runtime in order to re-run certain cells. Some of the APIs are not yet stable enough for repeated invocations"
]
},
{
"cell_type": "code",
"execution_count": 1,
"metadata": {
"cellView": "both",
"colab": {
"height": 51
},
"colab_type": "code",
"executionInfo": {
"elapsed": 6750,
"status": "ok",
"timestamp": 1598547312105,
"user": {
"displayName": "",
"photoUrl": "",
"userId": ""
},
"user_tz": 420
},
"id": "EPF7RGQDYK-M",
"outputId": "4aa093c1-5147-4928-9eb3-ba571c3c5326"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"TensorFlow version: 2.4.0\n",
"Numpy version: 1.16.4\n"
]
}
],
"source": [
"#@title Imports\n",
"\n",
"from pyiree import rt as ireert\n",
"from pyiree.tf import compiler as ireec\n",
"from pyiree.tf.support import tf_utils\n",
"\n",
"from matplotlib import pyplot as plt\n",
"import numpy as np\n",
"import tensorflow as tf\n",
"\n",
"plt.style.use(\"seaborn-whitegrid\")\n",
"plt.rcParams[\"font.family\"] = \"monospace\"\n",
"\n",
"# Print version information for future notebook users to reference.\n",
"print(\"TensorFlow version: \", tf.__version__)\n",
"print(\"Numpy version: \", np.__version__)"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "5vkQOMOMbXdy"
},
"source": [
"# Create and Train MNIST Model in TensorFlow\n",
"\n",
"The specific details of the training process here aren't critical to the model compilation and execution through IREE."
]
},
{
"cell_type": "code",
"execution_count": 2,
"metadata": {
"cellView": "both",
"colab": {
"height": 316
},
"colab_type": "code",
"executionInfo": {
"elapsed": 2696,
"status": "ok",
"timestamp": 1598547323963,
"user": {
"displayName": "",
"photoUrl": "",
"userId": ""
},
"user_tz": 420
},
"id": "XPo8ATGqqZbW",
"outputId": "8a98c0eb-5ba9-41d1-a702-8c15568bd946"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Sample image from the dataset:\n"
]
},
{
"data": {
"image/png": "iVBORw0KGgoAAAANSUhEUgAAAQwAAAEaCAYAAADkA2kVAAAABHNCSVQICAgIfAhkiAAAAAlwSFlz\nAAALEgAACxIB0t1+/AAAADl0RVh0U29mdHdhcmUAbWF0cGxvdGxpYiB2ZXJzaW9uIDMuMC4zLCBo\ndHRwOi8vbWF0cGxvdGxpYi5vcmcvnQurowAAED5JREFUeJzt3X1szef/x/FXT9sN07KJGTFVlrir\ncrRpUbT6nY4xrJgYi2QWtZXQsptks5lZRuamUzchGYtlk5DYjCFt52braoxUGLqoGatNNzOtdhTn\n9PfHfj1ZY855V+9Un4+/ml6fm+sc8TzXOT4+x6+8vLxcAGDgqO8JAGg4CAYAM4IBwIxgADAjGADM\nCAYAM4JRhwoLC+V0OtWzZ09NmDChvqdzV5o/f76cTqe6du2qnJycKu0bHx9f5X2qe87GplEH49Sp\nU5o8ebIiIyPVr18/zZgxo1bP16ZNG+Xm5urtt9+uleNnZ2dr1qxZkqRBgwbp77//liRlZWVp/Pjx\nCgsL02uvvVZpn/T0dPXo0UNOp1NOp1Px8fGesfLyci1atEj9+/dXdHS0UlNTVVJS4hn/5ZdfNGXK\nFDmdTsXExGjLli3VfgxvvvmmcnNz1a5du2ofq77POXz4cM/z6nQ61b17d73zzjs1eo66FlDfE6hP\nycnJmjBhgtatW6fS0lLt3LmzvqdULcePH1ePHj1UWFio4OBgNWvWTJIUFBSkKVOmKCcnR9euXbtl\nv2HDhmnx4sW3/H7Hjh3auXOnPvvsMwUFBWnGjBlauXKlXn31VblcLk2bNk2xsbFavny5/Pz89Pvv\nv9f6Y2xIvvzyS8/Pbrdb8fHxSkhIqMcZVV+jXWFcunRJZ8+e1dixY+Xv76/g4GCNHz/eM75v3z6N\nGjVKffr00eOPP661a9eq4qLY+Ph4JSUlKSYmRmvWrFFMTIzmzp0rSSooKFCXLl20atUqRUREaOTI\nkTpx4oRpTi6XSytWrFB8fLz69++vBQsW6MaNG+bH9MMPPygsLEzHjh1Tz549Pb+Pjo5WQkKCWrRo\nYT6WJJ0/f15Op1Nt2rRRs2bNFBcXp9OnT0uSDh06pKKiIqWmpuqBBx5Qs2bN1LFjxyod/06kpKSo\nf//+ioqK0uTJk/Xzzz9XGv/22281YMAAxcbGVlrxVPe5zczM1NChQ1VYWHhH887JyZHD4VBUVNQd\n7X+3aLTBaNmypdq1a6e5c+fq4MGDun79eqXx8vJyzZ07VwcOHND69eu1fv167d692zM+adIkJSYm\nKisrS7t27dK2bdtUVlbmGb98+bL279+vcePGafbs2bJcgb9+/Xp99dVX2rhxozIyMpSfn6+PP/7Y\n535LlixRZGSksrKyNH36dKWkpGjHjh0aNGiQ6bnYs2ePoqOjNWrUqEqPcfjw4SooKNBvv/2m0tJS\n7d27V7GxsZKkvLw8derUSXPmzFF0dLQmTpyo/Px80/mqo1u3btq2bZv279+vsLAwpaSkVBo/fPiw\ndu3apfT0dM2fP1/nz5+XdOfPbYUrV67ozJkzVYrMv23ZskVPPfWU/Pz87mj/u0WjDYbD4dBHH30k\nf39/JScnq3///lqxYoVnPC4uTpGRkQoMDNSjjz6q6OhonTx50jMeEhKiDh06qGPHjgoKClJwcLAu\nX77sGZ88ebLuu+8+TZgwQefPn9fZs2d9zmnz5s1KTk5WmzZt1Lx5c02cOFEZGRk+95s9e7ZWrlyp\n+Ph4HTx4UCEhIcrKytLXX3/tc98nn3xSmZmZys7OVnJyslJTU/XTTz9Jklq3bq1evXpp8ODBioyM\nlMPh8KzCSkpKdPjwYQ0YMEDffPONBgwYoNTUVJ/nq66pU6eqVatW8vf3V2JiovLy8iqNjx8/Xs2b\nN1d4eLh69+6t7OxsSXf+3FZITEzUjz/+qPbt21d5zsXFxcrKytLo0aOrvO/dplF/hhESEqIlS5bI\n7XbrwIEDmjlzpnr27KnY2FgdPXpU77//vk6dOqWbN2/q2rVrlZbcDodD/v7+8vf3lyQFBATo5s2b\nnvFWrVp5fh8cHKyLFy/6XLJfuHBBr7zyihyOfzrudrvVunVrr/scOXJEL7zwgq5du6aAgABFRkbq\n+vXrGjZsmNLT09WvXz+v+3fu3Nnzc0JCgjZv3qzs7Gx16tRJK1euVF5ennJycnT//ffrjTfe0IIF\nCzRv3jw1bdpULVq00NixYyVJzz33nNLS0lRUVFTltz5WLpdLaWlp2rlzpy5fviy32y232y2Xy+X5\nc3jooYc827dq1Up//vmnpDt7bmvK9u3b1bVrV4WGhtbJ+WpTow5GBYfDoX79+ikqKkr5+fmKjY1V\namqqJk2apHXr1ikwMFDTp0/3+bbi3+MXL15U+/btdfPmTRUXF3sCIkmBgYFyu9237P/II49o4cKF\n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\n",
"text/plain": [
"\u003cFigure size 600x400 with 1 Axes\u003e"
]
},
"metadata": {
"tags": []
},
"output_type": "display_data"
}
],
"source": [
"#@title Load MNIST dataset, setup training and evaluation\n",
"\n",
"# 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 floats:\n",
"x_train = x_train / 255\n",
"x_test = x_test / 255\n",
"\n",
"# Explicitly cast to float32 because numpy defaults to double precision and\n",
"# IREE uses single precision:\n",
"x_train = x_train.astype(np.float32)\n",
"x_test = x_test.astype(np.float32)\n",
"\n",
"print(\"Sample image from the dataset:\")\n",
"sample_index = np.random.randint(x_train.shape[0])\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()"
]
},
{
"cell_type": "code",
"execution_count": 3,
"metadata": {
"cellView": "both",
"colab": {},
"colab_type": "code",
"executionInfo": {
"elapsed": 47,
"status": "ok",
"timestamp": 1598547329812,
"user": {
"displayName": "",
"photoUrl": "",
"userId": ""
},
"user_tz": 420
},
"id": "tHq96SIJcNfx"
},
"outputs": [],
"source": [
"#@title Define a DNN model using tf.keras API\n",
"\n",
"def simple_dnn(num_classes):\n",
" \"\"\"Creates a simple multi-layer perceptron model.\"\"\"\n",
"\n",
" model = tf.keras.models.Sequential()\n",
" # Flatten to a 1d array (e.g. 28x28x1 -\u003e 784).\n",
" model.add(tf.keras.layers.Flatten())\n",
" # Fully-connected neural layer with 128 neurons, RELU activation.\n",
" model.add(tf.keras.layers.Dense(128, activation=\"relu\"))\n",
" # Fully-connected neural layer returning probability scores for each class.\n",
" model.add(tf.keras.layers.Dense(num_classes, activation=\"softmax\"))\n",
" return model"
]
},
{
"cell_type": "code",
"execution_count": 4,
"metadata": {
"cellView": "both",
"colab": {},
"colab_type": "code",
"executionInfo": {
"elapsed": 232,
"status": "ok",
"timestamp": 1598547335213,
"user": {
"displayName": "",
"photoUrl": "",
"userId": ""
},
"user_tz": 420
},
"id": "43BH_9YcsGs8"
},
"outputs": [],
"source": [
"#@markdown ### Training Parameters\n",
"\n",
"batch_size = 32 #@param { type: \"slider\", min: 10, max: 400 }\n",
"num_epochs = 8 #@param { type: \"slider\", min: 1, max: 20 }"
]
},
{
"cell_type": "code",
"execution_count": 5,
"metadata": {
"cellView": "both",
"colab": {
"height": 306
},
"colab_type": "code",
"executionInfo": {
"elapsed": 75641,
"status": "ok",
"timestamp": 1598547494584,
"user": {
"displayName": "",
"photoUrl": "",
"userId": ""
},
"user_tz": 420
},
"id": "7Gdxh7qWcPSO",
"outputId": "9dd7a26a-1fad-41a8-e03e-0c775be0ee25"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Epoch 1/8\n",
"1688/1688 [==============================] - 11s 6ms/step - loss: 1.0551 - accuracy: 0.7235 - val_loss: 0.3218 - val_accuracy: 0.9170\n",
"Epoch 2/8\n",
"1688/1688 [==============================] - 9s 5ms/step - loss: 0.3732 - accuracy: 0.8962 - val_loss: 0.2602 - val_accuracy: 0.9283\n",
"Epoch 3/8\n",
"1688/1688 [==============================] - 9s 5ms/step - loss: 0.3121 - accuracy: 0.9130 - val_loss: 0.2342 - val_accuracy: 0.9352\n",
"Epoch 4/8\n",
"1688/1688 [==============================] - 9s 5ms/step - loss: 0.2750 - accuracy: 0.9227 - val_loss: 0.2102 - val_accuracy: 0.9427\n",
"Epoch 5/8\n",
"1688/1688 [==============================] - 9s 5ms/step - loss: 0.2492 - accuracy: 0.9292 - val_loss: 0.1960 - val_accuracy: 0.9453\n",
"Epoch 6/8\n",
"1688/1688 [==============================] - 9s 5ms/step - loss: 0.2303 - accuracy: 0.9359 - val_loss: 0.1819 - val_accuracy: 0.9510\n",
"Epoch 7/8\n",
"1688/1688 [==============================] - 9s 5ms/step - loss: 0.2173 - accuracy: 0.9392 - val_loss: 0.1719 - val_accuracy: 0.9527\n",
"Epoch 8/8\n",
"1688/1688 [==============================] - 9s 5ms/step - loss: 0.2002 - accuracy: 0.9432 - val_loss: 0.1620 - val_accuracy: 0.9568\n"
]
},
{
"data": {
"text/plain": [
"\u003ctensorflow.python.keras.callbacks.History at 0x7fb369d3b208\u003e"
]
},
"execution_count": 5,
"metadata": {
"tags": []
},
"output_type": "execute_result"
}
],
"source": [
"#@title Train the Keras model\n",
"\n",
"tf_model = simple_dnn(NUM_CLASSES)\n",
"# Stateful optimizers like Adam create variable incompatible with compilation as\n",
"# currently implemented.\n",
"tf_model.compile(optimizer=\"sgd\", loss=\"sparse_categorical_crossentropy\", metrics=\"accuracy\")\n",
"tf_model.fit(x_train, y_train, batch_size, num_epochs, validation_split=0.1)"
]
},
{
"cell_type": "markdown",
"metadata": {
"colab_type": "text",
"id": "nZdVUd_dgTtc"
},
"source": [
"# Compile and Execute MNIST Model using IREE"
]
},
{
"cell_type": "code",
"execution_count": 7,
"metadata": {
"colab": {
"height": 819
},
"colab_type": "code",
"executionInfo": {
"elapsed": 2462,
"status": "ok",
"timestamp": 1598547512056,
"user": {
"displayName": "",
"photoUrl": "",
"userId": ""
},
"user_tz": 420
},
"id": "DmespEaFcSEL",
"outputId": "72c57513-92d3-4989-bb3b-4cac42968038"
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Imported MLIR:\n",
" \n",
"\n",
"module attributes {tf.versions = {bad_consumers = [], min_consumer = 12 : i32, producer = 506 : i32}} {\n",
" flow.variable @\"__iree_flow___sm_node15__model.layer-1.kernel\" opaque\u003c\"\", \"0xDEADBEEF\"\u003e : tensor\u003c784x128xf32\u003e attributes {sym_visibility = \"private\"}\n",
" flow.variable @\"__iree_flow___sm_node16__model.layer-1.bias\" opaque\u003c\"\", \"0xDEADBEEF\"\u003e : tensor\u003c128xf32\u003e attributes {sym_visibility = \"private\"}\n",
" flow.variable @\"__iree_flow___sm_node21__model.layer-2.kernel\" opaque\u003c\"\", \"0xDEADBEEF\"\u003e : tensor\u003c128x10xf32\u003e attributes {sym_visibility = \"private\"}\n",
" flow.variable @\"__iree_flow___sm_node22__model.layer-2.bias\" dense\u003c[-0.0863539576, 0.0952052548, 0.0697797537, -0.078638956, -0.0109204706, 0.178583801, -0.0201483201, 0.145516276, -0.258134842, -0.0348887108]\u003e : tensor\u003c10xf32\u003e attributes {sym_visibility = \"private\"}\n",
" func @predict(%arg0: tensor\u003c1x28x28x1xf32\u003e {tf._user_specified_name = \"x\"}) -\u003e tensor\u003c1x10xf32\u003e attributes {iree.module.export, iree.reflection = {abi = \"sip\", abiv = 1 : i32, sip = \"I8!S5!k0_0R3!_0\"}, tf._input_shapes = [#tf.shape\u003c1x28x28x1\u003e, #tf.shape\u003c*\u003e, #tf.shape\u003c*\u003e, #tf.shape\u003c*\u003e, #tf.shape\u003c*\u003e], tf.signature.is_stateful} {\n",
" %0 = flow.variable.address @\"__iree_flow___sm_node15__model.layer-1.kernel\" : !iree.ptr\u003ctensor\u003c784x128xf32\u003e\u003e\n",
" %1 = flow.variable.address @\"__iree_flow___sm_node16__model.layer-1.bias\" : !iree.ptr\u003ctensor\u003c128xf32\u003e\u003e\n",
" %2 = flow.variable.address @\"__iree_flow___sm_node21__model.layer-2.kernel\" : !iree.ptr\u003ctensor\u003c128x10xf32\u003e\u003e\n",
" %3 = flow.variable.address @\"__iree_flow___sm_node22__model.layer-2.bias\" : !iree.ptr\u003ctensor\u003c10xf32\u003e\u003e\n",
" %4 = mhlo.constant opaque\u003c\"\", \"0xDEADBEEF\"\u003e : tensor\u003c1x128xf32\u003e\n",
" %5 = mhlo.constant dense\u003c0xFF800000\u003e : tensor\u003cf32\u003e\n",
" %6 = mhlo.constant dense\u003c0.000000e+00\u003e : tensor\u003cf32\u003e\n",
" %7 = flow.variable.load.indirect %3 : !iree.ptr\u003ctensor\u003c10xf32\u003e\u003e -\u003e tensor\u003c10xf32\u003e\n",
" %8 = flow.variable.load.indirect %2 : !iree.ptr\u003ctensor\u003c128x10xf32\u003e\u003e -\u003e tensor\u003c128x10xf32\u003e\n",
" %9 = flow.variable.load.indirect %1 : !iree.ptr\u003ctensor\u003c128xf32\u003e\u003e -\u003e tensor\u003c128xf32\u003e\n",
" %10 = flow.variable.load.indirect %0 : !iree.ptr\u003ctensor\u003c784x128xf32\u003e\u003e -\u003e tensor\u003c784x128xf32\u003e\n",
" %11 = \"mhlo.reshape\"(%arg0) : (tensor\u003c1x28x28x1xf32\u003e) -\u003e tensor\u003c1x784xf32\u003e\n",
" %12 = \"mhlo.dot\"(%11, %10) : (tensor\u003c1x784xf32\u003e, tensor\u003c784x128xf32\u003e) -\u003e tensor\u003c1x128xf32\u003e\n",
" %13 = \"mhlo.broadcast_in_dim\"(%9) {broadcast_dimensions = dense\u003c1\u003e : tensor\u003c1xi64\u003e} : (tensor\u003c128xf32\u003e) -\u003e tensor\u003c1x128xf32\u003e\n",
" %14 = mhlo.add %12, %13 : tensor\u003c1x128xf32\u003e\n",
" %15 = mhlo.maximum %14, %4 : tensor\u003c1x128xf32\u003e\n",
" %16 = \"mhlo.dot\"(%15, %8) : (tensor\u003c1x128xf32\u003e, tensor\u003c128x10xf32\u003e) -\u003e tensor\u003c1x10xf32\u003e\n",
" %17 = \"mhlo.broadcast_in_dim\"(%7) {broadcast_dimensions = dense\u003c1\u003e : tensor\u003c1xi64\u003e} : (tensor\u003c10xf32\u003e) -\u003e tensor\u003c1x10xf32\u003e\n",
" %18 = mhlo.add %16, %17 : tensor\u003c1x10xf32\u003e\n",
" %19 = \"mhlo.reduce\"(%18, %5) ( {\n",
" ^bb0(%arg1: tensor\u003cf32\u003e, %arg2: tensor\u003cf32\u003e): // no predecessors\n",
" %26 = mhlo.maximum %arg1, %arg2 : tensor\u003cf32\u003e\n",
" \"mhlo.return\"(%26) : (tensor\u003cf32\u003e) -\u003e ()\n",
" }) {dimensions = dense\u003c1\u003e : tensor\u003c1xi64\u003e} : (tensor\u003c1x10xf32\u003e, tensor\u003cf32\u003e) -\u003e tensor\u003c1xf32\u003e\n",
" %20 = \"mhlo.broadcast_in_dim\"(%19) {broadcast_dimensions = dense\u003c0\u003e : tensor\u003c1xi64\u003e} : (tensor\u003c1xf32\u003e) -\u003e tensor\u003c1x10xf32\u003e\n",
" %21 = mhlo.subtract %18, %20 : tensor\u003c1x10xf32\u003e\n",
" %22 = \"mhlo.exponential\"(%21) : (tensor\u003c1x10xf32\u003e) -\u003e tensor\u003c1x10xf32\u003e\n",
" %23 = \"mhlo.reduce\"(%22, %6) ( {\n",
" ^bb0(%arg1: tensor\u003cf32\u003e, %arg2: tensor\u003cf32\u003e): // no predecessors\n",
" %26 = mhlo.add %arg1, %arg2 : tensor\u003cf32\u003e\n",
" \"mhlo.return\"(%26) : (tensor\u003cf32\u003e) -\u003e ()\n",
" }) {dimensions = dense\u003c1\u003e : tensor\u003c1xi64\u003e} : (tensor\u003c1x10xf32\u003e, tensor\u003cf32\u003e) -\u003e tensor\u003c1xf32\u003e\n",
" %24 = \"mhlo.broadcast_in_dim\"(%23) {broadcast_dimensions = dense\u003c0\u003e : tensor\u003c1xi64\u003e} : (tensor\u003c1xf32\u003e) -\u003e tensor\u003c1x10xf32\u003e\n",
" %25 = mhlo.divide %22, %24 : tensor\u003c1x10xf32\u003e\n",
" return %25 : tensor\u003c1x10xf32\u003e\n",
" }\n",
"}\n"
]
}
],
"source": [
"#@title Wrap the model in a tf.Module with IREE-compatible settings and convert to MLIR.\n",
"\n",
"# Since the model was written in sequential style, explicitly wrap in a module.\n",
"inference_module = tf.Module()\n",
"inference_module.model = tf_model\n",
"\n",
"# Hack: Convert to static shape. Won't be necessary once dynamic shapes are in.\n",
"dynamic_input_shape = list(tf_model.inputs[0].shape)\n",
"dynamic_input_shape[0] = 1 # Make fixed (batch=1)\n",
"\n",
"# Produce a concrete function to compile.\n",
"inference_module.predict = tf.function(input_signature=[\n",
" tf.TensorSpec(dynamic_input_shape, tf_model.inputs[0].dtype)\n",
"])(lambda x: tf_model.call(x, training=False))\n",
"\n",
"# Include the function to compile in the list of exported_names.\n",
"compiler_module = ireec.tf_module_to_compiler_module(\n",
" inference_module, exported_names=[\"predict\"])\n",
"\n",
"print(\"Imported MLIR:\\n\", compiler_module.to_asm(large_element_limit=100))"
]
},
{
"cell_type": "code",
"execution_count": 8,
"metadata": {
"colab": {},
"colab_type": "code",
"executionInfo": {
"elapsed": 48,
"status": "ok",
"timestamp": 1598547515930,
"user": {
"displayName": "",
"photoUrl": "",
"userId": ""
},
"user_tz": 420
},
"id": "G7v-2EbjyggO"
},
"outputs": [],
"source": [
"#@markdown ### Backend Configuration\n",
"\n",
"backend_choice = \"iree_vmla (CPU)\" #@param [ \"iree_vmla (CPU)\", \"iree_llvmjit (CPU)\", \"iree_vulkan (GPU/SwiftShader)\" ]\n",
"backend_choice = backend_choice.split(\" \")[0]\n",
"backend = tf_utils.BackendInfo(backend_choice)"
]
},
{
"cell_type": "code",
"execution_count": 9,
"metadata": {
"colab": {
"height": 51
},
"colab_type": "code",
"executionInfo": {
"elapsed": 727,
"status": "ok",
"timestamp": 1598547519894,
"user": {
"displayName": "",
"photoUrl": "",
"userId": ""
},
"user_tz": 420
},
"id": "IDHI7h3khJr9",
"outputId": "91b29a57-8584-4980-b4b0-24a1d7568805"
},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"Created IREE driver vmla: \u003ciree.bindings.python.pyiree.rt.binding.HalDriver object at 0x7fb362fde928\u003e\n",
"SystemContext driver=\u003ciree.bindings.python.pyiree.rt.binding.HalDriver object at 0x7fb362fde928\u003e\n"
]
}
],
"source": [
"#@title Compile the mhlo MLIR to an IREE backend and prepare a context to execute it\n",
"\n",
"# Compile the MLIR module into a VM module for execution.\n",
"flatbuffer_blob = compiler_module.compile(\n",
" target_backends=backend.compiler_targets)\n",
"vm_module = ireert.VmModule.from_flatbuffer(flatbuffer_blob)\n",
"\n",
"# Register the module with a runtime context.\n",
"config = ireert.Config(backend.driver)\n",
"ctx = ireert.SystemContext(config=config)\n",
"ctx.add_module(vm_module)"
]
},
{
"cell_type": "code",
"execution_count": 10,
"metadata": {
"colab": {
"height": 299
},
"colab_type": "code",
"executionInfo": {
"elapsed": 1212,
"status": "ok",
"timestamp": 1598547527476,
"user": {
"displayName": "",
"photoUrl": "",
"userId": ""
},
"user_tz": 420
},
"id": "S2FYao92Xd6r",
"outputId": "61315fd3-f3ec-4991-c10f-a719039d2d06"
},
"outputs": [
{
"data": {
"image/png": "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\n",
"text/plain": [
"\u003cFigure size 1200x400 with 2 Axes\u003e"
]
},
"metadata": {
"tags": []
},
"output_type": "display_data"
}
],
"source": [
"#@title Execute the compiled module and compare the results with TensorFlow\n",
"\n",
"# Invoke the 'predict' function with a single image as an argument\n",
"iree_prediction = ctx.modules.module.predict(x_train[sample_index][None, :])[0]\n",
"tf_prediction = tf_model.predict(x_train[sample_index][None, :])[0]\n",
"error = tf_prediction - iree_prediction\n",
"\n",
"fig, axs = plt.subplots(1, 2)\n",
"fig.set_figwidth(12)\n",
"\n",
"ax = axs[0]\n",
"ax.plot(iree_prediction, linewidth=2, label=backend.name)\n",
"ax.plot(tf_prediction, linewidth=2, label=\"tf\")\n",
"\n",
"ax.set_title(\"Predictions\")\n",
"ax.set_ylabel(\"Softmax 'Probability'\")\n",
"ax.set_xlabel(\"Digit\")\n",
"ax.set_ylim(0, 1)\n",
"ax.set_xlim(0, 9)\n",
"ax.legend(frameon=True)\n",
"\n",
"\n",
"ax = axs[1]\n",
"ax.plot(error)\n",
"\n",
"ax.set_title(\"Error\")\n",
"ax.set_ylabel(\"Numerical between TF and IREE\")\n",
"ax.set_xlabel(\"Digit\")\n",
"ylim = 1.25 * np.max(np.abs(error))\n",
"ax.set_ylim(-ylim, ylim)\n",
"ax.set_xlim(0, 9)\n",
"\n",
"fig.tight_layout()"
]
}
],
"metadata": {
"colab": {
"collapsed_sections": [],
"last_runtime": {
"build_target": "",
"kind": "local"
},
"name": "mnist_tensorflow.ipynb",
"provenance": []
},
"kernelspec": {
"display_name": "Python 3",
"name": "python3"
}
},
"nbformat": 4,
"nbformat_minor": 0
}