{ "cells": [ { "cell_type": "markdown", "metadata": {}, "source": [ "# Loopy: Dealing with Intermediate Results" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Setup code" ] }, { "cell_type": "code", "execution_count": 1, "metadata": { "collapsed": false }, "outputs": [], "source": [ "import numpy as np\n", "import pyopencl as cl\n", "import pyopencl.array\n", "import pyopencl.clmath\n", "import pyopencl.clrandom\n", "import loopy as lp" ] }, { "cell_type": "code", "execution_count": 2, "metadata": { "collapsed": false }, "outputs": [], "source": [ "ctx = cl.create_some_context()\n", "queue = cl.CommandQueue(ctx)" ] }, { "cell_type": "code", "execution_count": 3, "metadata": { "collapsed": false }, "outputs": [], "source": [ "grid = np.linspace(0, 2*np.pi, 2048, endpoint=False)\n", "h = grid[1] - grid[0]\n", "u = cl.clmath.sin(cl.array.to_device(queue, grid))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Two kernels: Finite Differences and a Flux" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We will implement computing the ODE right-hand side for Burgers' equation:\n", "$$\n", "\\frac{\\partial u}{\\partial t} + \\frac{\\partial }{\\partial x} \\left( \\frac{u^2}2 \\right) = 0,\n", "$$\n", "\n", "Now, it is likely that the code fore the derivative is initially separate from the code for the flux function $f(u):=u^2/2$.\n", "\n", "For simplicity, we will use central finite differences to build a kernel `fin_diff_knl` to take the derivative:" ] }, { "cell_type": "code", "execution_count": 4, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "---------------------------------------------------------------------------\n", "KERNEL: loopy_kernel\n", "---------------------------------------------------------------------------\n", "ARGUMENTS:\n", "f: GlobalArg, type: , shape: (2 + n), dim_tags: (N0:stride:1)\n", "h: ValueArg, type: \n", "n: ValueArg, type: \n", "out: GlobalArg, type: , shape: (2 + n), dim_tags: (N0:stride:1)\n", "---------------------------------------------------------------------------\n", "DOMAINS:\n", "[n] -> { [i] : 0 < i <= n }\n", "---------------------------------------------------------------------------\n", "INAME IMPLEMENTATION TAGS:\n", "i: None\n", "---------------------------------------------------------------------------\n", "INSTRUCTIONS:\n", "[i] \u001b[34mout[i]\u001b[0m <- \u001b[35m(-1)*(f[i + 1] + (-1)*f[i + -1]) / (2*h)\u001b[0m # \u001b[32minsn\u001b[0m\n", "---------------------------------------------------------------------------\n" ] } ], "source": [ "fin_diff_knl = lp.make_kernel(\n", " \"{[i]: 1<=i<=n}\",\n", " \"out[i] = -(f[i+1] - f[i-1])/(2*h)\",\n", " [lp.GlobalArg(\"out\", shape=\"2+n\"), ...])\n", "print(fin_diff_knl)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "------------\n", "Next, make the flux kernel as `flux_knl`.\n", "* Use `j` as the loop variable.\n", "* Make sure to declare `f` and `u` to be the right size." ] }, { "cell_type": "code", "execution_count": 5, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "---------------------------------------------------------------------------\n", "KERNEL: loopy_kernel\n", "---------------------------------------------------------------------------\n", "ARGUMENTS:\n", "f: GlobalArg, type: , shape: (2 + n), dim_tags: (N0:stride:1)\n", "u: GlobalArg, type: , shape: (2 + n), dim_tags: (N0:stride:1)\n", "---------------------------------------------------------------------------\n", "DOMAINS:\n", "[n] -> { [j] : 0 < j <= n }\n", "---------------------------------------------------------------------------\n", "INAME IMPLEMENTATION TAGS:\n", "j: None\n", "---------------------------------------------------------------------------\n", "INSTRUCTIONS:\n", "[j] \u001b[34mf[j]\u001b[0m <- \u001b[35mu[j]**2 / 2\u001b[0m # \u001b[32minsn\u001b[0m\n", "---------------------------------------------------------------------------\n" ] } ], "source": [ "flux_knl = lp.make_kernel(\n", " \"{[j]: 1<=j<=n}\",\n", " \"f[j] = u[j]**2/2\",\n", " [\n", " lp.GlobalArg(\"f\", shape=\"2+n\"),\n", " lp.GlobalArg(\"u\", shape=\"2+n\"),\n", " ])\n", "print(flux_knl)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Fuse the Kernels\n", "\n", "Next, fuse the two kernels together as `fused_knl`, using `lp.fuse_kernels([knl_a, knl_b])`:" ] }, { "cell_type": "code", "execution_count": 6, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "---------------------------------------------------------------------------\n", "KERNEL: loopy_kernel_and_loopy_kernel\n", "---------------------------------------------------------------------------\n", "ARGUMENTS:\n", "f: GlobalArg, type: , shape: (2 + n), dim_tags: (N0:stride:1)\n", "h: ValueArg, type: \n", "n: ValueArg, type: \n", "out: GlobalArg, type: , shape: (2 + n), dim_tags: (N0:stride:1)\n", "u: GlobalArg, type: , shape: (2 + n), dim_tags: (N0:stride:1)\n", "---------------------------------------------------------------------------\n", "DOMAINS:\n", "[n] -> { [i] : 0 < i <= n }\n", "[n] -> { [j] : 0 < j <= n }\n", "---------------------------------------------------------------------------\n", "INAME IMPLEMENTATION TAGS:\n", "i: None\n", "j: None\n", "---------------------------------------------------------------------------\n", "INSTRUCTIONS:\n", "[j] \u001b[34mf[j]\u001b[0m <- \u001b[35mu[j]**2 / 2\u001b[0m # \u001b[32minsn_0\u001b[0m\n", "[i] \u001b[34mout[i]\u001b[0m <- \u001b[35m(-1)*(f[i + 1] + (-1)*f[i + -1]) / (2*h)\u001b[0m # \u001b[32minsn\u001b[0m\n", "---------------------------------------------------------------------------\n" ] } ], "source": [ "fused_knl = lp.fuse_kernels([fin_diff_knl, flux_knl])\n", "\n", "print(fused_knl)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's take a look at the generated code:" ] }, { "cell_type": "code", "execution_count": 7, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "#define lid(N) ((int) get_local_id(N))\n", "#define gid(N) ((int) get_group_id(N))\n", "#if __OPENCL_C_VERSION__ < 120\n", "#pragma OPENCL EXTENSION cl_khr_fp64: enable\n", "#endif\n", "\n", "__kernel void __attribute__ ((reqd_work_group_size(1, 1, 1))) loopy_kernel_and_loopy_kernel(__global double *restrict out, __global double *restrict f, float const h, int const n, __global double const *restrict u)\n", "{\n", " for (int j = 1; j <= n; ++j)\n", " f[j] = u[j] * u[j] / 2.0;\n", " for (int i = 1; i <= n; ++i)\n", " out[i] = -1.0 * (f[i + 1] + -1.0 * f[i + -1]) / (2.0 * h);\n", "}\n" ] } ], "source": [ "fused_knl = lp.set_options(fused_knl, write_cl=True)\n", "evt, _ = fused_knl(queue, u=u, h=np.float32(1e-1))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Next, eliminate the separate loop to compute `f`:" ] }, { "cell_type": "code", "execution_count": 8, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "---------------------------------------------------------------------------\n", "KERNEL: loopy_kernel_and_loopy_kernel\n", "---------------------------------------------------------------------------\n", "ARGUMENTS:\n", "h: ValueArg, type: \n", "n: ValueArg, type: \n", "out: GlobalArg, type: , shape: (2 + n), dim_tags: (N0:stride:1)\n", "u: GlobalArg, type: , shape: (2 + n), dim_tags: (N0:stride:1)\n", "---------------------------------------------------------------------------\n", "DOMAINS:\n", "[n] -> { [i] : 0 < i <= n }\n", "[n] -> { [j] : 0 < j <= n }\n", "---------------------------------------------------------------------------\n", "INAME IMPLEMENTATION TAGS:\n", "i: None\n", "j: None\n", "---------------------------------------------------------------------------\n", "SUBSTIUTION RULES:\n", "f_subst(j) := u[j]**2 / 2\n", "---------------------------------------------------------------------------\n", "INSTRUCTIONS:\n", "[i] \u001b[34mout[i]\u001b[0m <- \u001b[35m(-1)*(f_subst(i + 1) + (-1)*f_subst(i + -1)) / (2*h)\u001b[0m # \u001b[32minsn\u001b[0m\n", "---------------------------------------------------------------------------\n" ] } ], "source": [ "fused_knl = lp.assignment_to_subst(fused_knl, \"f\")\n", "print(fused_knl)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Again, let's take a look at the generated code:" ] }, { "cell_type": "code", "execution_count": 9, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "#define lid(N) ((int) get_local_id(N))\n", "#define gid(N) ((int) get_group_id(N))\n", "#if __OPENCL_C_VERSION__ < 120\n", "#pragma OPENCL EXTENSION cl_khr_fp64: enable\n", "#endif\n", "\n", "__kernel void __attribute__ ((reqd_work_group_size(1, 1, 1))) loopy_kernel_and_loopy_kernel(__global double *restrict out, float const h, int const n, __global double const *restrict u)\n", "{\n", " for (int i = 1; i <= n; ++i)\n", " out[i] = -1.0 * (u[i + 1] * u[i + 1] / 2.0 + -1.0 * u[i + -1] * u[i + -1] / 2.0) / (2.0 * h);\n", "}\n" ] } ], "source": [ "fused_knl = lp.set_options(fused_knl, write_cl=True)\n", "evt, _ = fused_knl(queue, u=u, h=np.float32(1e-1))" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Transform the kernel for execution\n", "\n", "For easier transformation, renumber the loop to start at 0, using `affine_map_inames(kernel, old_inames, new_inames, equations)`:" ] }, { "cell_type": "code", "execution_count": 10, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "---------------------------------------------------------------------------\n", "KERNEL: loopy_kernel_and_loopy_kernel\n", "---------------------------------------------------------------------------\n", "ARGUMENTS:\n", "h: ValueArg, type: \n", "n: ValueArg, type: \n", "out: GlobalArg, type: , shape: (2 + n), dim_tags: (N0:stride:1)\n", "u: GlobalArg, type: , shape: (2 + n), dim_tags: (N0:stride:1)\n", "---------------------------------------------------------------------------\n", "DOMAINS:\n", "[n] -> { [inew] : 0 <= inew < n }\n", "[n] -> { [j] : 0 < j <= n }\n", "---------------------------------------------------------------------------\n", "INAME IMPLEMENTATION TAGS:\n", "inew: None\n", "j: None\n", "---------------------------------------------------------------------------\n", "SUBSTIUTION RULES:\n", "f_subst(j) := u[j]**2 / 2\n", "---------------------------------------------------------------------------\n", "INSTRUCTIONS:\n", "[inew] \u001b[34mout[1 + inew]\u001b[0m <- \u001b[35m(-1)*(f_subst(1 + 1 + inew) + (-1)*f_subst(-1 + 1 + inew)) / (2*h)\u001b[0m # \u001b[32minsn\u001b[0m\n", "---------------------------------------------------------------------------\n" ] } ], "source": [ "fused0_knl = lp.affine_map_inames(fused_knl, \"i\", \"inew\", \"inew+1=i\")\n", "\n", "print(fused0_knl)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, map the kernel to OpenCL axes:" ] }, { "cell_type": "code", "execution_count": 11, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "---------------------------------------------------------------------------\n", "KERNEL: loopy_kernel_and_loopy_kernel\n", "---------------------------------------------------------------------------\n", "ARGUMENTS:\n", "h: ValueArg, type: \n", "n: ValueArg, type: \n", "out: GlobalArg, type: , shape: (2 + n), dim_tags: (N0:stride:1)\n", "u: GlobalArg, type: , shape: (2 + n), dim_tags: (N0:stride:1)\n", "---------------------------------------------------------------------------\n", "DOMAINS:\n", "[n] -> { [inew_outer, inew_inner] : inew_inner >= 0 and -128inew_outer <= inew_inner <= 127 and inew_inner < n - 128inew_outer }\n", "[n] -> { [j] : 0 < j <= n }\n", "---------------------------------------------------------------------------\n", "INAME IMPLEMENTATION TAGS:\n", "inew_inner: l.0\n", "inew_outer: g.0\n", "j: None\n", "---------------------------------------------------------------------------\n", "SUBSTIUTION RULES:\n", "f_subst(j) := u[j]**2 / 2\n", "---------------------------------------------------------------------------\n", "INSTRUCTIONS:\n", "[inew_inner,inew_outer] \u001b[34mout[1 + inew_inner + inew_outer*128]\u001b[0m <- \u001b[35m(-1)*(f_subst(1 + 1 + inew_inner + inew_outer*128) + (-1)*f_subst(-1 + 1 + inew_inner + inew_outer*128)) / (2*h)\u001b[0m # \u001b[32minsn\u001b[0m\n", "---------------------------------------------------------------------------\n" ] } ], "source": [ "gpu_knl = lp.split_iname(fused0_knl, \"inew\", 128, outer_tag=\"g.0\", inner_tag=\"l.0\")\n", "print(gpu_knl)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Finally, precompute the fluxes locally in each workgroup:" ] }, { "cell_type": "code", "execution_count": 12, "metadata": { "collapsed": false }, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "#define lid(N) ((int) get_local_id(N))\n", "#define gid(N) ((int) get_group_id(N))\n", "#if __OPENCL_C_VERSION__ < 120\n", "#pragma OPENCL EXTENSION cl_khr_fp64: enable\n", "#endif\n", "\n", "__kernel void __attribute__ ((reqd_work_group_size(128, 1, 1))) loopy_kernel_and_loopy_kernel(__global double *restrict out, double const h, int const n, __global double const *restrict u)\n", "{\n", " __local double f_subst_0[130];\n", "\n", " if (1 + -128 * gid(0) + -1 * lid(0) + n >= 0)\n", " {\n", " f_subst_0[lid(0) + 0 * 128] = u[128 * gid(0) + lid(0) + 0 * 128] * u[128 * gid(0) + lid(0) + 0 * 128] / 2.0;\n", " if (1 + -1 * lid(0) >= 0 && -127 + -128 * gid(0) + -1 * lid(0) + n >= 0)\n", " f_subst_0[lid(0) + 1 * 128] = u[128 * gid(0) + lid(0) + 1 * 128] * u[128 * gid(0) + lid(0) + 1 * 128] / 2.0;\n", " }\n", " barrier(CLK_LOCAL_MEM_FENCE) /* for f_subst_0 (insn depends on f_subst) */;\n", " if (-1 + -128 * gid(0) + -1 * lid(0) + n >= 0)\n", " out[1 + lid(0) + gid(0) * 128] = -1.0 * (f_subst_0[2 + lid(0)] + -1.0 * f_subst_0[lid(0)]) / (2.0 * h);\n", "}\n" ] } ], "source": [ "precomp_knl = lp.precompute(gpu_knl, \"f_subst\", \"inew_inner\", fetch_bounding_box=True)\n", "precomp_knl = lp.tag_inames(precomp_knl, {\"j_0_outer\": \"unr\"})\n", "precomp_knl = lp.set_options(precomp_knl, return_dict=True)\n", "evt, _ = precomp_knl(queue, u=u, h=h)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Run the PDE" ] }, { "cell_type": "code", "execution_count": 13, "metadata": { "collapsed": false }, "outputs": [ { "data": { "image/png": "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\n", "text/plain": [ "" ] }, "metadata": {}, "output_type": "display_data" } ], "source": [ "import matplotlib.pyplot as pt\n", "\n", "# Forward Euler on a hyperbolic problem: terrible idea. Oh well.\n", "\n", "dt = 0.2*h\n", "u = cl.clmath.sin(cl.array.to_device(queue, grid))\n", "for i in range(1800):\n", " _, result_dict = precomp_knl(queue, u=u, h=h)\n", " out = result_dict[\"out\"]\n", " out[0] = out[-1] = 0\n", " u = u + dt*out\n", " \n", " if i % 300 == 0:\n", " pt.plot(u.get())" ] }, { "cell_type": "code", "execution_count": null, "metadata": { "collapsed": false }, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "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.5.1+" } }, "nbformat": 4, "nbformat_minor": 0 }