☰
AI基础设施与Agent开发技能树:vLLM/SGLang/Torch实战指南
2026/9/28 14:33:03 网站建设 项目流程

1. 为什么我把这条技能线拆成"infra底座"和"agent上层"两个栈

这几年做AI工程,我最大的体会是:模型能力已经不是唯一瓶颈,真正决定项目落地质量的,是底下那层AI infra,以及在上面跑起来的agent系统。vLLM、SGLang、Torch这些词,说起来每个都认识,但把它们串成一条能实际工作的技能链,需要反复踩坑才能做到。最近我把自己积累的vllm/sglang/torch技能和agent开发经验做了一次系统性整理,这篇文章就是这次整理的详细记录,适合两类人:一是算法工程师想往工程化方向走,二是agent应用开发者发现自己天天被推理引擎卡脖子。

1.1 先分清要解决的问题层次

很多人把"AI infra"和"agent"混在一起学,结果两边都没学透。我的处理方式是先把问题分成两层:infra层解决的是吞吐和延迟问题,比如显存够不够、并发高不高、量化后精度还能不能接受;agent层解决的是状态与决策问题,比如多轮对话的上下文怎么管理、工具调用失败怎么恢复、长期记忆怎么存取。

举个例子你就明白:推理引擎像发电站,agent像智能家居控制器。发电站输出的电压不稳定,智能家居再聪明也是废铁;反过来,发电站稳定但控制器逻辑混乱,家里一样会短路。所以我的技能整理从来都是两个栈并行,不搞偏科。

1.2 我实际维护的技能清单长什么样

下面这张表是我在本地笔记里长期更新的总纲,每次学完新东西都会回来改一遍:

技能域核心工具关键知识点最近踩过的坑
推理服务vLLM / SGLang / TensorRT-LLMcontinuous batching、prefix cache、量化、分布式vllm新版本性能反而下降,量化模型和镜像版本不兼容
模型训练框架PyTorch / CUDA数据加载、设备适配、版本矩阵torch版本不匹配,Jetson上直接pip install失败
容器与部署Docker / K8s镜像选型、GPU驱动、动态挂载官方镜像tag和模型架构不匹配,加载embedding模型报错
Agent框架LangGraph / 自研循环状态图、工具调用、重试恢复agent execution terminated due to error,定位半天
Agent记忆与安全向量库 / AMemGuard 这类防御方案memory poisoning、prompt injection、权限边界记忆内容被污染后,整个agent行为开始漂移

这张表最大的价值不是"列出工具",而是每条都对应一个具体问题。比如看到"vllm新版本性能下降",我不会只看release note,而是先翻自己的排错日志,确认是attention backend切换还是torch版本变化引起的。这样学习才不是漫无目的的收集。

1.3 这套体系适合谁

如果你是刚从模型训练转向推理服务的算法工程师,这套技能树可以帮你快速定位"该补什么";如果你是做agent应用开发的,这套体系能让你明白,智能体跑不稳,有时候不是你的逻辑写得差,而是vllm的调度参数没调对。接下来我把每条线都拆开讲,里面包含具体命令、思考过程和真实踩坑记录,不是泛泛而谈的科普。

2. vLLM:从"能跑"到"敢上生产"的部署与调优记录

vLLM现在基本是大模型推理服务的事实标准。它到底解决了什么问题?一句话:传统推理框架在显存里存KV Cache时是连续分配的,并发一高,显存碎片多到离谱;vLLM引入了PagedAttention,把KV Cache切成固定大小的块,有点像操作系统里的分页机制,按需分配。配合continuous batching(连续批处理),一个请求生成完立即腾出位置给新请求,不需要等整批结束。这两个机制叠加,吞吐量能比naive推理高一个数量级。但"能跑"和"敢上生产"之间,隔着一整套部署经验。

2.1 部署层面最容易翻车的三个点

第一是镜像版本。Docker Hub上的vllm/vllm-openai镜像,每个tag对应不同release版本。我见过不少人拿一个很老的镜像去加载新出的embedding模型,启动直接报错,因为老版本根本没有--task embedding这个参数。如果你要部署的是qwen3-embedding这类模型,务必选支持embedding task的新版镜像,启动命令大致是这样:

docker run --gpus all --ipc=host \ -v /data/models:/models \ -p 8000:8000 \ vllm/vllm-openai:latest \ --model /models/qwen3-embedding-0.6b \ --task embedding \ --max-model-len 8192

注意--task embedding必须显式指定,否则vLLM会把embedding模型当成生成模型来加载,结果就是输出一堆莫名其妙的文本。

