第 20 篇:大模型推理怎么变成服务(零门槛入门系列)
上一篇:第 19 篇《图片怎么进模型》 | 下一篇:第 21 篇《为什么大家都兼容同一个接口》
一句话导读:大模型推理的服务就是一个不结束的循环——等连接、解析请求、交给引擎、边算边回;"流式"只是把中间结果一片片发出去,而不是等全部算完。
关键词:推理引擎入门、大模型推理、服务、循环、流式、请求、响应
一个"能算"的引擎和一个"别人能用"的服务之间,差的不是算法,而是一层薄壳。这层壳做的事非常单调:有地方一直等着,有人来就问清楚要什么,然后把活交给引擎,再把结果送回去。这一篇就把这层壳拆开。
① 一句话概念
服务 = 一个不结束的循环:接单、算、边算边回。
② 起点:街角的外卖窗口
街角有个外卖窗口。它一天的流程其实只有一个动作在重复。
有人走到窗口前,窗口里的人问一句"要什么、要几份";客人说完,他把单子递进后厨;后厨开始做;做好了,他把东西递出去。然后他回到窗口,等下一个客人。
这个岗位有三个容易被忽略的特点。
第一,它永远不关门。不是接待完这一个就下班,而是回到起点继续等——所以它天生是一个循环。
第二,等待的时候它没有在干活。没人的时候,他不是在后台炒菜,就是站着;有人走到窗口前,才有下一步动作。
第三,也是最容易被做错的一点:出货不必等全部做完。客人要三份,第一份做好了就可以先递出去,不必让人干等到第三份出锅。递出去的只算"半个订单",可对客人来说已经能用了。
窗口本身不做菜,它只负责问清楚、传进去、递出去——做菜是后厨的事。这一层薄壳,就是服务。
③ 伪代码:一个不结束的接单循环
如图 1 所示:左边是一个循环,四步依次是"等待新连接 → 解析请求 → 交给引擎 → 边算边回";走到第四步时分出两条支路——要流式时每生成一个词就发一片,不要流式时一次发完。
图 1服务就是一个循环:走完四步后沿虚线回到开头,所以它"永远不关门";顶部两条支路对比"要流式"与"不要流式",底部两条提醒"谁在算、谁在等要说清"和"请求解析够用就好"。
函数 服务循环(引擎): 当 真: 连接 = 等待新连接() # 没人来就一直等 请求 = 解析请求(连接) # 要什么、要多少、要不要边算边发 若 请求.要流式: 对每个 新词 于 引擎.生成(请求): 发送一片(新词) # 不等全部算完 发送结束标记() 否则: 发送(引擎.生成(请求)) # 代价: 每个请求 O(生成长度) 次发送;串行时一次只服务一个生成流逐行要点
当 真::循环没有退出条件,这是"服务"和"一次性脚本"最大的区别。它只在被要求停下时才停。等待新连接():等的时候它不干正事——至于怎么等(阻塞在这里,还是隔一阵轮询一次),取决于实现;有人连上来,它才往下走。解析请求(连接):只取你能处理的字段,多余的一律忽略(第 21 篇会展开)。否则客户端多加一个字段,你就崩了。若 请求.要流式::这是分岔点。要流式,就边算边发——每一次发送只是整个响应的一小片,但一小片本身也是合法的传输;流结束时还必须补一条明确的结束标记,否则客户端分不清"还在算"和"已经完了"。不要流式,就等全部算完再发一整块。- 代价那一行:串行服务时,一个请求没生成完,后来的请求只能排在后面。这就是第 14 篇"一次服务很多人"要解决的问题。
④ 纸笔实验(必做)
题目:亲手写出 3 条流式响应片段,并检查格式。
规则只有两条:每个事件是一行data: ...,后面紧跟一个空行;流结束时补一条结束标记。(data:后面那对花括号是 JSON 记法——用花括号把"键: 值"包成一条记录,这里只是把算出的那个词装进去,不必深究语法。)
一个参考答案
data: {"词": "你"} data: {"词": "好"} data: [DONE]自查清单(逐条对)
- 每条
data:后面是否都有一个空行?空行才是"这一片到此为止"的边界;两条之间若没有空行,客户端会把它们当成同一条,解析失败。 - 最后一条是不是结束标记?没有它,客户端会一直等下去。
- 数一数:3 条片段 = 3 个
data:行 + 3 个空行。
这一步要体会的事:如果你只看第二条就关掉文件,手里拿的是"半个响应"——而它依然是一段合法的传输。这正是流式能成立的原因:响应不是一次性写完的,它可以写到一半就发出去。
可选 REPL 版
片段=['data: {"词":"你"}','data: {"词":"好"}','data: [DONE]']for一条in片段:print(一条)print()# 空行 = 一个事件的结束运行后你会发现每条消息之间都空了一行——那就是"边界"。
⑤ 进阶锚点
进阶锚点:本篇概念在《30 天手搓推理引擎》Day 20里被真正实现——
20-1 极简 HTTP:socket/bind/select 轮询/20-2 路由与自研最小 JSON/20-3 SSE 流式:响应怎么写一半就发给客户端。
入门版到这里就够了;进阶版会多出:服务端的真实源码位置、一份逐事件带时间戳的流式实测记录(口径见进阶系列与仓库 docs),以及"为什么自己写一个最小解析器就够了"的取舍讨论。
想动手 → 仓库https://gitee.com/pei-xiaoguang/kestrel-llm,从 Day 1 开始。
下篇预告:为什么全世界的模型服务都在兼容同一套接口,以及真正决定成败的那件小事——模板。