runmat_runtime/builtins/acceleration/gpu/
gpuinfo.rs1use super::gpudevice::{self, active_device_struct};
4use crate::builtins::acceleration::gpu::type_resolvers::gpuinfo_type;
5use crate::builtins::common::spec::{
6 BroadcastSemantics, BuiltinFusionSpec, BuiltinGpuSpec, ConstantStrategy, GpuOpKind,
7 ReductionNaN, ResidencyPolicy, ShapeRequirements,
8};
9
10use runmat_builtins::{IntValue, StructValue, Value};
11use runmat_macros::runtime_builtin;
12
13#[runmat_macros::register_gpu_spec(builtin_path = "crate::builtins::acceleration::gpu::gpuinfo")]
14pub const GPU_SPEC: BuiltinGpuSpec = BuiltinGpuSpec {
15 name: "gpuInfo",
16 op_kind: GpuOpKind::Custom("device-summary"),
17 supported_precisions: &[],
18 broadcast: BroadcastSemantics::None,
19 provider_hooks: &[],
20 constant_strategy: ConstantStrategy::InlineLiteral,
21 residency: ResidencyPolicy::GatherImmediately,
22 nan_mode: ReductionNaN::Include,
23 two_pass_threshold: None,
24 workgroup_size: None,
25 accepts_nan_mode: false,
26 notes: "Formats metadata reported by the active provider; no GPU kernels are launched.",
27};
28
29#[runmat_macros::register_fusion_spec(builtin_path = "crate::builtins::acceleration::gpu::gpuinfo")]
30pub const FUSION_SPEC: BuiltinFusionSpec = BuiltinFusionSpec {
31 name: "gpuInfo",
32 shape: ShapeRequirements::Any,
33 constant_strategy: ConstantStrategy::InlineLiteral,
34 elementwise: None,
35 reduction: None,
36 emits_nan: false,
37 notes: "Not eligible for fusion—returns a host-resident string.",
38};
39
40#[runtime_builtin(
41 name = "gpuInfo",
42 category = "acceleration/gpu",
43 summary = "Return a formatted status string that describes the active GPU provider.",
44 keywords = "gpu,gpuInfo,device,info,accelerate",
45 examples = "disp(gpuInfo())",
46 type_resolver(gpuinfo_type),
47 builtin_path = "crate::builtins::acceleration::gpu::gpuinfo"
48)]
49async fn gpu_info_builtin() -> crate::BuiltinResult<Value> {
50 match active_device_struct() {
51 Ok(info) => Ok(Value::String(format_summary(Some(&info)))),
52 Err(err) if err.message() == gpudevice::ERR_NO_PROVIDER => {
53 Ok(Value::String(format_summary(None)))
54 }
55 Err(err) => Err(err),
56 }
57}
58
59fn format_summary(info: Option<&StructValue>) -> String {
60 match info {
61 Some(struct_value) => {
62 let mut parts = Vec::new();
63 for (key, value) in struct_value.fields.iter() {
64 if let Some(fragment) = format_field(key, value) {
65 parts.push(fragment);
66 }
67 }
68 format!("GPU[{}]", parts.join(", "))
69 }
70 None => "GPU[no provider]".to_string(),
71 }
72}
73
74fn format_field(key: &str, value: &Value) -> Option<String> {
75 match value {
76 Value::Int(intv) => Some(format!("{key}={}", int_to_string(intv))),
77 Value::Num(n) => Some(format!("{key}={}", Value::Num(*n))),
78 Value::Bool(flag) => Some(format!("{key}={}", if *flag { "true" } else { "false" })),
79 Value::String(text) => Some(format!("{key}='{}'", escape_single_quotes(text))),
80 _ => None,
81 }
82}
83
84fn int_to_string(value: &IntValue) -> String {
85 match value {
86 IntValue::I8(v) => v.to_string(),
87 IntValue::I16(v) => v.to_string(),
88 IntValue::I32(v) => v.to_string(),
89 IntValue::I64(v) => v.to_string(),
90 IntValue::U8(v) => v.to_string(),
91 IntValue::U16(v) => v.to_string(),
92 IntValue::U32(v) => v.to_string(),
93 IntValue::U64(v) => v.to_string(),
94 }
95}
96
97fn escape_single_quotes(text: &str) -> String {
98 text.replace('\'', "''")
99}
100
101#[cfg(test)]
102pub(crate) mod tests {
103 use super::*;
104 use crate::builtins::common::test_support;
105 use futures::executor::block_on;
106 #[cfg(feature = "wgpu")]
107 use runmat_accelerate_api::AccelProvider;
108 use runmat_builtins::{ResolveContext, Type};
109
110 fn call() -> crate::BuiltinResult<Value> {
111 block_on(gpu_info_builtin())
112 }
113
114 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
115 #[test]
116 #[allow(non_snake_case)]
117 fn gpuInfo_with_provider_formats_summary() {
118 test_support::with_test_provider(|_| {
119 let value = call().expect("gpuInfo");
120 match value {
121 Value::String(text) => {
122 assert!(text.starts_with("GPU["), "unexpected prefix: {text}");
123 assert!(
124 text.contains("name='InProcess'"),
125 "expected provider name in: {text}"
126 );
127 assert!(
128 text.contains("vendor='RunMat'"),
129 "expected vendor in: {text}"
130 );
131 }
132 other => panic!("expected string result, got {other:?}"),
133 }
134 });
135 }
136
137 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
138 #[test]
139 #[allow(non_snake_case)]
140 fn gpuInfo_placeholder_matches_expectation() {
141 let placeholder = format_summary(None);
142 assert_eq!(placeholder, "GPU[no provider]");
143 }
144
145 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
146 #[test]
147 #[allow(non_snake_case)]
148 fn gpuInfo_format_summary_handles_core_value_types() {
149 let mut info = StructValue::new();
150 info.insert("device_id", Value::Int(IntValue::U32(0)));
151 info.insert("index", Value::Int(IntValue::U32(1)));
152 info.insert("name", Value::String("Ada'GPU".into()));
153 info.insert("vendor", Value::String("RunMat".into()));
154 info.insert("memory_bytes", Value::Int(IntValue::U64(12)));
155 info.insert("load", Value::Num(0.5));
156 info.insert("precision", Value::String("double".into()));
157 info.insert("supports_double", Value::Bool(true));
158 info.insert("ignored", Value::Struct(StructValue::new()));
159
160 let summary = format_summary(Some(&info));
161 assert!(
162 summary.starts_with("GPU["),
163 "summary should start with GPU[: {summary}"
164 );
165 assert!(
166 summary.contains("device_id=0"),
167 "expected device_id field in: {summary}"
168 );
169 assert!(
170 summary.contains("index=1"),
171 "expected index field in: {summary}"
172 );
173 assert!(
174 summary.contains("name='Ada''GPU'"),
175 "expected escaped name in: {summary}"
176 );
177 assert!(
178 summary.contains("vendor='RunMat'"),
179 "expected vendor field in: {summary}"
180 );
181 assert!(
182 summary.contains("memory_bytes=12"),
183 "expected memory_bytes field in: {summary}"
184 );
185 assert!(
186 summary.contains("load=0.5"),
187 "expected numeric load field in: {summary}"
188 );
189 assert!(
190 summary.contains("precision='double'"),
191 "expected precision field in: {summary}"
192 );
193 assert!(
194 summary.contains("supports_double=true"),
195 "expected supports_double field in: {summary}"
196 );
197 assert!(
198 !summary.contains("ignored"),
199 "unexpected field 'ignored' present in: {summary}"
200 );
201 }
202
203 #[test]
204 fn gpuinfo_type_is_string() {
205 assert_eq!(
206 gpuinfo_type(&[], &ResolveContext::new(Vec::new())),
207 Type::String
208 );
209 }
210
211 #[cfg_attr(target_arch = "wasm32", wasm_bindgen_test::wasm_bindgen_test)]
212 #[test]
213 #[cfg(feature = "wgpu")]
214 #[allow(non_snake_case)]
215 fn gpuInfo_wgpu_provider_reports_backend() {
216 use runmat_accelerate::backend::wgpu::provider::{
217 register_wgpu_provider, WgpuProviderOptions,
218 };
219
220 let options = WgpuProviderOptions {
221 force_fallback_adapter: true,
222 ..Default::default()
223 };
224
225 let provider = match register_wgpu_provider(options) {
226 Ok(p) => p,
227 Err(err) => {
228 tracing::warn!("Skipping gpuInfo WGPU provider test: {err}");
229 return;
230 }
231 };
232
233 let value = call().expect("gpuInfo");
234 match value {
235 Value::String(text) => {
236 assert!(text.starts_with("GPU["), "unexpected prefix: {text}");
237 let info = provider.device_info_struct();
238 let runmat_accelerate_api::ApiDeviceInfo {
239 vendor, backend, ..
240 } = info;
241 let expected_vendor = escape_single_quotes(&vendor);
242 assert!(
243 text.contains(&format!("vendor='{}'", expected_vendor)),
244 "expected vendor '{}' in summary: {}",
245 vendor,
246 text
247 );
248 if let Some(backend) = backend {
249 if !backend.is_empty() {
250 assert!(
251 text.contains(&format!("backend='{}'", backend)),
252 "expected backend '{}' in summary: {}",
253 backend,
254 text
255 );
256 }
257 }
258 }
259 other => panic!("expected string result, got {other:?}"),
260 }
261 }
262}