dsh-eval-guard 0.3.4 → 0.4.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/README.md CHANGED
@@ -24,15 +24,23 @@
24
24
  | Commit0 单库 | 480 分钟,默认关闭 |
25
25
  | SWE-Bench Pro 单任务 | 1440 分钟,默认关闭 |
26
26
  | FeatureBench 单 feature | 1440 分钟,默认关闭 |
27
+ | Neural-CPP 纯 C++ 张量后端 | 1440 分钟,默认关闭 |
27
28
 
28
29
  ## 任务与安装
29
30
 
30
- 设置页可安装八个任务包:
31
+ 设置页可安装九个任务包:
31
32
 
32
33
  - 原 5 个比赛任务:坦克大战、仓储调度、圈地、炸弹人、夺旗。
33
34
  - 3 个长时单任务:Commit0 单库实现、SWE-Bench Pro 单任务、FeatureBench 单 feature。
35
+ - 1 个纯 C++ 项目:Neural-CPP 张量运算与反向传播后端。
34
36
 
35
- 原 5 个任务直接放入打包好的 workspace.zip。后 3 个任务在安装时由 host 侧 setup 脚本按固定 commit clone 仓库、创建 .venv 并安装依赖;agent 运行时仍然禁网,使用工作区内的 .venv 跑测试。
37
+ Neural-CPP 工作区只包含 TASK.md、CMakeLists.txt 和 include/nn/nn.hpp;实现在 src/ 中从零完成,纯 C++17,无 Python/OpenCL/第三方库。
38
+
39
+ 评测时在临时目录独立构建,然后从 assets/neural-cpp/hidden/ 注入 C++ 测试源码;测试数据由固定种子在进程内生成,不依赖或复制外部数据文件。
40
+
41
+ 结果返回 coreAccuracy(核心 C++ 测试通过率)、matrixAccuracy、逐类型 metrics(f32/int128/MXFP4 的矩阵与反向准确率、平均绝对误差、fwd/bwd 毫秒、fwd/bwd GFLOP/s)、speedGflops,以及按矩阵吞吐计算的 speedGeomeanGflops 和 matrixSizes(f32/i128/MXFP4 均为 256×256×256)。确定性项目使用 4 组独立输入取平均;MXFP4 RNE 和随机舍入分开统计,随机舍入使用 2 组输入×每组 1024 次采样,逐元素比较参考均值/标准差和候选均值/标准差,按 3σ 和 0.1–10× 标准差规则统计正确率。参考值由测试侧独立标量实现先算完,再调用候选实现,参考计算不计入候选耗时。隐藏测试不在工作区,agent 无法读取。开发要求线程数最多 4,评测阶段使用全部逻辑 CPU。
42
+
43
+ 原 5 个任务直接放入打包好的 workspace.zip。后 3 个任务在安装时由 host 侧 setup 脚本按固定 commit clone 仓库、创建 .venv 并安装依赖;安装完成后会清理 `.git`、`setup.sh`、`remove.patch`、`spec.pdf.bz2` 等安装产物,工作区只保留任务本身素材。Commit0 的 `pytest.ini` 会去掉 coverage 参数,保证默认测试命令在沙箱内可运行。agent 运行时仍然禁网,使用工作区内的 .venv 跑测试;agent 可以自行 `git init` 或写自己的脚本。
36
44
 
37
45
  安装是后台任务:
38
46
 
@@ -66,10 +74,11 @@ Gitee/GitCode 都使用 git fetch --depth 1 origin <任务 commit> 验证。GitL
66
74
 
67
75
  设置页安装/评测共用一组任务和工作区选择;「评测当前工作区」只评测一个结果,不生成排行。
68
76
 
69
- 插件 eval/ 目录内置全部 8 个任务的单结果评测脚本:
77
+ 插件 eval/ 目录内置全部 9 个任务的单结果评测脚本:
70
78
 
71
79
  - Commit0:单元测试通过率。
72
80
  - SWE-Bench Pro / FeatureBench:隐藏 F2P/P2P 测试;全部通过才 resolved,同时返回 passRate。
81
+ - Neural-CPP:独立编译隐藏 C++ 测试,返回 accuracy 与 matmul speedGflops;数据在测试进程内按固定种子生成。
73
82
  - Tank / Warehouse / Land / Bomberman / CTF:运行官方引擎/裁判,只返回当前工作区候选者结果,不生成排名。
74
83
 
75
84
  评测也是后台任务,日志和结果实时显示,持久化在 jobs 目录。
