AI性能参数-截断、延迟与流式输出

4 阅读7分钟

引言

在上一篇文章中,我们讲解了 TokenContext Window 两个核心概念。今天我们继续深入,探讨当上下文窗口不够用时会发生什么——Truncation(截断),以及这一切会对你的使用体验产生什么影响——Latency(延迟)Streaming(流式输出)


三、Truncation(截断):当窗口装不下时

3.1 什么是截断

结合前面的 Token → Context Window,截断的本质一句话就能说清:

Truncation = 输入/输出的文本总长度超出了模型的硬性限制(主要是上下文窗口),系统按照既定规则强行"砍掉"一部分,让剩下的内容刚好能塞进去继续跑。

它不是模型"想不起来"这么文艺,而是一个非常冷酷的预处理/后处理工程机制——而且砍哪里、怎么砍,都是提前写死的规则。

3.2 两种截断场景

① Input Truncation(输入截断)

你一次性塞进去的内容(System Prompt + 历史对话 + 附件/文档 + 当前提问)转成 token 后 > 模型上下文窗口上限。

截断策略怎么砍后果
从头截断 Drop-Start丢掉最早期的那部分 token模型"失忆"了开头聊过什么,但保留当前问题和系统提示
从尾截断 Drop-End只保留末尾 N 个 token少见,会把刚发的提问也砍掉
智能截断强制保留 System Prompt + 当前输入,优先砍中间历史体验最好,很多产品级聊天界面在用

你可能经历过:"明明刚才还在聊那个方案,怎么突然 AI 像失忆了一样?"——大概率就是从头截断把早期历史切掉了。

② Output Truncation(输出截断)

就算输入顺利塞进去了,模型的输出本身也有上限(如 max_tokens),可能是:

  • 你或 API 参数显式设了一个较小的最大值
  • 平台默认限制了单次回复长度
  • 模型触发了停止符之外的长度天花板

结果就是:回复在半截突然断掉——最后一句说到一半、代码写到一半就没了。

3.3 识别截断信号

几个很典型的信号:

现象更像截断更像模型问题
回复末尾突然中断,最后一句不完整
模型突然"忘了"你 5 轮前交代的重要约束
上传的文档前半部分完全没被引用
回复内容完整,但事实错误、逻辑跳步
API 返回里有 truncated: true

3.4 实用规避策略

  1. 别把上下文窗口当仓库用——对话历史越长越危险
  2. System Prompt 放"不可丢失"的全局规则——多数产品会保护 System Prompt 不被砍
  3. 长文档别硬塞——改用摘要 / RAG / 分段处理
  4. 输出被截断——说"继续"续写,或显式要求控制长度

四、Latency(延迟):你等 AI 的时间

4.1 什么是延迟

延续前面的脉络:

Token 是计量单位 → Context Window 是容量天花板 → Truncation 是超限时的硬砍 → 而 Latency 就是你为这一切付出的时间代价。

一句话定义

Latency = 从你发出请求那一刻起,到拿到(可用的)模型输出为止,经历的时间。

4.2 两个关键延迟指标

指标全称你感受到的是什么
TTFTTime To First Token回车后多久出现第一个字("它有没有卡住?")
E2E LatencyEnd-to-End Latency完整回答全部生成完总共花多久

如果你用过流式输出(字一个个蹦出来),你直觉里其实同时在感受这两件事。

4.3 LLM 延迟的两阶段结构

传统 Web API 的延迟大致是一次性的,但 LLM 是自回归生成的,延迟结构完全不同:

你的请求进来
    │
    ├─ [1] Prefill(预填充)阶段
    │    把你的全部输入 token 序列做一次前向传播
    │    → 计算出 KV Cache,准备好"下一个 token 的预测起点"
    │    → 这部分决定了 TTFT(首字延迟)
    │
    ├─ [2] Decode(解码/生成)阶段
    │    逐个 token 生成:生成一个 → 拼回去 → 再预测下一个 → 循环
    │    → 这部分决定了"后面字蹦多快"(TPS / 吞吐)
    │
    └─ 输出完成

