From 32aaf8b4698f82d9b1317e2990267f57acc3310c Mon Sep 17 00:00:00 2001 From: Philippe Tillet Date: Sat, 6 Mar 2021 22:06:32 -0500 Subject: [PATCH] [GH-PAGES] Updated website --- .../02-fused-softmax.ipynb | 71 ++++++--- .../01-vector-add.py | 51 ++++-- .../tutorials_jupyter.zip | Bin 16525 -> 20276 bytes .../tutorials_python.zip | Bin 12683 -> 15822 bytes .../02-fused-softmax.py | 102 +++++++----- .../01-vector-add.ipynb | 29 +++- _images/sphx_glr_01-vector-add_001.png | Bin 0 -> 22458 bytes _images/sphx_glr_01-vector-add_thumb.png | Bin 26980 -> 14410 bytes _images/sphx_glr_02-fused-softmax_001.png | Bin 34439 -> 29969 bytes _images/sphx_glr_02-fused-softmax_thumb.png | Bin 22643 -> 20701 bytes .../tutorials/01-vector-add.rst.txt | 82 ++++++---- .../tutorials/02-fused-softmax.rst.txt | 147 +++++++++--------- .../tutorials/sg_execution_times.rst.txt | 6 +- getting-started/tutorials/01-vector-add.html | 58 ++++--- .../tutorials/02-fused-softmax.html | 121 +++++++------- .../tutorials/sg_execution_times.html | 6 +- searchindex.js | 2 +- 17 files changed, 400 insertions(+), 275 deletions(-) create mode 100644 _images/sphx_glr_01-vector-add_001.png diff --git a/_downloads/034d953b6214fedce6ea03803c712b89/02-fused-softmax.ipynb b/_downloads/034d953b6214fedce6ea03803c712b89/02-fused-softmax.ipynb index 8d6a725c1..d4817fc0a 100644 --- a/_downloads/034d953b6214fedce6ea03803c712b89/02-fused-softmax.ipynb +++ b/_downloads/034d953b6214fedce6ea03803c712b89/02-fused-softmax.ipynb @@ -15,7 +15,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n# Fused Softmax\nIn this tutorial, you will write a fused softmax layer that outperform's PyTorch implementation and learn about:\n\n- The benefits of kernel fusion for bandwidth-bound operations.\n- The syntax and usage of reduction operators in Triton.\n- The automatic vectorization capabilities of the Triton compiler.\n" + "\n# Fused Softmax\nIn this tutorial, you will write a fused softmax operation (that outperforms PyTorch) and learn about:\n\n- The benefits of kernel fusion for bandwidth-bound operations.\n- The syntax and usage of reduction operators in Triton.\n- The automatic vectorization capabilities of the Triton compiler.\n" ] }, { @@ -40,21 +40,21 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "When implemented naively in pytorch, computing :code:`y = naive_softmax(x)` for $x \\in R^{M \\times N}$ requires reading $7MN$ elements from DRAM and writing back $3MN + 2M$ elements.\nInstead, we want to write a custom \"fused\" pytorch operators that only reads X once and does all the necessary computations on-chip.\nThis would require reading and writing back only $MN$ bytes, so we could expect a theoretical speed-up of 5x.\nIn practice, though, we expect less because our kernel will spend some time computing exponentials and moving data around in shared memory.\n\n" + "When implemented naively in pytorch, computing :code:`y = naive_softmax(x)` for $x \\in R^{M \\times N}$ requires reading $7MN$ elements from DRAM and writing back $3MN + 2M$ elements.\nThis is obviously wasteful; we'd prefer to have a custom \"fused\" kernel that only reads X once and does all the necessary computations on-chip.\nIn this case, we would be reading and writing back only $MN$ bytes, so we could expect a theoretical speed-up of ~5x (i.e., $(10MN + 2M) / 2MN$).\nIn practice, though, we would be getting a bit less as our kernel computes exponentials and internally moves data around in shared memory.\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Compute Kernel\nOur softmax kernel works as follows: each program loads a row of X and writes back a normalized row of Y. Note that one important limitation of Triton is that each block must have a power-of-two number of elements, which means that we need to guard the memory operations properly if we want to handle any possible input shapes:\n\n .. code-block:: C\n\n __global__ void softmax(float* Y, float* X, int stride_xm, int stride_ym, int M, int N){\n // row index\n int m = get_program_id(0);\n // column indices\n int n [BLOCK] = 0 ... BLOCK;\n // the memory address of all the elements\n // that we want to load can be computed as follows\n float* px [BLOCK] = X + m*stride_xm + n;\n // because BLOCK has to be a power of two\n // (per Triton-C specs), it is important\n // to guard each memory operation with predicates\n // or we will read out of bounds\n bool check[BLOCK] = n < N;\n float x [BLOCK] = check ? *px : -F32_INFINITY;\n // syntax for reduction in Triton is:\n // x[..., OPERATOR, ...]\n // ^\n // index\n // The operators currently supported are {min, max, +}\n float z [BLOCK] = x - x[max];\n // The exponential in Triton is fast but approximate\n // (i.e., like __expf in CUDA)\n float num [BLOCK] = exp(z);\n float denom = num[+];\n // The result of the reduction is now stored in y\n float y [BLOCK] = num / denom;\n // We write it back\n float* py [BLOCK] = Y + m*stride_ym + n;\n *?(check)py = y;\n }\n\n" + "## Compute Kernel\nOur softmax kernel works as follows: each program loads a row of the input X, normalizes it and writes back the result to the output Y.\nNote that one important limitation of Triton is that each block must have a power-of-two number of elements,\nso we need to internally \"pad\" tiles and guard the memory operations properly if we want to handle any possible input shapes:\n\n .. code-block:: C\n\n __global__ void softmax(float* Y, float* X, int stride_xm, int stride_ym, int M, int N){\n // row index\n int m = get_program_id(0);\n // column indices\n int n [BLOCK] = 0 ... BLOCK;\n // the memory address of all the elements\n // that we want to load can be computed as follows\n float* px [BLOCK] = X + m*stride_xm + n;\n // because BLOCK has to be a power of two\n // (per Triton-C specs), it is important\n // to guard each memory operation with predicates\n // or we will read out of bounds\n bool check[BLOCK] = n < N;\n float x [BLOCK] = check ? *px : -F32_INFINITY;\n // syntax for reduction in Triton is:\n // x[:, :, OPERATOR, :, :]\n // ^\n // index\n // where operator is in {min, max, +}\n // for 1D vectors, this is just x[OPERATOR].\n float z [BLOCK] = x - x[max];\n // Note that exponentials in Triton are fast\n // but approximate (i.e., think __expf in CUDA)\n float num [BLOCK] = exp(z);\n float denom = num[+];\n // The result of the reduction is now stored in y\n float y [BLOCK] = num / denom;\n // We write it back\n float* py [BLOCK] = Y + m*stride_ym + n;\n *?(check)py = y;\n }\n\n" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ - "## Torch Bindings\nWe need to make sure that BLOCK is the smallest power of two\ngreater than the number of rows N of the input matrix.\nDifferent values of BLOCK will result in different kernels\n\n" + "## Torch Bindings\nHere our torch bindings is quite similar to that of the vector addition mentioned in the previous tutorial.\nWe just need to make sure that BLOCK is the smallest power of two greater than the number of columns N of the input matrix.\nThis means that different values of BLOCK will result in different kernels\n\n" ] }, { @@ -65,7 +65,14 @@ }, "outputs": [], "source": [ - "import torch\nimport triton\n\n# Source code for the Triton kernel\n_src = \"\"\"\n__global__ void softmax(float* Y, float* X, int stride_ym, int stride_xm, int M, int N){\n int m = get_program_id(0);\n int n [BLOCK] = 0 ... BLOCK;\n float* px [BLOCK] = X + m*stride_xm + n;\n bool check[BLOCK] = n < N;\n float x [BLOCK] = check ? *px : -F32_INFINITY;\n float z [BLOCK] = x - x[max];\n float num [BLOCK] = exp(z);\n float denom = num[+];\n float y [BLOCK] = num / denom;\n float* py [BLOCK] = Y + m*stride_ym + n;\n *?(check)py = y; \n}\n\"\"\"\n\n\ndef next_power_of_2(n):\n n -= 1\n n |= n >> 1\n n |= n >> 2\n n |= n >> 4\n n |= n >> 8\n n |= n >> 16\n n += 1\n return n\n\n\n_kernels = dict()\n\n\ndef make_kernel(N, device):\n BLOCK = next_power_of_2(N)\n key = (BLOCK, device)\n if key not in _kernels:\n defines = {'BLOCK': BLOCK}\n _kernels[key] = triton.kernel(_src, device=device, defines=defines)\n return _kernels[key]\n\n\nclass _softmax(torch.autograd.Function):\n @staticmethod\n def forward(ctx, x):\n # constraints of the op\n assert x.dtype == torch.float32\n y = torch.empty_like(x)\n # *create launch grid*:\n # here we just launch a grid of M programs\n M, N = y.shape\n grid = lambda opt: (M, )\n # *launch kernel*:\n kernel = make_kernel(N, y.device)\n kernel(y.data_ptr(), x.data_ptr(), y.stride(0), x.stride(0), M, N, grid=grid)\n return y\n\n\nsoftmax = _softmax.apply" + "import torch\nimport triton\n\n# Source code for the Triton kernel\n_src = \"\"\"\n__global__ void softmax(float* Y, float* X, int stride_ym, int stride_xm, int M, int N){\n int m = get_program_id(0);\n int n [BLOCK] = 0 ... BLOCK;\n float* px [BLOCK] = X + m*stride_xm + n;\n bool check[BLOCK] = n < N;\n float x [BLOCK] = check ? *px : -F32_INFINITY;\n float z [BLOCK] = x - x[max];\n float num [BLOCK] = exp(z);\n float denom = num[+];\n float y [BLOCK] = num / denom;\n float* py [BLOCK] = Y + m*stride_ym + n;\n *?(check)py = y; \n}\n\"\"\"\n\n\n# helper function to get the smaller power-of-two larger than a given number\ndef next_power_of_2(n):\n n -= 1\n n |= n >> 1\n n |= n >> 2\n n |= n >> 4\n n |= n >> 8\n n |= n >> 16\n n += 1\n return n\n\n\n# kernel caching mechanism\ndef make_kernel(N, device):\n cache = make_kernel.cache\n # Now are kernels are indexed not only by the provided device but also\n # by the rounded number of columns in the input matrix\n BLOCK = next_power_of_2(N)\n key = (BLOCK, device)\n if key not in cache:\n defines = {'BLOCK': BLOCK}\n cache[key] = triton.kernel(_src, device=device, defines=defines)\n return cache[key]\n\n\nmake_kernel.cache = dict()\n\n\nclass _softmax(torch.autograd.Function):\n @staticmethod\n def forward(ctx, x):\n # constraints of the op\n assert x.dtype == torch.float32\n y = torch.empty_like(x)\n # The launch grid is simple: we have one kernel instance per row of the input matrix\n M, N = y.shape\n grid = lambda opt: (M, )\n # Launch kernel\n kernel = make_kernel(N, y.device)\n kernel(y.data_ptr(), x.data_ptr(), y.stride(0), x.stride(0), M, N, grid=grid)\n return y\n\n\nsoftmax = _softmax.apply" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "We can use the above softmax function to compute the row-wise softmax of a given matrix.\n\n" ] }, { @@ -75,29 +82,11 @@ "## Unit Test\n\n" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": false - }, - "outputs": [], - "source": [ - "x = torch.randn(1823, 781, device='cuda')\ny_tri = softmax(x)\ny_ref = torch.softmax(x, axis=1)\nprint(y_tri)\nprint(y_ref)\nprint(torch.allclose(y_tri, y_ref))" - ] - }, { "cell_type": "markdown", "metadata": {}, "source": [ - "Seems to work!\n\n" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## Benchmarking\n\n" + "We make sure that we test our kernel on a matrix with an irregular number of rows and columns.\nThis will allow us to verify that our padding mechanism works.\n\n" ] }, { @@ -108,7 +97,39 @@ }, "outputs": [], "source": [ - "import matplotlib.pyplot as plt\n\nM = 4096\nNs = [128 * i for i in range(2, 50)]\ntri_ms = []\nref_ms = []\ndef_ms = []\nfor N in Ns:\n x = torch.randn(M, N, device='cuda', dtype=torch.float32)\n gbps = lambda ms: x.nelement() * x.element_size() * 1e-9 / (ms * 1e-3)\n tri_ms += [gbps(triton.testing.do_bench(lambda: softmax(x)))]\n ref_ms += [gbps(triton.testing.do_bench(lambda: torch.softmax(x, axis=1)))]\n def_ms += [gbps(triton.testing.do_bench(lambda: naive_softmax(x)))]\nplt.xlabel('N')\nplt.ylabel('Bandwidth (GB/s)')\nplt.plot(Ns, tri_ms, label='Triton')\nplt.plot(Ns, ref_ms, label='Torch')\nplt.plot(Ns, def_ms, label='Naive')\nplt.legend()\nplt.show()" + "torch.manual_seed(0)\nx = torch.randn(1823, 781, device='cuda')\ny_tri = softmax(x)\ny_ref = torch.softmax(x, axis=1)\nprint(torch.allclose(y_tri, y_ref))" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "As expected, the results are identical.\n\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Benchmarking\nHere we will benchmark our operation as a function of the number of columns in the input matrix -- assuming 4096 rows.\nWe will then compare its performance against (1) :code:`torch.softmax` and (2) the :code:`naive_softmax` defined above.\n\n" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": { + "collapsed": false + }, + "outputs": [], + "source": [ + "import matplotlib.pyplot as plt\n\nM = 4096\nNs = [256 * i for i in range(2, 50)]\ntri_bw = []\nref_bw = []\ndef_bw = []\nfor N in Ns:\n x = torch.randn(M, N, device='cuda', dtype=torch.float32)\n gbps = lambda ms: x.nelement() * x.element_size() * 1e-9 / (ms * 1e-3)\n do_bench = lambda fn: gbps(triton.testing.do_bench(fn, warmup=10, rep=100, clear_l2=True))\n tri_bw += [do_bench(lambda: softmax(x))]\n ref_bw += [do_bench(lambda: torch.softmax(x, axis=1))]\n def_bw += [do_bench(lambda: naive_softmax(x))]\nplt.xlabel('N')\nplt.ylabel('Bandwidth (GB/s)')\nplt.plot(Ns, tri_bw, label='Triton')\nplt.plot(Ns, ref_bw, label='Torch')\nplt.plot(Ns, def_bw, label='Naive')\nplt.legend()\nplt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "In the above plot, we can see that:\n\n - Triton is 4-5x faster than the naive implementation, which is consistent with our theoretical predictions.\n - Triton is significantly faster than :code:`torch.softmax` for very large input matrices. My guess from looking at the source-code of the `PyTorch kernel `_ is that PyTorch only partially fuses the computation of the softmax.\n This means that -- when temporary data is too large to fit entirely in the GPU's cache -- it transfers almost twice the amount of data necessary.\n Note that our Triton kernel is not only faster than PyTorch's CUDA kernel, it is also **easier to read, understand and maintain**.\n" ] } ], diff --git a/_downloads/62d97d49a32414049819dd8bb8378080/01-vector-add.py b/_downloads/62d97d49a32414049819dd8bb8378080/01-vector-add.py index e99216788..f04f1add5 100644 --- a/_downloads/62d97d49a32414049819dd8bb8378080/01-vector-add.py +++ b/_downloads/62d97d49a32414049819dd8bb8378080/01-vector-add.py @@ -1,7 +1,7 @@ """ Vector Addition ================= -In this tutorial, you will write a simple, high-performance vector addition using Triton and learn about: +In this tutorial, you will write a simple vector addition using Triton and learn about: - The basic syntax of the Triton programming language - The best practices for creating PyTorch custom operators using the :code:`triton.kernel` Python API @@ -122,9 +122,15 @@ class _add(torch.autograd.Function): # Just like we standard PyTorch ops We use the :code:`.apply` method to create a callable object for our function add = _add.apply +# %% +# We can now use the above function to compute the sum of two `torch.tensor` objects: + # %% # Unit Test # -------------------------- +# +# Of course, the first thing that we should check is that whether kernel is correct. This is pretty easy to test, as shown below: + torch.manual_seed(0) x = torch.rand(98432, device='cuda') y = torch.rand(98432, device='cuda') @@ -134,17 +140,40 @@ print(za) print(zb) print(f'The maximum difference between torch and triton is ' f'{torch.max(torch.abs(za - zb))}') +# %% +# Seems like we're good to go! + # %% # Benchmarking # -------------------------- -# We can now benchmark our custom op for vectors of increasing sizes to get a sense of how it does +# We can now benchmark our custom op for vectors of increasing sizes to get a sense of how it does relative to PyTorch. -warmup = 10 -rep = 200 -for N in [2**i for i in range(17, 26, 1)]: - x = torch.rand(N, device='cuda') - y = torch.rand(N, device='cuda') - triton_ms = triton.testing.do_bench(lambda: add(x, y), warmup=warmup, rep=rep) - torch_ms = triton.testing.do_bench(lambda: x + y, warmup=warmup, rep=rep) - # print the performance of triton and torch as well as the achieved bandwidth - print(f'{N} {triton_ms:.3f} {torch_ms:.3f}') \ No newline at end of file +import matplotlib.pyplot as plt + +# There are three tensors of 4N bytes each. So the bandwidth of a given kernel +# is 12N / time_ms * 1e-6 GB/s +gbps = lambda N, ms: 12 * N / ms * 1e-6 +# We want to benchmark small and large vector alike +sizes = [2**i for i in range(12, 25, 1)] +triton_bw = [] +torch_bw = [] +for N in sizes: + x = torch.rand(N, device='cuda', dtype=torch.float32) + y = torch.rand(N, device='cuda', dtype=torch.float32) + # Triton provide a do_bench utility function that can be used to benchmark + # arbitrary workloads. It supports a `warmup` parameter that is used to stabilize + # GPU clock speeds as well as a `rep` parameter that controls the number of times + # the benchmark is repeated. Importantly, we set `clear_l2 = True` to make sure + # that the L2 cache does not contain any element of x before each kernel call when + # N is small. + do_bench = lambda fn: gbps(N, triton.testing.do_bench(fn, warmup=10, rep=100, clear_l2=True)) + triton_bw += [do_bench(lambda: add(x, y))] + torch_bw += [do_bench(lambda: x + y)] +# We plot the results as a semi-log +plt.semilogx(sizes, triton_bw, label='Triton') +plt.semilogx(sizes, torch_bw, label='Torch') +plt.legend() +plt.show() + +# %% +# Seems like our simple element-wise operation operates at peak bandwidth. While this is a fairly low bar for a custom GPU programming language, this is a good start before we move to more advanced operations. \ No newline at end of file diff --git a/_downloads/662999063954282841dc90b8945f85ce/tutorials_jupyter.zip b/_downloads/662999063954282841dc90b8945f85ce/tutorials_jupyter.zip index 88a2898e6aeb74a52c414b154a042409423dcff6..1a024e60009d26bc820290f4631691cf528f8a46 100644 GIT binary patch delta 4410 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 OlyR<}-u;6mzVSc&BKY_K delta 1361 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 ZB@!>iZ)@}S)d%SmKB#0*J)MXa{s9!m&oKZ1 diff --git a/_downloads/d91442ac2982c4e0cc3ab0f43534afbc/02-fused-softmax.py b/_downloads/d91442ac2982c4e0cc3ab0f43534afbc/02-fused-softmax.py index f715e1af0..af8ca44cf 100644 --- a/_downloads/d91442ac2982c4e0cc3ab0f43534afbc/02-fused-softmax.py +++ b/_downloads/d91442ac2982c4e0cc3ab0f43534afbc/02-fused-softmax.py @@ -1,7 +1,7 @@ """ Fused Softmax ================= -In this tutorial, you will write a fused softmax layer that outperform's PyTorch implementation and learn about: +In this tutorial, you will write a fused softmax operation (that outperforms PyTorch) and learn about: - The benefits of kernel fusion for bandwidth-bound operations. - The syntax and usage of reduction operators in Triton. @@ -35,14 +35,16 @@ def naive_softmax(x): # %% # When implemented naively in pytorch, computing :code:`y = naive_softmax(x)` for :math:`x \in R^{M \times N}` requires reading :math:`7MN` elements from DRAM and writing back :math:`3MN + 2M` elements. -# Instead, we want to write a custom "fused" pytorch operators that only reads X once and does all the necessary computations on-chip. -# This would require reading and writing back only :math:`MN` bytes, so we could expect a theoretical speed-up of 5x. -# In practice, though, we expect less because our kernel will spend some time computing exponentials and moving data around in shared memory. +# This is obviously wasteful; we'd prefer to have a custom "fused" kernel that only reads X once and does all the necessary computations on-chip. +# In this case, we would be reading and writing back only :math:`MN` bytes, so we could expect a theoretical speed-up of ~5x (i.e., :math:`(10MN + 2M) / 2MN`). +# In practice, though, we would be getting a bit less as our kernel computes exponentials and internally moves data around in shared memory. # %% # Compute Kernel -# ---------------------------- -# Our softmax kernel works as follows: each program loads a row of X and writes back a normalized row of Y. Note that one important limitation of Triton is that each block must have a power-of-two number of elements, which means that we need to guard the memory operations properly if we want to handle any possible input shapes: +# ---------------- +# Our softmax kernel works as follows: each program loads a row of the input X, normalizes it and writes back the result to the output Y. +# Note that one important limitation of Triton is that each block must have a power-of-two number of elements, +# so we need to internally "pad" tiles and guard the memory operations properly if we want to handle any possible input shapes: # # .. code-block:: C # @@ -61,13 +63,14 @@ def naive_softmax(x): # bool check[BLOCK] = n < N; # float x [BLOCK] = check ? *px : -F32_INFINITY; # // syntax for reduction in Triton is: -# // x[..., OPERATOR, ...] +# // x[:, :, OPERATOR, :, :] # // ^ # // index -# // The operators currently supported are {min, max, +} +# // where operator is in {min, max, +} +# // for 1D vectors, this is just x[OPERATOR]. # float z [BLOCK] = x - x[max]; -# // The exponential in Triton is fast but approximate -# // (i.e., like __expf in CUDA) +# // Note that exponentials in Triton are fast +# // but approximate (i.e., think __expf in CUDA) # float num [BLOCK] = exp(z); # float denom = num[+]; # // The result of the reduction is now stored in y @@ -79,10 +82,10 @@ def naive_softmax(x): # %% # Torch Bindings -# ---------------------------- -# We need to make sure that BLOCK is the smallest power of two -# greater than the number of rows N of the input matrix. -# Different values of BLOCK will result in different kernels +# --------------- +# Here our torch bindings is quite similar to that of the vector addition mentioned in the previous tutorial. +# We just need to make sure that BLOCK is the smallest power of two greater than the number of columns N of the input matrix. +# This means that different values of BLOCK will result in different kernels import torch import triton @@ -105,6 +108,7 @@ __global__ void softmax(float* Y, float* X, int stride_ym, int stride_xm, int M, """ +# helper function to get the smaller power-of-two larger than a given number def next_power_of_2(n): n -= 1 n |= n >> 1 @@ -116,16 +120,20 @@ def next_power_of_2(n): return n -_kernels = dict() - - +# kernel caching mechanism def make_kernel(N, device): + cache = make_kernel.cache + # Now are kernels are indexed not only by the provided device but also + # by the rounded number of columns in the input matrix BLOCK = next_power_of_2(N) key = (BLOCK, device) - if key not in _kernels: + if key not in cache: defines = {'BLOCK': BLOCK} - _kernels[key] = triton.kernel(_src, device=device, defines=defines) - return _kernels[key] + cache[key] = triton.kernel(_src, device=device, defines=defines) + return cache[key] + + +make_kernel.cache = dict() class _softmax(torch.autograd.Function): @@ -134,11 +142,10 @@ class _softmax(torch.autograd.Function): # constraints of the op assert x.dtype == torch.float32 y = torch.empty_like(x) - # *create launch grid*: - # here we just launch a grid of M programs + # The launch grid is simple: we have one kernel instance per row of the input matrix M, N = y.shape grid = lambda opt: (M, ) - # *launch kernel*: + # Launch kernel kernel = make_kernel(N, y.device) kernel(y.data_ptr(), x.data_ptr(), y.stride(0), x.stride(0), M, N, grid=grid) return y @@ -146,41 +153,58 @@ class _softmax(torch.autograd.Function): softmax = _softmax.apply +# %% +# We can use the above softmax function to compute the row-wise softmax of a given matrix. + # %% # Unit Test # ---------- +# %% +# We make sure that we test our kernel on a matrix with an irregular number of rows and columns. +# This will allow us to verify that our padding mechanism works. + +torch.manual_seed(0) x = torch.randn(1823, 781, device='cuda') y_tri = softmax(x) y_ref = torch.softmax(x, axis=1) -print(y_tri) -print(y_ref) print(torch.allclose(y_tri, y_ref)) -# %% -# Seems to work! +#%% +# As expected, the results are identical. # %% # Benchmarking -# ---------- +# ------------- +# Here we will benchmark our operation as a function of the number of columns in the input matrix -- assuming 4096 rows. +# We will then compare its performance against (1) :code:`torch.softmax` and (2) the :code:`naive_softmax` defined above. import matplotlib.pyplot as plt M = 4096 -Ns = [128 * i for i in range(2, 50)] -tri_ms = [] -ref_ms = [] -def_ms = [] +Ns = [256 * i for i in range(2, 50)] +tri_bw = [] +ref_bw = [] +def_bw = [] for N in Ns: x = torch.randn(M, N, device='cuda', dtype=torch.float32) gbps = lambda ms: x.nelement() * x.element_size() * 1e-9 / (ms * 1e-3) - tri_ms += [gbps(triton.testing.do_bench(lambda: softmax(x)))] - ref_ms += [gbps(triton.testing.do_bench(lambda: torch.softmax(x, axis=1)))] - def_ms += [gbps(triton.testing.do_bench(lambda: naive_softmax(x)))] + do_bench = lambda fn: gbps(triton.testing.do_bench(fn, warmup=10, rep=100, clear_l2=True)) + tri_bw += [do_bench(lambda: softmax(x))] + ref_bw += [do_bench(lambda: torch.softmax(x, axis=1))] + def_bw += [do_bench(lambda: naive_softmax(x))] plt.xlabel('N') plt.ylabel('Bandwidth (GB/s)') -plt.plot(Ns, tri_ms, label='Triton') -plt.plot(Ns, ref_ms, label='Torch') -plt.plot(Ns, def_ms, label='Naive') +plt.plot(Ns, tri_bw, label='Triton') +plt.plot(Ns, ref_bw, label='Torch') +plt.plot(Ns, def_bw, label='Naive') plt.legend() -plt.show() \ No newline at end of file +plt.show() + +# %% +# In the above plot, we can see that: +# +# - Triton is 4-5x faster than the naive implementation, which is consistent with our theoretical predictions. +# - Triton is significantly faster than :code:`torch.softmax` for very large input matrices. My guess from looking at the source-code of the `PyTorch kernel `_ is that PyTorch only partially fuses the computation of the softmax. +# This means that -- when temporary data is too large to fit entirely in the GPU's cache -- it transfers almost twice the amount of data necessary. +# Note that our Triton kernel is not only faster than PyTorch's CUDA kernel, it is also **easier to read, understand and maintain**. \ No newline at end of file diff --git a/_downloads/f191ee1e78dc52eb5f7cba88f71cef2f/01-vector-add.ipynb b/_downloads/f191ee1e78dc52eb5f7cba88f71cef2f/01-vector-add.ipynb index f15de3adb..17fd70fb1 100644 --- a/_downloads/f191ee1e78dc52eb5f7cba88f71cef2f/01-vector-add.ipynb +++ b/_downloads/f191ee1e78dc52eb5f7cba88f71cef2f/01-vector-add.ipynb @@ -15,7 +15,7 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "\n# Vector Addition\nIn this tutorial, you will write a simple, high-performance vector addition using Triton and learn about:\n\n- The basic syntax of the Triton programming language\n- The best practices for creating PyTorch custom operators using the :code:`triton.kernel` Python API\n- The best practices for validating and benchmarking custom ops against native reference implementations\n" + "\n# Vector Addition\nIn this tutorial, you will write a simple vector addition using Triton and learn about:\n\n- The basic syntax of the Triton programming language\n- The best practices for creating PyTorch custom operators using the :code:`triton.kernel` Python API\n- The best practices for validating and benchmarking custom ops against native reference implementations\n" ] }, { @@ -47,7 +47,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Unit Test\n\n" + "We can now use the above function to compute the sum of two `torch.tensor` objects:\n\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Unit Test\n\nOf course, the first thing that we should check is that whether kernel is correct. This is pretty easy to test, as shown below:\n\n" ] }, { @@ -65,7 +72,14 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "## Benchmarking\nWe can now benchmark our custom op for vectors of increasing sizes to get a sense of how it does\n\n" + "Seems like we're good to go!\n\n" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Benchmarking\nWe can now benchmark our custom op for vectors of increasing sizes to get a sense of how it does relative to PyTorch.\n\n" ] }, { @@ -76,7 +90,14 @@ }, "outputs": [], "source": [ - "warmup = 10\nrep = 200\nfor N in [2**i for i in range(17, 26, 1)]:\n x = torch.rand(N, device='cuda')\n y = torch.rand(N, device='cuda')\n triton_ms = triton.testing.do_bench(lambda: add(x, y), warmup=warmup, rep=rep)\n torch_ms = triton.testing.do_bench(lambda: x + y, warmup=warmup, rep=rep)\n # print the performance of triton and torch as well as the achieved bandwidth\n print(f'{N} {triton_ms:.3f} {torch_ms:.3f}')" + "import matplotlib.pyplot as plt\n\n# There are three tensors of 4N bytes each. So the bandwidth of a given kernel\n# is 12N / time_ms * 1e-6 GB/s\ngbps = lambda N, ms: 12 * N / ms * 1e-6\n# We want to benchmark small and large vector alike\nsizes = [2**i for i in range(12, 25, 1)]\ntriton_bw = []\ntorch_bw = []\nfor N in sizes:\n x = torch.rand(N, device='cuda', dtype=torch.float32)\n y = torch.rand(N, device='cuda', dtype=torch.float32)\n # Triton provide a do_bench utility function that can be used to benchmark\n # arbitrary workloads. It supports a `warmup` parameter that is used to stabilize\n # GPU clock speeds as well as a `rep` parameter that controls the number of times\n # the benchmark is repeated. Importantly, we set `clear_l2 = True` to make sure\n # that the L2 cache does not contain any element of x before each kernel call when\n # N is small.\n do_bench = lambda fn: gbps(N, triton.testing.do_bench(fn, warmup=10, rep=100, clear_l2=True))\n triton_bw += [do_bench(lambda: add(x, y))]\n torch_bw += [do_bench(lambda: x + y)]\n# We plot the results as a semi-log\nplt.semilogx(sizes, triton_bw, label='Triton')\nplt.semilogx(sizes, torch_bw, label='Torch')\nplt.legend()\nplt.show()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Seems like our simple element-wise operation operates at peak bandwidth. While this is a fairly low bar for a custom GPU programming language, this is a good start before we move to more advanced operations.\n" ] } ], diff --git a/_images/sphx_glr_01-vector-add_001.png b/_images/sphx_glr_01-vector-add_001.png new file mode 100644 index 0000000000000000000000000000000000000000..014f7e09192a6195194055981781e4adc8e277a1 GIT binary patch literal 22458 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 D$0F!? literal 0 HcmV?d00001 diff --git a/_images/sphx_glr_01-vector-add_thumb.png b/_images/sphx_glr_01-vector-add_thumb.png index 274c8ca2d426f6f994e82cd2c28774107660e4bd..c23f0ff44eb8c41cd592a5ba2cb33ef2cde7418a 100644 GIT binary patch literal 14410 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 RvHfy diff --git a/_images/sphx_glr_02-fused-softmax_001.png b/_images/sphx_glr_02-fused-softmax_001.png index 20bdb8fa5838e43a187a3451ba7d13241c6ec1e9..ef680644d57f1dbdfc700ccce16f33af7807f631 100644 GIT binary patch literal 29969 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

