flash-linear-attention中的Chunkwise并行算法的理解

news2024/11/16 23:52:54

这里提一下,我维护的几三个记录个人学习笔记以及社区中其它大佬们的优秀博客链接的仓库都获得了不少star,感谢读者们的认可,我也会继续在开源社区多做贡献。github主页:https://github.com/BBuf ,欢迎来踩

在这里插入图片描述

0x0. 前言

我之前解读过causal linear attention的cuda实现,文章见:https://zhuanlan.zhihu.com/p/673896906 ,也是在评论区通过@sonta 了解到了flash-linear-attention的Chunkwise并行实现。上篇文章https://mp.weixin.qq.com/s/H6wWBxwIJNCzkIlH_uIuiw中说到后续想继续解析一下chunk_rwkv6的实现,chunk_rwkv6的实现思路仍然是沿用flash-linear-attention中的Chunkwise并行思路,由于之前没有认真看过这个Chunkwise的算法所以读起来有点困难,这里需要用普通并行以及RNN递归的视角去看待才能理解这个算法流程。这篇文章就从 Gated Linear Attention Transformers with Hardware-Efficient Training (https://arxiv.org/pdf/2312.06635) 这篇Paper对线性Attention的Chunwise并行讲解和伪代码入手深入理解下这个方法,另外我们也会在后面深入分析下代码的实现。这篇Paper的作者也是flash-linear-attention的作者。

0x1. Paper部分

Paper部分这里只关注Background里面和Linear Attention相关的两节。这里对其进行翻译和解读。

在这里插入图片描述

我们首先简要介绍一下线性注意力层的背景。对于符号表示,我们使用黑体大写字母表示矩阵(例如,S、Q),黑体小写字母表示向量(例如, q t q_t qt k t k_t kt),斜体大写字母表示可学习的参数矩阵(例如, W K W_K WK)。通常我们使用相同的字母表示矩阵的行,例如, q t q_t qt 表示矩阵 Q Q Q 的第 t t t 行。

在这里插入图片描述

2.1 并行和递归形式

标准的Transformers采用softmax注意力机制,该机制接受输入序列 X ∈ R L × d X \in \mathbb{R}^{L \times d} XRL×d(其中 L L L 是长度, d d d 是隐藏维度)并通过以下方式计算输出 O ∈ R L × d O \in \mathbb{R}^{L \times d} ORL×d

Q , K , V = X W Q , X W K , X W V , O = softmax ( ( Q K T ) ⊙ M ) V , Q, K, V = XW_Q, XW_K, XW_V, O = \text{softmax}\left((QK^T) \odot M\right) V, Q,K,V=XWQ,XWK,XWV,O=softmax((QKT)M)V,

其中 W Q , W K , W V ∈ R d × d W_Q, W_K, W_V \in \mathbb{R}^{d \times d} WQ,WK,WVRd×d 是可学习的矩阵, M ∈ { − ∞ , 1 } L × L M \in \{-\infty, 1\}^{L \times L} M{,1}L×L 是一个掩码,用于防止模型关注未来的token,即 M i j = 1 M_{ij} = 1 Mij=1 i ≥ j i \geq j ij M i j = − ∞ M_{ij} = -\infty Mij= i < j i < j i<j。 (这里我们假设一个简单的单头注意力。)上述的并行注意力形式可以在给定完整输入 X X X 的情况下并行计算 O O O,从而实现高效训练。然而,在推理过程中,Transformer必须使用以下递归形式:

q t , k t , v t = x t W Q , x t W K , x t W V q_t, k_t, v_t = x_t W_Q, x_t W_K, x_t W_V qt,kt,vt=xtWQ,xtWK,xtWV

o t = ∑ i = 1 t exp ⁡ ( q t k i T ) v i ∑ i = 1 t exp ⁡ ( q t k i T ) o_t = \frac{\sum_{i=1}^{t} \exp(q_t k_i^T) v_i}{\sum_{i=1}^{t} \exp(q_t k_i^T)} ot=i=1texp(qtkiT)i=1texp(qtkiT)vi

