1use super::gpudevice::{self, active_device_struct};
4use crate::builtins::common::spec::{
5 BroadcastSemantics, BuiltinFusionSpec, BuiltinGpuSpec, ConstantStrategy, GpuOpKind,
6 ReductionNaN, ResidencyPolicy, ShapeRequirements,
7};
8#[cfg(feature = "doc_export")]
9use crate::register_builtin_doc_text;
10use crate::{register_builtin_fusion_spec, register_builtin_gpu_spec};
11use runmat_builtins::{IntValue, StructValue, Value};
12use runmat_macros::runtime_builtin;
13
14#[cfg(feature = "doc_export")]
15pub const DOC_MD: &str = r#"---
16title: "gpuInfo"
17category: "acceleration/gpu"
18keywords: ["gpuInfo", "gpu", "device info", "accelerate", "summary"]
19summary: "Return a formatted status string that describes the active GPU provider."
20references:
21 - https://www.mathworks.com/help/parallel-computing/gpudevice.html
22gpu_support:
23 elementwise: false
24 reduction: false
25 precisions: []
26 broadcasting: "none"
27 notes: "Pure query builtin that formats provider metadata. When no provider is registered it returns `GPU[no provider]`."
28fusion:
29 elementwise: false
30 reduction: false
31 max_inputs: 1
32 constants: "inline"
33requires_feature: null
34tested:
35 unit: "builtins::acceleration::gpu::gpuinfo::tests::gpu_info_with_provider_formats_summary"
36 fallback: "builtins::acceleration::gpu::gpuinfo::tests::gpu_info_placeholder_matches_expectation"
37 doc: "builtins::acceleration::gpu::gpuinfo::tests::doc_examples_present"
38 wgpu: "builtins::acceleration::gpu::gpuinfo::tests::gpuInfo_wgpu_provider_reports_backend"
39---
40
41# What does the `gpuInfo` function do in MATLAB / RunMat?
42`gpuInfo()` returns a concise, human-readable string that summarises the active GPU acceleration
43provider. It is a convenience wrapper around [`gpuDevice`](./gpuDevice), intended for logging or
44displaying status information in the REPL and notebooks.
45
46## How does the `gpuInfo` function behave in MATLAB / RunMat?
47- Queries the same device metadata as `gpuDevice()` and formats the fields into
48 `GPU[key=value, ...]`.
49- Includes identifiers (`device_id`, `index`), descriptive strings (`name`, `vendor`, `backend`)
50 and capability hints (`precision`, `supports_double`, `memory_bytes` when available).
51- Escapes string values using MATLAB-style single quote doubling so they are display-friendly.
52- When no acceleration provider is registered, returns the placeholder string `GPU[no provider]`
53 instead of throwing an error, making it safe to call unconditionally.
54- Propagates unexpected errors (for example, if a provider fails while reporting metadata) so they
55 can be diagnosed.
56
57## GPU residency in RunMat (Do I need `gpuArray`?)
58`gpuInfo` is a pure metadata query and never changes residency. Arrays stay wherever they already
59live (GPU or CPU). Use `gpuArray`, `gather`, or RunMat Accelerate's auto-offload heuristics to move
60data between devices as needed.
61
62## Examples of using the `gpuInfo` function in MATLAB / RunMat
63
64### Displaying GPU status in the REPL
65```matlab
66disp(gpuInfo());
67```
68Expected output (in-process provider):
69```matlab
70GPU[device_id=0, index=1, name='InProcess', vendor='RunMat', backend='inprocess', precision='double', supports_double=true]
71```
72
73### Emitting a log line before a computation
74```matlab
75fprintf("Running on %s\n", gpuInfo());
76```
77Expected output:
78```matlab
79Running on GPU[device_id=0, index=1, name='InProcess', vendor='RunMat', backend='inprocess', precision='double', supports_double=true]
80```
81
82### Checking for double precision support quickly
83```matlab
84summary = gpuInfo();
85if contains(summary, "supports_double=true")
86 disp("Double precision kernels available.");
87else
88 disp("Falling back to single precision.");
89end
90```
91
92### Handling missing providers gracefully
93```matlab
94% Safe even when acceleration is disabled
95status = gpuInfo();
96if status == "GPU[no provider]"
97 warning("GPU acceleration is currently disabled.");
98end
99```
100
101### Combining `gpuInfo` with `gpuDevice` for structured data
102```matlab
103info = gpuDevice();
104summary = gpuInfo();
105if isfield(info, 'memory_bytes')
106 fprintf("%s (memory: %.2f GB)\n", summary, info.memory_bytes / 1e9);
107else
108 fprintf("%s (memory: unknown)\n", summary);
109end
110```
111Expected output (when the provider reports `memory_bytes`):
112```matlab
113GPU[device_id=0, index=1, name='InProcess', vendor='RunMat', backend='inprocess', precision='double', supports_double=true] (memory: 15.99 GB)
114```
115If the provider omits memory metadata, the fallback branch prints:
116```matlab
117GPU[device_id=0, index=1, name='InProcess', vendor='RunMat', backend='inprocess', precision='double', supports_double=true] (memory: unknown)
118```
119
120## FAQ
121
122### Does `gpuInfo` change GPU state?
123No. It only reads metadata and formats it into a string.
124
125### Will `gpuInfo` throw an error when no provider is registered?
126No. It returns `GPU[no provider]` so caller code can branch without exception handling.
127
128### How is `gpuInfo` different from `gpuDevice`?
129`gpuDevice` returns a struct that you can inspect programmatically. `gpuInfo` formats the same
130information into a single string that is convenient for logging and display.
131
132### Does the output order of fields stay stable?
133Yes. Fields are emitted in the same order as the `gpuDevice` struct: identifiers, descriptive
134strings, optional metadata, precision, and capability flags.
135
136### Are strings escaped in MATLAB style?
137Yes. Single quotes are doubled (e.g., `Ada'GPU` becomes `Ada''GPU`) so the summary can be pasted
138back into MATLAB code without breaking literal syntax.
139
140## See Also
141[gpuDevice](./gpuDevice), [gpuArray](./gpuArray), [gather](./gather)
142"#;
143
144pub const GPU_SPEC: BuiltinGpuSpec = BuiltinGpuSpec {
145 name: "gpuInfo",
146 op_kind: GpuOpKind::Custom("device-summary"),
147 supported_precisions: &[],
148 broadcast: BroadcastSemantics::None,
149 provider_hooks: &[],
150 constant_strategy: ConstantStrategy::InlineLiteral,
151 residency: ResidencyPolicy::GatherImmediately,
152 nan_mode: ReductionNaN::Include,
153 two_pass_threshold: None,
154 workgroup_size: None,
155 accepts_nan_mode: false,
156 notes: "Formats metadata reported by the active provider; no GPU kernels are launched.",
157};
158
159register_builtin_gpu_spec!(GPU_SPEC);
160
161pub const FUSION_SPEC: BuiltinFusionSpec = BuiltinFusionSpec {
162 name: "gpuInfo",
163 shape: ShapeRequirements::Any,
164 constant_strategy: ConstantStrategy::InlineLiteral,
165 elementwise: None,
166 reduction: None,
167 emits_nan: false,
168 notes: "Not eligible for fusion—returns a host-resident string.",
169};
170
171register_builtin_fusion_spec!(FUSION_SPEC);
172
173#[cfg(feature = "doc_export")]
174register_builtin_doc_text!("gpuInfo", DOC_MD);
175
176#[runtime_builtin(
177 name = "gpuInfo",
178 category = "acceleration/gpu",
179 summary = "Return a formatted status string that describes the active GPU provider.",
180 keywords = "gpu,gpuInfo,device,info,accelerate",
181 examples = "disp(gpuInfo())"
182)]
183fn gpu_info_builtin() -> Result<Value, String> {
184 match active_device_struct() {
185 Ok(info) => Ok(Value::String(format_summary(Some(&info)))),
186 Err(err) if err == gpudevice::ERR_NO_PROVIDER => Ok(Value::String(format_summary(None))),
187 Err(err) => Err(err.to_string()),
188 }
189}
190
191fn format_summary(info: Option<&StructValue>) -> String {
192 match info {
193 Some(struct_value) => {
194 let mut parts = Vec::new();
195 for (key, value) in struct_value.fields.iter() {
196 if let Some(fragment) = format_field(key, value) {
197 parts.push(fragment);
198 }
199 }
200 format!("GPU[{}]", parts.join(", "))
201 }
202 None => "GPU[no provider]".to_string(),
203 }
204}
205
206fn format_field(key: &str, value: &Value) -> Option<String> {
207 match value {
208 Value::Int(intv) => Some(format!("{key}={}", int_to_string(intv))),
209 Value::Num(n) => Some(format!("{key}={}", Value::Num(*n))),
210 Value::Bool(flag) => Some(format!("{key}={}", if *flag { "true" } else { "false" })),
211 Value::String(text) => Some(format!("{key}='{}'", escape_single_quotes(text))),
212 _ => None,
213 }
214}
215
216fn int_to_string(value: &IntValue) -> String {
217 match value {
218 IntValue::I8(v) => v.to_string(),
219 IntValue::I16(v) => v.to_string(),
220 IntValue::I32(v) => v.to_string(),
221 IntValue::I64(v) => v.to_string(),
222 IntValue::U8(v) => v.to_string(),
223 IntValue::U16(v) => v.to_string(),
224 IntValue::U32(v) => v.to_string(),
225 IntValue::U64(v) => v.to_string(),
226 }
227}
228
229fn escape_single_quotes(text: &str) -> String {
230 text.replace('\'', "''")
231}
232
233#[cfg(test)]
234mod tests {
235 use super::*;
236 use crate::builtins::common::test_support;
237 #[cfg(feature = "wgpu")]
238 use runmat_accelerate_api::AccelProvider;
239
240 #[test]
241 #[allow(non_snake_case)]
242 fn gpuInfo_with_provider_formats_summary() {
243 test_support::with_test_provider(|_| {
244 let value = gpu_info_builtin().expect("gpuInfo");
245 match value {
246 Value::String(text) => {
247 assert!(text.starts_with("GPU["), "unexpected prefix: {text}");
248 assert!(
249 text.contains("name='InProcess'"),
250 "expected provider name in: {text}"
251 );
252 assert!(
253 text.contains("vendor='RunMat'"),
254 "expected vendor in: {text}"
255 );
256 }
257 other => panic!("expected string result, got {other:?}"),
258 }
259 });
260 }
261
262 #[test]
263 #[allow(non_snake_case)]
264 fn gpuInfo_placeholder_matches_expectation() {
265 let placeholder = format_summary(None);
266 assert_eq!(placeholder, "GPU[no provider]");
267 }
268
269 #[test]
270 #[allow(non_snake_case)]
271 fn gpuInfo_format_summary_handles_core_value_types() {
272 let mut info = StructValue::new();
273 info.insert("device_id", Value::Int(IntValue::U32(0)));
274 info.insert("index", Value::Int(IntValue::U32(1)));
275 info.insert("name", Value::String("Ada'GPU".into()));
276 info.insert("vendor", Value::String("RunMat".into()));
277 info.insert("memory_bytes", Value::Int(IntValue::U64(12)));
278 info.insert("load", Value::Num(0.5));
279 info.insert("precision", Value::String("double".into()));
280 info.insert("supports_double", Value::Bool(true));
281 info.insert("ignored", Value::Struct(StructValue::new()));
282
283 let summary = format_summary(Some(&info));
284 assert!(
285 summary.starts_with("GPU["),
286 "summary should start with GPU[: {summary}"
287 );
288 assert!(
289 summary.contains("device_id=0"),
290 "expected device_id field in: {summary}"
291 );
292 assert!(
293 summary.contains("index=1"),
294 "expected index field in: {summary}"
295 );
296 assert!(
297 summary.contains("name='Ada''GPU'"),
298 "expected escaped name in: {summary}"
299 );
300 assert!(
301 summary.contains("vendor='RunMat'"),
302 "expected vendor field in: {summary}"
303 );
304 assert!(
305 summary.contains("memory_bytes=12"),
306 "expected memory_bytes field in: {summary}"
307 );
308 assert!(
309 summary.contains("load=0.5"),
310 "expected numeric load field in: {summary}"
311 );
312 assert!(
313 summary.contains("precision='double'"),
314 "expected precision field in: {summary}"
315 );
316 assert!(
317 summary.contains("supports_double=true"),
318 "expected supports_double field in: {summary}"
319 );
320 assert!(
321 !summary.contains("ignored"),
322 "unexpected field 'ignored' present in: {summary}"
323 );
324 }
325
326 #[test]
327 #[allow(non_snake_case)]
328 #[cfg(feature = "doc_export")]
329 fn gpuInfo_doc_examples_present() {
330 let blocks = test_support::doc_examples(DOC_MD);
331 assert!(!blocks.is_empty(), "expected at least one MATLAB example");
332 }
333
334 #[test]
335 #[cfg(feature = "wgpu")]
336 #[allow(non_snake_case)]
337 fn gpuInfo_wgpu_provider_reports_backend() {
338 use runmat_accelerate::backend::wgpu::provider::{
339 register_wgpu_provider, WgpuProviderOptions,
340 };
341
342 let options = WgpuProviderOptions {
343 force_fallback_adapter: true,
344 ..Default::default()
345 };
346
347 let provider = match register_wgpu_provider(options) {
348 Ok(p) => p,
349 Err(err) => {
350 eprintln!("Skipping gpuInfo WGPU provider test: {err}");
351 return;
352 }
353 };
354
355 let value = gpu_info_builtin().expect("gpuInfo");
356 match value {
357 Value::String(text) => {
358 assert!(text.starts_with("GPU["), "unexpected prefix: {text}");
359 let info = provider.device_info_struct();
360 let runmat_accelerate_api::ApiDeviceInfo {
361 vendor, backend, ..
362 } = info;
363 let expected_vendor = escape_single_quotes(&vendor);
364 assert!(
365 text.contains(&format!("vendor='{}'", expected_vendor)),
366 "expected vendor '{}' in summary: {}",
367 vendor,
368 text
369 );
370 if let Some(backend) = backend {
371 if !backend.is_empty() {
372 assert!(
373 text.contains(&format!("backend='{}'", backend)),
374 "expected backend '{}' in summary: {}",
375 backend,
376 text
377 );
378 }
379 }
380 }
381 other => panic!("expected string result, got {other:?}"),
382 }
383 }
384}