Skip to content

Commit 7a1614e

Browse files
baberabbKonradSzaferhaileyschoelkopf
authored
Chat Template fix (cont. #2235) (#2269)
* default chat template method fix * move chat_template to TemplateLM * remove hotfix * handle openai `chat_template` * Update lm_eval/api/model.py Co-authored-by: Hailey Schoelkopf <[email protected]> * add 'max_tokens' to gen_kwargs * pre-commit --------- Co-authored-by: KonradSzafer <[email protected]> Co-authored-by: Hailey Schoelkopf <[email protected]>
1 parent 928e8bb commit 7a1614e

File tree

6 files changed

+117
-124
lines changed

6 files changed

+117
-124
lines changed

lm_eval/api/model.py

Lines changed: 105 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import json
44
import logging
55
import os
6-
from typing import Dict, List, Optional, Tuple, Type, TypeVar
6+
from typing import Dict, List, Optional, Tuple, Type, TypeVar, Union
77

88
import transformers
99
from sqlitedict import SqliteDict
@@ -192,15 +192,13 @@ def tokenizer_name(self) -> str:
192192
"To use this model with chat templates, please implement the 'tokenizer_name' property."
193193
)
194194

195-
@property
196-
def chat_template(self) -> str:
197-
"""Must be defined for LM subclasses that implement Chat Templating.
198-
Should return the structure of the chat template applied to user/assistant messages.
199-
This is used only to save in the experiment results for reproducibility.
195+
def chat_template(self, chat_template: Union[bool, str] = False) -> Optional[str]:
196+
"""Returns the chat template structure for user/assistant messages if a template is provided.
197+
This method is intended to be overridden in a subclass to define a specific chat template format.
198+
For models that do not support chat templates, this method returns None by default.
200199
"""
201-
raise NotImplementedError(
202-
"To use this model with chat templates, please implement the 'chat_template' property."
203-
)
200+
201+
return ""
204202

205203
def set_cache_hook(self, cache_hook) -> None:
206204
self.cache_hook = cache_hook
@@ -316,6 +314,8 @@ class TemplateLM(LM):
316314
and boilerplate often included in other LM subclasses.
317315
"""
318316

