跳到主要内容

核心概念一网打尽

上两篇咱们聊了大模型的基础知识和工作原理。这篇来系统梳理一下大模型开发中会频繁遇到的核心概念。

这些概念不是面试背题用的,而是真的会影响你的开发。比如:

  • 不懂 Token,你就不知道为什么 API 有时候返回的内容被截断了
  • 不懂上下文窗口,你就不知道为什么模型"忘记"了之前的对话
  • 不懂 Temperature,你就不知道为什么同样的问题每次回答都不一样
  • 不懂 MoE,你就不知道为什么 DeepSeek 这么便宜还这么厉害

搞清楚这些概念,你才能更好地控制模型的行为,写出更有效的 Prompt,做出更合理的技术选型。

Token:大模型眼中的"文字"

什么是 Token

核心概念

Token 是大模型处理文本的最小单位。

你可能以为大模型是一个字一个字、或者一个词一个词处理的。实际上不是。大模型处理的是 Token——一种介于字和词之间的单位。

举个例子:

原文分词结果(Token)
你好世界你好 / 世界
Hello WorldHello / ␣World
programmingprogram / ming
ChatGPTChat / G / PT
人工智能人工 / 智能
今天天气不错今天 / 天气 / 不错
functionfunction
beautifulbeaut / iful

可以看到:

  • 常见的词往往是一个 Token
  • 不常见的词可能被拆成多个 Token
  • 英文单词可能被拆成词根和后缀
  • 空格通常会被算进 Token 里

为什么要用 Token 而不是字/词

为什么不用"字"作为单位?

如果用字符作为单位,英文就是 26 个字母加一些标点。词表太小,模型需要很长的序列才能表达一个词,效率太低。

比如 "programming" 需要 11 个字符,而作为 Token 只需要 2 个(program + ming)。

对于中文,如果用单字作为单位,词表就是几千个汉字。虽然可行,但会丢失很多词语级别的语义信息。"人工智能"被拆成"人"、"工"、"智"、"能"四个字,模型需要花额外的努力去理解这是一个整体。

为什么不用"词"作为单位?

如果用词作为单位,英文可能需要几十万个词(加上各种变体、专有名词),词表太大,而且会遇到大量"未登录词"(词表里没有的词)。

遇到新词怎么办?比如 "ChatGPT" 这个词,2022 年之前根本不存在。如果用词表,就只能当作未知词处理。

Token 是一个折中方案

Token 采用 子词分词(Subword Tokenization) 的方式,把常见词保持原样,把不常见的词拆成更小的单位。

最常用的子词分词算法是 BPE(Byte Pair Encoding,字节对编码)。它的基本思想是:

  1. 从最小单位(字符或字节)开始
  2. 统计训练语料中哪两个相邻单位最常一起出现
  3. 把最常见的组合合并成一个新单位
  4. 重复步骤 2-3,直到词表达到指定大小

通过这种方式,BPE 能自动学会:

  • 高频词保持完整(如 "the"、"is"、"今天")
  • 低频词被拆分(如 "programming" → "program" + "ming")
  • 新词也能处理(通过组合已有的子词)

BPE 算法详解

为了让你更直观地理解 BPE,咱们用一个简化的例子来走一遍流程。

假设我们的训练语料只有这些词及其出现频率:

low: 5次
lower: 2次
newest: 6次
widest: 3次

第 1 步:初始化

把每个词拆成字符,加上词尾标记 </w>

l o w </w>: 5
l o w e r </w>: 2
n e w e s t </w>: 6
w i d e s t </w>: 3

初始词表是所有字符:{l, o, w, e, r, n, s, t, i, d, </w>}

第 2 步:统计相邻字符对

统计所有相邻字符对的出现频率:

  • e s: 出现 6+3=9 次(newest 和 widest 都有)
  • s t: 出现 6+3=9 次
  • lo: 出现 5+2=7 次
  • ow: 出现 5+2=7 次
  • ...

第 3 步:合并最频繁的对

e ss t 出现次数最多。假设我们先合并 e ses

l o w </w>: 5
l o w e r </w>: 2
n e w es t </w>: 6
w i d es t </w>: 3

词表变成:{l, o, w, e, r, n, s, t, i, d, </w>, es}

第 4 步:继续合并

现在 es t 出现 9 次,合并 → est

l o w </w>: 5
l o w e r </w>: 2
n e w est </w>: 6
w i d est </w>: 3

词表变成:{l, o, w, e, r, n, s, t, i, d, </w>, es, est}

第 5 步:重复直到达到目标词表大小

继续合并高频对:

  • lolowlow</w>
  • est </w>est</w>
  • ...

最终可能得到这样的词表:

