char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
a2878d30d
char/aliceai-t5-moe
1// test-fusion: verify the backend fusion logic against a per-device baseline. 2// 3// for every dummy model generated by test-llama-archs, the tool runs the model on a single 4// device with fusion enabled and disabled, and reports: 5// - the per-fusion-type counters for each mode (prefill / decode, merged into "any" when the 6// per-graph counts match) 7// - the NMSE between the fused and unfused logits 8// - the NMSE between the device and a CPU reference 9// 10// the per-fusion-type counters are compared against a per-device baseline file (CSV) so a 11// fusion pattern that silently stops matching (or fires when it should not) is caught as a 12// regression. 13// 14// usage: 15// test-fusion --models DIR --device MTL0 --record baseline.csv # generate a baseline 16// test-fusion --models DIR --device MTL0 --check baseline.csv # validate against it 17// test-fusion --model FILE --device MTL0 --check baseline.csv # validate a single model 18 19#include "common.h" 20#include "log.h" 21#include "llama-cpp.h" 22 23#include "ggml.h" 24#include "gguf.h" 25 26#include <algorithm> 27#include <array> 28#include <cstring> 29#include <filesystem> 30#include <fstream> 31#include <iomanip> 32#include <iostream> 33#include <map> 34#include <random> 35#include <string> 36#include <vector> 37 38// generic fusion debugging API, resolved through the ad-hoc get_proc_address mechanism 39// (not part of the official ggml backend interface yet). a backend that adopts fusion debugging 40// exports these exact names. 41typedef void * ggml_backend_fusion_t ; 42 43typedef ggml_backend_fusion_t (* fusion_get_t ) (ggml_backend_dev_t ); 44typedef void (* fusion_stats_init_t ) (ggml_backend_fusion_t ); 45typedef void (* fusion_stats_reset_t ) (ggml_backend_fusion_t ); 46typedef int (* fusion_stats_get_t ) (ggml_backend_fusion_t ,const char ** ,uint64_t * ,int ); 47typedef void (* fusion_set_enabled_t ) (ggml_backend_fusion_t ,bool ); 48 49static bool silent_model_load_progress (float ,void * ) { 50return true; 51} 52 53struct gguf_context_ptr { 54gguf_context * ctx ; 55gguf_context_ptr (gguf_context * c ) :ctx (c ) {} 56 ~gguf_context_ptr () {if (ctx ) {gguf_free (ctx ); } } 57gguf_context * get ()const {return ctx ; } 58gguf_context_ptr (const gguf_context_ptr & )= delete ; 59gguf_context_ptr & operator= (const gguf_context_ptr & )= delete ; 60}; 61 62// NMSE between two vectors (same as tests/test-llama-archs.cpp) 63static double nmse (const std::vector < float > & a ,const std::vector < float > & b ) { 64GGML_ASSERT (a .size ()== b .size ()); 65double mse_a_b = 0.0 ; 66double mse_a_0 = 0.0 ; 67 68for (size_t i = 0 ;i < a .size ();i ++ ) { 69const float a_i = a [i ]; 70const float b_i = b [i ]; 71 72mse_a_b += (a_i - b_i )* (a_i - b_i ); 73mse_a_0 += a_i * a_i ; 74 } 75 76return mse_a_b /mse_a_0 ; 77} 78 79// deterministic token sequence 80static std::vector < llama_token > get_tokens (const uint32_t n_tokens ,const uint32_t n_vocab ,const size_t seed ) { 81 std::mt19937 gen (seed ); 82 std::uniform_int_distribution <> dis (0 ,n_vocab - 1 ); 83 std::vector < llama_token > ret ; 84ret .reserve (n_tokens ); 85for (uint32_t i = 0 ;i < n_tokens ;i ++ ) { 86ret .push_back (dis (gen )); 87 } 88return ret ; 89} 90 91// trim leading/trailing whitespace (used when parsing padded CSV columns) 92static std::string trim (const std::string & s ) { 93const size_t b = s .find_first_not_of (" \t\r\n" ); 94if (b == std::string::npos ) { 95return "" ; 96 } 97const size_t e = s .find_last_not_of (" \t\r\n" ); 98return s .substr (b ,e - b + 1 ); 99} 100 101static std::string get_arch (const std::string & path ) { 102gguf_init_params params = {/*no_alloc=*/ true,/*ctx=*/ nullptr }; 103gguf_context_ptr ctx (gguf_init_from_file (path .c_str (),params )); 104if (!ctx .get ()) { 105throw std::runtime_error ("failed to read gguf: " + path ); 106 } 107const int idx = gguf_find_key (ctx .get (),"general.architecture" ); 108if (idx < 0 ) { 109return "unknown" ; 110 } 111const char * val = gguf_get_val_str (ctx .get (),idx ); 112return val ?val :"unknown" ; 113} 114 115static llama_model_ptr load_model (const std::string & path ,ggml_backend_dev_t dev ) { 116llama_model_params model_params = llama_model_default_params (); 117model_params .progress_callback = silent_model_load_progress ; 118 std::vector < ggml_backend_dev_t > devs = {dev ,nullptr }; 119model_params .devices = devs .data (); 120model_params .split_mode = LLAMA_SPLIT_MODE_LAYER ; 121 122llama_model_ptr model (llama_model_load_from_file (path .c_str (),model_params )); 123if (!model ) { 124throw std::runtime_error ("failed to load model: " + path ); 125 } 126return model ; 127} 128 129// a fresh context (fresh state) from an already-loaded model 130static llama_context_ptr create_ctx (llama_model * model ,int n_ubatch ) { 131llama_context_params ctx_params = llama_context_default_params (); 132ctx_params .n_ctx = 0 ; 133ctx_params .n_threads = 4 ; 134ctx_params .n_threads_batch = 4 ; 135ctx_params .n_ubatch = n_ubatch ; 136ctx_params .n_batch = n_ubatch ; 137 138llama_context_ptr lctx (llama_init_from_model (model ,ctx_params )); 139if (!lctx ) { 140throw std::runtime_error ("failed to init context" ); 141 } 142return lctx ; 143} 144 145// decode all tokens in one batch; returns the logits of every token 146static std::vector < float > decode_prefill (llama_model * model ,llama_context * lctx ,const std::vector < llama_token > & tokens ) { 147const uint32_t n_vocab = llama_vocab_n_tokens (llama_model_get_vocab (model )); 148llama_batch batch = llama_batch_init (tokens .size (),0 ,1 ); 149for (size_t i = 0 ;i < tokens .size ();i ++ ) { 150common_batch_add (batch ,tokens [i ],i , {0 }, true); 151 } 152batch .n_tokens = tokens .size (); 153if (llama_decode (lctx ,batch )) { 154llama_batch_free (batch ); 155throw std::runtime_error ("prefill decode failed" ); 156 } 157 158 std::vector < float > ret ; 159ret .reserve (tokens .size ()* n_vocab ); 160for (size_t i = 0 ;i < tokens .size ();i ++ ) { 161const float * logits_ith = llama_get_logits_ith (lctx ,i ); 162for (uint32_t j = 0 ;j < n_vocab ;j ++ ) { 163ret .push_back (logits_ith [j ]); 164 } 165 } 166llama_batch_free (batch ); 167return ret ; 168} 169 170// decode one token at a time; returns the logits of the last token of each step 171static std::vector < float > decode_gen (llama_model * model ,llama_context * lctx ,const std::vector < llama_token > & tokens ) { 172const uint32_t n_vocab = llama_vocab_n_tokens (llama_model_get_vocab (model )); 173llama_batch batch = llama_batch_init (1 ,0 ,1 ); 174 std::vector < float > ret ; 175for (size_t i = 0 ;i < tokens .size ();i ++ ) { 176common_batch_clear (batch ); 177common_batch_add (batch ,tokens [i ],i , {0 }, true); 178if (llama_decode (lctx ,batch )) { 179llama_batch_free (batch ); 180throw std::runtime_error ("decode failed" ); 181 } 182const float * logits = llama_get_logits_ith (lctx ,0 ); 183for (uint32_t j = 0 ;j < n_vocab ;j ++ ) { 184ret .push_back (logits [j ]); 185 } 186 } 187llama_batch_free (batch ); 188return ret ; 189} 190 191static void read_counts (fusion_stats_get_t api_stats_get ,ggml_backend_fusion_t finfo , 192 std::vector < const char *> & labels , std::vector < uint64_t > & counts ) { 193const int n = api_stats_get (finfo ,nullptr ,nullptr ,0 ); 194labels .assign (n ,nullptr ); 195counts .assign (n ,0 ); 196api_stats_get (finfo ,labels .data (),counts .data (),n ); 197} 198 199// one row of the per-label report 200struct fusion_row { 201 std::string arch ; 202bool moe ; 203 std::string mode ; 204 std::string label ; 205uint64_t count_fused ; 206uint64_t count_unfused ; 207uint64_t expected ; 208double nmse_fus ; 209double nmse_dev ; 210bool ok_count ;// counts match the baseline 211bool ok_nmse ;// nmse within epsilon 212}; 213 214static void usage (const char * argv0 ) { 215printf ("%s: verify fusion counts on a device against a per-device baseline\n\n" ,argv0 ); 216printf ("usage: %s [options]\n\n" ,argv0 ); 217printf ("options:\n" ); 218printf (" --models DIR run over all .gguf models in a directory\n" ); 219printf (" --model FILE run over a single model file (mutually exclusive with --models)\n" ); 220printf (" --device NAME device to run on (e.g. MTL0, CPU)\n" ); 221printf (" --record CSV write the golden baseline\n" ); 222printf (" --check CSV validate the counters against a baseline (default)\n" ); 223printf (" -h, --help show this message and exit\n" ); 224} 225 226int main (int argc ,char ** argv ) { 227 std::string models_dir ; 228 std::string model_file ; 229 std::string device_name ; 230 std::string record_path ; 231 std::string check_path ; 232 233for (int i = 1 ;i < argc ;i ++ ) { 234const std::string arg = argv [i ]; 235const auto next = [& ](const char * name )-> std::string { 236if (i + 1 >=argc ) { 237LOG_ERR ("%s: %s requires an argument\n" ,__func__ ,name ); 238exit (1 ); 239 } 240return argv [++ i ]; 241 }; 242if (arg == "-h" || arg == "--help" ) { 243usage (argv [0 ]); 244exit (0 ); 245 } 246if (arg == "--models" ) {models_dir = next ("--models" ); } 247else if (arg == "--model" ) {model_file = next ("--model" ); } 248else if (arg == "--device" ){device_name = next ("--device" ); } 249else if (arg == "--record" ){record_path = next ("--record" ); } 250else if (arg == "--check" ) {check_path = next ("--check" ); } 251else { 252LOG_ERR ("%s: unknown argument: %s\n" ,__func__ ,arg .c_str ()); 253return 1 ; 254 } 255 } 256 257if (device_name .empty ()) { 258LOG_ERR ("%s: --device NAME is required\n" ,__func__ ); 259return 1 ; 260 } 261if (models_dir .empty ()&& model_file .empty ()) { 262LOG_ERR ("%s: --models DIR or --model FILE is required\n" ,__func__ ); 263return 1 ; 264 } 265if (!models_dir .empty ()&& !model_file .empty ()) { 266LOG_ERR ("%s: --models DIR and --model FILE are mutually exclusive\n" ,__func__ ); 267return 1 ; 268 } 269if (!record_path .empty ()&& !check_path .empty ()) { 270LOG_ERR ("%s: --record and --check are mutually exclusive\n" ,__func__ ); 271return 1 ; 272 } 273 274 std::vector < std::string > models ; 275if (!model_file .empty ()) { 276if (!std::filesystem::is_regular_file (model_file )) { 277LOG_ERR ("%s: model file '%s' does not exist\n" ,__func__ ,model_file .c_str ()); 278return 1 ; 279 } 280models .push_back (model_file ); 281 }else { 282if (!std::filesystem::exists (models_dir )|| !std::filesystem::is_directory (models_dir )) { 283LOG_ERR ("%s: models directory '%s' does not exist\n" ,__func__ ,models_dir .c_str ()); 284return 1 ; 285 } 286for (const auto & entry : std::filesystem::directory_iterator (models_dir )) { 287if (entry .is_regular_file ()&& entry .path ().extension ()== ".gguf" ) { 288models .push_back (entry .path ().string ()); 289 } 290 } 291 std::sort (models .begin (),models .end ()); 292 293if (models .empty ()) { 294LOG_ERR ("%s: no .gguf models found in '%s'\n" ,__func__ ,models_dir .c_str ()); 295return 1 ; 296 } 297 } 298 299common_init (); 300ggml_backend_load_all (); 301 302ggml_backend_dev_t dev = ggml_backend_dev_by_name (device_name .c_str ()); 303if (!dev ) { 304LOG_WRN ("%s: device '%s' not found - skipping (baseline is device-specific)\n" , 305__func__ ,device_name .c_str ()); 306return 0 ; 307 } 308 309// resolve the generic fusion debugging functions through the ad-hoc get_proc_address 310// mechanism; a backend that does not adopt fusion debugging exports none of them 311auto * reg = ggml_backend_dev_backend_reg (dev ); 312 313// output naming uses the backend base name (e.g. "MTL") rather than the specific device 314// name (e.g. "MTL0") the test was invoked with 315const std::string base_name = ggml_backend_reg_name (reg ); 316 317auto api_get = (fusion_get_t )ggml_backend_reg_get_proc_address (reg ,"ggml_backend_fusion_get" ); 318auto api_stats_init = (fusion_stats_init_t )ggml_backend_reg_get_proc_address (reg ,"ggml_backend_fusion_stats_init" ); 319auto api_stats_reset = (fusion_stats_reset_t )ggml_backend_reg_get_proc_address (reg ,"ggml_backend_fusion_stats_reset" ); 320auto api_stats_get = (fusion_stats_get_t )ggml_backend_reg_get_proc_address (reg ,"ggml_backend_fusion_stats_get" ); 321auto api_set_enabled = (fusion_set_enabled_t )ggml_backend_reg_get_proc_address (reg ,"ggml_backend_fusion_set_enabled" ); 322 323if (!api_get || !api_stats_init || !api_set_enabled || !api_stats_reset || !api_stats_get ) { 324LOG_ERR ("%s: device '%s' does not export the generic fusion debugging API " 325"(ggml_backend_fusion_*) - cannot run the fusion regression test\n" , 326__func__ ,device_name .c_str ()); 327return 1 ; 328 } 329 330ggml_backend_fusion_t finfo = api_get (dev ); 331 332// enable fusions stats 333api_stats_init (finfo ); 334 335const bool has_counts = true; 336 337// load the baseline (if any): key arch|moe|mode|label -> expected count 338 std::map < std::string ,uint64_t > baseline ; 339if (!check_path .empty ()) { 340 std::ifstream in (check_path ); 341if (!in ) { 342LOG_ERR ("%s: cannot open baseline '%s'\n" ,__func__ ,check_path .c_str ()); 343return 1 ; 344 } 345 std::string line ; 346while (std::getline (in ,line )) { 347if (line .empty ()|| line [0 ]== '#' ) { 348continue ; 349 } 350 std::vector < std::string > cols ; 351size_t pos = 0 ; 352while ((pos = line .find (',' ))!= std::string::npos ) { 353cols .push_back (trim (line .substr (0 ,pos ))); 354line .erase (0 ,pos + 1 ); 355 } 356cols .push_back (trim (line )); 357if (cols .size ()!= 5 ) { 358continue ; 359 } 360baseline [cols [0 ]+ "|" + cols [1 ]+ "|" + cols [2 ]+ "|" + cols [3 ]]= std::stoull (cols [4 ]); 361 } 362 } 363 364 std::vector < fusion_row > rows ; 365 366LOG_INF ("%s: running fusion test over %zu models on '%s'\n" ,__func__ ,models .size (),base_name .c_str ()); 367 368const size_t seed = 1 ; 369 370for (const auto & model_path :models ) { 371const std::string arch = get_arch (model_path ); 372const bool moe = arch .find ("moe" )!= std::string::npos ; 373 374llama_model_ptr model ; 375llama_model_ptr model_cpu ; 376uint32_t n_vocab = 0 ; 377try { 378model = load_model (model_path ,dev ); 379model_cpu = load_model (model_path ,ggml_backend_dev_by_name ("CPU" )); 380n_vocab = llama_vocab_n_tokens (llama_model_get_vocab (model .get ())); 381 }catch (const std::exception & e ) { 382LOG_ERR ("%s: %s: %s\n" ,__func__ ,model_path .c_str (),e .what ()); 383continue ; 384 } 385 386struct mode_cfg { 387 std::string name ; 388 std::vector < float > (* decode )(llama_model * ,llama_context * ,const std::vector < llama_token > & ); 389int n_tokens ; 390int n_graphs ;// graph runs per mode (prefill=1, decode=16) 391 }; 392const mode_cfg modes []= { 393 {"prefill" ,decode_prefill ,32 ,1 }, 394 {"decode" ,decode_gen ,16 ,16 }, 395 }; 396 397// per-label, per-mode data for this model; prefill and decode are merged into a single 398// "any" row when their per-graph counts match 399struct mode_data { 400bool present ; 401uint64_t count_fused ;// per graph 402uint64_t count_unfused ;// per graph 403double nmse_fus ; 404double nmse_dev ; 405bool ok_nmse ; 406 }; 407 std::map < std::string , std::array < mode_data ,2 >> mdata ; 408 409for (int mi = 0 ;mi < 2 ;mi ++ ) { 410const mode_cfg & mode = modes [mi ]; 411const auto tokens = get_tokens (mode .n_tokens ,n_vocab ,seed ); 412 413// CPU reference for this mode (fresh context, fresh state) 414 std::vector < float > logits_cpu ; 415try { 416llama_context_ptr ctx = create_ctx (model_cpu .get (),32 ); 417logits_cpu = mode .decode (model_cpu .get (),ctx .get (),tokens ); 418 }catch (const std::exception & e ) { 419LOG_WRN ("%s: %s: cpu reference: %s\n" ,__func__ ,model_path .c_str (),e .what ()); 420 } 421 422// fused run on a fresh context (fresh state) 423 std::vector < float > logits_fused ; 424 std::vector < const char *> labels ; 425 std::vector < uint64_t > counts_fused ; 426 { 427llama_context_ptr ctx = create_ctx (model .get (),32 ); 428if (has_counts ) { 429api_set_enabled (finfo , true); 430api_stats_reset (finfo ); 431 } 432logits_fused = mode .decode (model .get (),ctx .get (),tokens ); 433if (has_counts ) { 434read_counts (api_stats_get ,finfo ,labels ,counts_fused ); 435 } 436 } 437 438// unfused run on another fresh context (fresh state) 439 std::vector < float > logits_unfused ; 440 std::vector < uint64_t > counts_unfused ; 441 { 442llama_context_ptr ctx = create_ctx (model .get (),32 ); 443if (has_counts ) { 444api_set_enabled (finfo , false); 445api_stats_reset (finfo ); 446 } 447logits_unfused = mode .decode (model .get (),ctx .get (),tokens ); 448if (has_counts ) { 449read_counts (api_stats_get ,finfo ,labels ,counts_unfused ); 450 } 451 } 452 453const double nmse_fus = nmse (logits_fused ,logits_unfused ); 454const double nmse_dev = logits_cpu .empty () ?0.0 :nmse (logits_fused ,logits_cpu ); 455 456if (has_counts ) { 457for (int i = 0 ;i < (int )labels .size ();i ++ ) { 458const uint64_t fused = counts_fused [i ] /mode .n_graphs ; 459const uint64_t unfused = counts_unfused [i ] /mode .n_graphs ; 460if (fused == 0 && unfused == 0 ) { 461continue ; 462 } 463auto & d = mdata [labels [i ]][mi ]; 464d .present = true; 465d .count_fused = fused ; 466d .count_unfused = unfused ; 467d .nmse_fus = nmse_fus ; 468d .nmse_dev = nmse_dev ; 469d .ok_nmse = nmse_fus <=1e-4 ; 470 } 471 }else { 472rows .push_back ({arch ,moe ,mode .name ,"?" ,0 ,0 ,0 ,nmse_fus ,nmse_dev , true,nmse_fus <=1e-4 }); 473 } 474 } 475 476// build the per-label rows, merging prefill and decode into "any" when the per-graph 477// counts match (they always do for the deterministic fusion table) 478if (has_counts ) { 479for (auto & kv :mdata ) { 480const std::string & label = kv .first ; 481const auto & d = kv .second ; 482const bool both = d [0 ].present && d [1 ].present ; 483const bool match = both && d [0 ].count_fused == d [1 ].count_fused ; 484 485if (match ) { 486// one "any" row; use the worst NMSE across the two modes 487const std::string any_key = arch + "|" + (moe ?"1" :"0" )+ "|any|" + label ; 488const uint64_t expected = baseline .count (any_key ) ?baseline .at (any_key ) :0 ; 489const bool ok_count = check_path .empty ()|| d [0 ].count_fused == expected ; 490const bool ok_nmse = d [0 ].ok_nmse && d [1 ].ok_nmse ; 491const double nmse_fus = std::max (d [0 ].nmse_fus ,d [1 ].nmse_fus ); 492const double nmse_dev = std::max (d [0 ].nmse_dev ,d [1 ].nmse_dev ); 493rows .push_back ({arch ,moe ,"any" ,label ,d [0 ].count_fused ,d [0 ].count_unfused , 494expected ,nmse_fus ,nmse_dev ,ok_count ,ok_nmse }); 495 }else { 496// counts differ - keep a separate row per mode 497for (int mi = 0 ;mi < 2 ;mi ++ ) { 498if (!d [mi ].present ) { 499continue ; 500 } 501const mode_data & a = d [mi ]; 502const std::string mode_key = arch + "|" + (moe ?"1" :"0" )+ "|" + modes [mi ].name + "|" + label ; 503const uint64_t expected = baseline .count (mode_key ) ?baseline .at (mode_key ) :0 ; 504const bool ok_count = check_path .empty ()|| a .count_fused == expected ; 505rows .push_back ({arch ,moe ,modes [mi ].name ,label ,a .count_fused ,a .count_unfused , 506expected ,a .nmse_fus ,a .nmse_dev ,ok_count ,a .ok_nmse }); 507 } 508 } 509 } 510 } 511 512LOG_INF ("%s: %-20s (%s) done\n" ,__func__ ,arch .c_str (),model_path .c_str ()); 513 } 514 515// print the report 516 { 517 std::ofstream out (record_path ); 518 std::ostream & os = record_path .empty () ? std::cout :out ; 519if (!record_path .empty ()) { 520os <<"# test-fusion baseline for device " <<base_name <<"\n" ; 521os <<"# " << std::left 522 << std::setw (18 ) <<"arch" <<',' 523 << std::setw (4 ) <<"moe" <<',' 524 << std::setw (8 ) <<"mode" <<',' 525 << std::setw (28 ) <<"label" <<',' 526 << std::right << std::setw (7 ) <<"count" <<'\n' ; 527 } 528 529LOG_INF ("%-20s %-4s %-8s %-22s %7s %7s %7s %10s %10s %s\n" , 530"arch" ,"moe" ,"mode" ,"label" ,"fused" ,"unfused" ,"expected" ,"nmse_fus" ,"nmse_dev" ,"status" ); 531int n_ok = 0 ; 532int n_bad = 0 ; 533for (const auto & r :rows ) { 534const bool ok = r .ok_count && r .ok_nmse ; 535const char * status = ok ?"ok" :"FAIL" ; 536if (ok ) {n_ok ++ ; }else {n_bad ++ ; } 537LOG_INF ("%-20s %-4s %-8s %-22s %7llu %7llu %7llu %10.2e %10.2e %s\n" , 538r .arch .c_str (),r .moe ?"moe" :"dense" ,r .mode .c_str (),r .label .c_str (), 539 (unsigned long long )r .count_fused , (unsigned long long )r .count_unfused , 540 (unsigned long long )r .expected ,r .nmse_fus ,r .nmse_dev ,status ); 541if (!record_path .empty ()) { 542os << std::left 543 << std::setw (20 ) <<r .arch <<',' 544 << std::setw (4 ) << (r .moe ?"1" :"0" ) <<',' 545 << std::setw (8 ) <<r .mode <<',' 546 << std::setw (28 ) <<r .label <<',' 547 << std::right << std::setw (7 ) <<r .count_fused <<'\n' ; 548 } 549 } 550LOG_INF ("summary: %d ok, %d failed\n" ,n_ok ,n_bad ); 551if (!record_path .empty ()) { 552LOG_INF ("%s: baseline written to '%s'\n" ,__func__ ,record_path .c_str ()); 553 } 554 555if (n_bad && !models_dir .empty ()&& !check_path .empty ()) { 556LOG_WRN ("%s: if the fusion counts are expected to change, run with --record to update the baseline:\n" 557"\n" 558"./bin/test-llama-archs -o %s\n" 559"%s --device %s --models %s --record %s\n" , 560__func__ ,models_dir .c_str (),argv [0 ],device_name .c_str (),models_dir .c_str (),check_path .c_str ()); 561 } 562 563return n_bad ; 564 } 565}