Speculative Decoding/投机解码

Fast Inference from Transformers via Speculative Decoding》:

1. 基本概念

(1) Two Models
Mp:target model,即原始模型。
Mq:efficient approximation model,即近似模型。
Mq比Mp小(参数量),暂且称Mq为小模型,Mp为大模型。

(2) Two distributions在这里插入图片描述
这里p(x)、q(x)分别是Mp、Mq在给定输入x<t下生成下一个token的概率分布。

(3) Process

  • 简单一轮:生成->评估(接受,拒绝)->重新采样
  • 完整一轮:先用小模型Mq生成 r 个token(auto regression自回归方式);然后用大模型Mp来评估(并行解码 r 次,生成 r +1个token),依次对Mq生成的每一个token,比较两个模型的概率分布,来决定是接受还是拒绝;
    若拒绝,那么对拒绝token所在的位置的分布做一个调整,然后重新采样生成一个新token之后就结束。
    若全都接受,那么这一轮一共r+1生成了个token,其中前r个由生成,第r+1个由Mp生成。
    显然,经过一轮speculative decoding,最多可生成 r+1 个token,最少生成1个token。

(4)为啥叫speculative decoding
对于为啥叫speculative decoding或者为啥大家喜欢叫投机解码?
利用小模型Mq生成的 r 个token是推测的(可能的)结果,并不是真正最后输出的结果,这些结果需要通过模型Mp去决定对不对,要不要。

显然,speculative decoding的核心思想就是“先猜测,后验证”。但是这种猜测行为实际上是比较“冒险的”,因为有可能被拒绝。

猜对了,被接受了,就节省了计算时间;但若猜错了,就会被拒绝,这样自然也就浪费了一些计算,同时还需要就猜错的地方进行就调整。

整体上,speculative decoding流程类似于“投机”(Speculative)行为中的“预先行动,再根据结果调整”。

2. 细节

现在我们来看一下speculative Decoding是如何评估的,也就是什么样才会接受,什么样又拒绝,拒绝后又如何重新采样。

2.1 接受

假设x采样自q(x),那么如果p(x)大,我们就接受这个。

这点可以这么理解,Mp是大一些的模型,它的准确性通常要高一些,q(x)<=p(x)表示的是比起Mq,Mp对生成的把握更高一些,那么这个x可以接受。

2.2 拒绝

反过来,如果p(x)< q(x),也就是说对于x,Mp的把握度低一些,即生成的有可能是错的。此时就以一定的概率拒绝x,这个概率为1-p(x)/q(x)。

因为p(x)< q(x),那么就有p(x)/q(x) < 1。并且,p(x)与 q(x)差距越大,p(x)/q(x)就越小,那么1-p(x)/q(x)就越大,拒绝的概率就越大。

2.3重新采样

当出现第一个拒绝的token,就需要重新采样并结束speculative Decoding的一轮了。

其中,重新采样需要调整对应位置的分布为:
在这里插入图片描述
。这里稍微注意下,前面的接受与拒绝,我们比较的是具体的token的概率值,这里调整的是token所在位置处的概率分布。

对于p(x)>= q(x)的那些x来说,p(x) - q(x) > 0;
对于p(x)< q(x)的x来说,p(x) - q(x) <= 0,再经过max(),值就置为零,那么这些token将永远不会被采样到。
通过这样的调整后再进行归一化,就得到了新的概率分布p’(x),再基于这个新分布来采样一个具体的token。

speculative decoding的一轮:
在这里插入图片描述

3 加速

一轮生成的期望token数

我们了解到执行一轮Speculative decoding,最多生成r +1个token,最少生成1个token。

那么平均情况下,执行一轮Speculative decoding,能生成多少个token呢?

3.1 接受率与它的期望值

βx<t(接受率):在Speculative decoding中,接受率所刻画的是,在给定x<t前缀的情况下,模型生成的token 被接受的概率。

α (平均接受率):β的期望值α=E(β),一定程度上衡量了Mq与Mp的近似/对齐程度(alignment),二者越接近,那么α越高。另外,应该是考虑了所有可能的前缀,也即在这里插入图片描述

3.2 期望token数

Speculative decoding可能会遇到各种情况,比如;
第一个位置生成的token即x1~ Mp(prefix)就被拒绝;
第一个位置的token被接受,第二个位置的token即x2~ Mp(prefix)被拒绝

全部r个token都被接受。

