夜幕降临,回转寿司店迎来晚高峰。几位厨师站在传送带旁,各自负责一道工序:一个切鱼、一个捏饭团、一个摆盘。老规矩是——“先把所有订单的食材都捏成寿司,等全部捏完再统一收拾用过的盘子”。结果呢?前半程案板上堆满食材,几乎要爆桌;后半程传送带空了,厨师们却齐刷刷发呆。店长决定换一套节拍:“做一碟,收一碟”。每位厨师捏完一个寿司(前向传播),立刻回头收拾上一个用过的盘子(反向传播),再接着捏下一个。案板不再堆积,传送带不再空转,小店吞吐量翻倍。 这台寿司店的新规矩,正是大模型分布式训练中大名鼎鼎的 1F1B(One Forward One Backward)调度策略。它解决了流水线并行的两大痛点:显存峰值过高、流水线气泡浪费算力。如果你未来要训练千亿级大模型,理解1F1B,就是理解“如何让几千张GPU像默契的厨师团队一样,永不空转”。

岗位关联

掌握1F1B调度,是成为大模型分布式训练工程师的必修课。它能帮你把显存占用从O(N)压到O(1),在千卡集群中提升训练吞吐、压低通信开销,直接决定大模型训练任务的成本与效率。

目录
  1. 可视化演示
  2. 流水线基础:把大模型“切开”的学问
  3. 前后向节拍:1F1B的核心循环
  4. 显存优化:从O(N)到O(1)
  5. 气泡减少:把空转压到理论下限
  6. 调度时序:温启动、稳定态与排空
  7. 工程实践:从公式到千卡集群
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

展示系统处理流水线、实时性能指标曲线及多方案对比分析,包含数据流转、吞吐延迟监控与方案评估。

本节配有交互动效(进入视口后自动加载,约 3KB)。

系统可视化 处理流程:输入→处理→模型→输出 性能监控:实时指标 P=L×T

流水线基础:把大模型“切开”的学问

回转寿司店的后厨,没有哪位师傅能独自完成从洗米、捏团到摆盘的全部工序。店长把流程拆成几段,每位师傅专精一段,寿司沿着传送带一份份往下传。大模型训练也是同一个道理——一个上百层的Transformer模型塞不进一张GPU,于是工程师把网络按层切成几段,每段由一个设备(GPU)负责,这就是流水线并行(Pipeline Parallelism)。

但切好还不够,还要决定“一次做多少”。如果一口气把一整批数据(比如128条样本)丢给流水线,每个工位都要等上一个工位把整批处理完才开工,效率极低。所以工程上再把总批次切成许多小份,每一份就叫一个micro-batch(微批量),像传送带上一碟碟的寿司。每个micro-batch按顺序流过所有stage,一个stage做完自己的那层计算,就把中间结果传给下一个stage。

具体来说,假设模型共有L层,被P个stage平分,每个stage负责其中的L/P层。训练样本总批大小B被切成N个micro-batch,每份大小b=B/N。数据像寿司碟一样依次进入流水线,每个设备只保存自己那几层的参数和梯度,显存压力被自然分摊。这里有两个必须记住的概念:前向传播是从输入算出输出,就像把食材加工成寿司;反向传播是根据损失算出梯度,就像收拾用过的盘子、记录下次哪里该改进。

N=B÷bN = B \div b

1. 总批量B:一次参数更新所覆盖的全部样本数,好比晚高峰接到的总订单量。

BB

2. 切分份数N:把订单分成N碟,每碟是流水线上流动的最小单位。

NN

3. 每碟份量b:每个micro-batch包含的样本数,决定案板上每碟的“占地面积”。

b=B÷Nb = B \div N

流水线并行把大模型像寿司工序一样切开,再把数据按小碟分装。有了“切”和“分”,我们才能讨论接下来1F1B这部精密节拍器如何运转。

前后向节拍:1F1B的核心循环

店长立下的新规矩是“做一收一”。注意,厨师并不是每捏完一碟就非要立刻收拾同一碟的盘子——收拾动作要等客人吃完、盘子从远端传回来才行。1F1B的精髓在于:只要手头有活就干,优先做反向,腾不出手做反向时才做前向。于是每个micro-batch的前向和反向像齿轮一样交错啮合,整个厨房始终在运转。

