Showing Posts From
Llm
- 16 May, 2026
RAG系统工作流程的几个问题澄清
本文简单总结了RAG系统的整体工作流程和模块分解,并在其基础上解释了 RAG工作流程以及向量数据量存储与检索向量相关的几个问题。 RAG 系统的基本流程与基本模块组成 下图是一个典型的RAG系统的工作流程分解图: !78e0975c-3248-43c4-a2f6-100c321de106.png 从模块拆解的角度上,RAG系统主要包含有以下几个独立的模块: - Indexing (索引层):将非结构化文档清洗、基于语义拆解为不同长度的chunk、嵌入(Embedding,即转换为向量的操作)并存入向量数据库(Vector DB)。 - Retrieval (检索层):根据用户提问题的 Query 消息计算用户问题文本与向量数据库之间向量的余弦相似度,从向量数据库中召回 Top-K 个相关片段。 - Augmentation (增强层):将从向量数据库中召回的片段与原始的 Query 文本拼接,构建增强后的提示词文本(Prompt)。 - Generation (生成层):把增强后的提示词文本送入 LLM,由 LLM 根据增强后的上下文输出最终答案。 以上工作流程和模块拆解简单直接,本文后续部分对 RAG 系统在工作中的一些基本概念再进行澄清,以确保对整个系统工作的各个方面有更为清晰的理解。 向量数据库中存储向量的维度问题 如上图所示,加入知识库的文档,首先会根据语义被分割为不同长度的 chunk(也就是说,每个chunk包含有不同数量的token),那么如果每个chunk包含的token数量不同的话,他们对应的Embedding 向量的数据维度也就是不同的? _针对以上问题,实际的情况是:无论输入(包括用户输入问题的文本,以及RAG知识库中分解出来的文本Chunk)的文本是长是短(只要在模型的最大上下文窗口内),经过同一个 Embedding 模型处理后,其输出的向量维度(Dimension)永远是固定且相同的。_ 因此,即使两个 chunk 的长度不同,包含的 token 数量不等,经过 Embedding 模型转换以后,转换后的向量维度都是相同而且固定的,这个固定的向量维度都与 Embedding 模型所指定的 token 的维度相同。 在具体的转换流程中,无论是知识库文本的某个 Chunk A 还是 Query,经过模型降维和特征提取以后,输出都是一个 维度的浮点数数组(一维向量)。 也正因为如此,后续才能对两个维度完全相同的向量(即用户输入问题文本对应的向量,以及向量数据库中某个 chunk 对应的向量)进行相似度的计算。 那么,不同长度的文本是如何变成同样长度的向量的? 这是由于底层大语言模型(基于 Transformer 架构)在输出层使用了池化(Pooling)机制。具体实现中包含以下两个步骤: - Token 映射: 不同长度的文本首先被转为 Token 序列,每个 Token 在模型内部会映射为一个隐藏状态向量。此时是一个 的矩阵。N 为Token数量,D 为Token的维度。 - Pooling(降维聚合): 模型会通过某种策略将这个 的矩阵压缩成一个 的向量,代表整段文本的全局语义。这个 Pooling 操作常见的做法包括: - Mean Pooling(平均池化): 将所有 Token 的向量在每一列上求平均值。 - CLS Token: 提取输入序列最前面特殊的 [CLS] (Classification) Token 对应的向量,它被训练用来表征整句话的语义。 向量相似度的计算逻辑 如上所述,无论是用户输入问题的文本 prompt 还是保存在向量数据库中知识库文档文本段落对应的向量,其维度都是相同维度的 。在后续进行两个向量的相似度计算时,主要使用的逻辑是余弦相似度 (Cosine Similarity) 和 内积 (Dot Product)。 余弦相似度衡量的是两个向量在空间中的夹角,以向量 (Query)和向量 (Chunk)为例,它们的维度都是 : > 💡 在自然语言处理或音频检索中,特征向量的模长往往代表文本的长短或音频段的能量大小,而向量的方向才代表真正的语义或特征属性。因此使用以上的余弦相似度计算公式,可实现规模与抽象语义两者之间的的解耦。其中的分子部分 ()实际上就是两个向量之间的内积(Dot Product),用于衡量两个张量在各维度上的绝对重合度。而其中的分母部分 ()是一个标量,作为归一化因子,强制消除因张量范数(模长)的不同而带来的缩放误差。 两个相同维度的向量,经过以上公式计算以后,其结果的范围在 -1 到 1 之间。 - 结果值越接近 1,说明两个向量的夹角越小,向量在空间中接近重合,语义越相似,是 RAG 召回的完美目标。 - 结果值趋近于0,说明向量在空间中相互垂直,可以认为两个向量之间完全无关(即正交),两段文本讨论的是完全不同的主题,特征维度无交叉。 - 结果值接近于-1,说明向量在空间中是背道而驰,语义上是相反/对立的。在某些特定训练的 Embedding 空间中,这种情况代表语义的完全对立(如好与坏的极端)。 然后向量数据库基于这个公式(或其近似的高效算法如 HNSW)对向量数据库中保存的所有向量与用户提问问题的向量进行以上计算,召回计算结果中最匹配的 Top-K 个文档。 而在工程实践中,如果在保存到向量数据库中的向量在入库前,已经对所有的文档向量 进行了 归一化(也就是使其模长为 1),同时对查询向量 也做了相同的归一化操作,那么上面公式中的分母就变成了 1。此时,余弦相似度的计算就简化成了内积的计算: 向量数据库的检索流程 以上部分给出了通过两个向量的余弦相似度计算公式,找到与用户提问问题语义最接近向量的计算过程。但是,如果 RAG 数据库中所保存的内容很多,向量数据库中的记录数量很多的话,如果每次检索都要与所有的数据库进行执行余弦相似度计算的话,那么这个计算量是非常惊人的。 以下详细总结和解释一下在向量高速检索领域应用最主流的HNSW(Hierarchical Navigable Small World,分层可导航小世界)算法。 整体而言,这个所谓的HNSW算法采用了一种多层图(Graph)空间的方式来解决海量向量的计算和搜索问题,在这种多层图结构下,高层网络的节点稀疏,负责快速定位目标节点所在的大致区域,低层网络节点密集,负责执行精细的搜索并准确的定位到目标节点。这样的设计中,绝大多数节点之间并不直接相连,但任两点间只需很少的几步即可找到。 所谓的多层图结构,实际上就是把向量数据库中的每个向量在入库阶段放到不同的层上,高层节点包含有低层的连接信息,这样在进行向量检索的时候,先通过高层节点进行大致定位,然后通过找到的高层节点中的低层节点连接信息下沉到底层,逐步精准定位到距离最近的节点。 每个节点中包含的信息大致使用如下数据结构来进行描述: - Layer 2 的节点(即Max Level = 2): 包含 Layer 0 + Layer 1 + Layer 2 的连接信息。 - Layer 1 的节点(Max Level = 1): 包含 Layer 0 + Layer 1 的连接信息。 - Layer 0 的节点(Max Level = 0): 只包含 Layer 0 的连接信息。 在这种情况下,每次进行向量搜索的时候,先在 Layer 2 的向量列表中找到距离最近的向量 Node-Layer-2,然后拿到向量节点 Node-Layer-2 中保存的 layer_1_neighbors 列表,从中找到 Layer 1 中距离最近的向量 Node-Layer-1,再继续从 Node-Layer-1向量节点中拿到其对应的 layer_0_neighbors 列表,最终从这个列表中找到与目标向量最近距离的 Layer 0 向量。 因此,HNSW 算法的向量搜索流程大致就如下图所述,因为实际的RAG 工作流程中并不只是返回一条记录,而是要返回 Top K 条记录,因此在以上的检索流程中,往往还要在每一层增加一个备选列表,最终返回的结果就是前 K 个距离查询文本向量最近的向量列表: !HNSW_Architecture_Native.png 向量数据库的存储内容 接下来的问题是,对于 RAG 的向量数据库而言,其中保存了文本分割后所有 Chunk 对应的向量。那么在工程实践中,通常这些向量数据库中的每一条记录都保存了哪些信息呢? 通常情况下,一条标准的向量数据库记录至少包含有以下组件: - Primary Key (主键 ID):数据库记录的主键,跟关系型数据库的主键的功能类似,数据格式通常是 Int64 或 String (UUID),作为物理存储和内存索引映射的唯一凭证。 - Dense Vector (稠密向量):这是向量数据库存储记录的核心内容。其数据格式是一个浮点数数组(如 Float32, Float16 或 INT8),对应于各个文本 Chunk 的向量内容。向量数据的检索就是对这个稠密向量与用户输入 prompt 文本对应的向量进行的余弦相似度计算。 - Scalar Metadata (标量/元数据):用于保存伴随该向量存储的一些结构化属性,一般用做属性过滤(例如可以使用该属性区分当前的文本 Chunk 是哪个文本文件中的内容),数据类型通常为 Int, Varchar, JSON 等格式。 可以看到,以上向量数据库中的记录中并不包含原始的文本 Chunk 数据,那么这样的话,后面在进行向量搜索的时候,通过余弦相似度算法搜索到某个向量以后,它如何定位找到这个向量对应的 Chunk 文本以方便与用户输入问题 Prompt 文本进行拼接呢? 针对以上问题,最直接的做法就是:把文本 Chunk 的内容直接作为上述 Scalar Metadata 部分的一个特殊字段(如 VARCHAR 类型),将其与向量一起存储在向量数据库内部。这样后续检索到向量的同时,底层就可以直接把这个向量对应的 Chunk 文本字符串一起返回。 但是,以上方案存在的问题是,向量检索计算的本质是密集的浮点运算,它要求参与计算的特征数组必须驻留在 L3 Cache 或高速 RAM 中。纯文本内容的 Chunk 数据对于向量计算过程毫无用处,但如果把整个向量数据库的内容(包括以上提到的 Chunk 文本内容)一起加载在内存里,会导致严重的内存碎片和计算过程中的 Cache Miss。 因此,目前的主流向量数据库(如 FAISS、Qdrant)在进行向量存储的时候,只会存储 [Primary Key, Dense Vector]部分,而原始的文本 Chunk 内容、Metadata(如来源文档名、页码等)等海量文本信息,会存储在另外一个专门的 Key-Value 数据库(如 RocksDB, Redis)或文档数据库(如 MongoDB, Elasticsearch)中。此时,Primary Key 充当了连接两个异构数据库的外键(Foreign Key),等到向量检索计算找到向量以后,就可以通过 Primary Key 从另外一个数据库找到其对应的 Chunk 文本以及其他的 metadata 。
- 11 May, 2026
大模型交互中KV Cache的工作原理总结
本文通过描述Agent与大模型之间的交互流程,详细解释Input Token和Cached Token的区别,理解了这两者的区别,也就能够理解这两种token在算力需求和价格上的差异所在。 Input Token和Cached Token的区别 我们从各个大模型厂商购买Token额度的时候,看到有三种不同的价格:输入价格(缓存命中),输入价格(缓存未命中),输出价格。输入和输出Token的概念很好理解,但是这里的缓存命中和缓存未命中的区别是什么呢? !b8e61c06-b89d-4e35-ae1a-41f50eca9547.png 同样的,我们在使用各种Agent,例如Claude Code、Gemini Cli的时候,在结束本轮会话退出时,Agent都会输出本轮会话的输入Token、缓存Token以及输出Token的用量统计数据,那么这里的Input Tokens和Cache Reads之间的区别究竟是什么呢? !28145375-6db3-447c-a161-6ec65851c5d6.png 如果没有Cached Token的话? 首先简化问题,在不考虑任何缓存机制的情况下,Agent 与大模型之间的通信始终是绝对的无状态函数交互。大模型没有任何记忆,每次的交互和调用对它来说都是一次全新的过程。 在Agent(或者与大模型进行对话的Web页面)与LLM大模型之间进行会话的交互过程中,两种的角色分工如下: - Agent(智能体): 运行在本地机器或云服务器上的一段代码,是整个应用会话的业务控制中心,负责维护双方对话历史记录、管理工具(API 接口)以及控制执行流程。 - LLM(大模型): 云端的逻辑推理引擎。它只负责一件事:通过HTTP协议从Agent上接收一段纯文本输入,根据概率预测并输出下一段文本。 正是因为大模型在整个交互过程中没有任何记忆,所以在 Agent 与大模型之间进行会话的过程中,无论整个会话过程会持续多少轮,Agent在每次发起新一轮的会话时,都需要把所有的历史记录(Agent的问题以及大模型的答复)全部积累起来,发给大模型,这样大模型在收到以后才会对从头开始双方的会话记录理解的基础上,针对新的问题返回与之对应的答复。这样随着会话轮数的增加,Agent 发给大模型的历史会话记录自然会如滚雪球一般越来越大: !%E5%AE%8C%E7%BE%8E%E5%AF%B9%E9%BD%90%E7%89%88_%E4%B8%8A%E4%B8%8B%E6%96%87%E6%BB%9A%E9%9B%AA%E7%90%83%E6%95%88%E5%BA%94.png 这样就会带来两个问题: 问题一,带宽与 Payload 的体积爆炸问题: 每一轮交互,Agent 都要把几十 KB 甚至几 MB 的历史会话记录重新通过 HTTP 发送一遍。如果会话记录中包含有代码库或者PDF文件的话,经过很多轮的累计,其所需要的代码和内存空间是灾难性的。 问题二,算力极度浪费(滚雪球效应导致 Input Token 飙升)问题: LLM 本身是无状态的。在第 N 轮对话时,前 N-1 轮的文本 LLM 已经计算过 N-1 次了,但在没有缓存的情况下,它必须机械地再把这些文本的矩阵乘法重新算一次,这个算力就白白浪费了。 针对以上的这两个问题,就很有必要通过在 LLM 服务器端缓存KV Cache的方式来解决了。 KV Cache 如何解决问题? 目前主流的大语言模型都是 Transformer 架构,Transformer 架构中的内部计算流程细节可参考一文彻底搞懂Transformer模型的Encoder结构与计算流程 和一文彻底搞懂Transformer模型的Decoder结构与计算流程 两篇文章。 简单的总结,每个词对应的向量 Token 进入 Transformer 架构中进行注意力运算时,它的词向量 会分别乘以三个不同的权重矩阵 ,从而得到这个词在当前上下文语境中精准语义表达的的 向量。 当模型在工作的过程中要生成下一个词时,它会生成当前的 ,并使用这个当前的 向量,去和前面所有历史 Token 的 进行点积计算(算相关性分数);算完相关性分数后,再把所有历史 Token 的 按照分数加权求和。 在以上的这个注意力计算过程中,历史 Token 的 Q 是没有用到的,但它们的 K 和 V 则会在后面的 Token 生成中反复使用,而 KV Cache 要存的就是:_输入文本在经过每一层注意力运算时,通过矩阵乘法算出来的所有的_ _向量和_ _向量_。 这样下次新一轮会话进来的时候,历史会话中所有 token 的 KV 值就不需要计算了,直接从内存中读取即可。只需要计算新一轮会话内容的 KV 值,然后与历史会话的 KV cache 合并/拼接在一起即可。这样在 LLM 计算的过程中就少了计算历史会话文本所有 token 的这个环节。 通过下面的流程图来对KV Cache 缓存机制的运作流程进行解释: - 阶段一会对整个会话的完整上下文进行一次全量(总共1000个token)的注意力计算,计算完成后会把其结果的 KV Cache 保存起来。 - 阶段二在生成每个词的时候,就不再需要对上下文的文本内容进行全量注意力计算,只需要使用新的 向量与 KV Cache缓存的内容进行计算得到输出,然后把新生成词对应的 和 向量追加了 KV Cache 在后续环节中继续使用。 !LLM%E5%8F%8C%E9%98%B6%E6%AE%B5%E6%8E%A8%E7%90%86_Prefill%E4%B8%8EDecode.png 以上就是在大模型服务器端把 KV Cache 缓存起来能够极大程度上降低对 GPU 算力的需求和浪费。 隐式缓存问题和显式缓存问题 如上所述,通过在服务器端缓存 KV Cache 的方式确实是可以有效的解决算力浪费的问题(即以上的问题二)。但是需要注意的一点是,服务器的内存是有限的而且极其昂贵的资源,海量用户的海量请求需要在服务器端维持大量的缓存,这样也就意味着,这些缓存不可能一直保存,一定有一个时效性。 如果服务器端缓存的 KV Cache 的时间超过了其时间限制(例如Agent或者用户在当前的会话中超过1个小时没有新的请求),这个KV Cache就会被删除。那么,在这种情况(KV Cache超过时效被删除)下,用户又有新的请求发到大模型服务器的话,大模型会如何处理呢? 目前主流的大模型,针对以上问题有两种处理方案:隐式缓存和显式缓存。 1. 隐式缓存 在采用这种架构中,Agent与大模型之间的每一轮交互,Agent 都会老老实实地把所有的历史对话记录、System Prompt 加上最新的对话请求,全部打包成一个巨大的 JSON,通过 HTTP 发送给大模型。 当这个巨大的 JSON 到达云端的大模型服务器后,其 API 网关会快速计算这段文本的前缀哈希值(Hash)。 - 如果它通过这段前缀发现其内存中已经包含了历史会话的 KV Cache(即Cache命中的情况),大模型在后续的运算中就会直接跳过这部分文本的 Transformer 矩阵运算,只对尾部新增的请求内容进行计算。这种情况下只有新增请求部分会被计入Input Token。 - 而如果它通过这段前缀在内存中找不到这个历史会话记录的KV Cache(即Cache失效了),那么大模型就只好把这个 JSON 中包含的完整历史对话记录、System prompt以及最新的对话请求全部完整进行Transformer运算, 因此,在这种模式Cache命中的情况下,网络 I/O 的成本依然很高(因为每次会话仍然需要把完整的会话历史记录全部发上去),只是算力(GPU 的计算时间)被省下来了,所以大模型厂商会通过 Cache Token 的方式对大模型的使用费用打折,大模型计算的响应速度也变快了。 - 这种模式可以解决以上的问题二(算力浪费的问题),未能解决以上的问题一(网络带宽IO的问题)。 2. 显式缓存 另外一种模式则是显式缓存模式。在这种模式下,那些很厚重、不会频繁变动的静态上下文(比如整个项目的软硬件架构文档、长篇的 API 规范文档、技术手册、复杂的 System Prompt),Agent 只需要在最开始上传一次。服务器收到并计算完 KV Cache 后,会返回一个资源标识符(Cache ID)。 在后续 Agent 与大模型之间的反复对话中,Agent 的 JSON Payload 里不再需要包含那些厚重的静态上下文,而只需要填入一串字符串:"cached_content": "caches/12345-abcde",以及双方之间的会话记录即可。 大模型服务器收到 Agent 发过来的请求以后,首先使用请求包中的 Cache ID 对应的KV Cache是否仍然存在于内存中。如果缓存命中,则可以直接使用KV Cache,只对 Agent 发上来的会话记录进行 Transformer 计算,然后把 KV 值拼接起来,生成新的上下文。 但是如果 Cache ID 对应的KV Cache已经失效被删除,此时大模型服务器会直接返回一个 404 错误,并在附带信息中说明 Cached content not found。此时 Agent 就需要把本地缓存的静态上下文的所有资料重新发给大模型,针对所有的上下文重新进行一次密集的 Transformer 运算,然后返回一个新的 Cache ID 给 Agent。 因此,这种显式缓存的工作模式,可以同时解决问题一和问题二,只有在缓存失效的情况下,才需要把比较大的静态上下文发给大模型执行一次完整的Transformer运算。 !%E9%9A%90%E5%BC%8F%E7%BC%93%E5%AD%98vs%E6%98%BE%E5%BC%8F%E7%BC%93%E5%AD%98%E5%AF%B9%E6%AF%94.png
- 21 Mar, 2026
QWen3-VL-4B多模态大模型在RK3588上的适配流程记录
本文详细记录了在瑞芯微RK3588上进行Qwen3-VL-4B-Instruct多模态大模型的完整适配记录,以及在适配过程中容易出现问题的重点事项。 QWen3-VL-4B模型的下载与测试 阿里在 2025 年 9 月推出了Qwen3-VL 系列视觉语言模型。相较于其前代产品(如 Qwen2-VL),Qwen3-VL 的发布标志着多模态大模型从简单的视觉感知正式跨越到了深层的逻辑推理与自主 Agent 交互的新阶段。Qwen3-VL 延续了阿里的开源精神,推出了涵盖从端侧到云端的多种规模模型。 !image.png 要进行Qwen3-VL-4B-Instruct这个模型在瑞芯微平台上的适配,首先需要下载模型文件,并在PC端进行基本的推理测试,验证下载到的模型文件的正确性,这部分工作在详解QWen3-VL模型的测试以及vLLM生产环境部署流程一文中已有详细介绍。 Qwen3-VL-4B-Instruct模型在HuggingFace中的链接路径如下,按照上文使用transformers库从huggingface上下载并执行本地的推理验证: !image.png > 注:下文中我已经下载好的本地模型文件的路径为/mnt/d/HuggingFace/hub/models--Qwen--Qwen3-VL-4B-Instruct/snapshots/ebb281ec70b05090aa6165b016eac8ec08e71b17/ 环境的搭建 多模态大模型在瑞芯微平台NPU上的适配,需要同时用到瑞芯微的rknn-llm和rknn-toolkit2工具包。截至2026年1月份,这两个工具包在github上的最新版本是: - rknn-llm:1.2.3 - rknn-toolkit2:2.3.2 以下工作就基于这两个版本展开。比较奇葩的是,这两个工具包的最新版本,对于python环境的版本要求是相互冲突的。所以最好基于上面的两个工具包分别搭建各自的开发环境rknn和rkllm,在后续的不同步骤切换到不同的环境中执行。 rknn-toolkit2的环境搭建可以参考瑞芯微RK3588 NPU开发环境搭建笔记,rkllm的环境搭建流程可以参考轻量级LLM模型QWen2.5-0.5B在RK3588上的适配流程记录。 _整体来讲,QWen3-VL-4B-Instruct这个模型在RK3588开发板上的适配,主要是基于rknn-llm这个仓库中的__rknn-llm-main/examples/multimodal_model_demo__目录中所包含的文件展开。_ - 该目录下有一个README.md可以作为参考,但是其中缺少一些细节。 模型转换 针对QWen3-VL-4B-Instruct这类多模态模型的转换,实际上分为两个步骤,分别对应rknn模型和rkllm模型的转换。最终上板运行的demo,既需要同时加载独立的rknn模型文件和rkllm模型文件,也需要依赖于各自的运行时库:librkllmrt.so和librknnrt.so。 rknn模型的转换 这部分转换操作在rknn环境(即安装rknn-toolkit2工具包的环境中)执行。 这部分分为两个子步骤:模型的Vision部分转换为onnx文件,在从onnx格式的模型文件转换为上板运行的rknn模型文件。 在rknn-llm-main/examples/multimodal_model_demo目录下的export子目录中执行export_vision.py脚本转换模型的Vision部分到onnx文件中: 以上执行完成后会在当前export目录下生成一个onnx子目录,其中包含有转换好的模型Vision部分的onnx文件: 接下来,继续使用相同目录下的export_vision_rknn.py脚本把这个onnx文件转换为板端可运行的rknn模型文件: 以上执行完成后会在当前export目录下生成一个rknn子目录,其中包含有转换好的模型Vision部分的板端运行的rknn模型文件: 至此模型Vision部分的rknn上板运行的模型文件qwen3-vl_vision_rk3588.rknn就已经准备好了。 rkllm模型的转换 这部分转换操作在rkllm环境(即安装rknn-llm工具包的环境中)执行。 这部分同样可以分为两个子步骤:校准数据的准备,rkllm板端模型的转换。 校准数据准备的这部分工作主要在data子目录中进行,该目录下包含一个make_input_embeds_for_quantize.py脚本用于准备校准数据,但是这个脚本是针对Qwen2-VL的,直接在Qwen3-VL模型上使用会报错,所以我基于上面的脚本文件修改了一个make_input_embeds_for_quantize_qwen3-vl.py专门用于生成Qwen3-VL模型的校准数据,然后在export下执行: 以上执行完成后会在data/inputs.json和data/inputs_embeds/生成校准数据,后续rkllm板端模型的转换过程要用到这个校准数据。 接着就是模型转换的最后一步,也是最耗时的一步(主要原因是我的显卡型号比较新,与rkllm环境中的torch和cuda版本有冲突,所以只能使用CPU来执行模型转换):rkllm板端模型文件的转换。主要用到export下的export_rkllm.py脚本: 注意,以上脚本执行的过程中对内存需求巨大,如果在WSL中执行的话会在运行在出现Killed的问题,原因就是内存OOM了,此时需要修改WSL的内存和虚拟内存的配置:在自己的用户目录C:\Users[用户名]下创建一个.wslconfig文件,写入以下内容然后重启WSL,然后重新执行以上转换命令即可: 以上执行完成后会在当前目录下生成rkllm的板端执行文件_w8a8_rk3588.rkllm: 至此,板端推理demo运行所需要的两个板端模型文件qwen3-vl_vision_rk3588.rknn和_w8a8_rk3588.rkllm就准备好了。 板端推理应用程序编译 板端通用的推理应用程序代码在deploy目录下,需要注意RKLLM v1.2.3版本要求交叉编译的gcc版本在10.2以上,需要先下载工具链gcc-arm-10.2-2020.11-x86_64-aarch64-none-linux-gnu: !image.png 在deploy目录下,修改其中的build-linux.sh文件中的交叉编译工具链路径GCC_COMPILER,然后执行build-linux.sh脚本编译板端执行文件即可。这个编译过程完成后会把所有上板运行的文件(除以上两个板端模型文件以外)放入一个install目录下。 上板运行测试 上板运行测试的过程,把以上的install目录、两个板端模型文件qwen3-vl_vision_rk3588.rknn和_w8a8_rk3588.rkllm上传到板子的models目录下上。然后执行: 最终demo在rk3588上运行的效果如下图所示: !image.png 参考资料 - airockchip/rknn-llm - airockchip/rknn-toolkit2 - 【硬核部署】Qwen3-VL 在 RK3588 上的部署-CSDN博客 --- 尝试从底层原理的角度去理解和解释技术问题:音视频/摄像头/智能家居/蓝牙/WiFi/无线通信/AI。 敬请关注微信公众号:Pavel Han。
- 16 Mar, 2026
轻量级LLM模型QWen2.5-0.5B在RK3588上的适配流程记录
本文详细学习和总结基于瑞芯微的RKLLM工具及其官方的相关指导文档,把轻量级大语言模型Qwen2.5-0.5B适配到RK3588上的完整过程及其注意事项记录。 总体而言,在瑞芯微NPU上部署 LLM 和部署 YOLO 的核心逻辑与流程是一致的,都是:PC端转换模型 -> 推送模型和库到板端 -> 调用 API 运行。 但是,对于大语言模型的适配而言,不再使用 RKNN-Toolkit2 工具链,而是使用专门为大语言模型优化的 RKLLM-Toolkit 工具链。 - RKNN-Toolkit2: 处理卷积神经网络(CNN)和简单的 Transformer(Vision Transformer)。 - RKLLM-Toolkit: 专门处理大语言模型,支持权重量化(W4A16/W8A8)和 KV Cache 优化。 下图是瑞芯微RKLLM Toolkit及其软硬应用生态的框架图,与RKNN类似: !image.png - RKLLM-Toolkit:用于在PC上进行模型转换和量化。 - RKLLM Runtime:为瑞芯微NPU平台提供C/C++编程接口,帮助用户在NPU上部署RKLLM模型,加速LLM应用的运行。 - RKNPU内核驱动:负责与NPU硬件交互,可以在Rockchip的内核代码中找到。 环境搭建 从rkllm的Github仓库(airockchip/rknn-llm)上下载rkllm代码。 PC端环境 创建一个独立的rkllm环境,并进入rknn-llm仓库代码中的rkllm-toolkit/packages目录下,安装rkllm-toolkit及其依赖包: 板端环境 对于LLM大模型在瑞芯微NPU上的运行,在板端主要需要NPU驱动以及librkllmrt.so链接库(在rkllm-runtime/Linux/librkllm_api/aarch64下)的支持。 NPU驱动要求在v0.9.8版本以上,可以通过以下命令检查自己板端的驱动是否符合要求,否则就需要重新编译内核: 下载模型文件并在PC上推理测试 使用transformers从Huggingface上下载Qwen2.5-0.5B模型的模型文件,并在PC本机上执行推理测试,验证下载文件的正确性: HuggingFace下载速度慢的问题可以通过设置国内镜像来解决,在执行以上下载脚本之前设置环境变量: 下载后的模型文件列表如下图所示: !image.png 模型转换 基于环境搭建部分安装的rkllm-toolkit包,调用以下代码完成模型的转换: > 注意:在量化类型的支持上,RK3588不支持w4a16, 可支持w8a8, w8a8_g128, w8a8_g256, w8a8_g512这四种。 以上模型转换结束后会在当前目录下生成一个qwen2_5_0_5b_w8a8.rkllm文件,大小800M的样子。 板端编译与推理 以上的板端模型转换完成以后,接下来就是准备板端的推理代码: 对于板端推理代码的交叉编译,可以按照常规的开发方法,在PC上编译好以后上传到板子上执行。但是我使用的1.2.3版本的RKLLM-Toolkit,在文档中明确推荐的交叉编译工具版本是GCC10.2,而我PC上的瑞芯微平台的编译工具链是gcc-linaro-6.3.1-2017.05-x86_64_aarch64-linux-gnu。所幸我用的瑞芯微RK3588板子上运行的Debian系统有满足条件的编译工具: 所以这样就比较简单了,可以直接把板端推理代码的源文件(main.cpp)、头文件(rkllm.h)、链接库(librkllmrt.so)以及前面转换好的板端模型上传到板子上,直接在板子的debian系统中使用g++进行编译: 执行后的打印信息如下: 可以看到,这个模型在RK3588上已经跑起来了。但是0.5B规模的大语言模型所生成的内容还是存在比较大的问题,不仅轻松的解决了西咸一体化这个困扰西安发展30年的痼疾,而且明显把榆林的资料张冠李戴到了咸阳头上。 推理过程中的内存占用上,这个模型只用到了680MB的内存,而实际上光是板端转换后的模型文件就已经800MB了,这就说明瑞芯微RKLLM在执行时并没有把完整的模型文件全部加载到内存中,而是按需加载,这样的话对于嵌入式设备这类内存非常紧张的应用还是非常实用的。 参考资料 - airockchip/rknn-llm - RKLLM SDK User Guide v1.2.3
- 09 Mar, 2026
一文彻底搞懂Transformer模型的Decoder结构与计算流程
本文详细总结了Transformer模型架构中Decoder部分的总体网络结构,以及数据在其中的运算和流转流程。 之前在一文彻底搞懂Transformer模型的Encoder结构与计算流程一文中,详细整理了Transformer模型的Encoder架构,本文继续学习和解析Transformer的Decoder的结构以及完整的计算流程。因为Decoder与Encoder的模型结构与计算流程非常类似,所以最好参考一文彻底搞懂Transformer模型的Encoder结构与计算流程以及如何理解Transformer架构中的多头注意力机制?把Transformer的自注意力概率与计算流程先搞清楚。 下图是一个完整的Transformer Encoder-Decoder的网络架构: - 左侧为连续N个Encoder Block组成的Transformer Encoder,用于实现把输入的自然语言文本转换为向量表示的语义信息。 - 右侧为连续N个Decoder Block组成的Transformer Decoder,用于基于Encoder输出的语义信息的向量表示来实现自然语言功能的输出,如机器翻译,文本续写等。 !image.png Decoder的输入和输出 Transformer的输入和输出结构如下图所示。 !image.png Decoder的输入包含两部分数据: - 下方输入(Outputs Shifted Right):用于执行掩码自注意力 (Masked Self-Attention)计算。其内容是模型已生成的 Token 序列,从而让模型知道“我已经说了什么”。 - 中间输入(来自Encoder提取的语义向量):用于执行交叉注意力 (Cross Attention)计算。其输入内容是基于 Encoder 的语义向量计算得到的 K 和 V 矩阵,让模型知道“源句子的意思是什么”。其计算逻辑是 Decoder 会用自己的 Q(我想找哪个语义)去跟 Encoder 的 K, V 进行匹配。 Decoder的输出为上图最上方的Output Probilities。输出内容的维度等于模型输出词表大小(Vocabulary Size)的向量。通过最后的 Linear 层和 Softmax 层处理之后,这个向量代表了模型输出词表中每一个单词作为“下一个输出单词”出现的概率分布。通常选取概率最大的那个索引(Index),然后去字典里查出对应的单词作为下一个输出单词。 Outputs shifted right输入与自回归预测 以上提到Decoder下方输入Outputs Shifted Right的内容是模型已生成的 Token 序列,这个在推理流程中要如何理解? 在初始状态下,模型是没有输出的,此时 Outputs Shifted Right 部分的输入内容就是一个起始信号 (Start of Sentence)。此时模型会根据 Encoder 的语义和起始信号,在Decoder端执行一次前向推理,预测生成第一个词(例如 Hello)。接下来,新生成的这个词 hello 就会被接入到decoder的输入端,此时decoder的输入序列就变成:[, Hello],模型继续基于这个新的输入序列执行前向推理,预测出第二个词 world,第二个词同样被接入到输入端,这样输入端的输入序列就变成 [, Hello, World],依此类推,直到最终模型预测输出的结果为 (End of Sentence,结束符),此时推理停止。 以上就是Decoder执行前向推理预测生成一个个单词的完整流程,因为每生成一个单词,这个单词都会被重新拼接到输入预测序列的最后,用于推理新的单词,这个过程也就是所谓的自回归预测过程。 Decoder Block的结构 Transformer 中的Decoder部分由多个相同的Decoder Block组成,其主要功能是根据Encoder所提供的语义表示(如上图中Encoder部分的输出)和先前已生成的输出标记(如上图中的Outputs Shifted Right部分)生成最终的输出序列。 !image.png 每个Decoder Block由三个主要子层组成: - Masked Multi-Head Attention:与编码器的自注意力机制类似,但其主要目的是防止关注未来的 token,以保持自回归属性(确保生成过程中不会作弊)。 - Multi-Head Attention:该子层允许解码器关注编码器输出表示的相关部分。这使得解码器能够专注于输入的相关部分,这对于翻译等任务至关重要。 - Feed Forward:前馈神经网络,该子层处理屏蔽自注意力和编码器-解码器注意力机制的组合输出。 从网络结构和推理的计算流程上,Multi-Head Attention多头注意力模块与Feed Forward前馈模块与Encoder的相应模块没有差异,主要的区别在于Masked Multi-Head Attention模块以及Encoder输出作为Decoder输入的交叉注意力机制上。 Masked Multi-Head Attention 从工程实现的角度看,Masked Self-Attention(掩码自注意力)和 普通的 Self-Attention 在代码结构以及计算流程上 90% 是相同的。唯一的差异只是两个注意力的视野范围不同: - 普通的 Self-Attention (例如Encoder 使用自注意力模型):该模块对一段文本进行语义提取的时侯,可以同时看到从开头到结尾的所有词,因此可以根据全句的语境来理解每一个词,并提取其语义,因此普通的Self-Attention模块的视野范围是全局的。 - Masked Self-Attention (Decoder 使用):在使用掩膜自注意力模块的时候,当前词的输出只能参考之前已经输出的词,不能参考尚未在当前词位置后边的词。也就是说,掩膜自注意力模块的视野氛围是历史视角,即只能看到之前的词。 Masked Self-Attention在计算中的历史视角这一点,对于训练过程非常关键。 因为在训练阶段,训练样本中包含有针对某个问题的完整的正确答案序列。为了提高效率,训练过程的执行,不是像推理解码那样一个词一个词地跑 N 遍模型,而是把整个序列一次性输入,然后执行一次矩阵运算,通过一次推理得到答案序列。这样的话,如果采用普通的自注意力模块就会存在一个问题,在解码器生成文本的过程中,位置在前面的词会看到后面的词,这样的话,后面位置的词就会对前面位置的词产生影响,相当于模型在训练的过程中直接把答案看到了,这当然是模型训练不希望看到的情况。 此时,就可以在执行注意力的矩阵运算的过程中,如下图所示,通过一个三角掩码矩阵,在数学上强制让第 t 个词只能看到第 1 到第 t 个词,无法看到后面的词。这样就既实现了并行化训练,同时又完美模拟了无法看到未来的因果律。 !image.png 相应的,Masked Self-Attention模块的计算公式就变成了: 其中,M 是一个下三角矩阵。其逻辑如下: - 对于所有 j > i 的位置(即当前位置之后的词),将 设为 。 - 对于所有 j ≤ i 的位置(即当前和之前的词),将 设为 0。 > 注意:在正常的前向推理过程中,模型实际上是执行一次前向推理生成一个token,在这种情况下,每次执行前向推理时,通过outputs shift right传递给decoder模块的文本序列自然就不存在未来的token(因为这个时候未来的token还没有生成),这样的话,这个掩码机制其实根本就没必要了。只不过因为训练的过程中,Transformer中的参数都是在有掩码的情况下生成的,所以如果在前向推理的过程中,把这个掩膜自注意力模块去掉,可能会导致参数数值分布的细微偏移,从而影响预测的准确性。所以,即使前向推理推理过程实际上已经不再需要掩膜机制,仍然需要保留这个掩膜自注意力模块机制,确保模型在推理阶段的输出与训练阶段输出的准确性基本一致。 Encoder的输出与交叉注意力 在Encoder的自注意力(Self-Attention)机制的实现中,Q、K、V 全部来自同一个输入。而对于Decoder的多头注意力模块而言,其输入来源于两个不同的来源,这就是所谓的交叉注意力机制: - Query (Q)来自于 Decoder 上一层的输出,实际上也就是掩膜注意力模块的输出。其语义内涵代表“我已经生成了这些词,我现在需要寻找什么样的上下文信息来翻译下一个词?” - Key (K) 和 Value (V)则来自于 Encoder 的最终输出。其语义内涵表示“原文(源语言)中每个词的特征和含义是什么?” 因此,对于交叉注意力机制,可以把它想象成 Decoder 持有问题(Q),去 Encoder 的知识库(K, V)中寻找答案。 如下图所示,基于Decoder前一模块的输出,与 Wq 矩阵运算得到 Queries;基于Encoder 的输出分别于 Wk 和Wv 矩阵运算得到Keys和Values,然后基于Queries、Keys和Values调用相同的注意力机制的计算流程(可参考如何理解Transformer架构中的多头注意力机制?),得到交叉注意力模块的计算结果。 !image.png Decoder部分的工作流程总结 最后再来总结一下整个Decoder部分的工作流程如下。 !image.png 首先是从outputs shifted right端输入模型推理输出的Token序列,这个token序列会使用与Encoder相同的预处理方式,对token进行向量化(Embedding)并增加位置编码信息。 第二步是掩膜自注意力模块的处理流程,其输入是叠加了位置编码信息的Token向量序列,这部分计算流程在前文的Masked Multi-Head Attention 已做了非常详细的解释。掩膜自注意力模块的输出通过标准的Add & Norm进行残差连接和层归一化。 第三步是交叉注意力模块的处理流程,这部分计算流程在前文的 Encoder的输出与交叉注意力 已做了详细的解释。同样,其输出通过标准的Add & Norm进行残差连接和层归一化。 第四步是由两层全连接神经网络所组成的前馈网络Feed Forward。其作用是在经过了两次注意力机制(掩膜自注意力和交叉注意力)后,对提取到的复杂特征进行非线性变换和进一步的维度提取。最后仍然通过 Add & Norm 进行残差连接和层归一化。 以上的第二步到第四步的处理流程构成了一个Decoder Block,每个Decoder Block的输入数据和输出数据的维度相同。整个Decoder 部分包含有N个Decoder Block,上一步Decoder Block的输出作为下一个Decoder Block的输出,直到N个Decoder Block处理完毕。 最后是输出结构的转换层,包含两个部分:线性层和Linear和Softmax层。 - Linear (线性层):全连接映射层,用于将 Decoder 提取出来的抽象向量(例如 512维,768维等)投射到一个巨大的空间,即模块输出所能支持的完整词表的空间。如果输出的字典里有 30000 个单词,那么这一层的输出维度就是 30000。 - Softmax (归一化层):通过Softmax 将线性层输出的原始数值(Logits)转换为概率分布,最终输出一个预测概率向量,每个位置的值代表词典中对应单词成为下一个词的概率。接下来系统就根据预测概率向量,选取概率最高的单词作为结果输出,并反馈回底部的输入端,开始下一个词的预测。 参考资料 - Architecture and Working of Transformers in Deep Learning - GeeksforGeeks - How Transformers Work: A Detailed Exploration of Transformer Architecture | DataCamp - Cross-Attention Mechanism in Transformers - GeeksforGeeks
- 04 Mar, 2026
详解QWen3-VL模型的测试以及vLLM生产环境部署流程
本文对阿里于2025年9月份发布的Qwen3-VL视觉语言模型进行了简单介绍,并对其预训练模型进行了本地推理测试以及在生产环境中使用vLLM进行部署的流程进行了总结。 QWen3-VL模型简介 阿里在 2025 年 9 月推出了Qwen3-VL 系列视觉语言模型。相较于其前代产品(如 Qwen2-VL),Qwen3-VL 的发布标志着多模态大模型从简单的视觉感知正式跨越到了深层的逻辑推理与自主 Agent 交互的新阶段。Qwen3-VL 延续了阿里的开源精神,推出了涵盖从端侧到云端的多种规模模型。 !image.png 在模型的总体设计架构上,Qwen3-VL 延续了阿里一贯的Dense(稠密)+ MoE(混合专家)双线并行的策略,参数量从2B到235B不等。 - Dense:该模型中所有的参数在处理每一个 Token 时都会被全部激活并参与计算。 - 其代表版本是Qwen3-VL-2B / 8B / 32B。 - 优点是在同等总参数量下,知识密度最高,部署相对简单。但缺点则是当参数量变大时,计算的开销线性增长,推理会变得非常缓慢。 - 所以适用于参数量不是很大的应用场景。 - MoE:该模型结构在包含很多专家(Experts)子模型,在处理特定输入时,会由门控网络(Gating)动态挑选其中的一小部分参与计算,大部分参数是不需要激活的。 - 其代表版本是:Qwen3-VL-30B-A3B (表示总参数30B,激活参数3B);Qwen3-VL-235B-A22B (表示总参数235B,激活参数22B)。 - 优点是拥有超大规模模型的知识库,但推理速度和计算开销却像小模型一样快。缺点则是对显存的要求仍然很高,虽然计算时只激活一小部分,但推理时必须将全部参数加载进显存。 此外,Qwen3-VL首次引入了 Thinking 版本:当面对复杂的 STEM、数学题或深层因果分析,模型不再是快思考直接给出直觉答案,而是通过强化学习(RL)生成的推理链,逐步拆解图像细节。因此 Qwen3-VL模型支持两种交互范式:Instruct和Thinking。 - Instruct (指令微调版):快思考,接收指令后直接生成并给出最终结果。 - 其特点是低延迟、生成速度快、输出简洁。 - 适用场景为日常对话、简单的图片描述、基础 OCR、实时 UI 操控等对实时性要求高的任务。 - Thinking (推理增强版):慢思考,在给出最终答案前,模型会先生成一段不可见的(或可展开的)思维链 (Chain-of-Thought, CoT)。 - 其特点是准确率更高(尤其在 STEM、数学、逻辑推理的复杂任务中),但生成时间长,会消耗更多 Token。 - 适用场景是复杂的数学几何题、逻辑推理、长视频的因果分析、需要严谨证明的场景。 QWen3-VL的推理测试 以下基于HuggingFace的Transformers下载 Qwen3-VL-4B-Instruct-FP8 这个版本的预训练模型文件,并在本机上做一些简单的推理测试。以下给出一个对手写文字图片进行OCR识别的推理测试。 首先import必要的包并使用Transformer从Huggingface上下载预训练模型文件列表到本地,并加载进来: 以上代码会自动下载预训练模型文件到本地 HF-HOME 环境变量所指向的位置,模型权重文件比较大,下载需要比较长的时间。下载后的模型文件如下所示: !image.png Qwen3VLForConditionalGeneration和AutoProcessor在每次启动加载模型文件的时候,都会对比本地下载的模型文件与HuggingFace Hub仓库中文件的文件的md5是否匹配,这个过程需要的时间会比较长,所以第一次下载模型文件完成后,可以通过以下代码设置HF Offline模式,避免每次加载的校验过程: 写一个接口函数用于对传入的图片进行OCR识别: 向上面的接口函数传入一张图片并打印出来OCR识别的结果: 这里传递了一张手写文字的图片: !image.png 最终OCR识别的结果如下:可以看到,在图片中字迹很潦草的情况下,模型的识别正确率仍然非常高。 QWen3-VL的vLLM生产环境部署 vLLM 是目前支持 Qwen 系列最好的引擎。它可以把 Transformers 模型包装成一个高性能的 OpenAI 兼容接口,后续直接使用HTTP协议来对模型进行访问。 首先安装vllm和qwen_vl_utils支持包。因为vllm的运行对于pytorch以及cuda的版本有要求,所以最好在一个独立的环境中安装和运行vllm: > 注意,vllm在windows环境下的安装总是会报配置文件的错误,但是在Ubuntu下面没有这个问题,一般生产环境部署用的都是Linux,所以我也就没有追究这个问题了。 以上vllm包安装完成后,使用以下命令就可以一键启动QWen3-VL模型在vLLM的部署和运行了: 其中: - -model参数用于指定模型文件所在的目录。 - -served-model-name:指定后续通过HTTP API访问模型的模型名称 - -trust-remote-code:必须加上,因为 Qwen3-VL 有自定义的视觉处理逻辑。 - -gpu-memory-utilization 0.75:告诉 vLLM 占用 75% 的显存,vLLM 会预先占满显存来管理PagedAttention。如果显卡显存少的话需要调低这个值。 - -max-model-len: 设置最大上下文长度。如果显存小,可以调小这个值。 - -limit-mm-per-prompt: 使用json格式限制一次对话最多传几张图。 如果有以下打印信息,就表示vLLM环境中部署的Qwen3-vl模型已经运行起来了: 使用下面的代码测试本机所部署的模型: !image.png 针对上面url参数所指定的图片,从模型得到的返回信息如下,模型部署测试验证OK: 参考资料 - Introduction to Qwen3-VL
- 01 Feb, 2026
开源VLM模型MoonDream
本文详细介绍了业界非常流行的开源轻量级视觉语言模型MoonDream,总结了该模型的核心功能列表、基本功能测试流程以及相关的license等情况。 Moondream 是由开发者 Vikhyat Korrapati 发起并维护的一个开源、轻量级的视觉语言模型(Vision-Language Models, VLM)。该模型的定位是专为端侧设备(Edge Devices)和资源受限环境而设计的高性能 VLM,其核心优势是能以极低的能耗和内存占用(不到 4GB VRAM 甚至在 CPU 上流畅运行),实现与大模型(如 LLaVA 甚至早期 GPT-4V)相媲美甚至在特定任务上超越的视觉理解能力。 MoonDream的官网地址:Moondream !image.png 截至2025年底,MoonDream已经演进迭代到第三代,其前两代模型Moondream 1/2的参数规模大约为 1.6B - 2B,甚至还针对资源更紧张的应用提供了 0.5B参数量的版本。而最新的Moondream 3 模型采用了 MoE(混合专家)架构,总参数量达到了 9B,但单次推理的激活参数只有2B。 什么是VLM? 首先要更深入的理解所谓的视觉语言模型VLM究竟是什么?简单来说,VLM(Vision Language Model) 是一类能够同时理解图像和文字并实现跨模态交互的 AI 模型。 更通俗地讲,所谓的VLM模型,从其整体结构和工作的流程上可以如此理解: - 在输入端,可以认为其输入包含两个部分,一张图片,针对这张图片的一段自然语言的命令。 - 在输出端,VLM的输出是一段文本信息(例如以json格式封装的文本内容),即使是要输出位置坐标等数字内容,其输出的也是一段包含数字字符串的文本信息。 - 模型的工作流程:模型会按照自然语言的命令对输入的图像进行描述解释、目标搜索和定位等,最终输出描述这个图像内容和其他检索性信息的文字内容。 - 最典型的应用就是对图片内容进行描述、从图像中检测出特定物体所在的位置坐标、针对图像生成其OCR检测结果等。 其他的VLM模型包括:阿里的Qwen2-VL,开源的LLaVA 系列,微软的Phi-3.5/Phi-4 Vision以及Florence-2,谷歌的PaliGemma,国产AI Lab的InternVL 2。 MoonDream的环境搭建与核心功能演示 以下以应用上最为成熟、License最为宽松的MoonDream2描述环境搭建以及进行功能演示。 首先是从huggingface上下载Moondream2的模型仓库和权重文件,其模型仓库地址为:vikhyatk/moondream2 at main。 但是Moondream2的权重文件接近4GB,从HuggingFace上直接下载耗时很长,国内网络的情况下可以从Huggingface的镜像网站hf-mirror.com上进行下载。 以下是模型及其权重文件的下载代码,模型相关文件会被下载到D:\models\moondream2下: Moondream 的核心功能矩阵包括四种模式:caption, query, detect, point。这四种模式分别对应于不同的图像理解的颗粒度(从整体到局部)和输出形式(从非结构化文字到结构化坐标)。以下代码演示了以上几种模式在本地下载的MoonDream2模型文件上的推理流程。 首先安装演示代码的依赖包: 推理代码如下: 代码的执行结果如下图所示: !image.png MoonDream的License Moondream 的不同版本采用了完全不同的授权策略。 !image.png 可以看到。Moondream的版本1和版本2均采用了最宽松的Apache 2.0 license,可以自由的在商业领域中使用。而最新的MoonDream 3在商业领域中的使用则存在一定的附加条件: - 可自由使用的情况:在服务器上部署后仅供公司内部使用,做为公司产品的功能嵌入使用,其他的非盈利和研究使用。 - 禁止使用的情况(此时需要另外获得商业授权):搭建服务器后通过API方式以HTTP或者SDK等方式对外提供收费服务,与官方的付费服务高度重合的应用。 参考资料 - Overview | Moondream Docs
- 01 Nov, 2025
安装Nvidia CUDA支持的Pytorch开发环境记录
操作系统:Windows 11 硬件:HP暗夜精灵Max,Ultra9 275HX+5070Ti 检查Nvidia显卡驱动版本 在终端输入nvidia-smi: 可以看到以上显卡驱动支持的CUDA版本最高是12.9。 - 如果以上命令提示找不到,就需重新安装Nvidia的显卡驱动:Download The Official NVIDIA Drivers | NVIDIA 安装Pytorch的GPU版本 在Pytorch官方页面Get Started根据当前的CUDA版本选择安装对应的torch版本:因为前面查到的当前CUDA版本是12.9,所以这里选择的版本应该是CUDA12.8的Pytorch版本,复制下面的安装命令安装pytorch library。 > CUDA版本的匹配规则:PyTorch的CUDA版本应≤驱动支持的版本 !image.png 测试验证Pytorch的环境 以上安装完成后使用下面的脚本测试当前环境是否已经安装好: 正确的情况下,上面脚本执行后的打印消息为: 参考资料 - PyTorch CUDA支持错误:解决方案与安装指南_文心快码
- 19 Jul, 2025
盘点深度学习神经网络架构中的常用损失函数
本文对深度学习神经网络中常用的损失函数进行详细的学习和总结,对各种损失函数的特性及其适用性进行整理,方便以后理解各种深度神经网络的架构设计。 神经网络损失函数的定义 所谓的损失函数,可以看作是表征当前神经网络工作性能的评估指标,也就是说,当前神经网络对于输入其中的数据进行处理的结果,与实际的真实结果相比较在多大程度上不一致,因此,损失函数的值越小越好。神经网络在训练过程中的目标,就是要找到让损失函数最小的参数(神经网络的权重和偏置)。 例如,如下图所示的简单例子,我们希望通过训练神经网络来拟合一条直线 f(x) ,来最大程度上反映出来给定样本数据输入 x 与对应输出 y 之间的对应关系,后续可以基于这条直线与新的输入 x 找出其对应的 y 。那么怎么来评估这条直线对于样本数据的拟合程度有多好呢?答案就是使用损失函数来评估. 在下图中,损失函数就是所有训练样本数据中 y 与拟合直线对应输出 f(x) 之间的差值 (f(x)-y) 之和。针对所有训练样本数据而言,这个累加的差值之和越大,表示损失函数越大,拟合程度越差;否则损失函数越小,拟合程度越差. !image.png > 上图中如何计算各个样本点与拟合直线之间的差值,有两种方法,一种是使用其插值的绝对值 |f(x)-y| 。另外一种是对其距离的平方 (f(x)-y)^2 (也就是以均方误差作为损失函数)。一般情况下使用后者,因为后续进行参数学习的时候,要对损失函数计算微分,比起绝对值,平方的微分计算更为简单。 为什么要有损失函数? 以图像分类任务为例,很多时候我们会有疑惑,既然我们训练神经网络参数的目标是让其能够更准确的对图像进行分类,那么最直接评估神经网络性能的指标当然就是对图像分类判别的成功率,为什么还需要额外引入一个损失函数的指标呢? 最主要的原因在于,实际应用场景中的神经网络结构往往非常复杂,层数和参数非常多,在这种情况下,每次训练对网络权重和参数的微小修改,对于送入神经网络进行学习和训练并最终进行正确分类的图片数量不会产生任何影响,在这种情况下,下一步对于网络的权重等参数要如何调节就失去了指导方向.因此在实际的神经网络参数学习中,就需要一种能够在输出指标评价上更为精细和平滑的损失函数,来评估每次对参数微小调整后所产生的效果,继而决定下一步的参数调整方向。 均方误差MSE 均方误差(Mean Squared Error, MSE)是机器学习和统计学中最核心的损失函数之一,尤其适合用在回归任务(基于历史数据对模型训练,然后预测新的输入数据所对应的输出)中。 在数学计算上,均方误差的定义如下: 其中,n 为样本数量,为第 i 个训练样本对应的真实值, 为当前模型参数对第 i 个训练样本输入所计算出来的预测值。 那么 就是每个样本的预测误差,通过以上公式计算,就可以得到所有样本预测误差平方的算术平均值,从而反映出来模型在整个训练数据集上的平均误差强度。 训练均方误差的Python实现示例代码如下所示: 交叉熵误差Cross-Entropy 交叉熵误差则主要用于评估样本数据提供的真实概率分布 p 与模型预测概率分布 q 之间的差异状态,其计算公式如下所示: 其中: - C :表示模型输出类别判断的总数。例如对于手写字符判别而言,其识别结果为数字0-9,因此模型输出的类别总数 C 就是10个。 - :表示当前的训练样本对应于类别 i 的真实概率,一般使用one-hot编码。 - :表示当前模型所输出的预测数据属于类别 i 的概率,所有类别的概率之和为1,也就是说 。 > 所谓的one-hot编码,就是只有正确答案的标签为1,其他都是0的数组。例如手写数字6训练样本真实概率所对应的one-hot编码就是[0,0,0,0,0,0,1,0,0,0],只有6这个数字对应的索引位置概率为1,其他全部为0。 因此,对于多分类任务而言,如果当前训练样本的真实概率使用one-hot编码的话,那么实际上也就意味着在进行交叉熵误差计算的时候,只有真实类别 的这一项对于计算误差有贡献,其他类别都是0。 例如,对于某个多分类任务的训练而言,某个训练样本的真实标签为 y=[0,0,1] ,模型针对这个样本输入所预测出来的概率是 y^=[0.1,0.2,0.7] ,则计算该样本对应的交叉熵损失为: ,也就是说,只需要对真实标签概率为1的这一项的预测概率求其log值就可以了。 交叉熵误差损失函数的Python实现示例代码如下所示。需要注意,因为log0在数学上没有意义,所有交叉熵实现代码中需要对这种情况做特殊处理,使用一个很小的正数代替log0。 参考资料 - 《深入浅出神经网络与深度学习》 - 神经网络算法——损失函数(Loss Function)_神经网络损失函数-CSDN博客
- 17 Jul, 2025
盘点深度学习神经网络架构中的常用激活函数
本文对深度学习神经网络中常用的激活函数进行详细的学习和总结,对各个激活函数的特性及其适用性进行整理,方便以后理解各种深度神经网络的架构设计。 激活函数是什么? 一个神经网络的典型神经元的架构如下图所示: !image.png - 输入层的输入与其权重参数各自相乘后累加,然后加上该神经元的偏执参数bias,这就是神经元的输入信号。 - 神经元在其内部处理中,把以上输入信号作为参数,执行其激活函数 f(x) ,激活函数的输出即为神经元的输出。 因此,所谓神经元的激活函数,本质上就是神经节对输入信号加权和的非线性映射和变换而已。通过激活函数的映射,神经网络的计算输出可以逼近任意复杂的函数(如曲线边界分割、多目标重叠检测),解决实际应用中的复杂问题。 为什么需要激活函数? 在没有激活函数的情况下,无论神经网络的层数是多少,其输出都是输入的线性组合,这样就等效为单层的感知机,层数(也就是所谓的深度)失去了意义。激活函数要解决的问题,就是从线性空间向非线性空间的映射。 > _如果没有激活函数的话,实际上无论神经网络有多少层,其输出最终都是对输入数据的线性变换,无法处理复杂的非线性问题。而我们现实生活中要解决的问题基本上都是非线性问题。激活函数通过引入非线性的映射,使得神经网络可以逼近任意复杂的非线性函数。_ 阶跃函数 阶跃函数是神经网络发展早期的感知机所使用的激活函数。阶跃函数以阈值(通常为0)为界,输出二元值:当输入大于等于0时,阶跃函数输出为1;否则输出为0。 其图形如下所示: !image.png 阶跃型激活函数的主要特点: - 其输出呈阶梯状跳跃,在x=0处不连续,形成明显的“断点”。 - 其输出范围只有0和1两种状态(对应于全有和全无两种情况),无法表达中间状态和概率信息。因此比较适合二分类任务。 - 因为在x=0位置跳变,形成了不连续点,所以无法进行微分计算(导数计算要求函数在局部区间内平滑变化),因此无法使用梯度下降法(依赖于对激活函数的偏微分计算)和反向传播法训练参数。 > 想象一下,采用阶跃函数作为激活函数的情况下,对于神经网络中的权重和偏执等参数的微小修改,有可能会导致输出完全翻转,进而引起神经网络对于其他输入数据进行处理的行为发生复杂的巨变。因此,这种情况下,就很难通过逐步修改网络的权重和偏执参数来实现神经网络判断准确性的调优。 因为以上的阶跃函数所存在的只有两个值的取值范围、不可微分等特性,目前阶跃函数已经不用于主流网络的隐藏层,仅在理论教学(感知机)、嵌入式系统轻量级二分类系统等领域有少量的应用。 阶跃函数的Python实现如下: Sigmoid函数 Sigmoid函数也被称为Logistic函数,是一种S型曲线函数。Sigmoid函数是神经网络中经典的激活函数之一,尤其在早期深度学习和二分类任务中应用广泛。 Sigmoid函数的数学表达式如下: 其图形如下所示: !image.png > 从图形的对比上可以看到,Sigmoid激活函数和阶跃函数激活函数类似,但是在使用Sigmoid激活函数的情况下,权重和偏置的微小改动只会引起输出发生微小变化,这一点对于使用sigmoid作为激活函数的神经网络正常学习并调整其参数至关重要。 Sigmoid激活函数的主要特性总结如下: - 与阶跃函数类似,输出值范围在0到1之间,且在(0,0.5)点中心对称,因此非常适合用于表示概率。 - 随输入x的增大严格单调递增,输入增大时输出趋近1,减小时趋近0 - 曲线平滑,处处连续可导,可通过后续的梯度下降法逐次计算逼近极值。 Sigmoid激活函数在神经网络的发展早期阶段应用较广,但因为诸多缺点,在当前的神经网络架构中已经较少使用: - 梯度消失问题。从上面Sigmoid的图形上就可以看出,当输入大于5或者小于-5的时候,整个图形几乎呈现水平的趋势,导数趋近于0,这一点会导致在使用梯度下降法寻求损失函数极值的过程中参数无法更新,训练停滞,这一点在网络层数较多时尤其明显。 - 非零中心化问题。Sigmoid的输出范围为0到1之间,随着输入x的增益严格单调递增,所以无论输入如何,其输出始终大于0。这样就会限制后续基于梯度调整参数的方向,导致调整过程中反复横跳调整参数的方向,训练收敛速度会显著低于零中心化激活函数(如Tanh)。 - 计算开销大。因为涉及到指数运算,相比于ReLU等现代激活函数计算成本过高,拖累训练速度。 Sigmoid函数的Python实现如下: Tanh函数 Tanh函数又称为双曲正切函数,可以认为Tanh函数是Sigmoid函数的变形形式(本质上Tanh是Sigmoid的零中心化改进)。与Sigmoid函数最大的不同在于,Tanh函数的取值范围为-1到1之间单调递增,且是0均值。Tanh函数的数学表达式为: 尽管其数学表达式看上去很复杂,但是它的图形跟Sigmoid非常类似。Tanh函数的图形如下所示: !image.png Tanh激活函数的主要特性总结如下: - 输出范围在-1到1之间,并且以原点对称 (tanh(−x)=−tanh(x)) ,这样就避免了Sigmoid的非零中心化问题,使后续层输入分布更均衡(不再是恒正),从而加速梯度下降的收敛。 - 与Sigmoid一样,输出随输入的增大逐渐单调平滑增大,函数处处可导。 - 在梯度变化方面,相比Sigmoid而言,梯度在相同输入下更大,一定程度上缓解了梯度消失的问题。 总之,Tanh函数解决了Sigmoid存在的非零中心化的问题,可以加速基于梯度下降的参数训练过程,也一定程度上缓解了梯度消失的问题(当输入偏离0过大的时候仍然存在),但是因为要进行指数运算,仍然存在计算成本过高的问题。 Tanh函数的Python实现如下(实际上numpy已经有自己的tanh函数实现了): ReLU函数 Sigmoid是神经网络发展早期应用比较广泛的激活函数,而深度学习时期应用最广泛的激活函数则是ReLU(Rectified Linear Unit)。 ReLU函数在输入大于0的时候,直接输出原值;而输出小于0的时候,直接返回0。其输出范围为0到无穷大,数学表达式为: ReLU函数的图形如下所示: !image.png ReLU激活函数的主要特性总结如下: - 分段线性。正区间保持线性,梯度始终为1,负区间强制归零。 - 计算高效。是ReLU最大的优势,不需要任何三角函数和指数运算,计算速度比Sigmoid和Tanh快10倍以上。 - 稀疏激活。在输入为负的情况下输出为0,此时神经元处于不激活状态,相当于只允许部分关键神经元处于激活状态,从而迫使整个网络优先选择最重要的特征。 - 解决梯度消失的问题。在正区间梯度恒定为1,可以用于支持超深层的网络。而且线性正区间可避免饱和,梯度更新幅度较大,收敛速度显著快。 ReLU最大的问题是死亡神经元问题。该问题是指当使用ReLU激活函数时,某些神经元可能会出现永久失效的现象。在训练的过程中,当神经元的权重更新导致其输入恒为负值时,ReLU的输出恒为0,而梯度也为0,就会导致该神经元无法再更新参数,处于实质性的永久失效的状态。当模型中死亡神经元的占比较大时,就会明显的影响整个网络的工作性能和精度。 ReLU函数的Python实现如下: Leaky ReLU Leaky ReLU(Leaky Rectified Linear Unit)是ReLU激活函数的改进版本,旨在解决其以上提到的死亡神经元的问题。Leaky ReLU针对负区间引入了一个微小的正数斜率 a (一般是0.01): 因此,Leaky ReLU在负区间就不再恒定是0,而是以 a 为斜率的直线,取值范围也变成了负无穷大到正无穷大。 !image.png 以上的这个设计可以有效的解决ReLU存在的死亡神经元问题:通过负区间的斜率 α 可保留微小的梯度,确保反向传播时权重仍然可以更新。 此外Leaky ReLU仍然保持了计算效率高的优势。其计算仅需一次乘法比较,计算复杂度与ReLU相近,远低于Sigmoid/Tanh的指数运算。 Softmax函数 Softmax函数是在神经网络中用于处理多分类任务。例如,对MNIST手写数字数据集进行识别的分类任务)的核心激活函数,其核心作用是将神经网络的原始输出转换为概率分布,使每个类别的概率值非负且总和为1。 Softmax函数的主要作用就是进行多分类的概率转换,其输入是一个多类别的向量数组 z=z_1,z_2...z_k ,其对应的数学表达式为: 在以上softmax的表达式中,在分子方面,对第 i 类输入 z_i 取指数,以放大各个分类的差异;在分母方面,取所有类别输入的指数和,实现归一化,以确保输出的总概率之和为1。 !image.png Softmax激活函数的主要特性总结如下: - 概率归一化。所有输入对应的输出值范围在0和1之间,并且所有输出之和为1,符合概率公理,非常适合用于分类决策。 - 可放大差异。在softmax的计算中通过指数函数可以显著放大高输入的输出权重,这样可以使模型对高置信类别在输出重点表现更明确,减少模糊预测。 - Softmax激活函数与交叉熵损失函数是分类任务的标准组合,梯度计算高效且收敛快。 Softmax函数的Python实现如下: 参考资料 - 神经网络中的激活函数及其各自的优缺点、以及如何选择激活函数_神经网络的激活函数有哪些?他们对神经网络的性能有何影响。-CSDN博客 - 激活函数全解析-CSDN博客
- 19 Mar, 2025
非专业人士的AI课6:LLM大模型的典型应用级别
本系列文章的目标是从非AI从业者的角度出发,去尝试理解AI及其大模型在应用中的基本概念,争取通过对这些概念的深入理解和澄清,建立对大模型的工作机制流程以及应用框架方面的知识结构,从而在我们的生活和工作中更好的使用AI。 LLM大语言模型在特定行业领域中的应用,大致可以分为下图所示的几种级别。其中,从头开始设计和构建大模型当然是难度和门槛最高的,而最简单的应用就是我们所谓的提示词工程(Prompt Engineering),即在向LLM大语言模型提问的时候,对提问问题的语言组织结构(提示词,Prompt)进行精心安排,使大模型的输出更符合自己的提问需求。 !image.png 从头开始构建模型 对于从头开始构建大模型的应用而言,整个流程就如[[3. LLM大语言模型项目的典型开发流程详解]]所述,从项目需求分析和定义开始进行规划,选择合适的技术路线或者基于开源的大模型框架进行优化,针对模型和应用开发需求,提前准备训练数据,使用数据对模型的参数分别进行预训练和微调,最终部署到服务器上并推广给目标用户使用。 以上的全过程,对于团队能力、算力资源、数据、开发周期的要求都是非常高的。因此,这种开发模式也是大模型应用中门槛最高的类型。 私有化部署开源LLM后微调 这种方式相比从头进行大模型的开发而言,简单了一些,基本上是把已经完成预训练的开源LLM模型(这种已经完成预训练的模型也称为基础模型,Base Model)进行私有化部署,然后按照应用领域的微调需求,使用与该应用领域相对应的微调标注数据进行微调。微调之后的工作量与从头开始构建模型的模式差异不大。 这种开发模式,省去了大语言模型本身的设计开发,以及已经基于海量通用语义数据对模型进行了预训练,只需要进行微调即可,对于团队能力、算力资源的要求大大降低,因此这种开发模式对中小型团队和个人开发者而言比较友好。 指令微调LLM 首先解释一下基础大语言模型(Base LLM)和指令微调大语言模型(Instruction-Tuned LLM)。 - Base LLM 是一种通过大规模自监督学习(self-supervised learning)预训练的语言模型,主要依赖海量文本数据来学习语言结构和模式。这类模型虽然具备强大的文本生成能力,但在执行具体任务时往往缺乏明确的指令理解能力。也就是说,在[[3. LLM大语言模型项目的典型开发流程详解]]中完成了预训练,但是尚未进行微调的模型就是Base LLM,相当于这类模型具有对语言的语义规律和基础知识有充分的理解,但是缺乏对于用户输入指令的理解能力。 - 相比之下,Instruction-Tuned LLM 是在 Base LLM 的基础上进行了额外的指令微调,目的是使其更善于处理人类提供的任务指令,提高了实用性和交互体验。相比Base LLM Model,经过指令训练集进行指令微调以后,模型就具备了更强的任务执行能力,能够更准确地按照用户的需求生成符合用户预期的、有价值的内容。 基于Base LLM,对该模型进行指令数据集的微调之后,Base LLM就可以进化成为Instruction-Tuned LLM。指令数据集一般包含指令-上下文-响应的完整序列,因此指令微调对于数据的要求很高,一般包含人类标注员针对指令微调任务所编写的大量任务示例,用以训练模型理解任务指令并生成符合预期的响应。 下面是指令微调数据集的比较典型的例子: !image.png - 用于指令微调的数据集对于数据的内容和格式有比较高的要求,databricks-dolly-15k是一个开源的指令微调数据集,其中包含由数千名 Databricks 员工生成的遵循指令的记录。可以从HuggingFace上下载该数据集。 经过以上的指令数据集对Base LLM的微调之后,模型就可以对用户所输入的指令进行识别、执行并输出更符合用户预期需求的答复。而为了进一步约束模型输出的答案,部分模型还会采用人类反馈强化学习(RLHF)的模式让模型生成的答案更加符合人类的价值观和伦理道德偏好。 - 这个过程在[[3. LLM大语言模型项目的典型开发流程详解]]中已经有详细的描述。 其实,微调的含义往往超过了此处重点介绍的指令微调,实际的应用中,我们也会针对具体行业领域和应用场景所需要的专业知识,整理出来针对性的微调训练数据,在Base LLM的基础上针对这些具体的行业领域场景进行微调,使之更适合应用于该领域。这部分微调的过程基本上与指令微调类似,都是在Base LLM的基础上进一步使用额外的训练数据进行训练,使之更加符合应用的要求和预期。 > 在此之上的部分,都可以称之为AI模型开发(AI Model Development);而在此之下的则可以称之为AI应用开发(AI Application Development)。 RAG/AI Agent RAG和AI Agent是当前AI应用开发领域中最为火热的方向。 有关RAG部分的信息,在[[4. 一文说清楚RAG的工作流程以及与微调的区别]]一文中已经有非常详细的解释和描述,在此不再赘述。 有关AI Agent部分的信息,在[[5. AI Agent的框架与工作流程解析]]一文中已经有非常详细的解释和描述,在此不再赘述。 Prompt Engineering 下面的少样本提示和零样本提示都可以归纳入提示词工程(prompt engineering)的范畴。提示词工程就是我们精心设计、优化输入信息,从而引导AI生成高质量、准确、有针对性的回答的语言艺术。如果你写提示词的能力不够,那么AI给出的往往就像是用一堆正确的废话堆砌而成的文字垃圾;反之,它的回答会非常令人惊艳。所以,使用同样的大模型去查询相同的问题,大模型的输出质量与输入的提示词有非常强的关联。 提示词如何书写,并没有一个明确的答案,但是考虑到大语言模型进行输出信息推理的工作原理和流程,一个较好的提示词应该至少包含角色定义、提供上下文以及确定目标/提出问题这三部分信息。以下是参考资料三种提到的一种写好提示词的框架结构,按照这个框架对自己向LLM提出的问题和要求的描述进行精心组织,可以很有效的解决我们在工作和生活中的大量问题。 !image.png 少样本提示 所谓的少样本提示(Few Shot Prompting),就是针对一些稍微复杂的问题,我们使用语言很难轻易的把需求清楚的表达出来的时候,我们就可以在向大模型提问的时候,给大模型提供一些样本,让模型自己去分析样本的结构、逻辑、语气表达形式等,并根据自己学到的东西,给我们生成类似的内容。 下面是一个少样本提示的很经典的例子。用户向大模型输入的提示词是: !image.png 以上提示词从模型返回的结果是: !image.png 零样本提示 零样本提示(Zero Shot Prompting),是指不需要在提示词中包含任何示例,大模型就能够完成用户所指示的任务。这种提示词一般是用于面向比较简单、目标和过程清晰的任务,而且也要求用户的提示词十分明确,大模型具有相当强的能力理解用户的需求。 总之,针对相对比较简单和目标清晰的任务,可以使用零样本提示词尽可能明确、清晰、详尽的定义自己要模型执行的任务,以方便模型理解。 参考资料 - 《大模型应用开发:动手做AI Agent》 - 《大模型RAG实战:RAG原理、应用和系统构建》 - 《AI效率手册:从ChatGPT开启高效能》 - 一文说清楚什么是基础模型(Base LLM)、指令微调模型(Instruction-Tuned LLM)
- 18 Mar, 2025
非专业人士的AI课5:AI Agent的框架与工作流程解析
本系列文章的目标是从非AI从业者的角度出发,去尝试理解AI及其大模型在应用中的基本概念,争取通过对这些概念的深入理解和澄清,建立对大模型的工作机制流程以及应用框架方面的知识结构,从而在我们的生活和工作中更好的使用AI。 AI Agent是什么? 在AI的应用方面,目前最成熟和最广泛的仍然是以类似ChatGPT一样的对话应用,系统提供一个可支持文本输入或者语音输入的界面,用户在其中按照提示词工程的要求输入问题,AI系统对其进行学习研究、推理决策、生成答案,最后再通过文本框或者语音的形式把生成的答案展现给用户。 而AI Agent,也称为AI智能体或者AI智能代理,是一个具有一定程度自主性的人工系统。更具体地说,AI Agent是一个能够自动感知环境、做出决策并付诸行动的智能系统。 因此,相比于ChatGPT类型的对话方面的应用(重心在于能够回答用户所提出的问题),AI Agent系统所具备的自主性和适应性是其最大的特色。Agent能够自动通过对输入的分析感知环境、参数变量的变化,针对变化自主思考下一步的行动计划,并自行执行经过深思熟虑的思考后的行动,最终给出结果。 - ChatGPT类应用:会对话 - AI Agent:会做事 AI Agent的架构 下图是参考资料1第二章所提出的一个AI Agent的框架图。从图中可以看到,一个典型的AI Agent包括四大元素:规划,记忆,工具,执行。 !image.png 规划 AI Agent需要具备思考和规划的能力,而LLM所具备的推理能力很好的解决了这个问题,这也是LLM在推理方面逐渐成熟一下,AI Agent也随之爆发的原因所在。 对完整的复杂任务所进行的规划和思考,主要包含对子目标和任务的分解(Subgoal Decomposition)、连续的思考(即思维链)、自我反思和批评(Self-critics),以及对过去行动的反思(Reflection)等。 记忆 AI Agent的记忆包含两个方面,短期记忆和长期记忆。 短期记忆与任务执行的上下文相关,属于提示工程的一部分,一般在AI Agent运行过程中整理在对话的上下文中传递给大模型服务器。 长期记忆,则用于对AI Agent要用的信息长时间保存并能够实现高效率的检索,最典型的就是能够支持外部知识库,把Agent运行任务需要的外部知识以向量化的方式保存在外部数据库中,并且能够支持高效的检索。 工具 由于大模型一旦完成预训练,其内部能力和知识边界就基本固定下来,而且难以拓展。此外作为基于文本和自然语言的处理和生成工具,大语言模型不太适合一些工作(例如复杂浮点数字的计算),因此就需要一些外部工具来扩展Agent的能力,使其能够执行这些它很难胜任的任务。 AI Agent系统可调用的外部工具,典型的包括网络搜索、日历时间、计算器、本地代码执行能力(Function Calling)等。 执行 AI Agent需要具备调用工具执行任务的能力,与外界互动,通过调用工具的方式来完成一个动作或者子任务。 从上图可以看到,AI Agent如何规划和决策制定过程,如何调用工具来执行行动的完整工作流程是其应用的核心所在,这部分工作逻辑就称为AI Agent的推理引擎或者认知框架。推理引擎决定了Agent如何从感知的环境中抽取信息,如何规划未来的任务,如何利用过去的经验,以及如何调用工具。 研究人员提出了多种智能Agent的推理逻辑,如CoT、ToT、LLM+P等。其中,ReAct框架脱颖而出。 AI Agent的React框架 本质上讲,AI Agent的React框架就是一个思考-行动-观察这个完整序列所组成的循环。 - Thought:基于用户任务的总体目标、当前的状态以及可用的工具集,思考下一步的行动计划,确定下来行动方案。 - Action:基于思考的结果,决定具体要采取的行动并执行。 - Observation:收集并观察前一步的执行结果,对结果进行评估,决定下一轮次的思考方向。 !image.png 以上的流程从概念上理解非常简单,但是应该如何与现实中的AI应用的具体实现来相关联呢?以下通过一个基于LangChain开发框架的示例代码进行说明。 在下面的例子中,使用LangChain框架开发一个简单的Agent。这个Agent的工作流程非常简单,大模型首先对用户输入的问题进行分析,如果需要执行浮点数加减乘除的运算时,就调用本地实现的浮点数运算的代码,然后把结果返回给大模型,由大模型来组织语言输出结果。 其实这个功能完全可以基于大模型的API来使用function calling的方式来实现。但是,通过这个例子的打印信息,可以看到大模型进行思考、决策、调用工具并返回结果的过程,其他更复杂的Agent应用实际上也是类似的工作流程和逻辑。 以下例子自测通过,使用月之暗面的Kimi作为LLM服务器,关于LLM模型的API Key设置以及通过API调用可参考[[基于Python快速实现与Kimi的AI对话]]。 以上程序运行的结果是: 以上通过终端打印的信息很值得研究: - 首先就是,在用户问题提出的连续计算的需求,被明确的分成了两步,首先计算加法,然后再计算乘法。每一步的计算逻辑都是:模型给出这一步的计算式,本地代码运行这个计算式,把结果以字符串形式返回模型,模型基于中间结果再执行下一个计算式,直到最终计算完成后输出结果。 - 大模型竟然把每一步的计算式都已经用字符串的形式整理好了,本地代码拿到这个计算式以后只需要使用eval进行计算就可以了,这确实太方便了。 参考资料 - 《大模型应用开发:动手做AI Agent》
- 15 Mar, 2025
非专业人士的AI课4:一文说清楚RAG的工作流程以及与微调的区别
本系列文章的目标是从非AI从业者的角度出发,去尝试理解AI及其大模型在应用中的基本概念,争取通过对这些概念的深入理解和澄清,建立对大模型的工作机制流程以及应用框架方面的知识结构,从而在我们的生活和工作中更好的使用AI。 RAG RAG(Retrieval Augmented Generation,检索增强生成),是一种结合了大型预训练语言模型与知识检索技术的新型生成模型,为应对大模型应用开发中的诸多挑战提供了新的思路。 生成式大模型存在的问题 大模型并不具备在环境不断变化的场景中回答特定问题所需的全面知识。例如,早期的ChatGPT 3.5版本的预训练语料库时间截至2021年,这意味着模型无法准确输出2021年以后的事实性问题,这正是现代大模型所面临的知识更新困境。 实际上,对于一个大模型来说,更新基础模型知识库是非常困难的一件事情。首先,需要保证预训练数据的质量;其次,更新知识库后的模型通常都需要重新训练,至少要将新数据与旧数据按照一定的比例进行混合训练,而不能仅仅使用新数据,否则会出现灾难性遗忘的问题。总之,大模型的知识更新问题将成为模型的一个重要痛点,并且是一个值得研究的课题。 RAG则可以很好地解决以上问题。该框架可以使模型访问超出其训练数据范围之外的信息,使得模 型在每次生成时可以利用检索提供的外部更专业、更准确的知识,从而更好地回答用户问题。 RAG的工作流程 RAG的完整工作流程主要涉及3个阶段:数据准备、数据召回和答案生成。输出质量的高低主要取决于数据质量和检索策略。 - 数据准备阶段包括识别数据源、从数据源提取数据、清洗数据并将其存储在数据库中。 - 数据召回阶段包括根据用户输入的查询条件从数据库中检索相关数据。 - 答案生成阶段则是利用检索到的数据和用户输入的查询条件生成输出结果。 下图是一个使用LLM大语言模型和本地知识库数据库(问题相关的知识和信息已经保存在本地知识库中)配合来实现的RAG系统的工作流程。 !image.png - 1.用户通过提示词输入查询RAG系统的问题。 - 2.系统对用户提出的问题进行分解,首先调用搜索引擎从本地知识库数据库中检索与查询问题相关的知识片段。 - 3-4,把查询到的知识片段作为查询的上下文放到用户输入提示词中。 - 最后,LLM对用户输入的查询问题以及上下文信息进行解析,生成文本答案。 > 相比于大模型的直接推理输出,RAG系统还有一个非常突出的优势,就是其输出信息的可溯源性。众所周知,在大模型领域中,模型基本都基于Transformer的解码器结构,因此大模型的生成结果存在不可解释性的问题。而RAG系统配合大语言模型的输出结果,不仅给出了答案,还提供了信息溯源的引用链接。因此,相较于纯粹的大模型生成,RAG具有更强的可解释性。 实际上现在我们使用的大模型普遍都提供了联网搜索的功能,而这个联网搜索的功能本质上就是RAG的实现。在回答问题之前首先配合搜索引擎查询有参考价值的网页,对网页内容进行检索处理,然后把检索到的相关内容放在大模型对话的上下文中,最终由大模型返回结果。 以下是使用deepseek不打开联网搜索功能的情况下,查询2024年美国大选的结果,因此deepseek的当前模型训练数据中只包含2023年10月份之前的信息,所以无法给出准确的答案。 !image.png 以下开启了联网搜索(相当于打开RAG功能),可以通过联网查到最新的信息,并且还给出了信息溯源的链接。 !image.png RAG与微调的对比 模型微调是指在一个特定任务上用一定量的数据来训练模型的过程,通过在特定数据集上微调可以提高模型在特定数据集上的性能。如果有足够大的监督数据集,且数据集不会发生改变,那么微调是一个很不错的方法。然而,如果数据集是动态的,就需要不断地重新训练模型以跟上数据的变化,这种情况下使用微调的方案,成本就会很高,而且系统的灵活性很差。 !image.png 总的来说,RAG位于大模型应用难易链条的中部,它既不像重新训练或从头构建模型或者微调模型那么难,也不像通过简单提示工程直接向大模型提问那么容易。它克服了直接微调模型的3个缺点——成本高、信息更新困难以及缺乏可观察性。相较而言,由于RAG无须训练,因此成本较低;由于数据是实时获取的,因此始终是最新的;由于可以显示检索到的文档,因此结果更加可信。
- 14 Mar, 2025
非专业人士的AI课3:LLM大语言模型项目的典型开发流程详解
本系列文章的目标是从非AI从业者的角度出发,去尝试理解AI及其大模型在应用中的基本概念,争取通过对这些概念的深入理解和澄清,建立对大模型的工作机制流程以及应用框架方面的知识结构,从而在我们的生活和工作中更好的使用AI。 本文通过对参考资料的学习,对LLM大语言模型开发流程中的各个环节进行总结和详细的解释,建立对LLM大语言模型开发的初步知识结构。 总体而言,在商业领域中进行的大模型开发而言,其主要的开发流程包括以下几个方面: - 项目需求分析,确定项目的目标 - 基于项目目标进行前期的数据准备 - 基于项目目标进行模型的设计和选型 - 使用数据对模型进行训练 - 部署模型到应用环境中,提供项目需求预定义的服务 - 模型在实际应用中的运维 1. 确定项目目标 在每个与大模型相关的项目立项开发初期,首先要做的事情就是要明确项目的目标,并构建项目的系统框架。因为这涉及到后续工作流程中,针对性的进行模型架构、算法以及训练数据集等的选型。 项目的需求分析和目标制定,是大模型项目开发的起点,为后续的数据准备、模型设计\选型、模型训练提供了明确的方向和约束条件。因此在大模型项目立项之前,应该首先明确该项目需要面对的知识领域和具体要解决的问题,只有项目目标明确确定的情况下,才能选择出来合适的模型和训练数据,进而设计出高效并且成本、效益符合项目需求最大化的系统框架。 下图就是参考资料1中提供的一个财务分析模型的系统设计: !image.png - 针对该系统的功能和性能需求,从开源模型中选择规模和成本合适的模型作为系统设计的基础。 - 以网上收集的公开财报等相关数据为基础,将大模型作为数据集构造器,通过让其学习这些少量标注数据的内容,来自动扩充数据集,为该项目后续的训练阶段提供更多的训练样本。 2. 模型训练数据准备 该阶段的工作实际上包含两个方面,数据收集和数据预处理。 按照项目系统设计的训练所需,开发者可以从网络、公开数据集、用户生成数据、公司内部数据等途径获取训练数据。对于LLM大语言模型的训练而言,数据类型涵盖图片、文字、音频等。开发者可重点考虑开源的数据集,以及针对当前模型要解决的垂直领域问题相关的网络公开数据集。 数据预处理的环节,则需要针对文本、图像等不同的数据进行分别处理,其目的都是构建高质量数据集: - 针对文本数据,常用的数据处理方法一般包含低质过滤(判断和识别低质量的数据并去除)、冗余去除(从不同粒度的角度出发去掉数据中的冗余元素)、隐私消除(识别和去除数据中包含的个人/组织隐私和敏感信息)等。 - 针对图像数据,一般采用图像去噪(降噪)、图像重采样(分辨率缩放)和图像增强(提升对比度、亮度等)等技术,以提升图像数据质量。 分词与嵌入 数据处理完成后,下一步就是要进行分词,也就是Tokenization。所谓的分词就是把文本分割成离散的、有意义的最小语言单元的过程,而这个最小的语言单元就是Token,从训练的文本变成Token的过程使用Tokenizer完成。 - 在平时我们使用LLM大语言模型的过程中,可以清楚的看到模型的输出以一个字或者一个词的节奏按顺序吐出来,这个字或者词就是一个Token。而当我们调用API对LLM大模型进行访问的时候,费用也是基于与大模型交互的Token数量(包括用户向模型提问问题的上行Token数量和模型输出答案的下行Token数量)来进行收费的。 - 为了方便开发者对文本进行分词处理,许多主流的开发框架将分词功能封装成Tokenizer组件或者类,供开发者调用。Tokenizer可以简化开发流程,提高代码复用性,并且确保整个过程中的一致性。因此可以从HuggingFace上下载开源的分词器进行定制化的修改。 !image.png 经过以上的分词器对训练文本所执行的分词操作后,文本语句就会被分割为一个个最小的语言单元。但是这个语言单元仍然无法被大模型识别,此时还需要对每个语言单元执行一个向量化的嵌入操作(Embedding)。而嵌入操作,就是把词汇映射成数值形式的向量的过程。如此一来,模型就能够理解和处理这些向量,并基于这些数值化的特征进行计算和分析,从而实现对自然语言的深入处理和理解。 - 分词和嵌入的这两个概念在基于大模型的RAG应用中,也需要用到,同样是把外部文件或者网络上搜索到的文本文件的内容,执行分词操作(Tokenization)以及嵌入操作(Embedding)后送入大模型进行后续处理。 !image.png 3. 模型的设计和选型 基本上,所有LLM大语言模型的基础都是Transformer架构,而多模态模型中针对图像模块的处理一般则采用Visual Transformer架构。总的来说,大模型的开发、设计和优化调试是整个系统设计与实现过程中技术难度最高的部分。因此: - 从现实的角度来讲,小型开发团队或个人开发者在完成大多数的人工智能业务实现时,已经无须从头构建模型,而是可以根据任务所需选择开源大模型,如Deepseek、Qwen、LLaMA、ChatGLM、Alpaca等。 这种方式可以节省大量的模型设计时间,提升开发效率。 - 而对于有较多资源和能力的团队而言,考虑自行从头开发模型,在模型构建之前,一般需要首先选择合适的深度学习框架,目前主流的深度学习框架有TensorFlow、PyTorch等。其中TensorFlow是由谷歌团队研发的开源架构,PyTorch是Meta团队研发的开源框架,而国内主流的开源框架有百度的飞桨PaddlePaddle等。然后定义模型结构,包括层次结构、参数初始化、定义损失函数、选择优化算法等。 4. 模型的训练 大模型的训练过程一般包含分词器训练、预训练和微调3个基本步骤。 预训练Pre Training: - 基于大型语料库对模型进行训练,目的是让大模型能够掌握基本的自然语言理解和生成能力,以及掌握丰富、广泛的知识,为后续的任务执行打好基础。 - 在预训练阶段,从低成本渠道中获取大量无标注数据,并且把这些数据送入模型,由模型对这些数据的共性知识进行学习,形成一种对于语言的通用理解能力。 微调Fine Tuning: - 微调是一种在预训练语言模型的基础上使用目标任务数据进行训练的模型训练技术。 - 在微调阶段,则是针对模型应用的知识领域和方向,使用少量相关领域的标注数据(一般成本比较高)在预训练阶段的Base Model基础上进行进一步的训练,使其适应目标领域和特定任务的应用需求。 > 当前,无论是在计算机视觉还是NLP领域,广泛采用了“预训练+微调”的开发范式。而一般情况下,尤其是对于中小团队而言,开发者不需要自行对模型进行完整的预训练,而是可以选择合适的开源预训练模型,然后根据具体任务的需求进行微调即可。 !image.png 此外,如果需要对模型的输出进行无害化处理(使大模型输出的内容不会包含有害、不当或者不符合伦理道德的内容),还需要借助RLHF、RLAIF等技术使模型与人类的价值观对齐。 - RLHF,Reinforcement Learning from Human Feedback,基于人类反馈的强化学习。具体工作步骤是,从prompt数据库中采样一部分数据,让模型同时生成多个答案;然后由人类标注员对生成的多个答案进行打分或者排序,然后再使用强化学习算法对模型进行微调。整个过程相当于使用模型生成多个答案的打分或者排序信息,使模型能够更好的与人类的价值观和意图对齐,生成更自然、更符合人类期望的输出。 - RLAIF,Reinforcement Learning from AI Feedback,即基于AI反馈的强化学习。RLAIF是RLHF的一种替代方法,目标是使用AI模型所生产的反馈来代替人类标注员所给出的反馈,从而降低在模型训练中对人工标注的依赖。其工作流程主要是借助于另外一个更为强大的LLM模型,对当前正在训练的模型的输出答案进行评估和打分(在这个环节替代了RLHF的人力工作),然后通过强化学习算法对目标模型进行微调。 5. 模型的部署 模型部署是指将大模型部署到实际应用场景的过程。但是,模型部署并非只是简单的把已经训练好的模型部署在服务器上就好。 在模型部署中,一般需要基于前期训练好的模型进行模型量化、知识蒸馏、模型剪枝等操作,以实现模型压缩,最大限度地减少模型依赖的硬件资源。 6. 模型的应用和运维 最后一步,就是把训练好的大模型服务向目标用户开启。那么针对大模型的使用和大模型服务的运维,就需要提供易用、美观的前端工具。此外,还可以利用LangChain这类大模型开发框架,基于已训练和部署的大模型,开发更加多样化的大模型应用程序以及以训练和部署好的LLM大模型为中枢决策和推理模块,构建更为灵活和强大的AI Agent应用。 参考资料 - 《大模型导论》,张成文 - 一文说清楚什么是预训练(Pre-Training)、微调(Fine-Tuning)
- 13 Mar, 2025
非专业人士的AI课2:对大模型多模态以及开源闭源概念的总结
本系列文章的目标是从非AI从业者的角度出发,去尝试理解AI及其大模型在应用中的基本概念,争取通过对这些概念的深入理解和澄清,建立对大模型的工作机制流程以及应用框架方面的知识结构,从而在我们的生活和工作中更好的使用AI。 究竟什么是大语言模型LLM? 目前我们所说的大模型,全称是大语言模型LLM(Large Language Model)。所谓的”大”是指这个模型所包含的参数量非常大,目前市场上主流的LLM模型的参数量都达到了千亿以上的级别。而所谓的“语言”是指自然语言(Natural Language),也就是这个模型可以直接使用自然语言(而不是只有计算机和程序员才懂的程序代码)与人进行交互,用户对这个模型的使用需求,以自然语言的方式输入模型,而模型也以自然语言的方式给出响应和答复。 用户输入自然语言文本信息到模型中,模型根据用户的问题生成用户能够理解的自然语言文本的内容,这种模型就是大语言模型的单模态模型。 最典型的LLM单模态大语言模型就是引爆OpenAI的GPT-3.5模型,参数量高达1750亿。如此大的参数量,意味着可以提供卓越的性能、强大的学习能力,但是同时也意味着对计算和存储资源提出更高的要求,训练和推理的成本也更高。 单模态与多模态 目前大模型的发展趋势,基本上比较流行的大模型都是多模态模型。所谓的多模态,就是模型的输入输出,不仅能够支持GPT3.5这样的单模态模型所能够支持的自然语言文本信息,也能够支持图像、音频甚至视频方面的内容,例如以文生图、视频,以及根据图像生成描述性文本等。 !image.png OpenAI目前的多款在市场上广受欢迎的多模态大模型,包括可识别图像的多模态LLM大模型GPT-4及其以上的版本,文生图模型DALLE3,文生视频大模型Sora,语言转文字模型Whisper等。而国内阿里的通义千问,腾讯的混元,百度的文心一言,字节跳动的豆包等大模型基本也都支持了对图像、音频等的多模态支持。 Yolo 实际上除了LLM大语言模型以外,还有一些专业性更强、专注于解决某一方向问题的其他AI模型。例如用于在视频图像中进行目标检测(人,车辆,宠物等)的Yolo模型,这类模型大概有上千万参数,注重轻量化和实时性,适合边缘计算场景,可以对输入图像进行检测生成目标框和类别预测,当前约78%的自动驾驶企业和65%的智能摄像头厂商选择YOLO作为主力检测框架,但其具体版本需根据硬件性能调整。例如车载系统多采用YOLOv8m(平衡精度与速度),而门禁摄像头则倾向YOLOv5s(更低功耗)。 大模型的开源和闭源 大模型选择开源或者闭源的商业经营模式,决定了这个模型的代码、模型权重、训练数据、训练过程等核心资源是否完全公开,允许任何人查看、修改和分发(如Apache 2.0等传统开源许可证)。 例如,DeepSeek-R1模型采用Apache 2.0协议,开发者可基于其代码自由开发商业应用。这种开放性促进了全球开发者的协作创新,形成“社区驱动”的生态模式。 下表是目前主流大模型的开源和闭源状态列表: !image.png 可以看到,Meta的Llama,阿里的Qwen,还有最近爆火的deepseek都选择了开源的商业模式。而实际上,自从deepseek爆火,国内的腾讯、Kimi等一种大模型厂商也选择了对其开发的大模型进行开源。那么为什么这些厂商选择了开源的商业模式呢? 大模型开源模式的优势 - 大模型开源之后带来的最大好处就是可以快速的建立自己的技术生态,吸引更多中小型的开发者和企业快速的参与到开源模型的改进和优化中。例如,Meta的Llama系列开源后,迅速衍生出多个垂直领域的模型,加速了技术的迭代。 - 开源生态的成熟也可以快速推动上下游厂商之间的合作与适配,例如DeepSeek开源后,迅速得到了云厂商、社区和芯片厂商的跟进。 - 开源模式能够吸引更多用户和开发者,从而提升品牌知名度和行业影响力。例如,DeepSeek的开源举措不仅推动了技术的普及,还重塑了大模型市场的竞争格局。对比技术竞争力相差不大但是采用闭源模式的Kimi而言,尽管Kimi 2024年在市场推广中投入了超过9亿的营销费用,但是推销效果远远比不上deepseek开源模式推广的影响力。 - 开源模式的商业盈利方式更加多元化。开源并不意味着完全免费,厂商可以通过提供增值服务(如企业级解决方案、定制化服务、API接口等)来盈利,还能够通过数据和流量变现,例如通过广告、用户数据分析等方式实现商业化。 - 当然,大模型开源的同时,也会采用专利等方式对自己的核心技术进行保护。例如,DeepSeek在多个关键领域提交了专利申请,涵盖大模型训练优化、系统稳定性优化、网络与硬件优化等。这种“开源+专利”的双轨策略,既保护了核心技术,又避免了被竞争对手抢注或侵权。 大模型以开源的方式发布以后: - 竞争对手和有大模型开发能力的团队,可以研究大模型的相关代码,参与到对这个模型的改进和优化,以及针对某些垂直领域进行定制化的开发和训练。 - 云服务厂商可以把训练好的大模型部署在自己的云服务器中,通过API等方式为用户提供服务来谋求商业利益。 - 中小型团队和个人开发者可以基于开源大模型进行私有部署,并针对当前行业和机构的需求进行定制化训练和微调。 Hugging Face 对于开源大模型,就必须要提到在AI大模型开发领域大名鼎鼎的Hugging Face网站(www.huggingface.co)。Hugging Face类似于AI模型领域的Github,该网站上维护了全世界几乎所有的开源大模型的代码,以及开源的模型训练数据。截至2025年3月份为止,在Hugging Face上已经上传了接近150万个大模型,超过30万个开源的训练数据集。所以,这就为小型团队和个人开发者自行部署和训练开源大模型提供了极大的便利。 !image.png 实际上对于绝大多数小型开发团队或个人开发者而言,如果要参与大模型开发的话,在完成大多数的人工智能业务时无须从头构建模型,比较现实的做法,是根据自己要解决的任务类型,按需选择开源大模型,如Deepseek、通义QWen、LLaMA、ChatGLM、Alpaca等。从Hugging Face这样的网站上下载对应的开源大模型、训练数据集以及其他训练和部署的工作,基于这个基础进行定制化行业应用方面的开发和训练。这种方式可以节省大量的模型设计时间,提升开发效率。
- 11 Mar, 2025
以代码案例来详细介绍Function Calling
什么是Function Calling? 众所周知,大语言模型本身只是一个文本生成的工具,虽然有很强的文本生成能力和逻辑推理能力,但是LLM并不具备直接去执行外部功能操作的能力。因此,如果我们要把大模型用于实际产品的应用场景之中,只能利用大模型来生成文本信息的话,那么整个系统真正能够解决问题的能力就会大打折扣。 如果能够将大语言模型所具备的自然语言处理能力,与外部工具/API调用功能相结合的话,就能够显著增强大模型在实际应用场景中解决问题的能力。大模型所具备的function calling就提供了这样一种机制,能够把大模型处理文本的能力,与本地通过function调用的能力相结合,从而为大模型的实际运用提供了极大的拓展空间。 > 大语言模型function calling的工作逻辑,就是在以大语言模型为中心的人工智能系统中,利用大语言模型所具备的推理能力,在系统的运行过程中根据大模型的响应信息,自动调用本地/外部工具或API,从而达到扩展人工智能系统能力的目的。 Function Calling的工作流程解析 以下借助于参考资料1中的一个流程图来对大模型的function calling的执行流程进行说明。 在该流程案例中,当用户向大模型查询”北京今日气温“相关的信息时,因为涉及到对实时信息的查询,因此LLM本身是不可能直接输出正确结果的。此时就需要借助于类似function calling这样的外部工具。 在这种情况下,function calling机制要能够工作,在用户查询这个实时天气信息相关的问题时,大模型需要先知道当遇到这类问题的时候,应该调用的外部函数名称和参数列表是什么。这个信息(也就是给大模型提供的外部工具集Tools参数)与问题一起传递给大模型。 大模型对用户问题(及Tools中包含的外部函数列表中包含的外部函数描述信息)解析时,判断该问题是否需要调用Tools参数中所传递的外部函数。当判断该问题需要调用外部函数时,大模型会在返回的文本信息中包含该外部函数的调用参数列表。如下图的第二步所示。 用户应用程序收到大模型给出的答复后,在本地按照大模型返回的参数列表调用指定的外部函数,得到执行结果,并把执行结果包含在Json结构中,重新返回给大模型。如下图的第三步和第四步所示。 大模型收到外部函数的执行结果后,对其进行数据格式转换,并按照用户问题的语义信息,结合外部函数返回的执行结果信息,重新生成最终的文本答复给用户。如下图的第五步和第六步所示。 !image.png Function Calling的核心工作机制总结 以上所描述的Function Calling完整流程的正常运行,依赖于外部函数能力描述、大模型语义解析以及外部函数调用流程三个方面的支持。 外部函数能力描述 如果需要大模型在应用中能够针对特定的问题,自动生成对外部函数的调用信息,就需要首先向大模型传递外部函数的描述信息。因此,开发者需要预先定义好可供大模型应用在执行过程中可调用的函数清单,其中包含每个函数的API描述、参数规范(类型/格式说明)和返回结构等信息。这样,在大模型遇到相关问题的时候,才能够应该调用哪个外部函数,以及调用这个外部函数应该使用的参数列表。 以下是一个外部函数的描述信息,在向大模型发出问题请求时,在上下文中同时提供给大模型。该外部函数可以用于计算两个浮点数的乘积,调用时应该以字符串的形式提供两个浮点数参数。 大模型语义解析 在这个环节,大模型应该对用户输入的问题及其上下文中包含的外部工具函数集的信息进行解析: - 针对该问题,是直接按照传统LLM的流程来进行处理,还是需要调用问题上下文中中包含的外部函数来辅助? - 针对该问题,如果需要调用外部函数,对用户问题和外部函数集中的函数描述信息进行解析,找到对应于该问题的外部函数。 - 针对该问题,如果需要并且找到解决该问题的外部函数,需要进一步分析语义,并且按照该外部函数描述中的参数信息,整理出来调用该函数的参数列表。 以上信息完整获取后,大模型在其应答信息中把该信息返回给应用程序,下一步要由用户应用程序在本地来执行大模型所指定的外部函数。 外部函数调用流程 这部分就是大模型与本地应用程序配合,能够支持本地应用程序在收到大模型给出的答复信息后,从答复信息中得到要执行的外部函数及其参数列表信息,在本地/通过API执行这个外部功能函数,并把执行结果以及前一步骤完整过程的上下文(再强调一下,LLM没有记忆和状态,每次访问LLM都需要把完整的过程上下文一起上传,方便LLM对该过程有清楚完整的理解)发给大模型,由大模型生成最终的答案并返回。 Function Calling的例子 > Talk is stupid, show me the CODE。 以下以一个Python的代码例子来对大语言模型调用function calling的功能进行说明。该例子的大致运行流程如下。即对于传统LLM可支持的问题仍然通过传统的LLM工作流程来进行处理,但是对于LLM不支持且在function call tools已声明的应用中(此处的例子为查询当日天气、计算复杂浮点数相乘)调用function call功能在本地进行处理并把结果返回大模型,最终统一由大模型来输出答案。 - 蓝色部分模块由应用程序执行,红色部分模块由大模型执行。LLM-function-call-workflow.png 以上代码执行的打印信息为: 可以看到,从该程序的运行流程来看完全符合设计预期: - 当用户输入的问题是“陕西省的省会是哪个城市?”,这个问题是在大语言模型的知识范围和能力之内,因此大语言模型直接给出答案,不需要调用function calling功能。 - 当用户输入的问题是“北京今天天气怎么样?”,这个对于实时问题的查询明显超出了大模型的能力范围,再加上我们在大模型调用时提供了get_weather接口的描述信息,要求大模型在查询天气时,在响应的文本消息中返回调用该函数的参数列表。应用程序收到以后,自动调用get_weather函数并传递该参数列表,得到当前的天气状态,并提供给大语言模型生成最终的答案。 - 同样的道理,当当用户输入的问题是“3.567乘以25.345等于多少“这样的复杂浮点数乘法的计算,这也是明显超出大模型能力的应用,此时按照相同的逻辑,由大模型返回要调用的本次函数名称get_multiply_result和计算参数列表,自动调用本地的get_multiply_result函数得到计算结果以后,再返回给大语言模型生成最终的答案。 参考资料 - 不再混淆了!一文揭秘MCP Server、Function Call与Agent的核心区别
- 10 Mar, 2025
大模型的MCP到底是什么
MCP简介及其核心作用 MCP:Model Context Protocol,即模型上下文协议,是由Anthropic公司(也就是Claude大模型的开发商)在2024年11月份所开源的标准通信协议,其目的是为大语言模型(LLM)与外部服务之间的通信提供一个标准化的接口协议,实现与外部数据源、工具等的无缝连接,从而扩展大模型的能力。 从本质上讲,MCP是一种在智能体Agent和大模型应用程序开发过程中大家共同约定的一种技术规范和协议。它所解决的最大痛点问题,就是AI Agent及其应用开发中调用外部工具的技术门槛过高、碎片化严重的问题。 !image.png MCP通信协议的核心作用可以概括为: - 统一大模型与外部服务之间的通信接口和交互标准。通过这种类似于通用插座的协议设计模式,解决了大模型与不同外部服务(如 GitHub、Slack、本地数据库等)通信数据格式和交互规则不统一的问题。 - 有效的提升了开发效率。对于外部服务的开发者而言,只需要按照MCP规范开发一次MCP Server,所有兼容 MCP 的模型或者其他类型的客户端均可直接调用,无需重复适配。 - 可以优化大模型的工作性能。通过在交互流程中动态加载上下文数据,可以为模型提供更精准的上下文信息输入,从而提升回答的准确性和实用性。 MCP的客户端-服务器架构 MCP协议的实现是基于客户端-服务器架构的。 MCP Client MCP Client一般集成在用户交互英语程序(如基于大模型的应用程序、AI Agent、Chatbox等)之中,负责与MCP Server通信来整合Server所提供的能力和服务,驱动整个应用程序任务的执行。 MCP Client所包含的核心功能如下: - Client在初始化时,要向所有配置的MCP-Server发送能力查询请求,通过其响应消息收集可用的工具列表信息(包括工具名称、描述、参数等)。 - Client根据要执行的任务需求,动态调用合适的工具,并协调多工具之间的协作(例如调用天气查询服务API后结合其结果生成回复)。 - Client可以通过MCP通信协议支持对接本地以及远程的多种Server类型,无需修改代码即可扩展能力。 总之,MCP Client是MCP服务的主动消费方,根据整体应用程序的设计需求以及用户输入的数据信息,有效的对其注册的MCP服务组合进行调用,达成应用的设计目标。 MCP Server MCP Server则是被调用方,负责向外提供具体的能力服务或者数据接口。典型的MCP服务可以包括大模型服务(例如文本、图像生成等)、工具(本地文件、数据读取、爬虫、数据库查询)以及第三方的API(例如天气查询)访问。 MCP Server所包含的核心功能包括: - MCP Server应按照MCP协议所定义的标准化接口来实现,并向Client注册自身所具备的能力(如tool_name、description、参数规范)。MCP Client根据这些标准信息可以了解到MCP Server所能提供的服务,以及应该如何调用这个服务。 - 在运行过程中,MCP Server对外提供服务,处理来自Client的请求并返回结果(如执行Linux命令、查询数据库、调用大模型生成内容等)。 - 此外,作为服务的提供方而言,MCP Server还需要支持资源控制(如限制敏感数据访问)和安全性管理(如身份验证)。 总之,MCP Server是MCP服务的被动提供方,按照MCP协议标准的要求对外其他服务,Client既可以通过标准接口查询MCP Server所提供的服务,也同样可以通过标准接口来调用这些服务。 MCP的通信机制 为适应本地调用和远程调用MCP Server两者方式,MCP协议定义了两种通信机制:针对本地的stdio以及针对远程访问的HTTP/SSE通信。如下图所示: !image.png 针对远程访问的HTTP/SSE方式 SSE(Server Send Events)本身是针对HTTP协议支持长连接通信的一种扩展协议。使能SSE的情况下,HTTP客户端通过发送一个特殊的HTTP GET请求到服务器,请求中包含Accept: text/event-stream头,表明客户端期望接收SSE数据流。服务器响应后会保持连接打开,并可以持续向客户端推送数据。需要注意,SSE连接的情况下,只能由服务器向客户端单向推送数据,客户端不能使用这个链接向服务器发送数据。 当客户端通过HTTP SSE建立持久连接后,服务器就可在有新的数据的情况下,通过该连接向客户端持续推送数据流,实现从服务器到客户端的实时单向通信。而当客户端有新的请求时,仍然需要通过独立的HTTP Post消息向服务器发出请求,服务器则通过之前建立的HTTP SSE长连接向客户端持续返回流式响应。 客户端和服务器之间的所有请求和响应数据均采用JSON-RPC 2.0规范,以确保结构化数据传输。 针对本地访问的stdio方式 针对MCP Sever和Client之间的本地访问需求,MCP则定义了一种stdio(标准输入输出)的本地进程间通信的传输方式。 在这种模式下,MCP客户端在启动的过程中,直接在本地启动MCP服务器应用程序作为其子进程,此后两者之间使用操作系统的标准输入(stdin)和输出(stdout)管道来进行通信。通信流程采用同步交互的请求-响应机制,每条请求需等待对应响应后才返回,客户端通过stdin发送JSON-RPC 2.0格式请求,服务器端则通过stdout返回JSON-RPC 2.0格式的响应。 MCP的典型工作流程解释 对于标准的MCP Client和Server之间的交互流程而言,一般包含两个阶段:初始化及Server能力协商,服务请求与响应。 !image.png 初始化与Server能力协商 在MCP Client应用程序启动时,需要首先向其配置的所有MCP Server发送discover请求,获取其所具备的能力服务列表、描述信息、调用参数详情等。 MCP Server在收到Client的discovery请求后,返回结构化的工具描述信息,例如以下是一个天气查询服务的描述信息:{tool_name: "weather_query", description: "查询实时天气", parameters: {city: str}}。 MCP Client在收到以上信息以后,就能够在后续应用程序的运行过程中需要进行天气查询的时候,按照参数格式的要求调用这个MCP Server所提供的服务。 MCP服务请求与响应 MCP Client在应用程序的执行过程中,根据任务的执行需求来选择合适的工具(可选择的工具列表已经在前面的初始化阶段查询到),然后向MCP Server发送包含访问参数的JSON-RPC请求。例如{"method": "weather_query", "params": {"city": "Beijing"}}。 MCP Server收到Client的请求后,执行与调用参数对应的操作(例如调用OpenWeather API)并向Client返回结果(如温度、湿度数据)。 MCP Client收到Server的响应以后,将多个Server的返回结果整合,最后再传递给大模型以生成最终的响应文本消息(如生成包含天气建议的自然语言回复)。 MCP与function calling的区别 首先明确一点,MCP只是一个通信协议和流程的框架,在MCP应用中并不一定必须要存在AI大模型,用户完全可以开发一个独立的MCP客户端和MCP服务器端,然后在客户端和服务器端基于MCP协议进行通信交互,实现特定的功能。只不过,在AI大模型能够支持MCP协议的情况下,在MCP应用中引入大模型的参与,可以显著提升应用的智能化水平和用户体验。 其次,在MCP应用中,大模型通常扮演MCP Client的角色。具体来说: - MCP Client:大模型作为客户端,可直接通过MCP协议向预先定义MCP Server发起请求,调用外部工具或数据源。这个过程中,大模型负责解析用户的需求,决定需要调用哪些工具,并通过MCP协议与提供MCP服务的服务器进行交互。 - MCP Server:基于MCP协议对外提供外部工具和数据源访问的服务器应用,它将各种资源和服务封装成标准化的接口,供大模型(MCP Client)调用。 从MCP实现的功能上讲,它与function calling非常类似,但是两者有着本质上的区别: - 外部任务的执行主体不同:对于MCP应用而言,外部任务的执行由大模型服务器自行基于MCP协议访问对应的MCP Server,得到答案以后以自然语言返回给用户,而function calling是用户以自然语言提出的问题,大模型解析以后结构化数据的形式返回给用户的应用程序,由用户应用程序在本地执行function功能,因此用户应用程序需要自己处理外部函数的执行和结果的返回。 - 协议和交互的规范性:MCP定义了一个标准化的通信协议和工作流程,规范了MCP Client(大模型)和MCP Server(外部工具和服务)之间的交互逻辑,这使得不同模型和工具之间的交互更加统一和高效;而function calling的具体实现通常依赖于用户应用程序的自行定义,不同的应用程序可能会有不同的调用方式和接口设计,很难在不同的应用之间复用。 参考资料 - 实操干货!MCP全解析,一步步教你借助第三方MCP Server开发Agent - 从0手撕代码搭建MCP Client与Server!详解DeepSeek、ollama、vLLM接入MCP实战!_java_赋范大模型技术社区-DeepSeek技术社区
- 01 Mar, 2025
基于多模态大模型的HTTP API实现图像识别的功能(Python+C)
在之前的笔记中,通过在Python中利用OpenAI SDK访问月之暗面MoonShot的Kimi API,实现了一个单词对话和多轮对话的功能:[[基于Python快速实现与Kimi的AI对话]]。 本篇笔记在以上笔记的基础上,仍然利用OpenAI的API,访问Kimi多模态模型实现图片中对象识别的功能,同时使用Python和C代码实现并提供案例解析。能够利用C语言来访问多模态大模型开放出来的HTTP API接口,也就能够在低成本的MCU处理器上,基于合理的提示词设计,实现需要大模型服务支持才能提供出来的功能, 该例子的功能需求很简单,就是给大语言模型上传一张照片,请大模型给出照片中检测到的对象的描述。本案例使用在网上搜索到的一张小鸟的照片。 !image.png > 相比于与大模型之间进行的简单文本对话,要使用多模态大模型来实现图像识别的功能,最大的不同,在于客户端发给多模态大模型的HTTP请求,需要把图片文件进行base64编码后作为附件,与文本对话的提示词一起上传上去。 多模态大模型进行图像识别的Python实现 以下是基于Kimi多模态大模型进行图像识别的Python实现。需要注意的是: - 代码运行需要先在环境变量中设置MOONSHOT_API_KEY,把自己的API Key设置进去,具体可以参考[[基于Python快速实现与Kimi的AI对话]]。 - 要能够对上传的照片附件中所包含的对象进行识别,需要选择能够支持多模态的大模型,否则会提示模型不支持图像识别的报错信息。对于Kimi而言,此时应该设置的多模态大模型节点应该是moonshot-v1-8k-vision-preview。 可参考代码中的注释对于其运行流程进行详细的理解: 执行以上代码后,大模型返回的信息如下: 多模态大模型进行图像识别的C实现 以上利用Python和Kimi多模态大模型,基于HTTP API的方式可以很方便的实现图像识别的功能。但是大多数低成本的MCU和SOC上是无法运行Python的,所以如果要想在这类处理器上利用大模型来扩展自己的能力,就需要用C语言来实现相同的功能逻辑,这就是这里需要用C语言来重新实现以上功能的原因所在。 以下是使用Kimi多模态大模型进行图像识别功能的C语言代码实现: 可以看到,C语言的实现相对就要复杂很多,以上代码中自行实现了json的解析和base64编码,同时依赖libcurl来实现与Kimi Web API服务器之间的HTTP通信。因此,在编译时应该要链接libcurl库: 同样的,编译后生成的image-recognition要能够正常运行,也需要在Linux系统中设置MOONSHOT_API_KEY环境变量:
- 27 Feb, 2025
基于Python快速实现与Kimi的AI对话
准备工作 对于基于大语言模型的AI应用程序开发而言,用户的应用程序与Kimi或者OpenAI这类大语言模型之间基本上都是使用标准的HTTP接口进行请求,Kimi和OpenAI服务器针对用户应用程序提出的问题进行答复,答复的结果同样封装在HTTP的reponse message中传回给用户应用程序,用户应用程序对结果进行解析后呈现给用户,或者调用其他更加智能化的功能处理(例如AI Agent)。 在此选择国内大语言模型开发商月之暗面Moonshot(也就是Kimi)为例进行与LLM Server之间的AI应用程序交互逻辑的学习和总结。使用Kimi的原因在于: - 在deepseek之前,Kimi一直是我本人稳定使用的AI工具,其稳定性和输出结果的质量,相比于国内的其他大模型而言一直是名列前茅的。 - Moonshot为每个注册用户提供了15元的预充值费用,这个费用对于通过API进行测试开发而言,基本上是够用了。 对于开发基于Moonshot的AI应用程序而言,提前要做的准备工作主要包括两个部分: - 按照Moonshot的要求安装python及其Openai library。 - 申请访问Moonshot服务器的API Key,并且为安全起见以环境变量的方式配置到自己的开发机中。 安装Python及其openai library 在进行以下的调试和开发之前,首先要在本地开发环境中安装python和基于python的openai library: - Moonshot API的使用直接兼容了OpenAI的API和library,所以可以使用openai的python library,修改对接的LLM节点,与Moonshot的server进行通信交互。这样对于AI应用程序开发而言,就可以在很大程度上减少对接不同LLM Server的工作量,算是一个很取巧的设计。 - 与Moonshot Server的对接,要求python版本在3.7.1版本以上,openai library的版本在1.0.0以上。 访问python的官网下载自己系统对应的python:Download Python | Python.org,当前的最新版本是3.13.2。 Python安装以后,直接在系统的终端中输入以下命令安装openai library:pip install openai 以上完成后在终端中查询当前的python环境以及openai library版本是否符合以上要求: !image.png Moonshot API Key的申请与配置 访问Moonshot的用户中心进行API Key的申请:Moonshot AI - 开放平台。点击新建按钮申请API Key,API Key的名称按照自己的使用情况随意填写即可,项目选择默认的default即可,确认后即可生成API Key。 - 需要注意的是,因为API Key涉及到对Moonshot Server和API的访问所产生的费用,一旦泄露出去被滥用后会产生费用。所以为了保密和安全起见,生成的API Key只有在这个时候才能拷贝保存下来,所以务必在此时把API Key拷贝出来并保存在一个安全的地方。 ![[基于Python快速实现与Kimi的AI对话-Moonshot-Key.png]] 为了后续更加方便并且安全的使用,建议把以上API Key以环境变量的方式保存在一个名称为MOONSHOT_API_KEY的环境变量中。 单轮对话通信功能的实现 从通信和代码实现的逻辑上讲,对于单轮对话通信而言,AI应用程序客户端与Moonshot Server之间的通信流程非常简单,就是应用程序把需要向Moonshot Server查询的问题封装在一个HTTP Request消息中,Server收到后会把答复包含在一个HTTP Response消息中,回复给应用程序客户端。应用程序客户端对其消息进行解析后显示出来。无论是Request还是Response消息,都是封装在Json结构的文本内容。 代码具体的实现流程: - 基于OpenAI library创建一个连接Moonshot Server的client,在其中要指定正确的API Key和Server URL; - 把要向Server查询的问题文本字符串按照role+content的格式整理在json结构中; - 基于前面的client和整理好的查询问题json结构体,调用client.chat.completions.create发起与Moonshot Server之间的通信; - 应用程序对Moonshot Server的答复进行解析,正确的情况下显示出来。 以下代码的功能就是实现与Moonshot Server之间的一轮对话,查询当前日期以及截至当前日期中国的人口数量,同时在收到Moonshot Server答复以后,把这个答复信息保存在一个json文本文件中: 值得一提的是以上代码中用messages所包含的请求消息,在与Moonshot Server通信所发出的问题咨询中,默认包含了三种以下角色(role)。如果对prompt engineering有一点了解的话,可以更容易理解各个角色的含义。 - system role:这部分通过文本字符串描述信息定义清楚对于Server角色的定义,如果Server有一个清楚的身份和角色定义,就有可能给出更加准确、帮助性更强的答复。当然这部分是可选的。 - user role:这部分就是用户通过文本字符串形式提交给Server的问题。 - assistant role:这部分是Server针对用户所提出问题给出的答复,同样是文本字符串形式。 执行以上代码以后,在json中所保存的Server响应消息如下: 可以看到,现在测试使用的Moonshot server返回的时间信息是错误的,而且多次调用返回的时间还不一样,可见在应用代码中不能依赖于该服务返回对时间日期要求较高的响应信息。 多轮交互式对话通信功能的实现 以上实现了应用程序与Server之间进行单轮对话的功能。那么如果要实现多轮对话呢?众所周知,HTTP协议是无状态、无记忆的协议,所以服务器端既不会维护通信的连接状态,也不会缓存多轮对话的上下文,因此多轮对话与单轮对话最大的不同,就是要在客户端中自行管理和维护好通话的上下文信息,把问题背景相关的上下文信息全部汇总后发给Server进行问题查询。Server收到后对于多轮通话的内容进行学习和推理,从而给出针对最新问题的答复。 以下代码简单的演示了在多轮会话通信中,客户端如何汇总和整理多轮对话的信息,并且发送给Server进行最新问题的查询。 结语 - 本质上,基于大语言模型的AI应用程序的开发,就是使用HTTP协议与LLM Server之间进行的交互。因为HTTP协议本身是无状态无记忆的,所以每一轮HTTP通信,客户端也就是AI应用程序端都需要把历史会话信息完整的发送给LLM Server进行解析、理解并得到答案。所以,对于AI应用程序的开发而言,既可以采用以上基于Python的OpenAI SDK与LLM Server进行交互,也可以采用任意的HTTP library和框架(例如Kimi的开放平台网站上同样提供了基于Curl的HTTP通信交互的流程参考代码)来实现与LLM Server的通信和交互。 - 总的来说,因为大语言模型服务器使用HTTP协议对外释放API,那么对通过该协议来访问LLM Server的流程开发就非常简单,接下来就是要发挥想象力,根据自己产品面对的应用场景和问题,设计合理的提示词(Prompt Engineering),然后就可以利用LLM Server的强大文本推理和生成能力,实现可以解决问题的人工智能应用。 参考资料 - 基本信息 - Moonshot AI 开放平台