当前位置: 首页 > news >正文

从零开始理解FlashAttention:算法细节图解

🧠 向所有学习者致敬!

“学习不是装满一桶水,而是点燃一把火。” —— 叶芝


我的博客主页: https://lizheng.blog.csdn.net

🌐 欢迎点击加入AI人工智能社区!

🚀 让我们一起努力,共创AI未来! 🚀


注意力机制无疑是现代深度学习架构中最重要的构建模块之一。它被广泛应用于各种任务的最先进模型中,从自然语言处理(NLP)到计算机视觉。然而,注意力机制也是这些模型中最昂贵的操作之一。因此,自然而然地,大量研究致力于让它变得更快、更节省内存。而这些研究大多基于对注意力机制的近似,这可能会导致精度损失。

在从零开始理解FlashAttention系列的第二部分中,我们将深入探讨FlashAttention的细节,看看它是如何实现7.6倍的巨大加速,以及如何在计算精确注意力分数的同时实现 O ( N ) O(N) O(N) 的内存复杂度。

🚀 那么,让我们马上开始吧!


好的呀!那我就开始啦!以下是翻译后的文档内容:


这是多部分系列文章 从零开始理解FlashAttentio 的第 2 部分。

注意力机制无疑是现代深度学习架构中最重要的构建块之一。它被用于各种任务的最先进的模型中,从自然语言处理到计算机视觉都有它的身影。然而,注意力机制也是这些模型中最昂贵的操作之一。所以,很自然地,有很多研究致力于让它变得更快、更节省内存。大部分研究都是基于对注意力机制进行近似,但这可能会导致精度损失。

在 从零开始理解FlashAttentio的第 2 部分,我们将深入探讨 FlashAttention 的细节,看看它是如何实现高达 7.6 倍的速度提升,以及如何在仍然计算精确注意力分数的同时,达到 O(N) 的内存复杂度。

🚀 那我们马上开始吧!

1. 第 1 部分的内容回顾

在本系列的第一部分,我们为理解 FlashAttention 论文奠定了基础。

我们对注意力机制有了基本的直觉,了解了它是如何工作的,以及它在模型中的位置。我们还简要讨论了现代 GPU、CUDA 编程模型以及 GPU 的内存层次结构。

接着,我们转向研究 GPU 上两个矩阵是如何相乘的,以及我们如何通过利用 GPU 的内存层次结构(共享内存)、在单个线程中计算多个结果(块分块)以及将多个内核融合成一个内核(内核融合)来优化这个过程。

最后,我们得到了下面这张图,它展示了我们在注意力机制上的主要问题:我们需要读取和写入巨大的 NxN 矩阵作为中间结果,这不仅耗时,还需要大量内存。

Figure 1: Reads and writes for each kernel of the attention layer. Image by Sascha Kirch.

图 1:注意力层中每个内核的读取和写入操作。

我们最终搞清楚了什么阻止了我们将优化矩阵乘法时使用的技术应用到注意力机制上:

  • SoftMax 操作阻止了我们融合内核,因为它是在整个向量 x 上操作的,而不是在向量的块上操作,
  • 而且我们需要在反向传播中使用这些中间结果来计算梯度。

理解这两点可真是费了不少功夫,如果你坚持到这里,那可真是太棒了!🎉

现在,让我们保持这股劲头,继续深入探索:用 FlashAttention 解决这些问题!

2. FlashAttention

论文的标题 “FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness” 很具体,已经告诉我们它解决了哪些问题:

  • 快速:执行时间大幅减少。注意力层本身的速度提升了 7.6 倍,从头开始训练 GPT-2 的速度提升了 3.5 倍。
  • 节省内存:内存使用大幅减少,让我们可以训练更大的模型、更大的上下文窗口和更大的批次。因为标准注意力是 O(N²) 的内存复杂度,而 FlashAttention 是 O(N) 的。
  • 精确注意力:与其他通过近似注意力机制的部分来实现加速和减少内存需求的方法(如 Linformer、Performers 或 Reformer)不同,FlashAttention 在计算精确注意力分数的同时实现了减少。
  • IO 意识:FlashAttention 利用现代 GPU 的内存层次结构来优化不同内存层级之间的数据传输。这是通过使用共享内存来存储中间结果,并且只将最终结果写入全局内存来实现的。

将这些改进可视化后,看起来就像这样:

Figure 2: How FlashAttention improves compared to previous methods. Image by Sascha Kirch.

图 2:FlashAttention 与之前方法的改进对比。

虽然这些听起来对很多人来说还很抽象,但让我们明确一下它能让我们做什么:

  • 更快地训练相同的模型,
  • 在更大的批次上训练相同的模型,
  • 以相同的成本训练更大(可能性能更好的)模型,
  • 训练具有更大上下文窗口的模型,
  • 在更小的 GPU 上训练模型。

例如,在 8 块 A100 上训练 GPT-2 小模型,从 9.5 天缩短到了 2.7 天;GPT-2 中模型从 21.0 天缩短到了 6.9 天(使用 OpenWebText 数据集)。

