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.
- package/assets/neural-pendulum/SPEC.md +468 -0
- package/assets/neural-pendulum/TASK.md +358 -0
- package/assets/neural-pendulum/sim.cpp +126 -0
- package/assets/neural-pendulum/sim_eval +0 -0
- package/assets/neural-pendulum/workspace.zip +0 -0
- package/eval/neural-pendulum.py +15 -0
- package/lib/client/index.js +41 -4
- package/lib/client/index.js.map +1 -1
- package/lib/index.js +454 -60
- package/neural-cifar-eval/README.md +57 -0
- package/neural-cifar-eval/cifar_cache.py +201 -0
- package/neural-cifar-eval/common.py +67 -0
- package/neural-cifar-eval/fetch_cifar.py +31 -0
- package/neural-cifar-eval/hidden/.gitkeep +1 -0
- package/neural-cifar-eval/ops_eval.py +502 -0
- package/neural-cifar-eval/run_eval.py +208 -0
- package/neural-pendulum-eval/run_eval.py +289 -0
- package/neural-pendulum-eval/sim_eval +0 -0
- package/package.json +4 -2
- package/eval/__pycache__/common.cpython-310.pyc +0 -0
- package/eval/__pycache__/neural-cifar.cpython-310.pyc +0 -0
- package/eval/__pycache__/neural-fp32.cpython-310.pyc +0 -0
|
@@ -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())
|