4.4 反直觉的结论

  1. 输入越长 ≠ 仅仅多传点数据,而是直接推高 TTFT

    • 你塞进 context window 的每个 token 都要参与 prefill 的矩阵运算
  2. 输出越长 = 线性拉长 E2E 延迟

    • 每个输出 token 本质上是一次小步推理

4.5 影响延迟的因素

因素推高哪段延迟你能不能控
输入 token 数↑ TTFT✅ 能:精简 prompt、清历史
输出 token 数↑ E2E✅ 能:要求"控制在 X 句"
模型尺寸/参数量两段都↑❌ 选模型时定
并发/负载两段都↑❌ 平台侧控
是否走思维链/工具调用E2E 暴增✅ 能:不是所有问题都需要
网络往返 / 流式 vs 非流式感知延迟✅ 能:开流式

五、Streaming(流式输出):把等待变成陪伴

5.1 什么是流式输出

顺着前面的概念链:

Token → Context Window → Truncation → Latency

Streaming 就是为了解决"Latency 里那段最折磨人的等待"而生的传输方式:模型每生成一个 Token,不等全篇完工,立刻把它推给你。

5.2 非流式 vs 流式对比

特性非流式流式
模型侧行为照常逐 token 生成,但憋着不发照常逐 token 生成,但每出一个就发一个
网络传输等全部生成完,一次性返回用 chunked 分块传输,一边生一边吐
用户感受长时间空白 → 啪一下全出来很快出现第一个字,逐字蹦出来
E2E 总耗时基本相同基本相同
感知延迟很差——等待是"死"的好很多——等待变成"活的"

关键点:流式不加速推理,它只是提前把"已经完成的部分"交给你。

5.3 技术实现

常见的传输实现:

协议/机制备注
SSE (Server-Sent Events)LLM API 最常用:Content-Type: text/event-stream
HTTP chunked transfer底层分块发送
WebSocket双向更灵活,但多数场景用 SSE 更简单

5.4 API 示例对比

关闭流式(stream: false

{
  "choices": [{
    "message": {
      "role": "assistant",
      "content": "人工智能是一门研究如何让机器具备智能行为的学科……"
    }
  }],
  "usage": { "prompt_tokens": 120, "completion_tokens": 85 }
}

开启流式(stream: true

data: {"choices":[{"delta":{"role":"assistant"}}]}
data: {"choices":[{"delta":{"content":"人"}}]}
data: {"choices":[{"delta":{"content":"工"}}]}
data: {"choices":[{"delta":{"content":"智"}}]}
...
data: [DONE]

5.5 为什么要流式

  1. 把 TTFT 变成"可用时间"——第一个结论片断就有决策价值
  2. 超时与失败更可控——只要有 chunk 在推,就知道它还活着
  3. 成本/中止机会——看到模型跑偏,可以中途 abort 连接

5.6 流式的"坑位清单"

现象解法思路
JSON 解析难流式给的是碎片,不能直接 JSON.parse先拼完整字符串再解析
函数调用也是增量arguments 一截一截到按 index 拼接后再解析
截断发生在中途尾部可能断在不完整句子检查 finish_reason
前端闪屏/布局抖动字一蹦出来就触发重排用等宽容器/固定高度

完整概念链总结

Token(计量单位)
  → Context Window(容量上限,用 Token 计)
    → Truncation(塞超了就砍,砍的位置影响结果质量)
      → Latency(塞得越多、生成越长,等待时间越久)
        → Streaming(不改推理速度,但把"已完成 token"提前交付)

实用建议汇总

  1. 成本优化:看 token 数而非字符数,差别很大
  2. 体验优化:开启流式输出,把死等变成活等
  3. 内容管理:别把上下文窗口当仓库,长文档用 RAG 拆分
  4. 调试技巧:遇到 AI"失忆"先查 token 用量,遇到卡顿先查 TTFT

如果你在实际使用中有任何困惑,欢迎在评论区留言讨论!