evaluator.py 10.2 KB
Newer Older
Leo Gao's avatar
Leo Gao committed
1
2
import collections
import itertools
3
import numpy as np
Leo Gao's avatar
Leo Gao committed
4
import random
5
import lm_eval.api.metrics
6
7
import lm_eval.models
import lm_eval.tasks
8
import lm_eval.api
9
<<<<<<< HEAD
10
from lm_eval.utils import positional_deprecated, run_task_tests, make_table, create_iterator
11
import torch 
12
=======
13
from lm_eval.utils import positional_deprecated, run_task_tests, make_table, get_git_commit_hash
14

15
>>>>>>> upstream/big-refactor
Fabrizio Milo's avatar
Fabrizio Milo committed
16

17
@positional_deprecated
Fabrizio Milo's avatar
Fabrizio Milo committed
18
19
20
21
22
23
24
25
26
27
28
29
30
def simple_evaluate(
    model,
    model_args=None,
    tasks=[],
    num_fewshot=0,
    batch_size=None,
    device=None,
    no_cache=False,
    limit=None,
    bootstrap_iters=100000,
    check_integrity=False,
    decontamination_ngrams_path=None,
):
31

32
    """Instantiate and evaluate a model on a list of tasks.
33

34
35
36
    :param model: Union[str, LM]
        Name of model or LM object, see lm_eval.models.get_model
    :param model_args: Optional[str]
Fabrizio Milo's avatar
Fabrizio Milo committed
37
        String arguments for each model class, see LM.create_from_arg_string.
38
39
        Ignored if `model` argument is a LM object.
    :param tasks: list[Union[str, Task]]
Leo Gao's avatar
Leo Gao committed
40
        List of task names or Task objects. Task objects will be taken to have name task.EVAL_HARNESS_NAME if defined and type(task).__name__ otherwise.
41
42
43
44
45
    :param num_fewshot: int
        Number of examples in few-shot context
    :param batch_size: int, optional
        Batch size for model
    :param device: str, optional
46
        PyTorch device (e.g. "cpu" or "cuda:0") for running models
47
    :param no_cache: bool
Leo Gao's avatar
Leo Gao committed
48
        Whether or not to cache
49
50
51
52
    :param limit: int, optional
        Limit the number of examples per task (only use this for testing)
    :param bootstrap_iters:
        Number of iterations for bootstrap statistics
Stephen Hogg's avatar
Stephen Hogg committed
53
54
    :param check_integrity: bool
        Whether to run the relevant part of the test suite for the tasks
55
    :return
56
        Dictionary of results
57
    """
58
59
60
    random.seed(1234)
    np.random.seed(1234)

61
62
63
    assert tasks != [], "No tasks specified"

    if isinstance(model, str):
Fabrizio Milo's avatar
Fabrizio Milo committed
64
65
        if model_args is None:
            model_args = ""
66
        lm = lm_eval.api.model.get_model(model).create_from_arg_string(
Fabrizio Milo's avatar
Fabrizio Milo committed
67
68
            model_args, {"batch_size": batch_size, "device": device}
        )
69
    else:
70
        assert isinstance(model, lm_eval.api.model.LM)
71
        lm = model
72

73
    task_dict = lm_eval.api.task.get_task_dict(tasks, num_fewshot=num_fewshot)
Jonathan Tow's avatar
Merge  
Jonathan Tow committed
74

Stephen Hogg's avatar
Stephen Hogg committed
75
    if check_integrity:
76
        run_task_tests(task_list=tasks)
Stephen Hogg's avatar
Stephen Hogg committed
77

78
79
80
81
82
    results = evaluate(
        lm=lm,
        task_dict=task_dict,
        num_fewshot=num_fewshot,
        limit=limit,
Niklas Muennighoff's avatar
Niklas Muennighoff committed
83
        bootstrap_iters=bootstrap_iters,
Fabrizio Milo's avatar
Fabrizio Milo committed
84
        decontamination_ngrams_path=decontamination_ngrams_path,
85
    )
86

87
<<<<<<< HEAD
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
    if lm.rank == 0:
        # add info about the model and few shot config
        results["config"] = {
            "model": model,
            "model_args": model_args,
            "num_fewshot": num_fewshot,
            "batch_size": batch_size,
            "device": device,
            "no_cache": no_cache,
            "limit": limit,
            "bootstrap_iters": bootstrap_iters,
        }

        return results
    else:
        return None
