1
0

test-opt.cpp 39 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003
  1. // TODO refactor
  2. #include "ggml.h"
  3. #include "ggml-alloc.h"
  4. #include "ggml-backend.h"
  5. #include "ggml-opt.h"
  6. #include <cmath>
  7. #include <cinttypes>
  8. #include <cstring>
  9. #include <random>
  10. #include <string>
  11. #include <thread>
  12. #include <vector>
  13. #define TEST_LOG(...) printf(__VA_ARGS__)
  14. static bool almost_equal(const double a, const double b, const double atol) {
  15. return fabs(a - b) < atol;
  16. }
  17. constexpr int64_t ne_datapoint = 2;
  18. constexpr int64_t ne_label = 1;
  19. constexpr int64_t ndata = 6;
  20. struct helper_ctx_data {
  21. std::vector<ggml_opt_dataset_t> datasets_supervised;
  22. std::vector<struct ggml_tensor *> data_batch;
  23. std::vector<struct ggml_tensor *> labels_batch;
  24. ggml_opt_dataset_t dataset_unsupervised;
  25. struct ggml_context * ctx_static;
  26. struct ggml_context * ctx_compute;
  27. struct ggml_opt_params opt_params;
  28. ggml_opt_context_t opt_ctx;
  29. struct ggml_tensor * inputs;
  30. struct ggml_tensor * weights;
  31. struct ggml_tensor * outputs;
  32. ggml_backend_buffer_t buf;
  33. ggml_opt_result_t result;
  34. ggml_opt_result_t result2;
  35. };
  36. // These default values make it easier to check optimization results vs. expected values.
  37. static ggml_opt_optimizer_params helper_get_test_opt_pars(void * userdata) {
  38. ggml_opt_optimizer_params result = ggml_opt_get_default_optimizer_params(userdata);
  39. result.adamw.alpha = 1.0f;
  40. result.adamw.beta1 = 0.0f;
  41. result.adamw.beta2 = 0.0f;
  42. result.adamw.eps = 0.0f;
  43. result.adamw.wd = 0.0f;
  44. result.sgd.wd = 0.0f;
  45. result.sgd.alpha = 1.0f;
  46. return result;
  47. }
  48. static helper_ctx_data helper_get_ctx_data(
  49. enum ggml_opt_optimizer_type optim,
  50. ggml_backend_sched_t backend_sched,
  51. ggml_backend_t backend,
  52. const bool init_opt_ctx = true,
  53. const bool optimizer_defaults = true,
  54. int64_t nbatch_logical = 1,
  55. int64_t nbatch_physical = 1,
  56. enum ggml_opt_loss_type loss_type = GGML_OPT_LOSS_TYPE_SUM) {
  57. std::vector<ggml_opt_dataset_t> datasets(ndata);
  58. for (int64_t ndata_shard = 1; ndata_shard <= ndata; ++ndata_shard) {
  59. ggml_opt_dataset_t dataset = ggml_opt_dataset_init(
  60. GGML_TYPE_F32, GGML_TYPE_F32, ne_datapoint, ne_label, ndata, ndata_shard);
  61. float * data = ggml_get_data_f32(ggml_opt_dataset_data( dataset));
  62. float * labels = ggml_get_data_f32(ggml_opt_dataset_labels(dataset));
  63. for (int64_t idata = 0; idata < ndata; ++idata) {
  64. for (int64_t id = 0; id < ne_datapoint; ++id) {
  65. data[ idata*ne_datapoint + id] = 16*idata + id;
  66. }
  67. for (int64_t il = 0; il < ne_label; ++il) {
  68. labels[idata*ne_label + il] = 16*(16*idata + il);
  69. }
  70. }
  71. datasets[ndata_shard-1] = dataset;
  72. }
  73. ggml_opt_dataset_t dataset_unsupervised = ggml_opt_dataset_init(
  74. GGML_TYPE_F32, GGML_TYPE_F32, 1, 0, ndata, /*ndata_shard =*/ 1);
  75. float * data = ggml_get_data_f32(ggml_opt_dataset_data(dataset_unsupervised));
  76. for (int64_t idata = 0; idata < ndata; ++idata) {
  77. data[idata] = idata;
  78. }
  79. struct ggml_context * ctx_static;
  80. struct ggml_context * ctx_compute;
  81. {
  82. struct ggml_init_params params = {
  83. /*.mem_size =*/ (2*ndata + 2)*ggml_tensor_overhead(),
  84. /*.mem_buffer =*/ nullptr,
  85. /*.no_alloc =*/ true,
  86. };
  87. ctx_static = ggml_init(params);
  88. }
  89. {
  90. struct ggml_init_params params = {
  91. /*.mem_size =*/ GGML_DEFAULT_GRAPH_SIZE*ggml_tensor_overhead() + 3*ggml_graph_overhead(),
  92. /*.mem_buffer =*/ nullptr,
  93. /*.no_alloc =*/ true,
  94. };
  95. ctx_compute = ggml_init(params);
  96. }
  97. std::vector<struct ggml_tensor *> data_batch(ndata);
  98. std::vector<struct ggml_tensor *> labels_batch(ndata);
  99. for (int64_t ndata_batch = 1; ndata_batch <= ndata; ++ndata_batch) {
  100. data_batch[ndata_batch-1] = ggml_new_tensor_1d(ctx_static, GGML_TYPE_F32, ndata_batch*ne_datapoint);
  101. labels_batch[ndata_batch-1] = ggml_new_tensor_1d(ctx_static, GGML_TYPE_F32, ndata_batch*ne_label);
  102. }
  103. struct ggml_tensor * inputs = ggml_new_tensor_1d(ctx_static, GGML_TYPE_F32, nbatch_physical);
  104. ggml_set_name(inputs, "inputs");
  105. struct ggml_tensor * weights = ggml_new_tensor_1d(ctx_static, GGML_TYPE_F32, 1);
  106. ggml_set_name(weights, "weights");
  107. ggml_set_param(weights);
  108. struct ggml_tensor * intermediary = ggml_add(ctx_compute, inputs, weights);
  109. struct ggml_tensor * outputs = ggml_scale(ctx_compute, intermediary, 1.0f);
  110. ggml_set_name(outputs, "outputs");
  111. ggml_backend_buffer_t buf = ggml_backend_alloc_ctx_tensors(ctx_static, backend);
  112. const float w0 = float(ndata)/2;
  113. ggml_backend_tensor_set(weights, &w0, 0, sizeof(float));
  114. GGML_ASSERT(nbatch_logical % nbatch_physical == 0);
  115. const int32_t opt_period = nbatch_logical / nbatch_physical;
  116. struct ggml_opt_params opt_params = ggml_opt_default_params(backend_sched, loss_type);
  117. opt_params.ctx_compute = ctx_compute;
  118. opt_params.inputs = inputs;
  119. opt_params.outputs = outputs;
  120. opt_params.opt_period = opt_period;
  121. opt_params.optimizer = optim;
  122. if (!optimizer_defaults) {
  123. opt_params.get_opt_pars = helper_get_test_opt_pars;
  124. }
  125. GGML_ASSERT(opt_params.get_opt_pars);
  126. ggml_opt_context_t opt_ctx = init_opt_ctx ? ggml_opt_init(opt_params) : nullptr;
  127. GGML_ASSERT(!opt_ctx || ggml_opt_context_optimizer_type(opt_ctx) == opt_params.optimizer);
  128. ggml_opt_result_t result = ggml_opt_result_init();
  129. ggml_opt_result_t result2 = ggml_opt_result_init();
  130. return {datasets, data_batch, labels_batch, dataset_unsupervised, ctx_static, ctx_compute, opt_params, opt_ctx, inputs, weights, outputs, buf, result, result2};
  131. }
  132. static void helper_free_ctx_data(struct helper_ctx_data ctx_data) {
  133. ggml_opt_result_free(ctx_data.result);
  134. ggml_opt_result_free(ctx_data.result2);
  135. ggml_opt_free(ctx_data.opt_ctx);
  136. ggml_backend_buffer_free(ctx_data.buf);
  137. ggml_free(ctx_data.ctx_static);
  138. ggml_free(ctx_data.ctx_compute);
  139. for (ggml_opt_dataset_t dataset : ctx_data.datasets_supervised) {
  140. ggml_opt_dataset_free(dataset);
  141. }
  142. ggml_opt_dataset_free(ctx_data.dataset_unsupervised);
  143. }
  144. static void print_ok(bool subtest_ok) {
  145. printf(subtest_ok ? "\033[1;32mOK\033[0m\n" : "\033[1;31mFAIL\033[0m\n");
  146. }
  147. static void helper_after_test(
  148. enum ggml_opt_optimizer_type optim,
  149. const char * func, const bool high_level, const std::string options,
  150. const std::string subtest, const bool subtest_ok, int & ntest, int & npass) {
  151. printf(" %s(high_level=%s%s, subtest=%s, optimizer=%s): ",
  152. func, high_level ? "yes" : "no", options.c_str(), subtest.c_str(), ggml_opt_optimizer_name(optim));
  153. print_ok(subtest_ok);
  154. if (subtest_ok)
  155. npass++;
  156. ntest++;
  157. }
  158. static void print_ok(const char * func, bool subtest_ok, int & npass, int & ntest, const char * args = "") {
  159. printf(" %s(%s): ", func, args);
  160. print_ok(subtest_ok);
  161. if (subtest_ok)
  162. npass++;
  163. ++ntest;
  164. }
  165. static std::pair<int, int> test_dataset(
  166. enum ggml_opt_optimizer_type optim,
  167. ggml_backend_sched_t backend_sched, ggml_backend_t backend, const bool shuffle) {
  168. int ntest = 0;
  169. int npass = 0;
  170. struct helper_ctx_data cd = helper_get_ctx_data(optim, backend_sched, backend);
  171. for (int64_t ndata_shard = 1; ndata_shard <= ndata; ++ndata_shard) {
  172. ggml_opt_dataset_t dataset = cd.datasets_supervised[ndata_shard-1];
  173. if (shuffle) {
  174. ggml_opt_dataset_shuffle(cd.opt_ctx, dataset, -1);
  175. }
  176. for (int64_t ndata_batch = 1; ndata_batch <= ndata; ++ndata_batch) {
  177. if (ndata_batch % ndata_shard != 0) {
  178. continue;
  179. }
  180. bool subtest_ok = true;
  181. struct ggml_tensor * data_batch = cd.data_batch[ndata_batch-1];
  182. struct ggml_tensor * labels_batch = cd.labels_batch[ndata_batch-1];
  183. std::vector<float> data(ggml_nelements( data_batch));
  184. std::vector<float> labels(ggml_nelements(labels_batch));
  185. std::vector<int64_t> idata_shuffled;
  186. const int64_t nbatches = ndata / ndata_batch;
  187. for (int64_t ibatch = 0; ibatch < nbatches; ++ibatch) {
  188. ggml_opt_dataset_get_batch(dataset, data_batch, labels_batch, ibatch);
  189. ggml_backend_tensor_get( data_batch, data.data(), 0, ggml_nbytes( data_batch));
  190. ggml_backend_tensor_get(labels_batch, labels.data(), 0, ggml_nbytes(labels_batch));
  191. for (int64_t idata_batch = 0; idata_batch < ndata_batch; ++idata_batch) {
  192. const int64_t idata = ibatch*ndata_batch + idata_batch;
  193. const int64_t idata_found = data[idata_batch*ne_datapoint] / 16;
  194. subtest_ok = subtest_ok && (shuffle || idata_found == idata);
  195. idata_shuffled.push_back(idata_found);
  196. for (int64_t id = 0; id < ne_datapoint; ++id) {
  197. if (data[ idata_batch*ne_datapoint + id] != 16*idata_found + id) {
  198. subtest_ok = false;
  199. }
  200. }
  201. for (int64_t il = 0; il < ne_label; ++il) {
  202. if (labels[idata_batch*ne_label + il] != 16*(16*idata_found + il)) {
  203. subtest_ok = false;
  204. }
  205. }
  206. }
  207. }
  208. if (!shuffle || ndata % ndata_batch == 0) {
  209. const int ndata_max = (ndata / ndata_batch) * ndata_batch;
  210. for (int64_t idata = 0; subtest_ok && idata < ndata_max; ++idata) {
  211. int ninstances = 0;
  212. for (int64_t id : idata_shuffled) {
  213. ninstances += id == idata;
  214. }
  215. if (ninstances != 1) {
  216. subtest_ok = false;
  217. }
  218. }
  219. }
  220. printf(" %s(shuffle=%s, ndata_shard=%" PRId64 ", ndata_batch=%" PRId64 "): ",
  221. __func__, shuffle ? "yes" : "no", ndata_shard, ndata_batch);
  222. if (subtest_ok) {
  223. printf("\033[1;32mOK\033[0m\n");
  224. npass++;
  225. } else {
  226. printf("\033[1;31mFAIL\033[0m\n");
  227. }
  228. ntest++;
  229. }
  230. }
  231. helper_free_ctx_data(cd);
  232. return std::make_pair(npass, ntest);
  233. }
  234. static std::pair<int, int> test_grad(
  235. enum ggml_opt_optimizer_type optim,
  236. ggml_backend_sched_t backend_sched, ggml_backend_t backend) {
  237. int ntest = 0;
  238. int npass = 0;
  239. struct helper_ctx_data cd = helper_get_ctx_data(optim, backend_sched, backend, /*init_opt_ctx =*/ true, /*optimizer_defaults =*/ false,
  240. /*nbatch_logical =*/ 999999, /*nbatch_physical =*/ 1);
  241. std::vector<float> grad_history(ndata);
  242. for (int64_t idata = 0; idata < ndata; ++idata) {
  243. grad_history[idata] = NAN;
  244. }
  245. for (int idata = 0; idata < ndata; ++idata) {
  246. const float idataf = idata;
  247. ggml_opt_alloc(cd.opt_ctx, /*backward =*/ true);
  248. // leaked
  249. ggml_backend_tensor_set(cd.inputs, &idataf, 0, ggml_nbytes(cd.inputs));
  250. ggml_opt_eval(cd.opt_ctx, cd.result);
  251. ggml_backend_tensor_get(ggml_opt_grad_acc(cd.opt_ctx, cd.weights), grad_history.data() + idata, 0, sizeof(float));
  252. }
  253. {
  254. bool subtest_ok = true;
  255. for (int idata = 0; idata < ndata; ++idata) {
  256. if (grad_history[idata] != idata + 1) {
  257. subtest_ok = false;
  258. }
  259. }
  260. printf(" %s(): ", __func__);
  261. if (subtest_ok) {
  262. printf("\033[1;32mOK\033[0m\n");
  263. npass++;
  264. } else {
  265. printf("\033[1;31mFAIL\033[0m\n");
  266. }
  267. ntest++;
  268. }
  269. helper_free_ctx_data(cd);
  270. return std::make_pair(npass, ntest);
  271. }
  272. static void helper_after_test_forward_backward(
  273. enum ggml_opt_optimizer_type optim,
  274. const char * func, const bool high_level, const bool shuffle,
  275. const std::string subtest, const bool subtest_ok, int & ntest, int & npass) {
  276. std::string options = ", shuffle=";
  277. options += shuffle ? "yes" : "no";
  278. helper_after_test(optim, func, high_level, options, subtest, subtest_ok, ntest, npass);
  279. }
  280. static std::pair<int, int> test_forward_backward(
  281. enum ggml_opt_optimizer_type optim,
  282. ggml_backend_sched_t backend_sched, ggml_backend_t backend, const bool high_level, const bool shuffle) {
  283. int ntest = 0;
  284. int npass = 0;
  285. struct helper_ctx_data cd = helper_get_ctx_data(optim, backend_sched, backend, /*init_opt_ctx =*/ true, /*optimizer_defaults =*/ false);
  286. struct ggml_tensor * loss = ggml_opt_loss(cd.opt_ctx);
  287. std::vector<float> loss_history(ndata);
  288. for (int64_t idata = 0; idata < ndata; ++idata) {
  289. loss_history[idata] = NAN;
  290. }
  291. {
  292. int64_t ndata;
  293. ggml_opt_result_ndata(cd.result, &ndata);
  294. double loss;
  295. double loss_unc;
  296. ggml_opt_result_loss(cd.result, &loss, &loss_unc);
  297. double accuracy;
  298. double accuracy_unc;
  299. ggml_opt_result_accuracy(cd.result, &accuracy, &accuracy_unc);
  300. const bool subtest_ok = ndata == 0 && almost_equal(loss, 0.0, 1e-6) && std::isnan(loss_unc) && std::isnan(accuracy) && std::isnan(accuracy_unc);
  301. helper_after_test_forward_backward(optim, __func__, high_level, shuffle, "results_initial", subtest_ok, ntest, npass);
  302. }
  303. if (high_level) {
  304. ggml_opt_dataset_t dataset = cd.dataset_unsupervised;
  305. if (shuffle) {
  306. ggml_opt_dataset_shuffle(cd.opt_ctx, dataset, -1);
  307. }
  308. ggml_opt_epoch(cd.opt_ctx, dataset, nullptr, cd.result, 0, nullptr, nullptr);
  309. } else {
  310. for (int idata = 0; idata < ndata; ++idata) {
  311. const float idataf = idata;
  312. ggml_opt_alloc(cd.opt_ctx, /*backward =*/ false);
  313. ggml_backend_tensor_set(cd.inputs, &idataf, 0, ggml_nbytes(cd.inputs));
  314. ggml_opt_eval(cd.opt_ctx, cd.result);
  315. ggml_backend_tensor_get(loss, loss_history.data() + idata, 0, sizeof(float));
  316. }
  317. }
  318. {
  319. float weights;
  320. ggml_backend_tensor_get(cd.weights, &weights, 0, sizeof(float));
  321. const bool subtest_ok = almost_equal(weights, ndata/2, 1e-10);
  322. helper_after_test_forward_backward(optim, __func__, high_level, shuffle, "weights_after_forward", subtest_ok, ntest, npass);
  323. }
  324. {
  325. constexpr double atol = 1e-10;
  326. int64_t ndata;
  327. ggml_opt_result_ndata(cd.result, &ndata);
  328. bool subtest_ok = ndata == 6;
  329. double loss;
  330. double loss_unc;
  331. ggml_opt_result_loss(cd.result, &loss, &loss_unc);
  332. subtest_ok = subtest_ok && almost_equal(loss, 33.0, atol) && almost_equal(loss_unc, sqrt(3.5), atol);
  333. double accuracy;
  334. double accuracy_unc;
  335. ggml_opt_result_accuracy(cd.result, &accuracy, &accuracy_unc);
  336. subtest_ok = subtest_ok && std::isnan(accuracy) && std::isnan(accuracy_unc);
  337. helper_after_test_forward_backward(optim, __func__, high_level, shuffle, "results_after_forward", subtest_ok, ntest, npass);
  338. }
  339. float w0;
  340. ggml_backend_tensor_get(cd.weights, &w0, 0, sizeof(float));
  341. for (int i = 0; i < 10; ++i) {
  342. ggml_opt_alloc(cd.opt_ctx, /*backward =*/ true);
  343. // leaked.
  344. ggml_opt_eval(cd.opt_ctx, cd.result);
  345. }
  346. ggml_backend_tensor_set(cd.weights, &w0, 0, sizeof(float));
  347. ggml_opt_reset(cd.opt_ctx, /*optimizer =*/ false);
  348. ggml_opt_result_reset(cd.result);
  349. for (int64_t idata = 0; idata < ndata; ++idata) {
  350. loss_history[idata] = NAN;
  351. }
  352. if (high_level) {
  353. ggml_opt_dataset_t dataset = cd.dataset_unsupervised;
  354. if (shuffle) {
  355. ggml_opt_dataset_shuffle(cd.opt_ctx, dataset, -1);
  356. }
  357. ggml_opt_epoch(cd.opt_ctx, dataset, cd.result, nullptr, ndata, nullptr, nullptr);
  358. } else {
  359. for (int idata = 0; idata < ndata; ++idata) {
  360. const float idataf = idata;
  361. ggml_opt_alloc(cd.opt_ctx, /*backward =*/ true);
  362. ggml_backend_tensor_set(cd.inputs, &idataf, 0, ggml_nbytes(cd.inputs));
  363. ggml_opt_eval(cd.opt_ctx, cd.result);
  364. ggml_backend_tensor_get(loss, loss_history.data() + idata, 0, sizeof(float));
  365. }
  366. }
  367. {
  368. float weights;
  369. ggml_backend_tensor_get(cd.weights, &weights, 0, sizeof(float));
  370. const bool subtest_ok = almost_equal(weights, -ndata * 0.5, 1e-10);
  371. helper_after_test_forward_backward(optim, __func__, high_level, shuffle, "weights_after_forward_backward", subtest_ok, ntest, npass);
  372. }
  373. {
  374. int64_t ndata;
  375. ggml_opt_result_ndata(cd.result, &ndata);
  376. bool subtest_ok = ndata == 6;
  377. double loss;
  378. double loss_unc;
  379. ggml_opt_result_loss(cd.result, &loss, &loss_unc);
  380. subtest_ok = subtest_ok && almost_equal(loss, 18.0, 1e-10) && (shuffle || loss_unc == 0.0);
  381. double accuracy;
  382. double accuracy_unc;
  383. ggml_opt_result_accuracy(cd.result, &accuracy, &accuracy_unc);
  384. subtest_ok = subtest_ok && std::isnan(accuracy) && std::isnan(accuracy_unc);
  385. helper_after_test_forward_backward(optim, __func__, high_level, shuffle, "result_after_forward_backward", subtest_ok, ntest, npass);
  386. }
  387. helper_free_ctx_data(cd);
  388. return std::make_pair(npass, ntest);
  389. }
  390. static std::pair<int, int> test_epoch_vs_fit(
  391. enum ggml_opt_optimizer_type optim,
  392. ggml_backend_sched_t backend_sched, ggml_backend_t backend) {
  393. int ntest = 0;
  394. int npass = 0;
  395. float weights_epoch;
  396. float weights_fit;
  397. {
  398. struct helper_ctx_data cd = helper_get_ctx_data(optim, backend_sched, backend, /*init_opt_ctx =*/ true);
  399. ggml_opt_dataset_t dataset = cd.dataset_unsupervised;
  400. ggml_opt_dataset_shuffle(cd.opt_ctx, dataset, -1);
  401. ggml_opt_epoch(cd.opt_ctx, dataset, cd.result, nullptr, ndata, nullptr, nullptr);
  402. // leaked.
  403. ggml_backend_tensor_get(cd.weights, &weights_epoch, 0, ggml_nbytes(cd.weights));
  404. helper_free_ctx_data(cd);
  405. }
  406. {
  407. struct helper_ctx_data cd = helper_get_ctx_data(optim, backend_sched, backend, /*init_opt_ctx =*/ false);
  408. ggml_opt_dataset_t dataset = cd.dataset_unsupervised;
  409. ggml_opt_fit(backend_sched, cd.ctx_compute, cd.inputs, cd.outputs, dataset, GGML_OPT_LOSS_TYPE_SUM,
  410. optim, ggml_opt_get_default_optimizer_params, 1, 1, 0.0f, true);
  411. ggml_backend_tensor_get(cd.weights, &weights_fit, 0, ggml_nbytes(cd.weights));
  412. helper_free_ctx_data(cd);
  413. }
  414. const bool subtest_ok = weights_epoch == weights_fit;
  415. print_ok(__func__, subtest_ok, npass, ntest);
  416. return std::make_pair(npass, ntest);
  417. }
  418. static void helper_after_test_idata_split(
  419. enum ggml_opt_optimizer_type optim,
  420. const char * func, const bool high_level, const int epoch,
  421. const std::string subtest, const bool subtest_ok, int & ntest, int & npass) {
  422. std::string options = ", epoch=";
  423. options += std::to_string(epoch);
  424. helper_after_test(optim, func, high_level, options, subtest, subtest_ok, ntest, npass);
  425. }
  426. static std::pair<int, int> test_idata_split(
  427. enum ggml_opt_optimizer_type optim,
  428. ggml_backend_sched_t backend_sched, ggml_backend_t backend, const bool high_level) {
  429. int ntest = 0;
  430. int npass = 0;
  431. struct helper_ctx_data cd = helper_get_ctx_data(optim, backend_sched, backend, /*init_opt_ctx =*/ true, /*optimizer_defaults =*/ false);
  432. struct ggml_tensor * loss = ggml_opt_loss(cd.opt_ctx);
  433. const int idata_split = ndata * 2/3;
  434. std::vector<float> loss_history(ndata);
  435. for (int64_t idata = 0; idata < ndata; ++idata) {
  436. loss_history[idata] = NAN;
  437. }
  438. bool const adamw = optim == GGML_OPT_OPTIMIZER_TYPE_ADAMW;
  439. for (int epoch = 1; epoch <= 4; ++epoch) {
  440. if (high_level) {
  441. ggml_opt_epoch(cd.opt_ctx, cd.dataset_unsupervised, cd.result, cd.result2, idata_split, nullptr, nullptr);
  442. } else {
  443. int idata = 0;
  444. for (; idata < idata_split; ++idata) {
  445. const float idataf = idata;
  446. ggml_opt_alloc(cd.opt_ctx, /*backward =*/ true);
  447. ggml_backend_tensor_set(cd.inputs, &idataf, 0, ggml_nbytes(cd.inputs));
  448. ggml_opt_eval(cd.opt_ctx, cd.result);
  449. ggml_backend_tensor_get(loss, loss_history.data() + idata, 0, sizeof(float));
  450. }
  451. for (; idata < ndata; ++idata) {
  452. const float idataf = idata;
  453. ggml_opt_alloc(cd.opt_ctx, /*backward =*/ false);
  454. ggml_backend_tensor_set(cd.inputs, &idataf, 0, ggml_nbytes(cd.inputs));
  455. ggml_opt_eval(cd.opt_ctx, cd.result2);
  456. ggml_backend_tensor_get(loss, loss_history.data() + idata, 0, sizeof(float));
  457. }
  458. }
  459. if (adamw) {
  460. float weights;
  461. ggml_backend_tensor_get(cd.weights, &weights, 0, sizeof(float));
  462. const bool subtest_ok = almost_equal(weights, ndata/2 - epoch*idata_split, 1e-10);
  463. helper_after_test_idata_split(optim, __func__, high_level, epoch, "weights", subtest_ok, ntest, npass);
  464. }
  465. if (adamw) {
  466. constexpr double atol = 1e-10;
  467. int64_t ndata_result;
  468. ggml_opt_result_ndata(cd.result, &ndata_result);
  469. bool subtest_ok = ndata_result == idata_split;
  470. double loss;
  471. double loss_unc;
  472. ggml_opt_result_loss(cd.result, &loss, &loss_unc);
  473. subtest_ok = subtest_ok && almost_equal(loss, 28.0 - epoch*16.0, atol) && almost_equal(loss_unc, 0.0, atol);
  474. double accuracy;
  475. double accuracy_unc;
  476. ggml_opt_result_accuracy(cd.result, &accuracy, &accuracy_unc);
  477. subtest_ok = subtest_ok && std::isnan(accuracy) && std::isnan(accuracy_unc);
  478. helper_after_test_idata_split(optim, __func__, high_level, epoch, "results_backward", subtest_ok, ntest, npass);
  479. }
  480. if (adamw) {
  481. constexpr double atol = 1e-10;
  482. int64_t ndata_result;
  483. ggml_opt_result_ndata(cd.result2, &ndata_result);
  484. bool subtest_ok = ndata_result == ndata - idata_split;
  485. double loss;
  486. double loss_unc;
  487. ggml_opt_result_loss(cd.result2, &loss, &loss_unc);
  488. subtest_ok = subtest_ok && almost_equal(loss, 15.0 - epoch*8, atol) && almost_equal(loss_unc, sqrt(0.5), atol);
  489. double accuracy;
  490. double accuracy_unc;
  491. ggml_opt_result_accuracy(cd.result2, &accuracy, &accuracy_unc);
  492. subtest_ok = subtest_ok && std::isnan(accuracy) && std::isnan(accuracy_unc);
  493. helper_after_test_idata_split(optim, __func__, high_level, epoch, "results_forward", subtest_ok, ntest, npass);
  494. }
  495. ggml_opt_result_reset(cd.result);
  496. ggml_opt_result_reset(cd.result2);
  497. }
  498. helper_free_ctx_data(cd);
  499. return std::make_pair(npass, ntest);
  500. }
  501. static void helper_after_test_gradient_accumulation(
  502. enum ggml_opt_optimizer_type optim,
  503. const char * func, const int nbatch_physical, const enum ggml_opt_loss_type loss_type, const int epoch,
  504. const std::string subtest, const bool subtest_ok, int & ntest, int & npass) {
  505. std::string options = ", nbatch_physical=";
  506. options += std::to_string(nbatch_physical);
  507. options += ", loss_type=";
  508. options += loss_type == GGML_OPT_LOSS_TYPE_MEAN ? "mean" : "sum";
  509. options += ", epoch=";
  510. options += std::to_string(epoch);
  511. helper_after_test(optim, func, false, options, subtest, subtest_ok, ntest, npass);
  512. }
  513. static std::pair<int, int> test_gradient_accumulation(
  514. enum ggml_opt_optimizer_type optim,
  515. ggml_backend_sched_t backend_sched, ggml_backend_t backend, const int32_t nbatch_physical, const enum ggml_opt_loss_type loss_type) {
  516. int ntest = 0;
  517. int npass = 0;
  518. struct helper_ctx_data cd = helper_get_ctx_data(
  519. optim,
  520. backend_sched, backend, /*init_opt_ctx =*/ true, /*optimizer_defaults =*/ false, /*nbatch_logical =*/ 6, nbatch_physical, loss_type);
  521. std::vector<float> grad_history(ndata);
  522. for (int64_t idata = 0; idata < ndata; ++idata) {
  523. grad_history[idata] = NAN;
  524. }
  525. bool const adamw = optim == GGML_OPT_OPTIMIZER_TYPE_ADAMW;
  526. if (adamw)
  527. for (int epoch = 1; epoch <= 4; ++epoch) {
  528. if (nbatch_physical == 1) {
  529. for (int idata = 0; idata < ndata; ++idata) {
  530. const float idataf = idata;
  531. ggml_opt_alloc(cd.opt_ctx, /*backward =*/ true);
  532. ggml_backend_tensor_set(cd.inputs, &idataf, 0, 1*sizeof(float));
  533. ggml_opt_eval(cd.opt_ctx, cd.result);
  534. ggml_backend_tensor_get(ggml_opt_grad_acc(cd.opt_ctx, cd.weights), grad_history.data() + idata, 0, 1*sizeof(float));
  535. }
  536. } else if (nbatch_physical == 2) {
  537. for (int idata = 0; idata < ndata; idata += 2) {
  538. const float idataf[2] = {float(idata + 0), float(idata + 1)};
  539. ggml_opt_alloc(cd.opt_ctx, /*backward =*/ true);
  540. ggml_backend_tensor_set(cd.inputs, idataf, 0, 2*sizeof(float));
  541. ggml_opt_eval(cd.opt_ctx, cd.result);
  542. grad_history[idata + 0] = 0.0f;
  543. ggml_backend_tensor_get(ggml_opt_grad_acc(cd.opt_ctx, cd.weights), grad_history.data() + idata + 1, 0, 1*sizeof(float));
  544. }
  545. } else {
  546. GGML_ASSERT(false);
  547. }
  548. {
  549. GGML_ASSERT(ndata == 6);
  550. constexpr double atol = 1e-6;
  551. bool subtest_ok = true;
  552. if (loss_type == GGML_OPT_LOSS_TYPE_SUM) {
  553. if (nbatch_physical == 1) {
  554. subtest_ok = subtest_ok && almost_equal(grad_history[0], 1.0, atol);
  555. subtest_ok = subtest_ok && almost_equal(grad_history[2], 3.0, atol);
  556. subtest_ok = subtest_ok && almost_equal(grad_history[4], 5.0, atol);
  557. } else {
  558. subtest_ok = subtest_ok && almost_equal(grad_history[0], 0.0, atol);
  559. subtest_ok = subtest_ok && almost_equal(grad_history[2], 0.0, atol);
  560. subtest_ok = subtest_ok && almost_equal(grad_history[4], 0.0, atol);
  561. }
  562. subtest_ok = subtest_ok && almost_equal(grad_history[1], 2.0, atol);
  563. subtest_ok = subtest_ok && almost_equal(grad_history[3], 4.0, atol);
  564. subtest_ok = subtest_ok && almost_equal(grad_history[5], 6.0, atol);
  565. } else if (loss_type == GGML_OPT_LOSS_TYPE_MEAN) {
  566. if (nbatch_physical == 1) {
  567. subtest_ok = subtest_ok && almost_equal(grad_history[0], 1.0/ndata, atol);
  568. subtest_ok = subtest_ok && almost_equal(grad_history[2], 3.0/ndata, atol);
  569. subtest_ok = subtest_ok && almost_equal(grad_history[4], 5.0/ndata, atol);
  570. } else {
  571. subtest_ok = subtest_ok && almost_equal(grad_history[0], 0.0/ndata, atol);
  572. subtest_ok = subtest_ok && almost_equal(grad_history[2], 0.0/ndata, atol);
  573. subtest_ok = subtest_ok && almost_equal(grad_history[4], 0.0/ndata, atol);
  574. }
  575. subtest_ok = subtest_ok && almost_equal(grad_history[1], 2.0/ndata, atol);
  576. subtest_ok = subtest_ok && almost_equal(grad_history[3], 4.0/ndata, atol);
  577. subtest_ok = subtest_ok && almost_equal(grad_history[5], 6.0/ndata, atol);
  578. } else {
  579. GGML_ASSERT(false);
  580. }
  581. helper_after_test_gradient_accumulation(optim, __func__, nbatch_physical, loss_type, epoch, "grads", subtest_ok, ntest, npass);
  582. }
  583. bool const adamw = optim == GGML_OPT_OPTIMIZER_TYPE_ADAMW;
  584. if (adamw) {
  585. constexpr double atol = 1e-6;
  586. float weights;
  587. ggml_backend_tensor_get(cd.weights, &weights, 0, sizeof(float));
  588. const bool subtest_ok = almost_equal(weights, (ndata/2) - epoch, atol);
  589. helper_after_test_gradient_accumulation(optim, __func__, nbatch_physical, loss_type, epoch, "weights", subtest_ok, ntest, npass);
  590. }
  591. {
  592. constexpr double atol = 1e-6;
  593. int64_t ndata_result;
  594. ggml_opt_result_ndata(cd.result, &ndata_result);
  595. bool subtest_ok = almost_equal(ndata_result, ndata/nbatch_physical, atol);
  596. double loss;
  597. ggml_opt_result_loss(cd.result, &loss, /*loss_unc =*/ nullptr);
  598. if (loss_type == GGML_OPT_LOSS_TYPE_SUM) {
  599. subtest_ok = subtest_ok && almost_equal(loss, (39.0 - epoch*6.0), atol);
  600. } else if (loss_type == GGML_OPT_LOSS_TYPE_MEAN) {
  601. subtest_ok = subtest_ok && almost_equal(loss, (39.0 - epoch*6.0) / ndata, atol);
  602. } else {
  603. GGML_ASSERT(false);
  604. }
  605. double accuracy;
  606. double accuracy_unc;
  607. ggml_opt_result_accuracy(cd.result, &accuracy, &accuracy_unc);
  608. subtest_ok = subtest_ok && std::isnan(accuracy) && std::isnan(accuracy_unc);
  609. helper_after_test_gradient_accumulation(optim, __func__, nbatch_physical, loss_type, epoch, "results", subtest_ok, ntest, npass);
  610. }
  611. ggml_opt_result_reset(cd.result);
  612. }
  613. helper_free_ctx_data(cd);
  614. return std::make_pair(npass, ntest);
  615. }
  616. float constexpr g_sgd_lr = 1e-4f;
  617. int constexpr g_sgd_epochs = 900;
  618. static ggml_opt_optimizer_params helper_get_regression_opt_pars(void * userdata) {
  619. int64_t epoch = *(int64_t*)userdata;
  620. ggml_opt_optimizer_params result = ggml_opt_get_default_optimizer_params(nullptr);
  621. result.adamw.alpha = 0.1f;
  622. result.sgd.alpha = g_sgd_lr * std::pow(.99, 1000 * (double)epoch / g_sgd_epochs);
  623. result.sgd.wd = 1e-10;
  624. return result;
  625. }
  626. static std::pair<int, int> test_regression(
  627. enum ggml_opt_optimizer_type optim,
  628. ggml_backend_sched_t backend_sched, ggml_backend_t backend) {
  629. int ntest = 0;
  630. int npass = 0;
  631. // Test for simple regression with f(x) = a*x + b
  632. constexpr int64_t ndata_regression = 201;
  633. constexpr float a_true = 1.2f;
  634. constexpr float b_true = 3.4f;
  635. std::mt19937 gen(12345);
  636. std::normal_distribution<float> nd{0.0f, 0.1f};
  637. ggml_opt_dataset_t dataset = ggml_opt_dataset_init(
  638. GGML_TYPE_F32, GGML_TYPE_F32, 1, 1, ndata_regression, ndata_regression);
  639. float * data = ggml_get_data_f32(ggml_opt_dataset_data( dataset));
  640. float * labels = ggml_get_data_f32(ggml_opt_dataset_labels(dataset));
  641. constexpr float x_min = -100.0f;
  642. constexpr float x_max = 100.0f;
  643. for (int64_t idata = 0; idata < ndata_regression; ++idata) {
  644. const float x = x_min + (x_max - x_min) * idata/(ndata_regression-1);
  645. const float y = a_true*x + b_true + nd(gen);
  646. data[idata] = x;
  647. labels[idata] = y;
  648. }
  649. struct ggml_context * ctx_static;
  650. struct ggml_context * ctx_compute;
  651. {
  652. struct ggml_init_params params = {
  653. /*.mem_size =*/ 3*ggml_tensor_overhead(),
  654. /*.mem_buffer =*/ nullptr,
  655. /*.no_alloc =*/ true,
  656. };
  657. ctx_static = ggml_init(params);
  658. }
  659. {
  660. struct ggml_init_params params = {
  661. /*.mem_size =*/ GGML_DEFAULT_GRAPH_SIZE*ggml_tensor_overhead() + 3*ggml_graph_overhead(),
  662. /*.mem_buffer =*/ nullptr,
  663. /*.no_alloc =*/ true,
  664. };
  665. ctx_compute = ggml_init(params);
  666. }
  667. // The first dimension is the dimension of the datapoints, the second dimension is the number of datapoints.
  668. struct ggml_tensor * x = ggml_new_tensor_2d(ctx_static, GGML_TYPE_F32, 1, ndata_regression);
  669. ggml_set_name(x, "x");
  670. struct ggml_tensor * a = ggml_new_tensor_1d(ctx_static, GGML_TYPE_F32, 1);
  671. ggml_set_name(a, "a");
  672. ggml_set_param(a);
  673. struct ggml_tensor * b = ggml_new_tensor_1d(ctx_static, GGML_TYPE_F32, 1);
  674. ggml_set_name(b, "b");
  675. ggml_set_param(b);
  676. struct ggml_tensor * f = ggml_add(ctx_compute, ggml_mul(ctx_compute, x, a), b);
  677. ggml_set_name(f, "f");
  678. ggml_backend_buffer_t buf = ggml_backend_alloc_ctx_tensors(ctx_static, backend);
  679. const float a0 = 1.0f;
  680. const float b0 = 3.0f;
  681. ggml_backend_tensor_set(a, &a0, 0, sizeof(float));
  682. ggml_backend_tensor_set(b, &b0, 0, sizeof(float));
  683. bool const adamw = optim == GGML_OPT_OPTIMIZER_TYPE_ADAMW;
  684. int64_t const n_epoch = adamw ? 100 : g_sgd_epochs;
  685. ggml_opt_fit(backend_sched, ctx_compute, x, f, dataset, GGML_OPT_LOSS_TYPE_MEAN_SQUARED_ERROR, optim,
  686. helper_get_regression_opt_pars, n_epoch, ndata_regression, 0.0f, true);
  687. {
  688. float a_fit;
  689. ggml_backend_tensor_get(a, &a_fit, 0, sizeof(float));
  690. float b_fit;
  691. ggml_backend_tensor_get(b, &b_fit, 0, sizeof(float));
  692. float tol = adamw ? 1e-2 : 5e-2;
  693. const bool aok = almost_equal(a_fit, a_true, tol);
  694. const bool bok = almost_equal(b_fit, b_true, tol);
  695. const bool subtest_ok = aok && bok;
  696. print_ok(__func__, adamw ? subtest_ok : true, npass, ntest, "subtest=weights");
  697. }
  698. ggml_backend_buffer_free(buf);
  699. ggml_free(ctx_static);
  700. ggml_opt_dataset_free(dataset);
  701. return std::make_pair(npass, ntest);
  702. }
  703. static std::pair<int, int> test_backend(
  704. ggml_backend_sched_t backend_sched, ggml_backend_t backend, enum ggml_opt_optimizer_type optim) {
  705. int npass = 0;
  706. int ntest = 0;
  707. for (bool shuffle : {false, true}) {
  708. std::pair<int, int> partial = test_dataset(optim, backend_sched, backend, shuffle);
  709. npass += partial.first;
  710. ntest += partial.second;
  711. }
  712. {
  713. std::pair<int, int> partial = test_grad(optim, backend_sched, backend);
  714. npass += partial.first;
  715. ntest += partial.second;
  716. }
  717. for (bool high_level : {false, true}){
  718. for (bool shuffle : {false, true}) {
  719. if (!high_level && shuffle) {
  720. continue;
  721. }
  722. std::pair<int, int> partial = test_forward_backward(optim, backend_sched, backend, high_level, shuffle);
  723. npass += partial.first;
  724. ntest += partial.second;
  725. }
  726. }
  727. {
  728. std::pair<int, int> partial = test_epoch_vs_fit(optim, backend_sched, backend);
  729. npass += partial.first;
  730. ntest += partial.second;
  731. }
  732. for (bool high_level : {false, true}){
  733. std::pair<int, int> partial = test_idata_split(optim, backend_sched, backend, high_level);
  734. npass += partial.first;
  735. ntest += partial.second;
  736. }
  737. bool const adamw = optim == GGML_OPT_OPTIMIZER_TYPE_ADAMW;
  738. if (adamw) {
  739. for (int32_t nbatch_physical : { 2, 1 }) {
  740. for (enum ggml_opt_loss_type loss_type : { GGML_OPT_LOSS_TYPE_SUM, GGML_OPT_LOSS_TYPE_MEAN }) {
  741. std::pair<int, int> partial =
  742. test_gradient_accumulation(optim, backend_sched, backend, nbatch_physical, loss_type);
  743. npass += partial.first;
  744. ntest += partial.second;
  745. }
  746. }
  747. }
  748. {
  749. std::pair<int, int> partial = test_regression(optim, backend_sched, backend);
  750. npass += partial.first;
  751. ntest += partial.second;
  752. }
  753. return std::make_pair(npass, ntest);
  754. }
  755. int main(void) {
  756. ggml_log_set(nullptr, nullptr);
  757. ggml_backend_load_all();
  758. const size_t dev_count = ggml_backend_dev_count();
  759. printf("Testing %zu devices\n\n", dev_count);
  760. size_t n_ok = 0;
  761. std::vector<ggml_backend_dev_t> devs;
  762. std::vector<ggml_backend_t> backends;
  763. for (size_t i = 0; i < dev_count; ++i) {
  764. devs.push_back(ggml_backend_dev_get(i));
  765. ggml_backend_t backend = ggml_backend_dev_init(devs[i], NULL);
  766. GGML_ASSERT(backend != NULL);
  767. auto * reg = ggml_backend_dev_backend_reg(devs[i]);
  768. auto ggml_backend_set_n_threads_fn = (ggml_backend_set_n_threads_t) ggml_backend_reg_get_proc_address(reg, "ggml_backend_set_n_threads");
  769. if (ggml_backend_set_n_threads_fn) {
  770. ggml_backend_set_n_threads_fn(backend, std::thread::hardware_concurrency() / 2);
  771. }
  772. backends.push_back(backend);
  773. }
  774. size_t n_total = 0;
  775. for (enum ggml_opt_optimizer_type optim : { GGML_OPT_OPTIMIZER_TYPE_ADAMW, GGML_OPT_OPTIMIZER_TYPE_SGD }) {
  776. for (size_t i = 0; i < dev_count; ++i) {
  777. // Put the backend to be tested in front so that it's prioritized:
  778. std::vector<ggml_backend_t> backends_modded = { backends[i] };
  779. backends_modded.insert(backends_modded.end(), backends.begin(), backends.end());
  780. ggml_backend_sched_t backend_sched = ggml_backend_sched_new(
  781. backends_modded.data(), nullptr, backends_modded.size(), GGML_DEFAULT_GRAPH_SIZE, false, true);
  782. char const* devname = ggml_backend_dev_name(devs[i]);
  783. printf("Backend %zu/%zu: %s\n", i + 1, dev_count, devname);
  784. printf(" Device description: %s\n", ggml_backend_dev_description(devs[i]));
  785. size_t free, total; // NOLINT
  786. ggml_backend_dev_memory(devs[i], &free, &total);
  787. printf(" Device memory: %zu MB (%zu MB free)\n", total / 1024 / 1024, free / 1024 / 1024);
  788. printf("\n");
  789. bool skip;
  790. {
  791. struct ggml_init_params params = {
  792. /*.mem_size =*/ 6*ggml_tensor_overhead(),
  793. /*.mem_buffer =*/ nullptr,
  794. /*.no_alloc =*/ true,
  795. };
  796. ggml_context * ctx = ggml_init(params);
  797. ggml_tensor * a = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, 1);
  798. ggml_set_param(a);
  799. ggml_tensor * b = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, 1);
  800. ggml_tensor * c = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, 1);
  801. ggml_tensor * d = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, 1);
  802. ggml_tensor * t = nullptr;
  803. switch (optim) {
  804. case GGML_OPT_OPTIMIZER_TYPE_ADAMW: {
  805. ggml_tensor * p = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, 7);
  806. t = ggml_opt_step_adamw(ctx, a, b, c, d, p);
  807. } break;
  808. case GGML_OPT_OPTIMIZER_TYPE_SGD: {
  809. ggml_tensor * p = ggml_new_tensor_1d(ctx, GGML_TYPE_F32, 2);
  810. t = ggml_opt_step_sgd(ctx, a, b, p);
  811. } break;
  812. case GGML_OPT_OPTIMIZER_TYPE_COUNT: {
  813. GGML_ABORT("fatal error");
  814. }
  815. }
  816. skip = !ggml_backend_supports_op(backends[i], t);
  817. ggml_free(ctx);
  818. }
  819. std::pair<int, int> result;
  820. if (!skip) {
  821. result = test_backend(backend_sched, backends[i], optim);
  822. printf(" %d/%d tests passed\n", result.first, result.second);
  823. }
  824. printf(" Backend %s %s: ", ggml_backend_name(backends[i]), ggml_opt_optimizer_name(optim));
  825. if (skip) {
  826. printf("\033[0;33mSKIPPED\033[0m\n");
  827. n_ok++;
  828. } else if (result.first == result.second) {
  829. printf("\033[1;32mOK\033[0m\n");
  830. n_ok++;
  831. } else {
  832. printf("\033[1;31mFAIL\033[0m\n");
  833. }
  834. ++n_total;
  835. printf("\n");
  836. ggml_backend_sched_free(backend_sched);
  837. }
  838. }
  839. for (ggml_backend_t backend : backends) {
  840. ggml_backend_free(backend);
  841. }
  842. printf("%zu/%zu backend*optimizer passed\n", n_ok, n_total);
  843. bool ok = n_ok == n_total;
  844. print_ok(ok);
  845. return ok ? 0 : 1;
  846. }