所谓1F1B,全称是 One Forward One Backward,即“一个前向、一个反向”交替执行。每个stage完成某个micro-batch的前向计算后,不急着连续做下一个前向,而是先检查有没有已完成前向、可以立即做反向的micro-batch。如果有,就做那个反向;没有,才做下一个micro-batch的前向。

为什么需要这样的“插队”?看朴素调度就明白了。朴素调度(GPipe风格)先让所有N个micro-batch依次走完前向,直到最后一个micro-batch的前向结束,才开始逐一做反向。这导致两个严重问题:第一,每个stage必须把N份前向激活全部缓存下来等着反向使用,显存直接爆炸;第二,前向全部结束后、反向尚未开始时,流水线处于大段空转,GPU算力白白浪费。1F1B让反向任务穿插在前向之间,既及时释放激活,又让每个stage始终有计算可做。

每个micro-batch在一个stage上的完整节拍时间,可以简单记为:

Tstep=Tf+TbT_{\text{step}} = T_f + T_b

其中 \(T_f\) 是前向耗时,\(T_b\) 是反向耗时。这个“节拍”是后面分析一切时间开销的最小单位。

1. 前向耗时T_f:把一份食材做成寿司(计算输出)所需的时间。

TfT_f

2. 反向耗时T_b:把一只用过的盘子收拾干净(计算梯度)所需的时间。

TbT_b

3. 完成节拍:这个micro-batch在这一工位上的总占台时间,前向反向各一次。

Tf+TbT_f + T_b

厨师捏一碟(前向)、收拾一碟(反向)、再捏下一碟,动作之间几乎无缝衔接。1F1B的“节拍感”就是大模型训练吞吐的命脉。

显存优化:从O(N)到O(1)

老规矩下,案板为什么会爆?因为所有厨师先一口气捏完N碟寿司,然后集体等客人吃完、盘子送回。等待期间,这N碟寿司的“食材状态”必须全部保留在案板上,供后续收拾(反向)时对照。到了大模型训练里,这些“食材状态”就是前向激活(activation)——每层计算出的中间结果,反向传播必须用到它们来求梯度。

在朴素调度中,一个stage要等它经手的N个micro-batch全部完成前向,才开始做反向,所以必须把N份激活全部暂存。显存占用随N线性增长,写作O(N)。1F1B打破了这种堆积:一个micro-batch的前向刚完成,它的反向很快就会跟上并释放激活,因此每个stage的案板上永远只有极少数碟子。

精确一点说,1F1B下每个stage大约同时持有 2份 micro-batch激活:一份是本地正在做前向的“生食材”,另一份是刚传给下游、正等待反向的“半成品”。于是两种策略的显存峰值对比一目了然:

Mnaive∝N⋅m0,M1F1B≈2⋅m0M_{\text{naive}} \propto N \cdot m_0, \qquad M_{\text{1F1B}} \approx 2 \cdot m_0

其中 \(m_0\) 是一个micro-batch的激活体积。

1. 单碟体积m0:一碟寿司占的案板面积,即一个micro-batch的激活张量大小。

m0m_0

2. 旧策略缓存N份:所有寿司都摆在案板上等待收盘,占用随N线性增长。

Mnaive=N⋅m0M_{\text{naive}} = N \cdot m_0

3. 1F1B只留2份:一碟正在做、一碟等着收,案板空间与N无关。

M1F1B=2⋅m0M_{\text{1F1B}} = 2 \cdot m_0

显存占用从“要铺满整个晚高峰的订单”变成“只需两张碟的位置”。省下的显存可以用来加大微批次数、拉长序列,或直接塞进更大的模型——这正是1F1B最诱人的地方。

气泡减少:把空转压到理论下限

传送带空转是什么体验?回转带上没有寿司,客人干瞪眼,厨师也闲着——这叫流水线气泡(pipeline bubble)。在流水线并行中,气泡不可避免:第一批micro-batch需要时间逐级灌满流水线,最后一批也需要时间逐级排空,这两段时间里总有stage在等待。我们无法消灭气泡,但可以用1F1B把气泡压到理论下限。

气泡到底有多大?假设有P个stage、N个micro-batch,每个micro-batch在一个stage上的前向+反向总耗时为 \(T = T_f + T_b\)。理想情况下(通信被完全隐藏),流水线总墙钟时间为第一个micro-batch进入、到最后一个micro-batch离开所经过的时间:

