From 7c0e231f6f4d02b0d5ee60ef846b13f4b93b91b5 Mon Sep 17 00:00:00 2001 From: Bryce Adelstein Lelbach aka wash Date: Tue, 3 Mar 2026 11:50:48 -0500 Subject: [PATCH 01/18] Tutorials/Accelerated Python/Kernel Authoring: Improve book histogram plot formatting, add title and dataset size display. --- ...41__kernel_authoring__book_histogram.ipynb | 18 +++++++++++++-- ..._authoring__book_histogram__SOLUTION.ipynb | 22 +++++++++++++++---- 2 files changed, 34 insertions(+), 6 deletions(-) diff --git a/tutorials/accelerated-python/notebooks/kernels/41__kernel_authoring__book_histogram.ipynb b/tutorials/accelerated-python/notebooks/kernels/41__kernel_authoring__book_histogram.ipynb index cb229c1e..c46583fb 100644 --- a/tutorials/accelerated-python/notebooks/kernels/41__kernel_authoring__book_histogram.ipynb +++ b/tutorials/accelerated-python/notebooks/kernels/41__kernel_authoring__book_histogram.ipynb @@ -171,7 +171,14 @@ "# Print most frequently occuring characters.\n", "pairs = sorted(((i,c) for i,c in enumerate(histogram) if c), key=lambda x: x[1], reverse=True)[:20]\n", "labels = [('SPACE' if i==32 else chr(i)) if 32<=i<=126 else f'0x{i:02X}' for i,_ in pairs]\n", - "plt.barh(labels[::-1], [c for _,c in pairs][::-1]); plt.xlabel('count'); plt.tight_layout(); plt.show()" + "plt.barh(labels[::-1], [c for _,c in pairs][::-1])\n", + "plt.xlabel('count')\n", + "plt.tight_layout()\n", + "plt.title(\"Top 20 Bins\")\n", + "plt.show()\n", + "\n", + "length = os.path.getsize(\"books__15m.txt\")\n", + "print(f\"Characters in dataset: {length}\")" ] }, { @@ -357,7 +364,14 @@ "# Print most frequently occuring characters.\n", "pairs = sorted(((i,c) for i,c in enumerate(histogram) if c), key=lambda x: x[1], reverse=True)[:20]\n", "labels = [('SPACE' if i==32 else chr(i)) if 32<=i<=126 else f'0x{i:02X}' for i,_ in pairs]\n", - "plt.barh(labels[::-1], [c for _,c in pairs][::-1]); plt.xlabel('count'); plt.tight_layout(); plt.show()" + "plt.barh(labels[::-1], [c for _,c in pairs][::-1])\n", + "plt.xlabel('count')\n", + "plt.tight_layout()\n", + "plt.title(\"Top 20 Bins\")\n", + "plt.show()\n", + "\n", + "length = os.path.getsize(\"books__15m.txt\")\n", + "print(f\"Characters in dataset: {length}\")" ] }, { diff --git a/tutorials/accelerated-python/notebooks/kernels/solutions/41__kernel_authoring__book_histogram__SOLUTION.ipynb b/tutorials/accelerated-python/notebooks/kernels/solutions/41__kernel_authoring__book_histogram__SOLUTION.ipynb index 478f4768..f208bad6 100644 --- a/tutorials/accelerated-python/notebooks/kernels/solutions/41__kernel_authoring__book_histogram__SOLUTION.ipynb +++ b/tutorials/accelerated-python/notebooks/kernels/solutions/41__kernel_authoring__book_histogram__SOLUTION.ipynb @@ -186,7 +186,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "815cb072-b3a0-47aa-af6c-dd66c626a440", "metadata": { "colab": { @@ -215,7 +215,14 @@ "# Print most frequently occuring characters.\n", "pairs = sorted(((i,c) for i,c in enumerate(histogram) if c), key=lambda x: x[1], reverse=True)[:20]\n", "labels = [('SPACE' if i==32 else chr(i)) if 32<=i<=126 else f'0x{i:02X}' for i,_ in pairs]\n", - "plt.barh(labels[::-1], [c for _,c in pairs][::-1]); plt.xlabel('count'); plt.tight_layout(); plt.show()" + "plt.barh(labels[::-1], [c for _,c in pairs][::-1])\n", + "plt.xlabel('count')\n", + "plt.tight_layout()\n", + "plt.title(\"Top 20 Bins\")\n", + "plt.show()\n", + "\n", + "length = os.path.getsize(\"books__15m.txt\")\n", + "print(f\"Characters in dataset: {length}\")" ] }, { @@ -563,7 +570,7 @@ }, { "cell_type": "code", - "execution_count": 11, + "execution_count": null, "id": "73f0c3cd-349b-490f-b6bd-7afbeb442fff", "metadata": { "colab": { @@ -592,7 +599,14 @@ "# Print most frequently occuring characters.\n", "pairs = sorted(((i,c) for i,c in enumerate(histogram) if c), key=lambda x: x[1], reverse=True)[:20]\n", "labels = [('SPACE' if i==32 else chr(i)) if 32<=i<=126 else f'0x{i:02X}' for i,_ in pairs]\n", - "plt.barh(labels[::-1], [c for _,c in pairs][::-1]); plt.xlabel('count'); plt.tight_layout(); plt.show()" + "plt.barh(labels[::-1], [c for _,c in pairs][::-1])\n", + "plt.xlabel('count')\n", + "plt.tight_layout()\n", + "plt.title(\"Top 20 Bins\")\n", + "plt.show()\n", + "\n", + "length = os.path.getsize(\"books__15m.txt\")\n", + "print(f\"Characters in dataset: {length}\")" ] }, { From 7c88734c9f00ef1cb8f51dabd2c91bb8ae1ea55d Mon Sep 17 00:00:00 2001 From: Bryce Adelstein Lelbach aka wash Date: Tue, 3 Mar 2026 12:28:29 -0500 Subject: [PATCH 02/18] Tutorials/Accelerated Python/Kernel Authoring: Add configurable correctness check to copy kernel launch function. --- .../kernels/40__kernel_authoring__copy.ipynb | 682 +- ...40__kernel_authoring__copy__SOLUTION.ipynb | 6083 ++++++++--------- 2 files changed, 3381 insertions(+), 3384 deletions(-) diff --git a/tutorials/accelerated-python/notebooks/kernels/40__kernel_authoring__copy.ipynb b/tutorials/accelerated-python/notebooks/kernels/40__kernel_authoring__copy.ipynb index 1b722bbd..e66d5672 100644 --- a/tutorials/accelerated-python/notebooks/kernels/40__kernel_authoring__copy.ipynb +++ b/tutorials/accelerated-python/notebooks/kernels/40__kernel_authoring__copy.ipynb @@ -1,342 +1,344 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "-JpGaP7-D_5W" - }, - "source": [ - "## Kernel Authoring - Copy\n", - "\n", - "### Table of Contents\n", - "1. [Environment Setup](#1-environment-setup)\n", - "2. [The Baseline Kernel: Blocked Copy](#2-the-baseline-kernel-blocked-copy)\n", - "3. [Profiling the Baseline](#3-profiling-the-baseline)\n", - "4. [Optimization Challenge: Improved Memory Access](#4-optimization-challenge-improved-memory-access)\n", - "5. [Verification & Benchmarking](#5-verification--benchmarking)\n", - "6. [Profiling the Optimized Kernel](#6-profiling-the-optimized-kernel)\n", - "7. [Further Exploration](#7-further-exploration)\n", - "\n", - "---\n", - "\n", - "## 1. Environment Setup\n", - "\n", - "In this exercise, we'll learn how to analyze and reason about the performance of CUDA kernels using the NVIDIA Nsight Compute profiler. We'll look at a few different ways of writing a simple kernel that copies items from one array to another.\n", - "\n", - "First, we need to make sure the Nsight Compute profiler, Nsightful, Numba CUDA, and CuPy are available in our notebook:" - ] + "cells": [ + { + "cell_type": "markdown", + "metadata": { + "id": "-JpGaP7-D_5W" + }, + "source": [ + "## Kernel Authoring - Copy\n", + "\n", + "### Table of Contents\n", + "1. [Environment Setup](#1-environment-setup)\n", + "2. [The Baseline Kernel: Blocked Copy](#2-the-baseline-kernel-blocked-copy)\n", + "3. [Profiling the Baseline](#3-profiling-the-baseline)\n", + "4. [Optimization Challenge: Improved Memory Access](#4-optimization-challenge-improved-memory-access)\n", + "5. [Verification & Benchmarking](#5-verification--benchmarking)\n", + "6. [Profiling the Optimized Kernel](#6-profiling-the-optimized-kernel)\n", + "7. [Further Exploration](#7-further-exploration)\n", + "\n", + "---\n", + "\n", + "## 1. Environment Setup\n", + "\n", + "In this exercise, we'll learn how to analyze and reason about the performance of CUDA kernels using the NVIDIA Nsight Compute profiler. We'll look at a few different ways of writing a simple kernel that copies items from one array to another.\n", + "\n", + "First, we need to make sure the Nsight Compute profiler, Nsightful, Numba CUDA, and CuPy are available in our notebook:" + ] + }, + { + "cell_type": "code", + "metadata": { + "collapsed": true, + "id": "AoHkvSPMC5Fs" + }, + "source": [ + "import os\n", + "\n", + "if os.getenv(\"COLAB_RELEASE_TAG\") and not os.path.exists(\"/accelerated-computing-hub-installed\"): # If running in Google Colab:\n", + " print(\"Downloading NCU package.\")\n", + " !curl -s -L -O https://developer.download.nvidia.com/compute/cuda/repos/debian12/x86_64/nsight-compute-2025.2.1_2025.2.1.3-1_amd64.deb\n", + " print(\"Installing NCU package.\")\n", + " !dpkg -i nsight-compute-2025.2.1_2025.2.1.3-1_amd64.deb > /dev/null\n", + " !update-alternatives --install /opt/bin/ncu ncu /opt/nvidia/nsight-compute/2025.2.1/ncu 20250201 > /dev/null\n", + " print(\"Installing PIP packages.\")\n", + " !pip uninstall \"cuda-python\" --yes > /dev/null\n", + " !pip install \"numba-cuda\" \"cuda-cccl[test-cu12]\" \"nvtx\" \"nsightful[notebook] @ git+https://github.com/brycelelbach/nsightful.git\" > /dev/null 2>&1\n", + " open(\"/accelerated-computing-hub-installed\", \"a\").close()" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "A1SfTQk0EwUl" + }, + "source": [ + "## 2. The Baseline Kernel: Blocked Copy\n", + "\n", + "Now, we'll write our first kernel. Each thread will copy `items_per_thread` items from the `src` array to the `dst` array. We'll set the number of threads per block to a constant, `threads_per_block`. We'll calculate how many blocks to launch based on `items_per_thread` and `threads_per_block`. We use `cuda.grid(1)` to get the unique global 1D index of each thread.\n", + "\n", + "Each thread will copy a contiguous set of items, e.g. the items with indices `[base, base + items_per_thread)`:\n", + "\n", + "![image.png](data:image/png;base64,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)\n", + "\n", + "**NOTE: The next cell won't actually run any code, it will just write its contents to a file. This is necessary because we have to run the code with the Nsight Compute profiler.**" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "I9Tz2hG-_tBj" + }, + "source": [ + "%%writefile copy_blocked.py\n", + "\n", + "from numba import cuda\n", + "import cupy as cp\n", + "import cupyx as cpx\n", + "import sys\n", + "import os\n", + "\n", + "threads_per_block = 256 if len(sys.argv) < 2 else int(sys.argv[1])\n", + "items_per_thread = 64 if len(sys.argv) < 3 else int(sys.argv[2])\n", + "total_items = 2**28 if len(sys.argv) < 4 else int(sys.argv[3])\n", + "blocks = int(total_items / (threads_per_block * items_per_thread))\n", + "\n", + "src = cp.arange(total_items)\n", + "dst = cp.empty_like(src)\n", + "\n", + "@cuda.jit\n", + "def copy_blocked(src, dst, items_per_thread):\n", + " base = cuda.grid(1) * items_per_thread\n", + " for i in range(items_per_thread):\n", + " dst[base + i] = src[base + i]\n", + "\n", + "def launch(check):\n", + " copy_blocked[blocks, threads_per_block](src, dst, items_per_thread)\n", + "\n", + " if (check):\n", + " cp.testing.assert_array_equal(src, dst)\n", + "\n", + "if os.getenv(\"NV_COMPUTE_PROFILER_PERFWORKS_DIR\"): # Running under `ncu`.\n", + " launch(check=False) # `ncu` slows things down; so just launch once when running under it.\n", + "else:\n", + " launch(check=True)\n", + " D = cpx.profiler.benchmark(launch, (False,), n_repeat=15, n_warmup=1).gpu_times[0]\n", + " print(f\"{D.mean():.3g} s ± {(D.std() / D.mean()):.2%} (mean ± relative stdev of {D.size} runs)\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "TuR4yDV4H6IB" + }, + "source": [ + "## 3. Profiling the Baseline\n", + "\n", + "Next, we'll actually run the code by invoking the Nsight Compute `ncu` command line tool. The basic syntax for this tool is `ncu `, which will run ` ` while gathering a profile on how your kernels are performing. We're passing it some flags that describe what data it should collect and where it should save the results.\n", + "\n", + "There is an overhead to running code under the profiler. Your program may execute noticeably slower.\n", + "\n", + "**NOTE: To modify and rerun the above code, you must execute the previous cell to write the file and this one to execute it.**" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "5pyHvJtxVnDB" + }, + "source": [ + "!ncu -f --kernel-name regex:copy_blocked --set full -o copy_blocked python copy_blocked.py\n", + "copy_blocked_csv = !ncu --import copy_blocked.ncu-rep --csv" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "ijEtNGHhpLPu" + }, + "source": [ + "Let's take a look at the profiling report on the kernel. When you run the next cell, a number of tabs will be displayed. The first tab will have a summary of all of the Nsight recommendations and advisories. Subsequent tabs will have more detailed information on a particular area.\n", + "\n", + "**TODO:** Spend a few minutes reviewing the report. What stands out to you? Based on the information in the report, how can the kernel be improved?\n", + "\n", + "**EXTRA CREDIT:** Download the [Nsight Compute GUI](https://developer.nvidia.com/nsight-compute) and open the report in it to see even more information." + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "40w07iG5k6Vl" + }, + "source": [ + "import nsightful\n", + "\n", + "nsightful.display_ncu_csv_in_notebook(copy_blocked_csv)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "mL_9xT44qbMA" + }, + "source": [ + "## 4. Optimization Challenge: Improved Memory Access\n", + "\n", + "**TODO:** Now try to write a better version of our copy kernel.\n", + "\n", + "As a hint, given that this kernel does no compute and just moves data, our memory access patterns are probably important!\n", + "\n", + "Instead of using the `cuda.grid` utility, you may want to use the hierarchical coordinates of our thread to calculate the index:\n", + "\n", + "- `cuda.blockDim.x`: The number of threads per block.\n", + "- `cuda.blockIdx.x`: The global index of the current thread block.\n", + "- `cuda.threadIdx.x`: The local index of the current thread within this block." + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "B5PBpaY2HnE0" + }, + "source": [ + "%%writefile copy_optimized.py\n", + "\n", + "from numba import cuda\n", + "import cupy as cp\n", + "import cupyx as cpx\n", + "import sys\n", + "import os\n", + "\n", + "threads_per_block = 256 if len(sys.argv) < 2 else int(sys.argv[1])\n", + "items_per_thread = 64 if len(sys.argv) < 3 else int(sys.argv[2])\n", + "total_items = 2**28 if len(sys.argv) < 4 else int(sys.argv[3])\n", + "blocks = int(total_items / (threads_per_block * items_per_thread))\n", + "\n", + "src = cp.arange(total_items)\n", + "dst = cp.empty_like(src)\n", + "\n", + "@cuda.jit\n", + "def copy_optimized(src, dst, items_per_thread):\n", + " TODO() # TODO: You need to implement this kernel! DELETE THIS LINE.\n", + "\n", + "def launch(check):\n", + " copy_optimized[blocks, threads_per_block](src, dst, items_per_thread)\n", + "\n", + " if (check):\n", + " cp.testing.assert_array_equal(src, dst)\n", + "\n", + "if os.getenv(\"NV_COMPUTE_PROFILER_PERFWORKS_DIR\"): # Running under `ncu`.\n", + " launch(check=False) # `ncu` slows things down; so just launch once when running under it.\n", + "else:\n", + " launch(check=True)\n", + " D = cpx.profiler.benchmark(launch, (False,), n_repeat=15, n_warmup=1).gpu_times[0]\n", + " print(f\"{D.mean():.3g} s ± {(D.std() / D.mean()):.2%} (mean ± relative stdev of {D.size} runs)\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "qco9XOsTkPEJ" + }, + "source": [ + "## 5. Verification & Benchmarking\n", + "\n", + "Now, let's make sure our code works:" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "thgSpsCQkN2-" + }, + "source": [ + "!python copy_optimized.py" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "kC3Moh2m02-q" + }, + "source": [ + "Before we look at the report, let's compare the runtimes of both versions:" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "kJ7viF-i06qd" + }, + "source": [ + "copy_blocked_duration = !python copy_blocked.py\n", + "copy_optimized_duration = !python copy_optimized.py\n", + "speedup = float(copy_blocked_duration[0].split()[0]) / float(copy_optimized_duration[0].split()[0])\n", + "\n", + "print(f\"copy_blocked: {copy_blocked_duration[0]}\")\n", + "print(f\"copy_optimized: {copy_optimized_duration[0]}\")\n", + "print(f\"copy_optimized speedup over copy_blocked: {speedup:.2f}\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "mfrqUdzozGeU" + }, + "source": [ + "## 6. Profiling the Optimized Kernel\n", + "\n", + "Hopefully you see quite a speedup! Now let's profile the optimized variant:" + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "zO_y6ObXV_wX" + }, + "source": [ + "!ncu -f --kernel-name regex:copy_optimized --set full -o copy_optimized python copy_optimized.py\n", + "copy_optimized_csv = !ncu --import copy_optimized.ncu-rep --csv" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "DjPJRzXTD6uF" + }, + "source": [ + "Now let's look at the report to better understand what's going on." + ] + }, + { + "cell_type": "code", + "metadata": { + "id": "KjE0Vgu_zgs3" + }, + "source": [ + "nsightful.display_ncu_csv_in_notebook(copy_optimized_csv)" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": { + "id": "Xn6IPpuxD_kz" + }, + "source": [ + "## 7. Further Exploration\n", + "\n", + "**EXTRA CREDIT:** Experiment with different problem sizes, threads per block, and items per thread. You can pass them as command line arguments to the Python scripts. If you're feeling really ambitious, do a parameter sweep to study the impact these knobs have on performance." + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "gpuType": "T4", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + } }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true, - "id": "AoHkvSPMC5Fs" - }, - "outputs": [], - "source": [ - "import os\n", - "\n", - "if os.getenv(\"COLAB_RELEASE_TAG\") and not os.path.exists(\"/accelerated-computing-hub-installed\"): # If running in Google Colab:\n", - " print(\"Downloading NCU package.\")\n", - " !curl -s -L -O https://developer.download.nvidia.com/compute/cuda/repos/debian12/x86_64/nsight-compute-2025.2.1_2025.2.1.3-1_amd64.deb\n", - " print(\"Installing NCU package.\")\n", - " !dpkg -i nsight-compute-2025.2.1_2025.2.1.3-1_amd64.deb > /dev/null\n", - " !update-alternatives --install /opt/bin/ncu ncu /opt/nvidia/nsight-compute/2025.2.1/ncu 20250201 > /dev/null\n", - " print(\"Installing PIP packages.\")\n", - " !pip uninstall \"cuda-python\" --yes > /dev/null\n", - " !pip install \"numba-cuda\" \"cuda-cccl[test-cu12]\" \"nvtx\" \"nsightful[notebook] @ git+https://github.com/brycelelbach/nsightful.git\" > /dev/null 2>&1\n", - " open(\"/accelerated-computing-hub-installed\", \"a\").close()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "A1SfTQk0EwUl" - }, - "source": [ - "## 2. The Baseline Kernel: Blocked Copy\n", - "\n", - "Now, we'll write our first kernel. Each thread will copy `items_per_thread` items from the `src` array to the `dst` array. We'll set the number of threads per block to a constant, `threads_per_block`. We'll calculate how many blocks to launch based on `items_per_thread` and `threads_per_block`. We use `cuda.grid(1)` to get the unique global 1D index of each thread.\n", - "\n", - "Each thread will copy a contiguous set of items, e.g. the items with indices `[base, base + items_per_thread)`:\n", - "\n", - "![image.png](data:image/png;base64,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)\n", - "\n", - "**NOTE: The next cell won't actually run any code, it will just write its contents to a file. This is necessary because we have to run the code with the Nsight Compute profiler.**" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "I9Tz2hG-_tBj" - }, - "outputs": [], - "source": [ - "%%writefile copy_blocked.py\n", - "\n", - "from numba import cuda\n", - "import cupy as cp\n", - "import cupyx as cpx\n", - "import sys\n", - "import os\n", - "\n", - "threads_per_block = 256 if len(sys.argv) < 2 else int(sys.argv[1])\n", - "items_per_thread = 64 if len(sys.argv) < 3 else int(sys.argv[2])\n", - "total_items = 2**28 if len(sys.argv) < 4 else int(sys.argv[3])\n", - "blocks = int(total_items / (threads_per_block * items_per_thread))\n", - "\n", - "src = cp.arange(total_items)\n", - "dst = cp.empty_like(src)\n", - "\n", - "@cuda.jit\n", - "def copy_blocked(src, dst, items_per_thread):\n", - " base = cuda.grid(1) * items_per_thread\n", - " for i in range(items_per_thread):\n", - " dst[base + i] = src[base + i]\n", - "\n", - "copy_blocked[blocks, threads_per_block](src, dst, items_per_thread)\n", - "cp.testing.assert_array_equal(src, dst)\n", - "\n", - "def launch():\n", - " copy_blocked[blocks, threads_per_block](src, dst, items_per_thread)\n", - "\n", - "if os.getenv(\"NV_COMPUTE_PROFILER_PERFWORKS_DIR\"): # Running under `ncu`.\n", - " launch() # `ncu` slows things down; so just launch once when running under it.\n", - "else:\n", - " D = cpx.profiler.benchmark(launch, n_repeat=15, n_warmup=1).gpu_times[0]\n", - " print(f\"{D.mean():.3g} s ± {(D.std() / D.mean()):.2%} (mean ± relative stdev of {D.size} runs)\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "TuR4yDV4H6IB" - }, - "source": [ - "## 3. Profiling the Baseline\n", - "\n", - "Next, we'll actually run the code by invoking the Nsight Compute `ncu` command line tool. The basic syntax for this tool is `ncu `, which will run ` ` while gathering a profile on how your kernels are performing. We're passing it some flags that describe what data it should collect and where it should save the results.\n", - "\n", - "There is an overhead to running code under the profiler. Your program may execute noticeably slower.\n", - "\n", - "**NOTE: To modify and rerun the above code, you must execute the previous cell to write the file and this one to execute it.**" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "5pyHvJtxVnDB" - }, - "outputs": [], - "source": [ - "!ncu -f --kernel-name regex:copy_blocked --set full -o copy_blocked python copy_blocked.py\n", - "copy_blocked_csv = !ncu --import copy_blocked.ncu-rep --csv" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "ijEtNGHhpLPu" - }, - "source": [ - "Let's take a look at the profiling report on the kernel. When you run the next cell, a number of tabs will be displayed. The first tab will have a summary of all of the Nsight recommendations and advisories. Subsequent tabs will have more detailed information on a particular area.\n", - "\n", - "**TODO:** Spend a few minutes reviewing the report. What stands out to you? Based on the information in the report, how can the kernel be improved?\n", - "\n", - "**EXTRA CREDIT:** Download the [Nsight Compute GUI](https://developer.nvidia.com/nsight-compute) and open the report in it to see even more information." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "40w07iG5k6Vl" - }, - "outputs": [], - "source": [ - "import nsightful\n", - "\n", - "nsightful.display_ncu_csv_in_notebook(copy_blocked_csv)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "mL_9xT44qbMA" - }, - "source": [ - "## 4. Optimization Challenge: Improved Memory Access\n", - "\n", - "**TODO:** Now try to write a better version of our copy kernel.\n", - "\n", - "As a hint, given that this kernel does no compute and just moves data, our memory access patterns are probably important!\n", - "\n", - "Instead of using the `cuda.grid` utility, you may want to use the hierarchical coordinates of our thread to calculate the index:\n", - "\n", - "- `cuda.blockDim.x`: The number of threads per block.\n", - "- `cuda.blockIdx.x`: The global index of the current thread block.\n", - "- `cuda.threadIdx.x`: The local index of the current thread within this block." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "B5PBpaY2HnE0" - }, - "outputs": [], - "source": [ - "%%writefile copy_optimized.py\n", - "\n", - "from numba import cuda\n", - "import cupy as cp\n", - "import cupyx as cpx\n", - "import sys\n", - "import os\n", - "\n", - "threads_per_block = 256 if len(sys.argv) < 2 else int(sys.argv[1])\n", - "items_per_thread = 64 if len(sys.argv) < 3 else int(sys.argv[2])\n", - "total_items = 2**28 if len(sys.argv) < 4 else int(sys.argv[3])\n", - "blocks = int(total_items / (threads_per_block * items_per_thread))\n", - "\n", - "src = cp.arange(total_items)\n", - "dst = cp.empty_like(src)\n", - "\n", - "@cuda.jit\n", - "def copy_optimized(src, dst, items_per_thread):\n", - " TODO() # TODO: You need to implement this kernel! DELETE THIS LINE.\n", - "\n", - "copy_optimized[blocks, threads_per_block](src, dst, items_per_thread)\n", - "cp.testing.assert_array_equal(src, dst)\n", - "\n", - "def launch():\n", - " copy_optimized[blocks, threads_per_block](src, dst, items_per_thread)\n", - "\n", - "if os.getenv(\"NV_COMPUTE_PROFILER_PERFWORKS_DIR\"): # Running under `ncu`.\n", - " launch() # `ncu` slows things down; so just launch once when running under it.\n", - "else:\n", - " D = cpx.profiler.benchmark(launch, n_repeat=15, n_warmup=1).gpu_times[0]\n", - " print(f\"{D.mean():.3g} s ± {(D.std() / D.mean()):.2%} (mean ± relative stdev of {D.size} runs)\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "qco9XOsTkPEJ" - }, - "source": [ - "## 5. Verification & Benchmarking\n", - "\n", - "Now, let's make sure our code works:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "thgSpsCQkN2-" - }, - "outputs": [], - "source": [ - "!python copy_optimized.py" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "kC3Moh2m02-q" - }, - "source": [ - "Before we look at the report, let's compare the runtimes of both versions:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "kJ7viF-i06qd" - }, - "outputs": [], - "source": [ - "copy_blocked_duration = !python copy_blocked.py\n", - "copy_optimized_duration = !python copy_optimized.py\n", - "speedup = float(copy_blocked_duration[0].split()[0]) / float(copy_optimized_duration[0].split()[0])\n", - "\n", - "print(f\"copy_blocked: {copy_blocked_duration[0]}\")\n", - "print(f\"copy_optimized: {copy_optimized_duration[0]}\")\n", - "print(f\"copy_optimized speedup over copy_blocked: {speedup:.2f}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "mfrqUdzozGeU" - }, - "source": [ - "## 6. Profiling the Optimized Kernel\n", - "\n", - "Hopefully you see quite a speedup! Now let's profile the optimized variant:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "zO_y6ObXV_wX" - }, - "outputs": [], - "source": [ - "!ncu -f --kernel-name regex:copy_optimized --set full -o copy_optimized python copy_optimized.py\n", - "copy_optimized_csv = !ncu --import copy_optimized.ncu-rep --csv" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "DjPJRzXTD6uF" - }, - "source": [ - "Now let's look at the report to better understand what's going on." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "id": "KjE0Vgu_zgs3" - }, - "outputs": [], - "source": [ - "nsightful.display_ncu_csv_in_notebook(copy_optimized_csv)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "Xn6IPpuxD_kz" - }, - "source": [ - "## 7. Further Exploration\n", - "\n", - "**EXTRA CREDIT:** Experiment with different problem sizes, threads per block, and items per thread. You can pass them as command line arguments to the Python scripts. If you're feeling really ambitious, do a parameter sweep to study the impact these knobs have on performance." - ] - } - ], - "metadata": { - "accelerator": "GPU", - "colab": { - "gpuType": "T4", - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/tutorials/accelerated-python/notebooks/kernels/solutions/40__kernel_authoring__copy__SOLUTION.ipynb b/tutorials/accelerated-python/notebooks/kernels/solutions/40__kernel_authoring__copy__SOLUTION.ipynb index dc86022b..7a92168f 100644 --- a/tutorials/accelerated-python/notebooks/kernels/solutions/40__kernel_authoring__copy__SOLUTION.ipynb +++ b/tutorials/accelerated-python/notebooks/kernels/solutions/40__kernel_authoring__copy__SOLUTION.ipynb @@ -1,3100 +1,3095 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": { - "id": "-JpGaP7-D_5W" - }, - "source": [ - "## Exercise - Kernel Authoring - Copy - SOLUTION\n", - "\n", - "In this exercise, we'll learn how to analyze and reason about the performance of CUDA kernels using the NVIDIA Nsight Compute profiler.\n", - "\n", - "We'll look at a few different ways of writing a simple kernel that copies items from one array to another.\n", - "\n", - "First, we need to make sure the Nsight Compute profiler, Nsightful, Numba CUDA, and CuPy are available in our notebook:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "collapsed": true, - "id": "AoHkvSPMC5Fs", - "jupyter": { - "outputs_hidden": true - } - }, - "outputs": [], - "source": [ - "import os\n", - "\n", - "if os.getenv(\"COLAB_RELEASE_TAG\") and not os.path.exists(\"/accelerated-computing-hub-installed\"): # If running in Google Colab:\n", - " print(\"Downloading NCU package.\")\n", - " !curl -s -L -O https://developer.download.nvidia.com/compute/cuda/repos/debian12/x86_64/nsight-compute-2025.2.1_2025.2.1.3-1_amd64.deb\n", - " print(\"Installing NCU package.\")\n", - " !dpkg -i nsight-compute-2025.2.1_2025.2.1.3-1_amd64.deb > /dev/null\n", - " !update-alternatives --install /opt/bin/ncu ncu /opt/nvidia/nsight-compute/2025.2.1/ncu 20250201 > /dev/null\n", - " print(\"Installing PIP packages.\")\n", - " !pip uninstall \"cuda-python\" --yes > /dev/null\n", - " !pip install \"numba-cuda\" \"cuda-cccl[test-cu12]\" \"nvtx\" \"nsightful[notebook] @ git+https://github.com/brycelelbach/nsightful.git\" > /dev/null 2>&1\n", - " open(\"/accelerated-computing-hub-installed\", \"a\").close()" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "A1SfTQk0EwUl" - }, - "source": [ - "Now, we'll write our first kernel. Each thread will copy `items_per_thread` items from the `src` array to the `dst` array. We'll set the number of threads per block to a constant, `threads_per_block`. We'll calculate how many blocks to launch based on `items_per_thread` and `threads_per_block`. We use `cuda.grid(1)` to get the unique global 1D index of each thread.\n", - "\n", - "Each thread will copy a contiguous set of items, e.g. the items with indices `[base, base + items_per_thread)`:\n", - "\n", - "![image.png](data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAtAAAAB4CAYAAADbh8U2AAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAAFxEAABcRAcom8z8AAF/RSURBVHhe7Z0HnBvF2cYH0wwYYqpDCGBMCyRAIIGEQChfGuSDj5AESCEY26c7F2ogkIRiA8Y2NbQQuimh2HAn3Z3P2KaY0AMOmE4MuICN66lcP0k7+z3vaHZvJO3qdLK1tnXvw+9hd6fs7sy9Xv13NdoRayNbiEFSiJ/Cl8L3WUI8jeV8LNuQZ5Ox3YntOqwP1dVcpYT4EfLehC2nvJexj/fgU3U1V0jbEX4Edo/nZex/DXwjym2jq7pC2nnI+9yrnmOUScKzuoX4lq7mCunfhV/EPlJedR0j/9O0EGfraq5Qdxv4LuQnvOo5pnz4LqwP1lVdof5ZtH8sPeuSkZdGmReTQnxPV3OF/G8gfxbyk7n1TCP/c7ThfKxvqqsqYXsL5N0AN5vlc41jtKEM/b2G6KqukLYt/EP4SJTdSSevX01dNFCEY8eJxsRFIhy/R0TijVi+BcdFfYutHIklRUP7LFHbnBcboqn9u2JG54vYR0rU488biWeb0jL+FGXyYkPMXr4N8u5C2YRnfXJDq43zSYgIyoVjebGBcz9L1Ld+ivPMHCu3vkqLpbGfF8WMRF5siIaWb4gZHbOw72SmvTn1I3T+qi8+F3WJ88V0Oys2sL0Fyt2Acs0F2xCJt+FYj4jHVuTFBo79CzGj/T2cp1WgDZZobHsT2z/WtXo0q3l35IXRT53ebYBVPyZWiEj0SjFTbqlrZmTbA0Rd/HK0YYU6Vu450Db1Tzj+gaiL3SoeXbZhxG+O8I9wIHyclOIi+B640bLEW3Ac6TYZaUl4Vne3x7VO4lonca2zcK3T5b2M/E/TaY9rncS1TuJaZ+Fa51HPMeVTOaznX+skrnWW+ARLz7pk5KVR5kUs8691Nq51aB/yk7n1TCP/c7ThfKxnX+tsXOssXOssXOs86jnGMdrgR+C8eE6lxC+Q/h5sedUlY/8SfhPrefGMersjrw7Lztx6plFmBdpwJcplxTPyNkX65ZSfW8cx8pLwB6h7a0vLBnI9zpEMyYPkWDkSy1tltZxmhazXrGrrC7sGTSBX2za2P06H0r/TVVzJc+V2KP+AVWW1OmXzjHTsN4oyt+Mo2+qqrmSNDGEfi5yyXvWRn7JqrOe7Q92H6Wquukd1H0x5OP9koXNAGxbhWDX2eFyHDNnD7YFylLwNx4gVqo82tFBbE39I7KirukLeGTiHj3AO0nMfVD+EUKy2XktWJ4/R1VzZI+xhqN+IPurq5RyWoQ2XYHtzXVUJbdoM+78G+19VqD7yO+Bp7VXtX9dVXaVCqRPRvvl0np77oOMjL12dfhl/xx/pautXAJ0dAUOzcyHJzyg/DUv3YoTtXVD/3dxyfkb5KLyPrq6E7UleZf0M+Bunqyoh7WjsoyA0mkbZ57AcqKtT/a+gDS+YZQoZ9bux/I6uroS0i3LL9eIJuqoS6n8TjnqU8zTKvoXlV3R1asNAtCFslinCWRd17HO4Rxlfo/yduqoS0gYjrdbI/wCeir/X77C9tS4WrKavHAQgmi5m4b5ojrTFzC4bIGkDxLIBitafw2lH4s+JubYbG+KRNdsBql4Qz6u8wqZ9R+LdOF5WbADoLhLP4NgZwPR3A6CQjhNOZMUGwPmbOIeomI02eNVzTG14FvXrE2+jfA+00A1EXTRcVBuadN/kAmx9dLh4Gv/EMnn+JrCdS22IZcWGeDw2FP2yWP0NvOqZnoP78Eh8Ef52X9W1KaIGoP49qg254JvrxnZbzErjHFrP0LUzqk+cpP4G9Pf3queY6lMbIrG7dM0NRjirQQCi6VjiE6V3A5woqt14xvZ2qP9Cbjk/o2wKdb6rqyth+yKvsgWcfa2TuNZJXOu8y+YZZd/G0o1nrA9EWq1ZpghnX+skrnXe5TyN8tnXOlsMRdri3HJ+RtlFWLrxjPUBSLvHLNObUT4rnrF9klc5P+NvucHFM4DofDlGttnn4hTHwmPg0XAuQCEP8NYGcMq6IUxXpS9Xdf2gzTHln6f28SddVQnH+w7tVx3bq55p1CcARdlBurpA+taoP5Py8srnGm0DfNpytPyhrq6EPhhjj0M+tdurnmNqA24FcbwbdFUl3Hzsg/NaofbhVc/0Oar+x1h3b6bmVc/bHOf1SFFtoHOkv0VI/p+uroS/w+nu386rnmPdBpzvQ7qqEv6uX1PnRX9Lr3qmqQ0huZTq6OquFpywYEvcVGU/OCmnADi/cYCnGKM8wecWurroEGJPpBV88msaZfHJlgef9+eW68VZF2Rs/yInv6BxvPmweyeKtJ2w/V5uuV6cC5/Xe5TxNcr/XVdVwjY9te3LTcBieBddnerTE/CXvMr6GeWzLsgA3Uu8yhXwdHgTXZ36kW5k8p7gIw3XbvEKlr+cK8RmungwCjf/TIFpBm4zgEcmkMq1gtf4fBExnlLU4W4/En9PYYhXHdMzOx04y4bPcPx6BbYEn171TP8L5SKxrNgQ4ZYfYp9JMQvh4VXHNB0nHF+S9QR4ttwG9V8qqg10/gSw4Xg2fIZjl6ibAIJLr3qmqQ1002LbbmyIJ6MH4xxiYjb27VXHNJWhsnXxvXVtAaDdTEQSEfU38qpjmvqZytVFz9G1MwrHRqk+pJsEr3qOKT5UG2JzhPm0iNbroqeIpq47sTxf1Hs8ZS+zAE0/w5nhU6Q4o/x82I1nrO8Iv+dV1s8o/xNdXQnb13uV8zPKZ1/rJK51svCTY9MouwR2+xrr9AT8Ra+yfkb57GtdGtc6j3IFTDctbjx3d4uDsc9YThlfU1nYjWekbYbtSG65Qkb5rHjG9givcn5G+TlYuvFM60g7Bb4TPh8ONJ7lCLkzQOgDF5oIrvxMgEvLavtoXV3JCll3KPDLLe/lCxR4Xa+rKmH7p+rYzv4LmQA8JP8rR8kddHURrY5+BefwpmqDVx3TBJgEn1XyFF1dyQ7ZE1R958ahkDNtmKqrKiVDycOR1q4A2quO6Qx8rsRNy+66Oj3F3xJtmF1UP1IbcK6A3RG6ulI6lD7HvQnwqmeabgJCssk+redbzq6qrv2sKmslnZ9nHdM4Dup34e9wsK6uhLT/w43AR4Dzz3B+l9rD7Z4HMOUSoOYnucBjGvn4d5UxIGgplll3HiSk/QV5XWZZHydR7hbs1wVwEtKPgD8zyhXyv+HcJ9jbYr+1RplCXgVQHK6rukJ6DfbRmlPW0yj3IJZZw0iwfQD8vlOmF78LH6qrKqFPBiLtXqOMr3H8BJbn6aqukHY68prNsn5GuQiW7oWAhO3d4TecMoWM+p9geayuqoQ2DEX6Aid2vIz8htx6ZVXdmiMAQu0KHukpdBNBLkEggbT6yl4qN+BPP6NjlXrSmqtwvAZ5rQCrTFkvR+IS+8N6/EE1ZMNUXcsBON77gM/8eo4jcGMb3P4u1rNiQ0y1B2L/96p8Oo5XfTKdX0MbDQM5X9fsUX38dLShWbXTqy6Z9p3Jj4hp8azYwI3I7jivN9B3+fVM0znOaP8E7c3+G9OQELqRaGxLF24D+rCxrQPrExAxPQBOqov/FPC6rNc20DnUReeK2rZddc2M6GYoHHsG/ZDpb+/6mZuIpg7qx7N0zYzoRmZmV7u6SaEn3I3tb6PMSJ0biPCP7wi4HWeAT5J8I08aXgVQzL/WSVzrLFzrsst6Gvt8EMvsa53EtU7iWudR3sPvwtnXuswT5HuNMr7GedIwkLx4xj5OQ15zbnkvo1wEy+xrncS1TuJa51E+16j/KY53nK6qhO1NkX49nPaqk+MOlJuAOlnxjPSfwsuMcr5G/blYZsUztulm6BmnjJdxTCcuqB+z4hnbdCPjxhKO8Ta2A4tnepILeH2BoFDBl/n0GQYESUCRVMsambZGWXfTcAddXQnph1g11n/laF3Wz8gHWL0FaD9QV1XC8bemp6+qvnM8L9coR9NV6dG6qiuknwUgjakyXnXJmTbQ+hM4pvvNMQntpuETbxfZho8B4N/XVZWwv82Rfrs12iqmDa0o/2dd1RX2fRLasLLXNtA5hKxZdPOjqyoBZoegzEvYh3ddx9g/2ro4NSp1gq6qhL/4JmjDVWhDstA5qLig/Cp5Cw0b0dWpD76C81usbkToZogge7Sk4SBrH88Al00BLWfCNH74ZzpZSeeNgV8D3NRhOR4eB58Mfx/e1/BuulqWsI8ByNs7p6yX96Hj6WpZQvpXPcrnGeXyx4dCyzNPYD3rmEb9PXSVPCF/r9zyXsY+sm4AHLUIsbNX+VyjvudYNORtmVvWy6ifNw7dEbXPq46Ht9JVsoT623uU9bLn04qUEMcjjmYi3/dbCeQR5P8a69mAtDYKr/kVwO1GAPMvs54aEoTVJ84E6LwMQGqEr0KZ8wGbpwCyjhaNiX2V53TtK6Yv840NMW35XqqMUz7PyHsKSxor7KVpX+4s5kiPeoYp32/cLY3npf3Tcbzqkun8Hl/uGxuqfQXPgfYNT5OesSFmxLcvqg1e459Jd8/bXDz6BcoVaIPqxxXD8Ifzjo1pa3brtQ3UD5HV2+oa2aIhOYX+jnXxI0Rjy6li+spv6xo9isRHq+ee6psM3HzRk3K6IYskbhMN9jodogSQ+RV8Yyolfokjmk8NN0H6mfDLAJ5GLK+C6enhKcg7Gst9HWPb/1onca0zyvoZ+/C+1rXgWudRPteo732tk7jWeZTPNer7X+vQPq86Hva+1tm41nmXz3JbW8/QC1Pz5onNu7q865jGcYbBnvGM/N1yy3t59eqebxFMrVkjtvMqb5huuE7F8fPiGenAVXRDjhFXt2O5zuI5WZX8jhwrJwJ8LpS/l9vpZCUaU4z0sFVlvQQwuwnAcwn8Gztk/xhQ9A3k7at8ttyXQFFXy1LrqNYh8jxdzs+Ubzw5NoWz2sqzjmmqP1LuqavkCWA/tNdzIPsML6BxzUW14Uzpfutsyj7N3sKzTq6r5F66Sp7Qx7sX1YYzZd5v0EgKYotpQ0hmP9jQoifSnaM69/Gs53iMPDw5Mvld+9geeCZRXGG/mSfYzlAPfVNGNxfYLi2eNdQ9YMALPWU9XmezWGURYmy/tBBVgOkmJ/ZMI31FZ4EbgaI11x4k6uO3qKeS9GSwoSUJSP6VzmWx1p1oLHpD68LMcJx2gHMss6Sn0fTD1OlR/xuwIoU90xjnWwA36ukh1pOAaI5n1joXYozGoi+kOMs10huxXOt4Tlenz5Sj5ZcKbGAFM8ZX9yzWupJdZZ+NWIu5Q0kIommZGfLRiO2+xTMghn4gOCcXXgA2l+oiLFZZtSgzNOUMxN283DjsFuIgXaw0vSq3EuH4k4J+mKZ+FNhii2ew60j8Rl2CxVq3akrsJxra/ilmtHeLmd36aTTijqC6vuV10VA6RANatsJe8n4gCIjmeGaVRYi5/RBf/8Sy2yPuXsey5HhOh9LnW6OtlPpKnWCGxt9Wy7flCQH+yIvVr6S+7aiWjwOku924oyFB5+LmrcZ6vWNkh++3CFkCtGyZEuKRXGgBUH+JvEN0MRYrECH2voK4uwJupTjE8u807EZn9100NCMcuzXzozT9Zgga4xxJxMWMlrzX9LBY61SR2Km4aVuof/CYGdJBEB2JN4np0awxjsUINTehJ89Y4mqf5TjghuOZVValUuJUxFne02ikNWHZ53iWVfJUAEuHGtec/STwMux23Q3dY7E8lKpKUfwtdId0aIhGXNav+f2arGFEngKg/NUEZzLgeSG9s1kXYbECF928wT9EPGb9GKTPCjfXqB+0qa/SATDqFWstK0S45Re6BItVXtGbRRpaX89ANACafpiaGc5xmS5RtAAqNbD7oy8ytun9vhzPrECEeKM3i9BTZzcGdRz2KZ7lGHmwrJafq6/SQzDgmX7gZlVZkwJ9xRirX4ve0iFr5OsqDh2IpregVMtf6iLeAqDQDwC7THjG9hI47yXgLNZGp6aWowDOcfF0N71Fw1Zfpde3rBEN0eDe7sFikWjccyQxTw0johu5OQqg38ZVt+inbACUo3CRdidBISNtDczxzApUiL094HlmLFqWeg93UfFMb9aQIfmq++SPnjyPUdDyV12ExQpMiME94Hk2vZ2Dvg2hVxHWyOy3K5mKZr4qf8WEZ0uINqSdqIuwWBuMEJ/0FpgRiNHbsPypTvYXvY2CfrBFX5cTPNMrxxpa6NVov9clWKxgVRs7TP24kCZfUZPxJLInkSkgQDK9jYJ+sIWruwss6XRacDyz1ou6u8WhiEl3OAfWi4/nkDwv64kf/XCwyrqXfzjIWl+SVfKbuIF7Q46TUcTldL+3sSilhagx4RlQkkJa9oQCLNYGIsTnGCNWo0khjtJZ3orEfi3c9zi32uopNE3bzGKtTzXEDxeRlqmA50nqdX9FCnDyawdUHNO0zTqbxVovSibF4YjNqfAkxGRR8SzHyN0BKovdH2/RmOca+az0mD6bxQpSiMet7fPsPXKnUc8SIITexzzLARINJTS9c97XLzfddNNW11133bb91Xfccccg/BMv+48Z+ns/k6kPdHfkCfF5b068zvzc5x3VSpHEo2K2+ppcT/kce1FMf3+LqeOHD/Q6dn9yoX5eV+J4Xrf9DEB5FIHvwrNlqTdNbzF8+FSOZ47nQLyu+jldlT5bDdmgJ880bKNGxug9z5RHn7X0met1/P7iIOKZ+3kt+xkAcpMBIx1w1qw1jiZPnvzE3/72t5WTJk3qd54yZcrKa6+9dtENN9zg+VLydan+3M9kajv1ge6OPCE+R8AgYjdmk8lCMxXSD7Ro+AZ9Vd7UkRYNCTU0adLEiY9zP/v387oSjsX9vA77OZ0WlyGSFTwDpmlWOxXPEydyP3M8l9/rsp+TI5JHS5pSmqaEzkzX/DedJeizlj5z6bPX6zwq3UHFM/dz8f2MS27+A1QAyBD4ZrgWQHKaTs4TDvbc7bffbl9//fX9zrhDsdH+1MSJEz1nxFmX6s/9TKa2Ux/o7sgT4pSmYf+3A9DaE3R2vh6Nby/CsYliZjIsamPufPw4xrPcz/79vK7E/VxkP4djg0Wk5UAxc0HBtw4g2Gk2vIlwGHanneV+5ngOwsX2M+J0MOLzQLhgPMsq+Xs5RtbJanmd/IPcUSfjhnDirjhOij57vc6j0h1UPHM/997PdrW9B43Ll2Nlo6yRv9XJ2QKEZE11mCscZBZonQ6mjLuWirfT1uuuu46229DhnlO0rkvl93N/6OtMW8m67bN0d3gqLcQlJkBroPaeEtsRTQttCMd92uznSaqfK9vUxr7087qQdz9Prmj3uZ/rWg4Q9Ym3xOzUGlEfbxLTWnbWOb5C4PcSz3Qe/cB96ed1IO7n3vsZ0HwA/BZMb4Zpam0VBb+5tb+TP+U2fdair9vos7ff9PUkcvH9vC7k2c/qPCrdxfcz4PlB+wL9LUmNbJGj5D46q3jRQcwLBzq94u20dcMAaGe90pxpm9Peoi7QQhwMaE4aAN2JtEN1dlHCcRmgA7hAe/czLmAV6pIAuj5+kxpmRD9wpaFG4fhYnVO08vs5/9wq0n3p53Ug7ufe+9myxE2IYjXMiAyIHqezihYDdDDxzADdez/LavmKeq0d/dCVZseskqN1VvGigzgXjvHjJ9gvvDrTXrHqI3vJqncqzktXvW9/vPgN+/rrr7MnXjNxvQH0hPHX2LNefchesGqG/f6qSEWa2kZtpLZSm4sJaEDz1oDmVx2AJgOgQzq7KJkfhFePn2I/8eql9txV59jPrDqvIk1tozZSW4vt53Uhs5+vGX+9/eCrI+yGVSfa4VUnV6SpbdRGamvR/VyfmCiaujKzE9IPXsOJfyKq+/SDZbOfJ46/yf7Hqyfbj6za135o1bcq0tQ2aiO1teh+Xgcy+/na8TfbNz9/rH37siH2bQsr09Q2aiO1tdh+BkBPzAHof2LZp3jOArtrcc2acrU9/ulh9pXPD7GvfK7yfMW/hthXTTvWnnx18f28LmT282T087WTr7bPuXuYPfq+IfaYCnT1/UPsi+841p4ysQ/xHLKmqHdC6x+7WlXW4877dH8N/xHudb5vOohz4bjyivH22x+9hL2tsbvtZRXntL3CjnZ9at9ww/XrFaDHX3G1/cpHj9tx+wWc0TMVaWobtZHaSm0u9sKBmL3bBOi0yJn9yrYHiHD0ZBGOXSwiif11qivzg/CqK6bY9R9dZL+FW8x/26Mr0tQ2aiO1tS/9vLYy+/nqK26wH/voN/Zz9pH2bPuYijS1jdpIbS26nyPx34j6Fls0tGYm+QnHnxfT7awhSQjyAQCRk+GL4YLxPPGKm+37PjrGftIeZE+zd6pIU9uojdTWovt5Hcjs52uvvMW+Zd5B9j8sYd/ZXpmmtlEbqa3F9jPi8zcEzo6x/TyWWfFMb9tIh9IXyWp5gk7KUjZAw1Ousq94eaB9+X+Effmblee/zhf2hIaD7MlXFd/P60LZAA3OmXyVXf3AQPvsh4U94qHK81mPCPv8uw6yr5vYh3iukqerp88E0KNtmuTnZQIQAmdLP717HsuC726kgzgXDgLoee+9oECzzV5Sce60l9orWz/eIAD6xfces1fjY3kpPp4r0dQ2amMJAD3ZBGhs36qzMgrHq0VTR7d4Adn1if+I2jVf1zlK5gchQWX4vYvtN/Ev5FXcYlaiqW3UxvUN0I++9zvcNh1tP20fX5GmtlEb+wTQddFjRSQmMwDdZYu62LtittxG5yohiqsBIt1YEpD8p71d+MYzQeW97x0P0NzeftzetSJNbaM2rneA/veh9p1dwv57vDJNbaM29hGgj4XdaeYtS7yLbTee7Sr72zIkF+sxpe0AlJN0litPgP7XdvblbwA4X688X4YbgwmRQzcIgK65fzt7JGBz1IOVZ7oxuOAfh/YNoEPyWECzVAANWyHrU2EJ8X4OgHxXl/cUHcS5cDBAl09mPzNA+wvxeoEZv4hn9xVISuHYHPGMtEUjoITe/xyO/0znKJkfhAzQ5ZPZzwzQPqpbcwAAOq1itamT3lX+pXorhyEAyByCEcfY9o1nBujyyexnBmhvITbpR4RpI1a/xNKNZzlKXqRmH6R3QF+gXl93h85yxQAdTDwzQBcRz6PkAQDotAPQ9OpFAmhcqTPwARix4IN0eU/RQZwLBwN0+WT2MwO0vxCve8Pz4RS8JCnED3SWENPtTQEhH6kfZdFX4wQlja3H6Vwl84OQAbp8MvuZAdpHDdE9AMxdorEtM9V8fTwmpsXd6WNxkd7UssRHDpBo+8YzA3T5ZPYzA7S3EJt7AJq7nFjFegx249mqsm5R736mr8WxBJzcoLNcMUAHE88M0EXEc7W9B6C5S93w6aEcNAYa/89YQ8heuryn6CDOhYMBunwy+5kBurAQs7vBv0YMf0MnZTTX3kxE4otcgCYoicSzvmExPwgZoMsns58ZoH1UH9sLN3xJF6AjsWgOQG8GAFlEl23H2PaNZwbo8snsZwZobyE294KTRqxGYTeeASP/MAHaDtl57/FngA4mnhmgi4jnKrkXYjblAjTMAF3ADNDBeW0A2lcE0OH4QgboHjNAB+OSATpMQzgKAvRCumw7xjYDNAN02b0WAG0O4cgF6DsZoLPNAB2MSwHozhGdw9QQDn4CXZwZoIMzA3QwZoAOxmvxBNoA6HhCPJRwZ2XDRZoBOscM0MG4VIC2rCyATsBuPDNA55sBOhiXAtDtVe1fB0C34GM08yYOfgJd2AzQwZkBOhgzQAfjkgC6tnUXxOdSNZkKORL/QjyyZjudSxdpBugcM0AH4xIBehd4qRGrX8BuPDNA55sBOhiXAtALzl2wpRp2NBaxCoimdQboAmaADs5rA9CI2cPgv8C/RRz3vGeUATrPDNDBuCSApklTIrGQmNm9UMzsWijCzVUqTQsXaQboHDNAB+NSABrxuUk6LUIUs9pVlKazGaA9zAAdjEsBaJK8UG4la+Rv5Wj5O8Tt5gzQBcwAHZxLBWjE6wHwUiOGe6bXzAD0YgboHjNAB+OSANrRUyuGibqVe+stVwhwAujFWDJAazNAB+NSANoR4nQY4jQvngEi/CPCHDNAB+NSATpPAA41iYqGD1oO01meooM4Fw4G6PLJ7GcGaH8hZkc58atjOKyzMq+xC8c+E08nMwBNs7s1xA/XuUrmByEDdPlk9jMDdGlCgG8KEPkMSxOgfeOZAbp8MvuZAbo0WVXW312ApslUQvIaneWKATqYeGaALrGfARxzHfiwBC7OQuykszxFB3EuHAzQ5ZPZzwzQ/kL8jnXil4ztOp2VUST2hJpAVo0pja0WT8WzbhDND0IG6PLJ7GcG6NIFYH4CkezA82osfeOZAbp8MvuZAbo0pavSITWe9Fx4nG2nQ+mROssVA3Qw8cwAXWI/Azi+CUfg5+EfAULcMUpeooM4Fw4G6PLJ7GcGaH8hZsfkAHStzsqoLrGfaGh5XDS2vYTlL8R4e4DOUTI/CBmgyyeznxmgSxegeT/4cfglBPwvYN94ZoAun8x+ZoAuTfJMuY0MyautamueNcqaYFfbW+ssVwzQwcQzA3Rx/YwbvkGyRp6uxkFfKLfSycWLDuJcOBigyyeznxmg/dUrQPci84OQAbp8MvuZAbqAwm3fFk1dd4vGjvtEbfQQnVq0zH5mgC6fzH5mgPYXLsrfxs3e3fB9cJ/jmQE6mHhmgO69n+UJcktZJe+1z7Ft+uZEVsu7dFbxooM4Fw4G6PLJ7GcGaH8xQPfNDNDBuCSAplfWRWKviBcRyv+CI/EXxbTP+/SUw+xnBujyyexnBmhvrVkjtrMs8Qoi2Rly9BLcp3hmgA4mnhmge+9nwHP+e6D7KjqIc+FggC6fzH5mgPYXA3TfzAAdjEsC6HBsKAA6KRrb9RtjYq3mTITFyOxnBujyyexnBmhvdXaKoQBocyrvVrhP8cwAHUw8M0D33s/2CHuYDEkrayZCEqBj27gQ26uNXkQHcS4cDNDlk9nPDND+KgqgH16+jZj2ueeF2/wgZIAun8x+ZoD2Uf5MhDEvgAaEbOMHImY/M0CXT2Y/M0B7CzGaO5V3LDdu7dPsTe3f2jvZOb9NccQAHUw8M0AXEc9Vci8AdMoFaHoCDeg4DtAx3xJiIZZ/wLZnIDuigzgXDgbo8snsZwZofyFmC7+Foy5xpGhofR1Q8oWobz1H3G1vrnOUzA9CBujyyexnBmgfEUCHY6kegI5FcwEaAHKkZYnXYZrV7Zx584RvPDNAl09mPzNAewvxSQCdMgA6CrvxLM+UuwBIHrFqrNXWKOshAEneG8AYoIOJZwboIuLZC6ABzvMd+MD6KgDIEF3eU3QQ58LBAF0+mf3MAO0vxGuNCdCI4QadBdmbiHD0afUau9kWAUmniCT215lK5gchA3T5ZPYzA7SPegFoRPEmAOensXSApBP2jWcG6PLJ7GcGaG8hNgsDdLW8QP0gaxx8nnoP9Dk6yxUDdDDxzABdRDz7PIHG/zMGjEgseSIVbQbo4FwqQCeF+AGgucuI4at1lp6JMNYzEyFNpFK35gidq2R+EDJAl09mPzNA+6h3gN4MAJ07E6FvPDNAl09mPzNAe6tXgDZnIuSJVJQZoINxuQCap/I2zAAdnEsFaBJi9gy4DiA9GcsddXIGoCPxRTyVd48ZoINxuQAaALKILtuOsc1TeTNAl91lAegQT+WdawboYMwAHYAZoIPz2gA0CfGbNRZUST2Bji9kgO4xA3QwLiNAL6TLtmNsM0AzQJfdZQLoOxmgs80AHYzXAqDTDNBFmgE6OK8tQHuKATrPDNDBuCSApmnmzbdw1Ge/hQMXaQboHDNAB+NSABrxOQzx6fsWDgbofDNAB+NSABofnUMRs0kXoLFkgC5gBujgzAAdjBmgg3FJAD09ugfis0PFaVOnjdhtYYAubAboYFwKQHd0iD0Qnx1OrFqWaGGALmwG6GBcCkDHR8V3QMy+S+P17XPVmP1PGKALmAE6OK8tQCNu90EMf0VvZsQAnWcG6GBcEkBPk1uJcLROvTXmOTgcrRXT7S10Ll2kGaBzzAAdjEsBaMTmVoDmOiNWa7F045kBOt8M0MG4FIAmyWp5jBwjm+CZcrT8PgN0ATNAB+dSARrxug18L7zEEuINLA/VWQzQHmaADsYlATSpdvkuiNM/ica2S8W0L3fWqUq4SDNA55gBOhiXAtAkxOcu8J/gS2ldJysxQOebAToYlwrQJHu4PVD+WmampM8BaAtLfo2dNgN0cF4LgD4lJ4bv01kOQC9xAbqpyxYN8cN1rpL5QcgAXT6Z/cwAXZoQ4ATQS7A0Ado3nhmgyyeznxmgSxMA+m4ToLHd8wpSLQboYOKZAbrEfgZwrDDgoxveXWd5ig7iXDgYoMsns58ZoP2FeM2dibBeZwlB08OGY2+KZ5BFT59ntNuiNnqIzlUyPwgZoMsns58ZoEsTongAgPlNLE2A9o1nBujyyexnBujSBGC+RgE0/SjrAgXQl+ksVwzQwcQzA3SJ/QzguADu1PBxK7ylzvIUHcS5cDBAl09mPzNA+wvxOiYHoGt1VkbhllGA54SYlabhGw+LmXI7naNkfhAyQJdPZj8zQJcuAPMoOKHh+WHYN54ZoMsns58ZoEuTHCkPBTR/SLMRYvmerJFZN4MkBuhg4pkBuvh+tsfag+zT7J7fWwE8jk4J8RMs3QH+fqKDOBcOBujyyexnBmh/9QrQpLr4EaK2+QQxW26jU1yZH4QM0OWT2c8M0AVk25uIhsSRoqHlaKwP0KlZAjQfkUqJE7AsGM8M0OWT2c8M0P7CRXkTxOmRWB4Nb6qTXal3646SJ9vD7aE6KUsM0MHEMwN0cf2MeD1VjpGvydHy37jh+7lOLl50EOfCwQBdPpn9zADtr6IAuoDMD0IG6PLJ7GcGaB/RmP1I4loxs7tDNHV1Yv1qMX16HnQUktnPDNDlk9nPDNDewgWZxuxfC3fAnfA1XhBdSAzQwcQzA3Tv/Sz/IHeU1fK/6jV29K1JtfxcZxUvOohz4WCALp/MfmaA9hcDdN/MAB2MSwLox1Z/TURizWJWylaOxFaJcGywzi1KZj8zQJdPZj8zQHurvV18zbJEMy7Mznj9VVj2KZ4ZoIOJZwbo3vtZfVtSbcxESBOp9FV0EOfCwQBdPpn9zADtLwbovpkBOhiXBNA0E2E4ZvVM5R1PiIcSO+rcomT2MwN0+WT2MwO0t3BBppkILSwdgE7AfYpnBuhg4pkBuvd+9pzKG8CxLfxHS4gJWBZ8AweJDuJcOBigyyeznxmg/dUrQE9fOQggMk7UJyaKpq79dKor84OQAbp8MvuZAdpH9bG9ANCpHoCORc2ZCEkI8kGAkHHwRLhgPDNAl09mPzNAewvxuRecIngmYz0KZ8UzgOR/rSrrxtTZKc/xpAzQwcQzA3QR8ZwB6FQuQP/dgQ9A9NNYDtLlPUUHcS4cDNDlk9nPDND+QvzmvsYuorMyqk9cI2anbfECssOx10Vta9bL/M0PQgbo8snsZwZoHxUB0AAQGkfqAMnrsG88M0CXT2Y/M0B7C7FZEKABJD+T1bKFXmGHy1I8FUr9VGe5YoAOJp4ZoIuIZy+ABjS3O/CBdYml569hHdFBnAsHA3T5ZPYzA7S/AMxnOPFLxvajOkuI6famAJIPFUA3AEpmdgOi49/RuUrmByEDdPlk9jMDtI96AWgE+KaWJT7EUgGJtm88M0CXT2Y/M0B7q1eADsmb1Huga2B6D3SVvEpnuWKADiaeGaCLiGcvgM6BjzTMU3lrM0AH57UA6B3gMNyKG8DPsDxeZ3nMRNhpi1qeiZABuvwuE0DzTIQ5ZoAOxmUC6OyZCBmgGaADcrkAOgUzQGszQAfnUgGahJjdCv6fztzYzQD0QhegMz/M+q7OVTI/CBmgyyeznxmgfVQcQC+ky7ZjbPvGMwN0+WT2MwO0txCbvQH0nSZA2yF7gs5yxQAdTDwzQBcRzwzQfTMDdHBeG4D2FQN0nhmgg3HJAB2JpRmgizcDdDBeC4BOG7HKAN2LGaCDcckAbb7GroaimgHa1wzQwZkBOhgzQAfj0p9AR3sAuj7eYr7GDhdpBugcM0AH41IB2rKyALoFduOZATrfDNDBuBSAbq9p3w0AHbPHIlYB0YhfiwG6gBmggzMDdDBmgA7GJQF0Q8tOiM9F4nlcjskUu/et3lbn0kWaATrHDNDBuBSARnzuBIBe5MQq1hciXt14ZoDONwN0MC4FoBGnmwOgr5NjZbccLbsRr1MYoAuYATo4rw1AI2aPh2+AxyGOt9bJDNAeZoAOxiUBNCncfIaYmZwvmjrfEXXNp+tUJVykGaBzzAAdjEsBaFI6Lc5AjM6H34Gz4pkBOt8M0MG4FIAm2afZmwKiT5A1MvPe8hyA5rdwGGaADs6lAjTi9RC42YjhP+osB6AXM0D3mAE6GJcM0KTHPhsi6lcM0VuuEOAE0IsJnB0zQDNAB+FSAZqEGB1C1puuACJ3mQDNb+FggA7KpQJ0ngAcKQM+yAzQ2gzQwXktADrkxK+O4Xqd5bwH+hMxK5kBaAJpfo0dA3QAXiuA9hECfFOAyCcEzo6xza+xY4Auu9cGoP0EgL7dBejz1ZjSq3WWKwboYOKZAbrEfraEaDLg4z3Y/ZWsl+ggzoWDAbp8MvuZAdpfiNfcmQjrdFZGddGp4jlk0ZjSSOwL8VTHnjpHyfwgZIAun8x+ZoAuXZYlpiKSHXj+AvaNZwbo8snsZwbo0gRg/oN6owFB9BjbTlelf6+zXDFABxPPDNAl9jOAYy/4Afgp+Hs62Vd0EOfCwQBdPpn9zADtL8TsmByArtVZGU2P7iEiiTtFQ2s9/COU2kTnKJkfhAzQ5ZPZzwzQRWj8+AF6LUsI8j0A0XcCnOvhH2HbN54ZoMsns58ZoHsX4jQvnuW5cktZJf9ohaw56RHpC+zT7C10lisG6GDimQG6uH62q+2dZI36b6x9vj1YJxcvOohz4WCALp/MfmaA9levAN2LzA9CBujyyexnBugCamg5WjR1PylmdNaJp1qO0qlFy+xnBujyyexnBmh/4aJ8NG70noTr4D7HMwN0MPHMAN17P9vD7YG44XtcfWNyjm3LavmYzipedBDnwsEAXT6Z/cwA7S8G6L6ZAToYlwTQ4dhgEY6/Jf6FUCbXx94UDct63ipThMx+ZoAun8x+ZoD2ViwmBluWeAuR7Aw5ehPLPsUzA3Qw8cwA3Xs/yzFyd0BzGz5GM+P2aSKVvooO4lw4GKDLJ7OfGaD9xQDdNzNAB+MSAXpo9lTe8VZzIpViZPYzA3T5ZPYzA7S3cEEeCmg2p/JuhfsUzwzQwcQzA3QR8TzCHkaTp2TNREgCdOyKtT3URi+igzgXDgbo8snsZwZofxUF0A8v30U8vnyo3sqS+UHIAF0+mf3MAO2jXqbydtTaKnbp7BS9xjMDdPlk9jMDtLcAywWn8iYBRLbuPKtzb/lruZVOyhIDdDDxzABdRDzTVN4hmXIBGiYAORleaAmxOi3EOYCQTXV5T9FBnAsHA3T5ZPYzA7S/ELuF38IRaf2xaGz/EEASE5H45WKm3FLnKJkfhAzQ5ZPZzwzQPlJTeZtPoPMBGkH+Y0DIh3AMvhz2jWcG6PLJ7GcGaG8hNgmgzSfQWQANIPk6gKTRqrE6rSqrXtbI3XSWKwboYOKZAbqIePYB6I8d+ABEx7G9qy7vKTqIc+FggC6fzH5mgPYX4rbaiV8y4rfnPdDC3kSEo8+r19jNShGQpEVkzYE6U8n8IGSALp/MfmaA9lEvAI0o3sSy1AsZHSBJw77xzABdPpn9zADtLcRmQYBOV6Uvtc9F1lg48x7oC3WWKwboYOKZAbqIePYC6Bz4kFgO0+U9RQdxLhwM0OWT2c8M0P5CvH4HjjsxnBbiLzorMxNhxJiJcCaWdWuO0LlK5gchA3T5ZPYzA7SPegdor5kIfeOZAbp8MvuZAdpbiM3CT6DNmQgzAH2NznLFAB1MPDNAFxHPRQB0CuaZCLUZoINzqQBNSglxAuL2fsDzn7DcVidnALouvoin8u4xA3QwLiNAL6LLtmNs81TeDNBld1kAOiTvdAGaliF7gs5yxQAdTDwzQBcRzwzQfTMDdHBeG4D2FQF0OL6QAbrHDNDBuIwAvZAu246xzQDNAF12M0AHYwboYLwWAJ1mgC7SDNDBmQE6GDNAB+OSAPqp+DA1Tt8B6Pp4jAG6sBmgg3EpAN3ZKYYhPs23cNAPXxmgC5gBOhiXAtCdNZ1DZbXsdgEaSwboAmaADs4M0MGYAToYlwTQ4ebdEZ9toqnLFjPhcCwhZsS317l0kWaAzjEDdDAuBaA7OsTuiM82J1YtSySwdOOZATrfDNDBuBSAjg2PDbaqrDdovD7FK+L3HQboAmaADs5rA9CI3c0Qt4fBX9NJGTFA55kBOhiXBND0isVw7CHxDC7HcyQB9IPi7nmb61y6SDNA55gBOhiXAtCIzS0BzQ85sarX3XhmgM43A3QwLgWgSXK0PFSOkf/E8jH4WwzQBcwAHZxLBWjE7VcQs0/CzfDH8FE6iwHawwzQwbgkgCY9smY70dBWJRrbQiKyuucHsRAu0gzQOWaADsalADRpzRqxHWK0Cg7BWfHMAJ1vBuhgXCpAk3DZHWCPtwfojSyAtrDk19hpM0AH51IBGjH765wYfkhnOQC9xAVo+lq8IX64zlUyPwgZoMsns58ZoEsTApwAegmWJkD7xjMDdPlk9jMDdGkCQN9tAjS2r9ZZrhigg4lnBugS+9kS4gsDPjrgr+ssT9FBnAsHA3T5ZPYzA7S/EK+5MxFGdBalbCLCsdfEs8iicaUNrbYItx6kc5XMD0IG6PLJ7GcG6NKEKKaJVF7D0gRo33hmgC6fzH5mgC5N6er0eAXQY+AL4Gr7zzrLFQN0MPHMAF1iPwM4RsJRuBu+GhCyhc7yFB3EuXAQQL/94YuI/DV2FzCo0pyyl9vNnZ9sEAD98oeP2zF7Ls7omYo0tY3aWAJAj8kB6FqdlVE4foZobF8uZrRbIhK7XcyW2+gcJfODkKAy8uFF9lu4kr9uj65IU9uojesboB/78De4ZToSt07HVKSpbdTGdQnQJADzGfBy2IJvh33jmaDyvg+Psafb29pP2DtXpKlt1Mb1DtBvHmz/Iw3QbKtMU9uojev0CfQoeYAMyddpJkIrZL0qR8v9dZYrT4B+aWv78nkAToLoCvNl8wHQDQdvEABd/cDW9giAJkF0pXn4I8I+/66DSwJoxOkuiNueGbsBHd8CfNCMbplxHQVEB3EuHOOvnGC/9MZsuzn+qf1l/MOK88r4x/ZnX75lX3/9desVoCdcebX9zBsP24viM+2P440VaWobtZHaSm0uNqB7BWjStNX7qxkI59nuD1gcmR+EV185xZ7+xiX2i/Fx9vPxcyvS1DZqI7W1L/28tsru5+vth94Ybs+I/8Suj59Ykaa2URuprX3qZ/ohYUPbiaK+7SQx1R6oU7MEaN4/mRRHIOALxvPEK2+y73rjRPvR+J72I/H9KtLUNmojtbVP/byWMvv52vF/s//2r6PsO1YNtm//ojJNbaM2Ulv70s+I1S3hE+GTEK958axg5Gz5AzlC7qyTspQP0FfbV87Z3b7ixcH2FS9Uni9/ebB91ZNH2ZOv7ls/r63yAfpqe9w9uwOiB9s191eeQ1MH2xf9/SgAdPH9bAt7E9z0DZdj5EeyRn6M2P2NzipedBDnwkGePGWyPWXKlIq209b1BdA9/VzZpjY67S36Al0MQBeQ+UGY6WfytRXuTFv70s9rq/x+pvOYaJxTpTnTtj718/T3txCRxO3i6WRm2vlw/BYxd+5mOrco5fbzJPdcKtfUxj718zpQXj9PpvO4prJNbexDP+OCvAXA+XYsneFGt2DZp3jOAmi3r+lcKtl96+d1Ia9+vnbSNQDpyvWkSX3rZ7rJkyG5SA07Gmfbslqu1FnFiw5iXjjQ6fbEiRMr2D0X5/UJ0Jl+rmxTG532FnvhWNcAfe218MTJMC0r0Wgb2tjXfl5b5fcz9XGFG23sUz9Pa99NvfuZ4JkgOhJrxvZgnVuUcvt5kurnKRVtamOf+nkdyKufJ9G5VLL72M8A5t30u58dgG7Gsk/x7AnQ19K5VLKDj2evfp48aUpFe9KkPsYzzURYbcxESBOp9FU4yLO33Xabgsn+5htvvJE6uRtg3TP+pUzqz/1MprZTH+ju8NU6AOhnuJ977+e1FfdzEf1MMxGGY5YxE2FcPJTYUecWJe5njucgXEw/44JMMxFaWCqAhuPY7lM802ctjtNNn71e51HpDiqeuZ9772fPqbwBHDvDkywh7sAybxB/rnCQMGi9BReQfucpU6bQcjk8RHdH2dSf+5lMbac+0N3hK8Rs7ls4sgF6ltxBROJXivrWu8TMtkN1qiscq477ufd+XlvhWNzPvfVzfWwvAHTKBehILGpO5U0CgOyQTosrsbwL5njOMcdzMC6mnxGfe8EpDc/0BDoK90xNnxlTeiZA5EHAyW91cpZwrCHwcv3Z2+8cYDxzP/cWzxmATuUC9EMOfACiX8L2drq8p8aPHz/4hhtu2AXLfmdqNzp7Z6z3+mPLtRWO0W/7mazb3uvXfYjX3CfQPa+xI4XjN6vX2L0AR2LvicjqrNkK6Rjcz73389qKjsH93Es/FwfQNyOSHSB5r709e/ZNOgb3M8dzuV1MPyM+CwI0gOQUWSO7aWpkOVp2pkKp/9NZrnCMAfSZ21/7OsB45n7uLZ69ABrQ3GXAB+K78EyELNaGpJQQvzIBGvHcM5HKdHtTAMnHYnYq8w5omlAlZyZCFmuDUS8AjQDfFBfoj7FUQELGNscza4MUYrMwQFfLW9QPsmrgC7wnUmGxNhR5ArQJH4DnNAM0a2PSGiG2AzQ/iLhdieXbSSG+r7P0TISxnpkIZ3QyQLM2XPUO0F4zEXI8szZIITZ7A+i7smYirJJX6SwWa4NTMQCdYoBmbWxC7A6Av4PYzX6XaGYq74U9AE1QwgDN2kBVHEAvpMu2Y2xzPLM2SCE2CwN0SN5pArQdsifoLBZrgxMDNKt/iQGatTGJADoSSzNAsypBiE0C6LQRqwzQrI1WCqDN19jVUFQzQLMqVQzQrI1J0/KeQLear7HDRZoBmrXRqLMz7wl0C+zGMwM0a2NS+8j2ryFmV9MkKvYYNWa/iwGaVbligGZtTKKnzfSjV3pjzFw4EvtITF85SOfSRZoBmrXRCLG5A+z+6NWy1LobzwzQrI1J9mn2pojZy+yxdhwAHUfc/pkBmrXRCzF7Knx/WogrEcc9r6JhgGZtbKprPlHM7PqXaOp8UdQ2n6BTlXCRZoBmbVRKpcSJiNF/wS/CWfHMAM3aGCWr5ZGI3R+ojRyA5rdwsDYqIV6PgNuMGL5MZ2UAOhJf3APQ/BYO1kag+z7aVjyyIO99/AhwAujFBM6OGaBZG7oQo9vCefHMb+FgbfSyhABduPCBOGeAdoS++Bo8HD5cJwUq/E02xbH/D6Z3HQ/UySxD6JsaJ351DNfrLOc90P8Vs/g90Fmqbd1F1EWHi7rmI3VKsKKJiMKJn4u6+OmiYdnWOpXVixDg9B7o/xI4O2aAVh9aX4OHwz/USYEKf4fNceyT4N9i/Ss6mdWLrJB1qwvQ/B5oV3K03AV9MVyOlOvl+myPtwfIUfLnuKE5HX8bvj4XEgD6CUAHDd2w4FcAIWWf9WZDUUqI49D+59DuuXATXAs/o7fvTQvxO4IylLtbVwlUOPbWOI8lcBTOmj5c542Hn4fn4lz/guW2OrvfCG3Oncq7TmdlFIn9Xc1ESGNKw9EForb96zqnckVgHI7NETPa56L9M9HuWizniKaOuYDWh8ST8dPUTUUk9oiuEazunre5CMc/wDl2icebd9epGRFQ18bvEXOsmSKSqBVPNf9M57AgQNrfEckOPC+AKz6e0dYfo51ztZvgWngObVuWeATLKuoPrGfPQhqQcPzt4E/13+RAnayk866Fn4efhSfDu+jsfi3A2WmyRqbVTIQ1MgVw/JXOqmglq5M0BGAO2jtXVsuZWK9V22OwHZIPUb/QD9UAsOvl+ozjb47z+ADuwjllXZ8B9V/D3+pada418lmUqaHyOrv/CcCxC/wqQHIBAOQ7OrlfKAegmzWAvae3CaBPwxLXZXGDrhK4cPz58CLYfccx1reDZ+jzfRWmKdiTOE+C/34F0Whv7lTetTorI3raWp+4RtS3PCAa40fo1MqWCdDh2KrMzUPsIxegp8VOxXa3urlYX4rEXxF1sRVi2prddEpGjy7ZHuf2iHg6uUL8C+ddu+YCndN/NO3VrcS0z7fSW1ki+IKvwUXpASz7RTwjCkyAjmGbQPW/tK0BejilYb1nFtKAhePPo3OD99dJ9LfaBuc0Q5/vv+F39TrdBGypi1W80NatyHrTlfpR1ig5wgpZj6VHpYfTk0+dVdHKAuiQXEU3EOiDjxyATlWlTrWqrW4A9Hq7PuPYr+BcVgCS3evzytNWDsJ5vojzjAH86Vznq7dRVMub6W+pi1W05Nlyd/zd/gpfgfbvSgBSBS8HfNFTzlsBIf1yqADafx0BWLtxEwHAPgHpBND3IW8CwRk96cX6FpSP7YvhS+FfwhH4f3X6EPhGSkPde7E8lNJJqEtPjkfCYZj2V01pOpvqHoA6t2EZQXo1lu/Dn8ImQJ+BPJq2+gGdRGk3UxqWZ+qkfiG0tzBA93fVx68QsyxbPBnt+Xp72qpjAalJ9QS6Lnq5eCZdB6C90h1OURc9H74MPkVEEhFRH/tlJn3pjrgZmSxmpyMoP1VMW94DcAR84cQfRDheJ5o6sb/WceK+1T03c9NW7oNzuUk0tkYA+KOxnzdxDkvzANrRU6v/rL45qGseo1P6hyKJ/xVNXXOVw60/0qksLYDYzQSh8I91EqWdQGm4UDdifSJci/XJ8bjYXudfgu1JKHM21sPwHyi9vV18Heu3wBGYQPwnlE7COo3bHQvXwbXptPgz6u+ksyn/UPjvMNW9DP4AXg2bAL0t6p2F5TjaRn0agvM2nSv8bVWowoX2/i+sbn7QZvdvxsoIEHYFPW1OViXd63PHiI5jAapJegINXy7PkXUod6UznCIdSp+PG4/LkHcKykUAsOr6LP8gdwTwTpbjZAQAPjU5Kulen+WFcivs4w8oWwcArsNynBwp3euzPFPug7o3AQwj6ar0aKy/ifJLTYD+/Nefb5UKpX6Fet/SSQTaH+EcEnRsnVSxWn7m8m3Q3hk03Mg+Vw05mkHguNCAD8S52FeX71dCP9xCfZAU4lidRHD2M6QnseyA34JVXyFtCpYDsD0X691Yfgm/DZ+O9O2xpCfCXVifp/OWwYfofdJQC/qxJj3p/kTv71qdtw/8AUx/C9rfRzANrXkHNgF6EtXD8kSdpM5Vp92vk/qF0N7cMdDZQzj6uyKxa8WsNAC62Y0V8VT0GIBum2ho7cRyvmhs+0TMkbaoXX27oKdAtdEmpKUAuMvFzOR8bJ8lZsrtRF38WcBxUjS0zEPdpaK+dZV4cs331D4jgO4Z7WlA8geivuVjdcy66M0qryG6h2hqf0vMxD+J+sTb2O8H4umkhTr/9QXo2tgE8Qz+pP0JoOsSOwKgP1WvsSNHYu+Kh5dvo3NZED6g1BAWLE/RSZRGb3qwAMkWlh/D6k0l2H4Ayy2wfEnXaYXfgcdieycsX6Z0mJ4er8QyjuUxep8TdZ2PYDU8A/tRQ/k6O3GdluJDnT8f/gyW8BLYBehctbWJXbEP2h+9D9k77itIaOOOsOo73VfvwhzPhgBh1xJAA0x7PstHymMAye2A107kz7dqrE/tc2zbqrJuV+OTAW7IS2F7OWB4PqDuLHmu3A4g+yy2k3aNPQ+AvBT1VqOsuj5jeT7gOI3lB8j7WA0RCcm/qTx6qjpGvmXjXwXy3lZlxkoLy/+aAO3IHmNvj/I/tavss5HfjPN42oH7SlbH2R27o39a3YlUsCRw/CwHQI7X5fuV0A95AE1PoHWfvIblQJieHi+EF8M09GUa5aeFGKurCKz/Ude5iLaxPAYmYL6DtpH3DayfpPN2gOnJ/+tIJyC/VNe9WJel6aljMIG0CdD363ImQB+p0x7WSf1CaO851G7H+DtO01kskhdA0xPoSMICyM5X7xkeP3czUQeoDUdXYfurAN+p6ulvbbOKQ6Xa5hoFtLXRKzLbqw8Xja3dANypars+vg/A/GS13oCLaTj2JeD8HXG3vTnqjFNAWBe9UuU/2fxN0di+SkTiixigDTUl9sPfS6qJVJrURCprxIy4eorKyggAlgfQqZT4OaUBTj9E+g5YH4R1gug4/A2sP0X56bTIxC6EchfpOmp4HvZxnN5+UucfhPK/0etD4DXIW4Qyg5JJcTGVRdqNOv9wbBN8L4c9ARrpp8Pv6Xp/0ckVLbRzP1hSm3W712DJ8WwIkJoH0MlQ8liAGgHsfEDtoHnfmafGJFvV1ip7uP1VerpMT0AB2e71GRAdIshGORXj2MfhgNtuwO2DKn+U3AdW12eCXZT/EmXfpfHL2M9YNQ49JNX1ubuq+5uA7VXYXuQF0Kh7Lh2fjqfOoyp9rs6qaOHGYRj+Ll0OQOPvkCJwfM4EEADJb3X5fiUfgD5RQ9lNOomA7UWYnijvDj8FJ2D3hzxYv1v3YzMcxXpcbz9L+VjfHpA9Dvt8FmmfwfQDzpfhreA7qSysvt7Dkt7CQU+ic8dAq3JY/lQnUdoPddo/dVK/ENo8gdrtGO3/h85ikbwA+snVxwFw6UeVPX0VTjyNslHx+PKhoi7+sKhPdCgodhSO36ygOowy5Eg8Jl6k7ehrmfzYYDGjsxqQPAfrn+CYKeS9KSKrt0Xd60VTJ41n/r4qSwrHXvIcA+2oPwL0U81Hoa+kemMM9Vck9l/VfyxXgDCvJ9AOQKuHB1inoRI0bKAd69/GkoZhUJ2DVAUI6/dQHZjAN4q6Cb2Ptygf6/SE+hL4dfhzOAXTuOvdUOYOKot19QNXrA+E6Sk2TVWdBdDY3h3l63V5+tHnr3VWxQttPQp2ARr9QP3H8WwIkJoH0KlRqeMAahJ57vUZ608jLWrX2EOxfAjuJCjW2ZR/kwboqHK1jNkXqqfWr1M+wHswALDaCllzkP+JHCdTdsieR8M4sJyixjJXSff6jDIvwVljoB0BHr+C+vuh/P/gOAvhRViv+B8zo7+PQJ9ImsKbjL5cSuConqIaANIv7iZyVQxAY30z9A/9aO8L+OswvbWDniDvrSpAWKdx5NSP98CXaf8JPjUmxGDs63WsE3T/A74a2zTEg55wb4HlTbquGg+F9YFYp6fPBNomQF+sy43QSfTk+0ydNlkn9QuhvXdRux1j2/tVSPWAt0jMHYveb+QH0HUIWxOgaXgGPfH855d7Al5pbHS7elLsKBybpN7cEY4/AHi+THm2vASAe7qYbg9C+bmisbUDIH4Pyk4QM9qWoOxbaghCbewq8XQS57Ayc8N32vRNUf8/KPclA7ShupZTcOOSeeUiDXeJxF8QMxd4/tgMIPJ9uN/FM9pcCKDVWwuwvSX8MkxDNg6GHYB2X/mH9dv0fh6DaQwzmYD5DHgH7OtNLNPwE/AkmCD6U3gI8m7QdU/X+6Lx0vT0uxk2x0DTefxTl6Xj9YsfyTlCm0+htht+gfpEZ7MgAJk3QIekBWAzAfpZgOoaAO+eWD6M7XZAq3t9dvaD5QPwZcrnyEvSVenT7dPsQQDp560aqwPp99CENVhfgvW35ZlyG+xvvBq+MVKq67Oece8/8JdeAG0K53Cdehodsit+fDvaeooaukEAPVo9gX6NAOQGE0AAdGqoQX8T2q3A1weg1VghrBNAvwYvhQmg65AWx9K9E8T2L6gO0ugp8VexPBL1r8H64DZsY53GNL8J74+0H2PZSdtYH+AMGUGZeqQdBCj+M21jncZLmwB9OJxCOQLr76EMDfWgfYKU+tebVNDmx6mPHKPPztNZGanZCGOTxaxkVDzdtQrrl+ic/iEC39n0I0KPJ9CRaM/rGcPx5wDQzQqg62L/xHqHqG12fywCwP6paOwguJsqpi/6qoi0fxf5E8W0lp3Fo/HtUaddNCTeF9MWHyieWnWMqG9J4BjviamLBoonsE3v4G5omSOmrTgIcHyBmI1QjUQ/9QXoJwHdahjJand4VMWrPnapmi2TJv2hG476eFi9y9wQemQzABy9Co2edq6C+1U8o713og/8APpRvU3g+grcBhNAh3Ud933+WP+VTnsCy68mk+IHWL+jo0PsgSWNVU5iuRj+Lg0RwbIdXoKy22Pb+dEiza53MNbpB4a0r6WwC9B6rHQM5ajuSSjzDSy/BR+I9YofM5pOi0upXxyj3REsPd/WADDZqb+8hcMUoGwSPTn2fAI9SrrXZ8Dsc0hrVgAdko/AHVjv+TFfjfwJQZ0cLafSMA+U/S6geSK2d6Gnz1bIakPa+/L38kDUOwbrCezjfeQNRN8fTQANIJwjz5YHAbov0DD+qQnQcoTcGfuZhX2OR95B8Ik0rARpCezTd+x/pQj9cqkavkEAPUb1VyMByO9NAAGU0Y/l+t3XLGi3GhYBiP0fnUQAfZLuE2f8MgE09c8amIZwzIDpR4T7qQoQ1reE6f3MKadPsU5jnBXYYl9/N9LpSTZB8H/gbZFG+6c3cEid/yFM74GmMlnvDsX272D1I0Qy9kvlRursfiO0eZzTB1hfCfdAH2lafB8Ria9SP5JTb3WILVPjfPuLItEb1PjjJ6Nq3L3Skyt+JJq6aPiF+xYX9MvLCprVEI7odPQZ1TlY52be3VwX/bNobO8Uz2F/9DS6vuVNUZ/IfO1XF71RNLbJzDCP6HLR1LEA+3hP/TCOFIlfJ2a0p8TzlB9bgPzPsFyW9x5oR3Wxa9VPv8LR/vONWCQ2K3Njgb5/BvEaieV9m6KhbBV6xoGSZVj2m3hGe+/V7c68GQbC+smUBlB1xi9viW0aUmFhSUM4mnSdzA9eIazTq9WupXTH2F4AOFZP0rCvu4x0ej3dMvgLenMH0mjiFDcfZb+Al8JU7gB1AKirSxyI7TannGOk0Y8dj9LFKlZo46ycdk/UWa4AzQMBYpdZo623AXWP9YehAKbQ3hto/HGqKuVen1MjUz9SQypCsuctWyH5MvqnQw/hmEZPQgHY7vUZ25unq9N/Btx2qslpAMRIm4f9q+szHQd5kp4WA3jpx4cLFFCPy7w9A+vXIT9FdZG/AOuf4ZjL6AeGlE9ST6tDcjKgOkH7UUNGauQn6VBa/Vag0oU+mkU3FtT3uu2XiU4h9gZ0qHcgawhpBTj2ux8Sot3UD8egD9yZpOKZH/lR2jDaxnIT+NtI+x5MoHwg/AM47x2XSDsUPh4+ytwn1gmSj9R5+8B70j5h9TJyLAckhfiuzt8N28OwPMLJN4X0ryP9ODL+jv1yBkm0nYa+0GsBr4PzZ256KrqniCQ+U1+J04+z6lssUR8/TedWvsKxoWJ28hgx7fMddIoQUxcNVm/ieGpVzxt3CJbrEkeqJ8aRxP7op6M83wBB5WbJ4wG2P1TvbHZEsws+seL74lnkTVu9vwLjSNuhYvr76pWPSk8sO0zlh5FHoE5jon2GKKi/22x5jKht21WnVLbqEvvhb7VCxSkN4WhoSYnw6rzX2HV0iD0BIp8ZUEIwpoYS9AehrXQDcQzsvjYL6ztQGoD1G7SNfhmA7UNgGoO7DUwgS3XyHgyh7HeQfjx8LPw1nawgPJnEtdsWx2GdnhrTcenHguo1r1huge0jYKq7N+XTU2yku0+WkUaQTkNtjtH7obLkH8J5U1tXktA++gHhCrTbidMUlnlf89NX/wr26Adp9EO2apl5c08/kQLisfKY+Ki4e32ODY8NBvD+EIDmXp8JltE3R9ITY8Dt/sg7ioBWZ7tS5cbI46k+INy9PtPT/WRV8vvyXHl818iu/dWbN0LyMPs0270+d5/dfRjlo/7uncM7h+ryeddnHPtAOgbO/Tgcp1/c8KCd+6G/VqhYpSf9NTJFP9QkANkc4PEMlohvBdDkE3Q9Fmvjlm0PAJg8pp6Mqid7anmfzmWxNgyFYxeLWTRsI2Grp9D0hhTzpkcL0TvAssRjWCowIQNO+tWrK1kbvhCT6k0lRozS6/7y3hUMKDxDwTN9LU7jcENyYX94pzBr4xLicgx9K6DiNDO8Zf7qkfoH3gDmn1iZNz20YPkAQHqQymCxKkHheLV4uisztrSxnZ5Et6onrCzWhqCI3BbA/HLP8A0gRzjm+zaZdFpU58AJ/ViO45m1QQixuC1u8px3bCtj2zOe5Qh5ICA6mgUnVfIqnc1ibRACMN+mJlCh4RvnKoDOjudWIYYAnN2hBCzWhizc7O0MF/f1UWP7biIc/UzM7AaYAFBoPHQ4yhOusDYM0avqIvHX1PhwGlseiXe5Y8s9BEDZDXaHcWhA4XhmbRAigIZfc2IT612wZzzriUGmqqfQBCeZH7Mtk+fIfjkkkbVhyh5p/0KOlUl6NSBu+Jph940+LNZGJYDz6TD9mJNmbQzhpm8TneWvuug5mR++JTJjoWmmvaeaz9C5LNb6VSR2qmjqXCwa2uKiPt7rRBvptABy9AA0ACWFNI5n1gYhxOOp8GLEJr1nu2A8y9HyW+5TaOcHWtXyHp3NYm0QkqPkCfJceSli9GidVFiAE+9fyLNY60mIyf+BYzRen2wJ8WWn8RpBX82K76DeSzzHoq/HbfU0urH1cxGJ850ka8MQ/SiTXgtYhAAl9MO5t0yItizxeTLZ865jFmt9CjG5PWK0qHi2qqyb3afQmdexSVklx+lsFmvjEcBkJ0DKVJie8N3dYryDmMVaX0Is7g//14FnMrYXYzlUFymshtYzxIwOS8xozwzlUGNN4w/pXBZroxLghCb9oFe1mRDN8cza6KTfCrHYfU0YvcZttEzIGvlzXYTFClT2eHszvdo3pYX4Uw6kzIHddx2zWEEL8XeEJcQ7Zlzq2Pwjlr0P4SCpN3JEb1ZjoGlij7nYRSQ+VeeyWMFoWnwH0dB2sZjZcaWo7yx5rCeil17XdjOWLkBjm+OZFagQc/RtyMXweLjkeE6NSp1shaykGsoRgs9R46FfA1Dz77JYgYkmnsGN251yrFxo1VgNfX5VH6Dkag9Q+S+c915SFqvcQtz9Cv7CIyanYtm3i2uDvbUIx24XjW1fiMb257ImC2Gxyq3G1m8h/l5S337QBDfh+PPYHqxz+yzsYWvLErfBXwBenoM5nlmBCfFG78imKY/UDRzikH4KW3I8p0elL1Hv2qUn0ZlJPd5igGYFJZrdUYbkXHrThopDxKCsltfp7OIEMNnPEuJ9D2BphW/GenFfmbNYayHE2q7wfXCXRyzOhvPek1u0pi/bQ0xfmf+6xoeW7ihqW7NmfWSx1lqPrRiCm7a/iEh8uXpdHf2YVQ0lilmivvWbulTJoimo8Q8jL54BNzvCHM+sdarWVjEEcfUXAPNyxJ2CZzK2aWKftYpnAMsNcqz8UtbIhYCZX+lkFqtsah3VOgTx9heapdEdRkSmV9aVMrlPtxDfAkS/nAsuZKTTtNEXwzw2mlUWIc42Q5w15sYeGelhxJ47R/86U338J6Kpc55oaHsHgDNBhONq+nUWq2TN6BwmGuJjRUPLu+otMDTTIL3rmd5JTlOi18Xnihnxnhkd16EAMj8B0NCU1u/gSBNgjmfWWgkxNAzxNBZx9S7WXXB2jHQaGLfW8SzHyAPssXbeFPWAnH3TVemzaWy0PFdW9GyOrPJLjpL7IJbOsWqsd9UTZ7IJzzXy3a6arp4Ze/uiuBDbA1Qe8oIYMvIasHSnqXaE9O1gmhp7Xz93CLGHLp6n8UIMQJk9c+uYxnGHwb4TvqDMkNw6Hh6ii+cJedvABdsA7zldiE11lTzh/PbwqGN6b6/+c4Q8+jGnVz3X7aJn+tlcof5AmKYC96xLRv5Q2PcrMpT5em4d0/QGDCx9nwTr6dCpjFddOjeVh3PomfIZwja1vQ3L3Ji7Ac6bOn2tNc/eGmDzkfoC8ulkZja4+sQqpL0kItHxoi56imiMHyEa8Y/p0WU76Vr5otnj6uP7qHKNCW/XfuE/pmquvZmYtnwv//pIr4/thXJqSmFPPbbka951yVQf5zftS/+b30fWbAew27tgG+gJvp/G2wPUVNyF2jAjPkxMf99/wqb6FUPEU7qsV32ahpye6vqJpiGvW1mgDbQPnOP06b7/fsX06B4F61MfTf/M+99vXfsRoqG1VjS2L8ZNWeb9zvTUmeC5qcNWM2M2tj2nALsMwt63Buh8hKUCGzK2V8EvpdNqrOopME1FvS/yfOMZefQmBZrGel8/t7f7v48d9Tfr7BR7edUzvBfK+cYz9v81jzqm92lp8X+Yg/ztYJpu26uuMj3B18XzNH68GmtOU6h71iXj/IfB/p9HUj2x9axr2P/zKDMlecE2wHtOn17g88jG55F3Pcd7o4xnPCOPYqUWplfTuTFlGnk0hGhvXWWdi153Z1VbHxDkyGrZKUPyA6vKugvL3wC4j5Xj1HTLe9vD7aFeU1A7kmfJ3QjEfU1QVWA2RDXVdga8vOvDHWd3+L7BzD7N3lSdo0c9x/YIexggzp0WPleo/1Wveq7p/M6Uvt88yZFyW+orz7raOP4e+LP6/r6oY2THnl71XNP+cRxdPE90fp71DFM7dfE8yQvlVtRPXvUcUz/bx+b/GJD6Fud3hxwrl6gx9yY40zbgGVA9e63fRw5o2SwtxKWWxxhUckqI43VRpaTAPzQhXoa7Yfx78vUq7PM27CProoPtwci7B3mxnPK57kCZ57E8RFd1hX2cjfQFsKXLejkNL0DZal3NVZcQB2LfzyK/0yifZ5SJwg9iPesf2zKBDy8hbkTearN8rpHfheUrOIe89wqiX3+J/PeRT+fpWR/GDb9Yon/0OUBXVUIe3YA0wu26rJ8T2MdTWGaBOPa3GdKugL/U5TyNukks/wOfqKu6Qht+jP3MQx6V8apL55aC6e/0EOw+UdDHfwRLFWdY/wgeobPXvabbgwA3i9QPDNUTQsAOgQ+BNC0bWjJp9S1SzOx6V4QTJ+uaPYrEfww4modlEpYijBA2HdHLhpYvAcETxcwF2Rf5+tYhKPME8ls865MpPZJoA5w1KYDLVSR6vmhsXZIpm1PXqR+OpXCe72P9N7pWjxrih4sZ7S8jr9vzHGif5PrWVaIudpuYmzMMhsbz1sfvQT/FPOurfVAb4h2Ay+dFbTTv3y/6+WzR1L4Ax7G8z4Hqx9JiRscCHCfv36+oazlANLY8i3KdnvWdfdS3RLH+oKhbmv1h2bCMbqZuRBtXF6xPE540db6Cv1f2v1/6kWA49qZ6Fkc/VqXYoZiiWTBnW9huS4hw/GZVrkzCkQfhH9kiLPFp4G+UkfC7cF48I+1H9AQbTulynkb+l1hOhLPiGdtDkPcEli1Uzs8o04ZlE5wXz4D985G/xCyfa31+78N58Yy0w+GX4W7Ys772KuznNvRJ9ueRLQYj/R445lHHdAfKPI9l/ueRjc8jic8jqYY3eNUlp+EFKJsXz0g/APt+FstOXdbTKBOFH8R6VlwtW6Zupm5E3mqzfK6R3wW/inPIimfk0Y8E30S6Xwwl4JupnK5SFgFuqmkiCzVj4WiYvmon8KFtgA+g2gJgd8NxK2Q1IC3rpogmbUmH0pcgfyngSaK8VEvTmbQk4OmdVFXqVF3VFfZ5NPJepzKe9bVRZoWskjcQ5OmqSoD8HVHvIZxDwrd+Jr0dZeYAhA/QVV0hrwb/fYal5bmPTFoK5/BxemT6bF3NFeoegj57AWW6Cp0Djr8G63cDQrPGtBMU48blduyHJhTxrQ93osyL2D5cV3WVrkr/1hptfYS/U6E2WLqd5+lqrpBG8P80ynV41nf2US1jONfHcfOVdTOB9DNU7FAM6fhxYgr7pb/Nzej7dRfPgMr9ADB3wq0G1HyG9P11EYKezZEWcfKLMcqfrqsrYXu4Vzk/o/yjWLp33Vinp77LzDKFjLIrYfcRPdI2wfb9ueUKGeXH6OpK2D7Jq5yfUZ7G87r/0LBOs+x96FXWyyjbgWXW17PYnmKW6c3YxxW6qhK2jwLggia9y+caZd/A0v3qDuuDkOY5BMjPOOb/6epKSPsK0obDI+DyvpPcxp12fWI4wLRZPR2k90U3INQVTAOAaN0xgRG9U7ou0QNeNJ6apmOmPJqoxSyf656nkT/WtTMKxy5ST8BnELB71HNMY2fVOcRu1TUzerL1m9hnq3gGfzaveo7p/Og44dgnItLec+NET7/D8Yj4VxFtoKf09C7tunjWv1/Vh/QDOXXT4VHPMcEkHScSexQ3Lz1Pzeipbzi2TP0NvOo5boSpTDgGkF9lviGI/o73qn1TP3nVddzUlXmVYV08698v/q4nqTwFvx71HFMf0Q8BI7HZYtrnPR+UNKY5Eo8rWKbYob83nWt9Syf8eBBDg3C0TQA0w+FmrOOTobBR7i3YjWekEYC7Pw4r0lnxjPp/9Cjja5TPimdsfxNu9SrrZZT9BHbjGWmbYTuSW66QUT5rMhpsn+VVzs8o/yiWPZ9Hmae+y8wyhYyy9C2BG89Io7/jvbnlChnlx+rqStg+yaucn1F+NtzzeYS/A9LjHuU6AdyPYz2QoUGpmtRxgJsu+mGXO+kKmQDIMUEQ+Xw4ZE/RVZUAVYfBXS40+ZnqZ37A+J4JXgDJgag/R+2bynjVdUxwhjIAsawbU2yPtukHanT+XvUc6zbgePfqqkrY3hteo96Z7VXPsW4Dyn4O4N1TV8dnqpr58TE1LXUxbSCgrJZZD65wY3B61s2Ln3vaELZPs91vmOXv5a5I+0z9Hb3qmaa+qrbj9O2Drq6E+rcW1Qb9NBnlL9ZVlbA93G0DmcpkgP9xu8ouXzwDZH4A/wNw8yB8nE5WwvYWgKaZBEPFGvv6ra6uhG2aWc6zrJdR/jEsTYAeirQVZplCprKweRNAw0fcJ5/FGOXP0dWVsH2qVzk/ozy9JjALoNGPn3iV9TLKt8FH6OpK2KYfe3qW9/EEXVUJ28fl5Bc0jvcmlu6dKra3hf+dW87PKJvGMiue1osisUNFffxWwNGrIhyV6on0DNyfECwp6I1loCuS+DfgrefOXE3HHPt3Jg/QXciZH49JgNTPdO2MaqN/VaDlVSfXGXi8XdfMqLHtEOyzXcGrVx3T9GRdAfTqHoCejotcODZTjc2ldnrVc0wASe2oj2f9+xWRlpDqM2e4QiGrtiYeywLocGwovELMAqB71TFNgBuJrRRPtXxD16bv2wcg7WF9g5BfxzSdI8FtXTzr3y/acKoaq+w8Ofa1joVwbE4WQM+UW+LvcI8CaAXxLe8glu5HX52gSwQmnN23ATq3wq8CdiS28WmRb+T/G8uef7+Z6ZgpzbN8rmnfKJ8Vz+m0+ItXWT+jflY8Y/sQuN2rrJdRNhegt8B5zcwtV8io/ztdXQnbVV7l/Izyj2FpAvRQpK0wyxQyyq7E0o1nrNPwkYfNMr0Z5c/V1ZWwfapXOT+j/BzYBOgt4XuM/Hfg++HA4xnw9ltAzkyr2ooqiNSgqmDJAWoyAVGNvF5XUwI0fQ9OqvJmWS/TbIgh+R7W3eFN8tdyKxz3Bdp3Xvlca4DD+WY9xcY+zy+qPjkDdfdjzR1GAZDcH2kx1W6vOqZRBmU/xzm4wxBoOAPa8JSCV686pqkNtI+QrNLVlbDPM92bEK96pjNtiGQBdEjuipuTJWofXnVMZ44fw98y65sdbP+jqDaQ8bdMV6Uv1VWV7N/Z22O/T1o1Vgv29THKPShHycDjOU8AoR8A/mhyC/xbK2yU/SeWWeN8CLxQP5xb1ssoR8M0DtNVXaWFGId0fAJ613OMMkmUvRDrWeN8kE4/oPwgt7yXUa4Jy6xxY9geiH3QK9Y865hGORoakweOOK/fIQ+f8N71HKMMDaO5Bus9EAJ14kYC50ZPhT3rmUa5F7GfXXVVJaTTEIqbcst6GeXWwKfoqq6Q9nPse5VXHdMosxLtPQ/rvuOtAtc0uZVoiB0mZrReCFiaCc8HKEUzT4fblopwIv8F/+HmnyNvlZgB2PYELpigLQPQdyhgNdXQspOob3lWwVshAJ2pnu6+pcYym6ILbV30cuzDUk8+veqSG9TT5TZRH8v7eg9t+AHyF6ubBq+6ZDo3GsdbH/unGu5gSt1IRMPqHAu1IdOPC0Qt+jhXkeg49EWXgnSvumQ1pKYlCQC/EA3Pjpva5m/hZuIDdQyvumQ6N+rnulgT/g7Z4z5pfHkkOjXTxkJtwN9RvQ6xNf/Gj57mP7nme6J2zffL9SPBvgiwsxV8GHwhQSU8H+tR/AMk+F2K9bx4RtqJyCOgw6dQYaPsHVhm/47BFjshnW6TPOuYRjl6Ap4Vz0jfBBB+OdKzJovxMsq0YZn/dbXE55HlP27XNMrR0+OseMb2IKSHzXJ+RjkappH/eZTG55HE55FHHdMok4QvxHr255HE55GFzyOPOrlGuaZYLPs1ckgfiH1MzS3rZZRbimVePCONnuZ/D/4+1td7PHeN7NofAPQ7wM8jALQ3rCprEbYtBXVjAF018nXzySuJxh6j7EQHbvNAyzHyAFaJdCidPyRojDweELu0IITrcwCo3k9PrXVVpdj5scE4h6asJ+hezrThfcBv3htNcF4XIS/ZWxtQphNls79dg+Q4eRja90mvNxJ0DiFZC4jNHtZE44er5OOeNy6mkY++WoS/w5G6qivsdyTOob23NqAPaYjHX2n4ja6qJM+T+6L+28X0I2D9Wazn/c6D4qFrRNeBAGf/39IUlBD/DzNzGZN/ye5PAAAAAElFTkSuQmCC)\n", - "\n", - "**NOTE: The next cell won't actually run any code, it will just write its contents to a file. This is necessary because we have to run the code with the Nsight Compute profiler.**" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "I9Tz2hG-_tBj", - "outputId": "e52f41cb-f70b-4792-ea76-e78a554956f4" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Writing copy_blocked.py\n" - ] - } - ], - "source": [ - "%%writefile copy_blocked.py\n", - "\n", - "from numba import cuda\n", - "import cupy as cp\n", - "import cupyx as cpx\n", - "import sys\n", - "import os\n", - "\n", - "threads_per_block = 256 if len(sys.argv) < 2 else int(sys.argv[1])\n", - "items_per_thread = 64 if len(sys.argv) < 3 else int(sys.argv[2])\n", - "total_items = 2**28 if len(sys.argv) < 4 else int(sys.argv[3])\n", - "blocks = int(total_items / (threads_per_block * items_per_thread))\n", - "\n", - "src = cp.arange(total_items)\n", - "dst = cp.empty_like(src)\n", - "\n", - "@cuda.jit\n", - "def copy_blocked(src, dst, items_per_thread):\n", - " base = cuda.grid(1) * items_per_thread\n", - " for i in range(items_per_thread):\n", - " dst[base + i] = src[base + i]\n", - "\n", - "def launch():\n", - " copy_blocked[blocks, threads_per_block](src, dst, items_per_thread)\n", - "\n", - "if os.getenv(\"NV_COMPUTE_PROFILER_PERFWORKS_DIR\"): # Running under `ncu`.\n", - " launch() # `ncu` slows things down; so just launch once when running under it.\n", - "else:\n", - " D = cpx.profiler.benchmark(launch, n_repeat=15, n_warmup=1).gpu_times[0]\n", - " print(f\"{D.mean():.3g} s ± {(D.std() / D.mean()):.2%} (mean ± relative stdev of {D.size} runs)\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "TuR4yDV4H6IB" - }, - "source": [ - "Next, we'll actually run the code, by invoking the Nsight Compute `ncu` command line tool. The basic syntax for this tool is `ncu `, which will run ` ` while gathering a profile trace. We're passing it some flags that describe what data it should collect and where it should save the results.\n", - "\n", - "There is an overhead to running code under the profiler. Your program may execute noticably slower.\n", - "\n", - "When profiling and benchmarking, we need to run with a sufficient workload to get meaningful and representative results. If your runtime is too short, the profiler may not be able to report some metrics or the results may be inaccurate.\n", - "\n", - "**NOTE: To modify and rerun the above code, you must execute the previous cell to write the file and this one to execute it.**" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "5pyHvJtxVnDB", - "outputId": "87ad5a72-9244-4b21-9776-ecd93262f274" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "==PROF== Connected to process 1137 (/usr/bin/python3.11)\n", - "==PROF== Profiling \"copy_blocked[abi:v1,cw51cXTLSUwv1sDUaKthrqNgqqmjgOR3W3CwAkMXLaJtQYkOIgxJU0gCqOkEJoHkbttqdVhoqlspQGNFHSgJ5BnXagIA]\": 0%....50%....100% - 30 passes\n", - "==PROF== Disconnected from process 1137\n", - "==PROF== Report: /content/copy_blocked.ncu-rep\n" - ] - } - ], - "source": [ - "!ncu -f --kernel-name regex:copy_blocked --set full -o copy_blocked python copy_blocked.py\n", - "copy_blocked_csv = !ncu --import copy_blocked.ncu-rep --csv" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "ijEtNGHhpLPu" - }, - "source": [ - "Let's take a look at the profiling report on the kernel. When you run the next cell, a number of tabs will be displayed. The first tab will have a summary of all of the Nsight recommendations and advisories. Subsequent tabs will have more detailed information on a particular area.\n", - "\n", - "Remember, you can see even more information in the [Nsight Compute GUI](https://developer.nvidia.com/nsight-compute)." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 1176, - "referenced_widgets": [ - "b47fd7b6a0154f21bc182a368783f018", - "88670f4cd80d4823962f681c5db0b05c", - "efbee61b66c74e939e6ba9eb177b5104", - "da5ad97524f4499cb466a461c9e9a014", - "8bd862cae4a34d9eb8c6e0cbaba458fd", - "bd3d14f42d7c4e058cf86091a34e0495", - "fd81c004921e40acb4a5c89ce6b59345", - "5bc24b339fc8419da15158a611ccc7c0", - "e0520258747c44c3b3c03df575d08958", - "f02b7d8187324ecda1befc6000394fb8", - "a3c5590a108e4630ac47a029112fb8da", - "f8282e5d62ba4fdfbb055930aae07bb0", - "ddfcf54f17cb48b18c316d6fbc3f2df2", - "2566b43ad6544ff5b3535e1bcf848394", - "2be329446de8406aa008cba59ea2cfc0", - "8d0b84554fc14eb8aa1d73db53e7446c", - "7190fa5e9c1d4db5b53a19a00a43e29b", - "bca08631512947a9bb7678cae262caa1", - "520d86e9e9e142d2a9404675f4acab09", - "3b435d6a3d0148bc82ac4c72334d03ab", - "3d25f6fa3bd447d38ef0619d34129054", - "06d6f26f29494f45abcd43b287163314", - "5862752de4db4eafa9c1894aa8c85385", - "e2c606a3ec4b41bc85095363bed2a8bb", - "3fb5720992194190a644475c35bb3962", - "184b041944b040c1a715b4b892fe3405", - "81ee7055ea504002a61d86e34b9d2564", - "40e55caada4b4d4280e5d57bbf41daf7", - "8234d3926a0c413a996b1965cf862551", - "e2f9cbd9e16f49b7a84dc9bfe8808107", - "678ec7c648a94af9a6d16e4bc22d743c" - ] - }, - "id": "40w07iG5k6Vl", - "outputId": "f5d0becc-0285-4cb7-a78e-427cdc105454" - }, - "outputs": [ + "cells": [ { - "data": { - "application/javascript": "window[\"5ecc93f0-740d-11f0-abc7-0242ac1c000c\"] = google.colab.output.setIframeHeight(-1, true, {\"interactive\": true, \"maxHeight\": 99999});\n//# sourceURL=js_bae5335f4f", - "text/plain": [ - "" + "cell_type": "markdown", + "metadata": { + "id": "-JpGaP7-D_5W" + }, + "source": [ + "## Exercise - Kernel Authoring - Copy - SOLUTION\n", + "\n", + "In this exercise, we'll learn how to analyze and reason about the performance of CUDA kernels using the NVIDIA Nsight Compute profiler.\n", + "\n", + "We'll look at a few different ways of writing a simple kernel that copies items from one array to another.\n", + "\n", + "First, we need to make sure the Nsight Compute profiler, Nsightful, Numba CUDA, and CuPy are available in our notebook:" ] - }, - "metadata": {}, - "output_type": "display_data" }, { - "data": { - "text/html": [ - "\n", - " \n", - " " + "cell_type": "code", + "metadata": { + "collapsed": true, + "id": "AoHkvSPMC5Fs", + "jupyter": { + "outputs_hidden": true + } + }, + "source": [ + "import os\n", + "\n", + "if os.getenv(\"COLAB_RELEASE_TAG\") and not os.path.exists(\"/accelerated-computing-hub-installed\"): # If running in Google Colab:\n", + " print(\"Downloading NCU package.\")\n", + " !curl -s -L -O https://developer.download.nvidia.com/compute/cuda/repos/debian12/x86_64/nsight-compute-2025.2.1_2025.2.1.3-1_amd64.deb\n", + " print(\"Installing NCU package.\")\n", + " !dpkg -i nsight-compute-2025.2.1_2025.2.1.3-1_amd64.deb > /dev/null\n", + " !update-alternatives --install /opt/bin/ncu ncu /opt/nvidia/nsight-compute/2025.2.1/ncu 20250201 > /dev/null\n", + " print(\"Installing PIP packages.\")\n", + " !pip uninstall \"cuda-python\" --yes > /dev/null\n", + " !pip install \"numba-cuda\" \"cuda-cccl[test-cu12]\" \"nvtx\" \"nsightful[notebook] @ git+https://github.com/brycelelbach/nsightful.git\" > /dev/null 2>&1\n", + " open(\"/accelerated-computing-hub-installed\", \"a\").close()" ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" + "execution_count": null, + "outputs": [] }, { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "b47fd7b6a0154f21bc182a368783f018", - "version_major": 2, - "version_minor": 0 + "cell_type": "markdown", + "metadata": { + "id": "A1SfTQk0EwUl" }, - "text/plain": [ - "Dropdown(description='Kernel:', layout=Layout(width='400px'), options=('copy_blocked',), style=DescriptionStyl…" + "source": [ + "Now, we'll write our first kernel. Each thread will copy `items_per_thread` items from the `src` array to the `dst` array. We'll set the number of threads per block to a constant, `threads_per_block`. We'll calculate how many blocks to launch based on `items_per_thread` and `threads_per_block`. We use `cuda.grid(1)` to get the unique global 1D index of each thread.\n", + "\n", + "Each thread will copy a contiguous set of items, e.g. the items with indices `[base, base + items_per_thread)`:\n", + "\n", + "![image.png](data:image/png;base64,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)\n", + "\n", + "**NOTE: The next cell won't actually run any code, it will just write its contents to a file. This is necessary because we have to run the code with the Nsight Compute profiler.**" ] - }, - "metadata": {}, - "output_type": "display_data" }, { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "da5ad97524f4499cb466a461c9e9a014", - "version_major": 2, - "version_minor": 0 + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "I9Tz2hG-_tBj", + "outputId": "e52f41cb-f70b-4792-ea76-e78a554956f4" }, - "text/plain": [ - "Output()" + "source": [ + "%%writefile copy_blocked.py\n", + "\n", + "from numba import cuda\n", + "import cupy as cp\n", + "import cupyx as cpx\n", + "import sys\n", + "import os\n", + "\n", + "threads_per_block = 256 if len(sys.argv) < 2 else int(sys.argv[1])\n", + "items_per_thread = 64 if len(sys.argv) < 3 else int(sys.argv[2])\n", + "total_items = 2**28 if len(sys.argv) < 4 else int(sys.argv[3])\n", + "blocks = int(total_items / (threads_per_block * items_per_thread))\n", + "\n", + "src = cp.arange(total_items)\n", + "dst = cp.empty_like(src)\n", + "\n", + "@cuda.jit\n", + "def copy_blocked(src, dst, items_per_thread):\n", + " base = cuda.grid(1) * items_per_thread\n", + " for i in range(items_per_thread):\n", + " dst[base + i] = src[base + i]\n", + "\n", + "def launch(check):\n", + " copy_blocked[blocks, threads_per_block](src, dst, items_per_thread)\n", + "\n", + " if (check):\n", + " cp.testing.assert_array_equal(src, dst)\n", + "\n", + "if os.getenv(\"NV_COMPUTE_PROFILER_PERFWORKS_DIR\"): # Running under `ncu`.\n", + " launch(check=False) # `ncu` slows things down; so just launch once when running under it.\n", + "else:\n", + " launch(check=True)\n", + " D = cpx.profiler.benchmark(launch, (False,), n_repeat=15, n_warmup=1).gpu_times[0]\n", + " print(f\"{D.mean():.3g} s ± {(D.std() / D.mean()):.2%} (mean ± relative stdev of {D.size} runs)\")" + ], + "execution_count": 2, + "outputs": [ + { + "output_type": "stream", + "text": [ + "Writing copy_blocked.py\n" + ] + } ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "import nsightful\n", - "\n", - "nsightful.display_ncu_csv_in_notebook(copy_blocked_csv)" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "mL_9xT44qbMA" - }, - "source": [ - "In our kernel, each thread linearly accesses a chunk of contiguous memory, which is what you'd want on the CPU, but not on the GPU! Our access pattern looks like this:\n", - "\n", - "![image.png](data:image/png;base64,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)\n", - "\n", - "Threads are executed together in small groups called blocks. For load and store operations, if each thread in the block is operating on an array index that is nearby the array indices of the other threads, then the GPU can combine those contiguous memory operation into more efficient bulk operations. This is called memory coalescing, and it's essential for GPU performance.\n", - "\n", - "This means we want each thread to access a strided chunk of memory instead, like so:\n", - "\n", - "![image.png](data:image/png;base64,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)\n", - "\n", - "We call these access patterns blocked and striped respectively.\n", - "\n", - "![image.png](data:image/png;base64,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)\n", - "\n", - "We can no longer use the `cuda.grid` utility when implementing the striped arrangement. We need to access the hierarchical coordinates of our thread to compute the right step size:\n", - "\n", - "- `cuda.blockDim.x`: The number of threads per block.\n", - "- `cuda.blockIdx.x`: The global index of the current thread block.\n", - "- `cuda.threadIdx.x`: The local index of the current thread within this block." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "B5PBpaY2HnE0", - "outputId": "9bf8af36-f011-4eaa-ed28-c39abde2c315" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Writing copy_optimized.py\n" - ] - } - ], - "source": [ - "%%writefile copy_optimized.py\n", - "\n", - "from numba import cuda\n", - "import cupy as cp\n", - "import cupyx as cpx\n", - "import sys\n", - "import os\n", - "\n", - "threads_per_block = 256 if len(sys.argv) < 2 else int(sys.argv[1])\n", - "items_per_thread = 64 if len(sys.argv) < 3 else int(sys.argv[2])\n", - "total_items = 2**28 if len(sys.argv) < 4 else int(sys.argv[3])\n", - "blocks = int(total_items / (threads_per_block * items_per_thread))\n", - "\n", - "src = cp.arange(total_items)\n", - "dst = cp.empty_like(src)\n", - "\n", - "@cuda.jit\n", - "def copy_optimized(src, dst, items_per_thread):\n", - " bd = cuda.blockDim.x\n", - " bx = cuda.blockIdx.x\n", - " tx = cuda.threadIdx.x\n", - " items_per_block = bd * items_per_thread\n", - "\n", - " base = tx + bx * items_per_block\n", - " for i in range(0, items_per_block, bd):\n", - " dst[base + i] = src[base + i]\n", - "\n", - "def launch():\n", - " copy_optimized[blocks, threads_per_block](src, dst, items_per_thread)\n", - "\n", - "if os.getenv(\"NV_COMPUTE_PROFILER_PERFWORKS_DIR\"): # Running under `ncu`.\n", - " launch() # `ncu` slows things down; so just launch once when running under it.\n", - "else:\n", - " D = cpx.profiler.benchmark(launch, n_repeat=15, n_warmup=1).gpu_times[0]\n", - " print(f\"{D.mean():.3g} s ± {(D.std() / D.mean()):.2%} (mean ± relative stdev of {D.size} runs)\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "kC3Moh2m02-q" - }, - "source": [ - "Before we look at the report, let's compare the execution times of both versions:" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "kJ7viF-i06qd", - "outputId": "e2d84395-6324-4e55-c144-bc34399cc515" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "copy_blocked: 0.327 s ± 0.66% (mean ± relative stdev of 15 runs)\n", - "copy_optimized: 0.019 s ± 0.53% (mean ± relative stdev of 15 runs)\n", - "copy_optimized speedup over copy_blocked: 17.21\n" - ] - } - ], - "source": [ - "copy_blocked_duration = !python copy_blocked.py\n", - "copy_optimized_duration = !python copy_optimized.py\n", - "speedup = float(copy_blocked_duration[0].split()[0]) / float(copy_optimized_duration[0].split()[0])\n", - "\n", - "print(f\"copy_blocked: {copy_blocked_duration[0]}\")\n", - "print(f\"copy_optimized: {copy_optimized_duration[0]}\")\n", - "print(f\"copy_optimized speedup over copy_blocked: {speedup:.2f}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "mfrqUdzozGeU" - }, - "source": [ - "That's quite a difference! Now let's profile the optimized variant:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "zO_y6ObXV_wX", - "outputId": "8e643e49-6fbf-4b1e-ff58-d391700451ab" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "==PROF== Connected to process 1401 (/usr/bin/python3.11)\n", - "==PROF== Profiling \"copy_optimized[abi:v1,cw51cXTLSUwv1sDUaKthrqNgqqmjgOR3W3CwAkMXLaJtQYkOIgxJU0gCqOkEJoHkbttqdVhoqlspQGNFHSgJ5BnXagIA]\": 0%....50%....100% - 30 passes\n", - "==PROF== Disconnected from process 1401\n", - "==PROF== Report: /content/copy_optimized.ncu-rep\n" - ] - } - ], - "source": [ - "!ncu -f --kernel-name regex:copy_optimized --set full -o copy_optimized python copy_optimized.py\n", - "copy_optimized_csv = !ncu --import copy_optimized.ncu-rep --csv" - ] - }, - { - "cell_type": "markdown", - "metadata": { - "id": "DjPJRzXTD6uF" - }, - "source": [ - "Now let's dive into the profile report:" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 821, - "referenced_widgets": [ - "6be7ca5f9141465cb4b5126001b6c298", - "731f455485da4a31a8455bc7e3cdf1a8", - "adfba531343149f7a215ec7ec30bf8e1", - "7bd502fafaad4a1d9d5c7db9c1d54b9e", - "16b4cfe3f86041eea5ee6471175ddce6", - "d7dd0302a2cb475388f8fb2c5cf92f5d", - "8713dc92860b464aa1ce100891e33ed7", - "e3027ae6d73b4bca9df34d8bd61155c3", - "c0ac7614bdb74083a784cad1babfd7f4", - "ad7d9090fefd443fb56b97437c207ee8", - "f3b8695ddc6a445ebc82dde76f8d1f79", - "40c9bccee18f489b8a60f0bcc66b380e", - "143463ba065a4b96a6adbe97880b6330", - "fbcda75ba8764222b2acf028e8478b78", - "84fd4b1277464e378c55ceb8b0628045", - "a7948ce618014b68a77c29a97b5f8089", - "cfffbaee02274c83ac30daeac3c63f6e", - "b298377cb334442fb3be8dd55cfe8989", - "a9bbc802cafc48aca6c853e0ebd6806e", - "d74423ab0b3d416187826deaba0a57ba", - "dbe2bf8c7add48d8bf6e1f13b603fd38", - "0495f43209454c7dade96e09491b0a59", - "3bdb7a5b68b0484ebfa4584fe4440cef", - "c8a882d190254c939506955708d0a0aa", - "4812fe63767e41cc9cf82c9de944b383", - "17149f4044374e30b0926ee8683a08b6", - "144d071405684ec3bb0a8259ae566518", - "8fa15d63997f485795c9541d5151a876", - "918774a98d094a6b851bbc1df702ca4d", - "b457bbb2890e4f6ea0d007d3d05d555b", - "7ed48d6abaab4e95a347fa5c7b76bfac" - ] }, - "id": "KjE0Vgu_zgs3", - "outputId": "632a4728-519d-48fa-938e-7a9777d52c89" - }, - "outputs": [ { - "data": { - "application/javascript": "window[\"6a0b3852-740d-11f0-abc7-0242ac1c000c\"] = google.colab.output.setIframeHeight(-1, true, {\"interactive\": true, \"maxHeight\": 99999});\n//# sourceURL=js_b772b58d4e", - "text/plain": [ - "" + "cell_type": "markdown", + "metadata": { + "id": "TuR4yDV4H6IB" + }, + "source": [ + "Next, we'll actually run the code, by invoking the Nsight Compute `ncu` command line tool. The basic syntax for this tool is `ncu `, which will run ` ` while gathering a profile trace. We're passing it some flags that describe what data it should collect and where it should save the results.\n", + "\n", + "There is an overhead to running code under the profiler. Your program may execute noticably slower.\n", + "\n", + "When profiling and benchmarking, we need to run with a sufficient workload to get meaningful and representative results. If your runtime is too short, the profiler may not be able to report some metrics or the results may be inaccurate.\n", + "\n", + "**NOTE: To modify and rerun the above code, you must execute the previous cell to write the file and this one to execute it.**" ] - }, - "metadata": {}, - "output_type": "display_data" }, { - "data": { - "text/html": [ - "\n", - " \n", - " " + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "5pyHvJtxVnDB", + "outputId": "87ad5a72-9244-4b21-9776-ecd93262f274" + }, + "source": [ + "!ncu -f --kernel-name regex:copy_blocked --set full -o copy_blocked python copy_blocked.py\n", + "copy_blocked_csv = !ncu --import copy_blocked.ncu-rep --csv" ], - "text/plain": [ - "" + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "text": [ + "==PROF== Connected to process 1137 (/usr/bin/python3.11)\n", + "==PROF== Profiling \"copy_blocked[abi:v1,cw51cXTLSUwv1sDUaKthrqNgqqmjgOR3W3CwAkMXLaJtQYkOIgxJU0gCqOkEJoHkbttqdVhoqlspQGNFHSgJ5BnXagIA]\": 0%....50%....100% - 30 passes\n", + "==PROF== Disconnected from process 1137\n", + "==PROF== Report: /content/copy_blocked.ncu-rep\n" + ] + } ] - }, - "metadata": {}, - "output_type": "display_data" }, { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "6be7ca5f9141465cb4b5126001b6c298", - "version_major": 2, - "version_minor": 0 + "cell_type": "markdown", + "metadata": { + "id": "ijEtNGHhpLPu" }, - "text/plain": [ - "Dropdown(description='Kernel:', layout=Layout(width='400px'), options=('copy_optimized',), style=DescriptionSt…" + "source": [ + "Let's take a look at the profiling report on the kernel. When you run the next cell, a number of tabs will be displayed. The first tab will have a summary of all of the Nsight recommendations and advisories. Subsequent tabs will have more detailed information on a particular area.\n", + "\n", + "Remember, you can see even more information in the [Nsight Compute GUI](https://developer.nvidia.com/nsight-compute)." ] - }, - "metadata": {}, - "output_type": "display_data" }, { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "7bd502fafaad4a1d9d5c7db9c1d54b9e", - "version_major": 2, - "version_minor": 0 + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1176, + "referenced_widgets": [ + "b47fd7b6a0154f21bc182a368783f018", + "88670f4cd80d4823962f681c5db0b05c", + "efbee61b66c74e939e6ba9eb177b5104", + "da5ad97524f4499cb466a461c9e9a014", + "8bd862cae4a34d9eb8c6e0cbaba458fd", + "bd3d14f42d7c4e058cf86091a34e0495", + "fd81c004921e40acb4a5c89ce6b59345", + "5bc24b339fc8419da15158a611ccc7c0", + "e0520258747c44c3b3c03df575d08958", + "f02b7d8187324ecda1befc6000394fb8", + "a3c5590a108e4630ac47a029112fb8da", + "f8282e5d62ba4fdfbb055930aae07bb0", + "ddfcf54f17cb48b18c316d6fbc3f2df2", + "2566b43ad6544ff5b3535e1bcf848394", + "2be329446de8406aa008cba59ea2cfc0", + "8d0b84554fc14eb8aa1d73db53e7446c", + "7190fa5e9c1d4db5b53a19a00a43e29b", + "bca08631512947a9bb7678cae262caa1", + "520d86e9e9e142d2a9404675f4acab09", + "3b435d6a3d0148bc82ac4c72334d03ab", + "3d25f6fa3bd447d38ef0619d34129054", + "06d6f26f29494f45abcd43b287163314", + "5862752de4db4eafa9c1894aa8c85385", + "e2c606a3ec4b41bc85095363bed2a8bb", + "3fb5720992194190a644475c35bb3962", + "184b041944b040c1a715b4b892fe3405", + "81ee7055ea504002a61d86e34b9d2564", + "40e55caada4b4d4280e5d57bbf41daf7", + "8234d3926a0c413a996b1965cf862551", + "e2f9cbd9e16f49b7a84dc9bfe8808107", + "678ec7c648a94af9a6d16e4bc22d743c" + ] + }, + "id": "40w07iG5k6Vl", + "outputId": "f5d0becc-0285-4cb7-a78e-427cdc105454" }, - "text/plain": [ - "Output()" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "nsightful.display_ncu_csv_in_notebook(copy_optimized_csv)" - ] - } - ], - "metadata": { - "accelerator": "GPU", - "colab": { - "gpuType": "T4", - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 - }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.10.12" - }, - "widgets": { - "application/vnd.jupyter.widget-state+json": { - "0495f43209454c7dade96e09491b0a59": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "06d6f26f29494f45abcd43b287163314": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "143463ba065a4b96a6adbe97880b6330": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_17149f4044374e30b0926ee8683a08b6", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Warp State\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Warp Cycles Per Issued Instruction | cycle | 49.09 |\n| Warp Cycles Per Executed Instruction | cycle | 49.09 |\n| Avg. Active Threads Per Warp | | 32 |\n| Avg. Not Predicated Off Threads Per Warp | | 31.89 |\n\n🔧 **OPTIMIZATION**: On average, each warp of this kernel spends 45.3 cycles being stalled waiting for a scoreboard dependency on a L1TEX (local, global, surface, texture) operation. Find the instruction producing the data being waited upon to identify the culprit. To reduce the number of cycles waiting on L1TEX data accesses verify the memory access patterns are optimal for the target architecture, attempt to increase cache hit rates by increasing data locality (coalescing), or by changing the cache configuration. Consider moving frequently used data to shared memory. This stall type represents about 92.3% of the total average of 49.1 cycles between issuing two instructions.\n*Estimated Speedup (global): 12.96%*\n\nℹ️ **INFO**: Check the Warp Stall Sampling (All Samples) table for the top stall locations in your source based on sampling data. The Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#metrics-reference) provides more details on each stall reason.\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "144d071405684ec3bb0a8259ae566518": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "16b4cfe3f86041eea5ee6471175ddce6": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "17149f4044374e30b0926ee8683a08b6": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "184b041944b040c1a715b4b892fe3405": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "2566b43ad6544ff5b3535e1bcf848394": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_81ee7055ea504002a61d86e34b9d2564", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Instruction\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Avg. Executed Instructions Per Scheduler | inst | 1,610,547.20 |\n| Executed Instructions | inst | 257,687,552 |\n| Avg. Issued Instructions Per Scheduler | inst | 1,610,606.20 |\n| Issued Instructions | inst | 257,696,992 |\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "2be329446de8406aa008cba59ea2cfc0": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_40e55caada4b4d4280e5d57bbf41daf7", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Launch\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Block Size | | 256 |\n| Function Cache Configuration | | CachePreferNone |\n| Grid Size | | 16,384 |\n| Registers Per Thread | register/thread | 32 |\n| Shared Memory Configuration Size | Kbyte | 32.77 |\n| Driver Shared Memory Per Block | byte/block | 0 |\n| Dynamic Shared Memory Per Block | byte/block | 0 |\n| Static Shared Memory Per Block | byte/block | 0 |\n| # SMs | SM | 40 |\n| Threads | thread | 4,194,304 |\n| Uses Green Context | | 0 |\n| Waves Per SM | | 102.40 |\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "3b435d6a3d0148bc82ac4c72334d03ab": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "3bdb7a5b68b0484ebfa4584fe4440cef": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "3d25f6fa3bd447d38ef0619d34129054": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "3fb5720992194190a644475c35bb3962": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "40c9bccee18f489b8a60f0bcc66b380e": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_4812fe63767e41cc9cf82c9de944b383", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Scheduler\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| One or More Eligible | % | 16.02 |\n| Issued Warp Per Scheduler | | 0.16 |\n| No Eligible | % | 83.98 |\n| Active Warps Per Scheduler | warp | 7.87 |\n| Eligible Warps Per Scheduler | warp | 0.27 |\n\n🔧 **OPTIMIZATION**: Every scheduler is capable of issuing one instruction per cycle, but for this kernel each scheduler only issues an instruction every 6.2 cycles. This might leave hardware resources underutilized and may lead to less optimal performance. Out of the maximum of 8 warps per scheduler, this kernel allocates an average of 7.87 active warps per scheduler, but only an average of 0.27 warps were eligible per cycle. Eligible warps are the subset of active warps that are ready to issue their next instruction. Every cycle with no eligible warp results in no instruction being issued and the issue slot remains unused. To increase the number of eligible warps, avoid possible load imbalances due to highly different execution durations per warp. Reducing stalls indicated on the Warp State Statistics and Source Counters sections can help, too.\n*Estimated Speedup (local): 12.96%*\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "40e55caada4b4d4280e5d57bbf41daf7": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "4812fe63767e41cc9cf82c9de944b383": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "520d86e9e9e142d2a9404675f4acab09": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "5862752de4db4eafa9c1894aa8c85385": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "5bc24b339fc8419da15158a611ccc7c0": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_3d25f6fa3bd447d38ef0619d34129054", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Speed Of Light\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| DRAM Frequency | Ghz | 5.00 |\n| SM Frequency | Mhz | 585.00 |\n| Elapsed Cycles | cycle | 201,083,989 |\n| Memory Throughput | % | 61.70 |\n| DRAM Throughput | % | 61.70 |\n| Duration | ms | 343.73 |\n| L1/TEX Cache Throughput | % | 17.89 |\n| L2 Cache Throughput | % | 8.42 |\n| SM Active Cycles | cycle | 201,130,599.78 |\n| Compute (SM) Throughput | % | 1.28 |\n\n🔧 **OPTIMIZATION**: Memory is more heavily utilized than Compute: Look at the Memory Workload Analysis section to identify the DRAM bottleneck. Check memory replay (coalescing) metrics to make sure you're efficiently utilizing the bytes transferred. Also consider whether it is possible to do more work per memory access (kernel fusion) or whether there are values you can (re)compute.\n\nℹ️ **INFO**: The ratio of peak float (fp32) to double (fp64) performance on this device is 32:1. The kernel achieved 0% of this device's fp32 peak performance and 0% of its fp64 peak performance. See the Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#roofline) for more details on roofline analysis.\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "678ec7c648a94af9a6d16e4bc22d743c": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "6be7ca5f9141465cb4b5126001b6c298": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DropdownModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DropdownModel", - "_options_labels": [ - "copy_optimized" + "source": [ + "import nsightful\n", + "\n", + "nsightful.display_ncu_csv_in_notebook(copy_blocked_csv)" ], - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "DropdownView", - "description": "Kernel:", - "description_tooltip": null, - "disabled": false, - "index": 0, - "layout": "IPY_MODEL_731f455485da4a31a8455bc7e3cdf1a8", - "style": "IPY_MODEL_adfba531343149f7a215ec7ec30bf8e1" - } - }, - "7190fa5e9c1d4db5b53a19a00a43e29b": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_e2f9cbd9e16f49b7a84dc9bfe8808107", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Occupancy\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Block Limit SM | block | 16 |\n| Block Limit Registers | block | 8 |\n| Block Limit Shared Mem | block | 16 |\n| Block Limit Warps | block | 4 |\n| Theoretical Active Warps per SM | warp | 32 |\n| Theoretical Occupancy | % | 100 |\n| Achieved Occupancy | % | 97.54 |\n| Achieved Active Warps Per SM | warp | 31.21 |\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "731f455485da4a31a8455bc7e3cdf1a8": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": "400px" - } - }, - "7bd502fafaad4a1d9d5c7db9c1d54b9e": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_16b4cfe3f86041eea5ee6471175ddce6", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "# copy_optimized", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "d7dd0302a2cb475388f8fb2c5cf92f5d", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": "Tab(children=(Output(), Output(), Output(), Output(), Output(), Output(), Output(), Output(), Output(), Output…" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "7ed48d6abaab4e95a347fa5c7b76bfac": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "81ee7055ea504002a61d86e34b9d2564": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "8234d3926a0c413a996b1965cf862551": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "84fd4b1277464e378c55ceb8b0628045": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_8fa15d63997f485795c9541d5151a876", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Launch\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Block Size | | 256 |\n| Function Cache Configuration | | CachePreferNone |\n| Grid Size | | 16,384 |\n| Registers Per Thread | register/thread | 35 |\n| Shared Memory Configuration Size | Kbyte | 32.77 |\n| Driver Shared Memory Per Block | byte/block | 0 |\n| Dynamic Shared Memory Per Block | byte/block | 0 |\n| Static Shared Memory Per Block | byte/block | 0 |\n| # SMs | SM | 40 |\n| Threads | thread | 4,194,304 |\n| Uses Green Context | | 0 |\n| Waves Per SM | | 102.40 |\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "8713dc92860b464aa1ce100891e33ed7": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_d74423ab0b3d416187826deaba0a57ba", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Summary\n\n### Speed Of Light\n\nℹ️ **INFO**: The kernel is utilizing greater than 80.0% of the available compute or memory performance of the device. To further improve performance, work will likely need to be shifted from the most utilized to another unit. Start by analyzing DRAM in the Memory Workload Analysis section.\n\nℹ️ **INFO**: The ratio of peak float (fp32) to double (fp64) performance on this device is 32:1. The kernel achieved 0% of this device's fp32 peak performance and 0% of its fp64 peak performance. See the Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#roofline) for more details on roofline analysis.\n\n### Memory Workload\n\n⚠️ **WARNING**: The optional metric lts__average_gcomp_input_sector_success_rate.pct could not be found. Collecting it as an additional metric could enable the rule to provide more guidance.\n\n### Compute Workload\n\nℹ️ **INFO**: ALU is the highest-utilized pipeline (23.8%) based on active cycles, taking into account the rates of its different instructions. It executes integer and logic operations. It is well-utilized, but should not be a bottleneck.\n\n### Scheduler\n\n🔧 **OPTIMIZATION**: Every scheduler is capable of issuing one instruction per cycle, but for this kernel each scheduler only issues an instruction every 6.2 cycles. This might leave hardware resources underutilized and may lead to less optimal performance. Out of the maximum of 8 warps per scheduler, this kernel allocates an average of 7.87 active warps per scheduler, but only an average of 0.27 warps were eligible per cycle. Eligible warps are the subset of active warps that are ready to issue their next instruction. Every cycle with no eligible warp results in no instruction being issued and the issue slot remains unused. To increase the number of eligible warps, avoid possible load imbalances due to highly different execution durations per warp. Reducing stalls indicated on the Warp State Statistics and Source Counters sections can help, too.\n*Estimated Speedup (local): 12.96%*\n\n### Warp State\n\n🔧 **OPTIMIZATION**: On average, each warp of this kernel spends 45.3 cycles being stalled waiting for a scoreboard dependency on a L1TEX (local, global, surface, texture) operation. Find the instruction producing the data being waited upon to identify the culprit. To reduce the number of cycles waiting on L1TEX data accesses verify the memory access patterns are optimal for the target architecture, attempt to increase cache hit rates by increasing data locality (coalescing), or by changing the cache configuration. Consider moving frequently used data to shared memory. This stall type represents about 92.3% of the total average of 49.1 cycles between issuing two instructions.\n*Estimated Speedup (global): 12.96%*\n\nℹ️ **INFO**: Check the Warp Stall Sampling (All Samples) table for the top stall locations in your source based on sampling data. The Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#metrics-reference) provides more details on each stall reason.\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "88670f4cd80d4823962f681c5db0b05c": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": "400px" - } - }, - "8bd862cae4a34d9eb8c6e0cbaba458fd": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "8d0b84554fc14eb8aa1d73db53e7446c": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_8234d3926a0c413a996b1965cf862551", - "msg_id": "", + "execution_count": 4, "outputs": [ - { - "data": { - "text/markdown": "## PM Sampling\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Maximum Buffer Size | Mbyte | 3.47 |\n| Dropped Samples | sample | 0 |\n| Maximum Sampling Interval | cycle | 640,000 |\n| # Pass Groups | | 1 |\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "8fa15d63997f485795c9541d5151a876": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "918774a98d094a6b851bbc1df702ca4d": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "a3c5590a108e4630ac47a029112fb8da": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_e2c606a3ec4b41bc85095363bed2a8bb", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Compute & Memory Distribution\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Average DRAM Active Cycles | cycle | 1,059,639,779 |\n| Total DRAM Elapsed Cycles | cycle | 13,739,264,000 |\n| Average L1 Active Cycles | cycle | 201,130,599.78 |\n| Total L1 Elapsed Cycles | cycle | 8,048,353,288 |\n| Average L2 Active Cycles | cycle | 291,798,067.81 |\n| Total L2 Elapsed Cycles | cycle | 9,404,542,720 |\n| Average SM Active Cycles | cycle | 201,130,599.78 |\n| Total SM Elapsed Cycles | cycle | 8,048,353,288 |\n| Average SMSP Active Cycles | cycle | 201,107,006.62 |\n| Total SMSP Elapsed Cycles | cycle | 32,193,413,152 |\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "a7948ce618014b68a77c29a97b5f8089": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_918774a98d094a6b851bbc1df702ca4d", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## PM Sampling\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Maximum Buffer Size | Mbyte | 3.28 |\n| Dropped Samples | sample | 0 |\n| Maximum Sampling Interval | cycle | 160,000 |\n| # Pass Groups | | 1 |\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "a9bbc802cafc48aca6c853e0ebd6806e": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "ad7d9090fefd443fb56b97437c207ee8": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_3bdb7a5b68b0484ebfa4584fe4440cef", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Compute Workload\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Executed Ipc Active | inst/cycle | 0.64 |\n| Executed Ipc Elapsed | inst/cycle | 0.64 |\n| Issue Slots Busy | % | 16.02 |\n| Issued Ipc Active | inst/cycle | 0.64 |\n| SM Busy | % | 23.83 |\n\nℹ️ **INFO**: ALU is the highest-utilized pipeline (23.8%) based on active cycles, taking into account the rates of its different instructions. It executes integer and logic operations. It is well-utilized, but should not be a bottleneck.\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "adfba531343149f7a215ec7ec30bf8e1": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "initial" - } - }, - "b298377cb334442fb3be8dd55cfe8989": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_7ed48d6abaab4e95a347fa5c7b76bfac", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Source Counters\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Branch Instructions Ratio | % | 0.01 |\n| Branch Instructions | inst | 3,014,656 |\n| Branch Efficiency | % | 100 |\n| Avg. Divergent Branches | | 0 |\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } + { + "output_type": "display_data", + "data": { + "application/javascript": "window[\"5ecc93f0-740d-11f0-abc7-0242ac1c000c\"] = google.colab.output.setIframeHeight(-1, true, {\"interactive\": true, \"maxHeight\": 99999});\n//# sourceURL=js_bae5335f4f", + "text/plain": [ + "" + ] + } + }, + { + "output_type": "display_data", + "data": { + "text/html": [ + "\n", + " \n", + " " + ], + "text/plain": [ + "" + ] + } + }, + { + "output_type": "display_data", + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "b47fd7b6a0154f21bc182a368783f018", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Dropdown(description='Kernel:', layout=Layout(width='400px'), options=('copy_blocked',), style=DescriptionStyl…" + ] + } + }, + { + "output_type": "display_data", + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "da5ad97524f4499cb466a461c9e9a014", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Output()" + ] + } + } ] - } }, - "b457bbb2890e4f6ea0d007d3d05d555b": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "b47fd7b6a0154f21bc182a368783f018": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DropdownModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DropdownModel", - "_options_labels": [ - "copy_blocked" - ], - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "DropdownView", - "description": "Kernel:", - "description_tooltip": null, - "disabled": false, - "index": 0, - "layout": "IPY_MODEL_88670f4cd80d4823962f681c5db0b05c", - "style": "IPY_MODEL_efbee61b66c74e939e6ba9eb177b5104" - } - }, - "bca08631512947a9bb7678cae262caa1": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_678ec7c648a94af9a6d16e4bc22d743c", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Source Counters\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Branch Instructions Ratio | % | 0.01 |\n| Branch Instructions | inst | 2,490,368 |\n| Branch Efficiency | % | 100 |\n| Avg. Divergent Branches | | 0 |\n\n🔧 **OPTIMIZATION**: This kernel has uncoalesced global accesses resulting in a total of 402653184 excessive sectors (75% of the total 536870912 sectors). Check the L2 Theoretical Sectors Global Excessive table for the primary source locations. The CUDA Programming Guide (https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#device-memory-accesses) has additional information on reducing uncoalesced device memory accesses.\n*Estimated Speedup (global): 74.47%*\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } + { + "cell_type": "markdown", + "metadata": { + "id": "mL_9xT44qbMA" + }, + "source": [ + "In our kernel, each thread linearly accesses a chunk of contiguous memory, which is what you'd want on the CPU, but not on the GPU! Our access pattern looks like this:\n", + "\n", + "![image.png](data:image/png;base64,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)\n", + "\n", + "Threads are executed together in small groups called blocks. For load and store operations, if each thread in the block is operating on an array index that is nearby the array indices of the other threads, then the GPU can combine those contiguous memory operation into more efficient bulk operations. This is called memory coalescing, and it's essential for GPU performance.\n", + "\n", + "This means we want each thread to access a strided chunk of memory instead, like so:\n", + "\n", + "![image.png](data:image/png;base64,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)\n", + "\n", + "We call these access patterns blocked and striped respectively.\n", + "\n", + "![image.png](data:image/png;base64,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)\n", + "\n", + "We can no longer use the `cuda.grid` utility when implementing the striped arrangement. We need to access the hierarchical coordinates of our thread to compute the right step size:\n", + "\n", + "- `cuda.blockDim.x`: The number of threads per block.\n", + "- `cuda.blockIdx.x`: The global index of the current thread block.\n", + "- `cuda.threadIdx.x`: The local index of the current thread within this block." ] - } }, - "bd3d14f42d7c4e058cf86091a34e0495": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "TabModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "TabModel", - "_titles": { - "0": "Summary", - "1": "Speed Of Light", - "2": "Memory Workload", - "3": "Compute Workload", - "4": "Compute & Memory Distribution", - "5": "Scheduler", - "6": "Warp State", - "7": "Instruction", - "8": "Launch", - "9": "PM Sampling", - "10": "Occupancy", - "11": "Source Counters" + { + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "B5PBpaY2HnE0", + "outputId": "9bf8af36-f011-4eaa-ed28-c39abde2c315" }, - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "TabView", - "box_style": "", - "children": [ - "IPY_MODEL_fd81c004921e40acb4a5c89ce6b59345", - "IPY_MODEL_5bc24b339fc8419da15158a611ccc7c0", - "IPY_MODEL_e0520258747c44c3b3c03df575d08958", - "IPY_MODEL_f02b7d8187324ecda1befc6000394fb8", - "IPY_MODEL_a3c5590a108e4630ac47a029112fb8da", - "IPY_MODEL_f8282e5d62ba4fdfbb055930aae07bb0", - "IPY_MODEL_ddfcf54f17cb48b18c316d6fbc3f2df2", - "IPY_MODEL_2566b43ad6544ff5b3535e1bcf848394", - "IPY_MODEL_2be329446de8406aa008cba59ea2cfc0", - "IPY_MODEL_8d0b84554fc14eb8aa1d73db53e7446c", - "IPY_MODEL_7190fa5e9c1d4db5b53a19a00a43e29b", - "IPY_MODEL_bca08631512947a9bb7678cae262caa1" + "source": [ + "%%writefile copy_optimized.py\n", + "\n", + "from numba import cuda\n", + "import cupy as cp\n", + "import cupyx as cpx\n", + "import sys\n", + "import os\n", + "\n", + "threads_per_block = 256 if len(sys.argv) < 2 else int(sys.argv[1])\n", + "items_per_thread = 64 if len(sys.argv) < 3 else int(sys.argv[2])\n", + "total_items = 2**28 if len(sys.argv) < 4 else int(sys.argv[3])\n", + "blocks = int(total_items / (threads_per_block * items_per_thread))\n", + "\n", + "src = cp.arange(total_items)\n", + "dst = cp.empty_like(src)\n", + "\n", + "@cuda.jit\n", + "def copy_optimized(src, dst, items_per_thread):\n", + " bd = cuda.blockDim.x\n", + " bx = cuda.blockIdx.x\n", + " tx = cuda.threadIdx.x\n", + " items_per_block = bd * items_per_thread\n", + "\n", + " base = tx + bx * items_per_block\n", + " for i in range(0, items_per_block, bd):\n", + " dst[base + i] = src[base + i]\n", + "\n", + "def launch(check):\n", + " copy_optimized[blocks, threads_per_block](src, dst, items_per_thread)\n", + "\n", + " if (check):\n", + " cp.testing.assert_array_equal(src, dst)\n", + "\n", + "if os.getenv(\"NV_COMPUTE_PROFILER_PERFWORKS_DIR\"): # Running under `ncu`.\n", + " launch(check=False) # `ncu` slows things down; so just launch once when running under it.\n", + "else:\n", + " launch(check=True)\n", + " D = cpx.profiler.benchmark(launch, (False,), n_repeat=15, n_warmup=1).gpu_times[0]\n", + " print(f\"{D.mean():.3g} s ± {(D.std() / D.mean()):.2%} (mean ± relative stdev of {D.size} runs)\")" ], - "layout": "IPY_MODEL_520d86e9e9e142d2a9404675f4acab09", - "selected_index": 0 - } - }, - "c0ac7614bdb74083a784cad1babfd7f4": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_0495f43209454c7dade96e09491b0a59", - "msg_id": "", + "execution_count": 5, "outputs": [ - { - "data": { - "text/markdown": "## Memory Workload\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Memory Throughput | Gbyte/s | 278.16 |\n| Mem Busy | % | 27.59 |\n| Max Bandwidth | % | 87.04 |\n| L1/TEX Hit Rate | % | 0 |\n| L2 Hit Rate | % | 50.00 |\n| Mem Pipes Busy | % | 8.13 |\n\n⚠️ **WARNING**: The optional metric lts__average_gcomp_input_sector_success_rate.pct could not be found. Collecting it as an additional metric could enable the rule to provide more guidance.\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } + { + "output_type": "stream", + "text": [ + "Writing copy_optimized.py\n" + ] + } ] - } - }, - "c8a882d190254c939506955708d0a0aa": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } }, - "cfffbaee02274c83ac30daeac3c63f6e": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_b457bbb2890e4f6ea0d007d3d05d555b", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Occupancy\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Block Limit SM | block | 16 |\n| Block Limit Registers | block | 6 |\n| Block Limit Shared Mem | block | 16 |\n| Block Limit Warps | block | 4 |\n| Theoretical Active Warps per SM | warp | 32 |\n| Theoretical Occupancy | % | 100 |\n| Achieved Occupancy | % | 98.34 |\n| Achieved Active Warps Per SM | warp | 31.47 |\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } + { + "cell_type": "markdown", + "metadata": { + "id": "kC3Moh2m02-q" + }, + "source": [ + "Before we look at the report, let's compare the execution times of both versions:" ] - } }, - "d74423ab0b3d416187826deaba0a57ba": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "d7dd0302a2cb475388f8fb2c5cf92f5d": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "TabModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "TabModel", - "_titles": { - "0": "Summary", - "1": "Speed Of Light", - "2": "Memory Workload", - "3": "Compute Workload", - "4": "Compute & Memory Distribution", - "5": "Scheduler", - "6": "Warp State", - "7": "Instruction", - "8": "Launch", - "9": "PM Sampling", - "10": "Occupancy", - "11": "Source Counters" + { + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "kJ7viF-i06qd", + "outputId": "e2d84395-6324-4e55-c144-bc34399cc515" }, - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "TabView", - "box_style": "", - "children": [ - "IPY_MODEL_8713dc92860b464aa1ce100891e33ed7", - "IPY_MODEL_e3027ae6d73b4bca9df34d8bd61155c3", - "IPY_MODEL_c0ac7614bdb74083a784cad1babfd7f4", - "IPY_MODEL_ad7d9090fefd443fb56b97437c207ee8", - "IPY_MODEL_f3b8695ddc6a445ebc82dde76f8d1f79", - "IPY_MODEL_40c9bccee18f489b8a60f0bcc66b380e", - "IPY_MODEL_143463ba065a4b96a6adbe97880b6330", - "IPY_MODEL_fbcda75ba8764222b2acf028e8478b78", - "IPY_MODEL_84fd4b1277464e378c55ceb8b0628045", - "IPY_MODEL_a7948ce618014b68a77c29a97b5f8089", - "IPY_MODEL_cfffbaee02274c83ac30daeac3c63f6e", - "IPY_MODEL_b298377cb334442fb3be8dd55cfe8989" + "source": [ + "copy_blocked_duration = !python copy_blocked.py\n", + "copy_optimized_duration = !python copy_optimized.py\n", + "speedup = float(copy_blocked_duration[0].split()[0]) / float(copy_optimized_duration[0].split()[0])\n", + "\n", + "print(f\"copy_blocked: {copy_blocked_duration[0]}\")\n", + "print(f\"copy_optimized: {copy_optimized_duration[0]}\")\n", + "print(f\"copy_optimized speedup over copy_blocked: {speedup:.2f}\")" ], - "layout": "IPY_MODEL_a9bbc802cafc48aca6c853e0ebd6806e", - "selected_index": 0 - } - }, - "da5ad97524f4499cb466a461c9e9a014": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_8bd862cae4a34d9eb8c6e0cbaba458fd", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "# copy_blocked", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "bd3d14f42d7c4e058cf86091a34e0495", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": "Tab(children=(Output(), Output(), Output(), Output(), Output(), Output(), Output(), Output(), Output(), Output…" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "dbe2bf8c7add48d8bf6e1f13b603fd38": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "ddfcf54f17cb48b18c316d6fbc3f2df2": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_184b041944b040c1a715b4b892fe3405", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Warp State\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Warp Cycles Per Issued Instruction | cycle | 974.92 |\n| Warp Cycles Per Executed Instruction | cycle | 974.96 |\n| Avg. Active Threads Per Warp | | 32 |\n| Avg. Not Predicated Off Threads Per Warp | | 31.95 |\n\n🔧 **OPTIMIZATION**: On average, each warp of this kernel spends 503.1 cycles being stalled waiting for a scoreboard dependency on a L1TEX (local, global, surface, texture) operation. Find the instruction producing the data being waited upon to identify the culprit. To reduce the number of cycles waiting on L1TEX data accesses verify the memory access patterns are optimal for the target architecture, attempt to increase cache hit rates by increasing data locality (coalescing), or by changing the cache configuration. Consider moving frequently used data to shared memory. This stall type represents about 51.6% of the total average of 974.9 cycles between issuing two instructions.\n*Estimated Speedup (global): 38.3%*\n\n🔧 **OPTIMIZATION**: On average, each warp of this kernel spends 463.7 cycles being stalled waiting for the L1 instruction queue for local and global (LG) memory operations to be not full. Typically, this stall occurs only when executing local or global memory instructions extremely frequently. Avoid redundant global memory accesses. Try to avoid using thread-local memory by checking if dynamically indexed arrays are declared in local scope, of if the kernel has excessive register pressure causing by spills. If applicable, consider combining multiple lower-width memory operations into fewer wider memory operations and try interleaving memory operations and math instructions. This stall type represents about 47.6% of the total average of 974.9 cycles between issuing two instructions.\n*Estimated Speedup (global): 38.3%*\n\nℹ️ **INFO**: Check the Warp Stall Sampling (All Samples) table for the top stall locations in your source based on sampling data. The Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#metrics-reference) provides more details on each stall reason.\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "e0520258747c44c3b3c03df575d08958": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_06d6f26f29494f45abcd43b287163314", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Memory Workload\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Memory Throughput | Gbyte/s | 197.30 |\n| Mem Busy | % | 6.75 |\n| Max Bandwidth | % | 61.70 |\n| L1/TEX Hit Rate | % | 0.05 |\n| L2 Hit Rate | % | 2.36 |\n| Mem Pipes Busy | % | 1.28 |\n\n⚠️ **WARNING**: The optional metric lts__average_gcomp_input_sector_success_rate.pct could not be found. Collecting it as an additional metric could enable the rule to provide more guidance.\n\n🔧 **OPTIMIZATION**: The memory access pattern for global loads from DRAM might not be optimal. On average, only 8.0 of the 32 bytes transmitted per sector are utilized by each thread. This applies to the 99.6% of sectors missed in L2. This could possibly be caused by a stride between threads. Check the Source Counters section for uncoalesced global loads.\n*Estimated Speedup (global): 46.07%*\n\n🔧 **OPTIMIZATION**: The memory access pattern for global stores to DRAM might not be optimal. On average, only 8.0 of the 32 bytes transmitted per sector are utilized by each thread. This applies to the 95.6% of sectors missed in L2. This could possibly be caused by a stride between threads. Check the Source Counters section for uncoalesced global stores.\n*Estimated Speedup (global): 44.22%*\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "e2c606a3ec4b41bc85095363bed2a8bb": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "e2f9cbd9e16f49b7a84dc9bfe8808107": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "e3027ae6d73b4bca9df34d8bd61155c3": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_dbe2bf8c7add48d8bf6e1f13b603fd38", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Speed Of Light\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| DRAM Frequency | Ghz | 4.99 |\n| SM Frequency | Mhz | 585.00 |\n| Elapsed Cycles | cycle | 10,401,526 |\n| Memory Throughput | % | 87.04 |\n| DRAM Throughput | % | 87.04 |\n| Duration | ms | 17.78 |\n| L1/TEX Cache Throughput | % | 36.36 |\n| L2 Cache Throughput | % | 27.59 |\n| SM Active Cycles | cycle | 10,389,825.82 |\n| Compute (SM) Throughput | % | 23.80 |\n\nℹ️ **INFO**: The kernel is utilizing greater than 80.0% of the available compute or memory performance of the device. To further improve performance, work will likely need to be shifted from the most utilized to another unit. Start by analyzing DRAM in the Memory Workload Analysis section.\n\nℹ️ **INFO**: The ratio of peak float (fp32) to double (fp64) performance on this device is 32:1. The kernel achieved 0% of this device's fp32 peak performance and 0% of its fp64 peak performance. See the Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#roofline) for more details on roofline analysis.\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "efbee61b66c74e939e6ba9eb177b5104": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "initial" - } - }, - "f02b7d8187324ecda1befc6000394fb8": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_5862752de4db4eafa9c1894aa8c85385", - "msg_id": "", + "execution_count": 6, "outputs": [ - { - "data": { - "text/markdown": "## Compute Workload\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Executed Ipc Active | inst/cycle | 0.03 |\n| Executed Ipc Elapsed | inst/cycle | 0.03 |\n| Issue Slots Busy | % | 0.80 |\n| Issued Ipc Active | inst/cycle | 0.03 |\n| SM Busy | % | 1.11 |\n\n🔧 **OPTIMIZATION**: All compute pipelines are under-utilized. Either this kernel is very small or it doesn't issue enough warps per scheduler. Check the Launch Statistics and Scheduler Statistics sections for further details.\n*Estimated Speedup (local): 98.89%*\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } + { + "output_type": "stream", + "text": [ + "copy_blocked: 0.327 s ± 0.66% (mean ± relative stdev of 15 runs)\n", + "copy_optimized: 0.019 s ± 0.53% (mean ± relative stdev of 15 runs)\n", + "copy_optimized speedup over copy_blocked: 17.21\n" + ] + } ] - } }, - "f3b8695ddc6a445ebc82dde76f8d1f79": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_c8a882d190254c939506955708d0a0aa", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Compute & Memory Distribution\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Average DRAM Active Cycles | cycle | 77,278,172 |\n| Total DRAM Elapsed Cycles | cycle | 710,265,856 |\n| Average L1 Active Cycles | cycle | 10,389,825.82 |\n| Total L1 Elapsed Cycles | cycle | 415,992,616 |\n| Average L2 Active Cycles | cycle | 15,185,970.41 |\n| Total L2 Elapsed Cycles | cycle | 486,470,688 |\n| Average SM Active Cycles | cycle | 10,389,825.82 |\n| Total SM Elapsed Cycles | cycle | 415,992,616 |\n| Average SMSP Active Cycles | cycle | 10,389,220.56 |\n| Total SMSP Elapsed Cycles | cycle | 1,663,970,464 |\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } + { + "cell_type": "markdown", + "metadata": { + "id": "mfrqUdzozGeU" + }, + "source": [ + "That's quite a difference! Now let's profile the optimized variant:" ] - } }, - "f8282e5d62ba4fdfbb055930aae07bb0": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_3fb5720992194190a644475c35bb3962", - "msg_id": "", + { + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "zO_y6ObXV_wX", + "outputId": "8e643e49-6fbf-4b1e-ff58-d391700451ab" + }, + "source": [ + "!ncu -f --kernel-name regex:copy_optimized --set full -o copy_optimized python copy_optimized.py\n", + "copy_optimized_csv = !ncu --import copy_optimized.ncu-rep --csv" + ], + "execution_count": null, "outputs": [ - { - "data": { - "text/markdown": "## Scheduler\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| One or More Eligible | % | 0.80 |\n| Issued Warp Per Scheduler | | 0.01 |\n| No Eligible | % | 99.20 |\n| Active Warps Per Scheduler | warp | 7.81 |\n| Eligible Warps Per Scheduler | warp | 0.01 |\n\n🔧 **OPTIMIZATION**: Every scheduler is capable of issuing one instruction per cycle, but for this kernel each scheduler only issues an instruction every 124.9 cycles. This might leave hardware resources underutilized and may lead to less optimal performance. Out of the maximum of 8 warps per scheduler, this kernel allocates an average of 7.81 active warps per scheduler, but only an average of 0.01 warps were eligible per cycle. Eligible warps are the subset of active warps that are ready to issue their next instruction. Every cycle with no eligible warp results in no instruction being issued and the issue slot remains unused. To increase the number of eligible warps, avoid possible load imbalances due to highly different execution durations per warp. Reducing stalls indicated on the Warp State Statistics and Source Counters sections can help, too.\n*Estimated Speedup (local): 38.3%*\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } + { + "output_type": "stream", + "text": [ + "==PROF== Connected to process 1401 (/usr/bin/python3.11)\n", + "==PROF== Profiling \"copy_optimized[abi:v1,cw51cXTLSUwv1sDUaKthrqNgqqmjgOR3W3CwAkMXLaJtQYkOIgxJU0gCqOkEJoHkbttqdVhoqlspQGNFHSgJ5BnXagIA]\": 0%....50%....100% - 30 passes\n", + "==PROF== Disconnected from process 1401\n", + "==PROF== Report: /content/copy_optimized.ncu-rep\n" + ] + } ] - } }, - "fbcda75ba8764222b2acf028e8478b78": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_144d071405684ec3bb0a8259ae566518", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Instruction\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Avg. Executed Instructions Per Scheduler | inst | 1,664,614.40 |\n| Executed Instructions | inst | 266,338,304 |\n| Avg. Issued Instructions Per Scheduler | inst | 1,664,650.29 |\n| Issued Instructions | inst | 266,344,046 |\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } + { + "cell_type": "markdown", + "metadata": { + "id": "DjPJRzXTD6uF" + }, + "source": [ + "Now let's dive into the profile report:" ] - } }, - "fd81c004921e40acb4a5c89ce6b59345": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_3b435d6a3d0148bc82ac4c72334d03ab", - "msg_id": "", + { + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 821, + "referenced_widgets": [ + "6be7ca5f9141465cb4b5126001b6c298", + "731f455485da4a31a8455bc7e3cdf1a8", + "adfba531343149f7a215ec7ec30bf8e1", + "7bd502fafaad4a1d9d5c7db9c1d54b9e", + "16b4cfe3f86041eea5ee6471175ddce6", + "d7dd0302a2cb475388f8fb2c5cf92f5d", + "8713dc92860b464aa1ce100891e33ed7", + "e3027ae6d73b4bca9df34d8bd61155c3", + "c0ac7614bdb74083a784cad1babfd7f4", + "ad7d9090fefd443fb56b97437c207ee8", + "f3b8695ddc6a445ebc82dde76f8d1f79", + "40c9bccee18f489b8a60f0bcc66b380e", + "143463ba065a4b96a6adbe97880b6330", + "fbcda75ba8764222b2acf028e8478b78", + "84fd4b1277464e378c55ceb8b0628045", + "a7948ce618014b68a77c29a97b5f8089", + "cfffbaee02274c83ac30daeac3c63f6e", + "b298377cb334442fb3be8dd55cfe8989", + "a9bbc802cafc48aca6c853e0ebd6806e", + "d74423ab0b3d416187826deaba0a57ba", + "dbe2bf8c7add48d8bf6e1f13b603fd38", + "0495f43209454c7dade96e09491b0a59", + "3bdb7a5b68b0484ebfa4584fe4440cef", + "c8a882d190254c939506955708d0a0aa", + "4812fe63767e41cc9cf82c9de944b383", + "17149f4044374e30b0926ee8683a08b6", + "144d071405684ec3bb0a8259ae566518", + "8fa15d63997f485795c9541d5151a876", + "918774a98d094a6b851bbc1df702ca4d", + "b457bbb2890e4f6ea0d007d3d05d555b", + "7ed48d6abaab4e95a347fa5c7b76bfac" + ] + }, + "id": "KjE0Vgu_zgs3", + "outputId": "632a4728-519d-48fa-938e-7a9777d52c89" + }, + "source": [ + "nsightful.display_ncu_csv_in_notebook(copy_optimized_csv)" + ], + "execution_count": 8, "outputs": [ - { - "data": { - "text/markdown": "## Summary\n\n### Speed Of Light\n\n🔧 **OPTIMIZATION**: Memory is more heavily utilized than Compute: Look at the Memory Workload Analysis section to identify the DRAM bottleneck. Check memory replay (coalescing) metrics to make sure you're efficiently utilizing the bytes transferred. Also consider whether it is possible to do more work per memory access (kernel fusion) or whether there are values you can (re)compute.\n\nℹ️ **INFO**: The ratio of peak float (fp32) to double (fp64) performance on this device is 32:1. The kernel achieved 0% of this device's fp32 peak performance and 0% of its fp64 peak performance. See the Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#roofline) for more details on roofline analysis.\n\n### Memory Workload\n\n⚠️ **WARNING**: The optional metric lts__average_gcomp_input_sector_success_rate.pct could not be found. Collecting it as an additional metric could enable the rule to provide more guidance.\n\n🔧 **OPTIMIZATION**: The memory access pattern for global loads from DRAM might not be optimal. On average, only 8.0 of the 32 bytes transmitted per sector are utilized by each thread. This applies to the 99.6% of sectors missed in L2. This could possibly be caused by a stride between threads. Check the Source Counters section for uncoalesced global loads.\n*Estimated Speedup (global): 46.07%*\n\n🔧 **OPTIMIZATION**: The memory access pattern for global stores to DRAM might not be optimal. On average, only 8.0 of the 32 bytes transmitted per sector are utilized by each thread. This applies to the 95.6% of sectors missed in L2. This could possibly be caused by a stride between threads. Check the Source Counters section for uncoalesced global stores.\n*Estimated Speedup (global): 44.22%*\n\n### Compute Workload\n\n🔧 **OPTIMIZATION**: All compute pipelines are under-utilized. Either this kernel is very small or it doesn't issue enough warps per scheduler. Check the Launch Statistics and Scheduler Statistics sections for further details.\n*Estimated Speedup (local): 98.89%*\n\n### Scheduler\n\n🔧 **OPTIMIZATION**: Every scheduler is capable of issuing one instruction per cycle, but for this kernel each scheduler only issues an instruction every 124.9 cycles. This might leave hardware resources underutilized and may lead to less optimal performance. Out of the maximum of 8 warps per scheduler, this kernel allocates an average of 7.81 active warps per scheduler, but only an average of 0.01 warps were eligible per cycle. Eligible warps are the subset of active warps that are ready to issue their next instruction. Every cycle with no eligible warp results in no instruction being issued and the issue slot remains unused. To increase the number of eligible warps, avoid possible load imbalances due to highly different execution durations per warp. Reducing stalls indicated on the Warp State Statistics and Source Counters sections can help, too.\n*Estimated Speedup (local): 38.3%*\n\n### Warp State\n\n🔧 **OPTIMIZATION**: On average, each warp of this kernel spends 503.1 cycles being stalled waiting for a scoreboard dependency on a L1TEX (local, global, surface, texture) operation. Find the instruction producing the data being waited upon to identify the culprit. To reduce the number of cycles waiting on L1TEX data accesses verify the memory access patterns are optimal for the target architecture, attempt to increase cache hit rates by increasing data locality (coalescing), or by changing the cache configuration. Consider moving frequently used data to shared memory. This stall type represents about 51.6% of the total average of 974.9 cycles between issuing two instructions.\n*Estimated Speedup (global): 38.3%*\n\n🔧 **OPTIMIZATION**: On average, each warp of this kernel spends 463.7 cycles being stalled waiting for the L1 instruction queue for local and global (LG) memory operations to be not full. Typically, this stall occurs only when executing local or global memory instructions extremely frequently. Avoid redundant global memory accesses. Try to avoid using thread-local memory by checking if dynamically indexed arrays are declared in local scope, of if the kernel has excessive register pressure causing by spills. If applicable, consider combining multiple lower-width memory operations into fewer wider memory operations and try interleaving memory operations and math instructions. This stall type represents about 47.6% of the total average of 974.9 cycles between issuing two instructions.\n*Estimated Speedup (global): 38.3%*\n\nℹ️ **INFO**: Check the Warp Stall Sampling (All Samples) table for the top stall locations in your source based on sampling data. The Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#metrics-reference) provides more details on each stall reason.\n\n### Source Counters\n\n🔧 **OPTIMIZATION**: This kernel has uncoalesced global accesses resulting in a total of 402653184 excessive sectors (75% of the total 536870912 sectors). Check the L2 Theoretical Sectors Global Excessive table for the primary source locations. The CUDA Programming Guide (https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#device-memory-accesses) has additional information on reducing uncoalesced device memory accesses.\n*Estimated Speedup (global): 74.47%*\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } + { + "output_type": "display_data", + "data": { + "application/javascript": "window[\"6a0b3852-740d-11f0-abc7-0242ac1c000c\"] = google.colab.output.setIframeHeight(-1, true, {\"interactive\": true, \"maxHeight\": 99999});\n//# sourceURL=js_b772b58d4e", + "text/plain": [ + "" + ] + } + }, + { + "output_type": "display_data", + "data": { + "text/html": [ + "\n", + " \n", + " " + ], + "text/plain": [ + "" + ] + } + }, + { + "output_type": "display_data", + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "6be7ca5f9141465cb4b5126001b6c298", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Dropdown(description='Kernel:', layout=Layout(width='400px'), options=('copy_optimized',), style=DescriptionSt…" + ] + } + }, + { + "output_type": "display_data", + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "7bd502fafaad4a1d9d5c7db9c1d54b9e", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Output()" + ] + } + } ] - } } - } - } - }, - "nbformat": 4, - "nbformat_minor": 4 -} + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "gpuType": "T4", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.10.12" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "0495f43209454c7dade96e09491b0a59": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "06d6f26f29494f45abcd43b287163314": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "143463ba065a4b96a6adbe97880b6330": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_17149f4044374e30b0926ee8683a08b6", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Warp State\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Warp Cycles Per Issued Instruction | cycle | 49.09 |\n| Warp Cycles Per Executed Instruction | cycle | 49.09 |\n| Avg. Active Threads Per Warp | | 32 |\n| Avg. Not Predicated Off Threads Per Warp | | 31.89 |\n\n🔧 **OPTIMIZATION**: On average, each warp of this kernel spends 45.3 cycles being stalled waiting for a scoreboard dependency on a L1TEX (local, global, surface, texture) operation. Find the instruction producing the data being waited upon to identify the culprit. To reduce the number of cycles waiting on L1TEX data accesses verify the memory access patterns are optimal for the target architecture, attempt to increase cache hit rates by increasing data locality (coalescing), or by changing the cache configuration. Consider moving frequently used data to shared memory. This stall type represents about 92.3% of the total average of 49.1 cycles between issuing two instructions.\n*Estimated Speedup (global): 12.96%*\n\nℹ️ **INFO**: Check the Warp Stall Sampling (All Samples) table for the top stall locations in your source based on sampling data. The Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#metrics-reference) provides more details on each stall reason.\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "144d071405684ec3bb0a8259ae566518": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "16b4cfe3f86041eea5ee6471175ddce6": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "17149f4044374e30b0926ee8683a08b6": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "184b041944b040c1a715b4b892fe3405": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "2566b43ad6544ff5b3535e1bcf848394": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_81ee7055ea504002a61d86e34b9d2564", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Instruction\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Avg. Executed Instructions Per Scheduler | inst | 1,610,547.20 |\n| Executed Instructions | inst | 257,687,552 |\n| Avg. Issued Instructions Per Scheduler | inst | 1,610,606.20 |\n| Issued Instructions | inst | 257,696,992 |\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "2be329446de8406aa008cba59ea2cfc0": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_40e55caada4b4d4280e5d57bbf41daf7", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Launch\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Block Size | | 256 |\n| Function Cache Configuration | | CachePreferNone |\n| Grid Size | | 16,384 |\n| Registers Per Thread | register/thread | 32 |\n| Shared Memory Configuration Size | Kbyte | 32.77 |\n| Driver Shared Memory Per Block | byte/block | 0 |\n| Dynamic Shared Memory Per Block | byte/block | 0 |\n| Static Shared Memory Per Block | byte/block | 0 |\n| # SMs | SM | 40 |\n| Threads | thread | 4,194,304 |\n| Uses Green Context | | 0 |\n| Waves Per SM | | 102.40 |\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "3b435d6a3d0148bc82ac4c72334d03ab": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "3bdb7a5b68b0484ebfa4584fe4440cef": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "3d25f6fa3bd447d38ef0619d34129054": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "3fb5720992194190a644475c35bb3962": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "40c9bccee18f489b8a60f0bcc66b380e": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_4812fe63767e41cc9cf82c9de944b383", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Scheduler\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| One or More Eligible | % | 16.02 |\n| Issued Warp Per Scheduler | | 0.16 |\n| No Eligible | % | 83.98 |\n| Active Warps Per Scheduler | warp | 7.87 |\n| Eligible Warps Per Scheduler | warp | 0.27 |\n\n🔧 **OPTIMIZATION**: Every scheduler is capable of issuing one instruction per cycle, but for this kernel each scheduler only issues an instruction every 6.2 cycles. This might leave hardware resources underutilized and may lead to less optimal performance. Out of the maximum of 8 warps per scheduler, this kernel allocates an average of 7.87 active warps per scheduler, but only an average of 0.27 warps were eligible per cycle. Eligible warps are the subset of active warps that are ready to issue their next instruction. Every cycle with no eligible warp results in no instruction being issued and the issue slot remains unused. To increase the number of eligible warps, avoid possible load imbalances due to highly different execution durations per warp. Reducing stalls indicated on the Warp State Statistics and Source Counters sections can help, too.\n*Estimated Speedup (local): 12.96%*\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "40e55caada4b4d4280e5d57bbf41daf7": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "4812fe63767e41cc9cf82c9de944b383": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "520d86e9e9e142d2a9404675f4acab09": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "5862752de4db4eafa9c1894aa8c85385": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "5bc24b339fc8419da15158a611ccc7c0": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_3d25f6fa3bd447d38ef0619d34129054", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Speed Of Light\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| DRAM Frequency | Ghz | 5.00 |\n| SM Frequency | Mhz | 585.00 |\n| Elapsed Cycles | cycle | 201,083,989 |\n| Memory Throughput | % | 61.70 |\n| DRAM Throughput | % | 61.70 |\n| Duration | ms | 343.73 |\n| L1/TEX Cache Throughput | % | 17.89 |\n| L2 Cache Throughput | % | 8.42 |\n| SM Active Cycles | cycle | 201,130,599.78 |\n| Compute (SM) Throughput | % | 1.28 |\n\n🔧 **OPTIMIZATION**: Memory is more heavily utilized than Compute: Look at the Memory Workload Analysis section to identify the DRAM bottleneck. Check memory replay (coalescing) metrics to make sure you're efficiently utilizing the bytes transferred. Also consider whether it is possible to do more work per memory access (kernel fusion) or whether there are values you can (re)compute.\n\nℹ️ **INFO**: The ratio of peak float (fp32) to double (fp64) performance on this device is 32:1. The kernel achieved 0% of this device's fp32 peak performance and 0% of its fp64 peak performance. See the Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#roofline) for more details on roofline analysis.\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "678ec7c648a94af9a6d16e4bc22d743c": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "6be7ca5f9141465cb4b5126001b6c298": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DropdownModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DropdownModel", + "_options_labels": [ + "copy_optimized" + ], + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "DropdownView", + "description": "Kernel:", + "description_tooltip": null, + "disabled": false, + "index": 0, + "layout": "IPY_MODEL_731f455485da4a31a8455bc7e3cdf1a8", + "style": "IPY_MODEL_adfba531343149f7a215ec7ec30bf8e1" + } + }, + "7190fa5e9c1d4db5b53a19a00a43e29b": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_e2f9cbd9e16f49b7a84dc9bfe8808107", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Occupancy\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Block Limit SM | block | 16 |\n| Block Limit Registers | block | 8 |\n| Block Limit Shared Mem | block | 16 |\n| Block Limit Warps | block | 4 |\n| Theoretical Active Warps per SM | warp | 32 |\n| Theoretical Occupancy | % | 100 |\n| Achieved Occupancy | % | 97.54 |\n| Achieved Active Warps Per SM | warp | 31.21 |\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "731f455485da4a31a8455bc7e3cdf1a8": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": "400px" + } + }, + "7bd502fafaad4a1d9d5c7db9c1d54b9e": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_16b4cfe3f86041eea5ee6471175ddce6", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "# copy_optimized", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "d7dd0302a2cb475388f8fb2c5cf92f5d", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": "Tab(children=(Output(), Output(), Output(), Output(), Output(), Output(), Output(), Output(), Output(), Output…" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "7ed48d6abaab4e95a347fa5c7b76bfac": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "81ee7055ea504002a61d86e34b9d2564": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "8234d3926a0c413a996b1965cf862551": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "84fd4b1277464e378c55ceb8b0628045": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_8fa15d63997f485795c9541d5151a876", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Launch\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Block Size | | 256 |\n| Function Cache Configuration | | CachePreferNone |\n| Grid Size | | 16,384 |\n| Registers Per Thread | register/thread | 35 |\n| Shared Memory Configuration Size | Kbyte | 32.77 |\n| Driver Shared Memory Per Block | byte/block | 0 |\n| Dynamic Shared Memory Per Block | byte/block | 0 |\n| Static Shared Memory Per Block | byte/block | 0 |\n| # SMs | SM | 40 |\n| Threads | thread | 4,194,304 |\n| Uses Green Context | | 0 |\n| Waves Per SM | | 102.40 |\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "8713dc92860b464aa1ce100891e33ed7": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_d74423ab0b3d416187826deaba0a57ba", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Summary\n\n### Speed Of Light\n\nℹ️ **INFO**: The kernel is utilizing greater than 80.0% of the available compute or memory performance of the device. To further improve performance, work will likely need to be shifted from the most utilized to another unit. Start by analyzing DRAM in the Memory Workload Analysis section.\n\nℹ️ **INFO**: The ratio of peak float (fp32) to double (fp64) performance on this device is 32:1. The kernel achieved 0% of this device's fp32 peak performance and 0% of its fp64 peak performance. See the Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#roofline) for more details on roofline analysis.\n\n### Memory Workload\n\n⚠️ **WARNING**: The optional metric lts__average_gcomp_input_sector_success_rate.pct could not be found. Collecting it as an additional metric could enable the rule to provide more guidance.\n\n### Compute Workload\n\nℹ️ **INFO**: ALU is the highest-utilized pipeline (23.8%) based on active cycles, taking into account the rates of its different instructions. It executes integer and logic operations. It is well-utilized, but should not be a bottleneck.\n\n### Scheduler\n\n🔧 **OPTIMIZATION**: Every scheduler is capable of issuing one instruction per cycle, but for this kernel each scheduler only issues an instruction every 6.2 cycles. This might leave hardware resources underutilized and may lead to less optimal performance. Out of the maximum of 8 warps per scheduler, this kernel allocates an average of 7.87 active warps per scheduler, but only an average of 0.27 warps were eligible per cycle. Eligible warps are the subset of active warps that are ready to issue their next instruction. Every cycle with no eligible warp results in no instruction being issued and the issue slot remains unused. To increase the number of eligible warps, avoid possible load imbalances due to highly different execution durations per warp. Reducing stalls indicated on the Warp State Statistics and Source Counters sections can help, too.\n*Estimated Speedup (local): 12.96%*\n\n### Warp State\n\n🔧 **OPTIMIZATION**: On average, each warp of this kernel spends 45.3 cycles being stalled waiting for a scoreboard dependency on a L1TEX (local, global, surface, texture) operation. Find the instruction producing the data being waited upon to identify the culprit. To reduce the number of cycles waiting on L1TEX data accesses verify the memory access patterns are optimal for the target architecture, attempt to increase cache hit rates by increasing data locality (coalescing), or by changing the cache configuration. Consider moving frequently used data to shared memory. This stall type represents about 92.3% of the total average of 49.1 cycles between issuing two instructions.\n*Estimated Speedup (global): 12.96%*\n\nℹ️ **INFO**: Check the Warp Stall Sampling (All Samples) table for the top stall locations in your source based on sampling data. The Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#metrics-reference) provides more details on each stall reason.\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "88670f4cd80d4823962f681c5db0b05c": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": "400px" + } + }, + "8bd862cae4a34d9eb8c6e0cbaba458fd": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "8d0b84554fc14eb8aa1d73db53e7446c": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_8234d3926a0c413a996b1965cf862551", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## PM Sampling\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Maximum Buffer Size | Mbyte | 3.47 |\n| Dropped Samples | sample | 0 |\n| Maximum Sampling Interval | cycle | 640,000 |\n| # Pass Groups | | 1 |\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "8fa15d63997f485795c9541d5151a876": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "918774a98d094a6b851bbc1df702ca4d": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "a3c5590a108e4630ac47a029112fb8da": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_e2c606a3ec4b41bc85095363bed2a8bb", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Compute & Memory Distribution\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Average DRAM Active Cycles | cycle | 1,059,639,779 |\n| Total DRAM Elapsed Cycles | cycle | 13,739,264,000 |\n| Average L1 Active Cycles | cycle | 201,130,599.78 |\n| Total L1 Elapsed Cycles | cycle | 8,048,353,288 |\n| Average L2 Active Cycles | cycle | 291,798,067.81 |\n| Total L2 Elapsed Cycles | cycle | 9,404,542,720 |\n| Average SM Active Cycles | cycle | 201,130,599.78 |\n| Total SM Elapsed Cycles | cycle | 8,048,353,288 |\n| Average SMSP Active Cycles | cycle | 201,107,006.62 |\n| Total SMSP Elapsed Cycles | cycle | 32,193,413,152 |\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "a7948ce618014b68a77c29a97b5f8089": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_918774a98d094a6b851bbc1df702ca4d", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## PM Sampling\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Maximum Buffer Size | Mbyte | 3.28 |\n| Dropped Samples | sample | 0 |\n| Maximum Sampling Interval | cycle | 160,000 |\n| # Pass Groups | | 1 |\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "a9bbc802cafc48aca6c853e0ebd6806e": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "ad7d9090fefd443fb56b97437c207ee8": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_3bdb7a5b68b0484ebfa4584fe4440cef", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Compute Workload\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Executed Ipc Active | inst/cycle | 0.64 |\n| Executed Ipc Elapsed | inst/cycle | 0.64 |\n| Issue Slots Busy | % | 16.02 |\n| Issued Ipc Active | inst/cycle | 0.64 |\n| SM Busy | % | 23.83 |\n\nℹ️ **INFO**: ALU is the highest-utilized pipeline (23.8%) based on active cycles, taking into account the rates of its different instructions. It executes integer and logic operations. It is well-utilized, but should not be a bottleneck.\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "adfba531343149f7a215ec7ec30bf8e1": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "initial" + } + }, + "b298377cb334442fb3be8dd55cfe8989": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_7ed48d6abaab4e95a347fa5c7b76bfac", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Source Counters\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Branch Instructions Ratio | % | 0.01 |\n| Branch Instructions | inst | 3,014,656 |\n| Branch Efficiency | % | 100 |\n| Avg. Divergent Branches | | 0 |\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "b457bbb2890e4f6ea0d007d3d05d555b": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "b47fd7b6a0154f21bc182a368783f018": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DropdownModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DropdownModel", + "_options_labels": [ + "copy_blocked" + ], + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "DropdownView", + "description": "Kernel:", + "description_tooltip": null, + "disabled": false, + "index": 0, + "layout": "IPY_MODEL_88670f4cd80d4823962f681c5db0b05c", + "style": "IPY_MODEL_efbee61b66c74e939e6ba9eb177b5104" + } + }, + "bca08631512947a9bb7678cae262caa1": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_678ec7c648a94af9a6d16e4bc22d743c", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Source Counters\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Branch Instructions Ratio | % | 0.01 |\n| Branch Instructions | inst | 2,490,368 |\n| Branch Efficiency | % | 100 |\n| Avg. Divergent Branches | | 0 |\n\n🔧 **OPTIMIZATION**: This kernel has uncoalesced global accesses resulting in a total of 402653184 excessive sectors (75% of the total 536870912 sectors). Check the L2 Theoretical Sectors Global Excessive table for the primary source locations. The CUDA Programming Guide (https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#device-memory-accesses) has additional information on reducing uncoalesced device memory accesses.\n*Estimated Speedup (global): 74.47%*\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "bd3d14f42d7c4e058cf86091a34e0495": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "TabModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "TabModel", + "_titles": { + "0": "Summary", + "1": "Speed Of Light", + "2": "Memory Workload", + "3": "Compute Workload", + "4": "Compute & Memory Distribution", + "5": "Scheduler", + "6": "Warp State", + "7": "Instruction", + "8": "Launch", + "9": "PM Sampling", + "10": "Occupancy", + "11": "Source Counters" + }, + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "TabView", + "box_style": "", + "children": [ + "IPY_MODEL_fd81c004921e40acb4a5c89ce6b59345", + "IPY_MODEL_5bc24b339fc8419da15158a611ccc7c0", + "IPY_MODEL_e0520258747c44c3b3c03df575d08958", + "IPY_MODEL_f02b7d8187324ecda1befc6000394fb8", + "IPY_MODEL_a3c5590a108e4630ac47a029112fb8da", + "IPY_MODEL_f8282e5d62ba4fdfbb055930aae07bb0", + "IPY_MODEL_ddfcf54f17cb48b18c316d6fbc3f2df2", + "IPY_MODEL_2566b43ad6544ff5b3535e1bcf848394", + "IPY_MODEL_2be329446de8406aa008cba59ea2cfc0", + "IPY_MODEL_8d0b84554fc14eb8aa1d73db53e7446c", + "IPY_MODEL_7190fa5e9c1d4db5b53a19a00a43e29b", + "IPY_MODEL_bca08631512947a9bb7678cae262caa1" + ], + "layout": "IPY_MODEL_520d86e9e9e142d2a9404675f4acab09", + "selected_index": 0 + } + }, + "c0ac7614bdb74083a784cad1babfd7f4": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_0495f43209454c7dade96e09491b0a59", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Memory Workload\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Memory Throughput | Gbyte/s | 278.16 |\n| Mem Busy | % | 27.59 |\n| Max Bandwidth | % | 87.04 |\n| L1/TEX Hit Rate | % | 0 |\n| L2 Hit Rate | % | 50.00 |\n| Mem Pipes Busy | % | 8.13 |\n\n⚠️ **WARNING**: The optional metric lts__average_gcomp_input_sector_success_rate.pct could not be found. Collecting it as an additional metric could enable the rule to provide more guidance.\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "c8a882d190254c939506955708d0a0aa": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "cfffbaee02274c83ac30daeac3c63f6e": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_b457bbb2890e4f6ea0d007d3d05d555b", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Occupancy\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Block Limit SM | block | 16 |\n| Block Limit Registers | block | 6 |\n| Block Limit Shared Mem | block | 16 |\n| Block Limit Warps | block | 4 |\n| Theoretical Active Warps per SM | warp | 32 |\n| Theoretical Occupancy | % | 100 |\n| Achieved Occupancy | % | 98.34 |\n| Achieved Active Warps Per SM | warp | 31.47 |\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "d74423ab0b3d416187826deaba0a57ba": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "d7dd0302a2cb475388f8fb2c5cf92f5d": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "TabModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "TabModel", + "_titles": { + "0": "Summary", + "1": "Speed Of Light", + "2": "Memory Workload", + "3": "Compute Workload", + "4": "Compute & Memory Distribution", + "5": "Scheduler", + "6": "Warp State", + "7": "Instruction", + "8": "Launch", + "9": "PM Sampling", + "10": "Occupancy", + "11": "Source Counters" + }, + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "TabView", + "box_style": "", + "children": [ + "IPY_MODEL_8713dc92860b464aa1ce100891e33ed7", + "IPY_MODEL_e3027ae6d73b4bca9df34d8bd61155c3", + "IPY_MODEL_c0ac7614bdb74083a784cad1babfd7f4", + "IPY_MODEL_ad7d9090fefd443fb56b97437c207ee8", + "IPY_MODEL_f3b8695ddc6a445ebc82dde76f8d1f79", + "IPY_MODEL_40c9bccee18f489b8a60f0bcc66b380e", + "IPY_MODEL_143463ba065a4b96a6adbe97880b6330", + "IPY_MODEL_fbcda75ba8764222b2acf028e8478b78", + "IPY_MODEL_84fd4b1277464e378c55ceb8b0628045", + "IPY_MODEL_a7948ce618014b68a77c29a97b5f8089", + "IPY_MODEL_cfffbaee02274c83ac30daeac3c63f6e", + "IPY_MODEL_b298377cb334442fb3be8dd55cfe8989" + ], + "layout": "IPY_MODEL_a9bbc802cafc48aca6c853e0ebd6806e", + "selected_index": 0 + } + }, + "da5ad97524f4499cb466a461c9e9a014": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_8bd862cae4a34d9eb8c6e0cbaba458fd", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "# copy_blocked", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "bd3d14f42d7c4e058cf86091a34e0495", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": "Tab(children=(Output(), Output(), Output(), Output(), Output(), Output(), Output(), Output(), Output(), Output…" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "dbe2bf8c7add48d8bf6e1f13b603fd38": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "ddfcf54f17cb48b18c316d6fbc3f2df2": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_184b041944b040c1a715b4b892fe3405", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Warp State\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Warp Cycles Per Issued Instruction | cycle | 974.92 |\n| Warp Cycles Per Executed Instruction | cycle | 974.96 |\n| Avg. Active Threads Per Warp | | 32 |\n| Avg. Not Predicated Off Threads Per Warp | | 31.95 |\n\n🔧 **OPTIMIZATION**: On average, each warp of this kernel spends 503.1 cycles being stalled waiting for a scoreboard dependency on a L1TEX (local, global, surface, texture) operation. Find the instruction producing the data being waited upon to identify the culprit. To reduce the number of cycles waiting on L1TEX data accesses verify the memory access patterns are optimal for the target architecture, attempt to increase cache hit rates by increasing data locality (coalescing), or by changing the cache configuration. Consider moving frequently used data to shared memory. This stall type represents about 51.6% of the total average of 974.9 cycles between issuing two instructions.\n*Estimated Speedup (global): 38.3%*\n\n🔧 **OPTIMIZATION**: On average, each warp of this kernel spends 463.7 cycles being stalled waiting for the L1 instruction queue for local and global (LG) memory operations to be not full. Typically, this stall occurs only when executing local or global memory instructions extremely frequently. Avoid redundant global memory accesses. Try to avoid using thread-local memory by checking if dynamically indexed arrays are declared in local scope, of if the kernel has excessive register pressure causing by spills. If applicable, consider combining multiple lower-width memory operations into fewer wider memory operations and try interleaving memory operations and math instructions. This stall type represents about 47.6% of the total average of 974.9 cycles between issuing two instructions.\n*Estimated Speedup (global): 38.3%*\n\nℹ️ **INFO**: Check the Warp Stall Sampling (All Samples) table for the top stall locations in your source based on sampling data. The Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#metrics-reference) provides more details on each stall reason.\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "e0520258747c44c3b3c03df575d08958": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_06d6f26f29494f45abcd43b287163314", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Memory Workload\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Memory Throughput | Gbyte/s | 197.30 |\n| Mem Busy | % | 6.75 |\n| Max Bandwidth | % | 61.70 |\n| L1/TEX Hit Rate | % | 0.05 |\n| L2 Hit Rate | % | 2.36 |\n| Mem Pipes Busy | % | 1.28 |\n\n⚠️ **WARNING**: The optional metric lts__average_gcomp_input_sector_success_rate.pct could not be found. Collecting it as an additional metric could enable the rule to provide more guidance.\n\n🔧 **OPTIMIZATION**: The memory access pattern for global loads from DRAM might not be optimal. On average, only 8.0 of the 32 bytes transmitted per sector are utilized by each thread. This applies to the 99.6% of sectors missed in L2. This could possibly be caused by a stride between threads. Check the Source Counters section for uncoalesced global loads.\n*Estimated Speedup (global): 46.07%*\n\n🔧 **OPTIMIZATION**: The memory access pattern for global stores to DRAM might not be optimal. On average, only 8.0 of the 32 bytes transmitted per sector are utilized by each thread. This applies to the 95.6% of sectors missed in L2. This could possibly be caused by a stride between threads. Check the Source Counters section for uncoalesced global stores.\n*Estimated Speedup (global): 44.22%*\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "e2c606a3ec4b41bc85095363bed2a8bb": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "e2f9cbd9e16f49b7a84dc9bfe8808107": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "e3027ae6d73b4bca9df34d8bd61155c3": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_dbe2bf8c7add48d8bf6e1f13b603fd38", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Speed Of Light\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| DRAM Frequency | Ghz | 4.99 |\n| SM Frequency | Mhz | 585.00 |\n| Elapsed Cycles | cycle | 10,401,526 |\n| Memory Throughput | % | 87.04 |\n| DRAM Throughput | % | 87.04 |\n| Duration | ms | 17.78 |\n| L1/TEX Cache Throughput | % | 36.36 |\n| L2 Cache Throughput | % | 27.59 |\n| SM Active Cycles | cycle | 10,389,825.82 |\n| Compute (SM) Throughput | % | 23.80 |\n\nℹ️ **INFO**: The kernel is utilizing greater than 80.0% of the available compute or memory performance of the device. To further improve performance, work will likely need to be shifted from the most utilized to another unit. Start by analyzing DRAM in the Memory Workload Analysis section.\n\nℹ️ **INFO**: The ratio of peak float (fp32) to double (fp64) performance on this device is 32:1. The kernel achieved 0% of this device's fp32 peak performance and 0% of its fp64 peak performance. See the Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#roofline) for more details on roofline analysis.\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "efbee61b66c74e939e6ba9eb177b5104": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "initial" + } + }, + "f02b7d8187324ecda1befc6000394fb8": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_5862752de4db4eafa9c1894aa8c85385", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Compute Workload\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Executed Ipc Active | inst/cycle | 0.03 |\n| Executed Ipc Elapsed | inst/cycle | 0.03 |\n| Issue Slots Busy | % | 0.80 |\n| Issued Ipc Active | inst/cycle | 0.03 |\n| SM Busy | % | 1.11 |\n\n🔧 **OPTIMIZATION**: All compute pipelines are under-utilized. Either this kernel is very small or it doesn't issue enough warps per scheduler. Check the Launch Statistics and Scheduler Statistics sections for further details.\n*Estimated Speedup (local): 98.89%*\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "f3b8695ddc6a445ebc82dde76f8d1f79": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_c8a882d190254c939506955708d0a0aa", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Compute & Memory Distribution\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Average DRAM Active Cycles | cycle | 77,278,172 |\n| Total DRAM Elapsed Cycles | cycle | 710,265,856 |\n| Average L1 Active Cycles | cycle | 10,389,825.82 |\n| Total L1 Elapsed Cycles | cycle | 415,992,616 |\n| Average L2 Active Cycles | cycle | 15,185,970.41 |\n| Total L2 Elapsed Cycles | cycle | 486,470,688 |\n| Average SM Active Cycles | cycle | 10,389,825.82 |\n| Total SM Elapsed Cycles | cycle | 415,992,616 |\n| Average SMSP Active Cycles | cycle | 10,389,220.56 |\n| Total SMSP Elapsed Cycles | cycle | 1,663,970,464 |\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "f8282e5d62ba4fdfbb055930aae07bb0": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_3fb5720992194190a644475c35bb3962", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Scheduler\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| One or More Eligible | % | 0.80 |\n| Issued Warp Per Scheduler | | 0.01 |\n| No Eligible | % | 99.20 |\n| Active Warps Per Scheduler | warp | 7.81 |\n| Eligible Warps Per Scheduler | warp | 0.01 |\n\n🔧 **OPTIMIZATION**: Every scheduler is capable of issuing one instruction per cycle, but for this kernel each scheduler only issues an instruction every 124.9 cycles. This might leave hardware resources underutilized and may lead to less optimal performance. Out of the maximum of 8 warps per scheduler, this kernel allocates an average of 7.81 active warps per scheduler, but only an average of 0.01 warps were eligible per cycle. Eligible warps are the subset of active warps that are ready to issue their next instruction. Every cycle with no eligible warp results in no instruction being issued and the issue slot remains unused. To increase the number of eligible warps, avoid possible load imbalances due to highly different execution durations per warp. Reducing stalls indicated on the Warp State Statistics and Source Counters sections can help, too.\n*Estimated Speedup (local): 38.3%*\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "fbcda75ba8764222b2acf028e8478b78": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_144d071405684ec3bb0a8259ae566518", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Instruction\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Avg. Executed Instructions Per Scheduler | inst | 1,664,614.40 |\n| Executed Instructions | inst | 266,338,304 |\n| Avg. Issued Instructions Per Scheduler | inst | 1,664,650.29 |\n| Issued Instructions | inst | 266,344,046 |\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "fd81c004921e40acb4a5c89ce6b59345": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_3b435d6a3d0148bc82ac4c72334d03ab", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Summary\n\n### Speed Of Light\n\n🔧 **OPTIMIZATION**: Memory is more heavily utilized than Compute: Look at the Memory Workload Analysis section to identify the DRAM bottleneck. Check memory replay (coalescing) metrics to make sure you're efficiently utilizing the bytes transferred. Also consider whether it is possible to do more work per memory access (kernel fusion) or whether there are values you can (re)compute.\n\nℹ️ **INFO**: The ratio of peak float (fp32) to double (fp64) performance on this device is 32:1. The kernel achieved 0% of this device's fp32 peak performance and 0% of its fp64 peak performance. See the Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#roofline) for more details on roofline analysis.\n\n### Memory Workload\n\n⚠️ **WARNING**: The optional metric lts__average_gcomp_input_sector_success_rate.pct could not be found. Collecting it as an additional metric could enable the rule to provide more guidance.\n\n🔧 **OPTIMIZATION**: The memory access pattern for global loads from DRAM might not be optimal. On average, only 8.0 of the 32 bytes transmitted per sector are utilized by each thread. This applies to the 99.6% of sectors missed in L2. This could possibly be caused by a stride between threads. Check the Source Counters section for uncoalesced global loads.\n*Estimated Speedup (global): 46.07%*\n\n🔧 **OPTIMIZATION**: The memory access pattern for global stores to DRAM might not be optimal. On average, only 8.0 of the 32 bytes transmitted per sector are utilized by each thread. This applies to the 95.6% of sectors missed in L2. This could possibly be caused by a stride between threads. Check the Source Counters section for uncoalesced global stores.\n*Estimated Speedup (global): 44.22%*\n\n### Compute Workload\n\n🔧 **OPTIMIZATION**: All compute pipelines are under-utilized. Either this kernel is very small or it doesn't issue enough warps per scheduler. Check the Launch Statistics and Scheduler Statistics sections for further details.\n*Estimated Speedup (local): 98.89%*\n\n### Scheduler\n\n🔧 **OPTIMIZATION**: Every scheduler is capable of issuing one instruction per cycle, but for this kernel each scheduler only issues an instruction every 124.9 cycles. This might leave hardware resources underutilized and may lead to less optimal performance. Out of the maximum of 8 warps per scheduler, this kernel allocates an average of 7.81 active warps per scheduler, but only an average of 0.01 warps were eligible per cycle. Eligible warps are the subset of active warps that are ready to issue their next instruction. Every cycle with no eligible warp results in no instruction being issued and the issue slot remains unused. To increase the number of eligible warps, avoid possible load imbalances due to highly different execution durations per warp. Reducing stalls indicated on the Warp State Statistics and Source Counters sections can help, too.\n*Estimated Speedup (local): 38.3%*\n\n### Warp State\n\n🔧 **OPTIMIZATION**: On average, each warp of this kernel spends 503.1 cycles being stalled waiting for a scoreboard dependency on a L1TEX (local, global, surface, texture) operation. Find the instruction producing the data being waited upon to identify the culprit. To reduce the number of cycles waiting on L1TEX data accesses verify the memory access patterns are optimal for the target architecture, attempt to increase cache hit rates by increasing data locality (coalescing), or by changing the cache configuration. Consider moving frequently used data to shared memory. This stall type represents about 51.6% of the total average of 974.9 cycles between issuing two instructions.\n*Estimated Speedup (global): 38.3%*\n\n🔧 **OPTIMIZATION**: On average, each warp of this kernel spends 463.7 cycles being stalled waiting for the L1 instruction queue for local and global (LG) memory operations to be not full. Typically, this stall occurs only when executing local or global memory instructions extremely frequently. Avoid redundant global memory accesses. Try to avoid using thread-local memory by checking if dynamically indexed arrays are declared in local scope, of if the kernel has excessive register pressure causing by spills. If applicable, consider combining multiple lower-width memory operations into fewer wider memory operations and try interleaving memory operations and math instructions. This stall type represents about 47.6% of the total average of 974.9 cycles between issuing two instructions.\n*Estimated Speedup (global): 38.3%*\n\nℹ️ **INFO**: Check the Warp Stall Sampling (All Samples) table for the top stall locations in your source based on sampling data. The Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#metrics-reference) provides more details on each stall reason.\n\n### Source Counters\n\n🔧 **OPTIMIZATION**: This kernel has uncoalesced global accesses resulting in a total of 402653184 excessive sectors (75% of the total 536870912 sectors). Check the L2 Theoretical Sectors Global Excessive table for the primary source locations. The CUDA Programming Guide (https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#device-memory-accesses) has additional information on reducing uncoalesced device memory accesses.\n*Estimated Speedup (global): 74.47%*\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + } + } + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} \ No newline at end of file From 166df50fc2c453309b240eddc2fc613796004f59 Mon Sep 17 00:00:00 2001 From: Bryce Adelstein Lelbach aka wash Date: Tue, 3 Mar 2026 12:29:08 -0500 Subject: [PATCH 03/18] Tutorials/Accelerated Python/Kernel Authoring: Display book histogram dataset size in megabytes instead of bytes. Made-with: Cursor --- ...41__kernel_authoring__book_histogram.ipynb | 884 +-- ..._authoring__book_histogram__SOLUTION.ipynb | 6829 ++++++++--------- 2 files changed, 3847 insertions(+), 3866 deletions(-) diff --git a/tutorials/accelerated-python/notebooks/kernels/41__kernel_authoring__book_histogram.ipynb b/tutorials/accelerated-python/notebooks/kernels/41__kernel_authoring__book_histogram.ipynb index c46583fb..8af90f50 100644 --- a/tutorials/accelerated-python/notebooks/kernels/41__kernel_authoring__book_histogram.ipynb +++ b/tutorials/accelerated-python/notebooks/kernels/41__kernel_authoring__book_histogram.ipynb @@ -1,445 +1,445 @@ { - "cells": [ - { - "cell_type": "markdown", - "id": "f1a8560a-c91b-48db-af1c-18fcd4892448", - "metadata": { - "id": "f1a8560a-c91b-48db-af1c-18fcd4892448" - }, - "source": [ - "# Kernel Authoring - Book Histogram\n", - "\n", - "## Table of Contents\n", - "\n", - "1. [Environment Setup & Data Download](#1.-Environment-Setup-&-Data-Download)\n", - "2. [First Attempt: Global Memory Histogram](#2.-First-Attempt:-Global-Memory-Histogram)\n", - "3. [Fixing Data Races with Atomics](#3.-Fixing-Data-Races-with-Atomics)\n", - "4. [Profiling the Naive Solution](#4.-Profiling-the-Naive-Solution)\n", - "5. [Optimization: Shared Memory & Cooperative Groups](#5.-Optimization:-Shared-Memory-&-Cooperative-Groups)\n", - "6. [Performance Comparison](#6.-Performance-Comparison)\n", - "\n", - "## 1. Environment Setup & Data Download\n", - "\n", - "Let's learn to use some advanced CUDA features like shared memory, atomics, and [cuda.cooperative](https://nvidia.github.io/cccl/unstable/python/coop.html) to write an efficient histogram kernel to determine the most frequent characters in a collection of books.\n", - "\n", - "First, let's download our dataset and install the necessary tools." - ] + "cells": [ + { + "cell_type": "markdown", + "id": "f1a8560a-c91b-48db-af1c-18fcd4892448", + "metadata": { + "id": "f1a8560a-c91b-48db-af1c-18fcd4892448" + }, + "source": [ + "# Kernel Authoring - Book Histogram\n", + "\n", + "## Table of Contents\n", + "\n", + "1. [Environment Setup & Data Download](#1.-Environment-Setup-&-Data-Download)\n", + "2. [First Attempt: Global Memory Histogram](#2.-First-Attempt:-Global-Memory-Histogram)\n", + "3. [Fixing Data Races with Atomics](#3.-Fixing-Data-Races-with-Atomics)\n", + "4. [Profiling the Naive Solution](#4.-Profiling-the-Naive-Solution)\n", + "5. [Optimization: Shared Memory & Cooperative Groups](#5.-Optimization:-Shared-Memory-&-Cooperative-Groups)\n", + "6. [Performance Comparison](#6.-Performance-Comparison)\n", + "\n", + "## 1. Environment Setup & Data Download\n", + "\n", + "Let's learn to use some advanced CUDA features like shared memory, atomics, and [cuda.cooperative](https://nvidia.github.io/cccl/unstable/python/coop.html) to write an efficient histogram kernel to determine the most frequent characters in a collection of books.\n", + "\n", + "First, let's download our dataset and install the necessary tools." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ce42d5e5-db1e-46da-a64a-831d0f3d59ff", + "metadata": { + "id": "ce42d5e5-db1e-46da-a64a-831d0f3d59ff" + }, + "outputs": [], + "source": [ + "import os\n", + "\n", + "if os.getenv(\"COLAB_RELEASE_TAG\") and not os.path.exists(\"/accelerated-computing-hub-installed\"): # If running in Google Colab:\n", + " print(\"Downloading NCU package.\")\n", + " !curl -s -L -O https://developer.download.nvidia.com/compute/cuda/repos/debian12/x86_64/nsight-compute-2025.2.1_2025.2.1.3-1_amd64.deb\n", + " print(\"Installing NCU package.\")\n", + " !dpkg -i nsight-compute-2025.2.1_2025.2.1.3-1_amd64.deb > /dev/null\n", + " !update-alternatives --install /opt/bin/ncu ncu /opt/nvidia/nsight-compute/2025.2.1/ncu 20250201 > /dev/null\n", + " print(\"Installing PIP packages.\")\n", + " !pip uninstall \"cuda-python\" --yes > /dev/null\n", + " !pip install \"numba-cuda\" \"cuda-cccl[test-cu12]\" \"nvtx\" \"nsightful[notebook] @ git+https://github.com/brycelelbach/nsightful.git\" > /dev/null 2>&1\n", + " open(\"/accelerated-computing-hub-installed\", \"a\").close()\n", + "\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import nsightful\n", + "import urllib.request" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "6e1ae9dc-39f1-4c93-b923-85a96b45a057", + "metadata": { + "id": "6e1ae9dc-39f1-4c93-b923-85a96b45a057" + }, + "outputs": [], + "source": [ + "urllib.request.urlretrieve(\n", + " \"https://drive.usercontent.google.com/download?id=1MW1lPgkTq3YG9ikuq6u3d9sfpt-wKQZ0&export=download\",\n", + " \"books__15m.txt\")" + ] + }, + { + "cell_type": "markdown", + "id": "9109d3c0-e276-44cc-9f36-f8c79eb48b31", + "metadata": { + "id": "9109d3c0-e276-44cc-9f36-f8c79eb48b31" + }, + "source": [ + "## 2. First Attempt: Global Memory Histogram\n", + "\n", + "A histogram kernel counts the number of times a value occurs in a dataset. To implement this, we create an array that is large enough to store all possible values (in the case of counting 1-byte ASCII characters, 256 elements). Then for the value of each element in the dataset, we increment its location in the array.\n", + "\n", + "Let's try a simple way to implement this:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "61c12795-b14a-4447-9dcf-9748616cc453", + "metadata": { + "id": "61c12795-b14a-4447-9dcf-9748616cc453" + }, + "outputs": [], + "source": [ + "%%writefile histogram_global.py\n", + "\n", + "from numba import cuda\n", + "import cupy as cp\n", + "import cupyx as cpx\n", + "import sys\n", + "import os\n", + "\n", + "bins = 256\n", + "\n", + "values = cp.fromfile(\"books__15m.txt\", dtype=cp.uint8)\n", + "histogram = cp.zeros((bins), dtype=cp.int32)\n", + "\n", + "threads_per_block = 512 if len(sys.argv) < 3 else int(sys.argv[2])\n", + "items_per_thread = 8 if len(sys.argv) < 4 else int(sys.argv[3])\n", + "items_per_block = threads_per_block * items_per_thread\n", + "blocks = int(len(values) / (threads_per_block * items_per_thread))\n", + "assert values.size % items_per_block == 0\n", + "\n", + "@cuda.jit\n", + "def histogram_global(values, histogram):\n", + " for i in range(items_per_thread):\n", + " value = values[cuda.grid(1) * items_per_thread + i]\n", + " old_count = histogram[value]\n", + " new_count = old_count + 1\n", + " histogram[value] = new_count\n", + "\n", + "def launch(output):\n", + " histogram[:] = 0 # Reset histogram to 0 each trial.\n", + " histogram_global[blocks, threads_per_block](values, histogram)\n", + "\n", + " if (output):\n", + " cp.savetxt(sys.stdout, histogram, delimiter=\",\", fmt=\"%i\")\n", + "\n", + "if len(sys.argv) >= 2 and sys.argv[1] == \"output\":\n", + " launch(output=True)\n", + "elif os.getenv(\"NV_COMPUTE_PROFILER_PERFWORKS_DIR\"): # Running under `ncu`.\n", + " launch(output=False) # `ncu` slows things down; so just launch once when running under it.\n", + "else:\n", + " launch(output=False)\n", + " D = cpx.profiler.benchmark(launch, (False,), n_repeat=15, n_warmup=4).gpu_times[0]\n", + " print(f\"{D.mean():.3g} s ± {(D.std() / D.mean()):.2%} (mean ± relative stdev of {D.size} runs)\")" + ] + }, + { + "cell_type": "markdown", + "id": "27e30efa-3a37-402f-9414-e444214d8ce6", + "metadata": { + "id": "27e30efa-3a37-402f-9414-e444214d8ce6" + }, + "source": [ + "Now let's make sure it runs and check the output." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b3f32240-ad7b-4717-98b3-82b0298a099a", + "metadata": { + "id": "b3f32240-ad7b-4717-98b3-82b0298a099a" + }, + "outputs": [], + "source": [ + "!python histogram_global.py" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "815cb072-b3a0-47aa-af6c-dd66c626a440", + "metadata": { + "id": "815cb072-b3a0-47aa-af6c-dd66c626a440" + }, + "outputs": [], + "source": [ + "histogram_output = !python histogram_global.py output\n", + "histogram = np.loadtxt(histogram_output, delimiter=\",\")\n", + "\n", + "# Print most frequently occuring characters.\n", + "pairs = sorted(((i,c) for i,c in enumerate(histogram) if c), key=lambda x: x[1], reverse=True)[:20]\n", + "labels = [('SPACE' if i==32 else chr(i)) if 32<=i<=126 else f'0x{i:02X}' for i,_ in pairs]\n", + "plt.barh(labels[::-1], [c for _,c in pairs][::-1])\n", + "plt.xlabel('count')\n", + "plt.tight_layout()\n", + "plt.title(\"Top 20 Bins\")\n", + "plt.show()\n", + "\n", + "length = os.path.getsize(\"books__15m.txt\")\n", + "print(f\"Characters in dataset: {length / 1e6:.1f} MB\")" + ] + }, + { + "cell_type": "markdown", + "id": "b14fa522-b41b-4538-8c34-ecc355e55116", + "metadata": { + "id": "b14fa522-b41b-4538-8c34-ecc355e55116" + }, + "source": [ + "## 3. Fixing Data Races with Atomics\n", + "\n", + "It looks like something is wrong - our counts are very low, and the most common characters don't make a lot of sense. Many of our increments seem to get lost!\n", + "\n", + "What's happening here is called a data race. Many different threads are trying to access the bins of the histogram at the same time.\n", + "\n", + "Imagine that two threads are trying to update the same bin:\n", + "\n", + "- Thread 0 reads the count of the bin, which is 0, and stores it in its local variable `old_count`.\n", + "- Thread 0 adds 1 to its `old_count`, producing a `new_count` of 1.\n", + "- Thread 1 reads the count of the bin, which is still 0, and stores it in its local variable `old_count`.\n", + "- Thread 1 adds 1 to its `old_count`, producing a `new_count` of 1.\n", + "- Thread 0 stores `new_count` to the bin, setting it to 1.\n", + "- Thread 1 stores `new_count` to the bin, setting it to 1, and losing the increment from thread 0!\n", + "\n", + "To fix this, we need to use atomic operations. `cuda.atomic.add(array, index, value)` will perform `array[index] += value` as a single indivisible operation. This will ensure that no increments get lost.\n", + "\n", + "**TODO: Fix the code above by modifying it to use `cuda.atomic.add`.**" + ] + }, + { + "cell_type": "markdown", + "id": "08f4dded-26a7-4ef8-b981-e00c569ca4d0", + "metadata": { + "id": "08f4dded-26a7-4ef8-b981-e00c569ca4d0" + }, + "source": [ + "## 4. Profiling the Naive Solution\n", + "\n", + "Now let's profile our code." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "8dbd226c-66f2-43df-868a-6b024b1de24c", + "metadata": { + "id": "8dbd226c-66f2-43df-868a-6b024b1de24c" + }, + "outputs": [], + "source": [ + "!ncu -f --kernel-name regex:histogram_global --set full -o histogram_global python histogram_global.py\n", + "histogram_global_csv = !ncu --import histogram_global.ncu-rep --csv" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "ad12380e-253b-4410-ab34-9479411fdf81", + "metadata": { + "id": "ad12380e-253b-4410-ab34-9479411fdf81" + }, + "outputs": [], + "source": [ + "nsightful.display_ncu_csv_in_notebook(histogram_global_csv)" + ] + }, + { + "cell_type": "markdown", + "id": "e1f72831-780f-4cf5-8ff1-2092ecb193d9", + "metadata": { + "id": "e1f72831-780f-4cf5-8ff1-2092ecb193d9" + }, + "source": [ + "## 5. Optimization: Shared Memory & Cooperative Groups\n", + "\n", + "Looking at the profile trace, it seems like our code is quite slow - look at the memory workload tab and see how low the throughput is!\n", + "\n", + "One improvement we should make is to separate loading values from the histogram update and to perform striped loads (also known as coalesced access). We'll use [cuda.cooperative](https://nvidia.github.io/cccl/unstable/python/coop.html)'s block load instead of writing this by hand.\n", + "\n", + "**TODO: Rewrite the code below to use `cuda.cooperative` to load from `values` into local memory.**\n", + "- **Create a `coop.block.load(dtype, threads_per_block, items_per_thread, algorithm)` object outside of the kernel.**\n", + "- **Make sure to link the algorithm object to the kernel by adding a `link` parameter to the decorator.**\n", + "- **Create storage for the items we'll load with `cuda.local.array`.**\n", + "\n", + "**TODO: Look at the profile trace and code and think about how we could improve performance further.**\n", + "\n", + "**HINT:**\n", + "- **What sorts of operations are we performing? Are they expensive? Can we make the code more efficient by reducing the number of expensive operations we perform?**\n", + "- **You can allocate memory accessible by the entire block with `cuda.shared.array`.**\n", + "- **You can synchronize all threads within a block with `cuda.syncthreads`.**" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "cf7c9865-646a-4bbd-9b41-61cadfc5484c", + "metadata": { + "id": "cf7c9865-646a-4bbd-9b41-61cadfc5484c" + }, + "outputs": [], + "source": [ + "%%writefile histogram_localized.py\n", + "\n", + "from numba import cuda\n", + "import cupy as cp\n", + "import cupyx as cpx\n", + "import sys\n", + "import os\n", + "\n", + "bins = 256\n", + "\n", + "values = cp.fromfile(\"books__15m.txt\", dtype=cp.uint8)\n", + "histogram = cp.zeros((bins), dtype=cp.int32)\n", + "\n", + "threads_per_block = 512 if len(sys.argv) < 3 else int(sys.argv[2])\n", + "items_per_thread = 8 if len(sys.argv) < 4 else int(sys.argv[3])\n", + "items_per_block = threads_per_block * items_per_thread\n", + "blocks = int(len(values) / (threads_per_block * items_per_thread))\n", + "assert values.size % items_per_block == 0\n", + "\n", + "@cuda.jit\n", + "def histogram_localized(values, histogram):\n", + " for i in range(items_per_thread):\n", + " value = values[cuda.grid(1) * items_per_thread + i]\n", + " old_count = histogram[value]\n", + " new_count = old_count + 1\n", + " histogram[value] = new_count\n", + "\n", + "def launch(check, output):\n", + " histogram[:] = 0 # Reset histogram to 0 each trial.\n", + " histogram_localized[blocks, threads_per_block](values, histogram)\n", + "\n", + " if (check):\n", + " assert cp.sum(histogram) == len(values)\n", + "\n", + " if (output):\n", + " cp.savetxt(sys.stdout, histogram, delimiter=\",\", fmt=\"%i\")\n", + "\n", + "if len(sys.argv) >= 2 and sys.argv[1] == \"output\":\n", + " launch(check=True, output=True)\n", + "elif os.getenv(\"NV_COMPUTE_PROFILER_PERFWORKS_DIR\"): # Running under `ncu`.\n", + " launch(check=False, output=False) # `ncu` slows things down; so just launch once when running under it.\n", + "else:\n", + " launch(check=True, output=False)\n", + " D = cpx.profiler.benchmark(launch, (False, False), n_repeat=15, n_warmup=4).gpu_times[0]\n", + " print(f\"{D.mean():.3g} s ± {(D.std() / D.mean()):.2%} (mean ± relative stdev of {D.size} runs)\")" + ] + }, + { + "cell_type": "markdown", + "id": "fd090ee6-a5d3-46f6-a58e-d34e077a99c0", + "metadata": { + "id": "fd090ee6-a5d3-46f6-a58e-d34e077a99c0" + }, + "source": [ + "Now let's run the code and profile it." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "1b30e9b3-5a4c-4181-b642-b7def5e9f258", + "metadata": { + "id": "1b30e9b3-5a4c-4181-b642-b7def5e9f258" + }, + "outputs": [], + "source": [ + "!python histogram_localized.py" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "73f0c3cd-349b-490f-b6bd-7afbeb442fff", + "metadata": { + "id": "73f0c3cd-349b-490f-b6bd-7afbeb442fff" + }, + "outputs": [], + "source": [ + "histogram_output = !python histogram_localized.py output\n", + "histogram = np.loadtxt(histogram_output, delimiter=\",\")\n", + "\n", + "# Print most frequently occuring characters.\n", + "pairs = sorted(((i,c) for i,c in enumerate(histogram) if c), key=lambda x: x[1], reverse=True)[:20]\n", + "labels = [('SPACE' if i==32 else chr(i)) if 32<=i<=126 else f'0x{i:02X}' for i,_ in pairs]\n", + "plt.barh(labels[::-1], [c for _,c in pairs][::-1])\n", + "plt.xlabel('count')\n", + "plt.tight_layout()\n", + "plt.title(\"Top 20 Bins\")\n", + "plt.show()\n", + "\n", + "length = os.path.getsize(\"books__15m.txt\")\n", + "print(f\"Characters in dataset: {length / 1e6:.1f} MB\")" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d637b6b1-fb0b-4807-b70b-c80227c0fd6f", + "metadata": { + "id": "d637b6b1-fb0b-4807-b70b-c80227c0fd6f" + }, + "outputs": [], + "source": [ + "!ncu -f --kernel-name regex:histogram_localized --set full -o histogram_localized python histogram_localized.py\n", + "histogram_localized_csv = !ncu --import histogram_localized.ncu-rep --csv" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "114e8ff7-b6fb-42ad-abda-f6d53479c052", + "metadata": { + "id": "114e8ff7-b6fb-42ad-abda-f6d53479c052" + }, + "outputs": [], + "source": [ + "nsightful.display_ncu_csv_in_notebook(histogram_localized_csv)" + ] + }, + { + "cell_type": "markdown", + "id": "23df6c7a", + "metadata": {}, + "source": [ + "## 6. Performance Comparison\n", + "\n", + "Let's compare the execution time of our naive global memory implementation against our optimized shared memory implementation." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "2a3f9ca4-b61b-4536-9896-7a41498cc986", + "metadata": { + "id": "2a3f9ca4-b61b-4536-9896-7a41498cc986" + }, + "outputs": [], + "source": [ + "histogram_global_duration = !python histogram_global.py\n", + "histogram_localized_duration = !python histogram_localized.py\n", + "speedup = float(histogram_global_duration[0].split()[0]) / float(histogram_localized_duration[0].split()[0])\n", + "\n", + "print(f\"histogram_global: {histogram_global_duration[0]}\")\n", + "print(f\"histogram_localized: {histogram_localized_duration[0]}\")\n", + "print(f\"histogram_localized speedup over histogram_global: {speedup:.2f}\")" + ] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "gpuType": "T4", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + } }, - { - "cell_type": "code", - "execution_count": null, - "id": "ce42d5e5-db1e-46da-a64a-831d0f3d59ff", - "metadata": { - "id": "ce42d5e5-db1e-46da-a64a-831d0f3d59ff" - }, - "outputs": [], - "source": [ - "import os\n", - "\n", - "if os.getenv(\"COLAB_RELEASE_TAG\") and not os.path.exists(\"/accelerated-computing-hub-installed\"): # If running in Google Colab:\n", - " print(\"Downloading NCU package.\")\n", - " !curl -s -L -O https://developer.download.nvidia.com/compute/cuda/repos/debian12/x86_64/nsight-compute-2025.2.1_2025.2.1.3-1_amd64.deb\n", - " print(\"Installing NCU package.\")\n", - " !dpkg -i nsight-compute-2025.2.1_2025.2.1.3-1_amd64.deb > /dev/null\n", - " !update-alternatives --install /opt/bin/ncu ncu /opt/nvidia/nsight-compute/2025.2.1/ncu 20250201 > /dev/null\n", - " print(\"Installing PIP packages.\")\n", - " !pip uninstall \"cuda-python\" --yes > /dev/null\n", - " !pip install \"numba-cuda\" \"cuda-cccl[test-cu12]\" \"nvtx\" \"nsightful[notebook] @ git+https://github.com/brycelelbach/nsightful.git\" > /dev/null 2>&1\n", - " open(\"/accelerated-computing-hub-installed\", \"a\").close()\n", - "\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "import nsightful\n", - "import urllib.request" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "6e1ae9dc-39f1-4c93-b923-85a96b45a057", - "metadata": { - "id": "6e1ae9dc-39f1-4c93-b923-85a96b45a057" - }, - "outputs": [], - "source": [ - "urllib.request.urlretrieve(\n", - " \"https://drive.usercontent.google.com/download?id=1MW1lPgkTq3YG9ikuq6u3d9sfpt-wKQZ0&export=download\",\n", - " \"books__15m.txt\")" - ] - }, - { - "cell_type": "markdown", - "id": "9109d3c0-e276-44cc-9f36-f8c79eb48b31", - "metadata": { - "id": "9109d3c0-e276-44cc-9f36-f8c79eb48b31" - }, - "source": [ - "## 2. First Attempt: Global Memory Histogram\n", - "\n", - "A histogram kernel counts the number of times a value occurs in a dataset. To implement this, we create an array that is large enough to store all possible values (in the case of counting 1-byte ASCII characters, 256 elements). Then for the value of each element in the dataset, we increment its location in the array.\n", - "\n", - "Let's try a simple way to implement this:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "61c12795-b14a-4447-9dcf-9748616cc453", - "metadata": { - "id": "61c12795-b14a-4447-9dcf-9748616cc453" - }, - "outputs": [], - "source": [ - "%%writefile histogram_global.py\n", - "\n", - "from numba import cuda\n", - "import cupy as cp\n", - "import cupyx as cpx\n", - "import sys\n", - "import os\n", - "\n", - "bins = 256\n", - "\n", - "values = cp.fromfile(\"books__15m.txt\", dtype=cp.uint8)\n", - "histogram = cp.zeros((bins), dtype=cp.int32)\n", - "\n", - "threads_per_block = 512 if len(sys.argv) < 3 else int(sys.argv[2])\n", - "items_per_thread = 8 if len(sys.argv) < 4 else int(sys.argv[3])\n", - "items_per_block = threads_per_block * items_per_thread\n", - "blocks = int(len(values) / (threads_per_block * items_per_thread))\n", - "assert values.size % items_per_block == 0\n", - "\n", - "@cuda.jit\n", - "def histogram_global(values, histogram):\n", - " for i in range(items_per_thread):\n", - " value = values[cuda.grid(1) * items_per_thread + i]\n", - " old_count = histogram[value]\n", - " new_count = old_count + 1\n", - " histogram[value] = new_count\n", - "\n", - "def launch(output):\n", - " histogram[:] = 0 # Reset histogram to 0 each trial.\n", - " histogram_global[blocks, threads_per_block](values, histogram)\n", - "\n", - " if (output):\n", - " cp.savetxt(sys.stdout, histogram, delimiter=\",\", fmt=\"%i\")\n", - "\n", - "if len(sys.argv) >= 2 and sys.argv[1] == \"output\":\n", - " launch(output=True)\n", - "elif os.getenv(\"NV_COMPUTE_PROFILER_PERFWORKS_DIR\"): # Running under `ncu`.\n", - " launch(output=False) # `ncu` slows things down; so just launch once when running under it.\n", - "else:\n", - " launch(output=False)\n", - " D = cpx.profiler.benchmark(launch, (False,), n_repeat=15, n_warmup=4).gpu_times[0]\n", - " print(f\"{D.mean():.3g} s ± {(D.std() / D.mean()):.2%} (mean ± relative stdev of {D.size} runs)\")" - ] - }, - { - "cell_type": "markdown", - "id": "27e30efa-3a37-402f-9414-e444214d8ce6", - "metadata": { - "id": "27e30efa-3a37-402f-9414-e444214d8ce6" - }, - "source": [ - "Now let's make sure it runs and check the output." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "b3f32240-ad7b-4717-98b3-82b0298a099a", - "metadata": { - "id": "b3f32240-ad7b-4717-98b3-82b0298a099a" - }, - "outputs": [], - "source": [ - "!python histogram_global.py" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "815cb072-b3a0-47aa-af6c-dd66c626a440", - "metadata": { - "id": "815cb072-b3a0-47aa-af6c-dd66c626a440" - }, - "outputs": [], - "source": [ - "histogram_output = !python histogram_global.py output\n", - "histogram = np.loadtxt(histogram_output, delimiter=\",\")\n", - "\n", - "# Print most frequently occuring characters.\n", - "pairs = sorted(((i,c) for i,c in enumerate(histogram) if c), key=lambda x: x[1], reverse=True)[:20]\n", - "labels = [('SPACE' if i==32 else chr(i)) if 32<=i<=126 else f'0x{i:02X}' for i,_ in pairs]\n", - "plt.barh(labels[::-1], [c for _,c in pairs][::-1])\n", - "plt.xlabel('count')\n", - "plt.tight_layout()\n", - "plt.title(\"Top 20 Bins\")\n", - "plt.show()\n", - "\n", - "length = os.path.getsize(\"books__15m.txt\")\n", - "print(f\"Characters in dataset: {length}\")" - ] - }, - { - "cell_type": "markdown", - "id": "b14fa522-b41b-4538-8c34-ecc355e55116", - "metadata": { - "id": "b14fa522-b41b-4538-8c34-ecc355e55116" - }, - "source": [ - "## 3. Fixing Data Races with Atomics\n", - "\n", - "It looks like something is wrong - our counts are very low, and the most common characters don't make a lot of sense. Many of our increments seem to get lost!\n", - "\n", - "What's happening here is called a data race. Many different threads are trying to access the bins of the histogram at the same time.\n", - "\n", - "Imagine that two threads are trying to update the same bin:\n", - "\n", - "- Thread 0 reads the count of the bin, which is 0, and stores it in its local variable `old_count`.\n", - "- Thread 0 adds 1 to its `old_count`, producing a `new_count` of 1.\n", - "- Thread 1 reads the count of the bin, which is still 0, and stores it in its local variable `old_count`.\n", - "- Thread 1 adds 1 to its `old_count`, producing a `new_count` of 1.\n", - "- Thread 0 stores `new_count` to the bin, setting it to 1.\n", - "- Thread 1 stores `new_count` to the bin, setting it to 1, and losing the increment from thread 0!\n", - "\n", - "To fix this, we need to use atomic operations. `cuda.atomic.add(array, index, value)` will perform `array[index] += value` as a single indivisible operation. This will ensure that no increments get lost.\n", - "\n", - "**TODO: Fix the code above by modifying it to use `cuda.atomic.add`.**" - ] - }, - { - "cell_type": "markdown", - "id": "08f4dded-26a7-4ef8-b981-e00c569ca4d0", - "metadata": { - "id": "08f4dded-26a7-4ef8-b981-e00c569ca4d0" - }, - "source": [ - "## 4. Profiling the Naive Solution\n", - "\n", - "Now let's profile our code." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8dbd226c-66f2-43df-868a-6b024b1de24c", - "metadata": { - "id": "8dbd226c-66f2-43df-868a-6b024b1de24c" - }, - "outputs": [], - "source": [ - "!ncu -f --kernel-name regex:histogram_global --set full -o histogram_global python histogram_global.py\n", - "histogram_global_csv = !ncu --import histogram_global.ncu-rep --csv" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ad12380e-253b-4410-ab34-9479411fdf81", - "metadata": { - "id": "ad12380e-253b-4410-ab34-9479411fdf81" - }, - "outputs": [], - "source": [ - "nsightful.display_ncu_csv_in_notebook(histogram_global_csv)" - ] - }, - { - "cell_type": "markdown", - "id": "e1f72831-780f-4cf5-8ff1-2092ecb193d9", - "metadata": { - "id": "e1f72831-780f-4cf5-8ff1-2092ecb193d9" - }, - "source": [ - "## 5. Optimization: Shared Memory & Cooperative Groups\n", - "\n", - "Looking at the profile trace, it seems like our code is quite slow - look at the memory workload tab and see how low the throughput is!\n", - "\n", - "One improvement we should make is to separate loading values from the histogram update and to perform striped loads (also known as coalesced access). We'll use [cuda.cooperative](https://nvidia.github.io/cccl/unstable/python/coop.html)'s block load instead of writing this by hand.\n", - "\n", - "**TODO: Rewrite the code below to use `cuda.cooperative` to load from `values` into local memory.**\n", - "- **Create a `coop.block.load(dtype, threads_per_block, items_per_thread, algorithm)` object outside of the kernel.**\n", - "- **Make sure to link the algorithm object to the kernel by adding a `link` parameter to the decorator.**\n", - "- **Create storage for the items we'll load with `cuda.local.array`.**\n", - "\n", - "**TODO: Look at the profile trace and code and think about how we could improve performance further.**\n", - "\n", - "**HINT:**\n", - "- **What sorts of operations are we performing? Are they expensive? Can we make the code more efficient by reducing the number of expensive operations we perform?**\n", - "- **You can allocate memory accessible by the entire block with `cuda.shared.array`.**\n", - "- **You can synchronize all threads within a block with `cuda.syncthreads`.**" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "cf7c9865-646a-4bbd-9b41-61cadfc5484c", - "metadata": { - "id": "cf7c9865-646a-4bbd-9b41-61cadfc5484c" - }, - "outputs": [], - "source": [ - "%%writefile histogram_localized.py\n", - "\n", - "from numba import cuda\n", - "import cupy as cp\n", - "import cupyx as cpx\n", - "import sys\n", - "import os\n", - "\n", - "bins = 256\n", - "\n", - "values = cp.fromfile(\"books__15m.txt\", dtype=cp.uint8)\n", - "histogram = cp.zeros((bins), dtype=cp.int32)\n", - "\n", - "threads_per_block = 512 if len(sys.argv) < 3 else int(sys.argv[2])\n", - "items_per_thread = 8 if len(sys.argv) < 4 else int(sys.argv[3])\n", - "items_per_block = threads_per_block * items_per_thread\n", - "blocks = int(len(values) / (threads_per_block * items_per_thread))\n", - "assert values.size % items_per_block == 0\n", - "\n", - "@cuda.jit\n", - "def histogram_localized(values, histogram):\n", - " for i in range(items_per_thread):\n", - " value = values[cuda.grid(1) * items_per_thread + i]\n", - " old_count = histogram[value]\n", - " new_count = old_count + 1\n", - " histogram[value] = new_count\n", - "\n", - "def launch(check, output):\n", - " histogram[:] = 0 # Reset histogram to 0 each trial.\n", - " histogram_localized[blocks, threads_per_block](values, histogram)\n", - "\n", - " if (check):\n", - " assert cp.sum(histogram) == len(values)\n", - "\n", - " if (output):\n", - " cp.savetxt(sys.stdout, histogram, delimiter=\",\", fmt=\"%i\")\n", - "\n", - "if len(sys.argv) >= 2 and sys.argv[1] == \"output\":\n", - " launch(check=True, output=True)\n", - "elif os.getenv(\"NV_COMPUTE_PROFILER_PERFWORKS_DIR\"): # Running under `ncu`.\n", - " launch(check=False, output=False) # `ncu` slows things down; so just launch once when running under it.\n", - "else:\n", - " launch(check=True, output=False)\n", - " D = cpx.profiler.benchmark(launch, (False, False), n_repeat=15, n_warmup=4).gpu_times[0]\n", - " print(f\"{D.mean():.3g} s ± {(D.std() / D.mean()):.2%} (mean ± relative stdev of {D.size} runs)\")" - ] - }, - { - "cell_type": "markdown", - "id": "fd090ee6-a5d3-46f6-a58e-d34e077a99c0", - "metadata": { - "id": "fd090ee6-a5d3-46f6-a58e-d34e077a99c0" - }, - "source": [ - "Now let's run the code and profile it." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "1b30e9b3-5a4c-4181-b642-b7def5e9f258", - "metadata": { - "id": "1b30e9b3-5a4c-4181-b642-b7def5e9f258" - }, - "outputs": [], - "source": [ - "!python histogram_localized.py" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "73f0c3cd-349b-490f-b6bd-7afbeb442fff", - "metadata": { - "id": "73f0c3cd-349b-490f-b6bd-7afbeb442fff" - }, - "outputs": [], - "source": [ - "histogram_output = !python histogram_localized.py output\n", - "histogram = np.loadtxt(histogram_output, delimiter=\",\")\n", - "\n", - "# Print most frequently occuring characters.\n", - "pairs = sorted(((i,c) for i,c in enumerate(histogram) if c), key=lambda x: x[1], reverse=True)[:20]\n", - "labels = [('SPACE' if i==32 else chr(i)) if 32<=i<=126 else f'0x{i:02X}' for i,_ in pairs]\n", - "plt.barh(labels[::-1], [c for _,c in pairs][::-1])\n", - "plt.xlabel('count')\n", - "plt.tight_layout()\n", - "plt.title(\"Top 20 Bins\")\n", - "plt.show()\n", - "\n", - "length = os.path.getsize(\"books__15m.txt\")\n", - "print(f\"Characters in dataset: {length}\")" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d637b6b1-fb0b-4807-b70b-c80227c0fd6f", - "metadata": { - "id": "d637b6b1-fb0b-4807-b70b-c80227c0fd6f" - }, - "outputs": [], - "source": [ - "!ncu -f --kernel-name regex:histogram_localized --set full -o histogram_localized python histogram_localized.py\n", - "histogram_localized_csv = !ncu --import histogram_localized.ncu-rep --csv" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "114e8ff7-b6fb-42ad-abda-f6d53479c052", - "metadata": { - "id": "114e8ff7-b6fb-42ad-abda-f6d53479c052" - }, - "outputs": [], - "source": [ - "nsightful.display_ncu_csv_in_notebook(histogram_localized_csv)" - ] - }, - { - "cell_type": "markdown", - "id": "23df6c7a", - "metadata": {}, - "source": [ - "## 6. Performance Comparison\n", - "\n", - "Let's compare the execution time of our naive global memory implementation against our optimized shared memory implementation." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "2a3f9ca4-b61b-4536-9896-7a41498cc986", - "metadata": { - "id": "2a3f9ca4-b61b-4536-9896-7a41498cc986" - }, - "outputs": [], - "source": [ - "histogram_global_duration = !python histogram_global.py\n", - "histogram_localized_duration = !python histogram_localized.py\n", - "speedup = float(histogram_global_duration[0].split()[0]) / float(histogram_localized_duration[0].split()[0])\n", - "\n", - "print(f\"histogram_global: {histogram_global_duration[0]}\")\n", - "print(f\"histogram_localized: {histogram_localized_duration[0]}\")\n", - "print(f\"histogram_localized speedup over histogram_global: {speedup:.2f}\")" - ] - } - ], - "metadata": { - "accelerator": "GPU", - "colab": { - "gpuType": "T4", - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 5 + "nbformat": 4, + "nbformat_minor": 5 } diff --git a/tutorials/accelerated-python/notebooks/kernels/solutions/41__kernel_authoring__book_histogram__SOLUTION.ipynb b/tutorials/accelerated-python/notebooks/kernels/solutions/41__kernel_authoring__book_histogram__SOLUTION.ipynb index f208bad6..44642dc9 100644 --- a/tutorials/accelerated-python/notebooks/kernels/solutions/41__kernel_authoring__book_histogram__SOLUTION.ipynb +++ b/tutorials/accelerated-python/notebooks/kernels/solutions/41__kernel_authoring__book_histogram__SOLUTION.ipynb @@ -1,3482 +1,3463 @@ { - "cells": [ - { - "cell_type": "markdown", - "id": "f1a8560a-c91b-48db-af1c-18fcd4892448", - "metadata": { - "id": "f1a8560a-c91b-48db-af1c-18fcd4892448" - }, - "source": [ - "## Exercise - Kernel Authoring - Book Histogram - SOLUTION\n", - "\n", - "Let's learn to use some advanced CUDA features like shared memory, atomics, and [cuda.cooperative](https://nvidia.github.io/cccl/unstable/python/coop.html) to write an efficient histogram kernel to determine the most frequent characters in a collection of books.\n", - "\n", - "First, let's download our dataset." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "ce42d5e5-db1e-46da-a64a-831d0f3d59ff", - "metadata": { - "collapsed": true, - "id": "ce42d5e5-db1e-46da-a64a-831d0f3d59ff" - }, - "outputs": [], - "source": [ - "import os\n", - "\n", - "if os.getenv(\"COLAB_RELEASE_TAG\") and not os.path.exists(\"/accelerated-computing-hub-installed\"): # If running in Google Colab:\n", - " print(\"Downloading NCU package.\")\n", - " !curl -s -L -O https://developer.download.nvidia.com/compute/cuda/repos/debian12/x86_64/nsight-compute-2025.2.1_2025.2.1.3-1_amd64.deb\n", - " print(\"Installing NCU package.\")\n", - " !dpkg -i nsight-compute-2025.2.1_2025.2.1.3-1_amd64.deb > /dev/null\n", - " !update-alternatives --install /opt/bin/ncu ncu /opt/nvidia/nsight-compute/2025.2.1/ncu 20250201 > /dev/null\n", - " print(\"Installing PIP packages.\")\n", - " !pip uninstall \"cuda-python\" --yes > /dev/null\n", - " !pip install \"numba-cuda\" \"cuda-cccl[test-cu12]\" \"nvtx\" \"nsightful[notebook] @ git+https://github.com/brycelelbach/nsightful.git\" > /dev/null 2>&1\n", - " open(\"/accelerated-computing-hub-installed\", \"a\").close()\n", - "\n", - "import numpy as np\n", - "import matplotlib.pyplot as plt\n", - "import nsightful\n", - "import urllib.request" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "6e1ae9dc-39f1-4c93-b923-85a96b45a057", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "6e1ae9dc-39f1-4c93-b923-85a96b45a057", - "outputId": "ee334037-7f39-4a91-ad60-f0408a56b4de" - }, - "outputs": [ - { - "data": { - "text/plain": [ - "('books__15m.txt', )" - ] - }, - "execution_count": 3, - "metadata": {}, - "output_type": "execute_result" - } - ], - "source": [ - "urllib.request.urlretrieve(\n", - " \"https://drive.usercontent.google.com/download?id=1MW1lPgkTq3YG9ikuq6u3d9sfpt-wKQZ0&export=download\",\n", - " \"books__15m.txt\")" - ] - }, - { - "cell_type": "markdown", - "id": "9109d3c0-e276-44cc-9f36-f8c79eb48b31", - "metadata": { - "id": "9109d3c0-e276-44cc-9f36-f8c79eb48b31" - }, - "source": [ - "A histogram kernel counts the number of times a value occurs in a dataset. To implement this, we create an array that is large enough to store all possible values (in the case of counting 1-byte ASCII characters, 256 elements). Then for the value of each element in the dataset, we increment its location in the array.\n", - "\n", - "Let's try a simple way to implement this:" - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "61c12795-b14a-4447-9dcf-9748616cc453", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "61c12795-b14a-4447-9dcf-9748616cc453", - "outputId": "141b29b8-bbf7-4224-c85a-23e49ee7abaf" - }, - "outputs": [ + "cells": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "Writing histogram_global.py\n" - ] - } - ], - "source": [ - "%%writefile histogram_global.py\n", - "\n", - "from numba import cuda\n", - "import cupy as cp\n", - "import cupyx as cpx\n", - "import sys\n", - "import os\n", - "\n", - "bins = 256\n", - "\n", - "values = cp.fromfile(\"books__15m.txt\", dtype=cp.uint8)\n", - "histogram = cp.zeros((bins), dtype=cp.int32)\n", - "\n", - "threads_per_block = 512 if len(sys.argv) < 3 else int(sys.argv[2])\n", - "items_per_thread = 8 if len(sys.argv) < 4 else int(sys.argv[3])\n", - "items_per_block = threads_per_block * items_per_thread\n", - "blocks = int(len(values) / (threads_per_block * items_per_thread))\n", - "assert values.size % items_per_block == 0\n", - "\n", - "@cuda.jit\n", - "def histogram_global(values, histogram):\n", - " for i in range(items_per_thread):\n", - " value = values[cuda.grid(1) * items_per_thread + i]\n", - " cuda.atomic.add(histogram, value, 1)\n", - "\n", - "def launch(check, output):\n", - " histogram[:] = 0 # Reset histogram to 0 each trial.\n", - " histogram_global[blocks, threads_per_block](values, histogram)\n", - "\n", - " if (check):\n", - " assert cp.sum(histogram) == len(values)\n", - "\n", - " if (output):\n", - " cp.savetxt(sys.stdout, histogram, delimiter=\",\", fmt=\"%i\")\n", - "\n", - "if len(sys.argv) >= 2 and sys.argv[1] == \"output\":\n", - " launch(check=True, output=True)\n", - "elif os.getenv(\"NV_COMPUTE_PROFILER_PERFWORKS_DIR\"): # Running under `ncu`.\n", - " launch(check=False, output=False) # `ncu` slows things down; so just launch once when running under it.\n", - "else:\n", - " launch(check=True, output=False)\n", - " D = cpx.profiler.benchmark(launch, (False, False), n_repeat=15, n_warmup=4).gpu_times[0]\n", - " print(f\"{D.mean():.3g} s ± {(D.std() / D.mean()):.2%} (mean ± relative stdev of {D.size} runs)\")" - ] - }, - { - "cell_type": "markdown", - "id": "27e30efa-3a37-402f-9414-e444214d8ce6", - "metadata": { - "id": "27e30efa-3a37-402f-9414-e444214d8ce6" - }, - "source": [ - "Now let's make sure it runs and check the output." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "b3f32240-ad7b-4717-98b3-82b0298a099a", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" - }, - "id": "b3f32240-ad7b-4717-98b3-82b0298a099a", - "outputId": "cdf42faf-b6e6-45ad-85e2-d3b0d4c87ad5" - }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.00858 s ± 3.22% (mean ± relative stdev of 15 runs)\n" - ] - } - ], - "source": [ - "!python histogram_global.py" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "815cb072-b3a0-47aa-af6c-dd66c626a440", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 487 + "cell_type": "markdown", + "metadata": { + "id": "f1a8560a-c91b-48db-af1c-18fcd4892448" + }, + "source": [ + "## Exercise - Kernel Authoring - Book Histogram - SOLUTION\n", + "\n", + "Let's learn to use some advanced CUDA features like shared memory, atomics, and [cuda.cooperative](https://nvidia.github.io/cccl/unstable/python/coop.html) to write an efficient histogram kernel to determine the most frequent characters in a collection of books.\n", + "\n", + "First, let's download our dataset." + ], + "id": "f1a8560a-c91b-48db-af1c-18fcd4892448" }, - "id": "815cb072-b3a0-47aa-af6c-dd66c626a440", - "outputId": "d7a3d2e2-1df5-4681-c6a3-923cfb921c52" - }, - "outputs": [ { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAnUAAAHWCAYAAAARl3+JAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAMUNJREFUeJzt3Xt4FOXB/vF7E5IJAXY5J6EuRAERDAQU4cUTUdFoqS3WKqJAxFOLhxJQ1OhPYrB1AUXQSks5g/jWE4qnCiq6iIigEJR4QEUCqXJSdBeiLjSZ3x++rEYSJHGzwzz5fq7ruS539pnsvc4V9/aZmY3Htm1bAAAAcLUEpwMAAADgl6PUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABGjkdwEmVlZX6/PPP1axZM3k8HqfjAACABsy2be3Zs0ft2rVTQkLt190adKn7/PPP5ff7nY4BAAAQVVZWpqOOOqrW+zXoUtesWTNJ3//L83q9DqcBAAANWTgclt/vj/aT2mrQpe7AKVev10upAwAAR4S6XhLGjRIAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABggEZOBzgSZBUuVYKV6nQMAABwBCqdMNDpCIeFlToAAAADUOoAAAAMUOtSt2vXLo0cOVLt27eXZVlKT09Xbm6uVq5cKUnKzMyUx+ORx+NRkyZNdMIJJ+jxxx+v8jO+/fZbtWzZUq1bt1YkEqn2dRYtWqScnBz5fD41bdpUPXr00Pjx47V7925J0rx586Kv8+ORkpJS27cEAADgerUudRdeeKGKi4s1f/58ffTRR3rmmWeUk5OjL7/8Mjpn/Pjx2rZtm4qLi3XSSSdp8ODBeuONN6LPL1q0SMcff7yOO+44LV68+KDXuP322zV48GCddNJJeuGFF1RSUqLJkyfrnXfe0UMPPRSd5/V6tW3btipjy5YttX1LAAAArlerGyW+/vprrVixQsFgUP3795ckdejQQX369Kkyr1mzZkpPT1d6erqmTZumhQsX6tlnn9XJJ58sSZo9e7aGDh0q27Y1e/ZsDR48OLrvmjVrdPfdd2vq1KkaNWpUdHtmZqbOPvtsff3119FtHo9H6enptX7TAAAApqnVSl3Tpk3VtGlTLV68uMbTpj/VqFEjJSUlad++fZKkTZs2adWqVbr44ot18cUXa8WKFVVW1x5++GE1bdpU1157bbU/r3nz5rWJDAAA0CDUqtQ1atRI8+bN0/z589W8eXOdcsopuu222/Tuu+9WO3/fvn0KBAIKhUI688wzJUlz5szReeedpxYtWqhly5bKzc3V3Llzo/t8/PHHOuaYY5SUlPSzeUKhULRoHhjnnXdejfMjkYjC4XCVAQAAYII6XVP3+eef65lnntG5556rYDCoE044QfPmzYvOueWWW9S0aVOlpqZq4sSJmjBhggYOHKiKigrNnz9fQ4cOjc4dOnSo5s2bp8rKSkmSbduHnaVZs2Zav359lTFr1qwa5wcCAfl8vujw+/21ffsAAABHJI9dmxZVg6uuukovvfSStmzZoszMTA0dOlSXX365mjZtqrS0NHk8HknSv//9bw0cOFCJiYlV9q+oqNCLL76os88+W6NGjdKcOXO0e/fuQ67WzZs3T/n5+VWusfs5kUikymnjcDgsv98vf/5jfPkwAACoVry+fDgcDsvn8ykUCsnr9dZ6/5h8T123bt1UXl4efdy6dWt16tRJ6enp0UInfX+DxCWXXHLQ6toll1yi2bNnS5IuvfRS7d27V3//+9+rfa3alLifsixLXq+3ygAAADBBre5+/fLLL3XRRRfpiiuuUI8ePdSsWTO9/fbbmjRpkn73u98dct9du3bp2Wef1TPPPKOsrKwqzw0fPlwXXHCBdu/erb59++rmm2/WjTfeqM8++0wXXHCB2rVrp08++UTTp0/XqaeeGr0r1rZtbd++/aDXatu2rRIS+F5lAADQcNSq1DVt2lR9+/bVlClTtGnTJu3fv19+v19XX321brvttkPuu2DBAjVp0kRnnXXWQc+dddZZaty4sRYuXKg///nPmjhxok488URNmzZN06dPV2VlpTp27Kg//OEPysvLi+4XDoeVkZFx0M/btm0bX3UCAAAalJhcU+dWB85dc00dAACoSYO6pg4AAADOqtXpV1OVFOVy0wQAAHA1VuoAAAAMQKkDAAAwAKUOAADAAFxTJymrcCl3vwKod/G6gw5Aw8RKHQAAgAEodQAAAAag1AEAABiAUgcAAGAAV5e6yspKBQIBHX300WrcuLGys7P1xBNPOB0L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zcFUAAAAznFlqQMAAEBVrrv7tT6UFOXK6/U6HQMAAKDOWKkDAAAwAKUOAADAAJx+lZRVuFQJVqrTMeqsdMJApyMAAACHsVIHAABgAEodAACAASh1AAAABqDUAQAAGMC1pW7BggVq1aqVIpFIle2DBg3SsGHDHEoFAADgDNeWuosuukgVFRV65plnott27typ559/XldccYWDyQAAAOLPtaWucePGuvTSSzV37tzotoULF6p9+/bKycmpdp9IJKJwOFxlAAAAmMC1pU6Srr76ar344ov67LPPJEnz5s3T5ZdfLo/HU+38QCAgn88XHX6/P55xAQAA6o2rS12vXr2UnZ2tBQsWaO3atXrvvfd0+eWX1zi/oKBAoVAoOsrKyuIXFgAAoB65/i9KXHXVVZo6dao+++wzDRgw4JCrb5ZlybKsOKYDAACID1ev1EnSpZdeqv/85z+aOXMmN0gAAIAGy/Wlzufz6cILL1TTpk01aNAgp+MAAAA4wvWlTpI+++wzXXbZZZxaBQAADZarr6n76quvFAwGFQwG9fe//93pOAAAAI5xdanr1auXvvrqK02cOFFdunSp888pKcqV1+uNYTIAAID4cnWpKy0tdToCAADAEcGIa+oAAAAaOkodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGKCR0wGcZNu2JCkcDjucBAAANHQH+siBflJbDbrUffnll5Ikv9/vcBIAAIDv7dmzRz6fr9b7NehS17JlS0nS1q1b6/QvD/EXDofl9/tVVlYmr9frdBwcBo6Z+3DM3Idj5j7VHTPbtrVnzx61a9euTj+zQZe6hITvLyn0+Xz8EriM1+vlmLkMx8x9OGbuwzFzn58es1+yyMSNEgAAAAag1AEAABigQZc6y7JUWFgoy7KcjoLDxDFzH46Z+3DM3Idj5j71ccw8dl3vmwUAAMARo0Gv1AEAAJiCUgcAAGAASh0AAIABjC9106ZNU2ZmplJSUtS3b1+tWbPmkPMff/xxHXfccUpJSVH37t3173//O05JcUBtjtm8efPk8XiqjJSUlDimbdhee+01nX/++WrXrp08Ho8WL178s/sEg0GdcMIJsixLnTp10rx58+o9J35Q22MWDAYP+h3zeDzavn17fAJDgUBAJ510kpo1a6a2bdtq0KBB2rhx48/ux+eZc+pyzGLxeWZ0qXv00Uc1ZswYFRYWat26dcrOzlZubq527txZ7fw33nhDQ4YM0ZVXXqni4mINGjRIgwYNUklJSZyTN1y1PWbS91/cuG3btujYsmVLHBM3bOXl5crOzta0adMOa/7mzZs1cOBAnXHGGVq/fr3y8/N11VVXaenSpfWcFAfU9pgdsHHjxiq/Z23btq2nhPip5cuX67rrrtObb76pl156Sfv379c555yj8vLyGvfh88xZdTlmUgw+z2yD9enTx77uuuuijysqKux27drZgUCg2vkXX3yxPXDgwCrb+vbta//xj3+s15z4QW2P2dy5c22fzxendDgUSfZTTz11yDk333yzffzxx1fZNnjwYDs3N7cek6Emh3PMXn31VVuS/dVXX8UlE37ezp07bUn28uXLa5zD59mR5XCOWSw+z4xdqdu3b5/Wrl2rAQMGRLclJCRowIABWrVqVbX7rFq1qsp8ScrNza1xPmKrLsdMkvbu3asOHTrI7/frd7/7nd577714xEUd8DvmXj179lRGRobOPvtsrVy50uk4DVooFJL0w98vrw6/a0eWwzlm0i//PDO21H3xxReqqKhQWlpale1paWk1Xguyffv2Ws1HbNXlmHXp0kVz5szR008/rYULF6qyslInn3yy/vOf/8QjMmqppt+xcDisb7/91qFUOJSMjAxNnz5dixYt0qJFi+T3+5WTk6N169Y5Ha1BqqysVH5+vk455RRlZWXVOI/PsyPH4R6zWHyeNYpFYMAp/fr1U79+/aKPTz75ZHXt2lX//Oc/dddddzmYDDBDly5d1KVLl+jjk08+WZs2bdKUKVP00EMPOZisYbruuutUUlKi119/3ekoOEyHe8xi8Xlm7Epd69atlZiYqB07dlTZvmPHDqWnp1e7T3p6eq3mI7bqcsx+KikpSb169dInn3xSHxHxC9X0O+b1etW4cWOHUqG2+vTpw++YA66//no999xzevXVV3XUUUcdci6fZ0eG2hyzn6rL55mxpS45OVknnniili1bFt1WWVmpZcuWVWnCP9avX78q8yXppZdeqnE+Yqsux+ynKioqtGHDBmVkZNRXTPwC/I6ZYf369fyOxZFt27r++uv11FNP6ZVXXtHRRx/9s/vwu+asuhyzn6rT59kvus3iCPfII4/YlmXZ8+bNs99//337mmuusZs3b25v377dtm3bHjZsmH3rrbdG569cudJu1KiRfe+999offPCBXVhYaCclJdkbNmxw6i00OLU9ZkVFRfbSpUvtTZs22WvXrrUvueQSOyUlxX7vvfecegsNyp49e+zi4mK7uLjYlmTfd999dnFxsb1lyxbbtm371ltvtYcNGxad/+mnn9qpqan22LFj7Q8++MCeNm2anZiYaC9ZssSpt9Dg1PaYTZkyxV68eLH98ccf2xs2bLBHjRplJyQk2C+//LJTb6HBGTlypO3z+exgMGhv27YtOr755pvoHD7Pjix1OWax+DwzutTZtm3/7W9/s9u3b28nJyfbffr0sd98883oc/3797fz8vKqzH/sscfsY4891k5OTraPP/54+/nnn49zYtTmmOXn50fnpqWl2b/+9a/tdevWOZC6YTrwdRc/HQeOUV5ent2/f/+D9unZs6ednJxsH3PMMfbcuXPjnrshq+0xmzhxot2xY0c7JSXFbtmypZ2Tk2O/8sorzoRvoKo7XpKq/O7weXZkqcsxi8Xnmef/XhwAAAAuZuw1dQAAAA0JpQ4AAMAAlDoAAAADUOoAAAAMQKkDAAAwAKUOAADAAJQ6AAAAA1DqAAAADECpAwAAOAyvvfaazj//fLVr104ej0eLFy+u9c+wbVv33nuvjj32WFmWpV/96lf661//GpN8lDoAcFBpaak8Ho/Wr1/vdBQAP6O8vFzZ2dmaNm1anX/GqFGjNGvWLN1777368MMP9cwzz6hPnz4xydcoJj8FAADAcOedd57OO++8Gp+PRCK6/fbb9a9//Utff/21srKyNHHiROXk5EiSPvjgA/3jH/9QSUmJunTpIkk6+uijY5aPlToADVplZaUmTZqkTp06ybIstW/fPnoqZMOGDTrzzDPVuHFjtWrVStdcc4327t0b3TcnJ0f5+flVft6gQYN0+eWXRx9nZmbq7rvv1hVXXKFmzZqpffv2mjFjRvT5A/9B79WrlzweT/Q//gDc5/rrr9eqVav0yCOP6N1339VFF12kc889Vx9//LEk6dlnn9Uxxxyj5557TkcffbQyMzN11VVXaffu3TF5fUodgAatoKBAEyZM0B133KH3339f//u//6u0tDSVl5crNzdXLVq00FtvvaXHH39cL7/8sq6//vpav8bkyZPVu3dvFRcX69prr9XIkSO1ceNGSdKaNWskSS+//LK2bdumJ598MqbvD0B8bN26VXPnztXjjz+u0047TR07dtRNN92kU089VXPnzpUkffrpp9qyZYsef/xxLViwQPPmzdPatWv1hz/8ISYZOP0KoMHas2eP7r//fj344IPKy8uTJHXs2FGnnnqqZs6cqe+++04LFixQkyZNJEkPPvigzj//fE2cOFFpaWmH/Tq//vWvde2110qSbrnlFk2ZMkWvvvqqunTpojZt2kiSWrVqpfT09Bi/QwDxsmHDBlVUVOjYY4+tsj0SiahVq1aSvj8zEIlEtGDBgui82bNn68QTT9TGjRujp2TrilIHoMH64IMPFIlEdNZZZ1X7XHZ2drTQSdIpp5yiyspKbdy4sValrkePHtF/9ng8Sk9P186dO39ZeABHlL179yoxMVFr165VYmJileeaNm0qScrIyFCjRo2qFL+uXbtK+n6lj1IHAHXUuHHjX7R/QkKCbNuusm3//v0HzUtKSqry2OPxqLKy8he9NoAjS69evVRRUaGdO3fqtNNOq3bOKaecov/+97/atGmTOnbsKEn66KOPJEkdOnT4xRm4pg5Ag9W5c2c1btxYy5YtO+i5rl276p133lF5eXl028qVK5WQkBD9v+k2bdpo27Zt0ecrKipUUlJSqwzJycnRfQEc2fbu3av169dHv4Jo8+bNWr9+vbZu3apjjz1Wl112mYYPH64nn3xSmzdv1po1axQIBPT8889LkgYMGKATTjhBV1xxhYqLi7V27Vr98Y9/1Nlnn33Qadu6oNQBaLBSUlJ0yy236Oabb9aCBQu0adMmvfnmm5o9e7Yuu+wypaSkKC8vTyUlJXr11Vd1ww03aNiwYdFTr2eeeaaef/55Pf/88/rwww81cuRIff3117XK0LZtWzVu3FhLlizRjh07FAqF6uGdAoiFt99+W7169VKvXr0kSWPGjFGvXr00btw4SdLcuXM1fPhw3XjjjerSpYsGDRqkt956S+3bt5f0/er+s88+q9atW+v000/XwIED1bVrVz3yyCMxycfpVwAN2h133KFGjRpp3Lhx+vzzz5WRkaE//elPSk1N1dKlSzVq1CiddNJJSk1N1YUXXqj77rsvuu8VV1yhd955R8OHD1ejRo00evRonXHGGbV6/UaNGumBBx7Q+PHjNW7cOJ122mkKBoMxfpcAYiEnJ+egSy5+LCkpSUVFRSoqKqpxTrt27bRo0aL6iCePfah0AAAAcAVOvwIAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAb4/xVzwo7mVWfDAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "histogram_output = !python histogram_global.py output\n", - "histogram = np.loadtxt(histogram_output, delimiter=\",\")\n", - "\n", - "# Print most frequently occuring characters.\n", - "pairs = sorted(((i,c) for i,c in enumerate(histogram) if c), key=lambda x: x[1], reverse=True)[:20]\n", - "labels = [('SPACE' if i==32 else chr(i)) if 32<=i<=126 else f'0x{i:02X}' for i,_ in pairs]\n", - "plt.barh(labels[::-1], [c for _,c in pairs][::-1])\n", - "plt.xlabel('count')\n", - "plt.tight_layout()\n", - "plt.title(\"Top 20 Bins\")\n", - "plt.show()\n", - "\n", - "length = os.path.getsize(\"books__15m.txt\")\n", - "print(f\"Characters in dataset: {length}\")" - ] - }, - { - "cell_type": "markdown", - "id": "b14fa522-b41b-4538-8c34-ecc355e55116", - "metadata": { - "id": "b14fa522-b41b-4538-8c34-ecc355e55116" - }, - "source": [ - "It looks like something is wrong - our counts are very low, and the most common characters don't make a lot of sense. Many of our increments seem to get lost!\n", - "\n", - "What's happening here is called a data race. Many different threads are trying to access the bins of the histogram at the same time.\n", - "\n", - "Imagine that two threads are trying to update the same bin.\n", - "\n", - "- Thread 0 reads the count of the bin, which is 0, and stores it in its local variable `old_count`.\n", - "- Thread 0 adds 1 to its `old_count`, producing a `new_count` of 1.\n", - "- Thread 1 reads the count of the bin, which is still 0, and stores it in its local variable `old_count`.\n", - "- Thread 1 adds 1 to its `old_count`, producing a `new_count` of 1.\n", - "- Thread 0 stores `new_count` to the bin, setting it to 1.\n", - "- Thread 1 stores `new_count` to the bin, setting it to 1, and losing the increment from thread 0!\n", - "\n", - "To fix this, we need to use atomic operations. `cuda.atomic.add(array, index, value)` will perform `array[index] += value` as a single indivisible operation. This will ensure that no increments get lost." - ] - }, - { - "cell_type": "markdown", - "id": "08f4dded-26a7-4ef8-b981-e00c569ca4d0", - "metadata": { - "id": "08f4dded-26a7-4ef8-b981-e00c569ca4d0" - }, - "source": [ - "Now let's profile our code." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "8dbd226c-66f2-43df-868a-6b024b1de24c", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + "cell_type": "code", + "metadata": { + "collapsed": true, + "id": "ce42d5e5-db1e-46da-a64a-831d0f3d59ff" + }, + "source": [ + "import os\n", + "\n", + "if os.getenv(\"COLAB_RELEASE_TAG\") and not os.path.exists(\"/accelerated-computing-hub-installed\"): # If running in Google Colab:\n", + " print(\"Downloading NCU package.\")\n", + " !curl -s -L -O https://developer.download.nvidia.com/compute/cuda/repos/debian12/x86_64/nsight-compute-2025.2.1_2025.2.1.3-1_amd64.deb\n", + " print(\"Installing NCU package.\")\n", + " !dpkg -i nsight-compute-2025.2.1_2025.2.1.3-1_amd64.deb > /dev/null\n", + " !update-alternatives --install /opt/bin/ncu ncu /opt/nvidia/nsight-compute/2025.2.1/ncu 20250201 > /dev/null\n", + " print(\"Installing PIP packages.\")\n", + " !pip uninstall \"cuda-python\" --yes > /dev/null\n", + " !pip install \"numba-cuda\" \"cuda-cccl[test-cu12]\" \"nvtx\" \"nsightful[notebook] @ git+https://github.com/brycelelbach/nsightful.git\" > /dev/null 2>&1\n", + " open(\"/accelerated-computing-hub-installed\", \"a\").close()\n", + "\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import nsightful\n", + "import urllib.request" + ], + "execution_count": null, + "outputs": [], + "id": "ce42d5e5-db1e-46da-a64a-831d0f3d59ff" }, - "id": "8dbd226c-66f2-43df-868a-6b024b1de24c", - "outputId": "a082d13a-8e86-436d-c3de-8351f337d824" - }, - "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "==PROF== Connected to process 1318 (/usr/bin/python3.12)\n", - "==PROF== Profiling \"histogram_global[abi:v1,cw51cXTLSUwv1sDUaKthrqNgqqmjgOR3W3Cw6igA9duC0hkwnNGiHEkYkrqQBFBTDEwCyQe21eqwcFW3UoDGjzpQEsgzrtUEAA_3d_3d]\": 0%....50%....100% - 30 passes\n", - "==PROF== Disconnected from process 1318\n", - "==PROF== Report: /content/histogram_global.ncu-rep\n" - ] - } - ], - "source": [ - "!ncu -f --kernel-name regex:histogram_global --set full -o histogram_global python histogram_global.py\n", - "histogram_global_csv = !ncu --import histogram_global.ncu-rep --csv" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "ad12380e-253b-4410-ab34-9479411fdf81", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 1105, - "referenced_widgets": [ - "a21053863aa949e484f30f47bcf5c045", - "061f8098ad9a430aad63a106b4629fb5", - "5e77669944a346e6b628004967e7ba75", - "804c25036085410fbbcba82f19667d1d", - "face58a5cc384e4cb2b75921d967b2c5", - "899b07bbb6304e2a9143dece489f7775", - "b881cf3221d54ac4a68c38792d53bdf5", - "0d706bed98bf41eeb56205638c01f1ec", - "2a50582b630a4e63a41f10adb53303e2", - "dbf4eef8f47e4789a62977505c049825", - "179ce686ac1f4e60b5d34951446ad599", - "4d9753b4b11f425d9476d4f69932988c", - "15e004fe6f924bf5887331f0ad9da19f", - "f02f5a0cfd354371b95987c862f10637", - "50cc8f97b44549faa0f202361f5f7e6c", - "95e56a0ec4484f06aea59796d88e0464", - "39e58d26a4a84b99ad768f93a7a3a8a5", - "1edda20eae6441a99d31a1105f30b8c6", - "05bad554df84498daf462d395e980f52", - "af61114771e442e2bb8bc033e882dbe2", - "ea81f1518f0d47b995f50df7e74f9329", - "e9a4e840c12041809843a134cc29d791", - "b6bfff789d354bdd8c630826eaae63b6", - "0952fc28a1244c46befc2bed2cd7cb4c", - "83f0975a18e0401d859f001422aebc74", - "24c64a3e43f44f1bb65cee915d1aa9ce", - "63f8786f07e74f4caef3ebf115110daa", - "1006aafeb85f4ae9b1cabe28f33184f4", - "0f94f91896304b3c861750946febeccc", - "f47dbfc47f8647a38c370a2b7be404b2", - "f440961da49a4e6c9185ae5f55373df3", - "607214ca07174871b459e301eb7ad699", - "f1930ef855f844ee947a1c9b07ed95a4" - ] + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "6e1ae9dc-39f1-4c93-b923-85a96b45a057", + "outputId": "ee334037-7f39-4a91-ad60-f0408a56b4de" + }, + "source": [ + "urllib.request.urlretrieve(\n", + " \"https://drive.usercontent.google.com/download?id=1MW1lPgkTq3YG9ikuq6u3d9sfpt-wKQZ0&export=download\",\n", + " \"books__15m.txt\")" + ], + "execution_count": 3, + "outputs": [ + { + "output_type": "execute_result", + "data": { + "text/plain": [ + "('books__15m.txt', )" + ] + } + } + ], + "id": "6e1ae9dc-39f1-4c93-b923-85a96b45a057" }, - "id": "ad12380e-253b-4410-ab34-9479411fdf81", - "outputId": "f2326771-7c13-46fa-a489-bf494d76cc1e" - }, - "outputs": [ { - "data": { - "application/javascript": "window[\"de51e67e-9ed4-11f0-a7c3-0242ac1c000c\"] = google.colab.output.setIframeHeight(-1, true, {\"interactive\": true, \"maxHeight\": 99999});\n//# sourceURL=js_65154ce01f", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" + "cell_type": "markdown", + "metadata": { + "id": "9109d3c0-e276-44cc-9f36-f8c79eb48b31" + }, + "source": [ + "A histogram kernel counts the number of times a value occurs in a dataset. To implement this, we create an array that is large enough to store all possible values (in the case of counting 1-byte ASCII characters, 256 elements). Then for the value of each element in the dataset, we increment its location in the array.\n", + "\n", + "Let's try a simple way to implement this:" + ], + "id": "9109d3c0-e276-44cc-9f36-f8c79eb48b31" }, { - "data": { - "text/html": [ - "\n", - " \n", - " " + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "61c12795-b14a-4447-9dcf-9748616cc453", + "outputId": "141b29b8-bbf7-4224-c85a-23e49ee7abaf" + }, + "source": [ + "%%writefile histogram_global.py\n", + "\n", + "from numba import cuda\n", + "import cupy as cp\n", + "import cupyx as cpx\n", + "import sys\n", + "import os\n", + "\n", + "bins = 256\n", + "\n", + "values = cp.fromfile(\"books__15m.txt\", dtype=cp.uint8)\n", + "histogram = cp.zeros((bins), dtype=cp.int32)\n", + "\n", + "threads_per_block = 512 if len(sys.argv) < 3 else int(sys.argv[2])\n", + "items_per_thread = 8 if len(sys.argv) < 4 else int(sys.argv[3])\n", + "items_per_block = threads_per_block * items_per_thread\n", + "blocks = int(len(values) / (threads_per_block * items_per_thread))\n", + "assert values.size % items_per_block == 0\n", + "\n", + "@cuda.jit\n", + "def histogram_global(values, histogram):\n", + " for i in range(items_per_thread):\n", + " value = values[cuda.grid(1) * items_per_thread + i]\n", + " cuda.atomic.add(histogram, value, 1)\n", + "\n", + "def launch(check, output):\n", + " histogram[:] = 0 # Reset histogram to 0 each trial.\n", + " histogram_global[blocks, threads_per_block](values, histogram)\n", + "\n", + " if (check):\n", + " assert cp.sum(histogram) == len(values)\n", + "\n", + " if (output):\n", + " cp.savetxt(sys.stdout, histogram, delimiter=\",\", fmt=\"%i\")\n", + "\n", + "if len(sys.argv) >= 2 and sys.argv[1] == \"output\":\n", + " launch(check=True, output=True)\n", + "elif os.getenv(\"NV_COMPUTE_PROFILER_PERFWORKS_DIR\"): # Running under `ncu`.\n", + " launch(check=False, output=False) # `ncu` slows things down; so just launch once when running under it.\n", + "else:\n", + " launch(check=True, output=False)\n", + " D = cpx.profiler.benchmark(launch, (False, False), n_repeat=15, n_warmup=4).gpu_times[0]\n", + " print(f\"{D.mean():.3g} s ± {(D.std() / D.mean()):.2%} (mean ± relative stdev of {D.size} runs)\")" ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" + "execution_count": 4, + "outputs": [ + { + "output_type": "stream", + "text": [ + "Writing histogram_global.py\n" + ] + } + ], + "id": "61c12795-b14a-4447-9dcf-9748616cc453" }, { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "a21053863aa949e484f30f47bcf5c045", - "version_major": 2, - "version_minor": 0 + "cell_type": "markdown", + "metadata": { + "id": "27e30efa-3a37-402f-9414-e444214d8ce6" }, - "text/plain": [ - "Dropdown(description='Kernel:', layout=Layout(width='400px'), options=('histogram_global',), style=Description…" - ] - }, - "metadata": {}, - "output_type": "display_data" + "source": [ + "Now let's make sure it runs and check the output." + ], + "id": "27e30efa-3a37-402f-9414-e444214d8ce6" }, { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "804c25036085410fbbcba82f19667d1d", - "version_major": 2, - "version_minor": 0 + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "b3f32240-ad7b-4717-98b3-82b0298a099a", + "outputId": "cdf42faf-b6e6-45ad-85e2-d3b0d4c87ad5" }, - "text/plain": [ - "Output()" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "nsightful.display_ncu_csv_in_notebook(histogram_global_csv)" - ] - }, - { - "cell_type": "markdown", - "id": "e1f72831-780f-4cf5-8ff1-2092ecb193d9", - "metadata": { - "id": "e1f72831-780f-4cf5-8ff1-2092ecb193d9" - }, - "source": [ - "We improved the code by separating loading from values from the histogram update and to perform striped loads using [cuda.cooperative](https://nvidia.github.io/cccl/unstable/python/coop.html)'s block load instead of doing the I/O by hand.\n", - "\n", - "While that helps a bit, our code still has major issues. It's taking thousand of cycles to issue a single operation!\n", - "\n", - "This is happening due to contention - we have hundreds of thousands of threads performing atomic updates to just 256 bins of a global histogram. All of those atomic operations have to happen in order, so they are serialized by the memory subsystem, destroying our parallelism.\n", - "\n", - "Instead, we can construct a local histogram for each block, which we will update atomically within the block. Then, we synchronize all of the threads within the block, and we perform atomic updates of the global histogram with the aggregate counts t" - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "cf7c9865-646a-4bbd-9b41-61cadfc5484c", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + "source": [ + "!python histogram_global.py" + ], + "execution_count": 5, + "outputs": [ + { + "output_type": "stream", + "text": [ + "0.00858 s ± 3.22% (mean ± relative stdev of 15 runs)\n" + ] + } + ], + "id": "b3f32240-ad7b-4717-98b3-82b0298a099a" }, - "id": "cf7c9865-646a-4bbd-9b41-61cadfc5484c", - "outputId": "851b231f-4431-4cf3-b857-c5a966a7162f" - }, - "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "Writing histogram_localized.py\n" - ] - } - ], - "source": [ - "%%writefile histogram_localized.py\n", - "\n", - "from numba import cuda\n", - "import cupy as cp\n", - "import cupyx as cpx\n", - "import cuda.coop as coop\n", - "import sys\n", - "import os\n", - "\n", - "bins = 256\n", - "\n", - "values = cp.fromfile(\"books__15m.txt\", dtype=cp.uint8)\n", - "histogram = cp.zeros((bins), dtype=cp.int32)\n", - "\n", - "threads_per_block = 512 if len(sys.argv) < 3 else int(sys.argv[2])\n", - "items_per_thread = 32 if len(sys.argv) < 4 else int(sys.argv[3])\n", - "items_per_block = threads_per_block * items_per_thread\n", - "blocks = int(len(values) / (threads_per_block * items_per_thread))\n", - "assert values.size % items_per_block == 0\n", - "\n", - "block_load = coop.block.load(cp.uint8, threads_per_block, items_per_thread, 'striped')\n", - "\n", - "@cuda.jit(link=block_load.files)\n", - "def histogram_localized(values, histogram):\n", - " items = cuda.local.array(items_per_thread, dtype=values.dtype)\n", - "\n", - " base = cuda.blockIdx.x * items_per_block\n", - "\n", - " block_load(values[base : base + items_per_block], items)\n", - "\n", - " local_histogram = cuda.shared.array(bins, dtype=histogram.dtype)\n", - "\n", - " for i in range(0, bins, threads_per_block):\n", - " bin = i + cuda.threadIdx.x\n", - " if bin < local_histogram.size:\n", - " local_histogram[bin] = 0\n", - "\n", - " cuda.syncthreads()\n", - "\n", - " for i in range(items_per_thread):\n", - " cuda.atomic.add(local_histogram, items[i], 1)\n", - "\n", - " cuda.syncthreads()\n", - "\n", - " for i in range(0, bins, threads_per_block):\n", - " bin = i + cuda.threadIdx.x\n", - " if bin < histogram.size:\n", - " cuda.atomic.add(histogram, bin, local_histogram[bin])\n", - "\n", - "def launch(check, output):\n", - " histogram[:] = 0 # Reset histogram to 0 each trial.\n", - " histogram_localized[blocks, threads_per_block](values, histogram)\n", - "\n", - " if (check):\n", - " assert cp.sum(histogram) == len(values)\n", - "\n", - " if (output):\n", - " cp.savetxt(sys.stdout, histogram, delimiter=\",\", fmt=\"%i\")\n", - "\n", - "if len(sys.argv) >= 2 and sys.argv[1] == \"output\":\n", - " launch(check=True, output=True)\n", - "elif os.getenv(\"NV_COMPUTE_PROFILER_PERFWORKS_DIR\"): # Running under `ncu`.\n", - " launch(check=False, output=False) # `ncu` slows things down; so just launch once when running under it.\n", - "else:\n", - " launch(check=True, output=False)\n", - " D = cpx.profiler.benchmark(launch, (False, False), n_repeat=15, n_warmup=4).gpu_times[0]\n", - " print(f\"{D.mean():.3g} s ± {(D.std() / D.mean()):.2%} (mean ± relative stdev of {D.size} runs)\")" - ] - }, - { - "cell_type": "markdown", - "id": "d2de69a4-644d-481d-b2c7-a8674616e9e2", - "metadata": { - "id": "d2de69a4-644d-481d-b2c7-a8674616e9e2" - }, - "source": [ - "Let's make sure it runs correctly:" - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "1b30e9b3-5a4c-4181-b642-b7def5e9f258", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 487 + }, + "id": "815cb072-b3a0-47aa-af6c-dd66c626a440", + "outputId": "d7a3d2e2-1df5-4681-c6a3-923cfb921c52" + }, + "source": [ + "histogram_output = !python histogram_global.py output\n", + "histogram = np.loadtxt(histogram_output, delimiter=\",\")\n", + "\n", + "# Print most frequently occuring characters.\n", + "pairs = sorted(((i,c) for i,c in enumerate(histogram) if c), key=lambda x: x[1], reverse=True)[:20]\n", + "labels = [('SPACE' if i==32 else chr(i)) if 32<=i<=126 else f'0x{i:02X}' for i,_ in pairs]\n", + "plt.barh(labels[::-1], [c for _,c in pairs][::-1])\n", + "plt.xlabel('count')\n", + "plt.tight_layout()\n", + "plt.title(\"Top 20 Bins\")\n", + "plt.show()\n", + "\n", + "length = os.path.getsize(\"books__15m.txt\")\n", + "print(f\"Characters in dataset: {length / 1e6:.1f} MB\")" + ], + "execution_count": null, + "outputs": [ + { + "output_type": "display_data", + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAnUAAAHWCAYAAAARl3+JAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAMUNJREFUeJzt3Xt4FOXB/vF7E5IJAXY5J6EuRAERDAQU4cUTUdFoqS3WKqJAxFOLhxJQ1OhPYrB1AUXQSks5g/jWE4qnCiq6iIigEJR4QEUCqXJSdBeiLjSZ3x++rEYSJHGzwzz5fq7ruS539pnsvc4V9/aZmY3Htm1bAAAAcLUEpwMAAADgl6PUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABGjkdwEmVlZX6/PPP1axZM3k8HqfjAACABsy2be3Zs0ft2rVTQkLt190adKn7/PPP5ff7nY4BAAAQVVZWpqOOOqrW+zXoUtesWTNJ3//L83q9DqcBAAANWTgclt/vj/aT2mrQpe7AKVev10upAwAAR4S6XhLGjRIAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABggEZOBzgSZBUuVYKV6nQMAABwBCqdMNDpCIeFlToAAAADUOoAAAAMUOtSt2vXLo0cOVLt27eXZVlKT09Xbm6uVq5cKUnKzMyUx+ORx+NRkyZNdMIJJ+jxxx+v8jO+/fZbtWzZUq1bt1YkEqn2dRYtWqScnBz5fD41bdpUPXr00Pjx47V7925J0rx586Kv8+ORkpJS27cEAADgerUudRdeeKGKi4s1f/58ffTRR3rmmWeUk5OjL7/8Mjpn/Pjx2rZtm4qLi3XSSSdp8ODBeuONN6LPL1q0SMcff7yOO+44LV68+KDXuP322zV48GCddNJJeuGFF1RSUqLJkyfrnXfe0UMPPRSd5/V6tW3btipjy5YttX1LAAAArlerGyW+/vprrVixQsFgUP3795ckdejQQX369Kkyr1mzZkpPT1d6erqmTZumhQsX6tlnn9XJJ58sSZo9e7aGDh0q27Y1e/ZsDR48OLrvmjVrdPfdd2vq1KkaNWpUdHtmZqbOPvtsff3119FtHo9H6enptX7TAAAApqnVSl3Tpk3VtGlTLV68uMbTpj/VqFEjJSUlad++fZKkTZs2adWqVbr44ot18cUXa8WKFVVW1x5++GE1bdpU1157bbU/r3nz5rWJDAAA0CDUqtQ1atRI8+bN0/z589W8eXOdcsopuu222/Tuu+9WO3/fvn0KBAIKhUI688wzJUlz5szReeedpxYtWqhly5bKzc3V3Llzo/t8/PHHOuaYY5SUlPSzeUKhULRoHhjnnXdejfMjkYjC4XCVAQAAYII6XVP3+eef65lnntG5556rYDCoE044QfPmzYvOueWWW9S0aVOlpqZq4sSJmjBhggYOHKiKigrNnz9fQ4cOjc4dOnSo5s2bp8rKSkmSbduHnaVZs2Zav359lTFr1qwa5wcCAfl8vujw+/21ffsAAABHJI9dmxZVg6uuukovvfSStmzZoszMTA0dOlSXX365mjZtqrS0NHk8HknSv//9bw0cOFCJiYlV9q+oqNCLL76os88+W6NGjdKcOXO0e/fuQ67WzZs3T/n5+VWusfs5kUikymnjcDgsv98vf/5jfPkwAACoVry+fDgcDsvn8ykUCsnr9dZ6/5h8T123bt1UXl4efdy6dWt16tRJ6enp0UInfX+DxCWXXHLQ6toll1yi2bNnS5IuvfRS7d27V3//+9+rfa3alLifsixLXq+3ygAAADBBre5+/fLLL3XRRRfpiiuuUI8ePdSsWTO9/fbbmjRpkn73u98dct9du3bp2Wef1TPPPKOsrKwqzw0fPlwXXHCBdu/erb59++rmm2/WjTfeqM8++0wXXHCB2rVrp08++UTTp0/XqaeeGr0r1rZtbd++/aDXatu2rRIS+F5lAADQcNSq1DVt2lR9+/bVlClTtGnTJu3fv19+v19XX321brvttkPuu2DBAjVp0kRnnXXWQc+dddZZaty4sRYuXKg///nPmjhxok488URNmzZN06dPV2VlpTp27Kg//OEPysvLi+4XDoeVkZFx0M/btm0bX3UCAAAalJhcU+dWB85dc00dAACoSYO6pg4AAADOqtXpV1OVFOVy0wQAAHA1VuoAAAAMQKkDAAAwAKUOAADAAFxTJymrcCl3vwKod/G6gw5Aw8RKHQAAgAEodQAAAAag1AEAABiAUgcAAGAAV5e6yspKBQIBHX300WrcuLGys7P1xBNPOB0L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zcFUAAAAznFlqQMAAEBVrrv7tT6UFOXK6/U6HQMAAKDOWKkDAAAwAKUOAADAAJx+lZRVuFQJVqrTMeqsdMJApyMAAACHsVIHAABgAEodAACAASh1AAAABqDUAQAAGMC1pW7BggVq1aqVIpFIle2DBg3SsGHDHEoFAADgDNeWuosuukgVFRV65plnott27typ559/XldccYWDyQAAAOLPtaWucePGuvTSSzV37tzotoULF6p9+/bKycmpdp9IJKJwOFxlAAAAmMC1pU6Srr76ar344ov67LPPJEnz5s3T5ZdfLo/HU+38QCAgn88XHX6/P55xAQAA6o2rS12vXr2UnZ2tBQsWaO3atXrvvfd0+eWX1zi/oKBAoVAoOsrKyuIXFgAAoB65/i9KXHXVVZo6dao+++wzDRgw4JCrb5ZlybKsOKYDAACID1ev1EnSpZdeqv/85z+aOXMmN0gAAIAGy/Wlzufz6cILL1TTpk01aNAgp+MAAAA4wvWlTpI+++wzXXbZZZxaBQAADZarr6n76quvFAwGFQwG9fe//93pOAAAAI5xdanr1auXvvrqK02cOFFdunSp888pKcqV1+uNYTIAAID4cnWpKy0tdToCAADAEcGIa+oAAAAaOkodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGKCR0wGcZNu2JCkcDjucBAAANHQH+siBflJbDbrUffnll5Ikv9/vcBIAAIDv7dmzRz6fr9b7NehS17JlS0nS1q1b6/QvD/EXDofl9/tVVlYmr9frdBwcBo6Z+3DM3Idj5j7VHTPbtrVnzx61a9euTj+zQZe6hITvLyn0+Xz8EriM1+vlmLkMx8x9OGbuwzFzn58es1+yyMSNEgAAAAag1AEAABigQZc6y7JUWFgoy7KcjoLDxDFzH46Z+3DM3Idj5j71ccw8dl3vmwUAAMARo0Gv1AEAAJiCUgcAAGAASh0AAIABjC9106ZNU2ZmplJSUtS3b1+tWbPmkPMff/xxHXfccUpJSVH37t3173//O05JcUBtjtm8efPk8XiqjJSUlDimbdhee+01nX/++WrXrp08Ho8WL178s/sEg0GdcMIJsixLnTp10rx58+o9J35Q22MWDAYP+h3zeDzavn17fAJDgUBAJ510kpo1a6a2bdtq0KBB2rhx48/ux+eZc+pyzGLxeWZ0qXv00Uc1ZswYFRYWat26dcrOzlZubq527txZ7fw33nhDQ4YM0ZVXXqni4mINGjRIgwYNUklJSZyTN1y1PWbS91/cuG3btujYsmVLHBM3bOXl5crOzta0adMOa/7mzZs1cOBAnXHGGVq/fr3y8/N11VVXaenSpfWcFAfU9pgdsHHjxiq/Z23btq2nhPip5cuX67rrrtObb76pl156Sfv379c555yj8vLyGvfh88xZdTlmUgw+z2yD9enTx77uuuuijysqKux27drZgUCg2vkXX3yxPXDgwCrb+vbta//xj3+s15z4QW2P2dy5c22fzxendDgUSfZTTz11yDk333yzffzxx1fZNnjwYDs3N7cek6Emh3PMXn31VVuS/dVXX8UlE37ezp07bUn28uXLa5zD59mR5XCOWSw+z4xdqdu3b5/Wrl2rAQMGRLclJCRowIABWrVqVbX7rFq1qsp8ScrNza1xPmKrLsdMkvbu3asOHTrI7/frd7/7nd577714xEUd8DvmXj179lRGRobOPvtsrVy50uk4DVooFJL0w98vrw6/a0eWwzlm0i//PDO21H3xxReqqKhQWlpale1paWk1Xguyffv2Ws1HbNXlmHXp0kVz5szR008/rYULF6qyslInn3yy/vOf/8QjMmqppt+xcDisb7/91qFUOJSMjAxNnz5dixYt0qJFi+T3+5WTk6N169Y5Ha1BqqysVH5+vk455RRlZWXVOI/PsyPH4R6zWHyeNYpFYMAp/fr1U79+/aKPTz75ZHXt2lX//Oc/dddddzmYDDBDly5d1KVLl+jjk08+WZs2bdKUKVP00EMPOZisYbruuutUUlKi119/3ekoOEyHe8xi8Xlm7Epd69atlZiYqB07dlTZvmPHDqWnp1e7T3p6eq3mI7bqcsx+KikpSb169dInn3xSHxHxC9X0O+b1etW4cWOHUqG2+vTpw++YA66//no999xzevXVV3XUUUcdci6fZ0eG2hyzn6rL55mxpS45OVknnniili1bFt1WWVmpZcuWVWnCP9avX78q8yXppZdeqnE+Yqsux+ynKioqtGHDBmVkZNRXTPwC/I6ZYf369fyOxZFt27r++uv11FNP6ZVXXtHRRx/9s/vwu+asuhyzn6rT59kvus3iCPfII4/YlmXZ8+bNs99//337mmuusZs3b25v377dtm3bHjZsmH3rrbdG569cudJu1KiRfe+999offPCBXVhYaCclJdkbNmxw6i00OLU9ZkVFRfbSpUvtTZs22WvXrrUvueQSOyUlxX7vvfecegsNyp49e+zi4mK7uLjYlmTfd999dnFxsb1lyxbbtm371ltvtYcNGxad/+mnn9qpqan22LFj7Q8++MCeNm2anZiYaC9ZssSpt9Dg1PaYTZkyxV68eLH98ccf2xs2bLBHjRplJyQk2C+//LJTb6HBGTlypO3z+exgMGhv27YtOr755pvoHD7Pjix1OWax+DwzutTZtm3/7W9/s9u3b28nJyfbffr0sd98883oc/3797fz8vKqzH/sscfsY4891k5OTraPP/54+/nnn49zYtTmmOXn50fnpqWl2b/+9a/tdevWOZC6YTrwdRc/HQeOUV5ent2/f/+D9unZs6ednJxsH3PMMfbcuXPjnrshq+0xmzhxot2xY0c7JSXFbtmypZ2Tk2O/8sorzoRvoKo7XpKq/O7weXZkqcsxi8Xnmef/XhwAAAAuZuw1dQAAAA0JpQ4AAMAAlDoAAAADUOoAAAAMQKkDAAAwAKUOAADAAJQ6AAAAA1DqAAAADECpAwAAOAyvvfaazj//fLVr104ej0eLFy+u9c+wbVv33nuvjj32WFmWpV/96lf661//GpN8lDoAcFBpaak8Ho/Wr1/vdBQAP6O8vFzZ2dmaNm1anX/GqFGjNGvWLN1777368MMP9cwzz6hPnz4xydcoJj8FAADAcOedd57OO++8Gp+PRCK6/fbb9a9//Utff/21srKyNHHiROXk5EiSPvjgA/3jH/9QSUmJunTpIkk6+uijY5aPlToADVplZaUmTZqkTp06ybIstW/fPnoqZMOGDTrzzDPVuHFjtWrVStdcc4327t0b3TcnJ0f5+flVft6gQYN0+eWXRx9nZmbq7rvv1hVXXKFmzZqpffv2mjFjRvT5A/9B79WrlzweT/Q//gDc5/rrr9eqVav0yCOP6N1339VFF12kc889Vx9//LEk6dlnn9Uxxxyj5557TkcffbQyMzN11VVXaffu3TF5fUodgAatoKBAEyZM0B133KH3339f//u//6u0tDSVl5crNzdXLVq00FtvvaXHH39cL7/8sq6//vpav8bkyZPVu3dvFRcX69prr9XIkSO1ceNGSdKaNWskSS+//LK2bdumJ598MqbvD0B8bN26VXPnztXjjz+u0047TR07dtRNN92kU089VXPnzpUkffrpp9qyZYsef/xxLViwQPPmzdPatWv1hz/8ISYZOP0KoMHas2eP7r//fj344IPKy8uTJHXs2FGnnnqqZs6cqe+++04LFixQkyZNJEkPPvigzj//fE2cOFFpaWmH/Tq//vWvde2110qSbrnlFk2ZMkWvvvqqunTpojZt2kiSWrVqpfT09Bi/QwDxsmHDBlVUVOjYY4+tsj0SiahVq1aSvj8zEIlEtGDBgui82bNn68QTT9TGjRujp2TrilIHoMH64IMPFIlEdNZZZ1X7XHZ2drTQSdIpp5yiyspKbdy4sValrkePHtF/9ng8Sk9P186dO39ZeABHlL179yoxMVFr165VYmJileeaNm0qScrIyFCjRo2qFL+uXbtK+n6lj1IHAHXUuHHjX7R/QkKCbNuusm3//v0HzUtKSqry2OPxqLKy8he9NoAjS69evVRRUaGdO3fqtNNOq3bOKaecov/+97/atGmTOnbsKEn66KOPJEkdOnT4xRm4pg5Ag9W5c2c1btxYy5YtO+i5rl276p133lF5eXl028qVK5WQkBD9v+k2bdpo27Zt0ecrKipUUlJSqwzJycnRfQEc2fbu3av169dHv4Jo8+bNWr9+vbZu3apjjz1Wl112mYYPH64nn3xSmzdv1po1axQIBPT8889LkgYMGKATTjhBV1xxhYqLi7V27Vr98Y9/1Nlnn33Qadu6oNQBaLBSUlJ0yy236Oabb9aCBQu0adMmvfnmm5o9e7Yuu+wypaSkKC8vTyUlJXr11Vd1ww03aNiwYdFTr2eeeaaef/55Pf/88/rwww81cuRIff3117XK0LZtWzVu3FhLlizRjh07FAqF6uGdAoiFt99+W7169VKvXr0kSWPGjFGvXr00btw4SdLcuXM1fPhw3XjjjerSpYsGDRqkt956S+3bt5f0/er+s88+q9atW+v000/XwIED1bVrVz3yyCMxycfpVwAN2h133KFGjRpp3Lhx+vzzz5WRkaE//elPSk1N1dKlSzVq1CiddNJJSk1N1YUXXqj77rsvuu8VV1yhd955R8OHD1ejRo00evRonXHGGbV6/UaNGumBBx7Q+PHjNW7cOJ122mkKBoMxfpcAYiEnJ+egSy5+LCkpSUVFRSoqKqpxTrt27bRo0aL6iCePfah0AAAAcAVOvwIAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAb4/xVzwo7mVWfDAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + } + } + ], + "id": "815cb072-b3a0-47aa-af6c-dd66c626a440" }, - "id": "1b30e9b3-5a4c-4181-b642-b7def5e9f258", - "outputId": "3a04d1d0-3409-441c-af2a-3138e9dec324" - }, - "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "0.000714 s ± 2.97% (mean ± relative stdev of 15 runs)\n" - ] - } - ], - "source": [ - "!python histogram_localized.py" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "73f0c3cd-349b-490f-b6bd-7afbeb442fff", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 487 + "cell_type": "markdown", + "metadata": { + "id": "b14fa522-b41b-4538-8c34-ecc355e55116" + }, + "source": [ + "It looks like something is wrong - our counts are very low, and the most common characters don't make a lot of sense. Many of our increments seem to get lost!\n", + "\n", + "What's happening here is called a data race. Many different threads are trying to access the bins of the histogram at the same time.\n", + "\n", + "Imagine that two threads are trying to update the same bin.\n", + "\n", + "- Thread 0 reads the count of the bin, which is 0, and stores it in its local variable `old_count`.\n", + "- Thread 0 adds 1 to its `old_count`, producing a `new_count` of 1.\n", + "- Thread 1 reads the count of the bin, which is still 0, and stores it in its local variable `old_count`.\n", + "- Thread 1 adds 1 to its `old_count`, producing a `new_count` of 1.\n", + "- Thread 0 stores `new_count` to the bin, setting it to 1.\n", + "- Thread 1 stores `new_count` to the bin, setting it to 1, and losing the increment from thread 0!\n", + "\n", + "To fix this, we need to use atomic operations. `cuda.atomic.add(array, index, value)` will perform `array[index] += value` as a single indivisible operation. This will ensure that no increments get lost." + ], + "id": "b14fa522-b41b-4538-8c34-ecc355e55116" }, - "id": "73f0c3cd-349b-490f-b6bd-7afbeb442fff", - "outputId": "01324ca9-72c3-4317-a5be-f0bf144b7b7c" - }, - "outputs": [ { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAnUAAAHWCAYAAAARl3+JAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAMUNJREFUeJzt3Xt4FOXB/vF7E5IJAXY5J6EuRAERDAQU4cUTUdFoqS3WKqJAxFOLhxJQ1OhPYrB1AUXQSks5g/jWE4qnCiq6iIigEJR4QEUCqXJSdBeiLjSZ3x++rEYSJHGzwzz5fq7ruS539pnsvc4V9/aZmY3Htm1bAAAAcLUEpwMAAADgl6PUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABGjkdwEmVlZX6/PPP1axZM3k8HqfjAACABsy2be3Zs0ft2rVTQkLt190adKn7/PPP5ff7nY4BAAAQVVZWpqOOOqrW+zXoUtesWTNJ3//L83q9DqcBAAANWTgclt/vj/aT2mrQpe7AKVev10upAwAAR4S6XhLGjRIAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABggEZOBzgSZBUuVYKV6nQMAABwBCqdMNDpCIeFlToAAAADUOoAAAAMUOtSt2vXLo0cOVLt27eXZVlKT09Xbm6uVq5cKUnKzMyUx+ORx+NRkyZNdMIJJ+jxxx+v8jO+/fZbtWzZUq1bt1YkEqn2dRYtWqScnBz5fD41bdpUPXr00Pjx47V7925J0rx586Kv8+ORkpJS27cEAADgerUudRdeeKGKi4s1f/58ffTRR3rmmWeUk5OjL7/8Mjpn/Pjx2rZtm4qLi3XSSSdp8ODBeuONN6LPL1q0SMcff7yOO+44LV68+KDXuP322zV48GCddNJJeuGFF1RSUqLJkyfrnXfe0UMPPRSd5/V6tW3btipjy5YttX1LAAAArlerGyW+/vprrVixQsFgUP3795ckdejQQX369Kkyr1mzZkpPT1d6erqmTZumhQsX6tlnn9XJJ58sSZo9e7aGDh0q27Y1e/ZsDR48OLrvmjVrdPfdd2vq1KkaNWpUdHtmZqbOPvtsff3119FtHo9H6enptX7TAAAApqnVSl3Tpk3VtGlTLV68uMbTpj/VqFEjJSUlad++fZKkTZs2adWqVbr44ot18cUXa8WKFVVW1x5++GE1bdpU1157bbU/r3nz5rWJDAAA0CDUqtQ1atRI8+bN0/z589W8eXOdcsopuu222/Tuu+9WO3/fvn0KBAIKhUI688wzJUlz5szReeedpxYtWqhly5bKzc3V3Llzo/t8/PHHOuaYY5SUlPSzeUKhULRoHhjnnXdejfMjkYjC4XCVAQAAYII6XVP3+eef65lnntG5556rYDCoE044QfPmzYvOueWWW9S0aVOlpqZq4sSJmjBhggYOHKiKigrNnz9fQ4cOjc4dOnSo5s2bp8rKSkmSbduHnaVZs2Zav359lTFr1qwa5wcCAfl8vujw+/21ffsAAABHJI9dmxZVg6uuukovvfSStmzZoszMTA0dOlSXX365mjZtqrS0NHk8HknSv//9bw0cOFCJiYlV9q+oqNCLL76os88+W6NGjdKcOXO0e/fuQ67WzZs3T/n5+VWusfs5kUikymnjcDgsv98vf/5jfPkwAACoVry+fDgcDsvn8ykUCsnr9dZ6/5h8T123bt1UXl4efdy6dWt16tRJ6enp0UInfX+DxCWXXHLQ6toll1yi2bNnS5IuvfRS7d27V3//+9+rfa3alLifsixLXq+3ygAAADBBre5+/fLLL3XRRRfpiiuuUI8ePdSsWTO9/fbbmjRpkn73u98dct9du3bp2Wef1TPPPKOsrKwqzw0fPlwXXHCBdu/erb59++rmm2/WjTfeqM8++0wXXHCB2rVrp08++UTTp0/XqaeeGr0r1rZtbd++/aDXatu2rRIS+F5lAADQcNSq1DVt2lR9+/bVlClTtGnTJu3fv19+v19XX321brvttkPuu2DBAjVp0kRnnXXWQc+dddZZaty4sRYuXKg///nPmjhxok488URNmzZN06dPV2VlpTp27Kg//OEPysvLi+4XDoeVkZFx0M/btm0bX3UCAAAalJhcU+dWB85dc00dAACoSYO6pg4AAADOqtXpV1OVFOVy0wQAAHA1VuoAAAAMQKkDAAAwAKUOAADAAFxTJymrcCl3vwKod/G6gw5Aw8RKHQAAgAEodQAAAAag1AEAABiAUgcAAGAAV5e6yspKBQIBHX300WrcuLGys7P1xBNPOB0L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zcFUAAAAznFlqQMAAEBVrrv7tT6UFOXK6/U6HQMAAKDOWKkDAAAwAKUOAADAAJx+lZRVuFQJVqrTMeqsdMJApyMAAACHsVIHAABgAEodAACAASh1AAAABqDUAQAAGMC1pW7BggVq1aqVIpFIle2DBg3SsGHDHEoFAADgDNeWuosuukgVFRV65plnott27typ559/XldccYWDyQAAAOLPtaWucePGuvTSSzV37tzotoULF6p9+/bKycmpdp9IJKJwOFxlAAAAmMC1pU6Srr76ar344ov67LPPJEnz5s3T5ZdfLo/HU+38QCAgn88XHX6/P55xAQAA6o2rS12vXr2UnZ2tBQsWaO3atXrvvfd0+eWX1zi/oKBAoVAoOsrKyuIXFgAAoB65/i9KXHXVVZo6dao+++wzDRgw4JCrb5ZlybKsOKYDAACID1ev1EnSpZdeqv/85z+aOXMmN0gAAIAGy/Wlzufz6cILL1TTpk01aNAgp+MAAAA4wvWlTpI+++wzXXbZZZxaBQAADZarr6n76quvFAwGFQwG9fe//93pOAAAAI5xdanr1auXvvrqK02cOFFdunSp888pKcqV1+uNYTIAAID4cnWpKy0tdToCAADAEcGIa+oAAAAaOkodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGKCR0wGcZNu2JCkcDjucBAAANHQH+siBflJbDbrUffnll5Ikv9/vcBIAAIDv7dmzRz6fr9b7NehS17JlS0nS1q1b6/QvD/EXDofl9/tVVlYmr9frdBwcBo6Z+3DM3Idj5j7VHTPbtrVnzx61a9euTj+zQZe6hITvLyn0+Xz8EriM1+vlmLkMx8x9OGbuwzFzn58es1+yyMSNEgAAAAag1AEAABigQZc6y7JUWFgoy7KcjoLDxDFzH46Z+3DM3Idj5j71ccw8dl3vmwUAAMARo0Gv1AEAAJiCUgcAAGAASh0AAIABjC9106ZNU2ZmplJSUtS3b1+tWbPmkPMff/xxHXfccUpJSVH37t3173//O05JcUBtjtm8efPk8XiqjJSUlDimbdhee+01nX/++WrXrp08Ho8WL178s/sEg0GdcMIJsixLnTp10rx58+o9J35Q22MWDAYP+h3zeDzavn17fAJDgUBAJ510kpo1a6a2bdtq0KBB2rhx48/ux+eZc+pyzGLxeWZ0qXv00Uc1ZswYFRYWat26dcrOzlZubq527txZ7fw33nhDQ4YM0ZVXXqni4mINGjRIgwYNUklJSZyTN1y1PWbS91/cuG3btujYsmVLHBM3bOXl5crOzta0adMOa/7mzZs1cOBAnXHGGVq/fr3y8/N11VVXaenSpfWcFAfU9pgdsHHjxiq/Z23btq2nhPip5cuX67rrrtObb76pl156Sfv379c555yj8vLyGvfh88xZdTlmUgw+z2yD9enTx77uuuuijysqKux27drZgUCg2vkXX3yxPXDgwCrb+vbta//xj3+s15z4QW2P2dy5c22fzxendDgUSfZTTz11yDk333yzffzxx1fZNnjwYDs3N7cek6Emh3PMXn31VVuS/dVXX8UlE37ezp07bUn28uXLa5zD59mR5XCOWSw+z4xdqdu3b5/Wrl2rAQMGRLclJCRowIABWrVqVbX7rFq1qsp8ScrNza1xPmKrLsdMkvbu3asOHTrI7/frd7/7nd577714xEUd8DvmXj179lRGRobOPvtsrVy50uk4DVooFJL0w98vrw6/a0eWwzlm0i//PDO21H3xxReqqKhQWlpale1paWk1Xguyffv2Ws1HbNXlmHXp0kVz5szR008/rYULF6qyslInn3yy/vOf/8QjMmqppt+xcDisb7/91qFUOJSMjAxNnz5dixYt0qJFi+T3+5WTk6N169Y5Ha1BqqysVH5+vk455RRlZWXVOI/PsyPH4R6zWHyeNYpFYMAp/fr1U79+/aKPTz75ZHXt2lX//Oc/dddddzmYDDBDly5d1KVLl+jjk08+WZs2bdKUKVP00EMPOZisYbruuutUUlKi119/3ekoOEyHe8xi8Xlm7Epd69atlZiYqB07dlTZvmPHDqWnp1e7T3p6eq3mI7bqcsx+KikpSb169dInn3xSHxHxC9X0O+b1etW4cWOHUqG2+vTpw++YA66//no999xzevXVV3XUUUcdci6fZ0eG2hyzn6rL55mxpS45OVknnniili1bFt1WWVmpZcuWVWnCP9avX78q8yXppZdeqnE+Yqsux+ynKioqtGHDBmVkZNRXTPwC/I6ZYf369fyOxZFt27r++uv11FNP6ZVXXtHRRx/9s/vwu+asuhyzn6rT59kvus3iCPfII4/YlmXZ8+bNs99//337mmuusZs3b25v377dtm3bHjZsmH3rrbdG569cudJu1KiRfe+999offPCBXVhYaCclJdkbNmxw6i00OLU9ZkVFRfbSpUvtTZs22WvXrrUvueQSOyUlxX7vvfecegsNyp49e+zi4mK7uLjYlmTfd999dnFxsb1lyxbbtm371ltvtYcNGxad/+mnn9qpqan22LFj7Q8++MCeNm2anZiYaC9ZssSpt9Dg1PaYTZkyxV68eLH98ccf2xs2bLBHjRplJyQk2C+//LJTb6HBGTlypO3z+exgMGhv27YtOr755pvoHD7Pjix1OWax+DwzutTZtm3/7W9/s9u3b28nJyfbffr0sd98883oc/3797fz8vKqzH/sscfsY4891k5OTraPP/54+/nnn49zYtTmmOXn50fnpqWl2b/+9a/tdevWOZC6YTrwdRc/HQeOUV5ent2/f/+D9unZs6ednJxsH3PMMfbcuXPjnrshq+0xmzhxot2xY0c7JSXFbtmypZ2Tk2O/8sorzoRvoKo7XpKq/O7weXZkqcsxi8Xnmef/XhwAAAAuZuw1dQAAAA0JpQ4AAMAAlDoAAAADUOoAAAAMQKkDAAAwAKUOAADAAJQ6AAAAA1DqAAAADECpAwAAOAyvvfaazj//fLVr104ej0eLFy+u9c+wbVv33nuvjj32WFmWpV/96lf661//GpN8lDoAcFBpaak8Ho/Wr1/vdBQAP6O8vFzZ2dmaNm1anX/GqFGjNGvWLN1777368MMP9cwzz6hPnz4xydcoJj8FAADAcOedd57OO++8Gp+PRCK6/fbb9a9//Utff/21srKyNHHiROXk5EiSPvjgA/3jH/9QSUmJunTpIkk6+uijY5aPlToADVplZaUmTZqkTp06ybIstW/fPnoqZMOGDTrzzDPVuHFjtWrVStdcc4327t0b3TcnJ0f5+flVft6gQYN0+eWXRx9nZmbq7rvv1hVXXKFmzZqpffv2mjFjRvT5A/9B79WrlzweT/Q//gDc5/rrr9eqVav0yCOP6N1339VFF12kc889Vx9//LEk6dlnn9Uxxxyj5557TkcffbQyMzN11VVXaffu3TF5fUodgAatoKBAEyZM0B133KH3339f//u//6u0tDSVl5crNzdXLVq00FtvvaXHH39cL7/8sq6//vpav8bkyZPVu3dvFRcX69prr9XIkSO1ceNGSdKaNWskSS+//LK2bdumJ598MqbvD0B8bN26VXPnztXjjz+u0047TR07dtRNN92kU089VXPnzpUkffrpp9qyZYsef/xxLViwQPPmzdPatWv1hz/8ISYZOP0KoMHas2eP7r//fj344IPKy8uTJHXs2FGnnnqqZs6cqe+++04LFixQkyZNJEkPPvigzj//fE2cOFFpaWmH/Tq//vWvde2110qSbrnlFk2ZMkWvvvqqunTpojZt2kiSWrVqpfT09Bi/QwDxsmHDBlVUVOjYY4+tsj0SiahVq1aSvj8zEIlEtGDBgui82bNn68QTT9TGjRujp2TrilIHoMH64IMPFIlEdNZZZ1X7XHZ2drTQSdIpp5yiyspKbdy4sValrkePHtF/9ng8Sk9P186dO39ZeABHlL179yoxMVFr165VYmJileeaNm0qScrIyFCjRo2qFL+uXbtK+n6lj1IHAHXUuHHjX7R/QkKCbNuusm3//v0HzUtKSqry2OPxqLKy8he9NoAjS69evVRRUaGdO3fqtNNOq3bOKaecov/+97/atGmTOnbsKEn66KOPJEkdOnT4xRm4pg5Ag9W5c2c1btxYy5YtO+i5rl276p133lF5eXl028qVK5WQkBD9v+k2bdpo27Zt0ecrKipUUlJSqwzJycnRfQEc2fbu3av169dHv4Jo8+bNWr9+vbZu3apjjz1Wl112mYYPH64nn3xSmzdv1po1axQIBPT8889LkgYMGKATTjhBV1xxhYqLi7V27Vr98Y9/1Nlnn33Qadu6oNQBaLBSUlJ0yy236Oabb9aCBQu0adMmvfnmm5o9e7Yuu+wypaSkKC8vTyUlJXr11Vd1ww03aNiwYdFTr2eeeaaef/55Pf/88/rwww81cuRIff3117XK0LZtWzVu3FhLlizRjh07FAqF6uGdAoiFt99+W7169VKvXr0kSWPGjFGvXr00btw4SdLcuXM1fPhw3XjjjerSpYsGDRqkt956S+3bt5f0/er+s88+q9atW+v000/XwIED1bVrVz3yyCMxycfpVwAN2h133KFGjRpp3Lhx+vzzz5WRkaE//elPSk1N1dKlSzVq1CiddNJJSk1N1YUXXqj77rsvuu8VV1yhd955R8OHD1ejRo00evRonXHGGbV6/UaNGumBBx7Q+PHjNW7cOJ122mkKBoMxfpcAYiEnJ+egSy5+LCkpSUVFRSoqKqpxTrt27bRo0aL6iCePfah0AAAAcAVOvwIAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAb4/xVzwo7mVWfDAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "histogram_output = !python histogram_localized.py output\n", - "histogram = np.loadtxt(histogram_output, delimiter=\",\")\n", - "\n", - "# Print most frequently occuring characters.\n", - "pairs = sorted(((i,c) for i,c in enumerate(histogram) if c), key=lambda x: x[1], reverse=True)[:20]\n", - "labels = [('SPACE' if i==32 else chr(i)) if 32<=i<=126 else f'0x{i:02X}' for i,_ in pairs]\n", - "plt.barh(labels[::-1], [c for _,c in pairs][::-1])\n", - "plt.xlabel('count')\n", - "plt.tight_layout()\n", - "plt.title(\"Top 20 Bins\")\n", - "plt.show()\n", - "\n", - "length = os.path.getsize(\"books__15m.txt\")\n", - "print(f\"Characters in dataset: {length}\")" - ] - }, - { - "cell_type": "markdown", - "id": "06857a59-20f3-4e39-aece-18cfbb514170", - "metadata": { - "id": "06857a59-20f3-4e39-aece-18cfbb514170" - }, - "source": [ - "Now let's profile it:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "id": "d637b6b1-fb0b-4807-b70b-c80227c0fd6f", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + "cell_type": "markdown", + "metadata": { + "id": "08f4dded-26a7-4ef8-b981-e00c569ca4d0" + }, + "source": [ + "Now let's profile our code." + ], + "id": "08f4dded-26a7-4ef8-b981-e00c569ca4d0" }, - "id": "d637b6b1-fb0b-4807-b70b-c80227c0fd6f", - "outputId": "e3790713-5a2e-4b51-dd2c-182b8bc27a5b" - }, - "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "==PROF== Connected to process 1492 (/usr/bin/python3.12)\n", - "==PROF== Profiling \"histogram_localized[abi:v1,cw51cXTLSUwv1sDUaKthrqNgqqmjgOR3W3Cw6igA9duC0hkwnNGiHEkYkrqQBFBTDEwCyQe21eqwcFW3UoDGjzpQEsgzrtUEAA_3d_3d]\": 0%....50%....100% - 30 passes\n", - "==PROF== Disconnected from process 1492\n", - "==PROF== Report: /content/histogram_localized.ncu-rep\n" - ] - } - ], - "source": [ - "!ncu -f --kernel-name regex:histogram_localized --set full -o histogram_localized python histogram_localized.py\n", - "histogram_localized_csv = !ncu --import histogram_localized.ncu-rep --csv" - ] - }, - { - "cell_type": "code", - "execution_count": 13, - "id": "114e8ff7-b6fb-42ad-abda-f6d53479c052", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/", - "height": 1111, - "referenced_widgets": [ - "72eba4c5f11b49fb89df581268cc1ddc", - "277a7612626b44a0b009d28fe486ab3d", - "6f476f6ca14a45699799d56dedbfc4a9", - "29f54431514146cea6e08d352058ccfd", - "a48f630bb3584ce294f2e79d60c4b40e", - "a122e99121234ce396fa20aa6e462574", - "ea2f4b91760f475ebc10da01166b63b8", - "8d3c55d6d03b4d79aba3d5e7b1a73f2e", - "2a64bde12026429db15794fad73e7759", - "75a37b0df227406d87849b8ccc0e8cc8", - "0dcbea49322640a28c4f3459ea50c204", - "2bf258c6b7734068a20ed1fe5bcacf61", - "a130d437cec947d3ad49ab36fcfe1ef6", - "77b1acef7c294c85a13b408d4763b865", - "b8afefd142c641dd8733d7695d9cf14a", - "6439cea566ff46e08f08022e1c0d40d6", - "c799b237ce024a29b2e23307a7ff9e67", - "c92de1f9425a4f2f85bb07555c3d51a1", - "16d240c1461a444ea9cdbbd4a856e8f3", - "c8a33b735706409e93422519ca88fd38", - "650c0471ffea4b4bad75444a904978b1", - "da059452a8744262a3ebfec6543107bf", - "bb74cb5d3eb042e0b148a8554236a950", - "9f7ceb76de8e4d9c8a6d03403fc87038", - "cb604331c58f43bda3a0135cc761ef22", - "f2ef2b3576a54144a9faadd4ea42f0f4", - "9aee114e6f204e139cad4e9797cd9cc5", - "9c1ff914f65345ddba6465fc350ffa77", - "2ebcfbe930ca4061a86c8f1ea87b8eb0", - "64037d07a15d4e238471eeccce3d0ead", - "f261d85898c54dc9a72eaf5bda2703ca", - "d7db1b0b42b948ae8c3b97b059f68cdb", - "fd8251af0c4743e29b89a76a73a27af4" - ] + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "8dbd226c-66f2-43df-868a-6b024b1de24c", + "outputId": "a082d13a-8e86-436d-c3de-8351f337d824" + }, + "source": [ + "!ncu -f --kernel-name regex:histogram_global --set full -o histogram_global python histogram_global.py\n", + "histogram_global_csv = !ncu --import histogram_global.ncu-rep --csv" + ], + "execution_count": null, + "outputs": [ + { + "output_type": "stream", + "text": [ + "==PROF== Connected to process 1318 (/usr/bin/python3.12)\n", + "==PROF== Profiling \"histogram_global[abi:v1,cw51cXTLSUwv1sDUaKthrqNgqqmjgOR3W3Cw6igA9duC0hkwnNGiHEkYkrqQBFBTDEwCyQe21eqwcFW3UoDGjzpQEsgzrtUEAA_3d_3d]\": 0%....50%....100% - 30 passes\n", + "==PROF== Disconnected from process 1318\n", + "==PROF== Report: /content/histogram_global.ncu-rep\n" + ] + } + ], + "id": "8dbd226c-66f2-43df-868a-6b024b1de24c" }, - "id": "114e8ff7-b6fb-42ad-abda-f6d53479c052", - "outputId": "1936cb5f-6708-45bc-ee39-e01ddc857b89" - }, - "outputs": [ { - "data": { - "application/javascript": "window[\"e7c6b518-9ed4-11f0-a7c3-0242ac1c000c\"] = google.colab.output.setIframeHeight(-1, true, {\"interactive\": true, \"maxHeight\": 99999});\n//# sourceURL=js_abfc25056c", - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1105, + "referenced_widgets": [ + "a21053863aa949e484f30f47bcf5c045", + "061f8098ad9a430aad63a106b4629fb5", + "5e77669944a346e6b628004967e7ba75", + "804c25036085410fbbcba82f19667d1d", + "face58a5cc384e4cb2b75921d967b2c5", + "899b07bbb6304e2a9143dece489f7775", + "b881cf3221d54ac4a68c38792d53bdf5", + "0d706bed98bf41eeb56205638c01f1ec", + "2a50582b630a4e63a41f10adb53303e2", + "dbf4eef8f47e4789a62977505c049825", + "179ce686ac1f4e60b5d34951446ad599", + "4d9753b4b11f425d9476d4f69932988c", + "15e004fe6f924bf5887331f0ad9da19f", + "f02f5a0cfd354371b95987c862f10637", + "50cc8f97b44549faa0f202361f5f7e6c", + "95e56a0ec4484f06aea59796d88e0464", + "39e58d26a4a84b99ad768f93a7a3a8a5", + "1edda20eae6441a99d31a1105f30b8c6", + "05bad554df84498daf462d395e980f52", + "af61114771e442e2bb8bc033e882dbe2", + "ea81f1518f0d47b995f50df7e74f9329", + "e9a4e840c12041809843a134cc29d791", + "b6bfff789d354bdd8c630826eaae63b6", + "0952fc28a1244c46befc2bed2cd7cb4c", + "83f0975a18e0401d859f001422aebc74", + "24c64a3e43f44f1bb65cee915d1aa9ce", + "63f8786f07e74f4caef3ebf115110daa", + "1006aafeb85f4ae9b1cabe28f33184f4", + "0f94f91896304b3c861750946febeccc", + "f47dbfc47f8647a38c370a2b7be404b2", + "f440961da49a4e6c9185ae5f55373df3", + "607214ca07174871b459e301eb7ad699", + "f1930ef855f844ee947a1c9b07ed95a4" + ] + }, + "id": "ad12380e-253b-4410-ab34-9479411fdf81", + "outputId": "f2326771-7c13-46fa-a489-bf494d76cc1e" + }, + "source": [ + "nsightful.display_ncu_csv_in_notebook(histogram_global_csv)" + ], + "execution_count": 8, + "outputs": [ + { + "output_type": "display_data", + "data": { + "application/javascript": "window[\"de51e67e-9ed4-11f0-a7c3-0242ac1c000c\"] = google.colab.output.setIframeHeight(-1, true, {\"interactive\": true, \"maxHeight\": 99999});\n//# sourceURL=js_65154ce01f", + "text/plain": [ + "" + ] + } + }, + { + "output_type": "display_data", + "data": { + "text/html": [ + "\n", + " \n", + " " + ], + "text/plain": [ + "" + ] + } + }, + { + "output_type": "display_data", + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "a21053863aa949e484f30f47bcf5c045", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Dropdown(description='Kernel:', layout=Layout(width='400px'), options=('histogram_global',), style=Description…" + ] + } + }, + { + "output_type": "display_data", + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "804c25036085410fbbcba82f19667d1d", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Output()" + ] + } + } + ], + "id": "ad12380e-253b-4410-ab34-9479411fdf81" }, { - "data": { - "text/html": [ - "\n", - " \n", - " " + "cell_type": "markdown", + "metadata": { + "id": "e1f72831-780f-4cf5-8ff1-2092ecb193d9" + }, + "source": [ + "We improved the code by separating loading from values from the histogram update and to perform striped loads using [cuda.cooperative](https://nvidia.github.io/cccl/unstable/python/coop.html)'s block load instead of doing the I/O by hand.\n", + "\n", + "While that helps a bit, our code still has major issues. It's taking thousand of cycles to issue a single operation!\n", + "\n", + "This is happening due to contention - we have hundreds of thousands of threads performing atomic updates to just 256 bins of a global histogram. All of those atomic operations have to happen in order, so they are serialized by the memory subsystem, destroying our parallelism.\n", + "\n", + "Instead, we can construct a local histogram for each block, which we will update atomically within the block. Then, we synchronize all of the threads within the block, and we perform atomic updates of the global histogram with the aggregate counts t" ], - "text/plain": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" + "id": "e1f72831-780f-4cf5-8ff1-2092ecb193d9" }, { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "72eba4c5f11b49fb89df581268cc1ddc", - "version_major": 2, - "version_minor": 0 + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "cf7c9865-646a-4bbd-9b41-61cadfc5484c", + "outputId": "851b231f-4431-4cf3-b857-c5a966a7162f" }, - "text/plain": [ - "Dropdown(description='Kernel:', layout=Layout(width='400px'), options=('histogram_localized',), style=Descript…" - ] - }, - "metadata": {}, - "output_type": "display_data" + "source": [ + "%%writefile histogram_localized.py\n", + "\n", + "from numba import cuda\n", + "import cupy as cp\n", + "import cupyx as cpx\n", + "import cuda.coop as coop\n", + "import sys\n", + "import os\n", + "\n", + "bins = 256\n", + "\n", + "values = cp.fromfile(\"books__15m.txt\", dtype=cp.uint8)\n", + "histogram = cp.zeros((bins), dtype=cp.int32)\n", + "\n", + "threads_per_block = 512 if len(sys.argv) < 3 else int(sys.argv[2])\n", + "items_per_thread = 32 if len(sys.argv) < 4 else int(sys.argv[3])\n", + "items_per_block = threads_per_block * items_per_thread\n", + "blocks = int(len(values) / (threads_per_block * items_per_thread))\n", + "assert values.size % items_per_block == 0\n", + "\n", + "block_load = coop.block.load(cp.uint8, threads_per_block, items_per_thread, 'striped')\n", + "\n", + "@cuda.jit(link=block_load.files)\n", + "def histogram_localized(values, histogram):\n", + " items = cuda.local.array(items_per_thread, dtype=values.dtype)\n", + "\n", + " base = cuda.blockIdx.x * items_per_block\n", + "\n", + " block_load(values[base : base + items_per_block], items)\n", + "\n", + " local_histogram = cuda.shared.array(bins, dtype=histogram.dtype)\n", + "\n", + " for i in range(0, bins, threads_per_block):\n", + " bin = i + cuda.threadIdx.x\n", + " if bin < local_histogram.size:\n", + " local_histogram[bin] = 0\n", + "\n", + " cuda.syncthreads()\n", + "\n", + " for i in range(items_per_thread):\n", + " cuda.atomic.add(local_histogram, items[i], 1)\n", + "\n", + " cuda.syncthreads()\n", + "\n", + " for i in range(0, bins, threads_per_block):\n", + " bin = i + cuda.threadIdx.x\n", + " if bin < histogram.size:\n", + " cuda.atomic.add(histogram, bin, local_histogram[bin])\n", + "\n", + "def launch(check, output):\n", + " histogram[:] = 0 # Reset histogram to 0 each trial.\n", + " histogram_localized[blocks, threads_per_block](values, histogram)\n", + "\n", + " if (check):\n", + " assert cp.sum(histogram) == len(values)\n", + "\n", + " if (output):\n", + " cp.savetxt(sys.stdout, histogram, delimiter=\",\", fmt=\"%i\")\n", + "\n", + "if len(sys.argv) >= 2 and sys.argv[1] == \"output\":\n", + " launch(check=True, output=True)\n", + "elif os.getenv(\"NV_COMPUTE_PROFILER_PERFWORKS_DIR\"): # Running under `ncu`.\n", + " launch(check=False, output=False) # `ncu` slows things down; so just launch once when running under it.\n", + "else:\n", + " launch(check=True, output=False)\n", + " D = cpx.profiler.benchmark(launch, (False, False), n_repeat=15, n_warmup=4).gpu_times[0]\n", + " print(f\"{D.mean():.3g} s ± {(D.std() / D.mean()):.2%} (mean ± relative stdev of {D.size} runs)\")" + ], + "execution_count": 9, + "outputs": [ + { + "output_type": "stream", + "text": [ + "Writing histogram_localized.py\n" + ] + } + ], + "id": "cf7c9865-646a-4bbd-9b41-61cadfc5484c" }, { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "29f54431514146cea6e08d352058ccfd", - "version_major": 2, - "version_minor": 0 + "cell_type": "markdown", + "metadata": { + "id": "d2de69a4-644d-481d-b2c7-a8674616e9e2" }, - "text/plain": [ - "Output()" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "nsightful.display_ncu_csv_in_notebook(histogram_localized_csv)" - ] - }, - { - "cell_type": "markdown", - "id": "9aa1da9a-097a-4f7f-9e96-8891f5d7a2a2", - "metadata": { - "id": "9aa1da9a-097a-4f7f-9e96-8891f5d7a2a2" - }, - "source": [ - "Finally, let's benchmark our two approaches." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "2a3f9ca4-b61b-4536-9896-7a41498cc986", - "metadata": { - "colab": { - "base_uri": "https://localhost:8080/" + "source": [ + "Let's make sure it runs correctly:" + ], + "id": "d2de69a4-644d-481d-b2c7-a8674616e9e2" }, - "id": "2a3f9ca4-b61b-4536-9896-7a41498cc986", - "outputId": "08bb2e07-1dda-4cf1-c2d3-7125d575cd8f" - }, - "outputs": [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "histogram_global: 0.00881 s ± 1.13% (mean ± relative stdev of 15 runs)\n", - "histogram_localized: 0.000707 s ± 3.10% (mean ± relative stdev of 15 runs)\n", - "histogram_localized speedup over histogram_global: 12.46\n" - ] - } - ], - "source": [ - "histogram_global_duration = !python histogram_global.py\n", - "histogram_localized_duration = !python histogram_localized.py\n", - "speedup = float(histogram_global_duration[0].split()[0]) / float(histogram_localized_duration[0].split()[0])\n", - "\n", - "print(f\"histogram_global: {histogram_global_duration[0]}\")\n", - "print(f\"histogram_localized: {histogram_localized_duration[0]}\")\n", - "print(f\"histogram_localized speedup over histogram_global: {speedup:.2f}\")" - ] - } - ], - "metadata": { - "accelerator": "GPU", - "colab": { - "gpuType": "T4", - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "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.13.7" - }, - "widgets": { - "application/vnd.jupyter.widget-state+json": { - "05bad554df84498daf462d395e980f52": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_f1930ef855f844ee947a1c9b07ed95a4", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## WorkloadDistribution\n\n🔧 **OPTIMIZATION**: One or more L2 Slices have a much higher number of active cycles than the average number of active cycles. Maximum instance value is 89.83% above the average, while the minimum instance value is 60.99% below the average.\n*Estimated Speedup (global): 9.132%*\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "061f8098ad9a430aad63a106b4629fb5": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": "400px" - } - }, - "0952fc28a1244c46befc2bed2cd7cb4c": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "0d706bed98bf41eeb56205638c01f1ec": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_e9a4e840c12041809843a134cc29d791", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Speed Of Light\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| DRAM Frequency | Ghz | 5.00 |\n| SM Frequency | Mhz | 584.99 |\n| Elapsed Cycles | cycle | 4,835,673 |\n| Memory Throughput | % | 6.60 |\n| DRAM Throughput | % | 0.79 |\n| Duration | ms | 8.27 |\n| L1/TEX Cache Throughput | % | 13.20 |\n| L2 Cache Throughput | % | 5.65 |\n| SM Active Cycles | cycle | 4,808,396.12 |\n| Compute (SM) Throughput | % | 1.44 |\n\n🔧 **OPTIMIZATION**: This kernel exhibits low compute throughput and memory bandwidth utilization relative to the peak performance of this device. Achieved compute throughput and/or memory bandwidth below 60.0% of peak typically indicate latency issues. Look at Scheduler Statistics and Warp State Statistics for potential reasons.\n\nℹ️ **INFO**: The ratio of peak float (fp32) to double (fp64) performance on this device is 32:1. The kernel achieved 0% of this device's fp32 peak performance and 0% of its fp64 peak performance. See the Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#roofline) for more details on roofline analysis.\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "0dcbea49322640a28c4f3459ea50c204": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_cb604331c58f43bda3a0135cc761ef22", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Compute & Memory Distribution\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Average DRAM Active Cycles | cycle | 417,585 |\n| Total DRAM Elapsed Cycles | cycle | 11,925,504 |\n| Average L1 Active Cycles | cycle | 158,097.65 |\n| Total L1 Elapsed Cycles | cycle | 6,961,192 |\n| Average L2 Active Cycles | cycle | 184,213.25 |\n| Total L2 Elapsed Cycles | cycle | 8,208,544 |\n| Average SM Active Cycles | cycle | 158,097.65 |\n| Total SM Elapsed Cycles | cycle | 6,961,192 |\n| Average SMSP Active Cycles | cycle | 158,157.98 |\n| Total SMSP Elapsed Cycles | cycle | 27,844,768 |\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "0f94f91896304b3c861750946febeccc": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "1006aafeb85f4ae9b1cabe28f33184f4": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "15e004fe6f924bf5887331f0ad9da19f": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_63f8786f07e74f4caef3ebf115110daa", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Warp State\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Warp Cycles Per Issued Instruction | cycle | 2,467.51 |\n| Warp Cycles Per Executed Instruction | cycle | 2,473.27 |\n| Avg. Active Threads Per Warp | | 32 |\n| Avg. Not Predicated Off Threads Per Warp | | 32 |\n\n🔧 **OPTIMIZATION**: On average, each warp of this kernel spends 1886.3 cycles being stalled waiting for the L1 instruction queue for local and global (LG) memory operations to be not full. Typically, this stall occurs only when executing local or global memory instructions extremely frequently. Avoid redundant global memory accesses. Try to avoid using thread-local memory by checking if dynamically indexed arrays are declared in local scope, of if the kernel has excessive register pressure causing by spills. If applicable, consider combining multiple lower-width memory operations into fewer wider memory operations and try interleaving memory operations and math instructions. This stall type represents about 76.4% of the total average of 2467.5 cycles between issuing two instructions.\n*Estimated Speedup (global): 76.45%*\n\nℹ️ **INFO**: Check the Warp Stall Sampling (All Samples) table for the top stall locations in your source based on sampling data. The Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#metrics-reference) provides more details on each stall reason.\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "16d240c1461a444ea9cdbbd4a856e8f3": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_fd8251af0c4743e29b89a76a73a27af4", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## WorkloadDistribution\n\n🔧 **OPTIMIZATION**: One or more SMs have a much higher number of active cycles than the average number of active cycles. Maximum instance value is 8.44% above the average, while the minimum instance value is 3.29% below the average.\n*Estimated Speedup (global): 7.666%*\n\n🔧 **OPTIMIZATION**: One or more SMSPs have a much higher number of active cycles than the average number of active cycles. Maximum instance value is 8.37% above the average, while the minimum instance value is 3.25% below the average.\n*Estimated Speedup (global): 7.609%*\n\n🔧 **OPTIMIZATION**: One or more L1 Slices have a much higher number of active cycles than the average number of active cycles. Maximum instance value is 8.44% above the average, while the minimum instance value is 3.29% below the average.\n*Estimated Speedup (global): 7.666%*\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "179ce686ac1f4e60b5d34951446ad599": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_83f0975a18e0401d859f001422aebc74", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Compute & Memory Distribution\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Average DRAM Active Cycles | cycle | 326,938.50 |\n| Total DRAM Elapsed Cycles | cycle | 330,405,888 |\n| Average L1 Active Cycles | cycle | 4,808,396.12 |\n| Total L1 Elapsed Cycles | cycle | 193,385,088 |\n| Average L2 Active Cycles | cycle | 718,498.38 |\n| Total L2 Elapsed Cycles | cycle | 226,160,704 |\n| Average SM Active Cycles | cycle | 4,808,396.12 |\n| Total SM Elapsed Cycles | cycle | 193,385,088 |\n| Average SMSP Active Cycles | cycle | 4,807,310.28 |\n| Total SMSP Elapsed Cycles | cycle | 773,540,352 |\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "1edda20eae6441a99d31a1105f30b8c6": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_607214ca07174871b459e301eb7ad699", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Source Counters\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Branch Instructions Ratio | % | 0.03 |\n| Branch Instructions | inst | 58,624 |\n| Branch Efficiency | % | 0 |\n| Avg. Divergent Branches | | 0 |\n\n🔧 **OPTIMIZATION**: This kernel has uncoalesced global accesses resulting in a total of 4988724 excessive sectors (68% of the total 7329833 sectors). Check the L2 Theoretical Sectors Global Excessive table for the primary source locations. The CUDA Programming Guide (https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#device-memory-accesses) has additional information on reducing uncoalesced device memory accesses.\n*Estimated Speedup (global): 6.919%*\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "24c64a3e43f44f1bb65cee915d1aa9ce": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "277a7612626b44a0b009d28fe486ab3d": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": "400px" - } - }, - "29f54431514146cea6e08d352058ccfd": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_a48f630bb3584ce294f2e79d60c4b40e", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "# histogram_localized", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "a122e99121234ce396fa20aa6e462574", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": "Tab(children=(Output(), Output(), Output(), Output(), Output(), Output(), Output(), Output(), Output(), Output…" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "2a50582b630a4e63a41f10adb53303e2": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_b6bfff789d354bdd8c630826eaae63b6", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Memory Workload\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Memory Throughput | Gbyte/s | 2.53 |\n| Mem Busy | % | 4.12 |\n| Max Bandwidth | % | 6.60 |\n| L1/TEX Hit Rate | % | 26.05 |\n| L2 Hit Rate | % | 94.95 |\n| Mem Pipes Busy | % | 1.44 |\n\n⚠️ **WARNING**: The optional metric lts__average_gcomp_input_sector_success_rate.pct could not be found. Collecting it as an additional metric could enable the rule to provide more guidance.\n\n🔧 **OPTIMIZATION**: The memory access pattern for global loads from L1TEX might not be optimal. On average, only 4.0 of the 32 bytes transmitted per sector are utilized by each thread. This could possibly be caused by a stride between threads. Check the Source Counters section for uncoalesced global loads.\n*Estimated Speedup (global): 11.55%*\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "2a64bde12026429db15794fad73e7759": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_bb74cb5d3eb042e0b148a8554236a950", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Memory Workload\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Memory Throughput | Gbyte/s | 89.06 |\n| Mem Busy | % | 45.20 |\n| Max Bandwidth | % | 28.21 |\n| L1/TEX Hit Rate | % | 0 |\n| L2 Hit Rate | % | 50.38 |\n| Mem Pipes Busy | % | 28.21 |\n\n⚠️ **WARNING**: The optional metric lts__average_gcomp_input_sector_success_rate.pct could not be found. Collecting it as an additional metric could enable the rule to provide more guidance.\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "2bf258c6b7734068a20ed1fe5bcacf61": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_f2ef2b3576a54144a9faadd4ea42f0f4", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Scheduler\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| One or More Eligible | % | 7.55 |\n| Issued Warp Per Scheduler | | 0.08 |\n| No Eligible | % | 92.45 |\n| Active Warps Per Scheduler | warp | 7.66 |\n| Eligible Warps Per Scheduler | warp | 0.27 |\n\n🔧 **OPTIMIZATION**: Every scheduler is capable of issuing one instruction per cycle, but for this kernel each scheduler only issues an instruction every 13.2 cycles. This might leave hardware resources underutilized and may lead to less optimal performance. Out of the maximum of 8 warps per scheduler, this kernel allocates an average of 7.66 active warps per scheduler, but only an average of 0.27 warps were eligible per cycle. Eligible warps are the subset of active warps that are ready to issue their next instruction. Every cycle with no eligible warp results in no instruction being issued and the issue slot remains unused. To increase the number of eligible warps, avoid possible load imbalances due to highly different execution durations per warp. Reducing stalls indicated on the Warp State Statistics and Source Counters sections can help, too.\n*Estimated Speedup (local): 54.8%*\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "2ebcfbe930ca4061a86c8f1ea87b8eb0": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "39e58d26a4a84b99ad768f93a7a3a8a5": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_f440961da49a4e6c9185ae5f55373df3", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Occupancy\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Block Limit SM | block | 16 |\n| Block Limit Registers | block | 5 |\n| Block Limit Shared Mem | block | 16 |\n| Block Limit Warps | block | 2 |\n| Theoretical Active Warps per SM | warp | 32 |\n| Theoretical Occupancy | % | 100 |\n| Achieved Occupancy | % | 91.83 |\n| Achieved Active Warps Per SM | warp | 29.39 |\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "4d9753b4b11f425d9476d4f69932988c": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_24c64a3e43f44f1bb65cee915d1aa9ce", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Scheduler\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| One or More Eligible | % | 0.30 |\n| Issued Warp Per Scheduler | | 0.00 |\n| No Eligible | % | 99.70 |\n| Active Warps Per Scheduler | warp | 7.35 |\n| Eligible Warps Per Scheduler | warp | 0.01 |\n\n🔧 **OPTIMIZATION**: Every scheduler is capable of issuing one instruction per cycle, but for this kernel each scheduler only issues an instruction every 335.6 cycles. This might leave hardware resources underutilized and may lead to less optimal performance. Out of the maximum of 8 warps per scheduler, this kernel allocates an average of 7.35 active warps per scheduler, but only an average of 0.01 warps were eligible per cycle. Eligible warps are the subset of active warps that are ready to issue their next instruction. Every cycle with no eligible warp results in no instruction being issued and the issue slot remains unused. To increase the number of eligible warps, avoid possible load imbalances due to highly different execution durations per warp. Reducing stalls indicated on the Warp State Statistics and Source Counters sections can help, too.\n*Estimated Speedup (local): 93.4%*\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "50cc8f97b44549faa0f202361f5f7e6c": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_0f94f91896304b3c861750946febeccc", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Launch\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Block Size | | 512 |\n| Function Cache Configuration | | CachePreferNone |\n| Grid Size | | 3,664 |\n| Registers Per Thread | register/thread | 18 |\n| Shared Memory Configuration Size | Kbyte | 32.77 |\n| Driver Shared Memory Per Block | byte/block | 0 |\n| Dynamic Shared Memory Per Block | byte/block | 0 |\n| Static Shared Memory Per Block | byte/block | 0 |\n| # SMs | SM | 40 |\n| Threads | thread | 1,875,968 |\n| Uses Green Context | | 0 |\n| Waves Per SM | | 45.80 |\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "5e77669944a346e6b628004967e7ba75": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "initial" - } - }, - "607214ca07174871b459e301eb7ad699": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "63f8786f07e74f4caef3ebf115110daa": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "64037d07a15d4e238471eeccce3d0ead": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "6439cea566ff46e08f08022e1c0d40d6": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_64037d07a15d4e238471eeccce3d0ead", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## PM Sampling\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Maximum Buffer Size | Mbyte | 1.05 |\n| Dropped Samples | sample | 0 |\n| Maximum Sampling Interval | cycle | 20,000 |\n| # Pass Groups | | 1 |\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "650c0471ffea4b4bad75444a904978b1": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "6f476f6ca14a45699799d56dedbfc4a9": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DescriptionStyleModel", - "state": { - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DescriptionStyleModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "StyleView", - "description_width": "initial" - } - }, - "72eba4c5f11b49fb89df581268cc1ddc": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DropdownModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DropdownModel", - "_options_labels": [ - "histogram_localized" + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "1b30e9b3-5a4c-4181-b642-b7def5e9f258", + "outputId": "3a04d1d0-3409-441c-af2a-3138e9dec324" + }, + "source": [ + "!python histogram_localized.py" ], - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "DropdownView", - "description": "Kernel:", - "description_tooltip": null, - "disabled": false, - "index": 0, - "layout": "IPY_MODEL_277a7612626b44a0b009d28fe486ab3d", - "style": "IPY_MODEL_6f476f6ca14a45699799d56dedbfc4a9" - } - }, - "75a37b0df227406d87849b8ccc0e8cc8": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_9f7ceb76de8e4d9c8a6d03403fc87038", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Compute Workload\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Executed Ipc Active | inst/cycle | 0.30 |\n| Executed Ipc Elapsed | inst/cycle | 0.27 |\n| Issue Slots Busy | % | 7.55 |\n| Issued Ipc Active | inst/cycle | 0.30 |\n| SM Busy | % | 10.97 |\n\n🔧 **OPTIMIZATION**: All compute pipelines are under-utilized. Either this kernel is very small or it doesn't issue enough warps per scheduler. Check the Launch Statistics and Scheduler Statistics sections for further details.\n*Estimated Speedup (local): 96.64%*\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "77b1acef7c294c85a13b408d4763b865": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_9c1ff914f65345ddba6465fc350ffa77", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Instruction\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Avg. Executed Instructions Per Scheduler | inst | 11,908 |\n| Executed Instructions | inst | 1,905,280 |\n| Avg. Issued Instructions Per Scheduler | inst | 11,943.96 |\n| Issued Instructions | inst | 1,911,034 |\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "804c25036085410fbbcba82f19667d1d": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_face58a5cc384e4cb2b75921d967b2c5", - "msg_id": "", + "execution_count": 10, "outputs": [ - { - "data": { - "text/markdown": "# histogram_global", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - }, - { - "data": { - "application/vnd.jupyter.widget-view+json": { - "model_id": "899b07bbb6304e2a9143dece489f7775", - "version_major": 2, - "version_minor": 0 - }, - "text/plain": "Tab(children=(Output(), Output(), Output(), Output(), Output(), Output(), Output(), Output(), Output(), Output…" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "83f0975a18e0401d859f001422aebc74": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } + { + "output_type": "stream", + "text": [ + "0.000714 s ± 2.97% (mean ± relative stdev of 15 runs)\n" + ] + } + ], + "id": "1b30e9b3-5a4c-4181-b642-b7def5e9f258" }, - "899b07bbb6304e2a9143dece489f7775": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "TabModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "TabModel", - "_titles": { - "0": "Summary", - "1": "Speed Of Light", - "2": "Memory Workload", - "3": "Compute Workload", - "4": "Compute & Memory Distribution", - "5": "Scheduler", - "6": "Warp State", - "7": "Instruction", - "8": "Launch", - "9": "PM Sampling", - "10": "Occupancy", - "11": "Source Counters", - "12": "WorkloadDistribution" + { + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 487 + }, + "id": "73f0c3cd-349b-490f-b6bd-7afbeb442fff", + "outputId": "01324ca9-72c3-4317-a5be-f0bf144b7b7c" }, - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "TabView", - "box_style": "", - "children": [ - "IPY_MODEL_b881cf3221d54ac4a68c38792d53bdf5", - "IPY_MODEL_0d706bed98bf41eeb56205638c01f1ec", - "IPY_MODEL_2a50582b630a4e63a41f10adb53303e2", - "IPY_MODEL_dbf4eef8f47e4789a62977505c049825", - "IPY_MODEL_179ce686ac1f4e60b5d34951446ad599", - "IPY_MODEL_4d9753b4b11f425d9476d4f69932988c", - "IPY_MODEL_15e004fe6f924bf5887331f0ad9da19f", - "IPY_MODEL_f02f5a0cfd354371b95987c862f10637", - "IPY_MODEL_50cc8f97b44549faa0f202361f5f7e6c", - "IPY_MODEL_95e56a0ec4484f06aea59796d88e0464", - "IPY_MODEL_39e58d26a4a84b99ad768f93a7a3a8a5", - "IPY_MODEL_1edda20eae6441a99d31a1105f30b8c6", - "IPY_MODEL_05bad554df84498daf462d395e980f52" + "source": [ + "histogram_output = !python histogram_localized.py output\n", + "histogram = np.loadtxt(histogram_output, delimiter=\",\")\n", + "\n", + "# Print most frequently occuring characters.\n", + "pairs = sorted(((i,c) for i,c in enumerate(histogram) if c), key=lambda x: x[1], reverse=True)[:20]\n", + "labels = [('SPACE' if i==32 else chr(i)) if 32<=i<=126 else f'0x{i:02X}' for i,_ in pairs]\n", + "plt.barh(labels[::-1], [c for _,c in pairs][::-1])\n", + "plt.xlabel('count')\n", + "plt.tight_layout()\n", + "plt.title(\"Top 20 Bins\")\n", + "plt.show()\n", + "\n", + "length = os.path.getsize(\"books__15m.txt\")\n", + "print(f\"Characters in dataset: {length / 1e6:.1f} MB\")" ], - "layout": "IPY_MODEL_af61114771e442e2bb8bc033e882dbe2", - "selected_index": 0 - } - }, - "8d3c55d6d03b4d79aba3d5e7b1a73f2e": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_da059452a8744262a3ebfec6543107bf", - "msg_id": "", + "execution_count": null, "outputs": [ - { - "data": { - "text/markdown": "## Speed Of Light\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| DRAM Frequency | Ghz | 4.97 |\n| SM Frequency | Mhz | 584.87 |\n| Elapsed Cycles | cycle | 175,527 |\n| Memory Throughput | % | 45.20 |\n| DRAM Throughput | % | 28.01 |\n| Duration | us | 300.10 |\n| L1/TEX Cache Throughput | % | 90.41 |\n| L2 Cache Throughput | % | 6.09 |\n| SM Active Cycles | cycle | 158,097.65 |\n| Compute (SM) Throughput | % | 28.21 |\n\n🔧 **OPTIMIZATION**: This kernel exhibits low compute throughput and memory bandwidth utilization relative to the peak performance of this device. Achieved compute throughput and/or memory bandwidth below 60.0% of peak typically indicate latency issues. Look at Scheduler Statistics and Warp State Statistics for potential reasons.\n\nℹ️ **INFO**: The ratio of peak float (fp32) to double (fp64) performance on this device is 32:1. The kernel achieved 0% of this device's fp32 peak performance and 0% of its fp64 peak performance. See the Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#roofline) for more details on roofline analysis.\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "95e56a0ec4484f06aea59796d88e0464": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_f47dbfc47f8647a38c370a2b7be404b2", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## PM Sampling\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Maximum Buffer Size | Mbyte | 2.10 |\n| Dropped Samples | sample | 0 |\n| Maximum Sampling Interval | cycle | 20,000 |\n| # Pass Groups | | 1 |\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "9aee114e6f204e139cad4e9797cd9cc5": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "9c1ff914f65345ddba6465fc350ffa77": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "9f7ceb76de8e4d9c8a6d03403fc87038": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } + { + "output_type": "display_data", + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAnUAAAHWCAYAAAARl3+JAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAMUNJREFUeJzt3Xt4FOXB/vF7E5IJAXY5J6EuRAERDAQU4cUTUdFoqS3WKqJAxFOLhxJQ1OhPYrB1AUXQSks5g/jWE4qnCiq6iIigEJR4QEUCqXJSdBeiLjSZ3x++rEYSJHGzwzz5fq7ruS539pnsvc4V9/aZmY3Htm1bAAAAcLUEpwMAAADgl6PUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABGjkdwEmVlZX6/PPP1axZM3k8HqfjAACABsy2be3Zs0ft2rVTQkLt190adKn7/PPP5ff7nY4BAAAQVVZWpqOOOqrW+zXoUtesWTNJ3//L83q9DqcBAAANWTgclt/vj/aT2mrQpe7AKVev10upAwAAR4S6XhLGjRIAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABggEZOBzgSZBUuVYKV6nQMAABwBCqdMNDpCIeFlToAAAADUOoAAAAMUOtSt2vXLo0cOVLt27eXZVlKT09Xbm6uVq5cKUnKzMyUx+ORx+NRkyZNdMIJJ+jxxx+v8jO+/fZbtWzZUq1bt1YkEqn2dRYtWqScnBz5fD41bdpUPXr00Pjx47V7925J0rx586Kv8+ORkpJS27cEAADgerUudRdeeKGKi4s1f/58ffTRR3rmmWeUk5OjL7/8Mjpn/Pjx2rZtm4qLi3XSSSdp8ODBeuONN6LPL1q0SMcff7yOO+44LV68+KDXuP322zV48GCddNJJeuGFF1RSUqLJkyfrnXfe0UMPPRSd5/V6tW3btipjy5YttX1LAAAArlerGyW+/vprrVixQsFgUP3795ckdejQQX369Kkyr1mzZkpPT1d6erqmTZumhQsX6tlnn9XJJ58sSZo9e7aGDh0q27Y1e/ZsDR48OLrvmjVrdPfdd2vq1KkaNWpUdHtmZqbOPvtsff3119FtHo9H6enptX7TAAAApqnVSl3Tpk3VtGlTLV68uMbTpj/VqFEjJSUlad++fZKkTZs2adWqVbr44ot18cUXa8WKFVVW1x5++GE1bdpU1157bbU/r3nz5rWJDAAA0CDUqtQ1atRI8+bN0/z589W8eXOdcsopuu222/Tuu+9WO3/fvn0KBAIKhUI688wzJUlz5szReeedpxYtWqhly5bKzc3V3Llzo/t8/PHHOuaYY5SUlPSzeUKhULRoHhjnnXdejfMjkYjC4XCVAQAAYII6XVP3+eef65lnntG5556rYDCoE044QfPmzYvOueWWW9S0aVOlpqZq4sSJmjBhggYOHKiKigrNnz9fQ4cOjc4dOnSo5s2bp8rKSkmSbduHnaVZs2Zav359lTFr1qwa5wcCAfl8vujw+/21ffsAAABHJI9dmxZVg6uuukovvfSStmzZoszMTA0dOlSXX365mjZtqrS0NHk8HknSv//9bw0cOFCJiYlV9q+oqNCLL76os88+W6NGjdKcOXO0e/fuQ67WzZs3T/n5+VWusfs5kUikymnjcDgsv98vf/5jfPkwAACoVry+fDgcDsvn8ykUCsnr9dZ6/5h8T123bt1UXl4efdy6dWt16tRJ6enp0UInfX+DxCWXXHLQ6toll1yi2bNnS5IuvfRS7d27V3//+9+rfa3alLifsixLXq+3ygAAADBBre5+/fLLL3XRRRfpiiuuUI8ePdSsWTO9/fbbmjRpkn73u98dct9du3bp2Wef1TPPPKOsrKwqzw0fPlwXXHCBdu/erb59++rmm2/WjTfeqM8++0wXXHCB2rVrp08++UTTp0/XqaeeGr0r1rZtbd++/aDXatu2rRIS+F5lAADQcNSq1DVt2lR9+/bVlClTtGnTJu3fv19+v19XX321brvttkPuu2DBAjVp0kRnnXXWQc+dddZZaty4sRYuXKg///nPmjhxok488URNmzZN06dPV2VlpTp27Kg//OEPysvLi+4XDoeVkZFx0M/btm0bX3UCAAAalJhcU+dWB85dc00dAACoSYO6pg4AAADOqtXpV1OVFOVy0wQAAHA1VuoAAAAMQKkDAAAwAKUOAADAAFxTJymrcCl3vwKod/G6gw5Aw8RKHQAAgAEodQAAAAag1AEAABiAUgcAAGAAV5e6yspKBQIBHX300WrcuLGys7P1xBNPOB0L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zcFUAAAAznFlqQMAAEBVrrv7tT6UFOXK6/U6HQMAAKDOWKkDAAAwAKUOAADAAJx+lZRVuFQJVqrTMeqsdMJApyMAAACHsVIHAABgAEodAACAASh1AAAABqDUAQAAGMC1pW7BggVq1aqVIpFIle2DBg3SsGHDHEoFAADgDNeWuosuukgVFRV65plnott27typ559/XldccYWDyQAAAOLPtaWucePGuvTSSzV37tzotoULF6p9+/bKycmpdp9IJKJwOFxlAAAAmMC1pU6Srr76ar344ov67LPPJEnz5s3T5ZdfLo/HU+38QCAgn88XHX6/P55xAQAA6o2rS12vXr2UnZ2tBQsWaO3atXrvvfd0+eWX1zi/oKBAoVAoOsrKyuIXFgAAoB65/i9KXHXVVZo6dao+++wzDRgw4JCrb5ZlybKsOKYDAACID1ev1EnSpZdeqv/85z+aOXMmN0gAAIAGy/Wlzufz6cILL1TTpk01aNAgp+MAAAA4wvWlTpI+++wzXXbZZZxaBQAADZarr6n76quvFAwGFQwG9fe//93pOAAAAI5xdanr1auXvvrqK02cOFFdunSp888pKcqV1+uNYTIAAID4cnWpKy0tdToCAADAEcGIa+oAAAAaOkodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGKCR0wGcZNu2JCkcDjucBAAANHQH+siBflJbDbrUffnll5Ikv9/vcBIAAIDv7dmzRz6fr9b7NehS17JlS0nS1q1b6/QvD/EXDofl9/tVVlYmr9frdBwcBo6Z+3DM3Idj5j7VHTPbtrVnzx61a9euTj+zQZe6hITvLyn0+Xz8EriM1+vlmLkMx8x9OGbuwzFzn58es1+yyMSNEgAAAAag1AEAABigQZc6y7JUWFgoy7KcjoLDxDFzH46Z+3DM3Idj5j71ccw8dl3vmwUAAMARo0Gv1AEAAJiCUgcAAGAASh0AAIABjC9106ZNU2ZmplJSUtS3b1+tWbPmkPMff/xxHXfccUpJSVH37t3173//O05JcUBtjtm8efPk8XiqjJSUlDimbdhee+01nX/++WrXrp08Ho8WL178s/sEg0GdcMIJsixLnTp10rx58+o9J35Q22MWDAYP+h3zeDzavn17fAJDgUBAJ510kpo1a6a2bdtq0KBB2rhx48/ux+eZc+pyzGLxeWZ0qXv00Uc1ZswYFRYWat26dcrOzlZubq527txZ7fw33nhDQ4YM0ZVXXqni4mINGjRIgwYNUklJSZyTN1y1PWbS91/cuG3btujYsmVLHBM3bOXl5crOzta0adMOa/7mzZs1cOBAnXHGGVq/fr3y8/N11VVXaenSpfWcFAfU9pgdsHHjxiq/Z23btq2nhPip5cuX67rrrtObb76pl156Sfv379c555yj8vLyGvfh88xZdTlmUgw+z2yD9enTx77uuuuijysqKux27drZgUCg2vkXX3yxPXDgwCrb+vbta//xj3+s15z4QW2P2dy5c22fzxendDgUSfZTTz11yDk333yzffzxx1fZNnjwYDs3N7cek6Emh3PMXn31VVuS/dVXX8UlE37ezp07bUn28uXLa5zD59mR5XCOWSw+z4xdqdu3b5/Wrl2rAQMGRLclJCRowIABWrVqVbX7rFq1qsp8ScrNza1xPmKrLsdMkvbu3asOHTrI7/frd7/7nd577714xEUd8DvmXj179lRGRobOPvtsrVy50uk4DVooFJL0w98vrw6/a0eWwzlm0i//PDO21H3xxReqqKhQWlpale1paWk1Xguyffv2Ws1HbNXlmHXp0kVz5szR008/rYULF6qyslInn3yy/vOf/8QjMmqppt+xcDisb7/91qFUOJSMjAxNnz5dixYt0qJFi+T3+5WTk6N169Y5Ha1BqqysVH5+vk455RRlZWXVOI/PsyPH4R6zWHyeNYpFYMAp/fr1U79+/aKPTz75ZHXt2lX//Oc/dddddzmYDDBDly5d1KVLl+jjk08+WZs2bdKUKVP00EMPOZisYbruuutUUlKi119/3ekoOEyHe8xi8Xlm7Epd69atlZiYqB07dlTZvmPHDqWnp1e7T3p6eq3mI7bqcsx+KikpSb169dInn3xSHxHxC9X0O+b1etW4cWOHUqG2+vTpw++YA66//no999xzevXVV3XUUUcdci6fZ0eG2hyzn6rL55mxpS45OVknnniili1bFt1WWVmpZcuWVWnCP9avX78q8yXppZdeqnE+Yqsux+ynKioqtGHDBmVkZNRXTPwC/I6ZYf369fyOxZFt27r++uv11FNP6ZVXXtHRRx/9s/vwu+asuhyzn6rT59kvus3iCPfII4/YlmXZ8+bNs99//337mmuusZs3b25v377dtm3bHjZsmH3rrbdG569cudJu1KiRfe+999offPCBXVhYaCclJdkbNmxw6i00OLU9ZkVFRfbSpUvtTZs22WvXrrUvueQSOyUlxX7vvfecegsNyp49e+zi4mK7uLjYlmTfd999dnFxsb1lyxbbtm371ltvtYcNGxad/+mnn9qpqan22LFj7Q8++MCeNm2anZiYaC9ZssSpt9Dg1PaYTZkyxV68eLH98ccf2xs2bLBHjRplJyQk2C+//LJTb6HBGTlypO3z+exgMGhv27YtOr755pvoHD7Pjix1OWax+DwzutTZtm3/7W9/s9u3b28nJyfbffr0sd98883oc/3797fz8vKqzH/sscfsY4891k5OTraPP/54+/nnn49zYtTmmOXn50fnpqWl2b/+9a/tdevWOZC6YTrwdRc/HQeOUV5ent2/f/+D9unZs6ednJxsH3PMMfbcuXPjnrshq+0xmzhxot2xY0c7JSXFbtmypZ2Tk2O/8sorzoRvoKo7XpKq/O7weXZkqcsxi8Xnmef/XhwAAAAuZuw1dQAAAA0JpQ4AAMAAlDoAAAADUOoAAAAMQKkDAAAwAKUOAADAAJQ6AAAAA1DqAAAADECpAwAAOAyvvfaazj//fLVr104ej0eLFy+u9c+wbVv33nuvjj32WFmWpV/96lf661//GpN8lDoAcFBpaak8Ho/Wr1/vdBQAP6O8vFzZ2dmaNm1anX/GqFGjNGvWLN1777368MMP9cwzz6hPnz4xydcoJj8FAADAcOedd57OO++8Gp+PRCK6/fbb9a9//Utff/21srKyNHHiROXk5EiSPvjgA/3jH/9QSUmJunTpIkk6+uijY5aPlToADVplZaUmTZqkTp06ybIstW/fPnoqZMOGDTrzzDPVuHFjtWrVStdcc4327t0b3TcnJ0f5+flVft6gQYN0+eWXRx9nZmbq7rvv1hVXXKFmzZqpffv2mjFjRvT5A/9B79WrlzweT/Q//gDc5/rrr9eqVav0yCOP6N1339VFF12kc889Vx9//LEk6dlnn9Uxxxyj5557TkcffbQyMzN11VVXaffu3TF5fUodgAatoKBAEyZM0B133KH3339f//u//6u0tDSVl5crNzdXLVq00FtvvaXHH39cL7/8sq6//vpav8bkyZPVu3dvFRcX69prr9XIkSO1ceNGSdKaNWskSS+//LK2bdumJ598MqbvD0B8bN26VXPnztXjjz+u0047TR07dtRNN92kU089VXPnzpUkffrpp9qyZYsef/xxLViwQPPmzdPatWv1hz/8ISYZOP0KoMHas2eP7r//fj344IPKy8uTJHXs2FGnnnqqZs6cqe+++04LFixQkyZNJEkPPvigzj//fE2cOFFpaWmH/Tq//vWvde2110qSbrnlFk2ZMkWvvvqqunTpojZt2kiSWrVqpfT09Bi/QwDxsmHDBlVUVOjYY4+tsj0SiahVq1aSvj8zEIlEtGDBgui82bNn68QTT9TGjRujp2TrilIHoMH64IMPFIlEdNZZZ1X7XHZ2drTQSdIpp5yiyspKbdy4sValrkePHtF/9ng8Sk9P186dO39ZeABHlL179yoxMVFr165VYmJileeaNm0qScrIyFCjRo2qFL+uXbtK+n6lj1IHAHXUuHHjX7R/QkKCbNuusm3//v0HzUtKSqry2OPxqLKy8he9NoAjS69evVRRUaGdO3fqtNNOq3bOKaecov/+97/atGmTOnbsKEn66KOPJEkdOnT4xRm4pg5Ag9W5c2c1btxYy5YtO+i5rl276p133lF5eXl028qVK5WQkBD9v+k2bdpo27Zt0ecrKipUUlJSqwzJycnRfQEc2fbu3av169dHv4Jo8+bNWr9+vbZu3apjjz1Wl112mYYPH64nn3xSmzdv1po1axQIBPT8889LkgYMGKATTjhBV1xxhYqLi7V27Vr98Y9/1Nlnn33Qadu6oNQBaLBSUlJ0yy236Oabb9aCBQu0adMmvfnmm5o9e7Yuu+wypaSkKC8vTyUlJXr11Vd1ww03aNiwYdFTr2eeeaaef/55Pf/88/rwww81cuRIff3117XK0LZtWzVu3FhLlizRjh07FAqF6uGdAoiFt99+W7169VKvXr0kSWPGjFGvXr00btw4SdLcuXM1fPhw3XjjjerSpYsGDRqkt956S+3bt5f0/er+s88+q9atW+v000/XwIED1bVrVz3yyCMxycfpVwAN2h133KFGjRpp3Lhx+vzzz5WRkaE//elPSk1N1dKlSzVq1CiddNJJSk1N1YUXXqj77rsvuu8VV1yhd955R8OHD1ejRo00evRonXHGGbV6/UaNGumBBx7Q+PHjNW7cOJ122mkKBoMxfpcAYiEnJ+egSy5+LCkpSUVFRSoqKqpxTrt27bRo0aL6iCePfah0AAAAcAVOvwIAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAb4/xVzwo7mVWfDAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + } + } + ], + "id": "73f0c3cd-349b-490f-b6bd-7afbeb442fff" }, - "a122e99121234ce396fa20aa6e462574": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "TabModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "TabModel", - "_titles": { - "0": "Summary", - "1": "Speed Of Light", - "2": "Memory Workload", - "3": "Compute Workload", - "4": "Compute & Memory Distribution", - "5": "Scheduler", - "6": "Warp State", - "7": "Instruction", - "8": "Launch", - "9": "PM Sampling", - "10": "Occupancy", - "11": "Source Counters", - "12": "WorkloadDistribution" + { + "cell_type": "markdown", + "metadata": { + "id": "06857a59-20f3-4e39-aece-18cfbb514170" }, - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "TabView", - "box_style": "", - "children": [ - "IPY_MODEL_ea2f4b91760f475ebc10da01166b63b8", - "IPY_MODEL_8d3c55d6d03b4d79aba3d5e7b1a73f2e", - "IPY_MODEL_2a64bde12026429db15794fad73e7759", - "IPY_MODEL_75a37b0df227406d87849b8ccc0e8cc8", - "IPY_MODEL_0dcbea49322640a28c4f3459ea50c204", - "IPY_MODEL_2bf258c6b7734068a20ed1fe5bcacf61", - "IPY_MODEL_a130d437cec947d3ad49ab36fcfe1ef6", - "IPY_MODEL_77b1acef7c294c85a13b408d4763b865", - "IPY_MODEL_b8afefd142c641dd8733d7695d9cf14a", - "IPY_MODEL_6439cea566ff46e08f08022e1c0d40d6", - "IPY_MODEL_c799b237ce024a29b2e23307a7ff9e67", - "IPY_MODEL_c92de1f9425a4f2f85bb07555c3d51a1", - "IPY_MODEL_16d240c1461a444ea9cdbbd4a856e8f3" + "source": [ + "Now let's profile it:" ], - "layout": "IPY_MODEL_c8a33b735706409e93422519ca88fd38", - "selected_index": 0 - } - }, - "a130d437cec947d3ad49ab36fcfe1ef6": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_9aee114e6f204e139cad4e9797cd9cc5", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Warp State\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Warp Cycles Per Issued Instruction | cycle | 101.46 |\n| Warp Cycles Per Executed Instruction | cycle | 101.76 |\n| Avg. Active Threads Per Warp | | 32 |\n| Avg. Not Predicated Off Threads Per Warp | | 31.75 |\n\n🔧 **OPTIMIZATION**: On average, each warp of this kernel spends 66.8 cycles being stalled waiting for the MIO (memory input/output) instruction queue to be not full. This stall reason is high in cases of extreme utilization of the MIO pipelines, which include special math instructions, dynamic branches, as well as shared memory instructions. When caused by shared memory accesses, trying to use fewer but wider loads can reduce pipeline pressure. This stall type represents about 65.8% of the total average of 101.5 cycles between issuing two instructions.\n*Estimated Speedup (global): 54.8%*\n\nℹ️ **INFO**: Check the Warp Stall Sampling (All Samples) table for the top stall locations in your source based on sampling data. The Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#metrics-reference) provides more details on each stall reason.\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } + "id": "06857a59-20f3-4e39-aece-18cfbb514170" }, - "a21053863aa949e484f30f47bcf5c045": { - "model_module": "@jupyter-widgets/controls", - "model_module_version": "1.5.0", - "model_name": "DropdownModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/controls", - "_model_module_version": "1.5.0", - "_model_name": "DropdownModel", - "_options_labels": [ - "histogram_global" + { + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "d637b6b1-fb0b-4807-b70b-c80227c0fd6f", + "outputId": "e3790713-5a2e-4b51-dd2c-182b8bc27a5b" + }, + "source": [ + "!ncu -f --kernel-name regex:histogram_localized --set full -o histogram_localized python histogram_localized.py\n", + "histogram_localized_csv = !ncu --import histogram_localized.ncu-rep --csv" ], - "_view_count": null, - "_view_module": "@jupyter-widgets/controls", - "_view_module_version": "1.5.0", - "_view_name": "DropdownView", - "description": "Kernel:", - "description_tooltip": null, - "disabled": false, - "index": 0, - "layout": "IPY_MODEL_061f8098ad9a430aad63a106b4629fb5", - "style": "IPY_MODEL_5e77669944a346e6b628004967e7ba75" - } - }, - "a48f630bb3584ce294f2e79d60c4b40e": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "af61114771e442e2bb8bc033e882dbe2": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "b6bfff789d354bdd8c630826eaae63b6": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "b881cf3221d54ac4a68c38792d53bdf5": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_ea81f1518f0d47b995f50df7e74f9329", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Summary\n\n### Speed Of Light\n\n🔧 **OPTIMIZATION**: This kernel exhibits low compute throughput and memory bandwidth utilization relative to the peak performance of this device. Achieved compute throughput and/or memory bandwidth below 60.0% of peak typically indicate latency issues. Look at Scheduler Statistics and Warp State Statistics for potential reasons.\n\nℹ️ **INFO**: The ratio of peak float (fp32) to double (fp64) performance on this device is 32:1. The kernel achieved 0% of this device's fp32 peak performance and 0% of its fp64 peak performance. See the Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#roofline) for more details on roofline analysis.\n\n### Memory Workload\n\n⚠️ **WARNING**: The optional metric lts__average_gcomp_input_sector_success_rate.pct could not be found. Collecting it as an additional metric could enable the rule to provide more guidance.\n\n🔧 **OPTIMIZATION**: The memory access pattern for global loads from L1TEX might not be optimal. On average, only 4.0 of the 32 bytes transmitted per sector are utilized by each thread. This could possibly be caused by a stride between threads. Check the Source Counters section for uncoalesced global loads.\n*Estimated Speedup (global): 11.55%*\n\n### Compute Workload\n\n🔧 **OPTIMIZATION**: All compute pipelines are under-utilized. Either this kernel is very small or it doesn't issue enough warps per scheduler. Check the Launch Statistics and Scheduler Statistics sections for further details.\n*Estimated Speedup (local): 99.85%*\n\n### Scheduler\n\n🔧 **OPTIMIZATION**: Every scheduler is capable of issuing one instruction per cycle, but for this kernel each scheduler only issues an instruction every 335.6 cycles. This might leave hardware resources underutilized and may lead to less optimal performance. Out of the maximum of 8 warps per scheduler, this kernel allocates an average of 7.35 active warps per scheduler, but only an average of 0.01 warps were eligible per cycle. Eligible warps are the subset of active warps that are ready to issue their next instruction. Every cycle with no eligible warp results in no instruction being issued and the issue slot remains unused. To increase the number of eligible warps, avoid possible load imbalances due to highly different execution durations per warp. Reducing stalls indicated on the Warp State Statistics and Source Counters sections can help, too.\n*Estimated Speedup (local): 93.4%*\n\n### Warp State\n\n🔧 **OPTIMIZATION**: On average, each warp of this kernel spends 1886.3 cycles being stalled waiting for the L1 instruction queue for local and global (LG) memory operations to be not full. Typically, this stall occurs only when executing local or global memory instructions extremely frequently. Avoid redundant global memory accesses. Try to avoid using thread-local memory by checking if dynamically indexed arrays are declared in local scope, of if the kernel has excessive register pressure causing by spills. If applicable, consider combining multiple lower-width memory operations into fewer wider memory operations and try interleaving memory operations and math instructions. This stall type represents about 76.4% of the total average of 2467.5 cycles between issuing two instructions.\n*Estimated Speedup (global): 76.45%*\n\nℹ️ **INFO**: Check the Warp Stall Sampling (All Samples) table for the top stall locations in your source based on sampling data. The Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#metrics-reference) provides more details on each stall reason.\n\n### Source Counters\n\n🔧 **OPTIMIZATION**: This kernel has uncoalesced global accesses resulting in a total of 4988724 excessive sectors (68% of the total 7329833 sectors). Check the L2 Theoretical Sectors Global Excessive table for the primary source locations. The CUDA Programming Guide (https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#device-memory-accesses) has additional information on reducing uncoalesced device memory accesses.\n*Estimated Speedup (global): 6.919%*\n\n### WorkloadDistribution\n\n🔧 **OPTIMIZATION**: One or more L2 Slices have a much higher number of active cycles than the average number of active cycles. Maximum instance value is 89.83% above the average, while the minimum instance value is 60.99% below the average.\n*Estimated Speedup (global): 9.132%*\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "b8afefd142c641dd8733d7695d9cf14a": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_2ebcfbe930ca4061a86c8f1ea87b8eb0", - "msg_id": "", + "execution_count": null, "outputs": [ - { - "data": { - "text/markdown": "## Launch\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Block Size | | 512 |\n| Function Cache Configuration | | CachePreferNone |\n| Grid Size | | 916 |\n| Registers Per Thread | register/thread | 47 |\n| Shared Memory Configuration Size | Kbyte | 32.77 |\n| Driver Shared Memory Per Block | byte/block | 0 |\n| Dynamic Shared Memory Per Block | byte/block | 0 |\n| Static Shared Memory Per Block | Kbyte/block | 1.02 |\n| # SMs | SM | 40 |\n| Threads | thread | 468,992 |\n| Uses Green Context | | 0 |\n| Waves Per SM | | 11.45 |\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "bb74cb5d3eb042e0b148a8554236a950": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "c799b237ce024a29b2e23307a7ff9e67": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_f261d85898c54dc9a72eaf5bda2703ca", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Occupancy\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Block Limit SM | block | 16 |\n| Block Limit Registers | block | 2 |\n| Block Limit Shared Mem | block | 32 |\n| Block Limit Warps | block | 2 |\n| Theoretical Active Warps per SM | warp | 32 |\n| Theoretical Occupancy | % | 100 |\n| Achieved Occupancy | % | 95.92 |\n| Achieved Active Warps Per SM | warp | 30.69 |\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "c8a33b735706409e93422519ca88fd38": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "c92de1f9425a4f2f85bb07555c3d51a1": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_d7db1b0b42b948ae8c3b97b059f68cdb", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Source Counters\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Branch Instructions Ratio | % | 0.01 |\n| Branch Instructions | inst | 21,984 |\n| Branch Efficiency | % | 0 |\n| Avg. Divergent Branches | | 0 |\n\n🔧 **OPTIMIZATION**: This kernel has uncoalesced shared accesses resulting in a total of 2154026 excessive wavefronts (82% of the total 2637674 wavefronts). Check the L1 Wavefronts Shared Excessive table for the primary source locations. The CUDA Best Practices Guide (https://docs.nvidia.com/cuda/cuda-c-best-practices-guide/index.html#shared-memory-in-matrix-multiplication-c-ab) has an example on optimizing shared memory accesses.\n*Estimated Speedup (global): 74.19%*\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "cb604331c58f43bda3a0135cc761ef22": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "d7db1b0b42b948ae8c3b97b059f68cdb": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "da059452a8744262a3ebfec6543107bf": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "dbf4eef8f47e4789a62977505c049825": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_0952fc28a1244c46befc2bed2cd7cb4c", - "msg_id": "", - "outputs": [ - { - "data": { - "text/markdown": "## Compute Workload\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Executed Ipc Active | inst/cycle | 0.01 |\n| Executed Ipc Elapsed | inst/cycle | 0.01 |\n| Issue Slots Busy | % | 0.30 |\n| Issued Ipc Active | inst/cycle | 0.01 |\n| SM Busy | % | 0.37 |\n\n🔧 **OPTIMIZATION**: All compute pipelines are under-utilized. Either this kernel is very small or it doesn't issue enough warps per scheduler. Check the Launch Statistics and Scheduler Statistics sections for further details.\n*Estimated Speedup (local): 99.85%*\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "e9a4e840c12041809843a134cc29d791": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } + { + "output_type": "stream", + "text": [ + "==PROF== Connected to process 1492 (/usr/bin/python3.12)\n", + "==PROF== Profiling \"histogram_localized[abi:v1,cw51cXTLSUwv1sDUaKthrqNgqqmjgOR3W3Cw6igA9duC0hkwnNGiHEkYkrqQBFBTDEwCyQe21eqwcFW3UoDGjzpQEsgzrtUEAA_3d_3d]\": 0%....50%....100% - 30 passes\n", + "==PROF== Disconnected from process 1492\n", + "==PROF== Report: /content/histogram_localized.ncu-rep\n" + ] + } + ], + "id": "d637b6b1-fb0b-4807-b70b-c80227c0fd6f" }, - "ea2f4b91760f475ebc10da01166b63b8": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_650c0471ffea4b4bad75444a904978b1", - "msg_id": "", + { + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/", + "height": 1111, + "referenced_widgets": [ + "72eba4c5f11b49fb89df581268cc1ddc", + "277a7612626b44a0b009d28fe486ab3d", + "6f476f6ca14a45699799d56dedbfc4a9", + "29f54431514146cea6e08d352058ccfd", + "a48f630bb3584ce294f2e79d60c4b40e", + "a122e99121234ce396fa20aa6e462574", + "ea2f4b91760f475ebc10da01166b63b8", + "8d3c55d6d03b4d79aba3d5e7b1a73f2e", + "2a64bde12026429db15794fad73e7759", + "75a37b0df227406d87849b8ccc0e8cc8", + "0dcbea49322640a28c4f3459ea50c204", + "2bf258c6b7734068a20ed1fe5bcacf61", + "a130d437cec947d3ad49ab36fcfe1ef6", + "77b1acef7c294c85a13b408d4763b865", + "b8afefd142c641dd8733d7695d9cf14a", + "6439cea566ff46e08f08022e1c0d40d6", + "c799b237ce024a29b2e23307a7ff9e67", + "c92de1f9425a4f2f85bb07555c3d51a1", + "16d240c1461a444ea9cdbbd4a856e8f3", + "c8a33b735706409e93422519ca88fd38", + "650c0471ffea4b4bad75444a904978b1", + "da059452a8744262a3ebfec6543107bf", + "bb74cb5d3eb042e0b148a8554236a950", + "9f7ceb76de8e4d9c8a6d03403fc87038", + "cb604331c58f43bda3a0135cc761ef22", + "f2ef2b3576a54144a9faadd4ea42f0f4", + "9aee114e6f204e139cad4e9797cd9cc5", + "9c1ff914f65345ddba6465fc350ffa77", + "2ebcfbe930ca4061a86c8f1ea87b8eb0", + "64037d07a15d4e238471eeccce3d0ead", + "f261d85898c54dc9a72eaf5bda2703ca", + "d7db1b0b42b948ae8c3b97b059f68cdb", + "fd8251af0c4743e29b89a76a73a27af4" + ] + }, + "id": "114e8ff7-b6fb-42ad-abda-f6d53479c052", + "outputId": "1936cb5f-6708-45bc-ee39-e01ddc857b89" + }, + "source": [ + "nsightful.display_ncu_csv_in_notebook(histogram_localized_csv)" + ], + "execution_count": 13, "outputs": [ - { - "data": { - "text/markdown": "## Summary\n\n### Speed Of Light\n\n🔧 **OPTIMIZATION**: This kernel exhibits low compute throughput and memory bandwidth utilization relative to the peak performance of this device. Achieved compute throughput and/or memory bandwidth below 60.0% of peak typically indicate latency issues. Look at Scheduler Statistics and Warp State Statistics for potential reasons.\n\nℹ️ **INFO**: The ratio of peak float (fp32) to double (fp64) performance on this device is 32:1. The kernel achieved 0% of this device's fp32 peak performance and 0% of its fp64 peak performance. See the Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#roofline) for more details on roofline analysis.\n\n### Memory Workload\n\n⚠️ **WARNING**: The optional metric lts__average_gcomp_input_sector_success_rate.pct could not be found. Collecting it as an additional metric could enable the rule to provide more guidance.\n\n### Compute Workload\n\n🔧 **OPTIMIZATION**: All compute pipelines are under-utilized. Either this kernel is very small or it doesn't issue enough warps per scheduler. Check the Launch Statistics and Scheduler Statistics sections for further details.\n*Estimated Speedup (local): 96.64%*\n\n### Scheduler\n\n🔧 **OPTIMIZATION**: Every scheduler is capable of issuing one instruction per cycle, but for this kernel each scheduler only issues an instruction every 13.2 cycles. This might leave hardware resources underutilized and may lead to less optimal performance. Out of the maximum of 8 warps per scheduler, this kernel allocates an average of 7.66 active warps per scheduler, but only an average of 0.27 warps were eligible per cycle. Eligible warps are the subset of active warps that are ready to issue their next instruction. Every cycle with no eligible warp results in no instruction being issued and the issue slot remains unused. To increase the number of eligible warps, avoid possible load imbalances due to highly different execution durations per warp. Reducing stalls indicated on the Warp State Statistics and Source Counters sections can help, too.\n*Estimated Speedup (local): 54.8%*\n\n### Warp State\n\n🔧 **OPTIMIZATION**: On average, each warp of this kernel spends 66.8 cycles being stalled waiting for the MIO (memory input/output) instruction queue to be not full. This stall reason is high in cases of extreme utilization of the MIO pipelines, which include special math instructions, dynamic branches, as well as shared memory instructions. When caused by shared memory accesses, trying to use fewer but wider loads can reduce pipeline pressure. This stall type represents about 65.8% of the total average of 101.5 cycles between issuing two instructions.\n*Estimated Speedup (global): 54.8%*\n\nℹ️ **INFO**: Check the Warp Stall Sampling (All Samples) table for the top stall locations in your source based on sampling data. The Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#metrics-reference) provides more details on each stall reason.\n\n### Source Counters\n\n🔧 **OPTIMIZATION**: This kernel has uncoalesced shared accesses resulting in a total of 2154026 excessive wavefronts (82% of the total 2637674 wavefronts). Check the L1 Wavefronts Shared Excessive table for the primary source locations. The CUDA Best Practices Guide (https://docs.nvidia.com/cuda/cuda-c-best-practices-guide/index.html#shared-memory-in-matrix-multiplication-c-ab) has an example on optimizing shared memory accesses.\n*Estimated Speedup (global): 74.19%*\n\n### WorkloadDistribution\n\n🔧 **OPTIMIZATION**: One or more SMs have a much higher number of active cycles than the average number of active cycles. Maximum instance value is 8.44% above the average, while the minimum instance value is 3.29% below the average.\n*Estimated Speedup (global): 7.666%*\n\n🔧 **OPTIMIZATION**: One or more SMSPs have a much higher number of active cycles than the average number of active cycles. Maximum instance value is 8.37% above the average, while the minimum instance value is 3.25% below the average.\n*Estimated Speedup (global): 7.609%*\n\n🔧 **OPTIMIZATION**: One or more L1 Slices have a much higher number of active cycles than the average number of active cycles. Maximum instance value is 8.44% above the average, while the minimum instance value is 3.29% below the average.\n*Estimated Speedup (global): 7.666%*\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } + { + "output_type": "display_data", + "data": { + "application/javascript": "window[\"e7c6b518-9ed4-11f0-a7c3-0242ac1c000c\"] = google.colab.output.setIframeHeight(-1, true, {\"interactive\": true, \"maxHeight\": 99999});\n//# sourceURL=js_abfc25056c", + "text/plain": [ + "" + ] + } + }, + { + "output_type": "display_data", + "data": { + "text/html": [ + "\n", + " \n", + " " + ], + "text/plain": [ + "" + ] + } + }, + { + "output_type": "display_data", + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "72eba4c5f11b49fb89df581268cc1ddc", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Dropdown(description='Kernel:', layout=Layout(width='400px'), options=('histogram_localized',), style=Descript…" + ] + } + }, + { + "output_type": "display_data", + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "29f54431514146cea6e08d352058ccfd", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": [ + "Output()" + ] + } + } + ], + "id": "114e8ff7-b6fb-42ad-abda-f6d53479c052" }, - "ea81f1518f0d47b995f50df7e74f9329": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } + { + "cell_type": "markdown", + "metadata": { + "id": "9aa1da9a-097a-4f7f-9e96-8891f5d7a2a2" + }, + "source": [ + "Finally, let's benchmark our two approaches." + ], + "id": "9aa1da9a-097a-4f7f-9e96-8891f5d7a2a2" }, - "f02f5a0cfd354371b95987c862f10637": { - "model_module": "@jupyter-widgets/output", - "model_module_version": "1.0.0", - "model_name": "OutputModel", - "state": { - "_dom_classes": [], - "_model_module": "@jupyter-widgets/output", - "_model_module_version": "1.0.0", - "_model_name": "OutputModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/output", - "_view_module_version": "1.0.0", - "_view_name": "OutputView", - "layout": "IPY_MODEL_1006aafeb85f4ae9b1cabe28f33184f4", - "msg_id": "", + { + "cell_type": "code", + "metadata": { + "colab": { + "base_uri": "https://localhost:8080/" + }, + "id": "2a3f9ca4-b61b-4536-9896-7a41498cc986", + "outputId": "08bb2e07-1dda-4cf1-c2d3-7125d575cd8f" + }, + "source": [ + "histogram_global_duration = !python histogram_global.py\n", + "histogram_localized_duration = !python histogram_localized.py\n", + "speedup = float(histogram_global_duration[0].split()[0]) / float(histogram_localized_duration[0].split()[0])\n", + "\n", + "print(f\"histogram_global: {histogram_global_duration[0]}\")\n", + "print(f\"histogram_localized: {histogram_localized_duration[0]}\")\n", + "print(f\"histogram_localized speedup over histogram_global: {speedup:.2f}\")" + ], + "execution_count": 14, "outputs": [ - { - "data": { - "text/markdown": "## Instruction\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Avg. Executed Instructions Per Scheduler | inst | 14,289.60 |\n| Executed Instructions | inst | 2,286,336 |\n| Avg. Issued Instructions Per Scheduler | inst | 14,322.95 |\n| Issued Instructions | inst | 2,291,672 |\n", - "text/plain": "" - }, - "metadata": {}, - "output_type": "display_data" - } - ] - } - }, - "f1930ef855f844ee947a1c9b07ed95a4": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "f261d85898c54dc9a72eaf5bda2703ca": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "f2ef2b3576a54144a9faadd4ea42f0f4": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "f440961da49a4e6c9185ae5f55373df3": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } - }, - "f47dbfc47f8647a38c370a2b7be404b2": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } + { + "output_type": "stream", + "text": [ + "histogram_global: 0.00881 s ± 1.13% (mean ± relative stdev of 15 runs)\n", + "histogram_localized: 0.000707 s ± 3.10% (mean ± relative stdev of 15 runs)\n", + "histogram_localized speedup over histogram_global: 12.46\n" + ] + } + ], + "id": "2a3f9ca4-b61b-4536-9896-7a41498cc986" + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "gpuType": "T4", + "provenance": [] }, - "face58a5cc384e4cb2b75921d967b2c5": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "name": "python3" }, - "fd8251af0c4743e29b89a76a73a27af4": { - "model_module": "@jupyter-widgets/base", - "model_module_version": "1.2.0", - "model_name": "LayoutModel", - "state": { - "_model_module": "@jupyter-widgets/base", - "_model_module_version": "1.2.0", - "_model_name": "LayoutModel", - "_view_count": null, - "_view_module": "@jupyter-widgets/base", - "_view_module_version": "1.2.0", - "_view_name": "LayoutView", - "align_content": null, - "align_items": null, - "align_self": null, - "border": null, - "bottom": null, - "display": null, - "flex": null, - "flex_flow": null, - "grid_area": null, - "grid_auto_columns": null, - "grid_auto_flow": null, - "grid_auto_rows": null, - "grid_column": null, - "grid_gap": null, - "grid_row": null, - "grid_template_areas": null, - "grid_template_columns": null, - "grid_template_rows": null, - "height": null, - "justify_content": null, - "justify_items": null, - "left": null, - "margin": null, - "max_height": null, - "max_width": null, - "min_height": null, - "min_width": null, - "object_fit": null, - "object_position": null, - "order": null, - "overflow": null, - "overflow_x": null, - "overflow_y": null, - "padding": null, - "right": null, - "top": null, - "visibility": null, - "width": null - } + "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.13.7" + }, + "widgets": { + "application/vnd.jupyter.widget-state+json": { + "05bad554df84498daf462d395e980f52": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_f1930ef855f844ee947a1c9b07ed95a4", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## WorkloadDistribution\n\n🔧 **OPTIMIZATION**: One or more L2 Slices have a much higher number of active cycles than the average number of active cycles. Maximum instance value is 89.83% above the average, while the minimum instance value is 60.99% below the average.\n*Estimated Speedup (global): 9.132%*\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "061f8098ad9a430aad63a106b4629fb5": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": "400px" + } + }, + "0952fc28a1244c46befc2bed2cd7cb4c": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "0d706bed98bf41eeb56205638c01f1ec": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_e9a4e840c12041809843a134cc29d791", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Speed Of Light\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| DRAM Frequency | Ghz | 5.00 |\n| SM Frequency | Mhz | 584.99 |\n| Elapsed Cycles | cycle | 4,835,673 |\n| Memory Throughput | % | 6.60 |\n| DRAM Throughput | % | 0.79 |\n| Duration | ms | 8.27 |\n| L1/TEX Cache Throughput | % | 13.20 |\n| L2 Cache Throughput | % | 5.65 |\n| SM Active Cycles | cycle | 4,808,396.12 |\n| Compute (SM) Throughput | % | 1.44 |\n\n🔧 **OPTIMIZATION**: This kernel exhibits low compute throughput and memory bandwidth utilization relative to the peak performance of this device. Achieved compute throughput and/or memory bandwidth below 60.0% of peak typically indicate latency issues. Look at Scheduler Statistics and Warp State Statistics for potential reasons.\n\nℹ️ **INFO**: The ratio of peak float (fp32) to double (fp64) performance on this device is 32:1. The kernel achieved 0% of this device's fp32 peak performance and 0% of its fp64 peak performance. See the Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#roofline) for more details on roofline analysis.\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "0dcbea49322640a28c4f3459ea50c204": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_cb604331c58f43bda3a0135cc761ef22", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Compute & Memory Distribution\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Average DRAM Active Cycles | cycle | 417,585 |\n| Total DRAM Elapsed Cycles | cycle | 11,925,504 |\n| Average L1 Active Cycles | cycle | 158,097.65 |\n| Total L1 Elapsed Cycles | cycle | 6,961,192 |\n| Average L2 Active Cycles | cycle | 184,213.25 |\n| Total L2 Elapsed Cycles | cycle | 8,208,544 |\n| Average SM Active Cycles | cycle | 158,097.65 |\n| Total SM Elapsed Cycles | cycle | 6,961,192 |\n| Average SMSP Active Cycles | cycle | 158,157.98 |\n| Total SMSP Elapsed Cycles | cycle | 27,844,768 |\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "0f94f91896304b3c861750946febeccc": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "1006aafeb85f4ae9b1cabe28f33184f4": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "15e004fe6f924bf5887331f0ad9da19f": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_63f8786f07e74f4caef3ebf115110daa", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Warp State\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Warp Cycles Per Issued Instruction | cycle | 2,467.51 |\n| Warp Cycles Per Executed Instruction | cycle | 2,473.27 |\n| Avg. Active Threads Per Warp | | 32 |\n| Avg. Not Predicated Off Threads Per Warp | | 32 |\n\n🔧 **OPTIMIZATION**: On average, each warp of this kernel spends 1886.3 cycles being stalled waiting for the L1 instruction queue for local and global (LG) memory operations to be not full. Typically, this stall occurs only when executing local or global memory instructions extremely frequently. Avoid redundant global memory accesses. Try to avoid using thread-local memory by checking if dynamically indexed arrays are declared in local scope, of if the kernel has excessive register pressure causing by spills. If applicable, consider combining multiple lower-width memory operations into fewer wider memory operations and try interleaving memory operations and math instructions. This stall type represents about 76.4% of the total average of 2467.5 cycles between issuing two instructions.\n*Estimated Speedup (global): 76.45%*\n\nℹ️ **INFO**: Check the Warp Stall Sampling (All Samples) table for the top stall locations in your source based on sampling data. The Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#metrics-reference) provides more details on each stall reason.\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "16d240c1461a444ea9cdbbd4a856e8f3": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_fd8251af0c4743e29b89a76a73a27af4", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## WorkloadDistribution\n\n🔧 **OPTIMIZATION**: One or more SMs have a much higher number of active cycles than the average number of active cycles. Maximum instance value is 8.44% above the average, while the minimum instance value is 3.29% below the average.\n*Estimated Speedup (global): 7.666%*\n\n🔧 **OPTIMIZATION**: One or more SMSPs have a much higher number of active cycles than the average number of active cycles. Maximum instance value is 8.37% above the average, while the minimum instance value is 3.25% below the average.\n*Estimated Speedup (global): 7.609%*\n\n🔧 **OPTIMIZATION**: One or more L1 Slices have a much higher number of active cycles than the average number of active cycles. Maximum instance value is 8.44% above the average, while the minimum instance value is 3.29% below the average.\n*Estimated Speedup (global): 7.666%*\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "179ce686ac1f4e60b5d34951446ad599": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_83f0975a18e0401d859f001422aebc74", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Compute & Memory Distribution\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Average DRAM Active Cycles | cycle | 326,938.50 |\n| Total DRAM Elapsed Cycles | cycle | 330,405,888 |\n| Average L1 Active Cycles | cycle | 4,808,396.12 |\n| Total L1 Elapsed Cycles | cycle | 193,385,088 |\n| Average L2 Active Cycles | cycle | 718,498.38 |\n| Total L2 Elapsed Cycles | cycle | 226,160,704 |\n| Average SM Active Cycles | cycle | 4,808,396.12 |\n| Total SM Elapsed Cycles | cycle | 193,385,088 |\n| Average SMSP Active Cycles | cycle | 4,807,310.28 |\n| Total SMSP Elapsed Cycles | cycle | 773,540,352 |\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "1edda20eae6441a99d31a1105f30b8c6": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_607214ca07174871b459e301eb7ad699", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Source Counters\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Branch Instructions Ratio | % | 0.03 |\n| Branch Instructions | inst | 58,624 |\n| Branch Efficiency | % | 0 |\n| Avg. Divergent Branches | | 0 |\n\n🔧 **OPTIMIZATION**: This kernel has uncoalesced global accesses resulting in a total of 4988724 excessive sectors (68% of the total 7329833 sectors). Check the L2 Theoretical Sectors Global Excessive table for the primary source locations. The CUDA Programming Guide (https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#device-memory-accesses) has additional information on reducing uncoalesced device memory accesses.\n*Estimated Speedup (global): 6.919%*\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "24c64a3e43f44f1bb65cee915d1aa9ce": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "277a7612626b44a0b009d28fe486ab3d": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": "400px" + } + }, + "29f54431514146cea6e08d352058ccfd": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_a48f630bb3584ce294f2e79d60c4b40e", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "# histogram_localized", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "a122e99121234ce396fa20aa6e462574", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": "Tab(children=(Output(), Output(), Output(), Output(), Output(), Output(), Output(), Output(), Output(), Output…" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "2a50582b630a4e63a41f10adb53303e2": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_b6bfff789d354bdd8c630826eaae63b6", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Memory Workload\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Memory Throughput | Gbyte/s | 2.53 |\n| Mem Busy | % | 4.12 |\n| Max Bandwidth | % | 6.60 |\n| L1/TEX Hit Rate | % | 26.05 |\n| L2 Hit Rate | % | 94.95 |\n| Mem Pipes Busy | % | 1.44 |\n\n⚠️ **WARNING**: The optional metric lts__average_gcomp_input_sector_success_rate.pct could not be found. Collecting it as an additional metric could enable the rule to provide more guidance.\n\n🔧 **OPTIMIZATION**: The memory access pattern for global loads from L1TEX might not be optimal. On average, only 4.0 of the 32 bytes transmitted per sector are utilized by each thread. This could possibly be caused by a stride between threads. Check the Source Counters section for uncoalesced global loads.\n*Estimated Speedup (global): 11.55%*\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "2a64bde12026429db15794fad73e7759": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_bb74cb5d3eb042e0b148a8554236a950", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Memory Workload\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Memory Throughput | Gbyte/s | 89.06 |\n| Mem Busy | % | 45.20 |\n| Max Bandwidth | % | 28.21 |\n| L1/TEX Hit Rate | % | 0 |\n| L2 Hit Rate | % | 50.38 |\n| Mem Pipes Busy | % | 28.21 |\n\n⚠️ **WARNING**: The optional metric lts__average_gcomp_input_sector_success_rate.pct could not be found. Collecting it as an additional metric could enable the rule to provide more guidance.\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "2bf258c6b7734068a20ed1fe5bcacf61": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_f2ef2b3576a54144a9faadd4ea42f0f4", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Scheduler\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| One or More Eligible | % | 7.55 |\n| Issued Warp Per Scheduler | | 0.08 |\n| No Eligible | % | 92.45 |\n| Active Warps Per Scheduler | warp | 7.66 |\n| Eligible Warps Per Scheduler | warp | 0.27 |\n\n🔧 **OPTIMIZATION**: Every scheduler is capable of issuing one instruction per cycle, but for this kernel each scheduler only issues an instruction every 13.2 cycles. This might leave hardware resources underutilized and may lead to less optimal performance. Out of the maximum of 8 warps per scheduler, this kernel allocates an average of 7.66 active warps per scheduler, but only an average of 0.27 warps were eligible per cycle. Eligible warps are the subset of active warps that are ready to issue their next instruction. Every cycle with no eligible warp results in no instruction being issued and the issue slot remains unused. To increase the number of eligible warps, avoid possible load imbalances due to highly different execution durations per warp. Reducing stalls indicated on the Warp State Statistics and Source Counters sections can help, too.\n*Estimated Speedup (local): 54.8%*\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "2ebcfbe930ca4061a86c8f1ea87b8eb0": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "39e58d26a4a84b99ad768f93a7a3a8a5": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_f440961da49a4e6c9185ae5f55373df3", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Occupancy\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Block Limit SM | block | 16 |\n| Block Limit Registers | block | 5 |\n| Block Limit Shared Mem | block | 16 |\n| Block Limit Warps | block | 2 |\n| Theoretical Active Warps per SM | warp | 32 |\n| Theoretical Occupancy | % | 100 |\n| Achieved Occupancy | % | 91.83 |\n| Achieved Active Warps Per SM | warp | 29.39 |\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "4d9753b4b11f425d9476d4f69932988c": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_24c64a3e43f44f1bb65cee915d1aa9ce", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Scheduler\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| One or More Eligible | % | 0.30 |\n| Issued Warp Per Scheduler | | 0.00 |\n| No Eligible | % | 99.70 |\n| Active Warps Per Scheduler | warp | 7.35 |\n| Eligible Warps Per Scheduler | warp | 0.01 |\n\n🔧 **OPTIMIZATION**: Every scheduler is capable of issuing one instruction per cycle, but for this kernel each scheduler only issues an instruction every 335.6 cycles. This might leave hardware resources underutilized and may lead to less optimal performance. Out of the maximum of 8 warps per scheduler, this kernel allocates an average of 7.35 active warps per scheduler, but only an average of 0.01 warps were eligible per cycle. Eligible warps are the subset of active warps that are ready to issue their next instruction. Every cycle with no eligible warp results in no instruction being issued and the issue slot remains unused. To increase the number of eligible warps, avoid possible load imbalances due to highly different execution durations per warp. Reducing stalls indicated on the Warp State Statistics and Source Counters sections can help, too.\n*Estimated Speedup (local): 93.4%*\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "50cc8f97b44549faa0f202361f5f7e6c": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_0f94f91896304b3c861750946febeccc", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Launch\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Block Size | | 512 |\n| Function Cache Configuration | | CachePreferNone |\n| Grid Size | | 3,664 |\n| Registers Per Thread | register/thread | 18 |\n| Shared Memory Configuration Size | Kbyte | 32.77 |\n| Driver Shared Memory Per Block | byte/block | 0 |\n| Dynamic Shared Memory Per Block | byte/block | 0 |\n| Static Shared Memory Per Block | byte/block | 0 |\n| # SMs | SM | 40 |\n| Threads | thread | 1,875,968 |\n| Uses Green Context | | 0 |\n| Waves Per SM | | 45.80 |\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "5e77669944a346e6b628004967e7ba75": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "initial" + } + }, + "607214ca07174871b459e301eb7ad699": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "63f8786f07e74f4caef3ebf115110daa": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "64037d07a15d4e238471eeccce3d0ead": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "6439cea566ff46e08f08022e1c0d40d6": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_64037d07a15d4e238471eeccce3d0ead", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## PM Sampling\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Maximum Buffer Size | Mbyte | 1.05 |\n| Dropped Samples | sample | 0 |\n| Maximum Sampling Interval | cycle | 20,000 |\n| # Pass Groups | | 1 |\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "650c0471ffea4b4bad75444a904978b1": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "6f476f6ca14a45699799d56dedbfc4a9": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DescriptionStyleModel", + "state": { + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DescriptionStyleModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "StyleView", + "description_width": "initial" + } + }, + "72eba4c5f11b49fb89df581268cc1ddc": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DropdownModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DropdownModel", + "_options_labels": [ + "histogram_localized" + ], + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "DropdownView", + "description": "Kernel:", + "description_tooltip": null, + "disabled": false, + "index": 0, + "layout": "IPY_MODEL_277a7612626b44a0b009d28fe486ab3d", + "style": "IPY_MODEL_6f476f6ca14a45699799d56dedbfc4a9" + } + }, + "75a37b0df227406d87849b8ccc0e8cc8": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_9f7ceb76de8e4d9c8a6d03403fc87038", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Compute Workload\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Executed Ipc Active | inst/cycle | 0.30 |\n| Executed Ipc Elapsed | inst/cycle | 0.27 |\n| Issue Slots Busy | % | 7.55 |\n| Issued Ipc Active | inst/cycle | 0.30 |\n| SM Busy | % | 10.97 |\n\n🔧 **OPTIMIZATION**: All compute pipelines are under-utilized. Either this kernel is very small or it doesn't issue enough warps per scheduler. Check the Launch Statistics and Scheduler Statistics sections for further details.\n*Estimated Speedup (local): 96.64%*\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "77b1acef7c294c85a13b408d4763b865": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_9c1ff914f65345ddba6465fc350ffa77", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Instruction\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Avg. Executed Instructions Per Scheduler | inst | 11,908 |\n| Executed Instructions | inst | 1,905,280 |\n| Avg. Issued Instructions Per Scheduler | inst | 11,943.96 |\n| Issued Instructions | inst | 1,911,034 |\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "804c25036085410fbbcba82f19667d1d": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_face58a5cc384e4cb2b75921d967b2c5", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "# histogram_global", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "data": { + "application/vnd.jupyter.widget-view+json": { + "model_id": "899b07bbb6304e2a9143dece489f7775", + "version_major": 2, + "version_minor": 0 + }, + "text/plain": "Tab(children=(Output(), Output(), Output(), Output(), Output(), Output(), Output(), Output(), Output(), Output…" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "83f0975a18e0401d859f001422aebc74": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "899b07bbb6304e2a9143dece489f7775": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "TabModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "TabModel", + "_titles": { + "0": "Summary", + "1": "Speed Of Light", + "2": "Memory Workload", + "3": "Compute Workload", + "4": "Compute & Memory Distribution", + "5": "Scheduler", + "6": "Warp State", + "7": "Instruction", + "8": "Launch", + "9": "PM Sampling", + "10": "Occupancy", + "11": "Source Counters", + "12": "WorkloadDistribution" + }, + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "TabView", + "box_style": "", + "children": [ + "IPY_MODEL_b881cf3221d54ac4a68c38792d53bdf5", + "IPY_MODEL_0d706bed98bf41eeb56205638c01f1ec", + "IPY_MODEL_2a50582b630a4e63a41f10adb53303e2", + "IPY_MODEL_dbf4eef8f47e4789a62977505c049825", + "IPY_MODEL_179ce686ac1f4e60b5d34951446ad599", + "IPY_MODEL_4d9753b4b11f425d9476d4f69932988c", + "IPY_MODEL_15e004fe6f924bf5887331f0ad9da19f", + "IPY_MODEL_f02f5a0cfd354371b95987c862f10637", + "IPY_MODEL_50cc8f97b44549faa0f202361f5f7e6c", + "IPY_MODEL_95e56a0ec4484f06aea59796d88e0464", + "IPY_MODEL_39e58d26a4a84b99ad768f93a7a3a8a5", + "IPY_MODEL_1edda20eae6441a99d31a1105f30b8c6", + "IPY_MODEL_05bad554df84498daf462d395e980f52" + ], + "layout": "IPY_MODEL_af61114771e442e2bb8bc033e882dbe2", + "selected_index": 0 + } + }, + "8d3c55d6d03b4d79aba3d5e7b1a73f2e": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_da059452a8744262a3ebfec6543107bf", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Speed Of Light\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| DRAM Frequency | Ghz | 4.97 |\n| SM Frequency | Mhz | 584.87 |\n| Elapsed Cycles | cycle | 175,527 |\n| Memory Throughput | % | 45.20 |\n| DRAM Throughput | % | 28.01 |\n| Duration | us | 300.10 |\n| L1/TEX Cache Throughput | % | 90.41 |\n| L2 Cache Throughput | % | 6.09 |\n| SM Active Cycles | cycle | 158,097.65 |\n| Compute (SM) Throughput | % | 28.21 |\n\n🔧 **OPTIMIZATION**: This kernel exhibits low compute throughput and memory bandwidth utilization relative to the peak performance of this device. Achieved compute throughput and/or memory bandwidth below 60.0% of peak typically indicate latency issues. Look at Scheduler Statistics and Warp State Statistics for potential reasons.\n\nℹ️ **INFO**: The ratio of peak float (fp32) to double (fp64) performance on this device is 32:1. The kernel achieved 0% of this device's fp32 peak performance and 0% of its fp64 peak performance. See the Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#roofline) for more details on roofline analysis.\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "95e56a0ec4484f06aea59796d88e0464": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_f47dbfc47f8647a38c370a2b7be404b2", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## PM Sampling\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Maximum Buffer Size | Mbyte | 2.10 |\n| Dropped Samples | sample | 0 |\n| Maximum Sampling Interval | cycle | 20,000 |\n| # Pass Groups | | 1 |\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "9aee114e6f204e139cad4e9797cd9cc5": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "9c1ff914f65345ddba6465fc350ffa77": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "9f7ceb76de8e4d9c8a6d03403fc87038": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "a122e99121234ce396fa20aa6e462574": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "TabModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "TabModel", + "_titles": { + "0": "Summary", + "1": "Speed Of Light", + "2": "Memory Workload", + "3": "Compute Workload", + "4": "Compute & Memory Distribution", + "5": "Scheduler", + "6": "Warp State", + "7": "Instruction", + "8": "Launch", + "9": "PM Sampling", + "10": "Occupancy", + "11": "Source Counters", + "12": "WorkloadDistribution" + }, + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "TabView", + "box_style": "", + "children": [ + "IPY_MODEL_ea2f4b91760f475ebc10da01166b63b8", + "IPY_MODEL_8d3c55d6d03b4d79aba3d5e7b1a73f2e", + "IPY_MODEL_2a64bde12026429db15794fad73e7759", + "IPY_MODEL_75a37b0df227406d87849b8ccc0e8cc8", + "IPY_MODEL_0dcbea49322640a28c4f3459ea50c204", + "IPY_MODEL_2bf258c6b7734068a20ed1fe5bcacf61", + "IPY_MODEL_a130d437cec947d3ad49ab36fcfe1ef6", + "IPY_MODEL_77b1acef7c294c85a13b408d4763b865", + "IPY_MODEL_b8afefd142c641dd8733d7695d9cf14a", + "IPY_MODEL_6439cea566ff46e08f08022e1c0d40d6", + "IPY_MODEL_c799b237ce024a29b2e23307a7ff9e67", + "IPY_MODEL_c92de1f9425a4f2f85bb07555c3d51a1", + "IPY_MODEL_16d240c1461a444ea9cdbbd4a856e8f3" + ], + "layout": "IPY_MODEL_c8a33b735706409e93422519ca88fd38", + "selected_index": 0 + } + }, + "a130d437cec947d3ad49ab36fcfe1ef6": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_9aee114e6f204e139cad4e9797cd9cc5", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Warp State\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Warp Cycles Per Issued Instruction | cycle | 101.46 |\n| Warp Cycles Per Executed Instruction | cycle | 101.76 |\n| Avg. Active Threads Per Warp | | 32 |\n| Avg. Not Predicated Off Threads Per Warp | | 31.75 |\n\n🔧 **OPTIMIZATION**: On average, each warp of this kernel spends 66.8 cycles being stalled waiting for the MIO (memory input/output) instruction queue to be not full. This stall reason is high in cases of extreme utilization of the MIO pipelines, which include special math instructions, dynamic branches, as well as shared memory instructions. When caused by shared memory accesses, trying to use fewer but wider loads can reduce pipeline pressure. This stall type represents about 65.8% of the total average of 101.5 cycles between issuing two instructions.\n*Estimated Speedup (global): 54.8%*\n\nℹ️ **INFO**: Check the Warp Stall Sampling (All Samples) table for the top stall locations in your source based on sampling data. The Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#metrics-reference) provides more details on each stall reason.\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "a21053863aa949e484f30f47bcf5c045": { + "model_module": "@jupyter-widgets/controls", + "model_module_version": "1.5.0", + "model_name": "DropdownModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/controls", + "_model_module_version": "1.5.0", + "_model_name": "DropdownModel", + "_options_labels": [ + "histogram_global" + ], + "_view_count": null, + "_view_module": "@jupyter-widgets/controls", + "_view_module_version": "1.5.0", + "_view_name": "DropdownView", + "description": "Kernel:", + "description_tooltip": null, + "disabled": false, + "index": 0, + "layout": "IPY_MODEL_061f8098ad9a430aad63a106b4629fb5", + "style": "IPY_MODEL_5e77669944a346e6b628004967e7ba75" + } + }, + "a48f630bb3584ce294f2e79d60c4b40e": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "af61114771e442e2bb8bc033e882dbe2": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "b6bfff789d354bdd8c630826eaae63b6": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "b881cf3221d54ac4a68c38792d53bdf5": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_ea81f1518f0d47b995f50df7e74f9329", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Summary\n\n### Speed Of Light\n\n🔧 **OPTIMIZATION**: This kernel exhibits low compute throughput and memory bandwidth utilization relative to the peak performance of this device. Achieved compute throughput and/or memory bandwidth below 60.0% of peak typically indicate latency issues. Look at Scheduler Statistics and Warp State Statistics for potential reasons.\n\nℹ️ **INFO**: The ratio of peak float (fp32) to double (fp64) performance on this device is 32:1. The kernel achieved 0% of this device's fp32 peak performance and 0% of its fp64 peak performance. See the Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#roofline) for more details on roofline analysis.\n\n### Memory Workload\n\n⚠️ **WARNING**: The optional metric lts__average_gcomp_input_sector_success_rate.pct could not be found. Collecting it as an additional metric could enable the rule to provide more guidance.\n\n🔧 **OPTIMIZATION**: The memory access pattern for global loads from L1TEX might not be optimal. On average, only 4.0 of the 32 bytes transmitted per sector are utilized by each thread. This could possibly be caused by a stride between threads. Check the Source Counters section for uncoalesced global loads.\n*Estimated Speedup (global): 11.55%*\n\n### Compute Workload\n\n🔧 **OPTIMIZATION**: All compute pipelines are under-utilized. Either this kernel is very small or it doesn't issue enough warps per scheduler. Check the Launch Statistics and Scheduler Statistics sections for further details.\n*Estimated Speedup (local): 99.85%*\n\n### Scheduler\n\n🔧 **OPTIMIZATION**: Every scheduler is capable of issuing one instruction per cycle, but for this kernel each scheduler only issues an instruction every 335.6 cycles. This might leave hardware resources underutilized and may lead to less optimal performance. Out of the maximum of 8 warps per scheduler, this kernel allocates an average of 7.35 active warps per scheduler, but only an average of 0.01 warps were eligible per cycle. Eligible warps are the subset of active warps that are ready to issue their next instruction. Every cycle with no eligible warp results in no instruction being issued and the issue slot remains unused. To increase the number of eligible warps, avoid possible load imbalances due to highly different execution durations per warp. Reducing stalls indicated on the Warp State Statistics and Source Counters sections can help, too.\n*Estimated Speedup (local): 93.4%*\n\n### Warp State\n\n🔧 **OPTIMIZATION**: On average, each warp of this kernel spends 1886.3 cycles being stalled waiting for the L1 instruction queue for local and global (LG) memory operations to be not full. Typically, this stall occurs only when executing local or global memory instructions extremely frequently. Avoid redundant global memory accesses. Try to avoid using thread-local memory by checking if dynamically indexed arrays are declared in local scope, of if the kernel has excessive register pressure causing by spills. If applicable, consider combining multiple lower-width memory operations into fewer wider memory operations and try interleaving memory operations and math instructions. This stall type represents about 76.4% of the total average of 2467.5 cycles between issuing two instructions.\n*Estimated Speedup (global): 76.45%*\n\nℹ️ **INFO**: Check the Warp Stall Sampling (All Samples) table for the top stall locations in your source based on sampling data. The Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#metrics-reference) provides more details on each stall reason.\n\n### Source Counters\n\n🔧 **OPTIMIZATION**: This kernel has uncoalesced global accesses resulting in a total of 4988724 excessive sectors (68% of the total 7329833 sectors). Check the L2 Theoretical Sectors Global Excessive table for the primary source locations. The CUDA Programming Guide (https://docs.nvidia.com/cuda/cuda-c-programming-guide/index.html#device-memory-accesses) has additional information on reducing uncoalesced device memory accesses.\n*Estimated Speedup (global): 6.919%*\n\n### WorkloadDistribution\n\n🔧 **OPTIMIZATION**: One or more L2 Slices have a much higher number of active cycles than the average number of active cycles. Maximum instance value is 89.83% above the average, while the minimum instance value is 60.99% below the average.\n*Estimated Speedup (global): 9.132%*\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "b8afefd142c641dd8733d7695d9cf14a": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_2ebcfbe930ca4061a86c8f1ea87b8eb0", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Launch\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Block Size | | 512 |\n| Function Cache Configuration | | CachePreferNone |\n| Grid Size | | 916 |\n| Registers Per Thread | register/thread | 47 |\n| Shared Memory Configuration Size | Kbyte | 32.77 |\n| Driver Shared Memory Per Block | byte/block | 0 |\n| Dynamic Shared Memory Per Block | byte/block | 0 |\n| Static Shared Memory Per Block | Kbyte/block | 1.02 |\n| # SMs | SM | 40 |\n| Threads | thread | 468,992 |\n| Uses Green Context | | 0 |\n| Waves Per SM | | 11.45 |\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "bb74cb5d3eb042e0b148a8554236a950": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "c799b237ce024a29b2e23307a7ff9e67": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_f261d85898c54dc9a72eaf5bda2703ca", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Occupancy\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Block Limit SM | block | 16 |\n| Block Limit Registers | block | 2 |\n| Block Limit Shared Mem | block | 32 |\n| Block Limit Warps | block | 2 |\n| Theoretical Active Warps per SM | warp | 32 |\n| Theoretical Occupancy | % | 100 |\n| Achieved Occupancy | % | 95.92 |\n| Achieved Active Warps Per SM | warp | 30.69 |\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "c8a33b735706409e93422519ca88fd38": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "c92de1f9425a4f2f85bb07555c3d51a1": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_d7db1b0b42b948ae8c3b97b059f68cdb", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Source Counters\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Branch Instructions Ratio | % | 0.01 |\n| Branch Instructions | inst | 21,984 |\n| Branch Efficiency | % | 0 |\n| Avg. Divergent Branches | | 0 |\n\n🔧 **OPTIMIZATION**: This kernel has uncoalesced shared accesses resulting in a total of 2154026 excessive wavefronts (82% of the total 2637674 wavefronts). Check the L1 Wavefronts Shared Excessive table for the primary source locations. The CUDA Best Practices Guide (https://docs.nvidia.com/cuda/cuda-c-best-practices-guide/index.html#shared-memory-in-matrix-multiplication-c-ab) has an example on optimizing shared memory accesses.\n*Estimated Speedup (global): 74.19%*\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "cb604331c58f43bda3a0135cc761ef22": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "d7db1b0b42b948ae8c3b97b059f68cdb": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "da059452a8744262a3ebfec6543107bf": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "dbf4eef8f47e4789a62977505c049825": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_0952fc28a1244c46befc2bed2cd7cb4c", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Compute Workload\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Executed Ipc Active | inst/cycle | 0.01 |\n| Executed Ipc Elapsed | inst/cycle | 0.01 |\n| Issue Slots Busy | % | 0.30 |\n| Issued Ipc Active | inst/cycle | 0.01 |\n| SM Busy | % | 0.37 |\n\n🔧 **OPTIMIZATION**: All compute pipelines are under-utilized. Either this kernel is very small or it doesn't issue enough warps per scheduler. Check the Launch Statistics and Scheduler Statistics sections for further details.\n*Estimated Speedup (local): 99.85%*\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "e9a4e840c12041809843a134cc29d791": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "ea2f4b91760f475ebc10da01166b63b8": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_650c0471ffea4b4bad75444a904978b1", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Summary\n\n### Speed Of Light\n\n🔧 **OPTIMIZATION**: This kernel exhibits low compute throughput and memory bandwidth utilization relative to the peak performance of this device. Achieved compute throughput and/or memory bandwidth below 60.0% of peak typically indicate latency issues. Look at Scheduler Statistics and Warp State Statistics for potential reasons.\n\nℹ️ **INFO**: The ratio of peak float (fp32) to double (fp64) performance on this device is 32:1. The kernel achieved 0% of this device's fp32 peak performance and 0% of its fp64 peak performance. See the Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#roofline) for more details on roofline analysis.\n\n### Memory Workload\n\n⚠️ **WARNING**: The optional metric lts__average_gcomp_input_sector_success_rate.pct could not be found. Collecting it as an additional metric could enable the rule to provide more guidance.\n\n### Compute Workload\n\n🔧 **OPTIMIZATION**: All compute pipelines are under-utilized. Either this kernel is very small or it doesn't issue enough warps per scheduler. Check the Launch Statistics and Scheduler Statistics sections for further details.\n*Estimated Speedup (local): 96.64%*\n\n### Scheduler\n\n🔧 **OPTIMIZATION**: Every scheduler is capable of issuing one instruction per cycle, but for this kernel each scheduler only issues an instruction every 13.2 cycles. This might leave hardware resources underutilized and may lead to less optimal performance. Out of the maximum of 8 warps per scheduler, this kernel allocates an average of 7.66 active warps per scheduler, but only an average of 0.27 warps were eligible per cycle. Eligible warps are the subset of active warps that are ready to issue their next instruction. Every cycle with no eligible warp results in no instruction being issued and the issue slot remains unused. To increase the number of eligible warps, avoid possible load imbalances due to highly different execution durations per warp. Reducing stalls indicated on the Warp State Statistics and Source Counters sections can help, too.\n*Estimated Speedup (local): 54.8%*\n\n### Warp State\n\n🔧 **OPTIMIZATION**: On average, each warp of this kernel spends 66.8 cycles being stalled waiting for the MIO (memory input/output) instruction queue to be not full. This stall reason is high in cases of extreme utilization of the MIO pipelines, which include special math instructions, dynamic branches, as well as shared memory instructions. When caused by shared memory accesses, trying to use fewer but wider loads can reduce pipeline pressure. This stall type represents about 65.8% of the total average of 101.5 cycles between issuing two instructions.\n*Estimated Speedup (global): 54.8%*\n\nℹ️ **INFO**: Check the Warp Stall Sampling (All Samples) table for the top stall locations in your source based on sampling data. The Kernel Profiling Guide (https://docs.nvidia.com/nsight-compute/ProfilingGuide/index.html#metrics-reference) provides more details on each stall reason.\n\n### Source Counters\n\n🔧 **OPTIMIZATION**: This kernel has uncoalesced shared accesses resulting in a total of 2154026 excessive wavefronts (82% of the total 2637674 wavefronts). Check the L1 Wavefronts Shared Excessive table for the primary source locations. The CUDA Best Practices Guide (https://docs.nvidia.com/cuda/cuda-c-best-practices-guide/index.html#shared-memory-in-matrix-multiplication-c-ab) has an example on optimizing shared memory accesses.\n*Estimated Speedup (global): 74.19%*\n\n### WorkloadDistribution\n\n🔧 **OPTIMIZATION**: One or more SMs have a much higher number of active cycles than the average number of active cycles. Maximum instance value is 8.44% above the average, while the minimum instance value is 3.29% below the average.\n*Estimated Speedup (global): 7.666%*\n\n🔧 **OPTIMIZATION**: One or more SMSPs have a much higher number of active cycles than the average number of active cycles. Maximum instance value is 8.37% above the average, while the minimum instance value is 3.25% below the average.\n*Estimated Speedup (global): 7.609%*\n\n🔧 **OPTIMIZATION**: One or more L1 Slices have a much higher number of active cycles than the average number of active cycles. Maximum instance value is 8.44% above the average, while the minimum instance value is 3.29% below the average.\n*Estimated Speedup (global): 7.666%*\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "ea81f1518f0d47b995f50df7e74f9329": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "f02f5a0cfd354371b95987c862f10637": { + "model_module": "@jupyter-widgets/output", + "model_module_version": "1.0.0", + "model_name": "OutputModel", + "state": { + "_dom_classes": [], + "_model_module": "@jupyter-widgets/output", + "_model_module_version": "1.0.0", + "_model_name": "OutputModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/output", + "_view_module_version": "1.0.0", + "_view_name": "OutputView", + "layout": "IPY_MODEL_1006aafeb85f4ae9b1cabe28f33184f4", + "msg_id": "", + "outputs": [ + { + "data": { + "text/markdown": "## Instruction\n\n| Metric Name | Metric Unit | Metric Value |\n|-------------|-------------|--------------|\n| Avg. Executed Instructions Per Scheduler | inst | 14,289.60 |\n| Executed Instructions | inst | 2,286,336 |\n| Avg. Issued Instructions Per Scheduler | inst | 14,322.95 |\n| Issued Instructions | inst | 2,291,672 |\n", + "text/plain": "" + }, + "metadata": {}, + "output_type": "display_data" + } + ] + } + }, + "f1930ef855f844ee947a1c9b07ed95a4": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "f261d85898c54dc9a72eaf5bda2703ca": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "f2ef2b3576a54144a9faadd4ea42f0f4": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "f440961da49a4e6c9185ae5f55373df3": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "f47dbfc47f8647a38c370a2b7be404b2": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "face58a5cc384e4cb2b75921d967b2c5": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + }, + "fd8251af0c4743e29b89a76a73a27af4": { + "model_module": "@jupyter-widgets/base", + "model_module_version": "1.2.0", + "model_name": "LayoutModel", + "state": { + "_model_module": "@jupyter-widgets/base", + "_model_module_version": "1.2.0", + "_model_name": "LayoutModel", + "_view_count": null, + "_view_module": "@jupyter-widgets/base", + "_view_module_version": "1.2.0", + "_view_name": "LayoutView", + "align_content": null, + "align_items": null, + "align_self": null, + "border": null, + "bottom": null, + "display": null, + "flex": null, + "flex_flow": null, + "grid_area": null, + "grid_auto_columns": null, + "grid_auto_flow": null, + "grid_auto_rows": null, + "grid_column": null, + "grid_gap": null, + "grid_row": null, + "grid_template_areas": null, + "grid_template_columns": null, + "grid_template_rows": null, + "height": null, + "justify_content": null, + "justify_items": null, + "left": null, + "margin": null, + "max_height": null, + "max_width": null, + "min_height": null, + "min_width": null, + "object_fit": null, + "object_position": null, + "order": null, + "overflow": null, + "overflow_x": null, + "overflow_y": null, + "padding": null, + "right": null, + "top": null, + "visibility": null, + "width": null + } + } + } } - } - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} + }, + "nbformat": 4, + "nbformat_minor": 5 +} \ No newline at end of file From fc9cdf7514ad74ff896c2a1db6378592df960437 Mon Sep 17 00:00:00 2001 From: Bryce Adelstein Lelbach aka wash Date: Tue, 3 Mar 2026 12:54:39 -0500 Subject: [PATCH 04/18] Tutorials/Accelerated Python/Kernel Authoring: Add correctness check cell before profiling copy_blocked kernel. Add a verification cell that runs the script immediately after the %%writefile cell, matching the pattern used in the book histogram notebook. Made-with: Cursor --- .../kernels/40__kernel_authoring__copy.ipynb | 76 ++++---- ...40__kernel_authoring__copy__SOLUTION.ipynb | 169 ++++++++++-------- 2 files changed, 145 insertions(+), 100 deletions(-) diff --git a/tutorials/accelerated-python/notebooks/kernels/40__kernel_authoring__copy.ipynb b/tutorials/accelerated-python/notebooks/kernels/40__kernel_authoring__copy.ipynb index e66d5672..685d4ffc 100644 --- a/tutorials/accelerated-python/notebooks/kernels/40__kernel_authoring__copy.ipynb +++ b/tutorials/accelerated-python/notebooks/kernels/40__kernel_authoring__copy.ipynb @@ -28,10 +28,12 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": { "collapsed": true, "id": "AoHkvSPMC5Fs" }, + "outputs": [], "source": [ "import os\n", "\n", @@ -45,9 +47,7 @@ " !pip uninstall \"cuda-python\" --yes > /dev/null\n", " !pip install \"numba-cuda\" \"cuda-cccl[test-cu12]\" \"nvtx\" \"nsightful[notebook] @ git+https://github.com/brycelelbach/nsightful.git\" > /dev/null 2>&1\n", " open(\"/accelerated-computing-hub-installed\", \"a\").close()" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -68,9 +68,11 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": { "id": "I9Tz2hG-_tBj" }, + "outputs": [], "source": [ "%%writefile copy_blocked.py\n", "\n", @@ -106,15 +108,29 @@ " launch(check=True)\n", " D = cpx.profiler.benchmark(launch, (False,), n_repeat=15, n_warmup=1).gpu_times[0]\n", " print(f\"{D.mean():.3g} s ± {(D.std() / D.mean()):.2%} (mean ± relative stdev of {D.size} runs)\")" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", "metadata": { "id": "TuR4yDV4H6IB" }, + "source": [ + "Let's make sure it runs correctly:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!python copy_blocked.py" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, "source": [ "## 3. Profiling the Baseline\n", "\n", @@ -122,20 +138,20 @@ "\n", "There is an overhead to running code under the profiler. Your program may execute noticeably slower.\n", "\n", - "**NOTE: To modify and rerun the above code, you must execute the previous cell to write the file and this one to execute it.**" + "**NOTE: To modify and rerun the above code, you must execute the previous `%%writefile` and then this one to execute it.**" ] }, { "cell_type": "code", + "execution_count": null, "metadata": { "id": "5pyHvJtxVnDB" }, + "outputs": [], "source": [ "!ncu -f --kernel-name regex:copy_blocked --set full -o copy_blocked python copy_blocked.py\n", "copy_blocked_csv = !ncu --import copy_blocked.ncu-rep --csv" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -152,16 +168,16 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": { "id": "40w07iG5k6Vl" }, + "outputs": [], "source": [ "import nsightful\n", "\n", "nsightful.display_ncu_csv_in_notebook(copy_blocked_csv)" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -184,9 +200,11 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": { "id": "B5PBpaY2HnE0" }, + "outputs": [], "source": [ "%%writefile copy_optimized.py\n", "\n", @@ -220,9 +238,7 @@ " launch(check=True)\n", " D = cpx.profiler.benchmark(launch, (False,), n_repeat=15, n_warmup=1).gpu_times[0]\n", " print(f\"{D.mean():.3g} s ± {(D.std() / D.mean()):.2%} (mean ± relative stdev of {D.size} runs)\")" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -237,14 +253,14 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": { "id": "thgSpsCQkN2-" }, + "outputs": [], "source": [ "!python copy_optimized.py" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -252,14 +268,16 @@ "id": "kC3Moh2m02-q" }, "source": [ - "Before we look at the report, let's compare the runtimes of both versions:" + "Before we profile the optimized kernel, let's compare the runtimes of both versions:" ] }, { "cell_type": "code", + "execution_count": null, "metadata": { "id": "kJ7viF-i06qd" }, + "outputs": [], "source": [ "copy_blocked_duration = !python copy_blocked.py\n", "copy_optimized_duration = !python copy_optimized.py\n", @@ -268,9 +286,7 @@ "print(f\"copy_blocked: {copy_blocked_duration[0]}\")\n", "print(f\"copy_optimized: {copy_optimized_duration[0]}\")\n", "print(f\"copy_optimized speedup over copy_blocked: {speedup:.2f}\")" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -285,15 +301,15 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": { "id": "zO_y6ObXV_wX" }, + "outputs": [], "source": [ "!ncu -f --kernel-name regex:copy_optimized --set full -o copy_optimized python copy_optimized.py\n", "copy_optimized_csv = !ncu --import copy_optimized.ncu-rep --csv" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -306,14 +322,14 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": { "id": "KjE0Vgu_zgs3" }, + "outputs": [], "source": [ "nsightful.display_ncu_csv_in_notebook(copy_optimized_csv)" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -341,4 +357,4 @@ }, "nbformat": 4, "nbformat_minor": 0 -} \ No newline at end of file +} diff --git a/tutorials/accelerated-python/notebooks/kernels/solutions/40__kernel_authoring__copy__SOLUTION.ipynb b/tutorials/accelerated-python/notebooks/kernels/solutions/40__kernel_authoring__copy__SOLUTION.ipynb index 7a92168f..635db42d 100644 --- a/tutorials/accelerated-python/notebooks/kernels/solutions/40__kernel_authoring__copy__SOLUTION.ipynb +++ b/tutorials/accelerated-python/notebooks/kernels/solutions/40__kernel_authoring__copy__SOLUTION.ipynb @@ -17,6 +17,7 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": { "collapsed": true, "id": "AoHkvSPMC5Fs", @@ -24,6 +25,7 @@ "outputs_hidden": true } }, + "outputs": [], "source": [ "import os\n", "\n", @@ -37,9 +39,7 @@ " !pip uninstall \"cuda-python\" --yes > /dev/null\n", " !pip install \"numba-cuda\" \"cuda-cccl[test-cu12]\" \"nvtx\" \"nsightful[notebook] @ git+https://github.com/brycelelbach/nsightful.git\" > /dev/null 2>&1\n", " open(\"/accelerated-computing-hub-installed\", \"a\").close()" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -58,6 +58,7 @@ }, { "cell_type": "code", + "execution_count": 2, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -65,6 +66,15 @@ "id": "I9Tz2hG-_tBj", "outputId": "e52f41cb-f70b-4792-ea76-e78a554956f4" }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Writing copy_blocked.py\n" + ] + } + ], "source": [ "%%writefile copy_blocked.py\n", "\n", @@ -100,15 +110,6 @@ " launch(check=True)\n", " D = cpx.profiler.benchmark(launch, (False,), n_repeat=15, n_warmup=1).gpu_times[0]\n", " print(f\"{D.mean():.3g} s ± {(D.std() / D.mean()):.2%} (mean ± relative stdev of {D.size} runs)\")" - ], - "execution_count": 2, - "outputs": [ - { - "output_type": "stream", - "text": [ - "Writing copy_blocked.py\n" - ] - } ] }, { @@ -116,6 +117,22 @@ "metadata": { "id": "TuR4yDV4H6IB" }, + "source": [ + "Let's make sure it runs correctly:" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "!python copy_blocked.py" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, "source": [ "Next, we'll actually run the code, by invoking the Nsight Compute `ncu` command line tool. The basic syntax for this tool is `ncu `, which will run ` ` while gathering a profile trace. We're passing it some flags that describe what data it should collect and where it should save the results.\n", "\n", @@ -123,11 +140,12 @@ "\n", "When profiling and benchmarking, we need to run with a sufficient workload to get meaningful and representative results. If your runtime is too short, the profiler may not be able to report some metrics or the results may be inaccurate.\n", "\n", - "**NOTE: To modify and rerun the above code, you must execute the previous cell to write the file and this one to execute it.**" + "**NOTE: To modify and rerun the above code, you must execute the previous `%%writefile` and then this one to execute it.**" ] }, { "cell_type": "code", + "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -135,13 +153,9 @@ "id": "5pyHvJtxVnDB", "outputId": "87ad5a72-9244-4b21-9776-ecd93262f274" }, - "source": [ - "!ncu -f --kernel-name regex:copy_blocked --set full -o copy_blocked python copy_blocked.py\n", - "copy_blocked_csv = !ncu --import copy_blocked.ncu-rep --csv" - ], - "execution_count": null, "outputs": [ { + "name": "stdout", "output_type": "stream", "text": [ "==PROF== Connected to process 1137 (/usr/bin/python3.11)\n", @@ -150,6 +164,10 @@ "==PROF== Report: /content/copy_blocked.ncu-rep\n" ] } + ], + "source": [ + "!ncu -f --kernel-name regex:copy_blocked --set full -o copy_blocked python copy_blocked.py\n", + "copy_blocked_csv = !ncu --import copy_blocked.ncu-rep --csv" ] }, { @@ -165,6 +183,7 @@ }, { "cell_type": "code", + "execution_count": 4, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -206,24 +225,18 @@ "id": "40w07iG5k6Vl", "outputId": "f5d0becc-0285-4cb7-a78e-427cdc105454" }, - "source": [ - "import nsightful\n", - "\n", - "nsightful.display_ncu_csv_in_notebook(copy_blocked_csv)" - ], - "execution_count": 4, "outputs": [ { - "output_type": "display_data", "data": { "application/javascript": "window[\"5ecc93f0-740d-11f0-abc7-0242ac1c000c\"] = google.colab.output.setIframeHeight(-1, true, {\"interactive\": true, \"maxHeight\": 99999});\n//# sourceURL=js_bae5335f4f", "text/plain": [ "" ] - } + }, + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { "text/html": [ "\n", @@ -270,10 +283,11 @@ "text/plain": [ "" ] - } + }, + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "b47fd7b6a0154f21bc182a368783f018", @@ -283,10 +297,11 @@ "text/plain": [ "Dropdown(description='Kernel:', layout=Layout(width='400px'), options=('copy_blocked',), style=DescriptionStyl…" ] - } + }, + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "da5ad97524f4499cb466a461c9e9a014", @@ -296,8 +311,15 @@ "text/plain": [ "Output()" ] - } + }, + "metadata": {}, + "output_type": "display_data" } + ], + "source": [ + "import nsightful\n", + "\n", + "nsightful.display_ncu_csv_in_notebook(copy_blocked_csv)" ] }, { @@ -329,6 +351,7 @@ }, { "cell_type": "code", + "execution_count": 5, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -336,6 +359,15 @@ "id": "B5PBpaY2HnE0", "outputId": "9bf8af36-f011-4eaa-ed28-c39abde2c315" }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Writing copy_optimized.py\n" + ] + } + ], "source": [ "%%writefile copy_optimized.py\n", "\n", @@ -376,15 +408,6 @@ " launch(check=True)\n", " D = cpx.profiler.benchmark(launch, (False,), n_repeat=15, n_warmup=1).gpu_times[0]\n", " print(f\"{D.mean():.3g} s ± {(D.std() / D.mean()):.2%} (mean ± relative stdev of {D.size} runs)\")" - ], - "execution_count": 5, - "outputs": [ - { - "output_type": "stream", - "text": [ - "Writing copy_optimized.py\n" - ] - } ] }, { @@ -393,11 +416,12 @@ "id": "kC3Moh2m02-q" }, "source": [ - "Before we look at the report, let's compare the execution times of both versions:" + "Before we profile the optimized kernel, let's compare the runtimes of both versions:" ] }, { "cell_type": "code", + "execution_count": 6, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -405,18 +429,9 @@ "id": "kJ7viF-i06qd", "outputId": "e2d84395-6324-4e55-c144-bc34399cc515" }, - "source": [ - "copy_blocked_duration = !python copy_blocked.py\n", - "copy_optimized_duration = !python copy_optimized.py\n", - "speedup = float(copy_blocked_duration[0].split()[0]) / float(copy_optimized_duration[0].split()[0])\n", - "\n", - "print(f\"copy_blocked: {copy_blocked_duration[0]}\")\n", - "print(f\"copy_optimized: {copy_optimized_duration[0]}\")\n", - "print(f\"copy_optimized speedup over copy_blocked: {speedup:.2f}\")" - ], - "execution_count": 6, "outputs": [ { + "name": "stdout", "output_type": "stream", "text": [ "copy_blocked: 0.327 s ± 0.66% (mean ± relative stdev of 15 runs)\n", @@ -424,6 +439,15 @@ "copy_optimized speedup over copy_blocked: 17.21\n" ] } + ], + "source": [ + "copy_blocked_duration = !python copy_blocked.py\n", + "copy_optimized_duration = !python copy_optimized.py\n", + "speedup = float(copy_blocked_duration[0].split()[0]) / float(copy_optimized_duration[0].split()[0])\n", + "\n", + "print(f\"copy_blocked: {copy_blocked_duration[0]}\")\n", + "print(f\"copy_optimized: {copy_optimized_duration[0]}\")\n", + "print(f\"copy_optimized speedup over copy_blocked: {speedup:.2f}\")" ] }, { @@ -437,6 +461,7 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -444,13 +469,9 @@ "id": "zO_y6ObXV_wX", "outputId": "8e643e49-6fbf-4b1e-ff58-d391700451ab" }, - "source": [ - "!ncu -f --kernel-name regex:copy_optimized --set full -o copy_optimized python copy_optimized.py\n", - "copy_optimized_csv = !ncu --import copy_optimized.ncu-rep --csv" - ], - "execution_count": null, "outputs": [ { + "name": "stdout", "output_type": "stream", "text": [ "==PROF== Connected to process 1401 (/usr/bin/python3.11)\n", @@ -459,6 +480,10 @@ "==PROF== Report: /content/copy_optimized.ncu-rep\n" ] } + ], + "source": [ + "!ncu -f --kernel-name regex:copy_optimized --set full -o copy_optimized python copy_optimized.py\n", + "copy_optimized_csv = !ncu --import copy_optimized.ncu-rep --csv" ] }, { @@ -472,6 +497,7 @@ }, { "cell_type": "code", + "execution_count": 8, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -513,22 +539,18 @@ "id": "KjE0Vgu_zgs3", "outputId": "632a4728-519d-48fa-938e-7a9777d52c89" }, - "source": [ - "nsightful.display_ncu_csv_in_notebook(copy_optimized_csv)" - ], - "execution_count": 8, "outputs": [ { - "output_type": "display_data", "data": { "application/javascript": "window[\"6a0b3852-740d-11f0-abc7-0242ac1c000c\"] = google.colab.output.setIframeHeight(-1, true, {\"interactive\": true, \"maxHeight\": 99999});\n//# sourceURL=js_b772b58d4e", "text/plain": [ "" ] - } + }, + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { "text/html": [ "\n", @@ -575,10 +597,11 @@ "text/plain": [ "" ] - } + }, + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "6be7ca5f9141465cb4b5126001b6c298", @@ -588,10 +611,11 @@ "text/plain": [ "Dropdown(description='Kernel:', layout=Layout(width='400px'), options=('copy_optimized',), style=DescriptionSt…" ] - } + }, + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "7bd502fafaad4a1d9d5c7db9c1d54b9e", @@ -601,8 +625,13 @@ "text/plain": [ "Output()" ] - } + }, + "metadata": {}, + "output_type": "display_data" } + ], + "source": [ + "nsightful.display_ncu_csv_in_notebook(copy_optimized_csv)" ] } ], @@ -3092,4 +3121,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} \ No newline at end of file +} From c713c1ee091047872172021e46336e8d18cee8dd Mon Sep 17 00:00:00 2001 From: Bryce Adelstein Lelbach aka wash Date: Tue, 3 Mar 2026 13:03:41 -0500 Subject: [PATCH 05/18] Tutorials/Accelerated Python/Power Iteration: Fix typos in example usage of cpyx.profiler.profile and NVTX. --- .../fundamentals/06__asynchrony__power_iteration.ipynb | 4 ++-- .../solutions/06__asynchrony__power_iteration__SOLUTION.ipynb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tutorials/accelerated-python/notebooks/fundamentals/06__asynchrony__power_iteration.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/06__asynchrony__power_iteration.ipynb index 8745d64a..24cd8791 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/06__asynchrony__power_iteration.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/06__asynchrony__power_iteration.ipynb @@ -216,7 +216,7 @@ "The first is to limit when we start and stop profiling in the program. In Python, we can do this with `cupyx.profiler.profile()`, which give us a Python context manager. Any CUDA code used during scope will be included in the profile.\n", "\n", "```\n", - "not_in_the profile()\n", + "not_in_the_profile()\n", "with cpx.profiler.profile():\n", " in_the_profile()\n", "not_in_the_profile()\n", @@ -227,7 +227,7 @@ "We can also annotate specific regions of our code, which will show up in the profiler. We can even add categories, domains, and colors to these regions, and they can be nested. To add these annotations, we use `nvtx.annnotate()`, another Python context manager, this time from a library called NVTX.\n", "\n", "```\n", - "with nvtx.annotate(\"Loop\")\n", + "with nvtx.annotate(\"Loop\"):\n", " for i in range(20):\n", " with nvtx.annotate(f\"Step {i}\"):\n", " pass\n", diff --git a/tutorials/accelerated-python/notebooks/fundamentals/solutions/06__asynchrony__power_iteration__SOLUTION.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/solutions/06__asynchrony__power_iteration__SOLUTION.ipynb index 99328e66..32908206 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/solutions/06__asynchrony__power_iteration__SOLUTION.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/solutions/06__asynchrony__power_iteration__SOLUTION.ipynb @@ -214,7 +214,7 @@ "The first is to limit when we start and stop profiling in the program. In Python, we can do this with `cupyx.profiler.profile()`, which give us a Python context manager. Any CUDA code used during scope will be included in the profile.\n", "\n", "```\n", - "not_in_the profile()\n", + "not_in_the_profile():\n", "with cpx.profiler.profile():\n", " in_the_profile()\n", "not_in_the_profile()\n", @@ -225,7 +225,7 @@ "We can also annotate specific regions of our code, which will show up in the profiler. We can even add categories, domains, and colors to these regions, and they can be nested. To add these annotations, we use `nvtx.annnotate()`, another Python context manager, this time from a library called NVTX.\n", "\n", "```\n", - "with nvtx.annotate(\"Loop\")\n", + "with nvtx.annotate(\"Loop\"):\n", " for i in range(20):\n", " with nvtx.annotate(f\"Step {i}\"):\n", " pass\n", From 2f0ff1c4e2515c9e19c9c18a0d38d58d482eee7b Mon Sep 17 00:00:00 2001 From: Bryce Adelstein Lelbach aka wash Date: Tue, 3 Mar 2026 13:14:04 -0500 Subject: [PATCH 06/18] Tutorials/Accelerated Python/Kernel Authoring: Apply title, TOC, section header, and text updates to solution notebooks. These changes were made to the exercise notebooks in 2f5c4fc but were not applied to the corresponding solution notebooks. Made-with: Cursor --- ...40__kernel_authoring__copy__SOLUTION.ipynb | 42 ++- ..._authoring__book_histogram__SOLUTION.ipynb | 342 ++++++++++-------- 2 files changed, 209 insertions(+), 175 deletions(-) diff --git a/tutorials/accelerated-python/notebooks/kernels/solutions/40__kernel_authoring__copy__SOLUTION.ipynb b/tutorials/accelerated-python/notebooks/kernels/solutions/40__kernel_authoring__copy__SOLUTION.ipynb index 635db42d..405021c3 100644 --- a/tutorials/accelerated-python/notebooks/kernels/solutions/40__kernel_authoring__copy__SOLUTION.ipynb +++ b/tutorials/accelerated-python/notebooks/kernels/solutions/40__kernel_authoring__copy__SOLUTION.ipynb @@ -6,11 +6,21 @@ "id": "-JpGaP7-D_5W" }, "source": [ - "## Exercise - Kernel Authoring - Copy - SOLUTION\n", + "## Kernel Authoring - Copy - SOLUTION\n", "\n", - "In this exercise, we'll learn how to analyze and reason about the performance of CUDA kernels using the NVIDIA Nsight Compute profiler.\n", + "### Table of Contents\n", + "1. [Environment Setup](#1-environment-setup)\n", + "2. [The Baseline Kernel: Blocked Copy](#2-the-baseline-kernel-blocked-copy)\n", + "3. [Profiling the Baseline](#3-profiling-the-baseline)\n", + "4. [Solution: Optimized Memory Access](#4-solution-optimized-memory-access)\n", + "5. [Verification & Benchmarking](#5-verification--benchmarking)\n", + "6. [Profiling the Optimized Kernel](#6-profiling-the-optimized-kernel)\n", "\n", - "We'll look at a few different ways of writing a simple kernel that copies items from one array to another.\n", + "---\n", + "\n", + "## 1. Environment Setup\n", + "\n", + "In this exercise, we'll learn how to analyze and reason about the performance of CUDA kernels using the NVIDIA Nsight Compute profiler. We'll look at a few different ways of writing a simple kernel that copies items from one array to another.\n", "\n", "First, we need to make sure the Nsight Compute profiler, Nsightful, Numba CUDA, and CuPy are available in our notebook:" ] @@ -47,6 +57,8 @@ "id": "A1SfTQk0EwUl" }, "source": [ + "## 2. The Baseline Kernel: Blocked Copy\n", + "\n", "Now, we'll write our first kernel. Each thread will copy `items_per_thread` items from the `src` array to the `dst` array. We'll set the number of threads per block to a constant, `threads_per_block`. We'll calculate how many blocks to launch based on `items_per_thread` and `threads_per_block`. We use `cuda.grid(1)` to get the unique global 1D index of each thread.\n", "\n", "Each thread will copy a contiguous set of items, e.g. the items with indices `[base, base + items_per_thread)`:\n", @@ -134,11 +146,11 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "Next, we'll actually run the code, by invoking the Nsight Compute `ncu` command line tool. The basic syntax for this tool is `ncu `, which will run ` ` while gathering a profile trace. We're passing it some flags that describe what data it should collect and where it should save the results.\n", + "## 3. Profiling the Baseline\n", "\n", - "There is an overhead to running code under the profiler. Your program may execute noticably slower.\n", + "Next, we'll actually run the code by invoking the Nsight Compute `ncu` command line tool. The basic syntax for this tool is `ncu `, which will run ` ` while gathering a profile trace. We're passing it some flags that describe what data it should collect and where it should save the results.\n", "\n", - "When profiling and benchmarking, we need to run with a sufficient workload to get meaningful and representative results. If your runtime is too short, the profiler may not be able to report some metrics or the results may be inaccurate.\n", + "There is an overhead to running code under the profiler. Your program may execute noticeably slower.\n", "\n", "**NOTE: To modify and rerun the above code, you must execute the previous `%%writefile` and then this one to execute it.**" ] @@ -328,6 +340,8 @@ "id": "mL_9xT44qbMA" }, "source": [ + "## 4. Solution: Optimized Memory Access\n", + "\n", "In our kernel, each thread linearly accesses a chunk of contiguous memory, which is what you'd want on the CPU, but not on the GPU! Our access pattern looks like this:\n", "\n", "![image.png](data:image/png;base64,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)\n", @@ -416,6 +430,8 @@ "id": "kC3Moh2m02-q" }, "source": [ + "## 5. Verification & Benchmarking\n", + "\n", "Before we profile the optimized kernel, let's compare the runtimes of both versions:" ] }, @@ -456,6 +472,8 @@ "id": "mfrqUdzozGeU" }, "source": [ + "## 6. Profiling the Optimized Kernel\n", + "\n", "That's quite a difference! Now let's profile the optimized variant:" ] }, @@ -646,18 +664,6 @@ "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.10.12" - }, "widgets": { "application/vnd.jupyter.widget-state+json": { "0495f43209454c7dade96e09491b0a59": { diff --git a/tutorials/accelerated-python/notebooks/kernels/solutions/41__kernel_authoring__book_histogram__SOLUTION.ipynb b/tutorials/accelerated-python/notebooks/kernels/solutions/41__kernel_authoring__book_histogram__SOLUTION.ipynb index 44642dc9..b5589e04 100644 --- a/tutorials/accelerated-python/notebooks/kernels/solutions/41__kernel_authoring__book_histogram__SOLUTION.ipynb +++ b/tutorials/accelerated-python/notebooks/kernels/solutions/41__kernel_authoring__book_histogram__SOLUTION.ipynb @@ -2,24 +2,38 @@ "cells": [ { "cell_type": "markdown", + "id": "f1a8560a-c91b-48db-af1c-18fcd4892448", "metadata": { "id": "f1a8560a-c91b-48db-af1c-18fcd4892448" }, "source": [ - "## Exercise - Kernel Authoring - Book Histogram - SOLUTION\n", + "# Kernel Authoring - Book Histogram - SOLUTION\n", + "\n", + "## Table of Contents\n", + "\n", + "1. [Environment Setup & Data Download](#1.-Environment-Setup-&-Data-Download)\n", + "2. [First Attempt: Global Memory Histogram](#2.-First-Attempt:-Global-Memory-Histogram)\n", + "3. [Fixing Data Races with Atomics](#3.-Fixing-Data-Races-with-Atomics)\n", + "4. [Profiling the Naive Solution](#4.-Profiling-the-Naive-Solution)\n", + "5. [Solution: Shared Memory & Cooperative Groups](#5.-Solution:-Shared-Memory-&-Cooperative-Groups)\n", + "6. [Performance Comparison](#6.-Performance-Comparison)\n", + "\n", + "## 1. Environment Setup & Data Download\n", "\n", "Let's learn to use some advanced CUDA features like shared memory, atomics, and [cuda.cooperative](https://nvidia.github.io/cccl/unstable/python/coop.html) to write an efficient histogram kernel to determine the most frequent characters in a collection of books.\n", "\n", - "First, let's download our dataset." - ], - "id": "f1a8560a-c91b-48db-af1c-18fcd4892448" + "First, let's download our dataset and install the necessary tools." + ] }, { "cell_type": "code", + "execution_count": null, + "id": "ce42d5e5-db1e-46da-a64a-831d0f3d59ff", "metadata": { "collapsed": true, "id": "ce42d5e5-db1e-46da-a64a-831d0f3d59ff" }, + "outputs": [], "source": [ "import os\n", "\n", @@ -38,13 +52,12 @@ "import matplotlib.pyplot as plt\n", "import nsightful\n", "import urllib.request" - ], - "execution_count": null, - "outputs": [], - "id": "ce42d5e5-db1e-46da-a64a-831d0f3d59ff" + ] }, { "cell_type": "code", + "execution_count": 3, + "id": "6e1ae9dc-39f1-4c93-b923-85a96b45a057", "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -52,38 +65,42 @@ "id": "6e1ae9dc-39f1-4c93-b923-85a96b45a057", "outputId": "ee334037-7f39-4a91-ad60-f0408a56b4de" }, - "source": [ - "urllib.request.urlretrieve(\n", - " \"https://drive.usercontent.google.com/download?id=1MW1lPgkTq3YG9ikuq6u3d9sfpt-wKQZ0&export=download\",\n", - " \"books__15m.txt\")" - ], - "execution_count": 3, "outputs": [ { - "output_type": "execute_result", "data": { "text/plain": [ "('books__15m.txt', )" ] - } + }, + "execution_count": null, + "metadata": {}, + "output_type": "execute_result" } ], - "id": "6e1ae9dc-39f1-4c93-b923-85a96b45a057" + "source": [ + "urllib.request.urlretrieve(\n", + " \"https://drive.usercontent.google.com/download?id=1MW1lPgkTq3YG9ikuq6u3d9sfpt-wKQZ0&export=download\",\n", + " \"books__15m.txt\")" + ] }, { "cell_type": "markdown", + "id": "9109d3c0-e276-44cc-9f36-f8c79eb48b31", "metadata": { "id": "9109d3c0-e276-44cc-9f36-f8c79eb48b31" }, "source": [ + "## 2. First Attempt: Global Memory Histogram\n", + "\n", "A histogram kernel counts the number of times a value occurs in a dataset. To implement this, we create an array that is large enough to store all possible values (in the case of counting 1-byte ASCII characters, 256 elements). Then for the value of each element in the dataset, we increment its location in the array.\n", "\n", "Let's try a simple way to implement this:" - ], - "id": "9109d3c0-e276-44cc-9f36-f8c79eb48b31" + ] }, { "cell_type": "code", + "execution_count": 4, + "id": "61c12795-b14a-4447-9dcf-9748616cc453", "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -91,6 +108,15 @@ "id": "61c12795-b14a-4447-9dcf-9748616cc453", "outputId": "141b29b8-bbf7-4224-c85a-23e49ee7abaf" }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Writing histogram_global.py\n" + ] + } + ], "source": [ "%%writefile histogram_global.py\n", "\n", @@ -135,30 +161,22 @@ " launch(check=True, output=False)\n", " D = cpx.profiler.benchmark(launch, (False, False), n_repeat=15, n_warmup=4).gpu_times[0]\n", " print(f\"{D.mean():.3g} s ± {(D.std() / D.mean()):.2%} (mean ± relative stdev of {D.size} runs)\")" - ], - "execution_count": 4, - "outputs": [ - { - "output_type": "stream", - "text": [ - "Writing histogram_global.py\n" - ] - } - ], - "id": "61c12795-b14a-4447-9dcf-9748616cc453" + ] }, { "cell_type": "markdown", + "id": "27e30efa-3a37-402f-9414-e444214d8ce6", "metadata": { "id": "27e30efa-3a37-402f-9414-e444214d8ce6" }, "source": [ "Now let's make sure it runs and check the output." - ], - "id": "27e30efa-3a37-402f-9414-e444214d8ce6" + ] }, { "cell_type": "code", + "execution_count": 5, + "id": "b3f32240-ad7b-4717-98b3-82b0298a099a", "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -166,22 +184,23 @@ "id": "b3f32240-ad7b-4717-98b3-82b0298a099a", "outputId": "cdf42faf-b6e6-45ad-85e2-d3b0d4c87ad5" }, - "source": [ - "!python histogram_global.py" - ], - "execution_count": 5, "outputs": [ { + "name": "stdout", "output_type": "stream", "text": [ "0.00858 s ± 3.22% (mean ± relative stdev of 15 runs)\n" ] } ], - "id": "b3f32240-ad7b-4717-98b3-82b0298a099a" + "source": [ + "!python histogram_global.py" + ] }, { "cell_type": "code", + "execution_count": null, + "id": "815cb072-b3a0-47aa-af6c-dd66c626a440", "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -190,6 +209,18 @@ "id": "815cb072-b3a0-47aa-af6c-dd66c626a440", "outputId": "d7a3d2e2-1df5-4681-c6a3-923cfb921c52" }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAnUAAAHWCAYAAAARl3+JAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAMUNJREFUeJzt3Xt4FOXB/vF7E5IJAXY5J6EuRAERDAQU4cUTUdFoqS3WKqJAxFOLhxJQ1OhPYrB1AUXQSks5g/jWE4qnCiq6iIigEJR4QEUCqXJSdBeiLjSZ3x++rEYSJHGzwzz5fq7ruS539pnsvc4V9/aZmY3Htm1bAAAAcLUEpwMAAADgl6PUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABGjkdwEmVlZX6/PPP1axZM3k8HqfjAACABsy2be3Zs0ft2rVTQkLt190adKn7/PPP5ff7nY4BAAAQVVZWpqOOOqrW+zXoUtesWTNJ3//L83q9DqcBAAANWTgclt/vj/aT2mrQpe7AKVev10upAwAAR4S6XhLGjRIAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABggEZOBzgSZBUuVYKV6nQMAABwBCqdMNDpCIeFlToAAAADUOoAAAAMUOtSt2vXLo0cOVLt27eXZVlKT09Xbm6uVq5cKUnKzMyUx+ORx+NRkyZNdMIJJ+jxxx+v8jO+/fZbtWzZUq1bt1YkEqn2dRYtWqScnBz5fD41bdpUPXr00Pjx47V7925J0rx586Kv8+ORkpJS27cEAADgerUudRdeeKGKi4s1f/58ffTRR3rmmWeUk5OjL7/8Mjpn/Pjx2rZtm4qLi3XSSSdp8ODBeuONN6LPL1q0SMcff7yOO+44LV68+KDXuP322zV48GCddNJJeuGFF1RSUqLJkyfrnXfe0UMPPRSd5/V6tW3btipjy5YttX1LAAAArlerGyW+/vprrVixQsFgUP3795ckdejQQX369Kkyr1mzZkpPT1d6erqmTZumhQsX6tlnn9XJJ58sSZo9e7aGDh0q27Y1e/ZsDR48OLrvmjVrdPfdd2vq1KkaNWpUdHtmZqbOPvtsff3119FtHo9H6enptX7TAAAApqnVSl3Tpk3VtGlTLV68uMbTpj/VqFEjJSUlad++fZKkTZs2adWqVbr44ot18cUXa8WKFVVW1x5++GE1bdpU1157bbU/r3nz5rWJDAAA0CDUqtQ1atRI8+bN0/z589W8eXOdcsopuu222/Tuu+9WO3/fvn0KBAIKhUI688wzJUlz5szReeedpxYtWqhly5bKzc3V3Llzo/t8/PHHOuaYY5SUlPSzeUKhULRoHhjnnXdejfMjkYjC4XCVAQAAYII6XVP3+eef65lnntG5556rYDCoE044QfPmzYvOueWWW9S0aVOlpqZq4sSJmjBhggYOHKiKigrNnz9fQ4cOjc4dOnSo5s2bp8rKSkmSbduHnaVZs2Zav359lTFr1qwa5wcCAfl8vujw+/21ffsAAABHJI9dmxZVg6uuukovvfSStmzZoszMTA0dOlSXX365mjZtqrS0NHk8HknSv//9bw0cOFCJiYlV9q+oqNCLL76os88+W6NGjdKcOXO0e/fuQ67WzZs3T/n5+VWusfs5kUikymnjcDgsv98vf/5jfPkwAACoVry+fDgcDsvn8ykUCsnr9dZ6/5h8T123bt1UXl4efdy6dWt16tRJ6enp0UInfX+DxCWXXHLQ6toll1yi2bNnS5IuvfRS7d27V3//+9+rfa3alLifsixLXq+3ygAAADBBre5+/fLLL3XRRRfpiiuuUI8ePdSsWTO9/fbbmjRpkn73u98dct9du3bp2Wef1TPPPKOsrKwqzw0fPlwXXHCBdu/erb59++rmm2/WjTfeqM8++0wXXHCB2rVrp08++UTTp0/XqaeeGr0r1rZtbd++/aDXatu2rRIS+F5lAADQcNSq1DVt2lR9+/bVlClTtGnTJu3fv19+v19XX321brvttkPuu2DBAjVp0kRnnXXWQc+dddZZaty4sRYuXKg///nPmjhxok488URNmzZN06dPV2VlpTp27Kg//OEPysvLi+4XDoeVkZFx0M/btm0bX3UCAAAalJhcU+dWB85dc00dAACoSYO6pg4AAADOqtXpV1OVFOVy0wQAAHA1VuoAAAAMQKkDAAAwAKUOAADAAFxTJymrcCl3vwKod/G6gw5Aw8RKHQAAgAEodQAAAAag1AEAABiAUgcAAGAAV5e6yspKBQIBHX300WrcuLGys7P1xBNPOB0L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zcFUAAAAznFlqQMAAEBVrrv7tT6UFOXK6/U6HQMAAKDOWKkDAAAwAKUOAADAAJx+lZRVuFQJVqrTMeqsdMJApyMAAACHsVIHAABgAEodAACAASh1AAAABqDUAQAAGMC1pW7BggVq1aqVIpFIle2DBg3SsGHDHEoFAADgDNeWuosuukgVFRV65plnott27typ559/XldccYWDyQAAAOLPtaWucePGuvTSSzV37tzotoULF6p9+/bKycmpdp9IJKJwOFxlAAAAmMC1pU6Srr76ar344ov67LPPJEnz5s3T5ZdfLo/HU+38QCAgn88XHX6/P55xAQAA6o2rS12vXr2UnZ2tBQsWaO3atXrvvfd0+eWX1zi/oKBAoVAoOsrKyuIXFgAAoB65/i9KXHXVVZo6dao+++wzDRgw4JCrb5ZlybKsOKYDAACID1ev1EnSpZdeqv/85z+aOXMmN0gAAIAGy/Wlzufz6cILL1TTpk01aNAgp+MAAAA4wvWlTpI+++wzXXbZZZxaBQAADZarr6n76quvFAwGFQwG9fe//93pOAAAAI5xdanr1auXvvrqK02cOFFdunSp888pKcqV1+uNYTIAAID4cnWpKy0tdToCAADAEcGIa+oAAAAaOkodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGKCR0wGcZNu2JCkcDjucBAAANHQH+siBflJbDbrUffnll5Ikv9/vcBIAAIDv7dmzRz6fr9b7NehS17JlS0nS1q1b6/QvD/EXDofl9/tVVlYmr9frdBwcBo6Z+3DM3Idj5j7VHTPbtrVnzx61a9euTj+zQZe6hITvLyn0+Xz8EriM1+vlmLkMx8x9OGbuwzFzn58es1+yyMSNEgAAAAag1AEAABigQZc6y7JUWFgoy7KcjoLDxDFzH46Z+3DM3Idj5j71ccw8dl3vmwUAAMARo0Gv1AEAAJiCUgcAAGAASh0AAIABjC9106ZNU2ZmplJSUtS3b1+tWbPmkPMff/xxHXfccUpJSVH37t3173//O05JcUBtjtm8efPk8XiqjJSUlDimbdhee+01nX/++WrXrp08Ho8WL178s/sEg0GdcMIJsixLnTp10rx58+o9J35Q22MWDAYP+h3zeDzavn17fAJDgUBAJ510kpo1a6a2bdtq0KBB2rhx48/ux+eZc+pyzGLxeWZ0qXv00Uc1ZswYFRYWat26dcrOzlZubq527txZ7fw33nhDQ4YM0ZVXXqni4mINGjRIgwYNUklJSZyTN1y1PWbS91/cuG3btujYsmVLHBM3bOXl5crOzta0adMOa/7mzZs1cOBAnXHGGVq/fr3y8/N11VVXaenSpfWcFAfU9pgdsHHjxiq/Z23btq2nhPip5cuX67rrrtObb76pl156Sfv379c555yj8vLyGvfh88xZdTlmUgw+z2yD9enTx77uuuuijysqKux27drZgUCg2vkXX3yxPXDgwCrb+vbta//xj3+s15z4QW2P2dy5c22fzxendDgUSfZTTz11yDk333yzffzxx1fZNnjwYDs3N7cek6Emh3PMXn31VVuS/dVXX8UlE37ezp07bUn28uXLa5zD59mR5XCOWSw+z4xdqdu3b5/Wrl2rAQMGRLclJCRowIABWrVqVbX7rFq1qsp8ScrNza1xPmKrLsdMkvbu3asOHTrI7/frd7/7nd577714xEUd8DvmXj179lRGRobOPvtsrVy50uk4DVooFJL0w98vrw6/a0eWwzlm0i//PDO21H3xxReqqKhQWlpale1paWk1Xguyffv2Ws1HbNXlmHXp0kVz5szR008/rYULF6qyslInn3yy/vOf/8QjMmqppt+xcDisb7/91qFUOJSMjAxNnz5dixYt0qJFi+T3+5WTk6N169Y5Ha1BqqysVH5+vk455RRlZWXVOI/PsyPH4R6zWHyeNYpFYMAp/fr1U79+/aKPTz75ZHXt2lX//Oc/dddddzmYDDBDly5d1KVLl+jjk08+WZs2bdKUKVP00EMPOZisYbruuutUUlKi119/3ekoOEyHe8xi8Xlm7Epd69atlZiYqB07dlTZvmPHDqWnp1e7T3p6eq3mI7bqcsx+KikpSb169dInn3xSHxHxC9X0O+b1etW4cWOHUqG2+vTpw++YA66//no999xzevXVV3XUUUcdci6fZ0eG2hyzn6rL55mxpS45OVknnniili1bFt1WWVmpZcuWVWnCP9avX78q8yXppZdeqnE+Yqsux+ynKioqtGHDBmVkZNRXTPwC/I6ZYf369fyOxZFt27r++uv11FNP6ZVXXtHRRx/9s/vwu+asuhyzn6rT59kvus3iCPfII4/YlmXZ8+bNs99//337mmuusZs3b25v377dtm3bHjZsmH3rrbdG569cudJu1KiRfe+999offPCBXVhYaCclJdkbNmxw6i00OLU9ZkVFRfbSpUvtTZs22WvXrrUvueQSOyUlxX7vvfecegsNyp49e+zi4mK7uLjYlmTfd999dnFxsb1lyxbbtm371ltvtYcNGxad/+mnn9qpqan22LFj7Q8++MCeNm2anZiYaC9ZssSpt9Dg1PaYTZkyxV68eLH98ccf2xs2bLBHjRplJyQk2C+//LJTb6HBGTlypO3z+exgMGhv27YtOr755pvoHD7Pjix1OWax+DwzutTZtm3/7W9/s9u3b28nJyfbffr0sd98883oc/3797fz8vKqzH/sscfsY4891k5OTraPP/54+/nnn49zYtTmmOXn50fnpqWl2b/+9a/tdevWOZC6YTrwdRc/HQeOUV5ent2/f/+D9unZs6ednJxsH3PMMfbcuXPjnrshq+0xmzhxot2xY0c7JSXFbtmypZ2Tk2O/8sorzoRvoKo7XpKq/O7weXZkqcsxi8Xnmef/XhwAAAAuZuw1dQAAAA0JpQ4AAMAAlDoAAAADUOoAAAAMQKkDAAAwAKUOAADAAJQ6AAAAA1DqAAAADECpAwAAOAyvvfaazj//fLVr104ej0eLFy+u9c+wbVv33nuvjj32WFmWpV/96lf661//GpN8lDoAcFBpaak8Ho/Wr1/vdBQAP6O8vFzZ2dmaNm1anX/GqFGjNGvWLN1777368MMP9cwzz6hPnz4xydcoJj8FAADAcOedd57OO++8Gp+PRCK6/fbb9a9//Utff/21srKyNHHiROXk5EiSPvjgA/3jH/9QSUmJunTpIkk6+uijY5aPlToADVplZaUmTZqkTp06ybIstW/fPnoqZMOGDTrzzDPVuHFjtWrVStdcc4327t0b3TcnJ0f5+flVft6gQYN0+eWXRx9nZmbq7rvv1hVXXKFmzZqpffv2mjFjRvT5A/9B79WrlzweT/Q//gDc5/rrr9eqVav0yCOP6N1339VFF12kc889Vx9//LEk6dlnn9Uxxxyj5557TkcffbQyMzN11VVXaffu3TF5fUodgAatoKBAEyZM0B133KH3339f//u//6u0tDSVl5crNzdXLVq00FtvvaXHH39cL7/8sq6//vpav8bkyZPVu3dvFRcX69prr9XIkSO1ceNGSdKaNWskSS+//LK2bdumJ598MqbvD0B8bN26VXPnztXjjz+u0047TR07dtRNN92kU089VXPnzpUkffrpp9qyZYsef/xxLViwQPPmzdPatWv1hz/8ISYZOP0KoMHas2eP7r//fj344IPKy8uTJHXs2FGnnnqqZs6cqe+++04LFixQkyZNJEkPPvigzj//fE2cOFFpaWmH/Tq//vWvde2110qSbrnlFk2ZMkWvvvqqunTpojZt2kiSWrVqpfT09Bi/QwDxsmHDBlVUVOjYY4+tsj0SiahVq1aSvj8zEIlEtGDBgui82bNn68QTT9TGjRujp2TrilIHoMH64IMPFIlEdNZZZ1X7XHZ2drTQSdIpp5yiyspKbdy4sValrkePHtF/9ng8Sk9P186dO39ZeABHlL179yoxMVFr165VYmJileeaNm0qScrIyFCjRo2qFL+uXbtK+n6lj1IHAHXUuHHjX7R/QkKCbNuusm3//v0HzUtKSqry2OPxqLKy8he9NoAjS69evVRRUaGdO3fqtNNOq3bOKaecov/+97/atGmTOnbsKEn66KOPJEkdOnT4xRm4pg5Ag9W5c2c1btxYy5YtO+i5rl276p133lF5eXl028qVK5WQkBD9v+k2bdpo27Zt0ecrKipUUlJSqwzJycnRfQEc2fbu3av169dHv4Jo8+bNWr9+vbZu3apjjz1Wl112mYYPH64nn3xSmzdv1po1axQIBPT8889LkgYMGKATTjhBV1xxhYqLi7V27Vr98Y9/1Nlnn33Qadu6oNQBaLBSUlJ0yy236Oabb9aCBQu0adMmvfnmm5o9e7Yuu+wypaSkKC8vTyUlJXr11Vd1ww03aNiwYdFTr2eeeaaef/55Pf/88/rwww81cuRIff3117XK0LZtWzVu3FhLlizRjh07FAqF6uGdAoiFt99+W7169VKvXr0kSWPGjFGvXr00btw4SdLcuXM1fPhw3XjjjerSpYsGDRqkt956S+3bt5f0/er+s88+q9atW+v000/XwIED1bVrVz3yyCMxycfpVwAN2h133KFGjRpp3Lhx+vzzz5WRkaE//elPSk1N1dKlSzVq1CiddNJJSk1N1YUXXqj77rsvuu8VV1yhd955R8OHD1ejRo00evRonXHGGbV6/UaNGumBBx7Q+PHjNW7cOJ122mkKBoMxfpcAYiEnJ+egSy5+LCkpSUVFRSoqKqpxTrt27bRo0aL6iCePfah0AAAAcAVOvwIAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAb4/xVzwo7mVWfDAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "histogram_output = !python histogram_global.py output\n", "histogram = np.loadtxt(histogram_output, delimiter=\",\")\n", @@ -205,32 +236,22 @@ "\n", "length = os.path.getsize(\"books__15m.txt\")\n", "print(f\"Characters in dataset: {length / 1e6:.1f} MB\")" - ], - "execution_count": null, - "outputs": [ - { - "output_type": "display_data", - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAnUAAAHWCAYAAAARl3+JAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAMUNJREFUeJzt3Xt4FOXB/vF7E5IJAXY5J6EuRAERDAQU4cUTUdFoqS3WKqJAxFOLhxJQ1OhPYrB1AUXQSks5g/jWE4qnCiq6iIigEJR4QEUCqXJSdBeiLjSZ3x++rEYSJHGzwzz5fq7ruS539pnsvc4V9/aZmY3Htm1bAAAAcLUEpwMAAADgl6PUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABGjkdwEmVlZX6/PPP1axZM3k8HqfjAACABsy2be3Zs0ft2rVTQkLt190adKn7/PPP5ff7nY4BAAAQVVZWpqOOOqrW+zXoUtesWTNJ3//L83q9DqcBAAANWTgclt/vj/aT2mrQpe7AKVev10upAwAAR4S6XhLGjRIAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABggEZOBzgSZBUuVYKV6nQMAABwBCqdMNDpCIeFlToAAAADUOoAAAAMUOtSt2vXLo0cOVLt27eXZVlKT09Xbm6uVq5cKUnKzMyUx+ORx+NRkyZNdMIJJ+jxxx+v8jO+/fZbtWzZUq1bt1YkEqn2dRYtWqScnBz5fD41bdpUPXr00Pjx47V7925J0rx586Kv8+ORkpJS27cEAADgerUudRdeeKGKi4s1f/58ffTRR3rmmWeUk5OjL7/8Mjpn/Pjx2rZtm4qLi3XSSSdp8ODBeuONN6LPL1q0SMcff7yOO+44LV68+KDXuP322zV48GCddNJJeuGFF1RSUqLJkyfrnXfe0UMPPRSd5/V6tW3btipjy5YttX1LAAAArlerGyW+/vprrVixQsFgUP3795ckdejQQX369Kkyr1mzZkpPT1d6erqmTZumhQsX6tlnn9XJJ58sSZo9e7aGDh0q27Y1e/ZsDR48OLrvmjVrdPfdd2vq1KkaNWpUdHtmZqbOPvtsff3119FtHo9H6enptX7TAAAApqnVSl3Tpk3VtGlTLV68uMbTpj/VqFEjJSUlad++fZKkTZs2adWqVbr44ot18cUXa8WKFVVW1x5++GE1bdpU1157bbU/r3nz5rWJDAAA0CDUqtQ1atRI8+bN0/z589W8eXOdcsopuu222/Tuu+9WO3/fvn0KBAIKhUI688wzJUlz5szReeedpxYtWqhly5bKzc3V3Llzo/t8/PHHOuaYY5SUlPSzeUKhULRoHhjnnXdejfMjkYjC4XCVAQAAYII6XVP3+eef65lnntG5556rYDCoE044QfPmzYvOueWWW9S0aVOlpqZq4sSJmjBhggYOHKiKigrNnz9fQ4cOjc4dOnSo5s2bp8rKSkmSbduHnaVZs2Zav359lTFr1qwa5wcCAfl8vujw+/21ffsAAABHJI9dmxZVg6uuukovvfSStmzZoszMTA0dOlSXX365mjZtqrS0NHk8HknSv//9bw0cOFCJiYlV9q+oqNCLL76os88+W6NGjdKcOXO0e/fuQ67WzZs3T/n5+VWusfs5kUikymnjcDgsv98vf/5jfPkwAACoVry+fDgcDsvn8ykUCsnr9dZ6/5h8T123bt1UXl4efdy6dWt16tRJ6enp0UInfX+DxCWXXHLQ6toll1yi2bNnS5IuvfRS7d27V3//+9+rfa3alLifsixLXq+3ygAAADBBre5+/fLLL3XRRRfpiiuuUI8ePdSsWTO9/fbbmjRpkn73u98dct9du3bp2Wef1TPPPKOsrKwqzw0fPlwXXHCBdu/erb59++rmm2/WjTfeqM8++0wXXHCB2rVrp08++UTTp0/XqaeeGr0r1rZtbd++/aDXatu2rRIS+F5lAADQcNSq1DVt2lR9+/bVlClTtGnTJu3fv19+v19XX321brvttkPuu2DBAjVp0kRnnXXWQc+dddZZaty4sRYuXKg///nPmjhxok488URNmzZN06dPV2VlpTp27Kg//OEPysvLi+4XDoeVkZFx0M/btm0bX3UCAAAalJhcU+dWB85dc00dAACoSYO6pg4AAADOqtXpV1OVFOVy0wQAAHA1VuoAAAAMQKkDAAAwAKUOAADAAFxTJymrcCl3vwKod/G6gw5Aw8RKHQAAgAEodQAAAAag1AEAABiAUgcAAGAAV5e6yspKBQIBHX300WrcuLGys7P1xBNPOB0L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zcFUAAAAznFlqQMAAEBVrrv7tT6UFOXK6/U6HQMAAKDOWKkDAAAwAKUOAADAAJx+lZRVuFQJVqrTMeqsdMJApyMAAACHsVIHAABgAEodAACAASh1AAAABqDUAQAAGMC1pW7BggVq1aqVIpFIle2DBg3SsGHDHEoFAADgDNeWuosuukgVFRV65plnott27typ559/XldccYWDyQAAAOLPtaWucePGuvTSSzV37tzotoULF6p9+/bKycmpdp9IJKJwOFxlAAAAmMC1pU6Srr76ar344ov67LPPJEnz5s3T5ZdfLo/HU+38QCAgn88XHX6/P55xAQAA6o2rS12vXr2UnZ2tBQsWaO3atXrvvfd0+eWX1zi/oKBAoVAoOsrKyuIXFgAAoB65/i9KXHXVVZo6dao+++wzDRgw4JCrb5ZlybKsOKYDAACID1ev1EnSpZdeqv/85z+aOXMmN0gAAIAGy/Wlzufz6cILL1TTpk01aNAgp+MAAAA4wvWlTpI+++wzXXbZZZxaBQAADZarr6n76quvFAwGFQwG9fe//93pOAAAAI5xdanr1auXvvrqK02cOFFdunSp888pKcqV1+uNYTIAAID4cnWpKy0tdToCAADAEcGIa+oAAAAaOkodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGKCR0wGcZNu2JCkcDjucBAAANHQH+siBflJbDbrUffnll5Ikv9/vcBIAAIDv7dmzRz6fr9b7NehS17JlS0nS1q1b6/QvD/EXDofl9/tVVlYmr9frdBwcBo6Z+3DM3Idj5j7VHTPbtrVnzx61a9euTj+zQZe6hITvLyn0+Xz8EriM1+vlmLkMx8x9OGbuwzFzn58es1+yyMSNEgAAAAag1AEAABigQZc6y7JUWFgoy7KcjoLDxDFzH46Z+3DM3Idj5j71ccw8dl3vmwUAAMARo0Gv1AEAAJiCUgcAAGAASh0AAIABjC9106ZNU2ZmplJSUtS3b1+tWbPmkPMff/xxHXfccUpJSVH37t3173//O05JcUBtjtm8efPk8XiqjJSUlDimbdhee+01nX/++WrXrp08Ho8WL178s/sEg0GdcMIJsixLnTp10rx58+o9J35Q22MWDAYP+h3zeDzavn17fAJDgUBAJ510kpo1a6a2bdtq0KBB2rhx48/ux+eZc+pyzGLxeWZ0qXv00Uc1ZswYFRYWat26dcrOzlZubq527txZ7fw33nhDQ4YM0ZVXXqni4mINGjRIgwYNUklJSZyTN1y1PWbS91/cuG3btujYsmVLHBM3bOXl5crOzta0adMOa/7mzZs1cOBAnXHGGVq/fr3y8/N11VVXaenSpfWcFAfU9pgdsHHjxiq/Z23btq2nhPip5cuX67rrrtObb76pl156Sfv379c555yj8vLyGvfh88xZdTlmUgw+z2yD9enTx77uuuuijysqKux27drZgUCg2vkXX3yxPXDgwCrb+vbta//xj3+s15z4QW2P2dy5c22fzxendDgUSfZTTz11yDk333yzffzxx1fZNnjwYDs3N7cek6Emh3PMXn31VVuS/dVXX8UlE37ezp07bUn28uXLa5zD59mR5XCOWSw+z4xdqdu3b5/Wrl2rAQMGRLclJCRowIABWrVqVbX7rFq1qsp8ScrNza1xPmKrLsdMkvbu3asOHTrI7/frd7/7nd577714xEUd8DvmXj179lRGRobOPvtsrVy50uk4DVooFJL0w98vrw6/a0eWwzlm0i//PDO21H3xxReqqKhQWlpale1paWk1Xguyffv2Ws1HbNXlmHXp0kVz5szR008/rYULF6qyslInn3yy/vOf/8QjMmqppt+xcDisb7/91qFUOJSMjAxNnz5dixYt0qJFi+T3+5WTk6N169Y5Ha1BqqysVH5+vk455RRlZWXVOI/PsyPH4R6zWHyeNYpFYMAp/fr1U79+/aKPTz75ZHXt2lX//Oc/dddddzmYDDBDly5d1KVLl+jjk08+WZs2bdKUKVP00EMPOZisYbruuutUUlKi119/3ekoOEyHe8xi8Xlm7Epd69atlZiYqB07dlTZvmPHDqWnp1e7T3p6eq3mI7bqcsx+KikpSb169dInn3xSHxHxC9X0O+b1etW4cWOHUqG2+vTpw++YA66//no999xzevXVV3XUUUcdci6fZ0eG2hyzn6rL55mxpS45OVknnniili1bFt1WWVmpZcuWVWnCP9avX78q8yXppZdeqnE+Yqsux+ynKioqtGHDBmVkZNRXTPwC/I6ZYf369fyOxZFt27r++uv11FNP6ZVXXtHRRx/9s/vwu+asuhyzn6rT59kvus3iCPfII4/YlmXZ8+bNs99//337mmuusZs3b25v377dtm3bHjZsmH3rrbdG569cudJu1KiRfe+999offPCBXVhYaCclJdkbNmxw6i00OLU9ZkVFRfbSpUvtTZs22WvXrrUvueQSOyUlxX7vvfecegsNyp49e+zi4mK7uLjYlmTfd999dnFxsb1lyxbbtm371ltvtYcNGxad/+mnn9qpqan22LFj7Q8++MCeNm2anZiYaC9ZssSpt9Dg1PaYTZkyxV68eLH98ccf2xs2bLBHjRplJyQk2C+//LJTb6HBGTlypO3z+exgMGhv27YtOr755pvoHD7Pjix1OWax+DwzutTZtm3/7W9/s9u3b28nJyfbffr0sd98883oc/3797fz8vKqzH/sscfsY4891k5OTraPP/54+/nnn49zYtTmmOXn50fnpqWl2b/+9a/tdevWOZC6YTrwdRc/HQeOUV5ent2/f/+D9unZs6ednJxsH3PMMfbcuXPjnrshq+0xmzhxot2xY0c7JSXFbtmypZ2Tk2O/8sorzoRvoKo7XpKq/O7weXZkqcsxi8Xnmef/XhwAAAAuZuw1dQAAAA0JpQ4AAMAAlDoAAAADUOoAAAAMQKkDAAAwAKUOAADAAJQ6AAAAA1DqAAAADECpAwAAOAyvvfaazj//fLVr104ej0eLFy+u9c+wbVv33nuvjj32WFmWpV/96lf661//GpN8lDoAcFBpaak8Ho/Wr1/vdBQAP6O8vFzZ2dmaNm1anX/GqFGjNGvWLN1777368MMP9cwzz6hPnz4xydcoJj8FAADAcOedd57OO++8Gp+PRCK6/fbb9a9//Utff/21srKyNHHiROXk5EiSPvjgA/3jH/9QSUmJunTpIkk6+uijY5aPlToADVplZaUmTZqkTp06ybIstW/fPnoqZMOGDTrzzDPVuHFjtWrVStdcc4327t0b3TcnJ0f5+flVft6gQYN0+eWXRx9nZmbq7rvv1hVXXKFmzZqpffv2mjFjRvT5A/9B79WrlzweT/Q//gDc5/rrr9eqVav0yCOP6N1339VFF12kc889Vx9//LEk6dlnn9Uxxxyj5557TkcffbQyMzN11VVXaffu3TF5fUodgAatoKBAEyZM0B133KH3339f//u//6u0tDSVl5crNzdXLVq00FtvvaXHH39cL7/8sq6//vpav8bkyZPVu3dvFRcX69prr9XIkSO1ceNGSdKaNWskSS+//LK2bdumJ598MqbvD0B8bN26VXPnztXjjz+u0047TR07dtRNN92kU089VXPnzpUkffrpp9qyZYsef/xxLViwQPPmzdPatWv1hz/8ISYZOP0KoMHas2eP7r//fj344IPKy8uTJHXs2FGnnnqqZs6cqe+++04LFixQkyZNJEkPPvigzj//fE2cOFFpaWmH/Tq//vWvde2110qSbrnlFk2ZMkWvvvqqunTpojZt2kiSWrVqpfT09Bi/QwDxsmHDBlVUVOjYY4+tsj0SiahVq1aSvj8zEIlEtGDBgui82bNn68QTT9TGjRujp2TrilIHoMH64IMPFIlEdNZZZ1X7XHZ2drTQSdIpp5yiyspKbdy4sValrkePHtF/9ng8Sk9P186dO39ZeABHlL179yoxMVFr165VYmJileeaNm0qScrIyFCjRo2qFL+uXbtK+n6lj1IHAHXUuHHjX7R/QkKCbNuusm3//v0HzUtKSqry2OPxqLKy8he9NoAjS69evVRRUaGdO3fqtNNOq3bOKaecov/+97/atGmTOnbsKEn66KOPJEkdOnT4xRm4pg5Ag9W5c2c1btxYy5YtO+i5rl276p133lF5eXl028qVK5WQkBD9v+k2bdpo27Zt0ecrKipUUlJSqwzJycnRfQEc2fbu3av169dHv4Jo8+bNWr9+vbZu3apjjz1Wl112mYYPH64nn3xSmzdv1po1axQIBPT8889LkgYMGKATTjhBV1xxhYqLi7V27Vr98Y9/1Nlnn33Qadu6oNQBaLBSUlJ0yy236Oabb9aCBQu0adMmvfnmm5o9e7Yuu+wypaSkKC8vTyUlJXr11Vd1ww03aNiwYdFTr2eeeaaef/55Pf/88/rwww81cuRIff3117XK0LZtWzVu3FhLlizRjh07FAqF6uGdAoiFt99+W7169VKvXr0kSWPGjFGvXr00btw4SdLcuXM1fPhw3XjjjerSpYsGDRqkt956S+3bt5f0/er+s88+q9atW+v000/XwIED1bVrVz3yyCMxycfpVwAN2h133KFGjRpp3Lhx+vzzz5WRkaE//elPSk1N1dKlSzVq1CiddNJJSk1N1YUXXqj77rsvuu8VV1yhd955R8OHD1ejRo00evRonXHGGbV6/UaNGumBBx7Q+PHjNW7cOJ122mkKBoMxfpcAYiEnJ+egSy5+LCkpSUVFRSoqKqpxTrt27bRo0aL6iCePfah0AAAAcAVOvwIAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAb4/xVzwo7mVWfDAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - } - } - ], - "id": "815cb072-b3a0-47aa-af6c-dd66c626a440" + ] }, { "cell_type": "markdown", + "id": "b14fa522-b41b-4538-8c34-ecc355e55116", "metadata": { "id": "b14fa522-b41b-4538-8c34-ecc355e55116" }, "source": [ + "## 3. Fixing Data Races with Atomics\n", + "\n", "It looks like something is wrong - our counts are very low, and the most common characters don't make a lot of sense. Many of our increments seem to get lost!\n", "\n", "What's happening here is called a data race. Many different threads are trying to access the bins of the histogram at the same time.\n", "\n", - "Imagine that two threads are trying to update the same bin.\n", + "Imagine that two threads are trying to update the same bin:\n", "\n", "- Thread 0 reads the count of the bin, which is 0, and stores it in its local variable `old_count`.\n", "- Thread 0 adds 1 to its `old_count`, producing a `new_count` of 1.\n", @@ -240,21 +261,24 @@ "- Thread 1 stores `new_count` to the bin, setting it to 1, and losing the increment from thread 0!\n", "\n", "To fix this, we need to use atomic operations. `cuda.atomic.add(array, index, value)` will perform `array[index] += value` as a single indivisible operation. This will ensure that no increments get lost." - ], - "id": "b14fa522-b41b-4538-8c34-ecc355e55116" + ] }, { "cell_type": "markdown", + "id": "08f4dded-26a7-4ef8-b981-e00c569ca4d0", "metadata": { "id": "08f4dded-26a7-4ef8-b981-e00c569ca4d0" }, "source": [ + "## 4. Profiling the Naive Solution\n", + "\n", "Now let's profile our code." - ], - "id": "08f4dded-26a7-4ef8-b981-e00c569ca4d0" + ] }, { "cell_type": "code", + "execution_count": null, + "id": "8dbd226c-66f2-43df-868a-6b024b1de24c", "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -262,13 +286,9 @@ "id": "8dbd226c-66f2-43df-868a-6b024b1de24c", "outputId": "a082d13a-8e86-436d-c3de-8351f337d824" }, - "source": [ - "!ncu -f --kernel-name regex:histogram_global --set full -o histogram_global python histogram_global.py\n", - "histogram_global_csv = !ncu --import histogram_global.ncu-rep --csv" - ], - "execution_count": null, "outputs": [ { + "name": "stdout", "output_type": "stream", "text": [ "==PROF== Connected to process 1318 (/usr/bin/python3.12)\n", @@ -278,10 +298,15 @@ ] } ], - "id": "8dbd226c-66f2-43df-868a-6b024b1de24c" + "source": [ + "!ncu -f --kernel-name regex:histogram_global --set full -o histogram_global python histogram_global.py\n", + "histogram_global_csv = !ncu --import histogram_global.ncu-rep --csv" + ] }, { "cell_type": "code", + "execution_count": 8, + "id": "ad12380e-253b-4410-ab34-9479411fdf81", "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -325,22 +350,18 @@ "id": "ad12380e-253b-4410-ab34-9479411fdf81", "outputId": "f2326771-7c13-46fa-a489-bf494d76cc1e" }, - "source": [ - "nsightful.display_ncu_csv_in_notebook(histogram_global_csv)" - ], - "execution_count": 8, "outputs": [ { - "output_type": "display_data", "data": { "application/javascript": "window[\"de51e67e-9ed4-11f0-a7c3-0242ac1c000c\"] = google.colab.output.setIframeHeight(-1, true, {\"interactive\": true, \"maxHeight\": 99999});\n//# sourceURL=js_65154ce01f", "text/plain": [ "" ] - } + }, + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { "text/html": [ "\n", @@ -387,10 +408,11 @@ "text/plain": [ "" ] - } + }, + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "a21053863aa949e484f30f47bcf5c045", @@ -400,10 +422,11 @@ "text/plain": [ "Dropdown(description='Kernel:', layout=Layout(width='400px'), options=('histogram_global',), style=Description…" ] - } + }, + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "804c25036085410fbbcba82f19667d1d", @@ -413,29 +436,37 @@ "text/plain": [ "Output()" ] - } + }, + "metadata": {}, + "output_type": "display_data" } ], - "id": "ad12380e-253b-4410-ab34-9479411fdf81" + "source": [ + "nsightful.display_ncu_csv_in_notebook(histogram_global_csv)" + ] }, { "cell_type": "markdown", + "id": "e1f72831-780f-4cf5-8ff1-2092ecb193d9", "metadata": { "id": "e1f72831-780f-4cf5-8ff1-2092ecb193d9" }, "source": [ - "We improved the code by separating loading from values from the histogram update and to perform striped loads using [cuda.cooperative](https://nvidia.github.io/cccl/unstable/python/coop.html)'s block load instead of doing the I/O by hand.\n", + "## 5. Solution: Shared Memory & Cooperative Groups\n", + "\n", + "We improved the code by separating loading values from the histogram update and to perform striped loads (also known as coalesced access) using [cuda.cooperative](https://nvidia.github.io/cccl/unstable/python/coop.html)'s block load instead of doing the I/O by hand.\n", "\n", "While that helps a bit, our code still has major issues. It's taking thousand of cycles to issue a single operation!\n", "\n", "This is happening due to contention - we have hundreds of thousands of threads performing atomic updates to just 256 bins of a global histogram. All of those atomic operations have to happen in order, so they are serialized by the memory subsystem, destroying our parallelism.\n", "\n", "Instead, we can construct a local histogram for each block, which we will update atomically within the block. Then, we synchronize all of the threads within the block, and we perform atomic updates of the global histogram with the aggregate counts t" - ], - "id": "e1f72831-780f-4cf5-8ff1-2092ecb193d9" + ] }, { "cell_type": "code", + "execution_count": 9, + "id": "cf7c9865-646a-4bbd-9b41-61cadfc5484c", "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -443,6 +474,15 @@ "id": "cf7c9865-646a-4bbd-9b41-61cadfc5484c", "outputId": "851b231f-4431-4cf3-b857-c5a966a7162f" }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Writing histogram_localized.py\n" + ] + } + ], "source": [ "%%writefile histogram_localized.py\n", "\n", @@ -511,30 +551,22 @@ " launch(check=True, output=False)\n", " D = cpx.profiler.benchmark(launch, (False, False), n_repeat=15, n_warmup=4).gpu_times[0]\n", " print(f\"{D.mean():.3g} s ± {(D.std() / D.mean()):.2%} (mean ± relative stdev of {D.size} runs)\")" - ], - "execution_count": 9, - "outputs": [ - { - "output_type": "stream", - "text": [ - "Writing histogram_localized.py\n" - ] - } - ], - "id": "cf7c9865-646a-4bbd-9b41-61cadfc5484c" + ] }, { "cell_type": "markdown", + "id": "d2de69a4-644d-481d-b2c7-a8674616e9e2", "metadata": { "id": "d2de69a4-644d-481d-b2c7-a8674616e9e2" }, "source": [ "Let's make sure it runs correctly:" - ], - "id": "d2de69a4-644d-481d-b2c7-a8674616e9e2" + ] }, { "cell_type": "code", + "execution_count": 10, + "id": "1b30e9b3-5a4c-4181-b642-b7def5e9f258", "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -542,22 +574,23 @@ "id": "1b30e9b3-5a4c-4181-b642-b7def5e9f258", "outputId": "3a04d1d0-3409-441c-af2a-3138e9dec324" }, - "source": [ - "!python histogram_localized.py" - ], - "execution_count": 10, "outputs": [ { + "name": "stdout", "output_type": "stream", "text": [ "0.000714 s ± 2.97% (mean ± relative stdev of 15 runs)\n" ] } ], - "id": "1b30e9b3-5a4c-4181-b642-b7def5e9f258" + "source": [ + "!python histogram_localized.py" + ] }, { "cell_type": "code", + "execution_count": null, + "id": "73f0c3cd-349b-490f-b6bd-7afbeb442fff", "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -566,6 +599,18 @@ "id": "73f0c3cd-349b-490f-b6bd-7afbeb442fff", "outputId": "01324ca9-72c3-4317-a5be-f0bf144b7b7c" }, + "outputs": [ + { + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAnUAAAHWCAYAAAARl3+JAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAMUNJREFUeJzt3Xt4FOXB/vF7E5IJAXY5J6EuRAERDAQU4cUTUdFoqS3WKqJAxFOLhxJQ1OhPYrB1AUXQSks5g/jWE4qnCiq6iIigEJR4QEUCqXJSdBeiLjSZ3x++rEYSJHGzwzz5fq7ruS539pnsvc4V9/aZmY3Htm1bAAAAcLUEpwMAAADgl6PUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABGjkdwEmVlZX6/PPP1axZM3k8HqfjAACABsy2be3Zs0ft2rVTQkLt190adKn7/PPP5ff7nY4BAAAQVVZWpqOOOqrW+zXoUtesWTNJ3//L83q9DqcBAAANWTgclt/vj/aT2mrQpe7AKVev10upAwAAR4S6XhLGjRIAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABggEZOBzgSZBUuVYKV6nQMAABwBCqdMNDpCIeFlToAAAADUOoAAAAMUOtSt2vXLo0cOVLt27eXZVlKT09Xbm6uVq5cKUnKzMyUx+ORx+NRkyZNdMIJJ+jxxx+v8jO+/fZbtWzZUq1bt1YkEqn2dRYtWqScnBz5fD41bdpUPXr00Pjx47V7925J0rx586Kv8+ORkpJS27cEAADgerUudRdeeKGKi4s1f/58ffTRR3rmmWeUk5OjL7/8Mjpn/Pjx2rZtm4qLi3XSSSdp8ODBeuONN6LPL1q0SMcff7yOO+44LV68+KDXuP322zV48GCddNJJeuGFF1RSUqLJkyfrnXfe0UMPPRSd5/V6tW3btipjy5YttX1LAAAArlerGyW+/vprrVixQsFgUP3795ckdejQQX369Kkyr1mzZkpPT1d6erqmTZumhQsX6tlnn9XJJ58sSZo9e7aGDh0q27Y1e/ZsDR48OLrvmjVrdPfdd2vq1KkaNWpUdHtmZqbOPvtsff3119FtHo9H6enptX7TAAAApqnVSl3Tpk3VtGlTLV68uMbTpj/VqFEjJSUlad++fZKkTZs2adWqVbr44ot18cUXa8WKFVVW1x5++GE1bdpU1157bbU/r3nz5rWJDAAA0CDUqtQ1atRI8+bN0/z589W8eXOdcsopuu222/Tuu+9WO3/fvn0KBAIKhUI688wzJUlz5szReeedpxYtWqhly5bKzc3V3Llzo/t8/PHHOuaYY5SUlPSzeUKhULRoHhjnnXdejfMjkYjC4XCVAQAAYII6XVP3+eef65lnntG5556rYDCoE044QfPmzYvOueWWW9S0aVOlpqZq4sSJmjBhggYOHKiKigrNnz9fQ4cOjc4dOnSo5s2bp8rKSkmSbduHnaVZs2Zav359lTFr1qwa5wcCAfl8vujw+/21ffsAAABHJI9dmxZVg6uuukovvfSStmzZoszMTA0dOlSXX365mjZtqrS0NHk8HknSv//9bw0cOFCJiYlV9q+oqNCLL76os88+W6NGjdKcOXO0e/fuQ67WzZs3T/n5+VWusfs5kUikymnjcDgsv98vf/5jfPkwAACoVry+fDgcDsvn8ykUCsnr9dZ6/5h8T123bt1UXl4efdy6dWt16tRJ6enp0UInfX+DxCWXXHLQ6toll1yi2bNnS5IuvfRS7d27V3//+9+rfa3alLifsixLXq+3ygAAADBBre5+/fLLL3XRRRfpiiuuUI8ePdSsWTO9/fbbmjRpkn73u98dct9du3bp2Wef1TPPPKOsrKwqzw0fPlwXXHCBdu/erb59++rmm2/WjTfeqM8++0wXXHCB2rVrp08++UTTp0/XqaeeGr0r1rZtbd++/aDXatu2rRIS+F5lAADQcNSq1DVt2lR9+/bVlClTtGnTJu3fv19+v19XX321brvttkPuu2DBAjVp0kRnnXXWQc+dddZZaty4sRYuXKg///nPmjhxok488URNmzZN06dPV2VlpTp27Kg//OEPysvLi+4XDoeVkZFx0M/btm0bX3UCAAAalJhcU+dWB85dc00dAACoSYO6pg4AAADOqtXpV1OVFOVy0wQAAHA1VuoAAAAMQKkDAAAwAKUOAADAAFxTJymrcCl3vwKod/G6gw5Aw8RKHQAAgAEodQAAAAag1AEAABiAUgcAAGAAV5e6yspKBQIBHX300WrcuLGys7P1xBNPOB0LAAAg7lx992sgENDChQs1ffp0de7cWa+99pqGDh2qNm3aqH///k7HAwAAiBvXlrpIJKK7775bL7/8svr16ydJOuaYY/T666/rn//8Z7WlLhKJKBKJRB+Hw+G45QUAAKhPri11n3zyib755hudffbZVbbv27dPvXr1qnafQCCgoqKieMQDAACIK9eWur1790qSnn/+ef3qV7+q8pxlWdXuU1BQoDFjxkQfh8Nh+f3++gsJAAAQJ64tdd26dZNlWdq6dethXz9nWVaNhQ8AAMDNXFvqmjVrpptuukmjR49WZWWlTj31VIVCIa1cuVJer1d5eXlORwQAAIgb15Y6SbrrrrvUpk0bBQIBffrpp2revLlOOOEE3XbbbU5HAwAAiCtXlzqPx6NRo0Zp1KhRTkcBAABwlKu/fBgAAADfc/VKXayUFOXK6/U6HQMAAKDOWKkDAAAwAKUOAADAAJQ6AAAAA3BNnaSswqVKsFKdjgG4SumEgU5HAAD8CCt1AAAABqDUAQAAGMCYUpeTk6P8/HynYwAAADjCmFIHAADQkBlR6i6//HItX75c999/vzwejzwej0pLS52OBQAAEDdG3P16//3366OPPlJWVpbGjx8vSWrTps1B8yKRiCKRSPRxOByOW0YAAID6ZMRKnc/nU3JyslJTU5Wenq709HQlJiYeNC8QCMjn80WH3+93IC0AAEDsGVHqDldBQYFCoVB0lJWVOR0JAAAgJow4/Xq4LMuSZVlOxwAAAIg5Y1bqkpOTVVFR4XQMAAAARxhT6jIzM7V69WqVlpbqiy++UGVlpdORAAAA4saYUnfTTTcpMTFR3bp1U5s2bbR161anIwEAAMSNMdfUHXvssVq1apXTMQAAABxhzEodAABAQ2bMSt0vUVKUK6/X63QMAACAOmOlDgAAwACUOgAAAANQ6gAAAAzANXWSsgqXKsFKdToGUGelEwY6HQEA4DBW6gAAAAxAqQMAADAApQ4AAMAAri51kUhEf/7zn9W2bVulpKTo1FNP1VtvveV0LAAAgLhzdam7+eabtWjRIs2fP1/r1q1Tp06dlJubq927dzsdDQAAIK5cW+rKy8v1j3/8Q/fcc4/OO+88devWTTNnzlTjxo01e/bsaveJRCIKh8NVBgAAgAlcW+o2bdqk/fv365RTToluS0pKUp8+ffTBBx9Uu08gEJDP54sOv98fr7gAAAD1yrWlri4KCgoUCoWio6yszOlIAAAAMeHaUtexY0clJydr5cqV0W379+/XW2+9pW7dulW7j2VZ8nq9VQYAAIAJXPsXJZo0aaKRI0dq7Nixatmypdq3b69Jkybpm2++0ZVXXul0PAAAgLhybamTpAkTJqiyslLDhg3Tnj171Lt3by1dulQtWrRwOhoAAEBcubrUpaSk6IEHHtADDzzgdBQAAABHufaaOgAAAPzA1St1sVJSlMtNEwAAwNVYqQMAADAApQ4AAMAAlDoAAAADcE2dpKzCpUqwUp2OAdRa6YSBTkcAABwhWKkDAAAwAKUOAADAAJQ6AAAAA1DqAAAADODqUrdkyRKdeuqpat68uVq1aqXf/OY32rRpk9OxAAAA4s7Vpa68vFxjxozR22+/rWXLlikhIUEXXHCBKisrnY4GAAAQV67+SpMLL7ywyuM5c+aoTZs2ev/995WVlXXQ/EgkokgkEn0cDofrPSMAAEA8uHql7uOPP9aQIUN0zDHHyOv1KjMzU5K0devWaucHAgH5fL7o8Pv9cUwLAABQf1xd6s4//3zt3r1bM2fO1OrVq7V69WpJ0r59+6qdX1BQoFAoFB1lZWXxjAsAAFBvXHv69csvv9TGjRs1c+ZMnXbaaZKk119//ZD7WJYly7LiEQ8AACCuXFvqWrRooVatWmnGjBnKyMjQ1q1bdeuttzodCwAAwBGuPf2akJCgRx55RGvXrlVWVpZGjx6te+65x+lYAAAAjnDtSp0kDRgwQO+//36VbbZtO5QGAADAOa5dqQMAAMAPXL1SFyslRbnyer1OxwAAAKgzVuoAAAAMQKkDAAAwAKUOAADAAFxTJymrcKkSrFSnY6CBKp0w0OkIAAADsFIHAABgANeWupycHOXn5zsdAwAA4Ijg2lIHAACAH1DqAAAADODqUldZWambb75ZLVu2VHp6uu68806nIwEAADjC1aVu/vz5atKkiVavXq1JkyZp/Pjxeumll2qcH4lEFA6HqwwAAAATuLrU9ejRQ4WFhercubOGDx+u3r17a9myZTXODwQC8vl80eH3++OYFgAAoP64vtT9WEZGhnbu3Fnj/IKCAoVCoegoKyur74gAAABx4eovH05KSqry2OPxqLKyssb5lmXJsqz6jgUAABB3rl6pAwAAwPcodQAAAAag1AEAABjAtdfUBYPBg7YtXrw47jkAAACOBKzUAQAAGMC1K3WxVFKUK6/X63QMAACAOmOlDgAAwACUOgAAAANQ6gAAAAzANXWSsgqXKsFKdToGDFY6YaDTEQAAhmOlDgAAwACUOgAAAANQ6gAAAAxAqQMAADCAa2+UyMnJUY8ePZSSkqJZs2YpOTlZf/rTn3TnnXc6HQ0AACDuXL1SN3/+fDVp0kSrV6/WpEmTNH78eL300ks1zo9EIgqHw1UGAACACVxd6nr06KHCwkJ17txZw4cPV+/evbVs2bIa5wcCAfl8vujw+/1xTAsAAFB/XF/qfiwjI0M7d+6scX5BQYFCoVB0lJWV1XdEAACAuHDtNXWSlJSUVOWxx+NRZWVljfMty5JlWfUdCwAAIO5cvVIHAACA71HqAAAADECpAwAAMIBrr6kLBoMHbVu8eHHccwAAABwJXFvqYqmkKFder9fpGAAAAHXG6VcAAAADUOoAAAAMQKkDAAAwANfUScoqXKoEK9XpGDiClU4Y6HQEAAAOiZU6AAAAA1DqAAAADECpAwAAMAClDgAAwABGlLonnnhC3bt3V+PGjdWqVSsNGDBA5eXlTscCAACIG9ff/bpt2zYNGTJEkyZN0gUXXKA9e/ZoxYoVsm3b6WgAAABxY0Sp++9//6vf//736tChgySpe/fu1c6NRCKKRCLRx+FwOC4ZAQAA6pvrT79mZ2frrLPOUvfu3XXRRRdp5syZ+uqrr6qdGwgE5PP5osPv98c5LQAAQP1wfalLTEzUSy+9pBdeeEHdunXT3/72N3Xp0kWbN28+aG5BQYFCoVB0lJWVOZAYAAAg9lxf6iTJ4/HolFNOUVFRkYqLi5WcnKynnnrqoHmWZcnr9VYZAAAAJnD9NXWrV6/WsmXLdM4556ht27ZavXq1du3apa5duzodDQAAIG5cX+q8Xq9ee+01TZ06VeFwWB06dNDkyZN13nnnOR0NAAAgblxf6rp27aolS5Y4HQMAAMBRRlxTBwAA0NC5fqUuFkqKcrlpAgAAuBordQAAAAag1AEAABiAUgcAAGAArqmTlFW4VAlWqtMxcIQpnTDQ6QgAABw2VuoAAAAMYFSpy8nJUX5+vtMxAAAA4s6o069PPvmkkpKSnI4BAAAQd0aVupYtWzodAQAAwBGcfgUAADCAUSt1PycSiSgSiUQfh8NhB9MAAADEjlErdT8nEAjI5/NFh9/vdzoSAABATDSoUldQUKBQKBQdZWVlTkcCAACIiQZ1+tWyLFmW5XQMAACAmGtQK3UAAACmotQBAAAYgFIHAABgAKOuqQsGg05HAAAAcAQrdQAAAAYwaqWurkqKcuX1ep2OAQAAUGes1AEAABiAUgcAAGAASh0AAIABuKZOUlbhUiVYqU7HgINKJwx0OgIAAL8IK3UAAAAGoNQBAAAYgFIHAABgAGNL3b59+5yOAAAAEDfG3CiRk5OjrKwsNWrUSAsXLlT37t316quvOh0LAAAgLowpdZI0f/58jRw5UitXrqz2+UgkokgkEn0cDofjFQ0AAKBeGVXqOnfurEmTJtX4fCAQUFFRURwTAQAAxIdR19SdeOKJh3y+oKBAoVAoOsrKyuKUDAAAoH4ZtVLXpEmTQz5vWZYsy4pTGgAAgPgxaqUOAACgoaLUAQAAGIBSBwAAYABjrqkLBoNORwAAAHCMMaXulygpypXX63U6BgAAQJ1x+hUAAMAAlDoAAAADUOoAAAAMwDV1krIKlyrBSnU6Bv5P6YSBTkcAAMB1WKkDAAAwgHGlLicnR/n5+U7HAAAAiCvjSh0AAEBDRKkDAAAwgKtLXXl5uYYPH66mTZsqIyNDkydPdjoSAACAI1xd6saOHavly5fr6aef1osvvqhgMKh169Y5HQsAACDuXPuVJnv37tXs2bO1cOFCnXXWWZKk+fPn66ijjqpxn0gkokgkEn0cDofrPScAAEA8uHalbtOmTdq3b5/69u0b3dayZUt16dKlxn0CgYB8Pl90+P3+eEQFAACod64tdXVRUFCgUCgUHWVlZU5HAgAAiAnXlrqOHTsqKSlJq1evjm776quv9NFHH9W4j2VZ8nq9VQYAAIAJXHtNXdOmTXXllVdq7NixatWqldq2bavbb79dCQmu7akAAAB15tpSJ0n33HOP9u7dq/PPP1/NmjXTjTfeqFAo5HQsAACAuPPYtm07HcIp4XD4+xsm8h9TgpXqdBz8n9IJA52OAABA3B3oJaFQqE6XiHGuEgAAwACuPv0aKyVFudw0AQAAXI2VOgAAAANQ6gAAAAxAqQMAADAA19RJyipcyt2vccKdrQAA1A9W6gAAAAxgVKnLyclRfn6+0zEAAADizqhSBwAA0FBR6gAAAAxAqQMAADBAg7r7NRKJKBKJRB+Hw2EH0wAAAMROg1qpCwQC8vl80eH3+52OBAAAEBMNqtQVFBQoFApFR1lZmdORAAAAYqJBnX61LEuWZTkdAwAAIOYa1EodAACAqSh1AAAABqDUAQAAGMCoa+qCwaDTEQAAABzBSh0AAIABjFqpq6uSolx5vV6nYwAAANQZK3UAAAAGoNQBAAAYgFIHAABgAK6pk5RVuFQJVqrTMYxROmGg0xEAAGhwWKkDAAAwAKUOAADAAPVW6qZNm6bMzEylpKSob9++WrNmTb3sHwgElJiYqHvuuScWsQEAAFypXkrdo48+qjFjxqiwsFDr1q1Tdna2cnNztXPnzpjvP2fOHN18882aM2dOrN8GAACAa9RLqbvvvvt09dVXa8SIEerWrZumT5+u1NRUzZkzR8FgUMnJyVqxYkV0/qRJk9S2bVvt2LHjZ/f/seXLl+vbb7/V+PHjFQ6H9cYbb9TH2wEAADjixbzU7du3T2vXrtWAAQN+eJGEBA0YMECrVq1STk6O8vPzNWzYMIVCIRUXF+uOO+7QrFmzlJaW9rP7/9js2bM1ZMgQJSUlaciQIZo9e/Yhs0UiEYXD4SoDAADABDEvdV988YUqKiqUlpZWZXtaWpq2b98uSfrLX/6iFi1a6JprrtHQoUOVl5en3/72t4e9vySFw2E98cQTGjp0qCRp6NCheuyxx7R3794aswUCAfl8vujw+/0xec8AAABOc+Tu1+TkZD388MNatGiRvvvuO02ZMqXWP+Nf//qXOnbsqOzsbElSz5491aFDBz366KM17lNQUKBQKBQdZWVldX4PAAAAR5KYl7rWrVsrMTExen3cATt27FB6enr08YHr33bv3q3du3fXev/Zs2frvffeU6NGjaLj/fffP+QNE5Zlyev1VhkAAAAmiHmpS05O1oknnqhly5ZFt1VWVmrZsmXq16+fJGnTpk0aPXq0Zs6cqb59+yovL0+VlZWHvf+GDRv09ttvKxgMav369dERDAa1atUqffjhh7F+WwAAAEe0evkzYWPGjFFeXp569+6tPn36aOrUqSovL9eIESNUUVGhoUOHKjc3VyNGjNC5556r7t27a/LkyRo7duzP7i99v0rXp08fnX766Qe99kknnaTZs2fzvXUAAKBBqZdSN3jwYO3atUvjxo3T9u3b1bNnTy1ZskRpaWkaP368tmzZoueee06SlJGRoRkzZmjIkCE655xzlJ2dfcj99+3bp4ULF+qWW26p9rUvvPBCTZ48WXfffbeSkpLq4+0BAAAccTy2bdtOh3BKOBz+/i7Y/MeUYKU6HccYpRMGOh0BAADXOdBLQqFQna7752+/AgAAGKBeTr+6TUlRLnfCAgAAV2OlDgAAwACUOgAAAANw+lVSVuFSbpSIIW6UAAAg/lipAwAAMAClDgAAwAAxL3XTpk1TZmamUlJS1LdvX61Zsyam+2dmZsrj8cjj8ahx48bKzMzUxRdfrFdeeSWWbwMAAMBVYlrqHn30UY0ZM0aFhYVat26dsrOzlZubq507d8Z0//Hjx2vbtm3auHGjFixYoObNm2vAgAH661//Gsu3AwAA4BoxLXX33Xefrr76ao0YMULdunXT9OnTlZqaqjlz5igYDCo5OVkrVqyIzp80aZLatm2rHTt2/Oz+P9asWTOlp6erffv2Ov300zVjxgzdcccdGjdunDZu3BjLtwQAAOAKMSt1+/bt09q1azVgwIAffnhCggYMGKBVq1YpJydH+fn5GjZsmEKhkIqLi3XHHXdo1qxZ0b/peqj9f86oUaNk27aefvrpWL0lAAAA14hZqfviiy9UUVGhtLS0KtvT0tK0fft2SdJf/vIXtWjRQtdcc42GDh2qvLw8/fa3vz3s/Q+lZcuWatu2rUpLS2ucE4lEFA6HqwwAAAATxPXu1+TkZD388MNatGiRvvvuO02ZMiWmP9+2bXk8nhqfDwQC8vl80eH3+2P6+gAAAE6JWalr3bq1EhMTo9fHHbBjxw6lp6dHH7/xxhuSpN27d2v37t213r8mX375pXbt2qWjjz66xjkFBQUKhULRUVZWdljvDQAA4EgXs1KXnJysE088UcuWLYtuq6ys1LJly9SvXz9J0qZNmzR69GjNnDlTffv2VV5eniorKw97/0O5//77lZCQoEGDBtU4x7Iseb3eKgMAAMAEMf0zYWPGjFFeXp569+6tPn36aOrUqSovL9eIESNUUVGhoUOHKjc3VyNGjNC5556r7t27a/LkyRo7duzP7v9je/bs0fbt27V//35t3rxZCxcu1KxZsxQIBNSpU6dYviUAAABXiGmpGzx4sHbt2qVx48Zp+/bt6tmzp5YsWaK0tDSNHz9eW7Zs0XPPPSdJysjI0IwZMzRkyBCdc845ys7OPuT+PzZu3DiNGzdOycnJSk9P1//8z/9o2bJlOuOMM2L5dgAAAFzDY9u27XQIp4TD4e9vmMh/TAlWqtNxjFE6YaDTEQAAcJ0DvSQUCtXpEjH+9isAAIABYnr61a1KinK5aQIAALgaK3UAAAAGoNQBAAAYgFIHAABgAK6pk5RVuJS7X/8Pd64CAOBOrNQBAAAYgFIHAABgAEodAACAASh1AAAABnBtqcvMzNTUqVOrbOvZs6fuvPNOR/IAAAA4qUHd/RqJRBSJRKKPw+Gwg2kAAABix7UrdXURCATk8/miw+/3Ox0JAAAgJhpUqSsoKFAoFIqOsrIypyMBAADEhGtPvyYkJMi27Srb9u/ff8h9LMuSZVn1GQsAAMARrl2pa9OmjbZt2xZ9HA6HtXnzZgcTAQAAOMe1pe7MM8/UQw89pBUrVmjDhg3Ky8tTYmKi07EAAAAc4drTrwUFBdq8ebN+85vfyOfz6a677mKlDgAANFiuLXVer1ePPPJIlW15eXkOpQEAAHCWa0+/AgAA4AeuXamLpZKiXHm9XqdjAAAA1BkrdQAAAAag1AEAABiAUgcAAGAArqmTlFW4VAlWqtMx6k3phIFORwAAAPWMlToAAAADUOoAAAAMQKkDAAAwAKUOAADAAK4pdTk5ObrhhhuUn5+vFi1aKC0tTTNnzlR5eblGjBihZs2aqVOnTnrhhRecjgoAABB3ril1kjR//ny1bt1aa9as0Q033KCRI0fqoosu0sknn6x169bpnHPO0bBhw/TNN99Uu38kElE4HK4yAAAATOCqUpedna3/9//+nzp37qyCggKlpKSodevWuvrqq9W5c2eNGzdOX375pd59991q9w8EAvL5fNHh9/vj/A4AAADqh6tKXY8ePaL/nJiYqFatWql79+7RbWlpaZKknTt3Vrt/QUGBQqFQdJSVldVvYAAAgDhx1ZcPJyUlVXns8XiqbPN4PJKkysrKave3LEuWZdVfQAAAAIe4aqUOAAAA1aPUAQAAGIBSBwAAYADXXFMXDAYP2lZaWnrQNtu26z8MAADAEYaVOgAAAAO4ZqWuPpUU5crr9TodAwAAoM5YqQMAADAApQ4AAMAAnH6VlFW4VAlWqtMxfpHSCQOdjgAAABzESh0AAIABjCl1tm3rmmuuUcuWLeXxeLR+/XqnIwEAAMSNMadflyxZonnz5ikYDOqYY45R69atnY4EAAAQN8aUuk2bNikjI0Mnn3yy01EAAADizohSd/nll2v+/PmSJI/How4dOlT71yYAAABMZUSpu//++9WxY0fNmDFDb731lhITE52OBAAAEFdGlDqfz6dmzZopMTFR6enpNc6LRCKKRCLRx+FwOB7xAAAA6p0xd78ejkAgIJ/PFx1+v9/pSAAAADHRoEpdQUGBQqFQdJSVlTkdCQAAICaMOP16uCzLkmVZTscAAACIuQa1UgcAAGAqSh0AAIABjCl1+fn5fDcdAABosIwpdQAAAA1Zg7pRoiYlRbnyer1OxwAAAKgzVuoAAAAMQKkDAAAwAKUOAADAAFxTJymrcKkSrFSnY/wipRMGOh0BAAA4iJU6AAAAA1DqAAAADECpAwAAMAClDgAAwACUOgAAAAM0qLtfI5GIIpFI9HE4HHYwDQAAQOw0qJW6QCAgn88XHX6/3+lIAAAAMdGgSl1BQYFCoVB0lJWVOR0JAAAgJhrU6VfLsmRZltMxAAAAYq5BrdQBAACYilIHAABgAKNK3bx58+TxeJyOAQAAEHdGlbrNmzerf//+TscAAACIO6NulHjhhRf04IMPOh0DAAAg7jy2bdtOh3BKOByWz+dTKBSS1+t1Og4AAGjAfmkvMer0KwAAQENFqQMAADAApQ4AAMAARt0oUVdZhUuVYKU6HaPOSicMdDoCAABwGCt1AAAABqDUAQAAGIBSBwAAYABKHQAAgAFcVeqee+45NW/eXBUVFZKk9evXy+Px6NZbb43OueqqqzR06FCnIgIAADjCVaXutNNO0549e1RcXCxJWr58uVq3bq1gMBids3z5cuXk5FS7fyQSUTgcrjIAAABM4KpS5/P51LNnz2iJCwaDGj16tIqLi7V371599tln+uSTT9S/f/9q9w8EAvL5fNHh9/vjmB4AAKD+uKrUSVL//v0VDAZl27ZWrFih3//+9+ratatef/11LV++XO3atVPnzp2r3begoEChUCg6ysrK4pweAACgfrjuy4dzcnI0Z84cvfPOO0pKStJxxx2nnJwcBYNBffXVVzWu0kmSZVmyLCuOaQEAAOLDdSt1B66rmzJlSrTAHSh1wWCwxuvpAAAATOa6UteiRQv16NFDDz/8cLTAnX766Vq3bp0++uijQ67UAQAAmMp1pU76/rq6ioqKaKlr2bKlunXrpvT0dHXp0sXZcAAAAA5wZambOnWqbNvWcccdF922fv16bdu2zcFUAAAAznFlqQMAAEBVrrv7tT6UFOXK6/U6HQMAAKDOWKkDAAAwAKUOAADAAJx+lZRVuFQJVqrTMeqsdMJApyMAAACHsVIHAABgAEodAACAASh1AAAABqDUAQAAGMC1pW7BggVq1aqVIpFIle2DBg3SsGHDHEoFAADgDNeWuosuukgVFRV65plnott27typ559/XldccYWDyQAAAOLPtaWucePGuvTSSzV37tzotoULF6p9+/bKycmpdp9IJKJwOFxlAAAAmMC1pU6Srr76ar344ov67LPPJEnz5s3T5ZdfLo/HU+38QCAgn88XHX6/P55xAQAA6o2rS12vXr2UnZ2tBQsWaO3atXrvvfd0+eWX1zi/oKBAoVAoOsrKyuIXFgAAoB65/i9KXHXVVZo6dao+++wzDRgw4JCrb5ZlybKsOKYDAACID1ev1EnSpZdeqv/85z+aOXMmN0gAAIAGy/Wlzufz6cILL1TTpk01aNAgp+MAAAA4wvWlTpI+++wzXXbZZZxaBQAADZarr6n76quvFAwGFQwG9fe//93pOAAAAI5xdanr1auXvvrqK02cOFFdunSp888pKcqV1+uNYTIAAID4cnWpKy0tdToCAADAEcGIa+oAAAAaOkodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGKCR0wGcZNu2JCkcDjucBAAANHQH+siBflJbDbrUffnll5Ikv9/vcBIAAIDv7dmzRz6fr9b7NehS17JlS0nS1q1b6/QvD/EXDofl9/tVVlYmr9frdBwcBo6Z+3DM3Idj5j7VHTPbtrVnzx61a9euTj+zQZe6hITvLyn0+Xz8EriM1+vlmLkMx8x9OGbuwzFzn58es1+yyMSNEgAAAAag1AEAABigQZc6y7JUWFgoy7KcjoLDxDFzH46Z+3DM3Idj5j71ccw8dl3vmwUAAMARo0Gv1AEAAJiCUgcAAGAASh0AAIABjC9106ZNU2ZmplJSUtS3b1+tWbPmkPMff/xxHXfccUpJSVH37t3173//O05JcUBtjtm8efPk8XiqjJSUlDimbdhee+01nX/++WrXrp08Ho8WL178s/sEg0GdcMIJsixLnTp10rx58+o9J35Q22MWDAYP+h3zeDzavn17fAJDgUBAJ510kpo1a6a2bdtq0KBB2rhx48/ux+eZc+pyzGLxeWZ0qXv00Uc1ZswYFRYWat26dcrOzlZubq527txZ7fw33nhDQ4YM0ZVXXqni4mINGjRIgwYNUklJSZyTN1y1PWbS91/cuG3btujYsmVLHBM3bOXl5crOzta0adMOa/7mzZs1cOBAnXHGGVq/fr3y8/N11VVXaenSpfWcFAfU9pgdsHHjxiq/Z23btq2nhPip5cuX67rrrtObb76pl156Sfv379c555yj8vLyGvfh88xZdTlmUgw+z2yD9enTx77uuuuijysqKux27drZgUCg2vkXX3yxPXDgwCrb+vbta//xj3+s15z4QW2P2dy5c22fzxendDgUSfZTTz11yDk333yzffzxx1fZNnjwYDs3N7cek6Emh3PMXn31VVuS/dVXX8UlE37ezp07bUn28uXLa5zD59mR5XCOWSw+z4xdqdu3b5/Wrl2rAQMGRLclJCRowIABWrVqVbX7rFq1qsp8ScrNza1xPmKrLsdMkvbu3asOHTrI7/frd7/7nd577714xEUd8DvmXj179lRGRobOPvtsrVy50uk4DVooFJL0w98vrw6/a0eWwzlm0i//PDO21H3xxReqqKhQWlpale1paWk1Xguyffv2Ws1HbNXlmHXp0kVz5szR008/rYULF6qyslInn3yy/vOf/8QjMmqppt+xcDisb7/91qFUOJSMjAxNnz5dixYt0qJFi+T3+5WTk6N169Y5Ha1BqqysVH5+vk455RRlZWXVOI/PsyPH4R6zWHyeNYpFYMAp/fr1U79+/aKPTz75ZHXt2lX//Oc/dddddzmYDDBDly5d1KVLl+jjk08+WZs2bdKUKVP00EMPOZisYbruuutUUlKi119/3ekoOEyHe8xi8Xlm7Epd69atlZiYqB07dlTZvmPHDqWnp1e7T3p6eq3mI7bqcsx+KikpSb169dInn3xSHxHxC9X0O+b1etW4cWOHUqG2+vTpw++YA66//no999xzevXVV3XUUUcdci6fZ0eG2hyzn6rL55mxpS45OVknnniili1bFt1WWVmpZcuWVWnCP9avX78q8yXppZdeqnE+Yqsux+ynKioqtGHDBmVkZNRXTPwC/I6ZYf369fyOxZFt27r++uv11FNP6ZVXXtHRRx/9s/vwu+asuhyzn6rT59kvus3iCPfII4/YlmXZ8+bNs99//337mmuusZs3b25v377dtm3bHjZsmH3rrbdG569cudJu1KiRfe+999offPCBXVhYaCclJdkbNmxw6i00OLU9ZkVFRfbSpUvtTZs22WvXrrUvueQSOyUlxX7vvfecegsNyp49e+zi4mK7uLjYlmTfd999dnFxsb1lyxbbtm371ltvtYcNGxad/+mnn9qpqan22LFj7Q8++MCeNm2anZiYaC9ZssSpt9Dg1PaYTZkyxV68eLH98ccf2xs2bLBHjRplJyQk2C+//LJTb6HBGTlypO3z+exgMGhv27YtOr755pvoHD7Pjix1OWax+DwzutTZtm3/7W9/s9u3b28nJyfbffr0sd98883oc/3797fz8vKqzH/sscfsY4891k5OTraPP/54+/nnn49zYtTmmOXn50fnpqWl2b/+9a/tdevWOZC6YTrwdRc/HQeOUV5ent2/f/+D9unZs6ednJxsH3PMMfbcuXPjnrshq+0xmzhxot2xY0c7JSXFbtmypZ2Tk2O/8sorzoRvoKo7XpKq/O7weXZkqcsxi8Xnmef/XhwAAAAuZuw1dQAAAA0JpQ4AAMAAlDoAAAADUOoAAAAMQKkDAAAwAKUOAADAAJQ6AAAAA1DqAAAADECpAwAAOAyvvfaazj//fLVr104ej0eLFy+u9c+wbVv33nuvjj32WFmWpV/96lf661//GpN8lDoAcFBpaak8Ho/Wr1/vdBQAP6O8vFzZ2dmaNm1anX/GqFGjNGvWLN1777368MMP9cwzz6hPnz4xydcoJj8FAADAcOedd57OO++8Gp+PRCK6/fbb9a9//Utff/21srKyNHHiROXk5EiSPvjgA/3jH/9QSUmJunTpIkk6+uijY5aPlToADVplZaUmTZqkTp06ybIstW/fPnoqZMOGDTrzzDPVuHFjtWrVStdcc4327t0b3TcnJ0f5+flVft6gQYN0+eWXRx9nZmbq7rvv1hVXXKFmzZqpffv2mjFjRvT5A/9B79WrlzweT/Q//gDc5/rrr9eqVav0yCOP6N1339VFF12kc889Vx9//LEk6dlnn9Uxxxyj5557TkcffbQyMzN11VVXaffu3TF5fUodgAatoKBAEyZM0B133KH3339f//u//6u0tDSVl5crNzdXLVq00FtvvaXHH39cL7/8sq6//vpav8bkyZPVu3dvFRcX69prr9XIkSO1ceNGSdKaNWskSS+//LK2bdumJ598MqbvD0B8bN26VXPnztXjjz+u0047TR07dtRNN92kU089VXPnzpUkffrpp9qyZYsef/xxLViwQPPmzdPatWv1hz/8ISYZOP0KoMHas2eP7r//fj344IPKy8uTJHXs2FGnnnqqZs6cqe+++04LFixQkyZNJEkPPvigzj//fE2cOFFpaWmH/Tq//vWvde2110qSbrnlFk2ZMkWvvvqqunTpojZt2kiSWrVqpfT09Bi/QwDxsmHDBlVUVOjYY4+tsj0SiahVq1aSvj8zEIlEtGDBgui82bNn68QTT9TGjRujp2TrilIHoMH64IMPFIlEdNZZZ1X7XHZ2drTQSdIpp5yiyspKbdy4sValrkePHtF/9ng8Sk9P186dO39ZeABHlL179yoxMVFr165VYmJileeaNm0qScrIyFCjRo2qFL+uXbtK+n6lj1IHAHXUuHHjX7R/QkKCbNuusm3//v0HzUtKSqry2OPxqLKy8he9NoAjS69evVRRUaGdO3fqtNNOq3bOKaecov/+97/atGmTOnbsKEn66KOPJEkdOnT4xRm4pg5Ag9W5c2c1btxYy5YtO+i5rl276p133lF5eXl028qVK5WQkBD9v+k2bdpo27Zt0ecrKipUUlJSqwzJycnRfQEc2fbu3av169dHv4Jo8+bNWr9+vbZu3apjjz1Wl112mYYPH64nn3xSmzdv1po1axQIBPT8889LkgYMGKATTjhBV1xxhYqLi7V27Vr98Y9/1Nlnn33Qadu6oNQBaLBSUlJ0yy236Oabb9aCBQu0adMmvfnmm5o9e7Yuu+wypaSkKC8vTyUlJXr11Vd1ww03aNiwYdFTr2eeeaaef/55Pf/88/rwww81cuRIff3117XK0LZtWzVu3FhLlizRjh07FAqF6uGdAoiFt99+W7169VKvXr0kSWPGjFGvXr00btw4SdLcuXM1fPhw3XjjjerSpYsGDRqkt956S+3bt5f0/er+s88+q9atW+v000/XwIED1bVrVz3yyCMxycfpVwAN2h133KFGjRpp3Lhx+vzzz5WRkaE//elPSk1N1dKlSzVq1CiddNJJSk1N1YUXXqj77rsvuu8VV1yhd955R8OHD1ejRo00evRonXHGGbV6/UaNGumBBx7Q+PHjNW7cOJ122mkKBoMxfpcAYiEnJ+egSy5+LCkpSUVFRSoqKqpxTrt27bRo0aL6iCePfah0AAAAcAVOvwIAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAb4/xVzwo7mVWfDAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], "source": [ "histogram_output = !python histogram_localized.py output\n", "histogram = np.loadtxt(histogram_output, delimiter=\",\")\n", @@ -581,33 +626,22 @@ "\n", "length = os.path.getsize(\"books__15m.txt\")\n", "print(f\"Characters in dataset: {length / 1e6:.1f} MB\")" - ], - "execution_count": null, - "outputs": [ - { - "output_type": "display_data", - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAnUAAAHWCAYAAAARl3+JAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAMUNJREFUeJzt3Xt4FOXB/vF7E5IJAXY5J6EuRAERDAQU4cUTUdFoqS3WKqJAxFOLhxJQ1OhPYrB1AUXQSks5g/jWE4qnCiq6iIigEJR4QEUCqXJSdBeiLjSZ3x++rEYSJHGzwzz5fq7ruS539pnsvc4V9/aZmY3Htm1bAAAAcLUEpwMAAADgl6PUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABGjkdwEmVlZX6/PPP1axZM3k8HqfjAACABsy2be3Zs0ft2rVTQkLt190adKn7/PPP5ff7nY4BAAAQVVZWpqOOOqrW+zXoUtesWTNJ3//L83q9DqcBAAANWTgclt/vj/aT2mrQpe7AKVev10upAwAAR4S6XhLGjRIAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABggEZOBzgSZBUuVYKV6nQMAABwBCqdMNDpCIeFlToAAAADUOoAAAAMUOtSt2vXLo0cOVLt27eXZVlKT09Xbm6uVq5cKUnKzMyUx+ORx+NRkyZNdMIJJ+jxxx+v8jO+/fZbtWzZUq1bt1YkEqn2dRYtWqScnBz5fD41bdpUPXr00Pjx47V7925J0rx586Kv8+ORkpJS27cEAADgerUudRdeeKGKi4s1f/58ffTRR3rmmWeUk5OjL7/8Mjpn/Pjx2rZtm4qLi3XSSSdp8ODBeuONN6LPL1q0SMcff7yOO+44LV68+KDXuP322zV48GCddNJJeuGFF1RSUqLJkyfrnXfe0UMPPRSd5/V6tW3btipjy5YttX1LAAAArlerGyW+/vprrVixQsFgUP3795ckdejQQX369Kkyr1mzZkpPT1d6erqmTZumhQsX6tlnn9XJJ58sSZo9e7aGDh0q27Y1e/ZsDR48OLrvmjVrdPfdd2vq1KkaNWpUdHtmZqbOPvtsff3119FtHo9H6enptX7TAAAApqnVSl3Tpk3VtGlTLV68uMbTpj/VqFEjJSUlad++fZKkTZs2adWqVbr44ot18cUXa8WKFVVW1x5++GE1bdpU1157bbU/r3nz5rWJDAAA0CDUqtQ1atRI8+bN0/z589W8eXOdcsopuu222/Tuu+9WO3/fvn0KBAIKhUI688wzJUlz5szReeedpxYtWqhly5bKzc3V3Llzo/t8/PHHOuaYY5SUlPSzeUKhULRoHhjnnXdejfMjkYjC4XCVAQAAYII6XVP3+eef65lnntG5556rYDCoE044QfPmzYvOueWWW9S0aVOlpqZq4sSJmjBhggYOHKiKigrNnz9fQ4cOjc4dOnSo5s2bp8rKSkmSbduHnaVZs2Zav359lTFr1qwa5wcCAfl8vujw+/21ffsAAABHJI9dmxZVg6uuukovvfSStmzZoszMTA0dOlSXX365mjZtqrS0NHk8HknSv//9bw0cOFCJiYlV9q+oqNCLL76os88+W6NGjdKcOXO0e/fuQ67WzZs3T/n5+VWusfs5kUikymnjcDgsv98vf/5jfPkwAACoVry+fDgcDsvn8ykUCsnr9dZ6/5h8T123bt1UXl4efdy6dWt16tRJ6enp0UInfX+DxCWXXHLQ6toll1yi2bNnS5IuvfRS7d27V3//+9+rfa3alLifsixLXq+3ygAAADBBre5+/fLLL3XRRRfpiiuuUI8ePdSsWTO9/fbbmjRpkn73u98dct9du3bp2Wef1TPPPKOsrKwqzw0fPlwXXHCBdu/erb59++rmm2/WjTfeqM8++0wXXHCB2rVrp08++UTTp0/XqaeeGr0r1rZtbd++/aDXatu2rRIS+F5lAADQcNSq1DVt2lR9+/bVlClTtGnTJu3fv19+v19XX321brvttkPuu2DBAjVp0kRnnXXWQc+dddZZaty4sRYuXKg///nPmjhxok488URNmzZN06dPV2VlpTp27Kg//OEPysvLi+4XDoeVkZFx0M/btm0bX3UCAAAalJhcU+dWB85dc00dAACoSYO6pg4AAADOqtXpV1OVFOVy0wQAAHA1VuoAAAAMQKkDAAAwAKUOAADAAFxTJymrcCl3vwKod/G6gw5Aw8RKHQAAgAEodQAAAAag1AEAABiAUgcAAGAAV5e6yspKBQIBHX300WrcuLGys7P1xBNPOB0L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zcFUAAAAznFlqQMAAEBVrrv7tT6UFOXK6/U6HQMAAKDOWKkDAAAwAKUOAADAAJx+lZRVuFQJVqrTMeqsdMJApyMAAACHsVIHAABgAEodAACAASh1AAAABqDUAQAAGMC1pW7BggVq1aqVIpFIle2DBg3SsGHDHEoFAADgDNeWuosuukgVFRV65plnott27typ559/XldccYWDyQAAAOLPtaWucePGuvTSSzV37tzotoULF6p9+/bKycmpdp9IJKJwOFxlAAAAmMC1pU6Srr76ar344ov67LPPJEnz5s3T5ZdfLo/HU+38QCAgn88XHX6/P55xAQAA6o2rS12vXr2UnZ2tBQsWaO3atXrvvfd0+eWX1zi/oKBAoVAoOsrKyuIXFgAAoB65/i9KXHXVVZo6dao+++wzDRgw4JCrb5ZlybKsOKYDAACID1ev1EnSpZdeqv/85z+aOXMmN0gAAIAGy/Wlzufz6cILL1TTpk01aNAgp+MAAAA4wvWlTpI+++wzXXbZZZxaBQAADZarr6n76quvFAwGFQwG9fe//93pOAAAAI5xdanr1auXvvrqK02cOFFdunSp888pKcqV1+uNYTIAAID4cnWpKy0tdToCAADAEcGIa+oAAAAaOkodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGKCR0wGcZNu2JCkcDjucBAAANHQH+siBflJbDbrUffnll5Ikv9/vcBIAAIDv7dmzRz6fr9b7NehS17JlS0nS1q1b6/QvD/EXDofl9/tVVlYmr9frdBwcBo6Z+3DM3Idj5j7VHTPbtrVnzx61a9euTj+zQZe6hITvLyn0+Xz8EriM1+vlmLkMx8x9OGbuwzFzn58es1+yyMSNEgAAAAag1AEAABigQZc6y7JUWFgoy7KcjoLDxDFzH46Z+3DM3Idj5j71ccw8dl3vmwUAAMARo0Gv1AEAAJiCUgcAAGAASh0AAIABjC9106ZNU2ZmplJSUtS3b1+tWbPmkPMff/xxHXfccUpJSVH37t3173//O05JcUBtjtm8efPk8XiqjJSUlDimbdhee+01nX/++WrXrp08Ho8WL178s/sEg0GdcMIJsixLnTp10rx58+o9J35Q22MWDAYP+h3zeDzavn17fAJDgUBAJ510kpo1a6a2bdtq0KBB2rhx48/ux+eZc+pyzGLxeWZ0qXv00Uc1ZswYFRYWat26dcrOzlZubq527txZ7fw33nhDQ4YM0ZVXXqni4mINGjRIgwYNUklJSZyTN1y1PWbS91/cuG3btujYsmVLHBM3bOXl5crOzta0adMOa/7mzZs1cOBAnXHGGVq/fr3y8/N11VVXaenSpfWcFAfU9pgdsHHjxiq/Z23btq2nhPip5cuX67rrrtObb76pl156Sfv379c555yj8vLyGvfh88xZdTlmUgw+z2yD9enTx77uuuuijysqKux27drZgUCg2vkXX3yxPXDgwCrb+vbta//xj3+s15z4QW2P2dy5c22fzxendDgUSfZTTz11yDk333yzffzxx1fZNnjwYDs3N7cek6Emh3PMXn31VVuS/dVXX8UlE37ezp07bUn28uXLa5zD59mR5XCOWSw+z4xdqdu3b5/Wrl2rAQMGRLclJCRowIABWrVqVbX7rFq1qsp8ScrNza1xPmKrLsdMkvbu3asOHTrI7/frd7/7nd577714xEUd8DvmXj179lRGRobOPvtsrVy50uk4DVooFJL0w98vrw6/a0eWwzlm0i//PDO21H3xxReqqKhQWlpale1paWk1Xguyffv2Ws1HbNXlmHXp0kVz5szR008/rYULF6qyslInn3yy/vOf/8QjMmqppt+xcDisb7/91qFUOJSMjAxNnz5dixYt0qJFi+T3+5WTk6N169Y5Ha1BqqysVH5+vk455RRlZWXVOI/PsyPH4R6zWHyeNYpFYMAp/fr1U79+/aKPTz75ZHXt2lX//Oc/dddddzmYDDBDly5d1KVLl+jjk08+WZs2bdKUKVP00EMPOZisYbruuutUUlKi119/3ekoOEyHe8xi8Xlm7Epd69atlZiYqB07dlTZvmPHDqWnp1e7T3p6eq3mI7bqcsx+KikpSb169dInn3xSHxHxC9X0O+b1etW4cWOHUqG2+vTpw++YA66//no999xzevXVV3XUUUcdci6fZ0eG2hyzn6rL55mxpS45OVknnniili1bFt1WWVmpZcuWVWnCP9avX78q8yXppZdeqnE+Yqsux+ynKioqtGHDBmVkZNRXTPwC/I6ZYf369fyOxZFt27r++uv11FNP6ZVXXtHRRx/9s/vwu+asuhyzn6rT59kvus3iCPfII4/YlmXZ8+bNs99//337mmuusZs3b25v377dtm3bHjZsmH3rrbdG569cudJu1KiRfe+999offPCBXVhYaCclJdkbNmxw6i00OLU9ZkVFRfbSpUvtTZs22WvXrrUvueQSOyUlxX7vvfecegsNyp49e+zi4mK7uLjYlmTfd999dnFxsb1lyxbbtm371ltvtYcNGxad/+mnn9qpqan22LFj7Q8++MCeNm2anZiYaC9ZssSpt9Dg1PaYTZkyxV68eLH98ccf2xs2bLBHjRplJyQk2C+//LJTb6HBGTlypO3z+exgMGhv27YtOr755pvoHD7Pjix1OWax+DwzutTZtm3/7W9/s9u3b28nJyfbffr0sd98883oc/3797fz8vKqzH/sscfsY4891k5OTraPP/54+/nnn49zYtTmmOXn50fnpqWl2b/+9a/tdevWOZC6YTrwdRc/HQeOUV5ent2/f/+D9unZs6ednJxsH3PMMfbcuXPjnrshq+0xmzhxot2xY0c7JSXFbtmypZ2Tk2O/8sorzoRvoKo7XpKq/O7weXZkqcsxi8Xnmef/XhwAAAAuZuw1dQAAAA0JpQ4AAMAAlDoAAAADUOoAAAAMQKkDAAAwAKUOAADAAJQ6AAAAA1DqAAAADECpAwAAOAyvvfaazj//fLVr104ej0eLFy+u9c+wbVv33nuvjj32WFmWpV/96lf661//GpN8lDoAcFBpaak8Ho/Wr1/vdBQAP6O8vFzZ2dmaNm1anX/GqFGjNGvWLN1777368MMP9cwzz6hPnz4xydcoJj8FAADAcOedd57OO++8Gp+PRCK6/fbb9a9//Utff/21srKyNHHiROXk5EiSPvjgA/3jH/9QSUmJunTpIkk6+uijY5aPlToADVplZaUmTZqkTp06ybIstW/fPnoqZMOGDTrzzDPVuHFjtWrVStdcc4327t0b3TcnJ0f5+flVft6gQYN0+eWXRx9nZmbq7rvv1hVXXKFmzZqpffv2mjFjRvT5A/9B79WrlzweT/Q//gDc5/rrr9eqVav0yCOP6N1339VFF12kc889Vx9//LEk6dlnn9Uxxxyj5557TkcffbQyMzN11VVXaffu3TF5fUodgAatoKBAEyZM0B133KH3339f//u//6u0tDSVl5crNzdXLVq00FtvvaXHH39cL7/8sq6//vpav8bkyZPVu3dvFRcX69prr9XIkSO1ceNGSdKaNWskSS+//LK2bdumJ598MqbvD0B8bN26VXPnztXjjz+u0047TR07dtRNN92kU089VXPnzpUkffrpp9qyZYsef/xxLViwQPPmzdPatWv1hz/8ISYZOP0KoMHas2eP7r//fj344IPKy8uTJHXs2FGnnnqqZs6cqe+++04LFixQkyZNJEkPPvigzj//fE2cOFFpaWmH/Tq//vWvde2110qSbrnlFk2ZMkWvvvqqunTpojZt2kiSWrVqpfT09Bi/QwDxsmHDBlVUVOjYY4+tsj0SiahVq1aSvj8zEIlEtGDBgui82bNn68QTT9TGjRujp2TrilIHoMH64IMPFIlEdNZZZ1X7XHZ2drTQSdIpp5yiyspKbdy4sValrkePHtF/9ng8Sk9P186dO39ZeABHlL179yoxMVFr165VYmJileeaNm0qScrIyFCjRo2qFL+uXbtK+n6lj1IHAHXUuHHjX7R/QkKCbNuusm3//v0HzUtKSqry2OPxqLKy8he9NoAjS69evVRRUaGdO3fqtNNOq3bOKaecov/+97/atGmTOnbsKEn66KOPJEkdOnT4xRm4pg5Ag9W5c2c1btxYy5YtO+i5rl276p133lF5eXl028qVK5WQkBD9v+k2bdpo27Zt0ecrKipUUlJSqwzJycnRfQEc2fbu3av169dHv4Jo8+bNWr9+vbZu3apjjz1Wl112mYYPH64nn3xSmzdv1po1axQIBPT8889LkgYMGKATTjhBV1xxhYqLi7V27Vr98Y9/1Nlnn33Qadu6oNQBaLBSUlJ0yy236Oabb9aCBQu0adMmvfnmm5o9e7Yuu+wypaSkKC8vTyUlJXr11Vd1ww03aNiwYdFTr2eeeaaef/55Pf/88/rwww81cuRIff3117XK0LZtWzVu3FhLlizRjh07FAqF6uGdAoiFt99+W7169VKvXr0kSWPGjFGvXr00btw4SdLcuXM1fPhw3XjjjerSpYsGDRqkt956S+3bt5f0/er+s88+q9atW+v000/XwIED1bVrVz3yyCMxycfpVwAN2h133KFGjRpp3Lhx+vzzz5WRkaE//elPSk1N1dKlSzVq1CiddNJJSk1N1YUXXqj77rsvuu8VV1yhd955R8OHD1ejRo00evRonXHGGbV6/UaNGumBBx7Q+PHjNW7cOJ122mkKBoMxfpcAYiEnJ+egSy5+LCkpSUVFRSoqKqpxTrt27bRo0aL6iCePfah0AAAAcAVOvwIAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAb4/xVzwo7mVWfDAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - } - } - ], - "id": "73f0c3cd-349b-490f-b6bd-7afbeb442fff" + ] }, { "cell_type": "markdown", + "id": "06857a59-20f3-4e39-aece-18cfbb514170", "metadata": { "id": "06857a59-20f3-4e39-aece-18cfbb514170" }, "source": [ "Now let's profile it:" - ], - "id": "06857a59-20f3-4e39-aece-18cfbb514170" + ] }, { "cell_type": "code", + "execution_count": null, + "id": "d637b6b1-fb0b-4807-b70b-c80227c0fd6f", "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -615,13 +649,9 @@ "id": "d637b6b1-fb0b-4807-b70b-c80227c0fd6f", "outputId": "e3790713-5a2e-4b51-dd2c-182b8bc27a5b" }, - "source": [ - "!ncu -f --kernel-name regex:histogram_localized --set full -o histogram_localized python histogram_localized.py\n", - "histogram_localized_csv = !ncu --import histogram_localized.ncu-rep --csv" - ], - "execution_count": null, "outputs": [ { + "name": "stdout", "output_type": "stream", "text": [ "==PROF== Connected to process 1492 (/usr/bin/python3.12)\n", @@ -631,10 +661,15 @@ ] } ], - "id": "d637b6b1-fb0b-4807-b70b-c80227c0fd6f" + "source": [ + "!ncu -f --kernel-name regex:histogram_localized --set full -o histogram_localized python histogram_localized.py\n", + "histogram_localized_csv = !ncu --import histogram_localized.ncu-rep --csv" + ] }, { "cell_type": "code", + "execution_count": 13, + "id": "114e8ff7-b6fb-42ad-abda-f6d53479c052", "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -678,22 +713,18 @@ "id": "114e8ff7-b6fb-42ad-abda-f6d53479c052", "outputId": "1936cb5f-6708-45bc-ee39-e01ddc857b89" }, - "source": [ - "nsightful.display_ncu_csv_in_notebook(histogram_localized_csv)" - ], - "execution_count": 13, "outputs": [ { - "output_type": "display_data", "data": { "application/javascript": "window[\"e7c6b518-9ed4-11f0-a7c3-0242ac1c000c\"] = google.colab.output.setIframeHeight(-1, true, {\"interactive\": true, \"maxHeight\": 99999});\n//# sourceURL=js_abfc25056c", "text/plain": [ "" ] - } + }, + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { "text/html": [ "\n", @@ -740,10 +771,11 @@ "text/plain": [ "" ] - } + }, + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "72eba4c5f11b49fb89df581268cc1ddc", @@ -753,10 +785,11 @@ "text/plain": [ "Dropdown(description='Kernel:', layout=Layout(width='400px'), options=('histogram_localized',), style=Descript…" ] - } + }, + "metadata": {}, + "output_type": "display_data" }, { - "output_type": "display_data", "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "29f54431514146cea6e08d352058ccfd", @@ -766,23 +799,31 @@ "text/plain": [ "Output()" ] - } + }, + "metadata": {}, + "output_type": "display_data" } ], - "id": "114e8ff7-b6fb-42ad-abda-f6d53479c052" + "source": [ + "nsightful.display_ncu_csv_in_notebook(histogram_localized_csv)" + ] }, { "cell_type": "markdown", + "id": "9aa1da9a-097a-4f7f-9e96-8891f5d7a2a2", "metadata": { "id": "9aa1da9a-097a-4f7f-9e96-8891f5d7a2a2" }, "source": [ + "## 6. Performance Comparison\n", + "\n", "Finally, let's benchmark our two approaches." - ], - "id": "9aa1da9a-097a-4f7f-9e96-8891f5d7a2a2" + ] }, { "cell_type": "code", + "execution_count": 14, + "id": "2a3f9ca4-b61b-4536-9896-7a41498cc986", "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -790,18 +831,9 @@ "id": "2a3f9ca4-b61b-4536-9896-7a41498cc986", "outputId": "08bb2e07-1dda-4cf1-c2d3-7125d575cd8f" }, - "source": [ - "histogram_global_duration = !python histogram_global.py\n", - "histogram_localized_duration = !python histogram_localized.py\n", - "speedup = float(histogram_global_duration[0].split()[0]) / float(histogram_localized_duration[0].split()[0])\n", - "\n", - "print(f\"histogram_global: {histogram_global_duration[0]}\")\n", - "print(f\"histogram_localized: {histogram_localized_duration[0]}\")\n", - "print(f\"histogram_localized speedup over histogram_global: {speedup:.2f}\")" - ], - "execution_count": 14, "outputs": [ { + "name": "stdout", "output_type": "stream", "text": [ "histogram_global: 0.00881 s ± 1.13% (mean ± relative stdev of 15 runs)\n", @@ -810,7 +842,15 @@ ] } ], - "id": "2a3f9ca4-b61b-4536-9896-7a41498cc986" + "source": [ + "histogram_global_duration = !python histogram_global.py\n", + "histogram_localized_duration = !python histogram_localized.py\n", + "speedup = float(histogram_global_duration[0].split()[0]) / float(histogram_localized_duration[0].split()[0])\n", + "\n", + "print(f\"histogram_global: {histogram_global_duration[0]}\")\n", + "print(f\"histogram_localized: {histogram_localized_duration[0]}\")\n", + "print(f\"histogram_localized speedup over histogram_global: {speedup:.2f}\")" + ] } ], "metadata": { @@ -823,18 +863,6 @@ "display_name": "Python 3 (ipykernel)", "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.13.7" - }, "widgets": { "application/vnd.jupyter.widget-state+json": { "05bad554df84498daf462d395e980f52": { @@ -3460,4 +3488,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} \ No newline at end of file +} From 0870f94accce0e71b1028391a35e9d9260d7e96d Mon Sep 17 00:00:00 2001 From: Bryce Adelstein Lelbach aka wash Date: Tue, 3 Mar 2026 13:27:50 -0500 Subject: [PATCH 07/18] Tutorials/Accelerated Python/Kernel Authoring: Add output mode to copy kernel scripts to print problem size and dtype. Made-with: Cursor --- .../kernels/40__kernel_authoring__copy.ipynb | 26 ++++++++++++------- ...40__kernel_authoring__copy__SOLUTION.ipynb | 24 ++++++++++------- 2 files changed, 31 insertions(+), 19 deletions(-) diff --git a/tutorials/accelerated-python/notebooks/kernels/40__kernel_authoring__copy.ipynb b/tutorials/accelerated-python/notebooks/kernels/40__kernel_authoring__copy.ipynb index 685d4ffc..b498898b 100644 --- a/tutorials/accelerated-python/notebooks/kernels/40__kernel_authoring__copy.ipynb +++ b/tutorials/accelerated-python/notebooks/kernels/40__kernel_authoring__copy.ipynb @@ -82,9 +82,9 @@ "import sys\n", "import os\n", "\n", - "threads_per_block = 256 if len(sys.argv) < 2 else int(sys.argv[1])\n", - "items_per_thread = 64 if len(sys.argv) < 3 else int(sys.argv[2])\n", - "total_items = 2**28 if len(sys.argv) < 4 else int(sys.argv[3])\n", + "threads_per_block = 256 if len(sys.argv) < 3 else int(sys.argv[2])\n", + "items_per_thread = 64 if len(sys.argv) < 4 else int(sys.argv[3])\n", + "total_items = 2**28 if len(sys.argv) < 5 else int(sys.argv[4])\n", "blocks = int(total_items / (threads_per_block * items_per_thread))\n", "\n", "src = cp.arange(total_items)\n", @@ -102,7 +102,10 @@ " if (check):\n", " cp.testing.assert_array_equal(src, dst)\n", "\n", - "if os.getenv(\"NV_COMPUTE_PROFILER_PERFWORKS_DIR\"): # Running under `ncu`.\n", + "if len(sys.argv) >= 2 and sys.argv[1] == \"output\":\n", + " launch(check=True)\n", + " print(f\"Problem size: {total_items * src.dtype.itemsize / 2**30:.2f} GB, dtype: {src.dtype}\")\n", + "elif os.getenv(\"NV_COMPUTE_PROFILER_PERFWORKS_DIR\"): # Running under `ncu`.\n", " launch(check=False) # `ncu` slows things down; so just launch once when running under it.\n", "else:\n", " launch(check=True)\n", @@ -125,7 +128,7 @@ "metadata": {}, "outputs": [], "source": [ - "!python copy_blocked.py" + "!python copy_blocked.py output" ] }, { @@ -214,9 +217,9 @@ "import sys\n", "import os\n", "\n", - "threads_per_block = 256 if len(sys.argv) < 2 else int(sys.argv[1])\n", - "items_per_thread = 64 if len(sys.argv) < 3 else int(sys.argv[2])\n", - "total_items = 2**28 if len(sys.argv) < 4 else int(sys.argv[3])\n", + "threads_per_block = 256 if len(sys.argv) < 3 else int(sys.argv[2])\n", + "items_per_thread = 64 if len(sys.argv) < 4 else int(sys.argv[3])\n", + "total_items = 2**28 if len(sys.argv) < 5 else int(sys.argv[4])\n", "blocks = int(total_items / (threads_per_block * items_per_thread))\n", "\n", "src = cp.arange(total_items)\n", @@ -232,7 +235,10 @@ " if (check):\n", " cp.testing.assert_array_equal(src, dst)\n", "\n", - "if os.getenv(\"NV_COMPUTE_PROFILER_PERFWORKS_DIR\"): # Running under `ncu`.\n", + "if len(sys.argv) >= 2 and sys.argv[1] == \"output\":\n", + " launch(check=True)\n", + " print(f\"Problem size: {total_items * src.dtype.itemsize / 2**30:.2f} GB, dtype: {src.dtype}\")\n", + "elif os.getenv(\"NV_COMPUTE_PROFILER_PERFWORKS_DIR\"): # Running under `ncu`.\n", " launch(check=False) # `ncu` slows things down; so just launch once when running under it.\n", "else:\n", " launch(check=True)\n", @@ -259,7 +265,7 @@ }, "outputs": [], "source": [ - "!python copy_optimized.py" + "!python copy_optimized.py output" ] }, { diff --git a/tutorials/accelerated-python/notebooks/kernels/solutions/40__kernel_authoring__copy__SOLUTION.ipynb b/tutorials/accelerated-python/notebooks/kernels/solutions/40__kernel_authoring__copy__SOLUTION.ipynb index 405021c3..4a622183 100644 --- a/tutorials/accelerated-python/notebooks/kernels/solutions/40__kernel_authoring__copy__SOLUTION.ipynb +++ b/tutorials/accelerated-python/notebooks/kernels/solutions/40__kernel_authoring__copy__SOLUTION.ipynb @@ -96,9 +96,9 @@ "import sys\n", "import os\n", "\n", - "threads_per_block = 256 if len(sys.argv) < 2 else int(sys.argv[1])\n", - "items_per_thread = 64 if len(sys.argv) < 3 else int(sys.argv[2])\n", - "total_items = 2**28 if len(sys.argv) < 4 else int(sys.argv[3])\n", + "threads_per_block = 256 if len(sys.argv) < 3 else int(sys.argv[2])\n", + "items_per_thread = 64 if len(sys.argv) < 4 else int(sys.argv[3])\n", + "total_items = 2**28 if len(sys.argv) < 5 else int(sys.argv[4])\n", "blocks = int(total_items / (threads_per_block * items_per_thread))\n", "\n", "src = cp.arange(total_items)\n", @@ -116,7 +116,10 @@ " if (check):\n", " cp.testing.assert_array_equal(src, dst)\n", "\n", - "if os.getenv(\"NV_COMPUTE_PROFILER_PERFWORKS_DIR\"): # Running under `ncu`.\n", + "if len(sys.argv) >= 2 and sys.argv[1] == \"output\":\n", + " launch(check=True)\n", + " print(f\"Problem size: {total_items * src.dtype.itemsize / 2**30:.2f} GB, dtype: {src.dtype}\")\n", + "elif os.getenv(\"NV_COMPUTE_PROFILER_PERFWORKS_DIR\"): # Running under `ncu`.\n", " launch(check=False) # `ncu` slows things down; so just launch once when running under it.\n", "else:\n", " launch(check=True)\n", @@ -139,7 +142,7 @@ "metadata": {}, "outputs": [], "source": [ - "!python copy_blocked.py" + "!python copy_blocked.py output" ] }, { @@ -391,9 +394,9 @@ "import sys\n", "import os\n", "\n", - "threads_per_block = 256 if len(sys.argv) < 2 else int(sys.argv[1])\n", - "items_per_thread = 64 if len(sys.argv) < 3 else int(sys.argv[2])\n", - "total_items = 2**28 if len(sys.argv) < 4 else int(sys.argv[3])\n", + "threads_per_block = 256 if len(sys.argv) < 3 else int(sys.argv[2])\n", + "items_per_thread = 64 if len(sys.argv) < 4 else int(sys.argv[3])\n", + "total_items = 2**28 if len(sys.argv) < 5 else int(sys.argv[4])\n", "blocks = int(total_items / (threads_per_block * items_per_thread))\n", "\n", "src = cp.arange(total_items)\n", @@ -416,7 +419,10 @@ " if (check):\n", " cp.testing.assert_array_equal(src, dst)\n", "\n", - "if os.getenv(\"NV_COMPUTE_PROFILER_PERFWORKS_DIR\"): # Running under `ncu`.\n", + "if len(sys.argv) >= 2 and sys.argv[1] == \"output\":\n", + " launch(check=True)\n", + " print(f\"Problem size: {total_items * src.dtype.itemsize / 2**30:.2f} GB, dtype: {src.dtype}\")\n", + "elif os.getenv(\"NV_COMPUTE_PROFILER_PERFWORKS_DIR\"): # Running under `ncu`.\n", " launch(check=False) # `ncu` slows things down; so just launch once when running under it.\n", "else:\n", " launch(check=True)\n", From 85d39554b49dac681fec785d599ddf4918131a52 Mon Sep 17 00:00:00 2001 From: Bryce Adelstein Lelbach aka wash Date: Tue, 3 Mar 2026 13:38:59 -0500 Subject: [PATCH 08/18] Tutorials/Accelerated Python/Memory Spaces: Re-add checkpoint I/O to power iteration. The savetxt checkpoint I/O was removed from the 05 memory spaces notebooks in 2f5c4fc. This I/O is needed to set up the narrative for Notebook 06 (Asynchrony), whose baseline is the synchronous device-to-host copy + file write pattern introduced here. Made-with: Cursor --- .../05__memory_spaces__power_iteration.ipynb | 1086 ++++++++-------- ...ry_spaces__power_iteration__SOLUTION.ipynb | 1103 +++++++++-------- 2 files changed, 1102 insertions(+), 1087 deletions(-) diff --git a/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb index 3b367a5b..2210a947 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb @@ -1,540 +1,550 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Memory Spaces & Power Iteration\n", - "\n", - "## Table of Contents\n", - "1. [Introduction to Memory Spaces](#1-introduction-to-memory-spaces)\n", - "2. [The CPU Baseline (NumPy)](#2-the-cpu-baseline-numpy)\n", - "3. [The GPU Port (CuPy)](#3-the-gpu-port-cupy)\n", - "4. [Optimizing Data Generation](#4-optimizing-data-generation)\n", - "5. [Verification and Benchmarking](#5-verification-and-benchmarking)\n", - "6. [Extra Credit](#extra-credit)\n", - "\n", - "---\n", - "\n", - "## 1. Introduction to Memory Spaces\n", - "\n", - "Before we implement algorithms on the GPU, we must understand the hardware architecture. A heterogeneous system (like the one you are using) consists of two distinct memory spaces:\n", - "\n", - "1. **Host Memory (CPU):** System RAM. Accessible by the CPU.\n", - "2. **Device Memory (GPU):** High-bandwidth memory (HBM) attached to the GPU. Accessible by the GPU.\n", - "\n", - "The CPU cannot directly calculate data stored on the GPU, and the GPU cannot directly calculate data stored in System RAM. To perform work on the GPU, you must explicitly manage data movement.\n", - "\n", - "* **Host $\\to$ Device:** Move data to the GPU to compute.\n", - " * Syntax: `x_device = cp.asarray(x_host)`\n", - "* **Device $\\to$ Host:** Move results back to the CPU to save to disk, plot with Matplotlib, or print.\n", - " * Syntax: `y_host = cp.asnumpy(y_device)`\n", - "\n", - "### Implicit Transfers and Synchronization\n", - "\n", - "It is crucial to understand when CuPy interacts with the CPU implicitly. These interactions can kill performance because they force the GPU to pause (synchronize) while data moves.\n", - "\n", - "CuPy silently transfers and synchronizes when you:\n", - "1. **Print** a GPU array (`print(gpu_array)`).\n", - "2. **Convert** to a Python scalar (`float(gpu_array)` or `.item()`).\n", - "3. **Evaluate** a GPU scalar in a boolean context (`if gpu_scalar > 0:`).\n", - "\n", - "### The Task\n", - "To understand the implications of these concepts, let's experiment with estimating the dominant eigenvalue of a matrix using the **Power Iteration** algorithm.\n", - "\n", - "Before we dive into the code, let's understand the math behind the algorithm we are implementing.\n", - "\n", - "**Power Iteration** is a classic iterative method used to find the dominant eigenvalue (the eigenvalue with the largest absolute value) and its corresponding eigenvector of a square matrix $A$.\n", - "\n", - "#### How It Works\n", - "\n", - "The core idea is simple: if you repeatedly multiply a vector by a matrix $A$, the vector will eventually converge towards the dominant eigenvector of $A$, regardless of the initial vector you started with (provided the initial vector has some component in the direction of the dominant eigenvector).\n", - "\n", - "#### The Mathematical Steps\n", - "\n", - "Given a square matrix $A$ and a random initial vector $x_0$, the algorithm proceeds as follows for each step $k$:\n", - "\n", - "**1. Matrix-Vector Multiplication:**\n", - "\n", - "We calculate the next approximation of the vector:\n", - "\n", - "$$y = A x_k$$\n", - "\n", - "**2. Eigenvalue Estimation (Rayleigh Quotient):**\n", - "\n", - "We estimate the eigenvalue $\\lambda$ using the current vector. This is essentially projecting $y$ onto $x$:\n", - "\n", - "$$\\lambda_k = \\frac{x_k^T y}{x_k^T x_k} = \\frac{x_k^T A x_k}{x_k^T x_k}$$\n", - "\n", - "**3. Residual Calculation (Error Check):**\n", - "\n", - "We check how close we are to the true definition of an eigenvector ($Ax = \\lambda x$) by calculating the \"residual\" (error):\n", - "\n", - "$$r = ||y - \\lambda_k x_k||$$\n", - "\n", - "If $r$ is close to 0, we have converged.\n", - "\n", - "**4. Normalization:**\n", - "\n", - "To prevent the numbers from exploding (overflow) or vanishing (underflow), we normalize the vector for the next iteration:\n", - "\n", - "$$x_{k+1} = \\frac{y}{||y||}$$\n", - "\n", - "We will start with a standard CPU implementation, port it to the GPU using CuPy, and analyze the performance impact of memory transfers.\n" - ] + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Memory Spaces & Power Iteration\n", + "\n", + "## Table of Contents\n", + "1. [Introduction to Memory Spaces](#1-introduction-to-memory-spaces)\n", + "2. [The CPU Baseline (NumPy)](#2-the-cpu-baseline-numpy)\n", + "3. [The GPU Port (CuPy)](#3-the-gpu-port-cupy)\n", + "4. [Optimizing Data Generation](#4-optimizing-data-generation)\n", + "5. [Verification and Benchmarking](#5-verification-and-benchmarking)\n", + "6. [Extra Credit](#extra-credit)\n", + "\n", + "---\n", + "\n", + "## 1. Introduction to Memory Spaces\n", + "\n", + "Before we implement algorithms on the GPU, we must understand the hardware architecture. A heterogeneous system (like the one you are using) consists of two distinct memory spaces:\n", + "\n", + "1. **Host Memory (CPU):** System RAM. Accessible by the CPU.\n", + "2. **Device Memory (GPU):** High-bandwidth memory (HBM) attached to the GPU. Accessible by the GPU.\n", + "\n", + "The CPU cannot directly calculate data stored on the GPU, and the GPU cannot directly calculate data stored in System RAM. To perform work on the GPU, you must explicitly manage data movement.\n", + "\n", + "* **Host $\\to$ Device:** Move data to the GPU to compute.\n", + " * Syntax: `x_device = cp.asarray(x_host)`\n", + "* **Device $\\to$ Host:** Move results back to the CPU to save to disk, plot with Matplotlib, or print.\n", + " * Syntax: `y_host = cp.asnumpy(y_device)`\n", + "\n", + "### Implicit Transfers and Synchronization\n", + "\n", + "It is crucial to understand when CuPy interacts with the CPU implicitly. These interactions can kill performance because they force the GPU to pause (synchronize) while data moves.\n", + "\n", + "CuPy silently transfers and synchronizes when you:\n", + "1. **Print** a GPU array (`print(gpu_array)`).\n", + "2. **Convert** to a Python scalar (`float(gpu_array)` or `.item()`).\n", + "3. **Evaluate** a GPU scalar in a boolean context (`if gpu_scalar > 0:`).\n", + "\n", + "### The Task\n", + "To understand the implications of these concepts, let's experiment with estimating the dominant eigenvalue of a matrix using the **Power Iteration** algorithm.\n", + "\n", + "Before we dive into the code, let's understand the math behind the algorithm we are implementing.\n", + "\n", + "**Power Iteration** is a classic iterative method used to find the dominant eigenvalue (the eigenvalue with the largest absolute value) and its corresponding eigenvector of a square matrix $A$.\n", + "\n", + "#### How It Works\n", + "\n", + "The core idea is simple: if you repeatedly multiply a vector by a matrix $A$, the vector will eventually converge towards the dominant eigenvector of $A$, regardless of the initial vector you started with (provided the initial vector has some component in the direction of the dominant eigenvector).\n", + "\n", + "#### The Mathematical Steps\n", + "\n", + "Given a square matrix $A$ and a random initial vector $x_0$, the algorithm proceeds as follows for each step $k$:\n", + "\n", + "**1. Matrix-Vector Multiplication:**\n", + "\n", + "We calculate the next approximation of the vector:\n", + "\n", + "$$y = A x_k$$\n", + "\n", + "**2. Eigenvalue Estimation (Rayleigh Quotient):**\n", + "\n", + "We estimate the eigenvalue $\\lambda$ using the current vector. This is essentially projecting $y$ onto $x$:\n", + "\n", + "$$\\lambda_k = \\frac{x_k^T y}{x_k^T x_k} = \\frac{x_k^T A x_k}{x_k^T x_k}$$\n", + "\n", + "**3. Residual Calculation (Error Check):**\n", + "\n", + "We check how close we are to the true definition of an eigenvector ($Ax = \\lambda x$) by calculating the \"residual\" (error):\n", + "\n", + "$$r = ||y - \\lambda_k x_k||$$\n", + "\n", + "If $r$ is close to 0, we have converged.\n", + "\n", + "**4. Normalization:**\n", + "\n", + "To prevent the numbers from exploding (overflow) or vanishing (underflow), we normalize the vector for the next iteration:\n", + "\n", + "$$x_{k+1} = \\frac{y}{||y||}$$\n", + "\n", + "We will start with a standard CPU implementation, port it to the GPU using CuPy, and analyze the performance impact of memory transfers.\n" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "import numpy as np\n", + "import cupy as cp\n", + "import time\n", + "from dataclasses import dataclass\n", + "\n", + "# Configuration for the algorithm\n", + "@dataclass\n", + "class PowerIterationConfig:\n", + " dim: int = 4096 # Matrix size (dim x dim)\n", + " dominance: float = 0.1 # How much larger the top eigenvalue is (controls convergence speed)\n", + " max_steps: int = 400 # Maximum iterations\n", + " check_frequency: int = 10 # Check for convergence every N steps\n", + " progress: bool = True # Print progress logs\n", + " residual_threshold: float = 1e-10 # Stop if error is below this" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. The CPU Baseline (NumPy)\n", + "\n", + "We generate a random dense matrix that is diagonalizable. This data is generated on the **Host (CPU)** and resides in **Host Memory**.\n" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "def generate_host(cfg=PowerIterationConfig()):\n", + " \"\"\"Generates a random diagonalizable matrix on the CPU.\"\"\"\n", + " np.random.seed(42)\n", + "\n", + " # Create eigenvalues: One large one (1.0), the rest smaller\n", + " weak_lam = np.random.random(cfg.dim - 1) * (1.0 - cfg.dominance)\n", + " lam = np.random.permutation(np.concatenate(([1.0], weak_lam)))\n", + "\n", + " # Construct matrix A = P * D * P^-1\n", + " P = np.random.random((cfg.dim, cfg.dim)) # Random invertible matrix\n", + " D = np.diag(np.random.permutation(lam)) # Diagonal matrix of eigenvalues\n", + " A = ((P @ D) @ np.linalg.inv(P)) # The final matrix\n", + " return A\n", + "\n", + "# Generate the data on Host\n", + "print(\"Generating Host Data...\")\n", + "A_host = generate_host()\n", + "print(f\"Host Matrix Shape: {A_host.shape}\")\n", + "print(f\"Data Type: {A_host.dtype}\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Implementing Power Iteration (CPU)\n", + "\n", + "As described above, the Power Iteration algorithm repeatedly multiplies a vector $x$ by matrix $A$ ($y = Ax$) and normalizes the result. We initialize this algorithm with a vector of 1s ($x_0$) as our initial guess." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "def estimate_host(A, cfg=PowerIterationConfig()):\n", + " \"\"\"\n", + " Performs power iteration using purely NumPy (CPU).\n", + " \"\"\"\n", + " # Initialize vector of ones on Host\n", + " x = np.ones(A.shape[0], dtype=np.float64)\n", + "\n", + " for i in range(0, cfg.max_steps, cfg.check_frequency):\n", + " # Matrix-Vector multiplication\n", + " y = A @ x\n", + " \n", + " # Rayleigh quotient: (x . y) / (x . x)\n", + " lam = (x @ y) / (x @ x)\n", + " \n", + " # Calculate residual (error)\n", + " res = np.linalg.norm(y - lam * x)\n", + " \n", + " # Normalize vector for next step\n", + " x = y / np.linalg.norm(y)\n", + "\n", + " if cfg.progress:\n", + " print(f\"Step {i}: residual = {res:.3e}\")\n", + "\n", + " np.savetxt(f\"host_{i}.txt\", x) # Save a checkpoint.\n", + "\n", + " # Convergence check\n", + " if res < cfg.residual_threshold:\n", + " break\n", + "\n", + " # Run intermediate steps without checking residual to save compute\n", + " for _ in range(cfg.check_frequency - 1):\n", + " y = A @ x\n", + " x = y / np.linalg.norm(y)\n", + "\n", + " return (x.T @ (A @ x)) / (x.T @ x)\n", + "\n", + "# Run CPU Baseline\n", + "print(\"\\nRunning CPU Estimate...\")\n", + "start_time = time.time()\n", + "lam_est_host = estimate_host(A_host)\n", + "end_time = time.time()\n", + "\n", + "print(f\"\\nEstimated Eigenvalue (CPU): {lam_est_host}\")\n", + "print(f\"Time taken: {end_time - start_time:.4f}s\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. The GPU Port (CuPy)\n", + "\n", + "### Exercise: Port the CPU Implementation to GPU\n", + "\n", + "Now it's your turn! Your task is to convert the `estimate_host` function to run on the GPU using CuPy.\n", + "\n", + "**Remember the rules of Memory Spaces:**\n", + "1. **Transfer:** Move `A_host` from CPU to GPU using `cp.asarray()`.\n", + "2. **Compute:** Perform math using `cp` functions on the GPU.\n", + "3. **Retrieve:** Move result back to CPU using `cp.asnumpy()` or `.item()` if we need to print it or use it in standard Python.\n", + "\n", + "**Hint:** CuPy tries to replicate the NumPy API. In many cases, you can simply change `np.` to `cp.`. However, CuPy operations *must* run on data present in Device Memory.\n", + "\n", + "**Fill in the `TODO` sections in the skeleton code below:**\n" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "def estimate_device_exercise(A, cfg=PowerIterationConfig()):\n", + " \"\"\"\n", + " TODO: Port the power iteration algorithm to the GPU using CuPy.\n", + " \n", + " Steps to complete:\n", + " 1. Transfer the input matrix A to the GPU (if it's a numpy array)\n", + " 2. Initialize the vector x on the GPU\n", + " 3. Replace np operations with cp operations\n", + " 4. Copy x from device to host and save a checkpoint\n", + " 5. Return the result as a Python scalar\n", + " \"\"\"\n", + " # ---------------------------------------------------------\n", + " # TODO 1: MEMORY TRANSFER (Host -> Device)\n", + " # Check if A is a numpy array. If so, move it to GPU using cp.asarray()\n", + " # Otherwise, assume it's already on the device.\n", + " # ---------------------------------------------------------\n", + " if isinstance(A, np.ndarray):\n", + " A_gpu = ... # TODO: Transfer to GPU\n", + " else:\n", + " A_gpu = A\n", + " \n", + " # ---------------------------------------------------------\n", + " # TODO 2: Initialize vector of ones ON THE GPU\n", + " # Hint: Use cp.ones() instead of np.ones()\n", + " # ---------------------------------------------------------\n", + " x = ... # TODO: Create vector of ones on GPU\n", + " \n", + " for i in range(0, cfg.max_steps, cfg.check_frequency):\n", + " # ---------------------------------------------------------\n", + " # TODO 3: Perform GPU computations\n", + " # Replace the operations below with CuPy equivalents\n", + " # ---------------------------------------------------------\n", + " \n", + " # Matrix-Vector multiplication (this works the same with CuPy!)\n", + " y = A_gpu @ x\n", + " \n", + " # Rayleigh quotient\n", + " lam = (x @ y) / (x @ x)\n", + " \n", + " # TODO: Calculate residual using cp.linalg.norm (not np.linalg.norm)\n", + " res = ...\n", + " \n", + " # TODO: Normalize x using cp.linalg.norm\n", + " x = ...\n", + " \n", + " if cfg.progress:\n", + " print(f\"Step {i}: residual = {res:.3e}\")\n", + "\n", + " # ---------------------------------------------------------\n", + " # TODO 4: CHECKPOINT (Device -> Host I/O)\n", + " # Copy x from device to host using cp.asnumpy(), then save\n", + " # it to a file using np.savetxt().\n", + " # ---------------------------------------------------------\n", + " ... # TODO: Save checkpoint\n", + "\n", + " if res < cfg.residual_threshold:\n", + " break\n", + " \n", + " for _ in range(cfg.check_frequency - 1):\n", + " y = A_gpu @ x\n", + " x = y / cp.linalg.norm(y)\n", + " \n", + " # ---------------------------------------------------------\n", + " # TODO 5: MEMORY TRANSFER (Device -> Host)\n", + " # Return the eigenvalue as a Python scalar using .item()\n", + " # ---------------------------------------------------------\n", + " result = (x.T @ (A_gpu @ x)) / (x.T @ x)\n", + " return ...\n", + "\n", + "# Uncomment to test your implementation:\n", + "# lam_test = estimate_device_exercise(A_host, PowerIterationConfig(max_steps=50))\n", + "# print(f\"Your result: {lam_test}\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Optimizing Data Generation\n", + "\n", + "In the previous step, we generated data on the CPU and copied it to the GPU. For large datasets, the transfer time (`Host -> Device`) can be a bottleneck. \n", + "\n", + "It is almost always faster to **generate** the data directly on the GPU if possible.\n", + "\n", + "### Exercise: Generate Data Directly on the GPU\n", + "\n", + "Your task is to convert the `generate_host` function to generate the matrix directly on the GPU using CuPy's random functions.\n", + "\n", + "**Hints:**\n", + "- Use `cp.random.seed()` instead of `np.random.seed()`\n", + "- Use `cp.random.random()` instead of `np.random.random()`\n", + "- Use `cp.random.permutation()` instead of `np.random.permutation()`\n", + "- Use `cp.concatenate()`, `cp.array()`, `cp.diag()`, and `cp.linalg.inv()`\n", + "\n", + "**Fill in the `TODO` sections in the skeleton code below:**\n" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "def generate_device_exercise(cfg=PowerIterationConfig()):\n", + " \"\"\"\n", + " TODO: Generate a random diagonalizable matrix directly on the GPU.\n", + " \n", + " This should mirror the generate_host function but use CuPy instead of NumPy.\n", + " The key benefit: no Host->Device transfer needed!\n", + " \"\"\"\n", + " # ---------------------------------------------------------\n", + " # TODO 1: Set the random seed on the GPU\n", + " # ---------------------------------------------------------\n", + " ... \n", + " \n", + " # ---------------------------------------------------------\n", + " # TODO 2: Create eigenvalues on the GPU\n", + " # Generate (dim-1) random values, scale them, then combine with 1.0\n", + " # ---------------------------------------------------------\n", + " # TODO: Generate weak eigenvalues using cp.random.random()\n", + " weak_lam = ...\n", + " \n", + " # TODO: Concatenate [1.0] with weak_lam using cp.concatenate and cp.array\n", + " # Then permute them using cp.random.permutation\n", + " lam = ...\n", + " \n", + " # ---------------------------------------------------------\n", + " # TODO 3: Construct the matrix A = P * D * P^-1 on the GPU\n", + " # ---------------------------------------------------------\n", + " # TODO: Generate random matrix P using cp.random.random()\n", + " P = ...\n", + " \n", + " # TODO: Create diagonal matrix D using cp.diag()\n", + " D = ...\n", + " \n", + " # TODO: Compute A = P @ D @ P^-1 using cp.linalg.inv()\n", + " A = ...\n", + " \n", + " return A\n", + "\n", + "# Uncomment to test your implementation:\n", + "# print(\"\\nGenerating Data directly on GPU...\")\n", + "# start_time = time.time()\n", + "# A_device = generate_device_exercise()\n", + "# end_time = time.time()\n", + "# print(f\"Generation time: {end_time - start_time:.4f}s\")\n", + "\n", + "# print(\"Running GPU Estimate (Input is Device Array)...\")\n", + "# start_time = time.time()\n", + "# # No transfer overhead here because A_device is already on GPU\n", + "# lam_est_device_gen = estimate_device_exercise(A_device)\n", + "# cp.cuda.Stream.null.synchronize()\n", + "# end_time = time.time()\n", + "# print(f\"Compute time: {end_time - start_time:.4f}s\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Think About It\n", + "\n", + "Both functions use `seed(42)`. Are `A_host` and `A_device` identical? Try comparing them:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "# NOTE: Run these after completing both exercises above\n", + "# print(\"NumPy:\", A_host[0, :3])\n", + "# print(\"CuPy:\", A_device[0, :3].get())" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "What does this reveal about `np.random` vs `cp.random`?" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Verification and Benchmarking\n", + "\n", + "Finally, let's verify our accuracy against a reference implementation (`numpy.linalg.eigvals`) and benchmark the speedup.\n", + "\n", + "**Note on CuPy Limitations:** You might wonder why we use `np.linalg.eigvals` on the CPU instead of a CuPy equivalent. The reason is that CuPy does not yet implement `eigvals`. While CuPy covers a large portion of the NumPy API, it does not support every function. Always check the [CuPy documentation](https://docs.cupy.dev/en/stable/reference/comparison.html) to verify which functions are available before assuming a direct NumPy-to-CuPy conversion will work.\n" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "print(\"Calculating Reference Eigenvalue (numpy.linalg)...\")\n", + "# Note: calculating all eigenvalues is computationally expensive\n", + "lam_ref = np.linalg.eigvals(A_host).real.max()\n", + "\n", + "print(f\"\\n--- Results ---\")\n", + "print(f\"Reference: {lam_ref}\")\n", + "print(f\"CPU Est: {lam_est_host}\")\n", + "\n", + "# Uncomment after completing exercises:\n", + "# print(f\"GPU Est: {lam_est_device_gen}\")\n", + "\n", + "# Assert correctness for CPU\n", + "np.testing.assert_allclose(lam_est_host, lam_ref, rtol=1e-4)\n", + "# Uncomment after completing exercises:\n", + "# np.testing.assert_allclose(lam_est_device_gen, lam_ref, rtol=1e-4)\n", + "print(\"\\nCPU accuracy verification passed!\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### A Note on Verification\n", + "\n", + "As mentioned before, `A_host` and `A_device` are **different matrices** (NumPy and CuPy use different RNG implementations). Yet the verification passes. Why?\n", + "\n", + "Both matrices are *constructed* with one eigenvalue explicitly set to **1.0**. The verification confirms that power iteration correctly finds this dominant eigenvalue—not that the matrices are identical.\n", + "\n", + "**Key takeaway:** If you need to verify GPU computation against CPU on the *exact same data*, generate on one device and transfer to the other." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Benchmarking with `cupyx.profiler.benchmark`\n", + "\n", + "We use CuPy's built-in benchmarking utility for accurate GPU timing. This handles warmup and synchronization automatically.\n" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "# Uncomment and run after completing both exercises above:\n", + "# from cupyx.profiler import benchmark\n", + "\n", + "# cfg = PowerIterationConfig(progress=False)\n", + "\n", + "# # 1. CPU\n", + "# print(\"Timing CPU...\")\n", + "# result_cpu = benchmark(estimate_host, args=(A_host, cfg), n_repeat=10)\n", + "# t_cpu_ms = result_cpu.cpu_times.mean() * 1000\n", + "\n", + "# # 2. GPU (with transfer overhead)\n", + "# print(\"Timing GPU (Host Input)...\")\n", + "# result_transfer = benchmark(estimate_device_exercise, args=(A_host, cfg), n_repeat=10)\n", + "# t_gpu_transfer_ms = result_transfer.gpu_times.mean() * 1000\n", + "\n", + "# # 3. GPU (pure device)\n", + "# print(\"Timing GPU (Device Input)...\")\n", + "# result_pure = benchmark(estimate_device_exercise, args=(A_device, cfg), n_repeat=10)\n", + "# t_gpu_pure_ms = result_pure.gpu_times.mean() * 1000\n", + "\n", + "# print(f\"\\n--- Average Compute Times ---\")\n", + "# print(f\"CPU: {t_cpu_ms:.2f} ms\")\n", + "# print(f\"GPU (with transfer): {t_gpu_transfer_ms:.2f} ms\")\n", + "# print(f\"GPU (pure): {t_gpu_pure_ms:.2f} ms\")\n", + "\n", + "# speedup = t_cpu_ms / t_gpu_pure_ms\n", + "# print(f\"\\nSpeedup: {speedup:.1f}x\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## Extra Credit\n", + "\n", + "**Explore the impact of changing the following parameters:**\n", + "\n", + "1. **Problem Size (`dim`):** How does the GPU speedup change as you increase or decrease the matrix dimensions? Try values like 1024, 2048, 4096, 8192.\n", + "\n", + "2. **Compute Workload (`max_steps` and `dominance`):** The `dominance` parameter controls how quickly the algorithm converges. A smaller dominance means eigenvalues are closer together, requiring more iterations. How does this affect the CPU vs GPU comparison?\n", + "\n", + "3. **Check Frequency (`check_frequency`):** This controls how often we check for convergence (and trigger implicit CPU synchronization via the print statement). What happens to GPU performance when you check every step (`check_frequency=1`) vs. less frequently (`check_frequency=50`)?\n", + "\n", + "**Experiment below:**" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "# Try different configurations here!\n", + "# Example:\n", + "# cfg_large = PowerIterationConfig(dim=8192, progress=False)\n", + "# cfg_slow_converge = PowerIterationConfig(dominance=0.01, progress=False)\n", + "# cfg_frequent_check = PowerIterationConfig(check_frequency=1, progress=True)\n", + "\n", + "# Your experiments:" + ], + "execution_count": null, + "outputs": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + } }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "import cupy as cp\n", - "import time\n", - "from dataclasses import dataclass\n", - "\n", - "# Configuration for the algorithm\n", - "@dataclass\n", - "class PowerIterationConfig:\n", - " dim: int = 4096 # Matrix size (dim x dim)\n", - " dominance: float = 0.1 # How much larger the top eigenvalue is (controls convergence speed)\n", - " max_steps: int = 400 # Maximum iterations\n", - " check_frequency: int = 10 # Check for convergence every N steps\n", - " progress: bool = True # Print progress logs\n", - " residual_threshold: float = 1e-10 # Stop if error is below this" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 2. The CPU Baseline (NumPy)\n", - "\n", - "We generate a random dense matrix that is diagonalizable. This data is generated on the **Host (CPU)** and resides in **Host Memory**.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def generate_host(cfg=PowerIterationConfig()):\n", - " \"\"\"Generates a random diagonalizable matrix on the CPU.\"\"\"\n", - " np.random.seed(42)\n", - "\n", - " # Create eigenvalues: One large one (1.0), the rest smaller\n", - " weak_lam = np.random.random(cfg.dim - 1) * (1.0 - cfg.dominance)\n", - " lam = np.random.permutation(np.concatenate(([1.0], weak_lam)))\n", - "\n", - " # Construct matrix A = P * D * P^-1\n", - " P = np.random.random((cfg.dim, cfg.dim)) # Random invertible matrix\n", - " D = np.diag(np.random.permutation(lam)) # Diagonal matrix of eigenvalues\n", - " A = ((P @ D) @ np.linalg.inv(P)) # The final matrix\n", - " return A\n", - "\n", - "# Generate the data on Host\n", - "print(\"Generating Host Data...\")\n", - "A_host = generate_host()\n", - "print(f\"Host Matrix Shape: {A_host.shape}\")\n", - "print(f\"Data Type: {A_host.dtype}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Implementing Power Iteration (CPU)\n", - "\n", - "As described above, the Power Iteration algorithm repeatedly multiplies a vector $x$ by matrix $A$ ($y = Ax$) and normalizes the result. We initialize this algorithm with a vector of 1s ($x_0$) as our initial guess." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def estimate_host(A, cfg=PowerIterationConfig()):\n", - " \"\"\"\n", - " Performs power iteration using purely NumPy (CPU).\n", - " \"\"\"\n", - " # Initialize vector of ones on Host\n", - " x = np.ones(A.shape[0], dtype=np.float64)\n", - "\n", - " for i in range(0, cfg.max_steps, cfg.check_frequency):\n", - " # Matrix-Vector multiplication\n", - " y = A @ x\n", - " \n", - " # Rayleigh quotient: (x . y) / (x . x)\n", - " lam = (x @ y) / (x @ x)\n", - " \n", - " # Calculate residual (error)\n", - " res = np.linalg.norm(y - lam * x)\n", - " \n", - " # Normalize vector for next step\n", - " x = y / np.linalg.norm(y)\n", - "\n", - " if cfg.progress:\n", - " print(f\"Step {i}: residual = {res:.3e}\")\n", - "\n", - " # Convergence check\n", - " if res < cfg.residual_threshold:\n", - " break\n", - "\n", - " # Run intermediate steps without checking residual to save compute\n", - " for _ in range(cfg.check_frequency - 1):\n", - " y = A @ x\n", - " x = y / np.linalg.norm(y)\n", - "\n", - " return (x.T @ (A @ x)) / (x.T @ x)\n", - "\n", - "# Run CPU Baseline\n", - "print(\"\\nRunning CPU Estimate...\")\n", - "start_time = time.time()\n", - "lam_est_host = estimate_host(A_host)\n", - "end_time = time.time()\n", - "\n", - "print(f\"\\nEstimated Eigenvalue (CPU): {lam_est_host}\")\n", - "print(f\"Time taken: {end_time - start_time:.4f}s\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 3. The GPU Port (CuPy)\n", - "\n", - "### Exercise: Port the CPU Implementation to GPU\n", - "\n", - "Now it's your turn! Your task is to convert the `estimate_host` function to run on the GPU using CuPy.\n", - "\n", - "**Remember the rules of Memory Spaces:**\n", - "1. **Transfer:** Move `A_host` from CPU to GPU using `cp.asarray()`.\n", - "2. **Compute:** Perform math using `cp` functions on the GPU.\n", - "3. **Retrieve:** Move result back to CPU using `cp.asnumpy()` or `.item()` if we need to print it or use it in standard Python.\n", - "\n", - "**Hint:** CuPy tries to replicate the NumPy API. In many cases, you can simply change `np.` to `cp.`. However, CuPy operations *must* run on data present in Device Memory.\n", - "\n", - "**Fill in the `TODO` sections in the skeleton code below:**\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def estimate_device_exercise(A, cfg=PowerIterationConfig()):\n", - " \"\"\"\n", - " TODO: Port the power iteration algorithm to the GPU using CuPy.\n", - " \n", - " Steps to complete:\n", - " 1. Transfer the input matrix A to the GPU (if it's a numpy array)\n", - " 2. Initialize the vector x on the GPU\n", - " 3. Replace np operations with cp operations\n", - " 4. Return the result as a Python scalar\n", - " \"\"\"\n", - " # ---------------------------------------------------------\n", - " # TODO 1: MEMORY TRANSFER (Host -> Device)\n", - " # Check if A is a numpy array. If so, move it to GPU using cp.asarray()\n", - " # Otherwise, assume it's already on the device.\n", - " # ---------------------------------------------------------\n", - " if isinstance(A, np.ndarray):\n", - " A_gpu = ... # TODO: Transfer to GPU\n", - " else:\n", - " A_gpu = A\n", - " \n", - " # ---------------------------------------------------------\n", - " # TODO 2: Initialize vector of ones ON THE GPU\n", - " # Hint: Use cp.ones() instead of np.ones()\n", - " # ---------------------------------------------------------\n", - " x = ... # TODO: Create vector of ones on GPU\n", - " \n", - " for i in range(0, cfg.max_steps, cfg.check_frequency):\n", - " # ---------------------------------------------------------\n", - " # TODO 3: Perform GPU computations\n", - " # Replace the operations below with CuPy equivalents\n", - " # ---------------------------------------------------------\n", - " \n", - " # Matrix-Vector multiplication (this works the same with CuPy!)\n", - " y = A_gpu @ x\n", - " \n", - " # Rayleigh quotient\n", - " lam = (x @ y) / (x @ x)\n", - " \n", - " # TODO: Calculate residual using cp.linalg.norm (not np.linalg.norm)\n", - " res = ...\n", - " \n", - " # TODO: Normalize x using cp.linalg.norm\n", - " x = ...\n", - " \n", - " if cfg.progress:\n", - " print(f\"Step {i}: residual = {res:.3e}\")\n", - " \n", - " if res < cfg.residual_threshold:\n", - " break\n", - " \n", - " for _ in range(cfg.check_frequency - 1):\n", - " y = A_gpu @ x\n", - " x = y / cp.linalg.norm(y)\n", - " \n", - " # ---------------------------------------------------------\n", - " # TODO 4: MEMORY TRANSFER (Device -> Host)\n", - " # Return the eigenvalue as a Python scalar using .item()\n", - " # ---------------------------------------------------------\n", - " result = (x.T @ (A_gpu @ x)) / (x.T @ x)\n", - " return ...\n", - "\n", - "# Uncomment to test your implementation:\n", - "# lam_test = estimate_device_exercise(A_host, PowerIterationConfig(max_steps=50))\n", - "# print(f\"Your result: {lam_test}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 4. Optimizing Data Generation\n", - "\n", - "In the previous step, we generated data on the CPU and copied it to the GPU. For large datasets, the transfer time (`Host -> Device`) can be a bottleneck. \n", - "\n", - "It is almost always faster to **generate** the data directly on the GPU if possible.\n", - "\n", - "### Exercise: Generate Data Directly on the GPU\n", - "\n", - "Your task is to convert the `generate_host` function to generate the matrix directly on the GPU using CuPy's random functions.\n", - "\n", - "**Hints:**\n", - "- Use `cp.random.seed()` instead of `np.random.seed()`\n", - "- Use `cp.random.random()` instead of `np.random.random()`\n", - "- Use `cp.random.permutation()` instead of `np.random.permutation()`\n", - "- Use `cp.concatenate()`, `cp.array()`, `cp.diag()`, and `cp.linalg.inv()`\n", - "\n", - "**Fill in the `TODO` sections in the skeleton code below:**\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def generate_device_exercise(cfg=PowerIterationConfig()):\n", - " \"\"\"\n", - " TODO: Generate a random diagonalizable matrix directly on the GPU.\n", - " \n", - " This should mirror the generate_host function but use CuPy instead of NumPy.\n", - " The key benefit: no Host->Device transfer needed!\n", - " \"\"\"\n", - " # ---------------------------------------------------------\n", - " # TODO 1: Set the random seed on the GPU\n", - " # ---------------------------------------------------------\n", - " ... \n", - " \n", - " # ---------------------------------------------------------\n", - " # TODO 2: Create eigenvalues on the GPU\n", - " # Generate (dim-1) random values, scale them, then combine with 1.0\n", - " # ---------------------------------------------------------\n", - " # TODO: Generate weak eigenvalues using cp.random.random()\n", - " weak_lam = ...\n", - " \n", - " # TODO: Concatenate [1.0] with weak_lam using cp.concatenate and cp.array\n", - " # Then permute them using cp.random.permutation\n", - " lam = ...\n", - " \n", - " # ---------------------------------------------------------\n", - " # TODO 3: Construct the matrix A = P * D * P^-1 on the GPU\n", - " # ---------------------------------------------------------\n", - " # TODO: Generate random matrix P using cp.random.random()\n", - " P = ...\n", - " \n", - " # TODO: Create diagonal matrix D using cp.diag()\n", - " D = ...\n", - " \n", - " # TODO: Compute A = P @ D @ P^-1 using cp.linalg.inv()\n", - " A = ...\n", - " \n", - " return A\n", - "\n", - "# Uncomment to test your implementation:\n", - "# print(\"\\nGenerating Data directly on GPU...\")\n", - "# start_time = time.time()\n", - "# A_device = generate_device_exercise()\n", - "# end_time = time.time()\n", - "# print(f\"Generation time: {end_time - start_time:.4f}s\")\n", - "\n", - "# print(\"Running GPU Estimate (Input is Device Array)...\")\n", - "# start_time = time.time()\n", - "# # No transfer overhead here because A_device is already on GPU\n", - "# lam_est_device_gen = estimate_device_exercise(A_device)\n", - "# cp.cuda.Stream.null.synchronize()\n", - "# end_time = time.time()\n", - "# print(f\"Compute time: {end_time - start_time:.4f}s\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Think About It\n", - "\n", - "Both functions use `seed(42)`. Are `A_host` and `A_device` identical? Try comparing them:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# NOTE: Run these after completing both exercises above\n", - "# print(\"NumPy:\", A_host[0, :3])\n", - "# print(\"CuPy:\", A_device[0, :3].get())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "What does this reveal about `np.random` vs `cp.random`?" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 5. Verification and Benchmarking\n", - "\n", - "Finally, let's verify our accuracy against a reference implementation (`numpy.linalg.eigvals`) and benchmark the speedup.\n", - "\n", - "**Note on CuPy Limitations:** You might wonder why we use `np.linalg.eigvals` on the CPU instead of a CuPy equivalent. The reason is that CuPy does not yet implement `eigvals`. While CuPy covers a large portion of the NumPy API, it does not support every function. Always check the [CuPy documentation](https://docs.cupy.dev/en/stable/reference/comparison.html) to verify which functions are available before assuming a direct NumPy-to-CuPy conversion will work.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"Calculating Reference Eigenvalue (numpy.linalg)...\")\n", - "# Note: calculating all eigenvalues is computationally expensive\n", - "lam_ref = np.linalg.eigvals(A_host).real.max()\n", - "\n", - "print(f\"\\n--- Results ---\")\n", - "print(f\"Reference: {lam_ref}\")\n", - "print(f\"CPU Est: {lam_est_host}\")\n", - "\n", - "# Uncomment after completing exercises:\n", - "# print(f\"GPU Est: {lam_est_device_gen}\")\n", - "\n", - "# Assert correctness for CPU\n", - "np.testing.assert_allclose(lam_est_host, lam_ref, rtol=1e-4)\n", - "# Uncomment after completing exercises:\n", - "# np.testing.assert_allclose(lam_est_device_gen, lam_ref, rtol=1e-4)\n", - "print(\"\\nCPU accuracy verification passed!\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### A Note on Verification\n", - "\n", - "As mentioned before, `A_host` and `A_device` are **different matrices** (NumPy and CuPy use different RNG implementations). Yet the verification passes. Why?\n", - "\n", - "Both matrices are *constructed* with one eigenvalue explicitly set to **1.0**. The verification confirms that power iteration correctly finds this dominant eigenvalue—not that the matrices are identical.\n", - "\n", - "**Key takeaway:** If you need to verify GPU computation against CPU on the *exact same data*, generate on one device and transfer to the other." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Benchmarking with `cupyx.profiler.benchmark`\n", - "\n", - "We use CuPy's built-in benchmarking utility for accurate GPU timing. This handles warmup and synchronization automatically.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Uncomment and run after completing both exercises above:\n", - "# from cupyx.profiler import benchmark\n", - "\n", - "# cfg = PowerIterationConfig(progress=False)\n", - "\n", - "# # 1. CPU\n", - "# print(\"Timing CPU...\")\n", - "# result_cpu = benchmark(estimate_host, args=(A_host, cfg), n_repeat=10)\n", - "# t_cpu_ms = result_cpu.cpu_times.mean() * 1000\n", - "\n", - "# # 2. GPU (with transfer overhead)\n", - "# print(\"Timing GPU (Host Input)...\")\n", - "# result_transfer = benchmark(estimate_device_exercise, args=(A_host, cfg), n_repeat=10)\n", - "# t_gpu_transfer_ms = result_transfer.gpu_times.mean() * 1000\n", - "\n", - "# # 3. GPU (pure device)\n", - "# print(\"Timing GPU (Device Input)...\")\n", - "# result_pure = benchmark(estimate_device_exercise, args=(A_device, cfg), n_repeat=10)\n", - "# t_gpu_pure_ms = result_pure.gpu_times.mean() * 1000\n", - "\n", - "# print(f\"\\n--- Average Compute Times ---\")\n", - "# print(f\"CPU: {t_cpu_ms:.2f} ms\")\n", - "# print(f\"GPU (with transfer): {t_gpu_transfer_ms:.2f} ms\")\n", - "# print(f\"GPU (pure): {t_gpu_pure_ms:.2f} ms\")\n", - "\n", - "# speedup = t_cpu_ms / t_gpu_pure_ms\n", - "# print(f\"\\nSpeedup: {speedup:.1f}x\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---\n", - "\n", - "## Extra Credit\n", - "\n", - "**Explore the impact of changing the following parameters:**\n", - "\n", - "1. **Problem Size (`dim`):** How does the GPU speedup change as you increase or decrease the matrix dimensions? Try values like 1024, 2048, 4096, 8192.\n", - "\n", - "2. **Compute Workload (`max_steps` and `dominance`):** The `dominance` parameter controls how quickly the algorithm converges. A smaller dominance means eigenvalues are closer together, requiring more iterations. How does this affect the CPU vs GPU comparison?\n", - "\n", - "3. **Check Frequency (`check_frequency`):** This controls how often we check for convergence (and trigger implicit CPU synchronization via the print statement). What happens to GPU performance when you check every step (`check_frequency=1`) vs. less frequently (`check_frequency=50`)?\n", - "\n", - "**Experiment below:**" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Try different configurations here!\n", - "# Example:\n", - "# cfg_large = PowerIterationConfig(dim=8192, progress=False)\n", - "# cfg_slow_converge = PowerIterationConfig(dominance=0.01, progress=False)\n", - "# cfg_frequent_check = PowerIterationConfig(check_frequency=1, progress=True)\n", - "\n", - "# Your experiments:" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file diff --git a/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb index 9a14d16a..ef862364 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb @@ -1,551 +1,556 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Memory Spaces & Power Iteration\n", - "\n", - "## Table of Contents\n", - "1. [Introduction to Memory Spaces](#1-introduction-to-memory-spaces)\n", - "2. [The CPU Baseline (NumPy)](#2-the-cpu-baseline-numpy)\n", - "3. [The GPU Port (CuPy)](#3-the-gpu-port-cupy)\n", - "4. [Optimizing Data Generation](#4-optimizing-data-generation)\n", - "5. [Verification and Benchmarking](#5-verification-and-benchmarking)\n", - "6. [Extra Credit](#extra-credit)\n", - "\n", - "---\n", - "\n", - "## 1. Introduction to Memory Spaces\n", - "\n", - "Before we implement algorithms on the GPU, we must understand the hardware architecture. A heterogeneous system (like the one you are using) consists of two distinct memory spaces:\n", - "\n", - "1. **Host Memory (CPU):** System RAM. Accessible by the CPU.\n", - "2. **Device Memory (GPU):** High-bandwidth memory (HBM) attached to the GPU. Accessible by the GPU.\n", - "\n", - "The CPU cannot directly calculate data stored on the GPU, and the GPU cannot directly calculate data stored in System RAM. To perform work on the GPU, you must explicitly manage data movement.\n", - "\n", - "* **Host $\\to$ Device:** Move data to the GPU to compute.\n", - " * Syntax: `x_device = cp.asarray(x_host)`\n", - "* **Device $\\to$ Host:** Move results back to the CPU to save to disk, plot with Matplotlib, or print.\n", - " * Syntax: `y_host = cp.asnumpy(y_device)`\n", - "\n", - "### Implicit Transfers and Synchronization\n", - "\n", - "It is crucial to understand when CuPy interacts with the CPU implicitly. These interactions can kill performance because they force the GPU to pause (synchronize) while data moves.\n", - "\n", - "CuPy silently transfers and synchronizes when you:\n", - "1. **Print** a GPU array (`print(gpu_array)`).\n", - "2. **Convert** to a Python scalar (`float(gpu_array)` or `.item()`).\n", - "3. **Evaluate** a GPU scalar in a boolean context (`if gpu_scalar > 0:`).\n", - "\n", - "### The Task\n", - "To understand the implications of these concepts, let's experiment with estimating the dominant eigenvalue of a matrix using the **Power Iteration** algorithm.\n", - "\n", - "Before we dive into the code, let's understand the math behind the algorithm we are implementing.\n", - "\n", - "**Power Iteration** is a classic iterative method used to find the dominant eigenvalue (the eigenvalue with the largest absolute value) and its corresponding eigenvector of a square matrix $A$.\n", - "\n", - "#### How It Works\n", - "\n", - "The core idea is simple: if you repeatedly multiply a vector by a matrix $A$, the vector will eventually converge towards the dominant eigenvector of $A$, regardless of the initial vector you started with (provided the initial vector has some component in the direction of the dominant eigenvector).\n", - "\n", - "#### The Mathematical Steps\n", - "\n", - "Given a square matrix $A$ and a random initial vector $x_0$, the algorithm proceeds as follows for each step $k$:\n", - "\n", - "**1. Matrix-Vector Multiplication:**\n", - "\n", - "We calculate the next approximation of the vector:\n", - "\n", - "$$y = A x_k$$\n", - "\n", - "**2. Eigenvalue Estimation (Rayleigh Quotient):**\n", - "\n", - "We estimate the eigenvalue $\\lambda$ using the current vector. This is essentially projecting $y$ onto $x$:\n", - "\n", - "$$\\lambda_k = \\frac{x_k^T y}{x_k^T x_k} = \\frac{x_k^T A x_k}{x_k^T x_k}$$\n", - "\n", - "**3. Residual Calculation (Error Check):**\n", - "\n", - "We check how close we are to the true definition of an eigenvector ($Ax = \\lambda x$) by calculating the \"residual\" (error):\n", - "\n", - "$$r = ||y - \\lambda_k x_k||$$\n", - "\n", - "If $r$ is close to 0, we have converged.\n", - "\n", - "**4. Normalization:**\n", - "\n", - "To prevent the numbers from exploding (overflow) or vanishing (underflow), we normalize the vector for the next iteration:\n", - "\n", - "$$x_{k+1} = \\frac{y}{||y||}$$\n", - "\n", - "We will start with a standard CPU implementation, port it to the GPU using CuPy, and analyze the performance impact of memory transfers.\n" - ] + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Memory Spaces & Power Iteration\n", + "\n", + "## Table of Contents\n", + "1. [Introduction to Memory Spaces](#1-introduction-to-memory-spaces)\n", + "2. [The CPU Baseline (NumPy)](#2-the-cpu-baseline-numpy)\n", + "3. [The GPU Port (CuPy)](#3-the-gpu-port-cupy)\n", + "4. [Optimizing Data Generation](#4-optimizing-data-generation)\n", + "5. [Verification and Benchmarking](#5-verification-and-benchmarking)\n", + "6. [Extra Credit](#extra-credit)\n", + "\n", + "---\n", + "\n", + "## 1. Introduction to Memory Spaces\n", + "\n", + "Before we implement algorithms on the GPU, we must understand the hardware architecture. A heterogeneous system (like the one you are using) consists of two distinct memory spaces:\n", + "\n", + "1. **Host Memory (CPU):** System RAM. Accessible by the CPU.\n", + "2. **Device Memory (GPU):** High-bandwidth memory (HBM) attached to the GPU. Accessible by the GPU.\n", + "\n", + "The CPU cannot directly calculate data stored on the GPU, and the GPU cannot directly calculate data stored in System RAM. To perform work on the GPU, you must explicitly manage data movement.\n", + "\n", + "* **Host $\\to$ Device:** Move data to the GPU to compute.\n", + " * Syntax: `x_device = cp.asarray(x_host)`\n", + "* **Device $\\to$ Host:** Move results back to the CPU to save to disk, plot with Matplotlib, or print.\n", + " * Syntax: `y_host = cp.asnumpy(y_device)`\n", + "\n", + "### Implicit Transfers and Synchronization\n", + "\n", + "It is crucial to understand when CuPy interacts with the CPU implicitly. These interactions can kill performance because they force the GPU to pause (synchronize) while data moves.\n", + "\n", + "CuPy silently transfers and synchronizes when you:\n", + "1. **Print** a GPU array (`print(gpu_array)`).\n", + "2. **Convert** to a Python scalar (`float(gpu_array)` or `.item()`).\n", + "3. **Evaluate** a GPU scalar in a boolean context (`if gpu_scalar > 0:`).\n", + "\n", + "### The Task\n", + "To understand the implications of these concepts, let's experiment with estimating the dominant eigenvalue of a matrix using the **Power Iteration** algorithm.\n", + "\n", + "Before we dive into the code, let's understand the math behind the algorithm we are implementing.\n", + "\n", + "**Power Iteration** is a classic iterative method used to find the dominant eigenvalue (the eigenvalue with the largest absolute value) and its corresponding eigenvector of a square matrix $A$.\n", + "\n", + "#### How It Works\n", + "\n", + "The core idea is simple: if you repeatedly multiply a vector by a matrix $A$, the vector will eventually converge towards the dominant eigenvector of $A$, regardless of the initial vector you started with (provided the initial vector has some component in the direction of the dominant eigenvector).\n", + "\n", + "#### The Mathematical Steps\n", + "\n", + "Given a square matrix $A$ and a random initial vector $x_0$, the algorithm proceeds as follows for each step $k$:\n", + "\n", + "**1. Matrix-Vector Multiplication:**\n", + "\n", + "We calculate the next approximation of the vector:\n", + "\n", + "$$y = A x_k$$\n", + "\n", + "**2. Eigenvalue Estimation (Rayleigh Quotient):**\n", + "\n", + "We estimate the eigenvalue $\\lambda$ using the current vector. This is essentially projecting $y$ onto $x$:\n", + "\n", + "$$\\lambda_k = \\frac{x_k^T y}{x_k^T x_k} = \\frac{x_k^T A x_k}{x_k^T x_k}$$\n", + "\n", + "**3. Residual Calculation (Error Check):**\n", + "\n", + "We check how close we are to the true definition of an eigenvector ($Ax = \\lambda x$) by calculating the \"residual\" (error):\n", + "\n", + "$$r = ||y - \\lambda_k x_k||$$\n", + "\n", + "If $r$ is close to 0, we have converged.\n", + "\n", + "**4. Normalization:**\n", + "\n", + "To prevent the numbers from exploding (overflow) or vanishing (underflow), we normalize the vector for the next iteration:\n", + "\n", + "$$x_{k+1} = \\frac{y}{||y||}$$\n", + "\n", + "We will start with a standard CPU implementation, port it to the GPU using CuPy, and analyze the performance impact of memory transfers.\n" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "import numpy as np\n", + "import cupy as cp\n", + "import time\n", + "from dataclasses import dataclass\n", + "\n", + "# Configuration for the algorithm\n", + "@dataclass\n", + "class PowerIterationConfig:\n", + " dim: int = 4096 # Matrix size (dim x dim)\n", + " dominance: float = 0.1 # How much larger the top eigenvalue is (controls convergence speed)\n", + " max_steps: int = 400 # Maximum iterations\n", + " check_frequency: int = 10 # Check for convergence every N steps\n", + " progress: bool = True # Print progress logs\n", + " residual_threshold: float = 1e-10 # Stop if error is below this" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. The CPU Baseline (NumPy)\n", + "\n", + "We generate a random dense matrix that is diagonalizable. This data is generated on the **Host (CPU)** and resides in **Host Memory**.\n" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "def generate_host(cfg=PowerIterationConfig()):\n", + " \"\"\"Generates a random diagonalizable matrix on the CPU.\"\"\"\n", + " np.random.seed(42)\n", + "\n", + " # Create eigenvalues: One large one (1.0), the rest smaller\n", + " weak_lam = np.random.random(cfg.dim - 1) * (1.0 - cfg.dominance)\n", + " lam = np.random.permutation(np.concatenate(([1.0], weak_lam)))\n", + "\n", + " # Construct matrix A = P * D * P^-1\n", + " P = np.random.random((cfg.dim, cfg.dim)) # Random invertible matrix\n", + " D = np.diag(np.random.permutation(lam)) # Diagonal matrix of eigenvalues\n", + " A = ((P @ D) @ np.linalg.inv(P)) # The final matrix\n", + " return A\n", + "\n", + "# Generate the data on Host\n", + "print(\"Generating Host Data...\")\n", + "A_host = generate_host()\n", + "print(f\"Host Matrix Shape: {A_host.shape}\")\n", + "print(f\"Data Type: {A_host.dtype}\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Implementing Power Iteration (CPU)\n", + "\n", + "As described above, the Power Iteration algorithm repeatedly multiplies a vector $x$ by matrix $A$ ($y = Ax$) and normalizes the result. We initialize this algorithm with a vector of 1s ($x_0$) as our initial guess." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "def estimate_host(A, cfg=PowerIterationConfig()):\n", + " \"\"\"\n", + " Performs power iteration using purely NumPy (CPU).\n", + " \"\"\"\n", + " # Initialize vector of ones on Host\n", + " x = np.ones(A.shape[0], dtype=np.float64)\n", + "\n", + " for i in range(0, cfg.max_steps, cfg.check_frequency):\n", + " # Matrix-Vector multiplication\n", + " y = A @ x\n", + " \n", + " # Rayleigh quotient: (x . y) / (x . x)\n", + " lam = (x @ y) / (x @ x)\n", + " \n", + " # Calculate residual (error)\n", + " res = np.linalg.norm(y - lam * x)\n", + " \n", + " # Normalize vector for next step\n", + " x = y / np.linalg.norm(y)\n", + "\n", + " if cfg.progress:\n", + " print(f\"Step {i}: residual = {res:.3e}\")\n", + "\n", + " np.savetxt(f\"host_{i}.txt\", x) # Save a checkpoint.\n", + "\n", + " # Convergence check\n", + " if res < cfg.residual_threshold:\n", + " break\n", + "\n", + " # Run intermediate steps without checking residual to save compute\n", + " for _ in range(cfg.check_frequency - 1):\n", + " y = A @ x\n", + " x = y / np.linalg.norm(y)\n", + "\n", + " return (x.T @ (A @ x)) / (x.T @ x)\n", + "\n", + "# Run CPU Baseline\n", + "print(\"\\nRunning CPU Estimate...\")\n", + "start_time = time.time()\n", + "lam_est_host = estimate_host(A_host)\n", + "end_time = time.time()\n", + "\n", + "print(f\"\\nEstimated Eigenvalue (CPU): {lam_est_host}\")\n", + "print(f\"Time taken: {end_time - start_time:.4f}s\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 3. The GPU Port (CuPy)\n", + "\n", + "### Exercise: Port the CPU Implementation to GPU\n", + "\n", + "Now it's your turn! Your task is to convert the `estimate_host` function to run on the GPU using CuPy.\n", + "\n", + "**Remember the rules of Memory Spaces:**\n", + "1. **Transfer:** Move `A_host` from CPU to GPU using `cp.asarray()`.\n", + "2. **Compute:** Perform math using `cp` functions on the GPU.\n", + "3. **Retrieve:** Move result back to CPU using `cp.asnumpy()` or `.item()` if we need to print it or use it in standard Python.\n", + "\n", + "**Hint:** CuPy tries to replicate the NumPy API. In many cases, you can simply change `np.` to `cp.`. However, CuPy operations *must* run on data present in Device Memory.\n", + "\n", + "**Fill in the `TODO` sections in the skeleton code below:**\n" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "def estimate_device_exercise(A, cfg=PowerIterationConfig()):\n", + " \"\"\"\n", + " Port the power iteration algorithm to the GPU using CuPy.\n", + " \n", + " Steps to complete:\n", + " 1. Transfer the input matrix A to the GPU (if it's a numpy array)\n", + " 2. Initialize the vector x on the GPU\n", + " 3. Replace np operations with cp operations\n", + " 4. Return the result as a Python scalar\n", + " \"\"\"\n", + " # ---------------------------------------------------------\n", + " # SOLUTION: MEMORY TRANSFER (Host -> Device)\n", + " # Check if A is a numpy array. If so, move it to GPU using cp.asarray()\n", + " # Otherwise, assume it's already on the device.\n", + " # ---------------------------------------------------------\n", + " if isinstance(A, np.ndarray):\n", + " A_gpu = cp.asarray(A) # SOLUTION: Transfer to GPU\n", + " else:\n", + " A_gpu = A\n", + " \n", + " # ---------------------------------------------------------\n", + " # SOLUTION: Initialize vector of ones ON THE GPU\n", + " # ---------------------------------------------------------\n", + " x = cp.ones(A_gpu.shape[0], dtype=cp.float64) # SOLUTION: Create vector of ones on GPU\n", + " \n", + " for i in range(0, cfg.max_steps, cfg.check_frequency):\n", + " # ---------------------------------------------------------\n", + " # SOLUTION: Perform GPU computations using CuPy\n", + " # ---------------------------------------------------------\n", + " \n", + " # Matrix-Vector multiplication (this works the same with CuPy!)\n", + " y = A_gpu @ x\n", + " \n", + " # Rayleigh quotient\n", + " lam = (x @ y) / (x @ x)\n", + " \n", + " # SOLUTION: Calculate residual using cp.linalg.norm\n", + " res = cp.linalg.norm(y - lam * x)\n", + " \n", + " # SOLUTION: Normalize x using cp.linalg.norm\n", + " x = y / cp.linalg.norm(y)\n", + " \n", + " if cfg.progress:\n", + " print(f\"Step {i}: residual = {res:.3e}\")\n", + "\n", + " # SOLUTION: Copy x from device to host and save a checkpoint.\n", + " np.savetxt(f\"device_{i}.txt\", cp.asnumpy(x))\n", + "\n", + " if res < cfg.residual_threshold:\n", + " break\n", + " \n", + " for _ in range(cfg.check_frequency - 1):\n", + " y = A_gpu @ x\n", + " x = y / cp.linalg.norm(y)\n", + " \n", + " # ---------------------------------------------------------\n", + " # SOLUTION: MEMORY TRANSFER (Device -> Host)\n", + " # Return the eigenvalue as a Python scalar using .item()\n", + " # ---------------------------------------------------------\n", + " result = (x.T @ (A_gpu @ x)) / (x.T @ x)\n", + " return result.item() # SOLUTION: Convert GPU scalar to Python scalar\n", + "\n", + "# Run the GPU implementation\n", + "print(\"\\nRunning GPU Estimate (Input is Host Array)...\")\n", + "start_time = time.time()\n", + "lam_est_device = estimate_device_exercise(A_host)\n", + "cp.cuda.Stream.null.synchronize()\n", + "end_time = time.time()\n", + "\n", + "print(f\"\\nEstimated Eigenvalue (GPU): {lam_est_device}\")\n", + "print(f\"Time taken: {end_time - start_time:.4f}s\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Optimizing Data Generation\n", + "\n", + "In the previous step, we generated data on the CPU and copied it to the GPU. For large datasets, the transfer time (`Host -> Device`) can be a bottleneck. \n", + "\n", + "It is almost always faster to **generate** the data directly on the GPU if possible.\n", + "\n", + "### Exercise: Generate Data Directly on the GPU\n", + "\n", + "Your task is to convert the `generate_host` function to generate the matrix directly on the GPU using CuPy's random functions.\n", + "\n", + "**Hints:**\n", + "- Use `cp.random.seed()` instead of `np.random.seed()`\n", + "- Use `cp.random.random()` instead of `np.random.random()`\n", + "- Use `cp.random.permutation()` instead of `np.random.permutation()`\n", + "- Use `cp.concatenate()`, `cp.array()`, `cp.diag()`, and `cp.linalg.inv()`\n", + "\n", + "**Fill in the `TODO` sections in the skeleton code below:**\n" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "def generate_device_exercise(cfg=PowerIterationConfig()):\n", + " \"\"\"\n", + " Generate a random diagonalizable matrix directly on the GPU.\n", + " \n", + " This should mirror the generate_host function but use CuPy instead of NumPy.\n", + " The key benefit: no Host->Device transfer needed!\n", + " \"\"\"\n", + " # ---------------------------------------------------------\n", + " # SOLUTION: Set the random seed on the GPU\n", + " # ---------------------------------------------------------\n", + " cp.random.seed(42)\n", + " \n", + " # ---------------------------------------------------------\n", + " # SOLUTION: Create eigenvalues on the GPU\n", + " # Generate (dim-1) random values, scale them, then combine with 1.0\n", + " # ---------------------------------------------------------\n", + " # SOLUTION: Generate weak eigenvalues using cp.random.random()\n", + " weak_lam = cp.random.random(cfg.dim - 1) * (1.0 - cfg.dominance)\n", + " \n", + " # SOLUTION: Concatenate [1.0] with weak_lam using cp.concatenate and cp.array\n", + " # Then permute them using cp.random.permutation\n", + " lam = cp.random.permutation(cp.concatenate((cp.array([1.0]), weak_lam)))\n", + " \n", + " # ---------------------------------------------------------\n", + " # SOLUTION: Construct the matrix A = P * D * P^-1 on the GPU\n", + " # ---------------------------------------------------------\n", + " # SOLUTION: Generate random matrix P using cp.random.random()\n", + " P = cp.random.random((cfg.dim, cfg.dim))\n", + " \n", + " # SOLUTION: Create diagonal matrix D using cp.diag()\n", + " D = cp.diag(cp.random.permutation(lam))\n", + " \n", + " # SOLUTION: Compute A = P @ D @ P^-1 using cp.linalg.inv()\n", + " A = ((P @ D) @ cp.linalg.inv(P))\n", + " \n", + " return A\n", + "\n", + "print(\"\\nGenerating Data directly on GPU...\")\n", + "start_time = time.time()\n", + "A_device = generate_device_exercise()\n", + "end_time = time.time()\n", + "print(f\"Generation time: {end_time - start_time:.4f}s\")\n", + "\n", + "print(\"Running GPU Estimate (Input is Device Array)...\")\n", + "start_time = time.time()\n", + "# No transfer overhead here because A_device is already on GPU\n", + "lam_est_device_gen = estimate_device_exercise(A_device)\n", + "cp.cuda.Stream.null.synchronize()\n", + "end_time = time.time()\n", + "print(f\"Compute time: {end_time - start_time:.4f}s\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Think About It\n", + "\n", + "Both functions use `seed(42)`. Are `A_host` and `A_device` identical? Try comparing them:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "print(\"NumPy:\", A_host[0, :3])\n", + "print(\"CuPy:\", A_device[0, :3].get())" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "**SOLUTION:**\n", + "\n", + "This reveals that `np.random` and `cp.random` use **different random number generator (RNG) implementations**, even with the same seed.\n", + "\n", + "- NumPy uses the Mersenne Twister algorithm (or PCG64 in newer versions) on the CPU.\n", + "- CuPy uses a GPU-optimized RNG (typically XORWOW from cuRAND) that runs efficiently in parallel on thousands of GPU threads.\n", + "\n", + "Even with the same seed value (`42`), these different algorithms produce completely different sequences of \"random\" numbers. This is why `A_host` and `A_device` contain different values.\n", + "\n", + "**Key Takeaway:** If you need *identical* data on both CPU and GPU for verification purposes, you should:\n", + "1. Generate the data on one device (e.g., CPU with NumPy)\n", + "2. Transfer it to the other device (e.g., `cp.asarray(A_host)`)\n", + "\n", + "This guarantees bit-for-bit identical data, which is essential for debugging and validation." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Verification and Benchmarking\n", + "\n", + "Finally, let's verify our accuracy against a reference implementation (`numpy.linalg.eigvals`) and benchmark the speedup.\n", + "\n", + "**Note on CuPy Limitations:** You might wonder why we use `np.linalg.eigvals` on the CPU instead of a CuPy equivalent. The reason is that CuPy does not yet implement `eigvals`. While CuPy covers a large portion of the NumPy API, it does not support every function. Always check the [CuPy documentation](https://docs.cupy.dev/en/stable/reference/comparison.html) to verify which functions are available before assuming a direct NumPy-to-CuPy conversion will work.\n" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "print(\"Calculating Reference Eigenvalue (numpy.linalg)...\")\n", + "# Note: calculating all eigenvalues is computationally expensive\n", + "lam_ref = np.linalg.eigvals(A_host).real.max()\n", + "\n", + "print(f\"\\n--- Results ---\")\n", + "print(f\"Reference: {lam_ref}\")\n", + "print(f\"CPU Est: {lam_est_host}\")\n", + "print(f\"GPU Est: {lam_est_device_gen}\")\n", + "\n", + "# Assert correctness\n", + "np.testing.assert_allclose(lam_est_host, lam_ref, rtol=1e-4)\n", + "np.testing.assert_allclose(lam_est_device_gen, lam_ref, rtol=1e-4)\n", + "print(\"\\nAccuracy verification passed!\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### A Note on Verification\n", + "\n", + "As mentioned before, `A_host` and `A_device` are **different matrices** (NumPy and CuPy use different RNG implementations). Yet the verification passes. Why?\n", + "\n", + "Both matrices are *constructed* with one eigenvalue explicitly set to **1.0**. The verification confirms that power iteration correctly finds this dominant eigenvalue—not that the matrices are identical.\n", + "\n", + "**Key takeaway:** If you need to verify GPU computation against CPU on the *exact same data*, generate on one device and transfer to the other." + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "### Benchmarking with `cupyx.profiler.benchmark`\n", + "\n", + "We use CuPy's built-in benchmarking utility for accurate GPU timing. This handles warmup and synchronization automatically.\n" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "from cupyx.profiler import benchmark\n", + "\n", + "cfg = PowerIterationConfig(progress=False)\n", + "\n", + "# 1. CPU\n", + "print(\"Timing CPU...\")\n", + "result_cpu = benchmark(estimate_host, args=(A_host, cfg), n_repeat=10)\n", + "t_cpu_ms = result_cpu.cpu_times.mean() * 1000\n", + "\n", + "# 2. GPU (with transfer overhead)\n", + "print(\"Timing GPU (Host Input)...\")\n", + "result_transfer = benchmark(estimate_device_exercise, args=(A_host, cfg), n_repeat=10)\n", + "t_gpu_transfer_ms = result_transfer.gpu_times.mean() * 1000\n", + "\n", + "# 3. GPU (pure device)\n", + "print(\"Timing GPU (Device Input)...\")\n", + "result_pure = benchmark(estimate_device_exercise, args=(A_device, cfg), n_repeat=10)\n", + "t_gpu_pure_ms = result_pure.gpu_times.mean() * 1000\n", + "\n", + "print(f\"\\n--- Average Compute Times ---\")\n", + "print(f\"CPU: {t_cpu_ms:.2f} ms\")\n", + "print(f\"GPU (with transfer): {t_gpu_transfer_ms:.2f} ms\")\n", + "print(f\"GPU (pure): {t_gpu_pure_ms:.2f} ms\")\n", + "\n", + "speedup = t_cpu_ms / t_gpu_pure_ms\n", + "print(f\"\\nSpeedup: {speedup:.1f}x\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "---\n", + "\n", + "## Extra Credit\n", + "\n", + "**Explore the impact of changing the following parameters:**\n", + "\n", + "1. **Problem Size (`dim`):** How does the GPU speedup change as you increase or decrease the matrix dimensions? Try values like 1024, 2048, 4096, 8192.\n", + "\n", + "2. **Compute Workload (`max_steps` and `dominance`):** The `dominance` parameter controls how quickly the algorithm converges. A smaller dominance means eigenvalues are closer together, requiring more iterations. How does this affect the CPU vs GPU comparison?\n", + "\n", + "3. **Check Frequency (`check_frequency`):** This controls how often we check for convergence (and trigger implicit CPU synchronization via the print statement). What happens to GPU performance when you check every step (`check_frequency=1`) vs. less frequently (`check_frequency=50`)?\n", + "\n", + "**Experiment below:**" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "# Try different configurations here!\n", + "# Example:\n", + "# cfg_large = PowerIterationConfig(dim=8192, progress=False)\n", + "# cfg_slow_converge = PowerIterationConfig(dominance=0.01, progress=False)\n", + "# cfg_frequent_check = PowerIterationConfig(check_frequency=1, progress=True)\n", + "\n", + "# Your experiments:" + ], + "execution_count": null, + "outputs": [] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + } }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import numpy as np\n", - "import cupy as cp\n", - "import time\n", - "from dataclasses import dataclass\n", - "\n", - "# Configuration for the algorithm\n", - "@dataclass\n", - "class PowerIterationConfig:\n", - " dim: int = 4096 # Matrix size (dim x dim)\n", - " dominance: float = 0.1 # How much larger the top eigenvalue is (controls convergence speed)\n", - " max_steps: int = 400 # Maximum iterations\n", - " check_frequency: int = 10 # Check for convergence every N steps\n", - " progress: bool = True # Print progress logs\n", - " residual_threshold: float = 1e-10 # Stop if error is below this" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 2. The CPU Baseline (NumPy)\n", - "\n", - "We generate a random dense matrix that is diagonalizable. This data is generated on the **Host (CPU)** and resides in **Host Memory**.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def generate_host(cfg=PowerIterationConfig()):\n", - " \"\"\"Generates a random diagonalizable matrix on the CPU.\"\"\"\n", - " np.random.seed(42)\n", - "\n", - " # Create eigenvalues: One large one (1.0), the rest smaller\n", - " weak_lam = np.random.random(cfg.dim - 1) * (1.0 - cfg.dominance)\n", - " lam = np.random.permutation(np.concatenate(([1.0], weak_lam)))\n", - "\n", - " # Construct matrix A = P * D * P^-1\n", - " P = np.random.random((cfg.dim, cfg.dim)) # Random invertible matrix\n", - " D = np.diag(np.random.permutation(lam)) # Diagonal matrix of eigenvalues\n", - " A = ((P @ D) @ np.linalg.inv(P)) # The final matrix\n", - " return A\n", - "\n", - "# Generate the data on Host\n", - "print(\"Generating Host Data...\")\n", - "A_host = generate_host()\n", - "print(f\"Host Matrix Shape: {A_host.shape}\")\n", - "print(f\"Data Type: {A_host.dtype}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Implementing Power Iteration (CPU)\n", - "\n", - "As described above, the Power Iteration algorithm repeatedly multiplies a vector $x$ by matrix $A$ ($y = Ax$) and normalizes the result. We initialize this algorithm with a vector of 1s ($x_0$) as our initial guess." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def estimate_host(A, cfg=PowerIterationConfig()):\n", - " \"\"\"\n", - " Performs power iteration using purely NumPy (CPU).\n", - " \"\"\"\n", - " # Initialize vector of ones on Host\n", - " x = np.ones(A.shape[0], dtype=np.float64)\n", - "\n", - " for i in range(0, cfg.max_steps, cfg.check_frequency):\n", - " # Matrix-Vector multiplication\n", - " y = A @ x\n", - " \n", - " # Rayleigh quotient: (x . y) / (x . x)\n", - " lam = (x @ y) / (x @ x)\n", - " \n", - " # Calculate residual (error)\n", - " res = np.linalg.norm(y - lam * x)\n", - " \n", - " # Normalize vector for next step\n", - " x = y / np.linalg.norm(y)\n", - "\n", - " if cfg.progress:\n", - " print(f\"Step {i}: residual = {res:.3e}\")\n", - "\n", - " # Convergence check\n", - " if res < cfg.residual_threshold:\n", - " break\n", - "\n", - " # Run intermediate steps without checking residual to save compute\n", - " for _ in range(cfg.check_frequency - 1):\n", - " y = A @ x\n", - " x = y / np.linalg.norm(y)\n", - "\n", - " return (x.T @ (A @ x)) / (x.T @ x)\n", - "\n", - "# Run CPU Baseline\n", - "print(\"\\nRunning CPU Estimate...\")\n", - "start_time = time.time()\n", - "lam_est_host = estimate_host(A_host)\n", - "end_time = time.time()\n", - "\n", - "print(f\"\\nEstimated Eigenvalue (CPU): {lam_est_host}\")\n", - "print(f\"Time taken: {end_time - start_time:.4f}s\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 3. The GPU Port (CuPy)\n", - "\n", - "### Exercise: Port the CPU Implementation to GPU\n", - "\n", - "Now it's your turn! Your task is to convert the `estimate_host` function to run on the GPU using CuPy.\n", - "\n", - "**Remember the rules of Memory Spaces:**\n", - "1. **Transfer:** Move `A_host` from CPU to GPU using `cp.asarray()`.\n", - "2. **Compute:** Perform math using `cp` functions on the GPU.\n", - "3. **Retrieve:** Move result back to CPU using `cp.asnumpy()` or `.item()` if we need to print it or use it in standard Python.\n", - "\n", - "**Hint:** CuPy tries to replicate the NumPy API. In many cases, you can simply change `np.` to `cp.`. However, CuPy operations *must* run on data present in Device Memory.\n", - "\n", - "**Fill in the `TODO` sections in the skeleton code below:**\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def estimate_device_exercise(A, cfg=PowerIterationConfig()):\n", - " \"\"\"\n", - " Port the power iteration algorithm to the GPU using CuPy.\n", - " \n", - " Steps to complete:\n", - " 1. Transfer the input matrix A to the GPU (if it's a numpy array)\n", - " 2. Initialize the vector x on the GPU\n", - " 3. Replace np operations with cp operations\n", - " 4. Return the result as a Python scalar\n", - " \"\"\"\n", - " # ---------------------------------------------------------\n", - " # SOLUTION: MEMORY TRANSFER (Host -> Device)\n", - " # Check if A is a numpy array. If so, move it to GPU using cp.asarray()\n", - " # Otherwise, assume it's already on the device.\n", - " # ---------------------------------------------------------\n", - " if isinstance(A, np.ndarray):\n", - " A_gpu = cp.asarray(A) # SOLUTION: Transfer to GPU\n", - " else:\n", - " A_gpu = A\n", - " \n", - " # ---------------------------------------------------------\n", - " # SOLUTION: Initialize vector of ones ON THE GPU\n", - " # ---------------------------------------------------------\n", - " x = cp.ones(A_gpu.shape[0], dtype=cp.float64) # SOLUTION: Create vector of ones on GPU\n", - " \n", - " for i in range(0, cfg.max_steps, cfg.check_frequency):\n", - " # ---------------------------------------------------------\n", - " # SOLUTION: Perform GPU computations using CuPy\n", - " # ---------------------------------------------------------\n", - " \n", - " # Matrix-Vector multiplication (this works the same with CuPy!)\n", - " y = A_gpu @ x\n", - " \n", - " # Rayleigh quotient\n", - " lam = (x @ y) / (x @ x)\n", - " \n", - " # SOLUTION: Calculate residual using cp.linalg.norm\n", - " res = cp.linalg.norm(y - lam * x)\n", - " \n", - " # SOLUTION: Normalize x using cp.linalg.norm\n", - " x = y / cp.linalg.norm(y)\n", - " \n", - " if cfg.progress:\n", - " print(f\"Step {i}: residual = {res:.3e}\")\n", - " \n", - " if res < cfg.residual_threshold:\n", - " break\n", - " \n", - " for _ in range(cfg.check_frequency - 1):\n", - " y = A_gpu @ x\n", - " x = y / cp.linalg.norm(y)\n", - " \n", - " # ---------------------------------------------------------\n", - " # SOLUTION: MEMORY TRANSFER (Device -> Host)\n", - " # Return the eigenvalue as a Python scalar using .item()\n", - " # ---------------------------------------------------------\n", - " result = (x.T @ (A_gpu @ x)) / (x.T @ x)\n", - " return result.item() # SOLUTION: Convert GPU scalar to Python scalar\n", - "\n", - "# Run the GPU implementation\n", - "print(\"\\nRunning GPU Estimate (Input is Host Array)...\")\n", - "start_time = time.time()\n", - "lam_est_device = estimate_device_exercise(A_host)\n", - "cp.cuda.Stream.null.synchronize()\n", - "end_time = time.time()\n", - "\n", - "print(f\"\\nEstimated Eigenvalue (GPU): {lam_est_device}\")\n", - "print(f\"Time taken: {end_time - start_time:.4f}s\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 4. Optimizing Data Generation\n", - "\n", - "In the previous step, we generated data on the CPU and copied it to the GPU. For large datasets, the transfer time (`Host -> Device`) can be a bottleneck. \n", - "\n", - "It is almost always faster to **generate** the data directly on the GPU if possible.\n", - "\n", - "### Exercise: Generate Data Directly on the GPU\n", - "\n", - "Your task is to convert the `generate_host` function to generate the matrix directly on the GPU using CuPy's random functions.\n", - "\n", - "**Hints:**\n", - "- Use `cp.random.seed()` instead of `np.random.seed()`\n", - "- Use `cp.random.random()` instead of `np.random.random()`\n", - "- Use `cp.random.permutation()` instead of `np.random.permutation()`\n", - "- Use `cp.concatenate()`, `cp.array()`, `cp.diag()`, and `cp.linalg.inv()`\n", - "\n", - "**Fill in the `TODO` sections in the skeleton code below:**\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "def generate_device_exercise(cfg=PowerIterationConfig()):\n", - " \"\"\"\n", - " Generate a random diagonalizable matrix directly on the GPU.\n", - " \n", - " This should mirror the generate_host function but use CuPy instead of NumPy.\n", - " The key benefit: no Host->Device transfer needed!\n", - " \"\"\"\n", - " # ---------------------------------------------------------\n", - " # SOLUTION: Set the random seed on the GPU\n", - " # ---------------------------------------------------------\n", - " cp.random.seed(42)\n", - " \n", - " # ---------------------------------------------------------\n", - " # SOLUTION: Create eigenvalues on the GPU\n", - " # Generate (dim-1) random values, scale them, then combine with 1.0\n", - " # ---------------------------------------------------------\n", - " # SOLUTION: Generate weak eigenvalues using cp.random.random()\n", - " weak_lam = cp.random.random(cfg.dim - 1) * (1.0 - cfg.dominance)\n", - " \n", - " # SOLUTION: Concatenate [1.0] with weak_lam using cp.concatenate and cp.array\n", - " # Then permute them using cp.random.permutation\n", - " lam = cp.random.permutation(cp.concatenate((cp.array([1.0]), weak_lam)))\n", - " \n", - " # ---------------------------------------------------------\n", - " # SOLUTION: Construct the matrix A = P * D * P^-1 on the GPU\n", - " # ---------------------------------------------------------\n", - " # SOLUTION: Generate random matrix P using cp.random.random()\n", - " P = cp.random.random((cfg.dim, cfg.dim))\n", - " \n", - " # SOLUTION: Create diagonal matrix D using cp.diag()\n", - " D = cp.diag(cp.random.permutation(lam))\n", - " \n", - " # SOLUTION: Compute A = P @ D @ P^-1 using cp.linalg.inv()\n", - " A = ((P @ D) @ cp.linalg.inv(P))\n", - " \n", - " return A\n", - "\n", - "print(\"\\nGenerating Data directly on GPU...\")\n", - "start_time = time.time()\n", - "A_device = generate_device_exercise()\n", - "end_time = time.time()\n", - "print(f\"Generation time: {end_time - start_time:.4f}s\")\n", - "\n", - "print(\"Running GPU Estimate (Input is Device Array)...\")\n", - "start_time = time.time()\n", - "# No transfer overhead here because A_device is already on GPU\n", - "lam_est_device_gen = estimate_device_exercise(A_device)\n", - "cp.cuda.Stream.null.synchronize()\n", - "end_time = time.time()\n", - "print(f\"Compute time: {end_time - start_time:.4f}s\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Think About It\n", - "\n", - "Both functions use `seed(42)`. Are `A_host` and `A_device` identical? Try comparing them:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"NumPy:\", A_host[0, :3])\n", - "print(\"CuPy:\", A_device[0, :3].get())" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "**SOLUTION:**\n", - "\n", - "This reveals that `np.random` and `cp.random` use **different random number generator (RNG) implementations**, even with the same seed.\n", - "\n", - "- NumPy uses the Mersenne Twister algorithm (or PCG64 in newer versions) on the CPU.\n", - "- CuPy uses a GPU-optimized RNG (typically XORWOW from cuRAND) that runs efficiently in parallel on thousands of GPU threads.\n", - "\n", - "Even with the same seed value (`42`), these different algorithms produce completely different sequences of \"random\" numbers. This is why `A_host` and `A_device` contain different values.\n", - "\n", - "**Key Takeaway:** If you need *identical* data on both CPU and GPU for verification purposes, you should:\n", - "1. Generate the data on one device (e.g., CPU with NumPy)\n", - "2. Transfer it to the other device (e.g., `cp.asarray(A_host)`)\n", - "\n", - "This guarantees bit-for-bit identical data, which is essential for debugging and validation." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 5. Verification and Benchmarking\n", - "\n", - "Finally, let's verify our accuracy against a reference implementation (`numpy.linalg.eigvals`) and benchmark the speedup.\n", - "\n", - "**Note on CuPy Limitations:** You might wonder why we use `np.linalg.eigvals` on the CPU instead of a CuPy equivalent. The reason is that CuPy does not yet implement `eigvals`. While CuPy covers a large portion of the NumPy API, it does not support every function. Always check the [CuPy documentation](https://docs.cupy.dev/en/stable/reference/comparison.html) to verify which functions are available before assuming a direct NumPy-to-CuPy conversion will work.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "print(\"Calculating Reference Eigenvalue (numpy.linalg)...\")\n", - "# Note: calculating all eigenvalues is computationally expensive\n", - "lam_ref = np.linalg.eigvals(A_host).real.max()\n", - "\n", - "print(f\"\\n--- Results ---\")\n", - "print(f\"Reference: {lam_ref}\")\n", - "print(f\"CPU Est: {lam_est_host}\")\n", - "print(f\"GPU Est: {lam_est_device_gen}\")\n", - "\n", - "# Assert correctness\n", - "np.testing.assert_allclose(lam_est_host, lam_ref, rtol=1e-4)\n", - "np.testing.assert_allclose(lam_est_device_gen, lam_ref, rtol=1e-4)\n", - "print(\"\\nAccuracy verification passed!\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### A Note on Verification\n", - "\n", - "As mentioned before, `A_host` and `A_device` are **different matrices** (NumPy and CuPy use different RNG implementations). Yet the verification passes. Why?\n", - "\n", - "Both matrices are *constructed* with one eigenvalue explicitly set to **1.0**. The verification confirms that power iteration correctly finds this dominant eigenvalue—not that the matrices are identical.\n", - "\n", - "**Key takeaway:** If you need to verify GPU computation against CPU on the *exact same data*, generate on one device and transfer to the other." - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### Benchmarking with `cupyx.profiler.benchmark`\n", - "\n", - "We use CuPy's built-in benchmarking utility for accurate GPU timing. This handles warmup and synchronization automatically.\n" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "from cupyx.profiler import benchmark\n", - "\n", - "cfg = PowerIterationConfig(progress=False)\n", - "\n", - "# 1. CPU\n", - "print(\"Timing CPU...\")\n", - "result_cpu = benchmark(estimate_host, args=(A_host, cfg), n_repeat=10)\n", - "t_cpu_ms = result_cpu.cpu_times.mean() * 1000\n", - "\n", - "# 2. GPU (with transfer overhead)\n", - "print(\"Timing GPU (Host Input)...\")\n", - "result_transfer = benchmark(estimate_device_exercise, args=(A_host, cfg), n_repeat=10)\n", - "t_gpu_transfer_ms = result_transfer.gpu_times.mean() * 1000\n", - "\n", - "# 3. GPU (pure device)\n", - "print(\"Timing GPU (Device Input)...\")\n", - "result_pure = benchmark(estimate_device_exercise, args=(A_device, cfg), n_repeat=10)\n", - "t_gpu_pure_ms = result_pure.gpu_times.mean() * 1000\n", - "\n", - "print(f\"\\n--- Average Compute Times ---\")\n", - "print(f\"CPU: {t_cpu_ms:.2f} ms\")\n", - "print(f\"GPU (with transfer): {t_gpu_transfer_ms:.2f} ms\")\n", - "print(f\"GPU (pure): {t_gpu_pure_ms:.2f} ms\")\n", - "\n", - "speedup = t_cpu_ms / t_gpu_pure_ms\n", - "print(f\"\\nSpeedup: {speedup:.1f}x\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "---\n", - "\n", - "## Extra Credit\n", - "\n", - "**Explore the impact of changing the following parameters:**\n", - "\n", - "1. **Problem Size (`dim`):** How does the GPU speedup change as you increase or decrease the matrix dimensions? Try values like 1024, 2048, 4096, 8192.\n", - "\n", - "2. **Compute Workload (`max_steps` and `dominance`):** The `dominance` parameter controls how quickly the algorithm converges. A smaller dominance means eigenvalues are closer together, requiring more iterations. How does this affect the CPU vs GPU comparison?\n", - "\n", - "3. **Check Frequency (`check_frequency`):** This controls how often we check for convergence (and trigger implicit CPU synchronization via the print statement). What happens to GPU performance when you check every step (`check_frequency=1`) vs. less frequently (`check_frequency=50`)?\n", - "\n", - "**Experiment below:**" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "# Try different configurations here!\n", - "# Example:\n", - "# cfg_large = PowerIterationConfig(dim=8192, progress=False)\n", - "# cfg_slow_converge = PowerIterationConfig(dominance=0.01, progress=False)\n", - "# cfg_frequent_check = PowerIterationConfig(check_frequency=1, progress=True)\n", - "\n", - "# Your experiments:" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 2 -} + "nbformat": 4, + "nbformat_minor": 2 +} \ No newline at end of file From dbb7c131db4e57d350200d8bfef654a01f2096f0 Mon Sep 17 00:00:00 2001 From: Bryce Adelstein Lelbach aka wash Date: Tue, 3 Mar 2026 14:07:18 -0500 Subject: [PATCH 09/18] Tutorials/Accelerated Python/Memory Spaces: Remove unnecessary isinstance check before cp.asarray(). The isinstance(A, np.ndarray) guard added in 2f5c4fc is unnecessary because cp.asarray() already handles both cases: it copies a host array to the GPU, and is a no-op when the array is already on the GPU. Teaching users to call cp.asarray() unconditionally is the intended lesson. Made-with: Cursor --- .../05__memory_spaces__power_iteration.ipynb | 11 ++++------- ...05__memory_spaces__power_iteration__SOLUTION.ipynb | 11 ++++------- 2 files changed, 8 insertions(+), 14 deletions(-) diff --git a/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb index 2210a947..53aad8b3 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb @@ -231,7 +231,7 @@ " TODO: Port the power iteration algorithm to the GPU using CuPy.\n", " \n", " Steps to complete:\n", - " 1. Transfer the input matrix A to the GPU (if it's a numpy array)\n", + " 1. Transfer the input matrix A to the GPU\n", " 2. Initialize the vector x on the GPU\n", " 3. Replace np operations with cp operations\n", " 4. Copy x from device to host and save a checkpoint\n", @@ -239,13 +239,10 @@ " \"\"\"\n", " # ---------------------------------------------------------\n", " # TODO 1: MEMORY TRANSFER (Host -> Device)\n", - " # Check if A is a numpy array. If so, move it to GPU using cp.asarray()\n", - " # Otherwise, assume it's already on the device.\n", + " # Move A to the GPU using cp.asarray().\n", + " # If A is already on the GPU, cp.asarray() is a no-op.\n", " # ---------------------------------------------------------\n", - " if isinstance(A, np.ndarray):\n", - " A_gpu = ... # TODO: Transfer to GPU\n", - " else:\n", - " A_gpu = A\n", + " A_gpu = ... # TODO: Transfer to GPU\n", " \n", " # ---------------------------------------------------------\n", " # TODO 2: Initialize vector of ones ON THE GPU\n", diff --git a/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb index ef862364..a4e5b61a 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb @@ -231,20 +231,17 @@ " Port the power iteration algorithm to the GPU using CuPy.\n", " \n", " Steps to complete:\n", - " 1. Transfer the input matrix A to the GPU (if it's a numpy array)\n", + " 1. Transfer the input matrix A to the GPU\n", " 2. Initialize the vector x on the GPU\n", " 3. Replace np operations with cp operations\n", " 4. Return the result as a Python scalar\n", " \"\"\"\n", " # ---------------------------------------------------------\n", " # SOLUTION: MEMORY TRANSFER (Host -> Device)\n", - " # Check if A is a numpy array. If so, move it to GPU using cp.asarray()\n", - " # Otherwise, assume it's already on the device.\n", + " # If A is on the host, cp.asarray() copies it to the GPU.\n", + " # If A is already on the GPU, cp.asarray() is a no-op.\n", " # ---------------------------------------------------------\n", - " if isinstance(A, np.ndarray):\n", - " A_gpu = cp.asarray(A) # SOLUTION: Transfer to GPU\n", - " else:\n", - " A_gpu = A\n", + " A_gpu = cp.asarray(A) # SOLUTION: Transfer to GPU\n", " \n", " # ---------------------------------------------------------\n", " # SOLUTION: Initialize vector of ones ON THE GPU\n", From 90326738e75b864308d638bf42637b63573a4288 Mon Sep 17 00:00:00 2001 From: Bryce Adelstein Lelbach aka wash Date: Tue, 3 Mar 2026 14:09:51 -0500 Subject: [PATCH 10/18] Tutorials/Accelerated Python/Memory Spaces: Rename estimate_device_exercise and generate_device_exercise back to estimate_device and generate_device. The _exercise suffix was added in 2f5c4fc but breaks the naming symmetry with estimate_host and generate_host. The host/device naming convention is cleaner and mirrors the pattern used in the Notebook 06 (Asynchrony) notebooks. Made-with: Cursor --- .../05__memory_spaces__power_iteration.ipynb | 70 +++++++++---------- ...ry_spaces__power_iteration__SOLUTION.ipynb | 70 +++++++++---------- 2 files changed, 70 insertions(+), 70 deletions(-) diff --git a/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb index 53aad8b3..674a3f0a 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb @@ -85,7 +85,9 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "import numpy as np\n", "import cupy as cp\n", @@ -101,9 +103,7 @@ " check_frequency: int = 10 # Check for convergence every N steps\n", " progress: bool = True # Print progress logs\n", " residual_threshold: float = 1e-10 # Stop if error is below this" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -116,7 +116,9 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "def generate_host(cfg=PowerIterationConfig()):\n", " \"\"\"Generates a random diagonalizable matrix on the CPU.\"\"\"\n", @@ -137,9 +139,7 @@ "A_host = generate_host()\n", "print(f\"Host Matrix Shape: {A_host.shape}\")\n", "print(f\"Data Type: {A_host.dtype}\")" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -152,7 +152,9 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "def estimate_host(A, cfg=PowerIterationConfig()):\n", " \"\"\"\n", @@ -198,9 +200,7 @@ "\n", "print(f\"\\nEstimated Eigenvalue (CPU): {lam_est_host}\")\n", "print(f\"Time taken: {end_time - start_time:.4f}s\")" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -224,9 +224,11 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "def estimate_device_exercise(A, cfg=PowerIterationConfig()):\n", + "def estimate_device(A, cfg=PowerIterationConfig()):\n", " \"\"\"\n", " TODO: Port the power iteration algorithm to the GPU using CuPy.\n", " \n", @@ -293,11 +295,9 @@ " return ...\n", "\n", "# Uncomment to test your implementation:\n", - "# lam_test = estimate_device_exercise(A_host, PowerIterationConfig(max_steps=50))\n", + "# lam_test = estimate_device(A_host, PowerIterationConfig(max_steps=50))\n", "# print(f\"Your result: {lam_test}\")" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -324,9 +324,11 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "def generate_device_exercise(cfg=PowerIterationConfig()):\n", + "def generate_device(cfg=PowerIterationConfig()):\n", " \"\"\"\n", " TODO: Generate a random diagonalizable matrix directly on the GPU.\n", " \n", @@ -366,20 +368,18 @@ "# Uncomment to test your implementation:\n", "# print(\"\\nGenerating Data directly on GPU...\")\n", "# start_time = time.time()\n", - "# A_device = generate_device_exercise()\n", + "# A_device = generate_device()\n", "# end_time = time.time()\n", "# print(f\"Generation time: {end_time - start_time:.4f}s\")\n", "\n", "# print(\"Running GPU Estimate (Input is Device Array)...\")\n", "# start_time = time.time()\n", "# # No transfer overhead here because A_device is already on GPU\n", - "# lam_est_device_gen = estimate_device_exercise(A_device)\n", + "# lam_est_device_gen = estimate_device(A_device)\n", "# cp.cuda.Stream.null.synchronize()\n", "# end_time = time.time()\n", "# print(f\"Compute time: {end_time - start_time:.4f}s\")" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -392,14 +392,14 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "# NOTE: Run these after completing both exercises above\n", "# print(\"NumPy:\", A_host[0, :3])\n", "# print(\"CuPy:\", A_device[0, :3].get())" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -421,7 +421,9 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "print(\"Calculating Reference Eigenvalue (numpy.linalg)...\")\n", "# Note: calculating all eigenvalues is computationally expensive\n", @@ -439,9 +441,7 @@ "# Uncomment after completing exercises:\n", "# np.testing.assert_allclose(lam_est_device_gen, lam_ref, rtol=1e-4)\n", "print(\"\\nCPU accuracy verification passed!\")" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -467,7 +467,9 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "# Uncomment and run after completing both exercises above:\n", "# from cupyx.profiler import benchmark\n", @@ -481,12 +483,12 @@ "\n", "# # 2. GPU (with transfer overhead)\n", "# print(\"Timing GPU (Host Input)...\")\n", - "# result_transfer = benchmark(estimate_device_exercise, args=(A_host, cfg), n_repeat=10)\n", + "# result_transfer = benchmark(estimate_device, args=(A_host, cfg), n_repeat=10)\n", "# t_gpu_transfer_ms = result_transfer.gpu_times.mean() * 1000\n", "\n", "# # 3. GPU (pure device)\n", "# print(\"Timing GPU (Device Input)...\")\n", - "# result_pure = benchmark(estimate_device_exercise, args=(A_device, cfg), n_repeat=10)\n", + "# result_pure = benchmark(estimate_device, args=(A_device, cfg), n_repeat=10)\n", "# t_gpu_pure_ms = result_pure.gpu_times.mean() * 1000\n", "\n", "# print(f\"\\n--- Average Compute Times ---\")\n", @@ -496,9 +498,7 @@ "\n", "# speedup = t_cpu_ms / t_gpu_pure_ms\n", "# print(f\"\\nSpeedup: {speedup:.1f}x\")" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -521,7 +521,9 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "# Try different configurations here!\n", "# Example:\n", @@ -530,9 +532,7 @@ "# cfg_frequent_check = PowerIterationConfig(check_frequency=1, progress=True)\n", "\n", "# Your experiments:" - ], - "execution_count": null, - "outputs": [] + ] } ], "metadata": { @@ -544,4 +544,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb index a4e5b61a..32f177b1 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb @@ -85,7 +85,9 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "import numpy as np\n", "import cupy as cp\n", @@ -101,9 +103,7 @@ " check_frequency: int = 10 # Check for convergence every N steps\n", " progress: bool = True # Print progress logs\n", " residual_threshold: float = 1e-10 # Stop if error is below this" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -116,7 +116,9 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "def generate_host(cfg=PowerIterationConfig()):\n", " \"\"\"Generates a random diagonalizable matrix on the CPU.\"\"\"\n", @@ -137,9 +139,7 @@ "A_host = generate_host()\n", "print(f\"Host Matrix Shape: {A_host.shape}\")\n", "print(f\"Data Type: {A_host.dtype}\")" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -152,7 +152,9 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "def estimate_host(A, cfg=PowerIterationConfig()):\n", " \"\"\"\n", @@ -198,9 +200,7 @@ "\n", "print(f\"\\nEstimated Eigenvalue (CPU): {lam_est_host}\")\n", "print(f\"Time taken: {end_time - start_time:.4f}s\")" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -224,9 +224,11 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "def estimate_device_exercise(A, cfg=PowerIterationConfig()):\n", + "def estimate_device(A, cfg=PowerIterationConfig()):\n", " \"\"\"\n", " Port the power iteration algorithm to the GPU using CuPy.\n", " \n", @@ -288,15 +290,13 @@ "# Run the GPU implementation\n", "print(\"\\nRunning GPU Estimate (Input is Host Array)...\")\n", "start_time = time.time()\n", - "lam_est_device = estimate_device_exercise(A_host)\n", + "lam_est_device = estimate_device(A_host)\n", "cp.cuda.Stream.null.synchronize()\n", "end_time = time.time()\n", "\n", "print(f\"\\nEstimated Eigenvalue (GPU): {lam_est_device}\")\n", "print(f\"Time taken: {end_time - start_time:.4f}s\")" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -323,9 +323,11 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "def generate_device_exercise(cfg=PowerIterationConfig()):\n", + "def generate_device(cfg=PowerIterationConfig()):\n", " \"\"\"\n", " Generate a random diagonalizable matrix directly on the GPU.\n", " \n", @@ -364,20 +366,18 @@ "\n", "print(\"\\nGenerating Data directly on GPU...\")\n", "start_time = time.time()\n", - "A_device = generate_device_exercise()\n", + "A_device = generate_device()\n", "end_time = time.time()\n", "print(f\"Generation time: {end_time - start_time:.4f}s\")\n", "\n", "print(\"Running GPU Estimate (Input is Device Array)...\")\n", "start_time = time.time()\n", "# No transfer overhead here because A_device is already on GPU\n", - "lam_est_device_gen = estimate_device_exercise(A_device)\n", + "lam_est_device_gen = estimate_device(A_device)\n", "cp.cuda.Stream.null.synchronize()\n", "end_time = time.time()\n", "print(f\"Compute time: {end_time - start_time:.4f}s\")" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -390,13 +390,13 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "print(\"NumPy:\", A_host[0, :3])\n", "print(\"CuPy:\", A_device[0, :3].get())" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -431,7 +431,9 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "print(\"Calculating Reference Eigenvalue (numpy.linalg)...\")\n", "# Note: calculating all eigenvalues is computationally expensive\n", @@ -446,9 +448,7 @@ "np.testing.assert_allclose(lam_est_host, lam_ref, rtol=1e-4)\n", "np.testing.assert_allclose(lam_est_device_gen, lam_ref, rtol=1e-4)\n", "print(\"\\nAccuracy verification passed!\")" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -474,7 +474,9 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "from cupyx.profiler import benchmark\n", "\n", @@ -487,12 +489,12 @@ "\n", "# 2. GPU (with transfer overhead)\n", "print(\"Timing GPU (Host Input)...\")\n", - "result_transfer = benchmark(estimate_device_exercise, args=(A_host, cfg), n_repeat=10)\n", + "result_transfer = benchmark(estimate_device, args=(A_host, cfg), n_repeat=10)\n", "t_gpu_transfer_ms = result_transfer.gpu_times.mean() * 1000\n", "\n", "# 3. GPU (pure device)\n", "print(\"Timing GPU (Device Input)...\")\n", - "result_pure = benchmark(estimate_device_exercise, args=(A_device, cfg), n_repeat=10)\n", + "result_pure = benchmark(estimate_device, args=(A_device, cfg), n_repeat=10)\n", "t_gpu_pure_ms = result_pure.gpu_times.mean() * 1000\n", "\n", "print(f\"\\n--- Average Compute Times ---\")\n", @@ -502,9 +504,7 @@ "\n", "speedup = t_cpu_ms / t_gpu_pure_ms\n", "print(f\"\\nSpeedup: {speedup:.1f}x\")" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -527,7 +527,9 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "# Try different configurations here!\n", "# Example:\n", @@ -536,9 +538,7 @@ "# cfg_frequent_check = PowerIterationConfig(check_frequency=1, progress=True)\n", "\n", "# Your experiments:" - ], - "execution_count": null, - "outputs": [] + ] } ], "metadata": { @@ -550,4 +550,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} From f9493620808d026470b7690a15db9459d54eebe4 Mon Sep 17 00:00:00 2001 From: Bryce Adelstein Lelbach aka wash Date: Tue, 3 Mar 2026 14:19:56 -0500 Subject: [PATCH 11/18] Tutorials/Accelerated Python/Asynchrony: Limit warmup calls to 1 step to avoid unnecessary computation. Made-with: Cursor --- .../fundamentals/06__asynchrony__power_iteration.ipynb | 4 ++-- .../solutions/06__asynchrony__power_iteration__SOLUTION.ipynb | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/tutorials/accelerated-python/notebooks/fundamentals/06__asynchrony__power_iteration.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/06__asynchrony__power_iteration.ipynb index 24cd8791..54b98519 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/06__asynchrony__power_iteration.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/06__asynchrony__power_iteration.ipynb @@ -140,7 +140,7 @@ "A_device = generate_device()\n", "\n", "# Warmup to ensure modules are loaded and code is JIT compiled before timing.\n", - "estimate_device(A_device, cfg=PowerIterationConfig(progress=False))\n", + "estimate_device(A_device, cfg=PowerIterationConfig(max_steps=1, check_frequency=1, progress=False))\n", "\n", "start = cp.cuda.get_current_stream().record()\n", "lam_est_device = estimate_device(A_device).item()\n", @@ -305,7 +305,7 @@ "A_device = generate_device()\n", "\n", "# Warmup to ensure modules are loaded and code is JIT compiled before timing.\n", - "estimate_device(A_device, cfg=PowerIterationConfig(progress=False))\n", + "estimate_device(A_device, cfg=PowerIterationConfig(max_steps=1, check_frequency=1, progress=False))\n", "\n", "start = cp.cuda.get_current_stream().record()\n", "lam_est_device = estimate_device(A_device).item()\n", diff --git a/tutorials/accelerated-python/notebooks/fundamentals/solutions/06__asynchrony__power_iteration__SOLUTION.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/solutions/06__asynchrony__power_iteration__SOLUTION.ipynb index 32908206..ecc2d62f 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/solutions/06__asynchrony__power_iteration__SOLUTION.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/solutions/06__asynchrony__power_iteration__SOLUTION.ipynb @@ -147,7 +147,7 @@ "A_device = generate_device()\n", "\n", "# Warmup to ensure modules are loaded and code is JIT compiled before timing.\n", - "estimate_device(A_device, cfg=PowerIterationConfig(progress=False))\n", + "estimate_device(A_device, cfg=PowerIterationConfig(max_steps=1, check_frequency=1, progress=False))\n", "\n", "with cpx.profiler.profile():\n", " start = cp.cuda.get_current_stream().record()\n", @@ -352,7 +352,7 @@ "A_device = generate_device()\n", "\n", "# Warmup to ensure modules are loaded and code is JIT compiled before timing.\n", - "estimate_device(A_device, cfg=PowerIterationConfig(progress=False))\n", + "estimate_device(A_device, cfg=PowerIterationConfig(max_steps=1, check_frequency=1, progress=False))\n", "\n", "with cpx.profiler.profile():\n", " start = cp.cuda.get_current_stream().record()\n", From 7ace634ad817e86655139219c056304ea5092223 Mon Sep 17 00:00:00 2001 From: Bryce Adelstein Lelbach aka wash Date: Tue, 3 Mar 2026 14:38:08 -0500 Subject: [PATCH 12/18] Tutorials/Accelerated Python/Memory Spaces: Revert benchmarking to use the same input matrix for host and device. Different matrices converge at different rates, so it's only valid to benchmark on the same inputs. Use A_host for both host and device benchmarks instead of comparing A_host (CPU) against A_device (GPU-generated). Made-with: Cursor --- .../05__memory_spaces__power_iteration.ipynb | 97 ++++++++----------- ...ry_spaces__power_iteration__SOLUTION.ipynb | 94 ++++++++---------- 2 files changed, 80 insertions(+), 111 deletions(-) diff --git a/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb index 674a3f0a..2f41b270 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb @@ -166,13 +166,13 @@ " for i in range(0, cfg.max_steps, cfg.check_frequency):\n", " # Matrix-Vector multiplication\n", " y = A @ x\n", - " \n", + "\n", " # Rayleigh quotient: (x . y) / (x . x)\n", " lam = (x @ y) / (x @ x)\n", - " \n", + "\n", " # Calculate residual (error)\n", " res = np.linalg.norm(y - lam * x)\n", - " \n", + "\n", " # Normalize vector for next step\n", " x = y / np.linalg.norm(y)\n", "\n", @@ -231,7 +231,7 @@ "def estimate_device(A, cfg=PowerIterationConfig()):\n", " \"\"\"\n", " TODO: Port the power iteration algorithm to the GPU using CuPy.\n", - " \n", + "\n", " Steps to complete:\n", " 1. Transfer the input matrix A to the GPU\n", " 2. Initialize the vector x on the GPU\n", @@ -245,31 +245,31 @@ " # If A is already on the GPU, cp.asarray() is a no-op.\n", " # ---------------------------------------------------------\n", " A_gpu = ... # TODO: Transfer to GPU\n", - " \n", + "\n", " # ---------------------------------------------------------\n", " # TODO 2: Initialize vector of ones ON THE GPU\n", " # Hint: Use cp.ones() instead of np.ones()\n", " # ---------------------------------------------------------\n", " x = ... # TODO: Create vector of ones on GPU\n", - " \n", + "\n", " for i in range(0, cfg.max_steps, cfg.check_frequency):\n", " # ---------------------------------------------------------\n", " # TODO 3: Perform GPU computations\n", " # Replace the operations below with CuPy equivalents\n", " # ---------------------------------------------------------\n", - " \n", + "\n", " # Matrix-Vector multiplication (this works the same with CuPy!)\n", " y = A_gpu @ x\n", - " \n", + "\n", " # Rayleigh quotient\n", " lam = (x @ y) / (x @ x)\n", - " \n", + "\n", " # TODO: Calculate residual using cp.linalg.norm (not np.linalg.norm)\n", " res = ...\n", - " \n", + "\n", " # TODO: Normalize x using cp.linalg.norm\n", " x = ...\n", - " \n", + "\n", " if cfg.progress:\n", " print(f\"Step {i}: residual = {res:.3e}\")\n", "\n", @@ -282,11 +282,11 @@ "\n", " if res < cfg.residual_threshold:\n", " break\n", - " \n", + "\n", " for _ in range(cfg.check_frequency - 1):\n", " y = A_gpu @ x\n", " x = y / cp.linalg.norm(y)\n", - " \n", + "\n", " # ---------------------------------------------------------\n", " # TODO 5: MEMORY TRANSFER (Device -> Host)\n", " # Return the eigenvalue as a Python scalar using .item()\n", @@ -331,38 +331,38 @@ "def generate_device(cfg=PowerIterationConfig()):\n", " \"\"\"\n", " TODO: Generate a random diagonalizable matrix directly on the GPU.\n", - " \n", + "\n", " This should mirror the generate_host function but use CuPy instead of NumPy.\n", " The key benefit: no Host->Device transfer needed!\n", " \"\"\"\n", " # ---------------------------------------------------------\n", " # TODO 1: Set the random seed on the GPU\n", " # ---------------------------------------------------------\n", - " ... \n", - " \n", + " ...\n", + "\n", " # ---------------------------------------------------------\n", " # TODO 2: Create eigenvalues on the GPU\n", " # Generate (dim-1) random values, scale them, then combine with 1.0\n", " # ---------------------------------------------------------\n", " # TODO: Generate weak eigenvalues using cp.random.random()\n", " weak_lam = ...\n", - " \n", + "\n", " # TODO: Concatenate [1.0] with weak_lam using cp.concatenate and cp.array\n", " # Then permute them using cp.random.permutation\n", " lam = ...\n", - " \n", + "\n", " # ---------------------------------------------------------\n", " # TODO 3: Construct the matrix A = P * D * P^-1 on the GPU\n", " # ---------------------------------------------------------\n", " # TODO: Generate random matrix P using cp.random.random()\n", " P = ...\n", - " \n", + "\n", " # TODO: Create diagonal matrix D using cp.diag()\n", " D = ...\n", - " \n", + "\n", " # TODO: Compute A = P @ D @ P^-1 using cp.linalg.inv()\n", " A = ...\n", - " \n", + "\n", " return A\n", "\n", "# Uncomment to test your implementation:\n", @@ -426,34 +426,25 @@ "outputs": [], "source": [ "print(\"Calculating Reference Eigenvalue (numpy.linalg)...\")\n", - "# Note: calculating all eigenvalues is computationally expensive\n", "lam_ref = np.linalg.eigvals(A_host).real.max()\n", "\n", "print(f\"\\n--- Results ---\")\n", - "print(f\"Reference: {lam_ref}\")\n", - "print(f\"CPU Est: {lam_est_host}\")\n", + "print(f\"Power iteration (host) = {lam_est_host:.6e}\")\n", + "# Uncomment after completing exercises:\n", + "# print(f\"Power iteration (device) = {lam_est_device:.6e}\")\n", + "print(f\"`eigvals` reference = {lam_ref:.6e}\")\n", "\n", + "rel_err_host = abs(lam_est_host - lam_ref) / abs(lam_ref)\n", "# Uncomment after completing exercises:\n", - "# print(f\"GPU Est: {lam_est_device_gen}\")\n", + "# rel_err_device = abs(lam_est_device - lam_ref) / abs(lam_ref)\n", + "print()\n", + "print(f\"Relative error (host) = {rel_err_host:.3e}\")\n", + "# Uncomment after completing exercises:\n", + "# print(f\"Relative error (device) = {rel_err_device:.3e}\")\n", "\n", - "# Assert correctness for CPU\n", "np.testing.assert_allclose(lam_est_host, lam_ref, rtol=1e-4)\n", "# Uncomment after completing exercises:\n", - "# np.testing.assert_allclose(lam_est_device_gen, lam_ref, rtol=1e-4)\n", - "print(\"\\nCPU accuracy verification passed!\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### A Note on Verification\n", - "\n", - "As mentioned before, `A_host` and `A_device` are **different matrices** (NumPy and CuPy use different RNG implementations). Yet the verification passes. Why?\n", - "\n", - "Both matrices are *constructed* with one eigenvalue explicitly set to **1.0**. The verification confirms that power iteration correctly finds this dominant eigenvalue—not that the matrices are identical.\n", - "\n", - "**Key takeaway:** If you need to verify GPU computation against CPU on the *exact same data*, generate on one device and transfer to the other." + "# np.testing.assert_allclose(lam_est_device, lam_ref, rtol=1e-4)" ] }, { @@ -462,7 +453,9 @@ "source": [ "### Benchmarking with `cupyx.profiler.benchmark`\n", "\n", - "We use CuPy's built-in benchmarking utility for accurate GPU timing. This handles warmup and synchronization automatically.\n" + "We use CuPy's built-in benchmarking utility for accurate GPU timing. This handles warmup and synchronization automatically.\n", + "\n", + "We intentionally use `A_host` for both benchmarks, not `A_device`, because they're not the same matrices due to differences in NumPy and CuPy's random facilities. Different matrices converge at different rates, so it's only valid to benchmark on the same inputs." ] }, { @@ -476,27 +469,19 @@ "\n", "# cfg = PowerIterationConfig(progress=False)\n", "\n", - "# # 1. CPU\n", "# print(\"Timing CPU...\")\n", "# result_cpu = benchmark(estimate_host, args=(A_host, cfg), n_repeat=10)\n", "# t_cpu_ms = result_cpu.cpu_times.mean() * 1000\n", "\n", - "# # 2. GPU (with transfer overhead)\n", - "# print(\"Timing GPU (Host Input)...\")\n", - "# result_transfer = benchmark(estimate_device, args=(A_host, cfg), n_repeat=10)\n", - "# t_gpu_transfer_ms = result_transfer.gpu_times.mean() * 1000\n", - "\n", - "# # 3. GPU (pure device)\n", - "# print(\"Timing GPU (Device Input)...\")\n", - "# result_pure = benchmark(estimate_device, args=(A_device, cfg), n_repeat=10)\n", - "# t_gpu_pure_ms = result_pure.gpu_times.mean() * 1000\n", + "# print(\"Timing GPU...\")\n", + "# result_gpu = benchmark(estimate_device, args=(A_host, cfg), n_repeat=10)\n", + "# t_gpu_ms = result_gpu.gpu_times.mean() * 1000\n", "\n", "# print(f\"\\n--- Average Compute Times ---\")\n", - "# print(f\"CPU: {t_cpu_ms:.2f} ms\")\n", - "# print(f\"GPU (with transfer): {t_gpu_transfer_ms:.2f} ms\")\n", - "# print(f\"GPU (pure): {t_gpu_pure_ms:.2f} ms\")\n", + "# print(f\"Power iteration (host) = {t_cpu_ms:.2f} ms\")\n", + "# print(f\"Power iteration (device) = {t_gpu_ms:.2f} ms\")\n", "\n", - "# speedup = t_cpu_ms / t_gpu_pure_ms\n", + "# speedup = t_cpu_ms / t_gpu_ms\n", "# print(f\"\\nSpeedup: {speedup:.1f}x\")" ] }, diff --git a/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb index 32f177b1..bec66079 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb @@ -166,13 +166,13 @@ " for i in range(0, cfg.max_steps, cfg.check_frequency):\n", " # Matrix-Vector multiplication\n", " y = A @ x\n", - " \n", + "\n", " # Rayleigh quotient: (x . y) / (x . x)\n", " lam = (x @ y) / (x @ x)\n", - " \n", + "\n", " # Calculate residual (error)\n", " res = np.linalg.norm(y - lam * x)\n", - " \n", + "\n", " # Normalize vector for next step\n", " x = y / np.linalg.norm(y)\n", "\n", @@ -231,7 +231,7 @@ "def estimate_device(A, cfg=PowerIterationConfig()):\n", " \"\"\"\n", " Port the power iteration algorithm to the GPU using CuPy.\n", - " \n", + "\n", " Steps to complete:\n", " 1. Transfer the input matrix A to the GPU\n", " 2. Initialize the vector x on the GPU\n", @@ -244,29 +244,29 @@ " # If A is already on the GPU, cp.asarray() is a no-op.\n", " # ---------------------------------------------------------\n", " A_gpu = cp.asarray(A) # SOLUTION: Transfer to GPU\n", - " \n", + "\n", " # ---------------------------------------------------------\n", " # SOLUTION: Initialize vector of ones ON THE GPU\n", " # ---------------------------------------------------------\n", " x = cp.ones(A_gpu.shape[0], dtype=cp.float64) # SOLUTION: Create vector of ones on GPU\n", - " \n", + "\n", " for i in range(0, cfg.max_steps, cfg.check_frequency):\n", " # ---------------------------------------------------------\n", " # SOLUTION: Perform GPU computations using CuPy\n", " # ---------------------------------------------------------\n", - " \n", + "\n", " # Matrix-Vector multiplication (this works the same with CuPy!)\n", " y = A_gpu @ x\n", - " \n", + "\n", " # Rayleigh quotient\n", " lam = (x @ y) / (x @ x)\n", - " \n", + "\n", " # SOLUTION: Calculate residual using cp.linalg.norm\n", " res = cp.linalg.norm(y - lam * x)\n", - " \n", + "\n", " # SOLUTION: Normalize x using cp.linalg.norm\n", " x = y / cp.linalg.norm(y)\n", - " \n", + "\n", " if cfg.progress:\n", " print(f\"Step {i}: residual = {res:.3e}\")\n", "\n", @@ -275,11 +275,11 @@ "\n", " if res < cfg.residual_threshold:\n", " break\n", - " \n", + "\n", " for _ in range(cfg.check_frequency - 1):\n", " y = A_gpu @ x\n", " x = y / cp.linalg.norm(y)\n", - " \n", + "\n", " # ---------------------------------------------------------\n", " # SOLUTION: MEMORY TRANSFER (Device -> Host)\n", " # Return the eigenvalue as a Python scalar using .item()\n", @@ -330,7 +330,7 @@ "def generate_device(cfg=PowerIterationConfig()):\n", " \"\"\"\n", " Generate a random diagonalizable matrix directly on the GPU.\n", - " \n", + "\n", " This should mirror the generate_host function but use CuPy instead of NumPy.\n", " The key benefit: no Host->Device transfer needed!\n", " \"\"\"\n", @@ -338,30 +338,30 @@ " # SOLUTION: Set the random seed on the GPU\n", " # ---------------------------------------------------------\n", " cp.random.seed(42)\n", - " \n", + "\n", " # ---------------------------------------------------------\n", " # SOLUTION: Create eigenvalues on the GPU\n", " # Generate (dim-1) random values, scale them, then combine with 1.0\n", " # ---------------------------------------------------------\n", " # SOLUTION: Generate weak eigenvalues using cp.random.random()\n", " weak_lam = cp.random.random(cfg.dim - 1) * (1.0 - cfg.dominance)\n", - " \n", + "\n", " # SOLUTION: Concatenate [1.0] with weak_lam using cp.concatenate and cp.array\n", " # Then permute them using cp.random.permutation\n", " lam = cp.random.permutation(cp.concatenate((cp.array([1.0]), weak_lam)))\n", - " \n", + "\n", " # ---------------------------------------------------------\n", " # SOLUTION: Construct the matrix A = P * D * P^-1 on the GPU\n", " # ---------------------------------------------------------\n", " # SOLUTION: Generate random matrix P using cp.random.random()\n", " P = cp.random.random((cfg.dim, cfg.dim))\n", - " \n", + "\n", " # SOLUTION: Create diagonal matrix D using cp.diag()\n", " D = cp.diag(cp.random.permutation(lam))\n", - " \n", + "\n", " # SOLUTION: Compute A = P @ D @ P^-1 using cp.linalg.inv()\n", " A = ((P @ D) @ cp.linalg.inv(P))\n", - " \n", + "\n", " return A\n", "\n", "print(\"\\nGenerating Data directly on GPU...\")\n", @@ -436,31 +436,21 @@ "outputs": [], "source": [ "print(\"Calculating Reference Eigenvalue (numpy.linalg)...\")\n", - "# Note: calculating all eigenvalues is computationally expensive\n", "lam_ref = np.linalg.eigvals(A_host).real.max()\n", "\n", "print(f\"\\n--- Results ---\")\n", - "print(f\"Reference: {lam_ref}\")\n", - "print(f\"CPU Est: {lam_est_host}\")\n", - "print(f\"GPU Est: {lam_est_device_gen}\")\n", - "\n", - "# Assert correctness\n", - "np.testing.assert_allclose(lam_est_host, lam_ref, rtol=1e-4)\n", - "np.testing.assert_allclose(lam_est_device_gen, lam_ref, rtol=1e-4)\n", - "print(\"\\nAccuracy verification passed!\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "### A Note on Verification\n", + "print(f\"Power iteration (host) = {lam_est_host:.6e}\")\n", + "print(f\"Power iteration (device) = {lam_est_device:.6e}\")\n", + "print(f\"`eigvals` reference = {lam_ref:.6e}\")\n", "\n", - "As mentioned before, `A_host` and `A_device` are **different matrices** (NumPy and CuPy use different RNG implementations). Yet the verification passes. Why?\n", + "rel_err_host = abs(lam_est_host - lam_ref) / abs(lam_ref)\n", + "rel_err_device = abs(lam_est_device - lam_ref) / abs(lam_ref)\n", + "print()\n", + "print(f\"Relative error (host) = {rel_err_host:.3e}\")\n", + "print(f\"Relative error (device) = {rel_err_device:.3e}\")\n", "\n", - "Both matrices are *constructed* with one eigenvalue explicitly set to **1.0**. The verification confirms that power iteration correctly finds this dominant eigenvalue—not that the matrices are identical.\n", - "\n", - "**Key takeaway:** If you need to verify GPU computation against CPU on the *exact same data*, generate on one device and transfer to the other." + "np.testing.assert_allclose(lam_est_host, lam_ref, rtol=1e-4)\n", + "np.testing.assert_allclose(lam_est_device, lam_ref, rtol=1e-4)" ] }, { @@ -469,7 +459,9 @@ "source": [ "### Benchmarking with `cupyx.profiler.benchmark`\n", "\n", - "We use CuPy's built-in benchmarking utility for accurate GPU timing. This handles warmup and synchronization automatically.\n" + "We use CuPy's built-in benchmarking utility for accurate GPU timing. This handles warmup and synchronization automatically.\n", + "\n", + "We intentionally use `A_host` for both benchmarks, not `A_device`, because they're not the same matrices due to differences in NumPy and CuPy's random facilities. Different matrices converge at different rates, so it's only valid to benchmark on the same inputs." ] }, { @@ -482,27 +474,19 @@ "\n", "cfg = PowerIterationConfig(progress=False)\n", "\n", - "# 1. CPU\n", "print(\"Timing CPU...\")\n", "result_cpu = benchmark(estimate_host, args=(A_host, cfg), n_repeat=10)\n", "t_cpu_ms = result_cpu.cpu_times.mean() * 1000\n", "\n", - "# 2. GPU (with transfer overhead)\n", - "print(\"Timing GPU (Host Input)...\")\n", - "result_transfer = benchmark(estimate_device, args=(A_host, cfg), n_repeat=10)\n", - "t_gpu_transfer_ms = result_transfer.gpu_times.mean() * 1000\n", - "\n", - "# 3. GPU (pure device)\n", - "print(\"Timing GPU (Device Input)...\")\n", - "result_pure = benchmark(estimate_device, args=(A_device, cfg), n_repeat=10)\n", - "t_gpu_pure_ms = result_pure.gpu_times.mean() * 1000\n", + "print(\"Timing GPU...\")\n", + "result_gpu = benchmark(estimate_device, args=(A_host, cfg), n_repeat=10)\n", + "t_gpu_ms = result_gpu.gpu_times.mean() * 1000\n", "\n", "print(f\"\\n--- Average Compute Times ---\")\n", - "print(f\"CPU: {t_cpu_ms:.2f} ms\")\n", - "print(f\"GPU (with transfer): {t_gpu_transfer_ms:.2f} ms\")\n", - "print(f\"GPU (pure): {t_gpu_pure_ms:.2f} ms\")\n", + "print(f\"Power iteration (host) = {t_cpu_ms:.2f} ms\")\n", + "print(f\"Power iteration (device) = {t_gpu_ms:.2f} ms\")\n", "\n", - "speedup = t_cpu_ms / t_gpu_pure_ms\n", + "speedup = t_cpu_ms / t_gpu_ms\n", "print(f\"\\nSpeedup: {speedup:.1f}x\")" ] }, From 756d3c8b1c6378851f2d3c1b3a786abd263382c2 Mon Sep 17 00:00:00 2001 From: Bryce Adelstein Lelbach aka wash Date: Tue, 3 Mar 2026 15:17:04 -0500 Subject: [PATCH 13/18] =?UTF-8?q?Tutorials/Accelerated=20Python:=20Unify?= =?UTF-8?q?=20benchmark=20reporting=20to=20use=20milliseconds=20with=20mea?= =?UTF-8?q?n=20=C2=B1=20relative=20stdev=20format.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 05 Memory Spaces: Use cupyx.profiler.benchmark with mean/stdev/runs format. - 06 Asynchrony: Use time.perf_counter for single-run timing in ms. - 40/41 Kernel Authoring: Convert benchmark output from seconds to ms. - Rename timing variable from D to T across all notebooks. Made-with: Cursor --- .../05__memory_spaces__power_iteration.ipynb | 73 +- .../06__asynchrony__power_iteration.ipynb | 86 +- ...ry_spaces__power_iteration__SOLUTION.ipynb | 73 +- ...synchrony__power_iteration__SOLUTION.ipynb | 916 +++++++++--------- .../kernels/40__kernel_authoring__copy.ipynb | 70 +- ...41__kernel_authoring__book_histogram.ipynb | 146 +-- ...40__kernel_authoring__copy__SOLUTION.ipynb | 163 ++-- ..._authoring__book_histogram__SOLUTION.ipynb | 309 +++--- 8 files changed, 900 insertions(+), 936 deletions(-) diff --git a/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb index 2f41b270..fce50664 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb @@ -85,9 +85,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "import numpy as np\n", "import cupy as cp\n", @@ -103,7 +101,9 @@ " check_frequency: int = 10 # Check for convergence every N steps\n", " progress: bool = True # Print progress logs\n", " residual_threshold: float = 1e-10 # Stop if error is below this" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -116,9 +116,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "def generate_host(cfg=PowerIterationConfig()):\n", " \"\"\"Generates a random diagonalizable matrix on the CPU.\"\"\"\n", @@ -139,7 +137,9 @@ "A_host = generate_host()\n", "print(f\"Host Matrix Shape: {A_host.shape}\")\n", "print(f\"Data Type: {A_host.dtype}\")" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -152,9 +152,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "def estimate_host(A, cfg=PowerIterationConfig()):\n", " \"\"\"\n", @@ -200,7 +198,9 @@ "\n", "print(f\"\\nEstimated Eigenvalue (CPU): {lam_est_host}\")\n", "print(f\"Time taken: {end_time - start_time:.4f}s\")" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -224,9 +224,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "def estimate_device(A, cfg=PowerIterationConfig()):\n", " \"\"\"\n", @@ -297,7 +295,9 @@ "# Uncomment to test your implementation:\n", "# lam_test = estimate_device(A_host, PowerIterationConfig(max_steps=50))\n", "# print(f\"Your result: {lam_test}\")" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -324,9 +324,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "def generate_device(cfg=PowerIterationConfig()):\n", " \"\"\"\n", @@ -379,7 +377,9 @@ "# cp.cuda.Stream.null.synchronize()\n", "# end_time = time.time()\n", "# print(f\"Compute time: {end_time - start_time:.4f}s\")" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -392,14 +392,14 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "# NOTE: Run these after completing both exercises above\n", "# print(\"NumPy:\", A_host[0, :3])\n", "# print(\"CuPy:\", A_device[0, :3].get())" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -421,9 +421,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "print(\"Calculating Reference Eigenvalue (numpy.linalg)...\")\n", "lam_ref = np.linalg.eigvals(A_host).real.max()\n", @@ -445,7 +443,9 @@ "np.testing.assert_allclose(lam_est_host, lam_ref, rtol=1e-4)\n", "# Uncomment after completing exercises:\n", "# np.testing.assert_allclose(lam_est_device, lam_ref, rtol=1e-4)" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -460,9 +460,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "# Uncomment and run after completing both exercises above:\n", "# from cupyx.profiler import benchmark\n", @@ -470,20 +468,21 @@ "# cfg = PowerIterationConfig(progress=False)\n", "\n", "# print(\"Timing CPU...\")\n", - "# result_cpu = benchmark(estimate_host, args=(A_host, cfg), n_repeat=10)\n", - "# t_cpu_ms = result_cpu.cpu_times.mean() * 1000\n", + "# T_cpu = benchmark(estimate_host, args=(A_host, cfg), n_repeat=10).cpu_times\n", "\n", "# print(\"Timing GPU...\")\n", - "# result_gpu = benchmark(estimate_device, args=(A_host, cfg), n_repeat=10)\n", - "# t_gpu_ms = result_gpu.gpu_times.mean() * 1000\n", + "# T_gpu = benchmark(estimate_device, args=(A_host, cfg), n_repeat=10).gpu_times[0]\n", "\n", - "# print(f\"\\n--- Average Compute Times ---\")\n", - "# print(f\"Power iteration (host) = {t_cpu_ms:.2f} ms\")\n", - "# print(f\"Power iteration (device) = {t_gpu_ms:.2f} ms\")\n", + "# print()\n", + "# print(f\"Power iteration (host) = {T_cpu.mean() * 1000:.3g} ms ± {(T_cpu.std() / T_cpu.mean()):.2%} (mean ± relative stdev of {T_cpu.size} runs)\")\n", + "# print(f\"Power iteration (device) = {T_gpu.mean() * 1000:.3g} ms ± {(T_gpu.std() / T_gpu.mean()):.2%} (mean ± relative stdev of {T_gpu.size} runs)\")\n", "\n", - "# speedup = t_cpu_ms / t_gpu_ms\n", - "# print(f\"\\nSpeedup: {speedup:.1f}x\")" - ] + "# speedup = T_cpu.mean() / T_gpu.mean()\n", + "# print()\n", + "# print(f\"Speedup: {speedup:.1f}x\")" + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -506,9 +505,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "# Try different configurations here!\n", "# Example:\n", @@ -517,7 +514,9 @@ "# cfg_frequent_check = PowerIterationConfig(check_frequency=1, progress=True)\n", "\n", "# Your experiments:" - ] + ], + "execution_count": null, + "outputs": [] } ], "metadata": { @@ -529,4 +528,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file diff --git a/tutorials/accelerated-python/notebooks/fundamentals/06__asynchrony__power_iteration.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/06__asynchrony__power_iteration.ipynb index 54b98519..42ba0821 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/06__asynchrony__power_iteration.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/06__asynchrony__power_iteration.ipynb @@ -31,11 +31,9 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { "id": "rO4kOPuP_0JG" }, - "outputs": [], "source": [ "import os\n", "\n", @@ -46,7 +44,9 @@ " !pip install \"nvtx\" \"nsightful[notebook] @ git+https://github.com/brycelelbach/nsightful.git\" > /dev/null 2>&1\n", "\n", "print(\"Environment setup complete.\")" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -77,11 +77,9 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { "id": "sEicxhLO_9G9" }, - "outputs": [], "source": [ "%%writefile power_iteration__baseline.py\n", "\n", @@ -89,6 +87,7 @@ "import cupy as cp\n", "import cupyx as cpx\n", "import nvtx\n", + "import time\n", "from dataclasses import dataclass\n", "\n", "@dataclass\n", @@ -141,16 +140,17 @@ "\n", "# Warmup to ensure modules are loaded and code is JIT compiled before timing.\n", "estimate_device(A_device, cfg=PowerIterationConfig(max_steps=1, check_frequency=1, progress=False))\n", + "cp.cuda.get_current_stream().synchronize()\n", "\n", - "start = cp.cuda.get_current_stream().record()\n", + "start = time.perf_counter()\n", "lam_est_device = estimate_device(A_device).item()\n", - "stop = cp.cuda.get_current_stream().record()\n", - "\n", - "duration = cp.cuda.get_elapsed_time(start, stop) / 1e3\n", + "stop = time.perf_counter()\n", "\n", "print()\n", - "print(f\"GPU Execution Time: {duration:.3f} s\")" - ] + "print(f\"{(stop - start) * 1000:.3f} ms\")" + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -165,14 +165,14 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { "id": "1HU5p1IhAkTA" }, - "outputs": [], "source": [ "!nsys profile --cuda-event-trace=false --force-overwrite true -o power_iteration__baseline python power_iteration__baseline.py" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -189,17 +189,17 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { "id": "s6VVOnGQR3Ph" }, - "outputs": [], "source": [ "import nsightful\n", "\n", "!nsys export --type sqlite --quiet true --force-overwrite true power_iteration__baseline.nsys-rep\n", "nsightful.display_nsys_sqlite_file_in_notebook(\"power_iteration__baseline.sqlite\", title=\"Power Iteration - Baseline\")" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -269,9 +269,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "%%writefile power_iteration__async.py\n", "\n", @@ -279,6 +277,7 @@ "import cupy as cp\n", "import cupyx as cpx\n", "import nvtx\n", + "import time\n", "from dataclasses import dataclass\n", "\n", "@dataclass\n", @@ -306,16 +305,17 @@ "\n", "# Warmup to ensure modules are loaded and code is JIT compiled before timing.\n", "estimate_device(A_device, cfg=PowerIterationConfig(max_steps=1, check_frequency=1, progress=False))\n", + "cp.cuda.get_current_stream().synchronize()\n", "\n", - "start = cp.cuda.get_current_stream().record()\n", + "start = time.perf_counter()\n", "lam_est_device = estimate_device(A_device).item()\n", - "stop = cp.cuda.get_current_stream().record()\n", - "\n", - "duration = cp.cuda.get_elapsed_time(start, stop) / 1e3\n", + "stop = time.perf_counter()\n", "\n", "print()\n", - "print(f\"GPU Execution Time: {duration:.3f} s\")" - ] + "print(f\"{(stop - start) * 1000:.3f} ms\")" + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -326,14 +326,14 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { "id": "pszz-k8cDfqy" }, - "outputs": [], "source": [ "!python power_iteration__async.py" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -348,24 +348,22 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { "id": "uSPFNIb9KcPb" }, - "outputs": [], "source": [ "power_iteration_baseline_output = !python power_iteration__baseline.py\n", - "power_iteration_baseline_duration = float(power_iteration_baseline_output[-1].split()[-2])\n", + "power_iteration_baseline_duration = float(power_iteration_baseline_output[-1].split()[0])\n", "power_iteration_async_output = !python power_iteration__async.py\n", - "power_iteration_async_duration = float(power_iteration_async_output[-1].split()[-2])\n", + "power_iteration_async_duration = float(power_iteration_async_output[-1].split()[0])\n", "speedup = power_iteration_baseline_duration / power_iteration_async_duration\n", "\n", - "print(f\"GPU Execution Time\")\n", - "print()\n", - "print(f\"power_iteration_baseline: {power_iteration_baseline_duration:.3f} s\")\n", - "print(f\"power_iteration_async: {power_iteration_async_duration:.3f} s\")\n", + "print(f\"power_iteration_baseline: {power_iteration_baseline_output[-1]}\")\n", + "print(f\"power_iteration_async: {power_iteration_async_output[-1]}\")\n", "print(f\"power_iteration_async speedup over power_iteration_baseline: {speedup:.2f}\")" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -378,14 +376,14 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { "id": "BtQR4CHikWFK" }, - "outputs": [], "source": [ "!nsys profile --cuda-event-trace=false --capture-range=cudaProfilerApi --capture-range-end=stop --force-overwrite true -o power_iteration__async python power_iteration__async.py" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -398,15 +396,15 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { "id": "mWXBvi-hFGhU" }, - "outputs": [], "source": [ "!nsys export --type sqlite --quiet true --force-overwrite true power_iteration__async.nsys-rep\n", "nsightful.display_nsys_sqlite_file_in_notebook(\"power_iteration__async.sqlite\", title=\"Power Iteration - Async Event\")" - ] + ], + "execution_count": null, + "outputs": [] } ], "metadata": { @@ -423,4 +421,4 @@ }, "nbformat": 4, "nbformat_minor": 0 -} +} \ No newline at end of file diff --git a/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb index bec66079..76a164de 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb @@ -85,9 +85,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "import numpy as np\n", "import cupy as cp\n", @@ -103,7 +101,9 @@ " check_frequency: int = 10 # Check for convergence every N steps\n", " progress: bool = True # Print progress logs\n", " residual_threshold: float = 1e-10 # Stop if error is below this" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -116,9 +116,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "def generate_host(cfg=PowerIterationConfig()):\n", " \"\"\"Generates a random diagonalizable matrix on the CPU.\"\"\"\n", @@ -139,7 +137,9 @@ "A_host = generate_host()\n", "print(f\"Host Matrix Shape: {A_host.shape}\")\n", "print(f\"Data Type: {A_host.dtype}\")" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -152,9 +152,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "def estimate_host(A, cfg=PowerIterationConfig()):\n", " \"\"\"\n", @@ -200,7 +198,9 @@ "\n", "print(f\"\\nEstimated Eigenvalue (CPU): {lam_est_host}\")\n", "print(f\"Time taken: {end_time - start_time:.4f}s\")" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -224,9 +224,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "def estimate_device(A, cfg=PowerIterationConfig()):\n", " \"\"\"\n", @@ -296,7 +294,9 @@ "\n", "print(f\"\\nEstimated Eigenvalue (GPU): {lam_est_device}\")\n", "print(f\"Time taken: {end_time - start_time:.4f}s\")" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -323,9 +323,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "def generate_device(cfg=PowerIterationConfig()):\n", " \"\"\"\n", @@ -377,7 +375,9 @@ "cp.cuda.Stream.null.synchronize()\n", "end_time = time.time()\n", "print(f\"Compute time: {end_time - start_time:.4f}s\")" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -390,13 +390,13 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "print(\"NumPy:\", A_host[0, :3])\n", "print(\"CuPy:\", A_device[0, :3].get())" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -431,9 +431,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "print(\"Calculating Reference Eigenvalue (numpy.linalg)...\")\n", "lam_ref = np.linalg.eigvals(A_host).real.max()\n", @@ -451,7 +449,9 @@ "\n", "np.testing.assert_allclose(lam_est_host, lam_ref, rtol=1e-4)\n", "np.testing.assert_allclose(lam_est_device, lam_ref, rtol=1e-4)" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -466,29 +466,28 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "from cupyx.profiler import benchmark\n", "\n", "cfg = PowerIterationConfig(progress=False)\n", "\n", "print(\"Timing CPU...\")\n", - "result_cpu = benchmark(estimate_host, args=(A_host, cfg), n_repeat=10)\n", - "t_cpu_ms = result_cpu.cpu_times.mean() * 1000\n", + "T_cpu = benchmark(estimate_host, args=(A_host, cfg), n_repeat=10).cpu_times\n", "\n", "print(\"Timing GPU...\")\n", - "result_gpu = benchmark(estimate_device, args=(A_host, cfg), n_repeat=10)\n", - "t_gpu_ms = result_gpu.gpu_times.mean() * 1000\n", + "T_gpu = benchmark(estimate_device, args=(A_host, cfg), n_repeat=10).gpu_times[0]\n", "\n", - "print(f\"\\n--- Average Compute Times ---\")\n", - "print(f\"Power iteration (host) = {t_cpu_ms:.2f} ms\")\n", - "print(f\"Power iteration (device) = {t_gpu_ms:.2f} ms\")\n", + "print()\n", + "print(f\"Power iteration (host) = {T_cpu.mean() * 1000:.3g} ms ± {(T_cpu.std() / T_cpu.mean()):.2%} (mean ± relative stdev of {T_cpu.size} runs)\")\n", + "print(f\"Power iteration (device) = {T_gpu.mean() * 1000:.3g} ms ± {(T_gpu.std() / T_gpu.mean()):.2%} (mean ± relative stdev of {T_gpu.size} runs)\")\n", "\n", - "speedup = t_cpu_ms / t_gpu_ms\n", - "print(f\"\\nSpeedup: {speedup:.1f}x\")" - ] + "speedup = T_cpu.mean() / T_gpu.mean()\n", + "print()\n", + "print(f\"Speedup: {speedup:.1f}x\")" + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -511,9 +510,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "# Try different configurations here!\n", "# Example:\n", @@ -522,7 +519,9 @@ "# cfg_frequent_check = PowerIterationConfig(check_frequency=1, progress=True)\n", "\n", "# Your experiments:" - ] + ], + "execution_count": null, + "outputs": [] } ], "metadata": { @@ -534,4 +533,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file diff --git a/tutorials/accelerated-python/notebooks/fundamentals/solutions/06__asynchrony__power_iteration__SOLUTION.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/solutions/06__asynchrony__power_iteration__SOLUTION.ipynb index ecc2d62f..114416fd 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/solutions/06__asynchrony__power_iteration__SOLUTION.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/solutions/06__asynchrony__power_iteration__SOLUTION.ipynb @@ -1,460 +1,460 @@ { - "cells": [ - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "# Asynchrony and Power Iteration - SOLUTION\n", - "\n", - "## Table of Contents\n", - "1. [Introduction and Setup](#1-Introduction-and-Setup)\n", - " - [1.1 Environment Setup](#11-Environment-Setup)\n", - "2. [Theory: Streams and Synchronization](#2-Theory:-Streams-and-Synchronization)\n", - "3. [The Baseline Implementation](#3-The-Baseline-Implementation)\n", - "4. [Profiling the Baseline](#4-Profiling-the-Baseline)\n", - "5. [Better Visibility with NVTX](#5-Better-Visibility-with-NVTX)\n", - "6. [Implementing Asynchrony](#6-Implementing-Asynchrony)\n", - "7. [Performance Analysis](#7-Performance-Analysis)\n", - "\n", - "## 1. Introduction and Setup\n", - "\n", - "GPU programming is inherently asynchronous. In this exercise, we will explore the implications of this behavior when using CuPy and learn how to analyze the flow of execution using profiling tools.\n", - "\n", - "We will revisit the Power Iteration algorithm. Our goal is to take a standard implementation, profile it to identify bottlenecks caused by implicit synchronization, and then optimize it using CUDA streams and asynchronous memory transfers.\n", - "\n", - "### 1.1 Environment Setup\n", - "\n", - "First, we need to ensure the Nsight Systems profiler (nsys), Nsightful, and NVTX are installed and available." - ] + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Asynchrony and Power Iteration - SOLUTION\n", + "\n", + "## Table of Contents\n", + "1. [Introduction and Setup](#1-Introduction-and-Setup)\n", + " - [1.1 Environment Setup](#11-Environment-Setup)\n", + "2. [Theory: Streams and Synchronization](#2-Theory:-Streams-and-Synchronization)\n", + "3. [The Baseline Implementation](#3-The-Baseline-Implementation)\n", + "4. [Profiling the Baseline](#4-Profiling-the-Baseline)\n", + "5. [Better Visibility with NVTX](#5-Better-Visibility-with-NVTX)\n", + "6. [Implementing Asynchrony](#6-Implementing-Asynchrony)\n", + "7. [Performance Analysis](#7-Performance-Analysis)\n", + "\n", + "## 1. Introduction and Setup\n", + "\n", + "GPU programming is inherently asynchronous. In this exercise, we will explore the implications of this behavior when using CuPy and learn how to analyze the flow of execution using profiling tools.\n", + "\n", + "We will revisit the Power Iteration algorithm. Our goal is to take a standard implementation, profile it to identify bottlenecks caused by implicit synchronization, and then optimize it using CUDA streams and asynchronous memory transfers.\n", + "\n", + "### 1.1 Environment Setup\n", + "\n", + "First, we need to ensure the Nsight Systems profiler (nsys), Nsightful, and NVTX are installed and available." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "import os\n", + "\n", + "# Install necessary tools if running in Google Colab\n", + "if os.getenv(\"COLAB_RELEASE_TAG\"):\n", + " !curl -s -L -O https://developer.nvidia.com/downloads/assets/tools/secure/nsight-systems/2025_3/NsightSystems-linux-cli-public-2025.3.1.90-3582212.deb\n", + " !sudo dpkg -i NsightSystems-linux-cli-public-2025.3.1.90-3582212.deb > /dev/null\n", + " !pip install \"nvtx\" \"nsightful[notebook] @ git+https://github.com/brycelelbach/nsightful.git\" > /dev/null 2>&1\n", + "\n", + "print(\"Environment setup complete.\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 2. Theory: Streams and Synchronization\n", + "\n", + "All GPU work is launched asynchronously on a stream. The work items in a stream are executed in order. If you launch `f` on a stream and later launch `g` on that same stream, then `f` will be executed before `g`. But if `f` and `g` are launched on different streams, then their execution might overlap.\n", + "\n", + "**How CuPy handles this:**\n", + "\n", + "- **Default Stream:** Unless specified, CuPy launches work on the default CUDA stream.\n", + "\n", + "- **Sequential Device Execution:** By default, CuPy work executes sequentially on the GPU.\n", + "\n", + "- **Asynchronous Host Execution:** From the Python (Host) perspective, the code often returns immediately after launching the GPU kernel, before the work is actually finished.\n", + "\n", + "**SOLUTION:** Certain operations force the CPU to wait for the GPU to finish (implicit synchronization):\n", + "- Accessing element values from device arrays (e.g., `x[0]`, `.item()`)\n", + "- Printing device array values\n", + "- Device-to-host memory transfers with `cp.asnumpy()` (by default)\n", + "- Explicit synchronization calls\n", + "\n", + "## 3. The Baseline Implementation\n", + "\n", + "We will start with a baseline implementation of the Power Iteration algorithm.\n", + "\n", + "**Note:** The cell below writes the code to a file named `power_iteration__baseline.py`. We do this because we must run the code through the Nsight Systems profiler via the command line.\n", + "\n", + "**SOLUTION:** The baseline code below already includes NVTX annotations and `cpx.profiler.profile()` to demonstrate the proper profiling setup." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "%%writefile power_iteration__baseline.py\n", + "\n", + "import numpy as np\n", + "import cupy as cp\n", + "import cupyx as cpx\n", + "import nvtx\n", + "import time\n", + "from dataclasses import dataclass\n", + "\n", + "@dataclass\n", + "class PowerIterationConfig:\n", + " dim: int = 8192\n", + " dominance: float = 0.05\n", + " max_steps: int = 1000\n", + " check_frequency: int = 10\n", + " progress: bool = True\n", + " residual_threshold: float = 1e-10\n", + "\n", + "def generate_device(cfg=PowerIterationConfig()):\n", + " cp.random.seed(42)\n", + " weak_lam = cp.random.random(cfg.dim - 1) * (1.0 - cfg.dominance)\n", + " lam = cp.random.permutation(cp.concatenate((cp.asarray([1.0]), weak_lam)))\n", + " P = cp.random.random((cfg.dim, cfg.dim))\n", + " D = cp.diag(cp.random.permutation(lam))\n", + " A = ((P @ D) @ cp.linalg.inv(P))\n", + " return A\n", + "\n", + "def estimate_device(A, cfg=PowerIterationConfig()):\n", + " with nvtx.annotate(\"Setup\"):\n", + " A_gpu = cp.asarray(A) # If `A` is on the host, copy from host to device.\n", + " # Otherwise, does nothing.\n", + "\n", + " x = cp.ones(A_gpu.shape[0], dtype=np.float64)\n", + "\n", + " with nvtx.annotate(\"Loop\"):\n", + " for i in range(0, cfg.max_steps, cfg.check_frequency):\n", + " with nvtx.annotate(f\"Step {i} to {i + cfg.check_frequency}\"):\n", + " with nvtx.annotate(f\"Compute & Residual {i}\"):\n", + " y = A_gpu @ x\n", + " lam = (x @ y) / (x @ x) # Rayleigh quotient.\n", + " res = cp.linalg.norm(y - lam * x)\n", + " x = y / cp.linalg.norm(y) # Normalize for next step.\n", + "\n", + " with nvtx.annotate(f\"Copy {i}\"):\n", + " res_host = cp.asnumpy(res)\n", + " x_host = cp.asnumpy(x)\n", + "\n", + " with nvtx.annotate(f\"I/O {i}\"):\n", + " if cfg.progress:\n", + " print(f\"step {i}: residual = {res_host:.3e}\")\n", + "\n", + " np.savetxt(f\"device_{i}.txt\", x_host) # Copy from device to host and\n", + " # save a checkpoint.\n", + "\n", + " if res_host < cfg.residual_threshold:\n", + " break\n", + "\n", + " with nvtx.annotate(f\"Compute {i}\"):\n", + " for _ in range(cfg.check_frequency - 1):\n", + " y = A_gpu @ x # We have to use `A_gpu` here as well.\n", + " x = y / cp.linalg.norm(y) # Normalize for next step.\n", + "\n", + " return cp.asnumpy((x.T @ (A_gpu @ x)) / (x.T @ x)) # Copy from device to host.\n", + "\n", + "A_device = generate_device()\n", + "\n", + "# Warmup to ensure modules are loaded and code is JIT compiled before profiling.\n", + "estimate_device(A_device, cfg=PowerIterationConfig(max_steps=1, check_frequency=1, progress=False))\n", + "\n", + "with cpx.profiler.profile():\n", + " lam_est_device = estimate_device(A_device).item()\n", + "cp.cuda.get_current_stream().synchronize()\n", + "\n", + "start = time.perf_counter()\n", + "estimate_device(A_device, cfg=PowerIterationConfig(progress=False)).item()\n", + "stop = time.perf_counter()\n", + "\n", + "print()\n", + "print(f\"{(stop - start) * 1000:.3f} ms\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 4. Profiling the Baseline\n", + "\n", + "Now let's profile our code by running it under the Nsight Systems `nsys` tool. The syntax for this is `nsys `. It will run your program while collecting a birdseye view of everything going on in your program." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "!nsys profile --cuda-event-trace=false --capture-range=cudaProfilerApi --capture-range-end=stop --force-overwrite true -o power_iteration__baseline python power_iteration__baseline.py" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let's view our report and explore what's going on in our program.\n", + "\n", + "Run the next cell, which will generate the report and create a button that when clicked will open it up in Perfetto, a web-based no-install visual profiler.\n", + "\n", + "**EXTRA CREDIT:** Download the Nsight Systems GUI and open the report in it to see even more information." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "import nsightful\n", + "\n", + "!nsys export --type sqlite --quiet true --force-overwrite true power_iteration__baseline.nsys-rep\n", + "nsightful.display_nsys_sqlite_file_in_notebook(\"power_iteration__baseline.sqlite\", title=\"Power Iteration - Baseline\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 5. Better Visibility with NVTX\n", + "\n", + "Nsight Systems shows us a lot of information - sometimes it's too much and not all relevant.\n", + "\n", + "There's two ways that we can filter and annotate what we see in Nsight systems.\n", + "\n", + "The first is to limit when we start and stop profiling in the program. In Python, we can do this with `cupyx.profiler.profile()`, which give us a Python context manager. Any CUDA code used during scope will be included in the profile.\n", + "\n", + "```\n", + "not_in_the_profile():\n", + "with cpx.profiler.profile():\n", + " in_the_profile()\n", + "not_in_the_profile()\n", + "```\n", + "\n", + "For this to work, we have to pass `--capture-range=cudaProfilerApi --capture-range-end=stop` as flags to `nsys`.\n", + "\n", + "We can also annotate specific regions of our code, which will show up in the profiler. We can even add categories, domains, and colors to these regions, and they can be nested. To add these annotations, we use `nvtx.annnotate()`, another Python context manager, this time from a library called NVTX.\n", + "\n", + "```\n", + "with nvtx.annotate(\"Loop\"):\n", + " for i in range(20):\n", + " with nvtx.annotate(f\"Step {i}\"):\n", + " pass\n", + "```\n", + "\n", + "**SOLUTION:** The baseline code above already includes:\n", + "\n", + "- `nvtx.annotate()` regions for \"Setup\", \"Loop\", \"Step\", \"Compute & Residual\", \"Copy\", \"I/O\", and \"Compute\" phases.\n", + "\n", + "- A `cpx.profiler.profile()` around the timing section.\n", + "\n", + "- The `nsys` command includes `--capture-range=cudaProfilerApi --capture-range-end=stop` flags.\n", + "\n", + "From our profile trace, we can see that both our CPU and GPU are idly waiting for each other! Device code is idle during every I/O step when we print the residual and write the checkpoint, and host code spends a long time synchronizing on `cudaMemcpyAsync`.\n", + "\n", + "Here's what happens at the start of each I/O step:\n", + "\n", + "- We copy from device to host, which synchronizes with any outstanding work on the device. This blocks the host for awhile.\n", + "- After that synchronous transfer has completed, we begin the I/O (printing and writing the checkpoint). During this time, the device is idle.\n", + "- After the I/O has completed on the host, we start launching the next set of iterations.\n", + "\n", + "This is inefficient; we can do better by overlapping compute and I/O:\n", + "\n", + "- First, host code asynchronously initiate our device to host copies.\n", + "- Then, host code asynchronously launch the next set of compute steps on the device.\n", + "- Next, host code synchronize with the asynchronous copies we started.\n", + "- Finally, the host performs the I/O while the device performs the next set of compute steps.\n", + "\n", + "Everything is still going to run on one stream, but we want to be able to synchronize with just the I/O, which is launched on the stream before the compute work. We'll use a CUDA event, which we will record on the stream right after the copy. Then, we can synchronize with the event later, waiting for the I/O but not the compute!" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 6. Implementing Asynchrony\n", + "\n", + "Remember what we've learned about streams and how to use them with CuPy:\n", + "\n", + "- By default, all CuPy operations within a single thread run on the same stream. You can access this stream with `cp.cuda.get_current_stream()`.\n", + "\n", + "- You can create a new stream with `cp.cuda.Stream(non_blocking=True)`. Use `with` statements to use the stream for all CuPy operations within a block.\n", + "\n", + "- You can record an event on a stream by calling `.record()` on it.\n", + "\n", + "- You can synchronize on an event (or an entire stream) by calling `.synchronize()` on it.\n", + "\n", + "- Memory transfers will block by default. You can launch them asynchronously with `cp.asarray(..., blocking=False)` (for host to device transfers) and `cp.asnumpy(..., blocking=False)` (for device to host transfers).\n", + "\n", + "**SOLUTION:** The implementation below uses asynchronous memory transfers and CUDA events to overlap compute and I/O operations." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "%%writefile power_iteration__async.py\n", + "\n", + "import numpy as np\n", + "import cupy as cp\n", + "import cupyx as cpx\n", + "import nvtx\n", + "import time\n", + "from dataclasses import dataclass\n", + "\n", + "@dataclass\n", + "class PowerIterationConfig:\n", + " dim: int = 8192\n", + " dominance: float = 0.05\n", + " max_steps: int = 1000\n", + " check_frequency: int = 10\n", + " progress: bool = True\n", + " residual_threshold: float = 1e-10\n", + "\n", + "def generate_device(cfg=PowerIterationConfig()):\n", + " cp.random.seed(42)\n", + " weak_lam = cp.random.random(cfg.dim - 1) * (1.0 - cfg.dominance)\n", + " lam = cp.random.permutation(cp.concatenate((cp.asarray([1.0]), weak_lam)))\n", + " P = cp.random.random((cfg.dim, cfg.dim))\n", + " D = cp.diag(cp.random.permutation(lam))\n", + " A = ((P @ D) @ cp.linalg.inv(P))\n", + " return A\n", + "\n", + "def estimate_device(A, cfg=PowerIterationConfig()):\n", + " with nvtx.annotate(\"Setup\"):\n", + " A_gpu = cp.asarray(A) # If `A` is on the host, copy from host to device.\n", + " # Otherwise, does nothing.\n", + "\n", + " x = cp.ones(A_gpu.shape[0], dtype=np.float64)\n", + "\n", + " with nvtx.annotate(\"Loop\"):\n", + " for i in range(0, cfg.max_steps, cfg.check_frequency):\n", + " with nvtx.annotate(f\"Step {i} to {i + cfg.check_frequency}\"):\n", + " with nvtx.annotate(f\"Compute & Residual {i}\"):\n", + " y = A_gpu @ x\n", + " lam = (x @ y) / (x @ x) # Rayleigh quotient.\n", + " res = cp.linalg.norm(y - lam * x)\n", + " x = y / cp.linalg.norm(y) # Normalize for next step.\n", + "\n", + " with nvtx.annotate(f\"Copy {i}\"):\n", + " res_host = cp.asnumpy(res, blocking=False)\n", + " x_host = cp.asnumpy(x, blocking=False)\n", + " copy_event = cp.cuda.get_current_stream().record()\n", + "\n", + " with nvtx.annotate(f\"Compute {i}\"):\n", + " for _ in range(cfg.check_frequency - 1):\n", + " y = A_gpu @ x # We have to use `A_gpu` here as well.\n", + " x = y / cp.linalg.norm(y) # Normalize for next step.\n", + "\n", + " with nvtx.annotate(f\"I/O {i}\", payload=i):\n", + " copy_event.synchronize() # Wait for the copies to complete.\n", + "\n", + " if cfg.progress:\n", + " print(f\"step {i}: residual = {res_host:.3e}\")\n", + "\n", + " np.savetxt(f\"device_{i}.txt\", x_host) # Save a checkpoint.\n", + "\n", + " if res_host < cfg.residual_threshold:\n", + " break\n", + "\n", + " return cp.asnumpy((x.T @ (A_gpu @ x)) / (x.T @ x)) # Copy from device to host.\n", + "\n", + "A_device = generate_device()\n", + "\n", + "# Warmup to ensure modules are loaded and code is JIT compiled before profiling.\n", + "estimate_device(A_device, cfg=PowerIterationConfig(max_steps=1, check_frequency=1, progress=False))\n", + "cp.cuda.get_current_stream().synchronize()\n", + "\n", + "with cpx.profiler.profile():\n", + " start = time.perf_counter()\n", + " lam_est_device = estimate_device(A_device).item()\n", + " stop = time.perf_counter()\n", + "\n", + "print()\n", + "print(f\"{(stop - start) * 1000:.3f} ms\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Now let's make sure it works:" + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "!python power_iteration__async.py" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## 7. Performance Analysis\n", + "\n", + "Before we profile the improved code, let's compare the execution times of both." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "power_iteration_baseline_output = !python power_iteration__baseline.py\n", + "power_iteration_baseline_duration = float(power_iteration_baseline_output[-1].split()[0])\n", + "power_iteration_async_output = !python power_iteration__async.py\n", + "power_iteration_async_duration = float(power_iteration_async_output[-1].split()[0])\n", + "speedup = power_iteration_baseline_duration / power_iteration_async_duration\n", + "\n", + "print(f\"power_iteration_baseline: {power_iteration_baseline_output[-1]}\")\n", + "print(f\"power_iteration_async: {power_iteration_async_output[-1]}\")\n", + "print(f\"power_iteration_async speedup over power_iteration_baseline: {speedup:.2f}\")" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Next, let's capture a profile report of our improved code." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "!nsys profile --cuda-event-trace=false --capture-range=cudaProfilerApi --capture-range-end=stop --force-overwrite true -o power_iteration__async python power_iteration__async.py" + ], + "execution_count": null, + "outputs": [] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Finally, let's look at the profile in Perfetto and confirm we've gotten rid of the idling." + ] + }, + { + "cell_type": "code", + "metadata": {}, + "source": [ + "!nsys export --type sqlite --quiet true --force-overwrite true power_iteration__async.nsys-rep\n", + "nsightful.display_nsys_sqlite_file_in_notebook(\"power_iteration__async.sqlite\", title=\"Power Iteration - Async\")" + ], + "execution_count": null, + "outputs": [] + } + ], + "metadata": { + "accelerator": "GPU", + "colab": { + "gpuType": "T4", + "provenance": [] + }, + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" + } }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import os\n", - "\n", - "# Install necessary tools if running in Google Colab\n", - "if os.getenv(\"COLAB_RELEASE_TAG\"):\n", - " !curl -s -L -O https://developer.nvidia.com/downloads/assets/tools/secure/nsight-systems/2025_3/NsightSystems-linux-cli-public-2025.3.1.90-3582212.deb\n", - " !sudo dpkg -i NsightSystems-linux-cli-public-2025.3.1.90-3582212.deb > /dev/null\n", - " !pip install \"nvtx\" \"nsightful[notebook] @ git+https://github.com/brycelelbach/nsightful.git\" > /dev/null 2>&1\n", - "\n", - "print(\"Environment setup complete.\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 2. Theory: Streams and Synchronization\n", - "\n", - "All GPU work is launched asynchronously on a stream. The work items in a stream are executed in order. If you launch `f` on a stream and later launch `g` on that same stream, then `f` will be executed before `g`. But if `f` and `g` are launched on different streams, then their execution might overlap.\n", - "\n", - "**How CuPy handles this:**\n", - "\n", - "- **Default Stream:** Unless specified, CuPy launches work on the default CUDA stream.\n", - "\n", - "- **Sequential Device Execution:** By default, CuPy work executes sequentially on the GPU.\n", - "\n", - "- **Asynchronous Host Execution:** From the Python (Host) perspective, the code often returns immediately after launching the GPU kernel, before the work is actually finished.\n", - "\n", - "**SOLUTION:** Certain operations force the CPU to wait for the GPU to finish (implicit synchronization):\n", - "- Accessing element values from device arrays (e.g., `x[0]`, `.item()`)\n", - "- Printing device array values\n", - "- Device-to-host memory transfers with `cp.asnumpy()` (by default)\n", - "- Explicit synchronization calls\n", - "\n", - "## 3. The Baseline Implementation\n", - "\n", - "We will start with a baseline implementation of the Power Iteration algorithm.\n", - "\n", - "**Note:** The cell below writes the code to a file named `power_iteration__baseline.py`. We do this because we must run the code through the Nsight Systems profiler via the command line.\n", - "\n", - "**SOLUTION:** The baseline code below already includes NVTX annotations and `cpx.profiler.profile()` to demonstrate the proper profiling setup." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%writefile power_iteration__baseline.py\n", - "\n", - "import numpy as np\n", - "import cupy as cp\n", - "import cupyx as cpx\n", - "import nvtx\n", - "from dataclasses import dataclass\n", - "\n", - "@dataclass\n", - "class PowerIterationConfig:\n", - " dim: int = 8192\n", - " dominance: float = 0.05\n", - " max_steps: int = 1000\n", - " check_frequency: int = 10\n", - " progress: bool = True\n", - " residual_threshold: float = 1e-10\n", - "\n", - "def generate_device(cfg=PowerIterationConfig()):\n", - " cp.random.seed(42)\n", - " weak_lam = cp.random.random(cfg.dim - 1) * (1.0 - cfg.dominance)\n", - " lam = cp.random.permutation(cp.concatenate((cp.asarray([1.0]), weak_lam)))\n", - " P = cp.random.random((cfg.dim, cfg.dim))\n", - " D = cp.diag(cp.random.permutation(lam))\n", - " A = ((P @ D) @ cp.linalg.inv(P))\n", - " return A\n", - "\n", - "def estimate_device(A, cfg=PowerIterationConfig()):\n", - " with nvtx.annotate(\"Setup\"):\n", - " A_gpu = cp.asarray(A) # If `A` is on the host, copy from host to device.\n", - " # Otherwise, does nothing.\n", - "\n", - " x = cp.ones(A_gpu.shape[0], dtype=np.float64)\n", - "\n", - " with nvtx.annotate(\"Loop\"):\n", - " for i in range(0, cfg.max_steps, cfg.check_frequency):\n", - " with nvtx.annotate(f\"Step {i} to {i + cfg.check_frequency}\"):\n", - " with nvtx.annotate(f\"Compute & Residual {i}\"):\n", - " y = A_gpu @ x\n", - " lam = (x @ y) / (x @ x) # Rayleigh quotient.\n", - " res = cp.linalg.norm(y - lam * x)\n", - " x = y / cp.linalg.norm(y) # Normalize for next step.\n", - "\n", - " with nvtx.annotate(f\"Copy {i}\"):\n", - " res_host = cp.asnumpy(res)\n", - " x_host = cp.asnumpy(x)\n", - "\n", - " with nvtx.annotate(f\"I/O {i}\"):\n", - " if cfg.progress:\n", - " print(f\"step {i}: residual = {res_host:.3e}\")\n", - "\n", - " np.savetxt(f\"device_{i}.txt\", x_host) # Copy from device to host and\n", - " # save a checkpoint.\n", - "\n", - " if res_host < cfg.residual_threshold:\n", - " break\n", - "\n", - " with nvtx.annotate(f\"Compute {i}\"):\n", - " for _ in range(cfg.check_frequency - 1):\n", - " y = A_gpu @ x # We have to use `A_gpu` here as well.\n", - " x = y / cp.linalg.norm(y) # Normalize for next step.\n", - "\n", - " return cp.asnumpy((x.T @ (A_gpu @ x)) / (x.T @ x)) # Copy from device to host.\n", - "\n", - "A_device = generate_device()\n", - "\n", - "# Warmup to ensure modules are loaded and code is JIT compiled before timing.\n", - "estimate_device(A_device, cfg=PowerIterationConfig(max_steps=1, check_frequency=1, progress=False))\n", - "\n", - "with cpx.profiler.profile():\n", - " start = cp.cuda.get_current_stream().record()\n", - " lam_est_device = estimate_device(A_device).item()\n", - " stop = cp.cuda.get_current_stream().record()\n", - "\n", - "duration = cp.cuda.get_elapsed_time(start, stop) / 1e3\n", - "\n", - "print()\n", - "print(f\"GPU Execution Time: {duration:.3f} s\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 4. Profiling the Baseline\n", - "\n", - "Now let's profile our code by running it under the Nsight Systems `nsys` tool. The syntax for this is `nsys `. It will run your program while collecting a birdseye view of everything going on in your program." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!nsys profile --cuda-event-trace=false --capture-range=cudaProfilerApi --capture-range-end=stop --force-overwrite true -o power_iteration__baseline python power_iteration__baseline.py" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now let's view our report and explore what's going on in our program.\n", - "\n", - "Run the next cell, which will generate the report and create a button that when clicked will open it up in Perfetto, a web-based no-install visual profiler.\n", - "\n", - "**EXTRA CREDIT:** Download the Nsight Systems GUI and open the report in it to see even more information." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "import nsightful\n", - "\n", - "!nsys export --type sqlite --quiet true --force-overwrite true power_iteration__baseline.nsys-rep\n", - "nsightful.display_nsys_sqlite_file_in_notebook(\"power_iteration__baseline.sqlite\", title=\"Power Iteration - Baseline\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 5. Better Visibility with NVTX\n", - "\n", - "Nsight Systems shows us a lot of information - sometimes it's too much and not all relevant.\n", - "\n", - "There's two ways that we can filter and annotate what we see in Nsight systems.\n", - "\n", - "The first is to limit when we start and stop profiling in the program. In Python, we can do this with `cupyx.profiler.profile()`, which give us a Python context manager. Any CUDA code used during scope will be included in the profile.\n", - "\n", - "```\n", - "not_in_the_profile():\n", - "with cpx.profiler.profile():\n", - " in_the_profile()\n", - "not_in_the_profile()\n", - "```\n", - "\n", - "For this to work, we have to pass `--capture-range=cudaProfilerApi --capture-range-end=stop` as flags to `nsys`.\n", - "\n", - "We can also annotate specific regions of our code, which will show up in the profiler. We can even add categories, domains, and colors to these regions, and they can be nested. To add these annotations, we use `nvtx.annnotate()`, another Python context manager, this time from a library called NVTX.\n", - "\n", - "```\n", - "with nvtx.annotate(\"Loop\"):\n", - " for i in range(20):\n", - " with nvtx.annotate(f\"Step {i}\"):\n", - " pass\n", - "```\n", - "\n", - "**SOLUTION:** The baseline code above already includes:\n", - "\n", - "- `nvtx.annotate()` regions for \"Setup\", \"Loop\", \"Step\", \"Compute & Residual\", \"Copy\", \"I/O\", and \"Compute\" phases.\n", - "\n", - "- A `cpx.profiler.profile()` around the timing section.\n", - "\n", - "- The `nsys` command includes `--capture-range=cudaProfilerApi --capture-range-end=stop` flags.\n", - "\n", - "From our profile trace, we can see that both our CPU and GPU are idly waiting for each other! Device code is idle during every I/O step when we print the residual and write the checkpoint, and host code spends a long time synchronizing on `cudaMemcpyAsync`.\n", - "\n", - "Here's what happens at the start of each I/O step:\n", - "\n", - "- We copy from device to host, which synchronizes with any outstanding work on the device. This blocks the host for awhile.\n", - "- After that synchronous transfer has completed, we begin the I/O (printing and writing the checkpoint). During this time, the device is idle.\n", - "- After the I/O has completed on the host, we start launching the next set of iterations.\n", - "\n", - "This is inefficient; we can do better by overlapping compute and I/O:\n", - "\n", - "- First, host code asynchronously initiate our device to host copies.\n", - "- Then, host code asynchronously launch the next set of compute steps on the device.\n", - "- Next, host code synchronize with the asynchronous copies we started.\n", - "- Finally, the host performs the I/O while the device performs the next set of compute steps.\n", - "\n", - "Everything is still going to run on one stream, but we want to be able to synchronize with just the I/O, which is launched on the stream before the compute work. We'll use a CUDA event, which we will record on the stream right after the copy. Then, we can synchronize with the event later, waiting for the I/O but not the compute!" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 6. Implementing Asynchrony\n", - "\n", - "Remember what we've learned about streams and how to use them with CuPy:\n", - "\n", - "- By default, all CuPy operations within a single thread run on the same stream. You can access this stream with `cp.cuda.get_current_stream()`.\n", - "\n", - "- You can create a new stream with `cp.cuda.Stream(non_blocking=True)`. Use `with` statements to use the stream for all CuPy operations within a block.\n", - "\n", - "- You can record an event on a stream by calling `.record()` on it.\n", - "\n", - "- You can synchronize on an event (or an entire stream) by calling `.synchronize()` on it.\n", - "\n", - "- Memory transfers will block by default. You can launch them asynchronously with `cp.asarray(..., blocking=False)` (for host to device transfers) and `cp.asnumpy(..., blocking=False)` (for device to host transfers).\n", - "\n", - "**SOLUTION:** The implementation below uses asynchronous memory transfers and CUDA events to overlap compute and I/O operations." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%%writefile power_iteration__async.py\n", - "\n", - "import numpy as np\n", - "import cupy as cp\n", - "import cupyx as cpx\n", - "import nvtx\n", - "from dataclasses import dataclass\n", - "\n", - "@dataclass\n", - "class PowerIterationConfig:\n", - " dim: int = 8192\n", - " dominance: float = 0.05\n", - " max_steps: int = 1000\n", - " check_frequency: int = 10\n", - " progress: bool = True\n", - " residual_threshold: float = 1e-10\n", - "\n", - "def generate_device(cfg=PowerIterationConfig()):\n", - " cp.random.seed(42)\n", - " weak_lam = cp.random.random(cfg.dim - 1) * (1.0 - cfg.dominance)\n", - " lam = cp.random.permutation(cp.concatenate((cp.asarray([1.0]), weak_lam)))\n", - " P = cp.random.random((cfg.dim, cfg.dim))\n", - " D = cp.diag(cp.random.permutation(lam))\n", - " A = ((P @ D) @ cp.linalg.inv(P))\n", - " return A\n", - "\n", - "def estimate_device(A, cfg=PowerIterationConfig()):\n", - " with nvtx.annotate(\"Setup\"):\n", - " A_gpu = cp.asarray(A) # If `A` is on the host, copy from host to device.\n", - " # Otherwise, does nothing.\n", - "\n", - " x = cp.ones(A_gpu.shape[0], dtype=np.float64)\n", - "\n", - " with nvtx.annotate(\"Loop\"):\n", - " for i in range(0, cfg.max_steps, cfg.check_frequency):\n", - " with nvtx.annotate(f\"Step {i} to {i + cfg.check_frequency}\"):\n", - " with nvtx.annotate(f\"Compute & Residual {i}\"):\n", - " y = A_gpu @ x\n", - " lam = (x @ y) / (x @ x) # Rayleigh quotient.\n", - " res = cp.linalg.norm(y - lam * x)\n", - " x = y / cp.linalg.norm(y) # Normalize for next step.\n", - "\n", - " with nvtx.annotate(f\"Copy {i}\"):\n", - " res_host = cp.asnumpy(res, blocking=False)\n", - " x_host = cp.asnumpy(x, blocking=False)\n", - " copy_event = cp.cuda.get_current_stream().record()\n", - "\n", - " with nvtx.annotate(f\"Compute {i}\"):\n", - " for _ in range(cfg.check_frequency - 1):\n", - " y = A_gpu @ x # We have to use `A_gpu` here as well.\n", - " x = y / cp.linalg.norm(y) # Normalize for next step.\n", - "\n", - " with nvtx.annotate(f\"I/O {i}\", payload=i):\n", - " copy_event.synchronize() # Wait for the copies to complete.\n", - "\n", - " if cfg.progress:\n", - " print(f\"step {i}: residual = {res_host:.3e}\")\n", - "\n", - " np.savetxt(f\"device_{i}.txt\", x_host) # Save a checkpoint.\n", - "\n", - " if res_host < cfg.residual_threshold:\n", - " break\n", - "\n", - " return cp.asnumpy((x.T @ (A_gpu @ x)) / (x.T @ x)) # Copy from device to host.\n", - "\n", - "A_device = generate_device()\n", - "\n", - "# Warmup to ensure modules are loaded and code is JIT compiled before timing.\n", - "estimate_device(A_device, cfg=PowerIterationConfig(max_steps=1, check_frequency=1, progress=False))\n", - "\n", - "with cpx.profiler.profile():\n", - " start = cp.cuda.get_current_stream().record()\n", - " lam_est_device = estimate_device(A_device).item()\n", - " stop = cp.cuda.get_current_stream().record()\n", - "\n", - "duration = cp.cuda.get_elapsed_time(start, stop) / 1e3\n", - "\n", - "print()\n", - "print(f\"GPU Execution Time: {duration:.3f} s\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Now let's make sure it works:" - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!python power_iteration__async.py" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "## 7. Performance Analysis\n", - "\n", - "Before we profile the improved code, let's compare the execution times of both." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "power_iteration_baseline_output = !python power_iteration__baseline.py\n", - "power_iteration_baseline_duration = float(power_iteration_baseline_output[-1].split()[-2])\n", - "power_iteration_async_output = !python power_iteration__async.py\n", - "power_iteration_async_duration = float(power_iteration_async_output[-1].split()[-2])\n", - "speedup = power_iteration_baseline_duration / power_iteration_async_duration\n", - "\n", - "print(f\"GPU Execution Time\")\n", - "print()\n", - "print(f\"power_iteration_baseline: {power_iteration_baseline_duration:.3f} s\")\n", - "print(f\"power_iteration_async: {power_iteration_async_duration:.3f} s\")\n", - "print(f\"power_iteration_async speedup over power_iteration_baseline: {speedup:.2f}\")" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Next, let's capture a profile report of our improved code." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!nsys profile --cuda-event-trace=false --capture-range=cudaProfilerApi --capture-range-end=stop --force-overwrite true -o power_iteration__async python power_iteration__async.py" - ] - }, - { - "cell_type": "markdown", - "metadata": {}, - "source": [ - "Finally, let's look at the profile in Perfetto and confirm we've gotten rid of the idling." - ] - }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "!nsys export --type sqlite --quiet true --force-overwrite true power_iteration__async.nsys-rep\n", - "nsightful.display_nsys_sqlite_file_in_notebook(\"power_iteration__async.sqlite\", title=\"Power Iteration - Async\")" - ] - } - ], - "metadata": { - "accelerator": "GPU", - "colab": { - "gpuType": "T4", - "provenance": [] - }, - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - } - }, - "nbformat": 4, - "nbformat_minor": 0 -} + "nbformat": 4, + "nbformat_minor": 0 +} \ No newline at end of file diff --git a/tutorials/accelerated-python/notebooks/kernels/40__kernel_authoring__copy.ipynb b/tutorials/accelerated-python/notebooks/kernels/40__kernel_authoring__copy.ipynb index b498898b..8807f822 100644 --- a/tutorials/accelerated-python/notebooks/kernels/40__kernel_authoring__copy.ipynb +++ b/tutorials/accelerated-python/notebooks/kernels/40__kernel_authoring__copy.ipynb @@ -28,12 +28,10 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { "collapsed": true, "id": "AoHkvSPMC5Fs" }, - "outputs": [], "source": [ "import os\n", "\n", @@ -47,7 +45,9 @@ " !pip uninstall \"cuda-python\" --yes > /dev/null\n", " !pip install \"numba-cuda\" \"cuda-cccl[test-cu12]\" \"nvtx\" \"nsightful[notebook] @ git+https://github.com/brycelelbach/nsightful.git\" > /dev/null 2>&1\n", " open(\"/accelerated-computing-hub-installed\", \"a\").close()" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -68,11 +68,9 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { "id": "I9Tz2hG-_tBj" }, - "outputs": [], "source": [ "%%writefile copy_blocked.py\n", "\n", @@ -109,9 +107,11 @@ " launch(check=False) # `ncu` slows things down; so just launch once when running under it.\n", "else:\n", " launch(check=True)\n", - " D = cpx.profiler.benchmark(launch, (False,), n_repeat=15, n_warmup=1).gpu_times[0]\n", - " print(f\"{D.mean():.3g} s ± {(D.std() / D.mean()):.2%} (mean ± relative stdev of {D.size} runs)\")" - ] + " T = cpx.profiler.benchmark(launch, (False,), n_repeat=15, n_warmup=1).gpu_times[0]\n", + " print(f\"{T.mean() * 1000:.3g} ms ± {(T.std() / T.mean()):.2%} (mean ± relative stdev of {T.size} runs)\")" + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -124,12 +124,12 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "!python copy_blocked.py output" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -146,15 +146,15 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { "id": "5pyHvJtxVnDB" }, - "outputs": [], "source": [ "!ncu -f --kernel-name regex:copy_blocked --set full -o copy_blocked python copy_blocked.py\n", "copy_blocked_csv = !ncu --import copy_blocked.ncu-rep --csv" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -171,16 +171,16 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { "id": "40w07iG5k6Vl" }, - "outputs": [], "source": [ "import nsightful\n", "\n", "nsightful.display_ncu_csv_in_notebook(copy_blocked_csv)" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -203,11 +203,9 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { "id": "B5PBpaY2HnE0" }, - "outputs": [], "source": [ "%%writefile copy_optimized.py\n", "\n", @@ -242,9 +240,11 @@ " launch(check=False) # `ncu` slows things down; so just launch once when running under it.\n", "else:\n", " launch(check=True)\n", - " D = cpx.profiler.benchmark(launch, (False,), n_repeat=15, n_warmup=1).gpu_times[0]\n", - " print(f\"{D.mean():.3g} s ± {(D.std() / D.mean()):.2%} (mean ± relative stdev of {D.size} runs)\")" - ] + " T = cpx.profiler.benchmark(launch, (False,), n_repeat=15, n_warmup=1).gpu_times[0]\n", + " print(f\"{T.mean() * 1000:.3g} ms ± {(T.std() / T.mean()):.2%} (mean ± relative stdev of {T.size} runs)\")" + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -259,14 +259,14 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { "id": "thgSpsCQkN2-" }, - "outputs": [], "source": [ "!python copy_optimized.py output" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -279,11 +279,9 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { "id": "kJ7viF-i06qd" }, - "outputs": [], "source": [ "copy_blocked_duration = !python copy_blocked.py\n", "copy_optimized_duration = !python copy_optimized.py\n", @@ -292,7 +290,9 @@ "print(f\"copy_blocked: {copy_blocked_duration[0]}\")\n", "print(f\"copy_optimized: {copy_optimized_duration[0]}\")\n", "print(f\"copy_optimized speedup over copy_blocked: {speedup:.2f}\")" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -307,15 +307,15 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { "id": "zO_y6ObXV_wX" }, - "outputs": [], "source": [ "!ncu -f --kernel-name regex:copy_optimized --set full -o copy_optimized python copy_optimized.py\n", "copy_optimized_csv = !ncu --import copy_optimized.ncu-rep --csv" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -328,14 +328,14 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { "id": "KjE0Vgu_zgs3" }, - "outputs": [], "source": [ "nsightful.display_ncu_csv_in_notebook(copy_optimized_csv)" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -363,4 +363,4 @@ }, "nbformat": 4, "nbformat_minor": 0 -} +} \ No newline at end of file diff --git a/tutorials/accelerated-python/notebooks/kernels/41__kernel_authoring__book_histogram.ipynb b/tutorials/accelerated-python/notebooks/kernels/41__kernel_authoring__book_histogram.ipynb index 8af90f50..9eba8c1c 100644 --- a/tutorials/accelerated-python/notebooks/kernels/41__kernel_authoring__book_histogram.ipynb +++ b/tutorials/accelerated-python/notebooks/kernels/41__kernel_authoring__book_histogram.ipynb @@ -2,7 +2,6 @@ "cells": [ { "cell_type": "markdown", - "id": "f1a8560a-c91b-48db-af1c-18fcd4892448", "metadata": { "id": "f1a8560a-c91b-48db-af1c-18fcd4892448" }, @@ -23,16 +22,14 @@ "Let's learn to use some advanced CUDA features like shared memory, atomics, and [cuda.cooperative](https://nvidia.github.io/cccl/unstable/python/coop.html) to write an efficient histogram kernel to determine the most frequent characters in a collection of books.\n", "\n", "First, let's download our dataset and install the necessary tools." - ] + ], + "id": "f1a8560a-c91b-48db-af1c-18fcd4892448" }, { "cell_type": "code", - "execution_count": null, - "id": "ce42d5e5-db1e-46da-a64a-831d0f3d59ff", "metadata": { "id": "ce42d5e5-db1e-46da-a64a-831d0f3d59ff" }, - "outputs": [], "source": [ "import os\n", "\n", @@ -51,25 +48,27 @@ "import matplotlib.pyplot as plt\n", "import nsightful\n", "import urllib.request" - ] + ], + "execution_count": null, + "outputs": [], + "id": "ce42d5e5-db1e-46da-a64a-831d0f3d59ff" }, { "cell_type": "code", - "execution_count": null, - "id": "6e1ae9dc-39f1-4c93-b923-85a96b45a057", "metadata": { "id": "6e1ae9dc-39f1-4c93-b923-85a96b45a057" }, - "outputs": [], "source": [ "urllib.request.urlretrieve(\n", " \"https://drive.usercontent.google.com/download?id=1MW1lPgkTq3YG9ikuq6u3d9sfpt-wKQZ0&export=download\",\n", " \"books__15m.txt\")" - ] + ], + "execution_count": null, + "outputs": [], + "id": "6e1ae9dc-39f1-4c93-b923-85a96b45a057" }, { "cell_type": "markdown", - "id": "9109d3c0-e276-44cc-9f36-f8c79eb48b31", "metadata": { "id": "9109d3c0-e276-44cc-9f36-f8c79eb48b31" }, @@ -79,16 +78,14 @@ "A histogram kernel counts the number of times a value occurs in a dataset. To implement this, we create an array that is large enough to store all possible values (in the case of counting 1-byte ASCII characters, 256 elements). Then for the value of each element in the dataset, we increment its location in the array.\n", "\n", "Let's try a simple way to implement this:" - ] + ], + "id": "9109d3c0-e276-44cc-9f36-f8c79eb48b31" }, { "cell_type": "code", - "execution_count": null, - "id": "61c12795-b14a-4447-9dcf-9748616cc453", "metadata": { "id": "61c12795-b14a-4447-9dcf-9748616cc453" }, - "outputs": [], "source": [ "%%writefile histogram_global.py\n", "\n", @@ -130,40 +127,40 @@ " launch(output=False) # `ncu` slows things down; so just launch once when running under it.\n", "else:\n", " launch(output=False)\n", - " D = cpx.profiler.benchmark(launch, (False,), n_repeat=15, n_warmup=4).gpu_times[0]\n", - " print(f\"{D.mean():.3g} s ± {(D.std() / D.mean()):.2%} (mean ± relative stdev of {D.size} runs)\")" - ] + " T = cpx.profiler.benchmark(launch, (False,), n_repeat=15, n_warmup=4).gpu_times[0]\n", + " print(f\"{T.mean() * 1000:.3g} ms ± {(T.std() / T.mean()):.2%} (mean ± relative stdev of {T.size} runs)\")" + ], + "execution_count": null, + "outputs": [], + "id": "61c12795-b14a-4447-9dcf-9748616cc453" }, { "cell_type": "markdown", - "id": "27e30efa-3a37-402f-9414-e444214d8ce6", "metadata": { "id": "27e30efa-3a37-402f-9414-e444214d8ce6" }, "source": [ "Now let's make sure it runs and check the output." - ] + ], + "id": "27e30efa-3a37-402f-9414-e444214d8ce6" }, { "cell_type": "code", - "execution_count": null, - "id": "b3f32240-ad7b-4717-98b3-82b0298a099a", "metadata": { "id": "b3f32240-ad7b-4717-98b3-82b0298a099a" }, - "outputs": [], "source": [ "!python histogram_global.py" - ] + ], + "execution_count": null, + "outputs": [], + "id": "b3f32240-ad7b-4717-98b3-82b0298a099a" }, { "cell_type": "code", - "execution_count": null, - "id": "815cb072-b3a0-47aa-af6c-dd66c626a440", "metadata": { "id": "815cb072-b3a0-47aa-af6c-dd66c626a440" }, - "outputs": [], "source": [ "histogram_output = !python histogram_global.py output\n", "histogram = np.loadtxt(histogram_output, delimiter=\",\")\n", @@ -179,11 +176,13 @@ "\n", "length = os.path.getsize(\"books__15m.txt\")\n", "print(f\"Characters in dataset: {length / 1e6:.1f} MB\")" - ] + ], + "execution_count": null, + "outputs": [], + "id": "815cb072-b3a0-47aa-af6c-dd66c626a440" }, { "cell_type": "markdown", - "id": "b14fa522-b41b-4538-8c34-ecc355e55116", "metadata": { "id": "b14fa522-b41b-4538-8c34-ecc355e55116" }, @@ -206,11 +205,11 @@ "To fix this, we need to use atomic operations. `cuda.atomic.add(array, index, value)` will perform `array[index] += value` as a single indivisible operation. This will ensure that no increments get lost.\n", "\n", "**TODO: Fix the code above by modifying it to use `cuda.atomic.add`.**" - ] + ], + "id": "b14fa522-b41b-4538-8c34-ecc355e55116" }, { "cell_type": "markdown", - "id": "08f4dded-26a7-4ef8-b981-e00c569ca4d0", "metadata": { "id": "08f4dded-26a7-4ef8-b981-e00c569ca4d0" }, @@ -218,36 +217,36 @@ "## 4. Profiling the Naive Solution\n", "\n", "Now let's profile our code." - ] + ], + "id": "08f4dded-26a7-4ef8-b981-e00c569ca4d0" }, { "cell_type": "code", - "execution_count": null, - "id": "8dbd226c-66f2-43df-868a-6b024b1de24c", "metadata": { "id": "8dbd226c-66f2-43df-868a-6b024b1de24c" }, - "outputs": [], "source": [ "!ncu -f --kernel-name regex:histogram_global --set full -o histogram_global python histogram_global.py\n", "histogram_global_csv = !ncu --import histogram_global.ncu-rep --csv" - ] + ], + "execution_count": null, + "outputs": [], + "id": "8dbd226c-66f2-43df-868a-6b024b1de24c" }, { "cell_type": "code", - "execution_count": null, - "id": "ad12380e-253b-4410-ab34-9479411fdf81", "metadata": { "id": "ad12380e-253b-4410-ab34-9479411fdf81" }, - "outputs": [], "source": [ "nsightful.display_ncu_csv_in_notebook(histogram_global_csv)" - ] + ], + "execution_count": null, + "outputs": [], + "id": "ad12380e-253b-4410-ab34-9479411fdf81" }, { "cell_type": "markdown", - "id": "e1f72831-780f-4cf5-8ff1-2092ecb193d9", "metadata": { "id": "e1f72831-780f-4cf5-8ff1-2092ecb193d9" }, @@ -269,16 +268,14 @@ "- **What sorts of operations are we performing? Are they expensive? Can we make the code more efficient by reducing the number of expensive operations we perform?**\n", "- **You can allocate memory accessible by the entire block with `cuda.shared.array`.**\n", "- **You can synchronize all threads within a block with `cuda.syncthreads`.**" - ] + ], + "id": "e1f72831-780f-4cf5-8ff1-2092ecb193d9" }, { "cell_type": "code", - "execution_count": null, - "id": "cf7c9865-646a-4bbd-9b41-61cadfc5484c", "metadata": { "id": "cf7c9865-646a-4bbd-9b41-61cadfc5484c" }, - "outputs": [], "source": [ "%%writefile histogram_localized.py\n", "\n", @@ -323,40 +320,40 @@ " launch(check=False, output=False) # `ncu` slows things down; so just launch once when running under it.\n", "else:\n", " launch(check=True, output=False)\n", - " D = cpx.profiler.benchmark(launch, (False, False), n_repeat=15, n_warmup=4).gpu_times[0]\n", - " print(f\"{D.mean():.3g} s ± {(D.std() / D.mean()):.2%} (mean ± relative stdev of {D.size} runs)\")" - ] + " T = cpx.profiler.benchmark(launch, (False, False), n_repeat=15, n_warmup=4).gpu_times[0]\n", + " print(f\"{T.mean() * 1000:.3g} ms ± {(T.std() / T.mean()):.2%} (mean ± relative stdev of {T.size} runs)\")" + ], + "execution_count": null, + "outputs": [], + "id": "cf7c9865-646a-4bbd-9b41-61cadfc5484c" }, { "cell_type": "markdown", - "id": "fd090ee6-a5d3-46f6-a58e-d34e077a99c0", "metadata": { "id": "fd090ee6-a5d3-46f6-a58e-d34e077a99c0" }, "source": [ "Now let's run the code and profile it." - ] + ], + "id": "fd090ee6-a5d3-46f6-a58e-d34e077a99c0" }, { "cell_type": "code", - "execution_count": null, - "id": "1b30e9b3-5a4c-4181-b642-b7def5e9f258", "metadata": { "id": "1b30e9b3-5a4c-4181-b642-b7def5e9f258" }, - "outputs": [], "source": [ "!python histogram_localized.py" - ] + ], + "execution_count": null, + "outputs": [], + "id": "1b30e9b3-5a4c-4181-b642-b7def5e9f258" }, { "cell_type": "code", - "execution_count": null, - "id": "73f0c3cd-349b-490f-b6bd-7afbeb442fff", "metadata": { "id": "73f0c3cd-349b-490f-b6bd-7afbeb442fff" }, - "outputs": [], "source": [ "histogram_output = !python histogram_localized.py output\n", "histogram = np.loadtxt(histogram_output, delimiter=\",\")\n", @@ -372,51 +369,51 @@ "\n", "length = os.path.getsize(\"books__15m.txt\")\n", "print(f\"Characters in dataset: {length / 1e6:.1f} MB\")" - ] + ], + "execution_count": null, + "outputs": [], + "id": "73f0c3cd-349b-490f-b6bd-7afbeb442fff" }, { "cell_type": "code", - "execution_count": null, - "id": "d637b6b1-fb0b-4807-b70b-c80227c0fd6f", "metadata": { "id": "d637b6b1-fb0b-4807-b70b-c80227c0fd6f" }, - "outputs": [], "source": [ "!ncu -f --kernel-name regex:histogram_localized --set full -o histogram_localized python histogram_localized.py\n", "histogram_localized_csv = !ncu --import histogram_localized.ncu-rep --csv" - ] + ], + "execution_count": null, + "outputs": [], + "id": "d637b6b1-fb0b-4807-b70b-c80227c0fd6f" }, { "cell_type": "code", - "execution_count": null, - "id": "114e8ff7-b6fb-42ad-abda-f6d53479c052", "metadata": { "id": "114e8ff7-b6fb-42ad-abda-f6d53479c052" }, - "outputs": [], "source": [ "nsightful.display_ncu_csv_in_notebook(histogram_localized_csv)" - ] + ], + "execution_count": null, + "outputs": [], + "id": "114e8ff7-b6fb-42ad-abda-f6d53479c052" }, { "cell_type": "markdown", - "id": "23df6c7a", "metadata": {}, "source": [ "## 6. Performance Comparison\n", "\n", "Let's compare the execution time of our naive global memory implementation against our optimized shared memory implementation." - ] + ], + "id": "23df6c7a" }, { "cell_type": "code", - "execution_count": null, - "id": "2a3f9ca4-b61b-4536-9896-7a41498cc986", "metadata": { "id": "2a3f9ca4-b61b-4536-9896-7a41498cc986" }, - "outputs": [], "source": [ "histogram_global_duration = !python histogram_global.py\n", "histogram_localized_duration = !python histogram_localized.py\n", @@ -425,7 +422,10 @@ "print(f\"histogram_global: {histogram_global_duration[0]}\")\n", "print(f\"histogram_localized: {histogram_localized_duration[0]}\")\n", "print(f\"histogram_localized speedup over histogram_global: {speedup:.2f}\")" - ] + ], + "execution_count": null, + "outputs": [], + "id": "2a3f9ca4-b61b-4536-9896-7a41498cc986" } ], "metadata": { @@ -442,4 +442,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file diff --git a/tutorials/accelerated-python/notebooks/kernels/solutions/40__kernel_authoring__copy__SOLUTION.ipynb b/tutorials/accelerated-python/notebooks/kernels/solutions/40__kernel_authoring__copy__SOLUTION.ipynb index 4a622183..9d798a6a 100644 --- a/tutorials/accelerated-python/notebooks/kernels/solutions/40__kernel_authoring__copy__SOLUTION.ipynb +++ b/tutorials/accelerated-python/notebooks/kernels/solutions/40__kernel_authoring__copy__SOLUTION.ipynb @@ -27,7 +27,6 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { "collapsed": true, "id": "AoHkvSPMC5Fs", @@ -35,7 +34,6 @@ "outputs_hidden": true } }, - "outputs": [], "source": [ "import os\n", "\n", @@ -49,7 +47,9 @@ " !pip uninstall \"cuda-python\" --yes > /dev/null\n", " !pip install \"numba-cuda\" \"cuda-cccl[test-cu12]\" \"nvtx\" \"nsightful[notebook] @ git+https://github.com/brycelelbach/nsightful.git\" > /dev/null 2>&1\n", " open(\"/accelerated-computing-hub-installed\", \"a\").close()" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -70,7 +70,6 @@ }, { "cell_type": "code", - "execution_count": 2, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -78,15 +77,6 @@ "id": "I9Tz2hG-_tBj", "outputId": "e52f41cb-f70b-4792-ea76-e78a554956f4" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Writing copy_blocked.py\n" - ] - } - ], "source": [ "%%writefile copy_blocked.py\n", "\n", @@ -123,8 +113,17 @@ " launch(check=False) # `ncu` slows things down; so just launch once when running under it.\n", "else:\n", " launch(check=True)\n", - " D = cpx.profiler.benchmark(launch, (False,), n_repeat=15, n_warmup=1).gpu_times[0]\n", - " print(f\"{D.mean():.3g} s ± {(D.std() / D.mean()):.2%} (mean ± relative stdev of {D.size} runs)\")" + " T = cpx.profiler.benchmark(launch, (False,), n_repeat=15, n_warmup=1).gpu_times[0]\n", + " print(f\"{T.mean() * 1000:.3g} ms ± {(T.std() / T.mean()):.2%} (mean ± relative stdev of {T.size} runs)\")" + ], + "execution_count": 2, + "outputs": [ + { + "output_type": "stream", + "text": [ + "Writing copy_blocked.py\n" + ] + } ] }, { @@ -138,12 +137,12 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "!python copy_blocked.py output" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -160,7 +159,6 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -168,9 +166,13 @@ "id": "5pyHvJtxVnDB", "outputId": "87ad5a72-9244-4b21-9776-ecd93262f274" }, + "source": [ + "!ncu -f --kernel-name regex:copy_blocked --set full -o copy_blocked python copy_blocked.py\n", + "copy_blocked_csv = !ncu --import copy_blocked.ncu-rep --csv" + ], + "execution_count": null, "outputs": [ { - "name": "stdout", "output_type": "stream", "text": [ "==PROF== Connected to process 1137 (/usr/bin/python3.11)\n", @@ -179,10 +181,6 @@ "==PROF== Report: /content/copy_blocked.ncu-rep\n" ] } - ], - "source": [ - "!ncu -f --kernel-name regex:copy_blocked --set full -o copy_blocked python copy_blocked.py\n", - "copy_blocked_csv = !ncu --import copy_blocked.ncu-rep --csv" ] }, { @@ -198,7 +196,6 @@ }, { "cell_type": "code", - "execution_count": 4, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -240,18 +237,24 @@ "id": "40w07iG5k6Vl", "outputId": "f5d0becc-0285-4cb7-a78e-427cdc105454" }, + "source": [ + "import nsightful\n", + "\n", + "nsightful.display_ncu_csv_in_notebook(copy_blocked_csv)" + ], + "execution_count": 4, "outputs": [ { + "output_type": "display_data", "data": { "application/javascript": "window[\"5ecc93f0-740d-11f0-abc7-0242ac1c000c\"] = google.colab.output.setIframeHeight(-1, true, {\"interactive\": true, \"maxHeight\": 99999});\n//# sourceURL=js_bae5335f4f", "text/plain": [ "" ] - }, - "metadata": {}, - "output_type": "display_data" + } }, { + "output_type": "display_data", "data": { "text/html": [ "\n", @@ -298,11 +301,10 @@ "text/plain": [ "" ] - }, - "metadata": {}, - "output_type": "display_data" + } }, { + "output_type": "display_data", "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "b47fd7b6a0154f21bc182a368783f018", @@ -312,11 +314,10 @@ "text/plain": [ "Dropdown(description='Kernel:', layout=Layout(width='400px'), options=('copy_blocked',), style=DescriptionStyl…" ] - }, - "metadata": {}, - "output_type": "display_data" + } }, { + "output_type": "display_data", "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "da5ad97524f4499cb466a461c9e9a014", @@ -326,15 +327,8 @@ "text/plain": [ "Output()" ] - }, - "metadata": {}, - "output_type": "display_data" + } } - ], - "source": [ - "import nsightful\n", - "\n", - "nsightful.display_ncu_csv_in_notebook(copy_blocked_csv)" ] }, { @@ -368,7 +362,6 @@ }, { "cell_type": "code", - "execution_count": 5, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -376,15 +369,6 @@ "id": "B5PBpaY2HnE0", "outputId": "9bf8af36-f011-4eaa-ed28-c39abde2c315" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Writing copy_optimized.py\n" - ] - } - ], "source": [ "%%writefile copy_optimized.py\n", "\n", @@ -426,8 +410,17 @@ " launch(check=False) # `ncu` slows things down; so just launch once when running under it.\n", "else:\n", " launch(check=True)\n", - " D = cpx.profiler.benchmark(launch, (False,), n_repeat=15, n_warmup=1).gpu_times[0]\n", - " print(f\"{D.mean():.3g} s ± {(D.std() / D.mean()):.2%} (mean ± relative stdev of {D.size} runs)\")" + " T = cpx.profiler.benchmark(launch, (False,), n_repeat=15, n_warmup=1).gpu_times[0]\n", + " print(f\"{T.mean() * 1000:.3g} ms ± {(T.std() / T.mean()):.2%} (mean ± relative stdev of {T.size} runs)\")" + ], + "execution_count": 5, + "outputs": [ + { + "output_type": "stream", + "text": [ + "Writing copy_optimized.py\n" + ] + } ] }, { @@ -443,7 +436,6 @@ }, { "cell_type": "code", - "execution_count": 6, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -451,9 +443,18 @@ "id": "kJ7viF-i06qd", "outputId": "e2d84395-6324-4e55-c144-bc34399cc515" }, + "source": [ + "copy_blocked_duration = !python copy_blocked.py\n", + "copy_optimized_duration = !python copy_optimized.py\n", + "speedup = float(copy_blocked_duration[0].split()[0]) / float(copy_optimized_duration[0].split()[0])\n", + "\n", + "print(f\"copy_blocked: {copy_blocked_duration[0]}\")\n", + "print(f\"copy_optimized: {copy_optimized_duration[0]}\")\n", + "print(f\"copy_optimized speedup over copy_blocked: {speedup:.2f}\")" + ], + "execution_count": 6, "outputs": [ { - "name": "stdout", "output_type": "stream", "text": [ "copy_blocked: 0.327 s ± 0.66% (mean ± relative stdev of 15 runs)\n", @@ -461,15 +462,6 @@ "copy_optimized speedup over copy_blocked: 17.21\n" ] } - ], - "source": [ - "copy_blocked_duration = !python copy_blocked.py\n", - "copy_optimized_duration = !python copy_optimized.py\n", - "speedup = float(copy_blocked_duration[0].split()[0]) / float(copy_optimized_duration[0].split()[0])\n", - "\n", - "print(f\"copy_blocked: {copy_blocked_duration[0]}\")\n", - "print(f\"copy_optimized: {copy_optimized_duration[0]}\")\n", - "print(f\"copy_optimized speedup over copy_blocked: {speedup:.2f}\")" ] }, { @@ -485,7 +477,6 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -493,9 +484,13 @@ "id": "zO_y6ObXV_wX", "outputId": "8e643e49-6fbf-4b1e-ff58-d391700451ab" }, + "source": [ + "!ncu -f --kernel-name regex:copy_optimized --set full -o copy_optimized python copy_optimized.py\n", + "copy_optimized_csv = !ncu --import copy_optimized.ncu-rep --csv" + ], + "execution_count": null, "outputs": [ { - "name": "stdout", "output_type": "stream", "text": [ "==PROF== Connected to process 1401 (/usr/bin/python3.11)\n", @@ -504,10 +499,6 @@ "==PROF== Report: /content/copy_optimized.ncu-rep\n" ] } - ], - "source": [ - "!ncu -f --kernel-name regex:copy_optimized --set full -o copy_optimized python copy_optimized.py\n", - "copy_optimized_csv = !ncu --import copy_optimized.ncu-rep --csv" ] }, { @@ -521,7 +512,6 @@ }, { "cell_type": "code", - "execution_count": 8, "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -563,18 +553,22 @@ "id": "KjE0Vgu_zgs3", "outputId": "632a4728-519d-48fa-938e-7a9777d52c89" }, + "source": [ + "nsightful.display_ncu_csv_in_notebook(copy_optimized_csv)" + ], + "execution_count": 8, "outputs": [ { + "output_type": "display_data", "data": { "application/javascript": "window[\"6a0b3852-740d-11f0-abc7-0242ac1c000c\"] = google.colab.output.setIframeHeight(-1, true, {\"interactive\": true, \"maxHeight\": 99999});\n//# sourceURL=js_b772b58d4e", "text/plain": [ "" ] - }, - "metadata": {}, - "output_type": "display_data" + } }, { + "output_type": "display_data", "data": { "text/html": [ "\n", @@ -621,11 +615,10 @@ "text/plain": [ "" ] - }, - "metadata": {}, - "output_type": "display_data" + } }, { + "output_type": "display_data", "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "6be7ca5f9141465cb4b5126001b6c298", @@ -635,11 +628,10 @@ "text/plain": [ "Dropdown(description='Kernel:', layout=Layout(width='400px'), options=('copy_optimized',), style=DescriptionSt…" ] - }, - "metadata": {}, - "output_type": "display_data" + } }, { + "output_type": "display_data", "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "7bd502fafaad4a1d9d5c7db9c1d54b9e", @@ -649,13 +641,8 @@ "text/plain": [ "Output()" ] - }, - "metadata": {}, - "output_type": "display_data" + } } - ], - "source": [ - "nsightful.display_ncu_csv_in_notebook(copy_optimized_csv)" ] } ], @@ -3133,4 +3120,4 @@ }, "nbformat": 4, "nbformat_minor": 4 -} +} \ No newline at end of file diff --git a/tutorials/accelerated-python/notebooks/kernels/solutions/41__kernel_authoring__book_histogram__SOLUTION.ipynb b/tutorials/accelerated-python/notebooks/kernels/solutions/41__kernel_authoring__book_histogram__SOLUTION.ipynb index b5589e04..6dae3260 100644 --- a/tutorials/accelerated-python/notebooks/kernels/solutions/41__kernel_authoring__book_histogram__SOLUTION.ipynb +++ b/tutorials/accelerated-python/notebooks/kernels/solutions/41__kernel_authoring__book_histogram__SOLUTION.ipynb @@ -2,7 +2,6 @@ "cells": [ { "cell_type": "markdown", - "id": "f1a8560a-c91b-48db-af1c-18fcd4892448", "metadata": { "id": "f1a8560a-c91b-48db-af1c-18fcd4892448" }, @@ -23,17 +22,15 @@ "Let's learn to use some advanced CUDA features like shared memory, atomics, and [cuda.cooperative](https://nvidia.github.io/cccl/unstable/python/coop.html) to write an efficient histogram kernel to determine the most frequent characters in a collection of books.\n", "\n", "First, let's download our dataset and install the necessary tools." - ] + ], + "id": "f1a8560a-c91b-48db-af1c-18fcd4892448" }, { "cell_type": "code", - "execution_count": null, - "id": "ce42d5e5-db1e-46da-a64a-831d0f3d59ff", "metadata": { "collapsed": true, "id": "ce42d5e5-db1e-46da-a64a-831d0f3d59ff" }, - "outputs": [], "source": [ "import os\n", "\n", @@ -52,12 +49,13 @@ "import matplotlib.pyplot as plt\n", "import nsightful\n", "import urllib.request" - ] + ], + "execution_count": null, + "outputs": [], + "id": "ce42d5e5-db1e-46da-a64a-831d0f3d59ff" }, { "cell_type": "code", - "execution_count": 3, - "id": "6e1ae9dc-39f1-4c93-b923-85a96b45a057", "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -65,27 +63,26 @@ "id": "6e1ae9dc-39f1-4c93-b923-85a96b45a057", "outputId": "ee334037-7f39-4a91-ad60-f0408a56b4de" }, + "source": [ + "urllib.request.urlretrieve(\n", + " \"https://drive.usercontent.google.com/download?id=1MW1lPgkTq3YG9ikuq6u3d9sfpt-wKQZ0&export=download\",\n", + " \"books__15m.txt\")" + ], + "execution_count": 3, "outputs": [ { + "output_type": "execute_result", "data": { "text/plain": [ "('books__15m.txt', )" ] - }, - "execution_count": null, - "metadata": {}, - "output_type": "execute_result" + } } ], - "source": [ - "urllib.request.urlretrieve(\n", - " \"https://drive.usercontent.google.com/download?id=1MW1lPgkTq3YG9ikuq6u3d9sfpt-wKQZ0&export=download\",\n", - " \"books__15m.txt\")" - ] + "id": "6e1ae9dc-39f1-4c93-b923-85a96b45a057" }, { "cell_type": "markdown", - "id": "9109d3c0-e276-44cc-9f36-f8c79eb48b31", "metadata": { "id": "9109d3c0-e276-44cc-9f36-f8c79eb48b31" }, @@ -95,12 +92,11 @@ "A histogram kernel counts the number of times a value occurs in a dataset. To implement this, we create an array that is large enough to store all possible values (in the case of counting 1-byte ASCII characters, 256 elements). Then for the value of each element in the dataset, we increment its location in the array.\n", "\n", "Let's try a simple way to implement this:" - ] + ], + "id": "9109d3c0-e276-44cc-9f36-f8c79eb48b31" }, { "cell_type": "code", - "execution_count": 4, - "id": "61c12795-b14a-4447-9dcf-9748616cc453", "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -108,15 +104,6 @@ "id": "61c12795-b14a-4447-9dcf-9748616cc453", "outputId": "141b29b8-bbf7-4224-c85a-23e49ee7abaf" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Writing histogram_global.py\n" - ] - } - ], "source": [ "%%writefile histogram_global.py\n", "\n", @@ -159,24 +146,32 @@ " launch(check=False, output=False) # `ncu` slows things down; so just launch once when running under it.\n", "else:\n", " launch(check=True, output=False)\n", - " D = cpx.profiler.benchmark(launch, (False, False), n_repeat=15, n_warmup=4).gpu_times[0]\n", - " print(f\"{D.mean():.3g} s ± {(D.std() / D.mean()):.2%} (mean ± relative stdev of {D.size} runs)\")" - ] + " T = cpx.profiler.benchmark(launch, (False, False), n_repeat=15, n_warmup=4).gpu_times[0]\n", + " print(f\"{T.mean() * 1000:.3g} ms ± {(T.std() / T.mean()):.2%} (mean ± relative stdev of {T.size} runs)\")" + ], + "execution_count": 4, + "outputs": [ + { + "output_type": "stream", + "text": [ + "Writing histogram_global.py\n" + ] + } + ], + "id": "61c12795-b14a-4447-9dcf-9748616cc453" }, { "cell_type": "markdown", - "id": "27e30efa-3a37-402f-9414-e444214d8ce6", "metadata": { "id": "27e30efa-3a37-402f-9414-e444214d8ce6" }, "source": [ "Now let's make sure it runs and check the output." - ] + ], + "id": "27e30efa-3a37-402f-9414-e444214d8ce6" }, { "cell_type": "code", - "execution_count": 5, - "id": "b3f32240-ad7b-4717-98b3-82b0298a099a", "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -184,23 +179,22 @@ "id": "b3f32240-ad7b-4717-98b3-82b0298a099a", "outputId": "cdf42faf-b6e6-45ad-85e2-d3b0d4c87ad5" }, + "source": [ + "!python histogram_global.py" + ], + "execution_count": 5, "outputs": [ { - "name": "stdout", "output_type": "stream", "text": [ "0.00858 s ± 3.22% (mean ± relative stdev of 15 runs)\n" ] } ], - "source": [ - "!python histogram_global.py" - ] + "id": "b3f32240-ad7b-4717-98b3-82b0298a099a" }, { "cell_type": "code", - "execution_count": null, - "id": "815cb072-b3a0-47aa-af6c-dd66c626a440", "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -209,18 +203,6 @@ "id": "815cb072-b3a0-47aa-af6c-dd66c626a440", "outputId": "d7a3d2e2-1df5-4681-c6a3-923cfb921c52" }, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAnUAAAHWCAYAAAARl3+JAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAMUNJREFUeJzt3Xt4FOXB/vF7E5IJAXY5J6EuRAERDAQU4cUTUdFoqS3WKqJAxFOLhxJQ1OhPYrB1AUXQSks5g/jWE4qnCiq6iIigEJR4QEUCqXJSdBeiLjSZ3x++rEYSJHGzwzz5fq7ruS539pnsvc4V9/aZmY3Htm1bAAAAcLUEpwMAAADgl6PUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABGjkdwEmVlZX6/PPP1axZM3k8HqfjAACABsy2be3Zs0ft2rVTQkLt190adKn7/PPP5ff7nY4BAAAQVVZWpqOOOqrW+zXoUtesWTNJ3//L83q9DqcBAAANWTgclt/vj/aT2mrQpe7AKVev10upAwAAR4S6XhLGjRIAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABggEZOBzgSZBUuVYKV6nQMAABwBCqdMNDpCIeFlToAAAADUOoAAAAMUOtSt2vXLo0cOVLt27eXZVlKT09Xbm6uVq5cKUnKzMyUx+ORx+NRkyZNdMIJJ+jxxx+v8jO+/fZbtWzZUq1bt1YkEqn2dRYtWqScnBz5fD41bdpUPXr00Pjx47V7925J0rx586Kv8+ORkpJS27cEAADgerUudRdeeKGKi4s1f/58ffTRR3rmmWeUk5OjL7/8Mjpn/Pjx2rZtm4qLi3XSSSdp8ODBeuONN6LPL1q0SMcff7yOO+44LV68+KDXuP322zV48GCddNJJeuGFF1RSUqLJkyfrnXfe0UMPPRSd5/V6tW3btipjy5YttX1LAAAArlerGyW+/vprrVixQsFgUP3795ckdejQQX369Kkyr1mzZkpPT1d6erqmTZumhQsX6tlnn9XJJ58sSZo9e7aGDh0q27Y1e/ZsDR48OLrvmjVrdPfdd2vq1KkaNWpUdHtmZqbOPvtsff3119FtHo9H6enptX7TAAAApqnVSl3Tpk3VtGlTLV68uMbTpj/VqFEjJSUlad++fZKkTZs2adWqVbr44ot18cUXa8WKFVVW1x5++GE1bdpU1157bbU/r3nz5rWJDAAA0CDUqtQ1atRI8+bN0/z589W8eXOdcsopuu222/Tuu+9WO3/fvn0KBAIKhUI688wzJUlz5szReeedpxYtWqhly5bKzc3V3Llzo/t8/PHHOuaYY5SUlPSzeUKhULRoHhjnnXdejfMjkYjC4XCVAQAAYII6XVP3+eef65lnntG5556rYDCoE044QfPmzYvOueWWW9S0aVOlpqZq4sSJmjBhggYOHKiKigrNnz9fQ4cOjc4dOnSo5s2bp8rKSkmSbduHnaVZs2Zav359lTFr1qwa5wcCAfl8vujw+/21ffsAAABHJI9dmxZVg6uuukovvfSStmzZoszMTA0dOlSXX365mjZtqrS0NHk8HknSv//9bw0cOFCJiYlV9q+oqNCLL76os88+W6NGjdKcOXO0e/fuQ67WzZs3T/n5+VWusfs5kUikymnjcDgsv98vf/5jfPkwAACoVry+fDgcDsvn8ykUCsnr9dZ6/5h8T123bt1UXl4efdy6dWt16tRJ6enp0UInfX+DxCWXXHLQ6toll1yi2bNnS5IuvfRS7d27V3//+9+rfa3alLifsixLXq+3ygAAADBBre5+/fLLL3XRRRfpiiuuUI8ePdSsWTO9/fbbmjRpkn73u98dct9du3bp2Wef1TPPPKOsrKwqzw0fPlwXXHCBdu/erb59++rmm2/WjTfeqM8++0wXXHCB2rVrp08++UTTp0/XqaeeGr0r1rZtbd++/aDXatu2rRIS+F5lAADQcNSq1DVt2lR9+/bVlClTtGnTJu3fv19+v19XX321brvttkPuu2DBAjVp0kRnnXXWQc+dddZZaty4sRYuXKg///nPmjhxok488URNmzZN06dPV2VlpTp27Kg//OEPysvLi+4XDoeVkZFx0M/btm0bX3UCAAAalJhcU+dWB85dc00dAACoSYO6pg4AAADOqtXpV1OVFOVy0wQAAHA1VuoAAAAMQKkDAAAwAKUOAADAAFxTJymrcCl3vwKod/G6gw5Aw8RKHQAAgAEodQAAAAag1AEAABiAUgcAAGAAV5e6yspKBQIBHX300WrcuLGys7P1xBNPOB0L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zcFUAAAAznFlqQMAAEBVrrv7tT6UFOXK6/U6HQMAAKDOWKkDAAAwAKUOAADAAJx+lZRVuFQJVqrTMeqsdMJApyMAAACHsVIHAABgAEodAACAASh1AAAABqDUAQAAGMC1pW7BggVq1aqVIpFIle2DBg3SsGHDHEoFAADgDNeWuosuukgVFRV65plnott27typ559/XldccYWDyQAAAOLPtaWucePGuvTSSzV37tzotoULF6p9+/bKycmpdp9IJKJwOFxlAAAAmMC1pU6Srr76ar344ov67LPPJEnz5s3T5ZdfLo/HU+38QCAgn88XHX6/P55xAQAA6o2rS12vXr2UnZ2tBQsWaO3atXrvvfd0+eWX1zi/oKBAoVAoOsrKyuIXFgAAoB65/i9KXHXVVZo6dao+++wzDRgw4JCrb5ZlybKsOKYDAACID1ev1EnSpZdeqv/85z+aOXMmN0gAAIAGy/Wlzufz6cILL1TTpk01aNAgp+MAAAA4wvWlTpI+++wzXXbZZZxaBQAADZarr6n76quvFAwGFQwG9fe//93pOAAAAI5xdanr1auXvvrqK02cOFFdunSp888pKcqV1+uNYTIAAID4cnWpKy0tdToCAADAEcGIa+oAAAAaOkodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGKCR0wGcZNu2JCkcDjucBAAANHQH+siBflJbDbrUffnll5Ikv9/vcBIAAIDv7dmzRz6fr9b7NehS17JlS0nS1q1b6/QvD/EXDofl9/tVVlYmr9frdBwcBo6Z+3DM3Idj5j7VHTPbtrVnzx61a9euTj+zQZe6hITvLyn0+Xz8EriM1+vlmLkMx8x9OGbuwzFzn58es1+yyMSNEgAAAAag1AEAABigQZc6y7JUWFgoy7KcjoLDxDFzH46Z+3DM3Idj5j71ccw8dl3vmwUAAMARo0Gv1AEAAJiCUgcAAGAASh0AAIABjC9106ZNU2ZmplJSUtS3b1+tWbPmkPMff/xxHXfccUpJSVH37t3173//O05JcUBtjtm8efPk8XiqjJSUlDimbdhee+01nX/++WrXrp08Ho8WL178s/sEg0GdcMIJsixLnTp10rx58+o9J35Q22MWDAYP+h3zeDzavn17fAJDgUBAJ510kpo1a6a2bdtq0KBB2rhx48/ux+eZc+pyzGLxeWZ0qXv00Uc1ZswYFRYWat26dcrOzlZubq527txZ7fw33nhDQ4YM0ZVXXqni4mINGjRIgwYNUklJSZyTN1y1PWbS91/cuG3btujYsmVLHBM3bOXl5crOzta0adMOa/7mzZs1cOBAnXHGGVq/fr3y8/N11VVXaenSpfWcFAfU9pgdsHHjxiq/Z23btq2nhPip5cuX67rrrtObb76pl156Sfv379c555yj8vLyGvfh88xZdTlmUgw+z2yD9enTx77uuuuijysqKux27drZgUCg2vkXX3yxPXDgwCrb+vbta//xj3+s15z4QW2P2dy5c22fzxendDgUSfZTTz11yDk333yzffzxx1fZNnjwYDs3N7cek6Emh3PMXn31VVuS/dVXX8UlE37ezp07bUn28uXLa5zD59mR5XCOWSw+z4xdqdu3b5/Wrl2rAQMGRLclJCRowIABWrVqVbX7rFq1qsp8ScrNza1xPmKrLsdMkvbu3asOHTrI7/frd7/7nd577714xEUd8DvmXj179lRGRobOPvtsrVy50uk4DVooFJL0w98vrw6/a0eWwzlm0i//PDO21H3xxReqqKhQWlpale1paWk1Xguyffv2Ws1HbNXlmHXp0kVz5szR008/rYULF6qyslInn3yy/vOf/8QjMmqppt+xcDisb7/91qFUOJSMjAxNnz5dixYt0qJFi+T3+5WTk6N169Y5Ha1BqqysVH5+vk455RRlZWXVOI/PsyPH4R6zWHyeNYpFYMAp/fr1U79+/aKPTz75ZHXt2lX//Oc/dddddzmYDDBDly5d1KVLl+jjk08+WZs2bdKUKVP00EMPOZisYbruuutUUlKi119/3ekoOEyHe8xi8Xlm7Epd69atlZiYqB07dlTZvmPHDqWnp1e7T3p6eq3mI7bqcsx+KikpSb169dInn3xSHxHxC9X0O+b1etW4cWOHUqG2+vTpw++YA66//no999xzevXVV3XUUUcdci6fZ0eG2hyzn6rL55mxpS45OVknnniili1bFt1WWVmpZcuWVWnCP9avX78q8yXppZdeqnE+Yqsux+ynKioqtGHDBmVkZNRXTPwC/I6ZYf369fyOxZFt27r++uv11FNP6ZVXXtHRRx/9s/vwu+asuhyzn6rT59kvus3iCPfII4/YlmXZ8+bNs99//337mmuusZs3b25v377dtm3bHjZsmH3rrbdG569cudJu1KiRfe+999offPCBXVhYaCclJdkbNmxw6i00OLU9ZkVFRfbSpUvtTZs22WvXrrUvueQSOyUlxX7vvfecegsNyp49e+zi4mK7uLjYlmTfd999dnFxsb1lyxbbtm371ltvtYcNGxad/+mnn9qpqan22LFj7Q8++MCeNm2anZiYaC9ZssSpt9Dg1PaYTZkyxV68eLH98ccf2xs2bLBHjRplJyQk2C+//LJTb6HBGTlypO3z+exgMGhv27YtOr755pvoHD7Pjix1OWax+DwzutTZtm3/7W9/s9u3b28nJyfbffr0sd98883oc/3797fz8vKqzH/sscfsY4891k5OTraPP/54+/nnn49zYtTmmOXn50fnpqWl2b/+9a/tdevWOZC6YTrwdRc/HQeOUV5ent2/f/+D9unZs6ednJxsH3PMMfbcuXPjnrshq+0xmzhxot2xY0c7JSXFbtmypZ2Tk2O/8sorzoRvoKo7XpKq/O7weXZkqcsxi8Xnmef/XhwAAAAuZuw1dQAAAA0JpQ4AAMAAlDoAAAADUOoAAAAMQKkDAAAwAKUOAADAAJQ6AAAAA1DqAAAADECpAwAAOAyvvfaazj//fLVr104ej0eLFy+u9c+wbVv33nuvjj32WFmWpV/96lf661//GpN8lDoAcFBpaak8Ho/Wr1/vdBQAP6O8vFzZ2dmaNm1anX/GqFGjNGvWLN1777368MMP9cwzz6hPnz4xydcoJj8FAADAcOedd57OO++8Gp+PRCK6/fbb9a9//Utff/21srKyNHHiROXk5EiSPvjgA/3jH/9QSUmJunTpIkk6+uijY5aPlToADVplZaUmTZqkTp06ybIstW/fPnoqZMOGDTrzzDPVuHFjtWrVStdcc4327t0b3TcnJ0f5+flVft6gQYN0+eWXRx9nZmbq7rvv1hVXXKFmzZqpffv2mjFjRvT5A/9B79WrlzweT/Q//gDc5/rrr9eqVav0yCOP6N1339VFF12kc889Vx9//LEk6dlnn9Uxxxyj5557TkcffbQyMzN11VVXaffu3TF5fUodgAatoKBAEyZM0B133KH3339f//u//6u0tDSVl5crNzdXLVq00FtvvaXHH39cL7/8sq6//vpav8bkyZPVu3dvFRcX69prr9XIkSO1ceNGSdKaNWskSS+//LK2bdumJ598MqbvD0B8bN26VXPnztXjjz+u0047TR07dtRNN92kU089VXPnzpUkffrpp9qyZYsef/xxLViwQPPmzdPatWv1hz/8ISYZOP0KoMHas2eP7r//fj344IPKy8uTJHXs2FGnnnqqZs6cqe+++04LFixQkyZNJEkPPvigzj//fE2cOFFpaWmH/Tq//vWvde2110qSbrnlFk2ZMkWvvvqqunTpojZt2kiSWrVqpfT09Bi/QwDxsmHDBlVUVOjYY4+tsj0SiahVq1aSvj8zEIlEtGDBgui82bNn68QTT9TGjRujp2TrilIHoMH64IMPFIlEdNZZZ1X7XHZ2drTQSdIpp5yiyspKbdy4sValrkePHtF/9ng8Sk9P186dO39ZeABHlL179yoxMVFr165VYmJileeaNm0qScrIyFCjRo2qFL+uXbtK+n6lj1IHAHXUuHHjX7R/QkKCbNuusm3//v0HzUtKSqry2OPxqLKy8he9NoAjS69evVRRUaGdO3fqtNNOq3bOKaecov/+97/atGmTOnbsKEn66KOPJEkdOnT4xRm4pg5Ag9W5c2c1btxYy5YtO+i5rl276p133lF5eXl028qVK5WQkBD9v+k2bdpo27Zt0ecrKipUUlJSqwzJycnRfQEc2fbu3av169dHv4Jo8+bNWr9+vbZu3apjjz1Wl112mYYPH64nn3xSmzdv1po1axQIBPT8889LkgYMGKATTjhBV1xxhYqLi7V27Vr98Y9/1Nlnn33Qadu6oNQBaLBSUlJ0yy236Oabb9aCBQu0adMmvfnmm5o9e7Yuu+wypaSkKC8vTyUlJXr11Vd1ww03aNiwYdFTr2eeeaaef/55Pf/88/rwww81cuRIff3117XK0LZtWzVu3FhLlizRjh07FAqF6uGdAoiFt99+W7169VKvXr0kSWPGjFGvXr00btw4SdLcuXM1fPhw3XjjjerSpYsGDRqkt956S+3bt5f0/er+s88+q9atW+v000/XwIED1bVrVz3yyCMxycfpVwAN2h133KFGjRpp3Lhx+vzzz5WRkaE//elPSk1N1dKlSzVq1CiddNJJSk1N1YUXXqj77rsvuu8VV1yhd955R8OHD1ejRo00evRonXHGGbV6/UaNGumBBx7Q+PHjNW7cOJ122mkKBoMxfpcAYiEnJ+egSy5+LCkpSUVFRSoqKqpxTrt27bRo0aL6iCePfah0AAAAcAVOvwIAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAb4/xVzwo7mVWfDAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], "source": [ "histogram_output = !python histogram_global.py output\n", "histogram = np.loadtxt(histogram_output, delimiter=\",\")\n", @@ -236,11 +218,23 @@ "\n", "length = os.path.getsize(\"books__15m.txt\")\n", "print(f\"Characters in dataset: {length / 1e6:.1f} MB\")" - ] + ], + "execution_count": null, + "outputs": [ + { + "output_type": "display_data", + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAnUAAAHWCAYAAAARl3+JAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAMUNJREFUeJzt3Xt4FOXB/vF7E5IJAXY5J6EuRAERDAQU4cUTUdFoqS3WKqJAxFOLhxJQ1OhPYrB1AUXQSks5g/jWE4qnCiq6iIigEJR4QEUCqXJSdBeiLjSZ3x++rEYSJHGzwzz5fq7ruS539pnsvc4V9/aZmY3Htm1bAAAAcLUEpwMAAADgl6PUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABGjkdwEmVlZX6/PPP1axZM3k8HqfjAACABsy2be3Zs0ft2rVTQkLt190adKn7/PPP5ff7nY4BAAAQVVZWpqOOOqrW+zXoUtesWTNJ3//L83q9DqcBAAANWTgclt/vj/aT2mrQpe7AKVev10upAwAAR4S6XhLGjRIAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABggEZOBzgSZBUuVYKV6nQMAABwBCqdMNDpCIeFlToAAAADUOoAAAAMUOtSt2vXLo0cOVLt27eXZVlKT09Xbm6uVq5cKUnKzMyUx+ORx+NRkyZNdMIJJ+jxxx+v8jO+/fZbtWzZUq1bt1YkEqn2dRYtWqScnBz5fD41bdpUPXr00Pjx47V7925J0rx586Kv8+ORkpJS27cEAADgerUudRdeeKGKi4s1f/58ffTRR3rmmWeUk5OjL7/8Mjpn/Pjx2rZtm4qLi3XSSSdp8ODBeuONN6LPL1q0SMcff7yOO+44LV68+KDXuP322zV48GCddNJJeuGFF1RSUqLJkyfrnXfe0UMPPRSd5/V6tW3btipjy5YttX1LAAAArlerGyW+/vprrVixQsFgUP3795ckdejQQX369Kkyr1mzZkpPT1d6erqmTZumhQsX6tlnn9XJJ58sSZo9e7aGDh0q27Y1e/ZsDR48OLrvmjVrdPfdd2vq1KkaNWpUdHtmZqbOPvtsff3119FtHo9H6enptX7TAAAApqnVSl3Tpk3VtGlTLV68uMbTpj/VqFEjJSUlad++fZKkTZs2adWqVbr44ot18cUXa8WKFVVW1x5++GE1bdpU1157bbU/r3nz5rWJDAAA0CDUqtQ1atRI8+bN0/z589W8eXOdcsopuu222/Tuu+9WO3/fvn0KBAIKhUI688wzJUlz5szReeedpxYtWqhly5bKzc3V3Llzo/t8/PHHOuaYY5SUlPSzeUKhULRoHhjnnXdejfMjkYjC4XCVAQAAYII6XVP3+eef65lnntG5556rYDCoE044QfPmzYvOueWWW9S0aVOlpqZq4sSJmjBhggYOHKiKigrNnz9fQ4cOjc4dOnSo5s2bp8rKSkmSbduHnaVZs2Zav359lTFr1qwa5wcCAfl8vujw+/21ffsAAABHJI9dmxZVg6uuukovvfSStmzZoszMTA0dOlSXX365mjZtqrS0NHk8HknSv//9bw0cOFCJiYlV9q+oqNCLL76os88+W6NGjdKcOXO0e/fuQ67WzZs3T/n5+VWusfs5kUikymnjcDgsv98vf/5jfPkwAACoVry+fDgcDsvn8ykUCsnr9dZ6/5h8T123bt1UXl4efdy6dWt16tRJ6enp0UInfX+DxCWXXHLQ6toll1yi2bNnS5IuvfRS7d27V3//+9+rfa3alLifsixLXq+3ygAAADBBre5+/fLLL3XRRRfpiiuuUI8ePdSsWTO9/fbbmjRpkn73u98dct9du3bp2Wef1TPPPKOsrKwqzw0fPlwXXHCBdu/erb59++rmm2/WjTfeqM8++0wXXHCB2rVrp08++UTTp0/XqaeeGr0r1rZtbd++/aDXatu2rRIS+F5lAADQcNSq1DVt2lR9+/bVlClTtGnTJu3fv19+v19XX321brvttkPuu2DBAjVp0kRnnXXWQc+dddZZaty4sRYuXKg///nPmjhxok488URNmzZN06dPV2VlpTp27Kg//OEPysvLi+4XDoeVkZFx0M/btm0bX3UCAAAalJhcU+dWB85dc00dAACoSYO6pg4AAADOqtXpV1OVFOVy0wQAAHA1VuoAAAAMQKkDAAAwAKUOAADAAFxTJymrcCl3vwKod/G6gw5Aw8RKHQAAgAEodQAAAAag1AEAABiAUgcAAGAAV5e6yspKBQIBHX300WrcuLGys7P1xBNPOB0L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zcFUAAAAznFlqQMAAEBVrrv7tT6UFOXK6/U6HQMAAKDOWKkDAAAwAKUOAADAAJx+lZRVuFQJVqrTMeqsdMJApyMAAACHsVIHAABgAEodAACAASh1AAAABqDUAQAAGMC1pW7BggVq1aqVIpFIle2DBg3SsGHDHEoFAADgDNeWuosuukgVFRV65plnott27typ559/XldccYWDyQAAAOLPtaWucePGuvTSSzV37tzotoULF6p9+/bKycmpdp9IJKJwOFxlAAAAmMC1pU6Srr76ar344ov67LPPJEnz5s3T5ZdfLo/HU+38QCAgn88XHX6/P55xAQAA6o2rS12vXr2UnZ2tBQsWaO3atXrvvfd0+eWX1zi/oKBAoVAoOsrKyuIXFgAAoB65/i9KXHXVVZo6dao+++wzDRgw4JCrb5ZlybKsOKYDAACID1ev1EnSpZdeqv/85z+aOXMmN0gAAIAGy/Wlzufz6cILL1TTpk01aNAgp+MAAAA4wvWlTpI+++wzXXbZZZxaBQAADZarr6n76quvFAwGFQwG9fe//93pOAAAAI5xdanr1auXvvrqK02cOFFdunSp888pKcqV1+uNYTIAAID4cnWpKy0tdToCAADAEcGIa+oAAAAaOkodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGKCR0wGcZNu2JCkcDjucBAAANHQH+siBflJbDbrUffnll5Ikv9/vcBIAAIDv7dmzRz6fr9b7NehS17JlS0nS1q1b6/QvD/EXDofl9/tVVlYmr9frdBwcBo6Z+3DM3Idj5j7VHTPbtrVnzx61a9euTj+zQZe6hITvLyn0+Xz8EriM1+vlmLkMx8x9OGbuwzFzn58es1+yyMSNEgAAAAag1AEAABigQZc6y7JUWFgoy7KcjoLDxDFzH46Z+3DM3Idj5j71ccw8dl3vmwUAAMARo0Gv1AEAAJiCUgcAAGAASh0AAIABjC9106ZNU2ZmplJSUtS3b1+tWbPmkPMff/xxHXfccUpJSVH37t3173//O05JcUBtjtm8efPk8XiqjJSUlDimbdhee+01nX/++WrXrp08Ho8WL178s/sEg0GdcMIJsixLnTp10rx58+o9J35Q22MWDAYP+h3zeDzavn17fAJDgUBAJ510kpo1a6a2bdtq0KBB2rhx48/ux+eZc+pyzGLxeWZ0qXv00Uc1ZswYFRYWat26dcrOzlZubq527txZ7fw33nhDQ4YM0ZVXXqni4mINGjRIgwYNUklJSZyTN1y1PWbS91/cuG3btujYsmVLHBM3bOXl5crOzta0adMOa/7mzZs1cOBAnXHGGVq/fr3y8/N11VVXaenSpfWcFAfU9pgdsHHjxiq/Z23btq2nhPip5cuX67rrrtObb76pl156Sfv379c555yj8vLyGvfh88xZdTlmUgw+z2yD9enTx77uuuuijysqKux27drZgUCg2vkXX3yxPXDgwCrb+vbta//xj3+s15z4QW2P2dy5c22fzxendDgUSfZTTz11yDk333yzffzxx1fZNnjwYDs3N7cek6Emh3PMXn31VVuS/dVXX8UlE37ezp07bUn28uXLa5zD59mR5XCOWSw+z4xdqdu3b5/Wrl2rAQMGRLclJCRowIABWrVqVbX7rFq1qsp8ScrNza1xPmKrLsdMkvbu3asOHTrI7/frd7/7nd577714xEUd8DvmXj179lRGRobOPvtsrVy50uk4DVooFJL0w98vrw6/a0eWwzlm0i//PDO21H3xxReqqKhQWlpale1paWk1Xguyffv2Ws1HbNXlmHXp0kVz5szR008/rYULF6qyslInn3yy/vOf/8QjMmqppt+xcDisb7/91qFUOJSMjAxNnz5dixYt0qJFi+T3+5WTk6N169Y5Ha1BqqysVH5+vk455RRlZWXVOI/PsyPH4R6zWHyeNYpFYMAp/fr1U79+/aKPTz75ZHXt2lX//Oc/dddddzmYDDBDly5d1KVLl+jjk08+WZs2bdKUKVP00EMPOZisYbruuutUUlKi119/3ekoOEyHe8xi8Xlm7Epd69atlZiYqB07dlTZvmPHDqWnp1e7T3p6eq3mI7bqcsx+KikpSb169dInn3xSHxHxC9X0O+b1etW4cWOHUqG2+vTpw++YA66//no999xzevXVV3XUUUcdci6fZ0eG2hyzn6rL55mxpS45OVknnniili1bFt1WWVmpZcuWVWnCP9avX78q8yXppZdeqnE+Yqsux+ynKioqtGHDBmVkZNRXTPwC/I6ZYf369fyOxZFt27r++uv11FNP6ZVXXtHRRx/9s/vwu+asuhyzn6rT59kvus3iCPfII4/YlmXZ8+bNs99//337mmuusZs3b25v377dtm3bHjZsmH3rrbdG569cudJu1KiRfe+999offPCBXVhYaCclJdkbNmxw6i00OLU9ZkVFRfbSpUvtTZs22WvXrrUvueQSOyUlxX7vvfecegsNyp49e+zi4mK7uLjYlmTfd999dnFxsb1lyxbbtm371ltvtYcNGxad/+mnn9qpqan22LFj7Q8++MCeNm2anZiYaC9ZssSpt9Dg1PaYTZkyxV68eLH98ccf2xs2bLBHjRplJyQk2C+//LJTb6HBGTlypO3z+exgMGhv27YtOr755pvoHD7Pjix1OWax+DwzutTZtm3/7W9/s9u3b28nJyfbffr0sd98883oc/3797fz8vKqzH/sscfsY4891k5OTraPP/54+/nnn49zYtTmmOXn50fnpqWl2b/+9a/tdevWOZC6YTrwdRc/HQeOUV5ent2/f/+D9unZs6ednJxsH3PMMfbcuXPjnrshq+0xmzhxot2xY0c7JSXFbtmypZ2Tk2O/8sorzoRvoKo7XpKq/O7weXZkqcsxi8Xnmef/XhwAAAAuZuw1dQAAAA0JpQ4AAMAAlDoAAAADUOoAAAAMQKkDAAAwAKUOAADAAJQ6AAAAA1DqAAAADECpAwAAOAyvvfaazj//fLVr104ej0eLFy+u9c+wbVv33nuvjj32WFmWpV/96lf661//GpN8lDoAcFBpaak8Ho/Wr1/vdBQAP6O8vFzZ2dmaNm1anX/GqFGjNGvWLN1777368MMP9cwzz6hPnz4xydcoJj8FAADAcOedd57OO++8Gp+PRCK6/fbb9a9//Utff/21srKyNHHiROXk5EiSPvjgA/3jH/9QSUmJunTpIkk6+uijY5aPlToADVplZaUmTZqkTp06ybIstW/fPnoqZMOGDTrzzDPVuHFjtWrVStdcc4327t0b3TcnJ0f5+flVft6gQYN0+eWXRx9nZmbq7rvv1hVXXKFmzZqpffv2mjFjRvT5A/9B79WrlzweT/Q//gDc5/rrr9eqVav0yCOP6N1339VFF12kc889Vx9//LEk6dlnn9Uxxxyj5557TkcffbQyMzN11VVXaffu3TF5fUodgAatoKBAEyZM0B133KH3339f//u//6u0tDSVl5crNzdXLVq00FtvvaXHH39cL7/8sq6//vpav8bkyZPVu3dvFRcX69prr9XIkSO1ceNGSdKaNWskSS+//LK2bdumJ598MqbvD0B8bN26VXPnztXjjz+u0047TR07dtRNN92kU089VXPnzpUkffrpp9qyZYsef/xxLViwQPPmzdPatWv1hz/8ISYZOP0KoMHas2eP7r//fj344IPKy8uTJHXs2FGnnnqqZs6cqe+++04LFixQkyZNJEkPPvigzj//fE2cOFFpaWmH/Tq//vWvde2110qSbrnlFk2ZMkWvvvqqunTpojZt2kiSWrVqpfT09Bi/QwDxsmHDBlVUVOjYY4+tsj0SiahVq1aSvj8zEIlEtGDBgui82bNn68QTT9TGjRujp2TrilIHoMH64IMPFIlEdNZZZ1X7XHZ2drTQSdIpp5yiyspKbdy4sValrkePHtF/9ng8Sk9P186dO39ZeABHlL179yoxMVFr165VYmJileeaNm0qScrIyFCjRo2qFL+uXbtK+n6lj1IHAHXUuHHjX7R/QkKCbNuusm3//v0HzUtKSqry2OPxqLKy8he9NoAjS69evVRRUaGdO3fqtNNOq3bOKaecov/+97/atGmTOnbsKEn66KOPJEkdOnT4xRm4pg5Ag9W5c2c1btxYy5YtO+i5rl276p133lF5eXl028qVK5WQkBD9v+k2bdpo27Zt0ecrKipUUlJSqwzJycnRfQEc2fbu3av169dHv4Jo8+bNWr9+vbZu3apjjz1Wl112mYYPH64nn3xSmzdv1po1axQIBPT8889LkgYMGKATTjhBV1xxhYqLi7V27Vr98Y9/1Nlnn33Qadu6oNQBaLBSUlJ0yy236Oabb9aCBQu0adMmvfnmm5o9e7Yuu+wypaSkKC8vTyUlJXr11Vd1ww03aNiwYdFTr2eeeaaef/55Pf/88/rwww81cuRIff3117XK0LZtWzVu3FhLlizRjh07FAqF6uGdAoiFt99+W7169VKvXr0kSWPGjFGvXr00btw4SdLcuXM1fPhw3XjjjerSpYsGDRqkt956S+3bt5f0/er+s88+q9atW+v000/XwIED1bVrVz3yyCMxycfpVwAN2h133KFGjRpp3Lhx+vzzz5WRkaE//elPSk1N1dKlSzVq1CiddNJJSk1N1YUXXqj77rsvuu8VV1yhd955R8OHD1ejRo00evRonXHGGbV6/UaNGumBBx7Q+PHjNW7cOJ122mkKBoMxfpcAYiEnJ+egSy5+LCkpSUVFRSoqKqpxTrt27bRo0aL6iCePfah0AAAAcAVOvwIAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAb4/xVzwo7mVWfDAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + } + } + ], + "id": "815cb072-b3a0-47aa-af6c-dd66c626a440" }, { "cell_type": "markdown", - "id": "b14fa522-b41b-4538-8c34-ecc355e55116", "metadata": { "id": "b14fa522-b41b-4538-8c34-ecc355e55116" }, @@ -261,11 +255,11 @@ "- Thread 1 stores `new_count` to the bin, setting it to 1, and losing the increment from thread 0!\n", "\n", "To fix this, we need to use atomic operations. `cuda.atomic.add(array, index, value)` will perform `array[index] += value` as a single indivisible operation. This will ensure that no increments get lost." - ] + ], + "id": "b14fa522-b41b-4538-8c34-ecc355e55116" }, { "cell_type": "markdown", - "id": "08f4dded-26a7-4ef8-b981-e00c569ca4d0", "metadata": { "id": "08f4dded-26a7-4ef8-b981-e00c569ca4d0" }, @@ -273,12 +267,11 @@ "## 4. Profiling the Naive Solution\n", "\n", "Now let's profile our code." - ] + ], + "id": "08f4dded-26a7-4ef8-b981-e00c569ca4d0" }, { "cell_type": "code", - "execution_count": null, - "id": "8dbd226c-66f2-43df-868a-6b024b1de24c", "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -286,9 +279,13 @@ "id": "8dbd226c-66f2-43df-868a-6b024b1de24c", "outputId": "a082d13a-8e86-436d-c3de-8351f337d824" }, + "source": [ + "!ncu -f --kernel-name regex:histogram_global --set full -o histogram_global python histogram_global.py\n", + "histogram_global_csv = !ncu --import histogram_global.ncu-rep --csv" + ], + "execution_count": null, "outputs": [ { - "name": "stdout", "output_type": "stream", "text": [ "==PROF== Connected to process 1318 (/usr/bin/python3.12)\n", @@ -298,15 +295,10 @@ ] } ], - "source": [ - "!ncu -f --kernel-name regex:histogram_global --set full -o histogram_global python histogram_global.py\n", - "histogram_global_csv = !ncu --import histogram_global.ncu-rep --csv" - ] + "id": "8dbd226c-66f2-43df-868a-6b024b1de24c" }, { "cell_type": "code", - "execution_count": 8, - "id": "ad12380e-253b-4410-ab34-9479411fdf81", "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -350,18 +342,22 @@ "id": "ad12380e-253b-4410-ab34-9479411fdf81", "outputId": "f2326771-7c13-46fa-a489-bf494d76cc1e" }, + "source": [ + "nsightful.display_ncu_csv_in_notebook(histogram_global_csv)" + ], + "execution_count": 8, "outputs": [ { + "output_type": "display_data", "data": { "application/javascript": "window[\"de51e67e-9ed4-11f0-a7c3-0242ac1c000c\"] = google.colab.output.setIframeHeight(-1, true, {\"interactive\": true, \"maxHeight\": 99999});\n//# sourceURL=js_65154ce01f", "text/plain": [ "" ] - }, - "metadata": {}, - "output_type": "display_data" + } }, { + "output_type": "display_data", "data": { "text/html": [ "\n", @@ -408,11 +404,10 @@ "text/plain": [ "" ] - }, - "metadata": {}, - "output_type": "display_data" + } }, { + "output_type": "display_data", "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "a21053863aa949e484f30f47bcf5c045", @@ -422,11 +417,10 @@ "text/plain": [ "Dropdown(description='Kernel:', layout=Layout(width='400px'), options=('histogram_global',), style=Description…" ] - }, - "metadata": {}, - "output_type": "display_data" + } }, { + "output_type": "display_data", "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "804c25036085410fbbcba82f19667d1d", @@ -436,18 +430,13 @@ "text/plain": [ "Output()" ] - }, - "metadata": {}, - "output_type": "display_data" + } } ], - "source": [ - "nsightful.display_ncu_csv_in_notebook(histogram_global_csv)" - ] + "id": "ad12380e-253b-4410-ab34-9479411fdf81" }, { "cell_type": "markdown", - "id": "e1f72831-780f-4cf5-8ff1-2092ecb193d9", "metadata": { "id": "e1f72831-780f-4cf5-8ff1-2092ecb193d9" }, @@ -461,12 +450,11 @@ "This is happening due to contention - we have hundreds of thousands of threads performing atomic updates to just 256 bins of a global histogram. All of those atomic operations have to happen in order, so they are serialized by the memory subsystem, destroying our parallelism.\n", "\n", "Instead, we can construct a local histogram for each block, which we will update atomically within the block. Then, we synchronize all of the threads within the block, and we perform atomic updates of the global histogram with the aggregate counts t" - ] + ], + "id": "e1f72831-780f-4cf5-8ff1-2092ecb193d9" }, { "cell_type": "code", - "execution_count": 9, - "id": "cf7c9865-646a-4bbd-9b41-61cadfc5484c", "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -474,15 +462,6 @@ "id": "cf7c9865-646a-4bbd-9b41-61cadfc5484c", "outputId": "851b231f-4431-4cf3-b857-c5a966a7162f" }, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Writing histogram_localized.py\n" - ] - } - ], "source": [ "%%writefile histogram_localized.py\n", "\n", @@ -549,24 +528,32 @@ " launch(check=False, output=False) # `ncu` slows things down; so just launch once when running under it.\n", "else:\n", " launch(check=True, output=False)\n", - " D = cpx.profiler.benchmark(launch, (False, False), n_repeat=15, n_warmup=4).gpu_times[0]\n", - " print(f\"{D.mean():.3g} s ± {(D.std() / D.mean()):.2%} (mean ± relative stdev of {D.size} runs)\")" - ] + " T = cpx.profiler.benchmark(launch, (False, False), n_repeat=15, n_warmup=4).gpu_times[0]\n", + " print(f\"{T.mean() * 1000:.3g} ms ± {(T.std() / T.mean()):.2%} (mean ± relative stdev of {T.size} runs)\")" + ], + "execution_count": 9, + "outputs": [ + { + "output_type": "stream", + "text": [ + "Writing histogram_localized.py\n" + ] + } + ], + "id": "cf7c9865-646a-4bbd-9b41-61cadfc5484c" }, { "cell_type": "markdown", - "id": "d2de69a4-644d-481d-b2c7-a8674616e9e2", "metadata": { "id": "d2de69a4-644d-481d-b2c7-a8674616e9e2" }, "source": [ "Let's make sure it runs correctly:" - ] + ], + "id": "d2de69a4-644d-481d-b2c7-a8674616e9e2" }, { "cell_type": "code", - "execution_count": 10, - "id": "1b30e9b3-5a4c-4181-b642-b7def5e9f258", "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -574,23 +561,22 @@ "id": "1b30e9b3-5a4c-4181-b642-b7def5e9f258", "outputId": "3a04d1d0-3409-441c-af2a-3138e9dec324" }, + "source": [ + "!python histogram_localized.py" + ], + "execution_count": 10, "outputs": [ { - "name": "stdout", "output_type": "stream", "text": [ "0.000714 s ± 2.97% (mean ± relative stdev of 15 runs)\n" ] } ], - "source": [ - "!python histogram_localized.py" - ] + "id": "1b30e9b3-5a4c-4181-b642-b7def5e9f258" }, { "cell_type": "code", - "execution_count": null, - "id": "73f0c3cd-349b-490f-b6bd-7afbeb442fff", "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -599,18 +585,6 @@ "id": "73f0c3cd-349b-490f-b6bd-7afbeb442fff", "outputId": "01324ca9-72c3-4317-a5be-f0bf144b7b7c" }, - "outputs": [ - { - "data": { - "image/png": "iVBORw0KGgoAAAANSUhEUgAAAnUAAAHWCAYAAAARl3+JAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAMUNJREFUeJzt3Xt4FOXB/vF7E5IJAXY5J6EuRAERDAQU4cUTUdFoqS3WKqJAxFOLhxJQ1OhPYrB1AUXQSks5g/jWE4qnCiq6iIigEJR4QEUCqXJSdBeiLjSZ3x++rEYSJHGzwzz5fq7ruS539pnsvc4V9/aZmY3Htm1bAAAAcLUEpwMAAADgl6PUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABGjkdwEmVlZX6/PPP1axZM3k8HqfjAACABsy2be3Zs0ft2rVTQkLt190adKn7/PPP5ff7nY4BAAAQVVZWpqOOOqrW+zXoUtesWTNJ3//L83q9DqcBAAANWTgclt/vj/aT2mrQpe7AKVev10upAwAAR4S6XhLGjRIAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABggEZOBzgSZBUuVYKV6nQMAABwBCqdMNDpCIeFlToAAAADUOoAAAAMUOtSt2vXLo0cOVLt27eXZVlKT09Xbm6uVq5cKUnKzMyUx+ORx+NRkyZNdMIJJ+jxxx+v8jO+/fZbtWzZUq1bt1YkEqn2dRYtWqScnBz5fD41bdpUPXr00Pjx47V7925J0rx586Kv8+ORkpJS27cEAADgerUudRdeeKGKi4s1f/58ffTRR3rmmWeUk5OjL7/8Mjpn/Pjx2rZtm4qLi3XSSSdp8ODBeuONN6LPL1q0SMcff7yOO+44LV68+KDXuP322zV48GCddNJJeuGFF1RSUqLJkyfrnXfe0UMPPRSd5/V6tW3btipjy5YttX1LAAAArlerGyW+/vprrVixQsFgUP3795ckdejQQX369Kkyr1mzZkpPT1d6erqmTZumhQsX6tlnn9XJJ58sSZo9e7aGDh0q27Y1e/ZsDR48OLrvmjVrdPfdd2vq1KkaNWpUdHtmZqbOPvtsff3119FtHo9H6enptX7TAAAApqnVSl3Tpk3VtGlTLV68uMbTpj/VqFEjJSUlad++fZKkTZs2adWqVbr44ot18cUXa8WKFVVW1x5++GE1bdpU1157bbU/r3nz5rWJDAAA0CDUqtQ1atRI8+bN0/z589W8eXOdcsopuu222/Tuu+9WO3/fvn0KBAIKhUI688wzJUlz5szReeedpxYtWqhly5bKzc3V3Llzo/t8/PHHOuaYY5SUlPSzeUKhULRoHhjnnXdejfMjkYjC4XCVAQAAYII6XVP3+eef65lnntG5556rYDCoE044QfPmzYvOueWWW9S0aVOlpqZq4sSJmjBhggYOHKiKigrNnz9fQ4cOjc4dOnSo5s2bp8rKSkmSbduHnaVZs2Zav359lTFr1qwa5wcCAfl8vujw+/21ffsAAABHJI9dmxZVg6uuukovvfSStmzZoszMTA0dOlSXX365mjZtqrS0NHk8HknSv//9bw0cOFCJiYlV9q+oqNCLL76os88+W6NGjdKcOXO0e/fuQ67WzZs3T/n5+VWusfs5kUikymnjcDgsv98vf/5jfPkwAACoVry+fDgcDsvn8ykUCsnr9dZ6/5h8T123bt1UXl4efdy6dWt16tRJ6enp0UInfX+DxCWXXHLQ6toll1yi2bNnS5IuvfRS7d27V3//+9+rfa3alLifsixLXq+3ygAAADBBre5+/fLLL3XRRRfpiiuuUI8ePdSsWTO9/fbbmjRpkn73u98dct9du3bp2Wef1TPPPKOsrKwqzw0fPlwXXHCBdu/erb59++rmm2/WjTfeqM8++0wXXHCB2rVrp08++UTTp0/XqaeeGr0r1rZtbd++/aDXatu2rRIS+F5lAADQcNSq1DVt2lR9+/bVlClTtGnTJu3fv19+v19XX321brvttkPuu2DBAjVp0kRnnXXWQc+dddZZaty4sRYuXKg///nPmjhxok488URNmzZN06dPV2VlpTp27Kg//OEPysvLi+4XDoeVkZFx0M/btm0bX3UCAAAalJhcU+dWB85dc00dAACoSYO6pg4AAADOqtXpV1OVFOVy0wQAAHA1VuoAAAAMQKkDAAAwAKUOAADAAFxTJymrcCl3vwKod/G6gw5Aw8RKHQAAgAEodQAAAAag1AEAABiAUgcAAGAAV5e6yspKBQIBHX300WrcuLGys7P1xBNPOB0L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zcFUAAAAznFlqQMAAEBVrrv7tT6UFOXK6/U6HQMAAKDOWKkDAAAwAKUOAADAAJx+lZRVuFQJVqrTMeqsdMJApyMAAACHsVIHAABgAEodAACAASh1AAAABqDUAQAAGMC1pW7BggVq1aqVIpFIle2DBg3SsGHDHEoFAADgDNeWuosuukgVFRV65plnott27typ559/XldccYWDyQAAAOLPtaWucePGuvTSSzV37tzotoULF6p9+/bKycmpdp9IJKJwOFxlAAAAmMC1pU6Srr76ar344ov67LPPJEnz5s3T5ZdfLo/HU+38QCAgn88XHX6/P55xAQAA6o2rS12vXr2UnZ2tBQsWaO3atXrvvfd0+eWX1zi/oKBAoVAoOsrKyuIXFgAAoB65/i9KXHXVVZo6dao+++wzDRgw4JCrb5ZlybKsOKYDAACID1ev1EnSpZdeqv/85z+aOXMmN0gAAIAGy/Wlzufz6cILL1TTpk01aNAgp+MAAAA4wvWlTpI+++wzXXbZZZxaBQAADZarr6n76quvFAwGFQwG9fe//93pOAAAAI5xdanr1auXvvrqK02cOFFdunSp888pKcqV1+uNYTIAAID4cnWpKy0tdToCAADAEcGIa+oAAAAaOkodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGKCR0wGcZNu2JCkcDjucBAAANHQH+siBflJbDbrUffnll5Ikv9/vcBIAAIDv7dmzRz6fr9b7NehS17JlS0nS1q1b6/QvD/EXDofl9/tVVlYmr9frdBwcBo6Z+3DM3Idj5j7VHTPbtrVnzx61a9euTj+zQZe6hITvLyn0+Xz8EriM1+vlmLkMx8x9OGbuwzFzn58es1+yyMSNEgAAAAag1AEAABigQZc6y7JUWFgoy7KcjoLDxDFzH46Z+3DM3Idj5j71ccw8dl3vmwUAAMARo0Gv1AEAAJiCUgcAAGAASh0AAIABjC9106ZNU2ZmplJSUtS3b1+tWbPmkPMff/xxHXfccUpJSVH37t3173//O05JcUBtjtm8efPk8XiqjJSUlDimbdhee+01nX/++WrXrp08Ho8WL178s/sEg0GdcMIJsixLnTp10rx58+o9J35Q22MWDAYP+h3zeDzavn17fAJDgUBAJ510kpo1a6a2bdtq0KBB2rhx48/ux+eZc+pyzGLxeWZ0qXv00Uc1ZswYFRYWat26dcrOzlZubq527txZ7fw33nhDQ4YM0ZVXXqni4mINGjRIgwYNUklJSZyTN1y1PWbS91/cuG3btujYsmVLHBM3bOXl5crOzta0adMOa/7mzZs1cOBAnXHGGVq/fr3y8/N11VVXaenSpfWcFAfU9pgdsHHjxiq/Z23btq2nhPip5cuX67rrrtObb76pl156Sfv379c555yj8vLyGvfh88xZdTlmUgw+z2yD9enTx77uuuuijysqKux27drZgUCg2vkXX3yxPXDgwCrb+vbta//xj3+s15z4QW2P2dy5c22fzxendDgUSfZTTz11yDk333yzffzxx1fZNnjwYDs3N7cek6Emh3PMXn31VVuS/dVXX8UlE37ezp07bUn28uXLa5zD59mR5XCOWSw+z4xdqdu3b5/Wrl2rAQMGRLclJCRowIABWrVqVbX7rFq1qsp8ScrNza1xPmKrLsdMkvbu3asOHTrI7/frd7/7nd577714xEUd8DvmXj179lRGRobOPvtsrVy50uk4DVooFJL0w98vrw6/a0eWwzlm0i//PDO21H3xxReqqKhQWlpale1paWk1Xguyffv2Ws1HbNXlmHXp0kVz5szR008/rYULF6qyslInn3yy/vOf/8QjMmqppt+xcDisb7/91qFUOJSMjAxNnz5dixYt0qJFi+T3+5WTk6N169Y5Ha1BqqysVH5+vk455RRlZWXVOI/PsyPH4R6zWHyeNYpFYMAp/fr1U79+/aKPTz75ZHXt2lX//Oc/dddddzmYDDBDly5d1KVLl+jjk08+WZs2bdKUKVP00EMPOZisYbruuutUUlKi119/3ekoOEyHe8xi8Xlm7Epd69atlZiYqB07dlTZvmPHDqWnp1e7T3p6eq3mI7bqcsx+KikpSb169dInn3xSHxHxC9X0O+b1etW4cWOHUqG2+vTpw++YA66//no999xzevXVV3XUUUcdci6fZ0eG2hyzn6rL55mxpS45OVknnniili1bFt1WWVmpZcuWVWnCP9avX78q8yXppZdeqnE+Yqsux+ynKioqtGHDBmVkZNRXTPwC/I6ZYf369fyOxZFt27r++uv11FNP6ZVXXtHRRx/9s/vwu+asuhyzn6rT59kvus3iCPfII4/YlmXZ8+bNs99//337mmuusZs3b25v377dtm3bHjZsmH3rrbdG569cudJu1KiRfe+999offPCBXVhYaCclJdkbNmxw6i00OLU9ZkVFRfbSpUvtTZs22WvXrrUvueQSOyUlxX7vvfecegsNyp49e+zi4mK7uLjYlmTfd999dnFxsb1lyxbbtm371ltvtYcNGxad/+mnn9qpqan22LFj7Q8++MCeNm2anZiYaC9ZssSpt9Dg1PaYTZkyxV68eLH98ccf2xs2bLBHjRplJyQk2C+//LJTb6HBGTlypO3z+exgMGhv27YtOr755pvoHD7Pjix1OWax+DwzutTZtm3/7W9/s9u3b28nJyfbffr0sd98883oc/3797fz8vKqzH/sscfsY4891k5OTraPP/54+/nnn49zYtTmmOXn50fnpqWl2b/+9a/tdevWOZC6YTrwdRc/HQeOUV5ent2/f/+D9unZs6ednJxsH3PMMfbcuXPjnrshq+0xmzhxot2xY0c7JSXFbtmypZ2Tk2O/8sorzoRvoKo7XpKq/O7weXZkqcsxi8Xnmef/XhwAAAAuZuw1dQAAAA0JpQ4AAMAAlDoAAAADUOoAAAAMQKkDAAAwAKUOAADAAJQ6AAAAA1DqAAAADECpAwAAOAyvvfaazj//fLVr104ej0eLFy+u9c+wbVv33nuvjj32WFmWpV/96lf661//GpN8lDoAcFBpaak8Ho/Wr1/vdBQAP6O8vFzZ2dmaNm1anX/GqFGjNGvWLN1777368MMP9cwzz6hPnz4xydcoJj8FAADAcOedd57OO++8Gp+PRCK6/fbb9a9//Utff/21srKyNHHiROXk5EiSPvjgA/3jH/9QSUmJunTpIkk6+uijY5aPlToADVplZaUmTZqkTp06ybIstW/fPnoqZMOGDTrzzDPVuHFjtWrVStdcc4327t0b3TcnJ0f5+flVft6gQYN0+eWXRx9nZmbq7rvv1hVXXKFmzZqpffv2mjFjRvT5A/9B79WrlzweT/Q//gDc5/rrr9eqVav0yCOP6N1339VFF12kc889Vx9//LEk6dlnn9Uxxxyj5557TkcffbQyMzN11VVXaffu3TF5fUodgAatoKBAEyZM0B133KH3339f//u//6u0tDSVl5crNzdXLVq00FtvvaXHH39cL7/8sq6//vpav8bkyZPVu3dvFRcX69prr9XIkSO1ceNGSdKaNWskSS+//LK2bdumJ598MqbvD0B8bN26VXPnztXjjz+u0047TR07dtRNN92kU089VXPnzpUkffrpp9qyZYsef/xxLViwQPPmzdPatWv1hz/8ISYZOP0KoMHas2eP7r//fj344IPKy8uTJHXs2FGnnnqqZs6cqe+++04LFixQkyZNJEkPPvigzj//fE2cOFFpaWmH/Tq//vWvde2110qSbrnlFk2ZMkWvvvqqunTpojZt2kiSWrVqpfT09Bi/QwDxsmHDBlVUVOjYY4+tsj0SiahVq1aSvj8zEIlEtGDBgui82bNn68QTT9TGjRujp2TrilIHoMH64IMPFIlEdNZZZ1X7XHZ2drTQSdIpp5yiyspKbdy4sValrkePHtF/9ng8Sk9P186dO39ZeABHlL179yoxMVFr165VYmJileeaNm0qScrIyFCjRo2qFL+uXbtK+n6lj1IHAHXUuHHjX7R/QkKCbNuusm3//v0HzUtKSqry2OPxqLKy8he9NoAjS69evVRRUaGdO3fqtNNOq3bOKaecov/+97/atGmTOnbsKEn66KOPJEkdOnT4xRm4pg5Ag9W5c2c1btxYy5YtO+i5rl276p133lF5eXl028qVK5WQkBD9v+k2bdpo27Zt0ecrKipUUlJSqwzJycnRfQEc2fbu3av169dHv4Jo8+bNWr9+vbZu3apjjz1Wl112mYYPH64nn3xSmzdv1po1axQIBPT8889LkgYMGKATTjhBV1xxhYqLi7V27Vr98Y9/1Nlnn33Qadu6oNQBaLBSUlJ0yy236Oabb9aCBQu0adMmvfnmm5o9e7Yuu+wypaSkKC8vTyUlJXr11Vd1ww03aNiwYdFTr2eeeaaef/55Pf/88/rwww81cuRIff3117XK0LZtWzVu3FhLlizRjh07FAqF6uGdAoiFt99+W7169VKvXr0kSWPGjFGvXr00btw4SdLcuXM1fPhw3XjjjerSpYsGDRqkt956S+3bt5f0/er+s88+q9atW+v000/XwIED1bVrVz3yyCMxycfpVwAN2h133KFGjRpp3Lhx+vzzz5WRkaE//elPSk1N1dKlSzVq1CiddNJJSk1N1YUXXqj77rsvuu8VV1yhd955R8OHD1ejRo00evRonXHGGbV6/UaNGumBBx7Q+PHjNW7cOJ122mkKBoMxfpcAYiEnJ+egSy5+LCkpSUVFRSoqKqpxTrt27bRo0aL6iCePfah0AAAAcAVOvwIAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAb4/xVzwo7mVWfDAAAAAElFTkSuQmCC", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" - } - ], "source": [ "histogram_output = !python histogram_localized.py output\n", "histogram = np.loadtxt(histogram_output, delimiter=\",\")\n", @@ -626,22 +600,33 @@ "\n", "length = os.path.getsize(\"books__15m.txt\")\n", "print(f\"Characters in dataset: {length / 1e6:.1f} MB\")" - ] + ], + "execution_count": null, + "outputs": [ + { + "output_type": "display_data", + "data": { + "image/png": "iVBORw0KGgoAAAANSUhEUgAAAnUAAAHWCAYAAAARl3+JAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjAsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvlHJYcgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAMUNJREFUeJzt3Xt4FOXB/vF7E5IJAXY5J6EuRAERDAQU4cUTUdFoqS3WKqJAxFOLhxJQ1OhPYrB1AUXQSks5g/jWE4qnCiq6iIigEJR4QEUCqXJSdBeiLjSZ3x++rEYSJHGzwzz5fq7ruS539pnsvc4V9/aZmY3Htm1bAAAAcLUEpwMAAADgl6PUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABGjkdwEmVlZX6/PPP1axZM3k8HqfjAACABsy2be3Zs0ft2rVTQkLt190adKn7/PPP5ff7nY4BAAAQVVZWpqOOOqrW+zXoUtesWTNJ3//L83q9DqcBAAANWTgclt/vj/aT2mrQpe7AKVev10upAwAAR4S6XhLGjRIAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABggEZOBzgSZBUuVYKV6nQMAABwBCqdMNDpCIeFlToAAAADUOoAAAAMUOtSt2vXLo0cOVLt27eXZVlKT09Xbm6uVq5cKUnKzMyUx+ORx+NRkyZNdMIJJ+jxxx+v8jO+/fZbtWzZUq1bt1YkEqn2dRYtWqScnBz5fD41bdpUPXr00Pjx47V7925J0rx586Kv8+ORkpJS27cEAADgerUudRdeeKGKi4s1f/58ffTRR3rmmWeUk5OjL7/8Mjpn/Pjx2rZtm4qLi3XSSSdp8ODBeuONN6LPL1q0SMcff7yOO+44LV68+KDXuP322zV48GCddNJJeuGFF1RSUqLJkyfrnXfe0UMPPRSd5/V6tW3btipjy5YttX1LAAAArlerGyW+/vprrVixQsFgUP3795ckdejQQX369Kkyr1mzZkpPT1d6erqmTZumhQsX6tlnn9XJJ58sSZo9e7aGDh0q27Y1e/ZsDR48OLrvmjVrdPfdd2vq1KkaNWpUdHtmZqbOPvtsff3119FtHo9H6enptX7TAAAApqnVSl3Tpk3VtGlTLV68uMbTpj/VqFEjJSUlad++fZKkTZs2adWqVbr44ot18cUXa8WKFVVW1x5++GE1bdpU1157bbU/r3nz5rWJDAAA0CDUqtQ1atRI8+bN0/z589W8eXOdcsopuu222/Tuu+9WO3/fvn0KBAIKhUI688wzJUlz5szReeedpxYtWqhly5bKzc3V3Llzo/t8/PHHOuaYY5SUlPSzeUKhULRoHhjnnXdejfMjkYjC4XCVAQAAYII6XVP3+eef65lnntG5556rYDCoE044QfPmzYvOueWWW9S0aVOlpqZq4sSJmjBhggYOHKiKigrNnz9fQ4cOjc4dOnSo5s2bp8rKSkmSbduHnaVZs2Zav359lTFr1qwa5wcCAfl8vujw+/21ffsAAABHJI9dmxZVg6uuukovvfSStmzZoszMTA0dOlSXX365mjZtqrS0NHk8HknSv//9bw0cOFCJiYlV9q+oqNCLL76os88+W6NGjdKcOXO0e/fuQ67WzZs3T/n5+VWusfs5kUikymnjcDgsv98vf/5jfPkwAACoVry+fDgcDsvn8ykUCsnr9dZ6/5h8T123bt1UXl4efdy6dWt16tRJ6enp0UInfX+DxCWXXHLQ6toll1yi2bNnS5IuvfRS7d27V3//+9+rfa3alLifsixLXq+3ygAAADBBre5+/fLLL3XRRRfpiiuuUI8ePdSsWTO9/fbbmjRpkn73u98dct9du3bp2Wef1TPPPKOsrKwqzw0fPlwXXHCBdu/erb59++rmm2/WjTfeqM8++0wXXHCB2rVrp08++UTTp0/XqaeeGr0r1rZtbd++/aDXatu2rRIS+F5lAADQcNSq1DVt2lR9+/bVlClTtGnTJu3fv19+v19XX321brvttkPuu2DBAjVp0kRnnXXWQc+dddZZaty4sRYuXKg///nPmjhxok488URNmzZN06dPV2VlpTp27Kg//OEPysvLi+4XDoeVkZFx0M/btm0bX3UCAAAalJhcU+dWB85dc00dAACoSYO6pg4AAADOqtXpV1OVFOVy0wQAAHA1VuoAAAAMQKkDAAAwAKUOAADAAFxTJymrcCl3vwKod/G6gw5Aw8RKHQAAgAEodQAAAAag1AEAABiAUgcAAGAAV5e6yspKBQIBHX300WrcuLGys7P1xBNPOB0L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zcFUAAAAznFlqQMAAEBVrrv7tT6UFOXK6/U6HQMAAKDOWKkDAAAwAKUOAADAAJx+lZRVuFQJVqrTMeqsdMJApyMAAACHsVIHAABgAEodAACAASh1AAAABqDUAQAAGMC1pW7BggVq1aqVIpFIle2DBg3SsGHDHEoFAADgDNeWuosuukgVFRV65plnott27typ559/XldccYWDyQAAAOLPtaWucePGuvTSSzV37tzotoULF6p9+/bKycmpdp9IJKJwOFxlAAAAmMC1pU6Srr76ar344ov67LPPJEnz5s3T5ZdfLo/HU+38QCAgn88XHX6/P55xAQAA6o2rS12vXr2UnZ2tBQsWaO3atXrvvfd0+eWX1zi/oKBAoVAoOsrKyuIXFgAAoB65/i9KXHXVVZo6dao+++wzDRgw4JCrb5ZlybKsOKYDAACID1ev1EnSpZdeqv/85z+aOXMmN0gAAIAGy/Wlzufz6cILL1TTpk01aNAgp+MAAAA4wvWlTpI+++wzXXbZZZxaBQAADZarr6n76quvFAwGFQwG9fe//93pOAAAAI5xdanr1auXvvrqK02cOFFdunSp888pKcqV1+uNYTIAAID4cnWpKy0tdToCAADAEcGIa+oAAAAaOkodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAag1AEAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGKCR0wGcZNu2JCkcDjucBAAANHQH+siBflJbDbrUffnll5Ikv9/vcBIAAIDv7dmzRz6fr9b7NehS17JlS0nS1q1b6/QvD/EXDofl9/tVVlYmr9frdBwcBo6Z+3DM3Idj5j7VHTPbtrVnzx61a9euTj+zQZe6hITvLyn0+Xz8EriM1+vlmLkMx8x9OGbuwzFzn58es1+yyMSNEgAAAAag1AEAABigQZc6y7JUWFgoy7KcjoLDxDFzH46Z+3DM3Idj5j71ccw8dl3vmwUAAMARo0Gv1AEAAJiCUgcAAGAASh0AAIABjC9106ZNU2ZmplJSUtS3b1+tWbPmkPMff/xxHXfccUpJSVH37t3173//O05JcUBtjtm8efPk8XiqjJSUlDimbdhee+01nX/++WrXrp08Ho8WL178s/sEg0GdcMIJsixLnTp10rx58+o9J35Q22MWDAYP+h3zeDzavn17fAJDgUBAJ510kpo1a6a2bdtq0KBB2rhx48/ux+eZc+pyzGLxeWZ0qXv00Uc1ZswYFRYWat26dcrOzlZubq527txZ7fw33nhDQ4YM0ZVXXqni4mINGjRIgwYNUklJSZyTN1y1PWbS91/cuG3btujYsmVLHBM3bOXl5crOzta0adMOa/7mzZs1cOBAnXHGGVq/fr3y8/N11VVXaenSpfWcFAfU9pgdsHHjxiq/Z23btq2nhPip5cuX67rrrtObb76pl156Sfv379c555yj8vLyGvfh88xZdTlmUgw+z2yD9enTx77uuuuijysqKux27drZgUCg2vkXX3yxPXDgwCrb+vbta//xj3+s15z4QW2P2dy5c22fzxendDgUSfZTTz11yDk333yzffzxx1fZNnjwYDs3N7cek6Emh3PMXn31VVuS/dVXX8UlE37ezp07bUn28uXLa5zD59mR5XCOWSw+z4xdqdu3b5/Wrl2rAQMGRLclJCRowIABWrVqVbX7rFq1qsp8ScrNza1xPmKrLsdMkvbu3asOHTrI7/frd7/7nd577714xEUd8DvmXj179lRGRobOPvtsrVy50uk4DVooFJL0w98vrw6/a0eWwzlm0i//PDO21H3xxReqqKhQWlpale1paWk1Xguyffv2Ws1HbNXlmHXp0kVz5szR008/rYULF6qyslInn3yy/vOf/8QjMmqppt+xcDisb7/91qFUOJSMjAxNnz5dixYt0qJFi+T3+5WTk6N169Y5Ha1BqqysVH5+vk455RRlZWXVOI/PsyPH4R6zWHyeNYpFYMAp/fr1U79+/aKPTz75ZHXt2lX//Oc/dddddzmYDDBDly5d1KVLl+jjk08+WZs2bdKUKVP00EMPOZisYbruuutUUlKi119/3ekoOEyHe8xi8Xlm7Epd69atlZiYqB07dlTZvmPHDqWnp1e7T3p6eq3mI7bqcsx+KikpSb169dInn3xSHxHxC9X0O+b1etW4cWOHUqG2+vTpw++YA66//no999xzevXVV3XUUUcdci6fZ0eG2hyzn6rL55mxpS45OVknnniili1bFt1WWVmpZcuWVWnCP9avX78q8yXppZdeqnE+Yqsux+ynKioqtGHDBmVkZNRXTPwC/I6ZYf369fyOxZFt27r++uv11FNP6ZVXXtHRRx/9s/vwu+asuhyzn6rT59kvus3iCPfII4/YlmXZ8+bNs99//337mmuusZs3b25v377dtm3bHjZsmH3rrbdG569cudJu1KiRfe+999offPCBXVhYaCclJdkbNmxw6i00OLU9ZkVFRfbSpUvtTZs22WvXrrUvueQSOyUlxX7vvfecegsNyp49e+zi4mK7uLjYlmTfd999dnFxsb1lyxbbtm371ltvtYcNGxad/+mnn9qpqan22LFj7Q8++MCeNm2anZiYaC9ZssSpt9Dg1PaYTZkyxV68eLH98ccf2xs2bLBHjRplJyQk2C+//LJTb6HBGTlypO3z+exgMGhv27YtOr755pvoHD7Pjix1OWax+DwzutTZtm3/7W9/s9u3b28nJyfbffr0sd98883oc/3797fz8vKqzH/sscfsY4891k5OTraPP/54+/nnn49zYtTmmOXn50fnpqWl2b/+9a/tdevWOZC6YTrwdRc/HQeOUV5ent2/f/+D9unZs6ednJxsH3PMMfbcuXPjnrshq+0xmzhxot2xY0c7JSXFbtmypZ2Tk2O/8sorzoRvoKo7XpKq/O7weXZkqcsxi8Xnmef/XhwAAAAuZuw1dQAAAA0JpQ4AAMAAlDoAAAADUOoAAAAMQKkDAAAwAKUOAADAAJQ6AAAAA1DqAAAADECpAwAAOAyvvfaazj//fLVr104ej0eLFy+u9c+wbVv33nuvjj32WFmWpV/96lf661//GpN8lDoAcFBpaak8Ho/Wr1/vdBQAP6O8vFzZ2dmaNm1anX/GqFGjNGvWLN1777368MMP9cwzz6hPnz4xydcoJj8FAADAcOedd57OO++8Gp+PRCK6/fbb9a9//Utff/21srKyNHHiROXk5EiSPvjgA/3jH/9QSUmJunTpIkk6+uijY5aPlToADVplZaUmTZqkTp06ybIstW/fPnoqZMOGDTrzzDPVuHFjtWrVStdcc4327t0b3TcnJ0f5+flVft6gQYN0+eWXRx9nZmbq7rvv1hVXXKFmzZqpffv2mjFjRvT5A/9B79WrlzweT/Q//gDc5/rrr9eqVav0yCOP6N1339VFF12kc889Vx9//LEk6dlnn9Uxxxyj5557TkcffbQyMzN11VVXaffu3TF5fUodgAatoKBAEyZM0B133KH3339f//u//6u0tDSVl5crNzdXLVq00FtvvaXHH39cL7/8sq6//vpav8bkyZPVu3dvFRcX69prr9XIkSO1ceNGSdKaNWskSS+//LK2bdumJ598MqbvD0B8bN26VXPnztXjjz+u0047TR07dtRNN92kU089VXPnzpUkffrpp9qyZYsef/xxLViwQPPmzdPatWv1hz/8ISYZOP0KoMHas2eP7r//fj344IPKy8uTJHXs2FGnnnqqZs6cqe+++04LFixQkyZNJEkPPvigzj//fE2cOFFpaWmH/Tq//vWvde2110qSbrnlFk2ZMkWvvvqqunTpojZt2kiSWrVqpfT09Bi/QwDxsmHDBlVUVOjYY4+tsj0SiahVq1aSvj8zEIlEtGDBgui82bNn68QTT9TGjRujp2TrilIHoMH64IMPFIlEdNZZZ1X7XHZ2drTQSdIpp5yiyspKbdy4sValrkePHtF/9ng8Sk9P186dO39ZeABHlL179yoxMVFr165VYmJileeaNm0qScrIyFCjRo2qFL+uXbtK+n6lj1IHAHXUuHHjX7R/QkKCbNuusm3//v0HzUtKSqry2OPxqLKy8he9NoAjS69evVRRUaGdO3fqtNNOq3bOKaecov/+97/atGmTOnbsKEn66KOPJEkdOnT4xRm4pg5Ag9W5c2c1btxYy5YtO+i5rl276p133lF5eXl028qVK5WQkBD9v+k2bdpo27Zt0ecrKipUUlJSqwzJycnRfQEc2fbu3av169dHv4Jo8+bNWr9+vbZu3apjjz1Wl112mYYPH64nn3xSmzdv1po1axQIBPT8889LkgYMGKATTjhBV1xxhYqLi7V27Vr98Y9/1Nlnn33Qadu6oNQBaLBSUlJ0yy236Oabb9aCBQu0adMmvfnmm5o9e7Yuu+wypaSkKC8vTyUlJXr11Vd1ww03aNiwYdFTr2eeeaaef/55Pf/88/rwww81cuRIff3117XK0LZtWzVu3FhLlizRjh07FAqF6uGdAoiFt99+W7169VKvXr0kSWPGjFGvXr00btw4SdLcuXM1fPhw3XjjjerSpYsGDRqkt956S+3bt5f0/er+s88+q9atW+v000/XwIED1bVrVz3yyCMxycfpVwAN2h133KFGjRpp3Lhx+vzzz5WRkaE//elPSk1N1dKlSzVq1CiddNJJSk1N1YUXXqj77rsvuu8VV1yhd955R8OHD1ejRo00evRonXHGGbV6/UaNGumBBx7Q+PHjNW7cOJ122mkKBoMxfpcAYiEnJ+egSy5+LCkpSUVFRSoqKqpxTrt27bRo0aL6iCePfah0AAAAcAVOvwIAABiAUgcAAGAASh0AAIABKHUAAAAGoNQBAAAYgFIHAABgAEodAACAASh1AAAABqDUAQAAGIBSBwAAYABKHQAAgAEodQAAAAb4/xVzwo7mVWfDAAAAAElFTkSuQmCC", + "text/plain": [ + "
" + ] + } + } + ], + "id": "73f0c3cd-349b-490f-b6bd-7afbeb442fff" }, { "cell_type": "markdown", - "id": "06857a59-20f3-4e39-aece-18cfbb514170", "metadata": { "id": "06857a59-20f3-4e39-aece-18cfbb514170" }, "source": [ "Now let's profile it:" - ] + ], + "id": "06857a59-20f3-4e39-aece-18cfbb514170" }, { "cell_type": "code", - "execution_count": null, - "id": "d637b6b1-fb0b-4807-b70b-c80227c0fd6f", "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -649,9 +634,13 @@ "id": "d637b6b1-fb0b-4807-b70b-c80227c0fd6f", "outputId": "e3790713-5a2e-4b51-dd2c-182b8bc27a5b" }, + "source": [ + "!ncu -f --kernel-name regex:histogram_localized --set full -o histogram_localized python histogram_localized.py\n", + "histogram_localized_csv = !ncu --import histogram_localized.ncu-rep --csv" + ], + "execution_count": null, "outputs": [ { - "name": "stdout", "output_type": "stream", "text": [ "==PROF== Connected to process 1492 (/usr/bin/python3.12)\n", @@ -661,15 +650,10 @@ ] } ], - "source": [ - "!ncu -f --kernel-name regex:histogram_localized --set full -o histogram_localized python histogram_localized.py\n", - "histogram_localized_csv = !ncu --import histogram_localized.ncu-rep --csv" - ] + "id": "d637b6b1-fb0b-4807-b70b-c80227c0fd6f" }, { "cell_type": "code", - "execution_count": 13, - "id": "114e8ff7-b6fb-42ad-abda-f6d53479c052", "metadata": { "colab": { "base_uri": "https://localhost:8080/", @@ -713,18 +697,22 @@ "id": "114e8ff7-b6fb-42ad-abda-f6d53479c052", "outputId": "1936cb5f-6708-45bc-ee39-e01ddc857b89" }, + "source": [ + "nsightful.display_ncu_csv_in_notebook(histogram_localized_csv)" + ], + "execution_count": 13, "outputs": [ { + "output_type": "display_data", "data": { "application/javascript": "window[\"e7c6b518-9ed4-11f0-a7c3-0242ac1c000c\"] = google.colab.output.setIframeHeight(-1, true, {\"interactive\": true, \"maxHeight\": 99999});\n//# sourceURL=js_abfc25056c", "text/plain": [ "" ] - }, - "metadata": {}, - "output_type": "display_data" + } }, { + "output_type": "display_data", "data": { "text/html": [ "\n", @@ -771,11 +759,10 @@ "text/plain": [ "" ] - }, - "metadata": {}, - "output_type": "display_data" + } }, { + "output_type": "display_data", "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "72eba4c5f11b49fb89df581268cc1ddc", @@ -785,11 +772,10 @@ "text/plain": [ "Dropdown(description='Kernel:', layout=Layout(width='400px'), options=('histogram_localized',), style=Descript…" ] - }, - "metadata": {}, - "output_type": "display_data" + } }, { + "output_type": "display_data", "data": { "application/vnd.jupyter.widget-view+json": { "model_id": "29f54431514146cea6e08d352058ccfd", @@ -799,18 +785,13 @@ "text/plain": [ "Output()" ] - }, - "metadata": {}, - "output_type": "display_data" + } } ], - "source": [ - "nsightful.display_ncu_csv_in_notebook(histogram_localized_csv)" - ] + "id": "114e8ff7-b6fb-42ad-abda-f6d53479c052" }, { "cell_type": "markdown", - "id": "9aa1da9a-097a-4f7f-9e96-8891f5d7a2a2", "metadata": { "id": "9aa1da9a-097a-4f7f-9e96-8891f5d7a2a2" }, @@ -818,12 +799,11 @@ "## 6. Performance Comparison\n", "\n", "Finally, let's benchmark our two approaches." - ] + ], + "id": "9aa1da9a-097a-4f7f-9e96-8891f5d7a2a2" }, { "cell_type": "code", - "execution_count": 14, - "id": "2a3f9ca4-b61b-4536-9896-7a41498cc986", "metadata": { "colab": { "base_uri": "https://localhost:8080/" @@ -831,9 +811,18 @@ "id": "2a3f9ca4-b61b-4536-9896-7a41498cc986", "outputId": "08bb2e07-1dda-4cf1-c2d3-7125d575cd8f" }, + "source": [ + "histogram_global_duration = !python histogram_global.py\n", + "histogram_localized_duration = !python histogram_localized.py\n", + "speedup = float(histogram_global_duration[0].split()[0]) / float(histogram_localized_duration[0].split()[0])\n", + "\n", + "print(f\"histogram_global: {histogram_global_duration[0]}\")\n", + "print(f\"histogram_localized: {histogram_localized_duration[0]}\")\n", + "print(f\"histogram_localized speedup over histogram_global: {speedup:.2f}\")" + ], + "execution_count": 14, "outputs": [ { - "name": "stdout", "output_type": "stream", "text": [ "histogram_global: 0.00881 s ± 1.13% (mean ± relative stdev of 15 runs)\n", @@ -842,15 +831,7 @@ ] } ], - "source": [ - "histogram_global_duration = !python histogram_global.py\n", - "histogram_localized_duration = !python histogram_localized.py\n", - "speedup = float(histogram_global_duration[0].split()[0]) / float(histogram_localized_duration[0].split()[0])\n", - "\n", - "print(f\"histogram_global: {histogram_global_duration[0]}\")\n", - "print(f\"histogram_localized: {histogram_localized_duration[0]}\")\n", - "print(f\"histogram_localized speedup over histogram_global: {speedup:.2f}\")" - ] + "id": "2a3f9ca4-b61b-4536-9896-7a41498cc986" } ], "metadata": { @@ -3488,4 +3469,4 @@ }, "nbformat": 4, "nbformat_minor": 5 -} +} \ No newline at end of file From 6a1c179b89d9b1d745fd7e06b476b93562d2df7f Mon Sep 17 00:00:00 2001 From: Bryce Adelstein Lelbach aka wash Date: Tue, 3 Mar 2026 15:43:48 -0500 Subject: [PATCH 14/18] Tutorials/Accelerated Python/Memory Spaces: Separate eigvals into its own cell, fix capitalization, restore eigvals timing. - Split expensive np.linalg.eigvals call into a dedicated cell timed with time.perf_counter. - Report eigvals timing alongside host/device benchmarks. - Capitalize print labels consistently (Power Iteration, Relative Error). - Use "Timing Host"/"Timing Device" instead of "Timing CPU"/"Timing GPU". Made-with: Cursor --- .../05__memory_spaces__power_iteration.ipynb | 99 ++++++++++--------- ...ry_spaces__power_iteration__SOLUTION.ipynb | 99 ++++++++++--------- 2 files changed, 106 insertions(+), 92 deletions(-) diff --git a/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb index fce50664..5c3243f6 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb @@ -85,10 +85,13 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "import numpy as np\n", "import cupy as cp\n", + "from cupyx.profiler import benchmark\n", "import time\n", "from dataclasses import dataclass\n", "\n", @@ -101,9 +104,7 @@ " check_frequency: int = 10 # Check for convergence every N steps\n", " progress: bool = True # Print progress logs\n", " residual_threshold: float = 1e-10 # Stop if error is below this" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -116,7 +117,9 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "def generate_host(cfg=PowerIterationConfig()):\n", " \"\"\"Generates a random diagonalizable matrix on the CPU.\"\"\"\n", @@ -137,9 +140,7 @@ "A_host = generate_host()\n", "print(f\"Host Matrix Shape: {A_host.shape}\")\n", "print(f\"Data Type: {A_host.dtype}\")" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -152,7 +153,9 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "def estimate_host(A, cfg=PowerIterationConfig()):\n", " \"\"\"\n", @@ -198,9 +201,7 @@ "\n", "print(f\"\\nEstimated Eigenvalue (CPU): {lam_est_host}\")\n", "print(f\"Time taken: {end_time - start_time:.4f}s\")" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -224,7 +225,9 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "def estimate_device(A, cfg=PowerIterationConfig()):\n", " \"\"\"\n", @@ -295,9 +298,7 @@ "# Uncomment to test your implementation:\n", "# lam_test = estimate_device(A_host, PowerIterationConfig(max_steps=50))\n", "# print(f\"Your result: {lam_test}\")" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -324,7 +325,9 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "def generate_device(cfg=PowerIterationConfig()):\n", " \"\"\"\n", @@ -377,9 +380,7 @@ "# cp.cuda.Stream.null.synchronize()\n", "# end_time = time.time()\n", "# print(f\"Compute time: {end_time - start_time:.4f}s\")" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -392,14 +393,14 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "# NOTE: Run these after completing both exercises above\n", "# print(\"NumPy:\", A_host[0, :3])\n", "# print(\"CuPy:\", A_device[0, :3].get())" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -421,31 +422,38 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "print(\"Calculating Reference Eigenvalue (numpy.linalg)...\")\n", + "start = time.perf_counter()\n", "lam_ref = np.linalg.eigvals(A_host).real.max()\n", - "\n", - "print(f\"\\n--- Results ---\")\n", - "print(f\"Power iteration (host) = {lam_est_host:.6e}\")\n", + "T_ref = time.perf_counter() - start" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(f\"Power Iteration (Host) = {lam_est_host:.6e}\")\n", "# Uncomment after completing exercises:\n", - "# print(f\"Power iteration (device) = {lam_est_device:.6e}\")\n", - "print(f\"`eigvals` reference = {lam_ref:.6e}\")\n", + "# print(f\"Power Iteration (Device) = {lam_est_device:.6e}\")\n", + "print(f\"`eigvals` Reference = {lam_ref:.6e}\")\n", "\n", "rel_err_host = abs(lam_est_host - lam_ref) / abs(lam_ref)\n", "# Uncomment after completing exercises:\n", "# rel_err_device = abs(lam_est_device - lam_ref) / abs(lam_ref)\n", "print()\n", - "print(f\"Relative error (host) = {rel_err_host:.3e}\")\n", + "print(f\"Relative Error (Host) = {rel_err_host:.3e}\")\n", "# Uncomment after completing exercises:\n", - "# print(f\"Relative error (device) = {rel_err_device:.3e}\")\n", + "# print(f\"Relative Error (Device) = {rel_err_device:.3e}\")\n", "\n", "np.testing.assert_allclose(lam_est_host, lam_ref, rtol=1e-4)\n", "# Uncomment after completing exercises:\n", "# np.testing.assert_allclose(lam_est_device, lam_ref, rtol=1e-4)" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -460,29 +468,28 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "# Uncomment and run after completing both exercises above:\n", - "# from cupyx.profiler import benchmark\n", - "\n", "# cfg = PowerIterationConfig(progress=False)\n", "\n", - "# print(\"Timing CPU...\")\n", - "# T_cpu = benchmark(estimate_host, args=(A_host, cfg), n_repeat=10).cpu_times\n", + "# print(\"Timing Host...\")\n", + "# T_host = benchmark(estimate_host, args=(A_host, cfg), n_repeat=10).cpu_times\n", "\n", - "# print(\"Timing GPU...\")\n", - "# T_gpu = benchmark(estimate_device, args=(A_host, cfg), n_repeat=10).gpu_times[0]\n", + "# print(\"Timing Device...\")\n", + "# T_device = benchmark(estimate_device, args=(A_host, cfg), n_repeat=10).gpu_times[0]\n", "\n", "# print()\n", - "# print(f\"Power iteration (host) = {T_cpu.mean() * 1000:.3g} ms ± {(T_cpu.std() / T_cpu.mean()):.2%} (mean ± relative stdev of {T_cpu.size} runs)\")\n", - "# print(f\"Power iteration (device) = {T_gpu.mean() * 1000:.3g} ms ± {(T_gpu.std() / T_gpu.mean()):.2%} (mean ± relative stdev of {T_gpu.size} runs)\")\n", + "# print(f\"Power Iteration (Host) = {T_host.mean() * 1000:.3g} ms ± {(T_host.std() / T_host.mean()):.2%} (mean ± relative stdev of {T_host.size} runs)\")\n", + "# print(f\"Power Iteration (Device) = {T_device.mean() * 1000:.3g} ms ± {(T_device.std() / T_device.mean()):.2%} (mean ± relative stdev of {T_device.size} runs)\")\n", + "# print(f\"`eigvals` Reference = {T_ref * 1000:.3g} ms\")\n", "\n", - "# speedup = T_cpu.mean() / T_gpu.mean()\n", + "# speedup = T_host.mean() / T_device.mean()\n", "# print()\n", - "# print(f\"Speedup: {speedup:.1f}x\")" - ], - "execution_count": null, - "outputs": [] + "# print(f\"Speedup (Device over Host) = {speedup:.1f}x\")" + ] }, { "cell_type": "markdown", @@ -505,7 +512,9 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "# Try different configurations here!\n", "# Example:\n", @@ -514,9 +523,7 @@ "# cfg_frequent_check = PowerIterationConfig(check_frequency=1, progress=True)\n", "\n", "# Your experiments:" - ], - "execution_count": null, - "outputs": [] + ] } ], "metadata": { @@ -528,4 +535,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} diff --git a/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb index 76a164de..66e93306 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb @@ -85,10 +85,13 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "import numpy as np\n", "import cupy as cp\n", + "from cupyx.profiler import benchmark\n", "import time\n", "from dataclasses import dataclass\n", "\n", @@ -101,9 +104,7 @@ " check_frequency: int = 10 # Check for convergence every N steps\n", " progress: bool = True # Print progress logs\n", " residual_threshold: float = 1e-10 # Stop if error is below this" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -116,7 +117,9 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "def generate_host(cfg=PowerIterationConfig()):\n", " \"\"\"Generates a random diagonalizable matrix on the CPU.\"\"\"\n", @@ -137,9 +140,7 @@ "A_host = generate_host()\n", "print(f\"Host Matrix Shape: {A_host.shape}\")\n", "print(f\"Data Type: {A_host.dtype}\")" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -152,7 +153,9 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "def estimate_host(A, cfg=PowerIterationConfig()):\n", " \"\"\"\n", @@ -198,9 +201,7 @@ "\n", "print(f\"\\nEstimated Eigenvalue (CPU): {lam_est_host}\")\n", "print(f\"Time taken: {end_time - start_time:.4f}s\")" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -224,7 +225,9 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "def estimate_device(A, cfg=PowerIterationConfig()):\n", " \"\"\"\n", @@ -294,9 +297,7 @@ "\n", "print(f\"\\nEstimated Eigenvalue (GPU): {lam_est_device}\")\n", "print(f\"Time taken: {end_time - start_time:.4f}s\")" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -323,7 +324,9 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "def generate_device(cfg=PowerIterationConfig()):\n", " \"\"\"\n", @@ -375,9 +378,7 @@ "cp.cuda.Stream.null.synchronize()\n", "end_time = time.time()\n", "print(f\"Compute time: {end_time - start_time:.4f}s\")" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -390,13 +391,13 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "print(\"NumPy:\", A_host[0, :3])\n", "print(\"CuPy:\", A_device[0, :3].get())" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -431,27 +432,34 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "print(\"Calculating Reference Eigenvalue (numpy.linalg)...\")\n", + "start = time.perf_counter()\n", "lam_ref = np.linalg.eigvals(A_host).real.max()\n", - "\n", - "print(f\"\\n--- Results ---\")\n", - "print(f\"Power iteration (host) = {lam_est_host:.6e}\")\n", - "print(f\"Power iteration (device) = {lam_est_device:.6e}\")\n", - "print(f\"`eigvals` reference = {lam_ref:.6e}\")\n", + "T_ref = time.perf_counter() - start" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print(f\"Power Iteration (Host) = {lam_est_host:.6e}\")\n", + "print(f\"Power Iteration (Device) = {lam_est_device:.6e}\")\n", + "print(f\"`eigvals` Reference = {lam_ref:.6e}\")\n", "\n", "rel_err_host = abs(lam_est_host - lam_ref) / abs(lam_ref)\n", "rel_err_device = abs(lam_est_device - lam_ref) / abs(lam_ref)\n", "print()\n", - "print(f\"Relative error (host) = {rel_err_host:.3e}\")\n", - "print(f\"Relative error (device) = {rel_err_device:.3e}\")\n", + "print(f\"Relative Error (Host) = {rel_err_host:.3e}\")\n", + "print(f\"Relative Error (Device) = {rel_err_device:.3e}\")\n", "\n", "np.testing.assert_allclose(lam_est_host, lam_ref, rtol=1e-4)\n", "np.testing.assert_allclose(lam_est_device, lam_ref, rtol=1e-4)" - ], - "execution_count": null, - "outputs": [] + ] }, { "cell_type": "markdown", @@ -466,28 +474,27 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ - "from cupyx.profiler import benchmark\n", - "\n", "cfg = PowerIterationConfig(progress=False)\n", "\n", - "print(\"Timing CPU...\")\n", - "T_cpu = benchmark(estimate_host, args=(A_host, cfg), n_repeat=10).cpu_times\n", + "print(\"Timing Host...\")\n", + "T_host = benchmark(estimate_host, args=(A_host, cfg), n_repeat=10).cpu_times\n", "\n", - "print(\"Timing GPU...\")\n", - "T_gpu = benchmark(estimate_device, args=(A_host, cfg), n_repeat=10).gpu_times[0]\n", + "print(\"Timing Device...\")\n", + "T_device = benchmark(estimate_device, args=(A_host, cfg), n_repeat=10).gpu_times[0]\n", "\n", "print()\n", - "print(f\"Power iteration (host) = {T_cpu.mean() * 1000:.3g} ms ± {(T_cpu.std() / T_cpu.mean()):.2%} (mean ± relative stdev of {T_cpu.size} runs)\")\n", - "print(f\"Power iteration (device) = {T_gpu.mean() * 1000:.3g} ms ± {(T_gpu.std() / T_gpu.mean()):.2%} (mean ± relative stdev of {T_gpu.size} runs)\")\n", + "print(f\"Power Iteration (Host) = {T_host.mean() * 1000:.3g} ms ± {(T_host.std() / T_host.mean()):.2%} (mean ± relative stdev of {T_host.size} runs)\")\n", + "print(f\"Power Iteration (Device) = {T_device.mean() * 1000:.3g} ms ± {(T_device.std() / T_device.mean()):.2%} (mean ± relative stdev of {T_device.size} runs)\")\n", + "print(f\"`eigvals` Reference = {T_ref * 1000:.3g} ms\")\n", "\n", - "speedup = T_cpu.mean() / T_gpu.mean()\n", + "speedup = T_host.mean() / T_device.mean()\n", "print()\n", - "print(f\"Speedup: {speedup:.1f}x\")" - ], - "execution_count": null, - "outputs": [] + "print(f\"Speedup (Device over Host) = {speedup:.1f}x\")" + ] }, { "cell_type": "markdown", @@ -510,7 +517,9 @@ }, { "cell_type": "code", + "execution_count": null, "metadata": {}, + "outputs": [], "source": [ "# Try different configurations here!\n", "# Example:\n", @@ -519,9 +528,7 @@ "# cfg_frequent_check = PowerIterationConfig(check_frequency=1, progress=True)\n", "\n", "# Your experiments:" - ], - "execution_count": null, - "outputs": [] + ] } ], "metadata": { @@ -533,4 +540,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} \ No newline at end of file +} From e3a5d9418783f693c100d9b31c2a3f75417c7a8d Mon Sep 17 00:00:00 2001 From: Bryce Adelstein Lelbach aka wash Date: Tue, 3 Mar 2026 15:44:53 -0500 Subject: [PATCH 15/18] Tutorials/Accelerated Python/Asynchrony: Fix compute step NVTX annotations with accurate step ranges and per-step regions. Made-with: Cursor --- ...asynchrony__power_iteration__SOLUTION.ipynb | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/tutorials/accelerated-python/notebooks/fundamentals/solutions/06__asynchrony__power_iteration__SOLUTION.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/solutions/06__asynchrony__power_iteration__SOLUTION.ipynb index 114416fd..d232845a 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/solutions/06__asynchrony__power_iteration__SOLUTION.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/solutions/06__asynchrony__power_iteration__SOLUTION.ipynb @@ -136,10 +136,11 @@ " if res_host < cfg.residual_threshold:\n", " break\n", "\n", - " with nvtx.annotate(f\"Compute {i}\"):\n", - " for _ in range(cfg.check_frequency - 1):\n", - " y = A_gpu @ x # We have to use `A_gpu` here as well.\n", - " x = y / cp.linalg.norm(y) # Normalize for next step.\n", + " with nvtx.annotate(f\"Compute {i + 1} to {i + cfg.check_frequency}\"):\n", + " for j in range(i + 1, i + cfg.check_frequency):\n", + " with nvtx.annotate(f\"Compute Step {j}\"):\n", + " y = A_gpu @ x # We have to use `A_gpu` here as well.\n", + " x = y / cp.linalg.norm(y) # Normalize for next step.\n", "\n", " return cp.asnumpy((x.T @ (A_gpu @ x)) / (x.T @ x)) # Copy from device to host.\n", "\n", @@ -332,10 +333,11 @@ " x_host = cp.asnumpy(x, blocking=False)\n", " copy_event = cp.cuda.get_current_stream().record()\n", "\n", - " with nvtx.annotate(f\"Compute {i}\"):\n", - " for _ in range(cfg.check_frequency - 1):\n", - " y = A_gpu @ x # We have to use `A_gpu` here as well.\n", - " x = y / cp.linalg.norm(y) # Normalize for next step.\n", + " with nvtx.annotate(f\"Compute {i + 1} to {i + cfg.check_frequency}\"):\n", + " for j in range(i + 1, i + cfg.check_frequency):\n", + " with nvtx.annotate(f\"Compute Step {j}\"):\n", + " y = A_gpu @ x # We have to use `A_gpu` here as well.\n", + " x = y / cp.linalg.norm(y) # Normalize for next step.\n", "\n", " with nvtx.annotate(f\"I/O {i}\", payload=i):\n", " copy_event.synchronize() # Wait for the copies to complete.\n", From 2555b01977114fc141d452cfee6c78d4623a6aff Mon Sep 17 00:00:00 2001 From: Bryce Adelstein Lelbach aka wash Date: Tue, 3 Mar 2026 17:24:19 -0500 Subject: [PATCH 16/18] Tutorials/Accelerated Python/Memory Spaces: Remove inline benchmarking from sections 3 and 4. Restore the original style from before 2f5c4fc: just call the functions, print the estimates, and show both matrices side by side. Benchmarking belongs in section 5 where cupyx.profiler.benchmark is used properly. Made-with: Cursor --- .../05__memory_spaces__power_iteration.ipynb | 31 +++++++++--------- ...ry_spaces__power_iteration__SOLUTION.ipynb | 32 ++++++++----------- 2 files changed, 29 insertions(+), 34 deletions(-) diff --git a/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb index 5c3243f6..101d61e4 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb @@ -296,8 +296,10 @@ " return ...\n", "\n", "# Uncomment to test your implementation:\n", - "# lam_test = estimate_device(A_host, PowerIterationConfig(max_steps=50))\n", - "# print(f\"Your result: {lam_test}\")" + "# lam_est_device = estimate_device(A_host)\n", + "#\n", + "# print()\n", + "# print(lam_est_device)" ] }, { @@ -367,19 +369,12 @@ " return A\n", "\n", "# Uncomment to test your implementation:\n", - "# print(\"\\nGenerating Data directly on GPU...\")\n", - "# start_time = time.time()\n", "# A_device = generate_device()\n", - "# end_time = time.time()\n", - "# print(f\"Generation time: {end_time - start_time:.4f}s\")\n", - "\n", - "# print(\"Running GPU Estimate (Input is Device Array)...\")\n", - "# start_time = time.time()\n", - "# # No transfer overhead here because A_device is already on GPU\n", + "#\n", "# lam_est_device_gen = estimate_device(A_device)\n", - "# cp.cuda.Stream.null.synchronize()\n", - "# end_time = time.time()\n", - "# print(f\"Compute time: {end_time - start_time:.4f}s\")" + "#\n", + "# print()\n", + "# print(lam_est_device_gen)" ] }, { @@ -398,8 +393,14 @@ "outputs": [], "source": [ "# NOTE: Run these after completing both exercises above\n", - "# print(\"NumPy:\", A_host[0, :3])\n", - "# print(\"CuPy:\", A_device[0, :3].get())" + "#\n", + "# with np.printoptions(precision=4):\n", + "# print(\"A_host:\")\n", + "# print(A_host)\n", + "# print()\n", + "# print(\"A_device:\")\n", + "# print(A_device)\n", + "# print()" ] }, { diff --git a/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb index 66e93306..a1349583 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb @@ -288,15 +288,10 @@ " result = (x.T @ (A_gpu @ x)) / (x.T @ x)\n", " return result.item() # SOLUTION: Convert GPU scalar to Python scalar\n", "\n", - "# Run the GPU implementation\n", - "print(\"\\nRunning GPU Estimate (Input is Host Array)...\")\n", - "start_time = time.time()\n", "lam_est_device = estimate_device(A_host)\n", - "cp.cuda.Stream.null.synchronize()\n", - "end_time = time.time()\n", "\n", - "print(f\"\\nEstimated Eigenvalue (GPU): {lam_est_device}\")\n", - "print(f\"Time taken: {end_time - start_time:.4f}s\")" + "print()\n", + "print(lam_est_device)" ] }, { @@ -365,19 +360,12 @@ "\n", " return A\n", "\n", - "print(\"\\nGenerating Data directly on GPU...\")\n", - "start_time = time.time()\n", "A_device = generate_device()\n", - "end_time = time.time()\n", - "print(f\"Generation time: {end_time - start_time:.4f}s\")\n", "\n", - "print(\"Running GPU Estimate (Input is Device Array)...\")\n", - "start_time = time.time()\n", - "# No transfer overhead here because A_device is already on GPU\n", "lam_est_device_gen = estimate_device(A_device)\n", - "cp.cuda.Stream.null.synchronize()\n", - "end_time = time.time()\n", - "print(f\"Compute time: {end_time - start_time:.4f}s\")" + "\n", + "print()\n", + "print(lam_est_device_gen)" ] }, { @@ -395,8 +383,14 @@ "metadata": {}, "outputs": [], "source": [ - "print(\"NumPy:\", A_host[0, :3])\n", - "print(\"CuPy:\", A_device[0, :3].get())" + "\n", + "with np.printoptions(precision=4):\n", + " print(\"A_host:\")\n", + " print(A_host)\n", + " print()\n", + " print(\"A_device:\")\n", + " print(A_device)\n", + " print()" ] }, { From b5d19ee7cb632099a4bd88038d1e5376ec05d9ca Mon Sep 17 00:00:00 2001 From: Bryce Adelstein Lelbach aka wash Date: Tue, 3 Mar 2026 17:26:19 -0500 Subject: [PATCH 17/18] Tutorials/Accelerated Python/Memory Spaces: Remove outdated note about cupy.linalg.eigvals not being implemented. Made-with: Cursor --- .../05__memory_spaces__power_iteration.ipynb | 65 +++++++++---------- ...ry_spaces__power_iteration__SOLUTION.ipynb | 65 +++++++++---------- 2 files changed, 64 insertions(+), 66 deletions(-) diff --git a/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb index 101d61e4..10f5c7ad 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb @@ -85,9 +85,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "import numpy as np\n", "import cupy as cp\n", @@ -104,7 +102,9 @@ " check_frequency: int = 10 # Check for convergence every N steps\n", " progress: bool = True # Print progress logs\n", " residual_threshold: float = 1e-10 # Stop if error is below this" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -117,9 +117,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "def generate_host(cfg=PowerIterationConfig()):\n", " \"\"\"Generates a random diagonalizable matrix on the CPU.\"\"\"\n", @@ -140,7 +138,9 @@ "A_host = generate_host()\n", "print(f\"Host Matrix Shape: {A_host.shape}\")\n", "print(f\"Data Type: {A_host.dtype}\")" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -153,9 +153,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "def estimate_host(A, cfg=PowerIterationConfig()):\n", " \"\"\"\n", @@ -201,7 +199,9 @@ "\n", "print(f\"\\nEstimated Eigenvalue (CPU): {lam_est_host}\")\n", "print(f\"Time taken: {end_time - start_time:.4f}s\")" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -225,9 +225,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "def estimate_device(A, cfg=PowerIterationConfig()):\n", " \"\"\"\n", @@ -300,7 +298,9 @@ "#\n", "# print()\n", "# print(lam_est_device)" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -327,9 +327,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "def generate_device(cfg=PowerIterationConfig()):\n", " \"\"\"\n", @@ -375,7 +373,9 @@ "#\n", "# print()\n", "# print(lam_est_device_gen)" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -388,9 +388,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "# NOTE: Run these after completing both exercises above\n", "#\n", @@ -401,7 +399,9 @@ "# print(\"A_device:\")\n", "# print(A_device)\n", "# print()" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -417,26 +417,23 @@ "## 5. Verification and Benchmarking\n", "\n", "Finally, let's verify our accuracy against a reference implementation (`numpy.linalg.eigvals`) and benchmark the speedup.\n", - "\n", - "**Note on CuPy Limitations:** You might wonder why we use `np.linalg.eigvals` on the CPU instead of a CuPy equivalent. The reason is that CuPy does not yet implement `eigvals`. While CuPy covers a large portion of the NumPy API, it does not support every function. Always check the [CuPy documentation](https://docs.cupy.dev/en/stable/reference/comparison.html) to verify which functions are available before assuming a direct NumPy-to-CuPy conversion will work.\n" + "" ] }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "start = time.perf_counter()\n", "lam_ref = np.linalg.eigvals(A_host).real.max()\n", "T_ref = time.perf_counter() - start" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "print(f\"Power Iteration (Host) = {lam_est_host:.6e}\")\n", "# Uncomment after completing exercises:\n", @@ -454,7 +451,9 @@ "np.testing.assert_allclose(lam_est_host, lam_ref, rtol=1e-4)\n", "# Uncomment after completing exercises:\n", "# np.testing.assert_allclose(lam_est_device, lam_ref, rtol=1e-4)" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -469,9 +468,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "# Uncomment and run after completing both exercises above:\n", "# cfg = PowerIterationConfig(progress=False)\n", @@ -490,7 +487,9 @@ "# speedup = T_host.mean() / T_device.mean()\n", "# print()\n", "# print(f\"Speedup (Device over Host) = {speedup:.1f}x\")" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -513,9 +512,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "# Try different configurations here!\n", "# Example:\n", @@ -524,7 +521,9 @@ "# cfg_frequent_check = PowerIterationConfig(check_frequency=1, progress=True)\n", "\n", "# Your experiments:" - ] + ], + "execution_count": null, + "outputs": [] } ], "metadata": { @@ -536,4 +535,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file diff --git a/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb index a1349583..725efbb0 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb @@ -85,9 +85,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "import numpy as np\n", "import cupy as cp\n", @@ -104,7 +102,9 @@ " check_frequency: int = 10 # Check for convergence every N steps\n", " progress: bool = True # Print progress logs\n", " residual_threshold: float = 1e-10 # Stop if error is below this" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -117,9 +117,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "def generate_host(cfg=PowerIterationConfig()):\n", " \"\"\"Generates a random diagonalizable matrix on the CPU.\"\"\"\n", @@ -140,7 +138,9 @@ "A_host = generate_host()\n", "print(f\"Host Matrix Shape: {A_host.shape}\")\n", "print(f\"Data Type: {A_host.dtype}\")" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -153,9 +153,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "def estimate_host(A, cfg=PowerIterationConfig()):\n", " \"\"\"\n", @@ -201,7 +199,9 @@ "\n", "print(f\"\\nEstimated Eigenvalue (CPU): {lam_est_host}\")\n", "print(f\"Time taken: {end_time - start_time:.4f}s\")" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -225,9 +225,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "def estimate_device(A, cfg=PowerIterationConfig()):\n", " \"\"\"\n", @@ -292,7 +290,9 @@ "\n", "print()\n", "print(lam_est_device)" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -319,9 +319,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "def generate_device(cfg=PowerIterationConfig()):\n", " \"\"\"\n", @@ -366,7 +364,9 @@ "\n", "print()\n", "print(lam_est_device_gen)" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -379,9 +379,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "\n", "with np.printoptions(precision=4):\n", @@ -391,7 +389,9 @@ " print(\"A_device:\")\n", " print(A_device)\n", " print()" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -420,26 +420,23 @@ "## 5. Verification and Benchmarking\n", "\n", "Finally, let's verify our accuracy against a reference implementation (`numpy.linalg.eigvals`) and benchmark the speedup.\n", - "\n", - "**Note on CuPy Limitations:** You might wonder why we use `np.linalg.eigvals` on the CPU instead of a CuPy equivalent. The reason is that CuPy does not yet implement `eigvals`. While CuPy covers a large portion of the NumPy API, it does not support every function. Always check the [CuPy documentation](https://docs.cupy.dev/en/stable/reference/comparison.html) to verify which functions are available before assuming a direct NumPy-to-CuPy conversion will work.\n" + "" ] }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "start = time.perf_counter()\n", "lam_ref = np.linalg.eigvals(A_host).real.max()\n", "T_ref = time.perf_counter() - start" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "print(f\"Power Iteration (Host) = {lam_est_host:.6e}\")\n", "print(f\"Power Iteration (Device) = {lam_est_device:.6e}\")\n", @@ -453,7 +450,9 @@ "\n", "np.testing.assert_allclose(lam_est_host, lam_ref, rtol=1e-4)\n", "np.testing.assert_allclose(lam_est_device, lam_ref, rtol=1e-4)" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -468,9 +467,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "cfg = PowerIterationConfig(progress=False)\n", "\n", @@ -488,7 +485,9 @@ "speedup = T_host.mean() / T_device.mean()\n", "print()\n", "print(f\"Speedup (Device over Host) = {speedup:.1f}x\")" - ] + ], + "execution_count": null, + "outputs": [] }, { "cell_type": "markdown", @@ -511,9 +510,7 @@ }, { "cell_type": "code", - "execution_count": null, "metadata": {}, - "outputs": [], "source": [ "# Try different configurations here!\n", "# Example:\n", @@ -522,7 +519,9 @@ "# cfg_frequent_check = PowerIterationConfig(check_frequency=1, progress=True)\n", "\n", "# Your experiments:" - ] + ], + "execution_count": null, + "outputs": [] } ], "metadata": { @@ -534,4 +533,4 @@ }, "nbformat": 4, "nbformat_minor": 2 -} +} \ No newline at end of file From 7f7e7a4352d0d06e8306fc6e60876953b89221be Mon Sep 17 00:00:00 2001 From: Bryce Adelstein Lelbach aka wash Date: Tue, 3 Mar 2026 17:44:41 -0500 Subject: [PATCH 18/18] Tutorials/Accelerated Python/Memory Spaces: Fix benchmark to compare CPU wall-clock times for both host and device. The benchmarking cell was using .gpu_times[0] for the device benchmark but .cpu_times for the host benchmark, which is an apples-to-oranges comparison. The GPU time measures only device execution, excluding kernel launch overhead, synchronization, and other CPU-side costs. The CPU time (wall-clock) is the end-to-end time, which is the fair metric for both. This was introduced in 0c365ed. Made-with: Cursor --- .../fundamentals/05__memory_spaces__power_iteration.ipynb | 2 +- .../05__memory_spaces__power_iteration__SOLUTION.ipynb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb index 10f5c7ad..9411235d 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/05__memory_spaces__power_iteration.ipynb @@ -477,7 +477,7 @@ "# T_host = benchmark(estimate_host, args=(A_host, cfg), n_repeat=10).cpu_times\n", "\n", "# print(\"Timing Device...\")\n", - "# T_device = benchmark(estimate_device, args=(A_host, cfg), n_repeat=10).gpu_times[0]\n", + "# T_device = benchmark(estimate_device, args=(A_host, cfg), n_repeat=10).cpu_times\n", "\n", "# print()\n", "# print(f\"Power Iteration (Host) = {T_host.mean() * 1000:.3g} ms ± {(T_host.std() / T_host.mean()):.2%} (mean ± relative stdev of {T_host.size} runs)\")\n", diff --git a/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb index 725efbb0..57bdf048 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/solutions/05__memory_spaces__power_iteration__SOLUTION.ipynb @@ -475,7 +475,7 @@ "T_host = benchmark(estimate_host, args=(A_host, cfg), n_repeat=10).cpu_times\n", "\n", "print(\"Timing Device...\")\n", - "T_device = benchmark(estimate_device, args=(A_host, cfg), n_repeat=10).gpu_times[0]\n", + "T_device = benchmark(estimate_device, args=(A_host, cfg), n_repeat=10).cpu_times\n", "\n", "print()\n", "print(f\"Power Iteration (Host) = {T_host.mean() * 1000:.3g} ms ± {(T_host.std() / T_host.mean()):.2%} (mean ± relative stdev of {T_host.size} runs)\")\n",