一、写在前面:为什么要本地部署 + 微调
调用云端 API 能快速验证产品想法,但当你面对以下场景时,云端方案会碰壁:数据合规要求不允许用户数据出境、业务专有术语(如企业内部产品代码、行业黑话)云端模型完全不懂、高频调用成本在规模化后远超自建推理服务。本地部署解决“数据在哪里跑”的问题,微调解决“模型懂不懂你的业务”的问题,前后端联调解决“用户怎么用上它”的问题。三者构成了一条从零到一的完整工程链路。
本文按照实际开发顺序组织:先讲部署(把模型跑起来),再讲微调(让模型懂业务),最后用完整的实战案例串联 FastAPI 后端与前端联调。
二、本地部署:选对工具,算清显存
2.1 Ollama vs vLLM:不是竞品,是阶段
很多开发者纠结“到底用 Ollama 还是 vLLM”,本质上是在问“我该用什么工具”——但这两个工具解决的是不同阶段的问题。Ollama 优化的是“用起来爽”,vLLM 优化的是“扛得住压”。它们都提供聊天接口、都支持流式输出,单用户场景下几乎感觉不到差别——差别只在持续、高并发的负载下才会爆发。
架构差异决定了性能差距。Ollama 收到多个请求时排队串行处理——后面的人必须等前面的人生成完。vLLM 采用连续批处理(Continuous Batching) :新请求可以随时插进正在计算的批次中,GPU 几乎没有空闲瞬间。实测数据:单用户时两者速度几乎一样(68 vs 64 tokens/s),但 10 路并发下 vLLM 吞吐是 Ollama 的近 4 倍,50 路并发下差距拉到 4.5 倍。
显存管理是另一个分水岭。Ollama 为每个会话预先分配固定大小的 KV Cache——分多了浪费、分少了 OOM。vLLM 的 PagedAttention 像操作系统管理虚拟内存一样,按页动态分配 KV Cache,显存利用率可以做到 90% 以上,长上下文也不容易爆显存。
选型建议:个人开发环境、快速原型验证、消费级 GPU 选 Ollama。面向多人的生产服务、需要多卡分布式部署、追求极致吞吐选 vLLM。初创团队建议从 Ollama 快速启动,成熟服务推荐基于 vLLM 构建生产级架构。
维度OllamavLLM
定位开箱即用的本地模型管家生产级推理引擎
批处理串行排队连续批处理
KV Cache预分配固定大小PagedAttention 动态分配
多卡扩展基本不支持张量并行/流水线并行
模型格式GGUF 预量化HF safetensors + AWQ/GPTQ/FP8
适用场景个人开发、原型验证生产服务、高并发
2.2 量化:让消费级显卡跑得动大模型
量化的本质只有一句话:把每个参数的存储精度调低,用一点精度换一大截显存。7B 模型 fp16 权重约 14-15 GB,int8 约 7-8 GB,int4 约 4-5 GB。8 GB 的卡想跑 7B,fp16 根本装不下,4bit 才能舒适;24 GB 的卡想上 32B,也只有 Q4(约 20 GB 级)够得着。
三条量化路线各有主场:
GGUF(Ollama/llama.cpp 路线) :CPU/GPU 通用,纯 CPU 也能跑,门槛最低。Q4_K_M 是甜点档位——大多数人日常问答感觉不出与 fp16 的差距。配合 llama.cpp 可以在 CPU 与 GPU 之间灵活分配计算。
AWQ(vLLM 服务首选) :激活感知量化,不依赖复杂的逐层重建过程,量化速度更快。AWQ 量化后模型体积可缩减 75%,推理速度提升 20-40%,用 AutoGPTQ 量化的权重可直接被 vLLM 加载。
GPTQ(PyTorch 生态兼容) :在 GPU 消费级硬件上表现优秀,延迟降低显著。GPTQ-Int4 量化后模型同样缩减 75%,适合需要与 transformers 生态无缝衔接的场景。
量化不是免费的。位宽越低,丢的信息越多——Q8 接近无损,Q4 是“甜点”,Q2/Q3 则会明显掉智商(输出重复、逻辑断裂)。实际跑起来还会发现:量化省的不只是权重显存,KV Cache 也会被一起压缩——Q4 的 7B 在 8 GB 卡上开 4K 上下文还能留出并发余量,而 fp16 连加载都做不到。
2.3 硬件选型速查表
以 Qwen3.8-27B 为参考样本(官方 BF16 权重实测 51.75 GB,FP8 权重 28.75 GB),不同硬件的适配档位如下:
显存档位代表显卡可跑模型推荐量化档位典型速度
8-12 GBRTX 4060 8G / 5070 12G7B-9BQ4_K_M / AWQ INT425-40 tok/s
16 GBRTX 4060 Ti 16G / 5060 Ti 16G14B-27BQ4_K_M / FP830-50 tok/s
24 GBRTX 4090 / 5090 32G27B-35BQ4_K_M / AWQ INT440-80 tok/s
48 GB+A6000 / A100 40G70BAWQ INT4 / GPTQ INT415-25 tok/s
对于 MoE 架构模型,Strata 引擎等方案通过将 MoE 整体载入 RAM、仅将高频专家载入 VRAM,使 12GB 显存的 RTX 5070 也能运行 125B 参数的 Qwen3.8-Flash-Next 模型,2-bit 量化版达到 94 词元/秒的推理速度。
三、微调:从“通用学霸”到“领域专家”
3.1 微调方法选型
现成的大模型就像博学的“通才学霸”,它知道很多通用知识,但对你公司特有的产品代码、行业内部的报告格式却一无所知。微调就是让通用模型快速学会你的“独门秘籍”。
LoRA 是目前最流行、最实用的微调方法。它不给模型大脑动手术,而是在旁边加一个“外挂知识模块”——冻结预训练权重,在 Transformer 层的注意力模块旁路添加可训练的低秩分解矩阵。假设原始权重为 W,LoRA 引入 W‘ = W + BA,其中 B 和 A 是低秩矩阵,训练时只更新这两个小矩阵。秩 r 通常选 4-64,推荐从 8 或 16 开始尝试。
QLoRA 在 LoRA 基础上引入 4-bit 量化的基座模型,使用 NF4 数据类型和双重量化技术,进一步将显存需求降低至原有 LoRA 的 1/3 左右。例如,用 QLoRA 微调 70B 模型仅需 48GB 显存。
3.2 数据准备:80% 的精力在这里
微调的效果上限由数据质量决定,而非训练技巧。LLaMA-Factory 默认支持最通用的 Alpaca 格式(一个包含“指令-输入-输出”对的 JSON 列表文件):
json
[
{
“instruction”: “请把以下患者的俗语翻译为专业医学术语。”,
“input”: “我嗓子有点干,身上觉得发烫。”,
“output”: “患者自诉轻度咽部充血,伴随低热症状。”
},
{
“instruction”: “请把以下患者的俗语翻译为专业医术语。”,
“input”: “我肚子疼得拧成了麻花。”,
“output”: “患者自诉伴有急性腹部痉挛性疼痛。”
}
]
指令(instruction) 是你希望用户对 AI 发送的命令,输入(input) 是额外的上下文背景,输出(output) 是你希望 AI 模仿的标准答案。数据来源可以从专业书籍、论文、内部文档中抽取,并利用大模型进行知识蒸馏和思维链增强,最后务必请领域专家审核。
数据质量的三个关键原则:多样性——覆盖业务场景中的各种表达方式,避免模型只学会一种问法;一致性——同一类问题的回答风格和格式要保持统一;规模适中——通常 500-2000 条高质量样本即可让模型学会一个特定任务,关键是质量而非数量。
3.3 LLaMA-Factory 微调实战
LLaMA-Factory 是一个一站式的模型训练控制台,提供了一个网页可视化界面,让你只需通过鼠标“点选”就能完成大模型的全部训练配置与启动。
环境搭建:
bash
git clone https://github.com/hiyouga/LLaMA-Factory.git
cd LLaMA-Factory
pip install -e .[metrics,bitsandbytes]
启动 Web UI:
bash
llamafactory-cli webui
访问 http://localhost:7860 后,在面板中配置以下核心参数:
参数推荐值说明
微调方法LoRA仅训练约 0.1% 的参数,显存占用降低 80%
微调阶段Supervised Fine-Tuning (SFT)监督微调,最常用的微调形式
学习率2e-4LoRA 标准学习率
训练轮数3让模型把数据集从头到尾学习 3 遍
LoRA 秩 ®8 或 16秩越大表达能力越强,但过拟合风险也增加
LoRA Alpha16 或 32行业通用最优参数组合,兼顾拟合与防过拟合
批次大小2-4视显存而定,24GB 显存建议 4
训练完成后,LoRA 适配器权重文件通常只有 10-50 MB——这是 LoRA 最大的工程优势:可插拔,不同任务切换不同 LoRA 权重,无需重新加载基座模型。
3.4 模型合并与导出
训练产出的是 LoRA 适配器,部署时需要将其与基座模型合并,或者让推理框架同时加载基座模型和适配器。
方式一:合并导出(适合 Ollama 部署) :
python
from peft import PeftModel
from transformers import AutoModelForCausalLM, AutoTokenizer
base_model = AutoModelForCausalLM.from_pretrained(“Qwen/Qwen3-0.6B”)
lora_model = PeftModel.from_pretrained(base_model, “path/to/lora_output”)
merged_model = lora_model.merge_and_unload()
merged_model.save_pretrained(“path/to/merged_model”)
tokenizer.save_pretrained(“path/to/merged_model”)
合并后的模型与原始模型完全兼容,可直接用 transformers 标准流程部署。注意:仅在推理前合并,训练中应保持分离以便多任务切换。
方式二:动态加载适配器(适合 vLLM 部署) :vLLM 支持 Multi-LoRA 托管能力,允许在同一推理实例上动态加载多个 LoRA 适配器,对于需要服务多个定制化模型的场景极具价值。
四、前后端联调实战:从权重文件到可用产品
4.1 整体架构
训练完模型只是第一步。模型不提供服务,就只是一堆躺在磁盘上的权重文件。以下是一个完整的从训练到上线的架构:
text
┌──────────────────────────────────────────────────┐
│ 前端(React / Vue / 浏览器) │
│ EventSource / fetch + ReadableStream │
└────────────────────┬─────────────────────────────┘
│ HTTP / SSE
▼
┌──────────────────────────────────────────────────┐
│ FastAPI 推理服务(uvicorn) │
│ ┌────────────────────────────────────────────┐ │
│ │ GET /health → 健康检查 │ │
│ │ POST /chat → 单条对话(流式/非流式) │ │
│ │ POST /chat/batch → 批量对话 │ │
│ └────────────────────────────────────────────┘ │
│ ┌────────────────────────────────────────────┐ │
│ │ Tokenizer (chat_template) │ │
│ │ PeftModel (Base + LoRA adapter) │ │
│ │ model.generate() → reply │ │
│ └────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────┘
4.2 FastAPI 后端服务
以下是一个完整的 FastAPI 推理服务代码,支持单条对话、批量对话、健康检查和 SSE 流式输出:
python
“”"
Qwen3-0.6B + LoRA FastAPI 推理服务
启动:uvicorn fastapi_server:app –host 0.0.0.0 –port 8000
“”"
import json
import torch
from fastapi import FastAPI, HTTPException
from fastapi.responses import StreamingResponse
from pydantic import BaseModel
from transformers import AutoTokenizer, AutoModelForCausalLM, TextIteratorStreamer
from peft import PeftModel
from threading import Thread
==================== 配置区 ====================
MODEL_PATH = “./Qwen3-0.6B”
LORA_PATH = “./qwen_lora_output”
=================================================
app = FastAPI(title=“LoRA Inference Service”)
启动时加载模型(只加载一次,常驻内存)
print(“⏳ 正在加载 Tokenizer …”)
tok = AutoTokenizer.from_pretrained(MODEL_PATH, trust_remote_code=True)
print(“⏳ 正在加载 Base Model …”)
base = AutoModelForCausalLM.from_pretrained(
MODEL_PATH, torch_dtype=torch.float16, device_map=“auto”, trust_remote_code=True
)
print(“⏳ 正在加载 LoRA Adapter …”)
model = PeftModel.from_pretrained(base, LORA_PATH)
model.eval()
print(“✅ 模型加载完成”)
class ChatRequest(BaseModel):
message: str
max_tokens: int = 512
temperature: float = 0.7
@app.get(“/health”)
async def health():
return {“status”: “ok”, “model”: “Qwen3-0.6B+LoRA”}
@app.post(“/chat”)
async def chat(req: ChatRequest):
messages = [{“role”: “user”, “content”: req.message}]
text = tok.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
inputs = tok(text, return_tensors=“pt”).to(model.device)
with torch.no_grad():
outputs = model.generate(
**inputs, max_new_tokens=req.max_tokens,
temperature=req.temperature, do_sample=True,
)
reply = tok.decode(outputs[0][inputs[“input_ids”].shape[1]:], skip_special_tokens=True)
return {“reply”: reply}
@app.post(“/chat/stream”)
async def chat_stream(req: ChatRequest):
messages = [{“role”: “user”, “content”: req.message}]
text = tok.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
inputs = tok(text, return_tensors=“pt”).to(model.device)
streamer = TextIteratorStreamer(tok, skip_prompt=True, skip_special_tokens=True)
generation_kwargs = dict(
**inputs, max_new_tokens=req.max_tokens,
temperature=req.temperature, do_sample=True, streamer=streamer,
)
thread = Thread(target=model.generate, kwargs=generation_kwargs)
thread.start()
async def event_generator():
for token in streamer:
yield f"data: {json.dumps({'token': token})}\\n\\n"
yield "data: [DONE]\\n\\n"
return StreamingResponse(event_generator(), media_type="text/event-stream")
关键设计决策:模型在启动时一次性加载并常驻内存,避免每次请求重新加载带来的数秒延迟。/chat/stream 端点使用 TextIteratorStreamer 将模型生成过程异步化——模型在后台线程中生成,主线程通过 SSE 将 token 逐个推送给前端。
4.3 前端联调
前端通过 fetch + ReadableStream 消费 SSE 流(相比 EventSource 更灵活,支持 POST 请求):
javascript
async function streamChat(message) {
const response = await fetch(“http://localhost:8000/chat/stream”, {
method: “POST”,
headers: { “Content-Type”: “application/json” },
body: JSON.stringify({ message, max_tokens: 512 }),
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
let buffer = “”;
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split("\\n\\n");
buffer = lines.pop();
for (const line of lines) {
if (!line.startsWith("data: ")) continue;
const payload = line.slice(6);
if (payload === "[DONE]") return;
const data = JSON.parse(payload);
process.stdout.write(data.token);
}
}
}
这种流式输出的感知体验远优于等待完整响应——首 token 在约 200ms 内出现,用户看到的是逐字输出的实时效果,而非数秒白屏。
4.4 生产部署注意事项
鉴权:在 FastAPI 中添加 API Key 验证中间件,或使用 Nginx 反向代理层实现鉴权。
反向代理:Nginx 需要配置 proxy_buffering off 以支持 SSE 流式传输。
并发控制:单卡部署时,设置最大并发数避免显存溢出。可以在 FastAPI 层使用 asyncio.Semaphore 限制同时进行的生成请求数量。
监控:记录每次请求的 token 消耗、延迟、模型版本,便于后续分析和容量规划。
五、总结
从零搭建一个完整的大模型应用,核心流程可以总结为五个步骤:
第一步:选部署工具。 个人开发用 Ollama 快速验证,生产服务用 vLLM 扛并发。
第二步:定量化档位。 根据显卡显存选择量化方案——8GB 选 Q4_K_M,16GB 选 Q4_K_M 或 FP8,24GB 选 AWQ INT4。
第三步:准备微调数据。 80% 的精力花在数据上,500-2000 条高质量 Alpaca 格式样本即可让模型学会一个特定任务。
第四步:LLaMA-Factory 微调。 LoRA 秩 8-16,学习率 2e-4,训练 3 轮,产出 10-50MB 的适配器文件。
第五步:FastAPI 服务化 + 前端联调。 模型常驻内存,SSE 流式输出,前后端通过 HTTP + SSE 完成实时交互。
2026 年的大模型本地部署已经从“只有大厂玩得起”进入“消费级显卡也能跑”的阶段。真正拉开差距的不是模型选型,而是工程细节——量化档位是否算对、数据质量是否达标、流式输出是否顺畅、并发控制是否到位。把这五个步骤的每一个细节做到位,才是真正的竞争力。
网硕互联帮助中心




评论前必须登录!
注册