【免费下载链接】CLM
本指南以 examples/t_rex/README.md 为骨架,讲解如何用同一个 System One 客户端(clm.CLMClient)驱动 CLM 与 TypeSafe Jev 两个端点,在 Chrome 离线恐龙游戏的确定性 Python 克隆中实时比赛 60 秒,验证模型在高频决策、真实网络延迟与安全护盾下的综合表现。读完本文,你将掌握 T-Rex 基准测试的完整架构(确定性引擎、物理规划器、多请求在途、护盾干预),并能在本仓库中一键复现、解读results/*_realtime.json报告。
一、概览:同一套 wire format,一个客户端打两个端点
T-Rex runner 的核心实验设计是**“同一个被服务的 head、原封不动”:CLM 与 TypeSafe 托管的 Jev(jev-latest)在 Chrome 离线恐龙游戏上实时比赛。因为 CLM 的POST /v1/systemone原生兼容 TypeSafe 的线格式(wire format),两边玩家可以复用同一个客户端**——即 examples/common.py 中基于clm.CLMClient构建的Judge封装——只有 base URL 与 API key 不同。
从 examples/common.py 的ENDPOINTS可以看到两个端点的默认配置:
| 玩家 | base URL 环境变量 | 默认 base URL | 模型环境变量 | 默认模型 | key 环境变量 |
|---|---|---|---|---|---|
| clm | CLM_BASE_URL | http://127.0.0.1:8700 | CLM_MODEL | clm-latest | CLM_API_KEY |
| jev | TYPESAFE_BASE_URL | https://api.typesafe.ai | TYPESAFE_MODEL | jev-latest | TYPESAFE_API_KEY/JEV_API_KEY |
Judge在裸客户端之上额外提供了几层能力,均与实时评测场景直接相关:
- 指数退避重试:对 429 / 529 / 5xx 与传输错误重试(
RETRY_STATUS),适合脚本化批量评测;而实时比赛中真正发请求的ApiBackend(examples/t_rex/trex/backends.py)则不做重试,因为迟到的答案在实时游戏里已经无用,失败调用直接计入报告的errors。 - 延迟与 token 记账:每次请求记录客户端侧 wall-clock 延迟与输入 token 数,
judge.stats()可汇总 p50/p95。 - 可选 sqlite 答案缓存:断点续跑时命中缓存的回复不计入延迟(
cached=True时latency_ms=0)。 choose分块锦标赛:Jev 每题最多 255 个选项(max_options=255),超出时按块询问,再对每块头部做最终 Choice;CLM 无上限。
二、评测任务与计分口径
README 定义的任务非常明确:让恐龙存活 60 秒。
- 比赛场地是 examples/t_rex/trex/engine.py 实现的确定性 Python 克隆:与 Chromium 原版恐龙游戏保持相同常量、跳跃物理、碰撞盒、障碍规则与速度曲线(该克隆源自 laya-vs-jev,Apache-2.0;引擎 docstring 注明规则源自 Chromium,BSD-3-Clause)。
- 游戏以固定的60 FPS步进、实时推进(
FPS = 60,FRAME_MS = 1000 / 60)。 - 共5 个带种子的赛道(
--seeds 5,默认--seed 0),每个赛道 60 秒(--duration 60.0);撞毁后1.5 秒自动重开(RESTART_DELAY_MS = 1500)。 - “存活”定义为观察窗口内零死亡(
survived: game.deaths == 0)。 - 评测夹具的护盾(shield)默认开启,与上游一致:若规划器将某答案标为 unsafe,则用模型概率最高的安全动作替换;另有应急检查可在碰撞前代为行动。报告会逐条统计这类干预。
因此一个清晰的读数口径是:存活数字衡量的是“系统整体”(模型 + 规划器 + 护盾),而 agreement(与规划器最佳动作的一致性)与 intervention(干预)行衡量的是模型本身。
三、确定性 T-Rex 引擎:可复现的 60 FPS 世界
examples/t_rex/trex/engine.py 是一个纯 Python、无渲染依赖的确定性仿真,它保证“同一种子从同一状态开始的两局游戏,只要都还活着,就遇到完全相同的障碍序列”。关键实现事实:
- 确定性随机流:
RandomCourse为每个(seed, run_index, obstacle_index)派生独立随机流(random.Random(f"course-{seed}-{run_index}-{index}")),因此无论哪个进程、何时询问,同一障碍都能重现。 - 原版常量与规则:
SPEED=6、ACCELERATION=0.001、MAX_SPEED=13、GAP_COEFFICIENT=0.6、MAX_OBSTACLE_LENGTH=3、MAX_OBSTACLE_DUPLICATION=2、INVERT_DISTANCE=700等,以及三种障碍类型cactusSmall / cactusLarge / pterodactyl(各自带 width/height/y 高度档位/min_speed/碰撞盒,翼龙还有multiple_speed=999与双帧动画)。 - 碰撞判定沿用原版的两段式:先做外接矩形粗判,再做细化碰撞盒逐对相交(
check_collision)。 - 跳跃物理(
jump_step)逐帧更新:GRAVITY=0.6、INITIAL_JUMP_VELOCITY=-10、DROP_VELOCITY=-5,并复刻了“跳跃初速随当前速度略微降低”(INITIAL_JUMP_VELOCITY - speed / 10)与 speed drop / duck 的相互转换细节。 - 障碍生成规则:障碍移动量用
math.floor((speed + offset) * FPS / 1000 * dt)精确复算;连续同类障碍受too_many限制;Obstacle的gap由当前速度解析spec.gap(0~1 区间)得出。
正因为它把原版的规则、常量和碰撞盒都搬进了 Python,规划器才能在同一套算术上做逐帧前瞻,而不是用近似模型。
四、物理规划器:精确到帧的安全性分析
examples/t_rex/trex/planner.py 是整套评测的灵魂:一个确定性物理规划器,只观察屏幕上已有的障碍,用引擎自身的算术预测它们未来的运动,并以固定的决策粒度搜索恐龙未来的按键序列。它最重要的特点是把回答延迟计入安全分析:
当前选择的动作,只有在模型的答案真正到达的时刻才会生效,并且会一直保持到下一个答案到达。
也就是说,规划器要为“答案落地那一帧”标注每个动作(jump/duck/run)是 safe 还是 unsafe,并选出时序余量最佳的那个动作。两个模型收到的是完全相同的标签;可选的护盾也用同一份 safe 集合来否决模型的不安全选择。
4.1 搜索结构:与“自己的答案时序”下棋
Search类把安全性证明建模成一场玩家与自身答案时序之间的对抗游戏:每个答案落在上一答案之后若干帧,落点区间gap = (lo, hi)由对手任选。一个位置“可赢”,当且仅当存在某个动作对区间内的每一个落点都能继续赢,而超过 horizon 的位置视为已赢。这正是玩家在下一次答案被规划时所处的处境——所以标为 safe 的动作,总能保证在下一个答案落地时(无论何时落地)仍有安全动作可用。
- 搜索在
(frame, trex state, held keys, zero gap allowed, lock)上做记忆化(memoise),并对“同一帧落两个答案(zero gap)但不可能落三个”做了特判。 StaggeredSearch处理多请求在途的玩家:提问每隔几帧发出,等待下一问题的时间至多为period帧;若某个答案保持按键不变,则它对之后每一个可能的提问帧都必须是安全的;若答案改变了按键,则在落点区间内逐帧验证,而在此期间按旧按键问出的答案作废(会被丢弃)。- horizon 由屏幕上最远障碍的到达帧数推导(
Planner.horizon,默认max_horizon=150帧)。 - 当没有任何动作能扛过全部时序时,规划器退化为best-effort 选择:对每个动作逐一评估“若后续答案按往常时间到达,游戏还能否继续”,优先选择存活落点最多的动作,并在
Plan.robust=False上记录这次降级(报告中对应best_effort_decisions)。
4.2 推荐策略:给“迟到答案”留足余量
recommend()的原则是“答案只会迟到、不会早到”,因此最佳动作是在最早能独立清除威胁的那一刻开始机动,把整个时序窗口都变成余量:
- 空中时:快速 drop 能更早落地,但它是按键变更;下一答案还很远时不轻易花掉,只有
safe["duck"]且(run不安全,或玩家足够快且当前弧线不足以越过障碍)时才选duck,否则保持run。 - 地面时:若
threat is None就继续run;否则先看翼龙是否足够近(DUCK_LEAD_FRAMES = 14的提前量)再考虑duck,再试jump(用(action, until)lock 验证单一机动能否独立过关),最后才是run等待。
4.3 发给模型的标签
规划器输出的notes会把每个动作翻译成模型可读的自然语言,例如:
jump: "Clears the {label}"/"Jumps too early"/"Jumps for no reason"duck: "Passes under the {label}"(翼龙)/"Crouches; no benefit"run: "Waits; must act soon"/"Keeps running"
五、请求格式:每个决策一个 Choice
README 给出了比赛双方收到的逐决策请求原文(完全相同):
state: Dino runner game. 2 large cacti ahead, 96 px away. question: Choose the best safe action for the dinosaur. jump: Safe. Clears the 2 large cacti. Best. duck: Unsafe. Hits the 2 large cacti. Collision. run: Unsafe. Hits the 2 large cacti. Collision.从 examples/t_rex/trex/backends.py 的build_question看,它把规划结果构造成一次 Choice 请求:state描述局面(Dino runner game. …,含“前方是什么、距离多少像素”,空中时追加The dino is in the air.),question是 Choice 的instructions,三个criteria键分别承载jump / duck / run的标签与注释。两种提示词风格:
- labeled(默认):
Safe. {note}. Best./Safe. {note}./Unsafe. {note}. Collision.——不直接说出答案,只给标注。 - guided:
Best: {note}, safe./Safe: {note}./Collision: {note}.,并在 state 里追加Recommended action: {best}.。
模型返回三个动作上的概率分布(answers["action"]["probabilities"]),decide会先做合法性检查(概率必须有限且落在 0~1 之间)。玩家取概率最高的动作执行;护盾模式下,若该动作不在plan.safe_actions中,则改为 safe 集合内概率最高的动作。
六、实时决策回路:多请求在途、迟到答案作废
examples/t_rex/trex/pilot.py 实现实时决策主循环,README 的两个关键设计点是:
- 模型持续保持忙碌:一个答案就绪后,立即就“最新一帧”发问,并把“尚未落地的答案”告知规划器(
pending)。空窗会让 GPU 降频,而且真人看屏幕也不会等下一帧才开始思考。每个答案在到达后的第一个帧边界生效,所以模型的完整延迟本身就是游戏的一部分。 - 多请求在途:托管模型能并行应答,因此 Pilot 可以同时保持多个请求在途、每隔几帧发问(
stagger帧 =round(typical_frames / min(inflight, 4)),上限 12 帧)。每个答案仍然比发出时“老一个往返”,但玩家每几帧就轮到一次,而不是每个往返才轮到一次。每条请求都建立在“落地前按键不变”的前提上;一旦有别的答案改变了按键,前提即被打破,迟到的答案被丢弃、绝不执行(报告里对应answers_discarded)。
6.1 独立进程隔离延迟(trex/brain.py)
每个玩家运行在自己的进程里(examples/t_rex/trex/brain.py 的RemoteBrain通过 multiprocessing spawn 子进程跑serve),原因是 Python 解释器锁:如果窗口、赛道设计器和另一个玩家在同一进程,一次由大量短 Python 步骤组成的模型调用可能要为解释器锁等待数百毫秒。子进程内用ThreadPoolExecutor(workers=inflight)为每个在途请求开一条线程,并通过sys.setswitchinterval(0.001)让规划线程不至于卡住模型线程。
开局前还有warm-up:warm()先用当前帧快照向模型问 4 次,测出该玩家的答案延迟分布,得到latency_frames / typical_frames / jitter_frames / interval_frames(都以帧计),供规划器设定(first, gap, period)时序;inflight > 1时还会在赛前打开所有连接,避免比赛中途为 TCP 握手买单。
6.2 时序自适应的实时性
Pilot 会持续观察实际落地帧(observe_timing):一旦延迟突然超过latency_frames + max(4, jitter_frames*2)就立即清空历史(快速遗忘旧 regime),随后在最近 16 次回复内逐渐恢复。timing()把观测转成规划器需要的(first, gap, period):单请求在途时gap等于落地时间;多请求在途时period取最近提问间隔的 p95(longest_wait,上限 24 帧)。
七、护盾:到达检查与应急检查
护盾并非简单的“按标签否决”,而是由 examples/t_rex/trex/safety.py 的protect(snap, action)做有界到达检查,独立于递归规划器:
- 可逆按键(如保持 run/duck)做 12 帧碰撞检查(
urgent=12)。 - 跳跃与空中按键变更要检查到“即将到来的障碍被清除”为止,上限 42 帧(
horizon=42),因为一次不安全的起跳或下坠可能在很远的未来才造成碰撞。 - 若“立刻起跳”被拒,会尝试有界延迟跳跃是否可行(在恢复范围内搜索至多十二条固定轨迹,绝不递归展开决策树)。
- 对“已处于安全弧线”的空中 drop 会格外保守:只有当继续 hold 原按键真的会失败时,才允许缩短弧线。
护盾在 Pilot 中有两个触发点,分别计数:
- arrival_saves(到达时护盾):答案落地、即将应用时,
protect把 unsafe 的原始动作替换为安全动作(decision.arrival_intervened)。 - emergency_saves(应急护盾):答案仍在途或已失败时,主循环每帧用
protect(snapshot, held)检查当前按键,不安全就代为改键。
README 明确提醒:护盾开启计数的是“系统整体”的干预,而agreement_with_planner(模型所选与规划器最佳动作的一致性)与 intervention 行才是衡量模型本身的口径。
八、复现运行:从启动 clm-serve 到跑完 5 个种子
8.1 前置:本地 CLM 服务
README 要求先按主 README.md 的 Quickstart 启动clm-serve:
# 1. encoder(Qwen3-8B embeddings) vllm serve Qwen/Qwen3-8B --served-model-name qwen3-8b --runner pooling --max-model-len 2048 --port 8090 & # 2. CLM API on :8700(首次运行会下载约 75 MB 的参考 head) clm-serve对 Jev 玩家则需要在<repo>/.env中写入TYPESAFE_API_KEY=...(load_env会把KEY=VALUE行导出进环境变量,已有环境变量优先)。
8.2 安装与运行
pip install -r requirements.txt # 仓库根目录下,clm-serve 已启动(见主 README 的 Quickstart) python examples/t_rex/run.py --model clm # 5 seeds × 60 s,实时,护盾开启 python examples/t_rex/run.py --model jev python examples/t_rex/run.py --model clm --no-shield # 模型的答案原样执行 python examples/t_rex/run.py --model clm --lockstep 6 # 模型回答期间游戏冻结每次运行会实时打印一行摘要(survived / deaths / best_score / decisions / agreement / latency_p50 / model_p50 / errors),并把完整结果写入 examples/t_rex/results 下的results/<model>_<mode>.json。
8.3 环境变量覆盖
| 变量 | 作用 | 默认值 |
|---|---|---|
CLM_BASE_URL | CLM 端点地址 | http://127.0.0.1:8700 |
CLM_API_KEY | CLM 鉴权(本地服务通常留空) | 无 |
CLM_MODEL | CLM 请求模型名 | clm-latest |
TYPESAFE_BASE_URL | Jev 端点地址 | https://api.typesafe.ai |
TYPESAFE_MODEL | Jev 请求模型名 | jev-latest |
TYPESAFE_API_KEY/JEV_API_KEY | Jev 鉴权(也可放<repo>/.env) | 无 |
8.4 run.py 全部命令行参数
对照 examples/t_rex/run.py,除 README 中的--model外还有一批实验旋钮:
| 参数 | 默认值 | 说明 |
|---|---|---|
--model {clm,jev} | 必填 | 选择玩家端点 |
--seeds N | 5 | 运行局数,第 i 局使用--seed + i |
--seed N | 0 | 起始种子 |
--duration S | 60.0 | 每局秒数 |
--no-shield | 关 | 模型答案即使被标 unsafe 也原样执行 |
--lockstep FRAMES | 无 | 游戏等待模型回答期间冻结,之后每个决策推进 FRAMES 帧(延迟被移除) |
--inflight N | 6 | 实时模式保持的在途请求数(1–8,越界报错) |
--course-style {original,staged} | original | original 用 Chrome 原版障碍规则;staged 用上游更平缓的分阶段赛道(examples/t_rex/trex/course.py:Warm-up / Sprint / Bird attack / Final challenge 四阶段,为鸟类出现前的暖身期保留更多恢复空间) |
--prompt {labeled,guided} | labeled | 两种提示词风格(见第五节) |
--out PATH | 自动 | 结果 JSON 输出路径,默认results/<model>_<mode>[_noshield].json |
注意:--no-shield与--lockstep是供你自己做实验的旋钮,不在 README 的对比表口径内。
九、结果报告解读
每局运行产出results/<model>_realtime.json(README 说明其内容):per-seed 报告(deaths、score、decisions、answer latency、与规划器最佳动作的一致性、被丢弃的迟到答案、errors)。仓库已附 examples/t_rex/results/clm_realtime.json 作为示例(另一个玩家 examples/t_rex/results/jev_realtime.json 亦随仓库提供,可自行对照)。每个 seed 的字段如下:
| 字段 | 含义 |
|---|---|
seed | 该局种子 |
survived/deaths | 存活(零死亡)与否 / 死亡次数 |
best_score/scores | 最好成绩 / 每轮成绩 |
decisions | 实际生效的决策数 |
agreement_with_planner | 模型所选与规划器最佳动作的一致性比例(衡量模型) |
latency_ms_p50 / p95 | 客户端侧端到端延迟分位(含排队与网络) |
model_ms_p50 | 模型侧推断延迟分位 |
answers_discarded | 前提被打破、被丢弃的迟到答案数 |
errors/last_error | 请求失败次数与最后一条错误 |
best_effort_decisions | 规划器降级为 best-effort 的次数 |
shield_interventions/arrival_saves/emergency_saves | 规划阶段干预 / 到达时护盾 / 应急护盾 |
input_tokens | 输入 token 总量 |
game_seconds/wall_seconds | 游戏内时间 / 墙上时间 |
host_stall_seconds_dropped | 主机停顿被跳过的墙上时间 |
model/endpoint | 应答模型与端点描述 |
文件头部summary则汇总:mode(realtime / lockstepN)、shield、course_style、prompt、duration_s、seeds、总survived / deaths、mean_best_score、mean_decisions、mean_agreement_with_planner、latency_ms_p50_median、model_ms_p50_median、errors、answers_discarded、shield_interventions(三项护盾计数之和)与inflight。
以随仓库附带的 CLM 示例(clm-latest=CLM_v0.1-8B.pt,Qwen3-8B 编码器,单卡 RTX 4090,6 请求在途)为例,5 个种子全部存活、总死亡 0,mean_best_score 697.0,mean_decisions 3341.8,mean_agreement_with_planner 0.658,latency_ms_p50_median16.5 ms、model_ms_p50_median2.6 ms,errors 0,answers_discarded 合计 1250,护盾干预合计 4883(其中 arrival_saves 逐 seed 为 760/807/889/1029/1034,emergency_saves 为 0)。需要强调的是:这些数字只代表仓库内该次运行样本,任何跨模型的胜负结论都应基于你自己在同一环境、同一批种子下重跑两边的结果。
9.1 host_stall_seconds_dropped 的含义
实时运行依赖宿主机节奏:examples/t_rex/trex/pilot.py 的Pacer把墙上时间换算成 60 FPS 游戏帧;若主机停顿,错过的帧数会被丢弃而不是补跑成 burst(burst 会让游戏在无答案可落地的窗口里“盲跑”)。被跳过的墙上时间计入host_stall_seconds_dropped,因此一台停顿的主机会在结果中直接可见——这是判断某次运行是否“实时可信”的第一指标。
十、评测边界与可验证性
最后,把 README 的原始口径再强调一遍,避免误读实验设计:
- 同一个 head:两端点收到相同的 state 与相同的 Choice 问题,CLM 侧
clm-latest在 2026-09-23 运行(CLM_v0.1-8B.pt,Qwen3-8B 编码器,单卡 RTX 4090),Jev 侧在 2026-09-22 以jev-latest(应答为jev-1.13.0)运行;README 明确延迟为客户端侧、按请求计。 - 可复现:同一种子 → 同一障碍序列;规划器与引擎共享同一套算术;双端点共用同一个客户端。这些设计让任何第三方都能在自备环境中重跑并对照。
- 实验旋钮的边界:
--no-shield与--lockstep不属于对比表口径——前者让模型答案原样执行(衡量纯模型),后者把延迟从游戏中移除(衡量无延迟上限时的决策质量);README 的表格则反映“系统整体 + 真实延迟”的默认组合。
【免费下载链接】CLM
相关推荐
axios-retry与TypeScript:类型安全的重试插件开发指南
axios retry与TypeScript:类型安全的重试插件开发指南 axios retry是一个强大的Axios插件,它能够拦截失败的请求并在可能的情况下
后端网络华硕笔记本终极优化指南:G-Helper轻量级控制工具完全解析
华硕笔记本终极优化指南:G Helper轻量级控制工具完全解析 还在为Armoury Crate的臃肿和资源占用而烦恼吗?G Helper作为华硕笔记本的轻量级
桌面应用系统编程实测!Sunshine游戏串流性能对决:AMD/NVIDIA/Intel显卡延迟与帧率全面对比
实测!Sunshine游戏串流性能对决:AMD/NVIDIA/Intel显卡延迟与帧率全面对比 为什么选择自托管串流? 还在为云游戏高延迟烦恼?Sunshine
音视频后端
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考