File size: 8,926 Bytes
e6f9ec5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
import json
import random
from collections import defaultdict
from functools import partial

import numpy as np

from verl.utils.reward_score import math, gsm8k, math_verify, math_robust
from verl.utils.reward_score.prime_math import compute_score as prime_math_compute_score

def read_jsonl(file_path):
    data_list = []
    with open(file_path, 'r', encoding='utf-8') as file:
        lines = file.readlines()
        for line in lines:
            temp_json = json.loads(line)
            data_list.append(temp_json)

    return data_list

def write_jsonl(data_list, output_path):
    with open(output_path, 'w', encoding='utf-8') as file:
        for item in data_list:
            file.write(json.dumps(item, ensure_ascii=False))
            file.write('\n')

    print(f"文件生成至{output_path}.")

def compute_score(solution_str, ground_truth, data_source):
    if data_source == "openai/gsm8k":
        res = gsm8k.compute_score(solution_str, ground_truth, method='flexible')
        # res = math_verify.compute_score(solution_str, ground_truth)
        # res = math_robust.compute_score(solution_str, ground_truth)
    elif data_source in ["math500"]:
        res = math_verify.compute_score(solution_str, ground_truth)
        # res = math_robust.compute_score(solution_str, ground_truth)
    elif data_source in ["aime2024", "aime2025", "amc23", "minerva"]:
        # res = math_verify.compute_score(solution_str, ground_truth)
        res = math_robust.compute_score(solution_str, ground_truth)
    elif data_source in ['olympiadbench', "olympiads"]:
        # ground_truth 是 list,取第一个元素;去掉 LaTeX 的 $ 符号
        if isinstance(ground_truth, list):
            ground_truth = ground_truth[0]
        ground_truth = str(ground_truth).strip().strip('$').strip()
        res = prime_math_compute_score(solution_str, ground_truth)
        # prime_math 返回 (is_correct, format_correctness, extracted),取第一个
        if isinstance(res, tuple):
            res = res[0]
    else:
        raise NotImplementedError(f"No support score function for datasource - {data_source}")

    return res

def bootstrap_metric(data, subset_size, reduce_fns, n_bootstrap=1000, seed=42):
    """
    有放回 bootstrap 采样,估计各 reduce_fn 的均值和标准差。
    与 verl metric_utils.bootstrap_metric 逻辑一致。
    返回 [(mean, std), ...] 对应每个 reduce_fn。
    """
    np.random.seed(seed)
    bootstrap_metric_lsts = [[] for _ in range(len(reduce_fns))]
    for _ in range(n_bootstrap):
        bootstrap_idxs = np.random.choice(len(data), size=subset_size, replace=True)
        bootstrap_data = [data[i] for i in bootstrap_idxs]
        for i, reduce_fn in enumerate(reduce_fns):
            bootstrap_metric_lsts[i].append(reduce_fn(bootstrap_data))
    return [(np.mean(lst), np.std(lst)) for lst in bootstrap_metric_lsts]


def _eval_scores_for_item(item):
    """
    对一条数据的所有候选答案逐一打分,返回 (data_source, score_list)。
    """
    data_source = item['data_source']
    ground_truth = item['answer']
    total_solution_list = item["candidate_generated"] if len(item["candidate_generated"]) > 0 else [item['generated']]
    score_list = [
        compute_score(solution_str=sol, ground_truth=ground_truth, data_source=data_source)
        for sol in total_solution_list
    ]
    return data_source, score_list


