chore: import upstream snapshot with attribution
Lint test / lint (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:34:58 +08:00
commit a203934033
1368 changed files with 175001 additions and 0 deletions
+2
View File
@@ -0,0 +1,2 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from .app import webui_main
+114
View File
@@ -0,0 +1,114 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import gradio as gr
import os
from functools import partial
from packaging import version
from transformers.utils import strtobool
from typing import List, Optional, Union
import swift
from swift.arguments import (DeployArguments, EvalArguments, ExportArguments, RLHFArguments, SamplingArguments,
WebUIArguments)
from swift.pipelines import SwiftPipeline
from .llm_eval import LLMEval
from .llm_export import LLMExport
from .llm_grpo import LLMGRPO
from .llm_infer import LLMInfer
from .llm_rlhf import LLMRLHF
from .llm_sample import LLMSample
from .llm_train import LLMTrain
locale_dict = {
'title': {
'zh': '🚀SWIFT: 轻量级大模型训练推理框架',
'en': '🚀SWIFT: Scalable lightWeight Infrastructure for Fine-Tuning and Inference'
},
'sub_title': {
'zh':
'请查看 <a href=\"https://github.com/modelscope/ms-swift/tree/main/docs/source\" target=\"_blank\">'
'SWIFT 文档</a>来查看更多功能,使用SWIFT_UI_LANG=en环境变量来切换英文界面',
'en':
'Please check <a href=\"https://github.com/modelscope/ms-swift/tree/main/docs/source_en\" target=\"_blank\">'
'SWIFT Documentation</a> for more usages, Use SWIFT_UI_LANG=zh variable to switch to Chinese UI',
},
'star_beggar': {
'zh':
'喜欢<a href=\"https://github.com/modelscope/ms-swift\" target=\"_blank\">SWIFT</a>就动动手指给我们加个star吧🥺 ',
'en':
'If you like <a href=\"https://github.com/modelscope/ms-swift\" target=\"_blank\">SWIFT</a>, '
'please take a few seconds to star us🥺 '
},
}
class SwiftWebUI(SwiftPipeline):
args_class = WebUIArguments
args: args_class
def run(self):
lang = os.environ.get('SWIFT_UI_LANG') or self.args.lang
share_env = os.environ.get('WEBUI_SHARE')
share = strtobool(share_env) if share_env else self.args.share
server = os.environ.get('WEBUI_SERVER') or self.args.server_name
port_env = os.environ.get('WEBUI_PORT')
port = int(port_env) if port_env else self.args.server_port
LLMTrain.set_lang(lang)
LLMRLHF.set_lang(lang)
LLMGRPO.set_lang(lang)
LLMInfer.set_lang(lang)
LLMExport.set_lang(lang)
LLMEval.set_lang(lang)
LLMSample.set_lang(lang)
with gr.Blocks(title='SWIFT WebUI', theme=gr.themes.Base()) as app:
try:
_version = swift.__version__
except AttributeError:
_version = ''
gr.HTML(f"<h1><center>{locale_dict['title'][lang]}({_version})</center></h1>")
gr.HTML(f"<h3><center>{locale_dict['sub_title'][lang]}</center></h3>")
with gr.Tabs():
LLMTrain.build_ui(LLMTrain)
LLMRLHF.build_ui(LLMRLHF)
LLMGRPO.build_ui(LLMGRPO)
LLMInfer.build_ui(LLMInfer)
LLMExport.build_ui(LLMExport)
LLMEval.build_ui(LLMEval)
LLMSample.build_ui(LLMSample)
concurrent = {}
if version.parse(gr.__version__) < version.parse('4.0.0'):
concurrent = {'concurrency_count': 5}
app.load(
partial(LLMTrain.update_input_model, arg_cls=RLHFArguments),
inputs=[LLMTrain.element('model')],
outputs=[LLMTrain.element('train_record')] + list(LLMTrain.valid_elements().values()))
app.load(
partial(LLMRLHF.update_input_model, arg_cls=RLHFArguments),
inputs=[LLMRLHF.element('model')],
outputs=[LLMRLHF.element('train_record')] + list(LLMRLHF.valid_elements().values()))
app.load(
partial(LLMGRPO.update_input_model, arg_cls=RLHFArguments),
inputs=[LLMGRPO.element('model')],
outputs=[LLMGRPO.element('train_record')] + list(LLMGRPO.valid_elements().values()))
app.load(
partial(LLMInfer.update_input_model, arg_cls=DeployArguments, has_record=False),
inputs=[LLMInfer.element('model')],
outputs=list(LLMInfer.valid_elements().values()))
app.load(
partial(LLMExport.update_input_model, arg_cls=ExportArguments, has_record=False),
inputs=[LLMExport.element('model')],
outputs=list(LLMExport.valid_elements().values()))
app.load(
partial(LLMEval.update_input_model, arg_cls=EvalArguments, has_record=False),
inputs=[LLMEval.element('model')],
outputs=list(LLMEval.valid_elements().values()))
app.load(
partial(LLMSample.update_input_model, arg_cls=SamplingArguments, has_record=False),
inputs=[LLMSample.element('model')],
outputs=list(LLMSample.valid_elements().values()))
app.queue(**concurrent).launch(server_name=server, inbrowser=True, server_port=port, height=800, share=share)
def webui_main(args: Optional[Union[List[str], WebUIArguments]] = None):
return SwiftWebUI(args).main()
+429
View File
@@ -0,0 +1,429 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import dataclasses
import gradio as gr
import json
import os
import sys
import time
import typing
from collections import OrderedDict
from dataclasses import fields
from datetime import datetime
from functools import wraps
from gradio import Accordion, Audio, Button, Checkbox, Dropdown, File, Image, Slider, Tab, TabItem, Textbox, Video
from modelscope.hub.utils.utils import get_cache_dir
from typing import Any, Dict, List, Literal, Optional, Type, Union, get_args, get_origin
from swift.arguments import BaseArguments
from swift.model import get_matched_model_meta
from swift.template import TEMPLATE_MAPPING
all_langs = ['zh', 'en']
builder: Type['BaseUI'] = None
base_builder: Type['BaseUI'] = None
DEFAULT_GRPO_SYSTEM = (
'A conversation between User and Assistant. The user asks a question, and the Assistant solves it. '
'The assistant first thinks about the reasoning process in the mind and then provides the user '
'with the answer. The reasoning process and answer are enclosed within <think> </think> and <answer> '
'</answer> tags, respectively, i.e., <think> reasoning process here </think>'
'<answer> answer here </answer>')
def update_data(fn):
@wraps(fn)
def wrapper(*args, **kwargs):
elem_id = kwargs.get('elem_id', None)
self = args[0]
if builder is not None:
choices = base_builder.choice(elem_id)
if choices:
choices = [str(choice) if choice is not None else None for choice in choices]
kwargs['choices'] = choices
if not isinstance(self, (Tab, TabItem, Accordion)) and 'interactive' not in kwargs: # noqa
kwargs['interactive'] = True
if 'is_list' in kwargs:
self.is_list = kwargs.pop('is_list')
if base_builder and base_builder.default(elem_id) is not None and not kwargs.get('value'):
kwargs['value'] = base_builder.default(elem_id)
if builder is not None:
if elem_id in builder.locales(builder.lang):
values = builder.locale(elem_id, builder.lang)
if 'info' in values:
kwargs['info'] = values['info']
if 'value' in values:
kwargs['value'] = values['value']
if 'label' in values:
kwargs['label'] = values['label']
if hasattr(builder, 'visible'):
kwargs['visible'] = builder.visible
argument = base_builder.argument(elem_id)
if argument and 'label' in kwargs:
kwargs['label'] = kwargs['label'] + f'({argument})'
kwargs['elem_classes'] = 'align'
ret = fn(self, **kwargs)
self.constructor_args.update(kwargs)
if builder is not None:
builder.element_dict[elem_id] = self
return ret
return wrapper
Textbox.__init__ = update_data(Textbox.__init__)
Dropdown.__init__ = update_data(Dropdown.__init__)
Checkbox.__init__ = update_data(Checkbox.__init__)
Slider.__init__ = update_data(Slider.__init__)
TabItem.__init__ = update_data(TabItem.__init__)
Accordion.__init__ = update_data(Accordion.__init__)
Button.__init__ = update_data(Button.__init__)
File.__init__ = update_data(File.__init__)
Image.__init__ = update_data(Image.__init__)
Video.__init__ = update_data(Video.__init__)
Audio.__init__ = update_data(Audio.__init__)
class BaseUI:
choice_dict: Dict[str, List] = {}
default_dict: Dict[str, Any] = {}
locale_dict: Dict[str, Dict] = {}
element_dict: Dict[str, Dict] = {}
arguments: Dict[str, str] = {}
sub_ui: List[Type['BaseUI']] = []
group: str = None
lang: str = all_langs[0]
int_regex = r'^[-+]?[0-9]+$'
float_regex = r'[-+]?(?:\d*\.*\d+)'
bool_regex = r'^(T|t)rue$|^(F|f)alse$'
cache_dir = os.path.join(get_cache_dir(), 'swift-web-ui')
os.makedirs(cache_dir, exist_ok=True)
quote = '\'' if sys.platform != 'win32' else '"'
visible = True
_locale = {
'local_dir_alert': {
'value': {
'zh': '无法识别model_type和template,请手动选择',
'en': 'Cannot recognize the model_type and template, please choose manually'
}
},
}
@classmethod
def build_ui(cls, base_tab: Type['BaseUI']):
"""Build UI"""
global builder, base_builder
cls.element_dict = {}
old_builder = builder
old_base_builder = base_builder
builder = cls
base_builder = base_tab
cls.do_build_ui(base_tab)
builder = old_builder
base_builder = old_base_builder
if cls is base_tab:
for ui in cls.sub_ui:
ui.after_build_ui(base_tab)
@classmethod
def after_build_ui(cls, base_tab: Type['BaseUI']):
pass
@classmethod
def do_build_ui(cls, base_tab: Type['BaseUI']):
"""Build UI"""
pass
@classmethod
def save_cache(cls, key, value):
timestamp = str(int(time.time()))
key = key.replace('/', '-')
filename = os.path.join(cls.cache_dir, key + '-' + timestamp)
with open(filename, 'w', encoding='utf-8') as f:
json.dump(value, f)
@classmethod
def list_cache(cls, key):
files = []
key = key.replace('/', '-')
for _, _, filenames in os.walk(cls.cache_dir):
for filename in filenames:
if filename.startswith(key):
idx = filename.rfind('-')
key, ts = filename[:idx], filename[idx + 1:]
dt_object = datetime.fromtimestamp(int(ts))
formatted_time = dt_object.strftime('%Y/%m/%d %H:%M:%S')
files.append(formatted_time)
return sorted(files, reverse=True)
@classmethod
def load_cache(cls, key, timestamp) -> BaseArguments:
dt_object = datetime.strptime(timestamp, '%Y/%m/%d %H:%M:%S')
timestamp = int(dt_object.timestamp())
key = key.replace('/', '-')
filename = key + '-' + str(timestamp)
with open(os.path.join(cls.cache_dir, filename), 'r', encoding='utf-8') as f:
return json.load(f)
@classmethod
def clear_cache(cls, key):
key = key.replace('/', '-')
for _, _, filenames in os.walk(cls.cache_dir):
for filename in filenames:
if filename.startswith(key):
os.remove(os.path.join(cls.cache_dir, filename))
@classmethod
def choice(cls, elem_id):
"""Get choice by elem_id"""
for sub_ui in BaseUI.sub_ui:
_choice = sub_ui.choice(elem_id)
if _choice:
return _choice
return cls.choice_dict.get(elem_id, [])
@classmethod
def default(cls, elem_id):
"""Get choice by elem_id"""
if elem_id in cls.default_dict:
return cls.default_dict.get(elem_id)
for sub_ui in BaseUI.sub_ui:
_choice = sub_ui.default(elem_id)
if _choice:
return _choice
return None
@classmethod
def locale(cls, elem_id, lang):
"""Get locale by elem_id"""
return cls.locales(lang)[elem_id]
@classmethod
def locales(cls, lang):
"""Get locale by lang"""
locales = OrderedDict()
for sub_ui in cls.sub_ui:
_locales = sub_ui.locales(lang)
locales.update(_locales)
for key, value in cls.locale_dict.items():
locales[key] = {k: v[lang] for k, v in value.items()}
return locales
@classmethod
def elements(cls):
"""Get all elements"""
elements = OrderedDict()
elements.update(cls.element_dict)
for sub_ui in cls.sub_ui:
_elements = sub_ui.elements()
elements.update(_elements)
return elements
@classmethod
def valid_elements(cls):
valid_elements = OrderedDict()
elements = cls.elements()
for key, value in elements.items():
if isinstance(value, (Textbox, Dropdown, Slider, Checkbox)) and key != 'train_record':
valid_elements[key] = value
return valid_elements
@classmethod
def element_keys(cls):
return list(cls.elements().keys())
@classmethod
def valid_element_keys(cls):
return [
key for key, value in cls.elements().items()
if isinstance(value, (Textbox, Dropdown, Slider, Checkbox)) and key != 'train_record'
]
@classmethod
def element(cls, elem_id):
"""Get element by elem_id"""
elements = cls.elements()
return elements[elem_id]
@classmethod
def argument(cls, elem_id):
"""Get argument by elem_id"""
return cls.arguments.get(elem_id)
@classmethod
def set_lang(cls, lang):
cls.lang = lang
for sub_ui in cls.sub_ui:
sub_ui.lang = lang
@staticmethod
def get_choices_from_dataclass(dataclass):
choice_dict = {}
for f in fields(dataclass):
default_value = f.default
type_orign = get_origin(f.type)
type_args = get_args(f.type)
if 'MISSING_TYPE' in str(default_value):
default_value = None
if 'choices' in f.metadata:
choice_dict[f.name] = list(f.metadata['choices'])
if type_orign is Literal:
choice_dict[f.name] = list(type_args)
elif type_orign is Union and type(None) in type_args:
for inner_type in type_args:
if get_origin(inner_type) is Literal:
choice_dict[f.name] = list(get_args(inner_type))
break
if f.name in choice_dict and default_value not in choice_dict[f.name]:
choice_dict[f.name].insert(0, default_value)
return choice_dict
@staticmethod
def get_default_value_from_dataclass(dataclass):
default_dict = {}
for f in fields(dataclass):
if f.default.__class__ is dataclasses._MISSING_TYPE:
default_dict[f.name] = f.default_factory()
else:
default_dict[f.name] = f.default
if isinstance(default_dict[f.name], list):
try:
default_dict[f.name] = ' '.join(default_dict[f.name])
except TypeError:
default_dict[f.name] = None
if not default_dict[f.name] and default_dict[f.name] not in (0, False):
default_dict[f.name] = None
return default_dict
@staticmethod
def get_argument_names(dataclass):
arguments = {}
for f in fields(dataclass):
arguments[f.name] = f'--{f.name}'
return arguments
@classmethod
def update_input_model(cls,
model,
allow_keys=None,
has_record=True,
arg_cls=BaseArguments,
is_ref_model=False,
is_reward_model=False):
keys = cls.valid_element_keys()
if allow_keys:
keys = [key for key in keys if key in allow_keys]
if not model:
ret = [gr.update()] * (len(keys) + int(has_record))
if len(ret) == 1:
return ret[0]
else:
return ret
model_meta = get_matched_model_meta(model)
local_args_path = os.path.join(model, 'args.json')
if model_meta is None and not os.path.exists(local_args_path):
gr.Info(cls._locale['local_dir_alert']['value'][cls.lang])
ret = [gr.update()] * (len(keys) + int(has_record))
if len(ret) == 1:
return ret[0]
else:
return ret
if os.path.exists(local_args_path):
try:
if hasattr(arg_cls, 'resume_from_checkpoint'):
try:
args = arg_cls(resume_from_checkpoint=model, load_data_args=True)
except Exception as e:
if 'using `--model`' in str(e): # TODO a dirty fix
args = arg_cls(model=model, load_data_args=True)
else:
raise e
else:
if os.path.exists(os.path.join(model, 'adapter_config.json')):
args = arg_cls(adapters=model, load_data_args=True)
else:
args = arg_cls(model=model, load_data_args=True)
except ValueError:
return [gr.update()] * (len(keys) + int(has_record))
values = []
for key in keys:
arg_value = getattr(args, key, None)
if arg_value and key != 'model':
if key in ('torch_dtype', 'bnb_4bit_compute_dtype'):
arg_value = str(arg_value).split('.')[1]
if isinstance(arg_value, list) and key != 'dataset':
try:
arg_value = ' '.join(arg_value)
except Exception:
arg_value = None
values.append(gr.update(value=arg_value))
else:
values.append(gr.update())
ret = [gr.update(choices=[])] * int(has_record) + values
if len(ret) == 1:
return ret[0]
else:
return ret
else:
values = []
for key in keys:
if key not in ('template', 'model_type', 'ref_model_type', 'reward_model_type', 'system'):
values.append(gr.update())
elif key in ('template', 'model_type', 'ref_model_type', 'reward_model_type'):
if key == 'ref_model_type':
if is_ref_model:
values.append(gr.update(value=getattr(model_meta, 'model_type')))
else:
values.append(gr.update())
elif key == 'reward_model_type':
if is_reward_model:
values.append(gr.update(value=getattr(model_meta, 'model_type')))
else:
values.append(gr.update())
else:
values.append(gr.update(value=getattr(model_meta, key)))
else:
if cls.group == 'llm_grpo':
values.append(gr.update(value=DEFAULT_GRPO_SYSTEM))
else:
values.append(gr.update(value=TEMPLATE_MAPPING[model_meta.template].default_system))
if has_record:
return [gr.update(choices=cls.list_cache(model))] + values
else:
if len(values) == 1:
return values[0]
return values
@classmethod
def update_all_settings(cls, model, train_record, base_tab):
if not train_record:
return [gr.update()] * len(cls.valid_elements())
cache = cls.load_cache(model, train_record)
updates = []
for key, value in base_tab.valid_elements().items():
if key in cache:
updates.append(gr.update(value=cache[key]))
else:
updates.append(gr.update())
return updates
@classmethod
def update_ddp_num(cls, gpu_ids, use_ddp):
if use_ddp:
if 'cpu' in gpu_ids:
return None
else:
return len(gpu_ids)
return 1
+2
View File
@@ -0,0 +1,2 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from .llm_eval import LLMEval
+129
View File
@@ -0,0 +1,129 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import gradio as gr
from typing import Type
from swift.arguments import EvalArguments
from swift.utils import get_logger
from ..base import BaseUI
logger = get_logger()
class Eval(BaseUI):
group = 'llm_eval'
locale_dict = {
'eval_backend': {
'label': {
'zh': '评测后端',
'en': 'Eval backend'
},
'info': {
'zh': '选择评测后端',
'en': 'Select eval backend'
}
},
'eval_dataset': {
'label': {
'zh': '评测数据集',
'en': 'Evaluation dataset'
},
'info': {
'zh': '选择评测数据集,支持多选 (先选择评测后端)',
'en': 'Select eval dataset, multiple datasets supported (select eval backend first)'
}
},
'eval_limit': {
'label': {
'zh': '评测数据个数',
'en': 'Eval numbers for each dataset'
},
'info': {
'zh': '每个评测集的取样数',
'en': 'Number of rows sampled from each dataset'
}
},
'eval_output_dir': {
'label': {
'zh': '评测输出目录',
'en': 'Eval output dir'
},
'info': {
'zh': '评测结果的输出目录',
'en': 'The dir to save the eval results'
}
},
'custom_eval_config': {
'label': {
'zh': '自定义数据集评测配置',
'en': 'Custom eval config'
},
'info': {
'zh': '可以使用该配置评测自己的数据集,详见github文档的评测部分',
'en': 'Use this config to eval your own datasets, check the docs in github for details'
}
},
'eval_url': {
'label': {
'zh': '评测链接',
'en': 'The eval url'
},
'info': {
'zh':
'OpenAI样式的评测链接(如:http://localhost:8080/v1/chat/completions),用于评测接口(模型类型输入为实际模型类型)',
'en':
'The OpenAI style link(like: http://localhost:8080/v1/chat/completions) for '
'evaluation(Input actual model type into model_type)'
}
},
'api_key': {
'label': {
'zh': '接口token',
'en': 'The url token'
},
'info': {
'zh': 'eval_url的token',
'en': 'The token used with eval_url'
}
},
'infer_backend': {
'label': {
'zh': '推理框架',
'en': 'Infer backend'
},
}
}
@classmethod
def do_build_ui(cls, base_tab: Type['BaseUI']):
try:
eval_dataset_dict = EvalArguments.list_eval_dataset()
default_backend = EvalArguments.eval_backend
except Exception as e:
logger.warn(e)
eval_dataset_dict = {}
default_backend = None
with gr.Row():
gr.Dropdown(elem_id='eval_backend', choices=list(eval_dataset_dict.keys()), value=default_backend, scale=20)
gr.Dropdown(
elem_id='eval_dataset',
is_list=True,
choices=eval_dataset_dict.get(default_backend, []),
multiselect=True,
allow_custom_value=True,
scale=20)
gr.Textbox(elem_id='eval_limit', scale=20)
gr.Dropdown(elem_id='infer_backend', scale=20)
with gr.Row():
gr.Textbox(elem_id='custom_eval_config', scale=20)
gr.Textbox(elem_id='eval_output_dir', scale=20)
gr.Textbox(elem_id='eval_url', scale=20)
gr.Textbox(elem_id='api_key', scale=20)
def update_eval_dataset(backend):
return gr.update(choices=eval_dataset_dict[backend])
cls.element('eval_backend').change(update_eval_dataset, [cls.element('eval_backend')],
[cls.element('eval_dataset')])
+202
View File
@@ -0,0 +1,202 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import gradio as gr
import json
import os
import re
import sys
import time
from datetime import datetime
from functools import partial
from json import JSONDecodeError
from transformers.utils import is_torch_cuda_available, is_torch_npu_available
from typing import Type
from swift.arguments import EvalArguments
from swift.utils import get_device_count
from ..base import BaseUI
from ..llm_train import run_command_in_background_with_popen
from .eval import Eval
from .model import Model
from .runtime import EvalRuntime
class LLMEval(BaseUI):
group = 'llm_eval'
sub_ui = [Model, Eval, EvalRuntime]
cmd = 'eval'
locale_dict = {
'llm_eval': {
'label': {
'zh': 'LLM评测',
'en': 'LLM Evaluation',
}
},
'more_params': {
'label': {
'zh': '更多参数',
'en': 'More params'
},
'info': {
'zh': '以json格式或--xxx xxx命令行格式填入',
'en': 'Fill in with json format or --xxx xxx cmd format'
}
},
'evaluate': {
'value': {
'zh': '开始评测',
'en': 'Begin Evaluation'
},
},
'gpu_id': {
'label': {
'zh': '选择可用GPU',
'en': 'Choose GPU'
},
'info': {
'zh': '选择训练使用的GPU号,如CUDA不可用只能选择CPU',
'en': 'Select GPU to train'
}
},
}
choice_dict = BaseUI.get_choices_from_dataclass(EvalArguments)
default_dict = BaseUI.get_default_value_from_dataclass(EvalArguments)
arguments = BaseUI.get_argument_names(EvalArguments)
@classmethod
def do_build_ui(cls, base_tab: Type['BaseUI']):
with gr.TabItem(elem_id='llm_eval', label=''):
default_device = 'cpu'
device_count = get_device_count()
if device_count > 0:
default_device = '0'
with gr.Blocks():
Model.build_ui(base_tab)
Eval.build_ui(base_tab)
EvalRuntime.build_ui(base_tab)
with gr.Row(equal_height=True):
gr.Textbox(elem_id='more_params', lines=4, scale=20)
gr.Button(elem_id='evaluate', scale=2, variant='primary')
gr.Dropdown(
elem_id='gpu_id',
multiselect=True,
choices=[str(i) for i in range(device_count)] + ['cpu'],
value=default_device,
scale=8)
cls.element('evaluate').click(
cls.eval_model, list(base_tab.valid_elements().values()),
[cls.element('runtime_tab'), cls.element('running_tasks')])
base_tab.element('running_tasks').change(
partial(EvalRuntime.task_changed, base_tab=base_tab), [base_tab.element('running_tasks')],
list(base_tab.valid_elements().values()) + [cls.element('log')])
EvalRuntime.element('kill_task').click(
EvalRuntime.kill_task,
[EvalRuntime.element('running_tasks')],
[EvalRuntime.element('running_tasks')] + [EvalRuntime.element('log')],
)
@classmethod
def eval(cls, *args):
eval_args = cls.get_default_value_from_dataclass(EvalArguments)
kwargs = {}
kwargs_is_list = {}
other_kwargs = {}
more_params = {}
more_params_cmd = ''
keys = cls.valid_element_keys()
for key, value in zip(keys, args):
compare_value = eval_args.get(key)
compare_value_arg = str(compare_value) if not isinstance(compare_value, (list, dict)) else compare_value
compare_value_ui = str(value) if not isinstance(value, (list, dict)) else value
if key in eval_args and compare_value_ui != compare_value_arg and value:
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
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
kwargs.update(more_params)
model = kwargs.get('model')
if model and os.path.exists(model) and os.path.exists(os.path.join(model, 'args.json')):
if os.path.exists(os.path.join(model, 'adapter_config.json')):
kwargs['adapters'] = kwargs.pop('model')
eval_args = EvalArguments(
**{
key: value.split(' ') if key in kwargs_is_list and kwargs_is_list[key] else value
for key, value in kwargs.items()
})
params = ''
command = ['swift', 'eval']
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 more_params_cmd != '':
params += f'{more_params_cmd.strip()} '
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:])
all_envs = {}
devices = other_kwargs['gpu_id']
devices = [d for d in devices if d]
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 = ''
now = datetime.now()
time_str = f'{now.year}{now.month}{now.day}{now.hour}{now.minute}{now.second}'
file_path = f'output/{eval_args.model_type}-{time_str}'
if not os.path.exists(file_path):
os.makedirs(file_path, exist_ok=True)
log_file = os.path.join(os.getcwd(), f'{file_path}/run_eval.log')
eval_args.log_file = log_file
params += f'--log_file "{log_file}" '
command.extend(['--log_file', f'{log_file}'])
params += '--ignore_args_error true '
command.extend(['--ignore_args_error', 'true'])
if sys.platform == 'win32':
if cuda_param:
cuda_param = f'set {cuda_param} && '
run_command = f'{cuda_param}start /b swift eval {params} > {log_file} 2>&1'
else:
run_command = f'{cuda_param} nohup swift eval {params} > {log_file} 2>&1 &'
return command, all_envs, run_command, eval_args, log_file
@classmethod
def eval_model(cls, *args):
command, all_envs, run_command, eval_args, log_file = cls.eval(*args)
run_command_in_background_with_popen(command, all_envs, log_file)
return gr.update(open=True), EvalRuntime.refresh_tasks(log_file)
+78
View File
@@ -0,0 +1,78 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import gradio as gr
from functools import partial
from typing import Type
from swift.arguments import EvalArguments
from swift.model import ModelType, get_model_list
from swift.template import TEMPLATE_MAPPING
from ..base import BaseUI
class Model(BaseUI):
group = 'llm_eval'
locale_dict = {
'checkpoint': {
'value': {
'zh': '训练后的模型',
'en': 'Trained model'
}
},
'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,如果是训练后的模型请填入checkpoint-xxx的目录',
'en': 'The actual model id or path, if is a trained model, please fill in the checkpoint-xxx dir'
}
},
'reset': {
'value': {
'zh': '恢复初始值',
'en': 'Reset to default'
},
},
'template': {
'label': {
'zh': '模型Prompt模板类型',
'en': 'Prompt template type'
},
'info': {
'zh': '选择匹配模型的Prompt模板',
'en': 'Choose the template type of the model'
}
},
}
@classmethod
def do_build_ui(cls, base_tab: Type['BaseUI']):
with gr.Row():
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)
@classmethod
def after_build_ui(cls, base_tab: Type['BaseUI']):
cls.element('model').change(
partial(cls.update_input_model, arg_cls=EvalArguments, has_record=False),
inputs=[cls.element('model')],
outputs=list(cls.valid_elements().values()))
+107
View File
@@ -0,0 +1,107 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import gradio as gr
from packaging import version
from typing import Type
from swift.utils import get_logger
from ..base import BaseUI
from ..llm_infer import Runtime
logger = get_logger()
class EvalRuntime(Runtime):
group = 'llm_eval'
cmd = 'eval'
locale_dict = {
'runtime_tab': {
'label': {
'zh': '运行时',
'en': 'Runtime'
},
},
'running_cmd': {
'label': {
'zh': '运行命令',
'en': 'Command line'
},
'info': {
'zh': '执行的实际命令',
'en': 'The actual command'
}
},
'show_log': {
'value': {
'zh': '展示评测状态',
'en': 'Show eval status'
},
},
'stop_show_log': {
'value': {
'zh': '停止展示',
'en': 'Stop showing running status'
},
},
'log': {
'label': {
'zh': '日志输出',
'en': 'Logging content'
},
'info': {
'zh': '如果日志无更新请再次点击"展示评测状态"',
'en': 'Please press "Show eval status" if the log content is not updating'
}
},
'running_tasks': {
'label': {
'zh': '运行中评测',
'en': 'Running evaluation'
},
'info': {
'zh': '所有的swift eval命令启动的任务',
'en': 'All tasks started by swift eval'
}
},
'refresh_tasks': {
'value': {
'zh': '找回评测',
'en': 'Find evaluation'
},
},
'kill_task': {
'value': {
'zh': '杀死评测',
'en': 'Kill evaluation'
},
},
}
@classmethod
def do_build_ui(cls, base_tab: Type['BaseUI']):
with gr.Accordion(elem_id='runtime_tab', open=False, visible=True):
with gr.Blocks():
with gr.Row(equal_height=True):
gr.Dropdown(elem_id='running_tasks', scale=10)
gr.Button(elem_id='refresh_tasks', scale=1, variant='primary')
gr.Button(elem_id='show_log', scale=1, variant='primary')
gr.Button(elem_id='stop_show_log', scale=1)
gr.Button(elem_id='kill_task', scale=1, size='lg')
with gr.Row():
gr.Textbox(elem_id='log', lines=6, visible=False)
concurrency_limit = {}
if version.parse(gr.__version__) >= version.parse('4.0.0'):
concurrency_limit = {'concurrency_limit': 5}
cls.log_event = base_tab.element('show_log').click(cls.update_log, [], [cls.element('log')]).then(
cls.wait, [base_tab.element('running_tasks')], [cls.element('log')], **concurrency_limit)
base_tab.element('stop_show_log').click(cls.break_log_event, [cls.element('running_tasks')], [])
base_tab.element('refresh_tasks').click(
cls.refresh_tasks,
[base_tab.element('running_tasks')],
[base_tab.element('running_tasks')],
)
+2
View File
@@ -0,0 +1,2 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from .llm_export import LLMExport
+88
View File
@@ -0,0 +1,88 @@
# 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 Export(BaseUI):
group = 'llm_export'
locale_dict = {
'merge_lora': {
'label': {
'zh': '合并LoRA',
'en': 'Merge LoRA'
},
'info': {
'zh':
'LoRA合并的路径在填入的checkpoint同级目录,请查看运行时log获取更具体的信息',
'en':
'The output path is in the sibling directory as the input checkpoint. '
'Please refer to the runtime log for more specific information.'
},
},
'device_map': {
'label': {
'zh': '合并LoRA使用的device_map',
'en': 'The device_map when merge-lora'
},
'info': {
'zh': '如果显存不够请填入cpu',
'en': 'If GPU memory is not enough, fill in cpu'
},
},
'quant_bits': {
'label': {
'zh': '量化比特数',
'en': 'Quantize bits'
},
},
'quant_method': {
'label': {
'zh': '量化方法',
'en': 'Quantize method'
},
},
'quant_n_samples': {
'label': {
'zh': '量化集采样数',
'en': 'Sampled rows from calibration dataset'
},
},
'max_length': {
'label': {
'zh': '量化集的max-length',
'en': 'The quantize sequence length'
},
},
'output_dir': {
'label': {
'zh': '输出路径',
'en': 'Output dir'
},
},
'dataset': {
'label': {
'zh': '校准数据集',
'en': 'Calibration datasets'
},
},
}
@classmethod
def do_build_ui(cls, base_tab: Type['BaseUI']):
with gr.Row():
gr.Checkbox(elem_id='merge_lora', scale=10)
gr.Textbox(elem_id='device_map', scale=20)
with gr.Row():
gr.Dropdown(elem_id='quant_bits', scale=20)
gr.Dropdown(elem_id='quant_method', scale=20)
gr.Textbox(elem_id='quant_n_samples', scale=20)
gr.Textbox(elem_id='max_length', scale=20)
with gr.Row():
gr.Textbox(elem_id='output_dir', scale=20)
gr.Dropdown(
elem_id='dataset', multiselect=True, allow_custom_value=True, choices=get_dataset_list(), scale=20)
+204
View File
@@ -0,0 +1,204 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import gradio as gr
import json
import os
import re
import sys
from datetime import datetime
from functools import partial
from json import JSONDecodeError
from transformers.utils import is_torch_cuda_available, is_torch_npu_available
from typing import Type
from swift.arguments import ExportArguments
from swift.utils import get_device_count
from ..base import BaseUI
from ..llm_train import run_command_in_background_with_popen
from .export import Export
from .model import Model
from .runtime import ExportRuntime
class LLMExport(BaseUI):
group = 'llm_export'
sub_ui = [Model, Export, ExportRuntime]
locale_dict = {
'llm_export': {
'label': {
'zh': 'LLM导出',
'en': 'LLM Export',
}
},
'more_params': {
'label': {
'zh': '更多参数',
'en': 'More params'
},
'info': {
'zh': '以json格式或--xxx xxx命令行格式填入',
'en': 'Fill in with json format or --xxx xxx cmd format'
}
},
'export': {
'value': {
'zh': '开始导出',
'en': 'Begin Export'
},
},
'gpu_id': {
'label': {
'zh': '选择可用GPU',
'en': 'Choose GPU'
},
'info': {
'zh': '选择使用的GPU号,如CUDA不可用只能选择CPU',
'en': 'Select GPU to export'
}
},
}
choice_dict = BaseUI.get_choices_from_dataclass(ExportArguments)
default_dict = BaseUI.get_default_value_from_dataclass(ExportArguments)
arguments = BaseUI.get_argument_names(ExportArguments)
@classmethod
def do_build_ui(cls, base_tab: Type['BaseUI']):
with gr.TabItem(elem_id='llm_export', label=''):
default_device = 'cpu'
device_count = get_device_count()
if device_count > 0:
default_device = '0'
with gr.Blocks():
Model.build_ui(base_tab)
Export.build_ui(base_tab)
ExportRuntime.build_ui(base_tab)
with gr.Row(equal_height=True):
gr.Textbox(elem_id='more_params', lines=4, scale=20)
gr.Button(elem_id='export', scale=2, variant='primary')
gr.Dropdown(
elem_id='gpu_id',
multiselect=True,
choices=[str(i) for i in range(device_count)] + ['cpu'],
value=default_device,
scale=8)
cls.element('export').click(
cls.export_model, list(base_tab.valid_elements().values()),
[cls.element('runtime_tab'), cls.element('running_tasks')])
base_tab.element('running_tasks').change(
partial(ExportRuntime.task_changed, base_tab=base_tab), [base_tab.element('running_tasks')],
list(base_tab.valid_elements().values()) + [cls.element('log')])
ExportRuntime.element('kill_task').click(
ExportRuntime.kill_task,
[ExportRuntime.element('running_tasks')],
[ExportRuntime.element('running_tasks')] + [ExportRuntime.element('log')],
)
@classmethod
def export(cls, *args):
export_args = cls.get_default_value_from_dataclass(ExportArguments)
kwargs = {}
kwargs_is_list = {}
other_kwargs = {}
more_params = {}
more_params_cmd = ''
keys = cls.valid_element_keys()
for key, value in zip(keys, args):
compare_value = export_args.get(key)
compare_value_arg = str(compare_value) if not isinstance(compare_value, (list, dict)) else compare_value
compare_value_ui = str(value) if not isinstance(value, (list, dict)) else value
if key in export_args and compare_value_ui != compare_value_arg and value:
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
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
kwargs.update(more_params)
model = kwargs.get('model')
if os.path.exists(model) and os.path.exists(os.path.join(model, 'args.json')):
if os.path.exists(os.path.join(model, 'adapter_config.json')):
kwargs['adapters'] = kwargs.pop('model')
export_args = ExportArguments(
**{
key: value.split(' ') if key in kwargs_is_list and kwargs_is_list[key] else value
for key, value in kwargs.items()
})
params = ''
command = ['swift', 'export']
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 more_params_cmd != '':
params += f'{more_params_cmd.strip()} '
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:])
all_envs = {}
devices = other_kwargs['gpu_id']
devices = [d for d in devices if d]
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 = ''
now = datetime.now()
time_str = f'{now.year}{now.month}{now.day}{now.hour}{now.minute}{now.second}'
file_path = f'output/{export_args.model_type}-{time_str}'
if not os.path.exists(file_path):
os.makedirs(file_path, exist_ok=True)
log_file = os.path.join(os.getcwd(), f'{file_path}/run_export.log')
export_args.log_file = log_file
params += f'--log_file "{log_file}" '
command.extend(['--log_file', f'{log_file}'])
params += '--ignore_args_error true '
command.extend(['--ignore_args_error', 'true'])
additional_param = ''
if export_args.quant_method == 'gptq':
additional_param = 'OMP_NUM_THREADS=14'
all_envs['OMP_NUM_THREADS'] = '14'
if sys.platform == 'win32':
if cuda_param:
cuda_param = f'set {cuda_param} && '
if additional_param:
additional_param = f'set {additional_param} && '
run_command = f'{cuda_param}{additional_param}start /b swift export {params} > {log_file} 2>&1'
else:
run_command = f'{cuda_param} {additional_param} nohup swift export {params} > {log_file} 2>&1 &'
return command, all_envs, run_command, export_args, log_file
@classmethod
def export_model(cls, *args):
command, all_envs, run_command, export_args, log_file = cls.export(*args)
run_command_in_background_with_popen(command, all_envs, log_file)
return gr.update(open=True), ExportRuntime.refresh_tasks(log_file)
+83
View File
@@ -0,0 +1,83 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import gradio as gr
from functools import partial
from typing import Type
from swift.arguments import ExportArguments
from swift.model import ModelType, get_model_list
from swift.template import TEMPLATE_MAPPING
from ..base import BaseUI
class Model(BaseUI):
group = 'llm_export'
locale_dict = {
'checkpoint': {
'value': {
'zh': '训练后的模型',
'en': 'Trained model'
}
},
'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,如果是训练后的模型请填入checkpoint-xxx的目录',
'en': 'The actual model id or path, if is a trained model, please fill in the checkpoint-xxx dir'
}
},
'reset': {
'value': {
'zh': '恢复初始值',
'en': 'Reset to default'
},
},
'template': {
'label': {
'zh': '模型Prompt模板类型',
'en': 'Prompt template type'
},
'info': {
'zh': '选择匹配模型的Prompt模板',
'en': 'Choose the template type of the model'
}
},
}
ignored_models = ['int1', 'int2', 'int4', 'int8', 'awq', 'gptq', 'bnb', 'eetq', 'aqlm', 'hqq']
@classmethod
def do_build_ui(cls, base_tab: Type['BaseUI']):
with gr.Row():
all_models = [
model for model in get_model_list() if not any([ignored in model for ignored in cls.ignored_models])
]
gr.Dropdown(
elem_id='model',
scale=20,
choices=all_models,
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)
@classmethod
def after_build_ui(cls, base_tab: Type['BaseUI']):
cls.element('model').change(
partial(cls.update_input_model, arg_cls=ExportArguments, has_record=False),
inputs=[cls.element('model')],
outputs=list(cls.valid_elements().values()))
+75
View File
@@ -0,0 +1,75 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from swift.utils import get_logger
from ..llm_infer import Runtime
logger = get_logger()
class ExportRuntime(Runtime):
group = 'llm_export'
cmd = 'export'
locale_dict = {
'runtime_tab': {
'label': {
'zh': '运行时',
'en': 'Runtime'
},
},
'running_cmd': {
'label': {
'zh': '运行命令',
'en': 'Command line'
},
'info': {
'zh': '执行的实际命令',
'en': 'The actual command'
}
},
'show_log': {
'value': {
'zh': '展示导出状态',
'en': 'Show export status'
},
},
'stop_show_log': {
'value': {
'zh': '停止展示',
'en': 'Stop showing running status'
},
},
'log': {
'label': {
'zh': '日志输出',
'en': 'Logging content'
},
'info': {
'zh': '如果日志无更新请再次点击"展示导出状态"',
'en': 'Please press "Show export status" if the log content is not updating'
}
},
'running_tasks': {
'label': {
'zh': '运行中导出任务',
'en': 'Running export task'
},
'info': {
'zh': '所有的swift export命令启动的任务',
'en': 'All tasks started by swift export'
}
},
'refresh_tasks': {
'value': {
'zh': '找回导出任务',
'en': 'Find export'
},
},
'kill_task': {
'value': {
'zh': '杀死导出任务',
'en': 'Kill export'
},
},
}
+2
View File
@@ -0,0 +1,2 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from .llm_grpo import LLMGRPO
+7
View File
@@ -0,0 +1,7 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from ..llm_train import Advanced
class GRPOAdvanced(Advanced):
group = 'llm_grpo'
+7
View File
@@ -0,0 +1,7 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from ..llm_train import Dataset
class GRPODataset(Dataset):
group = 'llm_grpo'
+259
View File
@@ -0,0 +1,259 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import gradio as gr
import json
import os
import re
import sys
import time
from datetime import datetime
from functools import partial
from json import JSONDecodeError
from transformers.utils import is_torch_cuda_available, is_torch_npu_available
from typing import Type
from swift.arguments import RolloutArguments
from swift.utils import get_device_count, get_logger
from ..base import BaseUI
from ..llm_train import run_command_in_background_with_popen
from .external_runtime import RolloutRuntime
logger = get_logger()
class LLMRollout(BaseUI):
group = 'llm_grpo'
is_multimodal = True
sub_ui = [RolloutRuntime]
locale_dict = {
'tensor_parallel_size': {
'label': {
'zh': '张量并行大小',
'en': 'Tensor parallel size'
},
},
'data_parallel_size': {
'label': {
'zh': '数据并行大小',
'en': 'Data parallel size'
},
},
'max_model_len': {
'label': {
'zh': '模型支持的最大长度',
'en': 'Max model len'
},
},
'gpu_memory_utilization': {
'label': {
'zh': 'GPU显存利用率',
'en': 'GPU memory utilization'
},
},
'port': {
'label': {
'zh': 'Rollout端口',
'en': 'Rollout Port'
},
},
'llm_rollout': {
'label': {
'zh': '外部rollout模型部署',
'en': 'External rollout model deployment',
}
},
'rollout': {
'value': {
'zh': '开始Rollout',
'en': 'Start Rollout',
}
},
'load_alert': {
'value': {
'zh': 'Rollout中,请点击"展示rollout状态"查看',
'en': 'Start to rollout, '
'please Click "Show running '
'status" to view details',
}
},
'port_alert': {
'value': {
'zh': '该端口已被占用',
'en': 'The port has been occupied'
}
},
'rollout_gpu_id': {
'label': {
'zh': '选择用于rollout的GPU',
'en': 'Choose GPU for rollout'
}
},
'more_roll_params': {
'label': {
'zh': '更多rollout参数',
'en': 'More rollout params'
},
'info': {
'zh': '以json格式或--xxx xxx命令行格式填入',
'en': 'Fill in with json format or --xxx xxx cmd format'
}
}
}
choice_dict = BaseUI.get_choices_from_dataclass(RolloutArguments)
default_dict = BaseUI.get_default_value_from_dataclass(RolloutArguments)
arguments = BaseUI.get_argument_names(RolloutArguments)
@classmethod
def do_build_ui(cls, base_tab: Type['BaseUI']):
with gr.Accordion(elem_id='llm_rollout', open=False, visible=False):
default_device = 'cpu'
device_count = get_device_count()
if device_count > 0:
default_device = '0'
with gr.Blocks():
with gr.Row():
gr.Textbox(elem_id='tensor_parallel_size', lines=1, value='1', scale=4)
gr.Textbox(elem_id='data_parallel_size', lines=1, value='1', scale=4)
gr.Slider(elem_id='gpu_memory_utilization', minimum=0.0, maximum=1.0, step=0.05, value=0.9, scale=4)
with gr.Row(equal_height=True):
gr.Dropdown(
elem_id='rollout_gpu_id',
multiselect=True,
choices=[str(i) for i in range(device_count)] + ['cpu'],
value=default_device,
scale=4)
gr.Textbox(elem_id='port', lines=1, value='8000', scale=2)
gr.Textbox(elem_id='more_roll_params', lines=1, scale=8)
gr.Button(elem_id='rollout', scale=2, variant='primary')
RolloutRuntime.build_ui(base_tab)
base_tab.element('rollout_running_tasks').change(
partial(RolloutRuntime.task_changed, base_tab=base_tab),
[base_tab.element('rollout_running_tasks')],
list(cls.valid_elements().values()) + [cls.element('rollout_log')])
RolloutRuntime.element('rollout_kill_task').click(
RolloutRuntime.kill_task,
[RolloutRuntime.element('rollout_running_tasks')],
[RolloutRuntime.element('rollout_running_tasks')] + [RolloutRuntime.element('rollout_log')],
)
@classmethod
def rollout(cls, *args):
rollout_args = cls.get_default_value_from_dataclass(RolloutArguments)
kwargs = {}
kwargs_is_list = {}
other_kwargs = {}
more_params = {}
more_params_cmd = ''
model_args = args[-3:]
kwargs['model'] = model_args[0]
kwargs['model_type'] = model_args[1]
kwargs['template'] = model_args[2]
args = args[:-3]
keys = cls.valid_element_keys()
for key, value in zip(keys, args):
compare_value = rollout_args.get(key)
compare_value_arg = str(compare_value) if not isinstance(compare_value, (list, dict)) else compare_value
compare_value_ui = str(value) if not isinstance(value, (list, dict)) else value
if key in rollout_args and compare_value_ui != compare_value_arg and value:
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
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_roll_params' and value:
try:
more_params = json.loads(value)
except (JSONDecodeError or TypeError):
more_params_cmd = value
kwargs.update(more_params)
rollout_args = RolloutArguments(
**{
key: value.split(' ') if key in kwargs_is_list and kwargs_is_list[key] else value
for key, value in kwargs.items()
})
if rollout_args.port in RolloutRuntime.get_all_ports():
raise gr.Error(cls.locale('port_alert', cls.lang)['value'])
params = ''
command = ['swift', 'rollout']
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 'port' not in kwargs:
params += f'--port "{rollout_args.port}" '
command.extend(['--port', f'{rollout_args.port}'])
if more_params_cmd != '':
params += f'{more_params_cmd.strip()} '
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:])
devices = other_kwargs['rollout_gpu_id']
devices = [d for d in devices if d]
assert (len(devices) == 1 or 'cpu' not in devices)
gpus = ','.join(devices)
cuda_param = ''
all_envs = {}
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 = ''
output_dir = 'rollout_output'
now = datetime.now()
time_str = f'{now.year}{now.month}{now.day}{now.hour}{now.minute}{now.second}'
file_path = f'{output_dir}/{rollout_args.model_type}-{time_str}'
if not os.path.exists(file_path):
os.makedirs(file_path, exist_ok=True)
log_file = os.path.join(os.getcwd(), f'{file_path}/run_rollout.log')
rollout_args.log_file = log_file
params += f'--log_file "{log_file}" '
command.extend(['--log_file', f'{log_file}'])
params += '--ignore_args_error true '
command.extend(['--ignore_args_error', 'true'])
if sys.platform == 'win32':
if cuda_param:
cuda_param = f'set {cuda_param} && '
run_command = f'{cuda_param}start /b swift rollout {params} > {log_file} 2>&1'
else:
run_command = f'{cuda_param} nohup swift rollout {params} > {log_file} 2>&1 &'
return command, all_envs, run_command, rollout_args, log_file
@classmethod
def rollout_model(cls, *args):
command, all_envs, run_command, rollout_args, log_file = cls.rollout(*args)
logger.info(f'Running rollout command: {run_command}')
run_command_in_background_with_popen(command, all_envs, log_file)
gr.Info(cls.locale('load_alert', cls.lang)['value'])
time.sleep(2)
running_task = RolloutRuntime.refresh_tasks(log_file)
return gr.update(open=True), running_task
@classmethod
def external_rollout_display(cls, mode):
if mode == 'server':
return gr.update(visible=True, open=True)
return gr.update(visible=False)
+138
View File
@@ -0,0 +1,138 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import gradio as gr
import psutil
import subprocess
import sys
from packaging import version
from typing import Type
from swift.utils import get_logger
from ..base import BaseUI
from ..llm_infer import Runtime
logger = get_logger()
class RolloutRuntime(Runtime):
group = 'llm_grpo'
cmd = 'rollout'
locale_dict = {
'rollout_runtime_tab': {
'label': {
'zh': '运行时',
'en': 'Runtime'
},
},
'rollout_running_cmd': {
'label': {
'zh': '运行命令',
'en': 'Command line'
},
'info': {
'zh': '执行的实际命令',
'en': 'The actual command'
}
},
'rollout_show_log': {
'value': {
'zh': '展示rollout状态',
'en': 'Show running status'
},
},
'rollout_stop_show_log': {
'value': {
'zh': '停止展示',
'en': 'Stop showing running status'
},
},
'rollout_log': {
'label': {
'zh': '日志输出',
'en': 'Logging content'
},
'info': {
'zh': '如果日志无更新请再次点击"展示rollout状态"',
'en': 'Please press "Show running status" if the log content is not updating'
}
},
'rollout_running_tasks': {
'label': {
'zh': '运行中rollout',
'en': 'Running rollouts'
},
'info': {
'zh': '所有的swift rollout命令启动的任务',
'en': 'Started by swift rollout'
}
},
'rollout_refresh_tasks': {
'value': {
'zh': '找回rollout',
'en': 'Find rollout'
},
},
'rollout_kill_task': {
'value': {
'zh': '杀死rollout',
'en': 'Kill running task'
},
},
}
@classmethod
def do_build_ui(cls, base_tab: Type['BaseUI']):
with gr.Accordion(elem_id='rollout_runtime_tab', open=False, visible=True):
with gr.Blocks():
with gr.Row(equal_height=True):
gr.Dropdown(elem_id='rollout_running_tasks', scale=10, allow_custom_value=True)
with gr.Row(equal_height=True):
gr.Button(elem_id='rollout_refresh_tasks', scale=1, variant='primary')
gr.Button(elem_id='rollout_show_log', scale=1, variant='primary')
gr.Button(elem_id='rollout_stop_show_log', scale=1)
gr.Button(elem_id='rollout_kill_task', scale=1)
with gr.Row():
gr.Textbox(elem_id='rollout_log', lines=6, visible=False)
concurrency_limit = {}
if version.parse(gr.__version__) >= version.parse('4.0.0'):
concurrency_limit = {'concurrency_limit': 5}
base_tab.element('rollout_show_log').click(cls.update_log, [], [cls.element('rollout_log')]).then(
cls.wait, [base_tab.element('rollout_running_tasks')], [cls.element('rollout_log')],
**concurrency_limit)
base_tab.element('rollout_stop_show_log').click(cls.break_log_event,
[cls.element('rollout_running_tasks')], [])
base_tab.element('rollout_refresh_tasks').click(
cls.refresh_tasks,
[base_tab.element('rollout_running_tasks')],
[base_tab.element('rollout_running_tasks')],
)
@classmethod
def kill_task(cls, task):
if task:
pid, all_args = cls.parse_info_from_cmdline(task)
log_file = all_args['log_file']
parent_process = psutil.Process(int(pid))
children = parent_process.children(recursive=True)
commands = []
if sys.platform == 'win32':
commands.append(['taskkill', '/f', '/t', '/pid', pid])
for child in children:
commands.append(['taskkill', '/f', '/t', '/pid', f'{str(child.pid)}'])
else:
commands.append(['pkill', '-9', '-f', log_file])
for child in children:
commands.append(['kill', '-9', f'{str(child.pid)}'])
for cmd in commands:
try:
result = subprocess.run(cmd, capture_output=True, text=True)
assert result.returncode == 0
except Exception as e:
raise e
cls.break_log_event(task)
return [cls.refresh_tasks()] + [gr.update(value=None)]
+260
View File
@@ -0,0 +1,260 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import gradio as gr
from functools import partial
from typing import Type
from swift.arguments import BaseArguments
from swift.model import ModelType, get_model_list
from ..base import BaseUI
class GrpoAdvanced(BaseUI):
group = 'llm_grpo'
locale_dict = {
'grpo_advanced_tab': {
'label': {
'zh': 'GRPO高级参数设置',
'en': 'GRPO advanced settings'
},
},
'loss_type': {
'label': {
'zh': '损失归一化类型',
'en': 'Loss normalization type'
}
},
'epsilon': {
'label': {
'zh': 'Clip系数',
'en': 'Clip coefficient'
}
},
'epsilon_high': {
'label': {
'zh': 'Upper clip系数',
'en': 'Upper clip coefficient'
}
},
'move_model_batches': {
'label': {
'zh': '模型参数移动批次数',
'en': 'Batches of model params moving'
},
'info': {
'zh':
'在模型向vLLM等推理框架移动参数时,将模型分为多少个批次',
'en': ('How many batches to divide the model into '
'when moving parameters to an inference framework such as vLLM')
}
},
'multi_turn_scheduler': {
'label': {
'zh': '多轮调度器',
'en': 'Multi turn Scheduler'
},
'info': {
'zh': '多轮GRPO参数, 传入对应的plugin名称',
'en': 'Multi turn of GRPO parameters, pass in the corresponding plugin name'
}
},
'max_turns': {
'label': {
'zh': '多轮轮数上限',
'en': 'Max num of multi turn'
}
},
'dynamic_sample': {
'label': {
'zh': '动态采样',
'en': 'Dynamic sampling'
},
'info': {
'zh': '筛除group内奖励标准差为0的数据,额外采样新数据',
'en': 'Filter out data with a reward standard deviation of 0 within the group and sample new data'
}
},
'max_resample_times': {
'label': {
'zh': '最大重采样次数',
'en': 'Max num of resampling times'
},
'info': {
'zh': '动态采样设置下限制重采样次数',
'en': 'Limit the number of resampling times when dynamic_sample is set'
}
},
'overlong_filter': {
'label': {
'zh': '跳过超长样本',
'en': 'Skip overlong samples'
},
'info': {
'zh': '跳过超长截断的样本,不参与损失计算',
'en': 'Skip overlong truncated samples and exclude them from loss calculation'
}
},
'beta': {
'label': {
'zh': 'KL正则项系数',
'en': 'KL regularization coefficient'
}
},
'vllm_enable_prefix_caching': {
'label': {
'zh': '开启前缀缓存',
'en': 'Enable prefix cache'
},
'info': {
'zh': 'Colocate模式中vLLM透传参数',
'en': 'vLLM transparent transmission parameters in colocate mode'
}
},
'log_completions': {
'label': {
'zh': '记录生成内容',
'en': 'Record generated content'
},
'info': {
'zh': '是否记录训练中的模型生成内容',
'en': 'Whether to record the model generation content during training'
}
},
'num_iterations': {
'label': {
'zh': '每个批次更新次数',
'en': 'Num of updates per batch'
}
},
'reward_model': {
'label': {
'zh': '奖励模型id或路径',
'en': 'Reward Model id or path'
},
'info': {
'zh': '实际的模型id',
'en': 'The actual model id or model path'
}
},
'reward_model_type': {
'label': {
'zh': '奖励模型类型',
'en': 'Select Reward Model Type'
},
'info': {
'zh': 'SWIFT已支持的模型类型',
'en': 'Base model type supported by SWIFT'
}
},
'reward_model_plugin': {
'label': {
'zh': '奖励模型逻辑',
'en': 'Reward model logic'
},
'info': {
'zh': '利用reward_model_plugin自定义奖励模型的处理逻辑',
'en': 'Use reward_model_plugin to customize the processing logic of the reward model'
}
},
'external_plugins': {
'label': {
'zh': '外部插件文件',
'en': 'External plugin file'
},
'info': {
'zh': '外部插件文件列表,将被注册进插件模块中',
'en': 'List of external plugin files that will be registered into the plugin module'
}
},
'ref_model_type': {
'label': {
'zh': 'Ref模型类型',
'en': 'Ref model type'
},
'info': {
'zh': 'SWIFT已支持的模型类型',
'en': 'Model type supported by SWIFT'
}
},
'ref_model': {
'label': {
'zh': 'Ref模型id或路径',
'en': 'Ref model id or path'
},
'info': {
'zh': '实际的模型id或路径',
'en': 'The actual model id or path'
}
},
}
@classmethod
def do_build_ui(cls, base_tab: Type['BaseUI']):
with gr.TabItem(elem_id='grpo_advanced_tab'):
with gr.Blocks():
with gr.Row():
gr.Dropdown(elem_id='loss_type', choices=['grpo', 'bnpo', 'dr_grpo'], value='grpo', scale=4)
gr.Textbox(elem_id='epsilon', value=0.2, lines=1, scale=4)
gr.Textbox(elem_id='epsilon_high', value=None, lines=1, scale=4)
gr.Textbox(elem_id='beta', value=0.04, lines=1, scale=4)
gr.Textbox(elem_id='num_iterations', lines=1, scale=4)
with gr.Row():
gr.Textbox(elem_id='move_model_batches', lines=1, scale=4)
gr.Checkbox(elem_id='dynamic_sample', scale=4)
gr.Slider(elem_id='max_resample_times', minimum=1, maximum=16, step=1, value=3, scale=4)
gr.Checkbox(elem_id='overlong_filter', scale=4)
gr.Checkbox(elem_id='vllm_enable_prefix_caching', scale=4)
with gr.Row():
gr.Checkbox(elem_id='log_completions', scale=4)
gr.Textbox(elem_id='multi_turn_scheduler', lines=1, scale=4)
gr.Textbox(elem_id='max_turns', lines=1, scale=4)
gr.Textbox(elem_id='external_plugins', lines=1, scale=8)
with gr.Row():
gr.Textbox(elem_id='reward_model_plugin', lines=1, scale=8)
gr.Dropdown(elem_id='reward_model', multiselect=True, choices=get_model_list(), scale=8)
gr.Dropdown(
elem_id='reward_model_type',
multiselect=True,
choices=ModelType.get_model_name_list(),
allow_custom_value=True,
scale=4)
with gr.Blocks():
with gr.Row():
gr.Dropdown(
elem_id='ref_model', scale=12, value=None, choices=get_model_list(), allow_custom_value=True)
gr.Dropdown(elem_id='ref_model_type', choices=ModelType.get_model_name_list(), value=None, scale=8)
@classmethod
def after_build_ui(cls, base_tab: Type['BaseUI']):
cls.element('ref_model').change(
partial(cls.update_input_model, allow_keys=['ref_model_type'], has_record=False, is_ref_model=True),
inputs=[cls.element('ref_model')],
outputs=[cls.element('ref_model_type')])
cls.element('reward_model').change(
partial(cls.update_input_models, allow_keys=['reward_model_type'], is_reward_model=True, has_record=False),
inputs=[cls.element('reward_model')],
outputs=[cls.element('reward_model_type')])
@classmethod
def update_input_models(cls,
models,
allow_keys=None,
has_record=False,
arg_cls=BaseArguments,
is_reward_model=False):
if models is None:
return gr.update()
rm_type_str = ''
for model in models:
rm_type_str = ' '.join([
rm_type_str,
cls.update_input_model(
model,
allow_keys=allow_keys,
has_record=has_record,
arg_cls=arg_cls,
is_reward_model=is_reward_model)['value']
])
return gr.update(value=rm_type_str.strip())
+7
View File
@@ -0,0 +1,7 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from ..llm_train import Hyper
class GRPOHyper(Hyper):
group = 'llm_grpo'
+329
View File
@@ -0,0 +1,329 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import gradio as gr
from typing import Dict, Type
from swift.arguments import get_supported_tuners
from swift.utils import get_device_count, get_logger
from ..base import BaseUI
from ..llm_train import LLMTrain
from .advanced import GRPOAdvanced
from .dataset import GRPODataset
from .external_rollout import LLMRollout
from .grpo_advanced import GrpoAdvanced
from .hyper import GRPOHyper
from .model import GRPOModel
from .optimizer import GRPOOptimizer
from .quantization import GRPOQuantization
from .report_to import GRPOReportTo
from .reward import Reward
from .rollout import Rollout
from .runtime import GRPORuntime
from .save import GRPOSave
from .tuner import GRPOTuner
logger = get_logger()
class LLMGRPO(LLMTrain):
group = 'llm_grpo'
sub_ui = [
GRPOModel, GRPODataset, Reward, GRPORuntime, Rollout, GRPOSave, GRPOTuner, GRPOOptimizer, GRPOHyper,
GRPOQuantization, GRPOAdvanced, GrpoAdvanced, GRPOReportTo, LLMRollout
]
locale_dict: Dict[str, Dict] = {
'llm_grpo': {
'label': {
'zh': 'LLM GRPO',
'en': 'LLM GRPO',
}
},
'external_alert': {
'value': {
'zh': 'Err: {} \nRollout模型部署未完成,请检查日志,稍后开始训练!',
'en': 'Err: {} \nRollout model deployment is incomplete, '
'please check the logs and start training later!'
}
},
'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'
},
},
}
@classmethod
def do_build_ui(cls, base_tab: Type['BaseUI']):
with gr.TabItem(elem_id='llm_grpo', label=''):
default_device = 'cpu'
device_count = get_device_count()
if device_count > 0:
default_device = '0'
with gr.Blocks():
GRPOModel.build_ui(base_tab)
GRPODataset.build_ui(base_tab)
Reward.build_ui(base_tab)
with gr.Accordion(elem_id='train_param', open=True):
with gr.Row():
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)
gr.Textbox(elem_id='sequence_parallel_size', lines=1, 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=8)
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'])
GRPOHyper.build_ui(base_tab)
GRPORuntime.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')
Rollout.build_ui(base_tab)
LLMRollout.set_lang(cls.lang)
LLMRollout.build_ui(LLMRollout)
GRPOTuner.build_ui(base_tab)
with gr.Accordion(elem_id='extra_params', open=False):
with gr.Tabs():
GrpoAdvanced.build_ui(base_tab)
GRPOAdvanced.build_ui(base_tab)
GRPOQuantization.build_ui(base_tab)
GRPOSave.build_ui(base_tab)
GRPOReportTo.build_ui(base_tab)
with gr.Row():
gr.Textbox(elem_id='more_params', lines=4, scale=20)
cls.element('tuner_type').change(
GRPOHyper.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'),
],
queue=True)
Rollout.element('vllm_mode').change(LLMRollout.external_rollout_display, Rollout.element('vllm_mode'),
LLMRollout.element('llm_rollout'))
LLMRollout.element('rollout').click(
LLMRollout.rollout_model,
list(LLMRollout.valid_elements().values())
+ [cls.element('model'), cls.element('model_type'),
cls.element('template')],
[LLMRollout.element('rollout_runtime_tab'),
LLMRollout.element('rollout_running_tasks')])
GRPORuntime.element('kill_task').click(
GRPORuntime.kill_task,
[GRPORuntime.element('running_tasks')],
[GRPORuntime.element('running_tasks')] + [GRPORuntime.element('log')] + GRPORuntime.all_plots,
).then(GRPORuntime.reset, [], [GRPORuntime.element('logging_dir')] + [GRPOHyper.element('output_dir')])
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('ddp_num').change(Rollout.update_num_gen, [
GRPOHyper.element('per_device_train_batch_size'),
GRPOHyper.element('gradient_accumulation_steps'),
cls.element('ddp_num')
], [Rollout.element('num_generations')])
GRPOHyper.element('gradient_accumulation_steps').change(Rollout.update_num_gen, [
GRPOHyper.element('per_device_train_batch_size'),
GRPOHyper.element('gradient_accumulation_steps'),
cls.element('ddp_num')
], [Rollout.element('num_generations')])
GRPOHyper.element('per_device_train_batch_size').change(Rollout.update_num_gen, [
GRPOHyper.element('per_device_train_batch_size'),
GRPOHyper.element('gradient_accumulation_steps'),
cls.element('ddp_num')
], [Rollout.element('num_generations')])
@classmethod
def prepare_sub_to_filter(cls):
tabs_relation_dict = {
key: val
for key, val in zip(['tuner_type', 'optimizer', 'vllm_mode'],
[GRPOTuner.tabs_to_filter, GRPOOptimizer.tabs_to_filter, Rollout.tabs_to_filter])
}
return tabs_relation_dict
+7
View File
@@ -0,0 +1,7 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from ..llm_train import LoRA
class GRPOLoRA(LoRA):
group = 'llm_grpo'
+6
View File
@@ -0,0 +1,6 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from ..llm_train import Model
class GRPOModel(Model):
group = 'llm_grpo'
+7
View File
@@ -0,0 +1,7 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from ..llm_train import Optimizer
class GRPOOptimizer(Optimizer):
group = 'llm_grpo'
+7
View File
@@ -0,0 +1,7 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from ..llm_train import Quantization
class GRPOQuantization(Quantization):
group = 'llm_grpo'
+7
View File
@@ -0,0 +1,7 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from ..llm_train import ReportTo
class GRPOReportTo(ReportTo):
group = 'llm_grpo'
+50
View File
@@ -0,0 +1,50 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import gradio as gr
from typing import Type
from ..base import BaseUI
class Reward(BaseUI):
group = 'llm_grpo'
locale_dict = {
'reward_funcs': {
'label': {
'zh': '奖励函数',
'en': 'Reward functions'
},
'info': {
'zh': 'GRPO算法奖励函数',
'en': 'GRPO algorithm reward function'
}
},
'reward_weights': {
'label': {
'zh': '奖励函数权重',
'en': 'The weight of each reward function'
},
'info': {
'zh': '各奖励函数的权重之间用空格隔开',
'en': 'The weights of each reward function are separated by spaces'
}
},
'reward_param': {
'label': {
'zh': '奖励模型设置(更多参数->GRPO高级参数设置)',
'en': 'Reward settings(more params->GRPO advanced settings)'
},
}
}
@classmethod
def do_build_ui(cls, base_tab: Type['BaseUI']):
with gr.Accordion(elem_id='reward_param', open=True):
with gr.Row():
gr.Dropdown(
elem_id='reward_funcs',
multiselect=True,
choices=['accuracy', 'format', 'cosine', 'repetition', 'soft_overlong'],
scale=2,
allow_custom_value=True)
gr.Textbox(elem_id='reward_weights', lines=1, scale=2)
+233
View File
@@ -0,0 +1,233 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import gradio as gr
from typing import Type
from ..base import BaseUI
class Rollout(BaseUI):
group = 'llm_grpo'
locale_dict = {
'num_generations': {
'label': {
'zh': '采样数量',
'en': 'Number of samples'
},
'info': {
'zh': '每个prompt采样的数量,即论文中的G值',
'en': 'The number of samples for each prompt, that is, the G value in the paper'
}
},
'max_completion_length': {
'label': {
'zh': '最大生成长度',
'en': 'Max completion length'
},
'info': {
'zh': 'GRPO算法中的最大生成长度',
'en': 'Maximum generation length in GRPO algorithm'
}
},
'async_generate': {
'label': {
'zh': '异步生成',
'en': 'Async generate'
},
'info': {
'zh': '异步rollout以提高训练速度',
'en': 'Asynchronous rollout to increase training speed'
}
},
'temperature': {
'label': {
'zh': '采样温度',
'en': 'Temperature'
},
},
'top_k': {
'label': {
'zh': 'Top-k',
'en': 'Top-k'
},
},
'top_p': {
'label': {
'zh': 'Top-p',
'en': 'Top-p'
},
},
'repetition_penalty': {
'label': {
'zh': '重复惩罚',
'en': 'Repetition Penalty'
},
},
'use_vllm': {
'label': {
'zh': '使用vLLM',
'en': 'Using vLLM'
},
'info': {
'zh': '是否使用vLLM作为GRPO生成的推理后端',
'en': 'Whether to use vLLM as the infer_backend of generation by GRPO'
}
},
'vllm_mode': {
'label': {
'zh': 'vLLM集成模式',
'en': 'vLLM Integration Mode'
},
'info': {
'zh':
'Server模式使用`swift rollout`拉起的vLLM服务进行采样;Colocate模式使用程序内部署的vLLM',
'en':
'Server mode uses the vLLM server deployed by swift rollout for sampling,'
' colocate mode uses vLLM deployed in the program'
}
},
'vllm_gpu_memory_utilization': {
'label': {
'zh': 'GPU显存利用率',
'en': 'GPU memory utilization'
},
'info': {
'zh': 'vLLM透传参数',
'en': 'vLLM transparent transmission parameters'
}
},
'vllm_tensor_parallel_size': {
'label': {
'zh': '张量并行大小',
'en': 'Tensor parallel size'
},
'info': {
'zh': 'vLLM透传参数',
'en': 'vLLM transparent transmission parameters'
}
},
'vllm_max_model_len': {
'label': {
'zh': '模型支持的最大长度',
'en': 'Max model len'
},
'info': {
'zh': 'vLLM透传参数',
'en': 'vLLM transparent transmission parameters'
}
},
'sleep_level': {
'label': {
'zh': 'Sleep level',
'en': 'Sleep level'
},
'info': {
'zh': '训练时释放vLLM显存',
'en': 'Release vLLM memory during training'
}
},
'vllm_server_host': {
'label': {
'zh': 'vLLM服务主机',
'en': 'vLLM server host'
},
},
'vllm_server_port': {
'label': {
'zh': 'vLLM服务端口',
'en': 'vLLM server port'
},
},
'vllm_server_timeout': {
'label': {
'zh': '服务超时时间',
'en': 'Server timeout'
},
'info': {
'zh': '连接vLLM服务的超时时间',
'en': 'Timeout for connecting to vLLM server'
}
},
'offload_model': {
'label': {
'zh': '卸载模型',
'en': 'Offload model'
},
'info': {
'zh': '是否在vLLM推理时卸载模型',
'en': 'Whether to offload the model during vLLM inference'
}
},
'offload_optimizer': {
'label': {
'zh': '卸载优化器',
'en': 'Offload optimizer'
},
'info': {
'zh': '是否在vLLM推理时卸载优化器参数',
'en': 'Whether to offload optimizer parameters during vLLM inference'
}
},
'colocate_param': {
'label': {
'zh': 'Colocate模式参数',
'en': 'Colocate mode parameters'
}
},
'server_param': {
'label': {
'zh': 'Server模式参数',
'en': 'Server mode parameters'
}
},
'rollout_param': {
'label': {
'zh': 'Rollout设置(更多参数->GRPO高级参数设置)',
'en': 'Rollout settings(more params->GRPO advanced settings)'
}
}
}
tabs_to_filter = {
'colocate': [
'vllm_enable_prefix_caching', 'vllm_gpu_memory_utilization', 'vllm_tensor_parallel_size',
'vllm_max_model_len', 'sleep_level', 'offload_model', 'offload_optimizer'
],
'server': ['async_generate', 'vllm_server_host', 'vllm_server_port', 'vllm_server_timeout'],
'llm_rollout':
['tensor_parallel_size', 'data_parallel_size', 'max_model_len', 'gpu_memory_utilization', 'port']
}
@classmethod
def do_build_ui(cls, base_tab: Type['BaseUI']):
with gr.Accordion(elem_id='rollout_param', open=False):
with gr.Row():
gr.Slider(elem_id='temperature', minimum=0.0, maximum=10, step=0.1, value=1.0)
gr.Slider(elem_id='top_k', minimum=1, maximum=100, step=5, value=80)
gr.Slider(elem_id='top_p', minimum=0.0, maximum=1.0, step=0.05, value=1.0)
gr.Slider(elem_id='repetition_penalty', minimum=0.0, maximum=10, step=0.05, value=1.05)
with gr.Row():
gr.Checkbox(elem_id='use_vllm', value=True, scale=4)
gr.Dropdown(elem_id='vllm_mode', choices=['colocate', 'server'], scale=4)
gr.Slider(elem_id='num_generations', minimum=1, maximum=64, step=1, scale=4)
gr.Textbox(elem_id='max_completion_length', lines=1, value='512', scale=4)
with gr.Accordion(elem_id='colocate_param', open=True):
with gr.Row():
gr.Textbox(elem_id='vllm_gpu_memory_utilization', lines=1, value='0.5', scale=4)
gr.Textbox(elem_id='vllm_tensor_parallel_size', lines=1, value='1', scale=4)
gr.Textbox(elem_id='vllm_max_model_len', lines=1, value='', scale=4)
gr.Dropdown(elem_id='sleep_level', choices=['0', '1'], value='0', scale=4, allow_custom_value=True)
gr.Checkbox(elem_id='offload_model', value=True, scale=4)
gr.Checkbox(elem_id='offload_optimizer', value=True, scale=4)
with gr.Accordion(elem_id='server_param', open=True):
with gr.Row():
gr.Checkbox(elem_id='async_generate', scale=4)
gr.Textbox(elem_id='vllm_server_host', value='127.0.0.1', scale=4)
gr.Textbox(elem_id='vllm_server_port', lines=1, scale=4)
gr.Textbox(elem_id='vllm_server_timeout', lines=1, scale=4, value=120)
@staticmethod
def update_num_gen(per_device_batch_size, steps_per_generation, num_processes):
return int(per_device_batch_size) * int(steps_per_generation) * int(num_processes)
+166
View File
@@ -0,0 +1,166 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import gradio as gr
import os
from swift.utils import get_logger
from ..llm_train import Runtime
logger = get_logger()
class GRPORuntime(Runtime):
group = 'llm_grpo'
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 rlhf --rlhf_type grpo`命令)',
'en': 'All running tasks(started by `swift rlhf --rlhf_type grpo`)'
}
},
'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 save_cmd(cls, cmd):
if len(cmd) > 0:
cmd_sh, output_dir = cls.cmd_to_sh_format(cmd)
os.makedirs(output_dir, exist_ok=True)
sh_file_path = os.path.join(output_dir, 'grpo.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)
+7
View File
@@ -0,0 +1,7 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from ..llm_train import Save
class GRPOSave(Save):
group = 'llm_grpo'
+7
View File
@@ -0,0 +1,7 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from ..llm_train import Target
class GRPOTarget(Target):
group = 'llm_grpo'
+92
View File
@@ -0,0 +1,92 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import gradio as gr
from typing import Type
from ..base import BaseUI
from ..llm_train import Tuner
from .lora import GRPOLoRA
from .target import GRPOTarget
class GRPOTuner(Tuner):
group = 'llm_grpo'
sub_ui = [GRPOLoRA, GRPOTarget]
@classmethod
def do_build_ui(cls, base_tab: Type['BaseUI']):
with gr.Accordion(elem_id='tuner_params', open=False):
with gr.Tabs():
GRPOLoRA.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='2', 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.Checkbox(elem_id='vera_projection_prng_key', value=True, 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)
GRPOTarget.build_ui(base_tab)
+3
View File
@@ -0,0 +1,3 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from .llm_infer import LLMInfer
from .runtime import Runtime
+64
View File
@@ -0,0 +1,64 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import gradio as gr
from typing import Type
from ..base import BaseUI
class Generate(BaseUI):
group = 'llm_infer'
locale_dict = {
'max_new_tokens': {
'label': {
'zh': '生成序列最大长度',
'en': 'Max new tokens'
},
},
'temperature': {
'label': {
'zh': '采样温度',
'en': 'Temperature'
},
},
'top_k': {
'label': {
'zh': 'Top-k',
'en': 'Top-k'
},
},
'top_p': {
'label': {
'zh': 'Top-p',
'en': 'Top-p'
},
},
'repetition_penalty': {
'label': {
'zh': '重复惩罚',
'en': 'Repetition Penalty'
},
},
'system': {
'label': {
'zh': 'System字段',
'en': 'System'
},
'info': {
'zh': 'System字段支持在加载模型后修改',
'en': 'System can be modified after the model weights loaded'
}
},
}
@classmethod
def do_build_ui(cls, base_tab: Type['BaseUI']):
with gr.Row():
gr.Textbox(elem_id='max_new_tokens', lines=1, value='2048')
gr.Slider(elem_id='temperature', minimum=0.0, maximum=10, step=0.1, value=0.3)
gr.Slider(elem_id='top_k', minimum=1, maximum=100, step=5, value=20)
gr.Slider(elem_id='top_p', minimum=0.0, maximum=1.0, step=0.05, value=0.7)
gr.Slider(elem_id='repetition_penalty', minimum=0.0, maximum=10, step=0.05, value=1.05)
with gr.Row():
gr.Textbox(elem_id='system', lines=4, scale=20)
+427
View File
@@ -0,0 +1,427 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import gradio as gr
import json
import os
import re
import signal
import sys
from copy import deepcopy
from datetime import datetime
from functools import partial
from json import JSONDecodeError
from transformers.utils import is_torch_cuda_available, is_torch_npu_available
from typing import List, Type
from swift.arguments import DeployArguments, InferArguments
from swift.infer_engine import InferClient, InferRequest, RequestConfig
from swift.utils import get_device_count, get_logger
from ..base import BaseUI
from ..llm_train import run_command_in_background_with_popen
from .model import Model
from .runtime import Runtime
logger = get_logger()
class LLMInfer(BaseUI):
group = 'llm_infer'
is_multimodal = True
sub_ui = [Model, Runtime]
locale_dict = {
'generate_alert': {
'value': {
'zh': '请先部署模型',
'en': 'Please deploy model first',
}
},
'port': {
'label': {
'zh': '端口',
'en': 'Port'
},
},
'llm_infer': {
'label': {
'zh': 'LLM推理',
'en': 'LLM Inference',
}
},
'load_alert': {
'value': {
'zh': '部署中,请点击"展示部署状态"查看',
'en': 'Start to deploy model, '
'please Click "Show running '
'status" to view details',
}
},
'loaded_alert': {
'value': {
'zh': '模型加载完成',
'en': 'Model loaded'
}
},
'port_alert': {
'value': {
'zh': '该端口已被占用',
'en': 'The port has been occupied'
}
},
'chatbot': {
'value': {
'zh': '对话框',
'en': 'Chat bot'
},
},
'infer_model_type': {
'label': {
'zh': 'LoRA模块',
'en': 'LoRA module'
},
'info': {
'zh': '发送给server端哪个LoRA,默认为`default`',
'en': 'Which LoRA to use on server, default value is `default`'
}
},
'prompt': {
'label': {
'zh': '请输入:',
'en': 'Input:'
},
},
'clear_history': {
'value': {
'zh': '清除对话信息',
'en': 'Clear history'
},
},
'submit': {
'value': {
'zh': '🚀 发送',
'en': '🚀 Send'
},
},
'gpu_id': {
'label': {
'zh': '选择可用GPU',
'en': 'Choose GPU'
},
'info': {
'zh': '选择训练使用的GPU号,如CUDA不可用只能选择CPU',
'en': 'Select GPU to train'
}
},
}
choice_dict = BaseUI.get_choices_from_dataclass(InferArguments)
default_dict = BaseUI.get_default_value_from_dataclass(InferArguments)
arguments = BaseUI.get_argument_names(InferArguments)
@classmethod
def do_build_ui(cls, base_tab: Type['BaseUI']):
with gr.TabItem(elem_id='llm_infer', label=''):
default_device = 'cpu'
device_count = get_device_count()
if device_count > 0:
default_device = '0'
with gr.Blocks():
infer_request = gr.State(None)
Model.build_ui(base_tab)
Runtime.build_ui(base_tab)
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=8)
infer_model_type = gr.Textbox(elem_id='infer_model_type', scale=4)
gr.Textbox(elem_id='port', lines=1, value='8000', scale=4)
chatbot = gr.Chatbot(elem_id='chatbot', elem_classes='control-height')
with gr.Row(equal_height=True):
prompt = gr.Textbox(elem_id='prompt', lines=1, interactive=True)
with gr.Tabs(visible=cls.is_multimodal):
with gr.TabItem(label='Image'):
image = gr.Image(type='filepath')
with gr.TabItem(label='Video'):
video = gr.Video()
with gr.TabItem(label='Audio'):
audio = gr.Audio(type='filepath')
with gr.Row():
clear_history = gr.Button(elem_id='clear_history')
submit = gr.Button(elem_id='submit')
cls.element('load_checkpoint').click(
cls.deploy_model, list(base_tab.valid_elements().values()),
[cls.element('runtime_tab'), cls.element('running_tasks')])
submit.click(
cls.send_message,
inputs=[
cls.element('running_tasks'),
cls.element('template'), prompt, image, video, audio, infer_request, infer_model_type,
cls.element('system'),
cls.element('max_new_tokens'),
cls.element('temperature'),
cls.element('top_k'),
cls.element('top_p'),
cls.element('repetition_penalty')
],
outputs=[prompt, chatbot, image, video, audio, infer_request],
queue=True)
clear_history.click(
fn=cls.clear_session, inputs=[], outputs=[prompt, chatbot, image, video, audio, infer_request])
base_tab.element('running_tasks').change(
partial(Runtime.task_changed, base_tab=base_tab), [base_tab.element('running_tasks')],
list(cls.valid_elements().values()) + [cls.element('log')])
Runtime.element('kill_task').click(
Runtime.kill_task,
[Runtime.element('running_tasks')],
[Runtime.element('running_tasks')] + [Runtime.element('log')],
)
@classmethod
def deploy(cls, *args):
deploy_args = cls.get_default_value_from_dataclass(DeployArguments)
kwargs = {}
kwargs_is_list = {}
other_kwargs = {}
more_params = {}
more_params_cmd = ''
keys = cls.valid_element_keys()
for key, value in zip(keys, args):
compare_value = deploy_args.get(key)
compare_value_arg = str(compare_value) if not isinstance(compare_value, (list, dict)) else compare_value
compare_value_ui = str(value) if not isinstance(value, (list, dict)) else value
if key in deploy_args and compare_value_ui != compare_value_arg and value:
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
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
kwargs.update(more_params)
model = kwargs.get('model')
if os.path.exists(model) and os.path.exists(os.path.join(model, 'args.json')):
args_path = os.path.join(model, 'args.json')
if os.path.exists(os.path.join(model, 'adapter_config.json')):
kwargs['adapters'] = kwargs.pop('model')
with open(args_path, 'r', encoding='utf-8') as f:
_json = json.load(f)
kwargs['model_type'] = _json['model_type']
kwargs['tuner_type'] = _json['tuner_type']
deploy_args = DeployArguments(
**{
key: value.split(' ') if key in kwargs_is_list and kwargs_is_list[key] else value
for key, value in kwargs.items()
})
if deploy_args.port in Runtime.get_all_ports():
raise gr.Error(cls.locale('port_alert', cls.lang)['value'])
params = ''
command = ['swift', 'deploy']
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 'port' not in kwargs:
params += f'--port "{deploy_args.port}" '
command.extend(['--port', f'{deploy_args.port}'])
if more_params_cmd != '':
params += f'{more_params_cmd.strip()} '
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:])
all_envs = {}
devices = other_kwargs['gpu_id']
devices = [d for d in devices if d]
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 = ''
now = datetime.now()
time_str = f'{now.year}{now.month}{now.day}{now.hour}{now.minute}{now.second}'
file_path = f'output/{deploy_args.model_type}-{time_str}'
if not os.path.exists(file_path):
os.makedirs(file_path, exist_ok=True)
log_file = os.path.join(os.getcwd(), f'{file_path}/run_deploy.log')
deploy_args.log_file = log_file
params += f'--log_file "{log_file}" '
command.extend(['--log_file', f'{log_file}'])
params += '--ignore_args_error true '
command.extend(['--ignore_args_error', 'true'])
if sys.platform == 'win32':
if cuda_param:
cuda_param = f'set {cuda_param} && '
run_command = f'{cuda_param}start /b swift deploy {params} > {log_file} 2>&1'
else:
run_command = f'{cuda_param} nohup swift deploy {params} > {log_file} 2>&1 &'
return command, all_envs, run_command, deploy_args, log_file
@classmethod
def deploy_model(cls, *args):
command, all_envs, run_command, deploy_args, log_file = cls.deploy(*args)
logger.info(f'Running deployment command: {run_command}')
run_command_in_background_with_popen(command, all_envs, log_file)
gr.Info(cls.locale('load_alert', cls.lang)['value'])
running_task = Runtime.refresh_tasks(log_file)
return gr.update(open=True), running_task
@classmethod
def register_clean_hook(cls):
signal.signal(signal.SIGINT, LLMInfer.signal_handler)
if os.name != 'nt':
signal.signal(signal.SIGTERM, LLMInfer.signal_handler)
@staticmethod
def signal_handler(*args, **kwargs):
LLMInfer.clean_deployment()
sys.exit(0)
@classmethod
def clear_session(cls):
return '', [], gr.update(value=None), gr.update(value=None), gr.update(value=None), []
@classmethod
def _replace_tag_with_media(cls, infer_request: InferRequest):
total_history = []
messages = deepcopy(infer_request.messages)
if messages[0]['role'] == 'system':
messages.pop(0)
for i in range(0, len(messages), 2):
slices = messages[i:i + 2]
if len(slices) == 2:
user, assistant = slices
else:
user = slices[0]
assistant = {'role': 'assistant', 'content': None}
user['content'] = (user['content'] or '').replace('<image>', '').replace('<video>',
'').replace('<audio>', '').strip()
for media in user['medias']:
total_history.append([(media, ), None])
if user['content'] or assistant['content']:
total_history.append((user['content'], assistant['content']))
return total_history
@classmethod
def agent_type(cls, response):
if not response:
return None
if response.lower().endswith('observation:'):
return 'react'
if 'observation:' not in response.lower() and 'action input:' in response.lower():
return 'toolbench'
return None
@classmethod
def parse_text(cls, messages):
prepared_msgs = []
for message in messages:
if isinstance(message, tuple):
query = message[0].replace('<', '&lt;').replace('>', '&gt;').replace('*', '&ast;')
response = message[1].replace('<', '&lt;').replace('>', '&gt;').replace('*', '&ast;')
prepared_msgs.append((query, response))
else:
prepared_msgs.append(message)
return prepared_msgs
@classmethod
def send_message(cls, running_task, template_type, prompt: str, image, video, audio, infer_request: InferRequest,
infer_model_type, system, max_new_tokens, temperature, top_k, top_p, repetition_penalty):
if not infer_request:
infer_request = InferRequest(messages=[])
if system:
if not infer_request.messages or infer_request.messages[0]['role'] != 'system':
infer_request.messages.insert(0, {'role': 'system', 'content': system})
else:
infer_request.messages[0]['content'] = system
if not infer_request.messages or infer_request.messages[-1]['role'] != 'user':
infer_request.messages.append({'role': 'user', 'content': '', 'medias': []})
media = image or video or audio
media_type = 'images' if image else 'videos' if video else 'audios'
if media:
_saved_medias: List = getattr(infer_request, media_type)
if not _saved_medias or _saved_medias[-1] != media:
_saved_medias.append(media)
infer_request.messages[-1]['content'] = infer_request.messages[-1]['content'] + f'<{media_type[:-1]}>'
infer_request.messages[-1]['medias'].append(media)
if not prompt:
chatbot_content = cls._replace_tag_with_media(infer_request)
chatbot_content = cls.parse_text(chatbot_content)
yield '', chatbot_content, gr.update(value=None), gr.update(value=None), gr.update(
value=None), infer_request
return
else:
infer_request.messages[-1]['content'] = infer_request.messages[-1]['content'] + prompt
_, args = Runtime.parse_info_from_cmdline(running_task)
request_config = RequestConfig(
temperature=temperature, top_k=top_k, top_p=top_p, repetition_penalty=repetition_penalty)
request_config.stream = True
request_config.stop = ['Observation:']
request_config.max_tokens = max_new_tokens
stream_resp_with_history = ''
response = ''
i = len(infer_request.messages) - 1
for i in range(len(infer_request.messages) - 1, -1, -1):
if infer_request.messages[i]['role'] == 'assistant':
response = infer_request.messages[i]['content']
agent_type = cls.agent_type(response)
if i != len(infer_request.messages) - 1 and agent_type == 'toolbench':
infer_request.messages[i + 1]['role'] = 'tool'
chat = not template_type.endswith('generation')
_infer_request = deepcopy(infer_request)
for m in _infer_request.messages:
if 'medias' in m:
m.pop('medias')
model_kwargs = {}
if infer_model_type:
model_kwargs = {'model': infer_model_type}
gen_list = InferClient(
port=args['port'], ).infer(
infer_requests=[_infer_request], request_config=request_config, **model_kwargs)
if infer_request.messages[-1]['role'] != 'assistant':
infer_request.messages.append({'role': 'assistant', 'content': ''})
for chunk in gen_list[0]:
if chunk is None:
continue
stream_resp_with_history += chunk.choices[0].delta.content if chat else chunk.choices[0].text
infer_request.messages[-1]['content'] = stream_resp_with_history
chatbot_content = cls._replace_tag_with_media(infer_request)
chatbot_content = cls.parse_text(chatbot_content)
yield '', chatbot_content, gr.update(value=None), gr.update(value=None), gr.update(
value=None), infer_request
+129
View File
@@ -0,0 +1,129 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import gradio as gr
from functools import partial
from typing import Type
from swift.arguments import DeployArguments
from swift.model import ModelType, get_model_list
from swift.template import TEMPLATE_MAPPING
from ..base import BaseUI
from .generate import Generate
class Model(BaseUI):
group = 'llm_infer'
sub_ui = [Generate]
locale_dict = {
'model_type': {
'label': {
'zh': '选择模型类型',
'en': 'Select Model Type'
},
'info': {
'zh': 'SWIFT已支持的模型类型',
'en': 'Base model type supported by SWIFT'
}
},
'load_checkpoint': {
'value': {
'zh': '部署模型',
'en': 'Deploy model',
}
},
'model': {
'label': {
'zh': '模型id或路径',
'en': 'Model id or path'
},
'info': {
'zh': '实际的模型id,如果是训练后的模型请填入checkpoint-xxx的目录',
'en': 'The actual model id or path, if is a trained model, please fill in the checkpoint-xxx dir'
}
},
'template': {
'label': {
'zh': '模型Prompt模板类型',
'en': 'Prompt template type'
},
'info': {
'zh': '选择匹配模型的Prompt模板',
'en': 'Choose the template type of the model'
}
},
'merge_lora': {
'label': {
'zh': '合并LoRA',
'en': 'Merge LoRA'
},
'info': {
'zh': '仅在`tuner_type=lora`时可用',
'en': 'Only available when `tuner_type=lora`'
}
},
'adapters': {
'label': {
'zh': 'adapter id或路径',
'en': 'adapter id/path'
},
'info': {
'zh':
'只有一个lora模块时填adapter路径或`name=/path`;多个lora模块时填键值对:`name1=/path1 name2=/path2`',
'en': ('Single LoRA: Use path or name=/path. '
'Multiple LoRAs: Use key-value pairs, e.g., name1=/path1 name2=/path2.')
}
},
'more_params': {
'label': {
'zh': '更多参数',
'en': 'More params'
},
'info': {
'zh': '以json格式或--xxx xxx命令行格式填入',
'en': 'Fill in with json format or --xxx xxx cmd format'
}
},
'reset': {
'value': {
'zh': '恢复初始值',
'en': 'Reset to default'
},
},
'infer_backend': {
'label': {
'zh': '推理框架',
'en': 'Infer backend'
},
},
}
@classmethod
def do_build_ui(cls, base_tab: Type['BaseUI']):
with gr.Row(equal_height=True):
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)
gr.Checkbox(elem_id='merge_lora', scale=4)
gr.Button(elem_id='reset', scale=2)
with gr.Row():
gr.Dropdown(elem_id='infer_backend', value='transformers', scale=5)
Generate.set_lang(cls.lang)
Generate.build_ui(base_tab)
with gr.Row(equal_height=True):
gr.Textbox(elem_id='adapters', lines=1, is_list=True, scale=40)
gr.Textbox(elem_id='more_params', lines=1, scale=20)
gr.Button(elem_id='load_checkpoint', scale=2, variant='primary')
@classmethod
def after_build_ui(cls, base_tab: Type['BaseUI']):
cls.element('model').change(
partial(cls.update_input_model, arg_cls=DeployArguments, has_record=False),
inputs=[cls.element('model')],
outputs=list(cls.valid_elements().values()))
+293
View File
@@ -0,0 +1,293 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import collections
import gradio as gr
import os.path
import psutil
import re
import subprocess
import sys
import time
from datetime import datetime
from packaging import version
from typing import Dict, List, Tuple, Type
from swift.utils import format_time, get_logger
from ..base import BaseUI
logger = get_logger()
class Runtime(BaseUI):
handlers: Dict[str, Tuple[List, Tuple]] = {}
group = 'llm_infer'
cmd = 'deploy'
log_event = {}
locale_dict = {
'runtime_tab': {
'label': {
'zh': '运行时',
'en': 'Runtime'
},
},
'running_cmd': {
'label': {
'zh': '运行命令',
'en': 'Command line'
},
'info': {
'zh': '执行的实际命令',
'en': 'The actual command'
}
},
'show_log': {
'value': {
'zh': '展示部署状态',
'en': 'Show running status'
},
},
'stop_show_log': {
'value': {
'zh': '停止展示',
'en': 'Stop showing running status'
},
},
'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 deployments'
},
'info': {
'zh': '所有的swift deploy命令启动的任务',
'en': 'Started by swift deploy'
}
},
'refresh_tasks': {
'value': {
'zh': '找回部署',
'en': 'Find deployments'
},
},
'kill_task': {
'value': {
'zh': '杀死部署',
'en': 'Kill running task'
},
},
}
@classmethod
def do_build_ui(cls, base_tab: Type['BaseUI']):
with gr.Accordion(elem_id='runtime_tab', open=False, visible=True):
with gr.Blocks():
with gr.Row(equal_height=True):
gr.Dropdown(elem_id='running_tasks', scale=10, allow_custom_value=True)
gr.Button(elem_id='refresh_tasks', scale=1, variant='primary')
gr.Button(elem_id='show_log', scale=1, variant='primary')
gr.Button(elem_id='stop_show_log', scale=1)
gr.Button(elem_id='kill_task', scale=1)
with gr.Row():
gr.Textbox(elem_id='log', lines=6, visible=False)
concurrency_limit = {}
if version.parse(gr.__version__) >= version.parse('4.0.0'):
concurrency_limit = {'concurrency_limit': 5}
base_tab.element('show_log').click(cls.update_log, [],
[cls.element('log')]).then(cls.wait,
[base_tab.element('running_tasks')],
[cls.element('log')], **concurrency_limit)
base_tab.element('stop_show_log').click(cls.break_log_event, [cls.element('running_tasks')], [])
base_tab.element('refresh_tasks').click(
cls.refresh_tasks,
[base_tab.element('running_tasks')],
[base_tab.element('running_tasks')],
)
@classmethod
def break_log_event(cls, task):
if not task:
return
pid, all_args = cls.parse_info_from_cmdline(task)
cls.log_event[all_args['log_file']] = True
@classmethod
def update_log(cls):
return gr.update(visible=True)
@classmethod
def wait(cls, task):
if not task:
return [None]
_, args = cls.parse_info_from_cmdline(task)
log_file = args['log_file']
cls.log_event[log_file] = 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(log_file, False):
cls.log_event[log_file] = 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)
yield '\n'.join(lines)
except IOError:
pass
@classmethod
def get_all_ports(cls):
process_name = 'swift'
cmd_name = cls.cmd
ports = set()
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]) and any( # noqa
[cmd_name == cmdline for cmdline in cmdlines]): # noqa
try:
ports.add(int(cls.parse_info_from_cmdline(cls.construct_running_task(proc))[1].get('port', 8000)))
except IndexError:
pass
return ports
@classmethod
def refresh_tasks(cls, running_task=None):
log_file = running_task if not running_task or 'pid:' not in running_task else None
process_name = 'swift'
negative_name = 'swift.exe'
cmd_name = cls.cmd
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]) and not any([negative_name in cmdline
for cmdline in cmdlines]) and any( # noqa
[cmd_name == cmdline for cmdline in cmdlines]): # noqa
process.append(cls.construct_running_task(proc))
if log_file is not None and any( # noqa
[log_file == cmdline for cmdline in cmdlines]): # noqa
selected = cls.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())}'
@classmethod
def parse_info_from_cmdline(cls, task):
pid = None
for i in range(3):
slash = task.find('/')
if i == 0:
pid = task[:slash].split(':')[1]
task = task[slash + 1:]
args = task.split(f'swift {cls.cmd}')[1]
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]] = splits[1]
return pid, all_args
@classmethod
def kill_task(cls, task):
if task:
pid, all_args = cls.parse_info_from_cmdline(task)
log_file = all_args['log_file']
if sys.platform == 'win32':
command = ['taskkill', '/f', '/t', '/pid', pid]
else:
command = ['pkill', '-9', '-f', log_file]
try:
result = subprocess.run(command, capture_output=True, text=True)
assert result.returncode == 0
except Exception as e:
raise e
cls.break_log_event(task)
return [cls.refresh_tasks()] + [gr.update(value=None)]
@classmethod
def task_changed(cls, task, base_tab):
if task:
_, all_args = cls.parse_info_from_cmdline(task)
else:
all_args = {}
elements = list(base_tab.valid_elements().values())
ret = []
is_adapter = ('adapters' in all_args) and ('model' not in all_args)
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(' ')
elif isinstance(e, gr.Slider) and re.fullmatch(cls.int_regex, all_args[e.elem_id]):
arg = int(all_args[e.elem_id])
elif isinstance(e, gr.Slider) and re.fullmatch(cls.float_regex, all_args[e.elem_id]):
arg = float(all_args[e.elem_id])
else:
if e.elem_id == 'model':
if is_adapter:
arg = all_args['adapters']
else:
arg = all_args[e.elem_id]
else:
arg = all_args[e.elem_id]
ret.append(gr.update(value=arg))
else:
ret.append(gr.update())
cls.break_log_event(task)
return ret + [gr.update(value=None)]
+2
View File
@@ -0,0 +1,2 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from .llm_rlhf import LLMRLHF
+7
View File
@@ -0,0 +1,7 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from ..llm_train import Advanced
class RLHFAdvanced(Advanced):
group = 'llm_rlhf'
+7
View File
@@ -0,0 +1,7 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from ..llm_train import Dataset
class RLHFDataset(Dataset):
group = 'llm_rlhf'
+7
View File
@@ -0,0 +1,7 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from ..llm_train import Hyper
class RLHFHyper(Hyper):
group = 'llm_rlhf'
+331
View File
@@ -0,0 +1,331 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import gradio as gr
from functools import partial
from typing import Dict, Type
from swift.arguments import get_supported_tuners
from swift.utils import get_device_count, get_logger
from ..base import BaseUI
from ..llm_train import LLMTrain
from .advanced import RLHFAdvanced
from .dataset import RLHFDataset
from .hyper import RLHFHyper
from .model import RLHFModel
from .optimizer import RLHFOptimizer
from .quantization import RLHFQuantization
from .report_to import RLHFReportTo
from .rlhf import RLHF
from .runtime import RLHFRuntime
from .save import RLHFSave
from .tuner import RLHFTuner
logger = get_logger()
class LLMRLHF(LLMTrain):
group = 'llm_rlhf'
sub_ui = [
RLHFModel,
RLHFDataset,
RLHFHyper,
RLHFRuntime,
RLHFTuner,
RLHFOptimizer,
RLHF,
RLHFQuantization,
RLHFSave,
RLHFReportTo,
RLHFAdvanced,
]
locale_dict: Dict[str, Dict] = {
'llm_rlhf': {
'label': {
'zh': 'LLM人类对齐',
'en': 'LLM RLHF',
}
},
'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'
}
},
'rlhf_type': {
'label': {
'zh': '人类对齐算法类型',
'en': 'RLHF type'
},
},
'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'
},
},
}
@classmethod
def do_build_ui(cls, base_tab: Type['BaseUI']):
with gr.TabItem(elem_id='llm_rlhf', label=''):
default_device = 'cpu'
device_count = get_device_count()
if device_count > 0:
default_device = '0'
with gr.Blocks():
RLHFModel.build_ui(base_tab)
RLHFDataset.build_ui(base_tab)
with gr.Accordion(elem_id='train_param', open=True):
with gr.Row():
gr.Dropdown(elem_id='rlhf_type', scale=2)
gr.Dropdown(elem_id='tuner_type', scale=2, choices=list(get_supported_tuners()))
gr.Textbox(elem_id='seed', scale=2)
gr.Dropdown(elem_id='torch_dtype', scale=2)
gr.Checkbox(elem_id='use_liger_kernel', scale=2)
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)
RLHFHyper.build_ui(base_tab)
RLHFRuntime.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')
RLHFTuner.build_ui(base_tab)
RLHFOptimizer.build_ui(base_tab)
RLHF.build_ui(base_tab)
with gr.Accordion(elem_id='extra_params', open=False):
with gr.Tabs():
RLHFAdvanced.build_ui(base_tab)
RLHFQuantization.build_ui(base_tab)
RLHFSave.build_ui(base_tab)
RLHFReportTo.build_ui(base_tab)
with gr.Row():
gr.Textbox(elem_id='more_params', lines=4, scale=20)
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'))
cls.element('tuner_type').change(
RLHFHyper.update_lr,
inputs=[base_tab.element('tuner_type')],
outputs=[cls.element('learning_rate')])
cls.element('rlhf_type').change(
RLHF.update_beta, inputs=[base_tab.element('rlhf_type')], outputs=[base_tab.element('beta')])
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'),
],
queue=True)
base_tab.element('running_tasks').change(
partial(RLHFRuntime.task_changed, base_tab=base_tab), [base_tab.element('running_tasks')],
list(base_tab.valid_elements().values()) + [cls.element('log')] + RLHFRuntime.all_plots)
RLHFRuntime.element('kill_task').click(
RLHFRuntime.kill_task,
[RLHFRuntime.element('running_tasks')],
[RLHFRuntime.element('running_tasks')] + [RLHFRuntime.element('log')] + RLHFRuntime.all_plots,
).then(RLHFRuntime.reset, [], [RLHFRuntime.element('logging_dir')] + [RLHFHyper.element('output_dir')])
@classmethod
def prepare_sub_to_filter(cls):
tabs_relation_dict = {
key: val
for key, val in zip(['tuner_type', 'optimizer'], [RLHFTuner.tabs_to_filter, RLHFOptimizer.tabs_to_filter])
}
return tabs_relation_dict
@classmethod
def filter_rlhf_args(cls, uncleaned_kwargs):
cur_rlhf_type = uncleaned_kwargs.get('rlhf_type', 'dpo')
cur_selected = RLHF.rlhf_args_dict.pop(cur_rlhf_type, None)
for _, vals in RLHF.rlhf_args_dict.items():
for rlhf_arg in vals:
if uncleaned_kwargs.get(rlhf_arg) and (cur_selected is None or rlhf_arg not in cur_selected):
uncleaned_kwargs.pop(rlhf_arg)
+7
View File
@@ -0,0 +1,7 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from ..llm_train import LoRA
class RLHFLoRA(LoRA):
group = 'llm_rlhf'
+7
View File
@@ -0,0 +1,7 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from ..llm_train import Model
class RLHFModel(Model):
group = 'llm_rlhf'
+7
View File
@@ -0,0 +1,7 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from ..llm_train import Optimizer
class RLHFOptimizer(Optimizer):
group = 'llm_rlhf'
+7
View File
@@ -0,0 +1,7 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from ..llm_train import Quantization
class RLHFQuantization(Quantization):
group = 'llm_rlhf'
+7
View File
@@ -0,0 +1,7 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from ..llm_train import ReportTo
class RLHFReportTo(ReportTo):
group = 'llm_rlhf'
+209
View File
@@ -0,0 +1,209 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import gradio as gr
from functools import partial
from typing import Type
from swift.model import ModelType, get_model_list
from ..base import BaseUI
class RLHF(BaseUI):
group = 'llm_rlhf'
locale_dict = {
'rlhf_tab': {
'label': {
'zh': '对齐参数设置',
'en': 'Alignment params settings'
},
},
'ref_model': {
'label': {
'zh': 'Ref模型id或路径',
'en': 'Ref model id or path'
},
'info': {
'zh': '实际的模型id或路径',
'en': 'The actual model id or path'
}
},
'ref_model_type': {
'label': {
'zh': 'Ref模型类型',
'en': 'Ref model type'
},
'info': {
'zh': 'SWIFT已支持的模型类型',
'en': 'Model type supported by SWIFT'
}
},
'reward_model': {
'label': {
'zh': '奖励模型id或路径',
'en': 'Reward model id or path'
},
'info': {
'zh': '实际的模型id或路径',
'en': 'The actual model id or path'
}
},
'reward_model_type': {
'label': {
'zh': '奖励模型类型',
'en': 'Reward model type'
},
'info': {
'zh': 'SWIFT已支持的模型类型',
'en': 'Model type supported by SWIFT'
}
},
'teacher_model': {
'label': {
'zh': '教师模型id或路径',
'en': 'Teacher model id or path'
},
'info': {
'zh': '实际的模型id或路径',
'en': 'The actual model id or path'
}
},
'teacher_model_type': {
'label': {
'zh': '教师模型类型',
'en': 'Teacher model type'
},
'info': {
'zh': 'SWIFT已支持的模型类型',
'en': 'Model type supported by SWIFT'
}
},
'beta': {
'label': {
'zh': 'KL正则项系数',
'en': 'KL regression ratio'
},
},
'max_completion_length': {
'label': {
'zh': '最大生成长度',
'en': 'Max completion length'
},
},
'loss_scale': {
'label': {
'zh': '损失权重设置',
'en': 'Loss weights setting'
},
},
'lmbda': {
'label': {
'zh': 'GKD学生数据比例',
'en': 'GKD student data ratio'
},
},
'cpo_alpha': {
'label': {
'zh': 'CPO/SimPO中NLL损失系数',
'en': 'CPO/SimPO NLL loss coefficient'
},
},
'rpo_alpha': {
'label': {
'zh': 'DPO中混合sft交叉熵的系数',
'en': 'DPO Cross Entropy ratio'
},
},
'simpo_gamma': {
'label': {
'zh': 'SimPO reward margin',
'en': 'SimPO reward margin'
},
},
'desirable_weight': {
'label': {
'zh': 'KTO符合项系数',
'en': 'KTO desirable ratio'
},
},
'undesirable_weight': {
'label': {
'zh': 'KTO不符合项系数',
'en': 'KTO undesirable ratio'
},
}
}
rlhf_args_dict = {
'dpo': ['rpo_alpha', 'ref_model', 'ref_model_type'],
'cpo': ['cpo_alpha'],
'kto': ['desirable_weight', 'undesirable_weight', 'ref_model', 'ref_model_type'],
'simpo': ['simpo_gamma', 'cpo_alpha'],
'gkd': ['teacher_model', 'teacher_model_type', 'max_completion_length', 'lmbda'],
'ppo': ['reward_model', 'reward_model_type', 'max_completion_length', 'ref_model', 'ref_model_type']
}
@classmethod
def do_build_ui(cls, base_tab: Type['BaseUI']):
with gr.Accordion(elem_id='rlhf_tab', open=False):
with gr.Blocks():
with gr.Row():
gr.Slider(elem_id='beta', minimum=0., maximum=5.0, step=0.1, value=0.1, scale=10)
gr.Slider(elem_id='rpo_alpha', minimum=0., maximum=2, step=0.1, scale=10)
gr.Slider(elem_id='lmbda', minimum=0., maximum=1.0, step=0.1, scale=10)
gr.Slider(elem_id='simpo_gamma', minimum=0., maximum=2.0, step=0.1, scale=10)
gr.Slider(elem_id='desirable_weight', minimum=0., maximum=2.0, step=0.1, scale=10)
gr.Slider(elem_id='undesirable_weight', minimum=0., maximum=2.0, step=0.1, scale=10)
with gr.Row():
gr.Textbox(elem_id='max_completion_length', scale=10)
gr.Textbox(elem_id='loss_scale', scale=10)
gr.Slider(elem_id='cpo_alpha', minimum=0., maximum=1, step=0.1, scale=10)
gr.Dropdown(
elem_id='teacher_model',
scale=20,
value=None,
choices=get_model_list(),
allow_custom_value=True)
gr.Dropdown(
elem_id='teacher_model_type',
choices=ModelType.get_model_name_list(),
value=None,
scale=10,
allow_custom_value=True)
with gr.Row():
gr.Dropdown(
elem_id='ref_model', scale=20, value=None, choices=get_model_list(), allow_custom_value=True)
gr.Dropdown(
elem_id='ref_model_type',
choices=ModelType.get_model_name_list(),
value=None,
scale=10,
allow_custom_value=True)
gr.Dropdown(
elem_id='reward_model', scale=20, value=None, choices=get_model_list(), allow_custom_value=True)
gr.Dropdown(
elem_id='reward_model_type',
choices=ModelType.get_model_name_list(),
value=None,
scale=10,
allow_custom_value=True)
@classmethod
def after_build_ui(cls, base_tab: Type['BaseUI']):
cls.element('ref_model').change(
partial(cls.update_input_model, allow_keys=['ref_model_type'], has_record=False, is_ref_model=True),
inputs=[cls.element('ref_model')],
outputs=[cls.element('ref_model_type')])
cls.element('reward_model').change(
partial(cls.update_input_model, allow_keys=['reward_model_type'], has_record=False, is_ref_model=True),
inputs=[cls.element('reward_model')],
outputs=[cls.element('reward_model_type')])
cls.element('teacher_model').change(
partial(cls.update_input_model, allow_keys=['teacher_model_type'], has_record=False, is_ref_model=True),
inputs=[cls.element('teacher_model')],
outputs=[cls.element('teacher_model_type')])
@staticmethod
def update_beta(rlhf_type):
beta_value_dict = {'simpo': 2., 'gkd': 0.5, 'grpo': 0.04}
return beta_value_dict.get(rlhf_type, 0.1) if rlhf_type else 0.1
+166
View File
@@ -0,0 +1,166 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import gradio as gr
import os
from swift.utils import get_logger
from ..llm_train import Runtime
logger = get_logger()
class RLHFRuntime(Runtime):
group = 'llm_rlhf'
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_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': '运行中的任务(除`--rlhf_type grpo`之外的所有`swift rlhf`命令)',
'en': 'All running tasks(started by `swift rlhf` except `--rlhf_type grpo`)'
}
},
'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'
}
},
'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 save_cmd(cls, cmd):
if len(cmd) > 0:
cmd_sh, output_dir = cls.cmd_to_sh_format(cmd)
os.makedirs(output_dir, exist_ok=True)
sh_file_path = os.path.join(output_dir, 'rlhf.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)
+7
View File
@@ -0,0 +1,7 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from ..llm_train import Save
class RLHFSave(Save):
group = 'llm_rlhf'
+7
View File
@@ -0,0 +1,7 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from ..llm_train import Target
class RLHFTarget(Target):
group = 'llm_rlhf'
+92
View File
@@ -0,0 +1,92 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import gradio as gr
from typing import Type
from ..base import BaseUI
from ..llm_train import Tuner
from .lora import RLHFLoRA
from .target import RLHFTarget
class RLHFTuner(Tuner):
group = 'llm_rlhf'
sub_ui = [RLHFLoRA, RLHFTarget]
@classmethod
def do_build_ui(cls, base_tab: Type['BaseUI']):
with gr.Accordion(elem_id='tuner_params', open=False):
with gr.Tabs():
RLHFLoRA.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)
RLHFTarget.build_ui(base_tab)
+2
View File
@@ -0,0 +1,2 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from .llm_sample import LLMSample
+273
View File
@@ -0,0 +1,273 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import gradio as gr
import json
import os
import re
import sys
import time
from datetime import datetime
from functools import partial
from json import JSONDecodeError
from transformers.utils import is_torch_cuda_available, is_torch_npu_available
from typing import Type
from swift.arguments import SamplingArguments
from swift.dataset import get_dataset_list
from swift.utils import get_device_count, get_logger
from ..base import BaseUI
from ..llm_train import run_command_in_background_with_popen
from .model import Model
from .runtime import SampleRuntime
from .sample import Sample
logger = get_logger()
class LLMSample(BaseUI):
group = 'llm_sample'
is_multimodal = True
sub_ui = [Model, Sample, SampleRuntime]
locale_dict = {
'llm_sample': {
'label': {
'zh': 'LLM采样',
'en': 'LLM Sampling',
}
},
'sample': {
'value': {
'zh': '开始采样',
'en': 'Start sampling',
}
},
'load_alert': {
'value': {
'zh': '采样中,请点击"展示采样状态"查看',
'en': 'Start to sample, '
'please Click "Show running '
'status" to view details',
}
},
'gpu_id': {
'label': {
'zh': '选择可用GPU',
'en': 'Choose GPU'
},
'info': {
'zh': '选择采样使用的GPU号,如CUDA不可用只能选择CPU',
'en': 'Select GPU to sample'
}
},
'dataset': {
'label': {
'zh': '数据集名称',
'en': 'Dataset id/path'
},
'info': {
'zh': '选择采样的数据集,支持复选/本地路径',
'en': 'The dataset(s) to train the models, support multi select and local folder/files'
}
},
'num_sampling_batch_size': {
'label': {
'zh': '每次采样的批次大小',
'en': 'The batch size of sampling'
}
},
'num_sampling_batches': {
'label': {
'zh': '采样批次数量',
'en': 'Num of Sampling batches'
}
},
'output_dir': {
'label': {
'zh': '存储目录',
'en': 'The output dir',
},
'info': {
'zh': '设置采样结果存储在哪个文件夹下',
'en': 'Set the output folder',
}
},
'envs': {
'label': {
'zh': '环境变量',
'en': 'Extra env vars'
},
},
'more_params': {
'label': {
'zh': '更多参数',
'en': 'More params'
},
'info': {
'zh': '以json格式或--xxx xxx命令行格式填入',
'en': 'Fill in with json format or --xxx xxx cmd format'
}
},
}
choice_dict = BaseUI.get_choices_from_dataclass(SamplingArguments)
default_dict = BaseUI.get_default_value_from_dataclass(SamplingArguments)
arguments = BaseUI.get_argument_names(SamplingArguments)
@classmethod
def do_build_ui(cls, base_tab: Type['BaseUI']):
with gr.TabItem(elem_id='llm_sample', label=''):
default_device = 'cpu'
device_count = get_device_count()
if device_count > 0:
default_device = '0'
with gr.Blocks():
Model.build_ui(base_tab)
Sample.build_ui(base_tab)
with gr.Row():
gr.Dropdown(
elem_id='dataset',
multiselect=True,
choices=get_dataset_list(),
scale=20,
allow_custom_value=True)
gr.Slider(elem_id='num_sampling_batch_size', minimum=1, maximum=128, step=1, value=1, scale=10)
gr.Slider(elem_id='num_sampling_batches', minimum=1, maximum=128, step=1, value=1, scale=10)
SampleRuntime.build_ui(base_tab)
with gr.Row(equal_height=True):
gr.Dropdown(
elem_id='gpu_id',
multiselect=True,
choices=[str(i) for i in range(device_count)] + ['cpu'],
value=default_device,
scale=20)
gr.Textbox(elem_id='output_dir', value='sample_output', scale=20)
gr.Textbox(elem_id='envs', scale=20)
gr.Button(elem_id='sample', scale=2, variant='primary')
with gr.Row():
gr.Textbox(elem_id='more_params', lines=4)
cls.element('sample').click(
cls.sample_model, list(base_tab.valid_elements().values()),
[cls.element('runtime_tab'), cls.element('running_tasks')])
base_tab.element('running_tasks').change(
partial(SampleRuntime.task_changed, base_tab=base_tab), [base_tab.element('running_tasks')],
list(cls.valid_elements().values()) + [cls.element('log')])
SampleRuntime.element('kill_task').click(
SampleRuntime.kill_task,
[SampleRuntime.element('running_tasks')],
[SampleRuntime.element('running_tasks')] + [SampleRuntime.element('log')],
)
@classmethod
def sample(cls, *args):
sample_args = cls.get_default_value_from_dataclass(SamplingArguments)
kwargs = {}
kwargs_is_list = {}
other_kwargs = {}
more_params = {}
more_params_cmd = ''
keys = cls.valid_element_keys()
for key, value in zip(keys, args):
compare_value = sample_args.get(key)
compare_value_arg = str(compare_value) if not isinstance(compare_value, (list, dict)) else compare_value
compare_value_ui = str(value) if not isinstance(value, (list, dict)) else value
if key in sample_args and compare_value_ui != compare_value_arg and value:
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
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
kwargs.update(more_params)
model = kwargs.get('model')
if os.path.exists(model) and os.path.exists(os.path.join(model, 'args.json')):
args_path = os.path.join(model, 'args.json')
if os.path.exists(os.path.join(model, 'adapter_config.json')):
kwargs['adapters'] = kwargs.pop('model')
with open(args_path, 'r', encoding='utf-8') as f:
_json = json.load(f)
kwargs['model_type'] = _json['model_type']
kwargs['tuner_type'] = _json['tuner_type']
sample_args = SamplingArguments(
**{
key: value.split(' ') if key in kwargs_is_list and kwargs_is_list[key] else value
for key, value in kwargs.items()
})
params = ''
command = ['swift', 'sample']
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 more_params_cmd != '':
params += 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:])
all_envs = {}
devices = other_kwargs['gpu_id']
devices = [d for d in devices if d]
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 = ''
now = datetime.now()
time_str = f'{now.year}{now.month}{now.day}{now.hour}{now.minute}{now.second}'
file_path = f'output/{sample_args.model_type}-{time_str}'
if not os.path.exists(file_path):
os.makedirs(file_path, exist_ok=True)
log_file = os.path.join(os.getcwd(), f'{file_path}/run_sample.log')
sample_args.log_file = log_file
params += f'--log_file "{log_file}" '
command.extend(['--log_file', f'{log_file}'])
params += '--ignore_args_error true '
command.extend(['--ignore_args_error', 'true'])
if sys.platform == 'win32':
if cuda_param:
cuda_param = f'set {cuda_param} && '
run_command = f'{cuda_param}start /b swift sample {params} > {log_file} 2>&1'
else:
run_command = f'{cuda_param} nohup swift sample {params} > {log_file} 2>&1 &'
return command, all_envs, run_command, sample_args, log_file
@classmethod
def sample_model(cls, *args):
command, all_envs, run_command, sample_args, log_file = cls.sample(*args)
logger.info(f'Running sample command: {run_command}')
run_command_in_background_with_popen(command, all_envs, log_file)
gr.Info(cls.locale('load_alert', cls.lang)['value'])
time.sleep(2)
running_task = SampleRuntime.refresh_tasks(log_file)
return gr.update(open=True), running_task
+103
View File
@@ -0,0 +1,103 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import gradio as gr
from functools import partial
from typing import Type
from swift.arguments import SamplingArguments
from swift.model import ModelType, get_model_list
from swift.template import TEMPLATE_MAPPING
from ..base import BaseUI
class Model(BaseUI):
group = 'llm_sample'
locale_dict = {
'model_type': {
'label': {
'zh': '选择模型类型',
'en': 'Select Model Type'
},
'info': {
'zh': 'SWIFT已支持的模型类型,model是服务名称时请置空',
'en': 'Base model type supported by SWIFT, Please leave it blank if model is the service name'
}
},
'model': {
'label': {
'zh': '模型id、路径或模型服务名称',
'en': 'Model id, path or server name'
},
'info': {
'zh':
'实际的模型id,如果是训练后的模型请填入checkpoint-xxx的目录,如果是模型服务请填入模型服务名称',
'en': ('The actual model id or path, if is a trained model, please fill in the checkpoint-xxx dir'
'if is a model service, please fill in the server name')
}
},
'template': {
'label': {
'zh': '模型Prompt模板类型',
'en': 'Prompt template type'
},
'info': {
'zh': '选择匹配模型的Prompt模板,model是服务名称时请置空',
'en': 'Choose the template type of the model, Please leave it blank if model is the service name'
}
},
'system': {
'label': {
'zh': 'System字段',
'en': 'System'
},
'info': {
'zh': 'System字段支持在加载模型后修改',
'en': 'System can be modified after the model weights loaded'
}
},
'prm_model': {
'label': {
'zh': '过程奖励模型',
'en': 'Process Reward Model'
},
'info': {
'zh': '可以是模型id,或者plugin中定义的prm key',
'en': 'It can be a model id, or a prm key defined in the plugin'
}
},
'orm_model': {
'label': {
'zh': '结果奖励模型',
'en': 'Outcome Reward Model'
},
'info': {
'zh': '通常是通配符或测试用例等,定义在plugin中',
'en': 'Usually a wildcard or test case, etc., defined in the plugin'
}
},
}
@classmethod
def do_build_ui(cls, base_tab: Type['BaseUI']):
with gr.Row(equal_height=True):
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)
with gr.Row():
gr.Textbox(elem_id='system', lines=1)
with gr.Row():
gr.Textbox(elem_id='prm_model', scale=20)
gr.Textbox(elem_id='orm_model', scale=20)
@classmethod
def after_build_ui(cls, base_tab: Type['BaseUI']):
cls.element('model').change(
partial(cls.update_input_model, arg_cls=SamplingArguments, has_record=False),
inputs=[cls.element('model')],
outputs=list(cls.valid_elements().values()))
+75
View File
@@ -0,0 +1,75 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
from swift.utils import get_logger
from ..llm_infer import Runtime
logger = get_logger()
class SampleRuntime(Runtime):
group = 'llm_sample'
cmd = 'sample'
locale_dict = {
'runtime_tab': {
'label': {
'zh': '运行时',
'en': 'Runtime'
},
},
'running_cmd': {
'label': {
'zh': '运行命令',
'en': 'Command line'
},
'info': {
'zh': '执行的实际命令',
'en': 'The actual command'
}
},
'show_log': {
'value': {
'zh': '展示采样状态',
'en': 'Show running status'
},
},
'stop_show_log': {
'value': {
'zh': '停止展示',
'en': 'Stop showing running status'
},
},
'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 sampling'
},
'info': {
'zh': '所有的swift sample命令启动的任务',
'en': 'Started by swift sample'
}
},
'refresh_tasks': {
'value': {
'zh': '找回采样',
'en': 'Find sampling'
},
},
'kill_task': {
'value': {
'zh': '杀死采样',
'en': 'Kill running task'
},
},
}
+85
View File
@@ -0,0 +1,85 @@
# Copyright (c) ModelScope Contributors. All rights reserved.
import gradio as gr
from typing import Type
from ..base import BaseUI
class Sample(BaseUI):
group = 'llm_sample'
locale_dict = {
'sampler_type': {
'label': {
'zh': '采样类型',
'en': 'Sampler type'
},
},
'sampler_engine': {
'label': {
'zh': '推理引擎',
'en': 'Infer engine'
},
},
'num_return_sequences': {
'label': {
'zh': '采样返回的原始序列数量',
'en': 'Num of original sequences returned by sampling'
},
},
'n_best_to_keep': {
'label': {
'zh': '最佳序列数量',
'en': 'Num of best sequences'
},
},
'max_new_tokens': {
'label': {
'zh': '生成序列最大长度',
'en': 'Max new tokens'
},
},
'temperature': {
'label': {
'zh': '采样温度',
'en': 'Temperature'
},
},
'top_k': {
'label': {
'zh': 'Top-k',
'en': 'Top-k'
},
},
'top_p': {
'label': {
'zh': 'Top-p',
'en': 'Top-p'
},
},
'repetition_penalty': {
'label': {
'zh': '重复惩罚',
'en': 'Repetition Penalty'
},
},
}
@classmethod
def do_build_ui(cls, base_tab: Type['BaseUI']):
with gr.Row():
gr.Dropdown(elem_id='sampler_type', choices=['sample', 'distill'], value='sample', scale=5)
gr.Dropdown(
elem_id='sampler_engine',
choices=['transformers', 'lmdeploy', 'vllm', 'no', 'client'],
value='transformers',
scale=5)
gr.Slider(elem_id='num_return_sequences', minimum=1, maximum=128, step=1, value=64, scale=5)
gr.Slider(elem_id='n_best_to_keep', minimum=1, maximum=64, step=1, value=5, scale=5)
with gr.Row():
gr.Textbox(elem_id='max_new_tokens', lines=1, value='2048')
gr.Slider(elem_id='temperature', minimum=0.0, maximum=10, step=0.1, value=1.0)
gr.Slider(elem_id='top_k', minimum=1, maximum=100, step=5, value=20)
gr.Slider(elem_id='top_p', minimum=0.0, maximum=1.0, step=0.05, value=0.7)
gr.Slider(elem_id='repetition_penalty', minimum=0.0, maximum=10, step=0.05, value=1.05)
+15
View File
@@ -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
+114
View File
@@ -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)
+70
View File
@@ -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)
+149
View File
@@ -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
+579
View File
@@ -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)
+68
View File
@@ -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)
+127
View File
@@ -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()))
+139
View File
@@ -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)
+67
View File
@@ -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)
+74
View File
@@ -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)
+717
View File
@@ -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)
+83
View File
@@ -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)
+46
View File
@@ -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)
+80
View File
@@ -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)
+76
View File
@@ -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)
+366
View File
@@ -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)
+58
View File
@@ -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)