Save changes

This commit is contained in:
kferjaoui committed 2026-08-06 14:01:23 +02:00
1 parent 3504d96336
commit ce256dd30f
10 files changed
+1962 -233

No files matched your search

+515 -122
View File
@@ -16,7 +16,10 @@
"import boost_histogram as bh\n",
"import time\n",
"\n",
"from aare import File, ClusterFinder, ClusterFinderMT, ClusterCollector, ClusterFinderCUDA"
"from aare import File, ClusterFinder, ClusterFinderFrozen, ClusterFinderMT, ClusterCollector, ClusterFinderCUDA, ClusterFinderCUDAGraph\n",
"\n",
"from helper import (print_pinning_budget, centers, only_sets, footprint_mask, shift_dist,\n",
" train_pedestal, scan_mismatches, plot_masked_mismatch)"
]
},
{
@@ -26,8 +29,8 @@
"metadata": {},
"outputs": [],
"source": [
"# Helpers\n",
"N_BINS = 200\n",
"\n",
"def make_hist(clusters):\n",
" h = bh.Histogram(bh.axis.Regular(N_BINS, -2, 4000))\n",
" h.fill(clusters.sum())\n",
@@ -53,7 +56,7 @@
"text": [
"Image size: (400, 400)\n",
"Pedestal frames: 1000\n",
"Data frames: 50000\n"
"Data frames: 20000\n"
]
}
],
@@ -62,12 +65,13 @@
"f = File(base / 'Moench03new/cu_half_speed_master_4.json')\n",
"\n",
"n_frames_pd = 1000\n",
"N = 50000 #88999\n",
"cluster_size = (9, 9)\n",
"N = 20000 #88999\n",
"cluster_size = (3, 3)\n",
"rows = f.rows\n",
"cols = f.cols\n",
"image_size = (rows, cols)\n",
"capacity = 50_000 #3_000_000\n",
"BATCH_SIZE = 2000\n",
"\n",
"print(f'Image size: {image_size}')\n",
"print(f'Pedestal frames: {n_frames_pd}')\n",
@@ -77,6 +81,37 @@
{
"cell_type": "code",
"execution_count": 4,
"id": "0fbb5adb",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"── System RAM ──────────────────────────────────────────\n",
" Total RAM : 125.1 GiB\n",
" Currently available : 38.2 GiB (free + reclaimable cache)\n",
" Reserved headroom : 4.0 GiB (OS + CUDA context)\n",
" Safe pinning budget : 34.2 GiB\n",
"\n",
"── Frame layout ────────────────────────────────────────\n",
" Frame size : 400 × 400 × 2 B = 312.5 kB\n",
"\n",
"── Pinning estimate ────────────────────────────────────\n",
" Max frames pinnable : 114,685 (34.2 GiB)\n",
"\n",
" Note: no swap on this machine — exceeding available RAM\n",
" will trigger the OOM killer. Stay within the budget.\n"
]
}
],
"source": [
"print_pinning_budget(rows, cols)"
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "3e49d862-45f4-4d37-a078-947e6b9ec9e3",
"metadata": {},
"outputs": [
@@ -86,7 +121,7 @@
"500000"
]
},
"execution_count": 4,
"execution_count": 5,
"metadata": {},
"output_type": "execute_result"
}
@@ -113,7 +148,7 @@
},
{
"cell_type": "code",
"execution_count": 5,
"execution_count": 6,
"id": "6ee7c469-5a23-421a-9bb4-a76e8f57a0c1",
"metadata": {},
"outputs": [],
@@ -123,13 +158,25 @@
},
{
"cell_type": "code",
"execution_count": 6,
"execution_count": 7,
"id": "bc21879d-a795-43e3-8eed-0be0b4cfccd5",
"metadata": {},
"outputs": [],
"source": [
"N_STREAMS = 1\n",
"N_SIGMA = 5"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "9d8f69bf-27f6-48c8-b2da-4157d67e6b04",
"metadata": {},
"outputs": [],
"source": [
"if(SERIAL):\n",
" cf_cpu = ClusterFinder(image_size, cluster_size, capacity=capacity)\n",
" # cf_cpu = ClusterFinder(image_size, cluster_size, capacity=capacity)\n",
" cf_cpu = ClusterFinderFrozen(image_size, cluster_size, n_sigma=N_SIGMA, capacity=capacity)\n",
"else:\n",
" cf_cpu = ClusterFinderMT(image_size, cluster_size, capacity=capacity, n_threads=48)\n",
" sink = ClusterCollector(cf_cpu)"
@@ -137,48 +184,78 @@
},
{
"cell_type": "code",
"execution_count": 7,
"execution_count": 9,
"id": "00e2f63a-5e64-4d3e-becf-5e2835c6d712",
"metadata": {},
"outputs": [],
"source": [
"# Runs the destructor under the hood in case cf_cuda has already been constructed\n",
"# del cf_cuda\n",
"cf_cuda = None"
"cf_cuda_v1 = None\n",
"cf_cuda = None\n",
"cf_async = None\n",
"cf_graph = None"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "cbdcb805-708b-4205-bda9-2aa163d0e81f",
"execution_count": 10,
"id": "5d961cf4-e286-4c2c-a4e1-50cefc8d0b75",
"metadata": {},
"outputs": [],
"source": [
"N_STREAMS = 5\n",
"cf_cuda = ClusterFinderCUDA(image_size, \n",
"cf_cuda_v1 = ClusterFinderCUDA(image_size, \n",
" cluster_size, \n",
" n_sigma=7, \n",
" n_sigma=N_SIGMA, \n",
" max_clusters_per_frame=1500,\n",
" n_streams=N_STREAMS) "
]
},
{
"cell_type": "code",
"execution_count": 9,
"execution_count": 11,
"id": "cbdcb805-708b-4205-bda9-2aa163d0e81f",
"metadata": {},
"outputs": [],
"source": [
"cf_cuda = ClusterFinderCUDA(image_size, \n",
" cluster_size, \n",
" n_sigma=N_SIGMA, \n",
" max_clusters_per_frame=1500,\n",
" n_streams=N_STREAMS) "
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "f70d0805-a1e9-488b-8ec7-487900f7e026",
"metadata": {},
"outputs": [],
"source": [
"cf_async = ClusterFinderCUDA(image_size,\n",
" cluster_size,\n",
" n_sigma=7,\n",
" n_sigma=N_SIGMA,\n",
" max_clusters_per_frame=1500,\n",
" n_streams=N_STREAMS)"
]
},
{
"cell_type": "code",
"execution_count": 10,
"execution_count": 13,
"id": "09c3ec73",
"metadata": {},
"outputs": [],
"source": [
"cf_graph = ClusterFinderCUDAGraph(image_size,\n",
" cluster_size,\n",
" n_sigma=N_SIGMA,\n",
" max_clusters_per_frame=1500,\n",
" n_streams=N_STREAMS)"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "1546f405-1bf6-4073-ab35-8134be695a6c",
"metadata": {},
"outputs": [
@@ -186,7 +263,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"Pedestal (1000 frames): 0.659s\n"
"Pedestal (1000 frames): 1.030s\n"
]
}
],
@@ -195,8 +272,10 @@
"for _ in range(n_frames_pd):\n",
" img = f.read_frame()\n",
" cf_cpu.push_pedestal_frame(img.copy())\n",
" cf_cuda_v1.push_pedestal_frame(img.copy())\n",
" cf_cuda.push_pedestal_frame(img.copy())\n",
" cf_async.push_pedestal_frame(img.copy())\n",
" cf_graph.push_pedestal_frame(img.copy())\n",
"print(f'Pedestal ({n_frames_pd} frames): {time.perf_counter() - t0:.3f}s')"
]
},
@@ -210,7 +289,7 @@
},
{
"cell_type": "code",
"execution_count": 11,
"execution_count": 15,
"id": "4573be3d-5ba8-4e18-bab0-c874f2b7dcb2",
"metadata": {},
"outputs": [
@@ -218,7 +297,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"Reading 50000 frames: 2.294s (21800 FPS, 6652.882 GB/s)\n"
"Reading 20000 frames: 2.424s (8252 FPS, 2518.340 GB/s)\n"
]
}
],
@@ -241,7 +320,7 @@
},
{
"cell_type": "code",
"execution_count": 12,
"execution_count": 16,
"id": "ad47af54-c2ba-441a-a874-88dfe04d8a68",
"metadata": {},
"outputs": [],
@@ -251,7 +330,7 @@
},
{
"cell_type": "code",
"execution_count": 13,
"execution_count": 17,
"id": "fbb14fda-2852-4b73-ba2c-9952abac1d99",
"metadata": {},
"outputs": [
@@ -259,14 +338,14 @@
"name": "stderr",
"output_type": "stream",
"text": [
"100%|█████████████████████████████████████████████████████████████████████████████████████████████████| 50000/50000 [07:46<00:00, 107.08it/s]\n"
"100%|███████████████████████████████████████████████████████████████████████████████████████████████████████████| 20000/20000 [00:49<00:00, 400.34it/s]\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU clustering: 466.937s (107 FPS, 50982358 clusters, 1019.65/frame)\n"
"CPU clustering: 49.960s (400 FPS, 27665227 clusters, 1383.26/frame)\n"
]
}
],
@@ -302,94 +381,116 @@
"id": "8cfd7091-8020-49ff-b033-28bf773983d8",
"metadata": {},
"source": [
"## CUDA clustering"
"## CUDA clustering\n",
"Non-batched: 1 stream + pageable memory (i.e. no pinning)"
]
},
{
"cell_type": "code",
"execution_count": 14,
"execution_count": 18,
"id": "4f94cf35-5796-463d-bed8-f44a02d91fc6",
"metadata": {},
"outputs": [],
"source": [
"BATCHED = True\n",
"BATCH_SIZE = 3000"
"# Simplest: (non-batched) per-frame run on non-pinned data\n",
"cf_cuda_v1.reset_timers()\n",
"t0 = time.perf_counter()\n",
"\n",
"n_clusters_cuda_v1 = 0\n",
"hist_cuda_v1 = None\n",
"\n",
"# steal the clusters as we go rather than at the end of the dataset \n",
"# which might trigger an std::bad_alloc...\n",
"for idx, frame in enumerate(data):\n",
" cf_cuda_v1.find_clusters(frame)\n",
" clusters_frame = cf_cuda_v1.steal_clusters(realloc_same_capacity=True)\n",
"\n",
" n_clusters_cuda_v1 += clusters_frame.size\n",
"\n",
" h = make_hist(clusters_frame)\n",
" hist_cuda_v1 = h if hist_cuda_v1 is None else hist_cuda_v1 + h\n",
" \n",
"t_cuda_v1 = time.perf_counter() - t0\n",
"kernel_ms = cf_cuda_v1.avg_kernel_time_ms()"
]
},
{
"cell_type": "code",
"execution_count": 42,
"id": "50848f8c-2e66-45b8-b6d5-ca80f368e6ba",
"execution_count": 19,
"id": "e2ad507c-0d8d-4748-8b32-2a8647dee605",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CUDA clustering: 2.227s (8982 FPS, 27665079 clusters, 1383.25/frame)\n",
" Kernel only: 0.032 ms/frame\n",
" PCIe + overhead: 0.079 ms/frame\n",
"Speedup (CPU / CUDA): 22.44×\n"
]
}
],
"source": [
"print(f'CUDA clustering: {t_cuda_v1:.3f}s ({N/t_cuda_v1:.0f} FPS, '\n",
" f'{n_clusters_cuda_v1} clusters, {n_clusters_cuda_v1/N:.2f}/frame)')\n",
"print(f' Kernel only: {kernel_ms:.3f} ms/frame')\n",
"print(f' PCIe + overhead: {t_cuda_v1*1000/N - kernel_ms:.3f} ms/frame')\n",
"print(f'Speedup (CPU / CUDA): {t_cpu / t_cuda_v1:.2f}×')"
]
},
{
"cell_type": "markdown",
"id": "a3410e04-1a02-43c3-8d85-e6de9c9df103",
"metadata": {},
"source": [
"## CUDA clustering (batched + multi-streamed + pinned dataset)"
]
},
{
"cell_type": "code",
"execution_count": 20,
"id": "18649d77-5269-4155-8e3a-fcd7107ee22b",
"metadata": {},
"outputs": [],
"source": [
"if(BATCHED):\n",
" # Before warmup, pin the fixed size buffer\n",
" batch_buffer = np.empty((BATCH_SIZE, rows, cols), dtype=np.uint16)\n",
" cf_cuda.register_input_buffer(batch_buffer) # fixed ~610 MB for 2000 frames\n",
"# TEST\n",
"# Before warmup, pin the whole dataset (no additional copies, just pinning the buffer)\n",
"cf_cuda.register_input_buffer(data)\n",
"\n",
" # Warmup: first kernel launch pays CUDA context + pedestal H2D upload cost\n",
" _ = cf_cuda.find_clusters_batched(data[0:BATCH_SIZE], first_frame=0)\n",
" \n",
" clusters_cuda_per_frame = []\n",
"clusters_cuda_per_frame = []\n",
"\n",
" cf_cuda.reset_timers()\n",
" t0 = time.perf_counter()\n",
" for start in range(0, N, BATCH_SIZE):\n",
" stop = min(start + BATCH_SIZE, N)\n",
" batch_buffer[:stop-start] = data[start:stop] # CPU memcpy into pinned buf\n",
" clusters_cuda_per_frame.extend(\n",
" cf_cuda.find_clusters_batched(batch_buffer[:stop-start], first_frame=start)\n",
" )\n",
" t_cuda = time.perf_counter() - t0\n",
"cf_cuda.reset_timers()\n",
"t0 = time.perf_counter()\n",
"for start in range(0, N, BATCH_SIZE):\n",
" stop = min(start + BATCH_SIZE, N)\n",
" clusters_cuda_per_frame.extend(\n",
" cf_cuda.find_clusters_batched(data[start:stop], first_frame=start)\n",
" )\n",
"t_cuda = time.perf_counter() - t0\n",
"\n",
" cf_cuda.unregister_input_buffer() # release when done with this dataset\n",
"cf_cuda.unregister_input_buffer() # release when done with this dataset\n",
"\n",
" kernel_ms = cf_cuda.avg_kernel_time_ms()\n",
" \n",
" n_clusters_cuda = sum(cv.size for cv in clusters_cuda_per_frame)\n",
"kernel_ms = cf_cuda.avg_kernel_time_ms()\n",
"\n",
" hist_cuda = make_hist_from_batch(clusters_cuda_per_frame)\n",
" \n",
"else: \n",
" # Simpler: (non-batched) per-frame run on non-pinned data\n",
" cf_cuda.find_clusters(data[0])\n",
" _ = cf_cuda.steal_clusters(realloc_same_capacity=False)\n",
"n_clusters_cuda = sum(cv.size for cv in clusters_cuda_per_frame)\n",
"\n",
" cf_cuda.reset_timers()\n",
" t0 = time.perf_counter()\n",
"\n",
" n_clusters_cuda = 0\n",
" hist_cuda = None\n",
"\n",
" # steal the clusters as we go rather than at the end of the dataset \n",
" # which might trigger an std::bad_alloc...\n",
" for idx, frame in enumerate(data):\n",
" cf_cuda.find_clusters(frame)\n",
" clusters_frame = cf_cuda.steal_clusters(realloc_same_capacity=True)\n",
"\n",
" n_clusters_cuda += clusters_frame.size\n",
"\n",
" h = make_hist(clusters_frame)\n",
" hist_cuda = h if hist_cuda is None else hist_cuda + h\n",
" \n",
" t_cuda = time.perf_counter() - t0\n",
" kernel_ms = cf_cuda.avg_kernel_time_ms()"
"hist_cuda = make_hist_from_batch(clusters_cuda_per_frame)"
]
},
{
"cell_type": "code",
"execution_count": 43,
"execution_count": 21,
"id": "502d0d3b-6b1e-4cc9-91df-9d998bd849b5",
"metadata": {},
"outputs": [
{
"data": {
"text/plain": [
"(9, 9)"
"(3, 3)"
]
},
"execution_count": 43,
"execution_count": 21,
"metadata": {},
"output_type": "execute_result"
}
@@ -400,7 +501,7 @@
},
{
"cell_type": "code",
"execution_count": 44,
"execution_count": 22,
"id": "2e3e4b9c-7f23-4fb7-bc3e-fa3d766d51fc",
"metadata": {},
"outputs": [
@@ -408,7 +509,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"CPU clustering: 466.937s (107 FPS, 50982358 clusters, 1019.65/frame)\n"
"CPU clustering: 49.960s (400 FPS, 27665227 clusters, 1383.26/frame)\n"
]
}
],
@@ -419,7 +520,7 @@
},
{
"cell_type": "code",
"execution_count": 45,
"execution_count": 23,
"id": "4b8df93b-9a1b-41a5-9fed-9cda295fb523",
"metadata": {},
"outputs": [
@@ -427,10 +528,10 @@
"name": "stdout",
"output_type": "stream",
"text": [
"CUDA clustering: 3.684s (13571 FPS, 50577611 clusters, 1011.55/frame)\n",
"CUDA clustering: 1.541s (12975 FPS, 27665079 clusters, 1383.25/frame)\n",
" Kernel only: 0.029 ms/frame\n",
" PCIe + overhead: 0.045 ms/frame\n",
"Speedup (CPU / CUDA): 126.73×\n"
" PCIe + overhead: 0.048 ms/frame\n",
"Speedup (CPU / CUDA): 32.41×\n"
]
}
],
@@ -442,6 +543,65 @@
"print(f'Speedup (CPU / CUDA): {t_cpu / t_cuda:.2f}×')"
]
},
{
"cell_type": "markdown",
"id": "5d6d6da8",
"metadata": {},
"source": [
"## CUDA Graph clustering (pinned dataset)\n",
"Pre-records the H2D→kernel→D2H pipeline as a CUDA Graph per stream, reducing per-frame CPU API call overhead (~21 µs vs ~60 µs for the single-frame/unpinned-buffer streamed version)."
]
},
{
"cell_type": "code",
"execution_count": 24,
"id": "bda3d609",
"metadata": {},
"outputs": [],
"source": [
"cf_graph.register_input_buffer(data)\n",
"\n",
"clusters_graph_per_frame = []\n",
"\n",
"t0 = time.perf_counter()\n",
"for start in range(0, N, BATCH_SIZE):\n",
" stop = min(start + BATCH_SIZE, N)\n",
" clusters_graph_per_frame.extend(\n",
" cf_graph.find_clusters_batched(data[start:stop], first_frame=start)\n",
" )\n",
"t_graph = time.perf_counter() - t0\n",
"\n",
"cf_graph.unregister_input_buffer()\n",
"\n",
"n_clusters_graph = sum(cv.size for cv in clusters_graph_per_frame)\n",
"hist_graph = make_hist_from_batch(clusters_graph_per_frame)"
]
},
{
"cell_type": "code",
"execution_count": 25,
"id": "e6d28031",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CUDA Graph (pinned): 1.384s (14446 FPS, 27665079 clusters, 1383.25/frame)\n",
" Kernel + PCIe + overhead: 0.069 ms/frame (kernel not individually timed)\n",
"Speedup CUDA graph (vs Streamed/batched) : 1.11×\n",
"Speedup (CPU / CUDA Graph): 36.09×\n"
]
}
],
"source": [
"print(f'CUDA Graph (pinned): {t_graph:.3f}s ({N/t_graph:.0f} FPS, '\n",
" f'{n_clusters_graph} clusters, {n_clusters_graph/N:.2f}/frame)')\n",
"print(f' Kernel + PCIe + overhead: {t_graph*1000/N:.3f} ms/frame (kernel not individually timed)')\n",
"print(f'Speedup CUDA graph (vs Streamed/batched) : {t_cuda / t_graph:.2f}×')\n",
"print(f'Speedup (CPU / CUDA Graph): {t_cpu / t_graph:.2f}×')"
]
},
{
"cell_type": "markdown",
"id": "561e372d-97dd-4ed9-b66a-b8a1a5888b97",
@@ -452,17 +612,7 @@
},
{
"cell_type": "code",
"execution_count": 46,
"id": "09301746-33f1-43be-b395-9ef253d781b9",
"metadata": {},
"outputs": [],
"source": [
"BATCH_SIZE = 3000"
]
},
{
"cell_type": "code",
"execution_count": 47,
"execution_count": 26,
"id": "ab26ae22-53bd-4f68-a148-8457ae9b9c03",
"metadata": {},
"outputs": [
@@ -470,10 +620,10 @@
"name": "stdout",
"output_type": "stream",
"text": [
"CUDA(async pipeline): 3.384s (14774 FPS, 50577640 clusters, 1011.55/frame)\n",
" Kernel only: 0.031 ms/frame\n",
" PCIe + overhead: 0.036 ms/frame\n",
"Speedup vs batched: 1.09×\n"
"CUDA(async pipeline): 1.673s (11957 FPS, 27665079 clusters, 1383.25/frame)\n",
" Kernel only: 0.029 ms/frame\n",
" PCIe + overhead: 0.055 ms/frame\n",
"Speedup Async (vs Graph): 0.83×\n"
]
}
],
@@ -487,10 +637,6 @@
"\n",
"clusters_async = []\n",
"\n",
"# Warmup: first call pays CUDA context + pedestal upload cost\n",
"buf[0][:BATCH_SIZE] = data[:BATCH_SIZE]\n",
"_ = cf_async.find_clusters_batched(buf[0], first_frame=0)\n",
"\n",
"cf_async.reset_timers()\n",
"t0 = time.perf_counter()\n",
"\n",
@@ -535,7 +681,7 @@
"print(f' Kernel only: {kernel_ms_async:.3f} ms/frame')\n",
"print(f' PCIe + overhead: {t_async*1000/N - kernel_ms_async:.3f} ms/frame')\n",
"if 't_cuda' in dir():\n",
" print(f'Speedup vs batched: {t_cuda / t_async:.2f}×')"
" print(f'Speedup Async (vs Graph): {t_graph / t_async:.2f}×')"
]
},
{
@@ -550,7 +696,7 @@
},
{
"cell_type": "code",
"execution_count": 48,
"execution_count": 27,
"id": "d3a850df-7df0-485b-971d-381cfdc6be81",
"metadata": {},
"outputs": [
@@ -558,7 +704,7 @@
"name": "stdout",
"output_type": "stream",
"text": [
"Cluster count diff: 404747 (0.79%)\n"
"Cluster count diff: 148 (0.00%)\n"
]
}
],
@@ -578,7 +724,7 @@
},
{
"cell_type": "code",
"execution_count": 49,
"execution_count": 28,
"id": "aff8db7e-8332-4228-857f-a9875cf940d3",
"metadata": {},
"outputs": [
@@ -598,14 +744,16 @@
},
{
"cell_type": "code",
"execution_count": 50,
"execution_count": 29,
"id": "9adeea2f-9309-4c0a-905b-7d1203ba1f4e",
"metadata": {},
"metadata": {
"scrolled": true
},
"outputs": [
{
"data": {
"image/png":
"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oBC+gmIshBAHDCd3DNTlE+QDzYZiT7KrBi9XnTzbT3jtP+jM0MInsPTZp58+/eTrghMU+rbX9jTRORsMqa+PtL0dc9izf4cC7pNB07VluuFEe1995fa26HGmtkkcxQBTd9J68NJLLxkLCDPu7Iv6XpOajmuvY1/35xpRvHDiyO0PRhzUNG7O96NzTEZN92NDwXPs0qWLyTrEOJT6xllwotu9e/c621BoMn1tTdjraxKjVeHTelq8mAKXQoiCpmpaW16/559/3rwYDM2g7nvvvddYy2q71w/kN+ZQnZONHXxu19/Jz8834sF+oFATH330EYYPH17vYwjRWJDFQghxUDBIlU+Tr7vuOjOZrArdHb788stat7ddbfikuqY/5rXBiS+DlTnhsl1pCCeDTLdaFQbOrl271kxiOdmq+qrL5acu+LSeT0i575r2u6+JXU2wr3yKz8lzTftjZp99QTcsBvzySbUzDMiuCT555YSQAeUMkuakaH8DUGu7JoRB2FVrAXDyRZFES8f+XCOmVyX7qsdQ271QF7Xdj3Xdww3Bgw8+iNzcXCMAa3LNKSgoMMJjf2HQcU33CaFItAvr7QtmpKJLGIO4+aJAq8ntzob3Gi1iFK9V75WD/Y05VOdEeM8yAJ/3BQsT2vct7ysmXfj++++rvSig5A4lfBVZLIQQBwVdGjjB40SUmVuYb57uD/xjT597poik33zVuAIburNwIjBmzBgT08An4HSvYfpUZzjR40SET9Xbt29v3DmYLYbr+dTThpNaTp7pdsGUppzgcB0zEvEpIwuM3XHHHSYugfEAfHLKCRkn1PWdbDjDCQezEjETDFOvnnnmmeY8aC1gRihmZbLTv9YXpm9l1iRm02H8A11K6LrBJ7acuDBjFIVAXfB8ObnhJJz94xNaTohWrFhhPmcWpKpPhhmzwOxF7DOzLu2L+l4T242E7jK0ZvAJPDM68en2U0895XgCX99rRLctZhiirz7HmYKEIoL3G/fFOIK67oW64JjTRcfONkXRSLeemtLxHix0sWGMCt2D9hVnwTGmuOBklteQ/eO2tGDMnz/fZPVi1qh9pZytCu8vujDxaTwzcXF8KGA4ZrTCMXamqoWsJnj/0K2J7aOjo01mLFpMbPiUv2/fviauhimUuU9aNWipYNtD+RtzoOe0cOFC02ful6lk+TvElMTMbEXhYruQ8VrxO85sZjVlTLPHgaKkalyREI0ed0ePCyEaB8yGw6wzLVq0sIKDg62IiAjrmGOOsR566CErMzOz1qxQJCMjw7rooous+Ph4k43piiuusH7//XeX7C07duywrrrqKqtjx45m38wwc9RRR1njxo1zZGWxM+3069fPioqKMtszQ4Line truncated
"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 truncated
"text/plain": [
"<Figure size 800x600 with 2 Axes>"
]
@@ -621,31 +769,276 @@
")\n",
"\n",
"edges = hist_cpu.axes[0].edges\n",
"cpu_vals = hist_cpu.values()\n",
"cuda_vals = hist_cuda.values()\n",
"cpu_vals = hist_cpu.values()\n",
"cuda_vals = hist_cuda.values()\n",
"async_vals = hist_async.values()\n",
"graph_vals = hist_graph.values()\n",
"\n",
"ax_spec.stairs(cpu_vals, edges, label=f'CPU ({n_clusters_cpu} clusters)')\n",
"ax_spec.stairs(cuda_vals, edges, label=f'CUDA ({n_clusters_cuda} clusters)', linestyle='--')\n",
"ax_spec.stairs(async_vals, edges, label=f'CUDA_async ({n_clusters_async} clusters)', linestyle='-.')\n",
"ax_spec.stairs(cpu_vals, edges, label=f'CPU ({n_clusters_cpu} clusters)')\n",
"ax_spec.stairs(cuda_vals, edges, label=f'CUDA stream ({n_clusters_cuda} clusters)', linestyle='--')\n",
"ax_spec.stairs(async_vals, edges, label=f'CUDA async ({n_clusters_async} clusters)', linestyle='-.')\n",
"ax_spec.stairs(graph_vals, edges, label=f'CUDA graph ({n_clusters_graph} clusters)', linestyle=':')\n",
"ax_spec.set_ylabel('Counts')\n",
"ax_spec.set_title('Cluster energy spectrum: CPU vs CUDA')\n",
"ax_spec.set_title('Cluster energy spectrum: CPU vs CUDA variants')\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",
" ratio_cuda = np.where(cpu_vals > 0, cuda_vals / cpu_vals, np.nan)\n",
" ratio_graph = np.where(cpu_vals > 0, graph_vals / cpu_vals, np.nan)\n",
"\n",
"ax_ratio.stairs(ratio, edges, color='k')\n",
"ax_ratio.stairs(ratio_cuda, edges, label='CUDA stream / CPU', color='C1', linestyle='--')\n",
"ax_ratio.stairs(ratio_graph, edges, label='CUDA graph / CPU', color='C3', linestyle=':')\n",
"ax_ratio.axhline(1.0, color='gray', linewidth=0.5)\n",
"ax_ratio.set_ylabel('CUDA / CPU')\n",
"ax_ratio.set_ylabel('Variant / CPU')\n",
"ax_ratio.set_xlabel('Energy [ADU]')\n",
"ax_ratio.set_ylim(0.5, 2.0)\n",
"ax_ratio.legend(fontsize=8)\n",
"ax_ratio.grid(alpha=0.3)\n",
"\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": 30,
"id": "ea19690e",
"metadata": {},
"outputs": [],
"source": [
"sx, sy = cluster_size # cluster footprint, e.g. (7, 7)\n",
"rx, ry = sx // 2, sy // 2\n",
"N_SIGMA_VIS = N_SIGMA # back-compat alias used by the analysis cells"
]
},
{
"cell_type": "code",
"execution_count": 31,
"id": "c66373cb-4a10-4898-b488-326ffd025d77",
"metadata": {},
"outputs": [],
"source": [
"cf_cuda_m = None\n",
"cf_cpu_m = None\n",
"show = None\n",
"totals = None"
]
},
{
"cell_type": "code",
"execution_count": 32,
"id": "7d8f734e-2f77-4d68-ac7b-01842c540a44",
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"total CPU-only=148, CUDA-only=0; showing 8 frames with the most mismatches\n"
]
},
{
"data": {
"image/png":
"iVBORw0KGgoAAAANSUhEUgAABEEAABCGCAYAAABVHQgFAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3QV4U1cbB/B/krpSrEihQHF3dxlsyHAYMtxl+IDh7m6D4bIN94/B8MFwGO5QipRSd4l8zzklpaUCbQqV/H/PE0hu7rk596bJffPeIwqdTqcDEREREREREVE6p0zpChARERERERERfQ1MghARERERERGRUWAShIiIiIiIiIiMApMgRERERERERGQUmAQhIiIiIiIiIqPAJAgRERERERERGQUmQYiIiIiIiIjIKDAJQkRERERERERGgUkQIiIiIiIiIjIKTIJQuqPT6dC7d29kzJgRCoUCN27cSOkqEX1R4u987969PMpERJ+BcQJR/GrXro0hQ4bwEFG6xiQIpTtHjhzBhg0bcPDgQbx58wbFixdHWjnpiB+z0W/t27ePsc61a9fQoEEDZMiQAZkyZZLJnsDAwBjrvHjxAk2bNoW1tTUyZ86MwYMHIzw8POr558+fx3odcRPH7Wu5fv062rRpA0dHR1hYWKBgwYLo1asXHj58GGcdHRwcULNmTZw+ffqTJ2mRDBBlEkP8nXTo0AGFChWCUqmMc7vibyqu4xYaGhq1zsyZM1GhQgXY2toia9asaN68OR48eBAr+J40aRJy5MgBS0tLuR937txBanHq1Cm5X76+vildFSKiL4JxQtqPExI6V5UuXVqeZ/Xy5MkTtQ/ivCset23bFidOnIjztUNCQmTcIS6miftJ8dNPP6FcuXIwNzeX9fnY5x5jEfeI7YhjkC9fPqxatSrWtnbt2oWiRYvK1xL/79mzB6kJL9RQasQkCKU7T548Qfbs2VG1alVky5YNJiYmsdaJfrJPTcQJXvwg199+/fXXqOdev36N+vXrI3/+/Lh48aI8UYofz127do1aR6PRoHHjxggKCsI///yDP/74Q54chw8fHuu1/v777xivVbdu3a+yjyI5VblyZYSFhWHr1q24d+8eNm/eDHt7e4wfPz7OOoogwM7ODt999x2ePXuW7HUSdcmSJQt++eUXlCpVKt71RB2iHzNxE4GJnqjngAEDcOHC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 truncated
"text/plain": [
"<Figure size 1100x4240 with 16 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
},
{
"data": {
"image/png":
"iVBORw0KGgoAAAANSUhEUgAABEEAABCGCAYAAABVHQgFAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3QV4U1cbB/B/krpSrEihQHF3dxlsyHAYMtxl+IDh7m6D4bIN94/B8MFwGO5QipRSd4l8zzklpaUCbQqV/H/PE0hu7rk596bJffPeIwqdTqcDEREREREREVE6p0zpChARERERERERfQ1MghARERERERGRUWAShIiIiIiIiIiMApMgRERERERERGQUmAQhIiIiIiIiIqPAJAgRERERERERGQUmQYiIiIiIiIjIKDAJQkRERERERERGgUkQIiIiIiIiIjIKTIJQuqPT6dC7d29kzJgRCoUCN27cSOkqEX1R4u987969PMpERJ+BcQJR/GrXro0hQ4bwEFG6xiQIpTtHjhzBhg0bcPDgQbx58wbFixdHWjnpiB+z0W/t27ePsc61a9fQoEEDZMiQAZkyZZLJnsDAwBjrvHjxAk2bNoW1tTUyZ86MwYMHIzw8POr558+fx3odcRPH7Wu5fv062rRpA0dHR1hYWKBgwYLo1asXHj58GGcdHRwcULNmTZw+ffqTJ2mRDBBlEkP8nXTo0AGFChWCUqmMc7vibyqu4xYaGhq1zsyZM1GhQgXY2toia9asaN68OR48eBAr+J40aRJy5MgBS0tLuR937txBanHq1Cm5X76+vildFSKiL4JxQtqPExI6V5UuXVqeZ/Xy5MkTtQ/ivCset23bFidOnIjztUNCQmTcIS6miftJ8dNPP6FcuXIwNzeX9fnY5x5jEfeI7YhjkC9fPqxatSrWtnbt2oWiRYvK1xL/79mzB6kJL9RQasQkCKU7T548Qfbs2VG1alVky5YNJiYmsdaJfrJPTcQJXvwg199+/fXXqOdev36N+vXrI3/+/Lh48aI8UYofz127do1aR6PRoHHjxggKCsI///yDP/74Q54chw8fHuu1/v777xivVbdu3a+yjyI5VblyZYSFhWHr1q24d+8eNm/eDHt7e4wfPz7OOoogwM7ODt999x2ePXuW7HUSdcmSJQt++eUXlCpVKt71RB2iHzNxE4GJnqjngAEDcOHCBRw7dgxqtRrffPONfD/05syZgwULFmDZsmW4fPmy/BsVia2AgACkJyLZI/afiCi1YZyQfuKEzzVlyhS5D+LCxKZNm+TFJBFTTZ8+Pda64niIC2giobB79+4knwO7d++Odu3aJbheQsdYxDsi7qlRo4ZMCo0dO1YmrET99P7991/5Gp07d8Z///0n/xcJHhEnpjcREREpXQVKT3RE6UiXLl104s9af3N2dpbLa9WqpRswYIBu6NChukyZMulq1qwpl8+fP19XvHhxnZWVlc7JyUnXr18/XUBAQNT21q9fr7O3t9cdOHBAV7BgQZ2lpaWuVatWusDAQN2GDRvk9jNkyKAbOHCgTq1WR5ULCwvTjRw5UpcjRw657YoVK+pOnjyZYN1FHX/66ad4n//11191WbNm1Wk0mqhl169fl/v56NEj+fjw4cM6pVKpe/XqVdQ6v//+u87c3Fzn5+cnHz979kyWEWW/tqCgIF3mzJl1zZs3j/N5Hx+feOv48uVLuWzVqlUJHq89e/bI9ZIqvu3q/xYSw8PDQ9bl9OnT8rFWq9Vly5ZNN2vWrKh1QkND5Xb1+xWftWvX6ooWLaozMzOT2xB/z3riNcR+C+LvTDzWH8vofyfiuArPnz/XNWnSRP7tir9Psd1Dhw5FHffoN/GZ0td99uzZurx58+osLCx0JUuW1O3YsSPqNfSve+TIEV25cuV0pqamuhMnTuhu3Lihq127ts7GxkZna2urK1u2rO7y5cuJOo5ERMmFcUL6iBPiOtfplSpVSjdx4sSoxyJWW7hwYaz1JkyYII/F/fv3YywX5yxxTl65cqWuTp06Bu2PqIeoz8c+5xiPGjVKV7hw4RjL+vTpo6tcuXLU47Zt2+oaNWoUY52GDRvq2rdvn2C9/vnnHxkLi7hWxALffPONztvbO844KHqMoSfiFhEX6WNeEZOI2ET8HYnjPWPGDPmcuB9XXC7s379fxgSijIgtJk2apIuIiIjxuuI9aNasmYxVxPsl6tihQwf5NyJikfz58+vWrVuX4L4SxYUtQShdWbx4scz2Ozk5yYy6uNKut3HjRtkq5Ny5c1EtLETXhyVLluD27dvyedE0ctSoUTG2GRwcLNcRV0tE6wvRBLNly5Y4fPiwvImrE6tXr8bOnTujynTr1k2+jihz8+ZN2aSzUaNGePToUYL1F1c8RNPUYsWKYcSIETFaB4grImZmZrLOeqJZpyCu5uivCIirF6KrhV7Dhg1l2atXr8Z4rWbNmskuG9WqVYtR9y/pr7/+gqenZ6xjrCeuzMTHysoqSVcC9E1OxftmKNH1yNnZWf59NWnSRF6ZSYifn5/8XzSp1V/VcXd3l61D9ETz1Vq1auH8+fPxbmflypWyhYno/nTr1i3s379ftghKKrEt8Tdx5swZub3Zs2fDxsYGuXLlirrCJK6Wic+Q+EwJ48aNw/r162VdRAukoUOHolOnTjG6KAnivRXdgsSVu5IlS6Jjx47yeInPovgbHD16NExNTZNcdyIiQzBOSL9xQlK6rIjf2vv27YvRSkjEUqI1hbiJc/PTp09jlBPdaaJ3tzFEQsdY1CN6vKB/r65cuRIVC8W3TkIxhRgrr169ejLWFOVFDCm6R4nWxEkhYmQRl2zfvl3GDlu2bJHHSNDH4SJ+iB6Xi/dZxBCiZcvdu3dlXC66HX/cMmfixIn4/vvvZawiWtaIlkBi/f/9738yzhAxiYibiRIrdj8BojRMNJUU4zGoVCrZzSA68aNRdEWILvrYD3nz5sXUqVPRr18/rFixImq5ONGIL1kXFxf5uHXr1jLx8fbtW/nDUTSXrFOnDk6ePCmbJIoT6O+//46XL19GBRkioSESKOIkMGPGjDjrLn4sijqIeoukzJgxY2TTRtGtQhBNJIcNG4a5c+fKE7doyiqaRgrixCKIH9ii/2x0ol+rSJ6I5wRRZ9EdQ5xwRUJFnLhEvUUSSJyQviR9Eqhw4cKJKif2VRwP8b6KhEFiiB/cYqwPfRIlqUSdxQm6RIkS8Pf3l4G0OIbiPSpQoECs9UVgJd6v6tWrR41Lo38PPn6PxGNXV9d4X3vatGmyqbJ43/XE2CNJJcaNadWqldwXQfQz1tMnbERQpg82xfEXfzMiSVilSpWoMiJwEoFL9PdEJCFF957orzVy5Mio9zyuY0VE9LUwTkifcUJSiPOdONeJiyV669atw7fffiuPiSAuYIll4jysJ+JBQ394f84xjiumE49FV1ORKBJdv+NbR/9exkXEwuXLl48R64qESFKJ87w4t4t4R1x0EheL9ERXY0HEE9HjcpHsEBdFunTpEhVTiBhcJL9E4kNPjNcmkh/RX6tMmTKy/oI+2UKUWEyCkNHQf2FGJxIXIikhssrih604sYiBLsWPPjFgmCB+POsTIPqTi/jSFSew6Ms8PDyiBi8VP4DFIF7RiassYjDThMYD0RM/msUJRdRZbK9s2bLyBCVOjuKHtT4hIDLo4rXFfb24BgUV9dEvFyducRU/+nHx8fGRJ8X4ghsREJw9exafS/RfFVn6uOqRGGJcFxEciNY44mSvT0IkRs6cOXH//n0YSvRPFjc9EbiI92Xp0qXyKsjHBg4cKFsB6VvpRPfxexT9/fmY+LsS48GIqzbJRfzdiGTf0aNHZZ9okRARrTbiIz4f4nMRPbmhH1tHBCMJfc7E32vPnj1l4lC8lmgVFf3zRESUWjBOSHtxgqGin39FSwgRZ+lbQApif8WxmDx5clSsdfz4cYNf93OPcVzxwsfLExNT6FuCiHNxchFj04n4QFxwEkkj0VL249YpHxOtjkSrkOgtP8TxF7GGiPn0F64+/kyK2EXELCI2Fq8hBqAXsSJRYjEJQkZDn9TQE1fexYBTffv2ldlncUVA/GDt0aNHjC4XHzfdFyeWuJZptVp5X/wvTpTiCz56ckKInjj5FPEDW7yOuCoi7usz4uImWqGI/RGvK64kiBYkgsiyfzwYljipiv35+EpBdOLH/W+//Rbv8+K5xIyQru+m8zF9YkgkJfQtChLy559/ypY2+tlwPh6kVN/dJDoxUrx47ksTyRnRGiOuLk6DBg2SV3VEdxPRFURPfxVEXKERSZ3oiY743p/4jmVC9fo4kPy4C5FISojmsocOHZKJENF9Zf78+bLecdH/bYv1RVIpOtGdJ6HPmWgyLP5mRVkR8IorPKKbWIsWLRK1X0REXxrjhLQTJ+jP8yIO+LiLjIgDRIufT/Hy8sK7d++iYijRRePVq1exBjMVP87FuVIker6kj4+xiBk+btEh4gXRtVsfE8W3TkIxX2LjChFrfpycih5XiBhVdPcV53gx0KvoRiQueiTUhUrEFSKxJLqXfyz6gPMffybFeyDidxFTiNcSF4hEF9958+Ylap+ImAQhoyX6VIqWH+LHn/6Ho+jPaChxZVycMMVJSFzpSCox7oI4yUT/saynP7mJJpriZKG/Qi8CBpFVF91j9OXEiVv8UBVTrMVHjG0R1+voffzDN6lE1l5c/RBXOuKawk0ELtGDGTFGRXytBkRT2biuIokrC+JqxJcmAgJxNSV6yxSxTCQSxL6JMUj0gZWevruT6OKkb0EhWlOIcTXEuBxxEd27RMsjceVJdLv6FH3TU/E3oG/OK+r5MXFsRQJQ3ETLojVr1si6iybRQvS+wfqp90Qz1MR2R9IHteImrnr98MMPslsYkyBElNoxTki9cYJoLStiN3HOj979Qpz7RCLjc+IA0eJDbEO0JhDWrl2L9u3by5niops1a5Z87kLine truncated
"text/plain": [
"<Figure size 1100x4240 with 16 Axes>"
]
},
"execution_count": 32,
"metadata": {},
"output_type": "execute_result"
}
],
"source": [
"# =====================================================================\n",
"# Masked mismatch view — cluster pixels only, both finders side by side\n",
"# ---------------------------------------------------------------------\n",
"# Non-cluster pixels white, red dot at each cluster centre. Frames chosen\n",
"# by RAW mismatch (tol=0) so shifted twins are kept; the cut is centred on\n",
"# the strongest mismatch pixel. Scan + plot live in helper.py; `show` is\n",
"# reused by the walk-through and shift-check cells below.\n",
"# =====================================================================\n",
"cf_cpu_m = ClusterFinderFrozen(image_size, cluster_size, n_sigma=N_SIGMA,\n",
" capacity=capacity)\n",
"cf_cuda_m = ClusterFinderCUDA(image_size, cluster_size, n_sigma=N_SIGMA,\n",
" max_clusters_per_frame=1500, n_streams=N_STREAMS)\n",
"train_pedestal([cf_cpu_m, cf_cuda_m], f, n_frames_pd)\n",
"\n",
"show, totals = scan_mismatches(cf_cpu_m, cf_cuda_m, data, rx, ry,\n",
" scan_count=20000, n_show=8, tol=0)\n",
"print(f\"total CPU-only={totals['cpu_only']}, CUDA-only={totals['cu_only']}; \"\n",
" f\"showing {len(show)} frames with the most mismatches\")\n",
"plot_masked_mismatch(show, data, rx, ry, rows, cols, zoom=30, show_vals=True)\n"
]
},
{
"cell_type": "code",
"execution_count": 33,
"id": "299667d5",
"metadata": {
"scrolled": true
},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"frame 5905 centre (x=150, y=375) CPU-only (CPU accepts, CUDA rejects)\n",
" ^ strongest >1px mismatch in this displayed frame\n",
"raw cluster (ADU):\n",
"[[5822 6545 6086]\n",
" [5850 7601 6240]\n",
" [5967 6413 6369]]\n",
"\n",
"--- CPU (FP64 host pedestal) ---\n",
" pedestal-subtracted window:\n",
" [[ 43.7 647.1 112.7]\n",
" [ -10.5 1595.2 191.5]\n",
" [ 91.1 570.1 144.6]]\n",
" centre value = 1595.25 (local max? True)\n",
" max value = 1595.25\n",
" total = 3385.52\n",
" rms(centre) = 54.985\n",
" Test1: max > 5*rms = 274.93 -> True\n",
" Test3: total > 3*5*rms = 824.78 -> True\n",
" ACCEPT = localmax and (Test1 or Test3) = True\n",
"\n",
"--- CUDA (FP64 host pedestal) ---\n",
" pedestal-subtracted window:\n",
" [[ 43.7 647.1 112.7]\n",
" [ -10.5 1595.2 191.5]\n",
" [ 91.1 570.1 144.6]]\n",
" centre value = 1595.25 (local max? True)\n",
" max value = 1595.25\n",
" total = 3385.52\n",
" rms(centre) = 54.985\n",
" Test1: max > 5*rms = 274.93 -> True\n",
" Test3: total > 3*5*rms = 824.78 -> True\n",
" ACCEPT = localmax and (Test1 or Test3) = True\n",
"\n",
"RESULT: CPU = ACCEPT , CUDA = ACCEPT\n",
"pedestal mean gap @centre = 0.00 ADU; rms gap = 0.000\n",
"NOTE: recompute did not reproduce the scan's split for this exact pixel. Residual causes are the CPU's per-pixel in-frame pedestal update (affects window neighbours) or FP rounding right at threshold. The mismatch is real; try another PICK.\n"
]
}
],
"source": [
"# =====================================================================\n",
"# Manual walk-through: Test 1 & Test 3 on a DISPLAYED mismatch cluster\n",
"# ---------------------------------------------------------------------\n",
"# Analyses one mismatch cluster from a frame in `show` (masked view above).\n",
"# It picks the strongest >1px mismatch (only_sets, tol=1) so the recompute\n",
"# lands on a genuine add/drop rather than a 1px relabel. Each finder uses\n",
"# its DECISION pedestal captured before the frame: CPU = host (FP64),\n",
"# CUDA = device stream 0. With the double build both should now agree.\n",
"#\n",
"# value = centre pixel, pedestal-subtracted\n",
"# max = brightest of the window total = sum of the window\n",
"# rms = pedestal std at the centre\n",
"# Test 1 : max > nSigma * rms (single-pixel)\n",
"# Test 3 : total > sqrt(sx*sy)*nSigma*rms (total)\n",
"# ACCEPT = (centre is the local max) AND (Test1 OR Test3)\n",
"# =====================================================================\n",
"PICK = 0 # which displayed frame (index into `show`)\n",
"nsig = N_SIGMA_VIS\n",
"c3 = np.sqrt(sx * sy) # sqrt(sx*sy) for the total test\n",
"\n",
"def evaluate(frame_img, X0, Y0, mean, rms_img, f32):\n",
" cast = np.float32 if f32 else np.float64\n",
" sig = (frame_img[Y0-ry:Y0+ry+1, X0-rx:X0+rx+1]\n",
" - mean[Y0-ry:Y0+ry+1, X0-rx:X0+rx+1]).astype(cast)\n",
" value = cast(frame_img[Y0, X0] - mean[Y0, X0])\n",
" rms = cast(rms_img[Y0, X0])\n",
" thr1 = cast(nsig) * rms\n",
" thr3 = cast(c3) * cast(nsig) * rms\n",
" mx, total = sig.max(), sig.sum()\n",
" localmax = bool(value >= mx)\n",
" t1, t3 = bool(mx > thr1), bool(total > thr3)\n",
" accept = bool(value >= -thr1) and localmax and (t1 or t3)\n",
" return dict(sig=sig, value=value, rms=rms, thr1=thr1, thr3=thr3,\n",
" mx=mx, total=total, localmax=localmax, t1=t1, t3=t3, accept=accept)\n",
"\n",
"# --- pick the strongest >1px mismatch in the chosen frame ---\n",
"e = show[PICK]\n",
"frame_img = data[e['fid']].astype(np.float64)\n",
"cpu_only, cu_only = only_sets(e['cpu_c'], e['cu_c'])\n",
"sub_cpu = frame_img - e['ped_cpu']\n",
"sub_cu = frame_img - e['ped_cu']\n",
"cand = list(cpu_only | cu_only)\n",
"if not cand:\n",
" raise RuntimeError(f\"frame {e['fid']} (PICK={PICK}) has only shifted-twin \"\n",
" \"mismatches (no >1px difference) — choose another PICK.\")\n",
"X0, Y0 = max(cand, key=lambda p: max(sub_cpu[p[1], p[0]], sub_cu[p[1], p[0]]))\n",
"is_cpu_only = (X0, Y0) in cpu_only\n",
"if not (rx <= X0 < cols - rx and ry <= Y0 < rows - ry):\n",
" raise RuntimeError(f'centre ({X0},{Y0}) is on the frame border; '\n",
" 'the window would clip — pick another PICK/frame.')\n",
"\n",
"rc = evaluate(frame_img, X0, Y0, e['ped_cpu'], e['noise_cpu'], f32=False)\n",
"rg = evaluate(frame_img, X0, Y0, e['ped_cu'], e['noise_cu'], f32=False)\n",
"\n",
"print(f\"frame {e['fid']} centre (x={X0}, y={Y0}) \"\n",
" f\"{'CPU-only (CPU accepts, CUDA rejects)' if is_cpu_only else 'CUDA-only (CUDA accepts, CPU rejects)'}\")\n",
"print(' ^ strongest >1px mismatch in this displayed frame')\n",
"print('raw cluster (ADU):')\n",
"print(np.array2string(frame_img[Y0-ry:Y0+ry+1, X0-rx:X0+rx+1].astype(int)))\n",
"print()\n",
"\n",
"def show_detail(tag, r, f32):\n",
" print(f'--- {tag} ({\"FP32 device\" if f32 else \"FP64 host\"} pedestal) ---')\n",
" print(' pedestal-subtracted window:')\n",
" print(' ', np.array2string(r['sig'].astype(float), precision=1,\n",
" prefix=' '))\n",
" print(f\" centre value = {float(r['value']):8.2f} (local max? {r['localmax']})\")\n",
" print(f\" max value = {float(r['mx']):8.2f}\")\n",
" print(f\" total = {float(r['total']):8.2f}\")\n",
" print(f\" rms(centre) = {float(r['rms']):8.3f}\")\n",
" print(f\" Test1: max > {nsig}*rms = {float(r['thr1']):8.2f} -> {r['t1']}\")\n",
" print(f\" Test3: total > {c3:.0f}*{nsig}*rms = {float(r['thr3']):8.2f} -> {r['t3']}\")\n",
" print(f\" ACCEPT = localmax and (Test1 or Test3) = {r['accept']}\")\n",
" print()\n",
"\n",
"show_detail('CPU ', rc, False)\n",
"show_detail('CUDA', rg, False)\n",
"\n",
"print(f\"RESULT: CPU = {'ACCEPT' if rc['accept'] else 'reject'} , \"\n",
" f\"CUDA = {'ACCEPT' if rg['accept'] else 'reject'}\")\n",
"print(f\"pedestal mean gap @centre = \"\n",
" f\"{abs(float(e['ped_cpu'][Y0,X0]-e['ped_cu'][Y0,X0])):.2f} ADU; \"\n",
" f\"rms gap = {abs(float(e['noise_cpu'][Y0,X0]-e['noise_cu'][Y0,X0])):.3f}\")\n",
"if rc['accept'] == rg['accept']:\n",
" print(\"NOTE: recompute did not reproduce the scan's split for this exact \"\n",
" \"pixel. Residual causes are the CPU's per-pixel in-frame pedestal \"\n",
" \"update (affects window neighbours) or FP rounding right at \"\n",
" \"threshold. The mismatch is real; try another PICK.\")\n",
"else:\n",
" print(\"-> the test that flips (Test1/Test3) and the mean/rms gap above \"\n",
" \"show whether the pedestal (host vs device) or FP rounding caused \"\n",
" \"it. This is the pedestal each finder actually decided with.\")\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "9eb0f052-7ae8-425f-8ff6-02cb658fd545",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
+617
View File
@@ -0,0 +1,617 @@
{
"cells":
[
{
"cell_type": "markdown",
"id": "md-title",
"metadata": {},
"source": [
"# ClusterFinder — CPU vs CUDA performance\n",
"\n",
"Throughput comparison of the serial CPU finder against the CUDA variants:\n",
"per-frame, batched+pinned, CUDA-Graph, and the async double-buffered pipeline.\n",
"\n",
"CPU↔CUDA **correctness** (why the cluster counts differ) is analysed\n",
"separately in `ClusterFinderFrozen_vs_CUDA.ipynb`; here we only sanity-check that\n",
"the counts and spectra agree, and focus on timing."
]
},
{
"cell_type": "code",
"execution_count": 1,
"id": "imports",
"metadata": {},
"outputs": [],
"source": [
"import sys; sys.path.append('/home/ferjao_k/aare/build')\n",
"\n",
"from pathlib import Path\n",
"import matplotlib.pyplot as plt\n",
"import numpy as np\n",
"import boost_histogram as bh\n",
"import time\n",
"from tqdm import tqdm\n",
"\n",
"from aare import (File, ClusterFinder, ClusterFinderFrozen, ClusterFinderMT, ClusterCollector,\n",
" ClusterFinderCUDA, ClusterFinderCUDAGraph)\n",
"from helper import print_pinning_budget"
]
},
{
"cell_type": "code",
"execution_count": 2,
"id": "hist-helpers",
"metadata": {},
"outputs": [],
"source": [
"N_BINS = 200\n",
"\n",
"def make_hist(clusters):\n",
" h = bh.Histogram(bh.axis.Regular(N_BINS, -2, 4000))\n",
" h.fill(clusters.sum())\n",
" return h\n",
"\n",
"def make_hist_from_batch(result_list):\n",
" h = bh.Histogram(bh.axis.Regular(N_BINS, -2, 4000))\n",
" energies = [np.asarray(cv.sum()).ravel() for cv in result_list if cv.size > 0]\n",
" if energies:\n",
" h.fill(np.concatenate(energies))\n",
" return h"
]
},
{
"cell_type": "code",
"execution_count": 3,
"id": "config",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Image size: (400, 400)\n",
"Pedestal frames: 1000\n",
"Data frames: 20000\n",
"Total in file: 100000\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\n",
"N = 20000\n",
"cluster_size = (9, 9)\n",
"rows, cols = f.rows, f.cols\n",
"image_size = (rows, cols)\n",
"capacity = 1000\n",
"BATCH_SIZE = 2000\n",
"\n",
"N_STREAMS = 4\n",
"N_SIGMA = 5\n",
"\n",
"print(f'Image size: {image_size}')\n",
"print(f'Pedestal frames: {n_frames_pd}')\n",
"print(f'Data frames: {N}')\n",
"print(f'Total in file: {f.total_frames}')"
]
},
{
"cell_type": "code",
"execution_count": 4,
"id": "pinning",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"── System RAM ──────────────────────────────────────────\n",
" Total RAM : 125.1 GiB\n",
" Currently available : 112.7 GiB (free + reclaimable cache)\n",
" Reserved headroom : 4.0 GiB (OS + CUDA context)\n",
" Safe pinning budget : 108.7 GiB\n",
"\n",
"── Frame layout ────────────────────────────────────────\n",
" Frame size : 400 × 400 × 2 B = 312.5 kB\n",
"\n",
"── Pinning estimate ────────────────────────────────────\n",
" Max frames pinnable : 364,859 (108.7 GiB)\n",
"\n",
" Note: no swap on this machine — exceeding available RAM\n",
" will trigger the OOM killer. Stay within the budget.\n"
]
}
],
"source": [
"print_pinning_budget(rows, cols)"
]
},
{
"cell_type": "markdown",
"id": "md-build",
"metadata": {},
"source": [
"## Build finders\n",
"\n",
"`SERIAL` picks the CPU baseline: the sequential `ClusterFinder` or the\n",
"multi-threaded `ClusterFinderMT`. All finders are trained on the same pedestal\n",
"frames below."
]
},
{
"cell_type": "code",
"execution_count": 5,
"id": "build",
"metadata": {},
"outputs": [],
"source": [
"SERIAL = False\n",
"\n",
"if SERIAL:\n",
" # cf_cpu = ClusterFinder(image_size, cluster_size, n_sigma=N_SIGMA, capacity=capacity)\n",
" cf_cpu = ClusterFinderFrozen(image_size, cluster_size, n_sigma=N_SIGMA, capacity=capacity)\n",
"else:\n",
" cf_cpu = ClusterFinderMT(image_size, cluster_size, n_sigma=N_SIGMA,\n",
" capacity=capacity, n_threads=48)\n",
" sink = ClusterCollector(cf_cpu)\n",
"\n",
"cf_cuda_v1 = ClusterFinderCUDA(image_size, cluster_size, n_sigma=N_SIGMA,\n",
" max_clusters_per_frame=1500, n_streams=N_STREAMS)\n",
"cf_cuda = ClusterFinderCUDA(image_size, cluster_size, n_sigma=N_SIGMA,\n",
" max_clusters_per_frame=1500, n_streams=N_STREAMS)\n",
"# cf_async = ClusterFinderCUDA(image_size, cluster_size, n_sigma=N_SIGMA,\n",
"# max_clusters_per_frame=2000, n_streams=N_STREAMS)\n",
"cf_graph = ClusterFinderCUDAGraph(image_size, cluster_size, n_sigma=N_SIGMA,\n",
" max_clusters_per_frame=1500, n_streams=N_STREAMS)"
]
},
{
"cell_type": "markdown",
"id": "md-ped",
"metadata": {},
"source": [
"## Pedestal (all finders trained on identical frames)"
]
},
{
"cell_type": "code",
"execution_count": 6,
"id": "train",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Pedestal (1000 frames): 10.831s\n"
]
}
],
"source": [
"t0 = time.perf_counter()\n",
"for _ in range(n_frames_pd):\n",
" img = pd.read_frame()\n",
" cf_cpu.push_pedestal_frame(img.copy())\n",
" cf_cuda_v1.push_pedestal_frame(img.copy())\n",
" cf_cuda.push_pedestal_frame(img.copy())\n",
" # cf_async.push_pedestal_frame(img.copy())\n",
" cf_graph.push_pedestal_frame(img.copy())\n",
"print(f'Pedestal ({n_frames_pd} frames): {time.perf_counter() - t0:.3f}s')"
]
},
{
"cell_type": "markdown",
"id": "md-io",
"metadata": {},
"source": [
"## Read all data frames into memory (I/O out of the timing loop)"
]
},
{
"cell_type": "code",
"execution_count": 7,
"id": "io",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"Reading 20000 frames: 1.074s (18614 FPS, 5680.686 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",
"print(f'Reading {N} frames: {t_io:.3f}s ({N/t_io:.0f} FPS, '\n",
" f'{f.bytes_per_frame * N / 1024**2 / t_io:.3f} GB/s)')"
]
},
{
"cell_type": "markdown",
"id": "md-cpu",
"metadata": {},
"source": [
"## CPU clustering (baseline)"
]
},
{
"cell_type": "code",
"execution_count": 8,
"id": "cpu",
"metadata": {},
"outputs": [
{
"name": "stderr",
"output_type": "stream",
"text": [
"100%|███████████████████████████████████████████████████████████████████████████████████████████████████████| 20000/20000 [00:09<00:00, 2184.48it/s]\n"
]
},
{
"name": "stdout",
"output_type": "stream",
"text": [
"CPU clustering: 9.158s (2184 FPS, 28438072 clusters, 1421.90/frame)\n"
]
}
],
"source": [
"t0 = time.perf_counter()\n",
"for frame in tqdm(data):\n",
" cf_cpu.find_clusters(frame)\n",
"t_cpu = time.perf_counter() - t0\n",
"\n",
"if SERIAL:\n",
" clusters_cpu = cf_cpu.steal_clusters(realloc_same_capacity=False)\n",
" n_clusters_cpu = clusters_cpu.size\n",
" hist_cpu = make_hist(clusters_cpu)\n",
"else:\n",
" cf_cpu.stop(); sink.stop()\n",
" clusters_cpu = sink.steal_clusters()\n",
" hist_cpu = bh.Histogram(bh.axis.Regular(N_BINS, -2, 4000))\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 clustering: {t_cpu:.3f}s ({N/t_cpu:.0f} FPS, '\n",
" f'{n_clusters_cpu} clusters, {n_clusters_cpu/N:.2f}/frame)')"
]
},
{
"cell_type": "markdown",
"id": "md-v1",
"metadata": {},
"source": [
"## CUDA — per-frame (1 launch/frame, pageable memory)\n",
"Simplest path: no batching, no pinning. Isolates per-frame launch + PCIe overhead."
]
},
{
"cell_type": "code",
"execution_count": 9,
"id": "v1",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CUDA per-frame: 3.659s (5467 FPS, 28445699 clusters, 1422.28/frame)\n",
" Kernel only: 0.034 ms/frame\n",
" PCIe + overhead: 0.149 ms/frame\n",
"Speedup (CPU/CUDA): 2.50x\n"
]
}
],
"source": [
"cf_cuda_v1.reset_timers()\n",
"t0 = time.perf_counter()\n",
"\n",
"n_clusters_cuda_v1 = 0\n",
"hist_cuda_v1 = None\n",
"for frame in data:\n",
" cf_cuda_v1.find_clusters(frame)\n",
" clusters_frame = cf_cuda_v1.steal_clusters(realloc_same_capacity=True)\n",
" n_clusters_cuda_v1 += clusters_frame.size\n",
" h = make_hist(clusters_frame)\n",
" hist_cuda_v1 = h if hist_cuda_v1 is None else hist_cuda_v1 + h\n",
"\n",
"t_cuda_v1 = time.perf_counter() - t0\n",
"kernel_ms = cf_cuda_v1.avg_kernel_time_ms()\n",
"print(f'CUDA per-frame: {t_cuda_v1:.3f}s ({N/t_cuda_v1:.0f} FPS, '\n",
" f'{n_clusters_cuda_v1} clusters, {n_clusters_cuda_v1/N:.2f}/frame)')\n",
"print(f' Kernel only: {kernel_ms:.3f} ms/frame')\n",
"print(f' PCIe + overhead: {t_cuda_v1*1000/N - kernel_ms:.3f} ms/frame')\n",
"print(f'Speedup (CPU/CUDA): {t_cpu / t_cuda_v1:.2f}x')"
]
},
{
"cell_type": "markdown",
"id": "md-batched",
"metadata": {},
"source": [
"## CUDA — batched + multi-streamed + pinned dataset\n",
"Pins the whole input once, then submits `BATCH_SIZE`-frame chunks across `N_STREAMS` streams so H2D / kernel / D2H overlap."
]
},
{
"cell_type": "code",
"execution_count": 10,
"id": "batched",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CUDA batched: 3.088s (6477 FPS, 28439289 clusters, 1421.96/frame)\n",
" Kernel only: 0.031 ms/frame\n",
" PCIe + overhead: 0.123 ms/frame\n",
"Speedup (CPU/CUDA): 2.97x\n"
]
}
],
"source": [
"cf_cuda.register_input_buffer(data) # pin the whole dataset once\n",
"clusters_cuda_per_frame = []\n",
"\n",
"cf_cuda.reset_timers()\n",
"t0 = time.perf_counter()\n",
"for start in range(0, N, BATCH_SIZE):\n",
" stop = min(start + BATCH_SIZE, N)\n",
" clusters_cuda_per_frame.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",
"kernel_ms = cf_cuda.avg_kernel_time_ms()\n",
"n_clusters_cuda = sum(cv.size for cv in clusters_cuda_per_frame)\n",
"hist_cuda = make_hist_from_batch(clusters_cuda_per_frame)\n",
"\n",
"print(f'CUDA batched: {t_cuda:.3f}s ({N/t_cuda:.0f} FPS, '\n",
" f'{n_clusters_cuda} clusters, {n_clusters_cuda/N:.2f}/frame)')\n",
"print(f' Kernel only: {kernel_ms:.3f} ms/frame')\n",
"print(f' PCIe + overhead: {t_cuda*1000/N - kernel_ms:.3f} ms/frame')\n",
"print(f'Speedup (CPU/CUDA): {t_cpu / t_cuda:.2f}x')"
]
},
{
"cell_type": "markdown",
"id": "md-graph",
"metadata": {},
"source": [
"## CUDA — Graph (pinned dataset)\n",
"Pre-records the H2D->kernel->D2H pipeline as a CUDA Graph per stream, cutting per-frame CPU API overhead (~21 us vs ~60 us for the streamed version)."
]
},
{
"cell_type": "code",
"execution_count": 11,
"id": "graph",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
"CUDA Graph: 3.132s (6386 FPS, 28439289 clusters, 1421.96/frame)\n",
" Kernel+PCIe+ovhd: 0.157 ms/frame (kernel not individually timed)\n",
"Speedup (vs batched): 0.99x\n",
"Speedup (CPU/Graph): 2.92x\n"
]
}
],
"source": [
"cf_graph.register_input_buffer(data)\n",
"clusters_graph_per_frame = []\n",
"\n",
"t0 = time.perf_counter()\n",
"for start in range(0, N, BATCH_SIZE):\n",
" stop = min(start + BATCH_SIZE, N)\n",
" clusters_graph_per_frame.extend(\n",
" cf_graph.find_clusters_batched(data[start:stop], first_frame=start))\n",
"t_graph = time.perf_counter() - t0\n",
"\n",
"cf_graph.unregister_input_buffer()\n",
"n_clusters_graph = sum(cv.size for cv in clusters_graph_per_frame)\n",
"hist_graph = make_hist_from_batch(clusters_graph_per_frame)\n",
"\n",
"print(f'CUDA Graph: {t_graph:.3f}s ({N/t_graph:.0f} FPS, '\n",
" f'{n_clusters_graph} clusters, {n_clusters_graph/N:.2f}/frame)')\n",
"print(f' Kernel+PCIe+ovhd: {t_graph*1000/N:.3f} ms/frame (kernel not individually timed)')\n",
"print(f'Speedup (vs batched): {t_cuda / t_graph:.2f}x')\n",
"print(f'Speedup (CPU/Graph): {t_cpu / t_graph:.2f}x')"
]
},
{
"cell_type": "markdown",
"id": "md-async",
"metadata": {},
"source": [
"## CUDA — async double-buffered pipeline\n",
"Two pinned batch buffers: while the GPU processes `buf[cur]`, the CPU fills `buf[nxt]`, so H2D copies and kernels stay back-to-back."
]
},
{
"cell_type": "code",
"execution_count": 12,
"id": "async",
"metadata": {},
"outputs": [],
"source": [
"# buf = [np.empty((BATCH_SIZE, rows, cols), dtype=np.uint16) for _ in range(2)]\n",
"# cf_async.pin_buffer(buf[0])\n",
"# cf_async.pin_buffer(buf[1])\n",
"\n",
"# clusters_async = []\n",
"# cf_async.reset_timers()\n",
"# t0 = time.perf_counter()\n",
"\n",
"# cur = 0\n",
"# n0 = min(BATCH_SIZE, N)\n",
"# buf[cur][:n0] = data[:n0]\n",
"# tok = cf_async.submit_batch(buf[cur][:n0], first_frame=0)\n",
"\n",
"# for start in range(BATCH_SIZE, N, BATCH_SIZE):\n",
"# nxt = 1 - cur\n",
"# stop = min(start + BATCH_SIZE, N)\n",
"# n = stop - start\n",
"# buf[nxt][:n] = data[start:stop] # fill next while GPU runs current\n",
"# next_tok = cf_async.submit_batch(buf[nxt][:n], first_frame=start)\n",
"# clusters_async.extend(cf_async.collect(tok)) # drain previous\n",
"# tok = next_tok\n",
"# cur = nxt\n",
"# clusters_async.extend(cf_async.collect(tok)) # drain final\n",
"\n",
"# t_async = time.perf_counter() - t0\n",
"# cf_async.unpin_buffer(buf[0]); cf_async.unpin_buffer(buf[1])\n",
"\n",
"# kernel_ms_async = cf_async.avg_kernel_time_ms()\n",
"# n_clusters_async = sum(cv.size for cv in clusters_async)\n",
"# hist_async = make_hist_from_batch(clusters_async)\n",
"\n",
"# print(f'CUDA async pipeline: {t_async:.3f}s ({N/t_async:.0f} FPS, '\n",
"# f'{n_clusters_async} clusters, {n_clusters_async/N:.2f}/frame)')\n",
"# print(f' Kernel only: {kernel_ms_async:.3f} ms/frame')\n",
"# print(f' PCIe + overhead: {t_async*1000/N - kernel_ms_async:.3f} ms/frame')\n",
"# print(f'Speedup (vs Graph): {t_graph / t_async:.2f}x')"
]
},
{
"cell_type": "markdown",
"id": "md-agree",
"metadata": {},
"source": [
"## Agreement sanity check\n",
"Counts should match to a hair. The residual comes from the CUDA finder updating the pedestal once per frame vs the CPU's per-pixel update (analysed in `ClusterFinderFrozen_vs_CUDA.ipynb`)."
]
},
{
"cell_type": "code",
"execution_count": 13,
"id": "agree",
"metadata": {},
"outputs": [
{
"name": "stdout",
"output_type": "stream",
"text": [
" CPU 28,438,072 diff vs CPU 0 (0.0000%)\n",
" CUDA per-frame 28,445,699 diff vs CPU 7,627 (0.0268%)\n",
" CUDA batched 28,439,289 diff vs CPU 1,217 (0.0043%)\n",
" CUDA graph 28,439,289 diff vs CPU 1,217 (0.0043%)\n"
]
}
],
"source": [
"for name, n in [('CPU', n_clusters_cpu), ('CUDA per-frame', n_clusters_cuda_v1),\n",
" ('CUDA batched', n_clusters_cuda), ('CUDA graph', n_clusters_graph)]:\n",
" # ('CUDA async', n_clusters_async)]:\n",
" d = abs(n - n_clusters_cpu)\n",
" print(f' {name:<16} {n:>12,} diff vs CPU {d:>6,} ({d/max(n_clusters_cpu,1):.4%})')"
]
},
{
"cell_type": "markdown",
"id": "md-plots",
"metadata": {},
"source": [
"## Spectrum: CPU vs CUDA variants"
]
},
{
"cell_type": "code",
"execution_count": 14,
"id": "plots",
"metadata": {},
"outputs": [
{
"data": {
"image/png":
"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 truncated
"text/plain": [
"<Figure size 800x600 with 2 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"fig, (ax_spec, ax_ratio) = plt.subplots(\n",
" 2, 1, figsize=(8, 6), sharex=True,\n",
" gridspec_kw={'height_ratios': [3, 1]})\n",
"\n",
"edges = hist_cpu.axes[0].edges\n",
"cpu_vals = hist_cpu.values()\n",
"cuda_vals_v1 = hist_cuda_v1.values()\n",
"cuda_vals = hist_cuda.values()\n",
"# async_vals = hist_async.values()\n",
"graph_vals = hist_graph.values()\n",
"\n",
"ax_spec.stairs(cpu_vals, edges, label=f'CPU ({n_clusters_cpu} clusters)')\n",
"ax_spec.stairs(cuda_vals_v1, edges, label=f'CUDA single frames ({n_clusters_cuda} clusters)', linestyle='--')\n",
"ax_spec.stairs(cuda_vals, edges, label=f'CUDA batched ({n_clusters_cuda} clusters)', linestyle='--')\n",
"# ax_spec.stairs(async_vals, edges, label=f'CUDA async ({n_clusters_async} clusters)', linestyle='-.')\n",
"ax_spec.stairs(graph_vals, edges, label=f'CUDA graph ({n_clusters_graph} clusters)', linestyle=':')\n",
"ax_spec.set_ylabel('Counts')\n",
"ax_spec.set_title('Cluster energy spectrum: CPU vs CUDA variants')\n",
"ax_spec.legend()\n",
"ax_spec.grid(alpha=0.2)\n",
"\n",
"with np.errstate(divide='ignore', invalid='ignore'):\n",
" ratio_cuda = np.where(cpu_vals > 0, cuda_vals / cpu_vals, np.nan)\n",
" ratio_graph = np.where(cpu_vals > 0, graph_vals / cpu_vals, np.nan)\n",
"\n",
"ax_ratio.stairs(ratio_cuda, edges, label='CUDA batched / CPU', color='C1', linestyle='--')\n",
"ax_ratio.stairs(ratio_graph, edges, label='CUDA graph / CPU', color='C3', linestyle=':')\n",
"ax_ratio.axhline(1.0, color='gray', linewidth=0.5)\n",
"ax_ratio.set_ylabel('Variant / CPU')\n",
"ax_ratio.set_xlabel('Energy [ADU]')\n",
"ax_ratio.set_ylim(0.5, 2.0)\n",
"ax_ratio.legend(fontsize=8)\n",
"ax_ratio.grid(alpha=0.3)\n",
"\n",
"plt.tight_layout()\n",
"plt.show()"
]
},
{
"cell_type": "code",
"execution_count": null,
"id": "cbd03f58-0068-4ad6-aec6-51ed9c8f8f1e",
"metadata": {},
"outputs": [],
"source": []
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.11.15"
}
},
"nbformat": 4,
"nbformat_minor": 5
}
+25 -76
View File
@@ -17,12 +17,7 @@
"| `ClusterFinderFrozen` | frozen snapshot for all decisions; **deferred** to frame end |\n",
"| `ClusterFinderCUDA` | frozen per frame; batch update at frame end |\n",
"\n",
"`Frozen` and `CUDA` share the same update *model*.\n",
"\n",
"**Expected result** (CUDA at `n_streams=1`):\n",
"- `cpu vs cuda` → the current gap (what we're explaining)\n",
"- `frozen vs cuda` → **≈ 0** (timing was the whole story)\n",
"- `frozen vs cpu` → ≈ the gap (exactly the flipped frames)"
"`Frozen` and `CUDA` share the same update *model*."
]
},
{
@@ -54,13 +49,14 @@
"name": "stdout",
"output_type": "stream",
"text": [
"image (400, 400) pedestal 1000 data 50000 streams 1\n"
"image (400, 400) pedestal 1000 data 20000 streams 4\n"
]
}
],
"source": [
"base = Path('/mnt/sls_det_storage/matterhorn_data/aare_test_data/')\n",
"f = File(base / 'Moench03new/cu_half_speed_master_4.json')\n",
"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\n",
"N = 20000\n",
@@ -69,7 +65,7 @@
"image_size = (rows, cols)\n",
"capacity = 50_000\n",
"\n",
"N_STREAMS = 1 # single device pedestal -> apples-to-apples with the CPU finders\n",
"N_STREAMS = 4 # single device pedestal -> apples-to-apples with the CPU finders\n",
"N_SIGMA = 5\n",
"sx, sy = cluster_size\n",
"rx, ry = sx // 2, sy // 2\n",
@@ -86,7 +82,7 @@
"cf_cpu = ClusterFinder(image_size, cluster_size, n_sigma=N_SIGMA, capacity=capacity)\n",
"cf_frozen = ClusterFinderFrozen(image_size, cluster_size, n_sigma=N_SIGMA, capacity=capacity)\n",
"cf_cuda = ClusterFinderCUDA(image_size, cluster_size, n_sigma=N_SIGMA,\n",
" max_clusters_per_frame=1500, n_streams=N_STREAMS)"
" max_clusters_per_frame=3000, n_streams=N_STREAMS)"
]
},
{
@@ -99,23 +95,22 @@
"name": "stdout",
"output_type": "stream",
"text": [
"pedestal train: 0.80s\n",
"data: (50000, 400, 400) uint16\n"
"pedestal train: 0.78s\n",
"data: (20000, 400, 400) uint16\n"
]
}
],
"source": [
"# Train ALL three on the SAME pedestal frames, then load the data block.\n",
"f.seek(0)\n",
"t0 = time.perf_counter()\n",
"for _ in range(n_frames_pd):\n",
" img = f.read_frame().copy()\n",
" img = pd.read_frame().copy()\n",
" cf_cpu.push_pedestal_frame(img)\n",
" cf_frozen.push_pedestal_frame(img)\n",
" cf_cuda.push_pedestal_frame(img)\n",
"print(f'pedestal train: {time.perf_counter()-t0:.2f}s')\n",
"\n",
"f.seek(n_frames_pd)\n",
"f.seek(0)\n",
"data = f.read_n(N)\n",
"print('data:', data.shape, data.dtype)"
]
@@ -126,9 +121,8 @@
"metadata": {},
"source": [
"## The three-way comparison\n",
"\n",
"All three finders now carry the **same** trained pedestal. `compare_finders` runs each\n",
"over the same sampled frames and reports exact (tol=0) pairwise centre mismatches."
"- All three finders now carry the **same** trained pedestal. \n",
"- `compare_finders` runs each over the same sampled frames and reports exact (tol=0) pairwise centre mismatches."
]
},
{
@@ -144,15 +138,15 @@
"Scanned 20000 frames\n",
"\n",
"Total clusters per finder:\n",
" cpu 27,666,006\n",
" frozen 27,665,954\n",
" cuda 27,665,796\n",
" cpu 46,478,554\n",
" frozen 46,478,563\n",
" cuda 46,478,563\n",
"\n",
"Pairwise exact mismatches (tol=0):\n",
" pair A-only B-only total\n",
" cpu vs frozen 94 42 136 (0.0005%)\n",
" cpu vs cuda 252 42 294 (0.0011%)\n",
" frozen vs cuda 158 0 158 (0.0006%)\n"
" cpu vs frozen 13 22 35 (0.0001%)\n",
" cpu vs cuda 13 22 35 (0.0001%)\n",
" frozen vs cuda 0 0 0 (0.0000%)\n"
]
}
],
@@ -197,7 +191,7 @@
{
"data": {
"image/png":
"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 truncated
"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 truncated
"text/plain": [
"<Figure size 800x600 with 2 Axes>"
]
@@ -232,25 +226,14 @@
"name": "stdout",
"output_type": "stream",
"text": [
"frozen-only: 158 cuda-only: 0 frames shown: 8\n"
"frozen-only: 0 cuda-only: 0 frames shown: 0\n"
]
},
{
"data": {
"image/png":
"iVBORw0KGgoAAAANSUhEUgAABEEAABCFCAYAAADTiXqrAAAAOnRFWHRTb2Z0d2FyZQBNYXRwbG90bGliIHZlcnNpb24zLjEwLjgsIGh0dHBzOi8vbWF0cGxvdGxpYi5vcmcvwVt1zgAAAAlwSFlzAAAPYQAAD2EBqD+naQABAABJREFUeJzs3Qd4FNXXx/FfCiWUhN57EelNEaQpRUWx0ZSmggULUhRUVBQpitIUBbtiwb+CYAEsoCiICqigIk3pTTok1NR9n3N5NyYhgUACKfP9PM+6u7Mzd+/MDs7JmVsCfD6fTwAAAAAAANlcYEZXAAAAAAAA4HwgCQIAAAAAADyBJAgAAAAAAPAEkiAAAAAAAMATSIIAAAAAAABPIAkCAAAAAAA8gSQIAAAAAADwBJIgAAAAAADAE0iCAAAAAAAATyAJgizP5/PprrvuUqFChRQQEKDff/89o6sEZKhNmzbxbwEA/h9xApA2FSpU0PPPP89hRLZBEgRZ3ldffaUpU6Zo9uzZ+vfff1WrVi1lBX369FHlypUVEhKiokWL6vrrr9eaNWtOuuhYYifh45FHHon/fN++fbrqqqtUqlQp5cqVS2XLllXfvn0VERGR7HeuW7dO+fPnV4ECBRItX7RokZo2barChQu7+lx44YWaMGGCzqfvvvtOV199tatDnjx5VKNGDT344IPavn27+/z7779PdBzsmLVr105//PHHaS/Stsw+OxMrV65Ux44d43+D5ModNmzYSb9PiRIlTlrHjmfevHlVsGBBtWnTRkuWLIn/fP/+/br//vtVrVo1t9/lypVTv379FB4erszC/n0lPWcAIKsgTvBGnHCqa5Utt8/9El637fpctWpV3Xbbbfrtt9+S3X7btm3KmTOn2++zcfz4cVd+7dq1FRwcrBtuuOGkdZLGOf5H0thwxowZbt/t97TnTz755KSyJk+erIoVKyp37txq2LChfvjhB2UW3KhBZkASBFne+vXrVbJkSV166aXuD1C7uCQVFRWlzMYuSm+//bZWr16tr7/+2t2puuKKKxQbG5toveHDh7vkjv/x+OOPx38WGBjokieff/65/v77b3eB/+abb3T33Xef9H3R0dHq2rWrmjdvftJnFgBYULRw4UJXH/sOe7z22ms6H1599VWXHLDfzy7uq1at0iuvvOISAePGjUu07tq1a91xmDNnjg4cOOCSQOciYXD06FFVqlRJo0ePPimxkVDNmjUT/T4rVqxI9PkFF1ygl156yS23INKSKvY779mzx32+Y8cO9xg7dqxbx35DC9hvv/12ZTd2bsfFxWV0NQB4DHGCt+KE1LIYzK7bdtNj0qRJOnz4sC655BK9++67J61rx61Lly4uNvjxxx/P6vpnySO7yWH7cSr+OMf/sASN388//6ybbrpJPXv2dDeB7NnqlfDmykcffaQBAwboscce0/Lly93vaTeNtmzZouzGzlngrPiALOzWW2/12Wnsf5QvX94tb9mype++++7zDRw40Fe4cGFfixYt3PJx48b5atWq5cuTJ4+vTJkyvnvuucd36NCh+PLefvttX1hYmG/WrFm+Cy64wBcSEuLr2LGj7/Dhw74pU6a48gsUKODr27evLyYmJn67yMhI3+DBg32lSpVyZTdq1Mj33XffndG+/PHHH24f1q1bF7/Mvm/ChAlnVM4LL7zg9i2phx56yNejR4/4fTydG2+80a1/rm3dutWXM2dO34ABA5L9/MCBA+7ZjqcdH/97s2jRIrfsq6++OuXxsmX+c+NspFTuk08+6atbt+4ZlRUeHu7q/M0336S4zrRp09wxiY6OTnGd2NhY3+jRo32VK1d265YtW9Y3cuRI99nGjRvddyxfvty9T+43/+STT9w6fr///rvvsssu8+XLl8+XP39+X4MGDXy//PJL/HFP+LD9Ts15n/DfU/Xq1X1BQUG+DRs2uHUuvvhit419fumll/o2bdp0RscRAFKDOME7ccKp6m3L7XM/u5bZdTCpW265xV0D9+/fH78sLi7OV6lSJRdrPPzww75evXql+Zy8/vrrT1qeXJyTVJcuXXxXXXVVomVXXnml7+abb45/b9fiu+++O9E6F154oe+RRx45Zb0+++wzX8OGDX25cuVysbP9vsnFQUljDGN1tmX+GMCOX7du3XxFihTx5c6d21elShXfW2+95T5LGlNYzO5n61hdrQ7VqlXzTZo0Kf4z//d+9NFHbhtbx9a3+KF9+/YuPre4okaNGr45c+accl8BWoIgS3vhhRdcS4kyZcq4bPkvv/wS/9k777zjWoVYxt7uIPhbTkycOFF//fWX+3z+/Pl66KGHEpVpWX5b58MPP3R35K15YocOHfTFF1+4x3vvvefufHz88cfx2/Tq1ct9j23z559/qnPnzq6Fwj///JOq/Thy5Ii7I2FNF62pakLPPvusa/pZr149jRo16pStWqxFwcyZM9WyZctEy20/p0+f7u50pIbdOfjpp59OKudcsHrZPiX9HfxO1Q3D7qqczZ0Af5NTa5KZVvYbWzNj++1uvvlmbdiwIcV1bT/t3AkLC1PdunVTXM/ubIWGhibbqslvyJAh7twYOnSouyP2wQcfqHjx4me9H927d3f/juzfkDUHtm5XOXLkcC2srCuQ1cd/V2rQoEGpPu/t39MzzzyjN954w91ts7F7rBmwnVu2jd3VsjF97PcAgPRGnODtOOFMDRw4UIcOHdK8efMSdcOxa5m14LCWF9OmTXPrJGTXsITdbdKifv36roVz69at3XcnZNdMa02a0JVXXul+C2PHya7hSdex9/51kmOtay3Wveaaa9xv++233+qiiy46633wxyZffvmlazn08ssvq0iRIu6zpUuXumdrkWQxhZ2P5vXXX3etVyzWtW2efvppV47F6wk9/PDDrkWNrWP7ft999ykyMtK1UrIWtRYb5cuX76zrDo8gD4SsLrm7/JYhrlev3mm3tTvulu32s7sESVtj9OnTx2WWE7YYsay7LTe2bkBAgG/79u2Jym7durVvyJAhp/x+y3DnzZvXfadlvhN+rxk/frzv+++/d61EXn/9dZdRv/32208qx+4AWKsVK+faa6/1HTt2LP6zvXv3ulYCCxYsOO2dktKlS7u7LYGBgb7hw4f7zgdrjRMaGnra9ZLeIbH9uu6669wdm127dp1RS5AlS5a4Owzbtm1LVR1TKveLL77wffzxx74///zTN2/ePHfeFS9e3NUtIWsJYb+znSfWamLp0qUpfpdtW65cOd9jjz2W4joRERHuDoidE8k5m5YgdhyttVNykts+Nee9/9+TtTLx27dvn1tm5zUAnA/ECd6IE9KjJYgdF/vs2WefjV9mLRoStkKxFqBJr78WU8ycOTNNLUHWrFnje+2113y//fab76effnL7bddZ/+9icuTI4Zs6dWqi7ey9/SbGrslW/x9//DHROqNGjXItnFPSpEkTX/fu3VP8/Exbgtg5llKLmeS2N3YOfvDBB4mWjRgxwtUt4XbPP/98onVq167tGzZsWIp1B5KT8m1GIItLLoNtGXXLLFt22gYFi4mJcYNVWUsM6+9qbLAtG7DUz+6u2zgOCbPKtmz37t3u9bJly9x4Hjb2Q0KWlbYWHKe7+962bVuXCbcxIaxfp91Zt4Gs/Hck/OrUqeMG1uzUqVN86xA/G5zsySefdP1IH330UT3wwANuUCxz5513qlu3bmrRosVpj5kNnGV9YhcvXuxaAlSpUsX1D05pLIzNmzcrtXr06OH67yZlx+5MWgFYawVjv5n1k7U7RMWKFdOZaNSo0UkDjZ0N62PrZ4OdNWnSxJ07dtfCfgO/yy+/3M1atHfvXnenw99/N2m97Zy0uzA20Jn9nimxux92ftldovRi9b3jjjtcSye722WtOhL+O0gqtee9DSRn566ftQSxweHs7o2d+/ZddjzsrhcAnE/ECdkzTkiLE/mREy07zMGDB11LBRvTK2E933rrLXfN9EuPmMIGSLeHn8UUW7dudfFhwt8m6bFI7vikZp2ELEax8yC93HPPPW5weYsVrBWKtQC1lqUpsXHSbF9tPLSE9bA43VrPnurfrbUKse+bO3euiynsexPGHUBySIIg2/InNfzsQmyjittgYCNGjHB/jNlFzf6Hm7A7hXUBSMguGskt8w/waM9BQUGu+aE9J3S65nj2P3Z72B/zjRs3dkkOG+U7pYDC1vGP3p7wD00bKMweNmq5LbdBsKwJof1haU1cbUA0u4j6L4RWZ+tqYV0zevfuHV+Odenw/0G/a9cuN7NJSnWxrkFn0g3FulMkx/6Itu4flghKzR/CFoBZWTY7TNIy7X1yg6RaEJP0Inquzjk7dkm7QdlyCxTtYb+h/d5vvvmm69LiZ01rrSuJnTN2DiQ955LrBpRa1g3MH9j5Jf3t7Le2INiaxFrzVUvCWDeXG2+8MdkyU3veW12TBl7W9cuCFutuZgO42eB61vTYf34DwPlAnJC94gTb3hI0NghpwuuSvbflqYkD7CZDwv20rqZ2s8wGTPXzHx+7oWY3Lc4luy6+//778e8t1tu5c2eideymnL87rHU5sX0/1TppjSsspjAJ44qkv7PdJLK422IK6/ZiN22s24r/HEvKH1PbjaKEx9okjTGS/ru1ZJTdWLHvskSIdcG1wXJt5j0gJSRB4Bm//vqryyjb/xj9/wO3fp3p0XfTLrB2gUluRPUzYRcUu5OeEuunaU4VBPgvSv5yrP9owhlnPvvsM9eSxPqGli5d+qzrUr58eaUHa9lid5Oee+65ZKfbswRGwv6+Fpik1P/XkkAJx4Xxs2UJ766cK3a8LIA63XmQ9NhaCxC7gNt0dxaI+lsCpcSSKBawWJ/dhHeiUmIJI0uyJGzxZHd9kgs07WEtkCyotWSFJUGsNUfSWYvSet7b9vawRJDd7bJAkyQIgIxEnJC14wSLAey6ZLFSwtYC1hrBlqcmDvLine truncated
"text/plain": [
"<Figure size 1100x4240 with 16 Axes>"
]
},
"metadata": {},
"output_type": "display_data"
}
],
"source": [
"cf_frozen2 = ClusterFinderFrozen(image_size, cluster_size, n_sigma=N_SIGMA, capacity=capacity)\n",
"cf_cuda2 = ClusterFinderCUDA(image_size, cluster_size, n_sigma=N_SIGMA,\n",
" max_clusters_per_frame=1500, n_streams=N_STREAMS)\n",
" max_clusters_per_frame=3000, n_streams=N_STREAMS)\n",
"train_pedestal([cf_frozen2, cf_cuda2], f, n_frames_pd, seek=0)\n",
"\n",
"# scan_mismatches(a, b, ...) snapshots a.pedestal/noise (host) and b.device_*(0).\n",
@@ -269,9 +252,7 @@
"## Walkthrough of the surviving mismatch\n",
"\n",
"Manual Test1/Test3 recompute of the strongest residual cluster in `show`, under each\n",
"finder's decision-time snapshot pedestal. For 7×7 (a single residual) this shows exactly\n",
"what the lone mismatched cluster looks like and which test tips it — naming the cause\n",
"(FP knife-edge at threshold, a border/tie corner, or a genuine third source)."
"finder's decision-time snapshot pedestal."
]
},
{
@@ -284,44 +265,12 @@
"name": "stdout",
"output_type": "stream",
"text": [
"frame 35343 centre (x=19, y=349) accepted only by frozen\n",
"raw window (ADU):\n",
"[[5998 6480 6226]\n",
" [5952 9428 6189]\n",
" [6250 6202 6378]]\n",
"\n",
"--- frozen ---\n",
" subtracted window:\n",
" [[ -54.9 258. -127.8]\n",
" [ -56.4 3138.6 46.2]\n",
" [ 21.3 -91. -34.8]]\n",
" centre value = 3138.63 (local max? True)\n",
" max = 3138.63 total = 3099.23\n",
" rms(centre) = 54.017\n",
" Test1: max > 5*rms = 270.09 -> True\n",
" Test3: total > 3*5*rms = 810.26 -> True\n",
" ACCEPT = True\n",
"\n",
"--- cuda ---\n",
" subtracted window:\n",
" [[ -54.9 258. -127.8]\n",
" [ -56.4 3138.6 46.2]\n",
" [ 21.3 -91. -34.8]]\n",
" centre value = 3138.63 (local max? True)\n",
" max = 3138.63 total = 3099.23\n",
" rms(centre) = 54.017\n",
" Test1: max > 5*rms = 270.09 -> True\n",
" Test3: total > 3*5*rms = 810.26 -> True\n",
" ACCEPT = True\n",
"\n",
"RESULT: frozen = ACCEPT , cuda = ACCEPT\n",
"pedestal mean gap @centre = 0.0000 ADU; rms gap = 0.0000\n",
"NOTE: recompute agrees for this centre pixel — the split is FP rounding right at threshold or a window-neighbour effect; try another pick.\n"
"No residual mismatches to walk through.\n"
]
}
],
"source": [
"# Dissect the surviving frozen-vs-cuda residual (best seen for 7x7, where it's ~1).\n",
"# Dissect the surviving frozen-vs-cuda residual.\n",
"# Recomputes Test1/Test3 under each finder's decision-time snapshot pedestal, so the\n",
"# lone mismatched cluster is fully explained (which test flips, and the pedestal gap).\n",
"walkthrough(show, data, rx, ry, rows, cols, N_SIGMA, pick=0, labels=('frozen', 'cuda'))"