diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 1f868aba..c7ab5716 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -20,6 +20,60 @@ You can use the following scripts to help with development: * **`brev/dev-test.bash `** - Tests a Docker Compose file with the local repository mounted. Calls `test-docker-compose.bash` to run the tutorial's tests. +## Pre-Commit Hooks + +This repository uses [pre-commit](https://pre-commit.com/) to run automated +checks before commits and pushes. Install the hooks after cloning: + +```bash +pip install pre-commit +pre-commit install +pre-commit install --hook-type pre-push +``` + +The following hooks are configured: + +### `notebook-format` (pre-commit) + +Checks that Jupyter notebooks are in **canonical format**: 1-space JSON +indentation, sorted keys, standard metadata (kernelspec, colab settings, +language_info), nbformat 4.5, and cell IDs present. Non-SOLUTION notebooks +must have clean outputs (no outputs or execution counts). To auto-fix +formatting issues: + +```bash +python3 brev/test-notebook-format.py --fix +``` + +### `git-lfs` (pre-commit) + +Checks that binary files (`.pptx`, `.pdf`, `.png`, `.jpg`, `.jpeg`, `.gif`, +`.webp`) are properly tracked by Git LFS. Also verifies that no +`.gitattributes` files exist in subdirectories. If a file isn't tracked, fix it +with: + +```bash +git rm --cached +git add +``` + +### `git-signatures` (pre-push) + +Checks that all commits on your branch (compared to `origin/main`) are +cryptographically signed. Runs on `git push`, not on commit. See +[GitHub's documentation on commit signature verification](https://docs.github.com/en/authentication/managing-commit-signature-verification) +for setup instructions. + +### `test-links` (manual) + +Checks that links in Markdown files and Jupyter notebooks are valid using +[lychee](https://github.com/lycheeverse/lychee). This hook is configured as +a **manual** stage and won't run automatically. Run it explicitly with: + +```bash +pre-commit run test-links --hook-stage manual --all-files +``` + ## License The preferred license for contributions to this project is the detailed in the diff --git a/brev/test-notebook-format.py b/brev/test-notebook-format.py index 068b5c8a..cdf52b10 100755 --- a/brev/test-notebook-format.py +++ b/brev/test-notebook-format.py @@ -208,16 +208,26 @@ def canonicalize_notebook(notebook_path: Path) -> tuple[str, list[str]]: if cells_missing_ids: problems.append(f" {cells_missing_ids} cell(s) missing id field") - # Check for malformed stream outputs (SOLUTION notebooks keep outputs) + # Check for malformed outputs (SOLUTION notebooks keep outputs) if solution: for i, cell in enumerate(raw.get("cells", [])): if cell.get("cell_type") != "code": continue for j, out in enumerate(cell.get("outputs", [])): - if out.get("output_type") == "stream" and "name" not in out: + otype = out.get("output_type") + if otype == "stream" and "name" not in out: problems.append( f" Cell {i} output {j}: stream output missing 'name'" ) + if otype in ("execute_result", "display_data"): + if "metadata" not in out: + problems.append( + f" Cell {i} output {j}: {otype} missing 'metadata'" + ) + if otype == "execute_result" and "execution_count" not in out: + problems.append( + f" Cell {i} output {j}: execute_result missing 'execution_count'" + ) # -- Build canonical form via nbformat ----------------------------------- with warnings.catch_warnings(): @@ -229,13 +239,18 @@ def canonicalize_notebook(notebook_path: Path) -> tuple[str, list[str]]: nb.nbformat = STANDARD_NBFORMAT nb.nbformat_minor = STANDARD_NBFORMAT_MINOR - # Fix malformed stream outputs + # Fix malformed outputs for cell in nb.cells: if cell.get("cell_type") != "code": continue for out in cell.get("outputs", []): - if out.get("output_type") == "stream" and "name" not in out: + otype = out.get("output_type") + if otype == "stream" and "name" not in out: out["name"] = "stdout" + if otype in ("execute_result", "display_data") and "metadata" not in out: + out["metadata"] = {} + if otype == "execute_result" and "execution_count" not in out: + out["execution_count"] = cell.get("execution_count") # Strip outputs for non-SOLUTION notebooks if not solution: diff --git a/tutorials/accelerated-python/notebooks/fundamentals/01__numpy_intro__ndarray_basics.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/01__numpy_intro__ndarray_basics.ipynb index d54fb3f6..a3923b1b 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/01__numpy_intro__ndarray_basics.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/01__numpy_intro__ndarray_basics.ipynb @@ -60,8 +60,8 @@ "\n", "- **Data**: A pointer to the memory location holding the elements.\n", "- **dtype**: The data type (e.g., `int32`, `float64`) which is uniform across all elements.\n", - "- **Shape**: A tuple defining the size along each dimension (e.g., $(100, 50)$ for 100 rows and 50 columns).\n", - "- **Strides**: The number of bytes to step in memory to reach the next element along each dimension—this is how NumPy efficiently handles different shapes and views.\n", + "- **Shape**: A tuple defining the size along each dimension (e.g., `(100, 50)` for 100 rows and 50 columns).\n", + "- **Strides**: The number of bytes to step in memory to reach the next element along each dimension. This is how NumPy efficiently handles different shapes and views.\n", "\n", "Let's explore these properties by creating a large dataset.\n", "\n", @@ -69,7 +69,7 @@ "\n", "**Quick Docs**\n", "- `np.arange(start, stop, step)`: Returns evenly spaced values in the half-open interval $[\\text{start}, \\text{stop})$.\n", - "- `arr.nbytes`: Total bytes consumed by the array's elements (in bytes).\n", + "- `arr.nbytes`: Total bytes of storage for the array's elements.\n", "- `arr.ndim`: The number of array dimensions (integer).\n", "- `arr.size`: The total number of elements in the array (integer).\n", "- `arr.shape`: The tuple of array dimensions.\n" @@ -97,7 +97,8 @@ "source": [ "# TODO: Create the input data array with the numbers 1 to 50_000_000 (inclusive).\n", "# Hint: np.arange generates values within a half-open interval [start, stop)\n", - "arr = ..." + "arr = ...\n", + "arr" ] }, { @@ -110,7 +111,7 @@ "outputs": [], "source": [ "# TODO: Calculate how large the array is in GB with nbytes.\n", - "# Hint: GB is 1e9 bytes. The .nbytes attribute returns the total bytes consumed by the elements.\n", + "# Hint: GB is 2**30 bytes. The .nbytes attribute returns the total bytes consumed by the elements.\n", "# Note: This demonstrates that arrays are dense memory blocks, unlike pointer-heavy Python lists.\n", "arr..." ] @@ -124,7 +125,7 @@ }, "outputs": [], "source": [ - "# TODO: How many dimensions does the array have? (ndim)\n", + "# TODO: How many dimensions does the array have?\n", "arr..." ] }, @@ -137,7 +138,7 @@ }, "outputs": [], "source": [ - "# TODO: How many elements does the array have? (size)\n", + "# TODO: How many elements does the array have?\n", "arr..." ] }, @@ -163,7 +164,11 @@ "\n", "Arrays can logically represent data in many ways (e.g., 1D signal, 2D image, 4D video batch) independent of the underlying physical memory block.\n", "\n", - "A critical performance feature is that operations like transposing or `reshape` often return a **View** instead of a **Copy**. A View only changes the metadata (`shape` and `strides`) without duplicating the physical data, making these operations nearly instantaneous.\n", + "Most operations, like adding two arrays together, returns a **Copy**, which requires allocating a new array, which can negatively impact performance.\n", + "\n", + "Some operations, like transposing or `reshape()` often return a **View** instead of a **Copy**. A View only changes the metadata (`shape` and `strides`) without duplicating the physical data, making these operations nearly instantaneous.\n", + "\n", + "Most Copy operations take an `out` parameter that takes an array; if it provided, the result is written to that array instead of allocating a new one. For example, `A + B` or `np.add(A, B)` will return a new array with the result, but `np.add(A + B, out=A)` will place the result in `A` without an allocation.\n", "\n", "---\n", "\n", diff --git a/tutorials/accelerated-python/notebooks/fundamentals/02__numpy_linear_algebra__svd_reconstruction.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/02__numpy_linear_algebra__svd_reconstruction.ipynb index 684caa1b..a71c5ebc 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/02__numpy_linear_algebra__svd_reconstruction.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/02__numpy_linear_algebra__svd_reconstruction.ipynb @@ -28,7 +28,8 @@ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "import cv2\n", - "import urllib.request" + "import urllib.request\n", + "import os" ] }, { @@ -52,7 +53,10 @@ "source": [ "urllib.request.urlretrieve(\n", " \"https://drive.usercontent.google.com/download?id=1ClKrHt4-SIHaeBJdF0K3MG64jyVnt62L&export=download\",\n", - " \"loonie.jpg\")" + " \"loonie.jpg\")\n", + "\n", + "jpeg_size = os.path.getsize(\"loonie.jpg\")\n", + "print(f\"JPEG file size: {jpeg_size} bytes\")" ] }, { @@ -242,7 +246,7 @@ "id": "spCXu88mw-mT" }, "source": [ - "**TODO: Print the compression ratio for the values of `n` used above. This is the number of bytes of the reduced arrays added together and divided by the size of the original grayscale image array. How does this compare to the size of the original color JPEG, which is 756473 bytes?** _Hint: `.nbytes` works on sliced arrays!_" + "**TODO: Print the compression ratio for the values of `n` used above. This is the number of bytes of the reduced arrays added together and divided by the size of the original grayscale image array. How does this compare to the size of the original color JPEG (`jpeg_size`)?** _Hint: `.nbytes` works on sliced arrays!_" ] }, { diff --git a/tutorials/accelerated-python/notebooks/fundamentals/03__numpy_to_cupy__ndarray_basics.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/03__numpy_to_cupy__ndarray_basics.ipynb index 4b894e5d..83bbd2f6 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/03__numpy_to_cupy__ndarray_basics.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/03__numpy_to_cupy__ndarray_basics.ipynb @@ -60,7 +60,7 @@ "\n", "Let's compare the performance of creating a large 3D array (approx. 100 MB in size) on the CPU versus the GPU.\n", "\n", - "We will use `np.ones` for the CPU and `cp.ones` for the GPU.\n" + "We will use `np.ones()` for the CPU and `cp.ones()` for the GPU.\n" ] }, { @@ -71,7 +71,7 @@ "outputs": [], "source": [ "# CPU creation\n", - "print_benchmark(cpx.profiler.benchmark(np.ones, ((50, 500, 500),), n_repeat=10))" + "print_benchmark(cpx.profiler.benchmark(np.ones, ((50, 500, 500),), n_repeat=10, n_warmup=1))" ] }, { @@ -82,7 +82,7 @@ "outputs": [], "source": [ "# GPU creation\n", - "print_benchmark(cpx.profiler.benchmark(cp.ones, ((50, 500, 500),), n_repeat=10))" + "print_benchmark(cpx.profiler.benchmark(cp.ones, ((50, 500, 500),), n_repeat=10, n_warmup=1))" ] }, { @@ -92,11 +92,11 @@ "source": [ "We can see here that creating this array on the GPU is much faster than doing so on the CPU!\n", "\n", - "**About `cupyx.profiler.benchmark`:**\n", + "**About `cupyx.profiler.benchmark()`:**\n", "\n", - "We use CuPy's built-in `benchmark` utility for timing GPU operations. This is important because GPU operations are **asynchronous** - when you call a CuPy function, the CPU places a task in the GPU's \"to-do list\" (stream) and immediately moves on without waiting.\n", + "We use CuPy's built-in `benchmark()` utility for timing GPU operations. This is important because GPU operations are **asynchronous** - when you call a CuPy function, the CPU places a task in the GPU's \"to-do list\" (stream) and immediately moves on without waiting.\n", "\n", - "The `benchmark` function handles all the complexity of proper GPU timing for us:\n", + "The `benchmark()` function handles all the complexity of proper GPU timing for us:\n", "- It automatically synchronizes GPU streams to get accurate measurements.\n", "- It runs warm-up iterations to avoid cold-start overhead.\n", "- It reports both CPU wall-clock times (`cpu_times`) and GPU kernel times (`gpu_times`). We use `cpu_times` for all comparisons because it measures end-to-end wall-clock time, giving a fair apples-to-apples comparison between CPU and GPU code.\n", @@ -121,9 +121,7 @@ "metadata": {}, "outputs": [], "source": [ - "# Create fresh arrays for the benchmark\n", - "x_cpu = np.ones((50, 500, 500))\n", - "x_gpu = cp.ones((50, 500, 500))\n", + "multiply_shape = (50, 500, 500)\n", "\n", "def multiply(x):\n", " return x * 5" @@ -137,7 +135,8 @@ "outputs": [], "source": [ "# CPU Operation\n", - "print_benchmark(cpx.profiler.benchmark(multiply, (x_cpu,), n_repeat=10))" + "x_cpu = np.ones(multiply_shape)\n", + "print_benchmark(cpx.profiler.benchmark(multiply, (x_cpu,), n_repeat=10, n_warmup=1))" ] }, { @@ -148,7 +147,8 @@ "outputs": [], "source": [ "# GPU Operation\n", - "print_benchmark(cpx.profiler.benchmark(multiply, (x_gpu,), n_repeat=10))" + "x_gpu = cp.ones(multiply_shape)\n", + "print_benchmark(cpx.profiler.benchmark(multiply, (x_gpu,), n_repeat=10, n_warmup=1))" ] }, { @@ -166,7 +166,7 @@ "source": [ "#### Sequential Operations & Memory\n", "\n", - "Now let's do a couple of operations sequentially, something which would suffer from memory transfer times in Numba examples without explicit memory management." + "Now let's do a couple of operations sequentially." ] }, { @@ -176,6 +176,8 @@ "metadata": {}, "outputs": [], "source": [ + "sequential_math_shape = (50, 500, 500)\n", + "\n", "def sequential_math(x):\n", " x = x * 5\n", " x = x * x\n", @@ -183,6 +185,14 @@ " return x" ] }, + { + "cell_type": "markdown", + "id": "8bb2eba8", + "metadata": {}, + "source": [ + "Remember, each of these operations will return a **Copy**, not a **View**. This can be a common performance pitfall with NumPy and CuPy!" + ] + }, { "cell_type": "code", "execution_count": null, @@ -190,9 +200,9 @@ "metadata": {}, "outputs": [], "source": [ - "\n", "# CPU: Sequential math\n", - "print_benchmark(cpx.profiler.benchmark(sequential_math, (x_cpu,), n_repeat=10))" + "x_cpu = np.ones(sequential_math_shape)\n", + "print_benchmark(cpx.profiler.benchmark(sequential_math, (x_cpu,), n_repeat=10, n_warmup=1))" ] }, { @@ -203,7 +213,8 @@ "outputs": [], "source": [ "# GPU: Sequential math\n", - "print_benchmark(cpx.profiler.benchmark(sequential_math, (x_gpu,), n_repeat=10))" + "x_gpu = cp.ones(sequential_math_shape)\n", + "print_benchmark(cpx.profiler.benchmark(sequential_math, (x_gpu,), n_repeat=10, n_warmup=1))" ] }, { @@ -211,7 +222,7 @@ "id": "0f250bbb", "metadata": {}, "source": [ - "The GPU ran that much faster even without us explicitly managing memory. This is because CuPy is handling all of this for us transparently." + "But even with the copies, the GPU ran that much faster. The copies stay on the GPU; CuPy only transfers data from GPU to CPU when necessary or explicitly requested." ] }, { @@ -224,6 +235,16 @@ "GPUs excel at Linear Algebra. Let's look at **Singular Value Decomposition (SVD)**, a computationally heavy $O(N^3)$ operation." ] }, + { + "cell_type": "code", + "execution_count": null, + "id": "49a665a4", + "metadata": {}, + "outputs": [], + "source": [ + "svd_shape = (3000, 1000)" + ] + }, { "cell_type": "code", "execution_count": null, @@ -232,8 +253,8 @@ "outputs": [], "source": [ "# CPU SVD\n", - "x_cpu = np.random.random((1000, 1000))\n", - "print_benchmark(cpx.profiler.benchmark(np.linalg.svd, (x_cpu,), n_repeat=5))" + "x_cpu = np.random.random(svd_shape)\n", + "print_benchmark(cpx.profiler.benchmark(np.linalg.svd, (x_cpu,), n_repeat=5, n_warmup=1))" ] }, { @@ -244,8 +265,8 @@ "outputs": [], "source": [ "# GPU SVD\n", - "x_gpu = cp.random.random((1000, 1000))\n", - "print_benchmark(cpx.profiler.benchmark(cp.linalg.svd, (x_gpu,), n_repeat=5))" + "x_gpu = cp.random.random(svd_shape)\n", + "print_benchmark(cpx.profiler.benchmark(cp.linalg.svd, (x_gpu,), n_repeat=5, n_warmup=1))" ] }, { @@ -265,11 +286,13 @@ "\n", "A key feature of CuPy is that many **NumPy functions work on CuPy arrays without changing your code**.\n", "\n", - "When you pass a CuPy GPU array (`x_gpu`) into a NumPy function that supports the `__array_function__` protocol (e.g., `np.linalg.svd`), NumPy detects the CuPy input and **delegates the operation to CuPy’s own implementation**, which runs on the GPU.\n", + "When you pass a CuPy GPU array (`x_gpu`) into a NumPy function that supports the `__array_function__` protocol (e.g., `np.linalg.svd()`), NumPy detects the CuPy input and **delegates the operation to CuPy’s own implementation**, which runs on the GPU.\n", "\n", "This allows you to write code using standard `np.*` syntax and have it run on either CPU or GPU seamlessly - **as long as CuPy implements an override for that function.**\n", "\n", - "CuPy also protects you from hidden performance penalties: **it forbids implicit GPU → CPU copies**, raising a `TypeError` when NumPy tries to convert a `cupy.ndarray` into a `numpy.ndarray` behind the scenes. This ensures all device-to-host transfers are **explicit and intentional**, never silent." + "One common source of hidden performance penalties is **implicit transfers between CPU and GPU**. In some cases, CuPy guards against this: for example, when NumPy tries to convert a `cupy.ndarray` into a `numpy.ndarray` via the `__array__` protocol (e.g. `np.asarray(gpu_array)`), CuPy raises a `TypeError` instead of silently copying data to the host. \n", + "\n", + "However, CuPy **does** perform implicit GPU → CPU transfers in other cases, such as printing a GPU array, converting to a Python scalar (e.g. `float`, `.item()`), or evaluating a GPU scalar in a boolean context. We will explore these implicit transfers in a later notebook." ] }, { @@ -279,11 +302,11 @@ "metadata": {}, "outputs": [], "source": [ - "# We create the data on the GPU\n", - "x_gpu = cp.random.random((1000, 1000))\n", + "# We create the data on the GPU...\n", + "x_gpu = cp.random.random(svd_shape)\n", "\n", "# BUT we call the standard NumPy function - CuPy dispatches it to the GPU!\n", - "print_benchmark(cpx.profiler.benchmark(np.linalg.svd, (x_gpu,), n_repeat=5))" + "print_benchmark(cpx.profiler.benchmark(np.linalg.svd, (x_gpu,), n_repeat=5, n_warmup=1))" ] }, { @@ -293,9 +316,7 @@ "source": [ "### 4. Device Management\n", "\n", - "If you have multiple GPUs, CuPy uses the concept of a \"Current Device\" context. \n", - "\n", - "You can use a `with` statement to ensure specific arrays are created on specific cards (e.g., GPU 0 vs GPU 1).\n" + "If you have multiple GPUs, you can use a `with` statement to ensure specific arrays are created on specific devices (e.g., GPU 0 vs GPU 1)." ] }, { @@ -348,7 +369,7 @@ "2. Change the setup line to `xp = cp` (GPU Mode). Run it again.\n", "3. Observe how the exact same logic runs significantly faster on the GPU with CuPy while retaining the implementation properties of NumPy.\n", "\n", - "Note: We use `cupyx.profiler.benchmark` for timing, which automatically handles GPU synchronization." + "Note: We use `cupyx.profiler.benchmark()` for timing, which automatically handles GPU synchronization." ] }, { @@ -360,20 +381,20 @@ }, "outputs": [], "source": [ - "# Step 1.) Setup: Choose Your Device\n", + "# Step 1.) Setup: Choose Your Target\n", "xp = np # Toggle this to 'cp' for GPU acceleration\n", "\n", "print(f\"Running on: {xp.__name__.upper()}\")\n", "\n", "# Step 2.) Data Generation\n", "N = 50_000_000\n", - "print(f\"Generating {N:,} random elements ({N*8/1e9:.2f} GB)...\")\n", + "print(f\"Generating {N:,} random elements ({N*8/2**30:.2f} GB)...\")\n", "arr = xp.random.rand(N)\n", "\n", "# Step 3.) Heavy Computation (Timed)\n", "print(\"Sorting data...\")\n", "# cpx.profiler.benchmark() handles GPU synchronization automatically\n", - "result = cpx.profiler.benchmark(xp.sort, (arr,), n_repeat=5)\n", + "result = cpx.profiler.benchmark(xp.sort, (arr,), n_repeat=5, n_warmup=1)\n", "print_benchmark(result)\n", "\n", "# Step 4.) Manipulation & Broadcasting\n", @@ -392,7 +413,7 @@ "check_sums = xp.sum(normalized_matrix, axis=1)\n", "xp.testing.assert_allclose(check_sums, 1.0)\n", "\n", - "print(\" -> Verification: PASSED (All rows sum to 1.0)\")" + "print(\"Verification: PASSED (All rows sum to 1.0)\")" ] }, { @@ -400,7 +421,7 @@ "id": "077b7589", "metadata": {}, "source": [ - "**TODO: When working with CuPy arrays, try changing `xp.testing.assert_allclose` to `np.testing.assert_allclose`. What happens and why?**" + "**TODO: When working with CuPy arrays, try changing `xp.testing.assert_allclose()` to `np.testing.assert_allclose()`. What happens and why?**" ] }, { @@ -415,7 +436,7 @@ "\n", "**TODO:** \n", "1) **Generate Data:** Create a NumPy array (`y_cpu`) and a CuPy array (`y_gpu`) representing $\\sin(x)$ from $0$ to $2\\pi$ with `50,000,000` points.\n", - "2) **Benchmark CPU and GPU:** Use `benchmark()` from `cupyx.profiler` to measure both `np.sort` and `cp.sort`." + "2) **Benchmark CPU and GPU:** Use `benchmark()` from `cupyx.profiler` to measure both `np.sort()` and `cp.sort()`." ] }, { @@ -440,15 +461,15 @@ "\n", "# Step 2.) Benchmark NumPy (CPU)\n", "print(\"Benchmarking NumPy Sort (this may take a few seconds)...\")\n", - "# TODO: Use cpx.profiler.benchmark(function, (args,), n_repeat=5)\n", - "# Hint: Pass the function `np.sort` and the argument `(y_cpu,)`\n", + "# TODO: Use cpx.profiler.benchmark(function, (args,), n_repeat=5, n_warmup=1)\n", + "# Hint: Pass the function `np.sort()` and the argument `(y_cpu,)`\n", "# Note: The comma in (y_cpu,) is required to make it a tuple!\n", "\n", "\n", "# Step 3.) Benchmark CuPy (GPU)\n", "print(\"Benchmarking CuPy Sort...\")\n", - "# TODO: Use cpx.profiler.benchmark(function, (args,), n_repeat=5)\n", - "# Hint: Pass the function `cp.sort` and the argument `(y_gpu,)`\n", + "# TODO: Use cpx.profiler.benchmark(function, (args,), n_repeat=5, n_warmup=1)\n", + "# Hint: Pass the function `cp.sort()` and the argument `(y_gpu,)`\n", "# Note: The comma in (y_gpu,) is required to make it a tuple!" ] }, @@ -459,7 +480,7 @@ "id": "qnAvEk5QFAA8" }, "source": [ - "**EXTRA CREDIT: Benchmark with different array sizes and find the size at which CuPy and NumPy take the same amount of time. Try to extract the timing data from `cupyx.profiler.benchmark`'s return value and customize how the output is displayed. You could even make a graph.**" + "**EXTRA CREDIT: Benchmark with different array sizes and find the size at which CuPy and NumPy take the same amount of time. Try to extract the timing data from `cupyx.profiler.benchmark()`'s return value and customize how the output is displayed. You could even make a graph.**" ] }, { diff --git a/tutorials/accelerated-python/notebooks/fundamentals/04__numpy_to_cupy__svd_reconstruction.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/04__numpy_to_cupy__svd_reconstruction.ipynb index 8d905f2c..63edd066 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/04__numpy_to_cupy__svd_reconstruction.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/04__numpy_to_cupy__svd_reconstruction.ipynb @@ -323,7 +323,7 @@ "\n", "Imagine you're measuring how long it takes to ship a package to someone, but you only time how long it takes for you to drop it off at the post office, not how long it takes for them to receive it and send you a thank you.\n", "\n", - "Common Pythonic benchmarking tools like `%timeit` are not GPU aware, so it's easy to measure incorrectly with them. We can only use them when we know the code we're benchmarking will perform the proper synchronization. It's better to use something like [`cupyx.profiler.benchmark`](https://docs.cupy.dev/en/stable/reference/generated/cupyx.profiler.benchmark.html#cupyx.profiler.benchmark).\n", + "Common Pythonic benchmarking tools like `%timeit` are not GPU aware, so it's easy to measure incorrectly with them. We can only use them when we know the code we're benchmarking will perform the proper synchronization. It's better to use something like [`cupyx.profiler.benchmark()`](https://docs.cupy.dev/en/stable/reference/generated/cupyx.profiler.benchmark.html#cupyx.profiler.benchmark).\n", "\n", "First, we need a NumPy (CPU) and CuPy (GPU) copy of our image:" ] @@ -337,7 +337,7 @@ }, "outputs": [], "source": [ - "cpu_image = cv2.imread('loonie.jpg', cv2.IMREAD_GRAYSCALE)\n", + "cpu_image = cv2.imread(\"loonie.jpg\", cv2.IMREAD_GRAYSCALE)\n", "gpu_image = cp.asarray(cpu_image)" ] }, @@ -380,7 +380,7 @@ "id": "TE6qPht1xAkm" }, "source": [ - "Depending on your hardware, the CPU and GPU might be close to the same speed, or the GPU might even be slower! This is because the image is not big enough to fully utilize the GPU. We can simulate a larger image by tiling the image using `np.tile`. This duplicates the image both along axis 0 and axis 1:" + "Depending on your hardware, the CPU and GPU might be close to the same speed, or the GPU might even be slower! This is because the image is not big enough to fully utilize the GPU. We can simulate a larger image by tiling the image using `np.tile()`. This duplicates the image both along axis 0 and axis 1:" ] }, { @@ -392,7 +392,7 @@ }, "outputs": [], "source": [ - "cpu_image_tile = np.tile(cpu_image, (2, 2))\n", + "cpu_image_tile = np.tile(cpu_image, (3, 3))\n", "gpu_image_tile = cp.asarray(cpu_image_tile)" ] }, @@ -435,7 +435,7 @@ "id": "5nlgOqkBxAkw" }, "source": [ - "**TODO: Experiment with differ sizes of image by changing the `np.tile` arguments. When is the GPU faster?**" + "**TODO: Experiment with different sizes of image by changing the `np.tile()` arguments. When is the GPU faster?**" ] } ], 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 d614f34e..046cfd47 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 @@ -19,12 +19,12 @@ "\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", + "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 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", + "1. **Host Memory:** Accessible by the CPU.\n", + "2. **Device Memory:** 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", + "To ensure data is accessible from a particular processor, we need to explicity transfer it:\n", "\n", "* **Host $\\to$ Device:** Move data to the GPU to compute.\n", " * Syntax: `x_device = cp.asarray(x_host)`\n", @@ -101,7 +101,7 @@ "@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", + " dominance: float = 0.1 # How much larger the top eigenvalue is (controls convergence, higher == faster)\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", @@ -167,7 +167,7 @@ " \"\"\"\n", " Performs power iteration using purely NumPy (CPU).\n", " \"\"\"\n", - " # Initialize solution vector\n", + " # Initialize solution vector.\n", " x = np.ones(A.shape[0], dtype=np.float64)\n", "\n", " for i in range(0, cfg.max_steps, cfg.check_frequency):\n", @@ -187,7 +187,7 @@ " print(f\"Step {i}: residual = {res:.3e}\")\n", "\n", " # Save a checkpoint.\n", - " np.savetxt(f\"host_{i}.txt\", x)\n", + " np.savetxt(f\"/tmp/host_{i}.txt\", x)\n", "\n", " # Convergence check.\n", " if res < cfg.residual_threshold:\n", @@ -201,10 +201,12 @@ " return (x.T @ (A @ x)) / (x.T @ x)\n", "\n", "lam_est_host = estimate_host(A_host)\n", + "\n", "assert isinstance(lam_est_host, (np.ndarray, np.generic)), \"Must return a NumPy array or NumPy scalar\"\n", + "np.testing.assert_allclose(lam_est_host, 1, atol=1e-4)\n", "\n", "print()\n", - "print(lam_est_host)" + "print(\"Dominant Eigenvalue:\", lam_est_host)" ] }, { @@ -265,7 +267,7 @@ " if cfg.progress:\n", " print(f\"Step {i}: residual = {res:.3e}\")\n", "\n", - " np.savetxt(f\"host_{i}.txt\", x) # Save a checkpoint.\n", + " np.savetxt(f\"/tmp/host_{i}.txt\", x) # Save a checkpoint.\n", "\n", " # Convergence check\n", " if res < cfg.residual_threshold:\n", @@ -277,10 +279,12 @@ " x = y / np.linalg.norm(y)\n", "\n", "lam_est_device = estimate_device(A_host)\n", + "\n", "assert isinstance(lam_est_device, (np.ndarray, np.generic)), \"Must return a NumPy array or NumPy scalar\"\n", + "np.testing.assert_allclose(lam_est_device, 1, atol=1e-4)\n", "\n", "print()\n", - "print(lam_est_device)" + "print(\"Dominant Eigenvalue:\", lam_est_device)" ] }, { @@ -290,7 +294,7 @@ "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", + "In the previous step, we generated data on the CPU and copied it to the GPU. For large datasets, the transfer time from host to device can be a bottleneck. \n", "\n", "It is almost always faster to **generate** the data directly on the GPU if possible.\n", "\n", @@ -341,8 +345,10 @@ "\n", "lam_est_device_gen = estimate_device(A_device)\n", "\n", + "np.testing.assert_allclose(lam_est_device_gen, 1, atol=1e-4)\n", + "\n", "print()\n", - "print(lam_est_device_gen)" + "print(\"Dominant Eigenvalue:\", lam_est_device_gen)" ] }, { @@ -386,7 +392,7 @@ "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" + "Finally, let's verify our accuracy against a reference implementation (`numpy.linalg.eigvals()`) and benchmark the speedup.\n" ] }, { @@ -408,9 +414,9 @@ "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", + "print(f\"Power Iteration (Host) = {lam_est_host}\")\n", + "print(f\"Power Iteration (Device) = {lam_est_device}\")\n", + "print(f\"`eigvals` Reference = {lam_ref}\")\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", @@ -427,7 +433,7 @@ "id": "f092af24", "metadata": {}, "source": [ - "#### Benchmarking with `cupyx.profiler.benchmark`\n", + "#### 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", "\n", @@ -444,15 +450,15 @@ "cfg = PowerIterationConfig(progress=False)\n", "\n", "print(\"Timing Host...\")\n", - "T_host = cpx.profiler.benchmark(estimate_host, args=(A_host, cfg), n_repeat=10).cpu_times\n", + "T_host = cpx.profiler.benchmark(estimate_host, args=(A_host, cfg), n_repeat=10, n_warmup=1).cpu_times\n", "\n", "print(\"Timing Device...\")\n", - "T_device = cpx.profiler.benchmark(estimate_device, args=(A_host, cfg), n_repeat=10).cpu_times\n", + "T_device = cpx.profiler.benchmark(estimate_device, args=(A_host, cfg), n_repeat=10, n_warmup=1).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", - "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", + "print(f\"Power Iteration (Host) = {T_host.mean() * 1000:.6g} 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:.6g} ms ± {(T_device.std() / T_device.mean()):.2%} (mean ± relative stdev of {T_device.size} runs)\")\n", + "print(f\"`eigvals` Reference = {T_ref * 1000:.6g} ms\")\n", "\n", "speedup = T_host.mean() / T_device.mean()\n", "print()\n", 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 bb784bfc..1ca1fd64 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/06__asynchrony__power_iteration.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/06__asynchrony__power_iteration.ipynb @@ -101,7 +101,7 @@ " dim: int = 8192\n", " dominance: float = 0.05\n", " max_steps: int = 1000\n", - " check_frequency: int = 10\n", + " check_frequency: int = 15\n", " progress: bool = True\n", " residual_threshold: float = 1e-10\n", "\n", @@ -130,7 +130,7 @@ " print(f\"step {i}: residual = {res:.3e}\")\n", "\n", " # Copy from device to host and save a checkpoint.\n", - " np.savetxt(f\"device_{i}.txt\", cp.asnumpy(x))\n", + " np.savetxt(f\"/tmp/device_{i}.txt\", cp.asnumpy(x))\n", "\n", " if res < cfg.residual_threshold:\n", " break\n", @@ -152,6 +152,8 @@ "lam_est_device = estimate_device(A_device)\n", "stop = time.perf_counter()\n", "\n", + "np.testing.assert_allclose(lam_est_device, 1, atol=1e-4)\n", + "\n", "print()\n", "print(f\"{(stop - start) * 1000:.3f} ms\")" ] @@ -231,7 +233,7 @@ "\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", + "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.annotate()`, another Python context manager, this time from a library called NVTX.\n", "\n", "```\n", "with nvtx.annotate(\"Loop\"):\n", @@ -266,7 +268,7 @@ "- You can synchronize on an event (or an entire stream) by calling `.synchronize()` on it.\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", - "**TODO:** The code below contains the baseline implementation with NVTX annotations already added. Modify it to improve performance by adding asynchrony." + "**TODO:** Copy your NVTX annotated code from before into the cell below (make sure not to overwrite the %%writefile), and modify the code to improve performance by adding asynchrony." ] }, { @@ -290,7 +292,7 @@ " dim: int = 8192\n", " dominance: float = 0.05\n", " max_steps: int = 1000\n", - " check_frequency: int = 10\n", + " check_frequency: int = 15\n", " progress: bool = True\n", " residual_threshold: float = 1e-10\n", "\n", @@ -317,6 +319,8 @@ " lam_est_device = estimate_device(A_device)\n", " stop = time.perf_counter()\n", "\n", + "np.testing.assert_allclose(lam_est_device, 1, atol=1e-4)\n", + "\n", "print()\n", "print(f\"{(stop - start) * 1000:.3f} ms\")" ] @@ -415,7 +419,7 @@ "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\")" + "nsightful.display_nsys_sqlite_file_in_notebook(\"power_iteration__async.sqlite\", title=\"Power Iteration - Async\")" ] } ], diff --git a/tutorials/accelerated-python/notebooks/fundamentals/solutions/01__numpy_intro__ndarray_basics__SOLUTION.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/solutions/01__numpy_intro__ndarray_basics__SOLUTION.ipynb index 110e2aa3..4d337821 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/solutions/01__numpy_intro__ndarray_basics__SOLUTION.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/solutions/01__numpy_intro__ndarray_basics__SOLUTION.ipynb @@ -42,6 +42,13 @@ "execution_count": 1, "id": "cc4596d8-d9ff-4c66-8822-246c0fc830c7", "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:52:03.821583Z", + "iopub.status.busy": "2026-03-09T19:52:03.821348Z", + "iopub.status.idle": "2026-03-09T19:52:03.907234Z", + "shell.execute_reply": "2026-03-09T19:52:03.906331Z", + "shell.execute_reply.started": "2026-03-09T19:52:03.821562Z" + }, "id": "cc4596d8-d9ff-4c66-8822-246c0fc830c7" }, "outputs": [], @@ -60,8 +67,8 @@ "\n", "- **Data**: A pointer to the memory location holding the elements.\n", "- **dtype**: The data type (e.g., `int32`, `float64`) which is uniform across all elements.\n", - "- **Shape**: A tuple defining the size along each dimension (e.g., $(100, 50)$ for 100 rows and 50 columns).\n", - "- **Strides**: The number of bytes to step in memory to reach the next element along each dimension—this is how NumPy efficiently handles different shapes and views.\n", + "- **Shape**: A tuple defining the size along each dimension (e.g., `(100, 50)` for 100 rows and 50 columns).\n", + "- **Strides**: The number of bytes to step in memory to reach the next element along each dimension. This is how NumPy efficiently handles different shapes and views.\n", "\n", "Let's explore these properties by creating a large dataset.\n", "\n", @@ -69,7 +76,7 @@ "\n", "**Quick Docs**\n", "- `np.arange(start, stop, step)`: Returns evenly spaced values in the half-open interval $[\\text{start}, \\text{stop})$.\n", - "- `arr.nbytes`: Total bytes consumed by the array's elements (in bytes).\n", + "- `arr.nbytes`: Total bytes of storage for the array's elements.\n", "- `arr.ndim`: The number of array dimensions (integer).\n", "- `arr.size`: The total number of elements in the array (integer).\n", "- `arr.shape`: The tuple of array dimensions.\n" @@ -77,9 +84,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "465e35bd", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:52:03.908110Z", + "iopub.status.busy": "2026-03-09T19:52:03.907792Z", + "iopub.status.idle": "2026-03-09T19:52:03.913350Z", + "shell.execute_reply": "2026-03-09T19:52:03.912706Z", + "shell.execute_reply.started": "2026-03-09T19:52:03.908084Z" + } + }, "outputs": [], "source": [ "# Use a large number to clearly demonstrate the memory density of ndarrays\n", @@ -88,67 +103,159 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "5f1a613f-bc87-4950-b195-a66bb5bc05d3", "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:52:03.914581Z", + "iopub.status.busy": "2026-03-09T19:52:03.914156Z", + "iopub.status.idle": "2026-03-09T19:52:04.053344Z", + "shell.execute_reply": "2026-03-09T19:52:04.051792Z", + "shell.execute_reply.started": "2026-03-09T19:52:03.914555Z" + }, "id": "5f1a613f-bc87-4950-b195-a66bb5bc05d3" }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "array([ 1, 2, 3, ..., 49999998, 49999999, 50000000],\n", + " shape=(50000000,))" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SOLUTION: Create the input data array with the numbers 1 to 50_000_000 (inclusive).\n", "# np.arange generates values within a half-open interval [start, stop), so we use N + 1 as the stop value.\n", - "arr = np.arange(1, N + 1)" + "arr = np.arange(1, N + 1)\n", + "arr" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "50530f2c-29bf-4061-8f84-bc5be00a5622", "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:52:04.054277Z", + "iopub.status.busy": "2026-03-09T19:52:04.054048Z", + "iopub.status.idle": "2026-03-09T19:52:04.059635Z", + "shell.execute_reply": "2026-03-09T19:52:04.058336Z", + "shell.execute_reply.started": "2026-03-09T19:52:04.054259Z" + }, "id": "50530f2c-29bf-4061-8f84-bc5be00a5622" }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "0.3725290298461914" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SOLUTION: Calculate how large the array is in GB with nbytes.\n", - "# GB is 1e9 bytes. The .nbytes attribute returns the total bytes consumed by the elements.\n", + "# GB is 2**30 bytes. The .nbytes attribute returns the total bytes consumed by the elements.\n", "# Note: This demonstrates that arrays are dense memory blocks, unlike pointer-heavy Python lists.\n", - "arr.nbytes / 1e9" + "arr.nbytes / 2**30" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "ffc15dad-e2fd-4b96-8b39-3496519d0656", "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:52:04.060841Z", + "iopub.status.busy": "2026-03-09T19:52:04.060613Z", + "iopub.status.idle": "2026-03-09T19:52:04.065950Z", + "shell.execute_reply": "2026-03-09T19:52:04.064662Z", + "shell.execute_reply.started": "2026-03-09T19:52:04.060823Z" + }, "id": "ffc15dad-e2fd-4b96-8b39-3496519d0656" }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "1" + ] + }, + "execution_count": 5, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "# SOLUTION: How many dimensions does the array have? (ndim)\n", + "# SOLUTION: How many dimensions does the array have?\n", "arr.ndim" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "b15cdf25-eb35-4926-b306-90ffd62b3d28", "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:52:04.067100Z", + "iopub.status.busy": "2026-03-09T19:52:04.066888Z", + "iopub.status.idle": "2026-03-09T19:52:04.072294Z", + "shell.execute_reply": "2026-03-09T19:52:04.071345Z", + "shell.execute_reply.started": "2026-03-09T19:52:04.067082Z" + }, "id": "b15cdf25-eb35-4926-b306-90ffd62b3d28" }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "50000000" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "# SOLUTION: How many elements does the array have? (size)\n", + "# SOLUTION: How many elements does the array have?\n", "arr.size" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "63887722-c9d7-405e-a019-e75646115541", "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:52:04.073589Z", + "iopub.status.busy": "2026-03-09T19:52:04.073261Z", + "iopub.status.idle": "2026-03-09T19:52:04.079056Z", + "shell.execute_reply": "2026-03-09T19:52:04.077552Z", + "shell.execute_reply.started": "2026-03-09T19:52:04.073559Z" + }, "id": "63887722-c9d7-405e-a019-e75646115541" }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "(50000000,)" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SOLUTION: What is the shape of the array?\n", "arr.shape" @@ -163,7 +270,11 @@ "\n", "Arrays can logically represent data in many ways (e.g., 1D signal, 2D image, 4D video batch) independent of the underlying physical memory block.\n", "\n", - "A critical performance feature is that operations like transposing or `reshape` often return a **View** instead of a **Copy**. A View only changes the metadata (`shape` and `strides`) without duplicating the physical data, making these operations nearly instantaneous.\n", + "Most operations, like adding two arrays together, returns a **Copy**, which requires allocating a new array, which can negatively impact performance.\n", + "\n", + "Some operations, like transposing or `reshape()` often return a **View** instead of a **Copy**. A View only changes the metadata (`shape` and `strides`) without duplicating the physical data, making these operations nearly instantaneous.\n", + "\n", + "Most Copy operations take an `out` parameter that takes an array; if it provided, the result is written to that array instead of allocating a new one. For example, `A + B` or `np.add(A, B)` will return a new array with the result, but `np.add(A + B, out=A)` will place the result in `A` without an allocation.\n", "\n", "---\n", "\n", @@ -177,12 +288,31 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "1527b4f6-5d75-47d4-97e0-d0e78bbc59f9", "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:52:04.079731Z", + "iopub.status.busy": "2026-03-09T19:52:04.079529Z", + "iopub.status.idle": "2026-03-09T19:52:04.107196Z", + "shell.execute_reply": "2026-03-09T19:52:04.105954Z", + "shell.execute_reply.started": "2026-03-09T19:52:04.079714Z" + }, "id": "1527b4f6-5d75-47d4-97e0-d0e78bbc59f9" }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "array([0.0000000e+00, 2.0000004e-04, 4.0000008e-04, ..., 9.9999960e+02,\n", + " 9.9999980e+02, 1.0000000e+03], shape=(5000000,))" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SOLUTION: Create a new array with 5_000_000 elements containing equally spaced values between 0 to 1000 (inclusive).\n", "# np.linspace returns 'num' evenly spaced samples over the closed interval [start, stop].\n", @@ -192,12 +322,42 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "2f51aa2e-b994-4a91-aed6-4a4632eb7050", "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:52:04.108245Z", + "iopub.status.busy": "2026-03-09T19:52:04.108023Z", + "iopub.status.idle": "2026-03-09T19:52:04.717493Z", + "shell.execute_reply": "2026-03-09T19:52:04.716131Z", + "shell.execute_reply.started": "2026-03-09T19:52:04.108227Z" + }, "id": "2f51aa2e-b994-4a91-aed6-4a4632eb7050" }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "array([[0.0400675 , 0.03433849, 0.10189916, ..., 0.80917626, 0.62766635,\n", + " 0.69858587],\n", + " [0.92463309, 0.33014371, 0.97584063, ..., 0.46073024, 0.80102199,\n", + " 0.73499053],\n", + " [0.31035298, 0.098541 , 0.44346722, ..., 0.94924401, 0.89655879,\n", + " 0.84106206],\n", + " ...,\n", + " [0.12849301, 0.00662825, 0.01222947, ..., 0.52379592, 0.07114445,\n", + " 0.39887908],\n", + " [0.45638408, 0.87280363, 0.51137569, ..., 0.04572943, 0.06830721,\n", + " 0.69372701],\n", + " [0.57562788, 0.38413504, 0.65051016, ..., 0.3541537 , 0.33969769,\n", + " 0.92381857]], shape=(10000, 5000))" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SOLUTION: Create a random array that is 10_000 rows by 5_000 columns.\n", "# np.random.default_rng().random(size) returns random floats in [0.0, 1.0). size can be a tuple.\n", @@ -207,9 +367,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "id": "4ec06270-6e08-4cce-9385-9dc8b53e95fd", "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:52:04.718321Z", + "iopub.status.busy": "2026-03-09T19:52:04.718028Z", + "iopub.status.idle": "2026-03-09T19:52:05.032781Z", + "shell.execute_reply": "2026-03-09T19:52:05.031590Z", + "shell.execute_reply.started": "2026-03-09T19:52:04.718302Z" + }, "id": "4ec06270-6e08-4cce-9385-9dc8b53e95fd" }, "outputs": [], @@ -221,12 +388,42 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "id": "cdde560b-5ba6-484c-a601-00b7ef71273d", "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:52:05.033879Z", + "iopub.status.busy": "2026-03-09T19:52:05.033608Z", + "iopub.status.idle": "2026-03-09T19:52:05.040803Z", + "shell.execute_reply": "2026-03-09T19:52:05.039235Z", + "shell.execute_reply.started": "2026-03-09T19:52:05.033857Z" + }, "id": "cdde560b-5ba6-484c-a601-00b7ef71273d" }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "array([[2.45234896e-04, 4.48883822e-04, 6.00343428e-04, 7.66594983e-04,\n", + " 1.04022200e-03],\n", + " [1.27074932e-03, 1.30087806e-03, 2.29955069e-03, 2.33961878e-03,\n", + " 2.95750178e-03],\n", + " [3.02767082e-03, 3.13463767e-03, 3.29535008e-03, 3.82121766e-03,\n", + " 4.09544080e-03],\n", + " ...,\n", + " [9.97846485e-01, 9.97884396e-01, 9.97997115e-01, 9.98257761e-01,\n", + " 9.98390990e-01],\n", + " [9.98455493e-01, 9.98504022e-01, 9.98518150e-01, 9.98541082e-01,\n", + " 9.98887643e-01],\n", + " [9.99350612e-01, 9.99370475e-01, 9.99601011e-01, 9.99848239e-01,\n", + " 9.99918236e-01]], shape=(10000000, 5))" + ] + }, + "execution_count": 11, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SOLUTION: Reshape the array to have the last dimension of length 5.\n", "# Using -1 lets NumPy automatically calculate the first dimension.\n", @@ -257,12 +454,31 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "id": "44dd3ac2-c9b7-4327-ba63-860b074c0583", "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:52:05.041814Z", + "iopub.status.busy": "2026-03-09T19:52:05.041582Z", + "iopub.status.idle": "2026-03-09T19:52:05.352312Z", + "shell.execute_reply": "2026-03-09T19:52:05.351284Z", + "shell.execute_reply.started": "2026-03-09T19:52:05.041795Z" + }, "id": "44dd3ac2-c9b7-4327-ba63-860b074c0583" }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "array([3.10127913e-03, 1.01682986e-02, 1.73743170e-02, ...,\n", + " 4.99037675e+00, 4.99290639e+00, 4.99808857e+00], shape=(10000000,))" + ] + }, + "execution_count": 12, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SOLUTION: Find the sum of each row in the reshaped array (arr_new) above.\n", "# To sum the row's content, we reduce across the columns (axis=1).\n", @@ -295,12 +511,37 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "id": "b15342af-2916-481a-9724-9874acf4ed24", "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:52:05.353277Z", + "iopub.status.busy": "2026-03-09T19:52:05.353050Z", + "iopub.status.idle": "2026-03-09T19:52:05.885031Z", + "shell.execute_reply": "2026-03-09T19:52:05.883768Z", + "shell.execute_reply.started": "2026-03-09T19:52:05.353258Z" + }, "id": "b15342af-2916-481a-9724-9874acf4ed24" }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "array([[0.0790754 , 0.14474151, 0.1935793 , 0.24718671, 0.33541708],\n", + " [0.12497168, 0.12793468, 0.22614901, 0.2300895 , 0.29085513],\n", + " [0.17426129, 0.18041789, 0.18966789, 0.21993484, 0.23571809],\n", + " ...,\n", + " [0.19995414, 0.19996174, 0.19998432, 0.20003655, 0.20006325],\n", + " [0.19997481, 0.19998453, 0.19998736, 0.19999195, 0.20006136],\n", + " [0.19994656, 0.19995053, 0.19999666, 0.20004612, 0.20006013]],\n", + " shape=(10000000, 5))" + ] + }, + "execution_count": 13, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SOLUTION: Normalize each row of the 2D array (arr_new) by dividing by the sum you just computed (arr_sum).\n", "# 'arr_new' has shape (M, N) and 'arr_sum' has shape (M,).\n", @@ -312,12 +553,30 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "id": "b04622b8-c6de-4756-8a56-e3d2835a5eaf", "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:52:05.885870Z", + "iopub.status.busy": "2026-03-09T19:52:05.885649Z", + "iopub.status.idle": "2026-03-09T19:52:06.364026Z", + "shell.execute_reply": "2026-03-09T19:52:06.362963Z", + "shell.execute_reply.started": "2026-03-09T19:52:05.885852Z" + }, "id": "b04622b8-c6de-4756-8a56-e3d2835a5eaf" }, - "outputs": [], + "outputs": [ + { + "data": { + "text/plain": [ + "True" + ] + }, + "execution_count": 14, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# SOLUTION (EXTRA CREDIT): Prove that your normalized array is actually normalized.\n", "# If normalized correctly, the sum of every row should now be 1.0.\n", diff --git a/tutorials/accelerated-python/notebooks/fundamentals/solutions/02__numpy_linear_algebra__svd_reconstruction__SOLUTION.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/solutions/02__numpy_linear_algebra__svd_reconstruction__SOLUTION.ipynb index d040879a..75420a97 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/solutions/02__numpy_linear_algebra__svd_reconstruction__SOLUTION.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/solutions/02__numpy_linear_algebra__svd_reconstruction__SOLUTION.ipynb @@ -28,7 +28,8 @@ "import numpy as np\n", "import matplotlib.pyplot as plt\n", "import cv2\n", - "import urllib.request" + "import urllib.request\n", + "import os" ] }, { @@ -67,7 +68,10 @@ "source": [ "urllib.request.urlretrieve(\n", " \"https://drive.usercontent.google.com/download?id=1ClKrHt4-SIHaeBJdF0K3MG64jyVnt62L&export=download\",\n", - " \"loonie.jpg\")" + " \"loonie.jpg\")\n", + "\n", + "jpeg_size = os.path.getsize(\"loonie.jpg\")\n", + "print(f\"JPEG file size: {jpeg_size} bytes\")" ] }, { @@ -77,7 +81,7 @@ "id": "LcRb8UEbw_3Z" }, "source": [ - "Next we read the image in grayscale mode:" + "Then we read the image in grayscale mode:" ] }, { @@ -383,7 +387,7 @@ "id": "gSoV2Jj-w_31" }, "source": [ - "Now we'll print the compression ratio for the values of `n` used above. This is the number of bytes of the reduced arrays added together and divided by the size of the original grayscale image array. It seems we can get significant storage savings with this technique." + "Now we'll print the compression ratio for the values of `n` used above. This is the number of bytes of the reduced arrays added together and divided by the size of the original grayscale image array. How does this compare to the size of the original color JPEG (`jpeg_size`)? It seems we can get significant storage savings with this technique." ] }, { diff --git a/tutorials/accelerated-python/notebooks/fundamentals/solutions/03__numpy_to_cupy__ndarray_basics__SOLUTION.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/solutions/03__numpy_to_cupy__ndarray_basics__SOLUTION.ipynb index b2b8f0e4..c26fcce2 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/solutions/03__numpy_to_cupy__ndarray_basics__SOLUTION.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/solutions/03__numpy_to_cupy__ndarray_basics__SOLUTION.ipynb @@ -33,9 +33,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "824007e3", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:50:41.631701Z", + "iopub.status.busy": "2026-03-09T19:50:41.631462Z", + "iopub.status.idle": "2026-03-09T19:50:42.740439Z", + "shell.execute_reply": "2026-03-09T19:50:42.739247Z", + "shell.execute_reply.started": "2026-03-09T19:50:41.631680Z" + } + }, "outputs": [], "source": [ "import numpy as np\n", @@ -60,29 +68,61 @@ "\n", "Let's compare the performance of creating a large 3D array (approx. 100 MB in size) on the CPU versus the GPU.\n", "\n", - "We will use `np.ones` for the CPU and `cp.ones` for the GPU." + "We will use `np.ones()` for the CPU and `cp.ones()` for the GPU." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "60bba049", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:50:42.741857Z", + "iopub.status.busy": "2026-03-09T19:50:42.741361Z", + "iopub.status.idle": "2026-03-09T19:50:43.154406Z", + "shell.execute_reply": "2026-03-09T19:50:43.152971Z", + "shell.execute_reply.started": "2026-03-09T19:50:42.741821Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ones: 25.258 ms +/- 0.495 ms\n" + ] + } + ], "source": [ "# CPU creation\n", - "print_benchmark(cpx.profiler.benchmark(np.ones, ((50, 500, 500),), n_repeat=10))" + "print_benchmark(cpx.profiler.benchmark(np.ones, ((50, 500, 500),), n_repeat=10, n_warmup=1))" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "9fc87bfe", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:50:43.155666Z", + "iopub.status.busy": "2026-03-09T19:50:43.155150Z", + "iopub.status.idle": "2026-03-09T19:50:43.201057Z", + "shell.execute_reply": "2026-03-09T19:50:43.199832Z", + "shell.execute_reply.started": "2026-03-09T19:50:43.155640Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "ones: 0.038 ms +/- 0.024 ms\n" + ] + } + ], "source": [ "# GPU creation\n", - "print_benchmark(cpx.profiler.benchmark(cp.ones, ((50, 500, 500),), n_repeat=10))" + "print_benchmark(cpx.profiler.benchmark(cp.ones, ((50, 500, 500),), n_repeat=10, n_warmup=1))" ] }, { @@ -92,11 +132,11 @@ "source": [ "We can see here that creating this array on the GPU is much faster than doing so on the CPU!\n", "\n", - "**About `cupyx.profiler.benchmark`:**\n", + "**About `cupyx.profiler.benchmark()`:**\n", "\n", - "We use CuPy's built-in `benchmark` utility for timing GPU operations. This is important because GPU operations are **asynchronous** - when you call a CuPy function, the CPU places a task in the GPU's \"to-do list\" (stream) and immediately moves on without waiting.\n", + "We use CuPy's built-in `benchmark()` utility for timing GPU operations. This is important because GPU operations are **asynchronous** - when you call a CuPy function, the CPU places a task in the GPU's \"to-do list\" (stream) and immediately moves on without waiting.\n", "\n", - "The `benchmark` function handles all the complexity of proper GPU timing for us:\n", + "The `benchmark()` function handles all the complexity of proper GPU timing for us:\n", "- It automatically synchronizes GPU streams to get accurate measurements.\n", "- It runs warm-up iterations to avoid cold-start overhead.\n", "- It reports both CPU wall-clock times (`cpu_times`) and GPU kernel times (`gpu_times`). We use `cpu_times` for all comparisons because it measures end-to-end wall-clock time, giving a fair apples-to-apples comparison between CPU and GPU code.\n", @@ -116,14 +156,20 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "e4b2df00", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:50:43.202011Z", + "iopub.status.busy": "2026-03-09T19:50:43.201773Z", + "iopub.status.idle": "2026-03-09T19:50:43.206209Z", + "shell.execute_reply": "2026-03-09T19:50:43.204910Z", + "shell.execute_reply.started": "2026-03-09T19:50:43.201992Z" + } + }, "outputs": [], "source": [ - "# Create fresh arrays for the benchmark\n", - "x_cpu = np.ones((50, 500, 500))\n", - "x_gpu = cp.ones((50, 500, 500))\n", + "multiply_shape = (50, 500, 500)\n", "\n", "def multiply(x):\n", " return x * 5" @@ -131,24 +177,58 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "80f6b09d", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:50:43.207336Z", + "iopub.status.busy": "2026-03-09T19:50:43.207113Z", + "iopub.status.idle": "2026-03-09T19:50:43.599432Z", + "shell.execute_reply": "2026-03-09T19:50:43.598191Z", + "shell.execute_reply.started": "2026-03-09T19:50:43.207317Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "multiply: 32.204 ms +/- 0.517 ms\n" + ] + } + ], "source": [ "# CPU Operation\n", - "print_benchmark(cpx.profiler.benchmark(multiply, (x_cpu,), n_repeat=10))" + "x_cpu = np.ones(multiply_shape)\n", + "print_benchmark(cpx.profiler.benchmark(multiply, (x_cpu,), n_repeat=10, n_warmup=1))" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "a3d3fb89", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:50:43.600467Z", + "iopub.status.busy": "2026-03-09T19:50:43.600158Z", + "iopub.status.idle": "2026-03-09T19:50:43.617202Z", + "shell.execute_reply": "2026-03-09T19:50:43.616155Z", + "shell.execute_reply.started": "2026-03-09T19:50:43.600434Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "multiply: 0.084 ms +/- 0.033 ms\n" + ] + } + ], "source": [ "# GPU Operation\n", - "print_benchmark(cpx.profiler.benchmark(multiply, (x_gpu,), n_repeat=10))" + "x_gpu = cp.ones(multiply_shape)\n", + "print_benchmark(cpx.profiler.benchmark(multiply, (x_gpu,), n_repeat=10, n_warmup=1))" ] }, { @@ -166,16 +246,26 @@ "source": [ "#### Sequential Operations & Memory\n", "\n", - "Now let's do a couple of operations sequentially, something which would suffer from memory transfer times in Numba examples without explicit memory management." + "Now let's do a couple of operations sequentially." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "6fdd9254", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:50:43.618191Z", + "iopub.status.busy": "2026-03-09T19:50:43.617879Z", + "iopub.status.idle": "2026-03-09T19:50:43.622640Z", + "shell.execute_reply": "2026-03-09T19:50:43.621303Z", + "shell.execute_reply.started": "2026-03-09T19:50:43.618161Z" + } + }, "outputs": [], "source": [ + "sequential_math_shape = (50, 500, 500)\n", + "\n", "def sequential_math(x):\n", " x = x * 5\n", " x = x * x\n", @@ -183,26 +273,68 @@ " return x" ] }, + { + "cell_type": "markdown", + "id": "9e5a7f69", + "metadata": {}, + "source": [ + "Remember, each of these operations will return a **Copy**, not a **View**. This can be a common performance pitfall with NumPy and CuPy!" + ] + }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "7a9e5dde", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:50:43.623712Z", + "iopub.status.busy": "2026-03-09T19:50:43.623478Z", + "iopub.status.idle": "2026-03-09T19:50:44.871102Z", + "shell.execute_reply": "2026-03-09T19:50:44.869708Z", + "shell.execute_reply.started": "2026-03-09T19:50:43.623693Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "sequential_math: 110.001 ms +/- 0.655 ms\n" + ] + } + ], "source": [ "# CPU: Sequential math\n", - "print_benchmark(cpx.profiler.benchmark(sequential_math, (x_cpu,), n_repeat=10))" + "x_cpu = np.ones(sequential_math_shape)\n", + "print_benchmark(cpx.profiler.benchmark(sequential_math, (x_cpu,), n_repeat=10, n_warmup=1))" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "697c5b75", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:50:44.872025Z", + "iopub.status.busy": "2026-03-09T19:50:44.871796Z", + "iopub.status.idle": "2026-03-09T19:50:45.019025Z", + "shell.execute_reply": "2026-03-09T19:50:45.017619Z", + "shell.execute_reply.started": "2026-03-09T19:50:44.872006Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "sequential_math: 0.157 ms +/- 0.048 ms\n" + ] + } + ], "source": [ "# GPU: Sequential math\n", - "print_benchmark(cpx.profiler.benchmark(sequential_math, (x_gpu,), n_repeat=10))" + "x_gpu = cp.ones(sequential_math_shape)\n", + "print_benchmark(cpx.profiler.benchmark(sequential_math, (x_gpu,), n_repeat=10, n_warmup=1))" ] }, { @@ -210,7 +342,7 @@ "id": "eec1185b", "metadata": {}, "source": [ - "The GPU ran that much faster even without us explicitly managing memory. This is because CuPy is handling all of this for us transparently." + "But even with the copies, the GPU ran that much faster. The copies stay on the GPU; CuPy only transfers data from GPU to CPU when necessary or explicitly requested." ] }, { @@ -225,26 +357,76 @@ }, { "cell_type": "code", - "execution_count": null, - "id": "ec59cfdd", - "metadata": {}, + "execution_count": 10, + "id": "bed3783b", + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:50:45.020285Z", + "iopub.status.busy": "2026-03-09T19:50:45.019894Z", + "iopub.status.idle": "2026-03-09T19:50:45.024993Z", + "shell.execute_reply": "2026-03-09T19:50:45.023690Z", + "shell.execute_reply.started": "2026-03-09T19:50:45.020254Z" + } + }, "outputs": [], + "source": [ + "svd_shape = (3000, 1000)" + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "ec59cfdd", + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:50:45.026069Z", + "iopub.status.busy": "2026-03-09T19:50:45.025770Z", + "iopub.status.idle": "2026-03-09T19:50:54.233011Z", + "shell.execute_reply": "2026-03-09T19:50:54.231417Z", + "shell.execute_reply.started": "2026-03-09T19:50:45.026042Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "svd: 1449.570 ms +/- 47.411 ms\n" + ] + } + ], "source": [ "# CPU SVD\n", - "x_cpu = np.random.random((1000, 1000))\n", - "print_benchmark(cpx.profiler.benchmark(np.linalg.svd, (x_cpu,), n_repeat=5))" + "x_cpu = np.random.random(svd_shape)\n", + "print_benchmark(cpx.profiler.benchmark(np.linalg.svd, (x_cpu,), n_repeat=5, n_warmup=1))" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "id": "1030dbeb", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:50:54.234203Z", + "iopub.status.busy": "2026-03-09T19:50:54.233626Z", + "iopub.status.idle": "2026-03-09T19:50:57.460338Z", + "shell.execute_reply": "2026-03-09T19:50:57.459124Z", + "shell.execute_reply.started": "2026-03-09T19:50:54.234169Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "svd: 467.642 ms +/- 1.820 ms\n" + ] + } + ], "source": [ "# GPU SVD\n", - "x_gpu = cp.random.random((1000, 1000))\n", - "print_benchmark(cpx.profiler.benchmark(cp.linalg.svd, (x_gpu,), n_repeat=5))" + "x_gpu = cp.random.random(svd_shape)\n", + "print_benchmark(cpx.profiler.benchmark(cp.linalg.svd, (x_gpu,), n_repeat=5, n_warmup=1))" ] }, { @@ -264,25 +446,43 @@ "\n", "A key feature of CuPy is that many **NumPy functions work on CuPy arrays without changing your code**.\n", "\n", - "When you pass a CuPy GPU array (`x_gpu`) into a NumPy function that supports the `__array_function__` protocol (e.g., `np.linalg.svd`), NumPy detects the CuPy input and **delegates the operation to CuPy's own implementation**, which runs on the GPU.\n", + "When you pass a CuPy GPU array (`x_gpu`) into a NumPy function that supports the `__array_function__` protocol (e.g., `np.linalg.svd()`), NumPy detects the CuPy input and **delegates the operation to CuPy's own implementation**, which runs on the GPU.\n", "\n", "This allows you to write code using standard `np.*` syntax and have it run on either CPU or GPU seamlessly - **as long as CuPy implements an override for that function.**\n", "\n", - "CuPy also protects you from hidden performance penalties: **it forbids implicit GPU → CPU copies**, raising a `TypeError` when NumPy tries to convert a `cupy.ndarray` into a `numpy.ndarray` behind the scenes. This ensures all device-to-host transfers are **explicit and intentional**, never silent." + "One common source of hidden performance penalties is **implicit transfers between CPU and GPU**. In some cases, CuPy guards against this: for example, when NumPy tries to convert a `cupy.ndarray` into a `numpy.ndarray` via the `__array__` protocol (e.g. `np.asarray(gpu_array)`), CuPy raises a `TypeError` instead of silently copying data to the host. \n", + "\n", + "However, CuPy **does** perform implicit GPU → CPU transfers in other cases, such as printing a GPU array, converting to a Python scalar (e.g. `float`, `.item()`), or evaluating a GPU scalar in a boolean context. We will explore these implicit transfers in a later notebook." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "id": "12bd8e64", - "metadata": {}, - "outputs": [], - "source": [ - "# We create the data on the GPU\n", - "x_gpu = cp.random.random((1000, 1000))\n", + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:50:57.461537Z", + "iopub.status.busy": "2026-03-09T19:50:57.461182Z", + "iopub.status.idle": "2026-03-09T19:51:00.350501Z", + "shell.execute_reply": "2026-03-09T19:51:00.349240Z", + "shell.execute_reply.started": "2026-03-09T19:50:57.461506Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "svd: 463.685 ms +/- 0.124 ms\n" + ] + } + ], + "source": [ + "# We create the data on the GPU...\n", + "x_gpu = cp.random.random(svd_shape)\n", "\n", "# BUT we call the standard NumPy function - CuPy dispatches it to the GPU!\n", - "print_benchmark(cpx.profiler.benchmark(np.linalg.svd, (x_gpu,), n_repeat=5))" + "print_benchmark(cpx.profiler.benchmark(np.linalg.svd, (x_gpu,), n_repeat=5, n_warmup=1))" ] }, { @@ -292,17 +492,31 @@ "source": [ "### 4. Device Management\n", "\n", - "If you have multiple GPUs, CuPy uses the concept of a \"Current Device\" context. \n", - "\n", - "You can use a `with` statement to ensure specific arrays are created on specific cards (e.g., GPU 0 vs GPU 1)." + "If you have multiple GPUs, you can use a `with` statement to ensure specific arrays are created on specific devices (e.g., GPU 0 vs GPU 1)." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "id": "6aa4f888", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:51:00.351518Z", + "iopub.status.busy": "2026-03-09T19:51:00.351250Z", + "iopub.status.idle": "2026-03-09T19:51:00.356925Z", + "shell.execute_reply": "2026-03-09T19:51:00.355691Z", + "shell.execute_reply.started": "2026-03-09T19:51:00.351496Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Array is on device: \n" + ] + } + ], "source": [ "with cp.cuda.Device(0):\n", " x_on_gpu0 = cp.random.random((100000, 1000))\n", @@ -345,38 +559,51 @@ "2. Change the setup line to `xp = cp` (GPU Mode). Run it again.\n", "3. Observe how the exact same logic runs significantly faster on the GPU with CuPy while retaining the implementation properties of NumPy.\n", "\n", - "Note: We use `cupyx.profiler.benchmark` for timing, which automatically handles GPU synchronization." + "Note: We use `cupyx.profiler.benchmark()` for timing, which automatically handles GPU synchronization." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "id": "0ec79976", - "metadata": {}, - "outputs": [], - "source": [ - "# Re-defined here so this exercise cell is self-contained and can run independently.\n", - "def print_benchmark(result):\n", - " \"\"\"Print benchmark result using wall-clock (cpu_times) for fair comparison.\"\"\"\n", - " avg_ms = result.cpu_times.mean() * 1000\n", - " std_ms = result.cpu_times.std() * 1000\n", - " print(f\" -> {result.name}: {avg_ms:.3f} ms +/- {std_ms:.3f} ms\")\n", - "\n", - "# Step 1.) Setup: Choose Your Device\n", - "# SOLUTION: Changed from 'np' to 'cp' for GPU acceleration\n", + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:51:00.358081Z", + "iopub.status.busy": "2026-03-09T19:51:00.357810Z", + "iopub.status.idle": "2026-03-09T19:51:01.015830Z", + "shell.execute_reply": "2026-03-09T19:51:01.014668Z", + "shell.execute_reply.started": "2026-03-09T19:51:00.358060Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Running on: CUPY\n", + "Generating 50,000,000 random elements (0.37 GB)...\n", + "Sorting data...\n", + "sort: 33.497 ms +/- 0.343 ms\n", + "Verification: PASSED (All rows sum to 1.0)\n" + ] + } + ], + "source": [ + "# Step 1.) Setup: Choose Your Target\n", + "# Changed from 'np' to 'cp' for GPU acceleration\n", "xp = cp # Toggle this to 'np' for CPU mode\n", "\n", "print(f\"Running on: {xp.__name__.upper()}\")\n", "\n", "# Step 2.) Data Generation\n", "N = 50_000_000\n", - "print(f\"Generating {N:,} random elements ({N*8/1e9:.2f} GB)...\")\n", + "print(f\"Generating {N:,} random elements ({N*8/2**30:.2f} GB)...\")\n", "arr = xp.random.rand(N)\n", "\n", "# Step 3.) Heavy Computation (Timed)\n", "print(\"Sorting data...\")\n", "# cpx.profiler.benchmark() handles GPU synchronization automatically\n", - "result = cpx.profiler.benchmark(xp.sort, (arr,), n_repeat=5)\n", + "result = cpx.profiler.benchmark(xp.sort, (arr,), n_repeat=5, n_warmup=1)\n", "print_benchmark(result)\n", "\n", "# Step 4.) Manipulation & Broadcasting\n", @@ -395,7 +622,7 @@ "check_sums = xp.sum(normalized_matrix, axis=1)\n", "xp.testing.assert_allclose(check_sums, 1.0)\n", "\n", - "print(\" -> Verification: PASSED (All rows sum to 1.0)\")" + "print(\"Verification: PASSED (All rows sum to 1.0)\")" ] }, { @@ -403,7 +630,7 @@ "id": "e411249a", "metadata": {}, "source": [ - "**TODO: When working with CuPy arrays, try changing `xp.testing.assert_allclose` to `np.testing.assert_allclose`. What happens and why?**" + "**TODO: When working with CuPy arrays, try changing `xp.testing.assert_allclose()` to `np.testing.assert_allclose()`. What happens and why?**" ] }, { @@ -413,15 +640,15 @@ "source": [ "**SOLUTION:**\n", "\n", - "When you change `xp.testing.assert_allclose` to `np.testing.assert_allclose` while working with CuPy arrays (`xp = cp`), you will get a **`TypeError`**.\n", + "When you change `xp.testing.assert_allclose()` to `np.testing.assert_allclose()` while working with CuPy arrays (`xp = cp`), you will get a **`TypeError`**.\n", "\n", "This happens because:\n", "\n", - "1. `np.testing.assert_allclose` internally tries to convert its inputs to NumPy arrays.\n", - "2. CuPy arrays live on the GPU, and CuPy **explicitly forbids implicit GPU → CPU transfers**.\n", - "3. When NumPy's `assert_allclose` attempts to call `np.asarray()` on the CuPy array, CuPy raises a `TypeError` to prevent a silent (and potentially slow) data copy from GPU to CPU memory.\n", + "1. `np.testing.assert_allclose()` internally tries to convert its inputs to NumPy arrays via `np.asarray()`.\n", + "2. CuPy arrays live on the GPU, and CuPy **refuses to implicitly convert to NumPy arrays** through the `__array__` protocol.\n", + "3. When NumPy's `assert_allclose` attempts to call `np.asarray()` on the CuPy array, CuPy raises a `TypeError` to prevent a silent (and potentially slow) bulk data copy from GPU to CPU memory.\n", "\n", - "This is a **safety feature** of CuPy! It ensures that all device-to-host transfers are **explicit and intentional**. " + "This is a **safety feature** of CuPy! Note that CuPy does still perform implicit GPU → CPU transfers in some cases (e.g. printing, scalar conversion, boolean evaluation), but it draws the line at bulk array conversion via `np.asarray()`. " ] }, { @@ -434,15 +661,41 @@ "\n", "**TODO:** \n", "1) **Generate Data:** Create a NumPy array (`y_cpu`) and a CuPy array (`y_gpu`) representing $\\sin(x)$ from $0$ to $2\\pi$ with `50,000,000` points.\n", - "2) **Benchmark CPU and GPU:** Use `benchmark()` from `cupyx.profiler` to measure both `np.sort` and `cp.sort`." + "2) **Benchmark CPU and GPU:** Use `benchmark()` from `cupyx.profiler` to measure both `np.sort()` and `cp.sort()`." ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "id": "e65b3b18", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:51:01.017080Z", + "iopub.status.busy": "2026-03-09T19:51:01.016833Z", + "iopub.status.idle": "2026-03-09T19:51:08.277717Z", + "shell.execute_reply": "2026-03-09T19:51:08.276354Z", + "shell.execute_reply.started": "2026-03-09T19:51:01.017061Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Generating 50,000,000 points...\n", + "CPU array shape: (50000000,), dtype: float64\n", + "GPU array shape: (50000000,), dtype: float64\n", + "\n", + "Benchmarking NumPy Sort (this may take a few seconds)...\n", + "NumPy (CPU): 974.833 ms +/- 91.496 ms\n", + "\n", + "Benchmarking CuPy Sort...\n", + "CuPy (GPU): 32.867 ms +/- 0.327 ms\n", + "\n", + "GPU Speedup: 29.7x faster\n" + ] + } + ], "source": [ "# Step 1.) Generate Data\n", "N = 50_000_000\n", @@ -458,27 +711,27 @@ "# SOLUTION: Create y_gpu by taking cp.sin(x_gpu)\n", "y_gpu = cp.sin(x_gpu)\n", "\n", - "print(f\" CPU array shape: {y_cpu.shape}, dtype: {y_cpu.dtype}\")\n", - "print(f\" GPU array shape: {y_gpu.shape}, dtype: {y_gpu.dtype}\")\n", + "print(f\"CPU array shape: {y_cpu.shape}, dtype: {y_cpu.dtype}\")\n", + "print(f\"GPU array shape: {y_gpu.shape}, dtype: {y_gpu.dtype}\")\n", "\n", "# Step 2.) Benchmark NumPy (CPU)\n", "print(\"\\nBenchmarking NumPy Sort (this may take a few seconds)...\")\n", "# SOLUTION: Use benchmark with np.sort\n", - "cpu_result = cpx.profiler.benchmark(np.sort, (y_cpu,), n_repeat=5)\n", + "cpu_result = cpx.profiler.benchmark(np.sort, (y_cpu,), n_repeat=5, n_warmup=1)\n", "cpu_avg_ms = cpu_result.cpu_times.mean() * 1000\n", "cpu_std_ms = cpu_result.cpu_times.std() * 1000\n", - "print(f\" NumPy (CPU): {cpu_avg_ms:.3f} ms +/- {cpu_std_ms:.3f} ms\")\n", + "print(f\"NumPy (CPU): {cpu_avg_ms:.3f} ms +/- {cpu_std_ms:.3f} ms\")\n", "\n", "# Step 3.) Benchmark CuPy (GPU)\n", "print(\"\\nBenchmarking CuPy Sort...\")\n", "# SOLUTION: Use benchmark with cp.sort\n", - "gpu_result = cpx.profiler.benchmark(cp.sort, (y_gpu,), n_repeat=5)\n", + "gpu_result = cpx.profiler.benchmark(cp.sort, (y_gpu,), n_repeat=5, n_warmup=1)\n", "gpu_avg_ms = gpu_result.cpu_times.mean() * 1000\n", "gpu_std_ms = gpu_result.cpu_times.std() * 1000\n", - "print(f\" CuPy (GPU): {gpu_avg_ms:.3f} ms +/- {gpu_std_ms:.3f} ms\")\n", + "print(f\"CuPy (GPU): {gpu_avg_ms:.3f} ms +/- {gpu_std_ms:.3f} ms\")\n", "\n", "# Summary\n", - "print(f\"\\n*** GPU Speedup: {cpu_avg_ms / gpu_avg_ms:.1f}x faster ***\")" + "print(f\"\\nGPU Speedup: {cpu_avg_ms / gpu_avg_ms:.1f}x faster\")" ] }, { @@ -486,15 +739,45 @@ "id": "c6acab84", "metadata": {}, "source": [ - "**EXTRA CREDIT: Benchmark with different array sizes and find the size at which CuPy and NumPy take the same amount of time. Try to extract the timing data from `cupyx.profiler.benchmark`'s return value and customize how the output is displayed. You could even make a graph.**" + "**EXTRA CREDIT: Benchmark with different array sizes and find the size at which CuPy and NumPy take the same amount of time. Try to extract the timing data from `cupyx.profiler.benchmark()`'s return value and customize how the output is displayed. You could even make a graph.**" ] }, { "cell_type": "code", "execution_count": null, "id": "5334aee6", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:51:08.278761Z", + "iopub.status.busy": "2026-03-09T19:51:08.278516Z", + "iopub.status.idle": "2026-03-09T19:51:20.948909Z", + "shell.execute_reply": "2026-03-09T19:51:20.947579Z", + "shell.execute_reply.started": "2026-03-09T19:51:08.278741Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Benchmarking different array sizes...\n", + "======================================================================\n", + " Size | NumPy (CPU) | CuPy (GPU) | Winner\n", + "----------------------------------------------------------------------\n", + " 5 | 0.012 ms | 0.164 ms | CPU\n", + " 50 | 0.006 ms | 0.103 ms | CPU\n", + " 500 | 0.005 ms | 0.084 ms | CPU\n", + " 5,000 | 0.031 ms | 0.137 ms | CPU\n", + " 50,000 | 0.373 ms | 0.151 ms | GPU\n", + " 500,000 | 5.265 ms | 0.211 ms | GPU\n", + " 5,000,000 | 76.043 ms | 2.799 ms | GPU\n", + " 50,000,000 | 938.010 ms | 32.682 ms | GPU\n", + "======================================================================\n", + "\n", + "Crossover point: GPU becomes faster between 5,000 and 50,000 elements\n" + ] + } + ], "source": [ "# SOLUTION: Extra Credit - Finding the crossover point between CPU and GPU performance\n", "\n", @@ -553,8 +836,36 @@ "cell_type": "code", "execution_count": null, "id": "8d809375", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:51:20.949865Z", + "iopub.status.busy": "2026-03-09T19:51:20.949630Z", + "iopub.status.idle": "2026-03-09T19:51:21.973158Z", + "shell.execute_reply": "2026-03-09T19:51:21.971781Z", + "shell.execute_reply.started": "2026-03-09T19:51:20.949846Z" + } + }, + "outputs": [ + { + "data": { + "image/png": "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358jR46YvL/Zs2czY8YMo/bExEQSEhJeJ1QhhBBCvOVWr15NpUqVaNeuHZcvX+b8+fMMHTqUfPny0bRpU/744w+GDx8OQMuWLbG1tSU4OJjJkyfj6elJ69atU93vkCFDDJb37t3L7du3KVGiRK67ftF9ef6qa1xJ2gohhBBCmMn06dONkmc+Pj789ddf2XI8RVEICAhg+fLlxMTEUK9ePb799lvKlCkDwOHDh2nSpEmq254+fZqaNWtmS1wifdbW1pQuXRqAd999l99//52vvvqKHj16kJSURExMjMHTtvfu3cPDwyPN/U2aNAl/f3/9su6JZjc3tzzxYIIQQgghzOfEiRP4+voCkJycTLly5QgNDeXkyZP07NmT+/fvA9oa/bt37wbgnXfe4cqVKzx8+JBChQqlut+lS5fq/3z//n3WrVsHwCeffEKhQoVYsGABH3/8MVWqVOHUqVNERUVRpUoVHj58yO7du2nRokV2nnam2NraZqifJG2FEEIIIcyoYsWK7N+/X79saWn65dn06dMJCwsjKCgo1fVz5sxh8eLFrF69mhIlSjB16lRatWrF5cuXsbW1pW7duty9e9dgm6lTp3LgwAFq1Khhclwia2k0GhITE3n33XexsrLiwIEDdOnSBYCrV69y69Yt6tSpk+b2NjY22NjYGLWr1Wp5MlwIIYQQr+XFaxC1Wq1/qrRo0aKo1WratWtHpUqV+PPPP+nYsSN2dnZcuXKFatWq0bdv3wxdiyxdupQnT55QuXJl2rRpA8C4cePYv38/e/fu5fPPP+f333/nwYMHTJgwgVatWmXPyZooo9dbkrQVQgghhDAjS0vLNJ+KjImJYfz48Wzbto3ExERq1KjBwoULqVKlSqaPoygKixYtYsqUKXTo0AGANWvW4O7uztatW+nZsyfW1tYGsTx79oxt27YxatSot75EgblMmjSJNm3aULx4ceLi4li3bh2HDx9m7969ODk5MXjwYPz9/SlYsCCOjo6MGjWKOnXqZHgSMiGEEEKI7KDRaBg+fDjh4eFUrFhRX6/WycmJIUOGMGHCBLZv3w5onzzt3LlzmnVrX5SYmMjXX38NwPjx4/XtKpWK1atXU7lyZT777DMURaFmzZrMmjUrG87uzZCv0oUQQgghzOjatWsULlyYkiVL0qdPH27duqVf161bNyIjI9m9ezdnz56levXq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+/zVV18tcB8lbUVERETEXg4nbQGOHj3KX3/9xZkzZ7AWMOxn8ODBl3N6ERER+UdMDLYatsUxm43yqUralpOlS+Ghh+D8eWP9iiuwxsSQ+U9ZDakeDh065OwQRERERKQKcShpe/ToUe677z6+/fZbgAITtiaTSeUERERESklSkn0JWzD2O326bOMRICsLJk2Cl1/ObevVyxgWXbcuJCQ4LzYpd6Ghoc4OQURERESqEIeStiNHjmTz5s1MmTKF8PDwKl8yQDVtRUTE2fz8SjbSVpPYl7HkZLj7bmPGtxzjxsFLL4Gbm/0ZdhERERERkQI4lLTdsmULkydPZtasWaUdT4WkmrYiIuJs/frBxx/bt6/FYpRXlTLy558wcCDs22esu7pCdDSMGePcuKRchYWFYTab2bt3L25uboSFhWEqpn6xyWTi4MGD5RShiIiIiFRmDiVtGzRoQN26dUs7FhERESnAyZPw2mv27WsygY8P3H57mYZUfX35Jdx1F5w9a6z7+cFHH8F11zk3Lil31113HSaTCbPZnGddRERERKQ0OJS0nThxIq+++ipjxoyhVq1apR2TiIiI/GPvXrjpJjhyJLfNZIICysmTky9atgw8PMonvmrDaoWFC2HixNzSB23awCefQFiYU0MT51i6dGmR6yIiIiIil8OhpO1DDz1EdnY2zZo14/bbb6dBgwa4uLjk2cdkMjF+/PhSCVJERKQ6+vln4yn8nEnFGjUy5r2aPh3OnMmtcZvz0cfHSNgOGODUsKuezEwYOxbefju3LSIC3nkHPD2dFpZULO+88w49e/akcePGBW4/cuQI33//Pffee2/5BiYiIiIilZJDSdvdu3czb948Tp48ySuvvFLgPkraioiIOO7jj2HYMCNfCNCuHaxbByEhcP/9sHo1rFljJHR9fY0atrffrhG2pS4+HgYPhk2bctumTYNZs4xsucg/Ro8ezbvvvlto0nbLli2MHj1aSVsRERERsYtDSdsxY8Zw9uxZlixZQnh4eJWfnCs6Opro6Giys7OdHYqIiFQDr7wCjz+eWwLhhhuMJK2Xl7Hu4QHDhxuLlKHffjOGOh8/bqzXrGmMtr3rLufGJRWStaCaJRdJT0/H1dWhl94iIiIiUg059Mpx586dzJo1iwcffLC046mQIiMjiYyMJCUlpconqEVExHksFpgyBebPz22791544w2oUcN5cVVLq1bByJFw7pyx3qABxMRAx45ODUsqlv/+97/s3LnTtv7jjz+SlZWVb7/k5GRef/11mjdvXo7RiYiIiEhl5lDSNkwTboiIiJSqzEwYPRo++CC37Zln4NlncycYk3JgsRilD2bPzm3r2tWoVxEU5Ly4pEJas2YNs2bNAozSYEuWLGHJkiUF7uvj48M777xTnuGJiIiISCXmUNJ21qxZTJw4kbvvvpuGDRuWdkwiIiLVSnKyUTb122+NdbMZXnsNHnrIqWFVHUePwqlTxe/3998wY0buFwKM0bZLloC7e9nFJ5XWmDFjuPXWW7FarXTu3JnZs2fTv3//PPuYTCZq165NkyZNVB5BREREROzm0CvHH374AR8fH1q0aEHfvn1p2LAhLi4uefYxmUz861//KpUgRUREqqrjx6F/f9i921ivWRNWroQBA5wbV5Vx9Ci0aAEZGSU7zmSCBQtg/HgNdZZCBQcHExwcDMC3335Lq1atqFevnpOjEhEREZGqwKGk7auvvmr7/PPPPy9wHyVtRUREivb770bC9sQJY93fHz7/HMLDnRtXlXLqVMkTtgCLFsG4caUfj1RZ1113nbNDEBEREZEqxKGkrcViKe04REREqpVvv4WICEhJMdabNIEvvoBmzZwaluS49lpnRyAiIiIiItWY2dkBiIiIVDcffAD9+uUmbK+5BjZtUsJWREREREREDHYlbf/++2+HL3A5x1YU0dHRtG7dmmuuucbZoYiISCVmtcL8+TBsGFy4YLTdcosx6lZlMEVERERERCSHXUnbhg0bMnv2bE6ePGn3iU+cOMGMGTNo1KiRw8FVFJGRkezZs4ft27c7OxQREamksrPh8cdh0qTctgcfhJgYqF3baWGJiIiIiIhIBWRXTdvFixcTFRXF7Nmz6datG3379qVDhw6EhYVRt25drFYrZ86c4dChQ/zyyy9s2LCBLVu20KxZM1577bWyvgcREZEK7dw5GD4cPv44t232bJg2DUwm58UlIiIiIiIiFZNdSds777yT22+/nU8//ZSlS5fy3HPPcf78eUyX/E/TarVSo0YNbrzxRlavXs3AgQMxm1U2V0REqq+kJLjtNvj5Z2PdxQXeeANGj3ZuXGKIAmZd0tYC2FtG17NarcycOZM33niD5ORkunXrxuLFi2l2SUHjtWvXMnv2bP773//i4eHBddddR0xMTBlFJY6aPXt2odtMJhMeHh6EhobSp08f/Pz8yjEyEREREans7EraApjNZiIiIoiIiCAzM5MdO3awd+9ekpKSAPDz86Nly5Z07NgRd3f3MgtYRESksjh8GG66CfbtM9br1IHVq41JyKScWK3F7nIlsOGidbtfHBUgKiqKw4cP89ZbbxW4fd68eSxatIhly5YRFhbG9OnT6devH3v27MHDwwOAjz76iAcffJDnn3+e3r17k5WVxe7duy8jKikrUVFRdu3n7u7OzJkzmTJlStkGJCIiIiJVhkP/L3F3d+faa6/l2muvLe14REREqoTffoObb4a4OGM9KAjWroUOHZwbV7WSlQUvvFDsbq5AUCHbkpOTmThxIp988gmZmZl06tSJl19+mXbt2pU4HKvVysKFC5k2bRq33XYbAO+88w6BgYHExMRw9913k5WVxeOPP878+fO5//77bce2bt26xNeTspeYmFjk9r///pu9e/eyePFinnnmGRo3bszdd99domv88MMPzJ8/nx07dnDy5EnWrFlDRESEbfuoUaNYtmxZnmP69evH+vXrS3QdEREREalYVLtARESklH35JfTsmZuwbdECNm9WwrZcpaVBRASsWlXsrvuBEOAK4B7g6EXb7rjjDhISEvjiiy/YsWMHHTp0oE+fPpw+fbrEIR06dIi4uDj69u1ra/P29iY8PJzNmzcD8Ouvv3LixAnMZjNXX301wcHB9O/fXyNtKyg/P78il4YNG3LDDTfw0Ucf0bVrV1555ZUSXyM9PZ127doRHR1d6D433XQTJ0+etC0ffPDB5dyWiIiIiFQAl/MEoIiIiFxi6VJ48EFjkCfAtdfCp5+CylmWo9hYuPVWY7hzMcKBpRh1bE9i1LftAexOT2fXTz+xbds2EhISbKWfFixYQExMDKtXr2bMmDElCivunyx+YGBgnvbAwEDbtr/++gswHrt/6aWXaNy4MS+++CLXX389f/75J76+viW6plQMJpOJ2267jWeffbbEx/bv35/+/fsXuY+7uztBQYWNFxcRERGRykhJWxERkVJgtcKcOTBjRm7boEHw3ntQs6bz4qp2fv8dbrkFjh0z1j09ITMTzp8vcPeLU2FtMZK4ocCHmzeTUbs2aWlp+SaQOnfuHAcPHgTgxx9/zJNQO3/+PFarldWrV2O1WjGZTCxZsoR77rnH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yclMnDiRTz75hMzMTDp16sTLL79Mu3btSnw7VquVhQsXMm3aNG677TYA3nnnHQIDA4mJickzW72np6ft8W2Rqm7+/PmMGzcuX8IWoFWrVowbN4558+bleSPDHr/88gu9evWyrefUoh05ciRLly5l0qRJpKenM2bMGJKTk+nevTvr16+3u1aaiIiIiFRMStqKiJRUZRwt6gCrFRYsgMmTcycc69QJPvnESNyWWHk/VZCVBbGxcOQIHv/7H6SkGInZi5Oz8fElP+/FatQwErCFjZBt0AD8/MqlfsR+IATwALoCc4GcXrvjjjuoWbMmX3zxBd7e3ixZsoQ+ffrw559/4uvrW6LrHDp0iLi4OPr27Wtr8/b2Jjw8nM2bN+dJ2v7f//0fzz77LI0aNWLYsGGMHz8eV1e99JCq6fjx47i5uRW63c3NjePHj5f4vNdffz1Wa+G140wmE7Nnz2b27NklPreIiIiIVFz6n5OISElUxNGiZSAzEx5+GJYuzW278054+22oVctpYeXKzoa4uKJHyMbFgcWCGfBx5BouLlC/ftEjZAMCoAJM2hMOLMWoY3sSo75tD2B3ejq7fvqJbdu2kZCQYJt4aMGCBcTExLB69WrGjBlTomvFxcUBEBgYmKc9MDDQtg3gscceo0OHDvj6+rJp0yamTp3KyZMneemllxy9TZEK7aqrruK1115j2LBh1K9fP8+248eP89prr9GmTRsnRSciIiIilY2StpXZww9D585GAqllS2Np0EAzAomUpWpQgzox0Zhw7KefctuiomDGDCf9evngA2O5ODkbG2uMpHWU2QzBwUWPkA0KMhK3FYG/v5H8L+R7r/9Fn7fFSOKGAh9u3kxG7dqkpaXh5+eX55hz585x8OBBAH788Uf69889y/nz57Faraxevdo2i+qSJUu455577A455xFugLZt21KjRg0eeugh5s6dW+Cs9SKV3csvv0y/fv1o3rw5gwYNomnTpgDs37+fmJgYrFYry5cvd3KUIiIiIlJZKGlbmW3fbiwXq10bmjfPTeLmLM2aGTMHiYgUYfduGDAADh821j08YNkyY5RtPlarMST33DkjmZiRkffzS9f37XMsqAULSn5MYKAtAWtt0IBUHx/qtGyJOTTUaA8OhiIeY65wGjUy+s/Oshw+QPPRozlw5gw+VivBwcF89913+ffz8QGgU6dO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ajVK6fzzryoyYZQKslqtJfq7pr4pXFXvG0f7xVH2XkNJWxGRi1it8MEHMH48JCTktl99tZHAveaa3LaBAyE2FlavhjVr4PRpY9KxQYOMkggaYSsiUrX17t270G0mkwkPDw9CQ0O5+eabufXWW8ssjvDwcJYuXUqLFi04efIks2bNokePHuzevRtPT898+8+dO5dZs2bla09MTCSjJLNtiohIpZOamkqzsGbUrlkbD5eq+R+WjJoZpIelk5qaSsLF/6krhvqmcFW9bxztF0elpqbatZ+StiIi/zhwAB55BL7+OretTh149lkYNw4KKg/o4QHDhxuLiIhULwkJCZhMhY82+fvvv/n6669ZsmQJ/fr145NPPsHNza3U4+jfv7/t87Zt2xIeHk5oaCgffvgh999/f779p06dyoQJE2zrKSkpNGzYkICAALy8vEo9PhERqTjS0tLYf2g/Pu18qO1V29nhlIn0c+kkH0rG09OTevXq2X2c+qZwVb1vHO0XR3nYOcJLSVsRqfYyM2HePHjuOePzHIMGwaJF0KCB82ITEZGKa/fu3cXuc+7cOZYsWcKECROYN28ezzzzTJnH5ePjQ/PmzTlw4ECB293d3XF3d8/XbjabMZvNZR2eiIg4Uc4j4Dn/qiIrVtvj/CX5u6a+KVxV7xtH+8VR9l5Dr8pEpFr7/nto3x5mzMhN2DZqBJ9+Ch9/rIStiIhcnpo1a/LEE09w99138/7775fLNdPS0jh48CDBwcHlcj0RERERKX1K2opItXTqFIweDddfD3v3Gm0uLjBxIvzvf5pATERESle3bt04dOhQmZx74sSJfP/99xw+fJhNmzYxaNAgXFxcGDp0aJlcT0RERETKnsoj2CE6Opro6Giys7OdHYqIXCarFZYuhaeegqSk3PbwcGOisXbtnBaaiIhUYX///TeuBRVHLwXHjx9n6NChJCUlERAQQPfu3dmyZQsBAQFlcj0RERERKXsaaWuHyMhI9uzZw/bt20v93FFRUZhMpjxLy5YtS/06IgJ79hgja++7Lzdh6+0NixfDpk1K2IqISNmwWq18+umntGnTpkzOv2LFCmJjY8nMzOT48eOsWLGCJk2alMm1RERERKR8aKRtBXDllVeyYcMG23pZjcIQqa7OnYM5c2D+fLhwIbd96FB46SUICnJebCIiUnmdPn26yO3nzp1j3759LF68mE2bNrF8+fJyikxEREREKjtlBysAV1dXgpQ1EikTX34JjzwCf/2V29akCbz2Gtx4o/PiEhGRys/f3x+TyVTsfm5ubjz77LOqMSsiIiIidlPStgLYv38/ISEheHh40LVrV+bOnUujRo2cHZZIpRYXB+PHw4oVuW1ubjB5Mjz9NNSs6bzYRESkapgxY0aRSVsPDw9CQ0Pp06eP6suKiIiISIkoaetk4eHhLF26lBYtWnDy5ElmzZpFjx492L17N56ens4OT6TSsViMCcWmToWzZ3Pbe/aE11+HVq2cF5uIiFQtUVFRzg5BRERERKooJW2drH///rbP27ZtS3h4OKGhoXz44Yfcf//9ToxMpPLZtQseegi2bs1t8/ODBQtg5Eiw4wlWERGREtu6dSuHDh3Cz8+PHj164OHh4eyQRERERKSSU9K2gvHx8aF58+YcOHDA2aGIVBppaRAVBQsXQnZ2bvvo0TBvHvj7OysyERGpylJTU+nfvz+bN2+2tQUFBbF27Vrat2/vvMBEREREpNJT0raCSUtL4+DBg7RsOYIhQyApyRgpGBEBd9wBGrghktenn8K4cXDsWG5bq1aweDFcd53z4hIRkapv3rx5bNq0icGDB9O7d28OHDjA4sWLGTlyJLt27XJ2eCIiIiJSiZmdHUB1N3HiRL7//nsOHz7Mpk2b6N59EMnJLixfPpSYGPj+e4iJgXvvhZAQ+OwzZ0csUjEcOwaDBsFtt+UmbD08YM4c2LlTCVsRESl7H3/8MYMHD2b16tU88sgjvPTSS/zrX/9i9+7dHDp0yNnhiYiIg8aMGUObNm3w9vbG09OTjh078sEHHxR5zOHDhzGZTPmWvn37llPUIlLVaKStkx0/fpyhQ4eSlJSEp2cASUndgS1AABaLsU/Ox+RkI0EVEwMDBzonXhFny8qCV16B6dMhPT23/cYb4bXXoEkT58UmIiLVy+HDh3n88cfztPXr1w+r1crx48cJCwtzUmQiInI53njjDTp06MAdd9zBf//7X7Zv386wYcOoW7cuN910U5HH1q9fn9tvv9223qJFi7IOV0SqKCVtnWzFihUAZGQYI2lNJrBaC97XajW2jxoFsbEqlSDVz7ZtxkRjO3fmtgUGGrVs77pLE42JiEj5OnfuHHXq1MnTlrN+4cIFZ4QkIiKlYMuWLYSHhwOQlZVF8+bNOXToEF988UWxSdumTZuycOHCfO2HDh2iffv2pKWl8fPPP9OlSxfuvvtuVq5cyfDhw3n33XfL4lZEpBKrduURjh07xvXXX0/r1q1p27Ytq1atcnZIAKxaBWfOFJ6wzWG1GvutXl0+cYlUBGfPGnVru3TJTdiaTDB2LOzdC3ffrYStiIg4R3p6OqdPn86zgDFJ2aXtOdtERKRiy0nY5sjMzASMUbTF2bp1K7Vq1SIwMJBBgwaxb98+AMLCwli8eDEWi4XRo0ezbNkyVq5cSdOmTXnttddK/yZEpNKrdiNtXV1dWbhwIe3btycuLo6OHTty8803U7t2bafGFRMDZnNuKYSimEywaBFcey2EhSlZJVWX1Wq8ofHEE3DyZG5727awZImRxBUREXGmhx9+mIcffjhf++DBgwvcPzs7u6xDEhGRUmKxWHj44YeJjY3lyiuvZOzYsUXu37hxY6699lpq167N+vXriYmJYceOHezevRsvLy+GDRvGV199xbJlyxg9ejRubm68//77eHp6ltMdiUhlUu2StsHBwQQHBwMQFBSEv78/p0+fdnrSNinJvoQtGIms7duN2p3+/tC5s7GEh8M114CfX9nGKlIe/voLIiNh/frctlq1YNYsePxxcHNzXmwiIiIAM2fOdHYIIiJSRtLT0xk2bBiffvopV199NevXry8yuRoaGppnEsrExETq16/PsWPH2LRpk62swpQpU1i2bBlWq5U+ffpwzTXXlPm9iEjlVOGStj/88APz589nx44dnDx5kjVr1hAREZFnn+joaObPn09cXBzt2rXjlVdeoXPnziW+1o4dO8jOzqZhw4alFL3j/PzsH2l7sVOnYN06Y8nRtGneRG779qp/K5XH+fPw4oswe7ZR6znHwIHGBGSNGjkvNhERkYspaSsiUjXFxsYyYMAAfv31VwYMGMD777+fr4b53r17AWjUqBG1atXiyJEjhISEUKNGjXzny/jnPzYWi8U2WtfDw4P169ezZs0aBg0aVMZ3JCKVUYWraZuenk67du2Ijo4ucPvKlSuZMGECM2fO5Ndff6Vdu3b069ePhIQE2z7t27fnqquuyrfExsba9jl9+jT33nsv//73v8v8nuwREVGyhO2gQdC/f8Gjag8cgPffNx4p79oVvLyMEbiRkfDOO0YN0JImh0XKw08/QYcO8PTTuQnbBg1gzRr45BMlbEVEREREpOyFh4fz66+/4uXlRePGjZk2bRpPPPEE77//vm2fVq1a0apVK7Zt2wbA0qVLadiwIXfeeSdjxoyhY8eOXLhwgZCQEPr06QPA3Llz+e677+jevTvr1q3DbDbzwAMPcPz4cafcp4hUbBVupG3//v3p379/odtfeuklHnzwQUaPHg3A66+/ztq1a3nrrbeYMmUKADsvnlq+AJmZmURERDBlyhSuvfbaIvfLKTgOkJKSAhjvjllKOes5ZAg89piJs2fBai28SK3JZMXHB5Yvt+LhYZRK+Osv2LYNtm0zsX07/PorZGbmnuPCBfjlF2PJqW/u7W2lUye46qraXHedlS5dLAQGluotXZZZs2Yxe/bsPG0tWrRgz549gPFO5cSJE1m5ciWZmZnceOONREdHE1hON2GxWLBaraX+fVDVFdZvSUkwZYqJt97K/b41m6089hhERVnx9NQbDfqec5z6znHqO8ep7xxzab9Vhv47duwYZrPZNjlNRkZGgRPKNGjQgDvvvLO8wxMREQfkJFFTUlJ45ZVXbO0jR45k2LBhBR7Tp08ftm/fzo8//sjp06cJDAxk5MiRREVF4enpyZYtW4iKiqJ27dosW7aMK664gokTJzJv3jyGDx/ON998g9lc4cbViYgTVbikbVHOnz/Pjh07mDp1qq3NbDbTt29fNm/ebNc5rFYro0aNonfv3owYMaLIfefOncusWbPytScmJtoebyhN//qXO6NG+WAyWQtM3JpMVgAWLkwmJSWTf3LIeHpCnz7GAsbj5X/84cpvv7nx2281+O03N/bvz/ulPnvWxMaNJjZu9ORf/zLa6tfPpkOH81x99QWuvvoCbdtmUauWtdTv0x7p6em0aNGCDz/80Nbm4uJiG1E9efJkNmzYwJIlS/D09OSZZ57htttu49NPPy2X+CwWC2fPnsVqteoPawlc2m/GRGMezJrlxenTud/z7dufZ968FNq0yeLcOTh3zolBVxD6nnOc+s5x6jvHqe8cc2m/paamOjukIv3+++9cffXVLFy4kHHjxgHGa5iJEydiMpmwWnNfR7m4uNCqVSvatGnjrHBFRMROF//+tnefHj160KNHj0L379KlCxcuXMjT9sILL/DCCy84FqSIVHmVKml76tQpsrOz842mDAwMtNWTKc7PP//MypUradu2LTExMQC8++67Bb6Anjp1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c2m/VbXXXhVVZernRx99lFdffZXGjRtz3XXXsXr1atLT01mzZg0RERG2/0R+++23tkf2R40aRXZ2NsuXLwegX79+BAUF8cADD9C9e3eee+45pk2bZhsV2LdvX3x8fNi1a5etREBloL4pnPqmejp48CB33HcHPoN8qB1Y29nhlLr0+HSS1ySz6q1VNGnSxO7jqnq/gPqmKOqbgjnaL46y97WXRtqKCGAkZocPNxYRERGRimjBggV4eHjw3nvv8f7779OkSRMmTZpEREREoccsW7Ysz/qXX34JwPXXX1/gqMCJEycyb948hg8fzjfffFNp3nhR3xROfSMiIpWRkrYiIiIiIlIpuLu7M3/+/HwTM+Uo6CHC4h4szCkbkOOFF17ghRdecDxIJ1HfFE59IyIilZHe/hMRERERERERERGpQJS0FREREREREREREalAlLQVEREREakCoqOjady4MR4eHoSHh7Nt2zZnhyQiIiIiDlJNWztER0cTHR1Ndna2s0MREREREcln5cqVTJgwgddff53w8HAWLlxIv3792LdvH/Xq1XN2eCJShhITE0lJSXF2GGXKy8uLgIAAZ4chIlKulLS1Q2RkJJGRkaSkpODt7e3scERERERE8njppZd48MEHGT16NACvv/46a9eu5a233mLKlClOjk5EykpiYiJjhw0jMynJ2aGUKXc/Pxa//74StyJSrShpKyIiIiJSiZ0/f54dO3YwdepUW5vZbKZv375s3rzZiZEVrqqPDHR0VGBV7xdQ3xTFkb5JSUkhMymJJ93daVizZhlF5lzHzp3jxaQkUlJSlLQVkWpFSdsSsFqtAFXmxYLFYiE1NRUPDw/MZpU3Lgn1nWPUb45T3zlOfec49Z3j1HeOubTfcl5z5bwGk4KdOnWK7OxsAgMD87QHBgayd+/efPtnZmaSmZlpWz979iwAycnJWCyWsg0WSEpK4skHHyTz9Okyv5azuPv68uIbb+Dn52f3MdWhX0B9UxRH+iYlJYUsi4W07GxSqmg5v7TsbLIsFlJSUkhOTrb7uJSUFCzZFtJi08jOqHp9c+70OSzZ6peCqG8Kp74pmKP94ih7X+OarHoVbLfjx4/TsGFDZ4chIiIiUq0cO3aMBg0aODuMCis2Npb69euzadMmunbtamufNGkS33//PVu3bs2zf1RUFLNmzSrvMEVERETkIsW9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UrNmTVOHW6juXYytadOmxgVtchdj++677+jSpQslS5bE0tLS2L+1OChbtiw3btzg7NmzuLm5oZRi4MCBZGdn8/rrr+Pt7U379u0ZOXJknuPMuYii0+mwtLTEycmJI0eOcOfOHUqVKpVnMbYRI0awd+9eWrVqhaWlpbHIr5Qy27xAzvXlFrH/+OMP6tati16vx9LSkpUrVxIYGEhISAhr166levXq+Y4159zkfk3k9t3/+++/qVmzpvGPQF9++SU9e/Zk7dq1DB482Jg3MP/c6PV6bt++bZzND//OnB09ejSXLl1i+vTp1K9fn0qVKlG6dGmcnJyM+5lzbp6YEmYnLi5OAfn+BQcHG/f5/PPP1QsvvKBKlCihGjVqpH766SfTBVyESO6enOTuyUnunozk7clJ7p6c5O7JSe6EeHpOnjypWrVqpWrUqKGSkpKM43v27FEvvviiSkxMNGF0hWv27NnqzJkzxo83bdqkNBqN+vLLL/Pst2bNGuXv76/0en1hh1gk/Pbbb8rNzU3NmzdPKaWUwWBQSiml0+nU0KFD1aRJk4wfm7tbt24ppZTKyMhQSil17do1ValSJdW/f/88+126dEnVr19fbd++vdBjLCrGjBmjqlSpov7++2+l1L/3TUREhGrevLm6ceOGKcMrVGlpaerKlSvq4sWLxrFXXnlFVa9eXV25ciXPvp06dVKLFi0q7BBN5v7vG99++62ytrZWUVFRecYPHDig6tatq06fPl2Y4T3XNEr9358fhRBCCCGEEEI8F/766y/+/PNP2rVrR2ZmJra2tly5coWOHTuyYsUK6tSpY+oQn7ncxdhye/vmWrp0KaNGjWLu3LnGxdg+//xzoqKi2LRpk9n39gU4f/48qamplCxZEk9PT8qUKcOqVasIDg5mxYoVDBkyxLjvBx98wPnz5419Ws3ZgxZjO3HiBC1btiQgIMC4GBtA/fr1ef/9982+ty9AcnIyERERlClTBldXV/r37w9Ax44dSU5OJjY21vgY+19//cWAAQOIiooqNguyDR8+HKUUZcuWxc/Pj9DQUABat27N33//zfr163F3d8fBwYFGjRoxYsQIQkJCTBz5s3fixAnmz5+PtbU1rq6uTJgwATs7Oz788EOmT5+OVquld+/eWFhYoNfradiwIStWrCgWC7I9DdIeQQghhBBCCCGeM56enrzwwgsA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YAEDBw7khRde4IsvvgDMOzcA8fHxxsf9hw4dykcffcSKFSt45513+Pzzz6lRowaJiYkMGTIEf39/goODjYtG/9f7geeZTqfj008/Zdy4caxYsYLIyEjWrVvHTz/9xCuvvIJGo2HLli0EBwdz8OBBFixYQJ06dYy9j839vjE1aY8ghCgS9Ho9ERERtG7dOs8qt40bN+bTTz9l165dtG/fPs8xVatWfeD5CtqWlpbG9OnTiYiI4NKlS3m23dtQvmbNmvj5+aHVao2FRK1WS5MmTahevfp/XkuNGjVYtWoVer2eEydO8P333xMaGkpISAhVq1albdu2Dz3+xo0b9OzZk8qVK7Ny5Urjo1uXL18mPT2dpUuXPrDX1P3XVZD7f7CmpKSglGLq1KlMnTr1geetXLlyvvHk5GQAgoODH/h6169fz1Movr8PVNmyZbG1tTW2J7h3/N5+w8nJyfz+++8PLNTff+0F9ZtycHDI82jcgwQFBfHVV18xYsQIJk2aREBAAD179qR37975VhrOzae5PmInhBBCiKIrPT2dzp074+7uTlRUFJBTYLr/9xVzcf9ibGPGjKFChQrcvHkTS0tLtFot69evp2nTpvlamplzXiBngkru4lEAFStW5NVXXzW2BXNycuK1114jMjKS27dvc+vWLSpWrAiYf24g573FjRs3OHv2LG5ubiilGDhwINnZ2bz++ut4e3vTvn17Y2/bXOaeGysrK6ysrLh165bx/vH19SU+Pp7GjRszePBgVq1axfTp09HpdOj1emxsbADzz01RIEVbIUSRsHv3bs6fP09ERAQRERH5tmu12nxF23tnzt6voG19+/blwIEDvPPOO/j6+lK6dGkMBgMdO3bM99fTIUOGMH78eM6ePUtWVhY//fQTCxcufKxrsrS0pG7dutStW5emTZvSunVrtFrtfxZthw4dyj///MOhQ4fy9EjNjXHQoEEPLJLWq1fvged1dHREo9HkK1rmnnfChAl06NChwGMfVKzOPfbjjz/G19e3wH3u72+U29z/v8Ygb4HZYDBQt25dPvvsswL3rVKlymOf80Hs7OzYt28fcXFxbN68mW3btrF27VratGnDjh078pw7N5/3F52FEEIIIZ61ChUqEBUVZVwE19yLKLmLsU2cOJHw8HA6d+7MN998g16vJzk5mTFjxhhnBeb2aM1lznmBnCcJ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deXKlTz79uvXT5UtW7bAa8yVnZ2tNBqNevvtt/NtCwgIUHXr1lWZmZnGMYPBoF5++WXl5eVlHIuLi1OAiouLM+7j5eWlOnTooAwGg3G/27dvq6pVq6p27doZx3I/NyEhIcYxnU6n3NzclEajUXPmzDGOX7t2TdnZ2ang4GDj2KpVq5SFhYX64Ycf8sS+ZMkSBagff/zROAaoEiVKqJSUFONYYmKiAtTnn39uHPv4448VoE6dOpXnnGFhYQpQly9fzper+61evVoB6uDBg/+5rxDi2ZD2CEIIIYQQQgghxDOi1WpxdXWldevWQM4j7kFBQURERKDX6/Pt36tXL5ydnQs816hRo/KN2dnZGf+fmZnJlStXaNKkCQC//PKLcduQIUPIysriu+++M46tXbsWnU7HoEGDHnoNPj4+JCQkMGjQIFJTU5k/fz6BgYG4urry5ZdfPvTYe504cYJXX32V7t2789577wGglGL9+vV07doVpRRXrlwx/uvQoQPXr1/Pcx33S0tLQymFg4NDvvHdu3fTt29fbt68aTzn1atX6dChA8nJyZw7d67AcyYkJJCcnMyAAQO4evWq8dhbt24REBDAvn37MBgMeY4ZMWKE8f+WlpY0bNgQpRTDhw83jpcrVw5vb2/++usv49i6deuoVasWNWvWzHPtuTOa4+Li8rxO27Zt87SBqFevHvb29nnO+SDlypUDYMOGDfniv19uPq9cufKf5xVCPBtStBVCCCGEEEIIIZ4BvV5PREQErVu35tSpU6SkpJCSkkLjxo25ePEiu3btyndM1apVH3i+gralpaUxfvx4XF1dsbOzw9nZ2bjf9evXjfvVrFkTPz+/PP10tVotTZo0oXr16v95LTVq1GDVqlVcuXKFo0ePMmvWLKysrAgJCWHnzp3/efyNGzfo2bMnlStXZuXKlWg0GgAuX75Meno6S5cuxdnZOc+/YcOGAfkXOyuI+r92EblSUlJQSjF16tR8581dBO5B501OTgYgODg437FfffUVWVlZeXIL8MILL+T5uGzZstja2hrbE9w7fu3atTyvdfz48XyvU6NGjQJjvP91IKfAeu85HyQoKIhmzZoxYsQIXF1d6devH5GRkQUWcHPzmft5EkIUPulpK4QQQgghhBBCPAO7d+/m/PnzREREEBERkW+7Vqulffv2ecbunTl7v4K29e3blwMH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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n", + "Analysis Complete\n", + "Maximum GPU speedup: 28.7x at 50,000,000 elements\n" + ] + } + ], "source": [ "# SOLUTION: Extra Credit (continued) - Visualization\n", "\n", @@ -596,7 +907,7 @@ "plt.tight_layout()\n", "plt.show()\n", "\n", - "print(\"\\n*** Analysis Complete ***\")\n", + "print(\"\\nAnalysis Complete\")\n", "print(f\"Maximum GPU speedup: {max(speedups):.1f}x at {sizes[speedups.index(max(speedups))]:,} elements\")" ] } diff --git a/tutorials/accelerated-python/notebooks/fundamentals/solutions/04__numpy_to_cupy__svd_reconstruction__SOLUTION.ipynb b/tutorials/accelerated-python/notebooks/fundamentals/solutions/04__numpy_to_cupy__svd_reconstruction__SOLUTION.ipynb index ea1ed9d1..d3e4b1b5 100644 --- a/tutorials/accelerated-python/notebooks/fundamentals/solutions/04__numpy_to_cupy__svd_reconstruction__SOLUTION.ipynb +++ b/tutorials/accelerated-python/notebooks/fundamentals/solutions/04__numpy_to_cupy__svd_reconstruction__SOLUTION.ipynb @@ -81,7 +81,7 @@ "id": "Q1cCVj00xBJf" }, "source": [ - "Next we can read the image in grayscale mode:" + "Then we read the image in grayscale mode:" ] }, { @@ -113,7 +113,7 @@ } ], "source": [ - "image = cv2.imread('loonie.jpg', cv2.IMREAD_GRAYSCALE)\n", + "image = cv2.imread(\"loonie.jpg\", cv2.IMREAD_GRAYSCALE)\n", "\n", "image = xp.asarray(image) # Copy the image to the GPU.\n", "\n", @@ -222,7 +222,7 @@ "id": "wg5YYu8UxBJy" }, "source": [ - "The signular values are returned in descending order, which we can see if we look at the first 10 elements of `S`:" + "The singular values are returned in descending order, which we can see if we look at the first 10 elements of `S`:" ] }, { @@ -495,7 +495,7 @@ "id": "6vYLvNMwxBZU" }, "source": [ - "Now that you have gotten SVD to work on CuPy, let's benchmark the speed. To make things clearer, let's reimport NumPy and CuPy with their usual abbreviations:" + "Now that you have gotten SVD to work on CuPy, let's benchmark it! To make things clearer, let's reimport NumPy and CuPy with their usual abbreviations:" ] }, { @@ -523,7 +523,7 @@ "\n", "Imagine you're measuring how long it takes to ship a package to someone, but you only time how long it takes for you to drop it off at the post office, not how long it takes for them to receive it and send you a thank you.\n", "\n", - "Common Pythonic benchmarking tools like `%timeit` are not GPU aware, so it's easy to measure incorrectly with them. We can only use them when we know the code we're benchmarking will perform the proper synchronization. It's better to use something like [`cupyx.profiler.benchmark`](https://docs.cupy.dev/en/stable/reference/generated/cupyx.profiler.benchmark.html#cupyx.profiler.benchmark).\n", + "Common Pythonic benchmarking tools like `%timeit` are not GPU aware, so it's easy to measure incorrectly with them. We can only use them when we know the code we're benchmarking will perform the proper synchronization. It's better to use something like [`cupyx.profiler.benchmark()`](https://docs.cupy.dev/en/stable/reference/generated/cupyx.profiler.benchmark.html#cupyx.profiler.benchmark).\n", "\n", "First, we need a NumPy (CPU) and CuPy (GPU) copy of our image:" ] @@ -593,7 +593,7 @@ "id": "u8-b3zWLxBZV" }, "source": [ - "Depending on your hardware, the CPU and GPU might be close to the same speed, or the GPU might even be slower! This is because the image is not big enough to fully utilize the GPU. We can simulate a larger image by tiling the image using `np.tile`. This duplicates the image both along axis 0 and axis 1:" + "Depending on your hardware, the CPU and GPU might be close to the same speed, or the GPU might even be slower! This is because the image is not big enough to fully utilize the GPU. We can simulate a larger image by tiling the image using `np.tile()`. This duplicates the image both along axis 0 and axis 1:" ] }, { 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 470e5b8d..d0eb4c26 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 @@ -21,10 +21,10 @@ "\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", + "1. **Host Memory:** Accessible by the CPU.\n", + "2. **Device Memory:** 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", + "To ensure data is accessible from a particular processor, we need to explicity transfer it:\n", "\n", "* **Host $\\to$ Device:** Move data to the GPU to compute.\n", " * Syntax: `x_device = cp.asarray(x_host)`\n", @@ -86,9 +86,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "0cc26840", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:47:53.898364Z", + "iopub.status.busy": "2026-03-09T19:47:53.898143Z", + "iopub.status.idle": "2026-03-09T19:47:54.953845Z", + "shell.execute_reply": "2026-03-09T19:47:54.952344Z", + "shell.execute_reply.started": "2026-03-09T19:47:53.898341Z" + } + }, "outputs": [], "source": [ "import numpy as np\n", @@ -101,7 +109,7 @@ "@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", + " dominance: float = 0.1 # How much larger the top eigenvalue is (controls convergence, higher == faster)\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", @@ -120,10 +128,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "b2503345", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:47:54.955341Z", + "iopub.status.busy": "2026-03-09T19:47:54.954881Z", + "iopub.status.idle": "2026-03-09T19:48:00.598782Z", + "shell.execute_reply": "2026-03-09T19:48:00.597564Z", + "shell.execute_reply.started": "2026-03-09T19:47:54.955308Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Generating Host Data...\n", + "Host Matrix Shape: (4096, 4096)\n", + "Data Type: float64\n" + ] + } + ], "source": [ "def generate_host(cfg=PowerIterationConfig()):\n", " \"\"\"Generates a random diagonalizable matrix on the CPU.\"\"\"\n", @@ -158,41 +184,80 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "3c8229aa", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:48:00.599800Z", + "iopub.status.busy": "2026-03-09T19:48:00.599526Z", + "iopub.status.idle": "2026-03-09T19:48:01.945015Z", + "shell.execute_reply": "2026-03-09T19:48:01.943567Z", + "shell.execute_reply.started": "2026-03-09T19:48:00.599776Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Step 0: residual = 7.594e+00\n", + "Step 10: residual = 1.699e-02\n", + "Step 20: residual = 2.148e-02\n", + "Step 30: residual = 1.295e-02\n", + "Step 40: residual = 4.494e-03\n", + "Step 50: residual = 1.366e-03\n", + "Step 60: residual = 4.129e-04\n", + "Step 70: residual = 1.274e-04\n", + "Step 80: residual = 4.013e-05\n", + "Step 90: residual = 1.286e-05\n", + "Step 100: residual = 4.181e-06\n", + "Step 110: residual = 1.374e-06\n", + "Step 120: residual = 4.550e-07\n", + "Step 130: residual = 1.517e-07\n", + "Step 140: residual = 5.085e-08\n", + "Step 150: residual = 1.712e-08\n", + "Step 160: residual = 5.785e-09\n", + "Step 170: residual = 1.959e-09\n", + "Step 180: residual = 6.661e-10\n", + "Step 190: residual = 2.271e-10\n", + "Step 200: residual = 7.733e-11\n", + "\n", + "Dominant Eigenvalue: 0.9999999999599408\n" + ] + } + ], "source": [ "def estimate_host(A, cfg=PowerIterationConfig()) -> np.ndarray:\n", " \"\"\"\n", " Performs power iteration using purely NumPy (CPU).\n", " \"\"\"\n", - " # Initialize vector of ones on Host\n", + " # Initialize solution vector.\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", + " # Matrix-Vector multiplication.\n", " y = A @ x\n", "\n", - " # Rayleigh quotient\n", + " # Rayleigh quotient.\n", " lam = (x @ y) / (x @ x)\n", "\n", - " # Calculate residual (error)\n", + " # Calculate residual (error).\n", " res = np.linalg.norm(y - lam * x)\n", "\n", - " # Normalize vector for next step\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", + " # Save a checkpoint.\n", + " np.savetxt(f\"/tmp/host_{i}.txt\", x)\n", "\n", - " # Convergence check\n", + " # Convergence check.\n", " if res < cfg.residual_threshold:\n", " break\n", "\n", - " # Run intermediate steps without checking residual to save compute\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", @@ -200,10 +265,12 @@ " return (x.T @ (A @ x)) / (x.T @ x)\n", "\n", "lam_est_host = estimate_host(A_host)\n", + "\n", "assert isinstance(lam_est_host, (np.ndarray, np.generic)), \"Must return a NumPy array or NumPy scalar\"\n", + "np.testing.assert_allclose(lam_est_host, 1, atol=1e-4)\n", "\n", "print()\n", - "print(lam_est_host)" + "print(\"Dominant Eigenvalue:\", lam_est_host)" ] }, { @@ -229,10 +296,48 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "f36586ee", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:48:01.946197Z", + "iopub.status.busy": "2026-03-09T19:48:01.945815Z", + "iopub.status.idle": "2026-03-09T19:48:04.244716Z", + "shell.execute_reply": "2026-03-09T19:48:04.243488Z", + "shell.execute_reply.started": "2026-03-09T19:48:01.946170Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Step 0: residual = 7.594e+00\n", + "Step 10: residual = 1.699e-02\n", + "Step 20: residual = 2.148e-02\n", + "Step 30: residual = 1.295e-02\n", + "Step 40: residual = 4.494e-03\n", + "Step 50: residual = 1.366e-03\n", + "Step 60: residual = 4.129e-04\n", + "Step 70: residual = 1.274e-04\n", + "Step 80: residual = 4.013e-05\n", + "Step 90: residual = 1.286e-05\n", + "Step 100: residual = 4.181e-06\n", + "Step 110: residual = 1.374e-06\n", + "Step 120: residual = 4.550e-07\n", + "Step 130: residual = 1.517e-07\n", + "Step 140: residual = 5.085e-08\n", + "Step 150: residual = 1.712e-08\n", + "Step 160: residual = 5.785e-09\n", + "Step 170: residual = 1.960e-09\n", + "Step 180: residual = 6.659e-10\n", + "Step 190: residual = 2.267e-10\n", + "Step 200: residual = 7.754e-11\n", + "\n", + "Dominant Eigenvalue: 0.9999999999591407\n" + ] + } + ], "source": [ "def estimate_device(A, cfg=PowerIterationConfig()) -> np.ndarray:\n", " \"\"\"\n", @@ -242,7 +347,8 @@ " 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", + " 4. Copy x from device to host and save a checkpoint\n", + " 5. Return the result as a NumPy array\n", " \"\"\"\n", " # Transfer the input matrix from host to device.\n", " # If A is on the host, cp.asarray copies it to the GPU.\n", @@ -269,7 +375,7 @@ " print(f\"Step {i}: residual = {res:.3e}\")\n", "\n", " # Transfer x from device to host and save a checkpoint.\n", - " np.savetxt(f\"device_{i}.txt\", cp.asnumpy(x))\n", + " np.savetxt(f\"/tmp/device_{i}.txt\", cp.asnumpy(x))\n", "\n", " # Convergence check.\n", " if res < cfg.residual_threshold:\n", @@ -284,10 +390,12 @@ " return cp.asnumpy((x.T @ (A_gpu @ x)) / (x.T @ x))\n", "\n", "lam_est_device = estimate_device(A_host)\n", + "\n", "assert isinstance(lam_est_device, (np.ndarray, np.generic)), \"Must return a NumPy array or NumPy scalar\"\n", + "np.testing.assert_allclose(lam_est_device, 1, atol=1e-4)\n", "\n", "print()\n", - "print(lam_est_device)" + "print(\"Dominant Eigenvalue:\", lam_est_device)" ] }, { @@ -297,7 +405,7 @@ "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", + "In the previous step, we generated data on the CPU and copied it to the GPU. For large datasets, the transfer time from host to device can be a bottleneck. \n", "\n", "It is almost always faster to **generate** the data directly on the GPU if possible.\n", "\n", @@ -316,10 +424,49 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "7a363755", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:48:04.246171Z", + "iopub.status.busy": "2026-03-09T19:48:04.245926Z", + "iopub.status.idle": "2026-03-09T19:48:06.775967Z", + "shell.execute_reply": "2026-03-09T19:48:06.774876Z", + "shell.execute_reply.started": "2026-03-09T19:48:04.246149Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Step 0: residual = 2.346e+01\n", + "Step 10: residual = 2.859e-02\n", + "Step 20: residual = 2.984e-02\n", + "Step 30: residual = 1.433e-02\n", + "Step 40: residual = 5.035e-03\n", + "Step 50: residual = 1.643e-03\n", + "Step 60: residual = 5.309e-04\n", + "Step 70: residual = 1.726e-04\n", + "Step 80: residual = 5.663e-05\n", + "Step 90: residual = 1.875e-05\n", + "Step 100: residual = 6.253e-06\n", + "Step 110: residual = 2.098e-06\n", + "Step 120: residual = 7.077e-07\n", + "Step 130: residual = 2.396e-07\n", + "Step 140: residual = 8.140e-08\n", + "Step 150: residual = 2.772e-08\n", + "Step 160: residual = 9.461e-09\n", + "Step 170: residual = 3.234e-09\n", + "Step 180: residual = 1.107e-09\n", + "Step 190: residual = 3.792e-10\n", + "Step 200: residual = 1.299e-10\n", + "Step 210: residual = 4.469e-11\n", + "\n", + "Dominant Eigenvalue: 0.9999999999549047\n" + ] + } + ], "source": [ "def generate_device(cfg=PowerIterationConfig()):\n", " \"\"\"\n", @@ -362,8 +509,10 @@ "\n", "lam_est_device_gen = estimate_device(A_device)\n", "\n", + "np.testing.assert_allclose(lam_est_device_gen, 1, atol=1e-4)\n", + "\n", "print()\n", - "print(lam_est_device_gen)" + "print(\"Dominant Eigenvalue:\", lam_est_device_gen)" ] }, { @@ -378,12 +527,44 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "611859bb", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:48:06.777042Z", + "iopub.status.busy": "2026-03-09T19:48:06.776701Z", + "iopub.status.idle": "2026-03-09T19:48:06.839719Z", + "shell.execute_reply": "2026-03-09T19:48:06.838593Z", + "shell.execute_reply.started": "2026-03-09T19:48:06.777021Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "A_host:\n", + "[[-0.4937 -0.519 -0.2935 ... -0.1628 0.8361 0.531 ]\n", + " [-1.0859 0.0087 -0.0661 ... -0.1706 1.0955 0.7075]\n", + " [-0.4291 -0.3628 0.3393 ... 0.1813 0.2238 -0.2124]\n", + " ...\n", + " [-1.1089 -0.4564 -0.3024 ... 0.2075 1.2864 0.9066]\n", + " [-0.8714 -0.5109 -0.1201 ... -0.072 1.3048 0.4372]\n", + " [-1.6421 -0.6629 -0.2001 ... -0.2997 1.8579 1.5576]]\n", + "\n", + "A_device:\n", + "[[-0.3175 -0.227 0.0765 ... -0.2411 0.9861 -0.6663]\n", + " [-1.8135 -0.6378 -0.4466 ... -0.4503 3.2291 -1.3058]\n", + " [-0.6443 0.3033 0.9719 ... 0.1302 0.4531 -0.3723]\n", + " ...\n", + " [-1.8697 -0.7714 -0.2048 ... 0.0244 2.859 -1.1147]\n", + " [-1.1404 -0.9935 -0.4077 ... -0.2789 2.8553 -0.7879]\n", + " [-0.5979 -0.6596 0.1047 ... -0.1826 1.4195 -0.0636]]\n", + "\n" + ] + } + ], "source": [ - "\n", "with np.printoptions(precision=4):\n", " print(\"A_host:\")\n", " print(A_host)\n", @@ -398,6 +579,8 @@ "id": "3de33b00", "metadata": {}, "source": [ + "What does this reveal about `np.random` vs `cp.random`?\n", + "\n", "**SOLUTION:**\n", "\n", "This reveals that `np.random` and `cp.random` use **different random number generator (RNG) implementations**, even with the same seed.\n", @@ -421,14 +604,22 @@ "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" + "Finally, let's verify our accuracy against a reference implementation (`numpy.linalg.eigvals()`) and benchmark the speedup.\n" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "e5d4e603", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:48:06.840791Z", + "iopub.status.busy": "2026-03-09T19:48:06.840556Z", + "iopub.status.idle": "2026-03-09T19:48:40.245264Z", + "shell.execute_reply": "2026-03-09T19:48:40.244277Z", + "shell.execute_reply.started": "2026-03-09T19:48:06.840773Z" + } + }, "outputs": [], "source": [ "start = time.perf_counter()\n", @@ -438,14 +629,35 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "80b127ed", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:48:40.246588Z", + "iopub.status.busy": "2026-03-09T19:48:40.246128Z", + "iopub.status.idle": "2026-03-09T19:48:40.259350Z", + "shell.execute_reply": "2026-03-09T19:48:40.257131Z", + "shell.execute_reply.started": "2026-03-09T19:48:40.246561Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Power Iteration (Host) = 0.9999999999599408\n", + "Power Iteration (Device) = 0.9999999999591407\n", + "`eigvals` Reference = 1.000000000000234\n", + "\n", + "Relative Error (Host) = 4.029e-11\n", + "Relative Error (Device) = 4.109e-11\n" + ] + } + ], "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", + "print(f\"Power Iteration (Host) = {lam_est_host}\")\n", + "print(f\"Power Iteration (Device) = {lam_est_device}\")\n", + "print(f\"`eigvals` Reference = {lam_ref}\")\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", @@ -462,7 +674,7 @@ "id": "ef0bda61", "metadata": {}, "source": [ - "#### Benchmarking with `cupyx.profiler.benchmark`\n", + "#### 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", "\n", @@ -471,23 +683,46 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "39403462", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:48:40.260135Z", + "iopub.status.busy": "2026-03-09T19:48:40.259900Z", + "iopub.status.idle": "2026-03-09T19:48:58.805216Z", + "shell.execute_reply": "2026-03-09T19:48:58.803809Z", + "shell.execute_reply.started": "2026-03-09T19:48:40.260113Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Timing Host...\n", + "Timing Device...\n", + "\n", + "Power Iteration (Host) = 1342.18 ms ± 4.04% (mean ± relative stdev of 10 runs)\n", + "Power Iteration (Device) = 343.578 ms ± 3.54% (mean ± relative stdev of 10 runs)\n", + "`eigvals` Reference = 33400.8 ms\n", + "\n", + "Speedup (Device over Host) = 3.9x\n" + ] + } + ], "source": [ "cfg = PowerIterationConfig(progress=False)\n", "\n", "print(\"Timing Host...\")\n", - "T_host = cpx.profiler.benchmark(estimate_host, args=(A_host, cfg), n_repeat=10).cpu_times\n", + "T_host = cpx.profiler.benchmark(estimate_host, args=(A_host, cfg), n_repeat=10, n_warmup=1).cpu_times\n", "\n", "print(\"Timing Device...\")\n", - "T_device = cpx.profiler.benchmark(estimate_device, args=(A_host, cfg), n_repeat=10).cpu_times\n", + "T_device = cpx.profiler.benchmark(estimate_device, args=(A_host, cfg), n_repeat=10, n_warmup=1).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", - "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", + "print(f\"Power Iteration (Host) = {T_host.mean() * 1000:.6g} 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:.6g} ms ± {(T_device.std() / T_device.mean()):.2%} (mean ± relative stdev of {T_device.size} runs)\")\n", + "print(f\"`eigvals` Reference = {T_ref * 1000:.6g} ms\")\n", "\n", "speedup = T_host.mean() / T_device.mean()\n", "print()\n", @@ -516,9 +751,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "id": "2cdee8ff", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:48:58.806418Z", + "iopub.status.busy": "2026-03-09T19:48:58.806064Z", + "iopub.status.idle": "2026-03-09T19:48:58.810403Z", + "shell.execute_reply": "2026-03-09T19:48:58.809061Z", + "shell.execute_reply.started": "2026-03-09T19:48:58.806369Z" + } + }, "outputs": [], "source": [ "# Try different configurations here!\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 4807f482..4ff20ce0 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 @@ -99,7 +99,7 @@ " dim: int = 8192\n", " dominance: float = 0.05\n", " max_steps: int = 1000\n", - " check_frequency: int = 10\n", + " check_frequency: int = 15\n", " progress: bool = True\n", " residual_threshold: float = 1e-10\n", "\n", @@ -136,8 +136,8 @@ " 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", + " # Copy from device to host and save a checkpoint.\n", + " np.savetxt(f\"/tmp/device_{i}.txt\", x_host)\n", "\n", " if res_host < cfg.residual_threshold:\n", " break\n", @@ -164,6 +164,8 @@ "estimate_device(A_device, cfg=PowerIterationConfig(progress=False))\n", "stop = time.perf_counter()\n", "\n", + "np.testing.assert_allclose(lam_est_device, 1, atol=1e-4)\n", + "\n", "print()\n", "print(f\"{(stop - start) * 1000:.3f} ms\")" ] @@ -225,7 +227,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", @@ -233,7 +235,7 @@ "\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", + "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.annotate()`, another Python context manager, this time from a library called NVTX.\n", "\n", "```\n", "with nvtx.annotate(\"Loop\"):\n", @@ -307,7 +309,7 @@ " dim: int = 8192\n", " dominance: float = 0.05\n", " max_steps: int = 1000\n", - " check_frequency: int = 10\n", + " check_frequency: int = 15\n", " progress: bool = True\n", " residual_threshold: float = 1e-10\n", "\n", @@ -353,7 +355,8 @@ " 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", + " # Save a checkpoint.\n", + " np.savetxt(f\"/tmp/device_{i}.txt\", x_host)\n", "\n", " if res_host < cfg.residual_threshold:\n", " break\n", @@ -371,6 +374,8 @@ " lam_est_device = estimate_device(A_device)\n", " stop = time.perf_counter()\n", "\n", + "np.testing.assert_allclose(lam_est_device, 1, atol=1e-4)\n", + "\n", "print()\n", "print(f\"{(stop - start) * 1000:.3f} ms\")" ] 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 dc067187..84ff4439 100644 --- a/tutorials/accelerated-python/notebooks/kernels/40__kernel_authoring__copy.ipynb +++ b/tutorials/accelerated-python/notebooks/kernels/40__kernel_authoring__copy.ipynb @@ -149,7 +149,7 @@ "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", + "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 noticeably slower.\n", "\n", @@ -208,7 +208,7 @@ "\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", + "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", @@ -282,7 +282,7 @@ }, "outputs": [], "source": [ - "!python copy_optimized.py output" + "!python copy_optimized.py check" ] }, { @@ -322,7 +322,7 @@ "source": [ "### 6. Profiling the Optimized Kernel\n", "\n", - "Hopefully you see quite a speedup! Now let's profile the optimized variant:" + "That's quite a difference! Now let's profile the optimized variant:" ] }, { 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 dbcd7104..de8a0aec 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 @@ -208,7 +208,7 @@ "\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`.**" + "**TODO: Fix the code above by modifying it to use `cuda.atomic.add()`.**" ] }, { @@ -264,14 +264,14 @@ "**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", + "- **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`.**" + "- **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()`.**" ] }, { diff --git a/tutorials/accelerated-python/notebooks/kernels/43__kernel_authoring__black_and_white.ipynb b/tutorials/accelerated-python/notebooks/kernels/43__kernel_authoring__black_and_white.ipynb index 63c99206..0fc350b4 100644 --- a/tutorials/accelerated-python/notebooks/kernels/43__kernel_authoring__black_and_white.ipynb +++ b/tutorials/accelerated-python/notebooks/kernels/43__kernel_authoring__black_and_white.ipynb @@ -98,7 +98,7 @@ "id": "4e78dc11-5467-4a51-8e83-c5f5169a54a0", "metadata": {}, "source": [ - "**3. Set our two-dimensional thead size and block size.** _Hint: Our `threadsperblock` should still multiply to `128`._" + "**3. Set our two-dimensional thead size and block size.** _Hint: Our `threadsperblock` should still multiply to `256`._" ] }, { 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 ac1991ff..2e86d37d 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 @@ -28,14 +28,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "82b8f596", "metadata": { - "collapsed": true, - "id": "AoHkvSPMC5Fs", - "jupyter": { - "outputs_hidden": true - } + "execution": { + "iopub.execute_input": "2026-03-09T19:43:42.383973Z", + "iopub.status.busy": "2026-03-09T19:43:42.383690Z", + "iopub.status.idle": "2026-03-09T19:43:42.397535Z", + "shell.execute_reply": "2026-03-09T19:43:42.396362Z", + "shell.execute_reply.started": "2026-03-09T19:43:42.383942Z" + }, + "id": "AoHkvSPMC5Fs" }, "outputs": [], "source": [ @@ -84,6 +87,13 @@ "colab": { "base_uri": "https://localhost:8080/" }, + "execution": { + "iopub.execute_input": "2026-03-09T19:43:42.398487Z", + "iopub.status.busy": "2026-03-09T19:43:42.398179Z", + "iopub.status.idle": "2026-03-09T19:43:42.407555Z", + "shell.execute_reply": "2026-03-09T19:43:42.406444Z", + "shell.execute_reply.started": "2026-03-09T19:43:42.398458Z" + }, "id": "I9Tz2hG-_tBj", "outputId": "e52f41cb-f70b-4792-ea76-e78a554956f4" }, @@ -148,10 +158,26 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "baacfd0d", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:43:42.408684Z", + "iopub.status.busy": "2026-03-09T19:43:42.408440Z", + "iopub.status.idle": "2026-03-09T19:43:58.505975Z", + "shell.execute_reply": "2026-03-09T19:43:58.504698Z", + "shell.execute_reply.started": "2026-03-09T19:43:42.408663Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Problem size: 2.00 GB, dtype: int64\n" + ] + } + ], "source": [ "!python copy_blocked.py check" ] @@ -172,12 +198,19 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "54aea2c6", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, + "execution": { + "iopub.execute_input": "2026-03-09T19:43:58.507221Z", + "iopub.status.busy": "2026-03-09T19:43:58.506962Z", + "iopub.status.idle": "2026-03-09T19:44:09.831662Z", + "shell.execute_reply": "2026-03-09T19:44:09.830366Z", + "shell.execute_reply.started": "2026-03-09T19:43:58.507197Z" + }, "id": "5pyHvJtxVnDB", "outputId": "87ad5a72-9244-4b21-9776-ecd93262f274" }, @@ -186,10 +219,10 @@ "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" + "==PROF== Connected to process 644 (/usr/bin/python3.12)\n", + "==PROF== Profiling \"copy_blocked[abi:v1,cw51cXTLSUwv1sDUaKthoaNgqamjgOR3W3Cw6igA9duC0hkwnNGiHEkYkrqQBFBTDEwCyQe21eqwcFW3UoDGjzpQEsgzrtUEAA_3d_3d]\": 0%....50%....100% - 44 passes\n", + "==PROF== Disconnected from process 644\n", + "==PROF== Report: /accelerated-computing-hub/tutorials/accelerated-python/notebooks/kernels/solutions/copy_blocked.ncu-rep\n" ] } ], @@ -212,7 +245,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 5, "id": "4e99c6b9", "metadata": { "colab": { @@ -252,23 +285,17 @@ "678ec7c648a94af9a6d16e4bc22d743c" ] }, + "execution": { + "iopub.execute_input": "2026-03-09T19:44:09.833556Z", + "iopub.status.busy": "2026-03-09T19:44:09.833232Z", + "iopub.status.idle": "2026-03-09T19:44:09.969164Z", + "shell.execute_reply": "2026-03-09T19:44:09.967883Z", + "shell.execute_reply.started": "2026-03-09T19:44:09.833528Z" + }, "id": "40w07iG5k6Vl", "outputId": "f5d0becc-0285-4cb7-a78e-427cdc105454" }, "outputs": [ - { - "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" - }, { "data": { "text/html": [ @@ -323,7 +350,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "b47fd7b6a0154f21bc182a368783f018", + "model_id": "ef5ef643588149639d9499dde26dd7bf", "version_major": 2, "version_minor": 0 }, @@ -337,7 +364,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "da5ad97524f4499cb466a461c9e9a014", + "model_id": "8daf9c2eec544287875436afe1817f0b", "version_major": 2, "version_minor": 0 }, @@ -376,7 +403,7 @@ "\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", + "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", @@ -385,12 +412,19 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 6, "id": "d7b514e9", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, + "execution": { + "iopub.execute_input": "2026-03-09T19:44:09.970420Z", + "iopub.status.busy": "2026-03-09T19:44:09.970171Z", + "iopub.status.idle": "2026-03-09T19:44:09.976769Z", + "shell.execute_reply": "2026-03-09T19:44:09.975528Z", + "shell.execute_reply.started": "2026-03-09T19:44:09.970400Z" + }, "id": "B5PBpaY2HnE0", "outputId": "9bf8af36-f011-4eaa-ed28-c39abde2c315" }, @@ -462,10 +496,26 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "20fca1a6", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:44:09.978020Z", + "iopub.status.busy": "2026-03-09T19:44:09.977720Z", + "iopub.status.idle": "2026-03-09T19:44:18.612723Z", + "shell.execute_reply": "2026-03-09T19:44:18.611327Z", + "shell.execute_reply.started": "2026-03-09T19:44:09.977991Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Problem size: 2.00 GB, dtype: int64\n" + ] + } + ], "source": [ "!python copy_optimized.py check" ] @@ -480,12 +530,19 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": 8, "id": "0921fb67", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, + "execution": { + "iopub.execute_input": "2026-03-09T19:44:18.613944Z", + "iopub.status.busy": "2026-03-09T19:44:18.613674Z", + "iopub.status.idle": "2026-03-09T19:44:35.459607Z", + "shell.execute_reply": "2026-03-09T19:44:35.458456Z", + "shell.execute_reply.started": "2026-03-09T19:44:18.613917Z" + }, "id": "kJ7viF-i06qd", "outputId": "e2d84395-6324-4e55-c144-bc34399cc515" }, @@ -494,9 +551,9 @@ "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" + "copy_blocked: 38.1 ms ± 0.43% (mean ± relative stdev of 15 runs)\n", + "copy_optimized: 18.3 ms ± 0.28% (mean ± relative stdev of 15 runs)\n", + "copy_optimized speedup over copy_blocked: 2.08\n" ] } ], @@ -524,12 +581,19 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "79bc5620", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, + "execution": { + "iopub.execute_input": "2026-03-09T19:44:35.460832Z", + "iopub.status.busy": "2026-03-09T19:44:35.460599Z", + "iopub.status.idle": "2026-03-09T19:44:46.322224Z", + "shell.execute_reply": "2026-03-09T19:44:46.320473Z", + "shell.execute_reply.started": "2026-03-09T19:44:35.460814Z" + }, "id": "zO_y6ObXV_wX", "outputId": "8e643e49-6fbf-4b1e-ff58-d391700451ab" }, @@ -538,10 +602,10 @@ "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" + "==PROF== Connected to process 785 (/usr/bin/python3.12)\n", + "==PROF== Profiling \"copy_optimized[abi:v1,cw51cXTLSUwv1sDUaKthoaNgqamjgOR3W3Cw6igA9duC0hkwnNGiHEkYkrqQBFBTDEwCyQe21eqwcFW3UoDGjzpQEsgzrtUEAA_3d_3d]\": 0%....50%....100% - 44 passes\n", + "==PROF== Disconnected from process 785\n", + "==PROF== Report: /accelerated-computing-hub/tutorials/accelerated-python/notebooks/kernels/solutions/copy_optimized.ncu-rep\n" ] } ], @@ -562,7 +626,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 10, "id": "8f83e8fe", "metadata": { "colab": { @@ -602,23 +666,17 @@ "7ed48d6abaab4e95a347fa5c7b76bfac" ] }, + "execution": { + "iopub.execute_input": "2026-03-09T19:44:46.323839Z", + "iopub.status.busy": "2026-03-09T19:44:46.323450Z", + "iopub.status.idle": "2026-03-09T19:44:46.401570Z", + "shell.execute_reply": "2026-03-09T19:44:46.399984Z", + "shell.execute_reply.started": "2026-03-09T19:44:46.323802Z" + }, "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": [ - "" - ] - }, - "metadata": {}, - "output_type": "display_data" - }, { "data": { "text/html": [ @@ -673,7 +731,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "6be7ca5f9141465cb4b5126001b6c298", + "model_id": "2ef5798ed5d84b439f29fbf7da7c0020", "version_major": 2, "version_minor": 0 }, @@ -687,7 +745,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "7bd502fafaad4a1d9d5c7db9c1d54b9e", + "model_id": "51106bf176b2450c99d5dac3ba9dccb1", "version_major": 2, "version_minor": 0 }, 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 576c97a1..78eebcd5 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 @@ -27,10 +27,16 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "ce42d5e5-db1e-46da-a64a-831d0f3d59ff", "metadata": { - "collapsed": true, + "execution": { + "iopub.execute_input": "2026-03-09T19:42:36.221548Z", + "iopub.status.busy": "2026-03-09T19:42:36.221262Z", + "iopub.status.idle": "2026-03-09T19:42:37.304766Z", + "shell.execute_reply": "2026-03-09T19:42:37.303366Z", + "shell.execute_reply.started": "2026-03-09T19:42:36.221514Z" + }, "id": "ce42d5e5-db1e-46da-a64a-831d0f3d59ff" }, "outputs": [], @@ -59,12 +65,19 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": 2, "id": "6e1ae9dc-39f1-4c93-b923-85a96b45a057", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, + "execution": { + "iopub.execute_input": "2026-03-09T19:42:37.306151Z", + "iopub.status.busy": "2026-03-09T19:42:37.305690Z", + "iopub.status.idle": "2026-03-09T19:42:39.419885Z", + "shell.execute_reply": "2026-03-09T19:42:39.418825Z", + "shell.execute_reply.started": "2026-03-09T19:42:37.306115Z" + }, "id": "6e1ae9dc-39f1-4c93-b923-85a96b45a057", "outputId": "ee334037-7f39-4a91-ad60-f0408a56b4de" }, @@ -72,10 +85,10 @@ { "data": { "text/plain": [ - "('books__15m.txt', )" + "('books__15m.txt', )" ] }, - "execution_count": 3, + "execution_count": 2, "metadata": {}, "output_type": "execute_result" } @@ -102,12 +115,19 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": 3, "id": "61c12795-b14a-4447-9dcf-9748616cc453", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, + "execution": { + "iopub.execute_input": "2026-03-09T19:42:39.421022Z", + "iopub.status.busy": "2026-03-09T19:42:39.420694Z", + "iopub.status.idle": "2026-03-09T19:42:39.428753Z", + "shell.execute_reply": "2026-03-09T19:42:39.427169Z", + "shell.execute_reply.started": "2026-03-09T19:42:39.420984Z" + }, "id": "61c12795-b14a-4447-9dcf-9748616cc453", "outputId": "141b29b8-bbf7-4224-c85a-23e49ee7abaf" }, @@ -178,12 +198,19 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": 4, "id": "b3f32240-ad7b-4717-98b3-82b0298a099a", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, + "execution": { + "iopub.execute_input": "2026-03-09T19:42:39.429706Z", + "iopub.status.busy": "2026-03-09T19:42:39.429468Z", + "iopub.status.idle": "2026-03-09T19:42:47.995492Z", + "shell.execute_reply": "2026-03-09T19:42:47.994353Z", + "shell.execute_reply.started": "2026-03-09T19:42:39.429687Z" + }, "id": "b3f32240-ad7b-4717-98b3-82b0298a099a", "outputId": "cdf42faf-b6e6-45ad-85e2-d3b0d4c87ad5" }, @@ -192,7 +219,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.00858 s ± 3.22% (mean ± relative stdev of 15 runs)\n" + "4.19 ms ± 1.13% (mean ± relative stdev of 15 runs)\n" ] } ], @@ -202,26 +229,40 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "815cb072-b3a0-47aa-af6c-dd66c626a440", "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 487 }, + "execution": { + "iopub.execute_input": "2026-03-09T19:42:47.997273Z", + "iopub.status.busy": "2026-03-09T19:42:47.996885Z", + "iopub.status.idle": "2026-03-09T19:42:50.354006Z", + "shell.execute_reply": "2026-03-09T19:42:50.352914Z", + "shell.execute_reply.started": "2026-03-09T19:42:47.997232Z" + }, "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", + "image/png": "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2bJlSk1N1dGjR/1jJk+erP379ysvL09XXXWVBg8erPfee8///JIlS3TFFVfo8ssv19KlS897j/vvv1+DBw/WVVddpTfeeEPbt2/XtGnT9OGHH+rZZ5/1j3M6ndq/f3+JZe/eveXdJQAAgJBXrrtfv/76a61bt065ubnq2bOnJKlp06bq3LlziXG1a9dWfHy84uPjNXPmTC1atEivvfaarr76aknS3LlzNXToUJmmqblz52rw4MH+bTdt2qSHH35YM2bM0NixY/3rExMT1bt3b3399df+dQ6HQ/Hx8eXeaQAAALsp10xdrVq1VKtWLS1durTM06Y/VqNGDUVEROjMmTOSpPz8fG3YsEG33HKLbrnlFq1bt67E7Npzzz2nWrVq6U9/+lOpr1enTp3yRC7B5/PJ6/WWWAAAAOygXKWuRo0aysnJ0YIFC1SnTh1169ZN9913nz766KNSx585c0Zut1sej0fXXXedJGnevHnq16+f6tatq9jYWKWlpWn+/Pn+bT799FM1a9ZMERERF83j8Xj8RfPc0q9fvzLHu91uuVwu/5KQkFCe3QcAAKiyKnRN3VdffaVly5apb9++ys3NVceOHZWTk+Mfc88996hWrVqKiYnRI488oilTpqh///4qKirSggULNHToUP/YoUOHKicnR8XFxZIk0zQvOUvt2rW1devWEsucOXPKHJ+ZmSmPx+NfCgsLy7v7AAAAVVKF/qJEVFSUevfurd69e+uBBx7Q7bffrqysLI0YMUKSNGHCBI0YMUK1atVSXFycHA6HJGnlypX68ssvS1xDJ0lFRUVavXq1evfurVatWundd9/V2bNnLzpbFxYWphYtWlxybsMwZBhG+XYWAAAgBATke+ratGmjU6dO+R/Xr19fLVq0UHx8vL/QSd/fIHHrrbeeN7t26623au7cuZKk3/72tzp58qSefvrpUt/rhzdKAAAA4Hvlmqk7evSobr75Zt12221q166dateurQ8++EBTp07Vr3/96wtue/jwYb322mtatmyZkpOTSzw3fPhw3XjjjTp27Ji6dOmiiRMnavz48fryyy914403qnHjxvrss880a9Ysde/e3X9XrGmaOnDgwHnv1bBhQ4WF8b3KAACg+ihXqatVq5a6dOmi6dOnKz8/X2fPnlVCQoLuuOMO3XfffRfcduHChapZs6auv/768567/vrr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oTZu3KjDhw+rdevWVkcDAAAImpAvdU6nU++8845mzJghr9erpk2batq0aerXr5/V0QAAAIIm5Etd69attWLFCqtjAAAAWCrkS10gbM9O46YJAAAQ0mxxowQAAEB1R6kDAACwAUodAACADXBNnaTkrJUKM2KsjoEqpmBKf6sjAABwyZipAwAAsAFblbrU1FRlZGRYHQMAACDobHX69eWXX1ZERITVMQAAAILOVqUuNjbW6ggAAACW4PQrAACADdhqpu5ifD6ffD6f/7HX67UwDQAAQODYaqbuYtxut1wul39JSEiwOhIAAEBAVKtSl5mZKY/H418KCwutjgQAABAQ1er0q2EYMgzD6hgAAAABV61m6gAAAOyKUgcAAGADlDoAAAAbsNU1dbm5uVZHAAAAsAQzdQAAADZgq5m6itqenSan02l1DAAAgApjpg4AAMAGKHUAAAA2QKkDAACwAa6pk5SctVJhRozVMWChgin9rY4AAMBPwkwdAACADVDqAAAAbIBSBwAAYAO2LXVnzpyxOgIAAEDQ2OZGidTUVCUnJ6tGjRpatGiR2rZtqzVr1lgdCwAAIChsU+okacGCBRo9erTWr19f6vM+n08+n8//2Ov1BisaAABApbJVqWvZsqWmTp1a5vNut1vZ2dlBTAQAABActrqmrlOnThd8PjMzUx6Px78UFhYGKRkAAEDlstVMXc2aNS/4vGEYMgwjSGkAAACCx1YzdQAAANUVpQ4AAMAGKHUAAAA2YJtr6nJzc62OAAAAYBnblLqfYnt2mpxOp9UxAAAAKozTrwAAADZAqQMAALABSh0AAIANcE2dpOSslQozYqyOgf9TMKW/1REAAAg5zNQBAADYAKUOAADABmxX6lJTU5WRkWF1DAAAgKCyXakDAACojih1AAAANhDSpe7UqVMaPny4atWqpUaNGmnatGlWRwIAALBESJe6CRMmaO3atXr11Vf15ptvKjc3V1u2bClzvM/nk9frLbEAAADYQciWupMnT2ru3Ll67LHHdP3116tt27ZasGCBvvvuuzK3cbvdcrlc/iUhISGIiQEAACpPyJa6/Px8nTlzRl26dPGvi42NVVJSUpnbZGZmyuPx+JfCwsJgRAUAAKh01eovShiGIcMwrI4BAAAQcCE7U9e8eXNFRERo48aN/nXHjx/X7t27LUwFAABgjZCdqatVq5Z+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/PJLffbZZ+rZs2ep27vdbrlcLv+SkJAQxPQAAACVJ6RKnST17NlTubm5Mk1T69at029+8xu1bt1a7777rtauXavGjRurZcuWpW6bmZkpj8fjXwoLC4OcHgAAoHKE3JcPp6amat68efrwww8VERGhyy+/XKmpqcrNzdXx48fLnKWTJMMwZBhGENMCAAAER8jN1J27rm769On+Aneu1OXm5pZ5PR0AAICdhVypq1u3rtq1a6fnnnvOX+CuueYabdmyRbt3777gTB0AAIBdhVypk76/rq6oqMhf6mJjY9WmTRvFx8crKSnJ2nAAAAAWCMlSN2PGDJmmqcsvv9y/buvWrdq/f7+FqQAAAKwTkqUOAAAAJYXc3a+VYXt2mpxOp9UxAAAAKoyZOgAAABug1AEAANgAp18lJWetVJgRY3WMCiuY0t/qCAAAwGLM1AEAANgApQ4AAMAGKHUAAAA2QKkDAACwAUodAACADYRsqVu4cKHq1asnn89XYv3AgQM1bNgwi1IBAABYI2RL3c0336yioiItW7bMv+7QoUNavny5brvttlK38fl88nq9JRYAAAA7CNlSFx0drd/+9reaP3++f92iRYvUpEkTpaamlrqN2+2Wy+XyLwkJCUFKCwAAULlCttRJ0h133KE333xTX375pSQpJydHI0aMkMPhKHV8ZmamPB6PfyksLAxmXAAAgEoT0n9RokOHDkpJSdHChQvVp08f7dixQ8uXLy9zvGEYMgwjiAkBAACCI6RLnSTdfvvtmjFjhr788kv16tWLU6oAAKBaCunTr5L029/+Vl988YVmz55d5g0SAAAAdhfypc7lcummm25SrVq1NHDgQKvjAAAAWCLkS50kffnll/rd737H9XIAAKDaCulr6o4fP67c3Fzl5ubq6aefrvDrbM9Ok9PpDGAyAACA4ArpUtehQwcdP35cjzzyiJKSkqyOAwAAYJmQLnUFBQVWRwAAAKgSbHFNHQAAQHVHqQMAALABSh0AAIANUOoAAABsgFIHAABgA5Q6AAAAG6DUAQAA2AClDgAAwAYodQAAADZAqQMAALABSh0AAIANUOoAAABsgFIHAABgA5Q6AAAAG6DUAQAA2EANqwNYyTRNSZLX67U4CQAAqO7O9ZFz/aS8qnWpO3r0qCQpISHB4iQAAADfO3HihFwuV7m3q9alLjY2VpK0b9++Cv3jIfi8Xq8SEhJUWFgop9NpdRxcAo5Z6OGYhR6OWegp7ZiZpqkTJ06ocePGFXrNal3qwsK+v6TQ5XLxQxBinE4nxyzEcMxCD8cs9HDMQs+Pj9lPmWTiRgkAAAAboNQBAADYQLUudYZhKCsrS4ZhWB0Fl4hjFno4ZqGHYxZ6OGahpzKOmcOs6H2zAAAAqDKq9UwdAACAXVDqAAAAbIBSBwAAYAOUOgAAABuwfambOXOmEhMTFRUVpS5dumjTpk0XHL948WJdfvnlioqKUtu2bfWf//wnSElxTnmOWU5OjhwOR4klKioqiGmrt3feeUcDBgxQ48aN5XA4tHTp0otuk5ubq44dO8owDLVo0UI5OTmVnhP/U95jlpube97PmMPh0IEDB4ITGHK73brqqqtUu3ZtNWzYUAMHDtSuXbsuuh2fZ9apyDELxOeZrUvdCy+8oHHjxikrK0tbtmxRSkqK0tLSdOjQoVLHv/feexoyZIh+//vfKy8vTwMHDtTAgQO1ffv2ICevvsp7zKTvv417//79/mXv3r1BTFy9nTp1SikpKZo5c+Yljd+zZ4/69++va6+9Vlu3blVGRoZuv/12rVy5spKT4pzyHrNzdu3aVeLnrGHDhpWUED+2du1a3Xnnnfrvf/+rVatW6ezZs+rTp49OnTpV5jZ8nlmrIsdMCsDnmWljnTt3Nu+8807/46KiIrNx48am2+0udfwtt9xi9u/fv8S6Ll26mH/4wx8qNSf+p7zHbP78+abL5QpSOlyIJPOVV1654JiJEyeaV1xxRYl1gwcPNtPS0ioxGcpyKcdszZo1piTz+PHjQcmEizt06JApyVy7dm2ZY/g8q1ou5ZgF4vPMtjN1Z86c0ebNm9WrVy//urCwMPXq1UsbNmwodZsNGzaUGC9JaWlpZY5HYFXkmEnSyZMn1bRpUyUkJOjXv/61duzYEYy4qAB+xkJX+/bt1ahRI/Xu3Vvr16+3Ok615vF4JEmxsbFljuFnrWq5lGMm/fTPM9uWuiNHjqioqEhxcXEl1sfFxZV5LciBAwfKNR6BVZFjlpSUpHnz5unVV1/VokWLVFxcrKuvvlpffPFFMCKjnMr6GfN6vfrmm28sSoULadSokWbNmqUlS5ZoyZIlSkhIUGpqqrZs2WJ1tGqpuLhYGRkZ6tatm5KTk8scx+dZ1XGpxywQn2c1AhEYsErXrl3VtWtX/+Orr75arVu31j//+U/99a9/tTAZYA9JSUlKSkryP7766quVn5+v6dOn69lnn7UwWfV05513avv27Xr33XetjoJLdKnHLBCfZ7adqatfv77Cw8N18ODBEusPHjyo+Pj4UreJj48v13gEVkWO2Y9FRESoQ4cO+uyzzyojIn6isn7GnE6noqOjLUqF8urcuTM/YxYYM2aMXn/9da1Zs0Y///nPLziWz7OqoTzH7Mcq8nlm21IXGRmpTp06afXq1f51xcXFWr16dYkm/ENdu3YtMV6SVq1aVeZ4BFZFjtmPFRUVadu2bWrUqFFlxcRPwM+YPWzdupWfsSAyTVNjxozRK6+8orfffluXXXbZRbfhZ81aFTlmP1ahz7OfdJtFFff888+bhmGYOTk55s6dO81Ro0aZderUMQ8cOGCapmkOGzbMvPfee/3j169fb9aoUcN87LHHzI8//tjMysoyIyIizG3btlm1C9VOeY9Zdna2uXLlSjM/P9/cvHmzeeutt5pRUVHmjh07rNqFauXEiRNmXl6emZeXZ0oyH3/8cTMvL8/cu3evaZqmee+995rDhg3zj//888/NmJgYc8KECebHH39szpw50wwPDzdXrFhh1S5UO+U9ZtOnTzeXLl1qfvrpp+a2bdvMsWPHmmFhYeZbb71l1S5UO6NHjzZdLpeZm5tr7t+/37+cPn3aP4bPs6qlIscsEJ9nti51pmmaf//7380mTZqYkZGRZufOnc3//ve//ud69uxppqenlxj/4osvmq1atTIjIyPNK664wly+fHmQE6M8xywjI8M/Ni4uzrzhhhvMLVu2WJC6ejr3dRc/Xs4do/T0dLNnz57nbdO+fXszMjLSbNasmTl//vyg567OynvMHnnkEbN58+ZmVFSUGRsba6ampppvv/22NeGrqdKOl6QSPzt8nlUtFTlmgfg8c/zfmwMAACCE2faaOgAAgOqEUgcAAGADlDoAAAAboNQBAADYAKUOAADABih1AAAANkCpAwAAsAFKHQAAwCV45513NGDAADVu3FgOh0NLly4t92uYpqnHHntMrVq1kmEY+tnPfqa//e1vAclHqQMACxUUFMjhcGjr1q1WRwFwEadOnVJKSopmzpxZ4dcYO3as5syZo8cee0yffPKJli1bps6dOwckX42AvAoAAIDN9evXT/369SvzeZ/Pp/vvv1///ve/9fXXXys5OVmPPPKIUlNTJUkff/yx/vGPf2j79u1KSkqSJF122WUBy8dMHYBqrbi4WFOnTlWLFi1kGIaaNGniPxWybds2XXfddYqOjla9evU0atQonTx50r9tamqqMjIySrzewIEDNWLECP/jxMREPfzww7rttttUu3ZtNWnSRM8884z/+XO/0Dt06CCHw+H/5Q8g9IwZM0YbNmzQ888/r48++kg333yz+vbtq08//VSS9Nprr6lZs2Z6/fXXddlllykxMVG33367jh07FpD3p9QBqNYyMzM1ZcoUPfDAA9q5c6f+9a9/KS4uTqdOnVJaWprq1q2r999/X4sXL9Zbb72lMWPGlPs9pk2bpiuvvFJ5eXn605/+pNGjR2vXrl2SpE2bNkmS3nrrLe3fv18vv/xyQPcPQHDs27dP8+fP1+LFi9WjRw81b95cf/nLX9S9e3fNnz9fkvT5559r7969Wrx4sRYuXKicnBxt3rxZgwYNCkgGTr8CqLZOnDihJ554Qk899ZTS09MlSc2bN1f37t01e/Zsffvtt1q4cKFq1qwpSXrqqac0YMAAPfLII4qLi7vk97nhhhv0pz/9SZJ0zz33aPr06VqzZo2SkpLUoEEDSVK9evUUHx8f4D0EECzbtm1TUVGRWrVqVWK9z+dTvXr1JH1/ZsDn82nhwoX+cXPnzlWnTp20a9cu/ynZiqLUAai2Pv74Y/l8Pl1//fWlPpeSkuIvdJLUrVs3FRcXa9euXeUqde3atfP/t8PhUHx8vA4dOvTTwgOoUk6ePKnw8HBt3rxZ4eHhJZ6rVauWJKlRo0aqUaNGieLXunVrSd/P9FHqAKCCoqOjf9L2YWFhMk2zxLqzZ8+eNy4iIqLEY4fDoeLi4p/03gCqlg4dOqioqEiHDh1Sjx49Sh3TrVs3fffdd8rPz1fz5s0lSbt375YkNW3a9Cdn4Jo6ANVWy5YtFR0drdWrV5/3XOvWrfXhhx/q1KlT/nXr169XWFiY//+mGzRooP379/ufLyoq0vbt28uVITIy0r8tgKrt5MmT2rp1q/8riPbs2aOtW7dq3759atWqlX73u99p+PDhevnll7Vnzx5t2rRJbrdby5cvlyT16tVLHTt21G233aa8vDxt3rxZf/jDH9S7d+/zTttWBKUOQLUVFRWle+65RxMnTtTChQuVn5+v//73v5o7d65+97vfKSoqSunp6dq+fbvWrFmjP//5zxo2bJj/1Ot1112n5cuXa/ny5frkk080evRoff311+XK0LBhQ0VHR2vFihU6ePCgPB5PJewpgED44IMP1KFDB3Xo0EGSNG7cOHXo0EGTJk2SJM2fP1/Dhw/X+PHjlZSUpIEDB+r9999XkyZNJH0/u//aa6+pfv36uuaaa9S/f3+1bt1azz//fEDycfoVQLX2wAMPqEaNGpo0aZK++uorNWrUSH/84x8VExOjlStXauzYsbrqqqsUExOjm266SY8//rh/29tuu00ffvihhg8frho1aujuu+/WtddeW673r1Gjhp588klNnjxZkyZNUo8ePZSbmxvgvQQQCKmpqeddcvFDERERys7OVnZ2dpljGjdurCVLllRGPDnMC6UDAABASOD0KwAAgA1Q6gAAAGyAUgcAAGADlDoAAAAboNQBAADYAKUOAADABih1AAAANkCpAwAAsAFKHQAAgA1Q6gAAAGyAUgcAAGADlDoAAAAb+P9EYpjpexBJsAAAAABJRU5ErkJggg==", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Characters in dataset: 15.0 MB\n" + ] } ], "source": [ @@ -280,12 +321,19 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "8dbd226c-66f2-43df-868a-6b024b1de24c", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, + "execution": { + "iopub.execute_input": "2026-03-09T19:42:50.355341Z", + "iopub.status.busy": "2026-03-09T19:42:50.355104Z", + "iopub.status.idle": "2026-03-09T19:42:59.350039Z", + "shell.execute_reply": "2026-03-09T19:42:59.347797Z", + "shell.execute_reply.started": "2026-03-09T19:42:50.355322Z" + }, "id": "8dbd226c-66f2-43df-868a-6b024b1de24c", "outputId": "a082d13a-8e86-436d-c3de-8351f337d824" }, @@ -294,10 +342,10 @@ "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" + "==PROF== Connected to process 311 (/usr/bin/python3.12)\n", + "==PROF== Profiling \"histogram_global[abi:v1,cw51cXTLSUwv1sDUaKthoaNgqamjgOR3W3Cw6igA9duC0hkwnNGiHEkYkrqQBFBTDEwCyQe21eqwcFW3UoDGjzpQEsgzrtUEAA_3d_3d]\": 0%....50%....100% - 44 passes\n", + "==PROF== Disconnected from process 311\n", + "==PROF== Report: /accelerated-computing-hub/tutorials/accelerated-python/notebooks/kernels/solutions/histogram_global.ncu-rep\n" ] } ], @@ -308,7 +356,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": 7, "id": "ad12380e-253b-4410-ab34-9479411fdf81", "metadata": { "colab": { @@ -350,23 +398,17 @@ "f1930ef855f844ee947a1c9b07ed95a4" ] }, + "execution": { + "iopub.execute_input": "2026-03-09T19:42:59.351619Z", + "iopub.status.busy": "2026-03-09T19:42:59.351019Z", + "iopub.status.idle": "2026-03-09T19:42:59.519590Z", + "shell.execute_reply": "2026-03-09T19:42:59.518487Z", + "shell.execute_reply.started": "2026-03-09T19:42:59.351347Z" + }, "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" - }, { "data": { "text/html": [ @@ -421,7 +463,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "a21053863aa949e484f30f47bcf5c045", + "model_id": "049fca414b834d99a98ff0a9ebe39597", "version_major": 2, "version_minor": 0 }, @@ -435,7 +477,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "804c25036085410fbbcba82f19667d1d", + "model_id": "6f775d2efc07462f873cac04802d2a23", "version_major": 2, "version_minor": 0 }, @@ -471,12 +513,19 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": 8, "id": "cf7c9865-646a-4bbd-9b41-61cadfc5484c", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, + "execution": { + "iopub.execute_input": "2026-03-09T19:42:59.521142Z", + "iopub.status.busy": "2026-03-09T19:42:59.520709Z", + "iopub.status.idle": "2026-03-09T19:42:59.528970Z", + "shell.execute_reply": "2026-03-09T19:42:59.527704Z", + "shell.execute_reply.started": "2026-03-09T19:42:59.521108Z" + }, "id": "cf7c9865-646a-4bbd-9b41-61cadfc5484c", "outputId": "851b231f-4431-4cf3-b857-c5a966a7162f" }, @@ -571,12 +620,19 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": 9, "id": "1b30e9b3-5a4c-4181-b642-b7def5e9f258", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, + "execution": { + "iopub.execute_input": "2026-03-09T19:42:59.530815Z", + "iopub.status.busy": "2026-03-09T19:42:59.530600Z", + "iopub.status.idle": "2026-03-09T19:43:04.084544Z", + "shell.execute_reply": "2026-03-09T19:43:04.083429Z", + "shell.execute_reply.started": "2026-03-09T19:42:59.530797Z" + }, "id": "1b30e9b3-5a4c-4181-b642-b7def5e9f258", "outputId": "3a04d1d0-3409-441c-af2a-3138e9dec324" }, @@ -585,7 +641,7 @@ "name": "stdout", "output_type": "stream", "text": [ - "0.000714 s ± 2.97% (mean ± relative stdev of 15 runs)\n" + "0.16 ms ± 8.47% (mean ± relative stdev of 15 runs)\n" ] } ], @@ -595,26 +651,40 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "id": "73f0c3cd-349b-490f-b6bd-7afbeb442fff", "metadata": { "colab": { "base_uri": "https://localhost:8080/", "height": 487 }, + "execution": { + "iopub.execute_input": "2026-03-09T19:43:04.086115Z", + "iopub.status.busy": "2026-03-09T19:43:04.085849Z", + "iopub.status.idle": "2026-03-09T19:43:08.196008Z", + "shell.execute_reply": "2026-03-09T19:43:08.194609Z", + "shell.execute_reply.started": "2026-03-09T19:43:04.086089Z" + }, "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", + "image/png": "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2bJlSk1N1dGjR/1jJk+erP379ysvL09XXXWVBg8erPfee8///JIlS3TFFVfo8ssv19KlS897j/vvv1+DBw/WVVddpTfeeEPbt2/XtGnT9OGHH+rZZ5/1j3M6ndq/f3+JZe/eveXdJQAAgJBXrrtfv/76a61bt065ubnq2bOnJKlp06bq3LlziXG1a9dWfHy84uPjNXPmTC1atEivvfaarr76aknS3LlzNXToUJmmqblz52rw4MH+bTdt2qSHH35YM2bM0NixY/3rExMT1bt3b3399df+dQ6HQ/Hx8eXeaQAAALsp10xdrVq1VKtWLS1durTM06Y/VqNGDUVEROjMmTOSpPz8fG3YsEG33HKLbrnlFq1bt67E7Npzzz2nWrVq6U9/+lOpr1enTp3yRC7B5/PJ6/WWWAAAAOygXKWuRo0aysnJ0YIFC1SnTh1169ZN9913nz766KNSx585c0Zut1sej0fXXXedJGnevHnq16+f6tatq9jYWKWlpWn+/Pn+bT799FM1a9ZMERERF83j8Xj8RfPc0q9fvzLHu91uuVwu/5KQkFCe3QcAAKiyKnRN3VdffaVly5apb9++ys3NVceOHZWTk+Mfc88996hWrVqKiYnRI488oilTpqh///4qKirSggULNHToUP/YoUOHKicnR8XFxZIk0zQvOUvt2rW1devWEsucOXPKHJ+ZmSmPx+NfCgsLy7v7AAAAVVKF/qJEVFSUevfurd69e+uBBx7Q7bffrqysLI0YMUKSNGHCBI0YMUK1atVSXFycHA6HJGnlypX68ssvS1xDJ0lFRUVavXq1evfurVatWundd9/V2bNnLzpbFxYWphYtWlxybsMwZBhG+XYWAAAgBATke+ratGmjU6dO+R/Xr19fLVq0UHx8vL/QSd/fIHHrrbeeN7t26623au7cuZKk3/72tzp58qSefvrpUt/rhzdKAAAA4Hvlmqk7evSobr75Zt12221q166dateurQ8++EBTp07Vr3/96wtue/jwYb322mtatmyZkpOTSzw3fPhw3XjjjTp27Ji6dOmiiRMnavz48fryyy914403qnHjxvrss880a9Ysde/e3X9XrGmaOnDgwHnv1bBhQ4WF8b3KAACg+ihXqatVq5a6dOmi6dOnKz8/X2fPnlVCQoLuuOMO3XfffRfcduHChapZs6auv/768567/vrr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oTZu3KjDhw+rdevWVkcDAAAImpAvdU6nU++8845mzJghr9erpk2batq0aerXr5/V0QAAAIIm5Etd69attWLFCqtjAAAAWCrkS10gbM9O46YJAAAQ0mxxowQAAEB1R6kDAACwAUodAACADXBNnaTkrJUKM2KsjoEqpmBKf6sjAABwyZipAwAAsAFblbrU1FRlZGRYHQMAACDobHX69eWXX1ZERITVMQAAAILOVqUuNjbW6ggAAACW4PQrAACADdhqpu5ifD6ffD6f/7HX67UwDQAAQODYaqbuYtxut1wul39JSEiwOhIAAEBAVKtSl5mZKY/H418KCwutjgQAABAQ1er0q2EYMgzD6hgAAAABV61m6gAAAOyKUgcAAGADlDoAAAAbsNU1dbm5uVZHAAAAsAQzdQAAADZgq5m6itqenSan02l1DAAAgApjpg4AAMAGKHUAAAA2QKkDAACwAa6pk5SctVJhRozVMWChgin9rY4AAMBPwkwdAACADVDqAAAAbIBSBwAAYAO2LXVnzpyxOgIAAEDQ2OZGidTUVCUnJ6tGjRpatGiR2rZtqzVr1lgdCwAAIChsU+okacGCBRo9erTWr19f6vM+n08+n8//2Ov1BisaAABApbJVqWvZsqWmTp1a5vNut1vZ2dlBTAQAABActrqmrlOnThd8PjMzUx6Px78UFhYGKRkAAEDlstVMXc2aNS/4vGEYMgwjSGkAAACCx1YzdQAAANUVpQ4AAMAGKHUAAAA2YJtr6nJzc62OAAAAYBnblLqfYnt2mpxOp9UxAAAAKozTrwAAADZAqQMAALABSh0AAIANcE2dpOSslQozYqyOgf9TMKW/1REAAAg5zNQBAADYAKUOAADABmxX6lJTU5WRkWF1DAAAgKCyXakDAACojih1AAAANhDSpe7UqVMaPny4atWqpUaNGmnatGlWRwIAALBESJe6CRMmaO3atXr11Vf15ptvKjc3V1u2bClzvM/nk9frLbEAAADYQciWupMnT2ru3Ll67LHHdP3116tt27ZasGCBvvvuuzK3cbvdcrlc/iUhISGIiQEAACpPyJa6/Px8nTlzRl26dPGvi42NVVJSUpnbZGZmyuPx+JfCwsJgRAUAAKh01eovShiGIcMwrI4BAAAQcCE7U9e8eXNFRERo48aN/nXHjx/X7t27LUwFAABgjZCdqatVq5Z+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/PJLffbZZ+rZs2ep27vdbrlcLv+SkJAQxPQAAACVJ6RKnST17NlTubm5Mk1T69at029+8xu1bt1a7777rtauXavGjRurZcuWpW6bmZkpj8fjXwoLC4OcHgAAoHKE3JcPp6amat68efrwww8VERGhyy+/XKmpqcrNzdXx48fLnKWTJMMwZBhGENMCAAAER8jN1J27rm769On+Aneu1OXm5pZ5PR0AAICdhVypq1u3rtq1a6fnnnvOX+CuueYabdmyRbt3777gTB0AAIBdhVypk76/rq6oqMhf6mJjY9WmTRvFx8crKSnJ2nAAAAAWCMlSN2PGDJmmqcsvv9y/buvWrdq/f7+FqQAAAKwTkqUOAAAAJYXc3a+VYXt2mpxOp9UxAAAAKoyZOgAAABug1AEAANgAp18lJWetVJgRY3WMCiuY0t/qCAAAwGLM1AEAANgApQ4AAMAGKHUAAAA2QKkDAACwAUodAACADYRsqVu4cKHq1asnn89XYv3AgQM1bNgwi1IBAABYI2RL3c0336yioiItW7bMv+7QoUNavny5brvttlK38fl88nq9JRYAAAA7CNlSFx0drd/+9reaP3++f92iRYvUpEkTpaamlrqN2+2Wy+XyLwkJCUFKCwAAULlCttRJ0h133KE333xTX375pSQpJydHI0aMkMPhKHV8ZmamPB6PfyksLAxmXAAAgEoT0n9RokOHDkpJSdHChQvVp08f7dixQ8uXLy9zvGEYMgwjiAkBAACCI6RLnSTdfvvtmjFjhr788kv16tWLU6oAAKBaCunTr5L029/+Vl988YVmz55d5g0SAAAAdhfypc7lcummm25SrVq1NHDgQKvjAAAAWCLkS50kffnll/rd737H9XIAAKDaCulr6o4fP67c3Fzl5ubq6aefrvDrbM9Ok9PpDGAyAACA4ArpUtehQwcdP35cjzzyiJKSkqyOAwAAYJmQLnUFBQVWRwAAAKgSbHFNHQAAQHVHqQMAALABSh0AAIANUOoAAABsgFIHAABgA5Q6AAAAG6DUAQAA2AClDgAAwAYodQAAADZAqQMAALABSh0AAIANUOoAAABsgFIHAABgA5Q6AAAAG6DUAQAA2EANqwNYyTRNSZLX67U4CQAAqO7O9ZFz/aS8qnWpO3r0qCQpISHB4iQAAADfO3HihFwuV7m3q9alLjY2VpK0b9++Cv3jIfi8Xq8SEhJUWFgop9NpdRxcAo5Z6OGYhR6OWegp7ZiZpqkTJ06ocePGFXrNal3qwsK+v6TQ5XLxQxBinE4nxyzEcMxCD8cs9HDMQs+Pj9lPmWTiRgkAAAAboNQBAADYQLUudYZhKCsrS4ZhWB0Fl4hjFno4ZqGHYxZ6OGahpzKOmcOs6H2zAAAAqDKq9UwdAACAXVDqAAAAbIBSBwAAYAOUOgAAABuwfambOXOmEhMTFRUVpS5dumjTpk0XHL948WJdfvnlioqKUtu2bfWf//wnSElxTnmOWU5OjhwOR4klKioqiGmrt3feeUcDBgxQ48aN5XA4tHTp0otuk5ubq44dO8owDLVo0UI5OTmVnhP/U95jlpube97PmMPh0IEDB4ITGHK73brqqqtUu3ZtNWzYUAMHDtSuXbsuuh2fZ9apyDELxOeZrUvdCy+8oHHjxikrK0tbtmxRSkqK0tLSdOjQoVLHv/feexoyZIh+//vfKy8vTwMHDtTAgQO1ffv2ICevvsp7zKTvv417//79/mXv3r1BTFy9nTp1SikpKZo5c+Yljd+zZ4/69++va6+9Vlu3blVGRoZuv/12rVy5spKT4pzyHrNzdu3aVeLnrGHDhpWUED+2du1a3Xnnnfrvf/+rVatW6ezZs+rTp49OnTpV5jZ8nlmrIsdMCsDnmWljnTt3Nu+8807/46KiIrNx48am2+0udfwtt9xi9u/fv8S6Ll26mH/4wx8qNSf+p7zHbP78+abL5QpSOlyIJPOVV1654JiJEyeaV1xxRYl1gwcPNtPS0ioxGcpyKcdszZo1piTz+PHjQcmEizt06JApyVy7dm2ZY/g8q1ou5ZgF4vPMtjN1Z86c0ebNm9WrVy//urCwMPXq1UsbNmwodZsNGzaUGC9JaWlpZY5HYFXkmEnSyZMn1bRpUyUkJOjXv/61duzYEYy4qAB+xkJX+/bt1ahRI/Xu3Vvr16+3Ok615vF4JEmxsbFljuFnrWq5lGMm/fTPM9uWuiNHjqioqEhxcXEl1sfFxZV5LciBAwfKNR6BVZFjlpSUpHnz5unVV1/VokWLVFxcrKuvvlpffPFFMCKjnMr6GfN6vfrmm28sSoULadSokWbNmqUlS5ZoyZIlSkhIUGpqqrZs2WJ1tGqpuLhYGRkZ6tatm5KTk8scx+dZ1XGpxywQn2c1AhEYsErXrl3VtWtX/+Orr75arVu31j//+U/99a9/tTAZYA9JSUlKSkryP7766quVn5+v6dOn69lnn7UwWfV05513avv27Xr33XetjoJLdKnHLBCfZ7adqatfv77Cw8N18ODBEusPHjyo+Pj4UreJj48v13gEVkWO2Y9FRESoQ4cO+uyzzyojIn6isn7GnE6noqOjLUqF8urcuTM/YxYYM2aMXn/9da1Zs0Y///nPLziWz7OqoTzH7Mcq8nlm21IXGRmpTp06afXq1f51xcXFWr16dYkm/ENdu3YtMV6SVq1aVeZ4BFZFjtmPFRUVadu2bWrUqFFlxcRPwM+YPWzdupWfsSAyTVNjxozRK6+8orfffluXXXbZRbfhZ81aFTlmP1ahz7OfdJtFFff888+bhmGYOTk55s6dO81Ro0aZderUMQ8cOGCapmkOGzbMvPfee/3j169fb9aoUcN87LHHzI8//tjMysoyIyIizG3btlm1C9VOeY9Zdna2uXLlSjM/P9/cvHmzeeutt5pRUVHmjh07rNqFauXEiRNmXl6emZeXZ0oyH3/8cTMvL8/cu3evaZqmee+995rDhg3zj//888/NmJgYc8KECebHH39szpw50wwPDzdXrFhh1S5UO+U9ZtOnTzeXLl1qfvrpp+a2bdvMsWPHmmFhYeZbb71l1S5UO6NHjzZdLpeZm5tr7t+/37+cPn3aP4bPs6qlIscsEJ9nti51pmmaf//7380mTZqYkZGRZufOnc3//ve//ud69uxppqenlxj/4osvmq1atTIjIyPNK664wly+fHmQE6M8xywjI8M/Ni4uzrzhhhvMLVu2WJC6ejr3dRc/Xs4do/T0dLNnz57nbdO+fXszMjLSbNasmTl//vyg567OynvMHnnkEbN58+ZmVFSUGRsba6ampppvv/22NeGrqdKOl6QSPzt8nlUtFTlmgfg8c/zfmwMAACCE2faaOgAAgOqEUgcAAGADlDoAAAAboNQBAADYAKUOAADABih1AAAANkCpAwAAsAFKHQAAwCV45513NGDAADVu3FgOh0NLly4t92uYpqnHHntMrVq1kmEY+tnPfqa//e1vAclHqQMACxUUFMjhcGjr1q1WRwFwEadOnVJKSopmzpxZ4dcYO3as5syZo8cee0yffPKJli1bps6dOwckX42AvAoAAIDN9evXT/369SvzeZ/Pp/vvv1///ve/9fXXXys5OVmPPPKIUlNTJUkff/yx/vGPf2j79u1KSkqSJF122WUBy8dMHYBqrbi4WFOnTlWLFi1kGIaaNGniPxWybds2XXfddYqOjla9evU0atQonTx50r9tamqqMjIySrzewIEDNWLECP/jxMREPfzww7rttttUu3ZtNWnSRM8884z/+XO/0Dt06CCHw+H/5Q8g9IwZM0YbNmzQ888/r48++kg333yz+vbtq08//VSS9Nprr6lZs2Z6/fXXddlllykxMVG33367jh07FpD3p9QBqNYyMzM1ZcoUPfDAA9q5c6f+9a9/KS4uTqdOnVJaWprq1q2r999/X4sXL9Zbb72lMWPGlPs9pk2bpiuvvFJ5eXn605/+pNGjR2vXrl2SpE2bNkmS3nrrLe3fv18vv/xyQPcPQHDs27dP8+fP1+LFi9WjRw81b95cf/nLX9S9e3fNnz9fkvT5559r7969Wrx4sRYuXKicnBxt3rxZgwYNCkgGTr8CqLZOnDihJ554Qk899ZTS09MlSc2bN1f37t01e/Zsffvtt1q4cKFq1qwpSXrqqac0YMAAPfLII4qLi7vk97nhhhv0pz/9SZJ0zz33aPr06VqzZo2SkpLUoEEDSVK9evUUHx8f4D0EECzbtm1TUVGRWrVqVWK9z+dTvXr1JH1/ZsDn82nhwoX+cXPnzlWnTp20a9cu/ynZiqLUAai2Pv74Y/l8Pl1//fWlPpeSkuIvdJLUrVs3FRcXa9euXeUqde3atfP/t8PhUHx8vA4dOvTTwgOoUk6ePKnw8HBt3rxZ4eHhJZ6rVauWJKlRo0aqUaNGieLXunVrSd/P9FHqAKCCoqOjf9L2YWFhMk2zxLqzZ8+eNy4iIqLEY4fDoeLi4p/03gCqlg4dOqioqEiHDh1Sjx49Sh3TrVs3fffdd8rPz1fz5s0lSbt375YkNW3a9Cdn4Jo6ANVWy5YtFR0drdWrV5/3XOvWrfXhhx/q1KlT/nXr169XWFiY//+mGzRooP379/ufLyoq0vbt28uVITIy0r8tgKrt5MmT2rp1q/8riPbs2aOtW7dq3759atWqlX73u99p+PDhevnll7Vnzx5t2rRJbrdby5cvlyT16tVLHTt21G233aa8vDxt3rxZf/jDH9S7d+/zTttWBKUOQLUVFRWle+65RxMnTtTChQuVn5+v//73v5o7d65+97vfKSoqSunp6dq+fbvWrFmjP//5zxo2bJj/1Ot1112n5cuXa/ny5frkk080evRoff311+XK0LBhQ0VHR2vFihU6ePCgPB5PJewpgED44IMP1KFDB3Xo0EGSNG7cOHXo0EGTJk2SJM2fP1/Dhw/X+PHjlZSUpIEDB+r9999XkyZNJH0/u//aa6+pfv36uuaaa9S/f3+1bt1azz//fEDycfoVQLX2wAMPqEaNGpo0aZK++uorNWrUSH/84x8VExOjlStXauzYsbrqqqsUExOjm266SY8//rh/29tuu00ffvihhg8frho1aujuu+/WtddeW673r1Gjhp588klNnjxZkyZNUo8ePZSbmxvgvQQQCKmpqeddcvFDERERys7OVnZ2dpljGjdurCVLllRGPDnMC6UDAABASOD0KwAAgA1Q6gAAAGyAUgcAAGADlDoAAAAboNQBAADYAKUOAADABih1AAAANkCpAwAAsAFKHQAAgA1Q6gAAAGyAUgcAAGADlDoAAAAb+P9EYpjpexBJsAAAAABJRU5ErkJggg==", "text/plain": [ "
" ] }, "metadata": {}, "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Characters in dataset: 15.0 MB\n" + ] } ], "source": [ @@ -646,12 +716,19 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "id": "d637b6b1-fb0b-4807-b70b-c80227c0fd6f", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, + "execution": { + "iopub.execute_input": "2026-03-09T19:43:08.197078Z", + "iopub.status.busy": "2026-03-09T19:43:08.196782Z", + "iopub.status.idle": "2026-03-09T19:43:17.304512Z", + "shell.execute_reply": "2026-03-09T19:43:17.303068Z", + "shell.execute_reply.started": "2026-03-09T19:43:08.197047Z" + }, "id": "d637b6b1-fb0b-4807-b70b-c80227c0fd6f", "outputId": "e3790713-5a2e-4b51-dd2c-182b8bc27a5b" }, @@ -660,10 +737,10 @@ "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" + "==PROF== Connected to process 486 (/usr/bin/python3.12)\n", + "==PROF== Profiling \"histogram_localized[abi:v1,cw51cXTLSUwv1sDUaKthoaNgqamjgOR3W3Cw6igA9duC0hkwnNGiHEkYkrqQBFBTDEwCyQe21eqwcFW3UoDGjzpQEsgzrtUEAA_3d_3d]\": 0%....50%....100% - 44 passes\n", + "==PROF== Disconnected from process 486\n", + "==PROF== Report: /accelerated-computing-hub/tutorials/accelerated-python/notebooks/kernels/solutions/histogram_localized.ncu-rep\n" ] } ], @@ -674,7 +751,7 @@ }, { "cell_type": "code", - "execution_count": 13, + "execution_count": 12, "id": "114e8ff7-b6fb-42ad-abda-f6d53479c052", "metadata": { "colab": { @@ -716,23 +793,17 @@ "fd8251af0c4743e29b89a76a73a27af4" ] }, + "execution": { + "iopub.execute_input": "2026-03-09T19:43:17.306084Z", + "iopub.status.busy": "2026-03-09T19:43:17.305764Z", + "iopub.status.idle": "2026-03-09T19:43:17.384239Z", + "shell.execute_reply": "2026-03-09T19:43:17.382943Z", + "shell.execute_reply.started": "2026-03-09T19:43:17.306044Z" + }, "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" - }, { "data": { "text/html": [ @@ -787,7 +858,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "72eba4c5f11b49fb89df581268cc1ddc", + "model_id": "5f0096f2398a4d9a8acaec8a5d434344", "version_major": 2, "version_minor": 0 }, @@ -801,7 +872,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "29f54431514146cea6e08d352058ccfd", + "model_id": "5d54028815a74c27b703e31417de3b05", "version_major": 2, "version_minor": 0 }, @@ -831,12 +902,19 @@ }, { "cell_type": "code", - "execution_count": 14, + "execution_count": 13, "id": "2a3f9ca4-b61b-4536-9896-7a41498cc986", "metadata": { "colab": { "base_uri": "https://localhost:8080/" }, + "execution": { + "iopub.execute_input": "2026-03-09T19:43:17.385448Z", + "iopub.status.busy": "2026-03-09T19:43:17.385083Z", + "iopub.status.idle": "2026-03-09T19:43:23.549772Z", + "shell.execute_reply": "2026-03-09T19:43:23.548634Z", + "shell.execute_reply.started": "2026-03-09T19:43:17.385418Z" + }, "id": "2a3f9ca4-b61b-4536-9896-7a41498cc986", "outputId": "08bb2e07-1dda-4cf1-c2d3-7125d575cd8f" }, @@ -845,9 +923,9 @@ "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" + "histogram_global: 4.15 ms ± 0.60% (mean ± relative stdev of 15 runs)\n", + "histogram_localized: 0.163 ms ± 7.60% (mean ± relative stdev of 15 runs)\n", + "histogram_localized speedup over histogram_global: 25.46\n" ] } ], diff --git a/tutorials/accelerated-python/notebooks/kernels/solutions/43__kernel_authoring__black_and_white__SOLUTION.ipynb b/tutorials/accelerated-python/notebooks/kernels/solutions/43__kernel_authoring__black_and_white__SOLUTION.ipynb index 8da49492..349b7997 100644 --- a/tutorials/accelerated-python/notebooks/kernels/solutions/43__kernel_authoring__black_and_white__SOLUTION.ipynb +++ b/tutorials/accelerated-python/notebooks/kernels/solutions/43__kernel_authoring__black_and_white__SOLUTION.ipynb @@ -9,7 +9,9 @@ "\n", "Now that we've had a look at multi-dimensional indexing, why don't you try and use two-dimensional indexing to make our image black and white?\n", "\n", - "Instead of operating over all pixels channel by channel we want to just operate over all pixels and average the channels out." + "Instead of operating over all pixels channel by channel we want to just operate over all pixels and average the channels out.\n", + "\n", + "Before you begin, please turn off Google Colab's autocompletion by going to the settings gear in the top right -> Editor -> Uncheck \"Automatically trigger code completions\"." ] }, { diff --git a/tutorials/accelerated-python/notebooks/libraries/20__cudf__nyc_parking_violations.ipynb b/tutorials/accelerated-python/notebooks/libraries/20__cudf__nyc_parking_violations.ipynb index 7c8bc88a..15508907 100644 --- a/tutorials/accelerated-python/notebooks/libraries/20__cudf__nyc_parking_violations.ipynb +++ b/tutorials/accelerated-python/notebooks/libraries/20__cudf__nyc_parking_violations.ipynb @@ -87,8 +87,8 @@ "metadata": {}, "outputs": [], "source": [ - "import pandas as pd\n", "import numpy as np\n", + "import pandas as pd\n", "import cudf" ] }, @@ -632,6 +632,8 @@ "metadata": {}, "outputs": [], "source": [ + "%%time\n", + "\n", "# TODO: Filter for TAXI\n", "# taxi_df = ...\n", "\n", diff --git a/tutorials/accelerated-python/notebooks/libraries/23__cuda_cccl__customizing_algorithms.ipynb b/tutorials/accelerated-python/notebooks/libraries/23__cuda_cccl__customizing_algorithms.ipynb index e1215d01..cc1effb2 100644 --- a/tutorials/accelerated-python/notebooks/libraries/23__cuda_cccl__customizing_algorithms.ipynb +++ b/tutorials/accelerated-python/notebooks/libraries/23__cuda_cccl__customizing_algorithms.ipynb @@ -67,7 +67,7 @@ "source": [ "The [CUDA Core Compute Libraries (CCCL)](https://nvidia.github.io/cccl/unstable/python/) provide high-quality, high-performance abstractions for CUDA development in Python. The `cuda-cccl` Python package is composed of two indepdendent subpackages:\n", "\n", - "* `cuda.compute` is a **parallel algorithms library** containing algorithms like `reduce`, `transform`, `scan` and `sort`. These can be combined to implement more complex algorithms, while delivering the performance of hand-optimized CUDA kernels, portable across different GPU architectures. They are general-purpose and **designed to be used with CuPy, PyTorch and other array/tensor frameworks.**.\n", + "* `cuda.compute` is a **parallel algorithms library** containing algorithms like `reduce()`, `transform()`, `scan()` and `sort()`. These can be combined to implement more complex algorithms, while delivering the performance of hand-optimized CUDA kernels, portable across different GPU architectures. They are general-purpose and **designed to be used with CuPy, PyTorch and other array/tensor frameworks.**.\n", "\n", "* `cuda.coop` is a lower-level library containing **cooperative algorithms meant to be used within (numba) CUDA kernels**. Examples include _block-wide reduction_ and _warp-wide scan_, providing numba CUDA kernel developers with building blocks to create speed-of-light, custom kernels." ] @@ -461,8 +461,8 @@ " d_out = cp.empty(1, dtype=np.int32)\n", " h_init = np.array([0], dtype=np.int32)\n", "\n", - " cccl_result = cpx.profiler.benchmark(evens_sum_cccl, (d_input, d_out, h_init), n_repeat=1000)\n", - " cp_result = cpx.profiler.benchmark(evens_sum_cupy, (d_input, d_out, h_init), n_repeat=1000)\n", + " cccl_result = cpx.profiler.benchmark(evens_sum_cccl, (d_input, d_out, h_init), n_repeat=1000, n_warmup=1)\n", + " cp_result = cpx.profiler.benchmark(evens_sum_cupy, (d_input, d_out, h_init), n_repeat=1000, n_warmup=1)\n", "\n", " cccl_times.append(cccl_result.gpu_times.mean())\n", " cp_times.append(cp_result.gpu_times.mean())\n", @@ -717,7 +717,7 @@ "tags": [] }, "source": [ - "In this excercise, you'll use `merge_sort` with a custom comparator function to sort elements by the last digit.\n", + "In this exercise, you'll use `merge_sort` with a custom comparator function to sort elements by the last digit.\n", "For example, $[29, 9, 136, 1001, 72, 24, 32, 1] \\rightarrow [1001, 1, 72, 32, 24, 136, 29, 9]$." ] }, @@ -1070,7 +1070,7 @@ "id": "514e4183-fc23-4355-8ad4-7b9989c9eaf4", "metadata": {}, "source": [ - "A `CountingIterator` represents the sequence `a, a + 1, a + 2, a + 3,.... `. In the following example, we use a `CountingIterator` as the input to `reduce_into` to compute the sum $1 + 2 + 3 + 4 + 5 = 15$." + "A `CountingIterator` represents the sequence `a, a + 1, a + 2, a + 3,.... `. In the following example, we use a `CountingIterator` as the input to `reduce_into()` to compute the sum $1 + 2 + 3 + 4 + 5 = 15$." ] }, { @@ -1258,7 +1258,7 @@ "id": "e0cf37c9-5d7e-4b2d-a619-e4bd8dbeab46", "metadata": {}, "source": [ - "In the example below, we compute the `min` and `max` of a sequence within a single call to `reduce_into`, using `ZipIterator`. Note the need to define `MinMax` to specify the output type of `minmax_op`." + "In the example below, we compute the `min` and `max` of a sequence within a single call to `reduce_into()`, using `ZipIterator`. Note the need to define `MinMax` to specify the output type of `minmax_op`." ] }, { @@ -1398,7 +1398,7 @@ "# Perform the reduction.\n", "comp.inclusive_scan(it_input, it_output, reduce_op, h_init, len(d_input))\n", "\n", - "print(d_input)\n", + "print(f\"Input sequence: {d_input}\")\n", "\n", "h_input = d_input.get()\n", "expected = h_input.cumsum() / np.arange(1, len(h_input) + 1)\n", diff --git a/tutorials/accelerated-python/notebooks/libraries/28__pynvml.ipynb b/tutorials/accelerated-python/notebooks/libraries/28__pynvml.ipynb index 8c3780c8..240323b1 100644 --- a/tutorials/accelerated-python/notebooks/libraries/28__pynvml.ipynb +++ b/tutorials/accelerated-python/notebooks/libraries/28__pynvml.ipynb @@ -145,7 +145,7 @@ "metadata": {}, "outputs": [], "source": [ - "[pynvml.nvmlDeviceGetMemoryInfo(gpu).used / 1e9 for gpu in gpus]" + "[pynvml.nvmlDeviceGetMemoryInfo(gpu).used / 2**30 for gpu in gpus]" ] }, { @@ -155,7 +155,7 @@ "metadata": {}, "outputs": [], "source": [ - "[pynvml.nvmlDeviceGetMemoryInfo(gpu).free / 1e9 for gpu in gpus]" + "[pynvml.nvmlDeviceGetMemoryInfo(gpu).free / 2**30 for gpu in gpus]" ] }, { diff --git a/tutorials/accelerated-python/notebooks/libraries/solutions/20__cudf__nyc_parking_violations__SOLUTION.ipynb b/tutorials/accelerated-python/notebooks/libraries/solutions/20__cudf__nyc_parking_violations__SOLUTION.ipynb index 158c8eb8..de6d3ae5 100644 --- a/tutorials/accelerated-python/notebooks/libraries/solutions/20__cudf__nyc_parking_violations__SOLUTION.ipynb +++ b/tutorials/accelerated-python/notebooks/libraries/solutions/20__cudf__nyc_parking_violations__SOLUTION.ipynb @@ -82,9 +82,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "55729354", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:46:52.973044Z", + "iopub.status.busy": "2026-03-09T19:46:52.972837Z", + "iopub.status.idle": "2026-03-09T19:46:56.162204Z", + "shell.execute_reply": "2026-03-09T19:46:56.161128Z", + "shell.execute_reply.started": "2026-03-09T19:46:52.973020Z" + } + }, "outputs": [], "source": [ "import numpy as np\n", @@ -112,10 +120,87 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "88ee8eb6", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:46:56.163862Z", + "iopub.status.busy": "2026-03-09T19:46:56.163476Z", + "iopub.status.idle": "2026-03-09T19:46:56.186058Z", + "shell.execute_reply": "2026-03-09T19:46:56.184751Z", + "shell.execute_reply.started": "2026-03-09T19:46:56.163839Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Max value in series: 30\n", + "Columns: Index(['a', 'b', 'c'], dtype='object')\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
abc
1113
2243
3173
\n", + "
" + ], + "text/plain": [ + " a b c\n", + "1 1 1 3\n", + "2 2 4 3\n", + "3 1 7 3" + ] + }, + "execution_count": 2, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# A Series acts like a single column of data\n", "s = pd.Series([10, 20, 30])\n", @@ -153,10 +238,79 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "58fde563", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:46:56.187292Z", + "iopub.status.busy": "2026-03-09T19:46:56.186939Z", + "iopub.status.idle": "2026-03-09T19:46:56.201209Z", + "shell.execute_reply": "2026-03-09T19:46:56.199800Z", + "shell.execute_reply.started": "2026-03-09T19:46:56.187260Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
abc
2243
4324
5205
\n", + "
" + ], + "text/plain": [ + " a b c\n", + "2 2 4 3\n", + "4 3 2 4\n", + "5 2 0 5" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Select specific columns\n", "subset = df[[\"b\", \"c\"]]\n", @@ -186,10 +340,93 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "67c7ea6f", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:46:56.201999Z", + "iopub.status.busy": "2026-03-09T19:46:56.201778Z", + "iopub.status.idle": "2026-03-09T19:46:56.212839Z", + "shell.execute_reply": "2026-03-09T19:46:56.211848Z", + "shell.execute_reply.started": "2026-03-09T19:46:56.201981Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
abc
1113
3173
2243
5205
4324
\n", + "
" + ], + "text/plain": [ + " a b c\n", + "1 1 1 3\n", + "3 1 7 3\n", + "2 2 4 3\n", + "5 2 0 5\n", + "4 3 2 4" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Sort by column 'a' in ascending order\n", "sorted_df = df.sort_values(\"a\")\n", @@ -214,10 +451,37 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 5, "id": "76137b51", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:46:56.213759Z", + "iopub.status.busy": "2026-03-09T19:46:56.213535Z", + "iopub.status.idle": "2026-03-09T19:46:56.221240Z", + "shell.execute_reply": "2026-03-09T19:46:56.219811Z", + "shell.execute_reply.started": "2026-03-09T19:46:56.213740Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Sum of columns:\n", + " a 9\n", + "b 14\n", + "c 18\n", + "dtype: int64\n", + "\n", + "Value counts for 'a':\n", + " a\n", + "1 2\n", + "2 2\n", + "3 1\n", + "Name: count, dtype: int64\n" + ] + } + ], "source": [ "# Calculate the sum of every column\n", "print(\"Sum of columns:\\n\", df.sum())\n", @@ -244,10 +508,107 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 6, "id": "ed2cd6c6", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:46:56.222240Z", + "iopub.status.busy": "2026-03-09T19:46:56.221937Z", + "iopub.status.idle": "2026-03-09T19:46:56.242837Z", + "shell.execute_reply": "2026-03-09T19:46:56.241543Z", + "shell.execute_reply.started": "2026-03-09T19:46:56.222207Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " b c\n", + "a \n", + "1 4.0 3.0\n", + "2 2.0 4.0\n", + "3 2.0 4.0\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
bc
minmeanmax
a
114.03
202.05
322.04
\n", + "
" + ], + "text/plain": [ + " b c\n", + " min mean max\n", + "a \n", + "1 1 4.0 3\n", + "2 0 2.0 5\n", + "3 2 2.0 4" + ] + }, + "execution_count": 6, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Group by 'a' and calculate the mean of 'b' and 'c' for each group\n", "grouped_mean = df.groupby(\"a\").mean()\n", @@ -279,10 +640,105 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 7, "id": "099a4966", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:46:56.243846Z", + "iopub.status.busy": "2026-03-09T19:46:56.243592Z", + "iopub.status.idle": "2026-03-09T19:46:56.253898Z", + "shell.execute_reply": "2026-03-09T19:46:56.253020Z", + "shell.execute_reply.started": "2026-03-09T19:46:56.243821Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
abcnamesnames_upper
1113marioMARIO
2243luigiLUIGI
3173yoshiYOSHI
4324peachPEACH
5205toadTOAD
\n", + "
" + ], + "text/plain": [ + " a b c names names_upper\n", + "1 1 1 3 mario MARIO\n", + "2 2 4 3 luigi LUIGI\n", + "3 1 7 3 yoshi YOSHI\n", + "4 3 2 4 peach PEACH\n", + "5 2 0 5 toad TOAD" + ] + }, + "execution_count": 7, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Add a string column\n", "df[\"names\"] = [\"mario\", \"luigi\", \"yoshi\", \"peach\", \"toad\"]\n", @@ -310,10 +766,93 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, "id": "450b267f", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:46:56.255523Z", + "iopub.status.busy": "2026-03-09T19:46:56.255179Z", + "iopub.status.idle": "2026-03-09T19:46:56.271865Z", + "shell.execute_reply": "2026-03-09T19:46:56.270911Z", + "shell.execute_reply.started": "2026-03-09T19:46:56.255494Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
datevalueyear
02018-11-200.0368142018
12018-11-210.5508212018
22018-11-220.4316212018
32018-11-230.7665972018
42018-11-240.6944132018
\n", + "
" + ], + "text/plain": [ + " date value year\n", + "0 2018-11-20 0.036814 2018\n", + "1 2018-11-21 0.550821 2018\n", + "2 2018-11-22 0.431621 2018\n", + "3 2018-11-23 0.766597 2018\n", + "4 2018-11-24 0.694413 2018" + ] + }, + "execution_count": 8, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Create a date range\n", "date_df = pd.DataFrame()\n", @@ -344,10 +883,81 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 9, "id": "85562e0d", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:46:56.272825Z", + "iopub.status.busy": "2026-03-09T19:46:56.272610Z", + "iopub.status.idle": "2026-03-09T19:46:56.281506Z", + "shell.execute_reply": "2026-03-09T19:46:56.280393Z", + "shell.execute_reply.started": "2026-03-09T19:46:56.272806Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
abcnamesnames_uppera_plus_10
1113marioMARIO11
2243luigiLUIGI12
\n", + "
" + ], + "text/plain": [ + " a b c names names_upper a_plus_10\n", + "1 1 1 3 mario MARIO 11\n", + "2 2 4 3 luigi LUIGI 12" + ] + }, + "execution_count": 9, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "def add_ten(x):\n", " return x + 10\n", @@ -373,10 +983,88 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 10, "id": "c860f414", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:46:56.282466Z", + "iopub.status.busy": "2026-03-09T19:46:56.282221Z", + "iopub.status.idle": "2026-03-09T19:46:56.490328Z", + "shell.execute_reply": "2026-03-09T19:46:56.489249Z", + "shell.execute_reply.started": "2026-03-09T19:46:56.282447Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
bc
a
22.05.0
14.04.5
32.02.0
\n", + "
" + ], + "text/plain": [ + " b c\n", + "a \n", + "2 2.0 5.0\n", + "1 4.0 4.5\n", + "3 2.0 2.0" + ] + }, + "execution_count": 10, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Create a cuDF DataFrame (data resides on GPU)\n", "gdf = cudf.DataFrame({\n", @@ -402,10 +1090,26 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 11, "id": "295a312a", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:46:56.491667Z", + "iopub.status.busy": "2026-03-09T19:46:56.491334Z", + "iopub.status.idle": "2026-03-09T19:46:56.496816Z", + "shell.execute_reply": "2026-03-09T19:46:56.495742Z", + "shell.execute_reply.started": "2026-03-09T19:46:56.491638Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Error caught: Error parsing datetime string \"11/20/2018\" at position 2\n" + ] + } + ], "source": [ "# EXECUTE THIS CELL TO SEE THE ERROR\n", "try:\n", @@ -446,9 +1150,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 12, "id": "df7c10ce", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:46:56.497776Z", + "iopub.status.busy": "2026-03-09T19:46:56.497564Z", + "iopub.status.idle": "2026-03-09T19:46:56.502111Z", + "shell.execute_reply": "2026-03-09T19:46:56.500958Z", + "shell.execute_reply.started": "2026-03-09T19:46:56.497758Z" + } + }, "outputs": [], "source": [ "# A function that looks innocent but is NOT GPU-safe\n", @@ -476,10 +1188,27 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 13, "id": "56ece39b", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:46:56.503541Z", + "iopub.status.busy": "2026-03-09T19:46:56.503235Z", + "iopub.status.idle": "2026-03-09T19:46:56.783499Z", + "shell.execute_reply": "2026-03-09T19:46:56.782281Z", + "shell.execute_reply.started": "2026-03-09T19:46:56.503511Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "cuDF apply() constraint caught:\n", + "user defined function compilation failed.\n" + ] + } + ], "source": [ "# Execute this cell to observe the cuDF limitation\n", "try:\n", @@ -501,9 +1230,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 14, "id": "c8165725", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:46:56.784597Z", + "iopub.status.busy": "2026-03-09T19:46:56.784329Z", + "iopub.status.idle": "2026-03-09T19:46:57.393699Z", + "shell.execute_reply": "2026-03-09T19:46:57.392565Z", + "shell.execute_reply.started": "2026-03-09T19:46:56.784576Z" + } + }, "outputs": [], "source": [ "# GPU-safe version: no Python, no branching, pure math\n", @@ -563,9 +1300,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 15, "id": "714999f6", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:46:57.394963Z", + "iopub.status.busy": "2026-03-09T19:46:57.394666Z", + "iopub.status.idle": "2026-03-09T19:46:59.440630Z", + "shell.execute_reply": "2026-03-09T19:46:59.439175Z", + "shell.execute_reply.started": "2026-03-09T19:46:57.394935Z" + } + }, "outputs": [], "source": [ "![ -f nyc_parking_violations_2022.parquet ] || curl -fsSL -o nyc_parking_violations_2022.parquet https://data.rapids.ai/datasets/nyc_parking/nyc_parking_violations_2022.parquet" @@ -590,10 +1335,120 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 16, "id": "b7aa90d6", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:46:59.442194Z", + "iopub.status.busy": "2026-03-09T19:46:59.441858Z", + "iopub.status.idle": "2026-03-09T19:47:23.158734Z", + "shell.execute_reply": "2026-03-09T19:47:23.156437Z", + "shell.execute_reply.started": "2026-03-09T19:46:59.442165Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Index(['Summons Number', 'Plate ID', 'Registration State', 'Plate Type',\n", + " 'Issue Date', 'Violation Code', 'Vehicle Body Type', 'Vehicle Make',\n", + " 'Issuing Agency', 'Street Code1', 'Street Code2', 'Street Code3',\n", + " 'Vehicle Expiration Date', 'Violation Location', 'Violation Precinct',\n", + " 'Issuer Precinct', 'Issuer Code', 'Issuer Command', 'Issuer Squad',\n", + " 'Violation Time', 'Time First Observed', 'Violation County',\n", + " 'Violation In Front Of Or Opposite', 'House Number', 'Street Name',\n", + " 'Intersecting Street', 'Date First Observed', 'Law Section',\n", + " 'Sub Division', 'Violation Legal Code', 'Days Parking In Effect',\n", + " 'From Hours In Effect', 'To Hours In Effect', 'Vehicle Color',\n", + " 'Unregistered Vehicle?', 'Vehicle Year', 'Meter Number',\n", + " 'Feet From Curb', 'Violation Post Code', 'Violation Description',\n", + " 'No Standing or Stopping Violation', 'Hydrant Violation',\n", + " 'Double Parking Violation'],\n", + " dtype='object')\n" + ] + }, + { + "data": { + "text/html": [ + "
\n", + "\n", + "\n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + " \n", + "
Registration StateViolation DescriptionVehicle Body TypeIssue Date
0NY<NA>VAN06/25/2021
1NY<NA>SUBN06/25/2021
2TX<NA>SDN06/17/2021
3MO<NA>SDN06/16/2021
4NY<NA>TAXI07/04/2021
\n", + "
" + ], + "text/plain": [ + " Registration State Violation Description Vehicle Body Type Issue Date\n", + "0 NY VAN 06/25/2021\n", + "1 NY SUBN 06/25/2021\n", + "2 TX SDN 06/17/2021\n", + "3 MO SDN 06/16/2021\n", + "4 NY TAXI 07/04/2021" + ] + }, + "execution_count": 16, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "# Read parquet file\n", "df = pd.read_parquet(\"nyc_parking_violations_2022.parquet\")\n", @@ -627,10 +1482,57 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 17, "id": "b976401b", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:47:23.161004Z", + "iopub.status.busy": "2026-03-09T19:47:23.160235Z", + "iopub.status.idle": "2026-03-09T19:47:24.364711Z", + "shell.execute_reply": "2026-03-09T19:47:24.363549Z", + "shell.execute_reply.started": "2026-03-09T19:47:23.160966Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 1.16 s, sys: 25.7 ms, total: 1.19 s\n", + "Wall time: 1.19 s\n" + ] + }, + { + "data": { + "text/plain": [ + "Registration State\n", + "NY 43809\n", + "99 19\n", + "NJ 17\n", + "PA 15\n", + "FL 8\n", + "ME 7\n", + "TX 6\n", + "CA 5\n", + "CT 4\n", + "MI 4\n", + "MA 4\n", + "VA 2\n", + "IN 2\n", + "MS 1\n", + "OK 1\n", + "TN 1\n", + "RI 1\n", + "VT 1\n", + "WA 1\n", + "dtype: int64" + ] + }, + "execution_count": 17, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "%%time\n", "\n", @@ -662,10 +1564,57 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 18, "id": "48f6b1fc", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:47:24.365760Z", + "iopub.status.busy": "2026-03-09T19:47:24.365415Z", + "iopub.status.idle": "2026-03-09T19:47:27.301611Z", + "shell.execute_reply": "2026-03-09T19:47:27.300326Z", + "shell.execute_reply.started": "2026-03-09T19:47:24.365728Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU times: user 401 ms, sys: 663 ms, total: 1.06 s\n", + "Wall time: 2.93 s\n" + ] + }, + { + "data": { + "text/plain": [ + "Registration State\n", + "NY 43809\n", + "99 19\n", + "NJ 17\n", + "PA 15\n", + "FL 8\n", + "ME 7\n", + "TX 6\n", + "CA 5\n", + "MI 4\n", + "MA 4\n", + "CT 4\n", + "IN 2\n", + "VA 2\n", + "WA 1\n", + "MS 1\n", + "TN 1\n", + "OK 1\n", + "VT 1\n", + "RI 1\n", + "dtype: int64" + ] + }, + "execution_count": 18, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ "%%time\n", "\n", diff --git a/tutorials/accelerated-python/notebooks/libraries/solutions/23__cuda_cccl__customizing_algorithms__SOLUTION.ipynb b/tutorials/accelerated-python/notebooks/libraries/solutions/23__cuda_cccl__customizing_algorithms__SOLUTION.ipynb index d7725a94..9c432280 100644 --- a/tutorials/accelerated-python/notebooks/libraries/solutions/23__cuda_cccl__customizing_algorithms__SOLUTION.ipynb +++ b/tutorials/accelerated-python/notebooks/libraries/solutions/23__cuda_cccl__customizing_algorithms__SOLUTION.ipynb @@ -10,9 +10,17 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 1, "id": "bb956539", - "metadata": {}, + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:46:15.377241Z", + "iopub.status.busy": "2026-03-09T19:46:15.377030Z", + "iopub.status.idle": "2026-03-09T19:46:16.422774Z", + "shell.execute_reply": "2026-03-09T19:46:16.421599Z", + "shell.execute_reply.started": "2026-03-09T19:46:15.377221Z" + } + }, "outputs": [], "source": [ "import os\n", @@ -36,15 +44,31 @@ "id": "bdfa6bb2-3305-424c-bd71-f6ba77d1f61e", "metadata": {}, "source": [ - "### Excercise: computing the minimum value" + "### Exercise: computing the minimum value" ] }, { "cell_type": "code", - "execution_count": null, + "execution_count": 2, "id": "cd688543-5d38-4781-b2ff-cc6decb13e7a", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:46:16.424195Z", + "iopub.status.busy": "2026-03-09T19:46:16.423743Z", + "iopub.status.idle": "2026-03-09T19:46:22.868536Z", + "shell.execute_reply": "2026-03-09T19:46:22.867230Z", + "shell.execute_reply.started": "2026-03-09T19:46:16.424163Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Min reduction result: -6\n" + ] + } + ], "source": [ "\"\"\"\n", "Using `reduce_into()` to compute the minimum value of a sequence\n", @@ -53,7 +77,6 @@ "d_input = cp.array([-2, 3, 5, 1, 7, -6, 8, -4], dtype=np.int32)\n", "d_output = cp.empty(1, dtype=np.int32)\n", "\n", - "\n", "# begin TODO\n", "MAX_INT = np.iinfo(np.int32).max\n", "h_init = np.asarray([MAX_INT], dtype=np.int32)\n", @@ -76,10 +99,26 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 3, "id": "729d0fc6-ede4-43b3-98dd-54e9f65afbac", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:46:22.869547Z", + "iopub.status.busy": "2026-03-09T19:46:22.869272Z", + "iopub.status.idle": "2026-03-09T19:46:27.809728Z", + "shell.execute_reply": "2026-03-09T19:46:27.808515Z", + "shell.execute_reply.started": "2026-03-09T19:46:22.869525Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Result: [1001 1 72 32 24 136 29 9]\n" + ] + } + ], "source": [ "# Prepare the input and output arrays.\n", "d_in_keys = cp.asarray([29, 9, 136, 1001, 72, 24, 32, 1], dtype=\"int32\")\n", @@ -113,10 +152,28 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 4, "id": "3c81a123-0287-4a51-9e86-fcff1916445f", - "metadata": {}, - "outputs": [], + "metadata": { + "execution": { + "iopub.execute_input": "2026-03-09T19:46:27.810859Z", + "iopub.status.busy": "2026-03-09T19:46:27.810601Z", + "iopub.status.idle": "2026-03-09T19:46:32.756053Z", + "shell.execute_reply": "2026-03-09T19:46:32.754860Z", + "shell.execute_reply.started": "2026-03-09T19:46:27.810837Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Input sequence: [2. 3. 5. 1. 7. 6. 8. 4.]\n", + "Running average result: [2. 2.5 3.3333333 2.75 3.6 4. 4.571429\n", + " 4.5 ]\n" + ] + } + ], "source": [ "@comp.gpu_struct\n", "class SumAndCount:\n", @@ -136,10 +193,8 @@ "d_output = cp.empty(len(d_input), dtype=np.float32)\n", "h_init = SumAndCount(0, 0)\n", "\n", - "# begin TODO\n", "it_input = comp.ZipIterator(d_input, comp.ConstantIterator(np.int32(1)))\n", "it_output = comp.TransformOutputIterator(d_output, compute_running_average)\n", - "# end TODO\n", "\n", "# Perform the reduction.\n", "comp.inclusive_scan(it_input, it_output, reduce_op, h_init, len(d_input))\n", diff --git a/tutorials/nvmath-python/notebooks/01_kernel_fusion.ipynb b/tutorials/nvmath-python/notebooks/01_kernel_fusion.ipynb index 812b2c47..a41c5e13 100644 --- a/tutorials/nvmath-python/notebooks/01_kernel_fusion.ipynb +++ b/tutorials/nvmath-python/notebooks/01_kernel_fusion.ipynb @@ -37,8 +37,8 @@ "\n", "**Learning Objectives:**\n", "* Understand how **nvmath-python** coexists with array libraries like [NumPy](https://numpy.org/), [CuPy](https://cupy.dev/), and [PyTorch](https://pytorch.org/)\n", - "* Apply `nvmath.linalg.advanced.matmul` to perform matrix multiplication operations\n", - "* Benchmark GPU codes using `cupyx.profiler.benchmark` for accurate performance measurements\n", + "* Apply `nvmath.linalg.advanced.matmul()` to perform matrix multiplication operations\n", + "* Benchmark GPU codes using `cupyx.profiler.benchmark()` for accurate performance measurements\n", "* Explain the performance benefits of kernel fusion in composite operations\n", "* Use NVIDIA Nsight profiling tools to visualize kernel execution\n", "* Experiment with different matrix sizes to examine their effect on kernel fusion benefits" @@ -224,11 +224,11 @@ "metadata": {}, "source": [ "---\n", - "## Benchmarking GPU Codes with `cupyx.profiler.benchmark`\n", + "## Benchmarking GPU Codes with `cupyx.profiler.benchmark()`\n", "\n", "Since GPU kernels are launched asynchronously, a host call may return before the device work finishes. Naive timing methods (such as Python's `time.time()` method or Jupyter's `%%timeit` magic) measure only host-side overhead, not true device execution time. \n", "\n", - "The `cupyx.profiler.benchmark` function uses CUDA events, proper synchronization, warm-ups, and repeated runs to produce stable, device-level timing measurements. It removes much of the noise introduced by Python overhead, one-time setup costs, takes into account the asynchronous execution, and reports aggregated statistics so you get reproducible, comparable numbers.\n", + "The `cupyx.profiler.benchmark()` function uses CUDA events, proper synchronization, warm-ups, and repeated runs to produce stable, device-level timing measurements. It removes much of the noise introduced by Python overhead, one-time setup costs, takes into account the asynchronous execution, and reports aggregated statistics so you get reproducible, comparable numbers.\n", "\n", "Below is a helper function that benchmarks implementations and optionally compares them to report speedup/slowdown:" ] @@ -284,7 +284,7 @@ "\n", "- Make sure data is already allocated on the device before benchmarking (transfer costs should be excluded unless you intend to measure them).\n", "- Run several repeats and inspect distributions (median is usually more robust than min or mean).\n", - "- For end-to-end profiling (memory transfers, kernel launches, kernel internals), use NVIDIA tools like `nsys`, `nvprof`, or Nsight Systems. The `cupyx.profiler.benchmark` function focuses on accurate kernel timing within Python workflows.\n", + "- For end-to-end profiling (memory transfers, kernel launches, kernel internals), use NVIDIA tools like `nsys`, `nvprof`, or Nsight Systems. The `cupyx.profiler.benchmark()` function focuses on accurate kernel timing within Python workflows.\n", "- Watch out for GPU power/clock state and thermal throttling; for stable numbers, use consistent GPU governor/clock settings if available." ] }, @@ -595,7 +595,7 @@ "- **nvmath-python** is not an array library but rather a mathematical operations library that coexists with existing array libraries.\n", "- Kernel fusion combines multiple operations into a single GPU kernel, dramatically improving performance by reducing memory bandwidth requirements and kernel launch overhead.\n", "- For simple operations like matrix multiplication, **nvmath-python** and CuPy perform similarly since both use cuBLAS. However, for composite operations like GEMM, **nvmath-python** shows significant speedups through kernel fusion.\n", - "- Proper benchmarking using `cupyx.profiler.benchmark` is essential for accurate performance measurements on GPUs.\n", + "- Proper benchmarking using `cupyx.profiler.benchmark()` is essential for accurate performance measurements on GPUs.\n", "- NVIDIA Nsight profiling tools provide deep insights into kernel execution and help visualize the benefits of kernel fusion.\n", "\n", "**Next Steps:**\n", diff --git a/tutorials/nvmath-python/notebooks/02_mem_exec_spaces.ipynb b/tutorials/nvmath-python/notebooks/02_mem_exec_spaces.ipynb index eb34e82e..26a7d2bb 100644 --- a/tutorials/nvmath-python/notebooks/02_mem_exec_spaces.ipynb +++ b/tutorials/nvmath-python/notebooks/02_mem_exec_spaces.ipynb @@ -193,9 +193,9 @@ "\n", "The difference is noticeable, but where does the cost come from? The answer lies in understanding *specialized APIs* versus *generic APIs*.\n", "\n", - "`nvmath.linalg.advanced.matmul` belongs to a category of *specialized APIs*. In contrast to *generic APIs* such as `nvmath.fft.fft`, specialized APIs serve very specific needs, which comes at a cost of generality. Specifically, `nvmath.linalg.advanced.matmul` supports **GPU execution space only**. \n", + "`nvmath.linalg.advanced.matmul()` belongs to a category of *specialized APIs*. In contrast to *generic APIs* such as `nvmath.fft.fft()`, specialized APIs serve very specific needs, which comes at a cost of generality. Specifically, `nvmath.linalg.advanced.matmul()` supports **GPU execution space only**. \n", "\n", - "When `nvmath.linalg.advanced.matmul` receives CPU tensor inputs, it:\n", + "When `nvmath.linalg.advanced.matmul()` receives CPU tensor inputs, it:\n", "1. Copies the inputs from CPU memory to GPU memory (the execution space)\n", "2. Performs the operation on the GPU\n", "3. Copies the result back from GPU memory to CPU memory (the original memory space)\n", @@ -370,8 +370,8 @@ "**Key Takeaways:**\n", "\n", "- Memory space (where data is stored) and execution space (where computation happens) may differ, leading to data transfer costs.\n", - "- Some specialized APIs, such as `nvmath.linalg.advanced.matmul`, only support GPU execution, automatically transferring CPU data to GPU with associated overhead.\n", - "- Generic APIs like `nvmath.fft.fft` adapt to input location: CPU inputs execute on CPU, GPU inputs execute on GPU.\n", + "- Some specialized APIs, such as `nvmath.linalg.advanced.matmul()`, only support GPU execution, automatically transferring CPU data to GPU with associated overhead.\n", + "- Generic APIs like `nvmath.fft.fft()` adapt to input location: CPU inputs execute on CPU, GPU inputs execute on GPU.\n", "- Use **nvmath-python**'s logging mechanism to understand internal operations and identify potential bottlenecks.\n", "\n", "---\n", @@ -381,8 +381,8 @@ "\n", "**Key Takeaways:**\n", "- Memory space and execution space are distinct concepts that may not always align, potentially causing implicit data transfers.\n", - "- Specialized APIs (like `nvmath.linalg.advanced.matmul`) only support GPU execution, requiring data transfers when CPU inputs are provided.\n", - "- Generic APIs (like `nvmath.fft.fft`) adapt their execution space to match the input memory space, avoiding unnecessary transfers.\n", + "- Specialized APIs (like `nvmath.linalg.advanced.matmul()`) only support GPU execution, requiring data transfers when CPU inputs are provided.\n", + "- Generic APIs (like `nvmath.fft.fft()`) adapt their execution space to match the input memory space, avoiding unnecessary transfers.\n", "- The **nvmath-python** logging mechanism provides valuable insights into internal operations and helps identify performance bottlenecks.\n", "- Data transfer between CPU and GPU can significantly impact performance and should be minimized in production code.\n", "\n", diff --git a/tutorials/nvmath-python/notebooks/04_callbacks.ipynb b/tutorials/nvmath-python/notebooks/04_callbacks.ipynb index 7dc58f99..ffeb2c01 100644 --- a/tutorials/nvmath-python/notebooks/04_callbacks.ipynb +++ b/tutorials/nvmath-python/notebooks/04_callbacks.ipynb @@ -38,7 +38,7 @@ "**Learning Objectives:**\n", "* Understand what FFT callbacks are and how they work\n", "* Write custom epilog functions for FFT operations\n", - "* Use `nvmath.fft.compile_epilog` to JIT-compile callback functions\n", + "* Use `nvmath.fft.compile_epilog()` to JIT-compile callback functions\n", "* Apply FFT callbacks to a real-world image processing problem (Gaussian filtering)\n", "* Benchmark performance improvements from kernel fusion with callbacks\n", "* Use stateful FFT APIs to amortize JIT compilation costs across multiple executions\n", diff --git a/tutorials/nvmath-python/notebooks/06_sparse_solver.ipynb b/tutorials/nvmath-python/notebooks/06_sparse_solver.ipynb index 20c8d0bf..32a68ff6 100644 --- a/tutorials/nvmath-python/notebooks/06_sparse_solver.ipynb +++ b/tutorials/nvmath-python/notebooks/06_sparse_solver.ipynb @@ -41,7 +41,7 @@ "**Learning Objectives:**\n", "* Understand when and why to use sparse direct solvers for large linear systems\n", "* Download real-world sparse matrices from the [SuiteSparse Matrix Collection](https://sparse.tamu.edu/)\n", - "* Apply `nvmath.sparse.advanced.direct_solver` to solve sparse linear systems\n", + "* Apply `nvmath.sparse.advanced.direct_solver()` to solve sparse linear systems\n", "* Work with sparse matrices in CSR (Compressed Sparse Row) format using CuPy\n", "* Configure solver options for optimal performance using hybrid CPU-GPU execution\n", "* Validate the accuracy of numerical solutions using residual norms" @@ -167,7 +167,7 @@ "1. **Converting to CSR format**: The solver expects matrices in CSR (Compressed Sparse Row) format, which is more efficient for matrix-vector operations than COO format.\n", "2. **Transferring to GPU**: We'll use CuPy to create GPU-resident sparse matrices and vectors.\n", "3. **Defining the right-hand side**: For this example, we'll use a simple vector of ones.\n", - "4. **Solving the system**: Call `nvmath.sparse.advanced.direct_solver` to compute the solution.\n", + "4. **Solving the system**: Call `nvmath.sparse.advanced.direct_solver()` to compute the solution.\n", "5. **Validating the result**: Check the accuracy by computing the residual $ \\|A \\cdot x - b\\| $.\n", "\n", "Let's implement this workflow:\n" @@ -403,7 +403,7 @@ "**Key Takeaways:**\n", "\n", "- **Sparse direct solvers** are essential for efficiently solving linear systems where the coefficient matrix is sparse (mostly zeros).\n", - "- **nvmath-python** provides a high-level interface to NVIDIA's cuDSS library through `nvmath.sparse.advanced.direct_solver`.\n", + "- **nvmath-python** provides a high-level interface to NVIDIA's cuDSS library through `nvmath.sparse.advanced.direct_solver()`.\n", "- The solver seamlessly integrates with Python's scientific computing ecosystem, accepting **SciPy**, **CuPy**, and **PyTorch** sparse matrices.\n", "- **CSR (Compressed Sparse Row)** format is the primary sparse matrix format for the solver, offering efficient storage and computation.\n", "- **Hybrid CPU-GPU execution** can optimize performance by distributing work intelligently between CPU and GPU.\n",