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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
use std::collections::HashMap;
use std::ops::Deref;
use std::sync::{Arc, Mutex, PoisonError};
use cudarc::driver::{CudaContext, CudaFunction, CudaModule};
use cudarc::nvrtc::Ptx;
use tracing::debug;
use crate::artifact::{
ArtifactHash, ArtifactLoadError, KernelArea, LoadedArtifact, ModuleRequest, load_artifact,
};
use crate::{Error, Gpu, PtxTier};
/// How to open a [`Runtime`]; consumers fill it from their own environment variables
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Options {
/// Lower the PTX tier limit below the highest compiled-in tier the device supports
pub tier: Option<PtxTier>,
/// Load embedded PTX through driver JIT instead of cubins, for diagnosis
pub force_ptx_jit: bool,
}
/// A [`Gpu`] plus the kernel modules of one consumer's areas
///
/// Each area loads at most one module per runtime. A request with a different identity
/// than the cached one is an error, never a replacement, so a development load cannot
/// change what a later production request runs. Dereferences to [`Gpu`]
#[derive(Debug)]
pub struct Runtime<K: KernelArea> {
modules: Mutex<HashMap<K, LoadedKernels<K>>>,
force_ptx_jit: bool,
// declared last so loaded modules drop before the context
gpu: Gpu,
}
impl<K: KernelArea> Runtime<K> {
/// Opens device `ordinal` with `K`'s profile; see [`Gpu::open`]
pub fn open(ordinal: usize, options: Options) -> Result<Self, Error> {
Ok(Self {
modules: Mutex::new(HashMap::new()),
force_ptx_jit: options.force_ptx_jit,
gpu: Gpu::open(ordinal, K::PROFILE, options.tier)?,
})
}
/// The underlying GPU
pub fn gpu(&self) -> &Gpu {
&self.gpu
}
/// Whether this runtime forces PTX JIT
pub fn force_ptx_jit(&self) -> bool {
self.force_ptx_jit
}
/// The best runnable embedded artifact of `area` on this device, resolved without
/// loading: the exact cubin when the area embeds one for this device, else its PTX
///
/// Consumers with their own binding policy build a [`ModuleRequest`] themselves and
/// pass it through [`Self::effective_request`]
pub fn embedded_request(&self, area: K) -> Result<Option<ModuleRequest<K>>, Error> {
area.variants()
.driver_request(area, self.ptx_tier(), self.compute_capability())
.map(|request| self.effective_request(request))
.transpose()
}
/// Applies the forced-JIT diagnostic before a selection fixes a token or cache
/// identity
pub fn effective_request(&self, request: ModuleRequest<K>) -> Result<ModuleRequest<K>, Error> {
request.diagnostic_request(self.force_ptx_jit)
}
/// Loads the best runnable embedded artifact of `area`; see [`Self::embedded_request`]
pub fn load_embedded(&self, area: K) -> Result<LoadedKernels<K>, Error> {
let request = self
.embedded_request(area)?
.ok_or(Error::AreaTierNotCompiledIn {
area: area.name(),
tier: self.ptx_tier(),
device: self.compute_capability(),
feature: K::PROFILE.feature(self.ptx_tier()),
})?;
self.load(request)
}
/// Loads exactly `request`; a cached module with another identity is an error, never
/// a replacement, and a driver refusal never tries the other artifact format. A
/// request above the runtime's tier limit fails with [`Error::TierAboveLimit`]
pub fn load(&self, request: ModuleRequest<K>) -> Result<LoadedKernels<K>, Error> {
let area = request.area();
let tier = request.tier();
if tier > self.ptx_tier() {
return Err(Error::TierAboveLimit {
area: area.name(),
tier,
limit: self.ptx_tier(),
});
}
let requested = request.artifact();
let unavailable = |artifact| Error::ArtifactUnavailable {
module: area.name(),
artifact,
};
let embedded = area
.variants()
.embedded(tier)
.ok_or_else(|| unavailable(requested))?;
let ptx = embedded.text;
let ptx_sha256 = embedded.sha256();
let cubin = embedded.cubin(self.compute_capability());
let mut modules = self.modules.lock().unwrap_or_else(PoisonError::into_inner);
if let Some(loaded) = modules.get(&area) {
request.check_cached(loaded.request())?;
return Ok(loaded.clone());
}
let context = self.context();
let (inner, artifact) = load_artifact(
requested,
cubin,
ptx_sha256,
|bytes| context.load_module(Ptx::from_binary(bytes.to_vec())),
|| context.load_module(Ptx::from_src(ptx)),
)
.map_err(|error| match error {
ArtifactLoadError::Unavailable => unavailable(requested),
ArtifactLoadError::Driver(source) => Error::ArtifactLoad {
module: area.name(),
artifact: requested,
source,
},
})?;
debug!(area = area.name(), %tier, capability = %self.compute_capability(), ?artifact, "Loaded CUDA artifact");
let loaded = LoadedKernels {
request: ModuleRequest::new(area, tier, artifact),
context: context.clone(),
inner,
ptx_sha256,
};
debug!(embedded_ptx_sha256 = %loaded.ptx_sha256(), "CUDA embedded PTX identity");
modules.insert(area, loaded.clone());
Ok(loaded)
}
}
impl<K: KernelArea> Deref for Runtime<K> {
type Target = Gpu;
fn deref(&self) -> &Gpu {
&self.gpu
}
}
/// A kernel module loaded into a CUDA context
#[derive(Debug, Clone)]
pub struct LoadedKernels<K> {
request: ModuleRequest<K>,
// cudarc's function lookup does not bind the module's context, and a cached or
// moved module may be used first on a thread where it is not current
context: Arc<CudaContext>,
inner: Arc<CudaModule>,
ptx_sha256: ArtifactHash,
}
impl<K: KernelArea> LoadedKernels<K> {
/// Wraps a module that the caller loaded itself, such as an artifact proof that
/// loads every embedded variant. `context` must be the one `inner` was loaded into
pub fn from_module(
context: Arc<CudaContext>,
request: ModuleRequest<K>,
inner: Arc<CudaModule>,
ptx_sha256: ArtifactHash,
) -> Self {
Self {
request,
context,
inner,
ptx_sha256,
}
}
/// The complete identity the driver accepted
pub fn request(&self) -> ModuleRequest<K> {
self.request
}
/// The PTX tier of the variant that was loaded
pub fn tier(&self) -> PtxTier {
self.request.tier()
}
/// Identity of the artifact that the driver loaded
pub fn artifact(&self) -> LoadedArtifact {
self.request.artifact()
}
/// Identity of the fallback PTX text embedded alongside the loaded artifact
pub fn ptx_sha256(&self) -> ArtifactHash {
self.ptx_sha256
}
/// Looks up a kernel by its PTX entry name, which is the Rust function name
pub fn function(&self, kernel: &str) -> Result<CudaFunction, Error> {
self.context.bind_to_thread()?;
self.inner
.load_function(kernel)
.map_err(|source| Error::KernelMissing {
module: self.request.area().name(),
kernel: kernel.to_string(),
source,
})
}
}