[llama factory源码解析.回调函数]src >llamafactory >train >callbacks.py
回顾:trainer.py中的CustomTrainer就调用了这里面的回调方法

源代码
class SaveProcessorCallback(TrainerCallback):
def __init__(self, processor: "ProcessorMixin") -> None:
r"""
Initializes a callback for saving the processor.
"""
self.processor = processor
def on_train_end(self, args: "TrainingArguments", state: "TrainerState", control: "TrainerControl", **kwargs):
r"""
Event called at the end of training.
"""
if args.should_save:
getattr(self.processor, "image_processor").save_pretrained(args.output_dir)
class PissaConvertCallback(TrainerCallback):
r"""
Initializes a callback for converting the PiSSA adapter to a normal one.
"""
def on_train_begin(self, args: "TrainingArguments", state: "TrainerState", control: "TrainerControl", **kwargs):
r"""
Event called at the beginning of training.
"""
if args.should_save:
model = kwargs.pop("model")
pissa_init_dir = os.path.join(args.output_dir, "pissa_init")
logger.info("Initial PiSSA adatper will be saved at: {}.".format(pissa_init_dir))
if isinstance(model, PeftModel):
init_lora_weights = getattr(model.peft_config["default"], "init_lora_weights")
setattr(model.peft_config["default"], "init_lora_weights", True)
model.save_pretrained(pissa_init_dir, safe_serialization=args.save_safetensors)
setattr(model.peft_config["default"], "init_lora_weights", init_lora_weights)
让我们逐行解释 SaveProcessorCallback 和 PissaConvertCallback 类的代码。这些类继承自 TrainerCallback 并实现了特定的回调功能。
SaveProcessorCallback 类
类定义和初始化方法
python
复制
class SaveProcessorCallback(TrainerCallback):
def __init__(self, processor: "ProcessorMixin") -> None:
r"""
Initializes a callback for saving the processor.
"""
self.processor = processor
- 类定义:
SaveProcessorCallback继承自TrainerCallback,用于在训练结束时保存处理器。 - 初始化方法:
__init__方法接受一个processor参数,并将其赋值给实例变量self.processor。processor的类型注解为"ProcessorMixin"(字符串形式的类型注解,用于解决前向引用)。
on_train_end 方法
python
复制
def on_train_end(self, args: "TrainingArguments", state: "TrainerState", control: "TrainerControl", **kwargs):
r"""
Event called at the end of training.
"""
if args.should_save:
getattr(self.processor, "image_processor").save_pretrained(args.output_dir)
- 方法定义:
on_train_end方法在训练结束时被调用。 - 参数:
args("TrainingArguments"):训练参数。state("TrainerState"):训练状态。control("TrainerControl"):训练控制。**kwargs:其他关键字参数。
- 方法实现:
- 检查
args.should_save是否为真,如果为真,则调用self.processor的image_processor属性的save_pretrained方法,将处理器保存到args.output_dir指定的目录中。
- 检查
PissaConvertCallback 类
类定义和文档字符串
python
复制
class PissaConvertCallback(TrainerCallback):
r"""
Initializes a callback for converting the PiSSA adapter to a normal one.
"""
- 类定义:
PissaConvertCallback继承自TrainerCallback,用于在训练开始时转换 PiSSA 适配器。 - 文档字符串:描述了该类的功能,即将 PiSSA 适配器转换为普通适配器的回调。
on_train_begin 方法
python
复制
def on_train_begin(self, args: "TrainingArguments", state: "TrainerState", control: "TrainerControl", **kwargs):
r"""
Event called at the beginning of training.
"""
if args.should_save:
model = kwargs.pop("model")
pissa_init_dir = os.path.join(args.output_dir, "pissa_init")
logger.info("Initial PiSSA adatper will be saved at: {}.".format(pissa_init_dir))
if isinstance(model, PeftModel):
init_lora_weights = getattr(model.peft_config["default"], "init_lora_weights")
setattr(model.peft_config["default"], "init_lora_weights", True)
model.save_pretrained(pissa_init_dir, safe_serialization=args.save_safetensors)
setattr(model.peft_config["default"], "init_lora_weights", init_lora_weights)
- 方法定义:
on_train_begin方法在训练开始时被调用。 - 参数:
args("TrainingArguments"):训练参数。state("TrainerState"):训练状态。control("TrainerControl"):训练控制。**kwargs:其他关键字参数。
- 方法实现:
- 检查
args.should_save是否为真,如果为真,执行以下操作:- 从
kwargs中弹出model参数。 - 构造
pissa_init_dir路径,用于保存初始 PiSSA 适配器。 - 记录一条信息,指示初始 PiSSA 适配器将被保存的位置。
- 检查
model是否为PeftModel实例:- 获取当前
init_lora_weights的值。 - 将
init_lora_weights设置为True。 - 调用
model的save_pretrained方法,将模型保存到pissa_init_dir,并根据args.save_safetensors决定是否进行安全序列化。 - 恢复原始的
init_lora_weights值。
- 获取当前
- 从
- 检查
总结
SaveProcessorCallback类在训练结束时保存处理器。PissaConvertCallback类在训练开始时将 PiSSA 适配器转换为普通适配器并保存初始适配器。
这两个回调类通过在训练过程中的不同时间点执行自定义逻辑,增强了训练流程的灵活性和功能。
def on_train_end(self, args: "TrainingArguments", state: "TrainerState", control: "TrainerControl", **kwargs):
r"""
Event called at the end of training.
"""
if args.should_save:
model = kwargs.pop("model")
pissa_init_dir = os.path.join(args.output_dir, "pissa_init")
pissa_backup_dir = os.path.join(args.output_dir, "pissa_backup")
pissa_convert_dir = os.path.join(args.output_dir, "pissa_converted")
logger.info("Converted PiSSA adapter will be saved at: {}.".format(pissa_convert_dir))
# 1. save a pissa backup with init_lora_weights: True
# 2. save a converted lora with init_lora_weights: pissa
# 3. load the pissa backup with init_lora_weights: True
# 4. delete the initial adapter and change init_lora_weights to pissa
if isinstance(model, PeftModel):
init_lora_weights = getattr(model.peft_config["default"], "init_lora_weights")
setattr(model.peft_config["default"], "init_lora_weights", True)
model.save_pretrained(pissa_backup_dir, safe_serialization=args.save_safetensors)
setattr(model.peft_config["default"], "init_lora_weights", init_lora_weights)
model.save_pretrained(
pissa_convert_dir, safe_serialization=args.save_safetensors, convert_pissa_to_lora=pissa_init_dir
)
model.load_adapter(pissa_backup_dir, "default", is_trainable=True)
model.set_adapter("default")
model.delete_adapter("pissa_init")
setattr(model.peft_config["default"], "init_lora_weights", init_lora_weights)
继续逐行解释上述代码的后续部分,这是 PissaConvertCallback 类中的 on_train_end 方法的实现。
on_train_end 方法
方法定义和文档字符串
python
复制
def on_train_end(self, args: "TrainingArguments", state: "TrainerState", control: "TrainerControl", **kwargs):
r"""
Event called at the end of training.
"""
- 方法定义:
on_train_end方法在训练结束时被调用。 - 参数:
args("TrainingArguments"):训练参数。state("TrainerState"):训练状态。control("TrainerControl"):训练控制。**kwargs:其他关键字参数。
获取模型和目录路径
python
复制
if args.should_save:
model = kwargs.pop("model")
pissa_init_dir = os.path.join(args.output_dir, "pissa_init")
pissa_backup_dir = os.path.join(args.output_dir, "pissa_backup")
pissa_convert_dir = os.path.join(args.output_dir, "pissa_converted")
logger.info("Converted PiSSA adapter will be saved at: {}.".format(pissa_convert_dir))
- 检查:检查
args.should_save是否为真。 - 获取模型:从
kwargs中弹出model参数。 - 定义路径:
pissa_init_dir:初始 PiSSA 适配器保存路径。pissa_backup_dir:PiSSA 备份保存路径。pissa_convert_dir:转换后的 PiSSA 适配器保存路径。
- 日志记录:记录转换后的 PiSSA 适配器将被保存的位置。
处理 PiSSA 适配器
python
复制
# 1. save a pissa backup with init_lora_weights: True
# 2. save a converted lora with init_lora_weights: pissa
# 3. load the pissa backup with init_lora_weights: True
# 4. delete the initial adapter and change init_lora_weights to pissa
if isinstance(model, PeftModel):
init_lora_weights = getattr(model.peft_config["default"], "init_lora_weights")
setattr(model.peft_config["default"], "init_lora_weights", True)
model.save_pretrained(pissa_backup_dir, safe_serialization=args.save_safetensors)
setattr(model.peft_config["default"], "init_lora_weights", init_lora_weights)
model.save_pretrained(
pissa_convert_dir, safe_serialization=args.save_safetensors, convert_pissa_to_lora=pissa_init_dir
)
model.load_adapter(pissa_backup_dir, "default", is_trainable=True)
model.set_adapter("default")
model.delete_adapter("pissa_init")
setattr(model.peft_config["default"], "init_lora_weights", init_lora_weights)
-
注释:列出了处理 PiSSA 适配器的四个步骤:
- 使用
init_lora_weights: True保存一个 PiSSA 备份。 - 使用
init_lora_weights: pissa保存一个转换后的 lora。 - 加载使用
init_lora_weights: True保存的 PiSSA 备份。 - 删除初始适配器并将
init_lora_weights更改为pissa。
- 使用
-
检查模型类型:检查
model是否为PeftModel实例。 -
获取
init_lora_weights:获取当前init_lora_weights的值。 -
设置
init_lora_weights为True:将init_lora_weights设置为True。 -
保存 PiSSA 备份:调用
model的save_pretrained方法,将模型保存到pissa_backup_dir,并根据args.save_safetensors决定是否进行安全序列化。 -
恢复
init_lora_weights:将init_lora_weights恢复为原始值。 -
保存转换后的 lora:
- 调用
model的save_pretrained方法,将模型保存到pissa_convert_dir,同时传递convert_pissa_to_lora=pissa_init_dir参数用于转换。
- 调用
-
加载 PiSSA 备份:
- 调用
model的load_adapter方法,从pissa_backup_dir加载适配器,并设置为可训练。
- 调用
-
设置适配器:
- 调用
model的set_adapter方法,设置当前适配器为default。
- 调用
-
删除初始适配器:
- 调用
model的delete_adapter方法,删除pissa_init适配器。
- 调用
-
恢复
init_lora_weights:将init_lora_weights恢复为原始值。
总结
on_train_end方法在训练结束时被调用,执行一系列操作来处理 PiSSA 适配器,包括备份、转换、加载和删除。- 检查是否应该保存模型,如果是,则执行适配器的备份、转换和加载操作。
- 通过设置和恢复
init_lora_weights,确保适配器的正确处理和保存。
这些操作确保了在训练结束时,PiSSA 适配器能够被正确地转换和保存,从而可以在后续的训练或推理过程中使用。
前向引用是什么意思,和其他的引用有什么区别
processor 的类型注解为 "ProcessorMixin"(字符串形式的类型注解,用于解决前向引用)。
前向引用(forward reference)是指在代码中引用一个尚未定义的类或类型。在 Python 中,有时候需要在类定义之前引用该类。使用字符串形式的类型注解是一种解决这种情况的方法。
前向引用的解释
在 Python 的类型注解中,如果你需要引用一个在代码中尚未定义的类或类型,可以使用字符串形式的类型注解。这通常发生在以下两种情况中:
- 类的自引用:一个类的方法需要引用该类自身。例如,一个链表节点类的方法可能返回另一个链表节点。
- 相互引用的类:两个类相互引用对方。例如,A 类的方法返回 B 类的实例,而 B 类的方法返回 A 类的实例。
示例
使用字符串形式的前向引用
python
复制
class A:
def __init__(self, b: "B"):
self.b = b
class B:
def __init__(self, a: "A"):
self.a = a
在上面的例子中,类 A 和 B 相互引用对方。在定义 A 时,B 还没有定义,因此我们使用字符串形式 "B" 作为类型注解。同样,在定义 B 时,A 也已经定义好。
使用 from __future__ import annotations
从 Python 3.7 开始,可以使用 from __future__ import annotations 让所有的类型注解以字符串形式解析,从而避免前向引用的问题:
python
复制
from __future__ import annotations
class A:
def __init__(self, b: B):
self.b = b
class B:
def __init__(self, a: A):
self.a = a
与其他引用的区别
-
普通引用:在类型注解中直接引用已经定义的类或类型。这种方式要求被引用的类或类型已经定义。
python
复制class B: pass class A: def __init__(self, b: B): # 普通引用 self.b = b -
前向引用:在类型注解中引用尚未定义的类或类型,需要使用字符串形式。
python
复制class A: def __init__(self, b: "B"): # 前向引用 self.b = b class B: pass
结论
前向引用允许在类定义之前引用该类,这在类的自引用或相互引用时非常有用。使用字符串形式的类型注解是实现前向引用的常见方法。通过 from __future__ import annotations,可以在 Python 3.7 及以上版本中自动将所有类型注解解释为字符串形式,从而简化代码。
回调函数是干嘛的,和普通函数的区别在哪

