Quantum 3xplorer (Q3)
An AI-native quantum simulation engine
Loading...
Searching...
No Matches
counters.hpp
Go to the documentation of this file.
1#pragma once
2
3/**
4 * Centralized profiling counters for Q3.
5 *
6 * Encapsulates all global profiling state (call counts, timing accumulators)
7 * into a singleton to avoid scattered mutable globals across headers.
8 */
9
10#include <algorithm>
11#include <atomic>
12#include <cstdint>
13#include <map>
14#include <string>
15
16#include "io/logger.hpp"
18
19namespace q3 {
20
22 std::atomic<long long> fft_call_count{0};
23 std::atomic<long long> davidson_iteration_count{0};
24 std::atomic<long long> hamiltonian_application_count{0};
25
26 /**
27 * High-water mark of this rank's resident set, in bytes.
28 *
29 * Sampled at the phase boundaries that allocate, so that the reported peak
30 * covers the setup spike and the first Davidson restart rather than
31 * whichever moment the run happened to end on.
32 */
33 std::atomic<std::uint64_t> peak_resident_bytes{0};
34
35 // Davidson phase timing accumulators (milliseconds)
37 double seed_time_ms = 0;
39 double residual_time_ms = 0;
43 double collapse_time_ms = 0;
46
47 // H|ψ⟩ sub-component timing (accumulated across all calls)
51 /// The projector overlaps <beta|psi> of the nonlocal term, a part of nonlocal above.
53
54 void print_summary() const {
55 log::debug(" Davidson timing breakdown:");
56 log::debug(" seed: {:8.1f} ms", seed_time_ms);
57 log::debug(" ritz_vectors: {:8.1f} ms", ritz_vector_time_ms);
58 log::debug(" residuals: {:8.1f} ms", residual_time_ms);
59 log::debug(" orthogonalize: {:8.1f} ms", orthogonalize_time_ms);
60 log::debug(" hamiltonian_application: {:8.1f} ms",
62 log::debug(" fft: {:8.1f} ms", hamiltonian_fft_time_ms);
63 log::debug(" kinetic: {:8.1f} ms", hamiltonian_kinetic_time_ms);
64 log::debug(" nonlocal: {:8.1f} ms", hamiltonian_nonlocal_time_ms);
65 log::debug(" projector overlap: {:8.1f} ms",
67 log::debug(" subspace_update: {:8.1f} ms", subspace_update_time_ms);
68 log::debug(" diagonalize: {:8.1f} ms", diagonalize_time_ms);
69 log::debug(" collapse: {:8.1f} ms", collapse_time_ms);
70 log::debug(" TOTAL: {:8.1f} ms", total_time_ms());
71 }
72
73 void reset() {
75 }
76
77 /**
78 * The same breakdown print_summary() logs, as data.
79 *
80 * Caveat for anyone doing arithmetic on these: the four
81 * hamiltonian_* sub-component timers are NOT a partition of
82 * hamiltonian_application. They are accumulated in the applicator
83 * (hamiltonian_applicator/serial.hpp) while the parent is accumulated
84 * around the call site in davidson.hpp, and the sub-components are
85 * reached from paths the parent timer does not wrap -- a 2-atom
86 * silicon run reports 72.9 ms of hamiltonian_fft against 52.2 ms of
87 * hamiltonian_application. Use them to compare like with like across
88 * configurations, not to decompose the parent.
89 *
90 * Kept beside the printer so the two cannot drift: a stage added to one
91 * and forgotten in the other is the failure mode this guards against.
92 * Returns a flat map of stage name to milliseconds; the caller decides
93 * where it goes.
94 */
95 [[nodiscard]] std::map<std::string, double> as_map() const {
96 return {{"seed", seed_time_ms},
97 {"ritz_vectors", ritz_vector_time_ms},
98 {"residuals", residual_time_ms},
99 {"orthogonalize", orthogonalize_time_ms},
100 {"hamiltonian_application", hamiltonian_application_time_ms},
101 {"hamiltonian_fft", hamiltonian_fft_time_ms},
102 {"hamiltonian_kinetic", hamiltonian_kinetic_time_ms},
103 {"hamiltonian_nonlocal", hamiltonian_nonlocal_time_ms},
104 {"hamiltonian_projector_overlap", hamiltonian_projector_overlap_time_ms},
105 {"subspace_update", subspace_update_time_ms},
106 {"diagonalize", diagonalize_time_ms},
107 {"collapse", collapse_time_ms},
108 {"total", total_time_ms()}};
109 }
110
111 /**
112 * The same sum print_summary() reports as TOTAL. The Hamiltonian
113 * sub-components (fft, kinetic, nonlocal, allreduce) are deliberately
114 * excluded: they partition hamiltonian_application, so adding them
115 * again would double-count it.
116 */
122 };
123
125
126 /**
127 * Take a resident-memory reading and keep it if it is the largest so far.
128 *
129 * Returns the sample so a caller that wants to log the current value does
130 * not have to read the platform source twice.
131 */
134 if (!sample.available) {
135 return sample;
136 }
137 std::uint64_t observed = std::max(sample.current_bytes, sample.peak_bytes);
138 std::uint64_t previous = peak_resident_bytes.load(std::memory_order_relaxed);
139 while (observed > previous && !peak_resident_bytes.compare_exchange_weak(
140 previous, observed, std::memory_order_relaxed)) {}
141 return sample;
142 }
143
144 /**
145 * Record a reading and log it against a phase label.
146 *
147 * One line per allocating phase, at info, so that a run's own output says
148 * where its footprint came from. Phases that allocate nothing are not
149 * worth a probe; phases inside the Davidson loop log at debug instead,
150 * because there are as many of them as there are iterations.
151 */
152 void log_resident_checkpoint(const std::string& label) {
154 if (!sample.available) {
155 return;
156 }
157 log::info(" memory after {:<22}= {} (peak {})",
158 label,
161 }
162
163 /**
164 * Return freed heap to the operating system and record the reading after
165 * it, at debug level. Placed at phase boundaries: the peak recorded before
166 * the release stands, the current figure afterwards is what the next
167 * phase builds on.
168 */
169 void release_unused_heap(const std::string& label) {
170 std::uint64_t released = q3::release_unused_heap();
172 if (!sample.available) {
173 return;
174 }
175 log::debug(" memory released after {:<19}= {} (now {}, peak {})",
176 label,
180 }
181
182 /**
183 * The same reading at debug level, for probes that fire per SCF iteration
184 * or per Davidson restart. The console sink is pinned at info, so these
185 * reach a --log-file and nothing else.
186 */
187 void log_resident_checkpoint_debug(const std::string& label) {
189 if (!sample.available) {
190 return;
191 }
192 log::debug(" memory after {:<22}= {} (peak {})",
193 label,
196 }
197
198 /**
199 * The peak so far, after one more reading. The counter only advances when
200 * something samples it, and the kernel's high-water mark is folded in at
201 * each sample, so a reader that wants the run's true peak -- rather than
202 * the peak as of the last probe, which may predate the forces and stress
203 * -- takes a fresh sample first. This is what the end-of-run reports use.
204 */
205 std::uint64_t peak_resident_bytes_now() {
207 return peak_resident_bytes.load(std::memory_order_relaxed);
208 }
209
210 /// Print the peak resident set, omitted when no platform source could be read.
212 std::uint64_t peak = peak_resident_bytes_now();
213 if (peak == 0) {
214 return;
215 }
216 log::info(" Peak resident memory: {}",
218 }
219
221 static ProfilingCounters counters;
222 return counters;
223 }
224
225 /**
226 * Reset all profiling counters to zero.
227 *
228 * Must be called by the nanobind module between successive in-process
229 * invocations (Mode B) to prevent counter accumulation across calls.
230 * Subprocess runs (Mode A) each get a fresh process, so reset() is
231 * unnecessary there.
232 */
233 void reset() {
234 fft_call_count.store(0, std::memory_order_relaxed);
235 davidson_iteration_count.store(0, std::memory_order_relaxed);
236 hamiltonian_application_count.store(0, std::memory_order_relaxed);
237 peak_resident_bytes.store(0, std::memory_order_relaxed);
239 }
240
241 private:
242 ProfilingCounters() = default;
243};
244
245} // namespace q3
void debug(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:179
void info(spdlog::format_string_t< Args... > fmt, Args &&... args)
Definition logger.hpp:183
std::uint64_t release_unused_heap()
Definition memory.hpp:174
double hamiltonian_projector_overlap_time_ms
The projector overlaps <beta|psi> of the nonlocal term, a part of nonlocal above.
Definition counters.hpp:52
std::map< std::string, double > as_map() const
Definition counters.hpp:95
void log_resident_checkpoint_debug(const std::string &label)
Definition counters.hpp:187
std::atomic< long long > fft_call_count
Definition counters.hpp:22
ResidentMemorySample record_resident_memory()
Definition counters.hpp:132
void log_resident_checkpoint(const std::string &label)
Definition counters.hpp:152
static ProfilingCounters & instance()
Definition counters.hpp:220
std::uint64_t peak_resident_bytes_now()
Definition counters.hpp:205
std::atomic< long long > hamiltonian_application_count
Definition counters.hpp:24
void print_memory_summary()
Print the peak resident set, omitted when no platform source could be read.
Definition counters.hpp:211
std::atomic< std::uint64_t > peak_resident_bytes
Definition counters.hpp:33
void release_unused_heap(const std::string &label)
Definition counters.hpp:169
std::atomic< long long > davidson_iteration_count
Definition counters.hpp:23
DavidsonTimingAccumulator davidson_timing
Definition counters.hpp:124
static ResidentMemorySample sample()
Platforms without a known source report nothing rather than guessing.
Definition memory.hpp:146
static std::string format_gibibytes(std::uint64_t bytes)
Definition memory.hpp:152
std::uint64_t current_bytes
Resident set size at the moment of sampling.
Definition memory.hpp:49
std::uint64_t peak_bytes
High-water mark of the resident set over the life of the process.
Definition memory.hpp:51
bool available
Whether any source could be read.
Definition memory.hpp:59