第 12 章 并行化 Parallelization
本章要解决的问题
多个独立子任务串行执行太慢------扇出并行再聚合,怎么保证合并结果不冲突、成本不失控?
章节大纲
- 12.1 扇出-聚合 Fan-out / Fan-in
- 12.2 任务拆解与结果合并
- 12.3 吞吐优化与成本权衡
- 12.4 主流框架对照与变体
- 🛠 解决方案:合并冲突与成本失控防护
12.1 模式原理:扇出-聚合(Fan-out / Fan-in)
12.1.1 一句话定义
并行化(Parallelization)是把一个任务拆成多个相互独立的子任务同时执行(扇出 Fan-out),再在终点把各分支的结果合并成最终输出(聚合 Fan-in)。 它是 9 大模式中最直接面向延迟和吞吐优化的模式------其他模式也可以间接优化性能,但并行化的首要目标就是"更快"。

图 1:Fan-out / Fan-in
12.1.2 为什么需要并行:串行的延迟诅咒
看一个真实场景:季度经营分析报告,需要同时看销售数据、客户反馈、研发进度、市场舆情。串行执行时:

图 2:串行 vs 并行
text
分析销售数据 → 3 秒
分析客户反馈 → 4 秒
分析研发进度 → 3 秒
分析市场舆情 → 4 秒
─────────────────
总计 → 14 秒 ← 用户等得想退款
而并行执行:
text
分析销售数据 ─┐
分析客户反馈 ─┤ ← 同时开始
分析研发进度 ─┤
分析市场舆情 ─┘
─────────────────
总计 → ~4.5 秒 ← 用户满意
总延迟 ≈ 最慢分支的延迟(加上一点调度开销),而不是所有分支之和。任务越多,并行收益越大------这是 LLM 应用里回报最立竿见影的优化。
12.1.3 并行化的前提:任务必须"真独立"
并行化只有一个硬前提:子任务之间没有数据依赖。 判断方法很简单:把 A 的输出换成任何值,B 的结果都不变,那 A 和 B 就独立,可以并行。
需要警惕的是"伪独立":比如"分析客户反馈"和"分析市场舆情"都要读同一份原始数据,看起来独立,但如果它们共享一个会被修改的全局状态(比如同一个数据库连接池被限流),并发后反而互相拖累。并行前先检查共享资源(同一个 API Key 的限流、同一个数据库的写锁)。
12.2 任务拆解与结果合并
12.2.1 任务拆解的三种粒度
| 拆解方式 | 说明 | 例子 |
|---|---|---|
| 按数据分片 | 同一处理逻辑,不同数据块 | 1000 条评论分 10 批各 100 条做情绪分析 |
| 按维度分解 | 不同处理逻辑,同一数据 | 同一份财报同时分析营收/成本/风险 |
| 按方案并行 | 同一任务跑多个方案对比 | 同一个问题让 3 个不同提示词版本回答,投票取最优 |
第三种"按方案并行"值得多说一句:它和第 13 章反思模式配合紧密------**Self-consistency(自洽性采样)**就是让模型对同一问题多次独立回答,然后投票/聚类出最一致的答案,能显著提升推理类任务准确率。这是"用并行换质量"的典型。
12.2.2 聚合(Fan-in)的三种策略
并行容易,合并难。聚合是并行化的真正难点,三种策略:

图 3:三种聚合策略
| 聚合策略 | 做法 | 适用 |
|---|---|---|
| 拼接聚合 | 各分支结果按顺序拼成一个大文档 | 各分支产出是"并列章节"(如报告的各部分) |
| 投票聚合 | 多个答案里选出现次数最多/置信度最高的 | Self-consistency、方案对比 |
| 模型合成 | 把各分支结果交给一个"总结模型"统一整合 | 分支结果有交叉、需要提炼共识 |
投票聚合示例(Self-consistency):
python
import asyncio
async def ask_parallel(prompt, n=3):
# 并发发 3 次独立请求(不同 seed / 略高温度)
tasks = [call_api(prompt, temperature=0.7, seed=i) for i in range(n)]
answers = await asyncio.gather(*tasks)
# 投票:最长公共答案 / 或让模型选最一致
return max(set(answers), key=answers.count)
answer = asyncio.run(ask_parallel("鸡兔同笼,头35脚94,各几只?"))
12.2.3 聚合的冲突处理
聚合阶段最常见的两类冲突:

