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
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
// RLX — versatile ML compiler + runtime.
// Copyright (C) 2026 Eugene Hauptmann, Nataliya Kosmyna.
// SPDX-License-Identifier: MIT OR Apache-2.0
//
// Shared helpers for integration tests that touch GPU backends.
// Each test binary declaring `mod common;` uses only the helpers it needs, so
// the rest are dead code in that binary — 25 of them use only
// `skip_unless_available`, and `just lint` runs with `-D warnings`.
use Device;
use Cell;
use ;
static GPU_TEST_MUTEX: = new;
thread_local!
/// Does this device need serializing? On macOS `Vulkan` is Metal via MoltenVK,
/// so it is the same physical device as the rest of the list.
/// Serialize GPU backend use within one integration-test binary.
///
/// Metal, wgpu, Vulkan and CUDA/ROCm adapters are not safe to init/teardown
/// from multiple test threads at once on Apple Silicon (parallel runs may
/// SIGSEGV, or return garbage — `elementwise_backend_parity` produced
/// `worst_rel=1.0` on Vulkan about one run in six, and `vulkan_parity`'s
/// `fma_decomposed` did the same, both clean at `--test-threads=1`).
///
/// **Re-entrant on purpose.** A plain `Mutex` deadlocks the moment a guarded
/// helper is called from a guarded test, which is exactly the shape these files
/// have — a `run_on` chokepoint plus per-test `Session::new` sites. Making
/// re-acquisition on the same thread a no-op is what lets the guard be added at
/// every site mechanically, without auditing each call graph for nesting.
/// Take the GPU serialization lock for the rest of the enclosing scope,
/// whatever devices the caller goes on to use.
///
/// [`GpuTestGuard::acquire`] needs a `Device`, which is awkward at the top of a
/// test that loops over several. There is one global lock, so "serialize this
/// whole test against every other GPU test in the binary" is the useful
/// granularity — and because the guard is re-entrant, inner `acquire` calls
/// inside helpers cost nothing.
///
/// ```ignore
/// #[test]
/// fn my_gpu_parity() {
/// let _gpu = common::serialize_gpu();
/// for dev in devices() { /* ... */ }
/// }
/// ```
/// True when `device` cannot be used on this host and the test should return.
///
/// `Session::new` asserts on an unavailable device, so a parity test that does
/// not check first reports a *numerical failure* for something that is not one.
/// The case that matters in practice is not a missing driver but a present GPU
/// with no memory left: on a box already running a training job, wgpu's
/// `request_device` fails with "Not enough memory left", `is_available` goes
/// false, and an unguarded suite turns 37 tests red at once — indistinguishable
/// at a glance from a real regression in the kernels.
///
/// The opposite risk is that a skip is *silent*: a rig where the device should
/// work reports `ok` for tests that never ran. `RLX_REQUIRE_DEVICE=1` turns
/// every skip here into a failure. The GPU recipes in the `Justfile` set it for
/// you — asking for `just test-gpu` is asserting a GPU is present — so opting
/// back out is `just require_device=0 test-gpu`, and it is read with
/// [`rlx_ir::env::flag`] so that `=0` actually means off.
/// [`skip_unless_available`] with the label derived from the device.
///
/// Most device-gated tests loop over a `&[Device]` or take one as a parameter,
/// so there is no literal to name — `if !is_available(dev) { return }` was the
/// single most common unmigrated shape in this tree. `{dev:?}` is a perfectly
/// good label there ("Metal", "Cuda", …), and demanding a hand-written string
/// is what kept those call sites on the raw form.