GPT-4o
Poe
回调函数的定义
回调函数(callback function)是指通过参数传递给另一函数的函数。当这个函数完成某项任务或达到某个特定条件时,它会调用(或“回调”)之前传递给它的函数。回调函数通常用于异步编程、事件驱动编程或需要在特定条件下执行的操作。
回调函数的用途
回调函数通常用于以下场景:
- 异步操作:比如网络请求、文件读取等需要时间的操作,通过回调函数可以在操作完成后执行特定的代码。
- 事件驱动编程:在图形用户界面(GUI)编程中,按钮点击、鼠标移动等事件触发时调用回调函数。
- 函数的扩展:允许在函数执行过程中插入额外的操作,而无需修改函数的内部实现。
回调函数与普通函数的区别
- 调用方式:普通函数在代码中直接调用,而回调函数通常是作为参数传递给另一个函数,由后者在特定事件或条件下调用。
- 执行时机:普通函数在调用时立即执行,而回调函数的执行时机由传递它的函数决定。
- 灵活性:回调函数提供了更高的灵活性,可以让调用者决定在特定时间点执行哪些操作。
回调函数示例
基本示例
python
复制
def my_callback():
print("Callback function called!")
def process(callback):
print("Processing...")
# 模拟某种处理
callback() # 调用回调函数
process(my_callback)
在这个例子中,my_callback 是一个回调函数,它被传递给 process 函数并在特定时机(这里是处理完成后)被调用。
异步操作示例
python
复制
import time
def my_callback(result):
print(f"Callback called with result: {result}")
def async_process(data, callback):
print("Starting async process...")
time.sleep(2) # 模拟耗时操作
result = data * 2
callback(result) # 操作完成后调用回调函数
async_process(5, my_callback)
在这个例子中,async_process 模拟了一个异步操作,操作完成后调用回调函数 my_callback 并传递结果。
高级示例:事件驱动编程
使用回调函数在事件驱动编程中的应用,通常见于 GUI 编程。例如,在一个按钮点击事件中调用回调函数:
python
复制
import tkinter as tk
def on_button_click():
print("Button clicked!")
root = tk.Tk()
button = tk.Button(root, text="Click Me", command=on_button_click)
button.pack()
root.mainloop()
在这个例子中,on_button_click 是一个回调函数,当按钮被点击时,它将被调用。
总结
回调函数提供了一种在特定时机执行代码的机制,使得代码更加灵活和模块化。与普通函数不同,回调函数的调用被延迟到某个特定事件或条件发生时。通过使用回调函数,可以实现异步操作、事件驱动编程和函数的扩展,从而提高代码的可复用性和可维护性。
command=on_button_click就是调用该函数对吗