Binary file
Binary file
@@ -0,0 +1,631 @@
1
+ #include <nn/nn.hpp>
2
+
3
+ #include <algorithm>
4
+ #include <chrono>
5
+ #include <cmath>
6
+ #include <cstdint>
7
+ #include <cstring>
8
+ #include <cstdio>
9
+ #include <functional>
10
+ #include <random>
11
+ #include <string>
12
+ #include <thread>
13
+ #include <vector>
14
+
15
+ using nn::DType;
16
+ using nn::Shape;
17
+ using nn::Tensor;
18
+
19
+ static int g_passed = 0;
20
+ static int g_failed = 0;
21
+
22
+ static void report(const std::string& name, bool ok) {
23
+ if (ok) { ++g_passed; std::printf("TEST %s PASS\n", name.c_str()); }
24
+ else { ++g_failed; std::printf("TEST %s FAIL\n", name.c_str()); }
25
+ }
26
+
27
+ static bool close(float a, float b, float tol = 1e-4f) {
28
+ return std::fabs(a - b) <= tol * (1.0f + std::fabs(a) + std::fabs(b));
29
+ }
30
+
31
+ static std::vector<float> f32_data(const Tensor& t) {
32
+ Tensor c = t.contiguous();
33
+ std::vector<float> out(static_cast<size_t>(c.numel()));
34
+ if (out.empty()) return out;
35
+ std::memcpy(out.data(), c.data(), out.size() * sizeof(float));
36
+ return out;
37
+ }
38
+
39
+ static Tensor make_f32(const Shape& shape, const std::vector<float>& values) {
40
+ return Tensor::from_data(values.data(), shape, DType::F32);
41
+ }
42
+
43
+ static bool eq_data(const Tensor& t, const std::vector<float>& expected, float tol = 1e-4f) {
44
+ const std::vector<float> got = f32_data(t);
45
+ if (got.size() != expected.size()) return false;
46
+ for (size_t i = 0; i < got.size(); ++i) if (!close(got[i], expected[i], tol)) return false;
47
+ return true;
48
+ }
49
+
50
+ static bool shape_is(const Tensor& t, const Shape& shape) { return t.shape() == shape; }
51
+
52
+ static void test_dtype_metadata() {
53
+ bool ok = nn::dtype_size(DType::F32) == 4 && nn::dtype_size(DType::F64) == 8 &&
54
+ nn::dtype_size(DType::I8) == 1 && nn::dtype_size(DType::I16) == 2 &&
55
+ nn::dtype_size(DType::I32) == 4 && nn::dtype_size(DType::I64) == 8 &&
56
+ nn::dtype_size(DType::I128) == 16 && nn::dtype_size(DType::U128) == 16 &&
57
+ nn::dtype_size(DType::BF16) == 2 && nn::dtype_size(DType::FP8_E5M2) == 1 &&
58
+ nn::dtype_size(DType::MXFP4) == 1;
59
+ report("dtype_metadata", ok);
60
+ }
61
+
62
+ static void test_casts_float() {
63
+ Tensor a = make_f32({3}, {1.5f, -2.25f, 3.0f});
64
+ Tensor d = a.to(DType::F64);
65
+ Tensor back = d.to(DType::F32);
66
+ report("cast_float", d.dtype() == DType::F64 && eq_data(back, {1.5f, -2.25f, 3.0f}));
67
+ }
68
+
69
+ static void test_casts_int() {
70
+ Tensor a = make_f32({5}, {3.6f, -1.2f, 300.0f, -300.0f, 0.0f});
71
+ Tensor i = a.to(DType::I32);
72
+ Tensor narrow = a.to(DType::I8);
73
+ int32_t iv[5]{};
74
+ int8_t nv[5]{};
75
+ std::memcpy(iv, i.contiguous().data(), sizeof(iv));
76
+ std::memcpy(nv, narrow.contiguous().data(), sizeof(nv));
77
+ bool ok = iv[0] == 4 && iv[1] == -1 && iv[2] == 300 && iv[3] == -300 && iv[4] == 0;
78
+ ok = ok && nv[0] == 4 && nv[1] == -1 && nv[2] == 127 && nv[3] == -128 && nv[4] == 0;
79
+ report("cast_int_saturate", ok);
80
+ }
81
+
82
+ static void test_integer_casts() {
83
+ Tensor a = make_f32({4}, {0.0f, 1.0f, 2.0f, 3.0f});
84
+ bool ok = true;
85
+ for (DType d : {DType::I8, DType::I16, DType::I32, DType::I64, DType::I128,
86
+ DType::U8, DType::U16, DType::U32, DType::U64, DType::U128}) {
87
+ Tensor back = a.to(d).to(DType::F32);
88
+ ok = ok && eq_data(back, {0.0f, 1.0f, 2.0f, 3.0f});
89
+ }
90
+ report("integer_casts", ok);
91
+ }
92
+
93
+ static void test_low_precision_casts() {
94
+ Tensor a = make_f32({4}, {1.5f, -2.0f, 0.5f, 0.0f});
95
+ Tensor bf = a.to(DType::BF16).to(DType::F32);
96
+ Tensor fp8 = a.to(DType::FP8_E5M2).to(DType::F32);
97
+ Tensor mx = make_f32({32}, std::vector<float>(32, 1.0f)).to(DType::MXFP4).to(DType::F32);
98
+ bool ok = eq_data(bf, {1.5f, -2.0f, 0.5f, 0.0f}, 0.01f);
99
+ ok = ok && eq_data(fp8, {1.5f, -2.0f, 0.5f, 0.0f}, 0.01f);
100
+ ok = ok && eq_data(mx, std::vector<float>(32, 1.0f), 0.01f);
101
+ report("low_precision_casts", ok);
102
+ }
103
+
104
+ static void test_views() {
105
+ std::vector<float> v(24);
106
+ for (int i = 0; i < 24; ++i) v[i] = static_cast<float>(i);
107
+ Tensor a = make_f32({2, 3, 4}, v);
108
+ Tensor p = a.permute({2, 0, 1});
109
+ bool ok = shape_is(p, {4, 2, 3});
110
+ const std::vector<float> pv = f32_data(p);
111
+ ok = ok && pv.size() == 24 && close(pv[0], 0.0f) && close(pv[1], 4.0f) && close(pv[2], 8.0f);
112
+ Tensor c = a.reshape({6, 4});
113
+ ok = ok && shape_is(c, {6, 4}) && eq_data(c, v);
114
+ report("views_permute_reshape", ok);
115
+ }
116
+
117
+ static void test_slices() {
118
+ std::vector<float> v(10);
119
+ for (int i = 0; i < 10; ++i) v[i] = static_cast<float>(i);
120
+ Tensor a = make_f32({10}, v);
121
+ Tensor s = a.slice(0, 1, 8, 2);
122
+ Tensor r = a.slice(0, 8, 1, -2);
123
+ bool ok = eq_data(s, {1, 3, 5, 7}) && eq_data(r, {8, 6, 4, 2});
124
+ report("slices_negative_step", ok);
125
+ }
126
+
127
+ static void test_elementwise_broadcast() {
128
+ Tensor a = make_f32({2, 3}, {1, 2, 3, 4, 5, 6});
129
+ Tensor b = make_f32({3}, {10, 20, 30});
130
+ Tensor c = nn::add(a, b);
131
+ Tensor d = nn::mul(c, b);
132
+ bool ok = shape_is(c, {2, 3}) && eq_data(c, {11, 22, 33, 14, 25, 36});
133
+ ok = ok && eq_data(d, {110, 440, 990, 140, 500, 1080});
134
+ report("elementwise_broadcast", ok);
135
+ }
136
+
137
+ static void test_reductions() {
138
+ Tensor a = make_f32({2, 3}, {1, 2, 3, 4, 5, 6});
139
+ Tensor s = nn::sum(a, 1);
140
+ Tensor m = nn::mean(a, -1);
141
+ report("reductions", eq_data(s, {6, 15}) && close(f32_data(m)[0], 3.5f));
142
+ }
143
+
144
+ static void test_matmul_2d() {
145
+ Tensor a = make_f32({2, 3}, {1, 2, 3, 4, 5, 6});
146
+ Tensor b = make_f32({3, 2}, {7, 8, 9, 10, 11, 12});
147
+ Tensor c = nn::matmul(a, b);
148
+ report("matmul_2d", shape_is(c, {2, 2}) && eq_data(c, {58, 64, 139, 154}));
149
+ }
150
+
151
+ static void test_matmul_batch_and_nd() {
152
+ Tensor a = make_f32({2, 2, 3}, {1, 2, 3, 4, 5, 6, 1, 0, 0, 0, 1, 0});
153
+ Tensor b = make_f32({2, 3, 2}, {1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0});
154
+ Tensor c = nn::matmul(a, b);
155
+ bool ok = shape_is(c, {2, 2, 2}) && eq_data(c, {1, 2, 4, 5, 1, 0, 0, 1});
156
+ Tensor a2 = make_f32({2, 3, 4}, std::vector<float>(24, 1.0f));
157
+ Tensor b2 = make_f32({2, 4, 5}, std::vector<float>(40, 1.0f));
158
+ Tensor c2 = nn::matmul_nd(a2, b2, {0}, {0}, {1, 2}, {1, 2});
159
+ ok = ok && shape_is(c2, {2, 3, 5}) && eq_data(c2, std::vector<float>(30, 4.0f));
160
+ report("matmul_batch_nd", ok);
161
+ }
162
+
163
+ static void test_autograd_add_mul() {
164
+ Tensor a = make_f32({2}, {2.0f, 3.0f}); a.set_requires_grad(true);
165
+ Tensor b = make_f32({2}, {5.0f, 7.0f}); b.set_requires_grad(true);
166
+ Tensor c = nn::add(nn::mul(a, b), a);
167
+ nn::sum(c).backward();
168
+ bool ok = eq_data(a.grad(), {6.0f, 8.0f}) && eq_data(b.grad(), {2.0f, 3.0f});
169
+ report("autograd_add_mul", ok);
170
+ }
171
+
172
+ static void test_autograd_matmul() {
173
+ Tensor a = make_f32({2, 3}, {1, 2, 3, 4, 5, 6}); a.set_requires_grad(true);
174
+ Tensor b = make_f32({3, 2}, {7, 8, 9, 10, 11, 12}); b.set_requires_grad(true);
175
+ nn::sum(nn::matmul(a, b)).backward();
176
+ bool ok = eq_data(a.grad(), {15, 19, 23, 15, 19, 23});
177
+ ok = ok && eq_data(b.grad(), {5, 5, 7, 7, 9, 9});
178
+ report("autograd_matmul", ok);
179
+ }
180
+
181
+ static void test_autograd_view_slice() {
182
+ Tensor x = make_f32({2, 3}, {1, 2, 3, 4, 5, 6}); x.set_requires_grad(true);
183
+ Tensor s = x.slice(1, 0, 2);
184
+ nn::sum(s).backward();
185
+ report("autograd_slice", eq_data(x.grad(), {1, 1, 0, 1, 1, 0}));
186
+ }
187
+
188
+ static void test_stochastic_stats() {
189
+ const float lo = 1.0f;
190
+ const float hi = 1.0078125f; // bf16 adjacent around 1.0
191
+ const float mid = (lo + hi) * 0.5f;
192
+ std::vector<float> vals(4096, mid);
193
+ Tensor a = make_f32({4096}, vals);
194
+ Tensor q = a.to(DType::BF16, nn::Rounding::Stochastic, 12345);
195
+ Tensor f = q.to(DType::F32);
196
+ const std::vector<float> out = f32_data(f);
197
+ int upper = 0;
198
+ for (float v : out) { if (v > mid) ++upper; }
199
+ report("stochastic_stats", upper > 1500 && upper < 2600);
200
+ }
201
+
202
+ static void test_threads() {
203
+ nn::set_thread_mode(nn::ThreadMode::Development);
204
+ nn::set_num_threads(4);
205
+ int dev = nn::active_workers();
206
+ bool ok = dev >= 1 && dev <= nn::kDevMaxThreads;
207
+ nn::set_thread_mode(nn::ThreadMode::Evaluation);
208
+ nn::set_num_threads(0);
209
+ ok = ok && nn::active_workers() >= 1;
210
+ report("thread_policy", ok);
211
+ }
212
+
213
+ static void test_all_dtype_alloc() {
214
+ bool ok = true;
215
+ for (DType d : {DType::F32, DType::F64, DType::I8, DType::I16, DType::I32, DType::I64,
216
+ DType::I128, DType::U8, DType::U16, DType::U32, DType::U64, DType::U128,
217
+ DType::BF16, DType::FP8_E5M2, DType::MXFP4}) {
218
+ Tensor t = Tensor::empty(d == DType::MXFP4 ? Shape{32} : Shape{2, 3}, d);
219
+ ok = ok && t.numel() == (d == DType::MXFP4 ? 32 : 6) && t.dtype() == d;
220
+ }
221
+ report("all_dtype_alloc", ok);
222
+ }
223
+
224
+ static void bench_matmul() {
225
+ const int n = 256;
226
+ std::vector<float> av(static_cast<size_t>(n) * n), bv(static_cast<size_t>(n) * n);
227
+ for (size_t i = 0; i < av.size(); ++i) { av[i] = static_cast<float>((i * 17) % 101) * 0.01f; bv[i] = static_cast<float>((i * 29) % 97) * 0.01f; }
228
+ Tensor a = make_f32({n, n}, av);
229
+ Tensor b = make_f32({n, n}, bv);
230
+ nn::set_thread_mode(nn::ThreadMode::Evaluation);
231
+ nn::set_num_threads(0);
232
+ Tensor warm = nn::matmul(a, b);
233
+ (void)warm;
234
+ const int iters = 3;
235
+ auto start = std::chrono::steady_clock::now();
236
+ for (int i = 0; i < iters; ++i) { Tensor c = nn::matmul(a, b); (void)c; }
237
+ auto end = std::chrono::steady_clock::now();
238
+ double ms = std::chrono::duration<double, std::milli>(end - start).count() / iters;
239
+ double gflops = (2.0 * n * n * n) / (ms * 1e6);
240
+ std::printf("PERF_MS %.6f\nPERF_GFLOPS %.6f\n", ms, gflops);
241
+ }
242
+
243
+
244
+ // ---------------------------------------------------------------------------
245
+ // Metric section. Reference values are computed by local scalar reference code
246
+ // BEFORE calling the candidate implementation. Candidate timing only wraps
247
+ // candidate calls; reference loops are never inside the timed region.
248
+ // ---------------------------------------------------------------------------
249
+
250
+ static std::vector<float> ref_matmul_f32(const std::vector<float>& a, const std::vector<float>& b,
251
+ int M, int K, int N) {
252
+ std::vector<float> c(static_cast<size_t>(M) * N, 0.0f);
253
+ for (int i = 0; i < M; ++i)
254
+ for (int k = 0; k < K; ++k)
255
+ for (int j = 0; j < N; ++j)
256
+ c[static_cast<size_t>(i) * N + j] += a[static_cast<size_t>(i) * K + k] * b[static_cast<size_t>(k) * N + j];
257
+ return c;
258
+ }
259
+
260
+ static const float kMxTable[8] = {0.0f, 0.5f, 1.0f, 1.5f, 2.0f, 3.0f, 4.0f, 6.0f};
261
+
262
+ static float mx_quant_one(float x, float scale, std::mt19937_64* rng) {
263
+ const float q = x / scale;
264
+ const float aq = std::fabs(q);
265
+ int code = 0;
266
+ if (aq >= kMxTable[7]) {
267
+ code = 7;
268
+ } else {
269
+ int lo = 0, hi = 1;
270
+ for (int c = 0; c < 7; ++c) if (kMxTable[c] <= aq && aq <= kMxTable[c + 1]) { lo = c; hi = c + 1; break; }
271
+ const float t = (aq - kMxTable[lo]) / (kMxTable[hi] - kMxTable[lo]);
272
+ if (rng != nullptr && t > 0.0f && t < 1.0f) {
273
+ std::uniform_real_distribution<double> d(0.0, 1.0);
274
+ code = d(*rng) < t ? hi : lo;
275
+ } else {
276
+ code = t < 0.5f ? lo : hi;
277
+ }
278
+ }
279
+ const float v = kMxTable[code] * scale;
280
+ return q < 0 ? -v : v;
281
+ }
282
+
283
+ static std::vector<float> mx_quantize(const std::vector<float>& x, std::mt19937_64* rng) {
284
+ std::vector<float> out(x.size());
285
+ for (size_t base = 0; base < x.size(); base += 32) {
286
+ float mx = 0.0f;
287
+ for (size_t j = base; j < std::min(x.size(), base + 32); ++j) mx = std::max(mx, std::fabs(x[j]));
288
+ int e = 127;
289
+ if (mx > 0) e = static_cast<int>(std::floor(std::log2(mx / 4.0f))) + 127;
290
+ e = std::max(0, std::min(254, e));
291
+ const float scale = std::pow(2.0f, e - 127);
292
+ for (size_t j = base; j < std::min(x.size(), base + 32); ++j) out[j] = mx_quant_one(x[j], scale, rng);
293
+ }
294
+ return out;
295
+ }
296
+
297
+ static std::vector<float> ref_matmul_mx(const std::vector<float>& a, const std::vector<float>& b,
298
+ int M, int K, int N, std::mt19937_64* rng) {
299
+ std::vector<float> aq = mx_quantize(a, rng), bq = mx_quantize(b, rng);
300
+ std::vector<float> c = ref_matmul_f32(aq, bq, M, K, N);
301
+ return mx_quantize(c, rng);
302
+ }
303
+
304
+ static std::vector<__int128> ref_matmul_i128(const std::vector<__int128>& a,
305
+ const std::vector<__int128>& b, int M, int K, int N) {
306
+ std::vector<__int128> c(static_cast<size_t>(M) * N, 0);
307
+ for (int i = 0; i < M; ++i)
308
+ for (int k = 0; k < K; ++k)
309
+ for (int j = 0; j < N; ++j)
310
+ c[static_cast<size_t>(i) * N + j] += a[static_cast<size_t>(i) * K + k] * b[static_cast<size_t>(k) * N + j];
311
+ return c;
312
+ }
313
+
314
+ static double mean_abs_error_f32(const std::vector<float>& got, const std::vector<float>& ref) {
315
+ double s = 0.0;
316
+ for (size_t i = 0; i < got.size(); ++i) s += std::fabs(static_cast<double>(got[i]) - ref[i]);
317
+ return got.empty() ? 0.0 : s / got.size();
318
+ }
319
+
320
+ static double mean_abs_error_i128(const std::vector<__int128>& got, const std::vector<__int128>& ref) {
321
+ double s = 0.0;
322
+ for (size_t i = 0; i < got.size(); ++i) {
323
+ __int128 d = got[i] - ref[i];
324
+ if (d < 0) d = -d;
325
+ s += static_cast<double>(d);
326
+ }
327
+ return got.empty() ? 0.0 : s / got.size();
328
+ }
329
+
330
+ static std::vector<__int128> read_i128(const Tensor& t) {
331
+ Tensor c = t.contiguous();
332
+ std::vector<__int128> out(static_cast<size_t>(c.numel()), 0);
333
+ if (c.dtype() != DType::I128) return out;
334
+ std::memcpy(out.data(), c.data(), out.size() * sizeof(__int128));
335
+ return out;
336
+ }
337
+
338
+ static void emit_metric(const char* dtype, int stochastic, int M, int K, int N,
339
+ double fwd_acc, double fwd_err, double fwd_ms,
340
+ double bwd_acc, double bwd_err, double bwd_ms) {
341
+ const double ops = 2.0 * M * K * N;
342
+ const double fwd_gflops = fwd_ms > 0 ? ops / (fwd_ms * 1e6) : -1.0;
343
+ const double bwd_gflops = bwd_ms > 0 ? ops / (bwd_ms * 1e6) : -1.0;
344
+ std::printf("METRIC dtype=%s stochastic=%d m=%d k=%d n=%d fwd_accuracy=%.6f fwd_mean_err=%.9g fwd_ms=%.6f fwd_gflops=%.6f bwd_accuracy=%.6f bwd_mean_err=%.9g bwd_ms=%.6f bwd_gflops=%.6f\n",
345
+ dtype, stochastic, M, K, N, fwd_acc, fwd_err, fwd_ms, fwd_gflops,
346
+ bwd_acc, bwd_err, bwd_ms, bwd_gflops);
347
+ }
348
+
349
+ static double time_once_ms(const std::function<void()>& fn, int iters = 3) {
350
+ // warmup
351
+ fn();
352
+ auto start = std::chrono::steady_clock::now();
353
+ for (int i = 0; i < iters; ++i) fn();
354
+ auto end = std::chrono::steady_clock::now();
355
+ return std::chrono::duration<double, std::milli>(end - start).count() / iters;
356
+ }
357
+
358
+ static void fill_rand(std::vector<float>& v, uint64_t seed, float lo, float hi) {
359
+ std::mt19937_64 rng(seed);
360
+ std::uniform_real_distribution<float> d(lo, hi);
361
+ for (float& x : v) x = d(rng);
362
+ }
363
+
364
+ struct StatScore {
365
+ double accuracy = 0.0;
366
+ double mean_err = 0.0;
367
+ double std_ratio = 0.0;
368
+ double ref_mean_avg = 0.0;
369
+ double ref_std_avg = 0.0;
370
+ double cand_mean_avg = 0.0;
371
+ double cand_std_avg = 0.0;
372
+ };
373
+
374
+ static void add_stats(std::vector<double>& sum, std::vector<double>& sq, const std::vector<float>& v) {
375
+ for (size_t i = 0; i < v.size(); ++i) {
376
+ sum[i] += v[i];
377
+ sq[i] += static_cast<double>(v[i]) * v[i];
378
+ }
379
+ }
380
+
381
+ static StatScore score_stats(const std::vector<double>& ref_sum, const std::vector<double>& ref_sq,
382
+ const std::vector<double>& cand_sum, const std::vector<double>& cand_sq,
383
+ int samples) {
384
+ StatScore out;
385
+ const double n = std::max(1, samples);
386
+ int ok = 0;
387
+ for (size_t i = 0; i < ref_sum.size(); ++i) {
388
+ const double rm = ref_sum[i] / n;
389
+ const double rv = std::max(0.0, ref_sq[i] / n - rm * rm);
390
+ const double rs = std::sqrt(rv);
391
+ const double cm = cand_sum[i] / n;
392
+ const double cv = std::max(0.0, cand_sq[i] / n - cm * cm);
393
+ const double cs = std::sqrt(cv);
394
+ bool good = false;
395
+ if (rs <= 1e-12) good = std::fabs(cm - rm) <= 1e-6 && cs <= 1e-6;
396
+ else good = std::fabs(cm - rm) <= 3.0 * rs && cs >= 0.1 * rs && cs <= 10.0 * rs;
397
+ if (good) ++ok;
398
+ out.mean_err += std::fabs(cm - rm);
399
+ out.std_ratio += (rs > 1e-12) ? cs / rs : 1.0;
400
+ out.ref_mean_avg += rm;
401
+ out.ref_std_avg += rs;
402
+ out.cand_mean_avg += cm;
403
+ out.cand_std_avg += cs;
404
+ }
405
+ const double count = static_cast<double>(ref_sum.size());
406
+ out.accuracy = 100.0 * ok / count;
407
+ out.mean_err /= count;
408
+ out.std_ratio /= count;
409
+ out.ref_mean_avg /= count;
410
+ out.ref_std_avg /= count;
411
+ out.cand_mean_avg /= count;
412
+ out.cand_std_avg /= count;
413
+ return out;
414
+ }
415
+
416
+ static void metric_f32() {
417
+ const int M = 256, K = 256, N = 256;
418
+ const int DATA = 4;
419
+ double acc_f = 0.0, err_f = 0.0, ms_f = 0.0, acc_b = 0.0, err_b = 0.0, ms_b = 0.0;
420
+ for (int d = 0; d < DATA; ++d) {
421
+ std::vector<float> a(static_cast<size_t>(M) * K), b(static_cast<size_t>(K) * N);
422
+ fill_rand(a, 1000 + d, -1.0f, 1.0f);
423
+ fill_rand(b, 2000 + d, -1.0f, 1.0f);
424
+ const std::vector<float> ref_c = ref_matmul_f32(a, b, M, K, N);
425
+ std::vector<float> ref_da(static_cast<size_t>(M) * K), ref_db(static_cast<size_t>(K) * N);
426
+ for (int i = 0; i < M; ++i) for (int k = 0; k < K; ++k) { float s = 0; for (int j = 0; j < N; ++j) s += b[k * N + j]; ref_da[i * K + k] = s; }
427
+ for (int k = 0; k < K; ++k) for (int j = 0; j < N; ++j) { float s = 0; for (int i = 0; i < M; ++i) s += a[i * K + k]; ref_db[k * N + j] = s; }
428
+
429
+ Tensor at = make_f32({M, K}, a), bt = make_f32({K, N}, b);
430
+ const std::vector<float> got = f32_data(nn::matmul(at, bt));
431
+ double err = mean_abs_error_f32(got, ref_c);
432
+ int ok = 0; for (size_t i = 0; i < got.size(); ++i) if (std::fabs(got[i] - ref_c[i]) <= 1e-3f + 1e-4f * std::fabs(ref_c[i])) ++ok;
433
+ acc_f += 100.0 * ok / got.size(); err_f += err;
434
+ ms_f += time_once_ms([&] { Tensor c = nn::matmul(at, bt); (void)c; });
435
+
436
+ Tensor ag = make_f32({M, K}, a), bg = make_f32({K, N}, b);
437
+ ag.set_requires_grad(true); bg.set_requires_grad(true);
438
+ ms_b += time_once_ms([&] {
439
+ ag = make_f32({M, K}, a); bg = make_f32({K, N}, b);
440
+ ag.set_requires_grad(true); bg.set_requires_grad(true);
441
+ Tensor c = nn::matmul(ag, bg);
442
+ nn::sum(c).backward();
443
+ });
444
+ const std::vector<float> got_da = f32_data(ag.grad()), got_db = f32_data(bg.grad());
445
+ double err_da = mean_abs_error_f32(got_da, ref_da), err_db = mean_abs_error_f32(got_db, ref_db);
446
+ int ok_da = 0, ok_db = 0;
447
+ for (size_t i = 0; i < got_da.size(); ++i) if (std::fabs(got_da[i] - ref_da[i]) <= 1e-3f) ++ok_da;
448
+ for (size_t i = 0; i < got_db.size(); ++i) if (std::fabs(got_db[i] - ref_db[i]) <= 1e-3f) ++ok_db;
449
+ acc_b += 100.0 * (ok_da + ok_db) / (got_da.size() + got_db.size());
450
+ err_b += (err_da + err_db) / 2.0;
451
+ }
452
+ emit_metric("f32", 0, M, K, N, acc_f / DATA, err_f / DATA, ms_f / DATA, acc_b / DATA, err_b / DATA, ms_b / DATA);
453
+ }
454
+
455
+ static void metric_i128() {
456
+ const int M = 256, K = 256, N = 256;
457
+ const int DATA = 4;
458
+ double acc_f = 0.0, err_f = 0.0, ms_f = 0.0, acc_b = 0.0, err_b = 0.0, ms_b = 0.0;
459
+ for (int d = 0; d < DATA; ++d) {
460
+ std::vector<__int128> a(static_cast<size_t>(M) * K), b(static_cast<size_t>(K) * N);
461
+ for (size_t i = 0; i < a.size(); ++i) a[i] = static_cast<int>((i * 17 + d * 13) % 9) - 4;
462
+ for (size_t i = 0; i < b.size(); ++i) b[i] = static_cast<int>((i * 29 + d * 7) % 9) - 4;
463
+ const std::vector<__int128> ref_c = ref_matmul_i128(a, b, M, K, N);
464
+ std::vector<__int128> ref_da(static_cast<size_t>(M) * K), ref_db(static_cast<size_t>(K) * N);
465
+ for (int i = 0; i < M; ++i) for (int k = 0; k < K; ++k) { __int128 s = 0; for (int j = 0; j < N; ++j) s += b[k * N + j]; ref_da[i * K + k] = s; }
466
+ for (int k = 0; k < K; ++k) for (int j = 0; j < N; ++j) { __int128 s = 0; for (int i = 0; i < M; ++i) s += a[i * K + k]; ref_db[k * N + j] = s; }
467
+
468
+ Tensor at = Tensor::from_data(a.data(), {M, K}, DType::I128);
469
+ Tensor bt = Tensor::from_data(b.data(), {K, N}, DType::I128);
470
+ const std::vector<__int128> got = read_i128(nn::matmul(at, bt));
471
+ double err = mean_abs_error_i128(got, ref_c);
472
+ int ok = 0; for (size_t i = 0; i < got.size(); ++i) if (got[i] == ref_c[i]) ++ok;
473
+ acc_f += 100.0 * ok / got.size(); err_f += err;
474
+ ms_f += time_once_ms([&] { Tensor c = nn::matmul(at, bt); (void)c; });
475
+
476
+ Tensor ag = Tensor::from_data(a.data(), {M, K}, DType::I128);
477
+ Tensor bg = Tensor::from_data(b.data(), {K, N}, DType::I128);
478
+ ag.set_requires_grad(true); bg.set_requires_grad(true);
479
+ ms_b += time_once_ms([&] {
480
+ ag = Tensor::from_data(a.data(), {M, K}, DType::I128);
481
+ bg = Tensor::from_data(b.data(), {K, N}, DType::I128);
482
+ ag.set_requires_grad(true); bg.set_requires_grad(true);
483
+ Tensor c = nn::matmul(ag, bg);
484
+ nn::sum(c).backward();
485
+ });
486
+ const std::vector<__int128> got_da = read_i128(ag.grad()), got_db = read_i128(bg.grad());
487
+ double err_da = mean_abs_error_i128(got_da, ref_da), err_db = mean_abs_error_i128(got_db, ref_db);
488
+ int ok_da = 0, ok_db = 0;
489
+ for (size_t i = 0; i < got_da.size(); ++i) if (got_da[i] == ref_da[i]) ++ok_da;
490
+ for (size_t i = 0; i < got_db.size(); ++i) if (got_db[i] == ref_db[i]) ++ok_db;
491
+ acc_b += 100.0 * (ok_da + ok_db) / (got_da.size() + got_db.size());
492
+ err_b += (err_da + err_db) / 2.0;
493
+ }
494
+ emit_metric("i128", 0, M, K, N, acc_f / DATA, err_f / DATA, ms_f / DATA, acc_b / DATA, err_b / DATA, ms_b / DATA);
495
+ }
496
+
497
+ static void metric_mx() {
498
+ const int M = 256, K = 256, N = 256;
499
+ const int DATA = 4;
500
+ double acc_f = 0.0, err_f = 0.0, ms_f = 0.0, acc_b = 0.0, err_b = 0.0, ms_b = 0.0;
501
+ for (int d = 0; d < DATA; ++d) {
502
+ std::vector<float> a(static_cast<size_t>(M) * K), b(static_cast<size_t>(K) * N);
503
+ fill_rand(a, 3000 + d, -2.0f, 2.0f);
504
+ fill_rand(b, 4000 + d, -2.0f, 2.0f);
505
+ std::vector<float> aq = mx_quantize(a, nullptr), bq = mx_quantize(b, nullptr);
506
+ const std::vector<float> ref_c = mx_quantize(ref_matmul_f32(aq, bq, M, K, N), nullptr);
507
+ std::vector<float> ref_da(static_cast<size_t>(M) * K), ref_db(static_cast<size_t>(K) * N);
508
+ for (int i = 0; i < M; ++i) for (int k = 0; k < K; ++k) { float s = 0; for (int j = 0; j < N; ++j) s += bq[k * N + j]; ref_da[i * K + k] = s; }
509
+ for (int k = 0; k < K; ++k) for (int j = 0; j < N; ++j) { float s = 0; for (int i = 0; i < M; ++i) s += aq[i * K + k]; ref_db[k * N + j] = s; }
510
+ ref_da = mx_quantize(ref_da, nullptr); ref_db = mx_quantize(ref_db, nullptr);
511
+
512
+ Tensor at = make_f32({M, K}, a).to(DType::MXFP4);
513
+ Tensor bt = make_f32({K, N}, b).to(DType::MXFP4);
514
+ const std::vector<float> got = f32_data(nn::matmul(at, bt).to(DType::F32));
515
+ double err = mean_abs_error_f32(got, ref_c);
516
+ int ok = 0; for (size_t i = 0; i < got.size(); ++i) if (std::fabs(got[i] - ref_c[i]) <= 0.75f + 0.1f * std::fabs(ref_c[i])) ++ok;
517
+ acc_f += 100.0 * ok / got.size(); err_f += err;
518
+ ms_f += time_once_ms([&] { Tensor c = nn::matmul(at, bt); (void)c; });
519
+
520
+ Tensor ag = make_f32({M, K}, a).to(DType::MXFP4), bg = make_f32({K, N}, b).to(DType::MXFP4);
521
+ ag.set_requires_grad(true); bg.set_requires_grad(true);
522
+ ms_b += time_once_ms([&] {
523
+ ag = make_f32({M, K}, a).to(DType::MXFP4); bg = make_f32({K, N}, b).to(DType::MXFP4);
524
+ ag.set_requires_grad(true); bg.set_requires_grad(true);
525
+ Tensor c = nn::matmul(ag, bg);
526
+ nn::sum(c).backward();
527
+ });
528
+ const std::vector<float> got_da = f32_data(ag.grad().to(DType::F32)), got_db = f32_data(bg.grad().to(DType::F32));
529
+ double err_da = mean_abs_error_f32(got_da, ref_da), err_db = mean_abs_error_f32(got_db, ref_db);
530
+ int ok_da = 0, ok_db = 0;
531
+ for (size_t i = 0; i < got_da.size(); ++i) if (std::fabs(got_da[i] - ref_da[i]) <= 0.75f + 0.1f * std::fabs(ref_da[i])) ++ok_da;
532
+ for (size_t i = 0; i < got_db.size(); ++i) if (std::fabs(got_db[i] - ref_db[i]) <= 0.75f + 0.1f * std::fabs(ref_db[i])) ++ok_db;
533
+ acc_b += 100.0 * (ok_da + ok_db) / (got_da.size() + got_db.size());
534
+ err_b += (err_da + err_db) / 2.0;
535
+ }
536
+ emit_metric("mxfp4", 0, M, K, N, acc_f / DATA, err_f / DATA, ms_f / DATA, acc_b / DATA, err_b / DATA, ms_b / DATA);
537
+
538
+ const int STOCH_DATA = 2;
539
+ const int samples = 1024;
540
+ double s_acc_f = 0.0, s_err_f = 0.0, s_ms_f = 0.0, s_acc_b = 0.0, s_err_b = 0.0, s_ms_b = 0.0;
541
+ double s_ref_mean = 0.0, s_ref_std = 0.0, s_cand_mean = 0.0, s_cand_std = 0.0;
542
+ for (int d = 0; d < STOCH_DATA; ++d) {
543
+ std::vector<float> a(static_cast<size_t>(M) * K), b(static_cast<size_t>(K) * N);
544
+ fill_rand(a, 5000 + d, -2.0f, 2.0f);
545
+ fill_rand(b, 6000 + d, -2.0f, 2.0f);
546
+ const size_t out_n = static_cast<size_t>(M) * N;
547
+ std::vector<double> ref_sum(out_n, 0.0), ref_sq(out_n, 0.0), cand_sum(out_n, 0.0), cand_sq(out_n, 0.0);
548
+ std::vector<double> ref_da_sum(static_cast<size_t>(M) * K, 0.0), ref_da_sq(ref_da_sum.size(), 0.0);
549
+ std::vector<double> ref_db_sum(static_cast<size_t>(K) * N, 0.0), ref_db_sq(ref_db_sum.size(), 0.0);
550
+ std::vector<double> cand_da_sum(ref_da_sum.size(), 0.0), cand_da_sq(ref_da_sum.size(), 0.0);
551
+ std::vector<double> cand_db_sum(ref_db_sum.size(), 0.0), cand_db_sq(ref_db_sum.size(), 0.0);
552
+ std::mt19937_64 ref_rng(0xabcdefULL + d);
553
+ for (int t = 0; t < samples; ++t) {
554
+ const std::vector<float> raq = mx_quantize(a, &ref_rng), rbq = mx_quantize(b, &ref_rng);
555
+ const std::vector<float> rc = mx_quantize(ref_matmul_f32(raq, rbq, M, K, N), &ref_rng);
556
+ add_stats(ref_sum, ref_sq, rc);
557
+ std::vector<float> rda(static_cast<size_t>(M) * K), rdb(static_cast<size_t>(K) * N);
558
+ for (int i = 0; i < M; ++i) for (int k = 0; k < K; ++k) { float s = 0; for (int j = 0; j < N; ++j) s += rbq[k * N + j]; rda[i * K + k] = s; }
559
+ for (int k = 0; k < K; ++k) for (int j = 0; j < N; ++j) { float s = 0; for (int i = 0; i < M; ++i) s += raq[i * K + k]; rdb[k * N + j] = s; }
560
+ rda = mx_quantize(rda, nullptr); rdb = mx_quantize(rdb, nullptr);
561
+ add_stats(ref_da_sum, ref_da_sq, rda); add_stats(ref_db_sum, ref_db_sq, rdb);
562
+
563
+ const uint64_t seed = 100000 + static_cast<uint64_t>(d) * samples + t;
564
+ Tensor qa = make_f32({M, K}, a).to(DType::MXFP4, nn::Rounding::Stochastic, seed);
565
+ Tensor qb = make_f32({K, N}, b).to(DType::MXFP4, nn::Rounding::Stochastic, seed + 7919);
566
+ Tensor c = nn::matmul(qa, qb);
567
+ add_stats(cand_sum, cand_sq, f32_data(c.to(DType::F32)));
568
+ qa.set_requires_grad(true); qb.set_requires_grad(true);
569
+ Tensor cg = nn::matmul(qa, qb);
570
+ nn::sum(cg).backward();
571
+ add_stats(cand_da_sum, cand_da_sq, f32_data(qa.grad().to(DType::F32)));
572
+ add_stats(cand_db_sum, cand_db_sq, f32_data(qb.grad().to(DType::F32)));
573
+ }
574
+ StatScore f = score_stats(ref_sum, ref_sq, cand_sum, cand_sq, samples);
575
+ StatScore bscore = score_stats(ref_da_sum, ref_da_sq, cand_da_sum, cand_da_sq, samples);
576
+ StatScore bs2 = score_stats(ref_db_sum, ref_db_sq, cand_db_sum, cand_db_sq, samples);
577
+ s_acc_f += f.accuracy; s_err_f += f.mean_err;
578
+ s_acc_b += (bscore.accuracy + bs2.accuracy) / 2.0;
579
+ s_err_b += (bscore.mean_err + bs2.mean_err) / 2.0;
580
+ s_ref_mean += f.ref_mean_avg; s_ref_std += f.ref_std_avg;
581
+ s_cand_mean += f.cand_mean_avg; s_cand_std += f.cand_std_avg;
582
+ s_ms_f += time_once_ms([&] {
583
+ Tensor qa = make_f32({M, K}, a).to(DType::MXFP4, nn::Rounding::Stochastic, 7);
584
+ Tensor qb = make_f32({K, N}, b).to(DType::MXFP4, nn::Rounding::Stochastic, 11);
585
+ Tensor c = nn::matmul(qa, qb); (void)c;
586
+ }, 1);
587
+ s_ms_b += time_once_ms([&] {
588
+ Tensor qa = make_f32({M, K}, a).to(DType::MXFP4, nn::Rounding::Stochastic, 7);
589
+ Tensor qb = make_f32({K, N}, b).to(DType::MXFP4, nn::Rounding::Stochastic, 11);
590
+ qa.set_requires_grad(true); qb.set_requires_grad(true);
591
+ Tensor c = nn::matmul(qa, qb);
592
+ nn::sum(c).backward();
593
+ }, 1);
594
+ std::printf("STAT data=%d samples=%d fwd_acc=%.6f fwd_mean_err=%.9g fwd_std_ratio=%.9g bwd_acc=%.6f bwd_mean_err=%.9g bwd_std_ratio=%.9g ref_mean=%.9g ref_std=%.9g cand_mean=%.9g cand_std=%.9g\n",
595
+ d, samples, f.accuracy, f.mean_err, f.std_ratio, (bscore.accuracy + bs2.accuracy) / 2.0,
596
+ (bscore.mean_err + bs2.mean_err) / 2.0, (bscore.std_ratio + bs2.std_ratio) / 2.0,
597
+ f.ref_mean_avg, f.ref_std_avg, f.cand_mean_avg, f.cand_std_avg);
598
+ }
599
+ emit_metric("mxfp4", 1, M, K, N, s_acc_f / STOCH_DATA, s_err_f / STOCH_DATA, s_ms_f / STOCH_DATA,
600
+ s_acc_b / STOCH_DATA, s_err_b / STOCH_DATA, s_ms_b / STOCH_DATA);
601
+ std::printf("STAT dtype=mxfp4 stochastic=1 data=%d samples=%d ref_mean_avg=%.9g ref_std_avg=%.9g cand_mean_avg=%.9g cand_std_avg=%.9g\n",
602
+ STOCH_DATA, samples, s_ref_mean / STOCH_DATA, s_ref_std / STOCH_DATA,
603
+ s_cand_mean / STOCH_DATA, s_cand_std / STOCH_DATA);
604
+ }
605
+
606
+
607
+ int main() {
608
+ test_dtype_metadata();
609
+ test_casts_float();
610
+ test_casts_int();
611
+ test_integer_casts();
612
+ test_low_precision_casts();
613
+ test_views();
614
+ test_slices();
615
+ test_elementwise_broadcast();
616
+ test_reductions();
617
+ test_matmul_2d();
618
+ test_matmul_batch_and_nd();
619
+ test_autograd_add_mul();
620
+ test_autograd_matmul();
621
+ test_autograd_view_slice();
622
+ test_stochastic_stats();
623
+ test_threads();
624
+ test_all_dtype_alloc();
625
+ bench_matmul();
626
+ try { metric_f32(); } catch (const std::exception& e) { std::printf("METRIC dtype=f32 stochastic=0 error=%s\n", e.what()); }
627
+ try { metric_i128(); } catch (const std::exception& e) { std::printf("METRIC dtype=i128 stochastic=0 error=%s\n", e.what()); }
628
+ try { metric_mx(); } catch (const std::exception& e) { std::printf("METRIC dtype=mxfp4 stochastic=0 error=%s\n", e.what()); }
629
+ std::printf("SUMMARY %d %d\n", g_passed, g_failed);
630
+ return g_failed == 0 ? 0 : 1;
631
+ }
Binary file