Unverified Commit 6e56b834 authored by Matt's avatar Matt Committed by GitHub
Browse files

Update chat template docs and bump Jinja version (#31455)



* Update chat template docs

* Minor bug in the version check

* Update docs/source/en/chat_templating.md
Co-authored-by: default avatarJoshua Lochner <admin@xenova.com>

* Update docs/source/en/chat_templating.md
Co-authored-by: default avatarJoshua Lochner <admin@xenova.com>

* Update docs/source/en/chat_templating.md
Co-authored-by: default avatarJoshua Lochner <admin@xenova.com>

* Replace backticks with bolding because the doc builder was trying to parse them

* Replace backticks with bolding because the doc builder was trying to parse them

* Replace backticks with bolding because the doc builder was trying to parse them

* More cleanups to avoid upsetting the doc builder

* Add one more tip at the end

---------
Co-authored-by: default avatarJoshua Lochner <admin@xenova.com>
parent 28316d0e
...@@ -573,23 +573,21 @@ default template for that model class is used instead. Let's take a look at the ...@@ -573,23 +573,21 @@ default template for that model class is used instead. Let's take a look at the
"{% for message in messages %}{% if message['role'] == 'user' %}{{ ' ' }}{% endif %}{{ message['content'] }}{% if not loop.last %}{{ ' ' }}{% endif %}{% endfor %}{{ eos_token }}" "{% for message in messages %}{% if message['role'] == 'user' %}{{ ' ' }}{% endif %}{{ message['content'] }}{% if not loop.last %}{{ ' ' }}{% endif %}{% endfor %}{{ eos_token }}"
``` ```
That's kind of intimidating. Let's add some newlines and indentation to make it more readable. Note that the first That's kind of intimidating. Let's clean it up a little to make it more readable. In the process, though, we also make
newline after each block as well as any preceding whitespace before a block are ignored by default, using the sure that the newlines and indentation we add don't end up being included in the template output - see the tip on
Jinja `trim_blocks` and `lstrip_blocks` flags. However, be cautious - although leading whitespace on each [trimming whitespace](#trimming-whitespace) below!
line is stripped, spaces between blocks on the same line are not. We strongly recommend checking that your template
isn't printing extra spaces where it shouldn't be!
``` ```
{% for message in messages %} {%- for message in messages %}
{% if message['role'] == 'user' %} {%- if message['role'] == 'user' %}
{{ ' ' }} {{- ' ' }}
{% endif %} {%- endif %}
{{ message['content'] }} {{- message['content'] }}
{% if not loop.last %} {%- if not loop.last %}
{{ ' ' }} {{- ' ' }}
{% endif %} {%- endif %}
{% endfor %} {%- endfor %}
{{ eos_token }} {{- eos_token }}
``` ```
If you've never seen one of these before, this is a [Jinja template](https://jinja.palletsprojects.com/en/3.1.x/templates/). If you've never seen one of these before, this is a [Jinja template](https://jinja.palletsprojects.com/en/3.1.x/templates/).
...@@ -618,15 +616,15 @@ similarly to the way LLaMA formats them (note that the real LLaMA template inclu ...@@ -618,15 +616,15 @@ similarly to the way LLaMA formats them (note that the real LLaMA template inclu
messages and slightly different system message handling in general - don't use this one in your actual code!) messages and slightly different system message handling in general - don't use this one in your actual code!)
``` ```
{% for message in messages %} {%- for message in messages %}
{% if message['role'] == 'user' %} {%- if message['role'] == 'user' %}
{{ bos_token + '[INST] ' + message['content'] + ' [/INST]' }} {{- bos_token + '[INST] ' + message['content'] + ' [/INST]' }}
{% elif message['role'] == 'system' %} {%- elif message['role'] == 'system' %}
{{ '<<SYS>>\\n' + message['content'] + '\\n<</SYS>>\\n\\n' }} {{- '<<SYS>>\\n' + message['content'] + '\\n<</SYS>>\\n\\n' }}
{% elif message['role'] == 'assistant' %} {%- elif message['role'] == 'assistant' %}
{{ ' ' + message['content'] + ' ' + eos_token }} {{- ' ' + message['content'] + ' ' + eos_token }}
{% endif %} {%- endif %}
{% endfor %} {%- endfor %}
``` ```
Hopefully if you stare at this for a little bit you can see what this template is doing - it adds specific tokens based Hopefully if you stare at this for a little bit you can see what this template is doing - it adds specific tokens based
...@@ -642,15 +640,15 @@ existing template from another model and simply edit it for your needs! For exam ...@@ -642,15 +640,15 @@ existing template from another model and simply edit it for your needs! For exam
above and add "[ASST]" and "[/ASST]" to assistant messages: above and add "[ASST]" and "[/ASST]" to assistant messages:
``` ```
{% for message in messages %} {%- for message in messages %}
{% if message['role'] == 'user' %} {%- if message['role'] == 'user' %}
{{ bos_token + '[INST] ' + message['content'].strip() + ' [/INST]' }} {{- bos_token + '[INST] ' + message['content'].strip() + ' [/INST]' }}
{% elif message['role'] == 'system' %} {%- elif message['role'] == 'system' %}
{{ '<<SYS>>\\n' + message['content'].strip() + '\\n<</SYS>>\\n\\n' }} {{- '<<SYS>>\\n' + message['content'].strip() + '\\n<</SYS>>\\n\\n' }}
{% elif message['role'] == 'assistant' %} {%- elif message['role'] == 'assistant' %}
{{ '[ASST] ' + message['content'] + ' [/ASST]' + eos_token }} {{- '[ASST] ' + message['content'] + ' [/ASST]' + eos_token }}
{% endif %} {%- endif %}
{% endfor %} {%- endfor %}
``` ```
Now, simply set the `tokenizer.chat_template` attribute. Next time you use [`~PreTrainedTokenizer.apply_chat_template`], it will Now, simply set the `tokenizer.chat_template` attribute. Next time you use [`~PreTrainedTokenizer.apply_chat_template`], it will
...@@ -726,9 +724,9 @@ input formats. One popular choice is the `ChatML` format, and this is a good, fl ...@@ -726,9 +724,9 @@ input formats. One popular choice is the `ChatML` format, and this is a good, fl
It looks like this: It looks like this:
``` ```
{% for message in messages %} {%- for message in messages %}
{{'<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>' + '\n'}} {{- '<|im_start|>' + message['role'] + '\n' + message['content'] + '<|im_end|>' + '\n' }}
{% endfor %} {%- endfor %}
``` ```
If you like this one, here it is in one-liner form, ready to copy into your code. The one-liner also includes If you like this one, here it is in one-liner form, ready to copy into your code. The one-liner also includes
...@@ -776,23 +774,45 @@ it's time to put an end to them! ...@@ -776,23 +774,45 @@ it's time to put an end to them!
If you're unfamiliar with Jinja, we generally find that the easiest way to write a chat template is to first If you're unfamiliar with Jinja, we generally find that the easiest way to write a chat template is to first
write a short Python script that formats messages the way you want, and then convert that script into a template. write a short Python script that formats messages the way you want, and then convert that script into a template.
Remember that the template handler will receive the conversation history as a variable called `messages`. Each Remember that the template handler will receive the conversation history as a variable called `messages`.
message is a dictionary with two keys, `role` and `content`. You will be able to access `messages` in your template You will be able to access `messages` in your template just like you can in Python, which means you can loop over
just like you can in Python, which means you can loop over it with `{% for message in messages %}` or access it with `{% for message in messages %}` or access individual messages with `{{ messages[0] }}`, for example.
individual messages with, for example, `{{ messages[0] }}`.
You can also use the following tips to convert your code to Jinja: You can also use the following tips to convert your code to Jinja:
### For loops ### Trimming whitespace
For loops in Jinja look like this: By default, Jinja will print any whitespace that comes before or after a block. This can be a problem for chat
templates, which generally want to be very precise with whitespace! To avoid this, we strongly recommend writing
your templates like this:
```
{%- for message in messages %}
{{- message['role'] + message['content'] }}
{%- endfor %}
```
rather than like this:
``` ```
{% for message in messages %} {% for message in messages %}
{{ message['content'] }} {{ message['role'] + message['content'] }}
{% endfor %} {% endfor %}
``` ```
Adding `-` will strip any whitespace that comes before the block. The second example looks innocent, but the newline
and indentation may end up being included in the output, which is probably not what you want!
### For loops
For loops in Jinja look like this:
```
{%- for message in messages %}
{{- message['content'] }}
{%- endfor %}
```
Note that whatever's inside the {{ expression block }} will be printed to the output. You can use operators like Note that whatever's inside the {{ expression block }} will be printed to the output. You can use operators like
`+` to combine strings inside expression blocks. `+` to combine strings inside expression blocks.
...@@ -801,9 +821,9 @@ Note that whatever's inside the {{ expression block }} will be printed to the ou ...@@ -801,9 +821,9 @@ Note that whatever's inside the {{ expression block }} will be printed to the ou
If statements in Jinja look like this: If statements in Jinja look like this:
``` ```
{% if message['role'] == 'user' %} {%- if message['role'] == 'user' %}
{{ message['content'] }} {{- message['content'] }}
{% endif %} {%- endif %}
``` ```
Note how where Python uses whitespace to mark the beginnings and ends of `for` and `if` blocks, Jinja requires you Note how where Python uses whitespace to mark the beginnings and ends of `for` and `if` blocks, Jinja requires you
...@@ -819,14 +839,26 @@ conversation. Here's an example that puts these ideas together to add a generati ...@@ -819,14 +839,26 @@ conversation. Here's an example that puts these ideas together to add a generati
conversation if add_generation_prompt is `True`: conversation if add_generation_prompt is `True`:
``` ```
{% if loop.last and add_generation_prompt %} {%- if loop.last and add_generation_prompt %}
{{ bos_token + 'Assistant:\n' }} {{- bos_token + 'Assistant:\n' }}
{% endif %} {%- endif %}
``` ```
### Notes on whitespace ### Compatibility with non-Python Jinja
There are multiple implementations of Jinja in various languages. They generally have the same syntax,
but a key difference is that when you're writing a template in Python you can use Python methods, such as
`.lower()` on strings or `.items()` on dicts. This will break if someone tries to use your template on a non-Python
implementation of Jinja. Non-Python implementations are particularly common in deployment environments, where JS
and Rust are very popular.
Don't panic, though! There are a few easy changes you can make to your templates to ensure they're compatible across
all implementations of Jinja:
As much as possible, we've tried to get Jinja to ignore whitespace outside of {{ expressions }}. However, be aware - Replace Python methods with Jinja filters. These usually have the same name, for example `string.lower()` becomes
that Jinja is a general-purpose templating engine, and it may treat whitespace between blocks on the same line `string|lower`, and `dict.items()` becomes `dict|items`. One notable change is that `string.strip()` becomes `string|trim`.
as significant and print it to the output. We **strongly** recommend checking that your template isn't printing extra See the [list of built-in filters](https://jinja.palletsprojects.com/en/3.1.x/templates/#builtin-filters)
spaces where it shouldn't be before you upload it! in the Jinja documentation for more.
\ No newline at end of file - Replace `True`, `False` and `None`, which are Python-specific, with `true`, `false` and `none`.
- Directly rendering a dict or list may give different results in other implementations (for example, string entries
might change from single-quoted to double-quoted). Adding the `tojson` filter can help to ensure consistency here.
\ No newline at end of file
...@@ -441,8 +441,8 @@ def compile_jinja_template(template): ...@@ -441,8 +441,8 @@ def compile_jinja_template(template):
except ImportError: except ImportError:
raise ImportError("template requires jinja2 to be installed.") raise ImportError("template requires jinja2 to be installed.")
if version.parse(jinja2.__version__) <= version.parse("3.0.0"): if version.parse(jinja2.__version__) < version.parse("3.1.0"):
raise ImportError("template requires jinja2>=3.0.0 to be installed. Your version is " f"{jinja2.__version__}.") raise ImportError("template requires jinja2>=3.1.0 to be installed. Your version is " f"{jinja2.__version__}.")
def raise_exception(message): def raise_exception(message):
raise TemplateError(message) raise TemplateError(message)
......
...@@ -1890,9 +1890,9 @@ class PreTrainedTokenizerBase(SpecialTokensMixin, PushToHubMixin): ...@@ -1890,9 +1890,9 @@ class PreTrainedTokenizerBase(SpecialTokensMixin, PushToHubMixin):
except ImportError: except ImportError:
raise ImportError("apply_chat_template requires jinja2 to be installed.") raise ImportError("apply_chat_template requires jinja2 to be installed.")
if version.parse(jinja2.__version__) < version.parse("3.0.0"): if version.parse(jinja2.__version__) < version.parse("3.1.0"):
raise ImportError( raise ImportError(
"apply_chat_template requires jinja2>=3.0.0 to be installed. Your version is " f"{jinja2.__version__}." "apply_chat_template requires jinja2>=3.1.0 to be installed. Your version is " f"{jinja2.__version__}."
) )
def raise_exception(message): def raise_exception(message):
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment