char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
8172e6577
char/aliceai-t5-moe
1import pytest 2import requests 3import time 4import random 5 6from openai import OpenAI 7from utils import * 8 9server = ServerPreset .tinyllama2 () 10 11JSON_MULTIMODAL_KEY = "multimodal_data" 12JSON_PROMPT_STRING_KEY = "prompt_string" 13 14@ pytest . fixture ( autouse = True ) 15def create_server (): 16global server 17server = ServerPreset .tinyllama2 () 18 19@ pytest . mark . parametrize ( "prompt,n_predict,re_content,n_prompt,n_predicted,truncated,return_tokens" , [ 20( "I believe the meaning of life is" , 8 , "(going|bed)+" , 18 , 8 , False , False ), 21( "Write a joke about AI from a very long prompt which will not be truncated" , 64 , "(princesses|everyone|kids|Anna|forest)+" , 46 , 64 , False , True ), 22]) 23def test_completion (prompt :str ,n_predict :int ,re_content :str ,n_prompt :int ,n_predicted :int ,truncated :bool ,return_tokens :bool ): 24global server 25server .start () 26res = server .make_request ("POST" ,"/completion" ,data = { 27"n_predict" :n_predict , 28"prompt" :prompt , 29"return_tokens" :return_tokens , 30 }) 31assert res .status_code == 200 32assert res .body ["timings" ]["prompt_n" ]== n_prompt 33assert res .body ["timings" ]["predicted_n" ]== n_predicted 34assert res .body ["truncated" ]== truncated 35assert type (res .body ["has_new_line" ])== bool 36assert match_regex (re_content ,res .body ["content" ]) 37if return_tokens : 38assert len (res .body ["tokens" ])> 0 39assert all (type (tok )== int for tok in res .body ["tokens" ]) 40else : 41assert res .body ["tokens" ]== [] 42 43 44@ pytest . mark . parametrize ( "prompt,n_predict,re_content,n_prompt,n_predicted,truncated" , [ 45( "I believe the meaning of life is" , 8 , "(going|bed)+" , 18 , 8 , False ), 46( "Write a joke about AI from a very long prompt which will not be truncated" , 64 , "(princesses|everyone|kids|Anna|forest)+" , 46 , 64 , False ), 47]) 48def test_completion_stream (prompt :str ,n_predict :int ,re_content :str ,n_prompt :int ,n_predicted :int ,truncated :bool ): 49global server 50server .start () 51res = server .make_stream_request ("POST" ,"/completion" ,data = { 52"n_predict" :n_predict , 53"prompt" :prompt , 54"stream" :True , 55 }) 56content = "" 57for data in res : 58assert "stop" in data and type (data ["stop" ])== bool 59if data ["stop" ]: 60assert data ["timings" ]["prompt_n" ]== n_prompt 61assert data ["timings" ]["predicted_n" ]== n_predicted 62assert data ["truncated" ]== truncated 63assert data ["stop_type" ]== "limit" 64assert type (data ["has_new_line" ])== bool 65assert "generation_settings" in data 66assert server .n_predict is not None 67assert data ["generation_settings" ]["n_predict" ]== min (n_predict ,server .n_predict ) 68assert data ["generation_settings" ]["seed" ]== server .seed 69assert "adaptive_target" in data ["generation_settings" ] 70assert "adaptive_decay" in data ["generation_settings" ] 71assert match_regex (re_content ,content ) 72else : 73assert len (data ["tokens" ])> 0 74assert all (type (tok )== int for tok in data ["tokens" ]) 75content += data ["content" ] 76 77 78def test_completion_stream_vs_non_stream (): 79global server 80server .start () 81res_stream = server .make_stream_request ("POST" ,"/completion" ,data = { 82"n_predict" :8 , 83"prompt" :"I believe the meaning of life is" , 84"stream" :True , 85 }) 86res_non_stream = server .make_request ("POST" ,"/completion" ,data = { 87"n_predict" :8 , 88"prompt" :"I believe the meaning of life is" , 89 }) 90content_stream = "" 91for data in res_stream : 92content_stream += data ["content" ] 93assert content_stream == res_non_stream .body ["content" ] 94 95 96def test_completion_with_openai_library (): 97global server 98server .start () 99client = OpenAI (api_key = "dummy" ,base_url = f"http:// { server . server_host } : { server . server_port } /v1" ) 100res = client .completions .create ( 101model = "davinci-002" , 102prompt = "I believe the meaning of life is" , 103max_tokens = 8 , 104 ) 105assert res .system_fingerprint is not None and res .system_fingerprint .startswith ("b" ) 106assert res .choices [0 ].finish_reason == "length" 107assert res .choices [0 ].text is not None 108assert match_regex ("(going|bed)+" ,res .choices [0 ].text ) 109 110 111def test_completion_stream_with_openai_library (): 112global server 113server .start () 114client = OpenAI (api_key = "dummy" ,base_url = f"http:// { server . server_host } : { server . server_port } /v1" ) 115res = client .completions .create ( 116model = "davinci-002" , 117prompt = "I believe the meaning of life is" , 118max_tokens = 8 , 119stream = True , 120 ) 121output_text = '' 122for data in res : 123choice = data .choices [0 ] 124if choice .finish_reason is None : 125assert choice .text is not None 126output_text += choice .text 127assert match_regex ("(going|bed)+" ,output_text ) 128 129 130# Test case from https://github.com/ggml-org/llama.cpp/issues/13780 131@ pytest . mark . slow 132def test_completion_stream_with_openai_library_stops (): 133global server 134server .model_hf_repo = "bartowski/Phi-3.5-mini-instruct-GGUF:Q4_K_M" 135server .model_hf_file = None 136server .start () 137client = OpenAI (api_key = "dummy" ,base_url = f"http:// { server . server_host } : { server . server_port } /v1" ) 138res = client .completions .create ( 139model = "davinci-002" , 140prompt = "System: You are helpful assistant.\nAssistant:\nHey! How could I help?\nUser:\nTell me a joke.\nAssistant:\n" , 141stop = ["User:\n" ,"Assistant:\n" ], 142max_tokens = 200 , 143stream = True , 144 ) 145output_text = '' 146for data in res : 147choice = data .choices [0 ] 148if choice .finish_reason is None : 149assert choice .text is not None 150output_text += choice .text 151assert match_regex ("Sure, here's one for[\\s\\S]*" ,output_text ),f'Unexpected output: { output_text } ' 152 153 154@ pytest . mark . parametrize ( "n_slots" , [ 1 , 2 ]) 155def test_consistent_result_same_seed (n_slots :int ): 156global server 157server .n_slots = n_slots 158server .start () 159last_res = None 160for _ in range (4 ): 161res = server .make_request ("POST" ,"/completion" ,data = { 162"prompt" :"I believe the meaning of life is" , 163"seed" :42 , 164"temperature" :0.0 , 165"cache_prompt" :False ,# TODO: remove this once test_cache_vs_nocache_prompt is fixed 166 }) 167if last_res is not None : 168assert res .body ["content" ]== last_res .body ["content" ] 169last_res = res 170 171 172@ pytest . mark . parametrize ( "n_slots" , [ 1 , 2 ]) 173def test_different_result_different_seed (n_slots :int ): 174global server 175server .n_slots = n_slots 176server .start () 177last_res = None 178for seed in range (4 ): 179res = server .make_request ("POST" ,"/completion" ,data = { 180"prompt" :"I believe the meaning of life is" , 181"seed" :seed , 182"temperature" :1.0 , 183"cache_prompt" :False ,# TODO: remove this once test_cache_vs_nocache_prompt is fixed 184 }) 185if last_res is not None : 186assert res .body ["content" ]!= last_res .body ["content" ] 187last_res = res 188 189# TODO figure why it don't work with temperature = 1 190# @pytest.mark.parametrize("temperature", [0.0, 1.0]) 191@ pytest . mark . parametrize ( "n_batch" , [ 16 , 32 ]) 192@ pytest . mark . parametrize ( "temperature" , [ 0.0 ]) 193def test_consistent_result_different_batch_size (n_batch :int ,temperature :float ): 194global server 195server .n_batch = n_batch 196server .start () 197last_res = None 198for _ in range (4 ): 199res = server .make_request ("POST" ,"/completion" ,data = { 200"prompt" :"I believe the meaning of life is" , 201"seed" :42 , 202"temperature" :temperature , 203"cache_prompt" :False ,# TODO: remove this once test_cache_vs_nocache_prompt is fixed 204 }) 205if last_res is not None : 206assert res .body ["content" ]== last_res .body ["content" ] 207last_res = res 208 209 210@ pytest . mark . skip ( reason = "This test fails on linux, need to be fixed" ) 211def test_cache_vs_nocache_prompt (): 212global server 213server .start () 214res_cache = server .make_request ("POST" ,"/completion" ,data = { 215"prompt" :"I believe the meaning of life is" , 216"seed" :42 , 217"temperature" :1.0 , 218"cache_prompt" :True , 219 }) 220res_no_cache = server .make_request ("POST" ,"/completion" ,data = { 221"prompt" :"I believe the meaning of life is" , 222"seed" :42 , 223"temperature" :1.0 , 224"cache_prompt" :False , 225 }) 226assert res_cache .body ["content" ]== res_no_cache .body ["content" ] 227 228 229def test_nocache_long_input_prompt (): 230global server 231server .start () 232res = server .make_request ("POST" ,"/completion" ,data = { 233"prompt" :"I believe the meaning of life is" * 32 , 234"seed" :42 , 235"temperature" :1.0 , 236"cache_prompt" :False , 237 }) 238assert res .status_code == 400 239 240def test_json_prompt_no_mtmd (): 241global server 242server .start () 243res = server .make_request ("POST" ,"/completion" ,data = { 244"prompt" : {JSON_PROMPT_STRING_KEY :"I believe the meaning of life is" }, 245"seed" :42 , 246"temperature" :1.0 , 247"cache_prompt" :False , 248 }) 249assert res .status_code == 200 250 251def test_json_prompt_mtm_error_when_not_supported (): 252global server 253server .start () 254res = server .make_request ("POST" ,"/completion" ,data = { 255"prompt" : {JSON_PROMPT_STRING_KEY :"I believe the meaning of life is <__media__>" ,JSON_MULTIMODAL_KEY :"iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mNk+A8AAQUBAScY42YAAAAASUVORK5CYII=" }, 256"seed" :42 , 257"temperature" :1.0 , 258"cache_prompt" :False , 259 }) 260# MTMD is disabled on this model, so this should fail. 261assert res .status_code != 200 262 263def test_completion_with_tokens_input (): 264global server 265server .temperature = 0.0 266server .start () 267prompt_str = "I believe the meaning of life is" 268res = server .make_request ("POST" ,"/tokenize" ,data = { 269"content" :prompt_str , 270"add_special" :True , 271 }) 272assert res .status_code == 200 273tokens = res .body ["tokens" ] 274 275# single completion 276res = server .make_request ("POST" ,"/completion" ,data = { 277"prompt" :tokens , 278 }) 279assert res .status_code == 200 280assert type (res .body ["content" ])== str 281 282# batch completion 283res = server .make_request ("POST" ,"/completion" ,data = { 284"prompt" : [tokens ,tokens ], 285 }) 286assert res .status_code == 200 287assert type (res .body )== list 288assert len (res .body )== 2 289assert res .body [0 ]["content" ]== res .body [1 ]["content" ] 290 291# mixed string and tokens 292res = server .make_request ("POST" ,"/completion" ,data = { 293"prompt" : [tokens ,prompt_str ], 294 }) 295assert res .status_code == 200 296assert type (res .body )== list 297assert len (res .body )== 2 298assert res .body [0 ]["content" ]== res .body [1 ]["content" ] 299 300# mixed JSON and tokens 301res = server .make_request ("POST" ,"/completion" ,data = { 302"prompt" : [ 303tokens , 304 { 305JSON_PROMPT_STRING_KEY :"I believe the meaning of life is" , 306 }, 307 ], 308 }) 309assert res .status_code == 200 310assert type (res .body )== list 311assert len (res .body )== 2 312assert res .body [0 ]["content" ]== res .body [1 ]["content" ] 313 314# mixed string and tokens in one sequence 315res = server .make_request ("POST" ,"/completion" ,data = { 316"prompt" : [1 ,2 ,3 ,4 ,5 ,6 ,prompt_str ,7 ,8 ,9 ,10 ,prompt_str ], 317 }) 318assert res .status_code == 200 319assert type (res .body ["content" ])== str 320 321 322@ pytest . mark . parametrize ( "n_slots,n_requests" , [ 323( 1 , 3 ), 324( 2 , 2 ), 325( 2 , 4 ), 326( 4 , 2 ), # some slots must be idle 327( 4 , 6 ), 328]) 329def test_completion_parallel_slots (n_slots :int ,n_requests :int ): 330global server 331server .n_slots = n_slots 332server .temperature = 0.0 333server .start () 334 335PROMPTS = [ 336 ("Write a very long book." ,"(very|special|big)+" ), 337 ("Write another a poem." ,"(small|house)+" ), 338 ("What is LLM?" ,"(Dad|said)+" ), 339 ("The sky is blue and I love it." ,"(climb|leaf)+" ), 340 ("Write another very long music lyrics." ,"(friends|step|sky)+" ), 341 ("Write a very long joke." ,"(cat|Whiskers)+" ), 342 ] 343def check_slots_status (): 344should_all_slots_busy = n_requests >= n_slots 345time .sleep (0.1 ) 346res = server .make_request ("GET" ,"/slots" ) 347n_busy = sum ([1 for slot in res .body if slot ["is_processing" ]]) 348if should_all_slots_busy : 349assert n_busy == n_slots 350else : 351assert n_busy <= n_slots 352 353tasks = [] 354for i in range (n_requests ): 355prompt ,re_content = PROMPTS [i % len (PROMPTS )] 356tasks .append ((server .make_request , ("POST" ,"/completion" , { 357"prompt" :prompt , 358"seed" :42 , 359"temperature" :1.0 , 360 }))) 361tasks .append ((check_slots_status , ())) 362results = parallel_function_calls (tasks ) 363 364# check results 365for i in range (n_requests ): 366prompt ,re_content = PROMPTS [i % len (PROMPTS )] 367res = results [i ] 368assert res .status_code == 200 369assert type (res .body ["content" ])== str 370assert len (res .body ["content" ])> 10 371# FIXME: the result is not deterministic when using other slot than slot 0 372# assert match_regex(re_content, res.body["content"]) 373 374 375@ pytest . mark . parametrize ( 376"n_ctx,n_slots,n_predict_vals,expected_success" , 377[ 378( 256 , 4 , [ 80 , 40 , 80 , 80 ], [ True , True , True , True ]), 379( 256 , 4 , [ 70 , 70 , 70 , 70 ], [ False , False , False , False ]), 380( 256 , 4 , [ 90 , 90 , 40 , 90 ], [ False , False , True , False ]), 381( 256 , 4 , [ 90 , 90 , 40 , 75 ], [ True , True , True , True ]), 382], 383) 384def test_completion_unified (n_ctx ,n_slots ,n_predict_vals ,expected_success ): 385global server 386server .n_slots = n_slots 387server .kv_unified = True 388server .n_ctx = n_ctx 389server .start () 390prompt = "A" 391tasks = [] 392for n_predict in n_predict_vals : 393tasks .append ((server .make_request , ("POST" ,"/completion" , {"prompt" :prompt ,"n_predict" :n_predict }))) 394results = parallel_function_calls (tasks ) 395for res ,n_predict ,expect_ok in zip (results ,n_predict_vals ,expected_success ): 396if expect_ok : 397# the pool is aborted as a whole, so a request that fits on its own 398# is still dropped when the slots overlap, and it says so explicitly 399assert res .status_code == 200 or ( 400res .status_code == 500 401and "context size has been exceeded" in res .body ["error" ]["message" ].lower () 402 ) 403 404# note: https://github.com/ggml-org/llama.cpp/pull/18700#issuecomment-3728695581 405if res .status_code == 200 : 406assert "content" in res .body 407if "timings" in res .body : 408assert res .body ["timings" ]["predicted_n" ]== n_predict 409 410 411@ pytest . mark . parametrize ( 412"prompt,n_predict,response_fields" , 413[ 414( "I believe the meaning of life is" , 8 , []), 415( "I believe the meaning of life is" , 32 , [ "content" , "generation_settings/n_predict" , "prompt" ]), 416], 417) 418def test_completion_response_fields ( 419prompt :str ,n_predict :int ,response_fields :list [str ] 420): 421global server 422server .start () 423res = server .make_request ( 424"POST" , 425"/completion" , 426data = { 427"n_predict" :n_predict , 428"prompt" :prompt , 429"response_fields" :response_fields , 430 }, 431 ) 432assert res .status_code == 200 433assert "content" in res .body 434assert len (res .body ["content" ]) 435if len (response_fields ): 436assert res .body ["generation_settings/n_predict" ]== n_predict 437assert res .body ["prompt" ]== "<s> " + prompt 438assert isinstance (res .body ["content" ],str ) 439assert len (res .body )== len (response_fields ) 440else : 441assert len (res .body ) 442assert "generation_settings" in res .body 443 444 445def test_n_probs (): 446global server 447server .start () 448res = server .make_request ("POST" ,"/completion" ,data = { 449"prompt" :"I believe the meaning of life is" , 450"n_probs" :10 , 451"temperature" :0.0 , 452"n_predict" :5 , 453 }) 454assert res .status_code == 200 455assert "completion_probabilities" in res .body 456assert len (res .body ["completion_probabilities" ])== 5 457for tok in res .body ["completion_probabilities" ]: 458assert "id" in tok and tok ["id" ]> 0 459assert "token" in tok and type (tok ["token" ])== str 460assert "logprob" in tok and tok ["logprob" ]<= 0.0 461assert "bytes" in tok and type (tok ["bytes" ])== list 462assert len (tok ["top_logprobs" ])== 10 463for prob in tok ["top_logprobs" ]: 464assert "id" in prob and prob ["id" ]> 0 465assert "token" in prob and type (prob ["token" ])== str 466assert "logprob" in prob and prob ["logprob" ]<= 0.0 467assert "bytes" in prob and type (prob ["bytes" ])== list 468 469 470def test_n_probs_stream (): 471global server 472server .start () 473res = server .make_stream_request ("POST" ,"/completion" ,data = { 474"prompt" :"I believe the meaning of life is" , 475"n_probs" :10 , 476"temperature" :0.0 , 477"n_predict" :5 , 478"stream" :True , 479 }) 480for data in res : 481if data ["stop" ]== False : 482assert "completion_probabilities" in data 483assert len (data ["completion_probabilities" ])== 1 484for tok in data ["completion_probabilities" ]: 485assert "id" in tok and tok ["id" ]> 0 486assert "token" in tok and type (tok ["token" ])== str 487assert "logprob" in tok and tok ["logprob" ]<= 0.0 488assert "bytes" in tok and type (tok ["bytes" ])== list 489assert len (tok ["top_logprobs" ])== 10 490for prob in tok ["top_logprobs" ]: 491assert "id" in prob and prob ["id" ]> 0 492assert "token" in prob and type (prob ["token" ])== str 493assert "logprob" in prob and prob ["logprob" ]<= 0.0 494assert "bytes" in prob and type (prob ["bytes" ])== list 495 496 497def test_n_probs_post_sampling (): 498global server 499server .start () 500res = server .make_request ("POST" ,"/completion" ,data = { 501"prompt" :"Today was the day. Today I would finally become a" , 502"n_probs" :10 , 503"temperature" :1.0 , 504"n_predict" :5 , 505"post_sampling_probs" :True , 506 }) 507assert res .status_code == 200 508assert "completion_probabilities" in res .body 509assert len (res .body ["completion_probabilities" ])== 5 510for (i ,tok )in enumerate (res .body ["completion_probabilities" ]): 511assert "id" in tok and tok ["id" ]> 0 512assert "token" in tok and type (tok ["token" ])== str 513assert "prob" in tok and 0.0 < tok ["prob" ]<= 1.0 514assert "bytes" in tok and type (tok ["bytes" ])== list 515assert "top_probs" in tok and type (tok ["top_probs" ])== list 516 517for prob in tok ["top_probs" ]: 518assert "id" in prob and prob ["id" ]> 0 519assert "token" in prob and type (prob ["token" ])== str 520# 0.0 probability tokens should never be returned by the server 521assert "prob" in prob and 0.0 < prob ["prob" ]<= 1.0 522assert "bytes" in prob and type (prob ["bytes" ])== list 523 524if i == 0 : 525# The prompt is vague enough that we should get at least 10 possibilities 526# for the first token. 527assert len (tok ["top_probs" ])== 10 528 529if len (tok ["top_probs" ])< 10 : 530# Getting less than the requested number of probabilities should only happen 531# if the ones we did get already sum to 1.0. 532assert sum (p ["prob" ]for p in tok ["top_probs" ])== pytest .approx (1.0 ) 533 534def test_n_probs_post_backend_sampling (): 535"""Verify that the same probabilities are returned with and without backend sampling.""" 536global server 537server .backend_sampling = True 538server .start () 539 540def make_request (backend_sampling ): 541n_predict = 20 542 543res = server .make_request ("POST" ,"/completion" ,data = { 544"prompt" :"The countries of Europe, in random order, are:" , 545"n_probs" :10 , 546"n_predict" :n_predict , 547"post_sampling_probs" :True , 548"seed" :4242 , 549"backend_sampling" :backend_sampling , 550 }) 551assert res .status_code == 200 552 553total_probs = 0 554completions = res .body ["completion_probabilities" ] 555assert len (completions )== n_predict 556for tok in completions : 557# Handling of 0.0 probabilities differs between samplers and backend sampling. Filter them to normalize the 558# data. 559tok ["top_probs" ]= [x for x in tok ["top_probs" ]if x ["prob" ]> 0.0 ] 560total_probs += len (tok ["top_probs" ]) 561# Verify that we got at least two top probs on average, to ensure the effectiveness of the test. 562assert total_probs >= 2 * n_predict 563return completions 564 565def verify_token (a ,b ): 566assert a ["id" ]== b ["id" ] 567assert a ["token" ]== b ["token" ] 568assert a ["bytes" ]== b ["bytes" ] 569assert a ["prob" ]== pytest .approx (b ["prob" ],abs = 0.01 ) 570 571for (a ,b )in zip (make_request (True ),make_request (False )): 572verify_token (a ,b ) 573assert len (a ["top_probs" ])== len (b ["top_probs" ]) 574 575for (aa ,bb )in zip (a ["top_probs" ],b ["top_probs" ]): 576verify_token (aa ,bb ) 577 578@ pytest . mark . parametrize ( "tokenize,openai_style" , [( False , False ), ( False , True ), ( True , False ), ( True , True )]) 579def test_logit_bias (tokenize ,openai_style ): 580global server 581server .start () 582 583exclude = ["i" ,"I" ,"the" ,"The" ,"to" ,"a" ,"an" ,"be" ,"is" ,"was" ,"but" ,"But" ,"and" ,"And" ,"so" ,"So" ,"you" ,"You" ,"he" ,"He" ,"she" ,"She" ,"we" ,"We" ,"they" ,"They" ,"it" ,"It" ,"his" ,"His" ,"her" ,"Her" ,"book" ,"Book" ] 584 585logit_bias = [] 586if tokenize : 587res = server .make_request ("POST" ,"/tokenize" ,data = { 588"content" :" " + " " .join (exclude )+ " " , 589 }) 590assert res .status_code == 200 591tokens = res .body ["tokens" ] 592logit_bias = [[tok ,- 100 ]for tok in tokens ] 593 594else : 595logit_bias = [[" " + tok + " " ,- 100 ]for tok in exclude ] 596 597if openai_style : 598logit_bias = {el [0 ]:- 100 for el in logit_bias } 599 600res = server .make_request ("POST" ,"/completion" ,data = { 601"n_predict" :64 , 602"prompt" :"What is the best book" , 603"logit_bias" :logit_bias , 604"temperature" :0.0 605 }) 606assert res .status_code == 200 607output_text = res .body ["content" ] 608assert all (output_text .find (" " + tok + " " )== - 1 for tok in exclude ) 609 610 611def test_cancel_request (): 612global server 613server .n_ctx = 4096 614server .n_predict = - 1 615server .n_slots = 1 616server .server_slots = True 617server .start () 618# send a request that will take a long time, but cancel it before it finishes 619try : 620server .make_request ("POST" ,"/completion" ,data = { 621"prompt" :"I believe the meaning of life is" , 622 },timeout = 0.1 ) 623except requests .exceptions .ReadTimeout : 624pass # expected 625# make sure the slot is free 626time .sleep (2 ) 627res = server .make_request ("GET" ,"/slots" ) 628assert res .body [0 ]["is_processing" ]== False 629 630 631# this test exercises the host-memory prompt cache 632# ref: https://github.com/ggml-org/llama.cpp/pull/16391 633# ref: https://github.com/ggml-org/llama.cpp/pull/17078 634def test_completion_prompt_cache (): 635global server 636server .n_slots = 2 637server .kv_unified = True 638server .start () 639 640for _ in range (16 ): 641# generate alternating random prompts with variable lengths in order to get them in and out of the cache 642r = random .randint (0 ,4 ) 643prompt = (" Hello " + str (r ))* (40 + r ) 644n_prompt = (40 + r )* 5 + 2 645n_predict = random .randint (1 ,8 ) 646 647res = server .make_request ( 648"POST" , 649"/completion" , 650data = { 651"prompt" :prompt , 652"n_predict" :n_predict , 653 }, 654 ) 655 656assert res .status_code == 200 657assert "content" in res .body 658content = res .body ["content" ] 659assert isinstance (content ,str ) 660assert len (content )> 0 661 662assert type (res .body ["has_new_line" ])== bool 663assert "timings" in res .body 664timings = res .body ["timings" ] 665 666assert "prompt_n" in timings and timings ["prompt_n" ]+ timings ["cache_n" ]== n_prompt 667assert "predicted_n" in timings and timings ["predicted_n" ]== n_predict 668assert "tokens" in res .body and isinstance (res .body ["tokens" ],list )