llama_cpp_4/context/perf.rs
1//! Safe wrapper around `llama_perf_context_data`.
2use std::fmt::{Debug, Display, Formatter};
3
4use llama_cpp_sys_4::{ggml_time_us, llama_perf_context_print};
5
6use crate::context::LlamaContext;
7
8/// A wrapper around `llama_perf_context_data`.
9#[derive(Clone, Copy, Debug)]
10pub struct PerfContextData {
11 pub(crate) perf_context_data: llama_cpp_sys_4::llama_perf_context_data,
12}
13
14impl PerfContextData {
15 /// Create a new `PerfContextData`.
16 /// ```
17 /// # use llama_cpp_4::context::perf::PerfContextData;
18 /// let timings = PerfContextData::new(1.0, 2.0, 3.0, 4.0, 5, 6);
19 /// assert_eq!(timings.t_load_ms(), 2.0);
20 /// assert_eq!(timings.t_p_eval_ms(), 3.0);
21 /// assert_eq!(timings.t_eval_ms(), 4.0);
22 /// assert_eq!(timings.n_p_eval(), 5);
23 /// assert_eq!(timings.n_eval(), 6);
24 /// ```
25 #[allow(clippy::too_many_arguments)]
26 #[must_use]
27 pub fn new(
28 t_start_ms: f64,
29 // t_end_ms: f64,
30 t_load_ms: f64,
31 // t_sample_ms: f64,
32 t_p_eval_ms: f64,
33 t_eval_ms: f64,
34 // n_sample: i32,
35 n_p_eval: i32,
36 n_eval: i32,
37 ) -> Self {
38 Self {
39 perf_context_data: llama_cpp_sys_4::llama_perf_context_data {
40 t_start_ms,
41 // t_end_ms,
42 t_load_ms,
43 // t_sample_ms,
44 t_p_eval_ms,
45 t_eval_ms,
46 // n_sample,
47 n_p_eval,
48 n_eval,
49 n_reused: 0,
50 // Upstream counters, added after this constructor's signature
51 // was fixed (llama.cpp b11829). They are only meaningful on a
52 // value read back from a context, so a hand-built one reports
53 // zero; see the accessors below.
54 n_reallocs_same: 0,
55 n_reallocs_diff: 0,
56 },
57 }
58 }
59
60 /// print llama context performance data
61 /// load time
62 /// prompt eval time
63 /// eval time
64 /// total time
65 pub fn print(ctx: &LlamaContext<'_>) {
66 // SAFETY: a live, non-null context; this only reads the counters and
67 // writes to llama.cpp's log.
68 unsafe {
69 llama_perf_context_print(ctx.context.as_ptr());
70 };
71 }
72
73 /// Get the start time in milliseconds.
74 #[must_use]
75 pub fn t_start_ms(&self) -> f64 {
76 self.perf_context_data.t_start_ms
77 }
78
79 /// Get the end time in milliseconds.
80 #[must_use]
81 pub fn t_end_ms(&self) -> f64 {
82 // self.perf_context_data.t_end_ms
83
84 #[allow(clippy::cast_precision_loss)]
85 {
86 // SAFETY: no pointer is involved; this reads ggml's monotonic clock.
87 1e-3 * (unsafe { ggml_time_us() }) as f64
88 }
89 }
90
91 /// Get the load time in milliseconds.
92 #[must_use]
93 pub fn t_load_ms(&self) -> f64 {
94 self.perf_context_data.t_load_ms
95 }
96
97 // /// Get the sample time in milliseconds.
98 // #[must_use]
99 // pub fn t_sample_ms(&self) -> f64 {
100 // self.perf_context_data.t_sample_ms
101 // }
102
103 /// Get the prompt evaluation time in milliseconds.
104 #[must_use]
105 pub fn t_p_eval_ms(&self) -> f64 {
106 self.perf_context_data.t_p_eval_ms
107 }
108
109 /// Get the evaluation time in milliseconds.
110 #[must_use]
111 pub fn t_eval_ms(&self) -> f64 {
112 self.perf_context_data.t_eval_ms
113 }
114
115 // /// Get the number of samples.
116 // #[must_use]
117 // pub fn n_sample(&self) -> i32 {
118 // self.perf_context_data.n_sample
119 // }
120
121 /// Get the number of prompt evaluations.
122 #[must_use]
123 pub fn n_p_eval(&self) -> i32 {
124 self.perf_context_data.n_p_eval
125 }
126
127 /// Get the number of evaluations.
128 #[must_use]
129 pub fn n_eval(&self) -> i32 {
130 self.perf_context_data.n_eval
131 }
132
133 /// How many times llama.cpp reused a cached compute graph instead of
134 /// rebuilding it.
135 ///
136 /// Graph reuse is the fast path. A low count against a high
137 /// [`Self::n_eval`] means something is invalidating the graph every step —
138 /// a changing batch shape, or toggling a context flag mid-generation.
139 #[must_use]
140 pub fn n_reused(&self) -> i32 {
141 self.perf_context_data.n_reused
142 }
143
144 /// Graph reallocations where the graph size was unchanged.
145 ///
146 /// These should be rare: the shape did not change, so the buffers should
147 /// have been reusable. A climbing count points at allocator churn rather
148 /// than at your batching.
149 #[must_use]
150 pub fn n_reallocs_same(&self) -> i32 {
151 self.perf_context_data.n_reallocs_same
152 }
153
154 /// Graph reallocations where the graph size changed.
155 ///
156 /// Expected whenever the batch shape moves — a prefill of a different
157 /// length, or switching between prompt and single-token decode.
158 #[must_use]
159 pub fn n_reallocs_diff(&self) -> i32 {
160 self.perf_context_data.n_reallocs_diff
161 }
162
163 /// Set the start time in milliseconds.
164 pub fn set_t_start_ms(&mut self, t_start_ms: f64) {
165 self.perf_context_data.t_start_ms = t_start_ms;
166 }
167
168 // /// Set the end time in milliseconds.
169 // pub fn set_t_end_ms(&mut self, t_end_ms: f64) {
170 // self.perf_context_data.t_end_ms = t_end_ms;
171 // }
172
173 /// Set the load time in milliseconds.
174 pub fn set_t_load_ms(&mut self, t_load_ms: f64) {
175 self.perf_context_data.t_load_ms = t_load_ms;
176 }
177
178 // /// Set the sample time in milliseconds.
179 // pub fn set_t_sample_ms(&mut self, t_sample_ms: f64) {
180 // self.perf_context_data.t_sample_ms = t_sample_ms;
181 // }
182
183 /// Set the prompt evaluation time in milliseconds.
184 pub fn set_t_p_eval_ms(&mut self, t_p_eval_ms: f64) {
185 self.perf_context_data.t_p_eval_ms = t_p_eval_ms;
186 }
187
188 /// Set the evaluation time in milliseconds.
189 pub fn set_t_eval_ms(&mut self, t_eval_ms: f64) {
190 self.perf_context_data.t_eval_ms = t_eval_ms;
191 }
192
193 // /// Set the number of samples.
194 // pub fn set_n_sample(&mut self, n_sample: i32) {
195 // self.perf_context_data.n_sample = n_sample;
196 // }
197
198 /// Set the number of prompt evaluations.
199 pub fn set_n_p_eval(&mut self, n_p_eval: i32) {
200 self.perf_context_data.n_p_eval = n_p_eval;
201 }
202
203 /// Set the number of evaluations.
204 pub fn set_n_eval(&mut self, n_eval: i32) {
205 self.perf_context_data.n_eval = n_eval;
206 }
207}
208
209impl Display for PerfContextData {
210 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
211 writeln!(f, "load time = {:.2} ms", self.t_load_ms())?;
212 // writeln!(
213 // f,
214 // "sample time = {:.2} ms / {} runs ({:.2} ms per token, {:.2} tokens per second)",
215 // self.t_sample_ms(),
216 // self.n_sample(),
217 // self.t_sample_ms() / f64::from(self.n_sample()),
218 // 1e3 / self.t_sample_ms() * f64::from(self.n_sample())
219 // )?;
220 writeln!(
221 f,
222 "prompt eval time = {:.2} ms / {} tokens ({:.2} ms per token, {:.2} tokens per second)",
223 self.t_p_eval_ms(),
224 self.n_p_eval(),
225 self.t_p_eval_ms() / f64::from(self.n_p_eval()),
226 1e3 / self.t_p_eval_ms() * f64::from(self.n_p_eval())
227 )?;
228 writeln!(
229 f,
230 "eval time = {:.2} ms / {} runs ({:.2} ms per token, {:.2} tokens per second)",
231 self.t_eval_ms(),
232 self.n_eval(),
233 self.t_eval_ms() / f64::from(self.n_eval()),
234 1e3 / self.t_eval_ms() * f64::from(self.n_eval())
235 )?;
236 write!(
237 f,
238 "total time = {:.2} ms",
239 self.t_end_ms() - self.t_start_ms()
240 )
241 }
242}