This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
from .advanced import Advanced
|
||||
from .dataset import Dataset
|
||||
from .hyper import Hyper
|
||||
from .llm_train import LLMTrain
|
||||
from .lora import LoRA
|
||||
from .model import Model
|
||||
from .optimizer import Optimizer
|
||||
from .quantization import Quantization
|
||||
from .report_to import ReportTo
|
||||
from .runtime import Runtime
|
||||
from .save import Save
|
||||
from .target import Target
|
||||
from .tuner import Tuner
|
||||
from .utils import run_command_in_background_with_popen
|
||||
@@ -0,0 +1,114 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
import gradio as gr
|
||||
from typing import Type
|
||||
|
||||
from ..base import BaseUI
|
||||
|
||||
|
||||
class Advanced(BaseUI):
|
||||
|
||||
group = 'llm_train'
|
||||
|
||||
locale_dict = {
|
||||
'advanced_tab': {
|
||||
'label': {
|
||||
'zh': '高级参数设置',
|
||||
'en': 'Advanced settings'
|
||||
},
|
||||
},
|
||||
'tuner_backend': {
|
||||
'label': {
|
||||
'zh': 'Tuner backend',
|
||||
'en': 'Tuner backend'
|
||||
},
|
||||
'info': {
|
||||
'zh': 'Tuner实现框架',
|
||||
'en': 'The tuner backend'
|
||||
}
|
||||
},
|
||||
'weight_decay': {
|
||||
'label': {
|
||||
'zh': '权重衰减',
|
||||
'en': 'Weight decay'
|
||||
},
|
||||
'info': {
|
||||
'zh': '设置weight decay',
|
||||
'en': 'Set the weight decay'
|
||||
}
|
||||
},
|
||||
'logging_steps': {
|
||||
'label': {
|
||||
'zh': '日志打印步数',
|
||||
'en': 'Logging steps'
|
||||
},
|
||||
'info': {
|
||||
'zh': '设置日志打印的步数间隔',
|
||||
'en': 'Set the logging interval'
|
||||
}
|
||||
},
|
||||
'lr_scheduler_type': {
|
||||
'label': {
|
||||
'zh': 'LrScheduler类型',
|
||||
'en': 'The LrScheduler type'
|
||||
},
|
||||
'info': {
|
||||
'zh': '设置LrScheduler类型',
|
||||
'en': 'Set the LrScheduler type'
|
||||
}
|
||||
},
|
||||
'warmup_ratio': {
|
||||
'label': {
|
||||
'zh': '学习率warmup比例',
|
||||
'en': 'Lr warmup ratio'
|
||||
},
|
||||
'info': {
|
||||
'zh': '设置学习率warmup比例',
|
||||
'en': 'Set the warmup ratio in total steps'
|
||||
}
|
||||
},
|
||||
'truncation_strategy': {
|
||||
'label': {
|
||||
'zh': '数据集超长策略',
|
||||
'en': 'Dataset truncation strategy'
|
||||
},
|
||||
'info': {
|
||||
'zh': '如果token超长该如何处理',
|
||||
'en': 'How to deal with the rows exceed the max length'
|
||||
}
|
||||
},
|
||||
'max_steps': {
|
||||
'label': {
|
||||
'zh': '最大迭代步数',
|
||||
'en': 'Max steps',
|
||||
},
|
||||
'info': {
|
||||
'zh': '设置最大迭代步数,该值如果大于零则数据集迭代次数不生效',
|
||||
'en': 'Set the max steps, if the value > 0 then num_train_epochs has no effects',
|
||||
}
|
||||
},
|
||||
'max_grad_norm': {
|
||||
'label': {
|
||||
'zh': '梯度裁剪',
|
||||
'en': 'Max grad norm',
|
||||
},
|
||||
'info': {
|
||||
'zh': '设置梯度裁剪',
|
||||
'en': 'Set the max grad norm',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def do_build_ui(cls, base_tab: Type['BaseUI']):
|
||||
with gr.TabItem(elem_id='advanced_tab'):
|
||||
with gr.Blocks():
|
||||
with gr.Row():
|
||||
gr.Dropdown(elem_id='tuner_backend', scale=20)
|
||||
gr.Textbox(elem_id='weight_decay', lines=1, scale=20)
|
||||
gr.Textbox(elem_id='logging_steps', lines=1, scale=20)
|
||||
gr.Textbox(elem_id='lr_scheduler_type', lines=1, scale=20)
|
||||
with gr.Row():
|
||||
gr.Dropdown(elem_id='truncation_strategy', value=None, scale=20)
|
||||
gr.Textbox(elem_id='max_steps', lines=1, scale=20)
|
||||
gr.Textbox(elem_id='max_grad_norm', lines=1, scale=20)
|
||||
gr.Slider(elem_id='warmup_ratio', minimum=0.0, maximum=1.0, step=0.05, scale=20)
|
||||
@@ -0,0 +1,70 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
import gradio as gr
|
||||
from typing import Type
|
||||
|
||||
from swift.dataset import get_dataset_list
|
||||
from ..base import BaseUI
|
||||
|
||||
|
||||
class Dataset(BaseUI):
|
||||
|
||||
group = 'llm_train'
|
||||
|
||||
locale_dict = {
|
||||
'dataset': {
|
||||
'label': {
|
||||
'zh': '数据集名称',
|
||||
'en': 'Dataset Code'
|
||||
},
|
||||
'info': {
|
||||
'zh': '选择训练的数据集,支持复选/本地路径',
|
||||
'en': 'The dataset(s) to train the models, support multi select and local folder/files'
|
||||
}
|
||||
},
|
||||
'max_length': {
|
||||
'label': {
|
||||
'zh': '句子最大长度',
|
||||
'en': 'The max length',
|
||||
},
|
||||
'info': {
|
||||
'zh': '设置输入模型的最大长度',
|
||||
'en': 'Set the max length input to the model',
|
||||
}
|
||||
},
|
||||
'split_dataset_ratio': {
|
||||
'label': {
|
||||
'zh': '验证集拆分比例',
|
||||
'en': 'Split ratio of eval dataset'
|
||||
},
|
||||
'info': {
|
||||
'zh': '表示将总数据的多少拆分到验证集中',
|
||||
'en': 'Split the datasets by this ratio for eval'
|
||||
}
|
||||
},
|
||||
'padding_free': {
|
||||
'label': {
|
||||
'zh': '无填充批处理',
|
||||
'en': 'Padding-free batching'
|
||||
},
|
||||
'info': {
|
||||
'zh': '将一个batch中的数据进行展平而避免数据padding',
|
||||
'en': 'Flatten the data in a batch to avoid data padding'
|
||||
}
|
||||
},
|
||||
'dataset_param': {
|
||||
'label': {
|
||||
'zh': '数据集设置',
|
||||
'en': 'Dataset settings'
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def do_build_ui(cls, base_tab: Type['BaseUI']):
|
||||
with gr.Accordion(elem_id='dataset_param', open=True):
|
||||
with gr.Row():
|
||||
gr.Dropdown(
|
||||
elem_id='dataset', multiselect=True, choices=get_dataset_list(), scale=20, allow_custom_value=True)
|
||||
gr.Slider(elem_id='split_dataset_ratio', minimum=0.0, maximum=1.0, step=0.05, scale=10)
|
||||
gr.Slider(elem_id='max_length', minimum=32, maximum=32768, value=1024, step=1, scale=10)
|
||||
gr.Checkbox(elem_id='padding_free', scale=10)
|
||||
@@ -0,0 +1,149 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
import gradio as gr
|
||||
from typing import Type
|
||||
|
||||
from ..base import BaseUI
|
||||
|
||||
|
||||
class Hyper(BaseUI):
|
||||
|
||||
group = 'llm_train'
|
||||
|
||||
locale_dict = {
|
||||
'hyper_param': {
|
||||
'label': {
|
||||
'zh': '超参数设置(更多参数->其他参数设置)',
|
||||
'en': 'Hyper settings(more params->Extra settings)',
|
||||
},
|
||||
},
|
||||
'per_device_train_batch_size': {
|
||||
'label': {
|
||||
'zh': '训练batch size',
|
||||
'en': 'Train batch size',
|
||||
},
|
||||
'info': {
|
||||
'zh': '训练的batch size',
|
||||
'en': 'Set the train batch size',
|
||||
}
|
||||
},
|
||||
'per_device_eval_batch_size': {
|
||||
'label': {
|
||||
'zh': '验证batch size',
|
||||
'en': 'Val batch size',
|
||||
},
|
||||
'info': {
|
||||
'zh': '验证的batch size',
|
||||
'en': 'Set the val batch size',
|
||||
}
|
||||
},
|
||||
'learning_rate': {
|
||||
'label': {
|
||||
'zh': '学习率',
|
||||
'en': 'Learning rate',
|
||||
},
|
||||
'info': {
|
||||
'zh': '设置学习率',
|
||||
'en': 'Set the learning rate',
|
||||
}
|
||||
},
|
||||
'eval_steps': {
|
||||
'label': {
|
||||
'zh': '交叉验证步数',
|
||||
'en': 'Eval steps',
|
||||
},
|
||||
'info': {
|
||||
'zh': '设置每隔多少步数进行一次验证',
|
||||
'en': 'Set the step interval to validate',
|
||||
}
|
||||
},
|
||||
'num_train_epochs': {
|
||||
'label': {
|
||||
'zh': '数据集迭代轮次',
|
||||
'en': 'Train epoch',
|
||||
},
|
||||
'info': {
|
||||
'zh': '设置对数据集训练多少轮次',
|
||||
'en': 'Set the max train epoch',
|
||||
}
|
||||
},
|
||||
'gradient_accumulation_steps': {
|
||||
'label': {
|
||||
'zh': '梯度累计步数',
|
||||
'en': 'Gradient accumulation steps',
|
||||
},
|
||||
'info': {
|
||||
'zh': '设置梯度累计步数以减小显存占用',
|
||||
'en': 'Set the gradient accumulation steps',
|
||||
}
|
||||
},
|
||||
'attn_impl': {
|
||||
'label': {
|
||||
'zh': 'Flash Attention类型',
|
||||
'en': 'Flash Attention Type',
|
||||
},
|
||||
},
|
||||
'neftune_noise_alpha': {
|
||||
'label': {
|
||||
'zh': 'NEFTune噪声系数',
|
||||
'en': 'NEFTune noise coefficient'
|
||||
},
|
||||
'info': {
|
||||
'zh': '使用NEFTune提升训练效果, 一般设置为5或者10',
|
||||
'en': 'Use NEFTune to improve performance, normally the value should be 5 or 10'
|
||||
}
|
||||
},
|
||||
'save_steps': {
|
||||
'label': {
|
||||
'zh': '存储步数',
|
||||
'en': 'Save steps',
|
||||
},
|
||||
'info': {
|
||||
'zh': '设置每个多少步数进行存储',
|
||||
'en': 'Set the save steps',
|
||||
}
|
||||
},
|
||||
'output_dir': {
|
||||
'label': {
|
||||
'zh': '存储目录',
|
||||
'en': 'The output dir',
|
||||
},
|
||||
'info': {
|
||||
'zh': '设置输出模型存储在哪个文件夹下',
|
||||
'en': 'Set the output folder',
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def do_build_ui(cls, base_tab: Type['BaseUI']):
|
||||
with gr.Accordion(elem_id='hyper_param', open=False):
|
||||
with gr.Blocks():
|
||||
with gr.Row():
|
||||
gr.Slider(elem_id='per_device_train_batch_size', minimum=1, maximum=256, step=2, scale=20)
|
||||
gr.Slider(elem_id='per_device_eval_batch_size', minimum=1, maximum=256, step=2, scale=20)
|
||||
gr.Textbox(elem_id='learning_rate', value='1e-4', lines=1, scale=20)
|
||||
gr.Textbox(elem_id='num_train_epochs', lines=1, scale=20)
|
||||
gr.Slider(
|
||||
elem_id='gradient_accumulation_steps',
|
||||
minimum=1,
|
||||
maximum=256,
|
||||
step=2,
|
||||
value=1 if cls.group == 'llm_grpo' else 16,
|
||||
scale=20)
|
||||
with gr.Row():
|
||||
gr.Textbox(elem_id='eval_steps', lines=1, value='500', scale=20)
|
||||
gr.Textbox(elem_id='save_steps', value='500', lines=1, scale=20)
|
||||
gr.Textbox(elem_id='output_dir', scale=20)
|
||||
gr.Dropdown(
|
||||
elem_id='attn_impl',
|
||||
value=None,
|
||||
choices=[None, 'sdpa', 'eager', 'flash_attention_2', 'flash_attention_3', 'flash_attention_4'],
|
||||
scale=20)
|
||||
gr.Slider(elem_id='neftune_noise_alpha', minimum=0.0, maximum=20.0, step=0.5, scale=20)
|
||||
|
||||
@staticmethod
|
||||
def update_lr(tuner_type):
|
||||
if tuner_type == 'full':
|
||||
return 1e-5
|
||||
else:
|
||||
return 1e-4
|
||||
@@ -0,0 +1,579 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
import collections
|
||||
import gradio as gr
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
import sys
|
||||
import time
|
||||
from copy import deepcopy
|
||||
from functools import partial
|
||||
from json import JSONDecodeError
|
||||
from subprocess import PIPE, STDOUT, Popen
|
||||
from transformers.utils import is_torch_cuda_available, is_torch_npu_available
|
||||
from typing import Dict, Type
|
||||
|
||||
from swift.arguments import ExportArguments, RLHFArguments, get_supported_tuners
|
||||
from swift.utils import get_device_count, get_logger
|
||||
from ..base import BaseUI
|
||||
from .advanced import Advanced
|
||||
from .dataset import Dataset
|
||||
from .hyper import Hyper
|
||||
from .model import Model
|
||||
from .optimizer import Optimizer
|
||||
from .quantization import Quantization
|
||||
from .report_to import ReportTo
|
||||
from .runtime import Runtime
|
||||
from .save import Save
|
||||
from .self_cog import SelfCog
|
||||
from .task import Task
|
||||
from .tuner import Tuner
|
||||
from .utils import run_command_in_background_with_popen
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
|
||||
class LLMTrain(BaseUI):
|
||||
group = 'llm_train'
|
||||
|
||||
sub_ui = [
|
||||
Model,
|
||||
Dataset,
|
||||
Runtime,
|
||||
Save,
|
||||
Optimizer,
|
||||
Task,
|
||||
Tuner,
|
||||
Hyper,
|
||||
Quantization,
|
||||
SelfCog,
|
||||
Advanced,
|
||||
ReportTo,
|
||||
]
|
||||
|
||||
locale_dict: Dict[str, Dict] = {
|
||||
'llm_train': {
|
||||
'label': {
|
||||
'zh': 'LLM预训练/微调',
|
||||
'en': 'LLM PT/SFT',
|
||||
}
|
||||
},
|
||||
'train_stage': {
|
||||
'label': {
|
||||
'zh': '训练Stage',
|
||||
'en': 'Train Stage'
|
||||
},
|
||||
'info': {
|
||||
'zh': '请注意选择与此匹配的数据集',
|
||||
'en': 'Please choose matched dataset'
|
||||
}
|
||||
},
|
||||
'submit_alert': {
|
||||
'value': {
|
||||
'zh':
|
||||
'任务已开始,请查看tensorboard或日志记录,请勿关闭终端,否则训练过程将被打断',
|
||||
'en':
|
||||
'Task started, please check the tensorboard or log file, '
|
||||
'do not close the terminal, otherwise the training process will be interrupted'
|
||||
}
|
||||
},
|
||||
'dataset_alert': {
|
||||
'value': {
|
||||
'zh': '请选择或填入一个数据集',
|
||||
'en': 'Please input or select a dataset'
|
||||
}
|
||||
},
|
||||
'submit': {
|
||||
'value': {
|
||||
'zh': '🚀 开始训练',
|
||||
'en': '🚀 Begin'
|
||||
}
|
||||
},
|
||||
'dry_run': {
|
||||
'label': {
|
||||
'zh': '仅生成运行命令',
|
||||
'en': 'Dry-run'
|
||||
},
|
||||
'info': {
|
||||
'zh': '仅生成运行命令,开发者自行运行',
|
||||
'en': 'Generate run command only, for manually running'
|
||||
}
|
||||
},
|
||||
'gpu_id': {
|
||||
'label': {
|
||||
'zh': '选择可用GPU',
|
||||
'en': 'Choose GPU'
|
||||
},
|
||||
'info': {
|
||||
'zh': '选择训练使用的GPU号,如CUDA不可用只能选择CPU',
|
||||
'en': 'Select GPU to train'
|
||||
}
|
||||
},
|
||||
'tuner_type': {
|
||||
'label': {
|
||||
'zh': '训练方式',
|
||||
'en': 'Train type'
|
||||
},
|
||||
'info': {
|
||||
'zh': '选择训练的方式',
|
||||
'en': 'Select the tuner type'
|
||||
}
|
||||
},
|
||||
'seed': {
|
||||
'label': {
|
||||
'zh': '随机数种子',
|
||||
'en': 'Seed'
|
||||
},
|
||||
'info': {
|
||||
'zh': '选择随机数种子',
|
||||
'en': 'Select a random seed'
|
||||
}
|
||||
},
|
||||
'torch_dtype': {
|
||||
'label': {
|
||||
'zh': '训练精度',
|
||||
'en': 'Training Precision'
|
||||
},
|
||||
'info': {
|
||||
'zh': '选择训练精度',
|
||||
'en': 'Select the training precision'
|
||||
}
|
||||
},
|
||||
'envs': {
|
||||
'label': {
|
||||
'zh': '环境变量',
|
||||
'en': 'Extra env vars'
|
||||
},
|
||||
},
|
||||
'use_ddp': {
|
||||
'label': {
|
||||
'zh': '使用DDP',
|
||||
'en': 'Use DDP'
|
||||
},
|
||||
'info': {
|
||||
'zh': '是否使用数据并行训练',
|
||||
'en': 'Use Distributed Data Parallel to train'
|
||||
}
|
||||
},
|
||||
'ddp_num': {
|
||||
'label': {
|
||||
'zh': 'DDP分片数量',
|
||||
'en': 'Number of DDP sharding'
|
||||
},
|
||||
'info': {
|
||||
'zh': '启用多少进程的数据并行',
|
||||
'en': 'The data parallel size of DDP'
|
||||
}
|
||||
},
|
||||
'use_liger_kernel': {
|
||||
'label': {
|
||||
'zh': '使用Liger kernel',
|
||||
'en': 'Use Liger kernel'
|
||||
},
|
||||
'info': {
|
||||
'zh': 'Liger kernel可以有效降低显存使用',
|
||||
'en': 'Liger kernel can reduce memory usage'
|
||||
}
|
||||
},
|
||||
'sequence_parallel_size': {
|
||||
'label': {
|
||||
'zh': '序列并行大小',
|
||||
'en': 'Sequence parallel size',
|
||||
},
|
||||
'info': {
|
||||
'zh': '当前支持CPT/SFT/DPO/GRPO',
|
||||
'en': 'Currently supports CPT/SFT/DPO/GRPO',
|
||||
}
|
||||
},
|
||||
'deepspeed': {
|
||||
'label': {
|
||||
'zh': 'DeepSpeed',
|
||||
'en': 'DeepSpeed',
|
||||
},
|
||||
'info': {
|
||||
'zh': '可以选择下拉列表,也支持传入路径',
|
||||
'en': 'Choose from the dropbox or fill in a valid path',
|
||||
}
|
||||
},
|
||||
'resume_checkpoint_alert': {
|
||||
'value': {
|
||||
'zh': '检测到"args.json"在{}中,将从此检查点开始断点续训',
|
||||
'en': 'Detected that "args.json" is in {}, will start breakpoint resume training from this checkpoint'
|
||||
}
|
||||
},
|
||||
'resume_only_model_alert': {
|
||||
'value': {
|
||||
'zh':
|
||||
'检测到"args.json"在{}中,但未检测到优化器参数,将仅加载模型参数开始断点续训',
|
||||
'en':
|
||||
'"args.json" is detected in {}, but optimizer parameters are not detected. '
|
||||
'Only model parameters will be loaded to start breakpoint continuation training'
|
||||
}
|
||||
},
|
||||
'more_params': {
|
||||
'label': {
|
||||
'zh': '其他高级参数',
|
||||
'en': 'Other params'
|
||||
},
|
||||
'info': {
|
||||
'zh': '以json格式或--xxx xxx命令行格式填入',
|
||||
'en': 'Fill in with json format or --xxx xxx cmd format'
|
||||
}
|
||||
},
|
||||
'extra_params': {
|
||||
'label': {
|
||||
'zh': '其他参数设置',
|
||||
'en': 'Extra settings'
|
||||
},
|
||||
},
|
||||
'train_param': {
|
||||
'label': {
|
||||
'zh': '训练参数设置',
|
||||
'en': 'Train settings'
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
choice_dict = BaseUI.get_choices_from_dataclass(RLHFArguments)
|
||||
default_dict = BaseUI.get_default_value_from_dataclass(RLHFArguments)
|
||||
arguments = BaseUI.get_argument_names(RLHFArguments)
|
||||
|
||||
@classmethod
|
||||
def do_build_ui(cls, base_tab: Type['BaseUI']):
|
||||
with gr.TabItem(elem_id='llm_train', label=''):
|
||||
default_device = 'cpu'
|
||||
device_count = get_device_count()
|
||||
if device_count > 0:
|
||||
default_device = '0'
|
||||
with gr.Blocks():
|
||||
Model.build_ui(base_tab)
|
||||
Dataset.build_ui(base_tab)
|
||||
with gr.Accordion(elem_id='train_param', open=True):
|
||||
with gr.Row():
|
||||
gr.Dropdown(elem_id='train_stage', choices=['pt', 'sft'], value='sft', scale=4)
|
||||
gr.Dropdown(elem_id='tuner_type', scale=4, choices=list(get_supported_tuners()))
|
||||
gr.Textbox(elem_id='seed', scale=4)
|
||||
gr.Dropdown(elem_id='torch_dtype', scale=4)
|
||||
gr.Checkbox(elem_id='use_liger_kernel', scale=4)
|
||||
with gr.Row():
|
||||
gr.Dropdown(
|
||||
elem_id='gpu_id',
|
||||
multiselect=True,
|
||||
choices=[str(i) for i in range(device_count)] + ['cpu'],
|
||||
value=default_device,
|
||||
scale=4)
|
||||
gr.Checkbox(elem_id='use_ddp', value=False, scale=4)
|
||||
gr.Textbox(elem_id='ddp_num', value='1', scale=4)
|
||||
gr.Dropdown(
|
||||
elem_id='deepspeed',
|
||||
scale=4,
|
||||
allow_custom_value=True,
|
||||
value=None,
|
||||
choices=['zero0', 'zero1', 'zero2', 'zero3', 'zero2_offload', 'zero3_offload'])
|
||||
gr.Textbox(elem_id='sequence_parallel_size', lines=1, scale=4)
|
||||
Hyper.build_ui(base_tab)
|
||||
Runtime.build_ui(base_tab)
|
||||
with gr.Row(equal_height=True):
|
||||
gr.Textbox(elem_id='envs', scale=12)
|
||||
gr.Checkbox(elem_id='dry_run', value=False, scale=4)
|
||||
submit = gr.Button(elem_id='submit', scale=4, variant='primary')
|
||||
|
||||
Tuner.build_ui(base_tab)
|
||||
Optimizer.build_ui(base_tab)
|
||||
Task.build_ui(base_tab)
|
||||
with gr.Accordion(elem_id='extra_params', open=False):
|
||||
with gr.Tabs():
|
||||
Advanced.build_ui(base_tab)
|
||||
Quantization.build_ui(base_tab)
|
||||
SelfCog.build_ui(base_tab)
|
||||
Save.build_ui(base_tab)
|
||||
ReportTo.build_ui(base_tab)
|
||||
with gr.Row():
|
||||
gr.Textbox(elem_id='more_params', lines=4, scale=20)
|
||||
|
||||
cls.element('tuner_type').change(
|
||||
Hyper.update_lr, inputs=[base_tab.element('tuner_type')], outputs=[cls.element('learning_rate')])
|
||||
|
||||
submit.click(cls.train_local, list(cls.valid_elements().values()), [
|
||||
cls.element('running_cmd'),
|
||||
cls.element('logging_dir'),
|
||||
cls.element('runtime_tab'),
|
||||
cls.element('running_tasks'),
|
||||
cls.element('train_record'),
|
||||
])
|
||||
|
||||
base_tab.element('gpu_id').change(
|
||||
cls.update_ddp_num,
|
||||
[base_tab.element('gpu_id'), base_tab.element('use_ddp')], base_tab.element('ddp_num'))
|
||||
base_tab.element('use_ddp').change(
|
||||
cls.update_ddp_num,
|
||||
[base_tab.element('gpu_id'), base_tab.element('use_ddp')], base_tab.element('ddp_num'))
|
||||
base_tab.element('running_tasks').change(
|
||||
partial(Runtime.task_changed, base_tab=base_tab), [base_tab.element('running_tasks')],
|
||||
list(base_tab.valid_elements().values()) + [cls.element('log')] + Runtime.all_plots)
|
||||
Runtime.element('kill_task').click(
|
||||
Runtime.kill_task,
|
||||
[Runtime.element('running_tasks')],
|
||||
[Runtime.element('running_tasks')] + [Runtime.element('log')] + Runtime.all_plots,
|
||||
).then(Runtime.reset, [], [Runtime.element('logging_dir')] + [Hyper.element('output_dir')])
|
||||
|
||||
@classmethod
|
||||
def update_runtime(cls):
|
||||
return gr.update(open=True), gr.update(visible=True)
|
||||
|
||||
@classmethod
|
||||
def train(cls, *args):
|
||||
ignore_elements = ('logging_dir', 'more_params', 'train_stage', 'envs')
|
||||
default_args = cls.get_default_value_from_dataclass(RLHFArguments)
|
||||
extra_default_args = cls.get_default_value_from_dataclass(ExportArguments)
|
||||
kwargs = {}
|
||||
kwargs_is_list = {}
|
||||
other_kwargs = {}
|
||||
more_params = {}
|
||||
more_params_cmd = ''
|
||||
keys = cls.valid_element_keys()
|
||||
if cls.group in ('llm_grpo', 'llm_rlhf'):
|
||||
train_stage = 'rlhf'
|
||||
else:
|
||||
train_stage = 'sft'
|
||||
for key, value in zip(keys, args):
|
||||
compare_value = default_args.get(key) if key != 'hub_private_repo' else extra_default_args.get(key)
|
||||
if isinstance(value, str) and re.fullmatch(cls.int_regex, value):
|
||||
value = int(value)
|
||||
elif isinstance(value, str) and re.fullmatch(cls.float_regex, value):
|
||||
value = float(value)
|
||||
elif isinstance(value, str) and re.fullmatch(cls.bool_regex, value):
|
||||
value = True if value.lower() == 'true' else False
|
||||
if compare_value in ('true', 'false'):
|
||||
value = str(value).lower()
|
||||
if key not in ignore_elements and key in default_args and compare_value != value and (value or value
|
||||
in (0, False)):
|
||||
kwargs[key] = value if not isinstance(value, list) else ' '.join(value)
|
||||
kwargs_is_list[key] = isinstance(value, list) or getattr(cls.element(key), 'is_list', False)
|
||||
else:
|
||||
other_kwargs[key] = value
|
||||
if key == 'more_params' and value:
|
||||
try:
|
||||
more_params = json.loads(value)
|
||||
except (JSONDecodeError or TypeError):
|
||||
more_params_cmd = value
|
||||
|
||||
if key == 'train_stage':
|
||||
train_stage = value
|
||||
|
||||
model = kwargs.get('model')
|
||||
if '-merged' not in model and os.path.exists(model) and os.path.exists(os.path.join(model, 'args.json')):
|
||||
ckpt_dir = kwargs.pop('model')
|
||||
with open(os.path.join(ckpt_dir, 'args.json'), 'r', encoding='utf-8') as f:
|
||||
_json = json.load(f)
|
||||
kwargs['model'] = _json['model_dir']
|
||||
kwargs['model_type'] = _json['model_type']
|
||||
kwargs['template'] = _json['template']
|
||||
if os.path.exists(os.path.join(ckpt_dir, 'scheduler.pt')):
|
||||
kwargs['resume_from_checkpoint'] = ckpt_dir
|
||||
gr.Info(cls.locale('resume_checkpoint_alert', cls.lang)['value'].format(ckpt_dir))
|
||||
else:
|
||||
kwargs['resume_from_checkpoint'] = ckpt_dir
|
||||
kwargs['resume_only_model'] = True
|
||||
gr.Info(cls.locale('resume_only_model_alert', cls.lang)['value'].format(ckpt_dir))
|
||||
|
||||
model = kwargs.get('model')
|
||||
kwargs.update(more_params)
|
||||
if 'dataset' not in kwargs and 'custom_train_dataset_path' not in kwargs:
|
||||
raise gr.Error(cls.locale('dataset_alert', cls.lang)['value'])
|
||||
|
||||
cmd = train_stage
|
||||
if kwargs.get('deepspeed'):
|
||||
more_params_cmd += f' --deepspeed {kwargs.pop("deepspeed")} '
|
||||
use_liger_kernel = kwargs.get('use_liger_kernel', None)
|
||||
if use_liger_kernel:
|
||||
kwargs.pop('use_liger_kernel')
|
||||
if other_kwargs.get('use_muon'):
|
||||
kwargs['use_muon'] = other_kwargs.pop('use_muon')
|
||||
|
||||
# filter kwargs
|
||||
tabs_relation_dict = cls.prepare_sub_to_filter()
|
||||
cls.remove_useless_args(kwargs, tabs_relation_dict)
|
||||
use_muon = kwargs.pop('use_muon', None)
|
||||
if cls.group == 'llm_rlhf':
|
||||
cls.filter_rlhf_args(kwargs)
|
||||
try:
|
||||
sft_args = RLHFArguments(
|
||||
**{
|
||||
key: value.split(' ') if kwargs_is_list.get(key, False) and isinstance(value, str) else value
|
||||
for key, value in kwargs.items()
|
||||
})
|
||||
except Exception as e:
|
||||
raise e
|
||||
params = ''
|
||||
command = ['swift', cmd]
|
||||
if cls.group == 'llm_grpo' and sys.platform != 'win32':
|
||||
params += f'--rlhf_type {cls.quote}grpo{cls.quote} '
|
||||
command.extend(['--rlhf_type', 'grpo'])
|
||||
|
||||
sep = f'{cls.quote} {cls.quote}'
|
||||
for e in kwargs:
|
||||
if isinstance(kwargs[e], list):
|
||||
params += f'--{e} {cls.quote}{sep.join(kwargs[e])}{cls.quote} '
|
||||
command.extend([f'--{e}'] + kwargs[e])
|
||||
elif e in kwargs_is_list and kwargs_is_list[e]:
|
||||
all_args = [arg for arg in kwargs[e].split(' ') if arg.strip()]
|
||||
params += f'--{e} {cls.quote}{sep.join(all_args)}{cls.quote} '
|
||||
command.extend([f'--{e}'] + all_args)
|
||||
else:
|
||||
params += f'--{e} {cls.quote}{kwargs[e]}{cls.quote} '
|
||||
command.extend([f'--{e}', f'{kwargs[e]}'])
|
||||
if use_liger_kernel:
|
||||
params += f'--use_liger_kernel {cls.quote}{use_liger_kernel}{cls.quote} '
|
||||
command.extend(['--use_liger_kernel', f'{use_liger_kernel}'])
|
||||
if use_muon:
|
||||
params += f'--optimizer {cls.quote}muon{cls.quote} '
|
||||
command.extend(['--optimizer', 'muon'])
|
||||
more_params_cmd = more_params_cmd.strip()
|
||||
if more_params_cmd != '':
|
||||
params += f'{more_params_cmd} '
|
||||
more_params_cmd = [param.strip() for param in more_params_cmd.split('--')]
|
||||
more_params_cmd = [param.split(' ') for param in more_params_cmd if param]
|
||||
for param in more_params_cmd:
|
||||
command.extend([f'--{param[0]}'] + param[1:])
|
||||
params += f'--add_version False --output_dir {sft_args.output_dir} ' \
|
||||
f'--logging_dir {sft_args.logging_dir} --ignore_args_error True'
|
||||
command.extend([
|
||||
'--add_version', 'False', '--output_dir', f'{sft_args.output_dir}', '--logging_dir',
|
||||
f'{sft_args.logging_dir}', '--ignore_args_error', 'True'
|
||||
])
|
||||
all_envs = {}
|
||||
ddp_param = ''
|
||||
devices = other_kwargs['gpu_id']
|
||||
envs = other_kwargs['envs'] or ''
|
||||
envs = envs.strip()
|
||||
devices = [d for d in devices if d]
|
||||
if other_kwargs['use_ddp']:
|
||||
assert int(other_kwargs['ddp_num']) > 0
|
||||
ddp_param = f'NPROC_PER_NODE={int(other_kwargs["ddp_num"])}'
|
||||
all_envs['NPROC_PER_NODE'] = str(other_kwargs['ddp_num'])
|
||||
assert (len(devices) == 1 or 'cpu' not in devices)
|
||||
gpus = ','.join(devices)
|
||||
cuda_param = ''
|
||||
if gpus != 'cpu':
|
||||
if is_torch_npu_available():
|
||||
cuda_param = f'ASCEND_RT_VISIBLE_DEVICES={gpus}'
|
||||
all_envs['ASCEND_RT_VISIBLE_DEVICES'] = gpus
|
||||
elif is_torch_cuda_available():
|
||||
cuda_param = f'CUDA_VISIBLE_DEVICES={gpus}'
|
||||
all_envs['CUDA_VISIBLE_DEVICES'] = gpus
|
||||
else:
|
||||
cuda_param = ''
|
||||
if envs:
|
||||
env_list = envs.split(' ')
|
||||
for env in env_list:
|
||||
k, v = env.split('=')
|
||||
all_envs[k] = v
|
||||
log_file = os.path.join(sft_args.logging_dir, 'run.log')
|
||||
if sys.platform == 'win32':
|
||||
if cuda_param:
|
||||
cuda_param = f'set {cuda_param} && '
|
||||
if ddp_param:
|
||||
ddp_param = f'set {ddp_param} && '
|
||||
if envs:
|
||||
envs = [env.strip() for env in envs.split(' ') if env.strip()]
|
||||
_envs = ''
|
||||
for env in envs:
|
||||
_envs += f'set {env} && '
|
||||
envs = _envs
|
||||
run_command = f'{cuda_param}{ddp_param}{envs}start /b swift sft {params} > {log_file} 2>&1'
|
||||
else:
|
||||
run_command = f'{cuda_param} {ddp_param} {envs} nohup swift {cmd} {params} > {log_file} 2>&1 &'
|
||||
logger.info(f'Run training: {run_command}')
|
||||
if model:
|
||||
record = {}
|
||||
for key, value in zip(keys, args):
|
||||
if key in default_args or key in ('more_params', 'train_stage', 'use_ddp', 'ddp_num', 'gpu_id', 'envs'):
|
||||
record[key] = value or None
|
||||
cls.save_cache(model, record)
|
||||
return command, all_envs, log_file, run_command, sft_args, other_kwargs
|
||||
|
||||
@classmethod
|
||||
def train_studio(cls, *args):
|
||||
command, all_envs, log_file, run_command, sft_args, other_kwargs = cls.train(*args)
|
||||
if not other_kwargs['dry_run']:
|
||||
lines = collections.deque(maxlen=int(os.environ.get('MAX_LOG_LINES', 50)))
|
||||
env = deepcopy(os.environ)
|
||||
if len(all_envs) > 0:
|
||||
for k, v in all_envs.items():
|
||||
env[k] = v
|
||||
process = Popen(command, env=env, stdout=PIPE, stderr=STDOUT)
|
||||
with process.stdout:
|
||||
for line in iter(process.stdout.readline, b''):
|
||||
line = line.decode('utf-8')
|
||||
lines.append(line)
|
||||
yield ['\n'.join(lines)] + Runtime.plot(run_command) + [run_command]
|
||||
else:
|
||||
yield [
|
||||
'Current is dryrun mode so you can only view the training cmd, please duplicate this space to '
|
||||
'do training or use with inference.'
|
||||
] + [None] * len(Runtime.sft_plot) + [run_command]
|
||||
|
||||
@classmethod
|
||||
def train_local(cls, *args):
|
||||
command, all_envs, log_file, run_command, sft_args, other_kwargs = cls.train(*args)
|
||||
if cls.group == 'llm_grpo' and sft_args.vllm_mode == 'server':
|
||||
host = sft_args.vllm_server_host if sft_args.vllm_server_host else '127.0.0.1'
|
||||
port = sft_args.vllm_server_port if sft_args.vllm_server_port else '8000'
|
||||
try:
|
||||
import requests
|
||||
headers = {'Accept': 'application/json'}
|
||||
url = f'http://{host}:{port}/health/'
|
||||
response = requests.get(url, headers=headers)
|
||||
res = response.json()
|
||||
assert res['status'] == 'ok', 'statue must be ok'
|
||||
except Exception as err:
|
||||
gr.Info(cls.locale('external_alert', cls.lang)['value'].format(err))
|
||||
return [None] * 2 + [gr.update(open=False)] + [None] * 2
|
||||
if not other_kwargs['dry_run']:
|
||||
os.makedirs(sft_args.logging_dir, exist_ok=True)
|
||||
run_command_in_background_with_popen(command, all_envs, log_file)
|
||||
time.sleep(1) # to make sure the log file has been created.
|
||||
gr.Info(cls.locale('submit_alert', cls.lang)['value'])
|
||||
return run_command, sft_args.logging_dir, gr.update(open=True), Runtime.refresh_tasks(
|
||||
sft_args.output_dir, cls.group), gr.update(choices=cls.list_cache(sft_args.model))
|
||||
|
||||
@classmethod
|
||||
def prepare_sub_to_filter(cls):
|
||||
tabs_relation_dict = {
|
||||
key: val
|
||||
for key, val in zip(['tuner_type', 'optimizer', 'task_type'],
|
||||
[Tuner.tabs_to_filter, Optimizer.tabs_to_filter, Task.tabs_to_filter])
|
||||
}
|
||||
return tabs_relation_dict
|
||||
|
||||
@classmethod
|
||||
def remove_useless_args(cls, uncleaned_kwargs, tabs_relation_dict):
|
||||
for target, tabs_to_filter in tabs_relation_dict.items():
|
||||
target_value = uncleaned_kwargs.get(target)
|
||||
if target == 'tuner_type' and target_value is None:
|
||||
target_value = 'lora'
|
||||
elif target == 'vllm_mode' and target_value is None:
|
||||
target_value = 'colocate'
|
||||
elif target == 'optimizer':
|
||||
if uncleaned_kwargs.get('use_galore'):
|
||||
target_value = 'galore'
|
||||
if uncleaned_kwargs.get('lorap_lr_ratio'):
|
||||
target_value = 'lorap'
|
||||
if uncleaned_kwargs.get('vit_lr') or uncleaned_kwargs.get('aligner_lr'):
|
||||
target_value = 'multimodal'
|
||||
if uncleaned_kwargs.get('use_muon'):
|
||||
target_value = 'muon'
|
||||
|
||||
for tab_key in tabs_to_filter.keys():
|
||||
if tab_key == 'lora' and target_value in ('longlora', 'adalora'):
|
||||
continue
|
||||
if tab_key == 'lisa' and target_value == 'full' and uncleaned_kwargs.get('lisa_activated_layers'):
|
||||
continue
|
||||
if tab_key == 'lora_ga' and target_value == 'lora' and uncleaned_kwargs.get(
|
||||
'init_weights') == 'lora-ga':
|
||||
continue
|
||||
if tab_key != target_value:
|
||||
for arg in tabs_to_filter[tab_key]:
|
||||
if uncleaned_kwargs.get(arg) is not None:
|
||||
uncleaned_kwargs.pop(arg)
|
||||
@@ -0,0 +1,68 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
import gradio as gr
|
||||
from typing import Type
|
||||
|
||||
from ..base import BaseUI
|
||||
|
||||
|
||||
class LoRA(BaseUI):
|
||||
|
||||
group = 'llm_train'
|
||||
|
||||
locale_dict = {
|
||||
'lora_tab': {
|
||||
'label': {
|
||||
'zh': 'LoRA参数设置',
|
||||
'en': 'LoRA settings'
|
||||
},
|
||||
},
|
||||
'lora_rank': {
|
||||
'label': {
|
||||
'zh': 'LoRA的秩',
|
||||
'en': 'The LoRA rank'
|
||||
}
|
||||
},
|
||||
'lora_alpha': {
|
||||
'label': {
|
||||
'zh': 'LoRA的缩放因子',
|
||||
'en': 'The LoRA alpha'
|
||||
}
|
||||
},
|
||||
'lora_dropout': {
|
||||
'label': {
|
||||
'zh': 'LoRA的丢弃概率',
|
||||
'en': 'The LoRA dropout'
|
||||
}
|
||||
},
|
||||
'use_rslora': {
|
||||
'label': {
|
||||
'zh': '使用rsLoRA',
|
||||
'en': 'Use rsLoRA'
|
||||
}
|
||||
},
|
||||
'use_dora': {
|
||||
'label': {
|
||||
'zh': '使用DoRA',
|
||||
'en': 'Use DoRA'
|
||||
}
|
||||
},
|
||||
'lora_dtype': {
|
||||
'label': {
|
||||
'zh': 'LoRA部分的参数类型',
|
||||
'en': 'The dtype of LoRA'
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def do_build_ui(cls, base_tab: Type['BaseUI']):
|
||||
with gr.TabItem(elem_id='lora_tab'):
|
||||
with gr.Blocks():
|
||||
with gr.Row():
|
||||
gr.Slider(elem_id='lora_rank', value=8, minimum=1, maximum=512, step=8, scale=2)
|
||||
gr.Slider(elem_id='lora_alpha', value=32, minimum=1, maximum=512, step=8, scale=2)
|
||||
gr.Textbox(elem_id='lora_dropout', scale=2)
|
||||
with gr.Row():
|
||||
gr.Dropdown(elem_id='lora_dtype', scale=2, value=None)
|
||||
gr.Checkbox(elem_id='use_rslora', scale=2)
|
||||
gr.Checkbox(elem_id='use_dora', scale=2)
|
||||
@@ -0,0 +1,127 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
import gradio as gr
|
||||
from functools import partial
|
||||
from typing import Type
|
||||
|
||||
from swift.arguments import RLHFArguments
|
||||
from swift.model import ModelType, get_model_list
|
||||
from swift.template import TEMPLATE_MAPPING
|
||||
from ..base import BaseUI
|
||||
|
||||
|
||||
class Model(BaseUI):
|
||||
group = 'llm_train'
|
||||
|
||||
locale_dict = {
|
||||
'model_type': {
|
||||
'label': {
|
||||
'zh': '模型类型',
|
||||
'en': 'Select Model Type'
|
||||
},
|
||||
'info': {
|
||||
'zh': 'SWIFT已支持的模型类型',
|
||||
'en': 'Base model type supported by SWIFT'
|
||||
}
|
||||
},
|
||||
'model': {
|
||||
'label': {
|
||||
'zh': '模型id或路径',
|
||||
'en': 'Model id or path'
|
||||
},
|
||||
'info': {
|
||||
'zh': '实际的模型id',
|
||||
'en': 'The actual model id or model path'
|
||||
}
|
||||
},
|
||||
'template': {
|
||||
'label': {
|
||||
'zh': '模型Prompt模板类型',
|
||||
'en': 'Prompt template type'
|
||||
},
|
||||
'info': {
|
||||
'zh': '选择匹配模型的Prompt模板',
|
||||
'en': 'Choose the template type of the model'
|
||||
}
|
||||
},
|
||||
'system': {
|
||||
'label': {
|
||||
'zh': 'System字段',
|
||||
'en': 'System'
|
||||
},
|
||||
'info': {
|
||||
'zh': '选择system字段的内容',
|
||||
'en': 'Choose the content of the system field'
|
||||
}
|
||||
},
|
||||
'reset': {
|
||||
'value': {
|
||||
'zh': '恢复模型初始值',
|
||||
'en': 'Reset model default'
|
||||
},
|
||||
},
|
||||
'train_record': {
|
||||
'label': {
|
||||
'zh': '训练记录',
|
||||
'en': 'Train record'
|
||||
},
|
||||
'info': {
|
||||
'zh': '展示使用web-ui的历史训练及参数',
|
||||
'en': 'Show the training history and parameters'
|
||||
}
|
||||
},
|
||||
'clear_cache': {
|
||||
'value': {
|
||||
'zh': '删除训练记录',
|
||||
'en': 'Delete train records'
|
||||
},
|
||||
},
|
||||
'model_param': {
|
||||
'label': {
|
||||
'zh': '模型设置',
|
||||
'en': 'Model settings'
|
||||
},
|
||||
},
|
||||
'checkpoint': {
|
||||
'value': {
|
||||
'zh': '训练后的模型',
|
||||
'en': 'Trained model'
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def do_build_ui(cls, base_tab: Type['BaseUI']):
|
||||
with gr.Accordion(elem_id='model_param', open=True):
|
||||
with gr.Row(equal_height=True):
|
||||
model = gr.Dropdown(
|
||||
elem_id='model',
|
||||
scale=20,
|
||||
choices=get_model_list(),
|
||||
value='Qwen/Qwen2.5-7B-Instruct',
|
||||
allow_custom_value=True)
|
||||
gr.Dropdown(elem_id='model_type', choices=ModelType.get_model_name_list(), scale=20)
|
||||
gr.Dropdown(elem_id='template', choices=list(TEMPLATE_MAPPING.keys()), scale=20)
|
||||
train_record = gr.Dropdown(elem_id='train_record', choices=[], scale=20)
|
||||
clear_cache = gr.Button(elem_id='clear_cache', scale=2)
|
||||
with gr.Row():
|
||||
gr.Textbox(elem_id='system', lines=4 if cls.group == 'llm_grpo' else 1, scale=20)
|
||||
|
||||
def clear_record(model):
|
||||
if model:
|
||||
cls.clear_cache(model)
|
||||
return gr.update(choices=[])
|
||||
return gr.update()
|
||||
|
||||
clear_cache.click(clear_record, inputs=[model], outputs=[train_record])
|
||||
|
||||
@classmethod
|
||||
def after_build_ui(cls, base_tab: Type['BaseUI']):
|
||||
cls.element('model').change(
|
||||
partial(base_tab.update_input_model, arg_cls=RLHFArguments),
|
||||
inputs=[cls.element('model')],
|
||||
outputs=[cls.element('train_record')] + list(base_tab.valid_elements().values()))
|
||||
|
||||
cls.element('train_record').change(
|
||||
partial(base_tab.update_all_settings, base_tab=base_tab),
|
||||
inputs=[cls.element('model'), cls.element('train_record')],
|
||||
outputs=list(base_tab.valid_elements().values()))
|
||||
@@ -0,0 +1,139 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
import gradio as gr
|
||||
from typing import Type
|
||||
|
||||
from ..base import BaseUI
|
||||
|
||||
|
||||
class Optimizer(BaseUI):
|
||||
|
||||
group = 'llm_train'
|
||||
|
||||
locale_dict = {
|
||||
'galore_tab': {
|
||||
'label': {
|
||||
'zh': 'GaLore参数设置',
|
||||
'en': 'GaLore Settings'
|
||||
},
|
||||
},
|
||||
'use_galore': {
|
||||
'label': {
|
||||
'zh': '使用GaLore',
|
||||
'en': 'Use GaLore'
|
||||
},
|
||||
'info': {
|
||||
'zh': '使用GaLore来减少全参数训练的显存消耗',
|
||||
'en': 'Use GaLore to reduce GPU memory usage in full parameter training'
|
||||
}
|
||||
},
|
||||
'galore_rank': {
|
||||
'label': {
|
||||
'zh': 'GaLore的秩',
|
||||
'en': 'The rank of GaLore'
|
||||
},
|
||||
},
|
||||
'galore_update_proj_gap': {
|
||||
'label': {
|
||||
'zh': '投影矩阵更新间隔',
|
||||
'en': 'Projection matrix update interval'
|
||||
},
|
||||
'info': {
|
||||
'zh': 'GaLore分解矩阵的更新间隔',
|
||||
'en': 'Update interval of GaLore decomposition matrix'
|
||||
},
|
||||
},
|
||||
'galore_with_embedding': {
|
||||
'label': {
|
||||
'zh': '对嵌入层应用GaLore',
|
||||
'en': 'Use GaLore with embedding'
|
||||
},
|
||||
'info': {
|
||||
'zh': '是否对嵌入层应用GaLore',
|
||||
'en': 'Whether to apply GaLore to embedding'
|
||||
},
|
||||
},
|
||||
'lorap_tab': {
|
||||
'label': {
|
||||
'zh': 'LoRA+参数设置',
|
||||
'en': 'LoRA+ settings'
|
||||
},
|
||||
},
|
||||
'lorap_lr_ratio': {
|
||||
'label': {
|
||||
'zh': 'LoRA+学习率比率',
|
||||
'en': 'LoRA+ lr ratio'
|
||||
},
|
||||
'info': {
|
||||
'zh': '使用LoRA时指定该参数可使用LoRA+,建议值10~16',
|
||||
'en': 'When using LoRA, specify this parameter to use LoRA+, and the recommended value is 10 to 16'
|
||||
}
|
||||
},
|
||||
'muon_tab': {
|
||||
'label': {
|
||||
'zh': 'Muon参数设置',
|
||||
'en': 'Muon Settings'
|
||||
},
|
||||
},
|
||||
'use_muon': {
|
||||
'label': {
|
||||
'zh': '使用Muon',
|
||||
'en': 'Use Muon'
|
||||
},
|
||||
'info': {
|
||||
'zh': '使用Muon优化器,将在命令行参数中设置`--optimizer muon`',
|
||||
'en': 'Using the Muon optimizer, set `--optimizer muon` in the command line'
|
||||
}
|
||||
},
|
||||
'multimodal_tab': {
|
||||
'label': {
|
||||
'zh': '多模态参数设置',
|
||||
'en': 'Multimodal Settings'
|
||||
},
|
||||
},
|
||||
'vit_lr': {
|
||||
'label': {
|
||||
'zh': 'ViT的学习率',
|
||||
'en': 'Learning rate of ViT'
|
||||
},
|
||||
},
|
||||
'aligner_lr': {
|
||||
'label': {
|
||||
'zh': 'Aligner的学习率',
|
||||
'en': 'Learning rate of aligner'
|
||||
},
|
||||
},
|
||||
'optimizer_params': {
|
||||
'label': {
|
||||
'zh': '优化器参数',
|
||||
'en': 'Optimizer params'
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
tabs_to_filter = {
|
||||
'galore': ['use_galore', 'galore_with_embedding', 'galore_rank', 'galore_update_proj_gap'],
|
||||
'lorap': ['lorap_lr_ratio'],
|
||||
'multimodal': ['vit_lr', 'aligner_lr'],
|
||||
'muon': ['use_muon']
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def do_build_ui(cls, base_tab: Type['BaseUI']):
|
||||
with gr.Accordion(elem_id='optimizer_params', open=False):
|
||||
with gr.Tabs():
|
||||
with gr.TabItem(elem_id='galore_tab'):
|
||||
with gr.Row():
|
||||
gr.Checkbox(elem_id='use_galore', scale=4)
|
||||
gr.Checkbox(elem_id='galore_with_embedding', scale=4)
|
||||
gr.Slider(elem_id='galore_rank', minimum=8, maximum=256, step=8, scale=4)
|
||||
gr.Slider(elem_id='galore_update_proj_gap', minimum=10, maximum=1000, step=50, scale=4)
|
||||
with gr.TabItem(elem_id='lorap_tab'):
|
||||
with gr.Row():
|
||||
gr.Textbox(elem_id='lorap_lr_ratio', scale=4)
|
||||
with gr.TabItem(elem_id='multimodal_tab'):
|
||||
with gr.Row():
|
||||
gr.Textbox(elem_id='vit_lr', lines=1, scale=20)
|
||||
gr.Textbox(elem_id='aligner_lr', lines=1, scale=20)
|
||||
with gr.TabItem(elem_id='muon_tab'):
|
||||
with gr.Row():
|
||||
gr.Checkbox(elem_id='use_muon', scale=4)
|
||||
@@ -0,0 +1,67 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
import gradio as gr
|
||||
from typing import Type
|
||||
|
||||
from ..base import BaseUI
|
||||
|
||||
|
||||
class Quantization(BaseUI):
|
||||
|
||||
group = 'llm_train'
|
||||
|
||||
locale_dict = {
|
||||
'quantization_tab': {
|
||||
'label': {
|
||||
'zh': '量化参数设置',
|
||||
'en': 'Quantization settings'
|
||||
},
|
||||
},
|
||||
'quant_method': {
|
||||
'label': {
|
||||
'zh': '量化方式',
|
||||
'en': 'Quantization method'
|
||||
},
|
||||
'info': {
|
||||
'zh': '如果制定了量化位数,本参数默认为bnb',
|
||||
'en': 'Default is bnb if quantization_bit is specified'
|
||||
}
|
||||
},
|
||||
'quant_bits': {
|
||||
'label': {
|
||||
'zh': '量化bit数',
|
||||
'en': 'Quantization bit'
|
||||
},
|
||||
'info': {
|
||||
'zh': '设置量化bit数, 0代表不进行量化',
|
||||
'en': 'Set the quantization bit, 0 for no quantization'
|
||||
}
|
||||
},
|
||||
'bnb_4bit_compute_dtype': {
|
||||
'label': {
|
||||
'zh': '计算数据类型',
|
||||
'en': 'Computational data type'
|
||||
},
|
||||
},
|
||||
'bnb_4bit_quant_type': {
|
||||
'label': {
|
||||
'zh': '量化数据类型',
|
||||
'en': 'Quantization data type'
|
||||
},
|
||||
},
|
||||
'bnb_4bit_use_double_quant': {
|
||||
'label': {
|
||||
'zh': '使用嵌套量化',
|
||||
'en': 'Use double quantization'
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def do_build_ui(cls, base_tab: Type['BaseUI']):
|
||||
with gr.TabItem(elem_id='quantization_tab'):
|
||||
with gr.Row():
|
||||
gr.Dropdown(elem_id='quant_bits', value=None)
|
||||
gr.Dropdown(elem_id='quant_method', value=None)
|
||||
gr.Dropdown(elem_id='bnb_4bit_compute_dtype', value=None)
|
||||
gr.Dropdown(elem_id='bnb_4bit_quant_type', value=None)
|
||||
gr.Checkbox(elem_id='bnb_4bit_use_double_quant', value=None)
|
||||
@@ -0,0 +1,74 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
import gradio as gr
|
||||
from typing import Type
|
||||
|
||||
from ..base import BaseUI
|
||||
|
||||
|
||||
class ReportTo(BaseUI):
|
||||
|
||||
group = 'llm_train'
|
||||
|
||||
locale_dict = {
|
||||
'reporter_tab': {
|
||||
'label': {
|
||||
'zh': '训练记录',
|
||||
'en': 'Training report'
|
||||
},
|
||||
},
|
||||
'report_to': {
|
||||
'label': {
|
||||
'zh': '训练记录方式',
|
||||
'en': 'Report to'
|
||||
},
|
||||
},
|
||||
'swanlab_token': {
|
||||
'label': {
|
||||
'zh': 'SwanLab登录token',
|
||||
'en': 'The login token of SwanLab'
|
||||
},
|
||||
},
|
||||
'swanlab_project': {
|
||||
'label': {
|
||||
'zh': 'SwanLab项目名称',
|
||||
'en': 'Project of SwanLab'
|
||||
},
|
||||
},
|
||||
'swanlab_workspace': {
|
||||
'label': {
|
||||
'zh': 'SwanLab工作空间',
|
||||
'en': 'Workspace of SwanLab'
|
||||
},
|
||||
},
|
||||
'swanlab_exp_name': {
|
||||
'label': {
|
||||
'zh': 'SwanLab实验名称',
|
||||
'en': 'Experiment of SwanLab'
|
||||
},
|
||||
},
|
||||
'swanlab_mode': {
|
||||
'label': {
|
||||
'zh': 'SwanLab工作模式',
|
||||
'en': 'Work mode of SwanLab'
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def do_build_ui(cls, base_tab: Type['BaseUI']):
|
||||
with gr.TabItem(elem_id='reporter_tab'):
|
||||
with gr.Blocks():
|
||||
with gr.Row():
|
||||
gr.Dropdown(
|
||||
elem_id='report_to',
|
||||
multiselect=True,
|
||||
is_list=True,
|
||||
choices=['tensorboard', 'wandb', 'swanlab'],
|
||||
allow_custom_value=True,
|
||||
scale=20)
|
||||
gr.Textbox(elem_id='swanlab_token', lines=1, scale=20)
|
||||
gr.Textbox(elem_id='swanlab_project', lines=1, scale=20)
|
||||
with gr.Row():
|
||||
gr.Textbox(elem_id='swanlab_workspace', lines=1, scale=20)
|
||||
gr.Textbox(elem_id='swanlab_exp_name', lines=1, scale=20)
|
||||
gr.Dropdown(elem_id='swanlab_mode', scale=20)
|
||||
@@ -0,0 +1,717 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
import collections
|
||||
import gradio as gr
|
||||
import json
|
||||
import matplotlib.pyplot as plt
|
||||
import os
|
||||
import psutil
|
||||
import re
|
||||
import subprocess
|
||||
import sys
|
||||
import time
|
||||
import webbrowser
|
||||
from datetime import datetime
|
||||
from functools import partial
|
||||
from packaging import version
|
||||
from transformers import is_tensorboard_available
|
||||
from typing import Dict, List, Tuple, Type
|
||||
|
||||
from swift.utils import TB_COLOR, TB_COLOR_SMOOTH, format_time, get_logger, read_tensorboard_file, tensorboard_smoothing
|
||||
from ..base import BaseUI
|
||||
from .utils import close_loop, run_command_in_subprocess
|
||||
|
||||
logger = get_logger()
|
||||
|
||||
|
||||
class Runtime(BaseUI):
|
||||
|
||||
handlers: Dict[str, Tuple[List, Tuple]] = {}
|
||||
|
||||
group = 'llm_train'
|
||||
|
||||
all_plots = None
|
||||
|
||||
log_event = {}
|
||||
|
||||
sft_plot = [
|
||||
{
|
||||
'name': 'train/loss',
|
||||
'smooth': 0.9,
|
||||
},
|
||||
{
|
||||
'name': 'train/acc',
|
||||
'smooth': None,
|
||||
},
|
||||
{
|
||||
'name': 'train/learning_rate',
|
||||
'smooth': None,
|
||||
},
|
||||
{
|
||||
'name': 'eval/loss',
|
||||
'smooth': 0.9,
|
||||
},
|
||||
{
|
||||
'name': 'eval/acc',
|
||||
'smooth': None,
|
||||
},
|
||||
]
|
||||
|
||||
dpo_plot = [
|
||||
{
|
||||
'name': 'train/loss',
|
||||
'smooth': 0.9,
|
||||
},
|
||||
{
|
||||
'name': 'train/rewards/accuracies',
|
||||
'smooth': None,
|
||||
},
|
||||
{
|
||||
'name': 'train/rewards/margins',
|
||||
'smooth': 0.9,
|
||||
},
|
||||
{
|
||||
'name': 'train/logps/chosen',
|
||||
'smooth': 0.9,
|
||||
},
|
||||
{
|
||||
'name': 'train/logps/rejected',
|
||||
'smooth': 0.9,
|
||||
},
|
||||
]
|
||||
|
||||
kto_plot = [
|
||||
{
|
||||
'name': 'kl',
|
||||
'smooth': None,
|
||||
},
|
||||
{
|
||||
'name': 'rewards/chosen_sum',
|
||||
'smooth': 0.9,
|
||||
},
|
||||
{
|
||||
'name': 'logps/chosen_sum',
|
||||
'smooth': 0.9,
|
||||
},
|
||||
{
|
||||
'name': 'rewards/rejected_sum',
|
||||
'smooth': 0.9,
|
||||
},
|
||||
{
|
||||
'name': 'logps/rejected_sum',
|
||||
'smooth': 0.9,
|
||||
},
|
||||
]
|
||||
|
||||
orpo_plot = [
|
||||
{
|
||||
'name': 'train/loss',
|
||||
'smooth': 0.9,
|
||||
},
|
||||
{
|
||||
'name': 'train/rewards/accuracies',
|
||||
'smooth': None,
|
||||
},
|
||||
{
|
||||
'name': 'train/rewards/margins',
|
||||
'smooth': 0.9,
|
||||
},
|
||||
{
|
||||
'name': 'train/rewards/chosen',
|
||||
'smooth': 0.9,
|
||||
},
|
||||
{
|
||||
'name': 'train/log_odds_ratio',
|
||||
'smooth': 0.9,
|
||||
},
|
||||
]
|
||||
|
||||
grpo_plot = [
|
||||
{
|
||||
'name': 'train/loss',
|
||||
'smooth': 0.9,
|
||||
},
|
||||
{
|
||||
'name': 'train/reward',
|
||||
'smooth': 0.9,
|
||||
},
|
||||
{
|
||||
'name': 'train/learning_rate',
|
||||
'smooth': None,
|
||||
},
|
||||
{
|
||||
'name': 'train/completions/mean_length',
|
||||
'smooth': 0.9,
|
||||
},
|
||||
{
|
||||
'name': 'train/kl',
|
||||
'smooth': 0.9,
|
||||
},
|
||||
]
|
||||
|
||||
locale_dict = {
|
||||
'runtime_tab': {
|
||||
'label': {
|
||||
'zh': '运行时',
|
||||
'en': 'Runtime'
|
||||
},
|
||||
},
|
||||
'tb_not_found': {
|
||||
'value': {
|
||||
'zh': 'TensorBoard未安装,使用`pip install tensorboard`进行安装',
|
||||
'en': 'TensorBoard not found, install it by `pip install tensorboard`',
|
||||
}
|
||||
},
|
||||
'running_cmd': {
|
||||
'label': {
|
||||
'zh': '运行命令',
|
||||
'en': 'Command line'
|
||||
},
|
||||
'info': {
|
||||
'zh': '执行的实际命令',
|
||||
'en': 'The actual command'
|
||||
}
|
||||
},
|
||||
'show_running_cmd': {
|
||||
'value': {
|
||||
'zh': '展示运行命令',
|
||||
'en': 'Show running command line'
|
||||
},
|
||||
},
|
||||
'show_sh': {
|
||||
'label': {
|
||||
'zh': '展示sh命令行',
|
||||
'en': 'Show sh command line'
|
||||
},
|
||||
},
|
||||
'cmd_sh': {
|
||||
'label': {
|
||||
'zh': '训练命令行',
|
||||
'en': 'Training command line'
|
||||
},
|
||||
'info': {
|
||||
'zh':
|
||||
'如果训练命令行没有展示请再次点击"展示运行命令",点击下方的"保存训练命令"可以保存sh脚本',
|
||||
'en': ('Please press "Show running command line" if the content is none, '
|
||||
'click the "Save training command" below to save the sh script')
|
||||
}
|
||||
},
|
||||
'save_cmd_as_sh': {
|
||||
'value': {
|
||||
'zh': '保存训练命令',
|
||||
'en': 'Save training command'
|
||||
}
|
||||
},
|
||||
'save_cmd_alert': {
|
||||
'value': {
|
||||
'zh': '训练命令行将被保存在:{}',
|
||||
'en': 'The training command line will be saved in: {}'
|
||||
}
|
||||
},
|
||||
'close_cmd_show': {
|
||||
'value': {
|
||||
'zh': '关闭训练命令展示',
|
||||
'en': 'Close training command show'
|
||||
}
|
||||
},
|
||||
'show_log': {
|
||||
'value': {
|
||||
'zh': '展示运行状态',
|
||||
'en': 'Show running status'
|
||||
},
|
||||
},
|
||||
'stop_show_log': {
|
||||
'value': {
|
||||
'zh': '停止展示运行状态',
|
||||
'en': 'Stop showing running status'
|
||||
},
|
||||
},
|
||||
'logging_dir': {
|
||||
'label': {
|
||||
'zh': '日志路径',
|
||||
'en': 'Logging dir'
|
||||
},
|
||||
'info': {
|
||||
'zh': '支持手动传入文件路径',
|
||||
'en': 'Support fill custom path in'
|
||||
}
|
||||
},
|
||||
'log': {
|
||||
'label': {
|
||||
'zh': '日志输出',
|
||||
'en': 'Logging content'
|
||||
},
|
||||
'info': {
|
||||
'zh': '如果日志无更新请再次点击"展示运行状态"',
|
||||
'en': 'Please press "Show running status" if the log content is not updating'
|
||||
}
|
||||
},
|
||||
'running_tasks': {
|
||||
'label': {
|
||||
'zh': '运行中任务',
|
||||
'en': 'Running Tasks'
|
||||
},
|
||||
'info': {
|
||||
'zh': '运行中的任务(所有的swift sft/pt命令)',
|
||||
'en': 'All running tasks(started by swift sft/pt)'
|
||||
}
|
||||
},
|
||||
'refresh_tasks': {
|
||||
'value': {
|
||||
'zh': '找回运行时任务',
|
||||
'en': 'Find running tasks'
|
||||
},
|
||||
},
|
||||
'kill_task': {
|
||||
'value': {
|
||||
'zh': '杀死任务',
|
||||
'en': 'Kill running task'
|
||||
},
|
||||
},
|
||||
'tb_url': {
|
||||
'label': {
|
||||
'zh': 'Tensorboard链接',
|
||||
'en': 'Tensorboard URL'
|
||||
},
|
||||
'info': {
|
||||
'zh': '仅展示,不可编辑',
|
||||
'en': 'Not editable'
|
||||
}
|
||||
},
|
||||
'start_tb': {
|
||||
'value': {
|
||||
'zh': '打开TensorBoard',
|
||||
'en': 'Start TensorBoard'
|
||||
},
|
||||
},
|
||||
'close_tb': {
|
||||
'value': {
|
||||
'zh': '关闭TensorBoard',
|
||||
'en': 'Close TensorBoard'
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def do_build_ui(cls, base_tab: Type['BaseUI']):
|
||||
with gr.Accordion(elem_id='runtime_tab', open=False):
|
||||
with gr.Blocks():
|
||||
with gr.Row():
|
||||
with gr.Column(scale=3):
|
||||
with gr.Row():
|
||||
gr.Textbox(elem_id='running_cmd', lines=1, scale=3, interactive=False, max_lines=1)
|
||||
gr.Textbox(elem_id='logging_dir', lines=1, scale=3, max_lines=1)
|
||||
with gr.Row():
|
||||
gr.Button(elem_id='show_running_cmd', scale=2, variant='primary')
|
||||
gr.Button(elem_id='show_log', scale=2, variant='primary')
|
||||
gr.Button(elem_id='stop_show_log', scale=2)
|
||||
with gr.Column(scale=2):
|
||||
with gr.Row():
|
||||
gr.Textbox(elem_id='tb_url', lines=1, scale=4, interactive=False, max_lines=1)
|
||||
with gr.Row():
|
||||
gr.Button(elem_id='start_tb', scale=2, variant='primary')
|
||||
gr.Button(elem_id='close_tb', scale=2)
|
||||
with gr.Accordion(elem_id='show_sh', open=False, visible=False):
|
||||
with gr.Blocks():
|
||||
gr.Textbox(elem_id='cmd_sh', lines=8)
|
||||
with gr.Row(equal_height=True):
|
||||
gr.Button(elem_id='save_cmd_as_sh', variant='primary', scale=2)
|
||||
gr.Button(elem_id='close_cmd_show', scale=2)
|
||||
with gr.Row():
|
||||
gr.Textbox(elem_id='log', lines=6, visible=False)
|
||||
with gr.Row(equal_height=True):
|
||||
gr.Dropdown(elem_id='running_tasks', scale=10)
|
||||
gr.Button(elem_id='refresh_tasks', scale=1)
|
||||
gr.Button(elem_id='kill_task', scale=1)
|
||||
|
||||
with gr.Row():
|
||||
cls.all_plots = []
|
||||
plot = Runtime.sft_plot
|
||||
if base_tab.group == 'llm_rlhf':
|
||||
plot = Runtime.dpo_plot
|
||||
elif base_tab.group == 'llm_grpo':
|
||||
plot = Runtime.grpo_plot
|
||||
for idx, k in enumerate(plot):
|
||||
name = k['name']
|
||||
cls.all_plots.append(gr.Plot(elem_id=str(idx), label=name))
|
||||
|
||||
concurrency_limit = {}
|
||||
if version.parse(gr.__version__) >= version.parse('4.0.0'):
|
||||
concurrency_limit = {'concurrency_limit': 5}
|
||||
base_tab.element('show_log').click(
|
||||
Runtime.update_log, [base_tab.element('running_tasks')], [cls.element('log')] + cls.all_plots).then(
|
||||
Runtime.wait, [base_tab.element('logging_dir'),
|
||||
base_tab.element('running_tasks')], [cls.element('log')] + cls.all_plots,
|
||||
**concurrency_limit)
|
||||
|
||||
base_tab.element('stop_show_log').click(cls.break_log_event, [cls.element('running_tasks')], [])
|
||||
|
||||
base_tab.element('start_tb').click(
|
||||
Runtime.start_tb,
|
||||
[base_tab.element('logging_dir')],
|
||||
[base_tab.element('tb_url')],
|
||||
)
|
||||
|
||||
base_tab.element('close_tb').click(
|
||||
Runtime.close_tb,
|
||||
[base_tab.element('logging_dir')],
|
||||
[],
|
||||
)
|
||||
|
||||
base_tab.element('refresh_tasks').click(
|
||||
partial(Runtime.refresh_tasks, group=cls.group),
|
||||
[base_tab.element('running_tasks')],
|
||||
[base_tab.element('running_tasks')],
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def after_build_ui(cls, base_tab: Type['BaseUI']):
|
||||
cls.element('show_running_cmd').click(Runtime.show_train_sh, cls.element('running_cmd'),
|
||||
[cls.element('show_sh')] + [cls.element('cmd_sh')])
|
||||
cls.element('save_cmd_as_sh').click(cls.save_cmd, cls.element('running_cmd'), [])
|
||||
cls.element('close_cmd_show').click(Runtime.close_cmd_show, [], [cls.element('show_sh')])
|
||||
|
||||
@classmethod
|
||||
def get_plot(cls, task):
|
||||
if not task or 'swift sft' in task or 'swift pt' in task:
|
||||
return cls.sft_plot
|
||||
|
||||
args: dict = cls.parse_info_from_cmdline(task)[1]
|
||||
rlhf_type = args.get('rlhf_type', 'dpo')
|
||||
if rlhf_type in ('dpo', 'cpo', 'simpo'):
|
||||
return cls.dpo_plot
|
||||
elif rlhf_type == 'kto':
|
||||
return cls.kto_plot
|
||||
elif rlhf_type == 'orpo':
|
||||
return cls.orpo_plot
|
||||
elif rlhf_type == 'grpo':
|
||||
return cls.grpo_plot
|
||||
|
||||
@classmethod
|
||||
def update_log(cls, task):
|
||||
ret = [gr.update(visible=True)]
|
||||
plot = Runtime.get_plot(task)
|
||||
for i in range(len(plot)):
|
||||
p = plot[i]
|
||||
ret.append(gr.update(visible=True, label=p['name']))
|
||||
return ret
|
||||
|
||||
@classmethod
|
||||
def get_initial(cls, line):
|
||||
tqdm_starts = ['Train:', 'Map:', 'Val:', 'Filter:']
|
||||
for start in tqdm_starts:
|
||||
if line.startswith(start):
|
||||
return start
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def wait(cls, logging_dir, task):
|
||||
if not logging_dir:
|
||||
return [None] + Runtime.plot(task)
|
||||
log_file = os.path.join(logging_dir, 'run.log')
|
||||
cls.log_event[logging_dir] = False
|
||||
offset = 0
|
||||
latest_data = ''
|
||||
lines = collections.deque(maxlen=int(os.environ.get('MAX_LOG_LINES', 100)))
|
||||
try:
|
||||
with open(log_file, 'r', encoding='utf-8') as input:
|
||||
input.seek(offset)
|
||||
fail_cnt = 0
|
||||
while True:
|
||||
try:
|
||||
latest_data += input.read()
|
||||
except UnicodeDecodeError:
|
||||
continue
|
||||
if not latest_data:
|
||||
time.sleep(0.5)
|
||||
fail_cnt += 1
|
||||
if fail_cnt > 50:
|
||||
break
|
||||
|
||||
if cls.log_event.get(logging_dir, False):
|
||||
cls.log_event[logging_dir] = False
|
||||
break
|
||||
|
||||
if '\n' not in latest_data:
|
||||
continue
|
||||
latest_lines = latest_data.split('\n')
|
||||
if latest_data[-1] != '\n':
|
||||
latest_data = latest_lines[-1]
|
||||
latest_lines = latest_lines[:-1]
|
||||
else:
|
||||
latest_data = ''
|
||||
lines.extend(latest_lines)
|
||||
start = cls.get_initial(lines[-1])
|
||||
if start:
|
||||
i = len(lines) - 2
|
||||
while i >= 0:
|
||||
if lines[i].startswith(start):
|
||||
del lines[i]
|
||||
i -= 1
|
||||
else:
|
||||
break
|
||||
yield [gr.update(value='\n'.join(lines))] + Runtime.plot(task)
|
||||
time.sleep(0.5)
|
||||
except IOError:
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def break_log_event(cls, task):
|
||||
if not task:
|
||||
return
|
||||
pid, all_args = Runtime.parse_info_from_cmdline(task)
|
||||
cls.log_event[all_args['logging_dir']] = True
|
||||
|
||||
@classmethod
|
||||
def show_log(cls, logging_dir):
|
||||
webbrowser.open('file://' + os.path.join(logging_dir, 'run.log'), new=2)
|
||||
|
||||
@classmethod
|
||||
def start_tb(cls, logging_dir):
|
||||
if not is_tensorboard_available():
|
||||
gr.Error(cls.locale('tb_not_found', cls.lang)['value'])
|
||||
return ''
|
||||
|
||||
logging_dir = logging_dir.strip()
|
||||
logging_dir = logging_dir if not logging_dir.endswith(os.sep) else logging_dir[:-1]
|
||||
if logging_dir in cls.handlers:
|
||||
return cls.handlers[logging_dir][1]
|
||||
|
||||
handler, lines = run_command_in_subprocess('tensorboard', '--logdir', logging_dir, timeout=2)
|
||||
localhost_addr = ''
|
||||
for line in lines:
|
||||
if 'http://localhost:' in line:
|
||||
line = line[line.index('http://localhost:'):]
|
||||
localhost_addr = line[:line.index(' ')]
|
||||
cls.handlers[logging_dir] = (handler, localhost_addr)
|
||||
logger.info('===========Tensorboard Log============')
|
||||
logger.info('\n'.join(lines))
|
||||
webbrowser.open(localhost_addr, new=2)
|
||||
return localhost_addr
|
||||
|
||||
@staticmethod
|
||||
def close_tb(logging_dir):
|
||||
if logging_dir in Runtime.handlers:
|
||||
close_loop(Runtime.handlers[logging_dir][0])
|
||||
Runtime.handlers.pop(logging_dir)
|
||||
|
||||
@staticmethod
|
||||
def refresh_tasks(running_task=None, group=None):
|
||||
output_dir = running_task if not running_task or 'pid:' not in running_task else None
|
||||
process_name = 'swift'
|
||||
negative_names = ['swift.exe', 'swift-script.py']
|
||||
cmd_name = ['pt', 'sft'] if group == 'llm_train' else ['rlhf']
|
||||
process = []
|
||||
selected = None
|
||||
for proc in psutil.process_iter():
|
||||
try:
|
||||
cmdlines = proc.cmdline()
|
||||
except (psutil.ZombieProcess, psutil.AccessDenied, psutil.NoSuchProcess):
|
||||
cmdlines = []
|
||||
if any([
|
||||
process_name in cmdline for cmdline in cmdlines # noqa
|
||||
]) and not any([ # noqa
|
||||
negative_name in cmdline for negative_name in negative_names # noqa
|
||||
for cmdline in cmdlines # noqa
|
||||
]) and any([cmdline in cmd_name for cmdline in cmdlines]): # noqa
|
||||
if any([group == 'llm_rlhf' and 'grpo' in cmdline for cmdline in cmdlines]):
|
||||
continue
|
||||
if group == 'llm_grpo' and all(['grpo' not in cmdline for cmdline in cmdlines]):
|
||||
continue
|
||||
process.append(Runtime.construct_running_task(proc))
|
||||
if output_dir is not None and any( # noqa
|
||||
[output_dir == cmdline for cmdline in cmdlines]): # noqa
|
||||
selected = Runtime.construct_running_task(proc)
|
||||
if not selected:
|
||||
if running_task and running_task in process:
|
||||
selected = running_task
|
||||
if not selected and process:
|
||||
selected = process[0]
|
||||
return gr.update(choices=process, value=selected)
|
||||
|
||||
@staticmethod
|
||||
def construct_running_task(proc):
|
||||
pid = proc.pid
|
||||
ts = time.time()
|
||||
create_time = proc.create_time()
|
||||
create_time_formatted = datetime.fromtimestamp(create_time).strftime('%Y-%m-%d, %H:%M')
|
||||
|
||||
return f'pid:{pid}/create:{create_time_formatted}' \
|
||||
f'/running:{format_time(ts - create_time)}/cmd:{" ".join(proc.cmdline())}'
|
||||
|
||||
@staticmethod
|
||||
def parse_info_from_cmdline(task):
|
||||
pid = None
|
||||
if '/cmd:' in task:
|
||||
for i in range(3):
|
||||
slash = task.find('/')
|
||||
if i == 0:
|
||||
pid = task[:slash].split(':')[1]
|
||||
task = task[slash + 1:]
|
||||
if 'swift sft' in task:
|
||||
args = task.split('swift sft')[1]
|
||||
elif 'swift pt' in task:
|
||||
args = task.split('swift pt')[1]
|
||||
elif 'swift rlhf' in task:
|
||||
args = task.split('swift rlhf')[1]
|
||||
else:
|
||||
raise ValueError(f'Cannot parse cmd line: {task}')
|
||||
args = [arg.strip() for arg in args.split('--') if arg.strip()]
|
||||
all_args = {}
|
||||
for i in range(len(args)):
|
||||
space = args[i].find(' ')
|
||||
splits = args[i][:space], args[i][space + 1:]
|
||||
all_args[splits[0]] = str(splits[1]) if isinstance(splits[1], int) else splits[1]
|
||||
|
||||
output_dir = all_args['output_dir']
|
||||
if os.path.exists(os.path.join(output_dir, 'args.json')):
|
||||
with open(os.path.join(output_dir, 'args.json'), 'r', encoding='utf-8') as f:
|
||||
_json = json.load(f)
|
||||
for key in all_args.keys():
|
||||
all_args[key] = str(_json.get(key)) if isinstance(_json.get(key), int) else _json.get(key)
|
||||
if isinstance(all_args[key], list):
|
||||
if any([' ' in value for value in all_args[key] if isinstance(value, str)]):
|
||||
all_args[key] = [f'"{value}"' for value in all_args[key]]
|
||||
if len(all_args[key]) > 0 and isinstance(all_args[key][0], str):
|
||||
all_args[key] = ' '.join(all_args[key])
|
||||
return pid, all_args
|
||||
|
||||
@staticmethod
|
||||
def kill_task(task):
|
||||
if task:
|
||||
pid, all_args = Runtime.parse_info_from_cmdline(task)
|
||||
output_dir = all_args['output_dir']
|
||||
if sys.platform == 'win32':
|
||||
command = ['taskkill', '/f', '/t', '/pid', pid]
|
||||
else:
|
||||
command = ['pkill', '-9', '-f', output_dir]
|
||||
try:
|
||||
result = subprocess.run(command, capture_output=True, text=True)
|
||||
assert result.returncode == 0
|
||||
except Exception as e:
|
||||
raise e
|
||||
Runtime.break_log_event(task)
|
||||
return [Runtime.refresh_tasks()] + [gr.update(value=None)] * (len(Runtime.get_plot(task)) + 1)
|
||||
|
||||
@staticmethod
|
||||
def reset():
|
||||
return None, 'output'
|
||||
|
||||
@staticmethod
|
||||
def task_changed(task, base_tab):
|
||||
if task:
|
||||
_, all_args = Runtime.parse_info_from_cmdline(task)
|
||||
else:
|
||||
all_args = {}
|
||||
elements = list(base_tab.valid_elements().values())
|
||||
ret = []
|
||||
for e in elements:
|
||||
if e.elem_id in all_args:
|
||||
if isinstance(e, gr.Dropdown) and e.multiselect:
|
||||
arg = all_args[e.elem_id].split(' ')
|
||||
else:
|
||||
arg = all_args[e.elem_id]
|
||||
if isinstance(e, gr.Slider) and isinstance(arg, str) and re.fullmatch(base_tab.int_regex, arg):
|
||||
arg = int(arg)
|
||||
elif isinstance(e, gr.Slider) and isinstance(arg, str) and re.fullmatch(base_tab.float_regex, arg):
|
||||
arg = float(arg)
|
||||
elif isinstance(e, gr.Checkbox) and isinstance(arg, str) and re.fullmatch(base_tab.bool_regex, arg):
|
||||
arg = True if arg.lower() == 'true' else False
|
||||
ret.append(gr.update(value=arg))
|
||||
else:
|
||||
ret.append(gr.update())
|
||||
Runtime.break_log_event(task)
|
||||
return ret + [gr.update(value=None)] * (len(Runtime.get_plot(task)) + 1)
|
||||
|
||||
@staticmethod
|
||||
def plot(task):
|
||||
plot = Runtime.get_plot(task)
|
||||
if not task:
|
||||
return [None] * len(plot)
|
||||
_, all_args = Runtime.parse_info_from_cmdline(task)
|
||||
tb_dir = all_args['logging_dir']
|
||||
if not os.path.exists(tb_dir):
|
||||
return [None] * len(plot)
|
||||
fname = [
|
||||
fname for fname in os.listdir(tb_dir)
|
||||
if os.path.isfile(os.path.join(tb_dir, fname)) and fname.startswith('events.out')
|
||||
]
|
||||
if fname:
|
||||
fname = fname[0]
|
||||
else:
|
||||
return [None] * len(plot)
|
||||
tb_path = os.path.join(tb_dir, fname)
|
||||
data = read_tensorboard_file(tb_path)
|
||||
|
||||
plots = []
|
||||
for k in plot:
|
||||
name = k['name']
|
||||
smooth = k['smooth']
|
||||
if name == 'train/acc':
|
||||
if 'train/token_acc' in data:
|
||||
name = 'train/token_acc'
|
||||
if 'train/seq_acc' in data:
|
||||
name = 'train/seq_acc'
|
||||
if name == 'eval/acc':
|
||||
if 'eval/token_acc' in data:
|
||||
name = 'eval/token_acc'
|
||||
if 'eval/seq_acc' in data:
|
||||
name = 'eval/seq_acc'
|
||||
if name not in data:
|
||||
plots.append(None)
|
||||
continue
|
||||
_data = data[name]
|
||||
steps = [d['step'] for d in _data]
|
||||
values = [d['value'] for d in _data]
|
||||
if len(values) == 0:
|
||||
continue
|
||||
|
||||
plt.close('all')
|
||||
fig = plt.figure()
|
||||
ax = fig.add_subplot()
|
||||
# _, ax = plt.subplots(1, 1, squeeze=True, figsize=(8, 5), dpi=100)
|
||||
ax.set_title(name)
|
||||
if len(values) == 1:
|
||||
ax.scatter(steps, values, color=TB_COLOR_SMOOTH)
|
||||
elif smooth is not None:
|
||||
ax.plot(steps, values, color=TB_COLOR)
|
||||
values_s = tensorboard_smoothing(values, smooth)
|
||||
ax.plot(steps, values_s, color=TB_COLOR_SMOOTH)
|
||||
else:
|
||||
ax.plot(steps, values, color=TB_COLOR_SMOOTH)
|
||||
plots.append(fig)
|
||||
return plots
|
||||
|
||||
@classmethod
|
||||
def save_cmd(cls, cmd):
|
||||
if len(cmd) > 0:
|
||||
cmd_sh, output_dir = Runtime.cmd_to_sh_format(cmd)
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
sh_file_path = os.path.join(output_dir, 'train.sh')
|
||||
gr.Info(cls.locale('save_cmd_alert', cls.lang)['value'].format(sh_file_path))
|
||||
with open(sh_file_path, 'w', encoding='utf-8') as f:
|
||||
f.write(cmd_sh)
|
||||
|
||||
@staticmethod
|
||||
def show_train_sh(cmd):
|
||||
if len(cmd) == 0:
|
||||
return gr.update(visible=False, open=False), None
|
||||
cmd_sh, _ = Runtime.cmd_to_sh_format(cmd)
|
||||
return gr.update(visible=True, open=True), cmd_sh
|
||||
|
||||
@staticmethod
|
||||
def cmd_to_sh_format(cmd):
|
||||
cmd_sh = ''
|
||||
params = cmd.split('--')
|
||||
env_params = params[0].split('nohup')[0].strip()
|
||||
cmd_sh += (env_params + ' \\\n')
|
||||
swift_cmd = params[0].split('nohup')[1].strip()
|
||||
cmd_sh += ('nohup ' + swift_cmd + ' \\\n')
|
||||
for param in params[1:]:
|
||||
if param.startswith('output_dir'):
|
||||
output_dir = param.split(' ')[1].strip()
|
||||
cmd_sh += ('--' + param.strip() + ' \\\n')
|
||||
return cmd_sh, output_dir
|
||||
|
||||
@staticmethod
|
||||
def close_cmd_show():
|
||||
return gr.update(visible=False)
|
||||
@@ -0,0 +1,83 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
import gradio as gr
|
||||
from typing import Type
|
||||
|
||||
from ..base import BaseUI
|
||||
|
||||
|
||||
class Save(BaseUI):
|
||||
|
||||
group = 'llm_train'
|
||||
|
||||
locale_dict = {
|
||||
'save_tab': {
|
||||
'label': {
|
||||
'zh': '存储参数设置',
|
||||
'en': 'Saving settings'
|
||||
},
|
||||
},
|
||||
'push_to_hub': {
|
||||
'label': {
|
||||
'zh': '推送魔搭Hub',
|
||||
'en': 'Push to modelscope hub',
|
||||
},
|
||||
'info': {
|
||||
'zh': '是否推送魔搭的模型库',
|
||||
'en': 'Whether push the output model to modelscope hub',
|
||||
}
|
||||
},
|
||||
'hub_model_id': {
|
||||
'label': {
|
||||
'zh': '魔搭模型id',
|
||||
'en': 'The model-id in modelscope',
|
||||
},
|
||||
'info': {
|
||||
'zh': '设置魔搭的模型id',
|
||||
'en': 'Set the model-id of modelscope',
|
||||
}
|
||||
},
|
||||
'hub_private_repo': {
|
||||
'label': {
|
||||
'zh': '设置仓库私有',
|
||||
'en': 'Model is private',
|
||||
},
|
||||
'info': {
|
||||
'zh': '以私有方式推送魔搭hub',
|
||||
'en': 'Set the model as private',
|
||||
}
|
||||
},
|
||||
'hub_strategy': {
|
||||
'label': {
|
||||
'zh': '推送策略',
|
||||
'en': 'Push strategy',
|
||||
},
|
||||
'info': {
|
||||
'zh': '设置模型推送策略',
|
||||
'en': 'Set the push strategy',
|
||||
}
|
||||
},
|
||||
'hub_token': {
|
||||
'label': {
|
||||
'zh': '仓库token',
|
||||
'en': 'The hub token',
|
||||
},
|
||||
'info': {
|
||||
'zh': '该token可以在www.modelscope.cn找到',
|
||||
'en': 'Find the token in www.modelscope.cn',
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def do_build_ui(cls, base_tab: Type['BaseUI']):
|
||||
with gr.TabItem(elem_id='save_tab'):
|
||||
with gr.Blocks():
|
||||
with gr.Row():
|
||||
gr.Checkbox(elem_id='push_to_hub', scale=20)
|
||||
gr.Textbox(elem_id='hub_model_id', lines=1, scale=20)
|
||||
gr.Checkbox(elem_id='hub_private_repo', scale=20)
|
||||
gr.Dropdown(
|
||||
elem_id='hub_strategy',
|
||||
scale=20,
|
||||
choices=['end', 'every_save', 'checkpoint', 'all_checkpoints'])
|
||||
gr.Textbox(elem_id='hub_token', lines=1, scale=20)
|
||||
@@ -0,0 +1,46 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
import gradio as gr
|
||||
from typing import Type
|
||||
|
||||
from ..base import BaseUI
|
||||
|
||||
|
||||
class SelfCog(BaseUI):
|
||||
|
||||
group = 'llm_train'
|
||||
|
||||
locale_dict = {
|
||||
'selfcog_tab': {
|
||||
'label': {
|
||||
'zh': '自我认知任务参数设置',
|
||||
'en': 'Self cognition settings'
|
||||
},
|
||||
},
|
||||
'model_name': {
|
||||
'label': {
|
||||
'zh': '模型认知名称',
|
||||
'en': 'Model name'
|
||||
},
|
||||
'info': {
|
||||
'zh': '设置模型应当认知自己的名字, 格式为:中文名字 英文名字,中间以空格分隔',
|
||||
'en': 'Set the name of the model think itself of, the format is Chinesename Englishname, split by space'
|
||||
}
|
||||
},
|
||||
'model_author': {
|
||||
'label': {
|
||||
'zh': '模型作者',
|
||||
'en': 'Model author'
|
||||
},
|
||||
'info': {
|
||||
'zh': '设置模型认知的自己的作者, 格式为:中文作者 英文作者,中间以空格分隔',
|
||||
'en': 'Set the author of the model, the format is Chineseauthor Englishauthor, split by space'
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def do_build_ui(cls, base_tab: Type['BaseUI']):
|
||||
with gr.TabItem(elem_id='selfcog_tab'):
|
||||
with gr.Row():
|
||||
gr.Textbox(elem_id='model_name', scale=20, is_list=True)
|
||||
gr.Textbox(elem_id='model_author', scale=20, is_list=True)
|
||||
@@ -0,0 +1,80 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
import gradio as gr
|
||||
from typing import Type
|
||||
|
||||
from ..base import BaseUI
|
||||
|
||||
|
||||
class Target(BaseUI):
|
||||
|
||||
group = 'llm_train'
|
||||
|
||||
locale_dict = {
|
||||
'target_params': {
|
||||
'label': {
|
||||
'zh': 'Target模块参数',
|
||||
'en': 'Tuner modules params'
|
||||
}
|
||||
},
|
||||
'freeze_llm': {
|
||||
'label': {
|
||||
'zh': '冻结LLM',
|
||||
'en': 'Freeze LLM'
|
||||
},
|
||||
},
|
||||
'freeze_aligner': {
|
||||
'label': {
|
||||
'zh': '冻结aligner',
|
||||
'en': 'Freeze aligner'
|
||||
},
|
||||
},
|
||||
'freeze_vit': {
|
||||
'label': {
|
||||
'zh': '冻结ViT',
|
||||
'en': 'Freeze ViT'
|
||||
},
|
||||
},
|
||||
'target_modules': {
|
||||
'label': {
|
||||
'zh': '指定tuner模块',
|
||||
'en': 'Specify the tuner module'
|
||||
}
|
||||
},
|
||||
'target_regex': {
|
||||
'label': {
|
||||
'zh': 'Tuner模块regex表达式',
|
||||
'en': 'Tuner module regex expression'
|
||||
}
|
||||
},
|
||||
'modules_to_save': {
|
||||
'label': {
|
||||
'zh': '额外训练和存储的原模型模块',
|
||||
'en': 'Original model modules to train and save'
|
||||
}
|
||||
},
|
||||
'init_weights': {
|
||||
'label': {
|
||||
'zh': 'Tuner初始化方法',
|
||||
'en': 'Init tuner weights'
|
||||
},
|
||||
'info': {
|
||||
'zh': ('LoRA: gaussian/pissa/pissa_niter_[n]/olora/loftq/lora-ga/true/false,'
|
||||
'Bone: bat/true/false'),
|
||||
'en': ('LoRA: gaussian/pissa/pissa_niter_[n]/olora/loftq/lora-ga/true/false,'
|
||||
'Bone: bat/true/false'),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def do_build_ui(cls, base_tab: Type['BaseUI']):
|
||||
with gr.Blocks():
|
||||
with gr.Row():
|
||||
gr.Textbox(elem_id='target_modules', lines=1, value='all-linear', is_list=True, scale=5)
|
||||
gr.Checkbox(elem_id='freeze_llm', scale=5)
|
||||
gr.Checkbox(elem_id='freeze_aligner', scale=5)
|
||||
gr.Checkbox(elem_id='freeze_vit', scale=5)
|
||||
with gr.Row():
|
||||
gr.Textbox(elem_id='target_regex', scale=5)
|
||||
gr.Textbox(elem_id='modules_to_save', scale=5)
|
||||
gr.Textbox(elem_id='init_weights', scale=5)
|
||||
@@ -0,0 +1,76 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
import gradio as gr
|
||||
from typing import Type
|
||||
|
||||
from ..base import BaseUI
|
||||
|
||||
|
||||
class Task(BaseUI):
|
||||
|
||||
group = 'llm_train'
|
||||
|
||||
locale_dict = {
|
||||
'embed_tab': {
|
||||
'label': {
|
||||
'zh': '文本嵌入',
|
||||
'en': 'Embedding'
|
||||
},
|
||||
},
|
||||
'loss_type': {
|
||||
'label': {
|
||||
'zh': 'Loss类型',
|
||||
'en': 'Loss type'
|
||||
}
|
||||
},
|
||||
'seq_cls_tab': {
|
||||
'label': {
|
||||
'zh': '序列分类',
|
||||
'en': 'Sequence Classification'
|
||||
},
|
||||
},
|
||||
'num_labels': {
|
||||
'label': {
|
||||
'zh': '标签数量',
|
||||
'en': 'Number of labels'
|
||||
}
|
||||
},
|
||||
'use_chat_template': {
|
||||
'label': {
|
||||
'zh': '使用对话模板',
|
||||
'en': 'use chat template'
|
||||
},
|
||||
'info': {
|
||||
'zh': '使用对话模板或生成模板',
|
||||
'en': 'Use the chat template or generation template'
|
||||
}
|
||||
},
|
||||
'task_type': {
|
||||
'label': {
|
||||
'zh': '任务类型',
|
||||
'en': 'Task type'
|
||||
},
|
||||
},
|
||||
'task_params': {
|
||||
'label': {
|
||||
'zh': '任务参数',
|
||||
'en': 'Task params'
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
tabs_to_filter = {'embedding': ['loss_type'], 'seq_cls': ['num_labels', 'use_chat_template']}
|
||||
|
||||
@classmethod
|
||||
def do_build_ui(cls, base_tab: Type['BaseUI']):
|
||||
with gr.Accordion(elem_id='task_params', open=False):
|
||||
gr.Dropdown(elem_id='task_type', choices=['causal_lm', 'seq_cls', 'embedding'])
|
||||
with gr.Tabs():
|
||||
with gr.TabItem(elem_id='embed_tab'):
|
||||
with gr.Row():
|
||||
gr.Dropdown(
|
||||
elem_id='loss_type',
|
||||
choices=['cosine_similarity', 'contrastive', 'online_contrastive', 'infonce'])
|
||||
with gr.TabItem(elem_id='seq_cls_tab'):
|
||||
with gr.Row():
|
||||
gr.Textbox(elem_id='num_labels', scale=4)
|
||||
gr.Checkbox(elem_id='use_chat_template', value=True, scale=4)
|
||||
@@ -0,0 +1,366 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
import gradio as gr
|
||||
from typing import Type
|
||||
|
||||
from ..base import BaseUI
|
||||
from .lora import LoRA
|
||||
from .target import Target
|
||||
|
||||
|
||||
class Tuner(BaseUI):
|
||||
|
||||
group = 'llm_train'
|
||||
|
||||
sub_ui = [LoRA, Target]
|
||||
|
||||
locale_dict = {
|
||||
'adalora_tab': {
|
||||
'label': {
|
||||
'zh': 'AdaLoRA参数设置',
|
||||
'en': 'AdaLoRA settings'
|
||||
},
|
||||
},
|
||||
'adalora_target_r': {
|
||||
'label': {
|
||||
'zh': 'AdaLoRA的平均秩',
|
||||
'en': 'Average rank of AdaLoRA'
|
||||
},
|
||||
},
|
||||
'adalora_init_r': {
|
||||
'label': {
|
||||
'zh': 'AdaLoRA的初始秩',
|
||||
'en': 'Initial rank of AdaLoRA'
|
||||
},
|
||||
},
|
||||
'adalora_tinit': {
|
||||
'label': {
|
||||
'zh': 'AdaLoRA初始微调预热的步数',
|
||||
'en': 'Initial fine-tuning warmup steps of AdaLoRA'
|
||||
},
|
||||
},
|
||||
'adalora_tfinal': {
|
||||
'label': {
|
||||
'zh': 'AdaLoRA最终微调的步数',
|
||||
'en': 'Final fine-tuning steps of AdaLoRA'
|
||||
},
|
||||
},
|
||||
'adalora_deltaT': {
|
||||
'label': {
|
||||
'zh': 'AdaLoRA两次预算分配间隔',
|
||||
'en': 'Internval of AdaLoRA two budget allocations'
|
||||
},
|
||||
},
|
||||
'adalora_beta1': {
|
||||
'label': {
|
||||
'zh': 'AdaLoRA的EMA参数',
|
||||
'en': 'AdaLoRA EMA parameters'
|
||||
},
|
||||
},
|
||||
'adalora_beta2': {
|
||||
'label': {
|
||||
'zh': 'AdaLoRA的EMA参数',
|
||||
'en': 'AdaLoRA EMA parameters'
|
||||
},
|
||||
},
|
||||
'adalora_orth_reg_weight': {
|
||||
'label': {
|
||||
'zh': 'AdaLoRA的正交正则化参数',
|
||||
'en': 'Coefficient of AdaLoRA orthogonal regularization'
|
||||
},
|
||||
},
|
||||
'lora_ga_tab': {
|
||||
'label': {
|
||||
'zh': 'LoRA-GA参数设置',
|
||||
'en': 'LoRA-GA settings'
|
||||
},
|
||||
},
|
||||
'lora_ga_batch_size': {
|
||||
'label': {
|
||||
'zh': 'LoRA-GA初始化批处理大小',
|
||||
'en': 'LoRA-GA initialization batch size'
|
||||
},
|
||||
},
|
||||
'lora_ga_iters': {
|
||||
'label': {
|
||||
'zh': 'LoRA-GA初始化迭代次数',
|
||||
'en': 'LoRA-GA initialization iters'
|
||||
},
|
||||
},
|
||||
'lora_ga_max_length': {
|
||||
'label': {
|
||||
'zh': 'LoRA-GA初始化最大输入长度',
|
||||
'en': 'LoRA-GA initialization max length'
|
||||
},
|
||||
},
|
||||
'lora_ga_direction': {
|
||||
'label': {
|
||||
'zh': 'LoRA-GA初始化的初始方向',
|
||||
'en': 'LoRA-GA initialization direction'
|
||||
},
|
||||
},
|
||||
'lora_ga_scale': {
|
||||
'label': {
|
||||
'zh': 'LoRA-GA初始化缩放方式',
|
||||
'en': 'LoRA-GA initialization scaling method'
|
||||
},
|
||||
},
|
||||
'lora_ga_stable_gamma': {
|
||||
'label': {
|
||||
'zh': 'Gamma参数值',
|
||||
'en': 'Gamma value'
|
||||
},
|
||||
'info': {
|
||||
'zh': '当初始化时选择stable缩放时的gamma值',
|
||||
'en': 'Select the gamma value for stable scaling',
|
||||
}
|
||||
},
|
||||
'longlora': {
|
||||
'label': {
|
||||
'zh': 'LongLoRA参数设置',
|
||||
'en': 'LongLoRA settings'
|
||||
},
|
||||
},
|
||||
'reft_tab': {
|
||||
'label': {
|
||||
'zh': 'ReFT参数设置',
|
||||
'en': 'ReFT settings'
|
||||
},
|
||||
},
|
||||
'reft_layers': {
|
||||
'label': {
|
||||
'zh': '应用ReFT的层',
|
||||
'en': 'ReFT layers'
|
||||
},
|
||||
},
|
||||
'reft_rank': {
|
||||
'label': {
|
||||
'zh': 'ReFT矩阵的秩',
|
||||
'en': 'Rank of the ReFT matrix'
|
||||
},
|
||||
},
|
||||
'reft_intervention_type': {
|
||||
'label': {
|
||||
'zh': 'ReFT的类型',
|
||||
'en': 'ReFT intervention type'
|
||||
},
|
||||
},
|
||||
'vera_tab': {
|
||||
'label': {
|
||||
'zh': 'VeRA参数设置',
|
||||
'en': 'VeRA settings'
|
||||
},
|
||||
},
|
||||
'vera_rank': {
|
||||
'label': {
|
||||
'zh': 'VeRA注意力维度',
|
||||
'en': 'VeRA rank'
|
||||
},
|
||||
},
|
||||
'vera_projection_prng_key': {
|
||||
'label': {
|
||||
'zh': 'VeRA PRNG初始化key',
|
||||
'en': 'VeRA PRNG initialisation key'
|
||||
},
|
||||
},
|
||||
'vera_dropout': {
|
||||
'label': {
|
||||
'zh': 'VeRA的丢弃概率',
|
||||
'en': 'VeRA dropout'
|
||||
},
|
||||
},
|
||||
'vera_d_initial': {
|
||||
'label': {
|
||||
'zh': 'VeRA的d矩阵初始值',
|
||||
'en': 'Initial value of d matrix'
|
||||
},
|
||||
},
|
||||
'boft_tab': {
|
||||
'label': {
|
||||
'zh': 'BOFT参数设置',
|
||||
'en': 'BOFT settings'
|
||||
},
|
||||
},
|
||||
'boft_block_size': {
|
||||
'label': {
|
||||
'zh': 'BOFT块大小',
|
||||
'en': 'BOFT block size'
|
||||
},
|
||||
},
|
||||
'boft_block_num': {
|
||||
'label': {
|
||||
'zh': 'BOFT块数量',
|
||||
'en': 'Number of BOFT blocks'
|
||||
},
|
||||
'info': {
|
||||
'zh': '不能和boft_block_size同时使用',
|
||||
'en': 'Cannot be used with boft_block_size',
|
||||
}
|
||||
},
|
||||
'boft_dropout': {
|
||||
'label': {
|
||||
'zh': 'BOFT丢弃概率',
|
||||
'en': 'Dropout value of BOFT'
|
||||
},
|
||||
},
|
||||
'fourierft_tab': {
|
||||
'label': {
|
||||
'zh': 'FourierFT参数设置',
|
||||
'en': 'FourierFT settings'
|
||||
},
|
||||
},
|
||||
'fourier_n_frequency': {
|
||||
'label': {
|
||||
'zh': 'FourierFT频率数量',
|
||||
'en': 'Num of FourierFT frequencies'
|
||||
},
|
||||
},
|
||||
'fourier_scaling': {
|
||||
'label': {
|
||||
'zh': 'W矩阵缩放值',
|
||||
'en': 'W matrix scaling value'
|
||||
},
|
||||
},
|
||||
'llamapro_tab': {
|
||||
'label': {
|
||||
'zh': 'LLaMA Pro参数设置',
|
||||
'en': 'LLaMA Pro Settings'
|
||||
},
|
||||
},
|
||||
'llamapro_num_new_blocks': {
|
||||
'label': {
|
||||
'zh': 'LLaMA Pro插入层数',
|
||||
'en': 'LLaMA Pro new layers'
|
||||
},
|
||||
},
|
||||
'llamapro_num_groups': {
|
||||
'label': {
|
||||
'zh': 'LLaMA Pro对原模型的分组数',
|
||||
'en': 'LLaMA Pro groups of model'
|
||||
}
|
||||
},
|
||||
'lisa_tab': {
|
||||
'label': {
|
||||
'zh': 'LISA参数设置',
|
||||
'en': 'LISA settings'
|
||||
},
|
||||
},
|
||||
'lisa_activated_layers': {
|
||||
'label': {
|
||||
'zh': 'LISA激活层数',
|
||||
'en': 'Num of LISA activated layers'
|
||||
},
|
||||
'info': {
|
||||
'zh': 'LISA每次训练的模型层数,调整为正整数代表使用LISA',
|
||||
'en': 'Num of layers activated each time, a positive value means using LISA'
|
||||
}
|
||||
},
|
||||
'lisa_step_interval': {
|
||||
'label': {
|
||||
'zh': 'LISA切换层间隔',
|
||||
'en': 'The interval of LISA layers switching'
|
||||
}
|
||||
},
|
||||
'tuner_params': {
|
||||
'label': {
|
||||
'zh': 'Tuner参数',
|
||||
'en': 'Tuner params'
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
tabs_to_filter = {
|
||||
'lora': ['lora_rank', 'lora_alpha', 'lora_dropout', 'lora_dtype', 'use_rslora', 'use_dora'],
|
||||
'llamapro': ['llamapro_num_new_blocks', 'llamapro_num_groups'],
|
||||
'lisa': ['lisa_activated_layers', 'lisa_step_interval'],
|
||||
'adalora': [
|
||||
'adalora_target_r', 'adalora_init_r', 'adalora_tinit', 'adalora_tfinal', 'adalora_deltaT', 'adalora_beta1',
|
||||
'adalora_beta2', 'adalora_orth_reg_weight'
|
||||
],
|
||||
'lora_ga': [
|
||||
'lora_ga_batch_size', 'lora_ga_iters', 'lora_ga_max_length', 'lora_ga_direction', 'lora_ga_scale',
|
||||
'lora_ga_stable_gamma'
|
||||
],
|
||||
'reft': ['reft_layers', 'reft_rank', 'reft_intervention_type'],
|
||||
'vera': ['vera_rank', 'vera_projection_prng_key', 'vera_dropout', 'vera_d_initial'],
|
||||
'boft': ['boft_block_size', 'boft_block_num', 'boft_dropout'],
|
||||
'fourierft': ['fourier_n_frequency', 'fourier_scaling']
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def do_build_ui(cls, base_tab: Type['BaseUI']):
|
||||
with gr.Accordion(elem_id='tuner_params', open=False):
|
||||
with gr.Tabs():
|
||||
LoRA.set_lang(cls.lang)
|
||||
LoRA.build_ui(base_tab)
|
||||
with gr.TabItem(elem_id='llamapro_tab'):
|
||||
with gr.Blocks():
|
||||
with gr.Row():
|
||||
gr.Textbox(elem_id='llamapro_num_new_blocks', scale=2)
|
||||
gr.Textbox(elem_id='llamapro_num_groups', scale=2)
|
||||
with gr.TabItem(elem_id='lisa_tab'):
|
||||
with gr.Blocks():
|
||||
with gr.Row():
|
||||
gr.Textbox(elem_id='lisa_activated_layers', value='0', scale=2)
|
||||
gr.Textbox(elem_id='lisa_step_interval', value='20', scale=2)
|
||||
with gr.TabItem(elem_id='adalora_tab'):
|
||||
with gr.Blocks():
|
||||
with gr.Row():
|
||||
gr.Textbox(elem_id='adalora_target_r', value='8', scale=2)
|
||||
gr.Slider(elem_id='adalora_init_r', value=12, minimum=1, maximum=512, step=4, scale=2)
|
||||
gr.Textbox(elem_id='adalora_tinit', value='0', scale=2)
|
||||
gr.Textbox(elem_id='adalora_tfinal', value='0', scale=2)
|
||||
with gr.Row():
|
||||
gr.Textbox(elem_id='adalora_deltaT', value='1', scale=2)
|
||||
gr.Textbox(elem_id='adalora_beta1', value='0.85', scale=2)
|
||||
gr.Textbox(elem_id='adalora_beta2', value='0.85', scale=2)
|
||||
gr.Textbox(elem_id='adalora_orth_reg_weight', value='0.5', scale=2)
|
||||
with gr.TabItem(elem_id='lora_ga_tab'):
|
||||
with gr.Blocks():
|
||||
with gr.Row():
|
||||
gr.Slider(elem_id='lora_ga_batch_size', value=2, minimum=1, maximum=256, step=1, scale=20)
|
||||
gr.Textbox(elem_id='lora_ga_iters', value='2', scale=20)
|
||||
gr.Textbox(elem_id='lora_ga_max_length', value='2048', scale=20)
|
||||
gr.Dropdown(
|
||||
elem_id='lora_ga_direction',
|
||||
scale=20,
|
||||
value='ArB2r',
|
||||
choices=['ArBr', 'A2rBr', 'ArB2r', 'random'])
|
||||
gr.Dropdown(
|
||||
elem_id='lora_ga_scale',
|
||||
scale=20,
|
||||
value='stable',
|
||||
choices=['gd', 'unit', 'stable', 'weights'])
|
||||
gr.Textbox(elem_id='lora_ga_stable_gamma', value='16', scale=20)
|
||||
with gr.TabItem(elem_id='reft_tab'):
|
||||
with gr.Blocks():
|
||||
with gr.Row():
|
||||
gr.Textbox(elem_id='reft_layers', scale=2)
|
||||
gr.Slider(elem_id='reft_rank', value=4, minimum=1, maximum=512, step=4, scale=2)
|
||||
gr.Dropdown(
|
||||
elem_id='reft_intervention_type',
|
||||
scale=2,
|
||||
value='LoreftIntervention',
|
||||
choices=[
|
||||
'NoreftIntervention', 'LoreftIntervention', 'ConsreftIntervention',
|
||||
'LobireftIntervention', 'DireftIntervention', 'NodireftIntervention'
|
||||
])
|
||||
with gr.TabItem(elem_id='vera_tab'):
|
||||
with gr.Blocks():
|
||||
with gr.Row():
|
||||
gr.Slider(elem_id='vera_rank', value=256, minimum=1, maximum=512, step=4, scale=2)
|
||||
gr.Textbox(elem_id='vera_projection_prng_key', value='0', scale=2)
|
||||
gr.Textbox(elem_id='vera_dropout', value='0.0', scale=2)
|
||||
gr.Textbox(elem_id='vera_d_initial', value='0.1', scale=2)
|
||||
with gr.TabItem(elem_id='boft_tab'):
|
||||
with gr.Blocks():
|
||||
with gr.Row():
|
||||
gr.Textbox(elem_id='boft_block_size', value='4', scale=2)
|
||||
gr.Textbox(elem_id='boft_block_num', scale=2)
|
||||
gr.Textbox(elem_id='boft_dropout', value='0.0', scale=2)
|
||||
with gr.TabItem(elem_id='fourierft_tab'):
|
||||
with gr.Blocks():
|
||||
with gr.Row():
|
||||
gr.Textbox(elem_id='fourier_n_frequency', value='2000', scale=2)
|
||||
gr.Textbox(elem_id='fourier_scaling', value='300.0', scale=2)
|
||||
Target.set_lang(cls.lang)
|
||||
Target.build_ui(base_tab)
|
||||
@@ -0,0 +1,58 @@
|
||||
# Copyright (c) ModelScope Contributors. All rights reserved.
|
||||
import asyncio
|
||||
import os
|
||||
import subprocess
|
||||
import sys
|
||||
from asyncio.subprocess import PIPE, STDOUT
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
async def run_and_get_log(*args, timeout=None):
|
||||
process = await asyncio.create_subprocess_exec(*args, stdout=PIPE, stderr=STDOUT)
|
||||
lines = []
|
||||
while True:
|
||||
try:
|
||||
line = await asyncio.wait_for(process.stdout.readline(), timeout)
|
||||
except asyncio.TimeoutError:
|
||||
break
|
||||
else:
|
||||
if not line:
|
||||
break
|
||||
else:
|
||||
lines.append(str(line))
|
||||
return process, lines
|
||||
|
||||
|
||||
def run_command_in_subprocess(*args, timeout):
|
||||
if sys.platform == 'win32':
|
||||
loop = asyncio.ProactorEventLoop()
|
||||
asyncio.set_event_loop(loop)
|
||||
else:
|
||||
loop = asyncio.new_event_loop()
|
||||
asyncio.set_event_loop(loop)
|
||||
process, lines = loop.run_until_complete(run_and_get_log(*args, timeout=timeout))
|
||||
return (loop, process), lines
|
||||
|
||||
|
||||
def close_loop(handler):
|
||||
loop, process = handler
|
||||
process.kill()
|
||||
loop.close()
|
||||
|
||||
|
||||
def run_command_in_background_with_popen(command, all_envs, log_file):
|
||||
env = deepcopy(os.environ)
|
||||
if len(all_envs) > 0:
|
||||
for k, v in all_envs.items():
|
||||
env[k] = v
|
||||
daemon_kwargs = {}
|
||||
if sys.platform == 'win32':
|
||||
from subprocess import CREATE_NO_WINDOW, DETACHED_PROCESS
|
||||
daemon_kwargs['creationflags'] = DETACHED_PROCESS | CREATE_NO_WINDOW
|
||||
daemon_kwargs['close_fds'] = True
|
||||
else:
|
||||
daemon_kwargs['preexec_fn'] = os.setsid
|
||||
|
||||
with open(log_file, 'w', encoding='utf-8') as f:
|
||||
subprocess.Popen(
|
||||
command, stdout=f, stderr=subprocess.STDOUT, stdin=subprocess.DEVNULL, text=True, bufsize=1, env=env)
|
||||
Reference in New Issue
Block a user