CUDA学习之路[13]:Attention算子详解
写在前边
市面上讲解Transformer架构的博客、视频、书籍实在是太多了,我不敢保证自己真的能够讲的比这些大牛都优秀,只能说尽量把我对于Transformer的全部理解都写在这一个系列博客中。
为什么Transformer火到了现在?
很多人现在一谈起大模型、多模态等等就自然而然想到了Transformer,但是它最开始真的不是干这个的,从最开始处理NLP,一步一步到后边的大模型,其实中间还有很长一段路要走。
很多教程一上来就开始学技术,位置编码、自注意力、一堆词怼上来直接把人砸晕了。 但是技术本身的发展脉络是阶段性的,我们需要先了解整个Transformer的发展脉络,搞清楚每一个模块都是用来干嘛的。
这一讲完全讲历史,大家也就听一乐,后续几节我再实现具体的代码。
假设,我们现在要把下边这句话翻译成为中文:
The cat that was adopted last year and has been lazy ever since finally yawned.
句子很长,我们只需要看主谓宾:
The cat yawned.
这句话的翻译难点是什么?主语cat和真正的主句谓语yawned之间插入了一段很长的定语从句,这也是我们很多考研、考四六级同学面对长难句时候的困难——难以找到主谓宾。
在Transformer之前,处理自然语言的主流是RNN循环神经网络和LSTM长短期记忆网络。
对于RNN来说,处理句子就像玩传水游戏。它从The开始,把一部分水传给cat,再传给that…一直传到yawned。等传到最后句尾时,句首的信息已经在多次传递中严重衰减(梯度消失)。导致它根本想不起来主语是谁。

为了解决这个问题,LSTM引入了门控机制和细胞状态。通俗来说就是给原本的信息加上了便签,关键的词就进行标记。但是这样子做也有几个问题。首先是,用来标记的“笔记本”是容量有限的,句子太长就溢出了;并且它必须从前到后逐字阅读,很难进行并行计算,训练速度极慢。
直到1027年,Google团队发表了那篇改变历史的论文:《Attention is All you Need》
详解Transformer的各个模块
很多人第一次读《Attention is All you Need》,会有一个错觉,这帮人好像一夜之间造出了一个全新的架构,并持续火了十年。但如果你去翻论文的参考文献,会发现 Transformer 的每一个组件都能在之前的某篇论文中找到原型。
Google 团队的真正贡献,不是发明了注意力机制,也不是发明了位置编码,而是把这些机制串在一起,很多人觉得论文的核心是Attention,但在我看来是ALL。
它把和Attention无关的机制都祛除掉,只保留Attention核心,这才能说明“Attention is all you need!”

自注意力机制
注意力的idea比大多数人想象的更早,而且起源于计算机视觉,而非自然语言处理。
2014年,Google DeepMind 的 Volodymyr Mnih 等人发表了《Recurrent Models of Visual Attention》。 他们在处理图像分类时,让 RNN 模型每次只”看”图像的一个局部区域,然后根据当前看到的内容决定下一次看向哪里。 这就像人眼看东西,你不会一视同仁地扫描整个视野,而是会聚焦在关键区域。
紧接着同一年,Bahdanau等人使用类似attention的机制在机器翻译任务上,将翻译和对齐同时进行。