❓那我们究竟是如何达到这一点的,我们需要解决哪些问题呢?

让我们来一探究竟!

2.1.我们需要解决的问题是什么?

正如我们在 第 1 部分 学到的,我们面临的主要问题是,我们需要读取和写入巨大的 NxN 矩阵作为中间结果,这不仅耗时,还需要大量内存。理想情况下,如果我们能够将 SoftMax 内核与矩阵乘法内核融合,并且只将最终结果写入全局内存,那该多好呀。

但是,我们遇到了两个问题,阻止了我们这么做:

Figure 3: Two issues that prevent us from fusing kernels in the attention layer. Image by Sascha Kirch.

图 3:阻止我们在注意力层中融合内核的两个问题。

我们先从第一个问题说起:SoftMax 操作。

2.2. 修复 SoftMax

回想一下我们在 第 1 部分 中提到的,为了乘以两个矩阵,我们利用了共享内存。输入被分成更小的块,结果被累加起来以获得最终结果:

Figure 4: Usage of shared memory in matrix multiplication. Image by Sascha Kirch.

图 4:矩阵乘法中共享内存的使用。

如果我们想将 SoftMax 内核与矩阵乘法内核融合,我们也必须将输入分成块,并且以某种方式将它们结合起来以获得最终结果。这就是 SoftMax 操作的分解发挥作用的地方。

我们先假设我们能够访问到计算 SoftMax 所需的所有值,并且也将 SoftMax 的定义转换成更接近论文中使用的那种形式:

Figure 5: SoftMax equations. Image by Sascha Kirch.

图 5:SoftMax 方程。

到目前为止,这仅仅是重新表述了标准的 SoftMax 定义。

❗接下来可能是这个系列中最重要的部分啦: 我们将看到如何分解 SoftMax 操作,以便它可以一次应用于单独的块,这将允许我们将 SoftMax 内核与矩阵乘法内核融合,因为它们将操作相同的数据。

与其使用一个完整的向量 x 来计算 SoftMax,我们实际上在共享内存中只有 x 的一半可用。

Figure 6: Available data for the decomposed SoftMax operation. Image by Sascha Kirch.

图 6:分解的 SoftMax 操作可用的数据。

说清楚一点:我们不能简单地为 x 的第一部分计算 SoftMax,然后再为第二部分计算 SoftMax,因为我们需要知道整个向量 x 的最大值,才能计算数值稳定的 SoftMax。

相反,我们将采取以下方法:我们跟踪一些额外的指标,然后在每次迭代中,我们为当前块的 x 计算 SoftMax,将当前迭代的结果与之前的迭代结果结合起来,更新指标,然后相应地更新输出。

如果你还不明白,别担心!我们会详细讲解。

首先,让我展示一下将 SoftMax 操作应用于向量 x 的两个块时的新方程:

Figure 7: Equations of the decomposed SoftMax function. Image by Sascha Kirch.

图 7:分解的 SoftMax 函数的方程。

我知道这看起来很复杂。但如果你做数学运算并代入值,你会看到这与我们之前看到的标准 SoftMax 表达式是等价的。为了说明这一点,我深入研究并为你做了这个练习,用于 l(x)

Figure 8: Showing the equality of the decomposed SoftMax functions with the standard SoftMax function. Image by Sascha Kirch.

图 8:展示分解的 SoftMax 函数与标准 SoftMax 函数的等价性。

❓但我们从中学到了什么呢?

关键的见解是,我们现在可以为 x 的第一块计算 SoftMax,然后为第二块计算 SoftMax(这两次计算相对于整个向量 x 来说都是不正确的),重新缩放中间结果,然后将它们结合起来以获得最终且有效的 x 的 SoftMax。如果你仔细观察,函数 m(x)l(x) 是那些需要知道 x 中所有值的。因此,我们在全局内存中跟踪它们,并在每次迭代中逐步更新它们。我们这样做的原因有两个:

  1. 我们需要它们来更新和重新缩放后续迭代中的中间结果。
  2. 我们不想在反向传播时重新计算它们,以计算梯度。

好的,这仍然有点抽象。而且,我们还一直在谈论迭代……所以,是时候更具体一点,展示一下 FlashAttention 的前向传播的实际算法啦。

2.3 前向传播

在深入细节之前,我觉得先看看前向传播的高级执行过程是有意义的,并说明我们是如何迭代数据的。这已经可以让我们理解算法的大部分内容啦。

Figure 9: Animation of iterating over the data in the outer and inner loop of the FlashAttention algorithm. Image by Sascha Kirch.

图 9:FlashAttention 算法中外循环和内循环迭代数据的动画。

这个动画里有很多东西(抱歉啦,我也是没办法 😅),所以你应该明白以下几点:

  • 我们有两个循环来处理输入:
    • 一个外循环,迭代 KV 的块,索引为 j
    • 一个内循环,迭代 Q 的块,索引为 i
  • 我们多次迭代输出矩阵 O,在每次外循环迭代中更新其值。
  • 我们还跟踪并逐步更新 SoftMax 计算中的指标 m(x)l(x)
  • 我们不需要将 NxN 矩阵写入全局内存,而是将它们的小块保留在共享内存中,并且只将最终结果写入全局内存。
  • 每个块包含多行,对应多个标记,因此我们并行计算多个 SoftMax 操作。

