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