Seq2Seq 模型用编码器把整个源语言句子压缩成一个固定长度的向量,再由解码器从这个向量中生成目标语言。问题是:这个是定长向量,是严重的信息瓶颈。 句子一长,向量装不下,翻译质量就崩了。
Bahadanau的解法很符合直觉:不要让编码器把所有信息塞进一个向量里。而是让解码器在生成每一个目标词时,动态地去查看源语言句子的所有隐藏状态,然后自己决定”我现在最应该关注源句子的哪几个位置”。
简单来说就是用一个小型的前馈网络计算相关度分数:
这个分数衡量的是当前正在解码的这个词,与源句中第 i 个位置的编码器隐藏状态有多相关。
所有的分数组成了注意力权重,对源句子的隐藏状态做加权平均,得到一个上下文向量。 解码器每一步使用的上下文向量都是动态生成的,不同的目标词关注不同的源词。
这就是加性注意力 的由来,上述 score 函数用了 v^T tanh(Wx + Uy) 这种”加”的形式。
2015年,Luong、Pham、Manning发表了《Effective Approaches to Attention-based Neural Machine Translation》。这篇论文系统的对比了三种注意力得分的计算方式:
| 方式 | 公式 | 说明 |
|---|---|---|
| Dot-product | 直接点积 | |
| General | 加一个可学习矩阵 W,给两个向量对齐的空间 | |
| Concat (Additive) | Bahdanau 的方案,拼接后通过 MLP |
并且这篇文章提出来Global Attention和Local Attention两种策略。
Q·K^T 的直观表示。Google 团队后来加了一个缩放因子 1/√d_k(当然这也是Google的核心贡献)上述方案都是从“语言A”到“语言B”之间的注意力。
例如I Love you和我爱你,love和爱之间的注意力关系。 每一步解码器用自己的隐藏状态(Query)去和编码器的隐藏(Key/Value)计算注意力分数。 关键在于,Query和Key/Value是来自于两个不同语言句子之间的注意力计算,或者说是编码器-解码器之间的状态。
但是句子内部词和词之间的关系怎么办呢?
2016年,Cheng、Dong 和 Lapata 在《Long Short-Term Memory-Networks for Machine Reading》中提出了 Intra-Attention。 它的思想是:让一个句子内部的每个词关注句子中的所有其他词(包括自己),从而生成上下文感知的表示。
这其实就是自注意力的雏形——句子内部自己对自己做注意力。
同年,Parikh等人提出了Decomposable Attention,即先用注意力对齐两句话中的词,再聚合比较。这篇论文还拿到了当时EMNLP2016的最佳论文奖,证明了单纯的注意力机制+简单的前馈网络就能够在NLP上达到当时的SOTA。
到此为止,Google的切入点呼之欲出。
既然注意力能够跨句子,也能够在句子内部,并且纯注意力+前馈就能够打败复杂的LSTM模型,那为什么不彻底扔掉RNN,只保留注意力呢?
简单来说,Transformer的注意力就是下边这一行公式:
Attention(Q, K, V) = softmax(Q·K^T / √d_k) · V之前我们实际上也讲过,这里提问一下大家:
在若干注意力方案中,点积在大矩阵场景下的计算效率高于加性注意力;加性注意力需要每一对(Q, K)跑一遍MLP,点积注意力只需要一次矩阵乘。
√d_k?这是原始 Transformer 论文的第一个关键洞察。当 d_k 较大时(比如 64),点积值的方差会达到 d_k(假设 Q 和 K 的每个分量独立,均值为 0,方差为 1)。这意味着 softmax 的输入值范围很大,梯度会落在 softmax 的饱和区(梯度接近 0)。除以 √d_k 将方差控制回 1,使得梯度能正常流动。这是 Transformer 能训练成功的前提之一。
(1)映射到0-1且和为1,天然形成概率分布;(2)可微支持反向传播;(3)指数放大分数之间的差异;
多头注意力机制
如果只用一个注意力头,那么对于每个位置,模型只能用一种模式去看待和其他位置的关系。但是语言是复杂的,一个词可能与不同的词存在不同的类型关系:语法关系、语义关联、指代等等。
多头注意力类比CNN的多个卷积核,一个卷积层并不会只有一个filter,而是用几百个filter去分别提取边缘、纹理、颜色等不同的特征。
Transformer也是同理:
MultiHead(Q, K, V) = Concat(head_1, ..., head_h) · W^Ohead_i = Attention(Q·W_i^Q, K·W_i^K, V·W_i^V)每个head都拥有自己独立的Q、K、V投影矩阵,因此可以学习到不同类型的注意力方式。 论文中使用8个头,每个头的维度为64,展现出了稳定的效果。
后续研究表明,不同头确实学到了不同的行为模式:
- 有的头专注相邻位置(局部语法依赖)
- 有的头专注特定分隔符(如句号、逗号等等标点结构)
- 有的头专注远距离语义关联(如代词回指其先行词)
这个设计的精妙之处就在于,它参考多卷积核实现了多映射,但是却并没有引入更多的计算原语,就是8个独立的attention+1次拼接投影,硬件实现高效。
位置编码
如果只有Attention机制,完全算不上ALL,那另一个很重要的模块是什么呢?
——位置编码。
如果说Attention是Transformer的发动机,那位置编码就是它的导航。但是在原论文,位置编码占比不是很高,以至于很多人觉得它只是一个附加的小trick。
直到后来,位置编码的设计直接决定了模型的处理能力,以及能否泛化、甚至扩散到了视觉和多模态等等复杂的任务。
原本RNN天然具有时序感,因为它是逐个处理的,它并不需要位置信息,因为处理顺序本身就是位置信息。每个词的隐藏状态不仅包含了该词的语义,还隐式编码了它前边词的所有信息。
但是自注意力呢?它是完全对称的,它把输入顺序打乱了,换句话说自注意力是一个集合到集合的映射,而非序列到序列的映射,它天然就不感知位置顺序。
假设我们现在有一个极简的Transformer(d_model=4, 单头),输入三个中文词的词嵌入:
输入序列 S: ["我", "爱", "北京"]词嵌入: x₀=[1,0,1,0], x₁=[0,1,0,1], x₂=[1,1,0,0]为了简化,假设Q,K,V投影矩阵都是单位矩阵,跳过缩放因子,那么自注意力计算的过程是:
Step 1: 计算注意力分数矩阵(Q·K^T)
我(Q₀) 爱(Q₁) 北京(Q₂)我(K₀) 2 0 1爱(K₁) 0 2 1北京(K₂) 1 1 2Step 2: Softmax 后得到注意力权重
我 爱 北京我 0.58 0.08 0.34 ← 这一行决定"我"的输出:主要听自己(58%)和"北京"(34%)爱 0.08 0.58 0.34 ← 这一行决定"爱"的输出:主要听自己(58%)和"北京"(34%)北京 0.23 0.23 0.54 ← 这一行决定"北京"的输出:主要听自己(54%)Step 3: 用权重对 V 做加权平均,得到输出
output[0] = 0.58×[1,0,1,0] + 0.08×[0,1,0,1] + 0.34×[1,1,0,0] = [0.92, 0.42, 0.58, 0.08] ← "我"的输出output[1] = 0.08×[1,0,1,0] + 0.58×[0,1,0,1] + 0.34×[1,1,0,0] = [0.42, 0.92, 0.08, 0.58] ← "爱"的输出output[2] = 0.23×[1,0,1,0] + 0.23×[0,1,0,1] + 0.54×[1,1,0,0] = [0.77, 0.77, 0.23, 0.23] ← "北京"的输出同一个 token 的 Q 去和所有 token 的 K 做点积,权重由语义相似度决定,与 token 在序列中的位置无关。“我”关注”北京”是因为它们的词向量有分量重合(
x₀·x₂=1),而不是因为”北京”在第三个位置。
现在,打乱顺序!
输入序列 S': ["北京", "我", "爱"]词嵌入: x₂=[1,1,0,0], x₀=[1,0,1,0], x₁=[0,1,0,1]重复上述计算,你会发现打乱后“我”的注意力权重仍然由x₀ 与所有 token 的语义相似度决定,这些 token 的语义没有变(x₀, x₁, x₂ 都没变),所以“我”的注意力输出和打乱前是一模一样的,唯一的区别就是这个输出现在在序列的第二个位置。
无论 token 出现在序列的哪个位置,只要它的词嵌入不变,它的注意力输出就不变。模型天然不感知位置顺序。 这不仅仅是 “The cat sat on the mat” 和 “mat the on sat cat The” 的区别。 对于 Self-Attention 来说,这两个序列中每个词的输出值是完全相同的(只是排列顺序不同)。模型无法区分”狗咬人”和”人咬狗”,这对 NLP 来说是灾难性的。
如果对输入序列施加一个排列 π,输出序列也会按相同的 π 重排,但每个位置的输出值不变。形式化:SelfAttention(π(X)) = π(SelfAttention(X))。位置编码的作用就是让输入不再是纯 X,而是 X + PE,从而打破对称性。
在Transformer之前,显式编码也已经出现了。Seq2Seq和GlobalAttention都没有显式的位置编码,而是RNN,位置信息藏在隐藏状态里。
而Memory Networks(2015)是最早的显式位置编码之一。 Facebook AI Research 的 Sukhbaatar 等人在《End-To-End Memory Networks》中,为了让模型理解句子中词的顺序,给每个词加了一个固定的位置编码向量(不需要训练)。这是后来 Transformer 正弦编码的思想先驱。
除了固定编码之外,Gehring 等人用 CNN 替代了 RNN 来做序列建模(这也是 Transformer 扔掉 RNN 思路的重要铺垫)。CNN 和 Self-Attention 一样是不感知位置的。卷积核只看局部窗口,不知道自己在序列中的绝对位置。所以 ConvS2S 给每个位置引入了一个可学习的嵌入向量,加在词嵌入上。
这就是显式位置编码:固定编码 -》 正弦编码 -》可学习编码。
论文中选择了正弦编码:
PE(pos, 2i) = sin(pos / 10000^(2i/d_model))PE(pos, 2i+1) = cos(pos / 10000^(2i/d_model))这个公式一开始会觉得莫名其妙,我们来拆解一下:
pos是词在句子中的位置(0, 1, 2, …)i是维度索引(0 到 d_model/2 - 1)10000^(2i/d_model)是波长(或等价地,决定了频率):i 越大 → 分母的指数越大 → 波长越长 → 频率越低 → 变化越慢
换句话说,小i也就是高频sin/cos,对短距离位置变化敏感;大i对长距离位置变化敏感。
类似于用不同焦距的镜头,拍摄同一组序列。长焦镜头查看相邻词的位置关系;广角镜头查看全局位置结构。模型可以在训练中学会组合这些不同尺度的信号。
论文给出了数学理由:因为 sin(a+b) 和 cos(a+b) 可以用 sin(a), cos(a), sin(b), cos(b) 的线性组合表示。这意味着理论上 PE(pos+k) 可以被表示为 PE(pos) 的线性函数。模型因此可能学会推断相对位置关系。
尽管Transformer论文选择了正弦编码,但是工业界很快转向了可学习位置编码:
| 模型 | 年份 | 位置编码方式 | 最大长度 |
|---|---|---|---|
| BERT | 2018 | 可学习绝对位置嵌入 | 512 |
| GPT-1 | 2018 | 可学习绝对位置嵌入 | 512 |
| GPT-2 | 2019 | 可学习绝对位置嵌入 | 1024 |
但可学习位置编码有一个致命的局限:它是一个固定大小的查找表。如果你训练时的最大长度是 512,推理时来了一个 1024 长度的句子,位置 513~1024 根本没有被训练过。它们的嵌入向量是随机初始化的(或者根本不支持查表),模型在这些位置上的行为不可预测。这就是长度外推问题。
到此为止,我们就已经了解了位置编码的主要功能,但是有关位置编码还远远没有结束。实际上,模型并不需要真正知道某一个确定的位置是什么,而是词A和词B距离有多远。 换句话说,绝对位置,或许并没有那么重要,相对距离才是核心。
2018年,Shaw等人在《Self-Attention with Relative Position Representations》中,作者修改了 Transformer 的注意力计算,不再给每个 token 加一个绝对位置向量,而是在计算 attention score 时,根据两个 token 之间的相对距离 j - i 来查找一个可学习的关系向量,加到 key 上:
其中 是一个只依赖于相对距离的可学习向量。这有一个截断上限 K(比如 K=16),超过 K 的距离共享同一个向量,因为假设太远的词之间的直接关系不重要。
19年,Dai 等人在《Transformer-XL: Attentive Language Models Beyond a Fixed-Length Context》中,提出了一套更彻底的相对位置编码方案,具体内容大家可以查看论文。简单来说就是把注意力机制分为了四项:
- 内容 - 内容
- 内容 - 位置
- 全局内容偏置
- 全局位置偏置
这套设计让 Transformer-XL 能够在段落级别实现递归。上一段的隐藏状态可以被当前段使用,从而实现对超长文本的建模。
20年,Raffel等人在T5中提出了一个更加高效的相对距离方案。 21年,Ofir等人更是提出了ALiBi,简单来说是给每一个头一个独特的斜率:
ALiBi 的惊人之处在于外推能力,用 512 长度训练的模型,可以直接推理 2000+ 长度的序列,困惑度不升反降。
ALiBi 曾经被 BLOOM(BigScience 多语言大模型)和 MPT(MosaicML)系列采用,证明了极度简单的方案也可以非常有效。但它的局限也显而易见:线性衰减过于刚硬,不能表达更复杂的距离-重要性关系(比如”前文第 5 句的主语”可能比”前文第 3 句的从句”更重要,这不是简单的距离函数能描述的)。
截止到现在,针对位置编码已经有了系统性的演进,但究竟是谁来终结比赛的呢?
——RoPE(Rotary Position Embedding)