{l, o, w, e, r, n, s, t, i, d, </w>, es, est, est</w>, lo, low, low</w>, ...}

BPE 的精妙之处

通过这种方式:

  • "low" 变成了一个完整的 Token
  • "newest" 被切成 "new" + "est"
  • 即使遇到训练时没见过的词,比如 "lowest",也能切成 "low" + "est"

这就是 BPE 能处理未登录词的原因。

Token 和字符数的关系

一个非常实用的问题:1 个 Token 大约等于多少字/字符?

这取决于语言和具体的分词器,但有一些经验值:

语言大约比例
英文1 Token ≈ 4 个字符 ≈ 0.75 个单词
中文1 Token ≈ 1.5-2 个汉字
代码1 Token ≈ 3-4 个字符

所以:

  • 一篇 1000 字的中文文章,大约是 500-700 Token
  • 一篇 1000 词的英文文章,大约是 1300-1500 Token
  • 一段 100 行的代码,可能是 500-1000 Token

Token 为什么重要

Token 直接关系到两件事:能力边界使用成本

能力边界

大模型有一个上下文窗口(后面会详细讲),限制了一次能处理的 Token 数量。比如 GPT-4 Turbo 的上下文窗口是 128K Token,如果你的输入超过这个限制,要么会报错,要么早期的内容会被"遗忘"。

使用成本

API 调用按 Token 计费。输入 Token 和输出 Token 通常分开计价,输出 Token 往往更贵。

以 GPT-4 为例(价格可能已变化,仅作参考):

  • 输入:$0.03 / 1K Token
  • 输出:$0.06 / 1K Token

如果你的应用每天处理 100 万 Token,光 API 费用就要几十美元。所以优化 Token 使用量是降低成本的重要手段。

实际开发中的 Token 技巧

技巧 1:估算 Token 数量

在发送请求之前,先估算一下 Token 数量,避免超出限制或产生意外的费用。

import tiktoken

# 使用 GPT-4 的分词器
encoder = tiktoken.encoding_for_model("gpt-4")

text = "你好,请帮我写一段Python代码,实现冒泡排序算法。"
tokens = encoder.encode(text)
print(f"Token 数量: {len(tokens)}") # 输出: Token 数量: 约 20+
print(f"Token 列表: {tokens}")
print(f"解码回文本: {encoder.decode(tokens)}")

对于 Java 开发者,可以使用 jtokkit 库:

import com.knuddels.jtokkit.Encodings;
import com.knuddels.jtokkit.api.Encoding;
import com.knuddels.jtokkit.api.EncodingRegistry;
import com.knuddels.jtokkit.api.ModelType;
import java.util.List;

public class TokenCounter {
public static void main(String[] args) {
EncodingRegistry registry = Encodings.newDefaultEncodingRegistry();
Encoding encoding = registry.getEncodingForModel(ModelType.GPT_4);

String text = "你好,请帮我写一段Python代码,实现冒泡排序算法。";
List<Integer> tokens = encoding.encode(text);
System.out.println("Token 数量: " + tokens.size());
}
}

Maven 依赖:

<dependency>
<groupId>com.knuddels</groupId>
<artifactId>jtokkit</artifactId>
<version>0.6.1</version>
</dependency>

技巧 2:优化 Prompt 减少 Token

Token 越少,成本越低,响应也可能更快。但要注意,不能为了省 Token 而牺牲清晰度。

# 啰嗦版(约 50 Token)
请你帮我分析一下下面这段代码有什么问题,如果有问题的话请指出来,
并且告诉我应该怎么修改才能让代码正确运行。

# 精简版(约 20 Token)
分析以下代码的问题并给出修改建议:

技巧 3:控制输出长度

大多数 API 支持 max_tokens 参数,限制输出的最大 Token 数量。

response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "讲一个笑话"}],
max_tokens=100 # 限制输出最多 100 Token
)

如果不限制,模型可能会生成很长的回答,既浪费 Token 又可能跑题。

技巧 4:监控和分析 Token 使用

大多数 API 会在响应中返回 Token 使用情况:

{
"usage": {
"prompt_tokens": 156,
"completion_tokens": 423,
"total_tokens": 579
}
}

建立监控体系,分析 Token 消耗的分布,找出优化点。

上下文窗口:模型的"工作记忆"

什么是上下文窗口

上下文窗口(Context Window)是大模型一次能"看到"的最大 Token 数量。

你可以把它理解为模型的"工作记忆"。就像人类的短期记忆有容量限制,你没法同时记住一本书的所有内容。大模型也一样,它能同时处理的信息是有限的。

