Compare commits

...
1 Commits
4 changed files with 33 additions and 5 deletions
+5 -3
View File
@@ -114,11 +114,12 @@
}, },
"gen": { "gen": {
"name": "Qwen3 不同上下文长度速度对比", "name": "Qwen3 不同上下文长度速度对比",
"context_lengths": [512, 2048, 4096, 8192, 16384, 32768, 65536, 131072], "context_lengths": [4096, 8192, 16384, 32768, 65536, 98304, 131072],
"max_tokens": 128, "max_tokens": 128,
"samples": 2, "samples": 2,
"warmup": true, "warmup": true,
"avoid_cache": true "avoid_cache": true,
"interval": 5
} }
} }
``` ```
@@ -128,12 +129,13 @@
| 字段 | 类型 | 默认 | 说明 | | 字段 | 类型 | 默认 | 说明 |
|------|------|------|------| |------|------|------|------|
| `name` | string | `""` | 测试名称/主题(会存入测试记录并展示在历史与详情) | | `name` | string | `""` | 测试名称/主题(会存入测试记录并展示在历史与详情) |
| `context_lengths` | number[] | `[512,2048,4096,8192,16384,32768,65536,131072]` | 要测试的上下文长度列表,每个长度独立校准+预热+采样 | | `context_lengths` | number[] | `[4096,8192,16384,32768,65536,98304,131072]` | 要测试的上下文长度列表,每个长度独立校准+预热+采样 |
| `max_tokens` | number | `128` | 解码输出 token 长度 | | `max_tokens` | number | `128` | 解码输出 token 长度 |
| `samples` | number | `2` | 每个(长度×并发)组合的采样次数 | | `samples` | number | `2` | 每个(长度×并发)组合的采样次数 |
| `concurrency_levels` | number[] | `[1]` | 并发数列表(默认单流)。>1 时每采样同时发起 N 个并行流,聚合为整批吞吐指标;多档自动并排对比 | | `concurrency_levels` | number[] | `[1]` | 并发数列表(默认单流)。>1 时每采样同时发起 N 个并行流,聚合为整批吞吐指标;多档自动并排对比 |
| `warmup` | bool | `true` | 测试前空转预热(不计速度,按并发数预热) | | `warmup` | bool | `true` | 测试前空转预热(不计速度,按并发数预热) |
| `avoid_cache` | bool | `true` | 随机前缀避免缓存命中(每个并发流独立前缀) | | `avoid_cache` | bool | `true` | 随机前缀避免缓存命中(每个并发流独立前缀) |
| `interval` | number | `5` | 每次采样之间的间隔秒数,让接口空闲休息(0 表示不等待) |
**响应:** `{ "ok": true, "id": 9 }` **响应:** `{ "ok": true, "id": 9 }`
+5
View File
@@ -130,6 +130,11 @@
<div class="icon-input"><span class="icon"></span><input id="gen-timeout" type="number" min="30" step="30" value="1800"></div> <div class="icon-input"><span class="icon"></span><input id="gen-timeout" type="number" min="30" step="30" value="1800"></div>
<div class="hint">连接测试与速度采样共用:超过此时长仍未收到数据才判定超时(默认 1800 秒 = 30 分钟)。测慢接口/超长上下文时建议调大,如 3600。</div> <div class="hint">连接测试与速度采样共用:超过此时长仍未收到数据才判定超时(默认 1800 秒 = 30 分钟)。测慢接口/超长上下文时建议调大,如 3600。</div>
</div> </div>
<div class="field">
<label>测试间隔(秒)<span class="hint-inline">每次采样之间让接口空闲</span></label>
<div class="icon-input"><span class="icon"></span><input id="gen-interval" type="number" min="0" step="1" value="5"></div>
<div class="hint">每次采样完成后等待 N 秒再发下一次请求,让大模型接口空闲休息一下(默认 5 秒,设为 0 关闭)。</div>
</div>
<div class="btn-group"> <div class="btn-group">
<button class="btn primary block" id="btn-start">▶ 开始测试</button> <button class="btn primary block" id="btn-start">▶ 开始测试</button>
<button class="btn danger block" id="btn-cancel" disabled>■ 停止</button> <button class="btn danger block" id="btn-cancel" disabled>■ 停止</button>
+1
View File
@@ -71,6 +71,7 @@ function currentGen() {
avoid_cache: $("#gen-avoid-cache").checked, avoid_cache: $("#gen-avoid-cache").checked,
warmup: $("#gen-warmup").checked, warmup: $("#gen-warmup").checked,
read_timeout: parseInt($("#gen-timeout").value) || 1800, read_timeout: parseInt($("#gen-timeout").value) || 1800,
interval: Math.max(0, parseInt($("#gen-interval").value) || 5),
}; };
} }
+22 -2
View File
@@ -32,6 +32,15 @@ class TestRunner(threading.Thread):
def log(self, level, msg): def log(self, level, msg):
db.add_log(self.test_id, level, msg) db.add_log(self.test_id, level, msg)
def _sleep_interval(self, secs):
"""测试间隔等待:期间可被用户停止,返回 False 表示已被取消"""
deadline = time.time() + secs
while time.time() < deadline:
if self.should_stop():
return False
time.sleep(0.2)
return not self.should_stop()
# ───────────────────────── 主流程 ───────────────────────── # ───────────────────────── 主流程 ─────────────────────────
def run(self): def run(self):
@@ -61,6 +70,11 @@ class TestRunner(threading.Thread):
max_tokens = max(1, int(gen.get("max_tokens", 128))) # 解码输出长度 max_tokens = max(1, int(gen.get("max_tokens", 128))) # 解码输出长度
avoid_cache = bool(gen.get("avoid_cache")) avoid_cache = bool(gen.get("avoid_cache"))
warmup = bool(gen.get("warmup", True)) # 测试前空转预热 warmup = bool(gen.get("warmup", True)) # 测试前空转预热
# 测试间隔(秒):每次采样之间让接口空闲休息,默认 5 秒,0 表示不等待
try:
interval = 5 if gen.get("interval") in (None, "") else max(0, float(gen.get("interval")))
except (TypeError, ValueError):
interval = 5
# 并发数列表(默认单流 [1];支持 2/4 及自定义,如 [1,2,4,8] # 并发数列表(默认单流 [1];支持 2/4 及自定义,如 [1,2,4,8]
raw_concs = gen.get("concurrency_levels") or [] raw_concs = gen.get("concurrency_levels") or []
@@ -73,10 +87,10 @@ class TestRunner(threading.Thread):
if name: if name:
self.log("INFO", "测试名称(主题): %s" % name) self.log("INFO", "测试名称(主题): %s" % name)
self.log("INFO", "提供商: %s | 模型: %s" % (lp.PROVIDER_LABELS.get(provider, provider), model)) self.log("INFO", "提供商: %s | 模型: %s" % (lp.PROVIDER_LABELS.get(provider, provider), model))
self.log("INFO", "上下文长度: %s tokens | 生成长度: %d tokens | 并发数: %s | 每个组合采样: %d 次 | 预热: %s | 避免缓存: %s" self.log("INFO", "上下文长度: %s tokens | 生成长度: %d tokens | 并发数: %s | 每个组合采样: %d 次 | 预热: %s | 避免缓存: %s | 测试间隔: %g"
% (" / ".join(str(x) for x in lengths), max_tokens, % (" / ".join(str(x) for x in lengths), max_tokens,
" / ".join(str(x) for x in concurrency_levels), n, " / ".join(str(x) for x in concurrency_levels), n,
"" if warmup else "", "" if avoid_cache else "")) "" if warmup else "", "" if avoid_cache else "", interval))
to = self.gen.get("read_timeout") or "默认(1800)" to = self.gen.get("read_timeout") or "默认(1800)"
self.log("INFO", "请求超时: 连接 %s s | 等待首字/预处理 %s s(接口慢可在左侧调大)" self.log("INFO", "请求超时: 连接 %s s | 等待首字/预处理 %s s(接口慢可在左侧调大)"
% (self.gen.get("connect_timeout") or 60, to)) % (self.gen.get("connect_timeout") or 60, to))
@@ -86,6 +100,7 @@ class TestRunner(threading.Thread):
self.log("INFO", "校准完成: %.3f tok/字符(%.2f 字符/token" % (ratio, 1.0 / ratio)) self.log("INFO", "校准完成: %.3f tok/字符(%.2f 字符/token" % (ratio, 1.0 / ratio))
run_seq = 0 run_seq = 0
sample_done = 0 # 已完成的采样数,用于控制测试间隔(首个采样不等待)
last_conc = None # 只在实际切换并发档时打印一次表头,避免同一并发数重复刷屏 last_conc = None # 只在实际切换并发档时打印一次表头,避免同一并发数重复刷屏
for L in lengths: for L in lengths:
if self.should_stop(): if self.should_stop():
@@ -103,7 +118,12 @@ class TestRunner(threading.Thread):
for i in range(1, n + 1): for i in range(1, n + 1):
if self.should_stop(): if self.should_stop():
raise StopRequested() raise StopRequested()
if sample_done > 0 and interval > 0:
self.log("INFO", "⏳ 接口空闲休息 %g 秒后继续下一采样..." % interval)
if not self._sleep_interval(interval):
raise StopRequested()
run_seq += 1 run_seq += 1
sample_done += 1
self.log("INFO", "── [%d tok · 并发%d] 采样 %d/%d 开始 ──" % (L, C, i, n)) self.log("INFO", "── [%d tok · 并发%d] 采样 %d/%d 开始 ──" % (L, C, i, n))
try: try:
m = self._run_sample(C, base_prompt, max_tokens, avoid_cache) m = self._run_sample(C, base_prompt, max_tokens, avoid_cache)