import argparse
import os
import os.path as osp
import warnings
import torch
import torch.distributed as dist
from tqdm import tqdm
from vlmeval.config import supported_VLM
from vlmeval.smp import (dump, get_logger, get_pred_file_format, get_pred_file_path,
get_rank_and_world_size, load)
from vlmeval.utils import track_progress_rich
logger = get_logger(__name__)
FAIL_MSG = 'Failed to obtain answer via API.'
def parse_args():
parser = argparse.ArgumentParser()
parser.add_argument('--data', type=str, nargs='+', required=True)
parser.add_argument('--model', type=str, nargs='+', required=True)
parser.add_argument('--nproc', type=int, default=4, required=True)
parser.add_argument('--verbose', action='store_true')
args = parser.parse_args()
return args
# Only API model is accepted
def infer_data_api(model, work_dir, model_name, dataset, index_set=None, api_nproc=4, retry_failed=True):
rank, world_size = get_rank_and_world_size()
assert rank == 0 and world_size == 1
dataset_name = dataset.dataset_name
data = dataset.data
if index_set is not None:
data = data[data['index'].isin(index_set)]
model = supported_VLM[model_name]() if isinstance(model, str) else model
assert getattr(model, 'is_api', False)
if hasattr(model, 'set_dump_image'):
model.set_dump_image(dataset.dump_image)
lt, indices = len(data), list(data['index'])
# Build str→orig mapping for checkpoint key conversion
index_str_to_orig = {str(i): i for i in indices}
structs = []
for i in range(lt):
item = data.iloc[i]
if hasattr(dataset, 'force_use_dataset_prompt') and dataset.force_use_dataset_prompt:
struct = dataset.build_prompt(item)
elif hasattr(model, 'use_custom_prompt') and model.use_custom_prompt(dataset_name):
assert hasattr(model, 'build_prompt')
struct = model.build_prompt(item, dataset=dataset_name)
else:
struct = dataset.build_prompt(item)
structs.append(struct)
out_file = f'{work_dir}/{model_name}_{dataset_name}_checkpoint.pkl'
# To reuse records in MMBench_V11
if dataset_name in ['MMBench', 'MMBench_CN']:
pred_format = get_pred_file_format()
v11_pred = f'{work_dir}/{model_name}_{dataset_name}_V11.{pred_format}'
if osp.exists(v11_pred):
try:
reuse_inds = load('https://opencompass.openxlab.space/utils/mmb_reuse.pkl')
data_v11 = load(v11_pred)
ans_map = {str(x): y for x, y in zip(data_v11['index'], data_v11['prediction']) if x in reuse_inds}
dump(ans_map, out_file)
except Exception as err:
print(type(err), err)
res = {}
if osp.exists(out_file):
res = load(out_file)
if retry_failed:
res = {k: v for k, v in res.items() if FAIL_MSG not in v}
logger.info(f'Reuse {len(res)} inference results from previous run.')
structs = [s for i, s in zip(indices, structs) if str(i) not in res]
indices = [i for i in indices if str(i) not in res]
gen_func = model.generate
structs = [dict(message=struct, dataset=dataset_name) for struct in structs]
if len(structs):
str_indices = [str(i) for i in indices]
track_progress_rich(gen_func, structs, nproc=api_nproc, chunksize=api_nproc, save=out_file, keys=str_indices)
# Load the full accumulated results (str keys)
if osp.exists(out_file):
res = load(out_file)
# Convert str keys back to original types for caller compatibility
result = {index_str_to_orig[k]: v for k, v in res.items() if k in index_str_to_orig}
if index_set is not None:
result = {k: v for k, v in result.items() if k in index_set}
return result
def infer_data(model, model_name, work_dir, dataset, out_file, verbose=False, api_nproc=4, use_vllm=False,
retry_failed=True):
dataset_name = dataset.dataset_name
prev_file = f'{work_dir}/{model_name}_{dataset_name}_PREV.pkl'
res = load(prev_file) if osp.exists(prev_file) else {}
if osp.exists(out_file):
res.update(load(out_file))
rank, world_size = get_rank_and_world_size()
sheet_indices = list(range(rank, len(dataset), world_size))
lt = len(sheet_indices)
data = dataset.data.iloc[sheet_indices]
data_indices = [i for i in data['index']]
# If finished, will exit without building the model
all_finished = True
for i in range(lt):
idx = data.iloc[i]['index']
if idx not in res:
all_finished = False
if all_finished:
res = {k: res[k] for k in data_indices}
dump(res, out_file)
return model
# Data need to be inferred
data = data[~data['index'].isin(res)]
lt = len(data)
kwargs = {}
if model_name is not None and (
'Llama-4' in model_name
or 'Qwen2-VL' in model_name
or 'Qwen2.5-VL' in model_name
):
kwargs = {'use_vllm': use_vllm}
# (25.06.05) In newer version of transformers (after 4.50), with device_map='auto' and torchrun launcher,
# Transformers automatically adopt TP parallelism, which leads to compatibility problems with VLMEvalKit
# (In VLMEvalKit, we use torchrun to launch multiple model instances on a single node).
# To bypass this problem, we unset `WORLD_SIZE` before building the model to not use TP parallel.
ws_bak = os.environ.pop('WORLD_SIZE', None)
model = supported_VLM[model_name](**kwargs) if isinstance(model, str) else model
if ws_bak:
os.environ['WORLD_SIZE'] = ws_bak
is_api = getattr(model, 'is_api', False)
if is_api:
lt, indices = len(data), list(data['index'])
supp = infer_data_api(
model=model,
work_dir=work_dir,
model_name=model_name,
dataset=dataset,
index_set=set(indices),
api_nproc=api_nproc,
retry_failed=retry_failed)
for idx in indices:
assert idx in supp
res.update(supp)
res = {k: res[k] for k in data_indices}
dump(res, out_file)
return model
else:
model.set_dump_image(dataset.dump_image)
for i in tqdm(range(lt), desc=f'Infer {model_name}/{dataset_name}, Rank {rank}/{world_size}'):
idx = data.iloc[i]['index']
if idx in res:
continue
if hasattr(dataset, 'force_use_dataset_prompt') and dataset.force_use_dataset_prompt:
struct = dataset.build_prompt(data.iloc[i])
elif hasattr(model, 'use_custom_prompt') and model.use_custom_prompt(dataset_name):
struct = model.build_prompt(data.iloc[i], dataset=dataset_name)
else:
struct = dataset.build_prompt(data.iloc[i])
# If `SKIP_ERR` flag is set, the model will skip the generation if error is encountered
if os.environ.get('SKIP_ERR', False) == '1':
FAIL_MSG = 'Failed to obtain answer'
try:
response = model.generate(message=struct, dataset=dataset_name)
except RuntimeError as err:
torch.cuda.synchronize()
warnings.warn(f'{type(err)} {str(err)}')
response = f'{FAIL_MSG}: {type(err)} {str(err)}'
else:
response = model.generate(message=struct, dataset=dataset_name)
torch.cuda.empty_cache()
if verbose:
print(response, flush=True)
res[idx] = response
if (i + 1) % 10 == 0:
dump(res, out_file)
res = {k: res[k] for k in data_indices}
dump(res, out_file)
return model
# Add for agent evaluation
def _is_structured_record(v):
return isinstance(v, dict) and 'prediction' in v and 'extra_records' in v
# A wrapper for infer_data, do the pre & post processing
def infer_data_job(
model, work_dir, model_name, dataset, verbose=False, api_nproc=4, retry_failed=True, use_vllm=False
):
rank, world_size = get_rank_and_world_size()
dataset_name = dataset.dataset_name
# 使用环境变量控制的文件格式
result_file = get_pred_file_path(work_dir, model_name, dataset_name, use_env_format=True)
prev_file = f'{work_dir}/{model_name}_{dataset_name}_PREV.pkl'
if osp.exists(result_file):
if rank == 0:
data = load(result_file)
results = {k: v for k, v in zip(data['index'], data['prediction'])}
if retry_failed:
results = {k: v for k, v in results.items() if FAIL_MSG not in str(v)}
dump(results, prev_file)
if world_size > 1:
dist.barrier()
tmpl = osp.join(work_dir, '{}' + f'{world_size}_{dataset_name}.pkl')
out_file = tmpl.format(rank)
model = infer_data(
model=model, work_dir=work_dir, model_name=model_name, dataset=dataset,
out_file=out_file, verbose=verbose, api_nproc=api_nproc, use_vllm=use_vllm,
retry_failed=retry_failed)
if world_size > 1:
dist.barrier()
if rank == 0:
data_all = {}
for i in range(world_size):
data_all.update(load(tmpl.format(i)))
data = dataset.data
for x in data['index']:
assert x in data_all
if os.getenv('SPLIT_THINK', False):
if all(_is_structured_record(data_all[x]) for x in data['index']):
prediction = [data_all[x]['prediction'] for x in data['index']]
extra_records = [data_all[x]['extra_records'] for x in data['index']]
data['extra_records'] = extra_records
else:
prediction = [str(data_all[x]) for x in data['index']]
def split_thinking(s):
if '' in s:
splits = s.split('')
prediction = splits[-1].strip()
if len(splits) == 2 and '' in splits[0]:
thinking = splits[0].split('')[1].strip()
else:
thinking = ''.join(splits[:-1])
thinking += ''
warnings.warn('Failed to parse thinking, multiple tags or missing tag.')
else:
thinking = ''
prediction = s
return (prediction, thinking)
split_func = model.split_thinking if hasattr(model, 'split_thinking') else split_thinking
print(f'Prediction format: {os.getenv("SPLIT_THINK")},splitting func: {split_func}')
tups = [split_func(x) for x in prediction]
data['prediction'] = [x[0] for x in tups]
data['thinking'] = [x[1] for x in tups]
else:
# data['prediction'] = [str(data_all[x]) for x in data['index']]
# Add for agent evaluation
if all(_is_structured_record(data_all[x]) for x in data['index']):
data['prediction'] = [data_all[x]['prediction'] for x in data['index']]
data['extra_records'] = [data_all[x]['extra_records'] for x in data['index']]
else:
data['prediction'] = [str(data_all[x]) for x in data['index']]
if 'image' in data:
data.pop('image')
dump(data, result_file)
for i in range(world_size):
os.remove(tmpl.format(i))
# Clean up API checkpoint file
checkpoint_file = f'{work_dir}/{model_name}_{dataset_name}_checkpoint.pkl'
if osp.exists(checkpoint_file):
os.remove(checkpoint_file)
# Clean up PREV file
if osp.exists(prev_file):
os.remove(prev_file)
if world_size > 1:
dist.barrier()
return model