evalscope 0.8.2__py3-none-any.whl → 0.10.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- evalscope/__init__.py +2 -0
- evalscope/arguments.py +11 -3
- evalscope/backend/rag_eval/clip_benchmark/tasks/zeroshot_classification.py +0 -1
- evalscope/backend/rag_eval/utils/llm.py +1 -1
- evalscope/benchmarks/__init__.py +20 -1
- evalscope/benchmarks/arc/__init__.py +0 -5
- evalscope/benchmarks/arc/arc_adapter.py +24 -102
- evalscope/benchmarks/bbh/__init__.py +0 -4
- evalscope/benchmarks/bbh/bbh_adapter.py +20 -90
- evalscope/benchmarks/benchmark.py +70 -59
- evalscope/benchmarks/ceval/__init__.py +0 -5
- evalscope/benchmarks/ceval/ceval_adapter.py +24 -125
- evalscope/benchmarks/cmmlu/__init__.py +0 -5
- evalscope/benchmarks/cmmlu/cmmlu_adapter.py +22 -117
- evalscope/benchmarks/competition_math/__init__.py +0 -5
- evalscope/benchmarks/competition_math/competition_math_adapter.py +29 -371
- evalscope/benchmarks/data_adapter.py +115 -87
- evalscope/benchmarks/general_qa/__init__.py +0 -5
- evalscope/benchmarks/general_qa/general_qa_adapter.py +23 -79
- evalscope/benchmarks/gsm8k/__init__.py +0 -4
- evalscope/benchmarks/gsm8k/gsm8k_adapter.py +21 -101
- evalscope/benchmarks/hellaswag/__init__.py +0 -5
- evalscope/benchmarks/hellaswag/hellaswag_adapter.py +32 -99
- evalscope/benchmarks/humaneval/__init__.py +0 -4
- evalscope/benchmarks/humaneval/humaneval_adapter.py +18 -120
- evalscope/benchmarks/ifeval/__init__.py +0 -0
- evalscope/benchmarks/ifeval/ifeval_adapter.py +57 -0
- evalscope/benchmarks/ifeval/instructions.py +1478 -0
- evalscope/benchmarks/ifeval/instructions_registry.py +188 -0
- evalscope/benchmarks/ifeval/instructions_util.py +1670 -0
- evalscope/benchmarks/ifeval/utils.py +134 -0
- evalscope/benchmarks/iquiz/__init__.py +0 -0
- evalscope/benchmarks/iquiz/iquiz_adapter.py +63 -0
- evalscope/benchmarks/mmlu/__init__.py +0 -5
- evalscope/benchmarks/mmlu/mmlu_adapter.py +32 -130
- evalscope/benchmarks/mmlu_pro/__init__.py +0 -0
- evalscope/benchmarks/mmlu_pro/mmlu_pro_adapter.py +110 -0
- evalscope/benchmarks/race/__init__.py +0 -5
- evalscope/benchmarks/race/race_adapter.py +26 -123
- evalscope/benchmarks/trivia_qa/__init__.py +0 -5
- evalscope/benchmarks/trivia_qa/trivia_qa_adapter.py +23 -99
- evalscope/benchmarks/truthful_qa/__init__.py +0 -5
- evalscope/benchmarks/truthful_qa/truthful_qa_adapter.py +29 -88
- evalscope/cli/cli.py +2 -0
- evalscope/cli/start_app.py +29 -0
- evalscope/collections/__init__.py +3 -0
- evalscope/collections/evaluator.py +198 -0
- evalscope/collections/sampler.py +138 -0
- evalscope/collections/schema.py +126 -0
- evalscope/config.py +7 -5
- evalscope/constants.py +9 -26
- evalscope/evaluator/evaluator.py +87 -121
- evalscope/evaluator/reviewer/auto_reviewer.py +12 -4
- evalscope/metrics/__init__.py +3 -0
- evalscope/metrics/bundled_rouge_score/rouge_scorer.py +1 -1
- evalscope/metrics/math_accuracy.py +193 -50
- evalscope/metrics/metrics.py +18 -6
- evalscope/metrics/named_metrics.py +17 -0
- evalscope/metrics/rouge_metric.py +13 -8
- evalscope/models/__init__.py +14 -1
- evalscope/models/base_adapter.py +52 -0
- evalscope/models/chat_adapter.py +138 -0
- evalscope/models/choice_adapter.py +211 -0
- evalscope/models/custom_adapter.py +67 -0
- evalscope/models/local_model.py +74 -0
- evalscope/models/model.py +141 -0
- evalscope/models/server_adapter.py +111 -0
- evalscope/perf/__init__.py +1 -0
- evalscope/perf/main.py +0 -1
- evalscope/perf/plugin/api/custom_api.py +1 -1
- evalscope/perf/plugin/api/openai_api.py +1 -1
- evalscope/perf/plugin/datasets/flickr8k.py +1 -1
- evalscope/perf/plugin/datasets/longalpaca.py +1 -1
- evalscope/report/__init__.py +5 -0
- evalscope/report/app.py +506 -0
- evalscope/report/combinator.py +73 -0
- evalscope/report/generator.py +80 -0
- evalscope/report/utils.py +133 -0
- evalscope/run.py +48 -72
- evalscope/run_arena.py +1 -1
- evalscope/summarizer.py +1 -1
- evalscope/utils/__init__.py +1 -1
- evalscope/utils/chat_service.py +5 -4
- evalscope/utils/io_utils.py +8 -0
- evalscope/utils/logger.py +5 -0
- evalscope/utils/model_utils.py +15 -2
- evalscope/utils/utils.py +3 -25
- evalscope/version.py +2 -2
- {evalscope-0.8.2.dist-info → evalscope-0.10.0.dist-info}/METADATA +115 -21
- {evalscope-0.8.2.dist-info → evalscope-0.10.0.dist-info}/RECORD +99 -78
- tests/cli/test_collection.py +57 -0
- tests/cli/test_run.py +52 -1
- tests/rag/test_mteb.py +3 -2
- evalscope/models/api/__init__.py +0 -3
- evalscope/models/dummy_chat_model.py +0 -49
- evalscope/models/model_adapter.py +0 -525
- evalscope/models/openai_model.py +0 -103
- evalscope/tools/__init__.py +0 -1
- evalscope/tools/combine_reports.py +0 -133
- evalscope/tools/gen_mmlu_subject_mapping.py +0 -90
- /evalscope/{tools/rewrite_eval_results.py → models/custom/dummy_model.py} +0 -0
- /evalscope/{models/api → third_party/longbench_write/tools}/openai_api.py +0 -0
- {evalscope-0.8.2.dist-info → evalscope-0.10.0.dist-info}/LICENSE +0 -0
- {evalscope-0.8.2.dist-info → evalscope-0.10.0.dist-info}/WHEEL +0 -0
- {evalscope-0.8.2.dist-info → evalscope-0.10.0.dist-info}/entry_points.txt +0 -0
- {evalscope-0.8.2.dist-info → evalscope-0.10.0.dist-info}/top_level.txt +0 -0
evalscope/metrics/metrics.py
CHANGED
|
@@ -1,18 +1,15 @@
|
|
|
1
1
|
# Copyright (c) Alibaba, Inc. and its affiliates.
|
|
2
2
|
# Copyright (c) EleutherAI. and its affiliates.
|
|
3
3
|
# Copyright (c) OpenAI. and its affiliates.
|
|
4
|
+
|
|
4
5
|
import itertools
|
|
5
|
-
import jieba
|
|
6
6
|
import math
|
|
7
7
|
import numpy as np
|
|
8
8
|
import random
|
|
9
9
|
import sacrebleu
|
|
10
|
-
import sklearn.metrics
|
|
11
10
|
from collections import defaultdict
|
|
12
11
|
from collections.abc import Iterable
|
|
13
|
-
from
|
|
14
|
-
from nltk.translate.bleu_score import sentence_bleu
|
|
15
|
-
from typing import Dict, List, Union
|
|
12
|
+
from typing import TYPE_CHECKING, Dict, List, Union
|
|
16
13
|
|
|
17
14
|
|
|
18
15
|
def mean(arr):
|
|
@@ -38,6 +35,8 @@ def median(arr):
|
|
|
38
35
|
|
|
39
36
|
|
|
40
37
|
def matthews_corrcoef(items):
|
|
38
|
+
import sklearn.metrics
|
|
39
|
+
|
|
41
40
|
unzipped_list = list(zip(*items))
|
|
42
41
|
golds = unzipped_list[0]
|
|
43
42
|
preds = unzipped_list[1]
|
|
@@ -45,6 +44,8 @@ def matthews_corrcoef(items):
|
|
|
45
44
|
|
|
46
45
|
|
|
47
46
|
def f1_score(items):
|
|
47
|
+
import sklearn.metrics
|
|
48
|
+
|
|
48
49
|
unzipped_list = list(zip(*items))
|
|
49
50
|
golds = unzipped_list[0]
|
|
50
51
|
preds = unzipped_list[1]
|
|
@@ -103,12 +104,20 @@ def perplexity(items):
|
|
|
103
104
|
return math.exp(-mean(items))
|
|
104
105
|
|
|
105
106
|
|
|
106
|
-
def weighted_mean(items) -> float:
|
|
107
|
+
def weighted_mean(items: List) -> float:
|
|
107
108
|
# e.g. [(0,1), (0.5,1), (1,1)]
|
|
108
109
|
a, b = zip(*items)
|
|
109
110
|
return sum(a) / sum(b)
|
|
110
111
|
|
|
111
112
|
|
|
113
|
+
def micro_mean(items):
|
|
114
|
+
return sum([item.score * item.num for item in items]) / sum([item.num for item in items])
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def macro_mean(items):
|
|
118
|
+
return sum([item.score for item in items]) / len(items)
|
|
119
|
+
|
|
120
|
+
|
|
112
121
|
def weighted_perplexity(items):
|
|
113
122
|
return math.exp(-weighted_mean(items))
|
|
114
123
|
|
|
@@ -150,6 +159,9 @@ def bleu_ngram_one_sample(predict, reference):
|
|
|
150
159
|
}
|
|
151
160
|
|
|
152
161
|
"""
|
|
162
|
+
import jieba
|
|
163
|
+
from nltk import word_tokenize
|
|
164
|
+
from nltk.translate.bleu_score import sentence_bleu
|
|
153
165
|
|
|
154
166
|
def is_contains_chinese(strs):
|
|
155
167
|
for _char in strs:
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Callable
|
|
3
|
+
|
|
4
|
+
from evalscope.metrics.metrics import mean, weighted_mean
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@dataclass
|
|
8
|
+
class Metric:
|
|
9
|
+
name: str = 'default_metric'
|
|
10
|
+
object: Callable = field(default_factory=lambda: mean)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
AverageAccuracy = Metric(name='AverageAccuracy', object=mean)
|
|
14
|
+
WeightedAverageAccuracy = Metric(name='WeightedAverageAccuracy', object=weighted_mean)
|
|
15
|
+
AverageBLEU = Metric(name='AverageBLEU', object=mean)
|
|
16
|
+
WeightedAverageBLEU = Metric(name='WeightedAverageBLEU', object=weighted_mean)
|
|
17
|
+
Pass1 = Metric(name='Pass@1', object=mean)
|
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
# Copyright (c) Alibaba, Inc. and its affiliates.
|
|
2
2
|
|
|
3
3
|
import jieba
|
|
4
|
-
import logging
|
|
5
4
|
from collections import defaultdict
|
|
6
|
-
from pathlib import Path
|
|
7
5
|
from rouge_chinese import Rouge
|
|
8
6
|
from statistics import mean
|
|
9
7
|
from tqdm import tqdm
|
|
10
8
|
|
|
11
9
|
from evalscope.constants import MetricsConstant
|
|
12
10
|
from evalscope.metrics.bundled_rouge_score import rouge_scorer
|
|
11
|
+
from evalscope.utils.logger import get_logger
|
|
12
|
+
|
|
13
|
+
logger = get_logger()
|
|
13
14
|
|
|
14
15
|
|
|
15
16
|
class DummyTokenizer:
|
|
@@ -18,10 +19,6 @@ class DummyTokenizer:
|
|
|
18
19
|
return text.split()
|
|
19
20
|
|
|
20
21
|
|
|
21
|
-
HERE = Path(__file__).absolute().parent
|
|
22
|
-
|
|
23
|
-
logger = logging.getLogger(__name__)
|
|
24
|
-
|
|
25
22
|
scorer = rouge_scorer.RougeScorer(['rouge1', 'rouge2', 'rougeL'], tokenizer=DummyTokenizer())
|
|
26
23
|
zh_scorer = Rouge()
|
|
27
24
|
|
|
@@ -58,7 +55,11 @@ def compute_rouge_score_one_sample_zh(predict, reference):
|
|
|
58
55
|
p = ' '.join(jieba.cut(p)) if is_contains_chinese(p) else p
|
|
59
56
|
r = ' '.join(jieba.cut(r)) if is_contains_chinese(r) else r
|
|
60
57
|
|
|
61
|
-
|
|
58
|
+
try:
|
|
59
|
+
score = zh_scorer.get_scores(p, r, ignore_empty=True)[0]
|
|
60
|
+
except Exception as e:
|
|
61
|
+
logger.warning(f'rouge score error: {p} {r} {e}')
|
|
62
|
+
continue
|
|
62
63
|
result['rouge-1-r'] = score['rouge-1']['r']
|
|
63
64
|
result['rouge-1-p'] = score['rouge-1']['p']
|
|
64
65
|
result['rouge-1-f'] = score['rouge-1']['f']
|
|
@@ -75,7 +76,11 @@ def compute_rouge_score_one_sample_zh(predict, reference):
|
|
|
75
76
|
def compute_rouge_score_one_sample(predict, reference):
|
|
76
77
|
result = dict()
|
|
77
78
|
for p, r in zip(predict, reference):
|
|
78
|
-
|
|
79
|
+
try:
|
|
80
|
+
score = scorer.score(p, r)
|
|
81
|
+
except Exception as e:
|
|
82
|
+
logger.warning(f'rouge score error: {p} {r} {e}')
|
|
83
|
+
continue
|
|
79
84
|
result['rouge-1-r'] = score['rouge1'].recall
|
|
80
85
|
result['rouge-1-p'] = score['rouge1'].precision
|
|
81
86
|
result['rouge-1-f'] = score['rouge1'].fmeasure
|
evalscope/models/__init__.py
CHANGED
|
@@ -1,3 +1,16 @@
|
|
|
1
1
|
# Copyright (c) Alibaba, Inc. and its affiliates.
|
|
2
2
|
|
|
3
|
-
from evalscope.models.
|
|
3
|
+
from evalscope.models.base_adapter import BaseModelAdapter, initialize_model_adapter
|
|
4
|
+
from evalscope.models.chat_adapter import ChatGenerationModelAdapter
|
|
5
|
+
from evalscope.models.choice_adapter import ContinuationLogitsModelAdapter, MultiChoiceModelAdapter
|
|
6
|
+
from evalscope.models.custom import CustomModel
|
|
7
|
+
from evalscope.models.custom_adapter import CustomModelAdapter
|
|
8
|
+
from evalscope.models.local_model import LocalModel, get_local_model
|
|
9
|
+
from evalscope.models.model import BaseModel, ChatBaseModel, OpenAIModel
|
|
10
|
+
from evalscope.models.server_adapter import ServerModelAdapter
|
|
11
|
+
|
|
12
|
+
__all__ = [
|
|
13
|
+
'CustomModel', 'BaseModel', 'ChatBaseModel', 'OpenAIModel', 'BaseModelAdapter', 'ChatGenerationModelAdapter',
|
|
14
|
+
'MultiChoiceModelAdapter', 'ContinuationLogitsModelAdapter', 'CustomModelAdapter', 'ServerModelAdapter',
|
|
15
|
+
'LocalModel', 'get_local_model', 'initialize_model_adapter'
|
|
16
|
+
]
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import torch
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Optional, Union
|
|
4
|
+
|
|
5
|
+
from evalscope.constants import EvalType
|
|
6
|
+
from evalscope.models.custom import CustomModel
|
|
7
|
+
from evalscope.models.local_model import LocalModel
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from evalscope.config import TaskConfig
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class BaseModelAdapter(ABC):
|
|
14
|
+
|
|
15
|
+
def __init__(self, model: Optional[Union[LocalModel, CustomModel]], **kwargs):
|
|
16
|
+
if model is None:
|
|
17
|
+
self.model_cfg = kwargs.get('model_cfg', None)
|
|
18
|
+
elif isinstance(model, LocalModel):
|
|
19
|
+
self.model = model.model
|
|
20
|
+
self.model_id = model.model_id
|
|
21
|
+
self.model_revision = model.model_revision
|
|
22
|
+
self.device = model.device
|
|
23
|
+
self.tokenizer = model.tokenizer
|
|
24
|
+
self.model_cfg = model.model_cfg
|
|
25
|
+
elif isinstance(model, CustomModel):
|
|
26
|
+
self.model_cfg = model.config
|
|
27
|
+
else:
|
|
28
|
+
raise ValueError(f'Unsupported model type: {type(model)}')
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
@torch.no_grad()
|
|
32
|
+
def predict(self, *args, **kwargs) -> Any:
|
|
33
|
+
raise NotImplementedError
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def initialize_model_adapter(task_cfg: 'TaskConfig', model_adapter_cls: 'BaseModelAdapter', base_model: 'LocalModel'):
|
|
37
|
+
"""Initialize the model adapter based on the task configuration."""
|
|
38
|
+
if task_cfg.dry_run:
|
|
39
|
+
from evalscope.models.model import DummyChatModel
|
|
40
|
+
return DummyChatModel(model_cfg=dict())
|
|
41
|
+
elif task_cfg.eval_type == EvalType.CUSTOM:
|
|
42
|
+
if not isinstance(task_cfg.model, CustomModel):
|
|
43
|
+
raise ValueError(f'Expected evalscope.models.custom.CustomModel, but got {type(task_cfg.model)}.')
|
|
44
|
+
from evalscope.models import CustomModelAdapter
|
|
45
|
+
return CustomModelAdapter(custom_model=task_cfg.model)
|
|
46
|
+
elif task_cfg.eval_type == EvalType.SERVICE:
|
|
47
|
+
from evalscope.models import ServerModelAdapter
|
|
48
|
+
return ServerModelAdapter(
|
|
49
|
+
api_url=task_cfg.api_url, model_id=task_cfg.model, api_key=task_cfg.api_key, seed=task_cfg.seed)
|
|
50
|
+
else:
|
|
51
|
+
return model_adapter_cls(
|
|
52
|
+
model=base_model, generation_config=task_cfg.generation_config, chat_template=task_cfg.chat_template)
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import time
|
|
3
|
+
import torch
|
|
4
|
+
from typing import Union
|
|
5
|
+
|
|
6
|
+
from evalscope.models.base_adapter import BaseModelAdapter
|
|
7
|
+
from evalscope.models.local_model import LocalModel
|
|
8
|
+
from evalscope.utils.chat_service import ChatCompletionResponse, ChatCompletionResponseChoice, ChatMessage
|
|
9
|
+
from evalscope.utils.logger import get_logger
|
|
10
|
+
from evalscope.utils.model_utils import fix_do_sample_warning
|
|
11
|
+
|
|
12
|
+
logger = get_logger()
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ChatGenerationModelAdapter(BaseModelAdapter):
|
|
16
|
+
"""
|
|
17
|
+
Chat generation model adapter.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, model: LocalModel, **kwargs):
|
|
21
|
+
super().__init__(model)
|
|
22
|
+
|
|
23
|
+
self.generation_config = self._parse_generation_config(self.tokenizer, self.model)
|
|
24
|
+
|
|
25
|
+
custom_generation_config = kwargs.pop('generation_config', None)
|
|
26
|
+
custom_chat_template = kwargs.pop('chat_template', None)
|
|
27
|
+
|
|
28
|
+
if custom_generation_config:
|
|
29
|
+
logger.info('Updating generation config ...')
|
|
30
|
+
self.generation_config.update(**custom_generation_config)
|
|
31
|
+
|
|
32
|
+
if custom_chat_template:
|
|
33
|
+
self.tokenizer.chat_template = custom_chat_template
|
|
34
|
+
logger.info(f'Using custom chat template: {custom_chat_template}')
|
|
35
|
+
|
|
36
|
+
def _parse_generation_config(self, tokenizer, model):
|
|
37
|
+
from modelscope import GenerationConfig
|
|
38
|
+
|
|
39
|
+
generation_config = getattr(model, 'generation_config', GenerationConfig(do_sample=False))
|
|
40
|
+
|
|
41
|
+
try:
|
|
42
|
+
remote_config = GenerationConfig.from_pretrained(
|
|
43
|
+
self.model_id, revision=self.model_revision, trust_remote_code=True)
|
|
44
|
+
generation_config.update(**remote_config.to_dict())
|
|
45
|
+
except Exception:
|
|
46
|
+
logger.warning(f'Failed to get generation config of {self.model_id} from model hub, use default.')
|
|
47
|
+
|
|
48
|
+
if isinstance(self.model_id, str) and os.path.exists(self.model_id):
|
|
49
|
+
logger.warning(f'Got local model dir: {self.model_id}')
|
|
50
|
+
|
|
51
|
+
if tokenizer.eos_token_id is not None:
|
|
52
|
+
generation_config.eos_token_id = tokenizer.eos_token_id
|
|
53
|
+
if tokenizer.pad_token_id is not None:
|
|
54
|
+
generation_config.pad_token_id = tokenizer.pad_token_id
|
|
55
|
+
if generation_config.max_new_tokens is None:
|
|
56
|
+
generation_config.max_new_tokens = 2048
|
|
57
|
+
|
|
58
|
+
return generation_config
|
|
59
|
+
|
|
60
|
+
def _model_generate(self, query: str, system_prompt: str = None, infer_cfg: dict = {}) -> str:
|
|
61
|
+
"""
|
|
62
|
+
Args:
|
|
63
|
+
query: The input query.
|
|
64
|
+
system_prompt: The system prompt.
|
|
65
|
+
infer_cfg: The inference configuration.
|
|
66
|
+
Returns:
|
|
67
|
+
The prediction result.
|
|
68
|
+
"""
|
|
69
|
+
# For chat model, use the chat template to format the input
|
|
70
|
+
if self.tokenizer.chat_template is not None:
|
|
71
|
+
messages = [ChatMessage(role='user', content=query)]
|
|
72
|
+
if system_prompt:
|
|
73
|
+
messages = [ChatMessage(role='system', content=system_prompt)] + messages
|
|
74
|
+
formatted_prompt = self.tokenizer.apply_chat_template(messages, tokenize=False, add_generation_prompt=True)
|
|
75
|
+
else:
|
|
76
|
+
# For base model, use the query as the input
|
|
77
|
+
formatted_prompt = query
|
|
78
|
+
|
|
79
|
+
inputs = self.tokenizer(formatted_prompt, return_tensors='pt', padding=True).to(self.device)
|
|
80
|
+
input_ids = inputs['input_ids']
|
|
81
|
+
|
|
82
|
+
# Process infer_cfg
|
|
83
|
+
if isinstance(infer_cfg.get('num_return_sequences'), int) and infer_cfg['num_return_sequences'] > 1:
|
|
84
|
+
infer_cfg['do_sample'] = True
|
|
85
|
+
|
|
86
|
+
# stop settings
|
|
87
|
+
stop = infer_cfg.get('stop', None)
|
|
88
|
+
eos_token_id = self.tokenizer.encode(stop, add_special_tokens=False)[0] \
|
|
89
|
+
if stop else self.tokenizer.eos_token_id
|
|
90
|
+
|
|
91
|
+
if eos_token_id is not None:
|
|
92
|
+
infer_cfg['eos_token_id'] = eos_token_id
|
|
93
|
+
infer_cfg['pad_token_id'] = eos_token_id # setting eos_token_id as pad token
|
|
94
|
+
|
|
95
|
+
self.generation_config.update(**infer_cfg)
|
|
96
|
+
fix_do_sample_warning(self.generation_config)
|
|
97
|
+
|
|
98
|
+
# Run inference
|
|
99
|
+
output_ids = self.model.generate(**inputs, generation_config=self.generation_config)
|
|
100
|
+
|
|
101
|
+
response = self.tokenizer.decode(output_ids[0, len(input_ids[0]):], skip_special_tokens=True)
|
|
102
|
+
return response
|
|
103
|
+
|
|
104
|
+
@torch.no_grad()
|
|
105
|
+
def predict(self, inputs: Union[str, dict, list], infer_cfg: dict = {}) -> dict:
|
|
106
|
+
"""
|
|
107
|
+
Args:
|
|
108
|
+
inputs: The input data.
|
|
109
|
+
infer_cfg: The inference configuration.
|
|
110
|
+
Returns:
|
|
111
|
+
The prediction result.
|
|
112
|
+
"""
|
|
113
|
+
|
|
114
|
+
# Process inputs
|
|
115
|
+
if isinstance(inputs, str):
|
|
116
|
+
query = inputs
|
|
117
|
+
system_prompt = None
|
|
118
|
+
elif isinstance(inputs, dict):
|
|
119
|
+
query = inputs['data'][0]
|
|
120
|
+
system_prompt = inputs.get('system_prompt', None)
|
|
121
|
+
elif isinstance(inputs, list):
|
|
122
|
+
query = '\n'.join(inputs)
|
|
123
|
+
system_prompt = None
|
|
124
|
+
else:
|
|
125
|
+
raise TypeError(f'Unsupported inputs type: {type(inputs)}')
|
|
126
|
+
|
|
127
|
+
response = self._model_generate(query, system_prompt, infer_cfg)
|
|
128
|
+
|
|
129
|
+
choices_list = [
|
|
130
|
+
ChatCompletionResponseChoice(
|
|
131
|
+
index=0, message=ChatMessage(content=response, role='assistant'), finish_reason='stop')
|
|
132
|
+
]
|
|
133
|
+
|
|
134
|
+
res_d = ChatCompletionResponse(
|
|
135
|
+
model=self.model_id, choices=choices_list, object='chat.completion', created=int(time.time()),
|
|
136
|
+
usage=None).model_dump(exclude_unset=True)
|
|
137
|
+
|
|
138
|
+
return res_d
|
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
import time
|
|
3
|
+
import torch
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
from evalscope.models.base_adapter import BaseModelAdapter
|
|
7
|
+
from evalscope.models.local_model import LocalModel
|
|
8
|
+
from evalscope.utils.chat_service import ChatCompletionResponse, ChatCompletionResponseChoice, ChatMessage
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class MultiChoiceModelAdapter(BaseModelAdapter):
|
|
12
|
+
""" The multi-choice model adapter. """
|
|
13
|
+
|
|
14
|
+
_DEFAULT_MAX_LENGTH = 2048
|
|
15
|
+
|
|
16
|
+
def __init__(self, model: LocalModel, **kwargs):
|
|
17
|
+
super().__init__(model)
|
|
18
|
+
|
|
19
|
+
self._max_length = kwargs.get('max_length')
|
|
20
|
+
|
|
21
|
+
@property
|
|
22
|
+
def max_length(self):
|
|
23
|
+
if self._max_length:
|
|
24
|
+
return self._max_length
|
|
25
|
+
seqlen_config_attrs = ('n_positions', 'max_position_embeddings', 'n_ctx')
|
|
26
|
+
for attr in seqlen_config_attrs:
|
|
27
|
+
if hasattr(self.model.config, attr):
|
|
28
|
+
return getattr(self.model.config, attr)
|
|
29
|
+
if hasattr(self.tokenizer, 'model_max_length'):
|
|
30
|
+
if self.tokenizer.model_max_length == 1000000000000000019884624838656:
|
|
31
|
+
return self._DEFAULT_MAX_LENGTH
|
|
32
|
+
return self.tokenizer.model_max_length
|
|
33
|
+
return self._DEFAULT_MAX_LENGTH
|
|
34
|
+
|
|
35
|
+
@torch.no_grad()
|
|
36
|
+
def predict(self, inputs: dict, infer_cfg: dict = None) -> dict:
|
|
37
|
+
"""
|
|
38
|
+
Multi-choice model prediction func.
|
|
39
|
+
|
|
40
|
+
Args:
|
|
41
|
+
inputs (dict): The inputs for a doc. Format:
|
|
42
|
+
{'data': [full_prompt], 'multi_choices': ['A', 'B', 'C', 'D']}
|
|
43
|
+
|
|
44
|
+
infer_cfg (dict): inference configuration.
|
|
45
|
+
|
|
46
|
+
Returns:
|
|
47
|
+
res (dict): The model prediction results. Format:
|
|
48
|
+
{
|
|
49
|
+
'choices': [
|
|
50
|
+
{
|
|
51
|
+
'index': 0,
|
|
52
|
+
'message': {
|
|
53
|
+
'content': [-14.9609, -13.6015, ...], # loglikelihood values for inputs context-continuation pairs.
|
|
54
|
+
'role': 'assistant'
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
],
|
|
58
|
+
'created': 1677664795,
|
|
59
|
+
# For models on the ModelScope or HuggingFace, concat model_id and revision with "-".
|
|
60
|
+
'model': 'gpt-3.5-turbo-0613',
|
|
61
|
+
'object': 'chat.completion',
|
|
62
|
+
'usage': {
|
|
63
|
+
'completion_tokens': 17,
|
|
64
|
+
'prompt_tokens': 57,
|
|
65
|
+
'total_tokens': 74
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
"""
|
|
69
|
+
infer_cfg = infer_cfg or {}
|
|
70
|
+
self.model.generation_config.update(**infer_cfg)
|
|
71
|
+
|
|
72
|
+
input_data = inputs['data']
|
|
73
|
+
multi_choices = inputs['multi_choices']
|
|
74
|
+
|
|
75
|
+
output, input_info = self._get_logits(self.tokenizer, self.model, input_data)
|
|
76
|
+
assert output.shape[0] == 1
|
|
77
|
+
logits = output.flatten()
|
|
78
|
+
|
|
79
|
+
choice_logits = [logits[self.tokenizer(ch)['input_ids'][-1:]] for ch in multi_choices]
|
|
80
|
+
softval = torch.nn.functional.softmax(torch.tensor(choice_logits).float(), dim=0)
|
|
81
|
+
|
|
82
|
+
if softval.dtype in {torch.bfloat16, torch.float16}:
|
|
83
|
+
softval = softval.to(dtype=torch.float32)
|
|
84
|
+
probs = softval.detach().cpu().numpy()
|
|
85
|
+
pred: str = multi_choices[int(np.argmax(probs))] # Format: A or B or C or D
|
|
86
|
+
|
|
87
|
+
res_d = ChatCompletionResponse(
|
|
88
|
+
model=self.model_id,
|
|
89
|
+
choices=[
|
|
90
|
+
ChatCompletionResponseChoice(
|
|
91
|
+
index=0, message=ChatMessage(content=pred, role='assistant'), finish_reason='stop')
|
|
92
|
+
],
|
|
93
|
+
object='chat.completion',
|
|
94
|
+
created=int(time.time()),
|
|
95
|
+
usage=None).model_dump(exclude_unset=True)
|
|
96
|
+
|
|
97
|
+
return res_d
|
|
98
|
+
|
|
99
|
+
@staticmethod
|
|
100
|
+
def _get_logits(tokenizer, model, inputs: List[str]):
|
|
101
|
+
input_ids = tokenizer(inputs, padding=False)['input_ids']
|
|
102
|
+
input_ids = torch.tensor(input_ids, device=model.device)
|
|
103
|
+
tokens = {'input_ids': input_ids}
|
|
104
|
+
|
|
105
|
+
outputs = model(input_ids)['logits']
|
|
106
|
+
logits = outputs[:, -1, :]
|
|
107
|
+
log_probs = torch.nn.functional.softmax(logits, dim=-1)
|
|
108
|
+
return log_probs, {'tokens': tokens}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
class ContinuationLogitsModelAdapter(MultiChoiceModelAdapter):
|
|
112
|
+
"""
|
|
113
|
+
Continuation-logits model adapter.
|
|
114
|
+
"""
|
|
115
|
+
|
|
116
|
+
def __init__(self, model: LocalModel, **kwargs):
|
|
117
|
+
super().__init__(model, **kwargs)
|
|
118
|
+
|
|
119
|
+
@torch.no_grad()
|
|
120
|
+
def predict(self, inputs: dict, infer_cfg: dict = None) -> dict:
|
|
121
|
+
"""
|
|
122
|
+
Multi-choice model prediction func.
|
|
123
|
+
Args:
|
|
124
|
+
inputs (dict): The inputs for a doc. Format:
|
|
125
|
+
{'data': [(context, continuation), ...]}
|
|
126
|
+
infer_cfg (dict): inference configuration.
|
|
127
|
+
Returns:
|
|
128
|
+
res (dict): The model prediction results. Format:
|
|
129
|
+
{
|
|
130
|
+
'choices': [
|
|
131
|
+
{
|
|
132
|
+
'index': 0,
|
|
133
|
+
'message': {
|
|
134
|
+
'content': [-14.9609, -13.6015, ...], # loglikelihood values for inputs context-continuation pairs.
|
|
135
|
+
'role': 'assistant'
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
],
|
|
139
|
+
'created': 1677664795,
|
|
140
|
+
# For models on the ModelScope or HuggingFace, concat model_id and revision with "-".
|
|
141
|
+
'model': 'gpt-3.5-turbo-0613',
|
|
142
|
+
'object': 'chat.completion',
|
|
143
|
+
'usage': {
|
|
144
|
+
'completion_tokens': 17,
|
|
145
|
+
'prompt_tokens': 57,
|
|
146
|
+
'total_tokens': 74
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
"""
|
|
150
|
+
infer_cfg = infer_cfg or {}
|
|
151
|
+
|
|
152
|
+
pred_list: list = self.loglikelihood(inputs=inputs['data'], infer_cfg=infer_cfg)
|
|
153
|
+
|
|
154
|
+
res_d = ChatCompletionResponse(
|
|
155
|
+
model=self.model_id,
|
|
156
|
+
choices=[{
|
|
157
|
+
'index': 0,
|
|
158
|
+
'message': {
|
|
159
|
+
'content': pred_list,
|
|
160
|
+
'role': 'assistant'
|
|
161
|
+
}
|
|
162
|
+
}],
|
|
163
|
+
object='chat.completion',
|
|
164
|
+
created=int(time.time()),
|
|
165
|
+
usage=None).model_dump(exclude_unset=True)
|
|
166
|
+
|
|
167
|
+
return res_d
|
|
168
|
+
|
|
169
|
+
def loglikelihood(self, inputs: list, infer_cfg: dict = None) -> list:
|
|
170
|
+
self.model.generation_config.update(**infer_cfg)
|
|
171
|
+
# To predict one doc
|
|
172
|
+
doc_ele_pred = []
|
|
173
|
+
for ctx, continuation in inputs:
|
|
174
|
+
|
|
175
|
+
# ctx_enc shape: [context_tok_len] cont_enc shape: [continuation_tok_len]
|
|
176
|
+
ctx_enc, cont_enc = self._encode_pair(ctx, continuation)
|
|
177
|
+
|
|
178
|
+
inputs_tokens = torch.tensor(
|
|
179
|
+
(ctx_enc.tolist() + cont_enc.tolist())[-(self.max_length + 1):][:-1],
|
|
180
|
+
dtype=torch.long,
|
|
181
|
+
device=self.model.device).unsqueeze(0)
|
|
182
|
+
|
|
183
|
+
logits = self.model(inputs_tokens)[0]
|
|
184
|
+
logits = torch.nn.functional.log_softmax(logits.float(), dim=-1)
|
|
185
|
+
|
|
186
|
+
logits = logits[:, -len(cont_enc):, :]
|
|
187
|
+
cont_enc = cont_enc.unsqueeze(0).unsqueeze(-1)
|
|
188
|
+
logits = torch.gather(logits.cpu(), 2, cont_enc.cpu()).squeeze(-1)
|
|
189
|
+
|
|
190
|
+
choice_score = float(logits.sum())
|
|
191
|
+
doc_ele_pred.append(choice_score)
|
|
192
|
+
|
|
193
|
+
# e.g. [-2.3, -9.2, -12.9, 1.1], length=len(choices)
|
|
194
|
+
return doc_ele_pred
|
|
195
|
+
|
|
196
|
+
def _encode_pair(self, context, continuation):
|
|
197
|
+
n_spaces = len(context) - len(context.rstrip())
|
|
198
|
+
if n_spaces > 0:
|
|
199
|
+
continuation = context[-n_spaces:] + continuation
|
|
200
|
+
context = context[:-n_spaces]
|
|
201
|
+
|
|
202
|
+
whole_enc = self.tokenizer(context + continuation, padding=False)['input_ids']
|
|
203
|
+
whole_enc = torch.tensor(whole_enc, device=self.device)
|
|
204
|
+
|
|
205
|
+
context_enc = self.tokenizer(context, padding=False)['input_ids']
|
|
206
|
+
context_enc = torch.tensor(context_enc, device=self.device)
|
|
207
|
+
|
|
208
|
+
context_enc_len = len(context_enc)
|
|
209
|
+
continuation_enc = whole_enc[context_enc_len:]
|
|
210
|
+
|
|
211
|
+
return context_enc, continuation_enc
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
from typing import Any, Dict, List, Union
|
|
2
|
+
|
|
3
|
+
from evalscope.models.base_adapter import BaseModelAdapter
|
|
4
|
+
from evalscope.models.custom import CustomModel
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CustomModelAdapter(BaseModelAdapter):
|
|
8
|
+
|
|
9
|
+
def __init__(self, custom_model: CustomModel, **kwargs):
|
|
10
|
+
"""
|
|
11
|
+
Custom model adapter.
|
|
12
|
+
|
|
13
|
+
Args:
|
|
14
|
+
custom_model: The custom model instance.
|
|
15
|
+
**kwargs: Other args.
|
|
16
|
+
"""
|
|
17
|
+
self.custom_model = custom_model
|
|
18
|
+
super(CustomModelAdapter, self).__init__(model=custom_model)
|
|
19
|
+
|
|
20
|
+
def predict(self, inputs: Union[str, dict, list], **kwargs) -> List[Dict[str, Any]]:
|
|
21
|
+
"""
|
|
22
|
+
Model prediction func.
|
|
23
|
+
|
|
24
|
+
Args:
|
|
25
|
+
inputs (Union[str, dict, list]): The input data. Depending on the specific model.
|
|
26
|
+
str: 'xxx'
|
|
27
|
+
dict: {'data': [full_prompt]}
|
|
28
|
+
list: ['xxx', 'yyy', 'zzz']
|
|
29
|
+
**kwargs: kwargs
|
|
30
|
+
|
|
31
|
+
Returns:
|
|
32
|
+
res (dict): The model prediction results. Format:
|
|
33
|
+
{
|
|
34
|
+
'choices': [
|
|
35
|
+
{
|
|
36
|
+
'index': 0,
|
|
37
|
+
'message': {
|
|
38
|
+
'content': 'xxx',
|
|
39
|
+
'role': 'assistant'
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
],
|
|
43
|
+
'created': 1677664795,
|
|
44
|
+
'model': 'gpt-3.5-turbo-0613', # should be model_id
|
|
45
|
+
'object': 'chat.completion',
|
|
46
|
+
'usage': {
|
|
47
|
+
'completion_tokens': 17,
|
|
48
|
+
'prompt_tokens': 57,
|
|
49
|
+
'total_tokens': 74
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
"""
|
|
53
|
+
in_prompts = []
|
|
54
|
+
|
|
55
|
+
# Note: here we assume the inputs are all prompts for the benchmark.
|
|
56
|
+
for input_prompt in inputs:
|
|
57
|
+
if isinstance(input_prompt, str):
|
|
58
|
+
in_prompts.append(input_prompt)
|
|
59
|
+
elif isinstance(input_prompt, dict):
|
|
60
|
+
# TODO: to be supported for continuation list like truthful_qa
|
|
61
|
+
in_prompts.append(input_prompt['data'][0])
|
|
62
|
+
elif isinstance(input_prompt, list):
|
|
63
|
+
in_prompts.append('\n'.join(input_prompt))
|
|
64
|
+
else:
|
|
65
|
+
raise TypeError(f'Unsupported inputs type: {type(input_prompt)}')
|
|
66
|
+
|
|
67
|
+
return self.custom_model.predict(prompts=in_prompts, **kwargs)
|