上下文窗口包括了输入输出的总和。比如一个 8K 上下文的模型:

  • 如果你输入了 6K Token,那最多只能输出 2K Token
  • 如果你想要 4K 的输出,输入就只能有 4K

主流模型的上下文窗口

不同模型的上下文窗口差异很大:

模型上下文窗口大约相当于
GPT-3.54K / 16K约 3000 / 12000 汉字
GPT-48K / 32K / 128K约 6000 / 24000 / 96000 汉字
Claude 2100K约 75000 汉字
Claude 3200K约 150000 汉字(一本小说)
Claude 3.5200K约 150000 汉字
Qwen2.58K / 32K / 128K取决于具体版本
DeepSeek-V3128K约 96000 汉字
Gemini 1.5 Pro1M / 2M约 75-150 万汉字(几本书)

可以看到,上下文窗口在快速增长。从早期的 4K 到现在的百万级别,只用了不到两年。

上下文窗口的深入理解

上下文窗口包含什么?

一次完整的对话请求中,占用上下文窗口的内容包括:

[系统提示词 System Prompt] → 几十到几百 Token
[历史对话记录] → 根据对话长度,可能占大头
- 第1轮:用户问题 + 助手回答
- 第2轮:用户问题 + 助手回答
- ...
[当前用户问题] → 几十到几百 Token
[模型生成的回答] → 这也算在窗口内!

一个常见的误区是以为上下文窗口只包含输入,实际上输出也算

多轮对话的累积效应

大模型本身是没有记忆的。每次 API 调用都是独立的。所谓的"多轮对话",是在每次请求时把之前的对话历史都带上。

{
"messages": [
{"role": "system", "content": "你是一个编程助手"},
{"role": "user", "content": "什么是冒泡排序?"},
{"role": "assistant", "content": "冒泡排序是一种简单的排序算法...(500字)"},
{"role": "user", "content": "能给我写个例子吗?"},
{"role": "assistant", "content": "当然,这是一个Python实现...(300字)"},
{"role": "user", "content": "能改成Java吗?"}
]
}

看到问题了吗?每次请求都要带上所有历史。对话越长,每次请求消耗的 Token 越多,而且是重复付费的。

假设对话了 20 轮,每轮平均 500 Token,那第 20 轮的请求就要带上 10000 Token 的历史——光是"上下文"就要花不少钱。

超出上下文窗口怎么办

方案 1:截断(Truncation)

最简单的方法,超出部分直接丢掉。但可能丢失重要信息。

def truncate_messages(messages, max_tokens=4000, encoder=None):
"""截断历史消息,保留最新的"""
if encoder is None:
encoder = tiktoken.encoding_for_model("gpt-4")

total_tokens = 0
truncated = []

# 倒序遍历,保留最新的消息
for msg in reversed(messages):
msg_tokens = len(encoder.encode(str(msg)))
if total_tokens + msg_tokens > max_tokens:
break
truncated.insert(0, msg)
total_tokens += msg_tokens

return truncated

方案 2:分块处理(Chunking + Map-Reduce)

把长文本分成多个块,每块单独处理,最后汇总结果。

长文档(50000 字)

分成 10 个块(每块 5000 字)

每块单独让模型处理(提取要点/生成摘要)

把 10 个结果汇总

再让模型对汇总结果做最终处理

这种方法叫 Map-Reduce,适合摘要、提取关键信息、分析长文档等任务。

def summarize_long_document(document, chunk_size=3000):
"""对长文档进行分块摘要"""
# 分块
chunks = split_into_chunks(document, chunk_size)

# Map: 对每个块生成摘要
summaries = []
for chunk in chunks:
summary = call_llm(f"请用100字总结以下内容:\n{chunk}")
summaries.append(summary)

# Reduce: 汇总所有摘要
combined = "\n".join(summaries)
final_summary = call_llm(f"请综合以下摘要,给出最终总结:\n{combined}")

return final_summary

方案 3:滑动窗口 + 摘要

保留最近的完整对话,对早期对话做摘要。

def manage_conversation_history(messages, max_recent=5, max_tokens=6000):
"""管理对话历史:保留最近N轮 + 早期摘要"""
if len(messages) <= max_recent * 2: # 用户+助手算两条
return messages

# 分离早期和最近的消息
recent = messages[-(max_recent * 2):]
early = messages[:-(max_recent * 2)]

# 对早期消息做摘要
early_text = format_messages_as_text(early)
summary = call_llm(f"请简要概括以下对话的主要内容:\n{early_text}")

# 返回:摘要 + 最近消息
return [
{"role": "system", "content": f"之前对话摘要:{summary}"},
*recent
]

方案 4:选择更大上下文的模型