其根据当前token的表示 x t ∈ R 1 × d x_t \in \mathbb{R}^{1 \times d} xtR1×d 计算查询 ( q t ) ( q_t ) (qt)、键 ( k t ) ( k_t ) (kt)和值 ( v t ) ( v_t ) (vt)向量,并对键 { k 1 , … , k t } \{k_1, \ldots, k_t\} {k1,,kt} 和值 { v 1 , … , v t } \{v_1, \ldots, v_t\} {v1,,vt}(即 “KV Cache”)集合进行注意力计算。

线性注意力机制(Katharopoulos等人,2020)用一个具有相关特征映射 ϕ \phi ϕ 的核函数 k ( x , y ) k(x, y) k(x,y)替换 exp ⁡ ( q t k i T ) \exp(q_t k_i^T) exp(qtkiT)(即, k ( x , y ) = ⟨ ϕ ( x ) , ϕ ( y ) ⟩ k(x, y) = \langle \phi(x), \phi(y) \rangle k(x,y)=ϕ(x),ϕ(y)⟩)。

在这里插入图片描述

由于我们有
o t = ∑ i = 1 t ϕ ( q t ) ϕ ( k i ) ⊤ v i ∑ i = 1 t ϕ ( q t ) ϕ ( k i ) ⊤ = ϕ ( q t ) ∑ i = 1 t ϕ ( k i ) ⊤ v i ϕ ( q t ) ∑ i = 1 t ϕ ( k i ) ⊤ . o_t = \frac{\sum_{i=1}^{t} \phi(q_t) \phi(k_i)^{\top} v_i}{\sum_{i=1}^{t} \phi(q_t) \phi(k_i)^{\top}} = \frac{\phi(q_t) \sum_{i=1}^{t} \phi(k_i)^{\top} v_i}{\phi(q_t) \sum_{i=1}^{t} \phi(k_i)^{\top}}. ot=i=1tϕ(qt)ϕ(ki)i=1tϕ(qt)ϕ(ki)vi=ϕ(qt)i=1tϕ(ki)ϕ(qt)i=1tϕ(ki)vi.

S t = ∑ i = 1 t ϕ ( k i ) ⊤ v i S_t = \sum_{i=1}^{t} \phi(k_i)^{\top} v_i St=i=1tϕ(ki)vi z t = ∑ i = 1 t ϕ ( k i ) ⊤ z_t = \sum_{i=1}^{t} \phi(k_i)^{\top} zt=i=1tϕ(ki)其中 S t ∈ R d × d , z t ∈ R d × 1 , S_t \in \mathbb{R}^{d \times d}, z_t \in \mathbb{R}^{d \times 1}, StRd×d,ztRd×1, 我们可以将上述公式重写为一个 RNN,
S t = S t − 1 + ϕ ( k t ) ⊤ v t , z t = z t − 1 + ϕ ( k t ) ⊤ , o t = ϕ ( q t ) S t ϕ ( q t ) z t . S_t = S_{t-1} + \phi(k_t)^{\top} v_t, z_t = z_{t-1} + \phi(k_t)^{\top}, o_t = \frac{\phi(q_t) S_t}{\phi(q_t) z_t}. St=St1+ϕ(kt)vt,zt=zt1+ϕ(kt),ot=ϕ(qt)ztϕ(qt)St.

尽管已经探索了各种核函数 (Kasai 等, 2021; Peng 等, 2021; Choromanski 等, 2020),最近的工作发现一个线性核(即,将 ϕ \phi ϕ 设为恒等映射)在实践中无需正则化器也能很好地工作 (Qin 等, 2022)。这导致了一个(未正则化的)线性注意力层,其更新方程如下,
S t = S t − 1 + k t ⊤ v t , o t = q t S t . ( 1 ) S_t = S_{t-1} + k_t^{\top} v_t, o_t = q_t S_t. \quad (1) St=St1+ktvt,ot=qtSt.(1)

