[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"similar-ShaohonChen--Qwen3-SmVL":3,"tool-ShaohonChen--Qwen3-SmVL":61},[4,18,26,36,44,52],{"id":5,"name":6,"github_repo":7,"description_zh":8,"stars":9,"difficulty_score":10,"last_commit_at":11,"category_tags":12,"status":17},4358,"openclaw","openclaw\u002Fopenclaw","OpenClaw 是一款专为个人打造的本地化 AI 助手，旨在让你在自己的设备上拥有完全可控的智能伙伴。它打破了传统 AI 助手局限于特定网页或应用的束缚，能够直接接入你日常使用的各类通讯渠道，包括微信、WhatsApp、Telegram、Discord、iMessage 等数十种平台。无论你在哪个聊天软件中发送消息，OpenClaw 都能即时响应，甚至支持在 macOS、iOS 和 Android 设备上进行语音交互，并提供实时的画布渲染功能供你操控。\n\n这款工具主要解决了用户对数据隐私、响应速度以及“始终在线”体验的需求。通过将 AI 部署在本地，用户无需依赖云端服务即可享受快速、私密的智能辅助，真正实现了“你的数据，你做主”。其独特的技术亮点在于强大的网关架构，将控制平面与核心助手分离，确保跨平台通信的流畅性与扩展性。\n\nOpenClaw 非常适合希望构建个性化工作流的技术爱好者、开发者，以及注重隐私保护且不愿被单一生态绑定的普通用户。只要具备基础的终端操作能力（支持 macOS、Linux 及 Windows WSL2），即可通过简单的命令行引导完成部署。如果你渴望拥有一个懂你",349277,3,"2026-04-06T06:32:30",[13,14,15,16],"Agent","开发框架","图像","数据工具","ready",{"id":19,"name":20,"github_repo":21,"description_zh":22,"stars":23,"difficulty_score":10,"last_commit_at":24,"category_tags":25,"status":17},3808,"stable-diffusion-webui","AUTOMATIC1111\u002Fstable-diffusion-webui","stable-diffusion-webui 是一个基于 Gradio 构建的网页版操作界面，旨在让用户能够轻松地在本地运行和使用强大的 Stable Diffusion 图像生成模型。它解决了原始模型依赖命令行、操作门槛高且功能分散的痛点，将复杂的 AI 绘图流程整合进一个直观易用的图形化平台。\n\n无论是希望快速上手的普通创作者、需要精细控制画面细节的设计师，还是想要深入探索模型潜力的开发者与研究人员，都能从中获益。其核心亮点在于极高的功能丰富度：不仅支持文生图、图生图、局部重绘（Inpainting）和外绘（Outpainting）等基础模式，还独创了注意力机制调整、提示词矩阵、负向提示词以及“高清修复”等高级功能。此外，它内置了 GFPGAN 和 CodeFormer 等人脸修复工具，支持多种神经网络放大算法，并允许用户通过插件系统无限扩展能力。即使是显存有限的设备，stable-diffusion-webui 也提供了相应的优化选项，让高质量的 AI 艺术创作变得触手可及。",162132,"2026-04-05T11:01:52",[14,15,13],{"id":27,"name":28,"github_repo":29,"description_zh":30,"stars":31,"difficulty_score":32,"last_commit_at":33,"category_tags":34,"status":17},1381,"everything-claude-code","affaan-m\u002Feverything-claude-code","everything-claude-code 是一套专为 AI 编程助手（如 Claude Code、Codex、Cursor 等）打造的高性能优化系统。它不仅仅是一组配置文件，而是一个经过长期实战打磨的完整框架，旨在解决 AI 代理在实际开发中面临的效率低下、记忆丢失、安全隐患及缺乏持续学习能力等核心痛点。\n\n通过引入技能模块化、直觉增强、记忆持久化机制以及内置的安全扫描功能，everything-claude-code 能显著提升 AI 在复杂任务中的表现，帮助开发者构建更稳定、更智能的生产级 AI 代理。其独特的“研究优先”开发理念和针对 Token 消耗的优化策略，使得模型响应更快、成本更低，同时有效防御潜在的攻击向量。\n\n这套工具特别适合软件开发者、AI 研究人员以及希望深度定制 AI 工作流的技术团队使用。无论您是在构建大型代码库，还是需要 AI 协助进行安全审计与自动化测试，everything-claude-code 都能提供强大的底层支持。作为一个曾荣获 Anthropic 黑客大奖的开源项目，它融合了多语言支持与丰富的实战钩子（hooks），让 AI 真正成长为懂上",143909,2,"2026-04-07T11:33:18",[14,13,35],"语言模型",{"id":37,"name":38,"github_repo":39,"description_zh":40,"stars":41,"difficulty_score":32,"last_commit_at":42,"category_tags":43,"status":17},2271,"ComfyUI","Comfy-Org\u002FComfyUI","ComfyUI 是一款功能强大且高度模块化的视觉 AI 引擎，专为设计和执行复杂的 Stable Diffusion 图像生成流程而打造。它摒弃了传统的代码编写模式，采用直观的节点式流程图界面，让用户通过连接不同的功能模块即可构建个性化的生成管线。\n\n这一设计巧妙解决了高级 AI 绘图工作流配置复杂、灵活性不足的痛点。用户无需具备编程背景，也能自由组合模型、调整参数并实时预览效果，轻松实现从基础文生图到多步骤高清修复等各类复杂任务。ComfyUI 拥有极佳的兼容性，不仅支持 Windows、macOS 和 Linux 全平台，还广泛适配 NVIDIA、AMD、Intel 及苹果 Silicon 等多种硬件架构，并率先支持 SDXL、Flux、SD3 等前沿模型。\n\n无论是希望深入探索算法潜力的研究人员和开发者，还是追求极致创作自由度的设计师与资深 AI 绘画爱好者，ComfyUI 都能提供强大的支持。其独特的模块化架构允许社区不断扩展新功能，使其成为当前最灵活、生态最丰富的开源扩散模型工具之一，帮助用户将创意高效转化为现实。",107888,"2026-04-06T11:32:50",[14,15,13],{"id":45,"name":46,"github_repo":47,"description_zh":48,"stars":49,"difficulty_score":10,"last_commit_at":50,"category_tags":51,"status":17},4487,"LLMs-from-scratch","rasbt\u002FLLMs-from-scratch","LLMs-from-scratch 是一个基于 PyTorch 的开源教育项目，旨在引导用户从零开始一步步构建一个类似 ChatGPT 的大型语言模型（LLM）。它不仅是同名技术著作的官方代码库，更提供了一套完整的实践方案，涵盖模型开发、预训练及微调的全过程。\n\n该项目主要解决了大模型领域“黑盒化”的学习痛点。许多开发者虽能调用现成模型，却难以深入理解其内部架构与训练机制。通过亲手编写每一行核心代码，用户能够透彻掌握 Transformer 架构、注意力机制等关键原理，从而真正理解大模型是如何“思考”的。此外，项目还包含了加载大型预训练权重进行微调的代码，帮助用户将理论知识延伸至实际应用。\n\nLLMs-from-scratch 特别适合希望深入底层原理的 AI 开发者、研究人员以及计算机专业的学生。对于不满足于仅使用 API，而是渴望探究模型构建细节的技术人员而言，这是极佳的学习资源。其独特的技术亮点在于“循序渐进”的教学设计：将复杂的系统工程拆解为清晰的步骤，配合详细的图表与示例，让构建一个虽小但功能完备的大模型变得触手可及。无论你是想夯实理论基础，还是为未来研发更大规模的模型做准备",90106,"2026-04-06T11:19:32",[35,15,13,14],{"id":53,"name":54,"github_repo":55,"description_zh":56,"stars":57,"difficulty_score":10,"last_commit_at":58,"category_tags":59,"status":17},4292,"Deep-Live-Cam","hacksider\u002FDeep-Live-Cam","Deep-Live-Cam 是一款专注于实时换脸与视频生成的开源工具，用户仅需一张静态照片，即可通过“一键操作”实现摄像头画面的即时变脸或制作深度伪造视频。它有效解决了传统换脸技术流程繁琐、对硬件配置要求极高以及难以实时预览的痛点，让高质量的数字内容创作变得触手可及。\n\n这款工具不仅适合开发者和技术研究人员探索算法边界，更因其极简的操作逻辑（仅需三步：选脸、选摄像头、启动），广泛适用于普通用户、内容创作者、设计师及直播主播。无论是为了动画角色定制、服装展示模特替换，还是制作趣味短视频和直播互动，Deep-Live-Cam 都能提供流畅的支持。\n\n其核心技术亮点在于强大的实时处理能力，支持口型遮罩（Mouth Mask）以保留使用者原始的嘴部动作，确保表情自然精准；同时具备“人脸映射”功能，可同时对画面中的多个主体应用不同面孔。此外，项目内置了严格的内容安全过滤机制，自动拦截涉及裸露、暴力等不当素材，并倡导用户在获得授权及明确标注的前提下合规使用，体现了技术发展与伦理责任的平衡。",88924,"2026-04-06T03:28:53",[14,15,13,60],"视频",{"id":62,"github_repo":63,"name":64,"description_en":65,"description_zh":66,"ai_summary_zh":66,"readme_en":67,"readme_zh":68,"quickstart_zh":69,"use_case_zh":70,"hero_image_url":71,"owner_login":72,"owner_name":73,"owner_avatar_url":74,"owner_bio":75,"owner_company":76,"owner_location":77,"owner_email":78,"owner_twitter":78,"owner_website":78,"owner_url":79,"languages":80,"stars":93,"forks":94,"last_commit_at":95,"license":78,"difficulty_score":96,"env_os":97,"env_gpu":98,"env_ram":97,"env_deps":99,"category_tags":105,"github_topics":78,"view_count":32,"oss_zip_url":78,"oss_zip_packed_at":78,"status":17,"created_at":107,"updated_at":108,"faqs":109,"releases":149},5142,"ShaohonChen\u002FQwen3-SmVL","Qwen3-SmVL","将SmolVLM2的视觉头与Qwen3-0.6B模型进行了拼接微调","Qwen3-SmVL 是一个超轻量级的中文多模态大模型，旨在让小型语言模型具备“看图说话”的能力。它巧妙地将 SmolVLM2 高效的视觉编码模块与通义千问 Qwen3-0.6B 强大的中文语言理解能力进行了“拼接微调”。这一方案主要解决了当前端侧小模型（如 SmolVLM2）虽能低显存运行却不懂中文的痛点，让用户能在仅占用约 1GB 显存的设备上，使用流畅的中文进行图文交互。\n\n该工具特别适合资源受限场景下的开发者、研究人员以及希望在本地部署多模态应用的技术爱好者。其核心技术亮点在于创新的“模型拼接”思路：保留了成熟的视觉特征提取与映射架构，仅替换并微调了语言模型部分及特征对齐层。通过复用现有高质量组件，Qwen3-SmVL 以极低的训练成本实现了视觉与中文语义的高效融合，为构建低成本、高性能的端侧多模态应用提供了全新的技术路径和可复现的代码范例。","# Qwen3-\"VL\"——超小中文多模态模型的“拼接微调”之路1（附代码和SwanLab记录）\n\n* 作者：情感机器实验室——陈少宏\n\n* 邮箱：\u003Cshaohon_chen@115lab.club>\n\n* GitHub：[https:\u002F\u002Fgithub.com\u002FShaohonChen\u002FQwen3-SmVL](https:\u002F\u002Fgithub.com\u002FShaohonChen\u002FQwen3-SmVL)\n* SwanLab：[https:\u002F\u002Fswanlab.cn\u002F@ShaohonChen\u002FQwen3-SmVL\u002Foverview](https:\u002F\u002Fswanlab.cn\u002F@ShaohonChen\u002FQwen3-SmVL\u002Foverview)\n* 数据集：[https:\u002F\u002Fhuggingface.co\u002Fdatasets\u002FHuggingFaceM4\u002Fthe_cauldron](https:\u002F\u002Fhuggingface.co\u002Fdatasets\u002FHuggingFaceM4\u002Fthe_cauldron)\n\n> 💚 **特别感谢**  \n> 感谢 [@zhihuazhao-bit](https:\u002F\u002Fgithub.com\u002Fzhihuazhao-bit) 帮助审阅和修复了代码中众多的小 bug，并在 NV 上完成了测试！\n> \n> 感谢 [@lovelyyoshino](https:\u002F\u002Fgithub.com\u002Flovelyyoshino)帮助填补了数据集中的部分错误，并且提升了模型效果！\n\n## 摘要\n\n最近Huggingface团队发布了超小多模态模型SmolVLM2，可以做到端侧1GB显存推理。在怀着惊喜试用后发现，虽然模型有极其强大的视觉文本理解能力，但是模型却无法理解中文。这对一个“四六级压线过”的笔者来说十分不友好。刚好前段时间做SwanLab硬件检测适配时有一台未到期的沐曦曦云C500服务器，因此萌生了使用**沐曦GPU芯片**微调、把当前中文小模型扛把子Qwen3与SmolVLM2直接微调拼接的想法。\n\n本教程将介绍一种模型拼接的思路，将SmolVLM2的视觉模块（0.09B）与Qwen3最小的模型（0.6B）进行对齐微调，最终使得Qwen模型具备一定的视觉理解能力。由于笔者时间有限且考虑到文章篇幅的原因，因此该系列预计将以系列的方式放出。篇幅规划如下：\n\n* **第一篇**：如何构建和微调一个拼接模型（**本篇博客**）\n* **第二篇**：模型测评、数据集优化、回答人类对齐\n* **第三篇**：微调技巧介绍、视觉位置编码改动与模型结构优化\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_2e91ebdd1bc6.png\" alt=\"PPAP\" width=\"400\" \u002F>\n  \u003Cfigcaption>I have a Qwen, I have a SmolVLM...\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n\u003Cdiv style=\"background-color:#fff3cd; color:black; padding:10px; border-radius:4px; border:1px solid #fbe5b0; width: 90%; max-width: 100%; margin: auto;\">\n  ⚠️关于算力的注意：本教程涉及VLM微调训练，对算力要求较高，需要40G及以上的GPU显存才能运行本教程的训练代码。\n\u003C\u002Fdiv>\n\n## 目录\n\n* [SmolVLM2的背景知识](#SmolVLM2的背景知识)\n* [模型拼接和微调思路简介](#模型拼接和微调思路简介)\n* [模型拼接实现和关键代码讲解](#模型拼接实现和关键代码讲解)\n* [微调数据集构建](#微调数据集构建)\n* [微调方法与代码实现](#微调方法与代码实现)\n* [微调训练&结果展示](#微调训练&结果展示)\n* [代码及数据集链接汇总](#代码及数据集链接汇总)\n\n## SmolVLM2的背景知识\n\n首先，我们先回顾一下SmolVLM2模型的构建方案，SmolVLM2模型的整体包括三大块：视觉模型层，特征映射层和大语言模型层，见下图：\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_7c9119bc1060.png\" alt=\"smolvlm2\" width=\"400\" \u002F>\n  \u003Cfigcaption>SmolVLM2的架构图\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n这个设计是现在比较常见的VLM方案。核心设计思想就是让视觉模型的输出特征与经过embedding的文本特征直接拼接后输入到语言模型（LLM）当中，没有交叉注意力等模块。相比于早期LLaVA等架构，这种最大的优点就是可以最大程度复用已有的语言模型。以Qwen2.5-VL为例，其3B、7B、72B模型大小指的只是LLM部分，并没有包含Vision模块，实际上3B模型的参数量接近4B，视觉模块大概0.4B左右，三个不同大小的VLM使用的是统一的视觉模型。对于一些较大的VLM来说，构建视觉模型时绝大多数的训练都集中在特征映射模块和视觉模块，只在最后阶段为了最终效果进行整体微调时才会调整语言模块。保证了VLM的语言能力。\n\n下面简述一下各个模块的细节：\n\n* 视觉模型层：SmolVLM2-256M版本用的是Google的SigLip模型，一个基于ViT的视觉模型，选用的是最小的SigLip-93M的版本，HF论文里没具体写是直接用的SigLip的参数还是他们从零构建的（有注意到的读者可以评论留言下）。在SmolVLM2代码中对应的是`SmolVLMVisionTransformer`类\n\n* 特征映射层：就是一个简单的MLP，不过SmolVLM2中为了降低图像分辨率还做了一个Pixel shuffle来降低图像分辨率，进一步减少视觉的Token占用，减少了文本长度。HF团队在论文里提到对于参数量较小的VLM来说使用Pixel shuffle还能提升性能。但可训练参数其实就是一个单层的神经网络，这个模块的核心作用就是做特征对齐，将视觉特征从768维（SigLip的维度）映射到576维（SmolLLM2的维度）\n\n* 大语言模型：SmolVLM2-256M模型使用的文本模型是SmolLM-135M版本。可能是由于模型较小，HF团队在论文中说到训练时仅采用两阶段训练：大规模图文训练+针对视频任务的专门微调。为了保障模型的文本能力HF团队在训练数据中参杂了大概14%的纯文本微调数据。不过考虑到视觉模块本身参数量（93M）大小接近于文本模型（135M），因此笔者推测相比于冻结文本模型，数据平衡在这之中会起到更关键的作用。\n\nHF团队在原文中还提到了许多影像小模型VLM性能的trick，感兴趣的读者可以进一步参考SmolVLM2的论文\n\n## 模型拼接和微调思路简介\n\n正所谓顶级食材（模型）只需要最简单的烹饪。模型拼接的思路非常简单直接，基本就三步：\n\n1. 调整SmolVLM2的“上下文控制格式”，使得其与Qwen3兼容。\n\n2. 将模型的文本部分直接从SmolLM2换成Qwen3-0.6B，包括其文本tokenizer和词嵌入、文本模型、以及模型最后输出的语言模型头（LM Head）。\n\n3. 需要重新初始化特征映射层的MLP，从768->576的单层神经网络改成768->1024的单层神经网络即可。\n\n整体架构和对图文对前后处理依旧保持SmolVLM2的流程不变，具体改动见下图：\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_01013f7bc9da.png\" alt=\"concatation\" width=\"400\" \u002F>\n  \u003Cfigcaption>将Qwen3-0.6B替换SmolVLM2的语言模型部分\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n笔者接下来详细介绍下为了实现“拼接”，具体改动的地方，供之后有类似的任务的读者参考。\n\n## 模型拼接实现和关键代码讲解\n\n### 第一处改动：SmolVLM2的Tokenizers部分\n\n首先需要改动的就是需要改动的是SmolVLM2的Tokenizers部分，这里面主要是涉及两个问题：\n\n* 第一个问题是要将SmolVLM2用于指示图像位置的特殊令牌（Special Token）加入到Qwen3的Tokenizer当中，这么做的目的是防止SmolVLM2的图像Token`\u003Cimage>`被切分为`\u003C`、`image`、`>`三块。幸运的是，Qwen3本身在Tokenizers中预留了未来用于多模态的特殊特殊令牌`\u003C|image_pad|>`。因此读者直接使用了`\u003C|image_pad|>`代替了`\u003Cimage>`。用于在文本中预留图像特征的插入点。\n\n* 第二个问题是：SmolVLM2的chat_template和Qwen3的chat_template差别极大。chat_template的作用是通过格式化文本让模型清楚知道不同Token所代表的背景信息。用最近比较流行的话来说就是“上下文工程”（Context Engineering）。\n\n这里我列举了一下Qwen3、SmolVLM2、Qwen2.5-VL在聊天场景下的上下文，供读者参考。\n\n**Qwen3聊天上下文格式**\n\n以给一张图片，问题是“你的名字是什么?”，模型回答是“我的名字是Qwen”为例子。模型的上下文如下：\n\n```txt\n\u003C|im_start|>user\n你的名字是什么?\u003C|im_end|>\n\u003C|im_start|>assistant\n\u003Cthink>\n\n\u003C\u002Fthink>\n\n我的名字是Qwen\u003C|im_end|>\n\n```\n\n注意Qwen3上下文是没有预留图像位置的，但相比于一般的LLM和VLM多了一个用于插入模型思考过程的`\u003Cthink>\u003C\\think>`，以及包含额外的函数调用控制文本。为了便于读者理解，读者在在下面举了一个函数调用的例子。这些函数调用上下文用于控制模型调用外部函数、API或者MCP接口和接收其返回的信息。\n\n考虑到篇幅限制，本文就不粘贴带函数调用、推理、思考等一系列上下文的信息了（笔者打印了下发现实在太长了）。感兴趣的读者可以在Qwen3的官方文处了解详细设计\n\n* [Qwen3函数调用案例](https:\u002F\u002Fqwen.readthedocs.io\u002Fzh-cn\u002Flatest\u002Fframework\u002Ffunction_call.html#the-example-case)\n\n可以说正是这些复杂的上下文信息让模型有可能实现推理、调用函数等多样化的能力。包括多模态理解任务也需要先对上下文进行设计。\n\n**SmdwadwdoVLM2聊天上下文格式：**\n\n以给一张图片，问题是“How many dog in there.”，模型回答是“There are Three dogs.”为例子。三种不同模型的上下文如下：\n\n```txt\n\u003C|im_start|>User:\u003Cfake_token_around_image>\u003Crow_1_col_1>\u003Cimage>...\u003Cimage>\u003Cfake_token_around_image>\u003Crow_1_col_2>\u003Cimage>...\u003Cimage>\u003Cfake_token_around_image>\u003Crow_1_col_3>\u003Cimage>...\u003Cimage>...\u003Cfake_token_around_image>\u003Crow_4_col_4>\u003Cimage>...\u003Cimage>\n\n\u003Cfake_token_around_image>\u003Cglobal-img>\u003Cimage>...\u003Cimage>\u003Cfake_token_around_image>How many dog in there.\u003Cend_of_utterance>\nAssistant: There are Three dogs.\u003Cend_of_utterance>\nAssistant:\n```\n\n看起来非常乱，是因为有大量的`\u003Cimage>`占位符。`\u003Cimage>...\u003Cimage>`之间是许多的`\u003Cimage>`，笔者为了文章观感删掉了大量的占位符。注意模型的回车、空格均为上下文的一部分，在进行推理时需要严格遵守缩进关系。\n\n但是我们仍能找到熟悉的内容，如`User:`，`Assistant:`等用于提示模型用户的输入与模型应当输出的位置。这些关键词和Qwen类似。\n\n读者注意到了除了`\u003Cfake_token_around_image>`，`\u003Cimage>`等用于指示图像的词，还出现了\u003Crow_1_col_1>这种位置指示符，这是因为SmolVLM2为了防止降采样对图像分辨率影响，专门使用了`image splitting`技术，简单来说就是将全局图和高清的局部图共同输入到模型当中（见下图`image splitting`模块），感兴趣的读者可在文末找到HF的技术报告了解详细技术。\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_22901de9c61d.png\" alt=\"image-split\" width=\"400\" \u002F>\n  \u003Cfigcaption>SmolVLM2的完整推理流程，可以看到在图像输入前使用`image splitting`进行了预切分\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n**本博文的拼接模型Qwen3-SmVL模型**\n\n相比于Qwen3，SmolVLM2少了很多上下控制的\n\n为了尽可能保存或者说预留Qwen3的思考、函数调用等能力，笔者最终选择将SmolVLM2对于图像特征的排列插入到Qwen3的上下文格式当中。最终上下文格式如下：\n\n```txt\n\u003C|im_start|>user\n\u003Cvision_start>\u003Crow_1_col_1>\u003C|image_pad|>（图像插入的地方）\u003C|image_pad|>\u003Cvision_start>\n（用户提问的地方）\n\u003C|im_end|>\n\u003C|im_start|>assistant\n\u003Cthink>\n\n\u003C\u002Fthink>\n\n（模型回答的地方）\u003C|im_end|>\n\u003C|endoftext|>\n```\n\n可以看到读者尽量保持了与Qwen3的风格和复用特殊令牌。这样能够使得后续拼接的Qwen3-0.6B模型不至于受到上下文差异过大带来的性能损耗。实际上在设计微调上下文时应尽量与模型先前训练的任务接近，以减少微调带来的性能损失。\n\ntransformers实现模型上下文格式控制的代码并非python语言，而是一种前端文本格式控制的语言Jinja。这个语言的变量作用域设计简直可以说是有魔法在里面。配合上Qwen3功能丰富且复杂的上下文策略，让笔者花了2个小时用于修改chat_teamplate。这里笔者不赘述如何修改chat_template，感兴趣的读者可以去文末代码链接寻找`chat_template.jinja`文件，笔者专门将chat_template模版拿出来，并且做了格式化方便读者阅读。未来有时间了笔者专门写一篇模型上下文控制与jinja语言的博客。\n\n### 第二处改动：替换SmolVLM2的SmolLM2模型为Qwen3-0.6B\n\n替换模型这块没什么复杂的，主要是需要处理Transformers比较复杂的嵌套逻辑。Tranformers通常建议模型将预训练模型backbone和下游任务分开来。改动逻辑图如下：\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_d38a64f201e1.png\" alt=\"change_model\" width=\"400\" \u002F>\n  \u003Cfigcaption>替换smolvlm2的文本模块和语言模型头\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n以Qwen3为例，预训练Backbone模型为`Qwen3Model`，仅仅包含embedding层、各个Decoder层，最后输出的是所有输入token的hidden state。负责下游任务的Qwen3提供了包括：用于因果语言序列生成的`Qwen3ForCausalLM`，也就是大家常用的语言生成。负责句子分类`Qwen3ForSequenceClassification`，使用最后一个生成的token输入到一个单层MLP做序列级分类，做句子情绪分类等可以用这个下游模型；`Qwen3ForTokenClassification`用于做Token级分类，比如语言实体抽取任务可以使用这个下游模型。`Qwen3ForQuestionAnswering`则是专门做抽取式问答任务的模型，核心思想是输入（问题，参考文本）让模型从参考文本中找到与问题最相关的一段，这类任务由于RAG系统的出现没那么流行了，未来笔者专门出一个系列的教程阐述除了因果语言序列生成以外的任务则怎么微调。\n\n**关键代码如下**\n\n```python\nfrom transformers import (\n    AutoProcessor,\n    AutoModelForImageTextToText,\n    AutoTokenizer,\n    AutoModelForCausalLM\n)\n\n# 替换text模型和head\nsmolvlm2_02B_model = AutoModelForImageTextToText.from_pretrained(\n    \"model\u002FSmolVLM2-256M-Video-Instruct\",\n    torch_dtype=torch.bfloat16,\n    _attn_implementation=\"eager\",\n).to(device)\n\nqwen3_06b_model = AutoModelForCausalLM.from_pretrained(\n    \"model\u002FQwen3-0.6B\", torch_dtype=torch.bfloat16\n).to(device)\n\nsmolvlm2_02B_model.model.text_model = qwen3_06b_model.model\nsmolvlm2_02B_model.lm_head = qwen3_06b_model.lm_head\n...\n```\n\n接下来比较复杂的是替换所有的关键变量，比如模型内用于在文本序列中为图像特征预留的占位符`image_token_id`，用于指示停止生成的`eos_token_id`，和计算loss值会用到的`vocab_size`，Qwen的词表大小为151936，远远大过SmolVLM2的词表49280。具体代码如下：\n\n```python\n...\n# 替换词表大小\nsmolvlm2_02B_model.vocab_size = qwen3_06b_model.vocab_size\nsmolvlm2_02B_model.model.vocab_size = qwen3_06b_model.vocab_size\nsmolvlm2_02B_model.config.vocab_size = qwen3_06b_model.vocab_size\nsmolvlm2_02B_model.config.text_config.vocab_size = qwen3_06b_model.vocab_size\nsmolvlm2_02B_model.model.config.vocab_siz = qwen3_06b_model.vocab_size\nsmolvlm2_02B_model.model.config.text_config.vocab_size = qwen3_06b_model.vocab_size\n# 替换图像token\nsmolvlm2_02B_model.image_token_id = 151655\nsmolvlm2_02B_model.model.image_token_id = 151655\nsmolvlm2_02B_model.config.image_token_id = 151655\nsmolvlm2_02B_model.model.config.image_token_id = 151655\n# 替换模型生成停止符\nsmolvlm2_02B_model.generation_config.eos_token_id = 151645\n···\n```\n\n上面的代码可以看到在替换各个变量时需要将嵌套模型的变量一起替换掉，笔者之前训练时就因为仅仅替换了`SmolVLMForConditionalGeneration`而忘记替换`SmolVLMModel`中的`image_token_id`，导致语言模型接收不到图像特征，最后表现出来就是loss下降的极快且低，grad_norm看起来也学到位了，一推理效果特别差，附上错误训练的损失图：\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_b87c02c22cc6.png\" alt=\"fail_train\" width=\"800\" \u002F>\n  \u003Cfigcaption>SwanLab记录训练结果展示：蓝色为错误训练的完整微调loss图，可以看到损失下降很快，然而实际推理会发现模型并没有图像理解能力。冻结语言模型头（红色）后发现grad_norm为零且loss不收敛，正确的应该是黄色\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n笔者最早没发现改动错误，先做完整微调（蓝色曲线）后发现损失下降很快达到了0.1以下，结果实际一推理发现模型完全没有图像理解能力，就补了一个冻结语言模型只微调视觉模型的实验（红色曲线），结果发现损失完全没下降，才定位到了视觉特征传入有问题。后续修复后正确的损失下降过程见黄色图像。\n\n### 第三处改动：构建和替换特征映射层\n\n这个相对较简单，只需要重新构建一个维度对齐的`SmolVLMConnector`即可。Qwen3的hidden_dim是1024，SigLip的hidden_dim是768，因此构建一个768➡️1024映射的`SmolVLMConnector`即可。代码如下：\n\n```python\n···\n# 构建配置并且创建连接器\n@dataclass\nclass VisionConfig:\n    hidden_size: int = 768\n\n@dataclass\nclass TextConfig:\n    hidden_size: int = 1024\n\n@dataclass\nclass ConnectConfig:\n    scale_factor: int = 4\n    vision_config: VisionConfig = VisionConfig()\n    text_config: TextConfig = TextConfig()\n\nnew_connector_config = ConnectConfig()\n\n# 替换 SigLit 到 LLM 的 connector 层\nnew_connector = SmolVLMConnector(new_connector_config).to(device).to(torch.bfloat16)\nsmolvlm2_02B_model.model.connector = new_connector\n···\n```\n\n## 微调数据集构建\n\n笔者最初计划寻找中文多模态数据集，但发现相关的资料比较少。因此决定先用英文的多模态数据集凑合一下。之后再考虑通过数据合成的方式将部分数据翻译为中文。关于数据合成和配比的问题将在之后的博客讨论。\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_c8dede9d64b3.png\" alt=\"the_cauldron\" width=\"400\" \u002F>\n  \u003Cfigcaption>the_cauldron数据集logo\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n这里为了方便本项目直接使用HuggingFace团队整合的多模态数据集the Cauldron数据集，Cauldron翻译成中文类似于煮东西的“釜”，不知道HF团队是不是玩“炼丹”的梗。这个数据集整合了50个视觉微调任务数据集的训练集，用于微调Huggingface发布的多模态模型Idefics2模型。这50多个数据集都被处理成了一致的格式（见下图），共有1,880,992条数据，完整下载约169G，非常方便使用。\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_000d29fdea47.png\" alt=\"data_show\" width=\"800\" \u002F>\n  \u003Cfigcaption>数据集样本展示\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n不过可惜数据集的文本都是英文内容，且绝大多数数据集的回复非常短，只有一个词，这也给后面模型训练带来了麻烦。本篇博客暂时不讨论关于数据构建和配比的问题，后续有时间了专门做相关的实验。本博客先以为Qwen3模型带来视觉能力为核心目标。\n\n数据集的下载链接如下，国内推荐用modelscope下载：\n\n* [HuggingFace Hub](https:\u002F\u002Fhuggingface.co\u002Fdatasets\u002FHuggingFaceM4\u002Fthe_cauldron)\n* [ModelScope](https:\u002F\u002Fmodelscope.cn\u002Fdatasets\u002FAI-ModelScope\u002Fthe_cauldron)\n\n笔者在实际测试时发现\"mimic_cgd\"，\"localized_narratives\"，\"okvqa\"，\"ocrvqa\"，\"clevr_math\"这几个子数据集加载有点异常，建议使用此数据集训练的读者手动处理下，社区也有用户反馈这几个数据可以在原始来源处额外下载，未来笔者将会补全这几个数据集重新上传一次完整版的the Cauldron数据集。\n\n## 微调方法与代码实现\n\n### 冻结模型参数微调\n\n整体微调方法采用了CLM模型通常的Teacher Forcing的学习方法，损失就是标准的交叉熵损失。考虑到此次本教程的目标是先确保模型具备中文多模态能力（优化模型性能等之后撰写其他博客），因此为了实验效率，在对齐微调阶段**采用冻结视觉模型与文本模型，仅微调特征映射器和语言模型头**的方法。\n\n冻结模型参数的核心代码如下：\n\n```python\ndef freeze_model(qwen_smvl):\n    for _, param in qwen_smvl.model.text_model.named_parameters():\n        param.requires_grad = False\n    for _, param in qwen_smvl.model.vision_model.named_parameters():\n        param.requires_grad = False\n    return qwen_smvl\n```\n\n冻结后训练参数、模型总参数、与占比如下：\n\n```txt\ntrainable params: 12.00M || all params: 662.87M || trainable%: 1.81\n```\n\n### 文本长度，损失掩码和截断策略\n\n**文本长度**\n\n由于视觉特征需要占据大量的文本长度，笔者简单测试了下the_cauldron图像占0.8K到1.3K左右的token。而数据集中大多数文本token数在200-500左右，极少情况会有3-4K的情况。因此笔者统一采用2K的文本长度，超出部分截断处理。\n\n这里有一个不同于文本微调的细节要注意，文本截断长度不能小于图像token，否则会导致模型在进行特征拼接时报错（当然图像特征如果被截断了，这条训练数据也就没意义了）。因此对于显存不足64G的同学如果需要适当缩短文本长度（不建议低于1.5K），最好连同图像分辨率也缩小些。在后面的博客我们会专门增加对减少图片token占用的研究。\n\n同样由于文本长度受限，且图像特征没法截断，我们也没使用“packing dataset”的方法提升模型的训练效率。\n\n考虑到部分数据集存在多张图片的情况，考虑到本次训练仅采用2k的文本长度（与之对比HF在训练SmolVLM-256M版本采用的是8K的文本长度，2.2B版使用了16K的文本长度）。针对单条数据中存在多张图片的情况仅仅选用第一张。\n\n**损失掩码**\n\n在采用Teacher Forcing的学习方法时，文本微调中损失掩码有两种策略：\n\n* 对包含“用户问题”和“模型回复”的完整文本进行微调优化\n* 仅对“模型回复”部分进行微调优化\n\n这两种策略的对比如下图：\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_9da36413db54.png\" alt=\"mask\" width=\"800\" \u002F>\n  \u003Cfigcaption>两种微调掩码策略的差异，通常建议选择“仅微调模型回答部分”以增强泛化性\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n通常来说使用“仅微调模型回复部分”的策略模型更容易泛化（这点与HF在SmolVLM2的论文提到的trick）。然而笔者为了提高训练效率选择了完整文本微调。可以在后续博客中增加消融实验做进一步对比。\n\n值得注意的是，在进行完整文本微调时，需要单独屏蔽Image Token以防止对图像占位token计算损失，影响模型表现。\n\n**关键代码如下：**\n\n```python\ndef data_collate_fix2k(examples, processor, device, max_length=2048):\n    batch_text = []\n    batch_image = []\n    for example in examples:\n        images = example[\"images\"][:1]  # 只允许一张图，不然显存压力太大\n        batch_image.append(images)\n        image_num = len(images)\n        chat_texts = example[\"texts\"][0]\n        messages = [\n            {\n                \"role\": \"user\",\n                \"content\": [{\"type\": \"image\"}] * image_num\n                + [{\"type\": \"text\", \"text\": chat_texts[\"user\"]}],\n            },\n            {\n                \"role\": \"assistant\",\n                \"content\": [{\"type\": \"text\", \"text\": chat_texts[\"assistant\"]}],\n            },\n        ]\n        text = processor.apply_chat_template(\n            messages, enable_thinking=False, add_generation_prompt=False\n        )\n\n        batch_text.append(text)\n\n    batch = processor(\n        text=batch_text,\n        images=batch_image,\n        max_length=max_length,\n        return_tensors=\"pt\",\n        padding=\"max_length\",\n        truncation=True,\n    )\n    labels = batch[\"input_ids\"].clone()\n    labels[labels == processor.tokenizer.pad_token_id] = -100\n    labels[labels == processor.image_token_id] = -100\n    batch[\"labels\"] = labels\n    return batch.to(device, dtype=torch.bfloat16)\n```\n\n### 微调超参数设置\n\n**学习率**\n\n由于仅仅针对特征映射层（connector）进行训练，且conntector由于要对齐Qwen3的维度因此参数为随机初始化（理论上可以采用一些独特的初始化策略提升性能，但考虑到模型较小因此笔者没关注初始化策略）。因此学习率设置为lora中较为流行的1e-4学习率策略。\n\n为了保障有效收敛，学习率衰减基本是必备的trick，采用的是社区比较流行的cosine学习率衰减，衰减至0。warm up为整体步长的10%（在超过1000k step的情况下固定为50）。\n\n**batch size**\n\nBatch size通常来说越大越好，然而由于VLM模型的文本长度太大，因此采用每卡1 batch和4梯度累加（grad accelerate），在8卡训练中等效32 Batch size。\n\n**训练参数设置代码**\n\n```python\ntraining_args = TrainingArguments(\n    seed=42,\n    data_seed=42,\n    max_steps=200,\n    # num_train_epochs=1,  # 训练1个epoch 约1k steps\n    per_device_train_batch_size=1,\n    gradient_accumulation_steps=4,\n    dataloader_pin_memory=False,\n    warmup_ratio=0.1,\n    learning_rate=1e-4,\n    lr_scheduler_type=\"cosine\",\n    weight_decay=0.01,\n    logging_steps=5,\n    eval_strategy=\"steps\",\n    eval_steps=0.125,\n    save_strategy=\"steps\",\n    save_steps=0.125,\n    save_total_limit=8,\n    optim=\"adamw_torch\",\n    bf16=True,\n    output_dir=f\".\u002Fmodel\u002Ffreeze_except_connector_cocovqa\",\n    overwrite_output_dir=False,\n    report_to=\"swanlab\",\n    run_name=\"freeze_except_connector_cocovqa\",\n    remove_unused_columns=False,\n    gradient_checkpointing=False,\n)\n```\n\n### 训练环境\n\n微调代码基于沐曦的C500国产通用计算GPU实现，显存为64G。沐曦的AI芯片基本完全兼容pytorch和huggingface transformers场景，并且在做多模态训练时相比较其他国产AI芯片罕见的没有兼容性问题。读者在尝试本项目代码时可以采用Nvidia显存40G以上的显卡运行本教程。\n\n**笔者个人感觉沐曦的GPU整体适配效果还是非常好的，没遇到适配性的问题。体验上和用NV的GPU做训练没什么区别**。笔者自己也用过好几款国产GPU，沐曦的体验肯定是名列前茅的，包括代码中有指定flash attention在沐曦GPU上都能成功迁移，这点非常值得给沐曦团队点个赞。希望国产GPU生态能越发展越好，造福广大炼丹师；）。\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_f797a2e3f645.jpg\" alt=\"muxi-gpu\" width=\"400\" \u002F>\n  \u003Cfigcaption>沐曦国产GPU，笔者用的云端服务器没见过真机，因此找了张网图\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n训练环境的话除了安装GPU对应的驱动和pytorch外，本教程需要额外安装Huggingface全家桶，如下：\n\n```txt\ntorch   # 推荐版本>=6.0\ntorchvision\ntransformers>=4.53.0\naccelerate\ndatasets\nnum2words   # SmolVLM2需要\n```\n\n额外补充一句，如果采用沐曦GPU训练的话，需要在沐曦官方文档处寻找[沐曦版torch](https:\u002F\u002Fdeveloper.metax-tech.com\u002Fsoftnova\u002Findex)的安装方式进行下载。其他HF环境和NV基本一样。附赠一个沐曦查看GPU的命令：\n\n```bash\nmx-smi\n```\n\n效果如下：\n\n```bash\n=================== MetaX System Management Interface Log ===================\nTimestamp                                         : Sat Jul 12 14:58:51 2025\n\nAttached GPUs                                     : 8\n+---------------------------------------------------------------------------------+\n| MX-SMI 2.1.12                       Kernel Mode Driver Version: 2.12.13         |\n| MACA Version: 2.29.0.19             BIOS Version: 1.22.3.0                      |\n|------------------------------------+---------------------+----------------------+\n| GPU         NAME                   | Bus-id              | GPU-Util             |\n| Temp        Pwr:Usage\u002FCap          | Memory-Usage        |                      |\n|====================================+=====================+======================|\n| 0           MetaX C500             | 0000:0e:00.0        | 0%                   |\n| 36C         69W \u002F 350W             | 5680\u002F65536 MiB      |                      |\n+------------------------------------+---------------------+----------------------+\n| 1           MetaX C500             | 0000:0f:00.0        | 0%                   |\n| 38C         70W \u002F 350W             | 4986\u002F65536 MiB      |                      |\n+------------------------------------+---------------------+----------------------+\n| 2           MetaX C500             | 0000:10:00.0        | 0%                   |\n| 37C         69W \u002F 350W             | 4986\u002F65536 MiB      |                      |\n+------------------------------------+---------------------+----------------------+\n| 3           MetaX C500             | 0000:12:00.0        | 1%                   |\n| 37C         71W \u002F 350W             | 4986\u002F65536 MiB      |                      |\n+------------------------------------+---------------------+----------------------+\n| 4           MetaX C500             | 0000:35:00.0        | 0%                   |\n| 37C         70W \u002F 350W             | 4986\u002F65536 MiB      |                      |\n+------------------------------------+---------------------+----------------------+\n| 5           MetaX C500             | 0000:36:00.0        | 1%                   |\n| 36C         68W \u002F 350W             | 4986\u002F65536 MiB      |                      |\n+------------------------------------+---------------------+----------------------+\n| 6           MetaX C500             | 0000:37:00.0        | 0%                   |\n| 39C         73W \u002F 350W             | 4986\u002F65536 MiB      |                      |\n+------------------------------------+---------------------+----------------------+\n| 7           MetaX C500             | 0000:38:00.0        | 0%                   |\n| 38C         71W \u002F 350W             | 4986\u002F65536 MiB      |                      |\n+------------------------------------+---------------------+----------------------+\n\n+---------------------------------------------------------------------------------+\n| Process:                                                                        |\n|  GPU                    PID         Process Name                 GPU Memory     |\n|                                                                  Usage(MiB)     |\n|=================================================================================|\n|  0                  3496691         python3.10                   4066           |\n|  0                  3496692         python3.10                   102            |\n|  0                  3496693         python3.10                   102            |\n|  0                  3496694         python3.10                   102            |\n|  0                  3496695         python3.10                   102            |\n|  0                  3496696         python3.10                   102            |\n|  0                  3496697         python3.10                   102            |\n|  0                  3496698         python3.10                   170            |\n|  1                  3496692         python3.10                   4154           |\n|  2                  3496693         python3.10                   4154           |\n|  3                  3496694         python3.10                   4154           |\n|  4                  3496695         python3.10                   4154           |\n|  5                  3496696         python3.10                   4154           |\n|  6                  3496697         python3.10                   4154           |\n|  7                  3496698         python3.10                   4154           |\n+---------------------------------------------------------------------------------+\n```\n\n### 训练代码实现\n\n在构建训练代码时，笔者使用HuggingFace Transfomers框架的Trainer类来完成训练代码。Trainer类实现的训练逻辑基本能完成大部分微调任务。这里唯一需要提到的是笔者使用了Qwen3-0.6B而非通常此类任务该使用的Qwen3-0.6B-Base模型，Qwen3-0.6B相比于Qwen3-0.6B-Base模型经过了指令遵从微调、对齐等，能实现聊天问答功能。\n\n通常来说对经过微调的模型进行持续训练会一定程度带来性能损失，然而此次微调时笔者冻结了LLM参数，因此需要选用经过微调的模型来实现多模态问答能力。\n\n笔者在训练过程中使用的是bfloat16精度，相比于float16来说bfloat16增加了尾数位数，训练过程中精度会更高些。\n\n在前期进行方案验证阶段笔者采用的是cocoqa数据集，并且进行200steps的微调训练。在确定方案可行后笔者计划使用完整数据集进行微调训练，然而考虑到训练数据量仅仅只有整个模型的12M，因此笔者按参数量与训练Token的比值为1:10采样数据集，即总共从数据集中采样出60K条数据用于实际训练（文本长度按照2k计算，实际上有padding部分因此实际参与token数小于120M）。笔者认为参与训练的数量是足以令模型收敛的，后续实验也证明了模型确实能达到我们所期望的效果。\n\n**训练关键代码实现**\n\n代码比较长是因为增加了断点续训的能力\n\n```python\n################\n# 开启训练\n################\nlast_checkpoint = None  # load last checkpoint if available\nif (\n    os.path.isdir(training_args.output_dir)\n    and not training_args.overwrite_output_dir\n):\n    last_checkpoint = get_last_checkpoint(training_args.output_dir)\n    if last_checkpoint is None and len(os.listdir(training_args.output_dir)) > 0:\n        raise ValueError(\n            f\"Output directory ({training_args.output_dir}) already exists\"\n        )\n    print(\n        f\"Checkpoint detected, resuming training at {last_checkpoint}.\"\n    )\n# Init Trainer\ntrainer = Trainer(\n    model=qwen_smvl,\n    args=training_args,\n    train_dataset=raw_data[\"train\"],\n    eval_dataset=raw_data[\"test\"],\n    data_collator=collate_fn,\n)\ntrainer.train(resume_from_checkpoint=last_checkpoint)\nqwen_smvl.save_pretrained(training_args.output_dir)\n```\n\n完整代码见[代码及数据集链接汇总](#代码及数据集链接汇总)\n\n或者直接由[完整项目GitHub地址]()\n\n## 微调训练&结果展示\n\n### 环境安装与微调代码执行\n\n**代码准备与环境安装**\n\n可以在[GitHub仓库地址](https:\u002F\u002Fgithub.com\u002FShaohonChen\u002FQwen3-SmVL)处找到实验的完整代码。使用git clone后使用如下命令安装环境\n\n```bash\npip install -r requirements.txt\n```\n\n**数据集和模型下载**\n\n笔者附上自动下载脚本，注意该脚本使用[魔塔社区](https:\u002F\u002Fmodelscope.cn\u002F)完成模型与数据集的下载\n\n```bash\nbash download_resource.sh\n```\n\n### 小批量微调训练\n\n为了进行快速验证，笔者首先使用cocoqa数据集并且进行了200steps的训练，所有参数与前文所述一致。通过\n\n运行实验命令如下，推荐使用8卡进行训练，在8张沐曦GPU卡上预计需要使用20min\n\n```bash\n# 单GPU训练\nCUDA_VISIBLE_DEVICES=0 python train.py .\u002Fcocoqa_train.yaml\n# 8GPU训练\naccelerate launch --num_process 8 train.py .\u002Fcocoqa_train.yaml\n```\n\n注意，本项目使用SwanLab进行训练日志记录与分析，如果未登陆SwanLab需要使用`swanlab login`进行登陆。运行后看到如下结果即代表实验成功开启：\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_e3127a6ccfe5.png\" alt=\"run\" width=\"800\" \u002F>\n  \u003Cfigcaption>成功训练后可以看到SwanLab链接\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n下面是笔者完成小批量微调训练的训练损失、测试损失结果图\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_79447fcb174c.png\" alt=\"cocoqa_swanlab\" width=\"800\" \u002F>\n  \u003Cfigcaption>SwanLab训练可视化分析结果，可以看到最后训练损失和测试损失都收敛在0.65左右\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n模型在完成训练后会自动使用一张狗狗图片配合问题“图中有什么动物？”让模型根据图片进行推理，推理结果如下：\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_f9fdcca65b4a.png\" alt=\"bad_case\" width=\"800\" \u002F>\n  \u003Cfigcaption>SwanLab记录了模型训练好后的推理结果，可以看到模型能正常理解和回复中文\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n当时看到模型对着三只狗的图片回答“兔子”时笔者一时认为炼丹失败了，当然如果实际炼丹失败后模型是不会输出动物类型的，而是输出一些乱码或者告诉用户并没有看到图片。识别错误的原因实际上是由于训练步数过少导致的。后续加大训练步数与数据量后模型能正常识别出狗狗并且能准确的说出有三只狗。\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_a0a30bf0118f.png\" alt=\"dog\" width=\"250\" \u002F>\n  \u003Cfigcaption>附上三只眼神忧伤的狗子，难道长得很像兔子吗？\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\nPS: 作者公开了在[SwanLab上的训练结果](https:\u002F\u002Fswanlab.cn\u002F@ShaohonChen\u002FQwen3-SmVL\u002Foverview)，感兴趣的读者可以自己查看，SwanLab也支持Clone作者的训练日志，大家可以在自己训练时clone笔者的项目去做对照。\n\n### 完整微调训练结果展示\n\n运行实验命令如下，推荐使用8卡进行训练，在8片沐曦C500芯片上预计需要使用1.5h\n\n```bash\n# 单GPU训练\nCUDA_VISIBLE_DEVICES=0 python train.py .\u002Ffull_train.yaml\n# 8GPU训练\naccelerate launch --num_processes 8 train.py .\u002Ffull_train.yaml\n\n# 分阶段训练\nCUDA_VISIBLE_DEVICES=0 python train_staged.py .\u002Fstaged_training_test.yaml\n\n\n# 分阶段全量训练\nCUDA_VISIBLE_DEVICES=0 python train_staged.py .\u002Fstaged_training.yaml\n\n```\n\n下图展示了使用完整微调数据对比于小批量训练，可以看到全量数据微调时loss变得更为抖动，这是由于数据类型的丰富给模型的学习带来了一定的挑战。\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_bbe5823271fb.png\" alt=\"fulldata_swanlab\" width=\"800\" \u002F>\n  \u003Cfigcaption>红色为完整训练loss，黄色为小批量训练结果\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n进一步对比完整训练和小批量训练的训练和测试损失，可以看到完整训练的模型训练损失达到了0.61，远低于仅仅使用cocoqa模型的效果，评估损失也远低于前者，维持在0.58左右。\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_12ab18c3cd8a.png\" alt=\"evalloss\" width=\"800\" \u002F>\n  \u003Cfigcaption>红色为完整训练loss，黄色为小批量训练结果\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n这里值得一提的是，由于我们选用的测试集比较小（仅有64条数据），因此训练损失和测试损失的差距并不能直接理解为过拟合的证据。实际上在大模型训练上，如果数据集足够大的情况下，通常可以认为训练损失等同于评估损失。\n\n此外，模型通过分析1k步之后的训练损失、平均梯度范数（Grad Norm）变化。此时训练任务已过半，且学习率开始快速衰减。如下图，可以看到学习率快速衰减的情况下模型损失并没有明显的进一步下降，这说明模型已经实现了充分训练。\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_e448326071e7.png\" alt=\"1kstep\" width=\"800\" \u002F>\n  \u003Cfigcaption>1k step之后模型的训练损失变化\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n在训练效率方面，可以看到我们仍没有充分榨干沐曦GPU的性能，当然这也是由于多模态任务的网络本身架构上比较复杂，其中包含许多对图像、文本的拼接工作，这也导致了GPU性能没法完全利用。\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_a7572b7cd71e.png\" alt=\"mx-gpu-use\" width=\"800\" \u002F>\n  \u003Cfigcaption>SwanLab对沐曦C500训效率自动记录\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n同样在完成训练后使用狗狗图进行了测试，这次模型能理解图片、中文以及给出正确的回复。更为关键的是模型完全保留了Qwen3-0.6B原有的全部能力，包括函数调用、推理等。在此基础上，仅仅增加了0.09B参数量的情况下为模型带来了图像理解能力！\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_cbe43cedbef0.png\" alt=\"good_case\" width=\"800\" \u002F>\n  \u003Cfigcaption>同样的图片与问题，更大的数据量和更充足的数据使得模型能够正确给出回复\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n### 模型推理与效果分析\n\n等笔者下完数据集后未来补一下测试环节 ; ）\n\n可以关注[swanlab教程集合](https:\u002F\u002Fdocs.swanlab.cn\u002Fexamples\u002Fqwen3_smolvlm_muxi.html)获取最新更新教程！\n\n## 代码及数据集链接汇总\n\n微调用The Cauldron数据集下载链接：\n\n* HuggingFace Hub: [https:\u002F\u002Fhuggingface.co\u002Fdatasets\u002FHuggingFaceM4\u002Fthe_cauldron](https:\u002F\u002Fhuggingface.co\u002Fdatasets\u002FHuggingFaceM4\u002Fthe_cauldron)\n* ModelScope: [https:\u002F\u002Fmodelscope.cn\u002Fdatasets\u002FAI-ModelScope\u002Fthe_cauldron](https:\u002F\u002Fmodelscope.cn\u002Fdatasets\u002FAI-ModelScope\u002Fthe_cauldron)\n\nQwen3-0.6B模型下载：\n\n* HuggingFace Hub: [https:\u002F\u002Fhuggingface.co\u002FQwen\u002FQwen3-0.6B](https:\u002F\u002Fhuggingface.co\u002FQwen\u002FQwen3-0.6B)\n* ModelScope: [https:\u002F\u002Fmodelscope.cn\u002FQwen\u002FQwen3-0.6B](https:\u002F\u002Fmodelscope.cn\u002FQwen\u002FQwen3-0.6B)\n\n本实验完整代码GitHub链接：\n\n* 完整项目GitHub地址：[https:\u002F\u002Fgithub.com\u002FShaohonChen\u002FQwen3-SmVL](https:\u002F\u002Fgithub.com\u002FShaohonChen\u002FQwen3-SmVL)\n\n本实验SwanLab日志：\n\n* SwanLab训练过程查看：[https:\u002F\u002Fswanlab.cn\u002F@ShaohonChen\u002FQwen3-SmVL\u002Foverview](https:\u002F\u002Fswanlab.cn\u002F@ShaohonChen\u002FQwen3-SmVL\u002Foverview)\n\n## 参考资料\n\n* Huggingface SmolVLM2技术报告：[https:\u002F\u002Farxiv.org\u002Fpdf\u002F2504.05299](https:\u002F\u002Farxiv.org\u002Fpdf\u002F2504.05299)\n","# Qwen3-\"VL\"——超小中文多模态模型的“拼接微调”之路1（附代码和SwanLab记录）\n\n* 作者：情感机器实验室——陈少宏\n\n* 邮箱：\u003Cshaohon_chen@115lab.club>\n\n* GitHub：[https:\u002F\u002Fgithub.com\u002FShaohonChen\u002FQwen3-SmVL](https:\u002F\u002Fgithub.com\u002FShaohonChen\u002FQwen3-SmVL)\n* SwanLab：[https:\u002F\u002Fswanlab.cn\u002F@ShaohonChen\u002FQwen3-SmVL\u002Foverview](https:\u002F\u002Fswanlab.cn\u002F@ShaohonChen\u002FQwen3-SmVL\u002Foverview)\n* 数据集：[https:\u002F\u002Fhuggingface.co\u002Fdatasets\u002FHuggingFaceM4\u002Fthe_cauldron](https:\u002F\u002Fhuggingface.co\u002Fdatasets\u002FHuggingFaceM4\u002Fthe_cauldron)\n\n> 💚 **特别感谢**  \n> 感谢 [@zhihuazhao-bit](https:\u002F\u002Fgithub.com\u002Fzhihuazhao-bit) 帮助审阅和修复了代码中众多的小 bug，并在 NV 上完成了测试！\n> \n> 感谢 [@lovelyyoshino](https:\u002F\u002Fgithub.com\u002Flovelyyoshino)帮助填补了数据集中的部分错误，并且提升了模型效果！\n\n## 摘要\n\n最近Huggingface团队发布了超小多模态模型SmolVLM2，可以做到端侧1GB显存推理。在怀着惊喜试用后发现，虽然模型有极其强大的视觉文本理解能力，但是模型却无法理解中文。这对一个“四六级压线过”的笔者来说十分不友好。刚好前段时间做SwanLab硬件检测适配时有一台未到期的沐曦曦云C500服务器，因此萌生了使用**沐曦GPU芯片**微调、把当前中文小模型扛把子Qwen3与SmolVLM2直接微调拼接的想法。\n\n本教程将介绍一种模型拼接的思路，将SmolVLM2的视觉模块（0.09B）与Qwen3最小的模型（0.6B）进行对齐微调，最终使得Qwen模型具备一定的视觉理解能力。由于笔者时间有限且考虑到文章篇幅的原因，因此该系列预计将以系列的方式放出。篇幅规划如下：\n\n* **第一篇**：如何构建和微调一个拼接模型（**本篇博客**）\n* **第二篇**：模型测评、数据集优化、回答人类对齐\n* **第三篇**：微调技巧介绍、视觉位置编码改动与模型结构优化\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_2e91ebdd1bc6.png\" alt=\"PPAP\" width=\"400\" \u002F>\n  \u003Cfigcaption>I have a Qwen, I have a SmolVLM...\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n\u003Cdiv style=\"background-color:#fff3cd; color:black; padding:10px; border-radius:4px; border:1px solid #fbe5b0; width: 90%; max-width: 100%; margin: auto;\">\n  ⚠️关于算力的注意：本教程涉及VLM微调训练，对算力要求较高，需要40G及以上的GPU显存才能运行本教程的训练代码。\n\u003C\u002Fdiv>\n\n## 目录\n\n* [SmolVLM2的背景知识](#SmolVLM2的背景知识)\n* [模型拼接和微调思路简介](#模型拼接和微调思路简介)\n* [模型拼接实现和关键代码讲解](#模型拼接实现和关键代码讲解)\n* [微调数据集构建](#微调数据集构建)\n* [微调方法与代码实现](#微调方法与代码实现)\n* [微调训练&结果展示](#微调训练&结果展示)\n* [代码及数据集链接汇总](#代码及数据集链接汇总)\n\n## SmolVLM2的背景知识\n\n首先，我们先回顾一下SmolVLM2模型的构建方案，SmolVLM2模型的整体包括三大块：视觉模型层，特征映射层和大语言模型层，见下图：\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_7c9119bc1060.png\" alt=\"smolvlm2\" width=\"400\" \u002F>\n  \u003Cfigcaption>SmolVLM2的架构图\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n这个设计是现在比较常见的VLM方案。核心设计思想就是让视觉模型的输出特征与经过embedding的文本特征直接拼接后输入到语言模型（LLM）当中，没有交叉注意力等模块。相比于早期LLaVA等架构，这种最大的优点就是可以最大程度复用已有的语言模型。以Qwen2.5-VL为例，其3B、7B、72B模型大小指的只是LLM部分，并没有包含Vision模块，实际上3B模型的参数量接近4B，视觉模块大概0.4B左右，三个不同大小的VLM使用的是统一的视觉模型。对于一些较大的VLM来说，构建视觉模型时绝大多数的训练都集中在特征映射模块和视觉模块，只在最后阶段为了最终效果进行整体微调时才会调整语言模块。保证了VLM的语言能力。\n\n下面简述一下各个模块的细节：\n\n* 视觉模型层：SmolVLM2-256M版本用的是Google的SigLip模型，一个基于ViT的视觉模型，选用的是最小的SigLip-93M的版本，HF论文里没具体写是直接用的SigLip的参数还是他们从零构建的（有注意到的读者可以评论留言下）。在SmolVLM2代码中对应的是`SmolVLMVisionTransformer`类\n\n* 特征映射层：就是一个简单的MLP，不过SmolVLM2中为了降低图像分辨率还做了一个Pixel shuffle来降低图像分辨率，进一步减少视觉的Token占用，减少了文本长度。HF团队在论文里提到对于参数量较小的VLM来说使用Pixel shuffle还能提升性能。但可训练参数其实就是一个单层的神经网络，这个模块的核心作用就是做特征对齐，将视觉特征从768维（SigLip的维度）映射到576维（SmolLLM2的维度）\n\n* 大语言模型：SmolVLM2-256M模型使用的文本模型是SmolLM-135M版本。可能是由于模型较小，HF团队在论文中说到训练时仅采用两阶段训练：大规模图文训练+针对视频任务的专门微调。为了保障模型的文本能力HF团队在训练数据中参杂了大概14%的纯文本微调数据。不过考虑到视觉模块本身参数量（93M）大小接近于文本模型（135M），因此笔者推测相比于冻结文本模型，数据平衡在这之中会起到更关键的作用。\n\nHF团队在原文中还提到了许多影像小模型VLM性能的trick，感兴趣的读者可以进一步参考SmolVLM2的论文\n\n## 模型拼接和微调思路简介\n\n正所谓顶级食材（模型）只需要最简单的烹饪。模型拼接的思路非常简单直接，基本就三步：\n\n1. 调整SmolVLM2的“上下文控制格式”，使得其与Qwen3兼容。\n\n2. 将模型的文本部分直接从SmolLM2换成Qwen3-0.6B，包括其文本tokenizer和词嵌入、文本模型、以及模型最后输出的语言模型头（LM Head）。\n\n3. 需要重新初始化特征映射层的MLP，从768->576的单层神经网络改成768->1024的单层神经网络即可。\n\n整体架构和对图文对前后处理依旧保持SmolVLM2的流程不变，具体改动见下图：\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_01013f7bc9da.png\" alt=\"concatation\" width=\"400\" \u002F>\n  \u003Cfigcaption>将Qwen3-0.6B替换SmolVLM2的语言模型部分\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n笔者接下来详细介绍下为了实现“拼接”，具体改动的地方，供之后有类似的任务的读者参考。\n\n## 模型拼接实现和关键代码讲解\n\n### 第一处改动：SmolVLM2的Tokenizers部分\n\n首先需要改动的就是需要改动的是SmolVLM2的Tokenizers部分，这里面主要是涉及两个问题：\n\n* 第一个问题是要将SmolVLM2用于指示图像位置的特殊令牌（Special Token）加入到Qwen3的Tokenizer当中，这么做的目的是防止SmolVLM2的图像Token`\u003Cimage>`被切分为`\u003C`、`image`、`>`三块。幸运的是，Qwen3本身在Tokenizers中预留了未来用于多模态的特殊特殊令牌`\n\n### 第二处改动：替换SmolVLM2的SmolLM2模型为Qwen3-0.6B\n\n替换模型这块没什么复杂的，主要是需要处理Transformers比较复杂的嵌套逻辑。Tranformers通常建议模型将预训练模型backbone和下游任务分开来。改动逻辑图如下：\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_d38a64f201e1.png\" alt=\"change_model\" width=\"400\" \u002F>\n  \u003Cfigcaption>替换smolvlm2的文本模块和语言模型头\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n以Qwen3为例，预训练Backbone模型为`Qwen3Model`，仅仅包含embedding层、各个Decoder层，最后输出的是所有输入token的hidden state。负责下游任务的Qwen3提供了包括：用于因果语言序列生成的`Qwen3ForCausalLM`，也就是大家常用的语言生成。负责句子分类`Qwen3ForSequenceClassification`，使用最后一个生成的token输入到一个单层MLP做序列级分类，做句子情绪分类等可以用这个下游模型；`Qwen3ForTokenClassification`用于做Token级分类，比如语言实体抽取任务可以使用这个下游模型。`Qwen3ForQuestionAnswering`则是专门做抽取式问答任务的模型，核心思想是输入（问题，参考文本）让模型从参考文本中找到与问题最相关的一段，这类任务由于RAG系统的出现没那么流行了，未来笔者专门出一个系列的教程阐述除了因果语言序列生成以外的任务则怎么微调。\n\n**关键代码如下**\n\n```python\nfrom transformers import (\n    AutoProcessor,\n    AutoModelForImageTextToText,\n    AutoTokenizer,\n    AutoModelForCausalLM\n)\n\n# 替换text模型和head\nsmolvlm2_02B_model = AutoModelForImageTextToText.from_pretrained(\n    \"model\u002FSmolVLM2-256M-Video-Instruct\",\n    torch_dtype=torch.bfloat16,\n    _attn_implementation=\"eager\",\n).to(device)\n\nqwen3_06b_model = AutoModelForCausalLM.from_pretrained(\n    \"model\u002FQwen3-0.6B\", torch_dtype=torch.bfloat16\n).to(device)\n\nsmolvlm2_02B_model.model.text_model = qwen3_06b_model.model\nsmolvlm2_02B_model.lm_head = qwen3_06b_model.lm_head\n...\n```\n\n接下来比较复杂的是替换所有的关键变量，比如模型内用于在文本序列中为图像特征预留的占位符`image_token_id`，用于指示停止生成的`eos_token_id`，和计算loss值会用到的`vocab_size`，Qwen的词表大小为151936，远远大过SmolVLM2的词表49280。具体代码如下：\n\n```python\n...\n# 替换词表大小\nsmolvlm2_02B_model.vocab_size = qwen3_06b_model.vocab_size\nsmolvlm2_02B_model.model.vocab_size = qwen3_06b_model.vocab_size\nsmolvlm2_02B_model.config.vocab_size = qwen3_06b_model.vocab_size\nsmolvlm2_02B_model.config.text_config.vocab_size = qwen3_06b_model.vocab_size\nsmolvlm2_02B_model.model.config.vocab_siz = qwen3_06b_model.vocab_size\nsmolvlm2_02B_model.model.config.text_config.vocab_size = qwen3_06b_model.vocab_size\n# 替换图像token\nsmolvlm2_02B_model.image_token_id = 151655\nsmolvlm2_02B_model.model.image_token_id = 151655\nsmolvlm2_02B_model.config.image_token_id = 151655\nsmolvlm2_02B_model.model.config.image_token_id = 151655\n# 替换模型生成停止符\nsmolvlm2_02B_model.generation_config.eos_token_id = 151645\n···\n```\n\n上面的代码可以看到在替换各个变量时需要将嵌套模型的变量一起替换掉，笔者之前训练时就因为仅仅替换了`SmolVLMForConditionalGeneration`而忘记替换`SmolVLMModel`中的`image_token_id`，导致语言模型接收不到图像特征，最后表现出来就是loss下降的极快且低，grad_norm看起来也学到位了，一推理效果特别差，附上错误训练的损失图：\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_b87c02c22cc6.png\" alt=\"fail_train\" width=\"800\" \u002F>\n  \u003Cfigcaption>SwanLab记录训练结果展示：蓝色为错误训练的完整微调loss图，可以看到损失下降很快，然而实际推理会发现模型并没有图像理解能力。冻结语言模型头（红色）后发现grad_norm为零且loss不收敛，正确的应该是黄色\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n笔者最早没发现改动错误，先做完整微调（蓝色曲线）后发现损失下降很快达到了0.1以下，结果实际一推理发现模型完全没有图像理解能力，就补了一个冻结语言模型只微调视觉模型的实验（红色曲线），结果发现损失完全没下降，才定位到了视觉特征传入有问题。后续修复后正确的损失下降过程见黄色图像。\n\n### 第三处改动：构建和替换特征映射层\n\n这个相对较简单，只需要重新构建一个维度对齐的`SmolVLMConnector`即可。Qwen3的hidden_dim是1024，SigLip的hidden_dim是768，因此构建一个768➡️1024映射的`SmolVLMConnector`即可。代码如下：\n\n```python\n···\n# 构建配置并且创建连接器\n@dataclass\nclass VisionConfig:\n    hidden_size: int = 768\n\n@dataclass\nclass TextConfig:\n    hidden_size: int = 1024\n\n@dataclass\nclass ConnectConfig:\n    scale_factor: int = 4\n    vision_config: VisionConfig = VisionConfig()\n    text_config: TextConfig = TextConfig()\n\nnew_connector_config = ConnectConfig()\n\n# 替换 SigLit 到 LLM 的 connector 层\nnew_connector = SmolVLMConnector(new_connector_config).to(device).to(torch.bfloat16)\nsmolvlm2_02B_model.model.connector = new_connector\n···\n```\n\n## 微调数据集构建\n\n笔者最初计划寻找中文多模态数据集，但发现相关的资料比较少。因此决定先用英文的多模态数据集凑合一下。之后再考虑通过数据合成的方式将部分数据翻译为中文。关于数据合成和配比的问题将在之后的博客讨论。\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_c8dede9d64b3.png\" alt=\"the_cauldron\" width=\"400\" \u002F>\n  \u003Cfigcaption>the_cauldron数据集logo\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n这里为了方便本项目直接使用HuggingFace团队整合的多模态数据集the Cauldron数据集，Cauldron翻译成中文类似于煮东西的“釜”，不知道HF团队是不是玩“炼丹”的梗。这个数据集整合了50个视觉微调任务数据集的训练集，用于微调Huggingface发布的多模态模型Idefics2模型。这50多个数据集都被处理成了一致的格式（见下图），共有1,880,992条数据，完整下载约169G，非常方便使用。\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_000d29fdea47.png\" alt=\"data_show\" width=\"800\" \u002F>\n  \u003Cfigcaption>数据集样本展示\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n不过可惜数据集的文本都是英文内容，且绝大多数数据集的回复非常短，只有一个词，这也给后面模型训练带来了麻烦。本篇博客暂时不讨论关于数据构建和配比的问题，后续有时间了专门做相关的实验。本博客先以为Qwen3模型带来视觉能力为核心目标。\n\n数据集的下载链接如下，国内推荐用modelscope下载：\n\n* [HuggingFace Hub](https:\u002F\u002Fhuggingface.co\u002Fdatasets\u002FHuggingFaceM4\u002Fthe_cauldron)\n* [ModelScope](https:\u002F\u002Fmodelscope.cn\u002Fdatasets\u002FAI-ModelScope\u002Fthe_cauldron)\n\n笔者在实际测试时发现\"mimic_cgd\"，\"localized_narratives\"，\"okvqa\"，\"ocrvqa\"，\"clevr_math\"这几个子数据集加载有点异常，建议使用此数据集训练的读者手动处理下，社区也有用户反馈这几个数据可以在原始来源处额外下载，未来笔者将会补全这几个数据集重新上传一次完整版的the Cauldron数据集。\n\n## 微调方法与代码实现\n\n### 冻结模型参数微调\n\n整体微调方法采用了CLM模型通常的Teacher Forcing的学习方法，损失就是标准的交叉熵损失。考虑到此次本教程的目标是先确保模型具备中文多模态能力（优化模型性能等之后撰写其他博客），因此为了实验效率，在对齐微调阶段**采用冻结视觉模型与文本模型，仅微调特征映射器和语言模型头**的方法。\n\n冻结模型参数的核心代码如下：\n\n```python\ndef freeze_model(qwen_smvl):\n    for _, param in qwen_smvl.model.text_model.named_parameters():\n        param.requires_grad = False\n    for _, param in qwen_smvl.model.vision_model.named_parameters():\n        param.requires_grad = False\n    return qwen_smvl\n```\n\n冻结后训练参数、模型总参数、与占比如下：\n\n```txt\ntrainable params: 12.00M || all params: 662.87M || trainable%: 1.81\n```\n\n### 文本长度、损失掩码与截断策略\n\n**文本长度**\n\n由于视觉特征需要占用大量的文本长度，笔者简单测试了下the_cauldron图像占0.8K到1.3K左右的token。而数据集中大多数文本token数在200-500左右，极少情况会有3-4K的情况。因此笔者统一采用2K的文本长度，超出部分截断处理。\n\n这里有一个不同于文本微调的细节要注意，文本截断长度不能小于图像token，否则会导致模型在进行特征拼接时报错（当然图像特征如果被截断了，这条训练数据也就没意义了）。因此对于显存不足64G的同学如果需要适当缩短文本长度（不建议低于1.5K），最好连同图像分辨率也缩小些。在后面的博客我们会专门增加对减少图片token占用的研究。\n\n同样由于文本长度受限，且图像特征没法截断，我们也没使用“packing dataset”的方法提升模型的训练效率。\n\n考虑到部分数据集存在多张图片的情况，考虑到本次训练仅采用2k的文本长度（与之对比HF在训练SmolVLM-256M版本采用的是8K的文本长度，2.2B版使用了16K的文本长度）。针对单条数据中存在多张图片的情况仅仅选用第一张。\n\n**损失掩码**\n\n在采用Teacher Forcing的学习方法时，文本微调中损失掩码有两种策略：\n\n* 对包含“用户问题”和“模型回复”的完整文本进行微调优化\n* 仅对“模型回复”部分进行微调优化\n\n这两种策略的对比如下图：\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_9da36413db54.png\" alt=\"mask\" width=\"800\" \u002F>\n  \u003Cfigcaption>两种微调掩码策略的差异，通常建议选择“仅微调模型回答部分”以增强泛化性\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n通常来说使用“仅微调模型回复部分”的策略模型更容易泛化（这点与HF在SmolVLM2的论文提到的trick）。然而笔者为了提高训练效率选择了完整文本微调。可以在后续博客中增加消融实验做进一步对比。\n\n值得注意的是，在进行完整文本微调时，需要单独屏蔽Image Token以防止对图像占位token计算损失，影响模型表现。\n\n**关键代码如下：**\n\n```python\ndef data_collate_fix2k(examples, processor, device, max_length=2048):\n    batch_text = []\n    batch_image = []\n    for example in examples:\n        images = example[\"images\"][:1]  # 只允许一张图，不然显存压力太大\n        batch_image.append(images)\n        image_num = len(images)\n        chat_texts = example[\"texts\"][0]\n        messages = [\n            {\n                \"role\": \"user\",\n                \"content\": [{\"type\": \"image\"}] * image_num\n                + [{\"type\": \"text\", \"text\": chat_texts[\"user\"]}],\n            },\n            {\n                \"role\": \"assistant\",\n                \"content\": [{\"type\": \"text\", \"text\": chat_texts[\"assistant\"]}],\n            },\n        ]\n        text = processor.apply_chat_template(\n            messages, enable_thinking=False, add_generation_prompt=False\n        )\n\n        batch_text.append(text)\n\n    batch = processor(\n        text=batch_text,\n        images=batch_image,\n        max_length=max_length,\n        return_tensors=\"pt\",\n        padding=\"max_length\",\n        truncation=True,\n    )\n    labels = batch[\"input_ids\"].clone()\n    labels[labels == processor.tokenizer.pad_token_id] = -100\n    labels[labels == processor.image_token_id] = -100\n    batch[\"labels\"] = labels\n    return batch.to(device, dtype=torch.bfloat16)\n```\n\n### 微调超参数设置\n\n**学习率**\n\n由于仅仅针对特征映射层（connector）进行训练，且conntector由于要对齐Qwen3的维度因此参数为随机初始化（理论上可以采用一些独特的初始化策略提升性能，但考虑到模型较小因此笔者没关注初始化策略）。因此学习率设置为lora中较为流行的1e-4学习率策略。\n\n为了保障有效收敛，学习率衰减基本是必备的trick，采用的是社区比较流行的cosine学习率衰减，衰减至0。warm up为整体步长的10%（在超过1000k step的情况下固定为50）。\n\n**batch size**\n\nBatch size通常来说越大越好，然而由于VLM模型的文本长度太大，因此采用每卡1 batch和4梯度累加（grad accelerate），在8卡训练中等效32 Batch size。\n\n**训练参数设置代码**\n\n```python\ntraining_args = TrainingArguments(\n    seed=42,\n    data_seed=42,\n    max_steps=200,\n    # num_train_epochs=1,  # 训练1个epoch 约1k steps\n    per_device_train_batch_size=1,\n    gradient_accumulation_steps=4,\n    dataloader_pin_memory=False,\n    warmup_ratio=0.1,\n    learning_rate=1e-4,\n    lr_scheduler_type=\"cosine\",\n    weight_decay=0.01,\n    logging_steps=5,\n    eval_strategy=\"steps\",\n    eval_steps=0.125,\n    save_strategy=\"steps\",\n    save_steps=0.125,\n    save_total_limit=8,\n    optim=\"adamw_torch\",\n    bf16=True,\n    output_dir=f\".\u002Fmodel\u002Ffreeze_except_connector_cocovqa\",\n    overwrite_output_dir=False,\n    report_to=\"swanlab\",\n    run_name=\"freeze_except_connector_cocovqa\",\n    remove_unused_columns=False,\n    gradient_checkpointing=False,\n)\n```\n\n### 训练环境\n\n微调代码基于沐曦的C500国产通用计算GPU实现，显存为64G。沐曦的AI芯片基本完全兼容pytorch和huggingface transformers场景，并且在做多模态训练时相比较其他国产AI芯片罕见的没有兼容性问题。读者在尝试本项目代码时可以采用Nvidia显存40G以上的显卡运行本教程。\n\n**笔者个人感觉沐曦的GPU整体适配效果还是非常好的，没遇到适配性的问题。体验上和用NV的GPU做训练没什么区别**。笔者自己也用过好几款国产GPU，沐曦的体验肯定是名列前茅的，包括代码中有指定flash attention在沐曦GPU上都能成功迁移，这点非常值得给沐曦团队点个赞。希望国产GPU生态能越发展越好，造福广大炼丹师；）。\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_f797a2e3f645.jpg\" alt=\"muxi-gpu\" width=\"400\" \u002F>\n  \u003Cfigcaption>沐曦国产GPU，笔者用的云端服务器没见过真机，因此找了张网图\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n训练环境的话除了安装GPU对应的驱动和pytorch外，本教程需要额外安装Huggingface全家桶，如下：\n\n```txt\ntorch   # 推荐版本>=6.0\ntorchvision\ntransformers>=4.53.0\naccelerate\ndatasets\nnum2words   # SmolVLM2需要\n```\n\n额外补充一句，如果采用沐曦GPU训练的话，需要在沐曦官方文档处寻找[沐曦版torch](https:\u002F\u002Fdeveloper.metax-tech.com\u002Fsoftnova\u002Findex)的安装方式进行下载。其他HF环境和NV基本一样。附赠一个沐曦查看GPU的命令：\n\n```bash\nmx-smi\n```\n\n效果如下：\n\n```bash\n=================== MetaX System Management Interface Log ===================\nTimestamp                                         : Sat Jul 12 14:58:51 2025\n\nAttached GPUs                                     : 8\n+---------------------------------------------------------------------------------+\n| MX-SMI 2.1.12                       Kernel Mode Driver Version: 2.12.13         |\n| MACA Version: 2.29.0.19             BIOS Version: 1.22.3.0                      |\n|------------------------------------+---------------------+----------------------+\n| GPU         NAME                   | Bus-id              | GPU-Util             |\n| Temp        Pwr:Usage\u002FCap          | Memory-Usage        |                      |\n|====================================+=====================+======================|\n| 0           MetaX C500             | 0000:0e:00.0        | 0%                   |\n| 36C         69W \u002F 350W             | 5680\u002F65536 MiB      |                      |\n+------------------------------------+---------------------+----------------------+\n| 1           MetaX C500             | 0000:0f:00.0        | 0%                   |\n| 38C         70W \u002F 350W             | 4986\u002F65536 MiB      |                      |\n+------------------------------------+---------------------+----------------------+\n| 2           MetaX C500             | 0000:10:00.0        | 0%                   |\n| 37C         69W \u002F 350W             | 4986\u002F65536 MiB      |                      |\n+------------------------------------+---------------------+----------------------+\n| 3           MetaX C500             | 0000:12:00.0        | 1%                   |\n| 37C         71W \u002F 350W             | 4986\u002F65536 MiB      |                      |\n+------------------------------------+---------------------+----------------------+\n| 4           MetaX C500             | 0000:35:00.0        | 0%                   |\n| 37C         70W \u002F 350W             | 4986\u002F65536 MiB      |                      |\n+------------------------------------+---------------------+----------------------+\n| 5           MetaX C500             | 0000:36:00.0        | 1%                   |\n| 36C         68W \u002F 350W             | 4986\u002F65536 MiB      |                      |\n+------------------------------------+---------------------+----------------------+\n| 6           MetaX C500             | 0000:37:00.0        | 0%                   |\n| 39C         73W \u002F 350W             | 4986\u002F65536 MiB      |                      |\n+------------------------------------+---------------------+----------------------+\n| 7           MetaX C500             | 0000:38:00.0        | 0%                   |\n| 38C         71W \u002F 350W             | 4986\u002F65536 MiB      |                      |\n+------------------------------------+---------------------+----------------------+\n\n+---------------------------------------------------------------------------------+\n| Process:                                                                        |\n|  GPU                    PID         Process Name                 GPU Memory     |\n|                                                                  Usage(MiB)     |\n|=================================================================================|\n|  0                  3496691         python3.10                   4066           |\n|  0                  3496692         python3.10                   102            |\n|  0                  3496693         python3.10                   102            |\n|  0                  3496694         python3.10                   102            |\n|  0                  3496695         python3.10                   102            |\n|  0                  3496696         python3.10                   102            |\n|  0                  3496697         python3.10                   102            |\n|  0                  3496698         python3.10                   170            |\n|  1                  3496692         python3.10                   4154           |\n|  2                  3496693         python3.10                   4154           |\n|  3                  3496694         python3.10                   4154           |\n|  4                  3496695         python3.10                   4154           |\n|  5                  3496696         python3.10                   4154           |\n|  6                  3496697         python3.10                   4154           |\n|  7                  3496698         python3.10                   4154           |\n+---------------------------------------------------------------------------------+\n```\n\n### 训练代码实现\n\n在构建训练代码时，笔者使用HuggingFace Transfomers框架的Trainer类来完成训练代码。Trainer类实现的训练逻辑基本能完成大部分微调任务。这里唯一需要提到的是笔者使用了Qwen3-0.6B而非通常此类任务该使用的Qwen3-0.6B-Base模型，Qwen3-0.6B相比于Qwen3-0.6B-Base模型经过了指令遵从微调、对齐等，能实现聊天问答功能。\n\n通常来说对经过微调的模型进行持续训练会一定程度带来性能损失，然而此次微调时笔者冻结了LLM参数，因此需要选用经过微调的模型来实现多模态问答能力。\n\n笔者在训练过程中使用的是bfloat16精度，相比于float16来说bfloat16增加了尾数位数，训练过程中精度会更高些。\n\n在前期进行方案验证阶段笔者采用的是cocoqa数据集，并且进行200steps的微调训练。在确定方案可行后笔者计划使用完整数据集进行微调训练，然而考虑到训练数据量仅仅只有整个模型的12M，因此笔者按参数量与训练Token的比值为1:10采样数据集，即总共从数据集中采样出60K条数据用于实际训练（文本长度按照2k计算，实际上有padding部分因此实际参与token数小于120M）。笔者认为参与训练的数量是足以令模型收敛的，后续实验也证明了模型确实能达到我们所期望的效果。\n\n**训练关键代码实现**\n\n代码比较长是因为增加了断点续训的能力\n\n```python\n################\n# 开启训练\n################\nlast_checkpoint = None  # load last checkpoint if available\nif (\n    os.path.isdir(training_args.output_dir)\n    and not training_args.overwrite_output_dir\n):\n    last_checkpoint = get_last_checkpoint(training_args.output_dir)\n    if last_checkpoint is None and len(os.listdir(training_args.output_dir)) > 0:\n        raise ValueError(\n            f\"Output directory ({training_args.output_dir}) already exists\"\n        )\n    print(\n        f\"Checkpoint detected, resuming training at {last_checkpoint}.\"\n    )\n# Init Trainer\ntrainer = Trainer(\n    model=qwen_smvl,\n    args=training_args,\n    train_dataset=raw_data[\"train\"],\n    eval_dataset=raw_data[\"test\"],\n    data_collator=collate_fn,\n)\ntrainer.train(resume_from_checkpoint=last_checkpoint)\nqwen_smvl.save_pretrained(training_args.output_dir)\n```\n\n完整代码见[代码及数据集链接汇总](#代码及数据集链接汇总)\n\n或者直接由[完整项目GitHub地址]()\n\n## 微调训练&结果展示\n\n### 环境安装与微调代码执行\n\n**代码准备与环境安装**\n\n实验的完整代码可以在[GitHub仓库地址](https:\u002F\u002Fgithub.com\u002FShaohonChen\u002FQwen3-SmVL)找到。使用git clone克隆仓库后，运行以下命令安装依赖环境：\n\n```bash\npip install -r requirements.txt\n```\n\n**数据集和模型下载**\n\n笔者提供了一个自动下载脚本，该脚本通过[魔塔社区](https:\u002F\u002Fmodelscope.cn\u002F)完成模型与数据集的下载：\n\n```bash\nbash download_resource.sh\n```\n\n### 小批量微调训练\n\n为了快速验证效果，笔者首先使用cocoqa数据集进行了200步的训练，所有超参数均与前文一致。运行实验命令如下，推荐使用8卡进行训练，在8张沐曦GPU卡上预计需要20分钟：\n\n```bash\n# 单GPU训练\nCUDA_VISIBLE_DEVICES=0 python train.py .\u002Fcocoqa_train.yaml\n# 8GPU训练\naccelerate launch --num_process 8 train.py .\u002Fcocoqa_train.yaml\n```\n\n需要注意的是，本项目使用SwanLab记录和分析训练日志。如果尚未登录SwanLab，需先执行`swanlab login`进行登录。运行成功后，将显示如下结果，表明实验已顺利启动：\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_e3127a6ccfe5.png\" alt=\"run\" width=\"800\" \u002F>\n  \u003Cfigcaption>成功训练后可以看到SwanLab链接\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n以下是笔者完成小批量微调训练后的训练损失和测试损失曲线图：\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_79447fcb174c.png\" alt=\"cocoqa_swanlab\" width=\"800\" \u002F>\n  \u003Cfigcaption>SwanLab训练可视化分析结果，可以看到最后训练损失和测试损失都收敛在0.65左右\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n模型在训练完成后会自动使用一张狗狗图片配合问题“图中有什么动物？”让模型根据图片进行推理，推理结果如下：\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_f9fdcca65b4a.png\" alt=\"bad_case\" width=\"800\" \u002F>\n  \u003Cfigcaption>SwanLab记录了模型训练好后的推理结果，可以看到模型能正常理解和回复中文\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n当时看到模型对着三只狗的图片回答“兔子”时，笔者一度以为炼丹失败了。不过实际上，如果炼丹真的失败，模型不会输出动物类型，而是会输出乱码或提示用户未识别到图片。这次识别错误的原因主要是由于训练步数过少所致。后续增加训练步数和数据量后，模型能够正确识别出狗狗，并准确说出有三只狗。\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_a0a30bf0118f.png\" alt=\"dog\" width=\"250\" \u002F>\n  \u003Cfigcaption>附上三只眼神忧伤的狗子，难道长得很像兔子吗？\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\nPS：作者已在[SwanLab上公开了训练结果](https:\u002F\u002Fswanlab.cn\u002F@ShaohonChen\u002FQwen3-SmVL\u002Foverview)，感兴趣的读者可以自行查看。SwanLab还支持克隆作者的训练日志，大家可以在自己训练时克隆笔者的项目作为对照。\n\n### 完整微调训练结果展示\n\n运行实验命令如下，推荐使用8卡进行训练，在8片沐曦C500芯片上预计需要1.5小时：\n\n```bash\n# 单GPU训练\nCUDA_VISIBLE_DEVICES=0 python train.py .\u002Ffull_train.yaml\n# 8GPU训练\naccelerate launch --num_processes 8 train.py .\u002Ffull_train.yaml\n\n# 分阶段训练\nCUDA_VISIBLE_DEVICES=0 python train_staged.py .\u002Fstaged_training_test.yaml\n\n\n# 分阶段全量训练\nCUDA_VISIBLE_DEVICES=0 python train_staged.py .\u002Fstaged_training.yaml\n\n```\n\n下图展示了使用完整微调数据对比于小批量训练的效果，可以看到全量数据微调时损失波动更为明显，这是由于数据类型的丰富性给模型的学习带来了一定的挑战。\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_bbe5823271fb.png\" alt=\"fulldata_swanlab\" width=\"800\" \u002F>\n  \u003Cfigcaption>红色为完整训练loss，黄色为小批量训练结果\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n进一步对比完整训练和小批量训练的训练损失和测试损失，可以看到完整训练的模型训练损失达到了0.61，远低于仅使用cocoqa数据集的效果；评估损失也显著降低，维持在0.58左右。\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_12ab18c3cd8a.png\" alt=\"evalloss\" width=\"800\" \u002F>\n  \u003Cfigcaption>红色为完整训练loss，黄色为小批量训练结果\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n值得一提的是，由于我们选用的测试集规模较小（仅有64条数据），因此训练损失和测试损失之间的差距并不能直接说明模型存在过拟合现象。实际上，在大模型训练中，如果数据集足够大，通常可以认为训练损失与评估损失基本一致。\n\n此外，通过对1000步之后的训练损失和平均梯度范数（Grad Norm）变化进行分析，可以看出此时训练任务已过半，学习率开始快速衰减。如下图所示，在学习率快速衰减的情况下，模型损失并未出现明显下降，这表明模型已经实现了充分训练。\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_e448326071e7.png\" alt=\"1kstep\" width=\"800\" \u002F>\n  \u003Cfigcaption>1000步之后模型的训练损失变化\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n在训练效率方面，可以看到我们仍未完全发挥沐曦GPU的性能。这主要是由于多模态任务的网络架构较为复杂，涉及大量的图像与文本拼接操作，导致GPU资源未能被充分利用。\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_a7572b7cd71e.png\" alt=\"mx-gpu-use\" width=\"800\" \u002F>\n  \u003Cfigcaption>SwanLab对沐曦C500训练效率的自动记录\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n同样地，在训练完成后使用狗狗图片进行了测试，这次模型能够理解图片、中文，并给出正确的回复。更为关键的是，模型完全保留了Qwen3-0.6B原有的全部能力，包括函数调用、推理等。在此基础上，仅增加了0.09B参数量，便为模型赋予了图像理解能力！\n\n\u003Cdiv align=\"center\">\n  \u003Cfigure>\n  \u003Cimg src=\"https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_readme_cbe43cedbef0.png\" alt=\"good_case\" width=\"800\" \u002F>\n  \u003Cfigcaption>同样的图片与问题，更大的数据量和更充足的数据使得模型能够正确给出回复\u003C\u002Ffigcaption>\n  \u003C\u002Ffigure>\n\u003C\u002Fdiv>\n\n### 模型推理与效果分析\n\n待笔者补充完数据集后，未来会进一步完善测试环节 ; ）\n\n请关注[swanlab教程集合](https:\u002F\u002Fdocs.swanlab.cn\u002Fexamples\u002Fqwen3_smolvlm_muxi.html)获取最新更新教程！\n\n## 代码及数据集链接汇总\n\n微调使用的The Cauldron数据集下载链接：\n\n* HuggingFace Hub: [https:\u002F\u002Fhuggingface.co\u002Fdatasets\u002FHuggingFaceM4\u002Fthe_cauldron](https:\u002F\u002Fhuggingface.co\u002Fdatasets\u002FHuggingFaceM4\u002Fthe_cauldron)\n* ModelScope: [https:\u002F\u002Fmodelscope.cn\u002Fdatasets\u002FAI-ModelScope\u002Fthe_cauldron](https:\u002F\u002Fmodelscope.cn\u002Fdatasets\u002FAI-ModelScope\u002Fthe_cauldron)\n\nQwen3-0.6B模型下载：\n\n* HuggingFace Hub: [https:\u002F\u002Fhuggingface.co\u002FQwen\u002FQwen3-0.6B](https:\u002F\u002Fhuggingface.co\u002FQwen\u002FQwen3-0.6B)\n* ModelScope: [https:\u002F\u002Fmodelscope.cn\u002FQwen\u002FQwen3-0.6B](https:\u002F\u002Fmodelscope.cn\u002FQwen\u002FQwen3-0.6B)\n\n本实验完整代码GitHub链接：\n\n* 完整项目GitHub地址：[https:\u002F\u002Fgithub.com\u002FShaohonChen\u002FQwen3-SmVL](https:\u002F\u002Fgithub.com\u002FShaohonChen\u002FQwen3-SmVL)\n\n本实验SwanLab日志：\n\n* SwanLab训练过程查看：[https:\u002F\u002Fswanlab.cn\u002F@ShaohonChen\u002FQwen3-SmVL\u002Foverview](https:\u002F\u002Fswanlab.cn\u002F@ShaohonChen\u002FQwen3-SmVL\u002Foverview)\n\n## 参考资料\n\n* Huggingface SmolVLM2技术报告：[https:\u002F\u002Farxiv.org\u002Fpdf\u002F2504.05299](https:\u002F\u002Farxiv.org\u002Fpdf\u002F2504.05299)","# Qwen3-SmVL 快速上手指南\n\nQwen3-SmVL 是一个将 SmolVLM2 的视觉模块与 Qwen3-0.6B 语言模型进行拼接微调的超小中文多模态模型。本指南将帮助你快速搭建环境并运行微调代码。\n\n> ⚠️ **硬件要求**：本教程涉及 VLM 微调训练，对算力要求较高，需要 **40G 及以上显存** 的 GPU（如 A100\u002FA800）才能运行训练代码。\n\n## 1. 环境准备\n\n### 系统要求\n- **操作系统**: Linux (推荐 Ubuntu 20.04+)\n- **GPU**: NVIDIA GPU (显存 ≥ 40GB) 或 沐曦 (MetaX) C500 等兼容 CUDA 架构的国产卡\n- **Python**: 3.9 或更高版本\n- **CUDA**: 11.8 或更高版本 (根据显卡驱动调整)\n\n### 前置依赖\n确保已安装以下基础库：\n- `torch` (建议 2.0+)\n- `transformers` (最新版)\n- `accelerate`\n- `datasets`\n- `swanlab` (用于实验记录)\n\n## 2. 安装步骤\n\n### 步骤 1: 克隆项目代码\n```bash\ngit clone https:\u002F\u002Fgithub.com\u002FShaohonChen\u002FQwen3-SmVL.git\ncd Qwen3-SmVL\n```\n\n### 步骤 2: 安装 Python 依赖\n建议使用国内镜像源加速安装：\n```bash\npip install -r requirements.txt -i https:\u002F\u002Fpypi.tuna.tsinghua.edu.cn\u002Fsimple\n```\n*注：若项目中无 `requirements.txt`，请手动安装核心依赖：*\n```bash\npip install torch torchvision torchaudio --index-url https:\u002F\u002Fdownload.pytorch.org\u002Fwhl\u002Fcu118\npip install transformers datasets accelerate swanlab peft -i https:\u002F\u002Fpypi.tuna.tsinghua.edu.cn\u002Fsimple\n```\n\n### 步骤 3: 下载数据集\n本项目使用 `the_cauldron` 数据集。国内用户推荐使用 ModelScope 下载以提升速度：\n```bash\n# 使用 modelscope 库下载 (需先 pip install modelscope)\npython -c \"from modelscope import snapshot_download; snapshot_download('AI-ModelScope\u002Fthe_cauldron', cache_dir='.\u002Fdata')\"\n```\n或者直接从 HuggingFace 下载：\n```bash\nhuggingface-cli download --repo-type dataset HuggingFaceM4\u002Fthe_cauldron --local-dir .\u002Fdata\u002Fthe_cauldron\n```\n\n### 步骤 4: 准备预训练模型\n确保本地已下载以下两个模型权重（或修改代码中的路径为远程 ID）：\n1. `SmolVLM2-256M-Video-Instruct`\n2. `Qwen3-0.6B`\n\n## 3. 基本使用\n\n### 模型拼接与配置\n在运行微调前，需执行模型拼接逻辑。参考项目中的核心脚本（如 `train.py` 或 `concat_model.py`），关键代码逻辑如下：\n\n```python\nfrom transformers import AutoModelForImageTextToText, AutoModelForCausalLM\nimport torch\n\ndevice = \"cuda\" if torch.cuda.is_available() else \"cpu\"\n\n# 1. 加载 SmolVLM2 基座\nsmolvlm2_02B_model = AutoModelForImageTextToText.from_pretrained(\n    \"model\u002FSmolVLM2-256M-Video-Instruct\",\n    torch_dtype=torch.bfloat16,\n    _attn_implementation=\"eager\",\n).to(device)\n\n# 2. 加载 Qwen3-0.6B 文本模型\nqwen3_06b_model = AutoModelForCausalLM.from_pretrained(\n    \"model\u002FQwen3-0.6B\", \n    torch_dtype=torch.bfloat16\n).to(device)\n\n# 3. 替换文本模型骨干和 LM Head\nsmolvlm2_02B_model.model.text_model = qwen3_06b_model.model\nsmolvlm2_02B_model.lm_head = qwen3_06b_model.lm_head\n\n# 4. 更新关键配置参数 (词表大小、Token ID 等)\nsmolvlm2_02B_model.vocab_size = qwen3_06b_model.vocab_size\nsmolvlm2_02B_model.image_token_id = 151655  # Qwen3 的特殊图像 Token ID\nsmolvlm2_02B_model.generation_config.eos_token_id = 151645\n\n# 5. 重建特征映射层 (Connector): 768 (SigLip) -> 1024 (Qwen3)\n# 此处需调用项目内定义的 SmolVLMConnector 类进行替换\n# new_connector = SmolVLMConnector(config).to(device)\n# smolvlm2_02B_model.model.connector = new_connector\n```\n\n### 启动微调训练\n配置完成后，使用以下命令启动训练（示例命令，具体参数请参考项目 `run.sh` 或文档）：\n\n```bash\naccelerate launch train.py \\\n    --dataset_name \".\u002Fdata\u002Fthe_cauldron\" \\\n    --output_dir \".\u002Foutputs\u002Fqwen3-smvl\" \\\n    --per_device_train_batch_size 2 \\\n    --gradient_accumulation_steps 4 \\\n    --learning_rate 1e-4 \\\n    --num_train_epochs 1 \\\n    --logging_steps 10 \\\n    --report_to \"swanlab\"\n```\n\n### 查看训练结果\n训练过程中，数据将实时同步至 SwanLab。访问以下链接查看损失曲线、梯度范数等指标：\n- **SwanLab 面板**: [https:\u002F\u002Fswanlab.cn\u002F@ShaohonChen\u002FQwen3-SmVL\u002Foverview](https:\u002F\u002Fswanlab.cn\u002F@ShaohonChen\u002FQwen3-SmVL\u002Foverview)\n\n> 💡 **提示**: 初次运行时请密切关注 Loss 变化。若 Loss 下降极快但推理无效，可能是 `image_token_id` 或 Connector 维度未正确对齐，请检查上述“模型拼接”步骤中的配置替换是否完整。","一位嵌入式开发工程师正试图在显存仅有 1GB 的国产边缘设备上，部署一个能理解中文说明书图片的智能助手。\n\n### 没有 Qwen3-SmVL 时\n- **语言障碍严重**：现有的超小多模态模型（如 SmolVLM2）虽能端侧运行，但完全无法理解中文，面对中文设备说明书束手无策。\n- **模型体积过大**：主流的中文多模态模型参数量巨大，远超 1GB 显存限制，无法在资源受限的边缘设备上落地。\n- **开发成本高昂**：若要同时满足“中文能力”与“极小体积”，开发者需从零训练或进行复杂的全量微调，对算力和时间要求极高。\n- **架构复用困难**：难以直接利用现有的优秀中文小语言模型（如 Qwen3-0.6B）快速赋予其视觉能力，缺乏灵活的拼接方案。\n\n### 使用 Qwen3-SmVL 后\n- **原生中文支持**：通过拼接微调，Qwen3-SmVL 完美继承了 Qwen3-0.6B 的中文理解能力，能准确解读中文图文信息。\n- **极致轻量部署**：模型整体参数量极小，成功将多模态推理门槛降至 1GB 显存，轻松运行于沐曦等国产边缘芯片上。\n- **高效定制路径**：利用“拼接微调”思路，仅需调整特征映射层即可将视觉模块与中文 LLM 结合，大幅降低训练算力需求。\n- **灵活架构扩展**：验证了将 SmolVLM2 视觉头与任意小型中文 LLM 拼接的可行性，为后续更多端侧应用提供了标准化模板。\n\nQwen3-SmVL 通过巧妙的模型拼接技术，打破了中文能力与端侧轻量化不可兼得的僵局，让超低资源设备也能拥有聪明的“中国眼”。","https:\u002F\u002Foss.gittoolsai.com\u002Fimages\u002FShaohonChen_Qwen3-SmVL_13ec1323.png","ShaohonChen","Shaohon Chen","https:\u002F\u002Foss.gittoolsai.com\u002Favatars\u002FShaohonChen_70ba1832.png","a student of xidian university\r\nenjoy with dl","Xidian University","shaanxi china",null,"https:\u002F\u002Fgithub.com\u002FShaohonChen",[81,85,89],{"name":82,"color":83,"percentage":84},"Python","#3572A5",92,{"name":86,"color":87,"percentage":88},"Jinja","#a52a22",7.2,{"name":90,"color":91,"percentage":92},"Shell","#89e051",0.8,565,54,"2026-04-02T05:02:05",4,"未说明","必需，需 40GB 及以上显存（文中明确提及需要 40G 及以上的 GPU 显存才能运行训练代码），基于沐曦或 NVIDIA 架构测试",{"notes":100,"python":97,"dependencies":101},"本教程涉及 VLM 微调训练，对算力要求极高，必须拥有 40GB 及以上显存的 GPU 方可运行训练代码。模型拼接涉及修改 Chat Template (Jinja)、替换文本模型骨干及重新初始化特征映射层。数据集 'the_cauldron' 完整下载约 169GB，部分子集可能存在加载异常需手动处理。训练时采用冻结视觉和文本模型参数，仅微调特征映射器和语言模型头的策略。",[102,103,104],"transformers","torch","SwanLab",[35,15,106],"其他","2026-03-27T02:49:30.150509","2026-04-08T01:10:08.241319",[110,115,120,125,130,135,139,144],{"id":111,"question_zh":112,"answer_zh":113,"source_url":114},23331,"训练代码报错 'Some keys are not used by the HfArgumentParser' 或命令参数错误怎么办？","这通常是因为 YAML 配置文件中的键名与解析器不匹配，或者多卡\u002F单卡训练命令参数有误。请检查 `parse_yaml_file` 中的参数传递，确保 YAML 文件中没有未使用的键（如 `train_data`）。如果是多卡训练，请确认启动命令是否正确。建议参考最新代码修复了相关 bug 的版本。","https:\u002F\u002Fgithub.com\u002FShaohonChen\u002FQwen3-SmVL\u002Fissues\u002F6",{"id":116,"question_zh":117,"answer_zh":118,"source_url":119},23332,"运行时报错提示 chat_template 相关问题或版本不兼容如何解决？","这个问题通常是由于使用的 transformers 版本过新或过旧导致的，或者是代码中存在遗留的 bug。维护者已修复了 `chat_template` 相关的错误。解决方案是重新拉取最新的代码仓库，因为维护者和社区贡献者已经修复了旧版本中的 bug。","https:\u002F\u002Fgithub.com\u002FShaohonChen\u002FQwen3-SmVL\u002Fissues\u002F2",{"id":121,"question_zh":122,"answer_zh":123,"source_url":124},23333,"项目缺少博客中提到的 `chat_template.jinja` 文件怎么办？","这是由于维护者手动同步文件时遗漏导致的。该问题已被修复，请直接克隆或拉取最新的仓库代码，文件中现已包含 `chat_template.jinja`。","https:\u002F\u002Fgithub.com\u002FShaohonChen\u002FQwen3-SmVL\u002Fissues\u002F1",{"id":126,"question_zh":127,"answer_zh":128,"source_url":129},23334,"LLM 微调时是否需要屏蔽用户输入（User Input）部分的损失计算？","理论上需要屏蔽用户输入，只计算 Assistant 回复部分的损失。Transformers 的 chat 模版通常不会自动屏蔽输入部分。虽然本代码示例中未屏蔽（因为仅微调投影层影响不大），但在正式训练中建议使用 TRL 库的实现来屏蔽 input 部分以提升性能。参考实现：https:\u002F\u002Fhuggingface.co\u002Fdocs\u002Ftrl\u002Fsft_trainer#train-on-assistant-messages-only","https:\u002F\u002Fgithub.com\u002FShaohonChen\u002FQwen3-SmVL\u002Fissues\u002F14",{"id":131,"question_zh":132,"answer_zh":133,"source_url":134},23335,"加载微调后的模型时报错 'size mismatch for weight' 是什么原因？","这是因为在修改拼接后模型的配置时，仅修改了 `vocab_size`，而忽略了 `hidden_size` 等其他关键配置，导致加载权重时形状不匹配。解决方案是将新模型的 `text_config` 直接替换为基座模型（如 Qwen）的完整 config，例如：`smolvlm2_02B_model.model.config.text_config = qwen3_06B_model.config`。","https:\u002F\u002Fgithub.com\u002FShaohonChen\u002FQwen3-SmVL\u002Fissues\u002F11",{"id":136,"question_zh":137,"answer_zh":138,"source_url":114},23336,"对齐一个垂类多模态模型需要多少数据？推荐的训练步骤是什么？","业内主流方案通常分为 2-3 步：1. 冻结视觉和文本模块，只训练投影层（Projector）；2. 训练视觉 + 投影层，冻结文本模型；3. 全量微调（可选包含下游任务）。关于数据量，预训练和微调数据集的准备及分布至关重要，建议参考 SmoVLM2 论文 (https:\u002F\u002Fhuggingface.co\u002Fpapers\u002F2504.05299) 获取详细的数据集准备技巧和数据量预估。",{"id":140,"question_zh":141,"answer_zh":142,"source_url":143},23337,"为什么选择冻结文本模型进行微调？不冻结会有什么效果？","冻结语言模型主要是为了在使用英文数据集训练时，保留基座模型（如 Qwen）原有的中文能力。如果不冻结并进行全量微调，模型可能会遗忘原有的语言能力（灾难性遗忘），但理论上全量微调能带来更好的整体性能。如果显存允许，建议在对齐后进行一次全量微调。","https:\u002F\u002Fgithub.com\u002FShaohonChen\u002FQwen3-SmVL\u002Fissues\u002F5",{"id":145,"question_zh":146,"answer_zh":147,"source_url":148},23338,"当前代码采用 Monkey Patch 方式修改模型，是否应该创建新的模型文件？","理论上应该创建一个新的标准 Transformers 模型文件，而不是使用 Monkey Patch。当前采用补丁方式是为了快速验证拼接有效性且减少工作量。如果计划独立开发新的 VLM 或追求更好的训练性能（如支持 Megatron-LM、降低长文本显存消耗），建议重写一个标准的模型定义。","https:\u002F\u002Fgithub.com\u002FShaohonChen\u002FQwen3-SmVL\u002Fissues\u002F16",[]]