Ttotal=(N+P−1)×(Tf+Tb)T_{\text{total}} = (N + P - 1) \times (T_f + T_b)

而所有stage真正用于计算的总工时是 \(N \times (T_f + T_b)\)。两者之差就是气泡时间。于是气泡率为:

Bubble=(P−1)(N+P−1)\text{Bubble} = \frac{(P-1)}{(N+P-1)}

1. 有效总工时:所有N碟寿司的制作加收拾时间之和,这是“真正干活”的量。

N⋅(Tf+Tb)N \cdot (T_f + T_b)

2. 流水线总时长:传送带从第一碟开工到最后一碟收工的实际墙钟时间。

(N+P−1)⋅(Tf+Tb)(N + P - 1) \cdot (T_f + T_b)

3. 气泡占比:总时长减去有效工时后除以总时长,化简即得气泡率公式。

Bubble=1−NN+P−1=P−1N+P−1\text{Bubble} = 1 - \frac{N}{N+P-1} = \frac{P-1}{N+P-1}

注意到P固定时,N越大气泡率越小。1F1B本身并不能比GPipe把气泡率降得更低(两者理想公式相同),但它因为显存占用低,可以承受更大的N,于是间接把气泡摊薄到几乎看不见——晚高峰订单足够多时,传送带自然全程不空转。

调度时序:温启动、稳定态与排空

一家回转寿司店一天的节奏:开门时,第一碟寿司慢慢摆上传送带,几个工位陆续开工;晚高峰时,所有工位全速运转;打烊时,最后一碟寿司被取走,传送带逐渐清空。1F1B的一个完整训练step,也精确地分为同样的三个阶段。

温启动(warm-up):前 \(P-1\) 个micro-batch依此进入流水线。此时每个stage刚开始领到自己的第一个任务,越靠后的stage等待越久,气泡相对较多,就像刚开店时传送带上的寿司稀稀拉拉。稳定态(steady):流水线被灌满后,每个时间步都有前向和反向任务同时在执行,所有stage几乎满负荷,这是吞吐最高的黄金时段。排空(drain):最后 \(P-1\) 个micro-batch逐渐完成反向,流水线从尾到头清空,气泡再次出现,就像打烊前盘点的收尾阶段。

若令 \(T = T_f + T_b\),则三个阶段合起来的总时长为:

Ttotal=(P−1)⏟温启动×T+(N−P+1)⏟稳定态×T+(P−1)⏟排空×TT_{\text{total}} = \underbrace{(P-1)}_{\text{温启动}} \times T + \underbrace{(N-P+1)}_{\text{稳定态}} \times T + \underbrace{(P-1)}_{\text{排空}} \times T

这正是上一节总时长公式的展开形式。稳定态跨度 \(N-P+1\) 越大,整条流水线的有效工作时间占比就越高。

1. 温启动填充:前P-1个节拍把每个工位的第一个活补齐,气泡较多。

Twarm=(P−1)×TT_{\text{warm}} = (P-1) \times T

2. 稳定态满负荷:中间N-P+1个节拍前后向同时开工,利用率最高。

Tsteady=(N−P+1)×TT_{\text{steady}} = (N-P+1) \times T

3. 排空收尾:最后P-1个节拍让所有寿司流转完毕,气泡再次出现。

Tdrain=(P−1)×TT_{\text{drain}} = (P-1) \times T

理解了这三个阶段,你就能看懂1F1B的甘特图为什么总是“两头尖、中间胖”——和一家火爆寿司店一天的客流曲线如出一辙。调度时序的把握,直接影响你是否能准确预判训练任务的耗时。

工程实践:从公式到千卡集群

店长掌握了新规矩后,还得琢磨几个实际问题:每碟放多少料?高峰排几个班?传菜和结账能不能并行?1F1B从理论走向工程,同样绕不开这些细节。

第一,micro-batch数N怎么定? 由气泡率公式可知,N越大气泡越小,但N过大会增加调度器负担和通信次数。工程实践通常取 N ≥ 4P,此时稳定态占比超过75%,理想气泡率不超过25%。第二,通信与计算重叠。 stage之间只需要传递边界层(切分处)的激活和梯度,1F1B天然让通信与计算重叠——前向数据送往下游的同时,本stage可以立刻开始另一个micro-batch的反向计算,GPU不会干等数据。第三,千卡集群中的3D并行。 流水线并行通常与数据并行、张量并行组合,形成3D并行。1F1B省下的显存意味着可以开更大的N,直接拉高整体利用率。

