diff --git a/_posts/MachineLearning/LLM/2024-07-28-llm_finetune_practice.md b/_posts/MachineLearning/LLM/2024-07-28-llm_finetune_practice.md
index 39ab477f..ff9af174 100644
--- a/_posts/MachineLearning/LLM/2024-07-28-llm_finetune_practice.md
+++ b/_posts/MachineLearning/LLM/2024-07-28-llm_finetune_practice.md
@@ -432,7 +432,7 @@ LLaMA-Factory
```
workflow.py 的逻辑言简意赅,就是拼凑运行 Trainer的dataset、model、tokenizer、data_collator等参数
-1. 对于dataset 有一个load_dataset 和preprocess_dataset 的过程,preprocess_dataset 会根据任务目标不同,处理逻辑不同,也就是将数据转为input_ids 的方式不同。 最终转为trainer 也就是transformer model 可以接受的dataset,包含列 input_ids/attention_task/labels(或其它model.forward 可以支持的参数)。
+1. 对于dataset 有一个load_dataset 和preprocess_dataset 的过程,preprocess_dataset 会根据任务目标不同,处理逻辑不同,也就是将数据转为input_ids 的方式不同。 最终转为trainer 也就是transformer model 可以接受的dataset,包含列 input_ids/attention_task/labels(或其它model.forward 可以支持的参数)。 PS: 对于有监督任务,比如包含name=labels 列,至于input 列则名字随意,毕竟input text 总有一个通过tokenizer 转为input_ids 的过程。传入trainer的 dataset 有input_ids 和labels等列即可。
2. Trainer 对训练逻辑已经封的很好了,内部也支持了accelerate 和 deepspeed,只要合适的配置 training_args 即可。
以pt对应的workflow.py 为例
diff --git a/_posts/MachineLearning/LLM/2024-08-14-emebedding_finetune.md b/_posts/MachineLearning/LLM/2024-08-14-rag_emebedding.md
similarity index 56%
rename from _posts/MachineLearning/LLM/2024-08-14-emebedding_finetune.md
rename to _posts/MachineLearning/LLM/2024-08-14-rag_emebedding.md
index 3850a8b9..6abf17be 100644
--- a/_posts/MachineLearning/LLM/2024-08-14-emebedding_finetune.md
+++ b/_posts/MachineLearning/LLM/2024-08-14-rag_emebedding.md
@@ -15,45 +15,77 @@ keywords: llm emebedding
## 简介
+Foundation Model有两个代表,一个是Large Language Model,另一个是Embedding Model。前者聚焦文本空间,其形式化功能为text -> text;后者聚焦向量空间,其功能为text -> embedding。转为向量能做些什么呢?比较常见的使用场景包括retrieval(如检索知识库、检索Tool)、clustering(聚类)、classification(分类,通常需再接一层分类layer)等。
-在专业数据领域上,嵌入模型的表现不如 BM25,但是微调可以大大提升效果。embedding 模型 可能从未见过你文档的内容,也许你的文档的相似词也没有经过训练。在一些专业领域,通用的向量模型可能无法很好的理解一些专有词汇,所以不能保证召回的内容就非常准确,不准确则导致LLM回答容易产生幻觉(简而言之就是胡说八道)。可以通过 Prompt 暗示 LLM 可能没有相关信息,则会大大减少 LLM 幻觉的问题,实现更好的拒答。
-1. [大模型应用中大部分人真正需要去关心的核心——Embedding](https://mp.weixin.qq.com/s/Uqt3H2CfD0sr4P5u169yng)
-2. [分享Embedding 模型微调的实现](https://mp.weixin.qq.com/s/1AzDW9Ubk9sWup2XJWbvlA) ,此外,原则上:embedding 所得向量长度越长越好,过长的向量也会造成 embedding 模型在训练中越难收敛。 [手工微调embedding模型,让RAG应用检索能力更强](https://mp.weixin.qq.com/s/DuxqXcpW5EyLI3lj4ZJXdQ) 未细读
-3. embedding训练过程本质上偏向于训练数据的特点,这使得它在为数据集中(训练时)未见过的文本片段生成有意义的 embeddings 时表现不佳,特别是在含有丰富特定领域术语的数据集中,这一限制尤为突出。微调有时行不通或者成本较高。微调需要访问一个中到大型的标注数据集,包括与目标领域相关的查询、正面和负面文档。此外,创建这样的数据集需要领域专家的专业知识,以确保数据质量,这一过程十分耗时且成本高昂。而bm25等往往是精确匹配的,信息检索时面临词汇不匹配问题(查询与相关文档之间通常缺乏术语重叠)。幸运的是,出现了新的解决方法:学习得到的稀疏 embedding。通过优先处理关键文本元素,同时舍弃不必要的细节,学习得到的稀疏 embedding 完美平衡了捕获相关信息与避免过拟合两个方面,从而增强了它们在各种检索任务中的应用价值。支持稀疏向量后,一个chunk 在vdb中最少包含: id、text、稠密向量、稀疏向量等4个字段。
-## 向量匹配的几个问题
-1. **问题在语义上与其答案并不相同**。因此直接将问题与原始知识库进行比较不会有成果。假设一位律师需要搜索数千份文件以寻找投资者欺诈的证据。问题“什么证据表明鲍勃犯了金融欺诈行为? ”与“鲍勃于 3 月 14 日购买了 XYZ 股票”在语义上基本上没有任何重叠(其中隐含 XYZ 是竞争对手,3 月 14 日是收益公告发布前一周)。PS:构建数据以进行同类比较。与问题→支持文档相比,问题→问题比较将显著提高性能。从实用角度来说,您可以要求 ChatGPT 为每个支持文档生成示例问题,并让人类专家对其进行整理。本质上,您将预先填充自己的 Stack Overflow。此外,用户反馈的问答对也可以用于问题重写或检索。
-2. 向量嵌入和余弦相似度是模糊的。向量在完全捕捉任何给定语句的语义内容方面存在固有缺陷。另一个微妙的缺陷是,余弦相似度不一定能产生精确的排名,因为它隐含地假设每个维度都是平等的。在实践中,使用余弦相似度的语义搜索往往在方向上是正确的,但本质上是模糊的。它可以很好地预测前 20 个结果,但通常要求它单独可靠地将最佳答案排在第一位是过分的要求。
-3. 在互联网上训练的嵌入模型不了解你的业务和领域。在专业的垂直领域,待检索的文档往往都是非常专业的表述,而用户的问题往往是非常不专业的白话表达。所以直接拿用户的query去检索,召回的效果就会比较差。Keyword LLM就是解决这其中GAP的。例如在ChatDoctor中会先让大模型基于用户的query生成一系列的关键词,然后再用关键词去知识库中做检索。ChatDoctor是直接用In-Context Learning的方式进行关键词的生成。我们也可以对大模型在这个任务上进行微调,训练一个专门根据用户问题生成关键词的大模型。这就是ChatLaw中的方案。
+[没有思考过 Embedding,不足以谈 AI](https://mp.weixin.qq.com/s/7kPxUj2TN2pF9sV06Pd13Q)计算的基础是数,而自然语言是文字,因此很容易想到要做的第一步是让文字数字化,为行文方便,我们将这个过程叫做编码。要设计编码的方法,自然需要思考的问题是:哪些性质是编码规则必须要满足的?
+1. 每一个词具有唯一量化值,不同词需要具有不同的量化值
+2. 词义相近词需要有"相近"的量化值;词义不相近的词量化值需要尽量“远离”。当性质二得到满足时,同义的句子在序列特征上会更加接近,这将有利于计算机而言更高效地理解共性、区分特性;反之则会给计算机制造非常多的困难。**难以捕捉同质内容之间的共性,就意味着模型需要更多的参数才能描述同等的信息量**,学习的过程显然困难也会更大。OpenAI 的 Jack Rae 在 Standford 的分享 中提到了一个很深刻的理解语言模型的视角:语言模型就是一个压缩器。所有的压缩,大抵都能被概括在以下框架内:提取共性,保留个性,过滤噪声。带着这个视角去看,就更加容易认识到性质二的必要性。不同词所编码的数值,是否基于词义本身的相似性形成高区分度的聚类,会直接影响到语言模型对于输入数据的压缩效率。因为词是离散分布的,而计算模型的输出 —— 除非只使用非常简单的运算并且约束参数的权重 —— 很难恰好落在定义好的量化值中。对于神经网络模型,每一个节点、每一层都必须是连续的,否则便无法计算梯度从而无法应用反向传播算法。**这两个事实放在一起可能会出现的情况是:词的量化值可以全部是整数,但是语言模型的输出不一定**。例如当模型输出 1.5,词表只定义了 1 和 2,这时该如何处理呢?我们会希望 1 和 2 都可以,甚至 3 可能也不会太离谱,因此 1 和 2 所代表的词在词义上最好有某种共性。当相近的词聚集到一起,推断出有效输出的概率就会更高。
+3. 词义的多维性。对于每一个词,我们可以表达为一组数,而非一个数;这样一来,就可以在不同的维度上定义远近,词与词之间复杂的关系便能在这一高维的空间中得到表达。
- ![](/public/upload/machine/keyword_recall.jpg)
+图像可以有embedding,句子和段落也可以有 embedding —— 本质都是通过一组数来表达意义。段落的 embedding 可以作为基于语义搜索的高效索引,AI 绘画技术的背后,有着这两种 embedding 的互动 —— 未来如果有一个大一统的多模态模型,embedding 必然是其中的基石和桥梁 。
-## 稀疏向量
+## embedding 渊源
-稀疏向量 也是对chunk 算一个稀疏向量存起来,对query 算一个稀疏向量,然后对稀疏向量计算一个向量距离来评价相似度。
-1. 关键词检索。BM25是产生稀疏向量的一种方式。其稀疏性体现在,假设使用 jieba(中文分词库)分词,每个词用一个浮点数来表示其统计意义,那么,对于一份文档,就可以用 349047 长度的向量来表示。这个长度是词典的大小,对于某文档,该向量绝大部分位置是0,因此是稀疏向量。基于统计的BM25检索有用,但其对上下文不关心,这也阻碍了其检索准确性。假设有份文档,介绍的是 阿司匹林 的作用,但这篇文档仅标题用了 阿司匹林 这个词,但后续都用 A药 来代指 阿司匹林,这样的表述会导致 阿司匹林 这个词在该文档中的重要程度降低。
+[词嵌入的新时代](https://cloud.tencent.com/developer/article/2058413)为了让机器可以学习到文本的特征属性,我们需要一些将文本数值化的表示的方式。
+1. Word2vec算法通过使用一组固定维度的向量来表示单词,计算其方式可以捕获到单词的语义及单词与单词之间的关系。使用Word2vec的向量化表示方式可以用于判断单词是否相似,对立,或者说判断“男人‘与’女人”的关系就如同“国王”与“王后”。(这些话是不是听腻了〜 emmm水文必备)。另外还能捕获到一些语法的关系,这个在英语中很实用。例如“had”与“has”的关系如同“was”与“is”的关系。
+2. 上面介绍的词嵌入方式有一个很明显的问题,因为使用预训练好的词向量模型,那么无论上下文的语境关系如何,每个单词都只有一个唯一的且已经固定保存的向量化形式。这和中文的同音字其实也类似,用这个举一个例子吧, ‘长’ 这个字,在 ‘长度’ 这个词中表示度量,在 ‘长高’ 这个词中表示增加。那么为什么我们不通过”长’周围是度或者是高来判断它的读音或者它的语义呢?这个问题就派生出语境化的词嵌入模型。EMLo改变Word2vec类的将单词固定为指定长度的向量的处理方式,它是在为每个单词分配词向量之前先查看整个句子,然后使用bi-LSTM来训练它对应的词向量。
+3. Transformer论文和代码的发布,以及其在机器翻译等任务上取得的优异成果,让一些研究人员认为它是LSTM的替代品,事实上却是Transformer比LSTM更好的处理long-term dependancies(长程依赖)问题。从LSTM到Transformer的过渡中,我们发现少了些东西。ELMo的语言模型是双向的,但是OpenAI的transformer是前向训练的语言模型。我们能否让我们的Transformer模型也具有Bi-Lstm的特性呢?Bert = transformer encoder + 双向 + Masked learning。 bert base 有12层encoder,最后一层的第一个输出向量(对应[cls])可以作为整个输入的向量表示。PS:其实其它层输出向量、最后一层的非第一个输出向量 单个、或几个contact一下也都可以作为输入的表示
+ ![](/![](/public/upload/machine/bert_output.jpg))
- $$
- \text{score}(D, Q) = \sum_{n=1}^{N} \text{IDF}(q_n) \cdot \left(\frac{f(q_n, D)}{k_1 + 1} + \frac{k_1 (1 - b + b \cdot \frac{\text{length}(D)}{\text{avgDL}})}{f(q_n, D) + k_1}\right)
- $$
- 其中,f函数则是词在文档中的频率。
-2. 稠密检索。核心思想是通过神经网络获得一段语料的、语意化的、潜层向量表示。不同模型产生的向量有长有短,进行相似度检索时,必须由相同的模型产生相同长度的向量进行检索。
-3. BM25可以产生稀疏向量用于检索,另一种方式便是使用神经网络。与BM25不同的是
- 1. 神经网络赋予单词权重时,参考了更多上下文信息。
- 2. 另一个不同点是分词的方式。神经网络不同的模型有不同分词器,但总体上,神经网络的词比传统检索系统分的更小。比如,清华大学 在神经网络中可能被分为4个令牌,而使用jieba之类的,会分为1个词。神经网络的词典大约在几万到十几万不等,但文本检索中的词典大小通常几十万。分词方式不同,使得神经网络产生的稀疏向量,比BM25的向量更稠密,检索执行效率更高。这类的代表有:splade、colbert、cocondenser当使用这类模型编码文档时,其输出一般是:
- ```
- {
- "indices": [19400,724,12,243687,17799,6,1635,71073,89595,32,97079,33731,35645,55088,38],
- "values": [0.2203369140625,0.259765625,0.0587158203125,0.24072265625,0.287841796875,0.039581298828125,0.085693359375,0.19970703125,0.30029296875,0.0345458984375,0.29638671875,0.1082763671875,0.2442626953125,0.1480712890625,0.04840087890625]
- }
- ```
- 该向量编码的内容是 RAG:"稠密 或 稀疏?混合才是版本答案!",由 BGE-M3 模型产生。这个稀疏向量中,indices是令牌的ID,values是该令牌的权重。若文档存在重复令牌,则取最大的权重。产生稀疏向量后,无需将所有的都存入数据库中,可以只存权重较高的前几百个即可。具体数值可以根据自身的业务特性实验。
+## bert embedding
+
+[大模型 RAG 基础:信息检索、文本向量化及 BGE-M3 embedding 实践](https://arthurchiao.art/blog/rag-basis-bge-zh/)信息检索的技术发展大致可分为三个阶段:
+1. 基于统计信息的关键字匹配(statistical keyword matching)
+ 1. 是一种 sparse embedding —— embedding 向量的大部分字段都是 0;
+2. 基于深度学习模型的上下文和语义理解,
+ 2. 属于 dense embedding —— embedding 向量的大部分字段都非零;基于 dense vector,用最近邻算法就能对给定的 query 进行检索,强大且语义准确。
+3. 所谓的“学习型”表示,组合上面两种的优点,称为 learned sparse embedding。
+ 1. 既有深度学习模型的上下文和语义理解能力;又具备稀疏表示的可解释性(interpretability of sparse representations)和低计算复杂度。
+ 2. 先通过 BERT 等深度学习模型生成 dense embedding;再引入额外的步骤对以上 dense embedding 进行稀疏化,得到一个 sparse embedding;代表算法:BGE-M3。
+
+以输入 "Milvus is a vector database built for scalable similarity search" 为例,BERT dense embedding 工作过程:
+
+![](/public/upload/machine/bert_dense_embedding.jpg)
+
+1. Tokenization
+ 1. 将输入文本转成 token 序列
+ 2. BERT 还会插入两个特殊的 token:[CLS] token 表示开始,[SEP] token 表示一个句子的结束。
+2. Embedding:使用 embedding matrix 将每个 token 转换为一个向量,详见 BERT 论文;
+ 1. tokenizer的下一步就是将token的one-hot编码转换成更dense的embedding编码。在ELMo(Embeddings from Language Model)之前的模型中,embedding模型很多是单独训练的,而ELMo之后则爆发了直接将embedding层和上面的语言模型层共同训练的浪潮。每个单词会定位这个表中的某一行,而这一行就是这个单词学习到的在嵌入空间的语义。
+
+ ![](/public/upload/machine/embedding_matrix.jpg)
+3. Encoding:这些向量通过多层 encoder,每层由 self-attention 和 feed-forward 神经网络组成
+ 1. 会根据所有其他 token 提供的上下文细化每个 token 的表示。
+4. Output:输出一系列最终的 embedding vectors。
+最终生成的 dense embedding 能够捕捉单个单词的含义及其在句子中的相互关系。
+
+BGE 是一系列 embedding 模型,扩展了 BERT 的能力。BGE-M3 是目前最新的一个,3 个 M 是强调的多个 multi- 能力:Multi-Functionality/Multi-Linguisticity/Multi-Granularity。BGE-M3 生成 learned sparse embedding 的过程
+
+![](/public/upload/machine/bge_m3_learned_sparse_embedding.jpg)
-PS: 也就是稀疏向量 可以用一个词表长度的[]来表示,也有其对应的稀疏表示形式。但是计算的时候,还是用[] 去计算的。
+1. 先走 BERT dense embedding 的流程,
+2. 最后加一个 linear 层,得到 learned sparse embedding。
+
+in M3-Embedding, the [CLS] embedding is used for dense retrieval, while embeddings from other tokens are used for sparse retrieval and multi-vector retrieval. PS:既生成了spare embedding,又生成了 dense embedding。我说呢,只用一个[CLS] 对应的emebedding 其它的不用太浪费了。
+
+## 向量匹配的几个问题
+
+BERT 严重依赖预训练数据集的领域知识(domain-specific knowledge), 预训练过程使 BERT 偏向于预训练数据的特征, 因此在领域外(Out-Of-Domain),例如没有见过的文本片段,表现就不行了。另一方面,尽管传统 sparse embedding 在词汇不匹配问题时虽然也存在挑战, 但在领域外信息检索中,它们的表现却优于 BERT。 这是因为在这类算法中,未识别的术语不是靠“学习”,而是单纯靠“匹配”。
+1. **问题在语义上与其答案并不相同**。因此直接将问题与原始知识库进行比较不会有成果。假设一位律师需要搜索数千份文件以寻找投资者欺诈的证据。问题“什么证据表明鲍勃犯了金融欺诈行为? ”与“鲍勃于 3 月 14 日购买了 XYZ 股票”在语义上基本上没有任何重叠(其中隐含 XYZ 是竞争对手,3 月 14 日是收益公告发布前一周)。PS:构建数据以进行同类比较。与问题→支持文档相比,问题→问题比较将显著提高性能。从实用角度来说,您可以要求 ChatGPT 为每个支持文档生成示例问题,并让人类专家对其进行整理。本质上,您将预先填充自己的 Stack Overflow。此外,用户反馈的问答对也可以用于问题重写或检索。
+2. 向量嵌入和余弦相似度是模糊的。向量在完全捕捉任何给定语句的语义内容方面存在固有缺陷。另一个微妙的缺陷是,余弦相似度不一定能产生精确的排名,因为它隐含地假设每个维度都是平等的。在实践中,使用余弦相似度的语义搜索往往在方向上是正确的,但本质上是模糊的。它可以很好地预测前 20 个结果,但通常要求它单独可靠地将最佳答案排在第一位是过分的要求。
+3. 在互联网上训练的嵌入模型不了解你的业务和领域。在专业的垂直领域,待检索的文档往往都是非常专业的表述,而用户的问题往往是非常不专业的白话表达。所以直接拿用户的query去检索,召回的效果就会比较差。Keyword LLM就是解决这其中GAP的。例如在ChatDoctor中会先让大模型基于用户的query生成一系列的关键词,然后再用关键词去知识库中做检索。ChatDoctor是直接用In-Context Learning的方式进行关键词的生成。我们也可以对大模型在这个任务上进行微调,训练一个专门根据用户问题生成关键词的大模型。这就是ChatLaw中的方案。
+
+ ![](/public/upload/machine/keyword_recall.jpg)
## 微调emebedding
+在专业数据领域上,嵌入模型的表现不如 BM25,但是微调可以大大提升效果。embedding 模型 可能从未见过你文档的内容,也许你的文档的相似词也没有经过训练。在一些专业领域,通用的向量模型可能无法很好的理解一些专有词汇,所以不能保证召回的内容就非常准确,不准确则导致LLM回答容易产生幻觉(简而言之就是胡说八道)。可以通过 Prompt 暗示 LLM 可能没有相关信息,则会大大减少 LLM 幻觉的问题,实现更好的拒答。
+1. [大模型应用中大部分人真正需要去关心的核心——Embedding](https://mp.weixin.qq.com/s/Uqt3H2CfD0sr4P5u169yng)
+2. [分享Embedding 模型微调的实现](https://mp.weixin.qq.com/s/1AzDW9Ubk9sWup2XJWbvlA) ,此外,原则上:embedding 所得向量长度越长越好,过长的向量也会造成 embedding 模型在训练中越难收敛。 [手工微调embedding模型,让RAG应用检索能力更强](https://mp.weixin.qq.com/s/DuxqXcpW5EyLI3lj4ZJXdQ) 未细读
+3. embedding训练过程本质上偏向于训练数据的特点,这使得它在为数据集中(训练时)未见过的文本片段生成有意义的 embeddings 时表现不佳,特别是在含有丰富特定领域术语的数据集中,这一限制尤为突出。微调有时行不通或者成本较高。微调需要访问一个中到大型的标注数据集,包括与目标领域相关的查询、正面和负面文档。此外,创建这样的数据集需要领域专家的专业知识,以确保数据质量,这一过程十分耗时且成本高昂。而bm25等往往是精确匹配的,信息检索时面临词汇不匹配问题(查询与相关文档之间通常缺乏术语重叠)。幸运的是,出现了新的解决方法:学习得到的稀疏 embedding。通过优先处理关键文本元素,同时舍弃不必要的细节,学习得到的稀疏 embedding 完美平衡了捕获相关信息与避免过拟合两个方面,从而增强了它们在各种检索任务中的应用价值。支持稀疏向量后,一个chunk 在vdb中最少包含: id、text、稠密向量、稀疏向量等4个字段。
+
通常,**Embedding模型是通过对比学习来训练的**,而负样本的质量对模型性能至关重要。Language Embedding模型训练通常采用多阶段方案,分为弱监督的预训练以及有监督的精调训练。
1. 微调样本构建
2. 微调脚本
@@ -69,6 +101,12 @@ PS:文搜图时,CLIP模型的核心思想是通过对比学习(contrastive
2. Q-A(question-answer):这种方式比较有误导性,看起来感觉最应该用这种方式构建,但实际上线后,要检索的,是一堆documents,而不是answer,如果你真的用这个方式构建过样本,看一些case就会发现,answer跟实际的文档相差非常远,导致模型微调后,性能反而出现下降
3. Q-D(question-document):这种方式,在几个项目中实践下来,基本上是最适合的构建方式,因为**实际检索时,就是拿问题去检索文档,确保训练、推理时任务的一致性,也是减少模型性能损失最主要的一个方法**。
+样本数据格式示例
+```
+{"query": str, "pos": List[str], "neg":List[str]}
+```
+微调目的是让正样本和负样本的分数差变大。
+
### 训练过程
[探索更强中文Embedding模型:Conan-Embedding](https://mp.weixin.qq.com/s/5upU8Yf-6Bcn0kfxk7-V2Q) 不是特别直白。
@@ -92,9 +130,53 @@ PS:文搜图时,CLIP模型的核心思想是通过对比学习(contrastive
### 微调脚本
+1. llamaindex 有相关实践
+2. torchrun FlagEmbedding
+3. sentence-transformers
+
+## FlagEmbedding 微调
+
此处原始参考文档来自BGE官方仓库:
https://github.com/FlagOpen/FlagEmbedding/tree/master/examples/finetune
+### 准备微调样本
+
+1. 切分文档 得到chunk
+2. 使用llm依据chunk来生成对应query正样本,prompt 借鉴了 llama_index的generate_qa_embedding_pairs
+ ```
+ DEFAULT_QA_GENERATE_PROMPT_TMPL = """\
+ Given the context below, your task is to create a set of {num_questions_per_chunk} diverse questions that a human might ask, each within 20 words. \
+ Restrict the questions to the provided context.\
+
+ ---------------------
+ {context_str}
+ ---------------------
+
+ Try to cover various aspects and perspectives in your questions. \
+ Be concise and make sure the questions are relevant to the context. \
+ Generate questions based on the provided information directly, nothing else. \
+ """
+ ```
+3. 如何从一个questions set里挑出最合适的一个,这里考虑做二次生成,把之前生成的作为examples放在prompt里面。思路类似于COT,先生成几个,再基于这几个再生成一个。
+ ```
+ """
+ # Context:
+ {context}
+
+ # Generate a relevant and coherent question based on the information provided.
+
+ # Example:
+ {questions}
+
+ # Generate a question directly.
+ # Question:
+ """
+ ```
+4. 生成数据清洗
+5. 生成负例,主要逻辑就是针对每一个正样例,将所有的chunk按照embedding相似度排序,10-100的chunk中随机7条作为负样例(如果每次embedding匹配取top10的话)。PS: 比如一共有100个chunk,每个chunk 生成了xx个query,选用一个query,再根据query和100个chunk的embedding相似度排序,取10名开外的7条作为负例。
+
+### 微调emebedding
+
构造好微调样本后,就可以开始微调模型了。代码仓库中包含了4个版本的微调脚本,总体大同小异,此处以finetune_bge_embedding_v4.sh为例
```sh
@@ -146,7 +228,37 @@ embeddings = HuggingFaceBgeEmbeddings(
)
```
+### 模型评估(未完成)
+
+https://github.com/FlagOpen/FlagEmbedding/tree/master/examples/finetune
+
## 其它embedding 优化
[告别传统的文档切块!JinaAI提出Late Chunking技巧](https://mp.weixin.qq.com/s/BxJNcsfbwZpbG4boU6vjiw)对于传统的分块,类似于固定长度的分块。带来的一个比较大的问题是,上下文缺失。比如一个句子的主语在段落开头,后面的段落/句子中,有一些代词比如 It's, The city等等来表示主语。这种情况下确实主语的句子基本上就变得比较断章取义了~与先分块后向量化不同,JinaAI最新提出的“Late Chunking”方法是一个相反的步骤,首先将整个文本或尽可能多的文本输入到嵌入模型中。在输出层会为每个token生成一个向量表示,其中包含整个文本的文本信息。然后我们可以按照需要的块大小对对向量进行聚合得到每个chunk的embedding。这样的优势是,充分利用长上下文模型的优势,同时又不会让每个块的信息过多,干扰向量表征。
+
+## 其它
+
+### 稀疏向量
+
+稀疏向量 也是对chunk 算一个稀疏向量存起来,对query 算一个稀疏向量,然后对稀疏向量计算一个向量距离来评价相似度。
+1. 关键词检索。BM25是产生稀疏向量的一种方式。其稀疏性体现在,假设使用 jieba(中文分词库)分词,每个词用一个浮点数来表示其统计意义,那么,对于一份文档,就可以用 349047 长度的向量来表示。这个长度是词典的大小,对于某文档,该向量绝大部分位置是0,因此是稀疏向量。基于统计的BM25检索有用,但其对上下文不关心,这也阻碍了其检索准确性。假设有份文档,介绍的是 阿司匹林 的作用,但这篇文档仅标题用了 阿司匹林 这个词,但后续都用 A药 来代指 阿司匹林,这样的表述会导致 阿司匹林 这个词在该文档中的重要程度降低。
+
+ $$
+ \text{score}(D, Q) = \sum_{n=1}^{N} \text{IDF}(q_n) \cdot \left(\frac{f(q_n, D)}{k_1 + 1} + \frac{k_1 (1 - b + b \cdot \frac{\text{length}(D)}{\text{avgDL}})}{f(q_n, D) + k_1}\right)
+ $$
+ 其中,f函数则是词在文档中的频率。
+2. 稠密检索。核心思想是通过神经网络获得一段语料的、语意化的、潜层向量表示。不同模型产生的向量有长有短,进行相似度检索时,必须由相同的模型产生相同长度的向量进行检索。
+3. BM25可以产生稀疏向量用于检索,另一种方式便是使用神经网络。与BM25不同的是
+ 1. 神经网络赋予单词权重时,参考了更多上下文信息。
+ 2. 另一个不同点是分词的方式。神经网络不同的模型有不同分词器,但总体上,神经网络的词比传统检索系统分的更小。比如,清华大学 在神经网络中可能被分为4个令牌,而使用jieba之类的,会分为1个词。神经网络的词典大约在几万到十几万不等,但文本检索中的词典大小通常几十万。分词方式不同,使得神经网络产生的稀疏向量,比BM25的向量更稠密,检索执行效率更高。这类的代表有:splade、colbert、cocondenser当使用这类模型编码文档时,其输出一般是:
+ ```
+ {
+ "indices": [19400,724,12,243687,17799,6,1635,71073,89595,32,97079,33731,35645,55088,38],
+ "values": [0.2203369140625,0.259765625,0.0587158203125,0.24072265625,0.287841796875,0.039581298828125,0.085693359375,0.19970703125,0.30029296875,0.0345458984375,0.29638671875,0.1082763671875,0.2442626953125,0.1480712890625,0.04840087890625]
+ }
+ ```
+ 该向量编码的内容是 RAG:"稠密 或 稀疏?混合才是版本答案!",由 BGE-M3 模型产生。这个稀疏向量中,indices是令牌的ID,values是该令牌的权重。若文档存在重复令牌,则取最大的权重。产生稀疏向量后,无需将所有的都存入数据库中,可以只存权重较高的前几百个即可。具体数值可以根据自身的业务特性实验。
+
+
+PS: 也就是稀疏向量 可以用一个词表长度的[]来表示,也有其对应的稀疏表示形式。但是计算的时候,还是用[] 去计算的。
\ No newline at end of file
diff --git a/_posts/MachineLearning/LLM/2024-08-17-llm_pre_training.md b/_posts/MachineLearning/LLM/2024-08-17-llm_pre_training.md
index 0dfbd01d..d3715bd6 100644
--- a/_posts/MachineLearning/LLM/2024-08-17-llm_pre_training.md
+++ b/_posts/MachineLearning/LLM/2024-08-17-llm_pre_training.md
@@ -19,7 +19,7 @@ keywords: llm pretrain
1. 首先就是不断扩大模型和数据规模(Scaling Law)。
2. 一个是越来越强调数据质量的作用,各种数据筛选方法和工具越来越多,保证质量是第一位的
3. 不断增加数学、逻辑、代码这种能够提升大模型理性能力的数据配比比例,包括在预训练阶段(增加预训练数据此类数据比例,且在预训练后面阶段来上采样此类数据,就是说**同样数据多执行几遍,以增加其对模型参数影响的权重**)和Post-Training阶段(增加此类数据占比,Llama3的经过instruct的模型比仅做预训练模型相比,各种尺寸的效果提升都很大)皆是如此。
-目前看,在通用数据快被用完情况下,第三个因素会成为之后大模型进步的主导力量,包括使用数学、逻辑、代码合成数据在Post-Training阶段的应用,目前技术也越来越成熟,其质量和数量会是决定未来大模型效果差异的最关键因素。PS:合成数据其实是模型蒸馏的一种变体,合成数据是更大的模型输出数据作为Teacher,小点的模型作为Student从中学习知识,所以其实本质上是一种模型蒸馏。
+目前看,在通用数据快被用完情况下,第三个因素会成为之后大模型进步的主导力量,包括使用数学、逻辑、代码合成数据在Post-Training阶段的应用,目前技术也越来越成熟,其质量和数量会是决定未来大模型效果差异的最关键因素。PS:合成数据其实是模型蒸馏的一种变体,合成数据是更大的模型输出数据作为Teacher,小点的模型作为Student从中学习知识,所以其实本质上是一种模型蒸
自研 pretrain 模型的意义又有哪些呢?
1. 各公司仅仅是开源了模型参数,但并没有开源训练框架、训练数据等更核心的内容,其实本质上还是闭源。在这种情况下,每一个 qwen 模型的使用者都无法为下一版 qwen 模型的迭代做出贡献,qwen 团队也仅仅是收获了口碑,甚至因为自己的模型已经开源可以自行部署,买他们服务的客户可能都会变少。因此,在 llm 真正走向全面开源之前(大厂公开训练代码、配比数据,任何人都可以通过提 CR 来帮助大厂优化训练效率、炼丹技巧),掌握 pretrain 的技术能力依然是有意义的;
diff --git a/_posts/MachineLearning/LLM/2024-10-10-rerank_finetune.md b/_posts/MachineLearning/LLM/2024-10-10-rerank_finetune.md
new file mode 100644
index 00000000..e15de8bb
--- /dev/null
+++ b/_posts/MachineLearning/LLM/2024-10-10-rerank_finetune.md
@@ -0,0 +1,29 @@
+---
+
+layout: post
+title: rerank微调
+category: 架构
+tags: MachineLearning
+keywords: llm emebedding
+
+---
+
+
+
+* TOC
+{:toc}
+
+## 简介(未完成)
+
+
+## 为什么用rerank
+
+是使用elasticsearch的retrieval召回的内容相关度有问题,多数情况下score最高的chunk相关度没问题,但是top2-5的相关度就很随机了,这是最影响最终结果的。我们看了elasticsearch的相似度算法,es用的是KNN算法(开始我们以为是暴力搜索),但仔细看了一下,在es8的相似度检索中,用的其实是基于HNSW(分层的最小世界导航算法),HNSW是有能力在几毫秒内从数百万个数据点中找到最近邻的。为了检索的快速,HNSW算法会存在一些随机性,反映在实际召回结果中,最大的影响就是返回结果中top_K并不是我们最想要的,至少这K个文件的排名并不是我们认为的从高分到低分排序的。
+
+因为在搜索的时候存在随机性,这应该就是我们在RAG中第一次召回的结果往往不太满意的原因。但是这也没办法,如果你的索引有数百万甚至千万的级别,那你只能牺牲一些精确度,换回时间。这时候我们可以做的就是增加top_k的大小,比如从原来的10个,增加到30个。然后再使用更精确的算法来做rerank,使用一一计算打分的方式,做好排序。
+
+## 微调
+
+微调数据集格式为[query,正样本集合,负样本集合]。微调在Embeding模型与Reranker模型采用同类型数据集,并将语义相关性任务视为二分类任务,采用BCE作为损失函数。
+
+https://zhuanlan.zhihu.com/p/704562748 未细读
\ No newline at end of file
diff --git a/_posts/MachineLearning/LLM/2021-10-31-from_rnn_to_attention.md b/_posts/MachineLearning/Model/2021-10-31-from_rnn_to_attention.md
similarity index 100%
rename from _posts/MachineLearning/LLM/2021-10-31-from_rnn_to_attention.md
rename to _posts/MachineLearning/Model/2021-10-31-from_rnn_to_attention.md
diff --git a/_posts/MachineLearning/Model/2022-03-02-embedding.md b/_posts/MachineLearning/Model/2022-03-02-recsys_embedding.md
similarity index 89%
rename from _posts/MachineLearning/Model/2022-03-02-embedding.md
rename to _posts/MachineLearning/Model/2022-03-02-recsys_embedding.md
index 374d2db1..5384fa37 100644
--- a/_posts/MachineLearning/Model/2022-03-02-embedding.md
+++ b/_posts/MachineLearning/Model/2022-03-02-recsys_embedding.md
@@ -1,7 +1,7 @@
---
layout: post
-title: embedding的原理及实践
+title: 推荐系统embedding原理及实践
category: 架构
tags: MachineLearning
keywords: embedding
@@ -25,9 +25,8 @@ Embedding层是神经网络中的一层,用于将离散的符号(如单词
1. 把item_id当成特征,为什么有效?推荐算法的**传统机器学习时代**:博闻强记。推荐系统记住什么?能够记住的肯定是那些「常见、高频」的模式。到了春节,来了中国人,电商网站给他推饺子,大概率能够购买,到了感恩节,来了美国人,电商网站给他推火鸡,大概率也能购买。为什么?因为<春节,中国人,饺子>的模式、<感恩节、美国人、火鸡>的模式「在训练样本中出现得太多太多了,推荐系统只需要记得住」,下次遇到同样的场景,“照方扒抓药”,就能“药到病除”。如果user侧特征表明这个用户喜欢篮球,那么把“item_id = 某一款经典的耐克篮球鞋”,那两个信号一组合,效果岂不是再明显不过吗?所以把item_id当特征喂入模型,非常有必要,因为它是模型值得记住的。
2. 把item_id先embedding再喂入模型,为什么有效?如果让模型只牢牢记住``这个pattern就足够了吗?如果耐克新推出了一款篮球鞋,一个只会记忆的模型能够把这款新鞋推出去吗?答案是否定的,因为``由于在样本中出现次数少,根本不在模型的记忆中。而如果这时有了两款耐克鞋的embedding,理论上来讲,新款耐克鞋的item_id embedding应该与经典款耐克鞋embedding有几分相似(表现为向量空间距离近)。因为**基于embedding的模型已经由“精确匹配”进化为“模糊查找”**,模型会认为给“喜欢篮球的用户”推荐新款nike鞋,效果可能比推荐经典款差一些,但也差不多,值得一试。这就是引入item id embedding的意义。
-## 基本概念及原理
-### 推荐系统范畴
+## 基本概念及原理
一种表述:Embedding 是个英文术语,如果非要找一个中文翻译对照的话,我觉得“向量化”(Vectorize)最合适。Embedding 的过程,就是把数据集合映射到向量空间,进而**把数据进行向量化**的过程。Embedding 的目标,就是找到一组合适的向量,来刻画现有的数据集合。
1. 比如让国家作为模型参数,我们该如何用数字化的方式来表示它们呢?毕竟,模型只能消费数值,不能直接消费字符串。一种方法是把字符串转换为连续的整数,然后让模型去消费这些整数。。在理论上,这么做没有任何问题。但从模型的效果出发,整数的表达方式并不合理。为什么这么说呢?我们知道,连续整数之间,是存在比较关系的,比如 1 < 3,6 > 5,等等。但是原始的字符串之间,比如,国家并不存在大小关系,如果强行用 0 表示“中国”、用 1 表示“美国”,逻辑上就会出现“中国”<“美国”的悖论。**仅仅是把字符串转换为数字,转换得到的数值是不能直接喂给模型做训练**。
@@ -62,19 +61,6 @@ Embedding这块,spark MLlib 和 机器学习库 都提供了处理函数。利
在梯度下降这块对embedding weight也有针对性的优化算法,[从梯度下降到FTRL](https://zhuanlan.zhihu.com/p/144562494)FTRL是在广告/推荐领域会用到的优化方法,适用于对高维稀疏模型进行训练,获取稀疏解。
-### 大模型范畴
-
-tokenizer的下一步就是将token的one-hot编码转换成更dense的embedding编码。在ELMo(Embeddings from Language Model)之前的模型中,embedding模型很多是单独训练的,而ELMo之后则爆发了直接将embedding层和上面的语言模型层共同训练的浪潮。每个单词会定位这个表中的某一行,而这一行就是这个单词学习到的在嵌入空间的语义。
-
-![](/public/upload/machine/embedding_matrix.jpg)
-
-[没有思考过 Embedding,不足以谈 AI](https://mp.weixin.qq.com/s/7kPxUj2TN2pF9sV06Pd13Q)计算的基础是数,而自然语言是文字,因此很容易想到要做的第一步是让文字数字化,为行文方便,我们将这个过程叫做编码。要设计编码的方法,自然需要思考的问题是:哪些性质是编码规则必须要满足的?
-1. 每一个词具有唯一量化值,不同词需要具有不同的量化值
-2. 词义相近词需要有"相近"的量化值;词义不相近的词量化值需要尽量“远离”。当性质二得到满足时,同义的句子在序列特征上会更加接近,这将有利于计算机而言更高效地理解共性、区分特性;反之则会给计算机制造非常多的困难。**难以捕捉同质内容之间的共性,就意味着模型需要更多的参数才能描述同等的信息量**,学习的过程显然困难也会更大。OpenAI 的 Jack Rae 在 Standford 的分享 中提到了一个很深刻的理解语言模型的视角:语言模型就是一个压缩器。所有的压缩,大抵都能被概括在以下框架内:提取共性,保留个性,过滤噪声。带着这个视角去看,就更加容易认识到性质二的必要性。不同词所编码的数值,是否基于词义本身的相似性形成高区分度的聚类,会直接影响到语言模型对于输入数据的压缩效率。因为词是离散分布的,而计算模型的输出 —— 除非只使用非常简单的运算并且约束参数的权重 —— 很难恰好落在定义好的量化值中。对于神经网络模型,每一个节点、每一层都必须是连续的,否则便无法计算梯度从而无法应用反向传播算法。**这两个事实放在一起可能会出现的情况是:词的量化值可以全部是整数,但是语言模型的输出不一定**。例如当模型输出 1.5,词表只定义了 1 和 2,这时该如何处理呢?我们会希望 1 和 2 都可以,甚至 3 可能也不会太离谱,因此 1 和 2 所代表的词在词义上最好有某种共性。当相近的词聚集到一起,推断出有效输出的概率就会更高。
-3. 词义的多维性。对于每一个词,我们可以表达为一组数,而非一个数;这样一来,就可以在不同的维度上定义远近,词与词之间复杂的关系便能在这一高维的空间中得到表达。
-
-图像可以有embedding,句子和段落也可以有 embedding —— 本质都是通过一组数来表达意义。段落的 embedding 可以作为基于语义搜索的高效索引,AI 绘画技术的背后,有着这两种 embedding 的互动 —— 未来如果有一个大一统的多模态模型,embedding 必然是其中的基石和桥梁 。
-
## 实践
《深度学习推荐系统实战》为什么深度学习的结构特点不利于稀疏特征向量的处理呢?
diff --git a/_posts/MachineLearning/LLM/2023-10-30-from_attention_to_transformer.md b/_posts/MachineLearning/Model/2023-10-30-from_attention_to_transformer.md
similarity index 99%
rename from _posts/MachineLearning/LLM/2023-10-30-from_attention_to_transformer.md
rename to _posts/MachineLearning/Model/2023-10-30-from_attention_to_transformer.md
index b074a68d..a2e66bed 100644
--- a/_posts/MachineLearning/LLM/2023-10-30-from_attention_to_transformer.md
+++ b/_posts/MachineLearning/Model/2023-10-30-from_attention_to_transformer.md
@@ -304,7 +304,7 @@ $$
### 编码视角
-NLP 神经网络模型的本质就是对输入文本进行编码,常规的做法是首先对句子进行分词,然后将每个词语 (token) 都转化为对应的词向量 (token embeddings),这样文本就转换为一个由词语向量组成的矩阵 $X=(x_1,x_2,...,x_n)$,其中$x_i$就表示第i个词语的词向量。
+NLP 神经网络模型的本质就是对输入文本进行编码,常规的做法是首先对句子进行分词,因为模型是无法直接处理文本的,只能处理数字,就跟ASCII码表、Unicode码表一样,计算机在处理文字时也是先将文字转成对应的字码(token),然后为每个字码编写一个对应的数字记录在表中(Tokenizer)。然后将每个数字都转化为对应的词向量 (token embeddings),这样文本就转换为一个由词语向量组成的矩阵 $X=(x_1,x_2,...,x_n)$,其中$x_i$就表示第i个词语的词向量。
以将$x_t$ 编码为 $y_t$ 的视角来理解(结合了上下文词表示)
1. RNN(例如 LSTM)的方案很简单,每一个词语$x_t$对应的编码结果$y_t$通过递归地计算得到:$y_t=f(y_{t-1},xt)$,RNN 本质是一个马尔科夫决策过程,难以学习到全局的结构信息;
diff --git a/_posts/MachineLearning/Model/2024-10-11-bert.md b/_posts/MachineLearning/Model/2024-10-11-bert.md
new file mode 100644
index 00000000..ce38f2b1
--- /dev/null
+++ b/_posts/MachineLearning/Model/2024-10-11-bert.md
@@ -0,0 +1,63 @@
+---
+
+layout: post
+title: bert
+category: 架构
+tags: MachineLearning
+keywords: gcn
+
+---
+
+## 简介(未完成)
+
+* TOC
+{:toc}
+
+BERT 是一个用 Transformers 作为特征抽取器的深度双向预训练语言理解模型。通过海量语料预训练,得到序列当前最全面的局部和全局特征表示。
+
+[论文](https://arxiv.org/abs/1810.04805v1)
+
+bert 名称来自 Bidirectional Encoder Representations from Transformers. Unlike recent language representation models, BERT is designed to pre-train deep bidirectional(相对gpt的单向来说,是双向的) representations by jointly conditioning on both left and right context in all layers. As a result, the pre-trained BERT representations can be fine-tuned with just one additional output layer to create state-of-the-art models for a wide range of tasks, such as question answering and language inference, without substantial task-specific architecture modifications. PS:ELMo 是基于rnn,在应用到下游任务时,还需要做一些模型结构的变动。有了一个训练好的bert之后,只需要再加一个额外的层,就可以适配各种任务。
+
+
+## 模型结构
+
+BERT的基础集成单元是Transformer的Encoder,BERT与Transformer 的编码方式一样。将固定长度的字符串作为输入,数据由下而上传递计算,每一层都用到了self attention,并通过前馈神经网络传递其结果,将其交给下一个编码器。
+
+![](/public/upload/machine/bert_model.jpg)
+
+模型输入
+
+![](/public/upload/machine/bert_input.jpg)
+
+输入的第一个字符为[CLS],在这里字符[CLS]表达的意思很简单 - Classification (分类)。
+
+模型输出
+
+![](/public/upload/machine/bert_output.jpg)
+
+每个位置返回的输出都是一个隐藏层大小的向量(基本版本BERT为768)。以文本分类为例,我们重点关注第一个位置上的输出(第一个位置是分类标识[CLS]) bert 希望它最后的输出代表整个序列的信息。该向量现在可以用作我们选择的分类器的输入,在论文中指出使用单层神经网络作为分类器就可以取得很好的效果。例子中只有垃圾邮件和非垃圾邮件,如果你有更多的label,你只需要增加输出神经元的个数即可,另外把最后的激活函数换成softmax即可。
+
+![](/public/upload/machine/bert_classify.jpg)
+
+## 训练方式
+
+![](/public/upload/machine/bert_masked.jpg)
+
+PS:训练时,自己知道自己mask 了哪个词,所以也是无监督了。
+
+## 应用
+
+BERT的论文为我们介绍了几种BERT可以处理的NLP任务:
+1. 短文本相似
+ ![](/public/upload/machine/bert_similarity.jpg)
+2. 文本分类
+3. QA机器人
+4. 语义标注
+5. 特征提取 ==> rag 里的emebedding
+
+PS:最后一层的输出 选用[cls] 对应的embedding 或多个emebedding 套个FFNN + softmax,二分类或多分类任务就都可以解决了。
+
+## 其它
+
+WordPiece 分词会切词根, 切词根的目的是,很多词根是复用的,这样能减少此表大小(以3w 左右的词典,不然英文单词不只3w)
\ No newline at end of file
diff --git a/_posts/Technology/Linux/2019-11-25-linux_observability.md b/_posts/Technology/Linux/2019-11-25-linux_observability.md
index af629c1c..80ee70b5 100644
--- a/_posts/Technology/Linux/2019-11-25-linux_observability.md
+++ b/_posts/Technology/Linux/2019-11-25-linux_observability.md
@@ -53,11 +53,9 @@ top 命令找到`%CPU` 排位最高的进程id=32080,进而找到对应的容
### CPU 利用率
-CPU 利用率就是 CPU 非空闲态运行的时间占比,比如,单核 CPU 1s 内非空闲态运行时间为 0.8s,那么它的 CPU 使用率就是 80%;双核 CPU 1s 内非空闲态运行时间分别为 0.4s 和 0.6s,那么,总体 CPU 使用率就是 `(0.4s + 0.6s) / (1s * 2) = 50%`
```
%Cpu(s): 38.1 us, 4.2 sy, 0.0 ni, 53.5 id, 2.3 wa, 0.0 hi, 1.9 si, 0.0 st
```
-
**上述比例加起来是100%**
1. 用户态时间统计。进程nice > 0 计入到ni,nice<=0 计入到us
1. us(user):表示 CPU 在**用户态运行的时间**百分比,通常用户态 CPU 高表示有应用程序比较繁忙。典型的用户态程序包括:数据库、Web 服务器等。
@@ -77,8 +75,8 @@ CPU 利用率就是 CPU 非空闲态运行的时间占比,比如,单核 CPU
3. 把上面两个思路结合下,在采样上把周期定的细一些,在计算上,把周期定的粗一些。我们引入采样周期的概念,例如每1ms采样一次,记录这个时刻的cpu瞬时利用率。但是在统计3s内cpu 利用率的时候,就把t1到t1+3s时间段的瞬时值加起来,然后取个平均值。
[Linux 中 CPU 利用率是如何算出来的?](https://mp.weixin.qq.com/s/40KWGKNBoa35s533YGWYIQ)
-1. top 命令是读取 `/proc/stat` 伪文件 cpu 各项利用率数据,而这个数据在内核中的是根据 kernel_cpustat(内核变量) 来汇总并输出的。
-2. Linux 内核每隔固定周期会发出 timer interrupt (IRQ 0),每次当时间中断到来的时候,都会调用 update_process_times 来更新系统时间。更新后的时间都存储在我们前面提到的 PerCPU 变量 kcpustat_cpu 中。PS:percpu不只有rq。
+1. top 命令是读取 `/proc/stat` 伪文件 cpu 各项利用率数据,而这个数据在内核中的是根据 kernel_cpustat(内核变量) 来汇总并输出的,记录的是某个时间点各个指标所占用的节拍数。linux 会将瞬时值都累加到某个数据上( `/proc/stat`),为计算百分比,将t2和t1 的数据值相减,然后除以流逝的时间,除以总核数,就是我们日常看到的cpu利用率数据了,不是100%准确。
+2. Linux 内核每隔固定周期会发出 timer interrupt (IRQ 0),每次当时间中断到来的时候,都会调用 update_process_times 来更新系统时间。更新后的时间都存储在我们前面提到的 PerCPU 变量 kcpustat_cpu 中。PS:percpu数据不只有rq。
### CPU 平均负载
@@ -103,11 +101,11 @@ CPU 使用率是单位时间内 CPU 繁忙程度的统计。而平均负载不
[Linux 中的负载高低和 CPU 开销并不完全对应](https://mp.weixin.qq.com/s/1Pl4tT_Nq-fEZrtRpILiig)
-### 如何排查用户态 CPU 使用率高?
+### 如何排查用户态 CPU利用率高?
[一文说清linux system load](https://mp.weixin.qq.com/s/DQrsdroZUAFvtZ9bdPiPTA)导致load 飙高的原因,说简单也简单,无非就是runnable 或者 uninterruptible 的task 增多了。但是说复杂也复杂,因为导致task进入uninterruptible状态的路径非常多(粗略统计,可能有400-500条路径)。PS:
1. 周期性飙高
-2. IO原因
+2. IO原因
3. 内存原因,比如task 在申请内存的时候,可能会触发内存回收,如果触发的是直接内存回收,那对性能的伤害很大。
3. 锁,比如采用mutex_lock进行并发控制的路径上,一旦有task 拿着lock 不释放,其他的task 就会以TASK_UNINTERRUPTIBLE的状态等待,也会引起load飙高。
5. user CPU,有些情况下load飙高是业务的正常表现,此时一般表现为user cpu 飙高
@@ -124,6 +122,10 @@ CPU 使用率是单位时间内 CPU 繁忙程度的统计。而平均负载不
如果是非 Java 应用,可以将 jstack 替换为 perf。 **生产系统推荐使用 APM 产品,比如阿里云的 ARMS,可以自动记录每类线程的 CPU 耗时和方法栈(并在后台展示),开箱即用,自动保留问题现场**
+### CPI或IPC
+
+IPC 全称 Instruction Per Cycle,指每个时钟周期内执行的指令数,CPI 全称 Cycle Per Instruction,指执行每条指令所需的时钟周期数,这一对指标互为倒数。一个编译好的程序在底层是由一个个机器指令组成的,每条指令处理起来复杂度不同,所以指令之间需要的cpu周期差别是比较大的。程序编译生成可执行文件后,执行哪些二进制指令基本上也就固定了。对指令耗时影响比较大的是数据访问位置在哪里,寄存器、L1、L2、L3、内存(顺序io、随机io、跨numa node访问)会越来越慢,假如程序局部性原理把握的不好,CPI指标就会偏高。此外,内核的调度配置也有影响,比如绑核vs 进程经常漂移。可以通过perf 来查看程序运行耗费的cycles和instructions。
+
### 如何限制cpu的使用
[CFS Bandwidth Control](https://www.kernel.org/doc/Documentation/scheduler/sched-bwc.txt)
diff --git a/_posts/Technology/Storage/2017-10-31-inside_mysql.md b/_posts/Technology/Storage/2017-10-31-inside_mysql.md
index 60a31c66..c9168598 100644
--- a/_posts/Technology/Storage/2017-10-31-inside_mysql.md
+++ b/_posts/Technology/Storage/2017-10-31-inside_mysql.md
@@ -22,6 +22,8 @@ keywords: mysql innodb
## 基本架构
+按照近些年流行的概念来讲,MySQL 是一个典型的存储计算分离的架构,MySQL Server 作为计算层,Storage Engine 作为存储层。
+
![](/public/upload/storage/mysql_architecture.png)
查询缓存:key 是查询的语句,value 是查询的结果。如果你的查询能够直接在这个缓存中找到 key,那么这个 value 就会被直接返回给客户端。**大多数情况下我会建议你不要使用查询缓存**,只要有对一个表的更新,这个表上所有的查询缓存都会被清空。对于更新压力大的数据库来说,查询缓存的命中率会非常低。除非你的业务就是有一张静态表,很长时间才会更新一次。MySQL 8.0 版本直接将查询缓存的整块功能删掉了。
diff --git a/_posts/Technology/Storage/2021-01-22-mysql_isolation.md b/_posts/Technology/Storage/2021-01-22-mysql_isolation.md
index 3fa8bd5f..02811da2 100644
--- a/_posts/Technology/Storage/2021-01-22-mysql_isolation.md
+++ b/_posts/Technology/Storage/2021-01-22-mysql_isolation.md
@@ -41,6 +41,47 @@ keywords: mysql transaction isolation mvcc
## 并发控制
+### 从数据结构来看
+
+[MySQL 是怎么做并发控制的?](https://mp.weixin.qq.com/s/EjKtAj9H6KpuRlWAfoLYSA) 经典,**从代码层级给出了mysql 各层级加锁过程**。
+按照近些年流行的概念来讲,MySQL 是一个典型的存储计算分离的架构,MySQL Server 作为计算层,Storage Engine 作为存储层。所以并发访问的控制也需要在计算层和存储层分别进行处理。从数据访问的角度,用户视角下,MySQL 的数据分为:表、行、列。MySQL 内部视角下则包括了:表、表空间(在 MySQL 8.0 中,默认情况下一个表独占一个表空间)、索引、B+tree、页、行、列等。MySQL 中的并发访问控制也是基于 MySQL 内部的数据结构来进行设计的,具体包括:
+1. 表级别的并发访问控制,包括 Server 层和 Engine 层上的表;**表锁主要保护的是表结构**,在 MySQL 8.0 版本中,表结构的保护都是由 MDL 锁完成;非 InnoDB 表(CSV 表)还会依赖 Server 层的表锁进行并发控制,InnoDB 表不需要 Server 层加表锁;
+2. 页级别的并发访问控制,包括 Index 和 Page 上的并发访问;**主要是为了保护 B+tree 的安全性**。
+ 1. InnoDB 引擎通过 B+tree 来保存数据,B+tree 中的每一个节点都是一个数据页(Page),页也是 InnoDB 中数据读写的最小单元,InnoDB 中默认的页大小为 16KB。
+ 2. 页级别的并发访问控制发生在 B+tree 的遍历过程,也就是 B+tree 的加锁过程;加锁的对象包括了 index 和 page;加锁的类型包括了 S,SX 和 X,其中 S 锁和 SX 锁不互斥;查询过程只加 S 锁;修改过程,根据修改的类型加锁过程有所区别。如果是页内的数据修改,走乐观更新的逻辑,只有被修改的叶子节点加 X 锁;如果是悲观更新的逻辑,index 和根节点要加 SX 锁,索引可能被修改的节点都要加 X 锁;
+3. 行级别的并发访问控制;**行锁主要是为了保护行记录的一致性**。
+ 1. 行锁并不只是行记录上的锁,行锁的类型包括了:记录锁(Rec Lock)、间隙锁(Gap Lock)、下键锁(Next-Key Lock)和插入意向锁(Insert Intention Lock);PS:不仅是保护行记录本身的安全读写,必要时还要阻断行记录前后的插入,所以锁类型搞了这么多。
+ 2. 行锁是按需创建的,如果是第一次插入,默认不加锁(隐式锁),只有出现冲突时才会升级为显式锁;
+ 3. 记录锁(Rec Lock)上只有 S 锁和 S 锁兼容;间隙锁(Gap Lock)上 S 锁和 X 锁可以兼容,X 锁和 X 锁也可以兼容;下键锁(Next-Key Lock)就是记录锁和间隙锁的组合,处理的时候也是分开的;插入意向锁(Insert Intention Lock)的产生一定是因为有其他事务持有个待插入间隙的间隙锁;
+ 4. 所有锁的释放都是在事务提交时,所以为了减少死锁的产生,建议事务尽快提交;
+
+B+tree 的加锁过程
+1. 假设现在需要访问的数据是 ID = 400 的行,那么 B+tree 上的访问路径如下图所示。B+tree 的加锁过程其实也是按照上述访问路径进行的。具体的步骤如下:
+ 1. 加 index 上的 S 锁;
+ 2. 加根节点上的 S 锁;
+ 3. 加非叶子节点上的 S 锁;
+ 4. 加叶子节点上的 S 锁;
+ 5. 释放 index 和所有非叶子节点上的 S 锁;
+ ![](/public/upload/storage/btree_read_node_lock.jpg)
+2. 如果是页上的乐观更新(或者是页内的插入),那么 B+tree 上加锁的过程如下图所示:
+ 1. 加 index 上的 S 锁;
+ 2. 加根节点上的 S 锁;
+ 3. 加非叶子节点上的 S 锁;
+ 4. 加叶子节点上的 X 锁;
+ 5. 释放 index 和所有非叶子节点上的 S 锁;
+ ![](/public/upload/storage/btree_update_node_lock.jpg)
+ 可以看到,如果是页内的修改,其实加锁的逻辑和读过程的加锁类似很像,只是最后在叶子节点上加锁的类型不一样。
+3. 如果更新的数据无法在页内完成,或者说修改动作会造成 B+tree 结构的变化(SMO, Structure Modify Operation),又应该如何进行加锁?InnDB 在执行数据更新操作时,会首先尝试使用乐观更新(MODIFY LEAF),如果乐观更新失败,那么会 进入到悲观更新(MODIFY TREE)的逻辑,悲观更新的加锁过程如下图所示:
+ 1. 加 index 上的 SX 锁;(5.7 版本引入了 SX 锁,SX 锁和 S 锁不互斥,所以此时还可以读)
+ 2. 根节点不加锁
+ 3. 非叶子节点上不加锁,但是会搜索所有经过的节点;
+ 4. 判断可能修改的非叶子节点加 X 锁,根节点加 SX 锁;
+ 5. 叶子节点,包括前后叶子节点加 X 锁;
+ ![](/public/upload/storage/btree_zs_node_lock.jpg)
+ 在后续 B+tree 遍历的过程中,只是先收集索引经过的节点,并没有直接上锁。只有到了要修改的叶子节点时,才会去判断哪些非叶子节点也可能会修改,从而加上 X 锁。所以在整个 SMO 期间,除了可能会被修改的叶子节点和非叶子节点加的是 X 锁之外,其他的节点都没有加锁(index 和根节点是 SX 锁),非修改节点上的读操作可以正常进行。但是一棵 B+tree 上同时只能有一个 SMO 操作。
+
+### 从手段来看
+
[浅析数据库并发控制](https://zhuanlan.zhihu.com/p/45339550)**实现事务隔离的机制,称之为并发控制**。并行执行的事务可以满足某一隔离性级别的正确性要求。要满足正确性要求就一定需要对事务的操作做冲突检测,对有冲突的事务进行延后或者丢弃。并发控制机制 根据检测冲突的时机不同可以简单分成三类:
1. 基于Lock:最悲观的实现,需要在操作开始前,甚至是事务开始前,对要访问的数据库对象加锁,对冲突操作Delay;
2. 基于Timestamp:乐观的实现,每个事务在开始时获得全局递增的时间戳,期望按照开始时的时间戳依次执行,在操作真正写数据的时候检查冲突并选择Delay或者Abort;
@@ -54,7 +95,7 @@ keywords: mysql transaction isolation mvcc
并发控制机制并不与具体的隔离级别绑定。无论是乐观悲观的选择,多版本的实现,读写锁,两阶段锁等各种并发控制的机制,归根接地都是在确定的隔离级别上尽可能的提高系统吞吐,可以说隔离级别选择决定上限,而并发控制实现决定下限。
-## 锁
+## 锁(主要指行级别的并发访问控制)
锁的类型
1. 锁,锁表、锁行; 读写锁、互斥锁。
@@ -149,8 +190,9 @@ mysql> commit;
```
其它细节:同一个sql 不同的隔离级别可能会加不同的锁;发生死锁后,innodb 会选择回滚undo 量最小的事务。
-## MVCC 为数据提供多个副本
+当出现死锁时,如果开启了 performance_schema,可以通过查询 performance_schema 下的 data_locks 表查看所等待关系,然后手动进行处理;如果实在不想分析锁等待的关系,可以把 data_locks 表中所有涉及的连接全部 kill;如有真的出现了死锁,在 MySQL 的错误日志中会打印出锁等待关系,可以通过锁等待关系进行分析,优化业务侧的写入逻辑;
+## MVCC 为数据提供多个副本
### 实现细节
diff --git a/public/upload/machine/bert_classify.jpg b/public/upload/machine/bert_classify.jpg
new file mode 100644
index 00000000..9eb06dd7
Binary files /dev/null and b/public/upload/machine/bert_classify.jpg differ
diff --git a/public/upload/machine/bert_dense_embedding.jpg b/public/upload/machine/bert_dense_embedding.jpg
new file mode 100644
index 00000000..f00eacee
Binary files /dev/null and b/public/upload/machine/bert_dense_embedding.jpg differ
diff --git a/public/upload/machine/bert_input.jpg b/public/upload/machine/bert_input.jpg
new file mode 100644
index 00000000..e9d3f07c
Binary files /dev/null and b/public/upload/machine/bert_input.jpg differ
diff --git a/public/upload/machine/bert_masked.jpg b/public/upload/machine/bert_masked.jpg
new file mode 100644
index 00000000..64157dd5
Binary files /dev/null and b/public/upload/machine/bert_masked.jpg differ
diff --git a/public/upload/machine/bert_model.jpg b/public/upload/machine/bert_model.jpg
new file mode 100644
index 00000000..cc973fe8
Binary files /dev/null and b/public/upload/machine/bert_model.jpg differ
diff --git a/public/upload/machine/bert_output.jpg b/public/upload/machine/bert_output.jpg
new file mode 100644
index 00000000..941bed29
Binary files /dev/null and b/public/upload/machine/bert_output.jpg differ
diff --git a/public/upload/machine/bert_similarity.jpg b/public/upload/machine/bert_similarity.jpg
new file mode 100644
index 00000000..b3c39a42
Binary files /dev/null and b/public/upload/machine/bert_similarity.jpg differ
diff --git a/public/upload/machine/bge_m3_learned_sparse_embedding.jpg b/public/upload/machine/bge_m3_learned_sparse_embedding.jpg
new file mode 100644
index 00000000..2ebf6066
Binary files /dev/null and b/public/upload/machine/bge_m3_learned_sparse_embedding.jpg differ
diff --git a/public/upload/storage/btree_read_node_lock.jpg b/public/upload/storage/btree_read_node_lock.jpg
new file mode 100644
index 00000000..51d289b1
Binary files /dev/null and b/public/upload/storage/btree_read_node_lock.jpg differ
diff --git a/public/upload/storage/btree_update_node_lock.jpg b/public/upload/storage/btree_update_node_lock.jpg
new file mode 100644
index 00000000..0f77434e
Binary files /dev/null and b/public/upload/storage/btree_update_node_lock.jpg differ
diff --git a/public/upload/storage/btree_zs_node_lock.jpg b/public/upload/storage/btree_zs_node_lock.jpg
new file mode 100644
index 00000000..89c9150b
Binary files /dev/null and b/public/upload/storage/btree_zs_node_lock.jpg differ