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.
Files changed (106) hide show
  1. evalscope/__init__.py +2 -0
  2. evalscope/arguments.py +11 -3
  3. evalscope/backend/rag_eval/clip_benchmark/tasks/zeroshot_classification.py +0 -1
  4. evalscope/backend/rag_eval/utils/llm.py +1 -1
  5. evalscope/benchmarks/__init__.py +20 -1
  6. evalscope/benchmarks/arc/__init__.py +0 -5
  7. evalscope/benchmarks/arc/arc_adapter.py +24 -102
  8. evalscope/benchmarks/bbh/__init__.py +0 -4
  9. evalscope/benchmarks/bbh/bbh_adapter.py +20 -90
  10. evalscope/benchmarks/benchmark.py +70 -59
  11. evalscope/benchmarks/ceval/__init__.py +0 -5
  12. evalscope/benchmarks/ceval/ceval_adapter.py +24 -125
  13. evalscope/benchmarks/cmmlu/__init__.py +0 -5
  14. evalscope/benchmarks/cmmlu/cmmlu_adapter.py +22 -117
  15. evalscope/benchmarks/competition_math/__init__.py +0 -5
  16. evalscope/benchmarks/competition_math/competition_math_adapter.py +29 -371
  17. evalscope/benchmarks/data_adapter.py +115 -87
  18. evalscope/benchmarks/general_qa/__init__.py +0 -5
  19. evalscope/benchmarks/general_qa/general_qa_adapter.py +23 -79
  20. evalscope/benchmarks/gsm8k/__init__.py +0 -4
  21. evalscope/benchmarks/gsm8k/gsm8k_adapter.py +21 -101
  22. evalscope/benchmarks/hellaswag/__init__.py +0 -5
  23. evalscope/benchmarks/hellaswag/hellaswag_adapter.py +32 -99
  24. evalscope/benchmarks/humaneval/__init__.py +0 -4
  25. evalscope/benchmarks/humaneval/humaneval_adapter.py +18 -120
  26. evalscope/benchmarks/ifeval/__init__.py +0 -0
  27. evalscope/benchmarks/ifeval/ifeval_adapter.py +57 -0
  28. evalscope/benchmarks/ifeval/instructions.py +1478 -0
  29. evalscope/benchmarks/ifeval/instructions_registry.py +188 -0
  30. evalscope/benchmarks/ifeval/instructions_util.py +1670 -0
  31. evalscope/benchmarks/ifeval/utils.py +134 -0
  32. evalscope/benchmarks/iquiz/__init__.py +0 -0
  33. evalscope/benchmarks/iquiz/iquiz_adapter.py +63 -0
  34. evalscope/benchmarks/mmlu/__init__.py +0 -5
  35. evalscope/benchmarks/mmlu/mmlu_adapter.py +32 -130
  36. evalscope/benchmarks/mmlu_pro/__init__.py +0 -0
  37. evalscope/benchmarks/mmlu_pro/mmlu_pro_adapter.py +110 -0
  38. evalscope/benchmarks/race/__init__.py +0 -5
  39. evalscope/benchmarks/race/race_adapter.py +26 -123
  40. evalscope/benchmarks/trivia_qa/__init__.py +0 -5
  41. evalscope/benchmarks/trivia_qa/trivia_qa_adapter.py +23 -99
  42. evalscope/benchmarks/truthful_qa/__init__.py +0 -5
  43. evalscope/benchmarks/truthful_qa/truthful_qa_adapter.py +29 -88
  44. evalscope/cli/cli.py +2 -0
  45. evalscope/cli/start_app.py +29 -0
  46. evalscope/collections/__init__.py +3 -0
  47. evalscope/collections/evaluator.py +198 -0
  48. evalscope/collections/sampler.py +138 -0
  49. evalscope/collections/schema.py +126 -0
  50. evalscope/config.py +7 -5
  51. evalscope/constants.py +9 -26
  52. evalscope/evaluator/evaluator.py +87 -121
  53. evalscope/evaluator/reviewer/auto_reviewer.py +12 -4
  54. evalscope/metrics/__init__.py +3 -0
  55. evalscope/metrics/bundled_rouge_score/rouge_scorer.py +1 -1
  56. evalscope/metrics/math_accuracy.py +193 -50
  57. evalscope/metrics/metrics.py +18 -6
  58. evalscope/metrics/named_metrics.py +17 -0
  59. evalscope/metrics/rouge_metric.py +13 -8
  60. evalscope/models/__init__.py +14 -1
  61. evalscope/models/base_adapter.py +52 -0
  62. evalscope/models/chat_adapter.py +138 -0
  63. evalscope/models/choice_adapter.py +211 -0
  64. evalscope/models/custom_adapter.py +67 -0
  65. evalscope/models/local_model.py +74 -0
  66. evalscope/models/model.py +141 -0
  67. evalscope/models/server_adapter.py +111 -0
  68. evalscope/perf/__init__.py +1 -0
  69. evalscope/perf/main.py +0 -1
  70. evalscope/perf/plugin/api/custom_api.py +1 -1
  71. evalscope/perf/plugin/api/openai_api.py +1 -1
  72. evalscope/perf/plugin/datasets/flickr8k.py +1 -1
  73. evalscope/perf/plugin/datasets/longalpaca.py +1 -1
  74. evalscope/report/__init__.py +5 -0
  75. evalscope/report/app.py +506 -0
  76. evalscope/report/combinator.py +73 -0
  77. evalscope/report/generator.py +80 -0
  78. evalscope/report/utils.py +133 -0
  79. evalscope/run.py +48 -72
  80. evalscope/run_arena.py +1 -1
  81. evalscope/summarizer.py +1 -1
  82. evalscope/utils/__init__.py +1 -1
  83. evalscope/utils/chat_service.py +5 -4
  84. evalscope/utils/io_utils.py +8 -0
  85. evalscope/utils/logger.py +5 -0
  86. evalscope/utils/model_utils.py +15 -2
  87. evalscope/utils/utils.py +3 -25
  88. evalscope/version.py +2 -2
  89. {evalscope-0.8.2.dist-info → evalscope-0.10.0.dist-info}/METADATA +115 -21
  90. {evalscope-0.8.2.dist-info → evalscope-0.10.0.dist-info}/RECORD +99 -78
  91. tests/cli/test_collection.py +57 -0
  92. tests/cli/test_run.py +52 -1
  93. tests/rag/test_mteb.py +3 -2
  94. evalscope/models/api/__init__.py +0 -3
  95. evalscope/models/dummy_chat_model.py +0 -49
  96. evalscope/models/model_adapter.py +0 -525
  97. evalscope/models/openai_model.py +0 -103
  98. evalscope/tools/__init__.py +0 -1
  99. evalscope/tools/combine_reports.py +0 -133
  100. evalscope/tools/gen_mmlu_subject_mapping.py +0 -90
  101. /evalscope/{tools/rewrite_eval_results.py → models/custom/dummy_model.py} +0 -0
  102. /evalscope/{models/api → third_party/longbench_write/tools}/openai_api.py +0 -0
  103. {evalscope-0.8.2.dist-info → evalscope-0.10.0.dist-info}/LICENSE +0 -0
  104. {evalscope-0.8.2.dist-info → evalscope-0.10.0.dist-info}/WHEEL +0 -0
  105. {evalscope-0.8.2.dist-info → evalscope-0.10.0.dist-info}/entry_points.txt +0 -0
  106. {evalscope-0.8.2.dist-info → evalscope-0.10.0.dist-info}/top_level.txt +0 -0
@@ -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 nltk import word_tokenize
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
- score = zh_scorer.get_scores(p, r)[0]
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
- score = scorer.score(p, r)
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
@@ -1,3 +1,16 @@
1
1
  # Copyright (c) Alibaba, Inc. and its affiliates.
2
2
 
3
- from evalscope.models.model import BaseModel, ChatBaseModel
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)