利用率 \(U\) 是衡量流水线健康度的核心指标,它与气泡率互补:

U=NN+P−1U = \frac{N}{N+P-1}

1. 利用率定义:所有stage实际干活的时间占墙钟时间的比例。

U=N⋅T(N+P−1)⋅TU = \frac{N \cdot T}{(N+P-1) \cdot T}

2. 化简结果:分子分母约去T,利用率只由N和P决定。

U=NN+P−1U = \frac{N}{N+P-1}

3. 工程经验值:当N不小于4倍P时,理论利用率可以超过80%。

N≥4P⇒U≥80%N \ge 4P \Rightarrow U \ge 80\%

店长把“一碟多少料”和“排班节奏”调好,传送带在晚高峰几乎不停转——这正是1F1B在千卡集群中价值千金的现实写照。理解了这些工程参数,你才算真正把1F1B用到了实处。

AI 应用场景

大语言模型预训练中的显存破局

阶段:分布式训练(模型并行/流水线并行)

解决的问题:百亿乃至千亿参数的大模型,单卡显存根本无法容纳。传统朴素调度会在每个stage缓存全部N个micro-batch的激活,显存随N线性暴涨,导致工程师被迫把N压得很小,流水线气泡大、吞吐低。1F1B通过“前向与反向交替”,把每卡激活峰值压到常数级别,让N可以放心加大,吞吐随之提升。

具体案例:某团队用8卡流水线并行训练13B参数的大语言模型。开启1F1B调度后,单卡激活显存从约30GB直降到5GB。他们顺势把micro-batch数N从8提升到16,气泡率从23%降到7%,训练吞吐量提升约35%。同样的硬件,只换了一套“节拍”,效率就天差地别。

长序列微调:让上下文窗口再长一点

阶段:模型微调(SFT/继续预训练)

解决的问题:训练16K甚至128K长上下文的模型时,激活显存随序列长度二次增长,经常OOM。1F1B让激活缓存不再随micro-batch数量堆积,把宝贵的显存让给序列长度,从而支持更长的上下文训练。

具体案例:工程师在4张A100上微调7B模型,把上下文从8K扩展到32K。使用朴素调度时batch只能设为2,开启1F1B后,激活峰值明显下降,batch可以开到8且不OOM,长序列微调的吞吐提升显著。

千卡集群中的3D并行协同

阶段:大规模分布式训练

解决的问题:在数千卡集群中,单靠数据并行通信量巨大,单靠流水线并行又无法利用全部设备。1F1B与张量并行、数据并行组合成3D并行,在降低显存的同时,把通信量限制在每个stage边界的一小部分,避免跨节点通信成为瓶颈。

具体案例:某大模型训练平台在1024张GPU上训练175B模型,采用8路流水线并行 × 8路张量并行 × 16路数据并行。1F1B调度让每个stage的激活占用保持恒定,整体MFU(模型浮点利用率)从约38%提升到46%。

Python 演示

