diff --git a/include/aare/ClusterFinderMT.hpp b/include/aare/ClusterFinderMT.hpp index a4995d0d..5b61e8b9 100644 --- a/include/aare/ClusterFinderMT.hpp +++ b/include/aare/ClusterFinderMT.hpp @@ -135,26 +135,46 @@ class ClusterFinderMT { * @brief Collect all the clusters from the output queues and write them to * the sink */ + // void collect() { + // bool empty = true; + // Backoff backoff; + // while (!m_stop_requested || !empty || !m_processing_threads_stopped) + // { + // bool moved_any = false; + // for (auto &queue : m_output_queues) { + // while (auto *front = queue->frontPtr()) { + // while (!m_sink.write(std::move(*front))) { + // backoff.pause(); + // } + // queue->popFront(); + // moved_any = true; + // } + // } + // empty = !moved_any; + // if (moved_any) { + // backoff.reset(); + // } else { + // backoff.pause(); + // } + // } + // } void collect() { - bool empty = true; Backoff backoff; - while (!m_stop_requested || !empty || !m_processing_threads_stopped) { + for (;;) { + const bool done = m_stop_requested && m_processing_threads_stopped; bool moved_any = false; for (auto &queue : m_output_queues) { while (auto *front = queue->frontPtr()) { - while (!m_sink.write(std::move(*front))) { + while (!m_sink.write(std::move(*front))) backoff.pause(); - } queue->popFront(); moved_any = true; } } - empty = !moved_any; - if (moved_any) { - backoff.reset(); - } else { - backoff.pause(); - } + if (done && + !moved_any) // swept after observing done, so this is real + break; + moved_any ? backoff.reset() : backoff.pause(); } } diff --git a/python/tests/ClusterFinderCUDA.ipynb b/python/tests/ClusterFinderCUDA.ipynb index efd3415e..6f364cc7 100644 --- a/python/tests/ClusterFinderCUDA.ipynb +++ b/python/tests/ClusterFinderCUDA.ipynb @@ -1,595 +1,596 @@ -{ - "cells": [ - { - "cell_type": "markdown", - "id": "title", - "metadata": {}, - "source": [ - "# ClusterFinder — CPU vs CUDA\n", - "\n", - "Two finders over the same frames and the same pedestal:\n", - "\n", - "| | |\n", - "|---|---|\n", - "| **`ClusterFinderMT`** + `ClusterCollector` | multithreaded CPU baseline |\n", - "| **`ClusterFinderCUDA.find_clusters_batched`** | batched across CUDA streams, over a pinned input buffer, so H2D / kernel / D2H overlap |\n", - "\n", - "**Part 1** times them. **Part 2** compares what they found." - ] - }, - { - "cell_type": "code", - "execution_count": 1, - "id": "imports", - "metadata": {}, - "outputs": [], - "source": [ - "import sys; sys.path.append('/home/ferjao_k/aare/build')\n", - "\n", - "import time\n", - "from pathlib import Path\n", - "\n", - "import boost_histogram as bh\n", - "import matplotlib.pyplot as plt\n", - "import numpy as np\n", - "from tqdm import tqdm\n", - "\n", - "from aare import (File, ClusterFinderMT, ClusterCollector, ClusterFinderCUDA)" - ] - }, - { - "cell_type": "markdown", - "id": "md-config", - "metadata": {}, - "source": [ - "## Configuration" - ] - }, - { - "cell_type": "code", - "execution_count": 2, - "id": "config", - "metadata": {}, - "outputs": [ + { + "cells": + [ { - "name": "stdout", - "output_type": "stream", - "text": [ - "Image size: (400, 400) cluster (3, 3)\n", - "Pedestal frames: 1000\n", - "Data frames: 10000 of 100000 in file\n" - ] - } - ], - "source": [ - "base = Path('/mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/')\n", - "f = File(base / 'Cu_factor_10_data_master_0.json')\n", - "pd = File(base / 'Cu_factor_10_pedestal_master_0.json')\n", - "\n", - "n_frames_pd = 1000 # pedestal frames, taken from the head of the file\n", - "N = 10000 # data frames, read after those\n", - "cluster_size = (3, 3)\n", - "rows, cols = f.rows, f.cols\n", - "image_size = (rows, cols)\n", - "\n", - "N_SIGMA = 5\n", - "N_STREAMS = 4 # H2D / kernel / D2H overlap\n", - "BATCH_SIZE = 2000 # frames per find_clusters_batched call\n", - "\n", - "# CPU workers. ClusterFinderMT gives each thread its own ClusterFinder with its\n", - "# own pedestal, and data frames are handed out round-robin -- so at high thread\n", - "# counts each pedestal tracks on only 1/n of the frames while the CUDA finder's\n", - "# single pedestal sees all of them, and the cluster counts drift apart. Keep it\n", - "# low when comparing counts; raise it to measure CPU throughput.\n", - "N_THREADS = 4\n", - "\n", - "# Fixed per-frame output slot the kernel writes into, copied WHOLE on every D2H.\n", - "# Too low does not error -- it TRUNCATES. The CUDA cell prints the peak actually\n", - "# reached, which is how you size this for a new dataset.\n", - "CAP = 3000\n", - "\n", - "capacity = 10_000 # initial ClusterVector capacity, CPU side\n", - "N_BINS = 200\n", - "E_MIN, E_MAX = -2, 4000 # spectrum range, ADU\n", - "\n", - "print(f'Image size: {image_size} cluster {cluster_size}')\n", - "print(f'Pedestal frames: {n_frames_pd}')\n", - "print(f'Data frames: {N} of {f.total_frames} in file')" - ] - }, - { - "cell_type": "markdown", - "id": "md-build", - "metadata": {}, - "source": [ - "## Build the two finders" - ] - }, - { - "cell_type": "code", - "execution_count": 3, - "id": "build", - "metadata": {}, - "outputs": [], - "source": [ - "cf_cpu = ClusterFinderMT(image_size, cluster_size, n_sigma=N_SIGMA,\n", - " capacity=capacity, n_threads=N_THREADS)\n", - "sink = ClusterCollector(cf_cpu)\n", - "\n", - "cf_cuda = ClusterFinderCUDA(image_size, cluster_size, n_sigma=N_SIGMA,\n", - " max_clusters_per_frame=CAP, n_streams=N_STREAMS)" - ] - }, - { - "cell_type": "markdown", - "id": "md-ped", - "metadata": {}, - "source": [ - "## Pedestal\n", - "\n", - "Identical frames into both, so the implementation is the only thing that differs." - ] - }, - { - "cell_type": "code", - "execution_count": 4, - "id": "train", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Pedestal (1000 frames): 0.86s\n" - ] - } - ], - "source": [ - "f.seek(0)\n", - "t0 = time.perf_counter()\n", - "for _ in range(n_frames_pd):\n", - " pd_img = pd.read_frame()\n", - " cf_cpu.push_pedestal_frame(pd_img.copy())\n", - " cf_cuda.push_pedestal_frame(pd_img.copy())\n", - "print(f'Pedestal ({n_frames_pd} frames): {time.perf_counter() - t0:.2f}s')" - ] - }, - { - "cell_type": "markdown", - "id": "md-io", - "metadata": {}, - "source": [ - "## Read the data frames\n", - "\n", - "Kept out of both timing loops: the two finders run over the same in-memory array." - ] - }, - { - "cell_type": "code", - "execution_count": 5, - "id": "io", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "Reading 10000 frames: 5.15s (1,942 FPS, 3.20 GB at 0.62 GB/s)\n" - ] - } - ], - "source": [ - "f.seek(0)\n", - "t0 = time.perf_counter()\n", - "data = f.read_n(N)\n", - "t_io = time.perf_counter() - t0\n", - "gb = f.bytes_per_frame * N / 1e9\n", - "print(f'Reading {N} frames: {t_io:.2f}s ({N / t_io:,.0f} FPS, '\n", - " f'{gb:.2f} GB at {gb / t_io:.2f} GB/s)')" - ] - }, - { - "cell_type": "markdown", - "id": "md-cpu", - "metadata": {}, - "source": [ - "## 1 — CPU, multithreaded" - ] - }, - { - "cell_type": "code", - "execution_count": 6, - "id": "cpu", - "metadata": {}, - "outputs": [ - { - "name": "stderr", - "output_type": "stream", - "text": [ - "100%|█████████████████████████████████████████| 10000/10000 [00:02<00:00, 3440.17it/s]\n" + "cell_type": "markdown", + "id": "title", + "metadata": {}, + "source": [ + "# ClusterFinder — CPU vs CUDA\n", + "\n", + "Two finders over the same frames and the same pedestal:\n", + "\n", + "| | |\n", + "|---|---|\n", + "| **`ClusterFinderMT`** + `ClusterCollector` | multithreaded CPU baseline |\n", + "| **`ClusterFinderCUDA.find_clusters_batched`** | batched across CUDA streams, over a pinned input buffer, so H2D / kernel / D2H overlap |\n", + "\n", + "**Part 1** times them. **Part 2** compares what they found." ] }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "CPU MT (4 threads): 2.91s 3,434 FPS 23,242,745 clusters (2324.3/frame)\n" - ] - } - ], - "source": [ - "t0 = time.perf_counter()\n", - "# Pass the frame number: find_clusters(frame) defaults it to 0, and every\n", - "# resulting cluster is then stamped frame 0. Part 2 groups by it.\n", - "for i, frame in enumerate(tqdm(data)):\n", - " cf_cpu.find_clusters(frame, i)\n", - "t_cpu = time.perf_counter() - t0\n", - "\n", - "cf_cpu.stop(); sink.stop()\n", - "clusters_cpu = sink.steal_clusters()\n", - "\n", - "hist_cpu = bh.Histogram(bh.axis.Regular(N_BINS, E_MIN, E_MAX))\n", - "n_clusters_cpu = 0\n", - "for cv in clusters_cpu:\n", - " hist_cpu.fill(cv.sum())\n", - " n_clusters_cpu += cv.size\n", - "\n", - "print(f'CPU MT ({N_THREADS} threads): {t_cpu:.2f}s {N / t_cpu:,.0f} FPS '\n", - " f'{n_clusters_cpu:,} clusters ({n_clusters_cpu / N:.1f}/frame)')" - ] - }, - { - "cell_type": "markdown", - "id": "md-cuda", - "metadata": {}, - "source": [ - "## 2 — CUDA, batched + multi-streamed\n", - "\n", - "`register_input_buffer` page-locks the input once so H2D runs at full DMA\n", - "bandwidth instead of through the driver's staging buffer. It is paid once,\n", - "outside the loop, and released when the dataset is done." - ] - }, - { - "cell_type": "code", - "execution_count": 14, - "id": "cuda", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CUDA batched: 0.21s 48,578 FPS 23,244,600 clusters (2324.5/frame)\n", - " input pinned in 0.08s, once, outside the loop\n", - " vs CPU: 14.15x\n", - " peak clusters in one frame: 2,522 of CAP 3,000\n" - ] - } - ], - "source": [ - "t0 = time.perf_counter()\n", - "cf_cuda.register_input_buffer(data)\n", - "t_pin = time.perf_counter() - t0\n", - "\n", - "clusters_cuda = []\n", - "t0 = time.perf_counter()\n", - "for start in range(0, N, BATCH_SIZE):\n", - " stop = min(start + BATCH_SIZE, N)\n", - " clusters_cuda.extend(\n", - " cf_cuda.find_clusters_batched(data[start:stop], first_frame=start))\n", - "t_cuda = time.perf_counter() - t0\n", - "\n", - "cf_cuda.unregister_input_buffer()\n", - "\n", - "hist_cuda = bh.Histogram(bh.axis.Regular(N_BINS, E_MIN, E_MAX))\n", - "energies = [np.asarray(cv.sum()).ravel() for cv in clusters_cuda if cv.size > 0]\n", - "if energies:\n", - " hist_cuda.fill(np.concatenate(energies))\n", - "n_clusters_cuda = sum(cv.size for cv in clusters_cuda)\n", - "\n", - "print(f'CUDA batched: {t_cuda:.2f}s {N / t_cuda:,.0f} FPS '\n", - " f'{n_clusters_cuda:,} clusters ({n_clusters_cuda / N:.1f}/frame)')\n", - "print(f' input pinned in {t_pin:.2f}s, once, outside the loop')\n", - "print(f' vs CPU: {t_cpu / t_cuda:.2f}x')\n", - "\n", - "# One ClusterVector per frame, so this IS the per-frame maximum.\n", - "peak = max((cv.size for cv in clusters_cuda), default=0)\n", - "print(f' peak clusters in one frame: {peak:,} of CAP {CAP:,}')\n", - "if peak >= CAP:\n", - " print(' !! CAP REACHED -- clusters were dropped; raise CAP and re-run.')" - ] - }, - { - "cell_type": "markdown", - "id": "md-summary", - "metadata": {}, - "source": [ - "## Summary" - ] - }, - { - "cell_type": "code", - "execution_count": 8, - "id": "summary", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - " s FPS us/frame clusters\n", - "CPU MT (4 thr) 2.91 3,434 291.2 23,242,745\n", - "CUDA batched 0.55 18,074 55.3 23,244,269\n", - "\n", - "speedup: 5.26x\n" - ] - } - ], - "source": [ - "print(f'{\"\":<22} {\"s\":>7} {\"FPS\":>10} {\"us/frame\":>10} {\"clusters\":>13}')\n", - "for name, t, n in (('CPU MT (%d thr)' % N_THREADS, t_cpu, n_clusters_cpu),\n", - " ('CUDA batched', t_cuda, n_clusters_cuda)):\n", - " print(f'{name:<22} {t:7.2f} {N / t:10,.0f} {t * 1e6 / N:10.1f} {n:>13,}')\n", - "print(f'\\nspeedup: {t_cpu / t_cuda:.2f}x')" - ] - }, - { - "cell_type": "markdown", - "id": "md-part2", - "metadata": {}, - "source": [ - "# Part 2 — Where the two disagree\n", - "\n", - "The two implementations are not bit-identical by construction: the CUDA finder\n", - "updates its pedestal once per frame, the CPU finder per pixel, so once the\n", - "pedestals drift apart a few pixels land on opposite sides of the threshold.\n", - "\n", - "Three views, cheapest first: the **spectra**, the **cluster sets**, and **where\n", - "in the run** the difference accumulates." - ] - }, - { - "cell_type": "markdown", - "id": "md-spec", - "metadata": {}, - "source": [ - "## Spectra\n", - "\n", - "Both curves should lie on top of each other; the ratio panel is the sensitive\n", - "view." - ] - }, - { - "cell_type": "code", - "execution_count": 9, - "id": "spectra", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": - "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eoUKFVzPeffdd/3u/91335nXpvv5M23aNJNfnTt3Dsq5jx8/bvKiatWq5v0866yzTP47nThxQoYOHSq1atWSIkWKSIMGDeSLL77wOMagQYPkmWeekQMHDki0IcgDAAAAcvDXX39J48aNTU3Y888/Lz/99JO54G/VqpXcd999+cq7K6+8UrZt22aOPWfOHHOsPn36SIcOHSQtzXOu1s2bN8vSpUvNuSZOnJiv8y1ZskR+//13ueGGG1zbFi1aZIK82bNny4oVK0waNHBdtWqVa59SpUqZYEjPv3btWrn99tvN4h0Mep/r3HPPlRkzZpjn3HHHHXLbbbfJp59+mm3flJQU89jll1/u93h///23PPLII3LxxRcH9DrPDeDcXbp0MQH6W2+9Jb/99ptMnTrV1HI6PfHEE/L666/LqFGjZN26ddK7d2+55pprPPJGz1OtWrUcA9hIcViWZUU6EdFGo/ESJUqYQqe/akQDfZs0PZou/RUD5LedRKp81xw4x9xueKZdtsc+G5S5rcPQzH3shO8T8tvOKN/Rm9/Hjh2TTZs2SfXq1aVw4cISS9q3b28CBg0GtHbI3f79+00tl9I8mDlzZrbaJq3JW716tallctbk6fO8a/00iNRg54033pCePXu6ticnJ8uvv/5qao6aNWsm//77r5x66ql5eg0PPvigCSo/+OCDHPerW7eudO3a1ZzLn0aNGslVV10lTz31VMDn1/21xtK9tkzdeOONUqNGDYmPjzf5ofnkLj09XS699FITWC5evNhnvuX13F988YU5759//mmCWF8qVqwojz/+uAnuNG3OWkTN9ylTpni8NxosasAcLDl9VgKNU6jJA1Bg/Zxwq1l8aeP4wSwAgIJt7969JijQWjTvAE85A7xguOyyy0yzQG0i6R5Ia+3drbfeamqaNCB6//3383xsDUK02WVOtKnowYMH/QY+mhYNaDTYveSSS/J0fg1KvI+rr+uPP/6QwYP994HXJpPanPTOO+/M0/lyOvcnn3xi8kJrZc844wypWbOmqSk8evSoR3NO7wBLm21+++23HtvOP/98+eGHH8z+0SQh0gkAgGiV5EiT1MGlXfdPSIIUTd4R0TQBgC09Vdb/Yz3ni1RokLk+7RaR3+f63u+S/4lc+mjm+sp3RD7v53u/8vVFen0dcNI2btxoghv3pnyhpOfRWkOn+fPn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fv3koosuMv/kKePhz29FGQ+un376yQQZx44dk1NPPVVmzpwp55xzjusCle/v8OS3omwH17Rp02TlypXy448/FqjrE4K8GKGdRL3/6XlvQ870S9Opfv365sv1rLPOkrfffts1IEV+8pn3InDByF9f+/MeZNe1a1fXul4YN2nSxAR8n3/+uVx77bXk90m4//77Ze3atfLtt99me4wyHr78powHV61atWT16tVmoIgZM2ZI9+7dZeHCha7HKdvhyW8N9CjbwbNlyxbp06ePzJ071wxe6I8dyzfNNaOcjuITHx+f7VcAbXbl/asD8kZHR9JgT5twOkfZzCmfdR9tPqujJ/Fe5E2w8lf30VpYb7t27eLzkIsKFSqYIE/LO/mdfzq62ieffCLffPONVKpUybWdMh7e/PaFMn5ytKbi7LPPNj8I6QiEDRo0kJdffpmyHeb89oWynX8rVqww1xE6Mn1CQoJZNJgePXq0WXdeX9jx+oQgLwa+BLRgzps3z2O73temhsg/HQ53/fr15stT++jpB9Q9n/UDrV8EznzW9yExMdFjn23btsnPP//Me5GLYOWv1r5q35wffvjBtc+yZcvMNj4POduzZ4/5RVPLO/mdd/prrNYoffTRR/L111+bMk0Zj1x++0IZD/57oP8n+f4Ob377QtnOv8svv9w0jdVaU+eigfUtt9xi1s8880z7Xp9EZLgX5GsKhbfeesuM3tO3b18zhcJff/1FTubBww8/bC1YsMD6888/zehHOqytDpnrzEcdWUlHU/roo4/MELo33XSTzyF0K1WqZM2fP98MoXvZZZcxhYLbyFWrVq0yi361jBgxwqw7p/oIVv7qEMU6/LGOWqVL/fr1C+QUCjnltz6m5V2HcN60aZP1zTffWM2bNzfDP5Pf+XPPPfeY8qvfIe7TUhw5csS1D2U8fPlNGQ+uAQMGWIsWLTLfF2vXrjVD+uvIgXPnzjWPU7bDl9+U7dC71G10TTuXb4K8GDF27FiratWqVlJSktWoUSOPYaQRGOe8JxowV6xY0br22mutdevWuR7XYXR1mgUdSrdQoULWJZdcYj7s7o4ePWrdf//9VqlSpawiRYqYD+/mzZt5CyzLBBIabHgvOhR0MPNX53q75ZZbTICui67v27evwL0HOeW3Xgi3adPGKlu2rCnvOr+YbvfOS/I7cL7yWhedy82JMh6+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", - "text/plain": [ - "
" - ] - }, + "cell_type": "code", + "execution_count": 1, + "id": "imports", "metadata": {}, - "output_type": "display_data" - } - ], - "source": [ - "fig, (ax_spec, ax_ratio) = plt.subplots(\n", - " 2, 1, figsize=(9, 6.5), sharex=True, gridspec_kw={'height_ratios': [3, 1]})\n", - "\n", - "edges = hist_cpu.axes[0].edges\n", - "cpu_vals, cuda_vals = hist_cpu.values(), hist_cuda.values()\n", - "\n", - "ax_spec.stairs(cpu_vals, edges, label=f'CPU MT ({n_clusters_cpu:,})',\n", - " color='C0', linewidth=1.4)\n", - "ax_spec.stairs(cuda_vals, edges, label=f'CUDA ({n_clusters_cuda:,})',\n", - " color='C1', linestyle='--', linewidth=1.4)\n", - "ax_spec.set_ylabel('Counts')\n", - "ax_spec.set_title(f'Cluster energy spectrum — {N:,} frames')\n", - "ax_spec.legend()\n", - "ax_spec.grid(alpha=0.2)\n", - "\n", - "with np.errstate(divide='ignore', invalid='ignore'):\n", - " ratio = np.where(cpu_vals > 0, cuda_vals / cpu_vals, np.nan)\n", - "ax_ratio.stairs(ratio, edges, color='C1', linewidth=1.2)\n", - "ax_ratio.axhline(1.0, color='gray', linewidth=0.5)\n", - "ax_ratio.set_ylabel('CUDA / CPU')\n", - "ax_ratio.set_xlabel('Energy [ADU]')\n", - "ax_ratio.set_ylim(0.9, 1.1)\n", - "ax_ratio.grid(alpha=0.3)\n", - "\n", - "plt.tight_layout()\n", - "plt.show()" - ] - }, - { - "cell_type": "markdown", - "id": "md-sets", - "metadata": {}, - "source": [ - "## Cluster sets\n", - "\n", - "Every cluster is keyed by `(frame, x, y)`, so the two results can be differenced\n", - "directly — no per-frame Python loop.\n", - "\n", - "A cluster whose centre moved by one pixel was *relabelled*, not gained or lost,\n", - "so those are counted separately from genuine adds and drops." - ] - }, - { - "cell_type": "code", - "execution_count": 10, - "id": "sets", - "metadata": {}, - "outputs": [ - { - "name": "stdout", - "output_type": "stream", - "text": [ - "CPU 23,242,745 clusters\n", - "CUDA 23,244,269 clusters net +1,524 (0.0066%)\n", + "outputs": [], + "source": [ + "import sys; sys.path.append('/home/ferjao_k/aare/build')\n", "\n", - "exact-position mismatches CPU-only 1,976 CUDA-only 3,500\n", - " of which shifted by 1 px 120 107\n", - " genuine add / drop 1,856 3,393\n", + "import time\n", + "from pathlib import Path\n", "\n", - "frames touched by a genuine difference: 2,385 of 10,000\n", - "[4.3s]\n" + "import boost_histogram as bh\n", + "import matplotlib.pyplot as plt\n", + "import numpy as np\n", + "from tqdm import tqdm\n", + "\n", + "from aare import (File, ClusterFinderMT, ClusterCollector, ClusterFinderCUDA)" ] - } - ], - "source": [ - "def cluster_keys(vectors):\n", - " \"\"\"One int64 key per cluster: frame * rows*cols + y * cols + x.\n", - "\n", - " Centres are unique within a frame, so keys are globally unique and the sets\n", - " below need only a sort, never a np.unique.\n", - " \"\"\"\n", - " n = len(vectors)\n", - " sizes = np.fromiter((cv.size for cv in vectors), np.int64, n)\n", - " frames = np.repeat(\n", - " np.fromiter((cv.frame_number for cv in vectors), np.int64, n), sizes)\n", - " xy = [np.asarray(cv) for cv in vectors if cv.size]\n", - " if not xy:\n", - " return np.empty(0, np.int64), frames\n", - " x = np.concatenate([v['x'] for v in xy]).astype(np.int64)\n", - " y = np.concatenate([v['y'] for v in xy]).astype(np.int64)\n", - " return frames * (rows * cols) + y * cols + x, frames\n", - "\n", - "\n", - "t0 = time.perf_counter()\n", - "k_cpu, fr_cpu = cluster_keys(clusters_cpu)\n", - "k_cuda, fr_cuda = cluster_keys(clusters_cuda)\n", - "\n", - "# Sort ONCE. np.setdiff1d and np.isin each re-sort their argument on every call,\n", - "# and the shift check below calls one eight times per direction -- about twenty\n", - "# sorts of a 27-million-element array. Two sorts plus binary search instead.\n", - "s_cpu = np.sort(k_cpu)\n", - "s_cuda = np.sort(k_cuda)\n", - "\n", - "\n", - "def present_in(vals, sorted_keys):\n", - " \"\"\"Which of `vals` appear in an already-sorted key array.\"\"\"\n", - " if sorted_keys.size == 0:\n", - " return np.zeros(vals.size, dtype=bool)\n", - " i = np.searchsorted(sorted_keys, vals)\n", - " np.clip(i, 0, sorted_keys.size - 1, out=i)\n", - " return sorted_keys[i] == vals\n", - "\n", - "\n", - "cpu_only = s_cpu[~present_in(s_cpu, s_cuda)]\n", - "cuda_only = s_cuda[~present_in(s_cuda, s_cpu)]\n", - "\n", - "\n", - "def drop_shifted(only, sorted_other):\n", - " \"\"\"Drop mismatches that have a counterpart within 1 px in the other set.\"\"\"\n", - " keep = np.ones(only.size, dtype=bool)\n", - " for dy in (-1, 0, 1):\n", - " for dx in (-1, 0, 1):\n", - " if dx or dy:\n", - " keep &= ~present_in(only + dy * cols + dx, sorted_other)\n", - " return only[keep]\n", - "\n", - "\n", - "cpu_real = drop_shifted(cpu_only, s_cuda)\n", - "cuda_real = drop_shifted(cuda_only, s_cpu)\n", - "touched = np.unique(np.concatenate([cpu_real, cuda_real]) // (rows * cols)).size\n", - "\n", - "print(f'CPU {k_cpu.size:>12,} clusters')\n", - "print(f'CUDA {k_cuda.size:>12,} clusters net {k_cuda.size - k_cpu.size:+,}'\n", - " f' ({abs(k_cuda.size - k_cpu.size) / k_cpu.size:.4%})')\n", - "print()\n", - "print(f'exact-position mismatches CPU-only {cpu_only.size:>8,} '\n", - " f'CUDA-only {cuda_only.size:>8,}')\n", - "print(f' of which shifted by 1 px {cpu_only.size - cpu_real.size:>17,} '\n", - " f'{cuda_only.size - cuda_real.size:>17,}')\n", - "print(f' genuine add / drop {cpu_real.size:>17,} {cuda_real.size:>17,}')\n", - "print(f'\\nframes touched by a genuine difference: {touched:,} of {N:,}')\n", - "print(f'[{time.perf_counter() - t0:.1f}s]')" - ] - }, - { - "cell_type": "markdown", - "id": "md-drift", - "metadata": {}, - "source": [ - "## Where in the run it accumulates\n", - "\n", - "Cumulative `CUDA − CPU` against frame index. A curve that rises and then flattens\n", - "means the two pedestals settled into agreement and the difference is a start-up\n", - "transient; a steadily sloping line would mean a persistent per-frame bias." - ] - }, - { - "cell_type": "code", - "execution_count": 11, - "id": "drift", - "metadata": {}, - "outputs": [ - { - "data": { - "image/png": - "iVBORw0KGgoAAAANSUhEUgAAA3kAAAFeCAYAAAA4xtZyAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAY95JREFUeJzt3Qd8FGX+x/FfQkIIEErovSi9SlGwHFUUFU+xVzjLqdhRsd3ZTuTUEz317F3s97frqaAoIr0piCAovfdQA0n2//o+44TNZhOSkLK7fN6v10B2dnZ2dubZ2fnN8zy/Jy4QCAQMAAAAABAT4st6AwAAAAAAxYcgDwAAAABiCEEeAAAAAMQQgjwAAAAAiCEEeQAAAAAQQwjyAAAAACCGEOQBAAAAQAwhyAMAAACAGEKQBwAAAAAxhCAPUe+nn36yv/zlL9asWTOrUKGCVa5c2bp06WIPPfSQbd68OXu53r17u6mkPPXUU/bKK6+U2PqjUXHtk7i4OLvnnnuyH2udmrd06dIcy/3tb3+zxo0bW0JCglWrVs3N27t3r1155ZVWr149K1eunHXu3PmgtwfRL7RMFdSuXbvc67799ttcz+VVLpHTa6+9Zueee661atXK4uPjrWnTpnnuoh07dtgNN9xg9evXd+d3fX/ffvvtAu/Swrx+1qxZ1r9/f/cbovPH4MGD7ffffw+77BNPPGGtW7e2pKQk99tz77332r59+wq0TV9//bV169bNKlWq5MrLhx9+aIcS7Sftu3/+85855r/zzjvWrl07S05Odvtlzpw57rumvyP1fFBQjz32mCtPKit6r8Jei+g14abQffj+++/beeedZ4cffrjbj/puXXDBBbZo0aJ81797925r2bKlW+e//vWvXOVV34lVq1YVaptR9hLKegOAg/H888/bsGHD3MXCLbfcYm3btnU/IDNmzLBnnnnGJk+ebB988EGpBTQ1a9a0oUOHlsr7RYOS2icnn3yyO7YK3HwfffSRjRw50u68804bOHCgu/iSp59+2p599ll3Uda1a1f3YwUUlYI8XdBL6IVauHKJ3F5//XVbu3atHXnkkZaVlZVvcKQL4+nTp7uLWV2Evvnmm+4iVq87//zzD7h7C/r6BQsWuOOpIPDdd9+1PXv22F133WXHHXecCzZq1aqVvazOM3//+9/ttttuswEDBrj16waTLoKfe+65fLcnEAjY2Wef7bbl448/doGefr8Otd+FLVu22LXXXps9b8OGDXbRRRfZiSee6J7X+Vv76LLLLnPzop2uR3Ss+/bta5988kmR1nHmmWfaTTfdlGOebmoGe/DBB61u3brud7B58+a2YsUKe+CBB9yN7ylTprggOhyV5507d4Z9rl+/fu67escdd9irr75apG1HGQkAUWrSpEmBcuXKBU488cTAnj17cj2fnp4e+Oijj7If9+rVy00lpV27dsW+/qysrMCuXbsC0aq49olOVXfffXe+y9x///1uuXXr1uWYf9lllwWSk5MDxWnnzp3Fuj6UvoKUqXA2bNhQ5NceKvbu3RvYt29fns9nZmZm/33yyScHmjRpEna5zz77zO3rN998M8f8448/PlC/fv1ARkZGvttRmNefddZZgZo1awa2bduWPW/p0qWBxMTEwIgRI7Lnbdy4MVChQoXAX//61xzrHDlyZCAuLi7w888/57tNK1eudNv04IMPBg7F84zKRYMGDQK33XZbjvkTJ050++Wdd94pk+0q6nf65Zdfdq89kOAyX5TfRb3H1VdffcDlQn//ZNWqVa4cX3rppWFfM3Xq1ED58uUD7733nnufhx9+ONcy//3vf9311vLlywu13ShbNNdE1NLdKTUt0J1Tv9YmWPny5e3UU0/N8/VqbqXXhza7UlMrzQ9uZqgmO2pepCY/eq86deq4u1u6wytqEvHzzz/bd999l92MIrgJUlpamt18882uqYa2q0GDBq4JUeidM73ummuucXf92rRp494rvztn6enpdt9997ll1RSpRo0a1qdPH5s0aVL2Mrojffvtt+d476uvvtq2bt1aoOYq+hzBNXF+k7Tx48fbVVdd5Wrq9L66Y7569eocr8tvn4Sj/XT55Ze79anGTXdwf/311wM2i9N6dSdddGz8z6L/X3jhBdcUxd8G/7jqd1N3jHXnXs1aqlev7u6UhjbP0t399u3b24QJE+zoo4+2ihUr2iWXXFKk46oaDB0rraNTp0726aef5vpsqlFQTYM+h46/7tRefPHF7lj7VAtyxRVXWMOGDd37+s3FMjIy7EDUJEq1D6pt0ufW9qhGItxd3KlTp9qgQYPc8VD5Ouyww9znK8z25tXcKlzTRh3HU045xe2XI444Inv7/P2k1+ix7ojrzrJq7EOPVbhmUCq/Byp7qklQqwC1BlDZq127trvr/v3332cvo231a3S0v/0y5X8/wn0mv/yotke1Qjr2usOumiXVJgXT90XHRsvoffQ9/eyzz8Kep8KZOHGiOy+lpKS4dai86vW+H3/80a3rxRdfzPXa//3vf+451S751MRLtV3aFzq22vf/+c9/wp5HVbZVy6DvgJZdvHhxntupJpoFoVYYOhZnnXVWjvlqnq9zjcpncbxe3xuVsTPOOMOqVKmSvVyTJk3c+TS4NcgXX3zhzqlaR+g6dU7Jr+mlvgv6zsqtt96a45zof0/UZFTnIZ2P9H0TlXP9/mhZvwmevnPLli3LsX6//H3zzTfZ51F9Hn0f9f3WeUO1iGqKqu+/zl2htahq3n7//fdnN0VVOdRn0/cjmN5DZVvvoW3S9177TzXd+VH5Uo2nau18+v4ce+yx7u9zzjknR3PGcOcP/zyhY6EaKr2/tvell14q9He6tBS0zB8sfcZQum5RuVOtXigdb/2e6VyjJsR50e+A9qFaTyF60FwTUSkzM9P9yKj5XaNGjUr8/U466ST3nurnpx+zjRs3ukDKD5R0EaAf5qpVq7rAQfzAUz96vXr1spUrV7rmDh07dnQXc2oKNHfuXBs3blyOHzFdJOhHSM+r2UW4k7Z/YaJmiVpWF9768dI8NclYvny5u8DTRcdpp53m2tQr0NNFpvow3n333a5ZmaZwAXJBqBmNmqep+ZN+PNRc9sILL3TH5UD7JBx/W7Vf9dm7d+9uP/zwg/uMB6L30sWnLl71w6/31I+agsR//OMfLiD1t8u/cFKQpIui6667zjVxUf9NBczab7oYVtDiW7NmjftsI0aMcDcX9INd2OOqi21d6Os99GOpsnT66afbwoUL3UW/6H11saPAWcu1aNHCvbcujPRjrP3nN3PTNui99Hl0HHVhpuDi5Zdfzndf6cJd5VllRsGSgjR9/mnTpmXvI/nyyy/dD7su7EePHu3Kvdb/1VdfZS9TkO0tLK1TZVXNjXQcFUzpBoLmqRz7N3d0kawLvSVLlriLvIPl99/Vd0PfO/XlUrnSxabeV//rwljlS+Xq0ksvdd8BCW7KF46OmfrFKAjS+rVefR5dfOkCXLTfVJ50TNTEWN/7t956y90cKAjdTDn++ONdOdT3QPte3zsdQ61HF8+6saDgWWVE2x9M3wW9p8qGzJ8/330XdNwfeeQRt09UJvR90flPnyOYPk/Pnj3dDSqVzbzOW4Uxb948V/7UxzaYPqP/vLbxYF//22+/uRtB/vzQZceOHesCO93o0GukQ4cOOZZT2dD3wH8+HJUXHQOVZzVVVAAd+h3Rcwro1I/Yv/Gi752adGp+amqqKysqIzpH6jjpfUPfR+tR38PZs2e785N+G3Su0fy//vWv7vyk773K4PDhw93rdNPhz3/+s/tN0blO+0aBpI61yr+CTX3XtD069+v3RIGVgkYFbvpu6HuvGwx50XlQZUOBV3BTQZ3TFGjo+63AOjjYzus8oe+TblDpXK2beSrT6ov2pz/9qcDf6Wii31p9t3WcdONI54bQmw3h6MaljqN+X0PpvK1ypt/J0EA+mG4m+jeN9BpEiTKuSQSKZO3ata5Zwbnnnlvg14Q21xw/frxbh/4PtmTJEjdfzTD85jl6/Nhjj+W7/ryaYIwaNSoQHx8fmD59eq7mD1rv559/nj1Pj6tWrRrYvHnzAT/Pa6+95pZ//vnn81zmiy++cMs89NBDOearSYzmP/fccwdsrqKmVEOGDMnVPGXYsGE5ltN7aP6aNWuK1Czlf//7n3v9v//971zNoEK3zd8GHSufntc8NacLpm2vVKlSjnmTJ092yz7yyCM55q9YscI17QxunqXt17Jff/31QR3XOnXqBNLS0nKUYb1e6/H17ds3UK1atcD69evz3E9XXHFFoHLlyoFly5blmP+vf/3Lvc+BmouFNgdW86nvvvvOvfbHH3/Mfu6www5z0+7du/N8fUG21z8uocIdQ5U17X81afPNmTPHLVevXr0czdc+/PBDN//jjz8+YJNslYHQJoEHap6lpnzaN/369QucfvrpBWquGe4z+eVHTaKCtW3bNnDCCSdkP77lllvCNvfTMuHOU6F69OgRqF27dmD79u05PkP79u0DDRs2dMdaHn/8cbe+hQsXZi+n801SUlLgpptuyvG+el1w80W55pprXHNF/xzln0f/9Kc/BYoiv+aaLVq0yLGPfKtXr3bv+cADD+S77oK+/ocffnCP33rrrVzLahk9p9fI5Zdf7vZVOC1btgwMGDAg323yf19Cm8T535O77rorcCA6rjt27HDnteDzpV/+rr322hzLn3baaW7+6NGjc8zv3LlzoEuXLtmP9fm13P/93//lWE7nOM1/6qmncpzj9N0srDZt2rguFqH8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+QjRgN81UI4QKbIi7Do7LS3NVTb58UheCPKKwG+iqQAvUoI8jamkbYmEwofoRDkC5QiRgvMRKEeIFFkRep19oC5jkbOlAAAAAICDRpAHAAAAADGEIA8AAAAAYghBHgAAAADEEII8AAAAAIghBHkAAAAAEEMI8gAAAAAghhDkAQAAAEAMiZggb8KECTZo0CCrX7++G9zvww8/zPH80KFD3fzgqUePHjmWSU9Pt2uvvdZq1qxplSpVslNPPdVWrlyZYxmNWH/RRRdZ1apV3aS/t27dWiqfEQAAAECUSN9h9ts3ZoGARZuICfJ27txpnTp1sieffDLPZU488URbs2ZN9vT555/neP6GG26wDz74wN5++22bOHGi7dixw0455RTLzMzMXub888+3OXPm2BdffOEm/a1ADwAAAACyzXjJ4t84wypOecSiTYJFiIEDB7opP0lJSVa3bt2wz23bts1efPFFe/31161///5u3pgxY6xRo0Y2btw4O+GEE+yXX35xgd2UKVPsqKOOcss8//zz1rNnT1u4cKG1atWqBD4ZAAAAgKiTvt39t69eV6tg0SVigryC+Pbbb6127dpWrVo169Wrl40cOdI9lpkzZ9q+fftswIAB2cur6Wf79u1t0qRJLsibPHmya6LpB3iiJp+ap2XyCvLUDFSTLy0tzf2flZXlprKmbQgEAhGxLYhelCNQjhApOB+BcoSIkFjRNXvMqpAaMdfZBd2OqAnyVMt31llnWZMmTWzJkiX297//3fr27euCO9XwrV271sqXL2/Vq1fP8bo6deq450T/+0FhMM3zlwln1KhRdu+99+aar/59GRkZFgkHe/v27S7Qi4+PmBa4iDKUI1COECk4H4FyhEiQvHuXVfqjW9m+zZsj4jpb1/wxFeSdc8452X+rdq5bt24u4Pvss89s8ODBeb5OgY+StPiC/85rmVC33367DR8+PEdNnpqBKqCsUqWKRcKPobZf2xMJhQ/RiXIEyhEiBecjUI4QEZIruv+U0DElNTUirrMTEhJiK8gLVa9ePRfkLVq0yD1WX729e/e62rXg2rz169fb0Ucfnb3MunXrcq1rw4YNrsYvL6op1BRKBzoSDrYoyIuk7UF0ohyBcoRIwfkIlCOUubi4iDsfFXQbyn5Li2jTpk22YsUKF+xJ165dLTEx0caOHZu9jDJwzps3LzvIU4IV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jHHZNJ988kn75ptvrHXr1taxY8eirA4AAAAAEAmDoVerVs2GDRtmM2bMcMMo9O7du7i2CwAAAABQGoOhb9u2zcaOHWtLly51/fGaN29u/fr1s86dO7uhFAAAAAAg6gUOgSEURE00r7nmGktLS8sxv2rVqvbMM8/YOeecU9zbBwAAAABlKC52m2uqOeZf/vIXO+2001xWzd27d7tx89RUc9CgQXbRRRfZnDlzSnZrAQAAAADFU5P3xBNPuABPmTSDdenSxV577TUX8Km55ksvvVTQVQIAAAAAyqom74cffrArrrgiz+evvPJKmzhxYnFtFwAAAACgJIM8DZfQsmXLPJ/Xc6tWrSrKNgAAAAAASjvIU3PMChUq5Pl8UlKS7dmzp7i2CwAAAADKUODQyK755Zdfukya4WzdurW4tgkAAAAAIkIgCrNrFirIGzJkSL7Pa9w8AAAAAEAUBHlZWVkluyUAAAAAgNLrkwcAAAAAiKEgT2PhhZs++ugjW7BgQclupZndc889rjlo8FS3bt3s5wOBgFumfv36lpycbL1797aff/45xzrS09Pt2muvtZo1a1qlSpXs1FNPtZUrV5b4tgMAAABAxDXXvP7668PO37Fjh2vKedJJJ9mbb75pKSkpVlLatWtn48aNy35crly57L8feughGz16tBusXcM53H///Xb88cfbwoULs7fphhtusE8++cTefvttq1Gjht100012yimn2MyZM3OsCwAAAABiviZvy5YtYSfVjk2ZMsWWL19u9957b4lubEJCgqu986datWpl1+I99thjduedd9rgwYOtffv29uqrr7phHxR4yrZt2+zFF1+0Rx55xPr3729HHHGEjRkzxubOnZsjcAQAAAAACwQO3T558fHx1r17dxc8qZasJC1atMg1x2zWrJmde+659vvvv7v5S5YssbVr19qAAQNyjNvXq1cvmzRpknus2rp9+/blWEbrUkDoLwMAAAAAOUThAAKFGkIhP4cffniJ9m876qijXB9ANcVct26da4559NFHu353CvCkTp06OV6jx8uWLXN/a5ny5ctb9erVcy3jvz4vqq3U5EtLS3P/q5lqJGQd1TaoNjMStgXRi3IEyhEiBecjUI4QCeICf4yQF4ickQYKuh3FFuT99ttv1rBhQyspAwcOzP67Q4cO1rNnTzvssMNcs8wePXqEHadPgc+Bxu4ryDKjRo0K2xRVzVUzMjIsEg729u3b3WdRzSpAOQLnI0QzftdAOUIkqLhnj1X8IwdJxubNEXGdrWv+UgnyFFjMnj3bJTEZNGiQlRZlx1Swpyacp512mpunGrl69eplL7N+/frs2j314du7d68LzIJr87SMagTzc/vtt9vw4cNz1OQ1atTIradKlSoWCT+GClS1PZFQ+BCdKEegHCFScD4C5QiRIK5CBfd/5cqVrUpqakRcZytHSYGWK+gKFUCEq/FSZJuZmWknnniiG8KgtKj55C+//GLHHXec66OnIG7s2LEuoYoooPvuu+/swQcfdI+7du1qiYmJbpmzzz7bzVuzZo3NmzfPZebMj/r3aQqlAx0JB1t0bCJpexCdKEegHCFScD4C5QhlLu6P2Ccucq77C7oNBQ7ylL0yHNVktW7d2tq0aWMl6eabb3Y1hY0bN3a1b+qTpxq1IUOGuB8CDY/wwAMPWIsWLdykvytWrGjnn3++e33VqlXt0ksvdTWOGj4hNTXVrVO1gcq2CQAAAAD7RW92zQIHeQqmypKSupx33nm2ceNGN3SC+uFp6IYmTZq450eMGGG7d++2YcOGuSaZStTy1Vdf5Ri379FHH3VVnKrJ07L9+vVz4+oxRh4AAACAWEmvGRdQp7oCWL16tRts/K677srVD01j0KlmTTVjoRkuY5FqEFUzqM8dKX3yNm/e7GonI6EaGdGJcgTKESIF5yNQjhARxt1jNvFR23zh11ateZeIuM4uaBxS4C1VgKeVhluZ3kiZXrQMAAAAAKDsFDjI++KLL+ziiy/O83k99+mnnxbXdgEAAAAASjLIW7JkiUt6kheNkbd06dKibAMAAAAAoLSDvOTk5HyDOD2nZQAAAAAAURDkKVvl66+/nufzr732mh155JHFtV0AAAAAUHYCh8AQCsqcefzxx7skK7fcckt2Fs1169a5wcQ1FIGGLAAAAACAmBsUPRaDvD59+th//vMfu/766914c8qyqUHIlb4zMTHRnnjiCevbt2/Jbi0AAAAAoHiCPLniiivslFNOsXfffdcWL15sGmKvZcuWduaZZ7rEKwAAAACAKArypEGDBnbjjTeWzNYAAAAAAA5K2Q/bDgAAAAAoNgR5AAAAAJBL9GbXJMgDAAAAgDxFX3ZNgjwAAAAAiCFFCvK2bt1qL7zwgt1+++22efNmN2/WrFm2atWq4t4+AAAAAEBJZtf86aefrH///m5Q9KVLl9rll19uqamp9sEHH9iyZcvstddeK+wqAQAAAABlVZM3fPhwGzp0qC1atMgqVKiQPX/gwIE2YcKE4touAAAAAEBpBHnTp093g6KHGz9v7dq1RdkGAAAAAEBZBXmqvUtLS8s1f+HChVarVq3i2i4AAAAAKDuBQ2gIhT//+c9233332b59+9zjuLg4W758ud122212xhlnlMQ2AgAAAEDZiDsEhlD417/+ZRs2bLDatWvb7t27rVevXnb44YdbSkqKjRw5smS2EgAAAABQMtk1q1SpYhMnTrRvvvnGDZuQlZVlXbp0cRk3AQAAAABRFuRp2ISmTZta37593QQAAAAAiOLmms2bN7djjz3Wnn322eyB0AEAAAAAURrkzZgxw3r27Gn333+/1a9f3yViee+99yw9Pb1kthAAAAAASl3g0Any1P/u4Ycfdhk1//e//7kELBo3T/9fcsklJbOVAAAAAFAm4mI/yPNp6IQ+ffrY888/b+PGjXPNOF999dXi3ToAAAAAQOkEeStWrLCHHnrIOnfubN27d7dKlSrZk08+WdTVAQAAAADKIrvmc889Z2+88Yb98MMP1qpVK7vgggvsww8/dBk3AQAAAABRFuT94x//sHPPPdf+/e9/u1o8AAAAAEAUB3lKuKL+eAAAAACAKA3yfvrpJ2vfvr3Fx8fb3Llz8122Y8eOxbVtAAAAAFA2AoHYDvLULHPt2rVumAT9rZq8QNCH9h/r/8zMzJLcXgAAAAAoRXGxGeQtWbLEatWqlf03AAAAACCKg7wmTZpk/71s2TI7+uijLSEh50szMjJs0qRJOZYFAAAAAET4OHkaAH3z5s255m/bts09BwAAAACIoiDP73sXatOmTW5AdAAAAABAFAyhMHjwYPe/AryhQ4daUlJS9nNKtqIMnGrGCQAAAACIgiCvatWq2TV5KSkplpycnP1c+fLlrUePHnb55ZeXzFYCAAAAQFmIi9HsmvLyyy+7/5s2bWo333wzTTMBAAAAIJqDPN/dd99dMlsCAAAAACj9IE/++9//2rvvvmvLly+3vXv35nhu1qxZB79VAAAAAIDSya75+OOP21/+8herXbu2zZ4924488kirUaOG/f777zZw4MCibQUAAAAAoGyCvKeeesqee+45e/LJJ13C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rhAGa926deC2224rs21C5Fi/fr07uX333XfucVZWVqBu3bru5Obbs2dPoGrVqoFnnnnGPd66dau7caAbCL5Vq1YF4uPjA1988YV7rBsKWu+UKVOyl5k8ebKbt2DBglL8hChJ27dvD7Ro0cL9KPXq1Ss7yKMcoSBuvfXWwLHHHpvn85QjFIRuVl5yySU55g0ePDhw4YUXUo5QIBYS5JXmuUfBpl6j1/reeuutQFJSUmDbtm0lfgRprhmlVNWrZi8DBgzIMV+PJ02aVGbbhcixbds2939qaqr7f8mSJbZ27docZUZjvvTq1Su7zKhM7du3L8cyas7Qvn377GUmT57smiUcddRR2cv06NHDzaPsxY6rr77aTj75ZOvfv3+O+ZQjFMTHH39s3bp1s7POOss1Hz/iiCPs+eefpxyhUI499lj7+uuv7ddff3WPf/zxR5s4caKddNJJnI9QJEtK8VpIy+g1eq3vhBNOcE1Bg5uul5SEEn8HlIiNGzdaZmam1alTJ8d8PVbhxaFNN6+GDx/ufiB1ghG/XIQrM8uWLctepnz58la9evVcy/iv1/+6aAuleZS92PD222/brFmzbPr06bmeoxyhIH7//Xd7+umn3XnojjvusGnTprm+KLqYuvjiiylHKJBbb73V3bBs3bq1lStXzl33jBw50s477zzORyiStaV4LaT/Q99H69S6S+N6iSAvyqlDaejFfeg8HHquueYa++mnn9wdz+IoM6HLhFueshcbVqxYYddff7199dVXLqFTXihHyI8SY6gm74EHHnCPVZOnxAYK/BTkUY5QEO+8846NGTPG3nzzTWvXrp3NmTPHJYFSzciQIUMoRyiyuFK6FirL6yWaa0YpZejRXa3QOwHr16/PddcAhxZlg1JTqfHjx1vDhg2z59etW9f9n1+Z0TJqCqyMUfkts27dulzvu2HDBspeDFATEh1vZetNSEhw03fffWePP/64+9svB5Qj5KdevXrWtm3bHPPatGnjMs0J5yMUxC233GK33XabnXvuudahQweXvfDGG290WX8pRyiKuqV4LaRlQt9H61RT0NK4VifIi1Kq6tVF2NixY3PM1+Ojjz66zLYLZUd3hlSD9/7779s333zjUk4H02OdcILLjE5iuoD3y4zKVGJiYo5l1qxZY/PmzctepmfPnq75jJpf+aZOnermUfaiX79+/Wzu3Lnujrk/qUbmggsucH8rhTnlCAdyzDHH5BrCRf2qmjRp4v7mfISC2LVrl8XH57xU1Q1ufwgFyhEKq1kpXgtpGb1Gr/WplYyares9SlyJp3ZBiQ+h8OKLL7osPzfccIMbQmHp0qXs9UPQVVdd5bJDffvtt4E1a9ZkT7t27cpeRtmktMz777/v0gafd955YdMGN2zY0KU7V8rfvn37hk0brLT6yiSlqUOHDgyhEMOCs2sK5QgFGX4jISEhMHLkyMCiRYsCb7zxRqBixYqBMWPGUI5QYEOGDHFp7P0hFPTbVbNmzcCIESMoR8g3O/Ts2bPdpFBn9OjR7m9/iLHS+g3zh1Do16+fW4fWpXUyhAIK5D//+Y8bi6h8+fKBLl26ZKfLx6FHJ7Jwk8bOC04dfPfdd7v0wUrh+6c//cmd4ILt3r3bnYBSU1MDycnJ7oS1fPnyHMts2rQpcMEFF7hxZjTp7y1btpTaZ0XZBnmUIxTEJ5984i5wdK7R8D7PPfdcjucpRzgQXXTr3KMxXytUqBBo3ry5G/8sPT2dcoQ8jR8/Puz1kG4alPa5R4GlhgLROrQurVNDNpSGOP1T8vWFAAAAAIDSQJ88AAAAAIghBHkAAAAAEEMI8gAAAAAghhDkAQAAAEAMIcgDAAAAgBhCkAcAAAAAMYQgDwAAAABiCEEeAAAAAMQQgjwAAAAAiCEEeQAA5GPo0KEWFxeXa1q8eDH7DQAQkRLKegMAAIh0J554or388ss55tWqVSvH471791r58uVLecsAAMiNmjwAAA4gKSnJ6tatm2Pq16+fXXPNNTZ8+HCrWbOmHX/88W7Z0aNHW4cOHaxSpUrWqFEjGzZsmO3YsSN7Xa+88opVq1bNPv30U2vVqpVVrFjRzjzzTNu5c6e9+uqr1rRpU6tevbpde+21lpmZmSOIHDFihDVo0MCt+6ijjrJvv/2WYwcAyIWaPAAAikhB2VVXXWU//PCDBQIBNy8+Pt4ef/xxF6wtWbLEBXkKzp566qns1+3atcst8/bbb9v27dtt8ODBblLw9/nnn9vvv/9uZ5xxhh177LF2zjnnuNf85S9/saVLl7rX1K9f3z744ANXwzh37lxr0aIFxxAAkC0u4P8qAQCAsH3yxowZYxUqVMieN3DgQNuwYYNt27bNZs+ene9ee++991wguHHjxuyaPAVs6tN32GGHuXlXXnmlvf7667Zu3TqrXLmym6cAToHiM888Y7/99psL5FauXOkCPF///v3tyCOPtAceeIAjBwDIRk0eAAAH0KdPH3v66aezH6u55HnnnWfdunXLtez48eNd0DV//nxLS0uzjIwM27Nnj2uOqdeJmmj6AZ7UqVPHBXR+gOfPW79+vft71qxZrqawZcuWOd4rPT3datSowfEDAORAkAcAwAEoODv88MPDzg+2bNkyO+mkk1zN3D/+8Q9LTU21iRMn2qWXXmr79u3LXi4xMTHH65StM9y8rKws97f+L1eunM2cOdP9Hyw4MAQAgCAPAIBiNGPGDFdz98gjj7i+efLuu+8e9HqPOOIIl4RFNXvHHXdcMWwpACCWkV0TAIBioiaYCvKeeOIJlzxF/ezUp+5gqZnmBRdcYBdffLG9//77LqHL9OnT7cEHH3SJWgAACEaQBwBAMencubMbQkHBV/v27e2NN96wUaNGFcu6NU6fgrybbrrJDb1w6qmn2tSpU90wDQAABCO7JgAAAADEEGryAAAAACCGEOQBAAAAQAwhyAMAAACAGEKQBwAAAAAxhCAPAAAAAGIIQR4AAAAAxBCCPAAAAACIIQR5AAAAABBDCPIAAAAAIIYQ5AEAAABADCHIAwAAAIAYQpAHAAAAABY7/h+OpBRFZXdhnQAAAABJRU5ErkJggg==", - "text/plain": [ - "
" - ] - }, - "metadata": {}, - "output_type": "display_data" }, { - "name": "stdout", - "output_type": "stream", - "text": [ - "first half: -263 second half: +1,787\n" + "cell_type": "markdown", + "id": "md-config", + "metadata": {}, + "source": [ + "## Configuration" + ] + }, + { + "cell_type": "code", + "execution_count": 2, + "id": "config", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Image size: (400, 400) cluster (3, 3)\n", + "Pedestal frames: 1000\n", + "Data frames: 50000 of 100000 in file\n" + ] + } + ], + "source": [ + "base = Path('/mnt/sls_det_storage/moench_data/2603_MaxIVBeamtime/2026032408/process/xrf/')\n", + "f = File(base / 'Cu_factor_10_data_master_0.json')\n", + "pd = File(base / 'Cu_factor_10_pedestal_master_0.json')\n", + "\n", + "n_frames_pd = 1000 # pedestal frames, taken from the head of the file\n", + "N = 50000 # data frames, read after those\n", + "cluster_size = (3, 3)\n", + "rows, cols = f.rows, f.cols\n", + "image_size = (rows, cols)\n", + "\n", + "N_SIGMA = 5\n", + "N_STREAMS = 4 # H2D / kernel / D2H overlap\n", + "BATCH_SIZE = 2000 # frames per find_clusters_batched call\n", + "\n", + "# CPU workers. ClusterFinderMT gives each thread its own ClusterFinder with its\n", + "# own pedestal, and data frames are handed out round-robin -- so at high thread\n", + "# counts each pedestal tracks on only 1/n of the frames while the CUDA finder's\n", + "# single pedestal sees all of them, and the cluster counts drift apart. Keep it\n", + "# low when comparing counts; raise it to measure CPU throughput.\n", + "N_THREADS = 24\n", + "\n", + "# Fixed per-frame output slot the kernel writes into, copied WHOLE on every D2H.\n", + "# Too low does not error -- it TRUNCATES. The CUDA cell prints the peak actually\n", + "# reached, which is how you size this for a new dataset.\n", + "CAP = 3000\n", + "\n", + "capacity = 10_000 # initial ClusterVector capacity, CPU side\n", + "N_BINS = 200\n", + "E_MIN, E_MAX = -2, 4000 # spectrum range, ADU\n", + "\n", + "print(f'Image size: {image_size} cluster {cluster_size}')\n", + "print(f'Pedestal frames: {n_frames_pd}')\n", + "print(f'Data frames: {N} of {f.total_frames} in file')" + ] + }, + { + "cell_type": "markdown", + "id": "md-build", + "metadata": {}, + "source": [ + "## Build the two finders" + ] + }, + { + "cell_type": "code", + "execution_count": 3, + "id": "build", + "metadata": {}, + "outputs": [], + "source": [ + "cf_cpu = ClusterFinderMT(image_size, cluster_size, n_sigma=N_SIGMA,\n", + " capacity=capacity, n_threads=N_THREADS)\n", + "sink = ClusterCollector(cf_cpu)\n", + "\n", + "cf_cuda = ClusterFinderCUDA(image_size, cluster_size, n_sigma=N_SIGMA,\n", + " max_clusters_per_frame=CAP, n_streams=N_STREAMS)" + ] + }, + { + "cell_type": "markdown", + "id": "md-ped", + "metadata": {}, + "source": [ + "## Pedestal\n", + "\n", + "Identical frames into both, so the implementation is the only thing that differs." + ] + }, + { + "cell_type": "code", + "execution_count": 4, + "id": "train", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Pedestal (1000 frames): 2.01s\n" + ] + } + ], + "source": [ + "pd.seek(0)\n", + "t0 = time.perf_counter()\n", + "for _ in range(n_frames_pd):\n", + " pd_img = pd.read_frame()\n", + " cf_cpu.push_pedestal_frame(pd_img.copy())\n", + " cf_cuda.push_pedestal_frame(pd_img.copy())\n", + "print(f'Pedestal ({n_frames_pd} frames): {time.perf_counter() - t0:.2f}s')" + ] + }, + { + "cell_type": "markdown", + "id": "md-io", + "metadata": {}, + "source": [ + "## Read the data frames\n", + "\n", + "Kept out of both timing loops: the two finders run over the same in-memory array." + ] + }, + { + "cell_type": "code", + "execution_count": 5, + "id": "io", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Reading 50000 frames: 31.25s (1,600 FPS, 16.00 GB at 0.51 GB/s)\n" + ] + } + ], + "source": [ + "f.seek(0)\n", + "t0 = time.perf_counter()\n", + "data = f.read_n(N)\n", + "t_io = time.perf_counter() - t0\n", + "gb = f.bytes_per_frame * N / 1e9\n", + "print(f'Reading {N} frames: {t_io:.2f}s ({N / t_io:,.0f} FPS, '\n", + " f'{gb:.2f} GB at {gb / t_io:.2f} GB/s)')" + ] + }, + { + "cell_type": "markdown", + "id": "md-cpu", + "metadata": {}, + "source": [ + "## 1 — CPU, multithreaded" + ] + }, + { + "cell_type": "code", + "execution_count": 6, + "id": "cpu", + "metadata": {}, + "outputs": [ + { + "name": "stderr", + "output_type": "stream", + "text": [ + "100%|███████████████████████████████████████| 50000/50000 [00:12<00:00, 3977.38it/s]\n" + ] + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU MT (24 threads): 12.58s 3,976 FPS 116,015,414 clusters (2320.3/frame)\n" + ] + } + ], + "source": [ + "t0 = time.perf_counter()\n", + "# Pass the frame number: find_clusters(frame) defaults it to 0, and every\n", + "# resulting cluster is then stamped frame 0. Part 2 groups by it.\n", + "for i, frame in enumerate(tqdm(data)):\n", + " cf_cpu.find_clusters(frame, i)\n", + "t_cpu = time.perf_counter() - t0\n", + "\n", + "cf_cpu.stop(); sink.stop()\n", + "clusters_cpu = sink.steal_clusters()\n", + "\n", + "hist_cpu = bh.Histogram(bh.axis.Regular(N_BINS, E_MIN, E_MAX))\n", + "n_clusters_cpu = 0\n", + "for cv in clusters_cpu:\n", + " hist_cpu.fill(cv.sum())\n", + " n_clusters_cpu += cv.size\n", + "\n", + "print(f'CPU MT ({N_THREADS} threads): {t_cpu:.2f}s {N / t_cpu:,.0f} FPS '\n", + " f'{n_clusters_cpu:,} clusters ({n_clusters_cpu / N:.1f}/frame)')" + ] + }, + { + "cell_type": "markdown", + "id": "md-cuda", + "metadata": {}, + "source": [ + "## 2 — CUDA, batched + multi-streamed\n", + "\n", + "`register_input_buffer` page-locks the input once so H2D runs at full DMA\n", + "bandwidth instead of through the driver's staging buffer. It is paid once,\n", + "outside the loop, and released when the dataset is done." + ] + }, + { + "cell_type": "code", + "execution_count": 7, + "id": "cuda", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CUDA batched: 1.87s 26,720 FPS 116,013,348 clusters (2320.3/frame)\n", + " input pinned in 0.53s, once, outside the loop\n", + " vs CPU: 6.72x\n", + " peak clusters in one frame: 2,524 of CAP 3,000\n" + ] + } + ], + "source": [ + "t0 = time.perf_counter()\n", + "cf_cuda.register_input_buffer(data)\n", + "t_pin = time.perf_counter() - t0\n", + "\n", + "clusters_cuda = []\n", + "t0 = time.perf_counter()\n", + "for start in range(0, N, BATCH_SIZE):\n", + " stop = min(start + BATCH_SIZE, N)\n", + " clusters_cuda.extend(\n", + " cf_cuda.find_clusters_batched(data[start:stop], first_frame=start))\n", + "t_cuda = time.perf_counter() - t0\n", + "\n", + "cf_cuda.unregister_input_buffer()\n", + "\n", + "hist_cuda = bh.Histogram(bh.axis.Regular(N_BINS, E_MIN, E_MAX))\n", + "energies = [np.asarray(cv.sum()).ravel() for cv in clusters_cuda if cv.size > 0]\n", + "if energies:\n", + " hist_cuda.fill(np.concatenate(energies))\n", + "n_clusters_cuda = sum(cv.size for cv in clusters_cuda)\n", + "\n", + "print(f'CUDA batched: {t_cuda:.2f}s {N / t_cuda:,.0f} FPS '\n", + " f'{n_clusters_cuda:,} clusters ({n_clusters_cuda / N:.1f}/frame)')\n", + "print(f' input pinned in {t_pin:.2f}s, once, outside the loop')\n", + "print(f' vs CPU: {t_cpu / t_cuda:.2f}x')\n", + "\n", + "# One ClusterVector per frame, so this IS the per-frame maximum.\n", + "peak = max((cv.size for cv in clusters_cuda), default=0)\n", + "print(f' peak clusters in one frame: {peak:,} of CAP {CAP:,}')\n", + "if peak >= CAP:\n", + " print(' !! CAP REACHED -- clusters were dropped; raise CAP and re-run.')" + ] + }, + { + "cell_type": "markdown", + "id": "md-summary", + "metadata": {}, + "source": [ + "## Summary" + ] + }, + { + "cell_type": "code", + "execution_count": 8, + "id": "summary", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + " s FPS us/frame clusters\n", + "CPU MT (24 thr) 12.58 3,976 251.5 116,015,414\n", + "CUDA batched 1.87 26,720 37.4 116,013,348\n", + "\n", + "speedup: 6.72x\n" + ] + } + ], + "source": [ + "print(f'{\"\":<22} {\"s\":>7} {\"FPS\":>10} {\"us/frame\":>10} {\"clusters\":>13}')\n", + "for name, t, n in (('CPU MT (%d thr)' % N_THREADS, t_cpu, n_clusters_cpu),\n", + " ('CUDA batched', t_cuda, n_clusters_cuda)):\n", + " print(f'{name:<22} {t:7.2f} {N / t:10,.0f} {t * 1e6 / N:10.1f} {n:>13,}')\n", + "print(f'\\nspeedup: {t_cpu / t_cuda:.2f}x')" + ] + }, + { + "cell_type": "markdown", + "id": "md-part2", + "metadata": {}, + "source": [ + "# Part 2 — Where the two disagree\n", + "\n", + "The two implementations are not bit-identical by construction: the CUDA finder\n", + "updates its pedestal once per frame, the CPU finder per pixel, so once the\n", + "pedestals drift apart a few pixels land on opposite sides of the threshold.\n", + "\n", + "Three views, cheapest first: the **spectra**, the **cluster sets**, and **where\n", + "in the run** the difference accumulates." + ] + }, + { + "cell_type": "markdown", + "id": "md-spec", + "metadata": {}, + "source": [ + "## Spectra\n", + "\n", + "Both curves should lie on top of each other; the ratio panel is the sensitive\n", + "view." + ] + }, + { + "cell_type": "code", + "execution_count": 9, + "id": "spectra", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": + "iVBORw0KGgoAAAANSUhEUgAAA3kAAAKACAYAAAAhGrGwAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQAAgv1JREFUeJzt3QeYE1XXwPGTbTQBQRFBqgVQUETFgg1EaaJgbyiIICioyPupFBUBEStiAztYQNQXUWwIqCi+iCCIFFFEURCRLh2WbOZ7zt1NSLLZ3exukplM/r/nmU0ymSQ3N5PsnDm3eCzLsgQAAAAA4AppdhcAAAAAABA7BHkAAAAA4CIEeQAAAADgIgR5AAAAAOAiBHkAAAAA4CIEeQAAAADgIgR5AAAAAOAiBHkAAAAA4CIEeQAAAADgIgR5ABxr8eLFcuONN0r9+vWlbNmyctBBB8lJJ50kjz76qGzZsiWwXcuWLc0SL2PGjJHx48fH7fmRPNgXDvjjjz/E4/FEXCZNmpSv7n7//Xe59NJL5eCDDzbf5QsuuEAWLlwYdd0X5/H6+ieeeKL53ahZs6b069dPdu7cmW87Xaf36Ta6rT4mUtkL8swzz8jRRx8tWVlZ5n3/+++/UT8WAOIpI67PDgAl9NJLL8mtt94qDRs2lLvuukuOO+442b9/v3z//ffy/PPPy7fffitTpkxJ2IH9oYceKt26dUvI68G52Bfyu+222+Taa68NWXfMMceE3N64caOcffbZUqVKFXn11VdNQDVy5Ehzcmb+/Pnme16Y4jx+woQJ0qVLF+nRo4c8+eSTsmLFCrnnnnvkp59+kunTp4c8rwaN+viHH35YGjRoIBMnTpRrrrlGfD5fvvcUbtGiRXL77beb1+natatkZGRIxYoVC30MACSMBQAOM2fOHCs9Pd1q166dtXfv3nz379u3z/rggw8Ct88991yzxEvjxo1j/vw+n8/avXu3lSySqazxVJx9ITs729q/f7/lVqtWrbL0MOKxxx4rctu77rrLyszMtP7444/Aum3btlmHHnqodeWVV8bs8V6v16pRo4bVpk2bkMdPmDDBlPWTTz4JrPv444/NuokTJ4Zse8EFF1g1a9Y0z1WYN9980zz+u+++K7L8u3btKnIbAIglmmsCcJyHHnrINH168cUXpUyZMvnu16ZRF198cYGPnzVrlnm8XkZqXhbc9FKbgF199dWmuZa+VvXq1aV169bmLL2qV6+eLFu2TL766qtAUzRd57d9+3b5v//7P9OkVMt1xBFHmOZfu3btCnltfVzfvn1NFvLYY481r/Xaa68VWg9vv/22nHHGGVKhQgXTPK1t27byww8/hGyj2UW9b+XKldKhQwdzvXbt2vKf//xH9u3bF7Jtdna2PPjgg9KoUSPz+tWqVTPNYTVLEkzfX8eOHeW9996TZs2amazJ0KFDzX1aF23atJHy5cubx/fp00c+/vjjkPoePny4yWqsWbMm33vq3r27HHLIIbJ3794C33dRn0lwGTWbe8IJJ5gyHnnkkfL000/ne75oPyPN3mjzO22yV65cOdMs8PTTT5epU6cWuS/497k33njD1L2+hpZdP5cHHnjA3BdO90Ndr/tl+Pv66KOPTN1rOXR/0dv+x+ht3SdOPfVUk9lOBvo5nXfeeVK3bt3AukqVKplM2ocffiherzcmj587d66sW7fO7NfBrrjiCvPdCM7+63Vdp/cF08f+/fff8t133xVYHs0garZQnXbaaeZz9Gf69b4mTZrI119/LS1atDDfFd3v/d9p/f7UqFEj8NkOGDAg377o/17//PPP5nuvn7c+RjOO/vd51llnmfWagYz0W/LPP/9Ir169pFatWma/1/1fv8fhdT127Fhp2rSpeT3NROrvw6BBgwr9PAA4H801AThKTk6OfPHFF3LyySebYCXeNDDS19R+fnXq1JFNmzbJnDlzAn1r9EDw8ssvl8qVK5umesofeO7evVvOPfdc+euvv8xBkQYbGgTcf//9smTJEpk5c2bIwf37778vs2fPNvcffvjhcthhhxUa6N57773mgFMvNUB77LHHTJO1efPmmearftqMVYPem266yQQYenCpgZaWWV/LH8B06tTJvP7dd99tDj7//PNPGTJkiDko1WBBDzr9tK/T8uXLzWvrwaEeTOrBs75fva4Hhlr+t956ywSvwfTAcsSIEfLCCy+YoNJP+1FqfyfdXoOykn4mfhr0abCmQZTWpzbTu+OOO0xdaVBX3M9ID6zffPNNU4/Dhg0zB8ZaD/4grLB9wW/gwIEmMNdgPi0trdDPuCA//vijeZ7Bgweb19IDcw1mdN3nn38eOAmiTRA1IFy1alXIZ5doGnho3Wpgr31mdf8KPgmzZ88e+e233+SSSy7J91j9PPR+Dew1WImkOI9funRpYH2wzMxME7z471d6XYMsLXf4c/rv1+9JJPr5676v+/e4cePMc+tJDz/9rmgQqHWhn5fuC+rXX381+7fut/o90iDukUceMd9p/d0Lpt9r/dx79+5tmqxrU1LdB/SkxeTJk83nrwGcnpjQfVcDS/3d9Ad4ehJAX1f39aOOOso0cdfy6v6sZVb6fdRm8drk9vHHHzfb64kJbdoKIMnFNC8IAKX0zz//mCZQV199ddSPCW+u+eWXX5rn0MtIzcvGjRtnbm/atMncHj16dIma6I0cOdJKS0uz5s+fH7L+v//9b76mYXq7cuXK1pYtW4p8P6tXr7YyMjKs2267LWT9jh07rMMPPzykeVrXrl3Nc7/zzjsh23bo0MFq2LBh4PZbb71ltps8eXLIdlp2XT9mzJjAurp165rmsr/88ku+JnMej8datmxZyPq2bdvmq28t12GHHWaa1vo98sgjpr70cyhItJ+JllHLsmjRonxN7SpVqhRoHhftZ/T111+b24MHDy7RvuDf584555x89w0ZMsTcF073Q10fXB/6vsqVK2f99ddfgXX6HnU7bYYY3Ozv/fffN+unTp1q2eHvv/+2evbsafa92bNnmyaRp59+uinTSy+9FNhu7dq1Zp1+FuG0qaTep020C1Kcx48YMcLcXrduXb5ttQlngwYNArePOeYYs+9Gel/6HA899FCh79//+YXvW7p/6PrPP/+8yCbb2pz3q6++Mtv/+OOP+b7Xwd9X3bZatWpm/cKFCwPrN2/ebL6v/fv3D6zr1auXddBBB1l//vlnyGs+/vjj5vH+73Dfvn2tgw8+uNByAkhOKd1cU892X3TRRaZJkJ4V1bPsxaXHbnr2S88g6hldzTzoWTsAzle1alVzhlszZKNGjTJNITXjFS1tQqdnz7V5nzaB8i/avCpSc1FtbqYDRxTls88+M89zww03hDyvZr80KxX+vPpa+lsWno3QTF1wWbX5oW4X/Jxads2ChT+nPj48s6LNFPX9BmcRlQ5UEU4zahs2bJB3333X3NZ61ezfhRdeGNLctTSfSePGjU0zs2A6WIZmOvyjLkb7GX366afmUpuflsZll10mpaVl1eaefpptUppx1aZ/4euDP+dIgt93cZfCaPNBbVKtzR216aDWvf5f1Wam2gQx/PGRmqxGc19JHl/QttFuF22Z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/v33X2ozjvM9b968mfp1eZ0ffPDBcvjhh5cqsCTIAwAAQKn5A7zDDjtMypcvT7YpDkG0Zkk1y8NI7O6sc8uyZPfu3bJhwwZzu0aNGiV+LoI8AAAAlIoeCPsDvEMOOYTajAOCvNSo83LlyplLDfT0+1TSppsMvAIAAIBS8ffB0wwegNLxf49K07eVIA8AAAAxQTNCwBnfI4I8AAAAAHARgjwAAAAgRZ1zzjkyceJEu4uRMj766CNp1qyZGbUzngjyAAAAIKk+Muhtt90mRx55pJQpU0Zq164tF110kXz++eeBberVq2ea0emifaaaNGkiL7zwQuD+Bx54QE488cR8z60D0uhjZs2aVeDrt2zZ0mzz8MMP57uvQ4cO5j59/j/++EPS0tIC5Yi06HbFCTj0vV999dWBdS+++KIpT6VKlczzRZoSY8SIEdKiRQtTDzrcf0HGjx8vJ5xwgpQtW9ZMCdC3b99Cy7Nv3z7zORx66KFSoUIFufjii+Wvv/4q9mt7ItTL888/H/VgK+3btzePef/990v0vpVOu1CrVq18ddixY0ezLt6BNUEeAAAAUpYGTieffLJ88cUX8uijj8qSJUtk2rRp0qpVK+nTp0/ItsOGDZN169bJ4sWLpXPnztK7d295++23Y1IODSzHjRsXsu7vv/825fIPpa/b6Dotgy7/+c9/pHHjxoHbuvzf//1f1K/59NNPy4033mgCRz8dwr9du3YyaNCgAh+nE4RfccUVcssttxS4zahRo2Tw4MEyYMAAWbZsmQmY27ZtW2h5+vXrJ1OmTJFJkybJN998Izt37jRBkY5wWZzXVlqXwfXStWtXicbo0aML7BMX7Wurm266yQS4kWidP/PMMxJPTKEAAACAlHXrrbeag/p58+aZ7JGfBk/du3cP2bZixYomI6UefPBBeeedd0y256qrrip1OTSY0ef73//+J2eeeWYgE9amTRtZvXq1ua3D6QdPkn3QQQdJRkZGoEzFsWnTJpk5c6Y8+eST+QItVVjmcejQoYHyRbJ161a599575cMPP5TWrVuH1GlBtm3bJq+88oq88cYbcv7555t1b775pglstZz+ALGo1w6fULw4fvzxRxOczp8/P+IcddG+9tixY0327v7775dPP/003/2aobz99tvl999/N9njeCCTBwAAgJS0ZcsWk7XTjF1wgOdXVJM8bYZY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a6Vhw4ZmnZYz3LJly+Syyy6TevXqmfKOHj064nOvXbtWunTpIocccoiUL19eTjzxRFmwwD9FUWRfffWVnHzyyVK2bFlTr88//3yxX3vs2LFywgknSKVKlcxyxhlnyKefflro637zzTdy5plnmrKWK1fOBL5PPukfaTu/SZMmmdfv3LlzyHqv1yv33nuvHHPMMeY963sYNmxYyGd43333yYABAxwZWBPkAXC11t6nzVKQNp550jFtjsjwageWh45IaBkBAPb5448/TDCimbBHH31UlixZItOmTZNWrVpJnz59SvSc7dq1M0GWPrcGJfpcd9xxh3Ts2NEED8FWr14t3377rfTt21deeeWVEr3enDlz5Ndff5UrrrgisG7fvn1SrVo1E/g1bdo04uN2795tgpeHH35YDj/88IjbbN261QRNGrDqe/npp5/kiSeekIMPPrjA8qxatUo6dOggZ599tvzwww8yaNAguf3222Xy5MnFeu1atWqZ+7///nuznHfeedKpUycTIBakQoUKpi6//vprWb58uQnUdHnxxRfzbfvnn3/K//3f/5lyhnvkkUfkhRdekKeeesq8Z903HnvsMXnmmWcC21x44YWybds2+eyzz8Rp6JMHIKVl68+gJZLlX5GTbW+BAAAJdeutt5pMzrx580yA4Ne4cWPp3r17iZ5Ts4H+wOWII46Qk046SU4//XRp3bq1jB8/Xnr06BHYdty4cSb4u+WWW+TUU081Ga3gckRDs1GaDdSsmZ9myDRAUZpFLKi5oS5KM1KRaLCjmU0tZ/BzF0azdnXq1Alk54499lgTpD3++OMmexfta2s2NdiIESNMdk8znvr5RNKsWTOzBJdVm1TOnj1bbr755sD6nJwcue6662To0KHmvn///TfkeTTwvvjii02wqlnW+vXry1tvvWXeh5+u1/t1ffv27cVJyOQBSGknecdLE++bIvdtzF3O/j+RFrfbXSwAQAJs2bLFZO00YxcpsCosW1VcmoXSjFpwHy7LskzwpE0htVlhgwYN5J133in2c2vW6pRTTpF4mDp1qnluzRIedthhJoB66aWXCn2MBkgadAZr27atCZD2799fonJoUKbB7K5du0yzzWj98MMPJtN57rnnhqzXppea6bzpppsiPu6ss84y2d0VK1aY2z/++KNpCqpBXTANzDVIdBoyeQBcbWlGl7xr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yPfjBkHHbfn+T5Ti84nwOtpg00n7ebTlKFWQ9+uvv8qrr74qr732msmM6SAoxbFz505ZuXJlyBQJixYtMpOr6yia2ixz7dq18vrrr5v7dT68Z5991syTp6N56kAsGli+9VbYATgSN9+Rk5SmM3BJOn4XNN9RccpR1GsETy/BfEDuGoI/lSZSL+4AAcFS+fMq6reksLn77MRomvYqbNTpVFfY1CeJUppRPIMHdQo/0E+1AVGKOyVJCsooSTvQd955xwRXc+fOlZycHHnyySele/fuctBBBxXruXT6g1atWgVua/CmunbtKuPHjzf97FavXh24v379+vLJJ5/InXfeaSZi18nQn376aaZPSBQ3z3dU3B/HWP24lCYg5J939DjotHckzmgOaviMgNhMpB78fXJS8G+3RA39H02mrqA5EOM17YEbpVpQG88gb968efLyyy/L22+/bQZA6dKli7z77rtSq1YtOf/884sd4KmWLVuajrsF0UAv3LnnnisLFy4s9mshRjgQs+/HpaiDaiaGh90KG248URM7I7E4KE0MshbJ83+7NJk6Wp7AjiCvRYsWctttt5lgr2HDCANIALD3oDq4/16k+QXDOW3OH7jnIDSagxonzAnmNuHz7SXiO85BaWLwW518mdaSntTiZDoSHeSdd955ponmhg0b5Prrrzf975wylCiQksKbmQT33ytsIItIB+FkAVFa4f3DCgviEtVsKpXY2Zybg1Jn44RK/JFpRTIHeTqK5Zo1a2TcuHFyyy23mL55V111lbmPYM+laIaTvGd2izrrG35mkY76iKWigjiyEolpzm1HZg/OCuQ4oZIYRX2vCLTh9IFXdH65+++/3ywzZswwI2vqJJidOnWSyy+/3CwnnXRS/EqLxKIZTmqOMuaGg0OnjwTrdsm0r7hVPDN7fL+cpbBAju+i/aIJtIszqA4QpRJPoXDBBReYRadOePPNN03A98gjj5jRNuEiNMNJvX86bhjF080jwQKxHv20uPh+OQuBXHJ/PjT1RJyUejJ0nXFdB2TRhVEvk0w0/bY4o+ROxWnqmawjIXKCAggVyww93y+kmng1uSRIhxOCPJ/PZ6Y1eO+99+SPP/4wffF07jptpqmDsdBUM8kUNgGxIgMCAO5Qmgw9/bOR6ujbCDcHeTqf3cUXX2wmI2/atKkcf/zxZt3y5culW7duJvB7//3341taxB5nYxGNZO+jB6S60mTo6Z+NVMf/O7g5yNMM3tdffy2ff/65tGrVKuS+L774Qjp37iyvv/663HDDDfEoJwC7uKGPHoDSZ+78834BANwT5L311lsyaNCgfAGefw69AQMGyIQJEwjyALdxSx89AAVn6IOz82TuACB1grzFixfLo48+WuD97du3l6effjpW5QIAAInI0EfKzpO5A4DUCPK2bNki1atXL/B+vU+nU4DDMb8RACA4Qx8+NyYjKwNIdlv4TYs6yNP573Ti84Kkp6eL1+uN1UeDeGF+I7gRo/8BJcfIgQDchN+04o+uqaNolilTJuL9+/bti/apYDea4SBefXnsQh8ioOTs/v4CQCzxm1a8IK9r165FbsPImkAKKKovj104eQEAAFC8IG/cuHHRbgoglfryAAAAwFHS7C4AAAAAAMCGTJ7Oj+fxePKtr1y5sjRs2FD69OkjtWvXjmHRAAAAAABxC/JOPPHEiOv//fdf+eSTT+TZZ5+Vb775psDtAAAAAAAOCvKefPLJQu/XTN6gQYNMwAcAccWUCQAAAKUP8orSq1cvadu2bayeDrHCwTDciCkTAAAA4h/klStXTvbu3Rurp0OscDAMt2LKBAAAgPgGedOnT5cGDRrE6ukQSxwMAwAAACkj6iBv6tSpEddv27ZN5s+fL6+88oqMHz8+lmUDkAx0QvTg+fJ0svTgufQAAADgzCCvc+fOEddXrFhRGjVqZAK8K664IpZlA+B0GtCFB3wAAABIjiDP5/PFtyQAkk94xi44owcAAABbpNnzsgAAAAAAW4O8L774Qo477jjZvn17xH55jRs3ltmzZ8e6fABwYDoQzRTqQrNQAACA0jfXHD16tPTs2VMqVaqU777KlSubefJGjRolZ599drRPiXhgXjykwnQguoT3BwQAAEDxMnk//vijtGvXrsD727RpIwsWLIj26RDvA2E/DobhxulAdGEETwAAgNJl8tavXy+ZmZkF3p+RkSEbN26M9ukQT8yLBwAAAKSsqIO8I444QpYsWSJHH310xPsXL14sNWrUiGXZEA2aZwIAAAAoSXPNDh06yP333y979+7Nd9+ePXtkyJAh0rFjx2ifDrFC80wAAAAAJcnk3XvvvfLee+9JgwYNpG/fvtKwYUPxeDyyfPlyee655yQnJ0cGDx4c7dMhlmieCQAAAKC4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", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + } + ], + "source": [ + "fig, (ax_spec, ax_ratio) = plt.subplots(\n", + " 2, 1, figsize=(9, 6.5), sharex=True, gridspec_kw={'height_ratios': [3, 1]})\n", + "\n", + "edges = hist_cpu.axes[0].edges\n", + "cpu_vals, cuda_vals = hist_cpu.values(), hist_cuda.values()\n", + "\n", + "ax_spec.stairs(cpu_vals, edges, label=f'CPU MT ({n_clusters_cpu:,})',\n", + " color='C0', linewidth=1.4)\n", + "ax_spec.stairs(cuda_vals, edges, label=f'CUDA ({n_clusters_cuda:,})',\n", + " color='C1', linestyle='--', linewidth=1.4)\n", + "ax_spec.set_ylabel('Counts')\n", + "ax_spec.set_title(f'Cluster energy spectrum — {N:,} frames')\n", + "ax_spec.legend()\n", + "ax_spec.grid(alpha=0.2)\n", + "\n", + "with np.errstate(divide='ignore', invalid='ignore'):\n", + " ratio = np.where(cpu_vals > 0, cuda_vals / cpu_vals, np.nan)\n", + "ax_ratio.stairs(ratio, edges, color='C1', linewidth=1.2)\n", + "ax_ratio.axhline(1.0, color='gray', linewidth=0.5)\n", + "ax_ratio.set_ylabel('CUDA / CPU')\n", + "ax_ratio.set_xlabel('Energy [ADU]')\n", + "ax_ratio.set_ylim(0.9, 1.1)\n", + "ax_ratio.grid(alpha=0.3)\n", + "\n", + "plt.tight_layout()\n", + "plt.show()" + ] + }, + { + "cell_type": "markdown", + "id": "md-sets", + "metadata": {}, + "source": [ + "## Cluster sets\n", + "\n", + "Every cluster is keyed by `(frame, x, y)`, so the two results can be differenced\n", + "directly — no per-frame Python loop.\n", + "\n", + "A cluster whose centre moved by one pixel was *relabelled*, not gained or lost,\n", + "so those are counted separately from genuine adds and drops." + ] + }, + { + "cell_type": "code", + "execution_count": 10, + "id": "sets", + "metadata": {}, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "CPU 116,015,414 clusters\n", + "CUDA 116,013,348 clusters net -2,066 (0.0018%)\n", + "\n", + "exact-position mismatches CPU-only 67,148 CUDA-only 65,082\n", + " of which shifted by 1 px 52,084 51,984\n", + " genuine add / drop 15,064 13,098\n", + "\n", + "frames touched by a genuine difference: 20,426 of 50,000\n", + "[34.1s]\n" + ] + } + ], + "source": [ + "def cluster_keys(vectors):\n", + " \"\"\"One int64 key per cluster: frame * rows*cols + y * cols + x.\n", + "\n", + " Centres are unique within a frame, so keys are globally unique and the sets\n", + " below need only a sort, never a np.unique.\n", + " \"\"\"\n", + " n = len(vectors)\n", + " sizes = np.fromiter((cv.size for cv in vectors), np.int64, n)\n", + " frames = np.repeat(\n", + " np.fromiter((cv.frame_number for cv in vectors), np.int64, n), sizes)\n", + " xy = [np.asarray(cv) for cv in vectors if cv.size]\n", + " if not xy:\n", + " return np.empty(0, np.int64), frames\n", + " x = np.concatenate([v['x'] for v in xy]).astype(np.int64)\n", + " y = np.concatenate([v['y'] for v in xy]).astype(np.int64)\n", + " return frames * (rows * cols) + y * cols + x, frames\n", + "\n", + "\n", + "t0 = time.perf_counter()\n", + "k_cpu, fr_cpu = cluster_keys(clusters_cpu)\n", + "k_cuda, fr_cuda = cluster_keys(clusters_cuda)\n", + "\n", + "# Sort ONCE. np.setdiff1d and np.isin each re-sort their argument on every call,\n", + "# and the shift check below calls one eight times per direction -- about twenty\n", + "# sorts of a 27-million-element array. Two sorts plus binary search instead.\n", + "s_cpu = np.sort(k_cpu)\n", + "s_cuda = np.sort(k_cuda)\n", + "\n", + "\n", + "def present_in(vals, sorted_keys):\n", + " \"\"\"Which of `vals` appear in an already-sorted key array.\"\"\"\n", + " if sorted_keys.size == 0:\n", + " return np.zeros(vals.size, dtype=bool)\n", + " i = np.searchsorted(sorted_keys, vals)\n", + " np.clip(i, 0, sorted_keys.size - 1, out=i)\n", + " return sorted_keys[i] == vals\n", + "\n", + "\n", + "cpu_only = s_cpu[~present_in(s_cpu, s_cuda)]\n", + "cuda_only = s_cuda[~present_in(s_cuda, s_cpu)]\n", + "\n", + "\n", + "def drop_shifted(only, sorted_other):\n", + " \"\"\"Drop mismatches that have a counterpart within 1 px in the other set.\"\"\"\n", + " keep = np.ones(only.size, dtype=bool)\n", + " for dy in (-1, 0, 1):\n", + " for dx in (-1, 0, 1):\n", + " if dx or dy:\n", + " keep &= ~present_in(only + dy * cols + dx, sorted_other)\n", + " return only[keep]\n", + "\n", + "\n", + "cpu_real = drop_shifted(cpu_only, s_cuda)\n", + "cuda_real = drop_shifted(cuda_only, s_cpu)\n", + "touched = np.unique(np.concatenate([cpu_real, cuda_real]) // (rows * cols)).size\n", + "\n", + "print(f'CPU {k_cpu.size:>12,} clusters')\n", + "print(f'CUDA {k_cuda.size:>12,} clusters net {k_cuda.size - k_cpu.size:+,}'\n", + " f' ({abs(k_cuda.size - k_cpu.size) / k_cpu.size:.4%})')\n", + "print()\n", + "print(f'exact-position mismatches CPU-only {cpu_only.size:>8,} '\n", + " f'CUDA-only {cuda_only.size:>8,}')\n", + "print(f' of which shifted by 1 px {cpu_only.size - cpu_real.size:>17,} '\n", + " f'{cuda_only.size - cuda_real.size:>17,}')\n", + "print(f' genuine add / drop {cpu_real.size:>17,} {cuda_real.size:>17,}')\n", + "print(f'\\nframes touched by a genuine difference: {touched:,} of {N:,}')\n", + "print(f'[{time.perf_counter() - t0:.1f}s]')" + ] + }, + { + "cell_type": "markdown", + "id": "md-drift", + "metadata": {}, + "source": [ + "## Where in the run it accumulates\n", + "\n", + "Cumulative `CUDA − CPU` against frame index. A curve that rises and then flattens\n", + "means the two pedestals settled into agreement and the difference is a start-up\n", + "transient; a steadily sloping line would mean a persistent per-frame bias." + ] + }, + { + "cell_type": "code", + "execution_count": 11, + "id": "drift", + "metadata": {}, + "outputs": [ + { + "data": { + "image/png": + "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+/btjnXr1pkhFNjD3Jw5c5R3JFlce9fU8UcS07NnT3P+2rZtm+n9ktclHDLBvvaNlPOXgrwI8M477zhKlCjhSJ8+vaNatWqx3aBL+Fq4cKEJ7jwfjz/+eGwXwuximN0IsyvfO+64wxzoXJ0/f94ciHLnzu3IlCmTOXDt2rXLbZ6jR486Hn30UXPw5IPPjx8/7jYPA0uOs8ZlcFlcJrsrluDlLe/wwbHzbMpDEp8nn3wy9pyTL18+c4FjB3jKO+KPIE/HH0mIPe5dunTpzLh0LVu2dPz1118Rl3+i+Cf1ywtFRERERETkWlCbPBERERERkTCiIE9ERERERCSMKMgTEREREREJIwryREREREREwoiCPBERERERkTCiIE9ERERERCSMKMgTEREREREJIwryREREREREwoiCPBERERERkTCiIE9ERCSJ2rdvj6ioqDiPLVu2aF2KiEjApQ10AkREREJR06ZNMWXKFLdp+fLlc3t96dIlpE+f/hqnTEREIp1K8kRERJIhQ4YMKFiwoNujYcOG6Nq1K3r06IG8efOicePGZt7Ro0ejcuXKyJIlC4oVK4YuXbrgzJkzscv68MMPkTNnTnz33XeoUKECMmfOjAceeABnz57F1KlTUbJkSeTKlQvPPfccrl696hZE9u7dG0WKFDHLvuWWW/DLL79oe4qIRDiV5ImIiPgRg7LOnTtj6dKlcDgcZlp0dDTGjh1rgrXt27ebII/B2fjx42M/d+7cOTPPjBkzcPr0abRs2dI8GPz98MMP2LZtG1q1aoU6deqgTZs25jNPPPEEduzYYT5TuHBhzJw505Qwrl+/HuXKldN2FRGJUFEO+wwkIiIiPrfJmz59OjJmzBg7rVmzZjh8+DBOnjyJNWvWJPj5L774wgSCR44ciS3JY8DGNn1lypQx0zp16oSPPvoIBw8eRNasWc00BnAMFCdOnIitW7eaQG7Pnj0mwLM1atQIN998M4YNG6atKSISoVSSJyIikgz169fHhAkTYl+zuuTDDz+MGjVqxJl34cKFJuj6+++/cerUKVy5cgUXLlww1TH5OWIVTTvAowIFCpiAzg7w7GmHDh0yz1evXm1KCsuXL+/2XRcvXkSePHm0TUVEIpiCPBERkWRgcFa2bFmv013t3LkTd911lymZGzJkCHLnzo0lS5agQ4cOuHz5cux86dKlc/sce+v0Ni0mJsY85/80adLgjz/+MP9duQaGIiISeRTkiYiIpKJVq1aZkrtRo0aZtnn0+eefp3i5N910k+mEhSV7t99+ux9SKiIi4UK9a4qIiKQiVsFkkDdu3DjTeQrb2bFNXUqxmuajjz6Kdu3a4euvvzYduqxcuRKvv/666ahFREQil4I8ERGRVHTjjTeaIRQYfFWqVAkff/wxhg8f7pdlc5w+Bnk9e/Y0Qy/ce++9WL58uRmmQUREIpd61xQREREREQkjKskTEREREREJIwryREREREREwoiCPBERERERkTCiIE9ERERERCSMKMgTEREREREJIwryREREREREwoiCPBERERERkTCiIE9ERERERCSMKMgTEREREREJIwryREREREREwoiCPBERERERkTCiIE9ERERERATh4/8iP+7Jqb/ssQAAAABJRU5ErkJggg==", + "text/plain": [ + "
" + ] + }, + "metadata": {}, + "output_type": "display_data" + }, + { + "name": "stdout", + "output_type": "stream", + "text": [ + "first half: -468 second half: -1,598\n" + ] + } + ], + "source": [ + "cnt_cpu = np.bincount(fr_cpu, minlength=N)\n", + "cnt_cuda = np.bincount(fr_cuda, minlength=N)\n", + "cum = np.cumsum(cnt_cuda.astype(np.int64) - cnt_cpu.astype(np.int64))\n", + "\n", + "fig, ax = plt.subplots(figsize=(9, 3.6))\n", + "ax.plot(cum, color='C1', linewidth=1.2)\n", + "ax.axhline(0, color='gray', linewidth=0.5)\n", + "ax.set_xlabel('Frame')\n", + "ax.set_ylabel('cumulative CUDA - CPU')\n", + "ax.set_title(f'Cluster-count difference accumulating over {N:,} frames '\n", + " f'(final {cum[-1]:+,})')\n", + "ax.grid(alpha=0.25)\n", + "plt.tight_layout()\n", + "plt.show()\n", + "\n", + "half = N // 2\n", + "print(f'first half: {cum[half - 1]:+,} second half: {cum[-1] - cum[half - 1]:+,}')" ] } ], - "source": [ - "cnt_cpu = np.bincount(fr_cpu, minlength=N)\n", - "cnt_cuda = np.bincount(fr_cuda, minlength=N)\n", - "cum = np.cumsum(cnt_cuda.astype(np.int64) - cnt_cpu.astype(np.int64))\n", - "\n", - "fig, ax = plt.subplots(figsize=(9, 3.6))\n", - "ax.plot(cum, color='C1', linewidth=1.2)\n", - "ax.axhline(0, color='gray', linewidth=0.5)\n", - "ax.set_xlabel('Frame')\n", - "ax.set_ylabel('cumulative CUDA - CPU')\n", - "ax.set_title(f'Cluster-count difference accumulating over {N:,} frames '\n", - " f'(final {cum[-1]:+,})')\n", - "ax.grid(alpha=0.25)\n", - "plt.tight_layout()\n", - "plt.show()\n", - "\n", - "half = N // 2\n", - "print(f'first half: {cum[half - 1]:+,} second half: {cum[-1] - cum[half - 1]:+,}')" - ] - } - ], - "metadata": { - "kernelspec": { - "display_name": "Python 3 (ipykernel)", - "language": "python", - "name": "python3" - }, - "language_info": { - "codemirror_mode": { - "name": "ipython", - "version": 3 + "metadata": { + "kernelspec": { + "display_name": "Python 3 (ipykernel)", + "language": "python", + "name": "python3" }, - "file_extension": ".py", - "mimetype": "text/x-python", - "name": "python", - "nbconvert_exporter": "python", - "pygments_lexer": "ipython3", - "version": "3.11.15" - } - }, - "nbformat": 4, - "nbformat_minor": 5 -} + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.11.15" + } + }, + "nbformat": 4, + "nbformat_minor": 5 + }