dsh-eval-guard 0.6.1 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,502 @@
1
+ #!/usr/bin/env python3
2
+ """Evaluate Neural-FP32: numeric error + speed per operation.
3
+
4
+ Reference numbers are computed with PyTorch on the same data/parameters.
5
+ No expected tensors are stored in the workspace. Candidate timing only wraps
6
+ the CLI call; PyTorch reference computation is outside the timed region.
7
+ """
8
+ from __future__ import annotations
9
+
10
+ import math
11
+ import os
12
+ import shutil
13
+ import subprocess
14
+ import sys
15
+ import tempfile
16
+ import time
17
+ from pathlib import Path
18
+
19
+ import numpy as np
20
+ import torch
21
+ import torch.nn.functional as F
22
+
23
+ from common import EVAL_ROOT, clean_copy, emit, fail, parse_workspace
24
+
25
+ torch.set_num_threads(1)
26
+ torch.set_float32_matmul_precision('highest')
27
+ torch.use_deterministic_algorithms(True)
28
+ torch.backends.mkldnn.deterministic = True
29
+ torch.manual_seed(1234)
30
+
31
+ SEEDS = (11, 22, 33)
32
+
33
+
34
+ def write_tensor(path: Path, t: torch.Tensor) -> None:
35
+ arr = t.detach().cpu().contiguous().numpy().astype('<f4', copy=False)
36
+ path.write_bytes(arr.tobytes())
37
+
38
+
39
+ def read_tensor(path: Path, shape: tuple[int, ...]) -> torch.Tensor:
40
+ arr = np.frombuffer(path.read_bytes(), dtype='<f4').copy()
41
+ return torch.from_numpy(arr).reshape(shape)
42
+
43
+
44
+ def mre(cand: torch.Tensor, ref: torch.Tensor) -> float:
45
+ if cand.shape != ref.shape:
46
+ return float('inf')
47
+ c = cand.detach().cpu().numpy().astype(np.float64)
48
+ r = ref.detach().cpu().numpy().astype(np.float64)
49
+ if not np.isfinite(c).all():
50
+ return float('inf')
51
+ den = max(float(np.abs(r).sum()), 1e-30)
52
+ return float(np.abs(c - r).sum() / den)
53
+
54
+
55
+ def build_candidate(workspace: Path, root: Path) -> Path:
56
+ candidate = root / 'candidate'
57
+ clean_copy(workspace, candidate)
58
+ source = candidate
59
+ if not (source / 'CMakeLists.txt').is_file() and (candidate / 'workspace' / 'CMakeLists.txt').is_file():
60
+ source = candidate / 'workspace'
61
+ if not (source / 'CMakeLists.txt').is_file():
62
+ raise RuntimeError('CMakeLists.txt missing at workspace root or workspace/')
63
+ build = source / 'build'
64
+ if build.exists():
65
+ shutil.rmtree(build, ignore_errors=True)
66
+ build.mkdir(parents=True)
67
+ cfg = subprocess.run(['cmake', '-S', '.', '-B', 'build', '-DCMAKE_BUILD_TYPE=Release'],
68
+ cwd=str(source), capture_output=True, text=True, encoding='utf-8', errors='replace', timeout=180)
69
+ if cfg.returncode != 0:
70
+ raise RuntimeError('cmake configure failed\n' + (cfg.stdout + cfg.stderr)[-4000:])
71
+ made = subprocess.run(['cmake', '--build', 'build', '-j4'], cwd=str(source), capture_output=True,
72
+ text=True, encoding='utf-8', errors='replace', timeout=1800)
73
+ if made.returncode != 0:
74
+ raise RuntimeError('build failed\n' + (made.stdout + made.stderr)[-4000:])
75
+ binary = build / 'nn_task'
76
+ if not binary.is_file():
77
+ raise RuntimeError('build/nn_task missing')
78
+ return binary
79
+
80
+
81
+ class Case:
82
+ def __init__(self, inputs: dict[str, torch.Tensor], outputs: dict[str, torch.Tensor],
83
+ args, flops: float):
84
+ self.inputs = inputs
85
+ self.outputs = outputs
86
+ self.args = args
87
+ self.flops = flops
88
+
89
+
90
+ def run_operation(name: str, threshold: float, cases: list[Case], binary: Path, root: Path) -> dict:
91
+ op_dir = root / 'ops' / name
92
+ op_dir.mkdir(parents=True, exist_ok=True)
93
+ executable = True
94
+ total_diff = 0.0
95
+ total_ref = 0.0
96
+ total_sec = 0.0
97
+ total_flops = 0.0
98
+ first_error = ''
99
+ compared = False
100
+ for idx, case in enumerate(cases):
101
+ case_dir = op_dir / str(idx)
102
+ case_dir.mkdir(parents=True, exist_ok=True)
103
+ paths: dict[str, str] = {}
104
+ for key, tensor in case.inputs.items():
105
+ p = case_dir / (key + '.bin')
106
+ write_tensor(p, tensor)
107
+ paths[key] = str(p)
108
+ for key in case.outputs:
109
+ paths[key] = str(case_dir / (key + '.bin'))
110
+ cmd = [str(binary)] + [str(x) for x in case.args(paths)]
111
+ started = time.perf_counter()
112
+ try:
113
+ proc = subprocess.run(cmd, cwd=str(case_dir), capture_output=True, text=True,
114
+ encoding='utf-8', errors='replace', timeout=300)
115
+ except subprocess.TimeoutExpired:
116
+ executable = False
117
+ first_error = first_error or 'timeout'
118
+ continue
119
+ elapsed = time.perf_counter() - started
120
+ if proc.returncode != 0:
121
+ executable = False
122
+ first_error = first_error or (proc.stderr or proc.stdout or ('exit ' + str(proc.returncode)))[-500:]
123
+ continue
124
+ total_sec += elapsed
125
+ total_flops += case.flops
126
+ for key, ref in case.outputs.items():
127
+ p = case_dir / (key + '.bin')
128
+ if not p.is_file():
129
+ executable = False
130
+ first_error = first_error or ('missing output ' + key)
131
+ continue
132
+ try:
133
+ cand = read_tensor(p, tuple(ref.shape))
134
+ except Exception as exc:
135
+ executable = False
136
+ first_error = first_error or ('failed to read ' + key + ': ' + str(exc))
137
+ continue
138
+ # Numeric comparison is done even if the candidate is otherwise
139
+ # valid; a failed numeric comparison does not suppress speed.
140
+ total_ref += max(float(ref.detach().cpu().numpy().astype(np.float64).__abs__().sum()), 1e-30)
141
+ total_diff += float(np.abs(cand.detach().cpu().numpy().astype(np.float64) -
142
+ ref.detach().cpu().numpy().astype(np.float64)).sum())
143
+ compared = True
144
+ value = (total_diff / total_ref) if compared and total_ref > 0 else None
145
+ passed = executable and value is not None and value <= threshold
146
+ speed = (total_flops / total_sec / 1e9) if total_sec > 0 else None
147
+ return {
148
+ 'name': name,
149
+ 'executable': executable,
150
+ 'passed': passed,
151
+ 'weighted_mean_relative_error': value,
152
+ 'threshold': threshold,
153
+ 'speed_gflops': speed,
154
+ 'error': first_error if not executable else '',
155
+ }
156
+
157
+
158
+ def t(shape, seed, scale=1.0):
159
+ g = torch.Generator().manual_seed(seed)
160
+ return torch.randn(shape, generator=g, dtype=torch.float32) * scale
161
+
162
+
163
+ # ---------------------------------------------------------------------------
164
+ # Case builders. Every operation uses three independent data/parameter sets.
165
+ # ---------------------------------------------------------------------------
166
+
167
+ def cases_elementwise(op: str) -> list[Case]:
168
+ out = []
169
+ for s in SEEDS:
170
+ a = t((64, 256), s)
171
+ b = t((64, 256), s + 100)
172
+ if op == 'div':
173
+ b = torch.where(b.abs() < 0.2, b + 0.5, b)
174
+ ref = {'add': a + b, 'sub': a - b, 'mul': a * b, 'div': a / b}[op]
175
+ out.append(Case({'a': a, 'b': b}, {'out': ref},
176
+ lambda p: ['elementwise', '--op', op, '--shape', '64,256',
177
+ '--a', p['a'], '--b', p['b'], '--out', p['out']],
178
+ a.numel()))
179
+ return out
180
+
181
+
182
+ def cases_matmul() -> list[Case]:
183
+ out = []
184
+ for s in SEEDS:
185
+ a = t((256, 256), s)
186
+ b = t((256, 256), s + 1)
187
+ out.append(Case({'a': a, 'b': b}, {'out': a @ b},
188
+ lambda p: ['matmul', '--a-shape', '256,256', '--b-shape', '256,256',
189
+ '--a', p['a'], '--b', p['b'], '--out', p['out']],
190
+ 2 * 256 * 256 * 256))
191
+ return out
192
+
193
+
194
+ def cases_linear() -> list[Case]:
195
+ out = []
196
+ for s in SEEDS:
197
+ x = t((256, 256), s)
198
+ w = t((128, 256), s + 1)
199
+ b = t((128,), s + 2)
200
+ ref = x @ w.T + b
201
+ out.append(Case({'x': x, 'w': w, 'bias': b}, {'out': ref},
202
+ lambda p: ['linear', '--x-shape', '256,256', '--w-shape', '128,256',
203
+ '--b-shape', '128', '--x', p['x'], '--w', p['w'],
204
+ '--bias', p['bias'], '--out', p['out']],
205
+ 2 * 256 * 256 * 128))
206
+ return out
207
+
208
+
209
+ def cases_relu() -> list[Case]:
210
+ out = []
211
+ for s in SEEDS:
212
+ x = t((512, 512), s)
213
+ out.append(Case({'x': x}, {'out': torch.relu(x)},
214
+ lambda p: ['relu', '--shape', '512,512', '--x', p['x'], '--out', p['out']],
215
+ x.numel()))
216
+ return out
217
+
218
+
219
+ def cases_softmax() -> list[Case]:
220
+ out = []
221
+ for s in SEEDS:
222
+ x = t((256, 256), s)
223
+ out.append(Case({'x': x}, {'out': torch.softmax(x, dim=-1)},
224
+ lambda p: ['softmax', '--shape', '256,256', '--x', p['x'], '--out', p['out']],
225
+ 5 * x.numel()))
226
+ return out
227
+
228
+
229
+ def cases_layernorm() -> list[Case]:
230
+ out = []
231
+ for s in SEEDS:
232
+ x = t((256, 256), s)
233
+ gamma = torch.randn(256, generator=torch.Generator().manual_seed(s + 1)) * 0.2 + 1.0
234
+ beta = t((256,), s + 2) * 0.1
235
+ ref = F.layer_norm(x, (256,), gamma, beta, 1e-5)
236
+ out.append(Case({'x': x, 'gamma': gamma, 'beta': beta}, {'out': ref},
237
+ lambda p: ['layernorm', '--shape', '256,256', '--x', p['x'],
238
+ '--gamma', p['gamma'], '--beta', p['beta'],
239
+ '--eps', '1e-5', '--out', p['out']],
240
+ 8 * x.numel()))
241
+ return out
242
+
243
+
244
+ def cases_mse() -> list[Case]:
245
+ out = []
246
+ for s in SEEDS:
247
+ pred = t((512, 512), s)
248
+ target = t((512, 512), s + 1)
249
+ ref = ((pred - target) ** 2).mean().reshape(1)
250
+ out.append(Case({'pred': pred, 'target': target}, {'out': ref},
251
+ lambda p: ['mse', '--pred-shape', '512,512', '--target-shape', '512,512',
252
+ '--pred', p['pred'], '--target', p['target'], '--out', p['out']],
253
+ 2 * pred.numel()))
254
+ return out
255
+
256
+
257
+ def conv_output(h: int, w: int, k: int, stride: int, pad: int, dil: int) -> tuple[int, int]:
258
+ return ((h + 2 * pad - dil * (k - 1) - 1) // stride + 1,
259
+ (w + 2 * pad - dil * (k - 1) - 1) // stride + 1)
260
+
261
+
262
+ def cases_conv2d() -> list[Case]:
263
+ out = []
264
+ for s in SEEDS:
265
+ x = t((8, 3, 32, 32), s)
266
+ w = t((8, 3, 3, 3), s + 1)
267
+ b = t((8,), s + 2)
268
+ ref = F.conv2d(x, w, b, stride=1, padding=1, dilation=1, groups=1)
269
+ ho, wo = conv_output(32, 32, 3, 1, 1, 1)
270
+ out.append(Case({'x': x, 'w': w, 'bias': b}, {'out': ref},
271
+ lambda p: ['conv2d', '--x-shape', '8,3,32,32', '--w-shape', '8,3,3,3',
272
+ '--b-shape', '8', '--x', p['x'], '--w', p['w'], '--bias', p['bias'],
273
+ '--stride', '1,1', '--padding', '1,1', '--dilation', '1,1',
274
+ '--groups', '1', '--out', p['out']],
275
+ 2 * 8 * 8 * ho * wo * 3 * 3 * 3))
276
+ return out
277
+
278
+
279
+ def cases_matmul_backward() -> list[Case]:
280
+ out = []
281
+ for s in SEEDS:
282
+ a = t((256, 256), s).requires_grad_()
283
+ b = t((256, 256), s + 1).requires_grad_()
284
+ g = t((256, 256), s + 2)
285
+ c = a @ b
286
+ da, db = torch.autograd.grad(c, (a, b), g)
287
+ out.append(Case({'a': a.detach(), 'b': b.detach(), 'grad': g},
288
+ {'da': da, 'db': db},
289
+ lambda p: ['matmul-backward', '--a-shape', '256,256', '--b-shape', '256,256',
290
+ '--a', p['a'], '--b', p['b'], '--grad', p['grad'],
291
+ '--da', p['da'], '--db', p['db']],
292
+ 4 * 256 * 256 * 256))
293
+ return out
294
+
295
+
296
+ def cases_linear_backward() -> list[Case]:
297
+ out = []
298
+ for s in SEEDS:
299
+ x = t((256, 256), s).requires_grad_()
300
+ w = t((128, 256), s + 1).requires_grad_()
301
+ bias = t((128,), s + 2).requires_grad_()
302
+ y = x @ w.T + bias
303
+ g = t((256, 128), s + 3)
304
+ dx, dw, db = torch.autograd.grad(y, (x, w, bias), g)
305
+ out.append(Case({'x': x.detach(), 'w': w.detach(), 'bias': bias.detach(), 'grad': g},
306
+ {'dx': dx, 'dw': dw, 'db': db},
307
+ lambda p: ['linear-backward', '--x-shape', '256,256', '--w-shape', '128,256',
308
+ '--b-shape', '128', '--x', p['x'], '--w', p['w'], '--bias', p['bias'],
309
+ '--grad', p['grad'], '--dx', p['dx'], '--dw', p['dw'], '--db', p['db']],
310
+ 4 * 256 * 256 * 128))
311
+ return out
312
+
313
+
314
+ def cases_relu_backward() -> list[Case]:
315
+ out = []
316
+ for s in SEEDS:
317
+ x = t((512, 512), s).requires_grad_()
318
+ g = t((512, 512), s + 1)
319
+ y = torch.relu(x)
320
+ dx = torch.autograd.grad(y, x, g)[0]
321
+ out.append(Case({'x': x.detach(), 'grad': g}, {'dx': dx},
322
+ lambda p: ['relu-backward', '--shape', '512,512', '--x', p['x'],
323
+ '--grad', p['grad'], '--dx', p['dx']],
324
+ x.numel()))
325
+ return out
326
+
327
+
328
+ def cases_layernorm_backward() -> list[Case]:
329
+ out = []
330
+ for s in SEEDS:
331
+ x = t((256, 256), s).requires_grad_()
332
+ gamma = (torch.randn(256, generator=torch.Generator().manual_seed(s + 1)) * 0.2 + 1.0).requires_grad_()
333
+ g = t((256, 256), s + 2)
334
+ y = F.layer_norm(x, (256,), gamma, None, 1e-5)
335
+ dx, dgamma = torch.autograd.grad(y, (x, gamma), g)
336
+ out.append(Case({'x': x.detach(), 'gamma': gamma.detach(), 'grad': g},
337
+ {'dx': dx, 'dgamma': dgamma},
338
+ lambda p: ['layernorm-backward', '--shape', '256,256', '--x', p['x'],
339
+ '--gamma', p['gamma'], '--grad', p['grad'], '--eps', '1e-5',
340
+ '--dx', p['dx'], '--dgamma', p['dgamma']],
341
+ 10 * x.numel()))
342
+ return out
343
+
344
+
345
+ def cases_mse_backward() -> list[Case]:
346
+ out = []
347
+ for s in SEEDS:
348
+ pred = t((512, 512), s).requires_grad_()
349
+ target = t((512, 512), s + 1).requires_grad_()
350
+ loss = ((pred - target) ** 2).mean()
351
+ g = torch.tensor(1.0)
352
+ dp, dt = torch.autograd.grad(loss, (pred, target), g)
353
+ out.append(Case({'pred': pred.detach(), 'target': target.detach(), 'grad': g.reshape(1)},
354
+ {'dp': dp, 'dtarget': dt},
355
+ lambda p: ['mse-backward', '--pred-shape', '512,512', '--target-shape', '512,512',
356
+ '--pred', p['pred'], '--target', p['target'], '--grad', p['grad'],
357
+ '--dp', p['dp'], '--dtarget', p['dtarget']],
358
+ 4 * pred.numel()))
359
+ return out
360
+
361
+
362
+ def cases_conv2d_backward() -> list[Case]:
363
+ out = []
364
+ for s in SEEDS:
365
+ x = t((4, 3, 16, 16), s).requires_grad_()
366
+ w = t((8, 3, 3, 3), s + 1).requires_grad_()
367
+ b = t((8,), s + 2).requires_grad_()
368
+ y = F.conv2d(x, w, b, stride=1, padding=1)
369
+ g = t((4, 8, 16, 16), s + 3)
370
+ dx, dw, db = torch.autograd.grad(y, (x, w, b), g)
371
+ out.append(Case({'x': x.detach(), 'w': w.detach(), 'bias': b.detach(), 'grad': g},
372
+ {'dx': dx, 'dw': dw, 'db': db},
373
+ lambda p: ['conv2d-backward', '--x-shape', '4,3,16,16', '--w-shape', '8,3,3,3',
374
+ '--b-shape', '8', '--x', p['x'], '--w', p['w'], '--bias', p['bias'],
375
+ '--grad', p['grad'], '--stride', '1,1', '--padding', '1,1',
376
+ '--dilation', '1,1', '--groups', '1',
377
+ '--dx', p['dx'], '--dw', p['dw'], '--db', p['db']],
378
+ 4 * 4 * 8 * 16 * 16 * 3 * 3 * 3))
379
+ return out
380
+
381
+
382
+ def adamw_ref(p, g, m, v, lr, b1, b2, eps, wd, step):
383
+ m2 = b1 * m + (1 - b1) * g
384
+ v2 = b2 * v + (1 - b2) * g * g
385
+ mh = m2 / (1 - b1 ** step)
386
+ vh = v2 / (1 - b2 ** step)
387
+ p2 = p * (1 - lr * wd) - lr * mh / (torch.sqrt(vh) + eps)
388
+ return p2, m2, v2
389
+
390
+
391
+ def cases_adamw() -> list[Case]:
392
+ out = []
393
+ for s, step in zip(SEEDS, (1, 3, 7)):
394
+ p = t((256, 256), s)
395
+ g = t((256, 256), s + 1)
396
+ m = torch.zeros_like(p)
397
+ v = torch.zeros_like(p)
398
+ ref = adamw_ref(p, g, m, v, 1e-3, 0.9, 0.999, 1e-8, 0.01, step)
399
+ out.append(Case({'param': p, 'grad': g, 'm': m, 'v': v},
400
+ {'param_out': ref[0], 'm_out': ref[1], 'v_out': ref[2]},
401
+ lambda pth, step=step: ['adamw', '--param', pth['param'], '--grad', pth['grad'],
402
+ '--m', pth['m'], '--v', pth['v'], '--lr', '0.001',
403
+ '--beta1', '0.9', '--beta2', '0.999', '--eps', '1e-8',
404
+ '--weight-decay', '0.01', '--step', str(step),
405
+ '--out-param', pth['param_out'], '--out-m', pth['m_out'], '--out-v', pth['v_out']],
406
+ 10 * p.numel()))
407
+ return out
408
+
409
+
410
+ def cases_sgd_momentum() -> list[Case]:
411
+ out = []
412
+ for s in SEEDS:
413
+ p = t((256, 256), s)
414
+ g = t((256, 256), s + 1)
415
+ buf = t((256, 256), s + 2) * 0.01
416
+ lr, mu = 0.05, 0.9
417
+ b2 = mu * buf + g
418
+ p2 = p - lr * b2
419
+ out.append(Case({'param': p, 'grad': g, 'buffer': buf}, {'param_out': p2, 'buffer_out': b2},
420
+ lambda pth: ['sgd-momentum', '--param', pth['param'], '--grad', pth['grad'],
421
+ '--momentum-buffer', pth['buffer'], '--lr', '0.05',
422
+ '--momentum', '0.9', '--out-param', pth['param_out'],
423
+ '--out-buffer', pth['buffer_out']],
424
+ 6 * p.numel()))
425
+ return out
426
+
427
+
428
+ OPERATIONS = [
429
+ ('elementwise.add', 1e-6, lambda: cases_elementwise('add')),
430
+ ('elementwise.sub', 1e-6, lambda: cases_elementwise('sub')),
431
+ ('elementwise.mul', 1e-6, lambda: cases_elementwise('mul')),
432
+ ('elementwise.div', 1e-6, lambda: cases_elementwise('div')),
433
+ ('matmul', 1e-5, cases_matmul),
434
+ ('linear', 1e-5, cases_linear),
435
+ ('relu', 1e-6, cases_relu),
436
+ ('softmax', 1e-5, cases_softmax),
437
+ ('layernorm', 1e-5, cases_layernorm),
438
+ ('mse', 1e-6, cases_mse),
439
+ ('conv2d', 1e-4, cases_conv2d),
440
+ ('matmul-backward', 1e-4, cases_matmul_backward),
441
+ ('linear-backward', 1e-4, cases_linear_backward),
442
+ ('relu-backward', 1e-4, cases_relu_backward),
443
+ ('layernorm-backward', 1e-4, cases_layernorm_backward),
444
+ ('mse-backward', 1e-4, cases_mse_backward),
445
+ ('conv2d-backward', 1e-4, cases_conv2d_backward),
446
+ ('adamw', 1e-5, cases_adamw),
447
+ ('sgd-momentum', 1e-6, cases_sgd_momentum),
448
+ ]
449
+
450
+
451
+ def main() -> int:
452
+ workspace = parse_workspace()
453
+ if not torch.__version__:
454
+ return fail({'task': 'neural-fp32', 'ok': False, 'error': 'PyTorch unavailable'})
455
+ with tempfile.TemporaryDirectory(prefix='neural-fp32-eval-') as td:
456
+ root = Path(td)
457
+ try:
458
+ binary = build_candidate(workspace, root)
459
+ except Exception as exc:
460
+ return fail({'task': 'neural-fp32', 'ok': False, 'error': str(exc)})
461
+ rows = []
462
+ for name, threshold, builder in OPERATIONS:
463
+ try:
464
+ cases = builder()
465
+ row = run_operation(name, threshold, cases, binary, root)
466
+ except Exception as exc:
467
+ row = {'name': name, 'executable': False, 'passed': False,
468
+ 'weighted_mean_relative_error': None, 'threshold': threshold,
469
+ 'speed_gflops': None, 'error': str(exc)}
470
+ rows.append(row)
471
+
472
+ all_exec = all(row['executable'] for row in rows)
473
+ errs = [row['weighted_mean_relative_error'] for row in rows
474
+ if row['weighted_mean_relative_error'] is not None]
475
+ speeds = [row['speed_gflops'] for row in rows if row['speed_gflops'] and row['speed_gflops'] > 0]
476
+ max_err = max(errs) if errs else None
477
+ geomean = math.exp(sum(math.log(v) for v in speeds) / len(speeds)) if speeds else None
478
+
479
+ print(f"all_operations_executable = {str(all_exec).lower()}")
480
+ print(f"max_weighted_mean_relative_error = {max_err}")
481
+ print(f"speed_geomean = {geomean}")
482
+ for row in rows:
483
+ print(f"{row['name']}: executable={row['executable']} pass={row['passed']} "
484
+ f"mre={row['weighted_mean_relative_error']} speed_gflops={row['speed_gflops']}")
485
+ if row['error']:
486
+ print(f" error={row['error'][:300]}")
487
+
488
+ result = {
489
+ 'task': 'neural-fp32',
490
+ 'ok': True,
491
+ 'all_operations_executable': all_exec,
492
+ 'max_weighted_mean_relative_error': max_err,
493
+ 'speed_geomean': geomean,
494
+ 'operations': rows,
495
+ 'pytorch': torch.__version__,
496
+ }
497
+ emit(result)
498
+ return 0
499
+
500
+
501
+ if __name__ == '__main__':
502
+ raise SystemExit(main())