GPT-4o
Poe
不完全对。command=on_button_click 并不是在立即调用 on_button_click 函数,而是将 on_button_click 函数作为一个回调函数传递给按钮的 command 参数。按钮点击事件发生时,Tkinter 框架会调用这个回调函数。
详细解释
当你使用 command=on_button_click 时,你实际上是在将函数 on_button_click 的引用(而不是调用结果)传递给按钮的 command 属性。这样,当按钮被点击时,Tkinter 框架会执行 on_button_click 函数。
示例
python
复制
import tkinter as tk
def on_button_click():
print("Button clicked!")
root = tk.Tk()
button = tk.Button(root, text="Click Me", command=on_button_click) # 传递函数引用
button.pack()
root.mainloop()
在这个例子中:
on_button_click是一个回调函数,用于处理按钮点击事件。command=on_button_click将on_button_click函数的引用传递给按钮的command参数。- 当用户点击按钮时,
Tkinter框架会调用on_button_click函数,执行其中的代码。
回调函数与立即调用的区别
直接调用
如果你在 command 参数中使用 on_button_click()(带括号),就相当于立即调用该函数并将其返回值(如果有)赋给 command 参数。这通常不是你想要的效果,因为你希望在按钮被点击时才执行该函数,而不是在创建按钮时就执行。
python
复制
import tkinter as tk
def on_button_click():
print("Button clicked!")
root = tk.Tk()
button = tk.Button(root, text="Click Me", command=on_button_click()) # 错误:立即调用了函数
button.pack()
root.mainloop()
在这个错误的例子中,on_button_click() 会在创建按钮时立即被调用,而不是在按钮被点击时调用。
使用 lambda 传递带参数的回调函数
如果你的回调函数需要参数,可以使用 lambda 表达式来传递参数。
python
复制
import tkinter as tk
def on_button_click(message):
print(message)
root = tk.Tk()
button = tk.Button(root, text="Click Me", command=lambda: on_button_click("Button clicked!"))
button.pack()
root.mainloop()
在这个例子中,lambda 表达式创建了一个匿名函数,当按钮被点击时,该匿名函数会调用 on_button_click("Button clicked!")。
总结
command=on_button_click将函数引用传递给按钮的command属性,当按钮被点击时,Tkinter框架调用该函数。- 使用
command=on_button_click()会立即调用函数,而不是在按钮点击时调用,通常不是你想要的效果。 - 使用
lambda可以创建匿名函数,以便传递带参数的回调函数。
通过正确使用回调函数,可以让你的应用程序具有响应用户交互的能力,从而实现更复杂的逻辑和功能。
更多推荐


所有评论(0)