python38 行
def bubble_ratio(P, N):    """计算1F1B理想流水线气泡率(P为stage数,N为micro-batch数)"""    return (P - 1) / (N + P - 1)  def memory_peak(P, N, per_micro=1.0):    """对比朴素调度与1F1B的峰值激活显存(相对单位)"""    mem_naive = N * per_micro    # 朴素调度要缓存N份激活    mem_1f1b = 2.0 * per_micro   # 1F1B最多保留2份    return mem_naive, mem_1f1b  def find_workable_N(P, mem_budget, per_micro=1.0):    """在显存预算内寻找让利用率最高的N(工程选参模拟)"""    best_N, best_u = 0, 0.0    for N in range(1, 65):        _, mem = memory_peak(P, N, per_micro)        if mem <= mem_budget:            u = N / (N + P - 1)            if u > best_u:                best_u, best_N = u, N    return best_N, best_u  def main():    P = 4    print("P=%d 个stage时,不同N的气泡率与显存对比" % P)    print("N\t气泡率\t朴素显存\t1F1B显存")    for N in range(4, 13):        br = bubble_ratio(P, N)        mn, m1 = memory_peak(P, N)        print("%d\t%.1f%%\t%.0f份\t%.0f份" % (N, br * 100, mn, m1))    best_N, best_u = find_workable_N(P, mem_budget=8.0)    print("显存预算8份时,1F1B可用N=%d,理论利用率=%.1f%%" % (best_N, best_u * 100))  if __name__ == "__main__":    main()
目的:模拟P=4个stage时,不同micro-batch数N下的气泡率与显存占用,并演示“显存预算内如何选N”的工程决策过程。
  1. return (P - 1) / (N + P - 1)气泡率公式,N越大分母越大、气泡率越小。
  2. mem_naive = N * per_micro朴素调度显存随N线性增长,对应餐厅案板堆满N碟。
  3. mem_1f1b = 2.0 * per_micro1F1B显存恒为常数,与N无关,这是它的灵魂。
  4. find_workable_N在显存预算内枚举所有可行N,挑出利用率最高者,模拟工程师的选参逻辑。
  5. if __name__ == "__main__":提供可执行入口,直接运行即可看到完整对比结果。

常见误区

误区我用了1F1B,显存低了、传送带看起来不空了,气泡应该变成0了吧?那是不是P不管多大都没关系?

正解气泡的根源在于流水线“灌满”和“排空”两个阶段,即使理想1F1B,气泡率也严格等于(P-1)/(N+P-1),不可能为0。1F1B真正消灭的是“前向全部结束后集体等反向”造成的大段气泡,把空闲摊薄到理论下限。就像寿司店开店和打烊总有一段空转,只靠换节拍无法消除,但可以缩短到极限。

为何常见:动画里1F1B传送带看起来塞满了寿司,视觉上很饱满,就容易误以为气泡完全消失了;实际上气泡只是被压缩到很难察觉。

误区GPipe和1F1B的理想气泡率公式一模一样,既然吞吐相同,那1F1B是不是只是营销概念?

正解公式相同的前提是N相同。但GPipe显存随N线性增长,显存不够时只能用小N;1F1B显存恒定,可以把N调到很大,从而把气泡率大幅压低。换句话说,GPipe在“案板小”的限制下只能接小订单,1F1B案板始终只有两碟,可以无负担地接千碟大单,实际吞吐自然更高。

为何常见:只对比公式,忽略了公式背后的关键前提——显存预算限制下的N完全不同。

误区反向传播不是要等整个batch算完损失才能开始吗?那每个stage就应该先把所有micro-batch的前向做完,再做反向。

正解在分布式训练中,损失和梯度本来就是按micro-batch逐份计算的。每个micro-batch完成前向后,只要它的下游已经算完相应的反向,本stage就能立刻对它做反向。1F1B正是利用这种“流水线式依赖”做边做边收。

为何常见:把单机大batch训练中“整批求梯度”的惯性思维,错误地搬到了micro-batch流水线场景。

误区气泡率公式里N越大气泡越小,那我直接把N设成10000不就能把利用率推到99%了?

正解N过大会带来新的成本:micro-batch之间的通信次数线性增加,调度器开销上升,流水线填充和排空的时间也变长;同时反向传播的梯度累积延迟变长,可能影响数值收敛。工程上N通常取4到8倍P即可,在气泡率与开销之间取得平衡。

为何常见:只盯着单一指标(气泡率),忽略了真实系统中通信、调度、收敛性等隐形成本。

面试题

Q1. 1F1B中的“F“和”B”分别指什么?

单选题 · easy

  • A.前向传播与反向传播← 正确答案
  • B.浮点运算与批量归一
  • C.特征提取与标签预测
  • D.前向调度与回退算法

答案:A。F代表Forward(前向传播),即从输入计算输出;B代表Backward(反向传播),即根据损失计算梯度。这与寿司店“做寿司”和“收盘子”的比喻完全对应。其他选项都是凭空编造的干扰项。

Q2. 1F1B调度最直接解决的核心问题是什么?

单选题 · easy

  • A.显著降低激活显存峰值← 正确答案
  • B.完全消除流水线气泡
  • C.减少模型参数总量
  • D.加速单卡矩阵运算

