博客

  • 探索线性Attention的局限性:从“集中注意力”角度出发

    近年来,Transformer架构在自然语言处理领域取得了显著的成果,而Attention机制则是其核心所在。然而,随着研究的深入,传统的标准Attention机制暴露出了一些计算复杂度和资源需求上的问题,这促使研究者们开始探索更高效的线性Attention。然而,线性Attention在实际效果上却一直不如标准Attention。本文将从“集中注意力”的角度,探讨线性Attention的局限性,并尝试给出一个合理的解释。

    Attention机制的稀疏性

    什么是稀疏性?

    在《从熵不变性看Attention的Scale操作》一文中,我们用信息熵来度量Attention的“集中注意力”程度。熵越低,Attention越有可能集中在某个token上。然而,信息熵只能用于归一化且非负的Attention矩阵,这限制了其适用范围。因此,我们引入了另一种稀疏性度量指标:S(x) = E[|x|] / sqrt(E[x^2])。这个指标与信息熵类似,S(x)越小,表示对应的随机矢量越稀疏,即越有可能“一家独大”。

    标准Attention的稀疏性

    对于标准Attention机制,f = exp,我们可以推导出其稀疏性:

    S(a) = exp(-1/2 * σ^2 * ||q||^2)

    σ||q||趋向无穷大时,S(a)趋向于0,这意味着标准Attention可以任意稀疏地“集中注意力”。

    GAU的稀疏性

    对于Gated Attention Unit (GAU)机制,f = relu2,其稀疏性较为复杂,但通过计算可以发现,只有当偏置项β小于0时,稀疏性才有机会趋于0。这表明,GAU在某些条件下也能实现较高的稀疏性。

    线性Attention的局限性

    极简线性Attention

    对于最简单的线性Attention,即不加任何激活函数f = identical,其稀疏性为:

    S(a) = sqrt(2/π * γ * exp(-β^2/(2γ^2)) + β * erf(β/(2√γ)) / (β^2 + γ^2))

    从图像可以看出,极简线性Attention的稀疏性存在一个较高的下限,这意味着它难以“集中注意力”到关键位置上。

    一般线性Attention

    线性Attention的一般形式为a_j ∝ g(q) ⋅ h(k_j),其稀疏性为:

    S(a) = 1 / sqrt(1 + (σ~ * μ~ * ||q~||_2 / ||q~||_1)^2)

    这表明,要想线性Attention变得稀疏,可以通过降低k~串行的信噪比或增大q的模长。然而,非负型线性Attention通常只能表示绝对位置的重要性,难以表达相对位置的重要性。

    线性衰减Attention

    对于带有显式递归的线性RNN模型,其稀疏性为:

    S(a) = 1 - λ^n / n(1 - λ) * sqrt(1 + λ / (1 + λ^n))

    λ < 1时,随着n趋向无穷大,S(a)趋向0。这意味着这种模型可以实现较高的稀疏性,但其注意力仅能表达固定不变的注意力衰减,难以自适应地关注到长距离的上下文。

    结论

    本文通过Attention矩阵的稀疏程度,考察了不同Attention机制的潜力,得出以下结论:

    1. 标准Attention可以实现任意稀疏的注意力矩阵。
    2. 线性Attention难以实现高稀疏性,尤其是在表示相对位置的重要性时。
    3. 带有显式衰减的线性Attention可以实现稀疏性,但其注意力固定,难以自适应。

    这些发现或许能够解释线性Attention在实际效果上略逊一筹的原因。线性Attention在“集中注意力”方面存在固有的局限性,这使得它在处理复杂上下文时表现不如标准Attention。未来的研究或许需要在如何提高线性Attention的稀疏性和灵活性上继续努力,以期实现更高效且性能优越的Transformer模型。

    参考文献

    1. 《Transformer升级之路:3、从Performer到线性Attention》
    2. 《为什么现在的LLM都是Decoder-only的架构?》
    3. 《从熵不变性看Attention的Scale操作》
    4. 《FLASH:可能是近来最有意思的高效Transformer设计》
    5. 《相对位置编码Transformer的一个理论缺陷与对策》
    6. 《如何度量数据的稀疏程度?》
    7. 《线性Attention的探索:Attention必须有个Softmax吗?》
    8. 《Google新作试图“复活”RNN:RNN能否再次辉煌?》

    通过上述分析,我们不仅理解了不同Attention机制的稀疏性差异,还揭示了线性Attention在实际应用中的局限性。希望本文的讨论能够为未来的研究提供一些新的思路和方向。

  • 深度学习中的状态空间模型(SSM)初探

    引言

    前几天,笔者看了几篇介绍SSM(State Space Model)的文章,才发现原来自己从未认真了解过SSM,于是打算认真去学习一下SSM的相关内容,顺便开了这个新坑,记录一下学习所得。

    SSM的概念由来已久,但这里我们特指深度学习中的SSM,一般认为其开篇之作是2021年的S4,不算太老,而SSM最新最火的变体大概是去年的Mamba。当然,当我们谈到SSM时,也可能泛指一切线性RNN模型,这样RWKV、RetNet还有此前我们在《Google新作试图“复活”RNN:RNN能否再次辉煌?》介绍过的LRU都可以归入此类。不少SSM变体致力于成为Transformer的竞争者,尽管笔者并不认为有完全替代的可能性,但SSM本身优雅的数学性质也值得学习一番。

    尽管我们说SSM起源于S4,但在S4之前,SSM有一篇非常强大的奠基之作《HiPPO: Recurrent Memory with Optimal Polynomial Projections》(简称HiPPO),所以本文从HiPPO开始说起。

    基本形式

    先插句题外话,上面提到的SSM代表作HiPPO、S4、Mamba的一作都是Albert Gu,他还有很多篇SSM相关的作品,毫不夸张地说,这些工作筑起了SSM大厦的基础。不论SSM前景如何,这种坚持不懈地钻研同一个课题的精神都值得我们由衷地敬佩。

    言归正传。对于事先已经对SSM有所了解的读者,想必知道SSM建模所用的是线性ODE系统:

    [latex][ x′(t) = Ax(t) + Bu(t) ][/latex]
    [latex][ y(t) = Cx(t) + Du(t) ][/latex]

    其中 [latex]( u(t) \in \mathbb{R}^{d_i}, x(t) \in \mathbb{R}^d, y(t) \in \mathbb{R}^{d_o}, A \in \mathbb{R}^{d \times d}, B \in \mathbb{R}^{d \times d_i}, C \in \mathbb{R}^{d_o \times d}, D \in \mathbb{R}^{d_o \times d_i} )[/latex]。当然我们也可以将它离散化,那么就变成一个线性RNN模型,这部分我们在后面的文章再展开。不管离散化与否,其关键词都是“线性”,那么马上就有一个很自然的问题:为什么是线性系统?线性系统够了吗?

    我们可以从两个角度回答这个问题:线性系统既足够简单,也足够复杂。简单是指从理论上来说,线性化往往是复杂系统的一个最基本近似,所以线性系统通常都是无法绕开的一个基本点;复杂是指即便如此简单的系统,也可以拟合异常复杂的函数,为了理解这一点,我们只需要考虑一个 [latex]( \mathbb{R}^4 )[/latex] 的简单例子:

    [latex][ x′(t) = \begin{pmatrix} 1 & 0 & 0 & 0 \ 0 & -1 & 0 & 0 \ 0 & 0 & 0 & 1 \ 0 & 0 & -1 & 0 \end{pmatrix} x(t) ][/latex]

    这个例子的基本解是 [latex]( x(t) = (e^t, e^{-t}, \sin t, \cos t) )[/latex]。这意味着只要 [latex]( d )[/latex] 足够大,该线性系统就可以通过指数函数和三角函数的组合来拟合足够复杂的函数,而我们知道拟合能力很强的傅里叶级数也只不过是三角函数的组合,如果再加上指数函数显然就更强了,因此可以想象线性系统也有足够复杂的拟合能力。

    当然,这些解释某种意义上都是“马后炮”。HiPPO给出的结果更加本质:当我们试图用正交基去逼近一个动态更新的函数时,其结果就是如上的线性系统。这意味着,HiPPO不仅告诉我们线性系统可以逼近足够复杂的函数,还告诉我们怎么去逼近,甚至近似程度如何。

    有限压缩

    接下来,我们只考虑 [latex]( d_i = 1 )[/latex] 的特殊情形,[latex]( d_i > 1 )[/latex] 只不过是 [latex]( d_i = 1 )[/latex] 时的并行推广。此时,[latex]( u(t) )[/latex] 的输出是一个标量,进一步地,作为开头我们先假设 [latex]( t \in [0, 1] )[/latex],HiPPO的目标是:用一个有限维的矢量来储存这一段 [latex]( u(t) )[/latex] 的信息。

    看上去这是一个不大可能的需求,因为 [latex]( t \in [0, 1] )[/latex] 意味着 [latex]( u(t) )[/latex] 可能相当于无限个点组成的矢量,压缩到一个有限维的矢量可能严重失真。不过,如果我们对 [latex]( u(t) )[/latex] 做一些假设,并且允许一些损失,那么这个压缩是有可能做到的,并且大多数读者都已经尝试过。比如,当 [latex]( u(t) )[/latex] 在某点 ( n+1 ) 阶可导的,它对应的 ( n ) 阶泰勒展开式往往是 [latex]( u(t) )[/latex] 的良好近似,于是我们可以只储存展开式的 ( n+1 ) 个系数来作为 [latex]( u(t) )[/latex] 的近似表征,这就成功将 [latex]( u(t) )[/latex] 压缩为一个 ( n+1 ) 维矢量。

    当然,对于实际遇到的数据来说,“( n+1 ) 阶可导”这种条件可谓极其苛刻,我们通常更愿意使用在平方可积条件下的正交函数基展开,比如傅里叶(Fourier)级数,它的系数计算公式为:

    [latex][ c_n = \int_0^1 u(t) e^{-2i\pi nt} dt ][/latex]

    这时候取一个足够大的整数 [latex]( N )[/latex],只保留 [latex]( |n| \leq N )[/latex] 的系数,那么就将 [latex]( u(t) )[/latex] 压缩为一个 ( 2N+1 ) 维的矢量了。

    接下来,问题难度就要升级了。刚才我们说 [latex]( t \in [0, 1] )[/latex],这是一个静态的区间,而实际中 [latex]( u(t) )[/latex] 代表的是持续采集的信号,所以它是不断有新数据进入的,比如现在我们近似了 [latex]( [0, 1] )[/latex] 区间的数据,马上就有 [latex]( [1, 2] )[/latex] 的数据进来,你需要更新逼近结果来试图记忆整个 ( [0, 2] ) 区间,接下来是 ( [0, 3] )、( [0, 4] ) 等等,这我们称为“在线函数逼近”。而上面的傅里叶系数公式只适用于区间 ( [0, 1] ),因此需要将它进行推广。

    为此,我们设 [latex]( t \in [0, T] ),( s \mapsto t \leq T(s) )[/latex] 是 ( [0, 1] ) 到 ( [0, T] ) 的一个映射,那么 [latex]( u(t \leq T(s)) )[/latex] 作为 ( s ) 的函数时,它的定义区间就是 [latex]( [0, 1] )[/latex],于是就可以复用傅里叶系数公式:

    [latex][ c_n(T) = \int_0^1 u(t \leq T(s)) e^{-2i\pi ns} ds ][/latex]

    这里我们已经给系数加了标记 [latex]( (T) )[/latex],以表明此时的系数会随着 [latex]( T )[/latex] 的变化而变化。

    线性初现

    能将 [latex]( [0, 1] )[/latex] 映射到 [latex]( [0, T] )[/latex] 的函数有无穷多,而最终结果也因 [latex]( t \leq T(s) )[/latex] 而异,一些比较直观且相对简单的选择如下:

    1. [latex]( t \leq T(s) = sT )[/latex],即将 ( [0, 1] ) 均匀地映射到 ( [0, T] );
    2. 注意 [latex]( t \leq T(s) )[/latex] 并不必须是满射,所以像 [latex]( t \leq T(s) = s + T – 1 )[/latex] 也是允许的,这意味着只保留了最邻近窗口 ( [T – 1, T] ) 的信息,丢掉了更早的部分,更一般地有 [latex]( t \leq T(s) = sw + T – w )[/latex],其中 ( w ) 是一个常数,这意味着 ( T – w ) 前的信息被丢掉了;
    3. 也可以选择非均匀映射,比如 [latex]( t \leq T(s) = T\sqrt{s} )[/latex],它同样是 ( [0, 1] ) 到 ( [0, T] ) 的满射,但 ( s = 1/4 ) 时就映射到 ( T/2 ) 了,这意味着我们虽然关注全局的历史,但同时更侧重于 ( T ) 时刻附近的信息。

    现在我们以 [latex]( t \leq T(s) = (s + 1)w/2 + T – w )[/latex] 为例,代入傅里叶系数公式得到:

    [latex][ c_n(T) = \int_0^1 u(sw + T – w) e^{-2i\pi ns} ds ][/latex]

    现在我们两边求关于 ( T ) 的导数:

    [latex][ \frac{d}{dT} c_n(T) = \frac{1}{w} \left[ u(T) – u(T – w) \right] + \frac{2i\pi n}{w} c_n(T) ][/latex]

    其中我们用了分部积分公式。由于我们只保留了 [latex]( |n| \leq N )[/latex] 的系数,所以根据傅里叶级数的公式,可以认为如下是 [latex]( u(sw + T – w) )[/latex] 的一个良好近似:

    [latex][ u(sw + T – w) \approx \sum_{k=-N}^N c_k(T) e^{2i\pi ks} ][/latex]

    那么 [latex]( u(T – w) = u(sw + T – w) \big|{s=0} \approx \sum{k=-N}^N c_k(T) )[/latex],代入上式得:

    [latex][ \frac{d}{dT} c_n(T) \approx \frac{1}{w} \left[ u(T) – \sum_{k=-N}^N c_k(T) \right] + \frac{2i\pi n}{w} c_n(T) ][/latex]

    将 ( T ) 换成 ( t ),然后所有的 [latex]( c_n(t) )[/latex] 堆在一起记为 [latex]( x(t) = (c_{-N}, c_{-(N-1)}, \ldots, c_0, \ldots, c_{N-1}, c_N) )[/latex],并且不区分 [latex]( \approx )[/latex] 和 [latex]( = )[/latex],那么就可以写出:

    [latex][ x′(t) = A x(t) + B u(t) ][/latex]

    其中:

    [latex][ A_{n,k} = \begin{cases} \frac{2i\pi n – 1}{w}, & k = n \ -\frac{1}{w}, & k \ne n \end{cases}, \quad B_n = \frac{1}{w} ][/latex]

    这就出现了如上所示的线性ODE系统。即当我们试图用傅里叶级数去记忆一个实时函数的最邻近窗口内的状态时,结果自然而然地导致了一个线性ODE系统。

    一般框架

    当然,目前只是选择了一个特殊的 [latex]( t \leq T(s) )[/latex],换一个 [latex]( t \leq T(s) )[/latex] 就不一定有这么简单的结果了。此外,傅里叶级数的结论是在复数范围内的,进一步实数化也可以,但形式会变得复杂起来。所以,我们要将这一过程推广成一个一般化的框架,从而得到更一般、更简单的纯实数结论。

    设 [latex]( t \in [a, b] )[/latex],并且有目标函数 [latex]( u(t) )[/latex] 和函数基 [latex]( { g_n(t) }_{n=0}^N )[/latex],我们希望有后者的线性组合来逼近前者,目标是最小化 [latex]( L^2 )[/latex] 距离:

    [latex][ \arg\min_{c_1, \ldots, c_N} \int_a^b \left[ u(t) – \sum_{n=0}^N c_n g_n(t) \right]^2 dt ][/latex]

    这里我们主要在实数范围内考虑,所以方括号直接平方就行,不用取模。更一般化的目标函数还可以再加个权重函数 [latex]( \rho(t) )[/latex],但我们这里就不考虑了,毕竟HiPPO的主要结论其实也没考虑这个权重函数。

    对目标函数展开,得到:

    [latex][ \int_a^b u^2(t) dt – 2 \sum_{n=0}^N c_n \int_a^b u(t) g_n(t) dt + \sum_{m=0}^N \sum_{n=0}^N c_m c_n \int_a^b g_m(t) g_n(t) dt ][/latex]

    这里我们只考虑标准正交函数基,其定义为 [latex]( \int_a^b g_m(t) g_n(t) dt = \delta_{m,n} ),( \delta_{m,n} )[/latex] 是克罗内克δ函数,此时上式可以简化成:

    [latex][ \int_a^b u^2(t) dt – 2 \sum_{n=0}^N c_n \int_a^b u(t) g_n(t) dt + \sum_{n=0}^N c_n^2 ][/latex]

    这只是一个关于 [latex]( c_n )[/latex] 的二次函数,它的最小值是有解析解的:

    [latex][ c^*_n = \int_a^b u(t) g_n(t) dt ][/latex]

    这也被称为 [latex]( u(t) )[/latex] 与 [latex]( g_n(t) )[/latex] 的内积,它是有限维矢量空间的内积到函数空间的并行推广。简单起见,在不至于混淆的情况下,我们默认 [latex]( c_n )[/latex] 就是 [latex]( c^*_n )[/latex]。

    接下来的处理跟上一节是一样的,我们要对一般的 [latex]( t \in [0, T] )[/latex] 考虑 [latex]( u(t) )[/latex] 的近似,那么找一个 [latex]( [a, b] )[/latex] 到 [latex]( [0, T] )[/latex] 的映射 [latex]( s \mapsto t \leq T(s) )[/latex],然后计算系数:

    [latex][ c_n(T) = \int_a^b u(t \leq T(s)) g_n(s) ds ][/latex]

    同样是两边求 [latex]( T )[/latex] 的导数,然后用分部积分法:

    [latex][ \frac{d}{dT} c_n(T) = \int_a^b u'(t \leq T(s)) \frac{\partial t \leq T(s)}{\partial T} g_n(s) ds ][/latex]

    [latex][ = \int_a^b \left( \frac{\partial t \leq T(s)}{\partial T} / \frac{\partial t \leq T(s)}{\partial s} \right) g_n(s) du(t \leq T(s)) ][/latex]

    [latex][ = \left( \frac{\partial t \leq T(s)}{\partial T} / \frac{\partial t \leq T(s)}{\partial s} \right) g_n(s) \bigg|_{s=b}^{s=a} – \int_a^b u(t \leq T(s)) d \left[ \left( \frac{\partial t \leq T(s)}{\partial T} / \frac{\partial t \leq T(s)}{\partial s} \right) g_n(s) \right] ][/latex]

    请勒让德

    接下来的计算,就依赖于 [latex]( g_n(t) )[/latex] 和 [latex]( t \leq T(s) )[/latex] 的具体形式了。HiPPO的全称是High-order Polynomial Projection Operators,第一个P正是多项式(Polynomial)的首字母,所以HiPPO的关键是选取多项式为基。现在我们请出继傅里叶之后又一位大牛——勒让德(Legendre),接下来我们要选取的函数基正是以他命名的“勒让德多项式”。

    勒让德多项式 [latex]( p_n(t) )[/latex] 是关于 [latex]( t )[/latex] 的 [latex]( n )[/latex] 次函数,定义域为 [latex]( [-1, 1] )[/latex],满足:

    [latex][ \int_{-1}^1 p_m(t) p_n(t) dt = \frac{2}{2n+1} \delta_{m,n} ][/latex]

    所以 [latex]( p_n(t) )[/latex] 之间只是正交,还不是标准(平分积分为1),[latex]( g_n(t) = \sqrt{\frac{2n+1}{2}} p_n(t) )[/latex] 才是标准正交基。

    当我们对函数基 [latex]( {1, t, t^2, \ldots, t^n} )[/latex] 执行施密特正交化时,其结果正是勒让德多项式。相比傅里叶基,勒让德多项式的好处是它是纯粹定义在实数空间中的,并且多项式的形式能够有助于简化部分 [latex]( t \leq T(s) )[/latex] 的推导过程,这一点我们后面就可以看到。勒让德多项式有很多不同的定义和性质,这里我们不一一展开,有兴趣的读者自行看维基百科介绍即可。

    接下来我们用到两个递归公式来推导一个恒等公式,这两个递归公式是:

    [latex][ p′_{n+1}(t) – p′_{n-1}(t) = (2n+1) p_n(t) ][/latex]
    [latex][ p′_{n+1}(t) = (n+1) p_n(t) + t p′_n(t) ][/latex]

    由第一个公式迭代得到:

    [latex][ p′_{n+1}(t) = (2n+1) p_n(t) + (2n−3) p_{n−2}(t) + (2n-7) p_{n-4}(t) + ⋯ = \sum_{k=0}^n (2k+1) χ_{n−k} p_k(t) ][/latex]

    其中当 [latex]( k )[/latex] 是偶数时 [latex]( χ_k = 1 )[/latex] 否则 [latex]( χ_k = 0 )[/latex] 。代入第二个公式得到:

    [latex][ t p′_n(t) = n p_n(t) + (2n−3) p{n-2}(t) + (2n-7) p_{n-4}(t) + ⋯ ][/latex]

    继而有:

    [latex][ (t+1) p′_n(t) = n p_n(t) + (2n-1) p_{n−1}(t) + (2n-3) p_{n-2}(t) + ⋯ = – (n+1) p_n(t) + \sum_{k=0}^n (2k+1) p_k(t) ][/latex]

    这些就是等会要用到的恒等式。此外,勒让德多项式满足 ( p_n(1) = 1, p_n(-1) = (-1)^n ),这个边界值后面也会用到。

    正如 ( n ) 维空间中不止有一组正交基也一样,正交多项式也不止有勒让德多项式一种,比如还有切比雪夫(Chebyshev)多项式,如果算上加权的目标函数(即 [latex]( ρ(t) ≢ 1 )[/latex] ),还有拉盖尔多项式等,这些在原论文中都有提及,但HiPPO的主要结论还是基于勒让德多项式展开的,所以剩余部分这里也不展开讨论了。

    邻近窗口

    完成准备工作后,我们就可以代入具体的 [latex]( t ≤ T(s) )[/latex] 进行计算了,计算过程跟傅里叶级数的例子大同小异,只不过基函数换成了勒让德多项式构造的标准正交基 [latex]( g_n(t) = \sqrt{\frac{2n+1}{2}} p_n(t) )[/latex]。作为第一个例子,我们同样先考虑只保留最邻近窗口的信息,此时 [latex]( t ≤ T(s) = \frac{(s+1)w}{2} + T – w )[/latex] 将 [latex]( [−1, 1] )[/latex] 映射到 [latex]( [T−w, T] )[/latex],原论文将这种情形称为“LegT(Translated Legendre)”。

    直接代入之前得到的公式,马上得到:

    [latex][ \frac{d}{dT} c_n(T) = \sqrt{\frac{2(2n+1)}{w}} [u(T) – (−1)^n u(T−w)] – \frac{2}{w} \int_{-1}^1 u\left(\frac{(s+1)w}{2} + T – w\right) g′_n(s) ds ][/latex]

    我们首先处理 [latex]( u(T−w) )[/latex] 项,跟傅里叶级数那里同样的思路,我们截断 ( n ≤ N ) 作为 [latex]( u\left(\frac{(s+1)w}{2} + T – w\right) )[/latex] 的一个近似:

    [latex][ u\left(\frac{(s+1)w}{2} + T – w\right) ≈ \sum_{k=0}^N c_k(T) g_k(s) ][/latex]

    从而有 [latex]( u(T−w) ≈ \sum_{k=0}^N c_k(T) g_k(−1) = \sum_{k=0}^N (−1)^k c_k(T) \sqrt{\frac{2k+1}{2}} )[/latex] 。接着,利用之前的递归公式得到:

    [latex][ \int_{-1}^1 u\left(\frac{(s+1)w}{2} + T – w\right) g′n(s) ds = \int{-1}^1 u\left(\frac{(s+1)w}{2} + T – w\right) \sqrt{\frac{2n+1}{2}} p′_n(s) ds ][/latex]

    [latex][ = \int_{-1}^1 u\left(\frac{(s+1)w}{2} + T – w\right) \sqrt{\frac{2n+1}{2}} \left[\sum_{k=0}^{n-1} (2k+1) \chi_{n-1-k} p_k(s) \right] ds ][/latex]

    [latex][ = \int_{-1}^1 u\left(\frac{(s+1)w}{2} + T – w\right) \sqrt{\frac{2n+1}{2}} \left[\sum_{k=0}^{n-1} \sqrt{2(2k+1)} \chi_{n-1-k} g_k(s) \right] ds ][/latex]

    [latex][ = \sqrt{\frac{2n+1}{2}} \sum_{k=0}^{n-1} \sqrt{2(2k+1)} \chi_{n-1-k} c_k(T) ][/latex]

    将这些结果集成起来,就有:

    [latex][ \frac{d}{dT} c_n(T) ≈ \sqrt{\frac{2(2n+1)}{w}} u(T) – \sqrt{\frac{2(2n+1)}{w}} (−1)^n \sum_{k=0}^N (−1)^k c_k(T) \sqrt{\frac{2k+1}{2}} – \frac{2}{w} \sqrt{\frac{2n+1}{2}} \sum_{k=0}^{n-1} \sqrt{2(2k+1)} \chi_{n-1-k} c_k(T) ][/latex]

    再次地,将 [latex]( T )[/latex] 换回 [latex]( t )[/latex],并将所有的 [latex]( c_n(t) )[/latex] 堆在一起记为 [latex]( x(t) = (c_0, c_1, ⋯, c_N) )[/latex],那么根据上式可以写出:

    [latex][ x′(t) = A x(t) + B u(t) ][/latex]

    其中:

    [latex][ A_{n,k} = \begin{cases} -\frac{2}{w} \sqrt{\frac{(2n+1)(2k+1)}{2}}, & k < n \ -\frac{2}{w} \sqrt{\frac{(2n+1)(2k+1)}{2}} (−1)^{n−k}, & k ≥ n \end{cases}, \quad B_n = \sqrt{\frac{2(2n+1)}{w}} ][/latex]

    我们还可以给每个 [latex]( c_n(T) )[/latex] 都引入一个缩放因子,来使得上述结果更一般化。比如我们设 [latex]( c_n(T) = λ_n \tilde{c}_n(T) )[/latex],代入上式整理得:

    [latex][ \frac{d}{dT} \tilde{c}n(T) ≈ \sqrt{\frac{2(2n+1)}{w}} \frac{u(T)}{λ_n} – \sqrt{\frac{2(2n+1)}{w}} \frac{(−1)^n}{λ_n} \sum{k=0}^N \frac{(−1)^k c_k(T)}{λk} \sqrt{\frac{2k+1}{2}} – \frac{2}{w} \sqrt{\frac{2n+1}{2}} \sum{k=0}^{n-1} \frac{λk}{λ_n} \sqrt{2(2k+1)} \chi{n-1-k} \tilde{c}_k(T) ][/latex]

    如果取 [latex]( λ_n = \sqrt{2} )[/latex],那么 ( A ) 不变,[latex]( B_n = \sqrt{2(2n+1)} )[/latex],这就对齐了原论文的结果。如果取 [latex]( λ_n = \sqrt{\frac{2}{2n+1}} )[/latex],那么就得到了Legendre Memory Units中的结果:

    [latex][ x′(t) = A x(t) + B u(t) ][/latex]

    其中:

    [latex][ A_{n,k} = \begin{cases} 2n+1, & k < n \ (−1)^{n−k} (2n+1), & k ≥ n \end{cases}, \quad B_n = 2n+1 ][/latex]

    这些形式在理论上都是等价的,但可能存在不同的数值稳定性。比如一般来说当 [latex]( u(t) )[/latex] 的性态不是特别糟糕时,我们可以预期 [latex]( n )[/latex] 越大,[latex]( |c_n| )[/latex] 的值就相对越小,这样直接用 [latex]( c_n )[/latex] 的话 [latex]( x(t) )[/latex] 矢量的每个分量的尺度就不大对等,这样的系统在实际计算时容易出现数值稳定问题,而取 [latex]( λ_n = \sqrt{\frac{2}{2n+1}} )[/latex] 改用 [latex]( \tilde{c}_n )[/latex] 的话意味着数值小的分量会被适当放大,可能有助于缓解多尺度问题从而使得数值计算更稳定。

    整个区间

    现在我们继续计算另一个例子:[latex]( t ≤ T(s) = \frac{(s+1)T}{2} )[/latex],它将 [latex]( [−1, 1] )[/latex] 均匀映射到 [latex]( [0, T] )[/latex],这意味着我们没有舍弃任何历史信息,并且平等地对待所有历史,原论文将这种情形称为“LegS(Scaled Legendre)”。

    同样地,通过代入之前得到的公式:

    [latex][ \frac{d}{dT} c_n(T) = \sqrt{\frac{2(2n+1)}{T}} u(T) – \frac{1}{T} \int_{-1}^1 u\left(\frac{(s+1)T}{2}\right) (s+1) g′_n(s) ds ][/latex]

    利用之前的递归公式得到:

    [latex][ \int_{-1}^1 u\left(\frac{(s+1)T}{2}\right) (s+1) g′n(s) ds = \int{-1}^1 u\left(\frac{(s+1)T}{2}\right) \left[g_n(s) + (s+1) g′_n(s)\right] ds ][/latex]

    [latex][ = c_n(T) + \int_{-1}^1 u\left(\frac{(s+1)T}{2}\right) \sqrt{\frac{2n+1}{2}} p′_n(s) ds ][/latex]

    [latex][ = c_n(T) + \int_{-1}^1 u\left(\frac{(s+1)T}{2}\right) \left[-(n+1) g_n(s) + \sum_{k=0}^n \sqrt{(2n+1)(2k+1)} g_k(s)\right] ds ][/latex]

    [latex][ = c_n(T) – n c_n(T) + \sum_{k=0}^n \sqrt{(2n+1)(2k+1)} c_k(T) ][/latex]

    于是有:

    [latex][ \frac{d}{dT} c_n(T) = \sqrt{\frac{2(2n+1)}{T}} u(T) – \frac{1}{T} \left(-n c_n(T) + \sum_{k=0}^n \sqrt{(2n+1)(2k+1)} c_k(T)\right) ][/latex]

    将 ( T ) 换回 ( t ),将所有的 ( c_n(t) ) 堆在一起记为 [latex]( x(t) = (c_0, c_1, ⋯, c_N) )[/latex],那么根据上式可以写出:

    [latex][ x′(t) = A x(t) + B u(t) ][/latex]

    其中:

    [latex][ A_{n,k} = \begin{cases} \sqrt{(2n+1)(2k+1)}, & k < n \ n+1, & k = n \ 0, & k > n \end{cases}, \quad B_n = \sqrt{2(2n+1)} ][/latex]

    引入缩放因子来一般化结果也是可行的:设 [latex]( c_n(T) = λ_n \tilde{c}_n(T) )[/latex],代入上式整理得:

    [latex][ \frac{d}{dT} \tilde{c}n(T) = \sqrt{\frac{2(2n+1)}{T}} \frac{u(T)}{λ_n} – \frac{1}{T} \left(-n \tilde{c}_n(T) + \sum{k=0}^n \sqrt{(2n+1)(2k+1)} \frac{λ_k}{λ_n} \tilde{c}_k(T)\right) ][/latex]

    取 [latex]( λ_n = \sqrt{\frac{2}{2n+1}} )[/latex],就可以让 ( A ) 不变,[latex]( B_n = \sqrt{2(2n+1)} )[/latex],就对齐了原论文的结果。如果取 [latex]( λ_n = \sqrt{\frac{2}{2n+1}} )[/latex],就可以像上一节LegT的结果一样去掉根号:

    [latex][ x′(t) = A x(t) + B u(t) ][/latex]

    其中:

    [latex][ A_{n,k} = \begin{cases} 2(2n+1), & k < n \ n+1, & k = n \ 0, & k > n \end{cases}, \quad B_n = 2(2n+1) ][/latex]

    但原论文没有考虑这种情况,原因不详。

    延伸思考

    回顾Leg-S的整个推导,我们可以发现其中关键一步是将 [latex]( (s+1) g′_n(s) )[/latex] 拆成 [latex]( g_0(s), g_1(s), ⋯, g_n(s) )[/latex] 的线性组合,对于正交多项式来说,[latex]( (s+1) g′_n(s) )[/latex] 是一个 [latex]( n )[/latex] 次多项式,所以这种拆分必然可以精确成立,但如果是傅立叶级数的情况,[latex]( g_n(s) )[/latex] 是指数函数,此时类似的拆分做不到了,至少不能精确地做到,所以可以说选取正交多项式为基的根本目的是简化后面推导。

    特别要指出的是,HiPPO是一个自下而上的框架,它并没有一开始就假设系统必须是线性的,而是从正交基逼近的角度反过来推出其系数的动力学满足一个线性ODE系统,这样一来我们就可以确信,只要认可所做的假设,那么线性ODE系统的能力就是足够的,而不用去担心线性系统的能力限制了你的发挥。

    当然,HiPPO对于每一个解所做的假设及其物理含义也很清晰,所以对于重用了HiPPO矩阵的SSM,它怎么储存历史、能储存多少历史,从背后的HiPPO假设就一清二楚。比如LegT就是只保留 [latex]( w )[/latex] 大小的最邻近窗口信息,如果你用了LegT的HiPPO矩阵,那么就类似于一个Sliding Window Attention;而LegS理论上可以捕捉全部历史,但这有个分辨率问题,因为 [latex]( x(t) )[/latex] 的维度代表了拟合的阶数,它是一个固定值,用同阶的函数基去拟合另一个函数,肯定是区间越小越准确,区间越大误差也越大,这就好比为了一次性看完一幅大图,那么我们必须站得更远,从而看到的细节越少。

    诸如RWKV、LRU等模型,并没有重用HiPPO矩阵,而是改为可训练的矩阵,原则上具有更多的可能性来突破瓶颈,但从前面的分析大致上可以感知到,不同矩阵的线性ODE只是函数基不同,但本质上可能都只是有限阶函数基逼近的系数动力学。既然如此,分辨率与记忆长度就依然不可兼得,想要记忆更长的输入并且保持效果不变,那就只能增加整个模型的体量(即相当于增加hidden_size),这大概是所有线性系统的特性。

    文章小结

    本文以尽可能简单的方式重复了《HiPPO: Recurrent Memory with Optimal Polynomial Projections》(简称HiPPO)的主要推导。HiPPO通过适当的记忆假设,自下而上地导出了线性ODE系统,并且针对勒让德多项式的情形求出了相应的解析解(HiPPO矩阵),其结果被后来诸多SSM(State Space Model)使用,可谓是SSM的重要奠基之作。

    HiPPO框架展现了优雅的数学结构和强大的应用潜力,在处理时间序列数据时提供了一种高效的记忆机制。未来的研究可以进一步探索其在不同领域中的应用和改进。

    参考文献: https://spaces.ac.cn/archives/10114

  • 何加盐深度揭秘:我是怎么找资料的?

    在信息爆炸的时代,获取和整理信息的能力变得尤为重要。何加盐在其博客文章《何加盐深度揭秘:我是怎么找资料的?》中,详细介绍了他搜集资料的技巧和方法。以下是文章中的一些关键内容和技巧总结:

    一、用好搜索引擎

    1. 善于选择时间段

    通过设置搜索时间段,可以过滤掉大量无关信息。例如,想要了解百度创始人李彦宏的早期历程,可以将搜索时间设置在2000-2004年,这样可以找到更多关于他创业初期的报道。

    2. 善用关键词的组合

    关键词组合能让搜索结果更加精准。例如,搜索“程维 滴滴”可能会得到大量杂乱的信息,但如果组合搜索“程维 王刚”,就能找到更多关于滴滴和投资人的相关资料。

    3. 善用一些搜索命令

    • filetype:如“阿里巴巴组织结构 filetype:pdf”,能搜索到特定类型的文件。
    • 减号:如“张一鸣 -演员”,能过滤掉不相关的搜索结果。
    • site:如“马化腾 site:qq.com”,可以只搜索某个特定网站的信息。

    二、用好社交网站

    社交平台如微博、微信、知乎和脉脉等是搜集资料的重要渠道。通过浏览目标人物的社交账号,可以获取大量一手信息。例如,何加盐在写程维时,浏览了程维的所有微博;在写王兴时,浏览了王兴的饭否账号。

    三、用好视频网站

    视频网站是一个信息宝库,特别是知名人物的演讲、采访视频能提供大量鲜活的细节。例如,写程维时,何加盐参考了吴晓波的《二十年二十人》对程维的采访视频。

    四、用好名人演讲

    名人在各种场合的演讲活动通常会透露很多重要信息。马云在“18罗汉大会”上的演讲是了解他创业史的重要资料。

    五、用好传记作品

    传记是了解人物和企业最全面的方式。例如,《腾讯传》是了解腾讯发展历程的必读书目;《这世界既残酷,也温柔》是了解孙宇晨的主要资料来源之一。

    六、用好杂志

    专业杂志如《财经杂志》、《中国企业家》、《南方人物周刊》等,对人物和企业的报道非常深入。例如,何加盐写TMD三部曲时,引用了不少《财经杂志》的内容。

    总结

    何加盐分享的这些方法,旨在帮助大家更高效地搜集和整理信息。在此过程中,要注意两点:一是信息的真伪,需要多方交叉验证;二是使用好的工具,付费版通常能提供更高效的服务。

    希望这些技巧对你有所帮助!


    Learn more:

    1. 何加盐深度揭秘:我是怎么找资料的?-CSDN博客
  • DeepSeek-V2 大模型

    DeepSeek-V2 是一个强大、经济高效的混合专家语言模型。它具有2360亿个总参数,每个标记激活的参数为210亿。相比于DeepSeek 670亿,DeepSeek-V2 在性能上更强大,同时节省了42.5%的训练成本,将KV缓存减少了93.3%,并将最大生成吞吐量提升了5.76倍[1]

    DeepSeek-V2的配置方法如下:

    1. 下载模型:DeepSeek-V2 可以从 HuggingFace 的官方网站下载[1]
    2. 安装依赖:在配置 DeepSeek-V2 之前,确保已经安装了以下依赖项:
    • Python 3.6 或更高版本
    • PyTorch 1.7.0 或更高版本
    • Transformers 4.0.0 或更高版本
    1. 导入模型:在 Python 代码中导入 DeepSeek-V2 模型,可以使用以下代码:
       from transformers import GPT2LMHeadModel, GPT2Tokenizer
    
       model_name = "deepseek-ai/DeepSeek-V2"
       tokenizer = GPT2Tokenizer.from_pretrained(model_name)
       model = GPT2LMHeadModel.from_pretrained(model_name)
    1. 使用模型:使用导入的模型进行文本生成或其他自然语言处理任务。以下是一个简单的示例:
       input_text = "你好,DeepSeek-V2!"
       input_ids = tokenizer.encode(input_text, return_tensors="pt")
       output = model.generate(input_ids, max_length=50)
       generated_text = tokenizer.decode(output, skip_special_tokens=True)
       print(generated_text)

    这些是配置 DeepSeek-V2 的基本步骤。根据具体的应用场景和需求,你可能需要进一步调整模型的参数和设置。


    Learn more:

    1. GitHub – deepseek-ai/DeepSeek-V2: DeepSeek-V2: A Strong, Economical, and Efficient Mixture-of-Experts Language Model
    2. README.md · deepseek-ai/DeepSeek-V2 at main
    3. DeepSeek-V2/README.md at main · deepseek-ai/DeepSeek-V2 · GitHub

    配置和使用 DeepSeek-V2:强大、经济高效的混合专家语言模型

    DeepSeek-V2 是一款强大且经济高效的混合专家(Mixture-of-Experts, MoE)语言模型。相比于之前的 DeepSeek 67B,DeepSeek-V2 不仅性能更强,还显著降低了训练成本和资源消耗。本文将介绍如何配置和使用 DeepSeek-V2 模型。

    模型简介

    DeepSeek-V2 拥有 2360 亿个总参数,其中每个标记激活 210 亿个参数。与 DeepSeek 67B 相比,DeepSeek-V2 在多个方面表现出色:

    • 性能提升:模型性能显著提升。
    • 成本节约:节省了 42.5% 的训练成本。
    • 资源优化:KV 缓存减少了 93.3%。
    • 吞吐量提升:最大生成吞吐量提升了 5.76 倍。

    详细信息可以参考 DeepSeek-V2 的 GitHub 页面

    模型下载

    DeepSeek-V2 可以从 HuggingFace 的官方网站下载。以下是下载链接:

    环境依赖

    在配置 DeepSeek-V2 之前,确保你的环境中已经安装了以下依赖项:

    • Python 3.6 或更高版本
    • PyTorch 1.7.0 或更高版本
    • Transformers 4.0.0 或更高版本

    导入和使用模型

    导入模型

    在 Python 代码中导入 DeepSeek-V2 模型,可以使用以下代码:

    from transformers import GPT2LMHeadModel, GPT2Tokenizer
    
    model_name = "deepseek-ai/DeepSeek-V2"
    tokenizer = GPT2Tokenizer.from_pretrained(model_name)
    model = GPT2LMHeadModel.from_pretrained(model_name)

    使用模型进行文本生成

    使用导入的模型进行文本生成或其他自然语言处理任务。以下是一个简单的示例:

    input_text = "你好,DeepSeek-V2!"
    input_ids = tokenizer.encode(input_text, return_tensors="pt")
    output = model.generate(input_ids, max_length=50)
    generated_text = tokenizer.decode(output[0], skip_special_tokens=True)
    print(generated_text)

    详细配置和优化

    使用 Huggingface 的 Transformers 进行推理

    你可以直接使用 Huggingface 的 Transformers 库来进行模型推理。以下是一个示例代码:

    import torch
    from transformers import AutoTokenizer, AutoModelForCausalLM, GenerationConfig
    
    model_name = "deepseek-ai/DeepSeek-V2"
    tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
    max_memory = {i: "75GB" for i in range(8)}
    model = AutoModelForCausalLM.from_pretrained(model_name, trust_remote_code=True, device_map="sequential", torch_dtype=torch.bfloat16, max_memory=max_memory, attn_implementation="eager")
    model.generation_config = GenerationConfig.from_pretrained(model_name)
    model.generation_config.pad_token_id = model.generation_config.eos_token_id
    
    text = "An attention function can be described as mapping a query and a set of key-value pairs to an output, where the query, keys, values, and output are all vectors. The output is"
    inputs = tokenizer(text, return_tensors="pt")
    outputs = model.generate(**inputs.to(model.device), max_new_tokens=100)
    
    result = tokenizer.decode(outputs[0], skip_special_tokens=True)
    print(result)

    使用 vLLM 进行推理(推荐)

    为了更高效地执行模型推理,可以使用 vLLM 解决方案。以下是示例代码:

    from transformers import AutoTokenizer
    from vllm import LLM, SamplingParams
    
    max_model_len, tp_size = 8192, 8
    model_name = "deepseek-ai/DeepSeek-V2-Chat"
    tokenizer = AutoTokenizer.from_pretrained(model_name)
    llm = LLM(model=model_name, tensor_parallel_size=tp_size, max_model_len=max_model_len, trust_remote_code=True, enforce_eager=True)
    sampling_params = SamplingParams(temperature=0.3, max_tokens=256, stop_token_ids=[tokenizer.eos_token_id])
    
    messages_list = [
        [{"role": "user", "content": "Who are you?"}],
        [{"role": "user", "content": "Translate the following content into Chinese directly: DeepSeek-V2 adopts innovative architectures to guarantee economical training and efficient inference."}],
        [{"role": "user", "content": "Write a piece of quicksort code in C++."}],
    ]
    
    prompt_token_ids = [tokenizer.apply_chat_template(messages, add_generation_prompt=True) for messages in messages_list]
    
    outputs = llm.generate(prompt_token_ids=prompt_token_ids, sampling_params=sampling_params)
    
    generated_text = [output.outputs[0].text for output in outputs]
    print(generated_text)

    许可证和引用

    许可证

    这个代码库使用 MIT 许可证。DeepSeek-V2 Base/Chat 模型的使用遵循模型许可证。DeepSeek-V2 系列(包括 Base 和 Chat)支持商业用途。

    引用

    如果你在研究中使用了 DeepSeek-V2,请引用以下文献:

    @misc{deepseekv2,
          title={DeepSeek-V2: A Strong, Economical, and Efficient Mixture-of-Experts Language Model},
          author={DeepSeek-AI},
          year={2024},
          eprint={2405.04434},
          archivePrefix={arXiv},
          primaryClass={cs.CL}
    }

    联系方式

    如果你有任何问题,请在 GitHub 提出 issue 或联系 service@deepseek.com。


    更多信息请参考以下链接:

    1. GitHub – deepseek-ai/DeepSeek-V2
    2. README.md · deepseek-ai/DeepSeek-V2 at main
    3. DeepSeek-V2/README.md at main · deepseek-ai/DeepSeek-V2 · GitHub
  • Caddy 2中配置缓存

    在Caddy 2中配置缓存可以使用cache-handler插件。以下是配置Caddy 2缓存的步骤:

    1. 安装cache-handler插件:首先,确保已安装Caddy 2,并且可以使用插件。然后,下载并安装cache-handler插件。可以从GitHub的caddyserver/cache-handler仓库中获取插件的最新版本。
    2. 配置全局缓存选项:在Caddyfile中配置全局缓存选项。这些选项将适用于所有站点。以下是一些常用的全局选项:
    • order cache before rewrite:确保缓存处理在重写之前执行。
    • cache:启用缓存处理。
    • cache.allowed_http_verbs:允许被缓存的HTTP方法,默认为GET和HEAD。
    • cache.api.basepath:缓存管理API的基本路径。
    • cache.badger.path:配置Badger缓存存储的路径。
    • cache.cache_name:覆盖在Cache-Status响应头中使用的缓存名称。
    • cache.default_cache_control:如果上游未设置Cache-Control响应头,则设置默认值。 你可以根据需要配置其他选项。完整的全局选项列表可以在cache-handler插件的文档中找到[1]
    1. 配置站点缓存选项:在Caddyfile中为每个站点配置缓存选项。以下是一个示例配置:
       example.com {
         cache
         reverse_proxy your-app:8080
       }

    在这个示例中,cache指令启用了缓存处理,并将请求代理到your-app:8080

    1. 配置缓存键:你可以使用cache_keys指令为特定的URI配置缓存键生成规则。例如:
       cache_keys {
         .+\.css {
           disable_body
           disable_host
           disable_method
           disable_query
           headers X-Token Authorization
         }
       }

    在这个示例中,所有以.css结尾的URI将使用指定的规则生成缓存键。

    1. 配置CDN管理(可选):如果你使用CDN代理请求,可以配置CDN管理选项。这些选项将与缓存处理一起使用。例如:
       cdn {
         provider fastly
         api_key XXXX
         dynamic
         email darkweak@protonmail.com
         hostname domain.com
         network your_network
         strategy soft
         service_id 123456_id
         zone_id anywhere_zone
       }

    在这个示例中,使用Fastly作为CDN提供商,并配置了相关的API密钥、邮箱、主机名等信息。

    1. 保存并重新加载Caddy配置:完成配置后,保存Caddyfile并重新加载Caddy配置,以使更改生效。

    这些是配置Caddy 2缓存的基本步骤。你可以根据需要调整和扩展这些配置选项。请参考cache-handler插件的文档以获取更多详细信息和选项[1]


    Learn more:

    1. GitHub – caddyserver/cache-handler: Distributed HTTP caching module for Caddy
    2. State of HTTP Caching in Caddy 2 – Help – Caddy Community
    3. 【Caddy2】最新Caddy2配置文件解析 – 詩 – 博客园

    darkweak/souin 是一个HTTP缓存系统,符合RFC标准,并与@tyktechnologies、@traefik、@caddyserver、@go-chi、@bnkamalesh、@beego、@devfeel、@labstack、@gofiber、@go-goyave、@go-kratos、@gin-gonic、@roadrunner-server、@zalando、@zeromicro、@nginx和@apache兼容[1]。它可以作为反向代理缓存系统,可以放置在当前反向代理(如Apache、Nginx)的顶部,也可以作为Træfik、Caddy或Tyk等喜爱的反向代理的插件使用。由于它是用Go语言编写的,可以部署在任何服务器上,并且由于具有Docker集成,可以轻松安装在Swarm或Kubernetes实例上。它支持Vary、请求合并、陈旧的Cache-Control和其他与RFC-7234相关的规范,还支持新编写的RFC(目前处于草案阶段)http-cache-groups和http-invalidation。它还支持ESI标签,感谢go-esi包的支持[1]

    Souin的配置文件存储在/anywhere/configuration.yml中,可以根据需要提供自己的配置文件。配置文件中包含了各种配置选项,如缓存时间、反向代理URL、API配置、缓存键配置等[1]

    Souin还提供了各种插件,如Beego过滤器、Caddy模块、Chi中间件、Dotweb中间件、Echo中间件、Fiber中间件、Gin中间件、Goa中间件、Go-zero中间件、Goyave中间件、Hertz中间件、Kratos过滤器、Roadrunner中间件、Skipper过滤器、Træfik插件、Tyk插件、Webgo中间件、Prestashop插件和Wordpress插件等[1]

    Souin还支持Prometheus API、Souin API和Security API,可以用于监控和管理缓存系统[1]

    总结:darkweak/souin是一个符合RFC标准的HTTP缓存系统,可以作为反向代理缓存系统使用,也可以作为插件嵌入到其他反向代理中。它支持各种配置选项和插件,并提供了API用于监控和管理缓存系统。


    Learn more:

    1. GitHub – darkweak/souin: An HTTP cache system, RFC compliant, compatible with @tyktechnologies, @traefik, @caddyserver, @go-chi, @bnkamalesh, @beego, @devfeel, @labstack, @gofiber, @go-goyave, @go-kratos, @gin-gonic, @roadrunner-server, @zalando, @zeromicro, @nginx and @apache
    2. darkweak · GitHub
    3. Releases · darkweak/souin · GitHub
  • 深入解析 LoRA+:如何通过调整学习率进一步优化LLM

    在当前大规模语言模型(LLM)的参数高效微调方法中,LoRA(Low-Rank Adaptation)无疑是一个重要的手段。此前,我们在《梯度视角下的LoRA:简介、分析、猜测及推广》中曾简单讨论过LoRA。今天,我们来学习LoRA的一个新结论:给LoRA的两个矩阵分配不同的学习率,LoRA的效果还能进一步提升。这一结论出自最近的论文《LoRA+: Efficient Low Rank Adaptation of Large Models》(下称“LoRA+”)。

    为什么调整学习率显得如此重要?

    乍一看,给不同的矩阵分配不同的学习率似乎并没有什么特别之处,因为配置不同的学习率相当于引入了新的超参数,而通常来说,只要引入并精调超参数,模型性能都会有所提升。然而,“LoRA+”的特别之处在于,它从理论角度肯定了这一必要性,并且明确指出最优解必然是右矩阵的学习率大于左矩阵的学习率。

    LoRA基本原理回顾

    假设预训练参数为

    [latex]( W_0 \in \mathbb{R}^{n \times m} )[/latex]

    ,如果使用全量参数微调,那么增量也是一个 ( n \times m ) 矩阵。为了降低参数量,LoRA将更新量约束为低秩矩阵,即设 ( W = W_0 + AB ),其中

    [latex]( A \in \mathbb{R}^{n \times r}, B \in \mathbb{R}^{r \times m}, r \ll \min(n, m) )[/latex]

    。在训练时,只更新 ( A ) 和 ( B )。

    为了便于理解,我们假设层的输入是

    [latex]( X \in \mathbb{R}^{b \times n} )[/latex]

    ,层的运算为

    [latex]( XW = X(W_0 + AB) )[/latex]

    。由于“LoRA+”的结论跟预训练权重无关,不失一般性可以设

    [latex] ( W_0 = 0 )[/latex]

    ,那么层运算简化为

    [latex]( Y = XAB \in \mathbb{R}^{b \times m} )[/latex]

    结论简析

    “LoRA+”的结论是:为了使LoRA的效果尽可能接近最优,权重 ( B ) 的学习率应该要大于权重 ( A ) 的学习率。

    这个结论的核心在于,虽然 ( A ) 和 ( B ) 在表面上是对称的,但实际上它们有着固有的不对称性。即使将 ( A ) 或 ( B ) 全零初始化,结论依然成立。

    数值稳定性与贡献相当假设

    1. 数值稳定性

    数值稳定性意味着 ( X )、 ( XA )、 ( XAB ) 的每个分量都应该是 ( O(1) ) 级别的,而不依赖于网络宽度 ( n, m )。虽然 ( XA ) 是中间变量,但它的数值稳定性对梯度稳定性至关重要。

    为了实现数值稳定性,假设 ( X ) 是 ( O(1) ),那么 ( A ) 和 ( B ) 应该分别用 ( 1/n ) 和 ( 1/r ) 的方差初始化。

    2. 贡献相当

    为了使LoRA最优, ( A ) 和 ( B ) 两个矩阵对效果应该有同等程度的贡献。我们可以通过损失函数 ( L ) 的变化来衡量 ( A ) 和 ( B ) 的贡献:

    [latex][ L(A + \Delta A, B + \Delta B) – L(A, B) \approx \langle \frac{\partial L}{\partial A}, \Delta A \rangle + \langle \frac{\partial L}{\partial B}, \Delta B \rangle ][/latex]

    在Adam优化器中,如果两个参数的学习率之比是 ( \lambda ),那么经过长期训练后,这两个参数的数量级之比也是

    [latex]( \lambda )[/latex]

    。因此,如果 [latex]( \eta_B / \eta_A = O(\sqrt{n}) )[/latex],那么 ( B ) 和 ( A ) 的数量级之比也是 [latex]( O(\sqrt{n}) )[/latex],与我们的期望一致。

    快速推导

    通过矩阵求导,我们可以得到:

    [latex][ \frac{\partial L}{\partial A} = X^\top \frac{\partial L}{\partial Y} B^\top, \quad \frac{\partial L}{\partial B} = A^\top X^\top \frac{\partial L}{\partial Y} ][/latex]

    结合数值稳定性和贡献相当的假设,可以推导出:

    [latex][ \eta_B / \eta_A = O(\sqrt{n}) ][/latex]

    总结

    在这篇文章中,我们介绍并推导了“LoRA+”的结果,它支持LoRA的两个低秩矩阵 ( A ) 和 ( B ) 存在固有的不对称性。不管将哪个矩阵全零初始化,都应该将 ( B ) 的学习率设置得大于 ( A ),以达到更优的效果。这一理论不仅在实践中有效,也为我们提供了一个经典的理论指导训练的范例。

  • 论文分享:Score Identity Distillation——更快更好的扩散模型蒸馏方法

    引言

    今天我们分享的是一篇名为《Score Identity Distillation: Exponentially Fast Distillation of Pretrained Diffusion Models for One-Step Generation》的新论文。该论文探讨了如何更快更好地蒸馏扩散模型。

    即便没有做过蒸馏,大家可能也能猜到蒸馏的常规步骤:随机采样大量输入,然后用扩散模型生成相应结果作为输出,用这些输入输出作为训练数据对,来监督训练一个新模型。然而,众所周知,作为教师的原始扩散模型通常需要多步(比如1000步)迭代才能生成高质量输出,所以且不论中间训练细节如何,该方案的一个显著缺点是生成训练数据太费时费力。此外,蒸馏之后的学生模型通常或多或少都有效果损失。

    有没有方法能一次性解决这两个缺点呢?这就是上述论文试图要解决的问题。

    思路简介

    论文将所提方案称为“Score Identity Distillation(SiD)”,基于几个恒等式来设计和推导了整个框架。实际上,它的设计思想与几个恒等式并没有直接联系,其次几个恒等式都是已知的公式而不是新的,所以这个名字显得相当随意。

    本文将其称之为“重现江湖”,是因为SiD的思路跟之前在《从去噪自编码器到生成模型》介绍过的论文《Learning Generative Models using Denoising Density Estimators》(简称“DDE”)几乎一模一样,甚至最终形式也有五六分相似。只不过当时扩散模型还未露头角,所以DDE是将其作为一种新的生成模型提出的,在当时反而显得非常小众。而在扩散模型流行的今天,它可以重新表述为一种扩散模型的蒸馏方法,因为它需要一个训练好的去噪自编码器——这正好是扩散模型的核心。

    接下来笔者用自己的思路去介绍SiD。

    初级形式

    假设我们有一个在目标数据集训练好的教师扩散模型 ( \epsilon_{\phi^}(x_t, t) ),它需要多步采样才能生成高质量图片。我们的目标是训练一个单步采样的学生模型 ( x = g_{\theta}(z) ),即一个类似GAN的生成器,输入指定噪声 ( z ) 就可以直接生成符合要求的图像。如果我们有很多的 ( (z, x) ) 对,那么直接监督训练就可以了(当然损失函数和其他细节还需要进一步确定,读者可以自行参考相关工作),但如果没有呢?肯定不是不能训,因为就算没有 ( \epsilon_{\phi^}(x_t, t) ) 也能训,比如GAN,所以关键是怎么借助已经训练好的扩散模型提供更好的信号。

    SiD及前作DDE使用了一个看上去很绕但是也很聪明的思路:

    如果 ( g_{\theta}(z) ) 产生的数据分布跟目标分布很相似,那么拿 ( g_{\theta}(z) ) 生成的数据集去训练一个扩散模型 ( \epsilon_{\psi^}(x_t, t) ) 的话,它也应该跟 ( \epsilon_{\phi^}(x_t, t) ) 很相似?

    这个思路的聪明之处在于,它绕开了对教师模型生成样本的需求,也不需要训练教师模型的真实样本,因为“拿 ( g_{\theta}(z) ) 生成的数据集去训练一个扩散模型”只需要学生模型 ( g_{\theta}(z) ) 生成的数据(简称“学生数据”),而 ( g_{\theta}(z) ) 是一个单步模型,用它来生成数据时间上比较友好。

    当然,这还只是思路,将其转换为实际可行的训练方案还有一段路要走。

    方法与公式

    扩散模型回顾

    我们采用《生成扩散模型漫谈(三):DDPM = 贝叶斯 + 去噪》的形式,对输入 ( x_0 ) 进行加噪:
    [ x_t = \bar{\alpha}_t x_0 + \bar{\beta}_t \epsilon, \quad \epsilon \sim N(0, I) ]

    训练 ( \epsilon_{\phi^}(x_t, t) ) 的方式则是去噪: [ \phi^ = \arg\min_{\phi} E_{x_0 \sim \tilde{p}(x_0), \epsilon \sim N(0, I)} \left[ | \epsilon_{\phi}(\bar{\alpha}_t x_0 + \bar{\beta}_t \epsilon, t) – \epsilon |^2 \right] ]

    同样地,如果我们想用 ( g_{\theta}(z) ) 的学生数据训练一个扩散模型,那么训练目标是:
    [ \psi^* = \arg\min_{\psi} E_{z, \epsilon \sim N(0, I)} \left[ | \epsilon_{\psi}(x_t^{(g)}, t) – \epsilon |^2 \right] ]

    其中 ( x_t^{(g)} = \bar{\alpha}t g{\theta}(z) + \bar{\beta}t \epsilon ),是由学生数据加噪后的样本,其分布记为 ( p{\theta}(x_t^{(g)}) )。

    学生模型的学习

    我们可以通过最小化教师模型和学生模型生成的数据分布差异来学习学生模型:
    [ \theta^* = \arg\min_{\theta} E_{z, \epsilon \sim N(0, I)} \left[ | \epsilon_{\phi^}(x_t^{(g)}, t) – \epsilon_{\psi^}(x_t^{(g)}, t) |^2 \right] ]

    注意这个优化依赖于 ( \theta ),所以当 ( \theta ) 通过上式发生改变时,( \psi^* ) 的值也随之改变,因此需要交替优化,类似GAN一样。

    点睛之笔

    上述方法存在理论与实践之间的gap,主要体现在两个问题:

    1. 理论上要求先求出上式的最优解,然后才去优化,但实际上从训练成本考虑,我们并没有将它训练到最优就去优化了;
    2. 理论上 ( \psi^* ) 随 ( \theta ) 而变,即应该写成 ( \psi^(\theta) ),从而在优化时应该多出一项 ( \psi^(\theta) ) 对 ( \theta ) 的梯度,但实际上在优化时我们都只当 ( \psi^* ) 是常数。

    SiD的核心贡献是通过恒等变换,尽量消除优化目标对 ( \psi^* ) 的依赖,从而有效缓解上述两个问题。

    恒等变换

    我们具体来看做了什么恒等变换。我们先来看去噪目标:
    [ E_{x_0 \sim \tilde{p}(x_0), x_t \sim p(x_t | x_0)} \left[ | \epsilon_{\phi}(x_t, t) + \bar{\beta}t \nabla{x_t} \log p(x_t | x_0) |^2 \right] ]

    根据得分匹配相关结果,上述目标的最优解是 ( \epsilon_{\phi^}(x_t, t) = -\bar{\beta}t \nabla{x_t} \log p(x_t) )。同理,学生模型的训练目标的最优解是 ( \epsilon_{\psi^}(x_t^{(g)}, t) = -\bar{\beta}t \nabla{x_t^{(g)}} \log p_{\theta}(x_t^{(g)}) )。

    此时我们有:
    [ E_{z, \epsilon \sim N(0, I)} \left[ | \epsilon_{\phi^}(x_t^{(g)}, t) – \epsilon_{\psi^}(x_t^{(g)}, t) |^2 \right] ]

    通过恒等变换,我们可以将上式化简为:
    [ E_{z, \epsilon \sim N(0, I)} \left[ \langle \epsilon_{\phi^}(x_t^{(g)}, t) – \epsilon_{\psi^}(x_t^{(g)}, t), \epsilon_{\phi^*}(x_t^{(g)}, t) – \epsilon \rangle \right] ]

    这就是SiD的核心结果,能够高效地实现蒸馏。

    其他细节

    1. 论文的推导默认了 ( \bar{\alpha}_t = 1 )。
    2. 论文的结果是以 ( \mu(\bar{x}_t) = x_t – \bar{\beta}_t \epsilon(x_t, t) / \bar{\alpha}_t ) 为标准给出的,这与扩散模型常见的表述方式不同。
    3. SiD最终取了上式的相反数作为额外的损失函数,加权到改进的损失函数上,以取得更优的蒸馏效果。

    延伸思考

    对于式(3)和式(4)的交替优化,有不少读者可能会想到,但SiD的精彩之处是提出了恒等变换,使得训练更加稳定高效。

    文章小结

    在这篇文章中,我们介绍了一种新的将扩散模型蒸馏为单步生成模型的方案,其思想可以追溯到前两年的利用去噪自编码器训练生成模型的工作,它不需要获得教师模型的真实训练集,也不需要迭代教师模型来生成样本对,而引入了类似GAN的交替训练,同时提出了关键的恒等变换来稳定训练过程,整个方法有颇多值得学习之处。

  • Fast Transformer Decoding: One Write-Head is All You Need

    引言

    《Fast Transformer Decoding: One Write-Head is All You Need》是一篇提出了一种变换模型多头注意力机制变体的研究论文。该论文解决了变换模型在增量推理过程中由于反复加载大规模“键”和“值”张量而导致的内存带宽成本问题。

    论文要点

    1. 多查询注意力(Multi-Query Attention)

    作者提出了一种称为多查询注意力的变体,其中键和值在所有注意力头之间共享。这种方法减少了张量的大小以及在增量解码过程中对内存带宽的需求。

    2. 内存带宽的减少

    通过共享键和值,所提出的方法显著减少了在解码过程中反复加载这些张量的内存带宽成本。

    3. 更快的解码速度

    采用多查询注意力的模型在解码速度上比传统的多头注意力模型要快得多。

    4. 质量轻微下降

    作者通过实验验证了多查询注意力模型在相较于基线模型的情况下,质量仅有轻微下降。

    进一步了解

    1. [1911.02150] Fast Transformer Decoding: One Write-Head is All You Need
    2. Fast Transformer Decoding: One Write-Head is All You Need | Papers With Code
    3. dblp: Fast Transformer Decoding: One Write-Head is All You Need.

    通过这篇论文,我们可以看到在不牺牲太多模型质量的前提下,通过优化内存带宽和共享键值对,可以显著提高变换模型的解码速度。这对于提升自然语言处理任务中的推理效率具有重要意义。


    多查询注意力

    多查询注意力是一种注意力机制,其中键和值在所有注意力头之间共享。这意味着在解码过程中,只需要加载一次键和值向量,从而显著减少了内存带宽成本。

    传统多头注意力

    传统的多头注意力是一种注意力机制,其中每个注意力头都有自己的键和值向量。这意味着在解码过程中,需要反复加载这些向量,从而导致内存带宽成本高。

    多查询注意力和传统多头注意力之间的区别

    多查询注意力和传统多头注意力之间的主要区别在于键和值的共享方式。在多查询注意力中,键和值在所有注意力头之间共享,而在传统的多头注意力中,每个注意力头都有自己的键和值向量。

    多查询注意力和传统多头注意力之间的区别是否会对模型的性能产生影响?

    实验表明,采用多查询注意力的模型在解码速度上比传统的多头注意力模型要快得多,且质量仅有轻微下降。

  • OpenVINO-Java-API

    📚 简介

    OpenVINO™ 是一个用于优化和部署 AI 推理的开源工具包,旨在提升深度学习在计算机视觉、自动语音识别、自然语言处理和其他常见任务中的性能。它支持使用流行框架(如TensorFlow,PyTorch等)训练的模型,减少资源需求,并在从边缘到云的一系列英特尔®平台上高效部署。

    该项目通过Java Native Access (JNA) 实现了基于OpenVINO™工具套件的OpenVINO™ Java API,旨在推动 OpenVINO™在Java领域的应用。由于是基于 OpenVINO™ 开发,OpenVINO™ Java API 支持的所有平台与OpenVINO™ 一致。

    版本计划

    • 1.0: 实现基本函数,并提供Yolov8范例
    • 1.1: 实现Maven在线安装
    • 2.0: 实现库本地加载,告别复杂安装
    • 3.0: 实现在线加载

    Java库公示

    • JNA
    • OpenCV
    • OpenVINO

    ⚙ 如何安装

    以下文章提供了OpenVINO™ Java API在不同平台的安装方法,可以根据自己使用的平台进行安装。

    简短安装步骤

    1. 在OpenVINO官网下载对应平台的runtime库
    2. 将Runtime库加入环境变量
      • Windows: 放在系统路径中
      • Linux/Mac OS: 将库文件放入 /usr/lib/

    详细使用文档

    🏷 开始使用

    快速体验

    使用以下代码简单了解OpenVINO Java API的使用方法:

    public class OpenVINOTest {
    
        public static void main(String[] args) {
            // 实现OpenVINO 库加载
            OpenVINO vino = OpenVINO.load("libopenvino_c.dylib");
            // 如果将库放置在系统路径中,可以这样简写
            // OpenVINO vino = OpenVINO.load();
            Core core = new Core();  // 初始化 Core 核心
            Model model = core.read_model("./model.xml");  // 读取模型文件
            CompiledModel compiled_model = core.compiled_model(model, "AUTO");  // 将模型加载到设备
            InferRequest infer_request = compiled_model.create_infer_request();  // 创建推理通道
            Tensor input_tensor = infer_request.get_tensor("images");  // 获取输入节点Tensor
            infer_request.infer();  // 模型推理
            Tensor output_tensor = infer_request.get_tensor("output0");  // 获取输出节点Tensor
            // 清理 Core 非托管内存
            core.free();
        }
    }

    项目中所封装的类、对象例如Core、Model、Tensor等,通过调用 C API 接口实现,具有非托管资源,需要调用 dispose() 方法处理,否则就会出现内存泄漏。

    💻 应用案例

    • 爱克斯开发板使用OpenVINO™ Java API部署Yolov8模型
    • 基于Spring Boot 的在线AI服务
    • 基于客户端模式运行

    测试系统

    • Mac OS: Sonoma
    • Ubuntu: 23.04 (64位)
    • Windows

    🗂 API 文档

    (链接到相关API文档)

    🔃 更新日志

    (链接到更新日志)

    🎖 贡献

    如果您对OpenVINO™ 在Java使用感兴趣,有兴趣对开源社区做出自己的贡献,欢迎加入我们,一起开发OpenVINO™ Java API。如果你对该项目有一些想法或改进思路,欢迎联系我们,指导我们的工作。

    许可证书

    本项目的发布受Apache 2.0 license许可认证。


    GitHub 仓库地址: Hmm466/OpenVINO-Java-API


    通过这个项目,您可以在Java中更方便地使用OpenVINO™进行AI推理,加速您的开发流程并优化性能。如果有任何问题或建议,欢迎在仓库中提出Issue或进行讨论。


    OpenVINO™ Java API 安装指南(Windows)

    当前验证/测试环境

    • Windows: 7
    • JDK: 11.0.10
    • OpenVINO Runtime: 2023.2.0-12538-e7c1344d3c3
    • OpenCV: 4.8.0

    OpenVINO™ 环境配置

    OpenVINO™ 是一个用于优化和部署 AI 推理的开源工具包,旨在提升深度学习在计算机视觉、自动语音识别、自然语言处理和其他常见任务中的性能。它支持使用流行框架(如TensorFlow,PyTorch等)训练的模型,减少资源需求,并在从边缘到云的一系列英特尔®平台上高效部署。

    步骤

    1. 下载OpenVINO™ Runtime库 下载适用于您电脑平台的OpenVINO™ Runtime库。
    2. 拷贝动态链接库runtime/3rdparty/tbb/lib/*.dll 拷贝至 runtime/lib/intel64/release/ 下。
    3. 配置默认库路径runtime/lib/intel64/release/*.dll 拷贝至以下任意路径之一:
    • 自己喜欢的路径
    • C:\Windows

    OpenCV 环境配置

    1. 下载OpenCV 在官网下载适用于Windows平台的OpenCV,解压缩,并将其加入环境变量。
    2. 拷贝OpenCV动态链接库opencv_java*.dll 拷贝到自己喜欢的目录,或者将 build/lib/libopencv_java*.dll 移动到自己喜欢的目录。
    3. 加载OpenCV库 使用以下代码加载OpenCV库:
       OpenVINO.loadCvDll({opencv_java所在的目录});

    OpenVINO™ Java API 安装

    源码构建

    1. 克隆代码仓库
       git clone https://github.com/Hmm466/OpenVINO-Java-API.git
    1. 使用Maven安装 在代码仓库根目录下运行:
       mvn install

    Maven 引用

    如果您想在Maven项目中引用OpenVINO™ Java API,可以在 pom.xml 文件中添加以下依赖项:

    <dependencies>
        <dependency>
            <groupId>org.openvino</groupId>
            <artifactId>java-api</artifactId>
            <version>[按照最新下载的版本填入]</version>
        </dependency>
    </dependencies>

    在线Maven 安装

    该功能将在1.1版本上线


    通过以上步骤,您可以在Windows平台上成功安装并配置OpenVINO™ Java API。如果有任何问题或需要进一步的帮助,请在GitHub仓库中提出Issue或进行讨论。

    GitHub 仓库地址: Hmm466/OpenVINO-Java-API


    OpenVINO™ 生态系统概述

    简介

    OpenVINO™ 不只是一个工具,它还是一个庞大的实用程序生态系统,提供用于开发深度学习解决方案的整套工作流程。通过详细了解每个实用程序,可以充分利用 OpenVINO™ 工具套件。

    神经网络压缩框架 (NNCF)

    NNCF 是一套用于优化神经网络推理的高级算法,可以最大限度地减少准确度下降。在训练期间,NNCF 会对 PyTorch 和 TensorFlow 模型应用量化、过滤器修剪、二值化和稀疏性算法。

    更多资源:

    OpenVINO™ 安全插件

    OpenVINO™ 安全插件为模型开发人员和独立软件开发商提供了用于进行安全封装并安全执行模型的解决方案。

    更多资源:

    OpenVINO™ 与 TensorFlow 集成 (OVTF)

    OVTF 为 TensorFlow 开发人员提供 OpenVINO™ 优化功能的解决方案。只需在应用中添加两行代码,即可将推理分载给 OpenVINO™,同时保留 TensorFlow API。

    更多资源:

    DL Streamer

    DL Streamer 是一个基于 GStreamer 多媒体框架的流媒体分析框架,用于创建复杂的媒体分析管道。

    更多资源:

    深度学习工作台

    深度学习工作台是一个用于部署深度学习模型的基于 Web 的工具。它依托 OpenVINO™ 的核心功能并配有图形用户界面,提供了一种用于探索 OpenVINO™ 工作流程的各种可能性,以及导入、分析、优化并构建预训练模型的绝佳途径。您可以通过访问英特尔® DevCloud for the Edge 并在线启动深度学习工作台,执行所有此类任务。

    更多资源:

    OpenVINO™ 训练扩展 (OTE)

    OpenVINO™ 训练扩展 (OTE) 提供了一种用于使用 OpenVINO™ 工具套件训练深度学习模型并对其进行转换,以优化推理的便捷环境。

    更多资源:

    计算机视觉注释工具 (CVAT)

    CVAT 是一款用于计算机视觉的在线交互式视频和图像注释工具。

    更多资源:

    数据集管理框架 (Datumaro)

    Datumaro 是一个用于构建、转换和分析数据集的框架兼 CLI 工具。

    更多资源:


    通过详细了解和使用这些实用工具,能够充分发挥 OpenVINO™ 的强大功能,优化和部署高性能的深度学习解决方案。

    详细文档地址: OpenVINO™ 生态系统概述


    OpenVINO™ API 2.0 迁移指南

    概述

    本指南介绍了全新的 OpenVINO™ API (API 2.0) 以及新型 OpenVINO™ IR 模型格式:IR v11。本文将对新旧版本进行比较,并提供详细的迁移步骤。

    API 2.0 简介

    在从 TensorFlow、ONNX 运行时、PyTorch、PaddlePaddle 等其他框架迁移应用时,2022.1 之前的 OpenVINO™ 版本需要更改应用逻辑。这是因为:

    • 模型优化器更改了某些输入的输入精度。例如,具有 I64 输入的神经语言处理模型已更改为包含 I32 输入。
    • 模型优化器更改了 TensorFlow 模型的布局(请参阅 OpenVINO™ 中的布局)。
    • 由于设备插件的限制,推理引擎 API (InferenceEngine::CNNNetwork) 应用了一些输入和输出精度的转换规则。
    • 在模型优化器中进行模型转换期间,用户需要指定输入形状,并在应用中使用静态形状。

    OpenVINO™ 2022.1 引入了 API 2.0(也称为 OpenVINO™ API v2),以调整使用模型的逻辑。API 2.0 未更改布局和精度,使用张量名称和索引来执行操作以对输入和输出进行寻址。OpenVINO™ 运行时将用于推理的推理引擎 API 与用于处理模型和操作的 nGraph API 结合在一起。API 2.0 有通用的结构、命名约定样式、命名空间,并移除了重复的结构。

    现有的应用将继续正常使用 OpenVINO™ 运行时 2022.1,但强烈建议迁移到 API 2.0,以便使用其他功能,如预处理和动态形状支持。

    全新 OpenVINO™ IR v11

    为了支持这些功能,OpenVINO™ 引入了 OpenVINO™ IR v11,它现在是默认版本的模型优化器。以 OpenVINO™ IR v11 表示的模型的输入和输出与原始框架格式下的原始模型完全匹配,无需在转换过程中指定输入形状。生成的 OpenVINO™ IR v11 包含 -1,用于表示未定义维度。OpenVINO™ IR v11 完全兼容借助旧版 OpenVINO™ 使用的推理引擎 API 编写的应用。这种向后兼容性归功于 OpenVINO™ IR v11 中包含的其他运行时信息。

    向后兼容 OpenVINO™ IR v10

    API 2.0 还支持向后兼容 OpenVINO™ IR v10 模型。如果您拥有 OpenVINO™ IR v10 文件,也可以将其馈送到 OpenVINO™ 运行时。某些 OpenVINO™ 开发工具也支持将 OpenVINO™ IR v10 和 v11 作为输入:

    • 精度检查器默认使用 API 2.0 来测量模型精度,也支持通过使用 --use_new_api False 命令行参数切换到旧版 API。
    • 编译工具默认编译要在 API 2.0 中使用的模型。要在推理引擎 API 下使用生成的已编译 Blob,应传递附加 ov_api_1_0 选项。

    OpenVINO™ 2022.1 的训练后优化工具和深度学习工作台不支持 OpenVINO™ IR v10,需使用最新版本的模型优化器生成 OpenVINO™ IR v11 文件。

    API 2.0 与推理引擎 API 的行为差异

    推理引擎和 nGraph API 不会因引入了新 API 而被弃用,用户仍可以在应用中使用这些 API。但强烈建议迁移到 API 2.0,因为它提供了更多功能(在未来版本中会进一步扩展),例如:

    • 使用动态形状,提高神经语言处理 (NLP) 和超分辨率模型等兼容模型的性能。
    • 预处理模型,在推理模型中添加预处理操作并完全占用加速器,从而释放 CPU 资源。

    下表说明了基于这两个 API 的模型会使用哪种行为:旧行为还是新行为。

    APIIR v10IR v11ONNX 文件使用代码创建的模型
    推理引擎/nGraph API
    API 2.0

    通过迁移到 OpenVINO™ API 2.0,您可以充分利用新特性和增强功能,提升模型推理的性能和灵活性。

    详细文档地址: OpenVINO™ API 2.0 迁移指南

  • GQA: 从多头检查点训练广义多查询变换模型

    引言

    在自然语言处理领域,如何在不牺牲模型质量的前提下实现更快的推理速度一直是一个重要的研究课题。本文将介绍一篇名为《GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints》的研究论文,该论文提出了一种从多头检查点训练广义多查询变换模型的方法,旨在解决这一挑战。

    论文贡献

    1. 现有多头语言模型检查点的再训练

    论文提出了一种再训练现有多头语言模型检查点的方法,将其转换为具有多查询注意力(MQA)的模型。MQA使用单个键值头,大幅加快解码器推理速度。通过仅使用原始预训练计算量的5%,再训练现有模型,作者旨在实现更快的推理速度,而无需单独训练一个新模型。

    2. 引入分组查询注意力(GQA)

    论文引入了GQA,作为MQA的一种广义形式。GQA使用中间数量的键值头,数量介于1和查询头总数之间。该方法旨在平衡MQA的速度和多头注意力的质量。作者通过实验表明,再训练的GQA模型在保持与MQA相当速度的同时,能够达到接近多头注意力的质量。

    方法与实验结果

    再训练方法

    论文详细介绍了再训练现有多头语言模型检查点的具体步骤。通过仅使用原始预训练计算量的5%,再训练现有模型,使其具备MQA的特点,从而实现更快的推理速度。

    GQA的实现

    GQA通过使用中间数量的键值头,增加了模型的灵活性和适应性。实验结果表明,再训练的GQA模型在多个自然语言处理任务中表现出色,质量接近于多头注意力,同时推理速度与MQA相当。

    结论

    本文提出的方法为实现更快的推理速度提供了一个有效的解决方案,而无需牺牲模型质量。通过再训练现有多头语言模型检查点,并引入分组查询注意力(GQA),可以在保持高质量的同时,实现高效的推理。这一研究对于提升变换模型在各类自然语言处理任务中的效率和性能具有重要意义。


    进一步了解:

    1. arXiv:2305.13245 – GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints
    2. Papers With Code – GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints
    3. ACL Anthology – GQA: Training Generalized Multi-Query Transformer Models from Multi-Head Checkpoints