HeteroMoEProjSpace-v703 — 完整可独立加载模型(v70.3)

本仓库自包含:三骨干权重 + 投影空间 + MoA 融合 + VL 旁路 + 分类头全部在 model.safetensors(9.9GB,fp32 / 2,479.72M 参数),modeling_v703.py 单文件无同目录依赖, 三个 tokenizer / image processor 也在 tokenizer_qwen/ tokenizer_bert/ processor_vit/ 子目录。 只需这一个仓库即可完整推理,不用再去 Qwen / ModernBERT / SigLIP2 原始仓库拿分词组。

  • 数据集:VSR(Visual Spatial Reasoning,2 类:false / true)
  • held-out 准确率:0.5831(随机基线 0.5)
  • 任务特点:caption 只断言两物体空间关系("The cake is next to the person."), 必须真正看图才能判断真假 → 视觉不可被文本替代,因此 VL 融合有正增益 (同架构去掉 VL 旁路 = 0.5387,**+4.4 点**)

架构

三骨干 last_hidden_state
  Qwen3.5-2B (language_model)   [B,128,2048]
  ModernBERT-base               [B,128,768]
  SigLIP2-so400m (vision_model) [B,197,1152]
        ↓ P_m 投影(冻结正交)
  统一 256 维
        ↓
四槽维度投影空间 Q/K/V/O (各 256 → 共 1024)
        ↓ canonical [B,128,1024]
  ┌─────────────────────┴─────────────────────┐
  │ MoA 4 分支融合                              │ VL 跨模态融合旁路
  │  Attn / SSM(d_state=4) / Conv / SwiGLU      │  文本流(Qwen+BERT 均值) × 视觉流(ViT)
  │  + sparse_router (Load-Balancing + Z-Loss)  │  锚点共注意力 + Bilinear 门控
  └─────────────────────┬─────────────────────┘
        ↓ canonical + vl_out_proj(vl_out) 残差
      分类头 Linear(1024, 2)

⚠️ 与 HeteroMoEProjSpace-v2 不兼容:底座换代(Qwen2.5-0.5B → Qwen3.5-2B 等), modal_dims 从 [896, 256, 768] 变为 [2048, 768, 1152]。


用法

0. 安装

pip install -U "transformers>=4.57" torch accelerate pillow huggingface_hub

Qwen3.5 需要 transformers>=4.57(qwen3_5 架构)。 可选:pip install causal_conv1d flash-linear-attention 可显著加速 Qwen3.5 的线性注意力 (不装只是慢,不影响正确性,日志会打 fallback 警告)。

1. 加载(一个仓库全搞定)

import torch
from transformers import AutoModel, AutoTokenizer, AutoImageProcessor

REPO = "redmingwei/HeteroMoEProjSpace-v703"

model = AutoModel.from_pretrained(
    REPO, trust_remote_code=True, dtype=torch.float32,
).eval()

qwen_tok = AutoTokenizer.from_pretrained(REPO, subfolder="tokenizer_qwen")
bert_tok = AutoTokenizer.from_pretrained(REPO, subfolder="tokenizer_bert")
vit_proc = AutoImageProcessor.from_pretrained(REPO, subfolder="processor_vit")

如果有 GPU,想直接放卡上:

model = AutoModel.from_pretrained(
    REPO, trust_remote_code=True, dtype=torch.float16, device_map="cuda",
).eval()

2. 单条推理

from PIL import Image

text = "The cake is next to the person."      # VSR 断言:谁 + 空间关系 + 谁
image = Image.open("your_image.jpg").convert("RGB")

# 三个模态各自的输入(文本两个编码器各自 tokenize,图像走 SigLIP2 processor)
q = qwen_tok([text], return_tensors="pt", padding="max_length",
             truncation=True, max_length=128)
b = bert_tok([text], return_tensors="pt", padding="max_length",
             truncation=True, max_length=128)
v = vit_proc(images=[image], return_tensors="pt")

with torch.no_grad():
    out = model(
        qwen_input_ids=q["input_ids"],
        qwen_attention_mask=q["attention_mask"],
        bert_input_ids=b["input_ids"],
        bert_attention_mask=b["attention_mask"],
        bert_token_type_ids=b.get("token_type_ids"),   # ModernBERT 需要
        pixel_values=v["pixel_values"],
    )

LABELS = ["false", "true"]                       # 类别 0 / 1
probs = out.logits.softmax(-1)[0]
pred = int(out.logits.argmax(-1))
print("预测:", LABELS[pred], "| 置信度: %.4f" % float(probs[pred]))
print("两类概率:", {LABELS[i]: round(float(probs[i]), 4) for i in range(2)})

3. 输出字段

字段 形状 含义
out.logits [B, 2] 分类 logits,顺序 [false, true](y.mean(1) 过 head)
out.y [B, 128, 1024] 融合后的逐 token 表示(已含 VL 旁路残差)
out.alpha [B, 128, n_experts] 专家选择(MoA sparse_router,top-k 稀疏)
out.slot_alpha [B, 128, 4] 四槽门控(Q/K/V/O 各贡献多少,和为 1)

alpha 和 slot_alpha 都是 4 维但语义完全不同,别混用: 前者是"这个 token 走了哪几个 MoA 分支",后者是"这个 token 看哪几个功能槽"。

