diff --git a/tutorials/pyhpc/notebooks/02__power_iteration__cupy__memory_spaces.ipynb b/tutorials/pyhpc/notebooks/02__power_iteration__cupy__memory_spaces.ipynb index 4efd56ae..3a0645d9 100644 --- a/tutorials/pyhpc/notebooks/02__power_iteration__cupy__memory_spaces.ipynb +++ b/tutorials/pyhpc/notebooks/02__power_iteration__cupy__memory_spaces.ipynb @@ -100,10 +100,10 @@ "# Configuration for the algorithm\n", "@dataclass\n", "class PowerIterationConfig:\n", - " dim: int = 4096 # Matrix size (dim x dim)\n", - " dominance: float = 0.1 # How much larger the top eigenvalue is (controls convergence, higher == faster)\n", - " max_steps: int = 400 # Maximum iterations\n", - " check_frequency: int = 10 # Check for convergence every N steps\n", + " dim: int = 10000 # Matrix size (dim x dim)\n", + " dominance: float = 0.05 # How much larger the top eigenvalue is (controls convergence, higher == faster)\n", + " max_steps: int = 1000 # Maximum iterations\n", + " check_frequency: int = 25 # Check for convergence every N steps\n", " progress: bool = True # Print progress logs\n", " residual_threshold: float = 1e-10 # Stop if error is below this" ] @@ -194,7 +194,7 @@ " break\n", "\n", " # Run intermediate steps without checking residual to save compute.\n", - " for _ in range(cfg.check_frequency - 1):\n", + " for _ in range(i + 1, min(i + cfg.check_frequency, cfg.max_steps)):\n", " y = A @ x\n", " x = y / np.linalg.norm(y)\n", "\n", @@ -274,7 +274,7 @@ " break\n", "\n", " # Run intermediate steps without checking residual to save compute\n", - " for _ in range(cfg.check_frequency - 1):\n", + " for _ in range(i + 1, min(i + cfg.check_frequency, cfg.max_steps)):\n", " y = A @ x\n", " x = y / np.linalg.norm(y)\n", "\n", @@ -476,7 +476,7 @@ "\n", "**Explore the impact of changing the following parameters:**\n", "\n", - "1. **Problem Size (`dim`):** How does the GPU speedup change as you increase or decrease the matrix dimensions? Try values like 1024, 2048, 4096, 8192.\n", + "1. **Problem Size (`dim`):** How does the GPU speedup change as you decrease the matrix dimensions? Try values like 1024, 2048, 4096, 8192.\n", "\n", "2. **Compute Workload (`max_steps` and `dominance`):** The `dominance` parameter controls how quickly the algorithm converges. A smaller dominance means eigenvalues are closer together, requiring more iterations. How does this affect the CPU vs GPU comparison?\n", "\n", @@ -494,7 +494,7 @@ "source": [ "# Try different configurations here!\n", "# Example:\n", - "# cfg_large = PowerIterationConfig(dim=8192, progress=False)\n", + "# cfg_smaller = PowerIterationConfig(dim=8192, progress=False)\n", "# cfg_slow_converge = PowerIterationConfig(dominance=0.01, progress=False)\n", "# cfg_frequent_check = PowerIterationConfig(check_frequency=1, progress=True)\n", "\n", diff --git a/tutorials/pyhpc/notebooks/03__power_iteration__cupy__asynchrony.ipynb b/tutorials/pyhpc/notebooks/03__power_iteration__cupy__asynchrony.ipynb index 17ec1582..0dd90923 100644 --- a/tutorials/pyhpc/notebooks/03__power_iteration__cupy__asynchrony.ipynb +++ b/tutorials/pyhpc/notebooks/03__power_iteration__cupy__asynchrony.ipynb @@ -17,6 +17,7 @@ "5. [Better Visibility with NVTX](#5-Better-Visibility-with-NVTX)\n", "6. [Implementing Asynchrony](#6-Implementing-Asynchrony)\n", "7. [Performance Analysis](#7-Performance-Analysis)\n", + "8. [Balancing CPU I/O and GPU Compute](#8-Balancing-CPU-I/O-and-GPU-Compute)\n", "\n", "### 1. Introduction and Setup\n", "\n", @@ -49,7 +50,10 @@ " open(\"/accelerated-computing-hub-installed\", \"a\").close()\n", " print(\"All packages installed.\")\n", "\n", - "import nsightful" + "import nsightful\n", + "from jupyter_dark_detect import is_dark\n", + "import matplotlib.pyplot as plt\n", + "plt.style.use('dark_background' if is_dark() else 'default')" ] }, { @@ -93,15 +97,16 @@ "import cupy as cp\n", "import cupyx as cpx\n", "import nvtx\n", + "import sys\n", "import time\n", "from dataclasses import dataclass\n", "\n", "@dataclass\n", "class PowerIterationConfig:\n", - " dim: int = 8192\n", + " dim: int = 19000\n", " dominance: float = 0.05\n", - " max_steps: int = 1000\n", - " check_frequency: int = 15\n", + " max_steps: int = 400\n", + " check_frequency: int = 25 if len(sys.argv) < 2 else int(sys.argv[1])\n", " progress: bool = True\n", " residual_threshold: float = 1e-10\n", "\n", @@ -135,7 +140,7 @@ " if res < cfg.residual_threshold:\n", " break\n", "\n", - " for _ in range(cfg.check_frequency - 1):\n", + " for _ in range(i + 1, min(i + cfg.check_frequency, cfg.max_steps)):\n", " y = A_gpu @ x # We have to use `A_gpu` here as well.\n", " x = y / cp.linalg.norm(y) # Normalize for next step.\n", "\n", @@ -284,15 +289,16 @@ "import cupy as cp\n", "import cupyx as cpx\n", "import nvtx\n", + "import sys\n", "import time\n", "from dataclasses import dataclass\n", "\n", "@dataclass\n", "class PowerIterationConfig:\n", - " dim: int = 8192\n", + " dim: int = 19000\n", " dominance: float = 0.05\n", - " max_steps: int = 1000\n", - " check_frequency: int = 15\n", + " max_steps: int = 400\n", + " check_frequency: int = 25 if len(sys.argv) < 2 else int(sys.argv[1])\n", " progress: bool = True\n", " residual_threshold: float = 1e-10\n", "\n", @@ -421,6 +427,72 @@ "!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\")" ] + }, + { + "cell_type": "markdown", + "id": "b8a12d4e", + "metadata": {}, + "source": [ + "### 8. Balancing CPU I/O and GPU Compute\n", + "\n", + "Our asynchronous implementation overlaps checkpoint I/O on the CPU with power-iteration steps on the GPU. `check_frequency` controls how many GPU steps run between residual checks and checkpoints. A smaller value produces output more frequently, but may not provide enough GPU work to hide the I/O. A larger value provides more GPU work to overlap with the I/O, but delays output and convergence checks.\n", + "\n", + "**TODO:** Sweep check frequencies from 20 through 30 in increments of 1 and determine the output frequency with the lowest execution time. Run `python power_iteration__async.py ` for each value, record the final time reported by the script, and plot the results." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c6f42a91", + "metadata": {}, + "outputs": [], + "source": [ + "check_frequencies = list(range(20, 31))\n", + "async_durations = []\n", + "\n", + "print(\"Sweeping async check frequencies...\")\n", + "print(\"=\" * 45)\n", + "print(f\"{'Check Frequency':>18} | {'Time (ms)':>12}\")\n", + "print(\"-\" * 45)\n", + "\n", + "for check_frequency in check_frequencies:\n", + " # TODO: Run power_iteration__async.py with check_frequency, extract\n", + " # its reported time in milliseconds, and append it to async_durations.\n", + " pass\n", + "\n", + "print(\"=\" * 45)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "d3e7b580", + "metadata": {}, + "outputs": [], + "source": [ + "# TODO: Find the check frequency with the shortest execution time and\n", + "# display a performance graph. Highlight the optimal point." + ] + }, + { + "cell_type": "markdown", + "id": "e91c4a27", + "metadata": {}, + "source": [ + "Finally, profile the optimal check frequency. In the trace, compare the CPU I/O region with the GPU compute between checks. Does the GPU compute fully hide the I/O?" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "f24b6c39", + "metadata": {}, + "outputs": [], + "source": [ + "!nsys profile --cuda-event-trace=false --capture-range=cudaProfilerApi --capture-range-end=stop --force-overwrite true -o power_iteration__async__optimal python power_iteration__async.py {optimal_check_frequency}\n", + "!nsys export --type sqlite --quiet true --force-overwrite true power_iteration__async__optimal.nsys-rep\n", + "nsightful.display_nsys_sqlite_file_in_notebook(\"power_iteration__async__optimal.sqlite\", title=f\"Power Iteration - Async - Check Frequency {optimal_check_frequency}\")" + ] } ], "metadata": { diff --git a/tutorials/pyhpc/notebooks/solutions/02__power_iteration__cupy__memory_spaces__SOLUTION.ipynb b/tutorials/pyhpc/notebooks/solutions/02__power_iteration__cupy__memory_spaces__SOLUTION.ipynb index 59530981..5221bc44 100644 --- a/tutorials/pyhpc/notebooks/solutions/02__power_iteration__cupy__memory_spaces__SOLUTION.ipynb +++ b/tutorials/pyhpc/notebooks/solutions/02__power_iteration__cupy__memory_spaces__SOLUTION.ipynb @@ -86,7 +86,7 @@ }, { "cell_type": "code", - "execution_count": 1, + "execution_count": null, "id": "0cc26840", "metadata": { "execution": { @@ -108,10 +108,10 @@ "# Configuration for the algorithm\n", "@dataclass\n", "class PowerIterationConfig:\n", - " dim: int = 4096 # Matrix size (dim x dim)\n", - " dominance: float = 0.1 # How much larger the top eigenvalue is (controls convergence, higher == faster)\n", - " max_steps: int = 400 # Maximum iterations\n", - " check_frequency: int = 10 # Check for convergence every N steps\n", + " dim: int = 10000 # Matrix size (dim x dim)\n", + " dominance: float = 0.05 # How much larger the top eigenvalue is (controls convergence, higher == faster)\n", + " max_steps: int = 1000 # Maximum iterations\n", + " check_frequency: int = 25 # Check for convergence every N steps\n", " progress: bool = True # Print progress logs\n", " residual_threshold: float = 1e-10 # Stop if error is below this" ] @@ -128,7 +128,7 @@ }, { "cell_type": "code", - "execution_count": 2, + "execution_count": null, "id": "b2503345", "metadata": { "execution": { @@ -139,17 +139,7 @@ "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" - ] - } - ], + "outputs": [], "source": [ "def generate_host(cfg=PowerIterationConfig()):\n", " \"\"\"Generates a random diagonalizable matrix on the CPU.\"\"\"\n", @@ -184,7 +174,7 @@ }, { "cell_type": "code", - "execution_count": 3, + "execution_count": null, "id": "3c8229aa", "metadata": { "execution": { @@ -195,37 +185,7 @@ "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" - ] - } - ], + "outputs": [], "source": [ "def estimate_host(A, cfg=PowerIterationConfig()) -> np.ndarray:\n", " \"\"\"\n", @@ -258,7 +218,7 @@ " break\n", "\n", " # Run intermediate steps without checking residual to save compute.\n", - " for _ in range(cfg.check_frequency - 1):\n", + " for _ in range(i + 1, min(i + cfg.check_frequency, cfg.max_steps)):\n", " y = A @ x\n", " x = y / np.linalg.norm(y)\n", "\n", @@ -296,7 +256,7 @@ }, { "cell_type": "code", - "execution_count": 4, + "execution_count": null, "id": "f36586ee", "metadata": { "execution": { @@ -307,37 +267,7 @@ "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" - ] - } - ], + "outputs": [], "source": [ "def estimate_device(A, cfg=PowerIterationConfig()) -> np.ndarray:\n", " \"\"\"\n", @@ -382,7 +312,7 @@ " break\n", "\n", " # Run intermediate steps without checking residual to save compute.\n", - " for _ in range(cfg.check_frequency - 1):\n", + " for _ in range(i + 1, min(i + cfg.check_frequency, cfg.max_steps)):\n", " y = A_gpu @ x\n", " x = y / cp.linalg.norm(y)\n", "\n", @@ -424,7 +354,7 @@ }, { "cell_type": "code", - "execution_count": 5, + "execution_count": null, "id": "7a363755", "metadata": { "execution": { @@ -435,38 +365,7 @@ "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" - ] - } - ], + "outputs": [], "source": [ "def generate_device(cfg=PowerIterationConfig()):\n", " \"\"\"\n", @@ -527,7 +426,7 @@ }, { "cell_type": "code", - "execution_count": 6, + "execution_count": null, "id": "611859bb", "metadata": { "execution": { @@ -538,32 +437,7 @@ "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" - ] - } - ], + "outputs": [], "source": [ "with np.printoptions(precision=4):\n", " print(\"A_host:\")\n", @@ -609,7 +483,7 @@ }, { "cell_type": "code", - "execution_count": 7, + "execution_count": null, "id": "e5d4e603", "metadata": { "execution": { @@ -629,7 +503,7 @@ }, { "cell_type": "code", - "execution_count": 8, + "execution_count": null, "id": "80b127ed", "metadata": { "execution": { @@ -640,20 +514,7 @@ "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" - ] - } - ], + "outputs": [], "source": [ "print(f\"Power Iteration (Host) = {lam_est_host}\")\n", "print(f\"Power Iteration (Device) = {lam_est_device}\")\n", @@ -683,7 +544,7 @@ }, { "cell_type": "code", - "execution_count": 9, + "execution_count": null, "id": "39403462", "metadata": { "execution": { @@ -694,22 +555,7 @@ "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" - ] - } - ], + "outputs": [], "source": [ "cfg = PowerIterationConfig(progress=False)\n", "\n", @@ -740,7 +586,7 @@ "\n", "**Explore the impact of changing the following parameters:**\n", "\n", - "1. **Problem Size (`dim`):** How does the GPU speedup change as you increase or decrease the matrix dimensions? Try values like 1024, 2048, 4096, 8192.\n", + "1. **Problem Size (`dim`):** How does the GPU speedup change as you decrease the matrix dimensions? Try values like 1024, 2048, 4096, 8192.\n", "\n", "2. **Compute Workload (`max_steps` and `dominance`):** The `dominance` parameter controls how quickly the algorithm converges. A smaller dominance means eigenvalues are closer together, requiring more iterations. How does this affect the CPU vs GPU comparison?\n", "\n", @@ -751,7 +597,7 @@ }, { "cell_type": "code", - "execution_count": 10, + "execution_count": null, "id": "2cdee8ff", "metadata": { "execution": { @@ -766,7 +612,7 @@ "source": [ "# Try different configurations here!\n", "# Example:\n", - "# cfg_large = PowerIterationConfig(dim=8192, progress=False)\n", + "# cfg_smaller = PowerIterationConfig(dim=8192, progress=False)\n", "# cfg_slow_converge = PowerIterationConfig(dominance=0.01, progress=False)\n", "# cfg_frequent_check = PowerIterationConfig(check_frequency=1, progress=True)\n", "\n", diff --git a/tutorials/pyhpc/notebooks/solutions/03__power_iteration__cupy__asynchrony__SOLUTION.ipynb b/tutorials/pyhpc/notebooks/solutions/03__power_iteration__cupy__asynchrony__SOLUTION.ipynb index c9de16af..e3f6a870 100644 --- a/tutorials/pyhpc/notebooks/solutions/03__power_iteration__cupy__asynchrony__SOLUTION.ipynb +++ b/tutorials/pyhpc/notebooks/solutions/03__power_iteration__cupy__asynchrony__SOLUTION.ipynb @@ -15,6 +15,7 @@ "5. [Better Visibility with NVTX](#5-Better-Visibility-with-NVTX)\n", "6. [Implementing Asynchrony](#6-Implementing-Asynchrony)\n", "7. [Performance Analysis](#7-Performance-Analysis)\n", + "8. [Balancing CPU I/O and GPU Compute](#8-Balancing-CPU-I/O-and-GPU-Compute)\n", "\n", "### 1. Introduction and Setup\n", "\n", @@ -45,7 +46,10 @@ " open(\"/accelerated-computing-hub-installed\", \"a\").close()\n", " print(\"All packages installed.\")\n", "\n", - "import nsightful" + "import nsightful\n", + "from jupyter_dark_detect import is_dark\n", + "import matplotlib.pyplot as plt\n", + "plt.style.use('dark_background' if is_dark() else 'default')" ] }, { @@ -91,15 +95,16 @@ "import cupy as cp\n", "import cupyx as cpx\n", "import nvtx\n", + "import sys\n", "import time\n", "from dataclasses import dataclass\n", "\n", "@dataclass\n", "class PowerIterationConfig:\n", - " dim: int = 8192\n", + " dim: int = 19000\n", " dominance: float = 0.05\n", - " max_steps: int = 1000\n", - " check_frequency: int = 15\n", + " max_steps: int = 400\n", + " check_frequency: int = 25 if len(sys.argv) < 2 else int(sys.argv[1])\n", " progress: bool = True\n", " residual_threshold: float = 1e-10\n", "\n", @@ -143,7 +148,7 @@ " break\n", "\n", " with nvtx.annotate(f\"Compute {i + 1} to {i + cfg.check_frequency}\"):\n", - " for j in range(i + 1, i + cfg.check_frequency):\n", + " for j in range(i + 1, min(i + cfg.check_frequency, cfg.max_steps)):\n", " with nvtx.annotate(f\"Compute Step {j}\"):\n", " y = A_gpu @ x # We have to use `A_gpu` here as well.\n", " x = y / cp.linalg.norm(y) # Normalize for next step.\n", @@ -301,15 +306,16 @@ "import cupy as cp\n", "import cupyx as cpx\n", "import nvtx\n", + "import sys\n", "import time\n", "from dataclasses import dataclass\n", "\n", "@dataclass\n", "class PowerIterationConfig:\n", - " dim: int = 8192\n", + " dim: int = 19000\n", " dominance: float = 0.05\n", - " max_steps: int = 1000\n", - " check_frequency: int = 15\n", + " max_steps: int = 400\n", + " check_frequency: int = 25 if len(sys.argv) < 2 else int(sys.argv[1])\n", " progress: bool = True\n", " residual_threshold: float = 1e-10\n", "\n", @@ -344,7 +350,7 @@ " copy_event = cp.cuda.get_current_stream().record()\n", "\n", " with nvtx.annotate(f\"Compute {i + 1} to {i + cfg.check_frequency}\"):\n", - " for j in range(i + 1, i + cfg.check_frequency):\n", + " for j in range(i + 1, min(i + cfg.check_frequency, cfg.max_steps)):\n", " with nvtx.annotate(f\"Compute Step {j}\"):\n", " y = A_gpu @ x # We have to use `A_gpu` here as well.\n", " x = y / cp.linalg.norm(y) # Normalize for next step.\n", @@ -462,6 +468,91 @@ "!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\")" ] + }, + { + "cell_type": "markdown", + "id": "a47d9c2b", + "metadata": {}, + "source": [ + "### 8. Balancing CPU I/O and GPU Compute\n", + "\n", + "Our asynchronous implementation overlaps checkpoint I/O on the CPU with power-iteration steps on the GPU. `check_frequency` controls how many GPU steps run between residual checks and checkpoints. A smaller value produces output more frequently, but may not provide enough GPU work to hide the I/O. A larger value provides more GPU work to overlap with the I/O, but delays output and convergence checks.\n", + "\n", + "**SOLUTION:** Sweep check frequencies from 20 through 30 in increments of 1 and determine the output frequency with the lowest execution time. The script reports the solver time on its final line, so matrix setup remains outside the measurement." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "b52e8f31", + "metadata": {}, + "outputs": [], + "source": [ + "check_frequencies = list(range(20, 31))\n", + "async_durations = []\n", + "\n", + "print(\"Sweeping async check frequencies...\")\n", + "print(\"=\" * 45)\n", + "print(f\"{'Check Frequency':>18} | {'Time (ms)':>12}\")\n", + "print(\"-\" * 45)\n", + "\n", + "for check_frequency in check_frequencies:\n", + " output = !python power_iteration__async.py {check_frequency}\n", + " duration_ms = float(output[-1].split()[0])\n", + " async_durations.append(duration_ms)\n", + " print(f\"{check_frequency:>18} | {duration_ms:>9.3f} ms\")\n", + "\n", + "print(\"=\" * 45)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "c81a6d40", + "metadata": {}, + "outputs": [], + "source": [ + "optimal_index = async_durations.index(min(async_durations))\n", + "optimal_check_frequency = check_frequencies[optimal_index]\n", + "optimal_duration = async_durations[optimal_index]\n", + "\n", + "fig, ax = plt.subplots(figsize=(10, 5))\n", + "ax.plot(check_frequencies, async_durations, 'b-o', linewidth=2, markersize=8)\n", + "ax.scatter(optimal_check_frequency, optimal_duration, color='red', s=100, zorder=3,\n", + " label=f'Optimal: {optimal_check_frequency} steps')\n", + "for check_frequency, duration in zip(check_frequencies, async_durations):\n", + " ax.text(check_frequency, duration, f'{duration:.1f}', va='bottom', ha='center')\n", + "ax.set_xticks(check_frequencies)\n", + "ax.set_xlabel('Check Frequency (steps)', fontsize=12)\n", + "ax.set_ylabel('Execution Time (ms)', fontsize=12)\n", + "ax.set_title('Async Power Iteration: Check Frequency Sweep', fontsize=14)\n", + "ax.legend(fontsize=11)\n", + "ax.grid(True, alpha=0.3)\n", + "plt.tight_layout()\n", + "plt.show()\n", + "\n", + "print(f'Optimal check frequency: {optimal_check_frequency} steps ({optimal_duration:.3f} ms)')" + ] + }, + { + "cell_type": "markdown", + "id": "d26f7b95", + "metadata": {}, + "source": [ + "Finally, profile the optimal check frequency and verify that the GPU compute between checks overlaps the CPU I/O." + ] + }, + { + "cell_type": "code", + "execution_count": null, + "id": "e73b4a08", + "metadata": {}, + "outputs": [], + "source": [ + "!nsys profile --cuda-event-trace=false --capture-range=cudaProfilerApi --capture-range-end=stop --force-overwrite true -o power_iteration__async__optimal python power_iteration__async.py {optimal_check_frequency}\n", + "!nsys export --type sqlite --quiet true --force-overwrite true power_iteration__async__optimal.nsys-rep\n", + "nsightful.display_nsys_sqlite_file_in_notebook(\"power_iteration__async__optimal.sqlite\", title=f\"Power Iteration - Async - Check Frequency {optimal_check_frequency}\")" + ] } ], "metadata": {