多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

InternVL3-78B-AWQ 源码精读:chat 与 batch_chat 核心流程逐行拆解

InternVL3-78B-AWQ 源码精读:chat 与 batch_chat 核心流程逐行拆解 InternVL3-78B-AWQ 源码精读chat 与 batch_chat 核心流程逐行拆解【免费下载链接】InternVL3-78B-AWQ项目地址: https://ai.gitcode.com/hf_mirrors/OpenGVLab/InternVL3-78B-AWQ这是一篇面向新手与普通用户的多模态大模型源码入门指南。InternVL3-78B-AWQ是 OpenGVLab 开源的多模态大模型 InternVL3-78B 的 4bit AWQ 量化版本这个 HuggingFace 镜像仓库包含了可直接阅读与运行的推理源码。本文将逐行拆解其最核心的两个推理入口——modeling_internvl_chat.py中的chat与batch_chat方法带你彻底看懂一张图片从像素到文字的完整旅程。一、先建立直觉一张图看懂 InternVL3 的推理骨架在钻进代码之前先花 30 秒建立一个整体印象。InternVL3-78B-AWQ 遵循经典的「ViT-MLP-LLM」三段式架构模块作用本仓库中的实现️ 视觉编码器把图片变成视觉特征向量InternViT-6B-448px见modeling_intern_vit.py 投影层 MLP把视觉特征“翻译”成大模型能懂的语言空间mlp1LayerNorm → Linear → GELU → Linear 语言大模型真正负责理解和生成文字Qwen2.5-72B80 层 Transformer整个对话流程可以浓缩成一句话图片 → ViT 提特征 → MLP 投影 → 把视觉向量“塞进”文本词向量 → 交给 LLM 自回归生成。值得注意的是本仓库的模型权重是AWQ 4bit 量化版本config.json中quant_method: awq、bits: 4因此权重文件被切分成了 27 个pytorch_model-*.bin分片由pytorch_model.bin.index.json索引。量化后显存占用大幅下降让 78B 级模型在消费级/有限显存的服务器上推理成为可能。二、源码入口chat 与 batch_chat 藏在哪里全部推理逻辑都集中在根目录的modeling_internvl_chat.py中核心类为InternVLChatModel继承自PreTrainedModel。我们需要关注的方法与行号如下方法位置职责chatmodeling_internvl_chat.py第 253 行单条对话支持单图/多图/多轮batch_chatmodeling_internvl_chat.py第 204 行批量对话一次处理多个问题generatemodeling_internvl_chat.py第 309 行统一生成入口融合视觉与文本向量extract_featuremodeling_internvl_chat.py第 184 行提取并投影图像特征辅助的对话模板逻辑在conversation.py中get_conv_template模型配置在config.json中。如果你打算自己下载源码研究可以通过git clone https://gitcode.com/hf_mirrors/OpenGVLab/InternVL3-78B-AWQ获取整个仓库。三、chat 核心流程逐行拆解单图/多图/多轮对话chat方法是绝大多数用户的第一接触点它的签名很长但核心参数只有 5 个tokenizer、pixel_values图像像素、question问题、generation_config生成配置、history历史记录。下面按执行顺序拆解。第 1 步自动补image占位符3 行代码的小心机if history is None and pixel_values is not None and image not in question: question image\n question if num_patches_list is None: num_patches_list [pixel_values.shape[0]] if pixel_values is not None else []源码首先判断如果你传了图片、但问题里没有image占位符就自动在问题前面补一个。这意味着纯文本对话pixel_valuesNone和图文对话可以共用同一个入口新手完全不用记两套 API。num_patches_list则记录了每张图被切成了多少个 448×448 的 tile——这是 InternVL 动态分辨率策略的关键。第 2 步用对话模板组装完整 PromptInternVL3 的对话遵循固定模板config.json中template: internvl2_5。chat方法会从conversation.py的get_conv_template拿一个Conversation对象然后把历史对话和当前问题依次追加进去for (old_question, old_answer) in history: template.append_message(template.roles[0], old_question) template.append_message(template.roles[1], old_answer) template.append_message(template.roles[0], question) template.append_message(template.roles[1], None) query template.get_prompt()最终生成的 Prompt 长这样|im_start|system ... USER: image请问这张图里有什么\nASSISTANT:。多轮对话之所以能“记得”上下文秘密就在这个循环里——历史问答被原封不动地重新拼进 Prompt。第 3 步把image替换成真正的视觉 Token 序列这是 InternVL 家族最标志性的一段代码for num_patches in num_patches_list: image_tokens IMG_START_TOKEN IMG_CONTEXT_TOKEN * self.num_image_token * num_patches IMG_END_TOKEN query query.replace(image, image_tokens, 1)image会被替换成img IMG_CONTEXT × (256 × 分块数) /img。这里的 256 是怎么来的根据config.json(448 // 14)² × 0.5² 256即每张 448×448 的 tile 经 pixel shuffle 降采样后产生 256 个视觉 token。一张图被切成 N 个 tile就会产生 256×N 个IMG_CONTEXT占位符这些位置稍后会被真正的图像特征填充。第 4 步分词、生成与回复裁剪model_inputs tokenizer(query, return_tensorspt) input_ids model_inputs[input_ids].to(self.device) generation_config[eos_token_id] eos_token_id generation_output self.generate(pixel_valuespixel_values, input_idsinput_ids, ...) response tokenizer.batch_decode(generation_output, skip_special_tokensTrue)[0] response response.split(template.sep.strip())[0].strip()这里有两个极易被忽略的细节一是把eos_token_id强制设为模板分隔符ASSISTANT:的 token id让模型在回答结束时及时“闭嘴”二是生成后先用template.sep.strip()截断防止模型画蛇添足地继续输出。第 5 步history 如何支撑多轮对话history.append((question, response)) if return_history: return response, history一次对话结束后(question, response)被追加进历史列表并返回。下次调用时把它原样传回history模型就“记得”了之前聊过什么。这就是官方 README 中单图多轮对话示例能够连续追问、且每次追问都基于同一张图的底层原理。四、batch_chat 核心流程逐行拆解一次回答多个问题batch_chat专为批量场景设计——比如你有一批图片想一次性让模型给每张图写一段描述。它的整体思路与chat相同但有三处关键差异。第 1 步逐条构造 Query注意不支持多轮if history is not None or return_history: raise NotImplementedErrorbatch_chat的第一行代码就明确宣告批量模式不支持多轮对话。随后它遍历num_patches_list为每个问题独立创建一个新的对话模板、独立拼接 Prompt最后把所有 query 收集到一个列表中——每条 query 对应一个样本。第 2 步左填充padding_sideleft的奥妙tokenizer.padding_side left model_inputs tokenizer(queries, return_tensorspt, paddingTrue)这是批量推理最容易踩坑的地方。对于自回归生成模型如果像普通分类任务那样用右填充padding 会出现在序列末尾模型会先“回答”一堆无意义的 pad token导致结果错乱。batch_chat把 padding 方向改为左填充让所有样本的正文左对齐、pad 符号集中在左侧从而保证每条序列都能从正确位置开始生成。第 3 步批量生成与统一解码generation_output self.generate(pixel_valuespixel_values, input_idsinput_ids, ...) responses tokenizer.batch_decode(generation_output, skip_special_tokensTrue) responses [response.split(template.sep.strip())[0].strip() for response in responses] return responses所有样本共享一次generate调用batch再统一batch_decode并按分隔符裁剪最终返回一个与questions一一对应的响应列表。注意此时所有图片的pixel_values需要先横向拼接成一个大的张量num_patches_list则用来告诉模型每个样本各占多少个 tile——官方 README 的“单图批处理”示例正是这么做的。五、chat 与 batch_chat 完整对比对比维度chatbatch_chat输入形态单个 questionquestions 列表多轮对话✅ 支持通过 history❌ 不支持批量加速❌ 一次一条✅ 一次多条padding 策略无 padding强制左填充返回值response 或 (response, history)responses 列表典型场景交互式聊天、多轮追问批量图文打标、批量审核一句话总结交互式对话选chat追求吞吐选batch_chat。六、隐藏的引擎extract_feature 与 generate 如何被驱动chat与batch_chat最终都会调用generate而generate的底层又依赖extract_feature。理解这两个方法才算真正读懂了 InternVL3 的推理核心。extract_feature图像特征提取三步曲extract_feature第 184 行做了三件事ViT 前向把像素张量送入vision_model取出倒数第一层select_layer -1的隐藏状态并丢掉[CLS]分类 tokenPixel Shuffle 降采样借助pixel_shuffle把每张 448×448 图片的 1024 个 token 压缩为 256 个downsample_ratio 0.5大幅减少后续 LLM 需要处理的视觉 token 数量MLP 投影通过mlp1LayerNorm → Linear → GELU → Linear把视觉特征从 ViT 的 3200 维映射到 LLM 的 8192 维语言空间。generate把视觉向量“塞回”词向量矩阵generate第 309 行是整条流水线的“装配车间”input_embeds self.language_model.get_input_embeddings()(input_ids) selected (input_ids self.img_context_token_id) input_embeds[selected] vit_embeds.reshape(-1, C).to(input_embeds.device) outputs self.language_model.generate(inputs_embedsinput_embeds, ...)它先把文本 token 全部转成词向量然后精确找到所有IMG_CONTEXT对应的位置把该位置的向量整体替换为extract_feature产出的视觉向量最后将混合好的inputs_embeds交给底层 LLM 做自回归生成。整个过程完全发生在 embedding 层面所以pixel_values和文本可以共享同一个generate入口——这也是chat/batch_chat代码如此简洁的根本原因。七、新手避坑3 个最容易踩的坑⚠️坑 1批量模式下偷偷传 history。batch_chat遇到history或return_history会直接raise NotImplementedError批量场景请提前把多轮对话合并成单轮 prompt。⚠️坑 2num_patches_list与pixel_values数量对不上。chat里有一行断言len(pixel_values) sum(num_patches_list)。多图场景记得用每张图切块数构造num_patches_list否则会在替换视觉 token 时报维度错误。⚠️坑 3多卡推理时首尾层要放在同一张卡上。官方 README 的split_model工具函数特别说明要保证 LLM 的第一层和最后一层在同一设备上否则多卡推理会因张量跨设备而报错。八、总结回顾整条链路chat负责把单条对话“翻译”成带视觉占位符的 Promptbatch_chat把它扩展为批量左填充的矩阵两者最终汇聚到generate由extract_feature完成图像特征提取与投影再将视觉向量无缝嵌入文本词向量矩阵交给 Qwen2.5-72B 完成自回归生成。整份源码结构清晰、注释到位是学习多模态大模型推理流程不可多得的范本——拿到源码后不妨先从chat入手打断点亲眼看看IMG_CONTEXT是如何一步步变成视觉特征的。【免费下载链接】InternVL3-78B-AWQ项目地址: https://ai.gitcode.com/hf_mirrors/OpenGVLab/InternVL3-78B-AWQ创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表