这次我们来看一条能直接落到项目里的 LLM 应用开发链路:Qwen3 微调、vLLM 部署、Prompt 工程三件事怎么串起来用。很多同学单独学过 LoRA 微调,也单独配过 vLLM,但一到真实项目就卡在“模型调好了怎么发布”“接口怎么给业务调”“并发一上来怎么不崩”“客服回答总是不听话”这些环节上。这篇文章把全链路拆开,从模型选择、Unsloth 做 LoRA 微调,到 vLLM 启动 OpenAI 兼容服务,再到批量调用、Prompt 设计、性能观察和问题排查,一步一步讲清楚,不需要你有 8 卡 A100,单卡 16G 到 24G 显存的机器就能开始。
先说结论:这套方案的核心价值是“低成本验证 + 标准接口落地”。Unsloth 负责把微调门槛降下来,vLLM 负责把推理吞吐提上去,Qwen3 负责提供质量还不错的基座能力,Prompt 工程负责在不重新训练的情况下压榨模型表现。四者组合起来,个人开发者和中小团队都能在单机或少量 GPU 上跑通一套从“原始权重”到“业务 API”的完整流程。
文章会覆盖这些实操内容:Qwen3 各尺寸怎么选、vLLM 命令行和 Docker 两种部署方式、OpenAI 兼容接口怎么调用、Unsloth 微调脚本怎么写、LoRA 数据怎么准备、批量任务怎么设计、显存和性能怎么观察、常见启动和推理问题怎么排查。最后还会给出一份适合直接复用的最佳实践清单。如果你正在做 AI 客服、知识库问答、Agent 工具调用这类应用,这篇文章建议直接收藏。
1. 核心能力速览
| 能力项 | 说明 |
|---|---|
| 技术栈 | Qwen3 基座模型 + Unsloth LoRA 微调 + vLLM 推理部署 + Prompt 工程 |
| 开源情况 | Qwen3 开源权重;vLLM、Unsloth 均为开源项目 |
| 主要功能 | LoRA 微调、OpenAI 兼容 API、高并发推理、批量请求、结构化输出、Tool Calling |
| 推荐硬件 | 单卡 16G 起步,24G 更从容;CPU 可做轻量验证,但生产推理建议 GPU |
| 显存占用 | 与模型尺寸和量化方式强相关,需按实际模型版本测试确认 |
| 支持平台 | Linux 优先;Windows 可通过 WSL2 或 Docker Desktop 运行 |
| 启动方式 | vLLM 命令行启动 / Docker 启动 / Python 脚本启动 |
| API 能力 | OpenAI 兼容接口,路径包含 /v1/models、/v1/chat/completions、/v1/completions |
| 批量任务 | 支持高并发请求,vLLM 连续批处理(continuous batching)提升吞吐 |
| 适合场景 | 本地私有化部署、垂直领域问答、Agent 工具调用、批量文本处理、AI 客服 |
从这张表能看出,这四样东西刚好覆盖了 LLM 应用的四个关键阶段:选基座、调参数、上服务、写提示词。后面所有章节都围绕这张表展开。
2. 适用场景与使用边界
2.1 适合谁用
这套链路最适合三类人。第一类是把开源模型接到业务系统里的后端工程师,你需要的是一个稳定、高吞吐、接口标准的推理服务,vLLM 比 Ollama 这类轻量工具更适合生产环境。第二类是算法工程师和 AI 应用开发者,你需要在不改模型结构的前提下快速验证领域效果,Unsloth 的 LoRA 微调能把训练时间压得很短。第三类是技术负责人,需要在“调用云端 API”和“本地私有化部署”之间做技术选型,这四件套能帮你快速算清楚成本和效果边界。
2.2 能解决什么问题
它能解决几个非常具体的痛点:模型回答风格和业务不符,可以用微调修正;私有数据不能出内网,可以用 vLLM 本地部署;高并发查询时单条串行调用太慢,可以用 vLLM 的连续批处理提升吞吐;通用模型在垂直领域表现一般,可以用 Prompt 工程加 RAG 先做一轮优化,不够再上微调。这套链路最大的价值是每一层都可以单独替换,不会出现“换了基座就要重写全部业务代码”的问题。
2.3 使用边界与合规提醒
需要特别提醒的是,微调和部署本身没有问题,但使用边界必须明确。第一,微调数据要确认授权,不能把未授权的客服对话记录、用户隐私对话、版权文本直接拿去训练。第二,涉及人脸、声音、身份信息的内容要谨慎,本地模型虽然数据不出内网,但输出内容仍可能带有训练数据中的偏见或错误信息,上线前要做内容审核。第三,如果模型用于客服、医疗、金融等场景,输出内容不能直接作为最终决策依据,需要人工复核或加规则兜底。第四,本地部署并不等于完全安全,模型文件、API 服务、数据集都要做好访问控制。
3. 全栈链路概览:从原始权重到业务 API
3.1 整条链路怎么串
从项目工程的角度看,这条链路可以拆成五个环节。第一个环节是模型选型,确定用 Qwen3 的哪个尺寸、要不要量化。第二个环节是数据准备,把业务数据整理成微调或 Prompt 需要的格式。第三个环节是微调,用 Unsloth 跑 LoRA 训练,产出增量权重。第四个环节是部署推理,用 vLLM 把微调后的模型启动成 OpenAI 兼容服务。第五个环节是应用层开发,写 Prompt、接 API、做批量任务、做效果评估。
这五个环节并不是每次都必须全走一遍。如果基座模型本身效果够用,微调环节可以跳过;如果只是快速验证效果,也可以先用官方权重部署,再决定要不要微调。这条链路设计上的好处是每个环节都有明确的输入输出,单独替换不影响其他环节。
3.2 为什么选 Qwen3、Unsloth、vLLM 这个组合
Qwen3 系列覆盖了从 0.6B 到 235B 的多个尺寸,既有适合端侧的小模型,也有适合服务端的大模型,并且原生支持思考模式和非思考模式切换,这对 Prompt 工程和 Agent 场景很关键。Unsloth 的优势是训练速度快、显存占用低,它通过优化注意力机制和自动检查点策略,让 LoRA 微调在消费级显卡上也能跑,大幅降低了实验成本。vLLM 的核心是连续批处理和 PagedAttention,可以在同一张 GPU 上同时处理大量请求,吞吐表现优异,同时提供 OpenAI 兼容接口,业务代码几乎零改造就能接入。
3.3 vLLM、Ollama、SGLang 怎么选
很多同学会问,同样是推理框架,vLLM、SGLang、Ollama 到底有什么区别。简单来说,Ollama 主打开箱即用,适合个人学习和快速验证,但高并发和批量场景下的吞吐优化不如 vLLM;vLLM 和 SGLang 都面向生产推理,两者都有连续批处理、前缀缓存、量化支持,vLLM 生态更成熟、资料更多,SGLang 在某些长文本和结构化输出场景也有优势。如果你刚入门,建议先用 vLLM,遇到性能瓶颈再横向对比 SGLang。如果只是本机跑个 Demo,Ollama 会更省事,但后续要做接口服务和批量任务,尽早切到 vLLM 更稳妥。
4. 环境准备与前置条件
4.1 硬件与操作系统
部署这条链路,操作系统建议使用 Linux,Ubuntu 20.04 或 22.04 都是常见选择。Windows 用户可以通过 WSL2 或 Docker Desktop 运行,但 GPU 透传配置会稍微复杂一些。硬件方面,GPU 显存是最关键的指标:Qwen3-8B 用 BF16 精度加载时权重大约 16G,如果还要留推理和并发空间,16G 显存会比较紧张,24G 更从容;如果使用 4bit 量化,8B 模型的显存占用可以降到 6G 到 8G 左右,16G 显卡就能舒服很多。CPU 虽然可以跑,但速度会慢很多,只建议做功能验证,不建议做生产推理。
4.2 软件依赖
软件层面主要准备以下内容:NVIDIA 显卡驱动、CUDA 工具包、Python 3.10 到 3.12、PyTorch、HuggingFace 相关库。vLLM 对 CUDA 版本有要求,安装前先确认驱动版本和 CUDA 版本匹配。如果本机已经装了 PyTorch,要注意 vLLM 依赖的 PyTorch 版本,建议直接按 vLLM 官方文档安装对应版本,避免冲突。磁盘空间方面,Qwen3-8B 的 FP16 权重大约 16G,INT4 量化版大约 5G 到 6G,训练过程中还会产生检查点文件,建议预留 50G 到 100G 空间。
4.3 国内下载与镜像加速
模型权重一般从 HuggingFace 下载,国内网络环境建议先设置镜像环境变量,然后再用 huggingface-cli 或下载脚本拉取。这里给一个通用配置方式,不需要修改任何项目代码:
export HF_ENDPOINT=https://hf-mirror.com设置之后,HuggingFace 的下载请求会走镜像站点,速度会明显改善。如果你的网络环境无法直接访问 HuggingFace,也可以从 ModelScope 下载 Qwen3 权重,再手动指定本地路径给 vLLM 和 Unsloth 使用。
4.4 安装验证清单
正式安装之前,可以用下面这几条命令确认环境是否就绪:
nvidia-smi python --version pip --version nvcc --versionnvidia-smi 能看到 GPU 型号和驱动版本;python 和 pip 版本取决于具体项目要求;nvcc 用于确认 CUDA 工具链。如果哪一项缺失,先补齐再继续,否则后面安装 vLLM 或 Unsloth 时可能报编译错误。
5. Qwen3 模型选择与下载
5.1 Qwen3 各尺寸怎么选
Qwen3 系列型号比较多,选型思路可以按显存和场景来定。0.6B 和 1.7B 适合 CPU 推理、端侧部署或低显存环境,效果有限但速度快。4B 和 8B 是个人开发者和中小团队的主力选择,8B 在中文理解和指令跟随上表现不错,24G 显存可以直接用 BF16 加载。14B 和 32B 属于高效果档位,适合对质量要求高且显存充足的场景,32B 一般需要量化或多卡。30B-A3B 这类 MoE 模型参数量大但激活参数少,推理速度接近小模型,但完整加载需要较大显存,通常搭配量化使用。还有一个重要因素是思考模式,Qwen3 支持 thinking 和 non-thinking 两种模式,部署时可以通过请求参数控制,具体参数要以模型仓库和推理框架的支持情况为准。
5.2 下载模型权重
以 Qwen3-8B 为例,可以用 HuggingFace CLI 下载:
pip install -U huggingface_hub huggingface-cli download Qwen/Qwen3-8B \ --local-dir ./models/Qwen3-8B \ --local-dir-use-symlinks False下载完成后,检查目录里是否包含 config.json、tokenizer.json、model.safetensors 这些关键文件。模型文件较大,如果中途失败可以重新执行命令,HuggingFace 支持断点续传。下载完成后建议先记录路径,后续 vLLM 和 Unsloth 都会用到这个本地路径,避免每次启动都要重新下载。
5.3 量化版本怎么选
生产部署时量化是一个绕不开的话题。Qwen3 官方和社区提供了多种量化版本,常见的有 GPTQ、AWQ 和 FP8。FP8 在 Hopper 架构及更新显卡上速度表现好,AWQ 和 GPTQ 兼容性更广。量化的好处是显存占用大幅下降,代价是输出质量有小幅损失,具体损失因任务而异。如果你的显存刚好卡在“原版放不下、量化能放下”的临界点,建议同时准备原版和量化版,用同一批测试集做对比,不要只看显存数字。vLLM 启动时指定量化后的模型路径即可,不需要额外代码改动。
6. vLLM 部署 Qwen3:命令行到 OpenAI 兼容服务
6.1 安装 vLLM
vLLM 的安装方式以 pip 为主。官方会针对不同 CUDA 版本发布预编译包,第一次安装建议使用干净的虚拟环境,避免和已有 PyTorch 环境冲突:
python -m venv vllm_env source vllm_env/bin/activate pip install --upgrade pip pip install vllm安装完成后可以用下面的命令确认版本:
python -c "import vllm; print(vllm.__version__)"如果打印出版本号,说明安装成功。如果安装过程中报 CUDA 版本不匹配或编译错误,优先检查 NVIDIA 驱动和 CUDA 版本,再查看 vLLM 官方文档要求的 PyTorch 版本。
6.2 命令行启动推理服务
vLLM 最常用的启动方式是起一个 OpenAI 兼容的 API 服务。以 Qwen3-8B 模型为例,启动命令如下:
python -m vllm.entrypoints.openai.api_server \ --model ./models/Qwen3-8B \ --served-model-name qwen3-8b \ --tensor-parallel-size 1 \ --max-model-len 8192 \ --gpu-memory-utilization 0.9 \ --port 8000参数说明:--model 指定模型路径或 HuggingFace 仓库名;--served-model-name 是暴露给调用方的模型名,可以自定义;--tensor-parallel-size 表示张量并行卡数,单卡填 1;--max-model-len 是最大上下文长度,按实际显卡显存调整,显存小的机器可以降到 4096 或更低;--gpu-memory-utilization 控制显存使用上限,0.9 表示最多用 90% 显存;--port 指定服务监听端口,默认 8000。
如果你的显卡显存不大,可以加量化参数,例如加载 FP8 或 AWQ 量化后的模型:
python -m vllm.entrypoints.openai.api_server \ --model ./models/Qwen3-8B-AWQ \ --served-model-name qwen3-8b-awq \ --quantization awq \ --max-model-len 4096 \ --gpu-memory-utilization 0.9 \ --port 80006.3 Docker 方式部署
服务器环境里用 Docker 部署 vLLM 更干净,一条命令就能拉起服务,不污染宿主机 Python 环境。官方镜像已经包含了 vLLM 运行环境,只需要把模型目录挂载进去:
docker run --rm --gpus all \ -p 8000:8000 \ -v ~/models:/models \ vllm/vllm-openai \ --model /models/Qwen3-8B \ --served-model-name qwen3-8b \ --max-model-len 8192 \ --gpu-memory-utilization 0.9需要说明的是,--gpus all 会占用全部 GPU。如果机器上有多个进程要用不同显卡,建议用 --gpus '"device=0"' 指定单卡。镜像版本选择上建议使用带具体版本号的镜像,不要用 latest,方便回滚和复现。
6.4 验证服务是否启动成功
服务启动后,先访问模型列表接口确认模型已经加载:
curl http://127.0.0.1:8000/v1/models正常返回会包含模型名称和元信息。然后再测一个完整的对话请求:
curl http://127.0.0.1:8000/v1/chat/completions \ -H "Content-Type: application/json" \ -d '{ "model": "qwen3-8b", "messages": [ {"role": "system", "content": "你是一个简洁的技术助手。"}, {"role": "user", "content": "用三句话解释什么是 vLLM"} ], "temperature": 0.7, "max_tokens": 512 }'返回 JSON 中 choices[0].message.content 就是模型输出。能正常返回,说明 vLLM 服务已经跑通,后面就可以接业务了。
6.5 多卡并行与工具调用注意事项
如果模型单卡放不下,比如 32B 模型,可以通过 --tensor-parallel-size 参数把权重分散到多张显卡。双卡场景启动命令只需要把 --tensor-parallel-size 改为 2,前提是两张卡型号和显存一致,否则可能报错。另外,启用工具调用(Tool Calling/Function Calling)时,vLLM 需要指定 tool-call-parser,具体取值和 vLLM 版本、模型类型相关,配置前先确认当前版本支持情况,避免启动后接口报错。
7. Unsloth 微调 Qwen3:LoRA 实战
7.1 安装 Unsloth
Unsloth 的安装推荐使用虚拟环境,并先安装对应版本的 PyTorch。官方提供了分版本的安装命令,这里给出通用流程:
python -m venv unsloth_env source unsloth_env/bin/activate pip install --upgrade pip pip install "unsloth[colab-new] @ git+https://github.com/unslothai/unsloth.git"如果你的网络环境不支持直接从 GitHub 拉取,也可以使用国内加速方式下载后本地安装,这部分以实际网络情况为准。安装后验证:
python -c "import unsloth; print('unsloth ok')"7.2 加载模型并配置 LoRA
Unsloth 的核心用法是先把基座模型加载为 4bit 量化版本,再挂 LoRA 适配器。以 Qwen3-8B 为例,训练脚本可以这样写:
from unsloth import FastLanguageModel import torch model_name = "./models/Qwen3-8B" model, tokenizer = FastLanguageModel.from_pretrained( model_name=model_name, max_seq_length=4096, dtype=None, load_in_4bit=True, ) model = FastLanguageModel.get_peft_model( model, r=16, lora_alpha=16, lora_dropout=0, target_modules=[ "q_proj", "k_proj", "v_proj", "o_proj", "gate_proj", "up_proj", "down_proj", ], use_gradient_checkpointing="unsloth", random_state=42, )参数说明:r 是 LoRA 秩,常用 8 到 64,数据量小时先用 8 或 16;lora_alpha 是缩放系数,一般设为 r 的两倍左右;lora_dropout 设为 0 在 LoRA 训练中是常见选择,可以减少训练不稳定;target_modules 指定要挂 LoRA 的注意力层和 MLP 层,保持默认即可。use_gradient_checkpointing 设置为 unsloth,会在训练时用梯度检查点换显存空间。
7.3 准备微调数据集
LoRA 微调不需要海量数据,几百到几千条高质量样本就能有明显效果。常见的数据格式是 instruction 风格,每条样本包含 instruction、input、output 三个字段。下面是一个 JSONL 格式示例:
{"instruction": "用户询问商品退货政策", "input": "我昨天买的耳机还能退货吗", "output": "您好,耳机支持七天无理由退货。请您保留订单号和商品吊牌,在订单页面提交退货申请即可。"}数据准备时要注意几点:第一,质量优先,宁可 500 条人工清洗的高质量数据,也不要 5000 条爬来的脏数据;第二,覆盖业务的典型场景,把高频问题、难回答的问题都放进去;第三,output 要符合你期望的最终风格,比如客服话术就写完整客服话术,不要写半截话;第四,避免数据泄露,不要把测试集里想验证的问题写进训练集。
7.4 执行 LoRA 训练
数据准备好之后,用 transformers 的 Trainer 训练。下面是完整训练脚本:
from datasets import load_dataset from transformers import TrainingArguments, Trainer from unsloth import is_bfloat16_supported dataset = load_dataset("json", data_files="./data/train.jsonl", split="train") def format_prompt(sample): prompt = f"指令:{sample['instruction']}\n输入:{sample['input']}\n回答:" tokenized = tokenizer(prompt, truncation=True, max_length=4096) target = tokenizer(sample["output"], truncation=True, max_length=1024) return { "input_ids": tokenized["input_ids"] + target["input_ids"], "labels": [-100] * len(tokenized["input_ids"]) + target["input_ids"], "attention_mask": [1] * (len(tokenized["input_ids"]) + len(target["input_ids"])), } dataset = dataset.map(format_prompt, remove_columns=dataset.column_names) args = TrainingArguments( per_device_train_batch_size=2, gradient_accumulation_steps=8, warmup_steps=10, num_train_epochs=3, learning_rate=2e-4, fp16=not is_bfloat16_supported(), bf16=is_bfloat16_supported(), logging_steps=10, optim="adamw_8bit", weight_decay=0.01, lr_scheduler_type="linear", seed=42, output_dir="./outputs/qwen3-lora", report_to="none", ) trainer = Trainer( model=model, args=args, train_dataset=dataset, tokenizer=tokenizer, ) trainer.train()这段脚本的关键点是 labels 中把输入部分的 token 设为 -100,这样 loss 只计算输出部分,模型学的是“如何回答”而不是“如何复读指令”。训练结束后,模型会生成 LoRA 增量权重。
7.5 保存、合并与导出
LoRA 训练生成的只是增量权重,推理时有两种用法:第一种是直接加载 LoRA 权重和基座模型一起推理;第二种是把 LoRA 权重合并回基座模型,导出成完整的模型目录,再用 vLLM 部署。导出方式参考下面的脚本:
model.save_pretrained("lora_model") tokenizer.save_pretrained("lora_model") model.save_pretrained_merged("merged_model", tokenizer, save_method="merged_16bit") tokenizer.save_pretrained("merged_model")合并后的 merged_model 目录就是一个完整的模型,可以直接作为 vLLM 的 --model 参数传进去。到这里,微调阶段就完成了,下一步是把微调后的模型部署成服务。
7.6 没有大显存怎么办
如果显存只有 8G 到 12G,有几个可行方案。第一,使用更小的基座模型,比如 Qwen3-4B;第二,把 max_seq_length 调小到 2048 或 1024;第三,per_device_train_batch_size 调成 1,配合更大的 gradient_accumulation_steps;第四,减少训练样本长度,太长的样本先做截断。如果这些都不行,可以考虑用 Colab 或云 GPU 训练,训完导出 LoRA 权重再回到本地推理。Unsloth 本身对低显存环境做了很多优化,实际效果通常比传统 LoRA 训练流程好不少。
8. Prompt 工程实战
8.1 Prompt 工程、RAG、微调怎么选
很多同学纠结一个问题:做 AI 客服这类应用,到底应该用 Prompt 工程、RAG 还是微调?这三者解决的问题不同。Prompt 工程解决的是“模型会但不一定按你的方式说”的问题,比如格式、语气、步骤约束;RAG 解决的是“模型不知道但你数据库里有”的问题,比如内部文档、实时信息、私有知识;微调解决的是“模型长期稳定地按某种风格或领域规则输出”的问题,比如固定话术、特定术语、输出结构。一般建议的顺序是先 Prompt 工程,再结合 RAG,最后才考虑微调。微调成本最高,如果 Prompt 加检索已经能满足效果,就不需要动训练。
8.2 系统提示词设计
系统提示词是 Prompt 工程里性价比最高的一环。一个好的系统提示词应该包含角色、任务、约束、输出格式、边界处理五个部分。下面是一个客服场景的示例:
你是一个电商平台的售后客服,名字叫小安。 你的任务是根据知识库内容回答用户问题,不得编造不存在的政策。 回答要求: 1. 先用一句话共情用户; 2. 再给出明确的解决方案和操作路径; 3. 如果用户问题属于退款、投诉、发票,先引导提供订单号; 4. 如果知识库中没有答案,明确告知用户需要转人工,并记录问题类型。 始终使用简体中文,不使用 Markdown 表格,单次回答不超过 150 字。设计系统提示词时,要避免“你是一个 AI 助手”这种空泛设定,把判断标准写具体,模型的行为会更稳定。同时要明确边界,比如“不得回答与售后无关的问题”,避免模型被诱导输出不相关内容。
8.3 思考模式与非思考模式
Qwen3 的一个特点是支持思考模式和非思考模式。思考模式适合数学、逻辑、复杂任务,模型会先生成一段推理过程再输出答案;非思考模式适合客服、文本改写、信息抽取等对响应速度敏感的场景。实际使用时,要按接口文档设置相应参数,并根据场景选择模式。比如客服场景建议直接用非思考模式,延迟更稳定;代码生成和复杂分析建议开思考模式,答案质量更好。两种模式的效果差异要在你的业务数据集上实际对比,不要凭感觉选。
8.4 少样本示例与结构化输出
大部分场景下,在 Prompt 里给两三个示例比反复描述规则更有效。少样本示例要贴近真实输入分布,并且正例、反例都要有。比如要求模型输出 JSON,可以在系统提示词里直接给一个格式示例:
输出的 JSON 结构如下,不要输出任何额外内容: {"category": "售后", "intent": "退货", "order_required": true, "reply": "...", "confidence": 0.9}结构化输出对后续业务解析非常重要。vLLM 和 Qwen3 本身就支持 JSON 输出,可以在 API 请求中要求模型使用 JSON 格式。如果发现模型偶尔输出多余文字,优先检查提示词中的格式约束,而不是急着改模型。
8.5 Agent 与工具调用
Qwen3 在工具调用方面表现不错,适合做 Agent 场景。本地部署时,要在 vLLM 启动参数中配置 tool-call-parser,然后在请求 messages 中传入 tools 数组。工具调用的 Prompt 要写清楚工具的描述和参数格式,描述越准确,模型选工具的成功率越高。首次接入时建议先用一个简单工具测试,比如查天气、算价格,确认端到端流程通了再扩展复杂工具。
9. 接口 API 与批量任务
9.1 OpenAI 兼容接口调用
vLLM 启动后就是一个 OpenAI 兼容服务。业务代码可以复用 OpenAI SDK,也可以直接用 requests 调用。下面是用 Python 调用对话接口的通用示例:
import requests BASE_URL = "http://127.0.0.1:8000" MODEL_NAME = "qwen3-8b" def chat(messages, temperature=0.7, max_tokens=512): resp = requests.post( f"{BASE_URL}/v1/chat/completions", json={ "model": MODEL_NAME, "messages": messages, "temperature": temperature, "max_tokens": max_tokens, }, timeout=120, ) resp.raise_for_status() data = resp.json() return data["choices"][0]["message"]["content"] if __name__ == "__main__": result = chat([ {"role": "system", "content": "你是一个技术编辑。"}, {"role": "user", "content": "用 50 字介绍 Qwen3"}, ]) print(result)这个接口和 OpenAI 的 /v1/chat/completions 保持一致,业务端只需要改 BASE_URL 和 model 名称,其他地方基本不动。本地服务默认不校验 API Key,如果服务暴露在非本机网络,务必加认证或限制访问来源。
9.2 批量任务设计
批量任务是大模型应用里最常见的场景,比如批量改写文案、批量抽取信息、批量客服质检。直接写一个 for 循环逐个请求,速度慢且浪费 GPU。正确的做法是使用并发请求,同时给每个任务加日志、失败重试和结果落盘。下面是一个简单的并发批处理模板:
from concurrent.futures import ThreadPoolExecutor, as_completed import time def process_one(item): start = time.time() try: reply = chat( [ {"role": "system", "content": "你是文本抽取助手,输出 JSON。"}, {"role": "user", "content": f"从下面文本中抽取公司名称和金额:{item['text']}"}, ], temperature=0.1, max_tokens=256, ) return {"id": item["id"], "ok": True, "result": reply, "time": time.time() - start} except Exception as exc: return {"id": item["id"], "ok": False, "error": str(exc)} items = [ {"id": 1, "text": "北京某科技有限公司向上海某贸易公司支付货款 12000 元。"}, {"id": 2, "text": "深圳某工作室完成了一笔 8800 元的服务合同。"}, ] with ThreadPoolExecutor(max_workers=8) as executor: futures = [executor.submit(process_one, item) for item in items] for future in as_completed(futures): print(future.result())并发数并不是越大越好,它受 GPU 显存和 vLLM 最大并发序列数限制。实践中可以从 1 个并发开始,逐步提升,观察显存和响应时间,找到一个稳定点。批量任务的正确姿势是:输入文件、输出文件、日志文件分开保存;每个任务带唯一 ID;失败任务写入失败列表,后续单独重跑;增加超时时间,避免单个请求卡住整个任务。
9.3 vLLM 连续批处理与吞吐优化
vLLM 内置的连续批处理机制会让多个请求共享一次前向传播,显著提高吞吐。启动时可以通过 --max-num-seqs 控制单个批次最多承载的序列数,默认值取决于显存,可以按需调整。还可以开启 --enable-prefix-caching 缓存相同前缀的 KV 状态,对多轮对话和批量相似 Prompt 有帮助。如果业务大部分请求共享同一段系统提示词,前缀缓存能明显降低首字延迟。
10. 资源占用与性能观察
10.1 怎么观察显存占用
部署和调用过程中,显存占用是必须盯住的指标。最简单的观察方式是 nvidia-smi:
nvidia-smi -l 2该命令每两秒刷新一次显存使用情况。vLLM 启动后,显存占用会先升到一个高点,这是预分配显存,不是泄漏。真正需要关注的是推理过程中显存是否持续增长,如果持续增长到超限,说明 max-model-len 或 max-num-seqs 配置过大,需要调小。
10.2 延迟指标怎么看
衡量推理性能主要看两个指标:TTFT 和 TPOT。TTFT 是首字延迟,用户第一次看到输出前等待的时间;TPOT 是每个输出 token 的生成耗时。二者在 vLLM 日志中会有统计指标,也可以通过请求耗时粗略估算。如果 TTFT 偏高,优先检查前缀缓存是否开启、模型是否被量化、并发是否过载;如果 TPOT 偏高,考虑降低 max-model-len、减少并发数或换更大显存的 GPU。
10.3 降低显存占用的常用手段
第一,量化,FP8、AWQ、GPTQ 都能显著降低权重占用。第二,调小 max-model-len,上下文长度直接影响 KV cache 占用。第三,调小 max-num-seqs,同时降低并发序列数量。第四,使用更小尺寸的模型。第五,清理不必要的进程,同一个 GPU 上不要同时跑多个大模型服务。这些手段可以组合使用,但要注意每次调整后重新测效果,量化加上短上下文对输出质量的影响是叠加的。
10.4 CPU 推理与 GPU 推理的差异
CPU 推理在功能上可行,但速度差距很大。Qwen3-8B 在 CPU 上生成一个 token 可能需要几百毫秒到数秒,GPU 上通常是几十毫秒甚至更低。如果你只是验证接口流程,CPU 够用;如果要做批量任务或线上服务,GPU 是必须的。另外,CPU 推理时内存带宽比 CPU 核心数更关键,DDR5 和通道数量都会明显影响速度。
11. 常见问题与排查方法
| 问题现象 | 可能原因 | 排查方式 | 解决方案 |
|---|---|---|---|
| 启动后页面或接口打不开 | 端口被占用或服务未启动 | 检查日志,netstat 查看端口 | 更换端口或杀掉占用进程后重启 |
| vLLM 安装失败 | CUDA 版本不匹配或 Python 版本不对 | 查看报错信息,确认 nvcc 版本 | 按官方文档安装匹配版本,使用虚拟环境 |
| 模型文件缺失导致启动失败 | 下载不完整或路径错误 | 检查模型目录中 safetensors、config.json | 重新下载,确认本地路径 |
| 显存不足,启动 OOM | 模型太大或并发配置过高 | nvidia-smi 观察显存占用 | 量化模型、调小 max-model-len、减少并发 |
| 生成速度慢 | 上下文过长、量化损失或并发过载 | 观察 TTFT 和 TPOT,检查日志 | 开启前缀缓存,降低并发,换量化版本 |
| API 返回 401 或认证失败 | 服务端配置了 Key 但请求未带 | 查看启动参数和日志 | 请求头加 Authorization,或关闭认证限制 |
| 批量任务卡住 | 单请求超时或线程池耗尽 | 查看任务日志和超时时间 | 增加超时控制,失败任务单独重试 |
| 工具调用不生效 | 未配置 tool-call-parser 或格式错误 | 查看请求返回和错误日志 | 按 vLLM 版本配置 parser,检查 tools 格式 |
| 输出质量不稳定 | 投票温度过高或 Prompt 约束不清 | 对比多次输出 | 降低 temperature,强约束输出格式 |
| 训练 loss 不下降 | 学习率过高或数据格式错误 | 查看训练日志 | 调小 learning rate,检查数据 prompt 和 labels |
12. 最佳实践与建议
从工程角度整理几条直接能用的建议。第一,第一次跑通时使用小参数,比如 max-model-len 设为 2048,并发数设为 1,先确认链路通,再逐步放大。第二,保留一套最小可运行配置,写入项目 README,避免换机器后重新摸索。第三,模型目录、数据集、训练输出、日志、结果分别建目录,命名带日期和版本,尤其是模型权重,训练后要写清楚对应的基座版本和数据集版本。第四,批量任务必须加日志和失败重试,任务文件保留原始输入和原始输出,方便后面对比。第五,接口服务尽量只监听内网地址,如果必须对外暴露,加上 API Key 认证和访问白名单。第六,涉及用户数据、版权文本、人脸声音素材时,先确认授权再进入训练集,这个环节不能省。第七,上线前准备一套固定测试集,包含业务高频问题、边界问题、恶意输入,每次改 Prompt 或换模型都用同一套测试集评估,避免“修了一个问题,引出三个新问题”。
13. 总结与下一步
这条链路最值得尝试的点是它的模块化设计:Qwen3 选型、Unsloth 微调、vLLM 部署、Prompt 工程每一层都能独立替换和验证。按照文章的顺序,最先应该验证的是 vLLM 用官方 Qwen3 权重启动服务,跑通 /v1/chat/completions 接口,再决定要不要微调。最容易踩的坑有三个:环境安装时 CUDA 和 PyTorch 版本不匹配、显存不够导致 OOM、批量并发设置过大拖垮服务,这三个坑在启动阶段就会遇到,提前做好心理准备能省大量时间。
下一步可以做的方向很多:一是用你自己的业务数据做一组 LoRA 微调,对比微调前后在固定测试集上的效果;二是接入 RAG,先在一组内部文档上验证检索加生成的完整流程;三是把批量任务改造为异步任务队列,配合 Redis 或消息队列做更大规模的处理;四是尝试 SGLang 或其他推理框架做横向对比,确认 vLLM 在你的场景下是不是最优解。建议先把官方权重部署跑通,再按“Prompt 优化、RAG、微调”的顺序逐步升级,每一步都用同一个测试集评估,这样整个链路的效果变化是可控的。