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