如果你的场景确实需要处理长文本,考虑选择上下文窗口更大的模型。虽然可能更贵,但省去了复杂的工程处理。

注意:大上下文 ≠ 用满了也很准

常见误解

有一个常见的误解:觉得模型支持 128K 上下文,就能完美处理 128K 的内容。

实际上,上下文越长,模型的表现可能越差。研究表明:

"Lost in the Middle"问题

有研究发现,大模型对上下文中间部分的注意力会下降。如果关键信息藏在一长段文本的中间,模型可能会忽略它。

[开头的内容] → 注意力较高
[中间的内容] → 注意力下降 ← 关键信息可能被忽略
[结尾的内容] → 注意力较高

实验验证

给模型一个 10 万 Token 的上下文,在不同位置放一个关键信息(比如一个密码),然后问模型这个信息是什么。结果发现:

  • 放在开头:准确率 90%+
  • 放在中间:准确率可能降到 50%
  • 放在结尾:准确率 85%+
实践建议
  1. 不要因为有大上下文就一股脑塞满。精选最相关的内容,控制在合理的长度
  2. 把最重要的信息放在开头或结尾
  3. 如果必须提供很长的上下文,可以在最后重复强调关键点

Temperature:控制回答的"创意度"

什么是 Temperature

Temperature 是一个控制模型输出随机性的参数,取值范围通常是 0 到 2(不同模型可能不同)。

还记得大模型是怎么生成文本的吗?它会计算每个候选词的概率,然后选择一个词输出。Temperature 影响的就是这个选择过程

  • Temperature = 0:总是选择概率最高的词,输出最确定、最"保守"
  • Temperature = 1:按照原始概率分布采样,比较均衡
  • Temperature > 1:放大低概率词的机会,输出更随机、更"疯狂"

直观理解 Temperature

用一个例子来说明。假设模型在预测"今天天气真____"的下一个词,概率分布是:

候选词原始概率
60%
不错25%
10%
糟糕5%

Temperature = 0 时

直接选概率最高的"好"。问 100 次,100 次都选"好"。

Temperature = 0.7 时

概率分布会被调整,高概率词更占优势:

候选词调整后概率
~75%
不错~18%
~5%
糟糕~2%

大多数情况选"好",偶尔选"不错"。

Temperature = 1.5 时

概率分布变得更平均:

候选词调整后概率
~45%
不错~28%
~17%
糟糕~10%

低概率词也有更多机会被选中。输出更加多样化,但也更可能出现"奇怪"的回答。

Temperature 的数学原理

如果你想了解技术细节,Temperature 是这样工作的。

标准的 Softmax 函数:

P(wi) = exp(zi) / Σexp(zj)

带 Temperature 的 Softmax:

P(wi) = exp(zi/T) / Σexp(zj/T)

其中 T 就是 Temperature。

  • 当 T < 1 时,指数函数的差异被放大,高概率词更占优势(分布更"尖锐")
  • 当 T > 1 时,指数函数的差异被缩小,概率分布更平均(分布更"平坦")
  • 当 T → 0 时,趋近于总是选概率最高的词(贪婪解码,Greedy Decoding)
  • 当 T → ∞ 时,趋近于均匀随机选择

举个具体的数学例子

假设两个候选词的原始 logit 是 z1=2, z2=1

T=1 时

  • P1 = exp(2) / (exp(2)+exp(1)) = 7.39 / (7.39+2.72) = 73%
  • P2 = exp(1) / (exp(2)+exp(1)) = 2.72 / (7.39+2.72) = 27%

T=0.5 时

  • P1 = exp(4) / (exp(4)+exp(2)) = 54.6 / (54.6+7.39) = 88%
  • P2 = 12%

T=2 时

  • P1 = exp(1) / (exp(1)+exp(0.5)) = 2.72 / (2.72+1.65) = 62%
  • P2 = 38%

可以看到,T 越小,概率差距越大;T 越大,概率差距越小。

不同场景的 Temperature 建议

参数建议
场景推荐 Temperature原因
代码生成0 - 0.3代码需要精确,不能有太多随机性
数据提取/结构化输出0 - 0.2JSON 等结构化输出需要确定性
翻译0.3 - 0.5需要准确但也要自然流畅
问答/RAG0.3 - 0.7准确性重要,但希望表达有变化
文章写作0.7 - 1.0需要一定的创意和文采
头脑风暴1.0 - 1.5需要更多发散思维
创意故事/诗歌1.2 - 1.8鼓励意想不到的内容

实际代码示例

from openai import OpenAI
client = OpenAI()

# 确定性输出(适合代码生成)
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "写一个Python冒泡排序函数"}],
temperature=0 # 每次输出一样
)

