From d5c954e9539493126aa9dedc8451d913c7b576ce Mon Sep 17 00:00:00 2001 From: David Krasowska Date: Tue, 1 Sep 2026 17:53:02 +0200 Subject: [PATCH] fix: update adaptive image to log iteration time --- .../dynamic-benchmarks/plot_adaptive_image.py | 69 ++++++++++++++++--- .../polymerpim/adaptive_image/run.cc | 10 +++ 2 files changed, 68 insertions(+), 11 deletions(-) diff --git a/benchmarks/dynamic-benchmarks/plot_adaptive_image.py b/benchmarks/dynamic-benchmarks/plot_adaptive_image.py index 9d8f366..258d515 100755 --- a/benchmarks/dynamic-benchmarks/plot_adaptive_image.py +++ b/benchmarks/dynamic-benchmarks/plot_adaptive_image.py @@ -3,6 +3,7 @@ import csv import math from collections import defaultdict +from statistics import median from dataclasses import dataclass from pathlib import Path @@ -15,6 +16,7 @@ RUNS_CSV = RESULTS / "adaptive-image.csv" SUMMARY_CSV = RESULTS / "adaptive-image-summary.csv" FIGURE = RESULTS / "adaptive-image.pdf" +TRACE_TXT = RESULTS / "adaptive-image-trace.txt" MODEL_ORDER = ( "polymerpim-jit", @@ -34,6 +36,9 @@ class Summary: stddev_ms: float min_ms: float max_ms: float + max_mean_ms: float + max_min_ms: float + max_max_ms: float wall_mean_s: float wall_stddev_s: float wall_min_s: float @@ -82,6 +87,28 @@ def load_rows(): return rows +def load_traces(): + # Per-iteration times from the focused 64-DPU runs; absent is fine. + collected = defaultdict(list) + if not TRACE_TXT.is_file(): + return {} + for line in TRACE_TXT.read_text().splitlines(): + fields = line.split() + if len(fields) < 4 or not fields[3].startswith("iteration_ms="): + continue + series = [float(value) for value + in fields[3].split("=", 1)[1].split(";") if value] + if series: + collected[(f"polymerpim-{fields[0]}", int(fields[1]))].append(series) + # Median per iteration index, so one unlucky trial cannot set the shape. + traces = {} + for key, trials in collected.items(): + width = min(len(series) for series in trials) + traces[key] = [median(series[index] for series in trials) + for index in range(width)] + return traces + + def summarize(rows): grouped = defaultdict(list) for row in rows: @@ -91,6 +118,7 @@ def summarize(rows): summaries = [] for (model, elements_per_dpu, dpus), trials in sorted(grouped.items()): mean_ms, stddev_ms = pooled_stats(trials) + trial_maxima = [float(row["max"]) for row in trials] wall = [float(row["real_s"]) for row in trials if row["real_s"]] summaries.append(Summary( model=model, @@ -103,6 +131,9 @@ def summarize(rows): stddev_ms=stddev_ms, min_ms=min(float(row["min"]) for row in trials), max_ms=max(float(row["max"]) for row in trials), + max_mean_ms=sum(trial_maxima) / len(trial_maxima), + max_min_ms=min(trial_maxima), + max_max_ms=max(trial_maxima), wall_mean_s=sum(wall) / len(wall), wall_stddev_s=sample_stddev(wall), wall_min_s=min(wall), @@ -134,23 +165,39 @@ def plot(rows): if len(sizes) != 2: raise SystemExit("adaptive_image plot expects exactly two problem sizes") - figure, axes = plt.subplots(3, len(sizes), figsize=(10, 9.5), sharex="col") + traces = load_traces() + rows_count = 3 if traces else 2 + figure, axes = plt.subplots(rows_count, len(sizes), + figsize=(10, 3.2 * rows_count)) for column, size in enumerate(sizes): selected = [row for row in rows if row.elements_per_dpu == size] - worst_axis, mean_axis, wall_axis = axes[:, column] + column_axes = list(axes[:, column]) + trace_axis = column_axes.pop(0) if traces else None + mean_axis, worst_axis = column_axes for model in MODEL_ORDER: draw(mean_axis, selected, model, "mean_ms") - draw(worst_axis, selected, model, "max_ms") - draw(wall_axis, selected, model, - "wall_mean_s", "wall_min_s", "wall_max_s") - - worst_axis.set_title(f"{size:,} elements/DPU", fontweight="bold") + draw(worst_axis, selected, model, + "max_mean_ms", "max_min_ms", "max_max_ms") + if trace_axis is not None: + draw_series(trace_axis, model, + list(enumerate(traces.get((model, size), []), + start=1)), + linewidth=1.6) + + top_axis = trace_axis if trace_axis is not None else mean_axis + top_axis.set_title(f"{size:,} elements/DPU", fontweight="bold") mean_axis.set_ylabel("Mean iteration (ms)" if column == 0 else "") - worst_axis.set_ylabel("Worst iteration (ms)" if column == 0 else "") - wall_axis.set_ylabel( - "Process wall time: mean, min–max (s)" if column == 0 else "") + worst_axis.set_ylabel( + "Worst iteration: mean, min-max (ms)" if column == 0 else "") + if trace_axis is not None: + trace_axis.set_ylabel( + "Iteration time, 64 DPUs (ms)" if column == 0 else "") + trace_axis.set_xlabel("Iteration") + trace_axis.grid(True, which="major", color="#d8d8d8", + linewidth=0.8) + trace_axis.set_axisbelow(True) dpus = sorted({row.dpus for row in selected}) - for axis in (mean_axis, worst_axis, wall_axis): + for axis in (mean_axis, worst_axis): configure_axis(axis, dpus, dpus, "DPUs") figure.suptitle("Adaptive image: dynamic execution trade-offs", diff --git a/benchmarks/dynamic-benchmarks/polymerpim/adaptive_image/run.cc b/benchmarks/dynamic-benchmarks/polymerpim/adaptive_image/run.cc index ab77c42..9174e1d 100644 --- a/benchmarks/dynamic-benchmarks/polymerpim/adaptive_image/run.cc +++ b/benchmarks/dynamic-benchmarks/polymerpim/adaptive_image/run.cc @@ -73,6 +73,8 @@ int main() { BenchTimer timer; bench_stats_init(&stats); uint32_t fine_updates = 0; + std::vector iteration_ms; + iteration_ms.reserve(iterations); std::vector last_errors(channels, 0); RuntimeStatistics runtime_before = statistics(); @@ -115,8 +117,16 @@ int main() { bench_stop(&timer, 0); bench_stats_update(&stats, timer.time[0]); + iteration_ms.push_back(timer.time[0] / 1000.0); } + // Per-iteration trace: check_interval spikes dominate min/max. + std::cout << "iteration_ms="; + for (size_t i = 0; i < iteration_ms.size(); ++i) { + std::cout << (i != 0 ? ";" : "") << iteration_ms[i]; + } + std::cout << std::endl; + std::vector expected; std::vector target; if (check_correctness) {