方程 1 清楚地表明,线性注意力层本质上是一个带有矩阵值隐藏状态 S t S_t St 的线性递归层,通过外积 k t ⊤ v t = ( x t W K ) ⊤ ( x t W V ) k_t^{\top} v_t = (x_t W_K)^{\top} (x_t W_V) ktvt=(xtWK)(xtWV) 更新。 1 ^1 1 因果线性注意力的并行形式,其复杂度仍然是 L L L 的二次方,公式如下,
O = ( ( Q K ⊤ ) ⊙ M ) V , \mathbf{O} = \left((\mathbf{QK}^{\top}) \odot \mathbf{M}\right) \mathbf{V}, O=((QK)M)V,
其中 M ∈ { 0 , 1 } L × L \mathbf{M} \in \{0,1\}^{L \times L} M{0,1}L×L 是一个掩码,使得 M i j = 1 \mathbf{M}_{ij} = 1 Mij=1 i ≥ j i \geq j ij 时,且 M i j = 0 \mathbf{M}_{ij} = 0 Mij=0 i < j i < j i<j 时。由于 M \mathbf{M} M 的存在,不可能利用矩阵乘法的结合性质将并行形式的复杂度从二次降低到线性。 2 ^2 2

方程1的推导我没搞清楚怎么推出来的,大佬可以评论区指导下。

2.2 Chunkwise并行形式

在这里插入图片描述

线性注意力的逐chunk并行形式在并行和递归形式之间取得了平衡 (Hua 等, 2022; Sun 等, 2023a),并允许部分并行的训练。形式上,假设输入 X X X 现在被分割为不重叠的块,每个块的长度为 C C C。令 S [ i ] ∈ R d × d \mathbf{S}_{[i]} \in \mathbb{R}^{d \times d} S[i]Rd×d 表示处理 i i i 个块后的块级隐藏状态,即 S [ i ] : = S i C \mathbf{S}_{[i]} := \mathbf{S}_{iC} S[i]:=SiC。进一步设 Q [ i ] : = Q i C + 1 : ( i + 1 ) C + 1 ∈ R C × d \mathbf{Q}_{[i]} := \mathbf{Q}_{iC+1:(i+1)C+1} \in \mathbb{R}^{C \times d} Q[i]:=QiC+1:(i+1)C+1RC×d 为对应于第 i i i 块的查询向量;令 K [ i ] , V [ i ] , O [ i ] \mathbf{K}_{[i]}, \mathbf{V}_{[i]}, \mathbf{O}_{[i]} K[i],V[i],O[i] 类似地定义。然后我们有以下跨chunk递归公式(对于 i ∈ { 0 , 1 , . . . , L C − 1 } i \in \{0, 1, ..., \frac{L}{C} - 1\} i{0,1,...,CL1}):

S [ i + 1 ] = S [ i ] + ∑ j = i C + 1 ( i + 1 ) C k j ⊤ v j ∈ R d × d . ( 2 ) \mathbf{S}_{[i+1]} = \mathbf{S}_{[i]} + \sum_{j=iC+1}^{(i+1)C} \mathbf{k}_j^{\top} \mathbf{v}_j \in \mathbb{R}^{d \times d}. \quad (2) S[i+1]=S[i]+j=iC+1(i+1)CkjvjRd×d.(2)

这里 S [ 0 ] \mathbf{S}_{[0]} S[0] 可以初始化为零或者从前一个段的隐藏状态初始化。所有来自一个chunk的(即 K [ i ] ⊤ V [ i ] \mathbf{K}_{[i]}^{\top} \mathbf{V}_{[i]} K[i]V[i])RNN 输入的和可以在 O ( C 2 d ) O(C^2 d) O(C2d) 时间内并行计算。块内并行计算的输出如下所示:

O [ i + 1 ] = Q [ i + 1 ] S [ i ] + ( ( Q [ i + 1 ] K [ i + 1 ] ⊤ ) ⊙ M ) V [ i + 1 ] , \mathbf{O}_{[i+1]} = \mathbf{Q}_{[i+1]} \mathbf{S}_{[i]} + \left((\mathbf{Q}_{[i+1]} \mathbf{K}_{[i+1]}^{\top}) \odot \mathbf{M}\right) \mathbf{V}_{[i+1]}, O[i+1]=Q[i+1]S[i]+((Q[i+1]K[i+1])M)V[i+1],

