data: publish complete Calculet NPU research archive
This commit is contained in:
BIN
Binary file not shown.
@@ -0,0 +1,6 @@
|
||||
set -e
|
||||
|
||||
SRC_DIR="/models/"
|
||||
|
||||
./llama.cpp/build/bin/llama-bench -m ${SRC_DIR}Qwen2.5-0.5B-Instruct.vocab.gguf \
|
||||
-ca ${SRC_DIR}Qwen2.5-0.5B-Instruct-dynamic-W8A8-W4AF16_2_chips_4096_fa_calbin_2026-04-21 --no-warmup -p 128 -n 128 -o csv --device-info
|
||||
@@ -0,0 +1,6 @@
|
||||
set -e
|
||||
|
||||
SRC_DIR="/models/"
|
||||
|
||||
./llama.cpp/build/bin/llama-cli -m ${SRC_DIR}Qwen2.5-0.5B-Instruct.vocab.gguf \
|
||||
-ca ${SRC_DIR}Qwen2.5-0.5B-Instruct-dynamic-W8A8-W4AF16_2_chips_4096_fa_calbin_2026-04-21 --no-warmup -np 1
|
||||
@@ -0,0 +1,285 @@
|
||||
#!/usr/bin/env python3
|
||||
import subprocess
|
||||
import time
|
||||
import requests
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
from typing import List, Tuple, Optional
|
||||
|
||||
# 配置常量
|
||||
LOG_FILE = "llama_test_logs.csv"
|
||||
CONTENT_FILE = "llama_content_logs.csv"
|
||||
MAX_RETRIES = 10
|
||||
RETRY_DELAY = 15
|
||||
SERVER_PATH = "./build/bin/llama-server"
|
||||
|
||||
# 固定的测试prompt
|
||||
FIXED_PROMPT = "who are you?"
|
||||
|
||||
def init_log_file():
|
||||
"""初始化日志文件,写入CSV表头(如果文件不存在)"""
|
||||
if not os.path.exists(LOG_FILE):
|
||||
with open(LOG_FILE, "w") as f:
|
||||
f.write("timestamp,model_path,case_path,case_name,prompt_tokens,completion_tokens,memory_usage,Decode_TPS,TTFT,Prefill_TPS,Power,result\n")
|
||||
print(f"Initialized log file: {LOG_FILE}")
|
||||
if not os.path.exists(CONTENT_FILE):
|
||||
with open(CONTENT_FILE, "w") as f:
|
||||
f.write("timestamp,model_path,case_path,case_name,prompt,content\n")
|
||||
print(f"Initialized content file: {CONTENT_FILE}")
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser(
|
||||
description='Automatically test model (.gguf) files in case directories with fixed prompt.'
|
||||
)
|
||||
parser.add_argument(
|
||||
"-c", "--case_path",
|
||||
required=True,
|
||||
help="Root directory to scan for case folders containing .gguf model files."
|
||||
)
|
||||
parser.add_argument(
|
||||
"--port",
|
||||
type=int,
|
||||
default=8080,
|
||||
help="llama-server port"
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
def discover_gguf_models(root_dir: str) -> List[Tuple[str, str, str]]:
|
||||
"""
|
||||
遍历根目录,查找所有包含.gguf文件的文件夹。
|
||||
返回一个列表,元素为 (model_path, case_path, case_name) 元组。
|
||||
case_path 是包含.gguf文件的文件夹路径,case_name 是文件夹名称。
|
||||
"""
|
||||
test_cases = []
|
||||
|
||||
if not os.path.isdir(root_dir):
|
||||
print(f"Error: Provided root_dir '{root_dir}' is not a valid directory.")
|
||||
return test_cases
|
||||
|
||||
print(f"Scanning root directory: {root_dir}")
|
||||
|
||||
for item in os.scandir(root_dir):
|
||||
if item.is_dir():
|
||||
case_path = item.path
|
||||
case_name = item.name
|
||||
gguf_files = []
|
||||
|
||||
# 遍历文件夹内所有文件,查找.gguf文件
|
||||
for file_item in os.scandir(case_path):
|
||||
if file_item.is_file() and file_item.name.endswith('.gguf'):
|
||||
gguf_files.append(file_item.path)
|
||||
|
||||
if gguf_files:
|
||||
# 如果有多个.gguf文件,可以选择第一个或让用户选择
|
||||
# 这里我们选择第一个找到的.gguf文件
|
||||
model_path = gguf_files[0]
|
||||
if len(gguf_files) > 1:
|
||||
print(f" Warning: Found {len(gguf_files)} .gguf files in '{case_name}', using: {os.path.basename(model_path)}")
|
||||
test_cases.append((model_path, case_path, case_name))
|
||||
print(f" Found: Case='{case_name}', Model='{os.path.basename(model_path)}'")
|
||||
else:
|
||||
print(f" Skipping folder '{case_name}': No .gguf files found.")
|
||||
|
||||
print(f"Total discovered test cases: {len(test_cases)}\n")
|
||||
return test_cases
|
||||
|
||||
def start_server(model_path: str, case_path: str, port: int) -> Optional[subprocess.Popen]:
|
||||
"""启动llama-server进程,返回subprocess.Popen对象。如果启动失败,返回None。"""
|
||||
cmd = ["sudo", SERVER_PATH, "-m", str(model_path), "--case", str(case_path), "--port", str(port), "-np", str(1), "--verbose", "--no-warmup"]
|
||||
print(f"Starting server with: Model='{model_path}', Case='{case_path}'")
|
||||
print(f"Command: {' '.join(cmd)}")
|
||||
try:
|
||||
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
time.sleep(1) # 稍等片刻让进程启动
|
||||
return process
|
||||
except Exception as e:
|
||||
print(f"Failed to start server: {e}")
|
||||
return None
|
||||
|
||||
def health_check(health_url: str) -> bool:
|
||||
"""健康检查,重试直到服务就绪或超时"""
|
||||
for i in range(1, MAX_RETRIES + 1):
|
||||
try:
|
||||
response = requests.get(health_url, timeout=15)
|
||||
if response.status_code == 200:
|
||||
print(f"✔ llama-server is ready (attempt {i}/{MAX_RETRIES})")
|
||||
return True
|
||||
except requests.exceptions.RequestException as e:
|
||||
pass # 忽略连接错误
|
||||
print(f" Waiting for service to be ready (attempt {i}/{MAX_RETRIES}, retry in {RETRY_DELAY}s)...")
|
||||
time.sleep(RETRY_DELAY)
|
||||
print("✘ llama-server startup timed out")
|
||||
return False
|
||||
|
||||
def send_request(prompt: str, model_path: str, api_url: str) -> Optional[dict]:
|
||||
"""发送对话请求,提取并返回性能数据。如果失败,返回None。"""
|
||||
request_body = {
|
||||
"model": model_path,
|
||||
"messages": [{"role": "user", "content": prompt}],
|
||||
"stream": False
|
||||
}
|
||||
print(f" Sending request with prompt: '{prompt[:50]}...'" if len(prompt) > 50 else f" Sending request: '{prompt}'")
|
||||
try:
|
||||
response = requests.post(api_url, json=request_body, timeout=150)
|
||||
response.raise_for_status()
|
||||
data = response.json()
|
||||
except requests.exceptions.Timeout:
|
||||
print(f" ✗ Request timed out.")
|
||||
return None
|
||||
except requests.exceptions.RequestException as e:
|
||||
print(f" Request failed: {e}")
|
||||
return None
|
||||
except json.JSONDecodeError as e:
|
||||
print(f" JSON decode failed: {e}")
|
||||
return None
|
||||
|
||||
# 提取数据
|
||||
usage = data.get("usage", {})
|
||||
timings = data.get("timings", {})
|
||||
prompt_tokens = usage.get("prompt_tokens", 0)
|
||||
completion_tokens = usage.get("completion_tokens", 0)
|
||||
decode_tps = timings.get("predicted_per_second", 0.0)
|
||||
predicted_ms = timings.get("predicted_ms", 0.0)
|
||||
prompt_ms = timings.get("prompt_ms", 0.0)
|
||||
ttft = predicted_ms + prompt_ms
|
||||
prefill_tps = timings.get("prompt_per_second", 0.0)
|
||||
message = data.get("choices", [{}])[0].get("message", {})
|
||||
content = message.get("reasoning_content") or message.get("content", "")
|
||||
|
||||
return {
|
||||
"prompt_tokens": prompt_tokens,
|
||||
"completion_tokens": completion_tokens,
|
||||
"memory_usage": 0,
|
||||
"decode_tps": decode_tps,
|
||||
"ttft": ttft,
|
||||
"prefill_tps": prefill_tps,
|
||||
"Power": 0,
|
||||
"prompt": prompt,
|
||||
"content": content,
|
||||
"model_path": model_path
|
||||
}
|
||||
|
||||
def log_data(metrics: dict, case_path: str, case_name: str, result: str):
|
||||
"""记录数据到CSV文件"""
|
||||
if metrics is None:
|
||||
print("No valid data, skipping logging.")
|
||||
return
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
with open(LOG_FILE, "a") as f:
|
||||
row = f"{timestamp},{metrics['model_path']},{case_path},{case_name},{metrics['prompt_tokens']},{metrics['completion_tokens']},{metrics['memory_usage']:.2f},{metrics['decode_tps']:.2f},{metrics['ttft']:.2f},{metrics['prefill_tps']:.2f},{metrics['Power']:.1f},{result}\n"
|
||||
f.write(row)
|
||||
with open(CONTENT_FILE, "a") as f:
|
||||
# 对内容中的换行符和逗号进行处理,用空格替换换行,用占位符替换逗号以确保CSV格式正确
|
||||
DELIMITER = '\t'
|
||||
# safe_content = metrics['content'].replace('\n', ' ').replace(',', ';')
|
||||
# safe_prompt = metrics['prompt'].replace('\n', ' ').replace(',', ';')
|
||||
row = f"{timestamp},{metrics['model_path']}{DELIMITER}{case_path}{DELIMITER}{case_name}{DELIMITER}{metrics['prompt']}{DELIMITER}{metrics['content']}\n"
|
||||
f.write(row)
|
||||
|
||||
def main():
|
||||
"""主函数,执行完整流程"""
|
||||
print("=== Starting Automated llama-server Test Suite (Fixed Prompt) ===")
|
||||
|
||||
# 1. 初始化日志文件
|
||||
init_log_file()
|
||||
|
||||
# 2. 解析参数
|
||||
args = parse_args()
|
||||
|
||||
# 设置固定prompt
|
||||
#fixed_prompt = args.prompt
|
||||
print(f"Using fixed prompt: '{FIXED_PROMPT}'")
|
||||
|
||||
# 发现所有包含.gguf文件的文件夹
|
||||
test_cases = discover_gguf_models(args.case_path)
|
||||
|
||||
if not test_cases:
|
||||
print("No folders with .gguf files found. Exiting.")
|
||||
sys.exit(1)
|
||||
|
||||
overall_results = []
|
||||
|
||||
# 3. 遍历每个测试组合
|
||||
for model_path, case_path, case_name in test_cases:
|
||||
server_process = None
|
||||
combination_result = "FAIL" # 默认失败
|
||||
print(f"\n{'='*60}")
|
||||
print(f"Testing Case:")
|
||||
print(f" Case Name: {case_name}")
|
||||
print(f" Case Path: {case_path}")
|
||||
print(f" Model: {model_path}")
|
||||
print(f"{'='*60}")
|
||||
|
||||
try:
|
||||
# 启动服务器
|
||||
server_process = start_server(model_path, case_path, args.port)
|
||||
if not server_process:
|
||||
raise RuntimeError(f"Failed to start server for model '{model_path}' and case '{case_path}'")
|
||||
|
||||
# 健康检查
|
||||
health_url = f"http://localhost:{args.port}/health"
|
||||
api_url = f"http://localhost:{args.port}/v1/chat/completions"
|
||||
|
||||
if not health_check(health_url):
|
||||
raise RuntimeError("Health check failed. Server may not have started correctly.")
|
||||
|
||||
# 使用固定prompt发送请求
|
||||
print(f"\n Sending test request with fixed prompt...")
|
||||
|
||||
metrics = send_request(FIXED_PROMPT, model_path, api_url)
|
||||
|
||||
if metrics:
|
||||
# 记录请求的详细日志
|
||||
log_data(metrics, case_path, case_name, "REQUEST_OK")
|
||||
combination_result = "PASS"
|
||||
print(f"\n ✔ Case '{case_name}' TEST PASSED: Request succeeded.")
|
||||
else:
|
||||
combination_result = "FAIL"
|
||||
print(f"\n ✗ Case '{case_name}' TEST FAILED: Request failed.")
|
||||
|
||||
# 记录该组合的最终结果(汇总行)
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
# with open(LOG_FILE, "a") as f:
|
||||
# summary_row = f"{timestamp},{model_path},{case_path},{case_name},,,,,,,,{combination_result}\n"
|
||||
# f.write(summary_row)
|
||||
|
||||
overall_results.append((model_path, case_path, case_name, combination_result))
|
||||
|
||||
except Exception as e:
|
||||
print(f"\n ✗ An error occurred during testing of case '{case_name}': {e}")
|
||||
combination_result = "ERROR"
|
||||
overall_results.append((model_path, case_path, case_name, f"ERROR: {e}"))
|
||||
finally:
|
||||
# 停止当前组合的服务器进程
|
||||
if server_process and server_process.poll() is None:
|
||||
print(f" Stopping server for case '{case_name}'...")
|
||||
server_process.terminate()
|
||||
try:
|
||||
server_process.wait(timeout=5)
|
||||
except subprocess.TimeoutExpired:
|
||||
server_process.kill()
|
||||
# 短暂等待,避免端口占用影响下一个测试
|
||||
time.sleep(2)
|
||||
|
||||
# 4. 打印最终汇总报告
|
||||
print(f"\n{'='*60}")
|
||||
print("TEST SUITE SUMMARY:")
|
||||
print(f"{'='*60}")
|
||||
for model_path, case_path, case_name, result in overall_results:
|
||||
status_symbol = "✔" if result == "PASS" else "✗"
|
||||
print(f"{status_symbol} Case: {case_name:50} | Result: {result}")
|
||||
|
||||
# 判断整个测试套件是否全部通过
|
||||
all_passed = all(result == "PASS" for _, _, _, result in overall_results)
|
||||
if all_passed:
|
||||
print("\n✔ All test cases PASSED.")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("\n✗ Some test cases FAILED or encountered ERRORS.")
|
||||
sys.exit(1)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
@@ -0,0 +1,339 @@
|
||||
#!/usr/bin/env python3
|
||||
import subprocess
|
||||
import time
|
||||
import requests
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
import argparse
|
||||
from datetime import datetime
|
||||
import csv
|
||||
import random
|
||||
|
||||
# 配置常量
|
||||
LOG_FILE = "llama_perf_logs.csv"
|
||||
CONTENT_FILE = "llama_content_logs.csv"
|
||||
MAX_RETRIES = 10
|
||||
RETRY_DELAY = 15
|
||||
|
||||
def init_log_file():
|
||||
"""初始化日志文件,写入CSV表头(如果文件不存在)"""
|
||||
if not os.path.exists(LOG_FILE):
|
||||
with open(LOG_FILE, "w") as f:
|
||||
f.write("timestamp,model,prompt_tokens,completion_tokens,memory_usage,Decode_TPS,TTFT,Prefill_TPS,Power\n")
|
||||
print(f"Initialized log file: {LOG_FILE}")
|
||||
if not os.path.exists(CONTENT_FILE):
|
||||
with open(CONTENT_FILE, "w") as f:
|
||||
f.write("timestamp,model,prompt,content\n")
|
||||
print(f"Initialized content file: {CONTENT_FILE}")
|
||||
|
||||
def load_test_cases(test_file):
|
||||
"""从JSON文件加载测试用例"""
|
||||
try:
|
||||
with open(test_file, 'r', encoding='utf-8') as f:
|
||||
data = json.load(f)
|
||||
|
||||
meta = data.get("meta_info", {})
|
||||
test_cases = data.get("test_cases", [])
|
||||
|
||||
print(f"filename: {test_file}")
|
||||
print(f"version: {meta.get('version', 'N/A')}")
|
||||
print(f"find {len(test_cases)} test cases")
|
||||
|
||||
return test_cases
|
||||
except FileNotFoundError:
|
||||
print(f"error: file not found {test_file}")
|
||||
sys.exit(1)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"error: JSON decode failed - {e}")
|
||||
sys.exit(1)
|
||||
|
||||
def parse_args():
|
||||
parser = argparse.ArgumentParser()
|
||||
parser.add_argument(
|
||||
"-t", "--test_path",
|
||||
required=True,
|
||||
help="Path to test case JSON file"
|
||||
)
|
||||
parser.add_argument("--host", default="127.0.0.1", help="Server host")
|
||||
parser.add_argument("--port", type=int, default=8080, help="Server port")
|
||||
parser.add_argument(
|
||||
"--loop-forever",
|
||||
action="store_true",
|
||||
help="Run test cases repeatedly until interrupted"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--interval",
|
||||
type=float,
|
||||
default=0.0,
|
||||
help="Seconds to sleep between test cases"
|
||||
)
|
||||
return parser.parse_args()
|
||||
|
||||
def start_server(server_path, model_path, case_path, port):
|
||||
"""启动llama-server进程,返回subprocess.Popen对象"""
|
||||
cmd = ["sudo", server_path, "-m", str(model_path), "--case", str(case_path), "--port", str(port), "-np", str(1), "--verbose", "--no-warmup"]
|
||||
print(f"Start server: {' '.join(cmd)}")
|
||||
process = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
||||
time.sleep(1) # 稍等片刻让进程启动
|
||||
return process
|
||||
|
||||
def health_check(HEALTH_URL):
|
||||
"""健康检查,重试直到服务就绪或超时"""
|
||||
for i in range(1, MAX_RETRIES + 1):
|
||||
try:
|
||||
response = requests.get(HEALTH_URL, timeout=15)
|
||||
if response.status_code == 200:
|
||||
print(f"✔ llama-server is ready (attempt {i}/{MAX_RETRIES})")
|
||||
return True
|
||||
except requests.exceptions.RequestException as e:
|
||||
pass # 忽略连接错误
|
||||
print(f"Waiting for service to be ready (attempt {i}/{MAX_RETRIES},retry in {RETRY_DELAY}s)...")
|
||||
time.sleep(RETRY_DELAY)
|
||||
print("✘ llama-server startup timed out")
|
||||
return False
|
||||
|
||||
def extract_stream_parts(chunk):
|
||||
choices = chunk.get("choices", [])
|
||||
if not choices:
|
||||
return "", ""
|
||||
|
||||
c0 = choices[0]
|
||||
delta = c0.get("delta", {}) or {}
|
||||
|
||||
think_piece = delta.get("reasoning_content", "") or ""
|
||||
answer_piece = delta.get("content", "") or ""
|
||||
|
||||
# 兼容部分服务端在非 delta 里返回
|
||||
if not think_piece and not answer_piece:
|
||||
msg = c0.get("message", {}) or {}
|
||||
think_piece = msg.get("reasoning_content", "") or ""
|
||||
answer_piece = msg.get("content", "") or c0.get("text", "") or ""
|
||||
|
||||
return think_piece, answer_piece
|
||||
|
||||
def send_request(test_case, API_URL):
|
||||
model = "deepseek"
|
||||
test_id = test_case.get("id", "unknown")
|
||||
prompt = test_case.get("prompt", "")
|
||||
if not prompt:
|
||||
return {"error": f"prompt {test_id} is null, skip"}
|
||||
|
||||
request_body = {
|
||||
"model": model,
|
||||
"messages": [{"role": "user", "content": prompt}],
|
||||
"stream": True
|
||||
}
|
||||
|
||||
print("Prompt:")
|
||||
print(prompt)
|
||||
print("Response(stream):")
|
||||
|
||||
think_parts = []
|
||||
answer_parts = []
|
||||
think_opened = False
|
||||
answer_opened = False
|
||||
|
||||
usage = {}
|
||||
timings = {}
|
||||
|
||||
try:
|
||||
with requests.post(API_URL, json=request_body, stream=True, timeout=150) as response:
|
||||
response.raise_for_status()
|
||||
response.encoding = "utf-8"
|
||||
|
||||
for raw_line in response.iter_lines(decode_unicode=False):
|
||||
if not raw_line:
|
||||
continue
|
||||
|
||||
line = raw_line.decode("utf-8", errors="replace")
|
||||
if not line.startswith("data:"):
|
||||
continue
|
||||
|
||||
payload = line[5:].strip()
|
||||
if not payload:
|
||||
continue
|
||||
if payload == "[DONE]":
|
||||
break
|
||||
|
||||
try:
|
||||
chunk = json.loads(payload)
|
||||
except json.JSONDecodeError:
|
||||
continue
|
||||
|
||||
think_piece, answer_piece = extract_stream_parts(chunk)
|
||||
|
||||
if think_piece:
|
||||
if not think_opened:
|
||||
print("[THINK] ", end="", flush=True)
|
||||
think_opened = True
|
||||
print(think_piece, end="", flush=True)
|
||||
think_parts.append(think_piece)
|
||||
|
||||
if answer_piece:
|
||||
if not answer_opened:
|
||||
if think_opened:
|
||||
print("\n[ANSWER] ", end="", flush=True)
|
||||
else:
|
||||
print("[ANSWER] ", end="", flush=True)
|
||||
answer_opened = True
|
||||
print(answer_piece, end="", flush=True)
|
||||
answer_parts.append(answer_piece)
|
||||
|
||||
if "usage" in chunk and isinstance(chunk["usage"], dict):
|
||||
usage = chunk["usage"]
|
||||
if "timings" in chunk and isinstance(chunk["timings"], dict):
|
||||
timings = chunk["timings"]
|
||||
|
||||
print()
|
||||
|
||||
except requests.exceptions.Timeout:
|
||||
return {"error": f"test case {test_id} timed out"}
|
||||
except requests.exceptions.RequestException as e:
|
||||
return {"error": f"Request failed: {e}"}
|
||||
|
||||
think_content = "".join(think_parts)
|
||||
content = "".join(answer_parts)
|
||||
|
||||
prompt_tokens = usage.get("prompt_tokens", 0)
|
||||
completion_tokens = usage.get("completion_tokens", 0)
|
||||
decode_tps = timings.get("predicted_per_second", 0.0)
|
||||
predicted_ms = timings.get("predicted_ms", 0.0)
|
||||
prompt_ms = timings.get("prompt_ms", 0.0)
|
||||
ttft = predicted_ms + prompt_ms
|
||||
prefill_tps = timings.get("prompt_per_second", 0.0)
|
||||
|
||||
return {
|
||||
"prompt_tokens": prompt_tokens,
|
||||
"completion_tokens": completion_tokens,
|
||||
"memory_usage": 0,
|
||||
"decode_tps": decode_tps,
|
||||
"ttft": ttft,
|
||||
"prefill_tps": prefill_tps,
|
||||
"Power": 0,
|
||||
"prompt": prompt,
|
||||
"think_content": think_content,
|
||||
"content": content,
|
||||
"model": model
|
||||
}
|
||||
|
||||
def print_metrics(round_id, i, total, metrics):
|
||||
print("\n" + "=" * 80)
|
||||
print(f"[Round {round_id} | {i}/{total}]")
|
||||
print(
|
||||
f"prompt_tokens={metrics['prompt_tokens']}, "
|
||||
f"completion_tokens={metrics['completion_tokens']}, "
|
||||
f"decode_tps={metrics['decode_tps']:.2f}, "
|
||||
f"ttft={metrics['ttft']:.2f} ms, "
|
||||
f"prefill_tps={metrics['prefill_tps']:.2f}"
|
||||
)
|
||||
print("=" * 80)
|
||||
|
||||
def log_data(metrics):
|
||||
"""记录数据到CSV文件"""
|
||||
if metrics is None:
|
||||
print("No valid data, skipping logging")
|
||||
return
|
||||
|
||||
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
with open(LOG_FILE, "a", newline="", encoding="utf-8") as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow([
|
||||
timestamp,
|
||||
metrics["model"],
|
||||
metrics["prompt_tokens"],
|
||||
metrics["completion_tokens"],
|
||||
f"{metrics['memory_usage']:.2f}",
|
||||
f"{metrics['decode_tps']:.2f}",
|
||||
f"{metrics['ttft']:.2f}",
|
||||
f"{metrics['prefill_tps']:.2f}",
|
||||
f"{metrics['Power']:.1f}",
|
||||
])
|
||||
|
||||
with open(CONTENT_FILE, "a", newline="", encoding="utf-8") as f:
|
||||
writer = csv.writer(f)
|
||||
writer.writerow([
|
||||
timestamp,
|
||||
metrics["model"],
|
||||
metrics["prompt"],
|
||||
metrics["content"],
|
||||
])
|
||||
|
||||
def main():
|
||||
"""主函数,执行完整流程"""
|
||||
print("=== Starting llama-server test ===")
|
||||
server_process = None
|
||||
try:
|
||||
args = parse_args()
|
||||
|
||||
# 1. 初始化日志文件,加载json测试集
|
||||
init_log_file()
|
||||
|
||||
test_cases = load_test_cases(args.test_path)
|
||||
if not test_cases:
|
||||
print("error:load test_cases failed")
|
||||
sys.exit(1)
|
||||
|
||||
HEALTH_URL = f"http://{args.host}:{args.port}/health"
|
||||
API_URL = f"http://{args.host}:{args.port}/v1/chat/completions"
|
||||
|
||||
# server_process = start_server(args.server_path, MODEL_PATH, CALBIN_PATH, PORT)
|
||||
|
||||
# 3. 健康检查
|
||||
if not health_check(HEALTH_URL):
|
||||
raise RuntimeError("Health check failed")
|
||||
|
||||
round_id = 0
|
||||
|
||||
while True:
|
||||
round_id += 1
|
||||
successful_tests = 0
|
||||
# 每轮发送次数,默认=测试集大小;可按需改成固定值
|
||||
sample_count = len(test_cases)
|
||||
|
||||
print(f"\n=== Round {round_id} start: {sample_count} samples (with replacement) ===")
|
||||
|
||||
for i in range(1, sample_count + 1):
|
||||
test_case = random.choice(test_cases) # 有放回抽样,允许重复
|
||||
print(f"\n[Round {round_id} | Progress: {i}/{sample_count}]")
|
||||
|
||||
metrics = send_request(test_case, API_URL)
|
||||
if not metrics or "error" in metrics:
|
||||
err = metrics["error"] if isinstance(metrics, dict) and "error" in metrics else "unknown error"
|
||||
print(f"[Round {round_id} | {i}/{sample_count}] ERROR: {err}")
|
||||
else:
|
||||
print_metrics(round_id, i, sample_count, metrics)
|
||||
log_data(metrics)
|
||||
successful_tests += 1
|
||||
|
||||
if args.interval > 0 and i < sample_count:
|
||||
time.sleep(args.interval)
|
||||
|
||||
print(f"=== Round {round_id} done: {successful_tests}/{sample_count} success ===")
|
||||
|
||||
if not args.loop_forever:
|
||||
break
|
||||
|
||||
except KeyboardInterrupt:
|
||||
print("\nInterrupted by user, stopping stress test...")
|
||||
except Exception as e:
|
||||
print(f"✘ Test failed: {e}")
|
||||
sys.exit(1)
|
||||
finally:
|
||||
# 6. 停止服务器进程
|
||||
if server_process and server_process.poll() is None: # 检查进程是否仍在运行
|
||||
server_process.terminate()
|
||||
try:
|
||||
server_process.wait(timeout=5)
|
||||
print("✔ Test completed")
|
||||
except subprocess.TimeoutExpired:
|
||||
server_process.kill()
|
||||
print("✔ Test completed")
|
||||
|
||||
sys.exit(0)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
set -e
|
||||
|
||||
SRC_DIR="/models/"
|
||||
|
||||
./llama.cpp/build/bin/llama-server -m ${SRC_DIR}Qwen3-30B-A3B.vocab.gguf \
|
||||
-ca ${SRC_DIR}Qwen3-30B-A3B-dynamic-W8A8-W4AF16-full_layers_merged_2_chips_4096_fa_calbin_2026-04-21 --port 8031 --temp 0 --no-warmup --top-p 0.9 --seed 42 -np 1 2>&1 | tee server_run.log
|
||||
|
||||
# ./llama.cpp/build/bin/llama-server -m ${SRC_DIR}DeepSeek-R1-Distill-Qwen-7B.vocab.gguf \
|
||||
# -ca ${SRC_DIR}DeepSeek-R1-Distill-Qwen-7B-dynamic-W8A8-W4AF16_2_chips_4096_fa --port 8031 --temp 0 --no-warmup --top-p 0.9 --seed 42 -np 1 2>&1 | tee server_run.log
|
||||
|
||||
# ./llama.cpp/build/bin/llama-server -m ${SRC_DIR}Qwen2.5-0.5B-Instruct.vocab.gguf \
|
||||
# -ca ${SRC_DIR}Qwen2.5-0.5B-Instruct-dynamic-W8A8-W4AF16_2_chips_128_fa_calbin_2026-04-21 --port 8031 --temp 0 --no-warmup --top-p 0.9 --seed 42 -np 1 2>&1 | tee server_run.log
|
||||
@@ -0,0 +1,27 @@
|
||||
{
|
||||
"meta_info": {
|
||||
"description": "大模型测试集",
|
||||
"version": "1.0",
|
||||
"date": "2026-04-01"
|
||||
},
|
||||
"test_cases": [
|
||||
{
|
||||
"id": "case_001",
|
||||
"name": "short_input",
|
||||
"category": "llm-txt",
|
||||
"prompt": "用C++写冒泡排序"
|
||||
},
|
||||
{
|
||||
"id": "case_002",
|
||||
"name": "11_input",
|
||||
"category": "llm-txt",
|
||||
"prompt": "who are you?"
|
||||
},
|
||||
{
|
||||
"id": "case_003",
|
||||
"name": "16_input",
|
||||
"category": "llm-txt",
|
||||
"prompt": "Result only: 2 + 2 = ?"
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user