理解这个动画意味着你已经掌握了算法的核心,可能你已经开始明白它是如何节省时间和内存的啦。虽然对有些人来说这可能已经足够了,但我认为深入探究一下正在发生的事情是值得的。

我们继续来看看那些块状的中间结果,了解我们如何为它们计算 SoftMax。

Figure 10: How the SoftMax is applied across data chunks. Image by Sascha Kirch.

图 10:SoftMax 如何应用于数据块。

虽然内循环迭代索引为 i 的行,但外循环迭代索引为 j 的列。回想一下,SoftMax 是针对每一行(代表输入序列中的一个单独标记)计算的。因此,如果我们处理一个数据块,我们需要同时计算多个 SoftMax 操作。此外,随着我们在外循环中向前推进,我们需要将当前块 x(2) 的 SoftMax 与之前块 x(1) 的 SoftMax 结合起来。

有了这些知识,我们终于可以看看 FlashAttention 论文中呈现的实际算法啦(我们稍后会详细讨论初始化和迭代更新):

Figure 11: FlashAttention algorithm of the forward pass. Image by Sascha Kirch.

图 11:FlashAttention 前向传播算法。
试着将算法与上面的动画对应起来。同时,要明白这个算法融合了两个矩阵乘法内核和 SoftMax 内核,并且没有将 NxN 矩阵写入全局内存(即 HBM)。

你现在可能会问:如果我们没有中间结果,那我们该如何计算反向传播中的梯度呢?我们稍后会讨论这个问题。但首先,我们先完成前向传播。

我们来仔细看看步骤 6–11,我们在其中为当前块的 x 计算 SoftMax,并相应地更新指标 m(x)l(x)

此时,我们已经计算了 QK 的矩阵乘法,当前块的指标 m(x)l(x),并将它们与之前块的指标结合起来,以获得更新后的指标。

现在我们可以使用当前块的 x 和更新后的指标来更新输出矩阵 O。这是在步骤 12 中完成的。这一步有点棘手,因为它已经融合了 PV 的矩阵乘法内核与 SoftMax 内核。所以,让我们详细看看这一步:

None

图 12:解释 FlashAttention 算法内循环中的输出方程。

diag(向量)构造一个矩阵,该向量在对角线上,其余位置为零。这是一种技巧,用于将矩阵的每一行与向量中的对应值相乘。我们这样做是因为我们分别计算每个块的 SoftMax,因为这些对应于单独的标记。

再次强调,这种递归更新与融合内核相结合,确实很难理解 😅。但如果你代入值并进行数学运算,你会发现一切都很完美地结合在一起。

我们还没有涵盖的是算法中步骤 2 的初始化,即指标 m(x)l(x) 以及输出矩阵 O 的初始化。

由于 Om(x)l(x) 的更新是递归进行的(即我们重复使用之前迭代的结果),我们需要考虑一个基本情况,以防我们没有之前的迭代。因此,我们将 Ol(x) 初始化为零,将 m(x) 初始化为负无穷大。如果你进行数学运算并将初始值代入分解的 SoftMax 方程,你会发现我们得到了应用于 x 的第一块的标准 SoftMax 操作。

Figure 14: How and why the outputs are initialized the way they are. Image by Sascha Kirch.

图 13:输出为何以这种方式初始化。

剩下的就是运行整个算法并输出最终的 Nxd 矩阵 O。这就是前向传播!🎉恭喜你还坚持到了这里!

接下来,让我们讲讲通常在大多数博客文章中被忽视的部分:反向传播。这里就是我们计算梯度并更新模型参数的地方。

你知道吗?FlashAttention 的作者 Tri Dao 还参与了 Mamba 状态空间模型的研究。这是一种用于序列建模的替代 Transformer 的方法,可以将计算复杂度从 O(N²) 降低。如果你想了解更多,可以看看我关于它的系列文章哦。

2.4 反向传播

通常,反向传播并不是什么大不了的事,那我们为什么还要在这里讲呢?原因有两个:

  1. 我们缺少前向传播中用于计算梯度的中间结果,因为我们为了节省内存和时间而没有保存它们。
  2. 在反向传播中,我们也有和前向传播一样的问题:我们不希望生成巨大的 NxN 矩阵。所以,我们仍然需要一个在共享内存上操作的融合内核。

不过好消息是,反向传播的算法与前向传播非常相似,我们分别在外循环和内循环中迭代相同的数据块。

那么,那些丢失的中间结果怎么办呢?其实,我们可以在反向传播中重新计算它们。记住,我们的 GPU 核心比从全局内存读取和写入数据的速度快得多。所以即使我们像在前向传播中那样低效地重新计算相同的中间结果,我们也比将它们写入全局内存要快。

实际上,我们不需要重新计算所有内容:还记得我们把指标 m(x)l(x) 保留在全局内存中吗?现在我们可以利用它们来计算当前块的 x 的 SoftMax,并且这次从一开始就计算正确!

在跳到算法之前,我们先试着理解一下我们需要计算什么。

Figure 15: Comparing the forward pass and the backward pass through the attention layer. Image by Sascha Kirch.

图 14:比较注意力层的前向传播和反向传播。

我们需要计算损失相对于模型参数的梯度。这是通过应用链式法则来完成的,一次计算一个梯度,直到我们得到相对于输入矩阵 QKV 的损失梯度。所以,就像我的教授过去常说的那样:“这只是练习,不是惩罚”,让我们写下这些方程。

正如我们在后面将要看到的,这些方程中有一些需要注意的地方,我会明确指出:

  1. 计算 SoftMax 操作的梯度时,我们需要格外小心,因为 SoftMax 是按行计算的,而不是针对整个矩阵。这意味着我们需要分别计算输出矩阵 O 每一行的梯度!
  2. 术语可能会有点令人困惑,因为损失 L 相对于某个其他值(比如 V)的梯度表示为 𝜕L/𝜕V,并且用 dV 表示。
  3. 最后,我们仍然需要考虑分块迭代。

但,我们先从“简单”的开始,应用链式法则,看看我们需要计算哪些项。回想一下,我们感兴趣的是 dVdKdQ

Figure 16: Applying the chain rule to the gradients in the backward pass of the attention layer. Image by Sascha Kirch.

图 15:在注意力层的反向传播中应用链式法则计算梯度。

现在,让我们逐层计算这些梯度。我们先从 O=PV 的矩阵乘法开始。

Figure 17: Step 1 of back propagation through the attention layer: Multiplication of P and V. Image by Sascha Kirch.

图 16:反向传播通过注意力层的第一步:PV 的乘法。

通过这一步,我们已经计算出了我们感兴趣的第一个梯度:dV。可能最复杂的部分是理解如何对矩阵求导,尤其是转置是从哪里来的,为什么 dO 要么从左边乘,要么从右边乘。如果你扩展了表达式,并计算了相对于矩阵每个元素的导数,你就会明白转置是从哪里来的。作为一个经验法则,如果相对于第一个矩阵求导,上游梯度 dO(这是数学上的花哨说法,表示相对于输出矩阵 O 的损失梯度)从左边乘;如果相对于第二个矩阵求导,从右边乘。

计算 dKdQ 需要我们通过 SoftMax 和 QK 的矩阵乘法进行反向传播。我们继续来看 SoftMax。回想一下,SoftMax 是针对每一行 i 计算的,而不是针对整个矩阵。按照索引的表示,i: 表示第 i 行的所有列 j

Figure 18: Step 2 of backpropagation through the attention layer: the SoftMax operation. Image by Sascha Kirch.

图 17:反向传播通过注意力层的第二步:SoftMax 操作。

通过一些代数技巧,我们得到了单行的梯度 dS,并且通过计算所有行并将它们堆叠在一起,我们最终得到了 dS。有了 dS,我们现在可以计算相对于 QK 的矩阵乘法的梯度,以得到我们缺失的感兴趣梯度:dKdQ

Figure 19: Step 3 of back propagation through the attention layer: obtaining dQ and dK. Image by Sascha Kirch.

图 18:反向传播通过注意力层的第三步:得到 dQdK

就这样,我们已经计算出了注意力机制的所有梯度 dQdKdV,然后可以进一步向后传播并更新模型参数啦。

在查看论文中的实际算法之前,我们需要再讲最后一个内容:我们需要考虑分块和共享内存上的迭代,因为每次我们只有访问数据小块的权限。所以,在内循环的每次迭代中,我们需要重新计算中间结果。好消息是,我们已经将指标 m(x)l(x) 保留在全局内存中,所以我们可以利用它们来计算当前块的 x 的 SoftMax,而不需要再次逐步更新。

Figure 20: Recomputation of the intermediate results during FlashAttention’s backward pass. Image by Sascha Kirch.

图 19:在 FlashAttention 的反向传播中重新计算中间结果。

最后,我们可以看看反向传播的算法啦。

注意,它还涵盖了掩码和丢弃操作,我们之前为了简化而省略了这些,这样我们就可以专注于 SoftMax 部分。

Figure 21: FlashAttention algorithm of the backward pass. Image by Sascha Kirch.

图 20:FlashAttention 反向传播算法。

至此,我们已经完成了整个反向传播算法的讲解。🎉

让我们快速总结一下,回顾一下我们在本系列的这一部分中学到了什么。

我很好奇大家的想法 — 如果有什么让你印象深刻,或者你有不同的看法,欢迎在评论区留言哦。

如果你觉得这篇内容对你有帮助,那就给我点个赞吧,这样能让更多人看到哦。感谢你的阅读!

3. 总结

哇,这一趟下来可真是够刺激的,对吧? 😅 但你坚持到了最后,太厉害啦!