2021 年,苏剑林(Jianlin Su)在中文博客”科学空间”上连载了一系列文章,探讨如何用旋转矩阵来编码位置。这个想法后来以论文《RoFormer: Enhanced Transformer with Rotary Position Embedding》正式发表,成为了 LLaMA、Mistral、Qwen、Gemma、DeepSeek 等几乎所有现代大模型的标配。
核心思想是RoPE并不把位置编码直接加到Token向量上,而是把位置编码乘进去,也就是做旋转。
仔细看,点积如下:
我们惊奇的发现,Q和K的点积只以来于它们的相对位置n-m,而不是各自的绝对位置。这就是RoPE的核心性质:通过在绝对位置上做旋转,自动或者相对位置编码的效果。
这就是为什么RoPE中使用torch.polar。
| 对比方案 | RoPE 的优势 |
|---|---|
| vs 正弦编码 | RoPE 天然编码相对位置,而非只是绝对位置 |
| vs 可学习绝对编码 | RoPE 零参数,理论上有无限长外推潜力 |
| vs Shaw 相对编码 | RoPE 不需要 O(K) 的可学习参数;外推无截断 |
| vs T5 bias | RoPE 的位置信号随内容变化(乘法交互),而非固定的加法偏置 |
| vs ALiBi | RoPE 提供振荡衰减而非刚性线性衰减,更灵活 |
你以为到这里就结束了吗?站在2026年的当下,位置编码的历史还远远没有结束。
尽管RoPE比之前的方案有了好得多的外推能力,但是当你真的把推理长度拉到训练长度的100倍时,RoPE仍然会崩。
2023年,随着LLaMA的爆火,研究者们又是一顿猛干:
Position Interpolation (PI, 2023.06)。 把长序列的位置索引按比例压回训练范围内。比如一个模型训练时用了 2048 个位置,现在要处理 4096 个位置,就把实际位置
pos替换为pos × 2048/4096 = pos/2。效果出奇地好,证明了 RoPE 的编码空间是连续的,只需要在更密的采样点上插值即可。
NTK-aware Scaling (2023.08)。 但 PI 有一个问题:把所有频率都按相同比例压缩,导致高频维度的分辨率下降。NTK-aware 方案的改进很巧妙。不压缩位置索引,而是调低 RoPE 的频率基数(从 10000 调到一个更大的值,如 1000000)。这相当于把低频维度”推开”以覆盖更长的范围,同时保持高频维度不变(对邻近位置的敏感度不变)。“NTK”的名字来自 Neural Tangent Kernel 理论中的频带扩展思想。
YaRN (2023.10)。 Peng 等人结合了 PI 和 NTK 的优点,并引入了一个温度参数来精细控制注意力分布的”锐度”。YaRN成为了当时最流行的长上下文微调方案。
后续有关位置编码仍然由新的发现,但是由于笔者能力受限,给大家姑且抛砖引玉。当我们走出NLP,进入视觉、进入多模态,发现故事才刚刚开始。
从论文发表的速度和数量可以看出来,基本2-3个月一篇,需要极其强的科研实力。
直到现在,位置编码仍然是一个活跃的话题,位置编码看似只是 Transformer 的一个小组件,但它决定了模型能看多远、看多宽、以及能否将一种模态中的空间经验迁移到另一种模态。在接下来的章节中,我们将用 CUDA 和 Triton 来实现其中最重要的几种位置编码 kernel,特别是 RoPE 及其长上下文扩展。
残差连接
借鉴ResNet,保证模型的深度,不做过多赘述。
层归一化LN
参考之前的文章Norm系列。
前馈神经网络
FFN(x) = max(0, x·W_1 + b_1)·W_2 + b_2 (原论文用 ReLU)FFN(x) = GELU(x·W_1 + b_1)·W_2 + b_2 (现代 LLM 的常见选择)看起来似乎只是两个线性变换中间夹一个非线性激活,但是为什么必须要有前馈呢?
实际上,注意力层做的是信息路由,让Token A看到Token B的信息,并且聚合上下文。 FFN做的则是信息加工,赋予模型思考的能力。
更具体地说:FFN 的隐藏维度通常是 d_ff = 4 × d_model(比如 d_model=512 时,d_ff=2048)。这意味着每个 token 的表示先被扩展到 4 倍维度(给模型足够大的空间做非线性映射),再压缩回原始维度。可以类比为把一本书展开成一张巨大的表格,勾画关键信息,再折叠回去。
这也是为什么 LLM 的知识主要存储在 FFN 层中,编辑 FFN 的权重可以直接修改模型的知识。
一个标准的 Transformer block:
| 组件 | 参数量 (d_model=d, d_ff=4d) |
|---|---|
| QKV 投影 | 4 × d × d = 4d² |
| Output 投影 | d × d = d² |
| FFN W₁ + W₂ | 2 × (d × 4d) = 8d² |
| 总计 | ~13d²,其中 FFN 占 ~62% |
FFN 占了大部分参数,但 FLOPs 却与 Attention 相近(因为 Attention 是 O(n²·d),FFN 是 O(n·d²))。这是一个”参数大、计算小”的组件,非常适合存放大量知识。
为什么是这些组件?
如果把Transformer想象成为一个信息处理组件,需要在模型经历三层加工:
- 我应该和谁交流?
- 我获得了什么新的知识?
- 哪些知识需要保留,哪些需要更新?
这三个交替堆叠,形成了Transformer处理信息的节奏:全局交流、独立思考、选择性更新等等。
Transformer后的10年
Transformer是一篇17年的工作,但是大模型真正涌现基本是在23年左右。
这里我总结了一个简单的时间线,没那么详细,但是基本重要的模型都在其中:
架构层面
| 年份 | 改进 | 论文 | 核心变化 |
|---|---|---|---|
| 2018 | BERT | Devlin et al. | 双向 Transformer + 掩码语言模型预训练;开创 Pre-training + Fine-tuning 范式 |
| 2018 | GPT | Radford et al. | 单向(因果)Transformer + 生成式预训练;去除 Encoder 只留 Decoder |
| 2020 | GPT-3 | Brown et al. | 175B 参数, 上下文学习(In-Context Learning), 证明了 Scaling Law |
| 2023 | LLaMA | Touvron et al. | 开源高效 LLM 标杆;RMSNorm + SwiGLU + RoPE 成为新标配 |
| 2024 | Mamba-2 / SSD | Dao & Gu | 状态空间模型与线性注意力的统一;O(L) 替代方案的里程碑 |
注意力层面
| 年份 | 改进 | 核心变化 |
|---|---|---|
| 2019 | Sparse Attention (Sparse Transformer) | 只计算部分 token 对的注意力,O(L√L) or O(L log L) |
| 2021 | RoPE (Rotary Position Embedding) | 用旋转矩阵编码相对位置,成为 LLaMA 系标配 |
| 2022 | FlashAttention | IO-aware 精确注意力:tiling + online softmax,1.5-3× 加速 |
| 2023 | FlashAttention-2 | 改进的并行策略 + 更少的非 matmul FLOPs,2× 加速 |
| 2023 | GQA (Grouped Query Attention) | 多 Q head 共享少量 KV head,大幅减少 KV cache |
| 2023 | Flash-Decoding | 针对 long-context 推理的 decoding 优化 |
| 2024 | FlashAttention-3 | H100 Hopper 专用:WGMMA + TMA + FP8 |
激活函数
| 年份 | 改进 | 核心变化 |
|---|---|---|
| 2016 | GELU | Gaussian Error Linear Unit, BERT/GPT 标配 |
| 2020 | SwiGLU | Swish-gated Linear Unit: SiLU(x·W_gate) ⊙ (x·W_up) |
归一化层面
| 年份 | 改进 | 核心变化 |
|---|---|---|
| 2019 | RMSNorm | 去除去均值操作,LLaMA 标配 |
| 2022 | DeepNorm | 改进残差初始化,支持 1000 层 Transformer |
总结
写到这里,可以看到激活没有关于技术的细节,有的更多的是脉络,可能大家会觉得信息量很大,但是你并不需要记住每一个细节,真正需要理解的就是两个模块:
- 为什么需要注意力?
- 为什么要这么设计?
如果有任何问题,欢迎进群讨论或者B站评论区讨论。
支持与分享
如果这篇文章对你有帮助,欢迎分享给更多人或赞助支持!