第二是量化模型。很多人拿q8_0量化版的大模型往vLLM里塞,比如qwen3.8-27b的q8_0版本,如果镜像版本太老,经常加载到一半崩掉。原因很简单:GGUF/量化格式的加载后端是慢慢成熟的,老镜像根本没有对应实现。我的建议是:优先用官方release note里明确支持的"模型格式+镜像版本"组合,不要想当然。

第三是部署DeepSeek这类带reasoning的模型。直接vllm serve虽然能跑,但如果你不处理chat template,输出的推理链路格式会很乱。我在部署DeepSeek-R1系列时,会额外确认模板里的reasoning_content字段有没有被正确识别,必要时写一个自定义template再挂进去。

2.2 EngineCore与Scheduler、Executor的交互流程

vLLM升级到0.6之后,代码结构发生了一次大变化,新引入的EngineCore概念让很多老玩家也犯迷糊。我理解的整个交互是这样的:

while engine.has_requests(): seq_groups = scheduler.schedule() # 调度器决定本轮跑哪些请求 outputs = executor.execute_model(seq_groups) # 执行器真正跑模型前向 scheduler.update(outputs) # 根据输出更新状态

这里有几个角色要分清。Scheduler是"交通警察",它维护所有sequence的状态,决定哪些请求继续跑、哪些先抢占、哪些已经结束。Executor是"司机",负责把Scheduler给出的sequence group真正送进GPU跑前向。EngineCore则是把引擎和HTTP Server解耦的产物,让推理引擎可以被嵌入到更复杂的服务架构里,而不是必须绑定OpenAI风格的API接口。

我看源码的时候,第一件事就是顺着LLMEngine的入口找到scheduler和executor模块,而不是一上来读api_server。因为你如果先读HTTP层,很容易被请求解析的细节带偏,错过真正的核心逻辑。

2.3 Scheduler的调度逻辑:为什么并发上不去

很多人在vLLM上调了半天并发,发现吞吐就是上不去,这时候需要回到Scheduler的逻辑上去想。vLLM的Scheduler每轮会做三个决策:继续已有sequence、暂停某些sequence、为新请求分配资源。核心限制有两个:max_num_seqs(每轮最多处理的序列数)和max_model_len(单序列最大长度)。你以为把max_num_seqs调大就能提升吞吐,但显存是有限的,KV Cache分配不够时,Scheduler只能做preemption(抢占)。

抢占有交换到CPU和重新计算两种策略。交换到CPU会引入显存-CPU拷贝开销,重新计算则浪费算力。两种都不好,所以更实际的做法是控制并发和显存利用率的平衡:先跑vllm benchmark测出当前硬件的KV Cache上限,再倒推合理的max_num_seqs和max-model-len,而不是拍脑袋填参数。

2.4 "新版本性能下降"是怎么排查的

热度很高的一个问题就是vllm新版本性能下降。我真实遇到过:从0.4升到0.6,同样负载下tokens/s反而降了。第一反应不是骂开发组,而是先做四件事:查torch和CUDA版本是否和镜像内部构建一致;用benchmark脚本对比新旧版本;确认flash-attention backend是否真的开启;看看新版本默认配置有没有变化,比如prefix caching默认开启,如果你的请求前缀复用率极低,它反而会引入额外匹配开销。

排查完之后我发现,80%的情况不是vLLM本身变慢,而是环境变了。比如升级后自动切换到了新的attention kernel,你的GPU架构不在优化名单里,性能自然回落。我的习惯是升级前先保存"基线benchmark结果",没有基线就谈不上排查。

2.5 硬件适配:从L20到MI50再到Windows

生产环境里你没法挑显卡。我在L20上部署过minimax-h3,印象最深的是L20的算力规格比较特殊,需要CUDA 12.1以上的驱动环境,否则vLLM起不来。L20显存够但计算卡偏推理优化,部署时要适当降低--gpu-memory-utilization,给未来动态batch留点缓冲。

MI50这种AMD老卡也有不少人问。vLLM对ROCm平台的支持是存在的,但你要接受一个现实:ROCm版本的flash-attention等算子不一定齐全,性能可能比同级别N卡打折扣。部署时注意设置HIP_VISIBLE_DEVICES而不是CUDA_VISIBLE_DEVICES,这是新手最容易漏的。

至于Windows,vLLM官方支持一直很弱,你非得在Windows上跑,我建议用WSL2 + Docker,而不是直接pip装,否则编译过程会消耗掉你一整天。这套经验同样适用于给你的"AI infra技能"做环境隔离。

3. sglang与vllm:为什么我会同时保留两套引擎

