|
| 1 | +{ |
| 2 | + "cells": [ |
| 3 | + { |
| 4 | + "cell_type": "markdown", |
| 5 | + "metadata": {}, |
| 6 | + "source": [ |
| 7 | + "### Install HPAT\n", |
| 8 | + "```bash\n", |
| 9 | + "conda create -n HPAT python=3.6\n", |
| 10 | + "source activate HPAT\n", |
| 11 | + "conda install pandas\n", |
| 12 | + "conda install numba -c numba\n", |
| 13 | + "conda install pyarrow mpich -c conda-forge\n", |
| 14 | + "conda install hpat -c ehsantn\n", |
| 15 | + "```" |
| 16 | + ] |
| 17 | + }, |
| 18 | + { |
| 19 | + "cell_type": "markdown", |
| 20 | + "metadata": {}, |
| 21 | + "source": [ |
| 22 | + "### Pi Example\n", |
| 23 | + "Let's run a simple python numerical function that calculates Pi using a Monte Carlo method:" |
| 24 | + ] |
| 25 | + }, |
| 26 | + { |
| 27 | + "cell_type": "code", |
| 28 | + "execution_count": 2, |
| 29 | + "metadata": { |
| 30 | + "scrolled": false |
| 31 | + }, |
| 32 | + "outputs": [ |
| 33 | + { |
| 34 | + "name": "stdout", |
| 35 | + "output_type": "stream", |
| 36 | + "text": [ |
| 37 | + "Execution time: 4.863769292831421 \n", |
| 38 | + "result: 3.14138024\n" |
| 39 | + ] |
| 40 | + }, |
| 41 | + { |
| 42 | + "data": { |
| 43 | + "text/plain": [ |
| 44 | + "3.14138024" |
| 45 | + ] |
| 46 | + }, |
| 47 | + "execution_count": 2, |
| 48 | + "metadata": {}, |
| 49 | + "output_type": "execute_result" |
| 50 | + } |
| 51 | + ], |
| 52 | + "source": [ |
| 53 | + "import numpy as np\n", |
| 54 | + "import time\n", |
| 55 | + "\n", |
| 56 | + "def calc_pi(n):\n", |
| 57 | + " t1 = time.time()\n", |
| 58 | + " x = 2 * np.random.ranf(n) - 1\n", |
| 59 | + " y = 2 * np.random.ranf(n) - 1\n", |
| 60 | + " pi = 4 * np.sum(x**2 + y**2 < 1) / n\n", |
| 61 | + " print(\"Execution time:\", time.time()-t1, \"\\nresult:\", pi)\n", |
| 62 | + " return pi\n", |
| 63 | + "\n", |
| 64 | + "calc_pi(2 * 10**8)" |
| 65 | + ] |
| 66 | + }, |
| 67 | + { |
| 68 | + "cell_type": "markdown", |
| 69 | + "metadata": {}, |
| 70 | + "source": [ |
| 71 | + "Now let's run the same function with hpat.jit decoration:" |
| 72 | + ] |
| 73 | + }, |
| 74 | + { |
| 75 | + "cell_type": "code", |
| 76 | + "execution_count": 3, |
| 77 | + "metadata": {}, |
| 78 | + "outputs": [ |
| 79 | + { |
| 80 | + "name": "stdout", |
| 81 | + "output_type": "stream", |
| 82 | + "text": [ |
| 83 | + "Execution time: 1.8851549625396729 \n", |
| 84 | + "result: 3.1416436\n" |
| 85 | + ] |
| 86 | + }, |
| 87 | + { |
| 88 | + "data": { |
| 89 | + "text/plain": [ |
| 90 | + "3.1416436" |
| 91 | + ] |
| 92 | + }, |
| 93 | + "execution_count": 3, |
| 94 | + "metadata": {}, |
| 95 | + "output_type": "execute_result" |
| 96 | + } |
| 97 | + ], |
| 98 | + "source": [ |
| 99 | + "import hpat\n", |
| 100 | + "\n", |
| 101 | + "@hpat.jit\n", |
| 102 | + "def calc_pi(n):\n", |
| 103 | + " t1 = time.time()\n", |
| 104 | + " x = 2 * np.random.ranf(n) - 1\n", |
| 105 | + " y = 2 * np.random.ranf(n) - 1\n", |
| 106 | + " pi = 4 * np.sum(x**2 + y**2 < 1) / n\n", |
| 107 | + " print(\"Execution time:\", time.time()-t1, \"\\nresult:\", pi)\n", |
| 108 | + " return pi\n", |
| 109 | + "\n", |
| 110 | + "calc_pi(2 * 10**8)" |
| 111 | + ] |
| 112 | + }, |
| 113 | + { |
| 114 | + "cell_type": "markdown", |
| 115 | + "metadata": {}, |
| 116 | + "source": [ |
| 117 | + "The hpat.jit decoration gives the function to HPAT to compile. HPAT generates an efficient binary version which replaces the original function in the python environment. In this case, the HPAT version is ~2.5x faster.\n", |
| 118 | + "\n", |
| 119 | + "However, this function was run on a single core. Let's save this code in `pi.py`and run it on multiple cores using MPI:\n", |
| 120 | + "\n", |
| 121 | + "```bash\n", |
| 122 | + "mpiexec -n 4 python pi.py\n", |
| 123 | + "Execution time: 0.9372961521148682 \n", |
| 124 | + "result: 3.1415531\n", |
| 125 | + "```" |
| 126 | + ] |
| 127 | + }, |
| 128 | + { |
| 129 | + "cell_type": "markdown", |
| 130 | + "metadata": {}, |
| 131 | + "source": [ |
| 132 | + "`mpiexec` launches 4 python processes that run the same python program. HPAT parallelize the decorated function on these processes (using the MPI library for communication). It divides the problem space (`n`) and manages communication between cores. In this case, `np.sum` function requires a reduction. The generated function is scalable and can run on any number of cores (e.g. `mpiexec -n 10000`).\n", |
| 133 | + "\n", |
| 134 | + "HPAT supports threading within a single as well. Let's enable HPAT threading for this program:" |
| 135 | + ] |
| 136 | + }, |
| 137 | + { |
| 138 | + "cell_type": "code", |
| 139 | + "execution_count": 4, |
| 140 | + "metadata": {}, |
| 141 | + "outputs": [ |
| 142 | + { |
| 143 | + "name": "stdout", |
| 144 | + "output_type": "stream", |
| 145 | + "text": [ |
| 146 | + "Execution time: 0.46541595458984375 \n", |
| 147 | + "result: 3.14165164\n" |
| 148 | + ] |
| 149 | + }, |
| 150 | + { |
| 151 | + "data": { |
| 152 | + "text/plain": [ |
| 153 | + "3.14165164" |
| 154 | + ] |
| 155 | + }, |
| 156 | + "execution_count": 4, |
| 157 | + "metadata": {}, |
| 158 | + "output_type": "execute_result" |
| 159 | + } |
| 160 | + ], |
| 161 | + "source": [ |
| 162 | + "hpat.multithread_mode = True\n", |
| 163 | + "\n", |
| 164 | + "@hpat.jit\n", |
| 165 | + "def calc_pi(n):\n", |
| 166 | + " t1 = time.time()\n", |
| 167 | + " x = 2 * np.random.ranf(n) - 1\n", |
| 168 | + " y = 2 * np.random.ranf(n) - 1\n", |
| 169 | + " pi = 4 * np.sum(x**2 + y**2 < 1) / n\n", |
| 170 | + " print(\"Execution time:\", time.time()-t1, \"\\nresult:\", pi)\n", |
| 171 | + " return pi\n", |
| 172 | + "\n", |
| 173 | + "calc_pi(2 * 10**8)" |
| 174 | + ] |
| 175 | + }, |
| 176 | + { |
| 177 | + "cell_type": "markdown", |
| 178 | + "metadata": {}, |
| 179 | + "source": [ |
| 180 | + "HPAT can compile a subset of Python which includes Pandas and Numpy operations. See HPAT documentation [here](https://intellabs.github.io/hpat/). HPAT is built on top of Numba, which means Numba's restrictions apply as well. See Numba's documentation [here](http://numba.pydata.org/numba-doc/latest/index.html)." |
| 181 | + ] |
| 182 | + }, |
| 183 | + { |
| 184 | + "cell_type": "markdown", |
| 185 | + "metadata": {}, |
| 186 | + "source": [ |
| 187 | + "### Automatic Parallelization\n", |
| 188 | + "HPAT parallelizes programs automatically based on the map-reduce parallel pattern. Put simply, this means the compiler analyzes the program to determine whether each array should be distributed or not.\n", |
| 189 | + "\n", |
| 190 | + "To demonstrate parallelization, let's use a simple example. First generate some random number data:" |
| 191 | + ] |
| 192 | + }, |
| 193 | + { |
| 194 | + "cell_type": "code", |
| 195 | + "execution_count": 5, |
| 196 | + "metadata": {}, |
| 197 | + "outputs": [], |
| 198 | + "source": [ |
| 199 | + "import h5py\n", |
| 200 | + "f = h5py.File(\"data.h5\", \"w\")\n", |
| 201 | + "n = 16\n", |
| 202 | + "f.create_dataset('A', data=np.random.ranf(n))\n", |
| 203 | + "f.close()" |
| 204 | + ] |
| 205 | + }, |
| 206 | + { |
| 207 | + "cell_type": "markdown", |
| 208 | + "metadata": {}, |
| 209 | + "source": [ |
| 210 | + "Here is an example HPAT code that reads this data and sums the values:" |
| 211 | + ] |
| 212 | + }, |
| 213 | + { |
| 214 | + "cell_type": "code", |
| 215 | + "execution_count": 6, |
| 216 | + "metadata": {}, |
| 217 | + "outputs": [ |
| 218 | + { |
| 219 | + "name": "stdout", |
| 220 | + "output_type": "stream", |
| 221 | + "text": [ |
| 222 | + "9.022310532208113\n" |
| 223 | + ] |
| 224 | + } |
| 225 | + ], |
| 226 | + "source": [ |
| 227 | + "@hpat.jit\n", |
| 228 | + "def example_1D():\n", |
| 229 | + " f = h5py.File(\"data.h5\", \"r\")\n", |
| 230 | + " A = f['A'][:]\n", |
| 231 | + " return np.sum(A)\n", |
| 232 | + "\n", |
| 233 | + "r = example_1D()\n", |
| 234 | + "print(r)" |
| 235 | + ] |
| 236 | + }, |
| 237 | + { |
| 238 | + "cell_type": "markdown", |
| 239 | + "metadata": {}, |
| 240 | + "source": [ |
| 241 | + "Array A is the output of an I/O operation and is input to np.sum. Based on semantics of I/O and np.sum, HPAT determines that A can be distributed since I/O can output a distributed array and np.sum can take a distributed array as input. In map-reduce terminology, A is output of a map operator and is input to a reduce operator. Hence, HPAT distributes A and all operations associated with A (i.e. I/O and np.sum).\n", |
| 242 | + "\n", |
| 243 | + "#### Distribution Report\n", |
| 244 | + "The distributions found by HPAT can be printed using the hpat.distribution_report() function:" |
| 245 | + ] |
| 246 | + }, |
| 247 | + { |
| 248 | + "cell_type": "code", |
| 249 | + "execution_count": 8, |
| 250 | + "metadata": {}, |
| 251 | + "outputs": [ |
| 252 | + { |
| 253 | + "name": "stdout", |
| 254 | + "output_type": "stream", |
| 255 | + "text": [ |
| 256 | + "Array distributions:\n", |
| 257 | + " $A.688 1D_Block\n", |
| 258 | + "\n", |
| 259 | + "Parfor distributions:\n", |
| 260 | + " 18 1D_Block\n" |
| 261 | + ] |
| 262 | + } |
| 263 | + ], |
| 264 | + "source": [ |
| 265 | + "hpat.distribution_report()" |
| 266 | + ] |
| 267 | + }, |
| 268 | + { |
| 269 | + "cell_type": "markdown", |
| 270 | + "metadata": {}, |
| 271 | + "source": [ |
| 272 | + "This report suggests that the function has an array that is distributed in 1D_Block fashion. The variable name is renamed from A to $A.23 through the optimization passes. The report also suggests that there is a parfor (data-parallel for loop) that is \"1D_Block\" distributed.\n", |
| 273 | + "\n", |
| 274 | + "Arrays are distributed in one-dimensional block (1D_Block) manner among processors. This means that processors own equal chunks of each distributed array (except possibly the last processor). Multi-dimensional arrays are distributed along their first dimension by default. For example, chunks of rows are distributed for a 2D matrix. The figure below illustrates the distribution of a 9-element one-dimensional Numpy array, as well as a 9 by 2 array, on three processors. \n", |
| 275 | + "<img src=\"https://intellabs.github.io/hpat/_build/html/_images/dist.jpg\" width=\"35%\">\n", |
| 276 | + "HPAT replicates the arrays that are not distributed. This is called REP distribution for consistency.\n", |
| 277 | + "\n", |
| 278 | + "Checking the distribution report is generally use for ensuring that the function is parallelized as expected. However, HPAT is able to fuse operations and eliminate unused arrays. For example:" |
| 279 | + ] |
| 280 | + }, |
| 281 | + { |
| 282 | + "cell_type": "code", |
| 283 | + "execution_count": 15, |
| 284 | + "metadata": {}, |
| 285 | + "outputs": [ |
| 286 | + { |
| 287 | + "name": "stdout", |
| 288 | + "output_type": "stream", |
| 289 | + "text": [ |
| 290 | + "45\n", |
| 291 | + "Array distributions:\n", |
| 292 | + "\n", |
| 293 | + "Parfor distributions:\n", |
| 294 | + " 22 1D_Block\n" |
| 295 | + ] |
| 296 | + } |
| 297 | + ], |
| 298 | + "source": [ |
| 299 | + "@hpat.jit\n", |
| 300 | + "def example_elim():\n", |
| 301 | + " A = np.arange(10)\n", |
| 302 | + " return A.sum()\n", |
| 303 | + "\n", |
| 304 | + "r = example_elim()\n", |
| 305 | + "print(r)\n", |
| 306 | + "hpat.distribution_report()" |
| 307 | + ] |
| 308 | + }, |
| 309 | + { |
| 310 | + "cell_type": "markdown", |
| 311 | + "metadata": {}, |
| 312 | + "source": [ |
| 313 | + "In this case, the parfor for `arange` and `sum` are fused, and the intermediate array `A` is eliminated. Hence, the program has no arrays left after optimization." |
| 314 | + ] |
| 315 | + } |
| 316 | + ], |
| 317 | + "metadata": { |
| 318 | + "kernelspec": { |
| 319 | + "display_name": "Python 3", |
| 320 | + "language": "python", |
| 321 | + "name": "python3" |
| 322 | + }, |
| 323 | + "language_info": { |
| 324 | + "codemirror_mode": { |
| 325 | + "name": "ipython", |
| 326 | + "version": 3 |
| 327 | + }, |
| 328 | + "file_extension": ".py", |
| 329 | + "mimetype": "text/x-python", |
| 330 | + "name": "python", |
| 331 | + "nbconvert_exporter": "python", |
| 332 | + "pygments_lexer": "ipython3", |
| 333 | + "version": "3.6.5" |
| 334 | + } |
| 335 | + }, |
| 336 | + "nbformat": 4, |
| 337 | + "nbformat_minor": 2 |
| 338 | +} |
0 commit comments