AI大模型基础知识
AI大模型基础知识
AI大模型基础知识
本文档介绍大语言模型(LLM)的基础知识,包括命名规则、技术指标和使用细节。
目录
1. 模型命名规则
1.1 完整命名结构
大模型通常遵循以下命名规范:
1
模型系列-版本号-规模-训练类型-量化方法-精度-其他特性
1.2 命名示例解析
示例1:Qwen2.5-1.5B-Instruct
1
2
3
Qwen2.5 - 1.5B - Instruct
↓ ↓ ↓
模型系列 规模 训练类型
部分 | 含义 | 说明 |
---|---|---|
Qwen2.5 | 模型系列和版本 | 通义千问第2.5代 |
1.5B | 参数量 | 15亿(Billion)参数 |
Instruct | 训练类型 | 经过指令微调 |
示例2:Qwen2.5-7B-Instruct-GPTQ-Int4
1
2
3
Qwen2.5 - 7B - Instruct - GPTQ - Int4
↓ ↓ ↓ ↓ ↓
系列 规模 指令微调 量化方法 量化精度
1.3 常见后缀含义
训练类型后缀
后缀 | 含义 | 特点 | 适用场景 |
---|---|---|---|
-Base | 基础预训练模型 | 只完成预训练,擅长续写 | 需要进一步微调 |
-Instruct | 指令微调模型 | 能理解和执行指令 | 对话、问答、任务执行 ⭐ |
-Chat | 对话优化模型 | 专门优化对话能力 | 聊天机器人 |
-Code | 代码专用模型 | 针对代码生成优化 | 编程助手 |
-Math | 数学专用模型 | 针对数学问题优化 | 数学解题 |
-VL | 视觉语言模型 | 支持图像理解 | 多模态应用 |
量化方法后缀
后缀 | 含义 | 显存占用 | 质量损失 |
---|---|---|---|
-GPTQ | GPTQ量化算法 | 减少75% | 很小 |
-AWQ | AWQ量化算法 | 减少75% | 极小 |
-GGUF / -GGML | llama.cpp格式 | 减少50-75% | 可调 |
-Int4 | 4位整数量化 | 最小 | 较小 |
-Int8 | 8位整数量化 | 减少50% | 极小 |
Base vs Instruct 对比
1
2
3
4
5
6
7
# Base模型(续写模式)
输入:"今天天气真好"
输出:",阳光明媚,微风拂面,正是出游的好时节..." # 自动续写
# Instruct模型(指令模式)
输入:"今天天气真好"
输出:"是的!您想聊聊天气,还是有什么计划吗?" # 理解为对话
1.4 多后缀组合规则
当模型使用多种技术时,后缀会组合在一起:
排列顺序
1
模型系列 → 版本号 → 规模 → 训练类型 → 量化方法 → 量化精度
实际示例
完整模型名 | 各部分含义 |
---|---|
Qwen2.5-7B | 系列-规模(基础版) |
Qwen2.5-7B-Instruct | + 指令微调 |
Qwen2.5-7B-Instruct-GPTQ | + GPTQ量化 |
Qwen2.5-7B-Instruct-GPTQ-Int4 | + 4位精度 |
CodeLlama-13B-Instruct-AWQ | 代码+指令+AWQ量化 |
Llama-2-7B-Chat-GGUF-Q4_K_M | 对话+GGUF格式+4位混合量化 |
1.5 参数规模对照表
规模标识 | 参数量 | 显存需求(FP16) | 显存需求(Int4) | 适用设备 |
---|---|---|---|---|
0.5B | 5亿 | ~1-2 GB | ~0.5 GB | 手机、边缘设备 |
1.5B | 15亿 | ~3-4 GB | ~1 GB | 入门级GPU |
3B | 30亿 | ~6-8 GB | ~2 GB | 消费级GPU |
7B | 70亿 | ~14-16 GB | ~4-5 GB | RTX 3090/4090 |
13B | 130亿 | ~26-32 GB | ~8-10 GB | 专业GPU |
30B | 300亿 | ~60 GB | ~15 GB | A100等 |
70B | 700亿 | ~140 GB | ~35 GB | 多卡并行 |
2. 模型主要技术指标
2.1 核心指标概览
指标类别 | 关键指标 | 重要性 |
---|---|---|
规模指标 | 参数量、模型大小 | ⭐⭐⭐⭐⭐ |
长度指标 | 上下文长度、输入输出限制 | ⭐⭐⭐⭐⭐ |
性能指标 | 推理速度、吞吐量 | ⭐⭐⭐⭐ |
质量指标 | 基准测试得分、困惑度 | ⭐⭐⭐⭐ |
资源指标 | 显存占用、计算需求 | ⭐⭐⭐⭐⭐ |
2.2 上下文长度(Context Length)⭐
这是最重要的指标之一!
定义
1
2
上下文长度 = 系统提示词 + 历史对话 + 当前输入 + 模型输出
└────────── 所有这些加起来不能超过限制 ──────────┘
主流模型对比
模型 | 上下文长度 | 约等于字数(中文) | 说明 |
---|---|---|---|
GPT-3.5 | 4K tokens | ~3千字 | 早期版本 |
GPT-3.5-16K | 16K tokens | ~1.2万字 | 扩展版 |
GPT-4 | 8K tokens | ~6千字 | 标准版 |
GPT-4-32K | 32K tokens | ~2.4万字 | 长文本版 |
GPT-4-Turbo | 128K tokens | ~9.6万字 | 超长版 |
Claude 2 | 100K tokens | ~7.5万字 | 长文本专家 |
Claude 3 | 200K tokens | ~15万字 | 极长文本 |
Qwen2.5 | 32K - 128K | 2.4万 - 9.6万字 | 取决于版本 |
Llama 2 | 4K tokens | ~3千字 | 标准版 |
Llama 3 | 8K tokens | ~6千字 | 标准版 |
Llama 3.1 | 128K tokens | ~9.6万字 | 长文本版 |
Token与字符换算
1
2
3
4
5
6
7
8
9
中文:1个汉字 ≈ 1.3-1.5 tokens
1000 tokens ≈ 650-750个汉字
英文:1个单词 ≈ 1-2 tokens
1000 tokens ≈ 750个单词
示例:
"我爱编程" → 约5-6 tokens
"I love programming" → 约4-5 tokens
2.3 输入输出Token限制
限制类型
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
┌─────────────────────────────────────────┐
│ 最大上下文长度 (Max Context Length) │
│ 例如:32,768 tokens (32K) │
├─────────────────────────────────────────┤
│ │
│ ┌────────────────────────────────┐ │
│ │ 输入部分 (Input Tokens) │ │
│ │ - 系统提示词 │ │
│ │ - 历史对话 │ │
│ │ - 当前用户输入 │ │
│ │ 无明确限制,但占用上下文空间 │ │
│ └────────────────────────────────┘ │
│ + │
│ ┌────────────────────────────────┐ │
│ │ 输出部分 (Output Tokens) │ │
│ │ max_new_tokens = 2048-4096 │ │
│ │ ⭐ 通常有单独的限制 │ │
│ └────────────────────────────────┘ │
│ │
│ = 总和不能超过上下文长度 │
└─────────────────────────────────────────┘
典型配置示例
1
2
3
4
5
6
7
8
最大上下文 = 32,768 tokens (32K)
分配示例:
├─ 系统提示词: 200 tokens
├─ 历史对话: 5,000 tokens
├─ 当前用户输入: 2,000 tokens
├─ 可用于输出: 24,568 tokens
└─ 实际输出限制: 2,048 tokens (max_new_tokens设置)
2.4 性能指标
推理速度(Tokens per Second)
速度 | 体验效果 | 应用场景 |
---|---|---|
10-20 t/s | 缓慢打字 | 大模型/弱GPU |
30-50 t/s | 流畅打字 | 标准配置 ⭐ |
50-100 t/s | 快速生成 | 高性能GPU |
100+ t/s | 几乎瞬间 | 小模型/专用硬件 |
其他性能指标
- 首Token延迟(TTFT):从提交到开始输出的时间
- 良好:< 1秒
- 可接受:1-3秒
- 较慢:> 3秒
- 吞吐量(Throughput):每秒处理的请求数
- 重要性:影响服务器负载能力
2.5 质量指标
困惑度(Perplexity, PPL)
1
2
3
4
5
6
困惑度越低 = 模型预测越准确
典型值:
- 优秀:PPL < 10
- 良好:PPL 10-20
- 一般:PPL 20-50
基准测试
测试名称 | 评估内容 | 满分 |
---|---|---|
MMLU | 多任务语言理解(学术能力) | 100% |
C-Eval | 中文综合能力 | 100% |
HumanEval | 代码生成能力 | 100% |
GSM8K | 数学推理能力 | 100% |
BBH | 复杂推理能力 | 100% |
TruthfulQA | 真实性和准确性 | 100% |
2.6 资源消耗
显存占用计算
1
2
3
4
5
6
7
8
9
10
11
12
13
14
基础公式(FP16精度):
显存需求(GB) ≈ 参数量(B) × 2
示例:
- 1.5B模型:1.5 × 2 = 3 GB
- 7B模型: 7 × 2 = 14 GB
- 14B模型: 14 × 2 = 28 GB
量化后(Int4):
显存需求(GB) ≈ 参数量(B) × 0.5
示例:
- 7B-Int4: 7 × 0.5 = 3.5 GB
- 14B-Int4: 14 × 0.5 = 7 GB
实际显存占用(包含推理开销)
模型规模 | FP16 | Int8 | Int4 | 推荐显卡 |
---|---|---|---|---|
1.5B | 4 GB | 2 GB | 1 GB | GTX 1660以上 |
3B | 8 GB | 4 GB | 2 GB | RTX 3060 |
7B | 16 GB | 8 GB | 5 GB | RTX 3090/4070Ti |
13B | 32 GB | 16 GB | 10 GB | RTX 4090/A5000 |
30B | 60 GB | 30 GB | 18 GB | A100 40GB×2 |
70B | 140 GB | 70 GB | 40 GB | A100 80GB×2 |
3. 模型使用细节
3.1 模型的”记忆”机制
核心原理
❌ 模型本身没有对话缓存能力!
模型是无状态的,每次生成都是”失忆”的。
✅ 通过每次传入完整历史来”伪装”记忆
工作流程
1
2
3
4
5
6
7
8
9
10
# 每次对话都构建完整历史(代码片段)
conversation = [
{'role': 'system', 'content': enhanced_system_message},
]
# ⭐ 添加所有历史对话
for query_h, response_h in history:
conversation.append({'role': 'user', 'content': query_h})
conversation.append({'role': 'assistant', 'content': response_h})
# 添加当前问题
conversation.append({'role': 'user', 'content': query})
多轮对话示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 第一轮对话
输入:[系统提示] + [用户:"你好"]
输出:"你好!有什么可以帮你的?" # 生成了15个tokens
# 第二轮对话(重新输入所有历史)
输入:[系统提示]
+ [用户:"你好"]
+ [助手:"你好!有什么可以帮你的?"] ← 上一轮的完整内容
+ [用户:"我叫小明"] ← 新输入
输出:"很高兴认识你,小明!" # 只返回新生成的20个tokens
# 第三轮对话(再次重新输入所有历史)
输入:[系统提示]
+ [用户:"你好"]
+ [助手:"你好!有什么可以帮你的?"]
+ [用户:"我叫小明"]
+ [助手:"很高兴认识你,小明!"]
+ [用户:"我叫什么名字?"] ← 新输入
输出:"你叫小明。" # 能回答是因为历史中包含这个信息
Token消耗增长
1
2
3
4
5
6
第1轮:100 tokens (输入) + 15 tokens (输出) = 115 tokens
第2轮:100 + 15 + 50 (输入) + 20 tokens (输出) = 185 tokens
第3轮:100 + 15 + 50 + 20 + 30 (输入) + 10 (输出) = 225 tokens
第10轮:可能达到数千tokens
⚠️ 随着对话进行,token消耗会持续增长!
3.2 新旧内容识别机制
方法1:skip_prompt=True(推荐)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# TextIteratorStreamer 自动跳过输入部分
streamer = TextIteratorStreamer(
tokenizer=tokenizer,
skip_prompt=True, # ⭐ 自动识别并跳过input_ids部分
timeout=60.0,
skip_special_tokens=True
)
# 使用流式输出器
generation_kwargs = dict(
input_ids=inputs["input_ids"],
max_new_tokens=max_new_tokens,
streamer=streamer,
...
)
# 只返回新生成的token
for new_text in streamer:
yield new_text # 逐字输出(打字机效果)
方法2:手动切片
1
2
3
4
5
6
7
8
9
# 如果不使用streamer
output = model.generate(input_ids=input_ids, max_new_tokens=2048)
# output包含:input_ids + 新生成的tokens
input_length = input_ids.shape[1] # 输入长度:例如1300
new_tokens = output[:, input_length:] # 切片取新部分
# 解码新部分
new_text = tokenizer.decode(new_tokens[0])
自回归生成过程
1
2
3
4
5
6
7
8
9
10
11
12
13
# 模型内部生成机制(简化版)
input_tokens = [1, 2, 3, ..., 1300] # 输入的1300个token
# 逐个生成新token
步骤1: input=[1,2,...,1300] → 预测并生成 token_1301
步骤2: input=[1,2,...,1300,1301] → 预测并生成 token_1302
步骤3: input=[1,2,...,1301,1302] → 预测并生成 token_1303
步骤4: input=[1,2,...,1302,1303] → 预测并生成 token_1304
...
步骤2048: 停止(达到max_new_tokens限制)
# streamer只返回新生成的部分:
[1301, 1302, 1303, ..., 3348]
3.3 流式输出机制详解
什么是流式输出?
流式输出(Streaming Output)是指在模型生成过程中,实时逐个返回生成的token,而不是等待全部生成完毕后一次性返回。
1
2
3
4
5
6
7
8
9
10
# 项目中使用的流式输出实现
streamer = TextIteratorStreamer(tokenizer=tokenizer, skip_prompt=True)
# 在单独的线程中运行模型生成
thread = Thread(target=model.generate, kwargs=generation_kwargs)
thread.start()
# 实时逐个返回生成的token
for new_text in streamer:
yield new_text # 打字机效果
对比:流式 vs 非流式
1
2
3
4
5
6
7
8
# 非流式输出(批量返回)
output = model.generate(input_ids, max_new_tokens=2048)
result = tokenizer.decode(output)
return result # 一次性返回完整结果,用户需等待10秒
# 流式输出(逐步返回)
for token in streamer:
yield token # 每生成一个就返回一个,0.5秒就有反馈
流式输出的优点 ✅
1. 用户体验极佳 ⭐⭐⭐⭐⭐
打字机效果,即时反馈
1
2
3
4
5
6
非流式输出:
用户等待 ────── 5秒 ────── [完整文本突然出现]
流式输出:
用户等待 0.3秒 → "我" "是" "一" "个" "AI" "助" "手" ...
└─ 立即看到反馈,焦虑感降低
心理学优势:
- 用户知道系统在工作,不会觉得卡死
- 可以边生成边阅读,节省总体时间
- 类似ChatGPT的体验,符合用户期望
2. 感知延迟显著降低 ⭐⭐⭐⭐⭐
指标 | 非流式 | 流式 | 改善 |
---|---|---|---|
首次反馈 | 10秒 | 0.5秒 | 20倍 |
用户焦虑 | 高 | 低 | 显著改善 |
可读体验 | 突兀 | 自然 | 更好 |
1
2
3
4
5
# 非流式:首Token延迟 + 全部生成时间
总等待时间 = 0.5秒 + 10秒 = 10.5秒 ❌ 用户需要等10.5秒
# 流式:只需要首Token延迟
首次反馈 = 0.5秒 ✅ 用户0.5秒就看到输出开始
3. 支持提前终止 ⭐⭐⭐⭐
1
2
3
4
# 用户可以随时停止生成
for token in streamer:
if user_clicked_stop:
break # 立即停止,节省计算资源和时间
实际场景:
1
2
3
4
5
6
7
生成中:"今天我们来聊聊量子物理,首先从薛定谔的猫说起..."
用户想法:"这不是我要的内容!"
用户操作:点击停止按钮 ✅ 立即停止,重新提问
如果是非流式:
❌ 必须等10秒生成完毕才能看到内容
❌ 浪费了10秒和大量计算资源
4. 内存效率更高 ⭐⭐⭐
1
2
3
4
5
6
7
# 流式输出:处理一个token就释放
for token in streamer:
yield token # 内存中只保存当前token
# 非流式输出:保存完整结果
full_output = generate_all_tokens() # 2048个tokens
# 内存中保存完整的2048个tokens,然后一次性返回
5. 适合长文本生成 ⭐⭐⭐⭐⭐
1
2
3
4
5
6
7
8
9
生成5000字文章:
非流式:等待30秒 → [文章出现]
↓
用户体验:这是不是死机了?😰
流式:0.5秒开始 → "AI..." → "是..." → "人工智能..."
↓
用户体验:正在生成,可以边看边读 😊
6. 网络传输优化
1
2
3
4
5
6
7
# 流式传输可以边生成边发送
Token 1-10: 生成 0.3秒 → 立即发送 → 用户看到
Token 11-20: 生成 0.3秒 → 立即发送 → 用户看到
...
# 非流式需要等全部生成完
Token 1-2048: 生成 10秒 → 一次性发送 → 用户才看到
流式输出的缺点 ❌
1. 实现复杂度高 ⭐⭐⭐⭐
需要多线程/异步处理
1
2
3
4
5
6
7
8
9
10
11
12
# 流式实现(复杂)
# 需要单独的线程来运行模型
thread = Thread(target=model.generate, kwargs=generation_kwargs)
thread.start()
# 主线程从streamer中读取结果
for new_text in streamer:
yield new_text
# 非流式实现(简单)
output = model.generate(**kwargs)
return tokenizer.decode(output) # 一行搞定 ✅
潜在问题:
- 线程同步问题
- 异常处理复杂
- 资源清理困难
2. 难以进行整体后处理 ⭐⭐⭐⭐⭐
1
2
3
4
5
6
7
8
9
# 非流式:可以对完整文本进行处理
full_text = generate()
formatted_text = format_markdown(full_text) # 格式化
cleaned_text = remove_errors(formatted_text) # 清理
return cleaned_text # ✅ 返回处理后的完美结果
# 流式:每个token立即返回,无法整体处理
for token in streamer:
yield token # ❌ 已经发出去了,无法修改
实际问题示例:
1
2
3
4
5
6
7
8
9
10
11
场景1:Markdown格式化
生成:"### 标题\n\n**加粗" # 加粗标签不完整
返回:用户已经看到了 # ❌ 无法修正
场景2:敏感词过滤
生成:"这个方法很傻X"
返回:已经输出 "很傻" # ❌ "X" 还没生成,无法提前过滤
场景3:代码块识别
生成:"```python\nprint("
返回:已经输出,无法确定是否是完整代码块
3. 错误处理非常困难 ⭐⭐⭐⭐
1
2
3
4
5
6
7
8
9
# 流式输出中途出错
输出:"今天天气很好,我们来聊聊"
[突然报错:CUDA out of memory]
结果:用户看到半截内容,体验不佳 ❌
# 非流式出错
[生成过程出错]
返回:"抱歉,生成失败,请重试"
结果:用户收到清晰的错误提示 ✅
回滚困难:
1
2
3
已输出:200个token
第201个token生成失败
问题:无法撤回前面200个token ❌
4. 无法提前知道内容长度 ⭐⭐⭐
1
2
3
4
5
6
7
8
9
10
// 前端问题
// 非流式:可以预留空间
<div style="height: 500px"> // 知道内容高度
{full_content}
</div>
// 流式:高度不断变化
<div style="height: auto"> // 页面不断跳动
{streaming_content} // 用户体验可能不佳
</div>
5. 网络连接要求高 ⭐⭐⭐
1
2
3
4
5
6
7
# 流式需要保持长连接
连接时间:30秒(生成期间持续占用)
网络要求:稳定,不能断开
# 非流式只需短连接
连接时间:0.1秒(传输结果)
网络要求:低,可以重试
问题场景:
1
2
3
用户网络不稳定:
流式:断开 → 内容丢失 → 需要重新生成 ❌
非流式:断开 → 重传结果 → 快速恢复 ✅
6. 难以缓存 ⭐⭐⭐
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 非流式:可以轻松缓存
cache_key = hash(user_input)
if cache_key in cache:
return cache[cache_key] # 立即返回 ✅
else:
result = generate()
cache[cache_key] = result # 缓存完整结果
return result
# 流式:缓存复杂
# 需要缓存token序列,且难以判断何时完成 ❌
for token in streamer:
cache.append(token) # 边生成边缓存?
yield token
7. 并发处理复杂 ⭐⭐⭐
1
2
3
4
5
6
7
8
9
10
11
# 非流式:简单的请求队列
queue = [request1, request2, request3]
for req in queue:
result = process(req)
send(result)
# 流式:每个用户需要独立的streamer和线程
thread1 = Thread(streamer1) # 用户1
thread2 = Thread(streamer2) # 用户2
thread3 = Thread(streamer3) # 用户3
# 资源消耗更大 ❌
完整对比表
维度 | 流式输出 | 非流式输出 | 推荐 |
---|---|---|---|
用户体验 | ⭐⭐⭐⭐⭐ 极佳 | ⭐⭐ 较差 | 流式 |
感知延迟 | ⭐⭐⭐⭐⭐ 很低 | ⭐⭐ 很高 | 流式 |
实现难度 | ⭐⭐ 复杂 | ⭐⭐⭐⭐⭐ 简单 | 非流式 |
错误处理 | ⭐⭐ 困难 | ⭐⭐⭐⭐⭐ 简单 | 非流式 |
后处理能力 | ⭐ 几乎不可能 | ⭐⭐⭐⭐⭐ 灵活 | 非流式 |
内存效率 | ⭐⭐⭐⭐ 高 | ⭐⭐⭐ 一般 | 流式 |
缓存能力 | ⭐⭐ 困难 | ⭐⭐⭐⭐⭐ 简单 | 非流式 |
长文本生成 | ⭐⭐⭐⭐⭐ 优秀 | ⭐⭐ 较差 | 流式 |
网络要求 | ⭐⭐⭐ 较高 | ⭐⭐⭐⭐ 低 | 非流式 |
可中断性 | ⭐⭐⭐⭐⭐ 可以 | ⭐ 不可以 | 流式 |
使用场景建议
适合使用流式输出:
1
2
3
4
5
6
✅ 对话式应用(聊天机器人)
✅ 长文本生成(文章、故事)
✅ 实时性要求高的应用
✅ 用户需要即时反馈
✅ 内容较长(>100 tokens)
✅ 交互式应用(可以随时打断)
适合使用非流式输出:
1
2
3
4
5
6
✅ API接口(需要整体返回)
✅ 批处理任务
✅ 需要后处理的内容(格式化、审核)
✅ 需要缓存的场景
✅ 短文本生成(<50 tokens)
✅ 需要稳定网络传输
混合方案:
1
2
3
4
5
6
7
8
# 根据内容长度自动选择
if max_new_tokens < 100:
# 短文本:非流式
return generate_complete()
else:
# 长文本:流式
for token in stream_generate():
yield token
流式输出优化技巧
优化1:批量返回token
1
2
3
4
5
6
7
8
9
# 减少传输次数,提高效率
batch = []
for token in streamer:
batch.append(token)
if len(batch) >= 5: # 每5个token一起返回
yield ''.join(batch)
batch = []
if batch: # 返回剩余的
yield ''.join(batch)
优化2:添加心跳机制
1
2
3
4
5
# 保持长连接稳定
for i, token in enumerate(streamer):
yield token
if i % 50 == 0:
send_heartbeat() # 每50个token发送心跳
优化3:错误恢复
1
2
3
4
5
6
7
8
9
# 优雅地处理异常
try:
for token in streamer:
yield token
except TimeoutError:
yield "\n\n[生成超时,请重试]"
except Exception as e:
yield f"\n\n[生成中断: {str(e)}]"
logger.error(f"Stream error: {e}")
优化4:前端配合
1
2
3
4
5
6
7
8
9
10
11
// 自动滚动到底部
onTokenReceived = (token) => {
appendText(token);
scrollToBottom(); // 跟随最新内容
}
// 显示生成状态
<div>
{streamingText}
{isGenerating && <Spinner />} // 显示正在生成
</div>
项目实现分析
本项目使用的流式实现(第665-689行):
1
2
3
4
5
6
7
8
9
10
11
12
streamer = TextIteratorStreamer(
tokenizer=tokenizer,
skip_prompt=True, # ✅ 自动跳过输入
timeout=60.0, # ✅ 设置超时
skip_special_tokens=True # ✅ 跳过特殊token
)
thread = Thread(target=model.generate, kwargs=generation_kwargs)
thread.start() # ✅ 独立线程运行
for new_text in streamer:
yield new_text # ✅ 逐个返回
优点:
- ✅ 用户体验好(打字机效果)
- ✅ 适合对话场景
- ✅ 支持长文本生成
- ✅ 使用官方TextIteratorStreamer,稳定可靠
进一步改进建议:
1
2
3
4
5
6
7
8
9
10
11
# 添加更完善的异常处理
try:
for new_text in streamer:
if new_text: # 过滤空字符串
yield new_text
except TimeoutError:
yield "\n[生成超时]"
except Exception as e:
yield f"\n[错误: {str(e)}]"
finally:
thread.join(timeout=5) # 确保线程结束
总结
流式输出的核心价值:用户体验
- 在对话应用中几乎是必需的 ⭐
- 给用户”活着”的感觉
- 符合现代AI助手的交互模式(ChatGPT、Claude等)
代价:实现和维护的复杂度
- 需要处理并发、异常、网络等问题
- 难以进行整体内容处理
- 错误恢复较困难
本项目选择流式输出是正确的! 因为这是一个对话应用,用户体验是第一位的。打字机效果大大降低了用户的等待焦虑,提供了流畅的交互体验。
3.4 完整数据流示例
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
┌──────────────────────────────────────────────────────┐
│ 用户界面操作 │
├──────────────────────────────────────────────────────┤
│ 1. 拖动滑块: max_new_tokens = 3000 │
│ 2. 输入: "帮我写一篇关于AI的文章" │
│ 3. 点击发送 │
└──────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────┐
│ 后端处理流程 │
├──────────────────────────────────────────────────────┤
│ │
│ 步骤1: 构建完整对话历史 │
│ ┌─────────────────────────────────┐ │
│ │ conversation = [ │ │
│ │ system: "你是AI助手...", │ │
│ │ user: "你好", │ │
│ │ assistant: "你好!", │ │
│ │ user: "帮我写一篇关于AI的文章" │ ← 新输入 │
│ │ ] │ │
│ └─────────────────────────────────┘ │
│ │
│ 步骤2: Tokenize │
│ ┌─────────────────────────────────┐ │
│ │ text → tokens │ │
│ │ input_ids = [1,2,3,...,1500] │ │
│ │ (总共1500个token) │ │
│ └─────────────────────────────────┘ │
│ │
│ 步骤3: 模型生成 │
│ ┌─────────────────────────────────┐ │
│ │ model.generate( │ │
│ │ input_ids=[1,...,1500], │ │
│ │ max_new_tokens=3000 │ │
│ │ ) │ │
│ │ │ │
│ │ 自回归生成3000个新token: │ │
│ │ [1501,1502,1503,...,4500] │ │
│ └─────────────────────────────────┘ │
│ │
│ 步骤4: 流式输出(skip_prompt=True) │
│ ┌─────────────────────────────────┐ │
│ │ streamer只返回: │ │
│ │ [1501,1502,...,4500] │ │
│ │ (跳过了input_ids部分) │ │
│ └─────────────────────────────────┘ │
│ │
│ 步骤5: 解码为文本 │
│ ┌─────────────────────────────────┐ │
│ │ "AI是人工智能的缩写..." │ │
│ │ (约2200个中文字) │ │
│ └─────────────────────────────────┘ │
│ │
└──────────────────────────────────────────────────────┘
↓
┌──────────────────────────────────────────────────────┐
│ 前端显示 │
├──────────────────────────────────────────────────────┤
│ 打字机效果逐字显示: │
│ "AI是人工智能的缩写,代表着..." │
└──────────────────────────────────────────────────────┘
附录:快速参考表
Token限制对照表
模型 | 上下文 | 默认输出 | 最大输出 | 适用场景 |
---|---|---|---|---|
GPT-3.5 | 4K | 2K | 4K | 短对话 |
GPT-4 | 8K-128K | 2K-4K | 8K | 全场景 |
Claude 3 | 200K | 4K | 4K | 长文档分析 |
Qwen2.5 | 32K-128K | 2K | 8K | 中文对话 |
Llama 3 | 8K | 2K | 4K | 开源部署 |
显存需求速查表
模型 | FP16 | Int8 | Int4 | 推荐显卡 |
---|---|---|---|---|
1.5B | 4GB | 2GB | 1GB | GTX 1660+ |
7B | 16GB | 8GB | 5GB | RTX 3090 |
13B | 32GB | 16GB | 10GB | RTX 4090 |
参数调优速查表
参数 | 范围 | 默认值 | 效果 |
---|---|---|---|
temperature | 0.1-2.0 | 0.7 | 越低越确定,越高越创意 |
top_p | 0.1-1.0 | 0.9 | 核采样阈值 |
top_k | 1-100 | 50 | 候选词数量 |
repetition_penalty | 1.0-2.0 | 1.1 | 避免重复 |
max_new_tokens | 50-8192 | 2048 | 输出长度 |
总结
关键要点
- 模型命名:遵循”系列-规模-训练类型-量化方法”的规则
- Instruct模型:对话应用必选,能理解和执行指令
- 上下文长度:决定能记住多少对话历史
- 流式输出:提供打字机效果,大幅提升用户体验
本文由作者按照 CC BY 4.0 进行授权