很多人问sglang和vllm到底怎么选,我给出的答案可能有点反直觉:我两套都在用,而且不觉得重复。vLLM更像一个通用推理服务器,稳定、生态大、接口OpenAI兼容,适合做统一API入口;SGLang则更偏"编译器",它把结构化生成、函数调用、多轮前缀复用直接设计进引擎里,适合玩法比较重的agent场景。

3.1 最核心的区别:RadixAttention

SGLang最让我心动的是RadixAttention。简单说,它把KV Cache做成了一棵树,节点可以共享公共前缀。举个例子,你的agent每次请求都带一个很长的system prompt,如果100个并发请求里90个system prompt一样,SGLang可以只存一份公共前缀,后面的分支动态挂到树上。vLLM也有prefix cache,但SGLang的树状复用更激进,分叉之后如果某个分支后面又能对上,还能继续共享。这意味着在"超长system prompt + 多工具定义 + 多轮对话"这类场景里,SGLang的显存效率和TTFT都能比vLLM明显好一截。

3.2 从源码解析角度看SGLang该读哪三个模块

我读SGLang源码的经验,不建议从头到尾通读,按下面顺序走效率最高:

  • parser/grammar模块:SGLang的前端语言支持gen、select这类结构化指令,看这个模块你能理解"结构化生成"是怎么解析成约束的。
  • scheduler模块:重点看Radix Cache的操作,理解公共前缀的查找、命中、扩展是怎么实现的。
  • router模块:分布式部署时请求怎么路由到不同worker,这里讲得很清楚。

读scheduler时你会发现,SGLang的调度和vLLM的调度思路完全不同。vLLM更关注通用批处理,SGLang更关注如何把请求的结构化特性利用起来。理解了这点,你就不会在两个框架之间反复横跳了。

3.3 实际选型表

我自己在项目里的选择大概是这样:

场景推荐引擎原因
统一对外提供OpenAI风格APIvLLM生态成熟、兼容性好、问题排查资料最多
agent高频调用、长system promptSGLangRadix前缀复用收益大
需要严格结构化JSON输出SGLang结构化生成支持更自然
大量流式对话、简单chatvLLM调度成熟稳定,社区案例多
实验新模型、快速验证两者都试用同一份benchmark脚本跑分再决定

还有个经验:sglang和vllm的benchmark结果千万不要只看官方数字,因为负载特征差很多。我见过一个场景,官方benchmark里SGLang遥遥领先,但我们的业务请求全是短query,结果vLLM反而更稳。选型这种事,最终要落在自己的业务样本上。

4. torch环境:从pip install到Jetson交叉编译的整段记忆

Torch看起来不算AI infra里的重型装备,但它的安装问题能卡住一整条技术链。尤其是当你同时训模型、部署vLLM、跑机器人强化学习时,torch的版本变成了一个全局约束,牵一发动全身。

4.1 安装torch时的版本陷阱

先说说那个经典报错:could not find a version that satisfies the requirement torch。我见过太多人栽在这上面,包括我自己。这个问题的本质是:pip在指定的源和Python版本范围内,找不到满足条件的wheel。常见原因有四个:Python版本太新,比如3.12在pytorch早期版本根本没有对应的wheel;指定的源不是官方源;CUDA版本不在该torch版本的构建列表里;拼写错误或者包名不对。

如果你需要装老组合,比如torch==1.8.2配torchvision==0.9.2,就一定要用官方历史索引,基本格式是:

pip3 install torch==1.8.2 torchvision==0.9.2 torchaudio==0.8.2 \ --extra-index-url https://download.pytorch.org/whl/cu111

注意torch 1.8.2那个时期只有cu101/cu111等版本,没有cu118,你硬指cu118就会报找不到版本。先确认你显卡驱动的CUDA版本,再反查该torch版本支持哪些CUDA构建,最后再装。

另外我经常看到有人的代码长这样:from datasets import dataset,这是错的,正确是from datasets import Dataset(大写D)。这种拼写错误会和torch安装问题混在一起,让人误以为环境有问题,其实只是Python代码缺个大小写。排查时先看报错发生在import阶段还是运行阶段,能省很多时间。

4.2 Jetson上配置torch:不能当普通Linux处理

如果你要在Jetson系列设备上跑模型,直接在设备上执行常见的pip install命令,大概率会失败或者慢到怀疑人生。因为Jetson是ARM架构,很多torch wheel只有x86版本。正确姿势是先确认JetPack版本,然后去NVIDIA官方论坛或预编译wheel仓库找对应版本的torch和torchvision。

