LLM服务研究做了大半年,我越来越觉得最拖后腿的不是推理引擎本身,而是找不到一份"能统一认知"的实验数据。前阵子我把两个公开的请求日志丢进同一套调度器里对比测试,发现同一个算法的尾时延差异,竟然比算法带来的优化幅度还大。那种挫败感,凡是认真做过LLM serving研究的人应该都不陌生。
这个矛盾的根源很简单:LLM服务研究和传统系统研究不一样,它的负载高度依赖用户交互模式、提示词长度分布、并发突发特征,而这些东西目前散落在各个数据集里,格式不同、口径不同、脱敏程度也不同。所以当我看到"A dataset hub for LLM serving research"这个方向时,第一反应是"终于有人想把这块基础打牢了"。做LLM serving人,与其每次从零收集日志、清洗、统一格式,不如认真聊一聊:一个合格的数据集中心,究竟应该长什么样,能解决哪些问题,以及自己动手接入时有哪些值得警惕的细节。
这篇文章我会从实际研究者的视角拆开这个题目,不绕弯子,只讲怎么选数据、怎么用数据、怎么理解数据背后的物理含义。
1. 为什么LLM服务研究比传统系统研究更依赖"标准数据集"
1.1 论文复现的噩梦:两次实验的结果对不上
传统Web服务做性能评测,负载往往可以用TPS、QPS这类单一指标概括,请求之间相对独立。但LLM服务不一样,请求进来的瞬时并发是一回事,每个请求在GPU上实际占用的显存和计算量又是另一回事。
我见过很多团队复现别人的调度论文,把作者给的代码跑起来之后,发现SLO达标率差出一大截。多数情况下问题不在算法实现,而在数据集:训练数据里请求的输入长度是512还是4096,输出上限是128还是2048,直接决定了KV cache的占用和prefill阶段的计算量。哪怕两边用的都是同一个模型,只要trace里序列长度的分布形态不同,调度器对抢占和批量的决策就会完全不同。
这正是数据集中心的核心价值:让所有研究者拿到的是同一份具有明确统计特征的负载。否则你做的优化和隔壁团队做的优化根本没有可比的基础。
1.2 从ShareGPT到LMSYS Arena:公开数据集的碎片化现状
目前做LLM serving研究的人,手上常用的公开数据其实就那几类。一类是ShareGPT爬下来的用户-ChatGPT交互日志,特点是会话轮次多、用户输入长、覆盖日常闲聊;另一类是LMSYS Chatbot Arena的匿名对话数据,包含多种模型回答,更像真实世界的多模型对比场景;还有从Azure公共trace、OpenAI API日志衍生出来的请求记录,偏向生产环境的稳态负载。
这些数据在各自的论文里都挺好用,但放在同一个框架里横向比较就很痛苦。ShareGPT的请求是"会话级"的,LMSYS则是"单轮+多回答"结构,Azure类的trace往往只保留元信息,不保留具体文本内容。字段命名、时间戳单位、token计数方式全都不一样。想做个调度实验,得先花两周时间去统一这些格式,还得提心吊胆,怕自己转换逻辑和其他人对不上。
1.3 一份数据中心能带来的"消失变量"
所以我理想中的dataset hub,最核心的作用其实是消灭变量。它不只是把数据堆在一个网页上,而是把请求到达时间、输入长度、输出长度、会话上下文、模型代号、服务阶段、延迟分布边界这些字段全部标准化,并提供配套的加载、过滤、回放工具。
当大家用同一份数据、同一套工具、同一个压力发生器跑实验的时候,研究讨论才能从"你用的数据集是不是有问题"回到"你的算法到底做得怎么样"上。这也是系统研究领域走了很多年的路:从自建负载到标准化trace,再到公开benchmark,每一步都是在降低复现成本。LLM serving现在正站在这个拐点上。
2. 一个真正称得上门类齐全的数据集中心,应该具备哪些设计
2.1 统一的Trace格式:一秒钟能看懂的数据结构
先不谈功能,最基础的事情是把格式统一。不同来源的trace不能要求作者改成同一种schema,但hub可以提供一套canonical格式,再给出从ShareGPT、LMSYS、Azure log等格式转换到canonical的脚本。这里我比较推荐参考ETS(事件时间序列)或者Artillery trace的字段习惯,至少应该包含以下几列:
| 字段 | 含义 | 说明 |
|---|---|---|
| request_id | 请求唯一标识 | 关联同一会话内的多次调用 |
| arrival_time | 请求到达时间(微秒) | 用于模拟在线负载到达过程 |
| input_tokens | 输入token数 | 必须标注tokenizer来源 |
| output_tokens | 采样输出token数 | 建议区分max_tokens与实际生成量 |
| model_family | 模型类别 | 例如llama-70b、gpt-3.5等 |
| session_id | 会话ID | 无则置空,用于多轮关联 |
| priority | 优先级标签 | 生产环境可用 |
这套格式的好处是足够轻,足以覆盖调度、容量规划、扩缩容研究的大部分需要。字段太复杂反而没人用,字段太简单又不足以重建请求的burst模式。
2.2 按场景切分的数据目录:不是所有研究都需要全量真实trace
真实trace数据量很大,动不动几十万条记录,但很多研究场景其实只需要其中一部分。一个好的hub应该提供按场景裁剪的"数据包",比如:
- 在线交互包:侧重到达间隔和会话长度,采样频率高,适合做请求调度和放置策略。
- 批处理包:含有大量离线跑批请求,输入长且输出分布长尾,适合做连续批处理、抢占式调度的研究。
- 长上下文压力包:专门挑出输入长度超过32k的请求,用来压测KV cache管理和状态复用。
- 混合负载包:把在线突发和批处理任务混在一起,贴近生产环境。
每个数据包需要附一份统计报告:长度分布分位数、到达率变化曲线、请求并发度、会话轮次分布等。这些数字本身就能帮研究者判断自己的实验用哪个包更合适,不需要自己重新画图。
2.3 与模拟器、回放工具的即插即用
数据集只有能跑起来,才谈得上价值。hub除了提供数据,还需要提供与常见模拟器的适配器:比如和vLLM的LLMPredictor对接,把trace里的到达时间转换成调度事件;和SGLang的RadixAttention测试框架对接,把会话前缀提取出来做前缀复用实验。
这一点我建议hub团队直接和开源推理框架维护者合作。适配器不用多,能覆盖两三个主流引擎就行。研究者拿到适配器,本地用一个脚本就能把trace跑起来,这个hub才算真正降低门槛。否则只能下载一个JSON文件,那跟从Google Drive上下载数据没本质区别。
2.4 数据质量门禁:每个数据集都要有"体检报告"
数据不是"能下载"就完了,质量决定实验可信度。我的想法是,hub应该对每个数据集做自动质量检查并公开指标,包括:
- 字段完整性率:有没有大量空值,特别是时间戳和token数。
- tokenizer一致性:同一个trace里是不是混用了不同tokenizer,导致token数不可比。
- 时间戳单调性:是否存在请求在逻辑上到达时间晚于开始服务时间。
- 隐私脱敏等级:明确标明是否包含用户原始文本,脱敏到什么程度。
这些指标应该挂在数据集页面上,像营养表一样一目了然。研究者下载之前就能判断,这份数据适不适合自己的实验假设。
3. 不同研究类型应该怎么挑数据:从场景反推数据需求
3.1 在线聊天系统:最看重到达过程和会话特征
如果你研究的是连续批处理(continuous batching)或者请求调度,数据选择的核心不是平均输入长度,而是到达过程。真实生产系统的请求到达不是均匀泊松流,而是有明显高峰和低谷的"双峰曲线",还可能伴随秒级的突发。这个突发对GPU上的排队效应影响极大,如果测试数据里没有突发,你做的抢占策略在线上根本没机会被触发。
在线场景还需要关注会话轮次。多轮对话里,后续请求会携带前面的历史,随着轮数增加,KV cache的复用机会变大。一个好的在线数据包,应该把"同一session下的请求序列"保留完整,而不是把每条请求当成独立样本随机打乱。
举个例子,ShareGPT天然是会话级的,轮次信息完整;但如果你用LMSYS Chatbot Arena数据做在线实验,就得注意它是单轮为主,会话性很弱。用这份数据测出来的前缀复用率会明显低于真实聊天产品,部署上容易低估KV cache的放大效应。
3.2 批处理推理:把序列长度分布放到第一位
离线批处理(比如数据分析、批量摘要、代码生成)和在线聊天不同,请求不是"持续到达",而是一批任务同时提交。这时最有区分度的特征是序列长度分布,尤其是输入长度。
真实批处理任务里,输入长度往往不是正态分布,而是有多个峰值:一小部分长文档请求会拖着几十K的输入,其他请求只有几百token。这种分布会让prefill计算量呈长尾形态,直接影响调度器的"抢占哪些请求"决策。如果你只用平均长度均匀分布的数据,测试结果会永远偏向对小请求友好的算法。
批处理数据包里建议额外提供job粒度信息:一个job包含多少个request、job内部请求的并发度。很多研究是任务级调度,只看单请求trace是建不起模型的。
3.3 评估与基准测试:不要为了公平丢掉多样性
当你在做不同推理框架之间的优劣对比(vLLM vs. SGLang vs. TRT-LLM),需要的不是复杂trace,而是可复现、多样性高的基准集。这种用途下,hub应该提供任务分类标签:简单问答、代码生成、长上下文检索、多轮推理等,让研究者可以对不同任务混合输入,避免"小范围过拟合"。
我自己踩过的一个坑是:长时间只用一两个固定prompt模板压测,结果优化了半天,全是因为模板里的重复前缀让模型命中了cache。换成多样性更高的数据集之后,优化效果立刻缩水。所以基准场景,宁可多带一点"脏数据",也要防止实验被cache命中带偏。
3.4 调度器研究:既需要真实数据,也需要合成压力
调度器的研究者往往对数据有"既要真实又要可控"的矛盾需求。真实数据有说服力,但分布是固定的,没法测试极端情况。这个矛盾的解法是:hub提供"基于真实统计特征的合成数据生成器"。
我可以基于真实trace拟合一个分布模型,然后生成任意规模的请求流,把峰值放大2倍、把长序列占比提到20%、让会话轮数变深。这种数据不用下载,直接用hub提供的SDK在本地生成即可。合成数据在论文里作为补充实验很常见,关键是它必须基于真实统计特征,否则就成了拍脑袋造数据。
4. 自己动手接入数据集的完整链路:从原始日志到回放实验
4.1 收集与脱敏:先把隐私问题解决掉
假设你想贡献一份自己的生产环境trace给hub,第一步是收集。线上网关一般都会有access log,但日志里往往带着完整的prompt内容和回答文本,直接公开是不合适的。
脱敏策略建议分两档:第一档仅保留元数据,即时间戳、token数、模型ID、会话ID,去掉全部文本;第二档保留文本但做内容重写,比如用通用占位符替代用户输入,保留长度分布和语义类别,但不保留具体内容。文本保留会让数据集对对齐研究和可解释性研究更有用,但要付出隐私审查成本。对LLM serving系统研究来说,绝大多数场景第一档就够用了。
脱敏完成后,还要做匿名化:把用户ID、会话ID映射成随机串,避免通过ID串联出个人信息。这一步不是可选项,是上线的底线。
4.2 特征提取:四张表理解一批trace
拿到原始trace之后,我习惯先画四张表,判断这份数据要不要作为实验负载:
- 到达时间间隔分布:从delta-request-time中算,看是泊松、自回归还是有突发段。
- 输入token长度分布:分位数明确记录下来,特别是P50、P95、P99。
- 输出token长度分布:检查是否符合解码阶段的几何特征,和max_tokens设置的关系是什么。
- 并发度曲线:以秒为粒度统计正在处理的请求数,看最大并发和平均并发差多少倍。
如果第四张表显示最大并发是平均并发的50倍以上,这份trace会特别适合做弹性伸缩和抢占实验;如果分布很平、并发稳定,那它更适合做稳态吞吐测试。特征表是你决定数据集用途的依据,而不是先选数据集再去看它合适不合适。
4.3 采样与增强:真实trace不够用怎么办
真实trace往往样本量不足,特别是光鲜的"高峰数据"。常用的处理办法是分形插值或过采样,对到达过程做加速,比如把6小时的trace压缩成1小时,并同时放大请求率。这么做不会改变请求内部的长度分布,但因为过高的到达率会把系统推向过载,跑出来的延迟指标会和真实稳态不一样。
所以增强必须区分目标:如果是测吞吐上限,可以用时间压缩;如果是测SLO达标率,最好保持原始时间间隔,只复制多天trace并按合适的方式首尾拼接。拼接时注意不要在拼接点制造虚假的突发,比如午夜流量本身低,硬拼两个高峰会把burst制造得格外吓人。
合成增强工具最好和前面的特征提取联动。用生成器拟合出到达过程,生成一批和真实trace在KL散度上接近的合成trace,再叠加极端场景,这样既能保留真实感,又能制造压力。
4.4 版本管理与数据漂移:数据集也需要Git
数据集会过期,这是很多人没注意到的。LLM服务的负载分布不是静止的,随着模型性能变化,用户输入长度可能变长,输出偏好可能改变,甚至到达模式也会随产品流量迁移而改变。所以hub必须提供版本管理:每个数据集有版本号、发布日期、统计摘要的变更日志。
研究者使用数据时,论文里必须写清楚用的是哪一版。我看到过有些论文引用的LMSYS数据是旧版的,和新版统计特征差异很大,导致复现时结果对不上。dataset hub不仅是在下载时提供版本,还要在API层面强制要求版本参数,保证一个版本发布后不可变,新实验必须显式选择版本。这个机制听着小,实际能救很多paper的复现危机。
5. 构建和实验过程中实际踩过的坑:数据层面的"拦截网"
5.1 tokenizer导致的token计数不一致
第一个坑是token计数。同一段文本用llama tokenizer和gpt-2 tokenizer算出的token数可能相差20%以上。如果trace里记录了input_tokens,但没标tokenizer来源,你在做KV cache占用估算时就会错得离谱。
处理办法是:hub提供tokenizer-agnostic的"标准token化服务",所有数据集入库前统一用我们约定的tokenizer重算一遍,并在元数据里写清楚。对于原trace已经带token数但无法确认tokenizer的,宁可标注为"近似值"也不要让它冒充精确值。
5.2 时间和延迟的边界:请求到达不等于处理开始
第二个坑是时间戳的语义。很多trace里的时间戳是"请求进入队列的时间",而模拟器默认处理的是"请求进入系统的时间",这两者之间的差可能就是排队延迟。如果你直接用到达时间去做仿真,等于默认请求一进来就该启动处理,会把排队效应低估。
实际做法是把trace拆成三段时间戳:客户端发起时间、网关节点接收时间、调度器放入就绪队列时间。如果原始日志没有这么细,至少要把"到达系统"和"开始被调度"区分开,否则任何调度算法在你手里可能看起来都比真实系统好很多。这不是算法的问题,是时钟边界选错了。
5.3 前缀与KV cache复用带来的虚假提升
第三个坑,也是我认为最隐蔽的:当trace里同一session的请求被连续回放,前缀缓存命中率会高于随机线上请求。如果实验中为了"公平"把所有请求打乱,又可能完全失去前缀关联,低估真实聊天场景的cache效率。
正确做法是把trace里的session分组保留,提供两种模式:会话保留模式和打乱模式,并明确说明两种模式分别适合什么实验。调度实验尽量用会话保留模式,容量估算实验可以两种都跑,对比结果区间。如果你发现自己的优化在打乱模式下失效,而在保留模式下有效,那大概率你只是在优化cache命中,而不是优化调度本身。
5.4 长尾输出请求的"隐形成本"
最后一个坑和max_tokens有关。在线服务里用户可能设置max_tokens为2048,实际生成的输出平均只有300 token。但如果trace把这个上限和实际输出混在一起,压力测试里你会给每个请求预留2048的显存空间,显存占用被严重高估。
因此hub字段需要拆分max_output_tokens和actual_output_tokens。对容量规划研究,max决定资源配置,actual决定计算量;对调度研究,actual才是有效负载。不同的实验目标要用不同的字段,不能图省事只取一个。
6. 现阶段能做的三件具体事:不用等hub上线
6.1 先把自己手里的日志整理成canonical格式
即使你目前访问不了理想的dataset hub,也能立刻开始做标准化。挑最近一周的线上日志,把关键字段提取做成Parquet文件,用我前文说的四张特征表做体检,然后存到团队内部存储。
这个动作本身就是在造一个迷你hub。之后的实验全部从这份干净数据里取,而不是临时去翻原始网关日志。等正式hub出来了,直接把这份数据上传,成本极低。
6.2 用现有公开数据集先跑通一套回放流程
你可以用自己的节奏试一试:下载一份LMSYS数据,转换成canonical格式,写一个简单的脚本按到达时间排序,在vLLM里禁用连续批处理,再启用连续批处理,对比两轮的吞吐和时延。不需要特别精细,这组对比能让你立刻理解trace到达模式和连续批处理之间的耦合关系。
在这种实验里,你会发现"同样的trace,关闭连续批处理之后,P99延迟暴涨"是正常的,这不代表优化方向的正确与否,而是数据集的突发结构在起作用。先建立这个直觉,再做高级实验,你对数据集中心价值会有更具体的认知。
6.3 和hub建设者提出你的真实需求
最后一点,如果你真的认可dataset hub这个方向,不妨直接在社区或者GitHub上提出需求。学者和工程师最需要的不是"又一份公开数据",而是"能回答特定研究问题的,经过标注和验证的数据集"。你的需求本身就是在帮助hub定义质量的边界。
比如你需要session级trace,hub就会考虑把会话重组放在入库流程的前端;你需要字段级时间戳,hub就会要求贡献者提交更细的日志规范。一个以研究用途为中心的hub,最终形态应该是由使用者共同塑造的生态,而不是单方面的文件下载站。
我在实际参与几次实验对比之后的体会是:数据标准化带来的收益不是立竿见影的,但越到后面越显现出价值。真正跑过上百次仿真之后,你会发现自己最常回看的不是算法曲线,而是当初那份trace的统计描述和版本号。一个良性发展的dataset hub,会让每个做LLM serving研究的人少花三成时间在"数据对齐"上,而把这部分时间真正还给算法设计和实验分析。如果你手上正好有一批生产环境请求日志,不妨现在就开始整理成标准格式,未来的你和同行都会感谢今天这个决定。