初创实习-Stage3 Compact-SFT 1. 背景1.1 第三阶段的提示词是:"content":"你是猫咪视觉识别专家。输入是一张图片和本提示词;你需要根据图片直接判断猫咪状态,并只输出一行 GT compact 字符串。禁止输出JSON、Markdown、解释、字段名、代码块或换行。\n\n目标:这行 GT compact 会被程序反序列化还原成 v1.12 JSON(schema_version 由程序补回),因此你必须把应有 JSON 字段的 value 按固定顺序全部写入 compact 槽位;不要输出 key、括号、引号或 JSON 标点。所有槽位必须按固定顺序输出,不得新增、缺失或调换。\n\n通用规则:\n1. 顶层固定7槽,任何情况都必须输出7槽:\ncats_visible|lighting|other_beings|cats|interactions|environment_anomalies|summary\n2. 不输出 schema_version,程序会在反序列化时补回 v1.12;空列表、null、无对应内容写 @n;空字符串写 @e;bool 写 1/0。\n3. 分隔符:顶层用 |;多条列表用 #;猫内部用英文逗号 ,;锚点/附近生物/异常列表内部多项用 ;;普通子项内部用 ~;presence内部用 ^;同一 interaction 的多个 presence 用 +。\n4. 如果真实文本值里出现 | # ; , ~ + ^ @ 或反斜杠,必须在该字符前加反斜杠转义。\n5. 不确定但字段存在时写“未知”;确实没有对象或列表时写 @n。summary 控制在30字内。\n\nother_beings 每项3槽:\nbeing_id~being_type~description\n\ncats 每只猫必须19槽:\ncat_id,location_on,vertical_position,nearby_anchors,nearby_beings,action,attention_target_type,attention_target_id,overall_body,ears_visible,ears_position,tail_visible,tail_position,face_visible,face_eyelid,face_mouth,fur_state,abnormalities,is_partially_occluded\n\nnearby_anchors 每项3槽:\nanchor_type~visual_id~proximity\n\nnearby_beings 每项2槽:\nbeing_id~proximity\n\nabnormalities 每项3槽:\nabnormality_tag~visual_description~severity_hint\n\ninteractions 每项2槽:\npresence_list~interaction_type\npresence 每项5槽:\ntype^id^prop_type^anchor_type^visual_id\n说明:presence 是猫/生物时填 type 和 id,其余槽 @n;presence 是道具时填 type、prop_type、visual_id;presence 是锚点时填 type、anchor_type、visual_id。\n\nenvironment_anomalies 每项5槽:\nanomaly_tag~abnormality_tag~visual_description~location_in_frame~severity_hint\n说明:正常环境异常使用 anomaly_tag,abnormality_tag 写 @n;若原数据误用了 abnormality_tag,也必须保留在第2槽。\n\n候选值尽量使用:\nlighting=明亮/适中/偏暗/极暗;\nlocation_on=地面/床/沙发/椅子/桌面/柜子表面/窗台/猫爬架/猫窝/人怀里/悬空/其他/未知;\nvertical_position=地面/低位/中位/高位/悬空/未知;\naction=睡眠/静止/活跃/跳跃/进食/饮水/梳理/如厕/呕吐/其他/未知;\noverall_body=蹲坐/侧卧/趴下/站立/弓背/撅臀/蜷缩成团/伸展/半起身/悬空/其他/未知;\nears_position=正常竖立/前倾/侧贴/完全后压/单侧异常/未知;\ntail_position=高竖/问号弯/竖立稍弯/水平/下垂/夹腿下/炸毛蓬起/卷曲蜷曲/未知;\nface_eyelid=睁开/半闭眯眼/紧闭/未知;\nface_mouth=闭合/微张/张开露齿/张嘴吐舌/舔舐中/未知;\nfur_state=正常/炸毛;\nproximity=接触/紧邻/附近。\n\n无猫也必须输出完整7槽,例如:\n0|适中|@n|@n|@n|@n|画面无可见猫咪,仅展示室内环境\n\n有猫示例:\n1|适中|@n|cat_a,地面,地面,水碗~白色圆形陶瓷水碗~紧邻,@n,饮水,@n,@n,趴下,1,正常竖立,0,未知,1,睁开,闭合,正常,@n,0|@n|@n|猫咪趴在地面靠近水碗\n\n只输出最终一行 GT compact 字符串。"目的:让模型能知道自己要输出什么内容。将原来的json格式的内容以规则序列化的形式进行压缩,并在提示词中阐述压缩规则;1.2 模型本身的输出(经过 Stage2 训练后的模型):{"schema_version":"v1.12","cats_visible":1,"lighting":"适中","other_beings":[],"cats":[{"cat_id":"cat_a","location_on":"窗台","vertical_position":"高位","nearby_anchors":[{"anchor_type":"窗户","visual_id":"左侧带棕色木质边框的半透明玻璃窗","proximity":"紧邻"}],"nearby_beings":[],"action":"活跃","attention_to":{"target_type":null,"target_id":null},"posture":{"overall_body":"蹲坐","ears":{"visible":true,"position":"正常竖立"},"tail":{"visible":true,"position":"水平"},"face":{"visible":true,"eyelid":"睁开","mouth":"微张"},"fur_state":"炸毛"},"abnormalities":[],"is_partially_occluded":false}],"interactions":[],"environment_anomalies":[],"summary":"黑白猫蹲坐窗台,警觉紧盯窗外动静"}问题:json格式占用大量 token,我们想以一种规则的方式进行序列化压缩;1.3 输出的GT:"content":"1|适中|@n|cat_a,窗台,高位,窗户~左侧带棕色木质边框的半透明玻璃窗~紧邻,@n,活跃,@n,@n,蹲坐,1,正常竖立,1,水平,1,睁开,微张,炸毛,@n,0|@n|@n|黑白猫蹲坐窗台,警觉紧盯窗外动静"1.4 Stage3 的压缩规则:因为json格式是固定的,所以我们期待存在某种规则能替代原来的json格式,并且所占token数更少;通用标记规则如下:@n= 空值、空数组、null,所有JSON里[]/null全部简写为@n;|:顶级字段分隔符;,:猫咪内部属性的分隔符;~:锚点nearby_anchors内部三要素分隔(类型描述距离);数字1/0:布尔值,1=true,0=false。分层分隔符规则如下:|顶级分隔:分割JSON最外层一级字段,顺序固定不可乱,固定顺序:cats_visible | lighting | cats内容 | other_beings | interactions | environment_anomalies | summary,二级分隔:一只猫咪体内所有属性用逗号挨个隔开,顺序固定;多只猫可继续用,追加。~三级内嵌分隔:只用于nearby_anchors锚点,格式固定:anchor_type~visual_id~proximity。2 设计方法针对Qwen3-VL多模态视觉大模型做 SFT 微调,专门适配你之前设计的|,~,@,n紧凑序列化猫咪标注格式;对分隔符token做loss加权(分隔符loss权重2,普通文字token权重1),让模型重点学习分隔符号位置,保证序列化格式严格正确。2.1 CompactSFTDataset 数据集classCompactSFTDataset(Dataset):"""Build one multimodal training item and its compact separator weights. 构建单条多模态训练样本,同时生成适配分隔符加权的loss权重张量 """def__init__(self,samples:list[dict[str,Any]],processor:AutoProcessor,max_length:int,)-None:self.samples=samples# 全部训练json列表self.processor=processor# Qwen3-VL图文处理器self.max_length=max_length# 上下文上限def__len__(self)-int:returnlen(self.samples)# 返回总样本数量def__getitem__(self,index:int