当前位置: 首页 > news >正文

【Bug已解决】Kosmos2.5: index error on long ocr input 解决方案

【Bug已解决】Kosmos2.5: index error on long ocr input 解决方案

一、现象长什么样

Kosmos2.5 是一个面向 OCR/文档理解的多模态模型,输入通常是"一张文档图 + 一段提示文本"。当文档较长(高分辨率扫描、多行密集文字)时,generateforward抛索引错误:

# 现象 A:图像 patch 索引越界 IndexError: index 1024 is out of bounds for dimension 0 with size 1024 File ".../models/kosmos2_5/modeling_kosmos2_5.py", line 210, in forward img_feat = image_features[image_token_indices] # 现象 B:截断后位置错位 RuntimeError: index -1 is out of bounds for dimension 0 with size 0 # 长输入被截断到 max_length,但 image_token 的占位索引还指着被截掉的位置 # 现象 C:batch 内长短不一拼 padding 后越界 ValueError: too many indices for tensor of dimension 1 # padding 把序列补齐到统一长度,但 image_token_indices 仍是原始未 padding 的下标

最典型的触发:一张高分辨率文档图被切成很多 patch(比如 1024 个),加上 OCR 提示文本后总长度超过max_length=2048,截断逻辑只截了文本侧,却忘了同步修正图像 token 的索引 → 索引越界。

二、背景

Kosmos2.5 的输入构造流程是:

  1. 图像经 vision encoder 切成若干 patch 特征(image_features,长度 = patch 数 N)。
  2. 文本里用特殊<image>token 占位, tokenizer 后这些占位被展开成 N 个 image token,分布在序列的不同位置。
  3. forward 时,模型根据image_token_indices(这些 image token 在序列里的下标)去image_features里取对应特征,拼回序列。

这套机制依赖一个不变式:image_token_indices里的最大值 < N(patch 总数),且截断/ padding 后这些索引必须同步更新。当长 OCR 输入触发截断(为了塞进max_length)或 padding(为了 batch),这个不变式被打破,就出现上面的索引错误。

三、根因

根因有三类:

  1. 截断只截断文本,不修正 image_token_indicesprocessormax_length超限时直接裁掉文本 token 尾部,但 image token 的下标是相对"裁剪前序列"算的。裁剪后序列变短,那些指向被裁区域的 image token 下标变成非法(指向越界或负位置)。

  2. padding 后索引未平移/未 mask。 batch 推理时短样本被 pad 到最长。padding 在序列前面或后面插入了 dummy token,但image_token_indices仍是原始下标,没有随 padding 偏移 → 在 padding 之后的位置取 image_features 时错位。

  3. patch 数 N 与 image token 数不一致。 长文档切的 patch 数超过image_features实际长度(例如 image encoder 有自己内部的max_patches限制,超出的 patch 被丢弃),但 tokenizer 展开的 image token 数仍是按"未限制"算的 → 下标越界。

四、最小可运行复现

下面用纯 Python 模拟"截断文本后 image_token_indices 越界"的逻辑:

from typing import List def build_image_token_indices(seq_len: int, n_image_tokens: int) -> List[int]: """模拟:在序列末尾均匀放置 n_image_tokens 个 image token 的下标。""" step = max(1, seq_len // n_image_tokens) return list(range(0, seq_len, step))[:n_image_tokens] def truncate(seq_len: int, max_len: int) -> int: return min(seq_len, max_len) # 正常短输入 seq_len = 500 n_img = 100 idx = build_image_token_indices(seq_len, n_img) print("短输入 max idx:", max(idx), "patch 数 N:", n_img) # 合法 # 长输入触发截断 max_len = 300 new_len = truncate(seq_len, max_len) new_idx = build_image_token_indices(seq_len, n_img) # 索引仍按旧 seq_len 算! print("截断后序列长:", new_len, "但 image idx max:", max(new_idx)) assert max(new_idx) >= new_len, "复现成功:截断后 image token 索引越界" # 修正版:截断时同步裁剪 image_token_indices def truncate_with_indices(seq_len, max_len, idx): return [i for i in idx if i < max_len] fixed = truncate_with_indices(seq_len, max_len, idx) print("修正后 image idx:", fixed, "max:", max(fixed) if fixed else None) assert all(i < new_len for i in fixed), "修正失败"

运行后,原new_idx的最大值(~499)超过了截断后的序列长度(300),触发越界;修正函数把越界索引裁掉,恢复不变式。

五、解决方案(第一层:最小直接修复)

最快的止血:在调用 processor / 截断前,手动清洗 image_token_indices,使其始终落在有效范围内

import torch def sanitize_image_token_indices(image_token_indices, seq_len, image_features_len): """第一层修复:保证索引在 [0, seq_len) 且 < image_features_len。""" valid = [] for i in image_token_indices: if 0 <= i < seq_len and i < image_features_len: valid.append(i) # 若全部越界(极端长输入),退化为均匀取样 image_features if not valid and image_features_len > 0: step = max(1, image_features_len // seq_len) if seq_len else 1 valid = list(range(0, image_features_len, max(step, 1)))[:seq_len] return valid # 使用示意:在构造模型输入后、forward 前 inputs = processor(images=doc_image, text=prompt, return_tensors="pt", truncation=True, max_length=2048) seq_len = inputs["input_ids"].shape[1] image_token_indices = (inputs["input_ids"][0] == processor.image_token_id).nonzero().flatten().tolist() # 取出 image_features(来自 vision encoder) valid_idx = sanitize_image_token_indices(image_token_indices, seq_len, image_features.shape[0]) # 用合法索引重建(或传给模型一个已清洗的 indices 参数) assert max(valid_idx) < image_features.shape[0], "仍有越界,检查 image_features 长度"

第一层让用户立刻消除IndexError,长 OCR 文档也能跑。

六、解决方案(第二层:结构性改进)

把"索引与序列同步"做成KosmosIndexSync,在 processor 和模型之间统一维护不变式:

from dataclasses import dataclass from typing import List @dataclass class KosmosIndexSync: """维护 image_token_indices 与(截断后)序列长度、image_features 长度的一致。""" max_patches: int = 1024 def sync_after_truncation(self, indices: List[int], new_seq_len: int) -> List[int]: kept = [i for i in indices if 0 <= i < new_seq_len] # 同时保证不超过 image_features 实际容量 kept = [i for i in kept if i < self.max_patches] # 若因截断丢失过多 image token,从 image_features 均匀补回 if len(kept) < max(1, len(indices) // 2) and self.max_patches > 0: step = max(1, self.max_patches // max(new_seq_len, 1)) kept = list(range(0, self.max_patches, step))[:new_seq_len] return kept def sync_after_padding(self, indices: List[int], pad_left: int) -> List[int]: # padding 在左侧插入 dummy 时,所有索引右移 pad_left return [i + pad_left for i in indices] # 使用 sync = KosmosIndexSync(max_patches=1024) inputs = processor(images=doc_image, text=prompt, return_tensors="pt", truncation=True, max_length=2048, padding="max_length") seq_len = inputs["input_ids"].shape[1] raw_idx = (inputs["input_ids"][0] == processor.image_token_id).nonzero().flatten().tolist() valid = sync.sync_after_truncation(raw_idx, seq_len) if inputs.get("attention_mask") is not None: pad_left = int((inputs["attention_mask"][0] == 0).sum().item()) # 左 padding 数量 valid = sync.sync_after_padding(valid, pad_left)

KosmosIndexSync把"截断同步 + 左 padding 平移 + 容量上限"三件事集中处理,保证image_token_indices永远落在合法区间。

七、解决方案(第三层:断言 / CI 守护)

用 pytest 固化"长输入不产生越界索引"的契约:

import pytest def test_indices_within_bounds_after_truncation(): from index_sync import KosmosIndexSync sync = KosmosIndexSync(max_patches=1024) # 模拟长 OCR:1024 个 image token,序列被截到 300 indices = list(range(0, 10000, 10))[:1024] new_len = 300 valid = sync.sync_after_truncation(indices, new_len) assert all(0 <= i < new_len for i in valid), "截断后索引仍越界" assert all(i < 1024 for i in valid), "索引超过 image_features 容量" def test_padding_shifts_indices(): from index_sync import KosmosIndexSync sync = KosmosIndexSync() idx = [5, 10, 15] shifted = sync.sync_after_padding(idx, pad_left=4) assert shifted == [9, 14, 19], "左 padding 后索引应整体右移" def test_no_indexerror_on_long_ocr(): # 端到端:长文档不应抛 IndexError import torch from unittest.mock import MagicMock image_features = torch.randn(1024, 64, 64) # N=1024 patch indices = list(range(0, 10000, 10))[:1024] new_len = 300 valid = [i for i in indices if i < new_len and i < image_features.shape[0]] # 取特征不应越界 feats = image_features[valid] assert feats.shape[0] == len(valid)

CI 跑pytest tests/test_kosmos2_5_long_ocr.py,以后只要截断/padding 逻辑又忘了同步索引,测试立刻红灯。

八、排查清单

当 Kosmos2.5 在长 OCR 输入上报索引错误,按顺序查:

  1. IndexError: index X is out of bounds for image_featuresimage_token_indices越界,先用sanitize_image_token_indices清洗。
  2. index -1 is out of bounds→ 截断把 image token 全裁掉了,需要同步裁剪或均匀补回。
  3. batch 推理报too many indices→ padding 后索引未平移,用sync_after_padding右移。
  4. 确认image_features实际 patch 数 N,与 tokenizer 展开的 image token 数是否一致;不一致要限制max_patches
  5. 长期方案:把索引同步逻辑收进 processor(返回已清洗的 indices),而不是让模型侧去猜。

九、小结

"Kosmos2.5: index error on long ocr input" 的根因是:图像 token 的下标(image_token_indices)在长输入触发截断/padding 后未同步更新,破坏了'下标 < patch 数 & < 序列长'的不变式,于是索引越界。

  • 第一层:forward 前用手动sanitize_image_token_indices清洗越界索引,长文档立即能跑。
  • 第二层:用KosmosIndexSync统一处理"截断裁剪 + 左 padding 平移 + patch 容量上限",结构性保证索引合法。
  • 第三层:pytest 断言"截断后索引在界内、padding 后正确平移、长 OCR 端到端不越界",防止回归。

记住:多模态模型里,跨模态的索引(image token ↔ image_features)必须在每次序列变换(截断/padding)后同步修正;这个不变式一旦破坏,就是索引错误。

http://www.cnnetsun.cn/news/3905241.html

相关文章:

  • C++ std::string 底层实现深度解析:SSO、COW 与容量增长策略
  • 理财网站建设方案书:如何打造高转化率且值得信赖的在线财富管理平台
  • SpringBoot运动服装电商系统架构设计与实践
  • AI视频生成工具PixVerse Live本地部署与API集成全流程指南
  • MySQL2PG v2.0.0:高效MySQL到PostgreSQL迁移工具解析
  • 中医馆理疗机器人选型指南:从技术参数到 ROI 测算的完整分析
  • 【独家干货】心血管研究AAV载体选型指南
  • 深度解析山西省住房和城乡建设厅门户网官方网站:您获取最新住建政策、办事指南与行业数据的权威第一窗口
  • 年轻管理者快速成长的6个实操技巧
  • uniapp 5.03升级报错分析与解决方案
  • B站直播推流码获取终极指南:告别官方限制,轻松实现专业直播
  • 深入了解天津建设监理协会网站:助力天津工程建设规范化发展的专业门户与行业风向标
  • MiniExcel 从入门到实战:.NET 中极速、零依赖处理大数据的终极方案
  • OpenCore Legacy Patcher终极指南:4步让老款Mac安装最新macOS系统
  • JDK17安装指南与跨平台开发实践
  • Windows 10文件资源管理器导航窗格清理:注册表与组策略深度定制指南
  • C++重定义错误全解析:从ODR规则到工程实践解决方案
  • Dify 中级实验(05):并行执行——如何让多路任务同时跑?
  • 深耕本土与拥抱未来:青冈县网站建设全指南之如何打造高转化率的数字化名片
  • 深入解析Android应用启动流程:从startActivity到界面渲染的完整机制
  • 淄博网站建设优化seo如何让本地企业轻松实现流量倍增并快速抢占行业先机
  • Godot引擎中基于GDScript的2D随机地图生成算法与实践
  • 【金仓数据库征文】误删表拯救实录:sys_rman 全备、增备与按时间点恢复实测
  • 特摄变身时长设计分析:从W到Geats的节奏演变与创作指南
  • Scratch编程中变量的核心概念与应用技巧
  • 深入解析float与double:精度、范围、IEEE 754原理与实战避坑指南
  • LLM推理成本优化实战:从精细化计量到智能路由的降本增效架构
  • 休闲小游戏圈小猫离线可玩打发时间
  • 揭秘无锡网站建设wuxi8878:从草根逆袭到行业标杆的深度访谈与实战指南
  • Protobuf使用方法