大模型流式渲染踩坑记:从"打字机"到不卡的打字机
对接 DeepSeek R1 等推理模型 SSE 流式接口时,前端在渲染性能上踩过的坑。
大模型流式渲染踩坑记:从"打字机"到不卡的打字机
做 AI 安全平台的时候,我负责对接大模型的推理服务——包括 DeepSeek R1 这类会先”深度思考”再回答的模型。前端要做的事看起来很简单:SSE 接流,打字机效果输出。
真做起来,坑全在渲染这一侧。
坑一:token 到得比你渲染得快
SSE 的 chunk 高峰期一秒能到几十上百个。最初的写法是每个 chunk 一到就更新响应式状态,触发 Markdown 全量重新解析 + 重渲染——长回答到后面一帧都跑不满,输入框都开始跟着卡。
三板斧:
- 缓冲区攒批:chunk 先进缓冲区,不直接触发渲染
- rAF 节流:每帧(或每 50ms)把缓冲区一次性刷进状态,肉眼看依然是流畅的打字机,渲染次数从每秒上百降到每秒二十以内
- 增量 Markdown:已经闭合的 Markdown 块(前面的段落、代码块)缓存渲染结果不再动,只重解析最后一个未闭合的块。全量解析变局部解析,长文本不再越写越卡
坑二:思考过程不能和回答混在一起
R1 这类推理模型会先输出一大段思考,再输出正式回答。全部平铺的话,用户要滚过几百行”自言自语”才看到答案。
我们的方案是分离 + 折叠:思考阶段单独一个可折叠区域,流式期间默认展开、带一个呼吸中的”思考中”状态;正式回答开始时思考区自动折叠成一行摘要,随时可以点开回看。界面上”机器在想”和”机器在说”是两件事。
坑三:自动滚动是个体验雷区
打字机输出时视口要跟着内容走,这个”跟随滚动”几乎人人都写错过一版:
- 用户往上翻历史时,新 token 一到又被强行拽回底部——愤怒值 +10
- 判断”是否在底部”用精确等于,高 DPI 屏上永远不相等——跟随失效
正确的做法:只有用户本来就在底部(带几十像素容差)时才跟随;用户主动向上滚动即视为接管,显示一个”回到底部”的悬浮按钮,点了才恢复跟随。
坑四:停止生成不是断开连接就完了
“停止生成”按钮按下去,要做的不止 abort():缓冲区里没刷完的 token 要刷完(否则句子断在半个词上)、思考区状态要落定、消息要标记为”已中断”以便重新生成。这些收尾没做好,停止之后界面就是一副死机相。
结语
大模型前端的本质是用有限的渲染预算去追一个无限快的生产者。网络几乎从来不是瓶颈,渲染才是。攒批、节流、增量——三板斧下去,剩下的都是产品细节。
本文由作者按照 CC BY 4.0 进行授权