一组投影矩阵,让轻量级 Qwen3-VL 取代 MiniMax H3 中的 Qwen3-VL-32B 文本编码器。
显存占用从 15.7 GB 降至 4.5 GB,扩散模型、VAE 和采样器均无需任何改动。
图像质量相比 v3 没有可测的提升——全部增益都体现在语音上。 两个版本的图像评分完全重叠,且本测试中没有任何指标能将它们与 32B 版本区分开来——这并不等于说它们与 32B 完全相同:余弦相似度无法捕捉可数属性。v3.1 改进的是除英语以外的十种语言的发音——音素错误总体减少 29%,西班牙语、法语、德语和意大利语减少 60% 至 74%。
十一种语言,88 秒。每种语言选取的是与 32B 表现匹配的最小文件——而非最佳文件。其中九种语言在 4B 模型上运行。直接链接
⚠️ 视频画面和声音都刻意保持粗糙。 0.3 百万像素、6 步采样——这些设置使得 297 次渲染在经济上可行——随后进行放大。音频音质偏薄也是出于同样原因,32B 版本上完全一致,在 1 至 3 kHz 之间有相同的 19 dB 凹陷。这是发音测试,不是演示:它的存在是为了让你听到说出的是哪些词。
四个新文件: mmh3-4b-ClipProj-v3.1、mmh3-4b-ClipProj-v3.1-mlp、mmh3-8b-ClipProj-v3.1、mmh3-8b-ClipProj-v3.1-mlp。它们可在节点 0.1.13 上加载,无需修改代码——与 v3 使用相同的基础。建议从 mmh3-4b-ClipProj-v3.1 开始:投影仅 26 MB,加上编码器共 4.6 GB。
变更内容: 校准语料库现在为每种书写系统分配了相当的比例,而非以英语为主,并新增了原始阿拉伯语文本。音素错误总体减少 29%,欧洲语言减少 60% 至 74%。
对比基准: 32B 版本本身也无法完全复现自己。仅更改随机种子,它就会在 75 个音素中重新发音 5.8 个。四个 v3.1 文件在 6.4 至 7.0 之间——因此替换编码器的代价大约等同于重新掷一次随机种子的代价。所有数据均按该方差而非零基准进行归一化,在此尺度上,4B 与 8B 之间、岭回归与残差连接之间没有可区分差异。
完整报告 — 三个随机种子、297 次语音渲染、405 次图像渲染、方法、局限性和全部原始数据:bench3.1/README.md
⚠️ 概念验证——可用,但仍是概念验证。 它能运行并生成优质视频,以下所有数字均在真实硬件上测得。构建和测试仅在单一环境(Windows 11、NVIDIA、ComfyUI 0.31.0)中完成,探索范围有意保持有限。
从教程或文章前来? 8 月 11 日之前发布的任何内容所提到的文件均已移动。
h3_qwen3vl_4b_tap24、h3_control_zero和h3_control_identity仍然保留,位于下一级目录obsolete/中,因此没有丢失任何内容。但请使用当前版本:mmh3-4b-ClipProj-celeb-mlp对应 Qwen3-VL-4B,mmh3-8b-ClipProj-celeb-mlp对应 8B。它们在下方所有指标上均表现更优,且需要节点 0.1.4 或更高版本。
这些文件单独使用毫无意义。它们需要配套的自定义节点: github.com/nicolab28/ComfyUI-ClipProj
我并非机器学习研究者。我的本职工作是影像处理,编程对我而言既是工具也是爱好,而非谋生手段。这件事最初的起因不过是出于好奇:我想弄明白扩散模型究竟如何实际运用它的文本编码器,而我所知的唯一理解方式,就是把它拆开,看看拆完之后它还能不能跑起来。
所以,我最初的问题从来不是“如何节省显存”,而是“这件事到底是否可能”。我原本预料它会失败。两个从未一起训练过的模型之间的线性映射,在无梯度、无学习率的单次拟合中完成,按理说不应该能产出可用的视频。
但它成功了,而且最初的结果好到让我觉得只把它们留在自己的硬盘里实在说不过去。这就是全部的故事,也正是为什么它被标注为“概念验证”而非“工具”——它从来就不是按工具来设计的。
这也是为什么模型卡片上有如此多的测量数据。在向任何人展示之前,我必须先说服自己并没有自欺欺人,而这条路上尝试过的大部分方法最终都被证明是错的。这些尝试也都被记录了下来,分别写在 MEASUREMENTS.md 和 CALIBRATION.md 中。
-mlp 矩阵-mlp 矩阵现为 fp16 精度,体积减半。 残差网络此前以 fp32 发布,节点在加载时无论文件本身的精度如何都会强制转换为 fp32,因此以半精度存储只会让下载体积减半,而显存占用却毫无改善。节点 0.1.4 会以文件保存时的原始精度保留残差,并围绕它转换输入和输出。实测结果:4B 模型的显存占用从 480 MB 降至 240 MB,8B 模型从 576 MB 降至 288 MB。此处的文件已在同名下替换——请重新下载,并配合 0.1.4 版本使用,因为旧版节点加载这些文件后会直接将它们转回 fp32。
节点 0.1.4 还改进了加载替换编码器时的显存释放顺序——现在是在加载之前释放显存,而不是之后——如果你的显存紧张到无法同时容纳两个编码器,这一点至关重要。
两个原因,其中一个无声无息。
-mlp 矩阵携带残差网络,而旧节点未作说明便将其忽略。 它读取矩阵,发现无法识别的键,将其丢弃,仅应用线性部分。不会报错,不会警告,最终你评估的是普通矩阵,却以为自己测试了残差。节点 0.1.3 会读取它们。
所有文件均已重命名。 旧的 h3_qwen3vl_* 文件已移至 obsolete/ 目录,.pt 副本已删除:加载 pickle 会执行代码,而这对于一个仅包含六个张量的文件而言毫无意义。如果你的工作流引用了旧文件名,要么指向 obsolete/,更好的做法是切换到新文件集。
MiniMax H3 基于截断至 50 层的 Qwen3-VL-32B(NVFP4 格式下为 15.7 GB)进行条件生成,仅用于将提示词转换为 [seq, 5120] 张量。本仓库提供一种学习得到的映射,使更小的 Qwen3-VL 也能产生相同的条件生成结果:
cond = ((h - mean_in) / std_in) @ W * std_out + mean_out并且在 -mlp 文件中,还包含了输入同一标准化输入的小型残差网络的输出。
其原理在于,所有 Qwen3-VL 模型共享相同的分词器(151936 个 token):一个提示词在两个模型中会生成相同位置上的相同 token,因此可以学习到它们隐藏状态之间逐位置的映射关系。该矩阵通过纯岭回归拟合——无需梯度、无需迭代、无需学习率。残差网络是唯一需要训练的部分。
将它们放入 ComfyUI/models/clip_projections/ 目录。
如果显存充足,请从 mmh3-8b-ClipProj-celeb-mlp 开始;否则使用 mmh3-4b-ClipProj-celeb-mlp。
| 文件 | 编码器 | 覆盖人名 | 残差网络 | 测试余弦相似度 |
|---|---|---|---|---|
mmh3-4b-ClipProj | 任意 Qwen3-VL-4B | 否 | 否 | 0.7169 |
mmh3-4b-ClipProj-mlp | 任意 Qwen3-VL-4B | 否 | 是 | 0.7944 |
mmh3-4b-ClipProj-celeb | 任意 Qwen3-VL-4B | 是 | 否 | 0.7095 |
mmh3-4b-ClipProj-celeb-mlp | 任意 Qwen3-VL-4B | 是 | 是 | 0.7930 |
mmh3-8b-ClipProj | 任意 Qwen3-VL-8B | 否 | 否 | 0.7528 |
mmh3-8b-ClipProj-mlp | 任意 Qwen3-VL-8B | 否 | 是 | 0.7970 |
mmh3-8b-ClipProj-celeb | 任意 Qwen3-VL-8B | 是 | 否 | 0.7466 |
mmh3-8b-ClipProj-celeb-mlp | 任意 Qwen3-VL-8B | 是 | 是 | 0.8037 |
mmh3-ClipProj-control-zero | — | 对照,运行一次 | — | — |
mmh3-ClipProj-control-identity | — | 对照,运行一次 | — | — |
所有八个矩阵均在相同的通用语料上校准,并在相同的保留提示词上测试,因此该列在每一行之间具有可比性。
每个矩阵都适用于其对应尺寸的任何变体:将一个 bf16 校准的矩阵应用于经过 abliterated 处理的 fp8 编码器时,测得的余弦差距为 0.0023。你不需要使用矩阵校准时的确切检查点。不过,8B 矩阵需要 8B 编码器——因为输入维度是 4096 而非 2560——节点会检查宽度并在不匹配时拒绝运行。
这是 0.1.3 版本的变化所在,而问题出在语料上。
校准语料在 8632 行中约有 70 行提到了人名,约占训练 token 的 0.02%。因此,隐藏空间中承载身份信息的方向几乎没有受到任何约束,拟合过程便将能最小化风景描述误差的内容填充到了那里。结果就是,被提到的人被重建成了其他人。
-celeb 矩阵增加了 500 位按热度排名的人物,每人配 5 个短提示词和 2 个长提示词。它的得与失:
| 姓名 token | 句子其余部分 | 通用测试集 | |
|---|---|---|---|
| 不含 | 0.8265 | 0.9358 | 0.7944 |
| 包含 | 0.8844 | 0.9516 | 0.7930 |
在通用语料上损失了千分之七的余弦相似度,换来了承载身份信息的 token 上六个百分点的提升。句子其余部分也有改善,因为名人提示词较短,而通用语料中几乎没有少于十五个单词的内容。
有两个发现决定了这件事还值不值得继续推进。
每人两个上下文就足够了。 在模型见过的、但被保留的人物上下文上测试:两个上下文时达到 0.9875,五个时 0.9945,二十个时 0.9986。四十个是浪费。
五百个名字可以泛化到从未见过的名字。 一个位于热度排名 501 至 540 之间的保留区间,完全未出现在任何校准数据中,其重建分数为 0.8795,而覆盖名单内的名字为 0.8844。覆盖 500 个人并不是在教模型记住 500 个名字,而是在教映射如何处理这个空间区域。扩展到几千个名字收益甚微。
真正失败的地方不在于语料。 那些身份特征是面具而非面孔的角色,会被重建成一个穿着正确服装的陌生人。名气早于万物皆被拍照时代的古人,则会被重建得错误或面目模糊。此外,有些名字即使在纯 32B 模型上也会失败,所以在归咎于投影之前,请先运行参考模型——这一检查曾经推翻了我自己的三个结论。
通用语料来自 GokuScraper/seedance-2-prompts-datasets,筛选出十五个词以上的提示词并去重后,共 8632 行,中位词数为 128。500 位知名人物名单来自 Kaggle 上发布的 TMDB 导出数据,按热度排序,排名超过 1000 的音译人名予以剔除。
围绕每个人名,从模板生成五条短提示词,另有两条较长提示词按 MiniMax H3 的分节格式撰写,分别由 Mistral Small 和 Gemini Flash Lite 各写一半。重建语料所需的一切材料均位于节点的 calibration/ 文件夹中,包括长提示词所依据的系统提示词。
-mlp 文件携带一个 d_in → 16384 → 5120 的网络,激活函数为 GELU,以加法方式叠加在矩阵之上而非替换之。其最后一层初始化为零,因此在第一步时模型精确复现原矩阵,之后只会逐步优化。该残差网络带来了 0.05 到 0.08 的余弦相似度提升,相当于将语料扩充十一倍对线性映射所获增益的四倍。
两种方案孰优孰劣尚无定论。 余弦相似度无法预测这一点——这是本项目反复印证的最重要经验。请用你自己的提示词分别尝试两种方案。
构建过程中测得两项数据。宽度优于深度:在参数量相等的前提下,两个 8192 维隐藏层达到 0.7691,而单个 16384 维隐藏层为 0.7944。此外,残差网络的外推能力弱于矩阵——在未见过的语料范围之外,线性映射会平滑退化,而网络会直接崩溃。
| 4B | 8B | |
|---|---|---|
| 纯矩阵,不含人名 | 0.7169 | 0.7528 |
| 矩阵 + 残差 | 0.7944 | 0.7970 |
| 矩阵,覆盖人名 | 0.7095 | 0.7466 |
| 矩阵 + 残差,覆盖人名 | 0.7930 | 0.8037 |
0.79 的余弦值听起来不理想,但实际上并非如此——DiT 对误差的容忍度远高于该指标所暗示的水平。实际生成中表现稳健的场景包括:简单提示词、包含多个清晰镜头且彼此无串扰的结构化多镜头提示词、带首尾帧的 fl2va、带参考图像的 ref2va,以及自 0.1.3 版本起带参考视频的 ref2va。
保真度不会在短提示词上崩溃:在处理注意力汇聚点之后,逐 token 余弦值从 80 词时的 0.937 平滑降至 2 词时的 0.908。
首个版本丢失了非英语语音:一行法语台词有一半变成了西班牙语,8B 模型则把所有内容都输出成了英语。这是最明显的退化,当时我无法解释原因。
使用 mmh3-8b-ClipProj-celeb-mlp 后,一段同时包含英语、法语和西班牙语的三次剪辑样本,其输出效果与 32B 模型一致,与参考样本之间的音频电平差从 7.6 dB 降到了 3.5 dB。
之前归咎于投射层的一部分问题,其实并非投射层的问题。当一句台词的长度超过其镜头画面的约三分之二时,无论编码器生成的条件是什么,输出都会变得含糊不清——解决办法是延长镜头,而不是换一个更好的矩阵。此外,MiniMax H3 要求语音以 <d>[语言] ...</d> 格式包裹,并事先声明稳定的说话人 ID;否则,整段剪辑都会使用同一个声线、同一种口音。这两点在本项目中都没有文档记录,因为都不是我们的问题,但各自都耗费了我一天时间。
两个对照组矩阵的存在是为了证明学习得到的矩阵在起作用,而非扩散模型本身的功劳。相同的提示词、相同的随机种子,只有矩阵不同:
| 矩阵 | 对 "a red ball on a wood table" 的输出 |
|---|---|
mmh3-ClipProj-control-zero | 一片乡村风景——提示词被完全忽略 |
mmh3-ClipProj-control-identity | 一个燃烧中的金色物体——不可用 |
| 学习得到的矩阵 | 木桌上的红色球 |
‖W_identity‖ = 50.6,而 ‖W_learned‖ = 52.4 — 能量几乎相同,因此差异是结构性的,而非规模问题。
如果恒等对照组的输出看起来没问题,说明学习得到的矩阵没有增加任何价值——在信任它之前,你最好先弄清楚这一点。
之前的矩阵被保留了下来,因为发布在 r/StableDiffusion 上的对比帖是基于它们运行的,链接必须保持有效。它们没有名称覆盖能力,并且是在一个规模小三十倍的语料库上校准的。没有理由优先选择它们。
其中有一对 CONDPROJ 矩阵,这个故事的叙述价值在于,那个错误本身很有启发性。
DiT 并不会直接消费传入的条件信息:它会先将其通过 condition_proj — 一个 Linear(5120 → 5376) 层 — 送入 token 精炼器。该层的频谱非常不均匀——奇异值最高十分位与最低十分位之间相差 45 倍,52% 的能量集中在 10% 的方向上。普通的岭回归会忽略这一点,它花在 DiT 会乘以 0.10 的方向上的精力,与花在会乘以 37 的方向上的精力一样多。因此,针对该层的输出进行校准,再通过伪逆映射回去,应该能最小化 DiT 实际看到的误差。余弦相似度在 4B 模型上从 0.697 提升到 0.845,在 8B 模型上从 0.731 提升到 0.860。
然后我比较了两个矩阵实际输出的内容:
4B CONDPROJ against unweighted, same corpus cosine 0.999998
8B CONDPROJ against unweighted, same corpus cosine 0.999999它们是同一个函数。无正则化的最小二乘法对目标变量的可逆线性变换具有不变性,因此在某个空间中拟合并映射回来,得到的仍是同一个映射;只有岭惩罚会破坏这种不变性,而在 37,851 个训练 token 与 λ = 1000 的条件下,它几乎不起约束作用。全部的增益不过是在不同空间中度量所产生的假象。
这个想法来自 r/StableDiffusion 上的 u/stddealer,是个好主意。度量失误在我:我在发布余弦相似度之前,没有先检查矩阵是否发生了任何变化。
量化会牺牲事实性。 将 int8_convrot 与 bf16 在事实召回上对比,可以发现量化后会出现错误。日常使用无碍,但如果你的提示词依赖专有名词,这一点值得留意。
掩码会使身份失效。 一个靠服装而非面部识别的角色,会被识别为一个穿着合适衣服的陌生人。没有任何语料库能解决这个问题,因为身份本身就不在名字的表示之中。
计数不可靠,而且不是因为投影的问题。 要求三个东西,结果出来四个,32B 模型也是如此。逐项列举比直接报个数更可靠。
| 角色 | 模型 |
|---|---|
| 扩散模型 + VAE | Comfy-Org/MiniMax-H3 |
| 文本编码器,4B | Comfy-Org/Krea-2 → text_encoders/qwen3vl_4b_fp8_scaled.safetensors |
| 文本编码器,8B | 任意 ComfyUI 格式的 Qwen3-VL-8B(8B 矩阵期望 4096 维输入) |
32B 文本编码器不再需要——这正是本项目的核心意义所在。
这些矩阵以 MIT 协议发布,与节点本身一致。
它们派生自两个模型的激活值,法律地位尚不明确。按现状提供,仅供研究使用,对基于底层模型衍生的任何内容不主张所有权。
本项目与阿里巴巴 / Qwen、MiniMax 或 Comfy Org 无任何关联、背书或联系。
你对自己生成的内容以及所加载的每个模型的许可合规性,负有全部责任。
本工具通过 Anthropic Claude Code (Opus 5) 以氛围编程方式打造。文中所有数据均基于真实硬件实测,而非估算:若预测与实际不符,则以实测为准并修正文案。此前版本中的三处表述有误,现已在此更正。