P0h(N3%sVuj4vPe6^Drq zB3Q4BhrP_}ds3h0!^GndSS*nLX4nyX&P=4aMO0^q2Jne#J5l1^*|2E2qBs<2_;(ju zL1#Ahx7*(9Z6D6E>BLvqQr#7QZ=0)BR%ia9DiF-qAgsV#;{$LZw+d zWQk^4sySf3xIh_r3AgpYRc#cjuPfh+$7~K^7j)LtO&c}cJmw-nVHG;u!id=uwi}e) z4!j;oS9k)#PWpR?Cqsj?ZAn(>Lf*Fm^RVQwwMFG}jYC%%1m}*_k9TBqD6_#Iw^geJ z3$AWe1zVPk5nb48dtTgxn;f3hwcmnGT=J(P?z8gsMQQz8N2ZmVs@L;pe-;N>&+35z z9~vu3#&6-?uOB_H+f9wcS>>4%T_%UK`%73AZOIFs_|QO=DHlF}=Z*dKn3zbRsVr9P z_90H8@Z_v78%KQyGXUQ=ILNBdQhe!P$4a@jdv+6M@)U2g^X5)x#Z7(dYH!c{cz#xx zTzb%GA|T_`LZ|)hNKjAKp9plQo11|9`EF_9!9sH?HW|-TeEfH+s?YW69F}_NQ#)p6 z616MMOKq3lHC=Br&3oNgK|n%JBH-{ER6*!oz4`_+>JMS$(sFWG8v3qT86p$>A_+EC zb2~G2sF4rK*M9{ViJdht0fgzG`+}#|iRSSxO@Rm&(uA^--V0y9t-Ci|Uzz@)ZjE|- zmlUsw@NJA+_RhQtZE@Uyx7_joote;-czr0^ap3bo(B{*;BsGC6%i=EPS=SIPv;v`# zQ>WkO3iVKi#iC2DBIh}1(XGyzO}d-#0N=$x))~tKT1&uGROaWkqnm6#FKy?zN;E}h ztfLEu42|F?kKWx@tP|hF7~@p=j`0l6$h*)Ncye)gM3UvjHupDVp5^6$_tQZDh)kbT zie(%^0-WxQtRROI@! zluXK97Xy_hg_f;JHg@WznXTnJy^bB$Kdwkz%O!hUItqeAJ=-L%hcb`@2~A4+^K>h^*vtw;^&xL8UBW zT_ud8ZDb{^sq^B#t}JYIly9?CYdW|u4|aZ-4vnqHY{oiJWtFL@>dq=VUzE1C(P-!C zahA5^|Jn9T)UpCz?BSHVkoC`jFJ2n+;&>y*1>?b};s?g^QqkGw{Od7m-do@YZ+7#! zlX`k+d%BZXm)2;>oVmppK={2h^kOh}jtR}mSeD_1&k$9+a zQS7MMNtI#Qz7!7JvoizP#}{WKQas;z2WzG181ApO^-{fCd3VxSmkuZ>>VC1-W_^uB zW(%<~$&%gZCsOc1lfQFTHBDOLBp&#Ye^p{G4uXCIth6Sf<>edTT^_c;+i9oLFC{`e zT&G#=YITl7$2!$-Wo2==zQ+O&y{awQbcDIpRv=4>R<4?)KDh_*gPhxy9r`Ff0~xo|dTw zZ$7+`d7>5}?r;At)P5!LRA**PeR-04Al%E!+}2`edtK_Q*sP+z5d+ocOM5ns*h-sTEH|I(2R-277mT>ZK%hQ@@{=CK3i zJ<*ibRf(HRvpqwd-+H2B;UGfW%I0so)I{s4?Xr=D9j=Ba_Q0$EzIy31gj%_BLVI)N z(d%^!mXPP_xN)yDNncN|3A{CmdB#17z+Dm4tT0T%e~*HZGB%}v)mWD+-l!ysYM_?b zWR;i78D?U}P(r#3u6kah`sgZqW1rrc54wiq9@=LBHw6OD`Hpn>~&;(Hj5tcyg@d-OC=#b(H78=n8Pb?V~&A^@RW zQcphPa5>q%bx<5tQA-}=kijVe)!)Xt*r^3O<&a006-Bjm)6V83)~Qfkjsrt)OU~IV z^DYlJK4}>I#omp@mUP*j!5*_$H1zHMwDAj(?#pVKX&cMzc3sG9T=(JTm2&A2e7PFB zFFU-5>ZE_d0QT85*qu37KILICZ|bux{!=$^bP!-qt?^pXkhBF?1Gv3SUgF-{u+(0u z&s6EdX?|ZpI zu@-Xziy12ig|(0 zCB0}J0-ZC53974kGr!a+t)@Hs4`89UUL1KfeQgUmDCZHX>J3UP7 zHI-_wZ01I`lTa^+PFiQ@8n{h}pg7|k>vgDKQpU24H#n?1Uo>gfrG7G#kWI+DcK!Q8 z;2VzPyUnM1{@3YVvASwW$r>#n>aAAG+d+h;YH<|x7XI`&@+Txo9N@-8{EFNhYMh%thwS*h7M^#1S}3AlFdxo$MLsyU z@vqOv^tK7zh&0;)sCRa{h;HSE=V3Cl7c7cvcOt43Qg)AlN^IikMz1ZD#8{MlB1L=q zV@yr=#aB${^riL-=o8&^L=5A|PtNT&0EE$&L>c9J}--q}mwvfyS%m^x4Cy55Kx$PIr1YX6&tf zJG;x}j^n^QsUj%Vdy!55SPXte>pwv$x7_~)t#^=VaXwC#sWZOz{z*pjg3q{*NMZg- zmlaEp>cxH4*6VNugYa>m`r?q~=4!6`#airk=xwTCTtm-p!$~R`8AkzZ&ZzUK7z>H8 z5gn604kmcvcC*Y`k@P~a*hfB4MYHw*ndySQ6yS=oK~5EM7oW-8&+I;Qk-i|xrF|&j z6R8Jh0AfjO`2?e`VnQRGrN9SsTx7#_AKv^N~? zsK0?>9@@gvUC+cgrv;-=$BUMA=qPm0UuB<+A0ypXoU@bEt~(x`IIL7vao5miPoT)o z@lYu&bsAKct*g3ElD5tZ&E#_kCkDi7n-v=puM%pq)pIH@9b9vVzPmEAy;1%lommmx zeN)}YfWYi~e2J1!&f<&Kr2T%*ylH@9^sH=%(_NMv*Rv6a2=n&zP%Ty$-?a*C9S)u_ z1d-_<%qJwFJ0PL-+WAJ~7Lg|Q{XFT=YUhon_v_l8CeVLUfJZ!a8zDQourPAH&XFfZot>{j zeRf^RXY&Idau!H(clQAcX@7?p4r?S!h3V&yFM>P$m%ZV3w4RMsmv^3J;B(SD#Ni%p zbG?6Bl_>g+=R$X4%l@R$QsieF09TJlZz6-CYJ0dG#ice<2TChAB$J%i?U-bxZT=JpC zgla-4_u_@5;0V{7;gJ_Vk}`zmoA=~FBm7r4o07dTYvF?+TD#Bq!{ka?z#U&?O1mQn zVd5WH!yO9yRVH~EqopSal%N zs0Kw@Wvlt~{h{v`+jj5Ezr*nB&6seHy*aBO>?M9$WzMC4vkug$mbX+!ZG>klSryML zO=S@a)%_j@rUt6>Kb2}Oj*GxaY&);=CE2vP>@J!?&nDbb(5_aiMMm%Qj>f>f+rP|mr`@(}@Cwk*7`PJ%m{Px><2Mg8E z_?_^70zXg9^pbOmZ5*c6M$Qj6PtOMax%>hd+vQ;q52y$;{u$d9TDO#^&bE$Le%L7Z z?^&5-8$LT=HInoorNXsr4~4T?39*8J^ZB2^{Vcv>hOM_3$w;YcR~Aya##-nB3mq*- z%$ND_72{RJl2+~T>=ku``(gShf%x*|(|9bFgNEJ9CIgCo6;Z3UPDF!+1z)%E+e`Z^ zgeHBvo{3LyKyu+;RM08RQTgPI*QNz@LN5WNy|2FYDHJFMP?~Z}I~+jHWl#k9m38BK zG-(CF+R{wiaOY)EkDdeFlf_ztDn#}?NzV^wrea>c(K%kT9YZ_ei$=b@Ha5}=JXu_6 zXQj!9a0=UFhzn{Ph02S%{93Caa+vL*A!bXyWzzuwfll<NK;_FSrB z3!0-G#uL%1i9Pb(Z)qR*Po-?au))>%;8~r*A~~>ixWE0hv%HZsb~?@@>v40@A`36` zI6!}_@ho8Qw0GSZ6s@Q3{dj~F#dGRS*?L@9ek)tGpaigZpmvLI%s>X(r~@;RJvND9 zZ)_xuA^~gR-JQ$g_9mtiq+a)bTJ(O3yqjipV&wH~>U73nvirq5e3kOr^JEE7`k0nU z>}xA2o+vS#!69@FYwQ2nO*WExw3;@<_VDe7s6Do^wVE_!8ih4hl-N$)8o^UU-#qbc zhE*Z-v3&!o6--V9hHeTXqw3_Vqn$T=DJd2+_of%uLh~8-U$`e8Tv{phQyWLC_V>+~ z+Ux%fmsc1!Yo4Io-XjyED4ob`ieh^VCG62U5y;gd6kOS^B-(Z&Wu44_X<^3-0JT6+ z;Ci}!AZ_kMIqjh@qOKT#r$Yob`50#_v2EOww!Mb>^kCX7#XF)T!fcXgCj((8L_L zO4acATvT22%=#WO%W4bLzQ?;gqq05oxw-$ky;kk1A)`*&D5@!EsZ0oUDsVYDkJ(73 z_|Vto)5~PZ`FpHzUAA8PMMWWnT)lRyc`?DU zPcL&=)|xG|aN>$tExGIv$D|?)IE_@_0E_*d1+x2Sy~L|V9r0nV;{~)6w%lX4hbnrI zYbd2Oi_I-)x6FSc=}e( zuX=+u_DE4ZoOyRIvA_Nv_%>62TO~b18Qa(2Y*UV1ryH54#N;=JZYFfThYFe-DdlXS zM7->#0LctfKR9JY!ni!X+%PkkIXhGAh-5cu5}-2rVfTibKDe@YB!`pKv)FQk1zuJc(NB(yqz|TFA~tT03w@nup)u2<87?a4h~5B@^z|bo zy+t9tIWf*I7EmofNE#xKEn+5}Nl^qTC2vD@+_SA(X1BZ<2{dHZ7=mYp=bTCxq%|fV zwve?x-ksNIW$EY^r3{;(gq&I&QN53u*hk@>o3d;b z<|GwYNe@QB<-b$TLdgw?NbT$;q*IiIIo8SQ?JedgAgXhYV4j!KdT@VjL_c*Br7xA1 z>o*=LT4A1w?*XaW;FXZtozPIC6hNaR0*rAJAKzAlVRQ|RMuXe$z8Hcqu42lb zVj4lc!B=cnux>a$NRaIzGBmnB9ylS4bKSJr+4 zb1m&e_R25l3pal{=XG?5;_qK#nCmb5G)hjkZ%n>@%c^^Uq09aIR5Vak8$gRHa(RBW zuO>Lpy^P(by;ne!A5fzHO<<&3v2eb?_>-2*e~ejR`CZ<)b?_7ORJ7^NpH(B@fF%Uu zG7Zk}Je6O6Gv1bQ$I3q<03h-<61`TX=#O!q(w%->r zHjNc2UvA2(5>O#F_CE~$7FuZDlXwK%8*ECTgUhk9v1H^Mx>6))qgZXDL1DBT0w?}( z)Ro0FiG?(82_g9rE6k#s6%mEHVu6SnGL3#u^yi+fhxgh0Vlms=0Yfi!Z`9^+1R`GI zTQqUUn%$DlT9Sab>iP)$uIgncLv901)btA;11u%VOdYXZc=xetbsBiDvcEh_{3X$D z`(PxGZOUdg?F!Np8J}u0Q00rspJJ7CZStOx?Yu|tLy-UzPO#jbo+&p$F662Z==)H} z*8}5(zx{#MIkxPdm?%*2x=B@&St$q8@*d%w&0!w-aMP{D$A^?i;l3paHl6eBq@s!p z11+{SCUvp<#o?mSVdJHP)yoY4)DMqrGl=iT85@eg zEv&$wZ-7~Ir4acXx4iWExL+R)D~t%u?97^VF=lS6pf5I|*uu-h!n!zciaDq(Dp+nV z$}|_VFQ~0)lBSSEvA#OJZxL;i6S<>lxik21FeJlc;O@bjG=<&m+jo7XoQLV@ePQIwBK?Fo0WP5Tm_c;`J9$uHc8g3zTEI7 zGwnGPH5qo@i0|L0ULzEA6cv0ToU{>0dIvy^N-V`<<+LD#*i~NmgTYMN@hfl0TkaQ` zIIs5n{AAOYu*1T7?lGRVkUGgg5ZV&j=0w{QhF-8x{#~Z##yKtrm0R7gy_&T?E7CKKx`qoFnE$et(zx0%&sh`FNI2C*iWa>mxRk zoS+{H3BNFH`6Rq8$XE9Uf38ywa=~KTgv`G7J>El0_r>pM&xLv?Nf^UtVq^NKF+R@h zwhngTR&u`*bEH4oxOroofTciR$Ehod;B=2bmfb#IYwHqSdwYe&RfnfNXN7Z@!DxgR zDf=5-xm=Uk;X97udO$|i>OpS`HpzV9Zub=|PC(Ca@A*{q@JQs;LZr&xLtBDRVE8BC zDg)aF#bQsa9IiI(E_k}>EhmQLUh;a{sAJMw`xSBy&}QOPG%pmqWbO7-4*vd&{<^yM zXZ_1-hvLFgUaq{^UaGjP51NZpPExYA4qWGIQwSaUdk?#CQE~wh3|uxpL(HWhv{uex z=A*kE(L?o5ac8ePY@8f2U%p&R0j{{89fNmjPT9J1r+o;OUY{v^?eUJf0r0w-qoNW2 z3HVH+$2>G7=bbI z!3H(82YHc;TN|6E65@T>(GZz6m7L(K8XMnfl2#J(8Eb`u^(7mp5?dZZ%&|HQ>r{-4 zDzR9T7B;NED~3zRm#%RcfM3d>0r;|O&j^rM_aoeAMCJt>hnL5P*b%i~k-P#9PescQWBVcy^q-Y?l2n`E1sQbVU z*sLOs9s}gBuBUKd)Mh|=kps?3uTed!5~X%-4%I<>N>8@Gq3nXzI<6QuL=+O(z#JGr z^K#h~obs5l0JUhB;_VnhrpvztTm)|Fv+w-+i)%{s_;|Um0878dbmXOV-4-3-hkzdS zKob8+(x)n$g+ai5mjnFjrlzI}n}wA3fjHCWCMG7FfJu_4n)@|1mFn*LjOImaZ#GW; z?r%j$Wd0iRV;DJ^tQJSeD6P*n#NKXS-xmH5KSb1Q#e-Y(JW#zQ@oXuB$*?n1*1Pe$ z6uSHN>}sm#b=Aj_2;Xbod2dOFfKGV;rtW&hhST|_dL>^#f;4i0Nz@y**V4%qPC zv|=I*cgUkdt|eIKjb?-m-A~v9CWvUF^Jo)a`>$RB^0E;}NRVERA>i6^d_t|BcgwfZ zdAPr`b9UbFez;k1HHiB8^IfI+_~m+{P0e1_3}mYG6Iv)4ukqFKny%|wbf??F*qAEl z#7j#{zo4d0>qwR8Yf-#tU#ChtiTY0v&px%fL1}89OnSIXrwt3sHk#rm%U(dY)@Qe; zKvr+QJOX>jWc6EpRB`8k54^4a7$R!+PB{$W>2pw1FEp3rvubJ7a1*WF2DF87*S-!z(Py6`zcylBp z&`KxcL+JOQAfqdLIH6MRb9JPH{7j0smK0*DcRH5XZVBa`t0sw~9eM-4SvgV7AY!#R z4;vB-HDK-EkZI#UG!y~*wK?3knzXe*5`Kib(S#gT6i`jt!WP~#L1KKoL4gIoew$wp zzlggrfW4-|!xPMNK!+L`y#(|{xj1&;M`~8{!+oNN=k+iUZ4k0@Mfv;>hStCT!_eAK zN+MTQQ3+;N9t1pT&(AEMHQG{RQGJ>m2r)sM+oS2;g0Jf2wwpo|aTFt}*_wS(=kSo< zGwct}unB(1G=gwghJD-a<$5h&Au^z zVR+rw&`XM9Ynu!FP!HuQrwf9dSM6xYOdzj{W;%t5m9M4jTy+-={kInpi|$z8?z6SH z?|Bcq`X)9&!|p+Vbx)BnhuT^RS8ftXZF%MnokA=}l7TZR42|!uBTP10iWuDEB?)N~ zrmBjsHh|%2R+_8L&dvgsn{?zJaJ{NCM4bO2)_Cx@X5$@wvS{< z{uGS2bXO&!2L(23UB8OWzBz%~`BF5$-OU@FxnTKb<{UR!rH{M$0QZk-=G0w1L4OmseLPDksO~QiCgoP8+l@>%`VIZbi z%~aLY*K<1W>(1N*QH}lP@Czd&BUTq{MMXtGYzSgjMo^t_^|b=wIF(iH=Q5@hav^HM z&_)QSJLvG@F3cL9lNWna%<{v@MY0PtnTJo*T8ygjgerdT&dZ9gVp)gbEZS%0^G8~|JK4x>Ku2vcBjj)0MAsb1d0Ih_4UP~5cmbe zH_rf*MobJsBN_IJjxKvj*e44ycaEeau90UHpK-{+OcDgmj=C^z_a9W+#xX60w;j9W z%FXn%SE9Bj%s0esiHVIqlb-11slGQGzOq?23%cTc@fcScKrh8zVLg{HQK*B&4!CyO z)z)wz8NwAy74ZZVUHszadjWVN??(BM$VyJC*KZK?s$dN#gMz2~bBTbjI|4@C*4}<~ zwUJ73?3x*JrOshf2Cdr5W5&rxlKO$79*BdRFcjNlLg$TbU1D&ov6)~3&%4doF+90A zcQ?SZ()WR7Ax8Rg?h-h_;A!SD&}U>d`|v$p`Gx) zf@>G0T9I4ObZX|O^I|GdizwoUA{1PMn{MjmSC9G?Am@l3;9)y#>*Gk>89DPo@a2xa zjsE4#J}Zdn*0@%)iksHnZd`m;!KZocbz~5mh~Rxk;r zBOMP)k<_& z|AGX;4iCAzp@KDIyV$xT&sy3L(xM^itn${rfASR^WnA&DJ>BkC3Q&xH@;wlgtowPt z(iJ9E*!6GEr?!sbw>cUV9Y7hl`rV!om$iiLPCyY=!dIryR`<}lt@z?p}o~8a!gLjuYNh}Ue@-Qr(Tof5p_!XRs5LJyNrAd|8$kZp9_a8 zs+qbVacx`SL_4IH;ykO0&k7=I<_zpcDF4?0r-l`DoQ)2UC}9ju?~9mxF$p|W-_{I&>y(yNBv?rc7GmqH}5%zF%dy^|dy19^(uTg!A+$@5_&#<_U$=ifbxX-A6X3>ZDT@RuD{!PAZxr)L8?84t|F8w1L z>=tBZFp(9t)aQHrR1>qi+;g{8qv)U1-xNHg@_$q4%WXZW zhx;#q*5XctH>ylD^=s%T{BC?EK5Hs==2trPkR?v(iZhJA++lbbLqu(WKL~S@>>>aM zH{QyO?UyCtYGQd_L7XXFg8y%$#em5dzdP4D8I@5T9@G&vUtS}sLA82~1?$=^6f`2g z&=U!FwNq)&TB|$q*ZH-?V+2;12_jp&O+F4o>k@Uqd)jhgV;iRz4W7GGzYl_D&xoiDg;pjLr#?dDVNo55c_;*>@$Jsx^wU?MuX4Nijrsr$S$>vNZN z(>%q$L5D9eZ~7b)$}z*Df8>*qflr@vQw<0(ws1z{dSp%I*_)pr>7Fu|p<5#z>7L}M-Pd6Pbsc`xv3Ga`o6T4HoB&tX5j|OH()} zwE%X%w6N!#z2kI2jpIy^+d?rr3Ja6z$OS@bhFl*h;$yUmCN!*k{;AXO_cGOb(6^Mg ze9zg_WIPXXnHpiieD6-j*AGGsh+kRq$PN=V={uwtg7683E8~|)KYNRC>Nb;vYOxdY z*Mi8DnB5|ipC`Q@_cUJh7#8#kZ3({A)J&bpn%Nl-b8Ma=&pr>pUJz5L1IbWs1s9ea z`X92|q{(!G!#Uw~Rc-yZwlhTQLiw>fsYHgoGwZ)}k+Ol9Ad4_hsx~6gEUdcrh-|9* zDW9W`{e?eWwOBraQohRCCLYV$*Zl=h%@Q}Du1y=uFvfTJ96G&Hx}#a+7Q#>7m8Ho~ za_{li!|Nn)cs=EBxLT%=beO;VLR-LAM8(DFdCP0l=>>A}e{5rRN77ko|J$jgSQ$mp z?p+WkJiio4cE+g~qD(T*?^F+dqe4YOb+{3Q-D}{P7!p6N?_(yIJ?31^OqlVrXupHC zR_DEMj|!5%u&D$V7PL;W9lbIByRn2D7?W7;VtIYPVBijAxD~=;A28$;5Y<{=3i`!F zh$-&qlQ{23@AS5L2!0izNa6OV@S#5X3%VBCxLHxMRU3QX^w91CRT+tWlQ$5-|FXjU4>u;JV>#b&ZmY0_|=NpmB z>82v@{UVgVRGMcaMao9ok&EnD^OFx^dE$WCIHq;z(KO zB=R8;B^v>CH3SH@VSAO6W#H@g2M_n*vHEVkDd|A9_62`#I>{AnV9BtleukOkdE`l% z1!B#Ye!`-fWeiBu!@WCD@OPDc>5t>FA_efV(gcVNm8UBxpG*p&5fV+^GkMG+7>9oQ zMH3b#mpNN%ffyGX8<>+r4*}YDTp*(6ggna5Sy>1F>vCgP=q@fKTn%_wOa(U>BBn^* zqG<8bVPOiHvn3XYQx#?^bn*!u%bmgToF+Jv2HwIDAPmZqiDv8+8Tu^|T(gy(_&p?~ z!w9s9*9R~uZ-5(s%-Q8)&NpDTE%)L?mu)K9L|FEJ9zjC~oWmllE@1NRNaa3SrTyLVlFBROKx# z_w@9ned&+!I31BB61MINAt}%-{R%#Wonw>pMHK2(zcCpk_qy5RC1la=C^2kj-9!5E zUFwR!krwOf+5R=2{N{} zB~}l}|H z8{N2pBO^i2cc*7vRziVmTsEMuT!HxeT?)mJ#Ro3h(x5@T7Eudl!`e1L!?y7e&}(g< zOn(t{2mC1qzXhz^zxB>Gbc!hhK%Nu~N~A!Q)l{&0@D4oc;bInst0;2yNWY+!?Nt)_8s8KT&W)EO?;u za3^XY#5TC_Q%ik!{6bQaNqK$uCuD!B)GtNY{rK);K_y?EMZjURg{ioqZ5^K2zj~+d zzQ+x+P8VSJp?Xmfk^7`?qi}d=M8wnor;aMmF8I3?3GCz>6IH;-%u-7H@Y0JXc_=9S zB^y+iE9frO{2m?zwFo-+Oo18K zZH%v9;A%9rK%$Sx>z}bP9j?4Ax+gNUd!!C_4Hjko6yD1dd7SdDci`x7_RN{GdpnQ3 z&`#Ul^8D1^kBeJHjnsi!U=HqWet!S(aCP|&nr#vn>G4XacPJ>t!O4I(qrSOWKv3}3 z^z`(`cyde2eSd#{USgp23fo348(J=&8$12V?t49*vik6fN7uk~;Oc_u=E0Eza+=|q z=hBDQ8I}4fE-o$taVu%hkS%1Xy~QHXwC!A`RdalobF(7-Ht~SKO&pza*Iwzp4$QS9 zubakXf%EEYTPh1i+~aZw7#B=4^^O@NbNotp-ozW}KiS(i{6eOwwzjWus)TRv-jK2M z!qBDJu@-d2X2sHqijU!L(Eeq9s<#;zhBw7dA))Va`>DFSyEia0CL^SiB<;JR358;S$EsijuR(#Yx26LtW$!^ z0|r&fdwUbGl^E#K-%lE@0fWGjz5FOL4h9=;FrEwiWsV9f@#wfF zm3Ix7+hz`qdfeo~@8u~1j&K?KGK2=a2HPTt$2U4LaTnII&X=bTgjjcm1Rn@rXuP<= zq5M8&hT) z@e0;qguRZ4ajWN*UR&A}^QJxGL)0qm!{1&U6^ZE%ye8|cUh;`M0S*=K?5|(0xYx0{ z^K;xOi|==@Sfrs8&)?-bMzyjs(@Y+wY-(&AiqtXMDJHgAY=jQ>1Sw)Gl_t{;ViT?h zvZmuiY8$2y?OQTM5PyUiJxD9@y_ zbp6~Ja8zDeB!5qLX?@4R53HvgOAk^}QNgvOW54)F-{fRyYHI4U%FRqxRyh^loBI+R z0cuSueHfs+wC1l$Sue=%kLqx+x8z`;>1G}tNN%%NZ#Xj_I&>%?C@9r1CHR2RC*No( zW(HyDHXE0KSmiUK{Z1+yo;+qQ8yvKN^F~u8qcVM2*)_`db#>VjZZ0lEEvJ?FVRiEA zh~W9PIjY+sA$+p3F&{ph@%Hw9kobVNQmnS2Vb}is;ZL6IfJFf(S442I-uAb{&e0MK zKgG)$$-)?jiN>ul@%w{R>UAI3ByHPX_Qtw}1lOBnY_Wyq9B8%2xGV1c|0 zfgqiZt{saA9yTNDrv#ElP>}#H`UFh%JWYtoqT^4}iul*7)+V3NUnC!lq<#n~`-CKY z1EzUu5kPL+dSQRYh+EQ~d#-MZMYHm=UUZJO%AStA!aC>uF#D{#PiwX2(YnaK&MQ;G z-)5!V7s{Vnie>SkWqrte`#?0JqR;Z&$4t{BWKWDL!to_DEzh zr&-o;r}`?YPR5%-QHQQ>t!=*E(Ojk@bA$-$0vb&s8%&NLr^Z-v?yQu}moo^fyvu1z zVJ{AQ_TSuksr6+f@g@aJuiM1TP|thTkMHz0*^kitDuMcYkRXFu)cQ<~$8xXq^Cqt^ zx4qIka`jEMx|3u`mD9%|Z#&5Z_pci;>}R9T1M3t5DPO(L#*{*f!7x8%g*WRj8&Kq1 zV}^Ws;-HH1@V9U8Hgxb)jJG5+5U!(=YqX_?`)Td5cU4s#58Zlw zqKM#0j>px>S%Iea5)#4^5)$O{gM+u|L>3GiRCL!|3y~jR;ZiDL&RzTdt@hpfc76)) z?FHJb4i!DFxoE6R^q-{i;ou$Ymf^xA4j-aSyfW)}~q5BwGB7xebMZ$2^i(Kbiu#=JfaX=S~>= zF7m_f$$Pd{XJKh+-E@Btk5kJjnID}t8BA|Imh3SGRDZd$%yS+Uj@lAD_347!DE&^)YCARB8;cjJPj^f*q%{5 zw;dh^XZ3=1@n5;ftkVf&qmWnn!fcxuckwn=wlI2@QXcso@N&}*J|^GobS?H0YShP6 z&&EWNFrGQ_YpgA-tn47cxxqK!goVW}BO{|Y1&;{e&(s55nmFDI1SwrWEZVVLwF~|#32_%_&sEEbwyAEJSy(4xA3NLn9r(LK> z!_t;?AS~z`-e8yzc+sc7?d+8+xAYW?7QNn%D!%CX@{viE$|}m#L_tw8e|fTy0Oe&p ze(n-D9X|*%%EHR(HO!!}-WydDR%Annk^^PQr*uPkl)}}YlGJ|u_|XseoEJ6L+yvPf z=3F0(z1);mrjMKm+RLk1az2gmhTjc+rF9uQf_v!`X8n~D0^=mHnpvZ(#3UIThgAP_ zw(|CPeN)p#(0XtT8WF0Q9qI{?+dzbT@it!%pW!~D4y zUuc~)O@HWLGwjIErgEJ_P;p@kb>4_RMRjmOr7S z^D0S0!RqYfcvB!WME9ZD{8lUKnA!)>7o*?Cs-o>&~6Q9b_k0Q&zOdoQVxAW#DVcKM|RJd_H$N%cOjrM%;n3 z7RIni$ZC!W4?hIIkHv4#!kRjwVq)H-e0HDAAJNUT=R&I@A@4Rhh~O{c)P+y&C~(<} zHn^YgP=jj=J%{wGcL_QojU5|`U_-^^Ci-oWj(&-Coz0`-8_8XywIbTZe6-r*uNsoC zJ3BfGe)^J;Sy>aK`e_5vP@7lN+ZWG%*~tAG!JTOT8MqZxF#k=?b4bS1d1X}m$aSKV zLd#3+H2wS@A*RR_`5d=Gs zD~^r?xI|>@L*a7T@D-d`L`11U?res!n;m&&WkqDv8we5Dzo-k|047w6);(?B^;)7f z<5>fDWrFLC=>8A0%6|xT*jNrQeZWtQrw!2KMj6@}GNG^}KSX2|W(%=Xl$3mc z=O0UW_+BJvFd{YCG$0_oI0oNXzT_3ed{}EhaGm z9_vsNgR*X`>|~5pe1BFAw>eTh@fYX=#UZ^wFsw%F5+h~9hB&?V=cBF0-r@L=8@PMN zfx9x|SNaa31s-4g0iVt&<2_)t=@%H*y^OV^Tyxvo;La{SoA)*wSdx3moF54Ud{U2Z zs@S5u`}7V1PSdR6;pWx=NgF;eGc$8iRh3sk0gf-|h=qyTkzZPR?$dQ~`r5|KHBGuk zgVzR2cRs}lB|d*_TdA-zmD~m_~4=J=H_+!u`I>S5t1 z>-lRB7ruISv3X4>y1uO1TnX~|-Me=ac0}k&eLv3RUHVut*VWcWl&a3mS7EV8dD`#o z50^{691WgZLhXVQrh-oO9-7MAsd6q?o6YqksV&2pF`C~WMPzklWo2$NylEcp)BpL7 z<@A#ervwyO%B4&5P`hQm28LFJCumEFtpS!ECJY}0LH!R?S2A_V^o+X z0dC^OdSl`{{-M-w&1|0ZaAsy!@$n^3FR!g*QdVwTpzRrHVA%)DORX6?_bW`oK3my} zDWDxWp>sG&;A!QNKBJ=2DNSG0=ECC}6f9~tBbXL5yX70bzPPEoz~yz?)`wyY+a-gy z8o8H$xz#tWS%v4@l4GModVVUlnm`14iseN_M2PYRphW3W$JM=VJg+62db-rU#O~d( z!yhK34@C@BiM>w7FLkUs29D)zi_6RB3SI5cK_UXXA`f;O9!QnVq`7nGEKcKNnYjWU8~=GqXMu@JyA`+(?3-NTo{RE^d^9 z2$t-?f^OZqC29RB0E&>aus#VXv4=qviW*S+Q`30GXRti&3n!%IkKsimRa8+yAbD!J ztnOsyf*n*eYh&iS{S}QQlVk7GJ!`O9w@ut4cp~4Fy&-!44a7BlDFaOY+MLG!R^?|;}5v`=72s`IuD}`{SA+`oBNbwTI%c;x5+8cK28v`;y`1W zo+eYe@O2~6is}EP>HGe@4q0~bYl!Ihw%dfYqL#`V+2CK4Gf%Vy_S9bcgX5acj-0uN zG(NNriGO%xcs6j zK}D>;KnAY5o?#cYP}m@jo^*FV;7~lv=IH21Lf)T-B19aXo?d~t1g~XzE0-L-gcXb4FX~s1 z=nL1b7dt;)UHvd*J-xy4>I0381}_R@75J|nyg9k@)Tk^pc}$pBvx%#B6M%7y8nhK( ze=??fN%^xn8`r^N|A{W@X>}NnYIuD_-4}Pfb?Hi5-j!x`TDx@}@5Pp_-AgwK`|xCI z$jKc})*3!vX4m<~bh0adJq-<#sT_;7EbfGic>vVZZTnlZll;E33W ztgaaM1qQJZc5RMBg@QBdryuIum&3etZpG}6>8_Spy8VYqC5e9W3gxTl^|$m*8%KsU z^W+T{GZ$ZY1lB*O>?CPZU#4_-G%R#YI-jatgv#4;j=P`<4>Q`d>{?Z(8SnTC%Yn!U ztrQ1_J>5vk!MIM5Oxy&4|NN1<&B2gX52$#D5^pHuED9trm&taj1xZ&UVN@t99I z;CP*alKM=srwg#7OFc!hEy<@vozw}ILF)1r(%NFP-#4TO*%52F)uFkKuv-Q3^vrRf z+TFv0KqQsWqsQ*Fr;IXl8p|m>3>DUK7fdqMi0AE#T5{ zL(Iry-t_GK^fz94FGsn07J3*vjc}mrHS(D~KUpv)LPU_uaACIab9I$Q8#pwYoUj`a zv)6C!vDajN5US12iypEqC@%306bPXt!Wzl8yC4#Y{|68a3NgD*$#}ot9z-id=#7B! zdZ5Rjo}INs42oJDJqE8&C&z2lid!sURXENANG0?_wQG>>KLEG_a|P3 z^~b+%DjsnRezhVj=k07SsH>;%(WdI%M zU%-XDhh<+CV!vO{pC0=SFe-|WOJqDRpR#Z!^!XBgpV7P0Op8s4Wo8}C#`P7V( zI($}hINyod#zuYt0UDl!jRKD-X&44WL`(YeN8&5R=m{JVp^5?!iOEz?F`@fHPgyqi zXl2D4+RXalN;uvg5JW#iRPiBf`@WJLN-cp!5%Miyq@R%A!!L{)~qSKNKtgghtH<9>sHnF!Ph_1OSmCX_e0uj0OZRIX)sLydTeSm3kpw2~x zKCZ3Jh9|fWC?+Z3=1rz;+uoP>EVCatv&k1Bt1hBj{#qQi1y(~gS|1o3mqER@fq`Kd zRavgv9!+%!)~rC|6#-kywVNGl8-YioMMt(Jg4oO)+2Q`ZdsjZL-wXpU?2_=<*m``) za~8dL6}8X8lvIJU5v2-Bb%MD7=+OD{Ha<6Ta9oaF9VpC(jT;BbX?O-}!}%1Z%P3zN zm&Om&gh`35@AecSt_;w+9@*u8)a{ z8R7I>Tb-Zjuu9kubPQhF8mKg)86^B^%})Dbm^oUqW4Qze`*Sgu;R$TjGe-Ld z#4DFO3ATw9t*w!uWFEQ@Ns9M6{tXHwa(pR%4#4uvP?RwIrG5H3}1ZEZa}AKdoi zQ28WA4{i{qdbxgAGx6pMT5;qE; zXSHw40!_sLHes`zL!=sQ&*Ebd&`3m6mTQh)hX9@kMZ$kCJ$t+um6b~)yx;H&vu(t8 zgqeJ&szTB)G^445Kq&^rkuMq$58&KQpvgC*Fg!?)4xul>A;ABNn^& z;!qUep&9URA75LY#v8sED`JbC0GN6|C}zm0*~Kkr(fJVI{Q49ey!pw?~7L*yk zK&4<(Mq(2&sD(k_5imKjt*&8;bM5>IEoNC;1Sv34jItQ)btTJb1&V{Js&w#iSAQ%Q z2{|j_ib`0QBg9&_rE`&>a~-bca2TBKuY?OM+gS1W02tD0LhEfe!d!*`cAv->@P>cb z;WZAgIEbh{e|Fbu{%I5z7ADik^L6zgE;m?j3DOZuV$? zeEJwhrO%+LlZ4{LKOQza}KK$wZYmDGJHm+`M}+OtC7dP-NS|i*=3mO-|IQH zZ*H<%O;?v4MXr+7bPv{09a8w8^kblZ2?u_npGAr#2VMj2^oIWlVni=+^hNt(V4d5OqM?0=iY4W%TWyutVQtBcJBFI(}Ne~DZlFhzsWkJFB zMo%Bm-Q~jw!`~~zs7$nEf!CpQRobn`1KhOW9r*4d^h5em0bIzWnIIvL#yv#h#mD_X zeDxv;Kd^y(3HVm7{YRr=lo>jROy=ie_RTQ zXYAom;nd!dEhUClQtpaN)`V8gWmcOCi0huKtPd z5)m0$MTm+}-q`)?_Bu)SJw-79-&wYAR|OZ=4&C?!IS+dA9#d3BSI55GB>+akoE`z# zDr)^S-q2rw4TLYe2_~unXflXnC9qow;2L?$0>NaCmRLRSMmv&NR6rNU$T)DrTyzI0 zb~rFBAG=F;pe=L*J0w z2IbtRr$=ZWiW*PeizXJ$czNSTiU*Na@g9K)RML?t@(f$P}Rs9~%KhNZxwHgUyG+qQ6L!6Ri-Y$SW*Q`ZP4y7VGk=Vw7c zz;DMe`nf!ZaqEfnTAk~~BqUhi0L#RN_P-QE=n^zhqhd0)Gm-Bft7Ub zarX4|WZt~_j0HZLDdm!o9Yp+PST+#4(Y0Wj^ls26V34a|K5qH=09bbp8yF;@aG-5b{qum zf}$bUoY;{)errAig^V)e$Lwqpm}($m@?uVZe%|u$^LH%!fp?ptjX}e+P_(?MatGcb zffu8-B;CWL9m)c**-3tDzt@(yQ&Li}g;4updgowDHBn|*9+{s~HCT7R=8G`mdd);$ zuLg$QpaL95>yVLxhpa+;r+b%ar zyN>GuRJ2vIwYA-L6tE<(X0DLkI^>TNu9IDQ7G&h17w1#iNN8+8eEdCS^>>v6tKSBX zX4Ebx@p!_C<(&9e%A50L5TqQ?F?3-iZqlvGkp`rQ8Pc74yuvn8NL2+IR(E{c6ys7Q zTvGvLE~-qYv9vrnX?NmXiEAP1f^;RCvg%N-dNtAFquA)!sv>B*-*C-s-^8eX;OjD3O@KC%|7~qnh zX^Wu@HG+DZ_Ilu`8ipgciMSqYpkjYWM0tGa+LW8`>H9*b+@CB`e%|nM;9F*5gg8AV zWHcQajXA>+j}uP0`by_hqBl<&VP5+=_y6sZ$`($rjC zmgyr+9(lLtXHPntj)H;W6*tG4?p7|wt&HRXyTTCpgW=8gQn!sbC?y>0_to+z@2Ht` zAD)rmaZVfQaQ(g*v%f&sVEW3BJq-c2oB5_6ZTb zNfy>sd4FoBtU%0zxCbru+sL1sTBfhg?)o5cj}%5)r$9+NO}pa*-#yalnf)ImKDd@h z3>;V6J6s-O6rdrnQ`5W_Z)ZHXgE3BC=DJNAj*y;DRl{!^TE#o2MMQ}IgI>a^p$V0L47 z)#@*|&rVBTmjQ2FDW`&dgq)fCW|NIqM=76^FJ?Bn-oGCHQ6F!-V9_G*&gvayrl=)P z>D2P$_#7|sS5;!)Y1-VbJcvATEyq9ZI%oFAhC%+q9kFrVGsCfw3CHk?7r%J_yz5Q( ziVE**P=7{HjBZaRTYD}nqdhw8*?a7Jayf#d#zrSRz9@*#h}+5BlStSq60;aH>=mCg zxXt6(m({M-7p!MOE>PsW;5rlg!ha91H{TT($Kt+`#@c!lho44syQWO%-*=Cn?5XS- zEat423yckk-DfSRe!0oO&Drr;*`7?p~!r&fLHC z(2LW11YVzJj$%4t;K}K@&5^V5z-2FA;W2hBwT>yn7`;uYi_Su|u2=U+<2~#e*(EP` zM?A`*Q9xE8UrT9>Zy(Rub-L8T)iG;xwfralvkc7N3-;Ts z^Q|og6T;UIS;lMJ{&_k>f74!j5&O5g23K<4ejWZAm|DfUH6&yrzsf9&%UHMb++bXC zQxqP5e2&xkwzq|ES%!ElcL!d}3I26=ml9vR;(M?5`$a)~qbKTqt!`AS>Jqls6~1^Y z&`$RJUuWOOKf14a!RW%R-j?pF$0ivjWx_kf#hQHH3ERy$%~sTO)9}dfke_7~W=!Pg z#mW#b@Y9EiPe=RW?5K`YV22N0aJEajg4d~UZPZ^7GW2<*dg#~8>-3P2Dk@PKQEV|R z)i;Xqi(QpNzogxC+{h&o^ImT~`-Z=2ONs!?^!@H(YG5VMG_p)Q4sG9uX^DIJlwq}U z6{q+6%AcoxAZxa5v=Mpp8C^hQcBjvkev)8&mw&z24X` zTQP!o<6hwTb);V>@mEW4Wod0eYzPNl{xMbBjT!p=v#QN%0=7*tvFBfD0NOVEo;tEZ2rnxvD!E_GDn>^Ff>e6)|B|z zJA0I6m^02Cg}PKmfB8ekPl>Ac)jUr_w^`TB%Rizu+mLek(dRfVeV0?mjRIpHJYF)7 zjE}WfiWf=~vM9fv5R{;oeFQt0w zBpIDi{7%*S%=d~9e&ef(f6kr^nC+7|1~Qq z4rAbjS~DeswVmYqxs-iV{mnj&lLP6;-3&7gKTqB-j484f_v2cq*Pi&2bbKqd@lI*A9JLC7)z214Sr?s)^fce1DJ6ydjA=2ML@kX!CvO zmk7Kb6;O0$W~Ny}Ubgo -Mt-)I&+0S%e!%B?n&<2~No>D;=T#ztKwt#V!q+Ron( zk8+gjm(LHnkwWucritred3|dL#a$Y)#L>wyodFkD5gwI*aIEX)%28E|}@p z1UU*swA`LgE!ywW`a&9wublcUbv3o)u2}C^fuyYw5%&+} zj*p0_K!fz>I7O*81IjWSd`QR%fb;a#?ajEVRZAjv^TV2bRvukWMtX~M`(EZg(btkG zIN>57C$?m4oZ!%Cb>b7lm))-{?J6!v@3QZ+XnG$~Qr%mbAeNBHTfBr?=$Pt46ZddM zpwWG=w;2b7U=6c~&-Q0zJT7n+ZTRBEy=`aj@F^{*v)?~9Kv9TT7Wpdignu6;fb+kVxXWh%`p>|5C-+~&xm zM{OV8Tt0`gXmHD5=3@Ir;$98E%Hdv79L>bd1bPN(R+dsrt=e=xM6$iS)P@>m+IXNY zZ}`6cJsXDf@88e%%hMBWU@H1#NH_Bc8AfZWPQq(ZB_(dCF#&(ywq@M7kq>h)(8++C z1K4I}9po8`3r^a&^2|#WhMC9IMDxfecmxe+iTm;|pPQTM7;av$ov{j73Sb`Pc>je( zzj^fI_o@EV#?TQFUH#`KKc!ahmyRbY<=;N*=Sm!Up0zM-HeA(Y{-eKY*Tvlt(J{2q z1CLlWqFQ#p*))4&EHyv_7jH_{-0hEX8h(i9rR_S~b6KILfh`QdB?1K({Vylt{CC-m zUZC&U-`u~ubVrWczjEe7R(f?u|D=DLzCKxrmw`Ki{=(6n(d18SsV&G-_SGGr9t3Mof$bcl zhAbUz{&<|(?t9sPg}*~*MD!ttx{ne)(*L4kjR)R1Q$z(;=>39K2^WGBp;qtrA;(x% zp2zXI*4EY}yr`CPV3@(RNvdjUd_ZJ~vv;em4FAdt_}2-(32)v=;v}k*wzjWYG=vQe z@PZ4ps}gO_d*ip{zkkySPmeTZ2Ok77?^Z~Nb8g@{7_Gg1`xg5CO(d`%BuEm5euo$v zeY?P#kbeHh=)Zn5K9;ZDbm6B?t!#@Co(Yq{^M9S?EX)rq9sDi>Tslq!X-@w8hyHzu zs=qXd5evW~|F5lE(je%+zraWm%sD_W;P*q{?$v$%FH?iO==%G>5eV?|u5)m30CQ0e zd1?qJ&Q1BRQqI9>LhuGvIaIsHK;n5|#G-!bS!4mm;pnLh_D3Q@3Nj>k*1X6 z*P|ESTr%ZQk>0&K5OhmRu5sJw%5L>&|0*+6u=lxk1=`8Q(-j*rj>fA7T-kQnCGEDH z(;zJ&xF(L{1H=i04QqHi*>}|^V;8+(7J&ILm=+T&fxgBZlTX!@j0z^;XSQK7B9tMm z0cnD#dTq6LZ5uxEH3dXQ4STZv05>0p_so9hVRdteoT>qF+k&Lx(9_e)HBT@7?fDF& zk=3_Z_ui=r*~$T_pah|xBqv}x4&?7P^y`v11K5n1IhdZClLiFH;^otzNf2D-bq@rq zAxXP~qlVbQYd>RRWwnIM3OUpRnyEXOJKYZUTMAQ{=7i5t^?4mbOELyC93*YNG$0rS zW%;gp!Z7Ez*h}rS%XhfszqmGlZlDKwXpX&F4bJ5bqtZgz6&Dw&i_P(NZO!`3cp~Dy zYpZVNP#{!8Lwa{@Wkfl!u&^)-Bd?WWS06W70Z~5=0pDHdfZ0KyMcWmow_4+?O8_>! z{AXeW3~9&Q*oDXNy0O5yXZ1M9H!};GKywHUs-aA~0;FR*;88adtvP4cb(eB3+yzAm9A`UDdfrZf=A3m@=KP8}~R8lL2amYjj*Ib+I zzooyI+R`6SME{#Fw5x$YcN-EQc|S2TfISAW&L@q8=f9E?6G=FsYF!y%xcBw-=)nKZ z;acHitq^Jh#I~&=N-;oHx%SK2M_SDuo z0Ab%iG9#=|HZ*_j?>_{&Nw|4)iP4(tK9Q^HP=#TeS^jXWdxy>ZV`iojvSC8Af@1SS z;z<_6tUy$OZ-4RvrJ($fG~L$Zpsi z9efu<3^QT;ITHgSh|2a-w(a+@ya0_>V5`+WIdp?qU(YIqbTIttF`iWu4Mh}mRG|#X zU4{LD9JbB+Z!R4I3rt{!potX$@G{(awSx27-q^H-;%->|A^3QW{YGH5xE?3x;|IHO zB+D7>JVzH7#pD`Y4CDgVXSiLLMW;Mpl};$^kZ_O9jf{*SN^1>9&paMzn17)H0d+az zTJ_4}Ov>43fp%W_hs1n>fWTJS-{WJm4sBl#*I*u8>qTr_P!J6^(=&*kAc2S{q_EJP zj>@Y40F7k@Avw45TO@fcd@GVA%p4+%EVU|$x(g%Mmz;9kI4vJF!MPbL|Mv06Q|WD?%4P2*YdPvKoJr6vxtuW7hZTq`xt|k2Av| zFwiS>O`C}Rmq_@=L^MuN#MG;m{6!RA5Zv6uOq9IOqX-H58_?Sl0)4E56IZWZ4PV9A zsOs)6uAS}t`_MZfr(&Y&84UX#C+kd0OPjfx6?*HIpi^jtxzN)5hs!zmJgoIVWIAH- z1)5(IeFDG68nxgP8CkV)6iFQc}>)33Z84V1g@? z2`6VFYd)zJ+Y%EKlWWfH`E4hkkWgK;j6-}D(j6b3IqJHs2~o~K950$KL3~rd!(@zC zcX|Xpb%y}#E+|+IVyx;WN8ne`0sEnvxC9+TFYE{I|7ehCX2+!70?6b|l^}vT^-egGMxK3*6dxVQ;<5+nxOs_s%&g*bjO739gv6|2&odd z_d6g7JO7mr-h(qTfpge4Zf@>sA~(F!&8qmgGcpAt9*#2Edh7_q_@F;*j4)a@ZVl znB&;f;pW552@d#LbwuHSka%uTQRq5|n`Uh8huEQ}TB+KN#Lewnl%O|E4w02dUXYuB z2w4(E8-|)0|J(|aik^jmykY8JzN@o3&k#(BMMTIO%|QzsnM%`+p@UkI^HgAy5~;C zHy+s|nG}&pG8C*4HkVHywtRnc|33A;6kUJW@i%*TQ^t!_rs6i(FOIH@W%4J9hSr#m zyB5AR5Xny_*&ZjYD|+!#D9|)`d6m0)L}H%P{}k;(e+P}^bBPcB8Y}2JN0Q!2qUd>R zl(GNRIR)-8{DHOr{@?&7!$-+eam^#dAI!&ik;?p}Ld_E&n3eX9#IMT~V%$;2_`M)B z*KWYIaESN=3fk$Ak}31}kuC|6_H34LeT3I~63MTIl9qIao#Ow~FVUiT;lB!6#55{N P`0wN~jic#@O|Jhp`sI_f diff --git a/_images/sphx_glr_02-fused-softmax_thumb.png b/_images/sphx_glr_02-fused-softmax_thumb.png index 098217a502a038b7700998e1854f85223d01ff8a..939fa30ac264ffad596f838dd914b6d24c0f3828 100644 GIT binary patch literal 20701 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 diff --git a/_sources/getting-started/tutorials/01-vector-add.rst.txt b/_sources/getting-started/tutorials/01-vector-add.rst.txt index 8d62137a0..e035e3649 100644 --- a/_sources/getting-started/tutorials/01-vector-add.rst.txt +++ b/_sources/getting-started/tutorials/01-vector-add.rst.txt @@ -20,7 +20,7 @@ Vector Addition ================= -In this tutorial, you will write a simple, high-performance vector addition using Triton and learn about: +In this tutorial, you will write a simple vector addition using Triton and learn about: - The basic syntax of the Triton programming language - The best practices for creating PyTorch custom operators using the :code:`triton.kernel` Python API @@ -154,15 +154,22 @@ The only thing that matters when it comes to Triton and Torch is the :code:`trit -.. GENERATED FROM PYTHON SOURCE LINES 126-128 +.. GENERATED FROM PYTHON SOURCE LINES 126-127 + +We can now use the above function to compute the sum of two `torch.tensor` objects: + +.. GENERATED FROM PYTHON SOURCE LINES 129-133 Unit Test -------------------------- -.. GENERATED FROM PYTHON SOURCE LINES 128-137 +Of course, the first thing that we should check is that whether kernel is correct. This is pretty easy to test, as shown below: + +.. GENERATED FROM PYTHON SOURCE LINES 133-143 .. code-block:: default + torch.manual_seed(0) x = torch.rand(98432, device='cuda') y = torch.rand(98432, device='cuda') @@ -189,52 +196,67 @@ Unit Test -.. GENERATED FROM PYTHON SOURCE LINES 138-141 +.. GENERATED FROM PYTHON SOURCE LINES 144-145 + +Seems like we're good to go! + +.. GENERATED FROM PYTHON SOURCE LINES 147-150 Benchmarking -------------------------- -We can now benchmark our custom op for vectors of increasing sizes to get a sense of how it does +We can now benchmark our custom op for vectors of increasing sizes to get a sense of how it does relative to PyTorch. -.. GENERATED FROM PYTHON SOURCE LINES 141-150 +.. GENERATED FROM PYTHON SOURCE LINES 150-178 .. code-block:: default - warmup = 10 - rep = 200 - for N in [2**i for i in range(17, 26, 1)]: - x = torch.rand(N, device='cuda') - y = torch.rand(N, device='cuda') - triton_ms = triton.testing.do_bench(lambda: add(x, y), warmup=warmup, rep=rep) - torch_ms = triton.testing.do_bench(lambda: x + y, warmup=warmup, rep=rep) - # print the performance of triton and torch as well as the achieved bandwidth - print(f'{N} {triton_ms:.3f} {torch_ms:.3f}') + import matplotlib.pyplot as plt + + # There are three tensors of 4N bytes each. So the bandwidth of a given kernel + # is 12N / time_ms * 1e-6 GB/s + gbps = lambda N, ms: 12 * N / ms * 1e-6 + # We want to benchmark small and large vector alike + sizes = [2**i for i in range(12, 25, 1)] + triton_bw = [] + torch_bw = [] + for N in sizes: + x = torch.rand(N, device='cuda', dtype=torch.float32) + y = torch.rand(N, device='cuda', dtype=torch.float32) + # Triton provide a do_bench utility function that can be used to benchmark + # arbitrary workloads. It supports a `warmup` parameter that is used to stabilize + # GPU clock speeds as well as a `rep` parameter that controls the number of times + # the benchmark is repeated. Importantly, we set `clear_l2 = True` to make sure + # that the L2 cache does not contain any element of x before each kernel call when + # N is small. + do_bench = lambda fn: gbps(N, triton.testing.do_bench(fn, warmup=10, rep=100, clear_l2=True)) + triton_bw += [do_bench(lambda: add(x, y))] + torch_bw += [do_bench(lambda: x + y)] + # We plot the results as a semi-log + plt.semilogx(sizes, triton_bw, label='Triton') + plt.semilogx(sizes, torch_bw, label='Torch') + plt.legend() + plt.show() -.. rst-class:: sphx-glr-script-out - Out: - - .. code-block:: none - - 131072 0.022 0.006 - 262144 0.021 0.005 - 524288 0.022 0.017 - 1048576 0.037 0.037 - 2097152 0.074 0.073 - 4194304 0.144 0.143 - 8388608 0.289 0.285 - 16777216 0.566 0.562 - 33554432 1.131 1.121 +.. image:: /getting-started/tutorials/images/sphx_glr_01-vector-add_001.png + :alt: 01 vector add + :class: sphx-glr-single-img +.. GENERATED FROM PYTHON SOURCE LINES 179-179 + +Seems like our simple element-wise operation operates at peak bandwidth. While this is a fairly low bar for a custom GPU programming language, this is a good start before we move to more advanced operations. + + .. rst-class:: sphx-glr-timing - **Total running time of the script:** ( 0 minutes 3.225 seconds) + **Total running time of the script:** ( 0 minutes 4.784 seconds) .. _sphx_glr_download_getting-started_tutorials_01-vector-add.py: diff --git a/_sources/getting-started/tutorials/02-fused-softmax.rst.txt b/_sources/getting-started/tutorials/02-fused-softmax.rst.txt index 8d5e6e38f..b062ae23e 100644 --- a/_sources/getting-started/tutorials/02-fused-softmax.rst.txt +++ b/_sources/getting-started/tutorials/02-fused-softmax.rst.txt @@ -20,7 +20,7 @@ Fused Softmax ================= -In this tutorial, you will write a fused softmax layer that outperform's PyTorch implementation and learn about: +In this tutorial, you will write a fused softmax operation (that outperforms PyTorch) and learn about: - The benefits of kernel fusion for bandwidth-bound operations. - The syntax and usage of reduction operators in Triton. @@ -67,15 +67,17 @@ Let us consider instead the case of a simple (numerically stabilized) softmax op .. GENERATED FROM PYTHON SOURCE LINES 37-41 When implemented naively in pytorch, computing :code:`y = naive_softmax(x)` for :math:`x \in R^{M \times N}` requires reading :math:`7MN` elements from DRAM and writing back :math:`3MN + 2M` elements. -Instead, we want to write a custom "fused" pytorch operators that only reads X once and does all the necessary computations on-chip. -This would require reading and writing back only :math:`MN` bytes, so we could expect a theoretical speed-up of 5x. -In practice, though, we expect less because our kernel will spend some time computing exponentials and moving data around in shared memory. +This is obviously wasteful; we'd prefer to have a custom "fused" kernel that only reads X once and does all the necessary computations on-chip. +In this case, we would be reading and writing back only :math:`MN` bytes, so we could expect a theoretical speed-up of ~5x (i.e., :math:`(10MN + 2M) / 2MN`). +In practice, though, we would be getting a bit less as our kernel computes exponentials and internally moves data around in shared memory. -.. GENERATED FROM PYTHON SOURCE LINES 43-79 +.. GENERATED FROM PYTHON SOURCE LINES 43-82 Compute Kernel ----------------------------- -Our softmax kernel works as follows: each program loads a row of X and writes back a normalized row of Y. Note that one important limitation of Triton is that each block must have a power-of-two number of elements, which means that we need to guard the memory operations properly if we want to handle any possible input shapes: +---------------- +Our softmax kernel works as follows: each program loads a row of the input X, normalizes it and writes back the result to the output Y. +Note that one important limitation of Triton is that each block must have a power-of-two number of elements, +so we need to internally "pad" tiles and guard the memory operations properly if we want to handle any possible input shapes: .. code-block:: C @@ -94,13 +96,14 @@ Our softmax kernel works as follows: each program loads a row of X and writes ba bool check[BLOCK] = n < N; float x [BLOCK] = check ? *px : -F32_INFINITY; // syntax for reduction in Triton is: - // x[..., OPERATOR, ...] + // x[:, :, OPERATOR, :, :] // ^ // index - // The operators currently supported are {min, max, +} + // where operator is in {min, max, +} + // for 1D vectors, this is just x[OPERATOR]. float z [BLOCK] = x - x[max]; - // The exponential in Triton is fast but approximate - // (i.e., like __expf in CUDA) + // Note that exponentials in Triton are fast + // but approximate (i.e., think __expf in CUDA) float num [BLOCK] = exp(z); float denom = num[+]; // The result of the reduction is now stored in y @@ -110,15 +113,15 @@ Our softmax kernel works as follows: each program loads a row of X and writes ba *?(check)py = y; } -.. GENERATED FROM PYTHON SOURCE LINES 81-86 +.. GENERATED FROM PYTHON SOURCE LINES 84-89 Torch Bindings ----------------------------- -We need to make sure that BLOCK is the smallest power of two -greater than the number of rows N of the input matrix. -Different values of BLOCK will result in different kernels +--------------- +Here our torch bindings is quite similar to that of the vector addition mentioned in the previous tutorial. +We just need to make sure that BLOCK is the smallest power of two greater than the number of columns N of the input matrix. +This means that different values of BLOCK will result in different kernels -.. GENERATED FROM PYTHON SOURCE LINES 86-149 +.. GENERATED FROM PYTHON SOURCE LINES 89-156 .. code-block:: default @@ -144,6 +147,7 @@ Different values of BLOCK will result in different kernels """ + # helper function to get the smaller power-of-two larger than a given number def next_power_of_2(n): n -= 1 n |= n >> 1 @@ -155,16 +159,20 @@ Different values of BLOCK will result in different kernels return n - _kernels = dict() - - + # kernel caching mechanism def make_kernel(N, device): + cache = make_kernel.cache + # Now are kernels are indexed not only by the provided device but also + # by the rounded number of columns in the input matrix BLOCK = next_power_of_2(N) key = (BLOCK, device) - if key not in _kernels: + if key not in cache: defines = {'BLOCK': BLOCK} - _kernels[key] = triton.kernel(_src, device=device, defines=defines) - return _kernels[key] + cache[key] = triton.kernel(_src, device=device, defines=defines) + return cache[key] + + + make_kernel.cache = dict() class _softmax(torch.autograd.Function): @@ -173,11 +181,10 @@ Different values of BLOCK will result in different kernels # constraints of the op assert x.dtype == torch.float32 y = torch.empty_like(x) - # *create launch grid*: - # here we just launch a grid of M programs + # The launch grid is simple: we have one kernel instance per row of the input matrix M, N = y.shape grid = lambda opt: (M, ) - # *launch kernel*: + # Launch kernel kernel = make_kernel(N, y.device) kernel(y.data_ptr(), x.data_ptr(), y.stride(0), x.stride(0), M, N, grid=grid) return y @@ -192,21 +199,29 @@ Different values of BLOCK will result in different kernels -.. GENERATED FROM PYTHON SOURCE LINES 150-152 +.. GENERATED FROM PYTHON SOURCE LINES 157-158 + +We can use the above softmax function to compute the row-wise softmax of a given matrix. + +.. GENERATED FROM PYTHON SOURCE LINES 160-162 Unit Test ---------- -.. GENERATED FROM PYTHON SOURCE LINES 152-160 +.. GENERATED FROM PYTHON SOURCE LINES 164-166 + +We make sure that we test our kernel on a matrix with an irregular number of rows and columns. +This will allow us to verify that our padding mechanism works. + +.. GENERATED FROM PYTHON SOURCE LINES 166-173 .. code-block:: default + torch.manual_seed(0) x = torch.randn(1823, 781, device='cuda') y_tri = softmax(x) y_ref = torch.softmax(x, axis=1) - print(y_tri) - print(y_ref) print(torch.allclose(y_tri, y_ref)) @@ -219,47 +234,23 @@ Unit Test .. code-block:: none - tensor([[2.0935e-03, 6.4551e-04, 9.8605e-05, ..., 3.3981e-04, 2.7386e-03, - 9.1986e-05], - [7.0923e-04, 6.7521e-04, 5.1366e-04, ..., 9.8392e-04, 2.6547e-04, - 6.9062e-04], - [1.4032e-04, 5.8826e-04, 1.1694e-03, ..., 6.6423e-04, 1.8178e-04, - 6.7049e-04], - ..., - [1.1767e-03, 4.2703e-03, 6.0596e-04, ..., 9.5274e-04, 1.1681e-03, - 6.4924e-04], - [1.0772e-04, 7.4854e-04, 3.1912e-03, ..., 2.4980e-04, 1.9012e-03, - 5.2567e-04], - [2.8518e-03, 8.1899e-04, 7.7046e-04, ..., 1.3403e-03, 5.3167e-04, - 4.3268e-04]], device='cuda:0') - tensor([[2.0935e-03, 6.4551e-04, 9.8605e-05, ..., 3.3981e-04, 2.7386e-03, - 9.1986e-05], - [7.0923e-04, 6.7521e-04, 5.1366e-04, ..., 9.8392e-04, 2.6547e-04, - 6.9062e-04], - [1.4032e-04, 5.8826e-04, 1.1694e-03, ..., 6.6423e-04, 1.8178e-04, - 6.7049e-04], - ..., - [1.1767e-03, 4.2703e-03, 6.0596e-04, ..., 9.5274e-04, 1.1681e-03, - 6.4924e-04], - [1.0772e-04, 7.4854e-04, 3.1912e-03, ..., 2.4980e-04, 1.9012e-03, - 5.2567e-04], - [2.8518e-03, 8.1899e-04, 7.7046e-04, ..., 1.3403e-03, 5.3167e-04, - 4.3268e-04]], device='cuda:0') True -.. GENERATED FROM PYTHON SOURCE LINES 161-162 +.. GENERATED FROM PYTHON SOURCE LINES 174-175 -Seems to work! +As expected, the results are identical. -.. GENERATED FROM PYTHON SOURCE LINES 164-166 +.. GENERATED FROM PYTHON SOURCE LINES 177-181 Benchmarking ----------- +------------- +Here we will benchmark our operation as a function of the number of columns in the input matrix -- assuming 4096 rows. +We will then compare its performance against (1) :code:`torch.softmax` and (2) the :code:`naive_softmax` defined above. -.. GENERATED FROM PYTHON SOURCE LINES 166-186 +.. GENERATED FROM PYTHON SOURCE LINES 181-204 .. code-block:: default @@ -267,25 +258,28 @@ Benchmarking import matplotlib.pyplot as plt M = 4096 - Ns = [128 * i for i in range(2, 50)] - tri_ms = [] - ref_ms = [] - def_ms = [] + Ns = [256 * i for i in range(2, 50)] + tri_bw = [] + ref_bw = [] + def_bw = [] for N in Ns: x = torch.randn(M, N, device='cuda', dtype=torch.float32) gbps = lambda ms: x.nelement() * x.element_size() * 1e-9 / (ms * 1e-3) - tri_ms += [gbps(triton.testing.do_bench(lambda: softmax(x)))] - ref_ms += [gbps(triton.testing.do_bench(lambda: torch.softmax(x, axis=1)))] - def_ms += [gbps(triton.testing.do_bench(lambda: naive_softmax(x)))] + do_bench = lambda fn: gbps(triton.testing.do_bench(fn, warmup=10, rep=100, clear_l2=True)) + tri_bw += [do_bench(lambda: softmax(x))] + ref_bw += [do_bench(lambda: torch.softmax(x, axis=1))] + def_bw += [do_bench(lambda: naive_softmax(x))] plt.xlabel('N') plt.ylabel('Bandwidth (GB/s)') - plt.plot(Ns, tri_ms, label='Triton') - plt.plot(Ns, ref_ms, label='Torch') - plt.plot(Ns, def_ms, label='Naive') + plt.plot(Ns, tri_bw, label='Triton') + plt.plot(Ns, ref_bw, label='Torch') + plt.plot(Ns, def_bw, label='Naive') plt.legend() plt.show() + + .. image:: /getting-started/tutorials/images/sphx_glr_02-fused-softmax_001.png :alt: 02 fused softmax :class: sphx-glr-single-img @@ -294,10 +288,19 @@ Benchmarking +.. GENERATED FROM PYTHON SOURCE LINES 205-210 + +In the above plot, we can see that: + + - Triton is 4-5x faster than the naive implementation, which is consistent with our theoretical predictions. + - Triton is significantly faster than :code:`torch.softmax` for very large input matrices. My guess from looking at the source-code of the `PyTorch kernel `_ is that PyTorch only partially fuses the computation of the softmax. + This means that -- when temporary data is too large to fit entirely in the GPU's cache -- it transfers almost twice the amount of data necessary. + Note that our Triton kernel is not only faster than PyTorch's CUDA kernel, it is also **easier to read, understand and maintain**. + .. rst-class:: sphx-glr-timing - **Total running time of the script:** ( 0 minutes 5.758 seconds) + **Total running time of the script:** ( 0 minutes 33.773 seconds) .. _sphx_glr_download_getting-started_tutorials_02-fused-softmax.py: diff --git a/_sources/getting-started/tutorials/sg_execution_times.rst.txt b/_sources/getting-started/tutorials/sg_execution_times.rst.txt index 87cb0edbf..05bde0fa5 100644 --- a/_sources/getting-started/tutorials/sg_execution_times.rst.txt +++ b/_sources/getting-started/tutorials/sg_execution_times.rst.txt @@ -5,10 +5,10 @@ Computation times ================= -**00:08.983** total execution time for **getting-started_tutorials** files: +**00:33.773** total execution time for **getting-started_tutorials** files: +-----------------------------------------------------------------------------------------+-----------+--------+ -| :ref:`sphx_glr_getting-started_tutorials_02-fused-softmax.py` (``02-fused-softmax.py``) | 00:05.758 | 0.0 MB | +| :ref:`sphx_glr_getting-started_tutorials_02-fused-softmax.py` (``02-fused-softmax.py``) | 00:33.773 | 0.0 MB | +-----------------------------------------------------------------------------------------+-----------+--------+ -| :ref:`sphx_glr_getting-started_tutorials_01-vector-add.py` (``01-vector-add.py``) | 00:03.225 | 0.0 MB | +| :ref:`sphx_glr_getting-started_tutorials_01-vector-add.py` (``01-vector-add.py``) | 00:00.000 | 0.0 MB | +-----------------------------------------------------------------------------------------+-----------+--------+ diff --git a/getting-started/tutorials/01-vector-add.html b/getting-started/tutorials/01-vector-add.html index 321d5c186..02d7198ec 100644 --- a/getting-started/tutorials/01-vector-add.html +++ b/getting-started/tutorials/01-vector-add.html @@ -179,7 +179,7 @@ to download the full example code