104
=======
105
106
107
108
109
110
111
112
113
    # add info about the model and few shot config
    results["config"] = {
        "model": model,
        "model_args": model_args,
        "num_fewshot": num_fewshot,
        "batch_size": batch_size,
        "device": device,
        "no_cache": no_cache,
        "limit": limit,
114
        "bootstrap_iters": bootstrap_iters,
115
    }
116
    results["git_hash"] = get_git_commit_hash()
117
>>>>>>> upstream/big-refactor
118

Leo Gao's avatar
Leo Gao committed
119

Fabrizio Milo's avatar
Fabrizio Milo committed
120

121
decontaminate_suffix = "_decontaminate"
Leo Gao's avatar
Leo Gao committed
122

Fabrizio Milo's avatar
Fabrizio Milo committed
123

124
@positional_deprecated
Fabrizio Milo's avatar
Fabrizio Milo committed
125
126
127
128
129
130
131
132
def evaluate(
    lm,
    task_dict,
    num_fewshot=0,
    limit=None,
    bootstrap_iters=100000,
    decontamination_ngrams_path=None,
):
133
134
135
136
137
    """Instantiate and evaluate a model on a list of tasks.

    :param lm: obj
        Language Model
    :param task_dict: dict[str, Task]
Leo Gao's avatar
Leo Gao committed
138
        Dictionary of tasks. Tasks will be taken to have name task.EVAL_HARNESS_NAME if defined and type(task).__name__ otherwise.
139
140
141
142
143
144
145
146
147
    :param num_fewshot: int
        Number of examples in few-shot context
    :param limit: int, optional
        Limit the number of examples per task (only use this for testing)
    :param bootstrap_iters:
        Number of iterations for bootstrap statistics
    :return
        Dictionary of results
    """
148

Leo Gao's avatar
Leo Gao committed
149
    decontaminate = decontamination_ngrams_path is not None
150

Leo Gao's avatar
Leo Gao committed
151
    results = collections.defaultdict(dict)
Leo Gao's avatar
Leo Gao committed
152
    versions = collections.defaultdict(dict)
Leo Gao's avatar
Leo Gao committed
153
154
155
156
157
158

    requests = collections.defaultdict(list)
    requests_origin = collections.defaultdict(list)

    docs = {}

159
    # get lists of each type of request
160
    for task_name, task in task_dict.items():
Leo Gao's avatar
Leo Gao committed
161
        versions[task_name] = task.VERSION
162
    
Leo Gao's avatar
Leo Gao committed
163
        # deterministically shuffle docs and chop off the first `limit` because sometimes docs are in some kind of order
164
165
166
167
168
        # task_docs = list(task_doc_func())
        # rnd = random.Random()
        # rnd.seed(42)
        # rnd.shuffle(task_docs)

169
        task.build_all_requests(limit=limit, rank = lm.rank, world_size = lm.world_size)
170
        # aggregate Instances by LM method requested to get output.
171
172
        reqtype = "loglikelihood" if task.OUTPUT_TYPE == "multiple_choice" else task.OUTPUT_TYPE #TODO: this is hacky, fix in task.py
        requests[reqtype].extend(task.instances) 
173
174
175
176
177

        if lm.world_size > 1:
            instances_rnk = torch.tensor(len(task._instances), device = lm.device)
            gathered_item = lm.accelerator.gather(instances_rnk).cpu().detach().numpy().tolist()

178
            # compute number of pseudobatches to pad with (FSDP/DDP require even batches among ranks)
179
            numpad = max(gathered_item) - gathered_item[lm.rank]
180
181
    
    ### Run LM on inputs, get all outputs ###
Leo Gao's avatar
Leo Gao committed
182
183
    # execute each type of request
    for reqtype, reqs in requests.items():
Leo Gao's avatar
Leo Gao committed
184
        print("Running", reqtype, "requests")
185
186
187
188
189
        # create `K` copies of each request `req` based off `K = req.repeats`
        cloned_reqs = []
        for req in reqs:
            cloned_reqs.extend([req] * req.repeats)
        
190
        if (lm.world_size > 1) and (numpad > 0):
191
192
193
            for _ in range(numpad):
                cloned_reqs.extend([req] * req.repeats)

