From ac76a9e1408aebe105fd2ed826e5fa763061a896 Mon Sep 17 00:00:00 2001 From: codingma Date: Sun, 28 Apr 2024 11:31:34 +0800 Subject: [PATCH 01/33] support BAdam in WebUI Former-commit-id: 26f71703935407b94ed0787d91e156296bef9993 --- src/llmtuner/webui/components/train.py | 26 ++++++ src/llmtuner/webui/locales.py | 109 +++++++++++++++++++++++++ src/llmtuner/webui/runner.py | 9 ++ 3 files changed, 144 insertions(+) diff --git a/src/llmtuner/webui/components/train.py b/src/llmtuner/webui/components/train.py index 7dc324af..9d93a9b6 100644 --- a/src/llmtuner/webui/components/train.py +++ b/src/llmtuner/webui/components/train.py @@ -210,6 +210,32 @@ def create_train_tab(engine: "Engine") -> Dict[str, "Component"]: ) ) + with gr.Accordion(open=False) as badam_tab: + with gr.Row(): + use_badam = gr.Checkbox() + badam_mode = gr.Dropdown(choices=["layer", "ratio"], value="layer") + badam_mask_mode = gr.Dropdown(choices=["adjacent", "scatter"], value="adjacent") + badam_switch_mode = gr.Dropdown(choices=["ascending", "descending", "random", "fixed"], value="ascending") + badam_update_ratio = gr.Slider(value=0, minimum=0, maximum=1, step=0.01) + badam_switch_block_every = gr.Slider(value=50, minimum=-1, maximum=200, step=1) + + badam_verbose = gr.Dropdown(choices=[0, 1, 2], value=0) + + input_elems.update({use_badam, badam_mode, badam_switch_block_every, badam_switch_mode, badam_update_ratio, + badam_mask_mode, badam_verbose}) + elem_dict.update( + dict( + badam_tab=badam_tab, + use_badam=use_badam, + badam_mode=badam_mode, + badam_switch_block_every=badam_switch_block_every, + badam_switch_mode=badam_switch_mode, + badam_update_ratio=badam_update_ratio, + badam_mask_mode=badam_mask_mode, + badam_verbose=badam_verbose, + ) + ) + with gr.Row(): cmd_preview_btn = gr.Button() arg_save_btn = gr.Button() diff --git a/src/llmtuner/webui/locales.py b/src/llmtuner/webui/locales.py index d341c7b6..d3dd4dc2 100644 --- a/src/llmtuner/webui/locales.py +++ b/src/llmtuner/webui/locales.py @@ -891,6 +891,115 @@ LOCALES = { "info": "应用 GaLore 的模块名称。使用英文逗号分隔多个名称。", }, }, + "badam_tab": { + "en": { + "label": "BAdam configurations", + }, + "ru": { + "label": "Конфигурации BAdam", + }, + "zh": { + "label": "BAdam 参数设置", + }, + }, + "use_badam": { + "en": { + "label": "Use BAdam", + "info": "Enable the block coordinate optimization with Adam.", + }, + "ru": { + "label": "Использовать BAdam", + "info": "Включите блочную оптимизацию координат с Adam.", + }, + "zh": { + "label": "使用 BAdam", + "info": "使用多Block协同的Adam优化器。", + }, + }, + "badam_mode": { + "en": { + "label": "BAdam mode", + "info": "Whether to use layer-wise or ratio-wise BAdam optimizer.", + }, + "ru": { + "label": "Режим BAdam", + "info": "Использовать оптимизатор BAdam с обработкой слоев или с обработкой коэффициентов.", + }, + "zh": { + "label": "BAdam 模式", + "info": "使用layer或者ratio比例模式。", + }, + }, + "badam_switch_block_every": { + "en": { + "label": "Switch block frequency", + "info": "How often to switch model's block update. Set to -1 to disable the block update.", + }, + "ru": { + "label": "Частота переключения", + "info": "Как часто переключать обновление блока модели. Установите -1, чтобы отключить обновление блока.", + }, + "zh": { + "label": "切换block的频率", + "info": "控制切换block切换的频率,如果是-1,则不切换。", + }, + }, + "badam_switch_mode": { + "en": { + "label": "Switch mode", + "info": "The strategy of picking block to update for layer-wise BAdam.", + }, + "ru": { + "label": "Переключить режим", + "info": "Стратегия выбора блока для обновления в методе BAdam по слоям.", + }, + "zh": { + "label": "Block切换策略", + "info": "如果是layer类型的训练模式,如何切换block。", + }, + }, + "badam_update_ratio": { + "en": { + "label": "Update ratio", + "info": "The ratio of the update for ratio-wise BAdam.", + }, + "ru": { + "label": "Коэффициент обновления", + "info": "Коэффициент обновления для метода BAdam, основанного на коэффициентах.", + }, + "zh": { + "label": "Block更新比例", + "info": "如果是比例类型的训练模式,block每次更新的范围比例。", + }, + }, + "badam_mask_mode": { + "en": { + "label": "Mask mode", + "info": "The mode of the mask for BAdam optimizer.", + }, + "ru": { + "label": "Режим маски", + "info": "Режим маски для оптимизатора BAdam.", + }, + "zh": { + "label": "Mask模式", + "info": "BAdam优化器内训练参数的mask关系。", + }, + }, + "badam_verbose": { + "en": { + "label": "Verbosity level", + "info": "0 for no print, 1 for print the block prefix, 2 for print trainable parameters.", + }, + "ru": { + "label": "Уровень многословности", + "info": "0 для отсутствия печати, 1 для печати префикса блока, 2 для печати обучаемых параметров.", + }, + "zh": { + "label": "输出日志级别", + "info": "0:不输出,1:输出block前缀, 1:输出可训练的参数。", + }, + }, "cmd_preview_btn": { "en": { "value": "Preview command", diff --git a/src/llmtuner/webui/runner.py b/src/llmtuner/webui/runner.py index 8054484f..52584f31 100644 --- a/src/llmtuner/webui/runner.py +++ b/src/llmtuner/webui/runner.py @@ -151,6 +151,7 @@ class Runner: fp16=(get("train.compute_type") == "fp16"), bf16=(get("train.compute_type") == "bf16"), pure_bf16=(get("train.compute_type") == "pure_bf16"), + use_badam=get("train.use_badam"), ) args["disable_tqdm"] = True @@ -198,6 +199,14 @@ class Runner: args["galore_scale"] = get("train.galore_scale") args["galore_target"] = get("train.galore_target") + if args["use_badam"]: + args["badam_mode"] = get("train.badam_mode") + args["badam_switch_block_every"] = get("train.badam_switch_block_every") + args["badam_switch_mode"] = get("train.badam_switch_mode") + args["badam_update_ratio"] = get("train.badam_update_ratio") + args["badam_mask_mode"] = get("train.badam_mask_mode") + args["badam_verbose"] = get("train.badam_verbose") + return args def _parse_eval_args(self, data: Dict["Component", Any]) -> Dict[str, Any]: From 6f0b412265b0077289247d6a177e14e6f987b1a1 Mon Sep 17 00:00:00 2001 From: khazic Date: Sun, 28 Apr 2024 14:27:45 +0800 Subject: [PATCH 02/33] added the second sharegpt format Former-commit-id: d1ba32e4bb70489a9e6f5d3657988c9b7553a157 --- data/README.md | 32 ++++++++++++++++++++++++++++---- data/README_zh.md | 26 +++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 5 deletions(-) diff --git a/data/README.md b/data/README.md index 6de0430f..9158233f 100644 --- a/data/README.md +++ b/data/README.md @@ -94,20 +94,44 @@ Remember to set `"ranking": true` for the preference datasets. The dataset in sharegpt format should follow the below format: ```json +# The first sharegpt format [ { "conversations": [ { "from": "human", - "value": "user instruction" + "value": "用户指令" }, { "from": "gpt", - "value": "model response" + "value": "模型回答" } ], - "system": "system prompt (optional)", - "tools": "tool description (optional)" + "system": "系统提示词(选填)", + "tools": "工具描述(选填)" + } +] + +# The second sharegpt format + +[ + { + "type": "chatml", + "messages": [ + { + "role": "system", + "content": "You are a helpful assistant." + }, + { + "role": "user", + "content": "Tell me something about large language models." + }, + { + "role": "assistant", + "content": "Large language models are a type of language model ..." + } + ], + "source": "unknown" } ] ``` diff --git a/data/README_zh.md b/data/README_zh.md index fb6cb1d9..9abef5b6 100644 --- a/data/README_zh.md +++ b/data/README_zh.md @@ -37,7 +37,7 @@ ---- -该项目目前支持两种格式的数据集:**alpaca** 和 **sharegpt**,其中 alpaca 格式的数据集按照以下方式组织: +该项目目前支持三种格式的数据集:**alpaca** 和 **sharegpt**,其中 alpaca 格式的数据集按照以下方式组织: ```json [ @@ -94,6 +94,7 @@ 而 sharegpt 格式的数据集按照以下方式组织: ```json +# 第一种sharegpt格式 [ { "conversations": [ @@ -110,6 +111,29 @@ "tools": "工具描述(选填)" } ] + +# 第二种sharegpt格式 + +[ + { + "type": "chatml", + "messages": [ + { + "role": "system", + "content": "You are a helpful assistant." + }, + { + "role": "user", + "content": "Tell me something about large language models." + }, + { + "role": "assistant", + "content": "Large language models are a type of language model ..." + } + ], + "source": "unknown" + } +] ``` 对于上述格式的数据,`dataset_info.json` 中的 `columns` 应为: From db316422a4806315f6bfb4159684b4326562a558 Mon Sep 17 00:00:00 2001 From: khazic Date: Sun, 28 Apr 2024 14:30:05 +0800 Subject: [PATCH 03/33] Upgrade the second sharegpt format Former-commit-id: 288911fc7b1e12e53f3396c371cf4b4c7300b4bf --- data/README_zh.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/data/README_zh.md b/data/README_zh.md index 9abef5b6..5a9db167 100644 --- a/data/README_zh.md +++ b/data/README_zh.md @@ -120,15 +120,15 @@ "messages": [ { "role": "system", - "content": "You are a helpful assistant." + "content": "你是一个很有用的AI助手" }, { "role": "user", - "content": "Tell me something about large language models." + "content": "告诉我一些关于大模型的一些信息" }, { "role": "assistant", - "content": "Large language models are a type of language model ..." + "content": "大模型是一种语言模型" } ], "source": "unknown" From f15836c77acffc3552f2d25502e871b41c6a60d4 Mon Sep 17 00:00:00 2001 From: Lao Date: Sun, 28 Apr 2024 23:31:37 +0800 Subject: [PATCH 04/33] Update README_zh.md Former-commit-id: ce17eccf451649728cf7b45312fd7f75d3a8a246 --- data/README_zh.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/data/README_zh.md b/data/README_zh.md index 5a9db167..1fe98a9e 100644 --- a/data/README_zh.md +++ b/data/README_zh.md @@ -37,7 +37,7 @@ ---- -该项目目前支持三种格式的数据集:**alpaca** 和 **sharegpt**,其中 alpaca 格式的数据集按照以下方式组织: +该项目目前支持二种格式的数据集:**alpaca** 和 **sharegpt**,其中 alpaca 格式的数据集按照以下方式组织: ```json [ From 2186deceac4735547bc9d1fd76088eeead9ff2d6 Mon Sep 17 00:00:00 2001 From: hoshi-hiyouga Date: Thu, 2 May 2024 02:13:46 +0800 Subject: [PATCH 05/33] Update README.md Former-commit-id: b072ec9d1b18f7e9d5d2c9529eac55d29ca832c8 --- data/README.md | 145 +++++++++++++++++++++++++++++++++---------------- 1 file changed, 99 insertions(+), 46 deletions(-) diff --git a/data/README.md b/data/README.md index 9158233f..012de4e7 100644 --- a/data/README.md +++ b/data/README.md @@ -1,4 +1,4 @@ -If you are using a custom dataset, please provide your dataset definition in the following format in `dataset_info.json`. +If you are using a custom dataset, please add your **dataset description** to `dataset_info.json` according to the following format. We also provide several examples in the next section. ```json "dataset_name": { @@ -33,7 +33,7 @@ If you are using a custom dataset, please provide your dataset definition in the } ``` -Given above, you can use the custom dataset via specifying `--dataset dataset_name`. +After that, you can load the custom dataset by specifying `--dataset dataset_name`. ---- @@ -54,10 +54,11 @@ Currently we support dataset in **alpaca** or **sharegpt** format, the dataset i ] ``` -Regarding the above dataset, the `columns` in `dataset_info.json` should be: +Regarding the above dataset, the description in `dataset_info.json` should be: ```json "dataset_name": { + "file_name": "data.json", "columns": { "prompt": "instruction", "query": "input", @@ -70,76 +71,86 @@ Regarding the above dataset, the `columns` in `dataset_info.json` should be: The `query` column will be concatenated with the `prompt` column and used as the user prompt, then the user prompt would be `prompt\nquery`. The `response` column represents the model response. -The `system` column will be used as the system prompt. The `history` column is a list consisting string tuples representing prompt-response pairs in the history. Note that the responses in the history **will also be used for training**. +The `system` column will be used as the system prompt. The `history` column is a list consisting string tuples representing prompt-response pairs in the history. Note that the responses in the history **will also be used for training** in supervised fine-tuning. -For the pre-training datasets, only the `prompt` column will be used for training. - -For the preference datasets, the `response` column should be a string list whose length is 2, with the preferred answers appearing first, for example: +For the **pre-training datasets**, only the `prompt` column will be used for training, for example: ```json -{ - "instruction": "user instruction", - "input": "user input", - "output": [ - "chosen answer", - "rejected answer" - ] +[ + {"text": "document"}, + {"text": "document"} +] +``` + +Regarding the above dataset, the description in `dataset_info.json` should be: + +```json +"dataset_name": { + "file_name": "data.json", + "columns": { + "prompt": "text" + } } ``` -Remember to set `"ranking": true` for the preference datasets. +For the **preference datasets**, the `response` column should be a string list whose length is 2, with the preferred answers appearing first, for example: + +```json +[ + { + "instruction": "user instruction", + "input": "user input", + "output": [ + "chosen answer", + "rejected answer" + ] + } +] +``` + +Regarding the above dataset, the description in `dataset_info.json` should be: + +```json +"dataset_name": { + "file_name": "data.json", + "ranking": true, + "columns": { + "prompt": "instruction", + "query": "input", + "response": "output", + } +} +``` ---- -The dataset in sharegpt format should follow the below format: +The dataset in **sharegpt** format should follow the below format: ```json -# The first sharegpt format [ { "conversations": [ { "from": "human", - "value": "用户指令" + "value": "user instruction" }, { "from": "gpt", - "value": "模型回答" + "value": "model response" } ], - "system": "系统提示词(选填)", - "tools": "工具描述(选填)" - } -] - -# The second sharegpt format - -[ - { - "type": "chatml", - "messages": [ - { - "role": "system", - "content": "You are a helpful assistant." - }, - { - "role": "user", - "content": "Tell me something about large language models." - }, - { - "role": "assistant", - "content": "Large language models are a type of language model ..." - } - ], - "source": "unknown" + "system": "system prompt (optional)", + "tools": "tool description (optional)" } ] ``` -Regarding the above dataset, the `columns` in `dataset_info.json` should be: +Regarding the above dataset, the description in `dataset_info.json` should be: ```json "dataset_name": { + "file_name": "data.json", + "formatting": "sharegpt", "columns": { "messages": "conversations", "system": "system", @@ -156,4 +167,46 @@ Regarding the above dataset, the `columns` in `dataset_info.json` should be: where the `messages` column should be a list following the `u/a/u/a/u/a` order. -Pre-training datasets and preference datasets are incompatible with the sharegpt format yet. +We also supports the dataset in the **openai** format: + +```json +[ + { + "messages": [ + { + "role": "system", + "content": "system prompt (optional)" + }, + { + "role": "user", + "content": "user instruction" + }, + { + "role": "assistant", + "content": "model response" + } + ] + } +] +``` + +Regarding the above dataset, the description in `dataset_info.json` should be: + +```json +"dataset_name": { + "file_name": "data.json", + "formatting": "sharegpt", + "columns": { + "messages": "messages" + }, + "tags": { + "role_tag": "role", + "content_tag": "content", + "user_tag": "user", + "assistant_tag": "assistant", + "system_tag": "system" + } +} +``` + +Pre-training datasets and preference datasets are **incompatible** with the sharegpt format yet. From eea8a79e35f20ed47548c72dbe1c62c235c8f1d1 Mon Sep 17 00:00:00 2001 From: hoshi-hiyouga Date: Thu, 2 May 2024 02:14:55 +0800 Subject: [PATCH 06/33] Update README_zh.md Former-commit-id: d4d9180c401cb210654792d8052313e8db17fc51 --- data/README_zh.md | 139 ++++++++++++++++++++++++++++++++-------------- 1 file changed, 96 insertions(+), 43 deletions(-) diff --git a/data/README_zh.md b/data/README_zh.md index 1fe98a9e..6449c5d5 100644 --- a/data/README_zh.md +++ b/data/README_zh.md @@ -1,4 +1,4 @@ -如果您使用自定义数据集,请务必在 `dataset_info.json` 文件中按照以下格式提供数据集定义。 +如果您使用自定义数据集,请务必按照以下格式在 `dataset_info.json` 文件中添加**数据集描述**。我们在下面也提供了一些例子。 ```json "数据集名称": { @@ -33,11 +33,11 @@ } ``` -添加后可通过指定 `--dataset 数据集名称` 参数使用自定义数据集。 +然后,可通过使用 `--dataset 数据集名称` 参数加载自定义数据集。 ---- -该项目目前支持二种格式的数据集:**alpaca** 和 **sharegpt**,其中 alpaca 格式的数据集按照以下方式组织: +该项目目前支持两种格式的数据集:**alpaca** 和 **sharegpt**,其中 alpaca 格式的数据集按照以下方式组织: ```json [ @@ -54,10 +54,11 @@ ] ``` -对于上述格式的数据,`dataset_info.json` 中的 `columns` 应为: +对于上述格式的数据,`dataset_info.json` 中的描述应为: ```json "数据集名称": { + "file_name": "data.json", "columns": { "prompt": "instruction", "query": "input", @@ -70,31 +71,62 @@ 其中 `query` 列对应的内容会与 `prompt` 列对应的内容拼接后作为用户指令,即用户指令为 `prompt\nquery`。`response` 列对应的内容为模型回答。 -`system` 列对应的内容将被作为系统提示词。`history` 列是由多个字符串二元组构成的列表,分别代表历史消息中每轮的指令和回答。注意历史消息中的回答**也会被用于训练**。 +`system` 列对应的内容将被作为系统提示词。`history` 列是由多个字符串二元组构成的列表,分别代表历史消息中每轮的指令和回答。注意在指令监督学习时,历史消息中的回答**也会被用于训练**。 -对于预训练数据集,仅 `prompt` 列中的内容会用于模型训练。 - -对于偏好数据集,`response` 列应当是一个长度为 2 的字符串列表,排在前面的代表更优的回答,例如: +对于**预训练数据集**,仅 `prompt` 列中的内容会用于模型训练,例如: ```json -{ - "instruction": "用户指令", - "input": "用户输入", - "output": [ - "优质回答", - "劣质回答" - ] +[ + {"text": "document"}, + {"text": "document"} +] +``` + +对于上述格式的数据,`dataset_info.json` 中的描述应为: + +```json +"数据集名称": { + "file_name": "data.json", + "columns": { + "prompt": "text" + } } ``` -添加偏好数据集需要额外指定 `"ranking": true`。 +对于**偏好数据集**,`response` 列应当是一个长度为 2 的字符串列表,排在前面的代表更优的回答,例如: + +```json +[ + { + "instruction": "用户指令", + "input": "用户输入", + "output": [ + "优质回答", + "劣质回答" + ] + } +] +``` + +对于上述格式的数据,`dataset_info.json` 中的描述应为: + +```json +"数据集名称": { + "file_name": "data.json", + "ranking": true, + "columns": { + "prompt": "instruction", + "query": "input", + "response": "output", + } +} +``` ---- -而 sharegpt 格式的数据集按照以下方式组织: +而 **sharegpt** 格式的数据集按照以下方式组织: ```json -# 第一种sharegpt格式 [ { "conversations": [ @@ -111,35 +143,14 @@ "tools": "工具描述(选填)" } ] - -# 第二种sharegpt格式 - -[ - { - "type": "chatml", - "messages": [ - { - "role": "system", - "content": "你是一个很有用的AI助手" - }, - { - "role": "user", - "content": "告诉我一些关于大模型的一些信息" - }, - { - "role": "assistant", - "content": "大模型是一种语言模型" - } - ], - "source": "unknown" - } -] ``` -对于上述格式的数据,`dataset_info.json` 中的 `columns` 应为: +对于上述格式的数据,`dataset_info.json` 中的描述应为: ```json "数据集名称": { + "file_name": "data.json", + "formatting": "sharegpt", "columns": { "messages": "conversations", "system": "system", @@ -156,4 +167,46 @@ 其中 `messages` 列应当是一个列表,且符合 `用户/模型/用户/模型/用户/模型` 的顺序。 -预训练数据集和偏好数据集尚不支持 sharegpt 格式。 +我们同样支持 **openai** 格式的数据集: + +```json +[ + { + "messages": [ + { + "role": "system", + "content": "系统提示词(选填)" + }, + { + "role": "user", + "content": "用户指令" + }, + { + "role": "assistant", + "content": "模型回答" + } + ] + } +] +``` + +对于上述格式的数据,`dataset_info.json` 中的描述应为: + +```json +"数据集名称": { + "file_name": "data.json", + "formatting": "sharegpt", + "columns": { + "messages": "messages" + }, + "tags": { + "role_tag": "role", + "content_tag": "content", + "user_tag": "user", + "assistant_tag": "assistant", + "system_tag": "system" + } +} +``` + +预训练数据集和偏好数据集**尚不支持** sharegpt 格式。 From 1d00dede8eb8c8d0dc4d0d6d8b487fd5208f7328 Mon Sep 17 00:00:00 2001 From: hoshi-hiyouga Date: Thu, 2 May 2024 02:21:27 +0800 Subject: [PATCH 07/33] Update train.py Former-commit-id: dcd53cb89ae92f92ad1242e8988a18cac5292459 --- src/llmtuner/webui/components/train.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/llmtuner/webui/components/train.py b/src/llmtuner/webui/components/train.py index 9d93a9b6..be070869 100644 --- a/src/llmtuner/webui/components/train.py +++ b/src/llmtuner/webui/components/train.py @@ -214,25 +214,19 @@ def create_train_tab(engine: "Engine") -> Dict[str, "Component"]: with gr.Row(): use_badam = gr.Checkbox() badam_mode = gr.Dropdown(choices=["layer", "ratio"], value="layer") - badam_mask_mode = gr.Dropdown(choices=["adjacent", "scatter"], value="adjacent") badam_switch_mode = gr.Dropdown(choices=["ascending", "descending", "random", "fixed"], value="ascending") - badam_update_ratio = gr.Slider(value=0, minimum=0, maximum=1, step=0.01) badam_switch_block_every = gr.Slider(value=50, minimum=-1, maximum=200, step=1) + badam_update_ratio = gr.Slider(value=0, minimum=0, maximum=1, step=0.01) - badam_verbose = gr.Dropdown(choices=[0, 1, 2], value=0) - - input_elems.update({use_badam, badam_mode, badam_switch_block_every, badam_switch_mode, badam_update_ratio, - badam_mask_mode, badam_verbose}) + input_elems.update({use_badam, badam_mode, badam_switch_mode, badam_switch_block_every, badam_update_ratio}) elem_dict.update( dict( badam_tab=badam_tab, use_badam=use_badam, badam_mode=badam_mode, - badam_switch_block_every=badam_switch_block_every, badam_switch_mode=badam_switch_mode, + badam_switch_block_every=badam_switch_block_every, badam_update_ratio=badam_update_ratio, - badam_mask_mode=badam_mask_mode, - badam_verbose=badam_verbose, ) ) From ed8d9e0881025ba3d39ebf9184ead88677161d30 Mon Sep 17 00:00:00 2001 From: hiyouga Date: Thu, 2 May 2024 02:47:04 +0800 Subject: [PATCH 08/33] fix badam configs Former-commit-id: 9433c8c215881692f318b89df03af97b4eda4dd5 --- src/llmtuner/hparams/finetuning_args.py | 15 +++-- src/llmtuner/train/utils.py | 4 +- src/llmtuner/webui/components/train.py | 8 +-- src/llmtuner/webui/locales.py | 80 ++++++++----------------- src/llmtuner/webui/runner.py | 6 +- 5 files changed, 44 insertions(+), 69 deletions(-) diff --git a/src/llmtuner/hparams/finetuning_args.py b/src/llmtuner/hparams/finetuning_args.py index f4f71bc5..03bf52af 100644 --- a/src/llmtuner/hparams/finetuning_args.py +++ b/src/llmtuner/hparams/finetuning_args.py @@ -221,16 +221,18 @@ class BAdamArgument: default=None, metadata={"help": "The starting block index for layer-wise BAdam."}, ) - badam_switch_block_every: Optional[int] = field( - default=50, - metadata={"help": "How often to switch model's block update. Set to -1 to disable the block update."}, - ) badam_switch_mode: Optional[Literal["ascending", "descending", "random", "fixed"]] = field( default="ascending", metadata={"help": "the strategy of picking block to update for layer-wise BAdam."}, ) + badam_switch_interval: Optional[int] = field( + default=50, + metadata={ + "help": "Number of steps to update the block for layer-wise BAdam. Use -1 to disable the block update." + }, + ) badam_update_ratio: float = field( - default=0.0, + default=0.05, metadata={"help": "The ratio of the update for ratio-wise BAdam."}, ) badam_mask_mode: Literal["adjacent", "scatter"] = field( @@ -308,6 +310,9 @@ class FinetuningArguments(FreezeArguments, LoraArguments, RLHFArguments, GaloreA if self.use_galore and self.finetuning_type == "lora": raise ValueError("Cannot use LoRA with GaLore together.") + if self.use_galore and self.use_badam: + raise ValueError("Cannot use GaLore with BAdam together.") + if self.loraplus_lr_ratio is not None and self.finetuning_type != "lora": raise ValueError("`loraplus_lr_ratio` is only valid for the LoRA training.") diff --git a/src/llmtuner/train/utils.py b/src/llmtuner/train/utils.py index d9fc363d..21dac461 100644 --- a/src/llmtuner/train/utils.py +++ b/src/llmtuner/train/utils.py @@ -317,14 +317,14 @@ def _create_badam_optimizer( base_optimizer=base_optimizer, named_parameters_list=list(model.named_parameters()), block_prefix_list=None, - switch_block_every=finetuning_args.badam_switch_block_every, + switch_block_every=finetuning_args.badam_switch_interval, start_block=finetuning_args.badam_start_block, switch_mode=finetuning_args.badam_switch_mode, verbose=finetuning_args.badam_verbose, ) logger.info( f"Using BAdam optimizer with layer-wise update, switch mode is {finetuning_args.badam_switch_mode}, " - f"switch block every {finetuning_args.badam_switch_block_every} steps, " + f"switch block every {finetuning_args.badam_switch_interval} steps, " f"default start block is {finetuning_args.badam_start_block}" ) diff --git a/src/llmtuner/webui/components/train.py b/src/llmtuner/webui/components/train.py index be070869..c9671289 100644 --- a/src/llmtuner/webui/components/train.py +++ b/src/llmtuner/webui/components/train.py @@ -215,17 +215,17 @@ def create_train_tab(engine: "Engine") -> Dict[str, "Component"]: use_badam = gr.Checkbox() badam_mode = gr.Dropdown(choices=["layer", "ratio"], value="layer") badam_switch_mode = gr.Dropdown(choices=["ascending", "descending", "random", "fixed"], value="ascending") - badam_switch_block_every = gr.Slider(value=50, minimum=-1, maximum=200, step=1) - badam_update_ratio = gr.Slider(value=0, minimum=0, maximum=1, step=0.01) + badam_switch_interval = gr.Slider(value=50, minimum=1, maximum=1024, step=1) + badam_update_ratio = gr.Slider(value=0.05, minimum=0, maximum=1, step=0.01) - input_elems.update({use_badam, badam_mode, badam_switch_mode, badam_switch_block_every, badam_update_ratio}) + input_elems.update({use_badam, badam_mode, badam_switch_mode, badam_switch_interval, badam_update_ratio}) elem_dict.update( dict( badam_tab=badam_tab, use_badam=use_badam, badam_mode=badam_mode, badam_switch_mode=badam_switch_mode, - badam_switch_block_every=badam_switch_block_every, + badam_switch_interval=badam_switch_interval, badam_update_ratio=badam_update_ratio, ) ) diff --git a/src/llmtuner/webui/locales.py b/src/llmtuner/webui/locales.py index d3dd4dc2..1c474f34 100644 --- a/src/llmtuner/webui/locales.py +++ b/src/llmtuner/webui/locales.py @@ -905,15 +905,15 @@ LOCALES = { "use_badam": { "en": { "label": "Use BAdam", - "info": "Enable the block coordinate optimization with Adam.", + "info": "Enable the BAdam optimizer.", }, "ru": { "label": "Использовать BAdam", - "info": "Включите блочную оптимизацию координат с Adam.", + "info": "Включите оптимизатор BAdam.", }, "zh": { "label": "使用 BAdam", - "info": "使用多Block协同的Adam优化器。", + "info": "使用 BAdam 优化器。", }, }, "badam_mode": { @@ -923,25 +923,11 @@ LOCALES = { }, "ru": { "label": "Режим BAdam", - "info": "Использовать оптимизатор BAdam с обработкой слоев или с обработкой коэффициентов.", + "info": "Использовать ли оптимизатор BAdam с послоевой или пропорциональной настройкой.", }, "zh": { "label": "BAdam 模式", - "info": "使用layer或者ratio比例模式。", - }, - }, - "badam_switch_block_every": { - "en": { - "label": "Switch block frequency", - "info": "How often to switch model's block update. Set to -1 to disable the block update.", - }, - "ru": { - "label": "Частота переключения", - "info": "Как часто переключать обновление блока модели. Установите -1, чтобы отключить обновление блока.", - }, - "zh": { - "label": "切换block的频率", - "info": "控制切换block切换的频率,如果是-1,则不切换。", + "info": "使用 layer-wise 或 ratio-wise BAdam 优化器。", }, }, "badam_switch_mode": { @@ -950,12 +936,26 @@ LOCALES = { "info": "The strategy of picking block to update for layer-wise BAdam.", }, "ru": { - "label": "Переключить режим", - "info": "Стратегия выбора блока для обновления в методе BAdam по слоям.", + "label": "Режим переключения", + "info": "Стратегия выбора блока для обновления для послойного BAdam.", }, "zh": { - "label": "Block切换策略", - "info": "如果是layer类型的训练模式,如何切换block。", + "label": "切换策略", + "info": "Layer-wise BAdam 优化器的块切换策略。", + }, + }, + "badam_switch_interval": { + "en": { + "label": "Switch interval", + "info": "Number of steps to update the block for layer-wise BAdam.", + }, + "ru": { + "label": "Интервал переключения", + "info": "количество шагов для обновления блока для пошагового BAdam.", + }, + "zh": { + "label": "切换频率", + "info": "Layer-wise BAdam 优化器的块切换频率。", }, }, "badam_update_ratio": { @@ -965,39 +965,11 @@ LOCALES = { }, "ru": { "label": "Коэффициент обновления", - "info": "Коэффициент обновления для метода BAdam, основанного на коэффициентах.", + "info": "Коэффициент обновления для BAdam с учётом соотношений.", }, "zh": { - "label": "Block更新比例", - "info": "如果是比例类型的训练模式,block每次更新的范围比例。", - }, - }, - "badam_mask_mode": { - "en": { - "label": "Mask mode", - "info": "The mode of the mask for BAdam optimizer.", - }, - "ru": { - "label": "Режим маски", - "info": "Режим маски для оптимизатора BAdam.", - }, - "zh": { - "label": "Mask模式", - "info": "BAdam优化器内训练参数的mask关系。", - }, - }, - "badam_verbose": { - "en": { - "label": "Verbosity level", - "info": "0 for no print, 1 for print the block prefix, 2 for print trainable parameters.", - }, - "ru": { - "label": "Уровень многословности", - "info": "0 для отсутствия печати, 1 для печати префикса блока, 2 для печати обучаемых параметров.", - }, - "zh": { - "label": "输出日志级别", - "info": "0:不输出,1:输出block前缀, 1:输出可训练的参数。", + "label": "Block 更新比例", + "info": "Ratio-wise BAdam 优化器的更新比例。", }, }, "cmd_preview_btn": { diff --git a/src/llmtuner/webui/runner.py b/src/llmtuner/webui/runner.py index 52584f31..d53a4dfe 100644 --- a/src/llmtuner/webui/runner.py +++ b/src/llmtuner/webui/runner.py @@ -147,11 +147,11 @@ class Runner: shift_attn=get("train.shift_attn"), report_to="all" if get("train.report_to") else "none", use_galore=get("train.use_galore"), + use_badam=get("train.use_badam"), output_dir=get_save_dir(get("top.model_name"), get("top.finetuning_type"), get("train.output_dir")), fp16=(get("train.compute_type") == "fp16"), bf16=(get("train.compute_type") == "bf16"), pure_bf16=(get("train.compute_type") == "pure_bf16"), - use_badam=get("train.use_badam"), ) args["disable_tqdm"] = True @@ -201,11 +201,9 @@ class Runner: if args["use_badam"]: args["badam_mode"] = get("train.badam_mode") - args["badam_switch_block_every"] = get("train.badam_switch_block_every") args["badam_switch_mode"] = get("train.badam_switch_mode") + args["badam_switch_interval"] = get("train.badam_switch_interval") args["badam_update_ratio"] = get("train.badam_update_ratio") - args["badam_mask_mode"] = get("train.badam_mask_mode") - args["badam_verbose"] = get("train.badam_verbose") return args From 4cddd4be26ae6ca8ccced8daa39595fd53bd8f90 Mon Sep 17 00:00:00 2001 From: hiyouga Date: Thu, 2 May 2024 17:16:02 +0800 Subject: [PATCH 09/33] Update prepare.sh Former-commit-id: 39e964a97a8d764fcb4620cc3ff03b4f40c73d7e --- examples/lora_single_gpu/prepare.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/examples/lora_single_gpu/prepare.sh b/examples/lora_single_gpu/prepare.sh index 3652cea4..e86de636 100644 --- a/examples/lora_single_gpu/prepare.sh +++ b/examples/lora_single_gpu/prepare.sh @@ -1,4 +1,5 @@ #!/bin/bash +# use `--tokenized_path` in training script to load data CUDA_VISIBLE_DEVICES= python ../../src/train_bash.py \ --stage sft \ From 289d1f3679a16c1ffea980393be02fd7eea7f933 Mon Sep 17 00:00:00 2001 From: hiyouga Date: Fri, 3 May 2024 02:58:23 +0800 Subject: [PATCH 10/33] update webui and add CLIs Former-commit-id: 245fe47ece22a4b7822449b126715aaa8ec25aba --- Dockerfile | 2 +- README.md | 6 +- README_zh.md | 6 +- examples/extras/badam/sft.sh | 2 +- examples/extras/fsdp_qlora/sft.sh | 2 +- examples/extras/galore/sft.sh | 2 +- examples/extras/llama_pro/sft.sh | 2 +- examples/extras/loraplus/sft.sh | 2 +- examples/extras/mod/sft.sh | 2 +- examples/full_multi_gpu/multi_node.sh | 2 +- examples/full_multi_gpu/predict.sh | 2 +- examples/full_multi_gpu/single_node.sh | 2 +- examples/inference/api_demo.sh | 2 +- examples/inference/cli_demo.sh | 2 +- examples/inference/evaluate.sh | 2 +- examples/inference/web_demo.sh | 2 +- examples/lora_multi_gpu/ds_zero3.sh | 3 +- examples/lora_multi_gpu/multi_node.sh | 2 +- examples/lora_multi_gpu/single_node.sh | 2 +- examples/lora_single_gpu/dpo.sh | 2 +- examples/lora_single_gpu/orpo.sh | 2 +- examples/lora_single_gpu/ppo.sh | 2 +- examples/lora_single_gpu/predict.sh | 2 +- examples/lora_single_gpu/prepare.sh | 2 +- examples/lora_single_gpu/pretrain.sh | 2 +- examples/lora_single_gpu/reward.sh | 2 +- examples/lora_single_gpu/sft.sh | 2 +- examples/lora_single_gpu/sft_mllm.sh | 2 +- examples/merge_lora/merge.sh | 2 +- examples/merge_lora/quantize.sh | 2 +- examples/qlora_single_gpu/aqlm.sh | 2 +- examples/qlora_single_gpu/awq.sh | 2 +- examples/qlora_single_gpu/bitsandbytes.sh | 2 +- examples/qlora_single_gpu/gptq.sh | 2 +- requirements.txt | 1 + setup.py | 1 + src/api_demo.py | 16 --- src/cli_demo.py | 49 -------- src/evaluate.py | 9 -- src/export_model.py | 9 -- src/llmtuner/__init__.py | 10 +- src/llmtuner/api/__init__.py | 4 - src/llmtuner/api/app.py | 3 +- src/llmtuner/chat/chat_model.py | 43 +++++++ src/llmtuner/cli.py | 39 ++++++ src/llmtuner/eval/__init__.py | 4 - src/llmtuner/eval/evaluator.py | 2 +- src/llmtuner/extras/callbacks.py | 145 ++++++++++------------ src/llmtuner/extras/constants.py | 8 +- src/llmtuner/extras/logging.py | 34 +++-- src/llmtuner/extras/ploting.py | 25 +++- src/llmtuner/hparams/parser.py | 4 +- src/llmtuner/train/__init__.py | 4 - src/llmtuner/train/tuner.py | 8 +- src/llmtuner/webui/__init__.py | 4 - src/llmtuner/webui/common.py | 11 +- src/llmtuner/webui/components/export.py | 2 +- src/llmtuner/webui/components/train.py | 6 +- src/llmtuner/webui/engine.py | 4 +- src/llmtuner/webui/interface.py | 6 +- src/llmtuner/webui/runner.py | 77 +++++------- src/llmtuner/webui/utils.py | 108 ++++++++-------- src/{train_bash.py => train.py} | 4 +- src/train_web.py | 9 -- src/web_demo.py | 9 -- 65 files changed, 363 insertions(+), 372 deletions(-) delete mode 100644 src/api_demo.py delete mode 100644 src/cli_demo.py delete mode 100644 src/evaluate.py delete mode 100644 src/export_model.py create mode 100644 src/llmtuner/cli.py rename src/{train_bash.py => train.py} (67%) delete mode 100644 src/train_web.py delete mode 100644 src/web_demo.py diff --git a/Dockerfile b/Dockerfile index c3d231b5..4b8bb084 100644 --- a/Dockerfile +++ b/Dockerfile @@ -11,4 +11,4 @@ RUN pip install -e .[deepspeed,metrics,bitsandbytes,qwen] VOLUME [ "/root/.cache/huggingface/", "/app/data", "/app/output" ] EXPOSE 7860 -CMD [ "python", "src/train_web.py" ] +CMD [ "llamafactory-cli webui" ] diff --git a/README.md b/README.md index 04e5aa5b..8caac93f 100644 --- a/README.md +++ b/README.md @@ -346,7 +346,7 @@ To enable FlashAttention-2 on the Windows platform, you need to install the prec ```bash export CUDA_VISIBLE_DEVICES=0 # `set CUDA_VISIBLE_DEVICES=0` for Windows export GRADIO_SERVER_PORT=7860 # `set GRADIO_SERVER_PORT=7860` for Windows -python src/train_web.py # or python -m llmtuner.webui.interface +llamafactory-cli webui ```
For Alibaba Cloud users @@ -392,12 +392,12 @@ docker compose -f ./docker-compose.yml up -d See [examples/README.md](examples/README.md) for usage. -Use `python src/train_bash.py -h` to display arguments description. +Use `llamafactory-cli train -h` to display arguments description. ### Deploy with OpenAI-style API and vLLM ```bash -CUDA_VISIBLE_DEVICES=0,1 API_PORT=8000 python src/api_demo.py \ +CUDA_VISIBLE_DEVICES=0,1 API_PORT=8000 llamafactory-cli api \ --model_name_or_path meta-llama/Meta-Llama-3-8B-Instruct \ --template llama3 \ --infer_backend vllm \ diff --git a/README_zh.md b/README_zh.md index 2240c688..27522232 100644 --- a/README_zh.md +++ b/README_zh.md @@ -346,7 +346,7 @@ pip install https://github.com/jllllll/bitsandbytes-windows-webui/releases/downl ```bash export CUDA_VISIBLE_DEVICES=0 # Windows 使用 `set CUDA_VISIBLE_DEVICES=0` export GRADIO_SERVER_PORT=7860 # Windows 使用 `set GRADIO_SERVER_PORT=7860` -python src/train_web.py # 或 python -m llmtuner.webui.interface +llamafactory-cli webui ```
阿里云用户指南 @@ -392,12 +392,12 @@ docker compose -f ./docker-compose.yml up -d 使用方法请参考 [examples/README_zh.md](examples/README_zh.md)。 -您可以执行 `python src/train_bash.py -h` 来查看参数文档。 +您可以执行 `llamafactory-cli train -h` 来查看参数文档。 ### 利用 vLLM 部署 OpenAI API ```bash -CUDA_VISIBLE_DEVICES=0,1 API_PORT=8000 python src/api_demo.py \ +CUDA_VISIBLE_DEVICES=0,1 API_PORT=8000 llamafactory-cli api \ --model_name_or_path meta-llama/Meta-Llama-3-8B-Instruct \ --template llama3 \ --infer_backend vllm \ diff --git a/examples/extras/badam/sft.sh b/examples/extras/badam/sft.sh index c2319caa..61167dad 100644 --- a/examples/extras/badam/sft.sh +++ b/examples/extras/badam/sft.sh @@ -1,6 +1,6 @@ #!/bin/bash -CUDA_VISIBLE_DEVICES=0 python ../../../src/train_bash.py \ +CUDA_VISIBLE_DEVICES=0 llamafactory-cli train \ --stage sft \ --do_train \ --model_name_or_path meta-llama/Llama-2-7b-hf \ diff --git a/examples/extras/fsdp_qlora/sft.sh b/examples/extras/fsdp_qlora/sft.sh index e8b9ece7..9eb70a53 100644 --- a/examples/extras/fsdp_qlora/sft.sh +++ b/examples/extras/fsdp_qlora/sft.sh @@ -7,7 +7,7 @@ pip install "bitsandbytes>=0.43.0" CUDA_VISIBLE_DEVICES=0,1 accelerate launch \ --config_file ../../accelerate/fsdp_config.yaml \ - ../../../src/train_bash.py \ + ../../../src/train.py \ --stage sft \ --do_train \ --model_name_or_path meta-llama/Llama-2-70b-hf \ diff --git a/examples/extras/galore/sft.sh b/examples/extras/galore/sft.sh index da1779ed..283673e7 100644 --- a/examples/extras/galore/sft.sh +++ b/examples/extras/galore/sft.sh @@ -1,6 +1,6 @@ #!/bin/bash -CUDA_VISIBLE_DEVICES=0 python ../../../src/train_bash.py \ +CUDA_VISIBLE_DEVICES=0 llamafactory-cli train \ --stage sft \ --do_train \ --model_name_or_path meta-llama/Llama-2-7b-hf \ diff --git a/examples/extras/llama_pro/sft.sh b/examples/extras/llama_pro/sft.sh index 573078ff..3e26e0a6 100644 --- a/examples/extras/llama_pro/sft.sh +++ b/examples/extras/llama_pro/sft.sh @@ -1,6 +1,6 @@ #!/bin/bash -CUDA_VISIBLE_DEVICES=0 python ../../../src/train_bash.py \ +CUDA_VISIBLE_DEVICES=0 llamafactory-cli train \ --stage sft \ --do_train \ --model_name_or_path ../../../models/llama2-7b-pro \ diff --git a/examples/extras/loraplus/sft.sh b/examples/extras/loraplus/sft.sh index cb334e7d..8d152d9e 100644 --- a/examples/extras/loraplus/sft.sh +++ b/examples/extras/loraplus/sft.sh @@ -1,6 +1,6 @@ #!/bin/bash -CUDA_VISIBLE_DEVICES=0 python ../../src/train_bash.py \ +CUDA_VISIBLE_DEVICES=0 llamafactory-cli train \ --stage sft \ --do_train \ --model_name_or_path meta-llama/Llama-2-7b-hf \ diff --git a/examples/extras/mod/sft.sh b/examples/extras/mod/sft.sh index 2c8f04a3..5219751f 100644 --- a/examples/extras/mod/sft.sh +++ b/examples/extras/mod/sft.sh @@ -1,6 +1,6 @@ #!/bin/bash -CUDA_VISIBLE_DEVICES=0 python ../../../src/train_bash.py \ +CUDA_VISIBLE_DEVICES=0 llamafactory-cli train \ --stage sft \ --do_train \ --model_name_or_path meta-llama/Llama-2-7b-hf \ diff --git a/examples/full_multi_gpu/multi_node.sh b/examples/full_multi_gpu/multi_node.sh index d1382bc2..a1ffc0ee 100644 --- a/examples/full_multi_gpu/multi_node.sh +++ b/examples/full_multi_gpu/multi_node.sh @@ -6,7 +6,7 @@ python -m torch.distributed.run \ --node_rank $RANK \ --master_addr $MASTER_ADDR \ --master_port $MASTER_PORT \ - ../../src/train_bash.py \ + ../../src/train.py \ --deepspeed ../deepspeed/ds_z3_config.json \ --stage sft \ --do_train \ diff --git a/examples/full_multi_gpu/predict.sh b/examples/full_multi_gpu/predict.sh index 801df85a..7c2e458f 100644 --- a/examples/full_multi_gpu/predict.sh +++ b/examples/full_multi_gpu/predict.sh @@ -2,7 +2,7 @@ CUDA_VISIBLE_DEVICES=0,1,2,3 accelerate launch \ --config_file ../accelerate/single_config.yaml \ - ../../src/train_bash.py \ + ../../src/train.py \ --stage sft \ --do_predict \ --model_name_or_path ../../saves/LLaMA2-7B/full/sft \ diff --git a/examples/full_multi_gpu/single_node.sh b/examples/full_multi_gpu/single_node.sh index ea4acf90..73c7662d 100644 --- a/examples/full_multi_gpu/single_node.sh +++ b/examples/full_multi_gpu/single_node.sh @@ -1,6 +1,6 @@ #!/bin/bash -deepspeed --num_gpus 4 ../../src/train_bash.py \ +deepspeed --num_gpus 4 ../../src/train.py \ --deepspeed ../deepspeed/ds_z3_config.json \ --stage sft \ --do_train \ diff --git a/examples/inference/api_demo.sh b/examples/inference/api_demo.sh index aee86595..6f0f1b2e 100644 --- a/examples/inference/api_demo.sh +++ b/examples/inference/api_demo.sh @@ -1,6 +1,6 @@ #!/bin/bash -CUDA_VISIBLE_DEVICES=0 API_PORT=8000 python ../../src/api_demo.py \ +CUDA_VISIBLE_DEVICES=0 API_PORT=8000 llamafactory-cli api \ --model_name_or_path meta-llama/Llama-2-7b-hf \ --adapter_name_or_path ../../saves/LLaMA2-7B/lora/sft \ --template default \ diff --git a/examples/inference/cli_demo.sh b/examples/inference/cli_demo.sh index 3e4a1e4e..bc762411 100644 --- a/examples/inference/cli_demo.sh +++ b/examples/inference/cli_demo.sh @@ -1,6 +1,6 @@ #!/bin/bash -CUDA_VISIBLE_DEVICES=0 python ../../src/cli_demo.py \ +CUDA_VISIBLE_DEVICES=0 llamafactory-cli chat \ --model_name_or_path meta-llama/Llama-2-7b-hf \ --adapter_name_or_path ../../saves/LLaMA2-7B/lora/sft \ --template default \ diff --git a/examples/inference/evaluate.sh b/examples/inference/evaluate.sh index 1fc6ccf8..5030329d 100644 --- a/examples/inference/evaluate.sh +++ b/examples/inference/evaluate.sh @@ -1,6 +1,6 @@ #!/bin/bash -CUDA_VISIBLE_DEVICES=0 python ../../src/evaluate.py \ +CUDA_VISIBLE_DEVICES=0 llamafactory-cli eval \ --model_name_or_path meta-llama/Llama-2-7b-hf \ --adapter_name_or_path ../../saves/LLaMA2-7B/lora/sft \ --template fewshot \ diff --git a/examples/inference/web_demo.sh b/examples/inference/web_demo.sh index 8d6ed09d..a58cd2a0 100644 --- a/examples/inference/web_demo.sh +++ b/examples/inference/web_demo.sh @@ -1,7 +1,7 @@ #!/bin/bash # add `--visual_inputs True` to load MLLM -CUDA_VISIBLE_DEVICES=0 python ../../src/web_demo.py \ +CUDA_VISIBLE_DEVICES=0 llamafactory-cli webchat \ --model_name_or_path meta-llama/Llama-2-7b-hf \ --adapter_name_or_path ../../saves/LLaMA2-7B/lora/sft \ --template default \ diff --git a/examples/lora_multi_gpu/ds_zero3.sh b/examples/lora_multi_gpu/ds_zero3.sh index f429d15b..bc74a6de 100644 --- a/examples/lora_multi_gpu/ds_zero3.sh +++ b/examples/lora_multi_gpu/ds_zero3.sh @@ -1,6 +1,7 @@ #!/bin/bash +# ZeRO-3 enables weight sharding on multiple GPUs -deepspeed --num_gpus 4 ../../src/train_bash.py \ +deepspeed --num_gpus 4 ../../src/train.py \ --deepspeed ../deepspeed/ds_z3_config.json \ --stage sft \ --do_train \ diff --git a/examples/lora_multi_gpu/multi_node.sh b/examples/lora_multi_gpu/multi_node.sh index 85a3e026..a58cac20 100644 --- a/examples/lora_multi_gpu/multi_node.sh +++ b/examples/lora_multi_gpu/multi_node.sh @@ -3,7 +3,7 @@ CUDA_VISIBLE_DEVICES=0,1,2,3 accelerate launch \ --config_file ../accelerate/master_config.yaml \ - ../../src/train_bash.py \ + ../../src/train.py \ --stage sft \ --do_train \ --model_name_or_path meta-llama/Llama-2-7b-hf \ diff --git a/examples/lora_multi_gpu/single_node.sh b/examples/lora_multi_gpu/single_node.sh index 04529cf0..c0719c04 100644 --- a/examples/lora_multi_gpu/single_node.sh +++ b/examples/lora_multi_gpu/single_node.sh @@ -2,7 +2,7 @@ CUDA_VISIBLE_DEVICES=0,1,2,3 accelerate launch \ --config_file ../accelerate/single_config.yaml \ - ../../src/train_bash.py \ + ../../src/train.py \ --stage sft \ --do_train \ --model_name_or_path meta-llama/Llama-2-7b-hf \ diff --git a/examples/lora_single_gpu/dpo.sh b/examples/lora_single_gpu/dpo.sh index 56a2dfc3..2cb6cb01 100644 --- a/examples/lora_single_gpu/dpo.sh +++ b/examples/lora_single_gpu/dpo.sh @@ -1,6 +1,6 @@ #!/bin/bash -CUDA_VISIBLE_DEVICES=0 python ../../src/train_bash.py \ +CUDA_VISIBLE_DEVICES=0 llamafactory-cli train \ --stage dpo \ --do_train \ --model_name_or_path meta-llama/Llama-2-7b-hf \ diff --git a/examples/lora_single_gpu/orpo.sh b/examples/lora_single_gpu/orpo.sh index 407907b1..335707bf 100644 --- a/examples/lora_single_gpu/orpo.sh +++ b/examples/lora_single_gpu/orpo.sh @@ -1,6 +1,6 @@ #!/bin/bash -CUDA_VISIBLE_DEVICES=0 python ../../src/train_bash.py \ +CUDA_VISIBLE_DEVICES=0 llamafactory-cli train \ --stage orpo \ --do_train \ --model_name_or_path meta-llama/Llama-2-7b-hf \ diff --git a/examples/lora_single_gpu/ppo.sh b/examples/lora_single_gpu/ppo.sh index 6a5b770e..9eccb05e 100644 --- a/examples/lora_single_gpu/ppo.sh +++ b/examples/lora_single_gpu/ppo.sh @@ -1,6 +1,6 @@ #!/bin/bash -CUDA_VISIBLE_DEVICES=0 python ../../src/train_bash.py \ +CUDA_VISIBLE_DEVICES=0 llamafactory-cli train \ --stage ppo \ --do_train \ --model_name_or_path meta-llama/Llama-2-7b-hf \ diff --git a/examples/lora_single_gpu/predict.sh b/examples/lora_single_gpu/predict.sh index eb9a18c0..250efed1 100644 --- a/examples/lora_single_gpu/predict.sh +++ b/examples/lora_single_gpu/predict.sh @@ -1,6 +1,6 @@ #!/bin/bash -CUDA_VISIBLE_DEVICES=0 python ../../src/train_bash.py \ +CUDA_VISIBLE_DEVICES=0 llamafactory-cli train \ --stage sft \ --do_predict \ --model_name_or_path meta-llama/Llama-2-7b-hf \ diff --git a/examples/lora_single_gpu/prepare.sh b/examples/lora_single_gpu/prepare.sh index e86de636..277f9b7a 100644 --- a/examples/lora_single_gpu/prepare.sh +++ b/examples/lora_single_gpu/prepare.sh @@ -1,7 +1,7 @@ #!/bin/bash # use `--tokenized_path` in training script to load data -CUDA_VISIBLE_DEVICES= python ../../src/train_bash.py \ +CUDA_VISIBLE_DEVICES= llamafactory-cli train \ --stage sft \ --do_train \ --model_name_or_path meta-llama/Llama-2-7b-hf \ diff --git a/examples/lora_single_gpu/pretrain.sh b/examples/lora_single_gpu/pretrain.sh index 59bdfe62..0782f00c 100644 --- a/examples/lora_single_gpu/pretrain.sh +++ b/examples/lora_single_gpu/pretrain.sh @@ -1,6 +1,6 @@ #!/bin/bash -CUDA_VISIBLE_DEVICES=0 python ../../src/train_bash.py \ +CUDA_VISIBLE_DEVICES=0 llamafactory-cli train \ --stage pt \ --do_train \ --model_name_or_path meta-llama/Llama-2-7b-hf \ diff --git a/examples/lora_single_gpu/reward.sh b/examples/lora_single_gpu/reward.sh index 1212d082..678809fd 100644 --- a/examples/lora_single_gpu/reward.sh +++ b/examples/lora_single_gpu/reward.sh @@ -1,6 +1,6 @@ #!/bin/bash -CUDA_VISIBLE_DEVICES=0 python ../../src/train_bash.py \ +CUDA_VISIBLE_DEVICES=0 llamafactory-cli train \ --stage rm \ --do_train \ --model_name_or_path meta-llama/Llama-2-7b-hf \ diff --git a/examples/lora_single_gpu/sft.sh b/examples/lora_single_gpu/sft.sh index 3bfbc9b8..2047e21f 100644 --- a/examples/lora_single_gpu/sft.sh +++ b/examples/lora_single_gpu/sft.sh @@ -1,6 +1,6 @@ #!/bin/bash -CUDA_VISIBLE_DEVICES=0 python ../../src/train_bash.py \ +CUDA_VISIBLE_DEVICES=0 llamafactory-cli train \ --stage sft \ --do_train \ --model_name_or_path meta-llama/Llama-2-7b-hf \ diff --git a/examples/lora_single_gpu/sft_mllm.sh b/examples/lora_single_gpu/sft_mllm.sh index 7e900918..53e37262 100644 --- a/examples/lora_single_gpu/sft_mllm.sh +++ b/examples/lora_single_gpu/sft_mllm.sh @@ -1,6 +1,6 @@ #!/bin/bash -CUDA_VISIBLE_DEVICES=0 python ../../src/train_bash.py \ +CUDA_VISIBLE_DEVICES=0 llamafactory-cli train \ --stage sft \ --do_train \ --model_name_or_path llava-hf/llava-1.5-7b-hf \ diff --git a/examples/merge_lora/merge.sh b/examples/merge_lora/merge.sh index c50bd6ad..186e64a4 100644 --- a/examples/merge_lora/merge.sh +++ b/examples/merge_lora/merge.sh @@ -1,7 +1,7 @@ #!/bin/bash # DO NOT use quantized model or quantization_bit when merging lora weights -CUDA_VISIBLE_DEVICES=0 python ../../src/export_model.py \ +CUDA_VISIBLE_DEVICES=0 llamafactory-cli export \ --model_name_or_path meta-llama/Llama-2-7b-hf \ --adapter_name_or_path ../../saves/LLaMA2-7B/lora/sft \ --template default \ diff --git a/examples/merge_lora/quantize.sh b/examples/merge_lora/quantize.sh index aeedbe66..4a104645 100644 --- a/examples/merge_lora/quantize.sh +++ b/examples/merge_lora/quantize.sh @@ -1,7 +1,7 @@ #!/bin/bash # NEED TO run `merge.sh` before using this script -CUDA_VISIBLE_DEVICES=0 python ../../src/export_model.py \ +CUDA_VISIBLE_DEVICES=0 llamafactory-cli export \ --model_name_or_path ../../models/llama2-7b-sft \ --template default \ --export_dir ../../models/llama2-7b-sft-int4 \ diff --git a/examples/qlora_single_gpu/aqlm.sh b/examples/qlora_single_gpu/aqlm.sh index 68eb4482..1e0a71ca 100644 --- a/examples/qlora_single_gpu/aqlm.sh +++ b/examples/qlora_single_gpu/aqlm.sh @@ -1,6 +1,6 @@ #!/bin/bash -CUDA_VISIBLE_DEVICES=0 python ../../src/train_bash.py \ +CUDA_VISIBLE_DEVICES=0 llamafactory-cli train \ --stage sft \ --do_train \ --model_name_or_path BlackSamorez/Llama-2-7b-AQLM-2Bit-1x16-hf \ diff --git a/examples/qlora_single_gpu/awq.sh b/examples/qlora_single_gpu/awq.sh index b0f1f46b..c13c8134 100644 --- a/examples/qlora_single_gpu/awq.sh +++ b/examples/qlora_single_gpu/awq.sh @@ -1,6 +1,6 @@ #!/bin/bash -CUDA_VISIBLE_DEVICES=0 python ../../src/train_bash.py \ +CUDA_VISIBLE_DEVICES=0 llamafactory-cli train \ --stage sft \ --do_train \ --model_name_or_path TheBloke/Llama-2-7B-AWQ \ diff --git a/examples/qlora_single_gpu/bitsandbytes.sh b/examples/qlora_single_gpu/bitsandbytes.sh index 84bbb426..27f48d41 100644 --- a/examples/qlora_single_gpu/bitsandbytes.sh +++ b/examples/qlora_single_gpu/bitsandbytes.sh @@ -1,6 +1,6 @@ #!/bin/bash -CUDA_VISIBLE_DEVICES=0 python ../../src/train_bash.py \ +CUDA_VISIBLE_DEVICES=0 llamafactory-cli train \ --stage sft \ --do_train \ --model_name_or_path meta-llama/Llama-2-7b-hf \ diff --git a/examples/qlora_single_gpu/gptq.sh b/examples/qlora_single_gpu/gptq.sh index a971b09f..5b1b80e1 100644 --- a/examples/qlora_single_gpu/gptq.sh +++ b/examples/qlora_single_gpu/gptq.sh @@ -1,6 +1,6 @@ #!/bin/bash -CUDA_VISIBLE_DEVICES=0 python ../../src/train_bash.py \ +CUDA_VISIBLE_DEVICES=0 llamafactory-cli train \ --stage sft \ --do_train \ --model_name_or_path TheBloke/Llama-2-7B-GPTQ \ diff --git a/requirements.txt b/requirements.txt index ecba3ce1..f4818ed2 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,3 +16,4 @@ sse-starlette matplotlib fire packaging +pyyaml diff --git a/setup.py b/setup.py index 6a03138d..f7589eb8 100644 --- a/setup.py +++ b/setup.py @@ -52,6 +52,7 @@ def main(): python_requires=">=3.8.0", install_requires=get_requires(), extras_require=extra_require, + entry_points={"console_scripts": ["llamafactory-cli = llmtuner.cli:main"]}, classifiers=[ "Development Status :: 4 - Beta", "Intended Audience :: Developers", diff --git a/src/api_demo.py b/src/api_demo.py deleted file mode 100644 index a7140675..00000000 --- a/src/api_demo.py +++ /dev/null @@ -1,16 +0,0 @@ -import os - -import uvicorn - -from llmtuner import ChatModel, create_app - - -def main(): - chat_model = ChatModel() - app = create_app(chat_model) - print("Visit http://localhost:{}/docs for API document.".format(os.environ.get("API_PORT", 8000))) - uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("API_PORT", 8000)), workers=1) - - -if __name__ == "__main__": - main() diff --git a/src/cli_demo.py b/src/cli_demo.py deleted file mode 100644 index ba828f51..00000000 --- a/src/cli_demo.py +++ /dev/null @@ -1,49 +0,0 @@ -from llmtuner import ChatModel -from llmtuner.extras.misc import torch_gc - - -try: - import platform - - if platform.system() != "Windows": - import readline # noqa: F401 -except ImportError: - print("Install `readline` for a better experience.") - - -def main(): - chat_model = ChatModel() - messages = [] - print("Welcome to the CLI application, use `clear` to remove the history, use `exit` to exit the application.") - - while True: - try: - query = input("\nUser: ") - except UnicodeDecodeError: - print("Detected decoding error at the inputs, please set the terminal encoding to utf-8.") - continue - except Exception: - raise - - if query.strip() == "exit": - break - - if query.strip() == "clear": - messages = [] - torch_gc() - print("History has been removed.") - continue - - messages.append({"role": "user", "content": query}) - print("Assistant: ", end="", flush=True) - - response = "" - for new_text in chat_model.stream_chat(messages): - print(new_text, end="", flush=True) - response += new_text - print() - messages.append({"role": "assistant", "content": response}) - - -if __name__ == "__main__": - main() diff --git a/src/evaluate.py b/src/evaluate.py deleted file mode 100644 index 705a6e42..00000000 --- a/src/evaluate.py +++ /dev/null @@ -1,9 +0,0 @@ -from llmtuner import Evaluator - - -def main(): - Evaluator().eval() - - -if __name__ == "__main__": - main() diff --git a/src/export_model.py b/src/export_model.py deleted file mode 100644 index 4baeb2c3..00000000 --- a/src/export_model.py +++ /dev/null @@ -1,9 +0,0 @@ -from llmtuner import export_model - - -def main(): - export_model() - - -if __name__ == "__main__": - main() diff --git a/src/llmtuner/__init__.py b/src/llmtuner/__init__.py index b3a980a5..a3a97450 100644 --- a/src/llmtuner/__init__.py +++ b/src/llmtuner/__init__.py @@ -1,11 +1,3 @@ # Level: api, webui > chat, eval, train > data, model > extras, hparams -from .api import create_app -from .chat import ChatModel -from .eval import Evaluator -from .train import export_model, run_exp -from .webui import create_ui, create_web_demo - - -__version__ = "0.7.0" -__all__ = ["create_app", "ChatModel", "Evaluator", "export_model", "run_exp", "create_ui", "create_web_demo"] +__version__ = "0.7.1.dev0" diff --git a/src/llmtuner/api/__init__.py b/src/llmtuner/api/__init__.py index d7059fbd..e69de29b 100644 --- a/src/llmtuner/api/__init__.py +++ b/src/llmtuner/api/__init__.py @@ -1,4 +0,0 @@ -from .app import create_app - - -__all__ = ["create_app"] diff --git a/src/llmtuner/api/app.py b/src/llmtuner/api/app.py index 3f06fef1..36918d1b 100644 --- a/src/llmtuner/api/app.py +++ b/src/llmtuner/api/app.py @@ -224,7 +224,8 @@ def create_app(chat_model: "ChatModel") -> "FastAPI": return app -if __name__ == "__main__": +def run_api(): chat_model = ChatModel() app = create_app(chat_model) + print("Visit http://localhost:{}/docs for API document.".format(os.environ.get("API_PORT", 8000))) uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("API_PORT", 8000)), workers=1) diff --git a/src/llmtuner/chat/chat_model.py b/src/llmtuner/chat/chat_model.py index ba58dd2e..97ae87d7 100644 --- a/src/llmtuner/chat/chat_model.py +++ b/src/llmtuner/chat/chat_model.py @@ -2,6 +2,7 @@ import asyncio from threading import Thread from typing import TYPE_CHECKING, Any, AsyncGenerator, Dict, Generator, List, Optional, Sequence +from ..extras.misc import torch_gc from ..hparams import get_infer_args from .hf_engine import HuggingfaceEngine from .vllm_engine import VllmEngine @@ -95,3 +96,45 @@ class ChatModel: **input_kwargs, ) -> List[float]: return await self.engine.get_scores(batch_input, **input_kwargs) + + +def run_chat(): + try: + import platform + + if platform.system() != "Windows": + import readline # noqa: F401 + except ImportError: + print("Install `readline` for a better experience.") + + chat_model = ChatModel() + messages = [] + print("Welcome to the CLI application, use `clear` to remove the history, use `exit` to exit the application.") + + while True: + try: + query = input("\nUser: ") + except UnicodeDecodeError: + print("Detected decoding error at the inputs, please set the terminal encoding to utf-8.") + continue + except Exception: + raise + + if query.strip() == "exit": + break + + if query.strip() == "clear": + messages = [] + torch_gc() + print("History has been removed.") + continue + + messages.append({"role": "user", "content": query}) + print("Assistant: ", end="", flush=True) + + response = "" + for new_text in chat_model.stream_chat(messages): + print(new_text, end="", flush=True) + response += new_text + print() + messages.append({"role": "assistant", "content": response}) diff --git a/src/llmtuner/cli.py b/src/llmtuner/cli.py new file mode 100644 index 00000000..1b5bd658 --- /dev/null +++ b/src/llmtuner/cli.py @@ -0,0 +1,39 @@ +import sys +from enum import Enum, unique + +from .api.app import run_api +from .chat.chat_model import run_chat +from .eval.evaluator import run_eval +from .train.tuner import export_model, run_exp +from .webui.interface import run_web_demo, run_web_ui + + +@unique +class Command(str, Enum): + API = "api" + CHAT = "chat" + EVAL = "eval" + EXPORT = "export" + TRAIN = "train" + WEBDEMO = "webchat" + WEBUI = "webui" + + +def main(): + command = sys.argv.pop(1) + if command == Command.API: + run_api() + elif command == Command.CHAT: + run_chat() + elif command == Command.EVAL: + run_eval() + elif command == Command.EXPORT: + export_model() + elif command == Command.TRAIN: + run_exp() + elif command == Command.WEBDEMO: + run_web_demo() + elif command == Command.WEBUI: + run_web_ui() + else: + raise NotImplementedError("Unknown command: {}".format(command)) diff --git a/src/llmtuner/eval/__init__.py b/src/llmtuner/eval/__init__.py index 95ce0377..e69de29b 100644 --- a/src/llmtuner/eval/__init__.py +++ b/src/llmtuner/eval/__init__.py @@ -1,4 +0,0 @@ -from .evaluator import Evaluator - - -__all__ = ["Evaluator"] diff --git a/src/llmtuner/eval/evaluator.py b/src/llmtuner/eval/evaluator.py index 7446c6f5..4ea134c6 100644 --- a/src/llmtuner/eval/evaluator.py +++ b/src/llmtuner/eval/evaluator.py @@ -118,6 +118,6 @@ class Evaluator: f.write(score_info) -if __name__ == "__main__": +def run_eval(): evaluator = Evaluator() evaluator.eval() diff --git a/src/llmtuner/extras/callbacks.py b/src/llmtuner/extras/callbacks.py index 6e347c3c..fbe6f373 100644 --- a/src/llmtuner/extras/callbacks.py +++ b/src/llmtuner/extras/callbacks.py @@ -1,14 +1,18 @@ import json +import logging import os +import signal import time +from concurrent.futures import ThreadPoolExecutor from datetime import timedelta -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any, Dict +import transformers from transformers import TrainerCallback from transformers.trainer_utils import PREFIX_CHECKPOINT_DIR, has_length -from .constants import LOG_FILE_NAME -from .logging import get_logger +from .constants import TRAINER_LOG +from .logging import LoggerHandler, get_logger from .misc import fix_valuehead_checkpoint @@ -33,20 +37,32 @@ class FixValueHeadModelCallback(TrainerCallback): class LogCallback(TrainerCallback): - def __init__(self, runner=None): - self.runner = runner - self.in_training = False + def __init__(self, output_dir: str) -> None: + self.aborted = False + self.do_train = False + self.webui_mode = bool(int(os.environ.get("LLAMABOARD_ENABLED", "0"))) + if self.webui_mode: + signal.signal(signal.SIGABRT, self._set_abort) + self.logger_handler = LoggerHandler(output_dir) + logging.root.addHandler(self.logger_handler) + transformers.logging.add_handler(self.logger_handler) + + def _set_abort(self, signum, frame) -> None: + self.aborted = True + + def _reset(self, max_steps: int = 0) -> None: self.start_time = time.time() self.cur_steps = 0 - self.max_steps = 0 + self.max_steps = max_steps self.elapsed_time = "" self.remaining_time = "" - def timing(self): + def _timing(self, cur_steps: int) -> None: cur_time = time.time() elapsed_time = cur_time - self.start_time - avg_time_per_step = elapsed_time / self.cur_steps if self.cur_steps != 0 else 0 - remaining_time = (self.max_steps - self.cur_steps) * avg_time_per_step + avg_time_per_step = elapsed_time / cur_steps if cur_steps != 0 else 0 + remaining_time = (self.max_steps - cur_steps) * avg_time_per_step + self.cur_steps = cur_steps self.elapsed_time = str(timedelta(seconds=int(elapsed_time))) self.remaining_time = str(timedelta(seconds=int(remaining_time))) @@ -54,36 +70,27 @@ class LogCallback(TrainerCallback): r""" Event called at the beginning of training. """ - if state.is_local_process_zero: - self.in_training = True - self.start_time = time.time() - self.max_steps = state.max_steps + if args.should_log: + self.do_train = True + self._reset(max_steps=state.max_steps) - if args.save_on_each_node: - if not state.is_local_process_zero: - return - else: - if not state.is_world_process_zero: - return + if args.should_save: + os.makedirs(args.output_dir, exist_ok=True) + self.thread_pool = ThreadPoolExecutor(max_workers=1) - if os.path.exists(os.path.join(args.output_dir, LOG_FILE_NAME)) and args.overwrite_output_dir: - logger.warning("Previous log file in this folder will be deleted.") - os.remove(os.path.join(args.output_dir, LOG_FILE_NAME)) - - def on_train_end(self, args: "TrainingArguments", state: "TrainerState", control: "TrainerControl", **kwargs): - r""" - Event called at the end of training. - """ - if state.is_local_process_zero: - self.in_training = False - self.cur_steps = 0 - self.max_steps = 0 + if ( + args.should_save + and os.path.exists(os.path.join(args.output_dir, TRAINER_LOG)) + and args.overwrite_output_dir + ): + logger.warning("Previous trainer log in this folder will be deleted.") + os.remove(os.path.join(args.output_dir, TRAINER_LOG)) def on_substep_end(self, args: "TrainingArguments", state: "TrainerState", control: "TrainerControl", **kwargs): r""" Event called at the end of an substep during gradient accumulation. """ - if state.is_local_process_zero and self.runner is not None and self.runner.aborted: + if self.aborted: control.should_epoch_stop = True control.should_training_stop = True @@ -91,42 +98,41 @@ class LogCallback(TrainerCallback): r""" Event called at the end of a training step. """ - if state.is_local_process_zero: - self.cur_steps = state.global_step - self.timing() - if self.runner is not None and self.runner.aborted: - control.should_epoch_stop = True - control.should_training_stop = True + if args.should_log: + self._timing(cur_steps=state.global_step) - def on_evaluate(self, args: "TrainingArguments", state: "TrainerState", control: "TrainerControl", **kwargs): + if self.aborted: + control.should_epoch_stop = True + control.should_training_stop = True + + def on_train_end(self, args: "TrainingArguments", state: "TrainerState", control: "TrainerControl", **kwargs): r""" - Event called after an evaluation phase. + Event called at the end of training. """ - if state.is_local_process_zero and not self.in_training: - self.cur_steps = 0 - self.max_steps = 0 + self.thread_pool.shutdown(wait=True) + self.thread_pool = None - def on_predict( - self, args: "TrainingArguments", state: "TrainerState", control: "TrainerControl", *other, **kwargs + def on_prediction_step( + self, args: "TrainingArguments", state: "TrainerState", control: "TrainerControl", **kwargs ): r""" - Event called after a successful prediction. + Event called after a prediction step. """ - if state.is_local_process_zero and not self.in_training: - self.cur_steps = 0 - self.max_steps = 0 + eval_dataloader = kwargs.pop("eval_dataloader", None) + if args.should_log and has_length(eval_dataloader) and not self.do_train: + if self.max_steps == 0: + self.max_steps = len(eval_dataloader) + + self._timing(cur_steps=self.cur_steps + 1) + + def _write_log(self, output_dir: str, logs: Dict[str, Any]): + with open(os.path.join(output_dir, TRAINER_LOG), "a", encoding="utf-8") as f: + f.write(json.dumps(logs) + "\n") def on_log(self, args: "TrainingArguments", state: "TrainerState", control: "TrainerControl", **kwargs) -> None: r""" - Event called after logging the last logs. + Event called after logging the last logs, `args.should_log` has been applied. """ - if args.save_on_each_node: - if not state.is_local_process_zero: - return - else: - if not state.is_world_process_zero: - return - logs = dict( current_steps=self.cur_steps, total_steps=self.max_steps, @@ -141,26 +147,13 @@ class LogCallback(TrainerCallback): elapsed_time=self.elapsed_time, remaining_time=self.remaining_time, ) - if self.runner is not None: + logs = {k: v for k, v in logs.items() if v is not None} + if self.webui_mode and "loss" in logs and "learning_rate" in logs and "epoch" in logs: logger.info( "{{'loss': {:.4f}, 'learning_rate': {:2.4e}, 'epoch': {:.2f}}}".format( - logs["loss"] or 0, logs["learning_rate"] or 0, logs["epoch"] or 0 + logs["loss"], logs["learning_rate"], logs["epoch"] ) ) - os.makedirs(args.output_dir, exist_ok=True) - with open(os.path.join(args.output_dir, "trainer_log.jsonl"), "a", encoding="utf-8") as f: - f.write(json.dumps(logs) + "\n") - - def on_prediction_step( - self, args: "TrainingArguments", state: "TrainerState", control: "TrainerControl", **kwargs - ): - r""" - Event called after a prediction step. - """ - eval_dataloader = kwargs.pop("eval_dataloader", None) - if state.is_local_process_zero and has_length(eval_dataloader) and not self.in_training: - if self.max_steps == 0: - self.max_steps = len(eval_dataloader) - self.cur_steps += 1 - self.timing() + if args.should_save and self.thread_pool is not None: + self.thread_pool.submit(self._write_log, args.output_dir, logs) diff --git a/src/llmtuner/extras/constants.py b/src/llmtuner/extras/constants.py index 0329b374..bf542e69 100644 --- a/src/llmtuner/extras/constants.py +++ b/src/llmtuner/extras/constants.py @@ -24,8 +24,6 @@ IGNORE_INDEX = -100 LAYERNORM_NAMES = {"norm", "ln"} -LOG_FILE_NAME = "trainer_log.jsonl" - METHODS = ["full", "freeze", "lora"] MLLM_LIST = ["LLaVA1.5"] @@ -34,10 +32,16 @@ MOD_SUPPORTED_MODELS = ["bloom", "falcon", "gemma", "llama", "mistral", "mixtral PEFT_METHODS = ["lora"] +RUNNING_LOG = "running_log.txt" + SUBJECTS = ["Average", "STEM", "Social Sciences", "Humanities", "Other"] SUPPORTED_MODELS = OrderedDict() +TRAINER_CONFIG = "trainer_config.yaml" + +TRAINER_LOG = "trainer_log.jsonl" + TRAINING_STAGES = { "Supervised Fine-Tuning": "sft", "Reward Modeling": "rm", diff --git a/src/llmtuner/extras/logging.py b/src/llmtuner/extras/logging.py index bb270776..430b8a48 100644 --- a/src/llmtuner/extras/logging.py +++ b/src/llmtuner/extras/logging.py @@ -1,5 +1,9 @@ import logging +import os import sys +from concurrent.futures import ThreadPoolExecutor + +from .constants import RUNNING_LOG class LoggerHandler(logging.Handler): @@ -7,19 +11,35 @@ class LoggerHandler(logging.Handler): Logger handler used in Web UI. """ - def __init__(self): + def __init__(self, output_dir: str) -> None: super().__init__() - self.log = "" + formatter = logging.Formatter( + fmt="%(asctime)s - %(levelname)s - %(name)s - %(message)s", datefmt="%m/%d/%Y %H:%M:%S" + ) + self.setLevel(logging.INFO) + self.setFormatter(formatter) - def reset(self): - self.log = "" + os.makedirs(output_dir, exist_ok=True) + self.running_log = os.path.join(output_dir, RUNNING_LOG) + if os.path.exists(self.running_log): + os.remove(self.running_log) - def emit(self, record): + self.thread_pool = ThreadPoolExecutor(max_workers=1) + + def _write_log(self, log_entry: str) -> None: + with open(self.running_log, "a", encoding="utf-8") as f: + f.write(log_entry + "\n\n") + + def emit(self, record) -> None: if record.name == "httpx": return + log_entry = self.format(record) - self.log += log_entry - self.log += "\n\n" + self.thread_pool.submit(self._write_log, log_entry) + + def close(self) -> None: + self.thread_pool.shutdown(wait=True) + return super().close() def get_logger(name: str) -> logging.Logger: diff --git a/src/llmtuner/extras/ploting.py b/src/llmtuner/extras/ploting.py index fd3cb8a3..e53f1f89 100644 --- a/src/llmtuner/extras/ploting.py +++ b/src/llmtuner/extras/ploting.py @@ -1,7 +1,7 @@ import json import math import os -from typing import List +from typing import Any, Dict, List from transformers.trainer import TRAINER_STATE_NAME @@ -10,6 +10,7 @@ from .packages import is_matplotlib_available if is_matplotlib_available(): + import matplotlib.figure import matplotlib.pyplot as plt @@ -21,7 +22,7 @@ def smooth(scalars: List[float]) -> List[float]: EMA implementation according to TensorBoard. """ last = scalars[0] - smoothed = list() + smoothed = [] weight = 1.8 * (1 / (1 + math.exp(-0.05 * len(scalars))) - 0.5) # a sigmoid function for next_val in scalars: smoothed_val = last * weight + (1 - weight) * next_val @@ -30,7 +31,27 @@ def smooth(scalars: List[float]) -> List[float]: return smoothed +def gen_loss_plot(trainer_log: List[Dict[str, Any]]) -> "matplotlib.figure.Figure": + plt.close("all") + plt.switch_backend("agg") + fig = plt.figure() + ax = fig.add_subplot(111) + steps, losses = [], [] + for log in trainer_log: + if log.get("loss", None): + steps.append(log["current_steps"]) + losses.append(log["loss"]) + + ax.plot(steps, losses, color="#1f77b4", alpha=0.4, label="original") + ax.plot(steps, smooth(losses), color="#1f77b4", label="smoothed") + ax.legend() + ax.set_xlabel("step") + ax.set_ylabel("loss") + return fig + + def plot_loss(save_dictionary: os.PathLike, keys: List[str] = ["loss"]) -> None: + plt.switch_backend("agg") with open(os.path.join(save_dictionary, TRAINER_STATE_NAME), "r", encoding="utf-8") as f: data = json.load(f) diff --git a/src/llmtuner/hparams/parser.py b/src/llmtuner/hparams/parser.py index 977d7cf4..7fdd3234 100644 --- a/src/llmtuner/hparams/parser.py +++ b/src/llmtuner/hparams/parser.py @@ -10,6 +10,7 @@ from transformers.trainer_utils import get_last_checkpoint from transformers.utils import is_torch_bf16_gpu_available from transformers.utils.versions import require_version +from ..extras.constants import TRAINER_CONFIG from ..extras.logging import get_logger from ..extras.misc import check_dependencies, get_current_device from .data_args import DataArguments @@ -251,7 +252,8 @@ def get_train_args(args: Optional[Dict[str, Any]] = None) -> _TRAIN_CLS: and can_resume_from_checkpoint ): last_checkpoint = get_last_checkpoint(training_args.output_dir) - if last_checkpoint is None and len(os.listdir(training_args.output_dir)) > 0: + files = os.listdir(training_args.output_dir) + if last_checkpoint is None and len(files) > 0 and (len(files) != 1 or files[0] != TRAINER_CONFIG): raise ValueError("Output directory already exists and is not empty. Please set `overwrite_output_dir`.") if last_checkpoint is not None: diff --git a/src/llmtuner/train/__init__.py b/src/llmtuner/train/__init__.py index 6c22bc15..e69de29b 100644 --- a/src/llmtuner/train/__init__.py +++ b/src/llmtuner/train/__init__.py @@ -1,4 +0,0 @@ -from .tuner import export_model, run_exp - - -__all__ = ["export_model", "run_exp"] diff --git a/src/llmtuner/train/tuner.py b/src/llmtuner/train/tuner.py index a2eb121f..6822ffb5 100644 --- a/src/llmtuner/train/tuner.py +++ b/src/llmtuner/train/tuner.py @@ -23,9 +23,9 @@ if TYPE_CHECKING: logger = get_logger(__name__) -def run_exp(args: Optional[Dict[str, Any]] = None, callbacks: Optional[List["TrainerCallback"]] = None): +def run_exp(args: Optional[Dict[str, Any]] = None, callbacks: List["TrainerCallback"] = []): model_args, data_args, training_args, finetuning_args, generating_args = get_train_args(args) - callbacks = [LogCallback()] if callbacks is None else callbacks + callbacks.append(LogCallback(training_args.output_dir)) if finetuning_args.stage == "pt": run_pt(model_args, data_args, training_args, finetuning_args, callbacks) @@ -88,7 +88,3 @@ def export_model(args: Optional[Dict[str, Any]] = None): tokenizer.push_to_hub(model_args.export_hub_model_id, token=model_args.hf_hub_token) except Exception: logger.warning("Cannot save tokenizer, please copy the files manually.") - - -if __name__ == "__main__": - run_exp() diff --git a/src/llmtuner/webui/__init__.py b/src/llmtuner/webui/__init__.py index 3e82dd69..e69de29b 100644 --- a/src/llmtuner/webui/__init__.py +++ b/src/llmtuner/webui/__init__.py @@ -1,4 +0,0 @@ -from .interface import create_ui, create_web_demo - - -__all__ = ["create_ui", "create_web_demo"] diff --git a/src/llmtuner/webui/common.py b/src/llmtuner/webui/common.py index 9af4c439..a33e3db7 100644 --- a/src/llmtuner/webui/common.py +++ b/src/llmtuner/webui/common.py @@ -4,6 +4,7 @@ from collections import defaultdict from typing import Any, Dict, Optional from peft.utils import SAFETENSORS_WEIGHTS_NAME, WEIGHTS_NAME +from yaml import safe_dump, safe_load from ..extras.constants import ( DATA_CONFIG, @@ -29,7 +30,7 @@ DEFAULT_CACHE_DIR = "cache" DEFAULT_CONFIG_DIR = "config" DEFAULT_DATA_DIR = "data" DEFAULT_SAVE_DIR = "saves" -USER_CONFIG = "user.config" +USER_CONFIG = "user_config.yaml" def get_save_dir(*args) -> os.PathLike: @@ -47,7 +48,7 @@ def get_save_path(config_path: str) -> os.PathLike: def load_config() -> Dict[str, Any]: try: with open(get_config_path(), "r", encoding="utf-8") as f: - return json.load(f) + return safe_load(f) except Exception: return {"lang": None, "last_model": None, "path_dict": {}, "cache_dir": None} @@ -60,13 +61,13 @@ def save_config(lang: str, model_name: Optional[str] = None, model_path: Optiona user_config["last_model"] = model_name user_config["path_dict"][model_name] = model_path with open(get_config_path(), "w", encoding="utf-8") as f: - json.dump(user_config, f, indent=2, ensure_ascii=False) + safe_dump(user_config, f) def load_args(config_path: str) -> Optional[Dict[str, Any]]: try: with open(get_save_path(config_path), "r", encoding="utf-8") as f: - return json.load(f) + return safe_load(f) except Exception: return None @@ -74,7 +75,7 @@ def load_args(config_path: str) -> Optional[Dict[str, Any]]: def save_args(config_path: str, config_dict: Dict[str, Any]) -> str: os.makedirs(DEFAULT_CONFIG_DIR, exist_ok=True) with open(get_save_path(config_path), "w", encoding="utf-8") as f: - json.dump(config_dict, f, indent=2, ensure_ascii=False) + safe_dump(config_dict, f) return str(get_save_path(config_path)) diff --git a/src/llmtuner/webui/components/export.py b/src/llmtuner/webui/components/export.py index 4c224736..64273882 100644 --- a/src/llmtuner/webui/components/export.py +++ b/src/llmtuner/webui/components/export.py @@ -2,7 +2,7 @@ from typing import TYPE_CHECKING, Dict, Generator, List from ...extras.misc import torch_gc from ...extras.packages import is_gradio_available -from ...train import export_model +from ...train.tuner import export_model from ..common import get_save_dir from ..locales import ALERTS diff --git a/src/llmtuner/webui/components/train.py b/src/llmtuner/webui/components/train.py index c9671289..c709b916 100644 --- a/src/llmtuner/webui/components/train.py +++ b/src/llmtuner/webui/components/train.py @@ -245,7 +245,7 @@ def create_train_tab(engine: "Engine") -> Dict[str, "Component"]: with gr.Row(): resume_btn = gr.Checkbox(visible=False, interactive=False) - process_bar = gr.Slider(visible=False, interactive=False) + progress_bar = gr.Slider(visible=False, interactive=False) with gr.Row(): output_box = gr.Markdown() @@ -263,14 +263,14 @@ def create_train_tab(engine: "Engine") -> Dict[str, "Component"]: output_dir=output_dir, config_path=config_path, resume_btn=resume_btn, - process_bar=process_bar, + progress_bar=progress_bar, output_box=output_box, loss_viewer=loss_viewer, ) ) input_elems.update({output_dir, config_path}) - output_elems = [output_box, process_bar, loss_viewer] + output_elems = [output_box, progress_bar, loss_viewer] cmd_preview_btn.click(engine.runner.preview_train, input_elems, output_elems, concurrency_limit=None) arg_save_btn.click(engine.runner.save_args, input_elems, output_elems, concurrency_limit=None) diff --git a/src/llmtuner/webui/engine.py b/src/llmtuner/webui/engine.py index cebac3b9..964d65a2 100644 --- a/src/llmtuner/webui/engine.py +++ b/src/llmtuner/webui/engine.py @@ -41,7 +41,7 @@ class Engine: init_dict["train.dataset"] = {"choices": list_dataset().choices} init_dict["eval.dataset"] = {"choices": list_dataset().choices} init_dict["train.output_dir"] = {"value": "train_{}".format(get_time())} - init_dict["train.config_path"] = {"value": "{}.json".format(get_time())} + init_dict["train.config_path"] = {"value": "{}.yaml".format(get_time())} init_dict["eval.output_dir"] = {"value": "eval_{}".format(get_time())} init_dict["infer.image_box"] = {"visible": False} @@ -51,7 +51,7 @@ class Engine: yield self._update_component(init_dict) - if self.runner.alive and not self.demo_mode and not self.pure_chat: + if self.runner.running and not self.demo_mode and not self.pure_chat: yield {elem: elem.__class__(value=value) for elem, value in self.runner.running_data.items()} if self.runner.do_train: yield self._update_component({"train.resume_btn": {"value": True}}) diff --git a/src/llmtuner/webui/interface.py b/src/llmtuner/webui/interface.py index abca16c5..feb2a20a 100644 --- a/src/llmtuner/webui/interface.py +++ b/src/llmtuner/webui/interface.py @@ -68,5 +68,9 @@ def create_web_demo() -> gr.Blocks: return demo -if __name__ == "__main__": +def run_web_ui(): create_ui().queue().launch(server_name="0.0.0.0", server_port=None, share=False, inbrowser=True) + + +def run_web_demo(): + create_web_demo().queue().launch(server_name="0.0.0.0", server_port=None, share=False, inbrowser=True) diff --git a/src/llmtuner/webui/runner.py b/src/llmtuner/webui/runner.py index d53a4dfe..b14271b7 100644 --- a/src/llmtuner/webui/runner.py +++ b/src/llmtuner/webui/runner.py @@ -1,22 +1,19 @@ -import logging import os -import time -from threading import Thread -from typing import TYPE_CHECKING, Any, Dict, Generator +import signal +from copy import deepcopy +from subprocess import Popen, TimeoutExpired +from typing import TYPE_CHECKING, Any, Dict, Generator, Optional -import transformers +import psutil from transformers.trainer import TRAINING_ARGS_NAME from transformers.utils import is_torch_cuda_available -from ..extras.callbacks import LogCallback from ..extras.constants import TRAINING_STAGES -from ..extras.logging import LoggerHandler from ..extras.misc import get_device_count, torch_gc from ..extras.packages import is_gradio_available -from ..train import run_exp from .common import get_module, get_save_dir, load_args, load_config, save_args from .locales import ALERTS -from .utils import gen_cmd, gen_plot, get_eval_results, update_process_bar +from .utils import gen_cmd, get_eval_results, get_trainer_info, save_cmd if is_gradio_available(): @@ -34,24 +31,18 @@ class Runner: self.manager = manager self.demo_mode = demo_mode """ Resume """ - self.thread: "Thread" = None + self.trainer: Optional["Popen"] = None self.do_train = True self.running_data: Dict["Component", Any] = None """ State """ self.aborted = False self.running = False - """ Handler """ - self.logger_handler = LoggerHandler() - self.logger_handler.setLevel(logging.INFO) - logging.root.addHandler(self.logger_handler) - transformers.logging.add_handler(self.logger_handler) - - @property - def alive(self) -> bool: - return self.thread is not None def set_abort(self) -> None: self.aborted = True + if self.trainer is not None: + for children in psutil.Process(self.trainer.pid).children(): # abort the child process + os.kill(children.pid, signal.SIGABRT) def _initialize(self, data: Dict["Component", Any], do_train: bool, from_preview: bool) -> str: get = lambda elem_id: data[self.manager.get_elem_by_id(elem_id)] @@ -85,13 +76,11 @@ class Runner: if not from_preview and not is_torch_cuda_available(): gr.Warning(ALERTS["warn_no_cuda"][lang]) - self.logger_handler.reset() - self.trainer_callback = LogCallback(self) return "" def _finalize(self, lang: str, finish_info: str) -> str: finish_info = ALERTS["info_aborted"][lang] if self.aborted else finish_info - self.thread = None + self.trainer = None self.aborted = False self.running = False self.running_data = None @@ -270,11 +259,12 @@ class Runner: gr.Warning(error) yield {output_box: error} else: - args = self._parse_train_args(data) if do_train else self._parse_eval_args(data) - run_kwargs = dict(args=args, callbacks=[self.trainer_callback]) self.do_train, self.running_data = do_train, data - self.thread = Thread(target=run_exp, kwargs=run_kwargs) - self.thread.start() + args = self._parse_train_args(data) if do_train else self._parse_eval_args(data) + env = deepcopy(os.environ) + env["CUDA_VISIBLE_DEVICES"] = os.environ.get("CUDA_VISIBLE_DEVICES", "0") + env["LLAMABOARD_ENABLED"] = "1" + self.trainer = Popen("llamafactory-cli train {}".format(save_cmd(args)), env=env, shell=True) yield from self.monitor() def preview_train(self, data): @@ -291,9 +281,6 @@ class Runner: def monitor(self): get = lambda elem_id: self.running_data[self.manager.get_elem_by_id(elem_id)] - self.aborted = False - self.running = True - lang = get("top.lang") model_name = get("top.model_name") finetuning_type = get("top.finetuning_type") @@ -301,28 +288,31 @@ class Runner: output_path = get_save_dir(model_name, finetuning_type, output_dir) output_box = self.manager.get_elem_by_id("{}.output_box".format("train" if self.do_train else "eval")) - process_bar = self.manager.get_elem_by_id("{}.process_bar".format("train" if self.do_train else "eval")) + progress_bar = self.manager.get_elem_by_id("{}.progress_bar".format("train" if self.do_train else "eval")) loss_viewer = self.manager.get_elem_by_id("train.loss_viewer") if self.do_train else None - while self.thread is not None and self.thread.is_alive(): + while self.trainer is not None: if self.aborted: yield { output_box: ALERTS["info_aborting"][lang], - process_bar: gr.Slider(visible=False), + progress_bar: gr.Slider(visible=False), } else: + running_log, running_progress, running_loss = get_trainer_info(output_path) return_dict = { - output_box: self.logger_handler.log, - process_bar: update_process_bar(self.trainer_callback), + output_box: running_log, + progress_bar: running_progress, } - if self.do_train: - plot = gen_plot(output_path) - if plot is not None: - return_dict[loss_viewer] = plot + if self.do_train and running_loss is not None: + return_dict[loss_viewer] = running_loss yield return_dict - time.sleep(2) + try: + self.trainer.wait(2) + self.trainer = None + except TimeoutExpired: + continue if self.do_train: if os.path.exists(os.path.join(output_path, TRAINING_ARGS_NAME)): @@ -337,16 +327,11 @@ class Runner: return_dict = { output_box: self._finalize(lang, finish_info), - process_bar: gr.Slider(visible=False), + progress_bar: gr.Slider(visible=False), } - if self.do_train: - plot = gen_plot(output_path) - if plot is not None: - return_dict[loss_viewer] = plot - yield return_dict - def save_args(self, data): + def save_args(self, data: dict): output_box = self.manager.get_elem_by_id("train.output_box") error = self._initialize(data, do_train=True, from_preview=True) if error: diff --git a/src/llmtuner/webui/utils.py b/src/llmtuner/webui/utils.py index 74f74e6a..2ad1e62c 100644 --- a/src/llmtuner/webui/utils.py +++ b/src/llmtuner/webui/utils.py @@ -1,10 +1,13 @@ import json import os from datetime import datetime -from typing import TYPE_CHECKING, Any, Dict, Optional +from typing import Any, Dict, List, Optional, Tuple +from yaml import safe_dump + +from ..extras.constants import RUNNING_LOG, TRAINER_CONFIG, TRAINER_LOG from ..extras.packages import is_gradio_available, is_matplotlib_available -from ..extras.ploting import smooth +from ..extras.ploting import gen_loss_plot from .locales import ALERTS @@ -12,30 +15,6 @@ if is_gradio_available(): import gradio as gr -if is_matplotlib_available(): - import matplotlib.figure - import matplotlib.pyplot as plt - - -if TYPE_CHECKING: - from ..extras.callbacks import LogCallback - - -def update_process_bar(callback: "LogCallback") -> "gr.Slider": - if not callback.max_steps: - return gr.Slider(visible=False) - - percentage = round(100 * callback.cur_steps / callback.max_steps, 0) if callback.max_steps != 0 else 100.0 - label = "Running {:d}/{:d}: {} < {}".format( - callback.cur_steps, callback.max_steps, callback.elapsed_time, callback.remaining_time - ) - return gr.Slider(label=label, value=percentage, visible=True) - - -def get_time() -> str: - return datetime.now().strftime(r"%Y-%m-%d-%H-%M-%S") - - def can_quantize(finetuning_type: str) -> "gr.Dropdown": if finetuning_type != "lora": return gr.Dropdown(value="none", interactive=False) @@ -57,14 +36,19 @@ def check_json_schema(text: str, lang: str) -> None: gr.Warning(ALERTS["err_json_schema"][lang]) +def clean_cmd(args: Dict[str, Any]) -> Dict[str, Any]: + no_skip_keys = ["packing"] + return {k: v for k, v in args.items() if (k in no_skip_keys) or (v is not None and v is not False and v != "")} + + def gen_cmd(args: Dict[str, Any]) -> str: args.pop("disable_tqdm", None) args["plot_loss"] = args.get("do_train", None) current_devices = os.environ.get("CUDA_VISIBLE_DEVICES", "0") cmd_lines = ["CUDA_VISIBLE_DEVICES={} python src/train_bash.py ".format(current_devices)] - for k, v in args.items(): - if v is not None and v is not False and v != "": - cmd_lines.append(" --{} {} ".format(k, str(v))) + for k, v in clean_cmd(args).items(): + cmd_lines.append(" --{} {} ".format(k, str(v))) + cmd_text = "\\\n".join(cmd_lines) cmd_text = "```bash\n{}\n```".format(cmd_text) return cmd_text @@ -76,29 +60,49 @@ def get_eval_results(path: os.PathLike) -> str: return "```json\n{}\n```\n".format(result) -def gen_plot(output_path: str) -> Optional["matplotlib.figure.Figure"]: - log_file = os.path.join(output_path, "trainer_log.jsonl") - if not os.path.isfile(log_file) or not is_matplotlib_available(): - return +def get_time() -> str: + return datetime.now().strftime(r"%Y-%m-%d-%H-%M-%S") - plt.close("all") - plt.switch_backend("agg") - fig = plt.figure() - ax = fig.add_subplot(111) - steps, losses = [], [] - with open(log_file, "r", encoding="utf-8") as f: - for line in f: - log_info: Dict[str, Any] = json.loads(line) - if log_info.get("loss", None): - steps.append(log_info["current_steps"]) - losses.append(log_info["loss"]) - if len(losses) == 0: - return +def get_trainer_info(output_path: os.PathLike) -> Tuple[str, "gr.Slider", Optional["gr.Plot"]]: + running_log = "" + running_progress = gr.Slider(visible=False) + running_loss = None - ax.plot(steps, losses, color="#1f77b4", alpha=0.4, label="original") - ax.plot(steps, smooth(losses), color="#1f77b4", label="smoothed") - ax.legend() - ax.set_xlabel("step") - ax.set_ylabel("loss") - return fig + running_log_path = os.path.join(output_path, RUNNING_LOG) + if os.path.isfile(running_log_path): + with open(running_log_path, "r", encoding="utf-8") as f: + running_log = f.read() + + trainer_log_path = os.path.join(output_path, TRAINER_LOG) + if os.path.isfile(trainer_log_path): + trainer_log: List[Dict[str, Any]] = [] + with open(trainer_log_path, "r", encoding="utf-8") as f: + for line in f: + trainer_log.append(json.loads(line)) + + if len(trainer_log) != 0: + latest_log = trainer_log[-1] + percentage = latest_log["percentage"] + label = "Running {:d}/{:d}: {} < {}".format( + latest_log["current_steps"], + latest_log["total_steps"], + latest_log["elapsed_time"], + latest_log["remaining_time"], + ) + running_progress = gr.Slider(label=label, value=percentage, visible=True) + + if is_matplotlib_available(): + running_loss = gr.Plot(gen_loss_plot(trainer_log)) + + return running_log, running_progress, running_loss + + +def save_cmd(args: Dict[str, Any]) -> str: + output_dir = args["output_dir"] + os.makedirs(output_dir, exist_ok=True) + + with open(os.path.join(output_dir, TRAINER_CONFIG), "w", encoding="utf-8") as f: + safe_dump(clean_cmd(args), f) + + return os.path.join(output_dir, TRAINER_CONFIG) diff --git a/src/train_bash.py b/src/train.py similarity index 67% rename from src/train_bash.py rename to src/train.py index 9ddd0586..6a3212cb 100644 --- a/src/train_bash.py +++ b/src/train.py @@ -1,4 +1,4 @@ -from llmtuner import run_exp +from llmtuner.train.tuner import run_exp def main(): @@ -7,7 +7,7 @@ def main(): def _mp_fn(index): # For xla_spawn (TPUs) - main() + run_exp() if __name__ == "__main__": diff --git a/src/train_web.py b/src/train_web.py deleted file mode 100644 index 8327f4dd..00000000 --- a/src/train_web.py +++ /dev/null @@ -1,9 +0,0 @@ -from llmtuner import create_ui - - -def main(): - create_ui().queue().launch(server_name="0.0.0.0", server_port=None, share=False, inbrowser=True) - - -if __name__ == "__main__": - main() diff --git a/src/web_demo.py b/src/web_demo.py deleted file mode 100644 index 3b57ee73..00000000 --- a/src/web_demo.py +++ /dev/null @@ -1,9 +0,0 @@ -from llmtuner import create_web_demo - - -def main(): - create_web_demo().queue().launch(server_name="0.0.0.0", server_port=None, share=False, inbrowser=True) - - -if __name__ == "__main__": - main() From 572d25734aedf775a65545ecf8f4acf6c6e6d39b Mon Sep 17 00:00:00 2001 From: hiyouga Date: Fri, 3 May 2024 03:54:46 +0800 Subject: [PATCH 11/33] fix colab gradio Former-commit-id: 530f6b49bbf406809facdf5fc3b0d2585e2b8797 --- src/llmtuner/webui/interface.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/llmtuner/webui/interface.py b/src/llmtuner/webui/interface.py index feb2a20a..5f17d76d 100644 --- a/src/llmtuner/webui/interface.py +++ b/src/llmtuner/webui/interface.py @@ -69,8 +69,8 @@ def create_web_demo() -> gr.Blocks: def run_web_ui(): - create_ui().queue().launch(server_name="0.0.0.0", server_port=None, share=False, inbrowser=True) + create_ui().queue().launch(server_name="0.0.0.0") def run_web_demo(): - create_web_demo().queue().launch(server_name="0.0.0.0", server_port=None, share=False, inbrowser=True) + create_web_demo().queue().launch(server_name="0.0.0.0") From 59965c2dcabfe01265ac62ad260e86a01bf20df6 Mon Sep 17 00:00:00 2001 From: hiyouga Date: Fri, 3 May 2024 04:24:50 +0800 Subject: [PATCH 12/33] fix gen_args Former-commit-id: 17d2e5147ecc4015757d5d988b8872a0d3158a04 --- src/llmtuner/webui/utils.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/llmtuner/webui/utils.py b/src/llmtuner/webui/utils.py index 2ad1e62c..74683cb9 100644 --- a/src/llmtuner/webui/utils.py +++ b/src/llmtuner/webui/utils.py @@ -45,7 +45,7 @@ def gen_cmd(args: Dict[str, Any]) -> str: args.pop("disable_tqdm", None) args["plot_loss"] = args.get("do_train", None) current_devices = os.environ.get("CUDA_VISIBLE_DEVICES", "0") - cmd_lines = ["CUDA_VISIBLE_DEVICES={} python src/train_bash.py ".format(current_devices)] + cmd_lines = ["CUDA_VISIBLE_DEVICES={} llamafactory-cli train ".format(current_devices)] for k, v in clean_cmd(args).items(): cmd_lines.append(" --{} {} ".format(k, str(v))) From 65abcf1a943122fb8c6038242a778088933f0bde Mon Sep 17 00:00:00 2001 From: hiyouga Date: Fri, 3 May 2024 04:42:50 +0800 Subject: [PATCH 13/33] enable tqdm in webui Former-commit-id: 5e6f808e3cd4f9a143562f2facb4f369d985eb26 --- src/llmtuner/webui/runner.py | 2 -- src/llmtuner/webui/utils.py | 1 - 2 files changed, 3 deletions(-) diff --git a/src/llmtuner/webui/runner.py b/src/llmtuner/webui/runner.py index b14271b7..4ea08348 100644 --- a/src/llmtuner/webui/runner.py +++ b/src/llmtuner/webui/runner.py @@ -142,7 +142,6 @@ class Runner: bf16=(get("train.compute_type") == "bf16"), pure_bf16=(get("train.compute_type") == "pure_bf16"), ) - args["disable_tqdm"] = True if args["finetuning_type"] == "freeze": args["num_layer_trainable"] = get("train.num_layer_trainable") @@ -233,7 +232,6 @@ class Runner: temperature=get("eval.temperature"), output_dir=get_save_dir(get("top.model_name"), get("top.finetuning_type"), get("eval.output_dir")), ) - args["disable_tqdm"] = True if get("eval.predict"): args["do_predict"] = True diff --git a/src/llmtuner/webui/utils.py b/src/llmtuner/webui/utils.py index 74683cb9..c8729d36 100644 --- a/src/llmtuner/webui/utils.py +++ b/src/llmtuner/webui/utils.py @@ -42,7 +42,6 @@ def clean_cmd(args: Dict[str, Any]) -> Dict[str, Any]: def gen_cmd(args: Dict[str, Any]) -> str: - args.pop("disable_tqdm", None) args["plot_loss"] = args.get("do_train", None) current_devices = os.environ.get("CUDA_VISIBLE_DEVICES", "0") cmd_lines = ["CUDA_VISIBLE_DEVICES={} llamafactory-cli train ".format(current_devices)] From a2cb40735b7bab02c2432e39572638f2a71ed020 Mon Sep 17 00:00:00 2001 From: hiyouga Date: Fri, 3 May 2024 21:24:27 +0800 Subject: [PATCH 14/33] fix callback log multigpu #3559 Former-commit-id: 9585838ebe1f7ce508ec490f91d30920f134be3f --- src/llmtuner/extras/callbacks.py | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/src/llmtuner/extras/callbacks.py b/src/llmtuner/extras/callbacks.py index fbe6f373..76f07a42 100644 --- a/src/llmtuner/extras/callbacks.py +++ b/src/llmtuner/extras/callbacks.py @@ -70,11 +70,9 @@ class LogCallback(TrainerCallback): r""" Event called at the beginning of training. """ - if args.should_log: + if args.should_save: self.do_train = True self._reset(max_steps=state.max_steps) - - if args.should_save: os.makedirs(args.output_dir, exist_ok=True) self.thread_pool = ThreadPoolExecutor(max_workers=1) @@ -98,7 +96,7 @@ class LogCallback(TrainerCallback): r""" Event called at the end of a training step. """ - if args.should_log: + if args.should_save: self._timing(cur_steps=state.global_step) if self.aborted: @@ -119,7 +117,7 @@ class LogCallback(TrainerCallback): Event called after a prediction step. """ eval_dataloader = kwargs.pop("eval_dataloader", None) - if args.should_log and has_length(eval_dataloader) and not self.do_train: + if args.should_save and has_length(eval_dataloader) and not self.do_train: if self.max_steps == 0: self.max_steps = len(eval_dataloader) @@ -131,8 +129,11 @@ class LogCallback(TrainerCallback): def on_log(self, args: "TrainingArguments", state: "TrainerState", control: "TrainerControl", **kwargs) -> None: r""" - Event called after logging the last logs, `args.should_log` has been applied. + Event called after logging the last logs. """ + if not args.should_save: + return + logs = dict( current_steps=self.cur_steps, total_steps=self.max_steps, @@ -148,12 +149,12 @@ class LogCallback(TrainerCallback): remaining_time=self.remaining_time, ) logs = {k: v for k, v in logs.items() if v is not None} - if self.webui_mode and "loss" in logs and "learning_rate" in logs and "epoch" in logs: + if self.webui_mode and all(key in logs for key in ["loss", "learning_rate", "epoch"]): logger.info( "{{'loss': {:.4f}, 'learning_rate': {:2.4e}, 'epoch': {:.2f}}}".format( logs["loss"], logs["learning_rate"], logs["epoch"] ) ) - if args.should_save and self.thread_pool is not None: + if self.thread_pool is not None: self.thread_pool.submit(self._write_log, args.output_dir, logs) From 226587fc4a839277ce20ea7c6bc2f70c3a7f0fb1 Mon Sep 17 00:00:00 2001 From: hiyouga Date: Fri, 3 May 2024 23:06:52 +0800 Subject: [PATCH 15/33] fix slow op in dpo/orpo trainer Former-commit-id: 3010154adb43deb37fbb3a4648dccd27e848e9c3 --- src/llmtuner/train/dpo/trainer.py | 16 ++++++++-------- src/llmtuner/train/orpo/trainer.py | 20 ++++++++++---------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/llmtuner/train/dpo/trainer.py b/src/llmtuner/train/dpo/trainer.py index 35dcd8db..b144d561 100644 --- a/src/llmtuner/train/dpo/trainer.py +++ b/src/llmtuner/train/dpo/trainer.py @@ -165,13 +165,13 @@ class CustomDPOTrainer(DPOTrainer): reward_accuracies = (chosen_rewards > rejected_rewards).float() prefix = "eval_" if train_eval == "eval" else "" - metrics["{}rewards/chosen".format(prefix)] = chosen_rewards.cpu().mean() - metrics["{}rewards/rejected".format(prefix)] = rejected_rewards.cpu().mean() - metrics["{}rewards/accuracies".format(prefix)] = reward_accuracies.cpu().mean() - metrics["{}rewards/margins".format(prefix)] = (chosen_rewards - rejected_rewards).cpu().mean() - metrics["{}logps/rejected".format(prefix)] = policy_rejected_logps.detach().cpu().mean() - metrics["{}logps/chosen".format(prefix)] = policy_chosen_logps.detach().cpu().mean() - metrics["{}logits/rejected".format(prefix)] = policy_rejected_logits.detach().cpu().mean() - metrics["{}logits/chosen".format(prefix)] = policy_chosen_logits.detach().cpu().mean() + metrics["{}rewards/chosen".format(prefix)] = chosen_rewards.mean().cpu() + metrics["{}rewards/rejected".format(prefix)] = rejected_rewards.mean().cpu() + metrics["{}rewards/accuracies".format(prefix)] = reward_accuracies.mean().cpu() + metrics["{}rewards/margins".format(prefix)] = (chosen_rewards - rejected_rewards).mean().cpu() + metrics["{}logps/rejected".format(prefix)] = policy_rejected_logps.detach().mean().cpu() + metrics["{}logps/chosen".format(prefix)] = policy_chosen_logps.detach().mean().cpu() + metrics["{}logits/rejected".format(prefix)] = policy_rejected_logits.detach().mean().cpu() + metrics["{}logits/chosen".format(prefix)] = policy_chosen_logits.detach().mean().cpu() return losses.mean(), metrics diff --git a/src/llmtuner/train/orpo/trainer.py b/src/llmtuner/train/orpo/trainer.py index 5e0d70d9..88090a9e 100644 --- a/src/llmtuner/train/orpo/trainer.py +++ b/src/llmtuner/train/orpo/trainer.py @@ -113,15 +113,15 @@ class CustomORPOTrainer(DPOTrainer): reward_accuracies = (chosen_rewards > rejected_rewards).float() prefix = "eval_" if train_eval == "eval" else "" - metrics["{}rewards/chosen".format(prefix)] = chosen_rewards.cpu().mean() - metrics["{}rewards/rejected".format(prefix)] = rejected_rewards.cpu().mean() - metrics["{}rewards/accuracies".format(prefix)] = reward_accuracies.cpu().mean() - metrics["{}rewards/margins".format(prefix)] = (chosen_rewards - rejected_rewards).cpu().mean() - metrics["{}logps/rejected".format(prefix)] = rejected_logps.detach().cpu().mean() - metrics["{}logps/chosen".format(prefix)] = chosen_logps.detach().cpu().mean() - metrics["{}logits/rejected".format(prefix)] = rejected_logits.detach().cpu().mean() - metrics["{}logits/chosen".format(prefix)] = chosen_logits.detach().cpu().mean() - metrics["{}sft_loss".format(prefix)] = sft_loss.detach().cpu().mean() - metrics["{}odds_ratio_loss".format(prefix)] = odds_ratio_loss.detach().cpu().mean() + metrics["{}rewards/chosen".format(prefix)] = chosen_rewards.mean().cpu() + metrics["{}rewards/rejected".format(prefix)] = rejected_rewards.mean().cpu() + metrics["{}rewards/accuracies".format(prefix)] = reward_accuracies.mean().cpu() + metrics["{}rewards/margins".format(prefix)] = (chosen_rewards - rejected_rewards).mean().cpu() + metrics["{}logps/rejected".format(prefix)] = rejected_logps.detach().mean().cpu() + metrics["{}logps/chosen".format(prefix)] = chosen_logps.detach().mean().cpu() + metrics["{}logits/rejected".format(prefix)] = rejected_logits.detach().mean().cpu() + metrics["{}logits/chosen".format(prefix)] = chosen_logits.detach().mean().cpu() + metrics["{}sft_loss".format(prefix)] = sft_loss.detach().mean().cpu() + metrics["{}odds_ratio_loss".format(prefix)] = odds_ratio_loss.detach().mean().cpu() return batch_loss, metrics From 340f70cd82b8d573cd9d54cf0ed620db6c61413f Mon Sep 17 00:00:00 2001 From: hiyouga Date: Fri, 3 May 2024 23:15:19 +0800 Subject: [PATCH 16/33] fix webui resume Former-commit-id: 510e64ee709239bff2a8b7207b517922468c934a --- src/llmtuner/webui/runner.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/llmtuner/webui/runner.py b/src/llmtuner/webui/runner.py index 4ea08348..b04c9b00 100644 --- a/src/llmtuner/webui/runner.py +++ b/src/llmtuner/webui/runner.py @@ -278,6 +278,9 @@ class Runner: yield from self._launch(data, do_train=False) def monitor(self): + self.aborted = False + self.running = True + get = lambda elem_id: self.running_data[self.manager.get_elem_by_id(elem_id)] lang = get("top.lang") model_name = get("top.model_name") From 9fc7549d25e6266c503cde7a9922629398b850f2 Mon Sep 17 00:00:00 2001 From: hiyouga Date: Sat, 4 May 2024 00:19:19 +0800 Subject: [PATCH 17/33] fix eval in webui Former-commit-id: 24cc93ab1581fe7cc19e126b2037f4509258f125 --- src/llmtuner/extras/callbacks.py | 85 +++++++++++++++++++------- src/llmtuner/webui/common.py | 10 ++- src/llmtuner/webui/components/eval.py | 15 ++--- src/llmtuner/webui/components/train.py | 4 +- src/llmtuner/webui/runner.py | 4 +- src/llmtuner/webui/utils.py | 4 +- 6 files changed, 84 insertions(+), 38 deletions(-) diff --git a/src/llmtuner/extras/callbacks.py b/src/llmtuner/extras/callbacks.py index 76f07a42..a07c7059 100644 --- a/src/llmtuner/extras/callbacks.py +++ b/src/llmtuner/extras/callbacks.py @@ -5,7 +5,7 @@ import signal import time from concurrent.futures import ThreadPoolExecutor from datetime import timedelta -from typing import TYPE_CHECKING, Any, Dict +from typing import TYPE_CHECKING, Any, Dict, Optional import transformers from transformers import TrainerCallback @@ -38,8 +38,20 @@ class FixValueHeadModelCallback(TrainerCallback): class LogCallback(TrainerCallback): def __init__(self, output_dir: str) -> None: + r""" + Initializes a callback for logging training and evaluation status. + """ + """ Progress """ + self.start_time = 0 + self.cur_steps = 0 + self.max_steps = 0 + self.elapsed_time = "" + self.remaining_time = "" + self.thread_pool: Optional["ThreadPoolExecutor"] = None + """ Status """ self.aborted = False self.do_train = False + """ Web UI """ self.webui_mode = bool(int(os.environ.get("LLAMABOARD_ENABLED", "0"))) if self.webui_mode: signal.signal(signal.SIGABRT, self._set_abort) @@ -66,6 +78,19 @@ class LogCallback(TrainerCallback): self.elapsed_time = str(timedelta(seconds=int(elapsed_time))) self.remaining_time = str(timedelta(seconds=int(remaining_time))) + def _write_log(self, output_dir: str, logs: Dict[str, Any]) -> None: + with open(os.path.join(output_dir, TRAINER_LOG), "a", encoding="utf-8") as f: + f.write(json.dumps(logs) + "\n") + + def _create_thread_pool(self, output_dir: str) -> None: + os.makedirs(output_dir, exist_ok=True) + self.thread_pool = ThreadPoolExecutor(max_workers=1) + + def _close_thread_pool(self) -> None: + if self.thread_pool is not None: + self.thread_pool.shutdown(wait=True) + self.thread_pool = None + def on_train_begin(self, args: "TrainingArguments", state: "TrainerState", control: "TrainerControl", **kwargs): r""" Event called at the beginning of training. @@ -73,8 +98,7 @@ class LogCallback(TrainerCallback): if args.should_save: self.do_train = True self._reset(max_steps=state.max_steps) - os.makedirs(args.output_dir, exist_ok=True) - self.thread_pool = ThreadPoolExecutor(max_workers=1) + self._create_thread_pool(output_dir=args.output_dir) if ( args.should_save @@ -84,6 +108,12 @@ class LogCallback(TrainerCallback): logger.warning("Previous trainer log in this folder will be deleted.") os.remove(os.path.join(args.output_dir, TRAINER_LOG)) + def on_train_end(self, args: "TrainingArguments", state: "TrainerState", control: "TrainerControl", **kwargs): + r""" + Event called at the end of training. + """ + self._close_thread_pool() + def on_substep_end(self, args: "TrainingArguments", state: "TrainerState", control: "TrainerControl", **kwargs): r""" Event called at the end of an substep during gradient accumulation. @@ -103,31 +133,19 @@ class LogCallback(TrainerCallback): control.should_epoch_stop = True control.should_training_stop = True - def on_train_end(self, args: "TrainingArguments", state: "TrainerState", control: "TrainerControl", **kwargs): + def on_evaluate(self, args: "TrainingArguments", state: "TrainerState", control: "TrainerControl", **kwargs): r""" - Event called at the end of training. + Event called after an evaluation phase. """ - self.thread_pool.shutdown(wait=True) - self.thread_pool = None + self._close_thread_pool() - def on_prediction_step( - self, args: "TrainingArguments", state: "TrainerState", control: "TrainerControl", **kwargs - ): + def on_predict(self, args: "TrainingArguments", state: "TrainerState", control: "TrainerControl", **kwargs): r""" - Event called after a prediction step. + Event called after a successful prediction. """ - eval_dataloader = kwargs.pop("eval_dataloader", None) - if args.should_save and has_length(eval_dataloader) and not self.do_train: - if self.max_steps == 0: - self.max_steps = len(eval_dataloader) + self._close_thread_pool() - self._timing(cur_steps=self.cur_steps + 1) - - def _write_log(self, output_dir: str, logs: Dict[str, Any]): - with open(os.path.join(output_dir, TRAINER_LOG), "a", encoding="utf-8") as f: - f.write(json.dumps(logs) + "\n") - - def on_log(self, args: "TrainingArguments", state: "TrainerState", control: "TrainerControl", **kwargs) -> None: + def on_log(self, args: "TrainingArguments", state: "TrainerState", control: "TrainerControl", **kwargs): r""" Event called after logging the last logs. """ @@ -158,3 +176,26 @@ class LogCallback(TrainerCallback): if self.thread_pool is not None: self.thread_pool.submit(self._write_log, args.output_dir, logs) + + def on_prediction_step( + self, args: "TrainingArguments", state: "TrainerState", control: "TrainerControl", **kwargs + ): + r""" + Event called after a prediction step. + """ + eval_dataloader = kwargs.pop("eval_dataloader", None) + if args.should_save and has_length(eval_dataloader) and not self.do_train: + if self.max_steps == 0: + self._reset(max_steps=len(eval_dataloader)) + self._create_thread_pool(output_dir=args.output_dir) + + self._timing(cur_steps=self.cur_steps + 1) + if self.cur_steps % 5 == 0 and self.thread_pool is not None: + logs = dict( + current_steps=self.cur_steps, + total_steps=self.max_steps, + percentage=round(self.cur_steps / self.max_steps * 100, 2) if self.max_steps != 0 else 100, + elapsed_time=self.elapsed_time, + remaining_time=self.remaining_time, + ) + self.thread_pool.submit(self._write_log, args.output_dir, logs) diff --git a/src/llmtuner/webui/common.py b/src/llmtuner/webui/common.py index a33e3db7..d569f1fa 100644 --- a/src/llmtuner/webui/common.py +++ b/src/llmtuner/webui/common.py @@ -17,6 +17,7 @@ from ..extras.constants import ( TRAINING_STAGES, DownloadSource, ) +from ..extras.logging import get_logger from ..extras.misc import use_modelscope from ..extras.packages import is_gradio_available @@ -25,6 +26,9 @@ if is_gradio_available(): import gradio as gr +logger = get_logger(__name__) + + ADAPTER_NAMES = {WEIGHTS_NAME, SAFETENSORS_WEIGHTS_NAME} DEFAULT_CACHE_DIR = "cache" DEFAULT_CONFIG_DIR = "config" @@ -128,11 +132,15 @@ def list_adapters(model_name: str, finetuning_type: str) -> "gr.Dropdown": def load_dataset_info(dataset_dir: str) -> Dict[str, Dict[str, Any]]: + if dataset_dir == "ONLINE": + logger.info("dataset_dir is ONLINE, using online dataset.") + return {} + try: with open(os.path.join(dataset_dir, DATA_CONFIG), "r", encoding="utf-8") as f: return json.load(f) except Exception as err: - print("Cannot open {} due to {}.".format(os.path.join(dataset_dir, DATA_CONFIG), str(err))) + logger.warning("Cannot open {} due to {}.".format(os.path.join(dataset_dir, DATA_CONFIG), str(err))) return {} diff --git a/src/llmtuner/webui/components/eval.py b/src/llmtuner/webui/components/eval.py index 3910a746..222f9314 100644 --- a/src/llmtuner/webui/components/eval.py +++ b/src/llmtuner/webui/components/eval.py @@ -21,16 +21,16 @@ def create_eval_tab(engine: "Engine") -> Dict[str, "Component"]: with gr.Row(): dataset_dir = gr.Textbox(value=DEFAULT_DATA_DIR, scale=2) - dataset = gr.Dropdown(multiselect=True, scale=4) + dataset = gr.Dropdown(multiselect=True, allow_custom_value=True, scale=4) preview_elems = create_preview_box(dataset_dir, dataset) input_elems.update({dataset_dir, dataset}) elem_dict.update(dict(dataset_dir=dataset_dir, dataset=dataset, **preview_elems)) with gr.Row(): - cutoff_len = gr.Slider(value=1024, minimum=4, maximum=8192, step=1) + cutoff_len = gr.Slider(value=1024, minimum=4, maximum=65536, step=1) max_samples = gr.Textbox(value="100000") - batch_size = gr.Slider(value=8, minimum=1, maximum=512, step=1) + batch_size = gr.Slider(value=2, minimum=1, maximum=1024, step=1) predict = gr.Checkbox(value=True) input_elems.update({cutoff_len, max_samples, batch_size, predict}) @@ -48,30 +48,27 @@ def create_eval_tab(engine: "Engine") -> Dict[str, "Component"]: with gr.Row(): cmd_preview_btn = gr.Button() start_btn = gr.Button(variant="primary") - stop_btn = gr.Button(variant="stop") with gr.Row(): resume_btn = gr.Checkbox(visible=False, interactive=False) - process_bar = gr.Slider(visible=False, interactive=False) + progress_bar = gr.Slider(visible=False, interactive=False) with gr.Row(): output_box = gr.Markdown() - output_elems = [output_box, process_bar] + output_elems = [output_box, progress_bar] elem_dict.update( dict( cmd_preview_btn=cmd_preview_btn, start_btn=start_btn, - stop_btn=stop_btn, resume_btn=resume_btn, - process_bar=process_bar, + progress_bar=progress_bar, output_box=output_box, ) ) cmd_preview_btn.click(engine.runner.preview_eval, input_elems, output_elems, concurrency_limit=None) start_btn.click(engine.runner.run_eval, input_elems, output_elems) - stop_btn.click(engine.runner.set_abort) resume_btn.change(engine.runner.monitor, outputs=output_elems, concurrency_limit=None) dataset_dir.change(list_dataset, [dataset_dir], [dataset], queue=False) diff --git a/src/llmtuner/webui/components/train.py b/src/llmtuner/webui/components/train.py index c709b916..857c56ac 100644 --- a/src/llmtuner/webui/components/train.py +++ b/src/llmtuner/webui/components/train.py @@ -27,7 +27,7 @@ def create_train_tab(engine: "Engine") -> Dict[str, "Component"]: choices=list(TRAINING_STAGES.keys()), value=list(TRAINING_STAGES.keys())[0], scale=1 ) dataset_dir = gr.Textbox(value=DEFAULT_DATA_DIR, scale=1) - dataset = gr.Dropdown(multiselect=True, scale=4) + dataset = gr.Dropdown(multiselect=True, allow_custom_value=True, scale=4) preview_elems = create_preview_box(dataset_dir, dataset) input_elems.update({training_stage, dataset_dir, dataset}) @@ -52,7 +52,7 @@ def create_train_tab(engine: "Engine") -> Dict[str, "Component"]: ) with gr.Row(): - cutoff_len = gr.Slider(value=1024, minimum=4, maximum=16384, step=1) + cutoff_len = gr.Slider(value=1024, minimum=4, maximum=65536, step=1) batch_size = gr.Slider(value=2, minimum=1, maximum=1024, step=1) gradient_accumulation_steps = gr.Slider(value=8, minimum=1, maximum=1024, step=1) val_size = gr.Slider(value=0, minimum=0, maximum=1, step=0.001) diff --git a/src/llmtuner/webui/runner.py b/src/llmtuner/webui/runner.py index b04c9b00..59515a62 100644 --- a/src/llmtuner/webui/runner.py +++ b/src/llmtuner/webui/runner.py @@ -299,12 +299,12 @@ class Runner: progress_bar: gr.Slider(visible=False), } else: - running_log, running_progress, running_loss = get_trainer_info(output_path) + running_log, running_progress, running_loss = get_trainer_info(output_path, self.do_train) return_dict = { output_box: running_log, progress_bar: running_progress, } - if self.do_train and running_loss is not None: + if running_loss is not None: return_dict[loss_viewer] = running_loss yield return_dict diff --git a/src/llmtuner/webui/utils.py b/src/llmtuner/webui/utils.py index c8729d36..1f2b0591 100644 --- a/src/llmtuner/webui/utils.py +++ b/src/llmtuner/webui/utils.py @@ -63,7 +63,7 @@ def get_time() -> str: return datetime.now().strftime(r"%Y-%m-%d-%H-%M-%S") -def get_trainer_info(output_path: os.PathLike) -> Tuple[str, "gr.Slider", Optional["gr.Plot"]]: +def get_trainer_info(output_path: os.PathLike, do_train: bool) -> Tuple[str, "gr.Slider", Optional["gr.Plot"]]: running_log = "" running_progress = gr.Slider(visible=False) running_loss = None @@ -91,7 +91,7 @@ def get_trainer_info(output_path: os.PathLike) -> Tuple[str, "gr.Slider", Option ) running_progress = gr.Slider(label=label, value=percentage, visible=True) - if is_matplotlib_available(): + if do_train and is_matplotlib_available(): running_loss = gr.Plot(gen_loss_plot(trainer_log)) return running_log, running_progress, running_loss From 4c564dc5378e85889cb30220b764e6f9ceaeb995 Mon Sep 17 00:00:00 2001 From: hiyouga Date: Sat, 4 May 2024 00:31:02 +0800 Subject: [PATCH 18/33] update readme Former-commit-id: 1409654cef43376beaf021c30d533304b11c2c3c --- README.md | 10 +++++++++- README_zh.md | 10 +++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 8caac93f..5e7d61ea 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ [![GitHub last commit](https://img.shields.io/github/last-commit/hiyouga/LLaMA-Factory)](https://github.com/hiyouga/LLaMA-Factory/commits/main) [![PyPI](https://img.shields.io/pypi/v/llmtuner)](https://pypi.org/project/llmtuner/) [![Downloads](https://static.pepy.tech/badge/llmtuner)](https://pypi.org/project/llmtuner/) -[![Citation](https://img.shields.io/badge/citation-34-green)](#projects-using-llama-factory) +[![Citation](https://img.shields.io/badge/citation-42-green)](#projects-using-llama-factory) [![GitHub pull request](https://img.shields.io/badge/PRs-welcome-blue)](https://github.com/hiyouga/LLaMA-Factory/pulls) [![Discord](https://dcbadge.vercel.app/api/server/rKfvV9r9FK?compact=true&style=flat)](https://discord.gg/rKfvV9r9FK) [![Twitter](https://img.shields.io/twitter/follow/llamafactory_ai)](https://twitter.com/llamafactory_ai) @@ -441,6 +441,7 @@ If you have a project that should be incorporated, please contact via email or c 1. Huang et al. Key-Point-Driven Data Synthesis with its Enhancement on Mathematical Reasoning. 2024. [[arxiv]](https://arxiv.org/abs/2403.02333) 1. Duan et al. Negating Negatives: Alignment without Human Positive Samples via Distributional Dispreference Optimization. 2024. [[arxiv]](https://arxiv.org/abs/2403.03419) 1. Xie and Schwertfeger. Empowering Robotics with Large Language Models: osmAG Map Comprehension with LLMs. 2024. [[arxiv]](https://arxiv.org/abs/2403.08228) +1. Wu et al. Large Language Models are Parallel Multilingual Learners. 2024. [[arxiv]](https://arxiv.org/abs/2403.09073) 1. Zhang et al. EDT: Improving Large Language Models' Generation by Entropy-based Dynamic Temperature Sampling. 2024. [[arxiv]](https://arxiv.org/abs/2403.14541) 1. Weller et al. FollowIR: Evaluating and Teaching Information Retrieval Models to Follow Instructions. 2024. [[arxiv]](https://arxiv.org/abs/2403.15246) 1. Hongbin Na. CBT-LLM: A Chinese Large Language Model for Cognitive Behavioral Therapy-based Mental Health Question Answering. 2024. [[arxiv]](https://arxiv.org/abs/2403.16008) @@ -448,7 +449,14 @@ If you have a project that should be incorporated, please contact via email or c 1. Liu et al. Extensive Self-Contrast Enables Feedback-Free Language Model Alignment. 2024. [[arxiv]](https://arxiv.org/abs/2404.00604) 1. Luo et al. BAdam: A Memory Efficient Full Parameter Training Method for Large Language Models. 2024. [[arxiv]](https://arxiv.org/abs/2404.02827) 1. Du et al. Chinese Tiny LLM: Pretraining a Chinese-Centric Large Language Model. 2024. [[arxiv]](https://arxiv.org/abs/2404.04167) +1. Ma et al. Parameter Efficient Quasi-Orthogonal Fine-Tuning via Givens Rotation. 2024. [[arxiv]](https://arxiv.org/abs/2404.04316) 1. Liu et al. Dynamic Generation of Personalities with Large Language Models. 2024. [[arxiv]](https://arxiv.org/abs/2404.07084) +1. Shang et al. How Far Have We Gone in Stripped Binary Code Understanding Using Large Language Models. 2024. [[arxiv]](https://arxiv.org/abs/2404.09836) +1. Huang et al. LLMTune: Accelerate Database Knob Tuning with Large Language Models. 2024. [[arxiv]](https://arxiv.org/abs/2404.11581) +1. Deng et al. Text-Tuple-Table: Towards Information Integration in Text-to-Table Generation via Global Tuple Extraction. 2024. [[arxiv]](https://arxiv.org/abs/2404.14215) +1. Acikgoz et al. Hippocrates: An Open-Source Framework for Advancing Large Language Models in Healthcare. 2024. [[arxiv]](https://arxiv.org/abs/2404.16621) +1. Zhang et al. Small Language Models Need Strong Verifiers to Self-Correct Reasoning. 2024. [[arxiv]](https://arxiv.org/abs/2404.17140) +1. Zhou et al. FREB-TQA: A Fine-Grained Robustness Evaluation Benchmark for Table Question Answering. 2024. [[arxiv]](https://arxiv.org/abs/2404.18585) 1. **[StarWhisper](https://github.com/Yu-Yang-Li/StarWhisper)**: A large language model for Astronomy, based on ChatGLM2-6B and Qwen-14B. 1. **[DISC-LawLLM](https://github.com/FudanDISC/DISC-LawLLM)**: A large language model specialized in Chinese legal domain, based on Baichuan-13B, is capable of retrieving and reasoning on legal knowledge. 1. **[Sunsimiao](https://github.com/thomas-yanxin/Sunsimiao)**: A large language model specialized in Chinese medical domain, based on Baichuan-7B and ChatGLM-6B. diff --git a/README_zh.md b/README_zh.md index 27522232..bfb9feaa 100644 --- a/README_zh.md +++ b/README_zh.md @@ -5,7 +5,7 @@ [![GitHub last commit](https://img.shields.io/github/last-commit/hiyouga/LLaMA-Factory)](https://github.com/hiyouga/LLaMA-Factory/commits/main) [![PyPI](https://img.shields.io/pypi/v/llmtuner)](https://pypi.org/project/llmtuner/) [![Downloads](https://static.pepy.tech/badge/llmtuner)](https://pypi.org/project/llmtuner/) -[![Citation](https://img.shields.io/badge/citation-34-green)](#使用了-llama-factory-的项目) +[![Citation](https://img.shields.io/badge/citation-42-green)](#使用了-llama-factory-的项目) [![GitHub pull request](https://img.shields.io/badge/PRs-welcome-blue)](https://github.com/hiyouga/LLaMA-Factory/pulls) [![Discord](https://dcbadge.vercel.app/api/server/rKfvV9r9FK?compact=true&style=flat)](https://discord.gg/rKfvV9r9FK) [![Twitter](https://img.shields.io/twitter/follow/llamafactory_ai)](https://twitter.com/llamafactory_ai) @@ -441,6 +441,7 @@ export USE_MODELSCOPE_HUB=1 # Windows 使用 `set USE_MODELSCOPE_HUB=1` 1. Huang et al. Key-Point-Driven Data Synthesis with its Enhancement on Mathematical Reasoning. 2024. [[arxiv]](https://arxiv.org/abs/2403.02333) 1. Duan et al. Negating Negatives: Alignment without Human Positive Samples via Distributional Dispreference Optimization. 2024. [[arxiv]](https://arxiv.org/abs/2403.03419) 1. Xie and Schwertfeger. Empowering Robotics with Large Language Models: osmAG Map Comprehension with LLMs. 2024. [[arxiv]](https://arxiv.org/abs/2403.08228) +1. Wu et al. Large Language Models are Parallel Multilingual Learners. 2024. [[arxiv]](https://arxiv.org/abs/2403.09073) 1. Zhang et al. EDT: Improving Large Language Models' Generation by Entropy-based Dynamic Temperature Sampling. 2024. [[arxiv]](https://arxiv.org/abs/2403.14541) 1. Weller et al. FollowIR: Evaluating and Teaching Information Retrieval Models to Follow Instructions. 2024. [[arxiv]](https://arxiv.org/abs/2403.15246) 1. Hongbin Na. CBT-LLM: A Chinese Large Language Model for Cognitive Behavioral Therapy-based Mental Health Question Answering. 2024. [[arxiv]](https://arxiv.org/abs/2403.16008) @@ -448,7 +449,14 @@ export USE_MODELSCOPE_HUB=1 # Windows 使用 `set USE_MODELSCOPE_HUB=1` 1. Liu et al. Extensive Self-Contrast Enables Feedback-Free Language Model Alignment. 2024. [[arxiv]](https://arxiv.org/abs/2404.00604) 1. Luo et al. BAdam: A Memory Efficient Full Parameter Training Method for Large Language Models. 2024. [[arxiv]](https://arxiv.org/abs/2404.02827) 1. Du et al. Chinese Tiny LLM: Pretraining a Chinese-Centric Large Language Model. 2024. [[arxiv]](https://arxiv.org/abs/2404.04167) +1. Ma et al. Parameter Efficient Quasi-Orthogonal Fine-Tuning via Givens Rotation. 2024. [[arxiv]](https://arxiv.org/abs/2404.04316) 1. Liu et al. Dynamic Generation of Personalities with Large Language Models. 2024. [[arxiv]](https://arxiv.org/abs/2404.07084) +1. Shang et al. How Far Have We Gone in Stripped Binary Code Understanding Using Large Language Models. 2024. [[arxiv]](https://arxiv.org/abs/2404.09836) +1. Huang et al. LLMTune: Accelerate Database Knob Tuning with Large Language Models. 2024. [[arxiv]](https://arxiv.org/abs/2404.11581) +1. Deng et al. Text-Tuple-Table: Towards Information Integration in Text-to-Table Generation via Global Tuple Extraction. 2024. [[arxiv]](https://arxiv.org/abs/2404.14215) +1. Acikgoz et al. Hippocrates: An Open-Source Framework for Advancing Large Language Models in Healthcare. 2024. [[arxiv]](https://arxiv.org/abs/2404.16621) +1. Zhang et al. Small Language Models Need Strong Verifiers to Self-Correct Reasoning. 2024. [[arxiv]](https://arxiv.org/abs/2404.17140) +1. Zhou et al. FREB-TQA: A Fine-Grained Robustness Evaluation Benchmark for Table Question Answering. 2024. [[arxiv]](https://arxiv.org/abs/2404.18585) 1. **[StarWhisper](https://github.com/Yu-Yang-Li/StarWhisper)**: 天文大模型 StarWhisper,基于 ChatGLM2-6B 和 Qwen-14B 在天文数据上微调而得。 1. **[DISC-LawLLM](https://github.com/FudanDISC/DISC-LawLLM)**: 中文法律领域大模型 DISC-LawLLM,基于 Baichuan-13B 微调而得,具有法律推理和知识检索能力。 1. **[Sunsimiao](https://github.com/thomas-yanxin/Sunsimiao)**: 孙思邈中文医疗大模型 Sumsimiao,基于 Baichuan-7B 和 ChatGLM-6B 在中文医疗数据上微调而得。 From ed92038736d7afbbf7d65fdb24253c538a6736ac Mon Sep 17 00:00:00 2001 From: hiyouga Date: Sat, 4 May 2024 00:43:02 +0800 Subject: [PATCH 19/33] update readme and webui launch Former-commit-id: 9d2ce57345d9e1c5fe9ec235ae33e598757cdb5d --- README.md | 8 +++++--- README_zh.md | 8 +++++--- src/llmtuner/webui/interface.py | 4 ++-- 3 files changed, 12 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 5e7d61ea..4f363099 100644 --- a/README.md +++ b/README.md @@ -344,11 +344,12 @@ To enable FlashAttention-2 on the Windows platform, you need to install the prec #### Use local environment ```bash -export CUDA_VISIBLE_DEVICES=0 # `set CUDA_VISIBLE_DEVICES=0` for Windows -export GRADIO_SERVER_PORT=7860 # `set GRADIO_SERVER_PORT=7860` for Windows llamafactory-cli webui ``` +> [!TIPS] +> To modify the default setting in the LLaMA Board GUI, you can use environment variables, e.g., `export CUDA_VISIBLE_DEVICES=0 GRADIO_SERVER_NAME=0.0.0.0 GRADIO_SERVER_PORT=7860 GRADIO_SHARE=False` (use `set` command on Windows OS). +
For Alibaba Cloud users If you encountered display problems in LLaMA Board on Alibaba Cloud, try using the following command to set environment variables before starting LLaMA Board: @@ -392,7 +393,8 @@ docker compose -f ./docker-compose.yml up -d See [examples/README.md](examples/README.md) for usage. -Use `llamafactory-cli train -h` to display arguments description. +> [!TIPS] +> Use `llamafactory-cli train -h` to display arguments description. ### Deploy with OpenAI-style API and vLLM diff --git a/README_zh.md b/README_zh.md index bfb9feaa..8f9d5513 100644 --- a/README_zh.md +++ b/README_zh.md @@ -344,11 +344,12 @@ pip install https://github.com/jllllll/bitsandbytes-windows-webui/releases/downl #### 使用本地环境 ```bash -export CUDA_VISIBLE_DEVICES=0 # Windows 使用 `set CUDA_VISIBLE_DEVICES=0` -export GRADIO_SERVER_PORT=7860 # Windows 使用 `set GRADIO_SERVER_PORT=7860` llamafactory-cli webui ``` +> [!TIPS] +> 您可以使用环境变量来修改 LLaMA Board 可视化界面的默认设置,例如 `export CUDA_VISIBLE_DEVICES=0 GRADIO_SERVER_NAME=0.0.0.0 GRADIO_SERVER_PORT=7860 GRADIO_SHARE=False`(Windows 系统可使用 `set` 指令)。 +
阿里云用户指南 如果您在阿里云上使用 LLaMA Board 时遇到显示问题,请尝试在启动前使用以下命令设置环境变量: @@ -392,7 +393,8 @@ docker compose -f ./docker-compose.yml up -d 使用方法请参考 [examples/README_zh.md](examples/README_zh.md)。 -您可以执行 `llamafactory-cli train -h` 来查看参数文档。 +> [!TIPS] +> 您可以执行 `llamafactory-cli train -h` 来查看参数文档。 ### 利用 vLLM 部署 OpenAI API diff --git a/src/llmtuner/webui/interface.py b/src/llmtuner/webui/interface.py index 5f17d76d..459802f2 100644 --- a/src/llmtuner/webui/interface.py +++ b/src/llmtuner/webui/interface.py @@ -69,8 +69,8 @@ def create_web_demo() -> gr.Blocks: def run_web_ui(): - create_ui().queue().launch(server_name="0.0.0.0") + create_ui().queue().launch() def run_web_demo(): - create_web_demo().queue().launch(server_name="0.0.0.0") + create_web_demo().queue().launch() From 8d6b454e3341b3dde102124553b0a2a7869675a4 Mon Sep 17 00:00:00 2001 From: hiyouga Date: Sat, 4 May 2024 00:43:53 +0800 Subject: [PATCH 20/33] update readme Former-commit-id: d4283bb6bf5757db05ccb6d26f47658e4955fe6b --- README.md | 4 ++-- README_zh.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 4f363099..45732220 100644 --- a/README.md +++ b/README.md @@ -347,7 +347,7 @@ To enable FlashAttention-2 on the Windows platform, you need to install the prec llamafactory-cli webui ``` -> [!TIPS] +> [!TIP] > To modify the default setting in the LLaMA Board GUI, you can use environment variables, e.g., `export CUDA_VISIBLE_DEVICES=0 GRADIO_SERVER_NAME=0.0.0.0 GRADIO_SERVER_PORT=7860 GRADIO_SHARE=False` (use `set` command on Windows OS).
For Alibaba Cloud users @@ -393,7 +393,7 @@ docker compose -f ./docker-compose.yml up -d See [examples/README.md](examples/README.md) for usage. -> [!TIPS] +> [!TIP] > Use `llamafactory-cli train -h` to display arguments description. ### Deploy with OpenAI-style API and vLLM diff --git a/README_zh.md b/README_zh.md index 8f9d5513..4db1f843 100644 --- a/README_zh.md +++ b/README_zh.md @@ -347,7 +347,7 @@ pip install https://github.com/jllllll/bitsandbytes-windows-webui/releases/downl llamafactory-cli webui ``` -> [!TIPS] +> [!TIP] > 您可以使用环境变量来修改 LLaMA Board 可视化界面的默认设置,例如 `export CUDA_VISIBLE_DEVICES=0 GRADIO_SERVER_NAME=0.0.0.0 GRADIO_SERVER_PORT=7860 GRADIO_SHARE=False`(Windows 系统可使用 `set` 指令)。
阿里云用户指南 @@ -393,7 +393,7 @@ docker compose -f ./docker-compose.yml up -d 使用方法请参考 [examples/README_zh.md](examples/README_zh.md)。 -> [!TIPS] +> [!TIP] > 您可以执行 `llamafactory-cli train -h` 来查看参数文档。 ### 利用 vLLM 部署 OpenAI API From c32fc1d89bac005ab8eb20aad34bc0517208878f Mon Sep 17 00:00:00 2001 From: hiyouga Date: Sat, 4 May 2024 15:59:15 +0800 Subject: [PATCH 21/33] update api and support abort eval in webui Former-commit-id: ed8f8be752ba2dcbaa6e8b1dc0a2e2821db1a5b8 --- src/llmtuner/api/app.py | 205 +++++--------------------- src/llmtuner/api/chat.py | 176 ++++++++++++++++++++++ src/llmtuner/api/common.py | 20 +++ src/llmtuner/api/protocol.py | 12 +- src/llmtuner/chat/chat_model.py | 2 +- src/llmtuner/eval/evaluator.py | 5 +- src/llmtuner/extras/callbacks.py | 36 +++-- src/llmtuner/train/tuner.py | 4 +- src/llmtuner/webui/components/eval.py | 3 + src/llmtuner/webui/interface.py | 4 +- src/llmtuner/webui/locales.py | 2 +- 11 files changed, 277 insertions(+), 192 deletions(-) create mode 100644 src/llmtuner/api/chat.py create mode 100644 src/llmtuner/api/common.py diff --git a/src/llmtuner/api/app.py b/src/llmtuner/api/app.py index 36918d1b..375ee61f 100644 --- a/src/llmtuner/api/app.py +++ b/src/llmtuner/api/app.py @@ -1,36 +1,29 @@ -import json import os from contextlib import asynccontextmanager -from typing import Any, Dict, Sequence - -from pydantic import BaseModel +from typing import Annotated, Optional from ..chat import ChatModel -from ..data import Role as DataRole from ..extras.misc import torch_gc from ..extras.packages import is_fastapi_availble, is_starlette_available, is_uvicorn_available +from .chat import ( + create_chat_completion_response, + create_score_evaluation_response, + create_stream_chat_completion_response, +) from .protocol import ( - ChatCompletionMessage, ChatCompletionRequest, ChatCompletionResponse, - ChatCompletionResponseChoice, - ChatCompletionResponseStreamChoice, - ChatCompletionResponseUsage, - ChatCompletionStreamResponse, - Finish, - Function, - FunctionCall, ModelCard, ModelList, - Role, ScoreEvaluationRequest, ScoreEvaluationResponse, ) if is_fastapi_availble(): - from fastapi import FastAPI, HTTPException, status + from fastapi import Depends, FastAPI, HTTPException, status from fastapi.middleware.cors import CORSMiddleware + from fastapi.security.http import HTTPAuthorizationCredentials, HTTPBearer if is_starlette_available(): @@ -47,23 +40,8 @@ async def lifespan(app: "FastAPI"): # collects GPU memory torch_gc() -def dictify(data: "BaseModel") -> Dict[str, Any]: - try: # pydantic v2 - return data.model_dump(exclude_unset=True) - except AttributeError: # pydantic v1 - return data.dict(exclude_unset=True) - - -def jsonify(data: "BaseModel") -> str: - try: # pydantic v2 - return json.dumps(data.model_dump(exclude_unset=True), ensure_ascii=False) - except AttributeError: # pydantic v1 - return data.json(exclude_unset=True, ensure_ascii=False) - - def create_app(chat_model: "ChatModel") -> "FastAPI": app = FastAPI(lifespan=lifespan) - app.add_middleware( CORSMiddleware, allow_origins=["*"], @@ -71,161 +49,58 @@ def create_app(chat_model: "ChatModel") -> "FastAPI": allow_methods=["*"], allow_headers=["*"], ) + api_key = os.environ.get("API_KEY", None) + security = HTTPBearer(auto_error=False) - role_mapping = { - Role.USER: DataRole.USER.value, - Role.ASSISTANT: DataRole.ASSISTANT.value, - Role.SYSTEM: DataRole.SYSTEM.value, - Role.FUNCTION: DataRole.FUNCTION.value, - Role.TOOL: DataRole.OBSERVATION.value, - } + async def verify_api_key(auth: Annotated[Optional[HTTPAuthorizationCredentials], Depends(security)]): + if api_key and (auth is None or auth.credentials != api_key): + raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid API key.") - @app.get("/v1/models", response_model=ModelList) + @app.get( + "/v1/models", + response_model=ModelList, + status_code=status.HTTP_200_OK, + dependencies=[Depends(verify_api_key)], + ) async def list_models(): model_card = ModelCard(id="gpt-3.5-turbo") return ModelList(data=[model_card]) - @app.post("/v1/chat/completions", response_model=ChatCompletionResponse, status_code=status.HTTP_200_OK) + @app.post( + "/v1/chat/completions", + response_model=ChatCompletionResponse, + status_code=status.HTTP_200_OK, + dependencies=[Depends(verify_api_key)], + ) async def create_chat_completion(request: ChatCompletionRequest): if not chat_model.engine.can_generate: raise HTTPException(status_code=status.HTTP_405_METHOD_NOT_ALLOWED, detail="Not allowed") - if len(request.messages) == 0: - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid length") - - if request.messages[0].role == Role.SYSTEM: - system = request.messages.pop(0).content - else: - system = "" - - if len(request.messages) % 2 == 0: - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Only supports u/a/u/a/u...") - - input_messages = [] - for i, message in enumerate(request.messages): - if i % 2 == 0 and message.role not in [Role.USER, Role.TOOL]: - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid role") - elif i % 2 == 1 and message.role not in [Role.ASSISTANT, Role.FUNCTION]: - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid role") - - if message.role == Role.ASSISTANT and isinstance(message.tool_calls, list) and len(message.tool_calls): - name = message.tool_calls[0].function.name - arguments = message.tool_calls[0].function.arguments - content = json.dumps({"name": name, "argument": arguments}, ensure_ascii=False) - input_messages.append({"role": role_mapping[Role.FUNCTION], "content": content}) - else: - input_messages.append({"role": role_mapping[message.role], "content": message.content}) - - tool_list = request.tools - if isinstance(tool_list, list) and len(tool_list): - try: - tools = json.dumps([dictify(tool.function) for tool in tool_list], ensure_ascii=False) - except Exception: - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid tools") - else: - tools = "" - if request.stream: - if tools: - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Cannot stream function calls.") - - generate = stream_chat_completion(input_messages, system, tools, request) + generate = create_stream_chat_completion_response(request, chat_model) return EventSourceResponse(generate, media_type="text/event-stream") + else: + return await create_chat_completion_response(request, chat_model) - responses = await chat_model.achat( - input_messages, - system, - tools, - do_sample=request.do_sample, - temperature=request.temperature, - top_p=request.top_p, - max_new_tokens=request.max_tokens, - num_return_sequences=request.n, - ) - - prompt_length, response_length = 0, 0 - choices = [] - for i, response in enumerate(responses): - if tools: - result = chat_model.engine.template.format_tools.extract(response.response_text) - else: - result = response.response_text - - if isinstance(result, tuple): - name, arguments = result - function = Function(name=name, arguments=arguments) - response_message = ChatCompletionMessage( - role=Role.ASSISTANT, tool_calls=[FunctionCall(function=function)] - ) - finish_reason = Finish.TOOL - else: - response_message = ChatCompletionMessage(role=Role.ASSISTANT, content=result) - finish_reason = Finish.STOP if response.finish_reason == "stop" else Finish.LENGTH - - choices.append( - ChatCompletionResponseChoice(index=i, message=response_message, finish_reason=finish_reason) - ) - prompt_length = response.prompt_length - response_length += response.response_length - - usage = ChatCompletionResponseUsage( - prompt_tokens=prompt_length, - completion_tokens=response_length, - total_tokens=prompt_length + response_length, - ) - - return ChatCompletionResponse(model=request.model, choices=choices, usage=usage) - - async def stream_chat_completion( - messages: Sequence[Dict[str, str]], system: str, tools: str, request: ChatCompletionRequest - ): - choice_data = ChatCompletionResponseStreamChoice( - index=0, delta=ChatCompletionMessage(role=Role.ASSISTANT, content=""), finish_reason=None - ) - chunk = ChatCompletionStreamResponse(model=request.model, choices=[choice_data]) - yield jsonify(chunk) - - async for new_token in chat_model.astream_chat( - messages, - system, - tools, - do_sample=request.do_sample, - temperature=request.temperature, - top_p=request.top_p, - max_new_tokens=request.max_tokens, - ): - if len(new_token) == 0: - continue - - choice_data = ChatCompletionResponseStreamChoice( - index=0, delta=ChatCompletionMessage(content=new_token), finish_reason=None - ) - chunk = ChatCompletionStreamResponse(model=request.model, choices=[choice_data]) - yield jsonify(chunk) - - choice_data = ChatCompletionResponseStreamChoice( - index=0, delta=ChatCompletionMessage(), finish_reason=Finish.STOP - ) - chunk = ChatCompletionStreamResponse(model=request.model, choices=[choice_data]) - yield jsonify(chunk) - yield "[DONE]" - - @app.post("/v1/score/evaluation", response_model=ScoreEvaluationResponse, status_code=status.HTTP_200_OK) + @app.post( + "/v1/score/evaluation", + response_model=ScoreEvaluationResponse, + status_code=status.HTTP_200_OK, + dependencies=[Depends(verify_api_key)], + ) async def create_score_evaluation(request: ScoreEvaluationRequest): if chat_model.engine.can_generate: raise HTTPException(status_code=status.HTTP_405_METHOD_NOT_ALLOWED, detail="Not allowed") - if len(request.messages) == 0: - raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid request") - - scores = await chat_model.aget_scores(request.messages, max_length=request.max_length) - return ScoreEvaluationResponse(model=request.model, scores=scores) + return await create_score_evaluation_response(request, chat_model) return app -def run_api(): +def run_api() -> None: chat_model = ChatModel() app = create_app(chat_model) - print("Visit http://localhost:{}/docs for API document.".format(os.environ.get("API_PORT", 8000))) - uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("API_PORT", 8000)), workers=1) + api_host = os.environ.get("API_HOST", "0.0.0.0") + api_port = int(os.environ.get("API_PORT", "8000")) + print("Visit http://localhost:{}/docs for API document.".format(api_port)) + uvicorn.run(app, host=api_host, port=api_port) diff --git a/src/llmtuner/api/chat.py b/src/llmtuner/api/chat.py new file mode 100644 index 00000000..c9c00f16 --- /dev/null +++ b/src/llmtuner/api/chat.py @@ -0,0 +1,176 @@ +import json +import uuid +from typing import TYPE_CHECKING, AsyncGenerator, Dict, List, Optional, Tuple + +from ..data import Role as DataRole +from ..extras.packages import is_fastapi_availble +from .common import dictify, jsonify +from .protocol import ( + ChatCompletionMessage, + ChatCompletionResponse, + ChatCompletionResponseChoice, + ChatCompletionResponseUsage, + ChatCompletionStreamResponse, + ChatCompletionStreamResponseChoice, + Finish, + Function, + FunctionCall, + Role, + ScoreEvaluationResponse, +) + + +if is_fastapi_availble(): + from fastapi import HTTPException, status + + +if TYPE_CHECKING: + from ..chat import ChatModel + from .protocol import ChatCompletionRequest, ScoreEvaluationRequest + + +ROLE_MAPPING = { + Role.USER: DataRole.USER.value, + Role.ASSISTANT: DataRole.ASSISTANT.value, + Role.SYSTEM: DataRole.SYSTEM.value, + Role.FUNCTION: DataRole.FUNCTION.value, + Role.TOOL: DataRole.OBSERVATION.value, +} + + +async def _process_request(request: "ChatCompletionRequest") -> Tuple[List[Dict[str, str]], str, str]: + if len(request.messages) == 0: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid length") + + if request.messages[0].role == Role.SYSTEM: + system = request.messages.pop(0).content + else: + system = "" + + if len(request.messages) % 2 == 0: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Only supports u/a/u/a/u...") + + input_messages = [] + for i, message in enumerate(request.messages): + if i % 2 == 0 and message.role not in [Role.USER, Role.TOOL]: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid role") + elif i % 2 == 1 and message.role not in [Role.ASSISTANT, Role.FUNCTION]: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid role") + + if message.role == Role.ASSISTANT and isinstance(message.tool_calls, list) and len(message.tool_calls): + name = message.tool_calls[0].function.name + arguments = message.tool_calls[0].function.arguments + content = json.dumps({"name": name, "argument": arguments}, ensure_ascii=False) + input_messages.append({"role": ROLE_MAPPING[Role.FUNCTION], "content": content}) + else: + input_messages.append({"role": ROLE_MAPPING[message.role], "content": message.content}) + + tool_list = request.tools + if isinstance(tool_list, list) and len(tool_list): + try: + tools = json.dumps([dictify(tool.function) for tool in tool_list], ensure_ascii=False) + except Exception: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid tools") + else: + tools = "" + + return input_messages, system, tools + + +async def create_chat_completion_response( + request: "ChatCompletionRequest", chat_model: "ChatModel" +) -> "ChatCompletionResponse": + completion_id = "chatcmpl-{}".format(uuid.uuid4().hex) + input_messages, system, tools = await _process_request(request) + responses = await chat_model.achat( + input_messages, + system, + tools, + do_sample=request.do_sample, + temperature=request.temperature, + top_p=request.top_p, + max_new_tokens=request.max_tokens, + num_return_sequences=request.n, + ) + + prompt_length, response_length = 0, 0 + choices = [] + for i, response in enumerate(responses): + if tools: + result = chat_model.engine.template.format_tools.extract(response.response_text) + else: + result = response.response_text + + if isinstance(result, tuple): + name, arguments = result + function = Function(name=name, arguments=arguments) + tool_call = FunctionCall(id="call_{}".format(uuid.uuid4().hex), function=function) + response_message = ChatCompletionMessage(role=Role.ASSISTANT, tool_calls=[tool_call]) + finish_reason = Finish.TOOL + else: + response_message = ChatCompletionMessage(role=Role.ASSISTANT, content=result) + finish_reason = Finish.STOP if response.finish_reason == "stop" else Finish.LENGTH + + choices.append(ChatCompletionResponseChoice(index=i, message=response_message, finish_reason=finish_reason)) + prompt_length = response.prompt_length + response_length += response.response_length + + usage = ChatCompletionResponseUsage( + prompt_tokens=prompt_length, + completion_tokens=response_length, + total_tokens=prompt_length + response_length, + ) + + return ChatCompletionResponse(id=completion_id, model=request.model, choices=choices, usage=usage) + + +async def _create_stream_chat_completion_chunk( + completion_id: str, + model: str, + delta: "ChatCompletionMessage", + index: Optional[int] = 0, + finish_reason: Optional["Finish"] = None, +) -> str: + choice_data = ChatCompletionStreamResponseChoice(index=index, delta=delta, finish_reason=finish_reason) + chunk = ChatCompletionStreamResponse(id=completion_id, model=model, choices=[choice_data]) + return jsonify(chunk) + + +async def create_stream_chat_completion_response( + request: "ChatCompletionRequest", chat_model: "ChatModel" +) -> AsyncGenerator[str, None]: + completion_id = "chatcmpl-{}".format(uuid.uuid4().hex) + input_messages, system, tools = await _process_request(request) + if tools: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Cannot stream function calls.") + + yield _create_stream_chat_completion_chunk( + completion_id=completion_id, model=request.model, delta=ChatCompletionMessage(role=Role.ASSISTANT, content="") + ) + async for new_token in chat_model.astream_chat( + input_messages, + system, + tools, + do_sample=request.do_sample, + temperature=request.temperature, + top_p=request.top_p, + max_new_tokens=request.max_tokens, + ): + yield _create_stream_chat_completion_chunk( + completion_id=completion_id, model=request.model, delta=ChatCompletionMessage(content=new_token) + ) + + yield _create_stream_chat_completion_chunk( + completion_id=completion_id, model=request.model, delta=ChatCompletionMessage(), finish_reason=Finish.STOP + ) + yield "[DONE]" + + +async def create_score_evaluation_response( + request: "ScoreEvaluationRequest", chat_model: "ChatModel" +) -> "ScoreEvaluationResponse": + if len(request.messages) == 0: + raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid request") + + scores = await chat_model.aget_scores(request.messages, max_length=request.max_length) + return ScoreEvaluationResponse(model=request.model, scores=scores) diff --git a/src/llmtuner/api/common.py b/src/llmtuner/api/common.py new file mode 100644 index 00000000..5ad9a071 --- /dev/null +++ b/src/llmtuner/api/common.py @@ -0,0 +1,20 @@ +import json +from typing import TYPE_CHECKING, Any, Dict + + +if TYPE_CHECKING: + from pydantic import BaseModel + + +def dictify(data: "BaseModel") -> Dict[str, Any]: + try: # pydantic v2 + return data.model_dump(exclude_unset=True) + except AttributeError: # pydantic v1 + return data.dict(exclude_unset=True) + + +def jsonify(data: "BaseModel") -> str: + try: # pydantic v2 + return json.dumps(data.model_dump(exclude_unset=True), ensure_ascii=False) + except AttributeError: # pydantic v1 + return data.json(exclude_unset=True, ensure_ascii=False) diff --git a/src/llmtuner/api/protocol.py b/src/llmtuner/api/protocol.py index ece2132b..ae6e2e9b 100644 --- a/src/llmtuner/api/protocol.py +++ b/src/llmtuner/api/protocol.py @@ -51,7 +51,7 @@ class FunctionAvailable(BaseModel): class FunctionCall(BaseModel): - id: Literal["call_default"] = "call_default" + id: str type: Literal["function"] = "function" function: Function @@ -86,7 +86,7 @@ class ChatCompletionResponseChoice(BaseModel): finish_reason: Finish -class ChatCompletionResponseStreamChoice(BaseModel): +class ChatCompletionStreamResponseChoice(BaseModel): index: int delta: ChatCompletionMessage finish_reason: Optional[Finish] = None @@ -99,7 +99,7 @@ class ChatCompletionResponseUsage(BaseModel): class ChatCompletionResponse(BaseModel): - id: Literal["chatcmpl-default"] = "chatcmpl-default" + id: str object: Literal["chat.completion"] = "chat.completion" created: int = Field(default_factory=lambda: int(time.time())) model: str @@ -108,11 +108,11 @@ class ChatCompletionResponse(BaseModel): class ChatCompletionStreamResponse(BaseModel): - id: Literal["chatcmpl-default"] = "chatcmpl-default" + id: str object: Literal["chat.completion.chunk"] = "chat.completion.chunk" created: int = Field(default_factory=lambda: int(time.time())) model: str - choices: List[ChatCompletionResponseStreamChoice] + choices: List[ChatCompletionStreamResponseChoice] class ScoreEvaluationRequest(BaseModel): @@ -122,7 +122,7 @@ class ScoreEvaluationRequest(BaseModel): class ScoreEvaluationResponse(BaseModel): - id: Literal["scoreeval-default"] = "scoreeval-default" + id: str object: Literal["score.evaluation"] = "score.evaluation" model: str scores: List[float] diff --git a/src/llmtuner/chat/chat_model.py b/src/llmtuner/chat/chat_model.py index 97ae87d7..281ef0c1 100644 --- a/src/llmtuner/chat/chat_model.py +++ b/src/llmtuner/chat/chat_model.py @@ -98,7 +98,7 @@ class ChatModel: return await self.engine.get_scores(batch_input, **input_kwargs) -def run_chat(): +def run_chat() -> None: try: import platform diff --git a/src/llmtuner/eval/evaluator.py b/src/llmtuner/eval/evaluator.py index 4ea134c6..192f4815 100644 --- a/src/llmtuner/eval/evaluator.py +++ b/src/llmtuner/eval/evaluator.py @@ -118,6 +118,5 @@ class Evaluator: f.write(score_info) -def run_eval(): - evaluator = Evaluator() - evaluator.eval() +def run_eval() -> None: + Evaluator().eval() diff --git a/src/llmtuner/extras/callbacks.py b/src/llmtuner/extras/callbacks.py index a07c7059..a142928a 100644 --- a/src/llmtuner/extras/callbacks.py +++ b/src/llmtuner/extras/callbacks.py @@ -2,6 +2,7 @@ import json import logging import os import signal +import sys import time from concurrent.futures import ThreadPoolExecutor from datetime import timedelta @@ -91,6 +92,18 @@ class LogCallback(TrainerCallback): self.thread_pool.shutdown(wait=True) self.thread_pool = None + def on_init_end(self, args: "TrainingArguments", state: "TrainerState", control: "TrainerControl", **kwargs): + r""" + Event called at the end of the initialization of the `Trainer`. + """ + if ( + args.should_save + and os.path.exists(os.path.join(args.output_dir, TRAINER_LOG)) + and args.overwrite_output_dir + ): + logger.warning("Previous trainer log in this folder will be deleted.") + os.remove(os.path.join(args.output_dir, TRAINER_LOG)) + def on_train_begin(self, args: "TrainingArguments", state: "TrainerState", control: "TrainerControl", **kwargs): r""" Event called at the beginning of training. @@ -100,14 +113,6 @@ class LogCallback(TrainerCallback): self._reset(max_steps=state.max_steps) self._create_thread_pool(output_dir=args.output_dir) - if ( - args.should_save - and os.path.exists(os.path.join(args.output_dir, TRAINER_LOG)) - and args.overwrite_output_dir - ): - logger.warning("Previous trainer log in this folder will be deleted.") - os.remove(os.path.join(args.output_dir, TRAINER_LOG)) - def on_train_end(self, args: "TrainingArguments", state: "TrainerState", control: "TrainerControl", **kwargs): r""" Event called at the end of training. @@ -126,9 +131,6 @@ class LogCallback(TrainerCallback): r""" Event called at the end of a training step. """ - if args.should_save: - self._timing(cur_steps=state.global_step) - if self.aborted: control.should_epoch_stop = True control.should_training_stop = True @@ -152,6 +154,7 @@ class LogCallback(TrainerCallback): if not args.should_save: return + self._timing(cur_steps=state.global_step) logs = dict( current_steps=self.cur_steps, total_steps=self.max_steps, @@ -183,8 +186,17 @@ class LogCallback(TrainerCallback): r""" Event called after a prediction step. """ + if self.do_train: + return + + if self.aborted: + sys.exit(0) + + if not args.should_save: + return + eval_dataloader = kwargs.pop("eval_dataloader", None) - if args.should_save and has_length(eval_dataloader) and not self.do_train: + if has_length(eval_dataloader): if self.max_steps == 0: self._reset(max_steps=len(eval_dataloader)) self._create_thread_pool(output_dir=args.output_dir) diff --git a/src/llmtuner/train/tuner.py b/src/llmtuner/train/tuner.py index 6822ffb5..e1a997c1 100644 --- a/src/llmtuner/train/tuner.py +++ b/src/llmtuner/train/tuner.py @@ -23,7 +23,7 @@ if TYPE_CHECKING: logger = get_logger(__name__) -def run_exp(args: Optional[Dict[str, Any]] = None, callbacks: List["TrainerCallback"] = []): +def run_exp(args: Optional[Dict[str, Any]] = None, callbacks: List["TrainerCallback"] = []) -> None: model_args, data_args, training_args, finetuning_args, generating_args = get_train_args(args) callbacks.append(LogCallback(training_args.output_dir)) @@ -43,7 +43,7 @@ def run_exp(args: Optional[Dict[str, Any]] = None, callbacks: List["TrainerCallb raise ValueError("Unknown task.") -def export_model(args: Optional[Dict[str, Any]] = None): +def export_model(args: Optional[Dict[str, Any]] = None) -> None: model_args, data_args, finetuning_args, _ = get_infer_args(args) if model_args.export_dir is None: diff --git a/src/llmtuner/webui/components/eval.py b/src/llmtuner/webui/components/eval.py index 222f9314..60e22bb7 100644 --- a/src/llmtuner/webui/components/eval.py +++ b/src/llmtuner/webui/components/eval.py @@ -48,6 +48,7 @@ def create_eval_tab(engine: "Engine") -> Dict[str, "Component"]: with gr.Row(): cmd_preview_btn = gr.Button() start_btn = gr.Button(variant="primary") + stop_btn = gr.Button(variant="stop") with gr.Row(): resume_btn = gr.Checkbox(visible=False, interactive=False) @@ -61,6 +62,7 @@ def create_eval_tab(engine: "Engine") -> Dict[str, "Component"]: dict( cmd_preview_btn=cmd_preview_btn, start_btn=start_btn, + stop_btn=stop_btn, resume_btn=resume_btn, progress_bar=progress_bar, output_box=output_box, @@ -69,6 +71,7 @@ def create_eval_tab(engine: "Engine") -> Dict[str, "Component"]: cmd_preview_btn.click(engine.runner.preview_eval, input_elems, output_elems, concurrency_limit=None) start_btn.click(engine.runner.run_eval, input_elems, output_elems) + stop_btn.click(engine.runner.set_abort) resume_btn.change(engine.runner.monitor, outputs=output_elems, concurrency_limit=None) dataset_dir.change(list_dataset, [dataset_dir], [dataset], queue=False) diff --git a/src/llmtuner/webui/interface.py b/src/llmtuner/webui/interface.py index 459802f2..b293db90 100644 --- a/src/llmtuner/webui/interface.py +++ b/src/llmtuner/webui/interface.py @@ -68,9 +68,9 @@ def create_web_demo() -> gr.Blocks: return demo -def run_web_ui(): +def run_web_ui() -> None: create_ui().queue().launch() -def run_web_demo(): +def run_web_demo() -> None: create_web_demo().queue().launch() diff --git a/src/llmtuner/webui/locales.py b/src/llmtuner/webui/locales.py index 1c474f34..5bf925b7 100644 --- a/src/llmtuner/webui/locales.py +++ b/src/llmtuner/webui/locales.py @@ -1449,7 +1449,7 @@ ALERTS = { "info_aborting": { "en": "Aborted, wait for terminating...", "ru": "Прервано, ожидание завершения...", - "zh": "训练中断,正在等待线程结束……", + "zh": "训练中断,正在等待进程结束……", }, "info_aborted": { "en": "Ready.", From 6672ad7a83a27d72e1684820a09ea33bca98533c Mon Sep 17 00:00:00 2001 From: hiyouga Date: Sat, 4 May 2024 16:11:18 +0800 Subject: [PATCH 22/33] fix async stream api response Former-commit-id: 941924fdbd69c2529677564af61f9019086ef21f --- src/llmtuner/api/chat.py | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/llmtuner/api/chat.py b/src/llmtuner/api/chat.py index c9c00f16..716dec56 100644 --- a/src/llmtuner/api/chat.py +++ b/src/llmtuner/api/chat.py @@ -38,7 +38,7 @@ ROLE_MAPPING = { } -async def _process_request(request: "ChatCompletionRequest") -> Tuple[List[Dict[str, str]], str, str]: +def _process_request(request: "ChatCompletionRequest") -> Tuple[List[Dict[str, str]], str, str]: if len(request.messages) == 0: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Invalid length") @@ -77,11 +77,23 @@ async def _process_request(request: "ChatCompletionRequest") -> Tuple[List[Dict[ return input_messages, system, tools +def _create_stream_chat_completion_chunk( + completion_id: str, + model: str, + delta: "ChatCompletionMessage", + index: Optional[int] = 0, + finish_reason: Optional["Finish"] = None, +) -> str: + choice_data = ChatCompletionStreamResponseChoice(index=index, delta=delta, finish_reason=finish_reason) + chunk = ChatCompletionStreamResponse(id=completion_id, model=model, choices=[choice_data]) + return jsonify(chunk) + + async def create_chat_completion_response( request: "ChatCompletionRequest", chat_model: "ChatModel" ) -> "ChatCompletionResponse": completion_id = "chatcmpl-{}".format(uuid.uuid4().hex) - input_messages, system, tools = await _process_request(request) + input_messages, system, tools = _process_request(request) responses = await chat_model.achat( input_messages, system, @@ -124,23 +136,11 @@ async def create_chat_completion_response( return ChatCompletionResponse(id=completion_id, model=request.model, choices=choices, usage=usage) -async def _create_stream_chat_completion_chunk( - completion_id: str, - model: str, - delta: "ChatCompletionMessage", - index: Optional[int] = 0, - finish_reason: Optional["Finish"] = None, -) -> str: - choice_data = ChatCompletionStreamResponseChoice(index=index, delta=delta, finish_reason=finish_reason) - chunk = ChatCompletionStreamResponse(id=completion_id, model=model, choices=[choice_data]) - return jsonify(chunk) - - async def create_stream_chat_completion_response( request: "ChatCompletionRequest", chat_model: "ChatModel" ) -> AsyncGenerator[str, None]: completion_id = "chatcmpl-{}".format(uuid.uuid4().hex) - input_messages, system, tools = await _process_request(request) + input_messages, system, tools = _process_request(request) if tools: raise HTTPException(status_code=status.HTTP_400_BAD_REQUEST, detail="Cannot stream function calls.") From 87b9f70ab45e0cb3e54d2815692180cf9f4c711b Mon Sep 17 00:00:00 2001 From: hiyouga Date: Sat, 4 May 2024 16:13:52 +0800 Subject: [PATCH 23/33] remove empty stream response Former-commit-id: e984ba3167d765837cff1030acf59528bcde2f85 --- src/llmtuner/api/chat.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/llmtuner/api/chat.py b/src/llmtuner/api/chat.py index 716dec56..fa2f0d03 100644 --- a/src/llmtuner/api/chat.py +++ b/src/llmtuner/api/chat.py @@ -156,9 +156,10 @@ async def create_stream_chat_completion_response( top_p=request.top_p, max_new_tokens=request.max_tokens, ): - yield _create_stream_chat_completion_chunk( - completion_id=completion_id, model=request.model, delta=ChatCompletionMessage(content=new_token) - ) + if len(new_token) != 0: + yield _create_stream_chat_completion_chunk( + completion_id=completion_id, model=request.model, delta=ChatCompletionMessage(content=new_token) + ) yield _create_stream_chat_completion_chunk( completion_id=completion_id, model=request.model, delta=ChatCompletionMessage(), finish_reason=Finish.STOP From f99ab8606f8782ec56e1c461ea21e70a0b46b0c8 Mon Sep 17 00:00:00 2001 From: hiyouga Date: Sat, 4 May 2024 17:01:21 +0800 Subject: [PATCH 24/33] update readme Former-commit-id: 57a39783d137f6396016f88263092720e2a5a4cc --- README.md | 2 +- README_zh.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 45732220..347ebe7e 100644 --- a/README.md +++ b/README.md @@ -339,7 +339,7 @@ To enable FlashAttention-2 on the Windows platform, you need to install the prec ### Train with LLaMA Board GUI (powered by [Gradio](https://github.com/gradio-app/gradio)) > [!IMPORTANT] -> LLaMA Board GUI only supports training on a single GPU, please use [CLI](#command-line-interface) for distributed training. +> LLaMA Board GUI only supports training on a single GPU, please use [CLI](#train-with-command-line-interface) for distributed training. #### Use local environment diff --git a/README_zh.md b/README_zh.md index 4db1f843..8a2fb79b 100644 --- a/README_zh.md +++ b/README_zh.md @@ -339,7 +339,7 @@ pip install https://github.com/jllllll/bitsandbytes-windows-webui/releases/downl ### 利用 LLaMA Board 可视化界面训练(由 [Gradio](https://github.com/gradio-app/gradio) 驱动) > [!IMPORTANT] -> LLaMA Board 可视化界面目前仅支持单 GPU 训练,请使用[命令行接口](#命令行接口)来进行多 GPU 分布式训练。 +> LLaMA Board 可视化界面目前仅支持单 GPU 训练,请使用[命令行接口](#利用命令行接口训练)来进行多 GPU 分布式训练。 #### 使用本地环境 From 23f9efdf7d89983edd8e6c01fca79c1a6987e2c4 Mon Sep 17 00:00:00 2001 From: hiyouga Date: Sat, 4 May 2024 22:02:25 +0800 Subject: [PATCH 25/33] add cal_ppl script Former-commit-id: 3a666832c119606a8d5baf4694b96569bee18659 --- scripts/cal_flops.py | 12 +++---- scripts/cal_lr.py | 17 ++++------ scripts/cal_ppl.py | 79 +++++++++++++++++++++++++++++++++++++++++++ scripts/length_cdf.py | 9 +++-- 4 files changed, 95 insertions(+), 22 deletions(-) create mode 100644 scripts/cal_ppl.py diff --git a/scripts/cal_flops.py b/scripts/cal_flops.py index 35d98254..19414ce5 100644 --- a/scripts/cal_flops.py +++ b/scripts/cal_flops.py @@ -3,24 +3,22 @@ # Usage: python cal_flops.py --model_name_or_path path_to_model --batch_size 1 --seq_length 512 # Inspired by: https://www.deepspeed.ai/tutorials/flops-profiler/ -from typing import Optional - import fire import torch from deepspeed.accelerator import get_accelerator # type: ignore from deepspeed.profiling.flops_profiler import get_model_profile # type: ignore -from llmtuner import ChatModel +from llmtuner.chat import ChatModel def calculate_flops( model_name_or_path: str, - batch_size: Optional[int] = 1, - seq_length: Optional[int] = 256, - flash_attn: Optional[bool] = False, + batch_size: int = 1, + seq_length: int = 256, + flash_attn: str = "auto", ): with get_accelerator().device(0): - chat_model = ChatModel(dict(model_name_or_path=model_name_or_path, template="vanilla", flash_attn=flash_attn)) + chat_model = ChatModel(dict(model_name_or_path=model_name_or_path, template="empty", flash_attn=flash_attn)) fake_input = torch.ones((batch_size, seq_length), dtype=torch.long, device=chat_model.model.device) input_dict = {"input_ids": fake_input, "labels": fake_input.clone()} flops, macs, params = get_model_profile(chat_model.model, kwargs=input_dict, print_profile=True, detailed=True) diff --git a/scripts/cal_lr.py b/scripts/cal_lr.py index c1c1f7a2..7bf8839d 100644 --- a/scripts/cal_lr.py +++ b/scripts/cal_lr.py @@ -4,7 +4,6 @@ # Inspired by: https://github.com/imoneoi/openchat/blob/master/ochat/training_deepspeed/train.py import math -from typing import Optional import fire import torch @@ -25,12 +24,12 @@ BASE_BS = 4_000_000 # from llama paper def calculate_lr( model_name_or_path: str, batch_size: int, # total batch size, namely (batch size * gradient accumulation * world size) - stage: Optional[str] = "sft", - dataset: Optional[str] = "alpaca_en", - dataset_dir: Optional[str] = "data", - template: Optional[str] = "default", - cutoff_len: Optional[int] = 1024, # i.e. maximum input length during training - is_mistral: Optional[bool] = False, # mistral model uses a smaller learning rate, + stage: str = "sft", + dataset: str = "alpaca_en", + dataset_dir: str = "data", + template: str = "default", + cutoff_len: int = 1024, # i.e. maximum input length during training + is_mistral: bool = False, # mistral model uses a smaller learning rate, ): model_args, data_args, training_args, _, _ = get_train_args( dict( @@ -54,9 +53,7 @@ def calculate_lr( else: raise NotImplementedError - dataloader = DataLoader( - dataset=trainset, batch_size=batch_size, shuffle=True, collate_fn=data_collator, pin_memory=True - ) + dataloader = DataLoader(trainset, batch_size, shuffle=False, collate_fn=data_collator, pin_memory=True) valid_tokens, total_tokens = 0, 0 for batch in tqdm(dataloader): valid_tokens += torch.sum(batch["labels"] != IGNORE_INDEX).item() diff --git a/scripts/cal_ppl.py b/scripts/cal_ppl.py new file mode 100644 index 00000000..bdfc210b --- /dev/null +++ b/scripts/cal_ppl.py @@ -0,0 +1,79 @@ +# coding=utf-8 +# Calculates the ppl of pre-trained models. +# Usage: python cal_flops.py --model_name_or_path path_to_model --batch_size 1 --seq_length 512 + +import json +from typing import Dict + +import fire +import torch +from torch.utils.data import DataLoader +from tqdm import tqdm +from transformers import DataCollatorForLanguageModeling, DataCollatorForSeq2Seq + +from llmtuner.data import get_dataset +from llmtuner.extras.constants import IGNORE_INDEX +from llmtuner.hparams import get_train_args +from llmtuner.model import load_model, load_tokenizer + + +def cal_ppl( + model_name_or_path: str, + batch_size: int = 4, + stage: str = "sft", + dataset: str = "alpaca_en", + dataset_dir: str = "data", + template: str = "default", + cutoff_len: int = 1024, + train_on_prompt: bool = False, +): + model_args, data_args, training_args, finetuning_args, _ = get_train_args( + dict( + stage=stage, + model_name_or_path=model_name_or_path, + dataset=dataset, + dataset_dir=dataset_dir, + template=template, + cutoff_len=cutoff_len, + train_on_prompt=train_on_prompt, + output_dir="dummy_dir", + overwrite_cache=True, + ) + ) + tokenizer_module = load_tokenizer(model_args) + tokenizer = tokenizer_module["tokenizer"] + trainset = get_dataset(model_args, data_args, training_args, stage, **tokenizer_module) + model = load_model(tokenizer, model_args, finetuning_args, is_trainable=False) + if stage == "pt": + data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False) + elif stage == "sft": + data_collator = DataCollatorForSeq2Seq(tokenizer=tokenizer, label_pad_token_id=IGNORE_INDEX) + else: + raise NotImplementedError + + dataloader = DataLoader(trainset, batch_size, shuffle=False, collate_fn=data_collator, pin_memory=True) + criterion = torch.nn.CrossEntropyLoss(reduction="none") + perplexities = [] + batch: Dict[str, "torch.Tensor"] + with torch.no_grad(): + for batch in tqdm(dataloader): + batch = batch.to(model.device) + outputs = model(**batch) + shift_logits: "torch.Tensor" = outputs["logits"][..., :-1, :] + shift_labels: "torch.Tensor" = batch["labels"][..., 1:] + loss_mask = shift_labels != IGNORE_INDEX + flatten_logits = shift_logits.contiguous().view(shift_labels.size(0) * shift_labels.size(1), -1) + flatten_labels = shift_labels.contiguous().view(-1) + token_logps: "torch.Tensor" = criterion(flatten_logits, flatten_labels) + token_logps = token_logps.contiguous().view(shift_logits.size(0), -1) + sentence_logps = (token_logps * loss_mask).sum(-1) / loss_mask.sum(-1) + perplexities.extend(sentence_logps.exp().tolist()) + + with open("ppl.json", "w", encoding="utf-8") as f: + json.dump(perplexities, f, indent=2) + + print("Perplexities have been saved at ppl.json.") + + +if __name__ == "__main__": + fire.Fire(cal_ppl) diff --git a/scripts/length_cdf.py b/scripts/length_cdf.py index 1446f77a..da41a942 100644 --- a/scripts/length_cdf.py +++ b/scripts/length_cdf.py @@ -3,7 +3,6 @@ # Usage: python length_cdf.py --model_name_or_path path_to_model --dataset alpaca_en --template default from collections import defaultdict -from typing import Optional import fire from tqdm import tqdm @@ -15,10 +14,10 @@ from llmtuner.model import load_tokenizer def length_cdf( model_name_or_path: str, - dataset: Optional[str] = "alpaca_en", - dataset_dir: Optional[str] = "data", - template: Optional[str] = "default", - interval: Optional[int] = 1000, + dataset: str = "alpaca_en", + dataset_dir: str = "data", + template: str = "default", + interval: int = 1000, ): model_args, data_args, training_args, _, _ = get_train_args( dict( From 4df26e7439cc8139c9e2df7412e088ec17c2eb99 Mon Sep 17 00:00:00 2001 From: hiyouga Date: Sat, 4 May 2024 22:13:14 +0800 Subject: [PATCH 26/33] update ppl script Former-commit-id: 76a077bdcea127fe1a9ae9e334f443d0ea7c5c7b --- scripts/cal_ppl.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/scripts/cal_ppl.py b/scripts/cal_ppl.py index bdfc210b..6c8c6174 100644 --- a/scripts/cal_ppl.py +++ b/scripts/cal_ppl.py @@ -1,6 +1,6 @@ # coding=utf-8 -# Calculates the ppl of pre-trained models. -# Usage: python cal_flops.py --model_name_or_path path_to_model --batch_size 1 --seq_length 512 +# Calculates the ppl on the dataset of the pre-trained models. +# Usage: python cal_ppl.py --model_name_or_path path_to_model --save_name ppl.json import json from typing import Dict @@ -19,6 +19,7 @@ from llmtuner.model import load_model, load_tokenizer def cal_ppl( model_name_or_path: str, + save_name: str, batch_size: int = 4, stage: str = "sft", dataset: str = "alpaca_en", @@ -69,10 +70,10 @@ def cal_ppl( sentence_logps = (token_logps * loss_mask).sum(-1) / loss_mask.sum(-1) perplexities.extend(sentence_logps.exp().tolist()) - with open("ppl.json", "w", encoding="utf-8") as f: + with open(save_name, "w", encoding="utf-8") as f: json.dump(perplexities, f, indent=2) - print("Perplexities have been saved at ppl.json.") + print("Perplexities have been saved at {}.".format(save_name)) if __name__ == "__main__": From 5f8d83b63029561856e239cca06926fb7fbd7047 Mon Sep 17 00:00:00 2001 From: hiyouga Date: Sat, 4 May 2024 22:35:31 +0800 Subject: [PATCH 27/33] add avg ppl Former-commit-id: 25aeaae51b6d08a747e222bbcb27e75c4d56a856 --- scripts/cal_ppl.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/scripts/cal_ppl.py b/scripts/cal_ppl.py index 6c8c6174..06c2a43b 100644 --- a/scripts/cal_ppl.py +++ b/scripts/cal_ppl.py @@ -54,6 +54,7 @@ def cal_ppl( dataloader = DataLoader(trainset, batch_size, shuffle=False, collate_fn=data_collator, pin_memory=True) criterion = torch.nn.CrossEntropyLoss(reduction="none") + total_ppl = 0 perplexities = [] batch: Dict[str, "torch.Tensor"] with torch.no_grad(): @@ -68,11 +69,13 @@ def cal_ppl( token_logps: "torch.Tensor" = criterion(flatten_logits, flatten_labels) token_logps = token_logps.contiguous().view(shift_logits.size(0), -1) sentence_logps = (token_logps * loss_mask).sum(-1) / loss_mask.sum(-1) + total_ppl += sentence_logps.exp().sum().item() perplexities.extend(sentence_logps.exp().tolist()) with open(save_name, "w", encoding="utf-8") as f: json.dump(perplexities, f, indent=2) + print("Average perplexity is {:.2f}".format(total_ppl / len(perplexities))) print("Perplexities have been saved at {}.".format(save_name)) From df43fbb0292ea7abfdbc0b7c223a623b6eaac23d Mon Sep 17 00:00:00 2001 From: hiyouga Date: Sat, 4 May 2024 23:05:17 +0800 Subject: [PATCH 28/33] update scripts Former-commit-id: c1a53a0debf98be26f0f64d710a13c3b25140f75 --- scripts/cal_lr.py | 3 ++- scripts/cal_ppl.py | 35 +++++++++++++++++++++++++++++++++-- 2 files changed, 35 insertions(+), 3 deletions(-) diff --git a/scripts/cal_lr.py b/scripts/cal_lr.py index 7bf8839d..dd864162 100644 --- a/scripts/cal_lr.py +++ b/scripts/cal_lr.py @@ -4,6 +4,7 @@ # Inspired by: https://github.com/imoneoi/openchat/blob/master/ochat/training_deepspeed/train.py import math +from typing import Literal import fire import torch @@ -24,7 +25,7 @@ BASE_BS = 4_000_000 # from llama paper def calculate_lr( model_name_or_path: str, batch_size: int, # total batch size, namely (batch size * gradient accumulation * world size) - stage: str = "sft", + stage: Literal["pt", "sft"] = "sft", dataset: str = "alpaca_en", dataset_dir: str = "data", template: str = "default", diff --git a/scripts/cal_ppl.py b/scripts/cal_ppl.py index 06c2a43b..2e74c70a 100644 --- a/scripts/cal_ppl.py +++ b/scripts/cal_ppl.py @@ -3,7 +3,8 @@ # Usage: python cal_ppl.py --model_name_or_path path_to_model --save_name ppl.json import json -from typing import Dict +from dataclasses import dataclass +from typing import Any, Dict, Literal, Sequence import fire import torch @@ -17,11 +18,37 @@ from llmtuner.hparams import get_train_args from llmtuner.model import load_model, load_tokenizer +@dataclass +class PairwiseDataCollatorWithPadding(DataCollatorForSeq2Seq): + r""" + Data collator for pairwise data. + """ + + train_on_prompt: bool = False + + def __call__(self, features: Sequence[Dict[str, Any]]) -> Dict[str, torch.Tensor]: + r""" + Pads batched data to the longest sequence in the batch. + + We generate 2 * n examples where the first n examples represent chosen examples and + the last n examples represent rejected examples. + """ + chosen_features = [] + for feature in features: + prompt_len, answer_len = len(feature["prompt_ids"]), len(feature["chosen_ids"]) + input_ids = feature["prompt_ids"] + feature["chosen_ids"] + attention_mask = [1] * (prompt_len + answer_len) + labels = input_ids if self.train_on_prompt else [IGNORE_INDEX] * prompt_len + feature["chosen_ids"] + chosen_features.append({"input_ids": input_ids, "attention_mask": attention_mask, "labels": labels}) + + return super().__call__(chosen_features) + + def cal_ppl( model_name_or_path: str, save_name: str, batch_size: int = 4, - stage: str = "sft", + stage: Literal["pt", "sft", "rm"] = "sft", dataset: str = "alpaca_en", dataset_dir: str = "data", template: str = "default", @@ -49,6 +76,10 @@ def cal_ppl( data_collator = DataCollatorForLanguageModeling(tokenizer=tokenizer, mlm=False) elif stage == "sft": data_collator = DataCollatorForSeq2Seq(tokenizer=tokenizer, label_pad_token_id=IGNORE_INDEX) + elif stage == "rm": + data_collator = PairwiseDataCollatorWithPadding( + tokenizer=tokenizer, label_pad_token_id=IGNORE_INDEX, train_on_prompt=train_on_prompt + ) else: raise NotImplementedError From 9bbb5c846dab88a9046c228cde2f68ae54839b46 Mon Sep 17 00:00:00 2001 From: hiyouga Date: Sun, 5 May 2024 00:17:54 +0800 Subject: [PATCH 29/33] update webui Former-commit-id: af596988b1e8988fd3f1376a8e090ed8b80ee810 --- scripts/cal_ppl.py | 4 ++- src/llmtuner/webui/components/chatbot.py | 6 ++-- src/llmtuner/webui/components/eval.py | 10 +++--- src/llmtuner/webui/components/export.py | 2 +- src/llmtuner/webui/components/train.py | 42 ++++++++++++------------ 5 files changed, 33 insertions(+), 31 deletions(-) diff --git a/scripts/cal_ppl.py b/scripts/cal_ppl.py index 2e74c70a..9eebc57d 100644 --- a/scripts/cal_ppl.py +++ b/scripts/cal_ppl.py @@ -4,7 +4,7 @@ import json from dataclasses import dataclass -from typing import Any, Dict, Literal, Sequence +from typing import Any, Dict, Literal, Optional, Sequence import fire import torch @@ -53,6 +53,7 @@ def cal_ppl( dataset_dir: str = "data", template: str = "default", cutoff_len: int = 1024, + max_samples: Optional[int] = None, train_on_prompt: bool = False, ): model_args, data_args, training_args, finetuning_args, _ = get_train_args( @@ -63,6 +64,7 @@ def cal_ppl( dataset_dir=dataset_dir, template=template, cutoff_len=cutoff_len, + max_samples=max_samples, train_on_prompt=train_on_prompt, output_dir="dummy_dir", overwrite_cache=True, diff --git a/src/llmtuner/webui/components/chatbot.py b/src/llmtuner/webui/components/chatbot.py index 0a55460c..f83694b1 100644 --- a/src/llmtuner/webui/components/chatbot.py +++ b/src/llmtuner/webui/components/chatbot.py @@ -36,9 +36,9 @@ def create_chat_box( submit_btn = gr.Button(variant="primary") with gr.Column(scale=1): - max_new_tokens = gr.Slider(8, 4096, value=512, step=1) - top_p = gr.Slider(0.01, 1.0, value=0.7, step=0.01) - temperature = gr.Slider(0.01, 1.5, value=0.95, step=0.01) + max_new_tokens = gr.Slider(minimum=8, maximum=4096, value=512, step=1) + top_p = gr.Slider(minimum=0.01, maximum=1.0, value=0.7, step=0.01) + temperature = gr.Slider(minimum=0.01, maximum=1.5, value=0.95, step=0.01) clear_btn = gr.Button() tools.input(check_json_schema, inputs=[tools, engine.manager.get_elem_by_id("top.lang")]) diff --git a/src/llmtuner/webui/components/eval.py b/src/llmtuner/webui/components/eval.py index 60e22bb7..8b70283b 100644 --- a/src/llmtuner/webui/components/eval.py +++ b/src/llmtuner/webui/components/eval.py @@ -28,18 +28,18 @@ def create_eval_tab(engine: "Engine") -> Dict[str, "Component"]: elem_dict.update(dict(dataset_dir=dataset_dir, dataset=dataset, **preview_elems)) with gr.Row(): - cutoff_len = gr.Slider(value=1024, minimum=4, maximum=65536, step=1) + cutoff_len = gr.Slider(minimum=4, maximum=65536, value=1024, step=1) max_samples = gr.Textbox(value="100000") - batch_size = gr.Slider(value=2, minimum=1, maximum=1024, step=1) + batch_size = gr.Slider(minimum=1, maximum=1024, value=2, step=1) predict = gr.Checkbox(value=True) input_elems.update({cutoff_len, max_samples, batch_size, predict}) elem_dict.update(dict(cutoff_len=cutoff_len, max_samples=max_samples, batch_size=batch_size, predict=predict)) with gr.Row(): - max_new_tokens = gr.Slider(10, 2048, value=128, step=1) - top_p = gr.Slider(0.01, 1, value=0.7, step=0.01) - temperature = gr.Slider(0.01, 1.5, value=0.95, step=0.01) + max_new_tokens = gr.Slider(minimum=8, maximum=4096, value=512, step=1) + top_p = gr.Slider(minimum=0.01, maximum=1, value=0.7, step=0.01) + temperature = gr.Slider(minimum=0.01, maximum=1.5, value=0.95, step=0.01) output_dir = gr.Textbox() input_elems.update({max_new_tokens, top_p, temperature, output_dir}) diff --git a/src/llmtuner/webui/components/export.py b/src/llmtuner/webui/components/export.py index 64273882..134b77e0 100644 --- a/src/llmtuner/webui/components/export.py +++ b/src/llmtuner/webui/components/export.py @@ -85,7 +85,7 @@ def save_model( def create_export_tab(engine: "Engine") -> Dict[str, "Component"]: with gr.Row(): - export_size = gr.Slider(value=1, minimum=1, maximum=100, step=1) + export_size = gr.Slider(minimum=1, maximum=100, value=1, step=1) export_quantization_bit = gr.Dropdown(choices=["none", "8", "4", "3", "2"], value="none") export_quantization_dataset = gr.Textbox(value="data/c4_demo.json") export_device = gr.Radio(choices=["cpu", "cuda"], value="cpu") diff --git a/src/llmtuner/webui/components/train.py b/src/llmtuner/webui/components/train.py index 857c56ac..5cde660c 100644 --- a/src/llmtuner/webui/components/train.py +++ b/src/llmtuner/webui/components/train.py @@ -52,10 +52,10 @@ def create_train_tab(engine: "Engine") -> Dict[str, "Component"]: ) with gr.Row(): - cutoff_len = gr.Slider(value=1024, minimum=4, maximum=65536, step=1) - batch_size = gr.Slider(value=2, minimum=1, maximum=1024, step=1) - gradient_accumulation_steps = gr.Slider(value=8, minimum=1, maximum=1024, step=1) - val_size = gr.Slider(value=0, minimum=0, maximum=1, step=0.001) + cutoff_len = gr.Slider(minimum=4, maximum=65536, value=1024, step=1) + batch_size = gr.Slider(minimum=1, maximum=1024, value=2, step=1) + gradient_accumulation_steps = gr.Slider(minimum=1, maximum=1024, value=8, step=1) + val_size = gr.Slider(minimum=0, maximum=1, value=0, step=0.001) lr_scheduler_type = gr.Dropdown(choices=[scheduler.value for scheduler in SchedulerType], value="cosine") input_elems.update({cutoff_len, batch_size, gradient_accumulation_steps, val_size, lr_scheduler_type}) @@ -71,10 +71,10 @@ def create_train_tab(engine: "Engine") -> Dict[str, "Component"]: with gr.Accordion(open=False) as extra_tab: with gr.Row(): - logging_steps = gr.Slider(value=5, minimum=5, maximum=1000, step=5) - save_steps = gr.Slider(value=100, minimum=10, maximum=5000, step=10) - warmup_steps = gr.Slider(value=0, minimum=0, maximum=5000, step=1) - neftune_alpha = gr.Slider(value=0, minimum=0, maximum=10, step=0.1) + logging_steps = gr.Slider(minimum=1, maximum=1000, value=5, step=5) + save_steps = gr.Slider(minimum=10, maximum=5000, value=100, step=10) + warmup_steps = gr.Slider(minimum=0, maximum=5000, value=0, step=1) + neftune_alpha = gr.Slider(minimum=0, maximum=10, value=0, step=0.1) optim = gr.Textbox(value="adamw_torch") with gr.Row(): @@ -124,7 +124,7 @@ def create_train_tab(engine: "Engine") -> Dict[str, "Component"]: with gr.Accordion(open=False) as freeze_tab: with gr.Row(): - num_layer_trainable = gr.Slider(value=3, minimum=1, maximum=128, step=1) + num_layer_trainable = gr.Slider(minimum=1, maximum=128, value=2, step=1) name_module_trainable = gr.Textbox(value="all") input_elems.update({num_layer_trainable, name_module_trainable}) @@ -136,10 +136,10 @@ def create_train_tab(engine: "Engine") -> Dict[str, "Component"]: with gr.Accordion(open=False) as lora_tab: with gr.Row(): - lora_rank = gr.Slider(value=8, minimum=1, maximum=1024, step=1) - lora_alpha = gr.Slider(value=16, minimum=1, maximum=2048, step=1) - lora_dropout = gr.Slider(value=0, minimum=0, maximum=1, step=0.01) - loraplus_lr_ratio = gr.Slider(value=0, minimum=0, maximum=64, step=0.01) + lora_rank = gr.Slider(minimum=1, maximum=1024, value=8, step=1) + lora_alpha = gr.Slider(minimum=1, maximum=2048, value=16, step=1) + lora_dropout = gr.Slider(minimum=0, maximum=1, value=0, step=0.01) + loraplus_lr_ratio = gr.Slider(minimum=0, maximum=64, value=0, step=0.01) create_new_adapter = gr.Checkbox() with gr.Row(): @@ -180,9 +180,9 @@ def create_train_tab(engine: "Engine") -> Dict[str, "Component"]: with gr.Accordion(open=False) as rlhf_tab: with gr.Row(): - dpo_beta = gr.Slider(value=0.1, minimum=0, maximum=1, step=0.01) - dpo_ftx = gr.Slider(value=0, minimum=0, maximum=10, step=0.01) - orpo_beta = gr.Slider(value=0.1, minimum=0, maximum=1, step=0.01) + dpo_beta = gr.Slider(minimum=0, maximum=1, value=0.1, step=0.01) + dpo_ftx = gr.Slider(minimum=0, maximum=10, value=0, step=0.01) + orpo_beta = gr.Slider(minimum=0, maximum=1, value=0.1, step=0.01) reward_model = gr.Dropdown(multiselect=True, allow_custom_value=True) input_elems.update({dpo_beta, dpo_ftx, orpo_beta, reward_model}) @@ -193,9 +193,9 @@ def create_train_tab(engine: "Engine") -> Dict[str, "Component"]: with gr.Accordion(open=False) as galore_tab: with gr.Row(): use_galore = gr.Checkbox() - galore_rank = gr.Slider(value=16, minimum=1, maximum=1024, step=1) - galore_update_interval = gr.Slider(value=200, minimum=1, maximum=1024, step=1) - galore_scale = gr.Slider(value=0.25, minimum=0, maximum=1, step=0.01) + galore_rank = gr.Slider(minimum=1, maximum=1024, value=16, step=1) + galore_update_interval = gr.Slider(minimum=1, maximum=1024, value=200, step=1) + galore_scale = gr.Slider(minimum=0, maximum=1, value=0.25, step=0.01) galore_target = gr.Textbox(value="all") input_elems.update({use_galore, galore_rank, galore_update_interval, galore_scale, galore_target}) @@ -215,8 +215,8 @@ def create_train_tab(engine: "Engine") -> Dict[str, "Component"]: use_badam = gr.Checkbox() badam_mode = gr.Dropdown(choices=["layer", "ratio"], value="layer") badam_switch_mode = gr.Dropdown(choices=["ascending", "descending", "random", "fixed"], value="ascending") - badam_switch_interval = gr.Slider(value=50, minimum=1, maximum=1024, step=1) - badam_update_ratio = gr.Slider(value=0.05, minimum=0, maximum=1, step=0.01) + badam_switch_interval = gr.Slider(minimum=1, maximum=1024, value=50, step=1) + badam_update_ratio = gr.Slider(minimum=0, maximum=1, value=0.05, step=0.01) input_elems.update({use_badam, badam_mode, badam_switch_mode, badam_switch_interval, badam_update_ratio}) elem_dict.update( From a510ea9390ef8e69f266ba8d246f023f00486800 Mon Sep 17 00:00:00 2001 From: hiyouga Date: Sun, 5 May 2024 00:53:07 +0800 Subject: [PATCH 30/33] fix eval scripts Former-commit-id: 177604fb6bf09fdfc05216b5f1e5e1c95fcc8d43 --- evaluation/ceval/ceval.py | 14 ++-- evaluation/cmmlu/cmmlu.py | 134 +++++++++++++++++++------------------- evaluation/mmlu/mmlu.py | 12 +--- 3 files changed, 74 insertions(+), 86 deletions(-) diff --git a/evaluation/ceval/ceval.py b/evaluation/ceval/ceval.py index 33005de3..4111d6b4 100644 --- a/evaluation/ceval/ceval.py +++ b/evaluation/ceval/ceval.py @@ -19,7 +19,7 @@ import pandas as pd _CITATION = """\ @article{huang2023ceval, - title={C-Eval: A Multi-Level Multi-Discipline Chinese Evaluation Suite for Foundation Models}, + title={C-Eval: A Multi-Level Multi-Discipline Chinese Evaluation Suite for Foundation Models}, author={Huang, Yuzhen and Bai, Yuzhuo and Zhu, Zhihao and Zhang, Junlei and Zhang, Jinghan and Su, Tangjun and Liu, Junteng and Lv, Chuancheng and Zhang, Yikai and Lei, Jiayi and Fu, Yao and Sun, Maosong and He, Junxian}, journal={arXiv preprint arXiv:2305.08322}, year={2023} @@ -133,25 +133,19 @@ class Ceval(datasets.GeneratorBasedBuilder): datasets.SplitGenerator( name=datasets.Split.TEST, gen_kwargs={ - "filepath": os.path.join( - data_dir, "test", f"{task_name}_test.csv" - ), + "filepath": os.path.join(data_dir, "test", f"{task_name}_test.csv"), }, ), datasets.SplitGenerator( name=datasets.Split.VALIDATION, gen_kwargs={ - "filepath": os.path.join( - data_dir, "val", f"{task_name}_val.csv" - ), + "filepath": os.path.join(data_dir, "val", f"{task_name}_val.csv"), }, ), datasets.SplitGenerator( name=datasets.Split.TRAIN, gen_kwargs={ - "filepath": os.path.join( - data_dir, "dev", f"{task_name}_dev.csv" - ), + "filepath": os.path.join(data_dir, "dev", f"{task_name}_dev.csv"), }, ), ] diff --git a/evaluation/cmmlu/cmmlu.py b/evaluation/cmmlu/cmmlu.py index 62096203..37efb328 100644 --- a/evaluation/cmmlu/cmmlu.py +++ b/evaluation/cmmlu/cmmlu.py @@ -37,73 +37,73 @@ _LICENSE = "Creative Commons Attribution-NonCommercial-ShareAlike 4.0 Internatio _URL = "cmmlu.zip" task_list = [ - 'agronomy', - 'anatomy', - 'ancient_chinese', - 'arts', - 'astronomy', - 'business_ethics', - 'chinese_civil_service_exam', - 'chinese_driving_rule', - 'chinese_food_culture', - 'chinese_foreign_policy', - 'chinese_history', - 'chinese_literature', - 'chinese_teacher_qualification', - 'clinical_knowledge', - 'college_actuarial_science', - 'college_education', - 'college_engineering_hydrology', - 'college_law', - 'college_mathematics', - 'college_medical_statistics', - 'college_medicine', - 'computer_science', - 'computer_security', - 'conceptual_physics', - 'construction_project_management', - 'economics', - 'education', - 'electrical_engineering', - 'elementary_chinese', - 'elementary_commonsense', - 'elementary_information_and_technology', - 'elementary_mathematics', - 'ethnology', - 'food_science', - 'genetics', - 'global_facts', - 'high_school_biology', - 'high_school_chemistry', - 'high_school_geography', - 'high_school_mathematics', - 'high_school_physics', - 'high_school_politics', - 'human_sexuality', - 'international_law', - 'journalism', - 'jurisprudence', - 'legal_and_moral_basis', - 'logical', - 'machine_learning', - 'management', - 'marketing', - 'marxist_theory', - 'modern_chinese', - 'nutrition', - 'philosophy', - 'professional_accounting', - 'professional_law', - 'professional_medicine', - 'professional_psychology', - 'public_relations', - 'security_study', - 'sociology', - 'sports_science', - 'traditional_chinese_medicine', - 'virology', - 'world_history', - 'world_religions', + "agronomy", + "anatomy", + "ancient_chinese", + "arts", + "astronomy", + "business_ethics", + "chinese_civil_service_exam", + "chinese_driving_rule", + "chinese_food_culture", + "chinese_foreign_policy", + "chinese_history", + "chinese_literature", + "chinese_teacher_qualification", + "clinical_knowledge", + "college_actuarial_science", + "college_education", + "college_engineering_hydrology", + "college_law", + "college_mathematics", + "college_medical_statistics", + "college_medicine", + "computer_science", + "computer_security", + "conceptual_physics", + "construction_project_management", + "economics", + "education", + "electrical_engineering", + "elementary_chinese", + "elementary_commonsense", + "elementary_information_and_technology", + "elementary_mathematics", + "ethnology", + "food_science", + "genetics", + "global_facts", + "high_school_biology", + "high_school_chemistry", + "high_school_geography", + "high_school_mathematics", + "high_school_physics", + "high_school_politics", + "human_sexuality", + "international_law", + "journalism", + "jurisprudence", + "legal_and_moral_basis", + "logical", + "machine_learning", + "management", + "marketing", + "marxist_theory", + "modern_chinese", + "nutrition", + "philosophy", + "professional_accounting", + "professional_law", + "professional_medicine", + "professional_psychology", + "public_relations", + "security_study", + "sociology", + "sports_science", + "traditional_chinese_medicine", + "virology", + "world_history", + "world_religions", ] diff --git a/evaluation/mmlu/mmlu.py b/evaluation/mmlu/mmlu.py index 9f1bd101..f3218c38 100644 --- a/evaluation/mmlu/mmlu.py +++ b/evaluation/mmlu/mmlu.py @@ -136,25 +136,19 @@ class MMLU(datasets.GeneratorBasedBuilder): datasets.SplitGenerator( name=datasets.Split.TEST, gen_kwargs={ - "filepath": os.path.join( - data_dir, "data", "test", f"{task_name}_test.csv" - ), + "filepath": os.path.join(data_dir, "data", "test", f"{task_name}_test.csv"), }, ), datasets.SplitGenerator( name=datasets.Split.VALIDATION, gen_kwargs={ - "filepath": os.path.join( - data_dir, "data", "val", f"{task_name}_val.csv" - ), + "filepath": os.path.join(data_dir, "data", "val", f"{task_name}_val.csv"), }, ), datasets.SplitGenerator( name=datasets.Split.TRAIN, gen_kwargs={ - "filepath": os.path.join( - data_dir, "data", "dev", f"{task_name}_dev.csv" - ), + "filepath": os.path.join(data_dir, "data", "dev", f"{task_name}_dev.csv"), }, ), ] From fa9c7eb48ec3e3df0ccef8f6c7d378aa6d0c91ec Mon Sep 17 00:00:00 2001 From: hiyouga Date: Sun, 5 May 2024 02:44:35 +0800 Subject: [PATCH 31/33] add version and help to cli Former-commit-id: bd095eeb73b79e55d8a7e3e3dfbaf144c62622e2 --- src/llmtuner/cli.py | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/src/llmtuner/cli.py b/src/llmtuner/cli.py index 1b5bd658..f2619ab9 100644 --- a/src/llmtuner/cli.py +++ b/src/llmtuner/cli.py @@ -1,6 +1,7 @@ import sys from enum import Enum, unique +from . import __version__ from .api.app import run_api from .chat.chat_model import run_chat from .eval.evaluator import run_eval @@ -8,6 +9,19 @@ from .train.tuner import export_model, run_exp from .webui.interface import run_web_demo, run_web_ui +USAGE = """ +Usage: + llamafactory-cli api -h: launch an API server + llamafactory-cli chat -h: launch a chat interface in CLI + llamafactory-cli eval -h: do evaluation + llamafactory-cli export -h: merge LoRA adapters and export model + llamafactory-cli train -h: do training + llamafactory-cli webchat -h: launch a chat interface in Web UI + llamafactory-cli webui: launch LlamaBoard + llamafactory-cli version: show version info +""" + + @unique class Command(str, Enum): API = "api" @@ -17,6 +31,8 @@ class Command(str, Enum): TRAIN = "train" WEBDEMO = "webchat" WEBUI = "webui" + VERSION = "version" + HELP = "help" def main(): @@ -35,5 +51,9 @@ def main(): run_web_demo() elif command == Command.WEBUI: run_web_ui() + elif command == Command.VERSION: + print("Welcome to LLaMA Factory, version {}".format(__version__)) + elif command == Command.HELP: + print(USAGE) else: raise NotImplementedError("Unknown command: {}".format(command)) From 8fcfeeffcff5cd13993905a522f16251ffb46fb7 Mon Sep 17 00:00:00 2001 From: codingma Date: Sun, 5 May 2024 15:31:47 +0800 Subject: [PATCH 32/33] update wechat Former-commit-id: 845d5acd03141dbb6df7d03b640639e7497d4c49 --- assets/wechat.jpg | Bin 190885 -> 125594 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/assets/wechat.jpg b/assets/wechat.jpg index dccdbe292dfc30142da7a93b3c7fe87b956a5eab..0e17295a1fcbc986815ea1b1f7c0dfc87667ce71 100644 GIT binary patch literal 125594 zcmeFZbyQp5wm%x2qQ#v+(L#$m!P??bqy>tFwm@-faSPDm4h4!7r&zJ#4#nNw2~s>k zgN2uV&$-`w-yP$yIxqEni z{1g}z91|Y}+;QxrS zzX|(Kx)uOW0oZ>HJZx+{B0M}iB2psMAf+PxJ*eoZ{u=cE91s2)9KXk-zsDWwL(p(= zaPaZ)$?)*Vc!0D(p8xf@TSAHDle>8U0R|dMm@r5HGJvaZVVs%h|DKt|cccI2#XseM z_-^dqy!e|J_WOc`^NOUi{6AnSb-*Z(jVt_#!9q zStx$}>dS=iLM62T={5_v(7l);520K?*Rsr_WRAkz>@NYo|FEpOx`1iQ`ufPElJC!|jHg{vbOT`z{GMdtUtXwn_sOD)c~TrYD)Vx|=} zTKLtw-tMF`BNyxILU=IgAn^S=K%nh(@nEcFb{OVksdouPS|K;q5}X+J;}=8HMopro zdj`V&=yI5j^3J2J$)v3Xxk7!Ywbd6_MUEKJ{tlb2n zo2zjCQ=Cwe179OlMb>3(ahH~=_P8^WO!nOY(m5`}Y@9N95w^q!)F-<#U&@MPs@`0) zYI=m_5_wP@mwtjwjMcq~oryX--Ip+rs3{bxS%neVWGN=oA15U1-T^e@hL=i9LSmC@ z6d%-G-vPpO1?KQw!=3kz%b8Y9;ax^>^(v7RovyBM0fAE4gN)%8XDgbeuSg5ic*Km9 ziJNIQ&(_e?%6#cYBc8PBC;^cTrJLpKu%|Kfmc=ikjL4ATW7uPe9rcxxR2|0)qT@{xU|Qg} zzeo#fp8h1b+j{@$c`wV1U7iddT0kw0^weOuS8_HPQ!jnG&B%@TI^DO4<)0o)L2~#D zVSCKe`NvIZX#P=EInKg#U$;M^haR~`jcwln7)y~&7i|-d$~_z$1Wa@bQ#fmYKmH1h`0j?%8uEGapv-6hj9Y_z{I%wgRyb_-Vx#o(s7CR z%O<_$4iKGxBNLOURwz&Y;=YzVIY5sM+ImSYEOWIA`BGX_&&zNJ=*=p;1I%FDbm5X5 zr$=4e=WxUswodBJ>8iwAY6Y!Qbb0ILw+wvIxHg&8o2M0wUpsR+x5*Y2FFJhB$Xzl2AHe}p6Ds#ep*i!O_~-E3ZlAFN(>Bl&sZsfPozv@11yZ^&FH zuULX}uVPc=xQd5|g-z*b&v3kCE9YKAoGzQfx`1!>oYgYA;f74}yeUi3txLsD*GVlwL2Bur#w#ZI!LcsaP4DY&Oz0d(JXFRD>fCNmu4Bdou zS8#~_{nB6?NsI<|9VNraOHb{tZd%4WK#4VM;R(AQCZ`NvdzbrJ+PHhb7UTe3UzzQd zQ`?T(bhYy41bAGeTM=i^fRVH+PLkEC7bjy|PvU*R=xSMrw!!^fFrD06^BNHY`!%_YfC#0B~njpw#^34J5hLvw;qXIMe94Ou-I!){pd*)LXa6v84fPsy-Ny(G?MQ6>EGFX47fLkn{tXFul;=#|j+>^c z3t`hil?=^AnUt5u8%I{^9#eyRMbD%1Wa;=6rIfacvRy|DgeT8EoG<+&A1lC{7?882 z)BWSz=9p-8{&g7!@JPDuq_MA+(VssAsJp=2cNH87>qxE+?*MyxSUM~%@d^%+!ZKBh zC22-QX29{LAXRbX^fzHFo%U6)nKArzW5%`U3`%@A0PSK`s$zoblZuQ|!JC3WJyMR~ zh`BY@xArW!1A#bor76^dTAZeV81zoyq(XdUX3jvZiUo>I(Tg#IL9L8O#L!Fa4zB4?b6(g3m_#jg6m+XlU(2At&D|8O1u=|7FmoJC~dyBX0Z zLWGpRvcsO@ac2Adi79jU2UF<(B57a$)0Gy>vypc`CVv+(%J*G{bcro0(9MN|o8D}_ zmYb|o!f?-%Z90K0WPV-1KV;1#llUv|;VHu#HyTQ+vteQx11N8mayGWA_B**@MVbC) z*&E6!jhjwpln0`%dL&J{dmjHi&YE8Uqd(Bz25hFP7}O>B6XIUaQtCN1D@?9-S#q2n z(BjvC(6e)I-~5cRLQ}K{i<=|DT#a_L5s7~n_Pi&UCy!aC(Wh`H#hHpUHh8Q|DF0&TYN`&a4iQn$s;YGNYG7lzfjYDJxn zZBbKOl{exkPpecD&M@`r`m(L%-^cx=n=FPznk~AUfYUY|m|q}wio-02b?5HiNWlgf z84R;%2Gm%VGS1x~k)G=Bm+8h_7}&OL_C93{&DFkUq5F{DYOrfPht@?-s{%0K%pnha zT^iJblzIV2zN_E;Web5$$pE)3P5-dZ{c`{YW~uKrQabHcCZ;stZ=ozWdTU-;u{E%M zT`wFe)D-|?jqv~R-Zf3z#68KRf)rGv`3W5Cu~l!wq!Di3PH&`q2gss5yzzwJ0p3<( zGew+=9e|_QUgQlpd%RNCDSmA7;%hnl=Hi7sdAUMkFAiqfyJk7QHGNNktrp2q_O#_< zN)A#2UzfA#Ft)mii1eJW;{=_DRng#P8oAjjk@wWc53vbfS&obIEyk-Svc0HdY%caF z=1%*uZLH*Vme-}2;=0@F_RN$k-dH~Ky;Ir?RlG#Z6Sf{;tYh0#i%J= zq2+}SVXaQsEF5BOJ4WYu-nNe;`_t(CN181}O@E|+o0*$*VUOR3GN?N_xtCuSo(G6j zblHLt0ppTnPGp;@emEtH;$!<`sfBx@u5Nb#^8nU3KIwO?0Wx@<@!g_^U%DLVm>85n z(>|y1M$GXsC-fQGTve3)M%`3gVe;>`hD@JJg$8%x*Z8!Wskw;@y8>yK2Ks6veA(;2 zsU=4G(SV$E(q};*2Y~#idBX$ej{9y}?%%GoT^D-%SDe1n-2p<}rbV%-sv`UxiJ&i6 zYu!sWc-fb4^a#HXj`vcWw66u{m(UM_hozzwH5hH)m;O4Pk`8ui&$|PB+5-bm2&ko` zO|{%vI+J3RQr=}HzhSbC2NBHV*=R3z(WdIoN}hyStF^bAJhO0j&~2Q4l)iOLT=}C{ zljlPWK>XHb^!q>5!6ssYHAP83-)G-&Z@y9TpK-xLR=7bT%3%K$x&E1~`_Expm`6sp zcYuFn+s+gtCXk*5B(PWK>3fb!kf7=ljfXF^R9R2UPCu0xP}Dx}I}h5g(k|I@omy?FiSL&gk~@#;oxaC(u^Ih-hAOtL8H zgiY@A;QGjgkf)?_+Z@=&zNxBmw!M}GCP)t}AJS*`eOOrzr#(*3cY8yzJeU2t zPE1B6FgzMmVR`EG3?d^^XjkwH6d`h7xQg^Ifpw-j-zJzQO>9{*5&llX%j{A(Bym!#Acs05<#vG%)^Tk+y!}Z@?kr zuP6zqvB8y7FQBIjVw_r6chI!?Sgq?pjFh$+uJ!>hyqq!9vA+*JD-AYLPSS3Q5}7xv zi$603yFs!S7u3%OoZ{0mem3yCuu!H74D9rOHk&)u3$op)ooL6uXh>;?vj@`iX%yhL z^}qN1d4veh+;lY?BG`!KU#xqqnE0#qykhY- zeO_SrR&ftpoYnPchWmVKzFBq${BX^UyG&B$*_#VH=uDnhVzQl!L;c}`lNgs`-ur&b zQ%XEd-Q=B#wlZ7oUcXM$jJyvrbE5o)^(oJY7gB7j^Gxw=m8JHQ8sWIAm4suyOWaB? z4b!F^du+#&A1LK>p_@LEn2~2V_@w(&kV%ZOdfl_7c@x{vgNT}d;G$GThSY{{pvJC? z?l4_Kx3bWsen2Had-5E3;K4ETI*k2xE*?`)qu6-y)!c3NoEsnaZ|n~`PgKa-aPV1x zc`%L$f_Fj155UZ%FpU6uzN`)x>154~6LXuYR)57DM))goxb4Hu-bi(^O{4N)691RT zj@KC7iY7bYTe~+{IaM({dH1!YKQ#A)2dJ5% zaL-pQgeT^AX|Z8EAlt%bNU z$R?CVCns46&x3^p7$`||t>B(^6_nt)H8aS3t5Na+!+O`Bd{d4^x{l(&bs1y4s-At) zo(0R%9drkfI4&L#!|LkVSG~^ruxUD(E~7SH;S~QaC^Ua*xK^)bAv7W~=cM$g(;#3- z9B`_QGyQdS7Aq=QxT|fTH}ms8PRZL#^#W_hg)$F{=1&{ZEA=m6g(tP6KkV)CDBJ;?wB7yd|$4SW)&Zn8N^l9f07l*-VX*akmqj34VgiK3c+scRwAqQpb` zN?}`@m@#309-Q-dJEhGpc>O$PBFKofG$1=m`SBPf1<@iEmzjD8uu1 z`%U@%iVRs6nkg~ne7Y2tzkGY6_V$@LPR7sdhFGTBIOW{g#^=gnT?D8slGV|8PMHXr zW*Tj<`dz5()VVg1Nk?{G5?wMr`XuESXdsj(&kq!HU%^p{W4=Uzk&`77gA^(N@v;{j zfrfoHFjl)B(n`TDSAE_1l8BfCgE3*ys?5#22tWT5zovp~-0KAs?IFY~h zYOhd^BjpDvX4ob5(Z{_bBi;aztdQYXZSGpUh}BGv#hFTK@!X^r8=C_(hn|excw+6q zZHkZUs_YHpJP$C~k|OkEGTpm`eV*@f2a|MuN|2JA`KD0AYp>@E>j)cDOngitzHCL$ z6LDbvEi<)%K{^5CSu`L%UngCi|J_m`RZyz-IiKK>(In^NY;(w2a8$yEZv2*%r!9_LJhGJiZX!atEjqys3DZg5M0@F>Q?TP#)N)9u{Z#U`SHXT&av< z`(9ex6K@E%&Y(ICPX&B3c{=~lCV=Op{hWWX~`BoAC+q8Mf9veIP8PAxqsGE%$eWy&z zm`#;>abVZNaT!;@S+3@X0P)_^!x0_TUr7gQKqm~Q)yW~@x}Eo}AqPUm+=kS&-11xz zbM4~W6!ZU~s58v@3bC@fApZ=bnKx$q)+mUw#1xj3?z-jrcubzW?@xe~zn>xVCrdIF zE}XqF%2BvI>)h5kiEIgYV!cA_?4{mEWmcYVtp%*H1yq&VD|&}Lj`XUOY|UFE(fw&Z zob{ExtQ&i)-qEY4z?#M>&M~Qjds>YDZi%qUskT6t&}M|rpqnqR|Ij{FOwIR|CikGX zenER)u4_5(2#bGD3-_pZpWxRX8G|h83{w}T#4X$nb8)QCq{KBg$>X{|Bz1JEqS>ar zSIe{W2Nj@*h?!)?HSsg`=ez6gW`2G)#0iJlnRvpiit|1N;{^hhIVa7B5)Q(ip zg&DxH_teV3REf>TGCV4>+)PXS#3)aFNiq#t;&ZMni9nI75_Hl&A3o&4%JfE#<*c-$GcLrrgL$Xk z=K0F=imJEg<%%RyDg`*Fh2H|`J{=$4S89n6kTFx1w^Tca8Ck1-qL7G9!Ekzc`IHH^Ij7|q;plJqUwgi{6QhSFcg zKP0(!>M}t1y?@zC=$27R1%OmtPU&G-jJ3-=ep`cq}cc!+lP zGh?C-Xyb2+;>)+Sw})JhpO41mzXq2qj@h|5*}bR5e$j@P-+#g(w=i$WVi8namoa%L7&FQ;Y z+JP)!q>N$jZRVefs((lBIDfK~^#|jL|C91F@t-WV{QsU8e-7^dKlxw%$$NH!;8)rC9TAWnROvXiE`L>h=ZA~D8C};QH8TNJ!$bGW4E7lU3QZR)Y?5rh9D0a z>Gntj*0NByF6qXy%1sFrL0gSj-A+#s^+Pgsc)_lGOkViR(1O&GXh1DpgBH6`jHOCh z#x`6jBCDxmi*}8JA)9V0&#ELreD{u#_eJ|GiB_R_G#@?=8^X%6n=G#;eW_a@L`M>S z^2grgG?jH0fDPv%fQyh`X9^_BmwUH%=$q9{t>EIfgE3a8M7rn1bY9vS^xm&%YtKoV z>aSf7(iM{!Z)b=3YsdYh=d)WZ;k@0Cy`J&AyAJmaDI+3>@JH{x1{QX;igPMUmjC@zP}p(b#VDJ+TP!wzaB=xjRs64!AB&u z!1@7qxZA@A9xoWaOGJEPLSi=>sup|gy>cj_^A^k=pnDzJ3uRhejrh3i_h?MsVmIY$ zROB0;!ot3d1zAxg#AE+7XHAV4vzpj}6rGkUBuzBygioZnm0HOb2kNptv`bwBdJ<(N zFqRuV0l!-o{+b0mJj_)As>MMI^eV&Km}@W1(uh3u(jh-r5H5EBIjuW@FUK7K>WsGY z@}*NB{n+;kJ~Ky}SI$hwxP_>;t@`Nuh@K8#3QL7n*T<3FQ0m#k=ZhAfbbGX>Ge$?XtbzkM^u2{+fGjXPE(bhNbNOo=S0}2WE%VA z8XlZTE2=l1)E*UWi%D@}dL^^=GPYuo|wYukV)C!)8P5Lg)|qGDZx*gZq*oG9K8ZeTPF1Vw{b@@Tc%a$O1@=prGbQXAX4885rm!^Y7|X)X)=#R9KY?ecn5d{ z{pLO_*aff~Ghs$(tVf4}7>cFCq5L42NAM7t*$6&=pE<|T!(peL{_CrnW_Vc#3iIUo za$YO=Wm=R}v5Naq!HxsOG zUTo9Ob?v3J-irhd!ZfoF2Psf#YH#IQn2=jdN`%0o;o|35%17qL@9$lP?DAjY0j{;f zV7v%R8NxfjsPxGl09BfY>+Jh7POril!|h7ua}*iIlXSf80i@#Hiq?Sn>?Ztmf>z4c zGo|dXiUjA*P-==}*8nOWUr1`HP5-G3^^Gx{WiW+pW9G4aRxVz7Wag<1A3552B*jTa zdj<_6wXYee{^fYrn-t+VKYc0V_phpVL+=3F5wd+u zN4+MEi1m6^lPE^a`Y-aVNtCN;4v8$acL4fF5EZ~V9#=w8mT8ee-uYJvBRQu2K053d zi0Dl{OO$pV-FMHIg*DgJPalpiintbTt^k+h5@ix`JWn*EbjtN1%i`xjAuPN-hLH*nxO3j*^M`46fLyA#j8&4c3_$d9?WW8{8p^pm>V9`Tn94UnAKz%T)4O9Rp%FsY1*Ci=ycN8^wR?=Q8MIPs zsk7&wp1dQ=01316eT;~b6VBCB=)W4C?Ze|!ss-KKD($w7&Jf3L%F%J4vb--ED6Eh& z{Sdv;;1KNdQ-(HpL2pV~78ub6WWc@& z#hEsahhCLz5amyU=ZT0585={xA z00Yko)a?GRZ02{hujMylOnVY;Sj}990G7*lfIc6StDEpUz_SutOC%>8>!>r*LB6pi zQI)f`ih}vwB|yDhmT{?=@dBy3i|TRRFu7v01`?s}Gnzp*WFUuR2@vvk045;S9bgt^ zt<6#zfpbtKNI3dxj~E#+)#Lr(4iH0(dcZyql_IGdNAIK9$w=FC>k^D|QPzMfkuW$U zN>pO1^;3?7p;8QJ%{~S6aWg>-GkWcD1QMg!#Y;;igpHKgww@)d0!ZEwJviioJpM2LI~-5HqpA2f#OlKPrC{64ecEuE;Jv~ zD1!|3ybkcg3)6oaz@Fmv3M!ZQj)@bao+D4y1at!)Yw}t5c_PItUm2&)L#``nOs@QJ zBMu+kH3BVmMhNX!tqFxRaIXJNl5S{-*q{w-@g2bDLHVtXmpt4dsOg!?uUgn*9Svtm zFvg7On~I~b%e^NE3=g;0NfHN_m31)|y-J;dQlcU^WV<-qV8wL?suwV-FvfNsnej(+ ztYPjyasx29iJ$Jud!k0rPUJKSoW!{o@Z#6NKBy6KromVzv zX3vLhpe7iWu1GexR88=QpG&N}L`w7PRA%;4)EH5SEkEfpgycH#U+ffp%KV`$<1V)UOjC+yQ8faZ{ZsWqws$`R;$+hQCME`&~>bFK!~;TF=&3 zu0$kGmWVrgUck1R*Hz2)Z68D^D1(L`72Uh|xh;N%-HdsB-X%-;cm+)+j3%udL~D$4P?>_P}z7>8C_TlC8) zj}C$!h-9g^fRD64e4T{++(4kZd*oP9aU%O2V4b^s{6;a~=1Ou;0)wv3&+((xlf#dP zYi+yknfSCVt=Yf$LeElr?I>D~zO+&HTqwRk;BA;9Dq-i!puDLV#tfnN1vmX@S2H7` z5F%f8L}e$?ZpJQ!y*~aTUikres1$lI2c86)F=I1~=5&Y>s#P&L)OkKul}GzHYzv9rK^{a9trLFf`%7l_=C<#UmFB37IL*Jy7gS>9pft(_o!danNLOJ1}L z&pv4cP65d0UYQ%B1Ik^7Z^=Jz6}`EiNqK^(53!Fkj4H3KQ12(VyG4)136z_+9ILD}m|9hX}jS)0&smUfEH&6hi zazR${t7BU|&*FhrY@BDh`;EN4`^6f?(Z{NlgH*!`FYE8cJTC^^FX%BM%9+v#;C=L( zICI(q3{4uKK?)Y_!JbT%WxS+=x`k93qr_A_UcNDE=WUqmmf+Xi@@|_S2+?)J6_Q^m z(I2a6!a;n%%H9=0+*~YNsop0FQc)6UU@16$8%LX)NaEJTL58)W_azv-luO{VHDUlb z%LqU|hAjuK*`!KYXa-HcYxY1_nq^aS^7Bo`Zix=EEM-;=p$5I@G=DQ%Ob{TQ-NFwe z{+S3Lj^rs7X13Tg5~2fT2ukW0iZ8b=ck|CTSFT%x*?Zqx!zIoTwA#HwBnXEG2}yR4UByn z#$G+j%}<0MieW~?$bl>-qc}14k=23U-7`9_FnSoPy(n^2?#VHCuS%>pajd#U9`S5K zaChTqo+y}Axrx7~qRZJX=;8i_#=sx3GEawmVxV#BlynDpf>Jw5VhV&kR4v{hT+?sv zSr?<-D${GUh4_`&u6H|kfcxHbMRfkA1w9;dZw1v{8v-MhuztLByvM-30lbHB=#mzz zHX*i>)a@VFt80Ij$Xq9V{%1 z^wY~31^U)CPP??u#URp+-0+K67$2iM_1cikO&MLw0uA-mx6oZGFDAyKp&$I0ul1Ha zrBksZ^9*)$S9TBHDkZJF?)J?u%p~MGrdtU}Cv0s_zgk`LNFoK5gv!#TC5m18sC%Bd zQ*~-Y#901N&vrK@Ghu%CXkU>2kr=bw^`HzhDzrm&60B1NA?e*8=9E_(rVgBQjZmed z_W-qDeOmkCGlod#BlcK3`qWC?q?lu9qiX}9*Lzs^sFWhu^oIUqG>TXY%$sG^;5I+g zWpaPp$?oAv99d#r^IOlrU6v7DF`P^(r}KkP=We>1MmzhFcC6N8`eU&qV^~hT%v6$qmU4 zYl&T7;;$M4KmO&RNDs1``+P2wbtFqal~C*N%Nov0&ic^hagHNh@Y5Ny;zf*J9G1G( zw94*z89(Pe)iMhA#Gf>|ZGE;!32pNs;u|r5bNX~F_r#B@Ba8@^bxd7a>J!uWEkV8q z%Z84=4uYf6&oiMky~C(z_Xr0u(<#jor7j@O^Bt4R^aU5IQ(y0Ej3392_oPqJI&b)4 zw!Wh9NAo3TKYH>O#ADYrpP4Uw4)qCsu`j*{zAU?ufmH`Zx_IRvJm(L0sRtDAJ!z6Q${r53-mUs{9&jn8C+h|CrNsyCEyvqS&upGhY*F{%6ktf>eh04Cefw)9K?sdUmo4QMdjs{`6_1i4gO3W9QB`xBLgXuS`tLI%lUYXJr?BSMIH&zsv z5gxmr1dbSv#kLDE{M<3mGKo{Y)!`iLAS%!F^cp68MOOGd!m5m2fAghAvFoPoJdcck zCgt^LBDy|rQM>KiEFE@fRTXjq=;7(E&Ac?1w`TfJs4fiT$`lm(_F5zLV`z@94Eu^g zkHPT~+Kq}d&P3_H=|tCramxvF(m)%&AAfb_;#nr%3cgE;jS^h#UQWkW$zrUW(=pWo zzDi>>(2X%aY<3#N3x^n@Gw3cKA98LihjSMLJk8LT6PmB3A-z2XkePhsq`|?>Q+WSl z9K)uusJM3`aaCZ8dGy1D6-ND@ZCRj1-yPtdLzoM$w+y$s*9UvEC{7yxxhn59IVJ+E zTkb_)8Y>`~P}zO``8_Ka%6NkJ4TAGQ2aVOE<38|3u^T~(%QCvHI$)wa$C<~) zuxXIsma$Qb@sEvFf^_}*T_5FB>AZDbKPQ8oIAeFubN$1&+RJMFtxauxcrOXa7Q~U& z{;Mm%SKcx(K1z7Wbbf)6Dpyb1Bg-sPY_$&&g>6S84=Pik`B4!_hbykKo0I!rTZ`>) zEz!4s7J6H(IgoVNNq_ZjaM=sV*9*`LGYpJRXyfwCP}^SE9kO^?GbU7A0zQ(~`L525 zHhDnPb|Ho3QQOfPR^uxef6a5nY*kQ{h(~1lnJ7=Nm!%;MKCB@oEwa>H>z5x&2eeG5 zj;>p31tJar-j!0_&2X|VYgPx5W2A9xfUnX_y#KEP0f^vqbN9))Pqi^;DuasRB!vK| zOAEjJ!2g6(x?b$GRB%-|*NssyajjduIxVVYa4_`d%gk&Q8cZ?Z=0%}9S!c1}k0YNd^I>kE}(~uO6s*gzq#}KxbIW#Q?_;bj0~W zyQ8tf)l0o&V}ZD1>7^pSRkVu6IGa|D==~RV(&ERTk9d+DG|~ zB}>&8&a{U{p*a{wPHFElP&&i)*tN|{6+Nq3vdJ&g!eg(t8Q?=)y5cQf-^_=IGv-k( z!2VgQKpBg+P3@~_wBDkF$n+NTHfn`M`Xudx)%35~N4Q;|AJupJ*v#L&fTjL%IA zf01n}H-%O3*;CdKyQ$AnpGbOzf5FrV2Ooa0-(%G-aCQ;b)uq8CnIsu+u|b9WE)G{j z#5Y)XfP?A-s;f`Z=rCJ1s)uZgp5{&WYd@n`+Tk&TOnma%ig%=C9lsg;Z$YB?BuY(9^Ij0+Hu z&iK7~AU2dG?7_tGim0UQviUFak0 zKHH&H8>F`>d;G;4*p{Cadsm?ks(opVmTaWOCeLg#F~{e@i>g(xvExf|-XxZ}OoJp> zM=<7hrG@Ki6|w84_8SSw`q1igdHCRc)lDrutBn2Z_225ihP^v9$)Y~NyWLAA*N8i#QMeweZ9sU-Fi1%aK3GuDVZ)X1 zI+KU!kO&8w4v-G`-vJux zE<@G26_G*J&=uGimB}f##tp8_jl3r$%3-(=Rt!Gv4ODCh;JpU3 zL8`ESMWQO(S8RKScsHjoNGR|pn5lLq4$bj35$MMP+H#=8>glkjUNs7^e`X9BrICR# zp>j@gv@qVG_X%2T5#%uODw~az!l6y*lU_xVWd)^9FpIcjGl$ z;%v#(IC1DZLtq_fI=4xhLqqWDK9PhSbh!zqTcWhMbZ^4Opox9iuuEB6+VKGwru&Fp z=7*IsPr%j0Pm^P%H>gGfXB70cx3%&Kc&z1I_8rBW2P#*p|1TE-%ZSE2BX`4|ioJQr zVb`2$?-yq>X4DoJ_$u`spxH2krpX4oKG$;IH+`kCA=F_LaJq<>o#E(a=EFVVD~#~E zHf=wGkZ$?#-@v0<^e@eTYHy;it5H=0zbZ<&O!yoXao1Vm*_iIE{<$ z5G2Wpzo^JD&)lJD)Ru$s_y7&yI&fYF587&RbV+*z+3RXOwRJg!P%20>T4zGu^WM@X ztY!RHv;}ZQw@%r72f*F_BuuDGXS=K)1~d^cNl?17a9cAWbcg5|6B|@b)1o<@D?iH? zk(4vv^l1O(XoJEOEg8I=rx|HO>tyY}F>XHuZixE6OR42MjsVmr{HGFNB5B5=T{ojnd+^S%WK zHO!n*4jMcf387aQ89!S_QQw*2wFHRK8}7etb{Q6XPkT z*84&O&8F=s-@d-h%a0?~Qq<*qYR9-ujY?_G3*lv5SIgOtyxFXo^yzH5SWdPf1>z>& zSkTD{-r%0_y?a9!^OTF7NPFKAs=lhjovPE*ttMz2_@M^2dz{o;uMZ&WXz~y-N4i3z z3t*YbxdYjad32|z*%k4p?>q)xz3HMJ0Ra^WlKn-3@2Nm>fIV48QP>Jnu>^>0iEEZT z|0Dph+wr>{sg#y(yhJ8!{jTb4c#fgA=k9Rt03&@#8sON0M1|kF&Fd#r`Q>Teg~A&F zE?lx=r$Ycm*!^c9DtG(Eqkm1d#Sz~NjYI4h-(At5h$_R*(@y*jwhv7O>GP_XgX+fX8{ zCmCdGA{}Ce~NW ziG{pBAn3>E?9r~nIq4AstoY&M!Xg(qt~$K0QF;aKSu;NB3=C|-|BfHsc>a#%b`6D! zLTAeor0KmCQrnWqI-Jnzf7CNIS)+KZCU(*KZde3}Lp=2oW}NG49=_GLlAb2aWOb}6U+?5M?Pze zzPbaTm@eN1UO~ka-X5dA9qVGkvq>boByUD;g@f@R!rYg%heE{E9x|BuExgEBbMi!{ z?U?;1iqY8(89y6&!>@?Xw*&0cg$JiGjX6rmBvDrJr^X~6Vp_`j>M^Mu8nsWN$M{;X z54@POr($V@K{k_E8NrbSv2pL?%`@uw)@>oa({xiHUC$Og-ZEZBfuPBPymMEsR@Nik z)lM3LaDj@5N1SeK<_iF2Qj@+k2tysX03!X0SU@w!n}hLXl!Rp2hbaEh3+!gRFu?Ij zm^7vd)vq`E_X(f$Qr7+wErTm&_$~*Jl7XdQ#p_zI!B$w9`+tI`g=bB(*9-kihJAgll$pf4Dpd2_ym)(b4D}NpyzHCmk zN9~X(1u>c)-BnYXOrq4DW&TNDHunHRe7VTwadD9a1u-iln3;oIRPuFkoKw7shP&WT z5nnRcq5<{Gghk4FTQgOb2$2tyrkgbT+#At2?ui#`86n^7G}MTBK!w_x+LkloZ1+|z zb^`c&_e}H=HtWQJ-Y>(12CDTJN}6V)G=0$HPati|JZU{4=$ zZ><7-kIjA`1kQesmq1!$f1W)c!pJ$a2_SleKs zX;4IM^PYA^Ru`|`0WO(Rf^Tc?0Am4FEqqohb3lSBA5N%9E#+vM+d~H?juVR1|Jy1* z;Z}>*OLS|<*>T1dmd`yC9&ffud!cB6d(z7)CUv`pSA8^eiH{J zEhKJc`n(+DuaoLfqirj3;8%c$V$ah1xueG*DQmJpjnnW%fc>Jv>|}Zq&vW9p+-b0t zu0{=_Uy1VqmV6rLE=>L7gu|VQtjPT{j_JbP766=#Hx#6}?&>p5;KU3)AZ_HKfz0B~ z-2|IPt00)M{dogQ*&o!68SyeE59Fc&^ zMf_fWCP3@vMmfv>k~LJJZKb1p9m<==el^)|o%3~>xjIN!SSqVL@~#^eK~k3$3E3&G zl_ac2%VwwqY^{d))mZ8*#{axYOG9m^mw_F9B5H;L5zkttHju~eH>frwx2u>QNl~Ik zANMl%(H2aR$P#_)JHUdp!|ZmHPsY22#(~$fBPmX4F727bSvH%%dD5u~qX}N6OY<7b z_KV0qnJenD;^IMRZg&Y2DtF70u*2pbra~Bv5pQB3O%h)IvW$u_j-T_og~D5nh0i$R z#>T}c+BmUaazsrra<00sv_JhF{z_O=Z-sY>k4A#6vWz5IMn;zD4=dBLZ#PTk3CeuR0;E4VndslxRe5Egjd1b!j(`L*-h53)ZGR}< zE(`P}X@tv!9vmhU&we-4&yP7}d%h{_mB^A|n{q8_FkW&t#J;E0IFjH(_N>!|E?Dc3 z_<^7Fa|FH{Lu({k?T3#aEFSPSa-&K$Y{KV{eSKB2n_)*?)@CML$3KmGl`hS|$&3Y1 zmS0MH;2n=^xmPuxeLM2i8a}cEVn6?k@Y-M)#Q8Rs0yriPyRhFtSNFKb49eEKC=ZZAnilpHLVJkH~IAII@Nj+5l&;J3OmZhe@H#X$bO zOKZEA^NfjZ?el0njoZaI+M~3lv&=QULOUy_s^n=wf#0*Mhg$-_%@GRZl6gSzo^U%s z@wH!X?AteQ-X@_X*AX=m$y*=a_Sf@s2F-^~Af+v*a^j3gw~z7+n;beIYUHxBUx+Iz z-Cj&0{E_OTO$y#0ln=i!Y7GD#zUmvEjM(+Uvl(ApaMiP~y5ReWnO2q^Zf~#l%*<*b zuE}%~6&0qfO~1m#y*jkl%p}&r&E%``q?lUYJK?IU`oHPHa&KIbuVD$%2oGJoZKP(e9aB%s%xr8r;32QcfTLfbDe?t#le}mrGt{<*U<- zJ)W^QE~`{FAoPk&V3y{d=#Nrtx`p*dntF(q#GAy8%VLiEPwp4F%BOp;5hNG}f0o<` zqFZqy-XdniM<%VR6MK88H6M{{HHA^9Zn^Pw@5HlrP(skt{J^`3h(h*R$Lm2imV9TG zSRe4T%brBL^G&y??2`dDyB2-@A-_7zNYwVH6TJ0Wk9vUeHB%J;gY)eBjH$z zzlQT>YE_UH`dQVB!C48tTFXFmqDrsZPH#J@CkHZJt!eeQdYmpey*w-PN%6?NyPu5a zEZoXyY9(Ma>4~(c_h+3yb?Rr#*Jte9?iH=i=1I(;^X91$LabNQSJPYbA_TRmvBs|+ zoSr{@ew9?f``MRW;n2t2Ce?Rawq4tN=DNi$=M@nisOX&Nw=ta!`|3P=&}EqA_F}is z=HJVhS2uQj(om2m7M_@Jc{=S+;kN% zK(?&D;0?dW3DfdADE50XmlF(Xk9A!k+?N)=&>3PZ|7u~1>=vd-_gVFek&WAZ&`uG5mzoEv&*W8?T2{|A*lXC}+<#TcDBd*7JrTwEwt5O7D% z>3I??1mZ6o5ACuE{cS*FUeu!@+cVdg4-JOC${q)b;~R7f_BMV(F3CO|7dXF)J^H14 zO>84*#^r(AMvDt!n~U!rnWZ?KS!rK6cWh%fr}EtN#BreIwf0k$H3v76E{p6xhA)nu zyHZaf*4?(5ewpwOQsNZHl^PXTtb2`2p512W5f`)3z6NC?qBir+b^di(b7!?QB}+Wz6Aa>;ABk25gcR+7vuMn)~gnGs%so9A2hwr!!+>$3H{ zrcdffz}f8Gv=byZveyItdxbJjZ{5}l8#XZE((b$bY>2c%i!c3H!Lssinv+^{4kE}r z)u?B+kEak+uBW%ce>pd_23nTmSBKwO92AJ49A zzK_y-V2irzCf}Id9p1KL%$a<}UG9+eTR`{6J0^ug-JT7>I05?dGAqiNo%MYWVm2zP zY-r7IUKm8qG$jaG){<^f5%$)0XyBazDSW_ztxGtO;xBbaHEg5Y{hiOaPOpOabe4Na zNYc!MvF6e$PP2Fid#*DL@sj7&WykjnWcUmZCa+T9E=y3qHTuY?F&k;%Hn5d1p(yI` z^w&uEdu0PfASh3_tH7+VzuPrbr(9GLYPhc83}y3sDJNwv_oQNw`-l`gTukT9c251G zIP~(I+%)$)l&z?2JMmc?k#8uWtH3Mko8u|j_U{g7)%1GZ>$w0kVdn%^rdx20xALZ; zbt*<%+ox*14D{1=Ai?vzzKwh*)4x~_`S=kZIV!KtbyzPrYkVHwS+dX&?-LOn)l=6H zI?$MH`>rte-ayFc^Zv?93;nhF*7P)Eu`Lr=#b=PKwKvvhDZ{7*iE2+a5Lc?UV zD~LTDoNJoj7Al3`URx-SwLR?0tKiBxR`b3sU!!D)8m|jCHR*_x!mudn#Kw+!vCnTl zecByEsM!e@mEqshhvf&1*0GnjjmBrtK9^GTTlKcBJ9Z!dE#-HWbc9(S4$b4Ko^j=a z#k#Yrt$k~pwVS+-#Yy?R9egh8=983W1W7Omw)FAntdTC^cg?D@k&23tiH(fzvHtsO z1$Xi8>*ak1`frv3&q2V7HW%!9N}b6|z@C*$e1(`)t~gk!zss;Uf3*ij01+9cJ5BVU zg)5u#9yj#s%hop6uB?4JXME!nTR$lg_Xw!RxL#DqOw>AtbywA)0;l*Gy0nc6*9xh- z8>ZH3Mq4C_&|Kc&ggU(~jUz@2?uE@}MzZi)`6a3mO zt?$Yn3f0u^LbpL~2g+96$(wd9MXwyT-au=;J3J^^SoFBocp%}Qq1x^{JNoLjH8O-(~tKym8);87rnnT zvDf`zpy8JN=3hZsN3}IK4IQerkFMQn zq-~7Ivc#TcIakD%yZNQi<_Q~KTzGJ7OZlb8bd2RcTa~&Ir5Wy2C819)dHZz=__ApC zHsfDvxH8tN)ttqyqN(MkSeOlWb0@~s+|s5j&9vhyxiO68r|qfxj?dfk18Pu_ z>tM>cwv{{pcA;tdfRs+|MoqP>>#y!yPwvB;9XP{^IquZ-*So30|H&G)|Mm49|G^e^ z|Lf=CKiFjLfBjtizq+4g|6HTLUib5#tCRnIZ2#$h|KB+%f2{HUI|t?0x%dzEdj4Op zJO5@|;)hn6>(QEiI1jdNaj}uOliWQ6sQ5m#$zS^ts7p#AUWC1I*KUMysPp9hhK>o+ zKrw0VE8t#hQ~Z8F2l_#DwSm#dH>~~;--Sw)Sh(7A-eZH4R|64 z(4?7>-(}}Q2K0YkiaW<4vJl@Iz+uPJQ^)2gQ(YJqNF7H)xYVDIYx;RHCJyZi$jM0{ zeMzxn+q3-U*mH3R3*D=Ode-%~#-_3MupZ9xmxmG>Nw?~ovFN76>dSk}q#xg_PF}-G zQ>itY5UhWtb?yj={o86t)?Zx#!dsU~dzZYhhONs@F;A=mYyY`#$9n}T9_YL6I!ApN zjl4mhWhwfLS?Kcw82;lGgmI1P>bn_Z>vvd*jGh#iU#mUEG!OCn0KI#9p9KKIP#pToPq2VbO1sQF`Z)n?G69a>ZAg#!i+LC%rokKrsp40-kp z6#nf<`nf2NUanU?+OuXwJ|MONMt|HNOXQC}S-kMv!Kn?bvME9h{*NX5KN(}x&o^Qh z{;_2GzlaaSIsEf+sXyO`q=@@PoSOdavH9W7f1y}E`ofiDG!w?QYxN}$&HOy*K=lp5 zWV!55!Na<;4t@Jp)+?YEuCH^&n0h7vd1G{hhWg{Je-~7pYg)_Dn!mn*RYqB6O#!_t zG)nb$`s`ZWi@Ni-AY6FM(Va6I8RoV4-1}1pZBriF*wn^}wi`689MVA@`@bU0|C=}3 ze{^imxnhY!bqZJtOXIj|yJA+${(U0Kz3#G%10zHH&<@S34o(m0nR^&$3tQ=Rm)vx> z=#RJiYa0EOP2*3UE7a!Q|ByCf5GT>aWjo!JSts>&qYd>Tz87;AVu>YK=ziFGDw9fy zQa4w|>c&dOHsR_7`ODXY0D=JJ|AHLz8B_WJq1t6RSKlHII;h>|gZNU? zyljFJ$5-&X)6^T%Aw(F&+O?YCkuR`{&K9N6FeULz`<4&xcB{lHbrM*5fqh+OC8gif{$A0Z^K!SB(++S(VC{-i}2vg8ZCT zkG*_Uw4>KT%5|@DtlJ6=g+WJ{VS--|4}yh_P62fVBV&NO3|2PtDNK$fArWT>LFJ#| z;%f#$3Udct772jo#gq_505(8O3R?W-+tp5bt=I$4k3My5x}k1-$;W1|I#hPoU{DW- zHy@a*#Lw?IThNB$tiR9oeHKM$@o_wjzRS;SCn;p1duIii!}wbB;p5PGMo4*)`mHOx z87~`VeXsWH?yw+U<_WmPGpkk(myR%;L2Z>0I|!`YhI4?J35YY2LhuX+lts?aS3|yn zm3Se0+%CF8{*&Zyekq&AHg0#$dV7A{Js6snEcO8v>@QoWRqUuD6bA-0_$T~5Be@KB zAO_=L^I!n+Y4A6HwEoBkM8kW3xwTSWb1#ik(0=yo_dTnJ->%t!@{T%aP1l34T!0mI zs>g`4><2WdieX!!{ws-kzw}$c@LBX%03PZ=IFAv~Ftpf~8mTp0)EO6jMbwz|Fmg&= z_tr(Hn}i~q_JT;qA;b&FW#Y!nSHwkc`cg)|8n^}ln~D?2ZKA#~K~$>J3$ zA28q^V$zmrZxfzJO-)U4Y={o)S@DQ-Y+pPla^hHxIE`sq| zXG4DR7y&F;hnNs`j;}5|(ywLlXUIF-=RVA zWF9@umn;||Ki2Sd06AmK=jSeE#OS34ixs#YVuu%babS2%oe=qq8&sw-9DFC3%Ygn5-~-LkQxP$17UCZbck3>c zZGPtmMUzXfd71Mbq+k_`F+ENg!RoZMH{T{|122|4n{@6KG&;*@E;-em!M>lUgml1z z>B)kmkv>v820`Z$r7xfiyy18JHY^H7y*My()RnlZDD z=J}1=@TmK2ldAK;`b+bB$F^zC$JhIsPB1P%LNQm@0KP9!b`dX8vz}n`x(1M9fC`$T z$?elz^PP0r59_=ItRnQohhCOH7}1{9x#@EG-1!Zh_Nn%{hIV;G-`AB%ivr5h(3(#N zm5$p0?qO~Kh9M7gHs&>&qp9e5VZeHwEC4rkvRV!a_fdqjWsGR_E35V%y)oOO=w)!J zqknALtlrB!i(G@st@_qTEI_kP`0s#!6<%-jZ7l;tkuFRQwYh1TSN+?i)o&Hc1<2e7 zRGJIJ<(8e1nY6oeUgdSrB{HAdG?gsy zPy3J~K=Q~JE;G7$9no%*{hZ!v-B{*!{>y7VKK|^-4^UkMut9aad0g0I~2j1%>x$!jD6dzmVcoc`XA4sD+z*DVP*_-ldJkGuLp6c)XHu=3iZy&gF z6?$*;hdU~F-m1-PamTC!jMv}D=#ffS4zLCOi{2NtWPNv^)Ik|7-p_t3yD=L`x3S7j zJ)&DT*16f#UML87{b}a?NYoMbHv$m>XsTMRMLP#rn}K9O)98FDv{f?x6Vb@E-E96$ z)TqH*^b`$dgfolane}iH9E-CcQCy!V3tpj9Dt!r?2UjQ8q^CV|SZ}0ktB5=J`24ex z8NxaM-B_|qttxher4E10dDTrX@&9?lw%wbY9y`$CfQm zl?7@-yA^ql%-l zAxsm8>DLx-H$OB0A|DoC;yE=Jg_TDIX&pz0^uAS>=dbL#V0Cjsr*n^wT^q?jzMFK1 zG-;wl8Uau*{46vn%^H~zX2$a|Lw`V>s86I_ElaF=Kii3FLyrr|ps{lv*~Z_EUp66i ziFYWPERjbg{r4&X^a75FMj?@B8NB}5z_td*0XUp2jbW?>(uAg_O^^mxiXc2FM3Q(O zfR?3KG&Q5`BkGLIa#}18-%<9|avR@pub}pPVP(C0!n}3?TNcR8iD)1<0E#iy?763# zi7+P>-FGY(Y`8w4YbA*O{~?q9H#1ECrb(y#?t2EyPpA89dvcz_eWop>5f(6&C0J*S z4bqIB+&Ki?Q_u{yi@5h6t&SI2T7o>`G-C%~@yQ*dM{7`m^caR0G zzoyIH%jE2_!Ao{gfv4}f&j(nkvlY;x@U&PVyNY2Z!j_Z(4^2Kkz+v2D%YlN7gB9@f z%3{!;ay@(t0`R@GWx=i2&BWKjw@|b^?QK8=90#UQ)N{_`csRXo*7@*Cg|w}Et52t% zpVrXmd2Y-%ye4Yby`jAxwMVt?9s-2bBZ0@=@S8||j~yxFvH%5-tD@$)zJSewWFD>%h(Hi-l1lN@8-a2s(HFwu-uMb<+2af^>K9KE&-)_N(YPJlb$qKX z+FqBh_mMBjTWwKP}^!egT{%7!&CHoO!Oj!eSSqDy^?G(sK zME4mKf(;4~_Bqc9P%X+^NZ(M|ej?8=&7nPucDDW8Q6A+Tn@u#ny?EA}3~^atXt~J0 zmhxy1%+E-2T|XRCk=9vspl>wmRUwb)roc6tVnDtB|BA%&KVK%i@rAf*=^uxP1y?8zMq*>9!sclcx)&grQ6Q_qDSQQqBFfHfMVao@mXp zQWS`YKlHB8&1#MK7& z=-oNgDgWwQmM$omtU3aHD`r^Hko|^?`IntHg zTw1u0F4A6mZZfiR@T`6)_TnrcA6e-rNc+WH`z8vZpA)jtR8zJV*b)i_eQ+fx23_~; zj6>31-^jx~`|XKUJ11fT6_0M`J;CcV8pSxu2D@I@2@P}w3S?LSp4+H){2;ZBU>^i`|J?8$UHT3a)&2ng!aKj&Z*PzM!?tRn z+!Ny5-woXF8flSlHS!I`-5h_ZgjGnhwknKq$m+f!K6BargEZ&3En6R8JpmAaAc&Ht zH&_tY(9MI-(;h_gFcVV@9+(Y(YT3H;kmHK2Nh@B!Yqd^N-w}A6_Rh8^K7CVdx95fQ z$J+Ae?;(lf5Dq3cqPeij$=(kp%#L5L-~WNf?P0lpVp_z&rkVEyd?s)~zNRARJQB0t zmEGlkpygM&`>qt4BuD29#9G~;lK8EwGaT|uRfLPBd5+UF^Hq|v3<(8JRLX5@aXRo$ z%M}g}R~Z#1iL4#-?zw&{^se|?ZCyhWXbTS^;%L?ovv} z`olh8FW$O9iwie9eYu$cty;N#Dy&%is?zyh64!r~A^=pP?q7SM__{3bbr>pxHZ%Jl&oot)-+auuAA@5*U z5YN>q{`$G?g$CopdesqzL~e8cjuf%Xm7GW6_qzT87O_U^$BrR}!YZhbwoQDbYs#}~LYw1Ti?`sHXX zrfs|ama%s#s*1}3wo1q7L!glliS`1KfEzb5)4Mip%(XZm^IrsLa8J*`RgomlRii%n$u5$~E_81t1u2xB6yo|$0*%G`9u32@ zRlrfp#&Tm|tP`;EuFpZ$1d1b;M_c_f`6WyO&YBo-))0W%n=;QV`3&5<*eQVfL;)hO z&EUYYi2uy=a_kR?br}8_?*QOhHJJSa5Y_?%Y6j7hl0X$<-T-^E1GA3YfzN4Rhk{S! zQ4*8inC~5E`2JT&{l8zs^_TnF{xjeIZz8cJSpdx?z>7fAR`5od(z_xYG930C6uNtE zzV@>8vUj51rAb$v-jk?_T6RAmv>AHt8=kEPc`p~90G@h%lFAPVpNV3P8zGWW3_Bw* zg$}PpG-h zZ2&^90O7FY&-fhlH!?1t!ZIPAB4na3!-X$7&ZOBi$j?YmxECkQ_)3LWIeBTW=9F|a zIDW87J-zd&oXei>Y0zYGh&jRb2a7iXTBt$f@2mY1(N*}BPHPRHT3TA~DhNOS+sA-| zNA?D~4ctz0szv{K;fJ+V7$14r#L4xn2*&Fox%35`vwl__x)^MPPIr$HAP)bn)F9U5t z&wqK%S z5K1Wlk!aDDN_c7pc=dijXOx>+=h5UVd^SdVW_6xxSKaHbTEFhw>(indpVruOSI7@7 zvj4>Mgp421r(}|0B*UGl6pq&pCu2$M<6%xXyJ9 z+jHfbpQH-%J4&dunICGA(D-iSz;*xb+Y=>R;QT1kUmEAhr5jU@dl-g5-Y7Z+Cf@>i zBOz}VawyUjDON;JTVxBa8p;{$+R~C65tFTumTO#Sefo{usoTAGNogl|hUyNo zNOchI3b`4dgJFlLxiV*?en1Ba2_t0OI>wvGW=)D>BogZBTe|p?Pp=~ zBs?gngW71m*lwDad(IAwP5}eFi!{ShLIUq|jZcHv<-Zz-|AqgN$k}O1)1P1dGuX{4 z26R%Wi)x9@C&}>7=@^;7{O-Zxn9a90U;xT4(_#o9K4ABP@NthE#7Tnte!GW|(t-DC zQqo4$qZ@^Lq66C>(%_(SR~vfvS9M35d$o6CUE`0ey1>gT{|G36Tl9&!U8IQ`5G3WA zow*TLsWdMuR=^4a(z)^7*q)xFC41Y(&$%XS>br8SMow1vJ^La+f$o{`TRclJX|DlmU!>uoIUj6mG0nCe7_Fr?62ttVg}&> z+99g&iJuERn+RbuFv;lUq`rh9v>|EQgFG;U^Ji}a)8+J(16rCkHRwo0!9ee*qh^2w zizd|ga12Pm?bsZ}1jhR#xxWPRB`oYzsZsn)uR3xwc6J7PkOiFjk1Sb^cO94-8NU*p z<-${hiiU9O?KJ&R=jl38=US8Yth#}1m%mxXJ-bi|(kOpg*TrdrHgfRH${CE{PD241@@{*>2<(5H;3k!@%%-qRf{7OlXAEq3E1! zfXe0J@6%i}X+q{Tn01IUV1Oj;E^f@t&D%TMb0FQ|>!X3lGkqaj8US?CMKi`fI~@s8(AeRKIC^Fb zJt;hQ9NW?ZgYL9^*V5@3HxtZ>k!_ITLqmybZ`au0#OYOP-vMS`yn9oMd3f=` zIG^C$Z5CS#C*!J3Qe7%sbY&kW%)Ef)V}A8eF=~v|UVNk~yx#GwX^O=|XT`MO15L`# z&&pRN_CDr+l8(JVx4W&j8RmDacHA{%VN&lQbmgVg!%^p_g-|!m&7OPUll<5O!w$~{ zHvlBXCb9Lr=PCt*leW+lA6@cF$tychTeo?>e$#oy!@DyVtfue`Ul}?G*+BQYsgT!c zI)1C1u=7>8o@wd6lm4e?C33l@`2A*>;hbYZoI~(9ILKWbwrk)@R+J`w9N;+40(0fr zyPBoL7~iNYY2kb@_wx2Z4TJ;vaV@3{keju|>E8?Jc4iUpksWKZZ+)@5+p+D=v2yj7 zq7iFSljK`qY7!QNIt~C}KKZj{5`DGsD0-8YK#I4B$*bI^W3_R+bE5`!U%LBc(|h6G zRxaiNS}zb3!GG{~^seVsU^Re93jox;!0=gTX)WC<+gyx(HvHi2p7!>-JuaeFA8!Tg zi`y9Ub_&x4*^5*$;S58Mez2UVK~~HivvqEhb7p^@G=ARsc8%esDpjs&HLL=C4#)y! zW{huA4amYTA62<}@)ntMu&w4ck?c`h%ES(Z+2%nfA3jRnv@k0bdRQ>LMP}kp8|6P_E`ctLNIW!ye#g*mx#4`R-!-)vtAdZdBf--g56~ zn_1_DJ16BPjWb=KJ84nkjeH49yx#vA>)pgm|J^eGm4E+iy=(6sZr&c9mhh>w6V%Bb zG1Pt5C?c&9HbK9mVeaV;1y^qr zTz^%|s2q|q)0nOn=KI&Y4S~4wYCc2<7`rh+#9uy?Q31ecuh|dC9X)-KoVN3Y92nwm zXmI%+`>k(pm$^kqs++oPLOoOEnzvoV1YjOz@c{|P3xIPxK=fG^*NCUf_-(SM3|ia2 zrOd*eE*<^7`;{Nt^ny&kEzTEv9@wUPAQDDC1DFpc=E9tkmv958frG$wLLnkNG%FV6 zubSEUTbF!TUt~?qPWgB1q}Esj$c96VOS-^d_y1wAv(lPWP0!a{a#^vD=TJP~WcFEz zrnHn|M;FaV~>vy?7Gc|^w z^N=Pae`mpIlH3U1&{_o^-0qLoZ~~M1OEjE#KJz-6oUa%8cv8ob@Sxu#>fAaV)N^4P zs&WF54e`x_ob|B-q>lCh_7CVAh&~U1nv5q0%IEgPzqE0%amnh-5%*1i4AA5%gob!B z<51r)$%LB4pOXbQF<4+KC&8+O%qK9;l1#=GvS0R2x}+EyKOXa3wdX*j$>J@03s*;( z+&PT?nXzmph7<1K&_@*`dycM94RMm|YVeRLRNmpL9BgoInR{0;isgF_2s;bU0;BBJ z*(`AYOe+N-djczpv&o(&!oI_l<-i>Pbbi2G^OiMi9%rLczjdC~-Sy_H&V*EDR&52B zbj%*Z8MV(n~zbRQp3 zW#n4WSOUTZVSKP4Ko;YA37i>hw;*nq<>W5>GE(28FW@48d8eI>`Cb~%Z60)*YzCcv z;9@utmtkf;yD_Zw%RjXu!~k7o+4p?~=N2&HW#SxR6Qj!3HjTkMJ9Rv8zr|;&z%Dr{ z_jpLd7Mhgri5eU8K;UtB0^r0*2#Dj@4sfVVoh(uU#??6Ty(rJa_lD00!uFVpsnExY zP>HYGUo`R!&Xq;YuLX%qI?&d-!d{htez|=FGeZS}pOWx=vGF#?0_RlHB@YOY2%4$z zjC;ytDg6&iUrI30L0vX4rJ&%xCw1WO8eu8SwKl)XxOA)P0jslRD$$V*Vz1xNN&2Ie z<=j>|x za?w5#tQC`=X8o!q4gd8!Fd@l;wL6z``ev1W8H`myUS3YrC!mdv4P0>)vLi3^-}M)| z%={jZUikO#be#FU2$Dap;6c0RWAa>7f7vqCn0*6|&#JY2&67ZuyJvxfs|!LoL}}mz zI;GIb>@c4OjyHfH$zp7BvS723AY-JIxU&%S*eJ1#zi6b~D!)omr=gb5!^#iqyuiTu z{0sR_DlZ9$ywLXIfo$Z66f=5w(dZelH+X7Cj1fL^-(@ z{$;JL74d#Ja7gN@=(a-^Yx~cDGica>P33A-in-LL_vM8oN28S*S z=*;?TMWqnbw1^R_#|YU@(zZ_)EMgndFHt#jA$gnkTb-1!$i5sYV)Gb~*sl`vXla1g z^@hEkt{6sj-O!0~Dv)d!RS;9Vx~V_nN!2I8^G?Gk#yQLy^q?Yst{l%kj>9(Tppq%H zw?uKc(F#2ghGrz7sqPDVCb{Wv&t;}Fi-t??&fj`VT zh*QlE^b&%whCKz%Mj>NjgO*|J+C48jS;}*vBSDyhCjp57P0PSZfo;4EWT$|UVZ2}- z{D4!ZRRyq>Y{3a=UD*%PE@tJt|5R(76UV)C1uILCx1^nPfVdm3Hf)1OvS8*8a6G{Z zZAGkBVY~wM^U0^tfzQ6m%G`lic|&URK~Lb1E@glb4IWW}!yEs8v^`%bAj==8FPkeD zi|o&TFd;POVGv2wHhp~ehD-Nn!U~2!MUo(0CMt}rf3YpW^Ja==SbS{YRvw#M(n%$^ z{Hm;B`YFJ@Tu>NQ^Wjk1(pAF~HN9)faPn5t-ncOrV!hqe?yPrSqg7qpFIQt{K-Z&L z_0owj>y?ez)foPx%Cez_`iCz!Yo@PRn*7$n;8QH#{$76N;AbK4{u z{D7_#mjPng3?WEDvWczSGuN6|6X5LPY!h{Zhh6(oXI}R}q#sbi|CR_fx-Jret3-81 zM)!eJrDFMs7oI;Y5ZU;qN{q+7Fy4SWwgRjFM>x$;V0o-)5ibw0ix7SjPW&b`T>T#P zVL?za_-n>|gX1A|!4F;1B(cj8xB`*oM|t?WsXmYIF2DYnR`6bFUd|0{#$13!%{x`O zrcgjpFJoj@Xm5Q}+<4{euw1rQ!TC?SJHGUOmwHVAJcV2P0Tu`l1VQmC5C$_JR1@N= zWBm4M;5hANY(`f`+<}tE${$1coOVL`62bYd1n1n2eR%@p$?k%~G5Y)V113&yqiIoJ zu^-U*he4FgS_H-1)yRilI%}&K{k^aNgK(rJds^Ugk+Gb9cpl)8A$==+p=Lmi&cnOCavT>eDcx*mYiM_RK&d z52{|=!1dn%Wg>j90vV~f!+iv}B_s>V))xIbX{D37>@fN9r zC%HjH?K_&Yj2N4|#{7erUsoJ;wd@Jf?Fx5S2cfImwvO2vujmc;0*I{dcpGV?bO00< z!puaV}f{{gjO5Vw)Oy8nC0RfUw8*2=7d;`uWg^SYz*6@$rw zOT-1WNL#Syfo{|#Ez%hv*}p`0Pkj%EIGdUf8E<(^t5kDtqMmHwqy1~80-?^4}8JliB|$c4+~{ zE;A%R;)6J!kc>0`zO{!Kvc@*m*zs_MgQl2Ol9N$;jbiGk@(TL9;P2E2KhLtG0xTpdCg6qUHxq z5y~qWM3{4xiYADQ8Nqjpj-|sVH6o{WRxkh3+FMu}x1D6odMD(C0 zOxp}1K_@|;sUAPQ116N>$C?K5E7t8>b;bl-s=?#x93AdE3nm=bZsTDhNpll`Uf}u_wzoPSIcH2 zedRap`T@P>zOhc)m)LZB46>8JSGj;)>^FwH169Dmq)xlKIBT% zAv_=4mpX)=0mgk{a}6H2fi7NQ_k#jf89>%UEJaq2(9 zS;+vpOuFMRaM?`YK>&y((K+q*B>FzF>bOzN&}Qw102p8Fdx@|++#^PFqx}y=Rw+hfki(FH?DxYjebB*3)K%sOfEt8N-#Xn$qFH^ z88ln97(cxX#n=FR%)!NX#J&bV;faGfr9HtkJtL=vb$VT0!M={~a#!qb_o^MN0MQa@ zqC`pKF`|Blr1JbDFXw7LjgJij`Zs}wGHphWe4+pim3(DV#S%KRO|}>L&K67nO=`$JL&idw1QXE1<}x_oXIz=r|6>Isn}go9PF@ zLDLQ$2}^Kz_Rj;f^RODAktVt+{qvotSJ?5jo5A@^8m3nUe11T_%_Rbho_mx9K+(rX zz$?K_02xgsBm?Jx&$5N|dgf85Ec|HJurCOw{I2F=VUV0vhGon|#< z^4q2CF3#(x=WbYAQ+Dk@rtJ0+N=Cj5f?^Ua;QS1^vs04=V-^Ytu}&qQHC?viJZFAG z>f}CcX1PtG2Y|1bZ(g$-CPZp%(^#;r}_A(%NH$*ttI(7x*hFK)Fi!~VLRFjSbcQ+}Z znku-io#F3TIWP*=%&YH8XW@~VE_ZTs2@{;ZSdGYFcVj|Lz8)ceuNighl`xNgcLO!1 zs2n~exJV-|+z++u*Qq*46V}>|rpkyFJ|2RHlFXwEYA4YkayNB*tkTULbJ~0%x+Dyu zOOpVeH#zH|_JP>$(Q07f0Avq9b6&<`kKwdI=h-B|(Ew*8et>ss?h2@%Dfg~t4;;ws z_n8Bcm=;9;D1k|bIh*ctwSm9?W2I(EAw+%&?#2#15sXu(%5joi+?@ZWO3RkEcb@>Y z;xC^g{PSAwzgrtkl%Qu+pd^Np6h2>LZ80-Q4jeY<)rr3QBHMqTiPQL>E2WjXu&3$B z69=`(@0rqfPeh7`n(P)mbN-HqR`}F#wA2l&OJPS<#35co*;m4;oFEWfyw$L9W^uBh zC+88V&#@IA0jSkc#_t|x8?Hg@`V(7lM&;2OhYMzJ-tUo;ldV0mM~6VL=Z+sVb}oeOj!K)qryT=uk=7 z52D(i2Pu52i6$LA+aDdMcuGG^bg}5QJ;_QR*Ms~zG5fHdbbMH`yjPk&mDQGO8Tx(C za#QDuBbO$)TecG;39%&m8Ua>E=ccb6H5z9II{Q=i?m6|6D_IZ)0P6=(8XU9-1lZn^ z7D^!8epZ*)lIF&h5XrZM(JdYbRuP`40at=3$N-xGOdut0fh0fjk|>YWEy;UOKVlYR zkbXF__35RZWjt;r6*u}Fe?aF)@KXzs9UNvMYEDKElnx+l5(t_J{xNL05nrR$Duf_^K8D8g@U=}YfvODI|U9FqXrT7BNucnLQ9h5S8(C7nb@x8rWopbdbh}xFme9f1B1^GxfR=Epw)T--C-oG%M zwOj87Wr1cLOqu45K#-#gG!C}q-C_I zU@iIO$gT4aa!T3{TUk`^JXwuWS=Y)xf00Fl{Om59OWGks{6r$XIT+*H%eCA8g_oE-UIZPnegw}}e#4(M~-`EILSTT(JXqt;Cjw)@gS*VD+-c2zxiu7EiLjhothZ!2X2J(1P2Ce zuRFxRWWfW)3{x6{j603gZQr*&vE6V}v44(owbtH!s$*!Xmw)cNFP@uM@6>py@Xc^@ z$lX}meSFJ9U%sBnmXKAh@pZ|18kl~1;k2a$*V$QshtRW>xfyh&zgp%ay5P;?{XMz2 zv~({QIJumu6pg?Bpb|H<#cd~W$bP?e9$lGb>9|oO?N(y>DdCa+qI{K+Kzr^$pl8sa zblkrQrc}cV>`Wu}UVyb#F>{j`M6qNiKoO$a>#+Oo?2T6SE|Zf+?XgZp_%KrH6@aBT zTw#9ZKIDe>5?QD$O+NJldUjWEfk_vy^u)=xfw{Z^e6;;(&yLG8s0 zGc3zDn)$3|wO1$*9)5Ze`Jk%|y(g)(_|#GXYeYMs=EZU+m~?=|qcEC{d}(lL_d+C5 zHy)n&hGFXgKV2IHP8%;{SaS;lKX=GyFJb4d*B0kAck}EiFJJ2$xO_j04=7%|0Gc-#Mv|;g&z?*a*b<{wB)To zgmlq}69DXS1*0|()iOb~!=RRmLVp<7@Q&*B^M6tQeCv#v6WCNKrZ0ZY1RNIccvcDoi+0MR@#9&)OP8h^aFGL7b`C-Yd~wBJ~rmHav9W?ZeuH7Co_d>&ejiJQhZ{# z(?DZBV2e)H&Kze4Pw(&Y21$0@GM7xMBml*55WPfpFsAH{^o@Bvt}6|nImIb>Y81un z9u?a1{`~3Hf5ta*AdDm37>jz|AXjQuvo0^2#flXWG<*ZO|(7UV{l(nMXQ>H%6A)Ezx zq$9EU7gkEo&>VSKz7dgQWFDtSg;Ok>ALs=ZPV&w;P~I}0_7w81;LsO zH~;}7K-^*RSSIwm_1UWE0MbD01cwRqHQ=Sd+;D?Dg*6R;G2~k!+~WgRMe#bC&oVuW zy)GV&UoX=s7EoBvi^Dx8=Nt1ikaE4)VfmOD1kc=ggi%Cn;-r%Lj=urBX&N{?Cgv5b zT(fgOnWRGa84fs-Dt0`Nidm@9FoDU_6ehoUQ6rbNEjQrnS3n|W1nglhs*riQcNrBErnN48duG;wcL$|Y)=FNjD-4hdOufB> zppq<@N3t`TU#|#{AVwUKExuz|SK6ZVXxAW=Hx6Pkca&Fz!skYtI-uD$MK<8{hi> z@%HBNP`_>a_{bJ2OG(yIl%i6WEMaINN!m0;Or^3VEyzA1OV*GSSz1I{rb0^0SPEIQ zP7z~Y!;E!?ndy5@eV)&a?&rDh`+0t^-ydFDW_iD__jR7vxg5uFPV6`*(VIWUc6b%P zDxovU*HV!@8$2~?H)nL5Go!?SM`mpicXI1y_7P2*)y#wWIFXhCpa&D1rYFirch0IP zX4WwqllkwBEJ8WZnYb5tN%}={s(%5}>apF?cG1z>@zplG^akY>LZz>h`O|H1d?7Qw zWUYp0M#BAP{6)juqa((N`A4|@y}ytq)*HYbK+H9$h!G$Ts(o@?KAfmZE*YHLtDnvH zpszZ^)Fth#8)CA?vkR)0lRY@vh&4pP=D)!HW`9Od{Ql1KNZ++eu9z~m-u(Q#9qV(Y z&bwXau-X_rc~3h%6@u(QvKoB?a5w~3oc#sl!PTG!QDp`zH=o2<*Ia#)7&N_H6(eA$ zneu9bLi&s6#--f$@6RQ9ihW96hEHO|p?c=w`?F5|MAVG*^Uh&sY*-{{$6sK1wPgOx z%})yAKa$qbw{#iXdGQ?&@BMB*`rS$wJ#eIy-Bd9Kr1O27b1xEor(3pt4eUXK2+rF60FWw-9SA zMW{oa?a0!&E78u9-{KltHlN9`Lto#_&3*H*LS_TB)znTkjpoFk8v97R{%o_EDj%5h z;jxD*Y@;lAw=fw~j-hQ4Apv$1%7)v#hA^CT*w#^z(C#^t$pZa+;lBIJGh6Wc)78>k zvrmWYZRF+YUvZ-1Ag&@M`~deW94twI8}yZ@vtfDRO&^bN-#%(NzVyjK1#+Pc0_&L$ z#lU|+0+*l)USgIHp*4{KD(zC_7jX@(>W>~*jve*$D;Ku<=(2ja1KH&C;WE#a%uD`i z5-V%{^}XHz?nf$k77GANNo<`tYJQGH-+x_7iHhk~WdFK9ZG@lQKqc`Q?eM9wVy)g) zFmF-~M4;HPpS41m_)E<5__eqcrikYma=Bl@=D6)IW4CG6$V3ZqozdutD)G@Ncv2v< z*rzUbr#_mBVb}@dY1F(GAcIcg*>EqsH_$JTQ-cfEdpsU`{r%3ER^jt~Y~JXT8tWs@ zVYVINZb4<6(A`Hci!6uk&px-Q>l`xN5H(WO|q^^4vb! z9q)mW3(^?r&!|&Wq*A<899y*Qbdm@P^Xcm2B91SfrW- zRi>Cu;bAO~=fxCPnUBiDaV3@YhL`L4x9pYtbZ<)b+9!m!&(S{*7~COpV8|3fY{(&f zh;Kv58+M|E!5XF}ChBx$Rtdas>V1UBT(BKHD=g z$w#WMUtM7xQRbtgM^>bVyQ?vMvUy`58dkBlXAD$ zS}9Ip>`OG@ehmZltu_4@?6f|FZfBPGkc*9SoUN0nmVUgy-@7USN(ZhB_l?Gy>RWBtUl|YfI3A zXaNQOZ*~I3RMSOnr0!+v7s@dS(r}9V5)Rpy=8XGzuWD4~^V09=p8D({fAH3`J$&mc zYGov@S%Ubj4zr##vFRI@IENj4q0l`%Z!i-Mmb|NqNoWK&Vu%qr!rew%bnX)eX8rKn zT%(IM_3Ee694_3hY`k>gl0!8@EAVKzw!%xe`Vb0%K7R^J0-|0Y!nN!JpqAigjusEx zCDj;=jX9l*?ERX`YX0%|e%xIJFeIusj(1e9u`&?Kdxu z$~-=PpRZy$Kh_LEyYw$e2!Dxk{SywsoM8`&Ysg!@q0zAU*?sk=_YSR)Rk(5l!tHC5 z?&t?loYi!dVR}147|_dlzB*f;;9LH96iA@8Er%2lZe^YZg+1|PehYFE$YvQZ+|1z^ z%+2z}?<}Bpb=R$Iujy)Ax3!gI9GSVnR`@wMf8SsMq@*+qBn0sN%@5dnbe!yxOBV))xT>*k(Y{}*K3S3@cmjQP^ot#U)3rKA!(9=xc>IPmlk9lC7u@Z z`q0|xgQUii2Tbe&F3EmMyex^gV0TaE!EMLUas3+SgZ6N2efeSUTDS@gRXFDsU%`K~ zcsDw>QAN5W`ARkKXIo!$Z8S4CRN{7$Kh34Dt9uQ^2|k-b^!cOyCkR-Q7}xWdj+h;2 z*oF&82?#G+3>*Clt9|u?!P?|oFy81QtT+J}j@SdJse?zj?-w)isM)|tT+=&dH-ppL zOmETO5jxX z;&z*H($o(2&_pC zY+cPy1cM#{P(WCqC7u95#@r%I9Q}d%4*PO{+V=Sm(pr<%$g4B3>$rZs%cL~a=@9%_i=^Zz*6eP zt))9rT>QHojP$efvTYo;U-0L9dS5<}T~=L`t^octAb6qWd*Luz-f*!K@~5!GPIy=j zmYAhW&{JRw2zedNSAmC{yWWPYUz|Kql9B4NeBg!}3nG7Xzn2`jw2M2y)4Jz`aF2v9C&@j$jM18gtc(4^fVG{YiYdX_+ zmOvGh`-u<+?S(`rOC}eNGdT<)YCpo=j9Mf%n0{>DtVLD*qh`$Lr-m)x*Vi9ilf1PDI>6>O!T8Jy!-@&*$oD3cu; z#8uZ(jkHXmc<4c*FNY#x@Y8?#Q9sDIJ9HzpJ5%p2HY-X>jR{}0?UnZJk@o!_%vhbw zpM(wB7-68MPQ*~0y`OXpwR)!r_#J&4+^@RZ_fYUK|Dmp1q8TLWK9;bOZiphC z8!Y?|pi?Uqrs|l2C|;;ovXm0oAa&@5Jmt1Y31MnEBykr(^p_Ezp7+%PE z^(SJ1h!GWcilTN$x)~?!cf0@;f?|%IbX)c^KpMdg;Bnrve2Per;#qt98vS9{q;c8k z-CO2-f!ZYYZ=A3Sie@(>^b4buOz(FbTPGo~M?%1U((j)w5p}k6?fXnSOQm;yNAd69 zhn}h^z7*50CuC5&E;4SO7raUjDI&i=_c^t)W2NR?f2*>UxNrb^zwBo@^)5>$-mnjRm zV$H0lDPP-KE%exIp8Lv%@4FmsdT$y3I~BI!d+2`aC7Tu907uZ<%$LIvnJW*DdEmC1 zk4`?b-8E2MtG1fel6Zux8xKQG9LsggK+SDbnBsb!Rj@l5bC`(Ecg~o=t7+YR`nss- zvvf3s-_~ETsrno*wt!-UFqno0%OD@sp$Gy9}G|1{+%q5zfPol-t4 zGN_OcUy>+tDk0YJ4aXMg5LX7O9ap!Df(hPxSJnOt8)|}u0Tu?G5tTn5(oq1dSqBpa z6+NIi4>;WuY@;j>6Q=q5L`UNi(tn_Cot$BABu`J1f_f|t+hoymq9FKu(f6WN%=qJq zsB4x`qFA2fj~x_*(8mX^i~_uyhZc@Lgx;GVN6I8YJCHY`j9lv2BYBC>=~PenjPr>j z!Y6oEA=rI54LcQSl2ax?M}_67_w_*;ruXAok27`MO&Fd48%RbsVusmKx8tX%!hmAxC}-x^LGWefqwO7R#d31=rua{ZTUM)! z<<77pX|B;k2B?=B^u*GmL&j&}%IkgcJeeT`0ed1@+aS(MY zN0R>-*(EZ~Zr56(nsV#Di5itHvf)3H?_WA=DEEF-e%=4tpV6x!lSi=RoFSFBW}Yup zSn34rPu7-IncOx%r6*aS8~sLh`FridYuI$xj!d*d28FVQ$_|cRHXBJp@zl~0@@#bh!)n41cC;qMgx81|FWeZwq^KJ{o zxw}`d+V?0fISG>a8#NXB>%R4vHme#roE=8ruvp{jLTgZozm8P@epq1$csWTFYe)$Q z3^&k2dYk3@G)lcc>`Bbtr@k2(O3Sqb^oCa=yicJC%-W<0O$#hB1QLgjELpGBWE^AJ zRsvZH#oSbY>G4m2n&LJGkFKv)jaQ^bkEY}tWxuz%_l3<&loIzEwyFKXw1-Rfz3B|9 z$YIY<#4+-j=wT6|?-kp7EhawfJ}XbTFWc>-{pLT84!-MlSGDwibrU%314=XH*+re0~^p4+Zte&Crya07zv zvb6+K?@WzX^!x!h2h&rrEO}9PYO9aV5S5cfNm7KYlNLM^$nlu{ohr1V5rp1ED6(Rd zu{$lBb&-j&Nql(e&o`Byhg~;)c77GNbW$c1!4_T#V+|mBHK847O5Y1sysfSm1^Qg3 zNdoZD_-@enDY*KB;LC7KVe5~mxnh;v=innGN{5xrX;;Olj{Ce;>Ib zG&1^b@~uRJ7s5^GKXrwACHPu4@Rr0J5{q9c&(I-ad$}P%wrs=#wswaN`!{dykvX+M zPjl^=yzBSx=Ej#v9rh1-%pL)hjB%=25~v)iI?$5HAVdURWKwYJ;S|d1k4Hwld2Z#J zZ_bS3j$3u&U`93i(RFrqDiMbc_l*rTIWG<<2tof|( za6`xDO?U2mkJ7(&+C);1L#&FD8m6_aJBiOuN{T%5c;(n*51#PlGO`8X2h>F+?R!vA zqHAQ8=%&lTOAqRRi~hV9W+?PxWpuge@^zqA$@!ihNG0ab*K>EJ+Zp!|sQ37~mgmhI zNLj}cymuzX2YBaBTP)1%2id>DuCnvgf~9TF>&KQvEWPn5rGf67KGtvVFL#c=Q2qlqrVkkfV6!}+mxU;^25#x#)bT?|N&gLSdaR`PUe+jOR;{V6^U zerd!N4Qb~Y)k=UG2tCdbv#O-l=spsQPx; zC!>LNQx&+Rv}FEz)Pk$aA{aG0jb16!HZn}O;*e7NdG%PrGe4RjCN_7LRE(r;%C~O8 zG9;$hhHosZ*9AaL!C|q~7jaaxa6$~$Y=ty5L9W&8neQQ|Z zJ8I!N5_SW^JGKn%LS$}j#%1&h6>IlS=#SLLJi2SpWE#PStr!PE9tHtMd3;8j|1Xbi zkuL5CE#zZYp+@88X?2GztElt!?={$-$-0aw^t=QP#D3-$y7p$>mdt#q0lLCu<#{pm zxr(hOBS-6X#mr7dzOau4eb61+HKo2MRfhM3CN<=fK;H9juaZexoYDKBlvL|hj^M@b ztE;wYY?CtG>Z{9kb`_6sAm*TVL(+sq19qg551c5il@#%!xto4DA$+4|%Ts+D zWTSS;$yX)|xMty)Yg0j$bEUbrHC)vj=u@sOuNLw{Jy;G z;fIz_$+&8<`P5DkdUOSlFdX|gv$v+-!Um)Gd(KB>v~eBN6V(!=qP;Npi4auHqZrQE zejD~5Cczyj@|E09^vlBm-y1d<<3)oVY1 zwrWt6kxVRsd%|um*hf8cZ?~MU(}!wn3NCyE zJQ=k6B)1brK0)E<{1NV?CI03j*=SEQUAv2CtMdoz+(+#Px@0>?<=>%FJcR!v6}#W0 z!T&RZ|Bn-Q$J92Hc@eWW&DGwH-{is_9=3h3Vh`=?QJ=!45W1ePX8M4qXQALdQvvCC zqP3He%uSau6q(U@^%HSu(#Y#6mY&^~C)hJSpfP#~$~-pFOuwhU@yX{{OkPzxpi)3@ zO9&6c&*X}v1ay{@hix_ujGA9?)2fNrq1En;ZXB`51)TXAaI}ZC)^}Cm1lH*^&Nw7} zXwP<;;UjWOdMTviN=SEti_ZQrol!SBR_8T&onUcbm`Wj$;1R)qocHAvp#jg2@4htbE#G5UopMijGd> zsE@)f?4Kb`g(X?H=he_CNjt1X%dQ>NS({hl2l3oICTyQA7WM76CHf&@)|K?0683G- ze#l_~#V~M@a=;hvD5$_L_^xS_0D@3#w-`y#YDV$(x}S)?)Q2Cc+nJR;;`Wowg$X{C zn^{8yM9r48%9HE~i{}N9Ja?1zC}Vk>Lh~?I6P>nz4p7dGxfY6>EwpdNcEv$`C3)~S z`{YdOj}d5Y7Xmlc5KwD>i>t2oA5WT5Lh9xv zo|q`Qw0~U(_-z_m8@BRkC^zl^f(GgrL1Uy6zsb`W6J&q>N%;@V`AKXa4pyb@*Q!+h z)`jHB?1MQ0m#wY1%ClD44?9wAZ^L~F{-&T{wg8yQR4t^u{2C;R2hFh;?y=0Hibk8j zTYR{haC5rO&YG^g52!iF-_)EimQa&d^ly!wD11jUe{Ee*2bT_8%lZbh@zF%5WnS}I z1s5M)3#);V33GFNO9TUSzHm3V%l(*|D+{}rz7qD#JDDWwZZLot8$$i!4WS><4^k~j{OjL5#C;lX6Wd=N(>FR%k~F^SM%?@` z{wyN}%9mApA%TiTR%#+>+wneRgK|#t&45f#EvKh9X0HE46i&`qd&CaCnyS?#)&cdT zxt`gOHui!Mpnm{KnS{rFd1P31_dVoM%>Ap2$rTdT5p-!!$1dWR{dM1N9*A;_S7Veu zvXa5I6v#0}p8%gB;uoJGX8IyfUXm8x+2+MA$LlooJUm&_~u%xtWWP^av?P4aT8;Mr4#TLseXt7V&B6Gw>YmNl^!)FpSRPKQxDi0Dhqf{xbKJoYHwcXruQb=f&q0 z8OU=;n@Ke~t!|7G{-F3dyg>yFM6dogZ6_CG3kfu~Vbb&@@H{aGA>}~@P2EVgC$_GS zuAP?{=YJUX;jM(VsCr#Xa=i*^H0*qpA%CA&mTM;|baZmv_G@L=;s$lVJ66nR{X*#o zzO-D)r7U_;Fk! z3KAKb@K)3a8PeEf{?L*30yfHHPrbS4&B%d=C}R zh1jI@{;V^6nvjkYs;yTkU?h$rtl#d$A+W|NpZBZ=@#pkTXEp>*Y* zEh>dV^tQIGg6byQchMe5JbDw&YR2%Tc0=VmfA-|5pwecfCz|+XSOHsrkzFlb&o4~d& z!sXL;f{I_E-`z=k0mU&G{93N*v506Slc>N4_F1*d!fnrMwMYH!fY z@}bp}A@Y#fWpC5m{2yUThnuH9%Qg`fzHQzsf<7C;oJaQPib^ShO)O$MGB#11N8?@5 zrlyHX#*aD?JxsSa`nP0$E2=`57yl>kP48b3N12TrE@ARn{b$sjL@V#Quf~{QOYT$c zAs&f@&JP%K_sBrqw+@@f6#t$>!#-c%RS9sd7RMay0%I5Azr`T;0H+K`zPds7lV|C2!g4~N~+#CBzSd-C| z?@5BWf#D(-l{PIurpIVF!l6t*M-92baes2y|7qmG9TSg5Pp*?0b4NckeQM>-bw~(- z`ikl@wPDAWc5G^)E4a6Y`(2x{ZC`QGH~k_HN9YcjG+{`bONOX1;5)4VBzX?LN1^&@ z@agc1qmQObd`*UqF?{Elu0|j&fdmFpCSJnE-TIToDvY4(1aeei(AxgOz1Jav8`3#H z?&s$GT&rB;&QD*s{oW(L%^dha7ri0ew)_m~z@7@1M{d_9{VYdj=8?2v zy$+n=gtTvFSry6oaVlq`{SBm7B)TqptHqY`b;MGsVXE*TYFBIb)u>GezQ5DDAYiZd zX`foK>dE7G9j`2xVdkqk(ziQBbaRD{?MZtt3Quz-!V3(M*SrhLfCdaE>(S#7)V@yK z3aXw{bk~T`OW{Z6$Z~SJHxAZr_gr){3f%$!jJc|K7Ft(ZANkvN{yU zpJaOdV?olS)b0cyNIB)qarO)Q=BK!23;#2DC5oRKeVUw0Yns}^thsum?2LR~ELf8Be&YRxP<<9Vp)4CKHWT<$&!eHxou;YH90yei7z%8qtFmo(juzGvL9)~@X zhUD<{7efrlL7Rz5b>*e!zh z>^i21!AIO2A98+@A3&m(Ddb99@9idDjucKpPETWtS|0bTx!AGdy0N#`yA93|tDKki zF{B_nSo9p8E;bL#6w}19mQ7n^(=s%Di^LFs@)OW*Wh!J!*9U230pMMcamB| z*?8*Za4MqHo8>O?>GZ(srLmt<23HWAbT8{ zbh`l6wRz)Pm}2?uX@Xyc+J`=$BGgXRh9;r!<|Vpu`}VkBIg3g#SZMp2%>NI{nFr^w zJ4J467w52+*Bi9Q{JW>0(;sJ0J(C1JQM(5$v78Dcu>bZ~T0M(*FbrS(w+`F^Y%p>K z#C+o%B)eAqMCts%s;|roVPOrMwUh)QR;Em=q6Q zs=*JDe?$&WIuN4Yp>N2R3(TrXdnbQ^CO9ixt238mHUw2rCprX6$#BjSc0h$l7_e|> zA)ZbYf)47zAhB(q+* zLbZD=LVV5D6XD6Bg64SP^`1vllqJ|at{8$Ofep-!D)8trxiekI>w2DaEZ(WD4oMBwX~x{+ls=~`InwFARB`HmGez=0SG)Kd z{_#-myW?Y=1_cE$ZK{E(1M+m##djyr>o;uO(XNr@ME9V&cc$GRkc*g>gGj4e9O7+8 z5;LDLsd}<7A9Kr6=R?fc<6zQ-42v5(xK~`?eUd-OTM*=a#16G~q)YHQaJBbsTtV8& zt6u&hnP2fq@R;X%^6W=%weWrmo(<>hD&M$;ua5n^uFf*RN8L`LmC&Wf>MJDfBF)lS zlbe$9>mg7QcbsQ}I+)hU;OY7CTTHFS4C|)t=ko;3%Np>TI7aoNZ$%uO;6-fCN-go` zZ}GHEGkiX1FK)a>|D=#kagIQt$$Md7wk-NA?Z#ijJ9$lh4NcQKIBP~rEyl|=;8zW; z>{1j{F87?s{UV?kmw3iUo?a@^XCmB!En!gIz>cn9$3L?euQM;@3v)l-bfH-6O^AAu zKj0T2Dt!L$L_#T-w6NhJ#@N@qt@lIY_RMo-w8xPmS}We%Rp`c0k6@s@I}nz~0EAnY zt{5g8M-QJxO?Sb>sc<|I7pW!EHEPsZ`;xht^6J!&RR7F1MIH8TS$iYyS%t3eFEn6^ z%$C;CSPF*T`~a7`<6RHK@Y=A%+t@j!gJjJ)tFlC?CcLuUi|z;x|ES94Cqbv9Pc82D zd)i^w(r|k)9e++}OSuA#mjWklcMduiTjI-VoM<@^#;I`PRkh$(y0+;yWT}SjQRggt z>3BEeakzGXMl$~v+zXbh{&d`ULmO*kbCvKk>PI~@+hWcfCQ~Bj-VjiD73hi_Y{o;; zaPS`Pix_QpH>!kR)HS~B=3Gat#7c*&R$ncTWKNX5yuK5P6a60iRE8fjKXp=gE*i6P zKUtx?B`T$<-u%ZMzqgw>x(=Jz0e%QW;ztc+)-kB49$l&C{jNB=;&@=vWDC%okrX&1 z7p+L{(KC6s>F1uhHkw2q`_if(u;tL93&w$0+47O+6<#DSdxr~xqThP^B$no~ul@Zd zZ#7pDBf`7JE4}Yln^jT{aL9&L9DF1(7u-`|MM*E3Bl_oRZz?nL({9d*)E5)t>OZsM z5k(g%`EO_?dva4gFb|$go$~vM*q7o(1AdVa)u0Et#TaQf8Ry>?Nhgt3k^H?XlKl}g@YG=QV0#lGxW$04RL1b3+fj@E z0VcoN)Bt}QThMbcjnB#DxRESvMsE2}d1 zQY?D*c~@e;n}5-Su7S8dPwimpe@pvtM}OsYv)t4K+LkRk{5 z{WD|z=7io;M=BpY3=IibSI2RLyS@n>43R(czbq5dVKGz}meoN^%tOsWh6*x?ro!EX zg*OyI#SngPAsTB7nX;4~Z$Q}vXN#KBq-7Px_b7%
>3PhmE{^7@5t6%3RshBkL9G{i|8varp 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%0AO=`H-SU8hdvHSJ29@WgxWb!QEZC_pOb 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

-F;IfmAonOX_r8OL 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 From c57a42164cb87622da4f24e4769bc29ed7419f23 Mon Sep 17 00:00:00 2001 From: Oscar Date: Sun, 5 May 2024 23:35:19 +0800 Subject: [PATCH 33/33] Fix badam example outdated argument Former-commit-id: eeb415f6fa81ca9093ad0419d1343bd5f780a688 --- examples/extras/badam/sft.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/extras/badam/sft.sh b/examples/extras/badam/sft.sh index 61167dad..4bcfe9d2 100644 --- a/examples/extras/badam/sft.sh +++ b/examples/extras/badam/sft.sh @@ -10,7 +10,7 @@ CUDA_VISIBLE_DEVICES=0 llamafactory-cli train \ --finetuning_type full \ --use_badam \ --badam_switch_mode descending \ - --badam_switch_block_every 50 \ + --badam_switch_interval 50 \ --badam_verbose 2 \ --output_dir ../../../saves/LLaMA2-7B/badam/sft \ --overwrite_cache \