huoyunhf commited on
Commit
182ccb1
·
verified ·
1 Parent(s): f951c70

Upload folder using huggingface_hub

Browse files
.gitattributes CHANGED
@@ -33,3 +33,4 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ tokenizer.json filter=lfs diff=lfs merge=lfs -text
README.md CHANGED
@@ -1,3 +1,72 @@
1
- ---
2
- license: apache-2.0
3
- ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Qwen3-VL-2B-Instruct Geometry3K Model
2
+
3
+ This directory contains a Qwen3-VL-2B-Instruct model trained using **SFT (Supervised Fine-Tuning) + RL (Reinforcement Learning)** methods, specifically optimized for the Geometry3K geometric reasoning task.
4
+
5
+ ## Model Information
6
+
7
+ - **Base Model**: [Qwen3-VL-2B-Instruct](https://huggingface.co/Qwen/Qwen3-VL-2B-Instruct)
8
+ - **Training Method**: SFT + RL
9
+ - **Dataset**: Geometry3K
10
+ - **Baseline Accuracy**: **0.2612**
11
+ - **SFT+RL Accuracy**: **0.4692**
12
+
13
+ ## Directory Structure
14
+
15
+ ```
16
+ Qwen3-VL-2B-Instruct-Geometry3k/
17
+ ├── README.md # This file
18
+ ├── config.json # Model configuration file
19
+ ├── generation_config.json # Generation configuration
20
+ ├── tokenizer_config.json # Tokenizer configuration
21
+ ├── tokenizer.json # Tokenizer file
22
+ ├── vocab.json # Vocabulary file
23
+ ├── merges.txt # BPE merges file
24
+ ├── chat_template.jinja # Chat template
25
+ ├── geo3k_test_2048_qwen3-vl-2b-geometry3k.json # Test result data
26
+ ├── eval_geo3k.py # Evaluation script
27
+ └── geo3k_workflow.py # Workflow script
28
+ ```
29
+
30
+ ## Usage
31
+
32
+ ### 1. Start Model Service
33
+
34
+ Model inference is deployed using [vLLM](https://github.com/vllm-project/vllm):
35
+
36
+ ```bash
37
+ # Start vLLM service, listening on specified port (e.g., 6049)
38
+ vllm serve Qwen3-VL-2B-Instruct-Geometry3k --port 6049
39
+ ```
40
+
41
+ ### 2. Run Evaluation
42
+
43
+ The evaluation script uses [rLLM](https://github.com/rllm/rllm), calling the above vLLM service via OpenAI-compatible API:
44
+
45
+ ```bash
46
+ python eval_geo3k.py --port 6049 --model_name Qwen3-VL-2B-Instruct-Geometry3k
47
+ ```
48
+
49
+ **Dependency versions**:
50
+ - vLLM: 0.11.0 (model serving)
51
+ - rLLM: 0.2.1 (evaluation pipeline)
52
+
53
+ ## Performance Metrics
54
+
55
+ | Method | Accuracy |
56
+ |-----------|----------|
57
+ | Baseline | 0.2612 |
58
+ | SFT+RL | 0.4692 |
59
+
60
+
61
+ ## Notes
62
+
63
+ 1. The model uses BF16 precision and is recommended to run on GPUs that support BF16
64
+ 2. The model has merged LoRA weights and can be used directly without loading additional adapters
65
+ 3. Evaluation script: `eval_geo3k.py`. Optional parameters: `--n_parallel_tasks` (default 128), `--max_length` (default 2048)
66
+
67
+ ## Citation
68
+
69
+ If you use this model, please cite:
70
+ - **Geometry3K**: [hiyouga/geometry3k](https://huggingface.co/datasets/hiyouga/geometry3k) on Hugging Face (converted from [InterGPS](https://github.com/lupantech/InterGPS))
71
+ - **GRPO**: [DeepSeekMath](https://arxiv.org/abs/2402.03300) - Group Relative Policy Optimization, arXiv:2402.03300
72
+ - **Qwen-VL**: A Versatile Vision-Language Model for Understanding, Localization, Text Reading, and Beyond. arXiv preprint arXiv:2308.12966
added_tokens.json ADDED
@@ -0,0 +1,28 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "</think>": 151668,
3
+ "</tool_call>": 151658,
4
+ "</tool_response>": 151666,
5
+ "<think>": 151667,
6
+ "<tool_call>": 151657,
7
+ "<tool_response>": 151665,
8
+ "<|box_end|>": 151649,
9
+ "<|box_start|>": 151648,
10
+ "<|endoftext|>": 151643,
11
+ "<|file_sep|>": 151664,
12
+ "<|fim_middle|>": 151660,
13
+ "<|fim_pad|>": 151662,
14
+ "<|fim_prefix|>": 151659,
15
+ "<|fim_suffix|>": 151661,
16
+ "<|im_end|>": 151645,
17
+ "<|im_start|>": 151644,
18
+ "<|image_pad|>": 151655,
19
+ "<|object_ref_end|>": 151647,
20
+ "<|object_ref_start|>": 151646,
21
+ "<|quad_end|>": 151651,
22
+ "<|quad_start|>": 151650,
23
+ "<|repo_name|>": 151663,
24
+ "<|video_pad|>": 151656,
25
+ "<|vision_end|>": 151653,
26
+ "<|vision_pad|>": 151654,
27
+ "<|vision_start|>": 151652
28
+ }
chat_template.jinja ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {%- if tools %}
2
+ {{- '<|im_start|>system\n' }}
3
+ {%- if messages[0].role == 'system' %}
4
+ {%- if messages[0].content is string %}
5
+ {{- messages[0].content }}
6
+ {%- else %}
7
+ {%- for content in messages[0].content %}
8
+ {%- if 'text' in content %}
9
+ {{- content.text }}
10
+ {%- endif %}
11
+ {%- endfor %}
12
+ {%- endif %}
13
+ {{- '\n\n' }}
14
+ {%- endif %}
15
+ {{- "# Tools\n\nYou may call one or more functions to assist with the user query.\n\nYou are provided with function signatures within <tools></tools> XML tags:\n<tools>" }}
16
+ {%- for tool in tools %}
17
+ {{- "\n" }}
18
+ {{- tool | tojson }}
19
+ {%- endfor %}
20
+ {{- "\n</tools>\n\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\n<tool_call>\n{\"name\": <function-name>, \"arguments\": <args-json-object>}\n</tool_call><|im_end|>\n" }}
21
+ {%- else %}
22
+ {%- if messages[0].role == 'system' %}
23
+ {{- '<|im_start|>system\n' }}
24
+ {%- if messages[0].content is string %}
25
+ {{- messages[0].content }}
26
+ {%- else %}
27
+ {%- for content in messages[0].content %}
28
+ {%- if 'text' in content %}
29
+ {{- content.text }}
30
+ {%- endif %}
31
+ {%- endfor %}
32
+ {%- endif %}
33
+ {{- '<|im_end|>\n' }}
34
+ {%- endif %}
35
+ {%- endif %}
36
+ {%- set image_count = namespace(value=0) %}
37
+ {%- set video_count = namespace(value=0) %}
38
+ {%- for message in messages %}
39
+ {%- if message.role == "user" %}
40
+ {{- '<|im_start|>' + message.role + '\n' }}
41
+ {%- if message.content is string %}
42
+ {{- message.content }}
43
+ {%- else %}
44
+ {%- for content in message.content %}
45
+ {%- if content.type == 'image' or 'image' in content or 'image_url' in content %}
46
+ {%- set image_count.value = image_count.value + 1 %}
47
+ {%- if add_vision_id %}Picture {{ image_count.value }}: {% endif -%}
48
+ <|vision_start|><|image_pad|><|vision_end|>
49
+ {%- elif content.type == 'video' or 'video' in content %}
50
+ {%- set video_count.value = video_count.value + 1 %}
51
+ {%- if add_vision_id %}Video {{ video_count.value }}: {% endif -%}
52
+ <|vision_start|><|video_pad|><|vision_end|>
53
+ {%- elif 'text' in content %}
54
+ {{- content.text }}
55
+ {%- endif %}
56
+ {%- endfor %}
57
+ {%- endif %}
58
+ {{- '<|im_end|>\n' }}
59
+ {%- elif message.role == "assistant" %}
60
+ {{- '<|im_start|>' + message.role + '\n' }}
61
+ {%- if message.content is string %}
62
+ {{- message.content }}
63
+ {%- else %}
64
+ {%- for content_item in message.content %}
65
+ {%- if 'text' in content_item %}
66
+ {{- content_item.text }}
67
+ {%- endif %}
68
+ {%- endfor %}
69
+ {%- endif %}
70
+ {%- if message.tool_calls %}
71
+ {%- for tool_call in message.tool_calls %}
72
+ {%- if (loop.first and message.content) or (not loop.first) %}
73
+ {{- '\n' }}
74
+ {%- endif %}
75
+ {%- if tool_call.function %}
76
+ {%- set tool_call = tool_call.function %}
77
+ {%- endif %}
78
+ {{- '<tool_call>\n{"name": "' }}
79
+ {{- tool_call.name }}
80
+ {{- '", "arguments": ' }}
81
+ {%- if tool_call.arguments is string %}
82
+ {{- tool_call.arguments }}
83
+ {%- else %}
84
+ {{- tool_call.arguments | tojson }}
85
+ {%- endif %}
86
+ {{- '}\n</tool_call>' }}
87
+ {%- endfor %}
88
+ {%- endif %}
89
+ {{- '<|im_end|>\n' }}
90
+ {%- elif message.role == "tool" %}
91
+ {%- if loop.first or (messages[loop.index0 - 1].role != "tool") %}
92
+ {{- '<|im_start|>user' }}
93
+ {%- endif %}
94
+ {{- '\n<tool_response>\n' }}
95
+ {%- if message.content is string %}
96
+ {{- message.content }}
97
+ {%- else %}
98
+ {%- for content in message.content %}
99
+ {%- if content.type == 'image' or 'image' in content or 'image_url' in content %}
100
+ {%- set image_count.value = image_count.value + 1 %}
101
+ {%- if add_vision_id %}Picture {{ image_count.value }}: {% endif -%}
102
+ <|vision_start|><|image_pad|><|vision_end|>
103
+ {%- elif content.type == 'video' or 'video' in content %}
104
+ {%- set video_count.value = video_count.value + 1 %}
105
+ {%- if add_vision_id %}Video {{ video_count.value }}: {% endif -%}
106
+ <|vision_start|><|video_pad|><|vision_end|>
107
+ {%- elif 'text' in content %}
108
+ {{- content.text }}
109
+ {%- endif %}
110
+ {%- endfor %}
111
+ {%- endif %}
112
+ {{- '\n</tool_response>' }}
113
+ {%- if loop.last or (messages[loop.index0 + 1].role != "tool") %}
114
+ {{- '<|im_end|>\n' }}
115
+ {%- endif %}
116
+ {%- endif %}
117
+ {%- endfor %}
118
+ {%- if add_generation_prompt %}
119
+ {{- '<|im_start|>assistant\n' }}
120
+ {%- endif %}
chat_template.json ADDED
@@ -0,0 +1,4 @@
 
 
 
 
 
1
+ {
2
+ "chat_template": "{%- if tools %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0].role == 'system' %}\n {%- if messages[0].content is string %}\n {{- messages[0].content }}\n {%- else %}\n {%- for content in messages[0].content %}\n {%- if 'text' in content %}\n {{- content.text }}\n {%- endif %}\n {%- endfor %}\n {%- endif %}\n {{- '\\n\\n' }}\n {%- endif %}\n {{- \"# Tools\\n\\nYou may call one or more functions to assist with the user query.\\n\\nYou are provided with function signatures within <tools></tools> XML tags:\\n<tools>\" }}\n {%- for tool in tools %}\n {{- \"\\n\" }}\n {{- tool | tojson }}\n {%- endfor %}\n {{- \"\\n</tools>\\n\\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\\n<tool_call>\\n{\\\"name\\\": <function-name>, \\\"arguments\\\": <args-json-object>}\\n</tool_call><|im_end|>\\n\" }}\n{%- else %}\n {%- if messages[0].role == 'system' %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0].content is string %}\n {{- messages[0].content }}\n {%- else %}\n {%- for content in messages[0].content %}\n {%- if 'text' in content %}\n {{- content.text }}\n {%- endif %}\n {%- endfor %}\n {%- endif %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n{%- endif %}\n{%- set image_count = namespace(value=0) %}\n{%- set video_count = namespace(value=0) %}\n{%- for message in messages %}\n {%- if message.role == \"user\" %}\n {{- '<|im_start|>' + message.role + '\\n' }}\n {%- if message.content is string %}\n {{- message.content }}\n {%- else %}\n {%- for content in message.content %}\n {%- if content.type == 'image' or 'image' in content or 'image_url' in content %}\n {%- set image_count.value = image_count.value + 1 %}\n {%- if add_vision_id %}Picture {{ image_count.value }}: {% endif -%}\n <|vision_start|><|image_pad|><|vision_end|>\n {%- elif content.type == 'video' or 'video' in content %}\n {%- set video_count.value = video_count.value + 1 %}\n {%- if add_vision_id %}Video {{ video_count.value }}: {% endif -%}\n <|vision_start|><|video_pad|><|vision_end|>\n {%- elif 'text' in content %}\n {{- content.text }}\n {%- endif %}\n {%- endfor %}\n {%- endif %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"assistant\" %}\n {{- '<|im_start|>' + message.role + '\\n' }}\n {%- if message.content is string %}\n {{- message.content }}\n {%- else %}\n {%- for content_item in message.content %}\n {%- if 'text' in content_item %}\n {{- content_item.text }}\n {%- endif %}\n {%- endfor %}\n {%- endif %}\n {%- if message.tool_calls %}\n {%- for tool_call in message.tool_calls %}\n {%- if (loop.first and message.content) or (not loop.first) %}\n {{- '\\n' }}\n {%- endif %}\n {%- if tool_call.function %}\n {%- set tool_call = tool_call.function %}\n {%- endif %}\n {{- '<tool_call>\\n{\"name\": \"' }}\n {{- tool_call.name }}\n {{- '\", \"arguments\": ' }}\n {%- if tool_call.arguments is string %}\n {{- tool_call.arguments }}\n {%- else %}\n {{- tool_call.arguments | tojson }}\n {%- endif %}\n {{- '}\\n</tool_call>' }}\n {%- endfor %}\n {%- endif %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"tool\" %}\n {%- if loop.first or (messages[loop.index0 - 1].role != \"tool\") %}\n {{- '<|im_start|>user' }}\n {%- endif %}\n {{- '\\n<tool_response>\\n' }}\n {%- if message.content is string %}\n {{- message.content }}\n {%- else %}\n {%- for content in message.content %}\n {%- if content.type == 'image' or 'image' in content or 'image_url' in content %}\n {%- set image_count.value = image_count.value + 1 %}\n {%- if add_vision_id %}Picture {{ image_count.value }}: {% endif -%}\n <|vision_start|><|image_pad|><|vision_end|>\n {%- elif content.type == 'video' or 'video' in content %}\n {%- set video_count.value = video_count.value + 1 %}\n {%- if add_vision_id %}Video {{ video_count.value }}: {% endif -%}\n <|vision_start|><|video_pad|><|vision_end|>\n {%- elif 'text' in content %}\n {{- content.text }}\n {%- endif %}\n {%- endfor %}\n {%- endif %}\n {{- '\\n</tool_response>' }}\n {%- if loop.last or (messages[loop.index0 + 1].role != \"tool\") %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n{%- endif %}\n"
3
+ }
4
+
config.json ADDED
@@ -0,0 +1,65 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "Qwen3VLForConditionalGeneration"
4
+ ],
5
+ "dtype": "bfloat16",
6
+ "image_token_id": 151655,
7
+ "model_type": "qwen3_vl",
8
+ "text_config": {
9
+ "attention_bias": false,
10
+ "attention_dropout": 0.0,
11
+ "bos_token_id": 151643,
12
+ "dtype": "bfloat16",
13
+ "eos_token_id": 151645,
14
+ "head_dim": 128,
15
+ "hidden_act": "silu",
16
+ "hidden_size": 2048,
17
+ "initializer_range": 0.02,
18
+ "intermediate_size": 6144,
19
+ "max_position_embeddings": 262144,
20
+ "model_type": "qwen3_vl_text",
21
+ "num_attention_heads": 16,
22
+ "num_hidden_layers": 28,
23
+ "num_key_value_heads": 8,
24
+ "rms_norm_eps": 1e-06,
25
+ "rope_scaling": {
26
+ "mrope_interleaved": true,
27
+ "mrope_section": [
28
+ 24,
29
+ 20,
30
+ 20
31
+ ],
32
+ "rope_type": "default"
33
+ },
34
+ "rope_theta": 5000000,
35
+ "tie_word_embeddings": true,
36
+ "use_cache": true,
37
+ "vocab_size": 151936
38
+ },
39
+ "tie_word_embeddings": true,
40
+ "transformers_version": "4.57.1",
41
+ "video_token_id": 151656,
42
+ "vision_config": {
43
+ "deepstack_visual_indexes": [
44
+ 5,
45
+ 11,
46
+ 17
47
+ ],
48
+ "depth": 24,
49
+ "dtype": "bfloat16",
50
+ "hidden_act": "gelu_pytorch_tanh",
51
+ "hidden_size": 1024,
52
+ "in_channels": 3,
53
+ "initializer_range": 0.02,
54
+ "intermediate_size": 4096,
55
+ "model_type": "qwen3_vl",
56
+ "num_heads": 16,
57
+ "num_position_embeddings": 2304,
58
+ "out_hidden_size": 2048,
59
+ "patch_size": 16,
60
+ "spatial_merge_size": 2,
61
+ "temporal_patch_size": 2
62
+ },
63
+ "vision_end_token_id": 151653,
64
+ "vision_start_token_id": 151652
65
+ }
eval_geo3k.py ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import asyncio
2
+ import json
3
+ import os
4
+ from copy import deepcopy
5
+ from datetime import datetime
6
+
7
+ from geo3k_workflow import Geo3KWorkflow
8
+ from datasets import load_dataset
9
+ from rllm.data.dataset import DatasetRegistry
10
+ from rllm.engine import AgentWorkflowEngine, OpenAIEngine
11
+ from rllm.rewards.reward_fn import math_reward_fn
12
+
13
+
14
+ def load_data(n=1):
15
+ """Load geo3k data using the Dataset interface."""
16
+ dataset = load_dataset("hiyouga/geometry3k")
17
+ test_dataset = dataset["test"]
18
+ instruction_following = "Let's think step by step and output your final answer in \\boxed{}."
19
+
20
+ def process_fn(example, idx):
21
+ problem = example.pop("problem")
22
+ prompt = problem + instruction_following
23
+ answer = example.pop("answer")
24
+ image = example.pop("images")
25
+
26
+ data = {
27
+ "idx": idx,
28
+ "data_source": "geo3k",
29
+ "image": image,
30
+ "question": prompt,
31
+ "ground_truth": answer,
32
+ }
33
+ return data
34
+
35
+ # Preprocess datasets
36
+ test_dataset = test_dataset.map(function=process_fn, with_indices=True, num_proc=8)
37
+ data = []
38
+ for idx, example in enumerate(test_dataset):
39
+ for i in range(n):
40
+ data.append(deepcopy(example))
41
+ return data
42
+
43
+
44
+ def _make_json_serializable(obj):
45
+ """Recursively replace non-JSON-serializable values (e.g. PIL.Image) with placeholders."""
46
+ try:
47
+ from PIL import Image
48
+ if isinstance(obj, Image.Image):
49
+ return "<PIL.Image>"
50
+ except ImportError:
51
+ pass
52
+ if getattr(obj, "__class__", None) and getattr(obj.__class__, "__name__", "") in (
53
+ "PngImageFile",
54
+ "JpegImageFile",
55
+ "Image",
56
+ ):
57
+ return "<PIL.Image>"
58
+ if isinstance(obj, dict):
59
+ return {k: _make_json_serializable(v) for k, v in obj.items()}
60
+ if isinstance(obj, (list, tuple)):
61
+ return [_make_json_serializable(x) for x in obj]
62
+ try:
63
+ json.dumps(obj)
64
+ return obj
65
+ except (TypeError, ValueError):
66
+ return f"<{type(obj).__name__}>"
67
+
68
+
69
+ def evaluate_results(results):
70
+ """Evaluate the results and compute pass@k metrics."""
71
+ from collections import defaultdict
72
+
73
+ # Create a map to store correct answers per problem
74
+ problem_correct_map = defaultdict(int)
75
+ problem_total_map = defaultdict(int)
76
+
77
+ # Count correct answers for each problem
78
+ for episode in results:
79
+ idx = episode.task["idx"]
80
+
81
+ # Use the episode-level is_correct flag set by the workflow
82
+ is_correct = episode.is_correct
83
+
84
+ problem_correct_map[idx] += int(is_correct)
85
+ problem_total_map[idx] += 1
86
+
87
+ # Calculate pass@1 and pass@k
88
+ k = max(problem_total_map.values()) if problem_total_map else 1
89
+ total_problems = len(problem_correct_map)
90
+
91
+ if total_problems > 0:
92
+ pass_at_1 = sum(problem_correct_map.values()) / sum(problem_total_map.values())
93
+ pass_at_k = sum(1 for idx, correct in problem_correct_map.items() if correct > 0) / total_problems
94
+ else:
95
+ pass_at_1 = 0.0
96
+ pass_at_k = 0.0
97
+
98
+ print("Total unique problems:", total_problems)
99
+ print("Average Pass@1 Accuracy:", pass_at_1)
100
+ print(f"Average Pass@{k} Accuracy:", pass_at_k)
101
+
102
+
103
+ if __name__ == "__main__":
104
+ import os
105
+ import argparse
106
+
107
+ parser = argparse.ArgumentParser(description="Train Qwen VL models on GUI-360 JSON data")
108
+ parser.add_argument(
109
+ "--n_parallel_tasks",
110
+ type=int,default=128,
111
+ )
112
+ parser.add_argument(
113
+ "--model_name",
114
+ type=str,
115
+ required=True,
116
+ )
117
+ parser.add_argument(
118
+ "--max_length",
119
+ type=int,default=2048,
120
+ )
121
+ parser.add_argument(
122
+ "--port",
123
+ type=int,required=True,
124
+ )
125
+
126
+ args = parser.parse_args()
127
+
128
+ os.environ["TOKENIZERS_PARALLELISM"] = "true"
129
+
130
+ n_parallel_tasks = args.n_parallel_tasks
131
+ model_name = args.model_name
132
+ base_url="http://localhost:"+str(args.port)+"/v1"
133
+ max_length = args.max_length
134
+ print(f"Using model: {model_name} with base URL: {base_url}")
135
+ print(f"Using n_parallel_tasks: {n_parallel_tasks}")
136
+
137
+ rollout_engine = OpenAIEngine(
138
+ model=model_name,
139
+ max_prompt_length=1024,
140
+ max_response_length=max_length,
141
+ base_url=base_url,
142
+ api_key="None",
143
+ sampling_params={"temperature": 0.01},
144
+ )
145
+
146
+ engine = AgentWorkflowEngine(
147
+ workflow_cls=Geo3KWorkflow,
148
+ workflow_args={
149
+ "reward_function": math_reward_fn,
150
+ "encode_as_base64": True,
151
+ },
152
+ rollout_engine=rollout_engine,
153
+ config=None,
154
+ n_parallel_tasks=n_parallel_tasks,
155
+ retry_limit=1,
156
+ )
157
+
158
+ tasks = load_data(n=1)
159
+ print(f"Loaded {len(tasks)} geo3k tasks")
160
+
161
+ results = asyncio.run(engine.execute_tasks(tasks))
162
+
163
+ # Evaluate results (rewards are already assigned in the workflow)
164
+ print("Evaluating results...")
165
+ evaluate_results(results)
166
+
167
+ # Save results to logs/ under current working directory, filename with model name and timestamp
168
+ log_dir = os.path.join(os.getcwd(), "logs_test")
169
+ os.makedirs(log_dir, exist_ok=True)
170
+ # safe_model = model_name.replace("/", "_").replace(" ", "_")
171
+ log_name = f"geo3k_test_{max_length}_{model_name}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.json"
172
+ log_path = os.path.join(log_dir, log_name)
173
+ with open(log_path, "w") as f:
174
+ json.dump(
175
+ [_make_json_serializable(episode.to_dict()) for episode in results],
176
+ f,
177
+ indent=4,
178
+ )
179
+
180
+ print(f"\nResults saved to {log_path}")
generation_config.json ADDED
@@ -0,0 +1,13 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "bos_token_id": 151643,
3
+ "do_sample": true,
4
+ "eos_token_id": [
5
+ 151645,
6
+ 151643
7
+ ],
8
+ "pad_token_id": 151643,
9
+ "temperature": 0.7,
10
+ "top_k": 20,
11
+ "top_p": 0.8,
12
+ "transformers_version": "4.57.1"
13
+ }
geo3k_test_2048_qwen3-vl-2b-geometry3k.json ADDED
The diff for this file is too large to render. See raw diff
 
geo3k_workflow.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ from io import BytesIO
2
+
3
+ from PIL import Image
4
+
5
+ from rllm.agents.agent import Action, Episode, Step, Trajectory
6
+ from rllm.engine import ModelOutput, RolloutEngine
7
+ from rllm.rewards.reward_fn import RewardFunction, math_reward_fn
8
+ from rllm.workflows.simple_workflow import SimpleAgent
9
+ from rllm.workflows.workflow import TerminationEvent, TerminationReason, Workflow
10
+
11
+
12
+ class Geo3KWorkflow(Workflow):
13
+ def __init__(self, rollout_engine: RolloutEngine, reward_function: RewardFunction = None, encode_as_base64: bool = False, **kwargs):
14
+ """
15
+ Args:
16
+ encode_as_base64: Deprecated, kept for backward compatibility. Ignored.
17
+ """
18
+ super().__init__(rollout_engine, **kwargs)
19
+ self.agent = SimpleAgent()
20
+ self.reward_fn: RewardFunction = reward_function or math_reward_fn
21
+
22
+ async def run(self, task: dict, uid: str, **kwargs) -> Episode:
23
+ self.reset(task, uid)
24
+
25
+ question = task.get("question")
26
+ image = task.get("image", task.get("images", None))
27
+ if isinstance(image, list) and len(image) > 0:
28
+ image = image[0]
29
+ if isinstance(image, dict) and "bytes" in image:
30
+ image = Image.open(BytesIO(image["bytes"]))
31
+ assert isinstance(image, Image.Image) or image is None, f"Image must be a PIL.Image.Image, but got {type(image)}"
32
+
33
+ # Standard format: content is text, images is list[PIL.Image]
34
+ # Conversion to backend-specific format happens in rollout engine/renderer
35
+ if image is not None:
36
+ messages = [{"role": "user", "content": question, "images": [image]}]
37
+ else:
38
+ messages = [{"role": "user", "content": question}]
39
+
40
+ output: ModelOutput = await self.rollout_engine.get_model_response(messages, application_id=uid, **kwargs)
41
+ action = Action(output.content)
42
+ reward_result = self.reward_fn(task, action)
43
+
44
+ trajectory: Trajectory = self.agent.trajectory
45
+ trajectory.steps.append(
46
+ Step(
47
+ chat_completions=messages + [{"role": "assistant", "content": output.content, "reasoning": output.reasoning}],
48
+ thought=output.reasoning,
49
+ action=action,
50
+ reward=reward_result.reward,
51
+ model_output=output,
52
+ )
53
+ )
54
+
55
+ self.commit(agent=self.agent, reset=True)
56
+
57
+ if output.finish_reason == "length":
58
+ raise TerminationEvent(TerminationReason.MAX_RESPONSE_LENGTH_EXCEEDED)
59
+
60
+ raise TerminationEvent(TerminationReason.ENV_DONE)
merges.txt ADDED
The diff for this file is too large to render. See raw diff
 
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:559374bc7adf7c19601393456f409db02bf2f6601e9ce0c882c9978a6aa2733b
3
+ size 4255140312
preprocessor_config.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "size": {
3
+ "longest_edge": 16777216,
4
+ "shortest_edge": 65536
5
+ },
6
+ "patch_size": 16,
7
+ "temporal_patch_size": 2,
8
+ "merge_size": 2,
9
+ "image_mean": [
10
+ 0.5,
11
+ 0.5,
12
+ 0.5
13
+ ],
14
+ "image_std": [
15
+ 0.5,
16
+ 0.5,
17
+ 0.5
18
+ ],
19
+ "processor_class": "Qwen3VLProcessor",
20
+ "image_processor_type": "Qwen2VLImageProcessorFast"
21
+ }
special_tokens_map.json ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "additional_special_tokens": [
3
+ "<|im_start|>",
4
+ "<|im_end|>",
5
+ "<|object_ref_start|>",
6
+ "<|object_ref_end|>",
7
+ "<|box_start|>",
8
+ "<|box_end|>",
9
+ "<|quad_start|>",
10
+ "<|quad_end|>",
11
+ "<|vision_start|>",
12
+ "<|vision_end|>",
13
+ "<|vision_pad|>",
14
+ "<|image_pad|>",
15
+ "<|video_pad|>"
16
+ ],
17
+ "eos_token": {
18
+ "content": "<|im_end|>",
19
+ "lstrip": false,
20
+ "normalized": false,
21
+ "rstrip": false,
22
+ "single_word": false
23
+ },
24
+ "pad_token": {
25
+ "content": "<|endoftext|>",
26
+ "lstrip": false,
27
+ "normalized": false,
28
+ "rstrip": false,
29
+ "single_word": false
30
+ }
31
+ }
tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4
3
+ size 11422654
tokenizer_config.json ADDED
@@ -0,0 +1,239 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_bos_token": false,
3
+ "add_prefix_space": false,
4
+ "added_tokens_decoder": {
5
+ "151643": {
6
+ "content": "<|endoftext|>",
7
+ "lstrip": false,
8
+ "normalized": false,
9
+ "rstrip": false,
10
+ "single_word": false,
11
+ "special": true
12
+ },
13
+ "151644": {
14
+ "content": "<|im_start|>",
15
+ "lstrip": false,
16
+ "normalized": false,
17
+ "rstrip": false,
18
+ "single_word": false,
19
+ "special": true
20
+ },
21
+ "151645": {
22
+ "content": "<|im_end|>",
23
+ "lstrip": false,
24
+ "normalized": false,
25
+ "rstrip": false,
26
+ "single_word": false,
27
+ "special": true
28
+ },
29
+ "151646": {
30
+ "content": "<|object_ref_start|>",
31
+ "lstrip": false,
32
+ "normalized": false,
33
+ "rstrip": false,
34
+ "single_word": false,
35
+ "special": true
36
+ },
37
+ "151647": {
38
+ "content": "<|object_ref_end|>",
39
+ "lstrip": false,
40
+ "normalized": false,
41
+ "rstrip": false,
42
+ "single_word": false,
43
+ "special": true
44
+ },
45
+ "151648": {
46
+ "content": "<|box_start|>",
47
+ "lstrip": false,
48
+ "normalized": false,
49
+ "rstrip": false,
50
+ "single_word": false,
51
+ "special": true
52
+ },
53
+ "151649": {
54
+ "content": "<|box_end|>",
55
+ "lstrip": false,
56
+ "normalized": false,
57
+ "rstrip": false,
58
+ "single_word": false,
59
+ "special": true
60
+ },
61
+ "151650": {
62
+ "content": "<|quad_start|>",
63
+ "lstrip": false,
64
+ "normalized": false,
65
+ "rstrip": false,
66
+ "single_word": false,
67
+ "special": true
68
+ },
69
+ "151651": {
70
+ "content": "<|quad_end|>",
71
+ "lstrip": false,
72
+ "normalized": false,
73
+ "rstrip": false,
74
+ "single_word": false,
75
+ "special": true
76
+ },
77
+ "151652": {
78
+ "content": "<|vision_start|>",
79
+ "lstrip": false,
80
+ "normalized": false,
81
+ "rstrip": false,
82
+ "single_word": false,
83
+ "special": true
84
+ },
85
+ "151653": {
86
+ "content": "<|vision_end|>",
87
+ "lstrip": false,
88
+ "normalized": false,
89
+ "rstrip": false,
90
+ "single_word": false,
91
+ "special": true
92
+ },
93
+ "151654": {
94
+ "content": "<|vision_pad|>",
95
+ "lstrip": false,
96
+ "normalized": false,
97
+ "rstrip": false,
98
+ "single_word": false,
99
+ "special": true
100
+ },
101
+ "151655": {
102
+ "content": "<|image_pad|>",
103
+ "lstrip": false,
104
+ "normalized": false,
105
+ "rstrip": false,
106
+ "single_word": false,
107
+ "special": true
108
+ },
109
+ "151656": {
110
+ "content": "<|video_pad|>",
111
+ "lstrip": false,
112
+ "normalized": false,
113
+ "rstrip": false,
114
+ "single_word": false,
115
+ "special": true
116
+ },
117
+ "151657": {
118
+ "content": "<tool_call>",
119
+ "lstrip": false,
120
+ "normalized": false,
121
+ "rstrip": false,
122
+ "single_word": false,
123
+ "special": false
124
+ },
125
+ "151658": {
126
+ "content": "</tool_call>",
127
+ "lstrip": false,
128
+ "normalized": false,
129
+ "rstrip": false,
130
+ "single_word": false,
131
+ "special": false
132
+ },
133
+ "151659": {
134
+ "content": "<|fim_prefix|>",
135
+ "lstrip": false,
136
+ "normalized": false,
137
+ "rstrip": false,
138
+ "single_word": false,
139
+ "special": false
140
+ },
141
+ "151660": {
142
+ "content": "<|fim_middle|>",
143
+ "lstrip": false,
144
+ "normalized": false,
145
+ "rstrip": false,
146
+ "single_word": false,
147
+ "special": false
148
+ },
149
+ "151661": {
150
+ "content": "<|fim_suffix|>",
151
+ "lstrip": false,
152
+ "normalized": false,
153
+ "rstrip": false,
154
+ "single_word": false,
155
+ "special": false
156
+ },
157
+ "151662": {
158
+ "content": "<|fim_pad|>",
159
+ "lstrip": false,
160
+ "normalized": false,
161
+ "rstrip": false,
162
+ "single_word": false,
163
+ "special": false
164
+ },
165
+ "151663": {
166
+ "content": "<|repo_name|>",
167
+ "lstrip": false,
168
+ "normalized": false,
169
+ "rstrip": false,
170
+ "single_word": false,
171
+ "special": false
172
+ },
173
+ "151664": {
174
+ "content": "<|file_sep|>",
175
+ "lstrip": false,
176
+ "normalized": false,
177
+ "rstrip": false,
178
+ "single_word": false,
179
+ "special": false
180
+ },
181
+ "151665": {
182
+ "content": "<tool_response>",
183
+ "lstrip": false,
184
+ "normalized": false,
185
+ "rstrip": false,
186
+ "single_word": false,
187
+ "special": false
188
+ },
189
+ "151666": {
190
+ "content": "</tool_response>",
191
+ "lstrip": false,
192
+ "normalized": false,
193
+ "rstrip": false,
194
+ "single_word": false,
195
+ "special": false
196
+ },
197
+ "151667": {
198
+ "content": "<think>",
199
+ "lstrip": false,
200
+ "normalized": false,
201
+ "rstrip": false,
202
+ "single_word": false,
203
+ "special": false
204
+ },
205
+ "151668": {
206
+ "content": "</think>",
207
+ "lstrip": false,
208
+ "normalized": false,
209
+ "rstrip": false,
210
+ "single_word": false,
211
+ "special": false
212
+ }
213
+ },
214
+ "additional_special_tokens": [
215
+ "<|im_start|>",
216
+ "<|im_end|>",
217
+ "<|object_ref_start|>",
218
+ "<|object_ref_end|>",
219
+ "<|box_start|>",
220
+ "<|box_end|>",
221
+ "<|quad_start|>",
222
+ "<|quad_end|>",
223
+ "<|vision_start|>",
224
+ "<|vision_end|>",
225
+ "<|vision_pad|>",
226
+ "<|image_pad|>",
227
+ "<|video_pad|>"
228
+ ],
229
+ "bos_token": null,
230
+ "clean_up_tokenization_spaces": false,
231
+ "eos_token": "<|im_end|>",
232
+ "errors": "replace",
233
+ "extra_special_tokens": {},
234
+ "model_max_length": 262144,
235
+ "pad_token": "<|endoftext|>",
236
+ "split_special_tokens": false,
237
+ "tokenizer_class": "Qwen2Tokenizer",
238
+ "unk_token": null
239
+ }
video_preprocessor_config.json ADDED
@@ -0,0 +1,21 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "size": {
3
+ "longest_edge": 25165824,
4
+ "shortest_edge": 4096
5
+ },
6
+ "patch_size": 16,
7
+ "temporal_patch_size": 2,
8
+ "merge_size": 2,
9
+ "image_mean": [
10
+ 0.5,
11
+ 0.5,
12
+ 0.5
13
+ ],
14
+ "image_std": [
15
+ 0.5,
16
+ 0.5,
17
+ 0.5
18
+ ],
19
+ "processor_class": "Qwen3VLProcessor",
20
+ "video_processor_type": "Qwen3VLVideoProcessor"
21
+ }
vocab.json ADDED
The diff for this file is too large to render. See raw diff