• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (c) 2023-2024 Tomeu Vizoso <tomeu@tomeuvizoso.net>
3  * SPDX-License-Identifier: MIT
4  */
5 
6 #include <cstdio>
7 #include <fcntl.h>
8 #include <filesystem>
9 #include <fstream>
10 #include <gtest/gtest.h>
11 #include <xtensor/xrandom.hpp>
12 
13 #include <iostream>
14 #include "tensorflow/lite/c/c_api.h"
15 #include "test_executor.h"
16 
17 #define TEST_CONV2D           1
18 #define TEST_DEPTHWISE        1
19 #define TEST_ADD              1
20 #define TEST_FULLY_CONNECTED  1
21 #define TEST_MOBILENETV1      1
22 #define TEST_MOBILEDET        1
23 #define TEST_YOLOX            1
24 
25 #define TOLERANCE       2
26 #define MODEL_TOLERANCE 8
27 #define YOLOX_TOLERANCE 38
28 #define QUANT_TOLERANCE 2
29 
30 std::vector<bool> is_signed{false}; /* TODO: Support INT8? */
31 std::vector<bool> padding_same{false, true};
32 std::vector<int> stride{1, 2};
33 std::vector<int> output_channels{1, 32, 120, 128, 160, 256};
34 std::vector<int> input_channels{1, 32, 120, 128, 256};
35 std::vector<int> dw_channels{1, 32, 120, 128, 256};
36 std::vector<int> dw_weight_size{3, 5};
37 std::vector<int> weight_size{1, 3, 5};
38 std::vector<int> input_size{3, 5, 8, 80, 112};
39 std::vector<int> fc_channels{23, 46, 128, 256, 512};
40 std::vector<int> fc_size{128, 1280, 25088, 62720};
41 
42 static void
set_seed(unsigned seed)43 set_seed(unsigned seed)
44 {
45    srand(seed);
46    xt::random::seed(seed);
47 }
48 
49 static void
test_model(void * buf,size_t buf_size,std::string cache_dir,unsigned tolerance)50 test_model(void *buf, size_t buf_size, std::string cache_dir, unsigned tolerance)
51 {
52    void **input = NULL;
53    size_t num_inputs;
54    void **cpu_output;
55    size_t *output_sizes;
56    TfLiteType *output_types;
57    size_t num_outputs;
58    void **npu_output;
59 
60    TfLiteModel *model = TfLiteModelCreate(buf, buf_size);
61    assert(model);
62 
63    run_model(model, EXECUTOR_CPU, &input, &num_inputs, &cpu_output, &output_sizes, &output_types, &num_outputs, cache_dir);
64    run_model(model, EXECUTOR_NPU, &input, &num_inputs, &npu_output, &output_sizes, &output_types, &num_outputs, cache_dir);
65 
66    for (size_t i = 0; i < num_outputs; i++) {
67       for (size_t j = 0; j < output_sizes[i]; j++) {
68          switch (output_types[i]) {
69             case kTfLiteFloat32: {
70                float *cpu = ((float**)cpu_output)[i];
71                float *npu = ((float**)npu_output)[i];
72                if (abs(cpu[j] - npu[j]) > tolerance) {
73                   std::cout << "CPU: ";
74                   for (int k = 0; k < std::min(int(output_sizes[i]), 24); k++)
75                      std::cout << std::setfill('0') << std::setw(6) << cpu[k] << " ";
76                   std::cout << "\n";
77                   std::cout << "NPU: ";
78                   for (int k = 0; k < std::min(int(output_sizes[i]), 24); k++)
79                      std::cout << std::setfill('0') << std::setw(6) << npu[k] << " ";
80                   std::cout << "\n";
81 
82                   FAIL() << "Output at " << j << " from the NPU (" << std::setfill('0') << std::setw(2) << npu[j] << ") doesn't match that from the CPU (" << std::setfill('0') << std::setw(2) << cpu[j] << ").";
83                }
84                break;
85             }
86             default: {
87                uint8_t *cpu = ((uint8_t**)cpu_output)[i];
88                uint8_t *npu = ((uint8_t**)npu_output)[i];
89                if (abs(cpu[j] - npu[j]) > tolerance) {
90                   std::cout << "CPU: ";
91                   for (int k = 0; k < std::min(int(output_sizes[i]), 24); k++)
92                      std::cout << std::setfill('0') << std::setw(2) << std::hex << int(cpu[k]) << " ";
93                   std::cout << "\n";
94                   std::cout << "NPU: ";
95                   for (int k = 0; k < std::min(int(output_sizes[i]), 24); k++)
96                      std::cout << std::setfill('0') << std::setw(2) << std::hex << int(npu[k]) << " ";
97                   std::cout << "\n";
98 
99                   FAIL() << "Output at " << j << " from the NPU (" << std::setfill('0') << std::setw(2) << std::hex << int(npu[j]) << ") doesn't match that from the CPU (" << std::setfill('0') << std::setw(2) << std::hex << int(cpu[j]) << ").";
100                }
101                break;
102             }
103          }
104       }
105    }
106 
107    for (size_t i = 0; i < num_inputs; i++)
108       free(input[i]);
109    free(input);
110 
111    for (size_t i = 0; i < num_outputs; i++)
112       free(cpu_output[i]);
113    free(cpu_output);
114 
115    for (size_t i = 0; i < num_outputs; i++)
116       free(npu_output[i]);
117    free(npu_output);
118 
119    free(output_sizes);
120    free(output_types);
121 
122    TfLiteModelDelete(model);
123 }
124 
125 static void
test_model_file(std::string file_name,unsigned tolerance,bool use_cache)126 test_model_file(std::string file_name, unsigned tolerance, bool use_cache)
127 {
128    std::ostringstream cache_dir;
129 
130    if (use_cache)
131       cache_dir << "/var/cache/teflon_tests/" << std::filesystem::path(file_name).stem().c_str();
132 
133    set_seed(4);
134 
135    std::ifstream model_file(file_name, std::ios::binary);
136    std::vector<uint8_t> buffer((std::istreambuf_iterator<char>(model_file)),
137                                std::istreambuf_iterator<char>());
138    test_model(buffer.data(), buffer.size(), cache_dir.str(), tolerance);
139 }
140 
141 void
test_conv(int input_size,int weight_size,int input_channels,int output_channels,int stride,bool padding_same,bool is_signed,bool depthwise,int seed)142 test_conv(int input_size, int weight_size, int input_channels, int output_channels,
143           int stride, bool padding_same, bool is_signed, bool depthwise, int seed)
144 {
145    void *buf = NULL;
146    size_t buf_size;
147    std::ostringstream cache_dir, model_cache;
148    cache_dir << "/var/cache/teflon_tests/" << input_size << "_" << weight_size << "_" << input_channels << "_" << output_channels << "_" << stride << "_" << padding_same << "_" << is_signed << "_" << depthwise << "_" << seed;
149    model_cache << cache_dir.str() << "/"
150                << "model.tflite";
151 
152    if (weight_size > input_size)
153       GTEST_SKIP();
154 
155    set_seed(seed);
156 
157    if (cache_is_enabled()) {
158       if (access(model_cache.str().c_str(), F_OK) == 0) {
159          buf = read_buf(model_cache.str().c_str(), &buf_size);
160       }
161    }
162 
163    if (buf == 0) {
164       buf = conv2d_generate_model(input_size, weight_size,
165                                   input_channels, output_channels,
166                                   stride, padding_same, is_signed,
167                                   depthwise,
168                                   &buf_size);
169 
170       if (cache_is_enabled()) {
171          if (access(cache_dir.str().c_str(), F_OK) != 0) {
172             ASSERT_TRUE(std::filesystem::create_directories(cache_dir.str().c_str()));
173          }
174          std::ofstream file(model_cache.str().c_str(), std::ios::out | std::ios::binary);
175          file.write(reinterpret_cast<const char *>(buf), buf_size);
176          file.close();
177       }
178    }
179 
180    test_model(buf, buf_size, cache_dir.str(), TOLERANCE);
181    free(buf);
182 }
183 
184 void
test_add(int input_size,int weight_size,int input_channels,int output_channels,int stride,bool padding_same,bool is_signed,bool depthwise,int seed,unsigned tolerance)185 test_add(int input_size, int weight_size, int input_channels, int output_channels,
186          int stride, bool padding_same, bool is_signed, bool depthwise, int seed,
187          unsigned tolerance)
188 {
189    void *buf = NULL;
190    size_t buf_size;
191    std::ostringstream cache_dir, model_cache;
192    cache_dir << "/var/cache/teflon_tests/"
193              << "add_" << input_size << "_" << weight_size << "_" << input_channels << "_" << output_channels << "_" << stride << "_" << padding_same << "_" << is_signed << "_" << depthwise << "_" << seed;
194    model_cache << cache_dir.str() << "/"
195                << "model.tflite";
196 
197    if (weight_size > input_size)
198       GTEST_SKIP();
199 
200    set_seed(seed);
201 
202    if (cache_is_enabled()) {
203       if (access(model_cache.str().c_str(), F_OK) == 0) {
204          buf = read_buf(model_cache.str().c_str(), &buf_size);
205       }
206    }
207 
208    if (buf == 0) {
209       buf = add_generate_model(input_size, weight_size,
210                                input_channels, output_channels,
211                                stride, padding_same, is_signed,
212                                depthwise,
213                                &buf_size);
214 
215       if (cache_is_enabled()) {
216          if (access(cache_dir.str().c_str(), F_OK) != 0) {
217             ASSERT_TRUE(std::filesystem::create_directories(cache_dir.str().c_str()));
218          }
219          std::ofstream file(model_cache.str().c_str(), std::ios::out | std::ios::binary);
220          file.write(reinterpret_cast<const char *>(buf), buf_size);
221          file.close();
222       }
223    }
224 
225    test_model(buf, buf_size, cache_dir.str(), tolerance);
226    free(buf);
227 }
228 
229 void
test_fully_connected(int input_size,int output_channels,bool is_signed,int seed)230 test_fully_connected(int input_size, int output_channels, bool is_signed, int seed)
231 {
232    void *buf = NULL;
233    size_t buf_size;
234    std::ostringstream cache_dir, model_cache;
235    cache_dir << "/var/cache/teflon_tests/fc_" << input_size << "_" << output_channels << "_" << is_signed << "_" << seed;
236    model_cache << cache_dir.str() << "/"
237                << "model.tflite";
238 
239    set_seed(seed);
240 
241    if (cache_is_enabled()) {
242       if (access(model_cache.str().c_str(), F_OK) == 0) {
243          buf = read_buf(model_cache.str().c_str(), &buf_size);
244       }
245    }
246 
247    if (buf == 0) {
248       buf = fully_connected_generate_model(input_size, output_channels, is_signed, &buf_size);
249 
250       if (cache_is_enabled()) {
251          if (access(cache_dir.str().c_str(), F_OK) != 0) {
252             ASSERT_TRUE(std::filesystem::create_directories(cache_dir.str().c_str()));
253          }
254          std::ofstream file(model_cache.str().c_str(), std::ios::out | std::ios::binary);
255          file.write(reinterpret_cast<const char *>(buf), buf_size);
256          file.close();
257       }
258    }
259 
260    test_model(buf, buf_size, cache_dir.str(), TOLERANCE);
261    free(buf);
262 }
263 
264 #if TEST_CONV2D
265 
266 class Conv2D : public testing::TestWithParam<std::tuple<bool, bool, int, int, int, int, int>> {};
267 
TEST_P(Conv2D,Op)268 TEST_P(Conv2D, Op)
269 {
270    test_conv(std::get<6>(GetParam()),
271              std::get<5>(GetParam()),
272              std::get<4>(GetParam()),
273              std::get<3>(GetParam()),
274              std::get<2>(GetParam()),
275              std::get<1>(GetParam()),
276              std::get<0>(GetParam()),
277              false, /* depthwise */
278              4);
279 }
280 
281 static inline std::string
Conv2DTestCaseName(const testing::TestParamInfo<std::tuple<bool,bool,int,int,int,int,int>> & info)282 Conv2DTestCaseName(
283    const testing::TestParamInfo<std::tuple<bool, bool, int, int, int, int, int>> &info)
284 {
285    std::string name = "";
286 
287    name += "input_size_" + std::to_string(std::get<6>(info.param));
288    name += "_weight_size_" + std::to_string(std::get<5>(info.param));
289    name += "_input_channels_" + std::to_string(std::get<4>(info.param));
290    name += "_output_channels_" + std::to_string(std::get<3>(info.param));
291    name += "_stride_" + std::to_string(std::get<2>(info.param));
292    name += "_padding_same_" + std::to_string(std::get<1>(info.param));
293    name += "_is_signed_" + std::to_string(std::get<0>(info.param));
294 
295    return name;
296 }
297 
298 INSTANTIATE_TEST_SUITE_P(
299    , Conv2D,
300    ::testing::Combine(::testing::ValuesIn(is_signed),
301                       ::testing::ValuesIn(padding_same),
302                       ::testing::ValuesIn(stride),
303                       ::testing::ValuesIn(output_channels),
304                       ::testing::ValuesIn(input_channels),
305                       ::testing::ValuesIn(weight_size),
306                       ::testing::ValuesIn(input_size)),
307    Conv2DTestCaseName);
308 
309 #endif
310 
311 #if TEST_DEPTHWISE
312 
313 class DepthwiseConv2D : public testing::TestWithParam<std::tuple<bool, bool, int, int, int, int>> {};
314 
TEST_P(DepthwiseConv2D,Op)315 TEST_P(DepthwiseConv2D, Op)
316 {
317    test_conv(std::get<5>(GetParam()),
318              std::get<4>(GetParam()),
319              std::get<3>(GetParam()),
320              std::get<3>(GetParam()),
321              std::get<2>(GetParam()),
322              std::get<1>(GetParam()),
323              std::get<0>(GetParam()),
324              true, /* depthwise */
325              4);
326 }
327 
328 static inline std::string
DepthwiseConv2DTestCaseName(const testing::TestParamInfo<std::tuple<bool,bool,int,int,int,int>> & info)329 DepthwiseConv2DTestCaseName(
330    const testing::TestParamInfo<std::tuple<bool, bool, int, int, int, int>> &info)
331 {
332    std::string name = "";
333 
334    name += "input_size_" + std::to_string(std::get<5>(info.param));
335    name += "_weight_size_" + std::to_string(std::get<4>(info.param));
336    name += "_channels_" + std::to_string(std::get<3>(info.param));
337    name += "_stride_" + std::to_string(std::get<2>(info.param));
338    name += "_padding_same_" + std::to_string(std::get<1>(info.param));
339    name += "_is_signed_" + std::to_string(std::get<0>(info.param));
340 
341    return name;
342 }
343 
344 INSTANTIATE_TEST_SUITE_P(
345    , DepthwiseConv2D,
346    ::testing::Combine(::testing::ValuesIn(is_signed),
347                       ::testing::ValuesIn(padding_same),
348                       ::testing::ValuesIn(stride),
349                       ::testing::ValuesIn(dw_channels),
350                       ::testing::ValuesIn(dw_weight_size),
351                       ::testing::ValuesIn(input_size)),
352    DepthwiseConv2DTestCaseName);
353 
354 #endif
355 
356 #if TEST_ADD
357 
358 class Add : public testing::TestWithParam<std::tuple<bool, bool, int, int, int, int, int>> {};
359 
TEST_P(Add,Op)360 TEST_P(Add, Op)
361 {
362    test_add(std::get<6>(GetParam()),
363             std::get<5>(GetParam()),
364             std::get<4>(GetParam()),
365             std::get<3>(GetParam()),
366             std::get<2>(GetParam()),
367             std::get<1>(GetParam()),
368             std::get<0>(GetParam()),
369             false, /* depthwise */
370             4,
371             TOLERANCE);
372 }
373 
374 static inline std::string
AddTestCaseName(const testing::TestParamInfo<std::tuple<bool,bool,int,int,int,int,int>> & info)375 AddTestCaseName(
376    const testing::TestParamInfo<std::tuple<bool, bool, int, int, int, int, int>> &info)
377 {
378    std::string name = "";
379 
380    name += "input_size_" + std::to_string(std::get<6>(info.param));
381    name += "_weight_size_" + std::to_string(std::get<5>(info.param));
382    name += "_input_channels_" + std::to_string(std::get<4>(info.param));
383    name += "_output_channels_" + std::to_string(std::get<3>(info.param));
384    name += "_stride_" + std::to_string(std::get<2>(info.param));
385    name += "_padding_same_" + std::to_string(std::get<1>(info.param));
386    name += "_is_signed_" + std::to_string(std::get<0>(info.param));
387 
388    return name;
389 }
390 
391 INSTANTIATE_TEST_SUITE_P(
392    , Add,
393    ::testing::Combine(::testing::ValuesIn(is_signed),
394                       ::testing::ValuesIn(padding_same),
395                       ::testing::ValuesIn(stride),
396                       ::testing::ValuesIn(output_channels),
397                       ::testing::ValuesIn(input_channels),
398                       ::testing::ValuesIn(weight_size),
399                       ::testing::ValuesIn(input_size)),
400    AddTestCaseName);
401 
402 class AddQuant : public testing::TestWithParam<int> {};
403 
TEST_P(AddQuant,Op)404 TEST_P(AddQuant, Op)
405 {
406    test_add(40,
407             1,
408             1,
409             1,
410             1,
411             false, /* padding_same */
412             false, /* is_signed */
413             false, /* depthwise */
414             GetParam(),
415             QUANT_TOLERANCE);
416 }
417 
418 INSTANTIATE_TEST_SUITE_P(
419    , AddQuant,
420    ::testing::Range(0, 100));
421 
422 #endif
423 
424 #if TEST_FULLY_CONNECTED
425 
426 class FullyConnected : public testing::TestWithParam<std::tuple<bool, int, int>> {};
427 
TEST_P(FullyConnected,Op)428 TEST_P(FullyConnected, Op)
429 {
430    test_fully_connected(
431              std::get<2>(GetParam()),
432              std::get<1>(GetParam()),
433              std::get<0>(GetParam()),
434              4);
435 }
436 
437 static inline std::string
FullyConnectedTestCaseName(const testing::TestParamInfo<std::tuple<bool,int,int>> & info)438 FullyConnectedTestCaseName(
439    const testing::TestParamInfo<std::tuple<bool, int, int>> &info)
440 {
441    std::string name = "";
442 
443    name += "input_size_" + std::to_string(std::get<2>(info.param));
444    name += "_output_channels_" + std::to_string(std::get<1>(info.param));
445    name += "_is_signed_" + std::to_string(std::get<0>(info.param));
446 
447    return name;
448 }
449 
450 INSTANTIATE_TEST_SUITE_P(
451    , FullyConnected,
452    ::testing::Combine(::testing::ValuesIn(is_signed),
453                       ::testing::ValuesIn(output_channels),
454                       ::testing::ValuesIn(fc_size)),
455    FullyConnectedTestCaseName);
456 
457 #endif
458 
459 #if TEST_MOBILENETV1
460 
461 class MobileNetV1 : public ::testing::Test {};
462 
463 class MobileNetV1Param : public testing::TestWithParam<int> {};
464 
TEST(MobileNetV1,Whole)465 TEST(MobileNetV1, Whole)
466 {
467    std::ostringstream file_path;
468    assert(getenv("TEFLON_TEST_DATA"));
469    file_path << getenv("TEFLON_TEST_DATA") << "/mobilenet_v1_1.0_224_quant.tflite";
470 
471    test_model_file(file_path.str(), MODEL_TOLERANCE, true);
472 }
473 
TEST_P(MobileNetV1Param,Op)474 TEST_P(MobileNetV1Param, Op)
475 {
476    std::ostringstream file_path;
477    assert(getenv("TEFLON_TEST_DATA"));
478    file_path << getenv("TEFLON_TEST_DATA") << "/mb-" << std::setfill('0') << std::setw(3) << GetParam() << ".tflite";
479 
480    test_model_file(file_path.str(), MODEL_TOLERANCE, true);
481 }
482 
483 static inline std::string
MobileNetV1TestCaseName(const testing::TestParamInfo<int> & info)484 MobileNetV1TestCaseName(
485    const testing::TestParamInfo<int> &info)
486 {
487    std::string name = "";
488    std::string param = std::to_string(info.param);
489 
490    name += "mb";
491    name += std::string(3 - param.length(), '0');
492    name += param;
493 
494    return name;
495 }
496 
497 INSTANTIATE_TEST_SUITE_P(
498    , MobileNetV1Param,
499    ::testing::Range(0, 31),
500    MobileNetV1TestCaseName);
501 
502 #endif
503 
504 #if TEST_MOBILEDET
505 
506 class MobileDet : public ::testing::Test {};
507 
508 class MobileDetParam : public testing::TestWithParam<int> {};
509 
TEST(MobileDet,Whole)510 TEST(MobileDet, Whole)
511 {
512    std::ostringstream file_path;
513    assert(getenv("TEFLON_TEST_DATA"));
514    file_path << getenv("TEFLON_TEST_DATA") << "/ssdlite_mobiledet_coco_qat_postprocess.tflite";
515 
516    test_model_file(file_path.str(), MODEL_TOLERANCE, true);
517 }
518 
TEST_P(MobileDetParam,Op)519 TEST_P(MobileDetParam, Op)
520 {
521    std::ostringstream file_path;
522    assert(getenv("TEFLON_TEST_DATA"));
523    file_path << getenv("TEFLON_TEST_DATA") << "/mobiledet-" << std::setfill('0') << std::setw(3) << GetParam() << ".tflite";
524 
525    test_model_file(file_path.str(), MODEL_TOLERANCE, true);
526 }
527 
528 static inline std::string
MobileDetTestCaseName(const testing::TestParamInfo<int> & info)529 MobileDetTestCaseName(
530    const testing::TestParamInfo<int> &info)
531 {
532    std::string name = "";
533    std::string param = std::to_string(info.param);
534 
535    name += "mobiledet";
536    name += std::string(3 - param.length(), '0');
537    name += param;
538 
539    return name;
540 }
541 
542 INSTANTIATE_TEST_SUITE_P(
543    , MobileDetParam,
544    ::testing::Range(0, 124),
545    MobileDetTestCaseName);
546 
547 #endif
548 
549 #if TEST_YOLOX
550 
551 class YoloX : public ::testing::Test {};
552 
553 class YoloXParam : public testing::TestWithParam<int> {};
554 
TEST(YoloX,Whole)555 TEST(YoloX, Whole)
556 {
557    std::ostringstream file_path;
558    assert(getenv("TEFLON_TEST_DATA"));
559    file_path << getenv("TEFLON_TEST_DATA") << "/yolox.tflite";
560 
561    test_model_file(file_path.str(), YOLOX_TOLERANCE, true);
562 }
563 
TEST_P(YoloXParam,Op)564 TEST_P(YoloXParam, Op)
565 {
566    std::ostringstream file_path;
567    assert(getenv("TEFLON_TEST_DATA"));
568    file_path << getenv("TEFLON_TEST_DATA") << "/yolox-" << std::setfill('0') << std::setw(3) << GetParam() << ".tflite";
569 
570    test_model_file(file_path.str(), MODEL_TOLERANCE, true);
571 }
572 
573 static inline std::string
YoloXTestCaseName(const testing::TestParamInfo<int> & info)574 YoloXTestCaseName(
575    const testing::TestParamInfo<int> &info)
576 {
577    std::string name = "";
578    std::string param = std::to_string(info.param);
579 
580    name += "yolox";
581    name += std::string(3 - param.length(), '0');
582    name += param;
583 
584    return name;
585 }
586 
587 INSTANTIATE_TEST_SUITE_P(
588    , YoloXParam,
589    ::testing::Range(0, 128),
590    YoloXTestCaseName);
591 
592 #endif
593 
594 int
main(int argc,char ** argv)595 main(int argc, char **argv)
596 {
597    if (argc > 1 && !strcmp(argv[1], "generate_model")) {
598       void *buf = NULL;
599       size_t buf_size;
600 
601       assert(argc == 11);
602 
603       std::cout << "Generating model to ./model.tflite\n";
604 
605       int n = 2;
606       int input_size = atoi(argv[n++]);
607       int weight_size = atoi(argv[n++]);
608       int input_channels = atoi(argv[n++]);
609       int output_channels = atoi(argv[n++]);
610       int stride = atoi(argv[n++]);
611       int padding_same = atoi(argv[n++]);
612       int is_signed = atoi(argv[n++]);
613       int depthwise = atoi(argv[n++]);
614       int seed = atoi(argv[n++]);
615 
616       set_seed(seed);
617 
618       buf = conv2d_generate_model(input_size, weight_size,
619                                   input_channels, output_channels,
620                                   stride, padding_same, is_signed,
621                                   depthwise, &buf_size);
622 
623       int fd = open("model.tflite", O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
624       write(fd, buf, buf_size);
625       close(fd);
626 
627       return 0;
628    } else if (argc > 1 && !strcmp(argv[1], "run_model")) {
629       test_model_file(std::string(argv[2]), MODEL_TOLERANCE, false);
630    } else {
631       testing::InitGoogleTest(&argc, argv);
632       return RUN_ALL_TESTS();
633    }
634 }
635