图 4:并行成本权衡
- 数字冲突:两个分支对同一指标给出不同数字(如"客户满意度 91%" vs "88%")。对策:聚合提示词里声明"遇到冲突数据,标注数据来源与口径,不强行合并"。
- 语义冲突:两个分支给出矛盾结论("市场看涨" vs "市场承压")。对策:聚合时要求"保留分歧并说明原因",或加一个裁决步骤(见第 13 章反思)让第三模型裁定。
12.3 完整示例:并行报告生成
实现一个"四路并行 + 模型合成"的季度分析示例:
python
# 示例代码:演示 Fan-out/Fan-in 的核心逻辑
import asyncio, json
from openai import AsyncOpenAI
client = AsyncOpenAI(base_url="https://api.deepseek.com", api_key="<你的Key>")
BRANCHES = {
"销售": "从以下销售数据中提炼 3 个关键结论:{data}",
"客户": "从以下客户反馈中提炼 3 个关键结论:{data}",
"研发": "从以下研发进度中提炼 3 个关键结论:{data}",
"舆情": "从以下市场舆情中提炼 3 个关键结论:{data}",
}
# 并发限流:防止撞 API 限流(429)
MAX_CONCURRENT = 5
semaphore = asyncio.Semaphore(MAX_CONCURRENT)
async def run_branch(name, prompt):
async with semaphore:
resp = await client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user", "content": prompt}],
temperature=0.3,
)
return name, resp.choices[0].message.content
async def parallel_analyze(data_map):
# Fan-out:并发跑 4 个分支(return_exceptions 容错)
tasks = [run_branch(name, prompt.format(data=data_map[name]))
for name, prompt in BRANCHES.items()]
raw_results = await asyncio.gather(*tasks, return_exceptions=True)
# 过滤异常分支,失败分支降级为"数据暂缺"
results = []
for name, r in zip(BRANCHES.keys(), raw_results):
if isinstance(r, Exception):
results.append((name, "【数据暂缺】"))
else:
results.append(r)
# Fan-in:模型合成总报告
combined = "\n".join(f"【{n}】{r}" for n, r in results)
final = await client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user", "content":
f"以下是对四个维度的独立分析,请整合成一份结构化的季度报告,"
f"冲突数据需标注口径:\n{combined}"}],
temperature=0.4,
)
return final.choices[0].message.content
# 使用
report = asyncio.run(parallel_analyze({
"销售": "Q2 营收 1.2 亿,环比 +8%......",
"客户": "满意度 91%,投诉集中在物流......",
"研发": "3 个功能已上线,2 个延期......",
"舆情": "正面 62%,负面集中在价格......",
}))
关键点:
- Fan-out 用 AsyncOpenAI:并发必须用异步客户端,同步客户端会阻塞。
- Semaphore 限流是标配:并发请求超过 API 配额会触发 429,生产环境务必设上限。
- Fan-in 是"合成"不是"拼接":让总结模型统一整合、处理交叉与冲突。
- 失败分支要降级而非全崩 :
return_exceptions=True+ 降级处理,保住其余结果。 - 分支数要克制:4 路并行在成本和收益上比较甜点,盲目开 20 路并行会撞限流(见 12.4)。
12.4 主流框架对照与变体
12.4.1 框架对照
| 实现方式 | 特点 | 适用 |
|---|---|---|
Python asyncio.gather + AsyncOpenAI(本章主线) |
最直接,无框架依赖 | 中小规模并行 |
| LangGraph 并行边 / Map-Reduce | 框架内置扇出聚合,自动管理状态 | 已用 LangGraph |
LangChain map_reduce chain |
专为"分片处理→合并"设计 | 长文档分片处理 |
| 云函数/任务队列(AWS Step Functions 等) | 真分布式,可跨机并行 | 海量数据、长任务 |
12.4.2 变体一:Map-Reduce(分片并行)
长文档处理的标准姿势:Map(把文档切块并发处理)→ Reduce(把块结果合并)。这是 RAG 之外处理超长文本的另一条路(呼应第 8 章 8.2 分块策略)------区别是 RAG 检索用、Map-Reduce 全量分析用。
12.4.3 变体二:并行 + 评测(Multi-vote)
让同一任务跑 N 个不同配置(不同模型/不同温度/不同提示词版本),再统一评测选最优(呼应第 20 章评测)。这是"并行化 × 评测"的组合,常用于上线前的提示词选型。
12.4.4 变体三:分级并行(Hierarchical Parallelism)
先并行粗粒度任务,每个粗粒度任务内部再并行细粒度子任务。比如"多产品线分析":先并行各产品线,每条产品线内部再并行"销售/客户/研发"三个维度。收益是延迟进一步压低,代价是复杂度上升、资源抢占加剧------除非延迟是硬指标,否则一级并行通常够用。
🛠 解决方案:合并冲突与成本失控防护
常见问题
- "并行后撞限流,报 429 错误" :并发请求超过 API 配额。对策:用
asyncio.Semaphore限制最大并发数(比如 5),或在 Fan-out 前查配额余量(呼应第 24 章 A1/A2)。 - "合并结果自相矛盾":分支间数据冲突没处理。对策:聚合提示词声明冲突处理规则(标注口径/保留分歧),必要时加裁决步骤。
- "并行比串行还慢":大概率是分支里混进了"伪独立"任务(共享资源被锁)。对策:检查共享状态,把有依赖的子任务串行化。
- "成本翻倍了":并行是"用钱换时间"。对策:只对延迟敏感的路径并行;简单分支用小模型;合并输出设 max_tokens 上限。
- "某个分支失败,整个任务失败" :
asyncio.gather默认一个异常全崩。对策:用return_exceptions=True+ 对失败分支降级(返回"该维度数据暂缺"),保住其余结果(呼应第 23 章降级设计)。
python
results = await asyncio.gather(*tasks, return_exceptions=True)
results = [r if not isinstance(r, Exception) else f"【{name}】数据暂缺"
for name, r in zip(BRANCHES.keys(), results)]
解决方案速查表
| 现象 | 根因 | 解决方案 |
|---|---|---|
| 429 限流 | 并发超配额 | Semaphore 限流 + 查配额 |
| 结果矛盾 | 冲突未处理 | 聚合规则声明 + 裁决步骤 |
| 并行更慢 | 伪独立任务 | 检查共享资源,有依赖串行 |
| 成本翻倍 | 无差别并行 | 只并行敏感路径 + 小模型 |
| 单分支拖垮全局 | gather 无容错 | return_exceptions + 分支降级 |
实战提示
- 先量延迟再上并行:用 profiler 测出各步骤耗时,只有"瓶颈步骤"值得并行,别全线并行。
- 并发上限设硬阈值:Semaphore 限制并发数,避免"越并越慢"(资源竞争)。
- 聚合提示词写好冲突规则:这是并行项目里最容易被忽略、却最影响输出质量的一行。
- 评测并行收益:对比"串行基线 vs 并行"的延迟/成本/质量三项,用数据说话,并行不是免费的。