317+
tokenizer = None
318+
319319
@property
320320
@abc.abstractmethod
321321
def eot_token_id(self):
@@ -386,3 +386,99 @@ def loglikelihood_rolling(
386386
@abc.abstractmethod
387387
def generate_until(self, requests, disable_tqdm: bool = False) -> List[str]:
388388
pass
389+
390+
def chat_template(self, chat_template: Union[bool, str] = False) -> Optional[str]:
391+
"""
392+
Set and get the appropriate chat template for the model.
393+
This method sets the tokenizer's chat_template and returns the template string for reproducibility.
394+
395+
The template selection logic is adapted from the Transformers library's `apply_chat_template`
396+
method in the Tokenizer class. The original implementation can be found at:
397+
https://github.com/huggingface/transformers/blob/fc35907f95459d7a6c5281dfadd680b6f7b620e3/src/transformers/tokenization_utils_base.py#L1687
398+
399+
This method ensures that the right template is chosen based on the following:
400+
0. If the model has no 'tokenizer' attribute: assumes that there is only a single possible chat template, handled on the model provider side internally. Returns the empty string.
401+
1. If the model's tokenizer has multiple templates:
402+
a. Use the specified template if it exists in the dictionary.
403+
b. Use the default template from the list if no specific template is provided.
404+
c. Raise an error if no default template exists and no specific template is provided.
405+
2. If the model's tokenizer has a single template or no template:
406+
a. Use the tokenizer's chat template if available.
407+
b. Fall back to the default chat template if no tokenizer chat template exists.
408+
409+
Args:
410+
chat_template (Union[bool, str]): Specifies the chat template to use.
411+
- If False or None, no template is applied.
412+
- If True, the default or only available template is used.
413+
- If a string, the template with the matching name is used.
414+
415+
Returns:
416+
Optional[str]: The selected chat template, or None if no template is applied.
417+
"""
418+
if self.tokenizer is None:
419+
return ""
420+
421+
if chat_template is False or chat_template is None:
422+
eval_logger.warning(
423+
"model.chat_template was called with the chat_template set to False or None. "
424+
"Therefore no chat template will be applied. Make sure this is an intended behavior."
425+
)
426+
return None
427+
428+
# Convert boolean chat_template to None to ensure compatibility with the adapted logic
429+
if isinstance(chat_template, bool):
430+
chat_template = None
431+
using_default_template = False
432+
433+
# First, handle the cases when the model has a dict of multiple templates
434+
template = self.tokenizer.chat_template or self.tokenizer.default_chat_template
435+
436+
if isinstance(template, dict):
437+
using_default_dict = self.tokenizer.chat_template is None
438+
439+
if chat_template is not None:
440+
if chat_template in template:
441+
selected_template = template[chat_template]
442+
if using_default_dict:
443+
using_default_template = True
444+
else:
445+
raise ValueError(
446+
f"The specified chat template '{chat_template}' is not available. "
447+
f"Available template names are {sorted(template.keys())}."
448+
)
449+
else:
450+
# If user didn't pass a chat template, use the default template from the dict
451+
if "default" in template:
452+
selected_template = template["default"]
453+
using_default_template = True
454+
else:
455+
raise ValueError(
456+
"This model has multiple chat templates with no default specified! Please either pass a chat "
457+
"template or the name of the template you wish to use to the `chat_template` argument. Available "
458+
f"template names are {sorted(template.keys())}."
459+
)
460+
461+
# Cases when the model has a single template or no template
462+
else:
463+
# priority: `chat_template` argument > `tokenizer.chat_template` > `tokenizer.default_chat_template
464+
if isinstance(chat_template, str):
465+
eval_logger.warning(
466+
"Chat template name provided, but the tokenizer's chat template is not a dictionary. "
467+
"Using the tokenizer's chat template or the default template instead."
468+
)
469+
if self.tokenizer.chat_template is not None:
470+
selected_template = self.tokenizer.chat_template
471+
else:
472+
selected_template = self.tokenizer.default_chat_template
473+
using_default_template = True
474+
475+
if using_default_template:
476+
eval_logger.warning(
477+
"No chat template is set for this tokenizer, falling back to a default class-level template. This is "
478+
"very error-prone, because models are often trained with templates different from the class default! "
479+
"Default chat templates are a legacy feature and will be removed in Transformers v4.43, at which "
480+
"point any code depending on them will stop working. We recommend setting a valid chat template before "
481+
"then to ensure that this model continues working without issues."
482+
)
483+
484+
return selected_template

lm_eval/evaluator.py

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -289,18 +289,12 @@ def _adjust_config(task_dict):
289289
if check_integrity:
290290
run_task_tests(task_list=tasks)
291291

292-
# hotfix: delete when chat_template fixed
293-
try:
294-
chat = lm.chat_template(apply_chat_template)
295-
except: # noqa: E722
296-
chat = None
297-
298292
if evaluation_tracker is not None:
299293
evaluation_tracker.general_config_tracker.log_experiment_args(
300294
model_source=model,
301295
model_args=model_args,
302296
system_instruction=system_instruction,
303-
chat_template=chat,
297+
chat_template=lm.chat_template(apply_chat_template),
304298
fewshot_as_multiturn=fewshot_as_multiturn,
305299
)
306300

lm_eval/models/api_models.py

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -225,14 +225,6 @@ def header(self) -> dict:
225225
"""Override this property to return the headers for the API request."""
226226
return {"Authorization": f"Bearer {self.api_key}"}
227227

228-
@property
229-
def chat_template(self) -> str:
230-
"""Must be defined for LM subclasses that implement Chat Templating.
231-
Should return the structure of the chat template applied to user/assistant messages.
232-
Only used for logging and reproducibility.
233-
"""
234-
return ""
235-
236228
@property
237229
def tokenizer_name(self) -> str:
238230
"""Must be defined for LM subclasses which implement Chat Templating.

lm_eval/models/huggingface.py

Lines changed: 0 additions & 92 deletions
Original file line numberDiff line numberDiff line change
@@ -438,98 +438,6 @@ def world_size(self):
438438
def tokenizer_name(self) -> str:
439439
return self.tokenizer.name_or_path.replace("/", "__")
440440

441-
def chat_template(self, chat_template: Union[bool, str] = False) -> Optional[str]:
442-
"""
443-
Get the appropriate chat template for the model based on configuration and input.
444-
This method determines, and returns the correct chat template, ensuring reproducibility.
445-
446-
The template selection logic is adapted from the Transformers library's `apply_chat_template`
447-
method in the Tokenizer class. The original implementation can be found at:
448-
https://github.com/huggingface/transformers/blob/fc35907f95459d7a6c5281dfadd680b6f7b620e3/src/transformers/tokenization_utils_base.py#L1687
449-
450-
This method ensures that the right template is chosen based on the following:
451-
1. If the model's tokenizer has multiple templates:
452-
a. Use the specified template if it exists in the dictionary.
453-
b. Use the default template from the list if no specific template is provided.
454-
c. Raise an error if no default template exists and no specific template is provided.
455-
2. If the model's tokenizer has a single template or no template:
456-
a. Use the tokenizer's chat template if available.
457-
b. Fall back to the default chat template if no tokenizer chat template exists.
458-
459-
Args:
460-
chat_template (Union[bool, str]): Specifies the chat template to use.
461-
- If False or None, no template is applied.
462-
- If True, the default or only available template is used.
463-
- If a string, the template with the matching name is used.
464-
465-
Returns:
466-
Optional[str]: The selected chat template, or None if no template is applied.
467-
"""
468-
if chat_template is False or chat_template is None:
469-
eval_logger.warning(
470-
"model.chat_template was called with the chat_template set to False or None. "
471-
"Therefore no chat template will be applied. Make sure this is an intended behavior."
472-
)
473-
return None
474-
475-
# Convert boolean chat_template to None to ensure compatibility with the adapted logic
476-
if isinstance(chat_template, bool):
477-
chat_template = None
478-
using_default_template = False
479-
480-
# First, handle the cases when the model has a dict of multiple templates
481-
template = self.tokenizer.chat_template or self.tokenizer.default_chat_template
482-
483-
if isinstance(template, dict):
484-
using_default_dict = self.tokenizer.chat_template is None
485-
486-
if chat_template is not None:
487-
if chat_template in template:
488-
selected_template = template[chat_template]
489-
if using_default_dict:
490-
using_default_template = True
491-
else:
492-
raise ValueError(
493-
f"The specified chat template '{chat_template}' is not available. "
494-
f"Available template names are {sorted(template.keys())}."
495-
)
496-
else:
497-
# If user didn't pass a chat template, use the default template from the dict
498-
if "default" in template:
499-
selected_template = template["default"]
500-
using_default_template = True
501-
else:
502-
raise ValueError(
503-
"This model has multiple chat templates with no default specified! Please either pass a chat "
504-
"template or the name of the template you wish to use to the `chat_template` argument. Available "
505-
f"template names are {sorted(template.keys())}."
506-
)
507-
508-
# Cases when the model has a single template or no template
509-
else:
510-
# priority: `chat_template` argument > `tokenizer.chat_template` > `tokenizer.default_chat_template
511-
if isinstance(chat_template, str):
512-
eval_logger.warning(
513-
"Chat template name provided, but the tokenizer's chat template is not a dictionary. "
514-
"Using the tokenizer's chat template or the default template instead."
515-
)
516-
if self.tokenizer.chat_template is not None:
517-
selected_template = self.tokenizer.chat_template
518-
else:
519-
selected_template = self.tokenizer.default_chat_template
520-
using_default_template = True
521-
522-
if using_default_template:
523-
eval_logger.warning(
524-
"No chat template is set for this tokenizer, falling back to a default class-level template. This is "
525-
"very error-prone, because models are often trained with templates different from the class default! "
526-
"Default chat templates are a legacy feature and will be removed in Transformers v4.43, at which "
527-
"point any code depending on them will stop working. We recommend setting a valid chat template before "
528-
"then to ensure that this model continues working without issues."
529-
)
530-
531-
return selected_template
532-
533441
def _get_backend(
534442
self,
535443
config: Union[transformers.PretrainedConfig, transformers.AutoConfig],

lm_eval/models/openai_completions.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,10 @@ def _create_payload(
2929
) -> dict:
3030
if generate:
3131
gen_kwargs.pop("do_sample", False)
32-
max_tokens = gen_kwargs.pop("max_gen_toks", self._max_gen_toks)
32+
if "max_tokens" in gen_kwargs:
33+
max_tokens = gen_kwargs.pop("max_tokens")
34+
else:
35+
max_tokens = gen_kwargs.pop("max_gen_toks", self._max_gen_toks)
3336
temperature = gen_kwargs.pop("temperature", 0)
3437
stop = gen_kwargs.pop("until", ["<|endoftext|>"])
3538
return {
@@ -124,7 +127,10 @@ def _create_payload(
124127
**kwargs,
125128
) -> dict:
126129
gen_kwargs.pop("do_sample", False)
127-
max_tokens = gen_kwargs.pop("max_gen_toks", self._max_gen_toks)
130+
if "max_tokens" in gen_kwargs:
131+
max_tokens = gen_kwargs.pop("max_tokens")
132+
else:
133+
max_tokens = gen_kwargs.pop("max_gen_toks", self._max_gen_toks)
128134
temperature = gen_kwargs.pop("temperature", 0)
129135
stop = gen_kwargs.pop("until", ["<|endoftext|>"])
130136
if not isinstance(stop, (list, tuple)):
@@ -194,6 +200,9 @@ def loglikelihood(self, requests, **kwargs):
194200
), "Loglikelihood is not supported for gpt-3.5-turbo"
195201
return super().loglikelihood(requests, **kwargs)
196202

203+
def chat_template(self, chat_template: Union[bool, str] = False) -> Optional[str]:
204+
return ""
205+
197206

198207
@register_model("openai-chat-completions")
199208
class OpenAIChatCompletion(LocalChatCompletion):

lm_eval/models/vllm_causallms.py

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -187,12 +187,6 @@ def apply_chat_template(self, chat_history: List[Dict[str, str]]) -> str:
187187
chat_history, tokenize=False, add_generation_prompt=True
188188
)
189189

190-
@property
191-
def chat_template(self) -> str:
192-
if self.tokenizer.chat_template is not None:
193-
return self.tokenizer.chat_template
194-
return self.tokenizer.default_chat_template
195-
196190
@property
197191
def tokenizer_name(self) -> str:
198192
return self.tokenizer.name_or_path.replace("/", "__")

0 commit comments

Comments
 (0)