def single_eval(item, eval_mode="acc", at_num=None):
    """
    eval_mode: options in ['acc', 'avg@k', 'pass@k']

    avg@k  : mean@N,即所有候选答案的均值(at_num 忽略,始终使用全部候选)。
    pass@k : best@k/mean,用有放回 bootstrap 采样(1000次)从 N 个候选中取 k 个的最大值均值。
             at_num 指定 k;若 at_num=None 则 k=N(等价于 avg@k)。
    acc    : 仅对第一条候选答案评分(兼容旧逻辑)。
    """
    data_source = item['data_source']
    ground_truth = item['answer']
    total_solution_list = item["candidate_generated"] if len(item["candidate_generated"]) > 0 else [item['generated']]
    candidate_solution_num = len(total_solution_list)

    if at_num is not None:
        if candidate_solution_num < at_num:
            raise ValueError(
                f"at_num ({at_num}) exceeds the number of rollouts ({candidate_solution_num})"
            )
    else:
        at_num = candidate_solution_num

    if eval_mode == "acc":
        res = compute_score(
            solution_str=total_solution_list[0],
            ground_truth=ground_truth,
            data_source=data_source,
        )

    elif eval_mode == "avg@k":
        # mean@N:所有候选答案分数的均值,与 verl 的 mean@N 一致
        score_list = [
            compute_score(solution_str=sol, ground_truth=ground_truth, data_source=data_source)
            for sol in total_solution_list
        ]
        res = float(np.mean(score_list))

    elif eval_mode == "pass@k":
        # best@k/mean:有放回 bootstrap,每次从 N 个候选中采样 k 个取 max,重复 1000 次后取均值
        # 与 verl metric_utils.process_validation_metrics 中 best@N/mean 逻辑一致
        score_list = [
            compute_score(solution_str=sol, ground_truth=ground_truth, data_source=data_source)
            for sol in total_solution_list
        ]
        if at_num == candidate_solution_num and at_num == 1:
            # 只有 1 条候选,直接返回该分数
            res = float(score_list[0])
        else:
            [(bon_mean, _)] = bootstrap_metric(
                data=score_list,
                subset_size=at_num,
                reduce_fns=[np.max],
                n_bootstrap=1000,
                seed=42,
            )
            res = float(bon_mean)

    else:
        raise NotImplementedError(f"Not implement eval mode {eval_mode}.")

    return res, data_source, at_num


def eval_result(data, eval_mode="acc", at_num=None, print_avg=False):
    """
    聚合方式与 verl process_validation_metrics 一致:
      1. 对每条 item(每个 prompt)计算指标值
      2. 先在 data_source 内对所有 prompt 取均值(宏平均)
      3. 再跨 data_source 取均值(macro score)
    """
    # data_source -> list of per-prompt scores
    ds2scores = defaultdict(list)
    ds2at_num = {}

    for item in data:
        score, data_source, real_at_num = single_eval(item, eval_mode=eval_mode, at_num=at_num)
        ds2scores[data_source].append(score)
        ds2at_num[data_source] = real_at_num

    total_count = 0
    sum_score = 0
    macro_score = 0
    total_data_source = len(ds2scores)

    for data_source, scores in ds2scores.items():
        single_score = float(np.mean(scores))
        macro_score += single_score
        sum_score += sum(scores)
        total_count += len(scores)
        real_eval_mode = eval_mode.replace('k', str(ds2at_num[data_source]))
        print(f"[{data_source}]--{real_eval_mode}: {single_score * 100:.3g}%")

    if print_avg:
        print(f"Micro Score: {(sum_score / total_count) * 100:.3g}%")
        print(f"Macro Score: {(macro_score / total_data_source) * 100:.3g}%")


if __name__ == "__main__":
    # DATA_PATH = "data"
    RESULT_PATH = "results/math_0408"

    model_dict = {
    'DeepSeek-R1-Distill-Qwen-1.5B': 'models/DeepSeek-R1-Distill-Qwen-1.5B',
    'Qwen3-1.7B-Base': 'models/Qwen3-1.7B-Base',
}



    # dataset_dict = {
    #     "aime2024": 1,
    #     "aime2025": 1,
    #     # "amc23": 1,
    #     # "math500": 1,
    #     # 'minerva': 2,
    #     # "olympiads": 4,
    # }
    dataset_dict = {
        "aime2024": 6,
        "aime2025": 6,
        # "amc23": 5,
        # "math500": 5,
        # 'minerva': 6,
        # "olympiadbench": 12,
    }

    eval_list = []

    max_model_len=32768

    for k, v in model_dict.items():
        print(f"\n===== {k} =====\n")
        for dataset, split_num in dataset_dict.items():
            if 'aime' in dataset:
                max_token=38912
            else:
                max_token=32768

            max_token=16384

            max_token = min(max_token, max_model_len)
            RESULT_JSON = [f"{RESULT_PATH}/{dataset}_{k}_len{max_token}_t0.6_{i}_result.json" for i in range(split_num)]

            total_data_list = []

            for file_path in RESULT_JSON:
                if '2024' in dataset:
                    datasource = 'aime2024'
                elif '2025' in dataset:
                    datasource = 'aime2025'
                else:
                    datasource = dataset
                part_data = read_jsonl(file_path)
                for idx, item in enumerate(part_data):
                    part_data[idx]['data_source'] = datasource

                total_data_list.extend(part_data)

            # for i in [1,2,4,8,16]:
            eval_result(total_data_list, eval_mode="pass@k", at_num=32)

        print("\n")