FlashAttention 的基本思想其实很简单:我们将注意力层中的所有内核融合在一起,避免将巨大的 NxN 矩阵写入全局内存。为了实现这一点,我们需要解决两个问题:

  1. 将 SoftMax 操作分解,使其能够应用于输入矩阵的单独块。
  2. 在反向传播中重新计算中间结果,而不需要生成巨大的 NxN 矩阵。

我们花时间深入研究了算法的细节,理解了它是如何工作的。

4. 参考

论文

  • FlashAttention: Fast and Memory-Efficient Exact Attention with IO-Awareness by Tri Dao et al. (2022)
  • FlashAttention-2: Faster Attention with Better Parallelism and Work Partitioning by Tri Dao (2023)
  • FlashAttention-3: Fast and Accurate Attention with Asynchrony and Low-precision by Jay Sha et al. (2024)

博客文章

代码

  • FlashAttention GitHub Repository

相关文章:

从零开始理解FlashAttention:算法细节图解

🧠 向所有学习者致敬! “学习不是装满一桶水,而是点燃一把火。” —— 叶芝 我的博客主页: https://lizheng.blog.csdn.net 🌐 欢迎点击加入AI人工智能社区! 🚀 让我们一起努力,共创…...

js原型污染 + xss劫持base -- no-code b01lersctf 2025

