char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
acecd5603
char/aliceai-t5-moe
1#include "common.h" 2#include "download.h" 3#include "log.h" 4#include "llama.h" 5#include "mtmd.h" 6#include "mtmd-helper.h" 7#include "chat.h" 8#include "base64.hpp" 9 10#include "server-common.h" 11 12#include <random> 13#include <sstream> 14#include <fstream> 15#include <limits> 16#include <cstring> 17#include <type_traits> 18#include <chrono> 19#include <thread> 20 21#ifdef _WIN32 22// windows.h defines min and max as macros, which breaks std::min and std::max 23#define WIN32_LEAN_AND_MEAN 24#ifndef NOMINMAX 25# define NOMINMAX 26#endif 27#include <windows.h> 28#include <io.h> 29#else 30#include <errno.h> 31#include <fcntl.h> 32#include <poll.h> 33#include <unistd.h> 34#endif 35 36json format_error_response (const std::string & message ,const enum error_type type ) { 37 std::string type_str ; 38int code = 500 ; 39switch (type ) { 40case ERROR_TYPE_INVALID_REQUEST : 41type_str = "invalid_request_error" ; 42code = 400 ; 43break ; 44case ERROR_TYPE_AUTHENTICATION : 45type_str = "authentication_error" ; 46code = 401 ; 47break ; 48case ERROR_TYPE_NOT_FOUND : 49type_str = "not_found_error" ; 50code = 404 ; 51break ; 52case ERROR_TYPE_SERVER : 53type_str = "server_error" ; 54code = 500 ; 55break ; 56case ERROR_TYPE_PERMISSION : 57type_str = "permission_error" ; 58code = 403 ; 59break ; 60case ERROR_TYPE_NOT_SUPPORTED : 61type_str = "not_supported_error" ; 62code = 501 ; 63break ; 64case ERROR_TYPE_UNAVAILABLE : 65type_str = "unavailable_error" ; 66code = 503 ; 67break ; 68case ERROR_TYPE_EXCEED_CONTEXT_SIZE : 69type_str = "exceed_context_size_error" ; 70code = 400 ; 71break ; 72 } 73return json { 74 {"code" ,code }, 75 {"message" ,message }, 76 {"type" ,type_str }, 77 }; 78} 79 80// 81// server_slot_stats 82// 83 84json server_slot_stats::to_json ()const { 85json base = { 86 {"cache_n" ,n_prompt_cached }, 87 88 {"prompt_n" ,n_prompt_processed }, 89 {"prompt_ms" ,t_prompt_ms ()}, 90 {"prompt_per_token_ms" ,t_prompt_per_token_ms ()}, 91 {"prompt_per_second" ,n_prompt_tps ()}, 92 93 {"predicted_n" ,n_gen }, 94 {"predicted_ms" ,t_gen_ms ()}, 95 {"predicted_per_token_ms" ,t_gen_per_token_ms ()}, 96 {"predicted_per_second" ,n_gen_tps ()}, 97 }; 98 99if (n_draft_tokens > 0 ) { 100base ["draft_n" ]= n_draft_tokens ; 101base ["draft_n_accepted" ]= n_draft_accepted ; 102 } 103 104return base ; 105} 106 107// 108// random string / id 109// 110 111std::string random_string () { 112static const std::string str ("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz" ); 113 114 std::random_device rd ; 115 std::mt19937 generator (rd ()); 116 117 std::string result (32 ,' ' ); 118 119for (int i = 0 ;i < 32 ;++ i ) { 120result [i ]= str [generator () %str .size ()]; 121 } 122 123return result ; 124} 125 126std::string gen_chatcmplid () { 127return "chatcmpl-" + random_string (); 128} 129 130std::string gen_tool_call_id () { 131return random_string (); 132} 133 134const char * get_media_marker () { 135static const std::string marker = []() { 136// allow user to pin a reproducible marker via env var 137const char * env = getenv ("LLAMA_MEDIA_MARKER" ); 138if (env && env [0 ]!= '\0' ) { 139return std::string (env ); 140 } 141return std::string ("<__media_" )+ random_string ()+ "__>" ; 142 }(); 143return marker .c_str (); 144} 145 146// 147// lora utils 148// 149 150bool lora_all_alora (const std::vector < common_adapter_lora_info > & loras ) { 151bool found_alora = false; 152for (const auto & lora :loras ) { 153if (lora .scale != 0 ) { 154if (llama_adapter_get_alora_n_invocation_tokens (lora .ptr )== 0 ) { 155return false; 156 } 157found_alora = true; 158 } 159 } 160return found_alora ; 161} 162 163bool lora_should_clear_cache ( 164const std::vector < common_adapter_lora_info > & current , 165const std::vector < common_adapter_lora_info > & next ) { 166 167// This should always be called after determining that the two sets are 168// _not_ equal. This assert is therefore some slightly wasted work and 169// should be safe to remove as long as this method is called correctly. 170GGML_ASSERT (!are_lora_equal (current ,next )); 171 172return ( 173 !(lora_get_enabled_ids (current ).empty ()|| lora_all_alora (current ))|| 174 !lora_all_alora (next )); 175} 176 177std::map < int ,float > parse_lora_request (const json & data ) { 178 std::map < int ,float > lora ; 179 180// set value 181for (const auto & entry :data ) { 182int id = json_value (entry ,"id" ,-1 ); 183float scale = json_value (entry ,"scale" ,0.0f ); 184lora [id ]= scale ; 185 } 186 187return lora ; 188} 189 190bool are_lora_equal ( 191const std::vector < common_adapter_lora_info > & l1 , 192const std::vector < common_adapter_lora_info > & l2 ) { 193if (l1 .size ()!= l2 .size ()) { 194return false; 195 } 196for (size_t i = 0 ;i < l1 .size ();++ i ) { 197// we don't check lora.path to reduce the time complexity 198if (l1 [i ].scale != l2 [i ].scale || l1 [i ].ptr != l2 [i ].ptr ) { 199return false; 200 } 201 } 202return true; 203} 204 205std::vector < size_t > lora_get_enabled_ids (const std::vector < common_adapter_lora_info > & loras ) { 206 std::vector < size_t > enabled_ids ; 207for (size_t i = 0 ;i < loras .size ();++ i ) { 208if (loras [i ].scale > 0 ) { 209enabled_ids .push_back (i ); 210 } 211 } 212return enabled_ids ; 213} 214 215// 216// base64 utils (TODO: use the base64::decode from base64.hpp) 217// 218 219static const std::string base64_chars = 220"ABCDEFGHIJKLMNOPQRSTUVWXYZ" 221"abcdefghijklmnopqrstuvwxyz" 222"0123456789+/" ; 223 224static inline bool is_base64 (uint8_t c ) { 225return (isalnum (c )|| (c == '+' )|| (c == '/' )); 226} 227 228static inline raw_buffer base64_decode (const std::string & encoded_string ) { 229int i = 0 ; 230int j = 0 ; 231int in_ = 0 ; 232 233int in_len = encoded_string .size (); 234 235uint8_t char_array_4 [4 ]; 236uint8_t char_array_3 [3 ]; 237 238raw_buffer ret ; 239 240while (in_len -- && (encoded_string [in_ ]!= '=' )&& is_base64 (encoded_string [in_ ])) { 241char_array_4 [i ++ ]= encoded_string [in_ ];in_ ++ ; 242if (i == 4 ) { 243for (i = 0 ;i < 4 ;i ++ ) { 244char_array_4 [i ]= base64_chars .find (char_array_4 [i ]); 245 } 246 247char_array_3 [0 ]= ((char_array_4 [0 ] ) <<2 )+ ((char_array_4 [1 ]& 0x30 ) >>4 ); 248char_array_3 [1 ]= ((char_array_4 [1 ]& 0xf ) <<4 )+ ((char_array_4 [2 ]& 0x3c ) >>2 ); 249char_array_3 [2 ]= ((char_array_4 [2 ]& 0x3 ) <<6 )+ char_array_4 [3 ]; 250 251for (i = 0 ; (i < 3 );i ++ ) { 252ret .push_back (char_array_3 [i ]); 253 } 254 255i = 0 ; 256 } 257 } 258 259if (i ) { 260for (j = i ;j < 4 ;j ++ ) { 261char_array_4 [j ]= 0 ; 262 } 263 264for (j = 0 ;j < 4 ;j ++ ) { 265char_array_4 [j ]= base64_chars .find (char_array_4 [j ]); 266 } 267 268char_array_3 [0 ]= ((char_array_4 [0 ] ) <<2 )+ ((char_array_4 [1 ]& 0x30 ) >>4 ); 269char_array_3 [1 ]= ((char_array_4 [1 ]& 0xf ) <<4 )+ ((char_array_4 [2 ]& 0x3c ) >>2 ); 270char_array_3 [2 ]= ((char_array_4 [2 ]& 0x3 ) <<6 )+ char_array_4 [3 ]; 271 272for (j = 0 ;j < i - 1 ;j ++ ) { 273ret .push_back (char_array_3 [j ]); 274 } 275 } 276 277return ret ; 278} 279 280// 281// server_tokens implementation 282// 283 284namespace { 285 286constexpr uint32_t SERVER_TOKENS_STATE_VERSION = 1 ; 287 288uint32_t server_tokens_state_u32 (size_t value ) { 289if (value > std::numeric_limits < uint32_t > ::max ()) { 290throw std::runtime_error ("Server tokens state is too large" ); 291 } 292return value ; 293} 294 295class server_tokens_state_writer { 296public : 297template < typename T > 298void write (T value ) { 299 static_assert(std::is_trivially_copyable < T > ::value ,"T must be trivially copyable" ); 300const auto * ptr = reinterpret_cast < const char *> (& value ); 301data .insert (data .end (),ptr ,ptr + sizeof (value )); 302 } 303 304template < typename T > 305void write (const std::vector < T > & values ) { 306 static_assert(std::is_trivially_copyable < T > ::value ,"T must be trivially copyable" ); 307write (server_tokens_state_u32 (values .size ())); 308if (values .empty ()) { 309return ; 310 } 311const auto * ptr = reinterpret_cast < const char *> (values .data ()); 312data .insert (data .end (),ptr ,ptr + values .size ()* sizeof (T )); 313 } 314 315void write_media_chunk (const mtmd_input_chunk * chunk ) { 316size_t chunk_size = 0 ; 317if (mtmd_input_chunk_save (chunk ,nullptr ,0 ,& chunk_size )!= 0 || chunk_size == 0 ) { 318throw std::runtime_error ("Cannot serialize media chunk in server tokens" ); 319 } 320 std::vector < char > chunk_data (server_tokens_state_u32 (chunk_size )); 321if (mtmd_input_chunk_save (chunk ,chunk_data .data (),chunk_data .size (),nullptr )!= 0 ) { 322throw std::runtime_error ("Cannot serialize media chunk in server tokens" ); 323 } 324write (chunk_data ); 325 } 326 327 std::vector < char > take () { 328data .resize ((data .size ()+ sizeof (llama_token )- 1 ) /sizeof (llama_token )* sizeof (llama_token ),0 ); 329return std::move (data ); 330 } 331 332private : 333 std::vector < char > data ; 334}; 335 336class server_tokens_state_reader { 337public : 338server_tokens_state_reader (const char * data ,size_t size ) :data (data ),size (size ) {} 339 340template < typename T > 341T read () { 342 static_assert(std::is_trivially_copyable < T > ::value ,"T must be trivially copyable" ); 343if (size - pos < sizeof (T )) { 344throw std::runtime_error ("Unexpected end of server tokens state" ); 345 } 346T value ; 347 std::memcpy (& value ,data + pos ,sizeof (value )); 348pos += sizeof (value ); 349return value ; 350 } 351 352template < typename T > 353 std::vector < T > read_vector () { 354 static_assert(std::is_trivially_copyable < T > ::value ,"T must be trivially copyable" ); 355const uint32_t n_values = read < uint32_t > (); 356// reject before resizing, so that a small corrupted payload cannot request a huge allocation 357if (n_values > remaining () /sizeof (T )) { 358throw std::runtime_error ("Unexpected end of server tokens state" ); 359 } 360 std::vector < T > values (n_values ); 361if (n_values > 0 ) { 362 std::memcpy (values .data (),data + pos ,values .size ()* sizeof (T )); 363pos += values .size ()* sizeof (T ); 364 } 365return values ; 366 } 367 368size_t remaining ()const { 369return size - pos ; 370 } 371 372private : 373const char * data ; 374size_t size ; 375size_t pos = 0 ; 376}; 377 378}// namespace 379 380server_tokens::server_tokens (mtmd::input_chunks & mtmd_chunks ,bool has_mtmd ) :has_mtmd (has_mtmd ) { 381for (size_t i = 0 ;i < mtmd_chunks .size ();++ i ) { 382push_back (mtmd_chunks [i ]); 383 } 384} 385 386server_tokens::server_tokens (const llama_tokens & tokens ,bool has_mtmd ) :has_mtmd (has_mtmd ),tokens (tokens ) { 387} 388 389llama_pos server_tokens::pos_next (int64_t n_tokens )const { 390if (!has_mtmd ) { 391if (n_tokens < 0 ) { 392return tokens .size (); 393 } 394 395return n_tokens ; 396 } 397 398if (n_tokens < 0 ) { 399llama_pos res = tokens .size (); 400 401for (auto it = map_idx_to_media .begin ();it != map_idx_to_media .end ();++ it ) { 402const auto & chunk = it -> second ; 403res += mtmd_input_chunk_get_n_pos (chunk .get ())- mtmd_input_chunk_get_n_tokens (chunk .get ()); 404 } 405 406return res ; 407 } 408 409int64_t idx = 0 ; 410llama_pos pos = 0 ; 411 412GGML_ASSERT (n_tokens <= (int64_t )tokens .size ()); 413 414while (idx < n_tokens ) { 415const auto media_it = map_idx_to_media .find (idx ); 416if (media_it != map_idx_to_media .end ()) { 417const auto & chunk = media_it -> second ; 418const llama_pos n_pos = mtmd_input_chunk_get_n_pos (chunk .get ()); 419const size_t n_tok = mtmd_input_chunk_get_n_tokens (chunk .get ()); 420 421pos += n_pos ; 422idx += n_tok ; 423 }else { 424pos ++ ; 425idx ++ ; 426 } 427 } 428 429return pos ; 430} 431 432size_t server_tokens::size_up_to_pos (llama_pos max_pos )const { 433if (!has_mtmd ) { 434return std::min ((size_t )max_pos ,tokens .size ()); 435 } 436 437size_t idx = 0 ; 438llama_pos pos = 0 ; 439 440while (idx < tokens .size ()) { 441const auto media_it = map_idx_to_media .find (idx ); 442if (media_it != map_idx_to_media .end ()) { 443const auto & chunk = media_it -> second ; 444const llama_pos n_pos = mtmd_input_chunk_get_n_pos (chunk .get ()); 445const size_t n_tok = mtmd_input_chunk_get_n_tokens (chunk .get ()); 446 447pos += n_pos ; 448idx += n_tok ; 449 }else { 450pos ++ ; 451idx ++ ; 452 } 453 454if (pos >=max_pos ) { 455break ; 456 } 457 } 458 459return idx ; 460} 461 462std::string server_tokens::str ()const { 463 std::ostringstream oss ; 464oss <<"tokens: " ; 465for (size_t idx = 0 ;idx < tokens .size ();++ idx ) { 466llama_token t = tokens [idx ]; 467oss <<"idx:" <<idx <<" " ; 468if (t == LLAMA_TOKEN_NULL ) { 469oss <<"<embd> " ; 470 }else { 471oss <<t <<" " ; 472 } 473 } 474oss <<"\n" ; 475oss <<"image idx: " ; 476for (const auto & it :map_idx_to_media ) { 477oss <<it .first <<", " ; 478 } 479return oss .str (); 480} 481 482const mtmd::input_chunk_ptr & server_tokens::find_chunk (size_t idx )const { 483auto it = map_idx_to_media .find (idx ); 484if (it != map_idx_to_media .end ()) { 485return it -> second ; 486 } 487throw std::runtime_error ("Chunk not found" ); 488} 489 490std::pair < const mtmd::input_chunk_ptr * ,size_t > server_tokens::find_next_media_chunk (size_t idx )const { 491auto it = map_idx_to_media .upper_bound (idx ); 492if (it != map_idx_to_media .end ()) { 493return {& it -> second ,it -> first }; 494 } 495return {nullptr ,0 }; 496} 497 498void server_tokens::push_back (llama_token tok ) { 499if (tok == LLAMA_TOKEN_NULL ) { 500throw std::runtime_error ("Invalid token" ); 501 } 502tokens .emplace_back (tok ); 503} 504 505void server_tokens::push_back (const mtmd_input_chunk * chunk ) { 506auto type = mtmd_input_chunk_get_type (chunk ); 507if (type == MTMD_INPUT_CHUNK_TYPE_IMAGE || type == MTMD_INPUT_CHUNK_TYPE_AUDIO ) { 508GGML_ASSERT (has_mtmd ); 509const size_t n_tokens = mtmd_input_chunk_get_n_tokens (chunk ); 510size_t start_idx = tokens .size (); 511for (size_t i = 0 ;i < n_tokens ;++ i ) { 512tokens .emplace_back (LLAMA_TOKEN_NULL ); 513 } 514 mtmd::input_chunk_ptr new_chunk (mtmd_input_chunk_copy (chunk )); 515map_idx_to_media [start_idx ]= std::move (new_chunk ); 516 }else if (type == MTMD_INPUT_CHUNK_TYPE_TEXT ) { 517size_t n_tokens ; 518const auto * text_tokens = mtmd_input_chunk_get_tokens_text (chunk ,& n_tokens ); 519for (size_t i = 0 ;i < n_tokens ;++ i ) { 520push_back (text_tokens [i ]); 521 } 522 }else { 523GGML_ABORT ("Invalid chunk type" ); 524 } 525} 526 527void server_tokens::push_back_placeholder (const mtmd_input_chunk * chunk ) { 528auto type = mtmd_input_chunk_get_type (chunk ); 529if (type == MTMD_INPUT_CHUNK_TYPE_IMAGE || type == MTMD_INPUT_CHUNK_TYPE_AUDIO ) { 530GGML_ASSERT (has_mtmd ); 531 mtmd::input_chunk_ptr new_chunk (mtmd_input_chunk_get_placeholder (chunk )); 532GGML_ASSERT (new_chunk != nullptr && "failed to create placeholder chunk" ); 533const size_t n_tokens = mtmd_input_chunk_get_n_tokens (chunk ); 534size_t start_idx = tokens .size (); 535for (size_t i = 0 ;i < n_tokens ;++ i ) { 536tokens .emplace_back (LLAMA_TOKEN_NULL ); 537 } 538map_idx_to_media [start_idx ]= std::move (new_chunk ); 539 }else { 540push_back (chunk ); 541 } 542} 543 544void server_tokens::push_back (server_tokens & tokens ) { 545size_t start_idx = size (); 546for (size_t i = 0 ;i < tokens .size ();i ++ ) { 547push_back (tokens [i ]); 548 } 549if (tokens .has_mtmd ) { 550// Assert if we are copying MTMD chunks to a server_tokens that does not have mtmd. 551// We could also just check, but this will prevent silently dropping MTMD data. 552GGML_ASSERT (has_mtmd ); 553for (auto it = tokens .map_idx_to_media .begin ();it != tokens .map_idx_to_media .end (); ) { 554auto * chunk = tokens .map_idx_to_media [it -> first ].get (); 555 mtmd::input_chunk_ptr new_chunk (mtmd_input_chunk_copy (chunk )); 556map_idx_to_media [start_idx + it -> first ]= std::move (new_chunk ); 557 } 558 } 559} 560 561void server_tokens::insert (const llama_tokens & inp_tokens ) { 562tokens .insert (tokens .end (),inp_tokens .begin (),inp_tokens .end ()); 563} 564 565const llama_tokens & server_tokens::get_tokens ()const { 566GGML_ASSERT (!has_mtmd ); 567return tokens ; 568} 569 570std::vector < char > server_tokens::serialize ()const { 571 static_assert(sizeof (llama_token )== sizeof (uint32_t ),"unexpected llama_token size" ); 572 573server_tokens_state_writer writer ; 574writer .write ((llama_token )LLAMA_TOKEN_NULL ); 575writer .write (SERVER_TOKENS_STATE_VERSION ); 576writer .write (tokens ); 577 578 std::vector < uint32_t > media_keys ; 579media_keys .reserve (map_idx_to_media .size ()); 580for (const auto & item :map_idx_to_media ) { 581media_keys .push_back (server_tokens_state_u32 (item .first )); 582 } 583writer .write (media_keys ); 584 585for (const auto & item :map_idx_to_media ) { 586writer .write_media_chunk (item .second .get ()); 587 } 588 589return writer .take (); 590} 591 592server_tokens server_tokens::deserialize (const llama_tokens & packed ,bool has_mtmd ) { 593 static_assert(sizeof (llama_token )== sizeof (uint32_t ),"unexpected llama_token size" ); 594 595if (packed .empty ()|| packed [0 ]!= LLAMA_TOKEN_NULL ) { 596// plain token list, as written by older versions 597return server_tokens (packed ,has_mtmd ); 598 } 599 600server_tokens_state_reader reader (reinterpret_cast < const char *> (packed .data ()),packed .size ()* sizeof (llama_token )); 601reader .read < llama_token > ();// format marker 602if (reader .read < uint32_t > ()!= SERVER_TOKENS_STATE_VERSION ) { 603throw std::runtime_error ("Unsupported server tokens state version" ); 604 } 605 606const llama_tokens tokens = reader .read_vector < llama_token > (); 607 608// the media start indices, followed by the media chunks in the same order 609const std::vector < uint32_t > media_keys = reader .read_vector < uint32_t > (); 610if (!media_keys .empty ()&& !has_mtmd ) { 611throw std::runtime_error ("Cannot restore media tokens without an mmproj" ); 612 } 613 614server_tokens result (tokens ,has_mtmd ); 615 616for (const uint32_t key :media_keys ) { 617const size_t start_idx = key ; 618const std::vector < char > chunk_data = reader .read_vector < char > (); 619if (chunk_data .empty ()) { 620throw std::runtime_error ("Cannot load media chunk from server tokens state" ); 621 } 622 623 mtmd::input_chunk_ptr chunk (mtmd_input_chunk_load (chunk_data .data (),chunk_data .size ())); 624if (!chunk ) { 625throw std::runtime_error ("Cannot load media chunk from server tokens state" ); 626 } 627result .map_idx_to_media [start_idx ]= std::move (chunk ); 628 } 629 630if (reader .remaining () >=sizeof (llama_token )) { 631throw std::runtime_error ("Trailing data in server tokens state" ); 632 } 633 634return result ; 635} 636 637llama_tokens server_tokens::get_text_tokens ()const { 638llama_tokens res ; 639res .reserve (tokens .size ()); 640for (llama_token t :tokens ) { 641if (t != LLAMA_TOKEN_NULL ) { 642res .push_back (t ); 643 } 644 } 645return res ; 646} 647 648void server_tokens::set_token (llama_pos pos ,llama_token id ) { 649GGML_ASSERT (!has_mtmd );// only allow this if mtmd is disabled 650tokens [pos ]= id ; 651} 652 653void server_tokens::keep_first (size_t n ) { 654GGML_ASSERT (n <=tokens .size ()); 655if (has_mtmd ) { 656if (n == tokens .size ()) { 657return ;// nothing to do 658 } 659// we throw an error if we try to remove a token in the middle of an image 660// for ex. with input of 5 text tokens and 2 images: 661// [0] [1] [2] [3] [4] [img0] [img0] [img0] [img1] [img1] 662// n 1 2 3 4 5 6 7 8 9 10 663// allowed to resize ^ ^ 664// disallowed to resize ^ ^ ^ 665if (n > 0 ) { 666// make sure we never remove tokens in the middle of an image 667// note that the case where we keep a full image at the end is allowed: 668// tokens[n - 1] == LLAMA_TOKEN_NULL && tokens[n] != LLAMA_TOKEN_NULL 669if (tokens [n - 1 ]== LLAMA_TOKEN_NULL && tokens [n ]== LLAMA_TOKEN_NULL ) { 670find_chunk (n - 1 );// will throw an error if the token is not begin-of-chunk 671 } 672 } 673// remove all image chunks that are not used anymore 674for (auto it = map_idx_to_media .begin ();it != map_idx_to_media .end (); ) { 675size_t idx = it -> first ; 676if (idx >=n ) { 677it = map_idx_to_media .erase (it ); 678 }else { 679++ it ; 680 } 681 } 682 } 683tokens .resize (n ); 684} 685 686std::string server_tokens::detokenize (const llama_context * ctx ,bool special )const { 687llama_tokens text_tokens ; 688text_tokens .reserve (tokens .size ()); 689for (const auto & t :tokens ) { 690if (t != LLAMA_TOKEN_NULL ) { 691text_tokens .push_back (t ); 692 } 693 } 694return common_detokenize (ctx ,text_tokens ,special ); 695} 696 697size_t server_tokens::get_common_prefix (const server_tokens & b )const { 698const size_t max_idx = std::min (tokens .size (),b .tokens .size ()); 699 700if (!has_mtmd ) { 701for (size_t i = 0 ;i < max_idx ;++ i ) { 702if (tokens [i ]== b .tokens [i ]) { 703continue ; 704 } 705 706return i ; 707 } 708 709return max_idx ; 710 } 711 712for (size_t i = 0 ;i < max_idx ;++ i ) { 713const llama_token ai = tokens [i ]; 714const llama_token bi = b .tokens [i ]; 715 716if (ai == LLAMA_TOKEN_NULL && bi == LLAMA_TOKEN_NULL ) { 717const auto & a_chunk = find_chunk (i ); 718const auto & b_chunk = b .find_chunk (i ); 719 720GGML_ASSERT (a_chunk && b_chunk ); 721 722const std::string id_ai = mtmd_input_chunk_get_id (a_chunk .get ()); 723const std::string id_bi = mtmd_input_chunk_get_id (b_chunk .get ()); 724 725const size_t n_tok_a = mtmd_input_chunk_get_n_tokens (a_chunk .get ()); 726const size_t n_tok_b = mtmd_input_chunk_get_n_tokens (b_chunk .get ()); 727 728if (id_ai == id_bi && n_tok_a == n_tok_b ) { 729GGML_ASSERT (n_tok_a > 0 && "Invalid media chunk" );// should never happen 730i += n_tok_a - 1 ;// will be +1 by the for loop 731continue ; 732 } 733 734return i ; 735 } 736 737if (ai == bi ) { 738continue ; 739 } 740 741return i ; 742 } 743 744return max_idx ;// all tokens are equal 745} 746 747common_chat_msg_spans server_tokens::find_message_spans (const common_chat_msg_delimiters & delims )const { 748 std::map < size_t ,size_t > skips ; 749for (const auto & it :map_idx_to_media ) { 750skips [it .first ]= mtmd_input_chunk_get_n_tokens (it .second .get ()); 751 } 752return delims .split (tokens ,skips ); 753} 754 755bool server_tokens::validate (const struct llama_context * ctx )const { 756const llama_model * model = llama_get_model (ctx ); 757const llama_vocab * vocab = llama_model_get_vocab (model ); 758const int32_t n_vocab = llama_vocab_n_tokens (vocab ); 759size_t n_media = 0 ; 760 761for (size_t i = 0 ;i < tokens .size ();++ i ) { 762const auto & t = tokens [i ]; 763if (t == LLAMA_TOKEN_NULL ) { 764try { 765const auto & chunk = find_chunk (i ); 766if (mtmd_input_chunk_get_type (chunk .get ())== MTMD_INPUT_CHUNK_TYPE_TEXT ) { 767return false; 768 } 769const size_t n_tokens = mtmd_input_chunk_get_n_tokens (chunk .get ()); 770const llama_pos n_pos = mtmd_input_chunk_get_n_pos (chunk .get ()); 771if (n_tokens == 0 || n_pos <=0 || n_tokens > tokens .size ()- i ) { 772return false; 773 } 774for (size_t j = i ;j < i + n_tokens ;++ j ) { 775if (tokens [j ]!= LLAMA_TOKEN_NULL ) { 776return false; 777 } 778 } 779++ n_media ; 780i += n_tokens - 1 ; 781 }catch (const std::exception & e ) { 782return false; 783 } 784 }else if (t < 0 || t >=n_vocab ) { 785return false; 786 } 787 } 788return n_media == map_idx_to_media .size (); 789} 790 791server_tokens server_tokens::clone ()const { 792server_tokens res ; 793res .has_mtmd = has_mtmd ; 794res .tokens = tokens ; 795for (auto it = map_idx_to_media .begin ();it != map_idx_to_media .end ();++ it ) { 796size_t idx = it -> first ; 797const mtmd::input_chunk_ptr & chunk = it -> second ; 798res .map_idx_to_media [idx ]= mtmd::input_chunk_ptr (mtmd_input_chunk_copy (chunk .get ())); 799 } 800return res ; 801} 802 803// 804// tokenizer and input processing utils 805// 806 807bool json_is_array_of_numbers (const json & data ) { 808if (data .is_array ()) { 809for (const auto & e :data ) { 810if (!e .is_number_integer ()) { 811return false; 812 } 813 } 814return true; 815 } 816return false; 817} 818 819bool json_is_array_of_mixed_numbers_strings (const json & data ) { 820bool seen_string = false; 821bool seen_number = false; 822if (data .is_array ()) { 823for (const auto & e :data ) { 824seen_string |=e .is_string (); 825seen_number |=e .is_number_integer (); 826if (seen_number && seen_string ) { 827return true; 828 } 829 } 830 } 831return false; 832} 833 834bool json_is_array_and_contains_numbers (const json & data ) { 835if (data .is_array ()) { 836for (const auto & e :data ) { 837if (e .is_number_integer ()) { 838return true; 839 } 840 } 841return false; 842 } 843return false; 844} 845 846json json_get_nested_values (const std::vector < std::string > & paths ,const json & js ) { 847json result = json::object (); 848 849for (const std::string & path :paths ) { 850json current = js ; 851const auto keys = string_split < std::string > (path ,/*separator*/ '/' ); 852bool valid_path = true; 853for (const std::string & k :keys ) { 854if (valid_path && current .is_object ()&& current .contains (k )) { 855current = current [k ]; 856 }else { 857valid_path = false; 858 } 859 } 860if (valid_path ) { 861result [path ]= current ; 862 } 863 } 864return result ; 865} 866 867llama_tokens tokenize_mixed (const llama_vocab * vocab ,const json & json_prompt ,bool add_special ,bool parse_special ) { 868// If `add_bos` is true, we only add BOS, when json_prompt is a string, 869// or the first element of the json_prompt array is a string. 870llama_tokens prompt_tokens ; 871 872if (json_prompt .is_array ()) { 873bool first = true; 874for (const auto & p :json_prompt ) { 875if (p .is_string ()) { 876auto s = p .template get < std::string > (); 877 878llama_tokens p ; 879if (first ) { 880p = common_tokenize (vocab ,s ,add_special ,parse_special ); 881first = false; 882 }else { 883p = common_tokenize (vocab ,s , false,parse_special ); 884 } 885 886prompt_tokens .insert (prompt_tokens .end (),p .begin (),p .end ()); 887 }else { 888if (first ) { 889first = false; 890 } 891 892prompt_tokens .push_back (p .template get < llama_token > ()); 893 } 894 } 895 }else { 896auto s = json_prompt .template get < std::string > (); 897prompt_tokens = common_tokenize (vocab ,s ,add_special ,parse_special ); 898 } 899 900return prompt_tokens ; 901} 902 903size_t validate_utf8 (const std::string & text ) { 904size_t len = text .size (); 905if (len == 0 )return 0 ; 906 907// Check the last few bytes to see if a multi-byte character is cut off 908for (size_t i = 1 ;i <=4 && i <=len ;++ i ) { 909unsigned char c = text [len - i ]; 910// Check for start of a multi-byte sequence from the end 911if ((c & 0xE0 )== 0xC0 ) { 912// 2-byte character start: 110xxxxx 913// Needs at least 2 bytes 914if (i < 2 )return len - i ; 915 }else if ((c & 0xF0 )== 0xE0 ) { 916// 3-byte character start: 1110xxxx 917// Needs at least 3 bytes 918if (i < 3 )return len - i ; 919 }else if ((c & 0xF8 )== 0xF0 ) { 920// 4-byte character start: 11110xxx 921// Needs at least 4 bytes 922if (i < 4 )return len - i ; 923 } 924 } 925 926// If no cut-off multi-byte character is found, return full length 927return len ; 928} 929 930server_tokens process_mtmd_prompt ( 931mtmd_context * mctx , 932const std::string & prompt , 933const std::vector < raw_buffer > & files , 934const mtmd_helper_init_opt & init_opt , 935bool is_placeholder ) { 936// these will be freed upon going out of scope 937 mtmd::bitmaps bitmaps ; 938 std::vector < mtmd_helper::video_ptr > videos ; 939for (auto & file :files ) { 940auto out = mtmd_helper_bitmap_init_from_buf (mctx ,file .data (),file .size (),is_placeholder ,init_opt ); 941if (!out .bitmap ) { 942throw std::runtime_error ("Failed to load image or audio file" ); 943 } 944bitmaps .entries .emplace_back (out .bitmap ); 945if (out .video_ctx ) { 946videos .emplace_back (out .video_ctx ); 947 } 948 } 949// process prompt 950 std::vector < server_tokens > inputs ; 951// multimodal 952mtmd_input_text inp_txt = { 953prompt .data (), 954prompt .size (), 955/* add_special */ true, 956/* parse_special */ true, 957 }; 958 mtmd::input_chunks chunks (mtmd_input_chunks_init ()); 959auto bitmaps_c_ptr = bitmaps .c_ptr (); 960int32_t tokenized = mtmd_tokenize (mctx , 961chunks .ptr .get (), 962& inp_txt , 963bitmaps_c_ptr .data (), 964bitmaps_c_ptr .size ()); 965if (tokenized != 0 ) { 966throw std::runtime_error ("Failed to tokenize prompt" ); 967 } 968auto result = server_tokens (chunks , true); 969return result ; 970} 971 972/** 973* break the input "prompt" object into multiple prompt if needed, then tokenize them 974* use tokenize_input_prompts() if the input could be an array. 975* this supports these cases: 976* - "prompt": "string" 977* - "prompt": [12, 34, 56] 978* - "prompt": [12, 34, "string", 56, 78] 979* - "prompt": { "prompt_string": "string", "multimodal_data": [ "base64" ] } 980*/ 981static server_tokens tokenize_input_subprompt (const llama_vocab * vocab ,mtmd_context * mctx ,const json & json_prompt ,bool add_special ,bool parse_special ,const mtmd_helper_init_opt & init_opt ) { 982constexpr char JSON_STRING_PROMPT_KEY []= "prompt_string" ; 983constexpr char JSON_MTMD_DATA_KEY []= "multimodal_data" ; 984const bool has_mtmd = mctx != nullptr ; 985if (json_prompt .is_string ()|| json_is_array_of_mixed_numbers_strings (json_prompt )) { 986// string or mixed 987llama_tokens tmp = tokenize_mixed (vocab ,json_prompt ,add_special ,parse_special ); 988return server_tokens (tmp , false); 989 }else if (json_is_array_of_numbers (json_prompt )) { 990// array of tokens 991llama_tokens tmp = json_prompt .get < llama_tokens > (); 992return server_tokens (tmp , false); 993 }else if (json_prompt .contains (JSON_STRING_PROMPT_KEY )) { 994// JSON object with prompt key. 995if (json_prompt .contains (JSON_MTMD_DATA_KEY )) { 996if (!has_mtmd ) 997throw std::runtime_error ("Multimodal data provided, but model does not support multimodal requests." ); 998 999// JSON object with prompt and multimodal key. 1000 std::vector < raw_buffer > files ; 1001for (const auto & entry :json_prompt .at (JSON_MTMD_DATA_KEY )) { 1002files .push_back (base64_decode (entry )); 1003 } 1004return process_mtmd_prompt (mctx ,json_prompt .at (JSON_STRING_PROMPT_KEY ),files ,init_opt ); 1005 }else { 1006// Not multimodal, but contains a subobject. 1007llama_tokens tmp = tokenize_mixed (vocab ,json_prompt .at (JSON_STRING_PROMPT_KEY ),add_special ,parse_special ); 1008return server_tokens (tmp , false); 1009 } 1010 }else { 1011throw std::runtime_error ("\"prompt\" elements must be a string, a list of tokens, a JSON object containing a prompt string, or a list of mixed strings & tokens." ); 1012 } 1013} 1014 1015std::vector < server_tokens > tokenize_input_prompts (const llama_vocab * vocab ,mtmd_context * mctx ,const json & json_prompt ,bool add_special ,bool parse_special ,const mtmd_helper_init_opt & init_opt ) { 1016 std::vector < server_tokens > result ; 1017if (json_prompt .is_array ()&& !json_is_array_and_contains_numbers (json_prompt )) { 1018result .reserve (json_prompt .size ()); 1019for (const auto & p :json_prompt ) { 1020result .push_back (tokenize_input_subprompt (vocab ,mctx ,p ,add_special ,parse_special ,init_opt )); 1021 } 1022 }else { 1023result .push_back (tokenize_input_subprompt (vocab ,mctx ,json_prompt ,add_special ,parse_special ,init_opt )); 1024 } 1025if (result .empty ()) { 1026throw std::runtime_error ("\"prompt\" must not be empty" ); 1027 } 1028return result ; 1029} 1030 1031// 1032// OAI utils 1033// 1034 1035// used by /completions endpoint 1036json oaicompat_completion_params_parse (const json & body ) { 1037json llama_params ; 1038 1039if (!body .contains ("prompt" )) { 1040throw std::runtime_error ("\"prompt\" is required" ); 1041 } 1042 1043// Handle "stop" field 1044if (body .contains ("stop" )&& body .at ("stop" ).is_string ()) { 1045llama_params ["stop" ]= json::array ({body .at ("stop" ).get < std::string > ()}); 1046 }else { 1047llama_params ["stop" ]= json_value (body ,"stop" , json::array ()); 1048 } 1049 1050// Handle "echo" field 1051if (json_value (body ,"echo" , false)) { 1052throw std::runtime_error ("Only no echo is supported" ); 1053 } 1054 1055// Params supported by OAI but unsupported by llama.cpp 1056static const std::vector < std::string > unsupported_params {"best_of" ,"suffix" }; 1057for (const auto & param :unsupported_params ) { 1058if (body .contains (param )) { 1059throw std::runtime_error ("Unsupported param: " + param ); 1060 } 1061 } 1062 1063// Copy remaining properties to llama_params 1064for (const auto & item :body .items ()) { 1065// Exception: if "n_predict" is present, we overwrite the value specified earlier by "max_tokens" 1066if (!llama_params .contains (item .key ())|| item .key ()== "n_predict" ) { 1067llama_params [item .key ()]= item .value (); 1068 } 1069 } 1070 1071return llama_params ; 1072} 1073 1074// url can be 1075// - http(s):// for remote files 1076// - file:// for local files (only allowed if media_path is set) 1077// - data: for base64 encoded data with uri scheme (e.g. data:image/png;base64,...) 1078// - raw base64 encoded data 1079static void handle_media ( 1080 std::vector < raw_buffer > & out_files , 1081const std::string & url , 1082const std::string & media_path ) { 1083if (!media_path .empty ()) { 1084// should already be enforced by arg.cpp, but checking just in case 1085GGML_ASSERT (media_path .back ()== DIRECTORY_SEPARATOR ); 1086 } 1087 1088if (string_starts_with (url ,"http" )) { 1089// download remote image 1090// TODO @ngxson : maybe make these params configurable 1091common_remote_params params ; 1092params .max_size = 1024 * 1024 * 10 ;// 10MB 1093params .timeout = 10 ;// seconds 1094SRV_INF ("downloading image from '%s'\n" ,url .c_str ()); 1095auto res = common_remote_get_content (url ,params ); 1096if (200 <=res .first && res .first < 300 ) { 1097SRV_INF ("downloaded %zu bytes\n" ,res .second .size ()); 1098raw_buffer data ; 1099data .insert (data .end (),res .second .begin (),res .second .end ()); 1100out_files .push_back (data ); 1101 }else { 1102throw std::runtime_error ("Failed to download image" ); 1103 } 1104 1105 }else if (string_starts_with (url ,"file://" )) { 1106if (media_path .empty ()) { 1107throw std::invalid_argument ("file:// URLs are not allowed unless --media-path is specified" ); 1108 } 1109// load local image file 1110 std::string file_path = url .substr (7 );// remove "file://" 1111raw_buffer data ; 1112if (!fs_validate_filename (file_path , true)) { 1113throw std::invalid_argument ("file path is not allowed: " + file_path ); 1114 } 1115SRV_INF ("loading image from local file '%s'\n" , (media_path + file_path ).c_str ()); 1116 std::ifstream file (media_path + file_path , std::ios::binary ); 1117if (!file ) { 1118throw std::invalid_argument ("file does not exist or cannot be opened: " + file_path ); 1119 } 1120data .assign ((std::istreambuf_iterator < char > (file )), std::istreambuf_iterator < char > ()); 1121out_files .push_back (data ); 1122 1123 }else if (string_starts_with (url ,"data:" )) { 1124// try to decode base64 image, video, or audio 1125 std::vector < std::string > parts = string_split < std::string > (url ,/*separator*/ ',' ); 1126if (parts .size ()!= 2 ) { 1127throw std::invalid_argument ("Invalid uri-encoded base64 value" ); 1128 }else if (!string_starts_with (parts [0 ],"data:image/" ) 1129&& !string_starts_with (parts [0 ],"data:video/" ) 1130&& !string_starts_with (parts [0 ],"data:audio/" )) { 1131throw std::invalid_argument ("Invalid uri format: " + parts [0 ]); 1132 }else if (!string_ends_with (parts [0 ],"base64" )) { 1133throw std::invalid_argument ("uri must be base64 encoded" ); 1134 }else { 1135auto base64_data = parts [1 ]; 1136auto decoded_data = base64_decode (base64_data ); 1137out_files .push_back (decoded_data ); 1138 } 1139 1140 }else { 1141// try as raw base64 string 1142auto decoded_data = base64_decode (url ); 1143if (decoded_data .empty ()) { 1144throw std::runtime_error ("Invalid base64 value" ); 1145 } 1146out_files .push_back (decoded_data ); 1147 } 1148} 1149 1150// used by /chat/completions endpoint 1151json oaicompat_chat_params_parse ( 1152json & body ,/* openai api json semantics */ 1153const server_chat_params & opt , 1154 std::vector < raw_buffer > & out_files ) 1155{ 1156json llama_params ; 1157 1158auto tools = json_value (body ,"tools" ,json ()); 1159auto has_tools = tools .is_array ()&& !tools .empty (); 1160auto stream = json_value (body ,"stream" , false); 1161auto tool_choice = json_value (body ,"tool_choice" , std::string ("auto" )); 1162 1163if (!opt .use_jinja ) { 1164if (has_tools ) { 1165throw std::runtime_error ("tools param requires --jinja flag" ); 1166 } 1167if (tool_choice != "auto" ) { 1168throw std::runtime_error ("tool_choice param requires --jinja flag" ); 1169 } 1170 } 1171 1172// Handle "stop" field 1173if (body .contains ("stop" )&& body .at ("stop" ).is_string ()) { 1174llama_params ["stop" ]= json::array ({body .at ("stop" ).get < std::string > ()}); 1175 }else { 1176llama_params ["stop" ]= json_value (body ,"stop" , json::array ()); 1177 } 1178 1179auto json_schema = json_value (body ,"json_schema" ,json ()); 1180auto grammar = json_value (body ,"grammar" , std::string ()); 1181if (!json_schema .is_null ()&& !grammar .empty ()) { 1182throw std::runtime_error ("Cannot use both json_schema and grammar" ); 1183 } 1184 1185// Handle "response_format" field 1186if (body .contains ("response_format" )) { 1187json response_format = json_value (body ,"response_format" , json::object ()); 1188 std::string response_type = json_value (response_format ,"type" , std::string ()); 1189if (response_type == "json_object" ) { 1190if (response_format .contains ("schema" )|| json_schema .empty ()) { 1191json_schema = json_value (response_format ,"schema" , json::object ()); 1192 } 1193 }else if (response_type == "json_schema" ) { 1194auto schema_wrapper = json_value (response_format ,"json_schema" , json::object ()); 1195json_schema = json_value (schema_wrapper ,"schema" , json::object ()); 1196 }else if (!response_type .empty ()&& response_type != "text" ) { 1197throw std::invalid_argument ("response_format type must be one of \"text\" or \"json_object\", but got: " + response_type ); 1198 } 1199 } 1200 1201// an absent or empty schema means any object 1202if (json_schema .is_object ()&& json_schema .empty ()) { 1203json_schema ["type" ]= "object" ; 1204 } 1205 1206// get input files 1207if (!body .contains ("messages" )) { 1208throw std::invalid_argument ("'messages' is required" ); 1209 } 1210json & messages = body .at ("messages" ); 1211if (!messages .is_array ()) { 1212throw std::invalid_argument ("Expected 'messages' to be an array" ); 1213 } 1214for (auto & msg :messages ) { 1215 std::string role = json_value (msg ,"role" , std::string ()); 1216if (role != "assistant" && !msg .contains ("content" )) { 1217throw std::invalid_argument ("All non-assistant messages must contain 'content'" ); 1218 } 1219if (role == "assistant" ) { 1220if (!msg .contains ("content" )&& !msg .contains ("tool_calls" )) { 1221throw std::invalid_argument ("Assistant message must contain either 'content' or 'tool_calls'!" ); 1222 } 1223if (!msg .contains ("content" )) { 1224continue ;// avoid errors with no content 1225 } 1226 } 1227json & content = msg .at ("content" ); 1228if (content .is_string ()|| content .is_null ()) { 1229continue ; 1230 } 1231 1232if (!content .is_array ()) { 1233throw std::invalid_argument ("Expected 'content' to be a string or an array" ); 1234 } 1235 1236for (auto & p :content ) { 1237 std::string type = json_value (p ,"type" , std::string ()); 1238if (type == "image_url" ) { 1239if (!opt .allow_image ) { 1240throw std::runtime_error ("image input is not supported - hint: if this is unexpected, you may need to provide the mmproj" ); 1241 } 1242 1243json image_url = json_value (p ,"image_url" , json::object ()); 1244 std::string url = json_value (image_url ,"url" , std::string ()); 1245handle_media (out_files ,url ,opt .media_path ); 1246 1247p ["type" ]= "media_marker" ; 1248p ["text" ]= get_media_marker (); 1249p .erase ("image_url" ); 1250 1251 }else if (type == "input_audio" ) { 1252if (!opt .allow_audio ) { 1253throw std::runtime_error ("audio input is not supported - hint: if this is unexpected, you may need to provide the mmproj" ); 1254 } 1255 1256// note: don't need to validate "format", it's redundant 1257json input_audio = json_value (p ,"input_audio" , json::object ()); 1258 std::string url = json_value (input_audio ,"data" , 1259json_value (input_audio ,"url" , std::string ())); 1260handle_media (out_files ,url ,opt .media_path ); 1261 1262p ["type" ]= "media_marker" ; 1263p ["text" ]= get_media_marker (); 1264p .erase ("input_audio" ); 1265 1266 }else if (type == "input_video" ) { 1267if (!opt .allow_video ) { 1268throw std::runtime_error ("video input is not supported - hint: if this is unexpected, you may need to provide the mmproj" ); 1269 } 1270 1271json input_video = json_value (p ,"input_video" , json::object ()); 1272 std::string url = json_value (input_video ,"data" , 1273json_value (input_video ,"url" , std::string ())); 1274handle_media (out_files ,url ,opt .media_path ); 1275 1276p ["type" ]= "media_marker" ; 1277p ["text" ]= get_media_marker (); 1278p .erase ("input_video" ); 1279 1280 }else if (type != "text" ) { 1281throw std::invalid_argument ("unsupported content[].type" ); 1282 } 1283 } 1284 } 1285 1286auto caps = common_chat_templates_get_caps (opt .tmpls .get ()); 1287 1288common_chat_templates_inputs inputs ; 1289inputs .messages = common_chat_msgs_parse_oaicompat (messages ); 1290inputs .tools = common_chat_tools_parse_oaicompat (tools ); 1291inputs .tool_choice = common_chat_tool_choice_parse_oaicompat (tool_choice ); 1292inputs .json_schema = json_schema .is_null () ?"" :json_schema .dump (); 1293inputs .grammar = grammar ; 1294inputs .use_jinja = opt .use_jinja ; 1295inputs .parallel_tool_calls = json_value (body ,"parallel_tool_calls" ,caps ["supports_parallel_tool_calls" ]); 1296inputs .add_generation_prompt = json_value (body ,"add_generation_prompt" , true); 1297inputs .continue_final_message = body .contains ("continue_final_message" ) ? 1298common_chat_continuation_parse (body .at ("continue_final_message" )) : 1299COMMON_CHAT_CONTINUATION_NONE ; 1300if (inputs .continue_final_message == COMMON_CHAT_CONTINUATION_NONE && opt .prefill_assistant 1301&& !inputs .messages .empty ()&& inputs .messages .back ().role == "assistant" ) { 1302if (inputs .messages .size () >=2 && inputs .messages [inputs .messages .size ()- 2 ].role == "assistant" ) { 1303throw std::invalid_argument ("Cannot have 2 or more assistant messages at the end of the list." ); 1304 } 1305inputs .continue_final_message = COMMON_CHAT_CONTINUATION_AUTO ; 1306inputs .add_generation_prompt = false; 1307 } 1308if (inputs .continue_final_message != COMMON_CHAT_CONTINUATION_NONE && inputs .add_generation_prompt ) { 1309throw std::invalid_argument ("Cannot set both add_generation_prompt and continue_final_message to true." ); 1310 } 1311if (inputs .continue_final_message != COMMON_CHAT_CONTINUATION_NONE 1312&& !inputs .messages .empty () 1313&& inputs .messages .back ().role == "assistant" 1314&& !inputs .messages .back ().tool_calls .empty ()) { 1315throw std::invalid_argument ("Cannot continue an assistant message that contains tool calls." ); 1316 } 1317inputs .reasoning_format = opt .reasoning_format ; 1318if (body .contains ("reasoning_format" )) { 1319inputs .reasoning_format = common_reasoning_format_from_name (body .at ("reasoning_format" ).get < std::string > ()); 1320 } 1321inputs .enable_thinking = opt .enable_thinking ; 1322if (!inputs .tools .empty ()&& inputs .tool_choice != COMMON_CHAT_TOOL_CHOICE_NONE ) { 1323if (body .contains ("grammar" )) { 1324throw std::invalid_argument ("Cannot use custom grammar constraints with tools." ); 1325 } 1326llama_params ["parse_tool_calls" ]= true; 1327 } 1328 1329// merge the template args provided from command line with the args provided in the user request 1330auto chat_template_kwargs_object = json_value (body ,"chat_template_kwargs" , json::object ()); 1331inputs .chat_template_kwargs = opt .chat_template_kwargs ; 1332for (const auto & item :chat_template_kwargs_object .items ()) { 1333inputs .chat_template_kwargs [item .key ()]= item .value ().dump (); 1334 } 1335 1336// parse the "enable_thinking" kwarg to override the default value 1337auto enable_thinking_kwarg = json_value (inputs .chat_template_kwargs ,"enable_thinking" , std::string ("" )); 1338if (enable_thinking_kwarg == "true" ) { 1339inputs .enable_thinking = true; 1340 }else if (enable_thinking_kwarg == "false" ) { 1341inputs .enable_thinking = false; 1342 }else if (!enable_thinking_kwarg .empty ()&& enable_thinking_kwarg [0 ]== '"' ) { 1343throw std::invalid_argument ("invalid type for \"enable_thinking\" (expected boolean, got string)" ); 1344 } 1345 1346// Parse the OAI "reasoning_effort" field; "none" disables reasoning. 1347if (body .contains ("reasoning_effort" )) { 1348auto reasoning_effort = json_value (body ,"reasoning_effort" , std::string ("" )); 1349if (reasoning_effort == "none" ) { 1350inputs .enable_thinking = false; 1351inputs .chat_template_kwargs .erase ("reasoning_effort" ); 1352 }else if (!reasoning_effort .empty ()) { 1353inputs .chat_template_kwargs ["reasoning_effort" ]= json (reasoning_effort ).dump (); 1354 } 1355 } 1356 1357inputs .force_pure_content = opt .force_pure_content ; 1358 1359// Apply chat template to the list of messages 1360auto chat_params = common_chat_templates_apply (opt .tmpls .get (),inputs ); 1361 1362llama_params ["chat_format" ]= static_cast < int > (chat_params .format ); 1363llama_params ["prompt" ]= chat_params .prompt ; 1364if (!chat_params .grammar .empty ()) { 1365llama_params ["grammar" ]= chat_params .grammar ; 1366llama_params ["grammar_type" ]= std::string ("tool_calls" ); 1367 } 1368llama_params ["grammar_lazy" ]= chat_params .grammar_lazy ; 1369auto grammar_triggers = json::array (); 1370for (const auto & trigger :chat_params .grammar_triggers ) { 1371server_grammar_trigger ct (trigger ); 1372grammar_triggers .push_back (ct .to_json ()); 1373 } 1374llama_params ["grammar_triggers" ]= grammar_triggers ; 1375llama_params ["preserved_tokens" ]= chat_params .preserved_tokens ; 1376llama_params ["generation_prompt" ]= chat_params .generation_prompt ; 1377for (const auto & stop :chat_params .additional_stops ) { 1378llama_params ["stop" ].push_back (stop ); 1379 } 1380if (!chat_params .parser .empty ()) { 1381llama_params ["chat_parser" ]= chat_params .parser ; 1382 } 1383 1384llama_params ["message_delimiters" ]= chat_params .message_delimiters .to_json (); 1385 1386// Reasoning budget: pass parameters through to sampling layer 1387 { 1388int reasoning_budget = json_value (body ,"reasoning_budget_tokens" , 1389json_value (body ,"thinking_budget_tokens" ,-1 )); 1390if (reasoning_budget == -1 ) { 1391reasoning_budget = opt .reasoning_budget ; 1392 } 1393 1394if (!chat_params .thinking_end_tags .empty ()) { 1395llama_params ["reasoning_budget_tokens" ]= reasoning_budget ; 1396llama_params ["reasoning_budget_start_tag" ]= chat_params .thinking_start_tag ; 1397llama_params ["reasoning_budget_end_tags" ]= chat_params .thinking_end_tags ; 1398llama_params ["reasoning_budget_message" ]= json_value (body ,"reasoning_budget_message" ,opt .reasoning_budget_message ); 1399llama_params ["reasoning_control" ]= json_value (body ,"reasoning_control" , false); 1400 } 1401 } 1402 1403// Handle "logprobs" field 1404// TODO: The response format of this option is not yet OAI-compatible, but seems like no one really using it; We may need to fix it in the future 1405if (json_value (body ,"logprobs" , false)) { 1406if (has_tools && stream ) { 1407throw std::invalid_argument ("logprobs is not supported with tools + stream" ); 1408 } 1409llama_params ["n_probs" ]= json_value (body ,"top_logprobs" ,20 ); 1410 }else if (body .contains ("top_logprobs" )&& !body .at ("top_logprobs" ).is_null ()) { 1411throw std::invalid_argument ("top_logprobs requires logprobs to be set to true" ); 1412 } 1413 1414// Copy remaining properties to llama_params 1415// This allows user to use llama.cpp-specific params like "mirostat", ... via OAI endpoint. 1416// See "launch_slot_with_task()" for a complete list of params supported by llama.cpp 1417for (const auto & item :body .items ()) { 1418// Exception: if "n_predict" is present, we overwrite the value specified earlier by "max_tokens" 1419if (!llama_params .contains (item .key ())|| item .key ()== "n_predict" ) { 1420llama_params [item .key ()]= item .value (); 1421 } 1422 } 1423 1424return llama_params ; 1425} 1426 1427json format_embeddings_response_oaicompat ( 1428const json & request , 1429const std::string & model_name , 1430const json & embeddings , 1431bool use_base64 ) { 1432json data = json::array (); 1433int32_t n_tokens = 0 ; 1434int i = 0 ; 1435for (const auto & elem :embeddings ) { 1436json embedding_obj ; 1437 1438if (use_base64 ) { 1439const auto & vec = json_value (elem ,"embedding" , json::array ()).get < std::vector < float >> (); 1440const char * data_ptr = reinterpret_cast < const char *> (vec .data ()); 1441size_t data_size = vec .size ()* sizeof (float ); 1442embedding_obj = { 1443 {"embedding" , base64::encode (data_ptr ,data_size )}, 1444 {"index" ,i ++ }, 1445 {"object" ,"embedding" }, 1446 {"encoding_format" ,"base64" } 1447 }; 1448 }else { 1449embedding_obj = { 1450 {"embedding" ,json_value (elem ,"embedding" , json::array ())}, 1451 {"index" ,i ++ }, 1452 {"object" ,"embedding" } 1453 }; 1454 } 1455data .push_back (embedding_obj ); 1456 1457n_tokens += json_value (elem ,"tokens_evaluated" ,0 ); 1458 } 1459 1460json res = json { 1461 {"model" ,json_value (request ,"model" ,model_name )}, 1462 {"object" ,"list" }, 1463 {"usage" ,json { 1464 {"prompt_tokens" ,n_tokens }, 1465 {"total_tokens" ,n_tokens } 1466 }}, 1467 {"data" ,data } 1468 }; 1469 1470return res ; 1471} 1472 1473json format_response_rerank ( 1474const json & request , 1475const std::string & model_name , 1476const json & ranks , 1477bool is_tei_format , 1478 std::vector < std::string > & texts , 1479int top_n ) { 1480int32_t n_tokens = 0 ; 1481bool return_text = is_tei_format && json_value (request ,"return_text" , false); 1482 std::vector < json > elements ;// Temporary vector to hold unsorted elements 1483 std::string score_label = is_tei_format ?"score" :"relevance_score" ; 1484for (const auto & rank :ranks ) { 1485int index = json_value (rank ,"index" ,0 ); 1486json elem = json { 1487 {"index" ,index }, 1488 {score_label ,json_value (rank ,"score" ,0.0 )}, 1489 }; 1490n_tokens += json_value (rank ,"tokens_evaluated" ,0 ); 1491if (return_text ) { 1492elem ["text" ]= std::move (texts [index ]); 1493 } 1494elements .push_back (elem ); 1495 } 1496 1497 std::sort (elements .begin (),elements .end (), [score_label ](const json & a ,const json & b ) { 1498return json_value (a ,score_label ,0.0 )> json_value (b ,score_label ,0.0 ); 1499 }); 1500 1501elements .resize (std::min (top_n , (int )elements .size ())); 1502json results = elements ; 1503 1504if (is_tei_format )return results ; 1505 1506json res = json { 1507 {"model" ,json_value (request ,"model" ,model_name )}, 1508 {"object" ,"list" }, 1509 {"usage" ,json { 1510 {"prompt_tokens" ,n_tokens }, 1511 {"total_tokens" ,n_tokens } 1512 }}, 1513 {"results" ,results } 1514 }; 1515 1516return res ; 1517} 1518 1519 1520// 1521// other utils 1522// 1523 1524std::vector < llama_token_data > get_token_probabilities (llama_context * ctx ,int idx ,size_t n_top ) { 1525 std::vector < llama_token_data > cur ; 1526 1527const auto * logits = llama_get_logits_ith (ctx ,idx ); 1528const llama_token * sampled_ids = llama_get_sampled_candidates_ith (ctx ,idx ); 1529 1530const int n_logits = llama_get_sampled_logits_count_ith (ctx ,idx ); 1531 1532cur .resize (n_logits ); 1533if (sampled_ids ) { 1534for (int i = 0 ;i < n_logits ;i ++ ) { 1535cur [i ]= llama_token_data {sampled_ids [i ],logits [i ],0.0f }; 1536 } 1537 }else { 1538for (llama_token token_id = 0 ;token_id < n_logits ;token_id ++ ) { 1539cur [token_id ]= llama_token_data {token_id ,logits [token_id ],0.0f }; 1540 } 1541 } 1542 1543// sort tokens by logits (partial: only the leading `n_top` need ordering) 1544if (n_top > cur .size ()) { 1545n_top = cur .size (); 1546 } 1547if (n_top > 0 ) { 1548 std::partial_sort (cur .begin (),cur .begin ()+ n_top ,cur .end (), 1549 [](const llama_token_data & a ,const llama_token_data & b ) { 1550return a .logit > b .logit ; 1551 }); 1552 } 1553 1554// apply softmax 1555float max_l = - std::numeric_limits < float > ::infinity (); 1556if (n_top > 0 ) { 1557max_l = cur [0 ].logit ;// partial_sort guarantees the absolute maximum is at index 0 1558 }else { 1559for (const auto & t :cur ) { 1560max_l = std::max (max_l ,t .logit ); 1561 } 1562 } 1563float cum_sum = 0.0f ; 1564for (auto & t :cur ) { 1565float p = expf (t .logit - max_l ); 1566t .p = p ; 1567cum_sum += p ; 1568 } 1569for (auto & t :cur ) { 1570t .p /=cum_sum ; 1571 } 1572 1573return cur ; 1574} 1575 1576std::string safe_json_to_str (const json & data ) { 1577return data .dump_safe (); 1578} 1579 1580// TODO: reuse llama_detokenize 1581template < class Iter > 1582static std::string tokens_to_str (const llama_vocab * ctx ,Iter begin ,Iter end ) { 1583 std::string ret ; 1584for (;begin != end ;++ begin ) { 1585ret += common_token_to_piece (ctx ,* begin ); 1586 } 1587 1588return ret ; 1589} 1590 1591std::string tokens_to_str (llama_context * ctx ,const llama_tokens & tokens ) { 1592auto model = llama_get_model (ctx ); 1593return tokens_to_str (llama_model_get_vocab (model ),tokens .begin (),tokens .end ()); 1594} 1595 1596std::string tokens_to_str (const llama_vocab * vocab ,const llama_tokens & tokens ) { 1597return tokens_to_str (vocab ,tokens .begin (),tokens .end ()); 1598} 1599 1600// format incomplete utf-8 multibyte character for output 1601std::string tokens_to_output_formatted_string (const llama_context * ctx ,const llama_token token ) { 1602 std::string out = token == LLAMA_TOKEN_NULL ?"" :common_token_to_piece (ctx ,token ); 1603 1604// if the size is 1 and first bit is 1, meaning it's a partial character 1605// (size > 1 meaning it's already a known token) 1606if (out .size ()== 1 && (out [0 ]& 0x80 )== 0x80 ) { 1607 std::stringstream ss ; 1608ss << std::hex << (out [0 ]& 0xff ); 1609 std::string res (ss .str ()); 1610out = "byte: \\x" + res ; 1611 } 1612 1613return out ; 1614} 1615 1616// format server-sent event (SSE), return the formatted string to send 1617// note: if data is a json array, it will be sent as multiple events, one per item 1618std::string format_oai_sse (const json & data ) { 1619 std::ostringstream ss ; 1620auto send_single = [& ss ](const json & data ) { 1621ss <<"data: " << 1622safe_json_to_str (data ) << 1623"\n\n" ;// required by RFC 8895 - A message is terminated by a blank line (two line terminators in a row). 1624 }; 1625 1626if (data .is_array ()) { 1627for (const auto & item :data ) { 1628send_single (item ); 1629 } 1630 }else { 1631send_single (data ); 1632 } 1633 1634return ss .str (); 1635} 1636 1637std::string format_oai_resp_sse (const json & data ) { 1638 std::ostringstream ss ; 1639auto send_single = [& ss ](const json & event_obj ) { 1640ss <<"event: " <<event_obj .at ("event" ).get < std::string > () <<"\n" ; 1641ss <<"data: " <<safe_json_to_str (event_obj .at ("data" )) <<"\n\n" ; 1642 }; 1643 1644if (data .is_array ()) { 1645for (const auto & item :data ) { 1646send_single (item ); 1647 } 1648 }else { 1649send_single (data ); 1650 } 1651 1652return ss .str (); 1653} 1654 1655std::string format_anthropic_sse (const json & data ) { 1656 std::ostringstream ss ; 1657 1658auto send_event = [& ss ](const json & event_obj ) { 1659if (event_obj .contains ("event" )&& event_obj .contains ("data" )) { 1660ss <<"event: " <<event_obj .at ("event" ).get < std::string > () <<"\n" ; 1661ss <<"data: " <<safe_json_to_str (event_obj .at ("data" )) <<"\n\n" ; 1662 }else { 1663ss <<"data: " <<safe_json_to_str (event_obj ) <<"\n\n" ; 1664 } 1665 }; 1666 1667if (data .is_array ()) { 1668for (const auto & event :data ) { 1669send_event (event ); 1670 } 1671 }else { 1672send_event (data ); 1673 } 1674 1675return ss .str (); 1676} 1677 1678bool is_valid_utf8 (const std::string & str ) { 1679const unsigned char * bytes = reinterpret_cast < const unsigned char *> (str .data ()); 1680const unsigned char * end = bytes + str .length (); 1681 1682while (bytes < end ) { 1683if (* bytes <=0x7F ) { 1684// 1-byte sequence (0xxxxxxx) 1685bytes ++ ; 1686 }else if ((* bytes & 0xE0 )== 0xC0 ) { 1687// 2-byte sequence (110xxxxx 10xxxxxx) 1688if (end - bytes < 2 || (bytes [1 ]& 0xC0 )!= 0x80 ) 1689return false; 1690bytes += 2 ; 1691 }else if ((* bytes & 0xF0 )== 0xE0 ) { 1692// 3-byte sequence (1110xxxx 10xxxxxx 10xxxxxx) 1693if (end - bytes < 3 || (bytes [1 ]& 0xC0 )!= 0x80 || (bytes [2 ]& 0xC0 )!= 0x80 ) 1694return false; 1695bytes += 3 ; 1696 }else if ((* bytes & 0xF8 )== 0xF0 ) { 1697// 4-byte sequence (11110xxx 10xxxxxx 10xxxxxx 10xxxxxx) 1698if (end - bytes < 4 || (bytes [1 ]& 0xC0 )!= 0x80 || 1699 (bytes [2 ]& 0xC0 )!= 0x80 || (bytes [3 ]& 0xC0 )!= 0x80 ) 1700return false; 1701bytes += 4 ; 1702 }else { 1703// Invalid UTF-8 lead byte 1704return false; 1705 } 1706 } 1707 1708return true; 1709} 1710 1711llama_tokens format_prompt_infill ( 1712const llama_vocab * vocab , 1713const json & input_prefix , 1714const json & input_suffix , 1715const json & input_extra , 1716const int n_batch , 1717const int n_predict , 1718const int n_ctx , 1719const bool spm_infill , 1720const llama_tokens & tokens_prompt 1721 ) { 1722// TODO: optimize this block by reducing memory allocations and movement 1723 1724// use FIM repo-level pattern: 1725// ref: https://arxiv.org/pdf/2409.12186 1726// 1727// [FIM_REP]myproject 1728// [FIM_SEP]filename0 1729// extra chunk 0 1730// [FIM_SEP]filename1 1731// extra chunk 1 1732// ... 1733// [FIM_SEP]filename 1734// [FIM_PRE]prefix[FIM_SUF]suffix[FIM_MID]prompt 1735// 1736llama_tokens extra_tokens ; 1737extra_tokens .reserve (n_ctx ); 1738 1739auto tokens_prefix = tokenize_mixed (vocab ,input_prefix , false, false); 1740auto tokens_suffix = tokenize_mixed (vocab ,input_suffix , false, false); 1741 1742if (llama_vocab_fim_rep (vocab )!= LLAMA_TOKEN_NULL ) { 1743// TODO: make project name an input 1744static const auto k_fim_repo = common_tokenize (vocab ,"myproject\n" , false, false); 1745 1746extra_tokens .push_back (llama_vocab_fim_rep (vocab )); 1747extra_tokens .insert (extra_tokens .end (),k_fim_repo .begin (),k_fim_repo .end ()); 1748 } 1749for (const auto & chunk :input_extra ) { 1750// { "text": string, "filename": string } 1751const std::string text = json_value (chunk ,"text" , std::string ()); 1752const std::string filename = json_value (chunk ,"filename" , std::string ("tmp" )); 1753 1754if (llama_vocab_fim_sep (vocab )!= LLAMA_TOKEN_NULL ) { 1755const auto k_fim_file = common_tokenize (vocab ,filename + "\n" , false, false); 1756 1757extra_tokens .insert (extra_tokens .end (),llama_vocab_fim_sep (vocab )); 1758extra_tokens .insert (extra_tokens .end (),k_fim_file .begin (),k_fim_file .end ()); 1759 }else { 1760// chunk separator in binary form to avoid confusing the AI 1761static const char k_chunk_prefix_str []= {0x0a ,0x0a ,0x2d ,0x2d ,0x2d ,0x20 ,0x73 ,0x6e ,0x69 ,0x70 ,0x70 ,0x65 ,0x74 ,0x20 ,0x2d ,0x2d ,0x2d ,0x0a ,0x0a ,0x00 }; 1762static const auto k_chunk_prefix_tokens = common_tokenize (vocab ,k_chunk_prefix_str , false, false); 1763 1764extra_tokens .insert (extra_tokens .end (),k_chunk_prefix_tokens .begin (),k_chunk_prefix_tokens .end ()); 1765 } 1766 1767const auto chunk_tokens = common_tokenize (vocab ,text , false, false); 1768extra_tokens .insert (extra_tokens .end (),chunk_tokens .begin (),chunk_tokens .end ()); 1769 } 1770 1771if (llama_vocab_fim_sep (vocab )!= LLAMA_TOKEN_NULL ) { 1772// TODO: current filename 1773static const auto k_fim_file = common_tokenize (vocab ,"filename\n" , false, false); 1774 1775extra_tokens .insert (extra_tokens .end (),llama_vocab_fim_sep (vocab )); 1776extra_tokens .insert (extra_tokens .end (),k_fim_file .begin (),k_fim_file .end ()); 1777 } 1778 1779// for now pick FIM context to fit in a batch (ratio prefix:suffix = 3:1, TODO: configurable?) 1780const int n_prefix_take = std::min < int > (tokens_prefix .size (),3 * (n_batch /4 )); 1781const int n_suffix_take = std::min < int > (tokens_suffix .size (), std::max < int > (0 , (n_batch /4 )- (2 + tokens_prompt .size ()))); 1782 1783SRV_DBG ("n_prefix_take = %d, n_suffix_take = %d, total = %d\n" ,n_prefix_take ,n_suffix_take , (n_prefix_take + n_suffix_take )); 1784 1785// fill the rest of the context with extra chunks 1786const int n_extra_take = std::min < int > (std::max < int > (0 ,n_ctx - (n_batch )- 2 * n_predict ),extra_tokens .size ()); 1787 1788tokens_prefix .erase (tokens_prefix .begin (),tokens_prefix .begin ()+ tokens_prefix .size ()- n_prefix_take ); 1789tokens_suffix .resize (n_suffix_take ); 1790 1791tokens_prefix .insert (tokens_prefix .begin (),llama_vocab_fim_pre (vocab )); 1792tokens_prefix .insert (tokens_prefix .end (),tokens_prompt .begin (),tokens_prompt .end ()); 1793tokens_suffix .insert (tokens_suffix .begin (),llama_vocab_fim_suf (vocab )); 1794 1795auto embd_inp = spm_infill ?tokens_suffix :tokens_prefix ; 1796auto embd_end = spm_infill ?tokens_prefix :tokens_suffix ; 1797 1798if (llama_vocab_get_add_bos (vocab )) { 1799embd_inp .insert (embd_inp .begin (),llama_vocab_bos (vocab )); 1800 } 1801 1802SRV_DBG ("extra: n_ctx = %d, n_extra_take = %d, n_extra = %d\n" ,n_ctx ,n_extra_take , (int )extra_tokens .size ()); 1803 1804// put the extra context before the FIM prefix 1805embd_inp .insert (embd_inp .begin (),extra_tokens .end ()- n_extra_take ,extra_tokens .end ()); 1806 1807embd_inp .insert (embd_inp .end (),embd_end .begin (),embd_end .end ()); 1808embd_inp .push_back (llama_vocab_fim_mid (vocab )); 1809 1810return embd_inp ; 1811} 1812 1813server_tokens format_prompt_rerank ( 1814const struct llama_model * model , 1815const struct llama_vocab * vocab , 1816mtmd_context * mctx , 1817const std::string & query , 1818const std::string & doc , 1819const mtmd_helper_init_opt & init_opt ) { 1820server_tokens result = {}; 1821 1822const char * rerank_prompt = llama_model_chat_template (model ,"rerank" ); 1823 1824if (rerank_prompt != nullptr ) { 1825 std::string prompt = rerank_prompt ; 1826string_replace_all (prompt ,"{query}" ,query ); 1827string_replace_all (prompt ,"{document}" ,doc ); 1828server_tokens tokens = tokenize_input_subprompt (vocab ,mctx ,prompt , false, true,init_opt ); 1829result .push_back (tokens ); 1830 }else { 1831// Get EOS token - use SEP token as fallback if EOS is not available 1832server_tokens query_tokens = tokenize_input_subprompt (vocab ,mctx ,query , false, false,init_opt ); 1833server_tokens doc_tokens = tokenize_input_subprompt (vocab ,mctx ,doc , false, false,init_opt ); 1834llama_token eos_token = llama_vocab_eos (vocab ); 1835if (eos_token == LLAMA_TOKEN_NULL ) { 1836eos_token = llama_vocab_sep (vocab ); 1837 } 1838 1839if (llama_vocab_get_add_bos (vocab )) { 1840result .push_back (llama_vocab_bos (vocab )); 1841 } 1842result .push_back (query_tokens ); 1843if (llama_vocab_get_add_eos (vocab )) { 1844result .push_back (eos_token ); 1845 } 1846if (llama_vocab_get_add_sep (vocab )) { 1847result .push_back (llama_vocab_sep (vocab )); 1848 } 1849result .push_back (doc_tokens ); 1850if (llama_vocab_get_add_eos (vocab )) { 1851result .push_back (eos_token ); 1852 } 1853 } 1854 1855return result ; 1856} 1857 1858// 1859// server_subproc 1860// 1861 1862bool server_subproc::has_output () { 1863if (out_handle >=0 ) { 1864return true; 1865 } 1866FILE * f = sproc .stdout_file ();// combined stdout/stderr 1867if (!f ) { 1868return false; 1869 } 1870#ifdef _WIN32 1871HANDLE h = (HANDLE )_get_osfhandle (_fileno (f )); 1872if (h != INVALID_HANDLE_VALUE ) { 1873out_handle = (intptr_t )h ; 1874 } 1875#else 1876int fd = fileno (f ); 1877if (fd >=0 ) { 1878fcntl (fd ,F_SETFL ,fcntl (fd ,F_GETFL ,0 ) |O_NONBLOCK ); 1879out_handle = fd ; 1880 } 1881#endif 1882return out_handle >=0 ; 1883} 1884 1885int server_subproc::read_output (char * buf ,size_t len ) { 1886if (!has_output ()) { 1887return -1 ; 1888 } 1889#ifdef _WIN32 1890HANDLE h = (HANDLE )out_handle ; 1891DWORD avail = 0 ; 1892if (!PeekNamedPipe (h ,NULL ,0 ,NULL ,& avail ,NULL )) { 1893return -1 ;// pipe broken, child gone 1894 } 1895if (avail == 0 ) { 1896return 0 ; 1897 } 1898DWORD to_read = avail < (DWORD )len ?avail : (DWORD )len ; 1899DWORD got = 0 ; 1900if (!ReadFile (h ,buf ,to_read ,& got ,NULL )|| got == 0 ) { 1901return -1 ; 1902 } 1903return (int )got ; 1904#else 1905while (true) { 1906ssize_t r = read ((int )out_handle ,buf ,len ); 1907if (r > 0 ) { 1908return (int )r ; 1909 } 1910if (r == 0 ) { 1911return -1 ;// EOF 1912 } 1913if (errno == EINTR ) { 1914continue ; 1915 } 1916if (errno == EAGAIN || errno == EWOULDBLOCK ) { 1917return 0 ; 1918 } 1919return -1 ; 1920 } 1921#endif 1922} 1923 1924server_subproc::waiter::waiter () { 1925#ifndef _WIN32 1926int fds [2 ]; 1927GGML_ASSERT (pipe (fds )== 0 ); 1928for (int fd :fds ) { 1929fcntl (fd ,F_SETFL ,fcntl (fd ,F_GETFL ,0 ) |O_NONBLOCK ); 1930 } 1931wake_fd [0 ]= fds [0 ]; 1932wake_fd [1 ]= fds [1 ]; 1933#endif 1934} 1935 1936server_subproc::waiter::~waiter () { 1937#ifndef _WIN32 1938close ((int )wake_fd [0 ]); 1939close ((int )wake_fd [1 ]); 1940#endif 1941} 1942 1943void server_subproc::waiter::wake () { 1944#ifndef _WIN32 1945char c = 1 ; 1946 (void ) !write ((int )wake_fd [1 ],& c ,1 ); 1947#endif 1948} 1949 1950void server_subproc::waiter::wait (const std::vector < server_subproc *> & procs , std::vector < bool > & ready ,int64_t timeout_ms ) { 1951ready .assign (procs .size (), false); 1952#ifdef _WIN32 1953// no waitable wait exists for anonymous pipes, so poll them in 50 ms steps 1954bool any = false; 1955for (size_t i = 0 ;i < procs .size ();i ++ ) { 1956DWORD avail = 0 ; 1957if (!procs [i ]-> has_output ()|| !PeekNamedPipe ((HANDLE )procs [i ]-> out_handle ,NULL ,0 ,NULL ,& avail ,NULL )|| avail > 0 ) { 1958ready [i ]= true;// data or broken pipe, read_output() tells which 1959any = true; 1960 } 1961 } 1962if (!any ) { 1963int64_t step = timeout_ms < 0 ?50 : std::min < int64_t > (timeout_ms ,50 ); 1964 std::this_thread::sleep_for (std::chrono::milliseconds (step )); 1965 } 1966#else 1967 std::vector < pollfd > pfds ; 1968pfds .reserve (procs .size ()+ 1 ); 1969pfds .push_back ({ (int )wake_fd [0 ],POLLIN ,0 }); 1970for (auto * p :procs ) { 1971pfds .push_back ({p -> has_output () ? (int )p -> out_handle :-1 ,POLLIN ,0 });// poll() skips negative fds 1972 } 1973int timeout = timeout_ms < 0 ?-1 : (int ) std::min < int64_t > (timeout_ms , std::numeric_limits < int > ::max ()); 1974int r = poll (pfds .data (),pfds .size (),timeout ); 1975if (r < 0 && errno != EINTR ) { 1976LOG_ERR ("%s: poll() failed: %s\n" ,__func__ ,strerror (errno )); 1977 } 1978if (pfds [0 ].revents ) { 1979char buf [64 ]; 1980while (read ((int )wake_fd [0 ],buf ,sizeof (buf ))> 0 ) {} 1981 } 1982for (size_t i = 0 ;i < procs .size ();i ++ ) { 1983ready [i ]= pfds [i + 1 ].fd < 0 || pfds [i + 1 ].revents != 0 ; 1984 } 1985#endif 1986}