Vector Addition

-

In this tutorial, you will write a simple, high-performance vector addition using Triton and learn about:

+

In this tutorial, you will write a simple vector addition using Triton and learn about:

  • The basic syntax of the Triton programming language

  • The best practices for creating PyTorch custom operators using the triton.kernel Python API

  • @@ -297,9 +297,11 @@ programming model for more details).

    add = _add.apply
+

We can now use the above function to compute the sum of two torch.tensor objects:

Unit Test

+

Of course, the first thing that we should check is that whether kernel is correct. This is pretty easy to test, as shown below:

torch.manual_seed(0)
 x = torch.rand(98432, device='cuda')
 y = torch.rand(98432, device='cuda')
@@ -316,34 +318,42 @@ tensor([1.3713, 1.3076, 0.4940,  ..., 0.6682, 1.1984, 1.2696], device='cuda:
 The maximum difference between torch and triton is 0.0
 
+

Seems like we’re good to go!

Benchmarking

-

We can now benchmark our custom op for vectors of increasing sizes to get a sense of how it does

-
warmup = 10
-rep = 200
-for N in [2**i for i in range(17, 26, 1)]:
-    x = torch.rand(N, device='cuda')
-    y = torch.rand(N, device='cuda')
-    triton_ms = triton.testing.do_bench(lambda: add(x, y), warmup=warmup, rep=rep)
-    torch_ms = triton.testing.do_bench(lambda: x + y, warmup=warmup, rep=rep)
-    # print the performance of triton and torch as well as the achieved bandwidth
-    print(f'{N} {triton_ms:.3f} {torch_ms:.3f}')
+

We can now benchmark our custom op for vectors of increasing sizes to get a sense of how it does relative to PyTorch.

+
import matplotlib.pyplot as plt
+
+# There are three tensors of 4N bytes each. So the bandwidth of a given kernel
+# is 12N / time_ms * 1e-6 GB/s
+gbps = lambda N, ms: 12 * N / ms * 1e-6
+# We want to benchmark small and large vector alike
+sizes = [2**i for i in range(12, 25, 1)]
+triton_bw = []
+torch_bw = []
+for N in sizes:
+    x = torch.rand(N, device='cuda', dtype=torch.float32)
+    y = torch.rand(N, device='cuda', dtype=torch.float32)
+    # Triton provide a do_bench utility function that can be used to benchmark
+    # arbitrary workloads. It supports a `warmup` parameter that is used to stabilize
+    # GPU clock speeds as well as a `rep` parameter that controls the number of times
+    # the benchmark is repeated. Importantly, we set `clear_l2 = True` to make sure
+    # that the L2 cache does not contain any element of x before each kernel call when
+    # N is small.
+    do_bench = lambda fn: gbps(N, triton.testing.do_bench(fn, warmup=10, rep=100, clear_l2=True))
+    triton_bw += [do_bench(lambda: add(x, y))]
+    torch_bw += [do_bench(lambda: x + y)]
+# We plot the results as a semi-log
+plt.semilogx(sizes, triton_bw, label='Triton')
+plt.semilogx(sizes, torch_bw, label='Torch')
+plt.legend()
+plt.show()
 
-

Out:

-
131072 0.022 0.006
-262144 0.021 0.005
-524288 0.022 0.017
-1048576 0.037 0.037
-2097152 0.074 0.073
-4194304 0.144 0.143
-8388608 0.289 0.285
-16777216 0.566 0.562
-33554432 1.131 1.121
-
-
-

Total running time of the script: ( 0 minutes 3.225 seconds)

+01 vector add +

Seems like our simple element-wise operation operates at peak bandwidth. While this is a fairly low bar for a custom GPU programming language, this is a good start before we move to more advanced operations.

+

Total running time of the script: ( 0 minutes 4.784 seconds)

When implemented naively in pytorch, computing y = naive_softmax(x) for \(x \in R^{M \times N}\) requires reading \(7MN\) elements from DRAM and writing back \(3MN + 2M\) elements. -Instead, we want to write a custom “fused” pytorch operators that only reads X once and does all the necessary computations on-chip. -This would require reading and writing back only \(MN\) bytes, so we could expect a theoretical speed-up of 5x. -In practice, though, we expect less because our kernel will spend some time computing exponentials and moving data around in shared memory.

+This is obviously wasteful; we’d prefer to have a custom “fused” kernel that only reads X once and does all the necessary computations on-chip. +In this case, we would be reading and writing back only \(MN\) bytes, so we could expect a theoretical speed-up of ~5x (i.e., \((10MN + 2M) / 2MN\)). +In practice, though, we would be getting a bit less as our kernel computes exponentials and internally moves data around in shared memory.

Compute Kernel

-

Our softmax kernel works as follows: each program loads a row of X and writes back a normalized row of Y. Note that one important limitation of Triton is that each block must have a power-of-two number of elements, which means that we need to guard the memory operations properly if we want to handle any possible input shapes:

+

Our softmax kernel works as follows: each program loads a row of the input X, normalizes it and writes back the result to the output Y. +Note that one important limitation of Triton is that each block must have a power-of-two number of elements, +so we need to internally “pad” tiles and guard the memory operations properly if we want to handle any possible input shapes:

__global__ void softmax(float* Y, float* X, int stride_xm, int stride_ym, int M, int N){
   // row index
@@ -232,13 +234,14 @@ In practice, though, we expect less because our kernel will spend some time comp
   bool   check[BLOCK] = n < N;
   float  x    [BLOCK] = check ? *px : -F32_INFINITY;
   // syntax for reduction in Triton is:
-  // x[..., OPERATOR, ...]
+  // x[:, :, OPERATOR, :, :]
   //            ^
   //           index
-  // The operators currently supported are {min, max, +}
+  // where operator is in {min, max, +}
+  // for 1D vectors, this is just x[OPERATOR].
   float  z    [BLOCK] = x - x[max];
-  // The exponential in Triton is fast but approximate
-  // (i.e., like __expf in CUDA)
+  // Note that exponentials in Triton are fast
+  // but approximate (i.e., think __expf in CUDA)
   float  num  [BLOCK] = exp(z);
   float  denom         = num[+];
   // The result of the reduction is now stored in y
@@ -253,9 +256,9 @@ In practice, though, we expect less because our kernel will spend some time comp
 

Torch Bindings

-

We need to make sure that BLOCK is the smallest power of two -greater than the number of rows N of the input matrix. -Different values of BLOCK will result in different kernels

+

Here our torch bindings is quite similar to that of the vector addition mentioned in the previous tutorial. +We just need to make sure that BLOCK is the smallest power of two greater than the number of columns N of the input matrix. +This means that different values of BLOCK will result in different kernels

import torch
 import triton
 
@@ -277,6 +280,7 @@ Different values of BLOCK will result in different kernels

""" +# helper function to get the smaller power-of-two larger than a given number def next_power_of_2(n): n -= 1 n |= n >> 1 @@ -288,16 +292,20 @@ Different values of BLOCK will result in different kernels

return n -_kernels = dict() - - +# kernel caching mechanism def make_kernel(N, device): + cache = make_kernel.cache + # Now are kernels are indexed not only by the provided device but also + # by the rounded number of columns in the input matrix BLOCK = next_power_of_2(N) key = (BLOCK, device) - if key not in _kernels: + if key not in cache: defines = {'BLOCK': BLOCK} - _kernels[key] = triton.kernel(_src, device=device, defines=defines) - return _kernels[key] + cache[key] = triton.kernel(_src, device=device, defines=defines) + return cache[key] + + +make_kernel.cache = dict() class _softmax(torch.autograd.Function): @@ -306,11 +314,10 @@ Different values of BLOCK will result in different kernels

# constraints of the op assert x.dtype == torch.float32 y = torch.empty_like(x) - # *create launch grid*: - # here we just launch a grid of M programs + # The launch grid is simple: we have one kernel instance per row of the input matrix M, N = y.shape grid = lambda opt: (M, ) - # *launch kernel*: + # Launch kernel kernel = make_kernel(N, y.device) kernel(y.data_ptr(), x.data_ptr(), y.stride(0), x.stride(0), M, N, grid=grid) return y @@ -319,75 +326,63 @@ Different values of BLOCK will result in different kernels

softmax = _softmax.apply
+

We can use the above softmax function to compute the row-wise softmax of a given matrix.

Unit Test

-
x = torch.randn(1823, 781, device='cuda')
+

We make sure that we test our kernel on a matrix with an irregular number of rows and columns. +This will allow us to verify that our padding mechanism works.

+
torch.manual_seed(0)
+x = torch.randn(1823, 781, device='cuda')
 y_tri = softmax(x)
 y_ref = torch.softmax(x, axis=1)
-print(y_tri)
-print(y_ref)
 print(torch.allclose(y_tri, y_ref))
 

Out:

-
tensor([[2.0935e-03, 6.4551e-04, 9.8605e-05,  ..., 3.3981e-04, 2.7386e-03,
-         9.1986e-05],
-        [7.0923e-04, 6.7521e-04, 5.1366e-04,  ..., 9.8392e-04, 2.6547e-04,
-         6.9062e-04],
-        [1.4032e-04, 5.8826e-04, 1.1694e-03,  ..., 6.6423e-04, 1.8178e-04,
-         6.7049e-04],
-        ...,
-        [1.1767e-03, 4.2703e-03, 6.0596e-04,  ..., 9.5274e-04, 1.1681e-03,
-         6.4924e-04],
-        [1.0772e-04, 7.4854e-04, 3.1912e-03,  ..., 2.4980e-04, 1.9012e-03,
-         5.2567e-04],
-        [2.8518e-03, 8.1899e-04, 7.7046e-04,  ..., 1.3403e-03, 5.3167e-04,
-         4.3268e-04]], device='cuda:0')
-tensor([[2.0935e-03, 6.4551e-04, 9.8605e-05,  ..., 3.3981e-04, 2.7386e-03,
-         9.1986e-05],
-        [7.0923e-04, 6.7521e-04, 5.1366e-04,  ..., 9.8392e-04, 2.6547e-04,
-         6.9062e-04],
-        [1.4032e-04, 5.8826e-04, 1.1694e-03,  ..., 6.6423e-04, 1.8178e-04,
-         6.7049e-04],
-        ...,
-        [1.1767e-03, 4.2703e-03, 6.0596e-04,  ..., 9.5274e-04, 1.1681e-03,
-         6.4924e-04],
-        [1.0772e-04, 7.4854e-04, 3.1912e-03,  ..., 2.4980e-04, 1.9012e-03,
-         5.2567e-04],
-        [2.8518e-03, 8.1899e-04, 7.7046e-04,  ..., 1.3403e-03, 5.3167e-04,
-         4.3268e-04]], device='cuda:0')
-True
+
True
 
-

Seems to work!

+

As expected, the results are identical.

Benchmarking

+

Here we will benchmark our operation as a function of the number of columns in the input matrix – assuming 4096 rows. +We will then compare its performance against (1) torch.softmax and (2) the naive_softmax defined above.

import matplotlib.pyplot as plt
 
 M = 4096
-Ns = [128 * i for i in range(2, 50)]
-tri_ms = []
-ref_ms = []
-def_ms = []
+Ns = [256 * i for i in range(2, 50)]
+tri_bw = []
+ref_bw = []
+def_bw = []
 for N in Ns:
     x = torch.randn(M, N, device='cuda', dtype=torch.float32)
     gbps = lambda ms: x.nelement() * x.element_size() * 1e-9 / (ms * 1e-3)
-    tri_ms += [gbps(triton.testing.do_bench(lambda: softmax(x)))]
-    ref_ms += [gbps(triton.testing.do_bench(lambda: torch.softmax(x, axis=1)))]
-    def_ms += [gbps(triton.testing.do_bench(lambda: naive_softmax(x)))]
+    do_bench = lambda fn: gbps(triton.testing.do_bench(fn, warmup=10, rep=100, clear_l2=True))
+    tri_bw += [do_bench(lambda: softmax(x))]
+    ref_bw += [do_bench(lambda: torch.softmax(x, axis=1))]
+    def_bw += [do_bench(lambda: naive_softmax(x))]
 plt.xlabel('N')
 plt.ylabel('Bandwidth (GB/s)')
-plt.plot(Ns, tri_ms, label='Triton')
-plt.plot(Ns, ref_ms, label='Torch')
-plt.plot(Ns, def_ms, label='Naive')
+plt.plot(Ns, tri_bw, label='Triton')
+plt.plot(Ns, ref_bw, label='Torch')
+plt.plot(Ns, def_bw, label='Naive')
 plt.legend()
 plt.show()
 
02 fused softmax -

Total running time of the script: ( 0 minutes 5.758 seconds)

+

In the above plot, we can see that:

+
+
    +
  • Triton is 4-5x faster than the naive implementation, which is consistent with our theoretical predictions.

  • +
  • Triton is significantly faster than torch.softmax for very large input matrices. My guess from looking at the source-code of the PyTorch kernel is that PyTorch only partially fuses the computation of the softmax. +This means that – when temporary data is too large to fit entirely in the GPU’s cache – it transfers almost twice the amount of data necessary. +Note that our Triton kernel is not only faster than PyTorch’s CUDA kernel, it is also easier to read, understand and maintain.

  • +
+
+

Total running time of the script: ( 0 minutes 33.773 seconds)