char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
5f754ea0e
char/aliceai-t5-moe
1#include "arg.h" 2#include "preset.h" 3#include "peg-parser.h" 4#include "log.h" 5#include "download.h" 6 7#include <fstream> 8#include <sstream> 9#include <filesystem> 10#include <regex> 11 12static std::string rm_leading_dashes (const std::string & str ) { 13size_t pos = 0 ; 14while (pos < str .size ()&& str [pos ]== '-' ) { 15++ pos ; 16 } 17return str .substr (pos ); 18} 19 20static std::string canonical_tag (const std::string & tag ) { 21static const std::regex re_tag ("[-.]([A-Z0-9_]+)$" , std::regex::icase ); 22 std::smatch m ; 23if (std::regex_search (tag ,m ,re_tag )) { 24 std::string canon = m [1 ].str (); 25for (char & c :canon ) { 26c = (char ) std::toupper ((unsigned char )c ); 27 } 28return canon ; 29 } 30 std::string upper = tag ; 31for (char & c :upper ) { 32c = (char ) std::toupper ((unsigned char )c ); 33 } 34return upper ; 35} 36 37std::vector < std::string > common_preset::to_args (const std::string & bin_path )const { 38 std::vector < std::string > args ; 39 40if (!bin_path .empty ()) { 41args .push_back (bin_path ); 42 } 43 44for (const auto & [opt ,value ] :options ) { 45if (opt .is_preset_only ) { 46continue ;// skip preset-only options (they are not CLI args) 47 } 48 49// use the last arg as the main arg (i.e. --long-form) 50args .push_back (opt .args .back ()); 51 52// handle value(s) 53if (opt .value_hint == nullptr && opt .value_hint_2 == nullptr ) { 54// flag option, no value 55if (common_arg_utils::is_falsey (value )) { 56// use negative arg if available 57if (!opt .args_neg .empty ()) { 58args .back ()= opt .args_neg .back (); 59 }else { 60// otherwise, skip the flag 61// TODO: maybe throw an error instead? 62args .pop_back (); 63 } 64 } 65 } 66if (opt .value_hint != nullptr ) { 67// single value 68args .push_back (value ); 69 } 70if (opt .value_hint != nullptr && opt .value_hint_2 != nullptr ) { 71throw std::runtime_error (string_format ( 72"common_preset::to_args(): option '%s' has two values, which is not supported yet" , 73opt .args .back () 74 )); 75 } 76 } 77 78return args ; 79} 80 81std::string common_preset::to_ini ()const { 82 std::ostringstream ss ; 83 84ss <<"[" <<name <<"]\n" ; 85for (const auto & [opt ,value ] :options ) { 86auto espaced_value = value ; 87string_replace_all (espaced_value ,"\n" ,"\\\n" ); 88ss <<rm_leading_dashes (opt .args .back ()) <<" = " ; 89ss <<espaced_value <<"\n" ; 90 } 91ss <<"\n" ; 92 93return ss .str (); 94} 95 96void common_preset::set_option (const common_preset_context & ctx ,const std::string & env ,const std::string & value ) { 97// try if option exists, update it 98for (auto & [opt ,val ] :options ) { 99if (opt .env && env == opt .env ) { 100val = value ; 101return ; 102 } 103 } 104// if option does not exist, we need to add it 105if (ctx .key_to_opt .find (env )== ctx .key_to_opt .end ()) { 106throw std::runtime_error (string_format ( 107"%s: option with env '%s' not found in ctx_params" , 108__func__ ,env .c_str () 109 )); 110 } 111options [ctx .key_to_opt .at (env )]= value ; 112} 113 114void common_preset::unset_option (const std::string & env ) { 115for (auto it = options .begin ();it != options .end (); ) { 116const common_arg & opt = it -> first ; 117if (opt .env && env == opt .env ) { 118it = options .erase (it ); 119return ; 120 }else { 121++ it ; 122 } 123 } 124} 125 126bool common_preset::get_option (const std::string & env , std::string & value )const { 127for (const auto & [opt ,val ] :options ) { 128if (opt .env && env == opt .env ) { 129value = val ; 130return true; 131 } 132 } 133return false; 134} 135 136void common_preset::merge (const common_preset & other ) { 137for (const auto & [opt ,val ] :other .options ) { 138options [opt ]= val ;// overwrite existing options 139 } 140} 141 142void common_preset::apply_to_params (common_params & params ,const std::set < std::string > & handled_keys )const { 143for (const auto & [opt ,val ] :options ) { 144if (!handled_keys .empty ()) { 145if (!opt .env || handled_keys .find (opt .env )== handled_keys .end ()) { 146continue ; 147 } 148 } 149// apply each option to params 150if (opt .handler_string ) { 151opt .handler_string (params ,val ); 152 }else if (opt .handler_int ) { 153opt .handler_int (params , std::stoi (val )); 154 }else if (opt .handler_bool ) { 155opt .handler_bool (params , common_arg_utils::is_truthy (val )); 156 }else if (opt .handler_str_str ) { 157// not supported yet 158throw std::runtime_error (string_format ( 159"%s: option with two values is not supported yet" , 160__func__ 161 )); 162 }else if (opt .handler_void ) { 163opt .handler_void (params ); 164 }else { 165GGML_ABORT ("unknown handler type" ); 166 } 167 } 168} 169 170static std::map < std::string , std::map < std::string , std::string >> parse_ini_from_file (const std::string & path ) { 171 std::map < std::string , std::map < std::string , std::string >> parsed ; 172 173if (!std::filesystem::exists (path )) { 174throw std::runtime_error ("preset file does not exist: " + path ); 175 } 176 177 std::ifstream file (path ); 178if (!file .good ()) { 179throw std::runtime_error ("failed to open server preset file: " + path ); 180 } 181 182 std::string contents ((std::istreambuf_iterator < char > (file )), std::istreambuf_iterator < char > ()); 183 184static const auto parser = build_peg_parser ([](auto & p ) { 185// newline ::= "\r\n" / "\n" / "\r" 186auto newline = p .rule ("newline" ,p .literal ("\r\n" ) |p .literal ("\n" ) |p .literal ("\r" )); 187 188// ws ::= [ \t]* 189auto ws = p .rule ("ws" ,p .chars ("[ \t]" ,0 ,-1 )); 190 191// comment ::= [;#] (!newline .)* 192auto comment = p .rule ("comment" ,p .chars ("[;#]" ,1 ,1 )+ p .zero_or_more (p .negate (newline )+ p .any ())); 193 194// eol ::= ws comment? (newline / EOF) 195auto eol = p .rule ("eol" ,ws + p .optional (comment )+ (newline |p .end ())); 196 197// ident ::= [a-zA-Z_] [a-zA-Z0-9_.-]* 198auto ident = p .rule ("ident" ,p .chars ("[a-zA-Z_]" ,1 ,1 )+ p .chars ("[a-zA-Z0-9_.-]" ,0 ,-1 )); 199 200// value ::= (!eol-start .)* 201auto eol_start = p .rule ("eol-start" ,ws + (p .chars ("[;#]" ,1 ,1 ) |newline |p .end ())); 202auto value = p .rule ("value" ,p .zero_or_more (p .negate (eol_start )+ p .any ())); 203 204// header-line ::= "[" ws ident ws "]" eol 205auto header_line = p .rule ("header-line" ,"[" + ws + p .tag ("section-name" ,p .chars ("[^]]" ))+ ws + "]" + eol ); 206 207// kv-line ::= ident ws "=" ws value eol 208auto kv_line = p .rule ("kv-line" ,p .tag ("key" ,ident )+ ws + "=" + ws + p .tag ("value" ,value )+ eol ); 209 210// comment-line ::= ws comment (newline / EOF) 211auto comment_line = p .rule ("comment-line" ,ws + comment + (newline |p .end ())); 212 213// blank-line ::= ws (newline / EOF) 214auto blank_line = p .rule ("blank-line" ,ws + (newline |p .end ())); 215 216// line ::= header-line / kv-line / comment-line / blank-line 217auto line = p .rule ("line" ,header_line |kv_line |comment_line |blank_line ); 218 219// ini ::= line* EOF 220auto ini = p .rule ("ini" ,p .zero_or_more (line )+ p .end ()); 221 222return ini ; 223 }); 224 225common_peg_parse_context ctx (contents ); 226const auto result = parser .parse (ctx ); 227if (!result .success ()) { 228throw std::runtime_error ("failed to parse server config file: " + path ); 229 } 230 231 std::string current_section = COMMON_PRESET_DEFAULT_NAME ; 232 std::string current_key ; 233 234ctx .ast .visit (result , [& ](const auto & node ) { 235if (node .tag == "section-name" ) { 236const std::string section = std::string (node .text ); 237current_section = section ; 238parsed [current_section ]= {}; 239 }else if (node .tag == "key" ) { 240const std::string key = std::string (node .text ); 241current_key = key ; 242 }else if (node .tag == "value" && !current_key .empty ()&& !current_section .empty ()) { 243parsed [current_section ][current_key ]= std::string (node .text ); 244current_key .clear (); 245 } 246 }); 247 248return parsed ; 249} 250 251static std::map < std::string ,common_arg > get_map_key_opt (common_params_context & ctx_params ) { 252 std::map < std::string ,common_arg > mapping ; 253for (const auto & opt :ctx_params .options ) { 254for (const auto & env :opt .get_env ()) { 255mapping [env ]= opt ; 256 } 257for (const auto & arg :opt .get_args ()) { 258mapping [rm_leading_dashes (arg )]= opt ; 259 } 260 } 261return mapping ; 262} 263 264static bool is_bool_arg (const common_arg & arg ) { 265return !arg .args_neg .empty (); 266} 267 268static std::string parse_bool_arg (const common_arg & arg ,const std::string & key ,const std::string & value ) { 269// if this is a negated arg, we need to reverse the value 270for (const auto & neg_arg :arg .args_neg ) { 271if (rm_leading_dashes (neg_arg )== key ) { 272return common_arg_utils::is_truthy (value ) ?"false" :"true" ; 273 } 274 } 275// otherwise, not negated 276return value ; 277} 278 279common_preset_context::common_preset_context (llama_example ex ) 280 :ctx_params (common_params_parser_init (default_params ,ex )) { 281common_params_add_preset_options (ctx_params .options ); 282key_to_opt = get_map_key_opt (ctx_params ); 283} 284 285common_presets common_preset_context::load_from_ini (const std::string & path ,common_preset & global )const { 286common_presets out ; 287auto ini_data = parse_ini_from_file (path ); 288 289for (auto section :ini_data ) { 290common_preset preset ; 291 std::string section_name = section .first .empty () ? std::string (COMMON_PRESET_DEFAULT_NAME ) :section .first ; 292if (section_name != "*" && section_name != COMMON_PRESET_DEFAULT_NAME ) { 293auto colon_idx = section_name .rfind (':' ); 294if (colon_idx != std::string::npos ) { 295 std::string tag = section_name .substr (colon_idx + 1 ); 296 std::string canon_tag = canonical_tag (tag ); 297if (canon_tag != tag ) { 298section_name = section_name .substr (0 ,colon_idx + 1 )+ canon_tag ; 299 } 300 } 301 } 302preset .name = section_name ; 303LOG_DBG ("loading preset: %s\n" ,preset .name .c_str ()); 304for (const auto & [key ,value ] :section .second ) { 305if (key == "version" ) { 306// skip version key (reserved for future use) 307continue ; 308 } 309 310LOG_DBG ("option: %s = %s\n" ,key .c_str (),value .c_str ()); 311if (filter_allowed_keys && allowed_keys .find (key )== allowed_keys .end ()) { 312throw std::runtime_error (string_format ( 313"option '%s' is not allowed in remote presets" , 314key .c_str () 315 )); 316 } 317if (key_to_opt .find (key )!= key_to_opt .end ()) { 318const auto & opt = key_to_opt .at (key ); 319if (is_bool_arg (opt )) { 320preset .options [opt ]= parse_bool_arg (opt ,key ,value ); 321 }else { 322preset .options [opt ]= value ; 323 } 324LOG_DBG ("accepted option: %s = %s\n" ,key .c_str (),preset .options [opt ].c_str ()); 325 }else if (ignore_unknown_keys ) { 326LOG_WRN ("ignoring option '%s' from %s: not supported by this program\n" ,key .c_str (),path .c_str ()); 327 }else { 328throw std::runtime_error (string_format ( 329"option '%s' not recognized in preset '%s'" , 330key .c_str (),preset .name .c_str () 331 )); 332 } 333 } 334 335if (preset .name == COMMON_PRESET_DEFAULT_NAME && preset .options .empty ()) { 336continue ; 337 } 338 339if (preset .name == "*" ) { 340// handle global preset 341global = preset ; 342 }else { 343out [preset .name ]= preset ; 344 } 345 } 346 347return out ; 348} 349 350common_presets common_preset_context::load_from_cache ()const { 351common_presets out ; 352 353auto cached_models = common_list_cached_models (); 354for (const auto & model :cached_models ) { 355common_preset preset ; 356preset .name = model .to_string (); 357preset .set_option (* this ,"LLAMA_ARG_HF_REPO" ,model .to_string ()); 358out [preset .name ]= preset ; 359 } 360 361return out ; 362} 363 364struct local_model { 365 std::string name ; 366 std::string path ; 367 std::string path_mmproj ; 368 std::string path_draft ; 369}; 370 371// TODO @ngxson: handle "eagle3-" when it's supported by common_speculative_types_from_gguf() 372static const char * draft_prefixes []= {"mtp-" ,"dspark-" ,"dflash-" }; 373 374static bool is_mmproj_file (const std::string & fname ) { 375return fname .find ("mmproj" )!= std::string::npos ; 376} 377 378static bool is_draft_file (const std::string & fname ) { 379for (const auto & prefix :draft_prefixes ) { 380if (fname .rfind (prefix ,0 )== 0 ) { 381return true; 382 } 383 } 384return false; 385} 386 387common_presets common_preset_context::load_from_models_dir (const std::string & models_dir )const { 388if (!std::filesystem::exists (models_dir )|| !std::filesystem::is_directory (models_dir )) { 389throw std::runtime_error (string_format ("error: '%s' does not exist or is not a directory\n" ,models_dir .c_str ())); 390 } 391 392 std::vector < local_model > models ; 393auto scan_subdir = [& models ](const std::string & subdir_path ,const std::string & name ) { 394auto files = fs_list (subdir_path , false); 395common_file_info model_file ; 396common_file_info first_shard_file ; 397common_file_info mmproj_file ; 398common_file_info draft_file ; 399for (const auto & file :files ) { 400if (string_ends_with (file .name ,".gguf" )) { 401if (is_mmproj_file (file .name )) { 402mmproj_file = file ; 403 }else if (is_draft_file (file .name )) { 404if (draft_file .path .empty ()) { 405draft_file = file ;// first sidecar found wins 406 } 407 }else if (file .name .find ("-00001-of-" )!= std::string::npos ) { 408first_shard_file = file ; 409 }else { 410model_file = file ; 411 } 412 } 413 } 414// single file model 415local_model model { 416/* name */ name , 417/* path */ first_shard_file .path .empty () ?model_file .path :first_shard_file .path , 418/* path_mmproj */ mmproj_file .path ,// can be empty 419/* path_draft */ draft_file .path // can be empty 420 }; 421if (!model .path .empty ()) { 422models .push_back (model ); 423 } 424 }; 425 426auto files = fs_list (models_dir , true); 427for (const auto & file :files ) { 428if (file .is_dir ) { 429scan_subdir (file .path ,file .name ); 430 }else if (string_ends_with (file .name ,".gguf" )) { 431if (is_mmproj_file (file .name )|| is_draft_file (file .name )) { 432continue ;// companion file, cannot be loaded as a model on its own 433 } 434// single file model 435 std::string name = file .name ; 436string_replace_all (name ,".gguf" ,"" ); 437local_model model { 438/* name */ name , 439/* path */ file .path , 440/* path_mmproj */ "" , 441/* path_draft */ "" 442 }; 443models .push_back (model ); 444 } 445 } 446 447// convert local models to presets 448common_presets out ; 449for (const auto & model :models ) { 450common_preset preset ; 451preset .name = model .name ; 452preset .set_option (* this ,"LLAMA_ARG_MODEL" ,model .path ); 453if (!model .path_mmproj .empty ()) { 454preset .set_option (* this ,"LLAMA_ARG_MMPROJ" ,model .path_mmproj ); 455 } 456if (!model .path_draft .empty ()) { 457preset .set_option (* this ,"LLAMA_ARG_SPEC_DRAFT_MODEL" ,model .path_draft ); 458 } 459out [preset .name ]= preset ; 460 } 461 462return out ; 463} 464 465common_preset common_preset_context::load_from_args (int argc ,char ** argv )const { 466common_preset preset ; 467preset .name = COMMON_PRESET_DEFAULT_NAME ; 468 469bool ok = common_params_to_map (argc ,argv ,ctx_params .ex ,preset .options ); 470if (!ok ) { 471throw std::runtime_error ("failed to parse CLI arguments into preset" ); 472 } 473 474return preset ; 475} 476 477common_presets common_preset_context::cascade (const common_presets & base ,const common_presets & added )const { 478common_presets out = base ;// copy 479for (const auto & [name ,preset_added ] :added ) { 480if (out .find (name )!= out .end ()) { 481// if exists, merge 482common_preset & target = out [name ]; 483target .merge (preset_added ); 484 }else { 485// otherwise, add directly 486out [name ]= preset_added ; 487 } 488 } 489return out ; 490} 491 492common_presets common_preset_context::cascade (const common_preset & base ,const common_presets & presets )const { 493common_presets out ; 494for (const auto & [name ,preset ] :presets ) { 495common_preset tmp = base ;// copy 496tmp .name = name ; 497tmp .merge (preset ); 498out [name ]= std::move (tmp ); 499 } 500return out ; 501}