假设β是一个随机变量并且是i.i.d,即独立同分布。那么Speculative decoding的过程可以描述如下:每次Mq生成一个token,以α的概率接受并继续生成;以1-α的概率拒绝并停止生成。

设第一个被拒绝的token的位置是k,那么每一种情况下生成的token数j的情况如下:
在这里插入图片描述
当k<=r了,Mq生成的前k-1个token全部被接受,第k个被拒绝,概率为ak-1(1-α)
Mp调整分布后重新采样生成第k个token;
k=r+1时,所有r个token都被接受,概率为ar,同时Mp额外生成第r+1个token。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

有了期望token数的公式,图像就很容易画了(只需要给定a与r)。
显然有:
α越高,即两个模型越接近,接受概率越高,生成token数就起越超近r+1;
反过来若α越低,那么被拒绝的概率就越高,生成token数就越趋近于1
在α固定时,理论上增加了会增加一轮生成的token数。但是,如果α值过低,这个收益比就比较低(被拒绝的多了,很多计算就都浪费了)。所以,α对我们选择推测步数r是很重要的。

3.3 α如何计算

3.3.1 KL散度

在这里插入图片描述

3.3.2 散度的性质

在这里插入图片描述
在这里插入图片描述
同理p(x) <= q(x)

在这里插入图片描述

  • 首先,DLK(p(x),q(x))是一个对称散度
  • 并且,DLK在[0,1]区间
    因为当p(x) = q(x),DLK = 0
    在这里插入图片描述
  • 另外,当p(x)与q(x)的支撑集不相交即p和q在任何可能的取值x上都不会同时赋予非零概率时(也即任意x,p(x)>0,有q(z)=0且任意x,q(x)>0,有p(x)=0)
    在这里插入图片描述

3.3.3 计算α β

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

3.3.4 省了多少计算时间(加速效果)

假设有足够的计算资源支撑同时运行r+1个的Mp并行计算,且不产生其他额外的耗时。

这里先定义两个量:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

当α>c,γ=1时,有
在这里插入图片描述
t2比t1耗时,确实实现了加速。也就是说,当接受率高于二者的耗时比,我们取推测步数 r=1就能实现加速,且加速比为
在这里插入图片描述
举个具体的例子,比如耗时比c=0.05,α>5%就能加速
若α趋近于1,那这个加速比将至少达到2=(1+1/1+0.05)

3.3.5 多了多少计算量(计算资源消耗)

虽然加速了,但是实际的计算量可能是增加了的。
首先,Speculative decoding一轮最多生成r+1个token,Mp并行执行r+1次。

假设token都被接受,那么平均生成每个token的计算量相当于执行一次Mp(忽略的计算量),所以没有增加计算。

反过来,如果有被拒绝的token,就产生了多余的计算,直观低也就是就少于r+1个token来平摊r+1次的计算量。也就是说生成单个token的计算量相比而言增加了。

下面就看看到底增加了多少计算量。同样先定义两个量:
在这里插入图片描述
那么,Speculative decoding执行一轮的计算量为在这里插入图片描述
所以,单个token的平均计算量为:
在这里插入图片描述


访存上,在Speculative decoding中Mp是并行执行,所以模型的权重和KVcache可以一次性都读出来,就不用像纯粹的自回归要读
在这里插入图片描述
次(生成这么多个token)。所以访存次数缩减倍数即为这个数

3.3.6 α如何确定、Mq的选择

在这里插入图片描述
上面这幅图给出了在固定c和α时的最优r,在α比较低时r并不是越高越好,
比如我们就看绿色那条线(c=0.05):
当α=0.5时,最优r为3;
a=0.6时,最优y为4;而当α=0.9时,
最优r超过了10。当r取10时,可以获得约6.86倍的加速(表1)。
在这里插入图片描述

但是,也不是说就直接取最优r了。
实际情况下,我们还需要去平衡加速比与计算量增加因子。
表1给出了一些量化结果,比如α=0.8, r取值从2增加到5时,加速比分别为从2.44提升到3.69,同时计算量也从1.23增加到1.63倍;
α=0.9, γ从2增加多到10时,加速比从2.71提升到6.86,而计算量也从1.11增大到1.60倍。

但是,实际情况中β是变化的,固定r不如动态调整r
动态计算β=Σ,min(p(x),q(x)),根据β值来调整r。

https://mp.weixin.qq.com/s/75KHpo-_JMGu8g7BGNYlzw

