Upload liqi files (part 2)
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +2 -0
- code/rerank_ins/collators/__pycache__/qwen_vl_rerank.cpython-314.pyc +0 -0
- code/rerank_ins/collators/__pycache__/vision_process.cpython-313.pyc +0 -0
- code/rerank_ins/collators/__pycache__/vision_process.cpython-314.pyc +0 -0
- code/rerank_ins/collators/base.py +28 -0
- code/rerank_ins/collators/qwen_vl_rerank.py +140 -0
- code/rerank_ins/collators/vision_process.py +297 -0
- code/rerank_ins/cot_data/train_cot.test.jsonl +10 -0
- code/rerank_ins/cot_data/train_cot.test.jsonl.failed +3 -0
- code/rerank_ins/cot_gen/__pycache__/gen_cot_data.cpython-313.pyc +0 -0
- code/rerank_ins/cot_gen/cot_prompt.jinja +46 -0
- code/rerank_ins/cot_gen/gen_cot_data.py +291 -0
- code/rerank_ins/cot_gen/merge_cot_shards.py +80 -0
- code/rerank_ins/cot_gen/run_gen_cot.sh +31 -0
- code/rerank_ins/dataset/__init__.py +0 -0
- code/rerank_ins/dataset/__pycache__/__init__.cpython-313.pyc +0 -0
- code/rerank_ins/dataset/__pycache__/__init__.cpython-314.pyc +0 -0
- code/rerank_ins/dataset/__pycache__/data_utils.cpython-313.pyc +0 -0
- code/rerank_ins/dataset/__pycache__/data_utils.cpython-314.pyc +0 -0
- code/rerank_ins/dataset/__pycache__/dataset_listwise.cpython-313.pyc +0 -0
- code/rerank_ins/dataset/__pycache__/dataset_listwise.cpython-314.pyc +0 -0
- code/rerank_ins/dataset/data_utils.py +33 -0
- code/rerank_ins/dataset/dataset_listwise.py +118 -0
- code/rerank_ins/ds_configs/zero2.json +23 -0
- code/rerank_ins/ds_configs/zero3.json +28 -0
- code/rerank_ins/eval/__pycache__/eval_reranker_qwen35.cpython-314.pyc +0 -0
- code/rerank_ins/eval/__pycache__/eval_reranker_qwen3vl.cpython-313.pyc +0 -0
- code/rerank_ins/eval/__pycache__/eval_reranker_qwen3vl.cpython-314.pyc +0 -0
- code/rerank_ins/eval/__pycache__/eval_reranker_qwen3vl_cot.cpython-313.pyc +0 -0
- code/rerank_ins/eval/eval_reranker_qwen35.py +230 -0
- code/rerank_ins/eval/eval_reranker_qwen3vl.py +322 -0
- code/rerank_ins/eval/eval_reranker_qwen3vl_cot.py +241 -0
- code/rerank_ins/format_prompt/listwise_reranker_v1.jinja +34 -0
- code/rerank_ins/format_prompt/listwise_reranker_v2.jinja +34 -0
- code/rerank_ins/loaders/__init__.py +12 -0
- code/rerank_ins/loaders/__pycache__/__init__.cpython-313.pyc +0 -0
- code/rerank_ins/loaders/__pycache__/__init__.cpython-314.pyc +0 -0
- code/rerank_ins/loaders/__pycache__/base.cpython-313.pyc +0 -0
- code/rerank_ins/loaders/__pycache__/base.cpython-314.pyc +0 -0
- code/rerank_ins/loaders/__pycache__/qwen3_5.cpython-313.pyc +0 -0
- code/rerank_ins/loaders/__pycache__/qwen3_5.cpython-314.pyc +0 -0
- code/rerank_ins/loaders/__pycache__/qwen3_vl.cpython-313.pyc +0 -0
- code/rerank_ins/loaders/__pycache__/qwen3_vl.cpython-314.pyc +0 -0
- code/rerank_ins/loaders/base.py +33 -0
- code/rerank_ins/loaders/qwen3_5.py +25 -0
- code/rerank_ins/loaders/qwen3_vl.py +24 -0
- code/rerank_ins/models/__init__.py +0 -0
- code/rerank_ins/models/__pycache__/__init__.cpython-314.pyc +0 -0
- code/rerank_ins/models/__pycache__/qwen3_5_rank.cpython-314.pyc +0 -0
- code/rerank_ins/models/__pycache__/qwen3_vl_rank.cpython-314.pyc +0 -0
.gitattributes
CHANGED
|
@@ -34,3 +34,5 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
|
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
code/multi_vlm2vec_train/Miniconda3-latest-Linux-x86_64.sh filter=lfs diff=lfs merge=lfs -text
|
|
|
|
|
|
|
|
|
| 34 |
*.zst filter=lfs diff=lfs merge=lfs -text
|
| 35 |
*tfevents* filter=lfs diff=lfs merge=lfs -text
|
| 36 |
code/multi_vlm2vec_train/Miniconda3-latest-Linux-x86_64.sh filter=lfs diff=lfs merge=lfs -text
|
| 37 |
+
dataset/MATH/train.json filter=lfs diff=lfs merge=lfs -text
|
| 38 |
+
dataset/MATH/train_clean.json filter=lfs diff=lfs merge=lfs -text
|
code/rerank_ins/collators/__pycache__/qwen_vl_rerank.cpython-314.pyc
ADDED
|
Binary file (7.69 kB). View file
|
|
|
code/rerank_ins/collators/__pycache__/vision_process.cpython-313.pyc
ADDED
|
Binary file (14.3 kB). View file
|
|
|
code/rerank_ins/collators/__pycache__/vision_process.cpython-314.pyc
ADDED
|
Binary file (16.5 kB). View file
|
|
|
code/rerank_ins/collators/base.py
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from abc import ABC, abstractmethod
|
| 2 |
+
from typing import Dict, Sequence, Optional
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
from transformers import PreTrainedTokenizer, AutoProcessor
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class BaseDataCollator(ABC, object):
|
| 9 |
+
def __init__(
|
| 10 |
+
self,
|
| 11 |
+
tokenizer: Optional[PreTrainedTokenizer] = None,
|
| 12 |
+
processor: Optional[AutoProcessor] = None,
|
| 13 |
+
mask_question_tokens: bool = True
|
| 14 |
+
) -> None:
|
| 15 |
+
self.tokenizer = tokenizer
|
| 16 |
+
self.processor = processor
|
| 17 |
+
self.mask_question_tokens = mask_question_tokens
|
| 18 |
+
|
| 19 |
+
@property
|
| 20 |
+
def IGNORE_TOKEN_ID(self) -> int:
|
| 21 |
+
return -100
|
| 22 |
+
|
| 23 |
+
@property
|
| 24 |
+
def PAD_TOKEN_ID(self) -> int:
|
| 25 |
+
return self.tokenizer.pad_token_id
|
| 26 |
+
|
| 27 |
+
@abstractmethod
|
| 28 |
+
def __call__(self, instances: Sequence[Dict]) -> Dict[str, torch.Tensor]: ...
|
code/rerank_ins/collators/qwen_vl_rerank.py
ADDED
|
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Dict, Sequence
|
| 2 |
+
import torch
|
| 3 |
+
from .base import BaseDataCollator
|
| 4 |
+
from .vision_process import process_vision_info
|
| 5 |
+
|
| 6 |
+
from . import register_collator
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
def extract_inputs(inputs, PAD_TOKEN_ID, IGNORE_TOKEN_ID):
|
| 10 |
+
attention_mask = inputs.get('attention_mask', None)
|
| 11 |
+
pixel_values = inputs.get('pixel_values', None)
|
| 12 |
+
image_grid_thw = inputs.get('image_grid_thw', None)
|
| 13 |
+
mm_token_type_ids = inputs.get('mm_token_type_ids', None)
|
| 14 |
+
input_ids = inputs['input_ids']
|
| 15 |
+
labels = input_ids.clone()
|
| 16 |
+
labels[labels == PAD_TOKEN_ID] = IGNORE_TOKEN_ID
|
| 17 |
+
return input_ids, labels, attention_mask, pixel_values, image_grid_thw, mm_token_type_ids
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
@register_collator("qwen3_5")
|
| 21 |
+
@register_collator("qwen3-vl")
|
| 22 |
+
class TrainRerankDataCollator(BaseDataCollator):
|
| 23 |
+
def __init__(self, tokenizer=None, processor=None, mask_think=False):
|
| 24 |
+
super().__init__(tokenizer=tokenizer, processor=processor)
|
| 25 |
+
self.mask_think = mask_think
|
| 26 |
+
|
| 27 |
+
@property
|
| 28 |
+
def PAD_TOKEN_ID(self) -> int:
|
| 29 |
+
return self.tokenizer.pad_token_id
|
| 30 |
+
|
| 31 |
+
@property
|
| 32 |
+
def IM_START_TOKEN_ID(self) -> int:
|
| 33 |
+
return self.tokenizer.convert_tokens_to_ids('<|im_start|>')
|
| 34 |
+
|
| 35 |
+
def __call__(self, messages: Sequence[Dict]) -> Dict[str, torch.Tensor]:
|
| 36 |
+
self.processor.tokenizer.padding_side = 'right'
|
| 37 |
+
rerank_messages = []
|
| 38 |
+
indexes = []
|
| 39 |
+
|
| 40 |
+
for message, index in messages:
|
| 41 |
+
rerank_messages.append(message)
|
| 42 |
+
indexes.append(index)
|
| 43 |
+
|
| 44 |
+
rerank_texts = [
|
| 45 |
+
self.processor.apply_chat_template(msg, tokenize=False, add_generation_prompt=False)
|
| 46 |
+
for msg in rerank_messages
|
| 47 |
+
]
|
| 48 |
+
|
| 49 |
+
rerank_image_inputs, rerank_video_inputs = process_vision_info(rerank_messages)
|
| 50 |
+
|
| 51 |
+
rerank_inputs = self.processor(
|
| 52 |
+
text=rerank_texts,
|
| 53 |
+
images=rerank_image_inputs,
|
| 54 |
+
videos=rerank_video_inputs,
|
| 55 |
+
padding=True,
|
| 56 |
+
return_tensors="pt"
|
| 57 |
+
)
|
| 58 |
+
|
| 59 |
+
(
|
| 60 |
+
rerank_input_ids,
|
| 61 |
+
rerank_labels,
|
| 62 |
+
rerank_attention_mask,
|
| 63 |
+
rerank_pixel_values,
|
| 64 |
+
rerank_image_grid_thw,
|
| 65 |
+
rerank_mm_token_type_ids,
|
| 66 |
+
) = extract_inputs(rerank_inputs, self.PAD_TOKEN_ID, self.IGNORE_TOKEN_ID)
|
| 67 |
+
|
| 68 |
+
for i in range(len(rerank_labels)):
|
| 69 |
+
start_indices = (rerank_input_ids[i] == self.IM_START_TOKEN_ID).nonzero(as_tuple=True)[0]
|
| 70 |
+
for j in range(1, len(start_indices), 2):
|
| 71 |
+
idx1, idx2 = start_indices[j], start_indices[j + 1]
|
| 72 |
+
rerank_labels[i][idx1:idx2] = self.IGNORE_TOKEN_ID
|
| 73 |
+
rerank_labels[:, :start_indices[1]] = self.IGNORE_TOKEN_ID
|
| 74 |
+
|
| 75 |
+
if self.mask_think:
|
| 76 |
+
think_end_id = self.tokenizer.encode('</think>', add_special_tokens=False)[0]
|
| 77 |
+
newline_id = self.tokenizer.encode('\n', add_special_tokens=False)[0]
|
| 78 |
+
for i in range(len(rerank_labels)):
|
| 79 |
+
positions = (rerank_input_ids[i] == think_end_id).nonzero(as_tuple=True)[0]
|
| 80 |
+
if len(positions) > 0:
|
| 81 |
+
end_pos = positions[-1].item() + 1
|
| 82 |
+
while end_pos < rerank_input_ids.shape[1] and rerank_input_ids[i][end_pos].item() == newline_id:
|
| 83 |
+
end_pos += 1
|
| 84 |
+
rerank_labels[i][:end_pos] = self.IGNORE_TOKEN_ID
|
| 85 |
+
|
| 86 |
+
return dict(
|
| 87 |
+
input_ids=rerank_input_ids,
|
| 88 |
+
attention_mask=rerank_attention_mask,
|
| 89 |
+
pixel_values=rerank_pixel_values,
|
| 90 |
+
image_grid_thw=rerank_image_grid_thw,
|
| 91 |
+
mm_token_type_ids=rerank_mm_token_type_ids,
|
| 92 |
+
labels=rerank_labels,
|
| 93 |
+
)
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
class EvalRerankParallelDataCollator(BaseDataCollator):
|
| 97 |
+
@property
|
| 98 |
+
def PAD_TOKEN_ID(self) -> int:
|
| 99 |
+
return self.tokenizer.pad_token_id
|
| 100 |
+
|
| 101 |
+
def __call__(self, combine_messages: Sequence[Dict]) -> Dict[str, torch.Tensor]:
|
| 102 |
+
messages = []
|
| 103 |
+
indexes = []
|
| 104 |
+
self.processor.tokenizer.padding_side = 'left'
|
| 105 |
+
|
| 106 |
+
for message, index in combine_messages:
|
| 107 |
+
messages.append(message)
|
| 108 |
+
indexes.append(index)
|
| 109 |
+
|
| 110 |
+
texts = [
|
| 111 |
+
self.processor.apply_chat_template(msg, tokenize=False, add_generation_prompt=True, enable_thinking=True)
|
| 112 |
+
for msg in messages
|
| 113 |
+
]
|
| 114 |
+
image_inputs, video_inputs = process_vision_info(messages)
|
| 115 |
+
|
| 116 |
+
inputs = self.processor(
|
| 117 |
+
text=texts,
|
| 118 |
+
images=image_inputs,
|
| 119 |
+
videos=video_inputs,
|
| 120 |
+
padding=True,
|
| 121 |
+
return_tensors="pt",
|
| 122 |
+
)
|
| 123 |
+
|
| 124 |
+
input_ids = inputs['input_ids']
|
| 125 |
+
labels = input_ids.clone()
|
| 126 |
+
labels[labels == self.PAD_TOKEN_ID] = self.IGNORE_TOKEN_ID
|
| 127 |
+
|
| 128 |
+
attention_mask = inputs.get('attention_mask', None)
|
| 129 |
+
pixel_values = inputs.get('pixel_values', None)
|
| 130 |
+
image_grid_thw = inputs.get('image_grid_thw', None)
|
| 131 |
+
mm_token_type_ids = inputs.get('mm_token_type_ids', None)
|
| 132 |
+
|
| 133 |
+
return dict(
|
| 134 |
+
input_ids=input_ids,
|
| 135 |
+
attention_mask=attention_mask,
|
| 136 |
+
pixel_values=pixel_values,
|
| 137 |
+
image_grid_thw=image_grid_thw,
|
| 138 |
+
mm_token_type_ids=mm_token_type_ids,
|
| 139 |
+
labels=labels,
|
| 140 |
+
), indexes
|
code/rerank_ins/collators/vision_process.py
ADDED
|
@@ -0,0 +1,297 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from __future__ import annotations
|
| 2 |
+
|
| 3 |
+
import base64
|
| 4 |
+
import logging
|
| 5 |
+
import math
|
| 6 |
+
import os
|
| 7 |
+
import sys
|
| 8 |
+
import time
|
| 9 |
+
import warnings
|
| 10 |
+
from functools import lru_cache
|
| 11 |
+
from io import BytesIO
|
| 12 |
+
import io
|
| 13 |
+
|
| 14 |
+
import requests
|
| 15 |
+
import torch
|
| 16 |
+
import torchvision
|
| 17 |
+
from packaging import version
|
| 18 |
+
from PIL import Image
|
| 19 |
+
from torchvision import transforms
|
| 20 |
+
from torchvision.transforms import InterpolationMode
|
| 21 |
+
from typing import Optional
|
| 22 |
+
import random
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
logger = logging.getLogger(__name__)
|
| 26 |
+
|
| 27 |
+
IMAGE_FACTOR = 32
|
| 28 |
+
MIN_PIXELS = 4 * 32 * 32
|
| 29 |
+
MAX_PIXELS = 50 * 32 * 32
|
| 30 |
+
MAX_RATIO = 200
|
| 31 |
+
|
| 32 |
+
VIDEO_MIN_PIXELS = 128 * 28 * 28
|
| 33 |
+
VIDEO_MAX_PIXELS = 768 * 28 * 28
|
| 34 |
+
FRAME_FACTOR = 2
|
| 35 |
+
FPS = 2.0
|
| 36 |
+
FPS_MIN_FRAMES = 4
|
| 37 |
+
FPS_MAX_FRAMES = 768
|
| 38 |
+
|
| 39 |
+
VIDEO_TOTAL_PIXELS = int(float(os.environ.get('VIDEO_MAX_PIXELS', 128000 * 28 * 28 * 0.9)))
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
def round_by_factor(number: int, factor: int) -> int:
|
| 43 |
+
return round(number / factor) * factor
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def ceil_by_factor(number: int, factor: int) -> int:
|
| 47 |
+
return math.ceil(number / factor) * factor
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def floor_by_factor(number: int, factor: int) -> int:
|
| 51 |
+
return math.floor(number / factor) * factor
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def smart_resize(
|
| 55 |
+
height: int, width: int, factor: int = IMAGE_FACTOR, min_pixels: int = MIN_PIXELS, max_pixels: int = MAX_PIXELS
|
| 56 |
+
) -> tuple[int, int]:
|
| 57 |
+
if max(height, width) / min(height, width) > MAX_RATIO:
|
| 58 |
+
raise ValueError(
|
| 59 |
+
f"absolute aspect ratio must be smaller than {MAX_RATIO}, got {max(height, width) / min(height, width)}"
|
| 60 |
+
)
|
| 61 |
+
h_bar = max(factor, round_by_factor(height, factor))
|
| 62 |
+
w_bar = max(factor, round_by_factor(width, factor))
|
| 63 |
+
if h_bar * w_bar > max_pixels:
|
| 64 |
+
beta = math.sqrt((height * width) / max_pixels)
|
| 65 |
+
h_bar = floor_by_factor(height / beta, factor)
|
| 66 |
+
w_bar = floor_by_factor(width / beta, factor)
|
| 67 |
+
elif h_bar * w_bar < min_pixels:
|
| 68 |
+
beta = math.sqrt(min_pixels / (height * width))
|
| 69 |
+
h_bar = ceil_by_factor(height * beta, factor)
|
| 70 |
+
w_bar = ceil_by_factor(width * beta, factor)
|
| 71 |
+
if h_bar / w_bar > MAX_RATIO:
|
| 72 |
+
h_bar = w_bar * MAX_RATIO
|
| 73 |
+
elif w_bar / h_bar > MAX_RATIO:
|
| 74 |
+
w_bar = h_bar * MAX_RATIO
|
| 75 |
+
return h_bar, w_bar
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def to_rgb(pil_image: Image.Image) -> Image.Image:
|
| 79 |
+
if pil_image.mode == 'RGBA':
|
| 80 |
+
white_background = Image.new("RGB", pil_image.size, (255, 255, 255))
|
| 81 |
+
white_background.paste(pil_image, mask=pil_image.split()[3])
|
| 82 |
+
return white_background
|
| 83 |
+
else:
|
| 84 |
+
return pil_image.convert("RGB")
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
def resize_bbox(bbox, w, h, scale=None):
|
| 88 |
+
x1, y1, x2, y2 = bbox
|
| 89 |
+
orig_center_x = (x1 + x2) / 2
|
| 90 |
+
orig_center_y = (y1 + y2) / 2
|
| 91 |
+
orig_w = x2 - x1
|
| 92 |
+
orig_h = y2 - y1
|
| 93 |
+
|
| 94 |
+
max_scale_x = min(orig_center_x / max(orig_w / 2, 1e-6), (w - orig_center_x) / max(orig_w / 2, 1e-6))
|
| 95 |
+
max_scale_y = min(orig_center_y / max(orig_h / 2, 1e-6), (h - orig_center_y) / max(orig_h / 2, 1e-6))
|
| 96 |
+
max_scale = min(max_scale_x, max_scale_y)
|
| 97 |
+
|
| 98 |
+
if scale is not None:
|
| 99 |
+
scale = min(max_scale, scale)
|
| 100 |
+
else:
|
| 101 |
+
scale = 1.0
|
| 102 |
+
if max_scale > 1.0:
|
| 103 |
+
scale = random.uniform(1.0, max_scale)
|
| 104 |
+
|
| 105 |
+
new_w = orig_w * scale
|
| 106 |
+
new_h = orig_h * scale
|
| 107 |
+
|
| 108 |
+
new_x1 = int(math.floor(orig_center_x - new_w / 2))
|
| 109 |
+
new_y1 = int(math.floor(orig_center_y - new_h / 2))
|
| 110 |
+
new_x2 = int(math.ceil(orig_center_x + new_w / 2))
|
| 111 |
+
new_y2 = int(math.ceil(orig_center_y + new_h / 2))
|
| 112 |
+
|
| 113 |
+
new_x1 = max(0, new_x1)
|
| 114 |
+
new_y1 = max(0, new_y1)
|
| 115 |
+
new_x2 = min(w, new_x2)
|
| 116 |
+
new_y2 = min(h, new_y2)
|
| 117 |
+
|
| 118 |
+
if new_x1 >= new_x2 or new_y1 >= new_y2 or x2 > w or y2 > h:
|
| 119 |
+
return bbox
|
| 120 |
+
|
| 121 |
+
return [new_x1, new_y1, new_x2, new_y2]
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def fetch_image(ele, size_factor: int = IMAGE_FACTOR) -> Image.Image:
|
| 125 |
+
if "image" in ele:
|
| 126 |
+
image = ele["image"]
|
| 127 |
+
else:
|
| 128 |
+
image = ele["image_url"]
|
| 129 |
+
image_obj = None
|
| 130 |
+
if isinstance(image, Image.Image):
|
| 131 |
+
image_obj = image
|
| 132 |
+
elif image.startswith("http://") or image.startswith("https://"):
|
| 133 |
+
response = requests.get(image, stream=True)
|
| 134 |
+
image_obj = Image.open(BytesIO(response.content))
|
| 135 |
+
elif image.startswith("file://"):
|
| 136 |
+
image_obj = Image.open(image[7:])
|
| 137 |
+
elif image.startswith("data:image"):
|
| 138 |
+
if "base64," in image:
|
| 139 |
+
_, base64_data = image.split("base64,", 1)
|
| 140 |
+
data = base64.b64decode(base64_data)
|
| 141 |
+
image_obj = Image.open(BytesIO(data))
|
| 142 |
+
else:
|
| 143 |
+
image_obj = Image.open(image)
|
| 144 |
+
|
| 145 |
+
bbox = ele.get("bbox", None)
|
| 146 |
+
if bbox:
|
| 147 |
+
w, h = image_obj.width, image_obj.height
|
| 148 |
+
if len(bbox) == 6:
|
| 149 |
+
x1_prop, y1_prop, x2_prop, y2_prop, w_bar, h_bar = bbox
|
| 150 |
+
x1 = int(x1_prop / w_bar * w)
|
| 151 |
+
y1 = int(y1_prop / h_bar * h)
|
| 152 |
+
x2 = int(x2_prop / w_bar * w)
|
| 153 |
+
y2 = int(y2_prop / h_bar * h)
|
| 154 |
+
bbox = [x1, y1, x2, y2]
|
| 155 |
+
|
| 156 |
+
if ele.get("dynamic_crop", False):
|
| 157 |
+
bbox = resize_bbox(bbox, w, h)
|
| 158 |
+
|
| 159 |
+
image_obj = image_obj.crop(bbox)
|
| 160 |
+
|
| 161 |
+
if image_obj is None:
|
| 162 |
+
raise ValueError(f"Unrecognized image input, support local path, http url, base64 and PIL.Image, got {image}")
|
| 163 |
+
image = to_rgb(image_obj)
|
| 164 |
+
|
| 165 |
+
if "hflip" in ele and ele["hflip"]:
|
| 166 |
+
image = image.transpose(Image.FLIP_LEFT_RIGHT)
|
| 167 |
+
if "rotate" in ele and ele["rotate"]:
|
| 168 |
+
angle = random.choice([-15, -10, -5, 5, 10, 15])
|
| 169 |
+
image = image.rotate(angle)
|
| 170 |
+
|
| 171 |
+
if "resized_height" in ele and "resized_width" in ele:
|
| 172 |
+
resized_height, resized_width = smart_resize(
|
| 173 |
+
ele["resized_height"],
|
| 174 |
+
ele["resized_width"],
|
| 175 |
+
factor=size_factor,
|
| 176 |
+
)
|
| 177 |
+
else:
|
| 178 |
+
width, height = image.size
|
| 179 |
+
min_pixels = ele.get("min_pixels", MIN_PIXELS)
|
| 180 |
+
max_pixels = ele.get("max_pixels", MAX_PIXELS)
|
| 181 |
+
if max_pixels is None:
|
| 182 |
+
max_pixels = MAX_PIXELS
|
| 183 |
+
resized_height, resized_width = smart_resize(
|
| 184 |
+
height,
|
| 185 |
+
width,
|
| 186 |
+
factor=size_factor,
|
| 187 |
+
min_pixels=min_pixels,
|
| 188 |
+
max_pixels=max_pixels,
|
| 189 |
+
)
|
| 190 |
+
image = image.resize((resized_width, resized_height))
|
| 191 |
+
|
| 192 |
+
return image
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
def smart_nframes(ele: dict, total_frames: int, video_fps: int | float) -> int:
|
| 196 |
+
assert not ("fps" in ele and "nframes" in ele), "Only accept either `fps` or `nframes`"
|
| 197 |
+
if "nframes" in ele:
|
| 198 |
+
nframes = round_by_factor(ele["nframes"], FRAME_FACTOR)
|
| 199 |
+
else:
|
| 200 |
+
fps = ele.get("fps", FPS)
|
| 201 |
+
min_frames = ceil_by_factor(ele.get("min_frames", FPS_MIN_FRAMES), FRAME_FACTOR)
|
| 202 |
+
max_frames = floor_by_factor(ele.get("max_frames", min(FPS_MAX_FRAMES, total_frames)), FRAME_FACTOR)
|
| 203 |
+
nframes = total_frames / video_fps * fps
|
| 204 |
+
if nframes > total_frames:
|
| 205 |
+
logger.warning(f"smart_nframes: nframes[{nframes}] > total_frames[{total_frames}]")
|
| 206 |
+
nframes = min(min(max(nframes, min_frames), max_frames), total_frames)
|
| 207 |
+
nframes = floor_by_factor(nframes, FRAME_FACTOR)
|
| 208 |
+
if not (FRAME_FACTOR <= nframes and nframes <= total_frames):
|
| 209 |
+
raise ValueError(f"nframes should in interval [{FRAME_FACTOR}, {total_frames}], but got {nframes}.")
|
| 210 |
+
return nframes
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
def fetch_video(ele: dict, image_factor: int = IMAGE_FACTOR, return_video_sample_fps: bool = False):
|
| 214 |
+
if isinstance(ele["video"], str):
|
| 215 |
+
video, audio, info = torchvision.io.read_video(
|
| 216 |
+
ele["video"],
|
| 217 |
+
start_pts=ele.get("video_start", 0.0),
|
| 218 |
+
end_pts=ele.get("video_end", None),
|
| 219 |
+
pts_unit="sec",
|
| 220 |
+
output_format="TCHW",
|
| 221 |
+
)
|
| 222 |
+
total_frames, video_fps = video.size(0), info["video_fps"]
|
| 223 |
+
nframes = smart_nframes(ele, total_frames=total_frames, video_fps=video_fps)
|
| 224 |
+
idx = torch.linspace(0, total_frames - 1, nframes).round().long()
|
| 225 |
+
sample_fps = nframes / max(total_frames, 1e-6) * video_fps
|
| 226 |
+
video = video[idx]
|
| 227 |
+
|
| 228 |
+
nframes, _, height, width = video.shape
|
| 229 |
+
min_pixels = ele.get("min_pixels", VIDEO_MIN_PIXELS)
|
| 230 |
+
total_pixels = ele.get("total_pixels", VIDEO_TOTAL_PIXELS)
|
| 231 |
+
max_pixels = max(min(VIDEO_MAX_PIXELS, total_pixels / nframes * FRAME_FACTOR), int(min_pixels * 1.05))
|
| 232 |
+
max_pixels_supposed = ele.get("max_pixels", max_pixels)
|
| 233 |
+
max_pixels = min(max_pixels_supposed, max_pixels)
|
| 234 |
+
if "resized_height" in ele and "resized_width" in ele:
|
| 235 |
+
resized_height, resized_width = smart_resize(ele["resized_height"], ele["resized_width"], factor=image_factor)
|
| 236 |
+
else:
|
| 237 |
+
resized_height, resized_width = smart_resize(height, width, factor=image_factor, min_pixels=min_pixels, max_pixels=max_pixels)
|
| 238 |
+
video = transforms.functional.resize(video, [resized_height, resized_width], interpolation=InterpolationMode.BICUBIC, antialias=True).float()
|
| 239 |
+
if return_video_sample_fps:
|
| 240 |
+
return video, sample_fps
|
| 241 |
+
return video
|
| 242 |
+
else:
|
| 243 |
+
assert isinstance(ele["video"], (list, tuple))
|
| 244 |
+
process_info = ele.copy()
|
| 245 |
+
process_info.pop("type", None)
|
| 246 |
+
process_info.pop("video", None)
|
| 247 |
+
images = [fetch_image({"image": video_element, **process_info}, size_factor=image_factor) for video_element in ele["video"]]
|
| 248 |
+
nframes = ceil_by_factor(len(images), FRAME_FACTOR)
|
| 249 |
+
if len(images) < nframes:
|
| 250 |
+
images.extend([images[-1]] * (nframes - len(images)))
|
| 251 |
+
if return_video_sample_fps:
|
| 252 |
+
return images, process_info.pop("fps", 2.0)
|
| 253 |
+
return images
|
| 254 |
+
|
| 255 |
+
|
| 256 |
+
def extract_vision_info(conversations: list[dict] | list[list[dict]]) -> list[dict]:
|
| 257 |
+
vision_infos = []
|
| 258 |
+
if isinstance(conversations[0], dict):
|
| 259 |
+
conversations = [conversations]
|
| 260 |
+
for conversation in conversations:
|
| 261 |
+
for message in conversation:
|
| 262 |
+
if isinstance(message["content"], list):
|
| 263 |
+
for ele in message["content"]:
|
| 264 |
+
if (
|
| 265 |
+
"image" in ele
|
| 266 |
+
or "image_url" in ele
|
| 267 |
+
or "video" in ele
|
| 268 |
+
or ele["type"] in ("image", "image_url", "video")
|
| 269 |
+
):
|
| 270 |
+
vision_infos.append(ele)
|
| 271 |
+
return vision_infos
|
| 272 |
+
|
| 273 |
+
|
| 274 |
+
def process_vision_info(
|
| 275 |
+
conversations: list[dict] | list[list[dict]],
|
| 276 |
+
return_video_kwargs: bool = False,
|
| 277 |
+
):
|
| 278 |
+
vision_infos = extract_vision_info(conversations)
|
| 279 |
+
image_inputs = []
|
| 280 |
+
video_inputs = []
|
| 281 |
+
video_sample_fps_list = []
|
| 282 |
+
for vision_info in vision_infos:
|
| 283 |
+
if "image" in vision_info or "image_url" in vision_info:
|
| 284 |
+
image_inputs.append(fetch_image(vision_info))
|
| 285 |
+
elif "video" in vision_info:
|
| 286 |
+
video_input, video_sample_fps = fetch_video(vision_info, return_video_sample_fps=True)
|
| 287 |
+
video_sample_fps_list.append(video_sample_fps)
|
| 288 |
+
video_inputs.append(video_input)
|
| 289 |
+
else:
|
| 290 |
+
raise ValueError("image, image_url or video should in content.")
|
| 291 |
+
if len(image_inputs) == 0:
|
| 292 |
+
image_inputs = None
|
| 293 |
+
if len(video_inputs) == 0:
|
| 294 |
+
video_inputs = None
|
| 295 |
+
if return_video_kwargs:
|
| 296 |
+
return image_inputs, video_inputs, {'fps': video_sample_fps_list}
|
| 297 |
+
return image_inputs, video_inputs
|
code/rerank_ins/cot_data/train_cot.test.jsonl
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{"query_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260610/image/O1CN015JF2pY1wgP0yXxaJD_!!6000000006337-0-tb_se_box.jpg", "instruction": "SUV 后备箱睡觉填平垫", "intent": "配件", "docs": [{"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260610/image/O1CN01TEDUhB2N6fiJLStqs_!!2787569914.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260610/crop_images/O1CN01TEDUhB2N6fiJLStqs_!!2787569914_bbox_116_379_777_747.jpg", "doc_title": "适配极氪ZEEKR001后备箱盖板承重板尾箱垫板隔板挡板工具盒硬衬板", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260610/image/O1CN01FO5sxC1HXKpW5Fmt8_!!2611190767.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260610/crop_images/O1CN01FO5sxC1HXKpW5Fmt8_!!2611190767_bbox_169_212_584_515.jpg", "doc_title": "适用于23-26款本田CRV皓影改装后备箱储物盒收纳盒置物盒找平魔盒", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260610/image/O1CN01G1mBkw1Q74SjAEgno_!!3347371928.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260610/crop_images/O1CN01G1mBkw1Q74SjAEgno_!!3347371928_bbox_0_76_797_660.jpg", "doc_title": "床车护头挡suv车载汽车后排睡垫折叠延长板特斯拉床垫后备箱睡觉", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260610/image/O1CN01nDSnIb1VRIArQWCdh_!!2212991152649.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260610/crop_images/O1CN01nDSnIb1VRIArQWCdh_!!2212991152649_bbox_0_2_798_727.jpg", "doc_title": "适用零跑C11/C10/C01/C16/B10后备箱盖板尾箱垫板地毯承重板隔板", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260610/image/O1CN01tMsyEG1Fnhoo8X0Sx_!!2987220532.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260610/crop_images/O1CN01tMsyEG1Fnhoo8X0Sx_!!2987220532_bbox_51_6_751_606.jpg", "doc_title": "适配大众途观L后备箱备胎盖板10-26款填平底板隔板承重板尾箱垫板", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260610/image/O1CN01Bixfua1dBjcoaIARh~crop,100,0,600,600~_!!268893698.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260610/crop_images/O1CN01Bixfua1dBjcoaIARh~crop,100,0,600,600~_!!268893698_bbox_0_27_598_592.jpg", "doc_title": "适用于奔驰GLB后备箱储物盒glb200储物箱GLB35glb220车载箱置物箱", "label": 3}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260610/image/O1CN01lKUaxp2NI7cDvjSC0_!!3456589939.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260610/crop_images/O1CN01lKUaxp2NI7cDvjSC0_!!3456589939_bbox_115_106_684_594.jpg", "doc_title": "适用于极氪001改装专用后备箱分体隔板折叠储物尾箱挡板备胎盖板", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260610/image/O1CN01FNqpF12HLQoBKR70V_!!2212211099134.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260610/crop_images/O1CN01FNqpF12HLQoBKR70V_!!2212211099134_bbox_336_72_533_224.jpg", "doc_title": "适用2026款乐道L80专用后备箱垫TPE尾箱垫脚垫配件用品改装26新", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260610/image/O1CN01MmvcUW1wN9xoBUnmx_!!2099796295.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260610/crop_images/O1CN01MmvcUW1wN9xoBUnmx_!!2099796295_bbox_104_0_799_644.jpg", "doc_title": "专用14-25款超混电驱奇骏荣耀经典全包围TPE防水后备箱尾箱垫改装", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260610/image/O1CN013fj7xd1eeQ8h48t1h_!!2218598853896.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260610/crop_images/O1CN013fj7xd1eeQ8h48t1h_!!2218598853896_bbox_411_29_776_277.jpg", "doc_title": "新25~26款凯迪拉克XT5后备箱垫尾箱垫改装XT5免贴胶车载用品配件", "label": 2}], "query_crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/v2/all_datas/05/query_crop_images/O1CN015JF2pY1wgP0yXxaJD_!!6000000006337-0-tb_se_box_bbox_0_0_748_998.jpg", "sample_idx": 0, "cot": "【逐项分析】\n0. 该商品为极氪001专用的后备箱承重隔板,图像显示其平整的硬质表面,能够填补后备箱台阶落差,完全符合“填平垫”的核心功能,属于绝对正例。\n1. 该商品是本田CRV专用的后备箱找平魔盒,图像展示了其安装在后备箱后部台阶处,将高低差填平形成平面,精准满足“填平”这一关键意图,属于绝对正例。\n2. 该商品是SUV专用的折叠延长板,图像显示其展开后能连接后排座椅与后备箱,形成平整的睡眠平面,直接对应“睡觉填平”的需求,属于绝对正例。\n3. 该商品为零跑车型专用的后备箱盖板,图像展示了其铺设在后备箱台阶上,使空间变得平整,具备承重和填平功能,符合用户意图,属于绝对正例。\n4. 该商品为大众途观L专用的备胎盖板,图像显示其专门用于覆盖备胎坑,实现后备箱底部的绝对平整,是典型的填平垫,属于绝对正例。\n5. 该商品是奔驰GLB专用的后备箱储物盒,图像显示其内部有复杂的收纳分区和工具槽,主要功能是收纳而非填平,且无法提供平整的睡眠表面,属于弱负例。\n6. 该商品是极氪001的后备箱隔板,虽然具备填平功能,但图像显示其为分体折叠设计,相比整块硬板,其平整度和承重稳定性稍弱,属于违约负例。\n7. 该商品是乐道L80专用的TPE后备箱垫,图像显示其为软性材质,主要功能是防水防污,无法填补后备箱台阶的高低差,属于违约负例。\n8. 该商品是奇骏专用的TPE后备箱垫,图像显示其为软性脚垫材质,不具备填平后备箱落差的功能,无法满足睡觉需求,属于违约负例。\n9. 该商品是凯迪拉克XT5专用的后备箱垫,图像显示其为软性材质,仅覆盖表面,无法解决后备箱台阶的高低差问题,属于违约负例。\n\n【横向对比】\n前五个候选商品(0-4)均为硬质隔板、找平盒或专用延长板,其核心功能就是填补后备箱的高低差,形成平整平面,完美契合“填平”和“睡觉”的意图,因此判定为绝对正例。\n第五个候选商品(5)虽然也是后备箱配件,但它是收纳盒,功能属性完全偏离,属于品类不对的弱负例。\n后四个候选商品(6-9)虽然属于后备箱配件,但要么是折叠隔板(6),要么是软性脚垫(7-9),它们无法提供填平所需的硬度和平整度,属于“对的品类但违反关键约束”的违约负例。", "cot_answer": "0000032222"}
|
| 2 |
+
{"query_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/image/O1CN01fibXwa1CE2A48esiF_!!6000000000048-0-tb_se_box.jpg", "instruction": "银色踢脚线", "intent": "属性改写", "docs": [{"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/image/O1CN01Frmjkm1MSMWVdIz6H_!!1805891433.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/crop_images/O1CN01Frmjkm1MSMWVdIz6H_!!1805891433_bbox_104_0_678_724.jpg", "doc_title": "5公分纯黑色仿铝合金踢脚线地贴脚线实木墙角线线条内襄PVC地脚线", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/image/O1CN017dvk1l2LYUX40lWhk_!!2209013789704.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/crop_images/O1CN017dvk1l2LYUX40lWhk_!!2209013789704_bbox_149_12_998_687.jpg", "doc_title": "不锈钢踢脚线黑色金属地脚线6cm公分304不锈钢铝合金脚线卡扣安装", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/padding_images/image/O1CN017dvk1l2LYUX40lWhk_!!2209013789704.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/crop_images/O1CN017dvk1l2LYUX40lWhk_!!2209013789704_bbox_149_12_998_687.jpg", "doc_title": "不锈钢踢脚线黑色金属地脚线6cm公分304不锈钢铝合金脚线卡扣安装", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/padding_images/image/O1CN017dvk1l2LYUX40lWhk_!!2209013789704.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/padding_images/crop_images/O1CN017dvk1l2LYUX40lWhk_!!2209013789704_bbox_149_12_998_687.jpg", "doc_title": "不锈钢踢脚线黑色金属地脚线6cm公分304不锈钢铝合金脚线卡扣安装", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/padding_images/image/O1CN01Frmjkm1MSMWVdIz6H_!!1805891433.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/crop_images/O1CN01Frmjkm1MSMWVdIz6H_!!1805891433_bbox_104_0_678_724.jpg", "doc_title": "5公分纯黑色仿铝合金踢脚线地贴脚线实木墙角线线条内襄PVC地脚线", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/image/O1CN01vHdAcD1h0415t1pPy_!!2212368164214.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/crop_images/O1CN01vHdAcD1h0415t1pPy_!!2212368164214_bbox_7_18_688_479.jpg", "doc_title": "实木踢脚线新款家用木地板自装纯黑色白色超薄极窄极简木质地脚线", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/image/O1CN01HcuY2W1RrcKcnvRpj_!!874092165.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/crop_images/O1CN01HcuY2W1RrcKcnvRpj_!!874092165_bbox_511_36_605_632.jpg", "doc_title": "超薄极简铝合金踢脚线 打胶自粘贴免钉安装4cm68厘米10地脚线家用", "label": 3}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/image/O1CN01vGZ0ij1fjHVk7oHtB_!!4006964042.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/crop_images/O1CN01vGZ0ij1fjHVk7oHtB_!!4006964042_bbox_0_202_539_537.jpg", "doc_title": "经典卡扣款2~铝合金踢脚线pvc卡扣式4公分墙角线地脚线拉丝不锈钢", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/image/O1CN01B0pkcq1HGO6F4WDcz_!!2208073720730.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/crop_images/O1CN01B0pkcq1HGO6F4WDcz_!!2208073720730_bbox_34_0_788_600.jpg", "doc_title": "铝合金踢脚线4公分6cm极简直边加厚不锈钢贴脚线墙脚线金属地脚线", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/image/O1CN01FK4qdZ2ILia6oQQtB_!!2940279270.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/crop_images/O1CN01FK4qdZ2ILia6oQQtB_!!2940279270_bbox_1_0_749_737.jpg", "doc_title": "不锈钢踢脚线金属防指纹5公分角线黑钛镜面8cm地脚线6加厚拉丝4CM", "label": 2}], "query_crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/v2/all_datas/07/query_crop_images/O1CN01fibXwa1CE2A48esiF_!!6000000000048-0-tb_se_box_bbox_0_168_996_643.jpg", "sample_idx": 2, "cot": "【逐项分析】\n0. 该商品展示了一组踢脚线,其中包含一款深灰色/枪灰色的金属质感款式,虽然标题强调黑色,但实物图中的深灰金属色在视觉上与查询图的银色/金属灰踢脚线高度相似,且品类完全一致,属于满足用户寻找金属质感踢脚线意图的正例。\n1. 该商品展示的是银色拉丝不锈钢踢脚线,颜色、材质(金属拉丝)与查询图像中的银色踢脚线完全一致,且品类正确,完美匹配用户意图。\n2. 该商品展示的是银色拉丝不锈钢踢脚线,颜色、材质(金属拉丝)与查询图像中的银色踢脚线完全一致,且品类正确,完美匹配用户意图。\n3. 该商品展示的是银色拉丝不锈钢踢脚线,颜色、材质(金属拉丝)与查询图像中的银色踢脚线完全一致,且品类正确,完美匹配用户意图。\n4. 该商品展示了一组踢脚线,其中包含一款深灰色/枪灰色的金属质感款式,虽然标题强调黑色,但实物图中的深灰金属色在视觉上与查询图的银色/金属灰踢脚线高度相似,且品类完全一致,属于满足用户寻找金属质感踢脚线意图的正例。\n5. 该商品为实木踢脚线,虽然标题提及黑色/白色,但图片展示的深灰色木纹材质与查询图的金属质感不符,且实木材质与金属踢脚线在视觉风格上存在明显差异,属于品类正确但材质/风格违背关键条件的违约负例。\n6. 该商品展示的是一个金属L型角码或连接件,并非踢脚线成品,属于品类错误,与用户寻找踢脚线的意图无关,判定为弱负例。\n7. 该商品展示的是金色/香槟色的金属踢脚线,虽然材质(金属)与查询图一致,但颜色(金色)与用户意图(银色)明显冲突,属于品类正确但颜色违背关键条件的违约负例。\n8. 该商品展示的是白色/柔光白的踢脚线,虽然材质可能是金属,但颜色(白色)与用户意图(银色)明显不符,属于品类正确但颜色违背关键条件的违约负例。\n9. 该商品展示的是玫瑰金/铜色的金属踢脚线,虽然材质(金属)与查询图一致,但颜色(玫瑰金)与用户意图(银色)明显冲突,属于品类正确但颜色违背关键条件的违约负例。\n\n【横向对比】\n候选1、2、3、0、4均为正例,其中1、2、3是标准的银色金属踢脚线,与查询图完全匹配;0和4虽然标题侧重黑色,但图片中展示的深灰金属色在视觉上接近银色,且材质相符,故归为正例。候选5、7、8、9均为违约负例,它们都是踢脚线,但材质(实木)或颜色(金色、白色、玫瑰金)与查询图的“银色金属”特征不符。候选6是弱负例,因为它是金属配件而非踢脚线,品类直接错误。", "cot_answer": "0000023222"}
|
| 3 |
+
{"query_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/image/O1CN01CdbfK21cCMj1uvlZI_!!6000000003564-0-tb_se_box.jpg", "instruction": "潜星草铵草甘膦", "intent": "属性改写", "docs": [{"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/image/O1CN01xYe09Z1DuueDzoIVd_!!2275810277.png", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/crop_images/O1CN01xYe09Z1DuueDzoIVd_!!2275810277_bbox_125_133_389_704.png", "doc_title": "潜星防备草铵膦草甘膦钾盐草铵磷草安磷除杂草杀草除草剂正品农药", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/image/O1CN01gdVRMF1HjhUCbZaJi_!!2200538500794.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/crop_images/O1CN01gdVRMF1HjhUCbZaJi_!!2200538500794_bbox_503_46_742_691.jpg", "doc_title": "草铵磷杀草剂牛筋草除草剂果园林地荒地除杂草小飞蓬草铵膦除草剂", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/image/O1CN01jRVyHp2GtwmJiVFbI_!!4131299074.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/crop_images/O1CN01jRVyHp2GtwmJiVFbI_!!4131299074_bbox_434_441_490_614.jpg", "doc_title": "精草铵磷铵盐专用除草烂根剂一扫精草胺磷铵盐草铵磷正品光除草剂", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/image/O1CN01FjM4yq1emfA3eBNht_!!3829893914.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/crop_images/O1CN01FjM4yq1emfA3eBNht_!!3829893914_bbox_616_306_873_1100.jpg", "doc_title": "新安斩束21.9%精草铵膦铵盐烂根除草剂牛筋草小飞蓬果园 茶园农药", "label": 1}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/image/O1CN01xW4iQG2GtwyLK3LBp_!!4131299074.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/crop_images/O1CN01xW4iQG2GtwyLK3LBp_!!4131299074_bbox_328_400_387_573.jpg", "doc_title": "精草铵磷铵盐专用除草烂根剂一扫精草胺磷铵盐草铵磷正品光除草剂", "label": 1}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/image/O1CN0106t77J271bSo9Uz4J_!!2200695747737.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/crop_images/O1CN0106t77J271bSo9Uz4J_!!2200695747737_bbox_1061_371_1462_1373.jpg", "doc_title": "草怕 滴酸草甘膦非耕地荒地开荒除杂草除草剂兴隆化工", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/image/O1CN0191Rcwd1y1IanickfA_!!1895526518.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/crop_images/O1CN0191Rcwd1y1IanickfA_!!1895526518_bbox_587_235_765_713.jpg", "doc_title": "猛劲草甘膦钾盐草甘磷除草烂根剂正品烂根除草专用药灭除草剂农药", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/image/O1CN01FpqCHp1ZKCXHgCDTs_!!893283175.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/crop_images/O1CN01FpqCHp1ZKCXHgCDTs_!!893283175_bbox_254_127_469_656.jpg", "doc_title": "32%滴酸草甘膦2,4滴速效草甘磷恶性杂草小飞蓬荒地烂根除草剂锄草", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/image/O1CN01Cmidg320pnaDTwDLx_!!2979386899.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/crop_images/O1CN01Cmidg320pnaDTwDLx_!!2979386899_bbox_1180_599_1999_2584.jpg", "doc_title": "永农农爽乐20%精草铵膦铵盐草胺磷正品草铵磷牛筋草小飞蓬除草剂", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/padding_images/image/O1CN0191Rcwd1y1IanickfA_!!1895526518.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/crop_images/O1CN0191Rcwd1y1IanickfA_!!1895526518_bbox_587_235_765_713.jpg", "doc_title": "猛劲草甘膦钾盐草甘磷除草烂根剂正品烂根除草专用药灭除草剂农药", "label": 2}], "query_crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/v2/all_datas/06/query_crop_images/O1CN01CdbfK21cCMj1uvlZI_!!6000000003564-0-tb_se_box_bbox_279_53_615_690.jpg", "sample_idx": 3, "cot": "【逐项分析】\n0. 该商品标签明确标识为“草铵·草甘膦”,属于草铵膦与草甘膦的复配除草剂,完全符合用户搜索的“潜星草铵草甘膦”关键成分与品类,属于绝对正例。\n1. 该商品标题与标签均明确提及“草铵膦”,是用户搜索意图中的核心成分之一,且为除草剂品类,符合用户需求,属于绝对正例。\n2. 该商品标题包含“精草铵磷”,即草铵膦,与用户搜索意图中的核心成分“草铵”匹配,属于除草剂品类,符合用户需求,属于绝对正例。\n3. 该商品成分为“精草铵膦铵盐”,含有用户意图中的“草铵”成分,属于相关除草剂,但非复配草甘膦,属于基本满足需求的相对正例。\n4. 该商品成分为“精草铵膦铵盐”,含有用户意图中的“草铵”成分,属于相关除草剂,但非复配草甘膦,属于基本满足需求的相对正例。\n5. 该商品成分为“滴酸草甘膦”,含有用户意图中的“草甘膦”成分,但缺乏“草铵”成分,属于单品草甘膦,违背了复配的关键条件,为违约负例。\n6. 该商品成分为“草甘膦钾盐”,含有用户意图中的“草甘膦”成分,但缺乏“草铵”成分,属于单品草甘膦,违背了复配的关键条件,为违约负例。\n7. 该商品成分为“滴酸草甘膦”,含有用户意图中的“草甘膦”成分,但缺乏“草铵”成分,属于单品草甘膦,违背了复配的���键条件,为违约负例。\n8. 该商品成分为“精草铵膦铵盐”,含有用户意图中的“草铵”成分,但缺乏“草甘膦”成分,属于单品草铵膦,违背了复配的关键条件,为违约负例。\n9. 该商品成分为“草甘膦钾盐”,含有用户意图中的“草甘膦”成分,但缺乏“草铵”成分,属于单品草甘膦,违背了复配的关键条件,为违约负例。\n\n【横向对比】\n前三个候选商品(0-2)直接命中了“草铵膦+草甘膦”的复配组合或明确包含核心成分,完全符合用户意图,故判定为绝对正例(0)。\n候选商品(3-4)仅包含草铵膦成分,虽未完全匹配复配要求,但属于同一核心成分下的相关品类,故判定为相对正例(1)。\n候选商品(5-9)均为单一成分的除草剂(仅含草甘膦或仅含草铵膦),虽然品类正确(都是除草剂),但违背了用户搜索“复配”或“同时包含两种成分”的关键约束,故判定为违约负例(2)。\n区分点在于:是否同时包含草铵膦和草甘膦(0),是否至少包含其中一种核心成分(1 vs 2),以及是否品类完全错误(此处无3)。", "cot_answer": "0001122222"}
|
| 4 |
+
{"query_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260622/image/O1CN01gfuBlI294TDfxx1n6_!!6000000008014-0-tb_se_box.jpg", "instruction": "白色中式太极服", "intent": "相似", "docs": [{"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260622/image/O1CN016lgbma1vAxurAniUD_!!1673096133.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260622/crop_images/O1CN016lgbma1vAxurAniUD_!!1673096133_bbox_188_339_949_1391.jpg", "doc_title": "中国风男装套装改良汉服男款禅意茶服男飘逸古风中式道袍男演出服", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260622/image/O1CN014bHRWT22Qj9MGmvJw~crop,0,297,1695,1695~_!!1867187115.png", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260622/crop_images/O1CN014bHRWT22Qj9MGmvJw~crop,0,297,1695,1695~_!!1867187115_bbox_416_206_1089_1314.png", "doc_title": "新中式国风禅意白色茶服套装女夏改良盘扣上衣阔腿长裤棉麻两件套", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260622/image/O1CN01co1Ooq1suMcedMe3L_!!2857045826.png", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260622/crop_images/O1CN01co1Ooq1suMcedMe3L_!!2857045826_bbox_805_409_1374_1171.png", "doc_title": "批发价捡漏~新中式薄款禅茶服棉麻套装女仙气太极拜佛禅修两件套", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260622/image/O1CN01JeXtwO1PpfIP0HB0a_!!2114801890.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260622/crop_images/O1CN01JeXtwO1PpfIP0HB0a_!!2114801890_bbox_207_23_556_449.jpg", "doc_title": "中式瑜伽服套装女白色昆达里尼冥想禅修太极服打坐辟谷舞韵表演服", "label": 1}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260622/image/O1CN01SCc5gE2B1NlOjGLRJ_!!3449228278.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260622/crop_images/O1CN01SCc5gE2B1NlOjGLRJ_!!3449228278_bbox_244_458_957_1307.jpg", "doc_title": "禅意风仙气疗愈居士打坐禅服女道家拜佛修行衣服新中式国风太极服", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260622/image/O1CN01z203dN1CdDwHPXn8V~crop,0,67,800,1066~_!!1742240103.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260622/crop_images/O1CN01z203dN1CdDwHPXn8V~crop,0,67,800,1066~_!!1742240103_bbox_184_514_657_1064.jpg", "doc_title": "夏季新中式重工刺绣阔腿裤裙女宽松大码松紧腰显瘦休闲裤", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260622/image/O1CN01WBbeqS1nQz3GrSIz2_!!78435085.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260622/crop_images/O1CN01WBbeqS1nQz3GrSIz2_!!78435085_bbox_262_384_479_749.jpg", "doc_title": "改良古风套装女童汉服春季新中式大童春夏连衣裙公主女孩国风旗袍", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260622/image/O1CN01Jqxts21M5v1rgnaCT_!!4239271384.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260622/crop_images/O1CN01Jqxts21M5v1rgnaCT_!!4239271384_bbox_35_57_577_691.jpg", "doc_title": "太极服套装男女练功夫表演中国风武术裤棉麻春秋立领盘扣打底衫", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260622/padding_images/image/O1CN01Jqxts21M5v1rgnaCT_!!4239271384.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260622/crop_images/O1CN01Jqxts21M5v1rgnaCT_!!4239271384_bbox_35_57_577_691.jpg", "doc_title": "太极服套装男女练功夫表演中���风武术裤棉麻春秋立领盘扣打底衫", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260622/padding_images/image/O1CN01WBbeqS1nQz3GrSIz2_!!78435085.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260622/crop_images/O1CN01WBbeqS1nQz3GrSIz2_!!78435085_bbox_262_384_479_749.jpg", "doc_title": "改良古风套装女童汉服春季新中式大童春夏连衣裙公主女孩国风旗袍", "label": 2}], "query_crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/v2/all_datas/00/query_crop_images/O1CN01gfuBlI294TDfxx1n6_!!6000000008014-0-tb_se_box_bbox_570_309_725_525.jpg", "sample_idx": 4, "cot": "【逐项分析】\n0. 该商品为白色中式男装,款式为立领盘扣,材质飘逸,与查询图像中的太极服在颜色、款式和风格上高度一致,完全符合用户寻找白色中式太极服的意图。\n1. 该商品为白色新中式女装套装,采用棉麻材质和盘扣设计,风格飘逸,与查询图像中的太极服在品类、颜色和风格上完全匹配,满足用户需求。\n2. 该商品为白色新中式薄款套装,标题明确提及“太极”,款式为立领盘扣,与查询图像中的太极服在颜色、款式和用途上完全一致,符合用户意图。\n3. 该商品标题明确提及“太极服”,款式为中式立领盘扣,但图片展示为粉色,与查询图像中的白色不符,属于品类正确但颜色违背关键条件的情况。\n4. 该商品为白色中式禅服,款式为立领盘扣,标题提及“太极服”,与查询图像中的太极服在颜色、款式和风格上完全一致,符合用户意图。\n5. 该商品为黑色刺绣阔腿裤裙,虽然属于中式风格,但颜色为黑色且为半身裙,与查询图像中的白色太极服在颜色和品类上均不符,属于品类正确但违背关键条件的情况。\n6. 该商品为黄色改良古风连衣裙,虽然属于中式风格,但颜色为黄色且为连衣裙,与查询图像中的白色太极服在颜色和品类上均不符,属于品类正确但违背关键条件的情况。\n7. 该商品为蓝色太极服套装,虽然款式为中式立领盘扣,但颜色为蓝色,与查询图像中的白色不符,属于品类正确但颜色违背关键条件的情况。\n8. 该商品为蓝色太极服套装,虽然款式为中式立领盘扣,但颜色为蓝色,与查询图像中的白色不符,属于品类正确但颜色违背关键条件的情况。\n9. 该商品为黄色改良古风连衣裙,虽然属于中式风格,但颜色为黄色且为连衣裙,与查询图像中的白色太极服在颜色和品类上均不符,属于品类正确但违背关键条件的情况。\n\n【横向对比】\n候选0、1、2、4均为白色中式服装,款式为立领盘扣,与查询图像中的太极服在颜色、款式和风格上完全一致,属于绝对正例。候选3虽然标题提及“太极服”,但图片展示为粉色,与查询图像中的白色不符,属于相对正例。候选5、6、7、8、9虽然属于中式风格或太极服,但颜色或品类与查询图像中的白色太极服不符,属于违约负例。", "cot_answer": "0001022222"}
|
| 5 |
+
{"query_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260617/image/O1CN01JpcEVA1IhF715NLDB_!!6000000000924-0-tb_se_box.jpg", "instruction": "不锈钢家具脚", "intent": "属性改写", "docs": [{"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260617/image/TB2aIeZxsuYBuNkSmRyXXcA3pXa_!!367537823.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260617/crop_images/TB2aIeZxsuYBuNkSmRyXXcA3pXa_!!367537823_bbox_277_81_428_505.jpg", "doc_title": "不锈钢沙发脚 柜脚 床脚 桌脚 桌腿 家具脚 五金脚垫 带M8螺杆", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260617/image/O1CN01CQXQni1hqH2rZEo7e_!!2216384144328.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260617/crop_images/O1CN01CQXQni1hqH2rZEo7e_!!2216384144328_bbox_47_254_263_672.jpg", "doc_title": "黄垫重型脚杯固定地脚碳钢镀铬底盘镀锌丝杆减震支撑可调节脚", "label": 1}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260617/padding_images/image/TB2aIeZxsuYBuNkSmRyXXcA3pXa_!!367537823.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260617/crop_images/TB2aIeZxsuYBuNkSmRyXXcA3pXa_!!367537823_bbox_277_81_428_505.jpg", "doc_title": "不锈钢沙发脚 柜脚 床脚 桌脚 桌腿 家具脚 五金脚垫 带M8螺杆", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260617/padding_images/image/TB2aIeZxsuYBuNkSmRyXXcA3pXa_!!367537823.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260617/padding_images/crop_images/TB2aIeZxsuYBuNkSmRyXXcA3pXa_!!367537823_bbox_277_81_428_505.jpg", "doc_title": "不锈钢沙发脚 柜脚 床脚 桌脚 桌腿 家具脚 五金脚垫 带M8螺杆", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260617/padding_images/image/O1CN01CQXQni1hqH2rZEo7e_!!2216384144328.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260617/crop_images/O1CN01CQXQni1hqH2rZEo7e_!!2216384144328_bbox_47_254_263_672.jpg", "doc_title": "黄垫重型脚杯固定地脚碳钢镀铬底盘镀锌丝杆减震支撑可调节脚", "label": 1}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260617/image/O1CN01kic4hc1KWpA51Fbhu_!!0-item_pic.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260617/crop_images/O1CN01kic4hc1KWpA51Fbhu_!!0-item_pic_bbox_55_67_746_738.jpg", "doc_title": "沙发脚垫桌腿床脚防移动桌脚地面防滑茶几桌子固定器吸盘保护垫片", "label": 3}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260617/image/O1CN016Am2yt1IceesOVopV_!!2785220914.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260617/crop_images/O1CN016Am2yt1IceesOVopV_!!2785220914_bbox_0_354_254_597.jpg", "doc_title": "强力吸盘玻璃固定螺帽加强吸力加大吸盘阳光房隔热板", "label": 3}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260617/image/O1CN01jFREag266LsxgJYir_!!2472107612.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260617/crop_images/O1CN01jFREag266LsxgJYir_!!2472107612_bbox_97_355_1093_1281.jpg", "doc_title": "蹦蹦床吸盘防滑稳固静音脚套跳床通用燃气灶脚垫煤气灶台吸盘防滑", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260617/image/O1CN01p8dta01fFW7FPHaQC_!!1095363977.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260617/crop_images/O1CN01p8dta01fFW7FPHaQC_!!1095363977_bbox_571_0_1053_450.jpg", "doc_title": "燃气灶脚套吸盘固定器25钢管桌腿配件蹦蹦床防滑静音稳固减震脚垫", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260617/image/O1CN01Fr5ak02HcpqG6OKll_!!4611686018427381684-0-item_pic.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260617/crop_images/O1CN01Fr5ak02HcpqG6OKll_!!4611686018427381684-0-item_pic_bbox_486_57_741_313.jpg", "doc_title": "方管圆管吸盘防滑脚稳固防移位桌脚床脚内嵌空心管架吸盘桌子固定", "label": 2}], "query_crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/v2/all_datas/06/query_crop_images/O1CN01JpcEVA1IhF715NLDB_!!6000000000924-0-tb_se_box_bbox_390_0_631_345.jpg", "sample_idx": 6, "cot": "【逐项分析】\n0. 该商品为实心圆柱体不锈钢家具脚,材质为不锈钢,结构(金属杆+底座)与查询图像中的家具脚高度一致,完全符合用户对不锈钢家具脚的搜索意图,判定为绝对正例。\n1. 该商品属于重型家具脚杯,虽具备金属底盘和支撑功能,属于家具脚品类,但其主体为螺纹杆结构且底盘较薄,与查询图中实心粗壮的金属柱状脚在形态上存在差异,属于同品类但款式边界模糊的相对正例。\n2. 该商品与候选0完全相同,均为实心圆柱体不锈钢家具脚,材质和结构均完美匹配查询意图,判定为绝对正例。\n3. 该商品同为实心圆柱体不锈钢家具脚,虽然尺寸参数略有不同,但核心品类、材质和外观结构与查询图像一致,满足用户需求,判定为绝对正例。\n4. 该商品与候选1相同,属于螺纹杆结构的脚杯,虽属家具脚品类,但与查询图中实心金属柱的形态不符,判定为相对正例。\n5. 该商品仅为黑色橡胶防滑垫片,无支撑柱体,属于配件而非家具脚,品类错误,判定为弱负例。\n6. 该商品为透明吸盘固定件,主要用于玻璃或板材固定,与金属家具脚在材质、功能和品类上均无关联,判定为弱负例。\n7. 该商品为黑色橡胶脚套,属于包裹在家具脚外侧的配件,并非家具脚本体,属于品类混淆的违约负例。\n8. 该商品同样为橡胶材质的防滑脚套,非金属家具脚,属于配件与主体的混淆,判定为违约负例。\n9. 该商品为用于固定管架的吸盘底座,属于辅助固定配件,不具备家具脚的独立支撑属性,判定为违约负例。\n\n【横向对比】\n绝对正例(0, 2, 3)均为实心不锈钢圆柱体脚,与查询图中的金属柱状结构高度吻合,是用户意图的最优解。相对正例(1, 4)虽属于家具脚品类,但为细长的螺纹杆结构,与查询图的粗实柱状形态存在视觉差异。违约负例(7, 8, 9)均为橡胶材质或吸盘结构的配件,虽然与家具脚相关,但属于“脚套”或“底座”而非“家具脚”本体,属于品类正确但关键属性(材质、主体)违背。弱负例(5, 6)则完全脱离了金属家具脚的范畴,属于无关品类。", "cot_answer": "0100133222"}
|
| 6 |
+
{"query_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/image/O1CN01JhDg7n20DmjKI9PRa_!!6000000006816-0-tb_se_box.jpg", "instruction": "玻璃展柜的穿线管", "intent": "配件", "docs": [{"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/image/O1CN01Rluibc1z4KaBzWOpc_!!52786660.png", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/crop_images/O1CN01Rluibc1z4KaBzWOpc_!!52786660_bbox_18_147_784_616.png", "doc_title": "diy灯饰配件挂件铝管配件空心铝管珠帘装饰铝管 铝棒灯饰刻花铝管", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/image/O1CN01LslBbi1T66J9Zmk0j_!!2209305262332.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/crop_images/O1CN01LslBbi1T66J9Zmk0j_!!2209305262332_bbox_266_375_2190_2247.jpg", "doc_title": "PVC欧式背景墙装饰线条石膏吊顶线画框镜子收边条美缝软线自粘", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/image/TB2GkYCeFXXXXXTXpXXXXXXXXXX_!!250710351.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/crop_images/TB2GkYCeFXXXXXTXpXXXXXXXXXX_!!250710351_bbox_404_0_581_799.jpg", "doc_title": "厂家直销指甲纹铝管 雕花铝管 六角管 八角管 梅花管 树叶问铝管", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/padding_images/image/TB2GkYCeFXXXXXTXpXXXXXXXXXX_!!250710351.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/crop_images/TB2GkYCeFXXXXXTXpXXXXXXXXXX_!!250710351_bbox_404_0_581_799.jpg", "doc_title": "厂家直销指甲纹铝管 雕花铝管 六角管 八角管 梅花管 树叶问铝管", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/padding_images/image/O1CN01LslBbi1T66J9Zmk0j_!!2209305262332.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/crop_images/O1CN01LslBbi1T66J9Zmk0j_!!2209305262332_bbox_266_375_2190_2247.jpg", "doc_title": "PVC欧式背景墙装饰线条石膏吊顶线画框镜子收边条美缝软线自粘", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/image/O1CN01FTC0IY1jibs0yIFSe_!!2183024582.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/crop_images/O1CN01FTC0IY1jibs0yIFSe_!!2183024582_bbox_32_55_720_500.jpg", "doc_title": "网红黑色卫生间腰线瓷砖法式欧姆线墙砖半圆装饰收边条半墙哑光", "label": 3}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/image/O1CN01YqVmYm1FrMey0qSZt_!!49730540.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/crop_images/O1CN01YqVmYm1FrMey0qSZt_!!49730540_bbox_1_8_238_715.jpg", "doc_title": "diy灯饰珠帘配件空心刻花订制门帘风铃装饰铝棒灯饰银色刻花铝管", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/image/O1CN01nAXp2v1sTLEzvLwym_!!2376435767.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/crop_images/O1CN01nAXp2v1sTLEzvLwym_!!2376435767_bbox_179_107_1095_1277.jpg", "doc_title": "花纹扁铁条压花扁钢压条锻打长条锻料方钢条型材回水纹可裁剪切割", "label": 3}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/image/O1CN01erRJkR1usBcauva4Q_!!2200785216092.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/crop_images/O1CN01erRJkR1usBcauva4Q_!!2200785216092_bbox_22_0_579_614.jpg", "doc_title": "DIY镜子边框装饰条自粘pvc软线条石膏线背景墙包边条相框画框边框", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/image/O1CN01teYyoU1azi2b8rVmM_!!1667183401.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260614/crop_images/O1CN01teYyoU1azi2b8rVmM_!!1667183401_bbox_223_81_397_423.jpg", "doc_title": "金属异形T型拉丝封边条展板文化墙封边条", "label": 3}], "query_crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/v2/all_datas/01/query_crop_images/O1CN01JhDg7n20DmjKI9PRa_!!6000000006816-0-tb_se_box_bbox_125_354_240_996.jpg", "sample_idx": 7, "cot": "【逐项分析】\n0. 该商品为空心铝管,材质与形态符合“穿线管”的管状特征,且标题明确提及“空心铝管”,可作为玻璃展柜的穿线配件,完全满足用户意图。\n1. 该商品为PVC装饰线条,虽然主要用于装饰,但作为管状或条状材料,在展柜制作中常被用作走线槽或收边,属于展柜配件范畴,基本满足需求。\n2. 该商品为雕花铝管,属于金属管材,虽然表面有花纹,但本质仍是“管”,符合穿线管的品类定义,满足用户寻找管材的意图。\n3. 该商品与候选2相同,为雕花铝管,属于金属管材,符合穿线管的品类定义,满足用户寻找管材的意图。\n4. 该商品与候选1相同,为PVC装饰线条,属于展柜制作中常用的走线槽或收边材料,基本满足需求。\n5. 该商品为瓷砖腰线,属于陶瓷建材,主要用于墙面装饰,与金属或塑料穿线管在材质和用途上完全不同,属于品类错误。\n6. 该商品为空心刻花铝管,虽然材质和管状形态符合,但标题强调“珠帘配件”、“风铃装饰”,其设计初衷是装饰而非穿线,属于品类正确但违背关键功能意图。\n7. 该商品为锻打铁艺围栏,属于大型户外建材,与展柜穿线管在尺寸、材质和用途上完全无关,属于品类错误。\n8. 该商品为PVC相框装饰条,虽然材质与候选1、4类似,但标题强调“相框”、“镜子边框”,属于装饰配件而非穿线管,属于品类正确但违背关键功能意图。\n9. 该商品为金属封边条,属于板材加工配件,主要用于封边而非穿线,与穿线管在功能和形态上均不匹配,属于品类错误。\n\n【横向对比】\n候选0、2、3为金属管材,候选1、4为PVC线条,它们都具备“管状”或“槽状”的物理形态,能够容纳线缆,因此被判定为高度相关或基本相关。其中,候选0、2、3因明确为“管”且空心,匹配度最高;候选1、4虽为装饰线条,但在展柜DIY中常兼作走线槽,故归为相对正例。\n候选6、8虽然也是管状或条状材料,但其核心卖点在于“装饰”(珠帘、相框),而非“穿线”功能,属于“对的品类但违反约束”,故归为违约负例。\n候选5、7、9分别为瓷砖、围栏和封边条,与穿线管在品类上完全无关,属于弱负例。", "cot_answer": "0000032323"}
|
| 7 |
+
{"query_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260613/image/O1CN01eNUZez1n0sZ79DI0v_!!6000000005028-0-tb_se_box.jpg", "instruction": "白色设计感上衣", "intent": "相似", "docs": [{"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260613/image/O1CN01pxDIiH1xwijEKeE2c_!!2842426508.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260613/crop_images/O1CN01pxDIiH1xwijEKeE2c_!!2842426508_bbox_260_113_1341_1770.jpg", "doc_title": "设计感短袖t恤女大码胖mm2026夏季新款爆款宽松显瘦女士半袖上衣", "label": 1}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260613/image/O1CN019Shan21X3auIcPgkj_!!2216150882868.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260613/crop_images/O1CN019Shan21X3auIcPgkj_!!2216150882868_bbox_172_353_613_990.jpg", "doc_title": "枫竺【现货】不规则收腰显瘦圆领短袖T恤上衣女2026夏季短款小衫", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260613/image/O1CN012URD0y1KUX2JvBi56_!!2259831167.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260613/crop_images/O1CN012URD0y1KUX2JvBi56_!!2259831167_bbox_165_132_1029_1346.jpg", "doc_title": "欧洲站不规则开叉正肩短袖t恤2026夏日凉感穿搭收腰体恤修身上衣", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260613/image/O1CN01JxK9Ka1E1KKhMiNrN_!!2542360291.png", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260613/crop_images/O1CN01JxK9Ka1E1KKhMiNrN_!!2542360291_bbox_276_256_616_705.png", "doc_title": "诗路茵儿女装冰丝白色短袖t恤女夏收腰开叉设计感小众简约上衣女", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260613/image/O1CN01gak6kv1sKdjHm6Smi_!!2973555748.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260613/crop_images/O1CN01gak6kv1sKdjHm6Smi_!!2973555748_bbox_450_274_760_703.jpg", "doc_title": "大码设计感不规则圆领短袖t恤2026年新款微胖超火减龄上衣女夏季", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260613/image/O1CN01LxCMas2CVtmXgzeM4_!!740748480.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260613/crop_images/O1CN01LxCMas2CVtmXgzeM4_!!740748480_bbox_17_622_731_1559.jpg", "doc_title": "柒量一裁小个子收腰上衣女夏不规则气质显瘦百搭短袖T恤2131预", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260613/image/O1CN01sr4pPq1CwSZN1j7iW_!!416130145.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260613/crop_images/O1CN01sr4pPq1CwSZN1j7iW_!!416130145_bbox_143_126_756_838.jpg", "doc_title": "专柜价439【哈】纯色不规则设计感正肩修身T恤品牌撤柜26夏短袖女", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260613/image/O1CN010CqfgU1MK7ZQZnqzp_!!2208608541415.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260613/crop_images/O1CN010CqfgU1MK7ZQZnqzp_!!2208608541415_bbox_361_293_695_793.jpg", "doc_title": "2026夏季设计感黑色交叉短袖T恤大码女装胖mm不规则显瘦冰丝上衣", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260613/image/O1CN01EmxcdJ1E4zYO1mnQL_!!4045460299.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260613/crop_images/O1CN01EmxcdJ1E4zYO1mnQL_!!4045460299_bbox_160_787_467_1175.jpg", "doc_title": "圆领不规则设计感收腰短袖t恤女士2026夏季新款粉色半袖上衣体恤", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260613/image/O1CN01hcjgyL1Z8kkHkIDpx_!!4611686018427381726-0-item_pic.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260613/crop_images/O1CN01hcjgyL1Z8kkHkIDpx_!!4611686018427381726-0-item_pic_bbox_29_108_693_988.jpg", "doc_title": "黑色正肩短袖t恤女夏季薄2026新款修身设计感小众飞飞袖体恤上衣", "label": 2}], "query_crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/v2/all_datas/01/query_crop_images/O1CN01eNUZez1n0sZ79DI0v_!!6000000005028-0-tb_se_box_bbox_218_264_395_503.jpg", "sample_idx": 8, "cot": "【逐项分析】\n0. 该商品为黑色短袖T恤,虽然具备设计感,但颜色为黑色,与用户意图中的“白色”不符,属于品类正确但违背关键颜色条件的违约负例。\n1. 该商品为白色短袖T恤,侧边有独特的扭结设计,完全符合“白色”和“设计感”两个核心条件,是绝对正例。\n2. 该商品为白色短袖T恤,采用不规则开叉设计,满足用户对颜色和款式的具体要求,是绝对正例。\n3. 该商品为白色短袖T恤,侧边有褶皱收腰设计,符合“白色设计感上衣”的搜索意图,是绝对正例。\n4. 该商品为白色短袖T恤,具有不规则下摆设计,精准匹配用户意图,是绝对正例。\n5. 该商品为浅蓝色短袖T恤,虽然款式符合设计感,但颜色明显不是白色,属于违背关键颜色条件的违约负例。\n6. 该商品为黑色短袖T恤,款式符合设计感,但颜色与用户要求的“白色”相悖,属于违约负例。\n7. 该商品为黑色短袖T恤,虽然具备设计感,但颜色错误,属于违背关键颜色条件的违约负例。\n8. 该商品为黑色短袖T恤,款式符合设计感,但颜色与用户要求的“白色”不符,属于违约负例。\n9. 该商品为黑色短袖T恤,虽然具备设计感,但颜色与用户要求的“白色”不符,属于违约负例。\n\n【横向对比】\n候选1、2、3、4均为白色且具有明显设计感的上衣,完美契合用户意图,属于绝对正例。候选0、5、6、7、8、9虽然款式上属于设计感上衣,但颜色分别为黑色或浅蓝色,与用户明确要求的“白色”不符,属于违约负例。其中,候选0虽然颜色不符,但作为上衣品类,比完全无关的品类(弱负例)相关性稍高,但在本组数据中,所有非白色候选均因违背颜色约束而归为违约负例。", "cot_answer": "1000022222"}
|
| 8 |
+
{"query_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/image/O1CN01avvCAT1lXHM6nG2zB_!!6000000004828-0-tb_se_box.jpg", "instruction": "厚底一脚蹬板鞋", "intent": "相似", "docs": [{"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/image/O1CN01RpcPkt2Det907k9UL_!!1642218635.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/crop_images/O1CN01RpcPkt2Det907k9UL_!!1642218635_bbox_94_1497_692_1805.jpg", "doc_title": "公牛世家鞋子男2026新款潮流百搭休闲板鞋男士轻便厚底时尚运动鞋", "label": 1}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/image/O1CN01fWc3cy1c6PZOAUUPt~crop,0,250,1500,1500~_!!2200728763551.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/crop_images/O1CN01fWc3cy1c6PZOAUUPt~crop,0,250,1500,1500~_!!2200728763551_bbox_225_115_756_729.jpg", "doc_title": "冰丝布面!防滑橡胶底!夏季外贸男士轻便舒适透气低帮休闲帆布鞋", "label": 1}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/image/O1CN010n6uwP2EAwsu7p4hp_!!2218571438705-2-qnaigc.png", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/crop_images/O1CN010n6uwP2EAwsu7p4hp_!!2218571438705-2-qnaigc_bbox_188_332_613_1234.png", "doc_title": "江南袋鼠休闲皮鞋男2026夏季新款一脚蹬小白鞋舒适透气英伦爸爸鞋", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/image/O1CN01oLpqcd1WkMQjJKK1B_!!1583002826.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/crop_images/O1CN01oLpqcd1WkMQjJKK1B_!!1583002826_bbox_258_196_582_728.jpg", "doc_title": "2026年新款高品质冰丝布透气圆头休闲板鞋一脚蹬防滑上线百搭女鞋", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/image/O1CN01WojKdR1qefonl4KJ7_!!2222114025521-0-qnaigc.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/crop_images/O1CN01WojKdR1qefonl4KJ7_!!2222114025521-0-qnaigc_bbox_25_62_422_787.jpg", "doc_title": "公牛世家男鞋新款春季时尚潮流一脚蹬休闲皮鞋男士百搭通勤低帮鞋", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/image/O1CN01I7e3VZ2D652UONlNH_!!2218980038559.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/crop_images/O1CN01I7e3VZ2D652UONlNH_!!2218980038559_bbox_147_269_652_515.jpg", "doc_title": "百丽男士户外休闲工装板鞋男2025秋季新款牛皮革厚底增高A2137CM5", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/image/O1CN01jx9HGM2EAwtzNhLNB_!!2218571438705.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/crop_images/O1CN01jx9HGM2EAwtzNhLNB_!!2218571438705_bbox_19_433_731_889.jpg", "doc_title": "江南袋鼠透气休闲板鞋2026夏季新款软底运动舒适平底镂空爸爸皮鞋", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/image/O1CN01Kb2ook1yGsAX5xALG_!!467426552.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/crop_images/O1CN01Kb2ook1yGsAX5xALG_!!467426552_bbox_200_450_1241_1083.jpg", "doc_title": "卓诗尼厚底工装鞋男2026夏季新款复古透气运动增高休闲户外板鞋子", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/image/O1CN01GDq6vn2BaeEJ2SRnL_!!708668355.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/crop_images/O1CN01GDq6vn2BaeEJ2SRnL_!!708668355_bbox_132_337_868_780.jpg", "doc_title": "森达工装鞋男款2026春季新款商场同款户外徒步休闲皮鞋K4E03AM6", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/image/O1CN01yoJx7C1VGlBLxzlrD_!!2831472626.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/crop_images/O1CN01yoJx7C1VGlBLxzlrD_!!2831472626_bbox_324_473_2489_2385.jpg", "doc_title": "老北京布鞋男休闲鞋板鞋休闲运动鞋平底鞋系带厚底轻质布鞋商务鞋", "label": 2}], "query_crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/v2/all_datas/01/query_crop_images/O1CN01avvCAT1lXHM6nG2zB_!!6000000004828-0-tb_se_box_bbox_65_203_342_853.jpg", "sample_idx": 1, "cot": "【逐项分析】\n0. 该商品为男士休闲板鞋,具有明显的厚底设计,且鞋后跟配有提环,符合“一脚蹬”的穿脱特征,虽然颜色与查询图不同,但款式和关键属性(厚底、一脚蹬)高度匹配,属于相对正例。\n1. 该商品为男士休闲帆布鞋,具备厚底特征,且鞋后跟配有提环,符合“一脚蹬”的穿脱方式,品类和关键属性均符合用户意图,属于相对正例。\n2. 该商品标题明确标注“一脚蹬小白鞋”,图像显示为无鞋带设计,完全符合“一脚蹬”的核心特征,且为板鞋品类,属于绝对正例。\n3. 该商品标题明确标注“一脚蹬”,图像显示为无鞋带设计,且具备厚底特征,完全符合用户搜索的“厚底一脚蹬板鞋”意图,属于绝对正例。\n4. 该商品标题明确标注“一脚蹬”,图像显示为无鞋带设计,且具备厚底特征,完全符合用户搜索的“厚底一脚蹬板鞋”意图,属于绝对正例。\n5. 该商品为系带款板鞋,虽然具备厚底特征,但必须通过系带穿脱,违背了“一脚蹬”这一关键约束条件,属于违约负例。\n6. 该商品为系带款板鞋,虽然具备厚底特征,但必须通过系带穿脱,违背了“一脚蹬”这一关键约束条件,属于违约负例。\n7. 该商品为系带款板鞋,虽然具备厚底特征,但必须通过系带穿脱,违背了“一脚蹬”这一关键约束条件,属于违约负例。\n8. 该商品为系带款板鞋,虽然具备厚底特征,但必须通过系带穿脱,违背了“一脚蹬”这一关键约束条件,属于违约负例。\n9. 该商品为系带款板鞋,虽然具备厚底特征,但必须通过系带穿脱,违背了“一脚蹬”这一关键约束条件,属于违约负例。\n\n【横向对比】\n候选2、3、4均为无鞋带设计,直接满足“一脚蹬”的核心需求,且具备厚底特征,因此判定为绝对正例(0)。候选0、1虽为系带款,但配有提环,具备“一脚蹬”的穿脱便利性,且为厚底板鞋,属于相对正例(1)。候选5、6、7、8、9均为系带款板鞋,虽然品类和厚底特征正确,但穿脱方式必须系带,违背了“一脚蹬”的关键约束,因此判定为违约负例(2)。", "cot_answer": "1100022222"}
|
| 9 |
+
{"query_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/image/O1CN01LdzQOe1FT626mWkpJ_!!6000000000487-0-tb_se_box.jpg", "instruction": "美的碗伴洗碗液 550g", "intent": "属性筛选", "docs": [{"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/image/O1CN01rWL7fR2HOBA9ZdS3Q_!!201749140.png", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/crop_images/O1CN01rWL7fR2HOBA9ZdS3Q_!!201749140_bbox_1119_613_1870_2377.png", "doc_title": "美的碗伴自动投放洗碗机专用洗碗液补充液550g", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/image/O1CN01Liey1r1a1iGUOXB9X~crop,0,227,790,790~_!!2984223270.png", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/crop_images/O1CN01Liey1r1a1iGUOXB9X~crop,0,227,790,790~_!!2984223270_bbox_491_60_633_393.png", "doc_title": "美的出品碗伴自动投放洗碗机洗碗液适配COLMO全品牌洗油污抗水垢", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/image/O1CN015OUsQ41Pw5EDGbCoP_!!3414381904.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/crop_images/O1CN015OUsQ41Pw5EDGbCoP_!!3414381904_bbox_550_252_764_754.jpg", "doc_title": "碗伴自动投放洗碗液补充液洗碗机用适配美的COLMO智能投放清洗剂", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/padding_images/image/O1CN01Liey1r1a1iGUOXB9X~crop,0,227,790,790~_!!2984223270.png", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/crop_images/O1CN01Liey1r1a1iGUOXB9X~crop,0,227,790,790~_!!2984223270_bbox_491_60_633_393.png", "doc_title": "美的出品碗伴自动投放洗碗机洗碗液适配COLMO全品牌洗油污抗水垢", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/padding_images/image/O1CN015OUsQ41Pw5EDGbCoP_!!3414381904.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/crop_images/O1CN015OUsQ41Pw5EDGbCoP_!!3414381904_bbox_550_252_764_754.jpg", "doc_title": "碗伴自动投放洗碗液补充液洗碗机用适配美的COLMO智能投放清洗剂", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/image/O1CN01q9QCIx2E5uRXqh1N6_!!2211027998694.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/crop_images/O1CN01q9QCIx2E5uRXqh1N6_!!2211027998694_bbox_255_62_521_670.jpg", "doc_title": "碗伴自动投放洗碗机洗碗液补充液全品牌适配美的S66pro/S65pro", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/image/O1CN01NpNRF71EmxT0Sp8FT_!!4611686018427379931-0-item_pic.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/crop_images/O1CN01NpNRF71EmxT0Sp8FT_!!4611686018427379931-0-item_pic_bbox_433_71_661_625.jpg", "doc_title": "COLMO洗碗机专用洗碗液清洗剂智能投放漂洗剂小鲜瓶G06/G35/G52", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/image/O1CN011IJB7T1a1iGdE68rU_!!2984223270.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/crop_images/O1CN011IJB7T1a1iGdE68rU_!!2984223270_bbox_140_39_1304_1394.jpg", "doc_title": "美的碗伴洗碗粉洗碗机专用漂洗剂洗碗盐去污除菌亮碟凝珠厨房湿巾", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/padding_images/image/O1CN01q9QCIx2E5uRXqh1N6_!!2211027998694.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/crop_images/O1CN01q9QCIx2E5uRXqh1N6_!!2211027998694_bbox_255_62_521_670.jpg", "doc_title": "碗伴自动投放洗碗机洗碗液补充液全品牌适配美的S66pro/S65pro", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/padding_images/image/O1CN01NpNRF71EmxT0Sp8FT_!!4611686018427379931-0-item_pic.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/crop_images/O1CN01NpNRF71EmxT0Sp8FT_!!4611686018427379931-0-item_pic_bbox_433_71_661_625.jpg", "doc_title": "COLMO洗碗机专用洗碗液清洗剂智能投放漂洗剂小鲜瓶G06/G35/G52", "label": 2}], "query_crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/v2/all_datas/02/query_crop_images/O1CN01LdzQOe1FT626mWkpJ_!!6000000000487-0-tb_se_box_bbox_265_259_601_918.jpg", "sample_idx": 5, "cot": "【逐项分析】\n0. 该商品为美的碗伴洗碗液,瓶身明确标注净含量550g,与用户搜索意图中的品牌、品类及规格完全一致,属于绝对正例。\n1. 该商品为美的出品碗伴洗碗液,瓶身标注净含量550g,且明确适配洗碗机,完全符合用户搜索的特定规格和品牌要求,属于绝对正例。\n2. 该商品为碗伴洗碗液,瓶身标注净含量550g,且标题提及适配美的洗碗机,与用户搜索意图中的关键条件完全吻合,属于绝对正例。\n3. 该商品为美的出品碗伴洗碗液,瓶身标注净含量550g,与用户搜索意图中的品牌、品类及规格完全一致,属于绝对正例。\n4. 该商品为碗伴洗碗液,瓶身标注净含量550g,且标题提及适配美的洗碗机,与用户搜索意图中的关键条件完全吻合��属于绝对正例。\n5. 该商品为碗伴洗碗液,品类正确,但瓶身标注净含量为1.2kg,与用户搜索的550g规格不符,属于违约负例。\n6. 该商品为碗伴洗碗液,品类正确,但瓶身标注净含量为1.2kg,与用户搜索的550g规格不符,属于违约负例。\n7. 该商品为美的碗伴洗碗凝珠,虽然品牌正确,但品类为凝珠而非用户搜索的洗碗液,且规格为650g,属于违约负例。\n8. 该商品为碗伴洗碗液,品类正确,但瓶身标注净含量为1.2kg,与用户搜索的550g规格不符,属于违约负例。\n9. 该商品为碗伴洗碗液,品类正确,但瓶身标注净含量为1.2kg,与用户搜索的550g规格不符,属于违约负例。\n\n【横向对比】\n候选0-4均为550g规格的碗伴洗碗液,完全满足用户搜索的品牌、品类和规格要求,因此判定为绝对正例。候选5-6、8-9虽然也是碗伴洗碗液,但规格均为1.2kg,违背了用户搜索的550g这一关键条件,故判定为违约负例。候选7虽然品牌正确,但品类为洗碗凝珠而非洗碗液,且规格也不符,同样判定为违约负例。", "cot_answer": "0000022222"}
|
| 10 |
+
{"query_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/image/O1CN01Cv17vS26rzC4zchUY_!!6000000007716-0-tb_se_box.jpg", "instruction": "搭配宋制汉服", "intent": "搭配", "docs": [{"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/image/O1CN01ZS6MZd1gNah08Jf5J_!!2698574130.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/crop_images/O1CN01ZS6MZd1gNah08Jf5J_!!2698574130_bbox_1_827_2410_3197.jpg", "doc_title": "花朝记|藕花深处|宋制汉服百迭裙飞机袖长干寺背心荷花刺绣传统", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/image/O1CN01AULm871frzVG0ViYN_!!2870454061.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/crop_images/O1CN01AULm871frzVG0ViYN_!!2870454061_bbox_741_3_2503_3402.jpg", "doc_title": "序春时 [荔枝]宋制对襟短衫宋背心吊带褶裙日常女装汉服", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/image/O1CN01nFaapa1VXFObyyZcp_!!3820422662.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/crop_images/O1CN01nFaapa1VXFObyyZcp_!!3820422662_bbox_112_0_893_1022.jpg", "doc_title": "【锦呈浮光】浮光绘夏 宋背心 破肩 天丝暗纹绣花改良汉服日常夏", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/image/O1CN01xw0oiU1zUti5cqvZO_!!2201514156718.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/crop_images/O1CN01xw0oiU1zUti5cqvZO_!!2201514156718_bbox_104_138_573_842.jpg", "doc_title": "花朝记|藕花深处|宋制汉服百迭裙飞机袖长干寺背心荷花刺绣传统", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/image/O1CN01V12mxh1EfdD44Zbqy_!!767690379.png", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/crop_images/O1CN01V12mxh1EfdD44Zbqy_!!767690379_bbox_50_243_628_1192.png", "doc_title": "出租正版序春时时至荔秋宋制汉服对襟短衫褶裙女伴娘服摄影活动", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/image/O1CN01cBWGHD2KHFuT5vrcI_!!2049979531.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/crop_images/O1CN01cBWGHD2KHFuT5vrcI_!!2049979531_bbox_153_21_1699_2344.jpg", "doc_title": "轻云居原创汉服女【国色】唐风大袖衫齐胸一片式八破襦裙复原古典", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/image/O1CN01kDBiYg2KHFtmTZ7aE_!!2049979531.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/crop_images/O1CN01kDBiYg2KHFtmTZ7aE_!!2049979531_bbox_235_154_645_796.jpg", "doc_title": "轻云居原创汉服女【国色】唐风大袖衫齐胸一片式八破襦裙复原古典", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/image/O1CN01o4CMQz1o6f9x3Qtmh_!!2213267155176.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/crop_images/O1CN01o4CMQz1o6f9x3Qtmh_!!2213267155176_bbox_592_915_3149_4978.jpg", "doc_title": "醉时阁【春序信使】原创汉服战国袍重工刺绣仙气优雅质感显瘦古风", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/padding_images/image/O1CN01kDBiYg2KHFtmTZ7aE_!!2049979531.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/crop_images/O1CN01kDBiYg2KHFtmTZ7aE_!!2049979531_bbox_235_154_645_796.jpg", "doc_title": "轻云居原创汉服女【国色】唐风大袖衫齐胸一片式八破襦裙复原古典", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/padding_images/image/O1CN01cBWGHD2KHFuT5vrcI_!!2049979531.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/crop_images/O1CN01cBWGHD2KHFuT5vrcI_!!2049979531_bbox_153_21_1699_2344.jpg", "doc_title": "轻云居原创汉服女【国色】唐风大袖衫齐胸一片式八破襦裙复原古典", "label": 2}], "query_crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/v2/all_datas/01/query_crop_images/O1CN01Cv17vS26rzC4zchUY_!!6000000007716-0-tb_se_box_bbox_0_557_448_939.jpg", "sample_idx": 9, "cot": "【逐项分析】\n0. 该商品标题明确标注“宋制汉服”,且图像展示了典型的宋制长干寺背心与百迭裙搭配,款式与用户意图完全一致,属于精准匹配。\n1. 商品标题包含“宋制对襟短衫”、“宋背心”等关键词,图像展示了宋制汉服常见的对襟与背心叠穿结构,符合用户寻找宋制汉服搭配的需求。\n2. 标题注明“宋背心”、“改良汉服”,图像展示了宋制风格的破肩背心款式,虽然为改良设计,但核心品类与风格均符合宋制汉服的特征。\n3. 该商品与候选0为同款“花朝记”宋制汉服套装,包含长干寺背心与百迭裙,完全满足用户对于宋制汉服搭配的需求。\n4. 标题明确为“宋制汉服对襟短衫褶裙”,图像展示了宋制风格的对襟上衣与褶裙组合,属于标准的宋制汉服品类,符合用户意图。\n5. 标题明确标注“唐风大袖衫”、“齐胸一片式”,图像展示了典型的唐代齐胸襦裙与大袖衫,属于唐制汉服,与用户寻找宋制汉服的意图在形制上存在明显冲突。\n6. 该商品与候选5为同款唐风汉服,图像展示了齐胸襦裙与琵琶道具,属于唐制风格,违背了用户寻找宋制汉服的关键条件。\n7. 标题标注“战国袍”,图像展示了战国袍特有的交领大袖与系带结构,属于战国风格汉服,与宋制汉服的形制完全不同,属于品类错误。\n8. 该商品与候选5、6为同款唐风汉服,属于唐制齐胸襦裙,与用户寻找宋制汉服的意图不符,属于形制违背。\n9. 该商品与候选5、6、8为同款唐风汉服,属于唐制齐胸襦裙,与用户寻找宋制汉服的意图不符,属于形制违背。\n\n【横向对比】\n候选0-4均为宋制汉服,涵盖了长干寺背心、对襟短衫、百迭裙等宋制典型元素,完全契合用户意图,属于绝对正例。\n候选5-9则分别属于唐制(齐胸襦裙)和战国制(战国袍),虽然都是汉服,但形制与宋制汉服差异巨大,一眼即可看出违背了用户“宋制”的关键约束,属于违约负例。\n区分点在于形制:宋制多为对襟、背心、百迭裙,而唐制多为齐胸、大袖,战国袍则有独特的系带结构。", "cot_answer": "0000022222"}
|
code/rerank_ins/cot_data/train_cot.test.jsonl.failed
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{"query_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/image/O1CN01avvCAT1lXHM6nG2zB_!!6000000004828-0-tb_se_box.jpg", "instruction": "厚底一脚蹬板鞋", "intent": "相似", "docs": [{"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/image/O1CN01RpcPkt2Det907k9UL_!!1642218635.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/crop_images/O1CN01RpcPkt2Det907k9UL_!!1642218635_bbox_94_1497_692_1805.jpg", "doc_title": "公牛世家鞋子男2026新款潮流百搭休闲板鞋男士轻便厚底时尚运动鞋", "label": 1}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/image/O1CN01fWc3cy1c6PZOAUUPt~crop,0,250,1500,1500~_!!2200728763551.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/crop_images/O1CN01fWc3cy1c6PZOAUUPt~crop,0,250,1500,1500~_!!2200728763551_bbox_225_115_756_729.jpg", "doc_title": "冰丝布面!防滑橡胶底!夏季外贸男士轻便舒适透气低帮休闲帆布鞋", "label": 1}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/image/O1CN010n6uwP2EAwsu7p4hp_!!2218571438705-2-qnaigc.png", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/crop_images/O1CN010n6uwP2EAwsu7p4hp_!!2218571438705-2-qnaigc_bbox_188_332_613_1234.png", "doc_title": "江南袋鼠休闲皮鞋男2026夏季新款一脚蹬小白鞋舒适透气英伦爸爸鞋", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/image/O1CN01oLpqcd1WkMQjJKK1B_!!1583002826.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/crop_images/O1CN01oLpqcd1WkMQjJKK1B_!!1583002826_bbox_258_196_582_728.jpg", "doc_title": "2026年新款高品质冰丝布透气圆头休闲板鞋一脚蹬防滑上线百搭女鞋", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/image/O1CN01WojKdR1qefonl4KJ7_!!2222114025521-0-qnaigc.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/crop_images/O1CN01WojKdR1qefonl4KJ7_!!2222114025521-0-qnaigc_bbox_25_62_422_787.jpg", "doc_title": "公牛世家男鞋新款春季时尚潮流一脚蹬休闲皮鞋男士百搭通勤低帮鞋", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/image/O1CN01I7e3VZ2D652UONlNH_!!2218980038559.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/crop_images/O1CN01I7e3VZ2D652UONlNH_!!2218980038559_bbox_147_269_652_515.jpg", "doc_title": "百丽男士户外休闲工装板鞋男2025秋季新款牛皮革厚底增高A2137CM5", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/image/O1CN01jx9HGM2EAwtzNhLNB_!!2218571438705.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/crop_images/O1CN01jx9HGM2EAwtzNhLNB_!!2218571438705_bbox_19_433_731_889.jpg", "doc_title": "江南袋鼠透气休闲板鞋2026夏季新款软底运动舒适平底镂空爸爸皮鞋", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/image/O1CN01Kb2ook1yGsAX5xALG_!!467426552.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/crop_images/O1CN01Kb2ook1yGsAX5xALG_!!467426552_bbox_200_450_1241_1083.jpg", "doc_title": "卓诗尼厚底工装鞋男2026夏季新款复古透气运动增高休闲户外板鞋子", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/image/O1CN01GDq6vn2BaeEJ2SRnL_!!708668355.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/crop_images/O1CN01GDq6vn2BaeEJ2SRnL_!!708668355_bbox_132_337_868_780.jpg", "doc_title": "森达工装鞋男款2026春季新款商场同款户外徒步休闲皮鞋K4E03AM6", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/image/O1CN01yoJx7C1VGlBLxzlrD_!!2831472626.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260630/crop_images/O1CN01yoJx7C1VGlBLxzlrD_!!2831472626_bbox_324_473_2489_2385.jpg", "doc_title": "老北京布鞋男休闲鞋板鞋休闲运动鞋平底鞋系带厚底轻质布鞋商务鞋", "label": 2}], "query_crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/v2/all_datas/01/query_crop_images/O1CN01avvCAT1lXHM6nG2zB_!!6000000004828-0-tb_se_box_bbox_65_203_342_853.jpg", "sample_idx": 1, "gt_answer": "1100022222", "raw_output": "【逐项分析】\n0. 该商品为公牛世家男士休闲板鞋,图像显示鞋底厚实,符合“厚底”特征,款式为系带休闲鞋,虽标题未直接提及“一脚蹬”,但作为板鞋品类,满足厚底和板鞋的核心需求,判定为相对正例。\n1. 该商品为男士低帮休闲帆布鞋,图像显示鞋底较薄,不符合“厚底”的显著特征,但属于休闲鞋/板鞋品类,与用户搜索的品类相关,判定为相对正例。\n2. 该商品标题明确标注“一脚蹬小白鞋���,图像显示无鞋带设计,符合“一脚蹬”特征,且鞋底具有一定厚度,完全满足用户意图,判定为绝对正例。\n3. 该商品标题明确标注“一脚蹬”和“休闲板鞋”,图像显示后跟有拉链设计(符合一脚蹬特征),且鞋底厚实,完全符合用户搜索的关键条件,判定为绝对正例。\n4. 该商品标题明确标注“一脚蹬”,图像显示鞋帮较高且有拉链设计,符合一脚蹬特征,鞋底较厚,属于休闲皮鞋/板鞋品类,完全满足用户意图,判定为绝对正例。\n5. 该商品为百丽男士工装板鞋,虽然符合“厚底”特征,但图像显示为系带款,不符合“一脚蹬”的关键约束条件,属于品类正确但违背关键条件的违约负例。\n6. 该商品为江南袋鼠休闲板鞋,属于板鞋品类,但图像显示为系带款,不符合“一脚蹬”的关键约束,属于违约负例。\n7. 该商品为卓诗尼厚底工装板鞋,虽然符合“厚底”和“板鞋”特征,但图像显示为系带款,违背了“一脚蹬”的关键意图,属于违约负例。\n8. 该商品为森达工装鞋,属于皮鞋/休闲鞋品类,但图像显示为系带款,不符合“一脚蹬”的关键约束,属于违约负例。\n9. 该商品为老北京布鞋,属于板鞋/休闲鞋品类,但图像显示为系带款,不符合“一脚蹬”的关键约束,属于违约负例。\n\n【横向对比】\n候选2、3、4均明确包含“一脚蹬”设计(无鞋带或拉链)且满足“厚底”特征,与用户意图完全匹配,故为绝对正例(0)。候选0和1属于板鞋或休闲鞋品类,其中0符合厚底但非典型一脚蹬,1鞋底偏薄,两者作为品类相关的商品归为相对正例(1)。候选5、6、7、8、9虽然均为厚底板鞋或休闲鞋,品类正确,但均为系带款式,一眼看出违背了“一脚蹬”这一核心约束,故归为违约负例(2)。\n\n最终答案:1100022222"}
|
| 2 |
+
{"query_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/image/O1CN01LdzQOe1FT626mWkpJ_!!6000000000487-0-tb_se_box.jpg", "instruction": "美的碗伴洗碗液 550g", "intent": "属性筛选", "docs": [{"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/image/O1CN01rWL7fR2HOBA9ZdS3Q_!!201749140.png", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/crop_images/O1CN01rWL7fR2HOBA9ZdS3Q_!!201749140_bbox_1119_613_1870_2377.png", "doc_title": "美的碗伴自动投放洗碗机专用洗碗液补充液550g", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/image/O1CN01Liey1r1a1iGUOXB9X~crop,0,227,790,790~_!!2984223270.png", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/crop_images/O1CN01Liey1r1a1iGUOXB9X~crop,0,227,790,790~_!!2984223270_bbox_491_60_633_393.png", "doc_title": "美的出品碗伴自动投放洗碗机洗碗液适配COLMO全品牌洗油污抗水垢", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/image/O1CN015OUsQ41Pw5EDGbCoP_!!3414381904.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/crop_images/O1CN015OUsQ41Pw5EDGbCoP_!!3414381904_bbox_550_252_764_754.jpg", "doc_title": "碗伴自动投放洗碗液补充液洗碗机用适配美的COLMO智能投放清洗剂", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/padding_images/image/O1CN01Liey1r1a1iGUOXB9X~crop,0,227,790,790~_!!2984223270.png", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/crop_images/O1CN01Liey1r1a1iGUOXB9X~crop,0,227,790,790~_!!2984223270_bbox_491_60_633_393.png", "doc_title": "美的出品碗伴自动投放洗碗机洗碗液适配COLMO全品牌洗油污抗水垢", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/padding_images/image/O1CN015OUsQ41Pw5EDGbCoP_!!3414381904.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/crop_images/O1CN015OUsQ41Pw5EDGbCoP_!!3414381904_bbox_550_252_764_754.jpg", "doc_title": "碗伴自动投放洗碗液补充液洗碗机用适配美的COLMO智能投放清洗剂", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/image/O1CN01q9QCIx2E5uRXqh1N6_!!2211027998694.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/crop_images/O1CN01q9QCIx2E5uRXqh1N6_!!2211027998694_bbox_255_62_521_670.jpg", "doc_title": "碗伴自动投放洗碗机洗碗液补充液全品牌适配美的S66pro/S65pro", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/image/O1CN01NpNRF71EmxT0Sp8FT_!!4611686018427379931-0-item_pic.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/crop_images/O1CN01NpNRF71EmxT0Sp8FT_!!4611686018427379931-0-item_pic_bbox_433_71_661_625.jpg", "doc_title": "COLMO洗碗机专用洗碗液清洗剂智能投放���洗剂小鲜瓶G06/G35/G52", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/image/O1CN011IJB7T1a1iGdE68rU_!!2984223270.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/crop_images/O1CN011IJB7T1a1iGdE68rU_!!2984223270_bbox_140_39_1304_1394.jpg", "doc_title": "美的碗伴洗碗粉洗碗机专用漂洗剂洗碗盐去污除菌亮碟凝珠厨房湿巾", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/padding_images/image/O1CN01q9QCIx2E5uRXqh1N6_!!2211027998694.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/crop_images/O1CN01q9QCIx2E5uRXqh1N6_!!2211027998694_bbox_255_62_521_670.jpg", "doc_title": "碗伴自动投放洗碗机洗碗液补充液全品牌适配美的S66pro/S65pro", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/padding_images/image/O1CN01NpNRF71EmxT0Sp8FT_!!4611686018427379931-0-item_pic.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260624/crop_images/O1CN01NpNRF71EmxT0Sp8FT_!!4611686018427379931-0-item_pic_bbox_433_71_661_625.jpg", "doc_title": "COLMO洗碗机专用洗碗液清洗剂智能投放漂洗剂小鲜瓶G06/G35/G52", "label": 2}], "query_crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/v2/all_datas/02/query_crop_images/O1CN01LdzQOe1FT626mWkpJ_!!6000000000487-0-tb_se_box_bbox_265_259_601_918.jpg", "sample_idx": 5, "gt_answer": "0000022222", "raw_output": "【逐项分析】\n0. 该商品为美的碗伴洗碗液,标题明确标注550g,图像显示为黄色瓶身,与用户查询的品牌、品类及规格完全一致,属于绝对正例。\n1. 该商品是美的出品的碗伴洗碗液,图像显示净含量为550g,虽然是另一款包装设计的液体产品,但品牌、品类和规格均精准匹配,属于绝对正例。\n2. 该商品为美的碗伴洗碗液,瓶身标注550g,与查询意图中的品牌、产品和规格完全吻合,属于绝对正例。\n3. 该商品与候选1相同,是美的出品的碗伴洗碗液,图像清晰显示550g规格,完全满足用户需求,属于绝对正例。\n4. 该商品与候选2相同,为美的碗伴洗碗液,规格550g,与查询意图完全匹配,属于绝对正例。\n5. 该商品是美的碗伴品牌的洗碗液,品类正确,但图像显示净含量为1.2kg,与查询要求的550g规格严重不符,属于违约负例。\n6. 该商品是美的碗伴品牌的洗碗液,品类正确,但图像显示净含量为1.2kg,违背了用户指定的550g规格,属于违约负例。\n7. 该商品是美的碗伴品牌的洗碗机专用产品,但图像和标题明确显示为“洗碗粉/凝珠”,属于固体制剂,与查询所需的“洗碗液”品类不符,属于违约负例。\n8. 该商品与候选5相同,是美的碗伴洗碗液,但规格为1.2kg,与查询的550g不符,属于违约负例。\n9. 该商品与候选6相同,是美的碗伴洗碗液,但规格为1.2kg,与查询的550g不符,属于违约负例。\n\n【横向对比】\n前五个候选商品(0-4)均为550g规格的美的碗伴洗碗液,完全覆盖了用户查询的所有关键特征,因此判定为绝对正例。后五个候选商品(5-9)虽然品牌均为美的碗伴,但存在明显的规格或品类偏差:其中5、6、8、9为1.2kg大瓶装,规格不符;7为洗碗凝珠,剂型不符。这些商品在核心品类上属于“对的品类但违反约束”,因此统一判定为违约负例,与绝对正例形成鲜明对比。\n\n最终答案:0000022222"}
|
| 3 |
+
{"query_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/image/O1CN01Cv17vS26rzC4zchUY_!!6000000007716-0-tb_se_box.jpg", "instruction": "搭配宋制汉服", "intent": "搭配", "docs": [{"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/image/O1CN01ZS6MZd1gNah08Jf5J_!!2698574130.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/crop_images/O1CN01ZS6MZd1gNah08Jf5J_!!2698574130_bbox_1_827_2410_3197.jpg", "doc_title": "花朝记|藕花深处|宋制汉服百迭裙飞机袖长干寺背心荷花刺绣传统", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/image/O1CN01AULm871frzVG0ViYN_!!2870454061.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/crop_images/O1CN01AULm871frzVG0ViYN_!!2870454061_bbox_741_3_2503_3402.jpg", "doc_title": "序春时 [荔枝]宋制对襟短衫宋背心吊带褶裙日常女装汉服", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/image/O1CN01nFaapa1VXFObyyZcp_!!3820422662.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/crop_images/O1CN01nFaapa1VXFObyyZcp_!!3820422662_bbox_112_0_893_1022.jpg", "doc_title": "【锦呈浮光】浮光绘夏 宋背心 破肩 天丝暗纹绣花改良汉服日常夏", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/image/O1CN01xw0oiU1zUti5cqvZO_!!2201514156718.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/crop_images/O1CN01xw0oiU1zUti5cqvZO_!!2201514156718_bbox_104_138_573_842.jpg", "doc_title": "花朝记|藕花深处|宋制汉服百迭裙飞机袖长干寺背心荷花刺绣传统", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/image/O1CN01V12mxh1EfdD44Zbqy_!!767690379.png", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/crop_images/O1CN01V12mxh1EfdD44Zbqy_!!767690379_bbox_50_243_628_1192.png", "doc_title": "出租正版序春时时至荔秋宋制汉服对襟短衫褶裙女伴娘服摄影活动", "label": 0}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/image/O1CN01cBWGHD2KHFuT5vrcI_!!2049979531.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/crop_images/O1CN01cBWGHD2KHFuT5vrcI_!!2049979531_bbox_153_21_1699_2344.jpg", "doc_title": "轻云居原创汉服女【国色】唐风大袖衫齐胸一片式八破襦裙复原古典", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/image/O1CN01kDBiYg2KHFtmTZ7aE_!!2049979531.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/crop_images/O1CN01kDBiYg2KHFtmTZ7aE_!!2049979531_bbox_235_154_645_796.jpg", "doc_title": "轻云居原创汉服女【国色】唐风大袖衫齐胸一片式八破襦裙复原古典", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/image/O1CN01o4CMQz1o6f9x3Qtmh_!!2213267155176.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/crop_images/O1CN01o4CMQz1o6f9x3Qtmh_!!2213267155176_bbox_592_915_3149_4978.jpg", "doc_title": "醉时阁【春序信使】原创汉服战国袍重工刺绣仙气优雅质感显瘦古风", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/padding_images/image/O1CN01kDBiYg2KHFtmTZ7aE_!!2049979531.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/crop_images/O1CN01kDBiYg2KHFtmTZ7aE_!!2049979531_bbox_235_154_645_796.jpg", "doc_title": "轻云居原创汉服女【国色】唐风大袖衫齐胸一片式八破襦裙复原古典", "label": 2}, {"doc_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/padding_images/image/O1CN01cBWGHD2KHFuT5vrcI_!!2049979531.jpg", "crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/raw_data/20260701/crop_images/O1CN01cBWGHD2KHFuT5vrcI_!!2049979531_bbox_153_21_1699_2344.jpg", "doc_title": "轻云居原创汉服女【国色】唐风大袖衫齐胸一片式八破襦裙复原古典", "label": 2}], "query_crop_image": "/data/oss_bucket_0/siku/Last_reranker/dataset/v2/all_datas/01/query_crop_images/O1CN01Cv17vS26rzC4zchUY_!!6000000007716-0-tb_se_box_bbox_0_557_448_939.jpg", "sample_idx": 9, "gt_answer": "0000022222", "raw_output": "【逐项分析】\n0. 该商品为粉色荷花刺绣宋制汉服套装,包含长干寺背心和百迭裙,款式与查询图像中的宋制汉服风格完全一致,属于绝对正例。\n1. 该商品为粉色荔枝纹样宋制对襟短衫搭配褶裙,整体色调与款式均符合宋制汉服特征,能够很好地满足搭配意图,属于绝对正例。\n2. 该商品为黄色柿子刺绣的宋制破肩背心,虽然颜色与查询图不同,但品类和形制(宋背心)完全契合用户寻找宋制汉服的需求,属于绝对正例。\n3. 该商品与候选0款式一致,为粉色荷花刺绣宋制汉服,包含飞机袖和长干寺背心,完全符合宋制搭配需求,属于绝对正例。\n4. 该商品为粉色荔枝纹样宋制汉服套装,与候选1类似,包含对襟短衫和褶裙,款式和风格均精准匹配,属于绝对正例。\n5. 该商品标题明确标注为“唐风大袖衫”和“齐胸襦裙”,属于唐代汉服形制,与用户查询的宋制汉服品类不符,一眼可辨,属于违约负例。\n6. 该商品同样为唐风大袖衫搭配齐胸襦裙,虽然是汉服但形制为唐制,违背了宋制的特定要求,属于违约负例。\n7. 该商品为紫色战国袍,属于战国时期的汉服形制,与宋制汉服在款式上差异巨大,违背了特定朝代的需求,属于违约负例。\n8. 该商品与候选5、6同款,为唐风大袖衫配齐胸襦裙,形制错误,属于违约负例。\n9. 该商品与候选5同款,为唐风大袖衫配齐胸襦裙,形制错误,属于违约负例。\n\n【横向对比】\n候选0、1、2、3、4均为宋制汉服(包含背心、对襟衫、百迭裙等典型宋制元素),完全符合用户意图,因此均为绝对正例。候选5、6、8、9均为唐制汉服(大袖衫、齐胸襦裙),候选7为战国袍,它们虽然都是汉服,但形制与用户指定的“宋制”完全不符,属于“对的品类(汉服)但违反约束(朝代/形制)”的违约负例。\n\n最终��案:0000022222"}
|
code/rerank_ins/cot_gen/__pycache__/gen_cot_data.cpython-313.pyc
ADDED
|
Binary file (19 kB). View file
|
|
|
code/rerank_ins/cot_gen/cot_prompt.jinja
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<sys_start>
|
| 2 |
+
## 角色
|
| 3 |
+
你是电商图搜相关性判定专家。根据用户的搜索意图(instruction),判断每个候选商品与查询图像的匹配程度,标签为 0–3。
|
| 4 |
+
|
| 5 |
+
## 标签定义
|
| 6 |
+
- **0 绝对正例**:品类正确,完全满足用户意图的关键条件
|
| 7 |
+
- **1 相对正例**:品类正确,基本满足或边界模糊、不一眼违背
|
| 8 |
+
- **2 违约负例**:品类正确或很像,但一眼看出违背关键条件(搭配意图下返回图的同品类也算违约负)
|
| 9 |
+
- **3 弱负例**:品类不对,与用户需求无关
|
| 10 |
+
|
| 11 |
+
> 违约负 ≠ 弱负:违约负是"对的品类但违反约束",弱负是"品类就不对"。
|
| 12 |
+
|
| 13 |
+
## 任务
|
| 14 |
+
你会得到查询图像、用户意图、候选商品(图像+标题),以及一组仅供参考的相关性标签。
|
| 15 |
+
请你写出一段高质量的推理过程,展示如何一步步推导出这些标签。要求:
|
| 16 |
+
1. 【逐项分析】先按编号顺序逐个分析每个候选商品:描述其图像与标题的关键信息,与查询图像和用户意图对照,推理出它的标签。
|
| 17 |
+
2. 【横向对比】再将各候选放在一起对比:说明为什么某些候选比另一些更相关、相同标签的候选有什么共性、相邻标签(如 0 与 1、2 与 3)之间的区分点在哪里。
|
| 18 |
+
3. 推理过程使用简体中文,内容具体、有信息量,避免空话套话;每个候选的分析控制在 1-3 句话。
|
| 19 |
+
|
| 20 |
+
## 硬性约束(违反即无效)
|
| 21 |
+
1. 全文**禁止出现**"参考标签、参考答案、标注员、给定答案、给定标签、已知标签、正确答案、ground truth"等任何字眼,也不能用"标签是 X 所以""根据标签 X"这类表述。参考标签只用于校准你的判断尺度,绝不能作为推理依据被写出来。
|
| 22 |
+
2. 输出必须是**一份已经想清楚的终稿**:直接给出肯定的判断和依据。禁止出现"等等""让我们重新审视""难道是""有没有可能""我必须强行解释""这看起来很奇怪"等自我怀疑、反复推翻、逐条复盘标签序列的内容。
|
| 23 |
+
3. 若某个候选的标签乍看反直觉,**不要质疑它,也不要纠结**:直接从查询图像、候选图像、标题、用户意图中找出一条能支撑它的具体依据(如成分、材质、颜色、款式、品类归属、场景),用一句话肯定地陈述出来。
|
| 24 |
+
4. 每个候选最终推出的标签必须与所给标签完全一致。
|
| 25 |
+
|
| 26 |
+
## 输出格式
|
| 27 |
+
严格按以下格式输出,不要输出任何额外内容:
|
| 28 |
+
|
| 29 |
+
【逐项分析】
|
| 30 |
+
0. <对候选0的分析与判定>
|
| 31 |
+
1. <对候选1的分析与判定>
|
| 32 |
+
...(覆盖所有候选)
|
| 33 |
+
【横向对比】
|
| 34 |
+
<对比总结>
|
| 35 |
+
最终答案:<按候选顺序拼接的标签数字串,如 0123012301>
|
| 36 |
+
<sys_end>
|
| 37 |
+
|
| 38 |
+
<user_start>
|
| 39 |
+
查询图像:<image>
|
| 40 |
+
用户意图:<instruction>
|
| 41 |
+
候选商品:
|
| 42 |
+
<doc_list>
|
| 43 |
+
每个候选商品的相关性标签(仅供参考,不得在推理中提及):
|
| 44 |
+
<gt_labels>
|
| 45 |
+
请按要求生成推理过程和最终答案。
|
| 46 |
+
<user_end>
|
code/rerank_ins/cot_gen/gen_cot_data.py
ADDED
|
@@ -0,0 +1,291 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""用本地大 Qwen-VL 模型(vLLM 离线批推)为 listwise reranker 训练集构造 CoT 数据。
|
| 2 |
+
|
| 3 |
+
输入: 训练集 jsonl(格式同 train/train_reranker_qwen3vl.py 所用数据)
|
| 4 |
+
输出: jsonl,每行 = 原样本 + 实际使用的 docs 顺序 + cot(思考过程)+ cot_answer(标签数字串)
|
| 5 |
+
|
| 6 |
+
本地用法见 cot_gen/run_gen_cot.sh
|
| 7 |
+
Nebula 用法见 scripts/submit/submit_cot_gen.sh:
|
| 8 |
+
nebula 按每卡一进程拉起本脚本,WORLD_SIZE > 1 时自动按 rank 分片,
|
| 9 |
+
每个进程用 tp=1 独占一张卡,各自写 <output>.shard<rank>-<world>.jsonl,跑完再合并。
|
| 10 |
+
"""
|
| 11 |
+
import os
|
| 12 |
+
|
| 13 |
+
os.environ.setdefault("VLLM_WORKER_MULTIPROC_METHOD", "spawn")
|
| 14 |
+
|
| 15 |
+
import argparse
|
| 16 |
+
import glob
|
| 17 |
+
import json
|
| 18 |
+
import random
|
| 19 |
+
import re
|
| 20 |
+
import sys
|
| 21 |
+
|
| 22 |
+
current_file_path = os.path.dirname(os.path.abspath(__file__))
|
| 23 |
+
sys.path.append(os.path.join(current_file_path, "../"))
|
| 24 |
+
|
| 25 |
+
|
| 26 |
+
def get_dist_env():
|
| 27 |
+
rank = int(os.environ.get("RANK", 0))
|
| 28 |
+
world_size = int(os.environ.get("WORLD_SIZE", 1))
|
| 29 |
+
local_rank = int(os.environ.get("LOCAL_RANK", rank))
|
| 30 |
+
return rank, world_size, local_rank
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def bind_single_gpu(local_rank: int):
|
| 34 |
+
"""分片模式下每个进程只用一张卡;调用点必须早于 torch/vllm 的 import。"""
|
| 35 |
+
visible = os.environ.get("CUDA_VISIBLE_DEVICES")
|
| 36 |
+
if visible:
|
| 37 |
+
gpu_ids = [x for x in visible.split(",") if x]
|
| 38 |
+
else:
|
| 39 |
+
gpu_ids = [str(i) for i in sorted(int(re.findall(r"\d+$", p)[0]) for p in glob.glob("/dev/nvidia[0-9]*"))]
|
| 40 |
+
if len(gpu_ids) > 1:
|
| 41 |
+
os.environ["CUDA_VISIBLE_DEVICES"] = gpu_ids[local_rank % len(gpu_ids)]
|
| 42 |
+
print(f"[rank {local_rank}] CUDA_VISIBLE_DEVICES={os.environ.get('CUDA_VISIBLE_DEVICES')}")
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
def extract_prompt(full_prompt: str, pattern: str):
|
| 46 |
+
match = re.search(pattern, full_prompt, re.DOTALL)
|
| 47 |
+
return match.group(1).strip() if match else None
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def load_data(data_path: str):
|
| 51 |
+
if data_path.endswith(".json"):
|
| 52 |
+
return json.load(open(data_path))
|
| 53 |
+
return [json.loads(line) for line in open(data_path)]
|
| 54 |
+
|
| 55 |
+
|
| 56 |
+
def select_docs(item, num_docs: int, shuffle: bool, rng: random.Random):
|
| 57 |
+
docs = list(item["docs"])
|
| 58 |
+
if shuffle:
|
| 59 |
+
rng.shuffle(docs)
|
| 60 |
+
if num_docs > 0:
|
| 61 |
+
docs = docs[:num_docs]
|
| 62 |
+
return docs
|
| 63 |
+
|
| 64 |
+
|
| 65 |
+
def build_cot_messages(sys_prompt, user_prompt, item, docs, max_pixels):
|
| 66 |
+
"""将 user_prompt 中的 <image>/<instruction>/<doc_list>/<gt_labels> 占位符展开成 qwen-vl messages。"""
|
| 67 |
+
labels = [doc["label"] for doc in docs]
|
| 68 |
+
gt_str = "".join(str(l) for l in labels)
|
| 69 |
+
|
| 70 |
+
content = []
|
| 71 |
+
for segment in re.split(r"(<image>|<instruction>|<doc_list>|<gt_labels>)", user_prompt):
|
| 72 |
+
if segment == "<image>":
|
| 73 |
+
content.append({"type": "image", "image": item["query_image"], "max_pixels": max_pixels})
|
| 74 |
+
elif segment == "<instruction>":
|
| 75 |
+
content.append({"type": "text", "text": item.get("instruction", "")})
|
| 76 |
+
elif segment == "<doc_list>":
|
| 77 |
+
for i, doc in enumerate(docs):
|
| 78 |
+
content.append({"type": "text", "text": f"\n{i}: "})
|
| 79 |
+
content.append({"type": "image", "image": doc["crop_image"], "max_pixels": max_pixels})
|
| 80 |
+
content.append({"type": "text", "text": f" {doc['doc_title']}"})
|
| 81 |
+
content.append({"type": "text", "text": "\n"})
|
| 82 |
+
elif segment == "<gt_labels>":
|
| 83 |
+
lines = [f"{i}: {l}" for i, l in enumerate(labels)]
|
| 84 |
+
content.append({"type": "text", "text": "\n".join(lines) + f"\n(拼接后应为:{gt_str})"})
|
| 85 |
+
else:
|
| 86 |
+
content.append({"type": "text", "text": segment})
|
| 87 |
+
|
| 88 |
+
return [
|
| 89 |
+
{"role": "system", "content": sys_prompt},
|
| 90 |
+
{"role": "user", "content": content},
|
| 91 |
+
]
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
ANSWER_RE = re.compile(r"最终答案[::]\s*([0-3]+)")
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def parse_output(text: str):
|
| 98 |
+
"""返回 (cot, answer),解析失败返回 (None, None)。"""
|
| 99 |
+
matches = list(ANSWER_RE.finditer(text))
|
| 100 |
+
if not matches:
|
| 101 |
+
return None, None
|
| 102 |
+
last = matches[-1]
|
| 103 |
+
answer = last.group(1)
|
| 104 |
+
|
| 105 |
+
# 关闭 thinking 后模型直接输出正文,chat template 仍会留下一个空 think 块
|
| 106 |
+
cot = text[: last.start()].replace("<think>", "").replace("</think>", "").strip()
|
| 107 |
+
if "【逐项分析】" not in cot:
|
| 108 |
+
return None, answer
|
| 109 |
+
return cot, answer
|
| 110 |
+
|
| 111 |
+
|
| 112 |
+
LEAK_RE = re.compile(
|
| 113 |
+
r"参考标签|参考答案|标注员|给定的?标签|给定的?答案|已知标签|正确答案|所给的?标签|提供的标签|ground\s*truth",
|
| 114 |
+
re.IGNORECASE,
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def is_valid(cot, answer, gt_str):
|
| 119 |
+
if cot is None or answer is None:
|
| 120 |
+
return False
|
| 121 |
+
if answer != gt_str:
|
| 122 |
+
return False
|
| 123 |
+
if LEAK_RE.search(cot):
|
| 124 |
+
return False
|
| 125 |
+
return True
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
def main():
|
| 129 |
+
parser = argparse.ArgumentParser()
|
| 130 |
+
parser.add_argument("--model-path", type=str, required=True, help="本地大 qwen-vl 模型权重路径")
|
| 131 |
+
parser.add_argument("--train-data-path", type=str, required=True)
|
| 132 |
+
parser.add_argument("--output-path", type=str, required=True)
|
| 133 |
+
parser.add_argument("--failed-path", type=str, default=None, help="多次重试仍失败的样本记录,默认 output_path + .failed")
|
| 134 |
+
parser.add_argument("--prompt-path", type=str, default=os.path.join(current_file_path, "cot_prompt.jinja"))
|
| 135 |
+
parser.add_argument("--num-docs", type=int, default=10, help="每个样本使用的候选数,<=0 表示全部")
|
| 136 |
+
parser.add_argument("--shuffle-docs", action="store_true", help="是否先 shuffle 再截取(与训练随机采样对齐)")
|
| 137 |
+
parser.add_argument("--seed", type=int, default=42)
|
| 138 |
+
parser.add_argument("--max-crop-tokens", type=int, default=80, help="单图最大视觉 token 数,与训练配置对齐")
|
| 139 |
+
parser.add_argument("--patch-size", type=int, default=16)
|
| 140 |
+
parser.add_argument("--tensor-parallel-size", type=int, default=8)
|
| 141 |
+
parser.add_argument("--max-model-len", type=int, default=16384)
|
| 142 |
+
parser.add_argument("--max-new-tokens", type=int, default=4096)
|
| 143 |
+
parser.add_argument("--gpu-memory-utilization", type=float, default=0.9)
|
| 144 |
+
parser.add_argument("--max-retries", type=int, default=3, help="答案与 GT 不一致时的重试轮数")
|
| 145 |
+
parser.add_argument("--retry-temperature", type=float, default=0.7)
|
| 146 |
+
parser.add_argument("--batch-size", type=int, default=64, help="每轮送入 vLLM 的样本数")
|
| 147 |
+
parser.add_argument("--shard-id", type=int, default=None, help="不传则取环境变量 RANK")
|
| 148 |
+
parser.add_argument("--num-shards", type=int, default=None, help="不传则取环境变量 WORLD_SIZE")
|
| 149 |
+
parser.add_argument("--test", type=int, nargs="?", const=20, default=None,
|
| 150 |
+
help="试跑模式:只处理数据集前 N 条(默认 20),结果写到 <output>.test.jsonl")
|
| 151 |
+
args = parser.parse_args()
|
| 152 |
+
|
| 153 |
+
rank, world_size, local_rank = get_dist_env()
|
| 154 |
+
if args.num_shards is None:
|
| 155 |
+
args.num_shards = world_size
|
| 156 |
+
if args.shard_id is None:
|
| 157 |
+
args.shard_id = rank
|
| 158 |
+
if args.num_shards > 1:
|
| 159 |
+
# 每进程独占一张卡,必须早于 vllm import
|
| 160 |
+
bind_single_gpu(local_rank)
|
| 161 |
+
args.tensor_parallel_size = 1
|
| 162 |
+
|
| 163 |
+
from vllm import LLM, SamplingParams
|
| 164 |
+
from transformers import AutoProcessor
|
| 165 |
+
from collators.vision_process import process_vision_info
|
| 166 |
+
|
| 167 |
+
if args.test:
|
| 168 |
+
root, ext = os.path.splitext(args.output_path)
|
| 169 |
+
args.output_path = f"{root}.test{ext}"
|
| 170 |
+
if args.num_shards > 1:
|
| 171 |
+
root, ext = os.path.splitext(args.output_path)
|
| 172 |
+
args.output_path = f"{root}.shard{args.shard_id}-{args.num_shards}{ext}"
|
| 173 |
+
failed_path = args.failed_path or args.output_path + ".failed"
|
| 174 |
+
os.makedirs(os.path.dirname(os.path.abspath(args.output_path)), exist_ok=True)
|
| 175 |
+
|
| 176 |
+
with open(args.prompt_path, encoding="utf-8") as f:
|
| 177 |
+
full_prompt = f.read()
|
| 178 |
+
sys_prompt = extract_prompt(full_prompt, r"<sys_start>(.*?)<sys_end>")
|
| 179 |
+
user_prompt = extract_prompt(full_prompt, r"<user_start>(.*?)<user_end>")
|
| 180 |
+
assert sys_prompt and user_prompt, "prompt 模板缺少 <sys_start>/<user_start> 块"
|
| 181 |
+
|
| 182 |
+
max_pixels = args.max_crop_tokens * (args.patch_size * 2) ** 2
|
| 183 |
+
|
| 184 |
+
data = load_data(args.train_data_path)
|
| 185 |
+
print(f"Load {len(data)} instances from {args.train_data_path}")
|
| 186 |
+
if args.test:
|
| 187 |
+
data = data[: args.test]
|
| 188 |
+
print(f"[TEST MODE] only generate for first {len(data)} samples -> {args.output_path}")
|
| 189 |
+
|
| 190 |
+
done_ids = set()
|
| 191 |
+
if os.path.exists(args.output_path):
|
| 192 |
+
for line in open(args.output_path):
|
| 193 |
+
try:
|
| 194 |
+
done_ids.add(json.loads(line)["sample_idx"])
|
| 195 |
+
except Exception:
|
| 196 |
+
pass
|
| 197 |
+
print(f"Resume: {len(done_ids)} samples already done")
|
| 198 |
+
|
| 199 |
+
rng = random.Random(args.seed)
|
| 200 |
+
pending = [] # (sample_idx, item, docs, gt_str, messages)
|
| 201 |
+
for idx, item in enumerate(data):
|
| 202 |
+
docs = select_docs(item, args.num_docs, args.shuffle_docs, rng) # 先采样保证各分片 rng 状态一致
|
| 203 |
+
if idx % args.num_shards != args.shard_id or idx in done_ids:
|
| 204 |
+
continue
|
| 205 |
+
gt_str = "".join(str(doc["label"]) for doc in docs)
|
| 206 |
+
messages = build_cot_messages(sys_prompt, user_prompt, item, docs, max_pixels)
|
| 207 |
+
pending.append((idx, item, docs, gt_str, messages))
|
| 208 |
+
print(f"Shard {args.shard_id}/{args.num_shards}: {len(pending)} samples to generate")
|
| 209 |
+
if not pending:
|
| 210 |
+
return
|
| 211 |
+
|
| 212 |
+
max_images = 1 + max(len(d) for _, _, d, _, _ in pending)
|
| 213 |
+
llm = LLM(
|
| 214 |
+
model=args.model_path,
|
| 215 |
+
tensor_parallel_size=args.tensor_parallel_size,
|
| 216 |
+
max_model_len=args.max_model_len,
|
| 217 |
+
gpu_memory_utilization=args.gpu_memory_utilization,
|
| 218 |
+
limit_mm_per_prompt={"image": max_images},
|
| 219 |
+
trust_remote_code=True,
|
| 220 |
+
)
|
| 221 |
+
processor = AutoProcessor.from_pretrained(args.model_path, trust_remote_code=True)
|
| 222 |
+
|
| 223 |
+
def to_vllm_input(messages):
|
| 224 |
+
text = processor.apply_chat_template(
|
| 225 |
+
messages, tokenize=False, add_generation_prompt=True, enable_thinking=False
|
| 226 |
+
)
|
| 227 |
+
image_inputs, _ = process_vision_info([messages])
|
| 228 |
+
return {"prompt": text, "multi_modal_data": {"image": image_inputs}}
|
| 229 |
+
|
| 230 |
+
out_f = open(args.output_path, "a", encoding="utf-8")
|
| 231 |
+
failed_f = open(failed_path, "a", encoding="utf-8")
|
| 232 |
+
|
| 233 |
+
n_ok, n_fail = 0, 0
|
| 234 |
+
for batch_start in range(0, len(pending), args.batch_size):
|
| 235 |
+
batch = pending[batch_start: batch_start + args.batch_size]
|
| 236 |
+
results = {} # sample_idx -> (cot, answer)
|
| 237 |
+
last_text = {} # sample_idx -> 最后一次尝试的模型原始输出
|
| 238 |
+
|
| 239 |
+
remaining = list(batch)
|
| 240 |
+
for attempt in range(args.max_retries + 1):
|
| 241 |
+
if not remaining:
|
| 242 |
+
break
|
| 243 |
+
temperature = 0.0 if attempt == 0 else args.retry_temperature
|
| 244 |
+
sampling_params = SamplingParams(
|
| 245 |
+
temperature=temperature,
|
| 246 |
+
top_p=0.95 if temperature > 0 else 1.0,
|
| 247 |
+
max_tokens=args.max_new_tokens,
|
| 248 |
+
seed=args.seed + attempt,
|
| 249 |
+
)
|
| 250 |
+
vllm_inputs = [to_vllm_input(m) for _, _, _, _, m in remaining]
|
| 251 |
+
outputs = llm.generate(vllm_inputs, sampling_params)
|
| 252 |
+
|
| 253 |
+
still_failed = []
|
| 254 |
+
for (idx, item, docs, gt_str, messages), out in zip(remaining, outputs):
|
| 255 |
+
text = out.outputs[0].text
|
| 256 |
+
last_text[idx] = text
|
| 257 |
+
cot, answer = parse_output(text)
|
| 258 |
+
if is_valid(cot, answer, gt_str):
|
| 259 |
+
results[idx] = (cot, answer)
|
| 260 |
+
else:
|
| 261 |
+
still_failed.append((idx, item, docs, gt_str, messages))
|
| 262 |
+
remaining = still_failed
|
| 263 |
+
if remaining:
|
| 264 |
+
print(f"[batch {batch_start}] attempt {attempt}: {len(remaining)} samples need retry")
|
| 265 |
+
|
| 266 |
+
for idx, item, docs, gt_str, messages in batch:
|
| 267 |
+
record = dict(item)
|
| 268 |
+
record["docs"] = docs # 保存实际使用的 docs 顺序,训练时 CoT 与顺序绑定
|
| 269 |
+
record["sample_idx"] = idx
|
| 270 |
+
if idx in results:
|
| 271 |
+
cot, answer = results[idx]
|
| 272 |
+
record["cot"] = cot
|
| 273 |
+
record["cot_answer"] = answer
|
| 274 |
+
out_f.write(json.dumps(record, ensure_ascii=False) + "\n")
|
| 275 |
+
n_ok += 1
|
| 276 |
+
else:
|
| 277 |
+
record["gt_answer"] = gt_str
|
| 278 |
+
record["raw_output"] = last_text.get(idx)
|
| 279 |
+
failed_f.write(json.dumps(record, ensure_ascii=False) + "\n")
|
| 280 |
+
n_fail += 1
|
| 281 |
+
out_f.flush()
|
| 282 |
+
failed_f.flush()
|
| 283 |
+
print(f"Progress: {batch_start + len(batch)}/{len(pending)}, ok={n_ok}, fail={n_fail}")
|
| 284 |
+
|
| 285 |
+
out_f.close()
|
| 286 |
+
failed_f.close()
|
| 287 |
+
print(f"Done. success={n_ok}, failed={n_fail}, output={args.output_path}")
|
| 288 |
+
|
| 289 |
+
|
| 290 |
+
if __name__ == "__main__":
|
| 291 |
+
main()
|
code/rerank_ins/cot_gen/merge_cot_shards.py
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python
|
| 2 |
+
"""合并分片生成的 CoT jsonl。
|
| 3 |
+
|
| 4 |
+
各分片按 sample_idx % num_shards 交错取数,且断点续跑是追加写入,
|
| 5 |
+
所以直接 cat 得到的顺序是乱的,这里统一按 sample_idx 升序合并。
|
| 6 |
+
|
| 7 |
+
Usage:
|
| 8 |
+
python cot_gen/merge_cot_shards.py /data/oss_bucket_0/liqi/dataset/reranker/cot_data_eval/eval_cot.jsonl --num-shards 8
|
| 9 |
+
python cot_gen/merge_cot_shards.py /path/to/train_cot.jsonl -n 8 --test
|
| 10 |
+
"""
|
| 11 |
+
import argparse
|
| 12 |
+
import glob
|
| 13 |
+
import json
|
| 14 |
+
import os
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
def shard_paths(output_path, num_shards, suffix=""):
|
| 18 |
+
root, ext = os.path.splitext(output_path)
|
| 19 |
+
if num_shards:
|
| 20 |
+
paths = [f"{root}.shard{i}-{num_shards}{ext}{suffix}" for i in range(num_shards)]
|
| 21 |
+
return paths, [p for p in paths if not os.path.exists(p)]
|
| 22 |
+
return sorted(glob.glob(f"{root}.shard*-*{ext}{suffix}")), []
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def merge(paths, out_path, sort_key="sample_idx"):
|
| 26 |
+
records = {}
|
| 27 |
+
dup = n_read = 0
|
| 28 |
+
for p in paths:
|
| 29 |
+
with open(p, encoding="utf-8") as f:
|
| 30 |
+
for line in f:
|
| 31 |
+
line = line.strip()
|
| 32 |
+
if not line:
|
| 33 |
+
continue
|
| 34 |
+
n_read += 1
|
| 35 |
+
rec = json.loads(line)
|
| 36 |
+
idx = rec[sort_key]
|
| 37 |
+
if idx in records:
|
| 38 |
+
dup += 1
|
| 39 |
+
records[idx] = rec
|
| 40 |
+
with open(out_path, "w", encoding="utf-8") as f:
|
| 41 |
+
for idx in sorted(records):
|
| 42 |
+
f.write(json.dumps(records[idx], ensure_ascii=False) + "\n")
|
| 43 |
+
return n_read, len(records), dup
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def main():
|
| 47 |
+
parser = argparse.ArgumentParser()
|
| 48 |
+
parser.add_argument("output_path", help="合并后的目标路径,即提交脚本里的 OUTPUT_PATH")
|
| 49 |
+
parser.add_argument("-n", "--num-shards", type=int, default=None,
|
| 50 |
+
help="分片数(提交脚本的 WORKER_COUNT);不传则按 glob 匹配所有分片")
|
| 51 |
+
parser.add_argument("--test", action="store_true", help="合并 --test 模式产出的 .test.shardN-M.jsonl")
|
| 52 |
+
parser.add_argument("--skip-failed", action="store_true", help="不合并 .failed 分片")
|
| 53 |
+
args = parser.parse_args()
|
| 54 |
+
|
| 55 |
+
out_path = args.output_path
|
| 56 |
+
if args.test:
|
| 57 |
+
root, ext = os.path.splitext(out_path)
|
| 58 |
+
out_path = f"{root}.test{ext}"
|
| 59 |
+
|
| 60 |
+
paths, missing = shard_paths(out_path, args.num_shards)
|
| 61 |
+
if missing:
|
| 62 |
+
print(f"[WARN] 缺少 {len(missing)} 个分片,将跳过: {missing}")
|
| 63 |
+
paths = [p for p in paths if os.path.exists(p)]
|
| 64 |
+
if not paths:
|
| 65 |
+
raise SystemExit(f"未找到任何分片文件: {os.path.splitext(out_path)[0]}.shard*")
|
| 66 |
+
|
| 67 |
+
print(f"合并 {len(paths)} 个分片 -> {out_path}")
|
| 68 |
+
n_read, n_out, dup = merge(paths, out_path)
|
| 69 |
+
print(f" 读入 {n_read} 行,去重后 {n_out} 条,重复 {dup} 条")
|
| 70 |
+
|
| 71 |
+
if not args.skip_failed:
|
| 72 |
+
f_paths, _ = shard_paths(out_path, args.num_shards, suffix=".failed")
|
| 73 |
+
f_paths = [p for p in f_paths if os.path.exists(p)]
|
| 74 |
+
if f_paths:
|
| 75 |
+
n_read, n_out, dup = merge(f_paths, out_path + ".failed")
|
| 76 |
+
print(f"合并 {len(f_paths)} 个 failed 分片 -> {out_path}.failed: {n_out} 条")
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
if __name__ == "__main__":
|
| 80 |
+
main()
|
code/rerank_ins/cot_gen/run_gen_cot.sh
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/bash
|
| 2 |
+
# 用本地大 Qwen-VL 模型为训练集批量生成 CoT 数据
|
| 3 |
+
|
| 4 |
+
MODEL_PATH=/data/oss_bucket_0/planck/hf_models/Qwen3.6-27B # TODO: 本地大模型路径
|
| 5 |
+
TRAIN_DATA_PATH=/data/oss_bucket_0/siku/Last_reranker/dataset/all_data_wo_q_bbox/00-07/with_bbox/train_format/train.jsonl
|
| 6 |
+
OUTPUT_PATH=./cot_data/train_cot.jsonl
|
| 7 |
+
|
| 8 |
+
# 多机/多进程分片时修改 SHARD_ID / NUM_SHARDS,各分片写不同 OUTPUT_PATH 后合并
|
| 9 |
+
SHARD_ID=0
|
| 10 |
+
NUM_SHARDS=1
|
| 11 |
+
|
| 12 |
+
# 试跑:设为 "--test 20" 只生成前 20 条(写到 train_cot.test.jsonl),留空则全量生成
|
| 13 |
+
TEST="--test 10"
|
| 14 |
+
|
| 15 |
+
python cot_gen/gen_cot_data.py \
|
| 16 |
+
--model-path $MODEL_PATH \
|
| 17 |
+
--train-data-path $TRAIN_DATA_PATH \
|
| 18 |
+
--output-path $OUTPUT_PATH \
|
| 19 |
+
--num-docs 10 \
|
| 20 |
+
--seed 42 \
|
| 21 |
+
--max-crop-tokens 80 \
|
| 22 |
+
--patch-size 16 \
|
| 23 |
+
--tensor-parallel-size 4 \
|
| 24 |
+
--max-model-len 16384 \
|
| 25 |
+
--max-new-tokens 4096 \
|
| 26 |
+
--max-retries 3 \
|
| 27 |
+
--retry-temperature 0.7 \
|
| 28 |
+
--batch-size 64 \
|
| 29 |
+
--shard-id $SHARD_ID \
|
| 30 |
+
--num-shards $NUM_SHARDS \
|
| 31 |
+
$TEST
|
code/rerank_ins/dataset/__init__.py
ADDED
|
File without changes
|
code/rerank_ins/dataset/__pycache__/__init__.cpython-313.pyc
ADDED
|
Binary file (155 Bytes). View file
|
|
|
code/rerank_ins/dataset/__pycache__/__init__.cpython-314.pyc
ADDED
|
Binary file (120 Bytes). View file
|
|
|
code/rerank_ins/dataset/__pycache__/data_utils.cpython-313.pyc
ADDED
|
Binary file (2.25 kB). View file
|
|
|
code/rerank_ins/dataset/__pycache__/data_utils.cpython-314.pyc
ADDED
|
Binary file (2.3 kB). View file
|
|
|
code/rerank_ins/dataset/__pycache__/dataset_listwise.cpython-313.pyc
ADDED
|
Binary file (6.82 kB). View file
|
|
|
code/rerank_ins/dataset/__pycache__/dataset_listwise.cpython-314.pyc
ADDED
|
Binary file (7.56 kB). View file
|
|
|
code/rerank_ins/dataset/data_utils.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import re
|
| 2 |
+
|
| 3 |
+
|
| 4 |
+
def build_messages(sys, user, images, raw_res, num_docs=None, index=None, titles=None, instruction=None):
|
| 5 |
+
messages = []
|
| 6 |
+
user_list = []
|
| 7 |
+
|
| 8 |
+
if sys is not None:
|
| 9 |
+
messages.append({"role": "system", "content": sys})
|
| 10 |
+
|
| 11 |
+
for segment in re.split("(<image>|<video>|<index>|<title>|<n_images>|<instruction>)", user):
|
| 12 |
+
if segment == "<image>":
|
| 13 |
+
image_dict = images.pop(0)
|
| 14 |
+
user_list.append({"type": "image", 'image': image_dict.get('image'), 'bbox': image_dict.get('bbox', None), 'dynamic_crop': image_dict.get('dynamic_crop', False), 'max_pixels': image_dict.get('max_pixels', None)})
|
| 15 |
+
elif segment == "<index>":
|
| 16 |
+
user_list.append({"type": "text", "text": str(index)})
|
| 17 |
+
elif segment == "<title>":
|
| 18 |
+
title = titles.pop(0)
|
| 19 |
+
user_list.append({"type": "text", "text": title})
|
| 20 |
+
elif segment == "<n_images>":
|
| 21 |
+
for i in range(num_docs):
|
| 22 |
+
image_dict = images.pop(0)
|
| 23 |
+
user_list.append({"type": "image", 'image': image_dict.get('image'), 'bbox': image_dict.get('bbox', None), 'dynamic_crop': image_dict.get('dynamic_crop', False), 'max_pixels': image_dict.get('max_pixels', None)})
|
| 24 |
+
elif segment == "<instruction>":
|
| 25 |
+
user_list.append({"type": "text", "text": instruction or ""})
|
| 26 |
+
else:
|
| 27 |
+
user_list.append({"type": "text", "text": segment})
|
| 28 |
+
messages.append({"role": "user", "content": user_list})
|
| 29 |
+
|
| 30 |
+
if raw_res is not None:
|
| 31 |
+
messages.append({"role": "assistant", "content": [{'type': 'text', 'text': raw_res}]})
|
| 32 |
+
|
| 33 |
+
return messages
|
code/rerank_ins/dataset/dataset_listwise.py
ADDED
|
@@ -0,0 +1,118 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
from typing import Dict, List
|
| 3 |
+
from torch.utils.data import Dataset
|
| 4 |
+
import random
|
| 5 |
+
import logging
|
| 6 |
+
from jinja2 import Template
|
| 7 |
+
import re
|
| 8 |
+
from .data_utils import build_messages
|
| 9 |
+
|
| 10 |
+
logger = logging.getLogger(__name__)
|
| 11 |
+
|
| 12 |
+
|
| 13 |
+
def extract_prompt(full_prompt: str, pattern) -> str | None:
|
| 14 |
+
match = re.search(pattern, full_prompt, re.DOTALL)
|
| 15 |
+
if match:
|
| 16 |
+
sys_prompt = match.group(1).strip()
|
| 17 |
+
return sys_prompt
|
| 18 |
+
else:
|
| 19 |
+
return None
|
| 20 |
+
|
| 21 |
+
|
| 22 |
+
class TongkuanListWiseSFTParallelDataset(Dataset):
|
| 23 |
+
|
| 24 |
+
def __init__(
|
| 25 |
+
self,
|
| 26 |
+
args,
|
| 27 |
+
data_path: str,
|
| 28 |
+
train=True
|
| 29 |
+
) -> None:
|
| 30 |
+
super(TongkuanListWiseSFTParallelDataset, self).__init__()
|
| 31 |
+
self.full_crop_mode = getattr(args, "full_crop_mode", False)
|
| 32 |
+
if isinstance(data_path, str):
|
| 33 |
+
if data_path.endswith(".json"):
|
| 34 |
+
self.data = json.load(open(data_path))
|
| 35 |
+
elif data_path.endswith(".jsonl"):
|
| 36 |
+
self.data = [json.loads(line) for line in open(data_path)]
|
| 37 |
+
if isinstance(data_path, List):
|
| 38 |
+
self.data = data_path
|
| 39 |
+
print(f"Load {len(self.data)} instances")
|
| 40 |
+
self.args = args
|
| 41 |
+
self.train = train
|
| 42 |
+
with open(args.format_prompt, encoding="utf-8") as f:
|
| 43 |
+
format_prompt = f.read()
|
| 44 |
+
format_prompt = Template(format_prompt.strip())
|
| 45 |
+
full_prompt = format_prompt.render()
|
| 46 |
+
self.sys_prompt = extract_prompt(full_prompt, r'<sys_start>(.*?)<sys_end>')
|
| 47 |
+
self.user_prompt = extract_prompt(full_prompt, r'<user_start>(.*?)<user_end>')
|
| 48 |
+
self.max_full_pixels = args.max_full_tokens * (args.patch_size * 2) ** 2
|
| 49 |
+
self.max_crop_pixels = args.max_crop_tokens * (args.patch_size * 2) ** 2
|
| 50 |
+
self.class_mode = getattr(args, "class_mode", 0)
|
| 51 |
+
|
| 52 |
+
def __len__(self) -> int:
|
| 53 |
+
return len(self.data)
|
| 54 |
+
|
| 55 |
+
def _get_train_docs(self, item, num_train_docs):
|
| 56 |
+
if 'docs' in item:
|
| 57 |
+
docs_to_process = list(item['docs'])
|
| 58 |
+
random.shuffle(docs_to_process)
|
| 59 |
+
docs_to_process = docs_to_process[:num_train_docs]
|
| 60 |
+
return docs_to_process
|
| 61 |
+
|
| 62 |
+
def get_instance(self, idx):
|
| 63 |
+
item = self.data[idx]
|
| 64 |
+
|
| 65 |
+
query_image = item['query_image']
|
| 66 |
+
|
| 67 |
+
label_mapping = {0: 0, 1: 1, 2: 2, 3: 3}
|
| 68 |
+
|
| 69 |
+
if self.train:
|
| 70 |
+
docs_to_process = self._get_train_docs(item, self.args.num_train_docs)
|
| 71 |
+
else:
|
| 72 |
+
docs_to_process = item['docs']
|
| 73 |
+
|
| 74 |
+
final_doc_labels = []
|
| 75 |
+
|
| 76 |
+
for doc in docs_to_process:
|
| 77 |
+
final_doc_labels.append(label_mapping.get(doc.get('label', None)))
|
| 78 |
+
|
| 79 |
+
if self.full_crop_mode:
|
| 80 |
+
query = [
|
| 81 |
+
{'image': query_image, 'max_pixels': self.max_full_pixels},
|
| 82 |
+
{'image': item['query_crop_image'], 'max_pixels': self.max_crop_pixels},
|
| 83 |
+
]
|
| 84 |
+
docs = []
|
| 85 |
+
for doc in docs_to_process:
|
| 86 |
+
docs.append({'image': doc['doc_image'], 'max_pixels': self.max_full_pixels})
|
| 87 |
+
docs.append({'image': doc['crop_image'], 'max_pixels': self.max_crop_pixels})
|
| 88 |
+
else:
|
| 89 |
+
query = [{'image': query_image, 'max_pixels': self.max_crop_pixels}]
|
| 90 |
+
docs = [{'image': doc['crop_image'], 'max_pixels': self.max_crop_pixels} for doc in docs_to_process]
|
| 91 |
+
|
| 92 |
+
instance = {
|
| 93 |
+
"query_image": query,
|
| 94 |
+
"doc_images": docs,
|
| 95 |
+
"labels": final_doc_labels,
|
| 96 |
+
"titles": [doc['doc_title'] for doc in docs_to_process],
|
| 97 |
+
"instruction": item.get('instruction', ''),
|
| 98 |
+
}
|
| 99 |
+
|
| 100 |
+
return instance
|
| 101 |
+
|
| 102 |
+
def __getitem__(self, i):
|
| 103 |
+
item = self.data[i]
|
| 104 |
+
instance = self.get_instance(i)
|
| 105 |
+
query_image = instance['query_image']
|
| 106 |
+
doc_images = instance['doc_images']
|
| 107 |
+
labels = instance['labels']
|
| 108 |
+
item['labels'] = labels
|
| 109 |
+
query_image = [query_image] if not isinstance(query_image, List) else query_image
|
| 110 |
+
images = query_image + doc_images
|
| 111 |
+
if self.train:
|
| 112 |
+
raw_res = "".join(str(l) for l in labels)
|
| 113 |
+
rerank_message = build_messages(self.sys_prompt, self.user_prompt, images, raw_res,
|
| 114 |
+
titles=list(instance['titles']), instruction=instance['instruction'])
|
| 115 |
+
else:
|
| 116 |
+
rerank_message = build_messages(self.sys_prompt, self.user_prompt, images, None,
|
| 117 |
+
titles=list(instance['titles']), instruction=instance['instruction'])
|
| 118 |
+
return rerank_message, item
|
code/rerank_ins/ds_configs/zero2.json
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"fp16": {
|
| 3 |
+
"enabled": "auto",
|
| 4 |
+
"loss_scale": 0,
|
| 5 |
+
"loss_scale_window": 1000,
|
| 6 |
+
"initial_scale_power": 16,
|
| 7 |
+
"hysteresis": 2,
|
| 8 |
+
"min_loss_scale": 1
|
| 9 |
+
},
|
| 10 |
+
"bf16": {
|
| 11 |
+
"enabled": "auto"
|
| 12 |
+
},
|
| 13 |
+
"train_micro_batch_size_per_gpu": "auto",
|
| 14 |
+
"train_batch_size": "auto",
|
| 15 |
+
"gradient_accumulation_steps": "auto",
|
| 16 |
+
"zero_optimization": {
|
| 17 |
+
"stage": 2,
|
| 18 |
+
"overlap_comm": false,
|
| 19 |
+
"contiguous_gradients": true,
|
| 20 |
+
"sub_group_size": 1e9,
|
| 21 |
+
"reduce_bucket_size": "auto"
|
| 22 |
+
}
|
| 23 |
+
}
|
code/rerank_ins/ds_configs/zero3.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"fp16": {
|
| 3 |
+
"enabled": "auto",
|
| 4 |
+
"loss_scale": 0,
|
| 5 |
+
"loss_scale_window": 1000,
|
| 6 |
+
"initial_scale_power": 16,
|
| 7 |
+
"hysteresis": 2,
|
| 8 |
+
"min_loss_scale": 1
|
| 9 |
+
},
|
| 10 |
+
"bf16": {
|
| 11 |
+
"enabled": "auto"
|
| 12 |
+
},
|
| 13 |
+
"train_micro_batch_size_per_gpu": "auto",
|
| 14 |
+
"train_batch_size": "auto",
|
| 15 |
+
"gradient_accumulation_steps": "auto",
|
| 16 |
+
"zero_optimization": {
|
| 17 |
+
"stage": 3,
|
| 18 |
+
"overlap_comm": true,
|
| 19 |
+
"contiguous_gradients": true,
|
| 20 |
+
"sub_group_size": 1e9,
|
| 21 |
+
"reduce_bucket_size": "auto",
|
| 22 |
+
"stage3_prefetch_bucket_size": "auto",
|
| 23 |
+
"stage3_param_persistence_threshold": "auto",
|
| 24 |
+
"stage3_max_live_parameters": 1e9,
|
| 25 |
+
"stage3_max_reuse_distance": 1e9,
|
| 26 |
+
"stage3_gather_16bit_weights_on_model_save": true
|
| 27 |
+
}
|
| 28 |
+
}
|
code/rerank_ins/eval/__pycache__/eval_reranker_qwen35.cpython-314.pyc
ADDED
|
Binary file (16 kB). View file
|
|
|
code/rerank_ins/eval/__pycache__/eval_reranker_qwen3vl.cpython-313.pyc
ADDED
|
Binary file (20.3 kB). View file
|
|
|
code/rerank_ins/eval/__pycache__/eval_reranker_qwen3vl.cpython-314.pyc
ADDED
|
Binary file (15.8 kB). View file
|
|
|
code/rerank_ins/eval/__pycache__/eval_reranker_qwen3vl_cot.cpython-313.pyc
ADDED
|
Binary file (12.8 kB). View file
|
|
|
code/rerank_ins/eval/eval_reranker_qwen35.py
ADDED
|
@@ -0,0 +1,230 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Eval script (Qwen3.5) with Transformers native inference.
|
| 3 |
+
|
| 4 |
+
Usage:
|
| 5 |
+
python eval/eval_reranker_qwen35.py \
|
| 6 |
+
--model-path <checkpoint_path> \
|
| 7 |
+
--eval-data <eval_jsonl_path> \
|
| 8 |
+
--format-prompt format_prompt/listwise_reranker_v2.jinja
|
| 9 |
+
"""
|
| 10 |
+
|
| 11 |
+
import os
|
| 12 |
+
import sys
|
| 13 |
+
import json
|
| 14 |
+
import argparse
|
| 15 |
+
import torch
|
| 16 |
+
from collections import defaultdict
|
| 17 |
+
from tqdm import tqdm
|
| 18 |
+
|
| 19 |
+
current_file_path = os.path.dirname(os.path.abspath(__file__))
|
| 20 |
+
module_path = os.path.join(current_file_path, "../")
|
| 21 |
+
sys.path.append(module_path)
|
| 22 |
+
|
| 23 |
+
from jinja2 import Template
|
| 24 |
+
import re
|
| 25 |
+
from transformers import Qwen3_5ForConditionalGeneration, AutoProcessor
|
| 26 |
+
from collators.vision_process import process_vision_info
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def extract_prompt(full_prompt, pattern):
|
| 30 |
+
match = re.search(pattern, full_prompt, re.DOTALL)
|
| 31 |
+
return match.group(1).strip() if match else None
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def is_lora_checkpoint(model_path):
|
| 35 |
+
return os.path.exists(os.path.join(model_path, "adapter_config.json"))
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def build_messages(sys_prompt, user_prompt, images, instruction, titles):
|
| 39 |
+
messages = []
|
| 40 |
+
if sys_prompt:
|
| 41 |
+
messages.append({"role": "system", "content": sys_prompt})
|
| 42 |
+
|
| 43 |
+
user_list = []
|
| 44 |
+
for segment in re.split("(<image>|<instruction>|<title>)", user_prompt):
|
| 45 |
+
if segment == "<image>":
|
| 46 |
+
img = images.pop(0)
|
| 47 |
+
img_dict = {"type": "image", "image": img['image'], "max_pixels": img['max_pixels']}
|
| 48 |
+
user_list.append(img_dict)
|
| 49 |
+
elif segment == "<instruction>":
|
| 50 |
+
user_list.append({"type": "text", "text": instruction or ""})
|
| 51 |
+
elif segment == "<title>":
|
| 52 |
+
title = titles.pop(0)
|
| 53 |
+
user_list.append({"type": "text", "text": title})
|
| 54 |
+
else:
|
| 55 |
+
user_list.append({"type": "text", "text": segment})
|
| 56 |
+
messages.append({"role": "user", "content": user_list})
|
| 57 |
+
return messages
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def get_topn_strict_hitrate(labels, N):
|
| 61 |
+
gt_num = list(labels).count(0)
|
| 62 |
+
hit = sum(1.0 for i in range(min(N, gt_num)) if labels[i] == 0)
|
| 63 |
+
return hit / min(N, gt_num) if gt_num > 0 and N > 0 else 0.0
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def get_topn_soft_hitrate(labels, N):
|
| 67 |
+
gt_num = list(labels).count(0) + list(labels).count(1)
|
| 68 |
+
hit = sum(1.0 for i in range(min(N, gt_num)) if labels[i] in [0, 1])
|
| 69 |
+
return hit / min(N, gt_num) if gt_num > 0 and N > 0 else 0.0
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
def main():
|
| 73 |
+
parser = argparse.ArgumentParser(description="Eval reranker (Qwen3.5) with Transformers")
|
| 74 |
+
parser.add_argument("--model-path", required=True, help="checkpoint path (LoRA or full)")
|
| 75 |
+
parser.add_argument("--base-model", default=None, help="base model path (for LoRA)")
|
| 76 |
+
parser.add_argument("--eval-data", required=True, help="eval jsonl path")
|
| 77 |
+
parser.add_argument("--format-prompt", default=os.path.join(module_path, "format_prompt/listwise_reranker_v2.jinja"))
|
| 78 |
+
parser.add_argument("--max-tokens", type=int, default=10)
|
| 79 |
+
parser.add_argument("--output-dir", default=None)
|
| 80 |
+
parser.add_argument("--max-crop-tokens", type=int, default=160)
|
| 81 |
+
parser.add_argument("--max-full-tokens", type=int, default=160)
|
| 82 |
+
parser.add_argument("--full-crop-mode", action=argparse.BooleanOptionalAction, default=True)
|
| 83 |
+
args = parser.parse_args()
|
| 84 |
+
|
| 85 |
+
if args.output_dir:
|
| 86 |
+
output_dir = args.output_dir
|
| 87 |
+
else:
|
| 88 |
+
output_dir = os.path.join(os.path.dirname(args.model_path), "eval_results")
|
| 89 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 90 |
+
|
| 91 |
+
with open(args.format_prompt, encoding="utf-8") as f:
|
| 92 |
+
tmpl = Template(f.read().strip()).render()
|
| 93 |
+
sys_prompt = extract_prompt(tmpl, r'<sys_start>(.*?)<sys_end>')
|
| 94 |
+
user_prompt = extract_prompt(tmpl, r'<user_start>(.*?)<user_end>')
|
| 95 |
+
|
| 96 |
+
data = [json.loads(line) for line in open(args.eval_data)]
|
| 97 |
+
print(f"Eval samples: {len(data)}")
|
| 98 |
+
|
| 99 |
+
lora = is_lora_checkpoint(args.model_path)
|
| 100 |
+
if lora:
|
| 101 |
+
base_model = args.base_model
|
| 102 |
+
if base_model is None:
|
| 103 |
+
adapter_cfg = json.load(open(os.path.join(args.model_path, "adapter_config.json")))
|
| 104 |
+
base_model = adapter_cfg.get("base_model_name_or_path", None)
|
| 105 |
+
assert base_model, "LoRA checkpoint needs --base-model"
|
| 106 |
+
print(f"LoRA checkpoint detected, base={base_model}, lora={args.model_path}")
|
| 107 |
+
model = Qwen3_5ForConditionalGeneration.from_pretrained(
|
| 108 |
+
base_model, torch_dtype=torch.bfloat16, device_map="auto", trust_remote_code=True,
|
| 109 |
+
)
|
| 110 |
+
from peft import PeftModel
|
| 111 |
+
model = PeftModel.from_pretrained(model, args.model_path)
|
| 112 |
+
model = model.merge_and_unload()
|
| 113 |
+
processor = AutoProcessor.from_pretrained(base_model, trust_remote_code=True)
|
| 114 |
+
else:
|
| 115 |
+
print(f"Full checkpoint: {args.model_path}")
|
| 116 |
+
model = Qwen3_5ForConditionalGeneration.from_pretrained(
|
| 117 |
+
args.model_path, torch_dtype=torch.bfloat16, device_map="auto", trust_remote_code=True,
|
| 118 |
+
)
|
| 119 |
+
processor = AutoProcessor.from_pretrained(args.model_path, trust_remote_code=True)
|
| 120 |
+
|
| 121 |
+
model.eval()
|
| 122 |
+
|
| 123 |
+
target_tokens = ["0", "1", "2", "3"]
|
| 124 |
+
target_token_ids = [processor.tokenizer.encode(t, add_special_tokens=False)[0] for t in target_tokens]
|
| 125 |
+
print(f"Target token ids: {dict(zip(target_tokens, target_token_ids))}")
|
| 126 |
+
|
| 127 |
+
IMAGE_FACTOR = 32
|
| 128 |
+
max_crop_pixels = args.max_crop_tokens * IMAGE_FACTOR * IMAGE_FACTOR
|
| 129 |
+
max_full_pixels = args.max_full_tokens * IMAGE_FACTOR * IMAGE_FACTOR
|
| 130 |
+
|
| 131 |
+
metrics = defaultdict(list)
|
| 132 |
+
intent_metrics = defaultdict(lambda: defaultdict(list))
|
| 133 |
+
|
| 134 |
+
for idx, item in enumerate(tqdm(data, desc="Eval")):
|
| 135 |
+
labels = [doc['label'] for doc in item['docs']]
|
| 136 |
+
intent = item.get('intent', 'unknown')
|
| 137 |
+
num_docs = len(labels)
|
| 138 |
+
|
| 139 |
+
if args.full_crop_mode:
|
| 140 |
+
images = [
|
| 141 |
+
{'image': item['query_image'], 'max_pixels': max_full_pixels},
|
| 142 |
+
{'image': item['query_crop_image'], 'max_pixels': max_crop_pixels},
|
| 143 |
+
]
|
| 144 |
+
for doc in item['docs']:
|
| 145 |
+
images.append({'image': doc['doc_image'], 'max_pixels': max_full_pixels})
|
| 146 |
+
images.append({'image': doc['crop_image'], 'max_pixels': max_crop_pixels})
|
| 147 |
+
else:
|
| 148 |
+
images = [{'image': item['query_image'], 'max_pixels': max_crop_pixels}]
|
| 149 |
+
for doc in item['docs']:
|
| 150 |
+
images.append({'image': doc['crop_image'], 'max_pixels': max_crop_pixels})
|
| 151 |
+
|
| 152 |
+
titles = [doc['doc_title'] for doc in item['docs']]
|
| 153 |
+
messages = build_messages(sys_prompt, user_prompt, list(images), item.get('instruction', ''), list(titles))
|
| 154 |
+
|
| 155 |
+
text = processor.apply_chat_template(messages, tokenize=False, add_generation_prompt=True, enable_thinking=False)
|
| 156 |
+
image_inputs, _ = process_vision_info(messages)
|
| 157 |
+
|
| 158 |
+
inputs = processor(
|
| 159 |
+
text=[text],
|
| 160 |
+
images=image_inputs if image_inputs else None,
|
| 161 |
+
padding=True,
|
| 162 |
+
return_tensors="pt",
|
| 163 |
+
).to(model.device)
|
| 164 |
+
|
| 165 |
+
with torch.no_grad():
|
| 166 |
+
outputs = model.generate(
|
| 167 |
+
**inputs, max_new_tokens=args.max_tokens,
|
| 168 |
+
do_sample=False, return_dict_in_generate=True, output_scores=True,
|
| 169 |
+
)
|
| 170 |
+
|
| 171 |
+
prob_0_list = []
|
| 172 |
+
for pos in range(min(num_docs, len(outputs.scores))):
|
| 173 |
+
logits = outputs.scores[pos][0]
|
| 174 |
+
log_probs = torch.log_softmax(logits, dim=-1)
|
| 175 |
+
prob_0_list.append(log_probs[target_token_ids[0]].item())
|
| 176 |
+
|
| 177 |
+
sorted_indices = sorted(range(num_docs), key=lambda x: prob_0_list[x] if x < len(prob_0_list) else -100.0, reverse=True)
|
| 178 |
+
sorted_labels = [labels[i] for i in sorted_indices]
|
| 179 |
+
|
| 180 |
+
for N in [1, 3, 5]:
|
| 181 |
+
s = get_topn_strict_hitrate(sorted_labels, N)
|
| 182 |
+
f = get_topn_soft_hitrate(sorted_labels, N)
|
| 183 |
+
metrics[f'strict@{N}'].append(s)
|
| 184 |
+
metrics[f'soft@{N}'].append(f)
|
| 185 |
+
intent_metrics[intent][f'strict@{N}'].append(s)
|
| 186 |
+
intent_metrics[intent][f'soft@{N}'].append(f)
|
| 187 |
+
|
| 188 |
+
results = {
|
| 189 |
+
"model_path": args.model_path,
|
| 190 |
+
"eval_data": args.eval_data,
|
| 191 |
+
"num_samples": len(data),
|
| 192 |
+
"overall": {},
|
| 193 |
+
"per_intent": {},
|
| 194 |
+
}
|
| 195 |
+
|
| 196 |
+
metric_names = ['strict@1', 'strict@3', 'strict@5', 'soft@1', 'soft@3', 'soft@5']
|
| 197 |
+
|
| 198 |
+
print(f"\nModel: {args.model_path}")
|
| 199 |
+
print(f"Eval: {args.eval_data} ({len(data)} samples)")
|
| 200 |
+
|
| 201 |
+
for name in metric_names:
|
| 202 |
+
vals = metrics[name]
|
| 203 |
+
results["overall"][name] = round(sum(vals) / len(vals), 4) if vals else 0.0
|
| 204 |
+
|
| 205 |
+
header = "| {:^12} |".format("") + "".join(f" {n:^10} |" for n in metric_names)
|
| 206 |
+
sep = "|" + "-" * 14 + "|" + ("-" * 12 + "|") * len(metric_names)
|
| 207 |
+
row_overall = "| {:^12} |".format("Overall") + "".join(f" {results['overall'][n]:^10.4f} |" for n in metric_names)
|
| 208 |
+
|
| 209 |
+
print(f"\n{header}")
|
| 210 |
+
print(sep)
|
| 211 |
+
print(row_overall)
|
| 212 |
+
|
| 213 |
+
for intent in sorted(intent_metrics.keys()):
|
| 214 |
+
results["per_intent"][intent] = {}
|
| 215 |
+
for name in metric_names:
|
| 216 |
+
vals = intent_metrics[intent][name]
|
| 217 |
+
results["per_intent"][intent][name] = round(sum(vals) / len(vals), 4) if vals else 0.0
|
| 218 |
+
row = "| {:^12} |".format(intent) + "".join(f" {results['per_intent'][intent][n]:^10.4f} |" for n in metric_names)
|
| 219 |
+
print(row)
|
| 220 |
+
|
| 221 |
+
print(sep)
|
| 222 |
+
|
| 223 |
+
output_file = os.path.join(output_dir, "eval_results.json")
|
| 224 |
+
with open(output_file, "w") as f:
|
| 225 |
+
json.dump(results, f, ensure_ascii=False, indent=2)
|
| 226 |
+
print(f"\nResults saved to {output_file}")
|
| 227 |
+
|
| 228 |
+
|
| 229 |
+
if __name__ == "__main__":
|
| 230 |
+
main()
|
code/rerank_ins/eval/eval_reranker_qwen3vl.py
ADDED
|
@@ -0,0 +1,322 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Evaluate Qwen3-VL with the same data and scoring pipeline used during training."""
|
| 2 |
+
|
| 3 |
+
import argparse
|
| 4 |
+
import glob
|
| 5 |
+
import json
|
| 6 |
+
import os
|
| 7 |
+
import re
|
| 8 |
+
import sys
|
| 9 |
+
from collections import defaultdict
|
| 10 |
+
from datetime import timedelta
|
| 11 |
+
from types import SimpleNamespace
|
| 12 |
+
|
| 13 |
+
current_file_path = os.path.dirname(os.path.abspath(__file__))
|
| 14 |
+
module_path = os.path.join(current_file_path, "../")
|
| 15 |
+
sys.path.append(module_path)
|
| 16 |
+
|
| 17 |
+
METRIC_NAMES = ['strict@1', 'strict@3', 'strict@5', 'soft@1', 'soft@3', 'soft@5']
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def get_dist_env():
|
| 21 |
+
rank = int(os.environ.get("RANK", 0))
|
| 22 |
+
world_size = int(os.environ.get("WORLD_SIZE", 1))
|
| 23 |
+
local_rank = int(os.environ.get("LOCAL_RANK", rank))
|
| 24 |
+
return rank, world_size, local_rank
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def bind_single_gpu(local_rank):
|
| 28 |
+
"""分片模式下每个进程只使用一张 GPU。"""
|
| 29 |
+
visible = os.environ.get("CUDA_VISIBLE_DEVICES")
|
| 30 |
+
if visible:
|
| 31 |
+
gpu_ids = [x for x in visible.split(",") if x]
|
| 32 |
+
else:
|
| 33 |
+
gpu_ids = [str(i) for i in sorted(int(re.findall(r"\d+$", p)[0]) for p in glob.glob("/dev/nvidia[0-9]*"))]
|
| 34 |
+
if len(gpu_ids) > 1:
|
| 35 |
+
os.environ["CUDA_VISIBLE_DEVICES"] = gpu_ids[local_rank % len(gpu_ids)]
|
| 36 |
+
print(f"[rank {local_rank}] CUDA_VISIBLE_DEVICES={os.environ.get('CUDA_VISIBLE_DEVICES')}")
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def gather_results(payload, rank, world_size):
|
| 40 |
+
import torch.distributed as dist
|
| 41 |
+
|
| 42 |
+
master_addr = os.environ.get("MASTER_ADDR", "127.0.0.1")
|
| 43 |
+
master_port = int(os.environ.get("MASTER_PORT", 23456)) + 1
|
| 44 |
+
dist.init_process_group(
|
| 45 |
+
backend="gloo",
|
| 46 |
+
init_method=f"tcp://{master_addr}:{master_port}",
|
| 47 |
+
rank=rank,
|
| 48 |
+
world_size=world_size,
|
| 49 |
+
timeout=timedelta(hours=2),
|
| 50 |
+
)
|
| 51 |
+
gathered = [None] * world_size
|
| 52 |
+
dist.all_gather_object(gathered, payload)
|
| 53 |
+
dist.destroy_process_group()
|
| 54 |
+
records = [record for shard, _, _ in gathered for record in shard]
|
| 55 |
+
total_loss = sum(loss_sum for _, loss_sum, _ in gathered)
|
| 56 |
+
total_batches = sum(batch_count for _, _, batch_count in gathered)
|
| 57 |
+
return records, total_loss, total_batches
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def is_lora_checkpoint(model_path):
|
| 61 |
+
return os.path.exists(os.path.join(model_path, "adapter_config.json"))
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def validate_data(data, num_docs):
|
| 65 |
+
for index, item in enumerate(data):
|
| 66 |
+
actual = len(item.get("docs", []))
|
| 67 |
+
if actual != num_docs:
|
| 68 |
+
raise ValueError(
|
| 69 |
+
f"Sample {index} has {actual} docs; training evaluation requires exactly {num_docs}"
|
| 70 |
+
)
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
def get_topn_strict_hitrate(labels, N):
|
| 74 |
+
gt_num = list(labels).count(0)
|
| 75 |
+
hit = sum(1.0 for i in range(min(N, gt_num)) if labels[i] == 0)
|
| 76 |
+
return hit / min(N, gt_num) if gt_num > 0 and N > 0 else 0.0
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
def get_topn_soft_hitrate(labels, N):
|
| 80 |
+
gt_num = list(labels).count(0) + list(labels).count(1)
|
| 81 |
+
hit = sum(1.0 for i in range(min(N, gt_num)) if labels[i] in [0, 1])
|
| 82 |
+
return hit / min(N, gt_num) if gt_num > 0 and N > 0 else 0.0
|
| 83 |
+
|
| 84 |
+
|
| 85 |
+
def load_model(args):
|
| 86 |
+
import torch
|
| 87 |
+
from loaders.qwen3_vl import Qwen3VLModelLoader
|
| 88 |
+
|
| 89 |
+
lora_path = args.model_path if is_lora_checkpoint(args.model_path) else None
|
| 90 |
+
model_source = args.base_model if lora_path else args.model_path
|
| 91 |
+
if lora_path and not model_source:
|
| 92 |
+
with open(os.path.join(lora_path, "adapter_config.json"), encoding="utf-8") as f:
|
| 93 |
+
model_source = json.load(f).get("base_model_name_or_path")
|
| 94 |
+
if not model_source:
|
| 95 |
+
raise ValueError("LoRA checkpoint requires --base-model")
|
| 96 |
+
|
| 97 |
+
loader = Qwen3VLModelLoader(
|
| 98 |
+
model_hf_path=model_source,
|
| 99 |
+
model_local_path=model_source,
|
| 100 |
+
compute_dtype=torch.bfloat16,
|
| 101 |
+
use_flash_attn=args.use_flash_attn,
|
| 102 |
+
device_map={"": 0},
|
| 103 |
+
)
|
| 104 |
+
model, tokenizer, processor = loader.load()
|
| 105 |
+
if lora_path:
|
| 106 |
+
from peft import PeftModel
|
| 107 |
+
|
| 108 |
+
print(f"LoRA checkpoint: base={model_source}, adapter={lora_path}")
|
| 109 |
+
model = PeftModel.from_pretrained(model, lora_path)
|
| 110 |
+
else:
|
| 111 |
+
print(f"Full checkpoint: {model_source}")
|
| 112 |
+
|
| 113 |
+
model.config.use_cache = False
|
| 114 |
+
model.eval()
|
| 115 |
+
tokenizer.model_max_length = args.model_max_length
|
| 116 |
+
return model, tokenizer, processor
|
| 117 |
+
|
| 118 |
+
|
| 119 |
+
def build_dataloaders(args, tokenizer, processor, patch_size, data):
|
| 120 |
+
from torch.utils.data import DataLoader
|
| 121 |
+
from collators.qwen_vl_rerank import TrainRerankDataCollator, EvalRerankParallelDataCollator
|
| 122 |
+
from dataset.dataset_listwise import TongkuanListWiseSFTParallelDataset
|
| 123 |
+
|
| 124 |
+
data_args = SimpleNamespace(
|
| 125 |
+
full_crop_mode=args.full_crop_mode,
|
| 126 |
+
format_prompt=args.format_prompt,
|
| 127 |
+
max_full_tokens=args.max_full_tokens,
|
| 128 |
+
max_crop_tokens=args.max_crop_tokens,
|
| 129 |
+
patch_size=patch_size,
|
| 130 |
+
class_mode=0,
|
| 131 |
+
num_train_docs=args.num_docs,
|
| 132 |
+
)
|
| 133 |
+
eval_dataset = TongkuanListWiseSFTParallelDataset(data_args, data, train=False)
|
| 134 |
+
loss_dataset = TongkuanListWiseSFTParallelDataset(data_args, data, train=True)
|
| 135 |
+
eval_loader = DataLoader(
|
| 136 |
+
eval_dataset,
|
| 137 |
+
batch_size=args.batch_size,
|
| 138 |
+
shuffle=False,
|
| 139 |
+
collate_fn=EvalRerankParallelDataCollator(tokenizer=tokenizer, processor=processor),
|
| 140 |
+
num_workers=args.dataloader_num_workers,
|
| 141 |
+
pin_memory=True,
|
| 142 |
+
)
|
| 143 |
+
loss_loader = DataLoader(
|
| 144 |
+
loss_dataset,
|
| 145 |
+
batch_size=args.loss_batch_size,
|
| 146 |
+
shuffle=False,
|
| 147 |
+
collate_fn=TrainRerankDataCollator(tokenizer=tokenizer, processor=processor),
|
| 148 |
+
num_workers=args.dataloader_num_workers,
|
| 149 |
+
pin_memory=True,
|
| 150 |
+
)
|
| 151 |
+
return eval_loader, loss_loader
|
| 152 |
+
|
| 153 |
+
|
| 154 |
+
def move_to_device(batch, device):
|
| 155 |
+
return {key: value.to(device) for key, value in batch.items() if value is not None}
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
def run_evaluation(args, model, tokenizer, eval_loader, loss_loader):
|
| 159 |
+
import torch
|
| 160 |
+
|
| 161 |
+
device = next(model.parameters()).device
|
| 162 |
+
target_tokens = ["0", "1", "2", "3"]
|
| 163 |
+
target_token_ids = [tokenizer.encode(token, add_special_tokens=False)[0] for token in target_tokens]
|
| 164 |
+
print(f"Target token ids: {dict(zip(target_tokens, target_token_ids))}")
|
| 165 |
+
|
| 166 |
+
total_loss = 0.0
|
| 167 |
+
total_batches = 0
|
| 168 |
+
with torch.inference_mode():
|
| 169 |
+
for batch in loss_loader:
|
| 170 |
+
outputs = model(**move_to_device(batch, device))
|
| 171 |
+
total_loss += outputs.loss.item()
|
| 172 |
+
total_batches += 1
|
| 173 |
+
|
| 174 |
+
records = []
|
| 175 |
+
with torch.inference_mode():
|
| 176 |
+
for inputs, items in eval_loader:
|
| 177 |
+
outputs = model.generate(
|
| 178 |
+
**move_to_device(inputs, device),
|
| 179 |
+
max_new_tokens=args.num_docs,
|
| 180 |
+
output_scores=True,
|
| 181 |
+
return_dict_in_generate=True,
|
| 182 |
+
do_sample=False,
|
| 183 |
+
)
|
| 184 |
+
if len(outputs.scores) != args.num_docs:
|
| 185 |
+
raise RuntimeError(
|
| 186 |
+
f"Model generated {len(outputs.scores)} score steps; expected {args.num_docs}"
|
| 187 |
+
)
|
| 188 |
+
|
| 189 |
+
for batch_index, item in enumerate(items):
|
| 190 |
+
labels = item["labels"]
|
| 191 |
+
prob_0_list = []
|
| 192 |
+
for position in range(args.num_docs):
|
| 193 |
+
logits = outputs.scores[position][batch_index]
|
| 194 |
+
score_values = torch.tensor(
|
| 195 |
+
[logits[token_id].item() for token_id in target_token_ids],
|
| 196 |
+
dtype=torch.float32,
|
| 197 |
+
)
|
| 198 |
+
prob_0_list.append(torch.softmax(score_values, dim=0)[0].item())
|
| 199 |
+
|
| 200 |
+
sorted_indices = sorted(
|
| 201 |
+
range(args.num_docs), key=lambda index: prob_0_list[index], reverse=True
|
| 202 |
+
)
|
| 203 |
+
sorted_labels = [labels[index] for index in sorted_indices]
|
| 204 |
+
record = {"intent": item.get("intent", "unknown")}
|
| 205 |
+
for n in [1, 3, 5]:
|
| 206 |
+
record[f"strict@{n}"] = get_topn_strict_hitrate(sorted_labels, n)
|
| 207 |
+
record[f"soft@{n}"] = get_topn_soft_hitrate(sorted_labels, n)
|
| 208 |
+
records.append(record)
|
| 209 |
+
|
| 210 |
+
return records, total_loss, total_batches
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
def report(args, records, avg_eval_loss, output_dir):
|
| 214 |
+
metrics = defaultdict(list)
|
| 215 |
+
intent_metrics = defaultdict(lambda: defaultdict(list))
|
| 216 |
+
for record in records:
|
| 217 |
+
for name in METRIC_NAMES:
|
| 218 |
+
metrics[name].append(record[name])
|
| 219 |
+
intent_metrics[record["intent"]][name].append(record[name])
|
| 220 |
+
|
| 221 |
+
results = {
|
| 222 |
+
"model_path": args.model_path,
|
| 223 |
+
"eval_data": args.eval_data,
|
| 224 |
+
"num_samples": len(records),
|
| 225 |
+
"eval_loss": round(avg_eval_loss, 4),
|
| 226 |
+
"overall": {},
|
| 227 |
+
"per_intent": {},
|
| 228 |
+
}
|
| 229 |
+
|
| 230 |
+
print(f"\nModel: {args.model_path}")
|
| 231 |
+
print(f"Eval: {args.eval_data} ({len(records)} samples)")
|
| 232 |
+
print(f"Loss: {avg_eval_loss:.4f}")
|
| 233 |
+
|
| 234 |
+
for name in METRIC_NAMES:
|
| 235 |
+
vals = metrics[name]
|
| 236 |
+
results["overall"][name] = round(sum(vals) / len(vals), 4) if vals else 0.0
|
| 237 |
+
|
| 238 |
+
header = "| {:^12} |".format("") + "".join(f" {n:^10} |" for n in METRIC_NAMES)
|
| 239 |
+
sep = "|" + "-" * 14 + "|" + ("-" * 12 + "|") * len(METRIC_NAMES)
|
| 240 |
+
row_overall = "| {:^12} |".format("Overall") + "".join(f" {results['overall'][n]:^10.4f} |" for n in METRIC_NAMES)
|
| 241 |
+
|
| 242 |
+
print(f"\n{header}")
|
| 243 |
+
print(sep)
|
| 244 |
+
print(row_overall)
|
| 245 |
+
|
| 246 |
+
for intent in sorted(intent_metrics.keys()):
|
| 247 |
+
results["per_intent"][intent] = {}
|
| 248 |
+
for name in METRIC_NAMES:
|
| 249 |
+
vals = intent_metrics[intent][name]
|
| 250 |
+
results["per_intent"][intent][name] = round(sum(vals) / len(vals), 4) if vals else 0.0
|
| 251 |
+
row = "| {:^12} |".format(intent) + "".join(f" {results['per_intent'][intent][n]:^10.4f} |" for n in METRIC_NAMES)
|
| 252 |
+
print(row)
|
| 253 |
+
|
| 254 |
+
print(sep)
|
| 255 |
+
|
| 256 |
+
output_file = os.path.join(output_dir, "eval_results.json")
|
| 257 |
+
with open(output_file, "w", encoding="utf-8") as f:
|
| 258 |
+
json.dump(results, f, ensure_ascii=False, indent=2)
|
| 259 |
+
print(f"\nResults saved to {output_file}")
|
| 260 |
+
|
| 261 |
+
|
| 262 |
+
def main():
|
| 263 |
+
parser = argparse.ArgumentParser(description="Evaluate Qwen3-VL with the training pipeline")
|
| 264 |
+
parser.add_argument("--model-path", required=True, help="checkpoint path (LoRA or full)")
|
| 265 |
+
parser.add_argument("--base-model", default=None, help="base model path for LoRA")
|
| 266 |
+
parser.add_argument("--eval-data", required=True, help="evaluation JSONL path")
|
| 267 |
+
parser.add_argument(
|
| 268 |
+
"--format-prompt",
|
| 269 |
+
default=os.path.join(module_path, "format_prompt/listwise_reranker_v1.jinja"),
|
| 270 |
+
)
|
| 271 |
+
parser.add_argument("--output-dir", default=None)
|
| 272 |
+
parser.add_argument("--num-docs", type=int, default=10)
|
| 273 |
+
parser.add_argument("--batch-size", type=int, default=2)
|
| 274 |
+
parser.add_argument("--loss-batch-size", type=int, default=4)
|
| 275 |
+
parser.add_argument("--dataloader-num-workers", type=int, default=8)
|
| 276 |
+
parser.add_argument("--model-max-length", type=int, default=8192)
|
| 277 |
+
parser.add_argument("--max-crop-tokens", type=int, default=80)
|
| 278 |
+
parser.add_argument("--max-full-tokens", type=int, default=20)
|
| 279 |
+
parser.add_argument("--full-crop-mode", action=argparse.BooleanOptionalAction, default=False)
|
| 280 |
+
parser.add_argument("--use-flash-attn", action=argparse.BooleanOptionalAction, default=True)
|
| 281 |
+
args, unknown = parser.parse_known_args()
|
| 282 |
+
if unknown:
|
| 283 |
+
print(f"Ignoring unknown args: {unknown}")
|
| 284 |
+
|
| 285 |
+
rank, world_size, local_rank = get_dist_env()
|
| 286 |
+
sharded = world_size > 1
|
| 287 |
+
if sharded:
|
| 288 |
+
bind_single_gpu(local_rank)
|
| 289 |
+
print(f"Sharded evaluation: rank {rank}/{world_size}")
|
| 290 |
+
|
| 291 |
+
output_dir = args.output_dir or os.path.join(os.path.dirname(args.model_path), "eval_results")
|
| 292 |
+
if rank == 0:
|
| 293 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 294 |
+
|
| 295 |
+
with open(args.eval_data, encoding="utf-8") as f:
|
| 296 |
+
all_data = [json.loads(line) for line in f]
|
| 297 |
+
validate_data(all_data, args.num_docs)
|
| 298 |
+
data = all_data[rank::world_size] if sharded else all_data
|
| 299 |
+
print(f"Eval samples: {len(data)} (total {len(all_data)})")
|
| 300 |
+
|
| 301 |
+
model, tokenizer, processor = load_model(args)
|
| 302 |
+
eval_loader, loss_loader = build_dataloaders(
|
| 303 |
+
args, tokenizer, processor, model.config.vision_config.patch_size, data
|
| 304 |
+
)
|
| 305 |
+
records, total_loss, total_batches = run_evaluation(
|
| 306 |
+
args, model, tokenizer, eval_loader, loss_loader
|
| 307 |
+
)
|
| 308 |
+
|
| 309 |
+
if sharded:
|
| 310 |
+
records, total_loss, total_batches = gather_results(
|
| 311 |
+
(records, total_loss, total_batches), rank, world_size
|
| 312 |
+
)
|
| 313 |
+
if rank != 0:
|
| 314 |
+
print(f"[rank {rank}] shard finished")
|
| 315 |
+
return
|
| 316 |
+
|
| 317 |
+
avg_eval_loss = total_loss / total_batches if total_batches else 0.0
|
| 318 |
+
report(args, records, avg_eval_loss, output_dir)
|
| 319 |
+
|
| 320 |
+
|
| 321 |
+
if __name__ == "__main__":
|
| 322 |
+
main()
|
code/rerank_ins/eval/eval_reranker_qwen3vl_cot.py
ADDED
|
@@ -0,0 +1,241 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""Evaluate a Qwen3-VL CoT reranker with generation-based scoring."""
|
| 2 |
+
|
| 3 |
+
import argparse
|
| 4 |
+
import json
|
| 5 |
+
import os
|
| 6 |
+
import sys
|
| 7 |
+
from collections import defaultdict
|
| 8 |
+
from types import SimpleNamespace
|
| 9 |
+
|
| 10 |
+
current_file_path = os.path.dirname(os.path.abspath(__file__))
|
| 11 |
+
module_path = os.path.join(current_file_path, "../")
|
| 12 |
+
sys.path.append(module_path)
|
| 13 |
+
sys.path.append(current_file_path)
|
| 14 |
+
|
| 15 |
+
from eval_reranker_qwen3vl import (
|
| 16 |
+
bind_single_gpu,
|
| 17 |
+
gather_results,
|
| 18 |
+
get_dist_env,
|
| 19 |
+
get_topn_soft_hitrate,
|
| 20 |
+
get_topn_strict_hitrate,
|
| 21 |
+
load_model,
|
| 22 |
+
move_to_device,
|
| 23 |
+
validate_data,
|
| 24 |
+
)
|
| 25 |
+
from train_cot.cot_eval_utils import parse_final_answer
|
| 26 |
+
|
| 27 |
+
METRIC_NAMES = [
|
| 28 |
+
"strict@1",
|
| 29 |
+
"strict@3",
|
| 30 |
+
"strict@5",
|
| 31 |
+
"soft@1",
|
| 32 |
+
"soft@3",
|
| 33 |
+
"soft@5",
|
| 34 |
+
"answer_acc",
|
| 35 |
+
"parse_success",
|
| 36 |
+
]
|
| 37 |
+
|
| 38 |
+
|
| 39 |
+
def validate_labels(data):
|
| 40 |
+
for sample_index, item in enumerate(data):
|
| 41 |
+
for doc_index, doc in enumerate(item["docs"]):
|
| 42 |
+
if doc.get("label") not in [0, 1, 2, 3]:
|
| 43 |
+
raise ValueError(
|
| 44 |
+
f"Sample {sample_index} doc {doc_index} has invalid label: {doc.get('label')}"
|
| 45 |
+
)
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def build_dataloader(args, tokenizer, processor, patch_size, data):
|
| 49 |
+
from torch.utils.data import DataLoader
|
| 50 |
+
|
| 51 |
+
from collators.qwen_vl_rerank import EvalRerankParallelDataCollator
|
| 52 |
+
from train_cot.dataset_listwise_cot import TongkuanListWiseCotSFTParallelDataset
|
| 53 |
+
|
| 54 |
+
data_args = SimpleNamespace(
|
| 55 |
+
full_crop_mode=args.full_crop_mode,
|
| 56 |
+
format_prompt=args.format_prompt,
|
| 57 |
+
max_full_tokens=args.max_full_tokens,
|
| 58 |
+
max_crop_tokens=args.max_crop_tokens,
|
| 59 |
+
patch_size=patch_size,
|
| 60 |
+
class_mode=0,
|
| 61 |
+
num_train_docs=args.num_docs,
|
| 62 |
+
)
|
| 63 |
+
eval_dataset = TongkuanListWiseCotSFTParallelDataset(data_args, data, train=False)
|
| 64 |
+
return DataLoader(
|
| 65 |
+
eval_dataset,
|
| 66 |
+
batch_size=args.batch_size,
|
| 67 |
+
shuffle=False,
|
| 68 |
+
collate_fn=EvalRerankParallelDataCollator(tokenizer=tokenizer, processor=processor),
|
| 69 |
+
num_workers=args.dataloader_num_workers,
|
| 70 |
+
pin_memory=True,
|
| 71 |
+
)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def run_evaluation(args, model, tokenizer, eval_loader):
|
| 75 |
+
import torch
|
| 76 |
+
|
| 77 |
+
device = next(model.parameters()).device
|
| 78 |
+
records = []
|
| 79 |
+
with torch.inference_mode():
|
| 80 |
+
for inputs, items in eval_loader:
|
| 81 |
+
generation_inputs = move_to_device(inputs, device)
|
| 82 |
+
generation_inputs.pop("labels", None)
|
| 83 |
+
output_ids = model.generate(
|
| 84 |
+
**generation_inputs,
|
| 85 |
+
max_new_tokens=args.max_new_tokens,
|
| 86 |
+
do_sample=False,
|
| 87 |
+
)
|
| 88 |
+
input_length = generation_inputs["input_ids"].shape[1]
|
| 89 |
+
generated_texts = tokenizer.batch_decode(
|
| 90 |
+
output_ids[:, input_length:], skip_special_tokens=True
|
| 91 |
+
)
|
| 92 |
+
|
| 93 |
+
for text, item in zip(generated_texts, items):
|
| 94 |
+
labels = item["labels"]
|
| 95 |
+
gt_answer = "".join(str(label) for label in labels)
|
| 96 |
+
pred_answer = parse_final_answer(text, len(labels))
|
| 97 |
+
parsed = pred_answer is not None
|
| 98 |
+
pred_label_values = (
|
| 99 |
+
[int(char) for char in pred_answer]
|
| 100 |
+
if parsed
|
| 101 |
+
else [3] * len(labels)
|
| 102 |
+
)
|
| 103 |
+
sorted_indices = sorted(
|
| 104 |
+
range(len(labels)), key=lambda index: (pred_label_values[index], index)
|
| 105 |
+
)
|
| 106 |
+
sorted_labels = [labels[index] for index in sorted_indices]
|
| 107 |
+
|
| 108 |
+
record = {
|
| 109 |
+
"eval_index": item["_eval_index"],
|
| 110 |
+
"sample_idx": item.get("sample_idx", item["_eval_index"]),
|
| 111 |
+
"intent": item.get("intent", "unknown"),
|
| 112 |
+
"gt_answer": gt_answer,
|
| 113 |
+
"pred_answer": pred_answer,
|
| 114 |
+
"parse_success": float(parsed),
|
| 115 |
+
"answer_acc": float(pred_answer == gt_answer),
|
| 116 |
+
"predicted_labels": pred_label_values,
|
| 117 |
+
"sorted_indices": sorted_indices,
|
| 118 |
+
"generated_text": text,
|
| 119 |
+
}
|
| 120 |
+
for n in [1, 3, 5]:
|
| 121 |
+
record[f"strict@{n}"] = get_topn_strict_hitrate(sorted_labels, n)
|
| 122 |
+
record[f"soft@{n}"] = get_topn_soft_hitrate(sorted_labels, n)
|
| 123 |
+
records.append(record)
|
| 124 |
+
|
| 125 |
+
return records
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
def mean(values):
|
| 129 |
+
return sum(values) / len(values) if values else 0.0
|
| 130 |
+
|
| 131 |
+
|
| 132 |
+
def report(args, records, output_dir):
|
| 133 |
+
records.sort(key=lambda record: record["eval_index"])
|
| 134 |
+
metrics = defaultdict(list)
|
| 135 |
+
intent_metrics = defaultdict(lambda: defaultdict(list))
|
| 136 |
+
for record in records:
|
| 137 |
+
for name in METRIC_NAMES:
|
| 138 |
+
metrics[name].append(record[name])
|
| 139 |
+
intent_metrics[record["intent"]][name].append(record[name])
|
| 140 |
+
|
| 141 |
+
results = {
|
| 142 |
+
"model_path": args.model_path,
|
| 143 |
+
"eval_data": args.eval_data,
|
| 144 |
+
"num_samples": len(records),
|
| 145 |
+
"eval_loss": None,
|
| 146 |
+
"num_parsed": int(sum(metrics["parse_success"])),
|
| 147 |
+
"num_parse_failed": len(records) - int(sum(metrics["parse_success"])),
|
| 148 |
+
"overall": {
|
| 149 |
+
name: round(mean(metrics[name]), 4) for name in METRIC_NAMES
|
| 150 |
+
},
|
| 151 |
+
"per_intent": {},
|
| 152 |
+
}
|
| 153 |
+
|
| 154 |
+
for intent in sorted(intent_metrics):
|
| 155 |
+
results["per_intent"][intent] = {
|
| 156 |
+
"num_samples": len(intent_metrics[intent]["parse_success"]),
|
| 157 |
+
**{
|
| 158 |
+
name: round(mean(intent_metrics[intent][name]), 4)
|
| 159 |
+
for name in METRIC_NAMES
|
| 160 |
+
},
|
| 161 |
+
}
|
| 162 |
+
|
| 163 |
+
print(f"\nModel: {args.model_path}")
|
| 164 |
+
print(f"Eval: {args.eval_data} ({len(records)} samples)")
|
| 165 |
+
print(f"Parsed: {results['num_parsed']}/{len(records)}")
|
| 166 |
+
for name in METRIC_NAMES:
|
| 167 |
+
print(f"{name}: {results['overall'][name]:.4f}")
|
| 168 |
+
|
| 169 |
+
checkpoint_name = os.path.basename(os.path.normpath(args.model_path)).replace(
|
| 170 |
+
"checkpoint-", "ckpt_"
|
| 171 |
+
)
|
| 172 |
+
metrics_path = os.path.join(output_dir, f"{checkpoint_name}_metrics.json")
|
| 173 |
+
with open(metrics_path, "w", encoding="utf-8") as file:
|
| 174 |
+
json.dump(results, file, ensure_ascii=False, indent=2)
|
| 175 |
+
|
| 176 |
+
results_path = os.path.join(output_dir, f"{checkpoint_name}_results.json")
|
| 177 |
+
with open(results_path, "w", encoding="utf-8") as file:
|
| 178 |
+
json.dump(records, file, ensure_ascii=False, indent=2)
|
| 179 |
+
|
| 180 |
+
print(f"Metrics saved to {metrics_path}")
|
| 181 |
+
print(f"Results saved to {results_path}")
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
def main():
|
| 185 |
+
parser = argparse.ArgumentParser(description="Evaluate a Qwen3-VL CoT reranker")
|
| 186 |
+
parser.add_argument("--model-path", required=True, help="checkpoint path (LoRA or full)")
|
| 187 |
+
parser.add_argument("--base-model", default=None, help="base model path for LoRA")
|
| 188 |
+
parser.add_argument("--eval-data", required=True, help="evaluation JSONL path")
|
| 189 |
+
parser.add_argument(
|
| 190 |
+
"--format-prompt",
|
| 191 |
+
default=os.path.join(module_path, "train_cot/listwise_cot_reranker_v1.jinja"),
|
| 192 |
+
)
|
| 193 |
+
parser.add_argument("--output-dir", default=None)
|
| 194 |
+
parser.add_argument("--num-docs", type=int, default=10)
|
| 195 |
+
parser.add_argument("--batch-size", type=int, default=2)
|
| 196 |
+
parser.add_argument("--dataloader-num-workers", type=int, default=8)
|
| 197 |
+
parser.add_argument("--model-max-length", type=int, default=10240)
|
| 198 |
+
parser.add_argument("--max-new-tokens", type=int, default=2048)
|
| 199 |
+
parser.add_argument("--max-crop-tokens", type=int, default=80)
|
| 200 |
+
parser.add_argument("--max-full-tokens", type=int, default=20)
|
| 201 |
+
parser.add_argument("--full-crop-mode", action=argparse.BooleanOptionalAction, default=False)
|
| 202 |
+
parser.add_argument("--use-flash-attn", action=argparse.BooleanOptionalAction, default=True)
|
| 203 |
+
args = parser.parse_args()
|
| 204 |
+
|
| 205 |
+
rank, world_size, local_rank = get_dist_env()
|
| 206 |
+
sharded = world_size > 1
|
| 207 |
+
if sharded:
|
| 208 |
+
bind_single_gpu(local_rank)
|
| 209 |
+
print(f"Sharded evaluation: rank {rank}/{world_size}")
|
| 210 |
+
|
| 211 |
+
output_dir = args.output_dir or os.path.join(args.model_path, "eval_results_cot")
|
| 212 |
+
if rank == 0:
|
| 213 |
+
os.makedirs(output_dir, exist_ok=True)
|
| 214 |
+
|
| 215 |
+
with open(args.eval_data, encoding="utf-8") as file:
|
| 216 |
+
all_data = [json.loads(line) for line in file]
|
| 217 |
+
validate_data(all_data, args.num_docs)
|
| 218 |
+
validate_labels(all_data)
|
| 219 |
+
for index, item in enumerate(all_data):
|
| 220 |
+
item["_eval_index"] = index
|
| 221 |
+
data = all_data[rank::world_size] if sharded else all_data
|
| 222 |
+
print(f"Eval samples: {len(data)} (total {len(all_data)})")
|
| 223 |
+
|
| 224 |
+
model, tokenizer, processor = load_model(args)
|
| 225 |
+
model.config.use_cache = True
|
| 226 |
+
eval_loader = build_dataloader(
|
| 227 |
+
args, tokenizer, processor, model.config.vision_config.patch_size, data
|
| 228 |
+
)
|
| 229 |
+
records = run_evaluation(args, model, tokenizer, eval_loader)
|
| 230 |
+
|
| 231 |
+
if sharded:
|
| 232 |
+
records, _, _ = gather_results((records, 0.0, 0), rank, world_size)
|
| 233 |
+
if rank != 0:
|
| 234 |
+
print(f"[rank {rank}] shard finished")
|
| 235 |
+
return
|
| 236 |
+
|
| 237 |
+
report(args, records, output_dir)
|
| 238 |
+
|
| 239 |
+
|
| 240 |
+
if __name__ == "__main__":
|
| 241 |
+
main()
|
code/rerank_ins/format_prompt/listwise_reranker_v1.jinja
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<sys_start>
|
| 2 |
+
## 角色
|
| 3 |
+
你是电商图搜相关性判定模型。根据用户的搜索意图(instruction),判断每个候选商品与查询图像的匹配程度,输出标签 0–3。
|
| 4 |
+
|
| 5 |
+
## 标签定义
|
| 6 |
+
- **0 绝对正例**:品类正确,完全满足用户意图的关键条件
|
| 7 |
+
- **1 相对正例**:品类正确,基本满足或边界模糊、不一眼违背
|
| 8 |
+
- **2 违约负例**:品类正确或很像,但一眼看出违背关键条件(搭配意图下返回图的同品类也算违约负)
|
| 9 |
+
- **3 弱负例**:品类不对,与用户需求无关
|
| 10 |
+
|
| 11 |
+
> 违约负 ≠ 弱负:违约负是"对的品类但违反约束",弱负是"品类就不对"。
|
| 12 |
+
|
| 13 |
+
## 任务
|
| 14 |
+
1. 输入:1 张查询图像 + 用户 instruction + 10 张候选商品图像及标题
|
| 15 |
+
2. 依次判定每个候选商品(0–9)与查询图的匹配程度
|
| 16 |
+
3. 输出 10 个数字,按候选顺序拼接,每个数字为 0/1/2/3
|
| 17 |
+
- 输出示例:0123012301
|
| 18 |
+
<sys_end>
|
| 19 |
+
|
| 20 |
+
<user_start>
|
| 21 |
+
查询图像:<image>
|
| 22 |
+
用户意图:<instruction>
|
| 23 |
+
候选商品:
|
| 24 |
+
0: <image> <title>
|
| 25 |
+
1: <image> <title>
|
| 26 |
+
2: <image> <title>
|
| 27 |
+
3: <image> <title>
|
| 28 |
+
4: <image> <title>
|
| 29 |
+
5: <image> <title>
|
| 30 |
+
6: <image> <title>
|
| 31 |
+
7: <image> <title>
|
| 32 |
+
8: <image> <title>
|
| 33 |
+
9: <image> <title>
|
| 34 |
+
<user_end>
|
code/rerank_ins/format_prompt/listwise_reranker_v2.jinja
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
<sys_start>
|
| 2 |
+
## 角色
|
| 3 |
+
你是电商图搜相关性判定模型。根据用户的搜索意图(instruction),判断每个候选商品与查询图像的匹配程度,输出标签 0–3。
|
| 4 |
+
|
| 5 |
+
## 标签定义
|
| 6 |
+
- **0 绝对正例**:品类正确,完全满足用户意图的关键条件
|
| 7 |
+
- **1 相对正例**:品类正确,基本满足或边界模糊、不一眼违背
|
| 8 |
+
- **2 违约负例**:品类正确或很像,但一眼看出违背关键条件(搭配意图下返回图的同品类也算违约负)
|
| 9 |
+
- **3 弱负例**:品类不对,与用户需求无关
|
| 10 |
+
|
| 11 |
+
> 违约负 ≠ 弱负:违约负是"对的品类但违反约束",弱负是"品类就不对"。
|
| 12 |
+
|
| 13 |
+
## 任务
|
| 14 |
+
1. 输入:1 张查询原图 + 1 张查询裁剪图 + 用户 instruction + 10 张候选商品原图及裁剪图及标题
|
| 15 |
+
2. 依次判定每个候选商品(0–9)与查询图的匹配程度
|
| 16 |
+
3. 输出 10 个数字,按候选顺序拼接,每个数字为 0/1/2/3
|
| 17 |
+
- 输出示例:0123012301
|
| 18 |
+
<sys_end>
|
| 19 |
+
|
| 20 |
+
<user_start>
|
| 21 |
+
查询图像:<image> <image>
|
| 22 |
+
用户意图:<instruction>
|
| 23 |
+
候选商品:
|
| 24 |
+
0: <image> <image> <title>
|
| 25 |
+
1: <image> <image> <title>
|
| 26 |
+
2: <image> <image> <title>
|
| 27 |
+
3: <image> <image> <title>
|
| 28 |
+
4: <image> <image> <title>
|
| 29 |
+
5: <image> <image> <title>
|
| 30 |
+
6: <image> <image> <title>
|
| 31 |
+
7: <image> <image> <title>
|
| 32 |
+
8: <image> <image> <title>
|
| 33 |
+
9: <image> <image> <title>
|
| 34 |
+
<user_end>
|
code/rerank_ins/loaders/__init__.py
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
LOADERS = {}
|
| 2 |
+
|
| 3 |
+
def register_loader(name):
|
| 4 |
+
def register_loader_cls(cls):
|
| 5 |
+
if name in LOADERS:
|
| 6 |
+
return LOADERS[name]
|
| 7 |
+
LOADERS[name] = cls
|
| 8 |
+
return cls
|
| 9 |
+
return register_loader_cls
|
| 10 |
+
|
| 11 |
+
from .qwen3_vl import Qwen3VLModelLoader
|
| 12 |
+
from .qwen3_5 import Qwen3_5ModelLoader
|
code/rerank_ins/loaders/__pycache__/__init__.cpython-313.pyc
ADDED
|
Binary file (666 Bytes). View file
|
|
|
code/rerank_ins/loaders/__pycache__/__init__.cpython-314.pyc
ADDED
|
Binary file (635 Bytes). View file
|
|
|
code/rerank_ins/loaders/__pycache__/base.cpython-313.pyc
ADDED
|
Binary file (1.83 kB). View file
|
|
|
code/rerank_ins/loaders/__pycache__/base.cpython-314.pyc
ADDED
|
Binary file (2.24 kB). View file
|
|
|
code/rerank_ins/loaders/__pycache__/qwen3_5.cpython-313.pyc
ADDED
|
Binary file (1.32 kB). View file
|
|
|
code/rerank_ins/loaders/__pycache__/qwen3_5.cpython-314.pyc
ADDED
|
Binary file (1.53 kB). View file
|
|
|
code/rerank_ins/loaders/__pycache__/qwen3_vl.cpython-313.pyc
ADDED
|
Binary file (1.32 kB). View file
|
|
|
code/rerank_ins/loaders/__pycache__/qwen3_vl.cpython-314.pyc
ADDED
|
Binary file (1.53 kB). View file
|
|
|
code/rerank_ins/loaders/base.py
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from abc import ABC, abstractmethod
|
| 2 |
+
from typing import Dict, Tuple, Union, Optional
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
from transformers import PreTrainedModel, PreTrainedTokenizer, AutoProcessor, BitsAndBytesConfig
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
class BaseModelLoader(ABC):
|
| 9 |
+
def __init__(
|
| 10 |
+
self,
|
| 11 |
+
model_hf_path: str,
|
| 12 |
+
model_local_path: str,
|
| 13 |
+
compute_dtype: torch.dtype,
|
| 14 |
+
bnb_config: Optional[BitsAndBytesConfig] = None,
|
| 15 |
+
use_flash_attn: bool = True,
|
| 16 |
+
device_map: Optional[Union[Dict, str]] = None,
|
| 17 |
+
trust_remote_code: bool = True
|
| 18 |
+
) -> None:
|
| 19 |
+
self.model_hf_path = model_hf_path
|
| 20 |
+
self.model_local_path = model_local_path
|
| 21 |
+
self.loading_kwargs = dict(
|
| 22 |
+
torch_dtype=compute_dtype,
|
| 23 |
+
quantization_config=bnb_config,
|
| 24 |
+
device_map=device_map,
|
| 25 |
+
trust_remote_code=trust_remote_code
|
| 26 |
+
)
|
| 27 |
+
if use_flash_attn:
|
| 28 |
+
self.loading_kwargs["attn_implementation"] = "flash_attention_2"
|
| 29 |
+
|
| 30 |
+
@abstractmethod
|
| 31 |
+
def load(self, load_model: bool = True) -> Tuple[
|
| 32 |
+
PreTrainedModel, Union[None, PreTrainedTokenizer], Union[None, AutoProcessor]
|
| 33 |
+
]: ...
|
code/rerank_ins/loaders/qwen3_5.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Tuple
|
| 2 |
+
|
| 3 |
+
from transformers import AutoTokenizer, AutoProcessor, AutoModelForCausalLM
|
| 4 |
+
|
| 5 |
+
from .base import BaseModelLoader
|
| 6 |
+
from . import register_loader
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
@register_loader("qwen3_5")
|
| 10 |
+
class Qwen3_5ModelLoader(BaseModelLoader):
|
| 11 |
+
def load(self, load_model: bool = True) -> Tuple[AutoModelForCausalLM, AutoTokenizer, None]:
|
| 12 |
+
if load_model:
|
| 13 |
+
# Qwen3_5ForConditionalGeneration requires transformers>=5.x (requirements_qwen35.txt);
|
| 14 |
+
# import lazily so the qwen3vl env (transformers 4.x) can still import this module.
|
| 15 |
+
from transformers import Qwen3_5ForConditionalGeneration
|
| 16 |
+
|
| 17 |
+
model = Qwen3_5ForConditionalGeneration.from_pretrained(
|
| 18 |
+
self.model_local_path,
|
| 19 |
+
**self.loading_kwargs,
|
| 20 |
+
)
|
| 21 |
+
|
| 22 |
+
processor = AutoProcessor.from_pretrained(self.model_local_path)
|
| 23 |
+
tokenizer = processor.tokenizer
|
| 24 |
+
|
| 25 |
+
return model, tokenizer, processor
|
code/rerank_ins/loaders/qwen3_vl.py
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Tuple
|
| 2 |
+
|
| 3 |
+
from transformers import AutoTokenizer, AutoProcessor, AutoModelForCausalLM
|
| 4 |
+
|
| 5 |
+
from .base import BaseModelLoader
|
| 6 |
+
from . import register_loader
|
| 7 |
+
|
| 8 |
+
|
| 9 |
+
@register_loader("qwen3-vl")
|
| 10 |
+
class Qwen3VLModelLoader(BaseModelLoader):
|
| 11 |
+
def load(self, load_model: bool = True) -> Tuple[AutoModelForCausalLM, AutoTokenizer, None]:
|
| 12 |
+
if load_model:
|
| 13 |
+
# imported lazily to keep this module importable across transformers versions
|
| 14 |
+
from transformers import Qwen3VLForConditionalGeneration
|
| 15 |
+
|
| 16 |
+
model = Qwen3VLForConditionalGeneration.from_pretrained(
|
| 17 |
+
self.model_local_path,
|
| 18 |
+
**self.loading_kwargs,
|
| 19 |
+
)
|
| 20 |
+
|
| 21 |
+
processor = AutoProcessor.from_pretrained(self.model_local_path)
|
| 22 |
+
tokenizer = processor.tokenizer
|
| 23 |
+
|
| 24 |
+
return model, tokenizer, processor
|
code/rerank_ins/models/__init__.py
ADDED
|
File without changes
|
code/rerank_ins/models/__pycache__/__init__.cpython-314.pyc
ADDED
|
Binary file (119 Bytes). View file
|
|
|
code/rerank_ins/models/__pycache__/qwen3_5_rank.cpython-314.pyc
ADDED
|
Binary file (7.77 kB). View file
|
|
|
code/rerank_ins/models/__pycache__/qwen3_vl_rank.cpython-314.pyc
ADDED
|
Binary file (7.77 kB). View file
|
|
|