data: publish complete Calculet NPU research archive

This commit is contained in:
2026-08-02 14:57:11 +08:00
parent b5c4fbffab
commit 98e6cadcb6
12400 changed files with 6166642 additions and 0 deletions
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
+6
View File
@@ -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()
+12
View File
@@ -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 = ?"
}
]
}