Brunobkr/llama.cpp_AlgMor24_github
ΩFFFΣLLIa • llama.cpp • AlgMor24 ██████╗ ███████╗███████╗███████╗██╗ ██╗ ██╗ █████╗ ██╔═══██╗██╔════╝██╔════╝██╔════╝██║ ██║ ██║██╔══██╗ ██║ ██║█████╗ █████╗ █████╗ ██║ ██║ ██║███████║ ██║ ██║██╔══╝ ██╔══╝ ██╔══╝ ██║ ██║ ██║██╔══██║ ╚██████╔╝██║ ██║ ███████╗███████╗███████╗██║██║ ██║ ╚═════╝ ╚═╝ ╚═╝ ╚══════╝╚══════╝╚══════╝╚═╝╚═╝ ╚═╝ High-Performance LLM / VLM Inference & Autonomous Agentic Ecosystem… See the full description on the dataset page: https://huggingface.co/datasets/Brunobkr/llama.cpp_AlgMor24_github.
03.1k
1#!/usr/bin/env python32# -*- coding: utf-8 -*-3 4# type: ignore[reportUnusedImport]5 6import subprocess7import os8 9TMP_DIR = os.path.join(os.path.dirname(os.path.abspath(__file__)), "tmp")10import re11import json12from json import JSONDecodeError13import sys14import requests15import time16from concurrent.futures import ThreadPoolExecutor, as_completed17from typing import (18 Any,19 Callable,20 ContextManager,21 Iterable,22 Iterator,23 List,24 Literal,25 Tuple,26 Set,27)28from re import RegexFlag29import wget30 31 32DEFAULT_HTTP_TIMEOUT = 6033 34# per-request timeout, a hung server fails the test instead of stalling the CI for hours35DEFAULT_REQUEST_TIMEOUT = 60036 37 38class ServerResponse:39 headers: dict40 status_code: int41 body: dict | Any42 43 44class ServerError(Exception):45 def __init__(self, code, body):46 self.code = code47 self.body = body48 49 50class ServerProcess:51 # default options52 debug: bool = False53 server_port: int = 808054 server_host: str = "127.0.0.1"55 model_hf_repo: str | None = "ggml-org/models"56 model_hf_file: str | None = "tinyllamas/stories260K.gguf"57 model_alias: str = "tinyllama-2"58 temperature: float = 0.859 seed: int = 4260 offline: bool = False61 62 # custom options63 model_alias: str | None = None64 model_tags: str | None = None65 model_url: str | None = None66 model_file: str | None = None67 model_draft: str | None = None68 n_threads: int | None = None69 n_gpu_layer: int | None = None70 n_batch: int | None = None71 n_ubatch: int | None = None72 n_ctx: int | None = None73 n_ga: int | None = None74 n_ga_w: int | None = None75 n_predict: int | None = None76 n_prompts: int | None = 077 slot_save_path: str | None = None78 id_slot: int | None = None79 cache_prompt: bool | None = None80 n_slots: int | None = None81 ctk: str | None = None82 ctv: str | None = None83 fa: str | None = None84 server_continuous_batching: bool | None = False85 server_embeddings: bool | None = False86 server_reranking: bool | None = False87 server_metrics: bool | None = False88 kv_unified: bool | None = False89 server_slots: bool | None = False90 pooling: str | None = None91 api_key: str | None = None92 models_dir: str | None = None93 models_max: int | None = None94 models_preset: str | None = None95 no_models_autoload: bool | None = None96 lora_files: List[str] | None = None97 enable_ctx_shift: int | None = False98 spec_type: str | None = None99 spec_draft_n_min: int | None = None100 spec_draft_n_max: int | None = None101 no_ui: bool | None = None102 jinja: bool | None = None103 reasoning_format: Literal['deepseek', 'none', 'nothink'] | None = None104 reasoning: Literal['on', 'off', 'auto'] | None = None105 chat_template: str | None = None106 chat_template_file: str | None = None107 server_path: str | None = None108 mmproj_url: str | None = None109 media_path: str | None = None110 sleep_idle_seconds: int | None = None111 cache_ram: int | None = None112 no_cache_idle_slots: bool = False113 log_path: str | None = None114 ui_mcp_proxy: bool = False115 backend_sampling: bool = False116 gcp_compat: bool = False117 server_tools: str | None = None118 server_tools_runtime: str | None = None119 mcp_servers_config: str | None = None120 mcp_servers_json: str | None = None121 cors_origins: str | None = None122 123 # session variables124 process: subprocess.Popen | None = None125 126 def __init__(self):127 if "N_GPU_LAYERS" in os.environ:128 self.n_gpu_layer = int(os.environ["N_GPU_LAYERS"])129 if "DEBUG" in os.environ:130 self.debug = True131 if "PORT" in os.environ:132 self.server_port = int(os.environ["PORT"])133 self.external_server = "DEBUG_EXTERNAL" in os.environ134 135 def start(self, timeout_seconds: int = DEFAULT_HTTP_TIMEOUT) -> None:136 env = {137 **os.environ,138 "LLAMA_SERVER_DEBUG_FAKE_TIMING": "1",139 }140 if "LLAMA_CACHE" not in os.environ:141 env["LLAMA_CACHE"] = "tmp"142 if self.external_server:143 print(f"[external_server]: Assuming external server running on {self.server_host}:{self.server_port}")144 return145 if self.server_path is not None:146 server_path = self.server_path147 elif "LLAMA_SERVER_BIN_PATH" in os.environ:148 server_path = os.environ["LLAMA_SERVER_BIN_PATH"]149 elif os.name == "nt":150 server_path = "../../../build/bin/Release/llama-server.exe"151 else:152 server_path = "../../../build/bin/llama-server"153 server_args = [154 "--host",155 self.server_host,156 "--port",157 self.server_port,158 "--temp",159 self.temperature,160 "--seed",161 self.seed,162 ]163 if self.offline:164 server_args.append("--offline")165 if self.model_file:166 server_args.extend(["--model", self.model_file])167 if self.model_url:168 server_args.extend(["--model-url", self.model_url])169 if self.model_draft:170 server_args.extend(["--model-draft", self.model_draft])171 if self.model_hf_repo:172 server_args.extend(["--hf-repo", self.model_hf_repo])173 if self.model_hf_file:174 server_args.extend(["--hf-file", self.model_hf_file])175 if self.models_dir:176 server_args.extend(["--models-dir", self.models_dir])177 if self.models_max is not None:178 server_args.extend(["--models-max", self.models_max])179 if self.models_preset:180 server_args.extend(["--models-preset", self.models_preset])181 if self.cors_origins:182 server_args.extend(["--cors-origins", self.cors_origins])183 if self.n_batch:184 server_args.extend(["--batch-size", self.n_batch])185 if self.n_ubatch:186 server_args.extend(["--ubatch-size", self.n_ubatch])187 if self.n_threads:188 server_args.extend(["--threads", self.n_threads])189 if self.n_gpu_layer:190 server_args.extend(["--n-gpu-layers", self.n_gpu_layer])191 if self.server_continuous_batching:192 server_args.append("--cont-batching")193 if self.server_embeddings:194 server_args.append("--embedding")195 if self.server_reranking:196 server_args.append("--reranking")197 if self.server_metrics:198 server_args.append("--metrics")199 if self.kv_unified:200 server_args.append("--kv-unified")201 if self.server_slots:202 server_args.append("--slots")203 else:204 server_args.append("--no-slots")205 if self.pooling:206 server_args.extend(["--pooling", self.pooling])207 if self.model_alias:208 server_args.extend(["--alias", self.model_alias])209 if self.model_tags:210 server_args.extend(["--tags", self.model_tags])211 if self.n_ctx:212 server_args.extend(["--ctx-size", self.n_ctx])213 if self.n_slots:214 server_args.extend(["--parallel", self.n_slots])215 if self.ctk:216 server_args.extend(["-ctk", self.ctk])217 if self.ctv:218 server_args.extend(["-ctv", self.ctv])219 if self.fa is not None:220 server_args.extend(["-fa", self.fa])221 if self.n_predict:222 server_args.extend(["--n-predict", self.n_predict])223 if self.slot_save_path:224 server_args.extend(["--slot-save-path", self.slot_save_path])225 if self.n_ga:226 server_args.extend(["--grp-attn-n", self.n_ga])227 if self.n_ga_w:228 server_args.extend(["--grp-attn-w", self.n_ga_w])229 if self.debug:230 server_args.append("--verbose")231 if self.lora_files:232 for lora_file in self.lora_files:233 server_args.extend(["--lora", lora_file])234 if self.enable_ctx_shift:235 server_args.append("--context-shift")236 if self.spec_type:237 server_args.extend(["--spec-type", self.spec_type])238 if self.api_key:239 server_args.extend(["--api-key", self.api_key])240 if self.spec_draft_n_max:241 server_args.extend(["--spec-draft-n-max", self.spec_draft_n_max])242 if self.spec_draft_n_min:243 server_args.extend(["--spec-draft-n-min", self.spec_draft_n_min])244 if self.no_ui:245 server_args.append("--no-ui")246 if self.no_models_autoload:247 server_args.append("--no-models-autoload")248 if self.jinja:249 server_args.append("--jinja")250 else:251 server_args.append("--no-jinja")252 if self.reasoning_format is not None:253 server_args.extend(("--reasoning-format", self.reasoning_format))254 if self.reasoning is not None:255 server_args.extend(("--reasoning", self.reasoning))256 if self.chat_template:257 server_args.extend(["--chat-template", self.chat_template])258 if self.chat_template_file:259 server_args.extend(["--chat-template-file", self.chat_template_file])260 if self.mmproj_url:261 server_args.extend(["--mmproj-url", self.mmproj_url])262 if self.media_path:263 server_args.extend(["--media-path", self.media_path])264 if self.sleep_idle_seconds is not None:265 server_args.extend(["--sleep-idle-seconds", self.sleep_idle_seconds])266 if self.cache_ram is not None:267 server_args.extend(["--cache-ram", self.cache_ram])268 if self.no_cache_idle_slots:269 server_args.append("--no-cache-idle-slots")270 if self.ui_mcp_proxy:271 server_args.append("--ui-mcp-proxy")272 if self.server_tools:273 server_args.extend(["--tools", self.server_tools])274 if self.server_tools_runtime:275 server_args.extend(["--tools-runtime", self.server_tools_runtime])276 if self.mcp_servers_config:277 server_args.extend(["--mcp-servers-config", self.mcp_servers_config])278 if self.mcp_servers_json:279 server_args.extend(["--mcp-servers-json", self.mcp_servers_json])280 if self.backend_sampling:281 server_args.append("--backend_sampling")282 if self.gcp_compat:283 env["AIP_MODE"] = "PREDICTION"284 285 args = [str(arg) for arg in [server_path, *server_args]]286 print(f"tests: starting server with: {' '.join(args)}")287 288 flags = 0289 if "nt" == os.name:290 flags |= subprocess.DETACHED_PROCESS291 flags |= subprocess.CREATE_NEW_PROCESS_GROUP292 flags |= subprocess.CREATE_NO_WINDOW293 294 if self.log_path:295 self._log = open(self.log_path, "w")296 else:297 self._log = sys.stdout298 299 self.process = subprocess.Popen(300 [str(arg) for arg in [server_path, *server_args]],301 creationflags=flags,302 stdout=self._log,303 stderr=self._log if self._log != sys.stdout else sys.stdout,304 env=env,305 )306 server_instances.add(self)307 308 print(f"server pid={self.process.pid}, pytest pid={os.getpid()}")309 310 # wait for server to start311 start_time = time.time()312 last_print_time = start_time313 while time.time() - start_time < timeout_seconds:314 try:315 response = self.make_request("GET", "/health", headers={316 "Authorization": f"Bearer {self.api_key}" if self.api_key else None317 })318 if response.status_code == 200:319 self.ready = True320 return # server is ready321 except Exception as e:322 pass323 # Check if process died324 if self.process.poll() is not None:325 raise RuntimeError(f"Server process died with return code {self.process.returncode}")326 327 if time.time() - last_print_time >= 1.0:328 print(f"Waiting for server to start...")329 last_print_time = time.time()330 time.sleep(0.01)331 raise TimeoutError(f"Server did not start within {timeout_seconds} seconds")332 333 def stop(self) -> None:334 if self.external_server:335 print("[external_server]: Not stopping external server")336 return337 if self in server_instances:338 server_instances.remove(self)339 if self.process:340 print(f"Stopping server with pid={self.process.pid}")341 self.process.terminate()342 try:343 self.process.wait(timeout=5)344 except subprocess.TimeoutExpired:345 print(f"Server pid={self.process.pid} did not terminate in time, killing")346 self.process.kill()347 self.process.wait(timeout=5)348 except Exception as e:349 print(f"Error waiting for server: {e}")350 self.process = None351 if hasattr(self, '_log') and self._log != sys.stdout:352 self._log.close()353 354 def make_request(355 self,356 method: str,357 path: str,358 data: dict | Any | None = None,359 headers: dict | None = None,360 timeout: float | None = DEFAULT_REQUEST_TIMEOUT,361 ) -> ServerResponse:362 url = f"http://{self.server_host}:{self.server_port}{path}"363 parse_body = False364 if method == "GET":365 response = requests.get(url, headers=headers, timeout=timeout)366 parse_body = True367 elif method == "POST":368 response = requests.post(url, headers=headers, json=data, timeout=timeout)369 parse_body = True370 elif method == "DELETE":371 response = requests.delete(url, headers=headers, timeout=timeout)372 parse_body = True373 elif method == "OPTIONS":374 response = requests.options(url, headers=headers, timeout=timeout)375 else:376 raise ValueError(f"Unimplemented method: {method}")377 result = ServerResponse()378 result.headers = dict(response.headers)379 result.status_code = response.status_code380 if parse_body:381 try:382 result.body = response.json()383 except (JSONDecodeError, requests.exceptions.JSONDecodeError):384 result.body = response.text385 else:386 result.body = None387 print("Response from server", json.dumps(result.body, indent=2))388 return result389 390 def make_stream_request(391 self,392 method: str,393 path: str,394 data: dict | None = None,395 headers: dict | None = None,396 ) -> Iterator[dict]:397 url = f"http://{self.server_host}:{self.server_port}{path}"398 if method == "POST":399 response = requests.post(url, headers=headers, json=data, stream=True)400 else:401 raise ValueError(f"Unimplemented method: {method}")402 if response.status_code != 200:403 raise ServerError(response.status_code, response.json())404 for line_bytes in response.iter_lines():405 line = line_bytes.decode("utf-8")406 if '[DONE]' in line:407 break408 elif line.startswith('data: '):409 data = json.loads(line[6:])410 print("Partial response from server", json.dumps(data, indent=2))411 yield data412 413 def make_any_request(414 self,415 method: str,416 path: str,417 data: dict | None = None,418 headers: dict | None = None,419 timeout: float | None = DEFAULT_REQUEST_TIMEOUT,420 ) -> dict:421 stream = data.get('stream', False)422 if stream:423 content: list[str] = []424 reasoning_content: list[str] = []425 tool_calls: list[dict] = []426 finish_reason: Optional[str] = None427 428 content_parts = 0429 reasoning_content_parts = 0430 tool_call_parts = 0431 arguments_parts = 0432 433 for chunk in self.make_stream_request(method, path, data, headers):434 if chunk['choices']:435 assert len(chunk['choices']) == 1, f'Expected 1 choice, got {len(chunk["choices"])}'436 choice = chunk['choices'][0]437 if choice['delta'].get('content') is not None:438 assert len(choice['delta']['content']) > 0, f'Expected non empty content delta!'439 content.append(choice['delta']['content'])440 content_parts += 1441 if choice['delta'].get('reasoning_content') is not None:442 assert len(choice['delta']['reasoning_content']) > 0, f'Expected non empty reasoning_content delta!'443 reasoning_content.append(choice['delta']['reasoning_content'])444 reasoning_content_parts += 1445 if choice['delta'].get('finish_reason') is not None:446 finish_reason = choice['delta']['finish_reason']447 for tc in choice['delta'].get('tool_calls', []):448 if 'function' not in tc:449 raise ValueError(f"Expected function type, got {tc['type']}")450 if tc['index'] >= len(tool_calls):451 assert 'id' in tc452 assert tc.get('type') == 'function'453 assert 'function' in tc and 'name' in tc['function'] and len(tc['function']['name']) > 0, \454 f"Expected function call with name, got {tc.get('function')}"455 tool_calls.append(dict(456 id="",457 type="function",458 function=dict(459 name="",460 arguments="",461 )462 ))463 tool_call = tool_calls[tc['index']]464 if tc.get('id') is not None:465 tool_call['id'] = tc['id']466 fct = tc['function']467 assert 'id' not in fct, f"Function call should not have id: {fct}"468 if fct.get('name') is not None:469 tool_call['function']['name'] = tool_call['function'].get('name', '') + fct['name']470 if fct.get('arguments') is not None:471 tool_call['function']['arguments'] += fct['arguments']472 arguments_parts += 1473 tool_call_parts += 1474 else:475 # When `include_usage` is True (the default), we expect the last chunk of the stream476 # immediately preceding the `data: [DONE]` message to contain a `choices` field with an empty array477 # and a `usage` field containing the usage statistics (n.b., llama-server also returns `timings` in478 # the last chunk)479 assert 'usage' in chunk, f"Expected finish_reason in chunk: {chunk}"480 assert 'timings' in chunk, f"Expected finish_reason in chunk: {chunk}"481 print(f'Streamed response had {content_parts} content parts, {reasoning_content_parts} reasoning_content parts, {tool_call_parts} tool call parts incl. {arguments_parts} arguments parts')482 result = dict(483 choices=[484 dict(485 index=0,486 finish_reason=finish_reason,487 message=dict(488 role='assistant',489 content=''.join(content) if content else None,490 reasoning_content=''.join(reasoning_content) if reasoning_content else None,491 tool_calls=tool_calls if tool_calls else None,492 ),493 )494 ],495 )496 print("Final response from server", json.dumps(result, indent=2))497 return result498 else:499 response = self.make_request(method, path, data, headers, timeout=timeout)500 assert response.status_code == 200, f"Server returned error: {response.status_code}"501 return response.body502 503 504 505server_instances: Set[ServerProcess] = set()506 507 508class ServerPreset:509 @staticmethod510 def load_all() -> None:511 """ Load all server presets to ensure model files are cached. """512 servers: List[ServerProcess] = [513 method()514 for name, method in ServerPreset.__dict__.items()515 if callable(method) and name != "load_all"516 ]517 for server in servers:518 server.offline = False519 server.start()520 server.stop()521 522 @staticmethod523 def tinyllama2() -> ServerProcess:524 server = ServerProcess()525 server.offline = True # will be downloaded by load_all()526 server.model_hf_repo = "ggml-org/test-model-stories260K"527 server.model_hf_file = None528 server.model_alias = "tinyllama-2"529 server.n_ctx = 512530 server.n_batch = 32531 server.n_slots = 2532 server.n_predict = 64533 server.seed = 42534 return server535 536 @staticmethod537 def bert_bge_small() -> ServerProcess:538 server = ServerProcess()539 server.offline = True # will be downloaded by load_all()540 server.model_hf_repo = "ggml-org/models"541 server.model_hf_file = "bert-bge-small/ggml-model-f16.gguf"542 server.model_alias = "bert-bge-small"543 server.n_ctx = 512544 server.n_batch = 128545 server.n_ubatch = 128546 server.n_slots = 2547 server.seed = 42548 server.server_embeddings = True549 return server550 551 @staticmethod552 def bert_bge_small_with_fa() -> ServerProcess:553 server = ServerProcess()554 server.offline = True # will be downloaded by load_all()555 server.model_hf_repo = "ggml-org/models"556 server.model_hf_file = "bert-bge-small/ggml-model-f16.gguf"557 server.model_alias = "bert-bge-small"558 server.n_ctx = 1024559 server.n_batch = 300560 server.n_ubatch = 300561 server.n_slots = 2562 server.fa = "on"563 server.seed = 42564 server.server_embeddings = True565 return server566 567 @staticmethod568 def tinyllama_infill() -> ServerProcess:569 server = ServerProcess()570 server.offline = True # will be downloaded by load_all()571 server.model_hf_repo = "ggml-org/test-model-stories260K-infill"572 server.model_hf_file = None573 server.model_alias = "tinyllama-infill"574 server.n_ctx = 2048575 server.n_batch = 1024576 server.n_slots = 1577 server.n_predict = 64578 server.temperature = 0.0579 server.seed = 42580 return server581 582 @staticmethod583 def stories15m_moe() -> ServerProcess:584 server = ServerProcess()585 server.offline = True # will be downloaded by load_all()586 server.model_hf_repo = "ggml-org/stories15M_MOE"587 server.model_hf_file = "stories15M_MOE-F16.gguf"588 server.model_alias = "stories15m-moe"589 server.n_ctx = 2048590 server.n_batch = 1024591 server.n_slots = 1592 server.n_predict = 64593 server.temperature = 0.0594 server.seed = 42595 return server596 597 @staticmethod598 def jina_reranker_tiny() -> ServerProcess:599 server = ServerProcess()600 server.offline = True # will be downloaded by load_all()601 server.model_hf_repo = "ggml-org/models"602 server.model_hf_file = "jina-reranker-v1-tiny-en/ggml-model-f16.gguf"603 server.model_alias = "jina-reranker"604 server.n_ctx = 512605 server.n_batch = 512606 server.n_slots = 1607 server.seed = 42608 server.server_reranking = True609 return server610 611 @staticmethod612 def tinygemma3() -> ServerProcess:613 server = ServerProcess()614 server.offline = True # will be downloaded by load_all()615 # mmproj is already provided by HF registry API616 server.model_hf_file = None617 server.model_hf_repo = "ggml-org/tinygemma3-GGUF:Q8_0"618 server.model_alias = "tinygemma3"619 server.n_ctx = 1024620 server.n_batch = 32621 server.n_slots = 2622 server.n_predict = 4623 server.seed = 42624 return server625 626 @staticmethod627 def router() -> ServerProcess:628 server = ServerProcess()629 server.offline = True # will be downloaded by load_all()630 # router server has no models631 server.model_file = None632 server.model_alias = None633 server.model_hf_repo = None634 server.model_hf_file = None635 server.n_ctx = 1024636 server.n_batch = 16637 server.n_slots = 1638 server.n_predict = 16639 server.seed = 42640 return server641 642 643def parallel_function_calls(function_list: List[Tuple[Callable[..., Any], Tuple[Any, ...]]]) -> List[Any]:644 """645 Run multiple functions in parallel and return results in the same order as calls. Equivalent to Promise.all in JS.646 647 Example usage:648 649 results = parallel_function_calls([650 (func1, (arg1, arg2)),651 (func2, (arg3, arg4)),652 ])653 """654 results = [None] * len(function_list)655 exceptions = []656 657 def worker(index, func, args):658 try:659 result = func(*args)660 results[index] = result661 except Exception as e:662 exceptions.append((index, str(e)))663 664 with ThreadPoolExecutor() as executor:665 futures = []666 for i, (func, args) in enumerate(function_list):667 future = executor.submit(worker, i, func, args)668 futures.append(future)669 670 # Wait for all futures to complete671 for future in as_completed(futures):672 pass673 674 # Check if there were any exceptions675 if exceptions:676 print("Exceptions occurred:")677 for index, error in exceptions:678 print(f"Function at index {index}: {error}")679 680 return results681 682 683def match_regex(regex: str, text: str) -> bool:684 return (685 re.compile(686 regex, flags=RegexFlag.IGNORECASE | RegexFlag.MULTILINE | RegexFlag.DOTALL687 ).search(text)688 is not None689 )690 691 692def download_file(url: str, output_file_path: str | None = None) -> str:693 """694 Download a file from a URL to a local path. If the file already exists, it will not be downloaded again.695 696 output_file_path is the local path to save the downloaded file. If not provided, the file will be saved in the root directory.697 698 Returns the local path of the downloaded file.699 """700 file_name = url.split('/').pop()701 output_file = f'./tmp/{file_name}' if output_file_path is None else output_file_path702 if not os.path.exists(output_file):703 print(f"Downloading {url} to {output_file}")704 wget.download(url, out=output_file)705 print(f"Done downloading to {output_file}")706 else:707 print(f"File already exists at {output_file}")708 return output_file709 710 711def is_slow_test_allowed():712 return os.environ.get("SLOW_TESTS") == "1" or os.environ.get("SLOW_TESTS") == "ON"713 