1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
// RLX — versatile ML compiler + runtime.
// Copyright (C) 2026 Eugene Hauptmann, Nataliya Kosmyna.
// SPDX-License-Identifier: MIT OR Apache-2.0
//! **How long the GPU was actually busy for the last run.**
//!
//! ```no_run
//! # let mut exe: rlx_metal::backend::MetalExecutable = todo!();
//! exe.run(&[("x", &[0.0f32][..])]);
//! if let Some(ms) = rlx_metal::gpu_span::last_ms() {
//! println!("device busy {ms:.4} ms");
//! }
//! ```
//!
//! # Why a wall clock is not good enough
//!
//! Every Metal benchmark in this tree times `Instant::now()` around
//! `run()`, which measures **host encode + objc bridging + queue wait + GPU
//! execution**. On a busy machine the first three dominate, and the number stops
//! being about the kernel at all.
//!
//! That is not hypothetical. `reference_perf` on this box at load 50 reported
//! 24–47% spread and withheld its own aggregate; `cost.rs` puts objc bridging
//! alone at 5–20 µs per dispatch, which is a large fraction of a 0.25 ms decode
//! pass. A kernel change and a host-scheduling change look identical through a
//! wall clock.
//!
//! `MTLCommandBuffer` reports `GPUStartTime`/`GPUEndTime` — when the device
//! actually began and finished — so this isolates the part a kernel change can
//! move. It is CAKE's *"CUPTI timing"* in the form Apple provides.
//!
//! # What it does not fix
//!
//! A contended **GPU**. If something else is submitting work, the device span
//! for a given command buffer still stretches. Isolating host cost is not the
//! same as isolating device cost, and a run on a busy GPU is still not a
//! measurement — check `ioreg -c IOAccelerator | grep 'Device Utilization'`.
//!
//! # Scope
//!
//! Records the span of the **last completed `run()`**: earliest `GPUStartTime`
//! to latest `GPUEndTime` across that run's command buffers, so a multi-buffer
//! schedule is covered end to end. Gaps *between* buffers are included, which is
//! deliberate — a schedule that leaves the GPU idle mid-run is slower, and
//! summing per-buffer spans would hide exactly that.
use ;
/// `f64` bits, or `NOT_RECORDED` when the last run did not report one.
static LAST: AtomicU64 = new;
/// Sentinel: no span recorded. A real span is never this bit pattern.
const NOT_RECORDED: u64 = u64MAX;
/// Record a device span in milliseconds. Called by the backend after a run.
/// Device span of the last completed run, in milliseconds.
///
/// `None` when the backend did not record one — a coverage limitation the
/// caller should report rather than paper over with a wall clock.
/// Forget the last span. Use before a run whose span must not be confused with
/// an earlier one.