From dc3036059525047adf12459a380e18332e4d6b0e Mon Sep 17 00:00:00 2001 From: Salman Khan Date: Mon, 18 Oct 2021 01:54:40 +0100 Subject: [PATCH 1/5] added example conversion notebook --- docs/keras_to_xcore.ipynb | 284 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 284 insertions(+) create mode 100644 docs/keras_to_xcore.ipynb diff --git a/docs/keras_to_xcore.ipynb b/docs/keras_to_xcore.ipynb new file mode 100644 index 000000000..96cc0db5b --- /dev/null +++ b/docs/keras_to_xcore.ipynb @@ -0,0 +1,284 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": 49, + "id": "12967287", + "metadata": {}, + "outputs": [], + "source": [ + "import tensorflow as tf\n", + "from tflite2xcore import utils, analyze, version\n", + "import tflite2xcore.converter as xcore_conv\n", + "import numpy as np" + ] + }, + { + "cell_type": "markdown", + "id": "b592ad9d-79e0-4304-a005-57eb03bf26ef", + "metadata": {}, + "source": [ + "# Make a Model to convert\n", + "Use Keras to make a model of arbiraty size and shape" + ] + }, + { + "cell_type": "code", + "execution_count": 50, + "id": "39cfdc25", + "metadata": {}, + "outputs": [], + "source": [ + "pool_size = (2, 2)\n", + "input_shape = (3, 3, 4)\n", + "model = tf.keras.Sequential([\n", + " tf.keras.layers.AveragePooling2D(pool_size=pool_size, input_shape=input_shape)\n", + "])\n", + "# is this necessary?\n", + "model.compile()" + ] + }, + { + "cell_type": "markdown", + "id": "50d58472-a59c-45c5-b47c-fc8d571b7677", + "metadata": {}, + "source": [ + "## Convert keras model into a tflite model\n", + "The xcore converter cannot optimise a keras model to run on xcore devices, so it must first be converted into a tflite file." + ] + }, + { + "cell_type": "code", + "execution_count": 51, + "id": "aafe3198", + "metadata": {}, + "outputs": [], + "source": [ + "converter = tf.lite.TFLiteConverter.from_keras_model(model)" + ] + }, + { + "cell_type": "markdown", + "id": "fa2f5c47", + "metadata": {}, + "source": [ + "### Representitive Dataset\n", + "\n", + "Tensorflow can optimise the converted model if you pass it a a representative dataset. This dataset can be a small subset (around ~100-500 samples) of the training or validation data\n", + "\n", + "The below function randomly gemerates this, but see [the tensorflow ducumentation](https://www.tensorflow.org/lite/performance/post_training_quantization) to see how to do this in practice." + ] + }, + { + "cell_type": "code", + "execution_count": 52, + "id": "fa57eb0a", + "metadata": {}, + "outputs": [], + "source": [ + "# As an example use a random dataset\n", + "def representative_dataset():\n", + " batch_size = 8\n", + " for _ in range(100):\n", + " data = np.random.uniform(-0.1, 0.001, (batch_size, *input_shape))\n", + " yield [data.astype(np.float32)]" + ] + }, + { + "cell_type": "markdown", + "id": "52dca7ac-8a6a-4d9c-b4d3-42d0b3fbb622", + "metadata": {}, + "source": [ + "* **tf.lite.Optimize.DEFAULT:** Default optimization strategy that quantizes model weights. Enhanced optimizations are gained by providing a representative dataset that quantizes biases and activations as well. Converter will do its best to reduce size and latency, while minimizing the loss in accuracy.\n", + "\n", + "* **target_spec.supported_ops:** Import TFLITE ops. [Tensorflow docs](https://www.tensorflow.org/lite/guide/ops_select)" + ] + }, + { + "cell_type": "code", + "execution_count": 53, + "id": "7b093742", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "INFO:tensorflow:Assets written to: /var/folders/fg/_pf9q2tj3cl9yfjb392zl7rm0000gn/T/tmpy3xwidz9/assets\n" + ] + }, + { + "name": "stderr", + "output_type": "stream", + "text": [ + "INFO:tensorflow:Assets written to: /var/folders/fg/_pf9q2tj3cl9yfjb392zl7rm0000gn/T/tmpy3xwidz9/assets\n", + "2021-10-18 01:46:23.746990: I tensorflow/core/grappler/devices.cc:78] Number of eligible GPUs (core count >= 8, compute capability >= 0.0): 0 (Note: TensorFlow was not compiled with CUDA or ROCm support)\n", + "2021-10-18 01:46:23.747082: I tensorflow/core/grappler/clusters/single_machine.cc:357] Starting new session\n", + "2021-10-18 01:46:23.747987: I tensorflow/core/grappler/optimizers/meta_optimizer.cc:1144] Optimization results for grappler item: graph_to_optimize\n", + " function_optimizer: function_optimizer did nothing. time = 0.004ms.\n", + " function_optimizer: function_optimizer did nothing. time = 0ms.\n", + "\n", + "2021-10-18 01:46:23.761489: W tensorflow/compiler/mlir/lite/python/tf_tfl_flatbuffer_helpers.cc:345] Ignored output_format.\n", + "2021-10-18 01:46:23.761515: W tensorflow/compiler/mlir/lite/python/tf_tfl_flatbuffer_helpers.cc:348] Ignored drop_control_dependency.\n", + "fully_quantize: 0, inference_type: 6, input_inference_type: 9, output_inference_type: 9\n" + ] + } + ], + "source": [ + "# Set up the converter to convert our float model into int8 quantised model\n", + "#explain https://www.tensorflow.org/lite/performance/post_training_quantization\n", + "converter.optimizations = [tf.lite.Optimize.DEFAULT]\n", + "converter.representative_dataset = representative_dataset\n", + "converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]\n", + "converter.inference_input_type = tf.int8 \n", + "converter.inference_output_type = tf.int8\n", + "\n", + "tflite_model = converter.convert()\n", + "\n", + "# Save the model.\n", + "tflite_model_path = 'avgpooling2d.tflite'\n", + "with open(tflite_model_path, 'wb') as f:\n", + " f.write(tflite_model)" + ] + }, + { + "cell_type": "markdown", + "id": "379a4e6b-7a17-49c8-b289-0714b315f0bc", + "metadata": {}, + "source": [ + "# Optimise model for XCore\n", + "Use `xcore_conv.convert(input_path, output_path)` to make an xcore optimised version of the model." + ] + }, + { + "cell_type": "code", + "execution_count": 54, + "id": "a26aa710", + "metadata": {}, + "outputs": [], + "source": [ + "xcore_optimised_path = 'xcore_model.tflite'\n", + "xcore_conv.convert(tflite_model_path, xcore_optimised_path)" + ] + }, + { + "cell_type": "markdown", + "id": "73ee3d58-55da-4c5b-9ec5-46c82321b0a8", + "metadata": {}, + "source": [ + "# Check it worked\n", + "To check if it worked, we can use the interpreters to run the models and make sure that they produce the same output.\n", + "\n", + "For normal tensorflow tflite models, use `tensorflow.lite.Interpreter`. For XCore optimised models, the `XCOREInterpreter` must be used." + ] + }, + { + "cell_type": "code", + "execution_count": 55, + "id": "f37269b5", + "metadata": {}, + "outputs": [], + "source": [ + "from xcore_interpreters import XCOREInterpreter" + ] + }, + { + "cell_type": "code", + "execution_count": 56, + "id": "5ada7955", + "metadata": {}, + "outputs": [], + "source": [ + "tf_interpreter = tf.lite.Interpreter(model_path=tflite_model_path)\n", + "tf_interpreter.allocate_tensors()\n", + "\n", + "tf_input_details = tf_interpreter.get_input_details()\n", + "tf_output_details = tf_interpreter.get_output_details()\n", + "\n", + "tf_input_shape = tf_input_details[0]['shape']\n", + "# Fill with 126 so that xcore can be given same input\n", + "tf_input_data = np.array(np.random.randint(126, 127, tf_input_shape), dtype=np.int8)\n", + "\n", + "tf_interpreter.set_tensor(tf_input_details[0]['index'], tf_input_data)\n", + "\n", + "tf_interpreter.invoke()\n", + "tf_output_data = tf_interpreter.get_tensor(tf_output_details[0]['index'])" + ] + }, + { + "cell_type": "code", + "execution_count": 60, + "id": "62d94234", + "metadata": {}, + "outputs": [], + "source": [ + "xcore_interpreter = XCOREInterpreter(model_path=xcore_optimised_path)\n", + "xcore_interpreter.allocate_tensors()\n", + "\n", + "xcore_input_details = xcore_interpreter.get_input_details()\n", + "xcore_output_details = xcore_interpreter.get_output_details()\n", + "\n", + "xcore_input_shape = xcore_input_details[0]['shape']\n", + "# Fill with 126 so that xcore converter has the same inputs\n", + "xcore_input_data = np.array(np.random.randint(126, 127, xcore_input_shape), dtype=np.int8)\n", + "\n", + "xcore_interpreter.set_tensor(xcore_input_details[0]['index'], xcore_input_data)\n", + "\n", + "xcore_interpreter.invoke()\n", + "xcore_output_data = xcore_interpreter.get_tensor(xcore_output_details[0]['index'])" + ] + }, + { + "cell_type": "code", + "execution_count": 59, + "id": "3f8e306c-f6f1-42d3-b1b3-384142733fbe", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Both outputs equal:\n", + "True\n" + ] + } + ], + "source": [ + "print(\"Both outputs equal:\")\n", + "print(np.array_equal(xcore_output_data[0], tf_output_data[0]))\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "5ee7356b-80a7-40fe-a0a7-376444114c13", + "metadata": {}, + "outputs": [], + "source": [] + } + ], + "metadata": { + "interpreter": { + "hash": "0436a0dea52299ed28644175e220c962eae431d92561f4f402c0c00186dcb06f" + }, + "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.12" + } + }, + "nbformat": 4, + "nbformat_minor": 5 +} From f28ac9c6e1b08c6e2bccfc90aa3d6e47a332a334 Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 18 Oct 2021 01:10:03 +0000 Subject: [PATCH 2/5] Auto-formatted Code --- experimental/xformer/XCoreOptMain.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/experimental/xformer/XCoreOptMain.cpp b/experimental/xformer/XCoreOptMain.cpp index 9133d2308..bd20f0a06 100644 --- a/experimental/xformer/XCoreOptMain.cpp +++ b/experimental/xformer/XCoreOptMain.cpp @@ -8,9 +8,9 @@ #include "mlir/IR/AsmState.h" #include "mlir/Parser.h" #include "mlir/Pass/PassManager.h" +#include "tensorflow/compiler/mlir/lite/ir/tfl_ops.h" #include "llvm/Support/InitLLVM.h" #include "llvm/Support/ToolOutputFile.h" -#include "tensorflow/compiler/mlir/lite/ir/tfl_ops.h" using namespace llvm; using namespace mlir; From 875516932b8cea4af1f82691766516b0c75ec793 Mon Sep 17 00:00:00 2001 From: Salman Khan Date: Fri, 29 Oct 2021 12:50:18 +0100 Subject: [PATCH 3/5] added image --- docs/conversion_process.jpg | Bin 0 -> 69852 bytes docs/keras_to_xcore.ipynb | 72 +++++++++++++++++++++++++----------- 2 files changed, 50 insertions(+), 22 deletions(-) create mode 100644 docs/conversion_process.jpg diff --git a/docs/conversion_process.jpg b/docs/conversion_process.jpg new file mode 100644 index 0000000000000000000000000000000000000000..966c0b0c7813ddcf700cb19ac69f869940cdfc22 GIT binary patch literal 69852 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" + ] + }, { "cell_type": "code", - "execution_count": 49, + "execution_count": 73, "id": "12967287", "metadata": {}, "outputs": [], @@ -24,7 +44,7 @@ }, { "cell_type": "code", - "execution_count": 50, + "execution_count": 74, "id": "39cfdc25", "metadata": {}, "outputs": [], @@ -49,7 +69,7 @@ }, { "cell_type": "code", - "execution_count": 51, + "execution_count": 75, "id": "aafe3198", "metadata": {}, "outputs": [], @@ -71,7 +91,7 @@ }, { "cell_type": "code", - "execution_count": 52, + "execution_count": 76, "id": "fa57eb0a", "metadata": {}, "outputs": [], @@ -96,7 +116,7 @@ }, { "cell_type": "code", - "execution_count": 53, + "execution_count": 77, "id": "7b093742", "metadata": {}, "outputs": [ @@ -104,22 +124,22 @@ "name": "stdout", "output_type": "stream", "text": [ - "INFO:tensorflow:Assets written to: /var/folders/fg/_pf9q2tj3cl9yfjb392zl7rm0000gn/T/tmpy3xwidz9/assets\n" + "INFO:tensorflow:Assets written to: /var/folders/fg/_pf9q2tj3cl9yfjb392zl7rm0000gn/T/tmpoz298kvf/assets\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "INFO:tensorflow:Assets written to: /var/folders/fg/_pf9q2tj3cl9yfjb392zl7rm0000gn/T/tmpy3xwidz9/assets\n", - "2021-10-18 01:46:23.746990: I tensorflow/core/grappler/devices.cc:78] Number of eligible GPUs (core count >= 8, compute capability >= 0.0): 0 (Note: TensorFlow was not compiled with CUDA or ROCm support)\n", - "2021-10-18 01:46:23.747082: I tensorflow/core/grappler/clusters/single_machine.cc:357] Starting new session\n", - "2021-10-18 01:46:23.747987: I tensorflow/core/grappler/optimizers/meta_optimizer.cc:1144] Optimization results for grappler item: graph_to_optimize\n", - " function_optimizer: function_optimizer did nothing. time = 0.004ms.\n", + "INFO:tensorflow:Assets written to: /var/folders/fg/_pf9q2tj3cl9yfjb392zl7rm0000gn/T/tmpoz298kvf/assets\n", + "2021-10-29 12:36:01.609244: I tensorflow/core/grappler/devices.cc:78] Number of eligible GPUs (core count >= 8, compute capability >= 0.0): 0 (Note: TensorFlow was not compiled with CUDA or ROCm support)\n", + "2021-10-29 12:36:01.609484: I tensorflow/core/grappler/clusters/single_machine.cc:357] Starting new session\n", + "2021-10-29 12:36:01.611708: I tensorflow/core/grappler/optimizers/meta_optimizer.cc:1144] Optimization results for grappler item: graph_to_optimize\n", + " function_optimizer: function_optimizer did nothing. time = 0.006ms.\n", " function_optimizer: function_optimizer did nothing. time = 0ms.\n", "\n", - "2021-10-18 01:46:23.761489: W tensorflow/compiler/mlir/lite/python/tf_tfl_flatbuffer_helpers.cc:345] Ignored output_format.\n", - "2021-10-18 01:46:23.761515: W tensorflow/compiler/mlir/lite/python/tf_tfl_flatbuffer_helpers.cc:348] Ignored drop_control_dependency.\n", + "2021-10-29 12:36:01.635675: W tensorflow/compiler/mlir/lite/python/tf_tfl_flatbuffer_helpers.cc:345] Ignored output_format.\n", + "2021-10-29 12:36:01.635705: W tensorflow/compiler/mlir/lite/python/tf_tfl_flatbuffer_helpers.cc:348] Ignored drop_control_dependency.\n", "fully_quantize: 0, inference_type: 6, input_inference_type: 9, output_inference_type: 9\n" ] } @@ -152,7 +172,7 @@ }, { "cell_type": "code", - "execution_count": 54, + "execution_count": 78, "id": "a26aa710", "metadata": {}, "outputs": [], @@ -174,7 +194,7 @@ }, { "cell_type": "code", - "execution_count": 55, + "execution_count": 79, "id": "f37269b5", "metadata": {}, "outputs": [], @@ -184,7 +204,7 @@ }, { "cell_type": "code", - "execution_count": 56, + "execution_count": 80, "id": "5ada7955", "metadata": {}, "outputs": [], @@ -207,7 +227,7 @@ }, { "cell_type": "code", - "execution_count": 60, + "execution_count": 81, "id": "62d94234", "metadata": {}, "outputs": [], @@ -230,7 +250,7 @@ }, { "cell_type": "code", - "execution_count": 59, + "execution_count": 83, "id": "3f8e306c-f6f1-42d3-b1b3-384142733fbe", "metadata": {}, "outputs": [ @@ -238,14 +258,14 @@ "name": "stdout", "output_type": "stream", "text": [ - "Both outputs equal:\n", - "True\n" + "Both models' output the same result?\n", + "yes\n" ] } ], "source": [ - "print(\"Both outputs equal:\")\n", - "print(np.array_equal(xcore_output_data[0], tf_output_data[0]))\n" + "print(\"Both models' output the same result?\")\n", + "print(\"yes\" if np.array_equal(xcore_output_data[0], tf_output_data[0]) else \"no\")\n" ] }, { @@ -255,6 +275,14 @@ "metadata": {}, "outputs": [], "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cbb4f40b-0f94-45e7-b398-dda5d002294c", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { From 886667a9add19fb4b6523f6bbb1a50ca8e7a9f71 Mon Sep 17 00:00:00 2001 From: Salman Khan Date: Wed, 9 Mar 2022 10:54:39 +0000 Subject: [PATCH 4/5] changed to use python module --- docs/keras_to_xcore.ipynb | 165 +++++++++++++++++++++++++++----------- 1 file changed, 116 insertions(+), 49 deletions(-) diff --git a/docs/keras_to_xcore.ipynb b/docs/keras_to_xcore.ipynb index 39c823ad8..05c5a01f5 100644 --- a/docs/keras_to_xcore.ipynb +++ b/docs/keras_to_xcore.ipynb @@ -22,15 +22,76 @@ }, { "cell_type": "code", - "execution_count": 73, + "execution_count": 10, + "id": "8729a280-96ca-49aa-941e-ed0bf785c086", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Requirement already satisfied: xmos_ai_tools in /usr/local/anaconda3/lib/python3.8/site-packages (0.1.4)\n", + "Requirement already satisfied: numpy<2.0 in /usr/local/anaconda3/lib/python3.8/site-packages (from xmos_ai_tools) (1.20.1)\n", + "Requirement already satisfied: tensorflow in /usr/local/anaconda3/lib/python3.8/site-packages (2.8.0)\n", + "Requirement already satisfied: libclang>=9.0.1 in /usr/local/anaconda3/lib/python3.8/site-packages (from tensorflow) (13.0.0)\n", + "Requirement already satisfied: numpy>=1.20 in /usr/local/anaconda3/lib/python3.8/site-packages (from tensorflow) (1.20.1)\n", + "Requirement already satisfied: typing-extensions>=3.6.6 in /usr/local/anaconda3/lib/python3.8/site-packages (from tensorflow) (3.7.4.3)\n", + "Requirement already satisfied: tensorboard<2.9,>=2.8 in /usr/local/anaconda3/lib/python3.8/site-packages (from tensorflow) (2.8.0)\n", + "Requirement already satisfied: keras<2.9,>=2.8.0rc0 in /usr/local/anaconda3/lib/python3.8/site-packages (from tensorflow) (2.8.0)\n", + "Requirement already satisfied: opt-einsum>=2.3.2 in /usr/local/anaconda3/lib/python3.8/site-packages (from tensorflow) (3.3.0)\n", + "Requirement already satisfied: termcolor>=1.1.0 in /usr/local/anaconda3/lib/python3.8/site-packages (from tensorflow) (1.1.0)\n", + "Requirement already satisfied: google-pasta>=0.1.1 in /usr/local/anaconda3/lib/python3.8/site-packages (from tensorflow) (0.2.0)\n", + "Requirement already satisfied: six>=1.12.0 in /usr/local/anaconda3/lib/python3.8/site-packages (from tensorflow) (1.15.0)\n", + "Requirement already satisfied: gast>=0.2.1 in /usr/local/anaconda3/lib/python3.8/site-packages (from tensorflow) (0.5.3)\n", + "Requirement already satisfied: wrapt>=1.11.0 in /usr/local/anaconda3/lib/python3.8/site-packages (from tensorflow) (1.12.1)\n", + "Requirement already satisfied: h5py>=2.9.0 in /usr/local/anaconda3/lib/python3.8/site-packages (from tensorflow) (2.10.0)\n", + "Requirement already satisfied: protobuf>=3.9.2 in /usr/local/anaconda3/lib/python3.8/site-packages (from tensorflow) (3.19.4)\n", + "Requirement already satisfied: astunparse>=1.6.0 in /usr/local/anaconda3/lib/python3.8/site-packages (from tensorflow) (1.6.3)\n", + "Requirement already satisfied: tf-estimator-nightly==2.8.0.dev2021122109 in /usr/local/anaconda3/lib/python3.8/site-packages (from tensorflow) (2.8.0.dev2021122109)\n", + "Requirement already satisfied: flatbuffers>=1.12 in /usr/local/anaconda3/lib/python3.8/site-packages (from tensorflow) (2.0)\n", + "Requirement already satisfied: keras-preprocessing>=1.1.1 in /usr/local/anaconda3/lib/python3.8/site-packages (from tensorflow) (1.1.2)\n", + "Requirement already satisfied: grpcio<2.0,>=1.24.3 in /usr/local/anaconda3/lib/python3.8/site-packages (from tensorflow) (1.44.0)\n", + "Requirement already satisfied: setuptools in /usr/local/anaconda3/lib/python3.8/site-packages (from tensorflow) (52.0.0.post20210125)\n", + "Requirement already satisfied: absl-py>=0.4.0 in /usr/local/anaconda3/lib/python3.8/site-packages (from tensorflow) (1.0.0)\n", + "Requirement already satisfied: tensorflow-io-gcs-filesystem>=0.23.1 in /usr/local/anaconda3/lib/python3.8/site-packages (from tensorflow) (0.24.0)\n", + "Requirement already satisfied: wheel<1.0,>=0.23.0 in /usr/local/anaconda3/lib/python3.8/site-packages (from astunparse>=1.6.0->tensorflow) (0.36.2)\n", + "Requirement already satisfied: google-auth-oauthlib<0.5,>=0.4.1 in /usr/local/anaconda3/lib/python3.8/site-packages (from tensorboard<2.9,>=2.8->tensorflow) (0.4.6)\n", + "Requirement already satisfied: tensorboard-data-server<0.7.0,>=0.6.0 in /usr/local/anaconda3/lib/python3.8/site-packages (from tensorboard<2.9,>=2.8->tensorflow) (0.6.1)\n", + "Requirement already satisfied: requests<3,>=2.21.0 in /usr/local/anaconda3/lib/python3.8/site-packages (from tensorboard<2.9,>=2.8->tensorflow) (2.25.1)\n", + "Requirement already satisfied: markdown>=2.6.8 in /usr/local/anaconda3/lib/python3.8/site-packages (from tensorboard<2.9,>=2.8->tensorflow) (3.3.6)\n", + "Requirement already satisfied: tensorboard-plugin-wit>=1.6.0 in /usr/local/anaconda3/lib/python3.8/site-packages (from tensorboard<2.9,>=2.8->tensorflow) (1.8.1)\n", + "Requirement already satisfied: google-auth<3,>=1.6.3 in /usr/local/anaconda3/lib/python3.8/site-packages (from tensorboard<2.9,>=2.8->tensorflow) (2.6.0)\n", + "Requirement already satisfied: werkzeug>=0.11.15 in /usr/local/anaconda3/lib/python3.8/site-packages (from tensorboard<2.9,>=2.8->tensorflow) (1.0.1)\n", + "Requirement already satisfied: pyasn1-modules>=0.2.1 in /usr/local/anaconda3/lib/python3.8/site-packages (from google-auth<3,>=1.6.3->tensorboard<2.9,>=2.8->tensorflow) (0.2.8)\n", + "Requirement already satisfied: cachetools<6.0,>=2.0.0 in /usr/local/anaconda3/lib/python3.8/site-packages (from google-auth<3,>=1.6.3->tensorboard<2.9,>=2.8->tensorflow) (5.0.0)\n", + "Requirement already satisfied: rsa<5,>=3.1.4 in /usr/local/anaconda3/lib/python3.8/site-packages (from google-auth<3,>=1.6.3->tensorboard<2.9,>=2.8->tensorflow) (4.8)\n", + "Requirement already satisfied: requests-oauthlib>=0.7.0 in /usr/local/anaconda3/lib/python3.8/site-packages (from google-auth-oauthlib<0.5,>=0.4.1->tensorboard<2.9,>=2.8->tensorflow) (1.3.1)\n", + "Requirement already satisfied: importlib-metadata>=4.4 in /usr/local/anaconda3/lib/python3.8/site-packages (from markdown>=2.6.8->tensorboard<2.9,>=2.8->tensorflow) (4.11.2)\n", + "Requirement already satisfied: zipp>=0.5 in /usr/local/anaconda3/lib/python3.8/site-packages (from importlib-metadata>=4.4->markdown>=2.6.8->tensorboard<2.9,>=2.8->tensorflow) (3.4.1)\n", + "Requirement already satisfied: pyasn1<0.5.0,>=0.4.6 in /usr/local/anaconda3/lib/python3.8/site-packages (from pyasn1-modules>=0.2.1->google-auth<3,>=1.6.3->tensorboard<2.9,>=2.8->tensorflow) (0.4.8)\n", + "Requirement already satisfied: chardet<5,>=3.0.2 in /usr/local/anaconda3/lib/python3.8/site-packages (from requests<3,>=2.21.0->tensorboard<2.9,>=2.8->tensorflow) (4.0.0)\n", + "Requirement already satisfied: certifi>=2017.4.17 in /usr/local/anaconda3/lib/python3.8/site-packages (from requests<3,>=2.21.0->tensorboard<2.9,>=2.8->tensorflow) (2020.12.5)\n", + "Requirement already satisfied: idna<3,>=2.5 in /usr/local/anaconda3/lib/python3.8/site-packages (from requests<3,>=2.21.0->tensorboard<2.9,>=2.8->tensorflow) (2.10)\n", + "Requirement already satisfied: urllib3<1.27,>=1.21.1 in /usr/local/anaconda3/lib/python3.8/site-packages (from requests<3,>=2.21.0->tensorboard<2.9,>=2.8->tensorflow) (1.26.4)\n", + "Requirement already satisfied: oauthlib>=3.0.0 in /usr/local/anaconda3/lib/python3.8/site-packages (from requests-oauthlib>=0.7.0->google-auth-oauthlib<0.5,>=0.4.1->tensorboard<2.9,>=2.8->tensorflow) (3.2.0)\n" + ] + } + ], + "source": [ + "! pip install xmos_ai_tools\n", + "! pip install tensorflow" + ] + }, + { + "cell_type": "code", + "execution_count": 11, "id": "12967287", "metadata": {}, "outputs": [], "source": [ "import tensorflow as tf\n", - "from tflite2xcore import utils, analyze, version\n", - "import tflite2xcore.converter as xcore_conv\n", - "import numpy as np" + "import numpy as np\n", + "from xmos_ai_tools import xformer, xcore_tflm_host_interpreter" ] }, { @@ -44,7 +105,7 @@ }, { "cell_type": "code", - "execution_count": 74, + "execution_count": 12, "id": "39cfdc25", "metadata": {}, "outputs": [], @@ -69,7 +130,7 @@ }, { "cell_type": "code", - "execution_count": 75, + "execution_count": 13, "id": "aafe3198", "metadata": {}, "outputs": [], @@ -91,7 +152,7 @@ }, { "cell_type": "code", - "execution_count": 76, + "execution_count": 14, "id": "fa57eb0a", "metadata": {}, "outputs": [], @@ -116,7 +177,7 @@ }, { "cell_type": "code", - "execution_count": 77, + "execution_count": 15, "id": "7b093742", "metadata": {}, "outputs": [ @@ -124,23 +185,17 @@ "name": "stdout", "output_type": "stream", "text": [ - "INFO:tensorflow:Assets written to: /var/folders/fg/_pf9q2tj3cl9yfjb392zl7rm0000gn/T/tmpoz298kvf/assets\n" + "INFO:tensorflow:Assets written to: /var/folders/fg/_pf9q2tj3cl9yfjb392zl7rm0000gn/T/tmpd8ewikm8/assets\n" ] }, { "name": "stderr", "output_type": "stream", "text": [ - "INFO:tensorflow:Assets written to: /var/folders/fg/_pf9q2tj3cl9yfjb392zl7rm0000gn/T/tmpoz298kvf/assets\n", - "2021-10-29 12:36:01.609244: I tensorflow/core/grappler/devices.cc:78] Number of eligible GPUs (core count >= 8, compute capability >= 0.0): 0 (Note: TensorFlow was not compiled with CUDA or ROCm support)\n", - "2021-10-29 12:36:01.609484: I tensorflow/core/grappler/clusters/single_machine.cc:357] Starting new session\n", - "2021-10-29 12:36:01.611708: I tensorflow/core/grappler/optimizers/meta_optimizer.cc:1144] Optimization results for grappler item: graph_to_optimize\n", - " function_optimizer: function_optimizer did nothing. time = 0.006ms.\n", - " function_optimizer: function_optimizer did nothing. time = 0ms.\n", - "\n", - "2021-10-29 12:36:01.635675: W tensorflow/compiler/mlir/lite/python/tf_tfl_flatbuffer_helpers.cc:345] Ignored output_format.\n", - "2021-10-29 12:36:01.635705: W tensorflow/compiler/mlir/lite/python/tf_tfl_flatbuffer_helpers.cc:348] Ignored drop_control_dependency.\n", - "fully_quantize: 0, inference_type: 6, input_inference_type: 9, output_inference_type: 9\n" + "INFO:tensorflow:Assets written to: /var/folders/fg/_pf9q2tj3cl9yfjb392zl7rm0000gn/T/tmpd8ewikm8/assets\n", + "/usr/local/anaconda3/lib/python3.8/site-packages/tensorflow/lite/python/convert.py:746: UserWarning: Statistics for quantized inputs were expected, but not specified; continuing anyway.\n", + " warnings.warn(\"Statistics for quantized inputs were expected, but not \"\n", + "WARNING:absl:Buffer deduplication procedure will be skipped when flatbuffer library is not properly loaded\n" ] } ], @@ -172,13 +227,24 @@ }, { "cell_type": "code", - "execution_count": 78, + "execution_count": 16, "id": "a26aa710", "metadata": {}, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "0" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "xcore_optimised_path = 'xcore_model.tflite'\n", - "xcore_conv.convert(tflite_model_path, xcore_optimised_path)" + "xformer.convert(tflite_model_path, xcore_optimised_path, None)" ] }, { @@ -194,17 +260,7 @@ }, { "cell_type": "code", - "execution_count": 79, - "id": "f37269b5", - "metadata": {}, - "outputs": [], - "source": [ - "from xcore_interpreters import XCOREInterpreter" - ] - }, - { - "cell_type": "code", - "execution_count": 80, + "execution_count": 17, "id": "5ada7955", "metadata": {}, "outputs": [], @@ -227,12 +283,24 @@ }, { "cell_type": "code", - "execution_count": 81, + "execution_count": 18, "id": "62d94234", "metadata": {}, - "outputs": [], + "outputs": [ + { + "ename": "TypeError", + "evalue": "'module' object is not callable", + "output_type": "error", + "traceback": [ + "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", + "\u001b[0;31mTypeError\u001b[0m Traceback (most recent call last)", + "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0mxcore_interpreter\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mxcore_tflm_host_interpreter\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mmodel_path\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0mxcore_optimised_path\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0mxcore_interpreter\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mallocate_tensors\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0mxcore_input_details\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mxcore_interpreter\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_input_details\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0mxcore_output_details\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mxcore_interpreter\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mget_output_details\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", + "\u001b[0;31mTypeError\u001b[0m: 'module' object is not callable" + ] + } + ], "source": [ - "xcore_interpreter = XCOREInterpreter(model_path=xcore_optimised_path)\n", + "xcore_interpreter = xcore_tflm_host_interpreter(model_path=xcore_optimised_path)\n", "xcore_interpreter.allocate_tensors()\n", "\n", "xcore_input_details = xcore_interpreter.get_input_details()\n", @@ -250,19 +318,10 @@ }, { "cell_type": "code", - "execution_count": 83, + "execution_count": null, "id": "3f8e306c-f6f1-42d3-b1b3-384142733fbe", "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Both models' output the same result?\n", - "yes\n" - ] - } - ], + "outputs": [], "source": [ "print(\"Both models' output the same result?\")\n", "print(\"yes\" if np.array_equal(xcore_output_data[0], tf_output_data[0]) else \"no\")\n" @@ -283,6 +342,14 @@ "metadata": {}, "outputs": [], "source": [] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6ca3b7e5-703a-4818-98f1-f349057d0a26", + "metadata": {}, + "outputs": [], + "source": [] } ], "metadata": { @@ -290,7 +357,7 @@ "hash": "0436a0dea52299ed28644175e220c962eae431d92561f4f402c0c00186dcb06f" }, "kernelspec": { - "display_name": "Python 3 (ipykernel)", + "display_name": "Python 3", "language": "python", "name": "python3" }, @@ -304,7 +371,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.8.12" + "version": "3.8.8" } }, "nbformat": 4, From 57f99291838129ced7173710d98bc24cb9080a95 Mon Sep 17 00:00:00 2001 From: Andrew Stanford-Jason Date: Thu, 10 Mar 2022 08:29:22 +0000 Subject: [PATCH 5/5] Update keras_to_xcore.ipynb --- docs/keras_to_xcore.ipynb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/docs/keras_to_xcore.ipynb b/docs/keras_to_xcore.ipynb index 05c5a01f5..cabb280ca 100644 --- a/docs/keras_to_xcore.ipynb +++ b/docs/keras_to_xcore.ipynb @@ -115,7 +115,6 @@ "model = tf.keras.Sequential([\n", " tf.keras.layers.AveragePooling2D(pool_size=pool_size, input_shape=input_shape)\n", "])\n", - "# is this necessary?\n", "model.compile()" ] }, @@ -125,7 +124,7 @@ "metadata": {}, "source": [ "## Convert keras model into a tflite model\n", - "The xcore converter cannot optimise a keras model to run on xcore devices, so it must first be converted into a tflite file." + "The xcore converter cannot optimise a keras model directly to run on xcore devices, so it must first be converted into a tflite file(a flatbuffer)." ] }, { @@ -145,7 +144,7 @@ "source": [ "### Representitive Dataset\n", "\n", - "Tensorflow can optimise the converted model if you pass it a a representative dataset. This dataset can be a small subset (around ~100-500 samples) of the training or validation data\n", + "Tensorflow can optimise the converted model to int8 if you pass it a representative dataset. This dataset can be a small subset (around ~100-500 samples) of the training or validation data\n", "\n", "The below function randomly gemerates this, but see [the tensorflow ducumentation](https://www.tensorflow.org/lite/performance/post_training_quantization) to see how to do this in practice." ]