我踩过的坑是:装完torch之后忘了检查它是否用了CUDA支持。Jetson上的torch有的编译成CPU-only,跑模型跟蜗牛一样。验证方法很简单:

import torch print(torch.cuda.is_available())

如果不是True,换带CUDA的预编译包再装。这一步看起来不起眼,但直接影响后面所有推理和训练任务。

4.3 mujoco与机械狗:torch只是链条里的一环

有热搜词提到mujoco和torch机械狗,恰好这块我也整理过。用PyTorch训练机械狗策略时,torch只负责神经网络部分,MuJoCo负责物理仿真,二者通过gym接口衔接。真正坑人的不是torch,而是MuJoCo库本身:mujoco-py需要编译Cython扩展,经常因为系统缺依赖失败;MuJoCo新版的渲染依赖还要额外装egl或osmesa。

训练循环里和torch相关的优化点倒是值得记一下:pin_memory=True配合non_blocking=True可以加速数据从CPU到GPU的搬运;torch.set_num_threads(8)在仿真数据生成密集时控制CPU线程数,避免和MuJoCo抢核心。这些细节能让机械狗训练数据管线稳定很多。

4.4 环境隔离是我的底线

我现在的原则是:每个项目一个虚拟环境,依赖全部用锁文件固定。torch这种包尤其敏感——你很难记住哪个项目用的是cu118、哪个是cu111、哪个是CPU版。我常用的是pip-tools,写一个requirements.in,然后编译出requirements.txt。lock出来的文件不仅能复现环境,还能在换机器时快速恢复。很多人觉得麻烦,但等你被"新版本性能下降"和"torch版本不满足"轮番折磨之后,就会明白环境工程就是AI infra的一部分。

5. Agent开发:框架、编排与基础设施缺一不可

说完了infra底座,再讲上层agent。我的观点很直接:agent开发入门其实门槛不高,但真正的难点在于可靠性。而可靠性恰恰分布在框架、编排、记忆、安全四个方向。

5.1 一条比较务实的agent开发学习路线

最快捷的入门路径,是先理解agent的本质。别一开始就上LangGraph、AutoGen这种重型框架,先自己写一个最简循环:

messages = [system_prompt] while not done: response = llm.chat(messages, tools=tools) if response.tool_calls: for call in response.tool_calls: result = exec_tool(call) messages.append(tool_message(call.id, result)) else: done = True final_answer = response.content

这个循环就是agent的全部骨架:模型决定要不要调用工具、执行工具、把结果放回上下文、继续推理,直到结束。吴恩达的agent教程很适合建立这个概念框架,但如果你想真正掌握,一定要自己手写一遍循环,再去看框架怎么把它扩展成状态机。

之后的学习路线我建议是:先补function calling的细节,再接触编排框架,然后做记忆系统,最后补安全与权限控制。不要反过来。很多人一上来学多智能体编排,结果连单agent的工具调用失败都没处理好,项目自然没法落地上线。

5.2 skill、harness和agent到底有什么区别

这个问题在agent社区问得特别多,关键是没有统一术语。我的理解是分层看:

  • skill是原子能力。它可以是"读取PDF"、"执行SQL"、"生成图表"这类的可复用能力。甚至可以是一个小的子agent,但对外只暴露清晰的输入输出。
  • harness是承载agent循环的运行时。它负责调LLM、分发工具、处理异常、管理上下文窗口,是"操作系统"那一层。
  • agent是策略本身。它决定什么情况下调用哪个skill,什么时候停止,如何拆解复杂任务。

用游戏类比:harness是游戏引擎,skill是技能按键,agent是玩家策略。很多所谓的"agent框架",本质是提供harness + 编排图,而真正值钱的业务逻辑还是agent策略。

5.3 agent记忆:短期、长期和安全

记忆是agent区别于普通对话系统的重要能力。短期记忆就是当前上下文里的消息序列,简单直接;长期记忆通常落在一个外部存储里,比如向量库或数据库,关键是把对话摘要、用户偏好、任务状态结构化地写进去。

这里特别要提醒:很多文章把vLLM里的KV Cache也叫"记忆",这是完全不同的概念。KV Cache是推理引擎的临时状态,agent记忆是业务层面的持久化状态。你可以在vLLM的上下文中保存200k token,但重启服务后什么都没了;而长期记忆要的是"重启后还在"。

记忆安全是我最近关注的重点。你让agent把对话摘要写进记忆库,原始数据里可能混有恶意指令或不可信信息。我看到过一种叫AMemGuard的主动防御思路,专门针对LLM-based agent的记忆投毒:在记忆写入前做内容验证和权限检查,避免攻击者污染记忆库后让agent在后续对话中持续产生危险行为。工程上,我建议至少把外部工具返回的内容和系统指令分隔开,不要无脑拼接进上下文。

