test-opt.cpp 39 KB

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