# 有一定创意(适合文章写作)
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "写一篇关于春天的短文,150字左右"}],
temperature=0.8 # 每次可能有不同的表达
)

# 发散思维(适合头脑风暴)
response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "给我一些用AI改善生活的创意点子"}],
temperature=1.3 # 更加随机,更有可能出现意外的好点子
)

常见误区

常见误区

误区 1:Temperature 高 = 模型更聪明

很多人以为调高 Temperature 能让模型更"聪明"。其实不是的。

Temperature 只影响选词的随机性,不影响模型的"智商"。调高只是让它更敢冒险选择低概率的词,不是让它更有能力。

如果模型回答的内容本来就不对,调高 Temperature 只会让它更随机地胡说八道。

误区 2:Temperature 总是要调

对于大多数应用场景,默认的 Temperature(通常是 0.7 或 1.0)就够用了。没必要过度调参。

先跑通功能,再根据实际效果微调。

Top-P 和 Top-K:另外两种采样策略

除了 Temperature,还有两个常见的参数也控制采样过程:Top-PTop-K

Top-K 采样

Top-K 是指只从概率最高的 K 个候选词中选择。

比如设置 K=3,原始概率分布:

候选词概率
50%
不错25%
15%
糟糕5%
3%
其他2%

Top-K=3 后,只考虑前 3 个词,重新归一化:

候选词调整后概率
55.6%
不错27.8%
16.6%

低概率的"糟糕"、"坏"等词被完全排除,永远不会被选中。

Top-K 的问题:K 是固定的,但不同上下文的概率分布差异很大。

比如在有些上下文中,只有 1-2 个合理选项;在另一些上下文中,可能有 10 个都合理。固定的 K 不够灵活:

  • 如果 K 太小,可能排除了合理的选项
  • 如果 K 太大,可能包含了太多不合理的选项

Top-P 采样(核采样 / Nucleus Sampling)

Top-P 是指选择累积概率达到 P 的最小候选词集合。

比如设置 P=0.9,按概率从高到低累加:

候选词概率累积概率是否保留
50%50%
不错25%75%
15%90%
糟糕5%95%
其他5%100%

前 3 个词的累积概率正好达到 90%,所以只从这 3 个词中选择。

Top-P 的优势:它是自适应的。

  • 如果概率很集中(一个词就占 95%),候选集可能只有 1 个词
  • 如果概率分散(前 10 个词每个都占 9%),候选集可能有 10 个词

这样就能根据实际的概率分布自动调整候选集大小。

Temperature、Top-P、Top-K 的比较

参数作用优点缺点
Temperature调整整体概率分布的尖锐程度简单直观可能选到极低概率的词
Top-K只保留前 K 个候选明确排除低概率词K 值固定,不够灵活
Top-P保留累积概率达 P 的候选自适应候选集大小参数含义不如 K 直观

如何组合使用

这三个参数可以单独使用,也可以组合使用:

response = client.chat.completions.create(
model="gpt-4",
messages=[{"role": "user", "content": "..."}],
temperature=0.8, # 先调整概率分布
top_p=0.9, # 再从累积概率 90% 的词中选
)

一般建议

  1. 如果只调一个,优先用 Temperature(最直观)或 Top-P(更灵活)
  2. 不建议同时调 Temperature 和 Top-P,容易产生意想不到的效果
  3. OpenAI 官方建议:调整其中一个时,把另一个设为默认值(Temperature=1 或 Top-P=1)

MoE:让大模型又大又便宜

什么是 MoE

**MoE(Mixture of Experts,混合专家)**是一种模型架构,核心思想是:不是每次推理都使用全部参数,而是动态选择部分"专家"来处理当前输入。

打个比方。传统的大模型像一个综合医院的全员会诊。不管病人来看什么病,所有科室的医生都要参与。感冒患者?内科、外科、骨科、眼科……全部到场。虽然诊断可能很全面,但效率低、成本高。

MoE 模型像一个智能分诊系统。病人来了,前台先判断是什么情况,然后只叫相关科室的专家。感冒?叫呼吸内科就行了。骨折?叫骨科。不需要所有医生都参与。

MoE 的工作原理

MoE 模型的核心组件是:

  1. 多个专家(Experts):每个专家是一个独立的小型神经网络(通常是前馈网络 FFN)
  2. 门控网络(Router/Gating Network):决定当前输入应该使用哪些专家

处理流程

输入 Token

门控网络(Router)分析输入

选择 Top-K 个最相关的专家(通常 K=2)

只激活被选中的专家进行计算

合并专家的输出

输出结果

具体例子

假设一个 MoE 模型有 8 个专家,门控网络设置为每次选 2 个:

输入: "写一段Python代码"