5.4 "agent execution terminated due to error"这类问题该怎么定位

这个报错几乎每个agent开发者都遇到过。我第一次看到时以为是框架bug,后来发现90%是下面三种情况:

报错类型真正原因解决方式
模型返回了非法tool call输出不是合法JSON,或函数名不符对模型输出做schema校验,用pydantic或json schema强制
工具执行时抛异常工具内部没有捕获业务错误在工具调用层统一捕获并回传给模型
上下文溢出或关键字段丢失messages里丢掉了tool_call_id严格保持消息格式,注意role和id配对

一个非常实用的改善方式:在工具调用循环里统一捕获异常,把错误信息当成tool message回给模型。因为模型看到错误信息后往往能自我修正,这会极大减少整个循环的崩溃率。代码结构类似:

for call in response.tool_calls: try: result = tools[call.name](**call.arguments) except Exception as e: result = {"error": str(e), "suggestion": "请尝试修正参数后重试"} messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})

这段代码我从最早的agent项目用到现在,是可靠性提升最关键的一招。宁可让模型多跑一步,也不能让循环停在报错上。

5.5 框架选型与工程化收尾

框架方面,我的建议是看场景。LangGraph适合可编排的状态图,节点和边都比较明确,多工具、多阶段任务很适合;AutoGen偏向多agent对话协作;CrewAI适合角色扮演式的agent团队。自研循环适合对可控性要求极高的场景。选型时先问自己:"我的agent状态流转是否复杂?" 不复杂就别上重框架。

生产级agent最后一个建议是:把agent后端包装成OpenAI兼容接口,直接放在vLLM或SGLang服务后面。这样infra层和agent层就形成了清晰的上下层结构:底层引擎负责高吞吐和稳定响应,上层agent负责决策、记忆和工具调用。出问题时也能快速分层排查——是引擎慢了,还是agent逻辑写错了。

6. 我的日常整理方法:这类skills清单怎么维护

讲完具体技术线,最后一章分享一下我整理技能的方法。因为AI infra和agent每天都在变,整理不是一次性的,而是要形成一个可迭代的习惯。

6.1 每学一个技能都回答三个问题

我在笔记里给每条知识都强制写上三个回答:它解决什么问题,为什么非它不可;它跟同类方案之间怎么选,边界在哪里;哪些隐藏坑是文档里不会写的。以"vllm scheduler逻辑"为例,我会写:它解决的是动态batch和显存分配问题;和TensorRT-LLM的调度器本质思路接近,但vLLM对动态请求更友好;坑是抢占策略和max_model_len强相关,调参时必须一起看。这样归完类,知识就不是零散笔记,而是可以直接调用的决策依据。

6.2 按"操作手册+原理笔记+排错日志"三类归档

我所有的AI infra技能归档都分三类。操作手册只放命令、参数、步骤,追求"随手抄走就能用";原理笔记放调度流程、源码逻辑、设计动机,追求"为什么是这样";排错日志放错误信息、根因、解决过程,追求"下次遇到直接定位"。这三类各有不同生命周期:操作手册更新最频繁,原理笔记最稳定,排错日志最有个人价值。

所以我写博客或者给人讲经验时,几乎不用重新准备,直接从排错日志里找一段典型case展开就行。这也是为什么我建议你也这么做——它不单是知识管理,还是一种内容资产。

6.3 一个可以直接抄的目录结构

我的skills目录大概长这样:

skills/ ├── 00-infra/ │ ├── torch-install-notes.md │ ├── vllm-server-tuning.md │ ├── sglang-source-map.md │ ├── gpu-rocm-jetson.md │ └── bench-baselines/ ├── 01-agent/ │ ├── agent-loop-minimal.py │ ├── memory-design.md │ ├── skill-vs-harness.md │ ├── error-recovery.md │ └── safety-checklist.md └── 02-learn-log/ ├── 2025-xx-vllm-performance-drop.md └── 2025-xx-agent-memory-poisoning.md

每次学完新东西,先在learn-log里记一笔,过两周如果发现它还能反复用,再沉淀到infra或agent主文档里。这个"冷静期"机制帮我过滤掉大量一次性知识,留下来的都是真正经得起项目检验的经验。最后说个小技巧:维护这类清单别追求好看,重点是记录"你实际踩过的坑",因为网上的教程和文档永远覆盖不了你那个具体环境的特殊之处。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询