4. 批量推理

texts = [
    "The cake is next to the person.",
    "The horse is next to the bus.",
    "The person is at the left side of the tv.",
]
images = [Image.open(p).convert("RGB") for p in ["a.jpg", "b.jpg", "c.jpg"]]

q = qwen_tok(texts, return_tensors="pt", padding="max_length",
             truncation=True, max_length=128)
b = bert_tok(texts, return_tensors="pt", padding="max_length",
             truncation=True, max_length=128)
v = vit_proc(images=images, return_tensors="pt")

with torch.no_grad():
    out = model(
        qwen_input_ids=q["input_ids"].to(model.device),
        qwen_attention_mask=q["attention_mask"].to(model.device),
        bert_input_ids=b["input_ids"].to(model.device),
        bert_attention_mask=b["attention_mask"].to(model.device),
        bert_token_type_ids=b.get("token_type_ids", torch.zeros_like(b["input_ids"])).to(model.device),
        pixel_values=v["pixel_values"].to(model.device),
    )

labels = out.logits.argmax(-1).tolist()
for t, l in zip(texts, labels):
    print(("true " if l == 1 else "false"), "|", t)

5. 只跑部分模态

模型支持任意模态子集(缺的模态会被跳过,融合层自动适配):

# 只有文本(Qwen + BERT),没有图像
with torch.no_grad():
    out = model(qwen_input_ids=..., qwen_attention_mask=...,
                bert_input_ids=..., bert_attention_mask=...,
                bert_token_type_ids=...)

# 只有图像
with torch.no_grad():
    out = model(pixel_values=...)

⚠️ 只给部分模态时不要指望保持 0.5831——那个分数是三模态齐备时测的。 缺信息会让准确率下降,尤其是 VSR 这种必须看图的任​务。

6. 净室复现(验证部署正确)

example_io.pt 存了打包时的示例输入与期望输出:

from huggingface_hub import hf_hub_download

io = torch.load(hf_hub_download(REPO, "example_io.pt"),
                map_location="cpu", weights_only=False)

with torch.no_grad():
    out = model(
        qwen_input_ids=io["qwen_input_ids"],
        qwen_attention_mask=io["qwen_attention_mask"],
        bert_input_ids=io["bert_input_ids"],
        bert_attention_mask=io["bert_attention_mask"],
        bert_token_type_ids=io.get("bert_token_type_ids"),
        pixel_values=io["pixel_values"],
    )

print("max|Δlogits| =", (out.logits - io["expected_logits"]).abs().max().item())
print("max|Δy_mean| =", (out.y.mean(1) - io["expected_y_mean"]).abs().max().item())

打包时记录的值(fp32、CPU/GPU 均应为 0.000e+00):

logits   = [-2.485569, 2.069039]
y.shape  = [1, 128, 1024]
params   = 2479.72M

7. 常见问题

Q: 报 RuntimeError: ... meta device context ... A: 本仓库已修(__init__ 用 from_config 建结构)。若仍报,说明 modeling_v703.py 是旧版, 请确认 config.json 里的 auto_map 指向本仓库的 modeling_v703.py,并清缓存: rm -rf ~/.cache/huggingface/modules/transformers_modules/。

Q: 报 transformers.models.qwen3_5 不存在 A: 升级 pip install -U transformers>=4.57。

Q: 日志出现 causal_conv1d_fn is falling back / chunk_gated_delta_rule is falling back A: 只是速度警告,不影响正确性。装 causal_conv1d 和 flash-linear-attention 可消除。

Q: 显存不够 A: dtype=torch.float16 或 bfloat16 加载(9.9GB → 约 5GB); 或先 CPU 加载再按需 .to("cuda")。

Q: tokenizer 需要另外下载吗 A: 不需要。三个分词器都在本仓库 tokenizer_qwen/ tokenizer_bert/ processor_vit/ 里。 AutoTokenizer.from_pretrained(REPO, subfolder="tokenizer_qwen") 即可。


限制

  • 仅在 VSR(2 类) 上训练,不是通用视觉语言模型;只能判断"图是否支持文本断言"。
  • fp32 存储 9.9GB;半精度约 5GB。
  • 三骨干权重已包含在 model.safetensors 内,无需另外下载。
  • 训练用的是预提取 last_hidden_state,本仓库把提取路径也封装进来了, 但与原训练脚本的个别前处理细节(如 ViT 序列池化位置)可能有微小差异。
  • 文本长度固定 128(padding="max_length"),超长会被截断。

版本谱系

完整版本索引(22 个 checkpoint 的数据集 / 底座 / 融合层 / held-out / 适用场景)见 HeteroMoEProjSpace-v2/VERSIONS.md。

配置 VSR held-out
MoA 分支(无 VL) 0.5387
MoA + VL 融合(本仓库) 0.5831

同架构在 SNLI-VE(视觉冗余任务)上,加 VL 反而 -8 点(0.6424 → 0.5624): VL 融合的价值取决于"视觉是否携带文本没有的信息"。详见 VERSIONS.md。

Downloads last month
-
Safetensors
Model size
2B params
Tensor type
F32
·
Inference Providers NEW
This model isn't deployed by any Inference Provider. 🙋 Ask for provider support

Collection including redmingwei/HeteroMoEProjSpace-v703