题目信息:Found this new web framework the other day—you don’t need to write any code, just JSON. 我们先来搞清楚究竟发生了什么 当我们访问 /index /*** 处理 /:page 路径的 GET 请求* param {Object} req - 请求对象* param {Object} reply - 响应对象* returns {Pro…...

面试题:Java集合框架高频面试题总结

# Java集合框架高频面试题总结 ## 集合框架概述 1. **Java集合框架的主要组成部分** - Collection接口 - List: 有序可重复 - Set: 无序不可重复 - Queue: 队列 - Map接口: 键值对存储 2. **集合框架的继承体系** - Collection - List → Arra…...

【大模型ChatGPT+ArcGIS】数据处理、空间分析、可视化及多案例综合应用

在数字化和智能化的浪潮中,GIS(地理信息系统)和GPT(生成式预训练模型)的结合正日益成为推动科研、城市规划、环境监测等领域发展的关键技术。GIS以其强大的空间数据处理、先进的空间分析工具、灵活的地图制作与可视化能…...

使用JMETER中的JSON提取器实现接口关联

一、JSON提取器介绍 JSON提取器是JMETER工具中用于从JSON响应中提取数据的重要组件,常常用于接口关联场景中(参数传递)。 二、添加JSON提取器 举例(积分支付接口请求数据依赖于创建订单接口响应的payOrderId) 1.在…...

Filecoin存储管理:如何停止Lotus向特定存储路径写入新扇区数据

Filecoin存储管理:如何停止Lotus向特定存储路径写入新扇区数据 引言背景问题场景解决方案步骤1:修改sectorstore.json文件步骤2:重新加载存储配置步骤3:验证更改 技术原理替代方案最佳实践结论 引言 在Filecoin挖矿过程中&#x…...

Elasticsearch太重?它的超轻量的替代品找到了!

简要介绍 在海量数据时代,快速而精准地找到所需信息至关重要。如果您正为此苦恼,或者您是 Elasticsearch 的用户,并对其资源消耗或性能有所关注,那么今天我要向您介绍一款名为 Manticore Search 的开源搜索数据库,它或…...

【计算机视觉】OpenCV实战项目: Fire-Smoke-Dataset:基于OpenCV的早期火灾检测项目深度解析

Fire-Smoke-Dataset:基于OpenCV的早期火灾检测项目深度解析 在当今数字化时代,火灾检测技术的智能化发展至关重要。传统的火灾检测方法依赖于烟雾传感器或人工监控,往往存在响应延迟或误报的问题。而随着计算机视觉技术的飞速发展&#xff0…...

STM32--PWM--函数

TIM_OCInitTypeDef TIM_OCInitTypeDef 是 STM32 标准外设库中用于配置定时器输出比较(Output Compare, OC)功能的结构体,主要用于 PWM 生成、单脉冲输出等场景。 typedef struct {uint16_t TIM_OCMode; // 输出比较模式uint16_t TIM_…...

软件测试应用技术(3) -- 软件评测师(十六)

5 事件驱动架构软件测试 5.1 事件驱动架构软件测试概述 事件驱动架构,简称EDA,是常用的架构范式中的一种,其关注事件的产生、识别、处理、响应。对于事件驱动架构系统的测试应特别注意其业务逻辑处理上的异步特性导致的缺陷和事件队列处理中…...

人工智能之数学基础:二次型

本文重点 二次型作为线性代数领域的重要概念,架起了代数方程与几何分析之间的桥梁。从古典解析几何中的圆锥曲线方程到现代优化理论中的目标函数,二次型以其简洁的数学表达和丰富的结构特性,在数学物理、工程技术和经济金融等领域发挥着不可替代的作用。 二次型的基本概念…...

MongoDB 创建索引原则

MongoDB索引创建原则 MongoDB索引是提高查询性能的关键工具,以下是创建索引的核心原则和最佳实践: 一、索引基础原则 ‌1. 索引本质‌:索引类似书籍目录,通过B-Tree数据结构对字段值排序存储,使查询复杂度从O(n)降为…...

空间复杂度** 与 **所需辅助空间**

当我们说一个算法的 空间复杂度是 O(1),通常特指“辅助空间”是 O(1),即:除了输入数据本身之外,算法只使用常数级别的额外空间。 ✅ 正确认解: O(1) 空间复杂度 ≈ O(1) 辅助空间 这表示: 不随输入规模增长…...

Spring Web MVC快速入门

什么是Spring Web MVC Spring Web MVC 是基于 Servlet API 构建的原始 Web 框架,从⼀开始就包含在 Spring 框架中。它的正式名称“Spring Web MVC”来⾃其源模块的名称(Spring-webmvc),但它通常被称为"Spring MVC". View(视图) 指在应⽤程序…...

RT-Thread 深入系列 Part 1:RT-Thread 全景总览

摘要: 本文将从 RTOS 演进、RT-Thread 的版本分支、内核架构、核心特性、社区与生态、以及典型产品应用等多维度,全面呈现 RT-Thread 的全景图。 关键词:RT-Thread、RTOS、微内核、组件化、软件包管理、SMP 1. RTOS 演进与 RT-Thread 定位 2…...

黄金、碳排放期货市场API接口文档

StockTV 提供了多种期货市场的数据接口,包括获取K线图表数据、查询特定期货的实时行情等。以下为对接期货市场的详细接口说明。 一、获取K线图表数据 通过调用/futures/kline接口,您可以获取指定期货合约的历史K线数据(例如开盘价、最高价、…...

SpringAI框架中的RAG知识库检索与增强生成模型详解

SpringAI框架中的RAG知识库检索与增强生成模型详解 一、RAG简介 RAG(Retrieval-Augmented Generation)可以通过检索知识库,克服大模型训练完成后参数冻结的局限性,携带知识让大模型根据知识进行回答。 二、SpringAI框架支持的R…...

LVGL- 按钮矩阵控件

1 按钮矩阵控件 lv_btnmatrix 是 LVGL(Light and Versatile Graphics Library) v8 中提供的一个非常实用的控件,用于创建带有多个按钮的矩阵布局。它常用于实现虚拟键盘、数字键盘、操作面板、选择菜单等场景,特别适用于嵌入式设…...

C++学习-入门到精通-【5】类模板array和vector、异常捕获

C学习-入门到精通-【5】类模板array和vector、异常捕获 类模板array和vector、异常捕获 C学习-入门到精通-【5】类模板array和vector、异常捕获一、array对象array对象的声明使用array对象的例子使用常量变量指定array对象的大小 二、基于范围的for语句三、利用array对象存放成…...

`待办事项css样式

vue <template> <div class"box"> <div class"head"> <h2>待办事项</h2> <input type"text" placeholder"请输入您的待办事项&#xff0c;按回车添加"> </div> <div class"main&q…...

spring ai alibaba 使用 SystemPromptTemplate 很方便的集成 系统提示词

系统提示词可以是.st 文件了&#xff0c;便于修改和维护 1提示词内容&#xff1a; 你是一个有用的AI助手。 你是一个帮助人们查找信息的人工智能助手。 您的名字是{name} 你应该用你的名字和{voice}的风格回复用户的请求。 每一次回答的时候都要增加一个65字以内的标题形如:【…...

Vue3 官方宣布淘汰 Axios,拥抱Alova.js

过去十年,Axios 凭借其简洁的API设计和浏览器/Node.js双环境支持,成为前端开发者的首选请求库。但随着现代前端框架的演进和工程化需求的升级,Alova.js 以更轻量、更智能、更符合现代开发范式的姿态登场。 一、Axios的痛点 1,冗余的适配逻辑,比如Axios的通用配置(但实际…...

2025年数维杯C题数据收集方式分享

2025年数维杯C题”清明时节雨纷纷&#xff0c;何处踏青不误春&#xff1f;“需要我们根据题目的要求自行数据&#xff0c;下图为目前已经收集到的问题一二数据集&#xff0c;本文将为大家详细的介绍具体收集数据方式以及处理方式。 通过网盘分享的文件&#xff1a;分享数据集 …...

手写 vue 源码 === ref 实现

目录 响应式的基本实现 Proxy 与属性访问器 Proxy 的工作原理 属性访问器&#xff08;Getter/Setter&#xff09; 为什么解构会丢失响应性 ref 和 toRefs 的解决方案 proxyRefs&#xff1a;自动解包 ref 总结 Vue3 的响应式系统是其核心特性之一&#xff0c;它通过 Pro…...

Python爬虫抓取Bilibili弹幕并生成词云

1. 引言 Bilibili&#xff08;B站&#xff09;是国内知名的视频分享平台&#xff0c;拥有海量的弹幕数据。弹幕是B站的核心特色之一&#xff0c;用户通过弹幕进行实时互动&#xff0c;这些数据对于分析视频热度、用户情感倾向等具有重要价值。 本文将介绍如何利用Python爬虫技…...

【Python 字典(Dictionary)】

Python 中的字典&#xff08;Dictionary&#xff09;是最强大的键值对&#xff08;key-value&#xff09;数据结构&#xff0c;用于高效存储和访问数据。以下是字典的核心知识点&#xff1a; 一、基础特性 键值对存储&#xff1a;通过唯一键&#xff08;Key&#xff09;快速访…...

k8s之探针

探针介绍&#xff1a; 编排工具运行时&#xff0c;虽说pod挂掉会在控制器的调度下会重启&#xff0c;出现pod重启的时候&#xff0c;但是pod状态是running,无法真实的反应当时pod健康状态&#xff0c;我们可以通过Kubernetes的探针监控到pod的实时状态。 Kubernetes三种探针类…...

upload-labs靶场通关详解:第三关

一、分析源代码 代码注释如下&#xff1a; <?php // 初始化上传状态和消息变量 $is_upload false; $msg null;// 检查是否通过POST方式提交了表单 if (isset($_POST[submit])) {// 检查上传目录是否存在if (file_exists(UPLOAD_PATH)) {// 定义禁止上传的文件扩展名列表…...

LeetCode:101、对称二叉树

递归法&#xff1a; /*** Definition for a binary tree node.* public class TreeNode {* int val;* TreeNode left;* TreeNode right;* TreeNode() {}* TreeNode(int val) { this.val val; }* TreeNode(int val, TreeNode left, TreeNode right) {…...

zst-2001 历年真题 UML

UML - 第1题 ad UML - 第2题 依赖是暂时使用对象&#xff0c;关联是长期连接 依赖&#xff1a;依夜情 关联&#xff1a;天长地久 组合&#xff1a;组一辈子乐队 聚合&#xff1a;好聚好散 bd UML - 第3题 adc UML - 第4题 bad UML - 第5题 d UML - 第6题 …...

对称加密以及非对称加密

对称加密和非对称加密是两种不同的加密方式&#xff0c;它们在加密原理、密钥管理、安全性和性能等方面存在区别&#xff0c;以下是具体分析&#xff1a; 加密原理 对称加密&#xff1a;通信双方使用同一把密钥进行加密和解密。就像两个人共用一把钥匙&#xff0c;用这把钥匙锁…...

Java反射 八股版

目录 一、核心概念阐释 1. Class类 2. Constructor类 3. Method类 4. Field类 二、典型应用场景 1. 框架开发 2. 单元测试 3. JSON序列化/反序列化 三、性能考量 四、安全与访问控制 1. 安全管理器限制 2. 打破封装性 3. 安全风险 五、版本兼容性问题 六、最佳…...

C++跨平台开发实践:深入解析与常见问题处理指南

一、跨平台开发基础架构设计 1.1 跨平台架构的核心原则 分层设计模式&#xff1a; 平台抽象层(PAL)&#xff1a;将平台相关代码集中管理 核心逻辑层&#xff1a;完全平台无关的业务代码 平台实现层&#xff1a;针对不同平台的特定实现 代码组织最佳实践&#xff1a; pro…...

【“星睿O6”AI PC开发套件评测】+ MTCNN 开源模型部署和测试对比

经过了前几篇文章的铺垫&#xff0c;从搭建 tensorflow 开发环境&#xff0c;到测试官方 onnx 模型部署到 NPU&#xff0c;接着部署自己的 mnist tensorflow 模型到 NPU。这是一个从易到难的过程&#xff0c;本篇文章介绍开源复杂的人脸识别模型 mtcnn 到 “星睿O6” NPU 的部署…...

JAVA实战开源项目:装饰工程管理系统 (Vue+SpringBoot) 附源码x

本文项目编号 T 179 &#xff0c;文末自助获取源码 \color{red}{T179&#xff0c;文末自助获取源码} T179&#xff0c;文末自助获取源码 目录 一、系统介绍二、数据库设计三、配套教程3.1 启动教程3.2 讲解视频3.3 二次开发教程 四、功能截图五、文案资料5.1 选题背景5.2 国内…...

centos 7 安装 java 运行环境

centos 7 安装 java 运行环境 java -version java version "1.8.0_131" Java(TM) SE Runtime Environment (build 1.8.0_131-b11) Java HotSpot(TM) 64-Bit Server VM (build 25.131-b11, mixed mode)java -version java version "1.8.0_144" Java(TM) …...

力扣题解:21.合并两个有序链表(C语言)

将两个升序链表合并为一个新的升序链表是一个经典的链表操作问题。可以通过递归或迭代的方法来解决。以下是解释和代码实现&#xff1a; 递归&#xff1a; 每次比较两个链表的头节点&#xff0c;将较小的节点添加到新链表中&#xff0c;并递归处理剩余部分。 截至条件&#xf…...

iOS App 安全性探索:源码保护、混淆方案与逆向防护日常

iOS App 安全性探索&#xff1a;源码保护、混淆方案与逆向防护日常 在 iOS 开发者的日常工作中&#xff0c;我们总是关注功能的完整性、性能的优化和UI的细节&#xff0c;但常常忽视了另一个越来越重要的问题&#xff1a;发布后的应用安全。 尤其是对于中小团队或独立开发者&…...

SpringBoot默认并发处理(Tomcat)、项目限流详解

SpringBoot默认并发处理 在 Spring Boot 项目中&#xff0c;默认情况下&#xff0c;同一时间能处理的请求数由​​内嵌的 Tomcat 服务器​​的线程池配置决定。 默认并发处理能力​ 请求处理流程​ 请求到达​​&#xff1a;新请求首先进入 TCP 连接队列&#xff08;最大 ma…...

Xterminal(或 X Terminal)通常指一类现代化的终端工具 工具介绍

Xterminal&#xff08;或 X Terminal&#xff09;通常指一类现代化的终端工具&#xff0c;旨在为开发者、运维人员提供更高效、更智能的命令行操作体验。 &#x1f4e2;提示&#xff1a;文章排版原因&#xff0c;资源链接地址放在文章结尾&#x1f447;&#x1f447;&#xff…...

如何把win10 wsl的安装目录从c盘迁移到d盘

标题&#xff1a;如何把win10 wsl的安装目录从c盘迁移到d盘 通过microsoft store安装的 wsl 目录默认在 C:\Users[用户名]\AppData\Local\wsl 下 wsl的docker镜像以及dify的编译环境会占用大量硬盘空间&#xff0c;0.15.3 、1.1.3、1.3.1 三个版本的环境占用空间超过40GB [图…...

2025医疗信息化趋势:健康管理系统如何重构智慧医院生态

当北京协和医院的门诊大厅启用智能分诊机器人时&#xff0c;距离其3000公里外的三甲医院正通过健康管理系统将慢性病复诊率降低42%。这场静默发生的医疗革命&#xff0c;正在重新定义2025年智慧医院的建设标准。 一、穿透数据孤岛的三大核心引擎 最新版《智慧医院评价指标体系…...

java volatile关键字

volatile 是 Java 中用于保证多线程环境下变量可见性和禁止指令重排序的关键字。 普通变量不加volatile修饰有可见性问题&#xff0c;即有线程修改该变量值&#xff0c;其他线程无法立即感知该变量值修改了。代码&#xff1a; private static int intVal 0; // 普通变量未加 …...

中阳策略模型:结构节奏中的方向感知逻辑

中阳策略模型&#xff1a;结构节奏中的方向感知逻辑 在交易世界中&#xff0c;“节奏”与“结构”的互动远比大多数人想象得复杂。中阳研究团队在大量实战数据分析中提出一个核心观点&#xff1a;方向感的建立&#xff0c;必须以结构驱动为前提&#xff0c;以节奏确认为依据。 …...

死锁的形成

死锁的形成 背景学习资源死锁的本质 背景 面试可能会被问到. 学习资源 一个案例: https://www.bilibili.com/video/BV1pz421Y7kM 死锁的本质 互相持有对方的资源. 存在资源竞争都没有释放. 可能出现死锁. insert into demo_user (no, name) values (6, ‘test1’) on dupl…...

每天五分钟深度学习框架pytorch:视觉工具包torchvison

本文重点 在pytorch深度学习框架中,torchvision是一个非常优秀的视觉工具包,我们可以使用它加载一些著名的数据集,然后我们可以使用它来加载网络模型,比如vgg,resnet等等,还可以使用它来预处理一些图片数据,本节课程我们将学习一下它的使用方式。 torchvision的四部分…...

C++之运算符重载实例(日期类实现)

日期类实现 C 日期类的实现与深度解析一、代码结构概览1.1 头文件 Date.h1.2 源文件 Date.cpp 二、关键函数实现解析2.1 获取某月天数函数 GetMonthDay2.2 构造函数 Date2.3 日期加减法运算2.4 前置与后置自增/自减操作2.5 日期比较与差值计算 三、代码优化与注意事项3.1 代码优…...

数据分析怎么做?高效的数据分析方法有哪些?

目录 一、数据分析的对象和目的 &#xff08;一&#xff09;数据分析的常见对象 &#xff08;二&#xff09;数据分析的目的 二、数据分析怎么做&#xff1f; &#xff08;一&#xff09;明确问题 &#xff08;二&#xff09;收集数据 &#xff08;三&#xff09;清洗和…...

数组和切片的区别

&#x1f49d;&#x1f49d;&#x1f49d;欢迎来到我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 非常期待和您一起在这个小…...

【Linux】自定义shell的编写

&#x1f4dd;前言&#xff1a; 这篇文章我们来讲讲【Linux】简单自定义shell的编写&#xff0c;通过这个简单的模拟实现&#xff0c;进一步感受shell的工作原理。 &#x1f3ac;个人简介&#xff1a;努力学习ing &#x1f4cb;个人专栏&#xff1a;Linux &#x1f380;CSDN主页…...