Router 分析: 这是编程任务

选择专家: Expert3(代码专家)和 Expert7(Python专家)

只有 Expert3 和 Expert7 参与计算

其他 6 个专家"休息"

再来一个输入:

输入: "翻译这段文字到英文"

Router 分析: 这是翻译任务

选择专家: Expert1(语言理解)和 Expert5(英语生成)

只有 Expert1 和 Expert5 参与计算

MoE 的关键指标

看 MoE 模型时,有两个关键数字:

  • 总参数(Total Parameters):所有专家加起来的参数量
  • 激活参数(Active Parameters):每次推理实际使用的参数量

以几个典型 MoE 模型为例:

模型总参数激活参数专家数每次激活
Mixtral 8x7B46.7B12.9B82
Mixtral 8x22B141B39B82
DeepSeek-V2236B21B1606
DeepSeek-V3671B37B2568
GPT-4(传闻)~1.8T~220B~16~2

DeepSeek-V3 的总参数是 671B,看起来巨大无比。但每次推理只激活 37B,实际的计算成本和一个 37B 的普通模型差不多。

MoE 的优势

优势 1:性价比极高

用较少的计算量获得大模型的效果。

DeepSeek-V3:

  • 总参数 671B 的知识容量(学得多、知道得多)
  • 实际推理成本约等于 37B 的模型(跑得快、花得少)
  • 效果接近甚至超越 GPT-4

这就是为什么 DeepSeek 的 API 价格可以比 GPT-4 便宜 10-20 倍。

优势 2:可扩展性强

传统模型要变大,计算量会等比例增加。参数翻倍,计算量也翻倍。

MoE 可以只增加专家数量,而保持每次激活的专家数量不变。从 8 个专家增加到 256 个,但每次还是只激活 8 个,计算量基本不变。

理论上可以扩展到任意大的参数量,而计算成本增长有限。

优势 3:专家专精(可能)

研究者发现,不同的专家可能会自然地学会处理不同类型的任务:

  • 有的专家更擅长代码
  • 有的专家更擅长数学
  • 有的专家更擅长某种语言
  • 有的专家更擅长逻辑推理

门控网络会自动把相关的输入路由到对应的专家,实现某种程度的"专业分工"。

MoE 的局限性

局限 1:显存占用依然很大

虽然每次只激活部分专家,但所有专家的参数都要加载到显存中。

DeepSeek-V3 激活参数只有 37B,但部署时需要加载全部 671B 参数。以 FP16 精度,显存需求超过 1.3TB——需要 16 张 80GB 的 A100 才能装下。

对于本地部署来说,MoE 模型的显存需求比同等激活参数的普通模型大得多。

局限 2:负载均衡问题

如果门控网络总是把输入路由到少数几个专家,其他专家就被"冷落"了,造成:

  • 热门专家过载,成为瓶颈
  • 冷门专家参数浪费
  • 训练不均衡

需要专门的损失函数(如负载均衡损失)来约束,让专家之间的负载尽量均匀。

局限 3:训练复杂度高

MoE 模型的训练比普通模型更复杂:

  • 需要处理专家负载均衡
  • 分布式训练时的通信开销更大
  • 调参空间更大(专家数、激活数、路由策略……)

对开发者意味着什么

如果你通过 API 调用云端模型,MoE 对你来说是透明的。你只需要知道:

  1. 看激活参数而不是总参数:评估模型的计算成本时,看激活参数更准确
  2. 性价比考量:MoE 模型往往能以更低的 API 价格提供更好的效果
  3. 本地部署要注意显存:如果想本地部署 MoE 模型,要按总参数计算显存需求

深度思考与 CoT:让模型"想清楚再说"

什么是深度思考

最近一些模型(如 DeepSeek-R1、OpenAI o1)引入了"深度思考"模式。开启这个模式后,模型会在回答之前先进行一番"内心独白",把问题分析清楚再给出答案。

这种方式能显著提升复杂推理任务的准确率,尤其是数学、逻辑、代码等需要多步推理的任务。

CoT(思维链)的本质

CoT(Chain of Thought,思维链) 是实现深度思考的一种技术。核心思想是:让模型像人一样,一步一步地推理,把中间过程写出来,而不是直接跳到答案。

为什么这样有效?

想象你做一道复杂的数学题。如果直接心算,很容易出错。但如果在草稿纸上一步步写出来,每一步都是简单的计算,最后把结果串起来,正确率就高很多。

大模型也是一样。直接生成最终答案,中间的推理是"隐式"的,容易出错。把推理过程"显式"地写出来,相当于强迫模型一步一步想清楚。

具体例子对比

