我要提问
ARTICLE DETAIL

资讯详情

前沿编程新知与开发实战干货的深度解读。

第 8 篇:注意力到底是什么(零门槛入门系列)

第 8 篇:注意力到底是什么(零门槛入门系列) 一句话导读注意力attention不神秘它就是每个词按相关度对其它词做一次加权平均先算像不像再把分数归一化成一组比例最后按比例把历史信息混起来。关键词注意力、查询/键/值、分数、归一化softmax、加权平均承接上一章第 7 篇讲的是权重怎么存得省这一篇看模型怎么用这些权重去理解一句话核心机制就是注意力。很多入门材料把它讲成一堆矩阵乘法读者看完还是不知道它在干嘛我们先不谈实现先把它的想法讲清楚。① 一句话概念注意力就是按相关度做一次加权平均。② 起点会议室里的速记员想象你在一个长会上做记录。会议室里每个人都在说话你要一句一句写下来。关键的一点是你写下新的一句时并不是把前面所有话平等地过一遍。刚才财务同事报的那组数字对你现在要写的这句最重要开头那段闲聊停车位的话几乎可以忽略。你脑子里的动作其实是三步先判断前面哪些话跟现在这句有关、给每句话一个重视程度然后这个重视程度不是拍脑袋定的它有高有低加在一起正好是满满的一份最后你落笔写的那句是被前面最相关的那几句带偏的结果。还有两个细节值得记住。第一相关是相对当前这句而言的——同一段历史你现在写的是总结和你要写的是反驳重点完全不同。第二越相关的话影响越大但不是独占——最相关的那句可能占了七成影响可剩下那几成还是来自其它句子。这种按重视程度把信息搅在一起的动作就是下面要讲的机制。③ 伪代码像不像 → 归一化 → 加权平均函数 注意力(查询q, 键列表K, 值列表V, 维度) - 输出: 分数列表 [] 对每个 键k 于 键列表K: 分数列表.追加(点积(q, k)) # 当前词与历史词逐个比像不像 比例 归一化(分数列表 / 开方(维度)) # 变成加起来等于 1 的一组比例 输出 0 对每个 位置i 于 比例: 输出 输出 比例[i] * 值列表V[i] # 按比例把历史信息混起来 - 输出 # 代价: O(历史长度 × 维度)逐行要点第 2–4 行的分数就是像不像的量化。当前词会产出一个查询query我现在想找什么每个历史词会产出一个键key我能提供什么。两者做一次点积得到一个数——越像数越大。这一步是打分。第 5 行先缩放再归一化。光有分数还不能用因为分数有大有小、甚至可能是负数。先除以维度的开方把数值压一压再归一化softmax成一组加起来正好等于 1 的比例。归一化不是耍花招它保证后面是平均而不是累加。第 6–8 行是加权平均。每个历史位置还有一个值value把它们的值按上面的比例加起来。注意输出的长度是固定的和历史有多长无关——再多历史混出来的也只是一份结果。最后一行# 代价。这一步要跟每一个历史词比一次所以代价随历史长度线性增长。这个数字看着还好但第 10 篇会告诉你它很快会变成平方级的麻烦。分数怎么一步一步变成输出如图 1 所示。图 1注意力三步像不像 → 归一化 → 加权平均最左边是当前词的 Q下面竖排着历史词 1~4 的 K几条细箭头从它们汇进右边的① 逐个点积 / 分数像不像再向右依次是② 归一化 / 变成加起来等于 1 的比例和③ 按比例混合 / 把历史词的 V 加权求和最后是输出长度与历史无关图下半部分画出归一化之后的四条比例条0.45、0.30、0.18、0.07长短不同而加起来正好是 1红框提醒分数一大指数就会溢出。归一化这一步到底把分数变成了什么如图 2 所示。图 2softmax 在做什么把任意分数变成加起来等于 1 的比例左边一列是原始分数2.0、0.5、−1.0、1.0既有负数也有大数中间一个方框写着先取指数 / 再除以 / 总和右边是四条比例长条0.61、0.14、0.03、0.22条越长表示原来的分数越高右下角标着总和 1.00底部一句取指数保证是正数除以总和保证加起来是 1。④ 纸笔实验必做设定5 分钟3 个历史词它们跟当前词的相似度分数是[2, 1, 0]。手算一次归一化这里假设 开方(维度) 1也就是分数不需要再缩放。先备一张指数的小表e⁰ 1.00、e¹ 2.72、e² 7.39。取指数7.39、2.72、1.00求和7.39 2.72 1.00 11.11逐个除以和比例 0.665、0.245、0.090加起来 ≈ 1.000按比例混合假设三个历史词的值分别是 10、20、30输出 0.665×10 0.245×20 0.090×30 6.65 4.90 2.70 14.25预期结果输出 ≈ 14.25。同时你会看到一件有意思的事——最大的分数2只比第二名1大了 1却占了 66.5% 的份量。取指数会把差距放大这是 softmax 的性格它让明显更相关的那一项很快占主导。可选 REPL 版Python 伪代码import math 分数 [2, 1, 0] 指数 [math.exp(s) for s in 分数] 比例 [e / sum(指数) for e in 指数] 值 [10, 20, 30] 输出 sum(w * v for w, v in zip(比例, 值)) print([round(w, 3) for w in 比例], round(输出, 2)) # 预期 [0.665, 0.245, 0.09] 14.25⑤ 进阶锚点进阶锚点本篇概念在《30 天手搓推理引擎》Day 8里被真正实现——8-1 自注意力数学Q·K^T / softmax / V/8-2 在线 softmax为什么不能先算完 e^x 再除/8-3 KV 缓存结构按 token 还是按头存。入门版到这里就够了进阶版会多出真实源码里 Q、K、V 是怎么算出来又怎么相乘的、在线 softmax为什么要在数值上绕一圈附踩坑实验以及 KV 缓存到底按词token存还是按头存。想动手 → 仓库 https://gitee.com/pei-xiaoguang/kestrel-llm从 Day 1 开始。下篇预告第 9 篇我们回答一个很实际的问题——模型每吐出一个字为什么不必把前面所有字重新想一遍答案是把中间结果存下来也就是 KV 缓存KV cache。
返回列表