其中 O [ i + 1 ] ∈ R C × d \mathbf{O}_{[i+1]} \in \mathbb{R}^{C \times d} O[i+1]RC×d。这里的“块内”组件 O [ i + 1 ] intra \mathbf{O}_{[i+1]}^{\text{intra}} O[i+1]intra 具有与公式 1 完全相同的并行形式,因此需要 O ( C 2 d + C d 2 ) O(C^2 d + C d^2) O(C2d+Cd2)。而“块间”组件 O [ i + 1 ] inter \mathbf{O}_{[i+1]}^{\text{inter}} O[i+1]inter 负责从前一个块的隐藏状态贡献,并且需要 O ( C d 2 ) O(C d^2) O(Cd2)。训练复杂度因此为 O ( L C ( C 2 d + C d 2 ) ) = O ( L C d + L d 2 ) O\left(\frac{L}{C}(C^2 d + C d^2)\right) = O(L C d + L d^2) O(CL(C2d+Cd2))=O(LCd+Ld2),当 L > d L > d L>d 时小于 O ( L 2 d ) O(L^2 d) O(L2d)。注意,将 C = L C = L C=L 恢复了并行形式,而 C = 1 C = 1 C=1 恢复了递归形式。

从Paper里对Linear Attention和Chunkwise Linear Attention的描述可以理解到Chunkwise计算中最重要的一点,那就是在chunk间我们在计算KV的时候是不受到causal mask限制的,我们可以用一个大的矩阵乘法并行计算所有chunk的KV。不过由于公式(2)是逐chunk更新的,我们在当前的chunk i i i位置只能看到这个 i i i前面的其它chunk的KV,这也是后面的计算公式里面有一个前缀和的原因。而在chunk内部,则必须根据原始的Causal Attention逻辑来计算,也就是 ( ( Q [ i + 1 ] K [ i + 1 ] ⊤ ) ⊙ M ) V [ i + 1 ] \left((\mathbf{Q}_{[i+1]} \mathbf{K}_{[i+1]}^{\top}) \odot \mathbf{M}\right) \mathbf{V}_{[i+1]} ((Q[i+1]K[i+1])M)V[i+1]

0x2. 完全并行以及Chunkwise版本的Linear Attention测试代码

这里贴一下使用完全并行的Causal Linear Attention和Chunkwise Linear Attention进行计算的代码。

import torch
from einops import rearrange

def naive_linear_attn(q, k, v):
    q = q * (q.shape[-1] ** -0.5)
    scores = torch.matmul(q, k.transpose(-1, -2))
    mask = torch.triu(torch.ones(scores.shape[-2], scores.shape[-1], device=q.device), diagonal=1)
    scores = scores.masked_fill(mask.bool(), float(0))
    output = torch.matmul(scores, v)
    return output

def torch_chunk_linear_attn(q, k, v, chunk_size=64):
    q = rearrange(q, 'b h (n c) d -> b h n c d', c = chunk_size) * (q.shape[-1] **-0.5)
    k = rearrange(k, 'b h (n c) d -> b h n c d', c = chunk_size)
    v = rearrange(v, 'b h (n c) d -> b h n c d', c = chunk_size)
    kv = k.transpose(-1, -2) @ v
    kv = kv.cumsum(2)
    kv = torch.cat([
        torch.zeros_like(kv[:, :, :1]),
        kv[:, :, :-1]
    ], dim=2)
    inter = q @ kv # (b, h, n, c, d) @ (b, h, n, d, d) -> (b, h, n, c, d)
    intra = ((q @ k.transpose(-1, -2)).masked_fill_(torch.triu(torch.ones(chunk_size, chunk_size, dtype=bool, device=q.device), diagonal=1), 0)) @ v
    o = inter + intra
    return rearrange(o, 'b h n c d -> b h (n c) d')


if __name__ == "__main__":
    B = 4
    H = 4
    L = 1024
    D = 100
    dtype = torch.float32
    require_grad = True
    q = (torch.randn(B, H, L, D).to(dtype)).requires_grad_(require_grad)
    k = (torch.randn(B, H, L, D).to(dtype)).requires_grad_(require_grad)
    v = torch.randn(B, H, L, D).to(dtype).requires_grad_(require_grad)
    o1 = torch_chunk_linear_attn(q, k, v)
    o2 = naive_linear_attn(q, k, v)
    print('o1: ', o1.sum())
    print('o2: ', o2.sum())