答案:A。1F1B通过前向与反向交错执行,让激活被及时释放,把激活显存从O(N)降到O(1)。它只能把气泡压到理论下限(P-1)/(N+P-1),不能完全消除;也不能减少参数或加速单卡运算。

Q3. P个stage、N个micro-batch时,1F1B的理想气泡率公式是?

单选题 · mid

  • A.(P-1)/(N+P-1)← 正确答案
  • B.N/(N+P-1)
  • C.P/(N+P)
  • D.(N-1)/(N+P)

答案:A。气泡时间占总时长的比例为(P-1)/(N+P-1)。选项B是利用率公式而不是气泡率;C和D分子分母拼凑错误。把P=4、N=12代入选项A得3/15=20%,符合直觉。

Q4. 为什么1F1B能让每个stage的激活显存保持很低?

单选题 · mid

  • A.反向任务及时执行并释放前向激活← 正确答案
  • B.只存储模型参数而不存中间结果
  • C.依赖混合精度训练压缩激活
  • D.把激活张量直接丢弃不参与反向

答案:A。1F1B中每个micro-batch完成前向后,其反向很快被调度执行,激活随即释放,所以每个stage最多只需保留约2份激活。中间结果不能丢弃否则无法反向;混合精度是另一个独立技术,不是低显存的原因。

Q5. 若P=4、N=12,1F1B的理论气泡率约为多少?

单选题 · hard

  • A.约20%← 正确答案
  • B.约25%
  • C.约33%
  • D.约50%

答案:A。代入气泡率公式(P-1)/(N+P-1)=3/(12+3)=3/15=20%。这道题考查对公式的快速运用:分子是3,分母是15,商正好20%。其他选项对应不同错误代入方式,比如忘了减1或分母加错。

Q6. 以下哪些场景适合使用1F1B调度?(多选)

多选题 · mid

  • A.显存受限的大模型预训练← 正确答案
  • B.长序列模型的微调训练← 正确答案
  • C.单卡单机的小型模型推理
  • D.与张量并行组合的混合并行← 正确答案

答案:ABD。1F1B核心收益是降低流水线并行的显存峰值,因此显存受限的预训练和长序列微调都受益。它还可以与张量并行、数据并行组合成3D并行。单卡推理根本不涉及流水线并行,谈不上用1F1B。

Q7. 与传统“先全部前向再全部反向”相比,1F1B有哪些优势?(多选)

多选题 · hard

  • A.激活内存占用不再随N线性增长← 正确答案
  • B.可以把气泡压缩到理论下限← 正确答案
  • C.完全消除设备间的通信开销
  • D.支持更大的micro-batch数N← 正确答案

答案:ABD。1F1B让激活缓存保持常数(2份),因此支持更大N,也通过摊薄气泡让利用率更高。但通信开销只能减少和重叠,不能完全消除——stage之间仍需要传递边界激活和梯度。

Q8. 1F1B调度中“温启动”阶段持续多少个节拍?

单选题 · mid

  • A.P-1个节拍← 正确答案
  • B.N-P+1个节拍
  • C.N-1个节拍
  • D.P+1个节拍

答案:A。前P-1个micro-batch依次灌入流水线,这个填充过程称为温启动。稳定态持续N-P+1个节拍,排空也是P-1个节拍。B是稳定态的跨度,C和D是混淆项。

Q9. 在千卡集群中使用1F1B,提高训练利用率的有效手段有哪些?(多选)

多选题 · mid

  • A.增大N以降低气泡率← 正确答案
  • B.让通信与计算相互重叠← 正确答案
  • C.减小micro-batch的样本数
  • D.只增加stage数而不调整N

答案:AB。增大N直接降低气泡率;通信重叠提升GPU等待期间的利用效率。减小micro-batch样本数通常会让通信更频繁、吞吐下降;只增加stage数而不增大N会显著推高气泡率。

Q10. 工程实践中,micro-batch数N通常取多少比较合理?

单选题 · easy

  • A.N大于等于4倍的stage数← 正确答案
  • B.N恰好等于stage数
  • C.N小于stage数
  • D.N恒定等于1

答案:A。N≥4P时理论利用率可达80%以上,且调度开销可接受。N=P时气泡率高达(P-1)/(2P-1),接近50%;N恒等于1等于完全失去流水线并行意义,几乎没有加速效果。