From b9ee57685f44470067bba4e5eba971c7db171f9b Mon Sep 17 00:00:00 2001 From: Ke Ding Date: Sun, 28 Jun 2026 14:45:00 -0700 Subject: [PATCH] Add VANTAGE + TAR reproduction kit for cosmos3 reasoner (vlmevalkit) Signed-off-by: Ke Ding --- .../.github/scripts/assert_score.py | 61 + .../vlmevalkit/.github/workflows/lint.yml | 23 + .../.github/workflows/pr-run-test.yml | 90 + .../cosmos3/reasoner/vlmevalkit/.gitignore | 227 + .../vlmevalkit/.pre-commit-config.yaml | 45 + .../cosmos3/reasoner/vlmevalkit/LICENSE | 203 + .../reasoner/vlmevalkit/README-vlmevalkit.md | 158 + .../cosmos3/reasoner/vlmevalkit/README.md | 183 + .../vlmevalkit/assets/0_lamp_pred.png | Bin 0 -> 494911 bytes .../reasoner/vlmevalkit/assets/LOGO.svg | 24 + .../reasoner/vlmevalkit/assets/apple.jpg | Bin 0 -> 4576 bytes .../reasoner/vlmevalkit/assets/sample.mp4 | Bin 0 -> 499193 bytes .../vlmevalkit/cosmos_eval/.gitignore | 5 + .../data/metropolis/AETCBench_all.json | 4 + .../data/metropolis/VANTAGE_2DGrounding.json | 4 + .../data/metropolis/VANTAGE_2DPointing.json | 4 + .../data/metropolis/VANTAGE_Astro2D.json | 4 + .../data/metropolis/VANTAGE_DVC.json | 7 + .../metropolis/VANTAGE_EventVerification.json | 7 + .../data/metropolis/VANTAGE_SOT.json | 6 + .../data/metropolis/VANTAGE_Temporal.json | 8 + .../data/metropolis/VANTAGE_VQA.json | 8 + .../vlmevalkit/cosmos_eval/manifest.json | 56 + .../vlmevalkit/cosmos_eval/models/cosmos.json | 171 + .../vlmevalkit/cosmos_eval/parse_score.py | 787 +++ .../vlmevalkit/cosmos_eval/run_all.py | 279 ++ .../vlmevalkit/docs/en/.readthedocs.yaml | 17 + .../vlmevalkit/docs/en/ConfigSystem.md | 67 + .../vlmevalkit/docs/en/Contributors.md | 21 + .../vlmevalkit/docs/en/Development.md | 145 + .../vlmevalkit/docs/en/EvalByLMDeploy.md | 27 + .../reasoner/vlmevalkit/docs/en/Makefile | 20 + .../reasoner/vlmevalkit/docs/en/Quickstart.md | 236 + .../docs/en/_static/css/readthedocs.css | 63 + .../vlmevalkit/docs/en/_static/image/logo.svg | 24 + .../docs/en/_static/image/logo_icon.svg | 31 + .../vlmevalkit/docs/en/_static/js/custom.js | 10 + .../vlmevalkit/docs/en/_templates/404.html | 18 + .../docs/en/_templates/autosummary/class.rst | 13 + .../docs/en/_templates/callable.rst | 14 + .../reasoner/vlmevalkit/docs/en/conf.py | 234 + .../reasoner/vlmevalkit/docs/en/docutils.conf | 2 + .../reasoner/vlmevalkit/docs/en/index.rst | 41 + .../reasoner/vlmevalkit/docs/ja/README_ja.md | 117 + .../vlmevalkit/docs/zh-CN/.readthedocs.yaml | 17 + .../vlmevalkit/docs/zh-CN/ConfigSystem.md | 69 + .../vlmevalkit/docs/zh-CN/Development.md | 139 + .../vlmevalkit/docs/zh-CN/EvalByLMDeploy.md | 28 + .../reasoner/vlmevalkit/docs/zh-CN/Makefile | 20 + .../vlmevalkit/docs/zh-CN/Quickstart.md | 231 + .../vlmevalkit/docs/zh-CN/README_zh-CN.md | 131 + .../docs/zh-CN/_static/css/readthedocs.css | 63 + .../docs/zh-CN/_static/image/logo.svg | 24 + .../docs/zh-CN/_static/image/logo_icon.svg | 31 + .../docs/zh-CN/_static/js/custom.js | 10 + .../vlmevalkit/docs/zh-CN/_templates/404.html | 18 + .../zh-CN/_templates/autosummary/class.rst | 13 + .../docs/zh-CN/_templates/callable.rst | 14 + .../reasoner/vlmevalkit/docs/zh-CN/conf.py | 242 + .../vlmevalkit/docs/zh-CN/cp_origin_docs.sh | 9 + .../vlmevalkit/docs/zh-CN/docutils.conf | 2 + .../reasoner/vlmevalkit/docs/zh-CN/index.rst | 49 + .../reasoner/vlmevalkit/requirements.txt | 80 + .../reasoner/vlmevalkit/requirements/docs.txt | 11 + evaluation/cosmos3/reasoner/vlmevalkit/run.py | 551 +++ .../cosmos3/reasoner/vlmevalkit/run_api.py | 397 ++ .../vlmevalkit/scripts/AI2D_preproc.ipynb | 261 + .../vlmevalkit/scripts/apires_scan.py | 60 + .../reasoner/vlmevalkit/scripts/auto_run.py | 44 + .../vlmevalkit/scripts/convert_macbench.py | 98 + .../reasoner/vlmevalkit/scripts/cover.sh | 4 + .../vlmevalkit/scripts/data_browser.py | 179 + .../vlmevalkit/scripts/mmb_eval_gradio.py | 122 + .../vlmevalkit/scripts/prepare_spar_bench.py | 55 + .../reasoner/vlmevalkit/scripts/run.sh | 4 + .../vlmevalkit/scripts/run_endpoint_bench.sh | 286 ++ .../reasoner/vlmevalkit/scripts/srun.sh | 3 + .../reasoner/vlmevalkit/scripts/summarize.py | 123 + .../vlmevalkit/scripts/visualize.ipynb | 266 + .../cosmos3/reasoner/vlmevalkit/setup.cfg | 23 + .../cosmos3/reasoner/vlmevalkit/setup.py | 123 + .../reasoner/vlmevalkit/vlmeval/__init__.py | 43 + .../vlmevalkit/vlmeval/api/__init__.py | 58 + .../vlmeval/api/adapters/__init__.py | 13 + .../vlmevalkit/vlmeval/api/adapters/base.py | 78 + .../vlmeval/api/adapters/cogvlm2.py | 50 + .../vlmeval/api/adapters/interns1_1.py | 296 ++ .../vlmeval/api/adapters/internvl2.py | 166 + .../vlmeval/api/adapters/internvl3.py | 204 + .../vlmevalkit/vlmeval/api/adapters/qwen3.py | 14 + .../vlmevalkit/vlmeval/api/arm_thinker.py | 276 ++ .../vlmevalkit/vlmeval/api/bailingmm.py | 96 + .../reasoner/vlmevalkit/vlmeval/api/base.py | 335 ++ .../vlmevalkit/vlmeval/api/bedrock.py | 173 + .../vlmevalkit/vlmeval/api/bluelm_api.py | 240 + .../reasoner/vlmevalkit/vlmeval/api/claude.py | 154 + .../vlmevalkit/vlmeval/api/cloudwalk.py | 110 + .../vlmevalkit/vlmeval/api/cosmos_reason.py | 774 +++ .../vlmevalkit/vlmeval/api/doubao_vl_api.py | 209 + .../vlmevalkit/vlmeval/api/gcp_vertex.py | 211 + .../reasoner/vlmevalkit/vlmeval/api/gemini.py | 213 + .../vlmevalkit/vlmeval/api/glm_vision.py | 76 + .../reasoner/vlmevalkit/vlmeval/api/gpt.py | 334 ++ .../vlmevalkit/vlmeval/api/hf_chat_model.py | 263 + .../vlmevalkit/vlmeval/api/hunyuan.py | 184 + .../vlmevalkit/vlmeval/api/jt_vl_chat.py | 289 ++ .../vlmevalkit/vlmeval/api/jt_vl_chat_mini.py | 290 ++ .../vlmevalkit/vlmeval/api/kimivl_api.py | 166 + .../vlmevalkit/vlmeval/api/lmdeploy.py | 142 + .../vlmevalkit/vlmeval/api/lvs_service.py | 224 + .../reasoner/vlmevalkit/vlmeval/api/mimo.py | 41 + .../vlmevalkit/vlmeval/api/minimax_api.py | 124 + .../reasoner/vlmevalkit/vlmeval/api/mug_u.py | 214 + .../vlmevalkit/vlmeval/api/nemotron.py | 21 + .../vlmevalkit/vlmeval/api/nv_gemini.py | 277 ++ .../vlmevalkit/vlmeval/api/openai_sdk.py | 140 + .../vlmevalkit/vlmeval/api/qwen_api.py | 75 + .../vlmevalkit/vlmeval/api/qwen_vl_api.py | 222 + .../vlmeval/api/rbdashmm_chat3_5_api.py | 601 +++ .../vlmeval/api/rbdashmm_chat3_api.py | 530 ++ .../reasoner/vlmevalkit/vlmeval/api/reka.py | 59 + .../vlmeval/api/sensechat_vision.py | 411 ++ .../vlmevalkit/vlmeval/api/siliconflow.py | 288 ++ .../reasoner/vlmevalkit/vlmeval/api/stepai.py | 94 + .../reasoner/vlmevalkit/vlmeval/api/taichu.py | 359 ++ .../reasoner/vlmevalkit/vlmeval/api/taiyi.py | 196 + .../reasoner/vlmevalkit/vlmeval/api/telemm.py | 296 ++ .../vlmevalkit/vlmeval/api/telemm_thinking.py | 297 ++ .../vlmevalkit/vlmeval/api/together.py | 141 + .../vlmeval/api/video_chat_online_v2.py | 285 ++ .../reasoner/vlmevalkit/vlmeval/config.py | 2663 ++++++++++ .../vlmeval/dataset/CGAVCounting/__init__.py | 0 .../dataset/CGAVCounting/cg_av_counting.py | 415 ++ .../dataset/CGAVCounting/requirements.txt | 2 + .../vlmeval/dataset/CGAVCounting/utils.py | 423 ++ .../vlmeval/dataset/EgoExoBench/README.md | 79 + .../vlmeval/dataset/EgoExoBench/__init__.py | 1 + .../dataset/EgoExoBench/cvmhat_preprocess.py | 46 + .../dataset/EgoExoBench/egoexobench.py | 305 ++ .../dataset/EgoExoBench/tf2023_preprocess.py | 72 + .../vlmeval/dataset/EgoExoBench/utils.py | 758 +++ .../vlmeval/dataset/GUI/__init__.py | 0 .../vlmeval/dataset/GUI/osworld_g.py | 441 ++ .../vlmeval/dataset/GUI/screenspot.py | 462 ++ .../vlmeval/dataset/GUI/screenspot_pro.py | 461 ++ .../vlmeval/dataset/GUI/screenspot_v2.py | 203 + .../vlmevalkit/vlmeval/dataset/GUI/vbgd.py | 447 ++ .../vlmeval/dataset/GUI/venusbench.py | 186 + .../vlmeval/dataset/IFBench/__init__.py | 0 .../vlmeval/dataset/IFBench/ifbench.py | 17 + .../vlmevalkit/vlmeval/dataset/NPMM.py | 150 + .../vlmeval/dataset/Omni3D/README.md | 168 + .../dataset/Omni3D/configs/basic_gt.yaml | 56 + .../dataset/Omni3D/configs/category_meta.json | 1 + .../vlmeval/dataset/Omni3D/install_patch.sh | 118 + .../dataset/Omni3D/install_patch_vllm.sh | 123 + .../vlmeval/dataset/Omni3D/omni3d_dataset.py | 17 + .../vlmeval/dataset/OmniDocBench/__init__.py | 0 .../dataset/OmniDocBench/data_preprocess.py | 449 ++ .../vlmeval/dataset/OmniDocBench/metrics.py | 486 ++ .../dataset/OmniDocBench/omnidocbench.py | 557 +++ .../dataset/OmniDocBench/requirements.txt | 13 + .../vlmeval/dataset/OmniDocBench/utils.py | 1918 ++++++++ .../vlmeval/dataset/SGI_Bench_1_0/__init__.py | 0 .../dataset/SGI_Bench_1_0/deep_research.py | 183 + .../dataset/SGI_Bench_1_0/dry_experiment.py | 599 +++ .../dry_experiment_requirements.txt | 67 + .../SGI_Bench_1_0/experimental_reasoning.py | 306 ++ .../dataset/SGI_Bench_1_0/idea_generation.py | 896 ++++ .../vlmeval/dataset/SGI_Bench_1_0/readme.md | 12 + .../vlmeval/dataset/SGI_Bench_1_0/utils.py | 232 + .../dataset/SGI_Bench_1_0/wet_experiment.py | 360 ++ .../vlmevalkit/vlmeval/dataset/__init__.py | 494 ++ .../vlmevalkit/vlmeval/dataset/aetcbench.py | 778 +++ .../vlmevalkit/vlmeval/dataset/asclepius.py | 231 + .../vlmeval/dataset/av_3d_grounding.py | 17 + .../vlmeval/dataset/av_prompt_following.py | 17 + .../vlmeval/dataset/av_speakerbench.py | 317 ++ .../dataset/avspecial_collision_bench.py | 17 + .../dataset/avspecial_environment_bench.py | 17 + .../dataset/avspecial_ood_reasoning_bench.py | 17 + .../dataset/avspecial_stop_behavior_bench.py | 17 + .../vlmevalkit/vlmeval/dataset/blink_depth.py | 17 + .../vlmeval/dataset/blink_spatial.py | 17 + .../vlmeval/dataset/camera_bench.py | 17 + .../vlmeval/dataset/camera_intrinsic.py | 17 + .../vlmevalkit/vlmeval/dataset/causalvqa.py | 17 + .../vlmevalkit/vlmeval/dataset/cgbench.py | 1779 +++++++ .../vlmevalkit/vlmeval/dataset/chartbench.py | 309 ++ .../vlmevalkit/vlmeval/dataset/chartcap.py | 182 + .../vlmevalkit/vlmeval/dataset/chartmimic.py | 817 ++++ .../vlmevalkit/vlmeval/dataset/chartmuseum.py | 199 + .../vlmevalkit/vlmeval/dataset/chartqapro.py | 123 + .../vlmevalkit/vlmeval/dataset/chartx.py | 125 + .../vlmevalkit/vlmeval/dataset/charxiv.py | 257 + .../vlmevalkit/vlmeval/dataset/cmmmu.py | 358 ++ .../vlmeval/dataset/cosmos_cab_image.py | 17 + .../vlmeval/dataset/cosmos_cab_video.py | 28 + .../vlmevalkit/vlmeval/dataset/cosmos_erqa.py | 17 + .../vlmeval/dataset/cosmos_reason.py | 17 + .../vlmevalkit/vlmeval/dataset/creation.py | 778 +++ .../vlmevalkit/vlmeval/dataset/cv_bench.py | 17 + .../vlmevalkit/vlmeval/dataset/da2k.py | 312 ++ .../vlmevalkit/vlmeval/dataset/design2code.py | 205 + .../vlmevalkit/vlmeval/dataset/dream.py | 404 ++ .../vlmevalkit/vlmeval/dataset/dsrbench.py | 220 + .../vlmevalkit/vlmeval/dataset/dude.py | 223 + .../vlmevalkit/vlmeval/dataset/dynamath.py | 241 + .../vlmeval/dataset/embspatialbench.py | 89 + .../vlmevalkit/vlmeval/dataset/emma.py | 60 + .../vlmevalkit/vlmeval/dataset/eriq.py | 150 + .../vlmevalkit/vlmeval/dataset/erqa.py | 209 + .../vlmevalkit/vlmeval/dataset/erqabench.py | 123 + .../vlmevalkit/vlmeval/dataset/flames.py | 144 + .../vlmevalkit/vlmeval/dataset/foxbench.py | 236 + .../vlmevalkit/vlmeval/dataset/gobench.py | 208 + .../vlmevalkit/vlmeval/dataset/groundingme.py | 199 + .../vlmevalkit/vlmeval/dataset/gsm8k_v.py | 178 + .../vlmeval/dataset/health_surgi_bench.py | 17 + .../vlmevalkit/vlmeval/dataset/hipho.py | 913 ++++ .../vlmevalkit/vlmeval/dataset/image_base.py | 216 + .../vlmeval/dataset/image_caption.py | 76 + .../vlmevalkit/vlmeval/dataset/image_ccocr.py | 304 ++ .../vlmevalkit/vlmeval/dataset/image_mcq.py | 3213 ++++++++++++ .../vlmevalkit/vlmeval/dataset/image_mt.py | 133 + .../vlmeval/dataset/image_shortqa.py | 166 + .../vlmevalkit/vlmeval/dataset/image_vqa.py | 4343 +++++++++++++++++ .../vlmevalkit/vlmeval/dataset/image_yorn.py | 109 + .../vlmevalkit/vlmeval/dataset/intphys2.py | 17 + .../vlmeval/dataset/its_collision.py | 17 + .../vlmevalkit/vlmeval/dataset/lingoqa.py | 17 + .../vlmeval/dataset/locate_anything_bench.py | 17 + .../vlmeval/dataset/longvideobench.py | 343 ++ .../vlmeval/dataset/lv_event_verification.py | 17 + .../vlmevalkit/vlmeval/dataset/lvs.py | 28 + .../vlmeval/dataset/lvs_ai_hallucination.py | 17 + .../vlmevalkit/vlmeval/dataset/m3oralbench.py | 74 + .../vlmevalkit/vlmeval/dataset/m4bench.py | 194 + .../vlmevalkit/vlmeval/dataset/macbench.py | 294 ++ .../vlmevalkit/vlmeval/dataset/matbench.py | 186 + .../vlmeval/dataset/medqbench_caption.py | 459 ++ .../vlmeval/dataset/medqbench_mcq.py | 214 + .../dataset/medqbench_paired_description.py | 450 ++ .../vlmevalkit/vlmeval/dataset/megabench.py | 502 ++ .../dataset/metropolis2d/README_detection.md | 186 + .../dataset/metropolis2d/README_grounding.md | 216 + .../vlmeval/dataset/metropolis2d/__init__.py | 5 + .../dataset/metropolis2d/astro_2d_dataset.py | 586 +++ .../dataset/metropolis2d/datasets.yaml | 35 + .../metropolis2d/detection_2d_dataset.py | 372 ++ .../metropolis2d/grounding_2d_dataset.py | 783 +++ .../vlmeval/dataset/metropolis2d/utils.py | 536 ++ .../vlmeval/dataset/metropolis_dvc.py | 733 +++ .../dataset/metropolis_event_verification.py | 359 ++ .../vlmeval/dataset/metropolis_temporal.py | 1184 +++++ .../vlmeval/dataset/metropolis_vqa.py | 861 ++++ .../vlmevalkit/vlmeval/dataset/miabench.py | 166 + .../vlmeval/dataset/mindcubebench.py | 165 + .../vlmevalkit/vlmeval/dataset/mlvu.py | 460 ++ .../vlmeval/dataset/mmalignbench.py | 299 ++ .../vlmeval/dataset/mmbench_video.py | 266 + .../vlmevalkit/vlmeval/dataset/mmesci.py | 125 + .../vlmevalkit/vlmeval/dataset/mmgenbench.py | 68 + .../vlmevalkit/vlmeval/dataset/mmhelix.py | 585 +++ .../vlmevalkit/vlmeval/dataset/mmifeval.py | 485 ++ .../vlmevalkit/vlmeval/dataset/mmlongbench.py | 594 +++ .../vlmevalkit/vlmeval/dataset/mmmath.py | 455 ++ .../vlmeval/dataset/mmoral_opg_closed.py | 17 + .../vlmeval/dataset/mmoral_opg_open.py | 17 + .../vlmevalkit/vlmeval/dataset/mmreason.py | 248 + .../vlmeval/dataset/mmsafetybench.py | 230 + .../vlmevalkit/vlmeval/dataset/mmsi_video.py | 299 ++ .../vlmevalkit/vlmeval/dataset/mmsibench.py | 626 +++ .../vlmevalkit/vlmeval/dataset/moat.py | 177 + .../vlmevalkit/vlmeval/dataset/motionbench.py | 17 + .../vlmevalkit/vlmeval/dataset/moviechat1k.py | 269 + .../vlmevalkit/vlmeval/dataset/mssbench.py | 205 + .../vlmevalkit/vlmeval/dataset/mvbench.py | 677 +++ .../vlmevalkit/vlmeval/dataset/mvpbench.py | 17 + .../vlmevalkit/vlmeval/dataset/mvu_eval.py | 336 ++ .../vlmevalkit/vlmeval/dataset/oceanocr.py | 231 + .../vlmevalkit/vlmeval/dataset/odinw13.py | 17 + .../vlmeval/dataset/olmOCRBench/eval_req.txt | 6 + .../vlmeval/dataset/olmOCRBench/evaluator.py | 463 ++ .../dataset/olmOCRBench/katex/__init__.py | 1 + .../olmOCRBench/katex/auto-render.min.js | 1 + .../dataset/olmOCRBench/katex/katex.min.css | 1 + .../dataset/olmOCRBench/katex/katex.min.js | 1 + .../dataset/olmOCRBench/katex/render.py | 749 +++ .../dataset/olmOCRBench/olmocrbench.py | 46 + .../dataset/olmOCRBench/repeatdetect.py | 175 + .../vlmeval/dataset/olmOCRBench/tests.py | 1186 +++++ .../vlmeval/dataset/olmOCRBench/utils.py | 203 + .../vlmeval/dataset/omnispatialbench.py | 292 ++ .../vlmevalkit/vlmeval/dataset/omtgbench.py | 276 ++ .../vlmevalkit/vlmeval/dataset/ost_bench.py | 306 ++ .../vlmevalkit/vlmeval/dataset/plotqa.py | 54 + .../vlmeval/dataset/qbench_video.py | 354 ++ .../vlmeval/dataset/reasonmap_plus.py | 257 + .../vlmevalkit/vlmeval/dataset/refcoco.py | 614 +++ .../vlmevalkit/vlmeval/dataset/refspatial.py | 493 ++ .../vlmeval/dataset/refspatialbench.py | 285 ++ .../vlmeval/dataset/robospatial_home.py | 17 + .../vlmeval/dataset/robospatialbench.py | 294 ++ .../vlmevalkit/vlmeval/dataset/sarena.py | 48 + .../vlmevalkit/vlmeval/dataset/sat_bench.py | 17 + .../vlmevalkit/vlmeval/dataset/sfebench.py | 226 + .../vlmeval/dataset/share_robot_bench.py | 17 + .../vlmevalkit/vlmeval/dataset/simplevqa.py | 207 + .../vlmevalkit/vlmeval/dataset/sitebench.py | 439 ++ .../vlmevalkit/vlmeval/dataset/siuo.py | 113 + .../vlmevalkit/vlmeval/dataset/siuo_gen.py | 216 + .../vlmevalkit/vlmeval/dataset/siuo_mcq.py | 66 + .../vlmevalkit/vlmeval/dataset/slidevqa.py | 196 + .../vlmeval/dataset/sop_action_recognition.py | 17 + .../dataset/sop_temporal_localization.py | 17 + .../vlmevalkit/vlmeval/dataset/spar_bench.py | 28 + .../vlmevalkit/vlmeval/dataset/sparbench.py | 424 ++ .../vlmevalkit/vlmeval/dataset/spatial457.py | 208 + .../vlmeval/dataset/spatialvizbench.py | 145 + .../vlmevalkit/vlmeval/dataset/spbench.py | 237 + .../vlmevalkit/vlmeval/dataset/ssi_bench.py | 310 ++ .../vlmevalkit/vlmeval/dataset/starebench.py | 590 +++ .../vlmevalkit/vlmeval/dataset/stibench.py | 240 + .../vlmevalkit/vlmeval/dataset/tailgating.py | 17 + .../vlmevalkit/vlmeval/dataset/tamperbench.py | 545 +++ .../vlmevalkit/vlmeval/dataset/tempcompass.py | 651 +++ .../dataset/temporal_localization_bench.py | 17 + .../vlmevalkit/vlmeval/dataset/text_base.py | 95 + .../vlmevalkit/vlmeval/dataset/text_mcq.py | 130 + .../vlmevalkit/vlmeval/dataset/uni_svg.py | 51 + .../dataset/utils/NPMM/dominating_set.py | 74 + .../dataset/utils/NPMM/feedback_vertex.py | 102 + .../vlmeval/dataset/utils/NPMM/gcp.py | 95 + .../dataset/utils/NPMM/hamiltonian_cycle.py | 90 + .../vlmeval/dataset/utils/NPMM/maximum_cut.py | 91 + .../vlmeval/dataset/utils/NPMM/maximum_set.py | 68 + .../vlmeval/dataset/utils/NPMM/mcp.py | 116 + .../vlmeval/dataset/utils/NPMM/minimum_cut.py | 86 + .../vlmeval/dataset/utils/NPMM/tsp.py | 88 + .../dataset/utils/NPMM/vertex_cover.py | 88 + .../utils/Ocrbench_v2/IoUscore_metric.py | 89 + .../dataset/utils/Ocrbench_v2/TEDS_metric.py | 933 ++++ .../utils/Ocrbench_v2/page_ocr_metric.py | 49 + .../dataset/utils/Ocrbench_v2/parallel.py | 52 + .../utils/Ocrbench_v2/requirements.txt | 12 + .../spotting_eval/rrc_evaluation_funcs_1_1.py | 458 ++ .../utils/Ocrbench_v2/spotting_eval/script.py | 453 ++ .../utils/Ocrbench_v2/spotting_metric.py | 192 + .../dataset/utils/Ocrbench_v2/vqa_metric.py | 284 ++ .../dataset/utils/SArena/CLIP_Score.py | 74 + .../dataset/utils/SArena/DINO_Score.py | 57 + .../vlmeval/dataset/utils/SArena/FID.py | 153 + .../vlmeval/dataset/utils/SArena/LPIPS.py | 78 + .../vlmeval/dataset/utils/SArena/PSNR.py | 27 + .../vlmeval/dataset/utils/SArena/SSIM.py | 36 + .../vlmeval/dataset/utils/SArena/__init__.py | 0 .../dataset/utils/SArena/average_meter.py | 16 + .../dataset/utils/SArena/base_metric.py | 51 + .../vlmeval/dataset/utils/SArena/inception.py | 350 ++ .../vlmeval/dataset/utils/SArena/metrics.py | 83 + .../utils/SArena/requirements_sarena.txt | 18 + .../dataset/utils/SArena/token_length.py | 77 + .../dataset/utils/SArena/utils/__init__.py | 0 .../dataset/utils/SArena/utils/raster_svg.py | 34 + .../dataset/utils/SArena/video/CLIP_video.py | 233 + .../dataset/utils/SArena/video/DINO_video.py | 86 + .../vlmeval/dataset/utils/SArena/video/FVD.py | 68 + .../dataset/utils/SArena/video/LPIPS_video.py | 121 + .../dataset/utils/SArena/video/PSNR_video.py | 42 + .../dataset/utils/SArena/video/SSIM_video.py | 57 + .../video/viclip/bpe_simple_vocab_16e6.txt.gz | Bin 0 -> 1356917 bytes .../SArena/video/viclip/simple_tokenizer.py | 159 + .../utils/SArena/video/viclip/viclip.py | 220 + .../utils/SArena/video/viclip/viclip_text.py | 287 ++ .../SArena/video/viclip/viclip_vision.py | 464 ++ .../utils/SArena/video/video_metrics.py | 110 + .../vlmeval/dataset/utils/__init__.py | 11 + .../vlmeval/dataset/utils/ayavision.py | 46 + .../vlmevalkit/vlmeval/dataset/utils/bmmr.py | 279 ++ .../vlmeval/dataset/utils/bmmr_grade.py | 470 ++ .../dataset/utils/ccocr_evaluator/README.md | 59 + .../dataset/utils/ccocr_evaluator/__init__.py | 11 + .../dataset/utils/ccocr_evaluator/common.py | 223 + .../ccocr_evaluator/doc_parsing_evaluator.py | 256 + .../utils/ccocr_evaluator/kie_evaluator.py | 382 ++ .../utils/ccocr_evaluator/ocr_evaluator.py | 103 + .../vlmeval/dataset/utils/cgbench.py | 628 +++ .../dataset/utils/chartmimic/__init__.py | 0 .../utils/chartmimic/eval_configs/__init__.py | 0 .../chartmimic/eval_configs/global_config.py | 62 + .../dataset/utils/chartmimic/eval_req.txt | 5 + .../utils/chartmimic/evaluator/__init__.py | 0 .../evaluator/chart_type_and_color.py | 972 ++++ .../evaluator/chart_type_evaluator.py | 181 + .../evaluator/chart_type_evaluator_prefix.py | 375 ++ .../chartmimic/evaluator/color_evaluator.py | 326 ++ .../evaluator/color_evaluator_prefix.py | 840 ++++ .../utils/chartmimic/evaluator/color_utils.py | 85 + .../chartmimic/evaluator/grid_evaluator.py | 183 + .../chartmimic/evaluator/layout_evaluator.py | 168 + .../chartmimic/evaluator/legend_evaluator.py | 196 + .../chartmimic/evaluator/text_evaluator.py | 208 + .../dataset/utils/chartmimic/mp_util.py | 78 + .../vlmeval/dataset/utils/chartqapro.py | 356 ++ .../vlmeval/dataset/utils/chartx_eval.py | 412 ++ .../vlmeval/dataset/utils/corecognition.py | 270 + .../vlmevalkit/vlmeval/dataset/utils/crpe.py | 8 + .../dataset/utils/design2code/__init__.py | 0 .../utils/design2code/dedup_post_gen.py | 78 + .../utils/design2code/ocr_free_utils.py | 291 ++ .../utils/design2code/screenshot_single.py | 73 + .../dataset/utils/design2code/visual_score.py | 619 +++ .../vlmeval/dataset/utils/gsm8k_v.py | 228 + .../dataset/utils/hipho_prompt_inference.py | 120 + .../vlmeval/dataset/utils/hipho_verifier.py | 1349 +++++ .../vlmeval/dataset/utils/hrbench.py | 56 + .../vlmeval/dataset/utils/judge_util.py | 187 + .../vlmevalkit/vlmeval/dataset/utils/lens.py | 219 + .../vlmeval/dataset/utils/llavabench.py | 92 + .../vlmeval/dataset/utils/logicvista.py | 151 + .../vlmeval/dataset/utils/longvideobench.py | 82 + .../vlmevalkit/vlmeval/dataset/utils/lvs.py | 609 +++ .../vlmeval/dataset/utils/mathcanvas.py | 277 ++ .../utils/mathcanvas_evaluate_template.txt | 75 + .../vlmevalkit/vlmeval/dataset/utils/mathv.py | 216 + .../vlmeval/dataset/utils/mathverse.py | 209 + .../vlmeval/dataset/utils/mathvista.py | 167 + .../vlmeval/dataset/utils/megabench/README.md | 51 + .../dataset/utils/megabench/__init__.py | 5 + .../utils/megabench/aggregation/mean_agg.py | 23 + .../utils/megabench/aggregation/min_agg.py | 14 + .../megabench/aggregation/unsupported_agg.py | 8 + .../utils/megabench/aggregation_type.py | 26 + .../dataset/utils/megabench/evaluator.py | 398 ++ .../dataset/utils/megabench/metric_type.py | 260 + .../megabench/parsing/answer_str_parse.py | 130 + .../utils/megabench/parsing/common/parsers.py | 146 + .../utils/megabench/parsing/common/utils.py | 138 + .../utils/megabench/parsing/dummy_parse.py | 6 + .../utils/megabench/parsing/json_parse.py | 17 + .../dataset/utils/megabench/requirements.txt | 15 + .../utils/megabench/response_parse_type.py | 51 + .../scoring/ascii_art_gpt4o_judge.py | 129 + .../utils/megabench/scoring/chess_jaccard.py | 25 + .../megabench/scoring/common/conversions.py | 246 + .../utils/megabench/scoring/common/metrics.py | 102 + .../scoring/common/transformations.py | 120 + .../scoring/constrained_generation.py | 520 ++ .../scoring/coordinate_sequence_match.py | 67 + .../utils/megabench/scoring/dict_equality.py | 44 + .../scoring/dict_exact_match_agg_recall.py | 27 + .../scoring/dict_jaccard_agg_jaccard.py | 28 + .../dict_nbbox_iou_tuple_agg_jaccard.py | 27 + .../scoring/dict_set_equality_agg_jaccard.py | 28 + .../megabench/scoring/exact_str_match.py | 49 + .../exact_str_match_case_insensitive.py | 12 + .../scoring/general_numerical_match.py | 253 + .../utils/megabench/scoring/geo_proximity.py | 100 + .../dataset/utils/megabench/scoring/gleu.py | 18 + .../utils/megabench/scoring/jaccard.py | 75 + .../megabench/scoring/latex_expr_equality.py | 98 + .../longest_common_list_prefix_ratio.py | 15 + .../dataset/utils/megabench/scoring/mse.py | 66 + .../megabench/scoring/multi_ref_phrase.py | 27 + .../utils/megabench/scoring/nbbox_iou.py | 106 + .../utils/megabench/scoring/near_str_match.py | 24 + .../utils/megabench/scoring/nli_entailment.py | 19 + ...rmalized_similarity_damerau_levenshtein.py | 14 + .../scoring/number_rel_diff_ratio.py | 22 + .../megabench/scoring/positive_int_match.py | 31 + .../utils/megabench/scoring/program_judge.py | 141 + .../utils/megabench/scoring/sacrebleu_bleu.py | 23 + .../megabench/scoring/sequence_equality.py | 63 + .../utils/megabench/scoring/set_equality.py | 74 + .../utils/megabench/scoring/set_precision.py | 16 + .../megabench/scoring/simple_str_match.py | 29 + .../megabench/scoring/symbolic_planning.py | 266 + .../megabench/scoring/unsupported_scoring.py | 7 + .../utils/megabench/scoring/vlm_as_judge.py | 237 + .../utils/megabench/scoring/xml_nbbox_iou.py | 34 + .../scoring/xml_norm_point_distance.py | 37 + .../scoring/xml_norm_point_in_bbox.py | 35 + .../utils/megabench/tools/analysis_utils.py | 182 + .../tools/derive_breakdown_results.py | 139 + .../tools/get_si_subset_from_full.py | 90 + .../vlmeval/dataset/utils/megabench/utils.py | 69 + .../vlmevalkit/vlmeval/dataset/utils/mlvu.py | 189 + .../vlmeval/dataset/utils/mmbench_video.py | 71 + .../vlmevalkit/vlmeval/dataset/utils/mmdu.py | 128 + .../vlmeval/dataset/utils/mme_reasoning.py | 1202 +++++ .../dataset/utils/mmhelix/evaluator.py | 51 + .../utils/mmhelix/evaluators/aquarium_eval.py | 526 ++ .../utils/mmhelix/evaluators/binario_eval.py | 1012 ++++ .../utils/mmhelix/evaluators/bridges_eval.py | 391 ++ .../mmhelix/evaluators/calcudoku_eval.py | 218 + .../utils/mmhelix/evaluators/campsite_eval.py | 595 +++ .../mmhelix/evaluators/cryptomath_eval.py | 196 + .../utils/mmhelix/evaluators/eulero_eval.py | 348 ++ .../mmhelix/evaluators/futoshiki_eval.py | 509 ++ .../mmhelix/evaluators/graph_problems_eval.py | 984 ++++ .../utils/mmhelix/evaluators/hanoi_eval.py | 42 + .../utils/mmhelix/evaluators/hitori_eval.py | 430 ++ .../utils/mmhelix/evaluators/kakuro_eval.py | 598 +++ .../utils/mmhelix/evaluators/kukurasu_eval.py | 156 + .../utils/mmhelix/evaluators/maze_eval.py | 34 + .../mmhelix/evaluators/minesweeper_eval.py | 86 + .../utils/mmhelix/evaluators/nibbles_eval.py | 255 + .../utils/mmhelix/evaluators/nonogram_eval.py | 85 + .../utils/mmhelix/evaluators/numbrix_eval.py | 172 + .../utils/mmhelix/evaluators/shingoki_eval.py | 795 +++ .../evaluators/skyscrapers_evaluator.py | 230 + .../mmhelix/evaluators/slidingpuzzle_eval.py | 42 + .../utils/mmhelix/evaluators/snake_eval.py | 58 + .../utils/mmhelix/evaluators/sokoban_eval.py | 230 + .../mmhelix/evaluators/sudoku_evaluator.py | 111 + .../utils/mmhelix/evaluators/tapa_eval.py | 525 ++ .../evaluators/twentyfourpoints_evaluator.py | 243 + .../mmhelix/evaluators/wordladder_eval.py | 273 ++ .../mmhelix/evaluators/wordsearch_eval.py | 87 + .../vlmeval/dataset/utils/mmhelix/metrics.py | 71 + .../vlmeval/dataset/utils/mmhelix/parser.py | 71 + .../dataset/utils/mmhelix/utils/constants.py | 392 ++ .../dataset/utils/mmhelix/utils/validation.py | 224 + .../vlmeval/dataset/utils/mmif/__init__.py | 0 .../utils/mmif/function_and_compare.py | 431 ++ .../vlmeval/dataset/utils/mmniah.py | 298 ++ .../vlmevalkit/vlmeval/dataset/utils/mmsci.py | 437 ++ .../vlmeval/dataset/utils/mmsci4eval_req.txt | 6 + .../vlmevalkit/vlmeval/dataset/utils/mmvet.py | 110 + .../vlmeval/dataset/utils/moviechat1k.py | 114 + .../vlmeval/dataset/utils/multiple_choice.py | 686 +++ .../vlmeval/dataset/utils/mvbench.py | 512 ++ .../vlmeval/dataset/utils/naturalbench.py | 145 + .../dataset/utils/oceanoctbench/eval_req.txt | 2 + .../vlmeval/dataset/utils/ocr_reasoning.py | 173 + .../vlmeval/dataset/utils/ocrbench.py | 66 + .../vlmeval/dataset/utils/ocrbrnch_v2_eval.py | 445 ++ .../vlmeval/dataset/utils/olympiadbench.py | 698 +++ .../vlmeval/dataset/utils/omni3dbench.py | 100 + .../vlmeval/dataset/utils/omni_verifier.py | 222 + .../vlmeval/dataset/utils/physic.py | 105 + .../dataset/utils/physics_eval_utils.py | 147 + .../vlmevalkit/vlmeval/dataset/utils/phyx.py | 374 ++ .../vlmeval/dataset/utils/puzzlevqa.py | 154 + .../vlmeval/dataset/utils/qbench_video.py | 46 + .../vlmeval/dataset/utils/qspatial.py | 121 + .../vlmeval/dataset/utils/result_format.py | 42 + .../vlmeval/dataset/utils/sarena.py | 1041 ++++ .../vlmeval/dataset/utils/seephys.py | 318 ++ .../vlmeval/dataset/utils/shortqa.py | 275 ++ .../vlmeval/dataset/utils/simplevqa.py | 82 + .../vlmeval/dataset/utils/spatial457.py | 169 + .../dataset/utils/spatial_bench/__init__.py | 0 .../dataset/utils/spatial_bench/cal_scores.py | 543 +++ .../utils/spatial_bench/llm_extract.py | 312 ++ .../utils/spatial_bench/matching_func.py | 293 ++ .../utils/spatial_bench/tools/files.py | 57 + .../utils/spatial_bench/tools/utils.py | 564 +++ .../dataset/utils/ssi_bench/__init__.py | 1 + .../dataset/utils/ssi_bench/prompts.py | 589 +++ .../vlmeval/dataset/utils/tablevqabench.py | 498 ++ .../vlmeval/dataset/utils/tallyqa.py | 66 + .../vlmeval/dataset/utils/tamperbench.py | 723 +++ .../vlmeval/dataset/utils/tdbench.py | 109 + .../vlmeval/dataset/utils/tempcompass.py | 251 + .../vlmeval/dataset/utils/treebench.py | 65 + .../vlmeval/dataset/utils/uni_svg.py | 623 +++ .../dataset/utils/v2pbench/__init__.py | 0 .../vlmeval/dataset/utils/v2pbench/cau_acc.py | 119 + .../vlmeval/dataset/utils/vcrbench/cau_acc.py | 112 + .../dataset/utils/vcrbench/cau_total.py | 88 + .../vlmeval/dataset/utils/vcrbench/eval.py | 230 + .../vlmeval/dataset/utils/vcrbench/prompt.py | 271 + .../vlmevalkit/vlmeval/dataset/utils/vdc.py | 158 + .../vlmeval/dataset/utils/verifier.py | 357 ++ .../dataset/utils/vgrpbench/__init__.py | 0 .../aquarium/filter_prompt.json | 118 + .../battleships/filter_prompt.json | 91 + .../binairo/filter_prompt.json | 67 + .../coloredsudoku/filter_prompt.json | 66 + .../fieldexplore/filter_prompt.json | 102 + .../futoshiki/filter_prompt.json | 66 + .../hitori/filter_prompt.json | 79 + .../jigsawsudoku/filter_prompt.json | 66 + .../kakurasu/filter_prompt.json | 80 + .../kakuro/filter_prompt.json | 54 + .../killersudoku/filter_prompt.json | 90 + .../lightup/filter_prompt.json | 92 + .../nonogram/filter_prompt.json | 78 + .../oddevensudoku/filter_prompt.json | 66 + .../renzoku/filter_prompt.json | 66 + .../skyscraper/filter_prompt.json | 66 + .../starbattle/filter_prompt.json | 49 + .../sudoku/filter_prompt.json | 66 + .../thermometers/filter_prompt.json | 41 + .../treesandtents/filter_prompt.json | 79 + .../dataset/utils/vgrpbench/evaluation.py | 138 + .../utils/vgrpbench/puzzles/aquarium.py | 116 + .../utils/vgrpbench/puzzles/battleships.py | 161 + .../utils/vgrpbench/puzzles/binairo.py | 98 + .../utils/vgrpbench/puzzles/coloredsudoku.py | 60 + .../vgrpbench/puzzles/common_constriants.py | 53 + .../puzzles/common_get_game_factory.py | 43 + .../vgrpbench/puzzles/common_get_prompt.py | 52 + .../puzzles/common_puzzle_factory.py | 136 + .../utils/vgrpbench/puzzles/fieldexplore.py | 65 + .../utils/vgrpbench/puzzles/futoshiki.py | 96 + .../dataset/utils/vgrpbench/puzzles/hitori.py | 110 + .../utils/vgrpbench/puzzles/jigsawsudoku.py | 72 + .../utils/vgrpbench/puzzles/kakurasu.py | 79 + .../dataset/utils/vgrpbench/puzzles/kakuro.py | 93 + .../utils/vgrpbench/puzzles/killersudoku.py | 63 + .../utils/vgrpbench/puzzles/lightup.py | 159 + .../utils/vgrpbench/puzzles/nonogram.py | 132 + .../utils/vgrpbench/puzzles/oddevensudoku.py | 118 + .../utils/vgrpbench/puzzles/renzoku.py | 107 + .../utils/vgrpbench/puzzles/skyscraper.py | 97 + .../utils/vgrpbench/puzzles/starbattle.py | 130 + .../dataset/utils/vgrpbench/puzzles/sudoku.py | 34 + .../utils/vgrpbench/puzzles/thermometers.py | 109 + .../utils/vgrpbench/puzzles/treesandtents.py | 180 + .../vlmeval/dataset/utils/vgrpbench/score.py | 440 ++ .../vlmeval/dataset/utils/video_mmlu.py | 164 + .../vlmeval/dataset/utils/videoholmes.py | 79 + .../vlmeval/dataset/utils/videomme.py | 152 + .../vlmeval/dataset/utils/videott.py | 89 + .../vlmeval/dataset/utils/visualpuzzles.py | 153 + .../vlmeval/dataset/utils/visulogic.py | 123 + .../vlmeval/dataset/utils/vlm2bench.py | 240 + .../vlmeval/dataset/utils/vlmsarebiased.py | 95 + .../vlmeval/dataset/utils/vmcbench.py | 97 + .../vlmeval/dataset/utils/vqa_eval.py | 366 ++ .../vlmeval/dataset/utils/vtcbench.py | 166 + .../vlmeval/dataset/utils/wemath.py | 899 ++++ .../vlmeval/dataset/utils/worldsense.py | 240 + .../vlmevalkit/vlmeval/dataset/utils/yorn.py | 281 ++ .../vlmevalkit/vlmeval/dataset/v2pbench.py | 211 + .../vlmeval/dataset/vantage_pointing.py | 219 + .../vlmevalkit/vlmeval/dataset/vantage_sot.py | 968 ++++ .../vlmevalkit/vlmeval/dataset/vcr.py | 343 ++ .../vlmevalkit/vlmeval/dataset/vcrbench.py | 304 ++ .../vlmevalkit/vlmeval/dataset/vdc.py | 434 ++ .../vlmevalkit/vlmeval/dataset/video_base.py | 264 + .../vlmeval/dataset/video_concat_dataset.py | 89 + .../vlmeval/dataset/video_dataset_config.py | 596 +++ .../vlmeval/dataset/video_holmes.py | 263 + .../vlmevalkit/vlmeval/dataset/video_mmlu.py | 623 +++ .../vlmevalkit/vlmeval/dataset/videomme.py | 295 ++ .../vlmevalkit/vlmeval/dataset/videommmu.py | 720 +++ .../vlmevalkit/vlmeval/dataset/videophy2.py | 17 + .../vlmevalkit/vlmeval/dataset/videott.py | 249 + .../vlmeval/dataset/viewspatialbench.py | 159 + .../vlmevalkit/vlmeval/dataset/visfactor.py | 152 + .../vlmeval/dataset/vl_rewardbench.py | 176 + .../vlmevalkit/vlmeval/dataset/vladbench.py | 652 +++ .../vlmevalkit/vlmeval/dataset/vlm2bench.py | 112 + .../vlmevalkit/vlmeval/dataset/vlmbias.py | 57 + .../vlmevalkit/vlmeval/dataset/vlrmbench.py | 404 ++ .../vlmevalkit/vlmeval/dataset/vsibench.py | 849 ++++ .../vlmeval/dataset/vss_oobe_clips.py | 17 + .../vlmeval/dataset/warehouse_near_miss.py | 17 + .../vlmeval/dataset/warehouse_spatial_ai.py | 17 + .../vlmevalkit/vlmeval/dataset/where2place.py | 17 + .../vlmevalkit/vlmeval/dataset/wildvision.py | 226 + .../vlmevalkit/vlmeval/dataset/worldbench.py | 17 + .../vlmevalkit/vlmeval/dataset/worldsense.py | 349 ++ .../vlmevalkit/vlmeval/dataset/worldvqa.py | 418 ++ .../vlmevalkit/vlmeval/dataset/xstest.py | 147 + .../reasoner/vlmevalkit/vlmeval/inference.py | 280 ++ .../vlmevalkit/vlmeval/inference_api.py | 793 +++ .../vlmevalkit/vlmeval/inference_mt.py | 205 + .../vlmevalkit/vlmeval/inference_video.py | 283 ++ .../vlmevalkit/vlmeval/smp/__init__.py | 4 + .../reasoner/vlmevalkit/vlmeval/smp/file.py | 540 ++ .../reasoner/vlmevalkit/vlmeval/smp/log.py | 123 + .../reasoner/vlmevalkit/vlmeval/smp/misc.py | 287 ++ .../reasoner/vlmevalkit/vlmeval/smp/vlm.py | 201 + .../reasoner/vlmevalkit/vlmeval/tools.py | 687 +++ .../vlmevalkit/vlmeval/utils/__init__.py | 8 + .../vlmeval/utils/eval_3d_grounding.py | 431 ++ .../vlmevalkit/vlmeval/utils/matching_util.py | 160 + .../vlmevalkit/vlmeval/utils/mp_util.py | 147 + .../vlmeval/utils/result_transfer.py | 105 + .../vlmevalkit/vlmeval/utils/video_cache.py | 317 ++ .../vlmevalkit/vlmeval/vlm/__init__.py | 105 + .../reasoner/vlmevalkit/vlmeval/vlm/aki.py | 106 + .../reasoner/vlmevalkit/vlmeval/vlm/aria.py | 208 + .../vlmevalkit/vlmeval/vlm/bagel_umm.py | 476 ++ .../reasoner/vlmevalkit/vlmeval/vlm/base.py | 224 + .../vlmevalkit/vlmeval/vlm/bunnyllama3.py | 136 + .../vlmevalkit/vlmeval/vlm/cambrian.py | 86 + .../vlmevalkit/vlmeval/vlm/cambrian_s.py | 380 ++ .../vlmevalkit/vlmeval/vlm/chameleon.py | 49 + .../reasoner/vlmevalkit/vlmeval/vlm/cogvlm.py | 330 ++ .../reasoner/vlmevalkit/vlmeval/vlm/cosmos.py | 77 + .../vlmeval/vlm/cosmos_reason_hf.py | 481 ++ .../reasoner/vlmevalkit/vlmeval/vlm/covt.py | 384 ++ .../vlmevalkit/vlmeval/vlm/deepseek_ocr.py | 146 + .../vlmevalkit/vlmeval/vlm/deepseek_vl.py | 83 + .../vlmevalkit/vlmeval/vlm/deepseek_vl2.py | 164 + .../vlmevalkit/vlmeval/vlm/eagle_x.py | 144 + .../reasoner/vlmevalkit/vlmeval/vlm/emu.py | 312 ++ .../vlmevalkit/vlmeval/vlm/falcon_vlm.py | 33 + .../vlmevalkit/vlmeval/vlm/flash_vl.py | 143 + .../reasoner/vlmevalkit/vlmeval/vlm/gemma.py | 230 + .../vlmevalkit/vlmeval/vlm/granite_docling.py | 435 ++ .../vlmeval/vlm/granite_vision/__init__.py | 3 + .../vlm/granite_vision/granite_vision.py | 194 + .../vlmeval/vlm/h2ovl_mississippi.py | 119 + .../vlmeval/vlm/hawk_vl/__init__.py | 2 + .../vlmeval/vlm/hawk_vl/hawk/__init__.py | 1 + .../vlmeval/vlm/hawk_vl/hawk/constants.py | 14 + .../vlm/hawk_vl/hawk/model/__init__.py | 1 + .../vlm/hawk_vl/hawk/model/hawk_arch.py | 132 + .../hawk/model/language_model/hawk_qwen.py | 166 + .../model/multimodal_projector/builder.py | 86 + .../hawk/model/vision_encoder/__init__.py | 5 + .../model/vision_encoder/qwen_vit/__init__.py | 2 + .../qwen_vit/configuration_qwen_vit.py | 56 + .../qwen_vit/modeling_qwen_vit.py | 380 ++ .../vlmeval/vlm/hawk_vl/hawk/utils.py | 16 + .../vlmevalkit/vlmeval/vlm/hawk_vl/model.py | 152 + .../vlmevalkit/vlmeval/vlm/hawk_vl/prompt.py | 158 + .../vlmevalkit/vlmeval/vlm/idefics.py | 311 ++ .../vlmevalkit/vlmeval/vlm/insight_v.py | 247 + .../vlmevalkit/vlmeval/vlm/instructblip.py | 59 + .../vlmeval/vlm/interns1/__init__.py | 3 + .../vlmeval/vlm/interns1/interns1_chat.py | 294 ++ .../vlmeval/vlm/internvl/__init__.py | 3 + .../vlmeval/vlm/internvl/gui_template.yaml | 45 + .../vlmeval/vlm/internvl/internvl_chat.py | 574 +++ .../vlmevalkit/vlmeval/vlm/internvl/utils.py | 329 ++ .../reasoner/vlmevalkit/vlmeval/vlm/janus.py | 139 + .../vlmeval/vlm/keye_vlm/__init__.py | 1 + .../vlmevalkit/vlmeval/vlm/keye_vlm/model.py | 279 ++ .../vlmevalkit/vlmeval/vlm/keye_vlm/prompt.py | 156 + .../vlmevalkit/vlmeval/vlm/kimi_vl.py | 144 + .../reasoner/vlmevalkit/vlmeval/vlm/kosmos.py | 117 + .../reasoner/vlmevalkit/vlmeval/vlm/liquid.py | 87 + .../reasoner/vlmevalkit/vlmeval/vlm/llama4.py | 287 ++ .../vlmevalkit/vlmeval/vlm/llama_vision.py | 156 + .../vlmevalkit/vlmeval/vlm/llava/__init__.py | 8 + .../vlmevalkit/vlmeval/vlm/llava/llava.py | 910 ++++ .../vlmeval/vlm/llava/llava_xtuner.py | 241 + .../reasoner/vlmevalkit/vlmeval/vlm/logics.py | 73 + .../vlmevalkit/vlmeval/vlm/long_vita.py | 818 ++++ .../reasoner/vlmevalkit/vlmeval/vlm/mantis.py | 206 + .../reasoner/vlmevalkit/vlmeval/vlm/mgm.py | 160 + .../vlmevalkit/vlmeval/vlm/minicpm_v.py | 1425 ++++++ .../vlmevalkit/vlmeval/vlm/minigpt4.py | 88 + .../vlmevalkit/vlmeval/vlm/minimonkey.py | 491 ++ .../vlm/misc/blip2_instruct_vicuna13b.yaml | 43 + .../vlm/misc/blip2_instruct_vicuna7b.yaml | 43 + .../vlmeval/vlm/misc/minigpt4_13b_eval.yaml | 37 + .../vlmeval/vlm/misc/minigpt4_7b_eval.yaml | 38 + .../vlmeval/vlm/misc/minigptv2_eval.yaml | 36 + .../vlmevalkit/vlmeval/vlm/mixsense.py | 46 + .../vlmevalkit/vlmeval/vlm/mmalaya.py | 343 ++ .../reasoner/vlmevalkit/vlmeval/vlm/molmo.py | 212 + .../reasoner/vlmevalkit/vlmeval/vlm/monkey.py | 167 + .../vlmevalkit/vlmeval/vlm/moondream.py | 342 ++ .../vlmevalkit/vlmeval/vlm/mplug_owl2.py | 132 + .../vlmevalkit/vlmeval/vlm/mplug_owl3.py | 342 ++ .../reasoner/vlmevalkit/vlmeval/vlm/nvlm.py | 152 + .../vlmevalkit/vlmeval/vlm/ola/__init__.py | 1 + .../vlmeval/vlm/ola/ola/arguments.py | 65 + .../vlmeval/vlm/ola/ola/constants.py | 14 + .../vlmeval/vlm/ola/ola/conversation.py | 138 + .../vlmeval/vlm/ola/ola/datasets/__init__.py | 0 .../vlm/ola/ola/datasets/preprocess.py | 218 + .../vlmeval/vlm/ola/ola/mm_utils.py | 177 + .../vlmeval/vlm/ola/ola/model/__init__.py | 1 + .../vlmeval/vlm/ola/ola/model/builder.py | 93 + .../ola/ola/model/language_model/ola_qwen.py | 234 + .../ola/model/multimodal_encoder/builder.py | 11 + .../ola/model/multimodal_encoder/oryx_vit.py | 1024 ++++ .../ola/model/multimodal_projector/builder.py | 180 + .../multimodal_projector/pooler_projector.py | 68 + .../ola/model/multimodal_resampler/builder.py | 25 + .../model/multimodal_resampler/perceiver.py | 75 + .../vlmeval/vlm/ola/ola/model/ola_arch.py | 417 ++ .../ola/model/speech_encoder/beats/BEATs.py | 183 + .../model/speech_encoder/beats/Tokenizers.py | 173 + .../model/speech_encoder/beats/__init__.py | 0 .../model/speech_encoder/beats/backbone.py | 778 +++ .../ola/model/speech_encoder/beats/kaldi.py | 813 +++ .../ola/model/speech_encoder/beats/modules.py | 219 + .../model/speech_encoder/beats/quantizer.py | 215 + .../ola/ola/model/speech_encoder/builder.py | 11 + .../model/speech_encoder/speech_encoder.py | 76 + .../ola/ola/model/speech_projector/builder.py | 9 + .../speech_projector/speech_projector.py | 49 + .../vlmevalkit/vlmeval/vlm/ola/ola/utils.py | 214 + .../vlmevalkit/vlmeval/vlm/ola/ola_model.py | 176 + .../reasoner/vlmevalkit/vlmeval/vlm/omchat.py | 162 + .../vlmevalkit/vlmeval/vlm/omnilmm.py | 188 + .../vlmevalkit/vlmeval/vlm/open_flamingo.py | 102 + .../reasoner/vlmevalkit/vlmeval/vlm/oryx.py | 226 + .../vlmevalkit/vlmeval/vlm/ovis/__init__.py | 3 + .../vlmevalkit/vlmeval/vlm/ovis/ovis.py | 982 ++++ .../vlmeval/vlm/ovis/utils/__init__.py | 0 .../vlmevalkit/vlmeval/vlm/ovis/utils/mdp3.py | 378 ++ .../vlmevalkit/vlmeval/vlm/pandagpt.py | 65 + .../reasoner/vlmevalkit/vlmeval/vlm/parrot.py | 220 + .../vlmevalkit/vlmeval/vlm/phi3_vision.py | 162 + .../vlmevalkit/vlmeval/vlm/phi4_multimodal.py | 57 + .../vlmevalkit/vlmeval/vlm/pixtral.py | 72 + .../reasoner/vlmevalkit/vlmeval/vlm/points.py | 308 ++ .../vlmevalkit/vlmeval/vlm/qh_360vl.py | 60 + .../vlmevalkit/vlmeval/vlm/qianfan_vl.py | 124 + .../vlmeval/vlm/qtunevl/__init__.py | 4 + .../vlmeval/vlm/qtunevl/qtune_vl.py | 317 ++ .../vlmeval/vlm/qtunevl/qtune_vl_chat.py | 846 ++++ .../vlmeval/vlm/qwen2_vl/__init__.py | 2 + .../vlmevalkit/vlmeval/vlm/qwen2_vl/model.py | 759 +++ .../vlmevalkit/vlmeval/vlm/qwen2_vl/prompt.py | 157 + .../vlmeval/vlm/qwen3_vl/__init__.py | 5 + .../vlmevalkit/vlmeval/vlm/qwen3_vl/model.py | 428 ++ .../vlmevalkit/vlmeval/vlm/qwen3_vl/prompt.py | 162 + .../vlmevalkit/vlmeval/vlm/qwen_vl.py | 128 + .../reasoner/vlmevalkit/vlmeval/vlm/rbdash.py | 287 ++ .../vlmevalkit/vlmeval/vlm/ristretto.py | 398 ++ .../reasoner/vlmevalkit/vlmeval/vlm/ross.py | 163 + .../vlmevalkit/vlmeval/vlm/sail_vl.py | 657 +++ .../reasoner/vlmevalkit/vlmeval/vlm/slime.py | 77 + .../vlmevalkit/vlmeval/vlm/smolvlm.py | 867 ++++ .../vlmevalkit/vlmeval/vlm/spatial_mllm.py | 402 ++ .../vlmevalkit/vlmeval/vlm/thyme/__init__.py | 2 + .../vlmevalkit/vlmeval/vlm/thyme/model.py | 612 +++ .../vlmevalkit/vlmeval/vlm/thyme/prompt.py | 270 + .../vlmevalkit/vlmeval/vlm/thyme/sandbox.py | 990 ++++ .../vlmevalkit/vlmeval/vlm/thyme/utils.py | 65 + .../vlmevalkit/vlmeval/vlm/transcore_m.py | 171 + .../vlmevalkit/vlmeval/vlm/treevgr.py | 373 ++ .../vlmevalkit/vlmeval/vlm/ursa/__init__.py | 1 + .../vlmevalkit/vlmeval/vlm/ursa/ursa_chat.py | 169 + .../vlmeval/vlm/ursa/ursa_model/__init__.py | 30 + .../vlm/ursa/ursa_model/clip_encoder.py | 242 + .../vlm/ursa/ursa_model/configuration_ursa.py | 145 + .../ursa/ursa_model/image_processing_vlm.py | 208 + .../vlm/ursa/ursa_model/modeling_ursa.py | 704 +++ .../vlm/ursa/ursa_model/processing_ursa.py | 61 + .../vlmeval/vlm/ursa/ursa_model/projector.py | 100 + .../vlmeval/vlm/ursa/ursa_model/sam.py | 593 +++ .../vlmeval/vlm/ursa/ursa_model/siglip_vit.py | 663 +++ .../vlmevalkit/vlmeval/vlm/valley/__init__.py | 2 + .../vlm/valley/requirements_valley.txt | 33 + .../vlmevalkit/vlmeval/vlm/valley/valley2.py | 245 + .../vlmevalkit/vlmeval/vlm/valley/valley3.py | 418 ++ .../vlmevalkit/vlmeval/vlm/varco_vision.py | 273 ++ .../vlmeval/vlm/video_llm/__init__.py | 8 + .../vlmeval/vlm/video_llm/chat_uni_vi.py | 197 + .../processor/clip-patch14-224/config.json | 171 + .../clip-patch14-224/preprocessor_config.json | 19 + .../vlm/video_llm/configs/videochat2_hd.json | 56 + .../vlmeval/vlm/video_llm/llama_vid.py | 139 + .../vlmeval/vlm/video_llm/pllava.py | 99 + .../vlmeval/vlm/video_llm/video_chatgpt.py | 64 + .../vlmeval/vlm/video_llm/video_llava.py | 179 + .../vlmeval/vlm/video_llm/videochat2.py | 438 ++ .../reasoner/vlmevalkit/vlmeval/vlm/vila.py | 229 + .../vlmevalkit/vlmeval/vlm/vintern_chat.py | 394 ++ .../vlmevalkit/vlmeval/vlm/visualglm.py | 38 + .../reasoner/vlmevalkit/vlmeval/vlm/vita.py | 428 ++ .../vlmevalkit/vlmeval/vlm/vlaa_thinker.py | 178 + .../reasoner/vlmevalkit/vlmeval/vlm/vlm3r.py | 276 ++ .../reasoner/vlmevalkit/vlmeval/vlm/vlm_r1.py | 234 + .../vlmevalkit/vlmeval/vlm/vxverse.py | 130 + .../reasoner/vlmevalkit/vlmeval/vlm/wemm.py | 73 + .../vlmevalkit/vlmeval/vlm/wethink_vl.py | 170 + .../reasoner/vlmevalkit/vlmeval/vlm/x_vl.py | 59 + .../vlmeval/vlm/xcomposer/__init__.py | 7 + .../vlmeval/vlm/xcomposer/sharecaptioner.py | 97 + .../vlmeval/vlm/xcomposer/xcomposer.py | 151 + .../vlmeval/vlm/xcomposer/xcomposer2.py | 218 + .../vlmeval/vlm/xcomposer/xcomposer2_4KHD.py | 244 + .../vlmeval/vlm/xcomposer/xcomposer2d5.py | 412 ++ .../vlmevalkit/vlmeval/vlm/xgen_mm.py | 81 + .../reasoner/vlmevalkit/vlmeval/vlm/yi_vl.py | 157 + 880 files changed, 189798 insertions(+) create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/.github/scripts/assert_score.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/.github/workflows/lint.yml create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/.github/workflows/pr-run-test.yml create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/.gitignore create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/.pre-commit-config.yaml create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/LICENSE create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/README-vlmevalkit.md create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/README.md create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/assets/0_lamp_pred.png create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/assets/LOGO.svg create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/assets/apple.jpg create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/assets/sample.mp4 create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/cosmos_eval/.gitignore create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/cosmos_eval/data/metropolis/AETCBench_all.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/cosmos_eval/data/metropolis/VANTAGE_2DGrounding.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/cosmos_eval/data/metropolis/VANTAGE_2DPointing.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/cosmos_eval/data/metropolis/VANTAGE_Astro2D.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/cosmos_eval/data/metropolis/VANTAGE_DVC.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/cosmos_eval/data/metropolis/VANTAGE_EventVerification.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/cosmos_eval/data/metropolis/VANTAGE_SOT.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/cosmos_eval/data/metropolis/VANTAGE_Temporal.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/cosmos_eval/data/metropolis/VANTAGE_VQA.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/cosmos_eval/manifest.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/cosmos_eval/models/cosmos.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/cosmos_eval/parse_score.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/cosmos_eval/run_all.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/en/.readthedocs.yaml create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/en/ConfigSystem.md create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/en/Contributors.md create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/en/Development.md create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/en/EvalByLMDeploy.md create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/en/Makefile create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/en/Quickstart.md create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/en/_static/css/readthedocs.css create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/en/_static/image/logo.svg create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/en/_static/image/logo_icon.svg create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/en/_static/js/custom.js create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/en/_templates/404.html create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/en/_templates/autosummary/class.rst create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/en/_templates/callable.rst create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/en/conf.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/en/docutils.conf create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/en/index.rst create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/ja/README_ja.md create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/.readthedocs.yaml create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/ConfigSystem.md create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/Development.md create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/EvalByLMDeploy.md create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/Makefile create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/Quickstart.md create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/README_zh-CN.md create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/_static/css/readthedocs.css create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/_static/image/logo.svg create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/_static/image/logo_icon.svg create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/_static/js/custom.js create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/_templates/404.html create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/_templates/autosummary/class.rst create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/_templates/callable.rst create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/conf.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/cp_origin_docs.sh create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/docutils.conf create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/index.rst create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/requirements.txt create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/requirements/docs.txt create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/run.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/run_api.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/scripts/AI2D_preproc.ipynb create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/scripts/apires_scan.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/scripts/auto_run.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/scripts/convert_macbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/scripts/cover.sh create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/scripts/data_browser.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/scripts/mmb_eval_gradio.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/scripts/prepare_spar_bench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/scripts/run.sh create mode 100755 evaluation/cosmos3/reasoner/vlmevalkit/scripts/run_endpoint_bench.sh create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/scripts/srun.sh create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/scripts/summarize.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/scripts/visualize.ipynb create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/setup.cfg create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/setup.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/adapters/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/adapters/base.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/adapters/cogvlm2.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/adapters/interns1_1.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/adapters/internvl2.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/adapters/internvl3.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/adapters/qwen3.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/arm_thinker.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/bailingmm.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/base.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/bedrock.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/bluelm_api.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/claude.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/cloudwalk.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/cosmos_reason.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/doubao_vl_api.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/gcp_vertex.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/gemini.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/glm_vision.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/gpt.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/hf_chat_model.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/hunyuan.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/jt_vl_chat.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/jt_vl_chat_mini.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/kimivl_api.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/lmdeploy.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/lvs_service.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/mimo.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/minimax_api.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/mug_u.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/nemotron.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/nv_gemini.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/openai_sdk.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/qwen_api.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/qwen_vl_api.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/rbdashmm_chat3_5_api.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/rbdashmm_chat3_api.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/reka.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/sensechat_vision.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/siliconflow.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/stepai.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/taichu.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/taiyi.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/telemm.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/telemm_thinking.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/together.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/video_chat_online_v2.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/config.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/CGAVCounting/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/CGAVCounting/cg_av_counting.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/CGAVCounting/requirements.txt create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/CGAVCounting/utils.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/EgoExoBench/README.md create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/EgoExoBench/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/EgoExoBench/cvmhat_preprocess.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/EgoExoBench/egoexobench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/EgoExoBench/tf2023_preprocess.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/EgoExoBench/utils.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/GUI/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/GUI/osworld_g.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/GUI/screenspot.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/GUI/screenspot_pro.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/GUI/screenspot_v2.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/GUI/vbgd.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/GUI/venusbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/IFBench/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/IFBench/ifbench.py create mode 100755 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/NPMM.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/Omni3D/README.md create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/Omni3D/configs/basic_gt.yaml create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/Omni3D/configs/category_meta.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/Omni3D/install_patch.sh create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/Omni3D/install_patch_vllm.sh create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/Omni3D/omni3d_dataset.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/OmniDocBench/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/OmniDocBench/data_preprocess.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/OmniDocBench/metrics.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/OmniDocBench/omnidocbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/OmniDocBench/requirements.txt create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/OmniDocBench/utils.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/SGI_Bench_1_0/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/SGI_Bench_1_0/deep_research.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/SGI_Bench_1_0/dry_experiment.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/SGI_Bench_1_0/dry_experiment_requirements.txt create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/SGI_Bench_1_0/experimental_reasoning.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/SGI_Bench_1_0/idea_generation.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/SGI_Bench_1_0/readme.md create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/SGI_Bench_1_0/utils.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/SGI_Bench_1_0/wet_experiment.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/aetcbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/asclepius.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/av_3d_grounding.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/av_prompt_following.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/av_speakerbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/avspecial_collision_bench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/avspecial_environment_bench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/avspecial_ood_reasoning_bench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/avspecial_stop_behavior_bench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/blink_depth.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/blink_spatial.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/camera_bench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/camera_intrinsic.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/causalvqa.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/cgbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/chartbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/chartcap.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/chartmimic.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/chartmuseum.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/chartqapro.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/chartx.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/charxiv.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/cmmmu.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/cosmos_cab_image.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/cosmos_cab_video.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/cosmos_erqa.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/cosmos_reason.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/creation.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/cv_bench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/da2k.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/design2code.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/dream.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/dsrbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/dude.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/dynamath.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/embspatialbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/emma.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/eriq.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/erqa.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/erqabench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/flames.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/foxbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/gobench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/groundingme.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/gsm8k_v.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/health_surgi_bench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/hipho.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/image_base.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/image_caption.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/image_ccocr.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/image_mcq.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/image_mt.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/image_shortqa.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/image_vqa.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/image_yorn.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/intphys2.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/its_collision.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/lingoqa.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/locate_anything_bench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/longvideobench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/lv_event_verification.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/lvs.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/lvs_ai_hallucination.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/m3oralbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/m4bench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/macbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/matbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/medqbench_caption.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/medqbench_mcq.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/medqbench_paired_description.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/megabench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/metropolis2d/README_detection.md create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/metropolis2d/README_grounding.md create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/metropolis2d/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/metropolis2d/astro_2d_dataset.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/metropolis2d/datasets.yaml create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/metropolis2d/detection_2d_dataset.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/metropolis2d/grounding_2d_dataset.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/metropolis2d/utils.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/metropolis_dvc.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/metropolis_event_verification.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/metropolis_temporal.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/metropolis_vqa.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/miabench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/mindcubebench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/mlvu.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/mmalignbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/mmbench_video.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/mmesci.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/mmgenbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/mmhelix.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/mmifeval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/mmlongbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/mmmath.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/mmoral_opg_closed.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/mmoral_opg_open.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/mmreason.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/mmsafetybench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/mmsi_video.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/mmsibench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/moat.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/motionbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/moviechat1k.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/mssbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/mvbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/mvpbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/mvu_eval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/oceanocr.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/odinw13.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/olmOCRBench/eval_req.txt create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/olmOCRBench/evaluator.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/olmOCRBench/katex/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/olmOCRBench/katex/auto-render.min.js create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/olmOCRBench/katex/katex.min.css create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/olmOCRBench/katex/katex.min.js create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/olmOCRBench/katex/render.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/olmOCRBench/olmocrbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/olmOCRBench/repeatdetect.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/olmOCRBench/tests.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/olmOCRBench/utils.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/omnispatialbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/omtgbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/ost_bench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/plotqa.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/qbench_video.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/reasonmap_plus.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/refcoco.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/refspatial.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/refspatialbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/robospatial_home.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/robospatialbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/sarena.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/sat_bench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/sfebench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/share_robot_bench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/simplevqa.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/sitebench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/siuo.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/siuo_gen.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/siuo_mcq.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/slidevqa.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/sop_action_recognition.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/sop_temporal_localization.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/spar_bench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/sparbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/spatial457.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/spatialvizbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/spbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/ssi_bench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/starebench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/stibench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/tailgating.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/tamperbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/tempcompass.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/temporal_localization_bench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/text_base.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/text_mcq.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/uni_svg.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/NPMM/dominating_set.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/NPMM/feedback_vertex.py create mode 100755 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/NPMM/gcp.py create mode 100755 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/NPMM/hamiltonian_cycle.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/NPMM/maximum_cut.py create mode 100755 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/NPMM/maximum_set.py create mode 100755 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/NPMM/mcp.py create mode 100755 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/NPMM/minimum_cut.py create mode 100755 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/NPMM/tsp.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/NPMM/vertex_cover.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/Ocrbench_v2/IoUscore_metric.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/Ocrbench_v2/TEDS_metric.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/Ocrbench_v2/page_ocr_metric.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/Ocrbench_v2/parallel.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/Ocrbench_v2/requirements.txt create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/Ocrbench_v2/spotting_eval/rrc_evaluation_funcs_1_1.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/Ocrbench_v2/spotting_eval/script.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/Ocrbench_v2/spotting_metric.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/Ocrbench_v2/vqa_metric.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/SArena/CLIP_Score.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/SArena/DINO_Score.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/SArena/FID.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/SArena/LPIPS.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/SArena/PSNR.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/SArena/SSIM.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/SArena/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/SArena/average_meter.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/SArena/base_metric.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/SArena/inception.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/SArena/metrics.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/SArena/requirements_sarena.txt create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/SArena/token_length.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/SArena/utils/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/SArena/utils/raster_svg.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/SArena/video/CLIP_video.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/SArena/video/DINO_video.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/SArena/video/FVD.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/SArena/video/LPIPS_video.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/SArena/video/PSNR_video.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/SArena/video/SSIM_video.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/SArena/video/viclip/bpe_simple_vocab_16e6.txt.gz create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/SArena/video/viclip/simple_tokenizer.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/SArena/video/viclip/viclip.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/SArena/video/viclip/viclip_text.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/SArena/video/viclip/viclip_vision.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/SArena/video/video_metrics.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/ayavision.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/bmmr.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/bmmr_grade.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/ccocr_evaluator/README.md create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/ccocr_evaluator/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/ccocr_evaluator/common.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/ccocr_evaluator/doc_parsing_evaluator.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/ccocr_evaluator/kie_evaluator.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/ccocr_evaluator/ocr_evaluator.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/cgbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/chartmimic/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/chartmimic/eval_configs/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/chartmimic/eval_configs/global_config.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/chartmimic/eval_req.txt create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/chartmimic/evaluator/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/chartmimic/evaluator/chart_type_and_color.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/chartmimic/evaluator/chart_type_evaluator.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/chartmimic/evaluator/chart_type_evaluator_prefix.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/chartmimic/evaluator/color_evaluator.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/chartmimic/evaluator/color_evaluator_prefix.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/chartmimic/evaluator/color_utils.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/chartmimic/evaluator/grid_evaluator.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/chartmimic/evaluator/layout_evaluator.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/chartmimic/evaluator/legend_evaluator.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/chartmimic/evaluator/text_evaluator.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/chartmimic/mp_util.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/chartqapro.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/chartx_eval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/corecognition.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/crpe.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/design2code/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/design2code/dedup_post_gen.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/design2code/ocr_free_utils.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/design2code/screenshot_single.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/design2code/visual_score.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/gsm8k_v.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/hipho_prompt_inference.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/hipho_verifier.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/hrbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/judge_util.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/lens.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/llavabench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/logicvista.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/longvideobench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/lvs.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mathcanvas.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mathcanvas_evaluate_template.txt create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mathv.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mathverse.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mathvista.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/README.md create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/aggregation/mean_agg.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/aggregation/min_agg.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/aggregation/unsupported_agg.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/aggregation_type.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/evaluator.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/metric_type.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/parsing/answer_str_parse.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/parsing/common/parsers.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/parsing/common/utils.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/parsing/dummy_parse.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/parsing/json_parse.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/requirements.txt create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/response_parse_type.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/ascii_art_gpt4o_judge.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/chess_jaccard.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/common/conversions.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/common/metrics.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/common/transformations.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/constrained_generation.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/coordinate_sequence_match.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/dict_equality.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/dict_exact_match_agg_recall.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/dict_jaccard_agg_jaccard.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/dict_nbbox_iou_tuple_agg_jaccard.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/dict_set_equality_agg_jaccard.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/exact_str_match.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/exact_str_match_case_insensitive.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/general_numerical_match.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/geo_proximity.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/gleu.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/jaccard.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/latex_expr_equality.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/longest_common_list_prefix_ratio.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/mse.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/multi_ref_phrase.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/nbbox_iou.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/near_str_match.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/nli_entailment.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/normalized_similarity_damerau_levenshtein.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/number_rel_diff_ratio.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/positive_int_match.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/program_judge.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/sacrebleu_bleu.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/sequence_equality.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/set_equality.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/set_precision.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/simple_str_match.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/symbolic_planning.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/unsupported_scoring.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/vlm_as_judge.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/xml_nbbox_iou.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/xml_norm_point_distance.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/scoring/xml_norm_point_in_bbox.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/tools/analysis_utils.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/tools/derive_breakdown_results.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/tools/get_si_subset_from_full.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/megabench/utils.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mlvu.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmbench_video.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmdu.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mme_reasoning.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/evaluator.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/evaluators/aquarium_eval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/evaluators/binario_eval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/evaluators/bridges_eval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/evaluators/calcudoku_eval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/evaluators/campsite_eval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/evaluators/cryptomath_eval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/evaluators/eulero_eval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/evaluators/futoshiki_eval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/evaluators/graph_problems_eval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/evaluators/hanoi_eval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/evaluators/hitori_eval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/evaluators/kakuro_eval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/evaluators/kukurasu_eval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/evaluators/maze_eval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/evaluators/minesweeper_eval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/evaluators/nibbles_eval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/evaluators/nonogram_eval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/evaluators/numbrix_eval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/evaluators/shingoki_eval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/evaluators/skyscrapers_evaluator.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/evaluators/slidingpuzzle_eval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/evaluators/snake_eval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/evaluators/sokoban_eval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/evaluators/sudoku_evaluator.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/evaluators/tapa_eval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/evaluators/twentyfourpoints_evaluator.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/evaluators/wordladder_eval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/evaluators/wordsearch_eval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/metrics.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/parser.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/utils/constants.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmhelix/utils/validation.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmif/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmif/function_and_compare.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmniah.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmsci.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmsci4eval_req.txt create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mmvet.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/moviechat1k.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/multiple_choice.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/mvbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/naturalbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/oceanoctbench/eval_req.txt create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/ocr_reasoning.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/ocrbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/ocrbrnch_v2_eval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/olympiadbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/omni3dbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/omni_verifier.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/physic.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/physics_eval_utils.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/phyx.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/puzzlevqa.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/qbench_video.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/qspatial.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/result_format.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/sarena.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/seephys.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/shortqa.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/simplevqa.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/spatial457.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/spatial_bench/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/spatial_bench/cal_scores.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/spatial_bench/llm_extract.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/spatial_bench/matching_func.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/spatial_bench/tools/files.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/spatial_bench/tools/utils.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/ssi_bench/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/ssi_bench/prompts.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/tablevqabench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/tallyqa.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/tamperbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/tdbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/tempcompass.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/treebench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/uni_svg.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/v2pbench/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/v2pbench/cau_acc.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vcrbench/cau_acc.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vcrbench/cau_total.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vcrbench/eval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vcrbench/prompt.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vdc.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/verifier.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/configs/formating-prompt/aquarium/filter_prompt.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/configs/formating-prompt/battleships/filter_prompt.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/configs/formating-prompt/binairo/filter_prompt.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/configs/formating-prompt/coloredsudoku/filter_prompt.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/configs/formating-prompt/fieldexplore/filter_prompt.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/configs/formating-prompt/futoshiki/filter_prompt.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/configs/formating-prompt/hitori/filter_prompt.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/configs/formating-prompt/jigsawsudoku/filter_prompt.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/configs/formating-prompt/kakurasu/filter_prompt.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/configs/formating-prompt/kakuro/filter_prompt.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/configs/formating-prompt/killersudoku/filter_prompt.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/configs/formating-prompt/lightup/filter_prompt.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/configs/formating-prompt/nonogram/filter_prompt.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/configs/formating-prompt/oddevensudoku/filter_prompt.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/configs/formating-prompt/renzoku/filter_prompt.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/configs/formating-prompt/skyscraper/filter_prompt.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/configs/formating-prompt/starbattle/filter_prompt.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/configs/formating-prompt/sudoku/filter_prompt.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/configs/formating-prompt/thermometers/filter_prompt.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/configs/formating-prompt/treesandtents/filter_prompt.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/evaluation.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/puzzles/aquarium.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/puzzles/battleships.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/puzzles/binairo.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/puzzles/coloredsudoku.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/puzzles/common_constriants.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/puzzles/common_get_game_factory.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/puzzles/common_get_prompt.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/puzzles/common_puzzle_factory.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/puzzles/fieldexplore.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/puzzles/futoshiki.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/puzzles/hitori.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/puzzles/jigsawsudoku.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/puzzles/kakurasu.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/puzzles/kakuro.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/puzzles/killersudoku.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/puzzles/lightup.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/puzzles/nonogram.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/puzzles/oddevensudoku.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/puzzles/renzoku.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/puzzles/skyscraper.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/puzzles/starbattle.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/puzzles/sudoku.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/puzzles/thermometers.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/puzzles/treesandtents.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vgrpbench/score.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/video_mmlu.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/videoholmes.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/videomme.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/videott.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/visualpuzzles.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/visulogic.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vlm2bench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vlmsarebiased.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vmcbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vqa_eval.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/vtcbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/wemath.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/worldsense.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/utils/yorn.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/v2pbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/vantage_pointing.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/vantage_sot.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/vcr.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/vcrbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/vdc.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/video_base.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/video_concat_dataset.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/video_dataset_config.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/video_holmes.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/video_mmlu.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/videomme.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/videommmu.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/videophy2.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/videott.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/viewspatialbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/visfactor.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/vl_rewardbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/vladbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/vlm2bench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/vlmbias.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/vlrmbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/vsibench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/vss_oobe_clips.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/warehouse_near_miss.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/warehouse_spatial_ai.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/where2place.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/wildvision.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/worldbench.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/worldsense.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/worldvqa.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/xstest.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/inference.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/inference_api.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/inference_mt.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/inference_video.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/smp/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/smp/file.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/smp/log.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/smp/misc.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/smp/vlm.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/tools.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/utils/__init__.py create mode 100755 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/utils/eval_3d_grounding.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/utils/matching_util.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/utils/mp_util.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/utils/result_transfer.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/utils/video_cache.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/aki.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/aria.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/bagel_umm.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/base.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/bunnyllama3.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/cambrian.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/cambrian_s.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/chameleon.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/cogvlm.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/cosmos.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/cosmos_reason_hf.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/covt.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/deepseek_ocr.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/deepseek_vl.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/deepseek_vl2.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/eagle_x.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/emu.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/falcon_vlm.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/flash_vl.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/gemma.py create mode 100755 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/granite_docling.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/granite_vision/__init__.py create mode 100755 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/granite_vision/granite_vision.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/h2ovl_mississippi.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/hawk_vl/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/hawk_vl/hawk/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/hawk_vl/hawk/constants.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/hawk_vl/hawk/model/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/hawk_vl/hawk/model/hawk_arch.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/hawk_vl/hawk/model/language_model/hawk_qwen.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/hawk_vl/hawk/model/multimodal_projector/builder.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/hawk_vl/hawk/model/vision_encoder/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/hawk_vl/hawk/model/vision_encoder/qwen_vit/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/hawk_vl/hawk/model/vision_encoder/qwen_vit/configuration_qwen_vit.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/hawk_vl/hawk/model/vision_encoder/qwen_vit/modeling_qwen_vit.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/hawk_vl/hawk/utils.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/hawk_vl/model.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/hawk_vl/prompt.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/idefics.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/insight_v.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/instructblip.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/interns1/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/interns1/interns1_chat.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/internvl/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/internvl/gui_template.yaml create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/internvl/internvl_chat.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/internvl/utils.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/janus.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/keye_vlm/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/keye_vlm/model.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/keye_vlm/prompt.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/kimi_vl.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/kosmos.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/liquid.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/llama4.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/llama_vision.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/llava/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/llava/llava.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/llava/llava_xtuner.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/logics.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/long_vita.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/mantis.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/mgm.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/minicpm_v.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/minigpt4.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/minimonkey.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/misc/blip2_instruct_vicuna13b.yaml create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/misc/blip2_instruct_vicuna7b.yaml create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/misc/minigpt4_13b_eval.yaml create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/misc/minigpt4_7b_eval.yaml create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/misc/minigptv2_eval.yaml create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/mixsense.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/mmalaya.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/molmo.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/monkey.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/moondream.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/mplug_owl2.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/mplug_owl3.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/nvlm.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ola/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ola/ola/arguments.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ola/ola/constants.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ola/ola/conversation.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ola/ola/datasets/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ola/ola/datasets/preprocess.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ola/ola/mm_utils.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ola/ola/model/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ola/ola/model/builder.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ola/ola/model/language_model/ola_qwen.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ola/ola/model/multimodal_encoder/builder.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ola/ola/model/multimodal_encoder/oryx_vit.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ola/ola/model/multimodal_projector/builder.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ola/ola/model/multimodal_projector/pooler_projector.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ola/ola/model/multimodal_resampler/builder.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ola/ola/model/multimodal_resampler/perceiver.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ola/ola/model/ola_arch.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ola/ola/model/speech_encoder/beats/BEATs.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ola/ola/model/speech_encoder/beats/Tokenizers.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ola/ola/model/speech_encoder/beats/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ola/ola/model/speech_encoder/beats/backbone.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ola/ola/model/speech_encoder/beats/kaldi.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ola/ola/model/speech_encoder/beats/modules.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ola/ola/model/speech_encoder/beats/quantizer.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ola/ola/model/speech_encoder/builder.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ola/ola/model/speech_encoder/speech_encoder.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ola/ola/model/speech_projector/builder.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ola/ola/model/speech_projector/speech_projector.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ola/ola/utils.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ola/ola_model.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/omchat.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/omnilmm.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/open_flamingo.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/oryx.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ovis/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ovis/ovis.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ovis/utils/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ovis/utils/mdp3.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/pandagpt.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/parrot.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/phi3_vision.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/phi4_multimodal.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/pixtral.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/points.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/qh_360vl.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/qianfan_vl.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/qtunevl/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/qtunevl/qtune_vl.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/qtunevl/qtune_vl_chat.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/qwen2_vl/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/qwen2_vl/model.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/qwen2_vl/prompt.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/qwen3_vl/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/qwen3_vl/model.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/qwen3_vl/prompt.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/qwen_vl.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/rbdash.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ristretto.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ross.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/sail_vl.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/slime.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/smolvlm.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/spatial_mllm.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/thyme/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/thyme/model.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/thyme/prompt.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/thyme/sandbox.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/thyme/utils.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/transcore_m.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/treevgr.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ursa/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ursa/ursa_chat.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ursa/ursa_model/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ursa/ursa_model/clip_encoder.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ursa/ursa_model/configuration_ursa.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ursa/ursa_model/image_processing_vlm.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ursa/ursa_model/modeling_ursa.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ursa/ursa_model/processing_ursa.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ursa/ursa_model/projector.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ursa/ursa_model/sam.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/ursa/ursa_model/siglip_vit.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/valley/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/valley/requirements_valley.txt create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/valley/valley2.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/valley/valley3.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/varco_vision.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/video_llm/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/video_llm/chat_uni_vi.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/video_llm/configs/llama_vid/processor/clip-patch14-224/config.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/video_llm/configs/llama_vid/processor/clip-patch14-224/preprocessor_config.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/video_llm/configs/videochat2_hd.json create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/video_llm/llama_vid.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/video_llm/pllava.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/video_llm/video_chatgpt.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/video_llm/video_llava.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/video_llm/videochat2.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/vila.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/vintern_chat.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/visualglm.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/vita.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/vlaa_thinker.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/vlm3r.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/vlm_r1.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/vxverse.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/wemm.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/wethink_vl.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/x_vl.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/xcomposer/__init__.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/xcomposer/sharecaptioner.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/xcomposer/xcomposer.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/xcomposer/xcomposer2.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/xcomposer/xcomposer2_4KHD.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/xcomposer/xcomposer2d5.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/xgen_mm.py create mode 100644 evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/vlm/yi_vl.py diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/.github/scripts/assert_score.py b/evaluation/cosmos3/reasoner/vlmevalkit/.github/scripts/assert_score.py new file mode 100644 index 00000000..5d5fbfe5 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/.github/scripts/assert_score.py @@ -0,0 +1,61 @@ +import argparse +import ast +import json +import os + +import pandas as pd + + +def validate_scores(dataset_list, assert_score, model_name): + for dataset in dataset_list: + base_score = assert_score[dataset][model_name] + if dataset == "OCRBench_MINI": + score_file = os.path.join("outputs", f"{model_name}/{model_name}_{dataset}_score.json") + cur_score = 0 + with open(score_file, "r") as f: + total_score = json.load(f) + cur_score = total_score["Final Score Norm"] + assert ( + abs(cur_score - float(base_score)) <= 0.01 + ), f"{dataset} on {model_name}: cur_score is {cur_score}, base_score is {base_score}" + else: + score_file = os.path.join("outputs", f"{model_name}/{model_name}_{dataset}_acc.csv") + df = pd.read_csv(score_file) + cur_score = df["Overall"].iloc[0] + if dataset == "MMBench_V11_MINI": + cur_score = df.loc[df["split"] == "dev", "Overall"].values + assert ( + abs(cur_score - float(base_score)) <= 0.01 + ), f"{dataset} on {model_name}: cur_score is {cur_score}, base_score is {base_score}" + print(f"cur_score is {cur_score}, base_score is {base_score}") + + +def parse_arguments(): + parser = argparse.ArgumentParser(description="Validate model scores against csv/json data") + + parser.add_argument("--dataset", type=str, required=True, help="Space-separated list of datasets") + + parser.add_argument( + "--base_score", type=str, required=True, help="Dictionary string in format {dataset:{model:score}}" + ) + + parser.add_argument("--model-name", type=str, required=True, help="Name of the model to validate") + + return parser.parse_args() + + +def main(): + args = parse_arguments() + + try: + dataset_list = args.dataset.split() + base_score = ast.literal_eval(args.base_score) + except Exception as e: + print(f"Parameter parsing error: {str(e)}") + return + + validate_scores(dataset_list, base_score, args.model_name) + + +if __name__ == "__main__": + main() diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/.github/workflows/lint.yml b/evaluation/cosmos3/reasoner/vlmevalkit/.github/workflows/lint.yml new file mode 100644 index 00000000..1eb46dcb --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/.github/workflows/lint.yml @@ -0,0 +1,23 @@ +name: lint + +on: [push, pull_request] + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + lint: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + - name: Set up Python 3.10 + uses: actions/setup-python@v2 + with: + python-version: 3.10.15 + - name: Install pre-commit hook + run: | + pip install pre-commit + pre-commit install + - name: Linting + run: pre-commit run --all-files diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/.github/workflows/pr-run-test.yml b/evaluation/cosmos3/reasoner/vlmevalkit/.github/workflows/pr-run-test.yml new file mode 100644 index 00000000..25e214af --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/.github/workflows/pr-run-test.yml @@ -0,0 +1,90 @@ +name: pr_run_test + +on: + pull_request: + branches: + - "main" + paths-ignore: + - "docs/**" + - "**.md" + workflow_dispatch: + schedule: + - cron: '56 01 * * *' + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} + cancel-in-progress: true + +env: + BASE_SCORE: '{"MMBench_V11_MINI":{"Qwen2.5-VL-7B-Instruct":0.76363636,"InternVL3-8B":0.92727273,"llava-onevision-qwen2-0.5b-ov-hf":0.45454545},"MMStar_MINI":{"Qwen2.5-VL-7B-Instruct":0.6133333333333333,"InternVL3-8B":0.7,"llava-onevision-qwen2-0.5b-ov-hf":0.36},"AI2D_MINI":{"Qwen2.5-VL-7B-Instruct":0.7651821862348178,"InternVL3-8B":0.8218623481781376,"llava-onevision-qwen2-0.5b-ov-hf":0.48582995951417},"OCRBench_MINI":{"Qwen2.5-VL-7B-Instruct":15.7,"InternVL3-8B":17.3,"llava-onevision-qwen2-0.5b-ov-hf":5.5}}' + HF_HUB_CACHE: /mnt/shared-storage-gpfs2/gpfs2-shared-public/huggingface/hub + HF_HUB_OFFLINE: 1 + CONDA_PATH: /mnt/shared-storage-user/opencompass-shared/qa-llm-cicd/miniconda3 + CONDA_ENV: vlm_pr_test + KUBEBRAIN_CLUSTER_ENTRY: https://h.pjlab.org.cn + KUBEBRAIN_NAMESPACE: ailab-opencompass + +jobs: + vlm_test: + if: ${{!cancelled()}} + runs-on: [yidian_cu12_mllm] + strategy: + fail-fast: false + matrix: + dataset: ["MMBench_V11_MINI MMStar_MINI AI2D_MINI","OCRBench_MINI"] + model: ['llava-onevision-qwen2-0.5b-ov-hf', 'InternVL3-8B', 'Qwen2.5-VL-7B-Instruct'] + include: + - model: llava-onevision-qwen2-0.5b-ov-hf + model_name: llava + - model: Qwen2.5-VL-7B-Instruct + model_name: qwen + - model: InternVL3-8B + model_name: internvl + - dataset: MMBench_V11_MINI MMStar_MINI AI2D_MINI + dataset_name: mmbench + - dataset: OCRBench_MINI + dataset_name: ocrbench + steps: + - name: Clean workdir + run: sudo git clean -ffdx + - name: clone_repo + uses: actions/checkout@v3 + - name: reinstall vlmeval + run: | + . ${{env.CONDA_PATH}}/bin/activate + conda activate ${{env.CONDA_ENV}} + pip uninstall vlmeval -y + pip install . + pip install numpy==1.23.0 transformers==4.57.1 + - name: evaluation_model + run: | + . ${{env.CONDA_PATH}}/bin/activate + conda activate ${{env.CONDA_ENV}} + pip list + + rjob submit --metadata-name=vllm-pr-test-${{ github.run_id }}-${{matrix.model_name}}-${{matrix.dataset_name}}-${{ github.run_attempt }} --charged-group=opencompass_gpu --private-machine=group --group=opencompass_gpu --gpu=1 --cpu=16 --memory=32568 --private-machine=group --image=registry.h.pjlab.org.cn/ailab-puyu-puyu_wsp_cpu/vlmevalkit:auto-v0.0.10 --env=HF_HUB_CACHE=/mnt/shared-storage-gpfs2/gpfs2-shared-public/huggingface/hub --env=HF_HUB_OFFLINE=1 --env=LMUData=/mnt/shared-storage-user/opencompass-shared/qa-llm-cicd/LMUData --mount=gpfs://gpfs1/qa-llm-cicd:/mnt/shared-storage-user/qa-llm-cicd --mount=gpfs://gpfs1/opencompass-shared:/mnt/shared-storage-user/opencompass-shared --mount=gpfs://gpfs1/auto-eval-pipeline:/mnt/shared-storage-user/auto-eval-pipeline --mount=gpfs://gpfs2/gpfs2-shared-public:/mnt/shared-storage-gpfs2/gpfs2-shared-public --mount=gpfs://gpfs1/mllm:/mnt/shared-storage-user/mllm --host-network=True -- bash -exc 'cd ${{github.workspace}}; source /mnt/shared-storage-user/opencompass-shared/qa-llm-cicd/miniconda3/bin/activate; conda activate ${{env.CONDA_ENV}}; python run.py --data ${{matrix.dataset}} --model ${{matrix.model}} --work-dir /mnt/shared-storage-user/mllm/qa-llm-cicd/eval_report/${{ github.run_id }}/${{matrix.model}} --reuse --judge exact_matching 2>&1' + + for i in {1..1200}; do + current_status=$(rjob get vllm-pr-test-${{ github.run_id }}-${{matrix.model_name}}-${{matrix.dataset_name}}-${{ github.run_attempt }} | grep -oP 'rjob [^:]+: \K[^ ]+') + echo "Current status: $current_status, stop checking" + if [[ $current_status == "Succeeded" ]]; then + echo "Task succeeded" + rjob logs job vllm-pr-test-${{ github.run_id }}-${{matrix.model_name}}-${{matrix.dataset_name}}-${{ github.run_attempt }} -n 100 + exit 0 + elif [[ $current_status == "Failed" || $current_status == "Stopped" ]]; then + echo "Task failed or stopped, fetching logs" + rjob logs job vllm-pr-test-${{ github.run_id }}-${{matrix.model_name}}-${{matrix.dataset_name}}-${{ github.run_attempt }} + exit 1 + fi + sleep 6 + done + rjob stop vllm-pr-test-${{ github.run_id }}-${{matrix.model_name}}-${{matrix.dataset_name}}-${{ github.run_attempt }} + rjob logs job vllm-pr-test-${{ github.run_id }}-${{matrix.model_name}}-${{matrix.dataset_name}}-${{ github.run_attempt }} -n 100 + echo "Task timeout" + exit 1 + - name: assert_result + run: | + . ${{env.CONDA_PATH}}/bin/activate + conda activate ${{env.CONDA_ENV}} + cp -r /mnt/shared-storage-user/mllm/qa-llm-cicd/eval_report/${{ github.run_id }}/${{matrix.model}} outputs + python .github/scripts/assert_score.py --dataset "${{matrix.dataset}}" --base_score $BASE_SCORE --model-name ${{matrix.model}} diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/.gitignore b/evaluation/cosmos3/reasoner/vlmevalkit/.gitignore new file mode 100644 index 00000000..4f4ca813 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/.gitignore @@ -0,0 +1,227 @@ +.idea/ + +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST +.vscode/ +.gradio/ + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/#use-with-ide +.pdm.toml + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ +environment.yml + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# Images +images/ + +# Video files +*.mp4 +*.avi +*.mov +*.mkv +*.flv +*.wmv +*.webm + +scripts/*ttf +.history +cache_dir/* + +# Evaluation Outputs +outputs/* +demo.ipynb +*json +!vlmeval/dataset/utils/vgrpbench/configs/formating-prompt/**/*.json +.vscode +*.swp +GPT4o_MINI/ + +2weiyun* +script.py +Gemini* +Claude3-5V* +GLM4V* +GPT4o* +GPT4V* +mmmu_debug +bailingMM +BailingMM* +SenseChat* +Step* +DoubaoVL +arch +BlueLM* +mmb_* +gpt-4.1* +Reka* +Taiyi +TeleMM +apple.jpg +assets/LOGO.png +api_list.txt +vlmeval/gemini_tmp.py +run.sh +run_g.sh +tmp/ +vlmeval/dataset/Omni3D/tmp/ +InternVL* +Qwen* +CongRong* +Seed1.5* +aguvis* +grok-* +GLM4.5* +SenseNova* + +.DS_Store + + +slurm-logs/ + +data/ diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/.pre-commit-config.yaml b/evaluation/cosmos3/reasoner/vlmevalkit/.pre-commit-config.yaml new file mode 100644 index 00000000..d0b03227 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/.pre-commit-config.yaml @@ -0,0 +1,45 @@ +exclude: | + (?x)^( + scripts/| + assets/| + vlmeval/config.py | + vlmeval/dataset/utils/wemath.py | + vlmeval/dataset/OmniDocBench/ | + vlmeval/dataset/utils/megabench/ | + vlmeval/dataset/utils/vgrpbench/ | + vlmeval/dataset/utils/chartmimic/ | + vlmeval/vlm/ola/ | + vlmeval/vlm/ursa/ | + vlmeval/vlm/ovis/ | + vlmeval/dataset/utils/mme_reasoning.py + ) +repos: + - repo: https://github.com/PyCQA/flake8 + rev: 7.1.2 + hooks: + - id: flake8 + args: + [ + "--max-line-length=120", + "--ignore=W503", + ] + exclude: ^configs/ + - repo: https://github.com/PyCQA/isort + rev: 6.0.1 + hooks: + - id: isort + - repo: https://github.com/google/yapf + rev: v0.43.0 + hooks: + - id: yapf + args: ["--style={column_limit=120}"] + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v6.0.0 + hooks: + - id: trailing-whitespace + - id: check-yaml + - id: end-of-file-fixer + - id: requirements-txt-fixer + - id: check-merge-conflict + - id: mixed-line-ending + args: ["--fix=lf"] diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/LICENSE b/evaluation/cosmos3/reasoner/vlmevalkit/LICENSE new file mode 100644 index 00000000..d67ab032 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/LICENSE @@ -0,0 +1,203 @@ +Copyright 2023 VLMEvalKit Authors. All rights reserved. + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright 2023 VLMEvalKit Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/README-vlmevalkit.md b/evaluation/cosmos3/reasoner/vlmevalkit/README-vlmevalkit.md new file mode 100644 index 00000000..e94ce75d --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/README-vlmevalkit.md @@ -0,0 +1,158 @@ +![LOGO](https://opencompass.openxlab.space/utils/MMLB.jpg) + +A Toolkit for Evaluating Large Vision-Language Models. + +[![][github-contributors-shield]][github-contributors-link] • [![][github-forks-shield]][github-forks-link] • [![][github-stars-shield]][github-stars-link] • [![][github-issues-shield]][github-issues-link] • [![][github-license-shield]][github-license-link] + +English | [简体中文](/docs/zh-CN/README_zh-CN.md) | [日本語](/docs/ja/README_ja.md) + +🏆 OC Learderboard • +🏗️Quickstart • +📊Datasets & Models • +🛠️Development + +🤗 HF Leaderboard • +🤗 Evaluation Records • +🤗 HF Video Leaderboard • + +🔊 Discord • +📝 Report • +🎯Goal • +🖊️Citation + + +**VLMEvalKit** (the python package name is **vlmeval**) is an **open-source evaluation toolkit** of **large vision-language models (LVLMs)**. It enables **one-command evaluation** of LVLMs on various benchmarks, without the heavy workload of data preparation under multiple repositories. In VLMEvalKit, we adopt **generation-based evaluation** for all LVLMs, and provide the evaluation results obtained with both **exact matching** and **LLM-based answer extraction**. + +## Recent Codebase Changes +- **[2025-09-12]** **Major Update: Improved Handling for Models with Thinking Mode** + + A new feature in [PR 1229](https://github.com/open-compass/VLMEvalKit/pull/1175) that improves support for models with thinking mode. VLMEvalKit now allows for the use of a custom `split_thinking` function. **We strongly recommend this for models with thinking mode to ensure the accuracy of evaluation**. To use this new functionality, please enable the Environment Variable: `SPLIT_THINK=True`. By default, the function will parse content within `...` tags and store it in the `thinking` key of the output. For more advanced customization, you can also create a `split_think` function for model. Please see the InternVL implementation for an example. +- **[2025-09-12]** **Major Update: Improved Handling for Long Response(More than 16k/32k)** + + A new feature in [PR 1229](https://github.com/open-compass/VLMEvalKit/pull/1175) that improves support for models with long response outputs. VLMEvalKit can now save prediction files in TSV format. **Since individual cells in an `.xlsx` file are limited to 32,767 characters, we strongly recommend using this feature for models that generate long responses (e.g., exceeding 16k or 32k tokens) to prevent data truncation.** To use this new functionality, please enable the Environment Variable: `PRED_FORMAT=tsv`. +- **[2025-08-04]** In [PR 1175](https://github.com/open-compass/VLMEvalKit/pull/1175), we refine the `can_infer_option` and `can_infer_text`, which increasingly route the evaluation to LLM choice extractors and empirically leads to slight performance improvement for MCQ benchmarks. + +## 🆕 News +- **[2025-07-07]** Supported [**SeePhys**](https://seephys.github.io/), which is a ​full spectrum multimodal benchmark for evaluating physics reasoning across different knowledge levels. thanks to [**Quinn777**](https://github.com/Quinn777) 🔥🔥🔥 +- **[2025-07-02]** Supported [**OvisU1**](https://huggingface.co/AIDC-AI/Ovis-U1-3B), thanks to [**liyang-7**](https://github.com/liyang-7) 🔥🔥🔥 +- **[2025-06-16]** Supported [**PhyX**](https://phyx-bench.github.io/), a benchmark aiming to assess capacity for physics-grounded reasoning in visual scenarios. 🔥🔥🔥 +- **[2025-05-24]** To facilitate faster evaluations for large-scale or thinking models, **VLMEvalKit supports multi-node distributed inference** using **LMDeploy** (supports *InternVL Series, QwenVL Series, LLaMa4*) or **VLLM**(supports *QwenVL Series, LLaMa4*). You can activate this feature by adding the ```use_lmdeploy``` or ```use_vllm``` flag to your custom model configuration in [config.py](vlmeval/config.py) . Leverage these tools to significantly speed up your evaluation workflows 🔥🔥🔥 +- **[2025-05-24]** Supported Models: **InternVL3 Series, Gemini-2.5-Pro, Kimi-VL, LLaMA4, NVILA, Qwen2.5-Omni, Phi4, SmolVLM2, Grok, SAIL-VL-1.5, WeThink-Qwen2.5VL-7B, Bailingmm, VLM-R1, Taichu-VLR**. Supported Benchmarks: **HLE-Bench, MMVP, MM-AlignBench, Creation-MMBench, MM-IFEval, OmniDocBench, OCR-Reasoning, EMMA, ChaXiv,MedXpertQA, Physics, MSEarthMCQ, MicroBench, MMSci, VGRP-Bench, wildDoc, TDBench, VisuLogic, CVBench, LEGO-Puzzles, Video-MMLU, QBench-Video, MME-CoT, VLM2Bench, VMCBench, MOAT, Spatial457 Benchmark**. Please refer to [**VLMEvalKit Features**](https://aicarrier.feishu.cn/wiki/Qp7wwSzQ9iK1Y6kNUJVcr6zTnPe?table=tblsdEpLieDoCxtb) for more details. Thanks to all contributors 🔥🔥🔥 +- **[2025-02-20]** Supported Models: **InternVL2.5 Series, Qwen2.5VL Series, QVQ-72B, Doubao-VL, Janus-Pro-7B, MiniCPM-o-2.6, InternVL2-MPO, LLaVA-CoT, Hunyuan-Standard-Vision, Ovis2, Valley, SAIL-VL, Ross, Long-VITA, EMU3, SmolVLM**. Supported Benchmarks: **MMMU-Pro, WeMath, 3DSRBench, LogicVista, VL-RewardBench, CC-OCR, CG-Bench, CMMMU, WorldSense**. Thanks to all contributors 🔥🔥🔥 +- **[2024-12-11]** Supported [**NaturalBench**](https://huggingface.co/datasets/BaiqiL/NaturalBench), a vision-centric VQA benchmark (NeurIPS'24) that challenges vision-language models with simple questions about natural imagery. +- **[2024-12-02]** Supported [**VisOnlyQA**](https://github.com/psunlpgroup/VisOnlyQA/), a benchmark for evaluating the visual perception capabilities 🔥🔥🔥 +- **[2024-11-26]** Supported [**Ovis1.6-Gemma2-27B**](https://huggingface.co/AIDC-AI/Ovis1.6-Gemma2-27B), thanks to [**runninglsy**](https://github.com/runninglsy) 🔥🔥🔥 +- **[2024-11-25]** Create a new flag `VLMEVALKIT_USE_MODELSCOPE`. By setting this environment variable, you can download the video benchmarks supported from [**modelscope**](https://www.modelscope.cn) 🔥🔥🔥 + +## 🏗️ QuickStart + +See [[QuickStart](/docs/en/Quickstart.md) | [快速开始](/docs/zh-CN/Quickstart.md)] for a quick start guide. + +## 📊 Datasets, Models, and Evaluation Results + +### Evaluation Results + +**The performance numbers on our official multi-modal leaderboards can be downloaded from here!** + +[**OpenVLM Leaderboard**](https://huggingface.co/spaces/opencompass/open_vlm_leaderboard): [**Download All DETAILED Results**](http://opencompass.openxlab.space/assets/OpenVLM.json). + +Check **Supported Benchmarks** Tab in [**VLMEvalKit Features**](https://aicarrier.feishu.cn/wiki/Qp7wwSzQ9iK1Y6kNUJVcr6zTnPe?table=tblsdEpLieDoCxtb) to view all supported image & video benchmarks (70+). + +Check **Supported LMMs** Tab in [**VLMEvalKit Features**](https://aicarrier.feishu.cn/wiki/Qp7wwSzQ9iK1Y6kNUJVcr6zTnPe?table=tblsdEpLieDoCxtb) to view all supported LMMs, including commercial APIs, open-source models, and more (200+). + +**Transformers Version Recommendation:** + +Note that some VLMs may not be able to run under certain transformer versions, we recommend the following settings to evaluate each VLM: + +- **Please use** `transformers==4.33.0` **for**: `Qwen series`, `Monkey series`, `InternLM-XComposer Series`, `mPLUG-Owl2`, `OpenFlamingo v2`, `IDEFICS series`, `VisualGLM`, `MMAlaya`, `ShareCaptioner`, `MiniGPT-4 series`, `InstructBLIP series`, `PandaGPT`, `VXVERSE`. +- **Please use** `transformers==4.36.2` **for**: `Moondream1`. +- **Please use** `transformers==4.37.0` **for**: `LLaVA series`, `ShareGPT4V series`, `TransCore-M`, `LLaVA (XTuner)`, `CogVLM Series`, `EMU2 Series`, `Yi-VL Series`, `MiniCPM-[V1/V2]`, `OmniLMM-12B`, `DeepSeek-VL series`, `InternVL series`, `Cambrian Series`, `VILA Series`, `Llama-3-MixSenseV1_1`, `Parrot-7B`, `PLLaVA Series`. +- **Please use** `transformers==4.40.0` **for**: `IDEFICS2`, `Bunny-Llama3`, `MiniCPM-Llama3-V2.5`, `360VL-70B`, `Phi-3-Vision`, `WeMM`. +- **Please use** `transformers==4.42.0` **for**: `AKI`. +- **Please use** `transformers==4.44.0` **for**: `Moondream2`, `H2OVL series`. +- **Please use** `transformers==4.45.0` **for**: `Aria`. +- **Please use** `transformers==4.48.0` (or `4.46.0`) **for**: `LLaVA-Next series` (e.g., `llava-hf/llava-v1.6-vicuna-7b-hf`). +- **Please use** `transformers==latest` **for**: `PaliGemma-3B`, `Chameleon series`, `Video-LLaVA-7B-HF`, `Ovis series`, `Mantis series`, `MiniCPM-V2.6`, `OmChat-v2.0-13B-sinlge-beta`, `Idefics-3`, `GLM-4v-9B`, `VideoChat2-HD`, `RBDash_72b`, `Llama-3.2 series`, `Kosmos series`. +- **Please use** `transformers==4.50.3` (or `4.46.1` or `4.51` or `4.53`) **for**: `Molmo series`. +- **Please use** `transformers>=5.2.0` **for**: `Qwen3.5 series`. + +**Torchvision Version Recommendation:** + +Note that some VLMs may not be able to run under certain torchvision versions, we recommend the following settings to evaluate each VLM: + +- **Please use** `torchvision>=0.16` **for**: `Moondream series` and `Aria` + +**Flash-attn Version Recommendation:** + +Note that some VLMs may not be able to run under certain flash-attention versions, we recommend the following settings to evaluate each VLM: + +- **Please use** `pip install flash-attn --no-build-isolation` **for**: `Aria` + +```python +# Demo +from vlmeval.config import supported_VLM +model = supported_VLM['idefics_9b_instruct']() +# Forward Single Image +ret = model.generate(['assets/apple.jpg', 'What is in this image?']) +print(ret) # The image features a red apple with a leaf on it. +# Forward Multiple Images +ret = model.generate(['assets/apple.jpg', 'assets/apple.jpg', 'How many apples are there in the provided images? ']) +print(ret) # There are two apples in the provided images. +``` + +## 🛠️ Development Guide + +To develop custom benchmarks, VLMs, or simply contribute other codes to **VLMEvalKit**, please refer to [[Development_Guide](/docs/en/Development.md) | [开发指南](/docs/zh-CN/Development.md)]. + +**Call for contributions** + +To promote the contribution from the community and share the corresponding credit (in the next report update): + +- All Contributions will be acknowledged in the report. +- Contributors with 3 or more major contributions (implementing an MLLM, benchmark, or major feature) can join the author list of [VLMEvalKit Technical Report](https://www.arxiv.org/abs/2407.11691) on ArXiv. Eligible contributors can create an issue or dm kennyutc in [VLMEvalKit Discord Channel](https://discord.com/invite/evDT4GZmxN). + +Here is a [contributor list](/docs/en/Contributors.md) we curated based on the records. + +## 🎯 The Goal of VLMEvalKit + +**The codebase is designed to:** + +1. Provide an **easy-to-use**, **opensource evaluation toolkit** to make it convenient for researchers & developers to evaluate existing LVLMs and make evaluation results **easy to reproduce**. +2. Make it easy for VLM developers to evaluate their own models. To evaluate the VLM on multiple supported benchmarks, one just need to **implement a single `generate_inner()` function**, all other workloads (data downloading, data preprocessing, prediction inference, metric calculation) are handled by the codebase. + +**The codebase is not designed to:** + +1. Reproduce the exact accuracy number reported in the original papers of all **3rd party benchmarks**. The reason can be two-fold: + 1. VLMEvalKit uses **generation-based evaluation** for all VLMs (and optionally with **LLM-based answer extraction**). Meanwhile, some benchmarks may use different approaches (SEEDBench uses PPL-based evaluation, *eg.*). For those benchmarks, we compare both scores in the corresponding result. We encourage developers to support other evaluation paradigms in the codebase. + 2. By default, we use the same prompt template for all VLMs to evaluate on a benchmark. Meanwhile, **some VLMs may have their specific prompt templates** (some may not covered by the codebase at this time). We encourage VLM developers to implement their own prompt template in VLMEvalKit, if that is not covered currently. That will help to improve the reproducibility. + +## 🖊️ Citation + +If you find this work helpful, please consider to **star🌟** this repo. Thanks for your support! + +[![Stargazers repo roster for @open-compass/VLMEvalKit](https://reporoster.com/stars/open-compass/VLMEvalKit)](https://github.com/open-compass/VLMEvalKit/stargazers) + +If you use VLMEvalKit in your research or wish to refer to published OpenSource evaluation results, please use the following BibTeX entry and the BibTex entry corresponding to the specific VLM / benchmark you used. + +```bib +@inproceedings{duan2024vlmevalkit, + title={Vlmevalkit: An open-source toolkit for evaluating large multi-modality models}, + author={Duan, Haodong and Yang, Junming and Qiao, Yuxuan and Fang, Xinyu and Chen, Lin and Liu, Yuan and Dong, Xiaoyi and Zang, Yuhang and Zhang, Pan and Wang, Jiaqi and others}, + booktitle={Proceedings of the 32nd ACM International Conference on Multimedia}, + pages={11198--11201}, + year={2024} +} +``` + +

🔝Back to top

+ +[github-contributors-link]: https://github.com/open-compass/VLMEvalKit/graphs/contributors +[github-contributors-shield]: https://img.shields.io/github/contributors/open-compass/VLMEvalKit?color=c4f042&labelColor=black&style=flat-square +[github-forks-link]: https://github.com/open-compass/VLMEvalKit/network/members +[github-forks-shield]: https://img.shields.io/github/forks/open-compass/VLMEvalKit?color=8ae8ff&labelColor=black&style=flat-square +[github-issues-link]: https://github.com/open-compass/VLMEvalKit/issues +[github-issues-shield]: https://img.shields.io/github/issues/open-compass/VLMEvalKit?color=ff80eb&labelColor=black&style=flat-square +[github-license-link]: https://github.com/open-compass/VLMEvalKit/blob/main/LICENSE +[github-license-shield]: https://img.shields.io/github/license/open-compass/VLMEvalKit?color=white&labelColor=black&style=flat-square +[github-stars-link]: https://github.com/open-compass/VLMEvalKit/stargazers +[github-stars-shield]: https://img.shields.io/github/stars/open-compass/VLMEvalKit?color=ffcb47&labelColor=black&style=flat-square diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/README.md b/evaluation/cosmos3/reasoner/vlmevalkit/README.md new file mode 100644 index 00000000..5c2f335c --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/README.md @@ -0,0 +1,183 @@ + + +# TAR and VANTAGE Benchmarks for Cosmos3 Model + +This repository is an **NVIDIA-customized [VLMEvalKit](https://github.com/open-compass/VLMEvalKit)** that +releases the NVIDIA Metropolis "smart infra" benchmarks and makes the cosmos-reasoner scores on them +**publicly reproducible**. The reproduction kit lives in [`cosmos_eval/`](cosmos_eval/); the stock +VLMEvalKit inference + scoring engine it drives is the rest of the tree (upstream toolkit docs: +[`README-vlmevalkit.md`](README-vlmevalkit.md)). + +The kit ships, for each benchmark, the exact `run.py --config` settings the internal evaluation uses — +split into a shared dataset layer (`cosmos_eval/data/`) and a per-family model layer +(`cosmos_eval/models/`) — plus a parallel launcher (`cosmos_eval/run_all.py`) and a standalone score +reporter (`cosmos_eval/parse_score.py`). + +You deploy the model behind an OpenAI-compatible endpoint, point the launcher at it, and it composes +each config, runs the stock vlmevalkit `run.py`, and reports the headline score per benchmark. No +extra evaluation logic lives in the kit — inference and scoring are vlmevalkit's; the kit only composes +the inputs and reads the outputs. + +> **This rollout ships the NVIDIA Metropolis "smart infra" benchmarks — `AETCBench_all` (the official +> **TAR** / Traffic Anomaly Reasoning benchmark) + the 8 `VANTAGE_*` (9 total).** +> +> **⚠️ The datasets are not publicly available yet** — both the VANTAGE and TAR +> challenges are still ongoing. **We will update this code as soon as the data and evaluation are ready.** +> Until then these configs are published as a reference; `run_all` runs every benchmark in the manifest. + +``` +. # NVIDIA-customized VLMEvalKit (stock inference + scoring engine) +├── README.md # this file +├── README-vlmevalkit.md # upstream VLMEvalKit toolkit docs +├── run.py # stock vlmevalkit entrypoint (driven once per benchmark) +└── cosmos_eval/ # the reproduction kit + ├── data//.json # dataset section per benchmark (model-independent) + ├── models/.json # per-family model layer (day 1: cosmos) + ├── manifest.json # per-bench run.py CLI flags + ├── run_all.py # parallel launcher (compose → run.py → parse_score) + └── parse_score.py # standalone score reporter +``` + +--- + +## 1. Environment setup + +Use a clean virtual environment with **Python 3.10+**, run from the **repo root** (the directory that +holds this README): + +```bash +python -m venv .venv && source .venv/bin/activate + +# vlmevalkit's own dependencies +pip install -r requirements.txt + +# Extra packages required at runtime but not pinned by requirements.txt: +# einops, accelerate -> imported when vlmevalkit loads its dataset modules +# setuptools<81 -> jieba imports pkg_resources, which setuptools>=81 drops +# and Python 3.12 venvs omit +pip install einops accelerate "setuptools<81" + +# install vlmevalkit itself so `import vlmeval` resolves +pip install -e . +``` + +Sanity check: + +```bash +python -c "import vlmeval; print('vlmeval OK')" +``` + +--- + +## 2. Deploy the model endpoint (vLLM) + +Serve the model behind an OpenAI-compatible `/v1/chat/completions` endpoint (see also +**[Reasoner with vLLM](https://github.com/NVIDIA/cosmos/tree/main#reasoner-with-vllm)**): + +```bash +vllm serve nvidia/Cosmos3-Nano \ + --async-scheduling \ + --allowed-local-media-path / \ + --port 8080 \ + --max-model-len 128000 \ + --tensor-parallel-size 1 \ + --gpu-memory-utilization 0.9 \ + --enable-chunked-prefill \ + --mm-processor-cache-gb 0 \ + --mm-encoder-tp-mode data \ + --media-io-kwargs '{"video": {"num_frames": -1, "fps": -1}}' +``` + +The `--media-io-kwargs` / `--mm-*` flags let the server honor the per-benchmark frame sampling the +configs request, and `--allowed-local-media-path /` lets it read local media. (Raise +`--tensor-parallel-size` for larger models.) Any OpenAI-compatible server works (vLLM, SGLang, TGI, a +hosted NIM, …) as long as it answers `POST /v1/chat/completions` and `GET /v1/models`; the wrapper +health-checks the endpoint and auto-detects the served model id from `/v1/models`. + +--- + +## 3. Environment variables + +```bash +# Model under test +export COSMOS_API_BASE=http://:8080/v1/chat/completions +export COSMOS_MODEL= # or any placeholder; auto-detected from /v1/models +export COSMOS_API_KEY= +``` + +`run_all.py` substitutes `${COSMOS_MODEL}` / `${COSMOS_API_BASE}` into each composed config at launch. + +--- + +## 4. Run + +From the vlmevalkit repo root: + +```bash +# all benchmarks in the manifest +python cosmos_eval/run_all.py --model cosmos --concurrency 8 --work-dir ./out + +# a specific few +python cosmos_eval/run_all.py --benchmarks VANTAGE_VQA,AETCBench_all --work-dir ./out +``` + +Per benchmark, the launcher composes `model_conf = {class} ∪ defaults ∪ benchmarks[bench]` and +`dataset_conf = data[bench] ∪ {model_family}`, writes `/_configs/.json`, runs +`run.py --config … --verbose --save-eval-results` with the manifest's CLI flags (one `run.py` +subprocess per benchmark, `--concurrency` at a time), then reports the score. + +**Useful flags** + +| Flag | Effect | +|---|---| +| `--benchmarks A,B` | run only these benchmarks (default: all in the manifest) | +| `--concurrency N` | up to N benchmarks (each its own `run.py`) at once | +| `--dry-run` | print the `run.py` commands and exit | +| `--export-configs DIR` | compose + write each `--config` JSON to `DIR` (placeholders kept), then exit | +| `--import-configs DIR` | run from a dir of (possibly hand-edited) configs instead of composing | + +--- + +## 5. Data & scoring + +These benchmarks' datasets are being published to Hugging Face: +- **VANTAGE** (`nvidia/PhysicalAI-VANTAGE-Bench`) — test inputs are released; ground truth is withheld, + so scores come from the **VANTAGE leaderboard**. +- **TAR — Traffic Anomaly Reasoning** (config key `AETCBench_all`; `nvidia/PhysicalAI-Traffic-Anomaly-Reasoning`) — + test answers are redacted; submit predictions to the **AI City Challenge** evaluation server. + +`run_all` produces per-benchmark predictions (and a local score where ground truth is available); follow +each benchmark's leaderboard instructions to obtain official scores. + +--- + +## 6. Confirm the results + +After a run, each benchmark has its own output dir and a parsed score in the summary table: + +``` +=== cosmos_eval summary === + AETCBench_all ok + VANTAGE_2DGrounding ok + ... + 9/9 ok, 0 failed +``` + +`run.py` writes its evaluation output to `///T*/_.{dict,df}.eval.json`. +Report any single output directly with the standalone reporter: + +```bash +python cosmos_eval/parse_score.py --work-dir ./out/VANTAGE_VQA --dataset VANTAGE_VQA +# VANTAGE_VQA Overall: +``` + +`parse_score.py` prints the headline **Overall** (0–100) plus every native scalar sub-score key the +benchmark's eval JSON contains. It reads both eval-output shapes vlmevalkit emits +(`*.dict.eval.json`, `*.df.eval.json`). The scores it reports match the internal evaluation pipeline, +benchmark for benchmark. + +Each benchmark dir also keeps `run.log` (full `run.py` output) for debugging; the composed config +used is under `/_configs/.json`. \ No newline at end of file diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/assets/0_lamp_pred.png b/evaluation/cosmos3/reasoner/vlmevalkit/assets/0_lamp_pred.png new file mode 100644 index 0000000000000000000000000000000000000000..7f0cccab97511032152c669b77e9ebcad47775cb GIT binary patch literal 494911 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%0Al7hAkZhyBQ#~At4|2HWTsS^p0mm8VfT%D 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

FY>1huo- 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 yieEAT@N%k>NA6?ZJ!j9dGq + + +Created with Fabric.js 5.3.0 + + + + + + + + + + + + + VLMEvalKit + \ No newline at end of file diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/assets/apple.jpg b/evaluation/cosmos3/reasoner/vlmevalkit/assets/apple.jpg new file mode 100644 index 0000000000000000000000000000000000000000..34e1859aa4b053958abbfd254e8f53dc40a6cd2d GIT binary patch literal 4576 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 literal 0 HcmV?d00001 diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/assets/sample.mp4 b/evaluation/cosmos3/reasoner/vlmevalkit/assets/sample.mp4 new file mode 100644 index 0000000000000000000000000000000000000000..ff5d7589c8e86b513cc5196593b22f715388b4ef GIT binary patch literal 499193 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

`})|&KWaNTv819wDWT3ckSWm|wr9aocGtAtJ# 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≧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%0Db3&GK1TnMdqt!0wktKjia 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 dict[str, Any]: + """Load eval summary — backward compatible wrapper.""" + return load_eval_summary_with_metadata(filepath).data + + +def load_eval_summary_with_metadata(filepath: Path) -> EvalSummary: + """Load eval summary preserving column names for .df.eval.json files. + + For .dict.eval.json: columns will be empty (keys are already column names). + For .df.eval.json: columns contains the DataFrame column headers. + """ + doc = json.loads(filepath.read_text("utf-8")) + + if filepath.name.endswith(".dict.eval.json"): + return EvalSummary(data=doc) + + if not filepath.name.endswith(".df.eval.json"): + raise ValueError("expected filepath to end with known suffix") + + columns = doc["columns"] + index = [str(k) for k in doc["index"]] + data = doc["data"] + + if len(columns) == 1: + data = [x[0] for x in data] + + return EvalSummary( + data=dict(zip(index, data, strict=False)), + columns=[str(c) for c in columns], + ) + + +# =========================================================================== +# Vendored verbatim from vlmeval_metric/score_parser.py +# =========================================================================== + +# Column names to search for the "overall" metric, in priority order. +ACCURACY_KEYS = [ + "CV-Bench Accuracy", + "Overall", + "overall", + "English Overall Score", + "Accuracy", + "accuracy", + "acc", + "Acc", +] + + +def normalize_score(value: float) -> float: + """Normalize score to 0-100 range. + + VLMEvalKit is inconsistent: some datasets store 0-1 (AI2D, MMBench), + others store 0-100 (CountBenchQA, HallusionBench). + Heuristic: values <= 1.0 are 0-1 scale, multiply by 100. + """ + return value * 100 if value <= 1.0 else value + + +def _is_format_c(scores_dict: dict) -> bool: + """Check if all non-metadata keys are numeric strings with list values.""" + non_meta_keys = [k for k in scores_dict if k != "__columns__"] + if not non_meta_keys: + return False + if any(not k.isdigit() for k in non_meta_keys): + return False + return isinstance(scores_dict[non_meta_keys[0]], list) + + +def _detect_format(scores_dict: dict) -> str: + """Detect which format the scores dict is in. + + Returns one of: "A", "B", "C", "D", "empty", "unknown". + """ + if not scores_dict: + return "empty" + + if _is_format_c(scores_dict): + return "C" + + for key in ACCURACY_KEYS: + if key in scores_dict and isinstance(scores_dict[key], list): + return "B" + + for key in ACCURACY_KEYS: + if key in scores_dict and isinstance(scores_dict[key], int | float): + return "A" + + for key, value in scores_dict.items(): + if key != "__columns__" and isinstance(value, int | float): + return "D" + + return "unknown" + + +def _parse_format_a(scores_dict: dict) -> dict[str, str]: + """Parse flat dict with scalar values.""" + for key in ACCURACY_KEYS: + if key in scores_dict and isinstance(scores_dict[key], int | float): + score = normalize_score(scores_dict[key]) + return {"overall": f"{score:.2f}"} + return {} + + +def _parse_format_b(scores_dict: dict) -> dict[str, str]: + """Parse dict with array values (multi-split).""" + result: dict[str, str] = {} + splits = scores_dict.get("split", []) + if not isinstance(splits, list): + splits = [splits] + + for key in ACCURACY_KEYS: + if key not in scores_dict: + continue + values = scores_dict[key] + if not isinstance(values, list): + continue + + if len(splits) == len(values): + for split_name, val in zip(splits, values, strict=False): + if isinstance(val, int | float): + score = normalize_score(val) + result[str(split_name)] = f"{score:.2f}" + elif len(values) == 1 and isinstance(values[0], int | float): + score = normalize_score(values[0]) + result["overall"] = f"{score:.2f}" + + if result: + return result + + return {} + + +def _extract_accuracy_by_columns(values: list, columns: list[str]) -> float | None: + """Column-aware accuracy extraction. + + columns[0] is the label/Category column; columns[1:] correspond to values[0:]. + Finds the LAST column whose name contains an accuracy keyword. + """ + acc_col_names = {"accuracy", "acc", "overall", "iou"} + data_columns = columns[1:] + last_match: float | None = None + for j, col in enumerate(data_columns): + col_lower = str(col).lower() + if any(name in col_lower for name in acc_col_names): + if j < len(values) and isinstance(values[j], int | float): + last_match = values[j] + if last_match is not None: + return last_match + for j in range(len(values) - 1, -1, -1): + if isinstance(values[j], int | float): + return values[j] + return None + + +def _extract_accuracy_by_heuristic(values: list) -> float | None: + """Heuristic accuracy extraction when no column names available.""" + numeric = [v for v in values if isinstance(v, int | float)] + if not numeric: + return None + if len(numeric) == 1: + return numeric[0] + + small_values = [v for v in numeric if v <= 100] + if small_values and len(small_values) < len(numeric): + return small_values[-1] + + return numeric[0] + + +def _extract_accuracy_from_row(values: list, columns: list[str] | None = None) -> float | None: + """Extract the accuracy value from a data row.""" + if columns is not None and len(columns) > 1: + return _extract_accuracy_by_columns(values, columns) + return _extract_accuracy_by_heuristic(values) + + +def _iter_format_c_rows(scores_dict: dict) -> list[tuple[str, list]]: + """Iterate Format C rows in sorted order, skipping metadata.""" + rows = [] + for key, value in sorted(scores_dict.items(), key=lambda x: x[0]): + if key == "__columns__": + continue + if isinstance(value, list) and len(value) >= 2: + rows.append((key, value)) + return rows + + +def _get_column_list(scores_dict: dict) -> list[str] | None: + """Extract __columns__ metadata if present.""" + columns = scores_dict.get("__columns__") + if isinstance(columns, list) and columns: + return [str(c) for c in columns] + return None + + +def _parse_format_c(scores_dict: dict) -> dict[str, str]: + """Parse indexed lists format.""" + col_list = _get_column_list(scores_dict) + rows = _iter_format_c_rows(scores_dict) + + for _key, value in rows: + label = value[0] + if not isinstance(label, str): + continue + if label.lower() not in ("overall", "cv-bench accuracy"): + continue + acc = _extract_accuracy_from_row(value[1:], col_list) + if acc is not None: + return {"overall": f"{normalize_score(acc):.2f}"} + + for _key, value in rows: + label = value[0] if isinstance(value[0], str) else "unknown" + acc = _extract_accuracy_from_row(value[1:], col_list) + if acc is not None: + clean_label = label.lower().replace(" ", "_") + return {clean_label: f"{normalize_score(acc):.2f}"} + + return {} + + +def parse_scores(scores_dict: dict[str, Any]) -> dict[str, str]: + """Parse scores dict and extract metrics. + + Returns: + Dictionary mapping metric names to formatted score strings + (0-100 scale, 2 decimal places). + """ + fmt = _detect_format(scores_dict) + + if fmt == "A": + return _parse_format_a(scores_dict) + if fmt == "B": + return _parse_format_b(scores_dict) + if fmt == "C": + return _parse_format_c(scores_dict) + if fmt == "D": + for value in scores_dict.values(): + if isinstance(value, int | float): + score = normalize_score(value) + return {"overall": f"{score:.2f}"} + + return {} + + +# Dataset-specific score extraction overrides. +# Each entry maps dataset_name to a callable that extracts the overall score +# from the raw scores dict. Only needed for benchmarks where the generic +# parse_scores() logic doesn't pick the right metric. +SCORE_OVERRIDES: dict[str, Callable[[dict[str, Any]], float | None]] = { + # MMMU_DEV_VAL: multi-split, use validation split + "MMMU_DEV_VAL": lambda d: _extract_split_score(d, "validation"), + # Anomaly-detection-style binary classification (sklearn classification_report + # shape) — class distributions are heavily skewed, so macro-F1 is the honest + # summary; accuracy/micro-F1 is flattered by the majority class. + "TailgatingVerification": lambda d: _extract_key_score(d, "macro avg--f1-score"), + "LVEventVerification": lambda d: _extract_key_score(d, "macro avg--f1-score"), + "MetropolisEventVerification": lambda d: _extract_key_score(d, "macro avg--f1-score"), + "VANTAGE_EventVerification": lambda d: _extract_key_score(d, "macro avg--f1-score"), + "WarehouseNearMiss": lambda d: _extract_key_score(d, "macro avg--f1-score"), + "ITSCollision": lambda d: _extract_key_score(d, "macro avg--f1-score"), + "MetropolisDVC": lambda d: _extract_nested_score(d, "overall", "SODA_c"), + "VANTAGE_DVC": lambda d: _extract_nested_score(d, "overall", "SODA_c"), + # MVBench: overall is [correct, total, "pct%"], extract percentage + "MVBench": lambda d: _extract_pct_from_list(d, "overall"), + # Video-MME: nested {"overall": {"overall": "0.606"}}, use overall.overall + "Video-MME": lambda d: _extract_nested_score(d, "overall", "overall"), + # RefCOCO: Format C with columns [Split, Precision@1, Average IoU, Samples], + # macro-average row labeled "Average". Extract Precision@1 from that row. + "RefCOCO": lambda d: _extract_refcoco_precision(d), + # IFBench: Format C with columns [strict, loose], single row. + # Overall = average of strict and loose, normalized from 0-1 to 0-100. + "IFBench": lambda d: _extract_ifbench_avg(d), + # VideoPhy2: correlation score + "VideoPhy2": lambda d: _extract_key_score(d, "Correlation"), + # CausalVQA: average of unpaired and paired accuracy + "CausalVQA": lambda d: _extract_avg_keys(d, "Unpaired Accuracy", "Paired Accuracy"), + # MVPBench: average of single and pair accuracy + "MVPBench": lambda d: _extract_avg_keys(d, "Single Accuracy", "Pair Accuracy"), + # MetropolisTemporal: nested {"overall": {"iou": 0.45, ...}} + "MetropolisTemporal": lambda d: _extract_nested_score(d, "overall", "iou"), + "VANTAGE_Temporal": lambda d: _extract_nested_score(d, "overall", "iou"), + # MetropolisVQA: general-purpose VQA, classes roughly balanced — accuracy + # is the right summary (unlike the anomaly-detection benchmarks above). + "MetropolisVQA": lambda d: _extract_key_score(d, "accuracy"), + "VANTAGE_VQA": lambda d: _extract_key_score(d, "accuracy"), + "Metropolis2DGrounding": lambda d: _extract_key_score(d, "Mean_IoU"), + "VANTAGE_2DGrounding": lambda d: _extract_key_score(d, "Mean_IoU"), + # ThreeDAVGroundingBench: the harness writes a transposed 1-row DF as a flat + # dict — metric names are top-level keys (e.g. {"IoU Accuracy": 0.086, ..., + # "__columns__": ["0"]}). `_extract_key_score` reads the headline directly. + # Fixture: tests/fixtures/threedavgrounding_real_scores.json. + "ThreeDAVGroundingBench": lambda d: _extract_key_score(d, "IoU Accuracy"), + "Astro2DBench": lambda d: _extract_key_score(d, "f1"), + "VANTAGE_Astro2D": lambda d: _extract_key_score(d, "f1"), + "VANTAGE_SOT": lambda d: _extract_key_score(d, "Overall"), + # WarehouseSpatialAI: custom key not in ACCURACY_KEYS + "WarehouseSpatialAI": lambda d: _extract_key_score(d, "Overall_acc"), + # LingoQA: benchmark_score (0-1 scale) + "LingoQA": lambda d: _extract_key_score(d, "benchmark_score"), + # AVSpecial*Bench: "Overall Accuracy" key (not in ACCURACY_KEYS) + "AVSpecialCollisionBench": lambda d: _extract_key_score(d, "Overall Accuracy"), + "AVSpecialStopBehaviorBench": lambda d: _extract_key_score(d, "Overall Accuracy"), + # AVSpecialEnvironmentBench: pooled accuracy on 0-1 scale (normalize_score rescales to 0-100). + "AVSpecialEnvironmentBench": lambda d: _extract_key_score(d, "accuracy"), + # AVSpecialOODReasoningBench: mean Lingo-Judge sigmoid prob on 0-1 scale. + "AVSpecialOODReasoningBench": lambda d: _extract_key_score(d, "lingo_judge_mean"), + # AVPromptFollowingBench: sample-level success rate on 0-1 scale (normalize_score rescales to 0-100). + "AVPromptFollowingBench": lambda d: _extract_key_score(d, "success_rate"), + # AETCBench: harness writes a transposed 1-row DF as a flat dict — metric + # names are top-level keys (e.g. {"bcq_accuracy": ..., "weighted_mean": ..., + # "__columns__": ["0"]}). Pick "weighted_mean" as the headline. + "AETCBench_all": lambda d: _extract_key_score(d, "weighted_mean"), + # LVS_ai_hallucination: aggregate.avg_factual_accuracy on a 0-10 scale → 0-100. + "LVS_ai_hallucination": lambda d: _extract_lvs_ai_hallucination_score(d), + # CameraBench: aggregate.overall_f1 on a 0-1 scale → 0-100. + "CameraBench": lambda d: _extract_camera_bench_score(d), + # Cosmos-CAB-Video (both General and Camera variants): aggregate.overall_score on + # a 0-1 scale → 0-100. General = F1(precision, recall); Camera = mean of three macro-F1. + "Cosmos-CAB-Video_General": lambda d: _extract_cosmos_cab_score(d), + "Cosmos-CAB-Video_Camera": lambda d: _extract_cosmos_cab_score(d), + # Cosmos-CAB-Image: aggregate.overall_score = F1(precision, recall) on 0-1 → 0-100. + "Cosmos-CAB-Image": lambda d: _extract_cosmos_cab_score(d), + # LocateAnythingBench (both Box and Point variants): aggregate.overall_score on + # a 0-1 scale (mean of per-dataset F1 across 7 anchor datasets), rescale to 0-100. + "LocateAnythingBench-Box": lambda d: _extract_locate_anything_bench_score(d), + "LocateAnythingBench-Point": lambda d: _extract_locate_anything_bench_score(d), + # ODinW13: evaluate() returns a wide DataFrame (rows = ['Overall', *13 datasets], + # cols = ['mAP', 'mAP_50']) on the natural 0-1 mAP scale. Headline = average mAP at + # scores['Overall'][0]; the override applies the single 0-1 -> 0-100 conversion. + "ODinW13": lambda d: _extract_odinw_score(d), +} + +# Prefix-based overrides: dataset names starting with these prefixes use the +# corresponding override. Allows e.g. MVBench_8frame, MVBench_64frame to share logic. +SCORE_OVERRIDE_PREFIXES: list[tuple[str, Callable[[dict[str, Any]], float | None]]] = [ + ("MVBench", SCORE_OVERRIDES["MVBench"]), + ("Video-MME", SCORE_OVERRIDES["Video-MME"]), + ("tailgating_", SCORE_OVERRIDES["TailgatingVerification"]), + ("CausalVQA", SCORE_OVERRIDES["CausalVQA"]), + ("MetropolisTemporal", SCORE_OVERRIDES["MetropolisTemporal"]), + ("MetropolisVQA", SCORE_OVERRIDES["MetropolisVQA"]), + ("AETCBench", SCORE_OVERRIDES["AETCBench_all"]), + ("LVS_ai_hallucination", SCORE_OVERRIDES["LVS_ai_hallucination"]), + ("CameraBench", SCORE_OVERRIDES["CameraBench"]), + ("Cosmos-CAB-Video_", SCORE_OVERRIDES["Cosmos-CAB-Video_General"]), + ("Cosmos-CAB-Image", SCORE_OVERRIDES["Cosmos-CAB-Image"]), + ("LocateAnythingBench", SCORE_OVERRIDES["LocateAnythingBench-Box"]), +] + + +def _extract_key_score(scores_dict: dict[str, Any], key: str) -> float | None: + """Extract a score by direct key lookup from flattened eval dict.""" + val = scores_dict.get(key) + if isinstance(val, int | float): + return normalize_score(val) + return None + + +def _extract_df_split_column(scores_dict: dict[str, Any], column_name: str) -> float | None: + """Extract a scalar from a one-row .df.eval.json by column name via __columns__.""" + columns = _get_column_list(scores_dict) + if not columns or column_name not in columns: + return None + idx = columns.index(column_name) + rows = _iter_format_c_rows(scores_dict) + if not rows: + return None + _, row = rows[0] + if idx >= len(row): + return None + val = row[idx] + if isinstance(val, int | float): + return normalize_score(val) + return None + + +def _extract_avg_keys(scores_dict: dict[str, Any], *keys: str) -> float | None: + """Average multiple keys and normalize to 0-100.""" + vals = [scores_dict[k] for k in keys if isinstance(scores_dict.get(k), int | float)] + if len(vals) != len(keys): + return None + return normalize_score(sum(vals) / len(vals)) + + +def _extract_split_score(scores_dict: dict[str, Any], split_name: str) -> float | None: + """Extract score for a specific split from Format B (multi-split) data.""" + parsed = parse_scores(scores_dict) + val = parsed.get(split_name) + return float(val) if val else None + + +def _extract_pct_from_list(scores_dict: dict[str, Any], key: str) -> float | None: + """Extract percentage from [correct, total, "pct%"] format like MVBench.""" + val = scores_dict.get(key) + if not isinstance(val, list) or len(val) < 3: + return None + pct_str = val[2] + if isinstance(pct_str, str) and pct_str.endswith("%"): + try: + return float(pct_str.rstrip("%")) + except ValueError: + return None + # Fallback: compute from correct/total + if isinstance(val[0], int | float) and isinstance(val[1], int | float) and val[1] > 0: + return val[0] / val[1] * 100 + return None + + +def _extract_nested_score(scores_dict: dict[str, Any], outer_key: str, inner_key: str) -> float | None: + """Extract a value from a nested dict like {"overall": {"mIoU": 0.5}} or {"overall": {"overall": "0.606"}}.""" + outer = scores_dict.get(outer_key) + if isinstance(outer, dict): + val = outer.get(inner_key) + if isinstance(val, int | float): + return normalize_score(val) + if isinstance(val, str): + try: + return normalize_score(float(val)) + except ValueError: + return None + return None + + +def _extract_refcoco_precision(scores_dict: dict[str, Any]) -> float | None: + """Extract Precision@1 from RefCOCO's macro-average row. + + RefCOCO outputs Format C with columns ["Split", "Precision@1", "Average IoU", "Samples"]. + The last row (label "Average") contains the macro-average across all 8 splits. + """ + columns = _get_column_list(scores_dict) + rows = _iter_format_c_rows(scores_dict) + + # Find the column index for "Precision@1" + precision_idx: int | None = None + if columns and len(columns) > 1: + data_columns = columns[1:] # columns[0] is the label column + for j, col in enumerate(data_columns): + if "precision" in col.lower(): + precision_idx = j + break + + for _key, value in rows: + label = value[0] + if not isinstance(label, str) or label.lower() != "average": + continue + data = value[1:] + if precision_idx is not None and precision_idx < len(data): + val = data[precision_idx] + if isinstance(val, int | float): + return float(val) # already 0-100 scale + # Fallback: first numeric value in the row + for v in data: + if isinstance(v, int | float): + return float(v) + + return None + + +def _extract_ifbench_avg(scores_dict: dict[str, Any]) -> float | None: + """Extract average of strict and loose accuracy from IFBench. + + IFBench returns a 1-row, 2-column DataFrame which run.py transposes, + producing: {"strict": scalar, "loose": scalar, "__columns__": [0]}. + Overall = (strict + loose) / 2, normalized from 0-1 to 0-100. + """ + strict = scores_dict.get("strict") + loose = scores_dict.get("loose") + if isinstance(strict, int | float) and isinstance(loose, int | float): + return normalize_score((strict + loose) / 2) + return None + + +def _extract_lvs_ai_hallucination_score(scores_dict: dict[str, Any]) -> float | None: + """Extract aggregate.avg_factual_accuracy (0-10 scale) and rescale to 0-100.""" + aggregate = scores_dict.get("aggregate") + if not isinstance(aggregate, dict): + return None + raw = aggregate.get("avg_factual_accuracy") + if not isinstance(raw, int | float): + return None + return float(raw) * 10.0 + + +def _extract_camera_bench_score(scores_dict: dict[str, Any]) -> float | None: + """Extract the headline overall_f1 (0-100) for CameraBench from the wide + df.eval.json shape: 'Overall' row, columns [precision, recall, f1, accuracy]. + """ + overall_row = scores_dict.get("Overall") + columns = scores_dict.get("__columns__") + if not isinstance(overall_row, list) or not isinstance(columns, list): + return None + try: + idx = columns.index("f1") + except ValueError: + return None + if idx >= len(overall_row): + return None + val = overall_row[idx] + return float(val) if isinstance(val, int | float) else None + + +def _extract_cosmos_cab_score(scores_dict: dict[str, Any]) -> float | None: + """Extract the headline f1 (0-100) for Cosmos-CAB benchmarks from the wide + df.eval.json shape: 'Overall' row, columns starting with 'f1'. + """ + overall_row = scores_dict.get("Overall") + columns = scores_dict.get("__columns__") + if not isinstance(overall_row, list) or not isinstance(columns, list): + return None + try: + idx = columns.index("f1") + except ValueError: + return None + if idx >= len(overall_row): + return None + val = overall_row[idx] + return float(val) if isinstance(val, int | float) else None + + +def _extract_locate_anything_bench_score(scores_dict: dict[str, Any]) -> float | None: + """Extract the headline overall_score (0-100) for LocateAnythingBench. + + `evaluate()` returns a wide DataFrame: rows = ['Overall', *7 datasets]; + cols start with the headline metric (avg_f1 for Box, f1 for Point). + After `eval_data.load_eval_summary_with_metadata`, the 'Overall' row + becomes a top-level list whose first element is the headline score + (already on the 0-100 scale). + """ + overall_row = scores_dict.get("Overall") + if isinstance(overall_row, list) and overall_row and isinstance(overall_row[0], int | float): + return float(overall_row[0]) + return None + + +def _extract_odinw_score(scores_dict: dict[str, Any]) -> float | None: + """Extract the headline average mAP for ODinW13, converted to 0-100. + + `evaluate()` returns a wide DataFrame: rows = ['Overall', *13 datasets]; + cols = ['mAP', 'mAP_50'], values on the natural 0-1 mAP scale. After + `load_eval_summary_with_metadata`, the 'Overall' row becomes a top-level list + whose first element is the average mAP (0-1). `normalize_score` applies the + single 0-1 -> 0-100 conversion; since mAP is always in [0,1] this is + unambiguous and cannot double-scale. + """ + overall_row = scores_dict.get("Overall") + if isinstance(overall_row, list) and overall_row and isinstance(overall_row[0], int | float): + return normalize_score(float(overall_row[0])) + return None + + +def extract_overall_score(scores_dict: dict[str, Any], dataset_name: str = "") -> float: + """Extract a single overall score from VLMEvalKit output. + + Uses dataset-specific overrides when available, otherwise falls back to + generic parse_scores() logic. Returns the score on the 0-100 scale, or + 0.0 if extraction fails. + """ + # Check dataset-specific override first (exact match, then prefix match) + override = SCORE_OVERRIDES.get(dataset_name) + if override is None: + for prefix, fn in SCORE_OVERRIDE_PREFIXES: + if dataset_name.startswith(prefix): + override = fn + break + if override is not None: + result = override(scores_dict) + if result is not None: + return result + + # Generic fallback + parsed = parse_scores(scores_dict) + if not parsed: + return 0.0 + if "overall" in parsed: + try: + return float(parsed["overall"]) + except (ValueError, TypeError): + pass + score_str = next(iter(parsed.values())) + try: + return float(score_str) + except (ValueError, TypeError): + return 0.0 + + +# --- RynnScale harness adapter (layer-1: native .json -> standardized scores) --- +# The ``rynnscale`` backend emits a native ``.json`` ({"metrics": {...}}) whose keys +# use RynnScale's own vocabulary ("Object Cognition", "traj", ...). These functions map that +# into standardized 0-100 component scores; the result then flows through the common +# ``extract_overall_score`` like every other backend. Ported verbatim from +# rynnscale-metric/rynnscale_metric/score_parser.py for score parity. NOTE: RynnScale always +# reports 0-1, so this uses an unconditional ``round(value*100, 2)`` — deliberately distinct +# from the heuristic ``normalize_score`` above (which serves VLMEvalKit's mixed 0-1/0-100 storage). + +RYNNSCALE_BENCHMARK_REGISTRY: dict[str, dict[str, Any]] = { + "RynnBrainCog": { + "output_file": "RynnBrainCog.json", + "components": { + "RynnBrain-Object": "Object Cognition", + "RynnBrain-Spatial": "Spatial Cognition", + }, + }, + "RynnBrainLoc": { + "output_file": "RynnBrainLoc.json", + "components": { + "RynnBrain-Grounding": "Object Referring", + "RynnBrain-Area": "area", + "RynnBrain-Affordance": "affordance", + "RynnBrain-Trajectory": "traj", + }, + }, +} + + +def extract_rynnscale_suite_scores(metrics: dict[str, Any], suite_name: str) -> dict[str, float]: + """Extract component scores for a RynnScale suite, normalized to 0-100. + + Returns a dict with component names as keys plus an "Overall" key for the average. + """ + registry = RYNNSCALE_BENCHMARK_REGISTRY.get(suite_name) + if not registry: + return {} + + components = registry["components"] + scores: dict[str, float] = {} + values = [] + + for component_name, output_key in components.items(): + if output_key in metrics: + normalized = round(metrics[output_key] * 100.0, 2) # RynnScale is always 0-1; see note above + scores[component_name] = normalized + values.append(normalized) + + if values: + scores["Overall"] = round(sum(values) / len(values), 2) + + return scores + + +def extract_rynnscale_scores( + raw_metrics: dict[str, dict[str, Any]], + benchmarks: list[str], +) -> dict[str, dict[str, float]]: + """Extract scores for all requested RynnScale suites. + + Args: + raw_metrics: suite name -> full metrics dict from the RynnScale ``.json``. + benchmarks: suite names to extract (e.g. ["RynnBrainCog", "RynnBrainLoc"]). + + Returns: + suite name -> {component: score_0_100, "Overall": avg_0_100}. + """ + result: dict[str, dict[str, float]] = {} + for suite in benchmarks: + if suite in raw_metrics: + result[suite] = extract_rynnscale_suite_scores(raw_metrics[suite], suite) + return result + + +# =========================================================================== +# cosmos_eval CLI: locate the eval output, report Overall + native sub-scores. +# (Mirrors vlmeval_run._finalize_scores: load summary, inject __columns__, +# then extract_overall_score keyed on the benchmark name.) +# =========================================================================== + +# Metadata keys injected alongside real scores; never reported as sub-scores. +_META_PREFIX = "__" + + +def find_eval_output(work_dir: Path) -> Path | None: + """Newest `*/*/*.eval.json` under a run.py work-dir (matches the internal finder).""" + files = list(Path(work_dir).glob("*/*/*.eval.json")) + return max(files, key=lambda p: p.stat().st_mtime, default=None) + + +def load_scores(eval_path: Path) -> dict[str, Any]: + """Load an eval output into the scores dict the parser expects (with __columns__).""" + summary = load_eval_summary_with_metadata(Path(eval_path)) + scores = summary.data + if summary.columns: + scores["__columns__"] = summary.columns + return scores + + +def _infer_dataset_name(eval_path: Path) -> str: + """Best-effort benchmark name from a `_.{dict,df}.eval.json` filename. + + Used only when --dataset is omitted; the launcher always passes it explicitly + (the per-benchmark score override is keyed on this name). + """ + name = eval_path.name + for suffix in (".dict.eval.json", ".df.eval.json"): + if name.endswith(suffix): + name = name[: -len(suffix)] + break + return name.split("_", 1)[1] if "_" in name else name + + +def report( + work_dir: str | None = None, + eval_json: str | None = None, + dataset_name: str = "", +) -> dict[str, Any]: + """Resolve the eval output and return {eval_json, dataset, overall, subscores}.""" + eval_path = Path(eval_json) if eval_json else find_eval_output(Path(work_dir)) + if eval_path is None: + return {"eval_json": None, "dataset": dataset_name, "overall": None, "subscores": {}} + scores = load_scores(eval_path) + dataset = dataset_name or _infer_dataset_name(eval_path) + overall = extract_overall_score(scores, dataset_name=dataset) + subscores = { + k: v + for k, v in scores.items() + if not str(k).startswith(_META_PREFIX) and isinstance(v, (int, float)) and not isinstance(v, bool) + } + return {"eval_json": str(eval_path), "dataset": dataset, "overall": overall, "subscores": subscores} + + +def main(argv: list[str] | None = None) -> None: + ap = argparse.ArgumentParser(description="Report Overall + sub-scores from a run.py eval output.") + src = ap.add_mutually_exclusive_group(required=True) + src.add_argument("--work-dir", help="run.py output dir; the newest */*/*.eval.json is used") + src.add_argument("--eval-json", help="explicit path to a *.eval.json") + ap.add_argument("--dataset", default="", help="benchmark name (drives the per-benchmark score override)") + ap.add_argument("--json", action="store_true", help="emit machine-readable JSON") + args = ap.parse_args(argv) + + r = report(work_dir=args.work_dir, eval_json=args.eval_json, dataset_name=args.dataset) + if args.json: + print(json.dumps(r)) + return + if r["eval_json"] is None: + print("no eval output found", file=sys.stderr) + raise SystemExit(1) + print(f"{r['dataset']} Overall: {r['overall']:.2f}") + for k, v in sorted(r["subscores"].items()): + print(f" {k}: {v}") + + +if __name__ == "__main__": + main() diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/cosmos_eval/run_all.py b/evaluation/cosmos3/reasoner/vlmevalkit/cosmos_eval/run_all.py new file mode 100644 index 00000000..7a4c7be8 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/cosmos_eval/run_all.py @@ -0,0 +1,279 @@ +# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# SPDX-License-Identifier: Apache-2.0 + +"""Parallel launcher for the cosmos_eval kit. + +Composes each benchmark's `run.py --config` JSON from the committed split +artifacts (`data//.json` + `models/.json`), runs the +stock vlmevalkit `run.py` for each in a bounded process pool, and reports the +headline score per benchmark via `parse_score.py`. + +This is the END-USER entrypoint: stdlib only, no `vlmeval-metric` dependency. +The internal generator (`gen_oss_configs.py`) is what *produced* the artifacts; +this just consumes them with a dumb dict-merge — no routing/schema logic here. + +Quickstart: + + export COSMOS_API_BASE=https:///v1/chat/completions + export COSMOS_MODEL= COSMOS_API_KEY= + export OPENAI_API_BASE= OPENAI_API_KEY= # gpt-4o judge + python cosmos_eval/run_all.py --model cosmos --concurrency 8 --work-dir ./out + # runs every benchmark in the manifest + + # one/few: --benchmarks VANTAGE_VQA,AETCBench_all + # inspect: --export-configs ./cfgs (compose + write, no run) + # tweak-run: --import-configs ./cfgs (run from a dir of edited configs) +""" + +from __future__ import annotations + +import argparse +import json +import os +import string +import subprocess +import sys +from concurrent.futures import ThreadPoolExecutor, as_completed +from pathlib import Path +from typing import Any + +import parse_score # sibling module in cosmos_eval/ + +HERE = Path(__file__).resolve().parent +VLMEVALKIT_ROOT = HERE.parent +RUN_PY = VLMEVALKIT_ROOT / "run.py" + +# ConcatDataset benchmarks (e.g. Astro2D) evaluate per sub-dataset via +# `eval_file.replace(dataset_name, sub_name)`, which rewrites the name EVERYWHERE in the +# path — including the work-dir component. So they need a work-dir whose path does NOT +# contain the benchmark name, otherwise the sub-result targets a sibling dir that is never +# created (FileNotFoundError). These get a name-free (indexed) work-dir; all other benches +# keep the readable / layout. +_CONCAT_BENCHES = {"VANTAGE_Astro2D"} + + +# --------------------------------------------------------------------------- +# Kit loading + composition (the dumb dict-merge; mirrors gen_oss_configs.compose) +# --------------------------------------------------------------------------- + + +def load_model_layer(model: str) -> dict[str, Any]: + path = HERE / "models" / f"{model}.json" + if not path.exists(): + sys.exit(f"Error: no model layer at {path} (have: " + f"{', '.join(p.stem for p in sorted((HERE / 'models').glob('*.json')))})") + return json.loads(path.read_text("utf-8")) + + +def load_manifest() -> dict[str, Any]: + return json.loads((HERE / "manifest.json").read_text("utf-8")) + + +def load_data_conf(domain: str, bench: str) -> dict[str, Any]: + return json.loads((HERE / "data" / domain / f"{bench}.json").read_text("utf-8")) + + +def _render_env(obj: Any) -> Any: + """Substitute ${VAR} placeholders from the environment, recursively.""" + if isinstance(obj, str): + return string.Template(obj).safe_substitute(os.environ) + if isinstance(obj, dict): + return {k: _render_env(v) for k, v in obj.items()} + if isinstance(obj, list): + return [_render_env(v) for v in obj] + return obj + + +def compose(data_conf: dict[str, Any], model_layer: dict[str, Any], bench: str) -> tuple[dict, dict]: + """model_conf = {class} | defaults | benchmarks[bench]; dataset_conf = data | {model_family}. + + Placeholders are kept verbatim (env substitution is applied separately at write time). + """ + model_conf = {"class": model_layer["class"]} + model_conf.update(model_layer["defaults"]) + model_conf.update(model_layer["benchmarks"].get(bench, {})) + dataset_conf = dict(data_conf) + dataset_conf["model_family"] = model_layer["model_family"] + return model_conf, dataset_conf + + +def build_config(model_layer: dict[str, Any], domain: str, bench: str, *, render: bool) -> dict[str, Any]: + """The full `run.py --config` document for one benchmark.""" + model_conf, dataset_conf = compose(load_data_conf(domain, bench), model_layer, bench) + if render: + model_conf = _render_env(model_conf) + return {"model": {model_layer["model_key"]: model_conf}, "data": {bench: dataset_conf}} + + +# --------------------------------------------------------------------------- +# Benchmark selection +# --------------------------------------------------------------------------- + + +def select_benches( + manifest: dict[str, Any], + *, + domains: list[str] | None, + benchmarks: list[str] | None, +) -> list[dict[str, Any]]: + """Resolve the manifest into an ordered list of {key, bench, domain, run}. + + Runs every benchmark in the manifest, optionally narrowed by `domains` / `benchmarks`. + """ + selected: list[dict[str, Any]] = [] + for key, entry in manifest.items(): + domain, bench = key.split("/", 1) + if domains and domain not in domains: + continue + if benchmarks and bench not in benchmarks: + continue + selected.append({"key": key, "bench": bench, "domain": domain, "run": entry.get("run", {})}) + for i, it in enumerate(selected): + it["idx"] = i # stable index for name-free work-dirs (ConcatDataset benches) + return selected + + +def _bench_workdir(work_root: Path, item: dict[str, Any]) -> Path: + """Per-bench work-dir. ConcatDataset benches get a NAME-FREE dir (see _CONCAT_BENCHES).""" + if item["bench"] in _CONCAT_BENCHES: + return work_root / f"_concat_{item['idx']:02d}" + return work_root / item["bench"] + + +# --------------------------------------------------------------------------- +# run.py invocation +# --------------------------------------------------------------------------- + + +def _run_cmd(config_path: Path, work_dir: Path, run_flags: dict[str, Any]) -> list[str]: + """Build the stock `run.py --config` command (no --data/--model; cfg drives them).""" + cmd = [sys.executable, str(RUN_PY), "--config", str(config_path), "--work-dir", str(work_dir)] + cmd += ["--api-nproc", str(run_flags.get("api_nproc", 16))] + cmd += ["--judge-nproc", str(run_flags.get("judge_nproc", 4))] + if run_flags.get("judge"): + cmd += ["--judge", str(run_flags["judge"])] + if run_flags.get("judge_args"): + cmd += ["--judge-args", str(run_flags["judge_args"])] + cmd += ["--verbose", "--save-eval-results"] + return cmd + + +def run_one(item: dict[str, Any], model_layer: dict[str, Any], *, work_root: Path, + configs_dir: Path, import_dir: Path | None) -> dict[str, Any]: + """Compose (or import) the config, run run.py, then parse the score. Returns a result row.""" + bench, domain = item["bench"], item["domain"] + bench_out = _bench_workdir(work_root, item) + bench_out.mkdir(parents=True, exist_ok=True) + + if import_dir is not None: + config_path = import_dir / f"{bench}.json" + if not config_path.exists(): + return {**item, "status": "error", "score": None, "detail": f"no imported config {config_path}"} + # Imported configs may still carry ${...} placeholders; render into the work copy. + rendered = _render_env(json.loads(config_path.read_text("utf-8"))) + config_path = configs_dir / f"{bench}.json" + config_path.write_text(json.dumps(rendered, indent=2), "utf-8") + else: + cfg = build_config(model_layer, domain, bench, render=True) + config_path = configs_dir / f"{bench}.json" + config_path.write_text(json.dumps(cfg, indent=2), "utf-8") + + cmd = _run_cmd(config_path, bench_out, item["run"]) + log_path = bench_out / "run.log" + with log_path.open("w") as log: + proc = subprocess.run(cmd, cwd=str(VLMEVALKIT_ROOT), stdout=log, + stderr=subprocess.STDOUT, env=os.environ.copy()) + if proc.returncode != 0: + return {**item, "status": "error", "score": None, "detail": f"run.py exit {proc.returncode}; see {log_path}"} + + rep = parse_score.report(work_dir=str(bench_out), dataset_name=bench) + if rep["eval_json"] is None: + return {**item, "status": "no-eval", "score": None, "detail": f"no eval output; see {log_path}"} + return {**item, "status": "ok", "score": rep["overall"], "subscores": rep["subscores"]} + + +# --------------------------------------------------------------------------- +# CLI +# --------------------------------------------------------------------------- + + +def _csv(value: str | None) -> list[str] | None: + return [x.strip() for x in value.split(",") if x.strip()] if value else None + + +def _print_summary(results: list[dict[str, Any]]) -> None: + print("\n=== cosmos_eval summary ===") + width = max([len(r["bench"]) for r in results] + [9]) + for r in sorted(results, key=lambda x: x["key"]): + score = f"{r['score']:.2f}" if isinstance(r.get("score"), (int, float)) else "-" + line = f" {r['bench']:<{width}} {r['status']:<8} {score:>7}" + if r["status"] != "ok": + line += f" {r.get('detail', '')}" + print(line) + ok = sum(1 for r in results if r["status"] == "ok") + print(f"\n {ok}/{len(results)} ok, {len(results) - ok} failed") + + +def main(argv: list[str] | None = None) -> None: + ap = argparse.ArgumentParser(description=__doc__, formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--model", default="cosmos", help="model layer (models/.json); default cosmos") + ap.add_argument("--domains", default=None, help="comma-separated domains (default: all in the manifest)") + ap.add_argument("--benchmarks", default=None, help="comma-separated benchmark names (default: all in the manifest)") + ap.add_argument("--concurrency", type=int, default=4, help="max concurrent run.py subprocesses") + ap.add_argument("--work-dir", default="./cosmos_eval_out", help="run output root") + ap.add_argument("--dry-run", action="store_true", help="print the run.py commands and exit") + ap.add_argument("--export-configs", metavar="DIR", help="compose + write each config to DIR, then exit") + ap.add_argument("--import-configs", metavar="DIR", help="run from a dir of pre-composed configs instead of composing") + args = ap.parse_args(argv) + + model_layer = load_model_layer(args.model) + manifest = load_manifest() + + selected = select_benches(manifest, domains=_csv(args.domains), benchmarks=_csv(args.benchmarks)) + if not selected: + sys.exit("Error: no benchmarks selected (check --domains/--benchmarks).") + + # --export-configs: compose (placeholders intact) + write, then exit. + if args.export_configs: + out = Path(args.export_configs) + out.mkdir(parents=True, exist_ok=True) + for item in selected: + cfg = build_config(model_layer, item["domain"], item["bench"], render=False) + (out / f"{item['bench']}.json").write_text(json.dumps(cfg, indent=2), "utf-8") + print(f"Exported {len(selected)} config(s) to {out} (placeholders intact).") + return + + work_root = Path(args.work_dir) + configs_dir = work_root / "_configs" + configs_dir.mkdir(parents=True, exist_ok=True) + import_dir = Path(args.import_configs) if args.import_configs else None + + if args.dry_run: + for item in selected: + cfg_path = (import_dir or configs_dir) / f"{item['bench']}.json" + print(" ".join(_run_cmd(cfg_path, work_root / item["bench"], item["run"]))) + print(f"\n[dry-run] {len(selected)} benchmark(s) selected.") + return + + print(f"Running {len(selected)} benchmark(s) with concurrency {args.concurrency} -> {work_root}") + results: list[dict[str, Any]] = [] + with ThreadPoolExecutor(max_workers=max(1, args.concurrency)) as pool: + futures = { + pool.submit(run_one, item, model_layer, work_root=work_root, + configs_dir=configs_dir, import_dir=import_dir): item + for item in selected + } + for fut in as_completed(futures): + r = fut.result() + results.append(r) + mark = "ok" if r["status"] == "ok" else r["status"].upper() + score = f"{r['score']:.2f}" if isinstance(r.get("score"), (int, float)) else "-" + print(f" [{mark}] {r['bench']}: {score}") + + _print_summary(results) + if any(r["status"] != "ok" for r in results): + raise SystemExit(1) + + +if __name__ == "__main__": + main() diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/.readthedocs.yaml b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/.readthedocs.yaml new file mode 100644 index 00000000..c6cf8e2a --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/.readthedocs.yaml @@ -0,0 +1,17 @@ +version: 2 + +# Set the version of Python and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.8" + +formats: + - epub + +sphinx: + configuration: docs/en/conf.py + +python: + install: + - requirements: requirements/docs.txt diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/ConfigSystem.md b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/ConfigSystem.md new file mode 100644 index 00000000..120e0cb0 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/ConfigSystem.md @@ -0,0 +1,67 @@ +# Config System + +By default, VLMEvalKit launches the evaluation by setting the model name(s) (defined in `/vlmeval/config.py`) and dataset name(s) (defined in `vlmeval/dataset/__init__.py` or `vlmeval/dataset/video_dataset_config.py`) in the `run.py` script with the `--model` and `--data` arguments. Such approach is simple and efficient in most scenarios, however, it may not be flexible enough when the user wants to evaluate multiple models / datasets with different settings. + +To address this, VLMEvalKit provides a more flexible config system. The user can specify the model and dataset settings in a json file, and pass the path to the config file to the `run.py` script with the `--config` argument. Here is a sample config json: + +```json +{ + "model": { + "GPT4o_20240806_T00_HIGH": { + "class": "GPT4V", + "model": "gpt-4o-2024-08-06", + "temperature": 0, + "img_detail": "high" + }, + "GPT4o_20240806_T10_Low": { + "class": "GPT4V", + "model": "gpt-4o-2024-08-06", + "temperature": 1.0, + "img_detail": "low" + }, + "GPT4o_20241120": {} + }, + "data": { + "MME-RealWorld-Lite": { + "class": "MMERealWorld", + "dataset": "MME-RealWorld-Lite" + }, + "MMBench_DEV_EN_V11": { + "class": "ImageMCQDataset", + "dataset": "MMBench_DEV_EN_V11" + }, + "MMBench_Video_8frame_nopack":{}, + "Video-MME_16frame_subs": { + "class": "VideoMME", + "dataset": "Video-MME", + "nframe": 16, + "use_subtitle": true + } + } +} +``` + +Explanation of the config json: + +1. Now we support two fields: `model` and `data`, each of which is a dictionary. The key of the dictionary is the name of the model / dataset (set by the user), and the value is the setting of the model / dataset. +2. For items in `model`, the value is a dictionary containing the following keys: + - `class`: The class name of the model, which should be a class name defined in `vlmeval/vlm/__init__.py` (open-source models) or `vlmeval/api/__init__.py` (API models). + - Other kwargs: Other kwargs are model-specific parameters, please refer to the definition of the model class for detailed usage. For example, `model`, `temperature`, `img_detail` are arguments of the `GPT4V` class. It's noteworthy that the `model` argument is required by most model classes. + - Tip: The defined model in the `supported_VLM` of `vlmeval/config.py` can be used as a shortcut, for example, `GPT4o_20241120: {}` is equivalent to `GPT4o_20241120: {'class': 'GPT4V', 'model': 'gpt-4o-2024-11-20', 'temperature': 0, 'img_size': -1, 'img_detail': 'high', 'retry': 10, 'verbose': False}` +3. For the dictionary `data`, we suggest users to use the official dataset name as the key (or part of the key), since we frequently determine the post-processing / judging settings based on the dataset name. For items in `data`, the value is a dictionary containing the following keys: + - `class`: The class name of the dataset, which should be a class name defined in `vlmeval/dataset/__init__.py`. + - Other kwargs: Other kwargs are dataset-specific parameters, please refer to the definition of the dataset class for detailed usage. Typically, the `dataset` argument is required by most dataset classes. It's noteworthy that the `nframe` argument or `fps` argument is required by most video dataset classes. + - Tip: The defined dataset in the `supported_video_datasets` of `vlmeval/dataset/video_dataset_config.py` can be used as a shortcut, for example, `MMBench_Video_8frame_nopack: {}` is equivalent to `MMBench_Video_8frame_nopack: {'class': 'MMBenchVideo', 'dataset': 'MMBench-Video', 'nframe': 8, 'pack': False}`. +Saving the example config json to `config.json`, you can launch the evaluation by: + +```bash +python run.py --config config.json +``` + +That will generate the following output files under the working directory `$WORK_DIR` (Following the format `{$WORK_DIR}/{$MODEL_NAME}/{$MODEL_NAME}_{$DATASET_NAME}_*`): + +- `$WORK_DIR/GPT4o_20240806_T00_HIGH/GPT4o_20240806_T00_HIGH_MME-RealWorld-Lite*` +- `$WORK_DIR/GPT4o_20240806_T10_Low/GPT4o_20240806_T10_Low_MME-RealWorld-Lite*` +- `$WORK_DIR/GPT4o_20240806_T00_HIGH/GPT4o_20240806_T00_HIGH_MMBench_DEV_EN_V11*` +- `$WORK_DIR/GPT4o_20240806_T10_Low/GPT4o_20240806_T10_Low_MMBench_DEV_EN_V11*` +... diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/Contributors.md b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/Contributors.md new file mode 100644 index 00000000..ddf50c6c --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/Contributors.md @@ -0,0 +1,21 @@ +# Contributors + +## Contributors w. 3+ Major Contributions + +> In this section, we list all the contributors who have made significant contributions (3+) to the development of VLMEvalKit. + +New Qualified Contributors (2024.09): + +1. [amitbcp](https://github.com/amitbcp): The contributor helped support MUIRBench, Phi-3.5, Idefics3, VILA, and xGen-MM +2. [czczup](https://github.com/czczup): The contributor helped support the InternVL Series (V1.5, Mini-InternVL, V2, etc.) +3. [DseidLi](https://github.com/DseidLi): The contributor helped support LLaVA-OneVision, GQA, and developed the readthedocs site for VLMEvalKit +4. [mayubo2333](https://github.com/mayubo2333): The contributor helped support MMLongBench, SlideVQA, and DUDE +5. [sun-hailong](https://github.com/sun-hailong): The contributor helped support A-OKVQA, Parrot, MMMB, and MTL-MMBench +6. [PhoenixZ810](https://github.com/PhoenixZ810): The contributor helped support Video-ChatGPT, Chat-UniVI, and Llama-VID +7. [Cuiunbo](https://github.com/Cuiunbo): The contributor helped support OmniLMM-12B, MiniCPM-V Series (V1, V2, V2.5) + +## Full Contributor List + +> In this section, we list all the contributors as well as their corresponding contributions to the development of VLMEvalKit. + +TBD. diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/Development.md b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/Development.md new file mode 100644 index 00000000..0fe5a60e --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/Development.md @@ -0,0 +1,145 @@ +# Develop new Benchmark / MLLM + +> 🛠️ How to implement a new Benchmark / VLM in VLMEvalKit? + +## Implement a new benchmark + +Example PR: **Math-Vision Benchmark** ([#292](https://github.com/open-compass/VLMEvalKit/pull/292/files)) + +In VLMEvalKit, benchmarks are organized as dataset classes. When you try to implement a new benchmark, you can either reuse existing dataset classes (*e.g.*, You can reuse `ImageMCQDataset` when implementing a new multi-choice benchmark), or support a new dataset class. Each dataset must have the following two member functions (either reuse the one of the parent class or implement your own): + +- `build_prompt(self, line)`: The function input `line` is an integer (the sample index) or a `pd.Series` object (the raw record of the sample). The function outputs a `multi-modal message`, serving as the input of an MLLM. The `multi-modal message` is an interleaved list of multi-modal messages adopting the following format (the example includes an image and a text message): `[dict(type='image', value=IMAGE_PTH), dict(type='text', value=prompt)]`. +- `evaluate(self, eval_file, **judge_kwargs)`: The function input `eval_file` is the MLLM prediction (typically in `.xlsx` format). If the benchmark requires an external LLM (typically GPT) for evaluation, then `judge_kwargs` can pass the arguments for the LLM. The function outputs the benchmark evaluation results (metrics) in the form of `dict` or `pd.DataFrame`. + +We then brief the typical steps to implement a new benchmark under VLMEvalKit: + +### 1. Prepare your benchmark tsv file + +Currently, we organize a benchmark as one single TSV file. During inference, the data file will be automatically downloaded from the definited `DATASET_URL` link to `$LMUData` file (default path is `$HOME/LMUData`, if not set explicitly). You can upload the prepared TSV file to a downloadable address (e.g., Huggingface) or send it to us at . We will assist in uploading the dataset to the server. You can also customize `LMUData` path in the environment variable `LMUData=/path/to/your/data`. + +The contents of the TSV file consist of: + +| Dataset Name \ Fields | index | image | image_path | question | hint | multi-choice
options | answer | category | l2-category | split | +| --------------------------------------- | ----- | ----- | ---------- | -------- | ---- | ----------------------- | ------ | -------- | ----------- | ----- | +| MMBench_DEV_[CN/EN] | ✅ | ✅ | | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| MMBench_TEST_[CN/EN] | ✅ | ✅ | | ✅ | ✅ | ✅ | | ✅ | ✅ | ✅ | +| CCBench | ✅ | ✅ | | ✅ | | ✅ | ✅ | ✅ | | | +| SEEDBench_IMG | ✅ | ✅ | | ✅ | | ✅ | ✅ | ✅ | | | +| MME | ✅ | ✅ | | ✅ | | | ✅ | ✅ | | | +| MMVet | ✅ | ✅ | | ✅ | | | ✅ | ✅ | | | +| MMMU_DEV_VAL | ✅ | ✅ | ✅ | ✅ | | ✅ | ✅ | ✅ | ✅ | ✅ | +| COCO_VAL | ✅ | ✅ | | | | | ✅ | | | | +| OCRVQA_[TEST/TESTCORE] | ✅ | ✅ | | ✅ | | | ✅ | | | | +| TextVQA_VAL | ✅ | ✅ | | ✅ | | | ✅ | | | | +| VCR_[EN/ZH]\_[EASY/HARD]\_[ALL/500/100] | ✅ | ✅ | | ✅ | | | ✅ | | | | +| MMMB_[en/cn/pt/ar/tr/ru] | ✅ | ✅ | | ✅ | ✅ | ✅ | ✅ | ✅ | |✅ | +| MMBench_dev_[en/cn/pt/ar/tr/ru] | ✅ | ✅ | | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |✅ | + +

+ +**Intro to mandatory fields in the `TSV` file:** + +- **index:** Integer, Unique for each line in `tsv` +- **image:** The base64 of the image, you can use APIs implemented in `vlmeval/smp/vlm.py` for encoding and decoding: + - Encoding: `encode_image_to_base64 `(for PIL Image) / `encode_image_file_to_base64` (for image file path) + - Decoding: `decode_base64_to_image`(for PIL Image) / `decode_base64_to_image_file` (for image file path) +- **question**: The question corresponding to the image, a string +- **answer**: The answer to the question, a string. The `test` split does not need this field + +### 2. Cutomize your benchmark prompt + +`ImageBaseDataset` defines the default prompt format. If you need to add prompts specific to the dataset or input data in the `Interleave` format to the model, you can implement this through the `build_prompt(line)` function. This function takes a line from a TSV file as input, containing fields such as index, image, question, etc. The function returns a dictionary list of multimodal messages `msg` in the format `[dict(type='image', value=IMAGE_PTH), dict(type='text', value=prompt)]`, including the image path and the text prompt to be input into VLMs. For interleave type inputs, you can directly place the dictionary of the image path at the image token position. + +### 3. Cutomize your benchmark metrics + +To add evaluation for a new benchmark, you need to customize a class object to implement the dataset’s metrics calculation. Multimodal datasets inherit from the `ImageBaseDataset` object in `vlmeval/dataset/image_base.py`. The TYPE defines the type of dataset, `DATASET_URL` is the download address of the dataset, and `DATASET_MD5` is the MD5 checksum for consistency checking of the dataset file. + +In this class, **you need to implement** the `evaluate(eval_file, **judge_kwargs)` class function to calculate metrics and output results for the custom dataset. The function input `eval_file` is the path to the model prediction results file `{model_name}_{dataset}.xlsx`. This file can be read as a pandas.DataFrame using the `load(eval_file)` method, containing fields such as index, question, answer, category, prediction, etc. The judge_kwargs will pass a dictionary related to evaluation, such as the name of the `judge model`, the number of API request threads, etc. **The return value** of the function is the calculated accuracy and other metrics, formatted as a dictionary composed of lists, organized into a pandas.DataFrame. + +## Implement a new model + +Example PR: **Support LLaVA-Next-Interleave** ([#294](https://github.com/open-compass/VLMEvalKit/pull/294)) + +**1. Support `generate_inner` API (mandatory).** + +All existing models are implemented in `vlmeval/vlm`. For a minimal model, your model class **must implement the method** `generate_inner(msgs, dataset=None)`. In this function, you feed a multi-modal message to your VLM and return the VLM prediction (which is a string). The optional argument `dataset` can be used as the flag for the model to switch among various inference strategies. + +The multi-modal messages `msgs` is a list of dictionaries, each dictionary has two keys: type and value: +- `type`: We currently support two types, choices are ["image", "text"]. +- `value`: When type=='text' , the value is the text message (a single string); when type=='image', the value can be the local path of an image file, or the image URL. + +Currently a multi-modal message may contain arbitrarily interleaved images and texts. If your model do not support that, a practice can be taking the 1st image and concatenated text messages as the input. You can set the `INTERLEAVE = False` in your model class and use `self.message_to_promptimg(message, dataset=dataset)` to build your prompt and the first image's path. + +Here are some examples of multi-modal messages: + +```python +IMAGE_PTH = 'assets/apple.jpg' +IMAGE_URL = 'https://raw.githubusercontent.com/open-compass/VLMEvalKit/main/assets/apple.jpg' +msg1 = [ + dict(type='image', value=IMAGE_PTH), + dict(type='text', value='What is in this image?') +] +msg2 = [ + dict(type='image', value=IMAGE_URL), + dict(type='image', value=IMAGE_URL), + dict(type='text', value='How many apples are there in these images?') +] +response = model.generate(msg1) +``` + +For convenience sake, we also support to take a list of string as inputs. In that case, we will check if a string is an image path or image URL and automatically convert it to the list[dict] format: + +```python +IMAGE_PTH = 'assets/apple.jpg' +IMAGE_URL = 'https://raw.githubusercontent.com/open-compass/VLMEvalKit/main/assets/apple.jpg' +msg1 = [IMAGE_PTH, 'What is in this image?'] +msg2 = [IMAGE_URL, IMAGE_URL, 'How many apples are there in these images?'] +response = model.generate(msg1) +``` + +**Support Custom Prompt (optional).** + +Besides, your model can support **custom prompt building** by implementing two optional methods: `use_custom_prompt(dataset)` and `build_prompt(line, dataset=None)`. + +Both functions take the dataset name as the input: + +- `use_custom_prompt(dataset)` returns a boolean flag, indicating whether the model should use the custom prompt building strategy. +- If `use_custom_prompt(dataset)` returns True, `build_prompt(line, dataset)` should return a customly bulit multimodal message for the corresponding `dataset`, given `line`, which is a dictionary that includes the necessary information of a data sample. If `use_custom_prompt(dataset)` returns False, the default prompt building strategy will be used. + +**Support multi-turn chatting (optional).** + +You can also support the multi-turn chatting and evaluation with your VLM by supporting the `chat_inner(message, dataset)` function. The function outputs a single string response, and the `message` is a list of chat history, following the below format. + +```python +# Assume msg1, msg2, msg3, ... are multi-modal messages following the previously described format +# `chat_inner` take the following chat history list as input: +message = [ + dict(role='user', content=msg1), + dict(role='assistant', content=msg2), + dict(role='user', content=msg3), + dict(role='assistant', content=msg4), + ...... + dict(role='user', content=msgn), +] +# `message` should contain an odd number of chat utterances, the role of utterances should be interleaved "user" and "assistant", with the role of the last utterance to be "user". +# The chat function will call `chat_inner` +response = model.chat(message) +``` + +### Example PRs: + +- VLM that doesn't support interleaved images and texts, and does not use custom prompts: [[Model] Support glm-4v-9b](https://github.com/open-compass/VLMEvalKit/pull/221) +- VLM that supports interleaved images and texts and custom prompts: [Add MiniCPM-Llama3-V-2.5](https://github.com/open-compass/VLMEvalKit/pull/205) +- VLM API: [Feature add glmv](https://github.com/open-compass/VLMEvalKit/pull/201) + +## Contribute to VLMEvalKit + +If you want to contribute codes to **VLMEvalKit**, please do the pre-commit check before you submit a PR. That helps to keep the code tidy. + +```bash +# Under the directory of VLMEvalKit, install the pre-commit hook: +pip install pre-commit +pre-commit install +pre-commit run --all-files +# Then you can commit your code. +``` diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/EvalByLMDeploy.md b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/EvalByLMDeploy.md new file mode 100644 index 00000000..fc0a8c38 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/EvalByLMDeploy.md @@ -0,0 +1,27 @@ +# Using LMDeploy to Accelerate Evaluation and Inference + +VLMEvalKit supports testing VLM models deployed by LMDeploy. Below, we use InternVL2-8B as an example to show how to test the model. + +## Step 0: Install LMDeploy + +```bash +pip install lmdeploy +``` +For other installation methods, you can refer to LMDeploy's [documentation](https://github.com/InternLM/lmdeploy). + +## Step 1: Start the Inference Service + +```bash +lmdeploy serve api_server OpenGVLab/InternVL2-8B --model-name InternVL2-8B +``` +> [!IMPORTANT] +> Since models in VLMEvalKit may have custom behaviors when building prompts for different datasets, such as InternVL2's handling of HallusionBench, it is necessary to specify `--model-name` when starting the server. This allows the VLMEvalKit to select appropriate prompt construction strategy based on the name when using the LMDeploy API. +> +> If `--server-port`, is specified, the corresponding environment variable `LMDEPLOY_API_BASE` needs to be set. + + +## Step 2: Evaluation + +```bash +python run.py --data MMStar --model lmdeploy --verbose --api-nproc 64 +``` diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/Makefile b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/Makefile new file mode 100644 index 00000000..d4bb2cbb --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/Quickstart.md b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/Quickstart.md new file mode 100644 index 00000000..264d2cdd --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/Quickstart.md @@ -0,0 +1,236 @@ +# Quickstart + +Before running the evaluation script, you need to **configure** the VLMs and set the model_paths properly. + +After that, you can use a single script `run.py` to inference and evaluate multiple VLMs and benchmarks at a same time. + +## Step 0. Installation & Setup essential keys + +**Installation.** + +```bash +git clone https://github.com/open-compass/VLMEvalKit.git +cd VLMEvalKit +pip install -e . +``` + +**Setup Keys.** + +To infer with API models (GPT-4v, Gemini-Pro-V, etc.) or use LLM APIs as the **judge or choice extractor**, you need to first setup API keys. VLMEvalKit will use an judge **LLM** to extract answer from the output if you set the key, otherwise it uses the **exact matching** mode (find "Yes", "No", "A", "B", "C"... in the output strings). **The exact matching can only be applied to the Yes-or-No tasks and the Multi-choice tasks.** +- You can place the required keys in `$VLMEvalKit/.env` or directly set them as the environment variable. If you choose to create a `.env` file, its content will look like: + + ```bash + # The .env file, place it under $VLMEvalKit + # API Keys of Proprietary VLMs + # QwenVL APIs + DASHSCOPE_API_KEY= + # Gemini w. Google Cloud Backends + GOOGLE_API_KEY= + # OpenAI API + OPENAI_API_KEY= + OPENAI_API_BASE= + # StepAI API + STEPAI_API_KEY= + # REKA API + REKA_API_KEY= + # GLMV API + GLMV_API_KEY= + # CongRong API + CW_API_BASE= + CW_API_KEY= + # SenseNova API + SENSENOVA_API_KEY= + # Hunyuan-Vision API + HUNYUAN_SECRET_KEY= + HUNYUAN_SECRET_ID= + # LMDeploy API + LMDEPLOY_API_BASE= + # MiniMax API + MINIMAX_API_KEY= + # You can also set a proxy for calling api models during the evaluation stage + EVAL_PROXY= + ``` + +- Fill the blanks with your API keys (if necessary). Those API keys will be automatically loaded when doing the inference and evaluation. +## Step 1. Configuration + +**VLM Configuration**: All VLMs are configured in `vlmeval/config.py`. Few legacy VLMs (like MiniGPT-4, LLaVA-v1-7B) requires additional configuration (configuring the code / model_weight root in the config file). During evaluation, you should use the model name specified in `supported_VLM` in `vlmeval/config.py` to select the VLM. Make sure you can successfully infer with the VLM before starting the evaluation with the following command `vlmutil check {MODEL_NAME}`. + +Note: For the Qwen-VL series models (Qwen-VL, Qwen2-VL, Qwen2.5-VL), the upper and lower bounds of the number of pixels specified in vlmeval/config.py are as follows: + +``` +min_pixels=1280 * 28 * 28, +max_pixels=16384 * 28 * 28, +``` +Where 1280 is the maximum value recommended by Qwen for balancing performance, computational resources, and memory, and 16384 is the theoretical maximum value for model input. This setting has a positive effect on some vision tasks that require high resolution (such as document understanding). However, there is no practical basis for considering this setting. If you need to align with the official settings, you can remove these two values, or set them to the following values from the official Qwen demo: + +``` +min_pixels=256 * 28 * 28, +max_pixels=1280 * 28 * 28, +``` + +## Step 2. Evaluation + +**New!!!** We integrated a new config system to enable more flexible evaluation settings. Check the [Document](/docs/en/ConfigSystem.md) or run `python run.py --help` for more details 🔥🔥🔥 + +We use `run.py` for evaluation. To use the script, you can use `$VLMEvalKit/run.py` or create a soft-link of the script (to use the script anywhere): + +**Arguments** + +- `--data (list[str])`: Set the dataset names that are supported in VLMEvalKit (names can be found in the codebase README). +- `--model (list[str])`: Set the VLM names that are supported in VLMEvalKit (defined in `supported_VLM` in `vlmeval/config.py`). +- `--mode (str, default to 'all', choices are ['all', 'infer'])`: When `mode` set to "all", will perform both inference and evaluation; when set to "infer", will only perform the inference. +- `--api-nproc (int, default to 4)`: The number of threads for OpenAI API calling. +- `--work-dir (str, default to '.')`: The directory to save evaluation results. + +**Command for Evaluating Image Benchmarks ** + +You can run the script with `python` or `torchrun`: + +```bash +# When running with `python`, only one VLM instance is instantiated, and it might use multiple GPUs (depending on its default behavior). +# That is recommended for evaluating very large VLMs (like IDEFICS-80B-Instruct). + +# IDEFICS-80B-Instruct on MMBench_DEV_EN, MME, and SEEDBench_IMG, Inference and Evalution +python run.py --data MMBench_DEV_EN MME SEEDBench_IMG --model idefics_80b_instruct --verbose +# IDEFICS-80B-Instruct on MMBench_DEV_EN, MME, and SEEDBench_IMG, Inference only +python run.py --data MMBench_DEV_EN MME SEEDBench_IMG --model idefics_80b_instruct --verbose --mode infer + +# When running with `torchrun`, one VLM instance is instantiated on each GPU. It can speed up the inference. +# However, that is only suitable for VLMs that consume small amounts of GPU memory. + +# IDEFICS-9B-Instruct, Qwen-VL-Chat, mPLUG-Owl2 on MMBench_DEV_EN, MME, and SEEDBench_IMG. On a node with 8 GPU. Inference and Evaluation. +torchrun --nproc-per-node=8 run.py --data MMBench_DEV_EN MME SEEDBench_IMG --model idefics_80b_instruct qwen_chat mPLUG-Owl2 --verbose +# Qwen-VL-Chat on MME. On a node with 2 GPU. Inference and Evaluation. +torchrun --nproc-per-node=2 run.py --data MME --model qwen_chat --verbose +``` + +**Command for Evaluating Video Benchmarks** + +```bash +# When running with `python`, only one VLM instance is instantiated, and it might use multiple GPUs (depending on its default behavior). +# That is recommended for evaluating very large VLMs (like IDEFICS-80B-Instruct). + +# IDEFICS2-8B on MMBench-Video, with 8 frames as inputs and vanilla evaluation. On a node with 8 GPUs. MMBench_Video_8frame_nopack is a defined dataset setting in `vlmeval/dataset/video_dataset_config.py`. +torchrun --nproc-per-node=8 run.py --data MMBench_Video_8frame_nopack --model idefics2_8 +# GPT-4o (API model) on MMBench-Video, with 1 frame per second as inputs and pack evaluation (all questions of a video in a single query). +python run.py --data MMBench_Video_1fps_pack --model GPT4o +``` + +The evaluation results will be printed as logs, besides. **Result Files** will also be generated in the directory `$YOUR_WORKING_DIRECTORY/{model_name}`. Files ending with `.csv` contain the evaluated metrics. + +### Frequently Asked Questions + +#### Constructing Input Prompt: The `build_prompt()` Function +If you find that the model's output does not match the expected results when evaluating a specific benchmark, it could be due to the model not constructing the input prompt correctly. + +In VLMEvalKit, each `dataset` class includes a function named `build_prompt()`, which is responsible for formatting input questions. Different benchmarks can either customize their own `build_prompt()` function or use the default implementation. + +For instance, when handling the default [Multiple-Choice QA](https://github.com/open-compass/VLMEvalKit/blob/43af13e052de6805a8b08cd04aed5e0d74f82ff5/vlmeval/dataset/image_mcq.py#L164), the `ImageMCQDataset.build_prompt()` method combines elements such as `hint`, `question`, and `options` (if present in the dataset) into a complete question format, as shown below: + +``` +HINT +QUESTION +Options: +A. Option A +B. Option B +··· +Please select the correct answer from the options above. +``` + +Additionally, since different models may have varying evaluation requirements, VLMEvalKit also supports customizing the prompt construction method at the model level through `model.build_prompt()`. For an example, you can refer to [InternVL](https://github.com/open-compass/VLMEvalKit/blob/43af13e052de6805a8b08cd04aed5e0d74f82ff5/vlmeval/vlm/internvl_chat.py#L324). + +**Note: If both `model.build_prompt()` and `dataset.build_prompt()` are defined, `model.build_prompt()` will take precedence over `dataset.build_prompt()`, effectively overriding it.** + +Some models, such as Qwen2VL and InternVL, define extensive prompt-building methods for various types of benchmarks. To provide more flexibility in adapting to different benchmarks, VLMEvalKit allows users to customize the `model.use_custom_prompt()` function within the model. By adding or modifying the `use_custom_prompt()` function, you can decide which benchmarks should utilize the model's custom prompt logic. Below is an example: + +```python +def use_custom_prompt(self, dataset: str) -> bool: + from vlmeval.dataset import DATASET_TYPE, DATASET_MODALITY + dataset_type = DATASET_TYPE(dataset, default=None) + if not self._use_custom_prompt: + return False + if listinstr(['MMVet'], dataset): + return True + if dataset_type == 'MCQ': + return True + if DATASET_MODALITY(dataset) == 'VIDEO': + return False + return False +``` +Only when the `use_custom_prompt()` function returns `True` will VLMEvalKit call the model's `build_prompt()` function for the current benchmark. +With this approach, you can flexibly control which benchmarks use the model's custom prompt logic based on your specific needs, thereby better adapting to different models and tasks. + +#### Model Splitting + +Currently, VLMEvalKit automatically supports GPU resource allocation and model splitting between processes on the same machine. This feature is supported when the inference backend is `lmdeploy` or `transformers`, with the following behaviors: + +- When launching with `python` command, the model is by default allocated to all available GPUs. If you want to specify which GPUs to use, you can use `CUDA_VISIBLE_DEVICES` environment variable. +- When starting with `torchrun` command, each model instance will be allocated to `N_GPU // N_PROC` GPUs, where `N_PROC` is the number of processes specified by the `--nproc-per-node` parameter in the torchrun command. The value of `N_GPU` is determined as follows: + - If `CUDA_VISIBLE_DEVICES` environment variable is not set, `N_GPU` will be the total number of available GPUs. + - If `CUDA_VISIBLE_DEVICES` environment variable is set, `N_GPU` will be the number of GPUs specified by the `CUDA_VISIBLE_DEVICES` environment variable, and only the specified GPUs will be utilized. +Below are specific examples of running evaluation tasks on a machine equipped with 8 GPUs: + +```bash + +torchrun --nproc-per-node=2 run.py --data MMBench_DEV_EN --model InternVL3-78B + +python run.py --data MMBench_DEV_EN --model InternVL3-78B + +CUDA_VISIBLE_DEVICES=1,2,3,4,5,6 torchrun --nproc-per-node=3 run.py --data MMBench_DEV_EN --model InternVL3-38B +``` + +PS: The feature is not compatible with `vllm` backend. When you evaluate a model with `vllm` backend, please use `python` to launch, and all visible GPU devices will be used. + +#### Performance Discrepancies + +Model performance may vary across different environments. As a result, you might observe discrepancies between your evaluation results and those listed on the official VLMEvalKit leaderboard. These differences could be attributed to variations in versions of libraries such as `transformers`, `cuda`, and `torch`. + +Besides, if you encounter unexpected performance, we recommend first reviewing the local generation records (`{model}_{dataset}.xlsx`) or the evaluation records (`{model}_{dataset}_{judge_model}.xlsx`). This may help you better understand the evaluation outcomes and identify potential issues. + +## Deploy a local language model as the judge / choice extractor +The default setting mentioned above uses OpenAI's GPT as the judge LLM. However, you can also deploy a local judge LLM with [LMDeploy](https://github.com/InternLM/lmdeploy). + +First install: +``` +pip install lmdeploy openai +``` + +And then deploy a local judge LLM with the single line of code. LMDeploy will automatically download the model from Huggingface. Assuming we use internlm2-chat-1_8b as the judge, port 23333, and the key sk-123456 (the key must start with "sk-" and follow with any number you like): +``` +lmdeploy serve api_server internlm/internlm2-chat-1_8b --server-port 23333 +``` + +You need to get the model name registered by LMDeploy with the following python code: +``` +from openai import OpenAI +client = OpenAI( + api_key='sk-123456', + base_url="http://0.0.0.0:23333/v1" +) +model_name = client.models.list().data[0].id +``` + +Now set some environment variables to tell VLMEvalKit how to use the local judge LLM. As mentioned above, you can also set them in `$VLMEvalKit/.env` file: +``` +OPENAI_API_KEY=sk-123456 +OPENAI_API_BASE=http://0.0.0.0:23333/v1/chat/completions +LOCAL_LLM= +``` + +Finally, you can run the commands in step 2 to evaluate your VLM with the local judge LLM. + +Note that + +- If you hope to deploy the judge LLM in a single GPU and evaluate your VLM on other GPUs because of limited GPU memory, try `CUDA_VISIBLE_DEVICES=x` like +``` +CUDA_VISIBLE_DEVICES=0 lmdeploy serve api_server internlm/internlm2-chat-1_8b --server-port 23333 +CUDA_VISIBLE_DEVICES=1,2,3 torchrun --nproc-per-node=3 run.py --data HallusionBench --model qwen_chat --verbose +``` +- If the local judge LLM is not good enough in following the instructions, the evaluation may fail. Please report such failures (e.g., by issues). +- It's possible to deploy the judge LLM in different ways, e.g., use a private LLM (not from HuggingFace) or use a quantized LLM. Please refer to the [LMDeploy doc](https://lmdeploy.readthedocs.io/en/latest/serving/api_server.html). You can use any other deployment framework if they support OpenAI API. + + +### Using LMDeploy to Accelerate Evaluation and Inference + +You can refer this [doc](/docs/en/EvalByLMDeploy.md) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/_static/css/readthedocs.css b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/_static/css/readthedocs.css new file mode 100644 index 00000000..c83beffd --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/_static/css/readthedocs.css @@ -0,0 +1,63 @@ +.header-logo { + background-image: url("../image/logo.svg"); + background-size: 275px 80px; + height: 80px; + width: 275px; +} + + +@media screen and (min-width: 1100px) { + .header-logo { + top: -25px; + } +} + +pre { + white-space: pre; +} + +@media screen and (min-width: 2000px) { + .pytorch-content-left { + width: 1200px; + margin-left: 30px; + } + article.pytorch-article { + max-width: 1200px; + } + .pytorch-breadcrumbs-wrapper { + width: 1200px; + } + .pytorch-right-menu.scrolling-fixed { + position: fixed; + top: 45px; + left: 1580px; + } +} + + +article.pytorch-article section code { + padding: .2em .4em; + background-color: #f3f4f7; + border-radius: 5px; +} + +/* Disable the change in tables */ +article.pytorch-article section table code { + padding: unset; + background-color: unset; + border-radius: unset; +} + +table.autosummary td { + width: 50% +} + +img.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +article.pytorch-article p.rubric { + font-weight: bold; +} diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/_static/image/logo.svg b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/_static/image/logo.svg new file mode 100644 index 00000000..04353057 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/_static/image/logo.svg @@ -0,0 +1,24 @@ + + + +Created with Fabric.js 5.3.0 + + + + + + + + + + + + + VLMEvalKit + diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/_static/image/logo_icon.svg b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/_static/image/logo_icon.svg new file mode 100644 index 00000000..c46dd3b5 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/_static/image/logo_icon.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/_static/js/custom.js b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/_static/js/custom.js new file mode 100644 index 00000000..84da69d4 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/_static/js/custom.js @@ -0,0 +1,10 @@ +var collapsedSections = []; + +$(document).ready(function () { + $('.model-summary').DataTable({ + "stateSave": false, + "lengthChange": false, + "pageLength": 20, + "order": [] + }); +}); diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/_templates/404.html b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/_templates/404.html new file mode 100644 index 00000000..64910175 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/_templates/404.html @@ -0,0 +1,18 @@ +{% extends "layout.html" %} + +{% block body %} + +

Page Not Found

+

+ The page you are looking for cannot be found. +

+

+ If you just switched documentation versions, it is likely that the page you were on is moved. You can look for it in + the content table left, or go to the homepage. +

+ + +{% endblock %} diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/_templates/autosummary/class.rst b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/_templates/autosummary/class.rst new file mode 100644 index 00000000..4c3a7a9a --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/_templates/autosummary/class.rst @@ -0,0 +1,13 @@ +.. role:: hidden + :class: hidden-section +.. currentmodule:: {{ module }} + + +{{ name | underline}} + +.. autoclass:: {{ name }} + :members: + +.. + autogenerated from _templates/autosummary/class.rst + note it does not have :inherited-members: diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/_templates/callable.rst b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/_templates/callable.rst new file mode 100644 index 00000000..3a7b9d2b --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/_templates/callable.rst @@ -0,0 +1,14 @@ +.. role:: hidden + :class: hidden-section +.. currentmodule:: {{ module }} + + +{{ name | underline}} + +.. autoclass:: {{ name }} + :members: + :special-members: __call__ + +.. + autogenerated from _templates/callable.rst + note it does not have :inherited-members: diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/conf.py b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/conf.py new file mode 100644 index 00000000..e1d103e1 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/conf.py @@ -0,0 +1,234 @@ +# flake8: noqa +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. + +import ast +import os +import subprocess +import sys + +import pytorch_sphinx_theme +from sphinx.builders.html import StandaloneHTMLBuilder + +sys.path.insert(0, os.path.abspath('../../')) + +# -- Project information ----------------------------------------------------- + +project = 'VLMEvalKit' +copyright = '2023, VLMEvalKit' +author = 'VLMEvalKit Authors' + +# The full version, including alpha/beta/rc tags +version_file = '../../vlmeval/__init__.py' + + +def get_version(): + with open(version_file, 'r') as f: + file_content = f.read() + # Parse the file content into an abstract syntax tree (AST) + tree = ast.parse(file_content, filename=version_file) + + # Iterate through the body of the AST, looking for an assignment to __version__ + for node in tree.body: + if isinstance(node, ast.Assign): + for target in node.targets: + if isinstance(target, ast.Name) and target.id == '__version__': + return node.value.s + raise ValueError('__version__ not found') + + +release = get_version() + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.autosummary', + 'sphinx.ext.intersphinx', + 'sphinx.ext.napoleon', + 'sphinx.ext.viewcode', + 'myst_parser', + 'sphinx_copybutton', + 'sphinx_tabs.tabs', + 'notfound.extension', + 'sphinxcontrib.jquery', + 'sphinx_design', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +source_suffix = { + '.rst': 'restructuredtext', + '.md': 'markdown', +} + +language = 'en' + +# The master toctree document. +root_doc = 'index' +html_context = { + 'github_version': 'latest', +} +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'pytorch_sphinx_theme' +html_theme_path = [pytorch_sphinx_theme.get_html_theme_path()] + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# yapf: disable +html_theme_options = { + 'menu': [ + { + 'name': 'GitHub', + 'url': 'https://github.com/open-compass/VLMEvalKit' + }, + ], + # Specify the language of shared menu + 'menu_lang': 'en', + # Disable the default edit on GitHub + 'default_edit_on_github': False, +} +# yapf: enable + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] +html_css_files = [ + 'https://cdn.datatables.net/v/bs4/dt-1.12.1/datatables.min.css', + 'css/readthedocs.css' +] +html_js_files = [ + 'https://cdn.datatables.net/v/bs4/dt-1.12.1/datatables.min.js', + 'js/custom.js' +] + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = 'vlmevalkitdoc' + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (root_doc, 'vlmevalkit.tex', 'VLMEvalKit Documentation', author, + 'manual'), +] + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [(root_doc, 'vlmevalkit', 'VLMEvalKit Documentation', [author], + 1)] + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (root_doc, 'vlmevalkit', 'VLMEvalKit Documentation', author, + 'VLMEvalKit Authors', 'AGI evaluation toolbox and benchmark.', + 'Miscellaneous'), +] + +# -- Options for Epub output ------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +# +# epub_identifier = '' + +# A unique identification for the text. +# +# epub_uid = '' + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ['search.html'] + +# set priority when building html +StandaloneHTMLBuilder.supported_image_types = [ + 'image/svg+xml', 'image/gif', 'image/png', 'image/jpeg' +] + +# -- Extension configuration ------------------------------------------------- +# Ignore >>> when copying code +copybutton_prompt_text = r'>>> |\.\.\. ' +copybutton_prompt_is_regexp = True + +# Auto-generated header anchors +myst_heading_anchors = 3 +# Enable "colon_fence" extension of myst. +myst_enable_extensions = ['colon_fence', 'dollarmath'] + +# Configuration for intersphinx +intersphinx_mapping = { + 'python': ('https://docs.python.org/3', None), + 'numpy': ('https://numpy.org/doc/stable', None), + 'torch': ('https://pytorch.org/docs/stable/', None), + 'mmengine': ('https://mmengine.readthedocs.io/en/latest/', None), + 'transformers': + ('https://huggingface.co/docs/transformers/main/en/', None), +} +napoleon_custom_sections = [ + # Custom sections for data elements. + ('Meta fields', 'params_style'), + ('Data fields', 'params_style'), +] + +# Disable docstring inheritance +autodoc_inherit_docstrings = False +# Mock some imports during generate API docs. +autodoc_mock_imports = ['rich', 'attr', 'einops'] +# Disable displaying type annotations, these can be very verbose +autodoc_typehints = 'none' + +# The not found page +notfound_template = '404.html' diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/docutils.conf b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/docutils.conf new file mode 100644 index 00000000..0c00c846 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/docutils.conf @@ -0,0 +1,2 @@ +[html writers] +table_style: colwidths-auto diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/index.rst b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/index.rst new file mode 100644 index 00000000..425c7de4 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/en/index.rst @@ -0,0 +1,41 @@ +Welcome to the VLMEvalKit Tutorial! +========================================== + +VLMEvalKit Getting Started Guide +------------------------------- + +To help users get started quickly, we recommend the following process: + +- For users who want to use VLMEvalKit, we recommend reading the "Start Your First Step" section to set up the environment and start a mini-experiment to familiarize yourself with the process. + +- If you want to customize more modules, such as adding datasets and models, we provide an "Advanced Tutorial." + +We always welcome users' PRs (Pull Requests) and Issues to improve VLMEvalKit! + +.. _Start Your First Step: +.. toctree:: + :maxdepth: 1 + :caption: Start Your First Step + + Quickstart.md + +.. _Advanced Tutorial: +.. toctree:: + :maxdepth: 1 + :caption: Advanced Tutorial + + Development.md + ConfigSystem.md + +.. _Other Notes: +.. toctree:: + :maxdepth: 1 + :caption: Other Notes + + Contributors.md + +Index and Tables +================== + +* :ref:`genindex` +* :ref:`search` diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/ja/README_ja.md b/evaluation/cosmos3/reasoner/vlmevalkit/docs/ja/README_ja.md new file mode 100644 index 00000000..5bf9564b --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/ja/README_ja.md @@ -0,0 +1,117 @@ +
+ +![LOGO](http://opencompass.openxlab.space/utils/MMLB.jpg) + +VLMEvalKit: 大規模視覚言語モデルの評価ツールキット + +[![][github-contributors-shield]][github-contributors-link] • [![][github-forks-shield]][github-forks-link] • [![][github-stars-shield]][github-stars-link] • [![][github-issues-shield]][github-issues-link] • [![][github-license-shield]][github-license-link] + +[English](/README.md) | [简体中文](/docs/zh-CN/README_zh-CN.md) | 日本語 + +🏆 OpenCompass Learderboard • +📊Datasets & Models • +🏗️Quickstart • +🛠️Development • +🎯Goal • +🖊️Citation + +🤗 HF Leaderboard • +🤗 Evaluation Records • +🔊 Discord Channel • +📝 Technical Report +
+ +**VLMEvalKit**(pythonパッケージ名は**vlmeval**)は、**大規模視覚言語モデル(LVLMs)**の**オープンソース評価ツールキット**です。このツールキットは、複数のリポジトリでのデータ準備という重労働なしに、さまざまなベンチマークでLVLMsの**ワンコマンド評価**を可能にします。VLMEvalKitでは、すべてのLVLMsに対して**生成ベースの評価**を採用し、**正確なマッチング**と**LLMベースの回答抽出**の両方で得られた評価結果を提供します。 + +PS: 日本語の README には最新のアップデートがすべて含まれていない場合があります。英語版をご確認ください。 + +## 📊 データセット、モデル、および評価結果 + +**公式のマルチモーダルリーダーボードでのパフォーマンス数値は、ここからダウンロードできます!** + +[**OpenVLM Leaderboard**](https://huggingface.co/spaces/opencompass/open_vlm_leaderboard): [すべての詳細な結果をダウンロード](http://opencompass.openxlab.space/assets/OpenVLM.json)。 + +**Supported Benchmarks** in [**VLMEvalKit Features**](https://aicarrier.feishu.cn/wiki/Qp7wwSzQ9iK1Y6kNUJVcr6zTnPe?table=tblsdEpLieDoCxtb) を確認して、すべてのサポートされているベンチマーク(70以上)を表示してください。 + +**Supported LMMs** in [**VLMEvalKit Features**](https://aicarrier.feishu.cn/wiki/Qp7wwSzQ9iK1Y6kNUJVcr6zTnPe?table=tblsdEpLieDoCxtb) を確認して、すべてのサポートされている LMMs(200以上)を表示してください。 + +**Transformersバージョンの推奨事項:** + +特定のtransformerバージョンで一部のVLMが実行できない可能性があることに注意してください。各VLMを評価するために、以下の設定を推奨します: + +- **`transformers==4.33.0`を使用してください**: `Qwenシリーズ`, `Monkeyシリーズ`, `InternLM-XComposerシリーズ`, `mPLUG-Owl2`, `OpenFlamingo v2`, `IDEFICSシリーズ`, `VisualGLM`, `MMAlaya`, `ShareCaptioner`, `MiniGPT-4シリーズ`, `InstructBLIPシリーズ`, `PandaGPT`, `VXVERSE`, `GLM-4v-9B`. +- **`transformers==4.37.0`を使用してください**: `LLaVAシリーズ`, `ShareGPT4Vシリーズ`, `TransCore-M`, `LLaVA (XTuner)`, `CogVLMシリーズ`, `EMU2シリーズ`, `Yi-VLシリーズ`, `MiniCPM-[V1/V2]`, `OmniLMM-12B`, `DeepSeek-VLシリーズ`, `InternVLシリーズ`, `Cambrianシリーズ`, `VILA-VLシリーズ`. +- **`transformers==4.40.0`を使用してください**: `IDEFICS2`, `Bunny-Llama3`, `MiniCPM-Llama3-V2.5`, `360VL-70B`, `Phi-3-Vision`, `WeMM`. +- **`transformers==4.42.0`を使用してください**: `AKI`. +- **`transformers==latest`を使用してください**: `LLaVA-Nextシリーズ`, `PaliGemma-3B`, `Chameleon-VLシリーズ`, `Video-LLaVA-7B-HF`, `Ovis1.5シリーズ`, `Mantisシリーズ`, `MiniCPM-V2.6`. + +```python +# デモ +from vlmeval.config import supported_VLM +model = supported_VLM['idefics_9b_instruct']() +# 単一画像のフォワード +ret = model.generate(['assets/apple.jpg', 'この画像には何がありますか?']) +print(ret) # この画像には葉がついた赤いリンゴがあります。 +# 複数画像のフォワード +ret = model.generate(['assets/apple.jpg', 'assets/apple.jpg', '提供された画像にはリンゴが何個ありますか?']) +print(ret) # 提供された画像にはリンゴが2個あります。 +``` + +## 🏗️ クイックスタート + +クイックスタートガイドについては、[クイックスタート](/docs/en/Quickstart.md)を参照してください。 + +## 🛠️ 開発ガイド + +カスタムベンチマーク、VLMsを開発するか、単に**VLMEvalKit**に他のコードを貢献する場合は、[開発ガイド](/docs/en/Development.md)を参照してください。 + +コミュニティからの共有を奨励し、それに応じたクレジットを共有するために、次回のレポート更新では以下のことを実施します: + +- 全ての貢献に対して感謝の意を示します +- 新しいモデル、評価セット、または主要な機能への3つ以上の主要な貢献を持つ貢献者は、テクニカルレポートの著者リストに加わることができます。適格な貢献者は、issueを作成するか、または[VLM評価キット ディスコードチャンネル](https://discord.com/invite/evDT4GZmxN)で kennyutc にDMを送ることができます。私たちはそれに応じてフォローアップします。 + +## 🎯 VLMEvalKitの目標 + +**このコードベースは以下を目的として設計されています:** + +1. 研究者や開発者が既存のLVLMsを評価し、評価結果を**簡単に再現できるようにする**ための**使いやすい**、**オープンソースの評価ツールキット**を提供します。 +2. VLMの開発者が自分のモデルを簡単に評価できるようにします。複数のサポートされているベンチマークでVLMを評価するには、単一の`generate_inner()`関数を**実装するだけで**、他のすべてのワークロード(データのダウンロード、データの前処理、予測の推論、メトリックの計算)はコードベースによって処理されます。 + +**このコードベースは以下を目的として設計されていません:** + +1. すべての**第三者ベンチマーク**の元の論文で報告された正確な精度数値を再現すること。その理由は2つあります: + 1. VLMEvalKitは、すべてのVLMに対して**生成ベースの評価**を使用します(オプションで**LLMベースの回答抽出**を使用)。一方、一部のベンチマークは異なるアプローチを使用する場合があります(SEEDBenchはPPLベースの評価を使用します)。これらのベンチマークについては、対応する結果で両方のスコアを比較します。開発者には、コードベースで他の評価パラダイムをサポートすることをお勧めします。 + 2. デフォルトでは、すべてのVLMに対して同じプロンプトテンプレートを使用してベンチマークを評価します。一方、**一部のVLMには特定のプロンプトテンプレートがある**場合があります(現時点ではコードベースでカバーされていない場合があります)。VLMの開発者には、現在カバーされていない場合でも、VLMEvalKitで独自のプロンプトテンプレートを実装することをお勧めします。これにより、再現性が向上します。 + +## 🖊️ 引用 + +この作業が役立つ場合は、このリポジトリに**スター🌟**を付けてください。サポートありがとうございます! + +[![Stargazers repo roster for @open-compass/VLMEvalKit](https://reporoster.com/stars/open-compass/VLMEvalKit)](https://github.com/open-compass/VLMEvalKit/stargazers) + +研究でVLMEvalKitを使用する場合、または公開されたオープンソースの評価結果を参照する場合は、以下のBibTeXエントリと、使用した特定のVLM/ベンチマークに対応するBibTexエントリを使用してください。 + +```bib +@misc{duan2024vlmevalkit, + title={VLMEvalKit: An Open-Source Toolkit for Evaluating Large Multi-Modality Models}, + author={Haodong Duan and Junming Yang and Yuxuan Qiao and Xinyu Fang and Lin Chen and Yuan Liu and Xiaoyi Dong and Yuhang Zang and Pan Zhang and Jiaqi Wang and Dahua Lin and Kai Chen}, + year={2024}, + eprint={2407.11691}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2407.11691}, +} +``` + +

🔝Top に戻る

+ +[github-contributors-link]: https://github.com/open-compass/VLMEvalKit/graphs/contributors +[github-contributors-shield]: https://img.shields.io/github/contributors/open-compass/VLMEvalKit?color=c4f042&labelColor=black&style=flat-square +[github-forks-link]: https://github.com/open-compass/VLMEvalKit/network/members +[github-forks-shield]: https://img.shields.io/github/forks/open-compass/VLMEvalKit?color=8ae8ff&labelColor=black&style=flat-square +[github-issues-link]: https://github.com/open-compass/VLMEvalKit/issues +[github-issues-shield]: https://img.shields.io/github/issues/open-compass/VLMEvalKit?color=ff80eb&labelColor=black&style=flat-square +[github-license-link]: https://github.com/open-compass/VLMEvalKit/blob/main/LICENSE +[github-license-shield]: https://img.shields.io/github/license/open-compass/VLMEvalKit?color=white&labelColor=black&style=flat-square +[github-stars-link]: https://github.com/open-compass/VLMEvalKit/stargazers +[github-stars-shield]: https://img.shields.io/github/stars/open-compass/VLMEvalKit?color=ffcb47&labelColor=black&style=flat-square diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/.readthedocs.yaml b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/.readthedocs.yaml new file mode 100644 index 00000000..b7e46fe3 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/.readthedocs.yaml @@ -0,0 +1,17 @@ +version: 2 + +# Set the version of Python and other tools you might need +build: + os: ubuntu-22.04 + tools: + python: "3.8" + +formats: + - epub + +sphinx: + configuration: docs/zh-CN/conf.py + +python: + install: + - requirements: requirements/docs.txt diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/ConfigSystem.md b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/ConfigSystem.md new file mode 100644 index 00000000..14e8d495 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/ConfigSystem.md @@ -0,0 +1,69 @@ + +# 配置系统 + +默认情况下,VLMEvalKit通过在`run.py`脚本中使用`--model`和`--data`参数设置模型名称(在`/vlmeval/config.py`中定义)和数据集名称(在`vlmeval/dataset/__init__.py` 或 `vlmeval/dataset/video_dataset_config.py` 中定义)来启动评估。这种方法在大多数情况下简单且高效,但当用户希望使用不同设置评估多个模型/数据集时,可能不够灵活。 + +为了解决这个问题,VLMEvalKit提供了一个更灵活的配置系统。用户可以在json文件中指定模型和数据集设置,并通过`--config`参数将配置文件的路径传递给`run.py`脚本。以下是一个示例配置json: + +```json +{ + "model": { + "GPT4o_20240806_T00_HIGH": { + "class": "GPT4V", + "model": "gpt-4o-2024-08-06", + "temperature": 0, + "img_detail": "high" + }, + "GPT4o_20240806_T10_Low": { + "class": "GPT4V", + "model": "gpt-4o-2024-08-06", + "temperature": 1.0, + "img_detail": "low" + }, + "GPT4o_20241120": {} + }, + "data": { + "MME-RealWorld-Lite": { + "class": "MMERealWorld", + "dataset": "MME-RealWorld-Lite" + }, + "MMBench_DEV_EN_V11": { + "class": "ImageMCQDataset", + "dataset": "MMBench_DEV_EN_V11" + }, + "MMBench_Video_8frame_nopack":{}, + "Video-MME_16frame_subs": { + "class": "VideoMME", + "dataset": "Video-MME", + "nframe": 16, + "use_subtitle": true + } + } +} +``` + +配置json的解释: + +1. 现在我们支持两个字段:`model`和`data`,每个字段都是一个字典。字典的键是模型/数据集的名称(由用户设置),值是模型/数据集的设置。 +2. 对于`model`中的项目,值是一个包含以下键的字典: + - `class`:模型的类名,应该是`vlmeval/vlm/__init__.py`(开源模型)或`vlmeval/api/__init__.py`(API模型)中定义的类名。 + - 其他kwargs:其他kwargs是模型特定的参数,请参考模型类的定义以获取详细用法。例如,`model`、`temperature`、`img_detail`是`GPT4V`类的参数。值得注意的是,大多数模型类都需要`model`参数。 + - Tip:在位于`vlmeval/config.py`的变量`supported_VLM`中的已经被定义的模型可以作为`model`的键,而不需要填对应的值即可启动。例如,`GPT4o_20240806_T00_HIGH: {}`是等价于`GPT4o_20240806_T00_HIGH: {'class': 'GPT4V', 'model': 'gpt-4o-2024-08-06', 'temperature': 0, 'img_size': -1, 'img_detail': 'high', 'retry': 10, 'verbose': False}`。 +3. 对于字典`data`,我们建议用户使用官方数据集名称作为键(或键的一部分),因为我们经常根据数据集名称确定后处理/判断设置。对于`data`中的项目,值是一个包含以下键的字典: + - `class`:数据集的类名,应该是`vlmeval/dataset/__init__.py`中定义的类名。 + - 其他kwargs:其他kwargs是数据集特定的参数,请参考数据集类的定义以获取详细用法。通常,大多数数据集类都需要`dataset`参数。大多数视频数据集类都需要 `nframe` 或 `fps` 参数。 + - Tip:在位于`vlmeval/dataset/video_dataset_config.py`的变量`supported_video_dataset`中的已经被定义的数据集可以作为`data`的键,而不需要填对应的值即可启动。例如,`MMBench_Video_8frame_nopack: {}`是等价于`MMBench_Video_8frame_nopack: {'class': 'MMBenchVideo', 'dataset': 'MMBench-Video', 'nframe': 8, 'pack': False}`。 + +将示例配置json保存为`config.json`,您可以通过以下命令启动评估: + +```bash +python run.py --config config.json +``` + +这将在工作目录`$WORK_DIR`下生成以下输出文件(格式为`{$WORK_DIR}/{$MODEL_NAME}/{$MODEL_NAME}_{$DATASET_NAME}_*`): + +- `$WORK_DIR/GPT4o_20240806_T00_HIGH/GPT4o_20240806_T00_HIGH_MME-RealWorld-Lite*` +- `$WORK_DIR/GPT4o_20240806_T10_Low/GPT4o_20240806_T10_Low_MME-RealWorld-Lite*` +- `$WORK_DIR/GPT4o_20240806_T00_HIGH/GPT4o_20240806_T00_HIGH_MMBench_DEV_EN_V11*` +- `$WORK_DIR/GPT4o_20240806_T10_Low/GPT4o_20240806_T10_Low_MMBench_DEV_EN_V11*` +...... diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/Development.md b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/Development.md new file mode 100644 index 00000000..69db0649 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/Development.md @@ -0,0 +1,139 @@ +# 🛠️ 如何在 VLMEvalKit 中实现一个新的 Benchmark 或多模态模型(VLM) + +## 实现一个新的 benchmark + +示例 PR: **添加 Math-Vision Benchmark** ([#292](https://github.com/open-compass/VLMEvalKit/pull/292/files)) + +目前在 VLMEvalKit 中,benchmark 以数据集类的形式呈现,当你新增一个 benchmark 时,你可以选择复用现有的数据集类 (如单选题 benchmark 可复用 `ImageMCQDataset`),或是实现新的数据集类。你的数据集类必须支持以下两种方法 (复用父类或自行实现): + +- `build_prompt(self, line)`: 方法输入 `line` 类型为 int (对应数据 index) 或 `pd.Series` (对应数据原始 record)。方法输出一条 `multi-modal message` 作为多模态模型输入,`multi-modal message` 是一个图文交错的列表,如以下格式 (一图一文): `[dict(type='image', value=IMAGE_PTH), dict(type='text', value=prompt)]`。 +- `evaluate(self, eval_file, **judge_kwargs)`: 方法输入 `eval_file` 为多模态模型的预测结果 (多以 `.xlsx` 格式存在),如 benchmark evaluation 需要大语言模型 (一般为 GPT) 辅助,则 `judge_kwargs` 传入大语言模型的参数。方法输出 benchmark 的评测结果,以 `dict` 或 `pd.DataFrame` 的形式。 + +以下,我们简述新增数据集的通常步骤: + +### 1. TSV 数据文件准备 (图文评测集) + +目前,我们将每一个 benchmark 数据集设置为一个单独的 TSV 文件。在推理过程中,数据文件将从数据集定义的 `DATASET_URL` 链接地址自动下载到 `$LMUData` 中(如果没有明确设置的话,默认路径是 `$HOME/LMUData`)。你可以将准备好的 TSV 文件上传到一个可下载的地址(如:huggingface),或发送给我们 ,我们将帮助上传数据集到服务器中。此外,你也可以在环境变量中自定义设置下载路径 `LMUData=/path/to/your/data`。 + +TSV 文件中的内容组成为: + +| 数据集名称 \ 字段 | index | image | image_path | question | hint | multi-choice
options | answer | category | l2-category | split | +| ---------------------- | ----- | ----- | ---------- | -------- | ---- | ----------------------- | ------ | -------- | ----------- | ----- | +| MMBench_DEV_[CN/EN] | ✅ | ✅ | | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| MMBench_TEST_[CN/EN] | ✅ | ✅ | | ✅ | ✅ | ✅ | | ✅ | ✅ | ✅ | +| CCBench | ✅ | ✅ | | ✅ | | ✅ | ✅ | ✅ | | | +| SEEDBench_IMG | ✅ | ✅ | | ✅ | | ✅ | ✅ | ✅ | | | +| MME | ✅ | ✅ | | ✅ | | | ✅ | ✅ | | | +| MMVet | ✅ | ✅ | | ✅ | | | ✅ | ✅ | | | +| MMMU_DEV_VAL | ✅ | ✅ | ✅ | ✅ | | ✅ | ✅ | ✅ | ✅ | ✅ | +| COCO_VAL | ✅ | ✅ | | | | | ✅ | | | | +| OCRVQA_[TEST/TESTCORE] | ✅ | ✅ | | ✅ | | | ✅ | | | | +| TextVQA_VAL | ✅ | ✅ | | ✅ | | | ✅ | | | | +| VCR_[EN/ZH]\_[EASY/HARD]_[ALL/500/100] | ✅ | ✅ | | ✅ | | | ✅ | | | | + +
表 1. 支持的数据集的 TSV 字段。
+ +**TSV 中必须字段的介绍:** + +- **index:** 一个整数,`tsv` 中每一行的唯一标识 +- **image:** 图片的 base64 编码,你可以使用 `vlmeval/smp/vlm.py` 中实现的API进行编码和解码: + - 编码:`encode_image_to_base64`(对于PIL Image)/ `encode_image_file_to_base64`(对于图片文件路径) + - 解码:`decode_base64_to_image`(对于PIL Image)/ `decode_base64_to_image_file`(对于图片文件路径) +- **question:** 针对图像所提取出的问题,类型为字符串 +- **answer:** 问题的答案,类型为字符串,Test 集可缺失这一字段 + +### 2. 自定义数据集的 prompt 构建 + +`ImageBaseDataset` 定义了默认的 prompt 格式。如果需要针对数据集添加 prompt,或给模型输入 `Interleave` 的数据格式,可以通过 `build_prompt(line)` 函数实现。该函数输入为,每次给定 TSV 文件中的一行,包含 index, image, question 等内容作为 line。该函数将返回一个多模态消息 `msg` 的字典列表 `[dict(type='image', value=IMAGE_PTH), dict(type='text', value=prompt)]`,包括图片路径和将被输入到 VLMs 的文本 prompt。对于 interleave 类型输入,可以直接将图片路径的字典放置到 image token 位置。 + +### 3. 自定义数据集的指标实现 + +增加对 benchmark 的评测需要自定义一个该数据集的 class 对象,从而实现数据集的指标计算。图文多模态数据集均继承自 `vlmeval/dataset/image_base.py` 中的 `ImageBaseDataset` 对象。其中 `TYPE` 定义了数据集的类型;`DATASET_URL` 为数据集的下载地址;`DATASET_MD5` 为数据集文件的 md5 一致性编码检查。 + +在 class 中**需要实现** `evaluate(eval_file, **judge_kwargs)` 类函数,对自定义的数据集结果进行指标计算和结果输出。函数输入 `eval_file` 为模型预测结果 `{model_name}_{dataset}.xlsx` 的路径。可以通过 `load(eval_file)` 文件将其读取为 panda.DataFrames 类型,其中包含 index, question, answer, category, prediction 等字段。`judge_kwargs` 参数将传递一个评测相关的字典,如:judge 模型的名称,api 请求线程数等。**函数的返回值**为评估完成的准确度等指标,其格式为由 list 组成的字典,并组织成 panda.DataFrames 类型。 + +## 实现一个新的模型 + +示例 PR: **支持 LLaVA-Next-Interleave** ([#294](https://github.com/open-compass/VLMEvalKit/pull/294)) + +**1. 支持 `generate_inner` API (必须)** + +现有所有的模型都在 `vlmeval/vlm` 中实现。对于一个最基本的模型,你的模型类**应该实现方法** `generate_inner(msgs, dataset=None)`。这个函数将向 VLM 输入一个多模态数据,并返回 VLM 的预测(一个字符串)。可选参数 `dataset` 可以用作模型在不同推理策略之间切换的标志。 + +其中多模态消息 `msgs` 是一个字典列表,每个字典有两个键:类型和值: +- `type`:我们目前支持两种类型,选项是 ["image", "text"]。 +- `value`:当类型为 `text` 时,值是文本消息(一个字符串);当类型为 `image` 时,值可以是图像文件的本地路径,或者是图像的URL。 + +> 目前,一个多模态消息可能包含任意交错的图像和文本。如果你的模型不支持这一点,我们推荐的做法是取第一张图像和连接的文本消息作为模型的输入。你可以在模型的 class 中设置 `INTERLEAVE = False` 并调用 `self.message_to_promptimg(message, dataset=dataset)` 函数来获取你的 prompt 和第一张图片的地址。 + +一些多模态消息的例子: + +```python +IMAGE_PTH = 'assets/apple.jpg' +IMAGE_URL = 'https://raw.githubusercontent.com/open-compass/VLMEvalKit/main/assets/apple.jpg' +msg1 = [ + dict(type='image', value=IMAGE_PTH), + dict(type='text', value='What is in this image?') +] +msg2 = [ + dict(type='image', value=IMAGE_URL), + dict(type='image', value=IMAGE_URL), + dict(type='text', value='How many apples are there in these images?') +] +response = model.generate(msg1) +``` + +为了方便起见,我们还支持接受字符串列表作为输入。在这种情况下,我们将检查一个字符串是图像路径还是图像 URL,并自动将其转换为 `list[dict]` 格式: + +```python +IMAGE_PTH = 'assets/apple.jpg' +IMAGE_URL = 'https://raw.githubusercontent.com/open-compass/VLMEvalKit/main/assets/apple.jpg' +msg1 = [IMAGE_PTH, 'What is in this image?'] +msg2 = [IMAGE_URL, IMAGE_URL, 'How many apples are there in these images?'] +response = model.generate(msg1) +``` + +**2. 支持自定义提示词构建 (可选)** + +此外,你的模型可以通过实现两个可选方法来支持自定义提示构建:`use_custom_prompt(dataset)` 和 `build_prompt(line, dataset=None)`。 + +- `use_custom_prompt(dataset)` 将返回一个布尔值,指示模型是否应使用自定义提示构建策略。 +- 如果`use_custom_prompt(dataset)`返回 True,`build_prompt(line, dataset)` 应该为相应的数据集返回一个自定义构建的多模态消息,line 数据是一个包含数据样本所需信息的字典。如果`use_custom_prompt(dataset)` 返回False,则将使用默认的 prompt 构建策略。 + +**3. 支持多轮对话 (可选)** + +你可以通过支持 `chat_inner(message, dataset)` API 为你的模型新增多轮对话功能并兼容多轮对话评测。这个 API 输出一个字符串型回复,`message` 包含一个聊天记录的列表,格式如下: + +```python +# Assume msg1, msg2, msg3, ... are multi-modal messages following the previously described format +# `chat_inner` take the following chat history list as input: +message = [ + dict(role='user', content=msg1), + dict(role='assistant', content=msg2), + dict(role='user', content=msg3), + dict(role='assistant', content=msg4), + ...... + dict(role='user', content=msgn), +] +# `message` should contain an odd number of chat utterances, the role of utterances should be interleaved "user" and "assistant", with the role of the last utterance to be "user". +# The chat function will call `chat_inner` +response = model.chat(message) +``` + +### 示例 PRs: + +- 不支持交错的图像和文本,且不使用自定义提示的VLM:[[模型] 支持 glm-4v-9b](https://github.com/open-compass/VLMEvalKit/pull/221) +- 支持交错的图像和文本及自定义提示的VLM:[添加 MiniCPM-Llama3-V-2.5](https://github.com/open-compass/VLMEvalKit/pull/205) +- VLM API:[特征添加 glmv](https://github.com/open-compass/VLMEvalKit/pull/201) + +## 为 VLMEvalKit 贡献代码 + +如果你想为 **VLMEvalKit** 贡献代码,请在提交PR之前进行预提交检查。这有助于保持代码整洁。 + +```bash +# 在VLMEvalKit的目录下,安装预提交 hook: +pip install pre-commit +pre-commit install +pre-commit run --all-files +# 然后提交你的代码。 +``` diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/EvalByLMDeploy.md b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/EvalByLMDeploy.md new file mode 100644 index 00000000..cdb46c70 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/EvalByLMDeploy.md @@ -0,0 +1,28 @@ +# 使用 LMDeploy 加速评测推理 + +VLMEvalKit 支持测试由 LMDeploy 部署的 VLM 模型,下面以 InternVL2-8B 为例,展示如何测试模型 + +## 第0步 安装 LMDeploy + +```bash +pip install lmdeploy +``` + +其他安装方式可以参考 LMDeploy 的[文档](https://github.com/InternLM/lmdeploy) + +## 第1步 启动推理服务 + +```bash +lmdeploy serve api_server OpenGVLab/InternVL2-8B --model-name InternVL2-8B +``` +> [!IMPORTANT] +> 因为 VLMEvalKit 中的模型对于不同数据集在构建 prompt 时可能有自定义行为,如 InternVL2 对于 HallusionBench 的处理,所以,server 端在启动的时候需要指定 `--model-name`,这样在使用 LMDEploy api 时可以根据名字选择合适的 prompt 构建策略。 +> +> 如果指定了 `--server-port`,需要设置对应的环境变量 `LMDEPLOY_API_BASE` + + +## 第2步 评测 + +```bash +python run.py --data MMStar --model InternVL2-8B --verbose --api-nproc 64 +``` diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/Makefile b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/Makefile new file mode 100644 index 00000000..d4bb2cbb --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/Makefile @@ -0,0 +1,20 @@ +# Minimal makefile for Sphinx documentation +# + +# You can set these variables from the command line, and also +# from the environment for the first two. +SPHINXOPTS ?= +SPHINXBUILD ?= sphinx-build +SOURCEDIR = . +BUILDDIR = _build + +# Put it first so that "make" without argument is like "make help". +help: + @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) + +.PHONY: help Makefile + +# Catch-all target: route all unknown targets to Sphinx using the new +# "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). +%: Makefile + @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/Quickstart.md b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/Quickstart.md new file mode 100644 index 00000000..1245235b --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/Quickstart.md @@ -0,0 +1,231 @@ +# 快速开始 + +在运行评测脚本之前,你需要先**配置** VLMs,并正确设置模型路径。然后你可以使用脚本 `run.py` 进行多个VLMs和基准测试的推理和评估。 + +## 第0步 安装和设置必要的密钥 + +**安装** + +```bash +git clone https://github.com/open-compass/VLMEvalKit.git +cd VLMEvalKit +pip install -e . +``` + +**设置密钥** + +要使用 API 模型(如 GPT-4v, Gemini-Pro-V 等)进行推理,或使用 LLM API 作为**评判者或选择提取器**,你需要首先设置 API 密钥。如果你设置了密钥,VLMEvalKit 将使用一个评判 LLM 从输出中提取答案,否则它将使用**精确匹配模式**(在输出字符串中查找 "Yes", "No", "A", "B", "C"...)。**精确匹配模式只能应用于是或否任务和多项选择任务。** + +- 你可以将所需的密钥放在 `$VLMEvalKit/.env` 中,或直接将它们设置为环境变量。如果你选择创建 `.env` 文件,其内容将如下所示: + + ```bash + # .env 文件,将其放置在 $VLMEvalKit 下 + # 专有 VLMs 的 API 密钥 + # QwenVL APIs + DASHSCOPE_API_KEY= + # Gemini w. Google Cloud Backends + GOOGLE_API_KEY= + # OpenAI API + OPENAI_API_KEY= + OPENAI_API_BASE= + # StepAI API + STEPAI_API_KEY= + # REKA API + REKA_API_KEY= + # GLMV API + GLMV_API_KEY= + # CongRong API + CW_API_BASE= + CW_API_KEY= + # SenseNova API + SENSENOVA_API_KEY= + # Hunyuan-Vision API + HUNYUAN_SECRET_KEY= + HUNYUAN_SECRET_ID= + # LMDeploy API + LMDEPLOY_API_BASE= + # MiniMax API + MINIMAX_API_KEY= + # 你可以设置一个评估时代理,评估阶段产生的 API 调用将通过这个代理进行 + EVAL_PROXY= + ``` + +- 如果需要使用 API 在对应键值空白处填写上你的密钥。这些 API 密钥将在进行推理和评估时自动加载。 +## 第1步 配置 + +**VLM 配置**:所有 VLMs 都在 `vlmeval/config.py` 中配置。对于某些 VLMs(如 MiniGPT-4、LLaVA-v1-7B),需要额外的配置(在配置文件中配置代码 / 模型权重根目录)。在评估时,你应该使用 `vlmeval/config.py` 中 `supported_VLM` 指定的模型名称来选择 VLM。确保在开始评估之前,你可以成功使用 VLM 进行推理,使用以下命令 `vlmutil check {MODEL_NAME}`。 + +注:对于Qwen-VL系列模型(Qwen-VL, Qwen2-VL, Qwen2.5-VL),vlmeval/config.py 中所指定的像素数量上下界如下: + +``` +min_pixels=1280 * 28 * 28, +max_pixels=16384 * 28 * 28, +``` +其中,1280为Qwen官方为平衡性能、计算资源与内存的推荐最大值,而16384为模型输入的理论最大值。这种设定对于部分需要高分辨率的视觉任务(如文档理解)有着积极的作用。但考虑这一设定并没有实际的依据,如果需要与官方的设定对齐,可以去掉这两个数值,或是设置为以下来自Qwen官方demo的数值: + +``` +min_pixels=256 * 28 * 28, +max_pixels=1280 * 28 * 28, +``` + +## 第2步 评测 + +**新功能!!!** 我们集成了一个新的配置系统,以实现更灵活的评估设置。查看[文档](/docs/zh-CN/ConfigSystem.md)或运行`python run.py --help`了解更多详情 🔥🔥🔥 + +我们使用 `run.py` 进行评估。你可以使用 `$VLMEvalKit/run.py` 或创建脚本的软链接运行(以便在任何地方使用该脚本): + +**参数** + +- `--data (list[str])`: 设置在 VLMEvalKit 中支持的数据集名称(可以在代码库首页的 README 中找到支持的数据集列表) +- `--model (list[str])`: 设置在 VLMEvalKit 中支持的 VLM 名称(在 `vlmeval/config.py` 中的 `supported_VLM` 中定义) +- `--mode (str, 默认值为 'all', 可选值为 ['all', 'infer'])`:当 mode 设置为 "all" 时,将执行推理和评估;当设置为 "infer" 时,只执行推理 +- `--api-nproc (int, 默认值为 4)`: 调用 API 的线程数 +- `--work-dir (str, default to '.')`: 存放测试结果的目录 + +**用于评测图像多模态评测集的命令** + +你可以使用 `python` 或 `torchrun` 来运行脚本: + +```bash +# 使用 `python` 运行时,只实例化一个 VLM,并且它可能使用多个 GPU。 +# 这推荐用于评估参数量非常大的 VLMs(如 IDEFICS-80B-Instruct)。 + +# 在 MMBench_DEV_EN、MME 和 SEEDBench_IMG 上使用 IDEFICS-80B-Instruct 进行推理和评估 +python run.py --data MMBench_DEV_EN MME SEEDBench_IMG --model idefics_80b_instruct --verbose +# 在 MMBench_DEV_EN、MME 和 SEEDBench_IMG 上使用 IDEFICS-80B-Instruct 仅进行推理 +python run.py --data MMBench_DEV_EN MME SEEDBench_IMG --model idefics_80b_instruct --verbose --mode infer + +# 使用 `torchrun` 运行时,每个 GPU 上实例化一个 VLM 实例。这可以加快推理速度。 +# 但是,这仅适用于消耗少量 GPU 内存的 VLMs。 + +# 在 MMBench_DEV_EN、MME 和 SEEDBench_IMG 上使用 IDEFICS-9B-Instruct、Qwen-VL-Chat、mPLUG-Owl2。在具有 8 个 GPU 的节点上进行推理和评估。 +torchrun --nproc-per-node=8 run.py --data MMBench_DEV_EN MME SEEDBench_IMG --model idefics_80b_instruct qwen_chat mPLUG-Owl2 --verbose +# 在 MME 上使用 Qwen-VL-Chat。在具有 2 个 GPU 的节点上进行推理和评估。 +torchrun --nproc-per-node=2 run.py --data MME --model qwen_chat --verbose +``` + +**用于评测视频多模态评测集的命令** + +```bash +# 使用 `python` 运行时,只实例化一个 VLM,并且它可能使用多个 GPU。 +# 这推荐用于评估参数量非常大的 VLMs(如 IDEFICS-80B-Instruct)。 + +# 在 MMBench-Video 上评测 IDEFCIS2-8B, 视频采样 8 帧作为输入,不采用 pack 模式评测. MMBench_Video_8frame_nopack 是一个定义在 `vlmeval/dataset/video_dataset_config.py` 的数据集设定. +torchrun --nproc-per-node=8 run.py --data MMBench_Video_8frame_nopack --model idefics2_8 +# 在 MMBench-Video 上评测 GPT-4o (API 模型), 视频采样每秒一帧作为输入,采用 pack 模式评测 +python run.py --data MMBench_Video_1fps_pack --model GPT4o +``` + +评估结果将作为日志打印出来。此外,**结果文件**也会在目录 `$YOUR_WORKING_DIRECTORY/{model_name}` 中生成。以 `.csv` 结尾的文件包含评估的指标。 +### 常见问题 +#### 构建输入prompt:`build_prompt()`函数 +如果您在评测某个benchmark时,发现模型输出的结果与预期不符,可能是因为您使用的模型没有正确构建输入prompt。 + +在VLMEvalkit中,每个`dataset`类都包含一个名为`build_prompt()`的函数,用于构建输入问题的格式。不同的benchmark可以选择自定义`build_prompt()`函数,也可以使用默认的实现。 + +例如,在处理默认的[多选题/Multi-Choice QA]([vlmeval/dataset/image_mcq.py](https://github.com/open-compass/VLMEvalKit/blob/43af13e052de6805a8b08cd04aed5e0d74f82ff5/vlmeval/dataset/image_mcq.py#L164))时,`ImageMCQDataset.build_prompt()`类会将`hint`、`question`、`options`等元素(若数据集中包含)组合成一个完整的问题格式,如下所示: +``` +HINT +QUESTION +Options: +A. Option A +B. Option B +··· +Please select the correct answer from the options above. +``` + +此外,由于不同模型对评测的需求可能有所不同,VLMEvalkit也支持在模型层面自定义对不同benchmark构建prompt的方法,即`model.build_prompt()`,具体示例可以参考[InternVL](https://github.com/open-compass/VLMEvalKit/blob/43af13e052de6805a8b08cd04aed5e0d74f82ff5/vlmeval/vlm/internvl_chat.py#L324)。 + +**注意:当同时定义了`model.build_prompt()`以及`dataset.build_prompt()`时,`model.build_prompt()`将优先于`dataset.build_prompt()`,即前者会覆盖后者。** + +由于部分模型(如Qwen2VL,InternVL等)对于不同类型的benchmark定义了广泛的prompt构建方法,为了更灵活地适应不同的benchmark,VLMEvalkit支持在模型中自定义`model.use_custom_prompt()`函数。通过添加或者修改`use_custom_prompt()`函数,您可以决定对于哪些benchmark使用模型自定义的`use_custom_prompt()`方法,示例如下: +``` +def use_custom_prompt(self, dataset: str) -> bool: + from vlmeval.dataset import DATASET_TYPE, DATASET_MODALITY + dataset_type = DATASET_TYPE(dataset, default=None) + if not self._use_custom_prompt: + return False + if listinstr(['MMVet'], dataset): + return True + if dataset_type == 'MCQ': + return True + if DATASET_MODALITY(dataset) == 'VIDEO': + return False + return False +``` +仅当`use_custom_prompt()`函数返回`True`时,VLMEvalkit才会对当前benchmark调用模型的`build_prompt()`函数。 +通过这种方式,您可以根据具体需求灵活地控制哪些benchmark使用模型自定义的prompt构建逻辑,从而更好地适配不同模型和任务的需求。 + +#### 模型切分 + +目前 VLMEvalKit 的启动方式自动支持同机上进程间 GPU 资源的划分与模型切分。该功能在推理后端为 `lmdeploy` 或 `transformers` 时被支持,具体行为如下: + +- 基于 `python` 命令启动时,模型默认分配到所有可用的 GPU 上,如想指定使用哪些 GPU,可以使用 `CUDA_VISIBLE_DEVICES` 环境变量。 +- 基于 `torchrun` 命令启动时,每个模型实例会被分配到 `N_GPU // N_PROC` 个 GPU 上,`N_PROC` 为 torchrun 命令中的 `--nproc-per-node` 参数所指定的进程数。`N_GPU` 的取值为: + - 如 `CUDA_VISIBLE_DEVICES` 环境变量未设置,`N_GPU` 为全部可用 GPU 数量。 + - 如 `CUDA_VISIBLE_DEVICES` 环境变量被设置,`N_GPU` 为 `CUDA_VISIBLE_DEVICES` 环境变量所指定的 GPU 数量,并且,仅有指定的 GPU 会被利用。 + +下面提供了,在一台配备 8 块 GPU 的机器上运行评测任务的具体示例: +```bash +# +torchrun --nproc-per-node=2 run.py --data MMBench_DEV_EN --model InternVL3-78B +# +python run.py --data MMBench_DEV_EN --model InternVL3-78B +# +CUDA_VISIBLE_DEVICES=1,2,3,4,5,6 torchrun --nproc-per-node=3 run.py --data MMBench_DEV_EN --model InternVL3-38B +``` + +注:此方式不支持 `vllm` 后端,基于 `vllm` 后端起评测任务时,请用 `python` 命令启动,默认调用所有可见的 GPU。 + +#### 性能差距 +在不同的运行环境中,模型的性能表现可能会有所差异。因此,在评估过程中,您可能会发现自己的评测结果与VLMEvalKit官方榜单上的结果存在差距。这种差异可能与`transformers`, `cuda`, `torch`等版本的变化有关。 + +此外,对于异常的表现,我们建议您优先查看运行完成后的本地生成记录`{model}_{dataset}.xlsx`或者评估记录`{model}_{dataset}_{judge_model}.xlsx`,这可能会帮助您更好地理解评估结果并发现问题。 + + + +### 部署本地语言模型作为评判 / 选择提取器 +上述默认设置使用 OpenAI 的 GPT 作为评判 LLM。你也可以使用 [LMDeploy](https://github.com/InternLM/lmdeploy) 部署本地评判 LLM。 + +首先进行安装: +``` +pip install lmdeploy openai +``` + +然后可以通过一行代码部署本地评判 LLM。LMDeploy 将自动从 Huggingface 下载模型。假设我们使用 internlm2-chat-1_8b 作为评判,端口为 23333,密钥为 sk-123456(密钥必须以 "sk-" 开头,后跟任意数字): +``` +lmdeploy serve api_server internlm/internlm2-chat-1_8b --server-port 23333 +``` + +使用以下 Python 代码获取由 LMDeploy 注册的模型名称: +``` +from openai import OpenAI +client = OpenAI( + api_key='sk-123456', + base_url="http://0.0.0.0:23333/v1" +) +model_name = client.models.list().data[0].id +``` + +配置对应环境变量,以告诉 VLMEvalKit 如何使用本地评判 LLM。正如上面提到的,也可以在 `$VLMEvalKit/.env` 文件中设置: +``` +OPENAI_API_KEY=sk-123456 +OPENAI_API_BASE=http://0.0.0.0:23333/v1/chat/completions +LOCAL_LLM= +``` + +最后,你可以运行第2步中的命令,使用本地评判 LLM 来评估你的 VLM。 + +**请注意:** + +- 如果你希望将评判 LLM 部署在单独的一个 GPU 上,并且由于 GPU 内存有限而希望在其他 GPU 上评估你的 VLM,可以使用 `CUDA_VISIBLE_DEVICES=x` 这样的方法,例如: +``` +CUDA_VISIBLE_DEVICES=0 lmdeploy serve api_server internlm/internlm2-chat-1_8b --server-port 23333 +CUDA_VISIBLE_DEVICES=1,2,3 torchrun --nproc-per-node=3 run.py --data HallusionBench --model qwen_chat --verbose +``` +- 如果本地评判 LLM 在遵循指令方面不够好,评估过程可能会失败。请通过 issues 报告此类失败情况。 +- 可以以不同的方式部署评判 LLM,例如使用私有 LLM(而非来自 HuggingFace)或使用量化 LLM。请参考 [LMDeploy doc](https://lmdeploy.readthedocs.io/en/latest/serving/api_server.html) 文档。也可以使用其他支持 OpenAI API 框架的方法。 + +### 使用 LMDeploy 加速模型推理 + +可参考[文档](/docs/zh-CN/EvalByLMDeploy.md) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/README_zh-CN.md b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/README_zh-CN.md new file mode 100644 index 00000000..92c526fc --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/README_zh-CN.md @@ -0,0 +1,131 @@ +
+ +![LOGO](http://opencompass.openxlab.space/utils/MMLB.jpg) + +VLMEvalKit: 一种多模态大模型评测工具 + +[![][github-contributors-shield]][github-contributors-link] • [![][github-forks-shield]][github-forks-link] • [![][github-stars-shield]][github-stars-link] • [![][github-issues-shield]][github-issues-link] • [![][github-license-shield]][github-license-link] + +[English](/README.md) | 简体中文 | [日本語](/docs/ja/README_ja.md) + +🏆 OpenCompass 排行榜 • +🏗️ 快速开始 • +📊 数据集和模型 • +🛠️ 开发指南 • +🎯 我们的目标 • +🖊️ 引用 + +🤗 HuggingFace 排行榜 (存档全部性能) • +🤗 原始评测记录 • +🔊 Discord • +📝 技术报告 +
+ +**VLMEvalKit** (python 包名为 **vlmeval**) 是一款专为大型视觉语言模型 (Large Vision-Language Models, LVLMs) 评测而设计的开源工具包。该工具支持在各种基准测试上对大型视觉语言模型进行**一键评估**,无需进行繁重的数据准备工作,让评估过程更加简便。在 VLMEvalKit 中,我们对所有大型视觉语言模型生成的结果进行评测,并提供基于**精确匹配**与基于 **LLM 的答案提取**两种评测结果。 + +## 🆕 更新 + +- **[2025-04-29]** 优化 `torchrun` 启动逻辑:目前 `torchrun` 启动时,若进程数为 M,机器 GPU 卡数为 N,将会自动调整每个进程分配的 GPU 数量为 `N // M`。目前此分配方式适用于 `transformers`, `lmdeploy` 推理后端,`vllm` 推理后端仅支持使用 python 启动 🔥🔥🔥 +- **[2025-02-20]** 支持新模型:**InternVL2.5 series, QwenVL2.5 series, QVQ-72B, Doubao-VL, Janus-Pro-7B, MiniCPM-o-2.6, InternVL2-MPO, LLaVA-CoT, Hunyuan-Standard-Vision, Ovis2, Valley, SAIL-VL, Ross, Long-VITA, EMU3, SmolVLM**。支持新基准:**MMMU-Pro, WeMath, 3DSRBench, LogicVista, VL-RewardBench, CC-OCR, CG-Bench, CMMMU, WorldSense**。请参考[**VLMEvalKit Features**](https://aicarrier.feishu.cn/wiki/Qp7wwSzQ9iK1Y6kNUJVcr6zTnPe?table=tblsdEpLieDoCxtb)以获取更多信息。感谢社区的各位贡献者 🔥🔥🔥 +- **[2024-11-21]** 集成了一个新的配置系统,以实现更灵活的评估设置。查看[文档](/docs/zh-CN/ConfigSystem.md)或运行`python run.py --help`了解更多详情 🔥🔥🔥 +- **[2024-11-21]** 支持 **[QSpatial](https://andrewliao11.github.io/spatial_prompt/)**,一个用于定量空间推理的多模态基准(例如,确定大小/距离),感谢 **[andrewliao11](https://github.com/andrewliao11)** 提供官方支持 🔥🔥🔥 +- **[2024-11-21]** 支持 **[MM-Math](https://github.com/kge-sun/mm-math)**,一个包含约6K初中多模态推理数学问题的新多模态数学基准。GPT-4o-20240806在该基准上达到了22.5%的准确率 🔥🔥🔥 +- **[2024-11-16]** 支持 **[OlympiadBench](https://github.com/OpenBMB/OlympiadBench)**,一个多模态基准,包含奥林匹克级别的数学和物理问题 🔥🔥🔥 +- **[2024-11-16]** 支持 **[WildVision](https://huggingface.co/datasets/WildVision/wildvision-bench)**,一个基于多模态竞技场数据的主观多模态基准 🔥🔥🔥 +- **[2024-11-13]** 支持 **[MIA-Bench](https://arxiv.org/abs/2407.01509)**,一个多模态指令跟随基准 🔥🔥🔥 +- **[2024-11-08]** 支持 **[Aria](https://arxiv.org/abs/2410.05993)**,一个多模态原生 MoE 模型,感谢 **[teowu](https://github.com/teowu)** 🔥🔥🔥 +- **[2024-11-04]** 支持 **[WorldMedQA-V](https://www.arxiv.org/abs/2410.12722)**,该基准包含 1000 多个医学 VQA 问题,涵盖巴西、以色列、日本、西班牙等四个国家的语言,以及它们的英文翻译 🔥🔥🔥 + +## 🏗️ 快速开始 + +请参阅[**快速开始**](/docs/zh-CN/Quickstart.md)获取入门指南。 + +## 📊 评测结果,支持的数据集和模型 + +### 评测结果 + +**[OpenVLM Leaderboard](https://huggingface.co/spaces/opencompass/open_vlm_leaderboard)**: **[下载全部细粒度测试结果](http://opencompass.openxlab.space/assets/OpenVLM.json)**. + +请查看[**VLMEvalKit Features**](https://aicarrier.feishu.cn/wiki/Qp7wwSzQ9iK1Y6kNUJVcr6zTnPe?table=tblsdEpLieDoCxtb)中的 **Supported Benchmarks** 标签,以查看所有支持的图像和视频基准(70+)。 + +请查看[**VLMEvalKit Features**](https://aicarrier.feishu.cn/wiki/Qp7wwSzQ9iK1Y6kNUJVcr6zTnPe?table=tblsdEpLieDoCxtb)中的 **Supported LMMs** 标签,以查看所有支持的 LMMs,包括商业 API、开源模型等(200+)。 + +### 其他 + +**Transformers 的版本推荐:** + +**请注意**,某些 VLM 可能无法在某些特定的 transformers 版本下运行,我们建议使用以下设置来评估对应的VLM: + +- **请用** `transformers==4.33.0` **来运行**: `Qwen series`, `Monkey series`, `InternLM-XComposer Series`, `mPLUG-Owl2`, `OpenFlamingo v2`, `IDEFICS series`, `VisualGLM`, `MMAlaya`, `ShareCaptioner`, `MiniGPT-4 series`, `InstructBLIP series`, `PandaGPT`, `VXVERSE`. +- **请用** `transformers==4.37.0 ` **来运行**: `LLaVA series`, `ShareGPT4V series`, `TransCore-M`, `LLaVA (XTuner)`, `CogVLM Series`, `EMU2 Series`, `Yi-VL Series`, `MiniCPM-[V1/V2]`, `OmniLMM-12B`, `DeepSeek-VL series`, `InternVL series`, `Cambrian Series`, `VILA Series`, `Llama-3-MixSenseV1_1`, `Parrot-7B`, `PLLaVA Series`. +- **请用** `transformers==4.40.0 ` **来运行**: `IDEFICS2`, `Bunny-Llama3`, `MiniCPM-Llama3-V2.5`, `360VL-70B`, `Phi-3-Vision`, `WeMM`. +- **请用** `transformers==4.42.0 ` **来运行**: `AKI`. +- **请用** `transformers==latest` **来运行**: `LLaVA-Next series`, `PaliGemma-3B`, `Chameleon series`, `Video-LLaVA-7B-HF`, `Ovis series`, `Mantis series`, `MiniCPM-V2.6`, `OmChat-v2.0-13B-sinlge-beta`, `Idefics-3`, `GLM-4v-9B`, `VideoChat2-HD`. + +**如何测试一个 VLM 是否可以正常运行:** + +```python +from vlmeval.config import supported_VLM +model = supported_VLM['idefics_9b_instruct']() +# 前向单张图片 +ret = model.generate(['assets/apple.jpg', 'What is in this image?']) +print(ret) # 这张图片上有一个带叶子的红苹果 +# 前向多张图片 +ret = model.generate(['assets/apple.jpg', 'assets/apple.jpg', 'How many apples are there in the provided images? ']) +print(ret) # 提供的图片中有两个苹果 +``` + +## 🛠️ 开发指南 + +要开发自定义评测数据集,支持其他 VLMs,或为 VLMEvalKit 贡献代码,请参阅[**开发指南**](/docs/zh-CN/Development_zh-CN.md)。 + +为激励来自社区的共享并分享相应的 credit,在下一次 report 更新中,我们将: + +- 致谢所有的 contribution +- 具备三个或以上主要贡献 (支持新模型、评测集、或是主要特性) 的贡献者将可以加入技术报告的作者列表 。合条件的贡献者可以创建 issue 或是在 [VLMEvalKit Discord Channel](https://discord.com/invite/evDT4GZmxN) 私信 kennyutc,我们将进行跟进 + +## 🎯 VLMEvalKit 的目标 + +**该代码库的设计目标是:** + +1. 提供一个**易于使用**的**开源评估工具包**,方便研究人员和开发人员评测现有的多模态大模型,并使评测结果**易于复现**。 +2. 使 VLM 开发人员能够轻松地评测自己的模型。在多个支持的基准测试上评估 VLM,只需实现一个 `generate_inner()` 函数,所有其他工作负载(数据下载、数据预处理、预测推理、度量计算)都由代码库处理。 + +**该代码库的设计目标不是:** + +复现所有**第三方基准测试**原始论文中报告的准确数字。有两个相关的原因: +1. VLMEvalKit 对所有 VLMs 使用基于生成的评估(可选使用基于 LLM 的答案提取)。同时,一些基准测试可能官方使用不同的方法(*例如,SEEDBench 使用基于 PPL 的评估*)。对于这些基准测试,我们在相应的结果中比较两个得分。我们鼓励开发人员在代码库中支持其他评估范式。 +2. 默认情况下,我们对所有多模态模型使用相同的提示模板来评估基准测试。同时,**一些多模态模型可能有他们特定的提示模板**(目前可能未在代码库中涵盖)。我们鼓励 VLM 的开发人员在 VLMEvalKit 中实现自己的提示模板,如果目前未覆盖。这将有助于提高可复现性。 + +## 🖊️ 引用 + +如果我们的工作对您有所帮助,请考虑 **star🌟** VLMEvalKit。感谢支持! + +[![Stargazers repo roster for @open-compass/VLMEvalKit](https://reporoster.com/stars/open-compass/VLMEvalKit)](https://github.com/open-compass/VLMEvalKit/stargazers) + +如果您在研究中使用了 VLMEvalKit,或希望参考已发布的开源评估结果,请使用以下 BibTeX 条目以及与您使用的特定 VLM / 基准测试相对应的 BibTex 条目。 + +```bib +@misc{duan2024vlmevalkit, + title={VLMEvalKit: An Open-Source Toolkit for Evaluating Large Multi-Modality Models}, + author={Haodong Duan and Junming Yang and Yuxuan Qiao and Xinyu Fang and Lin Chen and Yuan Liu and Xiaoyi Dong and Yuhang Zang and Pan Zhang and Jiaqi Wang and Dahua Lin and Kai Chen}, + year={2024}, + eprint={2407.11691}, + archivePrefix={arXiv}, + primaryClass={cs.CV}, + url={https://arxiv.org/abs/2407.11691}, +} +``` + +

🔝回到顶部

+ +[github-contributors-link]: https://github.com/open-compass/VLMEvalKit/graphs/contributors +[github-contributors-shield]: https://img.shields.io/github/contributors/open-compass/VLMEvalKit?color=c4f042&labelColor=black&style=flat-square +[github-forks-link]: https://github.com/open-compass/VLMEvalKit/network/members +[github-forks-shield]: https://img.shields.io/github/forks/open-compass/VLMEvalKit?color=8ae8ff&labelColor=black&style=flat-square +[github-issues-link]: https://github.com/open-compass/VLMEvalKit/issues +[github-issues-shield]: https://img.shields.io/github/issues/open-compass/VLMEvalKit?color=ff80eb&labelColor=black&style=flat-square +[github-license-link]: https://github.com/open-compass/VLMEvalKit/blob/main/LICENSE +[github-license-shield]: https://img.shields.io/github/license/open-compass/VLMEvalKit?color=white&labelColor=black&style=flat-square +[github-stars-link]: https://github.com/open-compass/VLMEvalKit/stargazers +[github-stars-shield]: https://img.shields.io/github/stars/open-compass/VLMEvalKit?color=ffcb47&labelColor=black&style=flat-square diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/_static/css/readthedocs.css b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/_static/css/readthedocs.css new file mode 100644 index 00000000..c83beffd --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/_static/css/readthedocs.css @@ -0,0 +1,63 @@ +.header-logo { + background-image: url("../image/logo.svg"); + background-size: 275px 80px; + height: 80px; + width: 275px; +} + + +@media screen and (min-width: 1100px) { + .header-logo { + top: -25px; + } +} + +pre { + white-space: pre; +} + +@media screen and (min-width: 2000px) { + .pytorch-content-left { + width: 1200px; + margin-left: 30px; + } + article.pytorch-article { + max-width: 1200px; + } + .pytorch-breadcrumbs-wrapper { + width: 1200px; + } + .pytorch-right-menu.scrolling-fixed { + position: fixed; + top: 45px; + left: 1580px; + } +} + + +article.pytorch-article section code { + padding: .2em .4em; + background-color: #f3f4f7; + border-radius: 5px; +} + +/* Disable the change in tables */ +article.pytorch-article section table code { + padding: unset; + background-color: unset; + border-radius: unset; +} + +table.autosummary td { + width: 50% +} + +img.align-center { + display: block; + margin-left: auto; + margin-right: auto; +} + +article.pytorch-article p.rubric { + font-weight: bold; +} diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/_static/image/logo.svg b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/_static/image/logo.svg new file mode 100644 index 00000000..04353057 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/_static/image/logo.svg @@ -0,0 +1,24 @@ + + + +Created with Fabric.js 5.3.0 + + + + + + + + + + + + + VLMEvalKit + diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/_static/image/logo_icon.svg b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/_static/image/logo_icon.svg new file mode 100644 index 00000000..c46dd3b5 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/_static/image/logo_icon.svg @@ -0,0 +1,31 @@ + + + + + + + + + + + + + + + + + + + diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/_static/js/custom.js b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/_static/js/custom.js new file mode 100644 index 00000000..84da69d4 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/_static/js/custom.js @@ -0,0 +1,10 @@ +var collapsedSections = []; + +$(document).ready(function () { + $('.model-summary').DataTable({ + "stateSave": false, + "lengthChange": false, + "pageLength": 20, + "order": [] + }); +}); diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/_templates/404.html b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/_templates/404.html new file mode 100644 index 00000000..64910175 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/_templates/404.html @@ -0,0 +1,18 @@ +{% extends "layout.html" %} + +{% block body %} + +

Page Not Found

+

+ The page you are looking for cannot be found. +

+

+ If you just switched documentation versions, it is likely that the page you were on is moved. You can look for it in + the content table left, or go to the homepage. +

+ + +{% endblock %} diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/_templates/autosummary/class.rst b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/_templates/autosummary/class.rst new file mode 100644 index 00000000..4c3a7a9a --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/_templates/autosummary/class.rst @@ -0,0 +1,13 @@ +.. role:: hidden + :class: hidden-section +.. currentmodule:: {{ module }} + + +{{ name | underline}} + +.. autoclass:: {{ name }} + :members: + +.. + autogenerated from _templates/autosummary/class.rst + note it does not have :inherited-members: diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/_templates/callable.rst b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/_templates/callable.rst new file mode 100644 index 00000000..3a7b9d2b --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/_templates/callable.rst @@ -0,0 +1,14 @@ +.. role:: hidden + :class: hidden-section +.. currentmodule:: {{ module }} + + +{{ name | underline}} + +.. autoclass:: {{ name }} + :members: + :special-members: __call__ + +.. + autogenerated from _templates/callable.rst + note it does not have :inherited-members: diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/conf.py b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/conf.py new file mode 100644 index 00000000..92283478 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/conf.py @@ -0,0 +1,242 @@ +# flake8: noqa +# Configuration file for the Sphinx documentation builder. +# +# This file only contains a selection of the most common options. For a full +# list see the documentation: +# https://www.sphinx-doc.org/en/master/usage/configuration.html + +# -- Path setup -------------------------------------------------------------- + +# If extensions (or modules to document with autodoc) are in another directory, +# add these directories to sys.path here. If the directory is relative to the +# documentation root, use os.path.abspath to make it absolute, like shown here. + +import ast +import os +import subprocess +import sys + +import pytorch_sphinx_theme +from sphinx.builders.html import StandaloneHTMLBuilder + +sys.path.insert(0, os.path.abspath('../../')) + +# -- Project information ----------------------------------------------------- + +project = 'VLMEvalKit' +copyright = '2023, VLMEvalKit' +author = 'VLMEvalKit Authors' + +# The full version, including alpha/beta/rc tags +version_file = '../../vlmeval/__init__.py' + + +def get_version(): + with open(version_file, 'r') as f: + file_content = f.read() + # Parse the file content into an abstract syntax tree (AST) + tree = ast.parse(file_content, filename=version_file) + + # Iterate through the body of the AST, looking for an assignment to __version__ + for node in tree.body: + if isinstance(node, ast.Assign): + for target in node.targets: + if isinstance(target, ast.Name) and target.id == '__version__': + return node.value.s + raise ValueError('__version__ not found') + + +release = get_version() + +# -- General configuration --------------------------------------------------- + +# Add any Sphinx extension module names here, as strings. They can be +# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom +# ones. +extensions = [ + 'sphinx.ext.autodoc', + 'sphinx.ext.autosummary', + 'sphinx.ext.intersphinx', + 'sphinx.ext.napoleon', + 'sphinx.ext.viewcode', + 'myst_parser', + 'sphinx_copybutton', + 'sphinx_tabs.tabs', + 'notfound.extension', + 'sphinxcontrib.jquery', + 'sphinx_design', +] + +# Add any paths that contain templates here, relative to this directory. +templates_path = ['_templates'] + +# The suffix(es) of source filenames. +# You can specify multiple suffix as a list of string: +# +source_suffix = { + '.rst': 'restructuredtext', + '.md': 'markdown', +} + +language = 'cn' + +# The master toctree document. +root_doc = 'index' +html_context = { + 'github_version': 'latest', +} +# List of patterns, relative to source directory, that match files and +# directories to ignore when looking for source files. +# This pattern also affects html_static_path and html_extra_path. +exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store'] + +# -- Options for HTML output ------------------------------------------------- + +# The theme to use for HTML and HTML Help pages. See the documentation for +# a list of builtin themes. +# +html_theme = 'pytorch_sphinx_theme' +html_theme_path = [pytorch_sphinx_theme.get_html_theme_path()] + +# Theme options are theme-specific and customize the look and feel of a theme +# further. For a list of options available for each theme, see the +# documentation. +# yapf: disable +html_theme_options = { + 'menu': [ + { + 'name': 'GitHub', + 'url': 'https://github.com/open-compass/VLMEvalKit' + }, + ], + # Specify the language of shared menu + 'menu_lang': 'cn', + # Disable the default edit on GitHub + 'default_edit_on_github': False, +} +# yapf: enable + +# Add any paths that contain custom static files (such as style sheets) here, +# relative to this directory. They are copied after the builtin static files, +# so a file named "default.css" will overwrite the builtin "default.css". +html_static_path = ['_static'] +html_css_files = [ + 'https://cdn.datatables.net/v/bs4/dt-1.12.1/datatables.min.css', + 'css/readthedocs.css' +] +html_js_files = [ + 'https://cdn.datatables.net/v/bs4/dt-1.12.1/datatables.min.js', + 'js/custom.js' +] + +# -- Options for HTMLHelp output --------------------------------------------- + +# Output file base name for HTML help builder. +htmlhelp_basename = 'vlmevalkitdoc' + +# -- Options for LaTeX output ------------------------------------------------ + +latex_elements = { + # The paper size ('letterpaper' or 'a4paper'). + # + # 'papersize': 'letterpaper', + + # The font size ('10pt', '11pt' or '12pt'). + # + # 'pointsize': '10pt', + + # Additional stuff for the LaTeX preamble. + # + # 'preamble': '', +} + +# Grouping the document tree into LaTeX files. List of tuples +# (source start file, target name, title, +# author, documentclass [howto, manual, or own class]). +latex_documents = [ + (root_doc, 'vlmevalkit.tex', 'VLMEvalKit Documentation', author, + 'manual'), +] + +# -- Options for manual page output ------------------------------------------ + +# One entry per manual page. List of tuples +# (source start file, name, description, authors, manual section). +man_pages = [(root_doc, 'vlmevalkit', 'VLMEvalKit Documentation', [author], + 1)] + +# -- Options for Texinfo output ---------------------------------------------- + +# Grouping the document tree into Texinfo files. List of tuples +# (source start file, target name, title, author, +# dir menu entry, description, category) +texinfo_documents = [ + (root_doc, 'vlmevalkit', 'VLMEvalKit Documentation', author, + 'VLMEvalKit Authors', 'AGI evaluation toolbox and benchmark.', + 'Miscellaneous'), +] + +# -- Options for Epub output ------------------------------------------------- + +# Bibliographic Dublin Core info. +epub_title = project + +# The unique identifier of the text. This can be a ISBN number +# or the project homepage. +# +# epub_identifier = '' + +# A unique identification for the text. +# +# epub_uid = '' + +# A list of files that should not be packed into the epub file. +epub_exclude_files = ['search.html'] + +# set priority when building html +StandaloneHTMLBuilder.supported_image_types = [ + 'image/svg+xml', 'image/gif', 'image/png', 'image/jpeg' +] + +# -- Extension configuration ------------------------------------------------- +# Ignore >>> when copying code +copybutton_prompt_text = r'>>> |\.\.\. ' +copybutton_prompt_is_regexp = True + +# Auto-generated header anchors +myst_heading_anchors = 3 +# Enable "colon_fence" extension of myst. +myst_enable_extensions = ['colon_fence', 'dollarmath'] + +# Configuration for intersphinx +intersphinx_mapping = { + 'python': ('https://docs.python.org/3', None), + 'numpy': ('https://numpy.org/doc/stable', None), + 'torch': ('https://pytorch.org/docs/stable/', None), + 'mmengine': ('https://mmengine.readthedocs.io/en/latest/', None), + 'transformers': + ('https://huggingface.co/docs/transformers/main/en/', None), +} +napoleon_custom_sections = [ + # Custom sections for data elements. + ('Meta fields', 'params_style'), + ('Data fields', 'params_style'), +] + +# Disable docstring inheritance +autodoc_inherit_docstrings = False +# Mock some imports during generate API docs. +autodoc_mock_imports = ['rich', 'attr', 'einops'] +# Disable displaying type annotations, these can be very verbose +autodoc_typehints = 'none' + +# The not found page +notfound_template = '404.html' + + +def builder_inited_handler(app): + subprocess.run(['./cp_origin_docs.sh']) + + +def setup(app): + app.connect('builder-inited', builder_inited_handler) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/cp_origin_docs.sh b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/cp_origin_docs.sh new file mode 100644 index 00000000..1e728323 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/cp_origin_docs.sh @@ -0,0 +1,9 @@ +#!/usr/bin/env bash + +# Copy *.md files from docs/ if it doesn't have a Chinese translation + +for filename in $(find ../en/ -name '*.md' -printf "%P\n"); +do + mkdir -p $(dirname $filename) + cp -n ../en/$filename ./$filename +done diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/docutils.conf b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/docutils.conf new file mode 100644 index 00000000..0c00c846 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/docutils.conf @@ -0,0 +1,2 @@ +[html writers] +table_style: colwidths-auto diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/index.rst b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/index.rst new file mode 100644 index 00000000..5147a23b --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/docs/zh-CN/index.rst @@ -0,0 +1,49 @@ +欢迎来到 VLMEvalKit 中文教程! +========================================== + +VLMEvalKit 上手路线 +------------------------------- + +为了用户能够快速上手,我们推荐以下流程: + +- 对于想要使用 VLMEvalKit 的用户,我们推荐先阅读 开始你的第一步_ 部分来设置环境,并启动一个迷你实验熟悉流程。 + +- 若您想进行更多模块的自定义,例如增加数据集和模型,我们提供了 进阶教程_ 。 + +我们始终非常欢迎用户的 PRs 和 Issues 来完善 VLMEvalKit! + +.. _快速开始: +.. toctree:: + :maxdepth: 1 + :caption: 快速开始 + + Quickstart.md + + +.. .. _教程: +.. .. toctree:: +.. :maxdepth: 1 +.. :caption: 教程 + +.. user_guides/framework_overview.md + +.. _进阶教程: +.. toctree:: + :maxdepth: 1 + :caption: 进阶教程 + + Development.md + ConfigSystem.md + +.. .. _其他说明: +.. .. toctree:: +.. :maxdepth: 1 +.. :caption: 其他说明 + +.. notes/contribution_guide.md + +索引与表格 +================== + +* :ref:`genindex` +* :ref:`search` diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/requirements.txt b/evaluation/cosmos3/reasoner/vlmevalkit/requirements.txt new file mode 100644 index 00000000..13466c1f --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/requirements.txt @@ -0,0 +1,80 @@ + +absl-py +anls +apted +bert_score +boto3 +cairosvg +colormath +datasets==2.21.0 +decord; platform_machine != 'arm64' +distance +editdistance +emoji +eva-decord; platform_machine == 'arm64' +imageio +immutabledict +ipdb +jieba +json_repair +langdetect +latex2sympy2 +antlr4-python3-runtime==4.9.3 # latex2sympy2 1.5.4 ships an ATN-v3 parser; unpinned floats to 4.13.2 (ATN-v4) and breaks MathVision eval + +# Upstream benchmark dependencies (imported at module load time) +Levenshtein +loguru +lpips +math-verify==0.9.0 # unpinned floats latex2sympy2->1.9.1, backtracking math-verify to 0.1.0 which imports the uninstalled latex2sympy2_extended +matplotlib +nest_asyncio +nltk +num2words +numpy +omegaconf +openai +opencv-python +openpyxl +pandas +pillow +physical-ai-av==0.2.2 +polygon3 +portalocker +protobuf +pylatexenc + +### Default to Cloud API use case. Choose proper vllm/accelerate versions for local inference. +# vllm +# acceleratepython-box +python-box +python-dotenv +qwen-vl-utils +qwen-vl-utils==0.0.14 +regex +requests +rich +rouge +s3fs +scikit-learn + +scipy +sentence_transformers +sentencepiece +setuptools +spacy +sty +syllapy +tabulate +termcolor +tiktoken +timeout-decorator +timm +torch>=2.0.1 +torchmetrics +torchvision>=0.15.2 +tqdm +transformers==4.57.1 # 5.0 does not work; 4.57.0 is yanked on PyPI +unicodedata2 +validators +xlsxwriter +zss diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/requirements/docs.txt b/evaluation/cosmos3/reasoner/vlmevalkit/requirements/docs.txt new file mode 100644 index 00000000..02587e64 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/requirements/docs.txt @@ -0,0 +1,11 @@ +docutils==0.18.1 +modelindex +myst-parser +-e git+https://github.com/open-compass/pytorch_sphinx_theme.git#egg=pytorch_sphinx_theme +sphinx==6.1.3 +sphinx-copybutton +sphinx-design +sphinx-notfound-page +sphinx-tabs +sphinxcontrib-jquery +tabulate diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/run.py b/evaluation/cosmos3/reasoner/vlmevalkit/run.py new file mode 100644 index 00000000..cb9a4166 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/run.py @@ -0,0 +1,551 @@ +import argparse +import copy as cp +import datetime +import json +import os +import os.path as osp +import subprocess +from functools import partial + +import pandas as pd +from tabulate import tabulate + + +# GET the number of GPUs on the node without importing libs like torch +def get_gpu_list(): + CUDA_VISIBLE_DEVICES = os.environ.get('CUDA_VISIBLE_DEVICES', '') + if CUDA_VISIBLE_DEVICES != '': + gpu_list = [int(x) for x in CUDA_VISIBLE_DEVICES.split(',')] + return gpu_list + try: + ps = subprocess.Popen(('nvidia-smi', '--list-gpus'), stdout=subprocess.PIPE) + output = subprocess.check_output(('wc', '-l'), stdin=ps.stdout) + return list(range(int(output))) + except Exception: + return [] + + +RANK = int(os.environ.get('RANK', 0)) +WORLD_SIZE = int(os.environ.get('WORLD_SIZE', 1)) +LOCAL_WORLD_SIZE = int(os.environ.get("LOCAL_WORLD_SIZE", 1)) +LOCAL_RANK = int(os.environ.get("LOCAL_RANK", 1)) + +GPU_LIST = get_gpu_list() +if LOCAL_WORLD_SIZE > 1 and len(GPU_LIST): + NGPU = len(GPU_LIST) + assert NGPU >= LOCAL_WORLD_SIZE, "The number of processes should be less than or equal to the number of GPUs" + GPU_PER_PROC = NGPU // LOCAL_WORLD_SIZE + DEVICE_START_IDX = GPU_PER_PROC * LOCAL_RANK + CUDA_VISIBLE_DEVICES = [str(i) for i in GPU_LIST[DEVICE_START_IDX: DEVICE_START_IDX + GPU_PER_PROC]] + CUDA_VISIBLE_DEVICES = ','.join(CUDA_VISIBLE_DEVICES) + # Set CUDA_VISIBLE_DEVICES + os.environ['CUDA_VISIBLE_DEVICES'] = CUDA_VISIBLE_DEVICES + print( + f'RANK: {RANK}, LOCAL_RANK: {LOCAL_RANK}, WORLD_SIZE: {WORLD_SIZE},' + f'LOCAL_WORLD_SIZE: {LOCAL_WORLD_SIZE}, CUDA_VISIBLE_DEVICES: {CUDA_VISIBLE_DEVICES}' + ) + + +from vlmeval.config import supported_VLM +from vlmeval.dataset import build_dataset +from vlmeval.dataset.video_dataset_config import supported_video_datasets +from vlmeval.inference import infer_data_job +from vlmeval.inference_mt import infer_data_job_mt +from vlmeval.inference_video import infer_data_job_video +from vlmeval.smp import (MMBenchOfficialServer, get_pred_file_format, githash, listinstr, load, + load_env, ls, prepare_reuse_files, proxy_set, setup_logger, timestr) +from vlmeval.utils.result_transfer import MMMU_result_transfer, MMTBench_result_transfer + + +# Make WORLD_SIZE invisible when build models +def build_model_from_config(cfg, model_name, use_vllm=False): + import vlmeval.api + import vlmeval.vlm + ws_bak = os.environ.pop('WORLD_SIZE', None) + + config = cp.deepcopy(cfg[model_name]) + if use_vllm: + config['use_vllm'] = use_vllm + if 'class' not in config: + return supported_VLM[model_name](**config) + cls_name = config.pop('class') + if hasattr(vlmeval.api, cls_name): + model = getattr(vlmeval.api, cls_name)(**config) + elif hasattr(vlmeval.vlm, cls_name): + model = getattr(vlmeval.vlm, cls_name)(**config) + else: + raise ValueError(f'Class {cls_name} is not supported in `vlmeval.api` or `vlmeval.vlm`') + + if ws_bak: + os.environ['WORLD_SIZE'] = ws_bak + return model + + +def build_dataset_from_config(cfg, dataset_name): + import inspect + + import vlmeval.dataset + config = cp.deepcopy(cfg[dataset_name]) + if config == {}: + return supported_video_datasets[dataset_name]() + assert 'class' in config + cls_name = config.pop('class') + if hasattr(vlmeval.dataset, cls_name): + cls = getattr(vlmeval.dataset, cls_name) + sig = inspect.signature(cls.__init__) + if 'dataset' in sig.parameters and 'dataset' not in config: + config['dataset'] = dataset_name + has_var_keyword = any( + p.kind == inspect.Parameter.VAR_KEYWORD for p in sig.parameters.values() + ) + if has_var_keyword: + valid_params = config + else: + valid_params = {k: v for k, v in config.items() if k in sig.parameters} + return cls(**valid_params) + else: + raise ValueError(f'Class {cls_name} is not supported in `vlmeval.dataset`') + + +def parse_args(): + help_msg = """\ +You can launch the evaluation by setting either --data and --model or --config. + +--data and --model: + Each Arg should be a list of strings, specifying the names of datasets and models. + To find all supported model names, please refer to the `vlmeval/config.py` of check the output of the command \ + `vlmutil mlist all` in the terminal (you should first have vlmeval installed). + To find all supported dataset names, please refer to the `vlmeval/dataset/__init__.py` file. The python script \ + to print all supported dataset names is as follows: + ```python + from vlmeval.dataset import SUPPORTED_DATASETS + print(SUPPORTED_DATASETS) + ``` + or you can check the output of the command `vlmutil dlist all` in the terminal. + To find all supported video dataset default settings, please refer to the \ + `vlmeval/dataset/video_dataset_config.py` file. + +--config: + Launch the evaluation by specifying the path to the config json file. Sample Json Content: + ```json + { + "model": { + "GPT4o_20240806_T00_HIGH": { + "class": "GPT4V", + "model": "gpt-4o-2024-08-06", + "temperature": 0, + "img_detail": "high" + }, + "GPT4o_20240806_T10_Low": { + "class": "GPT4V", + "model": "gpt-4o-2024-08-06", + "temperature": 1.0, + "img_detail": "low" + }, + "GPT4o_20241120": {} + }, + "data": { + "MME-RealWorld-Lite": { + "class": "MMERealWorld", + "dataset": "MME-RealWorld-Lite" + }, + "MMBench_DEV_EN_V11": { + "class": "ImageMCQDataset", + "dataset": "MMBench_DEV_EN_V11" + }, + "MMBench_Video_8frame_nopack": {}, + "Video-MME_16frame_subs": { + "class": "VideoMME", + "dataset": "Video-MME", + "nframe": 16, + "use_subtitle": true, + } + } + } + ``` + Currently, only `model` and `data` are supported fields. The content of each field is a dictionary. + For `model`, the key is the name of the model, and the value is a dictionary containing the following keys: + - `class`: The class name of the model, which should be a class in `vlmeval.vlm` or `vlmeval.api`. + - Other keys are specific to the model, please refer to the corresponding class. + - Tip: The defined model in the `supported_VLM` of `vlmeval/config.py` can be used as a shortcut. + For `data`, the key is the name of the dataset (should be the same as the `dataset` field in most cases, \ + except for video datasets), and the value is a dictionary containing the following keys: + - `class`: The class name of the dataset, which should be a class in `vlmeval.dataset`. + - `dataset`: The name of the dataset, which should be a string that is accepted by the `dataset` argument of the \ + corresponding class. + - Other keys are specific to the dataset, please refer to the corresponding class. + - Tip: The defined dataset in the `supported_video_datasets` of `vlmeval/dataset/video_dataset_config.py` \ + can be used as a shortcut. + + The keys in the `model` and `data` fields will be used for naming the prediction files and evaluation results. + When launching with `--config`, args for API VLMs, such as `--retry`, `--verbose`, will be ignored. +""" + parser = argparse.ArgumentParser(description=help_msg, formatter_class=argparse.RawTextHelpFormatter) + # Essential Args, Setting the Names of Datasets and Models + parser.add_argument('--data', type=str, nargs='+', help='Names of Datasets') + parser.add_argument('--model', type=str, nargs='+', help='Names of Models') + parser.add_argument('--config', type=str, help='Path to the Config Json File') + # Work Dir + parser.add_argument('--work-dir', type=str, default='./outputs', help='select the output directory') + # Infer + Eval or Infer Only + parser.add_argument('--mode', type=str, default='all', choices=['all', 'infer', 'eval']) + # API Kwargs, Apply to API VLMs and Judge API LLMs + parser.add_argument('--api-nproc', type=int, default=4, help='Parallel API calling') + parser.add_argument('--retry', type=int, default=None, help='retry numbers for API VLMs') + parser.add_argument('--judge-nproc', type=int, default=None, help='Parallel judge API calls (default: api-nproc)') + parser.add_argument('--judge-args', type=str, default=None, help='Judge arguments in JSON format') + # Explicitly Set the Judge Model + parser.add_argument('--judge', type=str, default=None) + # Logging Utils + parser.add_argument('--verbose', action='store_true') + # Configuration for Resume + # Ignore: will not rerun failed VLM inference + parser.add_argument('--ignore', action='store_true', help='Ignore failed indices. ') + # Reuse: will reuse the existing prediction files + parser.add_argument('--reuse', action='store_true') + # Reuse-aux: if set, when reuse is True, will also reuse the auxiliary evaluation files + parser.add_argument('--reuse-aux', type=int, default=True, help='reuse auxiliary evaluation files') + parser.add_argument( + '--use-vllm', action='store_true', help='use vllm to generate, the flag is only supported in Llama4 for now') + parser.add_argument('--use-verifier', action='store_true', help='use verifier to evaluate') + parser.add_argument('--save-eval-results', action='store_true', help='save eval results to json') + + args = parser.parse_args() + return args + + +def _save_df_eval_results(eval_results: pd.DataFrame, filepath: str) -> None: + with open(filepath, "w") as outfile: + outfile.write(eval_results.to_json(orient="split")) + + +def _save_dict_eval_results(eval_results: dict, filepath: str) -> None: + with open(filepath, "w") as outfile: + json.dump(eval_results, outfile) + + +def main(): + args = parse_args() + use_config, cfg = False, None + if args.config is not None: + assert args.data is None and args.model is None, '--data and --model should not be set when using --config' + use_config, cfg = True, load(args.config) + args.model = list(cfg['model'].keys()) + args.data = list(cfg['data'].keys()) + else: + assert len(args.data), '--data should be a list of data files' + + if 'MMEVAL_ROOT' in os.environ: + args.work_dir = os.environ['MMEVAL_ROOT'] + + date, commit_id = timestr('day'), githash(digits=8) + eval_id = f"T{date}_G{commit_id}" + logger = setup_logger(log_file=os.path.join(args.work_dir, 'logs', f'{eval_id}_{timestr()}.log')) + + if RANK == 0: + if not args.reuse: + logger.warning('--reuse is not set, will not reuse previous (before one day) temporary files') + else: + logger.warning('--reuse is set, will reuse the latest prediction & temporary pickle files') + + if not use_config: + for k, v in supported_VLM.items(): + if hasattr(v, 'keywords') and 'retry' in v.keywords and args.retry is not None: + v.keywords['retry'] = args.retry + supported_VLM[k] = v + if hasattr(v, 'keywords') and 'verbose' in v.keywords and args.verbose is not None: + v.keywords['verbose'] = args.verbose + supported_VLM[k] = v + + # If FWD_API is set, will use class `GPT4V` for all API models in the config + if os.environ.get('FWD_API', None) == '1': + from vlmeval.api import GPT4V + from vlmeval.config import api_models as supported_APIs + for m in args.model: + if m in supported_APIs: + kws = supported_VLM[m].keywords + supported_VLM[m] = partial(GPT4V, **kws) + logger.warning(f'FWD_API is set, will use class `GPT4V` for {m}') + + if WORLD_SIZE > 1: + import torch.distributed as dist + dist.init_process_group( + backend='nccl', + timeout=datetime.timedelta(seconds=int(os.environ.get('DIST_TIMEOUT', 3600))) + ) + + for _, model_name in enumerate(args.model): + logger.info(f'=========== {model_name} ===========') + model = None + date, commit_id = timestr('day'), githash(digits=8) + eval_id = f"T{date}_G{commit_id}" + + pred_root = osp.join(args.work_dir, model_name, eval_id) + pred_root_meta = osp.join(args.work_dir, model_name) + os.makedirs(pred_root_meta, exist_ok=True) + + prev_pred_roots = ls(osp.join(args.work_dir, model_name), mode='dir') + if len(prev_pred_roots) and args.reuse: + prev_pred_roots.sort() + + if not osp.exists(pred_root): + os.makedirs(pred_root, exist_ok=True) + + if use_config: + model = build_model_from_config(cfg['model'], model_name, args.use_vllm) + + for _, dataset_name in enumerate(args.data): + logger.info(f'----------- {dataset_name} -----------') + if WORLD_SIZE > 1: + dist.barrier() + + try: + pred_format = get_pred_file_format() + result_file_base = f'{model_name}_{dataset_name}.{pred_format}' + + if use_config: + if WORLD_SIZE > 1: + if RANK == 0: + dataset = build_dataset_from_config(cfg['data'], dataset_name) + dist.barrier() + dataset = build_dataset_from_config(cfg['data'], dataset_name) + if dataset is None: + logger.error(f'Dataset {dataset_name} is not valid, will be skipped. ') + continue + else: + dataset_kwargs = {} + if dataset_name in ['MMLongBench_DOC', 'DUDE', 'DUDE_MINI', 'SLIDEVQA', 'SLIDEVQA_MINI']: + dataset_kwargs['model'] = model_name + + # If distributed, first build the dataset on the main process for doing preparation works + if WORLD_SIZE > 1: + if RANK == 0: + dataset = build_dataset(dataset_name, **dataset_kwargs) + dist.barrier() + + dataset = build_dataset(dataset_name, **dataset_kwargs) + if dataset is None: + logger.error(f'Dataset {dataset_name} is not valid, will be skipped. ') + continue + + # Handling Multi-Turn Dataset + result_file = osp.join(pred_root, result_file_base) + # Reuse the previous prediction file if exists + if RANK == 0 and len(prev_pred_roots): + prepare_reuse_files( + pred_root_meta=pred_root_meta, eval_id=eval_id, model_name=model_name, + dataset_name=dataset_name, reuse=args.reuse, reuse_aux=args.reuse_aux + ) + + if WORLD_SIZE > 1: + dist.barrier() + + if model is None: + model = model_name # which is only a name + + if args.mode != "eval": + # Perform the Inference + if dataset.MODALITY == 'VIDEO': + model = infer_data_job_video( + model, + work_dir=pred_root, + model_name=model_name, + dataset=dataset, + result_file_name=result_file_base, + verbose=args.verbose, + api_nproc=args.api_nproc, + use_vllm=args.use_vllm) + elif dataset.TYPE == 'MT': + model = infer_data_job_mt( + model, + work_dir=pred_root, + model_name=model_name, + dataset=dataset, + verbose=args.verbose, + api_nproc=args.api_nproc, + ignore_failed=args.ignore, + use_vllm=args.use_vllm) + else: + model = infer_data_job( + model, + work_dir=pred_root, + model_name=model_name, + dataset=dataset, + verbose=args.verbose, + api_nproc=args.api_nproc, + ignore_failed=args.ignore, + use_vllm=args.use_vllm) + + # Set the judge kwargs first before evaluation or dumping + + judge_kwargs = { + 'nproc': args.judge_nproc if args.judge_nproc is not None else args.api_nproc, + 'verbose': args.verbose, + 'retry': args.retry if args.retry is not None else 3, + **(json.loads(args.judge_args) if args.judge_args else {}), + } + + if args.retry is not None: + judge_kwargs['retry'] = args.retry + if args.judge is not None: + judge_kwargs['model'] = args.judge + else: + print(dataset_name) + if dataset.TYPE in ['MCQ', 'Y/N', 'MCQ_MMMU_Pro'] or listinstr( + ['moviechat1k', 'mme-reasoning'], dataset_name.lower() + ): + if listinstr(['WeMath', 'MME-Reasoning'], dataset_name): + judge_kwargs['model'] = 'gpt-4o-mini' + elif listinstr(['VisualPuzzles'], dataset_name): + judge_kwargs['model'] = 'exact_matching' + elif listinstr(['PuzzleVQA'], dataset_name): + judge_kwargs['model'] = 'exact_matching' + elif listinstr(['VisuLogic'], dataset_name): + judge_kwargs['model'] = 'exact_matching' + else: + judge_kwargs['model'] = 'chatgpt-0125' + elif listinstr(['MMVet', 'LLaVABench', 'MMBench_Video'], dataset_name): + if listinstr(['LLaVABench_KO'], dataset_name): + judge_kwargs['model'] = 'gpt-4o-0806' + else: + judge_kwargs['model'] = 'gpt-4-turbo' + elif listinstr(['VGRPBench'], dataset_name): + judge_kwargs['model'] = 'gpt-4o' + elif listinstr(['MathVista', 'MathVerse', 'MathVision', 'LENS', 'DynaMath', 'VL-RewardBench', 'LogicVista', 'MOAT', 'OCR_Reasoning', 'VTCBench', 'Asclepius', 'MMSafetyBench', 'MSSBench', 'SIUO', 'SIUO_GEN', 'XSTest', 'Flames'], dataset_name): # noqa: E501 + judge_kwargs['model'] = 'gpt-4o-mini' + elif listinstr(['OlympiadBench'], dataset_name): + use_api_judger = judge_kwargs.get("olympiad_use_api_judger", False) + if use_api_judger: + judge_kwargs['model'] = 'gpt-4o-mini' + elif listinstr(['MMLongBench', 'MMDU', 'DUDE', 'SLIDEVQA', 'MIA-Bench', 'WildVision', 'MMAlignBench', 'MM-IFEval'], dataset_name): # noqa: E501 + judge_kwargs['model'] = 'gpt-4o' + elif listinstr(['ChartMimic'], dataset_name): + judge_kwargs['model'] = 'gpt-4o' + elif listinstr(['VDC'], dataset_name): + judge_kwargs['model'] = 'llama31-8b' + elif listinstr(['Video_MMLU_QA', 'Video_MMLU_CAP'], dataset_name): + judge_kwargs['model'] = 'qwen-72b' + elif listinstr(['MMVMBench'], dataset_name): + judge_kwargs['model'] = 'gpt-4o' + elif listinstr(['CVQA_EN', 'CVQA_LOC'], dataset_name): + judge_kwargs['model'] = 'gpt-4.1' + elif listinstr(['M4Bench'], dataset_name): + judge_kwargs['model'] = 'gpt-4o' + elif listinstr(['AyaVisionBench'], dataset_name): + judge_kwargs['model'] = 'gpt-4.1' + elif listinstr(['MathCanvas'], dataset_name): + judge_kwargs['model'] = 'gpt-4.1-2025-04-14' + elif listinstr(['MMReason'], dataset_name): + judge_kwargs['model'] = 'gpt-4.1', + elif listinstr(['CoreCognition'], dataset_name): + judge_kwargs['model'] = 'gpt-4.1' + elif listinstr(['WorldVQA'], dataset_name): + judge_kwargs['model'] = 'gpt-4o-1120' + + if args.use_verifier: + judge_kwargs['use_verifier'] = True + if args.use_vllm: + judge_kwargs['use_vllm'] = True + + if RANK == 0: + logger.info(judge_kwargs) + + if WORLD_SIZE > 1: + dist.barrier() + + # Only RANK 0 handles the evaluation part + if RANK == 0: + # Prepare Submission Files for MMMU_TEST AND MMT-Bench_ALL + if dataset_name in ['MMMU_TEST']: + result_json = MMMU_result_transfer(result_file) + logger.info(f'Transfer MMMU_TEST result to json for official evaluation, ' + f'json file saved in {result_json}') + continue + elif 'MMT-Bench_ALL' in dataset_name: + submission_file = MMTBench_result_transfer(result_file, **judge_kwargs) + logger.info(f'Extract options from prediction of MMT-Bench FULL split for official evaluation ' + f'(https://eval.ai/web/challenges/challenge-page/2328/overview), ' + f'submission file saved in {submission_file}') + continue + + # Skip the evaluation part if only infer + if args.mode == 'infer': + continue + + # Skip the evaluation part if the dataset evaluation is not supported or annotations are missing + if 'MLLMGuard_DS' in dataset_name: + logger.info('The evaluation of MLLMGuard_DS is not supported yet. ') + continue + elif 'AesBench_TEST' == dataset_name: + logger.info(f'The results are saved in {result_file}. ' + f'Please send it to the AesBench Team via huangyipo@hotmail.com.') + continue + elif dataset_name in ['DocVQA_TEST', 'InfoVQA_TEST', 'Q-Bench1_TEST', 'A-Bench_TEST']: + logger.info(f'{dataset_name} is a test split without ground-truth. ' + 'Thus only the inference part is supported for those datasets. ') + continue + elif dataset_name in [ + 'MMBench_TEST_CN', 'MMBench_TEST_EN', 'MMBench', 'MMBench_CN', + 'MMBench_TEST_CN_V11', 'MMBench_TEST_EN_V11', 'MMBench_V11', 'MMBench_CN_V11' + ] and not MMBenchOfficialServer(dataset_name): + logger.error( + f'Can not evaluate {dataset_name} on non-official servers, will skip the evaluation.') + continue + + # Setup the proxy for the evaluation + eval_proxy = os.environ.get('EVAL_PROXY', None) + old_proxy = os.environ.get('HTTP_PROXY', '') + if eval_proxy is not None: + proxy_set(eval_proxy) + + # Perform the Evaluation + eval_results = dataset.evaluate(result_file, **judge_kwargs) + # Display Evaluation Results in Terminal + if eval_results is not None: + assert isinstance(eval_results, dict) or isinstance(eval_results, pd.DataFrame) + logger.info(f'The evaluation of model {model_name} x dataset {dataset_name} has finished! ') + logger.info('Evaluation Results:') + if isinstance(eval_results, dict): + logger.info('\n' + json.dumps(eval_results, indent=4)) + if args.save_eval_results: + _save_dict_eval_results( + eval_results, + osp.join(pred_root, f'{model_name}_{dataset_name}.dict.eval.json'), + ) + elif isinstance(eval_results, pd.DataFrame): + if len(eval_results) < len(eval_results.columns): + eval_results = eval_results.T + logger.info('\n' + tabulate(eval_results)) + if args.save_eval_results: + _save_df_eval_results( + eval_results, + osp.join(pred_root, f'{model_name}_{dataset_name}.df.eval.json'), + ) + + # Restore the proxy + if eval_proxy is not None: + proxy_set(old_proxy) + + # Create the symbolic links for the prediction files + files = os.listdir(pred_root) + files = [x for x in files if (f'{model_name}_{dataset_name}' in x or "status.json" in x)] + for f in files: + cwd = os.getcwd() + file_addr = osp.join(cwd, pred_root, f) + link_addr = osp.join(cwd, pred_root_meta, f) + if osp.exists(link_addr) or osp.islink(link_addr): + os.remove(link_addr) + os.symlink(file_addr, link_addr) + + except Exception as e: + logger.exception(f'Model {model_name} x Dataset {dataset_name} combination failed: {e}, ' + 'skipping this combination.') + continue + + if WORLD_SIZE > 1: + dist.destroy_process_group() + + +if __name__ == '__main__': + load_env() + main() diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/run_api.py b/evaluation/cosmos3/reasoner/vlmevalkit/run_api.py new file mode 100644 index 00000000..478021aa --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/run_api.py @@ -0,0 +1,397 @@ +import argparse +import asyncio +import datetime +import json +import os +from functools import partial +from pathlib import Path +from typing import List + +from vlmeval.api import LMDeployAPI +from vlmeval.api.adapters import get_adapter_registry +from vlmeval.config import supported_VLM +from vlmeval.dataset import build_dataset +from vlmeval.inference_api import APIEvalPipeline, DatasetConfig +from vlmeval.smp import (get_pred_file_format, githash, listinstr, load_env, prepare_reuse_files, + setup_logger, timestr) + +group_dic = { + 'general-mini': ['MMMU_Pro_10c'], + 'math-reasoning-mini': ['MathVista_MINI', 'OlympiadBench', 'IPhO_2025', 'Physics'], + 'sci-reasoning-mini': ['SFE', 'MaCBench', 'MicroVQA', 'XLRS-Bench-lite', 'MSEarthMCQ'], + 'language-mini': ['MM-IFEval'], + 'coding-mini': ['ChartMimic_v2_direct'], + 'svg-mini': ['SArena_MINI'], + 'agent-mini': ['ScreenSpot_v2_Mobile', 'ScreenSpot_v2_Desktop', 'ScreenSpot_v2_Web'], + 'video-mini': ['Video-MME_64frame', 'VideoMMMU_48frame'], + 'sensing-mini': ['RefCOCO', 'OCRBench_v2_MINI', 'CCOCR', 'ChartQAPro', 'BLINK'], +} + + +def get_judge_kwargs(dataset_name: str, args) -> dict: + """Determine the default judge kwargs by dataset name.""" + judge_kwargs = { + 'nproc': args.judge_api_nproc, + 'verbose': args.verbose, + 'retry': args.judge_retry, + 'timeout': args.judge_timeout, + **(json.loads(args.judge_args) if args.judge_args else {}), + } + + if args.judge_base_url: + judge_kwargs['api_base'] = f"{args.judge_base_url.rstrip('/')}/chat/completions" + if args.judge_key: + judge_kwargs['key'] = args.judge_key + + if args.judge is not None: + judge_kwargs['model'] = args.judge + else: + judge_kwargs['model'] = 'gpt-4o-mini' # default + + if listinstr(['WeMath', 'MME-Reasoning'], dataset_name): + judge_kwargs['model'] = 'gpt-4o-mini' + elif listinstr(['VisuLogic'], dataset_name): + judge_kwargs['model'] = 'exact_matching' + elif listinstr(['MMVet', 'LLaVABench', 'MMBench_Video'], dataset_name): + if listinstr(['LLaVABench_KO'], dataset_name): + judge_kwargs['model'] = 'gpt-4o-0806' + else: + judge_kwargs['model'] = 'gpt-4-turbo' + elif listinstr(['VGRPBench'], dataset_name): + judge_kwargs['model'] = 'gpt-4o' + elif listinstr(['MathVista', 'MathVerse', 'MathVision', 'DynaMath', + 'VL-RewardBench', 'LogicVista', 'MOAT', 'OCR_Reasoning'], dataset_name): + judge_kwargs['model'] = 'gpt-4o-mini' + elif listinstr(['OlympiadBench'], dataset_name): + use_api_judger = judge_kwargs.get("olympiad_use_api_judger", False) + if use_api_judger: + judge_kwargs['model'] = 'gpt-4o-mini' + elif listinstr(['MMLongBench', 'MMDU', 'DUDE', 'SLIDEVQA', 'MIA-Bench', + 'WildVision', 'MMAlignBench', 'MM-IFEval'], dataset_name): + judge_kwargs['model'] = 'gpt-4o' + elif listinstr(['ChartMimic'], dataset_name): + judge_kwargs['model'] = 'gpt-4o' + elif listinstr(['VDC'], dataset_name): + judge_kwargs['model'] = 'llama31-8b' + elif listinstr(['Video_MMLU_QA', 'Video_MMLU_CAP'], dataset_name): + judge_kwargs['model'] = 'qwen-72b' + elif listinstr(['MMVMBench'], dataset_name): + judge_kwargs['model'] = 'gpt-4o' + elif listinstr(['CVQA_EN', 'CVQA_LOC'], dataset_name): + judge_kwargs['model'] = 'gpt-4.1' + elif listinstr(['M4Bench'], dataset_name): + judge_kwargs['model'] = 'gpt-4o' + elif listinstr(['AyaVisionBench'], dataset_name): + judge_kwargs['model'] = 'gpt-4.1' + elif listinstr(['MathCanvas'], dataset_name): + judge_kwargs['model'] = 'gpt-4.1-2025-04-14' + elif listinstr(['MMReason'], dataset_name): + judge_kwargs['model'] = 'gpt-4.1' + elif listinstr(['Video-MME'], dataset_name): + judge_kwargs['model'] = 'chatgpt-0125' + + if args.use_verifier: + judge_kwargs['use_verifier'] = True + if args.use_vllm: + judge_kwargs['use_vllm'] = True + + return judge_kwargs + + +def parse_args(): + help_msg = """\ +VLMEvalKit API Pipeline Runner + +This script uses an optimized pipeline for API-based models with the following improvements: +- Cross-dataset unified inference queue +- Parallel inference and evaluation +- Better remote model utilization + +You can launch the evaluation by setting either --data and --model. + +--data and --model: + Specify dataset names and model configuration for API-based inference. + +For more details, see the documentation in run.py. +""" + parser = argparse.ArgumentParser( + description=help_msg, + formatter_class=argparse.RawTextHelpFormatter + ) + + parser.add_argument('--data', type=str, nargs='+', help='Names of Datasets') + parser.add_argument('--group', type=str, nargs='+', default=None, + help='Benchmark groups to evaluate (see group_dic). Use "all" to run all groups.') + + # ================ 推理模型参数 ============== + parser.add_argument('--model', type=str, required=True) + parser.add_argument('--base-url', type=str, default=None, + help='Base URL of OpenAI-compatible API (e.g. http://localhost:8080/v1). ' + 'If set, LMDeployAPI is used for inference without modifying config.py.') + parser.add_argument('--key', type=str, default='sk-admin', help='API key for inference model') + parser.add_argument('--thinker', action='store_true', + help='Enable thinking mode: doubles timeout and max_tokens.') + parser.add_argument('--use-enable-thinking', action='store_true', + help='Pass enable_thinking flag to the model.') + parser.add_argument('--enable-thinking', action='store_true', + help='Value of enable_thinking passed to model (requires --use-enable-thinking).') + parser.add_argument('--max-tokens', type=int, default=2 ** 15, + help='Max tokens for model generation.') + parser.add_argument('--temperature', type=float, default=None) + parser.add_argument('--top-k', type=int, default=None) + parser.add_argument('--top-p', type=float, default=None) + parser.add_argument('--repetition-penalty', type=float, default=None) + parser.add_argument('--presence-penalty', type=float, default=None) + parser.add_argument('--api-nproc', type=int, default=32, + help='Parallel API calling (inference concurrency)') + parser.add_argument('--timeout', type=int, default=1800, + help='Max time in seconds for a single inference request.') + parser.add_argument('--retry', type=int, default=6, + help='Retry times for failed inference.') + parser.add_argument('--custom-prompt', type=str, + choices=list(get_adapter_registry().keys()), default=None, + help='Manually select a model adapter by name.') + + # ================ judge 模型参数 ============== + parser.add_argument('--judge', type=str, default=None) + parser.add_argument('--judge-base-url', type=str, default=None, + help='Base URL of judge API') + parser.add_argument('--judge-key', type=str, default=None, + help='API key for judge model') + parser.add_argument('--judge-api-nproc', type=int, default=32, + help='Parallel API calling for judger') + parser.add_argument('--judge-retry', type=int, default=6, + help='Retry times for failed judgement.') + parser.add_argument('--judge-timeout', type=int, default=600, + help='Max time in seconds for judgement.') + # legacy judger parameters + parser.add_argument('--judge-args', type=str, default=None, + help='Judge arguments in JSON format') + + parser.add_argument('--work-dir', type=str, default='./outputs', + help='Select the output directory') + parser.add_argument('--mode', type=str, default='all', + choices=['all', 'infer', 'eval'], + help='Mode: all (infer+eval), infer (only), eval (only)') + parser.add_argument('--verbose', action='store_true') + parser.add_argument('--ignore', action='store_true', + help='Ignore failed indices') + parser.add_argument('--reuse', action='store_true', + help='Reuse existing prediction files') + parser.add_argument('--reuse-aux', type=int, default=True, + help='Reuse auxiliary evaluation files') + parser.add_argument('--use-vllm', action='store_true', + help='Use vllm to generate') + parser.add_argument('--use-verifier', action='store_true', + help='Use verifier to evaluate') + parser.add_argument('--monitor-interval', type=int, default=30, + help='Status monitoring interval (seconds)') + parser.add_argument('--debug', action='store_true', + help='Debug mode: run evaluation in main process') + + args = parser.parse_args() + return args + + +def main(): + args = parse_args() + + # ============================================== + # Resolve --group into dataset list + # ============================================== + if args.group is not None and len(args.group) > 0: + if 'all' in args.group: + groups = list(group_dic.keys()) + else: + groups = args.group + assert args.data is None, '--data and --group should not be set at the same time' + args.data = [] + for g in groups: + assert g in group_dic, f'Unknown group: {g}. Available: {list(group_dic.keys())}' + args.data.extend(group_dic[g]) + + assert args.data, '--data or --group must be set' + + # ============================================== + # Prepare work dir and logging + # ============================================== + date, commit_id = timestr('day'), githash(digits=8) + eval_id = f"T{date}_G{commit_id}" + model_name = args.model.replace('/', '--') + + # Work dir for the specified model + work_dir = Path(args.work_dir) / model_name + work_dir.mkdir(parents=True, exist_ok=True) + + # Work dir for the current run + pred_root = Path(args.work_dir) / model_name / eval_id + # List previous run + prev_pred_roots = sorted(d for d in work_dir.iterdir() if d.is_dir()) + pred_root.mkdir(exist_ok=True) + + log_file = Path(work_dir) / 'logs' / f'{eval_id}_{datetime.datetime.now().strftime("%H%M%S")}.log' + logger = setup_logger(log_file=str(log_file)) + logger.info(f'Log file: {log_file}') + + if args.mode == 'eval': + args.reuse = True + logger.info('Force to use `reuse=True` for eval mode.') + + if not args.reuse: + logger.warning('--reuse is not set, will not reuse previous temporary files') + else: + logger.info('--reuse is set, will reuse the latest prediction & temporary files') + + WORLD_SIZE = int(os.environ.get('WORLD_SIZE', 1)) + if WORLD_SIZE > 1: + logger.error("API pipeline does not support multi-process mode (WORLD_SIZE > 1).") + return + + # ============================================== + # Build model args (shared across all datasets) + # ============================================== + use_think_args = args.thinker + if args.base_url is not None: + model_args = dict( + model=args.model, + api_base=f"{args.base_url.rstrip('/')}/chat/completions", + key=args.key, + custom_prompt=args.custom_prompt, + max_tokens=args.max_tokens, + retry=args.retry, + timeout=args.timeout, + temperature=args.temperature, + top_k=args.top_k, + top_p=args.top_p, + repetition_penalty=args.repetition_penalty, + presence_penalty=args.presence_penalty, + verbose=args.verbose, + ) + model_args = {k: v for k, v in model_args.items() if v is not None} + if args.use_enable_thinking: + model_args['enable_thinking'] = args.enable_thinking + if use_think_args: + model_args.update(dict(timeout=args.timeout * 2, max_tokens=args.max_tokens * 2)) + model_builder = partial(LMDeployAPI, **model_args) + else: + assert model_name in supported_VLM, \ + f'Model "{model_name}" not found in supported_VLM. Consider using --base-url to specify an API endpoint.' + model_builder = supported_VLM[model_name] + + # ============================================== + # Prepare all datasets + # ============================================== + dataset_configs: List[DatasetConfig] = [] + + for ds_name in args.data: + logger.info(f'-------------------- {ds_name} --------------------') + + # Construct the dataset. + try: + dataset_kwargs = {} + if ds_name in [ + 'MMLongBench_DOC', 'DUDE', 'DUDE_MINI', + 'SLIDEVQA', 'SLIDEVQA_MINI', + ]: + dataset_kwargs['model'] = model_name + dataset = build_dataset(ds_name, **dataset_kwargs) + + if dataset is None: + logger.error(f'Dataset {ds_name} is not valid, will be skipped.') + continue + + # Prepare the result file. + pred_format = get_pred_file_format() + result_file_base = f'{model_name}_{ds_name}.{pred_format}' + result_file = str(pred_root / result_file_base) + + # Prepare the reuse file + if args.reuse and len(prev_pred_roots): + prepare_reuse_files( + pred_root_meta=str(work_dir), + eval_id=eval_id, + model_name=model_name, + dataset_name=ds_name, + reuse=args.reuse, + reuse_aux=args.reuse_aux + ) + + # Skip special datasets. + if ds_name in ['MMMU_TEST']: + logger.info(f'{ds_name} requires special handling, skipped in pipeline.') + continue + if 'MMT-Bench_ALL' in ds_name: + logger.info(f'{ds_name} requires special handling, skipped in pipeline.') + continue + + judge_kwargs = get_judge_kwargs(ds_name, args) + logger.info(f'Judge kwargs: {judge_kwargs}') + + # Complete the dataset config + if dataset.MODALITY == 'VIDEO': + dataset_type = 'video' + elif dataset.TYPE == 'MT': + dataset_type = 'mt' + else: + dataset_type = 'image' + dataset_config = DatasetConfig( + dataset_name=ds_name, + dataset_obj=dataset, + dataset_type=dataset_type, + model_obj=model_builder(), + model_name=model_name, + work_dir=str(pred_root), + result_file=result_file, + judge_kwargs=judge_kwargs, + verbose=args.verbose + ) + dataset_configs.append(dataset_config) + + except Exception as e: + logger.exception(f'Failed to prepare dataset {ds_name}: {e}') + continue + + # ============================================== + # Create and run pipeline + # ============================================== + if len(dataset_configs) == 0: + logger.warning('No valid datasets to evaluate.') + return + + logger.info(f"Starting API Pipeline for model: {model_name}") + logger.info(f"Total datasets: {len(dataset_configs)}") + + pipeline = APIEvalPipeline( + dataset_configs=dataset_configs, + concurrency=args.api_nproc, + monitor_interval=args.monitor_interval, + run_infer=args.mode in {'infer', 'all'}, + run_eval=args.mode in {'eval', 'all'}, + debug=args.debug + ) + + try: + asyncio.run(pipeline.run()) + except KeyboardInterrupt: + logger.warning("Pipeline interrupted by user.") + except Exception as e: + logger.exception(f"Pipeline failed with error: {e}") + + # Create symbolic links. + try: + files = list(pred_root.iterdir()) + for ds_name in args.data: + files_to_link = [f for f in files if f.is_file() and f'{model_name}_{ds_name}' in f.name] + for f in files_to_link: + file_addr = pred_root.absolute() / f.name + link_addr = work_dir.absolute() / f.name + if link_addr.exists() or link_addr.is_symlink(): + link_addr.unlink() + link_addr.symlink_to(file_addr.relative_to(link_addr.parent)) + except Exception as e: + logger.warning(f"Failed to create symbolic links: {e}") + + +if __name__ == '__main__': + load_env() + main() diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/scripts/AI2D_preproc.ipynb b/evaluation/cosmos3/reasoner/vlmevalkit/scripts/AI2D_preproc.ipynb new file mode 100644 index 00000000..f93b8a88 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/scripts/AI2D_preproc.ipynb @@ -0,0 +1,261 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import os, cv2\n", + "import string\n", + "import os.path as osp\n", + "import numpy as np\n", + "from collections import defaultdict\n", + "from vlmeval.smp import ls, load, dump, download_file, encode_image_file_to_base64, md5, mrlines\n", + "import pandas as pd\n", + "import matplotlib.pyplot as plt\n", + "import multiprocessing as mp\n", + "from PIL import Image, ImageFont, ImageDraw\n", + "\n", + "font_URL = 'http://opencompass.openxlab.space/utils/Fonts/timesb.ttf'\n", + "font_file = 'timesb.ttf'\n", + "if not osp.exists(font_file):\n", + " download_file(font_URL)\n", + " \n", + "test_split_URL = 'https://s3-us-east-2.amazonaws.com/prior-datasets/ai2d_test_ids.csv'\n", + "test_split_file = 'ai2d_test_ids.csv'\n", + "if not osp.exists(test_split_file):\n", + " download_file(test_split_URL)\n", + " \n", + "test_ids = set(mrlines(test_split_file))\n", + " \n", + "def proper_font_size(font_file, wh, text, ratio=1):\n", + " font_size = 2\n", + " while True:\n", + " font = ImageFont.truetype(font_file, font_size)\n", + " real_box = font.getbbox(text)\n", + " real_wh = (real_box[2] - real_box[0], real_box[3] - real_box[1])\n", + " if real_wh[0] > wh[0] * ratio or real_wh[1] > wh[1] * ratio:\n", + " break\n", + " font_size += 1\n", + " return font_size\n", + "\n", + "def cover_image(ann_path):\n", + " data = load(ann_path)\n", + " texts = list(data['text'].values())\n", + " raw_img = ann_path.replace('annotations', 'images').replace('.json', '')\n", + " tgt_img = raw_img.replace('images', 'images_abc')\n", + " img = Image.open(raw_img)\n", + " draw = ImageDraw.Draw(img)\n", + " for text in texts:\n", + " st, ed = tuple(text['rectangle'][0]), tuple(text['rectangle'][1])\n", + " T = text['replacementText']\n", + " draw.rectangle((st, ed), fill='white')\n", + " font_size = proper_font_size(font_file, (ed[0] - st[0], ed[1] - st[1]), T, ratio=1)\n", + " font = ImageFont.truetype(font_file, font_size)\n", + " text_box = font.getbbox(T)\n", + " text_wh = (text_box[2] - text_box[0], text_box[3] - text_box[1])\n", + " cx, cy = (st[0] + ed[0]) // 2, st[1]\n", + " stx = cx - text_wh[0] // 2\n", + " sty = cy - text_wh[1] // 2\n", + " draw.text((stx, sty), T, font=font, fill='black')\n", + " img.save(tgt_img) " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Process for no mask images\n", + "test_ids = set(mrlines(test_split_file))\n", + "\n", + "def detect_image_color(image):\n", + " gray_image = image.convert('L')\n", + " mean_brightness = np.mean(np.array(gray_image))\n", + " if mean_brightness < 127:\n", + " return 'white'\n", + " else:\n", + " return 'black'\n", + "\n", + "def cover_image(ann_path):\n", + " data = load(ann_path)\n", + " texts = list(data['text'].values())\n", + " raw_img = ann_path.replace('annotations', 'images').replace('.json', '')\n", + " tgt_img = raw_img.replace('images', 'images_abc')\n", + " img = Image.open(raw_img)\n", + " draw = ImageDraw.Draw(img)\n", + " color = detect_image_color(img)\n", + " font_size = 0\n", + " for text in texts:\n", + " st, ed = tuple(text['rectangle'][0]), tuple(text['rectangle'][1])\n", + " font_size += (ed[1] - st[1])\n", + " if len(texts) != 0:\n", + " font_size /= len(texts)\n", + " else:\n", + " font_size = 2\n", + " for text in texts:\n", + " st, ed = tuple(text['rectangle'][0]), tuple(text['rectangle'][1])\n", + " T = text['replacementText']\n", + " for i in range(2):\n", + " draw.rectangle(\n", + " [(st[0] - i, st[1] - i), (ed[0] + i, ed[1] + i)],\n", + " outline=color\n", + " )\n", + " font = ImageFont.truetype(font_file, font_size)\n", + " text_box = font.getbbox(T)\n", + " text_wh = (text_box[2] - text_box[0], text_box[3] - text_box[1])\n", + " cx, cy = (st[0] + ed[0]) // 2, st[1]\n", + " stx = cx - text_wh[0] // 2\n", + " sty = cy - text_wh[1] * 1.5\n", + " if sty < 0:\n", + " sty = cy + text_wh[1] * 1.3\n", + " draw.text((stx, sty), T, font=font, fill=color)\n", + " img.save(tgt_img) " + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "download_file('https://ai2-public-datasets.s3.amazonaws.com/diagrams/ai2d-all.zip')\n", + "os.system('unzip -o ai2d-all.zip')\n", + "\n", + "images = ls('ai2d/images/')\n", + "questions = ls('ai2d/questions/')\n", + "annotations = ls('ai2d/annotations/')\n", + "cates = load('ai2d/categories.json')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "pool = mp.Pool(32)\n", + "pool.map(cover_image, annotations)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def puncproc(inText):\n", + " import re\n", + " outText = inText\n", + " punct = [\n", + " ';', r'/', '[', ']', '\"', '{', '}', '(', ')', '=', '+', '\\\\', '_', '-',\n", + " '>', '<', '@', '`', ',', '?', '!'\n", + " ]\n", + " commaStrip = re.compile('(\\d)(,)(\\d)') # noqa: W605\n", + " periodStrip = re.compile('(?!<=\\d)(\\.)(?!\\d)') # noqa: W605\n", + " for p in punct:\n", + " if (p + ' ' in inText or ' ' + p in inText) or (re.search(commaStrip, inText) is not None):\n", + " outText = outText.replace(p, '')\n", + " else:\n", + " outText = outText.replace(p, ' ')\n", + " outText = periodStrip.sub('', outText, re.UNICODE)\n", + " return outText\n", + "\n", + "def check_choices(line):\n", + " def ischar(s):\n", + " s = str(s)\n", + " if s in ['{}', 'Both', 'None of above']:\n", + " return True\n", + " elif s.startswith('Stage ') and ischar(s[6:]):\n", + " return True\n", + " elif ' and ' in s and np.all([ischar(x) for x in s.split(' and ')]):\n", + " return True\n", + " elif len(s) <= 2:\n", + " return True\n", + " elif len(puncproc(s).split()) > 1:\n", + " return np.all([ischar(x) for x in puncproc(s).split()])\n", + " return False\n", + " n_char = sum([ischar(line[x]) for x in 'ABCD'])\n", + " return n_char >= 3\n", + "\n", + "def check_question(question):\n", + " words = puncproc(question).split()\n", + " for ch in string.ascii_lowercase + string.ascii_uppercase:\n", + " if ch in words:\n", + " return True\n", + " return False\n", + "\n", + "def is_abc(abc, choices, question):\n", + " if abc == 0:\n", + " return False\n", + " if check_choices(choices):\n", + " return True\n", + " if check_question(question):\n", + " return True\n", + " return False" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "data_all = defaultdict(list)\n", + "for qfile in questions:\n", + " data = load(qfile)\n", + " idx = data['imageName'].split('.')[0]\n", + " if idx not in test_ids:\n", + " continue\n", + " image_pth = qfile.replace('questions', 'images').replace('.json', '')\n", + " cate = cates[image_pth.split('/')[-1]]\n", + " for q, qmeta in data['questions'].items():\n", + " assert '.png-' in qmeta['questionId']\n", + " main, sub = qmeta['questionId'].split('.png-')\n", + " idx = int(main) * 100 + int(sub)\n", + " \n", + " answers = qmeta['answerTexts']\n", + " correct = qmeta['correctAnswer']\n", + " \n", + " data_all['index'].append(idx)\n", + " data_all['question'].append(q)\n", + " assert len(answers) == 4\n", + " for c, a in zip('ABCD', answers):\n", + " data_all[c].append(a)\n", + " data_all['answer'].append('ABCD'[qmeta['correctAnswer']])\n", + " data_all['category'].append(cate)\n", + " data_all['abcLabel'].append(qmeta['abcLabel'])\n", + " abc = is_abc(qmeta['abcLabel'], {x: data_all[x][-1] for x in 'ABCD'}, q)\n", + " # if qmeta['abcLabel'] and not abc:\n", + " # print(qmeta['abcLabel'], {x: data_all[x][-1] for x in 'ABCD'}, q)\n", + " data_all['image_path'].append(image_pth.replace('images', 'images_abc') if abc else image_pth)\n", + "data = pd.DataFrame(data_all)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "images = []\n", + "image_seen = {}\n", + "for idx, pth in zip(data['index'], data['image_path']):\n", + " images.append(encode_image_file_to_base64(pth))\n", + "\n", + "data['image'] = images\n", + "dump(data, 'AI2D_TEST.tsv')\n", + "print(md5('AI2D_TEST.tsv'))" + ] + } + ], + "metadata": { + "language_info": { + "name": "python" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/scripts/apires_scan.py b/evaluation/cosmos3/reasoner/vlmevalkit/scripts/apires_scan.py new file mode 100644 index 00000000..e8b33843 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/scripts/apires_scan.py @@ -0,0 +1,60 @@ +import os.path as osp +import sys + +from vlmeval.dataset import SUPPORTED_DATASETS +from vlmeval.smp import listinstr, load, ls + +FAIL_MSG = 'Failed to obtain answer via API.' + +root = sys.argv[1] +if root[-1] in '/\\': + root = root[:-1] + +model_name = root.split('/')[-1] + +for d in SUPPORTED_DATASETS: + from vlmeval.smp import get_pred_file_format + pred_format = get_pred_file_format() + fname = f'{model_name}_{d}.{pred_format}' + pth = osp.join(root, fname) + if osp.exists(pth): + data = load(pth) + # Detect Failure + assert 'prediction' in data + data['prediction'] = [str(x) for x in data['prediction']] + fail = [FAIL_MSG in x for x in data['prediction']] + if sum(fail): + nfail = sum(fail) + ntot = len(fail) + print(f'Model {model_name} x Dataset {d}: {nfail} out of {ntot} failed. {nfail / ntot * 100: .2f}%. ') + + eval_files = ls(root, match=f'{model_name}_{d}_') + eval_files = [x for x in eval_files if listinstr([f'{d}_openai', f'{d}_gpt'], x) and x.endswith('.xlsx')] + + if len(eval_files) == 0: + print(f'Model {model_name} x Dataset {d} openai missing') + continue + + assert len(eval_files) == 1 + eval_file = eval_files[0] + data = load(eval_file) + + if 'MMVet' in d: + bad = [x for x in data['log'] if 'All 5 retries failed.' in str(x)] + if len(bad): + print(f'Model {model_name} x Dataset {d} Evaluation: {len(bad)} out of {len(data)} failed.') + elif 'MathVista' in d: + bad = [x for x in data['res'] if FAIL_MSG in str(x)] + if len(bad): + print(f'Model {model_name} x Dataset {d} Evaluation: {len(bad)} out of {len(data)} failed.') + + elif d == 'LLaVABench': + sub = data[data['gpt4_score'] == -1] + sub = sub[sub['gpt4_score'] == -1] + if len(sub): + print(f'Model {model_name} x Dataset {d} Evaluation: {len(sub)} out of {len(data)} failed.') + else: + bad = [x for x in data['log'] if FAIL_MSG in str(x)] + if len(bad): + print(f'Model {model_name} x Dataset {d} Evaluation: {len(bad)} out of {len(data)} failed.') + \ No newline at end of file diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/scripts/auto_run.py b/evaluation/cosmos3/reasoner/vlmevalkit/scripts/auto_run.py new file mode 100644 index 00000000..1c428236 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/scripts/auto_run.py @@ -0,0 +1,44 @@ +import argparse +import os +import os.path as osp + +from vlmeval.config import supported_VLM +from vlmeval.smp import listinstr + + +def is_api(x): + return getattr(supported_VLM[x].func, 'is_api', False) + +models = list(supported_VLM) +models = [x for x in models if 'fs' not in x] +models = [x for x in models if not is_api(x)] +exclude_list = ['cogvlm-grounding-generalist', 'emu2'] +models = [x for x in models if x not in exclude_list] + +def is_large(x): + return '80b' in x or 'emu2' in x or '34B' in x + +small_models = [x for x in models if not is_large(x)] +large_models = [x for x in models if is_large(x)] +models = small_models + large_models + +parser = argparse.ArgumentParser() +parser.add_argument('--data', type=str, nargs='+', required=True) +args = parser.parse_args() + +# Skip some models +models = [x for x in models if not listinstr(['MiniGPT', 'grounding-generalist'], x)] + +for m in models: + from vlmeval.smp import get_pred_file_format + pred_format = get_pred_file_format() + unknown_datasets = [x for x in args.data if not osp.exists(f'{m}/{m}_{x}.{pred_format}')] + if len(unknown_datasets) == 0: + continue + dataset_str = ' '.join(unknown_datasets) + if '80b' in m: + cmd = f'python run.py --data {dataset_str} --model {m}' + else: + cmd = f'bash run.sh --data {dataset_str} --model {m}' + print(cmd) + os.system(cmd) \ No newline at end of file diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/scripts/convert_macbench.py b/evaluation/cosmos3/reasoner/vlmevalkit/scripts/convert_macbench.py new file mode 100644 index 00000000..e4ac308f --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/scripts/convert_macbench.py @@ -0,0 +1,98 @@ +import argparse +import string +import ast +from pathlib import Path +from tqdm import tqdm +from datasets import load_dataset, Dataset +import pandas as pd +# from vlmeval.smp.vlm import encode_image_to_base64 +from concurrent.futures import ProcessPoolExecutor + +prog_description = """\ +Convert original MaCBench dataset to TSV. +""" + + +def parse_args(): + parser = argparse.ArgumentParser(description=prog_description) + parser.add_argument('out', type=Path, help='output path') + args = parser.parse_args() + return args + + +CONFIGS = [ + 'afm-image', 'chem-lab-basic', 'chem-lab-comparison', + 'chem-lab-equipments', 'chirality', 'cif-atomic-species', + 'cif-crystal-system', 'cif-density', 'cif-symmetry', 'cif-volume', + 'electronic-structure', 'handdrawn-molecules', 'isomers', + 'mof-adsorption-strength-comparison', 'mof-adsorption-strength-order', + 'mof-capacity-comparison', 'mof-capacity-order', 'mof-capacity-value', + 'mof-henry-constant-comparison', 'mof-henry-constant-order', + 'mof-working-capacity-comparison', 'mof-working-capacity-order', + 'mof-working-capacity-value', 'org-schema', 'org-schema-wo-smiles', + 'organic-molecules', 'spectral-analysis', 'tables-qa', 'us-patent-figures', + 'us-patent-plots', 'xrd-pattern-matching', 'xrd-pattern-shape', + 'xrd-peak-position', 'xrd-relative-intensity' +] + + +def process_row(row): + example = ast.literal_eval(row['examples'][0]) + + image = example['qentries_modality']['image']['entry1']['value'].partition(',')[-1] + assert len(image) > 1000 + output = {'image': image} + + entries = example['qentries_modality']['image'] + question = example['input'] + for k, entry in entries.items(): + if entry['type'] == 'text': + question = question.replace('{' + k + '}', entry['value']) + else: + question = question.replace('{' + k + '}', '{image}') + output['question'] = question + + if 'target' in example: + answer = example['target'] + output['answer'] = str(answer) + elif 'target_scores' in example: + answer = [] + for i, (option, grade) in enumerate(example['target_scores'].items()): + column = string.ascii_uppercase[i] + output[column] = option + if grade: + answer.append(column) + output['answer'] = ','.join(answer) + + if row['relative_tolerance'] is not None: + output['relative_tolerance'] = row['relative_tolerance'] + return output + + +def process_single_config(name): + ds: Dataset = load_dataset('jablonkagroup/MaCBench', name)['train'] + processed_rows = [] + for row in ds: + processed_rows.append(process_row(row)) + df = pd.DataFrame(processed_rows) + df['category'] = str(name) + return df + + +def main(): + args = parse_args() + all_dfs = [] + with ProcessPoolExecutor() as executor: + tasks = [] + for name in CONFIGS: + tasks.append(executor.submit(process_single_config, name)) + for task in tqdm(tasks): + sub_df = task.result() + all_dfs.append(sub_df) + + df = pd.concat(all_dfs, ignore_index=True).reset_index() + df.to_csv(args.out, sep='\t', index=True) + + +if __name__ == "__main__": + main() diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/scripts/cover.sh b/evaluation/cosmos3/reasoner/vlmevalkit/scripts/cover.sh new file mode 100644 index 00000000..0a35c508 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/scripts/cover.sh @@ -0,0 +1,4 @@ +#!/bin/bash +DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) +cp $DIR/../config.py $DIR/../vlmeval/ +cp $DIR/../misc/* $DIR/../vlmeval/vlm/misc/ \ No newline at end of file diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/scripts/data_browser.py b/evaluation/cosmos3/reasoner/vlmevalkit/scripts/data_browser.py new file mode 100644 index 00000000..72ad5855 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/scripts/data_browser.py @@ -0,0 +1,179 @@ +""" +pip install gradio # proxy_on first +python vis_geochat_data.py +# browse data in http://127.0.0.1:10064 +""" + +import argparse +import base64 +import copy +import io +import json +import os +import os.path as osp +import string +import time +from io import BytesIO +from typing import Sequence + +import gradio as gr +import pandas as pd +from PIL import Image + +from vlmeval.api import OpenAIWrapper +from vlmeval.dataset import SUPPORTED_DATASETS, build_dataset +from vlmeval.smp import LMUDataRoot, encode_image_file_to_base64, load + +SYS = "You are a helpful assistant. Your job is to faithfully translate all provided text into Chinese faithfully. " + +# Translator = SiliconFlowAPI(model='Qwen/Qwen2.5-7B-Instruct', system_prompt=SYS) +Translator = OpenAIWrapper(model='gpt-4o-mini', system_prompt=SYS) + + +def image_to_mdstring(image): + return f"![image](data:image/jpeg;base64,{image})" + + +def images_to_md(images): + return '\n\n'.join([image_to_mdstring(image) for image in images]) + + +def mmqa_display(question, target_size=2048): + question = {k.lower() if len(k) > 1 else k: v for k, v in question.items()} + keys = list(question.keys()) + keys = [k for k in keys if k not in ['index', 'image']] + + idx = question.pop('index', 'XXX') + text = f'\n- INDEX: {idx}\n' + + if 'image' in question: + images = question.pop('image') + if images[0] == '[' and images[-1] == ']': + images = eval(images) + else: + images = [images] + else: + images = question.pop('image_path') + if images[0] == '[' and images[-1] == ']': + images = eval(images) + else: + images = [images] + images = [encode_image_file_to_base64(x) for x in images] + + qtext = question.pop('question', None) + if qtext is not None: + text += f'- QUESTION: {qtext}\n' + + if 'A' in question: + text += f'- Choices: \n' + for k in string.ascii_uppercase: + if k in question: + text += f'\t-{k}: {question.pop(k)}\n' + answer = question.pop('answer', None) + + for k in question: + if not pd.isna(question[k]): + text += f'- {k.upper()}. {question[k]}\n' + + if answer is not None: + text += f'- ANSWER: {answer}\n' + + image_md = images_to_md(images) + + return text, image_md + + +def parse_args(): + parser = argparse.ArgumentParser() + # Essential Args, Setting the Names of Datasets and Models + parser.add_argument('--port', type=int, default=7860) + args = parser.parse_args() + return args + + +def gradio_app_vis_dataset(port=7860): + data, loaded_obj = None, {} + + def btn_submit_click(filename, ann_id): + if filename not in loaded_obj: + return filename_change(filename, ann_id) + nonlocal data + data_desc = gr.Markdown(f'Visualizing {filename}, {len(data)} samples in total. ') + if ann_id < 0 or ann_id >= len(data): + return filename, ann_id, data_desc, gr.Markdown('Invalid Index'), gr.Markdown(f'Index out of range [0, {len(data) - 1}]') + item = data.iloc[ann_id] + text, image_md = mmqa_display(item) + return filename, ann_id, data_desc, image_md, text + + def btn_next_click(filename, ann_id): + return btn_submit_click(filename, ann_id + 1) + + # def translate_click(anno_en): + # return gr.Markdown(Translator.generate(anno_en)) + + def filename_change(filename, ann_id): + nonlocal data, loaded_obj + + def legal_filename(filename): + LMURoot = LMUDataRoot() + if filename in SUPPORTED_DATASETS: + return build_dataset(filename).data + elif osp.exists(filename): + data = load(filename) + assert 'index' in data and 'image' in data + image_map = {i: image for i, image in zip(data['index'], data['image'])} + for k, v in image_map.items(): + if (not isinstance(v, str) or len(v) < 64) and v in image_map: + image_map[k] = image_map[v] + data['image'] = [image_map[k] for k in data['index']] + return data + elif osp.exists(osp.join(LMURoot, filename)): + filename = osp.join(LMURoot, filename) + return legal_filename(filename) + else: + return None + + data = legal_filename(filename) + if data is None: + return filename, 0, gr.Markdown(''), gr.Markdown("File not found"), gr.Markdown("File not found") + + loaded_obj[filename] = data + return btn_submit_click(filename, 0) + + with gr.Blocks() as app: + + filename = gr.Textbox( + value='Dataset Name (supported by VLMEvalKit) or TSV FileName (Relative under `LMURoot` or Real Path)', + label='Dataset', + interactive=True, + visible=True) + + with gr.Row(): + ann_id = gr.Number(0, label='Sample Index (Press Enter)', interactive=True, visible=True) + btn_next = gr.Button("Next") + # btn_translate = gr.Button('CN Translate') + + with gr.Row(): + data_desc = gr.Markdown('Dataset Description', label='Dataset Description') + + with gr.Row(): + image_output = gr.Markdown('Image PlaceHolder', label='Image Visualization') + anno_en = gr.Markdown('Image Annotation', label='Image Annotation') + # anno_cn = gr.Markdown('Image Annotation (Chinese)', label='Image Annotation (Chinese)') + + input_components = [filename, ann_id] + all_components = [filename, ann_id, data_desc, image_output, anno_en] + + filename.submit(filename_change, input_components, all_components) + ann_id.submit(btn_submit_click, input_components, all_components) + btn_next.click(btn_next_click, input_components, all_components) + # btn_translate.click(translate_click, anno_en, anno_cn) + + # app.launch() + app.launch(server_name='0.0.0.0', debug=True, show_error=True, server_port=port) + + +if __name__ == "__main__": + args = parse_args() + gradio_app_vis_dataset(port=args.port) + diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/scripts/mmb_eval_gradio.py b/evaluation/cosmos3/reasoner/vlmevalkit/scripts/mmb_eval_gradio.py new file mode 100644 index 00000000..a98034e6 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/scripts/mmb_eval_gradio.py @@ -0,0 +1,122 @@ +import datetime +import os +import os.path as osp +import shutil + +import gradio as gr +import numpy as np +import pandas as pd + +from vlmeval.dataset import build_dataset +from vlmeval.smp import LMUDataRoot, cn_string, dump, load, md5 +from vlmeval.tools import EVAL + +HEADER = """ +# Welcome to MMBench👏👏 +We are delighted that you are willing to submit the evaluation results to the MMBench official website! The evaluation service currently can handle submissions of MMBench, MMBench-CN, and CCBench. We use `gpt-3.5-turbo-0125` to help answer matching. Evaluation Codes in VLMEvalKit: https://github.com/open-compass/VLMEvalKit. Please adopt / follow the implementation of VLMEvalKit to generate the submission files. + +The evaluation script is available at https://github.com/open-compass/VLMEvalKit/tree/main/scripts/mmb_eval_gradio.py +Please contact `opencompass@pjlab.org.cn` for any inquirys about this script. +""" + +def upload_file(file): + file_path = file.name + return file_path + +def prepare_file(file_name): + file_md5 = md5(file_name) + root = LMUDataRoot() + root = osp.join(root, 'eval_server') + os.makedirs(root, exist_ok=True) + suffix = file_name.split('.')[-1] + if suffix not in ['xlsx', 'tsv', 'csv']: + return False, "Please submit a file that ends with `.xlsx`, `.tsv`, or `.csv`" + new_file_name = osp.join(root, f'{file_md5}.{suffix}') + shutil.move(file_name, new_file_name) + eval_file = new_file_name + try: + data = load(eval_file) + except: + return False, "Your excel file can not be successfully loaded by `pd.read_excel`, please double check and submit again. " + for k in data.keys(): + data[k.lower() if k not in 'ABCD' else k] = data.pop(k) + if "index" not in data: + return False, "Your excel file should have a column named `index`, please double check and submit again" , {} + if "prediction" not in data: + return False, "Your excel file should have a column named `prediction`, please double check and submit again" , {} + for ch in 'ABCD': + if ch not in data: + return False, f"Your excel file should have a column named `{ch}`, please double check and submit again" , {} + dump(data, eval_file) + return True, eval_file + +def determine_dataset(eval_file): + data = load(eval_file) + def cn_ratio(data): + iscn = [cn_string(x) for x in data['question']] + return np.mean(iscn) + max_ind = np.max([int(x) for x in data['index'] if int(x) < 1e5]) + if max_ind < 1000 and 'l2-category' not in data: + return 'CCBench' if cn_ratio(data) > 0.5 else "Unknown" + elif max_ind < 3000 : + return 'MMBench_CN' if cn_ratio(data) > 0.5 else "MMBench" + else: + return 'MMBench_CN_V11' if cn_ratio(data) > 0.5 else "MMBench_V11" + + +def reformat_acc(acc): + splits = set(acc['split']) + keys = list(acc.keys()) + keys.remove('split') + nacc = {'Category': []} + for sp in splits: + nacc[sp.upper()] = [] + for k in keys: + nacc['Category'].append(k) + for sp in splits: + nacc[sp.upper()].append(acc[acc['split'] == sp].iloc[0][k] * 100) + return pd.DataFrame(nacc) + +def evaluate(file): + file_name = file.name + flag, eval_file = prepare_file(file_name) + if not flag: + return "Error: " + eval_file + dataset = determine_dataset(eval_file) + if dataset == 'Unknown': + return "Error: Cannot determine the dataset given your submitted file. " + + eval_id = eval_file.split('/')[-1].split('.')[0] + ret = f"Evaluation ID: {eval_id}\n" + timestamp = datetime.datetime.now().strftime('%Y.%m.%d %H:%M:%S') + ret += f'Evaluation Timestamp: {timestamp}\n' + eval_data = load(eval_file) + eval_data['index'] = [int(x) for x in eval_data['index']] + base_data = build_dataset(dataset).data + base_index_set = set([int(x) for x in base_data['index']]) + inds_more = {k for k in eval_data['index'] if k not in base_index_set} + if len(inds_more) > 0: + inds_more = set([x % 1e6 for x in inds_more]) + ret += f"Warning: The matched dataset is {dataset}. The following indices are not in the base dataset: {inds_more}\n" + ret += f"We automatically remove those indices, and still recommend you to check the indices in your prediction file.\n" + eval_data = eval_data[eval_data['index'].isin(base_index_set)] + dump(eval_data, eval_file) + + acc = EVAL(dataset, eval_file) + nacc = reformat_acc(acc).round(1) + return ret, nacc + +with gr.Blocks() as demo: + gr.Markdown(HEADER) + file_output = gr.File() + upload_button = gr.UploadButton("Click to upload you prediction files for a supported benchmark") + upload_button.upload(upload_file, upload_button, file_output) + + btn = gr.Button("🚀 Evaluate") + eval_log = gr.Textbox(label="Evaluation Log", placeholder="Your evaluation log will be displayed here") + df_empty = pd.DataFrame([], columns=['Evaluation Result']) + eval_result = gr.components.DataFrame(value=df_empty) + btn.click(evaluate, inputs=[file_output], outputs=[eval_log, eval_result]) + +if __name__ == '__main__': + demo.launch(server_name='0.0.0.0', debug=True, show_error=True) \ No newline at end of file diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/scripts/prepare_spar_bench.py b/evaluation/cosmos3/reasoner/vlmevalkit/scripts/prepare_spar_bench.py new file mode 100644 index 00000000..ec1f1715 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/scripts/prepare_spar_bench.py @@ -0,0 +1,55 @@ +"""Convert SPAR-Bench from HuggingFace to TSV format.""" +import argparse +import os +import base64 +from io import BytesIO +import pandas as pd +from datasets import load_dataset +from PIL import Image +from tqdm import tqdm + + +def encode_image_to_base64(image): + buffered = BytesIO() + image.save(buffered, format="PNG") + return base64.b64encode(buffered.getvalue()).decode() + + +def convert_spar_bench_to_tsv(dataset_name='jasonzhango/SPAR-Bench', output_dir='./data'): + print(f"Loading {dataset_name}...") + dataset = load_dataset(dataset_name) + test_data = dataset['test'] + + tsv_data = [] + for idx, example in enumerate(tqdm(test_data)): + images = example['image'] + if isinstance(images, list): + image_b64 = encode_image_to_base64(images[0]) + else: + image_b64 = encode_image_to_base64(images) + + tsv_data.append({ + 'index': idx, + 'image': image_b64, + 'question': example['question'], + 'answer': example['answer'], + 'task': example.get('task', 'unknown'), + 'img_type': example.get('img_type', 'single_view'), + 'format_type': example.get('format_type', 'unknown'), + 'source': example.get('source', 'unknown'), + }) + + df = pd.DataFrame(tsv_data) + os.makedirs(output_dir, exist_ok=True) + output_name = 'SPAR-Bench-Tiny.tsv' if 'Tiny' in dataset_name else 'SPAR-Bench.tsv' + output_path = os.path.join(output_dir, output_name) + df.to_csv(output_path, sep='\t', index=False) + print(f"✅ Saved to {output_path}") + + +if __name__ == '__main__': + parser = argparse.ArgumentParser() + parser.add_argument('--dataset', default='jasonzhango/SPAR-Bench') + parser.add_argument('--output_dir', default='./data') + args = parser.parse_args() + convert_spar_bench_to_tsv(args.dataset, args.output_dir) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/scripts/run.sh b/evaluation/cosmos3/reasoner/vlmevalkit/scripts/run.sh new file mode 100644 index 00000000..5dab509c --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/scripts/run.sh @@ -0,0 +1,4 @@ +#!/bin/bash +set -x +export GPU=$(nvidia-smi --list-gpus | wc -l) +torchrun --nproc-per-node=$GPU run.py ${@:1} \ No newline at end of file diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/scripts/run_endpoint_bench.sh b/evaluation/cosmos3/reasoner/vlmevalkit/scripts/run_endpoint_bench.sh new file mode 100755 index 00000000..64ee12d4 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/scripts/run_endpoint_bench.sh @@ -0,0 +1,286 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Run VLMEvalKit benchmark against a custom inference endpoint. +# Supports running a single dataset or all datasets with --all. + +# dataset_name:class_name +ALL_DATASETS=( + "MMMU_DEV_VAL:MMMUDataset" + "MathVista_MINI:MathVista" + "MathVision:MathVision" + "MMBench_DEV_EN:ImageMCQDataset" + "MMBench_DEV_EN_V11:ImageMCQDataset" + "HallusionBench:ImageYORNDataset" + "InfoVQA_VAL:ImageVQADataset" + "DocVQA_VAL:ImageVQADataset" + "AI2D_TEST:ImageMCQDataset" + "CountBenchQA:CountBenchQA" + "CosmosERQA:CosmosERQA" + "OCRBench_v2:OCRBench_v2" +) + +usage() { + cat <<'EOF' +Usage: ./run_endpoint_bench.sh --endpoint URL --model MODEL (--dataset DATASET | --all) [OPTIONS] + +Required: + --endpoint URL Full chat completions URL (e.g. https://...lepton.run/v1/chat/completions) + --model MODEL Model name served at the endpoint (e.g. Qwen3-VL-8B-Instruct) + --dataset DATASET Dataset name (e.g. CountBenchQA) + --all Run all built-in datasets + +Optional: + --dataset-class CLASS Dataset class name (e.g. ImageVQADataset; default: auto-detect or same as dataset) + --temperature FLOAT Sampling temperature (default: 0) + --max-tokens INT Max tokens to generate (default: 16384) + --presence-penalty FLOAT Penalize repeated tokens to reduce repetitive reasoning (default: not set) + --retry INT Number of retries (default: 10) + --timeout INT Request timeout in seconds (default: 300) + --enable-thinking Enable model thinking/reasoning (default: disabled) + --work-dir DIR Output directory (default: ./outputs) + --api-nproc INT Parallel API calls (default: 4) + --nframe INT Number of frames for dataset-side video extraction (override dataset default) + --fps FLOAT FPS for dataset-side video extraction (override dataset default) + --model-nframes INT Number of frames for model-side video processing (for VIDEO_LLM models) + --model-fps FLOAT FPS for model-side video processing (for VIDEO_LLM models) + --model-max-frames INT Max frames cap for model-side video processing + --run-name NAME Custom name for the model entry in config (default: derived from model name) + --keep-config Do not delete the temp config file after run + +Environment: + COSMOS_API_KEY API key for the inference endpoint (required) + OPENAI_API_KEY API key for LLM judge (required by some datasets) + OPENAI_API_BASE Custom base URL for LLM judge (optional) + NGC_API_KEY NGC API key for downloading datasets from DSS (required by some datasets) + NVDATASET_TENANTID Tenant ID for NVIDIA Data Services (required by some datasets) +EOF + exit 1 +} + +# Run a single benchmark. Args: dataset_name, class_name +run_one() { + local dataset="$1" + local dataset_class="$2" + + # Resolve paths relative to this script (which lives alongside run.py) + local script_dir + script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + + local config_file + config_file=$(mktemp "${script_dir}/tmp_config_XXXXXX.json") + + # Build the data section with optional video fields + local data_json="{\"class\": \"${dataset_class}\", \"dataset\": \"${dataset}\"" + if [[ -n "$NFRAME" ]]; then + data_json="${data_json}, \"nframe\": ${NFRAME}" + fi + if [[ -n "$FPS" ]]; then + data_json="${data_json}, \"fps\": ${FPS}" + fi + data_json="${data_json}}" + + # Build model-side video processing kwargs (independent of dataset config) + local model_video_kwargs="" + if [[ -n "$MODEL_NFRAMES" ]]; then + model_video_kwargs="${model_video_kwargs}, \"nframes\": ${MODEL_NFRAMES}" + fi + if [[ -n "$MODEL_FPS" ]]; then + model_video_kwargs="${model_video_kwargs}, \"fps\": ${MODEL_FPS}" + fi + if [[ -n "$MODEL_MAX_FRAMES" ]]; then + model_video_kwargs="${model_video_kwargs}, \"max_frames\": ${MODEL_MAX_FRAMES}" + fi + + cat > "$config_file" <= len(final.iloc[0].keys()): + print(tabulate(final)) + else: + print(tabulate(final.T)) + +if __name__ == '__main__': + args = parse_args() + if args.data == []: + args.data = list(SUPPORTED_DATASETS) + gen_table(args.model, args.data) \ No newline at end of file diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/scripts/visualize.ipynb b/evaluation/cosmos3/reasoner/vlmevalkit/scripts/visualize.ipynb new file mode 100644 index 00000000..84d08dcf --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/scripts/visualize.ipynb @@ -0,0 +1,266 @@ +{ + "cells": [ + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "import json\n", + "import copy as cp\n", + "import numpy as np\n", + "import matplotlib.pyplot as plt\n", + "import matplotlib.font_manager as fm\n", + "\n", + "def download_file(url, filename=None):\n", + " from urllib.request import urlretrieve\n", + " if filename is None:\n", + " filename = url.split('/')[-1]\n", + " urlretrieve(url, filename)\n", + "\n", + "font_URL = 'http://opencompass.openxlab.space/utils/Fonts/segoepr.ttf'\n", + "download_file(font_URL)\n", + "\n", + "font12 = fm.FontProperties(fname='segoepr.ttf', size=12)\n", + "font15 = fm.FontProperties(fname='segoepr.ttf', size=15, weight='bold')\n", + "font18 = fm.FontProperties(fname='segoepr.ttf', size=18, weight='bold')\n", + "\n", + "DATA_URL = 'http://opencompass.openxlab.space/utils/OpenVLM.json'\n", + "download_file(DATA_URL)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def pre_normalize(raw_data, labels):\n", + " data_list = cp.deepcopy(raw_data)\n", + " minimum, maximum, max_range, range_map = {}, {}, 0, {}\n", + " for lb in labels:\n", + " minimum[lb] = min([x[lb] for x in data_list])\n", + " maximum[lb] = max([x[lb] for x in data_list])\n", + " max_range = max(max_range, maximum[lb] - minimum[lb])\n", + " max_range *= 1.25\n", + " for lb in labels:\n", + " mid = (minimum[lb] + maximum[lb]) / 2\n", + " new_range = (mid - max_range / 2, mid + max_range / 2) if (mid + max_range / 2) < 100 else (100 - max_range, 100)\n", + " range_map[lb] = new_range\n", + " for item in data_list:\n", + " assert new_range[0] <= item[lb] <= new_range[1]\n", + " item[lb] = (item[lb] - new_range[0]) / max_range * 100\n", + " return data_list, range_map\n", + "\n", + "# solve the problem that some benchmark score is too high and out of range\n", + "def log_normalize(raw_data, labels):\n", + " data_list = cp.deepcopy(raw_data)\n", + " minimum, maximum, max_range, range_map = {}, {}, 0, {}\n", + " for lb in labels:\n", + " minimum[lb] = min([np.log(x[lb]) for x in data_list])\n", + " maximum[lb] = max([np.log(x[lb]) for x in data_list])\n", + " max_range = max(max_range, maximum[lb] - minimum[lb])\n", + " max_range *= 1.005\n", + " for lb in labels:\n", + " mid = (minimum[lb] + maximum[lb]) / 2\n", + " new_range = (mid - max_range / 2, mid + max_range / 2) if (mid + max_range / 2) < 100 else (100 - max_range, 100)\n", + " range_map[lb] = new_range\n", + " for item in data_list:\n", + " assert new_range[0] <= np.log(item[lb]) <= new_range[1]\n", + " item[lb] = (np.log(item[lb]) - new_range[0]) / max_range * 100\n", + " return data_list, range_map" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "# Draw MMBench Radar Graph\n", + "data = json.loads(open('OpenVLM.json').read())['results']\n", + "models = list(data)\n", + "print(models)\n", + "\n", + "# model2vis = [\n", + "# 'GPT-4v (detail: low)', 'GeminiProVision', 'Qwen-VL-Plus', \n", + "# 'InternLM-XComposer2-VL', 'LLaVA-v1.5-13B', 'CogVLM-17B-Chat',\n", + "# 'mPLUG-Owl2', 'Qwen-VL-Chat', 'IDEFICS-80B-Instruct'\n", + "# ]\n", + "\n", + "model2vis = [\n", + " # 'GPT-4v (detail: low)', 'GeminiProVision', 'InternLM-XComposer2-VL', \n", + " 'GPT-4v (1106, detail-low)', 'Gemini-1.0-Pro', 'Gemini-1.5-Pro', #'Gemini-1.5-Flash', 'Qwen-VL-Plus', \n", + " 'InternLM-XComposer2', 'LLaVA-v1.5-13B', 'CogVLM-17B-Chat',\n", + " 'mPLUG-Owl2', 'Qwen-VL-Chat', 'IDEFICS-80B-Instruct'\n", + "]\n", + "\n", + "colors = [\n", + " '#1f77b4', '#ff7f0e', '#2ca02c', '#d62728', '#9467bd', '#8c564b', \n", + " '#e377c2', '#7f7f7f', '#bcbd22'\n", + "]" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from collections import defaultdict\n", + "\n", + "split = 'MMBench_TEST_EN'\n", + "# data_sub = {k: v[split] for k, v in data.items()}\n", + "data_sub = {k: defaultdict(int, v)[split] for k, v in data.items()}\n", + "# solve the problem that some model lack the evaluation of MMBench_TEST_EN\n", + "\n", + "labels = list(data_sub[model2vis[0]])\n", + "labels.remove('Overall')\n", + "num_vars = len(labels)\n", + "\n", + "raw_data = [data_sub[m] for m in model2vis]\n", + "data_list, range_map = pre_normalize(raw_data, labels)\n", + "\n", + "alpha = 0.25\n", + "angles = np.linspace(0, 2 * np.pi, num_vars, endpoint=False).tolist()\n", + "angles_deg = np.linspace(0, 360, num_vars, endpoint=False).tolist()\n", + "fig, ax_base = plt.subplots(nrows=1, ncols=1, figsize=(10, 10), subplot_kw=dict(polar=True))\n", + "\n", + "for i in range(len(data_list)):\n", + " item = data_list[i]\n", + " model_name = model2vis[i]\n", + " color = colors[i]\n", + " tmp_angles = angles[:] + [angles[0]]\n", + " tmp_values = [item[lb] for lb in labels] + [item[labels[0]]]\n", + " ax_base.plot(tmp_angles, tmp_values, color=color, linewidth=1, linestyle='solid', label=model_name)\n", + " ax_base.fill(tmp_angles, tmp_values, color=color, alpha=alpha)\n", + " \n", + "angles += [angles[0]]\n", + "ax_base.set_ylim(0, 100)\n", + "ax_base.set_yticks([40, 60, 80, 100])\n", + "ax_base.set_yticklabels([''] * 4)\n", + "\n", + "ax_base.tick_params(pad=25)\n", + "ax_base.set_xticks(angles[:-1])\n", + "ax_base.set_xticklabels(labels, fontproperties=font18)\n", + "\n", + "leg = ax_base.legend(loc='center right', bbox_to_anchor=(1.6, 0.5), prop=font15, ncol=1, frameon=True, labelspacing=1.2)\n", + "for line in leg.get_lines():\n", + " line.set_linewidth(2.5)\n", + "\n", + "cx, cy, sz = 0.44, 0.435, 0.34\n", + "axes = [fig.add_axes([cx - sz, cy - sz, cx + sz, cy + sz], projection='polar', label='axes%d' % i) for i in range(num_vars)]\n", + " \n", + "for ax, angle, label in zip(axes, angles_deg, labels):\n", + " ax.patch.set_visible(False)\n", + " ax.grid(False)\n", + " ax.xaxis.set_visible(False)\n", + " cur_range = range_map[label]\n", + " label_list = [cur_range[0] + (cur_range[1] - cur_range[0]) / 5 * i for i in range(2, 6)]\n", + " label_list = [f'{x:.1f}' for x in label_list]\n", + " ax.set_rgrids(range(40, 120, 20), angle=angle, labels=label_list, font_properties=font12)\n", + " ax.spines['polar'].set_visible(False)\n", + " ax.set_ylim(0, 100)\n", + "\n", + "title_text = f'{len(model2vis)} Representative VLMs on MMBench Test.'\n", + "plt.figtext(.7, .95, title_text, fontproperties=font18, ha='center')\n", + "plt.show()" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "labels = ['SEEDBench_IMG', 'CCBench', 'MMBench_TEST_EN', 'MMBench_TEST_CN', 'MME', 'MMVet', 'MMMU_VAL', 'MathVista', 'HallusionBench', 'LLaVABench']\n", + "num_vars = len(labels)\n", + "\n", + "raw_data = [{k: data[m][k]['Overall'] for k in labels} for m in model2vis]\n", + "data_list, range_map = pre_normalize(raw_data, labels)\n", + "\n", + "alpha = 0.25\n", + "angles = np.linspace(0, 2 * np.pi, num_vars, endpoint=False).tolist()\n", + "angles_deg = np.linspace(0, 360, num_vars, endpoint=False).tolist()\n", + "fig, ax_base = plt.subplots(nrows=1, ncols=1, figsize=(10, 10), subplot_kw=dict(polar=True))\n", + "\n", + "for i in range(len(data_list)):\n", + " item = data_list[i]\n", + " model_name = model2vis[i]\n", + " color = colors[i]\n", + " tmp_angles = angles[:] + [angles[0]]\n", + " tmp_values = [item[lb] for lb in labels] + [item[labels[0]]]\n", + " ax_base.plot(tmp_angles, tmp_values, color=color, linewidth=1, linestyle='solid', label=model_name)\n", + " ax_base.fill(tmp_angles, tmp_values, color=color, alpha=alpha)\n", + " \n", + "angles += [angles[0]]\n", + "ax_base.set_ylim(0, 100)\n", + "ax_base.set_yticks([40, 60, 80, 100])\n", + "ax_base.set_yticklabels([''] * 4)\n", + "\n", + "ax_base.tick_params(pad=15)\n", + "ax_base.set_xticks(angles[:-1])\n", + "ax_base.set_xticklabels(labels, fontproperties=font18)\n", + "\n", + "dataset_map = {\n", + " 'MMBench_TEST_EN': 'MMBench (Test)', \n", + " 'MMBench_TEST_CN': 'MMBenchCN (Test)', \n", + " 'MathVista': 'MathVista (TestMini)', \n", + " 'MMMU_VAL': 'MMMU (Val)'\n", + "}\n", + "for i, label in enumerate(ax_base.get_xticklabels()):\n", + " x,y = label.get_position()\n", + " text = label.get_text()\n", + " text = dataset_map[text] if text in dataset_map else text\n", + " lab = ax_base.text(x, y, text, transform=label.get_transform(),\n", + " ha=label.get_ha(), va=label.get_va(), font_properties=font15)\n", + " lab.set_rotation(360 / num_vars * i + 270)\n", + " labels.append(lab)\n", + "ax_base.set_xticklabels([])\n", + "\n", + "leg = ax_base.legend(loc='center right', bbox_to_anchor=(1.6, 0.5), prop=font15, ncol=1, frameon=True, labelspacing=1.2)\n", + "for line in leg.get_lines():\n", + " line.set_linewidth(2.5)\n", + "\n", + "cx, cy, sz = 0.44, 0.435, 0.34\n", + "axes = [fig.add_axes([cx - sz, cy - sz, cx + sz, cy + sz], projection='polar', label='axes%d' % i) for i in range(num_vars)]\n", + " \n", + "for ax, angle, label in zip(axes, angles_deg, labels):\n", + " ax.patch.set_visible(False)\n", + " ax.grid(False)\n", + " ax.xaxis.set_visible(False)\n", + " cur_range = range_map[label]\n", + " label_list = [cur_range[0] + (cur_range[1] - cur_range[0]) / 5 * i for i in range(2, 6)]\n", + " label_list = [f'{x:.1f}' for x in label_list]\n", + " ax.set_rgrids(range(40, 120, 20), angle=angle, labels=label_list, font_properties=font12)\n", + " ax.spines['polar'].set_visible(False)\n", + " ax.set_ylim(0, 100)\n", + "\n", + "title_text = f'{len(model2vis)} Representative VLMs on {num_vars} Benchmarks in OpenCompass Multi-Modal Leaderboard.'\n", + "plt.figtext(.7, .95, title_text, fontproperties=font18, ha='center')\n", + "plt.show()" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "base", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.5" + } + }, + "nbformat": 4, + "nbformat_minor": 2 +} diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/setup.cfg b/evaluation/cosmos3/reasoner/vlmevalkit/setup.cfg new file mode 100644 index 00000000..8a683f39 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/setup.cfg @@ -0,0 +1,23 @@ +[flake8] +max-line-length = 99 +ignore = W503,E203,E741 +per-file-ignores = + run.py: E402 + vlmeval/vlm/__init__.py: F401, E402 + vlmeval/dataset/video_dataset_config.py: F405, F403 + +[yapf] +based_on_style = pep8 +blank_line_before_nested_class_or_def = true +split_before_expression_after_opening_paren = true +split_penalty_import_names = 0 +split_penalty_after_opening_bracket = 800 +column_limit = 99 + + +[isort] +line_length = 99 +multi_line_output = 0 +known_first_party = vlmeval +no_lines_before = STDLIB,LOCALFOLDER +sections = FUTURE,STDLIB,THIRDPARTY,FIRSTPARTY,LOCALFOLDER diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/setup.py b/evaluation/cosmos3/reasoner/vlmevalkit/setup.py new file mode 100644 index 00000000..253ef4cf --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/setup.py @@ -0,0 +1,123 @@ +import re +import sys +from os.path import exists + +from setuptools import find_packages, setup + + +def parse_requirements(fname='requirements.txt', with_version=True): + """Parse the package dependencies listed in a requirements file but strips + specific versioning information. + + Args: + fname (str): path to requirements file + with_version (bool, default=False): if True include version specs + + Returns: + List[str]: list of requirements items + + CommandLine: + python -c "import setup; print(setup.parse_requirements())" + """ + + require_fpath = fname + + def parse_line(line): + """Parse information from a line in a requirements text file.""" + if line.startswith('-r '): + # Allow specifying requirements in other files + target = line.split(' ')[1] + for info in parse_require_file(target): + yield info + else: + info = {'line': line} + if line.startswith('-e '): + info['package'] = line.split('#egg=')[1] + elif '@git+' in line: + info['package'] = line + else: + # Remove versioning from the package + pat = '(' + '|'.join(['>=', '==', '>']) + ')' + parts = re.split(pat, line, maxsplit=1) + parts = [p.strip() for p in parts] + + info['package'] = parts[0] + if len(parts) > 1: + op, rest = parts[1:] + if ';' in rest: + # Handle platform specific dependencies + # https://setuptools.readthedocs.io/en/latest/setuptools.html#declaring-platform-specific-dependencies + version, platform_deps = map(str.strip, + rest.split(';')) + info['platform_deps'] = platform_deps + else: + version = rest # NOQA + info['version'] = (op, version) + yield info + + def parse_require_file(fpath): + with open(fpath, 'r') as f: + for line in f.readlines(): + line = line.strip() + if line and not line.startswith('#'): + for info in parse_line(line): + yield info + + def gen_packages_items(): + if exists(require_fpath): + for info in parse_require_file(require_fpath): + parts = [info['package']] + if with_version and 'version' in info: + parts.extend(info['version']) + if not sys.version.startswith('3.4'): + # apparently package_deps are broken in 3.4 + platform_deps = info.get('platform_deps') + if platform_deps is not None: + parts.append(';' + platform_deps) + item = ''.join(parts) + yield item + + packages = list(gen_packages_items()) + return packages + + +with open('README.md', encoding="utf-8") as f: + readme = f.read() + + +def do_setup(): + setup( + name='vlmeval', + version='0.1.0', + description='OpenCompass VLM Evaluation Kit', + author='Haodong Duan', + author_email='dhd.efz@gmail.com', + maintainer='Haodong Duan', + maintainer_email='dhd.efz@gmail.com', + long_description=readme, + long_description_content_type='text/markdown', + cmdclass={}, + install_requires=parse_requirements('requirements.txt'), + setup_requires=[], + python_requires='>=3.7.0', + packages=find_packages(exclude=[ + 'test*', + 'paper_test*', + ]), + keywords=['AI', 'NLP', 'in-context learning'], + entry_points={ + 'console_scripts': ['vlmutil = vlmeval:cli'] + }, + classifiers=[ + 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Intended Audience :: Developers', + 'Intended Audience :: Education', + 'Intended Audience :: Science/Research', + ]) + + +if __name__ == '__main__': + do_setup() diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/__init__.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/__init__.py new file mode 100644 index 00000000..aefba9a0 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/__init__.py @@ -0,0 +1,43 @@ +import ssl + +# Temporarily bypass SSL certificate verification to download files from oss. +ssl._create_default_https_context = ssl._create_unverified_context + + +def load_env(): + import logging + import os + from pathlib import Path + logging.basicConfig( + format='[%(asctime)s] %(levelname)s - %(filename)s: %(funcName)s - %(lineno)d: %(message)s', + datefmt='%Y-%m-%d %H:%M:%S') + pth = Path(__file__).parent.parent / '.env' + if not pth.exists(): + logging.error(f'Did not detect the .env file at {pth}, failed to load.') + return + + from dotenv import dotenv_values + values = dotenv_values(str(pth)) + for k, v in values.items(): + if v is not None and len(v): + os.environ[k] = v + logging.info(f'API Keys successfully loaded from {pth}') + + +load_env() + +try: + import torch # noqa: F401 +except ImportError: + pass + +# from .api import * # noqa: F401, F403, E402 +# from .config import * # noqa: F401, F403, E402 +# from .dataset import * # noqa: F401, F403, E402 +# from .smp import * # noqa: F401, F403, E402 +from .tools import cli # noqa: F401, E402 + +# from .utils import * # noqa: F401, F403, E402 +# from .vlm import * # noqa: F401, F403, E402 + +__version__ = '0.2rc1' diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/__init__.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/__init__.py new file mode 100644 index 00000000..c77e2c38 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/__init__.py @@ -0,0 +1,58 @@ +from .arm_thinker import ARM_thinker +from .bailingmm import bailingMMAPI +from .bedrock import BedrockAPI +from .bluelm_api import BlueLM_API, BlueLMWrapper +from .claude import Claude3V, Claude_Wrapper +from .cloudwalk import CWWrapper +from .cosmos_reason import (CosmosReason1, CosmosReason1Think, CosmosReason2, CosmosReason2Think, + Qwen3VLThink) +from .doubao_vl_api import DoubaoVL +from .gcp_vertex import GCPVertexAPI +from .gemini import Gemini, GeminiWrapper +from .glm_vision import GLMVisionAPI +from .gpt import GPT4V, OpenAIWrapper +from .hf_chat_model import HFChatModel +from .hunyuan import HunyuanVision +from .jt_vl_chat import JTVLChatAPI +from .jt_vl_chat_mini import JTVLChatAPI_2B, JTVLChatAPI_Mini +from .kimivl_api import KimiVLAPI, KimiVLAPIWrapper +from .lmdeploy import LMDeployAPI, LMDeployWrapper +from .lvs_service import LVS_service +from .mimo import MiMo +from .minimax_api import MiniMaxAPI +from .mug_u import MUGUAPI +from .nemotron import Nemotron +from .nv_gemini import NVGemini +from .openai_sdk import OpenAISDKWrapper +from .qwen_api import QwenAPI +from .qwen_vl_api import Qwen2VLAPI, QwenVLAPI, QwenVLWrapper +from .rbdashmm_chat3_5_api import RBdashMMChat3_5_38B_API, RBdashMMChat3_78B_API +from .rbdashmm_chat3_api import RBdashChat3_5_API, RBdashMMChat3_API +from .reka import Reka +from .sensechat_vision import SenseChatVisionAPI, SenseChatVisionV2API +from .siliconflow import SiliconFlowAPI, TeleMMAPI +from .taichu import TaichuVLAPI, TaichuVLRAPI +from .taiyi import TaiyiAPI +from .telemm import TeleMM2_API +from .telemm_thinking import TeleMM2Thinking_API +from .together import TogetherAPI +from .video_chat_online_v2 import VideoChatOnlineV2API + +__all__ = [ + 'OpenAIWrapper', 'HFChatModel', 'GeminiWrapper', 'GPT4V', 'Gemini', 'QwenVLWrapper', + 'QwenVLAPI', 'QwenAPI', 'Claude3V', 'Claude_Wrapper', 'Reka', 'GLMVisionAPI', 'CWWrapper', + 'SenseChatVisionAPI', 'SenseChatVisionV2API', 'HunyuanVision', 'Qwen2VLAPI', 'BlueLMWrapper', 'BlueLM_API', + 'JTVLChatAPI', 'JTVLChatAPI_Mini', 'JTVLChatAPI_2B', 'bailingMMAPI', 'TaiyiAPI', 'TeleMMAPI', + 'SiliconFlowAPI', 'LMDeployAPI', 'ARM_thinker', 'OpenAISDKWrapper', 'LMDeployWrapper', + 'TaichuVLAPI', 'TaichuVLRAPI', 'DoubaoVL', "MUGUAPI", 'KimiVLAPIWrapper', 'KimiVLAPI', + 'RBdashMMChat3_API', 'RBdashChat3_5_API', 'RBdashMMChat3_78B_API', 'RBdashMMChat3_5_38B_API', + 'VideoChatOnlineV2API', 'TeleMM2_API', 'TeleMM2Thinking_API', 'TogetherAPI', 'GCPVertexAPI', + 'BedrockAPI', 'SenseChatVisionV2API', 'MiniMaxAPI', + 'CosmosReason1', 'CosmosReason1Think', + 'CosmosReason2', 'CosmosReason2Think', + 'Qwen3VLThink', + 'LVS_service', + 'Nemotron', + 'MiMo', + 'NVGemini', +] diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/adapters/__init__.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/adapters/__init__.py new file mode 100644 index 00000000..aab55d18 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/adapters/__init__.py @@ -0,0 +1,13 @@ +from .base import ModelAdapter, build_adapter, get_adapter_registry, register_adapter +from .cogvlm2 import CogVLM2Adapter +from .interns1_1 import InternS1_1NoThinkAdapter, InternS1_1ThinkAdapter +from .internvl2 import InternVL2Adapter +from .internvl3 import InternVL3Adapter +from .qwen3 import Qwen3Adapter + +__all__ = [ + 'ModelAdapter', 'register_adapter', 'build_adapter', 'get_adapter_registry', + 'InternVL2Adapter', 'InternVL3Adapter', + 'InternS1_1NoThinkAdapter', 'InternS1_1ThinkAdapter', + 'CogVLM2Adapter', 'Qwen3Adapter', +] diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/adapters/base.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/adapters/base.py new file mode 100644 index 00000000..9efeaf50 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/adapters/base.py @@ -0,0 +1,78 @@ +_ADAPTER_REGISTRY = {} + + +def register_adapter(name, factory=None): + """Register an adapter class/factory under the given name. + + Can be used as a decorator:: + + @register_adapter('my_adapter') + class MyAdapter(ModelAdapter): ... + + Or called directly for variants:: + + register_adapter('my_adapter-variant', partial(MyAdapter, flag=True)) + """ + if factory is None: + def decorator(cls): + _ADAPTER_REGISTRY[name] = cls + return cls + return decorator + _ADAPTER_REGISTRY[name] = factory + return factory + + +def build_adapter(name, **kwargs): + """Instantiate a registered adapter by name.""" + if name not in _ADAPTER_REGISTRY: + available = list(_ADAPTER_REGISTRY.keys()) + raise KeyError(f"Adapter '{name}' not found in registry. Available: {available}") + return _ADAPTER_REGISTRY[name](**kwargs) + + +def get_adapter_registry(): + """Return a copy of the global adapter registry.""" + return dict(_ADAPTER_REGISTRY) + + +class ModelAdapter: + """Base class for model-specific input/output processing hooks. + + Subclasses override only the methods they need. All methods have + sensible no-op defaults except ``build_prompt``, which raises + ``NotImplementedError``. + + The ``dump_image_func`` attribute is injected by the wrapper at + evaluation time via ``set_dump_image``. + """ + + def dump_image(self, line, dataset): + """Return image path(s) for this sample.""" + return self.dump_image_func(line) + + def override_model_args(self, dataset) -> dict: + """Return extra generation kwargs for this dataset. + + Recognised keys: ``system_prompt``, ``temperature``, etc. + """ + return {} + + def use_custom_prompt(self, dataset: str, system_prompt=None) -> bool: + """Whether to use this adapter's ``build_prompt`` for this dataset.""" + return False + + def build_prompt(self, line, dataset=None): + """Construct the message list for this sample.""" + raise NotImplementedError + + def process_inputs(self, inputs, dataset=None): + """Transform the inputs list before HTTP message formatting.""" + return inputs + + def process_payload(self, payload: dict, dataset=None) -> dict: + """Modify the HTTP payload dict before it is sent.""" + return payload + + def postprocess(self, response: str, dataset=None) -> str: + """Post-process the raw response string.""" + return response diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/adapters/cogvlm2.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/adapters/cogvlm2.py new file mode 100644 index 00000000..b83bb961 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/adapters/cogvlm2.py @@ -0,0 +1,50 @@ +import string + +import pandas as pd + +from vlmeval.dataset import DATASET_TYPE +from vlmeval.smp import cn_string +from .base import ModelAdapter, register_adapter + + +@register_adapter('cogvlm2') +class CogVLM2Adapter(ModelAdapter): + + def dump_image(self, line, dataset): + return self.dump_image_func(line) + + def use_custom_prompt(self, dataset, system_prompt=None): + assert dataset is not None + return DATASET_TYPE(dataset) in 'MCQ' + + def build_prompt(self, line, dataset=None): + assert dataset is None or isinstance(dataset, str) + assert self.use_custom_prompt(dataset) + tgt_path = self.dump_image(line, dataset) + + if dataset is not None and DATASET_TYPE(dataset) == 'MCQ': + question = line['question'] + hint = line['hint'] if ('hint' in line and not pd.isna(line['hint'])) else None + if hint is not None: + question = hint + '\n' + question + + option_candidate = string.ascii_uppercase + options = { + cand: line[cand] + for cand in option_candidate + if cand in line and not pd.isna(line[cand]) + } + for key, item in options.items(): + question += f'\n{key}. {item}' + prompt = question + + if not cn_string(prompt): + prompt = prompt + '\n' + "Answer with the option's letter from the given choices directly." + else: + prompt = prompt + '\n' + '请直接回答选项字母。' + else: + prompt = line['question'] + + message = [dict(type='text', value=prompt)] + message.extend([dict(type='image', value=p) for p in tgt_path]) + return message diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/adapters/interns1_1.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/adapters/interns1_1.py new file mode 100644 index 00000000..79dc52d1 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/adapters/interns1_1.py @@ -0,0 +1,296 @@ +import io +import os +import string + +import pandas as pd +from PIL import Image + +from vlmeval.dataset import DATASET_MODALITY, DATASET_TYPE +from vlmeval.smp import LMUDataRoot, get_logger, listinstr +from .base import ModelAdapter, register_adapter + +logger = get_logger(__name__) + + +@register_adapter('interns1_1_no_think') +class InternS1_1NoThinkAdapter(ModelAdapter): + + def __init__(self): + self.cot_prompt = None + self.screen_parse = True + self.split_think = True + + def dump_image(self, line, dataset): + return self.dump_image_func(line) + + def use_custom_prompt(self, dataset, system_prompt=None): + if dataset in [ + 'atomic_dataset', 'electro_dataset', 'mechanics_dataset', + 'optics_dataset', 'quantum_dataset', 'statistics_dataset', + 'Physics', 'SFE', 'SFE-zh', 'IPhO_2025', 'XLRS-Bench-lite', + 'OmniEarth-Bench', + ]: + return False + elif listinstr([ + 'MMDU', 'MME-RealWorld', 'MME-RealWorld-CN', 'WeMath_COT', + 'MMAlignBench', 'ScreenSpot', 'ChartQAPro', 'MMMU', + ], dataset): + return False + elif DATASET_MODALITY(dataset) == 'VIDEO': + return False + elif DATASET_TYPE(dataset) in ['Y/N', 'MCQ', 'VQA', 'GUI']: + return True + return False + + def build_prompt(self, line, dataset=None): + from pathlib import Path + + import yaml + + from vlmeval.dataset import build_dataset, infer_dataset_basename + from vlmeval.vlm.internvl.utils import (build_mcq_cot_prompt, build_multi_choice_prompt, + build_qa_cot_prompt, format_nav_prompt, + pile_action_history) + + assert self.use_custom_prompt(dataset) + + if listinstr(['ChartMimic'], dataset): + input_figure_path_rel = line['input_figure'] + ROOT = LMUDataRoot() + img_root = os.path.join(ROOT, 'images', 'ChartMimic') + tgt_path = [os.path.join(img_root, input_figure_path_rel)] + else: + tgt_path = self.dump_image(line, dataset) + + if DATASET_TYPE(dataset) == 'Y/N': + question = line['question'] + if listinstr(['MME'], dataset): + prompt = question + ' Answer the question using a single word or phrase.' + elif listinstr(['HallusionBench', 'AMBER'], dataset): + prompt = question + ' Please answer yes or no. Answer the question using a single word or phrase.' + else: + prompt = question + elif DATASET_TYPE(dataset) == 'MCQ': + prompt = build_multi_choice_prompt(line, dataset) + if os.getenv('USE_COT') == '1': + prompt = build_mcq_cot_prompt(line, prompt, self.cot_prompt) + elif DATASET_TYPE(dataset) == 'VQA': + question = line['question'] + if listinstr(['LLaVABench', 'WildVision'], dataset): + prompt = question + '\nAnswer this question in detail.' + elif listinstr(['OCRVQA', 'TextVQA', 'ChartQA', 'DocVQA', 'InfoVQA', 'OCRBench', + 'DUDE', 'SLIDEVQA', 'GQA', 'MMLongBench_DOC'], dataset): + prompt = question + '\nAnswer the question using a single word or phrase.' + elif listinstr(['MathVista', 'MathVision', 'VCR', 'MTVQA', 'MMVet', 'MathVerse', + 'MMDU', 'CRPE', 'MIA-Bench', 'MM-Math', 'DynaMath', 'QSpatial', + 'WeMath', 'LogicVista', 'MM-IFEval', 'ChartMimic'], dataset): + prompt = question + if os.getenv('USE_COT') == '1': + prompt = build_qa_cot_prompt(line, prompt, self.cot_prompt) + else: + prompt = question + '\nAnswer the question using a single word or phrase.' + elif DATASET_TYPE(dataset) == 'GUI': + vlmeval_root = Path(__file__).parent.parent.parent + tmpl_path = os.path.join(vlmeval_root, 'vlm/internvl/gui_template.yaml') + with open(tmpl_path, 'r') as f: + GUI_TEMPLATE = yaml.load(f, Loader=yaml.FullLoader) + ds_basename = infer_dataset_basename(dataset) + ds = build_dataset(dataset, skeleton=True) + action_space = ds.get_action_space() + traj_dict = ds.get_trajectory(line) + prompt_config = GUI_TEMPLATE[ds_basename] + if 'history' in prompt_config['placeholders']: + traj_dict['history'] = pile_action_history(traj_dict['history']) + prompt = format_nav_prompt( + ( + 'Please provide the bounding box coordinate of the region this sentence describes: {task}' # noqa: E501 + if self.screen_parse + else prompt_config['template'] + ), + prompt_config['placeholders'], + action_space=action_space, + **traj_dict, + ) + else: + prompt = line['question'] + if os.getenv('USE_COT') == '1': + prompt = build_qa_cot_prompt(line, prompt, self.cot_prompt) + + message = [dict(type='image', value=s) for s in tgt_path] + message.append(dict(type='text', value=prompt)) + return message + + def process_inputs(self, inputs, dataset=None): + from vlmeval.vlm.internvl.utils import build_video_prompt, reorganize_prompt + + image_items = [x.copy() for x in inputs if x['type'] == 'image'] + image_num = len(image_items) + prompt = reorganize_prompt(inputs, image_num, dataset=dataset) + + if dataset is not None and DATASET_MODALITY(dataset) == 'VIDEO': + prompt = build_video_prompt(prompt, dataset) + + if listinstr(['MMMU'], dataset) and len(image_items) > 0: + image = Image.open(image_items[0]['value']) + image = image.resize((image.width * 2, image.height * 2), Image.BILINEAR) + tmp_image = io.BytesIO() + image.save(tmp_image, format='PNG') + image_items[0]['value'] = tmp_image + + prompt = prompt.replace('', '') + return [*image_items, dict(type='text', value=prompt)] + + def postprocess(self, response, dataset=None): + if self.split_think and '' in response and '' in response: + thinking, _, answer = response.partition('')[-1].partition('') + logger.info('-----------Thinking-----------\n' + f'{thinking}\n' + '------------------------------') + return answer + elif self.split_think and '' in response: + thinking, _, answer = response.partition('') + logger.info('-----------Thinking-----------\n' + f'{thinking}\n' + '------------------------------') + return answer + return response + + +@register_adapter('interns1_1_think') +class InternS1_1ThinkAdapter(ModelAdapter): + + def __init__(self): + self.cot_prompt = None + self.screen_parse = True + self.split_think = True + + def dump_image(self, line, dataset): + return self.dump_image_func(line) + + def use_custom_prompt(self, dataset, system_prompt=None): + if dataset in ['SFE', 'SFE-zh', 'IPhO_2025']: + return True + return False + + def build_prompt(self, line, dataset=None): + assert self.use_custom_prompt(dataset) + if listinstr(['SFE'], dataset): + return self._build_sfe_prompt(line, dataset) + elif listinstr(['IPhO_2025'], dataset): + return self._build_hipho_prompt(line, dataset) + else: + raise ValueError(f'Custom prompt not supported for dataset: {dataset}') + + def _build_sfe_prompt(self, line, dataset): + MCQ_PROMPT = 'You are an expert in {discipline} and need to solve the following question.' + EXACT_MATCH_PROMPT = 'You are an expert in {discipline} and need to solve the following question.' + OPEN_QUESTION_PROMPT = 'You are an expert in {discipline} and need to solve the following question.' + + tgt_path = self.dump_image(line, dataset) + question_type = line['question_type'] + field = line['category'] + question = line['question'] + + if question_type == 'exact_match': + prompt = EXACT_MATCH_PROMPT.format(discipline=field) + question = prompt + ' ' + question + elif question_type == 'mcq': + prompt = MCQ_PROMPT.format(discipline=field) + question = prompt + ' ' + question + if not pd.isna(line['A']): + question += '\nChoices are:\n' + for ch in string.ascii_uppercase[:15]: + if not pd.isna(line[ch]): + question += f'{ch}. {line[ch]}\n' + else: + break + elif question_type == 'open_ended': + prompt = OPEN_QUESTION_PROMPT.format(discipline=field) + question = prompt + ' ' + question + + prompt_segs = question.split('') + assert len(prompt_segs) == len(tgt_path) + 1 + msgs = [] + for i in range(len(tgt_path)): + text = prompt_segs[i].strip() + if text: + msgs.append(dict(type='text', value=text)) + msgs.append(dict(type='image', value=tgt_path[i])) + text = prompt_segs[-1].strip() + if text: + msgs.append(dict(type='text', value=text)) + return msgs + + def _build_hipho_prompt(self, line, dataset): + def safe_str(val): + return '' if pd.isna(val) or val == '' else str(val) + + context = safe_str(line.get('context', '')) + question = safe_str(line['question']) + information = safe_str(line.get('information', '')) + + SYSTEM_PROMPTS_EN = ( + 'Please answer the problem adhering to the following rules:\n' + '1. Please use LaTeX format to represent the variables and formulas ' + 'used in the solution process and results.\n' + '2. Please put the final answer(s) in \\boxed{}, note that the unit of ' + 'the answer should not be included in \\boxed{}.\n' + '3. If the problem requires multiple answers, list them in order, each in a separate \\boxed{}.\n' + 'Problem: Information:{information}\n' + 'Context:{context}\n' + 'Question: {problem}' + ) + formatted_prompt = ( + SYSTEM_PROMPTS_EN + .replace('{context}', context) + .replace('{problem}', question) + .replace('{information}', information) + ) + + msgs = [] + image_val = str(line.get('image', '')).strip() + if image_val and not image_val.startswith('NO_IMAGE_PLACEHOLDER_'): + tgt_path = self.dump_image(line, dataset) + if tgt_path and tgt_path != ['']: + if isinstance(tgt_path, list): + msgs.extend([dict(type='image', value=p) for p in tgt_path]) + else: + msgs.append(dict(type='image', value=tgt_path)) + + msgs.append(dict(type='text', value=formatted_prompt)) + return msgs + + def process_inputs(self, inputs, dataset=None): + from vlmeval.vlm.internvl.utils import build_video_prompt, reorganize_prompt + + image_items = [x.copy() for x in inputs if x['type'] == 'image'] + image_num = len(image_items) + prompt = reorganize_prompt(inputs, image_num, dataset=dataset) + + if dataset is not None and DATASET_MODALITY(dataset) == 'VIDEO': + prompt = build_video_prompt(prompt, dataset) + + if listinstr(['MMMU'], dataset) and len(image_items) > 0: + image = Image.open(image_items[0]['value']) + image = image.resize((image.width * 2, image.height * 2), Image.BILINEAR) + tmp_image = io.BytesIO() + image.save(tmp_image, format='PNG') + image_items[0]['value'] = tmp_image + + prompt = prompt.replace('', '') + return [*image_items, dict(type='text', value=prompt)] + + def postprocess(self, response, dataset=None): + if self.split_think and '' in response and '' in response: + thinking, _, answer = response.partition('')[-1].partition('') + logger.info('-----------Thinking-----------\n' + f'{thinking}\n' + '------------------------------') + return answer + elif self.split_think and '' in response: + thinking, _, answer = response.partition('') + logger.info('-----------Thinking-----------\n' + f'{thinking}\n' + '------------------------------') + return answer + return response diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/adapters/internvl2.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/adapters/internvl2.py new file mode 100644 index 00000000..81a3cb95 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/adapters/internvl2.py @@ -0,0 +1,166 @@ +import copy +from functools import partial + +from vlmeval.dataset import DATASET_MODALITY, DATASET_TYPE +from vlmeval.smp import LMUDataRoot, listinstr +from .base import ModelAdapter, register_adapter + + +@register_adapter('internvl2') +class InternVL2Adapter(ModelAdapter): + + def __init__(self, use_mpo_prompt=False): + self.use_mpo_prompt = use_mpo_prompt + self.cot_prompt = None + self.screen_parse = False + + def dump_image(self, line, dataset): + return self.dump_image_func(line) + + def use_custom_prompt(self, dataset, system_prompt=None): + assert dataset is not None + if dataset in [ + 'atomic_dataset', 'electro_dataset', 'mechanics_dataset', + 'optics_dataset', 'quantum_dataset', 'statistics_dataset', + 'Physics', 'SFE', 'SFE-zh', + 'XLRS-Bench-lite', 'OmniEarth-Bench', + ]: + return False + if listinstr(['MMDU', 'MME-RealWorld', 'MME-RealWorld-CN', 'WeMath_COT', 'MMAlignBench'], dataset): + return False + if system_prompt is not None and '' in system_prompt and listinstr( + ['MicroVQA', 'MSEarthMCQ', 'MMSci_DEV_MCQ', 'MMMU', 'VisuLogic'], dataset + ): + return False + if DATASET_MODALITY(dataset) == 'VIDEO': + return False + return True + + def build_prompt(self, line, dataset=None): + import os + from pathlib import Path + + import yaml + + from vlmeval.dataset import build_dataset, infer_dataset_basename + from vlmeval.vlm.internvl.utils import (build_mcq_cot_prompt, build_mpo_prompt, + build_multi_choice_prompt, build_qa_cot_prompt, + format_nav_prompt, pile_action_history) + + use_mpo_prompt = self.use_mpo_prompt and ( + getattr(self, 'use_cot', False) + or dataset in ['MMStar', 'HallusionBench', 'OCRBench'] + ) + + assert self.use_custom_prompt(dataset) + assert dataset is None or isinstance(dataset, str) + + if not listinstr(['ChartMimic'], dataset): + tgt_path = self.dump_image(line, dataset) + else: + input_figure_path_rel = line['input_figure'] + ROOT = LMUDataRoot() + img_root = os.path.join(ROOT, 'images', 'ChartMimic') + tgt_path = [os.path.join(img_root, input_figure_path_rel)] + + if dataset is not None and DATASET_TYPE(dataset) == 'Y/N': + question = line['question'] + if listinstr(['MME'], dataset): + prompt = question + ' Answer the question using a single word or phrase.' + elif listinstr(['HallusionBench', 'AMBER'], dataset): + prompt = question + ' Please answer yes or no. Answer the question using a single word or phrase.' + else: + prompt = question + elif dataset is not None and DATASET_TYPE(dataset) == 'MCQ': + prompt = build_multi_choice_prompt(line, dataset) + if os.getenv('USE_COT') == '1': + prompt = build_mcq_cot_prompt(line, prompt, self.cot_prompt) + elif dataset is not None and DATASET_TYPE(dataset) == 'VQA': + question = line['question'] + if listinstr(['LLaVABench', 'WildVision'], dataset): + prompt = question + '\nAnswer this question in detail.' + elif listinstr(['OCRVQA', 'TextVQA', 'ChartQA', 'DocVQA', 'InfoVQA', 'OCRBench', + 'DUDE', 'SLIDEVQA', 'GQA', 'MMLongBench_DOC'], dataset): + prompt = question + '\nAnswer the question using a single word or phrase.' + elif listinstr(['MathVista', 'MathVision', 'VCR', 'MTVQA', 'MMVet', 'MathVerse', + 'MMDU', 'CRPE', 'MIA-Bench', 'MM-Math', 'DynaMath', 'QSpatial', + 'WeMath', 'LogicVista', 'MM-IFEval', 'ChartMimic'], dataset): + prompt = question + if os.getenv('USE_COT') == '1': + prompt = build_qa_cot_prompt(line, prompt, self.cot_prompt) + else: + prompt = question + '\nAnswer the question using a single word or phrase.' + elif dataset is not None and DATASET_TYPE(dataset) == 'GUI': + vlmeval_root = Path(__file__).parent.parent.parent + with open(os.path.join(vlmeval_root, 'vlm/internvl/gui_template.yaml'), 'r') as f: + GUI_TEMPLATE = yaml.load(f, Loader=yaml.FullLoader) + ds_basename = infer_dataset_basename(dataset) + ds = build_dataset(dataset, skeleton=True) + action_space = ds.get_action_space() + traj_dict = ds.get_trajectory(line) + prompt_config = GUI_TEMPLATE[ds_basename] + if 'history' in prompt_config['placeholders']: + traj_dict['history'] = pile_action_history(traj_dict['history']) + prompt = format_nav_prompt( + ( + 'Please provide the bounding box coordinate of the region this sentence describes: {task}' # noqa: E501 + if self.screen_parse + else prompt_config['template'] + ), + prompt_config['placeholders'], + action_space=action_space, + **traj_dict, + ) + else: + prompt = line['question'] + if os.getenv('USE_COT') == '1': + prompt = build_qa_cot_prompt(line, prompt, self.cot_prompt) + + message = [dict(type='text', value=prompt)] + message.extend([dict(type='image', value=s) for s in tgt_path]) + + if use_mpo_prompt: + message = build_mpo_prompt(message, line, dataset) + return message + + def get_max_num(self, dataset): + if dataset is None: + return 6 + res_12_datasets = ['ChartQA_TEST', 'MMMU_DEV_VAL', 'MMMU_TEST', 'MME-RealWorld', + 'VCR_EN', 'VCR_ZH', 'OCRVQA'] + res_18_datasets = ['DocVQA_VAL', 'DocVQA_TEST', 'DUDE', 'MMLongBench_DOC', 'SLIDEVQA'] + res_24_datasets = ['InfoVQA_VAL', 'InfoVQA_TEST', 'OCRBench', 'HRBench4K', 'HRBench8K'] + if DATASET_MODALITY(dataset) == 'VIDEO': + return 1 + elif listinstr(res_12_datasets, dataset): + return 12 + elif listinstr(res_18_datasets, dataset): + return 18 + elif listinstr(res_24_datasets, dataset): + return 24 + elif DATASET_TYPE(dataset) == 'GUI': + return 12 + return 6 + + def process_payload(self, payload, dataset=None): + max_num = self.get_max_num(dataset) + if max_num is not None: + payload = copy.deepcopy(payload) + for msg in payload['messages']: + content = msg['content'] + if isinstance(content, dict) and content.get('type') == 'image_url': + content['image_url']['max_dynamic_patch'] = max_num + elif isinstance(content, list): + for item in content: + if isinstance(item, dict) and item.get('type') == 'image_url': + item['image_url']['max_dynamic_patch'] = max_num + return payload + + def postprocess(self, response, dataset=None): + if self.use_mpo_prompt: + from vlmeval.vlm.internvl.utils import mpo_post_processing + return mpo_post_processing(response, dataset) + return response + + +register_adapter('internvl2-mpo-cot', partial(InternVL2Adapter, use_mpo_prompt=True)) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/adapters/internvl3.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/adapters/internvl3.py new file mode 100644 index 00000000..1cf07aa6 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/adapters/internvl3.py @@ -0,0 +1,204 @@ +import copy +import io +import os + +from vlmeval.dataset import DATASET_MODALITY, DATASET_TYPE +from vlmeval.smp import listinstr +from .base import ModelAdapter, register_adapter + +_GUI_TEMPLATE = None + + +def _get_gui_template(): + global _GUI_TEMPLATE + if _GUI_TEMPLATE is None: + from pathlib import Path + + import yaml + vlmeval_root = Path(__file__).parent.parent.parent + tmpl_path = os.path.join(vlmeval_root, 'vlm/internvl/gui_template.yaml') + with open(tmpl_path, 'r') as f: + _GUI_TEMPLATE = yaml.load(f, Loader=yaml.FullLoader) + return _GUI_TEMPLATE + + +@register_adapter('internvl3') +class InternVL3Adapter(ModelAdapter): + + def __init__(self): + self.cot_prompt = None + self.screen_parse = True + self.split_think = True + + def dump_image(self, line, dataset): + return self.dump_image_func(line) + + def override_model_args(self, dataset): + think_ds = [ + 'MMMU', 'MathVista', 'SFE', 'Physics', 'MathVision', + 'OlympiadBench', 'IPhO_2025', 'MaCBench', + ] + think_system = ( + 'You are an expert reasoner with extensive experience in all ' + 'areas. You approach problems through systematic thinking and ' + 'rigorous reasoning. Your response should reflect deep ' + 'understanding and precise logical thinking, making your ' + 'solution path and reasoning clear to others. Please put your ' + 'thinking process within ... tags.' + ) + if listinstr(think_ds, dataset): + return dict(system_prompt=think_system) + else: + return dict(temperature=0.) + + def use_custom_prompt(self, dataset, system_prompt=None): + if dataset in [ + 'atomic_dataset', 'electro_dataset', 'mechanics_dataset', + 'optics_dataset', 'quantum_dataset', 'statistics_dataset', + 'Physics', 'SFE', 'SFE-zh', 'IPhO_2025', 'XLRS-Bench-lite', + 'OmniEarth-Bench', + ]: + return False + elif listinstr([ + 'MMDU', 'MME-RealWorld', 'MME-RealWorld-CN', 'WeMath_COT', + 'MMAlignBench', 'ScreenSpot', 'ChartQAPro', 'MMMU', + ], dataset): + return False + elif DATASET_MODALITY(dataset) == 'VIDEO': + return False + elif DATASET_TYPE(dataset) in ['Y/N', 'MCQ', 'VQA', 'GUI']: + return True + return False + + def build_prompt(self, line, dataset=None): + from vlmeval.dataset import build_dataset, infer_dataset_basename + from vlmeval.smp import LMUDataRoot + from vlmeval.vlm.internvl.utils import (build_mcq_cot_prompt, build_multi_choice_prompt, + build_qa_cot_prompt, format_nav_prompt, + pile_action_history) + + assert self.use_custom_prompt(dataset) + + if listinstr(['ChartMimic'], dataset): + input_figure_path_rel = line['input_figure'] + ROOT = LMUDataRoot() + img_root = os.path.join(ROOT, 'images', 'ChartMimic') + tgt_path = [os.path.join(img_root, input_figure_path_rel)] + else: + tgt_path = self.dump_image(line, dataset) + + if DATASET_TYPE(dataset) == 'Y/N': + question = line['question'] + if listinstr(['MME'], dataset): + prompt = question + ' Answer the question using a single word or phrase.' + elif listinstr(['HallusionBench', 'AMBER'], dataset): + prompt = question + ' Please answer yes or no. Answer the question using a single word or phrase.' + else: + prompt = question + elif DATASET_TYPE(dataset) == 'MCQ': + prompt = build_multi_choice_prompt(line, dataset) + if os.getenv('USE_COT') == '1': + prompt = build_mcq_cot_prompt(line, prompt, self.cot_prompt) + elif DATASET_TYPE(dataset) == 'VQA': + question = line['question'] + if listinstr(['LLaVABench', 'WildVision'], dataset): + prompt = question + '\nAnswer this question in detail.' + elif listinstr(['OCRVQA', 'TextVQA', 'ChartQA', 'DocVQA', 'InfoVQA', 'OCRBench', + 'DUDE', 'SLIDEVQA', 'GQA', 'MMLongBench_DOC'], dataset): + prompt = question + '\nAnswer the question using a single word or phrase.' + elif listinstr(['MathVista', 'MathVision', 'VCR', 'MTVQA', 'MMVet', 'MathVerse', + 'MMDU', 'CRPE', 'MIA-Bench', 'MM-Math', 'DynaMath', 'QSpatial', + 'WeMath', 'LogicVista', 'MM-IFEval', 'ChartMimic'], dataset): + prompt = question + if os.getenv('USE_COT') == '1': + prompt = build_qa_cot_prompt(line, prompt, self.cot_prompt) + else: + prompt = question + '\nAnswer the question using a single word or phrase.' + elif DATASET_TYPE(dataset) == 'GUI': + GUI_TEMPLATE = _get_gui_template() + ds_basename = infer_dataset_basename(dataset) + ds = build_dataset(dataset, skeleton=True) + action_space = ds.get_action_space() + traj_dict = ds.get_trajectory(line) + prompt_config = GUI_TEMPLATE[ds_basename] + if 'history' in prompt_config['placeholders']: + traj_dict['history'] = pile_action_history(traj_dict['history']) + prompt = format_nav_prompt( + ( + 'Please provide the bounding box coordinate of the region this sentence describes: {task}' # noqa: E501 + if self.screen_parse + else prompt_config['template'] + ), + prompt_config['placeholders'], + action_space=action_space, + **traj_dict, + ) + else: + prompt = line['question'] + if os.getenv('USE_COT') == '1': + prompt = build_qa_cot_prompt(line, prompt, self.cot_prompt) + + message = [dict(type='image', value=s) for s in tgt_path] + message.append(dict(type='text', value=prompt)) + return message + + def process_inputs(self, inputs, dataset=None): + from PIL import Image + + from vlmeval.vlm.internvl.utils import build_video_prompt, reorganize_prompt + + image_items = [x.copy() for x in inputs if x['type'] == 'image'] + image_num = len(image_items) + prompt = reorganize_prompt(inputs, image_num, dataset=dataset) + + if dataset is not None and DATASET_MODALITY(dataset) == 'VIDEO': + prompt = build_video_prompt(prompt, dataset) + + if listinstr(['MMMU'], dataset) and len(image_items) > 0: + image = Image.open(image_items[0]['value']) + image = image.resize((image.width * 2, image.height * 2), Image.BILINEAR) + tmp_image = io.BytesIO() + image.save(tmp_image, format='PNG') + image_items[0]['value'] = tmp_image + + prompt = prompt.replace('', '') + return [*image_items, dict(type='text', value=prompt)] + + def get_max_num(self, dataset): + if dataset is None: + return None + res_12_datasets = ['ChartQA_TEST', 'MMMU_DEV_VAL', 'MMMU_TEST', 'MME-RealWorld', + 'VCR_EN', 'VCR_ZH', 'OCRVQA'] + res_18_datasets = ['DocVQA_VAL', 'DocVQA_TEST', 'DUDE', 'MMLongBench_DOC', 'SLIDEVQA'] + res_24_datasets = ['InfoVQA_VAL', 'InfoVQA_TEST', 'OCRBench', 'HRBench4K', 'HRBench8K'] + if DATASET_MODALITY(dataset) == 'VIDEO': + return 1 + elif listinstr(res_12_datasets, dataset): + return 12 + elif listinstr(res_18_datasets, dataset): + return 18 + elif listinstr(res_24_datasets, dataset): + return 24 + elif DATASET_TYPE(dataset) == 'GUI': + return 12 + return None + + def process_payload(self, payload, dataset=None): + max_num = self.get_max_num(dataset) + if max_num is not None: + payload = copy.deepcopy(payload) + for msg in payload['messages']: + content = msg['content'] + if isinstance(content, dict) and content.get('type') == 'image_url': + content['image_url']['max_dynamic_patch'] = max_num + elif isinstance(content, list): + for item in content: + if isinstance(item, dict) and item.get('type') == 'image_url': + item['image_url']['max_dynamic_patch'] = max_num + return payload + + def postprocess(self, response, dataset=None): + if self.split_think and '' in response and '' in response: + _, _, answer = response.partition('')[-1].partition('') + return answer + return response diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/adapters/qwen3.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/adapters/qwen3.py new file mode 100644 index 00000000..ae951304 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/adapters/qwen3.py @@ -0,0 +1,14 @@ +from .base import ModelAdapter, register_adapter + + +@register_adapter('qwen3') +class Qwen3Adapter(ModelAdapter): + + def __init__(self, max_pixels=None): + self.max_pixels = max_pixels + + def process_payload(self, payload, dataset=None): + if self.max_pixels: + payload = payload.copy() + payload['mm_processor_kwargs'] = {'max_pixels': self.max_pixels} + return payload diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/arm_thinker.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/arm_thinker.py new file mode 100644 index 00000000..08bc1072 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/arm_thinker.py @@ -0,0 +1,276 @@ +import base64 +import datetime as dt +import json +import logging +import os +import os.path as osp +import sys +import traceback +import uuid + +import numpy as np +import requests + +from vlmeval.api.base import BaseAPI +from vlmeval.smp import encode_image_to_base64 + + +class LMDeployWrapper(BaseAPI): + + is_api: bool = True + + custom_prompt: str = None + + def __init__( + self, + model: str = None, + retry: int = 5, + api_base: str = None, + key: str = "sk-123456", + verbose: bool = True, + timeout: int = 60, + agent_repo_root: str = None, + use_role_tool: bool = True, + **kwargs, + ): + # get key and api_base from environment variables + key = os.environ.get("LMDEPLOY_API_KEY", key) + api_base = os.environ.get("LMDEPLOY_API_BASE", api_base) + assert key is not None, "Please set the environment variable LMDEPLOY_API_KEY." + assert ( + api_base is not None + ), "Please set the environment variable LMDEPLOY_API_BASE." + self.key = key + self.api_base = api_base + model_url = "".join([api_base.split("v1")[0], "v1/models"]) + headers = {"Authorization": f"Bearer {self.key}"} + resp = requests.get(model_url, headers=headers) + model_id_list = [str(data["id"]) for data in resp.json()["data"]] + self.model = model if model in model_id_list else model_id_list[0] + + self.fail_msg = "Failed to obtain answer via API. " + self.timeout = timeout + self.extra_pt = kwargs.pop("extra_pt", None) + self.use_role_tool = use_role_tool + # Set up logging for agent mode if needed + self.debug_mode = kwargs.pop("debug_mode", False) + if self.debug_mode: + logging.basicConfig(level=logging.DEBUG, force=True) + + super().__init__( + retry=retry, verbose=verbose, **kwargs + ) + + if agent_repo_root is None: + raise ValueError('Please set `agent_repo_root` to ARM-Thinker directory, \ + which is cloned from here: https://github.com/InternLM/ARM-Thinker') + + print(f"agent_repo_root: {agent_repo_root}") + sys.path.append(agent_repo_root) + try: + from arm_agent.agent_verl import VerlAgent # noqa: F401 + except Exception: + raise ValueError('Please install ARM-Thinker from https://github.com/InternLM/ARM-Thinker') + + def prepare_itlist(self, inputs): + assert np.all([isinstance(x, dict) for x in inputs]) + has_images = np.sum([x["type"] == "image" for x in inputs]) + if has_images: + content_list = [] + for msg in inputs: + if msg["type"] == "text": + content_list.append(dict(type="text", text=msg["value"])) + elif msg["type"] == "image": + from PIL import Image + + img = Image.open(msg["value"]) + b64 = encode_image_to_base64(img) + extra_args = msg.copy() + extra_args.pop("type") + extra_args.pop("value") + img_struct = dict(url=f"data:image/jpeg;base64,{b64}", **extra_args) + content_list.append(dict(type="image_url", image_url=img_struct)) + else: + assert all([x["type"] == "text" for x in inputs]) + text = "\n".join([x["value"] for x in inputs]) + content_list = [dict(type="text", text=text)] + return content_list + + def prepare_inputs(self, inputs): + input_msgs = [] + assert isinstance(inputs, list) and isinstance(inputs[0], dict) + assert np.all(["type" in x for x in inputs]) or np.all( + ["role" in x for x in inputs] + ), inputs + if "role" in inputs[0]: + assert inputs[-1]["role"] == "user", inputs[-1] + for item in inputs: + input_msgs.append( + dict(role=item["role"], content=self.prepare_itlist(item["content"])) + ) + else: + input_msgs.append(dict(role="user", content=self.prepare_itlist(inputs))) + return input_msgs + + def generate_inner(self, inputs, **kwargs) -> tuple: + mode = kwargs.pop("mode", "direct") + dataset = kwargs.pop("dataset", None) + + if mode == "direct": + input_msgs = self.prepare_inputs(inputs) + headers = { + "Content-Type": "application/json", + "Authorization": f"Bearer {self.key}", + } + + if self.extra_pt: + input_msgs[-1]["content"][-1]["text"] = ( + input_msgs[-1]["content"][-1]["text"] + self.extra_pt + ) + + payload = { + "model": self.model, + "messages": input_msgs, + "n": 1, + "extra_body": {}, + } + if kwargs.get("repetition_penalty", None): + payload["extra_body"]["repetition_penalty"] = kwargs.get("repetition_penalty") + kwargs.pop("repetition_penalty") + + if kwargs.get("top_k", None): + payload["extra_body"]["top_k"] = kwargs.get("top_k") + kwargs.pop("top_k") + payload.update(kwargs) + + payload_copy = payload.copy() + payload_copy.pop("messages") + print(f"Full payload except messages:\n{payload_copy}") + + response = requests.post( + self.api_base, + headers=headers, + data=json.dumps(payload), + timeout=self.timeout * 1.1, + ) + ret_code = response.status_code + ret_code = 0 if (200 <= int(ret_code) < 300) else ret_code + answer = self.fail_msg + try: + resp_struct = json.loads(response.text) + answer = resp_struct["choices"][0]["message"]["content"].strip() + except Exception: + pass + return ret_code, answer, response + + elif mode == "agent": + timestamp = dt.datetime.now().strftime("%Y%m%d%H%M%S") + temp_dir = f"/tmp/agent_images_{timestamp}" + os.environ["TOOL_CALL_IMG_TEMP"] = temp_dir + os.makedirs(temp_dir, exist_ok=True) + + from arm_agent.agent_verl import VerlAgent + agent = VerlAgent( + model_name=self.model, + api_base=self.api_base.split("chat/completions")[0], + api_key=self.key, + use_role_tool=self.use_role_tool, + **kwargs + ) + + input_msgs = self.prepare_inputs(inputs) + + if self.extra_pt: + input_msgs[-1]["content"][-1]["text"] = ( + input_msgs[-1]["content"][-1]["text"] + self.extra_pt + ) + + result, tool_call_count = agent.run(user_messages=input_msgs) + rtn = result[-1]["content"] + + # "base64" or "save_to_path" or "hidden", default is "base64" + # Option "base64": Direct save the image base64 to result file, which will be large. + # Option "save_to_path": Save the image to path, which will be large. + # Option "hidden": Hidden the image, which will be small. + option_for_process_image_in_result = "save_to_path" + if option_for_process_image_in_result == "hidden": + for msg in result: + role = msg.get("role", "") + content = msg.get("content", "") + # print image part, base64 is too long to print + if role == "user" and isinstance(content, list): + for item in content: + if "type" in item and item["type"] == "image_url": + item["image_url"]["url"] = "hidden" + print(f"{role.upper()}:\n{content}\n") + elif option_for_process_image_in_result == "base64": + pass + elif option_for_process_image_in_result == "save_to_path": + # vlmevalkit_root is path like xxx/VLMEvalKit + vlmevalkit_root = osp.dirname(osp.dirname(osp.dirname(osp.abspath(__file__)))) + save_img_root = osp.join(vlmevalkit_root, "temp", dataset, self.model) + os.makedirs(save_img_root, exist_ok=True) + + # uuid + short_uid = uuid.uuid4().hex[:6] # 6 bit uuid + sub_dir_name = f"{timestamp}_{short_uid}" + img_counter = 0 + for msg in result: + content = msg.get("content", "") + if not isinstance(content, list): + continue + for itemm in content: + if not (isinstance(itemm, dict) and itemm.get("type") == "image_url"): + continue + image_url = itemm.get("image_url", {}).get("url", "") + if not image_url: + continue + try: + # judge whether it is base64 or url + if image_url.startswith("data:image"): + header, b64_data = image_url.split(",", 1) + ext = header.split("/")[1].split(";")[0] # extract extension + img_bytes = base64.b64decode(b64_data) + img_name = f"{sub_dir_name}/img_{img_counter:04d}.{ext}" + else: + raise ValueError( + f"Invalid image url: {image_url}, can only process base64 image url" + ) + # save image + img_path = os.path.join(save_img_root, img_name) + os.makedirs(os.path.dirname(img_path), exist_ok=True) + with open(img_path, "wb") as f: + f.write(img_bytes) + + # replace image_url["url"] with relative path + itemm["image_url"]["url"] = f"{img_name}" + img_counter += 1 + + print(f"[Saved] {img_path}, {itemm['image_url']['url']}") + except Exception as e: + print(f"Error: {e}") + print("Traceback:\n" + traceback.format_exc()) + raise ValueError(f"Error: {e}") + else: + raise ValueError(f"Invalid option: {option_for_process_image_in_result}") + + extra_records = {"tool_call_count": tool_call_count, "conversation": result} + print(f"tool_call_count in extra_records: {tool_call_count}") + + if rtn: + ret_code = 0 + return ret_code, rtn, extra_records + else: + ret_code = 1 + return ret_code, self.fail_msg, extra_records + else: + raise ValueError(f"Invalid mode: {mode}") + + +class ARM_thinker(LMDeployWrapper): + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def generate(self, message, dataset=None): + return super(ARM_thinker, self).generate(message, dataset=dataset) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/bailingmm.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/bailingmm.py new file mode 100644 index 00000000..fdbcf040 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/bailingmm.py @@ -0,0 +1,96 @@ +import base64 +import copy as cp +import json +import os +import time + +import requests + +from vlmeval.api.base import BaseAPI +from vlmeval.smp import get_logger + +logger = get_logger(__name__) + + +class bailingMMWrapper(BaseAPI): + + is_api: bool = True + + def __init__(self, + model: str, + retry: int = 5, + key: str = None, + verbose: bool = True, + system_prompt: str = None, + max_tokens: int = 1024, + proxy: str = None, + **kwargs): + + self.model = model + self.fail_msg = 'Failed to obtain answer via bailingMM API.' + if key is None: + key = os.environ.get('BAILINGMM_API_KEY', None) + assert key is not None, ('Please set the API Key for bailingMM.') + self.key = key + self.headers = {"Content-Type": "application/json"} + super().__init__(retry=retry, system_prompt=system_prompt, verbose=verbose, **kwargs) + + def image_to_base64(self, image_path): + with open(image_path, 'rb') as image_file: + encoded_string = str(base64.b64encode(image_file.read()), 'utf-8') + return encoded_string + + def prepare_inputs(self, inputs): + msgs = cp.deepcopy(inputs) + content = [] + for i, msg in enumerate(msgs): + if msg['type'] == 'text': + pass + else: + try: + image_data = self.image_to_base64(msg['value']) + except Exception as e: + if self.verbose: + logger.error(e) + image_data = '' + msg['value'] = image_data + content.append(msg) + return content + + def generate_inner(self, inputs, **kwargs) -> str: + assert isinstance(inputs, str) or isinstance(inputs, list) + start = time.time() + inputs = [inputs] if isinstance(inputs, str) else inputs + + messages = self.prepare_inputs(inputs) + + service_url = "https://bailingchat.alipay.com/api/proxy/eval/antgmm/completions" + + payload = { + "structInput": json.dumps([{"role": "user", "content": messages}]), + "sk": self.key, + "model": self.model, + "timeout": 180000 + } + response = requests.post(service_url, headers=self.headers, json=payload) + if self.verbose: + logger.info('Time for requesting is:') + logger.info(time.time() - start) + try: + assert response.status_code == 200 + output = json.loads(response.text) + answer = output['preds']['pred'] + if self.verbose: + logger.info(f'inputs: {inputs}\nanswer: {answer}') + return 0, answer, 'Succeeded! ' + except Exception as e: + if self.verbose: + logger.error(e) + logger.error(f'The input messages are {inputs}.') + return -1, self.fail_msg, '' + + +class bailingMMAPI(bailingMMWrapper): + + def generate(self, message, dataset=None): + return super(bailingMMAPI, self).generate(message, dataset=dataset) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/base.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/base.py new file mode 100644 index 00000000..fa3d5d21 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/base.py @@ -0,0 +1,335 @@ +import copy as cp +import random as rd +import threading +import time +from abc import abstractmethod + +from vlmeval.smp import concat_images_vlmeval, get_logger, parse_file + +logger = get_logger(__name__) + + +class BaseAPI: + + allowed_types = ['text', 'image', 'video'] + INTERLEAVE = True + INSTALL_REQ = False + + def __init__(self, + retry=10, + wait=1, + system_prompt=None, + verbose=True, + fail_msg='Failed to obtain answer via API.', + **kwargs): + """Base Class for all APIs. + + Args: + retry (int, optional): The retry times for `generate_inner`. Defaults to 10. + wait (int, optional): The wait time after each failed retry of `generate_inner`. Defaults to 1. + system_prompt (str, optional): Defaults to None. + verbose (bool, optional): Defaults to True. + fail_msg (str, optional): The message to return when failed to obtain answer. + Defaults to 'Failed to obtain answer via API.'. + **kwargs: Other kwargs for `generate_inner`. + """ + + self.wait = wait + self.retry = retry + self.system_prompt = system_prompt + self.verbose = verbose + self.fail_msg = fail_msg + self._fail_reason_local = threading.local() + + if len(kwargs): + logger.info(f'BaseAPI received the following kwargs: {kwargs}') + logger.info('Will try to use them as kwargs for `generate`. ') + self.default_kwargs = kwargs + + def __getstate__(self): + state = self.__dict__.copy() + state.pop('_fail_reason_local', None) + return state + + def __setstate__(self, state): + self.__dict__.update(state) + self._fail_reason_local = threading.local() + + @abstractmethod + def generate_inner(self, inputs, **kwargs): + """The inner function to generate the answer. + + Returns: + tuple(int, str, str): ret_code, response, log + """ + logger.warning('For APIBase, generate_inner is an abstract method. ') + assert 0, 'generate_inner not defined' + ret_code, answer, log = None, None, None + # if ret_code is 0, means succeed + return ret_code, answer, log + + def working(self): + """If the API model is working, return True, else return False. + + Returns: + bool: If the API model is working, return True, else return False. + """ + self.old_timeout = None + if hasattr(self, 'timeout'): + self.old_timeout = self.timeout + self.timeout = 120 + + retry = 5 + while retry > 0: + ret = self.generate('hello') + if ret is not None and ret != '' and self.fail_msg not in ret: + if self.old_timeout is not None: + self.timeout = self.old_timeout + return True + retry -= 1 + + if self.old_timeout is not None: + self.timeout = self.old_timeout + return False + + def check_content(self, msgs): + """Check the content type of the input. Four types are allowed: str, dict, liststr, listdict. + + Args: + msgs: Raw input messages. + + Returns: + str: The message type. + """ + if isinstance(msgs, str): + return 'str' + if isinstance(msgs, dict): + return 'dict' + if isinstance(msgs, list): + types = [self.check_content(m) for m in msgs] + if all(t == 'str' for t in types): + return 'liststr' + if all(t == 'dict' for t in types): + return 'listdict' + return 'unknown' + + def preproc_content(self, inputs): + """Convert the raw input messages to a list of dicts. + + Args: + inputs: raw input messages. + + Returns: + list(dict): The preprocessed input messages. Will return None if failed to preprocess the input. + """ + if self.check_content(inputs) == 'str': + return [dict(type='text', value=inputs)] + elif self.check_content(inputs) == 'dict': + assert 'type' in inputs and 'value' in inputs + return [inputs] + elif self.check_content(inputs) == 'liststr': + res = [] + for s in inputs: + mime, pth = parse_file(s) + if mime is None or mime == 'unknown': + res.append(dict(type='text', value=s)) + else: + res.append(dict(type=mime.split('/')[0], value=pth)) + return res + elif self.check_content(inputs) == 'listdict': + for item in inputs: + assert 'type' in item and 'value' in item + mime, s = parse_file(item['value']) + if mime is None: + assert item['type'] == 'text', item['value'] + else: + assert mime.split('/')[0] == item['type'] + item['value'] = s + return inputs + else: + return None + + # May exceed the context windows size, so try with different turn numbers. + def chat_inner(self, inputs, **kwargs): + _ = kwargs.pop('dataset', None) + while len(inputs): + try: + return self.generate_inner(inputs, **kwargs) + except Exception as e: + if self.verbose: + logger.info(f'{type(e)}: {e}') + inputs = inputs[1:] + while len(inputs) and inputs[0]['role'] != 'user': + inputs = inputs[1:] + continue + return -1, self.fail_msg + ': ' + 'Failed with all possible conversation turns.', None + + def chat(self, messages, **kwargs1): + """The main function for multi-turn chatting. Will call `chat_inner` with the preprocessed input messages.""" + assert hasattr(self, 'chat_inner'), 'The API model should has the `chat_inner` method. ' + for msg in messages: + assert isinstance(msg, dict) and 'role' in msg and 'content' in msg, msg + assert self.check_content(msg['content']) in ['str', 'dict', 'liststr', 'listdict'], msg + msg['content'] = self.preproc_content(msg['content']) + # merge kwargs + kwargs = cp.deepcopy(self.default_kwargs) + kwargs.update(kwargs1) + + answer = None + # a very small random delay [0s - 0.5s] + T = rd.random() * 0.5 + time.sleep(T) + + assert messages[-1]['role'] == 'user' + + for i in range(self.retry): + try: + ret_code, answer, log = self.chat_inner(messages, **kwargs) + if ret_code == 0 and self.fail_msg not in answer and answer != '': + if self.verbose: + print(answer) + return answer + elif self.verbose: + if not isinstance(log, str): + try: + log = log.text + except Exception as e: + logger.warning(f'Failed to parse {log} as an http response: {str(e)}. ') + logger.info(f'RetCode: {ret_code}\nAnswer: {answer}\nLog: {log}') + except Exception as err: + if self.verbose: + logger.error(f'An error occured during try {i}: ') + logger.error(f'{type(err)}: {err}') + # delay before each retry + if i < self.retry - 1: # Don't sleep after last attempt + T = rd.random() * self.wait * 2 + if self.verbose and T > 0: + logger.info(f'⏱️ Waiting {T:.1f}s before next retry...') + time.sleep(T) + + return self.fail_msg if answer in ['', None] else answer + + def preprocess_message_with_role(self, message): + system_prompt = '' + new_message = [] + + for data in message: + assert isinstance(data, dict) + role = data.pop('role', 'user') + if role == 'system': + system_prompt += data['value'] + '\n' + else: + new_message.append(data) + + if system_prompt != '': + if self.system_prompt is None: + self.system_prompt = system_prompt + else: + if system_prompt not in self.system_prompt: + self.system_prompt += '\n' + system_prompt + return new_message + + def generate(self, message, **kwargs1): + """The main function to generate the answer. Will call `generate_inner` with the preprocessed input messages. + + Args: + message: raw input messages. + + Returns: + str: The generated answer of the Failed Message if failed to obtain answer. + """ + if self.check_content(message) == 'listdict': + message = self.preprocess_message_with_role(message) + + assert self.check_content(message) in ['str', 'dict', 'liststr', 'listdict'], f'Invalid input type: {message}' + message = self.preproc_content(message) + assert message is not None and self.check_content(message) == 'listdict' + + # Defensive check: ensure allowed_types is set (fix for multiprocessing/state issues) + if not hasattr(self, 'allowed_types') or self.allowed_types is None: + if self.verbose: + logger.warning(f'allowed_types was None or missing! Setting to default. Class: {self.__class__.__name__}') + self.allowed_types = ['text', 'image', 'video'] + + for item in message: + try: + assert item['type'] in self.allowed_types, f'Invalid input type: {item["type"]}' + except TypeError as e: + logger.error(f'TypeError checking allowed_types: {e}') + logger.error(f'allowed_types value: {self.allowed_types}') + logger.error(f'allowed_types type: {type(self.allowed_types)}') + logger.error(f'Class: {self.__class__.__name__}') + # Force set it again + self.allowed_types = ['text', 'image', 'video'] + # Re-check + assert item['type'] in self.allowed_types, f'Invalid input type: {item["type"]}' + + # merge kwargs + kwargs = cp.deepcopy(self.default_kwargs) + kwargs.update(kwargs1) + + answer = None + ret_code = -1 + # a very small random delay [0s - 0.5s] + T = rd.random() * 0.5 + time.sleep(T) + + for i in range(self.retry): + try: + ret_code, answer, log = self.generate_inner(message, **kwargs) + # Check answer is valid before using 'in' operator to avoid TypeError with None + if ret_code == 0 and answer and answer != '' and self.fail_msg not in answer: + if self.verbose: + print(answer) + # Return both answer and extra_records + # Add for agent evaluation + if isinstance(log, dict): + return {"prediction": answer, "extra_records": log} + return answer + elif self.verbose: + if not isinstance(log, str): + try: + log = log.text + except Exception as e: + logger.warning(f'Failed to parse {log} as an http response: {str(e)}. ') + logger.info(f'RetCode: {ret_code}\nAnswer: {answer}\nLog: {log}') + except Exception as err: + if self.verbose: + logger.error(f'An error occured during try {i}: ') + logger.error(f'{type(err)}: {err}') + # delay before each retry + T = rd.random() * self.wait * 2 + time.sleep(T) + + self._fail_reason_local.reason = 2 if ret_code == -2 else 1 # 1=recoverable, 2=content_filter + return self.fail_msg if answer in ['', None] else answer + + def message_to_promptimg(self, message, dataset=None): + assert not self.INTERLEAVE + model_name = self.__class__.__name__ + import warnings + warnings.warn( + f'Model {model_name} does not support interleaved input. ' + 'Will use the first image and aggregated texts as prompt. ') + num_images = len([x for x in message if x['type'] == 'image']) + if num_images == 0: + prompt = '\n'.join([x['value'] for x in message if x['type'] == 'text']) + image = None + elif num_images == 1: + prompt = '\n'.join([x['value'] for x in message if x['type'] == 'text']) + image = [x['value'] for x in message if x['type'] == 'image'][0] + else: + prompt = '\n'.join([x['value'] if x['type'] == 'text' else '' for x in message]) + if dataset == 'BLINK': + image = concat_images_vlmeval( + [x['value'] for x in message if x['type'] == 'image'], + target_size=512) + else: + image = [x['value'] for x in message if x['type'] == 'image'][0] + return prompt, image + + def dump_image(self, line, dataset): + return self.dump_image_func(line) + + def set_dump_image(self, dump_image_func): + self.dump_image_func = dump_image_func diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/bedrock.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/bedrock.py new file mode 100644 index 00000000..0f97a826 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/bedrock.py @@ -0,0 +1,173 @@ +""" +AWS Bedrock API support via the Converse API. +Supports vision models (e.g. Claude on Bedrock) with image inputs. +Set AWS credentials via environment (AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_REGION) +or default profile. + +Requires: pip install boto3 +""" +import base64 +import os +import os.path as osp + +from ..smp import get_logger +from .base import BaseAPI + +try: + import boto3 + from botocore.exceptions import BotoCoreError, ClientError +except ImportError: + boto3 = None + BotoCoreError = ClientError = Exception + +logger = get_logger(__name__) + +# Common Bedrock vision model IDs (region-specific; use full ID in config) +BEDROCK_VISION_MODELS = { + "claude-3-5-sonnet": "anthropic.claude-3-5-sonnet-20241022-v2:0", + "claude-3-opus": "anthropic.claude-3-opus-20240229-v1:0", + "claude-3-sonnet": "anthropic.claude-3-sonnet-20240229-v1:0", + "claude-3-haiku": "anthropic.claude-3-haiku-20240307-v1:0", +} + + +def _image_path_to_format(path): + ext = osp.splitext(path)[-1].lower() + if ext in (".jpg", ".jpeg"): + return "jpeg" + if ext in (".png", ".gif", ".webp"): + return ext[1:] + return "jpeg" + + +class BedrockAPI(BaseAPI): + """VLM API using AWS Bedrock Converse API (supports text + image).""" + + is_api: bool = True + + def __init__( + self, + model_id: str, + region_name: str = None, + retry: int = 10, + wait: int = 1, + system_prompt: str = None, + verbose: bool = True, + temperature: float = 0, + max_tokens: int = 2048, + img_size: int = -1, + **kwargs, + ): + if boto3 is None: + raise ImportError("boto3 is required for BedrockAPI. Install with: pip install boto3") + + self.model_id = BEDROCK_VISION_MODELS.get(model_id, model_id) + self.region_name = region_name or os.environ.get("AWS_REGION", "us-east-1") + self.temperature = temperature + self.max_tokens = max_tokens + self.img_size = img_size + + super().__init__( + retry=retry, + wait=wait, + system_prompt=system_prompt, + verbose=verbose, + **kwargs, + ) + + self._client = boto3.client( + service_name="bedrock-runtime", + region_name=self.region_name, + ) + logger.info( + f"BedrockAPI: model_id={self.model_id}, region={self.region_name}" + ) + + def _encode_image_to_bytes(self, image_path, target_size=-1): + from ..smp import encode_image_file_to_base64 + + suffix = osp.splitext(image_path)[-1].lower() + fmt = "JPEG" if suffix in (".jpg", ".jpeg") else "PNG" + b64 = encode_image_file_to_base64( + image_path, target_size=target_size, fmt=fmt + ) + return base64.b64decode(b64), _image_path_to_format(image_path) + + def _build_content_blocks(self, inputs): + """Build Converse API content list from VLMEvalKit message list.""" + blocks = [] + for item in inputs: + if item["type"] == "text" and item["value"]: + blocks.append({"text": item["value"]}) + elif item["type"] == "image": + path = item["value"] + raw_bytes, fmt = self._encode_image_to_bytes( + path, target_size=self.img_size + ) + blocks.append({ + "image": { + "format": fmt, + "source": {"bytes": raw_bytes}, + } + }) + return blocks + + def _prepare_messages(self, inputs): + """Convert VLMEvalKit message list to Converse API messages. + inputs: either [ {type, value}, ... ] (single turn) or + [ {role, content: [ {type, value}, ... ] }, ... ] (multi-turn). + """ + if inputs and "role" in inputs[0]: + # Multi-turn chat + messages = [] + for msg in inputs: + role = msg["role"] + content = self._build_content_blocks(msg["content"]) + if not content: + content = [{"text": ""}] + messages.append({"role": role, "content": content}) + return messages + # Single turn + content = self._build_content_blocks(inputs) + if not content: + content = [{"text": ""}] + return [{"role": "user", "content": content}] + + def generate_inner(self, inputs, **kwargs): + messages = self._prepare_messages(inputs) + temperature = kwargs.pop("temperature", self.temperature) + max_tokens = kwargs.pop("max_tokens", self.max_tokens) + + request = { + "modelId": self.model_id, + "messages": messages, + "inferenceConfig": { + "maxTokens": max_tokens, + "temperature": temperature, + }, + } + if self.system_prompt: + request["system"] = [{"text": self.system_prompt}] + + try: + response = self._client.converse(**request) + except (BotoCoreError, ClientError) as e: + err_msg = str(e) + if self.verbose: + logger.error(f"Bedrock API error: {err_msg}") + return -1, self.fail_msg, err_msg + + answer = self.fail_msg + try: + content_list = response.get("output", {}).get("message", {}).get("content", []) + texts = [ + block["text"] + for block in content_list + if isinstance(block, dict) and "text" in block + ] + answer = "".join(texts).strip() if texts else self.fail_msg + except Exception as e: + if self.verbose: + logger.error(f"Parse response: {e}") + + return 0, answer, response diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/bluelm_api.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/bluelm_api.py new file mode 100644 index 00000000..1cf8abb5 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/bluelm_api.py @@ -0,0 +1,240 @@ +import base64 +import json +import os +import re + +import numpy as np +import requests + +from vlmeval.api.base import BaseAPI +from vlmeval.smp import concat_images_vlmeval, get_logger + +logger = get_logger(__name__) + + +def split_think(text: str) -> str: + """ + 提取think后的内容 + """ + if "" in text: + answer = text.split("")[1] + else: + if "" in text: + return 'Thinking mode too long to extract answer' + return text + return answer + + +def remove_boxed(s: str): + left = '\\boxed{' + try: + assert s[:len(left)] == left + assert s[-1] == '}' + return s[len(left):-1] + except Exception: + return None + + +def last_boxed_only_string(string: str): + idx = string.rfind('\\boxed') + if idx < 0: + idx = string.rfind('\\fbox') + if idx < 0: + return None + + i = idx + right_brace_idx = None + num_left_braces_open = 0 + while i < len(string): + if string[i] == '{': + num_left_braces_open += 1 + if string[i] == '}': + num_left_braces_open -= 1 + if num_left_braces_open == 0: + right_brace_idx = i + break + i += 1 + + if right_brace_idx is None: + retval = None + else: + retval = string[idx:right_brace_idx + 1] + + return retval + + +def extract_boxed(pred_str: str, strip_double_curly_brace=False): + boxed_str = last_boxed_only_string(pred_str) + if boxed_str is None: + return pred_str # 返回原始字符串 + answer = remove_boxed(boxed_str) + if answer is None: + return pred_str # 返回原始字符串 + if strip_double_curly_brace: + match = re.match('^\{(.*)\}$', answer) # noqa: W605 + if match: + answer = match.group(1) + return answer + + +def extract_boxed_answer(pred_str: str): + if pred_str.rfind('\\boxed') < 0 and pred_str.rfind('\\fbox') < 0: + return pred_str + return extract_boxed(pred_str, strip_double_curly_brace=True) + + +def get_streaming_response(response: requests.Response): + for chunk in response.iter_lines(chunk_size=4096, + decode_unicode=False): + if chunk: + data = json.loads(chunk.decode("utf-8")) + output = data.get("result") + yield output + + +def multimodal(images, text, url, key, temperature=0.6, max_tokens=32768, top_k=20, top_p=0.95, stream=True, history=[], timeout=60): # noqa: E501 + if images: + pics = [] + for image in images: + with open(image, 'rb') as f: + pic = base64.b64encode(f.read()).decode('utf-8') + pics.append(pic) + data = { + 'images': pics, 'text': text, 'key': key, 'temperature': temperature, + 'max_tokens': max_tokens, 'top_k': top_k, 'top_p': top_p, 'stream': stream + } + else: + data = { + 'text': text, 'key': key, 'temperature': temperature, + 'max_tokens': max_tokens, 'top_k': top_k, 'top_p': top_p, 'stream': stream + } + response = requests.post(url, json=data, headers={"Content-Type": "application/json"}, timeout=timeout) + if stream: + final_text = '' + for h in get_streaming_response(response): + final_text = h + else: + response_data = response.json() + final_text = response_data.get("result", "") + return final_text + + +class BlueLMWrapper(BaseAPI): + is_api: bool = True + + def __init__(self, + model: str = 'BlueLM-2.5-3B', + retry: int = 5, + verbose: bool = True, + temperature: float = 0.6, + system_prompt: str = None, + max_tokens: int = 32768, + top_k: int = 20, + top_p: float = 0.95, + timeout: int = 60, + key: str = None, + url: str = 'http://api-ai.vivo.com.cn/multimodal', + **kwargs): + + self.model = model + self.fail_msg = 'Failed to obtain answer BlueLM API. ' + self.max_tokens = max_tokens + self.temperature = temperature + self.top_k = top_k + self.top_p = top_p + self.url = url + self.key = key + self.timeout = timeout + + if self.key is None: + self.key = os.environ.get('BLUELM_API_KEY', None) + assert self.key is not None, ( + 'Please set the API Key (obtain it here: ' + 'contact by email : shuai.ren@vivo.com' + ) + + super().__init__(retry=retry, system_prompt=system_prompt, verbose=verbose, **kwargs) + + def message_to_promptimg(self, message, dataset=None): + + num_images = len([x for x in message if x['type'] == 'image']) + if num_images == 0: + prompt = '\n'.join([x['value'] for x in message if x['type'] == 'text']) + image = None + elif num_images == 1: + prompt = '\n'.join([x['value'] for x in message if x['type'] == 'text']) + image = [x['value'] for x in message if x['type'] == 'image'] + else: + prompt = '\n'.join([x['value'] if x['type'] == 'text' else '' for x in message]) + if dataset == 'BLINK': + image = concat_images_vlmeval( + [x['value'] for x in message if x['type'] == 'image'], + target_size=512) + else: + image = [x['value'] for x in message if x['type'] == 'image'] + + if dataset in ['MMBench_DEV_EN_V11', 'MMBench_DEV_CN_V11', 'MMBench_TEST_EN_V11', 'MMBench_TEST_CN_V11', + 'AI2D_TEST', 'AI2D_TEST_TO_MASK', 'MMMU_DEV_VAL', 'MMStar']: + prompt = prompt.replace('Please select the correct answer from the options above.', + 'Answer with the option’s letter from the given choices directly.') + prompt = prompt.replace('Question: Hint: Please answer the question and provide the correct option letter, e.g., A, B, C, D, at the end.\n', '') # noqa: E501 + elif dataset in ['ChartQA_TEST']: + prompt = prompt.replace('Answer the question using a single word or phrase.', + 'Answer the question using a single number or phrase.') + elif dataset in ['DocVQA_VAL', 'DocVQA_TEST', ]: + prompt = prompt.replace('Answer the question using a single word or phrase.', + 'Give the short answer directly.') + elif dataset in ['TextVQA_VAL']: + prompt = prompt.replace('Answer the question using a single word or phrase.', + 'When the provided information is insufficient, respond with ’Unanswerable’.' + 'Answer the question using a single word or phrase.') + elif dataset in ['MTVQA_TEST']: + prompt = prompt.replace( + '\nAnswer the question using a word or phrase in the language of the question.', '') + elif dataset in ['MathVista_MINI']: + if 'Choices:' in prompt: + prompt = prompt.replace('Choices:', 'Options:').replace('Hint:', 'Context:') + for i in range(1, 7): # replace A ~ F + prompt = prompt.replace(f'({chr(64 + i)})', f'{chr(64 + i)}.') + prompt += '\nAnswer with the option’s letter from the given choices directly.' + else: + prompt += '\nAnswer the question using a single word or phrase.' + elif dataset in ['HallusionBench']: + prompt = prompt + " Please answer yes or no." + return prompt, image + + def generate_inner(self, inputs, **kwargs) -> str: + + assert isinstance(inputs, str) or isinstance(inputs, list) + pure_text = np.all([x['type'] == 'text' for x in inputs]) + assert not pure_text + + prompt, image_path = self.message_to_promptimg(inputs, kwargs['dataset']) + + try: + response = multimodal( + images=image_path, text=prompt, url=self.url, key=self.key, temperature=self.temperature, + max_tokens=self.max_tokens, top_k=self.top_k, top_p=self.top_p, timeout=self.timeout) + if kwargs['dataset'] in [ + 'MMBench_DEV_EN_V11', 'MMBench_DEV_CN_V11', 'MMBench_TEST_EN_V11', 'MMBench_TEST_CN_V11', + 'AI2D_TEST', 'AI2D_TEST_TO_MASK', 'MMMU_DEV_VAL', 'MMStar', + 'OCRBench', 'MMVet', 'MathVista_MINI', 'HallusionBench' + ]: + + answer = split_think(response[0]) + answer = extract_boxed_answer(answer) + else: + answer = split_think(response[0]) + logger.info(f'answer : {answer}') + return 0, answer, 'Succeeded! ' + except Exception as err: + if self.verbose: + logger.error(f'{type(err)}: {err}') + logger.error(f'The input messages are {inputs}.') + return -1, '', '' + + +class BlueLM_API(BlueLMWrapper): + + def generate(self, message, dataset=None): + return super(BlueLM_API, self).generate(message, dataset=dataset) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/claude.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/claude.py new file mode 100644 index 00000000..c86302cb --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/claude.py @@ -0,0 +1,154 @@ +import json +import mimetypes +import os +import os.path as osp + +import numpy as np +import requests +from PIL import Image + +from vlmeval.api.base import BaseAPI +from vlmeval.smp import encode_image_to_base64, get_logger + +logger = get_logger(__name__) + +alles_url = 'https://openxlab.org.cn/gw/alles-apin-hub/v1/claude/v1/text/chat' +alles_headers = { + 'alles-apin-token': '', + 'Content-Type': 'application/json' +} +official_url = 'https://api.anthropic.com/v1/messages' +official_headers = { + 'x-api-key': '', + 'anthropic-version': '2023-06-01', + 'content-type': 'application/json' +} + + +class Claude_Wrapper(BaseAPI): + + is_api: bool = True + + def __init__(self, + backend: str = 'alles', + model: str = 'claude-3-opus-20240229', + key: str = None, + retry: int = 10, + timeout: int = 60, + system_prompt: str = None, + verbose: bool = True, + temperature: float = 0, + max_tokens: int = 2048, + **kwargs): + + if os.environ.get('ANTHROPIC_BACKEND', '') == 'official': + backend = 'official' + + assert backend in ['alles', 'official'], f'Invalid backend: {backend}' + self.backend = backend + self.url = alles_url if backend == 'alles' else official_url + self.model = model + self.temperature = temperature + self.max_tokens = max_tokens + self.headers = alles_headers if backend == 'alles' else official_headers + self.timeout = timeout + + if key is not None: + self.key = key + else: + self.key = os.environ.get('ALLES', '') if self.backend == 'alles' else os.environ.get('ANTHROPIC_API_KEY', '') # noqa: E501 + + if self.backend == 'alles': + self.headers['alles-apin-token'] = self.key + else: + self.headers['x-api-key'] = self.key + + super().__init__(retry=retry, verbose=verbose, system_prompt=system_prompt, **kwargs) + + def encode_image_file_to_base64(self, image_path, target_size=-1, fmt='.jpg'): + image = Image.open(image_path) + if fmt in ('.jpg', '.jpeg'): + format = 'JPEG' + elif fmt == '.png': + format = 'PNG' + else: + print(f'Unsupported image format: {fmt}, will cause media type match error.') + + return encode_image_to_base64(image, target_size=target_size, fmt=format) + + # inputs can be a lvl-2 nested list: [content1, content2, content3, ...] + # content can be a string or a list of image & text + def prepare_itlist(self, inputs): + assert np.all([isinstance(x, dict) for x in inputs]) + has_images = np.sum([x['type'] == 'image' for x in inputs]) + if has_images: + content_list = [] + for msg in inputs: + if msg['type'] == 'text' and msg['value'] != '': + content_list.append(dict(type='text', text=msg['value'])) + elif msg['type'] == 'image': + pth = msg['value'] + suffix = osp.splitext(pth)[-1].lower() + media_type = mimetypes.types_map.get(suffix, None) + assert media_type is not None + + content_list.append(dict( + type='image', + source={ + 'type': 'base64', + 'media_type': media_type, + 'data': self.encode_image_file_to_base64(pth, target_size=4096, fmt=suffix) + })) + else: + assert all([x['type'] == 'text' for x in inputs]) + text = '\n'.join([x['value'] for x in inputs]) + content_list = [dict(type='text', text=text)] + return content_list + + def prepare_inputs(self, inputs): + input_msgs = [] + assert isinstance(inputs, list) and isinstance(inputs[0], dict) + assert np.all(['type' in x for x in inputs]) or np.all(['role' in x for x in inputs]), inputs + if 'role' in inputs[0]: + assert inputs[-1]['role'] == 'user', inputs[-1] + for item in inputs: + input_msgs.append(dict(role=item['role'], content=self.prepare_itlist(item['content']))) + else: + input_msgs.append(dict(role='user', content=self.prepare_itlist(inputs))) + return input_msgs + + def generate_inner(self, inputs, **kwargs) -> str: + payload = { + 'model': self.model, + 'max_tokens': self.max_tokens, + 'messages': self.prepare_inputs(inputs), + **kwargs + } + if self.system_prompt is not None: + payload['system'] = self.system_prompt + + response = requests.request( + 'POST', self.url, headers=self.headers, data=json.dumps(payload), timeout=self.timeout * 1.1 + ) + ret_code = response.status_code + ret_code = 0 if (200 <= int(ret_code) < 300) else ret_code + answer = self.fail_msg + + try: + resp_struct = json.loads(response.text) + if self.backend == 'alles': + answer = resp_struct['data']['content'][0]['text'].strip() + elif self.backend == 'official': + answer = resp_struct['content'][0]['text'].strip() + except Exception as err: + if self.verbose: + logger.error(f'{type(err)}: {err}') + logger.error(response.text if hasattr(response, 'text') else response) + + return ret_code, answer, response + + +class Claude3V(Claude_Wrapper): + + def generate(self, message, dataset=None): + return super(Claude_Wrapper, self).generate(message) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/cloudwalk.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/cloudwalk.py new file mode 100644 index 00000000..6cbd60bc --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/cloudwalk.py @@ -0,0 +1,110 @@ +import json +import os + +import numpy as np +import requests + +from vlmeval.api.base import BaseAPI +from vlmeval.smp import encode_image_to_base64, get_logger + +logger = get_logger(__name__) + + +class CWWrapper(BaseAPI): + + is_api: bool = True + + def __init__(self, + model: str = 'cw-congrong-v2.0', + retry: int = 10, + key: str = None, + verbose: bool = True, + system_prompt: str = None, + temperature: float = 0, + timeout: int = 600, + api_base: str = '', + max_tokens: int = 2048, + img_detail: str = 'low', + **kwargs): + + self.model = model + self.cur_idx = 0 + self.fail_msg = 'Failed to obtain answer via API. ' + self.max_tokens = max_tokens + self.temperature = temperature + + base = os.environ.get('CW_API_BASE', None) + self.api_base = base if base is not None else api_base + + env_key = os.environ.get('CW_API_KEY', None) + self.key = env_key if env_key is not None else key + assert self.key is not None, 'API key not provided. Please set CW_API_KEY environment variable or \ + pass it to the constructor.' + + assert img_detail in ['high', 'low'] + self.img_detail = img_detail + + self.vision = True + self.timeout = timeout + + super().__init__(retry=retry, system_prompt=system_prompt, verbose=verbose, **kwargs) + + # inputs can be a lvl-2 nested list: [content1, content2, content3, ...] + # content can be a string or a list of image & text + def prepare_inputs(self, inputs): + input_msgs = [] + if self.system_prompt is not None: + input_msgs.append(dict(role='system', content=self.system_prompt)) + has_images = np.sum([x['type'] == 'image' for x in inputs]) + if has_images: + content_list = [] + for msg in inputs: + if msg['type'] == 'text': + content_list.append(dict(type='text', text=msg['value'])) + elif msg['type'] == 'image': + from PIL import Image + img = Image.open(msg['value']) + b64 = encode_image_to_base64(img) + img_struct = dict(url=f"data:image/jpeg;base64,{b64}", detail=self.img_detail) + content_list.append(dict(type='image_url', image_url=img_struct)) + input_msgs.append(dict(role='user', content=content_list)) + else: + assert all([x['type'] == 'text' for x in inputs]) + text = '\n'.join([x['value'] for x in inputs]) + input_msgs.append(dict(role='user', content=text)) + return input_msgs + + def generate_inner(self, inputs, **kwargs): + input_msgs = self.prepare_inputs(inputs) + temperature = kwargs.pop('temperature', self.temperature) + max_tokens = kwargs.pop('max_tokens', self.max_tokens) + + if 0 < max_tokens <= 100: + logger.warning( + 'Less than 100 tokens left, ' + 'may exceed the context window with some additional meta symbols. ' + ) + if max_tokens <= 0: + return 0, self.fail_msg + 'Input string longer than context window. ', 'Length Exceeded. ' + + headers = {'Content-Type': 'application/json', 'Authorization': f'{self.key}'} + payload = dict( + model=self.model, + messages=input_msgs, + max_tokens=max_tokens, + n=1, + temperature=temperature, + **kwargs) + response = requests.post(self.api_base, headers=headers, data=json.dumps(payload), timeout=self.timeout * 1.1) + ret_code = response.status_code + ret_code = 0 if (200 <= int(ret_code) < 300) else ret_code + answer = self.fail_msg + try: + resp_struct = json.loads(response.text) + answer = resp_struct['choices'][0]['message']['content'].strip() + except Exception as err: + if self.verbose: + logger.error(f'{type(err)}: {err}') + logger.error(response.text if hasattr(response, 'text') else response) + + return ret_code, answer, response diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/cosmos_reason.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/cosmos_reason.py new file mode 100644 index 00000000..69f8c70f --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/cosmos_reason.py @@ -0,0 +1,774 @@ +import copy +import os +import random +import re +import string +from typing import Literal + +import requests + +import numpy as np +from qwen_vl_utils import process_vision_info + +from vlmeval.dataset import img_root_map +from ..dataset import DATASET_MODALITY, DATASET_TYPE +from ..smp import * +from ..utils.video_cache import get_video_cache +from .base import BaseAPI + +logger = get_logger(__name__) + + +def build_multi_choice_prompt(line, dataset=None): + question = line['question'] + hint = line['hint'] if ('hint' in line and not pd.isna(line['hint'])) else None + if hint is not None: + question = hint + '\n' + question + + options = { + cand: line[cand] + for cand in string.ascii_uppercase + if cand in line and not pd.isna(line[cand]) + } + for key, item in options.items(): + question += f'\n{key}. {item}' + prompt = question + + if len(options): + if listinstr(['MMMU'], dataset): + prompt += '\n请直接回答选项字母。' if cn_string(prompt) else "\nPlease answer directly with only the letter of the correct option and nothing else." + else: + prompt += '\n请直接回答选项字母。' if cn_string(prompt) else "\nPlease answer directly with only the letter of the correct option and nothing else. " + else: + if listinstr(['MMMU'], dataset): + prompt += '\n请直接回答问题。' if cn_string(prompt) else '\nAnswer the question directly.' + else: + prompt += '\n请直接回答问题。' if cn_string(prompt) else '\nAnswer the question directly.' + + return prompt + + +APIBASES = { + # 'OFFICIAL': 'https:///v1/chat/completions', +} + + +import base64 +import subprocess +import threading + +import torch + + +def tensor_video_to_base64( + video_tensor: torch.Tensor, + fps: int, + crf: int = 0, # NOTE: yilzhao: the quality of writing the video is important since artifacts may be introduced and lead to wrong answers, for example, with crf=14, tailgating f1=0.7, with crf=0, tailgating f1=0.74 + base64_decode_code: str = "utf-8" +) -> str: + """ + Convert a (T, C, H, W) RGB tensor to an MP4 data URL using ffmpeg via pipes. + - video_tensor: RGB in [0,255] or [0,1], shape (T,C,H,W), C=3 + - fps: frames per second, fps could not be infered from video_tensor, so it is required + - crf: quality (lower is better; 18–28 reasonable) + """ + assert video_tensor.ndim == 4, "Expected (T,C,H,W)" + T, C, H, W = video_tensor.shape + assert C == 3, "Expected 3 channels (RGB)" + + # Ensure uint8 RGB [0,255] + if video_tensor.dtype != torch.uint8: + # If it's float, assume [0,1] or [0,255]; clamp & scale safely + v = video_tensor + if torch.is_floating_point(v) and v.max() <= 1.0: + v = (v * 255.0) + video_u8 = v.clamp(0, 255).to(torch.uint8) + else: + video_u8 = video_tensor + + # (T,C,H,W) -> (T,H,W,C), contiguous + video_thwc = video_u8.permute(0, 2, 3, 1).contiguous() + + # ffmpeg command: read raw rgb24, encode H.264, write MP4 to stdout + # Use fragmented MP4 so MP4 can be streamed to stdout (no moov at end) + cmd = [ + "ffmpeg", + "-threads", "1", + "-loglevel", "error", + "-f", "rawvideo", + "-pix_fmt", "rgb24", + "-s", f"{W}x{H}", + "-r", str(fps), + "-i", "-", # stdin + "-an", + "-vcodec", "libx264", + "-preset", "fast", + "-crf", str(crf), + "-pix_fmt", "yuv420p", # broad compatibility + "-movflags", "frag_keyframe+empty_moov", + "-f", "mp4", + "-" # stdout + ] + proc = subprocess.Popen(cmd, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, bufsize=10**6) + try: + out, err = proc.communicate(input=video_thwc.numpy().tobytes(), timeout=120) + except subprocess.TimeoutExpired: + proc.kill() + proc.communicate() + raise RuntimeError(f"ffmpeg timed out after 120s (shape={video_tensor.shape}, fps={fps})") + except Exception as e: + raise RuntimeError(f"ffmpeg communication failed: {e}") + + if proc.returncode != 0: + err_msg = err.decode('utf-8', errors='ignore') if err else "No error output" + raise RuntimeError(f"ffmpeg failed (returncode={proc.returncode}): {err_msg}\nVideo shape: {video_tensor.shape}, fps={fps}") + + video_base64 = base64.b64encode(out).decode(base64_decode_code) + return video_base64 + + +def process_video_info_to_video_url( + video_path, + image_patch_size=16, + use_cache=True, + **kwargs, +): + """ + Process video to base64 data URL with caching support. + + Args: + video_path: Path to video file + image_patch_size: Patch size for video processing + use_cache: Whether to use cache (default: True) + **kwargs: Additional processing parameters (fps, nframes, etc.) + + Returns: + tuple: (video_url, video_kwargs) + """ + # Check cache first if enabled + if use_cache: + cache = get_video_cache() + cached_result = cache.get(video_path, image_patch_size=image_patch_size, **kwargs) + if cached_result[0] is not None: + # Normalize for current vLLM (same rules as the miss path below). + # Older cache entries may have list-form fps and/or do_resize set. + cached_kwargs = cached_result[1] or {} + cached_kwargs.setdefault("do_sample_frames", False) + if isinstance(cached_kwargs.get("fps"), list): + cached_kwargs["fps"] = cached_kwargs["fps"][0] + cached_kwargs.pop("do_resize", None) + return cached_result[0], cached_kwargs + # Cache miss or caching disabled - process video + messages = [ + { + "role": "user", + "content": [ + { + "type": "video", + "video": video_path, + **kwargs, + } + ] + } + ] + # NOTE: tensor is large (1-2GB for 60s video at 1fps) + # This is the memory bottleneck - caching avoids this entirely + _, video_inputs, video_kwargs = process_vision_info(messages, return_video_kwargs=True, image_patch_size=image_patch_size) + video_base64 = tensor_video_to_base64( + video_inputs[0], + video_kwargs['fps'][0] + ) + video_url = f"data:video/mp4;base64,{video_base64}" + + # vLLM Qwen3-VL needs scalar fps (qwen-vl-utils returns list) and rejects + # do_resize (removed in vLLM PR #26193). Normalize before cache.set so + # cache-hit retries see the same shape we send on the live request. + video_kwargs.setdefault("do_sample_frames", False) + if isinstance(video_kwargs.get("fps"), list): + video_kwargs["fps"] = video_kwargs["fps"][0] + video_kwargs.pop("do_resize", None) + + if use_cache: + cache.set(video_path, video_url, video_kwargs, image_patch_size=image_patch_size, **kwargs) + + return video_url, video_kwargs + + +def _process_video_with_timeout( + video_path, + image_patch_size, + use_cache, + kwargs, + timeout=300, +): + """Run process_video_info_to_video_url on a daemon thread with a hard timeout. + + Needed because decord.VideoReader can stall indefinitely on non-faststart + MP4s over NFS; SIGALRM cannot interrupt C-extension blocking I/O. + """ + result: list = [None, None] # [payload, exception] + + def _worker(): + try: + result[0] = process_video_info_to_video_url( + video_path, + image_patch_size=image_patch_size, + use_cache=use_cache, + **kwargs, + ) + except Exception as e: + result[1] = e + + t = threading.Thread(target=_worker, daemon=True) + t.start() + t.join(timeout=timeout) + if t.is_alive(): + raise RuntimeError( + f"Video processing timed out after {timeout}s for {video_path}" + ) + if result[1] is not None: + raise result[1] + return result[0] + + +class CosmosReason(BaseAPI): + is_api: bool = True + VIDEO_LLM: bool = True # sometimes used by video datasets + # inputs can be a lvl-2 nested list: [content1, content2, content3, ...] + # content can be a string or a list of image & text + + # video llm, default settings + # NOTE: the priority of this set of video is lower than the one in video datasets, suggested to use total_pixels only for corpus videos (varying length and resolution) while using dataset specific setting for the others when dealing with certain use case (e.g. same length and resolution) + nframes: int = None + fps: float = None + total_pixels: int = None + max_pixels: int = None + min_pixels: int = None + max_frames: int = None + + def __init__(self, + model: str = 'qwen3_30b_a3b', + retry: int = 5, + wait: int = 5, + key: str = None, + verbose: bool = False, + system_prompt: str = None, + temperature: float = 0, + top_p: float = None, + top_k: int = None, + repetition_penalty: float = None, + presence_penalty: float = None, + timeout: int = 60, + api_base: str = None, + max_tokens: int = 2048, + seed: int = 1, + img_size: int = -1, + img_detail: str = 'high', + use_video_cache: bool = True, + **kwargs): + self.model = model + self.cur_idx = 0 + self.fail_msg = 'Failed to obtain answer via API. ' + self.max_tokens = max_tokens + self.temperature = temperature + self.top_p = top_p + self.top_k = top_k + self.repetition_penalty = repetition_penalty + self.presence_penalty = presence_penalty + self.seed = seed + + key = os.environ.get('COSMOS_API_KEY', '') + + self.key = key + assert img_size > 0 or img_size == -1 + self.img_size = img_size + assert img_detail in ['high', 'low'] + self.img_detail = img_detail + self.timeout = timeout + # args for video processing + self.nframes = kwargs.pop('nframes', None) + self.fps = kwargs.pop('fps', None) + self.total_pixels = kwargs.pop('total_pixels', None) + self.max_pixels = kwargs.pop('max_pixels', None) + self.min_pixels = kwargs.pop('min_pixels', None) + self.max_frames = kwargs.pop('max_frames', None) + self.use_nim = kwargs.pop('use_nim', False) + self.chat_template_kwargs = kwargs.pop('chat_template_kwargs', None) + self.response_format = kwargs.pop('response_format', None) + # Generic payload passthrough for vLLM-specific fields (e.g. structured_outputs). + self.extra_body = kwargs.pop('extra_body', None) + # Default Qwen3 / Reason2 setup. + self.image_patch_size = kwargs.pop('image_patch_size', None) + self.key = key + assert img_size > 0 or img_size == -1 + self.img_size = img_size + assert img_detail in ['high', 'low'] + self.img_detail = img_detail + self.timeout = timeout + self.use_video_cache = use_video_cache + + self.api_base = api_base + + # Track the last error type for inference stats reporting + self._last_error_type = None + + super().__init__(wait=wait, retry=retry, system_prompt=system_prompt, verbose=verbose, **kwargs) + logger.info(f'Using API Base: {self.api_base}; API Key: {self.key}') + + # Check if endpoint is accessible + self._check_endpoint_health() + + # Resolve model name from endpoint (auto-detect if declared name doesn't match) + self._resolve_model_name() + + def _resolve_model_name(self, max_retries: int = 5, retry_wait: int = 10): + """Query /v1/models and remap self.model if declared name doesn't match. + + Retries on transient errors (timeout, connection) up to max_retries times. + Raises RuntimeError if all retries are exhausted or the endpoint has no models. + """ + import time + models_url = self.api_base.rsplit('/chat/completions', 1)[0] + '/models' + last_error = None + for attempt in range(1, max_retries + 1): + try: + resp = requests.get(models_url, headers={'Authorization': f'Bearer {self.key}'}, timeout=10) + resp.raise_for_status() + models = resp.json().get('data', []) + if not models: + raise RuntimeError( + f'No models found at endpoint {models_url}. ' + f'Please verify the endpoint is serving a model at {self.api_base}' + ) + server_model = models[0]['id'] + if server_model != self.model: + logger.warning( + f'Model name remapped: {self.model!r} -> {server_model!r} (auto-detected from endpoint)' + ) + self.model = server_model + if len(models) > 1: + ids = [m['id'] for m in models] + logger.warning(f'Multiple models at endpoint: {ids}. Using first: {self.model!r}') + return + except RuntimeError: + raise + except Exception as e: + last_error = e + logger.warning( + f'Failed to query /v1/models (attempt {attempt}/{max_retries}): {e}' + ) + if attempt < max_retries: + time.sleep(retry_wait) + raise RuntimeError( + f'Cannot resolve model from endpoint after {max_retries} attempts: {last_error}. ' + f'Please verify the endpoint is serving a model at {self.api_base}' + ) from last_error + + def _check_endpoint_health(self): + """Check if the API endpoint is accessible""" + try: + # Try a simple GET request to check if endpoint is up + response = requests.get(self.api_base.rsplit('/', 1)[0], timeout=5) + logger.info(f'✓ Endpoint health check passed: {self.api_base}') + except requests.exceptions.Timeout: + logger.warning(f'⚠️ WARNING: Endpoint health check timed out: {self.api_base}') + logger.warning(f'⚠️ The endpoint may be slow or down. Evaluation will proceed but may fail.') + except requests.exceptions.ConnectionError: + logger.error(f'⚠️ ERROR: Cannot connect to endpoint: {self.api_base}') + logger.error(f'⚠️ Please verify the endpoint is running before starting evaluation.') + logger.error(f'⚠️ Evaluation will proceed but all requests will likely fail.') + except Exception as e: + logger.warning(f'⚠️ WARNING: Endpoint health check failed: {type(e).__name__}: {str(e)}') + + def use_custom_prompt(self, dataset): + assert dataset is not None + if listinstr(['BlinkSpatial', 'BlinkDepth', 'RealWorldQA', 'MMBench'], dataset): + return True + if DATASET_TYPE(dataset) == 'MCQ' or DATASET_TYPE(dataset).startswith('MCQ'): + # Use VLMEvalKit's standard MCQ prompt for fair benchmark comparison. + # CosmosReason's "only the letter...nothing else" directive suppresses + # model reasoning and causes accuracy drop on MCQ benchmarks such as MMMU. + return False + if listinstr([ + 'SparBench', 'EgoPlanBench2', 'CVBench', 'EmbSpatialBench', + 'RoboSpatialHome', 'RefSpatial', 'SATBench', 'Where2Place', + 'CosmosERQA', 'TemporalLocalization', 'AETCBench', + 'VANTAGE_SOT', 'MetropolisEventVerification', + 'MetropolisVQA', 'VANTAGE_VQA', + 'CountBenchQA', + 'AVSpecialOODReasoningBench', + ], dataset): + return False + if listinstr(['MathVision', 'MathVerse'], dataset): + # Math VQA benchmarks (TYPE='VQA'): use dataset's default prompt (raw question only). + # CosmosReason's "Answer using a single word or phrase" suppresses reasoning, + # which is critical for multi-step math computation. + return False + if listinstr(['IFBench'], dataset): + # IFBench (TYPE='OPENENDED'): use dataset's own build_prompt() (raw prompt). + # CosmosReason's OPENENDED fallthrough sets max_new_tokens=128, far too low + # for instruction-following tasks that require long responses. + return False + return True + + def build_prompt(self, line, dataset=None): + assert self.use_custom_prompt(dataset) + assert dataset is None or isinstance(dataset, str) + tgt_path = self.dump_image(line, dataset) + + kwargs_default = dict(do_sample=False, max_new_tokens=128, top_p=None, num_beams=1) + self.kwargs = kwargs_default + + if dataset is not None and DATASET_TYPE(dataset) == 'Y/N': + question = line['question'] + if listinstr(['MME'], dataset): + prompt = question + ' Answer the question using a single word or phrase.' + elif listinstr(['HallusionBench', 'AMBER'], dataset): + prompt = question + ' Please answer yes or no.' + else: + prompt = question + elif dataset is not None and (DATASET_TYPE(dataset) == 'MCQ' or DATASET_TYPE(dataset).startswith('MCQ')): + prompt = build_multi_choice_prompt(line, dataset) + + elif dataset is not None and DATASET_TYPE(dataset) == 'VQA': + question = line['question'] + if listinstr(['MathVista'], dataset): + prompt = question + ' Answer the question with a step-by-step process if the problem is complex, otherwise answer directly. Finally give the final answer with "The answer is ..."' + elif listinstr(['LLaVABench', 'WildVision'], dataset): + prompt = question + '\nAnswer this question in detail.' + elif listinstr(['OCRVQA', 'TextVQA', 'ChartQA', 'DocVQA', 'InfoVQA', 'OCRBench', + 'DUDE', 'SLIDEVQA', 'GQA', 'MMLongBench_DOC'], dataset): + prompt = question + '\nAnswer the question using a single word or phrase.' + elif listinstr(['Omni3D'], dataset): + prompt = question + elif listinstr(['Astro2D', 'Metropolis2D', 'Metropolis2DGrounding'], dataset): + prompt = question + else: + prompt = question + '\nAnswer the question using a single word or phrase.' + else: + prompt = line['question'] + # The base model expects image first instead of text first. + message = [dict(type='image', value=s) for s in tgt_path] + message.extend([dict(type='text', value=prompt)]) + return message + + def parse_answer(self, answer: str) -> str: + return answer + + def _get_video_processing_kwargs(self, msg) -> dict: + process_video_kwargs = copy.deepcopy(msg) + process_video_kwargs.pop('type') + process_video_kwargs.pop('value') + video_processing_kwargs_keys = ['nframes', 'fps', 'total_pixels', 'max_pixels', 'min_pixels', 'max_frames'] # if the msg provided the kwargs, do not override anything + if any(video_processing_kwargs_key in process_video_kwargs for video_processing_kwargs_key in video_processing_kwargs_keys): + return process_video_kwargs + for video_processing_kwargs_key in video_processing_kwargs_keys: + if video_processing_kwargs_value := getattr(self, video_processing_kwargs_key, None): + process_video_kwargs[video_processing_kwargs_key] = video_processing_kwargs_value + + return process_video_kwargs + + def prepare_itlist(self, inputs): + video_kwargs = None + assert np.all([isinstance(x, dict) for x in inputs]) + has_multimodal = np.sum([ + x['type'] == 'image' or x['type'] == 'video' or x['type'] == 'video_base64' for x in inputs + ]) + if has_multimodal: + content_list = [] + for msg in inputs: + if msg['type'] == 'text': + content_list.append(dict(type='text', text=msg['value'])) + elif msg['type'] == 'image': + b64 = encode_image_file_to_base64(msg['value'], target_size=self.img_size) + import mimetypes + mime_type = 'image/jpeg' if self.img_size > 0 else ( + mimetypes.guess_type(msg['value'])[0] or 'image/png') + img_struct = dict(url=f'data:{mime_type};base64,{b64}', detail=self.img_detail) + content_list.append(dict(type='image_url', image_url=img_struct)) + elif msg['type'] == 'video_base64': + # Pre-encoded video URL (avoid subprocess in multiprocessing) + content_list.append(dict( + type='video_url', + video_url=dict(url=msg['value']) + )) + elif msg['type'] == 'video': + assert self.image_patch_size is not None, "please set image_patch_size, 14 for qwen2.5vl series, 16 for qwen3.vl series" + video_url, video_kwargs = _process_video_with_timeout( + msg['value'], + image_patch_size=self.image_patch_size, + use_cache=self.use_video_cache, + kwargs=self._get_video_processing_kwargs(msg), + timeout=getattr(self, 'video_proc_timeout', 300), + ) + content_list.append(dict( # NOTE: confirm this: video being processed to ideal size + fps, recorded within the base64 encoded video bytes, assuming the server will use the video file's parameters as default + type='video_url', + video_url=dict( + url=video_url, + ) + )) + else: + assert all([x['type'] == 'text' for x in inputs]) + text = '\n'.join([x['value'] for x in inputs]) + content_list = [dict(type='text', text=text)] + return content_list, video_kwargs + + def prepare_inputs(self, inputs): + input_msgs, video_kwargs = [], None + if self.system_prompt is not None: + input_msgs.append(dict(role='system', content=self.system_prompt)) + assert isinstance(inputs, list) and isinstance(inputs[0], dict) + assert np.all(['type' in x for x in inputs]) or np.all(['role' in x for x in inputs]), inputs + if 'role' in inputs[0]: + assert inputs[-1]['role'] == 'user', inputs[-1] + for item in inputs: + output = self.prepare_itlist(item['content']) + if video_kwargs is None: + video_kwargs = output[1] + input_msgs.append(dict(role=item['role'], content=output[0])) + else: + output = self.prepare_itlist(inputs) + if video_kwargs is None: + video_kwargs = output[1] + input_msgs.append(dict(role='user', content=output[0])) + return input_msgs, video_kwargs + + def generate_inner(self, inputs, **kwargs) -> str: + self._last_error_type = None # reset per-sample; prevents leaking across samples + try: + input_msgs, video_kwargs = self.prepare_inputs(inputs) + except Exception as e: + # Non-fail_msg sentinel skips BaseAPI retry (it retries only when fail_msg in answer). + logger.warning(f'Video preparation failed (skipping): {type(e).__name__}: {e}') + return 0, 'VIDEO_LOAD_FAILED', None + kwargs.pop('dataset', None) + temperature = kwargs.pop('temperature', self.temperature) + top_p = kwargs.pop('top_p', self.top_p) + top_k = kwargs.pop('top_k', self.top_k) + repetition_penalty = kwargs.pop('repetition_penalty', self.repetition_penalty) + presence_penalty = kwargs.pop('presence_penalty', self.presence_penalty) + max_tokens = kwargs.pop('max_tokens', self.max_tokens) + seed = kwargs.pop('seed', self.seed) + # Will send request if use Azure, dk how to use openai client for it + headers = {'Content-Type': 'application/json', 'Authorization': f'Bearer {self.key}'} + payload = dict( + model=self.model, + messages=input_msgs, + n=1, + temperature=temperature, + top_p=top_p, + seed=seed, + max_tokens=max_tokens, + **kwargs, + ) + if self.chat_template_kwargs is not None: + payload['chat_template_kwargs'] = self.chat_template_kwargs + if self.response_format is not None: + payload['response_format'] = self.response_format + if self.extra_body: + payload.update(self.extra_body) + + if video_kwargs is not None: + if self.use_nim: + # TODO: NIM evaluation haven't been fully aligned yet. + payload['mm_processor_kwargs'] = { + "fps": video_kwargs['fps'], + } + payload['media_io_kwargs'] = { # for NIM deployments + "fps": video_kwargs['fps'][0], + } + else: + # Verified that this branch will bring expected number of tokens to the vllm server. (from server output) + # with previously used container (vllm 0.10.0), this is correct + payload.update({ + 'mm_processor_kwargs': video_kwargs + }) + + THINKING_RETRY = 1 + for i in range(THINKING_RETRY): + payload['seed'] += i + if self.use_nim: + # TODO: NIM evaluation haven't been fully aligned yet. + payload['nvext'] = { # these go into nvext for NIMs + "top_k": top_k, + "repetition_penalty": repetition_penalty, + } + else: + payload['top_k'] = top_k + payload['repetition_penalty'] = repetition_penalty + payload['presence_penalty'] = presence_penalty + try: + response = requests.post(self.api_base, headers=headers, data=json.dumps(payload), timeout=self.timeout * 1.1) + except requests.exceptions.Timeout: + logger.error(f'⚠️ ENDPOINT TIMEOUT: {self.api_base}') + logger.error(f'⚠️ The endpoint took longer than {self.timeout}s to respond. It may be down or overloaded.') + self._last_error_type = "timeout" + return 408, self.fail_msg, None + except requests.exceptions.ConnectionError as e: + logger.error(f'⚠️ ENDPOINT DOWN: {self.api_base}') + logger.error(f'⚠️ Cannot connect to the endpoint. Please check if the service is running.') + logger.error(f'⚠️ Error: {str(e)}') + self._last_error_type = "http_error" + return 503, self.fail_msg, None + except Exception as e: + logger.error(f'⚠️ REQUEST FAILED: {type(e).__name__}: {str(e)}') + self._last_error_type = "http_error" + return 500, self.fail_msg, None + + + ret_code = response.status_code + ret_code = 0 if (200 <= int(ret_code) < 300) else ret_code + + # Check for common error status codes + if ret_code == 401: + logger.error(f'⚠️ AUTHENTICATION FAILED (401): Invalid API key for {self.api_base}') + elif ret_code == 403: + logger.error(f'⚠️ ACCESS FORBIDDEN (403): API key lacks permissions for {self.api_base}') + elif ret_code == 404: + logger.error(f'⚠️ ENDPOINT NOT FOUND (404): {self.api_base}') + elif ret_code == 429: + logger.error(f'⚠️ RATE LIMIT EXCEEDED (429): Too many requests to {self.api_base}') + elif ret_code == 500: + logger.error(f'⚠️ SERVER ERROR (500): The endpoint {self.api_base} encountered an internal error') + elif ret_code == 503: + logger.error(f'⚠️ SERVICE UNAVAILABLE (503): {self.api_base} is temporarily unavailable') + elif ret_code != 0: + logger.error(f'⚠️ HTTP ERROR ({ret_code}): Request to {self.api_base} failed') + logger.error(f'⚠️ Response: {response.text[:1000] if hasattr(response, "text") else "N/A"}') + + if ret_code in (408, 504): + self._last_error_type = "timeout" + elif ret_code != 0: + self._last_error_type = "http_error" + + answer = self.fail_msg + try: + resp_struct = json.loads(response.text) + answer = resp_struct['choices'][0]['message']['content'].strip() + finish_reason = resp_struct['choices'][0]['finish_reason'] + if finish_reason == 'stop': + if self.verbose: + logger.info(f"prompt: {payload['messages'][0]['content'][-1]}") + logger.info(f'API Response: {answer}') + answer = self.parse_answer(answer) + if self._last_error_type is not None and self._last_error_type != "truncated": + logger.info(f'✅ Recovered after {self._last_error_type}') + self._last_error_type = None + break + else: + self._last_error_type = "truncated" + logger.info(f"⚠️ Answer truncated at max_tokens={payload['max_tokens']} (finish_reason={finish_reason}).") + except Exception as err: + if self.verbose: + logger.error(f'{type(err)}: {err}') + logger.error(response.text if hasattr(response, 'text') else response) + return ret_code, answer, response + + def generate(self, message, **kwargs): + """Wrap parent generate() to record per-sample inference outcome.""" + result = super().generate(message, **kwargs) + stats_file = os.environ.get("INFERENCE_STATS_FILE") + if stats_file: + is_success = result is not None and self.fail_msg not in str(result) + # Truncated responses return valid content (for scoring) but are still + # tracked as "truncated" so we can monitor max_tokens adequacy. + if self._last_error_type == "truncated": + category = "truncated" + elif is_success: + category = "success" + else: + category = self._last_error_type or "other" + try: + with open(stats_file, "a") as f: + f.write(json.dumps({"outcome": category}) + "\n") + except Exception: + pass + return result + + +class CosmosReason1(CosmosReason): + + def __init__(self, model: str = 'cosmos_reason1_7b', **kwargs): + image_patch_size = 14 + super().__init__(model=model, image_patch_size=image_patch_size, **kwargs) + + def parse_answer(self, answer: str) -> str: + pattern = r"^([^<]*(?:<(?!/?think>)[^<]*)*)<\/think>(?:\n|\n\n| |)([\s\S]*?)<\/answer>$" + match = re.search(pattern, answer, re.DOTALL) + if match: + return match.group(2).strip() + else: + return answer + +class CosmosReason1Think(CosmosReason1): + def prepare_inputs(self, inputs): + # NOTE: yilzhao: following cosmos reason1 thinking convention, adding the thinking prompt to the system prompt + input_msgs, video_kwargs = super().prepare_inputs(inputs) + reasoning_prompt = "\n".join([ + "", + "Make your response in the following format:", + "", + "your reasoning", + "", + "", + "your answer", + "", + ]) + for msg in input_msgs: + if msg['role'] == 'system': + if isinstance(msg['content'], list): + for content in msg['content']: + if content['type'] == 'text': + content['text'] += reasoning_prompt + elif isinstance(msg['content'], str): + msg['content'] += reasoning_prompt + return input_msgs, video_kwargs + +class CosmosReason2(CosmosReason): + + def __init__(self, model: str = 'qwen3_30b_a3b', **kwargs): + image_patch_size = 16 + super().__init__(model=model, image_patch_size=image_patch_size, **kwargs) + + def parse_answer(self, answer: str) -> str: + pattern = r"^([^<]*(?:<(?!/?think>)[^<]*)*)<\/think>(?:\n|\n\n| |)([\s\S]*?)$" + match = re.search(pattern, answer, re.DOTALL) + if match: + return match.group(2).strip() # answer + else: + return answer + + +class CosmosReason2Think(CosmosReason): + def __init__(self, model: str = 'qwen3_30b_a3b', **kwargs): + image_patch_size = 16 + self.pattern = r"^(.*)<\/think>(.+)$" + super().__init__(model=model, image_patch_size=image_patch_size, **kwargs) + + def parse_answer(self, answer: str) -> str: + # pattern = r"^([^<]*(?:<(?!/?think>)[^<]*)*)<\/think>(?:\n|\n\n| |)([\s\S]*?)$" + # match = re.search(pattern, answer, re.DOTALL) + match = re.match(self.pattern, answer, re.DOTALL) + if match: + return match.group(2).strip() # answer + else: + return answer + + def prepare_inputs(self, inputs): + input_msgs, video_kwargs = super().prepare_inputs(inputs) + for msg in input_msgs: + if msg['role'] == 'user': + for content in msg['content']: + if content['type'] == 'text': + content['text'] += '\nPlease provide your detailed reasoning within and tags before giving the final answer.' + return input_msgs, video_kwargs + +class Qwen3VLThink(CosmosReason2Think): + def parse_answer(self, answer: str) -> str: + # NOTE: yilzhao: Qwen3VLThink series do not return the first token since it is embedded, overriding the parsing logic to handle the different format of return + pattern = r"^([^<]*(?:<(?!/?think>)[^<]*)*)<\/think>(?:\n|\n\n| |)([\s\S]*?)$" + match = re.search(pattern, answer, re.DOTALL) + if match: + return match.group(2).strip() # answer + else: + return answer diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/doubao_vl_api.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/doubao_vl_api.py new file mode 100644 index 00000000..44f24de9 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/doubao_vl_api.py @@ -0,0 +1,209 @@ +import os +import os.path as osp + +import numpy as np +from openai import OpenAI + +from vlmeval.api.base import BaseAPI +from vlmeval.dataset import img_root_map +from vlmeval.smp import (LMUDataRoot, decode_base64_to_image_file, encode_image_to_base64, + get_logger, read_ok, toliststr) + +logger = get_logger(__name__) + + +class DoubaoVLWrapper(BaseAPI): + + is_api: bool = True + + def __init__(self, + model: str = '', + retry: int = 5, + verbose: bool = True, + system_prompt: str = None, + temperature: float = 0, + timeout: int = 60, + max_tokens: int = 4096, + api_base: str = 'https://ark.cn-beijing.volces.com/api/v3', # 使用系统推荐的服务区域地址 + **kwargs): + + self.model = model # This variable is unused + self.cur_idx = 0 + self.fail_msg = 'Failed to obtain answer via API. ' + self.temperature = temperature + self.max_tokens = max_tokens + + assert 'DOUBAO_VL_KEY' in os.environ, 'You may need to set the env variable DOUBAO_VL_KEY to use DOUBAO_VL.' + + key = os.environ.get('DOUBAO_VL_KEY', None) + assert key is not None, 'Please set the environment variable DOUBAO_VL_KEY. ' + self.key = key + + assert api_base is not None, 'Please set the variable API_BASE. ' + self.api_base = api_base + self.timeout = timeout + + super().__init__(retry=retry, system_prompt=system_prompt, verbose=verbose, **kwargs) + + # Models that require an EP + # assert self.model in ['Doubao-1.5-vision-pro', 'doubao-1-5-thinking-vision-pro-250428'] + EP_KEY = 'DOUBAO_VL_ENDPOINT' + '_' + self.model.replace('.', '_').replace('-', '_').upper() + endpoint = os.getenv(EP_KEY, None) + + if endpoint is not None: + self.endpoint = endpoint + else: + logger.warning( + f'Endpoint for model {model} is not set (can be set w. environment var {EP_KEY}. ' + f'By default, we will use the model name {model} as the EP if not set. ' + ) + self.endpoint = model + + self.client = OpenAI( + api_key=self.key, + base_url=self.api_base, + timeout=self.timeout + ) + + logger.info(f'Using API Base: {self.api_base}; End Point: {self.endpoint}; API Key: {self.key}') + + def dump_image(self, line, dataset): + """Dump the image(s) of the input line to the corresponding dataset folder. + + Args: + line (line of pd.DataFrame): The raw input line. + dataset (str): The name of the dataset. + + Returns: + str | list[str]: The paths of the dumped images. + """ + ROOT = LMUDataRoot() + assert isinstance(dataset, str) + + img_root = os.path.join(ROOT, 'images', img_root_map(dataset) if dataset in img_root_map(dataset) else dataset) + os.makedirs(img_root, exist_ok=True) + if 'image' in line: + if isinstance(line['image'], list): + tgt_path = [] + assert 'image_path' in line + for img, im_name in zip(line['image'], line['image_path']): + path = osp.join(img_root, im_name) + if not read_ok(path): + decode_base64_to_image_file(img, path) + tgt_path.append(path) + else: + tgt_path = osp.join(img_root, f"{line['index']}.jpg") + if not read_ok(tgt_path): + decode_base64_to_image_file(line['image'], tgt_path) + tgt_path = [tgt_path] + else: + assert 'image_path' in line + tgt_path = toliststr(line['image_path']) + + return tgt_path + + def use_custom_prompt(self, dataset_name): + if dataset_name == 'MathVerse_MINI_Vision_Only': + return True + else: + return False + + def build_prompt(self, line, dataset: str) -> list[dict[str, str]]: + + if dataset in {'MathVerse_MINI_Vision_Only'}: + return self. _build_mathVerse_mini_vision_only_prompt(line, dataset) + raise ValueError(f'Unsupported dataset: {dataset}') + + def _build_mathVerse_mini_vision_only_prompt(self, line, dataset=None): + assert self.use_custom_prompt(dataset) + assert dataset is None or isinstance(dataset, str) + + tgt_path = self.dump_image(line, dataset) + + question = line['question'] + + # remove 'directly' from the prompt, so the model will answer the question in Chain-of-Thought (CoT) manner + prompt = question.replace('directly', '', 1) + + msgs = [] + if isinstance(tgt_path, list): + msgs.extend([dict(type='image', value=p) for p in tgt_path]) + else: + msgs = [dict(type='image', value=tgt_path)] + msgs.append(dict(type='text', value=prompt)) + return msgs + + # inputs can be a lvl-2 nested list: [content1, content2, content3, ...] + # content can be a string or a list of image & text + def prepare_itlist(self, inputs): + assert np.all([isinstance(x, dict) for x in inputs]) + has_images = np.sum([x['type'] == 'image' for x in inputs]) + if has_images: + content_list = [] + for msg in inputs: + if msg['type'] == 'text': + content_list.append(dict(type='text', text=msg['value'])) + elif msg['type'] == 'image': + from PIL import Image + + img = Image.open(msg['value']) + b64 = encode_image_to_base64(img) + img_struct = dict(url=f'data:image/jpeg;base64,{b64}') + content_list.append(dict(type='image_url', image_url=img_struct)) + else: + assert all([x['type'] == 'text' for x in inputs]) + text = '\n'.join([x['value'] for x in inputs]) + content_list = [dict(type='text', text=text)] + return content_list + + def prepare_inputs(self, inputs): + input_msgs = [] + if self.system_prompt is not None: + input_msgs.append(dict(role='system', content=self.system_prompt)) + assert isinstance(inputs, list) and isinstance(inputs[0], dict) + assert np.all(['type' in x for x in inputs]) or np.all(['role' in x for x in inputs]), inputs + if 'role' in inputs[0]: + assert inputs[-1]['role'] == 'user', inputs[-1] + for item in inputs: + input_msgs.append(dict(role=item['role'], content=self.prepare_itlist(item['content']))) + else: + input_msgs.append(dict(role='user', content=self.prepare_itlist(inputs))) + return input_msgs + + def generate_inner(self, inputs, **kwargs) -> str: + + input_msgs = self.prepare_inputs(inputs) + temperature = kwargs.pop('temperature', self.temperature) + max_tokens = kwargs.pop('max_tokens', self.max_tokens) + + ret_code = -1 + answer = self.fail_msg + response = None + payload = dict(model=self.endpoint, messages=input_msgs, max_tokens=max_tokens, temperature=temperature) + try: + response = self.client.chat.completions.create(**payload) + answer = response.choices[0].message.content.strip() + ret_code = 0 + except Exception as err: + logger.error(f'{type(err)}: {err}') + logger.error(response.text if hasattr(response, 'text') else response) + + return ret_code, answer, response + + +class DoubaoVL(DoubaoVLWrapper): + + def generate(self, message, dataset=None): + return super(DoubaoVL, self).generate(message) + + +if __name__ == '__main__': + # export DOUBAO_VL_KEY='' + # export DOUBAO_VL_ENDPOINT='' + model = DoubaoVLWrapper(verbose=True) + inputs = [ + {'type': 'image', 'value': './assets/apple.jpg'}, + {'type': 'text', 'value': '请详细描述一下这张图片。'}, + ] + code, answer, resp = model.generate_inner(inputs) + print(code, answer, resp) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/gcp_vertex.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/gcp_vertex.py new file mode 100644 index 00000000..1b4d646d --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/gcp_vertex.py @@ -0,0 +1,211 @@ +""" +GCP (Google Cloud Platform) Vertex AI API support. +Single class for both Gemini and Claude on Vertex. Backend is chosen by model name: +- model starts with "claude-" -> AnthropicVertex (anthropic[vertex]) +- else -> Vertex AI GenerativeModel (google-cloud-aiplatform) +Uses Application Default Credentials (ADC). Set GOOGLE_CLOUD_PROJECT and optionally GOOGLE_CLOUD_LOCATION. + +Gemini: pip install google-cloud-aiplatform +Claude: pip install -U google-cloud-aiplatform "anthropic[vertex]" +""" +import mimetypes +import os +import os.path as osp + +import numpy as np + +from ..smp import encode_image_to_base64, get_logger +from .base import BaseAPI + +# Vertex AI (Gemini) +try: + import vertexai + from vertexai.generative_models import GenerationConfig, GenerativeModel + from vertexai.generative_models import Image as VertexImage + from vertexai.generative_models import Part +except ImportError: + vertexai = None + GenerativeModel = Part = GenerationConfig = VertexImage = None + +# Claude on Vertex +try: + from anthropic import AnthropicVertex +except ImportError: + AnthropicVertex = None + +logger = get_logger(__name__) + + +def _encode_image_file_to_base64(image_path, target_size=-1, fmt=".jpg"): + from PIL import Image + + image = Image.open(image_path) + if fmt in (".jpg", ".jpeg"): + pil_fmt = "JPEG" + elif fmt == ".png": + pil_fmt = "PNG" + else: + pil_fmt = "JPEG" + return encode_image_to_base64(image, target_size=target_size, fmt=pil_fmt) + + +def _is_claude_model(model: str) -> bool: + return model.strip().lower().startswith("claude-") + + +class GCPVertexAPI(BaseAPI): + """Single API for GCP Vertex AI: Gemini or Claude, chosen by model name (claude-* -> Claude).""" + + is_api: bool = True + + def __init__( + self, + model: str = "gemini-1.5-flash", + project_id: str = None, + location: str = None, + retry: int = 10, + wait: int = 1, + system_prompt: str = None, + verbose: bool = True, + temperature: float = 0, + max_tokens: int = 2048, + **kwargs, + ): + self.model = model + self._is_claude = _is_claude_model(model) + self.project_id = project_id or os.environ.get("GOOGLE_CLOUD_PROJECT") + self.location = location or os.environ.get( + "GOOGLE_CLOUD_LOCATION", + "global" if self._is_claude else "us-central1", + ) + self.temperature = temperature + self.max_tokens = max_tokens + + if not self.project_id: + raise ValueError( + "GCP project_id is required. Set GOOGLE_CLOUD_PROJECT or pass project_id=..." + ) + + super().__init__( + retry=retry, + wait=wait, + system_prompt=system_prompt, + verbose=verbose, + **kwargs, + ) + + if self._is_claude: + if AnthropicVertex is None: + raise ImportError( + "Claude on Vertex requires: pip install -U google-cloud-aiplatform \"anthropic[vertex]\"" + ) + self._client = AnthropicVertex( + project_id=self.project_id, + region=self.location, + ) + self._gemini_model = None + else: + if vertexai is None or GenerativeModel is None: + raise ImportError( + "GCP Vertex AI (Gemini) requires google-cloud-aiplatform. " + "Install with: pip install google-cloud-aiplatform" + ) + vertexai.init(project=self.project_id, location=self.location) + model_id = "gemini-1.0-pro-vision" if model == "gemini-1.0-pro" else model + self._gemini_model = GenerativeModel(model_id) + self._client = None + + logger.info( + f"GCPVertexAPI: model={self.model}, project={self.project_id}, " + f"location={self.location} ({'Claude' if self._is_claude else 'Gemini'})" + ) + + def _build_gemini_contents(self, inputs): + parts = [] + if self.system_prompt: + parts.append(self.system_prompt) + for inp in inputs: + if inp["type"] == "text" and inp["value"]: + parts.append(inp["value"]) + elif inp["type"] == "image": + parts.append(Part.from_image(VertexImage.load_from_file(inp["value"]))) + return parts + + def _generate_gemini(self, inputs, temperature, max_tokens): + contents = self._build_gemini_contents(inputs) + if not contents: + contents = [""] + resp = self._gemini_model.generate_content( + contents, + generation_config=GenerationConfig( + temperature=temperature, + max_output_tokens=max_tokens, + ), + ) + answer = resp.text.strip() if resp.text else self.fail_msg + return 0, answer, resp + + def _prepare_claude_content(self, inputs): + assert all(isinstance(x, dict) for x in inputs) + has_images = np.sum([x["type"] == "image" for x in inputs]) + if has_images: + content_list = [] + for item in inputs: + if item["type"] == "text" and item["value"]: + content_list.append({"type": "text", "text": item["value"]}) + elif item["type"] == "image": + pth = item["value"] + suffix = osp.splitext(pth)[-1].lower() + media_type = mimetypes.types_map.get(suffix) or "image/jpeg" + content_list.append({ + "type": "image", + "source": { + "type": "base64", + "media_type": media_type, + "data": _encode_image_file_to_base64( + pth, target_size=4096, fmt=suffix + ), + }, + }) + return content_list + text = "\n".join([x["value"] for x in inputs if x["type"] == "text"]) + return [{"type": "text", "text": text or ""}] + + def _prepare_claude_messages(self, inputs): + if inputs and "role" in inputs[0]: + return [ + {"role": item["role"], "content": self._prepare_claude_content(item["content"])} + for item in inputs + ] + return [{"role": "user", "content": self._prepare_claude_content(inputs)}] + + def _generate_claude(self, inputs, temperature, max_tokens): + messages = self._prepare_claude_messages(inputs) + create_kwargs = { + "model": self.model, + "max_tokens": max_tokens, + "messages": messages, + "temperature": temperature, + } + if self.system_prompt: + create_kwargs["system"] = self.system_prompt + resp = self._client.messages.create(**create_kwargs) + answer = ( + resp.content[0].text.strip() + if resp.content and getattr(resp.content[0], "text", None) + else self.fail_msg + ) + return 0, answer, resp + + def generate_inner(self, inputs, **kwargs): + temperature = kwargs.pop("temperature", self.temperature) + max_tokens = kwargs.pop("max_tokens", self.max_tokens) + + try: + if self._is_claude: + return self._generate_claude(inputs, temperature, max_tokens) + return self._generate_gemini(inputs, temperature, max_tokens) + except Exception as err: + if self.verbose: + logger.error(f"{type(err).__name__}: {err}") + return -1, self.fail_msg, str(err) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/gemini.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/gemini.py new file mode 100644 index 00000000..918cc679 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/gemini.py @@ -0,0 +1,213 @@ +import os +import time + +from PIL import Image + +from vlmeval.api.base import BaseAPI +from vlmeval.smp import get_logger, proxy_set + +headers = 'Content-Type: application/json' + + +logger = get_logger(__name__) + + +class GeminiWrapper(BaseAPI): + + is_api: bool = True + + def __init__(self, + model: str = 'gemini-1.0-pro', + retry: int = 5, + key: str = None, + verbose: bool = True, + temperature: float = 0.0, + system_prompt: str = None, + max_tokens: int = 2048, + proxy: str = None, + backend='genai', + project_id='vlmeval', + thinking_budget: int = None, # range from 0 to 24576 + # see https://ai.google.dev/gemini-api/docs/thinking + fps: int = 1, + media_resolution: str = None, + **kwargs): + + self.model = model + self.fail_msg = 'Failed to obtain answer via API. ' + self.max_tokens = max_tokens + self.temperature = temperature + self.thinking_budget = thinking_budget + self.fps = fps + # for image, high and medium resolution is 258 tokens per image [default], low resolution is 66 tokens per image + # for video, not support high resolution, medium resolution is 258 tokens per image [default], low resolution is 66 tokens per image # noqa: E501 + self.media_resolution = media_resolution + if self.media_resolution: + assert self.media_resolution in ['low', 'medium', 'high'] + if key is None: + key = os.environ.get('GOOGLE_API_KEY', None) + # Try to load backend from environment variable + be = os.environ.get('GOOGLE_API_BACKEND', None) + if be is not None and be in ['genai', 'vertex']: + backend = be + + assert backend in ['genai', 'vertex'] + if backend == 'genai': + # We have not evaluated Gemini-1.5 w. GenAI backend + assert key is not None # Vertex does not require API Key + try: + from google import genai + from google.genai import types + except ImportError as e: + raise ImportError( + "Could not import 'google.genai'. Please install it with:\n" + " pip install --upgrade google-genai" + ) from e + self.media_resolution_dict = { # noqa: F841 + 'low': types.MediaResolution.MEDIA_RESOLUTION_LOW, + 'medium': types.MediaResolution.MEDIA_RESOLUTION_MEDIUM, + 'high': types.MediaResolution.MEDIA_RESOLUTION_HIGH + } + self.genai = genai + self.client = genai.Client(api_key=key) + + self.backend = backend + self.project_id = project_id + self.api_key = key + + if proxy is not None: + proxy_set(proxy) + super().__init__(retry=retry, system_prompt=system_prompt, verbose=verbose, **kwargs) + + # Ensure allowed_types is set (fix for multiprocessing/state corruption) + if not hasattr(self, 'allowed_types') or self.allowed_types is None: + self.allowed_types = ['text', 'image', 'video'] + + def upload_video_genai(self, video_path): + from google.genai import types + myfile = self.client.files.upload(file=video_path) + + video_part = types.Part.from_uri( + file_uri=myfile.uri, + mime_type="video/mp4" + ) + + video_part.video_metadata = types.VideoMetadata(fps=self.fps) + + while True: + myfile = self.client.files.get(name=myfile.name) + if myfile.state == "ACTIVE": + break + time.sleep(2) + + return video_part + + def build_msgs_genai(self, inputs): + video_in_msg = False + video_parts = [] + text_and_images = [] if self.system_prompt is None else [self.system_prompt] + + for inp in inputs: + if inp['type'] == 'text': + text_and_images.append(inp['value']) + elif inp['type'] == 'image': + text_and_images.append(Image.open(inp['value'])) + elif inp['type'] == 'video': + video_file = self.upload_video_genai(inp['value']) + video_parts.append(video_file) + video_in_msg = True + + messages = video_parts + text_and_images + return messages, video_in_msg + + def build_msgs_vertex(self, inputs): + from vertexai.generative_models import Image, Part + messages = [] if self.system_prompt is None else [self.system_prompt] + for inp in inputs: + if inp['type'] == 'text': + messages.append(inp['value']) + elif inp['type'] == 'image': + messages.append(Part.from_image(Image.load_from_file(inp['value']))) + return messages + + def generate_inner(self, inputs, **kwargs) -> str: + if self.backend == 'genai': + from google.genai import types + assert isinstance(inputs, list) + model = self.model + messages, video_in_msg = self.build_msgs_genai(inputs) + + # Configure generation parameters + config_args = { + "temperature": self.temperature, + "max_output_tokens": self.max_tokens + } + # set resolution for vision input + if self.media_resolution: + if video_in_msg: + assert self.media_resolution != 'high', "For video input, only support medium and low resolution" + config_args["media_resolution"] = self.media_resolution_dict[self.media_resolution] + + # If thinking_budget is specified, add thinking_config + # By default, Gemini 2.5 Pro will automatically select + # a thinking budget not exceeding 8192 if not specified. + if self.thinking_budget is not None: + config_args["thinking_config"] = types.ThinkingConfig( + thinking_budget=self.thinking_budget + ) + config_args.update(kwargs) + + try: + resp = self.client.models.generate_content( + model=model, + contents=messages, + config=types.GenerateContentConfig(**config_args) + ) + + # Check if response has text + if hasattr(resp, 'text') and resp.text: + answer = resp.text + return 0, answer, 'Succeeded! ' + else: + # Response succeeded but no text - could be safety filter or model issue + error_msg = f'Response received but no text. ' + if hasattr(resp, 'prompt_feedback'): + error_msg += f'Prompt feedback: {resp.prompt_feedback}' + if hasattr(resp, 'candidates') and resp.candidates: + error_msg += f' Candidates: {resp.candidates}' + if self.verbose: + logger.error(error_msg) + logger.error(f'Full response object: {resp}') + return -1, '', error_msg + + except Exception as err: + if self.verbose: + logger.error(f'{type(err)}: {err}') + logger.error(f'The input messages are {inputs}.') + + return -1, '', str(err) + elif self.backend == 'vertex': + import vertexai + from vertexai.generative_models import GenerativeModel + vertexai.init(project=self.project_id, location='us-central1') + model_name = 'gemini-1.0-pro-vision' if self.model == 'gemini-1.0-pro' else self.model + model = GenerativeModel(model_name=model_name) + messages = self.build_msgs_vertex(inputs) + try: + resp = model.generate_content(messages) + answer = resp.text + return 0, answer, 'Succeeded! ' + except Exception as err: + if self.verbose: + logger.error(f'{type(err)}: {err}') + logger.error(f'The input messages are {inputs}.') + + return -1, '', '' + + +class Gemini(GeminiWrapper): + VIDEO_LLM = True + allowed_types = ['text', 'image', 'video'] # Explicitly set to avoid None issues + + def generate(self, message, dataset=None): + return super(Gemini, self).generate(message) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/glm_vision.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/glm_vision.py new file mode 100644 index 00000000..9a799786 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/glm_vision.py @@ -0,0 +1,76 @@ +import copy as cp +import os + +from vlmeval.api.base import BaseAPI +from vlmeval.smp import encode_image_file_to_base64, get_logger + +logger = get_logger(__name__) + + +class GLMVisionWrapper(BaseAPI): + + is_api: bool = True + + def __init__(self, + model: str, + retry: int = 5, + key: str = None, + verbose: bool = True, + system_prompt: str = None, + max_tokens: int = 4096, + proxy: str = None, + **kwargs): + + from zhipuai import ZhipuAI + self.model = model + self.fail_msg = 'Failed to obtain answer via API. ' + if key is None: + key = os.environ.get('GLMV_API_KEY', None) + assert key is not None, ( + 'Please set the API Key (obtain it here: ' + 'https://bigmodel.cn)' + ) + self.client = ZhipuAI(api_key=key) + super().__init__(retry=retry, system_prompt=system_prompt, verbose=verbose, **kwargs) + + def build_msgs(self, msgs_raw, system_prompt=None, dataset=None): + msgs = cp.deepcopy(msgs_raw) + content = [] + for i, msg in enumerate(msgs): + if msg['type'] == 'text': + content.append(dict(type='text', text=msg['value'])) + elif msg['type'] == 'image': + content.append(dict(type='image_url', image_url=dict(url=encode_image_file_to_base64(msg['value'])))) + if dataset in {'HallusionBench', 'POPE'}: + content.append(dict(type="text", text="Please answer yes or no.")) + ret = [dict(role='user', content=content)] + return ret + + def generate_inner(self, inputs, **kwargs) -> str: + assert isinstance(inputs, str) or isinstance(inputs, list) + inputs = [inputs] if isinstance(inputs, str) else inputs + + messages = self.build_msgs(msgs_raw=inputs, dataset=kwargs.get('dataset', None)) + + try: + response = self.client.chat.completions.create( + model=self.model, + messages=messages, + do_sample=False, + max_tokens=2048 + ) + answer = response.choices[0].message.content.strip() + if self.verbose: + logger.info(f'inputs: {inputs}\nanswer: {answer}') + return 0, answer, 'Succeeded!' + except Exception as err: + if self.verbose: + logger.error(f'{type(err)}: {err}') + logger.error(f'The input messages are {inputs}.') + return -1, self.fail_msg, '' + + +class GLMVisionAPI(GLMVisionWrapper): + + def generate(self, message, dataset=None): + return super(GLMVisionAPI, self).generate(message, dataset=dataset) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/gpt.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/gpt.py new file mode 100644 index 00000000..a80718b0 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/gpt.py @@ -0,0 +1,334 @@ +import json +import math +import os + +import numpy as np +import requests +from PIL import Image + +from vlmeval.smp import encode_image_to_base64, get_logger +from .base import BaseAPI + +logger = get_logger(__name__) + +APIBASES = { + 'OFFICIAL': 'https://api.openai.com/v1/chat/completions', +} + + +def GPT_context_window(model): + length_map = { + 'gpt-4': 8192, + 'gpt-4-0613': 8192, + 'gpt-4-turbo-preview': 128000, + 'gpt-4-1106-preview': 128000, + 'gpt-4-0125-preview': 128000, + 'gpt-4-vision-preview': 128000, + 'gpt-4-turbo': 128000, + 'gpt-4-turbo-2024-04-09': 128000, + 'gpt-3.5-turbo': 16385, + 'gpt-3.5-turbo-0125': 16385, + 'gpt-3.5-turbo-1106': 16385, + 'gpt-3.5-turbo-instruct': 4096, + } + if model in length_map: + return length_map[model] + else: + return 128000 + + +class OpenAIWrapper(BaseAPI): + + is_api: bool = True + + def __init__(self, + model: str = 'gpt-3.5-turbo-0613', + retry: int = 5, + key: str = None, + verbose: bool = False, + system_prompt: str = None, + temperature: float = 0, + timeout: int = 300, + api_base: str = None, + max_tokens: int = 2048, + img_size: int = -1, + total_img_size: int = -1, + img_detail: str = 'low', + use_azure: bool = False, + **kwargs): + + self.model = model + self.cur_idx = 0 + self.fail_msg = 'Failed to obtain answer via API. ' + self.max_tokens = max_tokens + self.temperature = temperature + self.use_azure = use_azure + + if 'step' in model: + env_key = os.environ.get('STEPAI_API_KEY', '') + if key is None: + key = env_key + elif 'yi-vision' in model: + env_key = os.environ.get('YI_API_KEY', '') + if key is None: + key = env_key + elif 'internvl2-pro' in model: + env_key = os.environ.get('InternVL2_PRO_KEY', '') + if key is None: + key = env_key + elif 'abab' in model: + env_key = os.environ.get('MiniMax_API_KEY', '') + if key is None: + key = env_key + elif 'moonshot' in model: + env_key = os.environ.get('MOONSHOT_API_KEY', '') + if key is None: + key = env_key + elif 'grok' in model: + env_key = os.environ.get('XAI_API_KEY', '') + if key is None: + key = env_key + elif 'gemini' in model and 'preview' in model and not os.environ.get('OPENAI_API_BASE'): + # Direct Google Gemini API. Skip when the user has pinned + # OPENAI_API_BASE to a different OpenAI-compatible gateway + # (e.g. NVIDIA's inference gateway, which serves Gemini under + # gcp/google/* model strings via the standard OpenAI shape). + env_key = os.environ.get('GOOGLE_API_KEY', '') + if key is None: + key = env_key + api_base = "https://generativelanguage.googleapis.com/v1beta/openai/chat/completions" + elif 'ernie' in model: + env_key = os.environ.get('BAIDU_API_KEY', '') + if key is None: + key = env_key + api_base = 'https://qianfan.baidubce.com/v2/chat/completions' + self.baidu_appid = os.environ.get('BAIDU_APP_ID', None) + else: + if use_azure: + env_key = os.environ.get('AZURE_OPENAI_API_KEY', None) + assert env_key is not None, 'Please set the environment variable AZURE_OPENAI_API_KEY. ' + + if key is None: + key = env_key + assert isinstance(key, str), ( + 'Please set the environment variable AZURE_OPENAI_API_KEY to your openai key. ' + ) + else: + env_key = os.environ.get('OPENAI_API_KEY', '') + if key is None: + key = env_key + + self.key = key + assert img_size > 0 or img_size == -1 + self.img_size = img_size + assert total_img_size > 0 or total_img_size == -1 + self.total_img_size = total_img_size + assert img_detail in ['high', 'low'] + self.img_detail = img_detail + self.timeout = timeout + self.is_max_completion_tokens = ('o1' in model) or ('o3' in model) or ('o4' in model) or ('gpt-5' in model) + self.is_o_model = ('o1' in model) or ('o3' in model) or ('o4' in model) + super().__init__(retry=retry, system_prompt=system_prompt, verbose=verbose, **kwargs) + + if use_azure: + api_base_template = ( + '{endpoint}openai/deployments/{deployment_name}/chat/completions?api-version={api_version}' + ) + endpoint = os.getenv('AZURE_OPENAI_ENDPOINT', None) + assert endpoint is not None, 'Please set the environment variable AZURE_OPENAI_ENDPOINT. ' + deployment_name = os.getenv('AZURE_OPENAI_DEPLOYMENT_NAME', None) + assert deployment_name is not None, 'Please set the environment variable AZURE_OPENAI_DEPLOYMENT_NAME. ' + api_version = os.getenv('OPENAI_API_VERSION', None) + assert api_version is not None, 'Please set the environment variable OPENAI_API_VERSION. ' + + self.api_base = api_base_template.format( + endpoint=os.getenv('AZURE_OPENAI_ENDPOINT'), + deployment_name=os.getenv('AZURE_OPENAI_DEPLOYMENT_NAME'), + api_version=os.getenv('OPENAI_API_VERSION') + ) + else: + if api_base is None: + if 'OPENAI_API_BASE' in os.environ and os.environ['OPENAI_API_BASE'] != '': + logger.info('Environment variable OPENAI_API_BASE is set. Will use it as api_base. ') + api_base = os.environ['OPENAI_API_BASE'] + else: + api_base = 'OFFICIAL' + + assert api_base is not None + + if api_base in APIBASES: + self.api_base = APIBASES[api_base] + elif api_base.startswith('http'): + self.api_base = api_base + else: + logger.error('Unknown API Base. ') + raise NotImplementedError + if os.environ.get('BOYUE', None): + self.api_base = os.environ.get('BOYUE_API_BASE') + self.key = os.environ.get('BOYUE_API_KEY') + + logger.info(f'Using API Base: {self.api_base}; API Key: {self.key}') + + # inputs can be a lvl-2 nested list: [content1, content2, content3, ...] + # content can be a string or a list of image & text + def prepare_itlist(self, inputs): + assert np.all([isinstance(x, dict) for x in inputs]) + has_images = np.sum([x['type'] == 'image' for x in inputs]) + has_videos = np.sum([x['type'] == 'video' for x in inputs]) + image_num = len([x['type'] == 'image' for x in inputs]) + if has_images or has_videos: + content_list = [] + for msg in inputs: + if msg['type'] == 'text': + content_list.append(dict(type='text', text=msg['value'])) + elif msg['type'] == 'image': + from PIL import Image + img = Image.open(msg['value']) + target_size = math.inf + if self.img_size > 0: + target_size = self.img_size + if self.total_img_size > 0: + target_size = min(target_size, int(self.img_size / (image_num**0.5))) + target_size = -1 if math.isinf(target_size) else target_size + b64 = encode_image_to_base64(img, target_size=target_size) + img_struct = dict(url=f'data:image/jpeg;base64,{b64}', detail=self.img_detail) + content_list.append(dict(type='image_url', image_url=img_struct)) + elif msg['type'] == 'video': + import base64 + import mimetypes + with open(msg['value'], 'rb') as fh: + b64 = base64.b64encode(fh.read()).decode('utf-8') + mime = mimetypes.guess_type(msg['value'])[0] or 'video/mp4' + vid_struct = dict(url=f'data:{mime};base64,{b64}') + content_list.append(dict(type='image_url', image_url=vid_struct)) + else: + assert all([x['type'] == 'text' for x in inputs]) + text = '\n'.join([x['value'] for x in inputs]) + content_list = [dict(type='text', text=text)] + return content_list + + def prepare_inputs(self, inputs): + input_msgs = [] + if self.system_prompt is not None: + input_msgs.append(dict(role='system', content=self.system_prompt)) + assert isinstance(inputs, list) and isinstance(inputs[0], dict) + assert np.all(['type' in x for x in inputs]) or np.all(['role' in x for x in inputs]), inputs + if 'role' in inputs[0]: + assert inputs[-1]['role'] == 'user', inputs[-1] + for item in inputs: + input_msgs.append(dict(role=item['role'], content=self.prepare_itlist(item['content']))) + else: + input_msgs.append(dict(role='user', content=self.prepare_itlist(inputs))) + return input_msgs + + def generate_inner(self, inputs, **kwargs) -> str: + input_msgs = self.prepare_inputs(inputs) + temperature = kwargs.pop('temperature', self.temperature) + max_tokens = kwargs.pop('max_tokens', self.max_tokens) + + # Will send request if use Azure, dk how to use openai client for it + if self.use_azure: + headers = {'Content-Type': 'application/json', 'api-key': self.key} + elif 'internvl2-pro' in self.model: + headers = {'Content-Type': 'application/json', 'Authorization': self.key} + else: + headers = {'Content-Type': 'application/json', 'Authorization': f'Bearer {self.key}'} + if hasattr(self, 'baidu_appid'): + headers['appid'] = self.baidu_appid + + payload = dict( + model=self.model, + messages=input_msgs, + n=1, + temperature=temperature, + **kwargs) + + if self.is_max_completion_tokens: + payload['max_completion_tokens'] = max_tokens + payload.pop('temperature') + else: + payload['max_tokens'] = max_tokens + + if 'gemini' in self.model: + payload.pop('max_tokens') + payload.pop('n') + payload['reasoning_effort'] = 'high' + + proxies = {} + if os.getenv('http_proxy'): + proxies['http'] = os.getenv('http_proxy') + if os.getenv('https_proxy'): + proxies['https'] = os.getenv('https_proxy') + proxies = proxies or None + + response = requests.post( + self.api_base, + headers=headers, + data=json.dumps(payload), + proxies=proxies, + timeout=self.timeout * 1.1, + ) + ret_code = response.status_code + ret_code = 0 if (200 <= int(ret_code) < 300) else ret_code + answer = self.fail_msg + try: + resp_struct = json.loads(response.text) + finish_reason = resp_struct.get('choices', [{}])[0].get('finish_reason', '') + if finish_reason == 'content_filter': + return -2, self.fail_msg, response + answer = resp_struct['choices'][0]['message']['content'].strip() + except Exception as err: + if self.verbose: + logger.error(f'{type(err)}: {err}') + logger.error(response.text if hasattr(response, 'text') else response) + + return ret_code, answer, response + + def get_image_token_len(self, img_path, detail='low'): + import math + if detail == 'low': + return 85 + + im = Image.open(img_path) + height, width = im.size + if width > 1024 or height > 1024: + if width > height: + height = int(height * 1024 / width) + width = 1024 + else: + width = int(width * 1024 / height) + height = 1024 + + h = math.ceil(height / 512) + w = math.ceil(width / 512) + total = 85 + 170 * h * w + return total + + def get_token_len(self, inputs) -> int: + import tiktoken + + try: + enc = tiktoken.encoding_for_model(self.model) + except Exception as err: + if 'gpt' in self.model.lower(): + if self.verbose: + logger.warning(f'{type(err)}: {err}') + enc = tiktoken.encoding_for_model('gpt-4') + else: + return 0 + assert isinstance(inputs, list) + tot = 0 + for item in inputs: + if 'role' in item: + tot += self.get_token_len(item['content']) + elif item['type'] == 'text': + tot += len(enc.encode(item['value'])) + elif item['type'] == 'image': + tot += self.get_image_token_len(item['value'], detail=self.img_detail) + return tot + + +class GPT4V(OpenAIWrapper): + + def generate(self, message, dataset=None): + return super(GPT4V, self).generate(message) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/hf_chat_model.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/hf_chat_model.py new file mode 100644 index 00000000..86f2ef5e --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/hf_chat_model.py @@ -0,0 +1,263 @@ +import os +import os.path as osp + +import torch + +from vlmeval.smp import get_logger, listinstr + +logger = get_logger(__name__) + + +def get_gpu_num(model_name): + model_name = model_name.lower() + kws = { + 8: ['65b', '70b'], + 4: ['30b', '33b', '35b', '40b'], + 2: ['13b', '14b', '20b', '8b'], + 1: ['6b', '7b', 'moss'], + } + for k in [8, 4, 2, 1]: + for keyword in kws[k]: + if keyword in model_name: + return k + return 8 + + +validated_llms = [ + 'internlm/internlm-chat-7b', 'internlm/internlm-chat-7b-8k', 'internlm/internlm-chat-20b', + 'Qwen/Qwen-7B-Chat', 'Qwen/Qwen-14B-Chat', + 'THUDM/chatglm2-6b', 'THUDM/chatglm2-6b-32k', 'THUDM/chatglm3-6b', 'THUDM/chatglm3-6b-32k', + 'baichuan-inc/Baichuan2-7B-Chat', 'baichuan-inc/Baichuan2-13B-Chat', + 'lmsys/vicuna-7b-v1.5', 'lmsys/vicuna-13b-v1.5', + 'meta-llama/Llama-2-7b-chat-hf', + 'meta-llama/Llama-3.1-8B-Instruct' +] +Auto_model = ['chatglm'] + + +class HFChatModel: + + def _get_context_length(self, model, model_path): + # By default, we use model.config.seq_length + model_path = model_path.lower() + if 'baichuan' in model_path: + context_window = model.config.model_max_length + elif 'internlm' in model_path or 'llama' in model_path: + context_window = model.config.max_position_embeddings + elif 'vicuna' in model_path: + context_window = model.generation_config.max_length + else: + # chatglm & qwen + context_window = model.config.seq_length + return context_window + + def _get_context_length_robust(self, model, model_path): + try: + context_window = self._get_context_length(model, model_path) + return context_window + except Exception as err: + logger.critical(f'{type(err)}: {err}') + logger.critical( + 'Failed to extract context_window information from config / generation_config. ' + 'Please read the above code and check if the logic works for you model path' + ) + raise NotImplementedError + + def __init__(self, + model_path, + system_prompt: str = None, + **kwargs): + + if 'vicuna' in model_path.lower() or 'llama' in model_path.lower(): + try: + from fastchat.model import get_conversation_template # noqa: F401 + except Exception as err: + logger.critical('Please install fastchat first to use vicuna. ') + raise err + + self.explicit_device = kwargs.pop('device', None) + if self.explicit_device is None: + # If CUDA_VISIBLE_DEVICES is not properly set + if 'CUDA_VISIBLE_DEVICES' not in os.environ or os.environ['CUDA_VISIBLE_DEVICES'] == '0,1,2,3,4,5,6,7': + num_gpu = get_gpu_num(model_path) + gpu_offset = kwargs.pop('gpu_offset', 0) + cuda_visible_devices = ','.join([str(i) for i in range(gpu_offset, gpu_offset + num_gpu)]) + os.environ['CUDA_VISIBLE_DEVICES'] = cuda_visible_devices + + from transformers import AutoModel, AutoModelForCausalLM, AutoTokenizer + + if model_path not in validated_llms: + logger.warning(f'{model_path} not in validated LLMs, may have inference troubles. ') + + self.model_path = model_path + if listinstr(Auto_model, model_path): + LoadModel = AutoModel + else: + LoadModel = AutoModelForCausalLM + assert osp.exists(model_path) or len(model_path.split('/')) == 2 + + device = self.explicit_device if self.explicit_device else 'auto' + + precision = {} + if 'internlm-chat-7b' in model_path: + precision = {'torch_dtype': torch.float16} + elif 'internlm-chat-20b' in model_path: + precision = {'torch_dtype': torch.bfloat16} + + self.tokenizer = AutoTokenizer.from_pretrained(model_path, trust_remote_code=True) + cuda_devices = os.environ.get('CUDA_VISIBLE_DEVICES', '0') + if ',' in cuda_devices: + device_ids = [int(x) for x in cuda_devices.split(',')] + _ = {i: i for i in range(len(device_ids))} + else: + _ = {'': 0} + + if 'llama' in self.model_path.lower(): + from lmdeploy import GenerationConfig, TurbomindEngineConfig, pipeline + print(f"Loading model {model_path} with {num_gpu} GPUs") + backend_config = TurbomindEngineConfig(tp=num_gpu) + self.gen_config = GenerationConfig(max_new_tokens=256) + model = pipeline(model_path, backend_config=backend_config) + else: + model = LoadModel.from_pretrained(model_path, trust_remote_code=True, device_map='cpu', **precision) + model = model.eval() + + if device != 'cpu': + model = model.to(f'cuda:{device}' if isinstance(device, int) else 'cuda') + try: + from transformers.generation import GenerationConfig + model.generation_config = GenerationConfig.from_pretrained( + model_path, trust_remote_code=True, device_map=device) + except Exception as err: + logger.warning(f'{type(err)}: {err}') + + self.context_length = self._get_context_length_robust(model=model, model_path=model_path) + + torch.cuda.empty_cache() + self.model = model + self.answer_buffer = 192 + self.system_prompt = system_prompt + for k, v in kwargs.items(): + logger.info(f'Following args will be used for generation (If not set specifically), {k}: {v}. ') + self.kwargs = kwargs + + def generate_str(self, input, **kwargs): + if 'baichuan' in self.model_path.lower(): + messages = [] + messages.append({'role': 'user', 'content': input}) + resp = self.model.chat(self.tokenizer, messages, **kwargs) + elif 'vicuna' in self.model_path.lower(): + from fastchat.model import get_conversation_template + conv = get_conversation_template('vicuna') + conv.append_message(conv.roles[0], input) + conv.append_message(conv.roles[1], None) + prompt = conv.get_prompt() + inputs = self.tokenizer([prompt], return_tensors='pt') + if torch.cuda.is_available(): + for k in inputs: + inputs[k] = inputs[k].cuda() + + params = dict(do_sample=True, temperature=0.7, repetition_penalty=1.0, max_new_tokens=512) + params.update(self.kwargs) + params.update(kwargs) + outputs = self.model.generate(**inputs, **params) + resp = self.tokenizer.decode( + outputs[0][len(inputs['input_ids'][0]):], + skip_special_tokens=True, + spaces_between_special_tokens=False) + elif 'llama' in self.model_path.lower(): + prompt = [{'role': 'system', 'content': self.system_prompt}, {'role': 'user', 'content': input}] + resp = self.model(prompt, gen_config=self.gen_config).text + else: + params = self.kwargs + params.update(kwargs) + resp, _ = self.model.chat(self.tokenizer, input, history=[], **params) + + return resp + + def length_ok(self, inputs): + tot = len(self.tokenizer.encode(self.system_prompt)) if self.system_prompt is not None else 0 + for s in inputs: + tot += len(self.tokenizer.encode(s)) + return tot + self.answer_buffer < self.context_length + + def generate_list(self, full_inputs, offset=0, **kwargs): + assert isinstance(full_inputs, list) + inputs = full_inputs[offset:] + if not self.length_ok(inputs): + return self.chat(full_inputs, offset + 1) + + model_path = self.model_path.lower() + + if sum([x in model_path for x in ['baichuan']]): + input_msgs = [] + if self.system_prompt is not None: + input_msgs.append(dict(role='user', content=self.system_prompt)) + if len(inputs): + assert isinstance(inputs, list) and isinstance(inputs[0], str) + roles = ['user', 'assistant'] if len(inputs) % 2 == 1 else ['assistant', 'user'] + roles = roles * len(inputs) + for role, msg in zip(roles, inputs): + input_msgs.append(dict(role=role, content=msg)) + response = self.model.chat(self.tokenizer, input_msgs) + elif sum([x in model_path for x in ['vicuna']]): + from fastchat.model import get_conversation_template + conv = get_conversation_template('vicuna') + assert isinstance(inputs, list) and isinstance(inputs[0], str) + if len(inputs) % 2 == 1: + if self.system_prompt is not None: + conv.append_message(conv.roles[0], self.system_prompt) + for i in range(len(inputs) // 2): + conv.append_message(conv.roles[0], inputs[2 * i]) + conv.append_message(conv.roles[1], inputs[2 * i + 1]) + else: + assert self.system_prompt is not None + conv.append_message(conv.roles[0], self.system_prompt) + conv.append_message(conv.roles[1], inputs[0]) + for i in range(len(inputs) // 2 - 1): + conv.append_message(conv.roles[0], inputs[2 * i + 1]) + conv.append_message(conv.roles[1], inputs[2 * i + 2]) + conv.append_message(conv.roles[0], inputs[-1]) + conv.append_message(conv.roles[1], None) + prompt = conv.get_prompt() + inputs = self.tokenizer([prompt], return_tensors='pt') + if torch.cuda.is_available(): + for k in inputs: + inputs[k] = inputs[k].cuda() + + params = dict(do_sample=True, temperature=0.7, repetition_penalty=1.0, max_new_tokens=512) + params.update(self.kwargs) + params.update(kwargs) + + outputs = self.model.generate(**inputs, **params) + response = self.tokenizer.decode( + outputs[0][len(inputs['input_ids'][0]):], + skip_special_tokens=True, + spaces_between_special_tokens=False) + response = response.lstrip('\n') + else: + # The default option, support internlm, chatglm, qwen + history, msg = [], None + if len(inputs) % 2 == 1: + if self.system_prompt is not None: + history = [(self.system_prompt, '')] + for i in range(len(inputs) // 2): + history.append((inputs[2 * i], inputs[2 * i + 1])) + else: + assert self.system_prompt is not None + history = [(self.system_prompt, inputs[0])] + for i in range(len(inputs) // 2 - 1): + history.append((inputs[2 * i + 1], inputs[2 * i + 2])) + msg = inputs[-1] + + params = self.kwargs + params.update(kwargs) + response, _ = self.model.chat(self.tokenizer, msg, history=history, **params) + + return response, offset + + def generate(self, inputs, **kwargs): + if isinstance(inputs, str): + return self.generate_str(inputs, **kwargs) + elif isinstance(inputs, list): + return self.generate_list(inputs, **kwargs) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/hunyuan.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/hunyuan.py new file mode 100644 index 00000000..29a75a5d --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/hunyuan.py @@ -0,0 +1,184 @@ +import json +import os +import string +import warnings + +import numpy as np +import pandas as pd + +from vlmeval.api.base import BaseAPI +from vlmeval.dataset import DATASET_TYPE +from vlmeval.smp import encode_image_to_base64, get_logger + +logger = get_logger(__name__) + + +class HunyuanWrapper(BaseAPI): + + is_api: bool = True + _apiVersion = '2024-12-31' + _service = 'hunyuan' + + def __init__(self, + model: str = 'hunyuan-standard-vision', + retry: int = 5, + secret_key: str = None, + secret_id: str = None, + verbose: bool = True, + system_prompt: str = None, + temperature: float = 0, + timeout: int = 60, + api_base: str = 'hunyuan.tencentcloudapi.com', + **kwargs): + + self.model = model + self.cur_idx = 0 + self.fail_msg = 'Failed to obtain answer via API. ' + self.temperature = temperature + + warnings.warn('You may need to set the env variable HUNYUAN_SECRET_ID & HUNYUAN_SECRET_KEY to use Hunyuan. ') + + secret_key = os.environ.get('HUNYUAN_SECRET_KEY', secret_key) + assert secret_key is not None, 'Please set the environment variable HUNYUAN_SECRET_KEY. ' + secret_id = os.environ.get('HUNYUAN_SECRET_ID', secret_id) + assert secret_id is not None, 'Please set the environment variable HUNYUAN_SECRET_ID. ' + + self.model = model + self.endpoint = api_base + self.secret_id = secret_id + self.secret_key = secret_key + self.timeout = timeout + + try: + from tencentcloud.common import credential + from tencentcloud.common.profile.client_profile import ClientProfile + from tencentcloud.common.profile.http_profile import HttpProfile + from tencentcloud.hunyuan.v20230901 import hunyuan_client + except ImportError as err: + logger.critical('Please install tencentcloud-sdk-python to use Hunyuan API. ') + raise err + + super().__init__(retry=retry, system_prompt=system_prompt, verbose=verbose, **kwargs) + + cred = credential.Credential(self.secret_id, self.secret_key) + httpProfile = HttpProfile(reqTimeout=300) + httpProfile.endpoint = self.endpoint + clientProfile = ClientProfile() + clientProfile.httpProfile = httpProfile + self.client = hunyuan_client.HunyuanClient(cred, '', clientProfile) + logger.info( + f'Using Endpoint: {self.endpoint}; API Secret ID: {self.secret_id}; API Secret Key: {self.secret_key}' + ) + + def use_custom_prompt(self, dataset_name): + if DATASET_TYPE(dataset_name) == 'MCQ': + return True + else: + return False + + def build_prompt(self, line, dataset=None): + assert self.use_custom_prompt(dataset) + assert dataset is None or isinstance(dataset, str) + + tgt_path = self.dump_image(line, dataset) + + question = line['question'] + options = { + cand: line[cand] + for cand in string.ascii_uppercase + if cand in line and not pd.isna(line[cand]) + } + options_prompt = 'Options:\n' + for key, item in options.items(): + options_prompt += f'{key}. {item}\n' + hint = line['hint'] if ('hint' in line and not pd.isna(line['hint'])) else None + prompt = '' + if hint is not None: + prompt += f'Hint: {hint}\n' + prompt += f'Question: {question}\n' + if len(options): + prompt += options_prompt + prompt += 'Answer with the option letter from the given choices directly.' + + msgs = [] + if isinstance(tgt_path, list): + msgs.extend([dict(type='image', value=p) for p in tgt_path]) + else: + msgs = [dict(type='image', value=tgt_path)] + msgs.append(dict(type='text', value=prompt)) + return msgs + + # inputs can be a lvl-2 nested list: [content1, content2, content3, ...] + # content can be a string or a list of image & text + def prepare_itlist(self, inputs): + assert np.all([isinstance(x, dict) for x in inputs]) + has_images = np.sum([x['type'] == 'image' for x in inputs]) + if has_images: + content_list = [] + for msg in inputs: + if msg['type'] == 'text': + content_list.append(dict(Type='text', Text=msg['value'])) + elif msg['type'] == 'image': + from PIL import Image + img = Image.open(msg['value']) + b64 = encode_image_to_base64(img) + img_struct = dict(Url=f'data:image/jpeg;base64,{b64}') + content_list.append(dict(Type='image_url', ImageUrl=img_struct)) + else: + assert all([x['type'] == 'text' for x in inputs]) + text = '\n'.join([x['value'] for x in inputs]) + content_list = [dict(Type='text', Text=text)] + return content_list + + def prepare_inputs(self, inputs): + input_msgs = [] + if self.system_prompt is not None: + input_msgs.append(dict(Role='system', Content=self.system_prompt)) + assert isinstance(inputs, list) and isinstance(inputs[0], dict) + assert np.all(['type' in x for x in inputs]) or np.all(['role' in x for x in inputs]), inputs + if 'role' in inputs[0]: + assert inputs[-1]['role'] == 'user', inputs[-1] + for item in inputs: + input_msgs.append(dict(Role=item['role'], Contents=self.prepare_itlist(item['content']))) + else: + input_msgs.append(dict(Role='user', Contents=self.prepare_itlist(inputs))) + return input_msgs + + def generate_inner(self, inputs, **kwargs) -> str: + from tencentcloud.common.exception.tencent_cloud_sdk_exception import \ + TencentCloudSDKException + from tencentcloud.hunyuan.v20230901 import models + + input_msgs = self.prepare_inputs(inputs) + temperature = kwargs.pop('temperature', self.temperature) + + payload = dict( + Model=self.model, + Messages=input_msgs, + Temperature=temperature, + TopK=1, + **kwargs) + + try: + req = models.ChatCompletionsRequest() + req.from_json_string(json.dumps(payload)) + resp = self.client.ChatCompletions(req) + resp = json.loads(resp.to_json_string()) + answer = resp['Choices'][0]['Message']['Content'] + return 0, answer, resp + except TencentCloudSDKException as e: + logger.error(f'Got error code: {e.get_code()}') + if e.get_code() == 'ClientNetworkError': + return -1, self.fail_msg + e.get_code(), None + elif e.get_code() in ['InternalError', 'ServerNetworkError']: + return -1, self.fail_msg + e.get_code(), None + elif e.get_code() in ['LimitExceeded']: + return -1, self.fail_msg + e.get_code(), None + else: + return -1, self.fail_msg + str(e), None + + +class HunyuanVision(HunyuanWrapper): + + def generate(self, message, dataset=None): + return super(HunyuanVision, self).generate(message) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/jt_vl_chat.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/jt_vl_chat.py new file mode 100644 index 00000000..7efac9ac --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/jt_vl_chat.py @@ -0,0 +1,289 @@ +import base64 +import json +import os +import os.path as osp +import string + +import pandas as pd +import requests + +from vlmeval.api.base import BaseAPI +from vlmeval.dataset import DATASET_TYPE, img_root_map +from vlmeval.smp import (LMUDataRoot, cn_string, concat_images_vlmeval, + decode_base64_to_image_file, get_logger, listinstr, read_ok, toliststr) + +logger = get_logger(__name__) + +API_ENDPOINT = "https://hl.jiutian.10086.cn/kunlun/ingress/api/hl-4a9c15/7b11a3451e1a4612a6661c3e22235df6/ai-4dfc1be4e6854a75a833aab9b956128c/service-e5893ba5c1154a1192cb8e60af11e276/v1/chat/completions" # noqa: E501 +APP_CODE = "B0m6Tuglt5shfY7t3GyoJn1V5yVAm0Ba" + + +class JTVLChatWrapper(BaseAPI): + is_api: bool = True + INTERLEAVE = False + + def __init__(self, + model: str = 'jt-vl-chat', + retry: int = 5, + wait: int = 5, + api_base: str = '', + app_code: str = '', + verbose: bool = True, + system_prompt: str = None, + temperature: float = 0.7, + max_tokens: int = 2048, + proxy: str = None, + **kwargs): + self.model = model + + self.temperature = temperature + self.max_tokens = max_tokens + self.api_base = API_ENDPOINT + self.app_code = APP_CODE + + super().__init__(wait=wait, retry=retry, system_prompt=system_prompt, verbose=verbose, **kwargs) + + def dump_image(self, line, dataset): + """Dump the image(s) of the input line to the corresponding dataset folder. + + Args: + line (line of pd.DataFrame): The raw input line. + dataset (str): The name of the dataset. + + Returns: + str | list[str]: The paths of the dumped images. + """ + ROOT = LMUDataRoot() + assert isinstance(dataset, str) + + img_root = os.path.join( + ROOT, 'images', + img_root_map(dataset) if dataset in img_root_map(dataset) else dataset) + os.makedirs(img_root, exist_ok=True) + if 'image' in line: + if isinstance(line['image'], list): + tgt_path = [] + assert 'image_path' in line + for img, im_name in zip(line['image'], line['image_path']): + path = osp.join(img_root, im_name) + if not read_ok(path): + decode_base64_to_image_file(img, path) + tgt_path.append(path) + else: + tgt_path = osp.join(img_root, f"{line['index']}.jpg") + if not read_ok(tgt_path): + decode_base64_to_image_file(line['image'], tgt_path) + tgt_path = [tgt_path] + else: + assert 'image_path' in line + tgt_path = toliststr(line['image_path']) + + return tgt_path + + def use_custom_prompt(self, dataset): + assert dataset is not None + if listinstr(['MMMU_DEV_VAL', 'MMMU_TEST'], dataset): + return False + else: + return True + + def build_multi_choice_prompt(self, line, dataset=None): + question = line['question'] + hint = line['hint'] if ('hint' in line and not pd.isna(line['hint'])) else None + if hint is not None: + question = hint + '\n' + question + + options = { + cand: line[cand] + for cand in string.ascii_uppercase if cand in line and not pd.isna(line[cand]) + } + for key, item in options.items(): + question += f'\n{key}. {item}' + prompt = question + + if len(options): + prompt += '\n请直接回答选项字母。' if cn_string( + prompt) else "\nAnswer with the option's letter from the given choices directly." + else: + prompt += '\n请直接回答问题。' if cn_string(prompt) else '\nAnswer the question directly.' + + return prompt + + def build_prompt(self, line, dataset=None): + assert self.use_custom_prompt(dataset) + assert dataset is None or isinstance(dataset, str) + + tgt_path = self.dump_image(line, dataset) + + if dataset is not None and listinstr(['MME'], dataset): + question = line['question'] + prompt = question + ' Answer the question using a single word or phrase.' + elif dataset is not None and listinstr(['HallusionBench'], dataset): + question = line['question'] + prompt = question + ' Please answer yes or no. Answer the question using a single word or phrase.' + elif dataset is not None and DATASET_TYPE(dataset) == 'MCQ': + prompt = self.build_multi_choice_prompt(line, dataset) + elif dataset is not None and DATASET_TYPE(dataset) == 'VQA': + if listinstr(['MathVista', 'MathVision'], dataset): + prompt = line['question'] + elif listinstr(['LLaVABench'], dataset): + question = line['question'] + prompt = question + '\nAnswer this question in detail.' + elif listinstr(['MMVet'], dataset): + prompt = line['question'] + else: + question = line['question'] + prompt = question + '\nAnswer the question using a single word or phrase.' + else: + prompt = line['question'] + message = [dict(type='text', value=prompt)] + message.extend([dict(type='image', value=s) for s in tgt_path]) + return message + + def message_to_promptimg(self, message, dataset=None): + assert not self.INTERLEAVE + model_name = self.__class__.__name__ + import warnings + + warnings.warn(f'Model {model_name} does not support interleaved input. ' + 'Will use the first image and aggregated texts as prompt. ') + num_images = len([x for x in message if x['type'] == 'image']) + if num_images == 0: + prompt = '\n'.join([x['value'] for x in message if x['type'] == 'text']) + image = None + else: + prompt = '\n'.join([x['value'] for x in message if x['type'] == 'text']) + if dataset == 'BLINK': + image = concat_images_vlmeval( + [x['value'] for x in message if x['type'] == 'image'], target_size=512) + else: + image = [x['value'] for x in message if x['type'] == 'image'][0] + return prompt, image + + def get_send_data(self, + prompt, + image_path, + temperature, + max_tokens, + stream=False, + understanding_plus=False): + image = '' + with open(image_path, 'rb') as f: + image = str(base64.b64encode(f.read()), 'utf-8') + send_data = { + "messages": [{ + "role": "user", + "content": prompt + }], + "image_base64": image, + "max_tokens": max_tokens, + "temperature": temperature, + "do_sample": False, + "understanding_plus": understanding_plus, + "stream": stream + } + return send_data + + def get_send_data_no_image(self, + prompt, + temperature, + max_tokens, + stream=False, + understanding_plus=False): + send_data = { + "messages": [{ + "role": "user", + "content": prompt + }], + "max_tokens": max_tokens, + "temperature": temperature, + "understanding_plus": understanding_plus, + "stream": stream + } + return send_data + + def generate_inner(self, inputs, **kwargs) -> str: + assert isinstance(inputs, str) or isinstance(inputs, list) + inputs = [inputs] if isinstance(inputs, str) else inputs + dataset = kwargs.get('dataset', None) + prompt, image_path = self.message_to_promptimg(message=inputs, dataset=dataset) + # print("prompt:",prompt) + if image_path: + send_data = self.get_send_data( + prompt=prompt, + image_path=image_path, + temperature=self.temperature, + max_tokens=self.max_tokens, + understanding_plus=True, + stream=True) + else: + send_data = self.get_send_data_no_image( + prompt=prompt, + temperature=self.temperature, + max_tokens=self.max_tokens, + understanding_plus=True, + stream=True) + + json_data = json.dumps(send_data) + + header_dict = {'Content-Type': 'application/json', 'Authorization': self.app_code} + + r = requests.post( + self.api_base, headers=header_dict, data=json_data, timeout=3000, stream=True) + try: + if send_data.get('stream', False): + chunks = [] + full_content = "" + # last_valid_usage = None # 用于记录最后一个有效的usage + # 流式处理 - 直接迭代并打印结果 + try: + for line in r.iter_lines(): + if line: + decoded_line = line.decode('utf-8') + if decoded_line.startswith('data: '): + event_data = decoded_line[6:] + if event_data == '[DONE]': + break + try: + chunk = json.loads(event_data) + chunks.append(chunk) + + # 记录最后一个有效的usage(不累加) + if 'usage' in chunk: + last_valid_usage = chunk['usage'] # noqa: F841 + + if 'choices' in chunk: + for choice in chunk['choices']: + if 'delta' in choice and 'content' in choice['delta']: + content = choice['delta']['content'] + print(content, end='', flush=True) + full_content += content + except json.JSONDecodeError: + continue + print() # 打印换行 + return 0, full_content, 'Succeeded! ' + except Exception as e: + return -1, f'Error: {str(e)}', '' + + else: + # 非流式处理 + try: + r_json = r.json() + output = r_json['choices'][0]['message']['content'] + return 0, output, 'Succeeded! ' + except Exception: + error_msg = f'Error! code {r.status_code} content: {r.content}' + error_con = r.content.decode('utf-8') + if self.verbose: + logger.error(error_msg) + logger.error(error_con) + logger.error(f'The input messages are {inputs}.') + return -1, error_msg, '' + except Exception as e: + return -1, f'Error: {str(e)}', '' + + +class JTVLChatAPI(JTVLChatWrapper): + + def generate(self, message, dataset=None): + return super(JTVLChatAPI, self).generate(message, dataset=dataset) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/jt_vl_chat_mini.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/jt_vl_chat_mini.py new file mode 100644 index 00000000..6299dbb9 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/jt_vl_chat_mini.py @@ -0,0 +1,290 @@ +# flake8: noqa +import base64 +import json +import os +import os.path as osp +import string + +import pandas as pd +import requests + +from vlmeval.api.base import BaseAPI +from vlmeval.dataset import DATASET_TYPE, img_root_map +from vlmeval.smp import (LMUDataRoot, cn_string, concat_images_vlmeval, + decode_base64_to_image_file, get_logger, listinstr, read_ok, toliststr) + +API_ENDPOINT = "https://hl.jiutian.10086.cn/kunlun/ingress/api/hl-4a9c15/7b11a3451e1a4612a6661c3e22235df6/ai-e7d64e71b61e421b8a41c0d43424d73a/service-cd5a067331e34cfea25f5a9d7960ffe8//v1/chat/completions" +API_ENDPOINT_2B = 'https://hl.jiutian.10086.cn/kunlun/ingress/api/hl-4a9c15/7b11a3451e1a4612a6661c3e22235df6/ai-3101961c75eb47ad8cc8d8ebb62fb8b0/service-c0bf9bac00824ace8639c0e8e0a4a5da/v1/chat/completions' +APP_CODE = "eyJ0eXAiOiJKV1QiLCJhbGciOiJSUzI1NiJ9.eyJzdWIiOiI1YjYyODY0ZjZmMWI0Yzg4YWE2ZDk1NzBhNDY1MWI3OSIsImlzcyI6ImFwaS1hdXRoLWtleSIsImV4cCI6NDg5ODU1NzAwN30.jLUbctPdJ74VC3Vwlr0nB4x9N2QxWtSGYE0vWsceZN-agDecVnH8pH8Q5SoCQ3-SBYx5jDx-UOg3kkoMqY9CdxiALauKU_UZ56CV2NKCcHUVeJIgNvfQJMb0z6yCCbSe80e1T8FxrxQXDvubyWtl4pTAhixYaEUqNG8rjUrDuA-vRgZ1e7HilBmU487OI76D9LUnU-zEdMWhzsCkh_Yy3M1Ur4PsKgMFi5QSmMuGSUGJjkpJHiGNx1QcevBLQSOCL2jvg15ifB2n2dD6zb8iPXFkfQTtmvbZofxWACSvkri-x9V3gFWg7DODwKUZsyyogPzRJVbmxDGruMsgiiCsPg" + + +logger = get_logger(__name__) + +class JTVLChatWrapper(BaseAPI): + is_api: bool = True + INTERLEAVE = False + + def __init__(self, + model: str = 'jt-vl-chat-mini', + retry: int = 5, + wait: int = 5, + api_base: str = '', + app_code: str = '', + verbose: bool = True, + system_prompt: str = None, + temperature: float = 0.7, + max_tokens: int = 2048, + proxy: str = None, + **kwargs): + self.model = model + + self.temperature = temperature + self.max_tokens = max_tokens + if model == 'jt-vl-chat-mini': + self.api_base = API_ENDPOINT + else: + self.api_base = API_ENDPOINT_2B + self.app_code = APP_CODE + + super().__init__(wait=wait, retry=retry, system_prompt=system_prompt, verbose=verbose, **kwargs) + + def dump_image(self, line, dataset): + """Dump the image(s) of the input line to the corresponding dataset folder. + + Args: + line (line of pd.DataFrame): The raw input line. + dataset (str): The name of the dataset. + + Returns: + str | list[str]: The paths of the dumped images. + """ + ROOT = LMUDataRoot() + assert isinstance(dataset, str) + + img_root = os.path.join(ROOT, 'images', img_root_map(dataset) if dataset in img_root_map(dataset) else dataset) + os.makedirs(img_root, exist_ok=True) + if 'image' in line: + if isinstance(line['image'], list): + tgt_path = [] + assert 'image_path' in line + for img, im_name in zip(line['image'], line['image_path']): + path = osp.join(img_root, im_name) + if not read_ok(path): + decode_base64_to_image_file(img, path) + tgt_path.append(path) + else: + tgt_path = osp.join(img_root, f"{line['index']}.jpg") + if not read_ok(tgt_path): + decode_base64_to_image_file(line['image'], tgt_path) + tgt_path = [tgt_path] + else: + assert 'image_path' in line + tgt_path = toliststr(line['image_path']) + + return tgt_path + + def use_custom_prompt(self, dataset): + assert dataset is not None + if listinstr(['MMMU_DEV_VAL', 'MMMU_TEST'], dataset): + return False + else: + return True + + def build_multi_choice_prompt(self, line, dataset=None): + question = line['question'] + hint = line['hint'] if ('hint' in line and not pd.isna(line['hint'])) else None + if hint is not None: + question = hint + '\n' + question + + options = { + cand: line[cand] + for cand in string.ascii_uppercase + if cand in line and not pd.isna(line[cand]) + } + for key, item in options.items(): + question += f'\n{key}. {item}' + prompt = question + + if len(options): + prompt += '\n请直接回答选项字母。' if cn_string( + prompt) else "\nAnswer with the option's letter from the given choices directly." + else: + prompt += '\n请直接回答问题。' if cn_string(prompt) else '\nAnswer the question directly.' + + return prompt + + def build_prompt(self, line, dataset=None): + assert self.use_custom_prompt(dataset) + assert dataset is None or isinstance(dataset, str) + + tgt_path = self.dump_image(line, dataset) + + if dataset is not None and listinstr(['MME'], dataset): + question = line['question'] + prompt = question + ' Answer the question using a single word or phrase.' + elif dataset is not None and listinstr(['HallusionBench'], dataset): + question = line['question'] + prompt = question + ' Please answer yes or no. Answer the question using a single word or phrase.' + elif dataset is not None and DATASET_TYPE(dataset) == 'MCQ': + prompt = self.build_multi_choice_prompt(line, dataset) + elif dataset is not None and DATASET_TYPE(dataset) == 'VQA': + if listinstr(['MathVista', 'MathVision'], dataset): + prompt = line['question'] + elif listinstr(['LLaVABench'], dataset): + question = line['question'] + prompt = question + '\nAnswer this question in detail.' + elif listinstr(['MMVet'], dataset): + prompt = line['question'] + else: + question = line['question'] + prompt = question + '\nAnswer the question using a single word or phrase.' + else: + prompt = line['question'] + message = [dict(type='text', value=prompt)] + message.extend([dict(type='image', value=s) for s in tgt_path]) + return message + + def message_to_promptimg(self, message, dataset=None): + assert not self.INTERLEAVE + model_name = self.__class__.__name__ + import warnings + warnings.warn( + f'Model {model_name} does not support interleaved input. ' + 'Will use the first image and aggregated texts as prompt. ') + num_images = len([x for x in message if x['type'] == 'image']) + if num_images == 0: + prompt = '\n'.join([x['value'] for x in message if x['type'] == 'text']) + image = None + else: + prompt = '\n'.join([x['value'] for x in message if x['type'] == 'text']) + if dataset == 'BLINK': + image = concat_images_vlmeval( + [x['value'] for x in message if x['type'] == 'image'], + target_size=512) + else: + image = [x['value'] for x in message if x['type'] == 'image'][0] + return prompt, image + + def get_send_data(self, prompt, image_path, temperature, max_tokens, stream=False): + image = '' + with open(image_path, 'rb') as f: + image = str(base64.b64encode(f.read()), 'utf-8') + send_data = { + "messages": [ + { + "role": "user", + "content": prompt + } + ], + "image_base64": image, + "max_tokens": max_tokens, + "temperature": temperature, + "do_sample": False, + "stream": stream + } + return send_data + + def get_send_data_no_image(self, prompt, temperature, max_tokens, stream=False): + send_data = { + "messages": [ + { + "role": "user", + "content": prompt + } + ], + "max_tokens": max_tokens, + "temperature": temperature, + "stream": stream, + } + return send_data + + def generate_inner(self, inputs, **kwargs) -> str: + assert isinstance(inputs, str) or isinstance(inputs, list) + inputs = [inputs] if isinstance(inputs, str) else inputs + dataset = kwargs.get('dataset', None) + prompt, image_path = self.message_to_promptimg(message=inputs, dataset=dataset) + # print("prompt:",prompt) + if image_path: + send_data = self.get_send_data( + prompt=prompt, + image_path=image_path, + temperature=self.temperature, + max_tokens=self.max_tokens, + stream=True) + else: + send_data = self.get_send_data_no_image( + prompt=prompt, + temperature=self.temperature, + max_tokens=self.max_tokens, + stream=True) + + json_data = json.dumps(send_data) + + header_dict = {'Content-Type': 'application/json', 'Authorization': 'Bearer ' + self.app_code} + + r = requests.post(self.api_base, headers=header_dict, data=json_data, timeout=3000, stream=True) + try: + if send_data.get('stream', False): + # 流式处理 + chunks = [] + full_content = "" + + try: + for line in r.iter_lines(): + if line: + decoded_line = line.decode('utf-8') + if decoded_line.startswith('data: '): + event_data = decoded_line[6:] + if event_data == '[DONE]': + break + try: + chunk = json.loads(event_data) + chunks.append(chunk) + + # 记录最后一个有效的usage(不累加) + if 'usage' in chunk: + _ = chunk['usage'] + + # 实时输出内容 + if 'choices' in chunk: + for choice in chunk['choices']: + if 'delta' in choice and 'content' in choice['delta']: + content = choice['delta']['content'] + print(content, end='', flush=True) + full_content += content + except json.JSONDecodeError: + continue + print("\n") # 换行 + + return 0, full_content, 'Succeeded! ' + + except Exception as e: + return -1, f'Error: {str(e)}', '' + else: + # 非流式处理 + try: + r_json = r.json() + output = r_json['choices'][0]['message']['content'] + return 0, output, 'Succeeded! ' + except: + error_msg = f'Error! code {r.status_code} content: {r.content}' + error_con = r.content.decode('utf-8') + if self.verbose: + logger.error(error_msg) + logger.error(error_con) + logger.error(f'The input messages are {inputs}.') + return -1, error_msg, '' + except Exception as e: + return -1, f'Error: {str(e)}', '' + + +class JTVLChatAPI_Mini(JTVLChatWrapper): + + def generate(self, message, dataset=None): + return super(JTVLChatAPI_Mini, self).generate(message, dataset=dataset) + + +class JTVLChatAPI_2B(JTVLChatWrapper): + + def generate(self, message, dataset=None): + return super(JTVLChatAPI_2B, self).generate(message, dataset=dataset) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/kimivl_api.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/kimivl_api.py new file mode 100644 index 00000000..2823698c --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/kimivl_api.py @@ -0,0 +1,166 @@ +import json +import os + +import numpy as np +import requests + +from vlmeval.api.base import BaseAPI +from vlmeval.smp import encode_image_to_base64, get_logger + +logger = get_logger(__name__) + +APIBASES = { + 'OFFICIAL': 'http://localhost:8000/v1/chat/completions', +} + + +def extract_summary(text: str, bot: str = "◁think▷", eot: str = "◁/think▷") -> str: + # 输出截断, 返回空字符串 + if bot in text and eot not in text: + return "" + if eot in text: + return text[text.index(eot) + len(eot):].strip() + return text + + +class KimiVLAPIWrapper(BaseAPI): + + is_api: bool = True + + def __init__(self, + model: str = 'api-kimi-vl-thinking-2506', + retry: int = 5, + key: str = None, + verbose: bool = True, + system_prompt: str = None, + temperature: float = 0.8, + timeout: int = 360, + api_base: str = 'OFFICIAL', + max_tokens: int = 32768, + **kwargs): + + self.model = model + self.cur_idx = 0 + self.fail_msg = 'Failed to obtain answer via API. ' + self.max_tokens = max_tokens + self.temperature = temperature + + if 'kimi' in model: + env_key = os.environ.get('KIMI_VL_API_KEY', '') + if key is None: + key = env_key + + self.key = key + self.timeout = timeout + + super().__init__(retry=retry, system_prompt=system_prompt, verbose=verbose, **kwargs) + + if 'KIMI_VL_API_BASE' in os.environ and os.environ['KIMI_VL_API_BASE'] != '': + logger.info('Environment variable KIMI_VL_API_BASE is set. Will use it as api_base. ') + api_base = os.environ['KIMI_VL_API_BASE'] + else: + api_base = 'OFFICIAL' + + print(api_base) + + assert api_base is not None + + if api_base in APIBASES: + self.api_base = APIBASES[api_base] + elif api_base.startswith('http'): + self.api_base = api_base + else: + logger.error('Unknown API Base. ') + raise NotImplementedError + + logger.info(f'Using API Base: {self.api_base}; API Key: {self.key}') + + # inputs can be a lvl-2 nested list: [content1, content2, content3, ...] + # content can be a string or a list of image & text + def prepare_itlist(self, inputs): + assert np.all([isinstance(x, dict) for x in inputs]) + has_images = np.sum([x['type'] == 'image' for x in inputs]) + if has_images: + content_list = [] + for msg in inputs: + if msg['type'] == 'text': + if msg["value"] == "": + continue + content_list.append(dict(type='text', text=msg['value'])) + + elif msg['type'] == 'image': + from PIL import Image + + img = Image.open(msg['value']) + b64 = encode_image_to_base64(img) + img_struct = dict(url=f'data:image/jpeg;base64,{b64}') + content_list.append(dict(type='image_url', image_url=img_struct)) + else: + assert all([x['type'] == 'text' for x in inputs]) + text = '\n'.join([x['value'] for x in inputs]) + content_list = [dict(type='text', text=text)] + return content_list + + def prepare_inputs(self, inputs): + input_msgs = [] + if self.system_prompt is not None: + input_msgs.append(dict(role='system', content=self.system_prompt)) + assert isinstance(inputs, list) and isinstance(inputs[0], dict) + assert np.all(['type' in x for x in inputs]) or np.all(['role' in x for x in inputs]), inputs + if 'role' in inputs[0]: + assert inputs[-1]['role'] == 'user', inputs[-1] + for item in inputs: + input_msgs.append(dict(role=item['role'], content=self.prepare_itlist(item['content']))) + else: + input_msgs.append(dict(role='user', content=self.prepare_itlist(inputs))) + if os.environ.get("THINKING_SKIPPED", False): + input_msgs.append({ + "role": "assistant", + "content": "◁think▷\n\n◁/think▷", + "partial": True + }) + logger.info("Add skip thinking pattern") + return input_msgs + + def generate_inner(self, inputs, **kwargs) -> str: + input_msgs = self.prepare_inputs(inputs) + temperature = kwargs.pop('temperature', self.temperature) + max_tokens = kwargs.pop('max_tokens', self.max_tokens) + + headers = {'Content-Type': 'application/json', 'Authorization': f'Bearer {self.key}'} + payload = dict( + model=self.model, + messages=input_msgs, + n=1, + temperature=temperature, + **kwargs) + print(self.model) + + payload['max_tokens'] = max_tokens + response = requests.post( + self.api_base, + headers=headers, data=json.dumps(payload), timeout=self.timeout * 1.1) + ret_code = response.status_code + ret_code = 0 if (200 <= int(ret_code) < 300) else ret_code + answer = self.fail_msg + try: + resp_struct = json.loads(response.text) + answer = resp_struct['choices'][0]['message']['content'].strip() + print(answer) + length_befofe_es = len(answer.split()) + answer = extract_summary(answer) + length_after_es = len(answer.split()) + if length_befofe_es != length_after_es: + logger.info("Thinking length: {}".format(length_befofe_es - length_after_es)) + except Exception as err: + if self.verbose: + logger.error(f'{type(err)}: {err}') + logger.error(response.text if hasattr(response, 'text') else response) + + return ret_code, answer, response + + +class KimiVLAPI(KimiVLAPIWrapper): + + def generate(self, message, dataset=None): + return super(KimiVLAPI, self).generate(message) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/lmdeploy.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/lmdeploy.py new file mode 100644 index 00000000..a8d286bc --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/lmdeploy.py @@ -0,0 +1,142 @@ +import os + +import numpy as np + +from ..smp import encode_image_to_base64, get_logger +from .adapters import build_adapter +from .openai_sdk import OpenAISDKWrapper + +logger = get_logger(__name__) + + +class LMDeployWrapper(OpenAISDKWrapper): + """OpenAI-compatible wrapper for lmdeploy-served models. + + Handles lmdeploy-specific details: + + * ``LMDEPLOY_API_KEY`` / ``LMDEPLOY_API_BASE`` environment variables + * Automatic adapter selection based on model name (see :meth:`_detect_adapter`) + * lmdeploy image-encoding format via :meth:`prepare_itlist` + + Model-specific prompt building and payload post-processing are + delegated to a :class:`~vlmeval.api.adapters.ModelAdapter` that is + selected automatically or specified via ``custom_prompt``. + """ + + def __init__(self, + model, + retry=5, + wait=5, + key='sk-123456', + verbose=True, + timeout=120, + api_base=None, + system_prompt=None, + custom_prompt=None, + **kwargs): + self.fail_msg = 'Failed to obtain answer via API. ' + self.timeout = timeout + + key = os.environ.get('LMDEPLOY_API_KEY', key) + api_base = api_base or os.environ.get('LMDEPLOY_API_BASE', None) + assert key is not None, 'Please set the environment variable LMDEPLOY_API_KEY.' + assert api_base, 'Please set the environment variable LMDEPLOY_API_BASE.' + self.key = key + self.api_base = api_base + + kwargs = {'max_tokens': 16384, 'temperature': 0.0, **kwargs} + kwargs = {k: v for k, v in kwargs.items() if v is not None} + + # Call OpenAISDKWrapper without model_adapter; we set self.adapter below + # after resolving the model name. + super().__init__( + retry=retry, + wait=wait, + system_prompt=system_prompt, + verbose=verbose, + **kwargs, + ) + + self.model = model + logger.info(f'lmdeploy evaluate model: {self.model}') + + # Resolve and instantiate adapter + if custom_prompt is None: + custom_prompt = self._detect_adapter(self.model) + if custom_prompt is not None: + self.adapter = build_adapter(custom_prompt) + logger.info(f'Using model adapter: {custom_prompt}') + + # ------------------------------------------------------------------ + # Adapter auto-detection + # ------------------------------------------------------------------ + + def _detect_adapter(self, model_name): + """Return the adapter name for *model_name*, or ``None``.""" + name = model_name.lower() + if 'cogvlm2-llama3-chat-19b' in name: + return 'cogvlm2' + if 'interns1' in name or 'intern-s1' in name: + return 'interns1_1_no_think' + if 'internvl3' in name: + return 'internvl3' + if 'internvl' in name: + if 'mpo' in name: + return 'internvl2-mpo-cot' + return 'internvl2' + if 'qwen3' in name: + return 'qwen3' + return None + + # ------------------------------------------------------------------ + # HTTP message formatting (lmdeploy-specific) + # ------------------------------------------------------------------ + + def prepare_itlist(self, inputs): + assert np.all([isinstance(x, dict) for x in inputs]) + has_images = np.sum([x['type'] == 'image' for x in inputs]) + if has_images: + content_list = [] + for msg in inputs: + if msg['type'] == 'text': + content_list.append(dict(type='text', text=msg['value'])) + elif msg['type'] == 'image': + from PIL import Image + img = Image.open(msg['value']) + b64 = encode_image_to_base64(img) + extra_args = {k: v for k, v in msg.items() if k not in ('type', 'value')} + img_struct = dict(url=f'data:image/jpeg;base64,{b64}', **extra_args) + content_list.append(dict(type='image_url', image_url=img_struct)) + else: + assert all(x['type'] == 'text' for x in inputs) + text = '\n'.join(x['value'] for x in inputs) + content_list = [dict(type='text', text=text)] + return content_list + + def prepare_inputs(self, inputs, system_prompt): + input_msgs = [] + if system_prompt is not None: + input_msgs.append(dict(role='system', content=system_prompt)) + assert isinstance(inputs, list) and isinstance(inputs[0], dict) + assert ( + np.all(['type' in x for x in inputs]) + or np.all(['role' in x for x in inputs]) + ), inputs + if 'role' in inputs[0]: + assert inputs[-1]['role'] == 'user', inputs[-1] + for item in inputs: + input_msgs.append( + dict(role=item['role'], content=self.prepare_itlist(item['content'])) + ) + else: + input_msgs.append(dict(role='user', content=self.prepare_itlist(inputs))) + return input_msgs + + +class LMDeployAPI(LMDeployWrapper): + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def generate(self, message, dataset=None): + return super().generate(message, dataset=dataset) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/lvs_service.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/lvs_service.py new file mode 100644 index 00000000..9e3afa7c --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/lvs_service.py @@ -0,0 +1,224 @@ +from ..smp import * +import os +import sys +import mimetypes +from .base import BaseAPI + + +API_BASE = "http://localhost:8009" + + +class LVSWrapper(BaseAPI): + + is_api: bool = True + + def __init__(self, + model: str = 'lvs', + retry: int = 5, + key: str = None, + verbose: bool = False, + system_prompt: str = None, + chunk_duration: int = 10, + num_frames_per_chunk: int = 10, + temperature: float = 0.1, + api_base: str = None, + max_tokens: int = 16384, + fail_msg: dict = {"events": [], "total_events": 0, "video_summary": ""}, + **kwargs): + + self.model = model + self.cur_idx = 0 + self.max_tokens = max_tokens + self.temperature = temperature + self.api_base = api_base if api_base is not None else API_BASE + self.chunk_duration = chunk_duration + self.num_frames_per_chunk = num_frames_per_chunk + + self.key = key + self.fail_msg = fail_msg + super().__init__(retry=retry, system_prompt=system_prompt, verbose=verbose, **kwargs) + + if api_base is None: + self.logger.info(f'API Base not provided. Using default API Base: {API_BASE}') + else: + self.logger.info(f'Using API Base: {self.api_base}; API Key: {self.key}') + + import requests + try: + response = requests.get( + self.api_base + '/v1/live', + ) + ret_code = response.status_code + if 200 <= int(ret_code) < 300: + self.logger.info( + f'Successfully connected to LVS at {self.api_base}. ' + f'Health check returned status code: {ret_code}' + f'Ensure LVS is deployed with /files API enabled by setting VIA_DEV_API=true' + ) + else: + raise ConnectionError( + f'LVS is not available at {self.api_base}. ' + f'Health check failed with status code: {ret_code}' + ) + except requests.exceptions.RequestException as e: + raise ConnectionError( + f'LVS is not available at {self.api_base}. ' + f'Connection error: {e}' + ) + + def generate_inner(self, inputs, *args, **kwargs) -> str: + video_url = None + text_prompt = None + + for item in inputs: + if item['type'] == 'video': + video_url = item['value'] + elif item['type'] == 'text': + text_prompt = item['value'] + + if video_url is None: + raise ValueError('No video URL found in inputs') + if text_prompt is None: + raise ValueError('No text prompt found in inputs') + + self.logger.info(f'Video path: {video_url}') + self.logger.info(f'Text Prompt: {text_prompt[:50] if len(text_prompt) > 50 else text_prompt}') + + # Upload file to LVS using multipart form-data + # Equivalent to: curl --form 'file=@"/path/to/video.mp4"' --form 'purpose="vision"' --form 'media_type="video"' + + # Verify file exists + if not os.path.exists(video_url): + log = f'Video file not found: {video_url}' + self.logger.error(log) + return 404, json.dumps(self.fail_msg), log + + # Get filename and determine MIME type + filename = os.path.basename(video_url) + mime_type, _ = mimetypes.guess_type(video_url) + if mime_type is None: + mime_type = 'video/mp4' # Default to mp4 + + self.logger.info(f'Uploading file: {filename} (MIME: {mime_type})') + + try: + with open(video_url, 'rb') as video_file: + # Build multipart form-data request + files = { + 'file': (filename, video_file, mime_type) + } + data = { + 'purpose': 'vision', + 'media_type': 'video' + } + + upload_response = requests.post( + f'{self.api_base}/files', + files=files, + data=data + ) + except IOError as e: + log = f'Failed to read video file: {video_url}, error: {e}' + self.logger.error(log) + return 500, json.dumps(self.fail_msg), log + + ret_code = upload_response.status_code + if not (200 <= ret_code < 300): + log = f'Failed to upload file. Status: {ret_code}, Response: {upload_response.text}' + self.logger.error(log) + return ret_code, json.dumps(self.fail_msg), log + + # Get file_id from upload response + try: + upload_data = upload_response.json() + file_id = upload_data['id'] + except (json.JSONDecodeError, KeyError) as e: + log = f'Failed to parse upload response: {upload_response.text}, error: {e}' + self.logger.error(log) + return ret_code, json.dumps(self.fail_msg), log + + self.logger.info(f'Successfully uploaded file. file_id: {file_id}') + + # Build summarize request + summarize_data = { + 'id': [file_id], + 'model': self.model, + 'auto_generate_prompt': True, + 'chunk_duration': self.chunk_duration, + 'num_frames_per_chunk': self.num_frames_per_chunk, + } + + try: + prompt_data = json.loads(text_prompt) + if isinstance(prompt_data, dict): + events = prompt_data.get('event_list', prompt_data.get('events', [])) + scenario = prompt_data.get('scenario', '') + # If events is a string (pipe-separated), convert to list + if isinstance(events, str): + events = [e.strip() for e in events.split('|')] + summarize_data['events'] = events + summarize_data['scenario'] = scenario + self.logger.info(f'Sending events: {events}, scenario: {scenario}') + except (json.JSONDecodeError, TypeError): + # If parsing fails, use text_prompt as-is as prompt + summarize_data['prompt'] = text_prompt + + # Call summarize API + self.logger.info('Calling /summarize API...') + summarize_response = requests.post( + f'{self.api_base}/summarize', + headers={'Content-Type': 'application/json'}, + json=summarize_data + ) + + http_status = summarize_response.status_code + if 200 <= http_status < 300: + try: + response_data = summarize_response.json() + except json.JSONDecodeError: + log = f'Failed to parse summarize response as JSON: {summarize_response.text}' + self.logger.error(log) + # Return non-zero ret_code to trigger retry + return 1, json.dumps(self.fail_msg), log + + try: + choices = response_data.get('choices', []) + if choices and len(choices) > 0: + message = choices[0].get('message', {}) + content = message.get('content', '') + + # Parse the content JSON + if content: + content_dict = json.loads(content) + events = content_dict.get('events', []) + + answer = json.dumps(events) + log = f'Successfully called summarize. Found {len(events)} events.' + self.logger.info(log) + # Return 0 for success (BaseAPI expects ret_code == 0 for success) + return 0, answer, log + else: + log = f'Empty content in response: {response_data}' + self.logger.warning(log) + return 1, json.dumps(self.fail_msg), log + else: + log = f'No choices found in response: {response_data}' + self.logger.error(log) + return 1, json.dumps(self.fail_msg), log + + except Exception as e: + log = f'Error parsing response: {e}, response_data: {response_data}' + self.logger.error(log) + return 1, json.dumps(self.fail_msg), log + else: + log = f'Failed to summarize. Status: {http_status}, Response: {summarize_response.text}' + self.logger.error(log) + # Return HTTP status code (non-zero) to trigger retry + return http_status, json.dumps(self.fail_msg), log + + +class LVS_service(LVSWrapper): + VIDEO_LLM = True + + def generate(self, message, dataset=None): + return super(LVS_service, self).generate(message) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/mimo.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/mimo.py new file mode 100644 index 00000000..a065ea57 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/mimo.py @@ -0,0 +1,41 @@ +"""Xiaomi MiMo family wrapper (MiMo-Embodied-7B, MiMo-VL-7B-{SFT,RL}, ...). + +All current MiMo VLMs ship a Qwen2.5-VL-7B backbone (HF vision_config.patch_size=14) +with hardcoded thinking-on; chat_template_kwargs.enable_thinking is a no-op. We +append ' /no_think' to the last user-text part (the trained-on signal per +MiMo-Embodied/lmms_eval/models/mivllm.py:217-222) so the server emits an empty + envelope plus the answer (~50x cheaper than default thinking-on), +then strip the envelope client-side with the CosmosReason2 no-answer-tag pattern. +""" + +import re + +from .cosmos_reason import CosmosReason + + +class MiMo(CosmosReason): + + NO_THINK_SUFFIX = ' /no_think' + _THINK_PATTERN = r"^([^<]*(?:<(?!/?think>)[^<]*)*)<\/think>(?:\n|\n\n| |)([\s\S]*?)$" + + def __init__(self, *args, **kwargs): + kwargs.setdefault('image_patch_size', 14) # Qwen2.5-VL family + super().__init__(*args, **kwargs) + + def generate_inner(self, inputs, **kwargs): + inputs = self._append_no_think(inputs) + return super().generate_inner(inputs, **kwargs) + + @classmethod + def _append_no_think(cls, inputs): + # BaseAPI.generate flattens to a listdict of {'type', 'value'} before + # reaching generate_inner. Append the suffix to the last text part. + for part in reversed(inputs): + if isinstance(part, dict) and part.get('type') == 'text': + part['value'] = part['value'].rstrip() + cls.NO_THINK_SUFFIX + return inputs + return inputs + + def parse_answer(self, answer: str) -> str: + match = re.search(self._THINK_PATTERN, answer, re.DOTALL) + return match.group(2).strip() if match else answer diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/minimax_api.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/minimax_api.py new file mode 100644 index 00000000..593562de --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/minimax_api.py @@ -0,0 +1,124 @@ +""" +MiniMax API support for text-based LLM evaluation. +OpenAI-compatible chat completions endpoint. +Set MINIMAX_API_KEY or pass key=... + +Models: MiniMax-M2.7, MiniMax-M2.5, MiniMax-M2.5-highspeed +API docs: https://platform.minimaxi.com/document/guides/chat-model/text-generation +""" +import json +import os + +import requests + +from vlmeval.smp.log import get_logger +from .base import BaseAPI + +MINIMAX_API_BASE = "https://api.minimax.io/v1/chat/completions" +logger = get_logger(__name__) + + +class MiniMaxAPI(BaseAPI): + """Text LLM API using MiniMax (OpenAI-compatible).""" + + is_api: bool = True + + def __init__( + self, + model: str = "MiniMax-M2.7", + key: str = None, + api_base: str = None, + retry: int = 10, + wait: int = 1, + system_prompt: str = None, + verbose: bool = True, + temperature: float = 0, + max_tokens: int = 2048, + timeout: int = 300, + **kwargs, + ): + self.model = model + self.key = key or os.environ.get("MINIMAX_API_KEY") + self.api_base = api_base or os.environ.get("MINIMAX_API_BASE", MINIMAX_API_BASE) + self.temperature = temperature + self.max_tokens = max_tokens + self.timeout = timeout + + if not self.key: + raise ValueError( + "MiniMax API key is required. Set MINIMAX_API_KEY or pass key=..." + ) + + super().__init__( + retry=retry, + wait=wait, + system_prompt=system_prompt, + verbose=verbose, + **kwargs, + ) + + logger.info(f"MiniMaxAPI: model={self.model}, api_base={self.api_base}") + + def _prepare_messages(self, inputs): + """Build OpenAI-style messages from VLMEvalKit input format.""" + messages = [] + if self.system_prompt: + messages.append({"role": "system", "content": self.system_prompt}) + + # Handle multi-turn chat format + if inputs and isinstance(inputs[0], dict) and "role" in inputs[0]: + for item in inputs: + content_parts = item.get("content", []) + text = "\n".join( + x["value"] for x in content_parts if x["type"] == "text" + ) + messages.append({"role": item["role"], "content": text or ""}) + else: + # Single-turn: extract text from inputs + text = "\n".join( + x["value"] for x in inputs if x["type"] == "text" + ) + messages.append({"role": "user", "content": text or ""}) + + return messages + + def generate_inner(self, inputs, **kwargs): + temperature = kwargs.pop("temperature", self.temperature) + max_tokens = kwargs.pop("max_tokens", self.max_tokens) + + messages = self._prepare_messages(inputs) + payload = { + "model": self.model, + "messages": messages, + "temperature": temperature, + "max_tokens": max_tokens, + } + + try: + response = requests.post( + self.api_base, + headers={ + "Authorization": f"Bearer {self.key}", + "Content-Type": "application/json", + }, + data=json.dumps(payload), + timeout=self.timeout * 1.1, + ) + except Exception as err: + if self.verbose: + logger.error(f"{type(err).__name__}: {err}") + return -1, self.fail_msg, str(err) + + ret_code = response.status_code + ret_code = 0 if (200 <= ret_code < 300) else ret_code + answer = self.fail_msg + + try: + data = response.json() + answer = data["choices"][0]["message"]["content"].strip() + except Exception as err: + if self.verbose: + logger.error(f"{type(err).__name__}: {err}") + logger.error(response.text) + + return ret_code, answer, response diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/mug_u.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/mug_u.py new file mode 100644 index 00000000..2d3662b1 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/mug_u.py @@ -0,0 +1,214 @@ +import json +import os + +import numpy as np +import requests + +from vlmeval.api.base import BaseAPI +from vlmeval.dataset import DATASET_MODALITY, DATASET_TYPE +from vlmeval.smp import encode_image_to_base64, get_logger, listinstr + +logger = get_logger(__name__) + + +class MUGUWrapper(BaseAPI): + + is_api: bool = True + + def __init__(self, + model: str, + retry: int = 5, + key: str = None, + verbose: bool = True, + temperature: float = 0.0, + timeout: int = 60, + api_base: str = None, + system_prompt: str = None, + max_tokens: int = 4096, + use_mpo_prompt: bool = False, + **kwargs): + self.fail_msg = 'Failed to obtain answer via API. ' + self.max_tokens = max_tokens + self.timeout = timeout + + api_base = 'https://shopee.sg/api/v1/compassllvm/v1/chat/completions' + assert api_base is not None, 'Please set the environment variable LMDEPLOY_API_BASE.' + self.api_base = api_base + super().__init__(retry=retry, system_prompt=system_prompt, verbose=verbose, **kwargs) + + model_url = ''.join([api_base.split('v1')[0], 'v1/models']) + _ = requests.get(model_url) + self.model = model + if hasattr(self, 'custom_prompt'): + logger.info(f'using custom prompt {self.custom_prompt}') + self.temperature = temperature + logger.info(f'Init temperature: {self.temperature}') + self.use_mpo_prompt = use_mpo_prompt + + self.temperature = 0.0 + + def use_custom_prompt(self, dataset): + assert dataset is not None + assert DATASET_MODALITY(dataset) != 'VIDEO', 'not supported' + if listinstr(['MMDU', 'MME-RealWorld', 'MME-RealWorld-CN'], dataset): + # For Multi-Turn we don't have custom prompt + return False + if DATASET_MODALITY(dataset) == 'VIDEO': + # For Video benchmarks we don't have custom prompt at here + return False + else: + return True + + def get_max_num(self, dataset): + assert dataset is not None + res_1_datasets = ['MMBench-Video', 'Video-MME', 'MVBench', 'Video', 'WorldSense'] + res_12_datasets = ['ChartQA_TEST', 'MMMU_DEV_VAL', 'MMMU_TEST', 'MME-RealWorld', + 'VCR_EN', 'VCR_ZH', 'OCRVQA'] + res_18_datasets = ['DocVQA_VAL', 'DocVQA_TEST', 'DUDE', 'MMLongBench_DOC', 'SLIDEVQA'] + res_24_datasets = ['InfoVQA_VAL', 'InfoVQA_TEST', 'OCRBench', 'HRBench4K', 'HRBench8K'] + if listinstr(res_1_datasets, dataset): + return 1 + elif listinstr(res_12_datasets, dataset): + return 12 + elif listinstr(res_18_datasets, dataset): + return 18 + elif listinstr(res_24_datasets, dataset): + return 24 + else: + return 6 + + def build_prompt(self, line, dataset=None): + assert self.use_custom_prompt(dataset) + assert dataset is None or isinstance(dataset, str) + from ..vlm.internvl.utils import (build_mcq_cot_prompt, build_mpo_prompt, + build_multi_choice_prompt, build_qa_cot_prompt, + reorganize_prompt) + + tgt_path = self.dump_image(line, dataset) + max_num = self.get_max_num(dataset) + if dataset is not None and DATASET_TYPE(dataset) == 'Y/N': + question = line['question'] + if listinstr(['MME'], dataset): + prompt = question + ' Answer the question using a single word or phrase.' + elif listinstr(['HallusionBench', 'AMBER'], dataset): + prompt = question + ' Please answer yes or no. Answer the question using a single word or phrase.' + else: + prompt = question + elif dataset is not None and DATASET_TYPE(dataset) == 'MCQ': + prompt = build_multi_choice_prompt(line, dataset) + if os.getenv('USE_COT') == '1': + prompt = build_mcq_cot_prompt(line, prompt) + elif dataset is not None and DATASET_TYPE(dataset) == 'VQA': + question = line['question'] + if listinstr(['LLaVABench', 'WildVision'], dataset): + prompt = question + '\nAnswer this question in detail.' + elif listinstr(['OCRVQA', 'TextVQA', 'ChartQA', 'DocVQA', 'InfoVQA', 'OCRBench', + 'DUDE', 'SLIDEVQA', 'GQA', 'MMLongBench_DOC'], dataset): + prompt = question + '\nAnswer the question using a single word or phrase.' + elif listinstr(['MathVista', 'MathVision', 'VCR', 'MTVQA', 'MMVet', 'MathVerse', + 'MMDU', 'CRPE', 'MIA-Bench', 'MM-Math', 'DynaMath', + 'QSpatial', 'WeMath', 'LogicVista'], dataset): + prompt = question + if os.getenv('USE_COT') == '1': + prompt = build_qa_cot_prompt(line, prompt) + else: + prompt = question + '\nAnswer the question using a single word or phrase.' + else: + # VQA_ex_prompt: OlympiadBench, VizWiz + prompt = line['question'] + if os.getenv('USE_COT') == '1': + prompt = build_qa_cot_prompt(line, prompt) + + message = [dict(type='text', value=prompt)] + image_num = len(tgt_path) + max_num = max(1, min(max_num, 64 // image_num)) + # TODO:support upscale_flag + message.extend([dict(type='image', value=s, max_dynamic_patch=max_num) for s in tgt_path]) + + if self.use_mpo_prompt: + message = build_mpo_prompt(message, line, dataset) + + # reorganize_prompt + prompt = reorganize_prompt(message, image_num, dataset=dataset) + prompt.replace('', '') + message[0] = dict(type='text', value=prompt) + return message + + def prepare_itlist(self, inputs): + assert np.all([isinstance(x, dict) for x in inputs]) + has_images = np.sum([x['type'] == 'image' for x in inputs]) + if has_images: + content_list = [] + for msg in inputs: + if msg['type'] == 'text': + content_list.append(dict(type='text', text=msg['value'])) + elif msg['type'] == 'image': + from PIL import Image + img = Image.open(msg['value']) + b64 = encode_image_to_base64(img) + extra_args = msg.copy() + extra_args.pop('type') + extra_args.pop('value') + img_struct = dict(url=f'data:image/jpeg;base64,{b64}', **extra_args) + content_list.append(dict(type='image_url', image_url=img_struct)) + else: + assert all([x['type'] == 'text' for x in inputs]) + text = '\n'.join([x['value'] for x in inputs]) + content_list = [dict(type='text', text=text)] + return content_list + + def prepare_inputs(self, inputs): + input_msgs = [] + if self.system_prompt is not None: + input_msgs.append(dict(role='system', content=self.system_prompt)) + assert isinstance(inputs, list) and isinstance(inputs[0], dict) + assert np.all(['type' in x for x in inputs]) or np.all(['role' in x for x in inputs]), inputs + if 'role' in inputs[0]: + assert inputs[-1]['role'] == 'user', inputs[-1] + for item in inputs: + input_msgs.append(dict(role=item['role'], content=self.prepare_itlist(item['content']))) + else: + input_msgs.append(dict(role='user', content=self.prepare_itlist(inputs))) + return input_msgs + + def generate_inner(self, inputs, **kwargs) -> str: + input_msgs = self.prepare_inputs(inputs) + + temperature = kwargs.pop('temperature', self.temperature) + logger.info(f'Generate temperature: {temperature}') + max_tokens = kwargs.pop('max_tokens', self.max_tokens) + + headers = {'Content-Type': 'application/json'} + payload = dict( + model=self.model, + messages=input_msgs, + max_tokens=max_tokens, + n=1, + top_k=1, + temperature=temperature, + stream=False, + **kwargs) + + response = requests.post( + self.api_base, + headers=headers, data=json.dumps(payload), timeout=self.timeout * 1.1) + ret_code = response.status_code + ret_code = 0 if (200 <= int(ret_code) < 300) else ret_code + answer = self.fail_msg + try: + resp_struct = json.loads(response.text) + answer = resp_struct['choices'][0]['message']['content'].strip() + + # for internvl2-8b-mpo-cot + if getattr(self, 'use_mpo_prompt', False): + from ..vlm.internvl.utils import mpo_post_processing + + answer = mpo_post_processing(answer, kwargs.get('dataset')) + except Exception: + pass + return ret_code, answer, response + + +class MUGUAPI(MUGUWrapper): + def generate(self, message, dataset=None): + return super(MUGUAPI, self).generate(message, dataset=dataset) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/nemotron.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/nemotron.py new file mode 100644 index 00000000..645f9438 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/nemotron.py @@ -0,0 +1,21 @@ +"""NVIDIA Nemotron VL family wrapper. + +NanoNemotronVLProcessor 400s on any mm_processor_kwarg (do_sample_frames, +fps, ...), so prepare_inputs drops video_kwargs to omit the field. +""" + +from .cosmos_reason import CosmosReason + + +class Nemotron(CosmosReason): + """NVIDIA Nemotron VL endpoint wrapper (Nemotron-3-Nano-Omni and successors).""" + + def __init__(self, *args, **kwargs): + # Required by CosmosReason.prepare_itlist video branch. + kwargs.setdefault('image_patch_size', 16) + super().__init__(*args, **kwargs) + + def prepare_inputs(self, inputs): + # NanoNemotronVLProcessor 400s on any mm_processor_kwarg. + input_msgs, _ = super().prepare_inputs(inputs) + return input_msgs, None diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/nv_gemini.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/nv_gemini.py new file mode 100644 index 00000000..421d2c07 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/nv_gemini.py @@ -0,0 +1,277 @@ +"""NV-deployed Gemini wrapper for gcp/google/gemini-3.1-{pro,flash-lite}-preview. + +Routes through NV inference gateway's Vertex passthrough +(inference-api.nvidia.com/vertex_ai/v1/.../models/{MODEL}:generateContent). +Native Gemini schema (contents/parts/inlineData) — the OpenAI-compat path on +the same gateway resamples video to ~1 fps server-side, so Vertex passthrough +is the only path that preserves the client's encoded fps end-to-end. + +Reuses cosmos_reason.process_video_info_to_video_url for client-side +resample + lossless ffmpeg re-encode; sets videoMetadata.fps to the +re-encoded fps so Vertex samples at exactly that rate (same semantic as the +vLLM --media-io-kwargs num_frames=-1, fps=-1 production setup). + +The same class targets vanilla GCP Vertex (aiplatform.googleapis.com); only +$OPENAI_API_KEY needs to hold a different bearer ($(gcloud auth +print-access-token)). +""" + +import base64 +import copy +import json +import os + +import requests + +from ..smp import get_logger +from .base import BaseAPI +from .cosmos_reason import _process_video_with_timeout + +logger = get_logger(__name__) + + +_VIDEO_KW_KEYS = ( + 'nframes', 'fps', 'total_pixels', 'max_pixels', 'min_pixels', 'max_frames', +) + + +class NVGemini(BaseAPI): + is_api: bool = True + VIDEO_LLM: bool = True + allowed_types = ['text', 'image', 'video'] + + def __init__( + self, + model: str, + api_base: str, + key: str | None = None, + enable_thinking: bool | None = None, + thinking_budget: int | None = None, + max_tokens: int = 8192, + temperature: float = 0.0, + retry: int = 10, + wait: int = 5, + timeout: int = 600, + video_proc_timeout: int = 300, + use_video_cache: bool = True, + verbose: bool = False, + image_patch_size: int = 16, + system_prompt: str | None = None, + **kwargs, + ): + self.model = model + self.api_base = api_base + self.key = key or os.environ.get('OPENAI_API_KEY', '') + self.thinking_budget = self._resolve_thinking_budget( + enable_thinking=enable_thinking, + thinking_budget=thinking_budget, + chat_template_kwargs=kwargs.get('chat_template_kwargs'), + ) + self.max_tokens = max_tokens + self.temperature = temperature + self.timeout = timeout + self.video_proc_timeout = video_proc_timeout + self.use_video_cache = use_video_cache + self.image_patch_size = image_patch_size + self._last_error_type = None + + super().__init__( + retry=retry, + wait=wait, + verbose=verbose, + system_prompt=system_prompt, + ) + logger.info( + f'NVGemini: model={self.model}, api_base={self.api_base}, ' + f'thinking_budget={self.thinking_budget}' + ) + self._preflight() + + @staticmethod + def _resolve_thinking_budget(enable_thinking, thinking_budget, chat_template_kwargs): + if thinking_budget is not None: + return int(thinking_budget) + flag = enable_thinking + if flag is None and chat_template_kwargs: + flag = chat_template_kwargs.get('enable_thinking') + return 8192 if flag else 0 + + def _preflight(self): + """One-shot :generateContent probe replacing CosmosReason's + _check_endpoint_health + _resolve_model_name. Logs loudly on failure + but does not raise — transient startup blips shouldn't kill the job, + and a genuinely broken endpoint will surface as http_error counts on + every subsequent request via the normal retry loop. + """ + payload = { + 'contents': [{'role': 'user', 'parts': [{'text': 'ping'}]}], + 'generationConfig': { + 'maxOutputTokens': 8, + 'thinkingConfig': {'thinkingBudget': 0}, + }, + } + headers = { + 'Authorization': f'Bearer {self.key}', + 'Content-Type': 'application/json', + } + try: + r = requests.post( + self.api_base, headers=headers, + data=json.dumps(payload), timeout=30, + ) + except Exception as e: + logger.warning( + f'⚠️ NVGemini preflight network error: {type(e).__name__}: {e}. ' + f'Proceeding; live requests will surface the issue.' + ) + return + if r.status_code == 200 and r.json().get('candidates'): + logger.info(f'✓ NVGemini preflight passed: {self.api_base}') + return + logger.error( + f'⚠️ NVGemini preflight failed (HTTP {r.status_code}): {r.text[:400]}. ' + f'Proceeding; live requests will surface the issue.' + ) + + # ---- part builders ------------------------------------------------------ + + def _video_kwargs(self, msg: dict) -> dict: + kw = copy.deepcopy(msg) + kw.pop('type', None) + kw.pop('value', None) + return {k: v for k, v in kw.items() if k in _VIDEO_KW_KEYS and v is not None} + + def _video_part(self, msg: dict) -> dict: + video_url, video_kwargs = _process_video_with_timeout( + msg['value'], + image_patch_size=self.image_patch_size, + use_cache=self.use_video_cache, + kwargs=self._video_kwargs(msg), + timeout=self.video_proc_timeout, + ) + b64 = video_url.split('base64,', 1)[1] + part = {'inlineData': {'mimeType': 'video/mp4', 'data': b64}} + fps = (video_kwargs or {}).get('fps') + if fps is not None: + part['videoMetadata'] = {'fps': fps if not isinstance(fps, list) else fps[0]} + return part + + def _image_part(self, msg: dict) -> dict: + with open(msg['value'], 'rb') as f: + b64 = base64.b64encode(f.read()).decode() + suffix = msg['value'].lower().rsplit('.', 1)[-1] + if suffix in ('jpg', 'jpeg'): + mime = 'image/jpeg' + elif suffix in ('png', 'webp', 'gif'): + mime = f'image/{suffix}' + else: + mime = 'image/png' + return {'inlineData': {'mimeType': mime, 'data': b64}} + + def _part_for(self, msg: dict) -> dict: + t = msg['type'] + if t == 'text': + return {'text': msg['value']} + if t == 'image': + return self._image_part(msg) + if t == 'video': + return self._video_part(msg) + raise ValueError(f'NVGemini: unsupported message type {t!r}') + + # ---- generate ----------------------------------------------------------- + + def generate_inner(self, inputs, **kwargs): + self._last_error_type = None + try: + parts = [self._part_for(x) for x in inputs] + except Exception as e: + logger.warning(f'NVGemini: input preparation failed: {type(e).__name__}: {e}') + return 0, 'VIDEO_LOAD_FAILED', None + + max_tokens = kwargs.get('max_tokens', self.max_tokens) + temperature = kwargs.get('temperature', self.temperature) + payload = { + 'contents': [{'role': 'user', 'parts': parts}], + 'generationConfig': { + 'maxOutputTokens': max_tokens, + 'temperature': temperature, + 'thinkingConfig': {'thinkingBudget': self.thinking_budget}, + }, + } + if self.system_prompt: + payload['systemInstruction'] = {'parts': [{'text': self.system_prompt}]} + + headers = { + 'Authorization': f'Bearer {self.key}', + 'Content-Type': 'application/json', + } + try: + r = requests.post( + self.api_base, headers=headers, + data=json.dumps(payload), timeout=self.timeout * 1.1, + ) + except requests.exceptions.Timeout: + logger.error(f'⚠️ ENDPOINT TIMEOUT: {self.api_base}') + self._last_error_type = 'timeout' + return 408, self.fail_msg, None + except requests.exceptions.ConnectionError as e: + logger.error(f'⚠️ ENDPOINT DOWN: {self.api_base}: {e}') + self._last_error_type = 'http_error' + return 503, self.fail_msg, None + except Exception as e: + logger.error(f'⚠️ REQUEST FAILED: {type(e).__name__}: {e}') + self._last_error_type = 'http_error' + return 500, self.fail_msg, None + + if r.status_code != 200: + self._last_error_type = 'timeout' if r.status_code in (408, 504) else 'http_error' + logger.error(f'⚠️ HTTP {r.status_code} from {self.api_base}: {r.text[:400]}') + return r.status_code, self.fail_msg + r.text[:400], r.text + + try: + data = r.json() + except Exception as e: + self._last_error_type = 'http_error' + return -1, self.fail_msg + f'json decode: {e}', r.text + + cands = data.get('candidates') or [] + if not cands: + self._last_error_type = 'http_error' + return -1, self.fail_msg + json.dumps(data)[:400], json.dumps(data) + + finish = cands[0].get('finishReason') + text = ''.join( + p.get('text', '') + for p in cands[0].get('content', {}).get('parts', []) + ) + if finish == 'MAX_TOKENS': + self._last_error_type = 'truncated' + logger.info(f"⚠️ Answer truncated at maxOutputTokens={max_tokens}.") + elif finish == 'SAFETY': + self._last_error_type = 'content_filter' + logger.info('⚠️ Response blocked by Gemini safety filter (finishReason=SAFETY).') + + if self.verbose: + logger.info(f'NVGemini response: {text[:200]}') + + return 0, text.strip(), r.text + + def generate(self, message, **kwargs): + """Record per-sample inference outcome to $INFERENCE_STATS_FILE, mirroring + CosmosReason.generate so run_metric.py aggregation works unchanged.""" + result = super().generate(message, **kwargs) + stats_file = os.environ.get('INFERENCE_STATS_FILE') + if stats_file: + is_success = result is not None and self.fail_msg not in str(result) + if self._last_error_type == 'truncated': + category = 'truncated' + elif is_success: + category = 'success' + else: + category = self._last_error_type or 'other' + try: + with open(stats_file, 'a') as f: + f.write(json.dumps({'outcome': category}) + '\n') + except Exception: + pass + return result diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/openai_sdk.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/openai_sdk.py new file mode 100644 index 00000000..8f734321 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/openai_sdk.py @@ -0,0 +1,140 @@ +import json + +import requests + +from ..smp import get_logger +from .base import BaseAPI + +logger = get_logger(__name__) + + +class OpenAISDKWrapper(BaseAPI): + """Base class for OpenAI-compatible API wrappers. + + Provides a standard ``generate_inner`` implementation with optional + :class:`~vlmeval.api.adapters.ModelAdapter` support for model-specific + prompt building and payload processing. + + Subclasses **must** implement :meth:`prepare_inputs` to convert the + internal message list into the HTTP message format expected by their + specific API endpoint. + + Attributes: + adapter: An optional :class:`ModelAdapter` instance. Set to + ``None`` to disable adapter hooks. + key (str): API authentication key. + api_base (str): Full URL of the chat completions endpoint. + model (str): Model identifier sent in the request payload. + timeout (int): HTTP request timeout in seconds. + """ + + is_api: bool = True + + def __init__(self, + retry=5, + wait=5, + verbose=True, + system_prompt=None, + model_adapter=None, + **kwargs): + """ + Args: + model_adapter: Either a :class:`ModelAdapter` instance, a + registered adapter name (``str``), or ``None``. + """ + self.adapter = None + if model_adapter is not None: + if isinstance(model_adapter, str): + from .adapters import build_adapter + self.adapter = build_adapter(model_adapter) + else: + self.adapter = model_adapter + + super().__init__( + retry=retry, + wait=wait, + system_prompt=system_prompt, + verbose=verbose, + **kwargs, + ) + + # ------------------------------------------------------------------ + # Adapter integration + # ------------------------------------------------------------------ + + def set_dump_image(self, dump_image_func): + if self.adapter is not None: + self.adapter.dump_image_func = dump_image_func + self.dump_image_func = dump_image_func + + def use_custom_prompt(self, dataset) -> bool: + if self.adapter is not None: + return self.adapter.use_custom_prompt(dataset, self.system_prompt) + return False + + def build_prompt(self, line, dataset=None): + if self.adapter is not None: + return self.adapter.build_prompt(line, dataset) + raise NotImplementedError + + # ------------------------------------------------------------------ + # HTTP formatting – subclasses must override + # ------------------------------------------------------------------ + + def prepare_inputs(self, inputs, system_prompt): + """Convert the internal message list to HTTP message dicts. + + Args: + inputs: List of ``{'type': ..., 'value': ...}`` dicts, or a + list of role-keyed dicts for multi-turn conversations. + system_prompt: System prompt string, or ``None``. + + Returns: + List of OpenAI-style message dicts with ``role`` and + ``content`` fields. + """ + raise NotImplementedError( + f'{type(self).__name__} must implement prepare_inputs()' + ) + + # ------------------------------------------------------------------ + # Core generation loop + # ------------------------------------------------------------------ + + def generate_inner(self, inputs, dataset=None, **kwargs) -> tuple: + if self.adapter is not None: + model_args = self.adapter.override_model_args(dataset) + system_prompt = model_args.pop('system_prompt', self.system_prompt) + inputs = self.adapter.process_inputs(inputs, dataset) + kwargs.update(model_args) + else: + system_prompt = self.system_prompt + + input_msgs = self.prepare_inputs(inputs, system_prompt) + + headers = { + 'Content-Type': 'application/json', + 'Authorization': f'Bearer {self.key}', + } + payload = dict(model=self.model, messages=input_msgs, n=1, **kwargs) + + if self.adapter is not None: + payload = self.adapter.process_payload(payload, dataset=dataset) + + response = requests.post( + self.api_base, + headers=headers, + data=json.dumps(payload), + timeout=self.timeout * 1.1, + ) + ret_code = response.status_code + ret_code = 0 if (200 <= int(ret_code) < 300) else ret_code + answer = self.fail_msg + try: + resp_struct = json.loads(response.text) + answer = resp_struct['choices'][0]['message']['content'].strip() + if self.adapter is not None: + answer = self.adapter.postprocess(answer, dataset=dataset) + except Exception: + pass + return ret_code, answer, response diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/qwen_api.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/qwen_api.py new file mode 100644 index 00000000..9fcea5fc --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/qwen_api.py @@ -0,0 +1,75 @@ +import copy as cp +import os +from http import HTTPStatus + +from vlmeval.api.base import BaseAPI +from vlmeval.smp import proxy_set + + +# Note: This is a pure language model API. +class QwenAPI(BaseAPI): + + is_api: bool = True + + def __init__(self, + model: str = 'qwen-max-1201', + retry: int = 5, + verbose: bool = True, + seed: int = 2680, + temperature: float = 0.0, + system_prompt: str = None, + key: str = None, + max_tokens: int = 2048, + proxy: str = None, + **kwargs): + + assert model in ['qwen-turbo', 'qwen-plus', 'qwen-max', 'qwen-max-1201', 'qwen-max-longcontext'] + self.model = model + import dashscope + self.fail_msg = 'Failed to obtain answer via API. ' + self.max_tokens = max_tokens + self.temperature = temperature + self.seed = seed + if key is None: + key = os.environ.get('DASHSCOPE_API_KEY', None) + assert key is not None, ( + 'Please set the API Key (obtain it here: ' + 'https://help.aliyun.com/zh/dashscope/developer-reference/vl-plus-quick-start)' + ) + dashscope.api_key = key + if proxy is not None: + proxy_set(proxy) + super().__init__(retry=retry, system_prompt=system_prompt, verbose=verbose, **kwargs) + + @staticmethod + def build_msgs(msgs_raw, system_prompt=None): + msgs = cp.deepcopy(msgs_raw) + ret = [] + if system_prompt is not None: + ret.append(dict(role='system', content=system_prompt)) + for i, msg in enumerate(msgs): + role = 'user' if i % 2 == 0 else 'assistant' + ret.append(dict(role=role, content=msg)) + return ret + + def generate_inner(self, inputs, **kwargs) -> str: + assert isinstance(inputs, str) or isinstance(inputs, list) + inputs = [inputs] if isinstance(inputs, str) else inputs + messages = self.build_msgs(msgs_raw=inputs, system_prompt=self.system_prompt) + + import dashscope + response = dashscope.Generation.call( + model=self.model, + messages=messages, + seed=self.seed, + temperature=self.temperature, + max_tokens=self.max_tokens, + result_format='message', # set the result to be "message" format. + ) + if response.status_code != HTTPStatus.OK: + return -1, 'Error: Bad Response Statuse Code. ', f'The response status code is {response.status_code}. ' + + try: + return 0, response['output']['choices'][0]['message']['content'].strip(), 'Succeeded! ' + except Exception as err: + return -1, f'Error: Failed to parse the response. {err}', response diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/qwen_vl_api.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/qwen_vl_api.py new file mode 100644 index 00000000..fee909f8 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/qwen_vl_api.py @@ -0,0 +1,222 @@ +from __future__ import annotations +import os +import warnings + +import numpy as np + +from vlmeval.api.base import BaseAPI +from vlmeval.smp import get_logger, proxy_set +from vlmeval.vlm.qwen2_vl.prompt import Qwen2VLPromptMixin + +logger = get_logger(__name__) + + +def ensure_image_url(image: str) -> str: + prefixes = ['http://', 'https://', 'file://', 'data:image;'] + if any(image.startswith(prefix) for prefix in prefixes): + return image + if os.path.exists(image): + return 'file://' + image + raise ValueError(f'Invalid image: {image}') + + +class Qwen2VLAPI(Qwen2VLPromptMixin, BaseAPI): + is_api: bool = True + + def __init__( + self, + model: str = 'qwen-vl-max-0809', + key: str | None = None, + min_pixels: int | None = None, + max_pixels: int | None = None, + max_length=1024, + top_p=0.001, + top_k=1, + temperature=0.01, + repetition_penalty=1.0, + presence_penalty=0.0, + seed=3407, + use_custom_prompt: bool = True, + **kwargs, + ): + import dashscope + + self.model = model + self.min_pixels = min_pixels + self.max_pixels = max_pixels + self.generate_kwargs = dict( + max_length=max_length, + top_p=top_p, + top_k=top_k, + temperature=temperature, + repetition_penalty=repetition_penalty, + presence_penalty=presence_penalty, + seed=seed, + ) + + key = os.environ.get('DASHSCOPE_API_KEY', None) if key is None else key + assert key is not None, ( + 'Please set the API Key (obtain it here: ' + 'https://help.aliyun.com/zh/dashscope/developer-reference/vl-plus-quick-start)' + ) + dashscope.api_key = key + super().__init__(use_custom_prompt=use_custom_prompt, **kwargs) + + def _prepare_content(self, inputs: list[dict[str, str]], dataset: str | None = None) -> list[dict[str, str]]: + """ + inputs list[dict[str, str]], each dict has keys: ['type', 'value'] + """ + content = [] + for s in inputs: + if s['type'] == 'image': + item = {'type': 'image', 'image': ensure_image_url(s['value'])} + if dataset == 'OCRBench': + item['min_pixels'] = 10 * 10 * 28 * 28 + warnings.warn(f"OCRBench dataset uses custom min_pixels={item['min_pixels']}") + if self.max_pixels is not None: + item['max_pixels'] = self.max_pixels + else: + if self.min_pixels is not None: + item['min_pixels'] = self.min_pixels + if self.max_pixels is not None: + item['max_pixels'] = self.max_pixels + elif s['type'] == 'text': + item = {'type': 'text', 'text': s['value']} + else: + raise ValueError(f"Invalid message type: {s['type']}, {s}") + content.append(item) + return content + + def generate_inner(self, inputs, **kwargs) -> str: + import dashscope + + messages = [] + if self.system_prompt is not None: + messages.append({'role': 'system', 'content': self.system_prompt}) + messages.append( + {'role': 'user', 'content': self._prepare_content(inputs, dataset=kwargs.get('dataset', None))} + ) + if self.verbose: + print(f'\033[31m{messages}\033[0m') + + # generate + generation_kwargs = self.generate_kwargs.copy() + kwargs.pop('dataset', None) + generation_kwargs.update(kwargs) + try: + response = dashscope.MultiModalConversation.call( + model=self.model, + messages=messages, + **generation_kwargs, + ) + if self.verbose: + print(response) + answer = response.output.choices[0]['message']['content'][0]['text'] + return 0, answer, 'Succeeded! ' + except Exception as err: + if self.verbose: + logger.error(f'{type(err)}: {err}') + logger.error(f'The input messages are {inputs}.') + return -1, '', '' + + +class QwenVLWrapper(BaseAPI): + + is_api: bool = True + + def __init__(self, + model: str = 'qwen-vl-plus', + retry: int = 5, + key: str = None, + verbose: bool = True, + temperature: float = 0.0, + system_prompt: str = None, + max_tokens: int = 2048, + proxy: str = None, + **kwargs): + + assert model in ['qwen-vl-plus', 'qwen-vl-max'] + self.model = model + import dashscope + self.fail_msg = 'Failed to obtain answer via API. ' + self.max_tokens = max_tokens + self.temperature = temperature + if key is None: + key = os.environ.get('DASHSCOPE_API_KEY', None) + assert key is not None, ( + 'Please set the API Key (obtain it here: ' + 'https://help.aliyun.com/zh/dashscope/developer-reference/vl-plus-quick-start)' + ) + dashscope.api_key = key + if proxy is not None: + proxy_set(proxy) + super().__init__(retry=retry, system_prompt=system_prompt, verbose=verbose, **kwargs) + + # inputs can be a lvl-2 nested list: [content1, content2, content3, ...] + # content can be a string or a list of image & text + def prepare_itlist(self, inputs): + assert np.all([isinstance(x, dict) for x in inputs]) + has_images = np.sum([x['type'] == 'image' for x in inputs]) + if has_images: + content_list = [] + for msg in inputs: + if msg['type'] == 'text': + content_list.append(dict(text=msg['value'])) + elif msg['type'] == 'image': + content_list.append(dict(image='file://' + msg['value'])) + else: + assert all([x['type'] == 'text' for x in inputs]) + text = '\n'.join([x['value'] for x in inputs]) + content_list = [dict(text=text)] + return content_list + + def prepare_inputs(self, inputs): + input_msgs = [] + if self.system_prompt is not None: + input_msgs.append(dict(role='system', content=self.system_prompt)) + assert isinstance(inputs, list) and isinstance(inputs[0], dict) + assert np.all(['type' in x for x in inputs]) or np.all(['role' in x for x in inputs]), inputs + if 'role' in inputs[0]: + assert inputs[-1]['role'] == 'user', inputs[-1] + for item in inputs: + input_msgs.append(dict(role=item['role'], content=self.prepare_itlist(item['content']))) + else: + input_msgs.append(dict(role='user', content=self.prepare_itlist(inputs))) + return input_msgs + + def generate_inner(self, inputs, **kwargs) -> str: + from dashscope import MultiModalConversation + + assert isinstance(inputs, str) or isinstance(inputs, list) + + if 'type' in inputs[0]: + pure_text = np.all([x['type'] == 'text' for x in inputs]) + else: + pure_text = True + for inp in inputs: + if not np.all([x['type'] == 'text' for x in inp['content']]): + pure_text = False + break + + assert not pure_text + messages = self.prepare_inputs(inputs) + gen_config = dict(max_output_tokens=self.max_tokens, temperature=self.temperature) + gen_config.update(kwargs) + try: + response = MultiModalConversation.call(model=self.model, messages=messages) + if self.verbose: + print(response) + answer = response.output.choices[0]['message']['content'][0]['text'] + return 0, answer, 'Succeeded! ' + except Exception as err: + if self.verbose: + logger.error(f'{type(err)}: {err}') + logger.error(f'The input messages are {inputs}.') + + return -1, '', '' + + +class QwenVLAPI(QwenVLWrapper): + + def generate(self, message, dataset=None): + return super(QwenVLAPI, self).generate(message) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/rbdashmm_chat3_5_api.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/rbdashmm_chat3_5_api.py new file mode 100644 index 00000000..b50ecd2f --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/rbdashmm_chat3_5_api.py @@ -0,0 +1,601 @@ +import json +import os +import re + +import numpy as np +import requests + +from vlmeval.api.base import BaseAPI +from vlmeval.dataset import DATASET_MODALITY, DATASET_TYPE +from vlmeval.smp import encode_image_to_base64, get_logger, listinstr + +logger = get_logger(__name__) + + +def process_prediction_text(text): + if not isinstance(text, str): + return text + + if "\\boxed{" in text: + start = text.rfind("\\boxed{") + i = start + len("\\boxed{") + brace_count = 1 + content_chars = [] + + while i < len(text) and brace_count > 0: + ch = text[i] + if ch == "{": + brace_count += 1 + elif ch == "}": + brace_count -= 1 + if brace_count > 0: + content_chars.append(ch) + i += 1 + + if content_chars: + inner = "".join(content_chars).strip() + if inner: + return inner + + redacted_pattern = r'.*?' + if re.search(redacted_pattern, text, re.DOTALL): + result = re.sub(redacted_pattern, '', text, flags=re.DOTALL) + return result.strip() + + # 默认返回 + return text + + +R1_SYSTEM_PROMPT = """ +You are an AI assistant that rigorously follows this response protocol: + +1. First, conduct a detailed analysis of the question. Consider different \ +angles, potential solutions, and reason through the problem step-by-step. \ +Enclose this entire thinking process within and tags. + +2. After the thinking section, provide a clear, concise, and direct answer to \ +the user's question. Separate the answer from the think section with a newline. + +Ensure that the thinking process is thorough but remains focused on the \ +query. The final answer should be standalone and not reference the thinking \ +section. +""".strip() + + +class RBdashMMChat3_78B_PromptUtil: + def __init__(self, use_mpo_prompt=False): + self.use_mpo_prompt = use_mpo_prompt + self.cot_prompt = 'Please answer the question and put the final answer within \\boxed{}.' + + def dump_image(self, line, dataset): + return self.dump_image_func(line) + + def use_custom_prompt(self, dataset): + assert dataset is not None + assert DATASET_MODALITY(dataset) != 'VIDEO', 'not supported' + if dataset in [ + 'atomic_dataset', 'electro_dataset', 'mechanics_dataset', + 'optics_dataset', 'quantum_dataset', 'statistics_dataset' + ]: + return False + if listinstr(['MMDU', 'MME-RealWorld', 'MME-RealWorld-CN', 'WeMath_COT', 'MMAlignBench'], dataset): + # For Multi-Turn we don't have custom prompt + return False + if DATASET_MODALITY(dataset) == 'VIDEO': + # For Video benchmarks we don't have custom prompt at here + return False + else: + return True + + def build_prompt(self, line, dataset=None): + use_cot = (os.getenv('USE_COT') == '1') + use_mpo_prompt = self.use_mpo_prompt and (use_cot or dataset in ['MMStar', 'HallusionBench', 'OCRBench']) + + assert self.use_custom_prompt(dataset) + assert dataset is None or isinstance(dataset, str) + from ..vlm.internvl.utils import (build_mcq_cot_prompt, build_mpo_prompt, + build_multi_choice_prompt, build_qa_cot_prompt, + reorganize_prompt) + + tgt_path = self.dump_image(line, dataset) + max_num = self.get_max_num(dataset) + if dataset is not None and DATASET_TYPE(dataset) == 'Y/N': + question = line['question'] + if listinstr(['MME'], dataset): + prompt = question + ' Answer the question using a single word or phrase.' + elif listinstr(['HallusionBench', 'AMBER'], dataset): + prompt = question + cot_prompt = ( + "Answer the preceding yes-or-no question. Think step by step logically, considering all " + "relevant information before answering. If you are uncertain or the problem is ambiguous, make a " + "reasoned guess based on the information provided. The last line of your response must be exactly " + "in this format: 'Answer: \\boxed{Yes}' or 'Answer: \\boxed{No}' (without quotes)." + ) + prompt = prompt + '\n' + cot_prompt + else: + prompt = question + elif dataset is not None and DATASET_TYPE(dataset) == 'MCQ': + prompt = build_multi_choice_prompt(line, dataset) + if os.getenv('USE_COT') == '1': + prompt = build_mcq_cot_prompt(line, prompt, self.cot_prompt) + elif dataset is not None and DATASET_TYPE(dataset) == 'VQA': + question = line['question'] + if listinstr(['LLaVABench', 'WildVision'], dataset): + prompt = question + '\nAnswer this question in detail.' + elif listinstr(['OCRVQA', 'TextVQA', 'ChartQA', 'DocVQA', 'InfoVQA', 'OCRBench', + 'DUDE', 'SLIDEVQA', 'GQA', 'MMLongBench_DOC'], dataset): + prompt = question + '\nAnswer the question using a single word or phrase. \ + The image is guaranteed to contain the correct answer. Please provide the most likely \ + answer — do not answer "No." Let us think step by step.' + elif listinstr(['MathVista', 'MathVision', 'VCR', 'MTVQA', 'MMVet', 'MathVerse', + 'MMDU', 'CRPE', 'MIA-Bench', 'MM-Math', 'DynaMath', + 'QSpatial', 'WeMath', 'LogicVista'], dataset): + if listinstr(['MMVet'], dataset): + prompt = question + "\nPlease first think and then answer the question." + else: + prompt = question + if os.getenv('USE_COT') == '1': + prompt = build_qa_cot_prompt(line, prompt, self.cot_prompt) + else: + prompt = question + '\nAnswer the question using a single word or phrase.' + else: + # VQA_ex_prompt: OlympiadBench, VizWiz + prompt = line['question'] + if os.getenv('USE_COT') == '1': + prompt = build_qa_cot_prompt(line, prompt) + + message = [dict(type='text', value=prompt)] + image_num = len(tgt_path) + max_num = max(1, min(max_num, 64 // image_num)) + # TODO:support upscale_flag + message.extend([dict(type='image', value=s, max_dynamic_patch=max_num) for s in tgt_path]) + + if use_mpo_prompt: + message = build_mpo_prompt(message, line, dataset) + + # reorganize_prompt + prompt = reorganize_prompt(message, image_num, dataset=dataset) + prompt.replace('', '') + message[0] = dict(type='text', value=prompt) + return message + + def get_max_num(self, dataset): + self.total_max_num = 64 + if dataset is None: + self.max_num = 6 + return None + res_8_datasets = ['MMStar'] + res_12_datasets = ['ChartQA_TEST', 'MMMU_DEV_VAL', 'MMMU_TEST', 'MME-RealWorld', 'HallusionBench', + 'VCR_EN', 'VCR_ZH', 'OCRVQA', 'BMMR', 'MathVista_MINI'] + res_18_datasets = ['DocVQA_VAL', 'DocVQA_TEST', 'DUDE', 'MMLongBench_DOC', 'SLIDEVQA'] + res_24_datasets = ['InfoVQA_VAL', 'InfoVQA_TEST', 'OCRBench', 'HRBench4K', 'HRBench8K', 'MMVet'] + if DATASET_MODALITY(dataset) == 'VIDEO': + self.max_num = 1 + elif listinstr(res_8_datasets, dataset): + return 8 + elif listinstr(res_12_datasets, dataset): + return 12 + elif listinstr(res_18_datasets, dataset): + return 18 + elif listinstr(res_24_datasets, dataset): + return 24 + else: + return 6 + + +class RBdashMMChat3_5_38B_PromptUtil: + def __init__(self, use_mpo_prompt=False): + self.use_mpo_prompt = use_mpo_prompt + self.cot_prompt = 'Please answer the question and put the final answer within \\boxed{}.' + + def dump_image(self, line, dataset): + return self.dump_image_func(line) + + def use_custom_prompt(self, dataset): + assert dataset is not None + assert DATASET_MODALITY(dataset) != 'VIDEO', 'not supported' + if dataset in [ + 'atomic_dataset', 'electro_dataset', 'mechanics_dataset', + 'optics_dataset', 'quantum_dataset', 'statistics_dataset' + ]: + return False + if listinstr(['MMDU', 'MME-RealWorld', 'MME-RealWorld-CN', 'WeMath_COT', 'MMAlignBench'], dataset): + # For Multi-Turn we don't have custom prompt + return False + if DATASET_MODALITY(dataset) == 'VIDEO': + # For Video benchmarks we don't have custom prompt at here + return False + else: + return True + + def build_prompt(self, line, dataset=None): + use_cot = (os.getenv('USE_COT') == '1') + use_mpo_prompt = self.use_mpo_prompt and (use_cot or dataset in ['MMStar', 'HallusionBench', 'OCRBench']) + + assert self.use_custom_prompt(dataset) + assert dataset is None or isinstance(dataset, str) + from ..vlm.internvl.utils import (build_mcq_cot_prompt, build_mpo_prompt, + build_multi_choice_prompt, build_qa_cot_prompt, + reorganize_prompt) + + tgt_path = self.dump_image(line, dataset) + max_num = self.get_max_num(dataset) + if dataset is not None and DATASET_TYPE(dataset) == 'Y/N': + question = line['question'] + if listinstr(['MME'], dataset): + prompt = question + ' Answer the question using a single word or phrase.' + elif listinstr(['HallusionBench', 'AMBER'], dataset): + prompt = question + cot_prompt = ( + "Answer the preceding yes-or-no question. Think step by step logically, considering all " + "relevant information before answering. If you are uncertain or the problem is ambiguous, make " + "a reasoned guess based on the information provided. The last line of your response must be " + "exactly in this format: 'Answer: \\boxed{Yes}' or 'Answer: \\boxed{No}' (without quotes)." + ) + prompt = prompt + '\n' + cot_prompt + else: + prompt = question + elif dataset is not None and DATASET_TYPE(dataset) == 'MCQ': + prompt = build_multi_choice_prompt(line, dataset) + if os.getenv('USE_COT') == '1': + prompt = build_mcq_cot_prompt(line, prompt, self.cot_prompt) + elif dataset is not None and DATASET_TYPE(dataset) == 'VQA': + question = line['question'] + if listinstr(['LLaVABench', 'WildVision'], dataset): + prompt = question + '\nAnswer this question in detail.' + elif listinstr(['OCRVQA', 'TextVQA', 'ChartQA', 'DocVQA', 'InfoVQA', 'OCRBench', + 'DUDE', 'SLIDEVQA', 'GQA', 'MMLongBench_DOC'], dataset): + prompt = question + '\nAnswer the question using a single word or phrase. \ + The image is guaranteed to contain the correct answer. Please provide the most likely \ + answer — do not answer "No." Let us think step by step.' + elif listinstr(['MathVista', 'MathVision', 'VCR', 'MTVQA', 'MMVet', 'MathVerse', + 'MMDU', 'CRPE', 'MIA-Bench', 'MM-Math', 'DynaMath', + 'QSpatial', 'WeMath', 'LogicVista'], dataset): + prompt = question + if os.getenv('USE_COT') == '1': + prompt = build_qa_cot_prompt(line, prompt, self.cot_prompt) + else: + prompt = question + '\nAnswer the question using a single word or phrase.' + else: + # VQA_ex_prompt: OlympiadBench, VizWiz + prompt = line['question'] + if os.getenv('USE_COT') == '1': + prompt = build_qa_cot_prompt(line, prompt) + + message = [dict(type='text', value=prompt)] + image_num = len(tgt_path) + max_num = max(1, min(max_num, 64 // image_num)) + # TODO:support upscale_flag + message.extend([dict(type='image', value=s, max_dynamic_patch=max_num) for s in tgt_path]) + + if use_mpo_prompt: + message = build_mpo_prompt(message, line, dataset) + + # reorganize_prompt + prompt = reorganize_prompt(message, image_num, dataset=dataset) + prompt.replace('', '') + message[0] = dict(type='text', value=prompt) + return message + + def get_max_num(self, dataset): + self.total_max_num = 64 + if dataset is None: + self.max_num = 6 + return None + res_8_datasets = ['MMStar'] + res_12_datasets = ['ChartQA_TEST', 'MMMU_DEV_VAL', 'MMMU_TEST', 'MME-RealWorld', + 'VCR_EN', 'VCR_ZH', 'OCRVQA', 'BMMR', 'MathVista_MINI'] + res_18_datasets = ['DocVQA_VAL', 'DocVQA_TEST', 'DUDE', 'MMLongBench_DOC', 'SLIDEVQA'] + res_24_datasets = ['InfoVQA_VAL', 'InfoVQA_TEST', 'OCRBench', 'HRBench4K', 'HRBench8K', 'MMVet'] + if DATASET_MODALITY(dataset) == 'VIDEO': + self.max_num = 1 + elif listinstr(res_8_datasets, dataset): + return 8 + elif listinstr(res_12_datasets, dataset): + return 12 + elif listinstr(res_18_datasets, dataset): + return 18 + elif listinstr(res_24_datasets, dataset): + return 24 + else: + return 6 + + +class RBdashMMChat3_78B_Wrapper(BaseAPI): + is_api: bool = True + custom_prompt = 'rbdash3' + prompt_map = { + 'rbdash3': RBdashMMChat3_78B_PromptUtil() + } + + def __init__(self, + model: str = None, + retry: int = 5, + key: str = 'sk-123456', + verbose: bool = True, + temperature: float = 0.0, + timeout: int = 600, + api_base: str = None, + system_prompt: str = None, + max_tokens: int = 20480, + **kwargs): + self.fail_msg = 'Failed to obtain answer via API. ' + self.max_tokens = max_tokens + self.timeout = timeout + + key = os.environ.get('LMDEPLOY_API_KEY', key) + api_base = os.environ.get('LMDEPLOY_API_BASE', api_base) + assert key is not None, 'Please set the environment variable LMDEPLOY_API_KEY.' + assert api_base is not None, 'Please set the environment variable LMDEPLOY_API_BASE.' + self.key = key + self.api_base = api_base + super().__init__(retry=retry, system_prompt=system_prompt, verbose=verbose, **kwargs) + + model_url = ''.join([api_base.split('v1')[0], 'v1/models']) + resp = requests.get(model_url) + model_id_list = [str(data['id']) for data in resp.json()['data']] + self.model = model if model in model_id_list else model_id_list[0] + logger.info(f'lmdeploy evaluate model: {self.model}') + + self.max_tokens = 20480 + self.temperature = 0.0 + self.custom_prompt = 'rbdash3' + + logger.info(f'using custom prompt {self.custom_prompt}') + logger.info(f'Init temperature: {self.temperature}') + self.system_prompt = R1_SYSTEM_PROMPT + + def set_dump_image(self, dump_image_func): + if self.custom_prompt in self.prompt_map: + self.prompt_map[self.custom_prompt].dump_image_func = dump_image_func + self.dump_image_func = dump_image_func + + def use_custom_prompt(self, dataset): + if self.custom_prompt in self.prompt_map: + return self.prompt_map[self.custom_prompt].use_custom_prompt(dataset) + return False + + def build_prompt(self, line, dataset=None): + if self.custom_prompt in self.prompt_map: + return self.prompt_map[self.custom_prompt].build_prompt(line, dataset) + raise NotImplementedError + + def prepare_itlist(self, inputs): + assert np.all([isinstance(x, dict) for x in inputs]) + has_images = np.sum([x['type'] == 'image' for x in inputs]) + if has_images: + content_list = [] + for msg in inputs: + if msg['type'] == 'text': + content_list.append(dict(type='text', text=msg['value'])) + print('text msg:', msg['value']) + elif msg['type'] == 'image': + from PIL import Image + img = Image.open(msg['value']) + b64 = encode_image_to_base64(img) + extra_args = msg.copy() + extra_args.pop('type') + extra_args.pop('value') + img_struct = dict(url=f'data:image/jpeg;base64,{b64}', **extra_args) + content_list.append(dict(type='image_url', image_url=img_struct)) + else: + assert all([x['type'] == 'text' for x in inputs]) + text = '\n'.join([x['value'] for x in inputs]) + content_list = [dict(type='text', text=text)] + return content_list + + def prepare_inputs(self, inputs): + input_msgs = [] + if self.system_prompt is not None: + input_msgs.append(dict(role='system', content=self.system_prompt)) + + assert isinstance(inputs, list) and isinstance(inputs[0], dict) + assert np.all(['type' in x for x in inputs]) or np.all(['role' in x for x in inputs]), inputs + if 'role' in inputs[0]: + assert inputs[-1]['role'] == 'user', inputs[-1] + for item in inputs: + input_msgs.append(dict(role=item['role'], content=self.prepare_itlist(item['content']))) + else: + input_msgs.append(dict(role='user', content=self.prepare_itlist(inputs))) + return input_msgs + + def generate_inner(self, inputs, **kwargs) -> str: + temperature = kwargs.pop('temperature', self.temperature) + logger.info(f'Generate temperature: {temperature}') + max_tokens = kwargs.pop('max_tokens', self.max_tokens) + dataset = kwargs.pop('dataset', None) + if listinstr(['MMMU_DEV_VAL', 'MathVista'], dataset): + self.system_prompt = R1_SYSTEM_PROMPT + else: + self.system_prompt = None + + input_msgs = self.prepare_inputs(inputs) + + if dataset is not None and listinstr(['BMMR'], dataset): + # BMMR dataset has a very long prompt, so we need to increase max_tokens + max_tokens = 8196 + logger.info('BMMR dataset detected, set max_tokens to 8196') + + headers = {'Content-Type': 'application/json', 'Authorization': f'Bearer {self.key}'} + payload = dict( + model=self.model, + messages=input_msgs, + max_tokens=max_tokens, + n=1, + temperature=temperature, + do_sample=False, + **kwargs) + response = requests.post( + self.api_base, + headers=headers, data=json.dumps(payload), timeout=self.timeout * 1.1) + ret_code = response.status_code + ret_code = 0 if (200 <= int(ret_code) < 300) else ret_code + answer = self.fail_msg + try: + resp_struct = json.loads(response.text) + answer = resp_struct['choices'][0]['message']['content'].strip() + answer = process_prediction_text(answer) + except Exception: + pass + return ret_code, answer, response + + +class RBdashMMChat3_5_38B_Wrapper(BaseAPI): + is_api: bool = True + custom_prompt = 'rbdash3_5' + prompt_map = { + 'rbdash3_5': RBdashMMChat3_5_38B_PromptUtil() + } + + def __init__(self, + model: str = None, + retry: int = 5, + key: str = 'sk-123456', + verbose: bool = True, + temperature: float = 0.0, + timeout: int = 600, + api_base: str = None, + system_prompt: str = None, + max_tokens: int = 20480, + **kwargs): + self.fail_msg = 'Failed to obtain answer via API. ' + self.max_tokens = max_tokens + self.timeout = timeout + + key = os.environ.get('LMDEPLOY_API_KEY', key) + api_base = os.environ.get('LMDEPLOY_API_BASE', api_base) + assert key is not None, 'Please set the environment variable LMDEPLOY_API_KEY.' + assert api_base is not None, 'Please set the environment variable LMDEPLOY_API_BASE.' + self.key = key + self.api_base = api_base + super().__init__(retry=retry, system_prompt=system_prompt, verbose=verbose, **kwargs) + + model_url = ''.join([api_base.split('v1')[0], 'v1/models']) + resp = requests.get(model_url) + model_id_list = [str(data['id']) for data in resp.json()['data']] + self.model = model if model in model_id_list else model_id_list[0] + logger.info(f'lmdeploy evaluate model: {self.model}') + + self.max_tokens = 20480 + self.temperature = 0.0 + self.custom_prompt = 'rbdash3_5' + + logger.info(f'using custom prompt {self.custom_prompt}') + logger.info(f'Init temperature: {self.temperature}') + self.system_prompt = R1_SYSTEM_PROMPT + + def set_dump_image(self, dump_image_func): + if self.custom_prompt in self.prompt_map: + self.prompt_map[self.custom_prompt].dump_image_func = dump_image_func + self.dump_image_func = dump_image_func + + def use_custom_prompt(self, dataset): + if self.custom_prompt in self.prompt_map: + return self.prompt_map[self.custom_prompt].use_custom_prompt(dataset) + return False + + def build_prompt(self, line, dataset=None): + if self.custom_prompt in self.prompt_map: + return self.prompt_map[self.custom_prompt].build_prompt(line, dataset) + raise NotImplementedError + + def prepare_itlist(self, inputs): + assert np.all([isinstance(x, dict) for x in inputs]) + has_images = np.sum([x['type'] == 'image' for x in inputs]) + if has_images: + content_list = [] + for msg in inputs: + if msg['type'] == 'text': + content_list.append(dict(type='text', text=msg['value'])) + print('text msg:', msg['value']) + elif msg['type'] == 'image': + from PIL import Image + + img = Image.open(msg['value']) + b64 = encode_image_to_base64(img) + extra_args = msg.copy() + extra_args.pop('type') + extra_args.pop('value') + img_struct = dict(url=f'data:image/jpeg;base64,{b64}', **extra_args) + content_list.append(dict(type='image_url', image_url=img_struct)) + else: + assert all([x['type'] == 'text' for x in inputs]) + text = '\n'.join([x['value'] for x in inputs]) + content_list = [dict(type='text', text=text)] + return content_list + + def prepare_inputs(self, inputs): + input_msgs = [] + if self.system_prompt is not None: + input_msgs.append(dict(role='system', content=self.system_prompt)) + + assert isinstance(inputs, list) and isinstance(inputs[0], dict) + assert np.all(['type' in x for x in inputs]) or np.all(['role' in x for x in inputs]), inputs + if 'role' in inputs[0]: + assert inputs[-1]['role'] == 'user', inputs[-1] + for item in inputs: + input_msgs.append(dict(role=item['role'], content=self.prepare_itlist(item['content']))) + else: + input_msgs.append(dict(role='user', content=self.prepare_itlist(inputs))) + return input_msgs + + def generate_inner(self, inputs, **kwargs) -> str: + temperature = kwargs.pop('temperature', self.temperature) + logger.info(f'Generate temperature: {temperature}') + max_tokens = kwargs.pop('max_tokens', self.max_tokens) + dataset = kwargs.pop('dataset', None) + if listinstr([ + 'MMMU_DEV_VAL', 'MathVista_MINI', 'MMBench_V11', + 'MMStar', 'DynaMath', 'MathVision', 'MathVerse', + 'LogicVista', 'WeMath'], dataset): + self.system_prompt = R1_SYSTEM_PROMPT + else: + self.system_prompt = None + + input_msgs = self.prepare_inputs(inputs) + + if dataset is not None and listinstr(['BMMR'], dataset): + # BMMR dataset has a very long prompt, so we need to increase max_tokens + max_tokens = 8196 + logger.info('BMMR dataset detected, set max_tokens to 8196') + + headers = {'Content-Type': 'application/json', 'Authorization': f'Bearer {self.key}'} + payload = dict( + model=self.model, + messages=input_msgs, + max_tokens=max_tokens, + n=1, + temperature=temperature, + do_sample=False, + **kwargs) + response = requests.post( + self.api_base, + headers=headers, data=json.dumps(payload), timeout=self.timeout * 1.1) + ret_code = response.status_code + ret_code = 0 if (200 <= int(ret_code) < 300) else ret_code + answer = self.fail_msg + try: + resp_struct = json.loads(response.text) + answer = resp_struct['choices'][0]['message']['content'].strip() + answer = process_prediction_text(answer) + except Exception: + pass + return ret_code, answer, response + + +class RBdashMMChat3_78B_API(RBdashMMChat3_78B_Wrapper): + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def generate(self, message, dataset=None): + return super(RBdashMMChat3_78B_API, self).generate(message, dataset=dataset) + + +class RBdashMMChat3_5_38B_API(RBdashMMChat3_5_38B_Wrapper): + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def generate(self, message, dataset=None): + return super(RBdashMMChat3_5_38B_API, self).generate(message, dataset=dataset) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/rbdashmm_chat3_api.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/rbdashmm_chat3_api.py new file mode 100644 index 00000000..c15efb03 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/rbdashmm_chat3_api.py @@ -0,0 +1,530 @@ +import json +import os +import re + +import numpy as np +import requests + +from vlmeval.api.base import BaseAPI +from vlmeval.smp import encode_image_to_base64, get_logger, listinstr + +logger = get_logger(__name__) + + +def process_prediction_text(text): + if not isinstance(text, str): + return text + + # ====== 处理 \boxed{}(支持嵌套)====== + if "\\boxed{" in text: + start = text.find("\\boxed{") + i = start + len("\\boxed{") + brace_count = 1 + content_chars = [] + + while i < len(text) and brace_count > 0: + ch = text[i] + if ch == "{": + brace_count += 1 + elif ch == "}": + brace_count -= 1 + if brace_count > 0: # 在外层 } 之前才加入字符 + content_chars.append(ch) + i += 1 + + if content_chars: + inner = "".join(content_chars).strip() + if inner: + return inner + + # ====== 删除 标签内容 ====== + redacted_pattern = r'.*?' + if re.search(redacted_pattern, text, re.DOTALL): + result = re.sub(redacted_pattern, '', text, flags=re.DOTALL) + return result.strip() + + # ====== 长文本截断 ====== + if len(text) > 32767: + return text[-30000:] + + # 默认返回 + return text + + +class RBdashMMChat3_PromptUtil: + def dump_image(self, line, dataset): + return self.dump_image_func(line) + + def use_custom_prompt(self, dataset): + assert dataset is not None + return True + + def build_prompt(self, line, dataset=None): + from ..vlm.internvl.utils import (build_mcq_cot_prompt, build_multi_choice_prompt, + build_qa_cot_prompt, build_yesorno_cot_prompt, + reorganize_prompt) + tgt_path = self.dump_image(line, dataset) + max_num = self.get_max_num(dataset) + if dataset is not None and listinstr(['MMBench_V11', 'MMBench_CN_V11'], dataset): + prompt = build_multi_choice_prompt(line, dataset) + elif dataset is not None and listinstr(['MMStar'], dataset): + prompt = build_multi_choice_prompt(line, dataset) + elif dataset is not None and listinstr(['MMMU_DEV_VAL'], dataset): + prompt = build_multi_choice_prompt(line, dataset) + self.cot_prompt = 'Please answer the question and put the final answer within \\boxed{}.' + prompt = build_mcq_cot_prompt(line, prompt, self.cot_prompt) + elif dataset is not None and listinstr(['MathVista_MINI'], dataset): + prompt = line['question'] + self.cot_prompt = 'Please answer the question and put the final answer within \\boxed{}.' + prompt = build_qa_cot_prompt(line, prompt, self.cot_prompt) + elif dataset is not None and listinstr(['OCRBench'], dataset): + prompt = line['question'] + '\nAnswer the question using a single word or phrase. \ + The image is guaranteed to contain the correct answer. Please provide the most likely \ + answer — do not answer "No." Let us think step by step.' + elif dataset is not None and listinstr(['AI2D_TEST'], dataset): + prompt = build_multi_choice_prompt(line, dataset) + elif dataset is not None and listinstr(['HallusionBench'], dataset): + self.cot_prompt = None + prompt = build_yesorno_cot_prompt(line, line['question'], self.cot_prompt) + elif dataset is not None and listinstr(['MMVet'], dataset): + prompt = line['question'] + "\nPlease first think and then answer the question." + + message = [dict(type='text', value=prompt)] + image_num = len(tgt_path) + max_num = max(1, min(max_num, 64 // image_num)) + + message.extend([dict(type='image', value=s, max_dynamic_patch=max_num) for s in tgt_path]) + + # reorganize_prompt + prompt = reorganize_prompt(message, image_num, dataset=dataset) + prompt.replace('', '') + message[0] = dict(type='text', value=prompt) + return message + + def get_max_num(self, dataset): + self.total_max_num = 64 + if dataset is None: + self.max_num = 6 + return None + elif dataset is not None and listinstr(['MMBench_V11'], dataset): + return 6 + elif dataset is not None and listinstr(['MMStar'], dataset): + return 8 + elif dataset is not None and listinstr(['MMMU_DEV_VAL'], dataset): + return 12 + elif dataset is not None and listinstr(['MathVista_MINI'], dataset): + return 12 + elif dataset is not None and listinstr(['OCRBench'], dataset): + return 24 + elif dataset is not None and listinstr(['AI2D_TEST'], dataset): + return 6 + elif dataset is not None and listinstr(['HallusionBench'], dataset): + return 12 + elif dataset is not None and listinstr(['MMVet'], dataset): + return 24 + else: + return 6 + + +class RBdashMMChat3_5_PromptUtil: + def dump_image(self, line, dataset): + return self.dump_image_func(line) + + def use_custom_prompt(self, dataset): + assert dataset is not None + return True + + def build_prompt(self, line, dataset=None): + from ..vlm.internvl.utils import (build_mcq_cot_prompt, build_multi_choice_prompt, + build_qa_cot_prompt, build_yesorno_cot_prompt, + reorganize_prompt) + tgt_path = self.dump_image(line, dataset) + max_num = self.get_max_num(dataset) + if dataset is not None and listinstr(['MMBench_V11'], dataset): + prompt = build_multi_choice_prompt(line, dataset) + self.cot_prompt = 'Please answer the question and put the final answer within \\boxed{}.' + prompt = build_mcq_cot_prompt(line, prompt, self.cot_prompt) + elif dataset is not None and listinstr(['MMStar'], dataset): + prompt = build_multi_choice_prompt(line, dataset) + self.cot_prompt = 'Please answer the question and put the final answer within \\boxed{}.' + prompt = build_mcq_cot_prompt(line, prompt, self.cot_prompt) + elif dataset is not None and listinstr(['MMMU_DEV_VAL'], dataset): + prompt = build_multi_choice_prompt(line, dataset) + self.cot_prompt = 'Please answer the question and put the final answer within \\boxed{}.' + prompt = build_mcq_cot_prompt(line, prompt, self.cot_prompt) + elif dataset is not None and listinstr(['MathVista_MINI'], dataset): + prompt = line['question'] + self.cot_prompt = 'Please answer the question and put the final answer within \\boxed{}.' + prompt = build_qa_cot_prompt(line, prompt, self.cot_prompt) + elif dataset is not None and listinstr(['OCRBench'], dataset): + prompt = line['question'] + '\nAnswer the question using a single word or phrase. \ + The image is guaranteed to contain the correct answer. Please provide the most likely \ + answer — do not answer "No." Let us think step by step.' + elif dataset is not None and listinstr(['AI2D_TEST'], dataset): + prompt = build_multi_choice_prompt(line, dataset) + elif dataset is not None and listinstr(['HallusionBench'], dataset): + self.cot_prompt = None + prompt = build_yesorno_cot_prompt(line, line['question'], self.cot_prompt) + elif dataset is not None and listinstr(['MMVet'], dataset): + prompt = line['question'] + elif listinstr(['MathVista', 'DynaMath', 'MathVision', 'MathVerse', 'LogicVista'], dataset): + cot_prompt = 'Please answer the question and put the final answer within \\boxed{}.' + prompt = build_qa_cot_prompt(line, line['question'], cot_prompt) + elif listinstr(['WeMath'], dataset): + prompt = build_multi_choice_prompt(line, dataset) + cot_prompt = 'Please answer the question and put the final answer within \\boxed{}.' + prompt = build_mcq_cot_prompt(line, prompt, cot_prompt) + + message = [dict(type='text', value=prompt)] + image_num = len(tgt_path) + max_num = max(1, min(max_num, 64 // image_num)) + + message.extend([dict(type='image', value=s, max_dynamic_patch=max_num) for s in tgt_path]) + + # reorganize_prompt + prompt = reorganize_prompt(message, image_num, dataset=dataset) + prompt.replace('', '') + message[0] = dict(type='text', value=prompt) + return message + + def get_max_num(self, dataset): + self.total_max_num = 64 + if dataset is None: + self.max_num = 6 + return None + elif dataset is not None and listinstr(['MMBench_V11'], dataset): + return 6 + elif dataset is not None and listinstr(['MMStar'], dataset): + return 8 + elif dataset is not None and listinstr(['MMMU_DEV_VAL'], dataset): + return 12 + elif dataset is not None and listinstr(['MathVista_MINI'], dataset): + return 12 + elif dataset is not None and listinstr(['OCRBench'], dataset): + return 24 + elif dataset is not None and listinstr(['AI2D_TEST'], dataset): + return 6 + elif dataset is not None and listinstr(['HallusionBench'], dataset): + return 6 + elif dataset is not None and listinstr(['MMVet'], dataset): + return 24 + else: + return 6 + + +R1_SYSTEM_PROMPT = """ +You are an AI assistant that rigorously follows this response protocol: + +1. First, conduct a detailed analysis of the question. Consider different \ +angles, potential solutions, and reason through the problem step-by-step. \ +Enclose this entire thinking process within and tags. + +2. After the thinking section, provide a clear, concise, and direct answer to \ +the user's question. Separate the answer from the think section with a newline. + +Ensure that the thinking process is thorough but remains focused on the \ +query. The final answer should be standalone and not reference the thinking \ +section. +""".strip() + + +class RBdashMMChat3Wrapper(BaseAPI): + + is_api: bool = True + custom_prompt = '' + prompt_map = { + "rbdashmm3chat": RBdashMMChat3_PromptUtil(), + } + + def __init__(self, + model: str = None, + retry: int = 5, + key: str = 'sk-123456', + verbose: bool = True, + temperature: float = 0.0, + timeout: int = 300, + api_base: str = None, + system_prompt: str = None, + max_tokens: int = 16384, + **kwargs): + self.fail_msg = 'Failed to obtain answer via API. ' + self.max_tokens = max_tokens + self.timeout = timeout + + key = os.environ.get('LMDEPLOY_API_KEY', key) + api_base = os.environ.get('LMDEPLOY_API_BASE', api_base) + assert key is not None, 'Please set the environment variable LMDEPLOY_API_KEY.' + assert api_base is not None, 'Please set the environment variable LMDEPLOY_API_BASE.' + self.key = key + self.api_base = api_base + super().__init__(retry=retry, system_prompt=system_prompt, verbose=verbose, **kwargs) + + model_url = ''.join([api_base.split('v1')[0], 'v1/models']) + resp = requests.get(model_url) + model_id_list = [str(data['id']) for data in resp.json()['data']] + self.model = model if model in model_id_list else model_id_list[0] + logger.info(f'lmdeploy evaluate model: {self.model}') + + self.custom_prompt = "rbdashmm3chat" + self.max_tokens = 16384 + self.temperature = 0.0 + if hasattr(self, 'custom_prompt'): + logger.info(f'using custom prompt {self.custom_prompt}') + + self.temperature = temperature + logger.info(f'Init temperature: {self.temperature}') + + self.system_prompt = None + + def set_dump_image(self, dump_image_func): + if self.custom_prompt in self.prompt_map: + self.prompt_map[self.custom_prompt].dump_image_func = dump_image_func + self.dump_image_func = dump_image_func + + def use_custom_prompt(self, dataset): + assert dataset is not None + return True + + def build_prompt(self, line, dataset=None): + if self.custom_prompt in self.prompt_map: + return self.prompt_map[self.custom_prompt].build_prompt(line, dataset) + raise NotImplementedError + + def prepare_itlist(self, inputs): + assert np.all([isinstance(x, dict) for x in inputs]) + has_images = np.sum([x['type'] == 'image' for x in inputs]) + if has_images: + content_list = [] + for msg in inputs: + if msg['type'] == 'text': + content_list.append(dict(type='text', text=msg['value'])) + elif msg['type'] == 'image': + from PIL import Image + img = Image.open(msg['value']) + b64 = encode_image_to_base64(img) + extra_args = msg.copy() + extra_args.pop('type') + extra_args.pop('value') + img_struct = dict(url=f'data:image/jpeg;base64,{b64}', **extra_args) + content_list.append(dict(type='image_url', image_url=img_struct)) + else: + assert all([x['type'] == 'text' for x in inputs]) + text = '\n'.join([x['value'] for x in inputs]) + content_list = [dict(type='text', text=text)] + return content_list + + def prepare_inputs(self, inputs, dataset): + input_msgs = [] + + if listinstr(['MMMU_DEV_VAL', 'MathVista_MINI'], dataset): + self.system_prompt = R1_SYSTEM_PROMPT + else: + self.system_prompt = None + + if self.system_prompt is not None: + input_msgs.append(dict(role='system', content=self.system_prompt)) + else: + input_msgs.append(dict(role='system', content="你是书生·万象,英文名是InternVL,是由上海人工智能实验室、清华大学及多家合作单位联合开发的多模态大语言模型。")) + + assert isinstance(inputs, list) and isinstance(inputs[0], dict) + assert np.all(['type' in x for x in inputs]) or np.all(['role' in x for x in inputs]), inputs + if 'role' in inputs[0]: + assert inputs[-1]['role'] == 'user', inputs[-1] + for item in inputs: + input_msgs.append(dict(role=item['role'], content=self.prepare_itlist(item['content']))) + else: + input_msgs.append(dict(role='user', content=self.prepare_itlist(inputs))) + return input_msgs + + def generate_inner(self, inputs, **kwargs) -> str: + temperature = kwargs.pop('temperature', self.temperature) + logger.info(f'Generate temperature: {temperature}') + max_tokens = kwargs.pop('max_tokens', self.max_tokens) + dataset = kwargs.pop('dataset', None) + + input_msgs = self.prepare_inputs(inputs, dataset) + + headers = {'Content-Type': 'application/json', 'Authorization': f'Bearer {self.key}'} + payload = dict( + model=self.model, + messages=input_msgs, + max_tokens=max_tokens, + n=1, + temperature=temperature, + session_len=40960, + do_sample=False, + **kwargs) + response = requests.post( + self.api_base, + headers=headers, data=json.dumps(payload), timeout=self.timeout * 1.1) + ret_code = response.status_code + ret_code = 0 if (200 <= int(ret_code) < 300) else ret_code + answer = self.fail_msg + try: + resp_struct = json.loads(response.text) + answer = resp_struct['choices'][0]['message']['content'].strip() + answer = process_prediction_text(answer) + except Exception: + pass + return ret_code, answer, response + + +class RBdashMMChat3_API(RBdashMMChat3Wrapper): + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def generate(self, message, dataset=None): + return super(RBdashMMChat3_API, self).generate(message, dataset=dataset) + + +class RBdashMMChat3_5_Wrapper(BaseAPI): + + is_api: bool = True + custom_prompt = '' + prompt_map = { + "rbdashmm3_5chat": RBdashMMChat3_5_PromptUtil(), + } + + def __init__(self, + model: str = None, + retry: int = 5, + key: str = 'sk-123456', + verbose: bool = True, + temperature: float = 0.0, + timeout: int = 300, + api_base: str = None, + system_prompt: str = None, + max_tokens: int = 20480, + **kwargs): + self.fail_msg = 'Failed to obtain answer via API. ' + self.max_tokens = max_tokens + self.timeout = timeout + + key = os.environ.get('LMDEPLOY_API_KEY', key) + api_base = os.environ.get('LMDEPLOY_API_BASE', api_base) + assert key is not None, 'Please set the environment variable LMDEPLOY_API_KEY.' + assert api_base is not None, 'Please set the environment variable LMDEPLOY_API_BASE.' + self.key = key + self.api_base = api_base + super().__init__(retry=retry, system_prompt=system_prompt, verbose=verbose, **kwargs) + + model_url = ''.join([api_base.split('v1')[0], 'v1/models']) + resp = requests.get(model_url) + model_id_list = [str(data['id']) for data in resp.json()['data']] + self.model = model if model in model_id_list else model_id_list[0] + logger.info(f'lmdeploy evaluate model: {self.model}') + + self.custom_prompt = "rbdashmm3_5chat" + self.max_tokens = 20480 + self.temperature = 0.0 + if hasattr(self, 'custom_prompt'): + logger.info(f'using custom prompt {self.custom_prompt}') + + self.temperature = temperature + logger.info(f'Init temperature: {self.temperature}') + + self.system_prompt = None + + def set_dump_image(self, dump_image_func): + if self.custom_prompt in self.prompt_map: + self.prompt_map[self.custom_prompt].dump_image_func = dump_image_func + self.dump_image_func = dump_image_func + + def use_custom_prompt(self, dataset): + assert dataset is not None + return True + + def build_prompt(self, line, dataset=None): + if self.custom_prompt in self.prompt_map: + return self.prompt_map[self.custom_prompt].build_prompt(line, dataset) + raise NotImplementedError + + def prepare_itlist(self, inputs): + assert np.all([isinstance(x, dict) for x in inputs]) + has_images = np.sum([x['type'] == 'image' for x in inputs]) + if has_images: + content_list = [] + for msg in inputs: + if msg['type'] == 'text': + content_list.append(dict(type='text', text=msg['value'])) + elif msg['type'] == 'image': + from PIL import Image + + img = Image.open(msg['value']) + b64 = encode_image_to_base64(img) + extra_args = msg.copy() + extra_args.pop('type') + extra_args.pop('value') + img_struct = dict(url=f'data:image/jpeg;base64,{b64}', **extra_args) + content_list.append(dict(type='image_url', image_url=img_struct)) + else: + assert all([x['type'] == 'text' for x in inputs]) + text = '\n'.join([x['value'] for x in inputs]) + content_list = [dict(type='text', text=text)] + return content_list + + def prepare_inputs(self, inputs, dataset): + input_msgs = [] + + if listinstr([ + 'MMMU_DEV_VAL', 'MathVista_MINI', 'MMBench_V11', + 'MMStar', 'DynaMath', 'MathVision', 'MathVerse', + 'LogicVista', 'WeMath'], dataset): + self.system_prompt = R1_SYSTEM_PROMPT + else: + self.system_prompt = None + + if self.system_prompt is not None: + input_msgs.append(dict(role='system', content=self.system_prompt)) + + assert isinstance(inputs, list) and isinstance(inputs[0], dict) + assert np.all(['type' in x for x in inputs]) or np.all(['role' in x for x in inputs]), inputs + if 'role' in inputs[0]: + assert inputs[-1]['role'] == 'user', inputs[-1] + for item in inputs: + input_msgs.append(dict(role=item['role'], content=self.prepare_itlist(item['content']))) + else: + input_msgs.append(dict(role='user', content=self.prepare_itlist(inputs))) + return input_msgs + + def generate_inner(self, inputs, **kwargs) -> str: + temperature = kwargs.pop('temperature', self.temperature) + logger.info(f'Generate temperature: {temperature}') + max_tokens = kwargs.pop('max_tokens', self.max_tokens) + dataset = kwargs.pop('dataset', None) + + input_msgs = self.prepare_inputs(inputs, dataset) + + headers = {'Content-Type': 'application/json', 'Authorization': f'Bearer {self.key}'} + payload = dict( + model=self.model, + messages=input_msgs, + max_tokens=max_tokens, + n=1, + temperature=temperature, + session_len=40960, + do_sample=False, + **kwargs) + response = requests.post( + self.api_base, + headers=headers, data=json.dumps(payload), timeout=self.timeout * 1.1) + ret_code = response.status_code + ret_code = 0 if (200 <= int(ret_code) < 300) else ret_code + answer = self.fail_msg + try: + resp_struct = json.loads(response.text) + answer = resp_struct['choices'][0]['message']['content'].strip() + answer = process_prediction_text(answer) + except Exception: + pass + return ret_code, answer, response + + +class RBdashChat3_5_API(RBdashMMChat3_5_Wrapper): + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def generate(self, message, dataset=None): + return super(RBdashChat3_5_API, self).generate(message, dataset=dataset) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/reka.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/reka.py new file mode 100644 index 00000000..ff08e64c --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/reka.py @@ -0,0 +1,59 @@ +import os + +from vlmeval.api.base import BaseAPI +from vlmeval.smp import encode_image_file_to_base64 + + +class Reka_Wrapper(BaseAPI): + + is_api: bool = True + INTERLEAVE: bool = False + + def __init__(self, + model: str = 'reka-flash-20240226', + key: str = None, + retry: int = 10, + system_prompt: str = None, + verbose: bool = True, + temperature: float = 0, + max_tokens: int = 1024, + **kwargs): + + try: + import reka # noqa: F401 + except ImportError: + raise ImportError('Please install reka by running "pip install reka-api"') + + self.model = model + default_kwargs = dict(temperature=temperature, request_output_len=max_tokens) + default_kwargs.update(kwargs) + self.kwargs = default_kwargs + if key is not None: + self.key = key + else: + self.key = os.environ.get('REKA_API_KEY', '') + super().__init__(retry=retry, verbose=verbose, system_prompt=system_prompt, **kwargs) + + def generate_inner(self, inputs, **kwargs) -> str: + import reka + reka.API_KEY = self.key + dataset = kwargs.pop('dataset', None) + prompt, image_path = self.message_to_promptimg(inputs, dataset=dataset) + image_b64 = encode_image_file_to_base64(image_path) + + response = reka.chat( + model_name=self.model, + human=prompt, + media_url=f'data:image/jpeg;base64,{image_b64}', + **self.kwargs) + + try: + return 0, response['text'], response + except Exception as err: + return -1, self.fail_msg + str(err), response + + +class Reka(Reka_Wrapper): + + def generate(self, message, dataset=None): + return super(Reka_Wrapper, self).generate(message) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/sensechat_vision.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/sensechat_vision.py new file mode 100644 index 00000000..fd72da3e --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/sensechat_vision.py @@ -0,0 +1,411 @@ +import os +import string +import time +from typing import Optional + +import pandas as pd +import requests + +from vlmeval.api.base import BaseAPI +from vlmeval.dataset import DATASET_TYPE, img_root_map +from vlmeval.smp import (LMUDataRoot, cn_string, decode_base64_to_image_file, get_logger, + listinstr, osp, read_ok, toliststr) + +logger = get_logger(__name__) + + +class SenseChatVisionWrapper(BaseAPI): + is_api: bool = True + + def __init__( + self, + base_url: str = "https://api.sensenova.cn/v1/llm/chat-completions", + api_key: str = None, + model: str = "SenseNova-V6-5-Pro", + retry: int = 5, + wait: int = 5, + verbose: bool = True, + system_prompt: str = None, + max_tokens: int = 16384, + **kwargs, + ): + self.base_url = base_url + self.model = model + self.fail_msg = "Failed to obtain answer via API. " + self.api_key = os.getenv("SENSENOVA_API_KEY", api_key) + assert self.api_key is not None, ( + "Please set the `SENSENOVA_API_KEY` environment variable or pass `api_key` in the config.json." + ) + self.max_new_tokens = max_tokens + self.thinking = False + super().__init__( + wait=wait, + retry=retry, + system_prompt=system_prompt, + verbose=verbose, + **kwargs, + ) + + def dump_image(self, line, dataset): + """Dump the image(s) of the input line to the corresponding dataset folder. + + Args: + line (line of pd.DataFrame): The raw input line. + dataset (str): The name of the dataset. + + Returns: + str | list[str]: The paths of the dumped images. + """ + ROOT = LMUDataRoot() + assert isinstance(dataset, str) + img_root = osp.join(ROOT, "images", img_root_map(dataset)) + os.makedirs(img_root, exist_ok=True) + if "image" in line: + if isinstance(line["image"], list): + tgt_path = [] + assert "image_path" in line + for img, im_name in zip(line["image"], line["image_path"]): + path = osp.join(img_root, im_name) + if not read_ok(path): + decode_base64_to_image_file(img, path) + tgt_path.append(path) + else: + tgt_path = osp.join(img_root, f"{line['index']}.jpg") + if not read_ok(tgt_path): + decode_base64_to_image_file(line["image"], tgt_path) + tgt_path = [tgt_path] + else: + assert "image_path" in line + tgt_path = toliststr(line["image_path"]) + + return tgt_path + + def image_to_base64(self, image_path): + import base64 + + with open(image_path, "rb") as image_file: + encoded_string = base64.b64encode(image_file.read()) + return encoded_string.decode("utf-8") + + def use_custom_prompt(self, *args, **kwargs): + """Check if the prompt is customized.""" + return True + + def build_multi_choice_prompt(self, line, dataset=None): + question = line["question"] + hint = line["hint"] if ("hint" in line and not pd.isna(line["hint"])) else None + if hint is not None: + question = hint + "\n" + question + + options = { + cand: line[cand] + for cand in string.ascii_uppercase + if cand in line and not pd.isna(line[cand]) + } + for key, item in options.items(): + question += f"\n{key}. {item}" + prompt = question + + if len(options): + prompt += ( + "\n请直接回答选项字母。" + if cn_string(prompt) + else "\nAnswer with the option's letter from the given choices directly." + ) + else: + prompt += ( + "\n请直接回答问题。" + if cn_string(prompt) + else "\nAnswer the question directly." + ) + + return prompt + + def build_mcq_cot_prompt(self, line, prompt): + question = line["question"] + options = { + cand: line[cand] + for cand in string.ascii_uppercase + if cand in line and not pd.isna(line[cand]) + } + for key, item in options.items(): + question += f'\n{key}. {item}' + prompt = { + 'multiple-choice': "You are an expert in {}. Please solve the university-level {} examination question, which includes interleaved images and text. Answer the preceding multiple choice question. The last line of your response should follow this format: 'Answer: \\boxed LETTER', where LETTER is one of the options. If you are uncertain or the problem is too complex, make a reasoned guess based on the information provided. Avoid repeating steps indefinitely—provide your best guess even if unsure. Think step by step logically, considering all relevant information before answering.", # noqa: E501 + 'open': 'You are an expert in {}. Please solve the university-level {} examination question, which includes interleaved images and text. Your output should be divided into two parts: First, reason about the correct answer. Then write the answer in the following format where X is only the answer and nothing else: "ANSWER: X"' # noqa: E501 + } + subject = '_'.join(line['id'].split('_')[1:-1]) + prompt = prompt[line['question_type']].format(subject, subject) + '\n' + question + return prompt + + def build_prompt(self, line, dataset=None): + assert self.use_custom_prompt(dataset) + assert dataset is None or isinstance(dataset, str) + + tgt_path = self.dump_image(line, dataset) + + if dataset is not None and listinstr(["MME"], dataset): + question = line["question"] + prompt = question + " Answer the question using a single word or phrase." + elif dataset is not None and listinstr(["HallusionBench"], dataset): + question = line["question"] + prompt = ( + question + + " Please answer yes or no. Answer the question using a single word or phrase." + ) + elif dataset is not None and DATASET_TYPE(dataset) == "MCQ": + prompt = self.build_multi_choice_prompt(line, dataset) + if "MMMU" in dataset: + prompt = self.build_mcq_cot_prompt(line, prompt) + self.thinking = True + elif dataset is not None and DATASET_TYPE(dataset) == "VQA": + if "MathVista" in dataset: + prompt = line["question"] + self.thinking = True + elif listinstr(["LLaVABench"], dataset): + question = line["question"] + prompt = question + "\nAnswer this question in detail." + elif listinstr(["MMVet"], dataset): + prompt = line["question"] + else: + question = line["question"] + prompt = ( + question + + "\nPlease reason step by step, and put your final answer within \\boxed{}." + ) + else: + prompt = line["question"] + + message = [dict(type="text", value=prompt)] + message.extend([dict(type="image", value=s) for s in tgt_path]) + + return message + + def message_to_promptimg(self, message, dataset=None): + if dataset is None or listinstr(["MMMU", "BLINK"], dataset): + prompt = "\n".join([x["value"] for x in message if x["type"] == "text"]) + image = [[x["value"] for x in message if x["type"] == "image"][0]] + else: + prompt = "\n".join([x["value"] for x in message if x["type"] == "text"]) + image = [x["value"] for x in message if x["type"] == "image"] + return prompt, image + + def set_max_num(self, dataset: Optional[str] = None) -> None: + """Set the max_num based on the dataset.""" + if dataset is not None and listinstr( + [ + "ChartQA_TEST", + "MMMU_DEV_VAL", + "MMMU_TEST", + "MME-RealWorld", + "VCR_EN", + "VCR_ZH", + "OCRVQA", + ], + dataset, + ): + self.max_num = 12 + elif dataset is not None and listinstr( + ["DocVQA_VAL", "DocVQA_TEST", "DUDE", "MMLongBench_DOC", "SLIDEVQA"], + dataset, + ): + self.max_num = 18 + elif dataset is not None and listinstr( + ["InfoVQA_VAL", "InfoVQA_TEST", "OCRBench", "HRBench4K", "HRBench8K"], + dataset, + ): + self.max_num = 24 + else: + self.max_num = 6 + + def generate_inner(self, inputs, **kwargs) -> str: + assert isinstance(inputs, str) or isinstance(inputs, list) + inputs = [inputs] if isinstance(inputs, str) else inputs + dataset = kwargs.get("dataset", None) + + self.set_max_num(dataset=dataset) + + prompt, image = self.message_to_promptimg(message=inputs, dataset=dataset) + content = [ + { + "image_base64": self.image_to_base64(item), + "type": "image_base64", + } + for item in image + ] + + content.append( + { + "text": prompt, + "type": "text", + } + ) + + message = [{"content": content, "role": "user"}] + data = { + "messages": message, + "max_new_tokens": self.max_new_tokens, + "model": self.model, + "stream": False, + "image_split_count": self.max_num, + "thinking": { + "enabled": self.thinking, + } + } + + headers = { + "Content-type": "application/json", + "Authorization": self.api_key, + } + + response = requests.post( + self.base_url, + headers=headers, + json=data, + ) + request_id = response.headers.get("x-request-id", "") + logger.info(f"Request-id: {request_id}") + + time.sleep(1) + try: + assert response.status_code == 200 + response = response.json()["data"]["choices"][0]["message"].strip() + if self.verbose: + logger.info(f"inputs: {inputs}\nanswer: {response}") + return 0, response, "Succeeded! " + except Exception as err: + if self.verbose: + logger.error( + "---------------------------ERROR---------------------------" + ) + logger.error(response.json()) + logger.error(err) + logger.error( + "---------------------------request_id---------------------------" + + request_id + ) + logger.error( + "api error" + + response.json()["error"]["message"] + + str( + [ + input["value"] if input["type"] == "image" else None + for input in inputs + ] + ) + ) + logger.error(f"The input messages are {inputs}.") + return -1, response.json()["error"]["message"], "" + + +class SenseChatVisionAPI(SenseChatVisionWrapper): + def generate(self, message, dataset=None): + return super(SenseChatVisionAPI, self).generate(message, dataset=dataset) + + +class SenseChatVisionV2API(BaseAPI): + + is_api: bool = True + + def __init__(self, + model: str = 'SenseNova-V6-5-Pro-20251215', + retry: int = 5, + key: str = None, + verbose: bool = False, + system_prompt: str = None, + temperature: float = 0, + timeout: int = 300, + api_base: str = "https://api.sensenova.cn/compatible-mode/v2/chat/completions", + max_completion_tokens: int = 4096, + img_size: int = -1, + **kwargs): + + self.model = model + self.fail_msg = 'Failed to obtain answer via API. ' + self.max_completion_tokens = max_completion_tokens + self.temperature = temperature + self.api_base = api_base + self.key = key + assert img_size > 0 or img_size == -1 + self.img_size = img_size + self.timeout = timeout + super().__init__(retry=retry, system_prompt=system_prompt, verbose=verbose, **kwargs) + + logger.info(f'Using API Base: {self.api_base}; API Key: {self.key}') + + def generate(self, message, dataset=None): + return super(SenseChatVisionV2API, self).generate(message) + + def prepare_itlist(self, inputs): + import numpy as np + + from vlmeval.smp import encode_image_to_base64 + + assert np.all([isinstance(x, dict) for x in inputs]) + has_images = np.sum([x['type'] == 'image' for x in inputs]) + image_num = len([x['type'] == 'image' for x in inputs]) + if has_images: + content_list = [] + for msg in inputs: + if msg['type'] == 'text': + content_list.append(dict(type='text', text=msg['value'])) + elif msg['type'] == 'image': + from PIL import Image + img = Image.open(msg['value']) + b64 = encode_image_to_base64(img, target_size=int(self.img_size / (image_num ** 0.5))) + img_struct = dict(url=f'data:image/jpeg;base64,{b64}') + content_list.append(dict(type='image_url', image_url=img_struct)) + else: + assert all([x['type'] == 'text' for x in inputs]) + text = '\n'.join([x['value'] for x in inputs]) + content_list = [dict(type='text', text=text)] + return content_list + + def prepare_inputs(self, inputs): + input_msgs = [] + if self.system_prompt is not None: + input_msgs.append(dict(role='system', content=self.system_prompt)) + assert isinstance(inputs, list) and isinstance(inputs[0], dict) + assert all(['type' in x for x in inputs]) or all(['role' in x for x in inputs]), inputs + if 'role' in inputs[0]: + assert inputs[-1]['role'] == 'user', inputs[-1] + for item in inputs: + input_msgs.append(dict(role=item['role'], content=self.prepare_itlist(item['content']))) + else: + input_msgs.append(dict(role='user', content=self.prepare_itlist(inputs))) + return input_msgs + + def generate_inner(self, inputs, **kwargs) -> str: + import json + input_msgs = self.prepare_inputs(inputs) + + headers = {'Content-Type': 'application/json', 'Authorization': f'Bearer {self.key}'} + + payload = dict(model=self.model, messages=input_msgs, stream=False, **kwargs) + + proxies = {} + if os.getenv('http_proxy'): + proxies['http'] = os.getenv('http_proxy') + if os.getenv('https_proxy'): + proxies['https'] = os.getenv('https_proxy') + proxies = proxies or None + + response = requests.post( + self.api_base, + headers=headers, + data=json.dumps(payload), + proxies=proxies, + timeout=self.timeout * 1.1, + ) + ret_code = response.status_code + ret_code = 0 if (200 <= int(ret_code) < 300) else ret_code + answer = self.fail_msg + try: + resp_struct = json.loads(response.text) + answer = resp_struct['choices'][0]['message']['content'].strip() + except Exception as err: + if self.verbose: + logger.error(f'{type(err)}: {err}') + logger.error(response.text if hasattr(response, 'text') else response) + + return ret_code, answer, response diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/siliconflow.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/siliconflow.py new file mode 100644 index 00000000..0114a29b --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/siliconflow.py @@ -0,0 +1,288 @@ +import base64 +import io +import json +import math +import os +import os.path as osp + +import requests +from PIL import Image + +from vlmeval.api.base import BaseAPI +from vlmeval.dataset import img_root_map +from vlmeval.smp import LMUDataRoot, decode_base64_to_image_file, get_logger, read_ok, toliststr + +logger = get_logger(__name__) + +API_BASE = "https://api.siliconflow.cn/v1/chat/completions" + + +def resize_image(image: Image.Image, max_height: int, max_width: int) -> Image.Image: + width, height = image.size + if min(width, height) < 50: + scale = 50 / min(width, height) + image = image.resize((int(width * scale), int(height * scale))) + current_pixels = width * height + + if current_pixels <= max_height * max_width: + return image + + scale = math.sqrt(max_height * max_width / current_pixels) + new_width = int(width * scale) + new_height = int(height * scale) + + return image.resize((new_width, new_height), Image.Resampling.LANCZOS) + + +def encode_image(path: str, max_height: int = 1024, max_width: int = 1024) -> str: + image = Image.open(path).convert("RGB") + image = resize_image(image, max_height, max_width) + width, height = image.size + if min(height, width) < 50: + scale = 50 / min(width, height) + image = image.resize((int(width * scale), int(height * scale))) + buffered = io.BytesIO() + image.save(buffered, format="PNG") + img_bytes = buffered.getvalue() + img_base64 = base64.b64encode(img_bytes).decode("utf-8") + return img_base64 + + +class SiliconFlowAPI(BaseAPI): + + is_api: bool = True + + def __init__( + self, + model: str = "deepseek-ai/DeepSeek-V2.5", + retry: int = 5, + key: str = None, + api_base: str = API_BASE, + verbose: bool = True, + system_prompt: str = None, + timeout: int = 60, + reasoning: bool = False, # If set, will return results in the format of {'content': '...', 'reasoning': '...'} + **kwargs, + ): + + self.model = model + self.api_base = api_base + self.reasoning = reasoning + self.timeout = timeout + + default_kwargs = { + "stream": False, + "temperature": 0, + "n": 1, + "max_tokens": 1280, + } + for k, v in default_kwargs.items(): + if k not in kwargs: + kwargs[k] = default_kwargs[k] + if key is not None: + self.key = key + else: + self.key = os.environ.get("SiliconFlow_API_KEY", "") + headers = {"Authorization": "Bearer {}", "Content-Type": "application/json"} + headers["Authorization"] = headers["Authorization"].format(self.key) + self.headers = headers + super().__init__( + retry=retry, + system_prompt=system_prompt, + verbose=verbose, + **kwargs, + ) + + @staticmethod + def build_msgs(msgs_raw): + messages = [] + message = {"role": "user", "content": []} + image_b64 = None + for msg in msgs_raw: + if msg["type"] == "image" and not image_b64: + image_b64 = encode_image(msg["value"]) + message["content"].append({ + "image_url": { + "url": f"data:image/png;base64,{image_b64}" + }, + "type": "image_url" + }) + elif msg["type"] == "text": + message["content"].append({"text": msg["value"], "type": "text"}) + + messages.append(message) + return messages + + def generate_inner(self, inputs, **kwargs) -> str: + default_kwargs = self.default_kwargs + default_kwargs.update(kwargs) + + payload = dict( + model=self.model, + messages=self.build_msgs(msgs_raw=inputs), + **default_kwargs, + ) + + response = requests.post( + self.api_base, headers=self.headers, data=json.dumps(payload), timeout=self.timeout * 1.1 + ) + ret_code = response.status_code + ret_code = 0 if (200 <= int(ret_code) < 300) else ret_code + + answer = self.fail_msg + try: + resp_struct = json.loads(response.text) + msg = resp_struct["choices"][0]["message"] + if self.reasoning and 'reasoning_content' in msg: + answer = {'content': msg['content'], 'reasoning': msg['reasoning_content']} + else: + answer = resp_struct["choices"][0]["message"]["content"].strip() + except Exception: + pass + return ret_code, answer, response + + +class TeleMMAPI(SiliconFlowAPI): + + is_api: bool = True + + def __init__( + self, + model: str = "TeleAI/TeleMM", + key: str = None, + max_height: int = 1280, + max_width: int = 784, + **kwargs, + ): + super().__init__(model=model, key=key, **kwargs) + self.max_height = max_height + self.max_width = max_width + + def dump_image(self, line, dataset): + """Dump the image(s) of the input line to the corresponding dataset folder. + + Args: + line (line of pd.DataFrame): The raw input line. + dataset (str): The name of the dataset. + + Returns: + str | list[str]: The paths of the dumped images. + """ + ROOT = LMUDataRoot() + assert isinstance(dataset, str) + # img_root = osp.join(ROOT, 'images', img_root_map[dataset] if dataset in img_root_map else dataset) + img_root = osp.join(ROOT, "images", img_root_map(dataset)) + os.makedirs(img_root, exist_ok=True) + if "image" in line: + if isinstance(line["image"], list): + tgt_path = [] + assert "image_path" in line + for img, im_name in zip(line["image"], line["image_path"]): + path = osp.join(img_root, im_name) + if not read_ok(path): + decode_base64_to_image_file(img, path) + tgt_path.append(path) + else: + tgt_path = osp.join(img_root, f"{line['index']}.jpg") + if not read_ok(tgt_path): + decode_base64_to_image_file(line["image"], tgt_path) + tgt_path = [tgt_path] + else: + assert "image_path" in line + tgt_path = toliststr(line["image_path"]) + return tgt_path + + def _prepare_content( + self, inputs: list[dict[str, str]], dataset: str = None + ) -> list[dict[str, str]]: + """ + inputs list[dict[str, str]], each dict has keys: ['type', 'value'] + """ + content = [] + has_image = False + for s in inputs: + if s["type"] == "image": + if not has_image: + item = { + "type": "image_url", + "image_url": { + "url": encode_image( + s["value"], + max_height=self.max_height, + max_width=self.max_width, + ) + }, + } + has_image = True + else: + continue + elif s["type"] == "text": + prompt = s["value"] + if len(prompt) == 0: + continue + if dataset == "HallusionBench": + prompt += " Please answer yes or no directly, without any unnecessary explanation." + elif dataset == "OCRBench": + prompt = ( + prompt + "\nExtract the text from the image intactly and " + + "answer the question concisely and clearly if possible." + ) + + elif ( + dataset == "AI2D_TEST" + or dataset == "MMStar" + or dataset == "MMBench_TEST_EN_V11" + or dataset == "MMVet" + ): + prompt = prompt.replace( + "Please select the correct answer from the options above. \n", + "Please select the correct option from the above choices based on the " + + "input image and question. The final output should only be one option, such as 'A'", + ) + elif dataset == "MMBench_TEST_CN_V11": + prompt = prompt.replace( + "Please select the correct answer from the options above. \n", + "请根据输入图像和问题从上述选项中选择正确选项,最终的输出只有一个选项,例如'A'", + ) + item = {"type": "text", "text": prompt} + else: + raise ValueError(f"Invalid message type: {s['type']}, {s}") + content.append(item) + + return content + + def generate_inner(self, inputs, **kwargs) -> str: + default_kwargs = self.default_kwargs + default_kwargs.update(kwargs) + + messages = [] + messages.append( + { + "role": "user", + "content": self._prepare_content( + inputs, dataset=kwargs.get("dataset", None) + ), + } + ) + + payload = dict(model=self.model, messages=messages, **default_kwargs) + + response = requests.post( + self.api_base, headers=self.headers, data=json.dumps(payload) + ) + ret_code = response.status_code + ret_code = 0 if (200 <= int(ret_code) < 300) else ret_code + + answer = self.fail_msg + try: + resp_struct = json.loads(response.text) + answer = resp_struct["choices"][0]["message"]["content"].strip() + return ret_code, answer, response + except Exception as err: + import traceback + + traceback.print_exc() + if self.verbose: + logger.error(f"{type(err)}: {err}") + logger.error(f"The input messages are {inputs}.") + return -1, "", "" diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/stepai.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/stepai.py new file mode 100644 index 00000000..cd9f9a9e --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/stepai.py @@ -0,0 +1,94 @@ +import json +import os + +import requests + +from vlmeval.api.base import BaseAPI +from vlmeval.smp import encode_image_file_to_base64, get_logger + +url = 'https://api.stepfun.com/v1/chat/completions' +headers = { + 'Content-Type': 'application/json', + 'Authorization': 'Bearer {}', +} + + +logger = get_logger(__name__) + + +class StepAPI_INT(BaseAPI): + + is_api: bool = True + + def __init__(self, + model: str = 'step-1v-8k', + retry: int = 10, + key: str = None, + temperature: float = 0, + max_tokens: int = 300, + verbose: bool = True, + system_prompt: str = None, + **kwargs): + self.model = model + self.fail_msg = 'Fail to obtain answer via API.' + self.headers = headers + self.temperature = temperature + self.max_tokens = max_tokens + self.system_prompt = system_prompt + if key is not None: + self.key = key + else: + self.key = os.environ.get('STEPAI_API_KEY', '') + headers['Authorization'] = headers['Authorization'].format(self.key) + + super().__init__(retry=retry, verbose=verbose, system_prompt=system_prompt, **kwargs) + + @staticmethod + def build_msgs(msgs_raw): + messages = [] + message = {'role': 'user', 'content': []} + + for msg in msgs_raw: + if msg['type'] == 'image': + image_b64 = encode_image_file_to_base64(msg['value']) + message['content'].append({ + 'image_url': {'url': 'data:image/webp;base64,%s' % (image_b64)}, + 'type': 'image_url' + }) + elif msg['type'] == 'text': + message['content'].append({ + 'text': msg['value'], + 'type': 'text' + }) + + messages.append(message) + return messages + + def generate_inner(self, inputs, **kwargs) -> str: + print(inputs, '\n') + payload = dict( + model=self.model, + max_tokens=self.max_tokens, + temperature=self.temperature, + messages=self.build_msgs(msgs_raw=inputs), + **kwargs) + response = requests.post(url, headers=headers, data=json.dumps(payload)) + ret_code = response.status_code + ret_code = 0 if (200 <= int(ret_code) < 300) else ret_code + + answer = self.fail_msg + try: + resp_struct = json.loads(response.text) + answer = resp_struct['choices'][0]['message']['content'].strip() + except Exception as err: + if self.verbose: + logger.error(f'{type(err)}: {err}') + logger.error(response.text if hasattr(response, 'text') else response) + + return ret_code, answer, response + + +class Step1V_INT(StepAPI_INT): + + def generate(self, message, dataset=None): + return super(StepAPI_INT, self).generate(message) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/taichu.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/taichu.py new file mode 100644 index 00000000..7a73b68a --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/taichu.py @@ -0,0 +1,359 @@ +import base64 +import copy +import json +import os +import re +import string +from io import BytesIO + +import pandas as pd +import requests +from PIL import Image + +from vlmeval.api.base import BaseAPI +from vlmeval.dataset import DATASET_TYPE +from vlmeval.smp import get_logger, listinstr + +logger = get_logger(__name__) + + +class ChatResponse(dict): + def __getattr__(self, name): + value = self.get(name) + if isinstance(value, dict): + return ChatResponse(value) # 如果值是字典,递归包装成 DotDict + elif isinstance(value, list): + return [ChatResponse(v) if isinstance(v, dict) else v for v in value] # 如果值是列表,处理其中的字典 + return value + + def __setattr__(self, name, value): + self[name] = value + + def __delattr__(self, name): + del self[name] + + +class TaichuVLWrapper(BaseAPI): + is_api: bool = True + + def __init__(self, + model: str = 'Taichu-VL-2B', + retry: int = 5, + verbose: bool = True, + temperature: float = 0.0, + system_prompt: str = None, + max_tokens: int = 4096, + key: str = None, + url: str = None, + **kwargs): + + self.model = model + self.kwargs = kwargs + self.max_tokens = max_tokens + + self.system_prompt = '[sys]You are a helpful assistant.[/sys]' + self.hint_prompt = '||' + self.mcq_prompt = '||' + + self.datasets_use_system = ['MMVet'] + self.datasets_use_multichoice = [ + 'MathVista', 'MathVision'] + + openai_key = os.environ.get('OPENAI_API_KEY', None) + use_openai = os.environ.get('USE_OPENAI_EVAL', True) + self.use_openai_evaluate = (isinstance(openai_key, str) and openai_key.startswith('sk-') and use_openai) + + self.api_key = os.environ.get('TAICHU_API_KEY', key) + self.api_url = url + + assert self.api_key is not None, 'Please set the API Key' + + super().__init__(retry=retry, system_prompt=self.system_prompt, verbose=verbose, **kwargs) + + def use_custom_prompt(self, dataset): + if listinstr(['MCQ', 'VQA'], DATASET_TYPE(dataset)): + return True + elif dataset is not None and listinstr(['HallusionBench'], dataset): + return True + return False + + def clear_prompt(self, prompt): + prompt = re.sub(r"Hint:.*?Question:", "", prompt, flags=re.S).strip() + prompt = re.sub(r"\nChoices:\n.*", "", prompt, flags=re.S).strip() + return prompt + + def encode_image(self, pil_image): + buffer = BytesIO() + pil_image.save(buffer, format='PNG') + base64_str = base64.b64encode(buffer.getvalue()).decode("utf-8") + return base64_str + + def build_prompt(self, line, dataset=None): + if isinstance(line, int): + line = self.data.iloc[line] + + tgt_path = self.dump_image(line, dataset) + question = line['question'] + hint = None + if listinstr(self.datasets_use_system, dataset): + system_prompt = self.system_prompt + else: + system_prompt = '' + mcq = False + if DATASET_TYPE(dataset) == 'MCQ' or listinstr(self.datasets_use_multichoice, dataset): + options = { + cand: line[cand] + for cand in string.ascii_uppercase + if cand in line and not pd.isna(line[cand]) + } + if listinstr(self.datasets_use_multichoice, dataset): + options = {} + if not pd.isna(line['choices']): + for i, c in enumerate(eval(line['choices'])): + options[string.ascii_uppercase[i]] = c + question = self.clear_prompt(question) + + # support chinese + if listinstr(['_CN', '_cn'], dataset): + options_prompt = '\n选项:\n' + else: + options_prompt = '\nOPTIONS:\n' + options_prompt += '\n'.join(f"{key}:{value}" for key, value in options.items()) + hint = line['hint'] if ('hint' in line and not pd.isna(line['hint'])) else None + mcq = True if len(options) else False + if len(options): + prompt = question + options_prompt + else: + prompt = question + else: + prompt = question + + msgs = [] + if system_prompt: + msgs.append(dict(type='text', value=system_prompt)) + + if isinstance(tgt_path, list): + msgs.extend([dict(type='image', value=p) for p in tgt_path]) + else: + msgs.append(dict(type='image', value=tgt_path)) + + if hint: + prompt = 'Hint: ' + hint + '\n' + prompt + msgs.append(dict(type='text', value=prompt)) + + if mcq: + msgs.append(dict(type='text', value=self.mcq_prompt)) + return msgs + + def prompt_to_request_messages(self, inputs): + + messages = [ + {'role': 'user', 'content': []} + ] + is_mcq = False + for x in inputs: + if x['type'] == 'text': + if x['value'] == self.system_prompt: + messages = [{'role': 'system', 'content': [{"type": "text", "text": x['value']}]}] + messages + elif self.mcq_prompt == x['value']: + is_mcq = True + else: + messages[-1]['content'].append( + {"type": "text", "text": x['value']}, + ) + if x['type'] == 'image': + _url = self.encode_image(Image.open(x['value'])) + messages[-1]['content'].append( + {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{_url}"}}, + ) + else: + continue + + return messages, is_mcq + + def generate_inner(self, inputs, **kwargs) -> str: + messages, is_mcq = self.prompt_to_request_messages(inputs) + + data = { + "model": self.model, + "messages": messages, + "max_tokens": self.max_tokens, + "temperature": 0, + "top_p": 0.8, + "stream": False, + "extra_body": { + "repetition_penalty": 1 + } + } + + headers = { + 'Authorization': self.api_key, + 'Content-Type': 'application/json' + } + + try: + chat_response = requests.post(self.api_url, json=data, headers=headers) + response = ChatResponse(json.loads(chat_response.content)) + result = response.choices[0].message.content + # Extract index to exact matching when ChatGPT is unavailable. + if self.use_openai_evaluate is False and is_mcq is True: + try: + result = result[0] + except Exception: + result = 'A' + return 0, result, 'Succeeded! ' + except Exception as err: + if self.verbose: + logger.error(f'{type(err)}: {err}') + logger.error(f'The input messages are {inputs}.') + return -1, '', '' + + +class TaichuVLAPI(TaichuVLWrapper): + + def generate(self, message, dataset=None): + return super(TaichuVLAPI, self).generate(message, dataset=dataset) + + +class TaichuVLRWrapper(BaseAPI): + is_api: bool = True + + def __init__(self, + model: str = 'taichu_vlr_3b', + retry: int = 5, + verbose: bool = True, + temperature: float = 0.0, + system_prompt: str = None, + max_tokens: int = 4096, + use_reasoning_prompt: bool = True, + post_process: bool = True, + key: str = None, + url: str = None, + **kwargs): + + self.model = model + self.kwargs = kwargs + self.max_tokens = max_tokens + self.system_prompt = system_prompt + self.use_reasoning_prompt = use_reasoning_prompt + self.post_process = post_process + self.verbose = verbose + + openai_key = os.environ.get('OPENAI_API_KEY', None) + use_openai = os.environ.get('USE_OPENAI_EVAL', True) + self.use_openai_evaluate = (isinstance(openai_key, str) and openai_key.startswith('sk-') and use_openai) + + self.api_key = os.environ.get('TAICHU_API_KEY', key) + self.api_url = url + + assert self.api_key is not None, 'Please set the API Key' + + super().__init__(retry=retry, system_prompt=self.system_prompt, verbose=verbose, **kwargs) + + def use_custom_prompt(self, dataset): + return False + + def encode_image(self, pil_image): + buffer = BytesIO() + pil_image.save(buffer, format='PNG') + base64_str = base64.b64encode(buffer.getvalue()).decode("utf-8") + return base64_str + + def post_process_func(self, response): + resp = response.split('\\boxed{')[-1] + lt = len(resp) + counter, end = 1, None + for i in range(lt): + if resp[i] == '{': + counter += 1 + elif resp[i] == '}': + counter -= 1 + if counter == 0: + end = i + break + elif i == lt - 1: + end = lt + break + if end is not None: + response = resp[:end] + return response + + def prompt_to_request_messages(self, inputs): + + messages = [ + {'role': 'user', 'content': []} + ] + for x in inputs: + if x['type'] == 'text': + messages[-1]['content'].append( + {"type": "text", "text": x['value']}, + ) + if x['type'] == 'image': + _url = self.encode_image(Image.open(x['value'])) + messages[-1]['content'].append( + {"type": "image_url", "image_url": {"url": f"data:image/jpeg;base64,{_url}"}}, + ) + else: + continue + + PROMPT = ( + "First thinks about the reasoning process in the mind and then provides the user with the answer. " + "Put your final answer within \\boxed{}. " + "The response of reasoning and answer are formatted in reasoning \\boxed{answer here} .\n" # noqa: E501 + ) + + if self.use_reasoning_prompt: + for content in messages[0]['content']: + if content['type'] == 'text': + content['text'] = PROMPT + content['text'] + break + + return messages + + def generate_inner(self, inputs, **kwargs) -> str: + messages = self.prompt_to_request_messages(inputs) + if self.verbose: + verbose_messages = copy.deepcopy(messages) + for mess in verbose_messages: + if mess['role'] == 'user': + for content in mess['content']: + if content['type'] == 'image_url': + content['image_url']['url'] = '' + print(f'\033[31m{verbose_messages}\033[0m') + + data = { + "model": self.model, + "messages": messages, + "max_tokens": self.max_tokens, + "temperature": 0, + "top_p": 0.8, + "stream": False, + "repetition_penalty": 1.0 + } + + headers = { + 'Authorization': f"Bearer {self.api_key}", + 'Content-Type': 'application/json' + } + + try: + chat_response = requests.post(self.api_url, json=data, headers=headers) + response = ChatResponse(json.loads(chat_response.content)) + result = response.choices[0].message.content + if self.post_process: + result = self.post_process_func(result) + if self.verbose: + print(f'\033[32m{result}\033[0m') + + return 0, result, 'Succeeded! ' + except Exception as err: + if self.verbose: + logger.error(f'{type(err)}: {err}') + logger.error(f'The input messages are {inputs}.') + return -1, '', '' + + +class TaichuVLRAPI(TaichuVLRWrapper): + + def generate(self, message, dataset=None): + return super(TaichuVLRAPI, self).generate(message, dataset=dataset) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/taiyi.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/taiyi.py new file mode 100644 index 00000000..1638af8c --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/taiyi.py @@ -0,0 +1,196 @@ +import base64 +import json +import os +import string + +import numpy as np +import pandas as pd +import requests + +from vlmeval.api.base import BaseAPI +from vlmeval.dataset import DATASET_TYPE +from vlmeval.smp import cn_string, get_logger, listinstr + +logger = get_logger(__name__) + + +class TaiyiWrapper(BaseAPI): + + is_api: bool = True + + def __init__(self, + model: str = 'taiyi', + retry: int = 5, + key: str = None, + verbose: bool = False, + system_prompt: str = None, + temperature: float = 0, + timeout: int = 60, + url: str = "https://taiyi.megvii.com/v1/chat/completions", + max_tokens: int = 1024, + **kwargs): + + self.model = model + self.fail_msg = 'Failed to obtain answer via API. ' + self.max_tokens = max_tokens + self.temperature = temperature + + if key is None: + key = os.environ.get('TAIYI_API_KEY', None) + assert key is not None, ('Please set the API Key ') + self.key = key + + self.timeout = timeout + super().__init__(retry=retry, system_prompt=system_prompt, verbose=verbose, **kwargs) + assert url is not None, ('Please set the url ') + self.url = url + logger.info(f'Using url: {self.url}; API Key: {self.key}') + + def use_custom_prompt(self, dataset): + if DATASET_TYPE(dataset) == 'Y/N' or DATASET_TYPE(dataset) == 'MCQ' or DATASET_TYPE(dataset) == 'VQA': + return True + return False + + def prepare_inputs(self, inputs): + input_msgs = [] + if self.system_prompt is not None: + input_msgs.append(dict(role='system', content=self.system_prompt)) + has_images = np.sum([x['type'] == 'image' for x in inputs]) + if has_images: + content_list = [] + for msg in inputs: + if msg['type'] == 'text': + content_list.append(dict(type='text', text=msg['value'])) + elif msg['type'] == 'image': + imgbytes = open(msg['value'], 'rb').read() + b64 = base64.b64encode(imgbytes).decode('ascii') + img_struct = dict(url=f'data:image/jpeg;base64,{b64}') + content_list.append(dict(type='image_url', image_url=img_struct)) + input_msgs.append(dict(role='user', content=content_list)) + else: + assert all([x['type'] == 'text' for x in inputs]) + text = '\n'.join([x['value'] for x in inputs]) + input_msgs.append(dict(role='user', content=text)) + return input_msgs + + def image_first(self, msgs): + nr_img = 0 + for s in msgs: + if s['type'] == 'image': + nr_img += 1 + + if nr_img == 1: + new_msgs = [] + img_msg = None + for s in msgs: + if s['type'] == 'text': + new_msgs.append(s) + else: + img_msg = s + new_msgs.insert(0, img_msg) + else: + new_msgs = msgs + + return new_msgs + + def build_multi_choice_prompt(self, line, dataset=None): + question = line['question'] + hint = line['hint'] if ('hint' in line and not pd.isna(line['hint'])) else None + if hint is not None: + question = hint + '\n' + question + + options = { + cand: line[cand] + for cand in string.ascii_uppercase + if cand in line and not pd.isna(line[cand]) + } + for key, item in options.items(): + question += f'\n{key}. {item}' + prompt = question + + if len(options): + prompt += '\n请直接回答选项字母。' if cn_string( + prompt) else "\nAnswer with the option's letter from the given choices directly." + else: + prompt += '\n请直接回答问题。' if cn_string(prompt) else '\nAnswer the question directly.' + + return prompt + + def build_yorn_prompt(self, line, dataset=None): + if listinstr(['HallusionBench'], dataset): + pre_prompt = 'Read the following question carefully, think and solve it step by step.\n\n' + else: + pre_prompt = '' + + prompt = pre_prompt + line['question'] + ' Please answer yes or no as the final answer.' + + return prompt + + def build_vqa_prompt(self, line, dataset=None): + if listinstr(['OCRBench'], dataset): + pre_prompt = 'Carefully identify the text in the image and answer the question.\n\n' + else: + pre_prompt = '' + + if listinstr(['MMVet'], dataset): + post_prompt = '\nAnswer this question in detail.' + else: + post_prompt = '' + + prompt = pre_prompt + line['question'] + post_prompt + + return prompt + + def build_prompt(self, line, dataset=None): + assert self.use_custom_prompt(dataset) + assert dataset is None or isinstance(dataset, str) + tgt_path = self.dump_image(line, dataset) + + if DATASET_TYPE(dataset) == 'MCQ': + prompt = self.build_multi_choice_prompt(line, dataset) + elif DATASET_TYPE(dataset) == 'Y/N': + prompt = self.build_yorn_prompt(line, dataset) + elif DATASET_TYPE(dataset) == 'VQA': + prompt = self.build_vqa_prompt(line, dataset) + else: + raise RuntimeError(f'Invalid dataset type: {DATASET_TYPE(dataset)}') + message = [] + message.extend([dict(type='image', value=s) for s in tgt_path]) + message.extend([dict(type='text', value=prompt)]) + + # interleave dataset + if dataset.startswith('MMMU_'): + from .. import MMMUDataset + message = MMMUDataset.split_MMMU(message) + message = self.image_first(message) + + return message + + def generate_inner(self, inputs, **kwargs) -> str: + + input_msgs = self.prepare_inputs(inputs) + temperature = kwargs.pop('temperature', self.temperature) + + headers = {'Authorization': f'Bearer {self.key}'} + payload = dict( + model=self.model, + messages=input_msgs, + n=1, + temperature=temperature, + **kwargs) + response = requests.post(self.url, headers=headers, data=json.dumps(payload), timeout=self.timeout * 1.1) + ret_code = response.status_code + ret_code = 0 if (200 <= int(ret_code) < 300) else ret_code + answer = self.fail_msg + try: + resp_struct = json.loads(response.text) + answer = resp_struct['choices'][0]['message']['content'].strip() + except Exception: + pass + return ret_code, answer, response + + +class TaiyiAPI(TaiyiWrapper): + + def generate(self, message, dataset=None): + return super(TaiyiAPI, self).generate(message) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/telemm.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/telemm.py new file mode 100644 index 00000000..a2b5a2ef --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/telemm.py @@ -0,0 +1,296 @@ +import json +import os +import re + +import numpy as np +import requests + +from vlmeval.api.base import BaseAPI +from vlmeval.dataset import DATASET_MODALITY, DATASET_TYPE +from vlmeval.smp import encode_image_to_base64, get_logger, listinstr + +logger = get_logger(__name__) + +API_BASE = "https://openapi.teleagi.cn:443/aipaas/aiproxy-stream/chat/completions/8005400012" + +PREDEFINED_SYSTEM_PROMPT = """ +You are an AI assistant that rigorously follows this response protocol: + +1. First, conduct a detailed analysis of the question. Consider different \ +angles, potential solutions, and reason through the problem step-by-step. \ +Enclose this entire thinking process within {START} and {END} tags. + +2. After the thinking section, provide a clear, concise, and direct answer to \ +the user's question. Separate the answer from the think section with a newline. + +Ensure that the thinking process is thorough but remains focused on the \ +query. The final answer should be standalone and not reference the thinking \ +section. +""".strip() + + +class TeleMM2_PromptUtil: + def __init__(self): + self.cot_prompt = 'Please answer the question and put the final answer within \\boxed{}.' + + def dump_image(self, line, dataset): + return self.dump_image_func(line) + + def use_custom_prompt(self, dataset): + assert dataset is not None + assert DATASET_MODALITY(dataset) != 'VIDEO', 'not supported' + return True + + def build_prompt(self, line, dataset=None): + assert self.use_custom_prompt(dataset) + assert dataset is None or isinstance(dataset, str) + from ..vlm.internvl.utils import (build_mcq_cot_prompt, build_multi_choice_prompt, + build_qa_cot_prompt, reorganize_prompt) + + tgt_path = self.dump_image(line, dataset) + if dataset is not None and DATASET_TYPE(dataset) == 'Y/N': + question = line['question'] + if listinstr(['HallusionBench'], dataset): + cot_prompt = ( + "Answer the preceding yes-or-no question. Think step by step logically, considering all " + "relevant information before answering. If you are uncertain or the problem is ambiguous, make a " + "reasoned guess based on the information provided. The last line of your response must be exactly " + "in this format: 'Answer: \\boxed{Yes}' or 'Answer: \\boxed{No}' (without quotes)." + ) + prompt = question + '\n' + cot_prompt + else: + prompt = question + elif dataset is not None and DATASET_TYPE(dataset) == 'MCQ': + prompt = build_multi_choice_prompt(line, dataset) + if listinstr(['MMMU_DEV_VAL'], dataset): + prompt = build_mcq_cot_prompt(line, prompt, self.cot_prompt) + elif dataset is not None and DATASET_TYPE(dataset) == 'VQA': + question = line['question'] + if listinstr(['OCRBench'], dataset): + OCR_prompt = ( + "\nAnswer the question using a single word or phrase. " + "The image is guaranteed to contain the correct answer. Please provide the most likely " + "answer — do not answer \"No.\". Do not answer 'r'. " + ) + prompt = question + OCR_prompt + elif listinstr(['MathVista'], dataset): + prompt = build_qa_cot_prompt(line, question, self.cot_prompt) + elif listinstr(['MMVet'], dataset): + prompt = question + "\nPlease first think and then answer the question. " + else: + prompt = question + '\nAnswer the question using a single word or phrase.' + else: + prompt = line['question'] + + message = [dict(type='text', value=prompt)] + image_num = len(tgt_path) + message.extend([dict(type='image', value=s) for s in tgt_path]) + + prompt = reorganize_prompt(message, image_num, dataset=dataset) + prompt.replace('', '') + message[0] = dict(type='text', value=prompt) + return message + + +class TeleMM2_Wrapper(BaseAPI): + is_api: bool = True + custom_prompt = 'telemm2' + prompt_map = { + 'telemm2': TeleMM2_PromptUtil() + } + + def __init__(self, + model: str = None, + retry: int = 3, + verbose: bool = True, + timeout: int = 600, + authorization: str = None, + app_id: str = None, + system_prompt: str = None, + max_tokens: int = 20480, + **kwargs): + self.fail_msg = 'Failed to obtain answer via API. ' + self.max_tokens = max_tokens + self.timeout = timeout + + self.api_base = API_BASE + self.authorization = os.environ.get('AUTHOR', authorization) + self.app_id = os.environ.get('APP_ID', app_id) + super().__init__(retry=retry, system_prompt=system_prompt, verbose=verbose, **kwargs) + + self.model = model + + logger.info(f'evaluate model: {self.model}') + + self.max_tokens = 20480 + self.temperature = 0.0 + self.custom_prompt = 'telemm2' + + logger.info(f'using custom prompt {self.custom_prompt}') + logger.info(f'Init temperature: {self.temperature}') + self.system_prompt = None + + def set_dump_image(self, dump_image_func): + if self.custom_prompt in self.prompt_map: + self.prompt_map[self.custom_prompt].dump_image_func = dump_image_func + self.dump_image_func = dump_image_func + + def use_custom_prompt(self, dataset): + if self.custom_prompt in self.prompt_map: + return self.prompt_map[self.custom_prompt].use_custom_prompt(dataset) + return False + + def build_prompt(self, line, dataset=None): + if self.custom_prompt in self.prompt_map: + return self.prompt_map[self.custom_prompt].build_prompt(line, dataset) + raise NotImplementedError + + def prepare_itlist(self, inputs): + assert np.all([isinstance(x, dict) for x in inputs]) + has_images = np.sum([x['type'] == 'image' for x in inputs]) + if has_images: + content_list = [] + for msg in inputs: + if msg['type'] == 'text': + content_list.append(dict(type='text', text=msg['value'])) + print('text msg:', msg['value']) + elif msg['type'] == 'image': + from PIL import Image + + img = Image.open(msg['value']) + b64 = encode_image_to_base64(img) + extra_args = msg.copy() + extra_args.pop('type') + extra_args.pop('value') + img_struct = dict(url=f'data:image/jpeg;base64,{b64}', **extra_args) + content_list.append(dict(type='image_url', image_url=img_struct)) + else: + assert all([x['type'] == 'text' for x in inputs]) + text = '\n'.join([x['value'] for x in inputs]) + content_list = [dict(type='text', text=text)] + return content_list + + def prepare_inputs(self, inputs): + input_msgs = [] + if self.system_prompt is not None: + input_msgs.append(dict(role='system', content=self.system_prompt)) + + assert isinstance(inputs, list) and isinstance(inputs[0], dict) + assert np.all(['type' in x for x in inputs]) or np.all(['role' in x for x in inputs]), inputs + if 'role' in inputs[0]: + assert inputs[-1]['role'] == 'user', inputs[-1] + for item in inputs: + input_msgs.append(dict(role=item['role'], content=self.prepare_itlist(item['content']))) + else: + input_msgs.append(dict(role='user', content=self.prepare_itlist(inputs))) + return input_msgs + + def extract_boxed(self, text): + if "\\boxed{" not in text: + return None + + start = text.rfind("\\boxed{") + i = start + len("\\boxed{") + brace_count = 1 + content_chars = [] + + while i < len(text) and brace_count > 0: + ch = text[i] + if ch == "{": + brace_count += 1 + elif ch == "}": + brace_count -= 1 + if brace_count > 0: + content_chars.append(ch) + i += 1 + + if content_chars: + inner = "".join(content_chars).strip() + if inner: + return inner + return None + + def extract_answer(self, text, dataset_name=None): + if not isinstance(text, str): + return text + + boxed = self.extract_boxed(text) + if boxed is not None: + return boxed + + if listinstr(['MMVet'], dataset_name): + redacted_pattern = r'.*?' + if re.search(redacted_pattern, text, re.DOTALL): + result = re.sub(redacted_pattern, '', text, flags=re.DOTALL) + return result.strip() + return text + + elif listinstr(['MMMU_DEV_VAL'], dataset_name): + last_end = text.rfind("") + if last_end != -1: + result = text[last_end + len(""):] + return result.strip() + return text + else: + last_end = text.rfind("") + if last_end != -1: + result = text[last_end + len(""):] + return result.strip() + return text + + def generate_inner(self, inputs, **kwargs) -> str: + max_tokens = kwargs.pop('max_tokens', self.max_tokens) + dataset = kwargs.pop('dataset', None) + if listinstr(['MathVista'], dataset): + self.system_prompt = PREDEFINED_SYSTEM_PROMPT.format(START="", END="") + elif listinstr(['MMMU_DEV_VAL'], dataset): + self.system_prompt = PREDEFINED_SYSTEM_PROMPT.format(START="", END="") + else: + self.system_prompt = None + + input_msgs = self.prepare_inputs(inputs) + + if listinstr(['MMMU_DEV_VAL'], dataset): + do_sample = True + temperature = 0.6 + else: + do_sample = False + temperature = 0.0 + + logger.info(f'Generate temperature: {temperature}') + + headers = { + 'Content-Type': 'application/json', + 'Authorization': self.authorization, + 'X-APP-ID': self.app_id} + + payload = dict( + model=self.model, + messages=input_msgs, + max_tokens=max_tokens, + n=1, + temperature=temperature, + do_sample=do_sample, + **kwargs) + response = requests.post( + self.api_base, + headers=headers, data=json.dumps(payload), timeout=self.timeout) + ret_code = response.status_code + ret_code = 0 if (200 <= int(ret_code) < 300) else ret_code + answer = self.fail_msg + try: + resp_struct = json.loads(response.text) + answer = resp_struct['choices'][0]['message']['content'].strip() + answer = self.extract_answer(answer, dataset) + except Exception: + pass + return ret_code, answer, response + + +class TeleMM2_API(TeleMM2_Wrapper): + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def generate(self, message, dataset=None): + return super(TeleMM2_API, self).generate(message, dataset=dataset) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/telemm_thinking.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/telemm_thinking.py new file mode 100644 index 00000000..fbf1ec42 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/telemm_thinking.py @@ -0,0 +1,297 @@ +import json +import os +import re + +import numpy as np +import requests + +from vlmeval.api.base import BaseAPI +from vlmeval.dataset import DATASET_MODALITY, DATASET_TYPE +from vlmeval.smp import encode_image_to_base64, get_logger, listinstr + +logger = get_logger(__name__) + +API_BASE = "https://openapi.teleagi.cn:443/aipaas/aiproxy-stream/chat/completions/8005400012" + +PREDEFINED_SYSTEM_PROMPT = """ +You are an AI assistant that rigorously follows this response protocol: + +1. First, conduct a detailed analysis of the question. Consider different \ +angles, potential solutions, and reason through the problem step-by-step. \ +Enclose this entire thinking process within {START} and {END} tags. + +2. After the thinking section, provide a clear, concise, and direct answer to \ +the user's question. Separate the answer from the think section with a newline. + +Ensure that the thinking process is thorough but remains focused on the \ +query. The final answer should be standalone and not reference the thinking \ +section. +""".strip() + + +class TeleMM2Thinking_PromptUtil: + def __init__(self): + self.cot_prompt = 'Please answer the question and put the final answer within \\boxed{}.' + + def dump_image(self, line, dataset): + return self.dump_image_func(line) + + def use_custom_prompt(self, dataset): + assert dataset is not None + assert DATASET_MODALITY(dataset) != 'VIDEO', 'not supported' + return True + + def build_prompt(self, line, dataset=None): + assert self.use_custom_prompt(dataset) + assert dataset is None or isinstance(dataset, str) + from ..vlm.internvl.utils import (build_mcq_cot_prompt, build_multi_choice_prompt, + build_qa_cot_prompt, reorganize_prompt) + + tgt_path = self.dump_image(line, dataset) + if dataset is not None and DATASET_TYPE(dataset) == 'Y/N': + question = line['question'] + if listinstr(['HallusionBench'], dataset): + cot_prompt = ( + "Answer the preceding yes-or-no question. Think step by step logically, considering all " + "relevant information before answering. If you are uncertain or the problem is ambiguous, make a " + "reasoned guess based on the information provided. The last line of your response must be exactly " + "in this format: 'Answer: \\boxed{Yes}' or 'Answer: \\boxed{No}' (without quotes)." + ) + prompt = question + '\n' + cot_prompt + else: + prompt = question + elif dataset is not None and DATASET_TYPE(dataset) == 'MCQ': + prompt = build_multi_choice_prompt(line, dataset) + if listinstr(['MMMU_DEV_VAL'], dataset): + prompt = build_mcq_cot_prompt(line, prompt, self.cot_prompt) + elif dataset is not None and DATASET_TYPE(dataset) == 'VQA': + question = line['question'] + if listinstr(['OCRBench'], dataset): + OCR_prompt = ( + "\nAnswer the question using a single word or phrase. " + "The image is guaranteed to contain the correct answer. Please provide the most likely " + "answer — do not answer \"No.\". Do not answer 'r'. " + ) + prompt = question + OCR_prompt + elif listinstr(['MathVista'], dataset): + prompt = build_qa_cot_prompt(line, question, self.cot_prompt) + elif listinstr(['MMVet'], dataset): + prompt = question + "\nPlease first think and then answer the question. " + else: + prompt = question + '\nAnswer the question using a single word or phrase.' + else: + prompt = line['question'] + + message = [dict(type='text', value=prompt)] + image_num = len(tgt_path) + message.extend([dict(type='image', value=s) for s in tgt_path]) + + prompt = reorganize_prompt(message, image_num, dataset=dataset) + prompt.replace('', '') + message[0] = dict(type='text', value=prompt) + return message + + +class TeleMM2Thinking_Wrapper(BaseAPI): + is_api: bool = True + custom_prompt = 'telemm2thinking' + prompt_map = { + 'telemm2thinking': TeleMM2Thinking_PromptUtil() + } + + def __init__(self, + model: str = None, + retry: int = 3, + verbose: bool = True, + timeout: int = 600, + authorization: str = None, + app_id: str = None, + system_prompt: str = None, + max_tokens: int = 20480, + **kwargs): + self.fail_msg = 'Failed to obtain answer via API. ' + self.max_tokens = max_tokens + self.timeout = timeout + + self.api_base = API_BASE + self.authorization = os.environ.get('AUTHOR', authorization) + self.app_id = os.environ.get('APP_ID', app_id) + super().__init__(retry=retry, system_prompt=system_prompt, verbose=verbose, **kwargs) + + self.model = model + + logger.info(f'evaluate model: {self.model}') + + self.max_tokens = 20480 + self.temperature = 0.0 + self.custom_prompt = 'telemm2thinking' + + logger.info(f'using custom prompt {self.custom_prompt}') + logger.info(f'Init temperature: {self.temperature}') + self.system_prompt = None + + def set_dump_image(self, dump_image_func): + if self.custom_prompt in self.prompt_map: + self.prompt_map[self.custom_prompt].dump_image_func = dump_image_func + self.dump_image_func = dump_image_func + + def use_custom_prompt(self, dataset): + if self.custom_prompt in self.prompt_map: + return self.prompt_map[self.custom_prompt].use_custom_prompt(dataset) + return False + + def build_prompt(self, line, dataset=None): + if self.custom_prompt in self.prompt_map: + return self.prompt_map[self.custom_prompt].build_prompt(line, dataset) + raise NotImplementedError + + def prepare_itlist(self, inputs): + assert np.all([isinstance(x, dict) for x in inputs]) + has_images = np.sum([x['type'] == 'image' for x in inputs]) + if has_images: + content_list = [] + for msg in inputs: + if msg['type'] == 'text': + content_list.append(dict(type='text', text=msg['value'])) + print('text msg:', msg['value']) + elif msg['type'] == 'image': + from PIL import Image + + img = Image.open(msg['value']) + b64 = encode_image_to_base64(img) + extra_args = msg.copy() + extra_args.pop('type') + extra_args.pop('value') + img_struct = dict(url=f'data:image/jpeg;base64,{b64}', **extra_args) + content_list.append(dict(type='image_url', image_url=img_struct)) + else: + assert all([x['type'] == 'text' for x in inputs]) + text = '\n'.join([x['value'] for x in inputs]) + content_list = [dict(type='text', text=text)] + return content_list + + def prepare_inputs(self, inputs): + input_msgs = [] + if self.system_prompt is not None: + input_msgs.append(dict(role='system', content=self.system_prompt)) + + assert isinstance(inputs, list) and isinstance(inputs[0], dict) + assert np.all(['type' in x for x in inputs]) or np.all(['role' in x for x in inputs]), inputs + if 'role' in inputs[0]: + assert inputs[-1]['role'] == 'user', inputs[-1] + for item in inputs: + input_msgs.append(dict(role=item['role'], content=self.prepare_itlist(item['content']))) + else: + input_msgs.append(dict(role='user', content=self.prepare_itlist(inputs))) + return input_msgs + + def extract_boxed(self, text): + if "\\boxed{" not in text: + return None + + start = text.rfind("\\boxed{") + i = start + len("\\boxed{") + brace_count = 1 + content_chars = [] + + while i < len(text) and brace_count > 0: + ch = text[i] + if ch == "{": + brace_count += 1 + elif ch == "}": + brace_count -= 1 + if brace_count > 0: + content_chars.append(ch) + i += 1 + + if content_chars: + inner = "".join(content_chars).strip() + if inner: + return inner + return None + + def extract_answer(self, text, dataset_name=None): + if not isinstance(text, str): + return text + + boxed = self.extract_boxed(text) + if boxed is not None: + return boxed + + if listinstr(['MMVet'], dataset_name): + redacted_pattern = r'.*?' + if re.search(redacted_pattern, text, re.DOTALL): + result = re.sub(redacted_pattern, '', text, flags=re.DOTALL) + return result.strip() + return text + + elif listinstr(['MMMU_DEV_VAL'], dataset_name): + last_end = text.rfind("") + if last_end != -1: + result = text[last_end + len(""):] + return result.strip() + return text + else: + last_end = text.rfind("") + if last_end != -1: + result = text[last_end + len(""):] + return result.strip() + return text + + def generate_inner(self, inputs, **kwargs) -> str: + max_tokens = kwargs.pop('max_tokens', self.max_tokens) + dataset = kwargs.pop('dataset', None) + if listinstr(['MathVista'], dataset): + self.system_prompt = PREDEFINED_SYSTEM_PROMPT.format(START="", END="") + elif listinstr(['MMMU_DEV_VAL'], dataset): + self.system_prompt = PREDEFINED_SYSTEM_PROMPT.format(START="", END="") + else: + self.system_prompt = None + + input_msgs = self.prepare_inputs(inputs) + + if listinstr(['MMMU_DEV_VAL'], dataset): + do_sample = True + temperature = 0.6 + else: + do_sample = False + temperature = 0.0 + + logger.info(f'Generate temperature: {temperature}') + + headers = { + 'Content-Type': 'application/json', + 'Authorization': self.authorization, + "Model": "TeleMM2Thinking", + 'X-APP-ID': self.app_id} + + payload = dict( + model=self.model, + messages=input_msgs, + max_tokens=max_tokens, + n=1, + temperature=temperature, + do_sample=do_sample, + **kwargs) + response = requests.post( + self.api_base, + headers=headers, data=json.dumps(payload), timeout=self.timeout) + ret_code = response.status_code + ret_code = 0 if (200 <= int(ret_code) < 300) else ret_code + answer = self.fail_msg + try: + resp_struct = json.loads(response.text) + answer = resp_struct['choices'][0]['message']['content'].strip() + answer = self.extract_answer(answer, dataset) + except Exception: + pass + return ret_code, answer, response + + +class TeleMM2Thinking_API(TeleMM2Thinking_Wrapper): + + def __init__(self, **kwargs): + super().__init__(**kwargs) + + def generate(self, message, dataset=None): + return super(TeleMM2Thinking_API, self).generate(message, dataset=dataset) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/together.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/together.py new file mode 100644 index 00000000..269d1e91 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/together.py @@ -0,0 +1,141 @@ +""" +Together AI API support for vision models. +OpenAI-compatible chat completions with image_url (base64 or URL). +Set TOGETHER_API_KEY or pass key=... + +Requires: pip install together (optional; or use requests with api_base) +""" +import json +import os + +import numpy as np +import requests + +from ..smp import encode_image_to_base64, get_logger +from .base import BaseAPI + +logger = get_logger(__name__) + +TOGETHER_API_BASE = "https://api.together.xyz/v1/chat/completions" + + +class TogetherAPI(BaseAPI): + """VLM API using Together AI (OpenAI-compatible; supports vision).""" + + is_api: bool = True + + def __init__( + self, + model: str = "meta-llama/Llama-3.2-11B-Vision-Instruct-Turbo", + key: str = None, + api_base: str = None, + retry: int = 10, + wait: int = 1, + system_prompt: str = None, + verbose: bool = True, + temperature: float = 0, + max_tokens: int = 2048, + timeout: int = 300, + img_size: int = -1, + **kwargs, + ): + self.model = model + self.key = key or os.environ.get("TOGETHER_API_KEY") + self.api_base = api_base or os.environ.get("TOGETHER_API_BASE", TOGETHER_API_BASE) + self.temperature = temperature + self.max_tokens = max_tokens + self.timeout = timeout + self.img_size = img_size + + if not self.key: + raise ValueError( + "Together API key is required. Set TOGETHER_API_KEY or pass key=..." + ) + + super().__init__( + retry=retry, + wait=wait, + system_prompt=system_prompt, + verbose=verbose, + **kwargs, + ) + + logger.info(f"TogetherAPI: model={self.model}, api_base={self.api_base}") + + def _prepare_content(self, inputs): + """Build OpenAI-style content list (text + image_url with base64).""" + assert all(isinstance(x, dict) for x in inputs) + has_images = np.sum([x["type"] == "image" for x in inputs]) + if has_images: + content_list = [] + for item in inputs: + if item["type"] == "text": + content_list.append({"type": "text", "text": item["value"]}) + elif item["type"] == "image": + from PIL import Image + + img = Image.open(item["value"]) + b64 = encode_image_to_base64(img, target_size=self.img_size) + content_list.append({ + "type": "image_url", + "image_url": {"url": f"data:image/jpeg;base64,{b64}"}, + }) + return content_list + text = "\n".join([x["value"] for x in inputs if x["type"] == "text"]) + return [{"type": "text", "text": text or ""}] + + def _prepare_messages(self, inputs): + if self.system_prompt: + out = [{"role": "system", "content": self.system_prompt}] + else: + out = [] + if inputs and "role" in inputs[0]: + for item in inputs: + out.append({ + "role": item["role"], + "content": self._prepare_content(item["content"]), + }) + else: + out.append({"role": "user", "content": self._prepare_content(inputs)}) + return out + + def generate_inner(self, inputs, **kwargs): + temperature = kwargs.pop("temperature", self.temperature) + max_tokens = kwargs.pop("max_tokens", self.max_tokens) + + messages = self._prepare_messages(inputs) + payload = { + "model": self.model, + "messages": messages, + "temperature": temperature, + "max_tokens": max_tokens, + } + + try: + response = requests.post( + self.api_base, + headers={ + "Authorization": f"Bearer {self.key}", + "Content-Type": "application/json", + }, + data=json.dumps(payload), + timeout=self.timeout * 1.1, + ) + except Exception as err: + if self.verbose: + logger.error(f"{type(err).__name__}: {err}") + return -1, self.fail_msg, str(err) + + ret_code = response.status_code + ret_code = 0 if (200 <= ret_code < 300) else ret_code + answer = self.fail_msg + + try: + data = response.json() + answer = data["choices"][0]["message"]["content"].strip() + except Exception as err: + if self.verbose: + logger.error(f"{type(err).__name__}: {err}") + logger.error(response.text) + + return ret_code, answer, response diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/video_chat_online_v2.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/video_chat_online_v2.py new file mode 100644 index 00000000..5b2da4b8 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/api/video_chat_online_v2.py @@ -0,0 +1,285 @@ +# flake8: noqa +import base64 +import json +import os +import os.path as osp +import string + +import pandas as pd +import requests + +from vlmeval.api.base import BaseAPI +from vlmeval.dataset import DATASET_TYPE, img_root_map +from vlmeval.smp import (LMUDataRoot, cn_string, concat_images_vlmeval, + decode_base64_to_image_file, get_logger, listinstr, read_ok, toliststr) + +logger = get_logger(__name__) + +class VideoChatOnlineV2Wrapper(BaseAPI): + is_api: bool = True + INTERLEAVE = False + + def __init__(self, + model: str = 'VideoChatOnlineV2', + retry: int = 5, + wait: int = 5, + url: str = '', + key: str = '', + verbose: bool = True, + system_prompt: str = None, + temperature: float = 0.7, + max_tokens: int = 2048, + proxy: str = None, + **kwargs): + self.model = model + + self.temperature = temperature + self.max_tokens = max_tokens + self.url = url + self.key = key + + + super().__init__(wait=wait, retry=retry, system_prompt=system_prompt, verbose=verbose, **kwargs) + + def dump_image(self, line, dataset): + """Dump the image(s) of the input line to the corresponding dataset folder. + + Args: + line (line of pd.DataFrame): The raw input line. + dataset (str): The name of the dataset. + + Returns: + str | list[str]: The paths of the dumped images. + """ + ROOT = LMUDataRoot() + assert isinstance(dataset, str) + + img_root = os.path.join(ROOT, 'images', img_root_map(dataset) if dataset in img_root_map(dataset) else dataset) + os.makedirs(img_root, exist_ok=True) + if 'image' in line: + if isinstance(line['image'], list): + tgt_path = [] + assert 'image_path' in line + for img, im_name in zip(line['image'], line['image_path']): + path = osp.join(img_root, im_name) + if not read_ok(path): + decode_base64_to_image_file(img, path) + tgt_path.append(path) + else: + tgt_path = osp.join(img_root, f"{line['index']}.jpg") + if not read_ok(tgt_path): + decode_base64_to_image_file(line['image'], tgt_path) + tgt_path = [tgt_path] + else: + assert 'image_path' in line + tgt_path = toliststr(line['image_path']) + + return tgt_path + + def use_custom_prompt(self, dataset): + assert dataset is not None + if listinstr(['MMMU_DEV_VAL','MMMU_TEST'], dataset): + return False + else: + return True + + def build_multi_choice_prompt(self, line, dataset=None): + question = line['question'] + hint = line['hint'] if ('hint' in line and not pd.isna(line['hint'])) else None + if hint is not None: + question = hint + '\n' + question + + options = { + cand: line[cand] + for cand in string.ascii_uppercase + if cand in line and not pd.isna(line[cand]) + } + + for key, item in options.items(): + question += f'\n{key}. {item}' + prompt = question + + if len(options): + prompt += '\n请直接回答选项字母。' if cn_string( + prompt) else "\nAnswer with the option's letter from the given choices directly." + else: + prompt += '\n请直接回答问题。' if cn_string(prompt) else '\nAnswer the question directly.' + + return prompt + + + def build_prompt(self, line, dataset=None): + assert self.use_custom_prompt(dataset) + assert dataset is None or isinstance(dataset, str) + + tgt_path = self.dump_image(line, dataset) + + if dataset is not None and listinstr(['MME'], dataset): + question = line['question'] + prompt = question + ' Answer the question using a single word or phrase.' + elif dataset is not None and listinstr(['HallusionBench'], dataset): + question = line['question'] + prompt = question + ' Please answer yes or no. Answer the question using a single word or phrase.' + elif dataset is not None and DATASET_TYPE(dataset) == 'MCQ': + prompt = self.build_multi_choice_prompt(line, dataset) + elif dataset is not None and DATASET_TYPE(dataset) == 'VQA': + if listinstr(['MathVista', 'MathVision'], dataset): + prompt = line['question'] + elif listinstr(['LLaVABench'], dataset): + question = line['question'] + prompt = question + '\nAnswer this question in detail.' + elif listinstr(['MMVet'], dataset): + prompt = line['question'] + else: + question = line['question'] + prompt = question + '\nAnswer the question using a single word or phrase.' + else: + prompt = line['question'] + message = [dict(type='text', value=prompt)] + message.extend([dict(type='image', value=s) for s in tgt_path]) + return message + + + def message_to_promptimg(self, message, dataset=None): + assert not self.INTERLEAVE + model_name = self.__class__.__name__ + import warnings + warnings.warn( + f'Model {model_name} does not support interleaved input. ' + 'Will use the first image and aggregated texts as prompt. ') + num_images = len([x for x in message if x['type'] == 'image']) + if num_images == 0: + prompt = '\n'.join([x['value'] for x in message if x['type'] == 'text']) + image = None + else: + prompt = '\n'.join([x['value'] for x in message if x['type'] == 'text']) + if dataset == 'BLINK': + image = concat_images_vlmeval( + [x['value'] for x in message if x['type'] == 'image'], + target_size=512) + else: + image = [x['value'] for x in message if x['type'] == 'image'][0] + return prompt, image + + def get_send_data(self,prompt, image_path, temperature, max_tokens,stream=False): + image = '' + with open(image_path, 'rb') as f: + image = str(base64.b64encode(f.read()), 'utf-8') + send_data = { + "messages": [ + { + "role": "user", + "content": prompt + } + ], + "image_base64": image, + "max_tokens": max_tokens, + "temperature": temperature, + "do_sample": False, + "stream": stream + } + return send_data + + def get_send_data_no_image(self,prompt, temperature, max_tokens, stream=False): + send_data = { + "messages": [ + { + "role": "user", + "content": prompt + } + ], + "max_tokens": max_tokens, + "temperature": temperature, + "do_sample": False, + "stream": stream + } + return send_data + + def generate_inner(self, inputs, **kwargs) -> str: + assert isinstance(inputs, str) or isinstance(inputs, list) + inputs = [inputs] if isinstance(inputs, str) else inputs + dataset = kwargs.get('dataset', None) + prompt, image_path = self.message_to_promptimg(message=inputs, dataset=dataset) + + if image_path: + send_data = self.get_send_data( + prompt=prompt, + image_path=image_path, + temperature=self.temperature, + max_tokens=self.max_tokens, + stream = True) + else: + send_data = self.get_send_data_no_image( + prompt=prompt, + temperature=self.temperature, + max_tokens=self.max_tokens, + stream = True) + + json_data = json.dumps(send_data) + + header_dict = {'Content-Type': 'application/json','Authorization':self.key} + + r = requests.post(self.url, headers=header_dict, data=json_data, timeout=3000,stream=True) + try: + if send_data.get('stream', False): + # 流式处理 + chunks = [] + full_content = "" + last_valid_usage = None # 用于记录最后一个有效的usage + + try: + for line in r.iter_lines(): + if line: + decoded_line = line.decode('utf-8') + if decoded_line.startswith('data: '): + event_data = decoded_line[6:] + if event_data == '[DONE]': + break + try: + chunk = json.loads(event_data) + chunks.append(chunk) + + # 记录最后一个有效的usage(不累加) + if 'usage' in chunk: + last_valid_usage = chunk['usage'] + + # 实时输出内容 + if 'choices' in chunk: + for choice in chunk['choices']: + if 'delta' in choice and 'content' in choice['delta']: + content = choice['delta']['content'] + # print(content, end='', flush=True) + full_content += content + except json.JSONDecodeError: + continue + + # print('流式输出内容:', full_content) + + return 0,full_content,'Succeeded! ' + + except Exception as e: + return -1,f'Error: {str(e)}','' + else: + # 非流式处理 + try: + r_json = r.json() + output = r_json['choices'][0]['message']['content'] + return 0,output,'Succeeded! ' + except: + error_msg = f'Error! code {r.status_code} content: {r.content}' + error_con = r.content.decode('utf-8') + if self.verbose: + logger.error(error_msg) + logger.error(error_con) + logger.error(f'The input messages are {inputs}.') + return -1,error_msg,'' + except Exception as e: + return -1,f'Error: {str(e)}','' + + + + +class VideoChatOnlineV2API(VideoChatOnlineV2Wrapper): + + def generate(self, message, dataset=None): + return super(VideoChatOnlineV2API, self).generate(message, dataset=dataset) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/config.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/config.py new file mode 100644 index 00000000..e65f2d5a --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/config.py @@ -0,0 +1,2663 @@ +import copy as cp +import os +from functools import partial + +import vlmeval.api as api +import vlmeval.vlm as vlm + +PandaGPT_ROOT = None +MiniGPT4_ROOT = None +TransCore_ROOT = None +Yi_ROOT = None +OmniLMM_ROOT = None +Mini_Gemini_ROOT = None +VXVERSE_ROOT = None +VideoChat2_ROOT = None +VideoChatGPT_ROOT = None +PLLaVA_ROOT = None +RBDash_ROOT = None +VITA_ROOT = None +LLAVA_V1_7B_MODEL_PTH = "Please set your local path to LLaVA-7B-v1.1 here, the model weight is obtained by merging LLaVA delta weight based on vicuna-7b-v1.1 in https://github.com/haotian-liu/LLaVA/blob/main/docs/MODEL_ZOO.md with vicuna-7b-v1.1. " + +video_models = { + "Cosmos-Reason1-7B": partial( + vlm.CosmosReasonHF, + model_path="nvidia/Cosmos-Reason1-7B", + ), + # An example of a finetuned Reason1 model. Model path can be on the HuggingFace hub or locally. + "Cosmos-Reason1-7B-VideoPhy2": partial( + vlm.CosmosReasonHF, + model_path="shunznv/Cosmos-Reason1-7B-VideoPhy2", + ), + "Video-LLaVA-7B": partial(vlm.VideoLLaVA, model_path="LanguageBind/Video-LLaVA-7B"), + "Video-LLaVA-7B-HF": partial( + vlm.VideoLLaVA_HF, model_path="LanguageBind/Video-LLaVA-7B-hf" + ), + "VideoChat2-HD": partial( + vlm.VideoChat2_HD, + model_path="OpenGVLab/VideoChat2_HD_stage4_Mistral_7B", + root=VideoChat2_ROOT, + config_file="./vlmeval/vlm/video_llm/configs/videochat2_hd.json", + ), + "Chat-UniVi-7B": partial(vlm.Chatunivi, model_path="Chat-UniVi/Chat-UniVi"), + "Chat-UniVi-7B-v1.5": partial( + vlm.Chatunivi, model_path="Chat-UniVi/Chat-UniVi-7B-v1.5" + ), + "LLaMA-VID-7B": partial( + vlm.LLaMAVID, model_path="YanweiLi/llama-vid-7b-full-224-video-fps-1" + ), + "Video-ChatGPT": partial( + vlm.VideoChatGPT, model_path="MBZUAI/Video-ChatGPT-7B", dir_root=VideoChatGPT_ROOT + ), + "PLLaVA-7B": partial(vlm.PLLaVA, model_path="ermu2001/pllava-7b", dir_root=PLLaVA_ROOT), + "PLLaVA-13B": partial( + vlm.PLLaVA, model_path="ermu2001/pllava-13b", dir_root=PLLaVA_ROOT + ), + "PLLaVA-34B": partial( + vlm.PLLaVA, model_path="ermu2001/pllava-34b", dir_root=PLLaVA_ROOT + ), +} + +ungrouped = { + "AKI": partial(vlm.AKI, name="AKI", ckpt_pth="Sony/AKI-4B-phi-3.5-mini"), + "TransCore_M": partial(vlm.TransCoreM, root=TransCore_ROOT), + "PandaGPT_13B": partial(vlm.PandaGPT, name="PandaGPT_13B", root=PandaGPT_ROOT), + "flamingov2": partial( + vlm.OpenFlamingo, + name="v2", + mpt_pth="anas-awadalla/mpt-7b", + ckpt_pth="openflamingo/OpenFlamingo-9B-vitl-mpt7b", + ), + "VisualGLM_6b": partial(vlm.VisualGLM, model_path="THUDM/visualglm-6b"), + "mPLUG-Owl2": partial(vlm.mPLUG_Owl2, model_path="MAGAer13/mplug-owl2-llama2-7b"), + "mPLUG-Owl3": partial(vlm.mPLUG_Owl3, model_path="mPLUG/mPLUG-Owl3-7B-240728"), + "OmniLMM_12B": partial( + vlm.OmniLMM12B, model_path="openbmb/OmniLMM-12B", root=OmniLMM_ROOT + ), + "MGM_7B": partial( + vlm.Mini_Gemini, model_path="YanweiLi/MGM-7B-HD", root=Mini_Gemini_ROOT + ), + "Bunny-llama3-8B": partial(vlm.BunnyLLama3, model_path="BAAI/Bunny-v1_1-Llama-3-8B-V"), + "VXVERSE": partial(vlm.VXVERSE, model_name="XVERSE-V-13B", root=VXVERSE_ROOT), + "360VL-70B": partial(vlm.QH_360VL, model_path="qihoo360/360VL-70B"), + "Llama-3-MixSenseV1_1": partial( + vlm.LLama3Mixsense, model_path="Zero-Vision/Llama-3-MixSenseV1_1" + ), + "Parrot": partial(vlm.Parrot, model_path="AIDC-AI/Parrot-7B"), + "OmChat": partial(vlm.OmChat, model_path="omlab/omchat-v2.0-13B-single-beta_hf"), + "RBDash_72b": partial( + vlm.RBDash, model_path="RBDash-Team/RBDash-v1.5", root=RBDash_ROOT + ), + "Pixtral-12B": partial(vlm.Pixtral, model_path="mistralai/Pixtral-12B-2409"), + "Ministral-3-14B-Instruct-2512_api": partial( + api.LMDeployAPI, + api_base="http://0.0.0.0:8000/v1/chat/completions", + model='Ministral-3-14B-Instruct-2512', + temperature=0.15, + max_new_tokens=32768, + retry=6, + timeout=1800, + ), + "Falcon2-VLM-11B": partial(vlm.Falcon2VLM, model_path="tiiuae/falcon-11B-vlm"), + "KVL": partial(vlm.InternVLChat, model_path="amoeba04/KVL", version="V2.0"), +} + +o1_key = os.environ.get('O1_API_KEY', None) +o1_base = os.environ.get('O1_API_BASE', None) +o1_apis = { + 'o1': partial( + api.GPT4V, + model="o1-2024-12-17", + key=o1_key, + api_base=o1_base, + temperature=0, + img_detail='high', + retry=3, + timeout=1800, + max_tokens=16384, + verbose=False, + + ), + 'o3': partial( + api.GPT4V, + model="o3-2025-04-16", + key=o1_key, + api_base=o1_base, + temperature=0, + img_detail='high', + retry=3, + timeout=1800, + max_tokens=16384, + verbose=False, + ), + 'o4-mini': partial( + api.GPT4V, + model="o4-mini-2025-04-16", + key=o1_key, + api_base=o1_base, + temperature=0, + img_detail='high', + retry=3, + timeout=1800, + max_tokens=16384, + verbose=False, + ), +} + +api_models = { + # Lepton API models + "qwen3_235b_a22b": partial( + api.CosmosReason2, + model="Qwen3-VL-235B-A22B-Instruct", + api_base="https://b5k2m9x7-qwen3-vl-235b-a22b-instruct.xenon.lepton.run/v1/chat/completions", + temperature=0, + retry=10, + timeout=300, # Increased timeout for video processing (5 minutes) + verbose=False, + ), + "qwen3_30b_a3b": partial( + api.CosmosReason2, + model="Qwen3-VL-30B-A3B-Instruct", + api_base="https://b5k2m9x7-qwen3-vl-30b-a3b-instruct.xenon.lepton.run/v1/chat/completions", + temperature=0, + retry=10, + timeout=300, # Increased timeout for video processing (5 minutes) + ), + "qwen3_8b": partial( + api.CosmosReason2, + model="Qwen3-VL-8B-Instruct", + api_base="https://b5k2m9x7-qwen3-vl-8b.xenon.lepton.run/v1/chat/completions", + temperature=0, + retry=10, + ), + "cosmos_reason1_7b": partial( + api.CosmosReason1, + model="Cosmos-Reason1-7B", + api_base="https://b5k2m9x7-cosmos-reason1-7b-1107.xenon.lepton.run/v1/chat/completions", + temperature=0, + retry=10, + ), + "GeminiPro3-0": partial( + api.Gemini, model="gemini-3-pro-preview", temperature=0, retry=10, max_tokens=16384 + ), + "LVS_service": partial( + api.LVS_service, + model="Qwen/Qwen3-VL-8B-Instruct", + temperature=0, + retry=5, + verbose=False, + max_tokens=16384, + api_base="http://10.111.53.95:8009", + fail_msg={"events": [], "total_events": 0, "video_summary": ""}, + chunk_duration=10, + num_frames_per_chunk=15, + ), + # GPT + "GPT4V": partial( + api.GPT4V, + model="gpt-4-1106-vision-preview", + temperature=0, + img_size=512, + img_detail="low", + retry=10, + verbose=False, + ), + "GPT4V_HIGH": partial( + api.GPT4V, + model="gpt-4-1106-vision-preview", + temperature=0, + img_size=-1, + img_detail="high", + retry=10, + verbose=False, + ), + "GPT4V_20240409": partial( + api.GPT4V, + model="gpt-4-turbo-2024-04-09", + temperature=0, + img_size=512, + img_detail="low", + retry=10, + verbose=False, + ), + "GPT4V_20240409_HIGH": partial( + api.GPT4V, + model="gpt-4-turbo-2024-04-09", + temperature=0, + img_size=-1, + img_detail="high", + retry=10, + verbose=False, + ), + "GPT4o": partial( + api.GPT4V, + model="gpt-4o-2024-05-13", + temperature=0, + img_size=512, + img_detail="low", + retry=10, + verbose=False, + ), + "GPT4o_HIGH": partial( + api.GPT4V, + model="gpt-4o-2024-05-13", + temperature=0, + img_size=-1, + img_detail="high", + retry=10, + verbose=False, + ), + "GPT4o_20240806": partial( + api.GPT4V, + model="gpt-4o-2024-08-06", + temperature=0, + img_size=-1, + img_detail="high", + retry=10, + verbose=False, + ), + "GPT4o_20241120": partial( + api.GPT4V, + model="gpt-4o-2024-11-20", + temperature=0, + img_size=-1, + img_detail="high", + retry=10, + verbose=False, + ), + "ChatGPT4o": partial( + api.GPT4V, + model="chatgpt-4o-latest", + temperature=0, + img_size=-1, + img_detail="high", + retry=10, + verbose=False, + ), + "GPT4o_MINI": partial( + api.GPT4V, + model="gpt-4o-mini-2024-07-18", + temperature=0, + img_size=-1, + img_detail="high", + retry=10, + verbose=False, + ), + "GPT4.5": partial( + api.GPT4V, + model='gpt-4.5-preview-2025-02-27', + temperature=0, + timeout=600, + img_size=-1, + img_detail='high', + retry=10, + verbose=False, + ), + "gpt-4.1-2025-04-14": partial( + api.GPT4V, + model="gpt-4.1-2025-04-14", + temperature=0, + img_size=-1, + img_detail="high", + retry=10, + verbose=False, + ), + "gpt-4.1-mini-2025-04-14": partial( + api.GPT4V, + model="gpt-4.1-mini-2025-04-14", + temperature=0, + img_size=-1, + img_detail="high", + retry=10, + verbose=False, + ), + "gpt-4.1-nano-2025-04-14": partial( + api.GPT4V, + model="gpt-4.1-nano-2025-04-14", + temperature=0, + img_size=-1, + img_detail="high", + retry=10, + verbose=False, + ), + "gpt-5-2025-08-07": partial( + api.GPT4V, + model="gpt-5-2025-08-07", + img_detail="high", + retry=3, + verbose=False, + max_tokens=2**14, + timeout=3000, + ), + "gpt-5-mini-2025-08-07": partial( + api.GPT4V, + model="gpt-5-mini-2025-08-07", + img_detail="high", + retry=3, + verbose=False, + max_tokens=2**14, + timeout=300, + ), + "gpt-5-nano-2025-08-07": partial( + api.GPT4V, + model="gpt-5-nano-2025-08-07", + img_detail="high", + retry=3, + verbose=False, + max_tokens=2**14, + timeout=300, + ), + "gpt-5.1-2025-11-13": partial( + api.GPT4V, + model="gpt-5.1-2025-11-13", + img_detail="high", + retry=3, + verbose=False, + max_tokens=2**14, + timeout=300, + ), + # Gemini + "GeminiPro1-0": partial( + api.Gemini, model="gemini-1.0-pro", temperature=0, retry=10 + ), # now GeminiPro1-0 is only supported by vertex backend + "GeminiPro1-5": partial( + api.Gemini, model="gemini-1.5-pro", temperature=0, retry=10 + ), + "GeminiFlash1-5": partial( + api.Gemini, model="gemini-1.5-flash", temperature=0, retry=10 + ), + "GeminiPro1-5-002": partial( + api.GPT4V, model="gemini-1.5-pro-002", temperature=0, retry=10 + ), # Internal Use Only + "GeminiFlash1-5-002": partial( + api.GPT4V, model="gemini-1.5-flash-002", temperature=0, retry=10 + ), # Internal Use Only + "GeminiFlash2-0": partial( + api.Gemini, model="gemini-2.0-flash", temperature=0, retry=10 + ), + "GeminiFlashLite2-0": partial( + api.Gemini, model="gemini-2.0-flash-lite", temperature=0, retry=10 + ), + "GeminiFlash2-5": partial( + api.Gemini, model="gemini-2.5-flash", temperature=0, retry=10 + ), + "GeminiPro2-5": partial( + api.GPT4V, + model="gemini-2.5-pro", + temperature=0, + retry=10, + timeout=6000, + max_tokens=65536, + ), + "Gemini-3.1-Pro-Preview": partial( + api.GPT4V, + model="gemini-3.1-pro-preview-thinking", + retry=10, + timeout=3600, + max_tokens=65536, + img_detail='high', + ), + + # GCP Vertex AI – Claude (same GCPVertexAPI; model name selects Claude backend) + "GCP_Claude3-5Sonnet": partial( + api.GCPVertexAPI, + model="claude-3-5-sonnet-20241022", + temperature=0, + retry=10, + ), + "GCP_Claude3-5Haiku": partial( + api.GCPVertexAPI, + model="claude-3-5-haiku@20241022", + temperature=0, + retry=10, + ), + "GCP_Claude3-7Sonnet": partial( + api.GCPVertexAPI, + model="claude-3-7-sonnet@20250219", + temperature=0, + retry=10, + ), + "GCP_ClaudeSonnet4-5": partial( + api.GCPVertexAPI, + model="claude-sonnet-4-5@20250929", + temperature=0, + retry=10, + ), + "GCP_ClaudeOpus4-6": partial( + api.GCPVertexAPI, + model="claude-opus-4-6", + temperature=0, + retry=10, + ), + # Qwen-VL + "QwenVLPlus": partial(api.QwenVLAPI, model="qwen-vl-plus", temperature=0, retry=10), + "QwenVLMax": partial(api.QwenVLAPI, model="qwen-vl-max", temperature=0, retry=10), + "QwenVLMax-250408": partial(api.QwenVLAPI, model="qwen-vl-max-2025-04-08", temperature=0, retry=10), + + # Reka + "RekaEdge": partial(api.Reka, model="reka-edge-20240208"), + "RekaFlash": partial(api.Reka, model="reka-flash-20240226"), + "RekaCore": partial(api.Reka, model="reka-core-20240415"), + # Step1V + "Step1V": partial( + api.GPT4V, + model="step-1v-32k", + api_base="https://api.stepfun.com/v1/chat/completions", + temperature=0, + retry=10, + img_size=-1, + img_detail="high", + ), + "Step1.5V-mini": partial( + api.GPT4V, + model="step-1.5v-mini", + api_base="https://api.stepfun.com/v1/chat/completions", + temperature=0, + retry=10, + img_size=-1, + img_detail="high", + ), + "Step1o": partial( + api.GPT4V, + model="step-1o-vision-32k", + api_base="https://api.stepfun.com/v1/chat/completions", + temperature=0, + retry=10, + img_size=-1, + img_detail="high", + ), + "Step3-VL-10B_api": partial( + api.LMDeployAPI, + api_base="http://0.0.0.0:8000/v1/chat/completions", + model='Step3-VL-10B', + temperature=1.0, + max_tokens=40960, + repetition_penalty=1.0, + presence_penalty=0.0, + top_p=0.95, + top_k=20, + retry=6, + timeout=1800, + ), + # Yi-Vision + "Yi-Vision": partial( + api.GPT4V, + model="yi-vision", + api_base="https://api.lingyiwanwu.com/v1/chat/completions", + temperature=0, + retry=10, + ), + # Together AI (set TOGETHER_API_KEY) + "Together_Llama3.2-11B-Vision": partial( + api.TogetherAPI, + model="meta-llama/Llama-3.2-11B-Vision-Instruct-Turbo", + temperature=0, + max_tokens=2048, + retry=10, + ), + "Together_Llama3.2-90B-Vision": partial( + api.TogetherAPI, + model="meta-llama/Llama-3.2-90B-Vision-Instruct-Turbo", + temperature=0, + max_tokens=2048, + retry=10, + ), + "Together_Llama4-Scout-17B": partial( + api.TogetherAPI, + model="meta-llama/Llama-4-Scout-17B-16E-Instruct", + temperature=0, + max_tokens=2048, + retry=10, + ), + "Together_Llama4-Maverick-17B": partial( + api.TogetherAPI, + model="meta-llama/Llama-4-Maverick-17B-128E-Instruct-FP8", + temperature=0, + max_tokens=2048, + retry=10, + ), + "Together_Qwen2-VL-72B": partial( + api.TogetherAPI, + model="Qwen/Qwen2-VL-72B-Instruct", + temperature=0, + max_tokens=2048, + retry=10, + ), + # MiniMax (set MINIMAX_API_KEY) + "MiniMax-M2.7": partial( + api.MiniMaxAPI, + model="MiniMax-M2.7", + temperature=0, + max_tokens=2048, + retry=10, + ), + "MiniMax-M2.5": partial( + api.MiniMaxAPI, + model="MiniMax-M2.5", + temperature=0, + max_tokens=2048, + retry=10, + ), + "MiniMax-M2.5-highspeed": partial( + api.MiniMaxAPI, + model="MiniMax-M2.5-highspeed", + temperature=0, + max_tokens=2048, + retry=10, + ), + # Claude + "Claude3V_Opus": partial( + api.Claude3V, model="claude-3-opus-20240229", temperature=0, retry=10, verbose=False + ), + "Claude3V_Sonnet": partial( + api.Claude3V, + model="claude-3-sonnet-20240229", + temperature=0, + retry=10, + verbose=False, + ), + "Claude3V_Haiku": partial( + api.Claude3V, + model="claude-3-haiku-20240307", + temperature=0, + retry=10, + verbose=False, + ), + "Claude3-5V_Sonnet": partial( + api.Claude3V, + model="claude-3-5-sonnet-20240620", + temperature=0, + retry=10, + verbose=False, + ), + "Claude3-5V_Sonnet_20241022": partial( + api.Claude3V, + model="claude-3-5-sonnet-20241022", + temperature=0, + retry=10, + verbose=False, + ), + "Claude3-7V_Sonnet": partial( + api.Claude3V, + model="claude-3-7-sonnet-20250219", + temperature=0, + retry=10, + verbose=False, + ), + "Claude4_Opus": partial( + api.Claude3V, + model="claude-4-opus-20250514", + temperature=0, + retry=10, + verbose=False, + timeout=1800 + ), + "Claude4_Sonnet": partial( + api.Claude3V, + model="claude-4-sonnet-20250514", + temperature=0, + retry=10, + verbose=False, + timeout=1800 + ), + "Claude-Opus-4-6": partial( + api.GPT4V, + model="claude-opus-4-6-thinking", + retry=10, + timeout=3600, + max_tokens=65536, + img_detail='high', + ), + "GPT-5.4-2026-03-05": partial( + api.GPT4V, + model="gpt-5.4-2026-03-05", + retry=10, + timeout=3600, + max_tokens=65536, + img_detail='high', + ), + "GPT-5.2": partial( + api.GPT4V, + model="gpt-5.2", + retry=10, + timeout=3600, + max_tokens=65536, + img_detail='high', + ), + # AWS Bedrock (Converse API; set AWS_REGION or pass region_name) + "Bedrock_Claude3-5Sonnet": partial( + api.BedrockAPI, + model_id="anthropic.claude-3-5-sonnet-20241022-v2:0", + temperature=0, + retry=10, + ), + "Bedrock_Claude3Opus": partial( + api.BedrockAPI, + model_id="anthropic.claude-3-opus-20240229-v1:0", + temperature=0, + retry=10, + ), + "Bedrock_Claude3Sonnet": partial( + api.BedrockAPI, + model_id="anthropic.claude-3-sonnet-20240229-v1:0", + temperature=0, + retry=10, + ), + "Bedrock_Claude3Haiku": partial( + api.BedrockAPI, + model_id="anthropic.claude-3-haiku-20240307-v1:0", + temperature=0, + retry=10, + ), + # GLM4V + "GLM4V": partial(api.GLMVisionAPI, model="glm4v-biz-eval", temperature=0, retry=10), + "GLM4V_PLUS": partial(api.GLMVisionAPI, model="glm-4v-plus", temperature=0, retry=10), + "GLM4V_PLUS_20250111": partial( + api.GLMVisionAPI, model="glm-4v-plus-0111", temperature=0, retry=10 + ), + # MiniMax abab + "abab6.5s": partial( + api.GPT4V, + model="abab6.5s-chat", + api_base="https://api.minimax.chat/v1/chat/completions", + temperature=0, + retry=10, + ), + "abab7-preview": partial( + api.GPT4V, + model="abab7-chat-preview", + api_base="https://api.minimax.chat/v1/chat/completions", + temperature=0, + retry=10, + ), + # CongRong + "CongRong-v1.5": partial(api.CWWrapper, model="cw-congrong-v1.5", temperature=0, retry=10), + "CongRong-v2.0": partial(api.CWWrapper, model="cw-congrong-v2.0", temperature=0, retry=10), + # SenseNova + "SenseNova-V6-Pro": partial( + api.SenseChatVisionAPI, model="SenseNova-V6-Pro", temperature=0, retry=10 + ), + "SenseNova-V6-Reasoner": partial( + api.SenseChatVisionAPI, model="SenseNova-V6-Reasoner", temperature=0, retry=10 + ), + "SenseNova-V6-5-Pro": partial( + api.SenseChatVisionAPI, model="SenseNova-V6-5-Pro", retry=10 + ), + "SenseNova-V6-5-Pro-20251215": partial( + api.SenseChatVisionV2API, model="SenseNova-V6-5-Pro-20251215", max_completion_tokens=40960, repetition_penalty=1.05, temperature=0.6, top_p=0.95, top_k=20, timeout=1800, retry=3, img_size=4096 + ), + "HunYuan-Vision": partial( + api.HunyuanVision, model="hunyuan-vision", temperature=0, retry=10 + ), + "HunYuan-Standard-Vision": partial( + api.HunyuanVision, model="hunyuan-standard-vision", temperature=0, retry=10 + ), + "HunYuan-Large-Vision": partial( + api.HunyuanVision, model="hunyuan-large-vision", temperature=0, retry=10 + ), + "BailingMM-Lite-1203": partial( + api.bailingMMAPI, model="BailingMM-Lite-1203", temperature=0, retry=10 + ), + "BailingMM-Pro-0120": partial( + api.bailingMMAPI, model="BailingMM-Pro-0120", temperature=0, retry=10 + ), + # BlueLM-2.5 + "BlueLM-2.5-3B": partial(api.BlueLM_API, model="BlueLM-2.5-3B", temperature=0, retry=3), + # JiuTian-VL + "JTVL": partial(api.JTVLChatAPI, model="jt-vl-chat", temperature=0, retry=10), + "JTVL-Mini": partial(api.JTVLChatAPI_Mini, model="jt-vl-chat-mini", temperature=0, retry=10), + "JTVL-2B": partial(api.JTVLChatAPI_2B, model="jt-vl-chat-2b", temperature=0, retry=10), + "VideoChatOnlineV2": partial(api.VideoChatOnlineV2API, model="videochatonline_v2", temperature=0, retry=10), + "Taiyi": partial(api.TaiyiAPI, model="taiyi", temperature=0, retry=10), + # TeleMM + "TeleMM": partial(api.TeleMMAPI, model="TeleAI/TeleMM", temperature=0, retry=10), + "TeleMM2.0": partial(api.TeleMM2_API, model="TeleAI/TeleMM", retry=3, timeout=600), + "TeleMM2.0Thinking": partial(api.TeleMM2Thinking_API, model="TeleAI/TeleMM", retry=3, timeout=600), + "Qwen2.5-VL-32B-Instruct-SiliconFlow": partial( + api.SiliconFlowAPI, model="Qwen/Qwen2.5-VL-32B-Instruct", temperature=0, retry=10), + "Qwen3-VL-8B--crop--arm_thinker_prompt--sglang": partial( + api.ARM_thinker, + mode="agent", + agent_repo_root="/path/to/your/ARM-Thinker", + model="Qwen/Qwen3-VL-8B-Instruct", + retry=10, + timeout=300, + api_base="http://100.97.158.184:38888/v1/chat/completions", + key="EMPTY", + temperature=0.0, + max_tokens=4096, + # agent params + max_round=16, + max_tool_response_length=4096, + tool_config_path="/path/to/your/ARM-Thinker/examples/self/multiturn/config/tool_config/image_zoom_in_tool_config.yaml", + # special for sglang server + use_role_tool=False, + system_template_type="CommonSystemTemplate", + # extra prompt to adapt to the ARM-Thinker prompt template [CommonSystemTemplate] + extra_pt="\n\n**Important Requirement:**\nThe given image is `original_image`. You must output your reasoning inside `...`. After reasoning, either output the final answer within `...` or call a tool within `...`. You may call tools multiple times across turns to assist with judgment or verification, **but only one tool per turn**. If a tool call fails, you can retry or stop and give your final answer. Once no more tool calls are needed, provide your final answer or judgment within `...`.", + ), + "Qwen3-VL-8B--crop--official_prompt--vllm": partial( + api.ARM_thinker, + mode="agent", + agent_repo_root="/path/to/your/ARM-Thinker", + model="Qwen/Qwen3-VL-8B-Instruct", + retry=10, + timeout=300, + api_base="http://100.97.203.103:40001/v1/chat/completions", + key="EMPTY", + temperature=0.0, + max_tokens=4096, + extra_pt="", + # agent params + max_round=16, + max_tool_response_length=4096, + system_template_type="Qwen3VLSystemTemplateWithTools", + tool_config_path="/path/to/your/ARM-Thinker/examples/self/multiturn/config/tool_config/image_zoom_in_tool_qwen3vl_config.yaml", + use_role_tool=True, + ), + # lmdeploy api + "lmdeploy_internvl_78B_MPO": partial( + api.LMDeployAPI, + model="InternVL2_5-78B-MPO", + api_base="http://0.0.0.0:23333/v1/chat/completions", + temperature=0, + retry=10, + timeout=100, + ), + "lmdeploy_qvq_72B_preview": partial( + api.LMDeployAPI, + model="QVQ-72B-Preview", + api_base="http://0.0.0.0:23333/v1/chat/completions", + temperature=0, + retry=10, + timeout=300, + ), + 'Taichu-VLR-3B': partial( + api.TaichuVLRAPI, + model='taichu_vlr_3b', + url="https://platform.wair.ac.cn/maas/v1/chat/completions" + ), + 'Taichu-VLR-7B': partial( + api.TaichuVLRAPI, + model='taichu_vlr_7b', + url="https://platform.wair.ac.cn/maas/v1/chat/completions" + ), + # doubao_vl + "DoubaoVL": partial( + api.DoubaoVL, model="Doubao-1.5-vision-pro", temperature=0, retry=3, verbose=False + ), + "Seed1.5-VL": partial( + api.DoubaoVL, + model="doubao-1-5-thinking-vision-pro-250428", + temperature=0, + retry=3, + verbose=False, + max_tokens=16384, + ), + "Seed1.6": partial( + api.DoubaoVL, + model="doubao-seed-1.6-250615", + temperature=0, + retry=3, + verbose=False, + max_tokens=16384, + ), + "Seed1.6-Flash": partial( + api.DoubaoVL, + model="doubao-seed-1.6-flash-250615", + temperature=0, + retry=3, + verbose=False, + max_tokens=16384, + ), + "Seed1.6-Thinking": partial( + api.DoubaoVL, + model="doubao-seed-1.6-thinking-250615", + temperature=0, + retry=3, + verbose=False, + max_tokens=16384, + ), + "Doubao-Seed-2.0-Pro-260215": partial( + api.GPT4V, + model="doubao-seed-2-0-pro-260215", + retry=3, + timeout=1200, + max_tokens=32768, + ), + # Shopee MUG-U + 'MUG-U-7B': partial( + api.MUGUAPI, + model='MUG-U', + temperature=0, + retry=10, + verbose=False, + timeout=300), + # grok + "grok-vision-beta": partial( + api.GPT4V, + model="grok-vision-beta", + api_base="https://api.x.ai/v1/chat/completions", + temperature=0, + retry=10, + ), + "grok-2-vision-1212": partial( + api.GPT4V, + model="grok-2-vision", + api_base="https://api.x.ai/v1/chat/completions", + temperature=0, + retry=10, + ), + "grok-4-0709": partial( + api.GPT4V, + model="grok-4-0709", + api_base="https://api.x.ai/v1/chat/completions", + temperature=0, + retry=3, + timeout=1200, + max_tokens=16384 + ), + "Grok-4.1-Fast": partial( + api.GPT4V, + model="grok-4-1-fast-reasoning", + retry=3, + timeout=1200, + max_tokens=16384, + ), + # kimi + "moonshot-v1-8k": partial( + api.GPT4V, + model="moonshot-v1-8k-vision-preview", + api_base="https://api.moonshot.cn/v1/chat/completions", + temperature=0, + retry=10, + ), + "moonshot-v1-32k": partial( + api.GPT4V, + model="moonshot-v1-32k-vision-preview", + api_base="https://api.moonshot.cn/v1/chat/completions", + temperature=0, + retry=10, + ), + "moonshot-v1-128k": partial( + api.GPT4V, + model="moonshot-v1-128k-vision-preview", + api_base="https://api.moonshot.cn/v1/chat/completions", + temperature=0, + retry=10, + ), + 'ernie4.5-turbo': partial( + api.GPT4V, + model='ernie-4.5-turbo-vl-32k', + temperature=0, + retry=3, + max_tokens=12000, + ), + 'ernie4.5-a3b': partial( + api.GPT4V, + model='ernie-4.5-vl-28b-a3b', + temperature=0, + retry=3, + max_tokens=8000, + ), + "360zhinao3-vl": + partial( + api.GPT4V, + model="360zhinao3-vl", + api_base="https://api.360.cn/v1/chat/completions", + temperature=0.6, + top_p=0.95, + top_k=20, + thinking_budget=18000, + max_tokens=24000, + presence_penalty=0.0, + frequency_penalty=0.0, + retry=3, + ), +} + +api_models['gpt-5'] = cp.deepcopy(api_models['gpt-5-2025-08-07']) +api_models['gpt-5-mini'] = cp.deepcopy(api_models['gpt-5-mini-2025-08-07']) +api_models['gpt-5-nano'] = cp.deepcopy(api_models['gpt-5-nano-2025-08-07']) + +emu_series = { + "emu2_chat": partial(vlm.Emu, model_path="BAAI/Emu2-Chat"), + "emu3_chat": partial(vlm.Emu3_chat, model_path="BAAI/Emu3-Chat"), + "emu3_gen": partial(vlm.Emu3_gen, model_path="BAAI/Emu3-Gen"), +} + +granite_vision_series = { + 'granite_vision_3.1_2b_preview': partial(vlm.GraniteVision3, model_path="ibm-granite/granite-vision-3.1-2b-preview"), + 'granite_vision_3.2_2b': partial(vlm.GraniteVision3, model_path="ibm-granite/granite-vision-3.2-2b"), + 'granite_vision_3.3_2b': partial(vlm.GraniteVision3, model_path="ibm-granite/granite-vision-3.3-2b"), +} + +mmalaya_series = { + "MMAlaya": partial(vlm.MMAlaya, model_path="DataCanvas/MMAlaya"), + "MMAlaya2": partial(vlm.MMAlaya2, model_path="DataCanvas/MMAlaya2"), +} + +minicpm_series = { + "MiniCPM-V": partial(vlm.MiniCPM_V, model_path="openbmb/MiniCPM-V"), + "MiniCPM-V-2": partial(vlm.MiniCPM_V, model_path="openbmb/MiniCPM-V-2"), + "MiniCPM-Llama3-V-2_5": partial( + vlm.MiniCPM_Llama3_V, model_path="openbmb/MiniCPM-Llama3-V-2_5" + ), + "MiniCPM-V-2_6": partial(vlm.MiniCPM_V_2_6, model_path="openbmb/MiniCPM-V-2_6"), + "MiniCPM-o-2_6": partial(vlm.MiniCPM_o_2_6, model_path="openbmb/MiniCPM-o-2_6"), + "MiniCPM-V-4": partial(vlm.MiniCPM_V_4, model_path="openbmb/MiniCPM-V-4"), + "MiniCPM-V-4_5": partial(vlm.MiniCPM_V_4_5, model_path="openbmb/MiniCPM-V-4_5"), + "MiniCPM-o-4_5": partial(vlm.MiniCPM_o_4_5, model_path="openbmb/MiniCPM-o-4_5"), + "MiniCPM-o-4_5_api": partial( + api.LMDeployAPI, + api_base="http://0.0.0.0:8000/v1/chat/completions", + model='MiniCPM-o-4_5', + temperature=0.7, + top_p=0.8, + top_k=100, + repetition_penalty=1.02, + max_tokens=32768, + retry=6, + timeout=1800, + ), +} + +xtuner_series = { + "llava-internlm2-7b": partial( + vlm.LLaVA_XTuner, + llm_path="internlm/internlm2-chat-7b", + llava_path="xtuner/llava-internlm2-7b", + visual_select_layer=-2, + prompt_template="internlm2_chat", + ), + "llava-internlm2-20b": partial( + vlm.LLaVA_XTuner, + llm_path="internlm/internlm2-chat-20b", + llava_path="xtuner/llava-internlm2-20b", + visual_select_layer=-2, + prompt_template="internlm2_chat", + ), + "llava-internlm-7b": partial( + vlm.LLaVA_XTuner, + llm_path="internlm/internlm-chat-7b", + llava_path="xtuner/llava-internlm-7b", + visual_select_layer=-2, + prompt_template="internlm_chat", + ), + "llava-v1.5-7b-xtuner": partial( + vlm.LLaVA_XTuner, + llm_path="lmsys/vicuna-7b-v1.5", + llava_path="xtuner/llava-v1.5-7b-xtuner", + visual_select_layer=-2, + prompt_template="vicuna", + ), + "llava-v1.5-13b-xtuner": partial( + vlm.LLaVA_XTuner, + llm_path="lmsys/vicuna-13b-v1.5", + llava_path="xtuner/llava-v1.5-13b-xtuner", + visual_select_layer=-2, + prompt_template="vicuna", + ), + "llava-llama-3-8b": partial( + vlm.LLaVA_XTuner, + llm_path="xtuner/llava-llama-3-8b-v1_1", + llava_path="xtuner/llava-llama-3-8b-v1_1", + visual_select_layer=-2, + prompt_template="llama3_chat", + ), +} + +qwen_series = { + "qwen_base": partial(vlm.QwenVL, model_path="Qwen/Qwen-VL"), + "qwen_chat": partial(vlm.QwenVLChat, model_path="Qwen/Qwen-VL-Chat"), + "monkey": partial(vlm.Monkey, model_path="echo840/Monkey"), + "monkey-chat": partial(vlm.MonkeyChat, model_path="echo840/Monkey-Chat"), + "minimonkey": partial(vlm.MiniMonkey, model_path="mx262/MiniMonkey"), +} + +thyme_series = { + "Thyme-7B": partial(vlm.Thyme, model_path="Kwai-Keye/Thyme-RL") +} + +llava_series = { + "llava_v1.5_7b": partial(vlm.LLaVA, model_path="liuhaotian/llava-v1.5-7b"), + "llava_v1.5_13b": partial(vlm.LLaVA, model_path="liuhaotian/llava-v1.5-13b"), + "llava_v1_7b": partial(vlm.LLaVA, model_path=LLAVA_V1_7B_MODEL_PTH), + "sharegpt4v_7b": partial(vlm.LLaVA, model_path="Lin-Chen/ShareGPT4V-7B"), + "sharegpt4v_13b": partial(vlm.LLaVA, model_path="Lin-Chen/ShareGPT4V-13B"), + "llava_next_vicuna_7b": partial( + vlm.LLaVA_Next, model_path="llava-hf/llava-v1.6-vicuna-7b-hf" + ), + "llava_next_vicuna_13b": partial( + vlm.LLaVA_Next, model_path="llava-hf/llava-v1.6-vicuna-13b-hf" + ), + "llava_next_mistral_7b": partial( + vlm.LLaVA_Next, model_path="llava-hf/llava-v1.6-mistral-7b-hf" + ), + "llava_next_yi_34b": partial(vlm.LLaVA_Next, model_path="llava-hf/llava-v1.6-34b-hf"), + "llava_next_llama3": partial( + vlm.LLaVA_Next, model_path="llava-hf/llama3-llava-next-8b-hf" + ), + "llava_next_72b": partial(vlm.LLaVA_Next, model_path="llava-hf/llava-next-72b-hf"), + "llava_next_110b": partial(vlm.LLaVA_Next, model_path="llava-hf/llava-next-110b-hf"), + "llava_next_qwen_32b": partial( + vlm.LLaVA_Next2, model_path="lmms-lab/llava-next-qwen-32b" + ), + "llava_next_interleave_7b": partial( + vlm.LLaVA_Next, model_path="llava-hf/llava-interleave-qwen-7b-hf" + ), + "llava_next_interleave_7b_dpo": partial( + vlm.LLaVA_Next, model_path="llava-hf/llava-interleave-qwen-7b-dpo-hf" + ), + "llava-onevision-qwen2-0.5b-ov-hf": partial( + vlm.LLaVA_OneVision_HF, model_path="llava-hf/llava-onevision-qwen2-0.5b-ov-hf" + ), + "llava-onevision-qwen2-0.5b-si-hf": partial( + vlm.LLaVA_OneVision_HF, model_path="llava-hf/llava-onevision-qwen2-0.5b-si-hf" + ), + "llava-onevision-qwen2-7b-ov-hf": partial( + vlm.LLaVA_OneVision_HF, model_path="llava-hf/llava-onevision-qwen2-7b-ov-hf" + ), + "llava-onevision-qwen2-7b-si-hf": partial( + vlm.LLaVA_OneVision_HF, model_path="llava-hf/llava-onevision-qwen2-7b-si-hf" + ), + "llava_onevision_qwen2_0.5b_si": partial( + vlm.LLaVA_OneVision, model_path="lmms-lab/llava-onevision-qwen2-0.5b-si" + ), + "llava_onevision_qwen2_7b_si": partial( + vlm.LLaVA_OneVision, model_path="lmms-lab/llava-onevision-qwen2-7b-si" + ), + "llava_onevision_qwen2_72b_si": partial( + vlm.LLaVA_OneVision, model_path="lmms-lab/llava-onevision-qwen2-72b-si" + ), + "llava_onevision_qwen2_0.5b_ov": partial( + vlm.LLaVA_OneVision, model_path="lmms-lab/llava-onevision-qwen2-0.5b-ov" + ), + "llava_onevision_qwen2_7b_ov": partial( + vlm.LLaVA_OneVision, model_path="lmms-lab/llava-onevision-qwen2-7b-ov" + ), + "llava_onevision_qwen2_72b_ov": partial( + vlm.LLaVA_OneVision, model_path="lmms-lab/llava-onevision-qwen2-72b-ov-sft" + ), + "Aquila-VL-2B": partial(vlm.LLaVA_OneVision, model_path="BAAI/Aquila-VL-2B-llava-qwen"), + "llava_video_qwen2_7b": partial( + vlm.LLaVA_OneVision, model_path="lmms-lab/LLaVA-Video-7B-Qwen2" + ), + "llava_video_qwen2_72b": partial( + vlm.LLaVA_OneVision, model_path="lmms-lab/LLaVA-Video-72B-Qwen2" + ), + "LLaVA-OneVision-1.5-8B-Instruct": partial( + vlm.LLaVA_OneVision_1_5, model_path="lmms-lab/LLaVA-OneVision-1.5-8B-Instruct", max_new_tokens=8192 + ), +} + +varco_vision_series = { + "varco-vision-hf": partial( + vlm.LLaVA_OneVision_HF, model_path="NCSOFT/VARCO-VISION-14B-HF" + ), + "varco-vision-2-1.7b": partial( + vlm.VarcoVision, model_path="NCSOFT/VARCO-VISION-2.0-1.7B" + ), + "varco-vision-2-14b": partial( + vlm.VarcoVision, model_path="NCSOFT/VARCO-VISION-2.0-14B" + ), +} + +vita_series = { + "vita": partial(vlm.VITA, model_path="VITA-MLLM/VITA", root=VITA_ROOT), + "vita_qwen2": partial(vlm.VITAQwen2, model_path="VITA-MLLM/VITA-1.5", root=VITA_ROOT), +} + +long_vita_series = { + "Long-VITA-16K": partial( + vlm.LongVITA, model_path="VITA-MLLM/Long-VITA-16K_HF", max_num_frame=128 + ), + "Long-VITA-128K": partial( + vlm.LongVITA, model_path="VITA-MLLM/Long-VITA-128K_HF", max_num_frame=256 + ), + "Long-VITA-1M": partial( + vlm.LongVITA, model_path="VITA-MLLM/Long-VITA-1M_HF", max_num_frame=256 + ), +} + +interns1_mini = { + "Intern-S1-mini": partial( + vlm.InternS1Chat, model_path="/mnt/shared-storage-user/mllm/lijinsong/models/Intern-S1-mini/" + ), +} + +internvl = { + "InternVL-Chat-V1-1": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL-Chat-V1-1", version="V1.1" + ), + "InternVL-Chat-V1-2": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL-Chat-V1-2", version="V1.2" + ), + "InternVL-Chat-V1-2-Plus": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL-Chat-V1-2-Plus", version="V1.2" + ), + "InternVL-Chat-V1-5": partial( + vlm.InternVLChat, + model_path="OpenGVLab/InternVL-Chat-V1-5", + version="V1.5", + ) +} + +mini_internvl = { + "Mini-InternVL-Chat-2B-V1-5": partial( + vlm.InternVLChat, model_path="OpenGVLab/Mini-InternVL-Chat-2B-V1-5", version="V1.5" + ), + "Mini-InternVL-Chat-4B-V1-5": partial( + vlm.InternVLChat, model_path="OpenGVLab/Mini-InternVL-Chat-4B-V1-5", version="V1.5" + ), +} + +internvl2 = { + "InternVL2-1B": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL2-1B", version="V2.0" + ), + "InternVL2-2B": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL2-2B", version="V2.0" + ), + "InternVL2-4B": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL2-4B", version="V2.0" + ), + "InternVL2-8B": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL2-8B", version="V2.0" + ), + "InternVL2-26B": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL2-26B", version="V2.0" + ), + "InternVL2-40B": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL2-40B", version="V2.0" + ), + "InternVL2-76B": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL2-Llama3-76B", version="V2.0" + ), + "InternVL2-8B-MPO": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL2-8B-MPO", version="V2.0" + ), + "InternVL2-8B-MPO-CoT": partial( + vlm.InternVLChat, + model_path="OpenGVLab/InternVL2-8B-MPO", + version="V2.0", + use_mpo_prompt=True, + ), +} + +internvl2_5 = { + "InternVL2_5-1B": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL2_5-1B", version="V2.0" + ), + "InternVL2_5-2B": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL2_5-2B", version="V2.0" + ), + "QTuneVL1-2B": partial( + vlm.InternVLChat, model_path="hanchaow/QTuneVL1-2B", version="V2.0" + ), + "InternVL2_5-4B": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL2_5-4B", version="V2.0" + ), + "InternVL2_5-8B": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL2_5-8B", version="V2.0" + ), + "InternVL2_5-26B": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL2_5-26B", version="V2.0" + ), + "InternVL2_5-38B": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL2_5-38B", version="V2.0" + ), + "InternVL2_5-78B": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL2_5-78B", version="V2.0" + ), + # InternVL2.5 series with Best-of-N evaluation + "InternVL2_5-8B-BoN-8": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL2_5-8B", version="V2.0", + best_of_n=8, reward_model_path="OpenGVLab/VisualPRM-8B", + ), +} + +internvl2_5_mpo = { + "InternVL2_5-1B-MPO": partial( + vlm.InternVLChat, + model_path="OpenGVLab/InternVL2_5-1B-MPO", + version="V2.0", + use_mpo_prompt=True, + ), + "InternVL2_5-2B-MPO": partial( + vlm.InternVLChat, + model_path="OpenGVLab/InternVL2_5-2B-MPO", + version="V2.0", + use_mpo_prompt=True, + ), + "InternVL2_5-4B-MPO": partial( + vlm.InternVLChat, + model_path="OpenGVLab/InternVL2_5-4B-MPO", + version="V2.0", + use_mpo_prompt=True, + ), + "InternVL2_5-8B-MPO": partial( + vlm.InternVLChat, + model_path="OpenGVLab/InternVL2_5-8B-MPO", + version="V2.0", + use_mpo_prompt=True, + ), + "InternVL2_5-26B-MPO": partial( + vlm.InternVLChat, + model_path="OpenGVLab/InternVL2_5-26B-MPO", + version="V2.0", + use_mpo_prompt=True, + ), + "InternVL2_5-38B-MPO": partial( + vlm.InternVLChat, + model_path="OpenGVLab/InternVL2_5-38B-MPO", + version="V2.0", + use_mpo_prompt=True, + ), + "InternVL2_5-78B-MPO": partial( + vlm.InternVLChat, + model_path="OpenGVLab/InternVL2_5-78B-MPO", + version="V2.0", + use_mpo_prompt=True, + ), + "InternVL2_5-8B-GUI": partial( + vlm.InternVLChat, + model_path="/fs-computility/mllm1/shared/zhaoxiangyu/models/internvl2_5_8b_internlm2_5_7b_dynamic_res_stage1", + version="V2.0", + max_new_tokens=512, + screen_parse=False, + ), + "InternVL3-7B-GUI": partial( + vlm.InternVLChat, + model_path="/fs-computility/mllm1/shared/zhaoxiangyu/GUI/checkpoints/internvl3_7b_dynamic_res_stage1_56/", + version="V2.0", + max_new_tokens=512, + screen_parse=False, + ), +} + +internvl3 = { + "InternVL3-1B": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL3-1B", version="V2.0" + ), + "InternVL3-2B": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL3-2B", version="V2.0" + ), + "InternVL3-8B": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL3-8B", version="V2.0", + ), + "InternVL3-9B": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL3-9B", version="V2.0" + ), + "InternVL3-14B": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL3-14B", version="V2.0" + ), + "InternVL3-38B": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL3-38B", version="V2.0" + ), + "InternVL3-78B": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL3-78B", version="V2.0" + ), +} + +internvl3_5 = { + "InternVL3_5-1B": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL3_5-1B", version="V2.0" + ), + "InternVL3_5-2B": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL3_5-2B", version="V2.0" + ), + "InternVL3_5-4B": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL3_5-4B", version="V2.0" + ), + "InternVL3_5-8B": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL3_5-8B", version="V2.0" + ), + "InternVL3_5-14B": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL3_5-14B", version="V2.0" + ), + "InternVL3_5-GPT-OSS-20B-A4B-Preview": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL3_5-GPT-OSS-20B-A4B-Preview", version="V2.0" + ), + "InternVL3_5-30B-A3B": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL3_5-30B-A3B", version="V2.0" + ), + "InternVL3_5-38B": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL3_5-38B", version="V2.0" + ), + "InternVL3_5-241B-A28B": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL3_5-241B-A28B", version="V2.0", + max_new_tokens=16384, + ), + + "InternVL3_5-1B-Thinking": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL3_5-1B", use_lmdeploy=True, + max_new_tokens=2**16, cot_prompt_version="r1", do_sample=True, version="V2.0" + ), + "InternVL3_5-2B-Thinking": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL3_5-2B", use_lmdeploy=True, + max_new_tokens=2**16, cot_prompt_version="r1", do_sample=True, version="V2.0" + ), + "InternVL3_5-4B-Thinking": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL3_5-4B", use_lmdeploy=True, + max_new_tokens=2**16, cot_prompt_version="r1", do_sample=True, version="V2.0" + ), + "InternVL3_5-8B-Thinking": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL3_5-8B", use_lmdeploy=True, + max_new_tokens=2**16, cot_prompt_version="r1", do_sample=True, version="V2.0" + ), + "InternVL3_5-14B-Thinking": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL3_5-14B", use_lmdeploy=True, + max_new_tokens=2**16, cot_prompt_version="r1", do_sample=True, version="V2.0" + ), + "InternVL3_5-GPT-OSS-20B-A4B-Preview-Thinking": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL3_5-GPT-OSS-20B-A4B-Preview", use_lmdeploy=True, + max_new_tokens=2**16, cot_prompt_version="r1", do_sample=True, version="V2.0" + ), + "InternVL3_5-30B-A3B-Thinking": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL3_5-30B-A3B", use_lmdeploy=True, + max_new_tokens=2**16, cot_prompt_version="r1", do_sample=True, version="V2.0" + ), + "InternVL3_5-38B-Thinking": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL3_5-38B", use_lmdeploy=True, + max_new_tokens=2**16, cot_prompt_version="r1", do_sample=True, version="V2.0" + ), + "InternVL3_5-241B-A28B-Thinking": partial( + vlm.InternVLChat, model_path="OpenGVLab/InternVL3_5-241B-A28B", use_lmdeploy=True, + max_new_tokens=2**16, cot_prompt_version="r1", do_sample=True, version="V2.0" + ), + "InternVL3_5-241B-A28B-Thinking-api": partial( + api.LMDeployAPI, model="internvl-3.5-241b", use_lmdeploy=True, + max_new_tokens=2**16, cot_prompt_version="r1", do_sample=True, version="V2.0" + ), +} + +qwen3vl_series = { + "Qwen3-VL-235B-A22B-Instruct": partial( + vlm.Qwen3VLChat, + model_path="Qwen/Qwen3-VL-235B-A22B-Instruct", + use_custom_prompt=False, + use_vllm=True, + temperature=0.7, + max_new_tokens=16384, + repetition_penalty=1.0, + presence_penalty=1.5, + top_p=0.8, + top_k=20 + ), + "Qwen3-VL-235B-A22B-Thinking": partial( + vlm.Qwen3VLChat, + model_path="Qwen/Qwen3-VL-235B-A22B-Thinking", + use_custom_prompt=False, + use_vllm=True, + temperature=1.0, + max_new_tokens=40960, + repetition_penalty=1.0, + presence_penalty=0.0, + top_p=0.95, + top_k=20 + ), + "Qwen3-VL-30B-A3B-Instruct": partial( + vlm.Qwen3VLChat, + model_path="Qwen/Qwen3-VL-30B-A3B-Instruct", + use_custom_prompt=False, + use_vllm=True, + temperature=0.7, + max_new_tokens=16384, + repetition_penalty=1.0, + presence_penalty=1.5, + top_p=0.8, + top_k=20 + ), + "Qwen3-VL-30B-A3B-Thinking": partial( + vlm.Qwen3VLChat, + model_path="Qwen/Qwen3-VL-30B-A3B-Thinking", + use_custom_prompt=False, + use_vllm=True, + temperature=1.0, + max_new_tokens=40960, + repetition_penalty=1.0, + presence_penalty=0.0, + top_p=0.95, + top_k=20 + ), + "Qwen3-VL-8B-Thinking": partial( + vlm.Qwen3VLChat, + model_path="Qwen/Qwen3-VL-8B-Thinking", + use_custom_prompt=False, + use_vllm=True, + temperature=1.0, + max_new_tokens=40960, + repetition_penalty=1.0, + presence_penalty=0.0, + top_p=0.95, + top_k=20 + ), + "Qwen3-VL-4B-Thinking": partial( + vlm.Qwen3VLChat, + model_path="Qwen/Qwen3-VL-4B-Thinking", + use_custom_prompt=False, + use_vllm=True, + temperature=1.0, + max_new_tokens=40960, + repetition_penalty=1.0, + presence_penalty=0.0, + top_p=0.95, + top_k=20 + ), + "Qwen3-VL-8B-Instruct": partial( + vlm.Qwen3VLChat, + model_path="Qwen/Qwen3-VL-8B-Instruct", + use_custom_prompt=False, + use_vllm=True, + temperature=0.7, + max_new_tokens=16384, + repetition_penalty=1.0, + presence_penalty=1.5, + top_p=0.8, + top_k=20 + ), + "Qwen3-VL-4B-Instruct": partial( + vlm.Qwen3VLChat, + model_path="Qwen/Qwen3-VL-4B-Instruct", + use_custom_prompt=False, + use_vllm=True, + temperature=0.7, + max_new_tokens=16384, + repetition_penalty=1.0, + presence_penalty=1.5, + top_p=0.8, + top_k=20 + ), + "Qwen3-VL-2B-Instruct": partial( + vlm.Qwen3VLChat, + model_path="Qwen/Qwen3-VL-2B-Instruct", + use_custom_prompt=False, + use_vllm=True, + temperature=0.7, + max_new_tokens=16384, + repetition_penalty=1.0, + presence_penalty=1.5, + top_p=0.8, + top_k=20 + ), + "Qwen3-VL-32B-Instruct": partial( + vlm.Qwen3VLChat, + model_path="Qwen/Qwen3-VL-32B-Instruct", + use_custom_prompt=False, + use_vllm=True, + temperature=0.7, + max_new_tokens=16384, + repetition_penalty=1.0, + presence_penalty=1.5, + top_p=0.8, + top_k=20 + + ), + "Qwen3-VL-2B-Thinking": partial( + vlm.Qwen3VLChat, + model_path="Qwen/Qwen3-VL-2B-Thinking", + use_custom_prompt=False, + use_vllm=True, + temperature=1.0, + max_new_tokens=40960, + repetition_penalty=1.0, + presence_penalty=0.0, + top_p=0.95, + top_k=20 + ), + "Qwen3-VL-32B-Thinking": partial( + vlm.Qwen3VLChat, + model_path="Qwen/Qwen3-VL-32B-Thinking", + use_custom_prompt=False, + use_vllm=False, + temperature=1.0, + max_new_tokens=40960, + repetition_penalty=1.0, + presence_penalty=0.0, + top_p=0.95, + top_k=20 + ), + "Qwen3-Omni-30B-A3B-Instruct": partial( + vlm.Qwen3VLChat, + model_path="Qwen/Qwen3-Omni-30B-A3B-Instruct", + use_custom_prompt=False, + use_vllm=True, + temperature=0.6, + top_p=0.95, + top_k=20, + max_new_tokens=16384, + ), + "Qwen3-Omni-30B-A3B-Thinking": partial( + vlm.Qwen3VLChat, + model_path="Qwen/Qwen3-Omni-30B-A3B-Thinking", + use_custom_prompt=False, + use_vllm=True, + temperature=0.6, + top_p=0.95, + top_k=20, + max_new_tokens=16384, + ), + "Qwen3-Omni-30B-A3B-Captioner": partial( + vlm.Qwen3VLChat, + model_path="Qwen/Qwen3-Omni-30B-A3B-Captioner", + use_custom_prompt=False, + use_vllm=True, + temperature=0.6, + top_p=0.95, + top_k=20, + max_new_tokens=16384, + ), + +} + +qwen3_5_series = { + # vllm serve command example: + # vllm serve Qwen/Qwen3.5-122B-A10B --port 8000 --tensor-parallel-size 8 --max-model-len 262144 --reasoning-parser qwen3 + "Qwen3.5-35B-A3B_api": partial( + api.LMDeployAPI, + model="Qwen/Qwen3.5-122B-A10B", + api_base="http://0.0.0.0:8000/v1/chat/completions", + temperature=0.6, + top_p=0.95, + top_k=20, + presence_penalty=1.5, + repetition_penalty=1.0, + max_new_tokens=32768, + retry=6, + timeout=1800, + ), + "Qwen3.5-397B-A17B_api": partial( + api.LMDeployAPI, + api_base="http://0.0.0.0:8000/v1/chat/completions", + model='Qwen3.5-397B', + temperature=0.6, + top_p=0.95, + top_k=20, + presence_penalty=1.5, + repetition_penalty=1.0, + max_tokens=32768, + retry=10, + timeout=1800, + ), + "Qwen3.5-122B-A10B_ThinkMode_api": partial( + api.LMDeployAPI, + api_base="http://0.0.0.0:8000/v1/chat/completions", + model='Qwen3.5-35B', + temperature=1.0, + top_p=0.95, + top_k=20, + min_p=0.0, + presence_penalty=1.5, + repetition_penalty=1.0, + max_tokens=81920, + retry=10, + timeout=900, + ), + "Qwen3.5-122B-A10B_InstructMode_api": partial( + api.LMDeployAPI, + model="Qwen/Qwen3.5-122B-A10B", + api_base="http://0.0.0.0:8000/v1/chat/completions", + temperature=1.0, + top_p=0.95, + top_k=20, + min_p=0.0, + presence_penalty=1.5, + repetition_penalty=1.0, + max_tokens=81920, + retry=10, + timeout=900, + chat_template_kwargs={"enable_thinking": False}, + ), + "Qwen3.5-397B-A17B": partial( + vlm.Qwen3VLChat, + model_path="Qwen/Qwen3.5-397B-A17B", + use_custom_prompt=False, + use_vllm=True, + temperature=1.0, + top_p=0.95, + top_k=20, + presence_penalty=1.5, + max_new_tokens=32768, + ), + "Qwen3.5-122B-A10B": partial( + vlm.Qwen3VLChat, + model_path="Qwen/Qwen3.5-122B-A10B", + use_custom_prompt=False, + use_vllm=True, + temperature=1.0, + top_p=0.95, + top_k=20, + presence_penalty=1.5, + max_new_tokens=32768, + ), + "Qwen3.5-35B-A3B": partial( + vlm.Qwen3VLChat, + model_path="Qwen/Qwen3.5-35B-A3B", + use_custom_prompt=False, + use_vllm=True, + temperature=1.0, + top_p=0.95, + top_k=20, + presence_penalty=1.5, + max_new_tokens=32768, + ), + "Qwen3.5-27B": partial( + vlm.Qwen3VLChat, + model_path="Qwen/Qwen3.5-27B", + use_custom_prompt=False, + use_vllm=True, + temperature=1.0, + top_p=0.95, + top_k=20, + presence_penalty=1.5, + max_new_tokens=32768, + ), +} + +sail_series = { + "SAIL-VL-2B": partial(vlm.SailVL, model_path="BytedanceDouyinContent/SAIL-VL-2B"), + "SAIL-VL-1.5-2B": partial(vlm.SailVL, model_path="BytedanceDouyinContent/SAIL-VL-1d5-2B", use_msac = True), + "SAIL-VL-1.5-8B": partial(vlm.SailVL, model_path="BytedanceDouyinContent/SAIL-VL-1d5-8B", use_msac = True), + "SAIL-VL-1.6-8B": partial(vlm.SailVL, model_path="BytedanceDouyinContent/SAIL-VL-1d6-8B", use_msac = True), + "SAIL-VL-1.7-Thinking-2B-2507": partial(vlm.SailVL, model_path="BytedanceDouyinContent/SAIL-VL-1d7-Thinking-2B-2507", use_msac = True, use_cot=True, max_new_tokens=4096), + "SAIL-VL-1.7-Thinking-8B-2507": partial(vlm.SailVL, model_path="BytedanceDouyinContent/SAIL-VL-1d7-Thinking-8B-2507", use_msac = True, use_cot=True, max_new_tokens=4096), + "SAIL-VL2-2B": partial(vlm.SailVL, model_path="BytedanceDouyinContent/SAIL-VL2-2B", use_msac = True), + "SAIL-VL2-8B": partial(vlm.SailVL, model_path="BytedanceDouyinContent/SAIL-VL2-8B", use_msac = True), +} + +ristretto_series = { + "Ristretto-3B": partial(vlm.Ristretto, model_path="LiAutoAD/Ristretto-3B"), +} + +yivl_series = { + "Yi_VL_6B": partial(vlm.Yi_VL, model_path="01-ai/Yi-VL-6B", root=Yi_ROOT), + "Yi_VL_34B": partial(vlm.Yi_VL, model_path="01-ai/Yi-VL-34B", root=Yi_ROOT), +} + +xcomposer_series = { + "XComposer": partial(vlm.XComposer, model_path="internlm/internlm-xcomposer-vl-7b"), + "sharecaptioner": partial(vlm.ShareCaptioner, model_path="Lin-Chen/ShareCaptioner"), + "XComposer2": partial(vlm.XComposer2, model_path="internlm/internlm-xcomposer2-vl-7b"), + "XComposer2_1.8b": partial( + vlm.XComposer2, model_path="internlm/internlm-xcomposer2-vl-1_8b" + ), + "XComposer2_4KHD": partial( + vlm.XComposer2_4KHD, model_path="internlm/internlm-xcomposer2-4khd-7b" + ), + "XComposer2d5": partial( + vlm.XComposer2d5, model_path="internlm/internlm-xcomposer2d5-7b" + ), +} + +minigpt4_series = { + "MiniGPT-4-v2": partial(vlm.MiniGPT4, mode="v2", root=MiniGPT4_ROOT), + "MiniGPT-4-v1-7B": partial(vlm.MiniGPT4, mode="v1_7b", root=MiniGPT4_ROOT), + "MiniGPT-4-v1-13B": partial(vlm.MiniGPT4, mode="v1_13b", root=MiniGPT4_ROOT), +} + +idefics_series = { + "idefics_9b_instruct": partial( + vlm.IDEFICS, model_path="HuggingFaceM4/idefics-9b-instruct" + ), + "idefics_80b_instruct": partial( + vlm.IDEFICS, model_path="HuggingFaceM4/idefics-80b-instruct" + ), + "idefics2_8b": partial(vlm.IDEFICS2, model_path="HuggingFaceM4/idefics2-8b"), + # Idefics3 follows Idefics2 Pattern + "Idefics3-8B-Llama3": partial( + vlm.IDEFICS2, model_path="HuggingFaceM4/Idefics3-8B-Llama3" + ), + 'granite-docling-258M': partial( + vlm.DOCLING, model_path="ibm-granite/granite-docling-258M" + ) + +} + +smolvlm_series = { + "SmolVLM-256M": partial(vlm.SmolVLM, model_path="HuggingFaceTB/SmolVLM-256M-Instruct"), + "SmolVLM-500M": partial(vlm.SmolVLM, model_path="HuggingFaceTB/SmolVLM-500M-Instruct"), + "SmolVLM": partial(vlm.SmolVLM, model_path="HuggingFaceTB/SmolVLM-Instruct"), + "SmolVLM-DPO": partial(vlm.SmolVLM, model_path="HuggingFaceTB/SmolVLM-Instruct-DPO"), + "SmolVLM-Synthetic": partial(vlm.SmolVLM, model_path="HuggingFaceTB/SmolVLM-Synthetic"), + "SmolVLM2-256M": partial( + vlm.SmolVLM2, model_path="HuggingFaceTB/SmolVLM2-256M-Video-Instruct" + ), + "SmolVLM2-500M": partial( + vlm.SmolVLM2, model_path="HuggingFaceTB/SmolVLM2-500M-Video-Instruct" + ), + "SmolVLM2": partial(vlm.SmolVLM2, model_path="HuggingFaceTB/SmolVLM2-2.2B-Instruct"), +} + +instructblip_series = { + "instructblip_7b": partial(vlm.InstructBLIP, name="instructblip_7b"), + "instructblip_13b": partial(vlm.InstructBLIP, name="instructblip_13b"), +} + +deepseekvl_series = { + "deepseek_vl_7b": partial(vlm.DeepSeekVL, model_path="deepseek-ai/deepseek-vl-7b-chat"), + "deepseek_vl_1.3b": partial( + vlm.DeepSeekVL, model_path="deepseek-ai/deepseek-vl-1.3b-chat" + ), +} + +deepseekvl2_series = { + "deepseek_vl2_tiny": partial( + vlm.DeepSeekVL2, model_path="deepseek-ai/deepseek-vl2-tiny" + ), + "deepseek_vl2_small": partial( + vlm.DeepSeekVL2, model_path="deepseek-ai/deepseek-vl2-small" + ), + "deepseek_vl2": partial(vlm.DeepSeekVL2, model_path="deepseek-ai/deepseek-vl2"), +} + +deepseekocr_series = { + "DeepSeek-OCR": partial( + vlm.DeepSeekOCR, model_path="deepseek-ai/DeepSeek-OCR" + ), +} + +janus_series = { + "Janus-1.3B": partial(vlm.Janus, model_path="deepseek-ai/Janus-1.3B"), + "Janus-Pro-1B": partial(vlm.Janus, model_path="deepseek-ai/Janus-Pro-1B"), + "Janus-Pro-7B": partial(vlm.Janus, model_path="deepseek-ai/Janus-Pro-7B"), +} + +cogvlm_series = { + "cogvlm-grounding-generalist": partial( + vlm.CogVlm, + model_path="THUDM/cogvlm-grounding-generalist-hf", + tokenizer_name="lmsys/vicuna-7b-v1.5", + ), + "cogvlm-chat": partial( + vlm.CogVlm, model_path="THUDM/cogvlm-chat-hf", tokenizer_name="lmsys/vicuna-7b-v1.5" + ), + "cogvlm2-llama3-chat-19B": partial( + vlm.CogVlm, model_path="THUDM/cogvlm2-llama3-chat-19B" + ), + "glm-4v-9b": partial(vlm.GLM4v, model_path="THUDM/glm-4v-9b"), + "GLM4_1VThinking-9b": partial(vlm.GLMThinking, model_path="THUDM/GLM-4.1V-9B-Thinking"), + "GLM4_5V": partial(vlm.GLMThinking, model_path="THUDM/GLM-4.5V"), + "GLM4_6V": partial(vlm.GLMThinking, model_path="THUDM/GLM-4.6V"), + "GLM4_6V-api": partial( + api.LMDeployAPI, + api_base="http://0.0.0.0:8000/v1/chat/completions", + model='glm-4.6v', + temperature=0.8, + top_p=0.6, + top_k=2, + repetition_penalty=1.1, + max_tokens=16384, + retry=6, + timeout=1800, + ), +} + +wemm_series = { + "WeMM": partial(vlm.WeMM, model_path="feipengma/WeMM"), +} + +cambrian_series = { + "cambrian_8b": partial(vlm.Cambrian, model_path="nyu-visionx/cambrian-8b"), + "cambrian_13b": partial(vlm.Cambrian, model_path="nyu-visionx/cambrian-13b"), + "cambrian_34b": partial(vlm.Cambrian, model_path="nyu-visionx/cambrian-34b"), + + "cambrian-s-0.5b": partial(vlm.CambrianS, model_path="nyu-visionx/Cambrian-S-0.5B"), + "cambrian-s-1.5b": partial(vlm.CambrianS, model_path="nyu-visionx/Cambrian-S-1.5B"), + "cambrian-s-3b": partial(vlm.CambrianS, model_path="nyu-visionx/Cambrian-S-3B"), + "cambrian-s-7b": partial(vlm.CambrianS, model_path="nyu-visionx/Cambrian-S-7B"), +} + +chameleon_series = { + "chameleon_7b": partial(vlm.Chameleon, model_path="facebook/chameleon-7b"), + "chameleon_30b": partial(vlm.Chameleon, model_path="facebook/chameleon-30b"), +} + +vila_series = { + "VILA1.5-3b": partial(vlm.VILA, model_path="Efficient-Large-Model/VILA1.5-3b"), + "Llama-3-VILA1.5-8b": partial( + vlm.VILA, model_path="Efficient-Large-Model/Llama-3-VILA1.5-8b" + ), + "VILA1.5-13b": partial(vlm.VILA, model_path="Efficient-Large-Model/VILA1.5-13b"), + "VILA1.5-40b": partial(vlm.VILA, model_path="Efficient-Large-Model/VILA1.5-40b"), + "NVILA-8B": partial(vlm.NVILA, model_path="Efficient-Large-Model/NVILA-8B"), + "NVILA-15B": partial(vlm.NVILA, model_path="Efficient-Large-Model/NVILA-15B"), +} + +ovis_series = { + "Ovis1.5-Llama3-8B": partial(vlm.Ovis, model_path="AIDC-AI/Ovis1.5-Llama3-8B"), + "Ovis1.5-Gemma2-9B": partial(vlm.Ovis, model_path="AIDC-AI/Ovis1.5-Gemma2-9B"), + "Ovis1.6-Gemma2-9B": partial(vlm.Ovis1_6, model_path="AIDC-AI/Ovis1.6-Gemma2-9B"), + "Ovis1.6-Llama3.2-3B": partial(vlm.Ovis1_6, model_path="AIDC-AI/Ovis1.6-Llama3.2-3B"), + "Ovis1.6-Gemma2-27B": partial( + vlm.Ovis1_6_Plus, model_path="AIDC-AI/Ovis1.6-Gemma2-27B" + ), + "Ovis2-1B": partial(vlm.Ovis2, model_path="AIDC-AI/Ovis2-1B"), + "Ovis2-2B": partial(vlm.Ovis2, model_path="AIDC-AI/Ovis2-2B"), + "Ovis2-4B": partial(vlm.Ovis2, model_path="AIDC-AI/Ovis2-4B"), + "Ovis2-8B": partial(vlm.Ovis2, model_path="AIDC-AI/Ovis2-8B"), + "Ovis2-16B": partial(vlm.Ovis2, model_path="AIDC-AI/Ovis2-16B"), + "Ovis2-34B": partial(vlm.Ovis2, model_path="AIDC-AI/Ovis2-34B"), + "Ovis-U1-3B": partial(vlm.OvisU1, model_path="AIDC-AI/Ovis-U1-3B"), + "Ovis2.5-2B": partial(vlm.Ovis2_5, model_path="AIDC-AI/Ovis2.5-2B"), + "Ovis2.5-9B": partial(vlm.Ovis2_5, model_path="AIDC-AI/Ovis2.5-9B"), + "Ovis2.6-30B-A3B_api": partial( + api.LMDeployAPI, + api_base="http://0.0.0.0:8000/v1/chat/completions", + model='Ovis2.6-30B-A3B', + max_tokens=32768, + retry=6, + timeout=1800, + ), +} + +mantis_series = { + "Mantis-8B-siglip-llama3": partial( + vlm.Mantis, model_path="TIGER-Lab/Mantis-8B-siglip-llama3" + ), + "Mantis-8B-clip-llama3": partial( + vlm.Mantis, model_path="TIGER-Lab/Mantis-8B-clip-llama3" + ), + "Mantis-8B-Idefics2": partial(vlm.Mantis, model_path="TIGER-Lab/Mantis-8B-Idefics2"), + "Mantis-8B-Fuyu": partial(vlm.Mantis, model_path="TIGER-Lab/Mantis-8B-Fuyu"), +} + +phi3_series = { + "Phi-3-Vision": partial( + vlm.Phi3Vision, model_path="microsoft/Phi-3-vision-128k-instruct" + ), + "Phi-3.5-Vision": partial( + vlm.Phi3_5Vision, model_path="microsoft/Phi-3.5-vision-instruct" + ), +} + +phi4_series = { + 'Phi-4-Vision': partial(vlm.Phi4Multimodal, model_path='microsoft/Phi-4-multimodal-instruct'), +} + +xgen_mm_series = { + "xgen-mm-phi3-interleave-r-v1.5": partial( + vlm.XGenMM, model_path="Salesforce/xgen-mm-phi3-mini-instruct-interleave-r-v1.5" + ), + "xgen-mm-phi3-dpo-r-v1.5": partial( + vlm.XGenMM, model_path="Salesforce/xgen-mm-phi3-mini-instruct-dpo-r-v1.5" + ), +} + +hawkvl_series = { + "HawkVL-2B": partial( + vlm.HawkVL, + model_path="xjtupanda/HawkVL-2B", + min_pixels=4 * 28 * 28, + max_pixels=6800 * 28 * 28, + use_custom_prompt=True + ) +} + +qwen2vl_series = { + "Qwen-VL-Max-20250813": partial( + api.Qwen2VLAPI, + model="qwen-vl-max-2025-08-13", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + max_length=8192, + ), + "Qwen-VL-Max-0809": partial( + api.Qwen2VLAPI, + model="qwen-vl-max-0809", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + ), + "Qwen-VL-Plus-0809": partial( + api.Qwen2VLAPI, + model="qwen-vl-plus-0809", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + ), + "QVQ-72B-Preview": partial( + vlm.Qwen2VLChat, + model_path="Qwen/QVQ-72B-Preview", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + system_prompt="You are a helpful and harmless assistant. You are Qwen developed by Alibaba. You should think step-by-step.", + max_new_tokens=8192, + post_process=False, + ), + "Qwen2-VL-72B-Instruct": partial( + vlm.Qwen2VLChat, + model_path="Qwen/Qwen2-VL-72B-Instruct", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + ), + "Qwen2-VL-7B-Instruct": partial( + vlm.Qwen2VLChat, + model_path="Qwen/Qwen2-VL-7B-Instruct", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + ), + "Qwen2-VL-7B-Instruct-AWQ": partial( + vlm.Qwen2VLChat, + model_path="Qwen/Qwen2-VL-7B-Instruct-AWQ", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + ), + "Qwen2-VL-7B-Instruct-GPTQ-Int4": partial( + vlm.Qwen2VLChat, + model_path="Qwen/Qwen2-VL-7B-Instruct-GPTQ-Int4", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + ), + "Qwen2-VL-7B-Instruct-GPTQ-Int8": partial( + vlm.Qwen2VLChat, + model_path="Qwen/Qwen2-VL-7B-Instruct-GPTQ-Int8", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + ), + "Qwen2-VL-2B-Instruct": partial( + vlm.Qwen2VLChat, + model_path="Qwen/Qwen2-VL-2B-Instruct", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + ), + "Qwen2-VL-2B-Instruct-AWQ": partial( + vlm.Qwen2VLChat, + model_path="Qwen/Qwen2-VL-2B-Instruct-AWQ", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + ), + "Qwen2-VL-2B-Instruct-GPTQ-Int4": partial( + vlm.Qwen2VLChat, + model_path="Qwen/Qwen2-VL-2B-Instruct-GPTQ-Int4", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + ), + "Qwen2-VL-2B-Instruct-GPTQ-Int8": partial( + vlm.Qwen2VLChat, + model_path="Qwen/Qwen2-VL-2B-Instruct-GPTQ-Int8", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + ), + "XinYuan-VL-2B-Instruct": partial( + vlm.Qwen2VLChat, + model_path="Cylingo/Xinyuan-VL-2B", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + ), + "Qwen2.5-VL-3B-Instruct": partial( + vlm.Qwen2VLChat, + model_path="Qwen/Qwen2.5-VL-3B-Instruct", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + use_custom_prompt=False, + ), + "Qwen2.5-VL-3B-Instruct-AWQ": partial( + vlm.Qwen2VLChat, + model_path="Qwen/Qwen2.5-VL-3B-Instruct-AWQ", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + use_custom_prompt=False, + ), + "Qwen2.5-VL-7B-Instruct": partial( + vlm.Qwen2VLChat, + model_path="Qwen/Qwen2.5-VL-7B-Instruct", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + use_custom_prompt=False, + ), + "Qwen2.5-VL-7B-Instruct-ForVideo": partial( + vlm.Qwen2VLChat, + model_path="Qwen/Qwen2.5-VL-7B-Instruct", + min_pixels=128 * 28 * 28, + max_pixels=768 * 28 * 28, + total_pixels=24576 * 28 * 28, + use_custom_prompt=False, + ), + "Qwen2.5-VL-7B-Instruct-AWQ": partial( + vlm.Qwen2VLChat, + model_path="Qwen/Qwen2.5-VL-7B-Instruct-AWQ", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + use_custom_prompt=False, + ), + "Qwen2.5-VL-32B-Instruct": partial( + vlm.Qwen2VLChat, + model_path="Qwen/Qwen2.5-VL-32B-Instruct", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + use_custom_prompt=False, + ), + "Qwen2.5-VL-72B-Instruct": partial( + vlm.Qwen2VLChat, + model_path="Qwen/Qwen2.5-VL-72B-Instruct", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + use_custom_prompt=False, + ), + "MiMo-VL-7B-SFT": partial( + vlm.Qwen2VLChat, + model_path="XiaomiMiMo/MiMo-VL-7B-SFT", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + use_custom_prompt=False, + use_lmdeploy=True + ), + "MiMo-VL-7B-RL": partial( + vlm.Qwen2VLChat, + model_path="XiaomiMiMo/MiMo-VL-7B-RL", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + use_custom_prompt=False, + use_lmdeploy=True + ), + "Qwen2.5-VL-72B-Instruct-ForVideo": partial( + vlm.Qwen2VLChat, + model_path="Qwen/Qwen2.5-VL-72B-Instruct", + min_pixels=128 * 28 * 28, + max_pixels=768 * 28 * 28, + total_pixels=24576 * 28 * 28, + use_custom_prompt=False, + ), + "Qwen2.5-VL-72B-Instruct-AWQ": partial( + vlm.Qwen2VLChat, + model_path="Qwen/Qwen2.5-VL-72B-Instruct-AWQ", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + use_custom_prompt=False, + ), + "Qwen2.5-Omni-7B-ForVideo": partial( + vlm.Qwen2VLChat, + model_path="Qwen/Qwen2.5-Omni-7B", + min_pixels=128 * 28 * 28, + max_pixels=768 * 28 * 28, + total_pixels=24576 * 28 * 28, + use_custom_prompt=False, + use_audio_in_video=True, # set use audio in video + ), + "Qwen2.5-Omni-7B": partial( + vlm.Qwen2VLChat, + model_path="Qwen/Qwen2.5-Omni-7B", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + use_custom_prompt=False, + ), + # Qwen3-VL series (local inference) + "Qwen3-VL-8B-Instruct": partial( + vlm.Qwen2VLChat, + model_path="Qwen/Qwen3-VL-8B-Instruct", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + use_custom_prompt=False, + use_vllm=True, + ), + "Qwen3-VL-30B-A3B-Instruct": partial( + vlm.Qwen2VLChat, + model_path="Qwen/Qwen3-VL-30B-A3B-Instruct", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + use_custom_prompt=False, + use_vllm=True, + ), + "Qwen3-VL-235B-A22B-Instruct": partial( + vlm.Qwen2VLChat, + model_path="Qwen/Qwen3-VL-235B-A22B-Instruct", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + use_custom_prompt=False, + use_vllm=True, + ), + 'VLM-R1': partial( + vlm.VLMR1Chat, + model_path='omlab/VLM-R1-Qwen2.5VL-3B-Math-0305', + min_pixels=1280*28*28, + max_pixels=16384*28*28, + use_custom_prompt=False), + 'VLAA-Thinker-Qwen2.5VL-3B': partial( + vlm.VLAAThinkerChat, + model_path='UCSC-VLAA/VLAA-Thinker-Qwen2.5VL-3B', + min_pixels=1280*28*28, + max_pixels=16384*28*28, + use_custom_prompt=False, + post_process=True, # post processing for evaluation + system_prompt=('' + "You are VL-Thinking🤔, a helpful assistant with excellent reasoning ability." + " A user asks you a question, and you should try to solve it." + " You should first think about the reasoning process in the mind and then provides the user with the answer." + " The reasoning process and answer are enclosed within and" + " tags, respectively, i.e., reasoning process here " + " answer here " + ), + ), + 'VLAA-Thinker-Qwen2.5VL-7B': partial( + vlm.VLAAThinkerChat, + model_path='UCSC-VLAA/VLAA-Thinker-Qwen2.5VL-7B', + min_pixels=1280*28*28, + max_pixels=16384*28*28, + use_custom_prompt=False, + post_process=True, # post processing for evaluation + system_prompt=('' + "You are VL-Thinking🤔, a helpful assistant with excellent reasoning ability." + " A user asks you a question, and you should try to solve it." + " You should first think about the reasoning process in the mind and then provides the user with the answer." + " The reasoning process and answer are enclosed within and" + " tags, respectively, i.e., reasoning process here " + " answer here " + ), + ), + 'WeThink-Qwen2.5VL-7B': partial( + vlm.WeThinkVL, + model_path='yangjie-cv/WeThink-Qwen2.5VL-7B', + min_pixels=1280*28*28, + max_pixels=16384*28*28, + use_custom_prompt=False, + system_prompt=("You FIRST think about the reasoning process as an internal monologue and then provide the final answer.\nThe reasoning process MUST BE enclosed within tags. The final answer MUST BE enclosed within tags." + ), + ), +} + +slime_series = { + "Slime-7B": partial(vlm.SliME, model_path="yifanzhang114/SliME-vicuna-7B"), + "Slime-8B": partial(vlm.SliME, model_path="yifanzhang114/SliME-Llama3-8B"), + "Slime-13B": partial(vlm.SliME, model_path="yifanzhang114/SliME-vicuna-13B"), +} + +eagle_series = { + "Eagle-X4-8B-Plus": partial(vlm.Eagle, model_path="NVEagle/Eagle-X4-8B-Plus"), + "Eagle-X4-13B-Plus": partial(vlm.Eagle, model_path="NVEagle/Eagle-X4-13B-Plus"), + "Eagle-X5-7B": partial(vlm.Eagle, model_path="NVEagle/Eagle-X5-7B"), + "Eagle-X5-13B": partial(vlm.Eagle, model_path="NVEagle/Eagle-X5-13B"), + "Eagle-X5-13B-Chat": partial(vlm.Eagle, model_path="NVEagle/Eagle-X5-13B-Chat"), + "Eagle-X5-34B-Chat": partial(vlm.Eagle, model_path="NVEagle/Eagle-X5-34B-Chat"), + "Eagle-X5-34B-Plus": partial(vlm.Eagle, model_path="NVEagle/Eagle-X5-34B-Plus"), +} + +moondream_series = { + "Moondream1": partial(vlm.Moondream1, model_path="vikhyatk/moondream1"), + "Moondream2": partial(vlm.Moondream2, model_path="vikhyatk/moondream2"), + "Moondream3": partial(vlm.Moondream3, model_path="moondream/moondream3-preview"), +} + +llama_series = { + "Llama-3.2-11B-Vision-Instruct": partial( + vlm.llama_vision, model_path="meta-llama/Llama-3.2-11B-Vision-Instruct" + ), + "LLaVA-CoT": partial(vlm.llama_vision, model_path="Xkev/Llama-3.2V-11B-cot"), + "Llama-3.2-90B-Vision-Instruct": partial( + vlm.llama_vision, model_path="meta-llama/Llama-3.2-90B-Vision-Instruct" + ), + "Llama-4-Scout-17B-16E-Instruct": partial( + vlm.llama4, model_path="meta-llama/Llama-4-Scout-17B-16E-Instruct", use_vllm=True + ), +} + +molmo_series = { + "molmoE-1B-0924": partial(vlm.molmo, model_path="allenai/MolmoE-1B-0924"), + "molmo-7B-D-0924": partial(vlm.molmo, model_path="allenai/Molmo-7B-D-0924"), + "molmo-7B-O-0924": partial(vlm.molmo, model_path="allenai/Molmo-7B-O-0924"), + "molmo-72B-0924": partial(vlm.molmo, model_path="allenai/Molmo-72B-0924"), +} + +kosmos_series = { + "Kosmos2": partial(vlm.Kosmos2, model_path="microsoft/kosmos-2-patch14-224") +} + +points_series = { + "POINTS-Yi-1.5-9B-Chat": partial( + vlm.POINTS, model_path="WePOINTS/POINTS-Yi-1-5-9B-Chat" + ), + "POINTS-Qwen-2.5-7B-Chat": partial( + vlm.POINTS, model_path="WePOINTS/POINTS-Qwen-2-5-7B-Chat" + ), + "POINTSV15-Qwen-2.5-7B-Chat": partial( + vlm.POINTSV15, model_path="WePOINTS/POINTS-1-5-Qwen-2-5-7B-Chat" + ), +} + +nvlm_series = { + "NVLM": partial(vlm.NVLM, model_path="nvidia/NVLM-D-72B"), + "NVLM-D-72B_api": partial( + api.LMDeployAPI, + api_base="http://0.0.0.0:8000/v1/chat/completions", + model='NVLM-D-72B', + top_p=1.0, + max_new_tokens=32768, + retry=6, + timeout=1800, + ), +} + +vintern_series = { + "Vintern-3B-beta": partial(vlm.VinternChat, model_path="5CD-AI/Vintern-3B-beta"), + "Vintern-1B-v2": partial(vlm.VinternChat, model_path="5CD-AI/Vintern-1B-v2"), +} + +aria_series = {"Aria": partial(vlm.Aria, model_path="rhymes-ai/Aria")} + +h2ovl_series = { + "h2ovl-mississippi-2b": partial(vlm.H2OVLChat, model_path="h2oai/h2ovl-mississippi-2b"), + "h2ovl-mississippi-1b": partial( + vlm.H2OVLChat, model_path="h2oai/h2ovl-mississippi-800m" + ), +} + +valley_series = { + "valley2": partial( + vlm.Valley2Chat, model_path="bytedance-research/Valley-Eagle-7B" + ), + "valley2_dpo": partial( + vlm.Valley2Chat, model_path="bytedance-research/Valley2-DPO" + ), + "valley2.5": partial( + vlm.Valley3Chat, use_gthinker_thinking=True, model_path="bytedance-research/Valley2.5" + ), +} + +ola_series = { + "ola": partial(vlm.Ola, model_path="THUdyh/Ola-7b"), +} + +xvl_series = { + "X-VL-4B": partial(vlm.X_VL_HF, model_path="YannQi/X-VL-4B", temperature=0, retry=10), +} + +ross_series = { + "ross-qwen2-7b": partial(vlm.Ross, model_path="HaochenWang/ross-qwen2-7b"), +} + +ursa_series = { + "URSA-8B": partial(vlm.UrsaChat, model_path="URSA-MATH/URSA-8B"), + "URSA-8B-PS-GRPO": partial(vlm.UrsaChat, model_path="URSA-MATH/URSA-8B-PS-GRPO") +} + +gemma_series = { + "paligemma-3b-mix-448": partial( + vlm.PaliGemma, model_path="google/paligemma-3b-mix-448" + ), + + # 3B + "paligemma2-3b-pt-224": partial(vlm.PaliGemma, model_path="google/paligemma2-3b-pt-224"), + "paligemma2-3b-pt-448": partial(vlm.PaliGemma, model_path="google/paligemma2-3b-pt-448"), + "paligemma2-3b-mix-224": partial(vlm.PaliGemma, model_path="google/paligemma2-3b-mix-224"), + "paligemma2-3b-mix-448": partial(vlm.PaliGemma, model_path="google/paligemma2-3b-mix-448"), + + # 10B + "paligemma2-10b-pt-224": partial(vlm.PaliGemma, model_path="google/paligemma2-10b-pt-224"), + "paligemma2-10b-pt-448": partial(vlm.PaliGemma, model_path="google/paligemma2-10b-pt-448"), + "paligemma2-10b-mix-224": partial(vlm.PaliGemma, model_path="google/paligemma2-10b-mix-224"), + "paligemma2-10b-mix-448": partial(vlm.PaliGemma, model_path="google/paligemma2-10b-mix-448"), + + # 28B + "paligemma2-28b-pt-224": partial(vlm.PaliGemma, model_path="google/paligemma2-28b-pt-224"), + "paligemma2-28b-pt-448": partial(vlm.PaliGemma, model_path="google/paligemma2-28b-pt-448"), + "paligemma2-28b-mix-224": partial(vlm.PaliGemma, model_path="google/paligemma2-28b-mix-224"), + "paligemma2-28b-mix-448": partial(vlm.PaliGemma, model_path="google/paligemma2-28b-mix-448"), + + 'Gemma3-4B': partial(vlm.Gemma3, model_path='google/gemma-3-4b-it'), + 'Gemma3-12B': partial(vlm.Gemma3, model_path='google/gemma-3-12b-it'), + 'Gemma3-27B': partial(vlm.Gemma3, model_path='google/gemma-3-27b-it') +} + +aguvis_series = { + "aguvis_7b": partial( + vlm.Qwen2VLChatAguvis, + model_path=os.getenv( + "EVAL_MODEL", + "xlangai/Aguvis-7B-720P", + ), + min_pixels=256 * 28 * 28, + max_pixels=46 * 26 * 28 * 28, + use_custom_prompt=False, + mode='grounding', + ) +} + +kimi_series = { + 'Kimi-VL-A3B-Thinking': partial(vlm.KimiVL, model_path='moonshotai/Kimi-VL-A3B-Thinking'), + 'Kimi-VL-A3B-Instruct': partial(vlm.KimiVL, model_path='moonshotai/Kimi-VL-A3B-Instruct'), + 'Kimi-VL-A3B-Thinking-2506': partial(vlm.KimiVL, model_path='moonshotai/Kimi-VL-A3B-Thinking-2506', temperature=0.8, max_tokens=32768, extract_summary=True), + 'Kimi-K2.5-api': partial( + api.LMDeployAPI, + api_base="http://0.0.0.0:8000/v1/chat/completions", + model='Kimi-K2.5', + temperature=1.0, + top_p=0.95, + max_tokens=32768, + retry=6, + timeout=1800, + ) +} + +flash_vl = { + 'Flash-VL-2B-Dynamic-ISS': partial(vlm.FlashVL, model_path='FlashVL/FlashVL-2B-Dynamic-ISS') +} + + +oryx_series = { + 'oryx': partial(vlm.Oryx, model_path="THUdyh/Oryx-1.5-7B"), +} + +# recommend: vllm serve moonshotai/Kimi-VL-A3B-Thinking-2506 +# --served-model-name api-kimi-vl-thinking-2506 --trust-remote-code +# --tensor-parallel-size 2 --max-num-batched-tokens 131072 +# --max-model-len 131072 --limit-mm-per-prompt image=256 +kimi_vllm_series = { + "api-kimi-vl-thinking-2506": partial( + api.KimiVLAPI, + model="api-kimi-vl-thinking-2506", + ), + "api-kimi-vl-thinking": partial( + api.KimiVLAPI, + model="api-kimi-vl-thinking", + ), + "api-kimi-vl": partial( + api.KimiVLAPI, + model="api-kimi-vl", + max_new_tokens=2048, + temperature=0, + ), +} + + +treevgr_series = { + 'TreeVGR-7B': partial( + vlm.TreeVGR, + model_path='HaochenWang/TreeVGR-7B', + min_pixels=1280*28*28, max_pixels=16384*28*28, + ), +} + +# QTuneVL series +qtunevl_series = { + "QTuneVL1_5-2B": partial( + vlm.QTuneVLChat, model_path="hanchaow/QTuneVL1_5-2B", version="V1.5" + ), + + "QTuneVL1_5-3B": partial( + vlm.QTuneVL, + model_path="hanchaow/QTuneVL1_5-3B", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + use_custom_prompt=True, + post_process=True + ), +} + +# RbdashMM series via lmdeploy API +rbdashmm_api_series_lmdeploy = { + "rbdashmm3_DPO_38B_api": partial( + api.RBdashMMChat3_API, + api_base="http://0.0.0.0:23333/v1/chat/completions", + temperature=0, + retry=3, + timeout=600 + ), + "rbdashmm3_5_DPO_38B_api": partial( + api.RBdashChat3_5_API, + api_base="http://0.0.0.0:23333/v1/chat/completions", + temperature=0, + retry=3, + timeout=600 + ), + "rbdashmm3_5_38B_api": partial( + api.RBdashMMChat3_5_38B_API, + api_base="http://0.0.0.0:23333/v1/chat/completions", + temperature=0, + retry=3, + timeout=600 + ), + "rbdashmm3_78B_api": partial( + api.RBdashMMChat3_78B_API, + api_base="http://0.0.0.0:23333/v1/chat/completions", + temperature=0, + retry=3, + timeout=600 + ) +} + +logics_series = { + "Logics-Thinking-8B": partial(vlm.Logics_Thinking,model_path='Logics-MLLM/Logics-Thinking-8B'), + "Logics-Thinking-32B": partial(vlm.Logics_Thinking,model_path='Logics-MLLM/Logics-Thinking-32B'), +} + +insight_v_series = { + "insightv": partial(vlm.InsightV, pretrained_reason="THUdyh/Insight-V-Reason-LLaMA3", pretrained_summary="THUdyh/Insight-V-Summary-LLaMA3"), +} + +cosmos_series = { + 'Cosmos-Reason1-7B': partial(vlm.Cosmos, model_path='nvidia/Cosmos-Reason1-7B', use_vllm=True), +} + +keye_series = { + "Keye-VL-1.5-8B-auto":partial(vlm.KeyeChat, model_path="Kwai-Keye/Keye-VL-1_5-8B"), + "Keye-VL-1.5-8B-think":partial(vlm.KeyeChat, model_path="Kwai-Keye/Keye-VL-1_5-8B", think=True), + "Keye-VL-1.5-8B-nothink":partial(vlm.KeyeChat, model_path="Kwai-Keye/Keye-VL-1_5-8B", no_think=True), + "Keye-VL-8B-Preview-think":partial(vlm.KeyeChat, model_path="Kwai-Keye/Keye-VL-8B-Preview", think=True), +} + +qianfanvl_series = { + 'Qianfan-VL-3B': partial(vlm.Qianfan_VL, model_path='baidu/Qianfan-VL-3B'), + 'Qianfan-VL-8B': partial(vlm.Qianfan_VL, model_path='baidu/Qianfan-VL-8B'), + 'Qianfan-VL-70B': partial(vlm.Qianfan_VL, model_path='baidu/Qianfan-VL-70B'), +} + +lfm2vl_series = { + "LFM2-VL-450M": partial(vlm.LFM2VL, model_path="LiquidAI/LFM2-VL-450M"), + "LFM2-VL-1.6B": partial(vlm.LFM2VL, model_path="LiquidAI/LFM2-VL-1.6B"), + "LFM2-VL-3B": partial(vlm.LFM2VL, model_path="LiquidAI/LFM2-VL-3B"), + "LFM2.5-VL-1.6B": partial(vlm.LFM2VL, model_path="LiquidAI/LFM2.5-VL-1.6B"), +} + +covt_series = { + "CoVT-7B-seg": partial( + vlm.CoVTChat, + model_path="Wakals/CoVT-7B-seg", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + use_custom_prompt=False, + ), + "CoVT-7B-depth": partial( + vlm.CoVTChat, + model_path="Wakals/CoVT-7B-depth", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + use_custom_prompt=False, + ), + "CoVT-7B-seg_depth_dino": partial( + vlm.CoVTChat, + model_path="Wakals/CoVT-7B-seg_depth_dino", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + use_custom_prompt=False, + ), + "CoVT-7B-seg_depth_dino_edge": partial( + vlm.CoVTChat, + model_path="Wakals/CoVT-7B-seg_depth_dino_edge", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + use_custom_prompt=False, + ), +} + +bagel_series = { + "BAGEL-7B-MoT": partial(vlm.Bagel, model_path='ByteDance-Seed/BAGEL-7B-MoT'), +} + +spatial_related_models = { + # 3B models + "MindCube-Qwen2.5VL-RawQA-SFT": partial( + vlm.Qwen2VLChat, + model_path="MLL-Lab/MindCube-Qwen2.5VL-RawQA-SFT", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + use_custom_prompt=False, + ), + "MindCube-Qwen2.5VL-Aug-CGMap-FFR-Out-SFT": partial( + vlm.Qwen2VLChat, + model_path="MLL-Lab/MindCube-Qwen2.5VL-Aug-CGMap-FFR-Out-SFT", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + use_custom_prompt=False, + ), + "MindCube-Qwen2.5VL-Plain-CGMap-FFR-Out-SFT": partial( + vlm.Qwen2VLChat, + model_path="MLL-Lab/MindCube-Qwen2.5VL-Plain-CGMap-FFR-Out-SFT", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + use_custom_prompt=False, + ), + "SpatialLadder-3B": partial( + vlm.Qwen2VLChat, + model_path="hongxingli/SpatialLadder-3B", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + use_custom_prompt=False, + ), + "Spatial-MLLM-subset-sft": partial( + vlm.SpatialMLLM, + model_path="Diankun/Spatial-MLLM-subset-sft", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + max_num_frames=16, + use_custom_prompt=False, + post_process=True, + ), + "VST-3B-SFT": partial( + vlm.Qwen2VLChat, + model_path="rayruiyang/VST-3B-SFT", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + use_custom_prompt=False, + ), + + # 7B models + "SpaceR-SFT-7B": partial( + vlm.Qwen2VLChat, + model_path="RUBBISHLIKE/SpaceR-SFT-7B", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + use_custom_prompt=False, + ), + "ViLaSR": partial( + vlm.Qwen2VLChat, + model_path="inclusionAI/ViLaSR", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + use_custom_prompt=False, + ), + "VST-7B-SFT": partial( + vlm.Qwen2VLChat, + model_path="rayruiyang/VST-7B-SFT", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + use_custom_prompt=False, + ), + "VLM-3R": partial( + vlm.VLM3R, + model_path="Journey9ni/vlm-3r-llava-qwen2-lora", + ), +} + +sensenova_si_series = { + # SenseNova-SI-1.0 series + "SenseNova-SI-InternVL3-2B": partial( + vlm.InternVLChat, + model_path="sensenova/SenseNova-SI-InternVL3-2B", + use_custom_prompt=False, + version="V2.0" + ), + "SenseNova-SI-InternVL3-8B": partial( + vlm.InternVLChat, + model_path="sensenova/SenseNova-SI-InternVL3-8B", + use_custom_prompt=False, + version="V2.0" + ), + # SenseNova-SI-1.1 series + "SenseNova-SI-1.1-InternVL3-2B": partial( + vlm.InternVLChat, + model_path="sensenova/SenseNova-SI-1.1-InternVL3-2B", + use_custom_prompt=False, + version="V2.0" + ), + "SenseNova-SI-1.1-InternVL3-8B": partial( + vlm.InternVLChat, + model_path="sensenova/SenseNova-SI-1.1-InternVL3-8B", + use_custom_prompt=False, + version="V2.0" + ), + "SenseNova-SI-1.1-Qwen2.5-VL-3B": partial( + vlm.Qwen2VLChat, + model_path="sensenova/SenseNova-SI-1.1-Qwen2.5-VL-3B", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + use_custom_prompt=False, + ), + "SenseNova-SI-1.1-Qwen2.5-VL-7B": partial( + vlm.Qwen2VLChat, + model_path="sensenova/SenseNova-SI-1.1-Qwen2.5-VL-7B", + min_pixels=1280 * 28 * 28, + max_pixels=16384 * 28 * 28, + use_custom_prompt=False, + ), + "SenseNova-SI-1.1-Qwen3-VL-8B": partial( + vlm.Qwen3VLChat, + model_path="sensenova/SenseNova-SI-1.1-Qwen3-VL-8B", + use_custom_prompt=False, + ), + "SenseNova-SI-1.1-BAGEL-7B-MoT": partial( + vlm.Bagel, + model_path='sensenova/SenseNova-SI-1.1-BAGEL-7B-MoT' + ), + # SenseNova-SI-1.2 series + "SenseNova-SI-1.2-InternVL3-8B": partial( + vlm.InternVLChat, + model_path="sensenova/SenseNova-SI-1.2-InternVL3-8B", + use_custom_prompt=False, + version="V2.0" + ), + # SenseNova-SI-1.3 series + "SenseNova-SI-1.3-InternVL3-8B": partial( + vlm.InternVLChat, + model_path="sensenova/SenseNova-SI-1.3-InternVL3-8B", + use_custom_prompt=False, + version="V2.0" + ), +} + +internvl_groups = [ + internvl, internvl2, internvl2_5, mini_internvl, internvl2_5_mpo, + internvl3, internvl3_5 +] +internvl_series = {} +for group in internvl_groups: + internvl_series.update(group) + +interns1_groups = [ + interns1_mini +] +interns1_series = {} +for group in interns1_groups: + interns1_series.update(group) + +supported_VLM = {} + +model_groups = [ + ungrouped, o1_apis, api_models, xtuner_series, qwen_series, llava_series, granite_vision_series, + internvl_series, yivl_series, xcomposer_series, minigpt4_series, + idefics_series, instructblip_series, deepseekvl_series, deepseekvl2_series, deepseekocr_series, + janus_series, minicpm_series, cogvlm_series, wemm_series, cambrian_series, + chameleon_series, video_models, ovis_series, vila_series, mantis_series, + mmalaya_series, phi3_series, phi4_series, xgen_mm_series, qwen2vl_series, qwen3vl_series, qwen3_5_series, + slime_series, eagle_series, moondream_series, llama_series, molmo_series, + kosmos_series, points_series, nvlm_series, vintern_series, h2ovl_series, + aria_series, smolvlm_series, sail_series, valley_series, vita_series, + ross_series, emu_series, ola_series, ursa_series, gemma_series, + long_vita_series, ristretto_series, kimi_series, aguvis_series, hawkvl_series, + flash_vl, kimi_vllm_series, oryx_series, treevgr_series, varco_vision_series, qtunevl_series, + xvl_series, thyme_series, logics_series, cosmos_series, keye_series, qianfanvl_series, + lfm2vl_series, rbdashmm_api_series_lmdeploy, interns1_series, insight_v_series, covt_series +] + +# add by EASI team +model_groups.extend([bagel_series, spatial_related_models, sensenova_si_series]) + +for grp in model_groups: + supported_VLM.update(grp) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/CGAVCounting/__init__.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/CGAVCounting/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/CGAVCounting/cg_av_counting.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/CGAVCounting/cg_av_counting.py new file mode 100644 index 00000000..6ebcdae6 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/CGAVCounting/cg_av_counting.py @@ -0,0 +1,415 @@ +import json +import os +import os.path as osp +from pathlib import Path + +import numpy as np +import pandas as pd +import portalocker +from huggingface_hub import snapshot_download +from PIL import Image + +from vlmeval.smp import (dump, get_cache_path, get_file_extension, get_intermediate_file_path, + load, md5, modelscope_flag_set) +from ..utils.cgbench import post_process, unzip_hf_zip +from ..video_base import VideoBaseDataset +from .utils import get_timestampes, rating_func + + +class CGAVCounting(VideoBaseDataset): + + dataset = "CG-AV-Counting" + + TYPE = "Video-Counting" + + MD5 = "d1cd8486353ab85178098d443264a7d0" + + SYS = "" + + def __init__( + self, + dataset="CG-AV-Counting", + use_frame_time=False, + nframe=0, + fps=-1, + ): + super().__init__(dataset=dataset, nframe=nframe, fps=fps) + self.use_frame_time = use_frame_time + self.dataset_name = dataset + self.frame_tmpl_clue = 'frame-{}.jpg' + + @classmethod + def supported_datasets(cls): + return ["CGAVCounting"] + + def frame_paths_clue(self, video, timestamp_list): + frame_root = osp.join(self.frame_root, video) + os.makedirs(frame_root, exist_ok=True) + return [osp.join(frame_root, self.frame_tmpl_clue.format(i)) for i in timestamp_list] + + def save_video_frames_clue(self, video, uid, timestamp_list): + if type(uid) is not str: + uid = str(uid) + import decord + frame_paths = self.frame_paths_clue(uid, timestamp_list) + flag = np.all([osp.exists(p) for p in frame_paths]) + if flag: + frame = Image.open(frame_paths[0]) + return frame_paths, frame.width, frame.height + vid_path = osp.join(self.data_root, video) + vid = decord.VideoReader(vid_path) + frames = [] + # 获取视频的帧率 + fps = vid.get_avg_fps() + lock_path = osp.splitext(vid_path)[0] + '.lock' + with portalocker.Lock(lock_path, 'w', timeout=30): + for timestamp_sec in timestamp_list: + # 计算视频帧对应的索引 + frame_idx = int(timestamp_sec * fps) + + # 获取对应帧 + frame = vid[frame_idx] + + # 将帧转换为PIL图像 + img = Image.fromarray(frame.asnumpy()) + frames.append(img) + for im, pth in zip(frames, frame_paths): + if not osp.exists(pth): + im.save(pth) + return frame_paths, frames[0].width, frames[0].height + + def format_time(self, t): + return f"{t:.2f}" + + def get_output_filename(self, item): + video_id = Path(item["video"]).stem + start_str = self.format_time(item["query_interval"][0]) + end_str = self.format_time(item["query_interval"][1]) + return f"{video_id}_{start_str}_{end_str}.mp4" + + def prepare_dataset(self, dataset_name="CG-AV-Counting", repo_id="CG-Bench/CG-AV-Counting"): + + def check_integrity(pth): + data_file = osp.join(pth, f"{dataset_name}.tsv") + + if not os.path.exists(data_file): + return False + + if md5(data_file) != self.MD5: + return False + data = load(data_file) + for video_pth in data["video"]: + if not osp.exists(osp.join(pth, video_pth)): + return False + return True + + cache_path = get_cache_path(repo_id) + + if cache_path is not None and check_integrity(cache_path): + dataset_path = cache_path + else: + + def generate_tsv(pth): + + tsv_file = osp.join(pth, f"{dataset_name}.tsv") + + task_modes = ["long_acc", "ref_acc", "clue_acc"] + all_data = [] + for task_mode in task_modes: + with open(osp.join(pth, "cg-av-counting.json"), "r") as f: + data_file = pd.DataFrame(json.load(f)) + + data_file = data_file.assign(index=range(len(data_file))) + data_file["video_uid"] = data_file["video"].replace(".mp4", "") + data_file["video"] = data_file["video"].apply(lambda x: f"cg_videos_720p/{x}") + + data_file["ref_video_path"] = "" + data_file["ref_video_uid"] = "" + + if task_mode in ["ref_acc"]: + data_file["ref_video_path"] = data_file.apply( + lambda row: f"ref_videos/{self.get_output_filename(row)}", axis=1 + ) + data_file["ref_video_uid"] = data_file["ref_video_path"].apply( + lambda x: x.split("/")[-1].replace(".mp4", "")) + + data_file["task_mode"] = task_mode + + if task_mode == "clue_acc": + data_file["answer"] = data_file["clue"].apply(json.dumps) + + data_file = data_file[ + [ + "index", + "video_uid", + "video", + "ref_video_path", + "ref_video_uid", + "question", + "answer", + "type", + "category", + "task_mode" + ] + ] + + all_data.append(data_file) + + final_data = pd.concat(all_data, ignore_index=True) + final_data["index"] = range(len(final_data)) + final_data.to_csv(tsv_file, sep="\t", index=False) + dataset_path = cache_path + + if modelscope_flag_set(): + from modelscope import dataset_snapshot_download + + dataset_path = dataset_snapshot_download(dataset_id=repo_id) + else: + dataset_path = snapshot_download(repo_id=repo_id, repo_type="dataset") + + unzip_hf_zip(dataset_path) + + generate_tsv(dataset_path) + + tsv_file = osp.join(dataset_path, f"{dataset_name}.tsv") + + return dict(data_file=tsv_file, root=dataset_path) + + def build_prompt(self, line, video_llm): + if isinstance(line, int): + assert line < len(self) + line = self.data.iloc[line] + task_mode = line["task_mode"] + assert task_mode in ["long_acc", "clue_acc", "ref_acc"] + if task_mode == "long_acc": + user_prompt = "" + message = [] + video_path = line["video"] + if video_llm: + message.append(dict(type="video", value=osp.join(self.data_root, video_path))) + else: + image_paths, frame_indices, vid_fps = self.save_video_frames( + video_path, uid=line["video_uid"], num_frames=self.nframe, fps=self.fps + ) + message.extend(dict(type="image", value=im) for im in image_paths) + + if self.use_frame_time: + user_prompt += get_timestampes(frame_indices, vid_fps) + + user_prompt += ( + f"Please answer the question '{line['question']}' with a number. Just output the number itself, " + "don't output anything else." + ) + message.append(dict(type="text", value=user_prompt)) + elif task_mode == "ref_acc": + user_prompt = "" + message = [] + video_path = line["ref_video_path"] + if video_llm: + message.append(dict(type="video", value=osp.join(self.data_root, video_path))) + else: + image_paths, frame_indices, vid_fps = self.save_video_frames( + video_path, uid=line["ref_video_uid"], num_frames=self.nframe, fps=self.fps + ) + message.extend(dict(type="image", value=im) for im in image_paths) + + if self.use_frame_time: + user_prompt += get_timestampes(frame_indices, vid_fps) + user_prompt += ( + f"Please answer the question '{line['question']}' with a number. Just output the number itself, " + "don't output anything else." + ) + message.append(dict(type="text", value=user_prompt)) + elif task_mode == "clue_acc": + if line["category"] == "event": + user_prompt = "" + message = [] + video_path = line["video"] + if video_llm: + message.append(dict(type="video", value=osp.join(self.data_root, video_path))) + else: + image_paths, frame_indices, vid_fps = self.save_video_frames( + video_path, uid=line["video_uid"], num_frames=self.nframe, fps=self.fps + ) + message.extend(dict(type="image", value=im) for im in image_paths) + user_prompt += get_timestampes(frame_indices, vid_fps) + + user_prompt += ( + f"Watch the video and provide your answer to the question '{line['question']}', " + "including the start and end timestamps for each event." + "Format your answer in JSON, enclosed in and tags. " + "The output should look like this: [[\"start_time\", \"end_time\"], ...]. " + "Ensure each timestamp is in seconds (e.g., 'xx.xx')." + ) + message.append(dict(type="text", value=user_prompt)) + elif line["category"] == "object": + user_prompt = "" + message = [] + video_path = line["video"] + clue_timestamp_list = [] + for clue in json.loads(line["answer"]): + if clue["timestamp"] not in clue_timestamp_list: + clue_timestamp_list.append(clue["timestamp"]) + image_paths, width, height = self.save_video_frames_clue( + video_path, uid=line["video_uid"], timestamp_list=clue_timestamp_list + ) + message.append( + dict(type="text", value=f"There are {len(image_paths)} frames in the size of {width}x{height}")) + for idx, im in enumerate(image_paths): + message.append(dict(type="text", value=f"Frame{idx + 1}:")) + message.append(dict(type="image", value=im)) + user_prompt += ( + f"Answer the question '{line['question']}', " + "including the bounding box for the query object in the first frame " + "where it appears. For subsequent frames where the object appears, " + "do not provide the bounding box again. " + "Format your answer in JSON, enclosed within and tags. " + "The output should look like this: " + "{\"Frame1\": [[x_min, y_min, x_max, y_max]], \"Frame2\": [...],...}. " + "In the output, each frame should either contain the bounding box of the object " + "(if it appears for the first time in that frame) or an empty list `[]` " + "(if the object does not appear or it has already been labeled in a previous frame). " + "Ensure that bounding boxes are listed as [x_min, y_min, x_max, y_max]." + ) + message.append(dict(type="text", value=user_prompt)) + elif line["category"] == "attribute": + user_prompt = "" + message = [] + video_path = line["video"] + clue_timestamp_list = [] + for clue_ in json.loads(line["answer"]): + for clue in clue_: + if clue["timestamp"] not in clue_timestamp_list: + clue_timestamp_list.append(clue["timestamp"]) + image_paths, width, height = self.save_video_frames_clue( + video_path, uid=line["video_uid"], timestamp_list=clue_timestamp_list + ) + message.append(dict( + type="text", + value=f"There are {len(image_paths)} frames in the size of {width}x{height}")) + for idx, im in enumerate(image_paths): + message.append(dict(type="text", value=f"Frame{idx + 1}:")) + message.append(dict(type="image", value=im)) + user_prompt += ( + f"Answer the question '{line['question']}', clustering the objects according to the question. " + "For each unique cluster, assign a unique label and return the bounding box for each object in " + "the first frame where it appears. For subsequent frames where the object appears, " + "do not output anything. " + "Format your answer in JSON, enclosed within and tags. " + "The output should look like this: " + "{\"Frame 1\": [{\"bbox\": [x_min, y_min, x_max, y_max], 'label': \"Label 1\"}], " + "\"Frame 2\": [...], ...}. " + "In the output, each frame should either contain the bounding box and label for the object " + "(if it appears for the first time in that frame) or an empty list `[]` " + "(if the object has already been labeled or does not appear in that frame). " + "The label should correspond to a unique object cluster according to the question." + ) + message.append(dict(type="text", value=user_prompt)) + print(message) + return message + + def save_video_frames(self, video, uid, num_frames=8, fps=-1): + + if type(uid) is not str: + uid = str(uid) + import decord + vid_path = osp.join(self.data_root, video) + vid = decord.VideoReader(vid_path) + vid_fps = vid.get_avg_fps() + n_frames = len(vid) + + if num_frames > 0 and fps < 0: + step_size = len(vid) / (num_frames + 1) + indices = [int(i * step_size) for i in range(1, num_frames + 1)] + + frame_paths = self.frame_paths(uid) + elif fps > 0: + total_duration = n_frames / vid_fps + required_frames = int(total_duration * fps) + step_size = vid_fps / fps + indices = [int(i * step_size) for i in range(required_frames)] + frame_paths = self.frame_paths_fps(uid, len(indices)) + + # Save and validate frames + valid_paths = [] + valid_indices = [] + lock_path = osp.splitext(vid_path)[0] + '.lock' + with portalocker.Lock(lock_path, 'w', timeout=30): + if not np.all([osp.exists(p) for p in frame_paths]): + images = [vid[i].asnumpy() for i in indices] + for i, (img_array, path) in enumerate(zip(images, frame_paths)): + if osp.exists(path): + try: + with Image.open(path) as img: + img.verify() + valid_paths.append(path) + valid_indices.append(indices[i]) + except Exception: + continue + else: + try: + img = Image.fromarray(img_array) + img.save(path) + img.verify() + valid_paths.append(path) + valid_indices.append(indices[i]) + except Exception: + continue + else: + for i, path in enumerate(frame_paths): + try: + with Image.open(path) as img: + img.verify() + valid_paths.append(path) + valid_indices.append(indices[i]) + except Exception: + continue + + return valid_paths, valid_indices, vid_fps + + def evaluate(self, eval_file, **judge_kwargs): + + assert get_file_extension(eval_file) in ['xlsx', 'json', 'tsv'], \ + 'data file should be an supported format (xlsx/json/tsv) file' + + tgt_file = get_intermediate_file_path(eval_file, '_rating', 'json') + score_file = get_intermediate_file_path(eval_file, '_score', 'csv') + + data = load(eval_file) + + data_un = data[~pd.isna(data["prediction"])] + data_pred_na = data[pd.isna(data["prediction"])] + + data_pred_na["score"] = -1 + + scores_df = data_un.apply( + lambda row: post_process( + response=row["prediction"], + right_answer=row["answer"], + task_mode=row["task_mode"], + category=row["category"] + ), + axis=1, + result_type='expand' + ) + + data_un = pd.concat([data_un, scores_df], axis=1) + + data = pd.concat([data_pred_na, data_un]) + + rejected_count = (data["score"] == -1).sum() + + print( + f"Among {len(data)} questions, " + f"failed to obtain prediction for {len(data_pred_na)} questions, " + f"failed to obtain the score for {rejected_count - len(data_pred_na)} questions. " + f"Those questions will be counted as -1 score in ALL rating, and will not be counted in VALID rating." + ) + + dump(data, score_file) + + rating = rating_func(score_file) + + dump(rating, tgt_file) + + return rating diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/CGAVCounting/requirements.txt b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/CGAVCounting/requirements.txt new file mode 100644 index 00000000..3c5cbc61 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/CGAVCounting/requirements.txt @@ -0,0 +1,2 @@ +scipy +word2number diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/CGAVCounting/utils.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/CGAVCounting/utils.py new file mode 100644 index 00000000..353ff838 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/CGAVCounting/utils.py @@ -0,0 +1,423 @@ +import json +import math +import re +import signal +import zipfile +from pathlib import Path + +import numpy as np +from tqdm import tqdm + +from vlmeval.smp import load + + +def rating_func(data_path): + df = load(data_path) + + task_mode_fields = { + "long_acc": ["acc", "oboa", "mae", "rmse"], + "ref_acc": ["acc", "oboa", "mae", "rmse"], + "clue_acc": ["wcs", "ifa"], + } + + rating = {} + + for task_mode, fields in task_mode_fields.items(): + sub_df = df[df["task_mode"] == task_mode] + for field in fields: + values = sub_df[field] + if field == "rmse": + # RMSE: sqrt(mean(x^2)) + rmse_val = np.sqrt(values.mean()) + rating[f"{task_mode}/rmse"] = round(rmse_val, 4) + else: + rating[f"{task_mode}/{field}"] = round(values.mean(), 4) + + return rating + + +def get_timestampes(frame_indices, fps): + seconds = list(map(lambda x: str(round(x / fps, 4)), frame_indices)) + timestamps = ", ".join(seconds) + return "A total of {frame_num} frames are sampled. Their corresponding timestamps are:\n\n{timestamps}\n\n".format( + frame_num=len(frame_indices), timestamps=timestamps + ) + + +def time_str_to_seconds(time_str: str) -> float: + time_str = time_str.strip() + if '.' in time_str: + time_main, milliseconds = time_str.split('.') + milliseconds = float(f"0.{milliseconds}") + else: + time_main = time_str + milliseconds = 0.0 + + parts = list(map(int, time_main.split(":"))) + + if len(parts) == 2: + minutes, seconds = parts + total_seconds = minutes * 60 + seconds + elif len(parts) == 3: + hours, minutes, seconds = parts + total_seconds = hours * 3600 + minutes * 60 + seconds + else: + raise ValueError(f"Invalid time format: {time_str}") + + return total_seconds + milliseconds + + +def extract_outer_json(text): + stack = [] + start_idx = None + opening = {'{': '}', '[': ']'} + closing = {'}': '{', ']': '['} + + for i, char in enumerate(text): + if char in opening: + if not stack: + start_idx = i # 最外层起点 + stack.append(char) + elif char in closing: + if stack and stack[-1] == closing[char]: + stack.pop() + if not stack and start_idx is not None: + candidate = text[start_idx:i + 1] + try: + return json.dumps(json.loads(candidate)) + except json.JSONDecodeError: + continue # 尝试下一个 JSON 块 + return None + + +def compute_tiou(t1, t2): + """Temporal IoU""" + inter_start = max(t1[0], t2[0]) + inter_end = min(t1[1], t2[1]) + inter = max(0.0, inter_end - inter_start) + union = max(t1[1], t2[1]) - min(t1[0], t2[0]) + return inter / union if union > 0 else 0.0 + + +def compute_sIoU(box1, box2): + """ + Complete IoU (sIoU) between two bounding boxes. + Args: + box1 (list or np.array): [x1, y1, x2, y2] of ground truth box + box2 (list or np.array): [x1, y1, x2, y2] of predicted box + + Returns: + IoU (float): The IoU score between the two boxes. + """ + + # Ensure the coordinates are ordered: [min_x, min_y, max_x, max_y] + box1 = np.array([min(box1[0], box1[2]), min(box1[1], box1[3]), + max(box1[0], box1[2]), max(box1[1], box1[3])]) + box2 = np.array([min(box2[0], box2[2]), min(box2[1], box2[3]), + max(box2[0], box2[2]), max(box2[1], box2[3])]) + + # Compute the intersection area + inter_x1 = max(box1[0], box2[0]) + inter_y1 = max(box1[1], box2[1]) + inter_x2 = min(box1[2], box2[2]) + inter_y2 = min(box1[3], box2[3]) + + inter_area = max(0, inter_x2 - inter_x1) * max(0, inter_y2 - inter_y1) + + # Compute areas of the individual boxes + area1 = (box1[2] - box1[0]) * (box1[3] - box1[1]) + area2 = (box2[2] - box2[0]) * (box2[3] - box2[1]) + + # Compute union area + union = area1 + area2 - inter_area + iou = inter_area / union if union > 0 else 0.0 + + return iou + + +def greedy_matching(gt_instances, pred_instances, iou_func): + """Greedy matching based on maximum IoU""" + unmatched_gt = set(range(len(gt_instances))) + unmatched_pred = set(range(len(pred_instances))) + matches = [] + + while unmatched_gt and unmatched_pred: + max_iou = -1 + best_match = None + for gt_idx in unmatched_gt: + for pred_idx in unmatched_pred: + iou = iou_func(gt_instances[gt_idx], pred_instances[pred_idx]) + if iou > max_iou: + max_iou = iou + best_match = (gt_idx, pred_idx) + + if best_match: + gt_idx, pred_idx = best_match + matches.append((gt_idx, pred_idx)) + unmatched_gt.remove(gt_idx) + unmatched_pred.remove(pred_idx) + + return matches + + +def compute_cluster_pair_wcs(gt, pred, iou_type): + if iou_type == 'tIoU': + loc_sum = 0.0 + for g in gt: + loc_sum += max([compute_tiou(g, p) for p in pred] or [0.0]) + loc_acc = loc_sum / len(gt) if gt else 0.0 + count_penalty = 1.0 - abs(len(pred) - len(gt)) / max(len(gt), 1) + # count_penalty = 1.0 + return math.sqrt(loc_acc * max(0, count_penalty)) + + elif iou_type == 'sIoU': + # group by frame index + from collections import defaultdict + gt_by_f = defaultdict(list) + pred_by_f = defaultdict(list) + for f, box in gt: + gt_by_f[f].append(box) + for f, box in pred: + pred_by_f[f].append(box) + + all_f = set(gt_by_f) | set(pred_by_f) + wcs = 0.0 + for f in all_f: + gt_f = gt_by_f.get(f, []) + pred_f = pred_by_f.get(f, []) + matches = greedy_matching(gt_f, pred_f, compute_sIoU) + loc_sum = sum([compute_sIoU(gt_f[i], pred_f[j]) for i, j in matches]) + loc_acc = loc_sum / len(gt_f) if gt_f else 0.0 + count_penalty = 1.0 - abs(len(pred_f) - len(gt_f)) / max(len(gt_f), 1) + # count_penalty = 1.0 + wcs += math.sqrt(loc_acc * max(0, count_penalty)) + return wcs / max(len(all_f), 1) + + else: + raise ValueError("Unsupported iou_type") + + +class TimeoutException(Exception): + pass + + +def timeout_handler(signum, frame): + raise TimeoutException("Function execution exceeded the time limit.") + + +def compute_wcs_unlabeled(gt_clusters, pred_clusters, iou_type='tIoU', + timeout=10): # 主要是给attribute用的,但是object和event视作一个cluster也能用 + from scipy.optimize import linear_sum_assignment + + # Set the timeout signal handler + signal.signal(signal.SIGALRM, timeout_handler) + signal.alarm(timeout) # Set the alarm to go off in 'timeout' seconds + + try: + # Original function logic + K = len(gt_clusters) + M = len(pred_clusters) + + # Build cost matrix (we want max score → min cost) + score_matrix = np.zeros((K, M)) + for i in range(K): + for j in range(M): + score_matrix[i, j] = compute_cluster_pair_wcs(gt_clusters[i], pred_clusters[j], iou_type) + + cost_matrix = -score_matrix # maximize score → minimize cost + + row_ind, col_ind = linear_sum_assignment(cost_matrix) + + matched_scores = [score_matrix[i, j] for i, j in zip(row_ind, col_ind)] + + # WCS = average over gt clusters (including unmatched = 0) + total_wcs = sum(matched_scores) + return total_wcs / K + + except TimeoutException: + print(gt_clusters, pred_clusters) + print("Function execution exceeded the time limit.") + return None # or you can return some default value to indicate timeout + + finally: + signal.alarm(0) # Cancel the alarm after the function completes or times out + + +def post_process(response, right_answer, task_mode, category): + from word2number import w2n + if task_mode in ["long_acc", "ref_acc"]: + result = {"acc": 0, "oboa": 0, "mae": 0, "rmse": 0} + if response: + try: + pred = w2n.word_to_num(response) + except Exception: + pred = 0 + if abs(float(right_answer) - float(pred)) <= 1e-5: + result["acc"] = 1 + + if abs(float(right_answer) - float(pred)) <= 1: + result["oboa"] = 1 + + if abs(float(right_answer) - float(pred)) <= max(2 * float(right_answer), 100): + result["mae"] = abs(float(right_answer) - float(pred)) + result["rmse"] = abs(float(right_answer) - float(pred)) ** 2 + else: + result["mae"] = abs(float(right_answer) * 2) + result["rmse"] = abs(float(right_answer) * 2) ** 2 + elif task_mode == "clue_acc": + result = {"wcs": 0, "ifa": 0} + if response: + clues = json.loads(right_answer) + content_match = re.search(r"(.*?)", response, re.DOTALL) + student_answer = content_match.group(1).strip() if content_match else response.strip() + j = None + try: + try: + j = json.loads(student_answer) + except Exception: + j = json.loads(extract_outer_json(student_answer)) + except Exception: + pass + if j is not None: + try: + if category == "event": + pred = [] + for e in j: + + if isinstance(e[0], str) and isinstance(e[1], str) and ":" in e[0] and ":" in e[1]: + pred.append([time_str_to_seconds(e[0]), time_str_to_seconds(e[1])]) + else: + pred.append([float(e[0].split(" ")[0]) if isinstance(e[0], str) else e[0], + float(e[1].split(" ")[0]) if isinstance(e[1], str) else e[1]]) + gt = [] + for e in clues: + gt.append([float(e['start']), float(e['end'])]) + + result["wcs"] = compute_wcs_unlabeled([gt], [pred], "tIoU") + result["ifa"] = 1 + elif category == "object": + gt = [] + clue_timestamp_list = [] + for clue in clues: + if clue["timestamp"] not in clue_timestamp_list: + clue_timestamp_list.append(clue["timestamp"]) + for clue in clues: + gt.append((clue_timestamp_list.index(clue["timestamp"]), clue['bbox'])) + pred = [] + for key in j.keys(): + if "Frame" not in key: + continue + idx = int(key.replace("Frame", "")) - 1 + if len(j[key]) == 0: + continue + if isinstance(j[key][0], list) and len(j[key][0]) == 4: + for e in j[key]: + if isinstance(e, list) and len(e) == 4: + pred.append((idx, e)) + elif isinstance(j[key][0], list) and len(j[key][0]) == 2: + for ii in range(int(len(j[key]) // 2)): + if isinstance(j[key][ii * 2], list) and len(j[key][ii * 2]) == 2 and isinstance( + j[key][ii * 2 + 1], list) and len(j[key][ii * 2 + 1]) == 2: + pred.append((idx, [j[key][ii * 2][0], j[key][ii * 2][1], j[key][ii * 2 + 1][0], + j[key][ii * 2 + 1][1]])) + result["wcs"] = compute_wcs_unlabeled([gt], [pred], "sIoU") + result["ifa"] = 1 + elif category == "attribute": + gt = [] + clue_timestamp_list = [] + for clue_ in clues: + for clue in clue_: + if clue["timestamp"] not in clue_timestamp_list: + clue_timestamp_list.append(clue["timestamp"]) + for clue_ in clues: + gt_ = [] + for clue in clue_: + gt_.append((clue_timestamp_list.index(clue["timestamp"]), clue['bbox'])) + gt.append(gt_) + pred = {} + for key in j.keys(): + if "Frame" not in key: + continue + idx = int(key.replace("Frame", "")) - 1 + for e in j[key]: + if e['label'] not in pred.keys(): + pred[e['label']] = [] + if 'bbox' in e: + if isinstance(e['bbox'], list) and len(e['bbox']) == 4: + pred[e['label']].append((idx, e['bbox'])) + if 'bbox_2d' in e: + if isinstance(e['bbox_2d'], list) and len(e['bbox_2d']) == 4: + pred[e['label']].append((idx, e['bbox_2d'])) + pred_list = [pred[key] for key in pred] + result["wcs"] = compute_wcs_unlabeled(gt, pred_list, "sIoU") + result["ifa"] = 1 + except Exception: + pass + + return result + + +def get_chunk_number(filename): + try: + num = filename.split("chunk_")[1].split(".zip")[0] + return int(num) + except Exception: + return float('inf') + + +def auto_merge_and_unzip_parts(target_dir, extract_dir, zip_prefix=None): + target_dir = Path(target_dir) + extract_dir = Path(extract_dir) + extract_dir.mkdir(parents=True, exist_ok=True) + + # 匹配 zip 分卷:例如 video_chunk_001.zip.part000 + part_files = sorted(target_dir.glob("*.zip.part*")) + groups = {} + + # 分组:根据前缀提取 group 名(即 zip 文件名) + for part_file in part_files: + match = re.match(r"(.*\.zip)\.part\d+$", part_file.name) + if match: + zip_name = match.group(1) + if zip_prefix is None or Path(zip_name).stem.startswith(zip_prefix): + groups.setdefault(zip_name, []).append(part_file) + + if not groups: + print(f"No matching zip parts found with prefix: {zip_prefix}") + return + + # 合并每一组分卷 -> 解压 + for zip_name, parts in tqdm(groups.items(), desc="Merging and unzipping"): + parts = sorted(parts, key=lambda p: int(p.name.split("part")[-1])) + zip_path = target_dir / zip_name + + # 合并分卷 + with open(zip_path, 'wb') as outfile: + for part in parts: + with open(part, 'rb') as infile: + outfile.write(infile.read()) + + # 解压合并后的 zip 文件 + with zipfile.ZipFile(zip_path, 'r') as zip_ref: + zip_ref.extractall(extract_dir) + + # 删除合并后的 zip 文件(可注释) + zip_path.unlink() + + +def unzip_hf_zip(target_dir): + target_dir = Path(target_dir) + + videos_dir = target_dir / "cg_videos_720p" + ref_videos_dir = target_dir / "ref_videos" + + if videos_dir.exists() and ref_videos_dir.exists(): + print("all target dirs exist, skip.") + return + + videos_dir.mkdir(parents=True, exist_ok=True) + + auto_merge_and_unzip_parts(target_dir, ref_videos_dir, zip_prefix="ref_videos") + auto_merge_and_unzip_parts(target_dir, videos_dir, zip_prefix="videos") + + print("sucessfully unzip all files.") diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/EgoExoBench/README.md b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/EgoExoBench/README.md new file mode 100644 index 00000000..34491189 --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/EgoExoBench/README.md @@ -0,0 +1,79 @@ +# EgoExoBench + +This is the official repository of [EgoExoBench: A +Benchmark for First- and Third-person View Video +Understanding in MLLMs]() + +## 📊 Benchmark Overview + +**EgoExoBench** is a large-scale benchmark designed to evaluate cross-view video understanding in multimodal large language models (MLLMs). It contains paired egocentric–exocentric videos and over **7,300 multiple-choice questions** across **11 subtasks**, covering three key dimensions of ego–exo reasoning: + +* **Ego-Exo Relation** +* **Ego-Exo View Transition** +* **Ego-Exo Temporal Reasoning** + +## 📝 Data Preparation + +### Video Data + +EgoExoBench builds upon six publicly available ego–exo datasets. + +* [Ego-Exo4D](https://ego-exo4d-data.org/) +* [LEMMA](https://sites.google.com/view/lemma-activity) +* [EgoExoLearn](https://huggingface.co/datasets/hyf015/EgoExoLearn) +* [TF2023](https://github.com/ziweizhao1993/PEN) +* [EgoMe](https://huggingface.co/datasets/HeqianQiu/EgoMe) +* [CVMHAT](https://github.com/RuizeHan/CVMHT) + +The script will automatically download the processed video data, **except Ego-Exo4D**, due to license restrictions. You need to manually download it from the [official website](https://ego-exo4d-data.org/) and organize it as shown below. + +If you prefer to download all datasets manually, you can simply create empty `processed_videos/` and `processed_frames/` folders and organize the datasets in the following structure: + +``` +[LMUData]/videos/EgoExoBench +├── CVMHAT/ +│ └── data/ +├── EgoExo4D/ +│ └── takes/ +├── EgoExoLearn/ +├── EgoMe/ +├── LEMMA/ +├── TF2023/ +│ └── data/ +├── processed_frames/ +└── processed_videos/ +``` +### Multiple-Choice Questions (MCQs) + +The script will automatically download the EgoExoBench **multiple-choice questions (MCQs)** file from this [link](https://huggingface.co/datasets/Heleun/EgoExoBench_MCQ). + +## 🚀 Model Evaluation + +Use the following commands to evaluate your VLMs on EgoExoBench: + +```shell +# For lightweight vision-language models +torchrun --nproc-per-node=1 run.py \ + --data EgoExoBench_MCQ \ + --model Qwen2.5-VL-7B-Instruct-ForVideo + +# For larger models with higher memory usage +python run.py \ + --data EgoExoBench_MCQ \ + --model Qwen2.5-VL-72B-Instruct-ForVideo +``` + +To skip evaluation on the **Ego-Exo4D** portion of the benchmark, specify the `EgoExoBench_64frame_skip_EgoExo4D` configuration with the **`--data`** argument. + +``` +# Example command to skip Ego-Exo4D +torchrun --nproc-per-node=1 run.py \ + --data EgoExoBench_64frame_skip_EgoExo4D \ + --model [Your_Model_Name] +``` + +> 💡 Note: If you encounter errors related to stacking videos with varying frame counts, try using `transformers==4.49.0` as a temporary workaround. + +## 🙏 Acknowledgements + +EgoExoBench builds upon publicly available ego–exo datasets: [Ego-Exo4D](https://ego-exo4d-data.org/), [LEMMA](https://sites.google.com/view/lemma-activity), [EgoExoLearn](https://huggingface.co/datasets/hyf015/EgoExoLearn), [TF2023](https://github.com/ziweizhao1993/PEN), [EgoMe](https://huggingface.co/datasets/HeqianQiu/EgoMe), [CVMHAT](https://github.com/RuizeHan/CVMHT). Thanks for open-sourcing! diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/EgoExoBench/__init__.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/EgoExoBench/__init__.py new file mode 100644 index 00000000..f3fe93da --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/EgoExoBench/__init__.py @@ -0,0 +1 @@ +from .egoexobench import EgoExoBench_MCQ # noqa: F401 diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/EgoExoBench/cvmhat_preprocess.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/EgoExoBench/cvmhat_preprocess.py new file mode 100644 index 00000000..2045461a --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/EgoExoBench/cvmhat_preprocess.py @@ -0,0 +1,46 @@ +import json +import os +import xml.etree.ElementTree as ET + +import cv2 + +# replace with your actual path +ann_file = 'EgoExoBench/MCQ/Ego-Exo-Relation/person_relation.json' + + +def add_bbox(bbox_img_path): + bbox_dir = os.path.dirname(bbox_img_path) + os.makedirs(bbox_dir, exist_ok=True) + ori_img_dir = os.path.dirname(bbox_img_path).replace('bbox', 'frame_sel') + frame_idx, person_id = os.path.basename(bbox_img_path).split('.')[0].split('_') + ori_img_path = os.path.join(ori_img_dir, frame_idx + '.jpg') + xml_file = ori_img_path.replace('data', 'GT_xml').replace('frame_sel/', '').replace('.jpg', '.xml') + + tree = ET.parse(xml_file) + root = tree.getroot() + im = cv2.imread(ori_img_path) + for object in root.findall('object'): + object_name = object.find('name').text + if object_name != person_id: + continue + im_copy = im.copy() + Xmin = int(object.find('rectangle').find('xmin').text) + Ymin = int(object.find('rectangle').find('ymin').text) + Xmax = int(object.find('rectangle').find('xmax').text) + Ymax = int(object.find('rectangle').find('ymax').text) + color = (255, 0, 0) + cv2.rectangle(im_copy, (Xmin, Ymin), (Xmax, Ymax), color, 3) + cv2.imwrite(bbox_img_path, im_copy) + return + + +with open(ann_file, 'r') as f: + ann_data = json.load(f) + for aitem in ann_data.values(): + image_paths = [] + image_paths.extend(aitem['query']['image_paths']) + for oitem in aitem['options']: + image_paths.extend(oitem['image_paths']) + + for image_path in image_paths: + add_bbox(image_path) diff --git a/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/EgoExoBench/egoexobench.py b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/EgoExoBench/egoexobench.py new file mode 100644 index 00000000..1d37651c --- /dev/null +++ b/evaluation/cosmos3/reasoner/vlmevalkit/vlmeval/dataset/EgoExoBench/egoexobench.py @@ -0,0 +1,305 @@ +import glob +import os +import os.path as osp +import re +import shutil +import warnings + +import numpy as np +import pandas as pd +import torch +import torchvision.transforms as T +from huggingface_hub import snapshot_download +from PIL import Image +from torchvision import transforms + +from vlmeval.smp import (dump, get_cache_path, get_file_extension, get_intermediate_file_path, + load, md5) +from vlmeval.smp.file import LMUDataRoot +from ..utils import DEBUG_MESSAGE, build_judge +from ..video_base import VideoBaseDataset +from .utils import Stack, ToTorchFormatTensor + +FAIL_MSG = 'Failed to obtain answer via API.' + + +class EgoExoBench_MCQ(VideoBaseDataset): + MD5 = '9c0aa8da235d766d02dd7e9a19182719' + TYPE = 'Video-MCQ' + DEFAULT_JUDGE = ['chatgpt-0125', 'gpt-4-0125'] + + def __init__(self, dataset='EgoExoBench_MCQ', nframe=64, skip_EgoExo4D=False): + super().__init__(dataset=dataset, nframe=nframe) + self.frame_fps = 2 + self.skip_EgoExo4D = skip_EgoExo4D + + @classmethod + def supported_datasets(cls): + return ['EgoExoBench_MCQ'] + + def prepare_dataset(self, dataset_name='EgoExoBench_MCQ', repo_id='Heleun/EgoExoBench_MCQ', video_repo_id='onlyfaces/EgoExoBench'): # noqa: E501 + def check_integrity(pth): + data_file = osp.join(pth, f'{dataset_name}.tsv') + + if not osp.exists(data_file): + return False + + if md5(data_file) != self.MD5: + return False + + return True + cache_path = get_cache_path(repo_id) + self.video_root = os.path.join(LMUDataRoot(), 'videos', 'EgoExoBench') + os.makedirs(self.video_root, exist_ok=True) + if not osp.exists(osp.join(self.video_root, 'processed_videos')) or not osp.exists(osp.join(self.video_root, 'processed_frames')): # noqa: E501 + snapshot_download( + repo_id=video_repo_id, + repo_type='dataset', + allow_patterns=['*.tar.gz.part*'], + local_dir=self.video_root + ) + + def combine_and_extract(root_dir, prefix, remove_parts=True): + parts_pattern = osp.join(root_dir, f'{prefix}.tar.gz.part*') + combined_archive = osp.join(root_dir, f'{prefix}.tar.gz') + if not osp.exists(combined_archive): + parts = sorted(glob.glob(parts_pattern)) + with open(combined_archive, 'wb') as outfile: + for part in parts: + with open(part, 'rb') as infile: + shutil.copyfileobj(infile, outfile) + shutil.unpack_archive(combined_archive, root_dir) + if remove_parts: + for part in parts: + os.remove(part) + os.remove(combined_archive) + + combine_and_extract(self.video_root, 'processed_videos') + combine_and_extract(self.video_root, 'processed_frames') + + if cache_path is not None and check_integrity(cache_path): + dataset_path = cache_path + else: + dataset_path = snapshot_download(repo_id=repo_id, repo_type='dataset') + + data_file = osp.join(dataset_path, f'{dataset_name}.tsv') + + # transform + self.transform = T.Compose([ + Stack(), + ToTorchFormatTensor() + ]) + + return dict(root=dataset_path, data_file=data_file) + + def get_index(self, bound, fps, max_frame, first_idx=0, num_segments=16): + start, end = bound if bound else (-100000, 100000) + start_idx = max(first_idx, round(start * fps)) + end_idx = min(round(end * fps), max_frame) + seg_size = (end_idx - start_idx) / num_segments + mid_seg_size = seg_size / 2 + indices = np.arange(num_segments) + frame_indices = start_idx + mid_seg_size + np.round(seg_size * indices) + return frame_indices.astype(int) + + def load_into_video_and_process(self, media, mcq_idx): + try: + from moviepy.editor import ImageSequenceClip, VideoFileClip + except Exception: + raise ImportError( + 'MoviePy is not installed, please install it by running "pip install moviepy==1.0.3"' + ) + video_root = self.video_root + if media['type'] in ['image']: + original_image_path = osp.join(video_root, media['image_paths'][0]) + processed_video_path = osp.join(video_root, 'processed_videos', f'{mcq_idx}.jpg') + if not os.path.exists(processed_video_path): + shutil.copy(original_image_path, processed_video_path) + return dict(type='image', value=processed_video_path) + elif media['type'] in ['frames']: + input_images = [osp.join(video_root, im) for im in media['image_paths']] + processed_video_path = osp.join(video_root, 'processed_videos', f'{mcq_idx}.mp4') + media['nframes'] = len(input_images) // 2 * 2 + if not os.path.exists(processed_video_path): + # using MoviePy to transform images into mp4 + image_files = sorted(input_images) + image_clip = ImageSequenceClip(image_files, fps=self.frame_fps) + image_clip.write_videofile(processed_video_path, codec='libx264') + image_clip.close() + elif media['type'] in ['video']: + original_video_path = osp.join(video_root, media['video_path']) + processed_video_path = osp.join(video_root, 'processed_videos', f'{mcq_idx}.mp4') + if 'video_start' in media and 'video_end' in media and media['video_start'] is not None and media['video_end'] is not None: # noqa: E501 + video_start, video_end = media['video_start'], media['video_end'] + if not os.path.exists(processed_video_path): + video_clip = VideoFileClip(original_video_path) + clip = video_clip.subclip(video_start, min(video_end, video_clip.duration)) + clip.write_videofile(processed_video_path) + clip.close() + else: + if not os.path.exists(processed_video_path): + shutil.copy(original_video_path, processed_video_path) + else: + raise ValueError(f"Unsupported media type: {media['type']}") + + return dict(type='video', value=processed_video_path, nframes=media.get('nframes', 8)) + + def save_video_into_images(self, media, mcq_idx): + bound = None + video_root = self.video_root + + if media['type'] in ['frames', 'image']: + media_paths = [osp.join(video_root, im) for im in media['image_paths']] + save_dir = osp.join(video_root, 'processed_frames', str(mcq_idx)) + os.makedirs(save_dir, exist_ok=True) + input_images = [] + for media_path in media_paths: + img_path = media_path.split('/')[-1] + save_image_path = osp.join(save_dir, img_path) + shutil.copy(media_path, save_image_path) + input_images.append(save_image_path) + return input_images + + if 'video_start' in media and 'video_end' in media and media['video_start'] is not None and media['video_end'] is not None: # noqa: E501 + bound = ( + media['video_start'], media['video_end'] + ) + video_path = os.path.join(video_root, media['video_path']) + + def read_video(video_path, bound=None, num_segments=16): + from decord import VideoReader, cpu + vr = VideoReader(video_path, ctx=cpu(0), num_threads=1) + max_frame = len(vr) - 1 + fps = float(vr.get_avg_fps()) + + images_group = list() + frame_indices = self.get_index(bound, fps, max_frame, first_idx=0, num_segments=num_segments) + save_dir = osp.join(video_root, 'processed_frames', str(mcq_idx)) + + if osp.exists(save_dir) and len(os.listdir(save_dir)) > 0: + return None, frame_indices + + for frame_index in frame_indices: + img = Image.fromarray(vr[frame_index].asnumpy()) + images_group.append(img) + torch_imgs = self.transform(images_group) + return torch_imgs, frame_indices + + def save_video_frames(imgs, video_root, frame_indices, mcq_idx): + save_dir = osp.join(video_root, 'processed_frames', str(mcq_idx)) + os.makedirs(save_dir, exist_ok=True) + frame_paths = [osp.join(save_dir, f'{fidx:07d}.jpg') for fidx in frame_indices] + + flag = np.all([osp.exists(pth) for pth in frame_paths]) + + if not flag: + block_size = imgs.size(0) // len(frame_indices) + split_tensors = torch.split(imgs, block_size) + to_pil = transforms.ToPILImage() + images = [to_pil(arr) for arr in split_tensors] + for im, pth in zip(images, frame_paths): + if not osp.exists(pth): + im.save(pth) + + return frame_paths + + torch_imgs, frame_indices = read_video(video_path, bound, media['nframes']) + img_frame_paths = save_video_frames(torch_imgs, video_root, frame_indices, mcq_idx) + return img_frame_paths + + def process_text_and_media(self, text, media_list, video_llm, mcq_idx): + + message = [] + chunks = re.split(r'(|

4&>+ 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 z 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