axiom-of-choice commited on
Commit
e27770b
·
verified ·
1 Parent(s): b81f935

Add MLX 4-bit conversion of BAAI/AREX-Turbo with evaluation against bf16

Browse files
.gitattributes CHANGED
@@ -33,3 +33,5 @@ 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
+ assets/quant_tradeoff.png filter=lfs diff=lfs merge=lfs -text
37
+ tokenizer.json filter=lfs diff=lfs merge=lfs -text
README.md ADDED
@@ -0,0 +1,179 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ library_name: mlx
3
+ license: apache-2.0
4
+ pipeline_tag: text-generation
5
+ base_model: BAAI/AREX-Turbo
6
+ language:
7
+ - en
8
+ tags:
9
+ - agent
10
+ - deep-research
11
+ - reasoning
12
+ - tool-use
13
+ - long-context
14
+ - qwen3.5
15
+ - dense
16
+ - image-text-to-text
17
+ - mlx
18
+ ---
19
+
20
+ # mlx-community/AREX-Turbo-4bit
21
+
22
+ [BAAI/AREX-Turbo](https://huggingface.co/BAAI/AREX-Turbo) converted to MLX and quantized to
23
+ **4-bit**, for inference on Apple Silicon.
24
+
25
+ Converted with `mlx-vlm` 0.6.8 (`mlx` 0.32.0).
26
+
27
+ ## Quantization
28
+
29
+ | | |
30
+ |---|---|
31
+ | Requested bits | 4 |
32
+ | Group size | 64 |
33
+ | Mode | affine |
34
+ | **Effective bits per weight** | **5.347** |
35
+ | On-disk size | 2.9 GB |
36
+
37
+ The effective figure exceeds 4 because `mlx-vlm` **leaves the vision tower
38
+ in bf16 by design** and quantizes only the language model — the vision encoder is
39
+ a small share of the weights but disproportionately sensitive to quantization
40
+ error. Verified on this conversion:
41
+
42
+ ```
43
+ language_model: QuantizedLinear x186, QuantizedEmbedding x1 <- 4-bit
44
+ vision_tower : Linear x50, LayerNorm x25, Conv3d x1 <- bf16
45
+ ```
46
+
47
+ ## Evaluation
48
+
49
+ All numbers below were measured against the **unquantized bf16 source** on an
50
+ M2 Pro (32 GB), greedy decoding throughout.
51
+
52
+ ### Distributional fidelity (teacher-forced, deterministic)
53
+
54
+ Perplexity over 204 tokens of held-out text spanning prose, code, legal and
55
+ scientific registers; top-1 agreement and KL are computed per position over the
56
+ full next-token distribution. These are the numbers to judge quantization by —
57
+ they involve no sampling and no decoding choices.
58
+
59
+ | Metric | This model (4-bit) | bf16 reference |
60
+ |---|---|---|
61
+ | Perplexity | 3.3663 | 3.0171 |
62
+ | Perplexity ratio | **1.1158** | 1.000 |
63
+ | Top-1 agreement with bf16 | **0.9265** | — |
64
+ | KL(bf16 ‖ quant) | **0.052755** nats/token | 0 |
65
+
66
+ ### Generation agreement vs bf16
67
+
68
+ Greedy (`temperature=0.0`) continuations, scored against the bf16 output as
69
+ reference.
70
+
71
+ | Metric | Score |
72
+ |---|---|
73
+ | BLEU | 49.52 |
74
+ | chrF | 64.31 |
75
+ | ROUGE-1 / ROUGE-L | 0.7302 / 0.6997 |
76
+ | Exact match | 2/6 |
77
+
78
+ **Read these as agreement, not quality.** bf16 is the reference here, not ground
79
+ truth, so a divergence is only a defect if the quantized answer is *worse*. It
80
+ is not always: on `17 * 23` the bf16 model started a long derivation while the
81
+ 4-bit model answered `391` directly — a mismatch that counts against BLEU
82
+ while being the better response. That is why the two sections below exist.
83
+
84
+ ### Task accuracy (ground truth, no judge)
85
+
86
+ Pass rate on 8 short prompts with verifiable answers (arithmetic, factual
87
+ recall, sorting). Deterministic — this is the only layer that measures
88
+ correctness rather than similarity.
89
+
90
+ | | Accuracy |
91
+ |---|---|
92
+ | bf16 | 7/8 |
93
+ | **4-bit** | **8/8** |
94
+
95
+ ### Judged quality (LLM-as-judge, blind)
96
+
97
+ 18 open-ended prompts graded by `claude-sonnet-5`. The judge never sees which
98
+ model produced which answer. Pairwise comparisons are run **twice with positions
99
+ swapped**; a verdict counts only if the judge picks the same model both times,
100
+ and disagreements are reported as `inconsistent` rather than resolved silently.
101
+ Absolute grades are averaged over 2 repeats per answer.
102
+
103
+ | Metric | bf16 | 4-bit |
104
+ |---|---|---|
105
+ | Absolute quality (1-5) | 4.778 ± 0.092 | 4.583 ± 0.141 |
106
+ | Pairwise wins | 3 | 0 |
107
+ | Ties | 12 | |
108
+ | Inconsistent (judge flipped) | 3 | |
109
+
110
+ Gap of **0.195** against combined judge noise of **0.233** →
111
+ **indistinguishable from bf16**.
112
+
113
+ Judge noise is not negligible and is reported rather than hidden: grading the
114
+ same bf16 model across separate runs varied by ~0.17 on this scale. Differences
115
+ smaller than the combined SEM should not be read as a ranking.
116
+
117
+ ### Across all variants
118
+
119
+ | Variant | bpw | PPL ratio | Top-1 agree | KL | BLEU | Accuracy | Judge (1-5) | Size |
120
+ |---|---|---|---|---|---|---|---|---|
121
+ | 4-bit | 5.347 | 1.1158 | 0.9265 | 0.052755 | 49.52 | 8/8 | 4.583 | 2.9 GB |
122
+ | 6-bit | 7.2 | 1.0014 | 0.9902 | 0.002364 | 65.02 | 7/8 | 4.583 | 3.8 GB |
123
+ | 8-bit | 9.053 | 0.997 | 0.9951 | 0.000691 | 82.65 | 7/8 | 4.694 | 4.8 GB |
124
+
125
+ ![cost vs fidelity](assets/quant_tradeoff.png)
126
+
127
+ BLEU against bf16 falls from 82.7 (8-bit) to 49.5 (4-bit), while task accuracy
128
+ and judged quality stay flat. Judged quality is statistically indistinguishable
129
+ from bf16 at every bit width tested.
130
+
131
+ ![divergence from bf16](assets/fidelity_curve.png)
132
+
133
+ ### Throughput (M2 Pro, 32 GB, 128-token decode)
134
+
135
+ Hardware-specific; will differ on other chips.
136
+
137
+ | Variant | Decode tok/s | Peak RAM |
138
+ |---|---|---|
139
+ | bf16 (source) | 18.2 | 9.229 GB |
140
+ | 4-bit | 60.9 | 3.677 GB |
141
+ | 6-bit | 44.5 | 4.917 GB |
142
+ | 8-bit | 34.7 | 6.152 GB |
143
+
144
+ ### What was not measured
145
+
146
+ No standard task benchmarks (MMLU, GSM8K, agentic/tool-use evals) were run. The
147
+ accuracy layer above is 8 short prompts, not a benchmark. The vision path
148
+ was checked for coherence on sample images but not scored. The source model is
149
+ agent/deep-research oriented, and none of its agentic capabilities were
150
+ evaluated here — if that is your use case, measure on your own data.
151
+
152
+ ## Usage
153
+
154
+ ```bash
155
+ pip install mlx-vlm
156
+ ```
157
+
158
+ ```python
159
+ from mlx_vlm import load, generate
160
+ from mlx_vlm.prompt_utils import apply_chat_template
161
+ from mlx_vlm.utils import load_config
162
+
163
+ model, processor = load("mlx-community/AREX-Turbo-4bit")
164
+ config = load_config("mlx-community/AREX-Turbo-4bit")
165
+
166
+ prompt = apply_chat_template(processor, config, "What can you do?", num_images=0)
167
+ print(generate(model, processor, prompt, max_tokens=256, verbose=False).text)
168
+ ```
169
+
170
+ With an image:
171
+
172
+ ```python
173
+ prompt = apply_chat_template(processor, config, "Describe this image.", num_images=1)
174
+ out = generate(model, processor, prompt, image=["<path-or-url>"], max_tokens=256)
175
+ print(out.text)
176
+ ```
177
+
178
+ See the [original model card](https://huggingface.co/BAAI/AREX-Turbo) for capabilities,
179
+ intended use and limitations. All credit for the model belongs to its authors.
assets/fidelity_curve.png ADDED
assets/quant_tradeoff.png ADDED

Git LFS Details

  • SHA256: 10097b1ddb04670c4abe1257127b1a5826e6eb68a64efc3d46af9dc8d82a316f
  • Pointer size: 131 Bytes
  • Size of remote file: 174 kB
chat_template.jinja ADDED
@@ -0,0 +1,154 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {%- set image_count = namespace(value=0) %}
2
+ {%- set video_count = namespace(value=0) %}
3
+ {%- macro render_content(content, do_vision_count, is_system_content=false) %}
4
+ {%- if content is string %}
5
+ {{- content }}
6
+ {%- elif content is iterable and content is not mapping %}
7
+ {%- for item in content %}
8
+ {%- if 'image' in item or 'image_url' in item or item.type == 'image' %}
9
+ {%- if is_system_content %}
10
+ {{- raise_exception('System message cannot contain images.') }}
11
+ {%- endif %}
12
+ {%- if do_vision_count %}
13
+ {%- set image_count.value = image_count.value + 1 %}
14
+ {%- endif %}
15
+ {%- if add_vision_id %}
16
+ {{- 'Picture ' ~ image_count.value ~ ': ' }}
17
+ {%- endif %}
18
+ {{- '<|vision_start|><|image_pad|><|vision_end|>' }}
19
+ {%- elif 'video' in item or item.type == 'video' %}
20
+ {%- if is_system_content %}
21
+ {{- raise_exception('System message cannot contain videos.') }}
22
+ {%- endif %}
23
+ {%- if do_vision_count %}
24
+ {%- set video_count.value = video_count.value + 1 %}
25
+ {%- endif %}
26
+ {%- if add_vision_id %}
27
+ {{- 'Video ' ~ video_count.value ~ ': ' }}
28
+ {%- endif %}
29
+ {{- '<|vision_start|><|video_pad|><|vision_end|>' }}
30
+ {%- elif 'text' in item %}
31
+ {{- item.text }}
32
+ {%- else %}
33
+ {{- raise_exception('Unexpected item type in content.') }}
34
+ {%- endif %}
35
+ {%- endfor %}
36
+ {%- elif content is none or content is undefined %}
37
+ {{- '' }}
38
+ {%- else %}
39
+ {{- raise_exception('Unexpected content type.') }}
40
+ {%- endif %}
41
+ {%- endmacro %}
42
+ {%- if not messages %}
43
+ {{- raise_exception('No messages provided.') }}
44
+ {%- endif %}
45
+ {%- if tools and tools is iterable and tools is not mapping %}
46
+ {{- '<|im_start|>system\n' }}
47
+ {{- "# Tools\n\nYou have access to the following functions:\n\n<tools>" }}
48
+ {%- for tool in tools %}
49
+ {{- "\n" }}
50
+ {{- tool | tojson }}
51
+ {%- endfor %}
52
+ {{- "\n</tools>" }}
53
+ {{- '\n\nIf you choose to call a function ONLY reply in the following format with NO suffix:\n\n<tool_call>\n<function=example_function_name>\n<parameter=example_parameter_1>\nvalue_1\n</parameter>\n<parameter=example_parameter_2>\nThis is the value for the second parameter\nthat can span\nmultiple lines\n</parameter>\n</function>\n</tool_call>\n\n<IMPORTANT>\nReminder:\n- Function calls MUST follow the specified format: an inner <function=...></function> block must be nested within <tool_call></tool_call> XML tags\n- Required parameters MUST be specified\n- You may provide optional reasoning for your function call in natural language BEFORE the function call, but NOT after\n- If there is no function call available, answer the question like normal with your current knowledge and do not tell the user about function calls\n</IMPORTANT>' }}
54
+ {%- if messages[0].role == 'system' %}
55
+ {%- set content = render_content(messages[0].content, false, true)|trim %}
56
+ {%- if content %}
57
+ {{- '\n\n' + content }}
58
+ {%- endif %}
59
+ {%- endif %}
60
+ {{- '<|im_end|>\n' }}
61
+ {%- else %}
62
+ {%- if messages[0].role == 'system' %}
63
+ {%- set content = render_content(messages[0].content, false, true)|trim %}
64
+ {{- '<|im_start|>system\n' + content + '<|im_end|>\n' }}
65
+ {%- endif %}
66
+ {%- endif %}
67
+ {%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}
68
+ {%- for message in messages[::-1] %}
69
+ {%- set index = (messages|length - 1) - loop.index0 %}
70
+ {%- if ns.multi_step_tool and message.role == "user" %}
71
+ {%- set content = render_content(message.content, false)|trim %}
72
+ {%- if not(content.startswith('<tool_response>') and content.endswith('</tool_response>')) %}
73
+ {%- set ns.multi_step_tool = false %}
74
+ {%- set ns.last_query_index = index %}
75
+ {%- endif %}
76
+ {%- endif %}
77
+ {%- endfor %}
78
+ {%- if ns.multi_step_tool %}
79
+ {{- raise_exception('No user query found in messages.') }}
80
+ {%- endif %}
81
+ {%- for message in messages %}
82
+ {%- set content = render_content(message.content, true)|trim %}
83
+ {%- if message.role == "system" %}
84
+ {%- if not loop.first %}
85
+ {{- raise_exception('System message must be at the beginning.') }}
86
+ {%- endif %}
87
+ {%- elif message.role == "user" %}
88
+ {{- '<|im_start|>' + message.role + '\n' + content + '<|im_end|>' + '\n' }}
89
+ {%- elif message.role == "assistant" %}
90
+ {%- set reasoning_content = '' %}
91
+ {%- if message.reasoning_content is string %}
92
+ {%- set reasoning_content = message.reasoning_content %}
93
+ {%- else %}
94
+ {%- if '</think>' in content %}
95
+ {%- set reasoning_content = content.split('</think>')[0].rstrip('\n').split('<think>')[-1].lstrip('\n') %}
96
+ {%- set content = content.split('</think>')[-1].lstrip('\n') %}
97
+ {%- endif %}
98
+ {%- endif %}
99
+ {%- set reasoning_content = reasoning_content|trim %}
100
+ {%- if loop.index0 > ns.last_query_index %}
101
+ {{- '<|im_start|>' + message.role + '\n<think>\n' + reasoning_content + '\n</think>\n\n' + content }}
102
+ {%- else %}
103
+ {{- '<|im_start|>' + message.role + '\n' + content }}
104
+ {%- endif %}
105
+ {%- if message.tool_calls and message.tool_calls is iterable and message.tool_calls is not mapping %}
106
+ {%- for tool_call in message.tool_calls %}
107
+ {%- if tool_call.function is defined %}
108
+ {%- set tool_call = tool_call.function %}
109
+ {%- endif %}
110
+ {%- if loop.first %}
111
+ {%- if content|trim %}
112
+ {{- '\n\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
113
+ {%- else %}
114
+ {{- '<tool_call>\n<function=' + tool_call.name + '>\n' }}
115
+ {%- endif %}
116
+ {%- else %}
117
+ {{- '\n<tool_call>\n<function=' + tool_call.name + '>\n' }}
118
+ {%- endif %}
119
+ {%- if tool_call.arguments is defined %}
120
+ {%- for args_name, args_value in tool_call.arguments|items %}
121
+ {{- '<parameter=' + args_name + '>\n' }}
122
+ {%- set args_value = args_value | tojson | safe if args_value is mapping or (args_value is sequence and args_value is not string) else args_value | string %}
123
+ {{- args_value }}
124
+ {{- '\n</parameter>\n' }}
125
+ {%- endfor %}
126
+ {%- endif %}
127
+ {{- '</function>\n</tool_call>' }}
128
+ {%- endfor %}
129
+ {%- endif %}
130
+ {{- '<|im_end|>\n' }}
131
+ {%- elif message.role == "tool" %}
132
+ {%- if loop.previtem and loop.previtem.role != "tool" %}
133
+ {{- '<|im_start|>user' }}
134
+ {%- endif %}
135
+ {{- '\n<tool_response>\n' }}
136
+ {{- content }}
137
+ {{- '\n</tool_response>' }}
138
+ {%- if not loop.last and loop.nextitem.role != "tool" %}
139
+ {{- '<|im_end|>\n' }}
140
+ {%- elif loop.last %}
141
+ {{- '<|im_end|>\n' }}
142
+ {%- endif %}
143
+ {%- else %}
144
+ {{- raise_exception('Unexpected message role.') }}
145
+ {%- endif %}
146
+ {%- endfor %}
147
+ {%- if add_generation_prompt %}
148
+ {{- '<|im_start|>assistant\n' }}
149
+ {%- if enable_thinking is defined and enable_thinking is false %}
150
+ {{- '<think>\n\n</think>\n\n' }}
151
+ {%- else %}
152
+ {{- '<think>\n' }}
153
+ {%- endif %}
154
+ {%- endif %}
config.json ADDED
@@ -0,0 +1,120 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "architectures": [
3
+ "Qwen3_5ForConditionalGeneration"
4
+ ],
5
+ "dtype": "bfloat16",
6
+ "hidden_size": 2560,
7
+ "image_token_id": 248056,
8
+ "model_type": "qwen3_5",
9
+ "quantization": {
10
+ "group_size": 64,
11
+ "bits": 4,
12
+ "mode": "affine"
13
+ },
14
+ "quantization_config": {
15
+ "group_size": 64,
16
+ "bits": 4,
17
+ "mode": "affine"
18
+ },
19
+ "text_config": {
20
+ "attention_bias": false,
21
+ "attention_dropout": 0.0,
22
+ "attn_output_gate": true,
23
+ "bos_token_id": null,
24
+ "dtype": "bfloat16",
25
+ "eos_token_id": 248044,
26
+ "full_attention_interval": 4,
27
+ "head_dim": 256,
28
+ "hidden_act": "silu",
29
+ "hidden_size": 2560,
30
+ "initializer_range": 0.02,
31
+ "intermediate_size": 9216,
32
+ "layer_types": [
33
+ "linear_attention",
34
+ "linear_attention",
35
+ "linear_attention",
36
+ "full_attention",
37
+ "linear_attention",
38
+ "linear_attention",
39
+ "linear_attention",
40
+ "full_attention",
41
+ "linear_attention",
42
+ "linear_attention",
43
+ "linear_attention",
44
+ "full_attention",
45
+ "linear_attention",
46
+ "linear_attention",
47
+ "linear_attention",
48
+ "full_attention",
49
+ "linear_attention",
50
+ "linear_attention",
51
+ "linear_attention",
52
+ "full_attention",
53
+ "linear_attention",
54
+ "linear_attention",
55
+ "linear_attention",
56
+ "full_attention",
57
+ "linear_attention",
58
+ "linear_attention",
59
+ "linear_attention",
60
+ "full_attention",
61
+ "linear_attention",
62
+ "linear_attention",
63
+ "linear_attention",
64
+ "full_attention"
65
+ ],
66
+ "linear_conv_kernel_dim": 4,
67
+ "linear_key_head_dim": 128,
68
+ "linear_num_key_heads": 16,
69
+ "linear_num_value_heads": 32,
70
+ "linear_value_head_dim": 128,
71
+ "mamba_ssm_dtype": "float32",
72
+ "max_position_embeddings": 262144,
73
+ "mlp_only_layers": [],
74
+ "model_type": "qwen3_5_text",
75
+ "mtp_num_hidden_layers": 1,
76
+ "mtp_use_dedicated_embeddings": false,
77
+ "num_attention_heads": 16,
78
+ "num_hidden_layers": 32,
79
+ "num_key_value_heads": 4,
80
+ "pad_token_id": null,
81
+ "partial_rotary_factor": 0.25,
82
+ "rms_norm_eps": 1e-06,
83
+ "rope_parameters": {
84
+ "mrope_interleaved": true,
85
+ "mrope_section": [
86
+ 11,
87
+ 11,
88
+ 10
89
+ ],
90
+ "partial_rotary_factor": 0.25,
91
+ "rope_theta": 10000000,
92
+ "rope_type": "default"
93
+ },
94
+ "tie_word_embeddings": true,
95
+ "use_cache": true,
96
+ "vocab_size": 248320
97
+ },
98
+ "tie_word_embeddings": true,
99
+ "transformers_version": "5.2.0",
100
+ "video_token_id": 248057,
101
+ "vision_config": {
102
+ "deepstack_visual_indexes": [],
103
+ "depth": 24,
104
+ "dtype": "bfloat16",
105
+ "hidden_act": "gelu_pytorch_tanh",
106
+ "hidden_size": 1024,
107
+ "in_channels": 3,
108
+ "initializer_range": 0.02,
109
+ "intermediate_size": 4096,
110
+ "model_type": "qwen3_5",
111
+ "num_heads": 16,
112
+ "num_position_embeddings": 2304,
113
+ "out_hidden_size": 2560,
114
+ "patch_size": 16,
115
+ "spatial_merge_size": 2,
116
+ "temporal_patch_size": 2
117
+ },
118
+ "vision_end_token_id": 248054,
119
+ "vision_start_token_id": 248053
120
+ }
configuration.json ADDED
@@ -0,0 +1 @@
 
 
1
+ {"framework": "pytorch", "task": "text-generation", "allow_remote": true}
inference/__init__.py ADDED
@@ -0,0 +1 @@
 
 
1
+ """Minimal inference helpers for AREX."""
inference/inference.py ADDED
@@ -0,0 +1,56 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """Run one AREX BrowseComp generation through an OpenAI-compatible endpoint."""
3
+
4
+ from __future__ import annotations
5
+
6
+ import argparse
7
+ import os
8
+
9
+ from prompts import build_messages
10
+
11
+
12
+ def parse_args() -> argparse.Namespace:
13
+ parser = argparse.ArgumentParser()
14
+ parser.add_argument("--question", required=True)
15
+ parser.add_argument(
16
+ "--base-url",
17
+ default=os.environ.get("AREX_BASE_URL", "http://127.0.0.1:8000/v1"),
18
+ )
19
+ parser.add_argument(
20
+ "--api-key",
21
+ default=os.environ.get("AREX_API_KEY", "EMPTY"),
22
+ )
23
+ parser.add_argument(
24
+ "--model",
25
+ default=os.environ.get("AREX_MODEL", "AREX-Turbo"),
26
+ )
27
+ parser.add_argument("--max-tokens", type=int, default=8192)
28
+ return parser.parse_args()
29
+
30
+
31
+ def main() -> None:
32
+ args = parse_args()
33
+ try:
34
+ from openai import OpenAI
35
+ except ImportError as exc:
36
+ raise SystemExit("Install the client first: pip install -U openai") from exc
37
+
38
+ client = OpenAI(
39
+ base_url=args.base_url.rstrip("/") + "/",
40
+ api_key=args.api_key,
41
+ timeout=600.0,
42
+ )
43
+ response = client.chat.completions.create(
44
+ model=args.model,
45
+ messages=build_messages(args.question),
46
+ max_tokens=args.max_tokens,
47
+ temperature=1.0,
48
+ top_p=0.95,
49
+ presence_penalty=1.5,
50
+ extra_body={"top_k": 20},
51
+ )
52
+ print(response.choices[0].message.content or "")
53
+
54
+
55
+ if __name__ == "__main__":
56
+ main()
inference/prompts.py ADDED
@@ -0,0 +1,357 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Directly formattable AREX prompts for BrowseComp.
2
+
3
+ The prompt text and tool schemas mirror the public AREX evaluation harness.
4
+ The exported system prompt already contains its tool descriptions; callers only
5
+ need to format the user prompt with ``question=...``.
6
+ """
7
+
8
+ from __future__ import annotations
9
+
10
+ import json
11
+
12
+
13
+ _SYSTEM_PROMPT_TEMPLATE = """\
14
+ You are a dedicated worker agent. \
15
+ Your primary role is to plan and orchestrate comprehensive, multi-step research to deliver a accurate answer with thorough and well-supported evidences in response to the user's query. \
16
+ You analyze the problem, plan your research plan, carry out concrete research activities, iteratively use tools and deliver detailed findings with evidences, until complete the whole task.
17
+
18
+ ### Research loop (recommended)
19
+ - Start broad enough to map the landscape, then narrow down. Keep a verification list to help your research.
20
+ - Iteratively use tools like `search` and `visit` to find clues and evidences step by step, until finsh the task.
21
+ - For key claims, Do Not rely on snippets: use `visit` to read full pages.
22
+ - If a line of inquiry fails, change your angle and keep going — the answer exists.
23
+ - You MUST include an explicit verification step before finishing.
24
+ - If the verification step do not fully meet the task requirements, do not finish the task, but should continue to expand the search scope or change the mindset to continue your research.
25
+
26
+
27
+ ### Global Rules (non-negotiable)
28
+ - **Research**: Use available tools to gather information and conduct thorough investigation
29
+ - **Fact-Based:** All information in your final report must be derived from and supported by the sources you have analyzed, and each piece of evidence must cite the relevant `url`.
30
+ - **Persistence**: The question is guaranteed to have a correct answer that has been validated. If evidence is missing, your approach is insufficient — iterate by research with alternative angles and keep going.
31
+ - **Tool integrity**: Never simulate tool outputs. Always call tools.
32
+
33
+
34
+ **Critical Rules:**
35
+ - **ALWAYS use the provided tools.** Never simulate tool outputs or pretend to call tools.
36
+ - The question is guaranteed to have a correct answer that can be found through persistent exploration. If your current approach yields insufficient evidence, broaden and try alternative angles, keywords, and sources.
37
+ - Only call ONE tool function at one time.
38
+ - Please try to **expand your search scope** and **search from multiple perspectives** to avoid being limited to one idea when unable to find the answer.
39
+
40
+ # Tools
41
+
42
+ You have access to the following functions:
43
+
44
+ {tool_des}
45
+
46
+ If you choose to call a function ONLY reply in the following format with NO suffix:
47
+
48
+ <tool_call>
49
+ <function=example_function_name>
50
+ <parameter=example_parameter_1>
51
+ value_1
52
+ </parameter>
53
+ <parameter=example_parameter_2>
54
+ This is the value for the second parameter
55
+ that can span
56
+ multiple lines
57
+ </parameter>
58
+ </function>
59
+ </tool_call>
60
+
61
+ <IMPORTANT>
62
+ Reminder:
63
+ - Function calls MUST follow the specified format: an inner <function=...></function> block must be nested within <tool_call></tool_call> XML tags
64
+ - Required parameters MUST be specified
65
+ - For structured parameters such as `query` and `evidences`, the parameter content MUST be valid JSON
66
+ - You may provide optional reasoning in natural language BEFORE the tool call, but NOT after.
67
+ - **ALWAYS call tools. Never simulate tool outputs.**
68
+ </IMPORTANT>\
69
+ """
70
+
71
+
72
+ _USER_PROMPT_TEMPLATE = """\
73
+ Question: {question}
74
+
75
+
76
+ **Your Workflow**:
77
+
78
+ **Phase 1: Plan Your Research**
79
+
80
+ 1. Analyze the question and identify key information needs; Resolve ambiguities or contradictions.
81
+ 2. Brainstorm search queries and keywords from different angles. Plan what should investigate at the first step.
82
+ 3. Create a Verification Checklist. This checklist can start empty and be built up dynamically as your understanding of the problem evolves.
83
+
84
+ Example:
85
+
86
+ The user is asking about [topic]. To answer this correctly, I need to identify what specific information is required and what would constitute a complete answer...
87
+ The fisrt step I'll need to search from...
88
+
89
+
90
+ Verification checklist:
91
+ - [ ] Every key claim is supported by evidence from seaching results
92
+ - [ ] No unresolved contradictions remain
93
+ - [ ] The final response matches all constraints in the question
94
+
95
+
96
+ **Phase 2: Execute search tool**
97
+
98
+ Example:
99
+
100
+ <tool_call>
101
+ <function=search>
102
+ <parameter=query>[
103
+ "first search query",
104
+ "second complementary search query"
105
+ ]</parameter>
106
+ </function>
107
+ </tool_call>
108
+
109
+ **Phase 3: Execute visit tool**
110
+
111
+ Example:
112
+
113
+ <tool_call>
114
+ <function=visit>
115
+ <parameter=url>The URL(s) of the webpage(s) to visit.</parameter>
116
+ <parameter=goal>The goal of the visit for webpage(s).</parameter>
117
+ </function>
118
+ </tool_call>
119
+
120
+
121
+ **Phase 4: Iterate**
122
+ - Continue searching and visiting pages step by step until you have comprehensive information
123
+ - Refine your queries based on what you learn
124
+ - Do NOT stop at search snippets: use `visit` for key claims and critical evidences
125
+ - If the current approach is unproductive, change angle/keywords/sources and keep going — the answer exists
126
+ - Only call one tool each step, carefully analyze the tool's response and the next step and then decide the tool call next step. Strive to make tool calls precise and efficient.
127
+
128
+
129
+ **Phase 5: Final Answer**
130
+
131
+ When you have sufficient information, use the `finish` tool:
132
+
133
+ You MUST Follow:
134
+ 1. **Mandatory verification step**:
135
+ - Re-check every critical claim and citation against the gathered evidences.
136
+ - Only proceed to `finish` once your verification checklist is fully satisfied, otherwise adjust the research plan and continue searching.
137
+ - The `evidences` parameter MUST be a JSON array, and each item MUST contain exactly one `evidence` field and one `url` field.
138
+
139
+ 2. The `finish` tool can only be called separately, do not call `finish` and other fucntions at the same time.
140
+
141
+
142
+ Example:
143
+ I've gathered **comprehensive evidence** and cross-checked all critical claims. My verification checklist is fully satisfied: every key claim has supporting evidence, all contradictions have been resolved, and the answer matches all constraints in the question. I'm confident I can now provide a complete, accurate answer with proper citations.
144
+
145
+ <tool_call>
146
+ <function=finish>
147
+ <parameter=answer>Your concise answer</parameter>
148
+ <parameter=evidences>[
149
+ {{
150
+ "evidence": "The specific verified fact or data point supporting the answer.",
151
+ "url": "https://example.com/source-1"
152
+ }},
153
+ {{
154
+ "evidence": "Another verified fact supporting the answer.",
155
+ "url": "https://example.com/source-2"
156
+ }}
157
+ ]</parameter>
158
+ <parameter=confidence>Your confidence score</parameter>
159
+ </function>
160
+ </tool_call>
161
+
162
+
163
+ <CRITICAL>
164
+ - **START with a search tool call**
165
+ - If you need in-depth analysis or reflection on the tool response, output `<think>` block **before** calling the tool, where you can output the thinking content.
166
+ - Do NOT write any text after the tool call.
167
+ - Do NOT provide answers from your own knowledge
168
+ - ALWAYS use the actual tools provided and Do NOT simulate tool outputs
169
+ - The answer exists and has been validated; do not give up. If you're missing evidence, try more exploration.
170
+ </CRITICAL>
171
+
172
+ Now begin your research by calling the search tool.\
173
+ """
174
+
175
+
176
+ _SEARCH_TOOL = {
177
+ "type": "function",
178
+ "function": {
179
+ "name": "search",
180
+ "description": "Performs batched web searches: supply an array 'query'; the tool retrieves the top 10 results for each query in one call",
181
+ "parameters": {
182
+ "type": "object",
183
+ "properties": {
184
+ "query": {
185
+ "type": "array",
186
+ "items": {"type": "string"},
187
+ "description": "Array of query strings. Include multiple complementary search queries in a single call.",
188
+ }
189
+ },
190
+ "required": ["query"],
191
+ },
192
+ },
193
+ }
194
+
195
+ _GOOGLE_SCHOLAR_TOOL = {
196
+ "type": "function",
197
+ "function": {
198
+ "name": "google_scholar",
199
+ "description": "Leverage Google Scholar to retrieve relevant information from academic publications. Accepts multiple queries.",
200
+ "parameters": {
201
+ "type": "object",
202
+ "properties": {
203
+ "query": {
204
+ "type": "array",
205
+ "items": {
206
+ "type": "string",
207
+ "description": "The search query.",
208
+ },
209
+ "minItems": 1,
210
+ "description": "The list of search queries for Google Scholar.",
211
+ }
212
+ },
213
+ "required": ["query"],
214
+ },
215
+ },
216
+ }
217
+
218
+ _VISIT_TOOL = {
219
+ "type": "function",
220
+ "function": {
221
+ "name": "visit",
222
+ "description": "Visit webpage(s) and return the summary of the content.",
223
+ "parameters": {
224
+ "type": "object",
225
+ "properties": {
226
+ "url": {
227
+ "type": ["string", "array"],
228
+ "items": {"type": "string"},
229
+ "minItems": 1,
230
+ "description": "The URL(s) of the webpage(s) to visit. Can be a single URL or an array of URLs.",
231
+ },
232
+ "goal": {
233
+ "type": "string",
234
+ "description": "The goal of the visit for webpage(s).",
235
+ },
236
+ },
237
+ },
238
+ "required": ["url", "goal"],
239
+ },
240
+ }
241
+
242
+ _UPDATE_CONTEXT_TOOL = {
243
+ "type": "function",
244
+ "function": {
245
+ "name": "update_context",
246
+ "description": """This tool allows you to compress your memory into a new `context` to maintain long-term focus and avoid context window exhaustion.
247
+ **When to use this tool:**
248
+ 1. **Context Saturation:** When the conversation history becomes too long, threatening the token limit.
249
+ 2. **Loss of Focus:** When the current search trajectory is cluttered with irrelevant data or the reasoning path has become confusing.
250
+ 3. **Milestone Reached:** After completing a significant sub-task, to solidify progress before moving to the next phase.
251
+
252
+ **Strict Standards for new `context`:**
253
+ When calling `update_context`, the `context` parameter string MUST be a high-density, lossless distillation of all the current messages. It must replace the deleted history with actionable intelligence. You must include:
254
+ * **Confirmed Knowledge with Citations:** You MUST retain the `url` for each fact you preserve.** If you lose the `url` now, you will fail the final verification step. Use the format: `[Fact statement] (Verified in: url)`.
255
+ * **Current State & Next Steps:** Where exactly are we in the problem-solving process and a precise plan for the refreshed context.
256
+ You can also include (but not necessary):
257
+ * **Negative Constraints:** Explicitly state what has been tried and FAILED to prevent repetition.
258
+
259
+ * **Limitation:** The `update_context` function can only be called separately; do not call `update_context` function and other fucntions at the same time.
260
+
261
+ **Critical:** You should aim to solve the problem in a single pass if possible. However, if the task requires extended reasoning, apply this tool strategically. Do not overuse it; "over-cleaning" can lead to loss of subtle details. Aim for maximum information density with minimum token usage.
262
+ """,
263
+ "parameters": {
264
+ "type": "object",
265
+ "properties": {
266
+ "context": {
267
+ "type": "string",
268
+ "description": "a high-density, loss-less distillation of the current state include **Confirmed Knowledge with Citations** and **Current State**.",
269
+ }
270
+ },
271
+ "required": ["context"],
272
+ },
273
+ },
274
+ }
275
+
276
+ _FINISH_TOOL = {
277
+ "type": "function",
278
+ "function": {
279
+ "name": "finish",
280
+ "description": "Return the final result when you have a definitive answer. This function signals that your research is complete and you're ready to present the final answer to the user.",
281
+ "parameters": {
282
+ "type": "object",
283
+ "properties": {
284
+ "answer": {
285
+ "type": "string",
286
+ "description": "A succinct, final answer to the user's query",
287
+ },
288
+ "evidences": {
289
+ "type": "array",
290
+ "description": "Array of evidence objects supporting the final answer. Each piece of evidence must cite exactly **one** `url`. If a fact is supported by multiple documents, choose the most authoritative one.",
291
+ "items": {
292
+ "type": "object",
293
+ "properties": {
294
+ "evidence": {
295
+ "type": "string",
296
+ "description": "The specific verified fact or data point (one sentence)",
297
+ },
298
+ "url": {
299
+ "type": "string",
300
+ "description": "The source URL of the evidence",
301
+ },
302
+ },
303
+ "required": ["evidence", "url"],
304
+ },
305
+ },
306
+ "confidence": {
307
+ "type": "string",
308
+ "description": " Your confidence score between 0% and 100% for your answer",
309
+ },
310
+ },
311
+ "required": ["answer", "evidences", "confidence"],
312
+ },
313
+ },
314
+ }
315
+
316
+
317
+ BROWSECOMP_TOOLS = (
318
+ _SEARCH_TOOL,
319
+ _GOOGLE_SCHOLAR_TOOL,
320
+ _VISIT_TOOL,
321
+ _UPDATE_CONTEXT_TOOL,
322
+ _FINISH_TOOL,
323
+ )
324
+
325
+
326
+ def _format_tool_description(tools: tuple[dict, ...]) -> str:
327
+ body = "<tools>\n"
328
+ body += "".join(
329
+ json.dumps(tool, indent=2, ensure_ascii=False) + "\n"
330
+ for tool in tools
331
+ )
332
+ return body + "</tools>\n"
333
+
334
+
335
+ BROWSECOMP_SYSTEM_PROMPT = _SYSTEM_PROMPT_TEMPLATE.format(
336
+ tool_des=_format_tool_description(BROWSECOMP_TOOLS)
337
+ )
338
+ BROWSECOMP_USER_PROMPT = _USER_PROMPT_TEMPLATE
339
+
340
+
341
+ def build_messages(question: str) -> list[dict[str, str]]:
342
+ """Return the initial system/user messages for one BrowseComp agent run."""
343
+
344
+ return [
345
+ {"role": "system", "content": BROWSECOMP_SYSTEM_PROMPT},
346
+ {
347
+ "role": "user",
348
+ "content": BROWSECOMP_USER_PROMPT.format(question=question),
349
+ },
350
+ ]
351
+
352
+
353
+ __all__ = [
354
+ "BROWSECOMP_SYSTEM_PROMPT",
355
+ "BROWSECOMP_USER_PROMPT",
356
+ "build_messages",
357
+ ]
model.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c1de0b2de2fa521726739b4b83d842a2a682a7e33f881911d6ea4c6b7dc086f4
3
+ size 3034299183
model.safetensors.index.json ADDED
The diff for this file is too large to render. See raw diff
 
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
+ }
processor_config.json ADDED
@@ -0,0 +1,50 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "image_processor": {
3
+ "do_convert_rgb": true,
4
+ "do_normalize": true,
5
+ "do_rescale": true,
6
+ "image_mean": [
7
+ 0.5,
8
+ 0.5,
9
+ 0.5
10
+ ],
11
+ "image_processor_type": "Qwen3VLImageProcessor",
12
+ "image_std": [
13
+ 0.5,
14
+ 0.5,
15
+ 0.5
16
+ ],
17
+ "max_pixels": 16777216,
18
+ "merge_size": 2,
19
+ "min_pixels": 65536,
20
+ "patch_size": 16,
21
+ "rescale_factor": 0.00392156862745098,
22
+ "temporal_patch_size": 2
23
+ },
24
+ "processor_class": "Qwen3VLProcessor",
25
+ "video_processor": {
26
+ "do_convert_rgb": true,
27
+ "do_normalize": true,
28
+ "do_rescale": true,
29
+ "fps": 2.0,
30
+ "image_mean": [
31
+ 0.5,
32
+ 0.5,
33
+ 0.5
34
+ ],
35
+ "image_std": [
36
+ 0.5,
37
+ 0.5,
38
+ 0.5
39
+ ],
40
+ "max_frames": 768,
41
+ "max_pixels": 25165824,
42
+ "merge_size": 2,
43
+ "min_frames": 4,
44
+ "min_pixels": 4096,
45
+ "patch_size": 16,
46
+ "rescale_factor": 0.00392156862745098,
47
+ "temporal_patch_size": 2,
48
+ "video_processor_type": "Qwen3VLVideoProcessor"
49
+ }
50
+ }
tokenizer.json ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:06b9509352d2af50381ab2247e083b80d32d5c0aba91c272ca9ff729b6a0e523
3
+ size 19989325
tokenizer_config.json ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "add_prefix_space": false,
3
+ "audio_bos_token": "<|audio_start|>",
4
+ "audio_eos_token": "<|audio_end|>",
5
+ "audio_token": "<|audio_pad|>",
6
+ "backend": "tokenizers",
7
+ "bos_token": null,
8
+ "clean_up_tokenization_spaces": false,
9
+ "eos_token": "<|im_end|>",
10
+ "errors": "replace",
11
+ "image_token": "<|image_pad|>",
12
+ "is_local": true,
13
+ "local_files_only": false,
14
+ "model_max_length": 262144,
15
+ "model_specific_special_tokens": {
16
+ "audio_bos_token": "<|audio_start|>",
17
+ "audio_eos_token": "<|audio_end|>",
18
+ "audio_token": "<|audio_pad|>",
19
+ "image_token": "<|image_pad|>",
20
+ "video_token": "<|video_pad|>",
21
+ "vision_bos_token": "<|vision_start|>",
22
+ "vision_eos_token": "<|vision_end|>"
23
+ },
24
+ "pad_token": "<|endoftext|>",
25
+ "pretokenize_regex": "(?i:'s|'t|'re|'ve|'m|'ll|'d)|[^\\r\\n\\p{L}\\p{N}]?[\\p{L}\\p{M}]+|\\p{N}| ?[^\\s\\p{L}\\p{M}\\p{N}]+[\\r\\n]*|\\s*[\\r\\n]+|\\s+(?!\\S)|\\s+",
26
+ "processor_class": "Qwen3VLProcessor",
27
+ "split_special_tokens": false,
28
+ "tokenizer_class": "Qwen2Tokenizer",
29
+ "unk_token": null,
30
+ "video_token": "<|video_pad|>",
31
+ "vision_bos_token": "<|vision_start|>",
32
+ "vision_eos_token": "<|vision_end|>"
33
+ }
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