{ "nbformat": 4, "nbformat_minor": 0, "metadata": { "colab": { "name": "SpeedUpYourAlgorithms_Numba.ipynb", "version": "0.3.2", "provenance": [] }, "kernelspec": { "name": "python3", "display_name": "Python 3" }, "accelerator": "GPU" }, "cells": [ { "metadata": { "id": "QPIVdxjZ_6az", "colab_type": "text" }, "cell_type": "markdown", "source": [ "\n", "This is a part of series I am writing, named \"**Speed Up Your Algorithms**\".\n", "\n", "Here is the list of posts with files I have written: [Github-SpeedUpYourAlgorithms](https://github.com/PuneetGrov3r/MediumPosts/tree/master/SpeedUpYourAlgorithms)" ] }, { "metadata": { "id": "tI78SzIeTZSH", "colab_type": "text" }, "cell_type": "markdown", "source": [ "# Initial" ] }, { "metadata": { "id": "vF74OXegST3A", "colab_type": "code", "colab": {} }, "cell_type": "code", "source": [ "!pip install numba" ], "execution_count": 0, "outputs": [] }, { "metadata": { "id": "YmsI2Ou7SyTX", "colab_type": "code", "colab": {} }, "cell_type": "code", "source": [ "# https://medium.com/@iphoenix179/running-cuda-c-c-in-jupyter-or-how-to-run-nvcc-in-google-colab-663d33f53772\n", "# Needed for using cuda with Numba (https://numba.pydata.org/numba-doc/dev/user/installing.html#installing-using-pip-on-x86-x86-64-platforms)\n", "# Numba only supports cuda till 9.1\n", "!apt update -qq;\n", "!wget https://developer.nvidia.com/compute/cuda/8.0/Prod2/local_installers/cuda-repo-ubuntu1604-8-0-local-ga2_8.0.61-1_amd64-deb\n", "!mv cuda-repo-ubuntu1604-8-0-local-ga2_8.0.61-1_amd64-deb cuda-repo-ubuntu1604-8-0-local-ga2_8.0.61-1_amd64.deb\n", "!dpkg -i cuda-repo-ubuntu1604-8-0-local-ga2_8.0.61-1_amd64.deb\n", "!apt-get update -qq;\n", "!apt-get install cuda-8-0 gcc-5 g++-5 -y -qq;\n", "!ln -s /usr/bin/gcc-5 /usr/local/cuda/bin/gcc;\n", "!ln -s /usr/bin/g++-5 /usr/local/cuda/bin/g++;" ], "execution_count": 0, "outputs": [] }, { "metadata": { "id": "_26iiW8S4ECp", "colab_type": "code", "colab": {} }, "cell_type": "code", "source": [ "import os\n", "os.environ[\"NUMBAPRO_CUDA_DRIVER\"] = \"/usr/lib64-nvidia/libcuda.so\"\n", "os.environ[\"NUMBAPRO_NVVM\"] = \"/usr/local/cuda-8.0/nvvm/lib64/libnvvm.so\"\n", "os.environ[\"NUMBAPRO_LIBDEVICE\"] = \"/usr/local/cuda-8.0/nvvm/libdevice/\"\n", "#os.environ[\"LD_LIBRARY_PATH\"] = \"/usr/local/cuda/lib64\"" ], "execution_count": 0, "outputs": [] }, { "metadata": { "id": "qvkZoVOv9u_F", "colab_type": "code", "colab": {} }, "cell_type": "code", "source": [ "!numba -s" ], "execution_count": 0, "outputs": [] }, { "metadata": { "id": "9wh6C6ac7pZJ", "colab_type": "text" }, "cell_type": "markdown", "source": [ "# Import" ] }, { "metadata": { "id": "5vndypxM7rGN", "colab_type": "code", "colab": {} }, "cell_type": "code", "source": [ "import os\n", "import numpy as np # Numba supports many functions from numpy (https://numba.pydata.org/numba-doc/dev/reference/numpysupported.html)\n", "from numba import jit, njit, vectorize, cuda\n", "import math # Numba supports many functions from math (http://numba.pydata.org/numba-doc/0.17.0/reference/pysupported.html)\n", "\n", "import matplotlib.pyplot as plt" ], "execution_count": 0, "outputs": [] }, { "metadata": { "id": "3bnRKV978fFN", "colab_type": "text" }, "cell_type": "markdown", "source": [ "# 4. Using basic numba functionalities (Just @jit it!)" ] }, { "metadata": { "id": "_6DEmDigpMfv", "colab_type": "code", "colab": {} }, "cell_type": "code", "source": [ "a = np.ones((1, 100), dtype=np.float64)\n", "b = np.ones((100, 1), dtype=np.float64)" ], "execution_count": 0, "outputs": [] }, { "metadata": { "id": "V91pNrvmpG-3", "colab_type": "code", "colab": {} }, "cell_type": "code", "source": [ "#\n", "# Simple Python function\n", "#\n", "\n", "def func(a, b):\n", " for i in range(100000):\n", " constant = math.pow((a@b)[0][0], 1./2)/math.exp((a@b)[0][0]/1000)\n", " a = np.array([[constant]*100], dtype=np.float64)\n", " return a" ], "execution_count": 0, "outputs": [] }, { "metadata": { "id": "G9URbB5dpNhJ", "colab_type": "code", "colab": { "base_uri": "https://localhost:8080/", "height": 34 }, "outputId": "425aadd9-4eff-4223-802a-887a7acc70a4" }, "cell_type": "code", "source": [ "%timeit res = func(a, b)" ], "execution_count": 8, "outputs": [ { "output_type": "stream", "text": [ "1 loop, best of 3: 775 ms per loop\n" ], "name": "stdout" } ] }, { "metadata": { "id": "baBmONpCioUe", "colab_type": "code", "colab": {} }, "cell_type": "code", "source": [ "#\n", "# Numba with nopython = True\n", "#\n", "\n", "@njit # or @jit(nopython=True)\n", "def njit_func(a, b):\n", " for i in range(100000):\n", " constant = math.pow((a@b)[0][0], 1./2)/math.exp((a@b)[0][0]/1000)\n", " a = np.array([[constant]*100], dtype=np.float64)\n", " return a" ], "execution_count": 0, "outputs": [] }, { "metadata": { "id": "-d90HRMllKwr", "colab_type": "code", "colab": { "base_uri": "https://localhost:8080/", "height": 34 }, "outputId": "6849decb-b67f-48d0-a1d2-cb74dfa802e7" }, "cell_type": "code", "source": [ "%timeit res = njit_func(a, b)" ], "execution_count": 18, "outputs": [ { "output_type": "stream", "text": [ "10 loops, best of 3: 109 ms per loop\n" ], "name": "stdout" } ] }, { "metadata": { "id": "WUEcagVshnon", "colab_type": "code", "colab": {} }, "cell_type": "code", "source": [ "#\n", "# Basic Numba compiler with type information provided\n", "#\n", "\n", "@jit('float64(float64, float64)')\n", "def jit_func(a, b):\n", " for i in range(100000):\n", " constant = math.pow((a@b)[0][0], 1./2)/math.exp((a@b)[0][0]/1000)\n", " a = np.array([[constant]*100], dtype=np.float64)\n", " return a" ], "execution_count": 0, "outputs": [] }, { "metadata": { "id": "TbYs5rtup5KX", "colab_type": "code", "colab": { "base_uri": "https://localhost:8080/", "height": 34 }, "outputId": "a317b078-295a-47e3-8c86-8f538b62e25c" }, "cell_type": "code", "source": [ "%timeit res = jit_func(a, b)" ], "execution_count": 17, "outputs": [ { "output_type": "stream", "text": [ "10 loops, best of 3: 109 ms per loop\n" ], "name": "stdout" } ] }, { "metadata": { "id": "RPMMOCq2uuPQ", "colab_type": "code", "colab": { "base_uri": "https://localhost:8080/", "height": 417 }, "outputId": "648f342c-7328-41f5-c39d-f79e5bd40162" }, "cell_type": "code", "source": [ "fig, ax = plt.subplots(1, 1)\n", "ax.plot([\"a\", \"b\", \"c\"], [821, 447, 440], \"-o\") # Results without caching\n", "ax.set_xticklabels([\"pyFunc\", \"Jit\", \"Njit\"])\n", "fig.suptitle(\"Basic Numba Functionalities\", fontsize=17)\n", "ax.set_ylabel(\"Time (ms)\")\n", "ax.set_xlabel(\"Method\")" ], "execution_count": 27, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "Text(0.5,0,'Method')" ] }, "metadata": { "tags": [] }, "execution_count": 27 }, { "output_type": "display_data", "data": { "image/png": "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\n8803qKiowMqVK1FZWYkdO3bg008/hSAIaNWqFebMmYPXXnut1vv39/fH559/jhUrVmDo0KGwtbVF\nr1698O677zbqLO5x48Zh165dDwwLf/LJJ4iIiMCECRNga2uLESNGYPz48fjggw8e+T7q8uGHH+If\n//gH4uLiYGFhgfDwcEybNg3AnSmHjz76CMuXL8fQoUPRvn17zJo1C35+foiNjcXs2bPxySefYNCg\nQY2+fzc3N3z44YdYuXIlwsLC4O7ujrfeekt79vijsLa2xpYtW7Bs2TKEh4cDuHPy3qZNm9C6dWsA\nwKuvvor4+HiMHz8eo0ePxoQJE2psw8zMDBs3bsQnn3yCWbNmobCwEC4uLpg4cSKmTp36SHkGDx6M\nhIQEbN68GWvXrkVAQAA+++wz5OTk4Ny5cxg9ejS+++67erczZcoULFmyBK+88grefvtt+Pn51bi+\nRYsW+Oqrr/Cf//wHb731FgoLC9GyZUsMGjRIOzXUsWNHrF27FmvXrkV4eDjKy8vh5OSEfv361Tp9\nRKZJ0OhinI2IiIhEwaF1IiIiI8YiJyIiMmIsciIiIiPGIiciIjJiLHIiIiIjxiInIiIyYixyIiIi\nI8YiJyIiMmIsciIiIiPGIiciIjJiLHIiIiIjxiInIiIyYixyIiIiI8YiJyIiMmIsciIiIiPGIici\nIjJiLHIiIiIjxiInIiIyYixyIiIiI8YiJyIiMmIsciIiIiPGIiciIjJiLHIiIiIjxiInIiIyYixy\nIiIiI8YiJyIiMmIysQM0hkpVqNPtOThYITe3RKfbJGqOuK8QNYyu9xWl0rbO6/iMHIBMJhU7ApFR\n4L5C1DBNua+wyImIiIwYi5yIiMiIsciJiIiMGIuciIjIiLHIiYiIjBiLnIiIyIixyImIiIwYi5yI\niMiIGeU7u+lKzPlM7Dt5DRk5JXBztMKQnu3wdGdnsWMRERE1mMkWecz5TKyNTtReTlcVay+zzImI\nyFiY7ND6vpPX6lie2qQ5iIiIHofJFnlGdu1vZn8zp7iJkxARETWeyRa5W0urWpe7Olo3cRIiIqLG\nM9kiH9KzXa3Ln+ni2rRBiIiIHoPJFvnTnZ3xRqgPWiltIJUIcLA1BwCcSLiFyiq1yOmIiIgaxmTP\nWgfulPnTnZ2hVNoiK6sAG/Yl4UTCLXx75BLCn/MSOx4REVG9TPYZ+f0EQcC457zg3tIaR2Jv4HRS\n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\n", "text/plain": [ "" ] }, "metadata": { "tags": [] } } ] }, { "metadata": { "id": "xnOl22rJqHTm", "colab_type": "text" }, "cell_type": "markdown", "source": [ "# 5. The @vectorize wrapper" ] }, { "metadata": { "id": "QKNgTakJp_L8", "colab_type": "code", "colab": {} }, "cell_type": "code", "source": [ "#\n", "# Making Ufuncs with Numba.\n", "#\n", "\n", "@vectorize\n", "def vec_func(a, b):\n", " # Now we can pass arrays too, and operate\n", " # inside like they are scalars:\n", " for i in range(100000):\n", " a = math.pow(a*b, 1./2)/math.exp(a*b/1000)\n", " return a\n", "# This is similar to functions before, for comparison. But..." ], "execution_count": 0, "outputs": [] }, { "metadata": { "id": "hobQGwJCrjEA", "colab_type": "code", "colab": { "base_uri": "https://localhost:8080/", "height": 34 }, "outputId": "6f89dc77-a79c-43dc-bfee-9b6ba34e9fce" }, "cell_type": "code", "source": [ "%timeit res = vec_func(a, b)" ], "execution_count": 20, "outputs": [ { "output_type": "stream", "text": [ "1 loop, best of 3: 35.4 s per loop\n" ], "name": "stdout" } ] }, { "metadata": { "id": "RrOcEpPgrn6B", "colab_type": "code", "colab": {} }, "cell_type": "code", "source": [ "#\n", "# This is slow because previously we were doing some\n", "# operations on 1,00,000 scalars obtained by multiplying\n", "# (a@b), but now we are multiplying individual elements \n", "# of a and b for 1,00,000 times. Also numba is taking care\n", "# of broadcasting too. So, in this case we are applying this\n", "# loop for 100 times.\n", "#\n", "res.shape # Previously it was (1, 100)" ], "execution_count": 0, "outputs": [] }, { "metadata": { "id": "nZSgu8R8sJaM", "colab_type": "code", "colab": {} }, "cell_type": "code", "source": [ "@vectorize(['float64(float64, float64)'], target='parallel')\n", "def vecParallel_func(a, b):\n", " for i in range(100000):\n", " a = math.pow(a*b, 1./2)/math.exp(a*b/1000)\n", " return a" ], "execution_count": 0, "outputs": [] }, { "metadata": { "id": "Z0piW2xJtgHc", "colab_type": "code", "colab": { "base_uri": "https://localhost:8080/", "height": 34 }, "outputId": "b7fe8658-0237-451b-dca1-78571cb6e8ac" }, "cell_type": "code", "source": [ "%timeit res = vecParallel_func(a, b)" ], "execution_count": 23, "outputs": [ { "output_type": "stream", "text": [ "1 loop, best of 3: 18.6 s per loop\n" ], "name": "stdout" } ] }, { "metadata": { "id": "rLiJPLY0tjvs", "colab_type": "code", "colab": {} }, "cell_type": "code", "source": [ "@vectorize(['float64(float64, float64)'], target='cuda')\n", "def vecCuda_func(a, b):\n", " for i in range(100000):\n", " a = math.pow(a*b, 1./2)/math.exp(a*b/1000)\n", " return a" ], "execution_count": 0, "outputs": [] }, { "metadata": { "id": "naP5t1btub6C", "colab_type": "code", "colab": { "base_uri": "https://localhost:8080/", "height": 34 }, "outputId": "a9231757-14c6-46a2-841b-8e9105d20caf" }, "cell_type": "code", "source": [ "%timeit res = vecCuda_func(a, b)" ], "execution_count": 25, "outputs": [ { "output_type": "stream", "text": [ "1 loop, best of 3: 282 ms per loop\n" ], "name": "stdout" } ] }, { "metadata": { "id": "ymlt0rKIuhf-", "colab_type": "code", "colab": {} }, "cell_type": "code", "source": [ "# Woah!!" ], "execution_count": 0, "outputs": [] }, { "metadata": { "id": "fH42xHl5xNiZ", "colab_type": "code", "colab": { "base_uri": "https://localhost:8080/", "height": 417 }, "outputId": "0d9443e8-a58f-4c59-9c14-90c1a888bad9" }, "cell_type": "code", "source": [ "fig, ax = plt.subplots(1, 1)\n", "ax.plot([\"a\", \"b\", \"c\"], [35.8, 19.2, 0.568], \"-o\")\n", "ax.set_xticklabels([\"Vectorize\", \"Vectorize Parallel\", \"Vectorize Cuda\"])\n", "fig.suptitle(\"@vectorize wrapper\", fontsize=17)\n", "ax.set_ylabel(\"Time (sec)\")\n", "ax.set_xlabel(\"Method\")" ], "execution_count": 162, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "Text(0.5,0,'Method')" ] }, "metadata": { "tags": [] }, "execution_count": 162 }, { "output_type": "display_data", "data": { "image/png": "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\n", "text/plain": [ "" ] }, "metadata": { "tags": [] } } ] }, { "metadata": { "id": "P1VRyAOtzrYD", "colab_type": "text" }, "cell_type": "markdown", "source": [ "# 6. Running your functions on GPU\n" ] }, { "metadata": { "id": "J3_Zv57yxsBV", "colab_type": "code", "colab": {} }, "cell_type": "code", "source": [ "@cuda.jit\n", "def cudaKernal_func(a, b, result): # cuda.jit does not return result yet\n", " pos = cuda.grid(1)\n", " if (pos < a.shape[1]) and (pos < b.shape[0]):\n", " for i in range(100000):\n", " result[pos] = math.exp(a[0][pos]*b[pos][0])" ], "execution_count": 0, "outputs": [] }, { "metadata": { "id": "S4pShvAI1YjR", "colab_type": "code", "colab": {} }, "cell_type": "code", "source": [ "result = np.zeros((100,), dtype=np.float64)" ], "execution_count": 0, "outputs": [] }, { "metadata": { "id": "RxlLDdbT1gFf", "colab_type": "code", "colab": { "base_uri": "https://localhost:8080/", "height": 34 }, "outputId": "d5419ec2-d734-4635-9e90-e6f50b47ec05" }, "cell_type": "code", "source": [ "threadsperblock = 32\n", "blockspergrid = (100 + 31) // 32 # blockspergrid = (array.size + (threadsperblock - 1)) // threadsperblock\n", "\n", "%timeit cudaKernal_func[threadsperblock, blockspergrid](a, b, result)" ], "execution_count": 30, "outputs": [ { "output_type": "stream", "text": [ "1 loop, best of 3: 66.2 ms per loop\n" ], "name": "stdout" } ] }, { "metadata": { "id": "I0Q2FOAk1rXw", "colab_type": "code", "colab": { "base_uri": "https://localhost:8080/", "height": 34 }, "outputId": "a12d6f7d-acd0-4277-d0c5-f3760eadd5f2" }, "cell_type": "code", "source": [ "result.shape" ], "execution_count": 31, "outputs": [ { "output_type": "execute_result", "data": { "text/plain": [ "(100,)" ] }, "metadata": { "tags": [] }, "execution_count": 31 } ] }, { "metadata": { "id": "1DjchmIF13_j", "colab_type": "code", "colab": {} }, "cell_type": "code", "source": [ "#\n", "# Here, we have only used it for 1D arrays. You can use it for any Tensor. For eg:\n", "# For 2D array operations you would have used: x, y = cuda.grid(2)\n", "#" ], "execution_count": 0, "outputs": [] }, { "metadata": { "id": "e1snDXNG3c9A", "colab_type": "code", "colab": {} }, "cell_type": "code", "source": [ "@cuda.jit(device=True)\n", "def cudaDevice_func(a, b):\n", " for i in range(100000):\n", " a = math.exp(a*b)\n", " return a" ], "execution_count": 0, "outputs": [] }, { "metadata": { "id": "dgv5aV6n3wgv", "colab_type": "code", "colab": {} }, "cell_type": "code", "source": [ "@cuda.jit\n", "def cudaKernal_func2(a, b, result): # cuda.jit does not return result yet\n", " pos = cuda.grid(1)\n", " if (pos < a.shape[1]) and (pos < b.shape[0]):\n", " result[pos] = cudaDevice_func(a[0][pos], b[pos][0])" ], "execution_count": 0, "outputs": [] }, { "metadata": { "id": "Ro7kzwPS32vv", "colab_type": "code", "colab": { "base_uri": "https://localhost:8080/", "height": 50 }, "outputId": "2c5866c2-7e8c-4cc5-ebcf-fd4492a7464f" }, "cell_type": "code", "source": [ "%timeit cudaKernal_func2[threadsperblock, blockspergrid](a, b, result)" ], "execution_count": 35, "outputs": [ { "output_type": "stream", "text": [ "The slowest run took 5.08 times longer than the fastest. This could mean that an intermediate result is being cached.\n", "1 loop, best of 3: 41.4 ms per loop\n" ], "name": "stdout" } ] } ] }