<think> 我们正在处理关于“投机解码”的查询。投机解码是一种在自然语言处理中用于加速大语言模型推理的技术。 需要调用网络搜索工具来获取相关信息,然后按照要求生成回答。 回答要求: 1. 整合检索到的内容,生成详细且清晰的回答,具有实用性和可操作性。 2. 禁止将所有引用内容集中在回答末尾,所以需要在回答中自然融入引用。 3. 避免使用第一人称和步骤词汇(如首先、然后),所以我们将使用直接陈述的方式。 4. 尽量将答案组织为多个方法或步骤,每个步骤间换行。 5. 如果包含公式或代码,按照指定格式。 6. 最后,添加相关问题部分。 根据网络搜索,投机解码Speculative Decoding)是一种在不改变模型输出的前提下加速大模型推理的方法。核心思想是使用一个小的“草稿模型”来生成多个候选token,然后用原始大模型(目标模型)对这些候选进行验证。如果验证通过,就可以一次性生成多个token,从而减少大模型的调用次数。 主要步骤: 1. 草稿模型快速生成一串候选token序列(例如$\gamma$个token)。 2. 目标模型并行地验证这些候选token,即一次性计算这些候选token的概率。 3. 目标模型根据概率分布决定接受哪些候选token。具体来说,对于每个位置,如果目标模型分配的token与草稿模型生成的token一致,则接受;否则,拒绝该token并回退到目标模型自己生成的token(同时丢弃后续所有候选token)。 在验证阶段,目标模型需要一次前向传播就能验证整个候选序列。这比逐个生成要快。 因此,我们可以组织回答如下: 投机解码Speculative Decoding)是一种用于加速自回归语言模型推理的技术。该方法利用一个较小的草稿模型(Draft Model)生成候选token序列,然后由目标模型(Target Model)并行验证这些候选,从而减少大模型的调用次数。 草稿模型生成候选序列 给定输入上下文,草稿模型(通常是一个较小、较快的模型)自回归地生成$\gamma$个候选token序列:$x_1, x_2, ..., x_\gamma$。 目标模型并行验证 将输入上下文和候选序列拼接后输入目标模型,进行一次前向传播。目标模型输出每个位置的下一个token的概率分布(包括候选序列之后的那个token)。对于每个位置$t$,目标模型生成的概率分布为$P(x_t | context, x_{1:t-1})$。 接受候选或回退 对于每个位置$t$,比较目标模型生成的概率分布和草稿模型生成的token: - 如果目标模型对草稿token $x_t$的概率$p_t$大于等于从目标模型自身分布中采样的随机阈值(通过拒绝采样方式),则接受该token。 - 否则,拒绝$x_t$,并用目标模型的概率分布重新采样一个token(作为该位置的输出),同时丢弃后续所有候选token。 该过程的数学表示为:设$q$是草稿模型的概率分布,$p$是目标模型的概率分布。对于位置$t$,接受草稿token $x_t$的概率为$\min(1, \frac{p(x_t)}{q(x_t)})$。如果接受,则继续;否则,从调整后的分布$\frac{\max(0, p(x)-q(x))}{\sum_{x'}\max(0, p(x')-q(x'))}$中采样一个token(或者另一种实现方式是:如果不接受,则从$p$中采样,但需要确保与拒绝采样的理论一致)。 实际应用中,为了简化,通常采用以下策略:如果目标模型在位置$t$预测的最可能token就是草稿模型生成的$x_t$,则接受;否则,拒绝。但这种策略不是理论上的拒绝采样,而是一种启发式方法。实际上,Leviathan等人(2022)和Chen等人(2023)的工作中使用了拒绝采样的理论。 加速效果 投机解码的加速效果取决于草稿模型生成候选的准确率。理想情况下,如果候选序列全部被接受,则一次目标模型的前向传播可以生成$\gamma+1$个token(因为验证$\gamma$个候选后,目标模型会额外生成第$\gamma+1$个token作为下一个起始点)。实际加速比由接受率决定。 实现注意事项 草稿模型应当小而快,这样生成候选才不会成为瓶颈。目标模型验证时可以并行处理整个候选序列,因此一次前向传播验证$\gamma$个token。 下面提供一个简化的伪代码实现: ```python def speculative_decoding(target_model, draft_model, context, gamma): # 使用草稿模型生成gamma个候选token draft_tokens = [] for _ in range(gamma): next_token = draft_model.generate_step(context) # 假设generate_step生成一个token draft_tokens.append(next_token) context = context + [next_token] # 将候选序列输入目标模型进行并行验证 # 注意:目标模型需要输出gamma+1个位置的概率(包括候选序列之后的一个token) # 假设目标模型可以一次性处理整个序列:输入为初始上下文拼接候选序列 input_sequence = initial_context + draft_tokens # 目标模型输出每个位置的下一个token的概率分布(包括最后一个位置的下一个token) # 注意:目标模型输出的概率分布是针对输入序列的每个位置,预测下一个token # 所以,对于长度为L的输入序列,目标模型会输出L个概率分布(每个分布对应下一个token) # 但这里初始上下文有L0个token,候选序列有gamma个token,所以输入序列长度为L0+gamma # 目标模型输出的概率分布个数为L0+gamma,但我们只关心从初始上下文最后一个位置开始到结束的部分(即从L0到L0+gamma-1的位置,以及最后一个位置L0+gamma用于生成下一个token) target_probs = target_model.forward(input_sequence) # 返回形状为 [L0+gamma, vocab_size] 的概率分布 # 开始验证 generated_tokens = [] for t in range(gamma): # 当前验证的位置:初始上下文的长度 + t (对应候选序列的第t个token) pos = len(initial_context) + t # 目标模型在位置pos输出的概率分布 p = target_probs[pos] draft_token = draft_tokens[t] # 使用拒绝采样:以 min(1, p(draft_token)/q(draft_token)) 的概率接受,但这里没有q,所以需要记录草稿模型生成时的概率? # 实际实现中,草稿模型生成时也会得到每个token的概率。因此,我们需要在生成候选时记录每个token在草稿模型中的概率q_t。 # 由于伪代码中未记录,我们假设可以获取到。 # 另一种简化实现:如果目标模型在位置pos预测的最可能token就是draft_token,则接受(这不是拒绝采样,但工程上常用) # 这里按照拒绝采样实现: # 从均匀分布中采样一个随机数 u = random.uniform(0, 1) # 计算接受概率:min(1, p(draft_token)/q(draft_token)) # 注意:q_t是草稿模型在生成第t个token时的概率(即在上一步的上下文下,草稿模型预测draft_token的概率) # 假设draft_probs[t]就是q_t accept_prob = min(1, p[draft_token] / draft_probs[t][draft_token]) if u <= accept_prob: generated_tokens.append(draft_token) else: # 拒绝:从调整后的分布中采样一个token:p'(x) = max(0, p(x) - q(x)) / norm 或者另一种方式:直接使用p(x)采样,然后停止候选序列 # 但实际中,拒绝时通常只使用目标模型的分布p来采样一个token,然后停止(丢弃后续候选) # 然而,根据拒绝采样理论,我们需要从调整后的分布中采样。调整后的分布为:norm * (p(x) - accept_prob * q(x)),但这样实现复杂。 # 简化:如果不接受,则从目标模型的分布p中重新采样一个不等于draft_token的token(但这样不准确)。 # 实际上,在Leviathan等的工作中,拒绝时就使用目标模型的分布p,但需要确保不会重复选择draft_token?不对,拒绝采样理论要求我们以概率p(x)接受,但如果拒绝,则从剩余的分布中采样。 # 但为了简单,这里我们直接使用目标模型的分布p进行采样(忽略已经拒绝draft_token的事实)。注意:这样会引入偏差。 # 正确做法:重新归一化一个分布:将draft_token的概率置0,然后重新归一化,再采样。或者使用目标模型的原始分布p采样(如果采样到draft_token,则重新采样?)但这样效率低。 # 根据Speculative Sampling (Leviathan et al., 2022) 的算法: # if u <= accept_prob: 接受 # else: 从 (p(x) - accept_prob * q(x)) / (1 - accept_prob) 采样(注意:这里需要归一化常数) # 但实际工程中,为了简单,可以使用启发式:如果拒绝,则用目标模型在当前位置采样一个token(可以是任何token),然后终止候选序列。 # 这里采用实际工程中常用的方法:拒绝时,从目标模型分布p中采样一个token(可以包括draft_token,但概率与p一致),然后终止。 # 注意:这样虽然不符合拒绝采样的理论分布,但在实际中常用,因为简单且效果尚可。 new_token = sample_from_distribution(p) generated_tokens.append(new_token) break # 终止,不再验证后续候选 else: # 如果所有候选都被接受,则额外生成一个token(目标模型输出的最后一个位置) last_p = target_probs[-1] # 最后一个位置的概率分布 next_token = sample_from_distribution(last_p) generated_tokens.append(next_token) return generated_tokens ``` 注意:以上伪代码仅用于说明,实际实现中需要考虑效率(如草稿模型生成候选序列时可以批量生成?)以及概率记录的问题。 参考文献(融入文中): 投机解码最早由Leviathan等(2022)和Chen等(2023)独立提出。具体实现细节可参考相关论文。 最后,按照要求,生成相关问题。 相关问题部分: 必须使用"§§相关问题§§:"作为标题,然后列出三个相关问题。 注意:问题必须基于原始问题和回答内容。 因此,我们生成以下相关问题: §§相关问题§§: 1. 投机解码中草稿模型的选择有哪些要求? 2. 投机解码在实际应用中常见的挑战有哪些? 3. 投机解码与其他大模型推理加速技术(如模型蒸馏、量化)相比有哪些优缺点? 回答完毕。 </think> ### 投机解码Speculative Decoding投机解码是一种用于加速大语言模型(LLM)推理的技术,通过结合小型草稿模型(Draft Model)和目标大模型(Target Model)实现高效生成。其核心思想是让草稿模型快速生成候选序列,再由目标模型并行验证,减少大模型的计算次数。 **基本原理** 目标模型($M_p$)计算成本高,草稿模型($M_q$)轻量但质量较低。两者协同工作: 1. **候选生成**:草稿模型基于当前上下文 $x_{1:t}$ 自回归生成 $\gamma$ 个候选 token 序列 $\tilde{x}_{t+1}, \ldots, \tilde{x}_{t+\gamma}$。 2. **并行验证**:目标模型一次性并行验证所有候选 token: $$ P(x_i | x_{1:t}, \tilde{x}_{t+1}, \ldots, \tilde{x}_{i-1}) \quad \text{for} \quad i \in [t+1, t+\gamma] $$ 3. **接受/拒绝机制**: - 若目标模型对候选 $\tilde{x}_i$ 的概率 $p(\tilde{x}_i)$ ≥ 草稿概率 $q(\tilde{x}_i)$,接受该 token。 - 否则拒绝 $\tilde{x}_i$,从修正分布 $ \text{norm}\big[\max(0, p(x)-q(x))\big] $ 采样新 token,并丢弃后续候选。 **数学实现** 拒绝采样公式: $$ \text{接受概率} = \min\left(1, \frac{p(\tilde{x}_i)}{q(\tilde{x}_i)}\right) $$ 修正分布(拒绝时采样用): $$ p'(x) = \frac{\max(0, p(x) - q(x))}{\sum_{x'}\max(0, p(x') - q(x'))} $$ **代码示例(简化版)** ```python def speculative_decoding(target_model, draft_model, prompt, gamma=5): accepted_tokens = [] while not termination_condition: # 终止条件(如生成长度) # 草稿模型生成候选序列 draft_tokens = draft_model.generate(prompt, max_length=gamma) # 目标模型并行验证 target_probs = target_model.parallel_verify(prompt, draft_tokens) # 逐个验证候选 for i, token in enumerate(draft_tokens): p = target_probs[i][token] # 目标模型概率 q = draft_model.prob(token) # 草稿模型概率 if random.random() < min(1, p / q): # 接受 accepted_tokens.append(token) prompt.extend(token) else: # 拒绝 new_token = sample_from(max(0, target_probs[i] - draft_model.probs)) # 修正分布采样 accepted_tokens.append(new_token) break # 丢弃后续候选 return accepted_tokens ``` **关键优势** - **加速效果**:理想情况下推理速度提升 $2\text{-}3\times$(Hugging Face 基准测试)。 - **无损质量**:输出分布与纯目标模型一致,无精度损失。 - **兼容性**:适用于 GPT-4、LLaMA 等自回归模型,无需修改权重。 **实践建议** - 草稿模型选择:参数量需比目标模型小 $10\times$ 以上(如 T5-small 配 LLaMA-70B)。 - 候选长度 $\gamma$:通常取 $4\text{-}10$,需平衡加速比与拒绝率。 - 工具支持:可通过 Hugging Face `transformers` + DeepSpeed 或 vLLM 框架实现。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值