194
195
196
197
198
199
200
        # run requests through model
        resps = getattr(lm, reqtype)(cloned_reqs)

        # put responses from model into a list of length K for each request.
        for x, req in zip(resps, cloned_reqs):
            req.resps.append(x)

201
202
203
    if lm.world_size > 1:
        lm.accelerator.wait_for_everyone()

204
205
206
207
208
209
210
211
    ### Postprocess outputs ###
    # TODO: del model here, maybe (idea: allow user to specify device of e.g. reward model separately)
    for task_name, task in task_dict.items():
        task.apply_filters()


    ### Collect values of metrics on all datapoints ###
    # TODO: make metric configurable, add metric registry 
Leo Gao's avatar
Leo Gao committed
212
213
214
    vals = collections.defaultdict(list)

    # unpack results and sort back in order and return control to Task
215
216
217
218
    for task_name, task in task_dict.items():
        # calculate values for each filter setup (TODO: make getting list of keys cleaner)
        # TODO: make it possible to use a different metric per key
        for key in task.instances[0].filtered_resps.keys():
Benjamin Fattori's avatar
Benjamin Fattori committed
219
            doc_iterator = itertools.islice(enumerate(task.test_docs()), lm.rank, limit, lm.world_size) if task.has_test_docs() else itertools.islice(enumerate(task.validation_docs()), lm.rank, limit, lm.world_size)
220
            for doc_id, doc in doc_iterator:
221
222
                # subset instances to only this document id ; sort by idx
                requests = list(filter(lambda x: x.doc_id == doc_id, task.instances))
223
                requests.sort(key=lambda x: x.idx)
224
225
226
227
                metrics = task.process_results(doc, [req.filtered_resps[key] for req in requests])
                for metric, value in metrics.items():
                    vals[(task_name, key, metric)].append(value)
    
228
229
230
231
232
233
234
235
236
    if lm.world_size > 1:
        # if multigpu, then gather data across all ranks    
        vals_torch = collections.defaultdict(list)
        for (task_name, key, metric), items in vals.items():
            
            numitem = 0 
            if type(items[0]) == tuple:
                numitem = len(items[0]) 
    
237
238
            # distributed gather requires all ranks to have same dimensions
            # so we pad out with float32 min value
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
            pad_value = torch.finfo(torch.float32).min
            metrics_tensor = torch.tensor(items, device = lm.device)
            
            original_dtype = metrics_tensor.dtype # store original dtype 
            torch_device_tensor = lm.accelerator.pad_across_processes(metrics_tensor.to(torch.float32), pad_index = pad_value)
            gathered_item = lm.accelerator.gather(torch_device_tensor)
    
            if numitem > 0:
                gathered_filtered = gathered_item[gathered_item[:,0] != pad_value]
            else:
                gathered_filtered = gathered_item[gathered_item != pad_value]
                
            gathered_item = gathered_filtered.to(original_dtype).cpu().detach().numpy().tolist()
            # reconvert if we were passed a tuple of values
            if numitem > 0:
                gathered_item = [tuple(g) for g in gathered_item]
    
            if lm.rank == 0:
                vals_torch[(task_name, key, metric)] = gathered_item
    
        vals = vals_torch
260
261


262
263
264
265
266
267
    if lm.rank == 0:
        ### Aggregate results over all datapoints ###
        # aggregate results ; run bootstrap CIs
        for (task_name, key, metric), items in vals.items():
            task = task_dict[task_name]
            results[task_name][metric + " - filter=" + key] = task.aggregation()[metric](items)
Leo Gao's avatar
Leo Gao committed
268

269
270
            # hotfix: bleu, chrf, ter seem to be really expensive to bootstrap
            # so we run them less iterations. still looking for a cleaner way to do this
271

272
273
274
275
276
277
278
279
280
            stderr = lm_eval.api.metrics.stderr_for_metric(
                metric=task.aggregation()[metric],
                bootstrap_iters=min(bootstrap_iters, 1000)
                if metric in ["bleu", "chrf", "ter"]
                else bootstrap_iters,
            )

            if stderr is not None:
                results[task_name][metric + " - filter=" + key + "_stderr"] = stderr(items)
Fabrizio Milo's avatar
Fabrizio Milo committed
281

282
        return {"results": dict(results), "versions": dict(versions)}
Fabrizio Milo's avatar
Fabrizio Milo committed
283

284
285
    else:
        return None