Skip to content

Commit d613c81

Browse files
authored
Support ChatML prompt format in worker (#3668)
1 parent 8c0e1a3 commit d613c81

7 files changed

Lines changed: 77 additions & 42 deletions

File tree

inference/worker/chat_chain.py

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,8 +14,6 @@
1414
PREFIX,
1515
SUFFIX,
1616
THOUGHT_SEQ,
17-
V2_ASST_PREFIX,
18-
V2_PROMPTER_PREFIX,
1917
)
2018
from chat_chain_utils import compose_tools_from_plugin, extract_tool_and_input, prepare_prompt, use_tool
2119
from hf_langchain_inference import HFInference
@@ -26,6 +24,7 @@
2624
from oasst_shared.model_configs import ModelConfig
2725
from oasst_shared.schemas import inference
2826
from settings import settings
27+
from utils import special_tokens
2928

3029
# Exclude tools description from final prompt. Saves ctx space but can hurt output
3130
# quality especially if truncation kicks in. Dependent on model used
@@ -143,7 +142,11 @@ def handle_plugin_usage(
143142
action_input_format = (
144143
JSON_FORMAT_PAYLOAD if prompt_template.template.find("payload") != -1 else JSON_FORMAT_NO_PAYLOAD
145144
)
146-
eos_token = tokenizer.eos_token if hasattr(tokenizer, "eos_token") else ""
145+
eos_token = ""
146+
if special_tokens["end"]:
147+
eos_token = special_tokens["end"]
148+
elif hasattr(tokenizer, "eos_token"):
149+
eos_token = tokenizer.eos_token
147150
tool_names = [tool.name for tool in tools]
148151

149152
chain = PromptedLLM(
@@ -170,7 +173,7 @@ def handle_plugin_usage(
170173
),
171174
)
172175

173-
init_prompt = f"{input_prompt}{eos_token}{V2_ASST_PREFIX}"
176+
init_prompt = f"{input_prompt}{eos_token}{special_tokens['assistant']}"
174177
init_prompt, chain_response = chain.call(init_prompt)
175178

176179
inner_monologue.append("In: " + str(init_prompt))
@@ -203,7 +206,9 @@ def handle_plugin_usage(
203206

204207
# Save previous chain response for use in final prompt
205208
prev_chain_response = chain_response
206-
new_prompt = f"{input_prompt}{eos_token}{V2_ASST_PREFIX}{chain_response}{OBSERVATION_SEQ} {tool_response}"
209+
new_prompt = (
210+
f"{input_prompt}{eos_token}{special_tokens['assistant']}{chain_response}{OBSERVATION_SEQ} {tool_response}"
211+
)
207212

208213
new_prompt, chain_response = chain.call(new_prompt)
209214

@@ -239,15 +244,13 @@ def handle_plugin_usage(
239244
chain_finished = True
240245

241246
if REMOVE_TOOLS_FROM_FINAL_PROMPT:
242-
TEMPLATE = f"""{V2_PROMPTER_PREFIX}{PREFIX}{SUFFIX}"""
247+
TEMPLATE = f"""{special_tokens['prompter']}{PREFIX}{SUFFIX}"""
243248
input_variables = ["input", "chat_history", "language", "current_time"]
244249

245250
prompt_template = PromptTemplate(input_variables=input_variables, template=TEMPLATE)
246251
tool_names = None
247252

248-
final_input = (
249-
f"{input_prompt}{eos_token}{V2_ASST_PREFIX}\n{prev_chain_response}{OBSERVATION_SEQ} {tool_response}"
250-
)
253+
final_input = f"{input_prompt}{eos_token}{special_tokens['assistant']}\n{prev_chain_response}{OBSERVATION_SEQ} {tool_response}"
251254
inner_prompt = prepare_prompt(
252255
final_input,
253256
prompt_template,
@@ -312,14 +315,18 @@ def handle_standard_usage(
312315
tokenizer: transformers.PreTrainedTokenizer,
313316
custom_instructions: str = "",
314317
):
315-
eos_token = tokenizer.eos_token if hasattr(tokenizer, "eos_token") else ""
318+
eos_token = ""
319+
if special_tokens["end"]:
320+
eos_token = special_tokens["end"]
321+
elif hasattr(tokenizer, "eos_token"):
322+
eos_token = tokenizer.eos_token
316323
current_time = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
317324

318325
# Non-plugin prompt template can include some external data e.g. datetime, language
319326
action_input_format = (
320327
JSON_FORMAT_PAYLOAD if prompt_template.template.find("payload") != -1 else JSON_FORMAT_NO_PAYLOAD
321328
)
322-
input = f"{original_prompt}{eos_token}{V2_ASST_PREFIX}"
329+
input = f"{original_prompt}{eos_token}{special_tokens['assistant']}"
323330
init_prompt = prepare_prompt(
324331
input,
325332
prompt_template,
@@ -372,7 +379,7 @@ def handle_conversation(
372379
plugin_enabled = len(tools) > 0
373380
memory: ConversationBufferMemory = build_memory(work_request)
374381

375-
TEMPLATE = f"""{V2_PROMPTER_PREFIX}{PREFIX}{tools_instructions_template}{SUFFIX}"""
382+
TEMPLATE = f"""{special_tokens['prompter']}{PREFIX}{tools_instructions_template}{SUFFIX}"""
376383
input_variables = [
377384
"input",
378385
"chat_history",

inference/worker/chat_chain_prompts.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,3 @@
1-
V2_ASST_PREFIX = "<|assistant|>"
2-
V2_PROMPTER_PREFIX = "<|prompter|>"
3-
V2_SYSTEM_PREFIX = "<|system|>"
4-
51
ASSISTANT_PREFIX = "Open Assistant"
62
HUMAN_PREFIX = "Human"
73
OBSERVATION_SEQ = "Observation:"

inference/worker/chat_chain_utils.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
import requests
66
import transformers
7-
from chat_chain_prompts import INSTRUCTIONS, OBSERVATION_SEQ, TOOLS_PREFIX, V2_ASST_PREFIX, V2_PROMPTER_PREFIX
7+
from chat_chain_prompts import INSTRUCTIONS, OBSERVATION_SEQ, TOOLS_PREFIX
88
from hf_langchain_inference import HFInference
99
from langchain.agents import Tool
1010
from langchain.memory import ConversationBufferMemory
@@ -13,15 +13,15 @@
1313
from oasst_shared.schemas import inference
1414
from openapi_parser import prepare_plugin_for_llm
1515
from settings import settings
16-
from utils import shared_tokenizer_lock
16+
from utils import shared_tokenizer_lock, special_tokens
1717

1818
RESPONSE_MAX_LENGTH = 2048
1919
DESCRIPTION_FOR_MODEL_MAX_LENGTH = 512
2020

2121
llm_json_parser = HFInference(
2222
inference_server_url=settings.inference_server_url,
2323
max_new_tokens=512,
24-
stop_sequences=["</s>"],
24+
stop_sequences=[special_tokens["end"] if special_tokens["end"] else "</s>"],
2525
top_k=5,
2626
temperature=0.20,
2727
repetition_penalty=(1 / 0.83),
@@ -139,7 +139,7 @@ def prepare_json(json_str: str) -> str:
139139
except json.decoder.JSONDecodeError as e:
140140
logger.warning(f"JSON is still not valid, trying to fix it with LLM {fixed_json}")
141141
# If still invalid, try using LLM to fix
142-
prompt = f"""{V2_PROMPTER_PREFIX}Below is malformed JSON object string:
142+
prompt = f"""{special_tokens['prompter']}Below is malformed JSON object string:
143143
--------------
144144
{json_str}
145145
--------------
@@ -151,7 +151,7 @@ def prepare_json(json_str: str) -> str:
151151
1. If malformed JSON object string contains multiple objects, you merge them into one.
152152
2. You will never made up or add any new data, you will only fix the malformed JSON object string.
153153
154-
Here is the fixed JSON object string:</s>{V2_ASST_PREFIX}"""
154+
Here is the fixed JSON object string:{special_tokens['end'] or '</s>'}{special_tokens['assistant']}"""
155155
logger.warning(f"JSON Fix Prompt: {prompt}")
156156
out = llm_json_parser.generate(prompts=[prompt]).generations[0][0].text
157157
out = out[: out.find("}") + 1]

inference/worker/settings.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,9 @@ class Settings(pydantic.BaseSettings):
1111

1212
oa_protocol_version: str = "v2"
1313

14+
# Supported: oasst, chatml
15+
model_prompt_format: str = "oasst"
16+
1417
retry_on_error: bool = True
1518
hf_pause: float = 0.075
1619
max_parallel_requests: int = 1

inference/worker/utils.py

Lines changed: 31 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -11,14 +11,29 @@
1111
import sseclient
1212
import transformers
1313
import websocket
14-
from chat_chain_prompts import V2_PROMPTER_PREFIX, V2_SYSTEM_PREFIX
1514
from loguru import logger
1615
from oasst_shared.schemas import inference
1716
from settings import settings
1817

1918
shared_tokenizer_lock = threading.Lock()
2019

2120

21+
if settings.model_prompt_format == "chatml":
22+
special_tokens = {
23+
"prompter": "<|im_start|>user\n",
24+
"assistant": "<|im_start|>assistant\n",
25+
"system": "<|im_start|>system\n",
26+
"end": "<|im_end|>\n",
27+
}
28+
else:
29+
special_tokens = {
30+
"prompter": "<|prompter|>",
31+
"assistant": "<|assistant|>",
32+
"system": "<|system|>",
33+
"end": "",
34+
}
35+
36+
2237
class TokenBuffer:
2338
"""
2439
A buffer for storing and managing tokens based on various conditions including stop sequences.
@@ -80,6 +95,13 @@ def get_max_input_length(worker_config: inference.WorkerConfig, plugin_used: boo
8095
return max_input_length
8196

8297

98+
def get_tokens_until(tokens: list[int], target: int | list[int]) -> list[int]:
99+
if isinstance(target, int):
100+
return tokens[: tokens.index(target)]
101+
else:
102+
return next((i for i in range(len(tokens) - len(target) + 1) if tokens[i : i + len(target)] == target))
103+
104+
83105
def truncate_prompt(
84106
tokenizer: transformers.PreTrainedTokenizer,
85107
worker_config: inference.WorkerConfig,
@@ -96,13 +118,14 @@ def truncate_prompt(
96118
"""
97119
with shared_tokenizer_lock:
98120
ids = tokenizer.encode(prompt)
99-
prompter_prefix_id = tokenizer.convert_tokens_to_ids(V2_PROMPTER_PREFIX)
121+
# prompter_prefix_ids could be int or list of ints
122+
prompter_prefix_ids = tokenizer.convert_tokens_to_ids(special_tokens["prompter"])
100123

101124
system_prompt: str | None = None
102125
system_tokens: list[int] | None = None
103-
if prompt.startswith(V2_SYSTEM_PREFIX):
104-
system_prompt = prompt[: prompt.index(V2_PROMPTER_PREFIX)]
105-
system_tokens = ids[: ids.index(prompter_prefix_id)]
126+
if prompt.startswith(special_tokens["system"]):
127+
system_prompt = prompt[: prompt.index(special_tokens["prompter"])]
128+
system_tokens = get_tokens_until(ids, prompter_prefix_ids)
106129

107130
max_input_length = get_max_input_length(worker_config, plugin_used)
108131

@@ -117,9 +140,9 @@ def truncate_prompt(
117140
with shared_tokenizer_lock:
118141
prompt = tokenizer.decode(ids)
119142

120-
if V2_PROMPTER_PREFIX not in prompt:
121-
prompt = V2_PROMPTER_PREFIX + prompt
122-
ids = tokenizer.encode(V2_PROMPTER_PREFIX) + ids
143+
if special_tokens["prompter"] not in prompt:
144+
prompt = special_tokens["prompter"] + prompt
145+
ids = tokenizer.encode(special_tokens["prompter"]) + ids
123146

124147
if system_tokens:
125148
prompt = system_prompt + prompt

inference/worker/work.py

Lines changed: 14 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,11 @@
1414
OBSERVATION_SEQ,
1515
START_SEQ,
1616
THOUGHT_SEQ,
17-
V2_ASST_PREFIX,
18-
V2_PROMPTER_PREFIX,
19-
V2_SYSTEM_PREFIX,
2017
)
2118
from loguru import logger
2219
from oasst_shared.schemas import inference
2320
from settings import settings
24-
from utils import shared_tokenizer_lock
21+
from utils import shared_tokenizer_lock, special_tokens
2522

2623

2724
def make_prompt_and_parameters(
@@ -32,10 +29,14 @@ def make_prompt_and_parameters(
3229
if settings.oa_protocol_version != "v2":
3330
raise RuntimeError(f"Unsupported oa protocol version: {settings.oa_protocol_version}")
3431

35-
eos_token = tokenizer.eos_token if hasattr(tokenizer, "eos_token") else ""
32+
eos_token = ""
33+
if special_tokens["end"]:
34+
eos_token = special_tokens["end"]
35+
elif hasattr(tokenizer, "eos_token"):
36+
eos_token = tokenizer.eos_token
3637

3738
def _prepare_message(message: inference.MessageRead) -> str:
38-
prefix = V2_ASST_PREFIX if message.is_assistant else V2_PROMPTER_PREFIX
39+
prefix = special_tokens["assistant"] if message.is_assistant else special_tokens["prompter"]
3940
return prefix + message.content + eos_token
4041

4142
# Construct prompt
@@ -44,7 +45,7 @@ def _prepare_message(message: inference.MessageRead) -> str:
4445
# Prepend system prompt and custom_instructions if it was specified in work parameters
4546
work_params = work_request.parameters
4647
if work_params.system_prompt or work_params.user_profile or work_params.user_response_instructions:
47-
pre_prompt = V2_SYSTEM_PREFIX + (work_params.system_prompt or "")
48+
pre_prompt = special_tokens["system"] + (work_params.system_prompt or "")
4849

4950
if work_params.user_profile or work_params.user_response_instructions:
5051
pre_prompt = f"""{pre_prompt}\n{CUSTOM_INSTRUCTIONS_PREFIX.format(user_profile=work_params.user_profile or "", user_response_instructions=work_params.user_response_instructions or "")}"""
@@ -53,14 +54,14 @@ def _prepare_message(message: inference.MessageRead) -> str:
5354
messages = [pre_prompt] + messages
5455

5556
# Stringify and append assistant prefix to signify start of generation
56-
prompt = "".join(messages) + V2_ASST_PREFIX
57+
prompt = "".join(messages) + special_tokens["assistant"]
5758

5859
parameters = interface.GenerateStreamParameters.from_work_parameters(work_request.parameters)
5960
if settings.use_stop_sequences:
6061
parameters.stop = [
61-
V2_PROMPTER_PREFIX,
62-
V2_ASST_PREFIX,
63-
V2_SYSTEM_PREFIX,
62+
special_tokens["prompter"],
63+
special_tokens["assistant"],
64+
special_tokens["system"],
6465
]
6566
if eos_token:
6667
parameters.stop.append(eos_token)
@@ -73,9 +74,9 @@ def _prepare_message(message: inference.MessageRead) -> str:
7374
def prepare_safe_prompt(prompt: str, label: str, rots: str) -> str:
7475
"""Given a prompt, safety label, and safety rule of thumb, prepare a 'safe prompt' to replace the prompt."""
7576
pre_prompt = f"Answer the following request with {label} as responsible chatbot that believes that {rots}: "
76-
input_list = prompt.split(V2_PROMPTER_PREFIX)
77+
input_list = prompt.split(special_tokens["prompter"])
7778
input_list[-1] = pre_prompt + input_list[-1]
78-
return V2_PROMPTER_PREFIX.join(input_list)
79+
return special_tokens["prompter"].join(input_list)
7980

8081

8182
def is_safety_triggered(safety_label: str, safety_level: int) -> bool:

oasst-shared/oasst_shared/model_configs.py

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,4 +145,9 @@ def compat_hash(self) -> str:
145145
max_total_length=2048,
146146
quantized=True,
147147
),
148+
"OA_SFT_Llama2_70B_10": ModelConfig(
149+
model_id="OpenAssistant/llama2-70b-oasst-sft-v10",
150+
max_input_length=3072,
151+
max_total_length=4096,
152+
),
148153
}

0 commit comments

Comments
 (0)