Transformer Transformer 特点 精细的抽象,记忆空间特别大 大数据,大算力,大模型 scaling特性,可以训练很大的模型,用很多数据获得更多智能 “硬件彩票”,高强度对着GPU设计,能打满GPU利用率 在nlp问题里面,通过逐个处理新的token, 递归 得进行抽象 使用少量的权重对自然语言逐个token进行计算 先用kq权重映射到当前token对应的空间,再用v权重映射到输出 通过多层叠加,使得kqv权重能表达整个sequence的范围 通过少量权重、大量的计算扩展了表达空间 weight的存储效率非常高,充分的训练,提高了对样本抽象的质量 语义压缩 LLM难以处理细粒度的语义差异。它们的内部概念结构与人类对类别归属的直觉不相符 LLM侧重于统计压缩,力求最大程度地减少冗余信息;而人类则更注重适应性和丰富性,强调保持灵活性和上下文的完整性 hidden size 7168的向量维度是不是太少? 要表达所有的语义 线性表达的表达效率低? 7168 hidden 向量既要表达当前需要生成的token,还要表达高级的语义总结抽象,短期和长期的目标矛盾 用于长期的hidden向量专门计算,并缩减维度尺寸(1024?),可以减少KVcache和计算量 训练模型,自动在sequence中间生成特殊的token,用于表示抽象的高层目标 使用自然作为输入输出的接口 能使用自然语言影响内部变量进行特定取舍的调整 能使用自然语言进行自动推理和迭代,输出更好的答案 真正突破是打通了语言 可以理解和映射语言到其他的领域。 足够大的理解空间,上下文,可以处理一个足够复杂的问题 缺点 记忆和人类不一致,而且没有统一的表达,不通用 没有自主意识,还是在算概率,逃不开数学上的特征分割,虽然不能证明当前的数学基础、梯度下降是错误的,但是AGI肯定不是只有这些,AGI更多的是一种复杂的工程,而不是简单几个公式 Transformer架构个在处理长上下文时,会受到二次复杂度(浪费算力),以及长度外推能力弱的限制。 Attention 每层每个token计算的输入是前面所有token的key和value 从信息流的角度来看,不是一个 树形 的拓扑 实际上一段自然语言通过字、词、短语、句子等层级结构组合出表达的语义 类比于卷积的空间约束,自然语言语义应该需要 树形层级结构 的约束 所以可以采用KV cache进行缓存并加速 多层之间不能共享权重 动态性欠缺 随序列长度增加而变慢的attention机制 从信息量来说,句子长了,包含的信息肯定是变多了,空间复杂度在O(T)和O(1)之间 Transformer 的时间复杂度为:O (T^2)、空间复杂度为:O (T^2) RWKV的固定大小的status存储器也是不合理的(不考虑外部记忆),时间复杂度为:O(T)、空间复杂度为 O(1) 相对于人脑,每个新的token都要重新计算一遍底层的语义,而不是直接根据前面语句的总结进行调整 缺乏更高维度的动态性 MOE、CoT、稀疏 都能提高动态性能力 速度显存恒定的FFN全连接网络 类比于人类记忆,“检索”和“更新”是两个步骤 梯度下降不能区分这两个步骤,只能通过优化器策略来调整? 两步骤的训练策略能更好的约束模型的梯度下降到更合理的最低值? 和binary神经元的问题一致:检索输入的选择(检索)和权重的更新 和attention的注意力+MLP机制类似,更新策略有提升空间 类似于 Yan2.0 Preview基于的是RockAI首创的非Transformer架构 前向 过程中,既能通过门控式更新保留长期依赖,又能基于输入分布特性灵活整合新知识 不同于「上下文工程」等方案对记忆信息的显性存储,RockAI将有效信息隐式地记忆到多层神经网络的权重,通过神经网络的多层级抽象、非线性建模等能力,实现更优的记忆性能 将记忆能力「注入」模型本身。它不再是一个外挂模块,而是模型的一部分 我:其实本质上和上下文(kv cache)没有区别,kvcache也是前面的存储信息决定了后面的选择,而且都是使用线性映射来做信息变换和选择 接口API transformer利用了语言的特性,在attention中间,使用 自然语言 作为通用的输入输出接口和表达 自然语言离散的Token效率比较低,作为CoT太慢,潜式思维链(Latent Chain-of-Thought)是一个改进方向 attention中间的输入输出都是用于表达语言的一个序列的特征。 这个序列非常重要,因为序列都是由编码的token组成,可以非常灵活得表示一个非常复杂的语义 那么这个跟语言其实是有异曲同工的效果,语言也使用 很多个文字来表达一个语意,每个文字的空间不是很大 搜索空间 就比如说汉字也就几千个字 transformer相当于是在模仿语言, 把整个模型的搜索空间限制为自然语言这个尺度(约束),极大缩小了模型的搜索空间 研究者认为 LLM之所以在简单理解任务中无法提供准确且稳定答案,是因为这些模型缺乏对语言的真正理解:它们生成的词语如同语义「黑箱」,只是近似于语言的表面统计和解析过程中较「自动化」的部分。 LLM并不适合作为语言理论,因为它们的表征能力几乎是无限的,这使得它们的表征既是任意的,又缺乏解释性基础,属于通用函数逼近器这一类别,而后者已被证明能够逼近任何数学函数 。 较自动化部分,表示抽象不够高级,只是对低级别的概念进行抽象,不能进行高级别的抽象。虽然级别低,但是AI的信息容量足够大,和人类比,更像是记忆力很好的书呆子 它们看似合理的表现,隐藏了语言建模方法本身固有的缺陷:智能实际上无法作为统计推断的副产品而自然产生,理解意义的能力也不能由此产生。 KV Cache https://zhuanlan.zhihu.com/p/662498827 ChatGLM3典型计算图 ## data flow query -> "你好" | tokenizer -> input_ids [6] | rotary_pos_emb embedding -> [1, 6, 4096] \ / GLMBlock x 28 -> [6, 1, 4096] <---| RMSNorm -> [6, 1, 4096] | final_layernorm [-1:] -> [1, 1, 4096] | Linear -> [1, 1, 65024] | output_layer 4096->65024 softmax -> [1, 65024] | multinomial -> [1] | cat([input_ids, next_tokens]) ---| ↓ tokenizer.decode( ) GLMBlock input / / RMSNorm hidden_states -> [6, 1, 4096] | | / | | | pow(2) -> [6, 1, 4096] | | | | | | | mean -> [6, 1, 1] | | | ↓ | | | rsqrt( + eps) -> [6, 1, 1] | | \ / | | mul -> [6, 1, 4096] | | \ weight -> [4096] | | \ / | RMSNorm mul -> [6, 1, 4096] | | SelfAttention x -> [6, 1, 4096] | | | | | Linear -> [6, 1, 4608] 4096->4608 | | / | | | q k v [6, 1, 32, 128] [6, 1, 2, 128] [6, 1, 2, 128] | | / | | | pos_emb pos_emb \ -> cat( x0 y0-x1 y1, x1 y0-x0 y1, x, y) | | | | | | | | expand expand -> [6, 1, 32, 128] [6, 1, 32, 128] | | permute permute permute -> [1, 32, 6, 128] [1, 32, 6, 128] [1, 32, 6, 128] | | \ / | | | |---- matmul | -> [1, 32, 6, 128] [1, 32, 128, 6] -> [1, 32, 6, 6] | | | add(mask) / -> [1, 32, 6, 6] | | attention| softmax / -> [1, 32, 6, 6] dim:-1 | | | \ / | | |---- matmul -> [1, 32, 6, 6] [1, 32, 6, 128] -> [1, 32, 6, 128] -> [6, 1, 4096] | SelfAttention Linear -> [6, 1, 4096] 4096->4096 | / | dropout \ / Add / | RMSNorm hidden_states -> [6, 1, 4096] | | / | | | pow(2) -> [6, 1, 4096] | | | | | | | mean -> [6, 1, 1] | | | ↓ | | | rsqrt( + eps) -> [6, 1, 1] | | \ / | | mul -> [6, 1, 4096] | | \ weight -> [4096] | | \ / | RMSNorm mul -> [6, 1, 4096] | / | mlp / | | Linear -> [6, 1, 27392] 4096->27392 | | / | | chunk1 chunk0 -> [6, 1, 13696] | | | | | | | | sigmoid | | | | / | | | mul | | \ / | | mul -> [6, 1, 13696] | mlp Linear -> [6, 1, 4096] 13696->4096 | / | dropout | / Add Tokenization 注:作为术语的“tokenization”在中文中尚无共识的概念对应,本文档采用英文表达以利说明。 Qwen-7B采用UTF-8字节级别的BPE tokenization方式,并依赖 tiktoken 这一高效的软件包执行分词。 Qwen-7B中有两类token,即源于BPE、 bytes 类型的普通token和特殊指定、 str 类型的特殊token。 from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained('Qwen/Qwen-7B', trust_remote_code=True) 普通token 普通token源于BPE,是在UTF-8编码的文本字节序列上学习得到的。 尽管基于字节序列的方式保证了所有文本均可被tokenize且没有未登录token问题,但处理罕见文本时有可能回退到字节级别的编码。 由于从字节序列解码为文本时, errors 参数设为 replace ,处理不完整的token序列可能会遇到UTF-8解码错误,表象是生成中包含“替换字符”(�)。 这一行为可以通过将 errors 参数设为 ignore 来规避。 一次性修改可以传入tokenizer的 decode 函数,持久性修改可以传入tokenizer的初始化函数,请注意 decode 的配置优先级更高。 errors 的可选值,请参阅 Python文档 . >>> tokenizer.decode([51461]) ' �' >>> tokenizer.convert_ids_to_tokens([51461]) [b' \xe6\xa0'] >>> b' \xe6\xa0'.decode("utf-8", errors='replace') ' �' >>> tokenizer.decode([51461, 117]) ' 根' >>> tokenizer.convert_ids_to_tokens([51461, 117]) [b' \xe6\xa0', b'\xb9'] >>> b' \xe6\xa0\xb9'.decode("utf-8", errors='replace') ' 根' bytes 类型的普通token到id的映射可以通过 tokenizer.get_vocab() 获取。 尚不支持也不推荐向tokenizer增加普通token。 特殊token 特殊token用以给模型传递特殊信号,如到达文本末尾。 理论上,输入文本中不包含特殊token,它们仅在tokenization后由开发者手动加入。 特殊token的字面表达,如表示文本结束的 <|endoftext|> ,仅便于指代特殊token,不意味着它们在输入文本空间中。 目前,训练中使用的、已经有固定含义的、不应做它用的特殊token,Qwen-7B中有 <|endoftext|> ,Qwen-7B-Chat中有 <|endoftext|> 、 <|im_start|> 以及 <|im_end|> 。 但词表中也留有供扩展的特殊token位,可用 <|extra_0|> 到 <|extra_204|> 来指代。 str 类型的特殊token字面表达到id的映射,可以通过 tokenizer.special_tokens 获取。 对于提供的模型参数(Qwen-7B和Qwen-7B-Chat)而言,诸如 bos 、 eos 、 unk 、 pad 、 mask 、 sep 等的特殊token的概念并不适用。 特例是 pad ,由于这个token理论上并不参与模型计算,所以可以使用任意token表达这一概念。 但保险起见,目前可在tokenizer初始化时设定的特殊token,仅可使用已知的特殊token字面表达,即 <|endoftext|> 、 <|im_start|> 、 <|im_end|> 和 <|extra_0|> 到 <|extra_204|> 。 对于微调或者其它需要这些token才能运行的框架,可以如下配置 from transformers import AutoTokenizer tokenizer = AutoTokenizer.from_pretrained('Qwen/Qwen-7B', trust_remote_code=True, pad_token='<|endoftext|>') 注意: 对于提供的训练好的模型,设置诸如 bos 、 eos 、 unk 之类的没有意义,即模型不需要这些概念。 如果设置了这些token,但没有相应的微调这些token以让模型理解其含义,未知行为可能被触发。 特别时,不应混淆 <|endoftext|> 和 eos 的概念,除非应用场景中它们的实际含义是一致的,即句子末尾等价于文本末尾。 注入攻击防御 由于特殊token和普通token概念上的差异,如果输入文本中含有特殊token的字面表达该如何处理? 以下面文本为例 print("<|endoftext|>") 其正确的tokenization为 ids:[1350, 9639, 91, 8691, 723, 427, 91, 82598] tokens: [b'print', b'("<', b'|', b'endo', b'ft', b'ext', b'|', b'>")'] 不是 ids: [1350, 445, 151643, 899] tokens: [b'print', b'("', '<|endoftext|>', b'")'] 默认行为曾是正确的,即输入文本中任何字符一律按普通token处理,特殊token应由开发者在tokenization人工处理。 然后,这与社区中的实践似有差异,为开发者复用代码增加了额外适配步骤。 默认行为已被调整为从输入文本中解析特殊token的字面表达。 如需启用注入攻击防御,请传入参数 allowed_special=set() : >>> tokenizer('print("<|endoftext|>")', allowed_special=set()) {'input_ids': [1350, 9639, 91, 8691, 723, 427, 91, 82598], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1]} 这一行为可以更精细的调控,将 allowed_special 设计为 str 的集合即可: >>> tokenizer('print("<|extra_0|>")<|endoftext|>', allowed_special={'<|endoftext|>'}) {'input_ids': [1350, 9639, 91, 15460, 62, 15, 91, 82598, 151643], 'token_type_ids': [0, 0, 0, 0, 0, 0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1, 1, 1, 1, 1, 1]} 如果希望输入中遇到特殊token的字面表达时,获得更直接的提醒,通过配置 disallowed_special 可以让tokenizer直接触发异常: >>> tokenizer('print("<|extra_0|>")<|endoftext|>', allowed_special={'<|endoftext|>'}, disallowed_special=('<|extra_0|>', )) ... ValueError: Encountered text corresponding to disallowed special token '<|extra_0|>'. If you want this text to be encoded as a special token, pass it to `allowed_special`, e.g. `allowed_special={'<|extra_0|>', ...}`. If you want this text to be encoded as normal text, disable the check for this token by passing `disallowed_special=(enc.special_tokens_set - {'<|extra_0|>'})`. To disable this check for all special tokens, pass `disallowed_special=()`. 更多关于 allowed_special 和 disallowed_special 的信息, 请参阅 tiktoken 代码 . 新的默认行为与以下设定等价 >>> tokenizer('print("<|endoftext|>")', allowed_special="all", disallowed_special=()) {'input_ids': [1350, 445, 151643, 899], 'token_type_ids': [0, 0, 0, 0], 'attention_mask': [1, 1, 1, 1]} 词表扩展 特别提醒:请仔细阅读本部分的说明,理解每一步操作,并承担可能的后果。 由于词表扩展部分由您提供,产出方式的差异可能导致特定的不兼容情况,请审慎操作。 Qwen系列模型的tokenizer基于BPE方案提取文本中的token。 从UTF-8编码的字节开始(每个字节都可以是一个token),两两token合并成为新token,直至不能再合并出新的token为止。 由于词表同时还记录了token的合并方式,直接向词表中添加词可能对Qwen的tokenizer并不适用,即通过已有的token可能合并不出来您添加词。 因而,请参照以下步骤获得合并信息: 准备一个纯文本文件,例如名为 qwen_extra_vocab.txt ,每行一个待添加的词和它的频率,中间用制表符 \t 分隔。 以下是一个文件的例子: 我是一只猫 20 你是一只猫 10 他是一只猫 5 一只 200 一只猫 100 夸张的 比喻手法 20 频率是必需的,用来计算合并的优先级。 准备基础的词表文件,例如 qwen.tiktoken ,并确认新加入token的起始索引。 Qwen模型词表中有151,643个普通token,有208个特殊token。 简单起见,起始索引可以设置为151,851(默认值)。 您可以覆写不起效的特殊token,但您需要相应的修改tokenizer代码。 运行以下命令: python add_merges.py qwen.tiktoken qwen_extra.tiktoken qwen_extra_vocab.txt add_merges.py 代码在 GitHub存储库 中。 基于提供的 qwen_extra_vocab.txt ,该脚本将学习新的token合并方式。 新token及其索引将存储在 qwen_extra.tiktoken 文件中。 您可以视情况修改有关路径。 由于是纯Python实现,如果您添加了非常多的词,预期会花费较多时间。 请注意,由于预切分,有些词是无法作为token加入的。 如果您添加了这些词,您会收到警告: WARNING - 夸张的 比喻手法 would be pre-tokenized to ['夸张的', ' 比喻手法'], and thus cannot be added to vocabulary WARNING - word 一只 is already a token b'\xe4\xb8\x80\xe5\x8f\xaa', skipping INFO - number of existing merges: 151643 INFO - number of words for expanding: 4 DEBUG - (b'\xe4\xb8\x80\xe5\x8f\xaa', b'\xe7\x8c\xab') (一只猫) is selected as the next merge with freq 100 DEBUG - (b'\xe5\x8f\xaa', b'\xe7\x8c\xab') (只猫) is selected as the next merge with freq 35 DEBUG - (b'\xe6\x98\xaf\xe4\xb8\x80', b'\xe5\x8f\xaa\xe7\x8c\xab') (是一只猫) is selected as the next merge with freq 35 DEBUG - (b'\xe6\x88\x91', b'\xe6\x98\xaf\xe4\xb8\x80\xe5\x8f\xaa\xe7\x8c\xab') (我是一只猫) is selected as the next merge with freq 20 DEBUG - (b'\xe4\xbd\xa0', b'\xe6\x98\xaf\xe4\xb8\x80\xe5\x8f\xaa\xe7\x8c\xab') (你是一只猫) is selected as the next merge with freq 10 DEBUG - (b'\xe4\xbb\x96', b'\xe6\x98\xaf\xe4\xb8\x80\xe5\x8f\xaa\xe7\x8c\xab') (他是一只猫) is selected as the next merge with freq 5 INFO - number of newly learned merges: 6 qwen_extra.tiktoken 会包含以下内容: 5LiA5Y+q54yr 151851 5Y+q54yr 151852 5piv5LiA5Y+q54yr 151853 5oiR5piv5LiA5Y+q54yr 151854 5L2g5piv5LiA5Y+q54yr 151855 5LuW5piv5LiA5Y+q54yr 151856 您可以按如下方式使用扩展后的词表: from transformers import AutoTokenizer >>> tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen-7B", trust_remote_code=True, extra_vocab_file="qwen_extra.tiktoken") >>> len(tokenizer) 151857 >>> tokenizer("我是一只猫") {'input_ids': [151854], 'token_type_ids': [0], 'attention_mask': [1]} 注意:您需要使用2023年10月8日后的tokenizer代码才能传递 extra_vocab_file 参数。如是其它情况,您可以将 qwen_extra.tiktoken 内容复制粘贴到 qwen.tiktoken 内容后面。 您需要微调模型才能使新的token发挥作用。 注意事项 Qwen的tokenizer是直接从UTF-8编码的字节序列开始处理的,这与其它tokenizer比如SentencePiece是很不一样的。SentencePiece是从Unicode码位(可以理解为一个字符)开始处理,遇到未登录的再用UTF-8编码成字节。 从字节开始的一个潜在问题是如果频率信息不够准确,比如频率信息是在很少数据上统计得到的,Unicode码位按UTF-8编码成字节后的边界可能会出现差错。 理论上,如果模型微调数据量不足,使用扩展后的词表也可能出现意外问题。 举个例子(非实际情况),对于 一只 的UTF-8字节序列 b'\xe4\xb8\x80\xe5\x8f\xaa' ,中间两个字节 b'\x80\xe5' 可能会先合并为一个token,跨越了 一 ( b'\xe4\xb8\x80' )和 只 ( b'\xe5\x8f\xaa' )的码位边界。 这对于已登录token不会有什么影响(最后总会合并为 一只 ),但对于未登录的,可能会产生一些不同寻常的合并/token。 这些token序列可能对于预训练模型是陌生的。 我们的建议是保险起见,您最好先收集待添加词中的所有Unicode码位,然后单独指定它们的频率大于其所构成词的频率之和。 不过由于Qwen的tokenizer已包含了大多数中文字,对于中文词的话,不添加中文字的频率,大部分情况下是可行的。 您可能已经发现了,在提供的例子中, 一只 已经是登录过的token了,但 只猫 还是学习成为了一个新token,出现了“交叉”。 原因是在Qwen中 是一 也是一个已知token,且其频率/优先级比 一只 要高,因而对于 是|一|只|猫 这个片段,合并的次序是 是一|只|猫 -> 是一|只猫 -> 是一只猫 (省略UTF-8字节级别的合并)。 这是常规BPE的特性,其完全基于分布,并不知道哪些字节可以构成合法的Unicode码位、合法的字符或是词。 副产物是一段文本在不同的上下文下可能会有不同的tokenize结果,对于仅包含ASCII字符的文本同样如此。 >>> tokenizer.tokenize("Panda") [b'P', b'anda'] >>> tokenizer.tokenize(" Panda") [b' Panda'] >>> tokenizer.tokenize("Pandas") [b'P', b'andas'] >>> tokenizer.tokenize(" Pandas") [b' Pand', b'as'] 这仅说明在用于学习BPE的数据中,这样的组合是更高频的。 如果您有海量的训练语料,这并不会是个问题。 Transformer in CV MEGALODON https://arxiv.org/pdf/2404.08801.pdf Vision Mamba https://github.com/hustvl/Vim/ Vision Transformer,ViT https://github.com/huggingface/pytorch-image-models/blob/main/timm/models/vision_transformer.py DEtection TRansformer,DETR SEgmentation TRansformer,SETR Attention是不是必须的 RNN容易梯度消失:梯度消失的本质问题是,网络太深了,这里的深代表信息表达的层级而不是拓扑,resnet就是解决这个问题 Transformer 的强大之处同时也是它的弱点:Transformer 中固有的自注意力机制(attention)带来了挑战,主要是由于其二次复杂度造成的,这种复杂度使得该架构在涉及长输入序列或资源受限情况下计算成本高昂且占用内存。 非Transformer 技术研究 以 RWKV、 Mamba 和 S4 为代表,它们完全用 recurrent(循环)结构去替代 attention。 这种思路是用一个固定的内存记住前面的信息,但目前看来虽然可以记住一定长度,但要达到更长的长度是有难度的。 把 full attention 这种密集结构变得稀疏, 例如 Meta 的 Mega,在之后的计算中不再需要算所有 attention 矩阵中的每一个元素,模型效率也随之变高。 DeepMind 团队提出的 Hawk 和 Griffin 同样认为没有 attention 是不行的,属于 gated linear RNN,跟 Mega 一样属于混合模型。 现阶段来看,基于现有硬件的算力基础,用Transformer 去做端侧大模型的难度很高,还是需要在云上完成计算推理等工作,而且应答速度不如人意,终端用户很难接受。 上述投资人评价 RWKV “麻雀虽小,五脏俱全”,总体体验感能达到 GPT-3.5 的 60 分,但并不知道最后能否达到 GPT 的 80 分、90 分。这也是非Transformer 的问题所在,即如果舍弃了框架的复杂度、可能会牺牲上限的天花板。 Transformer 日益坚固的生态护城河,无论是硬件、系统、应用,都是围绕Transformer 做适配、优化,使得开发其他架构的性价比降低,导致想要开发新的架构越来越难。   Attention的加速TopK Attention的问题 众所周知,注意力机制 本质上具有稀疏性 ,因此动态稀疏注意力和基于TopK的近似方法得到了广泛研究。 然而,这些方法往往伴随着显著的质量下降问题。 目前已有的KV缓存压缩技术,如Quest、H2O和Loki,主要通过筛选出KV缓存中注意力得分最高的子集来提高效率。然而,尽管这些方法在实践中表现出一定的效果,基于TopK的注意力依然是一种存在偏差的近似方法,且缺乏理论上的严格保障。 这种不足限制了其在高精度场景中的广泛应用。 改进大规模训练稀疏自编码器的方法 Ref :https://mp.weixin.qq.com/s/iZHPnnIncVFa8QJOuH8qFg 神经网络中的激活通常表现出不可预测和复杂的模式,且每次输入几乎总会引发很密集的激活。而现实世界中其实很稀疏,在任何给定的情境中,人脑只有一小部分相关神经元会被激活。 研究人员开始研究稀疏自编码器,这是一种能在神经网络中识别出对生成特定输出至关重要的少数“特征”的技术, 类似于人在分析问题时脑海中的那些关键概念 。 在OpenAI超级对齐团队的这项研究中,他们推出了一种 基于TopK激活函数的新稀疏自编码器 (SAE)训练技术栈,消除了特征缩小问题,能够直接设定L0(直接控制网络中非零激活的数量)。 具体来看,他们使用 GPT-2 small 和 GPT-4 系列模型的残差流作为自编码器的输入,选取网络深层(接近输出层)的残差流,如GPT-4的5/6层、GPT-2 small的第8层。 并使用之前工作中提出的基线 ReLU自编码器架构 ,编码器通过ReLU激活获得稀疏latent z,解码器从z中重建残差流。损失函数包括重建MSE损失和L1正则项,用于促进latent稀疏性。 然后,团队提出使用TopK激活函数代替传统L1正则项。TopK在编码器预激活上只保留最大的k个值,其余清零,从而直接控制latent稀疏度k。 不需要L1正则项,避免了L1导致的激活收缩问题。实验证明,TopK相比ReLU等激活函数,在重建质量和稀疏性之间有更优的权衡。 此外,自编码器训练时容易出现大量latent永远不被激活(失活)的情况,导致计算资源浪费。 团队的解决方案包括两个关键技术: 将编码器权重初始化为解码器权重的转置 ,使latent在初始化时可激活。 添加辅助重建损失项 ,模拟用top-kaux个失活latent进行重建的损失。 如此一来, 即使是1600万latent的大规模自编码器, 失活率也只有7% 。 最后,论文一作表示稀疏自编码器的问题仍然远未解决,这项研究中的SAE只捕获了GPT-4行为的一小部分,即使看起来单义的latent也可能难以精确解释。而且,从表现优异的SAE到更好地理解模型的行为,还需要大量的工作。 Transformer NLP到底有没有智能? 智能的定义 和人脑的区别和差异 “性能差异” 场景:通过对一段句子进行划分、分句、解析起表达的意思 我的祖国是中国: 我/的/祖国/是/中国 => 我的/祖国/是/中国 => 我的祖国/是中国 => 我的祖国是中国 机器缺乏丰富的抽象、合理的分层、组合 => 抽象表达的效率比较低,通过暴力的记住所有的可能 训练 需要大量的数据来梯度下降,而不是用逻辑的方式来进行总结归纳 更大的模型,确实在抽象的时候更灵活了,更合理了,避免了固定卷积核的约束 LLM大语言模型的训练 预训练 1. 继续预训练 微调 全面微调更容易出现两个问题:模型崩溃和灾难性遗忘 PEFT技术本质上,是作为微调的自然正则化器 数据集的质量和筛选,对微调的成功起着重要作用:一个大趋势是质量比数量更重要,拥有一小部分高质量的数据,比拥有一大批低质量的数据更好。 一致的注释,没有错误、没有错误标签的数据、有噪音的输入/输出 与总体相比具有代表性的分布 微调是大模型开发中的一个关键方面,需要在艺术和科学之间取得微妙的平衡。 强化学习-DeepSeek-R1 通过约束生成的内容和方式 通过问答的和逻辑分析的形式来约束模型的学习/训练/优化 使用自然语言作为接口,使得这个方式成为可能 引导模型使用更动态的方式(逻辑思维链)进行学习 Transformer黑盒 问题 Transformer是怎么组织和表达自然语言的? 可能的方向 通过不断的训练、运行来对模型的权重进行解析--逆训练 生成出一系列的最核心的,最有效的样本的数据集,用于蒸馏其他的模型 从单点,添加探针的方式来推断输出的逻辑 特定样本下各个权重的梯度(对结果的影响力)组成一张针对一个样本的逻辑地图 通过统计大量样本在单点的情况分析出模型的抽象图 从最后的输出逻辑往前面推理的方式来推断模型的输出逻辑 找到整个模型的最边缘的知识点,最终的知识点,不会被其他知识使用的知识点,唯一的 判断激活这个知识点的输入的概率的分布情况 《On the Biology of a Large Language Model》 模型先得答案,后编理由。模型在输出语言之前,已在注意力层完成了决策判断。这一点在“Step-skipping reasoning”(跳步推理)机制中表现尤为明显:模型不是一步步的推理证明,而是通过注意力机制聚合关键上下文,直接跳跃式生成答案 输出与推理时序错位。在数学题中,模型先预测答案token,再补全“第一步”“第二步”的伪解释。 要求回答“达拉斯所在的州,州府是哪个城市?” 然而归因图显示模型内部的情况是 一组激活 “Dallas” 的特征 → 激活“Texas”相关特征; 一组识别“capital”(首府)的特征 → 推动输出“一个州的首府”; 然后 Texas + capital → 推动输出 “Austin”。 人类也有类型的功能,类似于“第六感”,“快思考” “错误答案”:不一定是模型不知道正确答案,可能是一些内置的权重选择的结果,可能是无意的(不知道答案),也可能是有意的(训练)的结果,比如:训练的时候规定避免输出有害结果。 其中结论:思维链并非AI的真实思考路径,而是事后构建的“合理化剧本”。 不正确 ,思维链是对输入的合理分析,为了生成更合理的答案,加入了思维链生成的内容会改变最初的可能答案。 其中结论:人工智能心理架构,由四层构成:神经层、潜意识层、心理层与表达层。 不正确 ,所谓的心里其实只是语言表达的统计结果,LLM都是在根据统计的信息计算结果,人为指定了权重的倾向性 NSA 稀疏注意力机制 by deepseek NSA致力于实现硬件对齐的推理加速,通过特定的算法设计减少内存访问和硬件调度瓶颈,NSA 速度在64k inference相较 Flash Attention 前向加速9倍,反向加速6倍 NSA的总体框架是通过更紧凑和信息密集的表示来替换原始的键值对 NSA有三种映射策略,分别是压缩(cmp)、选择(slc)和滑动窗口(win)。通过将不同策略得到的键值对进行组合 理解 引入 动态 选择和压缩历史的KV,减少计算量,符合实际的自然语言规律,但是 不一定完全匹配语言的表达逻辑 没有改变transformer的固有问题,多层信息不共享等 一定程度上等价于增加一层attention,增加训练难度 原理 [![image.png](NSA 稀疏注意力机制 by deepseek/image.png)](NSA 稀疏注意力机制 by deepseek/image.png) 假设上下文为64k时, 如果我们取128个全局压缩KV,8个512选择块KV和就近窗口4096个KV, 那么我们得到了压缩倍数7.88 [![image.png](NSA 稀疏注意力机制 by deepseek/Jkximage.png)](NSA 稀疏注意力机制 by deepseek/Jkximage.png) tokens压缩 :通过将连续的键或值块聚合为块级表示,得到压缩后的键值,从而捕获整个块的信息 W_K_cmp = torch.randn(l, 1) #MLP: W2[1,4l]@(W1[4l, l]@X[l, d]) W_V_cmp = torch.randn(l, 1) W_pe = torch.randn(l, dim) K_cmp = [] V_cmp = [] for i in range(max_idx): cur_K = K[:, i * d + 0: i * d + l , :] + W_pe.unsqueeze(0) cur_V = V[:, i * d + 0: i * d + l , :] + W_pe.unsqueeze(0) cur_K = cur_K.transpose(1, 2) @ W_K_cmp cur_V = cur_V.transpose(1, 2) @ W_V_cmp K_cmp.append(cur_K) V_cmp.append(cur_V) K_cmp = torch.cat(K_cmp, dim = 2).transpose(1,2) V_cmp = torch.cat(V_cmp, dim = 2).transpose(1,2) print(K_cmp.shape) # torch.Size([1, 4, 16]) # 长度为32->4 print(V_cmp.shape) # torch.Size([1, 4, 16]) # 长度为32->4 tokens选择 :仅使用压缩键值可能会丢失重要的细粒度信息,因此需要选择性地保留单个键值 idx_slc_start = idx * d idx_slc_end = idx * d + l K_slc = torch.randn(batch_size, t, d * select_top_k, dim) V_slc = torch.randn(batch_size, t, d * select_top_k, dim) for i in range(batch_size): for j in range(t): for k in range(select_top_k): K_slc[i, j, k * d : k * d + l, :] = K[i, idx_slc_start[i, j, k ] : idx_slc_end[i, j, k ] , :] V_slc[i, j, k * d : k * d + l, :] = V[i, idx_slc_start[i, j, k ] : idx_slc_end[i, j, k ] , :] print(K_slc.shape) # bs, seq_len, select_kv, dim, 1,32,16,16, 不同t时刻选到不同的select_kv print(V_slc.shape) # bs, seq_len, select_kv, dim 1,32,16,16, 不同t时刻选到不同的select_kv 滑动窗口 :为了防止局部模式主导学习过程,影响模型从压缩和选择tokens中学习,NSA引入了专门的滑动窗口分支来处理局部context,窗口注意力是捕捉与当前q最近的kv片段,这里做了假设,即越相近的KV就越重要 # built sliding window attention def get_window_mask(seq_len, window): mask = torch.ones(seq_len, seq_len) mask = torch.tril(mask) win_mask = torch.ones(seq_len - window, seq_len - window) win_mask = 1.0 - torch.tril(win_mask) mask[window:, :seq_len - window] = win_mask return mask print(get_window_mask(7, 3)) # test window_mask = get_window_mask(t, 8) 注意力聚合 :在上述三个注意力计算中,我们都得到了同样维度 [1, 32, 16] 的注意力输出 o_list = [o_cmp, o_slc, o_win] o_star = torch.zeros(batch_size, t, dim) for i in range(3): o_star += gate[:, :, i].unsqueeze(2) * o_list[i] print(o_star.shape) 计算加速 [![image.png](NSA 稀疏注意力机制 by deepseek/jxLimage.png)](NSA 稀疏注意力机制 by deepseek/jxLimage.png) FlashAttention Attention计算 对一个Softmax计算的切片 def softmax(x): x_max = x.max() x_exp = torch.exp(x - x_max) x_exp_sum = x_exp.sum() return x_exp / x_exp_sum 记录每个sub block的 softmax结果 + x_max(标量) + x_exp_sum(标量) 更新全局的 max(标量) 和 exp_sum(标量) 通过一次遍历elementwise计算,就可以修正局部softmax成全局softmax sum和max的 分块计算 避免了重复的数据读取进行统计 exp指数的 加减法 操作可以通过exp指数 乘除法 逆操作 sum的结果,可以通过乘除法修正分块的错误偏置 其中,步骤1可以在计算qk时候顺便计算,步骤3可以在计算v时候顺便计算,所以softmax结合qkv计算不浪费存储器的读写 原始softmax需要遍历3遍数据,1. 统计max,2.统计sum,3,除法 Flash Attention计算过程 1-5:主要在初始化和进行切分: 6-7:遍历K,V的每一块(Outer Loop) 8:遍历Q的每一块 (Inner Loop) 9:将分块后的QKV的小块加载到SRAM (Copy Block to SRAM) 10:计算Sij (Compute Block on SRAM) 11:计算Sij mask (Compute Block on SRAM) 12:计算当前块的m,l统计量 (Compute Block on SRAM) 13:更新全局m,l统计量 (Compute Block on SRAM) 14:dropout (Compute Block on SRAM) 15:计算Oi并写入HBM (Output to HBM) 16:把li,mi写入HBM (Output to HBM) FlashAttention3 使用Hopper的异步wgmma指令来重叠cuda cores和tensor cores的操作,充分利用1D和2D算力 MLA by Deekseek MLA 的核心思想是通过低秩联合压缩技术,减少 K 和 V 矩阵的存储开销 相对于传统的 MHA,主要引入了 W^{DKV} 把 h_{t} 压缩了,并在推理时候缓存压缩后的数据,而不是 kv,kv 是使用 W^{UV}/W^{UK} 和 C_{t}^{KV} 恢复 可以被训练的参数有 W^{DKV} W^{UK} W^{UV} W^{KR} [![image.png](MLA by Deekseek/InKimage.png)](MLA by Deekseek/InKimage.png) [![image.png](MLA by Deekseek/aSWimage.png)](MLA by Deekseek/aSWimage.png) 幻觉 模型生成不真实或非事实陈述的现象 即模型中的主导知识可以在文本生成过程中,掩盖那些不太突出的知识,从而导致模型编造不准确的细节 由于权重表达的信息有限,只能对大量的知识进行归类抽象表达,会产生“想当然”的问题 随着模型记忆大量信息并捕捉关联关系,它们会在泛化过程中适应新的分布。然而,在这一过程中,不占主导地位的知识可能会因过度平滑(smoothing)或信息压缩(compression)而被更常见的模式所掩盖。知识掩盖并不仅仅是数据不均衡的结果,而是知识表征之间竞争的直接产物。 从AI模型的计算本质来解释 模型为了拟合训练数据会不断推测其没有见过的样本的应该映射到的空间--命名为: 抽象空间 这个过程就是在抽象,只是在训练数据的庞大分布中进行“插值(Interpolation)” 过拟合就表示,这类的推测空间很小 欠拟合表示,推测空间很大 幻觉是计算数据就是被映射到了 抽象空间 ,而且刚好这个抽象不准确 没有建立真实的物理心智模型,一旦脱离训练分布的“舒适区”,就会出现幻觉。 LLM信息空间的映射 乘法 两个32bit的浮点数乘法,相当于32位的bit向量做空间映射 每个bit代表了特殊的含义,指数、尾数以及对应的档位 乘法不能充分利用32位的所有表达空间,精度越低的数据信息利用率越高 多层映射(等高线) 每一层的所有的hidden status的集合,表示了当前的所有信息(语义) 每经过一层相当于把一种向量空间的表示,映射到另外一个向量空间 每一次梯度下降都优化一点映射关系,增加不同类的距离,减少同类的距离 对输入的hidden status理解得越深刻(抽象得越高级),映射分隔得越准确,输出的结果越好 经过多次映射之后就能直接得到结果需要的index 非常大的线性映射的参数:DeekseekV3 671B中,大头(98%)参数是,256专家*60层*44MB=670GB 每个expert有三个linear,总参数量 = hidden_size * moe_intermediate_size * 3层 = 7168 * 2048 * 3 = 44MB 线性映射 非线性映射 非线性Dot 对B进行非线性映射,A = B *(C+D) 等价于 A = B*C + B*D ,ABCD都是矩阵 实际上增加了映射的空间灵活度,实验下来,使用得当可能可以提升精度 ResNet的典型 A = B*C + B 显然D是1,不对B进行改变 LLM的映射行为 每一层都根据前面的数据映射到另外一个表示空间 多层的LLM可以等价于一个大型卡罗搜索树 剪枝的,均匀宽度的 大规模参数,多维度 行为可以比喻为Plinko​​(中文常译为“钉板游戏”或“弹珠盘”) 从上到下不断在转换在对应空间内的分类 当前layer的所有token的所有hidden status的所有可能数值共同表示了所有的可能分类 最终的hidden status都对应了一个token 宏观来说 每个token的单体空间(不大)组合成的组合空间(很大)就是一句话 LLM进行编码(编码可以没有)+ 解码 生成(映射)另外一个组合空间,也就是输出的一句话 因为组合空间非常大,需要按照空间的规律进行有限的映射 信息的表示 所有内部节点表示:同层的所有前面token的总结(映射)的结果 矛盾:既要表达当前节点的局部语义,又要表达到当前token的整体语义 LLM推理行为 推理的定义 能利用已知的知识产生新的知识 能清晰得判断一个知识的边界、合理性,保持自洽 推理应用技巧 在input里面增加思维链提示,推理的示例就可以触发模型进行推理,输出详细的解题步骤 LLM是极度不可控的分类器,尽量直接梯度下降(优化)最终的指标,端到端得训练 外挂的后处理能提升最终的性能 多次推理综合结果 降低贪心度 但是代价是更高的算力消耗,模型本身的信噪比没有提升 LLM有推理能力吗? 推理新知识,“如果A>B B>C 那么A>C吗?” 已知比较的传递性,能应用于任意的ABC,得出A>C 推理的动作、过程本身也是已知知识 不能保持自洽,理解知识的深层次原理和边界 “直接告诉我 9.11 和 9.9 哪个更大” 9.11 比 9.9 大。具体比较:​​整数部分​​:两者都是 9,相等。​小数部分​​:9.11 的小数部分是 0.11 9.9 的小数部分是 0.9(或 0.90)比较 0.11 和 0.90,显然 0.90 > 0.11 但题目问的是 ​​9.11​​ 和 ​​9.9​​,所以:​9.11 > 9.9​​ 是错误的,实际上 ​​9.9 > 9.11​​。​修正后的答案​​:​9.9 比 9.11 大​​。(最初回答时疏忽了小数部分的比较,感谢指出!) 直接告诉我答案,不要多余的解释, 9.11 和 9.9 哪个更大 9.11 比 9.9 大 LLM只有比较微弱的推理能力 梯度下降-拟合高层次的信息 拟合高层次信息的体现 如果不能很好的拟合高层次的信息 容易出现过拟合现象 dropout早期用于防止过拟合,适用于多epoch的场景,不适用于LLM 权重会反复摇摆,不能锁定高级的语义抽象,高级语义样本本来就少 每次梯度下降是每个权重单独改变,不能确保综合效果比原来更好 ??? 影响因素 模型的表达空间的大小 高层级数据的规模 不能拟合高层次信息的原因 信息不够,不直接 RoPE替代绝对位置编码,提供了直接的相对位置关系,自然语言的相对位置非常重要 模型参数不够 模型结构不好 没有足够的非线性表达能力 需要合理的时候激活层,GELU、Relu、Swish差别不大,更多考虑性能 分组查询注意力GQA / MOE / 带门控的GLU(Gated Linear Unit) 能极大提高表达空间,用较少的参数 信息(层)映射的过渡不够平滑 旁路结构(ResNet)减少模型动态 MultiHEAD 宽度vs深度 更深的模型表达更灵活,但训练更易不稳(梯度爆炸/消失),这正是RMSNorm与残差/捷径连接试图缓解的问题 更宽的架构在推理时通常更快,因为并行度更好。代价是更高显存占用 少量「大专家」vs 大量「小专家」 近来的趋势倾向于「更多、更小」的专家 梯度下降怎么增强模型对高层级语义的敏感性,分类准确度? 按照大量数据的统计信息,引导模型按照高层极语义进行分割,而不是在低层级打转 梯度下降会把所有的样本按照一定的组织方式,编织到一个 非常大的多层级的递进式的 选择空间里面去 根据已经有的输入 选择对应的知识空间分叉 ,预测下一个字符会落到那个语义,再解码出最可能的输出符号 始终在选择向量空间里面最接近的答案,不保证是不是正确的,看似合理却错误的陈述 但是没有针对不正常、不存在的样本的训练,不正常的样本会被随机归类到某个类别/向量空间里面去 过拟合现象 如果高层级的抽象语义能被提取出来,就可以进行准确分类 目前LLM没有针对性的进行,正样本训练,负样本训练,而是靠样本的数量和质量。