Adjust the default values of lora_dropout and num_train_epochs, while applying a unified decorator in the CLI to support log capturing. Use an inelegant approach to print all terminal content to a file, enabled via full_log.

This commit is contained in:
xming521
2025-05-21 21:57:12 +08:00
parent 42585eb663
commit ed9d8e320e
6 changed files with 123 additions and 12 deletions
+1 -1
View File
@@ -12,7 +12,7 @@ alwaysApply: true
# Your rule content
- You can @ files here
- The project uses uv as the package manager and pyproject.toml as the project configuration file.
- Unless I ask you to, code comments don't need to be excessive.You should prioritize using Chinese to comment code.
- Unless I ask you to, code comments don't need to be excessive.
- Prefer using the encapsulated logger `from weclone.utils.log import logger` for printing.
- When retrieving values from a parameter dictionary read from a configuration file, the `get` method should be preferred whenever possible.
+5 -2
View File
@@ -8,6 +8,9 @@
"finetuning_type": "lora",
"trust_remote_code": true
},
"cli_args": {
"full_log": false
},
"make_dataset_args": {
//数据处理配置
"include_type": [
@@ -60,7 +63,7 @@
"use_fast_tokenizer": true,
"lora_target": "q_proj,v_proj",
"lora_rank": 4,
"lora_dropout": 0.4,
"lora_dropout": 0.3,
"weight_decay": 0.1,
"overwrite_cache": true,
"per_device_train_batch_size": 8,
@@ -71,7 +74,7 @@
"save_steps": 100,
"learning_rate": 1e-4,
"warmup_ratio": 0.1,
"num_train_epochs": 3,
"num_train_epochs": 2,
"plot_loss": true,
"fp16": true,
"flash_attn": "fa2",
+33 -7
View File
@@ -4,7 +4,11 @@ from pathlib import Path
import os
import sys
import functools
from weclone.utils.log import logger
from weclone.utils.log import logger, capture_output
from weclone.utils.config import load_config
cli_config: dict | None = None
try:
import tomllib # type: ignore Python 3.11+
@@ -30,15 +34,37 @@ def clear_argv(func):
return wrapper
def apply_common_decorators(capture_output_enabled=False):
"""
A unified decorator for applications
"""
def decorator(original_cmd_func):
@functools.wraps(original_cmd_func)
def new_runtime_wrapper(*args, **kwargs):
if cli_config and cli_config.get("full_log", False):
return capture_output(original_cmd_func)(*args, **kwargs)
else:
return original_cmd_func(*args, **kwargs)
func_with_clear_argv = clear_argv(new_runtime_wrapper)
return functools.wraps(original_cmd_func)(func_with_clear_argv)
return decorator
@click.group()
def cli():
"""WeClone: 从聊天记录创造数字分身的一站式解决方案"""
_check_project_root()
_check_versions()
global cli_config
cli_config = load_config(arg_type="cli_args")
@cli.command("make-dataset", help="处理聊天记录CSV文件,生成问答对数据集。")
@clear_argv
@apply_common_decorators()
def qa_generator():
"""处理聊天记录CSV文件,生成问答对数据集。"""
from weclone.data.qa_generator import DataProcessor
@@ -48,7 +74,7 @@ def qa_generator():
@cli.command("train-sft", help="使用准备好的数据集对模型进行微调。")
@clear_argv
@apply_common_decorators()
def train_sft():
"""使用准备好的数据集对模型进行微调。"""
from weclone.train.train_sft import main as train_sft_main
@@ -57,7 +83,7 @@ def train_sft():
@cli.command("webchat-demo", help="启动 Web UI 与微调后的模型进行交互测试。") # 命令名修改为 web-demo
@clear_argv
@apply_common_decorators()
def web_demo():
"""启动 Web UI 与微调后的模型进行交互测试。"""
from weclone.eval.web_demo import main as web_demo_main
@@ -66,7 +92,7 @@ def web_demo():
# TODO 添加评估功能 @cli.command("eval-model", help="使用从训练数据中划分出来的验证集评估。")
@clear_argv
@apply_common_decorators()
def eval_model():
"""使用从训练数据中划分出来的验证集评估。"""
from weclone.eval.eval_model import main as evaluate_main
@@ -75,7 +101,7 @@ def eval_model():
@cli.command("test-model", help="使用常见聊天问题测试模型。")
@clear_argv
@apply_common_decorators()
def test_model():
"""测试"""
from weclone.eval.test_model import main as test_main
@@ -84,7 +110,7 @@ def test_model():
@cli.command("server", help="启动API服务,提供模型推理接口。")
@clear_argv
@apply_common_decorators()
def server():
"""启动API服务,提供模型推理接口。"""
from weclone.server.api_service import main as server_main
+1 -1
View File
@@ -47,7 +47,7 @@ class DataProcessor:
pass
self.blocked_words = list(set(config_blocked_words + file_blocked_words))
logger.info(f"聊天记录禁用词: {self.blocked_words}")
# logger.info(f"聊天记录禁用词: {self.blocked_words}")
if self.config["single_combine_strategy"] == "time_window":
self.single_combine_strategy = TimeWindowStrategy(
+3 -1
View File
@@ -19,7 +19,9 @@ def load_config(arg_type: str):
logger.error(f"Error loading configuration file {config_path}: {e}")
sys.exit(1)
if arg_type == "web_demo" or arg_type == "api_service":
if arg_type == "cli_args":
config = s_config["cli_args"]
elif arg_type == "web_demo" or arg_type == "api_service":
# infer_args和common_args求并集
config = {**s_config["infer_args"], **s_config["common_args"]}
elif arg_type == "train_pt":
+80
View File
@@ -1,5 +1,6 @@
from loguru import logger
import sys
from functools import wraps
logger.remove()
@@ -9,3 +10,82 @@ logger.add(
colorize=True,
level="INFO",
)
logger.add(
"logs/weclone.log", # 日志文件路径
rotation="1 day", # 每天轮换一个新的日志文件
retention="7 days", # 保留最近7天的日志文件
compression="zip", # 压缩旧的日志文件
level="DEBUG", # 文件日志级别
format="{time:YYYY-MM-DD HH:mm:ss.SSS} | {level: <8} | {name}:{function}:{line} - {message}", # 日志格式
encoding="utf-8", # 文件编码
enqueue=True, # 异步写入,避免阻塞
)
def capture_output(func):
@wraps(func)
def wrapper(*args, **kwargs):
log_sink_buffer = []
def list_sink(message):
log_sink_buffer.append(message.record["message"])
sink_id = logger.add(list_sink, format="{message}", level="INFO")
original_stdout = sys.stdout
original_stderr = sys.stderr
class OutputTeeToGlobalLog:
def __init__(self, original_stream, log_method):
self.original_stream = original_stream
self.log_method = log_method
self.current_line_content = "" # Represents the current state of the line to be logged
def write(self, data_chunk):
self.original_stream.write(data_chunk) # Pass through to console
if data_chunk.endswith("\\r") and "\\n" not in data_chunk:
self.current_line_content = data_chunk[:-1] # Store without the trailing \\r
return
full_buffer = self.current_line_content + data_chunk
lines_to_process = full_buffer.split("\\n")
for i in range(len(lines_to_process) - 1):
line = lines_to_process[i]
final_content_of_line = line
last_cr = line.rfind("\\r")
if last_cr != -1:
final_content_of_line = line[last_cr + 1 :]
escaped_log = final_content_of_line.replace("{", "{{").replace("}", "}}")
if final_content_of_line.strip() or line:
self.log_method(escaped_log, raw=True)
self.current_line_content = lines_to_process[-1]
def flush(self):
self.original_stream.flush()
if self.current_line_content:
final_content_of_line = self.current_line_content
last_cr = self.current_line_content.rfind("\\r")
if last_cr != -1:
final_content_of_line = self.current_line_content[last_cr + 1 :]
escaped_log = final_content_of_line.replace("{", "{{").replace("}", "}}")
if final_content_of_line.strip() or self.current_line_content:
self.log_method(escaped_log, raw=True)
self.current_line_content = ""
sys.stdout = OutputTeeToGlobalLog(original_stdout, logger.opt(raw=True).info)
sys.stderr = OutputTeeToGlobalLog(original_stderr, logger.opt(raw=True).error)
try:
func(*args, **kwargs)
finally:
sys.stdout = original_stdout
sys.stderr = original_stderr
logger.remove(sink_id)
return wrapper