2023-09-28 19:04:36 +03:00
// A basic application simulating a server with multiple clients.
2023-11-23 12:34:20 +01:00
// The clients submit requests to the server and they are processed in parallel.
2023-09-28 19:04:36 +03:00
#include "common.h"
#include "llama.h"
#include <cmath>
#include <cstdio>
#include <string>
#include <vector>
2023-10-06 14:16:38 +01:00
#include <ctime>
2023-09-28 19:04:36 +03:00
// trim whitespace from the beginning and end of a string
static std :: string trim ( const std :: string & str ) {
size_t start = 0 ;
size_t end = str . size ();
while ( start < end && isspace ( str [ start ])) {
start += 1 ;
}
while ( end > start && isspace ( str [ end - 1 ])) {
end -= 1 ;
}
return str . substr ( start , end - start );
}
static std :: string k_system =
R "(Transcript of a never ending dialog, where the User interacts with an Assistant.
The Assistant is helpful , kind , honest , good at writing , and never fails to answer the User ' s requests immediately and with precision .
User : Recommend a nice restaurant in the area .
Assistant : I recommend the restaurant "The Golden Duck" . It is a 5 star restaurant with a great view of the city . The food is delicious and the service is excellent . The prices are reasonable and the portions are generous . The restaurant is located at 123 Main Street , New York , NY 10001. The phone number is ( 212 ) 555 - 1234. The hours are Monday through Friday from 11 : 00 am to 10 : 00 pm . The restaurant is closed on Saturdays and Sundays .
User : Who is Richard Feynman ?
Assistant : Richard Feynman was an American physicist who is best known for his work in quantum mechanics and particle physics . He was awarded the Nobel Prize in Physics in 1965 for his contributions to the development of quantum electrodynamics . He was a popular lecturer and author , and he wrote several books , including "Surely You're Joking, Mr. Feynman!" and "What Do You Care What Other People Think?" .
User :) ";
static std :: vector < std :: string > k_prompts = {
"What is the meaning of life?" ,
"Tell me an interesting fact about llamas." ,
"What is the best way to cook a steak?" ,
"Are you familiar with the Special Theory of Relativity and can you explain it to me?" ,
"Recommend some interesting books to read." ,
"What is the best way to learn a new language?" ,
"How to get a job at Google?" ,
"If you could have any superpower, what would it be?" ,
"I want to learn how to play the piano." ,
};
struct client {
2023-10-18 16:21:57 +03:00
~ client () {
if ( ctx_sampling ) {
llama_sampling_free ( ctx_sampling );
}
}
2023-09-28 19:04:36 +03:00
int32_t id = 0 ;
llama_seq_id seq_id = - 1 ;
llama_token sampled ;
int64_t t_start_prompt ;
int64_t t_start_gen ;
int32_t n_prompt = 0 ;
int32_t n_decoded = 0 ;
int32_t i_batch = - 1 ;
std :: string input ;
std :: string prompt ;
std :: string response ;
2023-10-18 16:21:57 +03:00
struct llama_sampling_context * ctx_sampling = nullptr ;
2023-09-28 19:04:36 +03:00
};
2023-10-06 14:16:38 +01:00
static void print_date_time () {
std :: time_t current_time = std :: time ( nullptr );
std :: tm * local_time = std :: localtime ( & current_time );
char buffer [ 80 ];
strftime ( buffer , sizeof ( buffer ), "%Y-%m-%d %H:%M:%S" , local_time );
printf ( " \n\033 [35mrun parameters as at %s \033 [0m \n " , buffer );
}
// Define a split string function to ...
static std :: vector < std :: string > split_string ( const std :: string & input , char delimiter ) {
std :: vector < std :: string > tokens ;
std :: istringstream stream ( input );
std :: string token ;
while ( std :: getline ( stream , token , delimiter )) {
tokens . push_back ( token );
}
return tokens ;
}
2023-09-28 19:04:36 +03:00
int main ( int argc , char ** argv ) {
srand ( 1234 );
gpt_params params ;
if ( gpt_params_parse ( argc , argv , params ) == false ) {
return 1 ;
}
// number of simultaneous "clients" to simulate
const int32_t n_clients = params . n_parallel ;
2024-03-08 17:31:00 -05:00
// dedicate one sequence to the system prompt
params . n_parallel += 1 ;
2023-09-28 19:04:36 +03:00
// requests to simulate
const int32_t n_seq = params . n_sequences ;
// insert new requests as soon as the previous one is done
const bool cont_batching = params . cont_batching ;
2023-11-23 19:07:56 +02:00
const bool dump_kv_cache = params . dump_kv_cache ;
2023-09-28 19:04:36 +03:00
#ifndef LOG_DISABLE_LOGS
log_set_target ( log_filename_generator ( "parallel" , "log" ));
LOG_TEE ( "Log start \n " );
log_dump_cmdline ( argc , argv );
#endif // LOG_DISABLE_LOGS
// init llama.cpp
2024-02-16 01:31:07 -08:00
llama_backend_init ();
llama_numa_init ( params . numa );
2023-09-28 19:04:36 +03:00
llama_model * model = NULL ;
llama_context * ctx = NULL ;
// load the target model
std :: tie ( model , ctx ) = llama_init_from_gpt_params ( params );
2023-10-06 14:16:38 +01:00
// load the prompts from an external file if there are any
if ( params . prompt . empty ()) {
printf ( " \n\033 [32mNo new questions so proceed with build-in defaults. \033 [0m \n " );
} else {
// Output each line of the input params.prompts vector and copy to k_prompts
int index = 0 ;
printf ( " \n\033 [32mNow printing the external prompt file %s \033 [0m \n\n " , params . prompt_file . c_str ());
std :: vector < std :: string > prompts = split_string ( params . prompt , '\n' );
for ( const auto & prompt : prompts ) {
k_prompts . resize ( index + 1 );
k_prompts [ index ] = prompt ;
index ++ ;
printf ( "%3d prompt: %s \n " , index , prompt . c_str ());
}
}
2023-09-28 19:04:36 +03:00
fprintf ( stderr , " \n\n " );
fflush ( stderr );
2023-10-18 16:21:57 +03:00
const int n_ctx = llama_n_ctx ( ctx );
2023-09-28 19:04:36 +03:00
std :: vector < client > clients ( n_clients );
for ( size_t i = 0 ; i < clients . size (); ++ i ) {
auto & client = clients [ i ];
client . id = i ;
2023-10-20 21:07:23 +03:00
client . ctx_sampling = llama_sampling_init ( params . sparams );
2023-09-28 19:04:36 +03:00
}
std :: vector < llama_token > tokens_system ;
tokens_system = :: llama_tokenize ( ctx , k_system , true );
const int32_t n_tokens_system = tokens_system . size ();
llama_seq_id g_seq_id = 0 ;
// the max batch size is as large as the context to handle cases where we get very long input prompt from multiple
// users. regardless of the size, the main loop will chunk the batch into a maximum of params.n_batch tokens at a time
2023-10-18 16:21:57 +03:00
llama_batch batch = llama_batch_init ( n_ctx , 0 , 1 );
2023-09-28 19:04:36 +03:00
int32_t n_total_prompt = 0 ;
int32_t n_total_gen = 0 ;
int32_t n_cache_miss = 0 ;
2023-11-23 19:07:56 +02:00
struct llama_kv_cache_view kvc_view = llama_kv_cache_view_init ( ctx , n_clients );
2023-09-28 19:04:36 +03:00
const auto t_main_start = ggml_time_us ();
LOG_TEE ( "%s: Simulating parallel requests from clients: \n " , __func__ );
LOG_TEE ( "%s: n_parallel = %d, n_sequences = %d, cont_batching = %d, system tokens = %d \n " , __func__ , n_clients , n_seq , cont_batching , n_tokens_system );
LOG_TEE ( " \n " );
{
LOG_TEE ( "%s: Evaluating the system prompt ... \n " , __func__ );
2023-10-18 16:21:57 +03:00
for ( int32_t i = 0 ; i < n_tokens_system ; ++ i ) {
llama_batch_add ( batch , tokens_system [ i ], i , { 0 }, false );
2023-09-28 19:04:36 +03:00
}
2023-09-28 21:42:38 +02:00
if ( llama_decode ( ctx , batch ) != 0 ) {
2023-09-28 19:04:36 +03:00
LOG_TEE ( "%s: llama_decode() failed \n " , __func__ );
return 1 ;
}
// assign the system KV cache to all parallel sequences
2024-03-08 17:31:00 -05:00
for ( int32_t i = 1 ; i <= n_clients ; ++ i ) {
llama_kv_cache_seq_cp ( ctx , 0 , i , - 1 , - 1 );
2023-09-28 19:04:36 +03:00
}
LOG_TEE ( " \n " );
}
LOG_TEE ( "Processing requests ... \n\n " );
while ( true ) {
2023-11-23 19:07:56 +02:00
if ( dump_kv_cache ) {
llama_kv_cache_view_update ( ctx , & kvc_view );
2024-05-22 20:04:20 +03:00
llama_kv_cache_dump_view_seqs ( kvc_view , 40 );
2023-11-23 19:07:56 +02:00
}
2023-10-18 16:21:57 +03:00
llama_batch_clear ( batch );
2023-09-28 19:04:36 +03:00
// decode any currently ongoing sequences
for ( auto & client : clients ) {
if ( client . seq_id == - 1 ) {
continue ;
}
client . i_batch = batch . n_tokens ;
2024-03-08 17:31:00 -05:00
llama_batch_add ( batch , client . sampled , n_tokens_system + client . n_prompt + client . n_decoded , { client . id + 1 }, true );
2023-10-18 16:21:57 +03:00
client . n_decoded += 1 ;
2023-09-28 19:04:36 +03:00
}
if ( batch . n_tokens == 0 ) {
// all sequences have ended - clear the entire KV cache
2024-03-08 17:31:00 -05:00
for ( int i = 1 ; i <= n_clients ; ++ i ) {
llama_kv_cache_seq_rm ( ctx , i , - 1 , - 1 );
// but keep the system prompt
llama_kv_cache_seq_cp ( ctx , 0 , i , - 1 , - 1 );
2023-09-28 19:04:36 +03:00
}
LOG_TEE ( "%s: clearing the KV cache \n " , __func__ );
}
// insert new sequences for decoding
if ( cont_batching || batch . n_tokens == 0 ) {
for ( auto & client : clients ) {
if ( client . seq_id == - 1 && g_seq_id < n_seq ) {
client . seq_id = g_seq_id ;
client . t_start_prompt = ggml_time_us ();
client . t_start_gen = 0 ;
client . input = k_prompts [ rand () % k_prompts . size ()];
client . prompt = client . input + " \n Assistant:" ;
client . response = "" ;
2023-10-18 16:21:57 +03:00
llama_sampling_reset ( client . ctx_sampling );
2023-09-28 19:04:36 +03:00
// do not prepend BOS because we have a system prompt!
std :: vector < llama_token > tokens_prompt ;
tokens_prompt = :: llama_tokenize ( ctx , client . prompt , false );
for ( size_t i = 0 ; i < tokens_prompt . size (); ++ i ) {
2024-03-08 17:31:00 -05:00
llama_batch_add ( batch , tokens_prompt [ i ], i + n_tokens_system , { client . id + 1 }, false );
2023-09-28 19:04:36 +03:00
}
// extract the logits only for the last token
if ( batch . n_tokens > 0 ) {
batch . logits [ batch . n_tokens - 1 ] = true ;
}
client . n_prompt = tokens_prompt . size ();
client . n_decoded = 0 ;
client . i_batch = batch . n_tokens - 1 ;
2023-10-06 14:16:38 +01:00
LOG_TEE ( " \033 [31mClient %3d, seq %4d, started decoding ... \033 [0m \n " , client . id , client . seq_id );
2023-09-28 19:04:36 +03:00
g_seq_id += 1 ;
// insert new requests one-by-one
//if (cont_batching) {
// break;
//}
}
}
}
if ( batch . n_tokens == 0 ) {
break ;
}
// process in chunks of params.n_batch
int32_t n_batch = params . n_batch ;
for ( int32_t i = 0 ; i < ( int32_t ) batch . n_tokens ; i += n_batch ) {
// experiment: process in powers of 2
//if (i + n_batch > (int32_t) batch.n_tokens && n_batch > 32) {
// n_batch /= 2;
// i -= n_batch;
// continue;
//}
const int32_t n_tokens = std :: min ( n_batch , ( int32_t ) ( batch . n_tokens - i ));
llama_batch batch_view = {
n_tokens ,
2023-10-18 16:21:57 +03:00
batch . token + i ,
2023-09-28 19:04:36 +03:00
nullptr ,
2023-10-18 16:21:57 +03:00
batch . pos + i ,
batch . n_seq_id + i ,
batch . seq_id + i ,
batch . logits + i ,
2023-09-28 19:04:36 +03:00
0 , 0 , 0 , // unused
};
2023-09-28 21:42:38 +02:00
const int ret = llama_decode ( ctx , batch_view );
2023-09-28 19:04:36 +03:00
if ( ret != 0 ) {
if ( n_batch == 1 || ret < 0 ) {
// if you get here, it means the KV cache is full - try increasing it via the context size
LOG_TEE ( "%s : failed to decode the batch, n_batch = %d, ret = %d \n " , __func__ , n_batch , ret );
return 1 ;
}
LOG ( "%s : failed to decode the batch, retrying with n_batch = %d \n " , __func__ , n_batch / 2 );
n_cache_miss += 1 ;
// retry with half the batch size to try to find a free slot in the KV cache
n_batch /= 2 ;
i -= n_batch ;
continue ;
}
LOG ( "%s : decoded batch of %d tokens \n " , __func__ , n_tokens );
for ( auto & client : clients ) {
if ( client . i_batch < ( int ) i || client . i_batch >= ( int ) ( i + n_tokens )) {
continue ;
}
//printf("client %d, seq %d, token %d, pos %d, batch %d\n",
// client.id, client.seq_id, client.sampled, client.n_decoded, client.i_batch);
2023-10-18 16:21:57 +03:00
const llama_token id = llama_sampling_sample ( client . ctx_sampling , ctx , NULL , client . i_batch - i );
2023-10-20 21:07:23 +03:00
llama_sampling_accept ( client . ctx_sampling , ctx , id , true );
2023-09-28 19:04:36 +03:00
if ( client . n_decoded == 1 ) {
// start measuring generation time after the first token to make sure all concurrent clients
// have their prompt already processed
client . t_start_gen = ggml_time_us ();
}
const std :: string token_str = llama_token_to_piece ( ctx , id );
2023-10-18 16:21:57 +03:00
2023-09-28 19:04:36 +03:00
client . response += token_str ;
client . sampled = id ;
//printf("client %d, seq %d, token %d, pos %d, batch %d: %s\n",
// client.id, client.seq_id, id, client.n_decoded, client.i_batch, token_str.c_str());
if ( client . n_decoded > 2 &&
2024-04-21 13:50:41 +02:00
( llama_token_is_eog ( model , id ) ||
2023-09-28 19:04:36 +03:00
( params . n_predict > 0 && client . n_decoded + client . n_prompt >= params . n_predict ) ||
client . response . find ( "User:" ) != std :: string :: npos ||
client . response . find ( '\n' ) != std :: string :: npos )) {
// basic reverse prompt
const size_t pos = client . response . find ( "User:" );
if ( pos != std :: string :: npos ) {
client . response = client . response . substr ( 0 , pos );
}
// delete only the generated part of the sequence, i.e. keep the system prompt in the cache
2024-03-08 17:31:00 -05:00
llama_kv_cache_seq_rm ( ctx , client . id + 1 , - 1 , - 1 );
llama_kv_cache_seq_cp ( ctx , 0 , client . id + 1 , - 1 , - 1 );
2023-09-28 19:04:36 +03:00
const auto t_main_end = ggml_time_us ();
2023-10-06 14:16:38 +01:00
LOG_TEE ( " \033 [31mClient %3d, seq %3d/%3d, prompt %4d t, response %4d t, time %5.2f s, speed %5.2f t/s, cache miss %d \033 [0m \n Input: %s \n\033 [35mResponse: %s \033 [0m \n\n " ,
client . id , client . seq_id , n_seq , client . n_prompt , client . n_decoded ,
2023-09-28 19:04:36 +03:00
( t_main_end - client . t_start_prompt ) / 1e6 ,
( double ) ( client . n_prompt + client . n_decoded ) / ( t_main_end - client . t_start_prompt ) * 1e6 ,
n_cache_miss ,
:: trim ( client . input ). c_str (),
:: trim ( client . response ). c_str ());
n_total_prompt += client . n_prompt ;
n_total_gen += client . n_decoded ;
2023-10-18 16:21:57 +03:00
2023-09-28 19:04:36 +03:00
client . seq_id = - 1 ;
}
client . i_batch = - 1 ;
}
}
}
const auto t_main_end = ggml_time_us ();
2023-10-06 14:16:38 +01:00
print_date_time ();
LOG_TEE ( " \n %s: n_parallel = %d, n_sequences = %d, cont_batching = %d, system tokens = %d \n " , __func__ , n_clients , n_seq , cont_batching , n_tokens_system );
if ( params . prompt_file . empty ()) {
params . prompt_file = "used built-in defaults" ;
}
LOG_TEE ( "External prompt file: \033 [32m%s \033 [0m \n " , params . prompt_file . c_str ());
LOG_TEE ( "Model and path used: \033 [32m%s \033 [0m \n\n " , params . model . c_str ());
2023-09-28 19:04:36 +03:00
LOG_TEE ( "Total prompt tokens: %6d, speed: %5.2f t/s \n " , n_total_prompt , ( double ) ( n_total_prompt ) / ( t_main_end - t_main_start ) * 1e6 );
LOG_TEE ( "Total gen tokens: %6d, speed: %5.2f t/s \n " , n_total_gen , ( double ) ( n_total_gen ) / ( t_main_end - t_main_start ) * 1e6 );
LOG_TEE ( "Total speed (AVG): %6s speed: %5.2f t/s \n " , "" , ( double ) ( n_total_prompt + n_total_gen ) / ( t_main_end - t_main_start ) * 1e6 );
LOG_TEE ( "Cache misses: %6d \n " , n_cache_miss );
2023-10-06 14:16:38 +01:00
LOG_TEE ( " \n " );
2023-09-28 19:04:36 +03:00
llama_print_timings ( ctx );
llama_batch_free ( batch );
llama_free ( ctx );
llama_free_model ( model );
llama_backend_free ();
fprintf ( stderr , " \n\n " );
return 0 ;
}