问题:一个水池有两个进水管,A 管单独注满需要 4 小时,B 管单独注满需要 6 小时。两管同时开,多久能注满?

不用 CoT(直接回答)

答案是 2.4 小时。

答案是对的,但如果题目稍微复杂一点,模型可能就算错了。

用 CoT(思维链回答)

让我一步步分析这个问题。

1. 首先确定每个管的效率:
- A 管每小时注入水池的 1/4
- B 管每小时注入水池的 1/6

2. 两管同时开时,每小时注入量:
1/4 + 1/6 = 3/12 + 2/12 = 5/12

3. 注满整个水池需要的时间:
1 ÷ (5/12) = 12/5 = 2.4 小时

所以两管同时开,需要 2.4 小时注满水池。

通过显式地写出每一步,模型犯错的概率大大降低。

如何触发 CoT

方式 1:在 Prompt 中明确要求

请一步一步地思考这个问题,写出你的推理过程,然后给出最终答案。

问题:xxx

或者:

Let's think step by step.

这是最简单的触发方式,称为 Zero-shot CoT

方式 2:提供 Few-shot 示例

给模型几个带思维链的例子,它会学着用同样的方式回答:

问题:一辆车以 60 公里/小时的速度行驶,2 小时能走多远?

思考过程:
1. 已知速度是 60 公里/小时
2. 已知时间是 2 小时
3. 根据公式:距离 = 速度 × 时间
4. 距离 = 60 × 2 = 120 公里

答案:120 公里

---

问题:小明有 100 元,买了 3 本书,每本 25 元,还剩多少钱?

思考过程:

模型会自动续写思考过程。

方式 3:使用支持深度思考的模型

一些模型内置了深度思考能力:

  • OpenAI o1 / o1-mini:专门优化的推理模型
  • DeepSeek-R1:开源的深度思考模型
  • Claude 3.5 extended thinking:支持扩展思考模式

这些模型会自动进行内部推理。有的会展示思考过程,有的只展示最终答案。

CoT 的效果数据

研究表明,CoT 在不同任务上的提升幅度:

任务类型提升幅度说明
数学推理10-30%越复杂的数学题提升越明显
逻辑推理15-25%多步逻辑推理效果显著
代码生成10-20%减少逻辑错误和边界情况遗漏
常识推理5-15%对一些需要推理的常识问题有帮助
简单问答0 或负简单任务不需要 CoT

什么时候用 CoT

CoT 不是银弹,要根据任务类型选择:

适合用 CoT 的场景

  • 数学计算和推理
  • 逻辑推理和分析
  • 复杂的代码生成(需要先设计再编码)
  • 多步骤的分析任务
  • 需要综合多个因素做决策

不适合用 CoT 的场景

  • 简单的事实问答("中国的首都是哪里?")
  • 日常闲聊
  • RAG 场景(答案已经在检索结果中)
  • 创意写作(思考过程可能反而限制创意)
  • 翻译任务(直接翻译通常更好)

CoT 的代价

  • 增加输出 Token(思考过程也要付费)
  • 增加延迟(生成思考过程需要时间)
  • 有时候"想太多"反而出错

模型量化:让大模型"瘦身"

什么是量化

量化(Quantization)是一种模型压缩技术,通过降低参数的数值精度来减少模型大小和计算量。

大模型的参数通常是 32 位浮点数(FP32)或 16 位浮点数(FP16/BF16)。每个参数用 2-4 个字节存储。

量化就是把这些参数转换成更低精度的表示,比如 8 位整数(INT8)、4 位整数(INT4),甚至 2 位(INT2)。

原始参数 (FP16):   0.0012345678...    [16 bits = 2 bytes]
INT8 量化后: 3 [8 bits = 1 byte] → 压缩 2 倍
INT4 量化后: 1 [4 bits = 0.5 byte] → 压缩 4 倍

量化带来的好处

好处 1:显存占用减少

一个 7B 参数的模型:

精度每参数字节模型大小运行时显存估算
FP16214 GB~18-20 GB
INT817 GB~10-12 GB
INT40.53.5 GB~5-6 GB

量化后,原本需要 24GB 显存才能跑的模型,用 8GB 的消费级显卡也能跑了。

好处 2:推理速度提升

  • 低精度计算更快(硬件对 INT8 计算有优化)
  • 内存带宽需求降低(数据搬运更快)
  • 缓存利用率更高

量化后的模型推理速度可能提升 2-4 倍。

好处 3:部署成本降低

  • 可以用更便宜的 GPU
  • 或者在同样的 GPU 上部署更大的模型
  • 甚至可以用 CPU 运行(GGUF 格式)

常见的量化方法

PTQ(Post-Training Quantization)训练后量化

在模型训练完成后进行量化,不需要重新训练。

