十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

LMCache Request Stream SDK 深度解析:用有状态请求流编排 KV Cache 的多阶段推理

LMCache Request Stream SDK 深度解析:用有状态请求流编排 KV Cache 的多阶段推理 LMCache Request Stream SDK 深度解析用有状态请求流编排 KV Cache 的多阶段推理【免费下载链接】LMCacheLMCache: Supercharge Your LLM with the Fastest KV Cache Layer项目地址: https://gitcode.com/GitHub_Trending/lm/LMCache导读LMCacheRequestStream是 LMCache SDK 提供的有状态、单一请求级编排层它把一次完整推理请求prefill → 修改缓存 KV → decode绑定为一个逻辑流让开发者无需手工在多个无状态LMCacheSDKContext调用之间搬运 token 状态。本文以 docs/design/sdk/request.md 为主线结合 lmcache/sdk/request.py、lmcache/sdk/context.py 等源码与 token_dropping 示例系统讲解其状态模型、Suffix 契约、完整公开 API、性能指标字段以及如何用它实现「取 KV → 编辑 KV如 token dropping→ 放回 KV → 继续生成」的实战工作流。读完本文你将掌握 Request Stream 的全部公开接口、内部状态流转语义与端到端接入方法。为什么需要 Request Stream无状态 Context 的痛点按照 context.md 的定位LMCacheSDKContext是无状态的它的retrieve()/store()只按 token id 将 KV以及可选的 query张量移入移出正在运行的 LMCache MP 服务器一次调用结束即遗忘。但对于 token dropping 这类真实需求一次请求会横跨多个推理 passprefill把 prompt 的 KV 写入缓存修改缓存 KV取回 KV、做压缩/裁剪等编辑、再写回decode基于编辑后的 KV 继续生成。这三个 pass 在引擎侧会被编码成不同的请求如果由用户自己维护上一次缓存到哪里了、还有哪些 token 没进缓存极易出错。LMCacheRequestStream正是为消除这份心智负担而生它把属于同一个逻辑请求的所有推理 pass 绑定在一条流上自动管理 token 序列、缓存对齐边界、已生成 token 计数与结束标志。定位关系RequestStream 本身不单独使用LMCacheBatchedStream 会包装多条 RequestStream 用于批量提交请求其底层依赖的LMCacheSDKContext/LMCacheSDKCacheKind定义见 context.md。一段代码看懂核心用法原文档给出的最小示例完整展示了 Request Stream 的三段式生命周期import lmcache.sdk as lmc_sdk kind lmc_sdk.LMCacheSDKCacheKind.KV ctx lmc_sdk.connect(kindkind, url..., http_url..., model_name...) request lmc_sdk.request.create_request( [ctx], post_completion, prompt_token_idssource_tokens # contexts: iterable ) request.generate({max_tokens: 1}) # prefill - offload the prompt KV request.modify_kv(drop_tokens) # drop_tokens: retrieve - edit - store request.generate({max_tokens: 256}) # replay the uncached tail decode三个调用分别对应三个 passgenerate提交推理并触发 KV 卸载modify_kv完成取回-编辑-写回整条链路第二次generate重放未缓存的尾部并继续解码。其中post_completion(prompt_token_ids, sampling_params, cache_salt)是注入的引擎调用函数例如 vLLM 的/v1/completions流式接口每生成一个 token 产出一次TokenEvent(token_id, text)。完整的可运行版本见 token-dropping 示例。状态模型流内每个字段的职责在源码 lmcache/sdk/request.py 中LMCacheRequestStream.__init__从初始 prompt 建立全部内部状态。整条逻辑序列是tokens与_suffix_tokens的组合各字段语义如下字段含义何时变化tokens支撑已存 KV 的 token 序列作为下一次请求的 prompt 提交KV 被update修改时整体替换generate追加生成 tokenupdate替换为新序列_suffix_tokens不在已存 KV 中的 tokenmodify_kv之后留下的非 chunk 对齐尾部由下一次generate消费modify_kv记录generate前置到 prompt 后清空_decoded/_text_parts跨所有generate的累计生成 token 数 / 文本对应decoded_tokens、output_text不受压缩等 KV 操作影响每次generate累加done某次generate产出 token 数 max_tokens视为触发 EOS时为 Trueupdate被调用时重置为 Falsegenerate判定update重置request_stream_id在构造时生成str(uuid.uuid4())用于在批量场景中唯一标识一条流。done 的判定细节见 request.py#L227-L230# produces less than max_tokens -- EOS output_tokens len(gen_tokens) max_tokens sampling_params.get(max_tokens, 1) self.done output_tokens max_tokens当max_tokens未显式提供时默认为 1——这正是 prefill 阶段强制max_tokens1能判定结束的原因。update()被调用后会重置done False因为缓存内容已改变需要重新开始生成流程。Suffix 契约chunk 对齐边界如何被跨越retrieve只返回chunk 对齐的前缀LMCacheSDKContext.retrieve会把 token 数截断到(len(tokens) // chunk_size) * chunk_size见 context.py#L400-L408不足一个 chunk 时直接返回None。因此modify_kv编辑完 KV 后剩余部分sub-chunk 尾部 尚未卸载的 token在缓存里没有对应的 KV必须由流自身携带跨越这次编辑modify_kv在 KV 修改完成后把未缓存尾部tokens[cached_len:]记录进_suffix_tokensgenerate提交请求前把_suffix_tokens连同调用方传入的suffix_tokens前置拼接到tokens随后清空。对应源码在 request.py#L199-L202pending self._suffix_tokens list(suffix_tokens) self._suffix_tokens [] if pending: self.tokens.extend(pending)这样即使缓存只覆盖了 prompt 前 90% 的 token剩余 10% 也不会丢失会在下一次推理时作为 prompt 的一部分被重新提交、重新生成 KV。公开 API 全景构造LMCacheRequestStream(...)与create_request(...)两者等价create_request只是工厂函数见 request.py#L93-L115LMCacheRequestStream( contexts: Iterable[LMCacheSDKContext], post_completion: PostCompletion, prompt_token_ids: Sequence[int], cache_salt: str , )contexts是可迭代对象如[kv_ctx]或[kv_ctx, q_ctx]KV Query 双上下文场景构造时按ctx.kind建立 kind → context 的映射。cache_salt是按用户隔离的盐默认为空字符串参与缓存寻址避免不同用户共享同一前缀的 KV。使用lmcache.sdk.connect(kind...)可同时获得 KV 与 QUERY 两种 kind 的 context分派逻辑见 lmcache/sdk/init.py。generate(sampling_params, suffix_tokens())→StreamPerfMetrics运行一次推理 pass 并把结果追加进流历史request.py#L183-L242先将_suffix_tokens与调用方suffix_tokens合并追加到tokens调用self.post_completion(self.tokens, sampling_params, self.cache_salt)流式取 token遍历TokenEvent统计生成 token、文本与时间间隔若迭代中途抛异常会包装为LMCacheRequestStreamErrorfinally中仍会累计已生成部分返回本次调用的StreamPerfMetrics。modify_kv(fn, timeout30.0, poll_interval0.2)编辑缓存 KV 的高层入口request.py#L309-L339对每个注册的 context 调用retrieve轮询等待缓存就绪从 KV 张量的 token 维tensors[KV].shape[2]得到cached_len把tokens[cached_len:]连同既有_suffix_tokens存入_suffix_tokens调用fn(tensors, tokens[:cached_len])得到编辑后的(new_kv, new_tokens)其中tensors是Mapping[LMCacheSDKCacheKind, torch.Tensor]覆盖该算法用到的每种缓存类型通过update写回新 KV。ModifyFnType的类型签名定义在 context.py#L45-L48ModifyFnType Callable[ [Mapping[LMCacheSDKCacheKind, torch.Tensor], Sequence[int]], tuple[torch.Tensor, Sequence[int]], ]retrieve(kind, timeout30.0, poll_interval0.2)与update(kind, kv, tokens)两者是对 context 同名方法的薄封装request.py#L244-L307retrieve按self.tokens轮询取缓存timeout秒内拿不到即抛LMCacheRequestStreamError返回的 KV 形状为[2, L, T, D]Q 形状为[1, L, T, D]update调用ctx.store(kv, tokens, cache_salt)写回编辑后的 KV然后self.tokens list(tokens)并self.done False若 store 报告该 KV 已缓存去重命中会记录一条 warning 而非报错。访问器属性request_stream_id流的唯一 IDsuffix_tokens待追加到 prompt 的尾部 token 列表decoded_tokens跨所有段累计生成的 token 数output_text跨所有段拼接的生成文本output_tokens当前完整 token 序列含生成部分is_done是否已结束EOS。StreamPerfMetrics单次 generate 的性能报告每次generate返回冻结的StreamPerfMetricsdataclassrequest.py#L35-L55所有时间单位均为秒字段含义duration本次generate()调用耗时秒input_tokens输入 token 数prompt suffixoutput_tokens本次调用生成的 token 数input_tput输入吞吐tokens/soutput_tput生成吞吐tokens/stpot相邻生成 token 之间的时间间隔列表秒首对间隔被计作 TTFTttft首 token 时间秒实现上tpot记录time.perf_counter()的逐 token 差值ttft取第一个间隔、tpot取其余间隔request.py#L240-L241。在批量场景中LMCacheBatchedStream会把每条流的这份指标聚合成统一的Metrics报告见 batch.md。PostCompletion 协议与真实实现PostCompletion是定义引擎调用的Protocolrequest.py#L71-L90def __call__( self, prompt_token_ids: list[int], sampling_params: dict[str, Any], cache_salt: str, ) - Iterable[TokenEvent]: ...TokenEventrequest.py#L58-L69只含两个字段token_id生成 token 的 id与text该 token 的解码文本。仓库中最直接的参考实现是 examples/token_dropping/utils.py#L258-L307 的make_post_completion它用httpx.stream以 SSE 方式 POST vLLM 的/v1/completionsstreamTrue逐行解析data:事件把每个 choice 包装为lmc_request.TokenEvent(token_id..., text...)产出cache_salt非空时还会透传给 vLLM。也就是说接入任意支持流式补全的引擎只需提供一个同样签名的回调即可。端到端实战Token Dropping 工作流examples/token_dropping/random_token_dropping.ipynb 给出了完整可运行的链路核心步骤启动 LMCache server使用共享内存传输时指定--shm-name与--no-l1-use-lazylmcache server \ --l1-size-gb 150 \ --eviction-policy LRU \ --chunk-size 256 \ --port 6555 \ --http-port 8080 \ --shm-name lmcache_kvcache_sdk_e2e \ --no-l1-use-lazy \ --supported-transfer-mode auto启动 vLLM并接入LMCacheMPConnectorkv_connector_extra_config指向 6555 端口注意加--return-tokens-as-token-ids以便逐 token 回调拿到 token id构造 SDK context 与post_completion把多条请求加入LMCacheBatchedStreambatch lmc_sdk.batch.LMCacheBatchedStream() for prompt in prompts: request lmc_sdk.request.create_request( contexts[ctx], post_completionpost_completion, prompt_token_idsprompt, ) batch.add(request) results batch.prefill( sampling_params{max_tokens: 1, temperature: 1.0, ignore_eos: True} ) results.emit() results batch.decode( sampling_params{max_tokens: max_tokens, temperature: 1.0, ignore_eos: True} ) results.emit()清空缓存后用batch.modify(drop_tokens)对每条流的 KV 执行裁剪modify_kv语义再batch.decode对比吞吐。批量层LMCacheBatchedStream的关键行为batch.md 与 lmcache/sdk/batch.pyprefill强制max_tokens1对全部流跑一遍generate并汇报 prefill 指标modify(fn, ...)并发对每条流应用modify_kv只汇报耗时decode跑全部流并汇报 decode 指标add/get_request_stream(stream_id)以request_stream_id为键管理成员底层run_request_streams通过线程池并发调用各流的generateget_perf_metrics(duration, fmt, width, mode, ...)聚合为Metricsmode取prefill或decode支持终端表格emit()与to_dict()两种输出。底层机制chunk 对齐、寻址与传输理解 Request Stream 的边界行为需要知道它的存储语义来自 context.md缓存寻址context 构建IPCCacheServerKey(model_name, world_size1, worker_id0, token_ids, start0, endchunk-aligned, request_id, cache_salt)缓存身份 token-chunk 哈希 model_namekv_rank(worker_id)cache_salt而request_idstore-/retrieve-uuid只标识请求会话不参与缓存身份。传输数据面优先走共享内存SHM否则回退 pickle均由ContiguousTransferWrapper屏蔽差异——SDK 从不分支判断传输方式这也解释了为什么示例中可以透明地依赖 SHM。注册前提模型布局必须已由某个 vLLM 实例通过REGISTER_KV_CACHE注册到服务器SDK 从/config、/status读取chunk_size与kv_cache_layout并解码几何HND 顺序、CPU 侧分配无法仅凭model_name推导。已知限制当前仅支持world_size 1与单一非 hybrid kernel groupQUERY 类型在model##query键下由 vLLM worker 的 Q ring 通过REGISTER_Q_CACHE注册。注意事项与边界行为retrieve/update/modify_kv的timeout默认 30 秒、poll_interval默认 0.2 秒超时抛LMCacheRequestStreamErrorgenerate中途异常也会包装为该错误并携带request_stream_id。update会把流的 token 序列整体替换为new_tokens因此编辑 KV 的算法必须返回与 KV 对应的完整 token 序列且len(tokens)需与kv.shape[2]一致store 侧会校验见 context.py#L478-L486。done依赖sampling_params[max_tokens]的对比判定prefill 阶段max_tokens1是必然产生结束信号的约定实际业务中若想禁止 EOS示例使用了ignore_eosTrue配合。output_tokens返回的是流内部tokens的引用包含 prompt 与生成 token 的完整序列如需纯生成部分请结合decoded_tokens与output_text使用。总而言之LMCacheRequestStream把无状态的 KV 移动原语升级为有状态的请求级编排是 SDK 中实现 KV 编辑类优化token dropping、KV 压缩、rerope 重算等的核心抽象配合LMCacheBatchedStream即可直接支撑离线批处理场景其完整设计可继续阅读 context.md 与 batch.md实战入口见 token_dropping 示例。【免费下载链接】LMCacheLMCache: Supercharge Your LLM with the Fastest KV Cache Layer项目地址: https://gitcode.com/GitHub_Trending/lm/LMCache创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表