代码非常短,读了0x1节就很好理解了,这里就不继续啰嗦了。需要注意的是对于这个例子,如果使用float16/bfloat16可能会发生溢出导致测试无法通过,所以需要使用float32来计算。

0x3. Chunkwise Linear Attention的优势

从0x1节已经看到,Chunwise Linear Attention是介于完全并行和RNN递归形式的一种平衡的方案,打破了在Causal mask逻辑下的类Linear Attention结构必须先算q*k的限制,例如在faster-transformers里面实现的Linear Attention(注意Linear Attention中的核函数是可选的,不一定是这篇paper里提到的indentity)使用了一个完整的cuda kernel并且经过一系列优化最终在cuda core上取得了不错的性能,但这个实现却是完全不如Chunkwise Linear Attention的实现的,因为它拆散了gemm无法在Tensor Core上运行。另外一个例子就是,对于RWKV6这种模型来说(请看 https://zhuanlan.zhihu.com/p/696054687),它的naive实现中全部都是elementwise算子(即使是cuda kernel实现也只是做了fuse):

def naive_recurrent_rwkv6(
    q,
    k,
    v,
    w,
    u,
    initial_state=None,
    output_final_state=False
):
    # 记录输入张量 q 的原始数据类型。
    orig_dtype = q.dtype
    # 将输入张量转换为 32 位浮点数类型。
    q, k, v, w, u = map(lambda x: x.float(), (q, k, v, w, u))
    # 获取query张量的形状信息。
    batch_size, n_heads, seq_len, d_head_k = q.shape
    # 获取值张量的形状信息。
    _, _, _, d_head_v = v.shape
    # 初始化注意力张量为全零张量,形状为 (B, H, D, D),在 GPU 上进行计算。
    h = torch.zeros(batch_size, n_heads, d_head_k, d_head_v, dtype=torch.float32, device=q.device)
    # 初始化输出张量为全零张量,形状同值张量 v
    o = torch.zeros_like(v)

    # 如果提供了初始状态 initial_state,则将注意力张量 h 更新为初始状态:
    if initial_state is not None:
        h += initial_state

    # 对序列长度进行迭代,每次迭代处理一个位置的输入:
    for i in range(seq_len):
        q_i = q[:, :, i, :] # 获取当前位置的query张量。shape为[B, H, D]
        k_i = k[:, :, i] # 获取当前位置的key张量。shape为[B, H, D]
        v_i = v[:, :, i, :] # 获取当前位置的value张量。shape为[B, H, D]
        # 获取当前位置的权重张量,并使用指数函数进行处理。shape为[B, H, D]
        w_i = w[:, :, i].exp()
        # 计算当前位置的键值乘积,elementwise操作。
        # shape变化为[B, H, D, 1] * [B, H, D, 1] -> [B, H, D, 1]
        kv_i = k_i[..., None] * v_i[..., None, :] 
        # 计算当前位置的注意力加权输出,都是elementwise操作。
        # h的shape为[B, H, D, D]
        # u[None, ..., None]的shape为[1, H, D, 1]
        # q_i[..., None]的shape为[B, H, D, 1]
        # h + u[None, ..., None] * kv_i 的shape为:
        # [B, H, D, D] + [1, H, D, 1] * [B, H, D, 1] ->
        # [B, H, D, D] + [B, H, D, 1] ->
        # [B, H, D, D]
        o_i = (h + u[None, ..., None] * kv_i) * q_i[..., None] 
        # 将当前位置的输出加入到输出张量中。
        # o[:, :, i]的shape为[B, H, D],o_i.sum(-2)的shape为[B, H, D]
        o[:, :, i] = o_i.sum(-2)
        # 更新注意力张量 h
        # h的shape为[B, H, D, D]
        # w_i[..., None]的shape为[B, H, D, 1]
        # kv_i的shape为[B, H, D, 1]
        # h * w_i[..., None] 的shape为[B, H, D, D]也是element-wise操作
        h = h * w_i[..., None] + kv_i
    return o.to(orig_dtype)

而使用上Chunwise的算法之后,通过一些工程努力就可以把这个代码改成部分矩阵乘的方式,从而使用TensorCore。当序列长度越来越长时,相比于rwkv6的当前实现就可以获得更多的加速。

当然,使用Chunkwise来写各种Linear Attention架构也需要做出一些工程上的努力,看到这个库都是用Triton来实现的,相比于cuda阅读起来会简单非常多,后续有机会再继续阅读。

贴一下作者之前在我写的那个Linear Attention CUDA实现文章下的截图,也算是第一次从评论区学到了一个很棒的算法,respect。

在这里插入图片描述

在这里插入图片描述

0x4. 总结

本文解读了一下flash-linear-attention中的Chunkwise并行算法,希望对从事Linear Attention研究或者工程优化的读者拓宽视野有帮助。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1697868.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MySQL——索引与事务

目录 前言 一、索引 1.索引概述 &#xff08;1&#xff09;基本概念 &#xff08;2&#xff09;索引作用 &#xff08;3&#xff09;索引特点 &#xff08;4&#xff09;适用场景 2.索引的操作 &#xff08;1&#xff09;查看索引 &#xff08;2&#xff09;创建索引…

【算法题】520 钻石争霸赛 2024 全解析

都是自己写的代码&#xff0c;发现自己的问题是做题速度还是不够快 520-1 爱之恒久远 在 520 这个特殊的日子里&#xff0c;请你直接在屏幕上输出&#xff1a;Forever and always。 输入格式&#xff1a; 本题没有输入。 输出格式&#xff1a; 在一行中输出 Forever and always…

2024年推荐的适合电脑和手机操作的线上兼职副业平台

总是会有人在找寻着线上兼职副业&#xff0c;那么在如今的2024年&#xff0c;互联网提供了诸多方便&#xff0c;无论你是宝妈、大学生、程序员、外卖小哥还是打工族&#xff0c;如果你正在寻找副业机会&#xff0c;那么这篇文章将为你提供一些适合电脑和手机操作的线上兼职副业…

[Linux]文件/文件描述符fd

一、关于文件 文件&#xff1d;内容&#xff0b;属性 那么所有对文件的操作&#xff0c;就是对内容/属性操作。内容是数据&#xff0c;属性也是数据&#xff0c;那么存储文件&#xff0c;就必须既存储内容数据&#xff0c;又存储属性数据。默认就是在磁盘中的文件。当进程访问…

知识分享:隔多久查询一次网贷大数据信用报告比较好?

随着互联网金融的快速发展&#xff0c;越来越多的人开始接触和使用网络贷款。而在这个过程中&#xff0c;网贷大数据信用报告成为了评估借款人信用状况的重要依据。那么&#xff0c;隔多久查询一次网贷大数据信用报告比较好呢?接下来随小易大数据平台小编去看看吧。 首先&…

YOLOV5 改进:替换backbone为EfficientNet

1、介绍 本章将会把yolov5的主干网络替换成EfficientNet V2,这里直接粘贴代码 详细的可以参考之前的内容:YOLOV5 改进:替换backbone(MobileNet为例)_yolov5主干网络更换为mobile-CSDN博客 更多的backbone更换参考本专栏: YOLOV5 实战项目(训练、部署、改进等等)_听风吹…

2024电工杯B题平衡膳食食谱的优化设计及评价原创论文分享

大家好&#xff0c;从昨天肝到现在&#xff0c;终于完成了2024电工杯数学建模B题的完整论文啦。 实在精力有限&#xff0c;具体的讲解大家可以去讲解视频&#xff1a; 给大家看一下目录吧&#xff1a; 目录 摘 要&#xff1a; 10 一、问题重述 14 二&#xff0e;问题分析 …

2024.05.26 第 399 场周赛

Leetcode 第 399 场周赛 优质数对的总数 I Leetcode 优质数对的总数 I 给你两个整数数组 nums1 和 nums2&#xff0c;长度分别为 n 和 m。同时给你一个正整数 k。 如果 nums1[i] 可以被 nums2[j] * k 整除&#xff0c;则称数对 (i, j) 为 优质数对&#xff08;0 < i < n…

航运复兴?大摩不信!

大摩认为&#xff0c;从供需关系来看红海危机只是推迟了航运业下行周期的到来&#xff0c;一旦干扰消除&#xff0c;行业可能重回周期性低迷。 红海危机加剧运力紧张&#xff0c;航运市场价格飞涨。 大摩在24日的一份报告中指出&#xff0c;受红海危机干扰航运市场运力&#…

加密与安全_AES RSA 密钥对生成及PEM格式的代码实现

文章目录 RSA&#xff08;非对称&#xff09;和AES&#xff08;对称&#xff09;加密算法一、RSA&#xff08;Rivest-Shamir-Adleman&#xff09;二、AES&#xff08;Advanced Encryption Standard&#xff09; RSA加密三种填充模式一、RSA填充模式二、常见的RSA填充模式组合三…

Python小游戏——俄罗斯方块

文章目录 项目介绍环境配置代码设计思路1.初始化和导入库&#xff1a;2.定义颜色和屏幕尺寸&#xff1a;3.定义游戏逻辑&#xff1a;4.游戏循环&#xff1a; 源代码效果图 项目介绍 俄罗斯方块游戏是一款经典的益智游戏&#xff0c;玩家通过旋转和移动各种形状的方块&#xff…

页面<html>上多了一个滚动条,定位发现是<body>里面多了一个id为trans-tooltip的div

现象分析&#xff1a; 页面根标签html多了一个滚动条&#xff0c;发现body里面多了一个id为trans-tooltip的div&#xff0c;虽然width为0&#xff0c;height为0&#xff0c;但是其子元素还是有高度&#xff0c;占据了空间&#xff0c;最终导致了滚动条&#xff1b; 根本原因&…

怎么在pyqt中显示matplotlib的绘图?

想要在pyqt中显示matplotlib的绘图&#xff0c;在绘图时&#xff0c;其实不必使用以下语句&#xff1a; matplotlib.use("Qt5Agg") # 声明使用QT5最关键的语句是&#xff1a; from matplotlib.backends.backend_qt5agg import FigureCanvasQTAggFigureCanvasQTAgg…

Selenium 自动化测试工具<2>(Selenium 常用API的使用方法)

文章目录 浏览器操作浏览器最大化设置浏览器的大小浏览器的前进和后退操作浏览器滚动条 键盘事件单个按键用法键盘组合键用法 鼠标事件不同窗口搜索定位一组元素定位多层框架下拉框定位alert、confirm、prompt 的处理上传文件操作自动截屏 继上一篇文章对 Selenium API 的使用&…

HTML蓝色爱心

目录 写在前面 HTML入门 完整代码 代码分析 运行结果 系列推荐 写在后面 写在前面 最近好冷吖&#xff0c;小编给大家准备了一个超级炫酷的爱心&#xff0c;一起来看看吧&#xff01; HTML入门 HTML全称为HyperText Markup Language&#xff0c;是一种标记语言&#…

Linux(六)

Linux&#xff08;六&#xff09; 自定义头文件自定义头文件中写什么如何引入头文件条件编译条件编译作用 gcc工作原理Make 工作管理器什么是Make什么是Makefile/makefileMakefile假目标Makefile中的变量自定义变量预定义变量自动变量 Makefile中变量展开方式递归展开方式简单展…

【Python】 如何使用.whl文件安装Python包?

基本原理 在Python的世界中&#xff0c;.whl文件是一种分发格式&#xff0c;它代表“Wheel”。Wheel是一种Python包格式&#xff0c;旨在提供一种快速、可靠且兼容的方式&#xff0c;用于安装Python库。与源代码包相比&#xff0c;Wheel文件是预编译的&#xff0c;这意味着它们…

【2024.5.26 软件设计师】记录第一次参加软考(附资料)

&#x1f57a;作者&#xff1a; 主页 我的专栏C语言从0到1探秘C数据结构从0到1探秘Linux &#x1f618;欢迎 ❤️关注 &#x1f44d;点赞 &#x1f64c;收藏 ✍️留言 文章目录 前言考试分析选择题案例分析题话外 软考总结资料 前言 这是我第一次参加软考&#xff0c;其实我并…