代表方法:

  • GPTQ:分析权重分布,选择最优的量化策略
  • AWQ:考虑激活值分布,保护重要权重
  • GGUF:llama.cpp 项目推广的格式

QAT(Quantization-Aware Training)量化感知训练

在训练过程中就考虑量化,让模型"学会"适应低精度表示。效果通常比 PTQ 更好,但需要重新训练。

量化格式详解

GPTQ

  • 需要 GPU 运行
  • 量化过程需要校准数据集
  • 精度损失较小
  • 社区支持好,HuggingFace 上有大量预量化模型
# 使用 GPTQ 模型(通过 transformers)
from transformers import AutoModelForCausalLM

model = AutoModelForCausalLM.from_pretrained(
"TheBloke/Llama-2-7B-GPTQ",
device_map="auto"
)

AWQ(Activation-aware Weight Quantization)

  • 需要 GPU 运行
  • 考虑激活值的分布,保护对输出影响大的权重
  • 量化质量可能比 GPTQ 更好
  • 推理速度也更快

GGUF(GPT-Generated Unified Format)

  • 支持 CPU 运行,不需要显卡
  • 由 llama.cpp 项目推广
  • 支持多种量化级别
  • 通过 Ollama 等工具很容易使用

GGUF 的量化级别命名约定:

格式量化位数大小(相对 FP16)质量
Q8_08 bit~50%几乎无损
Q6_K6 bit~42%很小的损失
Q5_K_M5 bit~35%小损失,推荐
Q5_K_S5 bit~33%稍大损失
Q4_K_M4 bit~28%明显但可接受
Q4_K_S4 bit~26%更明显损失
Q3_K_M3 bit~22%显著损失
Q2_K2 bit~15%严重损失,不推荐

如何选择量化级别

选择建议

选择建议

  1. 显存充足:用 Q8_0 或 Q6_K,质量损失极小
  2. 显存一般:用 Q5_K_M(最佳平衡点),或 Q4_K_M
  3. 显存紧张:用 Q4_K_S 或 Q4_K_M
  4. 极端情况:Q3 及以下不推荐,质量损失太大

经验法则

  • 如果可以接受的话,优先选 Q5_K_M
  • Q4 是底线,再低效果会明显下降
  • 宁可选稍小的模型用高精度,也不要选大模型用过低精度

量化的代价

量化不是免费的午餐,会有一定的质量损失:

精度下降的表现

  • 复杂推理能力下降
  • 数学计算更容易出错
  • 知识召回可能不准确
  • 小语种能力下降更明显

量化敏感的任务

  • 数学计算
  • 代码生成
  • 精确的事实问答
  • 需要精细区分的分类

量化不敏感的任务

  • 日常对话
  • 创意写作
  • 情感分析
  • 简单的问答

好消息:对于大多数应用场景,Q4 或 Q5 量化的模型和原始模型的差异不容易察觉。只有在对质量要求极高的场景,才需要考虑更高精度。

小结

这篇咱们详细介绍了大模型开发中的核心概念:

1. Token

  • 大模型处理文本的最小单位,使用 BPE 等子词分词算法
  • 影响能力边界(上下文窗口)和使用成本(API 计费)
  • 中文 1 Token ≈ 1.5-2 汉字,英文 1 Token ≈ 0.75 词

2. 上下文窗口

  • 模型一次能处理的最大 Token 数
  • 包含输入和输出,从 4K 发展到百万级别
  • 越长不一定越好,有"Lost in the Middle"问题

3. Temperature

  • 控制输出的随机性,值域通常 0-2
  • 0 = 确定性,1 = 均衡,>1 = 更随机
  • 代码/数据提取用低值,创意任务用高值

4. Top-P/Top-K

  • 另外两种采样策略
  • Top-K:只从前 K 个候选词选
  • Top-P:只从累积概率达 P 的候选词选
  • 建议只调一个,不要和 Temperature 同时调

5. MoE(混合专家)

  • 动态激活部分参数的架构
  • 看激活参数而不是总参数来评估计算成本
  • 性价比高,代表:DeepSeek-V3、Mixtral

6. 深度思考与 CoT

  • 让模型一步步推理,提高复杂任务准确率
  • 可以通过 Prompt 触发,也有专门的模型支持
  • 复杂推理任务用 CoT,简单任务直接回答

7. 量化

  • 降低参数精度,压缩模型大小
  • 常见格式:GPTQ、AWQ、GGUF
  • Q5_K_M 是性价比较高的选择
  • 让大模型能在消费级硬件上运行

下一篇咱们来看看主流的大模型有哪些,以及如何根据你的需求选择合适的模型。

🎁优惠