2.6 秒不算长。可你把它放进实时游戏,玩家敲完回车,盯着 NPC 头像卡两秒半,心里已经犯嘀咕:这角色是不是掉线了。arXiv 那篇系统映射给出的范围是 2.6 秒到 15 秒,同一个“说句话”的动作,慢的快六倍。差在哪?不在模型大小,在循环外面多套了多少道工序。这篇我们把这个延迟账本拆开,先搭一个最小可运行的 NPC 响应循环,跑一遍,再对照那篇论文的组件采用率,看 15 秒是怎么自己加上去的。
那篇映射从 377 篇里筛出 42 篇,第一次把组件采用、延迟、安全覆盖、可复现性放在同一把尺子下量。延迟区间只是结果,不是原因。要解释 2.6 秒和 15 秒的差距,得自己把循环搭出来。
最朴素的版本二十行能跑:
class NPC:
def __init__(self, persona, world_state):
self.persona = persona
self.memory = []
def respond(self, player, world_state):
ctx = (
f"{self.persona}\n"
f"World: {world_state}\n"
f"Recent: {self.memory[-6:]}\n"
f"Player: {player}\n"
f"NPC:"
)
raw = model.sample(ctx)
self.memory.append(f"Player: {player}")
self.memory.append(f"NPC: {raw}")
return raw
追一次执行。玩家说一句,respond 把四样东西拼成一段字符串——设定、当前世界状态、最近几轮对话、玩家输入——然后调一次 model.sample。返回的文本塞回 memory,完事。这个循环里唯一真正花钱的工序是 model.sample(ctx)。一次采样前向,序列长度决定算力。2.6 秒这头就是这么来的:上下文短,一次前向快,返回直接交给玩家。
但另一头爬到 15 秒,不是模型更大,是这循环外面多插了几道工序。论文给的那组组件采用率,读一下就是工程判断:世界状态对接 69%,短期记忆 60%,长期记忆 31%,反思机制 38%,RAG 14%。大家敢上只加长度的组件,不敢随便上加往返的组件。
世界状态对接是只加长度的典型。游戏里某个 torch 亮没亮、某扇 door 还锁没锁,序列化成一行字符串塞进 ctx。prompt 确实变长一截,但循环的形状没变,还是那次 model.sample 扛下一切。所以 69% 的系统都上了——便宜,不新增工序。短期记忆同理,self.memory[-6:] 本来就是循环的一部分,截几条拼进上下文,不是新东西。
长期记忆不是往 memory 里多 append 两条,是把 memory 换成一个带检索的存储。respond 在拼这次上下文之前,要先做一次语义检索,命中谁、把谁拼回来。这一下就多了 embedding 前向、向量索引查询、再加一段检索结果拼进上下文——三次网络往返,还没算检索回来的文本又把序列撑长。这是另一类组件。
反思机制更别扭。它要求 respond 在给玩家看台词之前,先采一版内部思考——“这个 NPC 此刻盘算的是什么”——再把这段思考拼回同一段上下文,采第二次。一次玩家交互,两次串行的主模型采样。前一发的输出加进后一发的输入,序列不但没缩短,反而多出一整块思考文本。延迟直接翻倍。
RAG 是最贵的,也是最稀少的,14%。它等于在循环还没走到 model.sample 之前,先插一脚出去翻知识库:生成查询、检索、重排、把命中的段落再拼回 prompt。有时候查询生成本身又是一次模型调用。这一套下来,主模型采样可能摊到两次,中间还夹一个检索系统。
15 秒这头就是这么堆出来的。不是哪个单点特别慢,而是每上一套组件,就在那次 model.sample 外面套一圈工序。延迟不是玄学,是循环里插了几道工序、每道工序是加了一次往返还是加了一截序列。
我一开始以为那篇映射报告 2.6 秒到 15 秒,只是想说明这技术还没到能上线。翻完数据再想,不是这么回事。这个区间真正给的是延迟账本的横截面:2.6 秒那头,基本只有世界状态和短期记忆;15 秒那头,反思、长期检索、RAG 挂得差不多齐。中间那一大段,是不同团队在“给 NPC 加多少脑子”和“玩家还能不能等”之间做的取舍。
可复现性为什么在这块地这么难,同一个根。最小循环里 prompt 是 f-string 拼出来的字符串,model.sample 后面跟着一堆没说死的参数——温度、top-p、上下文窗口、模型版本。42 篇论文能原样复现的少,因为这些系统连“输入是什么”都没版本化。换个模型版本、改一行 prompt,整个 NPC 行为就漂。一个连 prompt 都拼在代码里、没有独立版本管理的系统,谈复现约等于重做。
安全覆盖也一样。六个结构性障碍里它占一个,但根子和可复现性同一根:安全测试的前提是输入输出可枚举、可回归,而这些系统连上下文怎么拼出来的都没个稳定结构。不是不想测,是测试对象每次跑都可能不一样。
回到开头那个问题:为什么 2.6 秒起步?因为哪怕最朴素的版本,也要把设定、世界状态、对话历史、玩家输入拼成一段 token 序列,再喂给一个前向。这是这条路的底价。往后的每一分延迟,都是你自己决定往上加的。组件采用率表读出来就一句话:大家不傻,都知道哪道工序值得上、哪道工序暂时还买不起。
想再深一层,去翻那篇 arXiv 的 42 篇里挑一个开源的(arXiv DOI 10.48550/arXiv.2611.05427),照着它的组件列表拆一次延迟;或者动手给这个最小循环加一个长期记忆组件,就加一层语义检索,别的都不动。加完你会直接踩到延迟账本的第一行:检索回来是对是错,这都要等;等多久,取决于你把检索放在循环的哪一格。这块地还远没到讲完的那一步。
