LlamaForCausalLM.forward 被多繼承包裝,要重載庫函數來計算 流式指標?!
Baseline 写的很巧妙,但是真的好用吗? Fuck 多继承!
多继承思路——Mixin
Mixin是一种程序设计中的多继承思路,一句话来说就是「我们想要给原有的类 A 添加新的 method, 所以先定义一个专门实现了这些新 method 的 Meta-method 类B(这个类通常不应该被直接实例化 ),然后将原有的类 和 Meta-method class B 进行多继承,形成一个新的类」。
EG. 1:
1 | |
为了方便维护 Airplane ,我们将它的独有的子类方法(飞行)放到一个新的方法类中,然后多继承让他拥有吧。
EG. 2:
一个抽象的例子,UML大家还能看懂吧。
什么?被这样包装后,函数接口和 Transformer.trainer 中使用的不一致了?
1 | |
可以看到,作者将多模态信息的编码和对齐过程放在了 forward 函数中,封装简洁的同时也对程序员屏蔽了细节。
可惜,顺便对 Transformer.trainer 也屏蔽了细节……
Transformer.trainer 在 __init__ 阶段可选地接受一个 compute_metrics(),来在 Transformer.trainer.eval_loop 中支持对生成式指标的计算。而生成式指标计算中,如果你没有给我你真正处理完的 labels,我拿着原始的纯文本的 labels ,岂不是拔剑四顾心茫然?
重载库函数
只能重载 Transformer.trainer.eval_loop 函数,让他将在 forward() 中处理过的真正的 labels 给到 self.compute_metrics()
1 | |
先继承 Trainer,然后将原实现复制进来,进行大刀阔斧的修改,顺便去掉我们用不到的特殊判断。
1 | |
但是 python 竟然命名空间上不像 cpp 一样会在继承的时候顺便继承父类的命名空间,所以要手动将这个子类中重载方法中用到的所有类内方法 import 进来。
什么?直接OOM,实验室的服务器把我踢出登录,让我冷静半个小时不给登入?
原来有些生成式指标是不支持流式计算的,即需要将 eval_dataset 全部推理完,再将输出的 pred, labels 等丢进 compute_metric(),才能计算出准确的值。如 F1_score 就不像 acc 可以将每个独立的算出来然后求平均那么简单(是不等价的)。
所以 Transformer.trainer.eval_loop 的默认实现是将所有算完的先 detach().cpu() 放在内存中等着。。。天大的内存也不够你这么放啊,因为我是 MLLM 的输入输出,sequence 巨长。
添加流式计算
所以我们应该打开流式开关,并修改 compute_metric() 方法,让他在平时先计算每一个 eval_batch 的结果,仅仅将结果存着,而不是所有的 preds, labels. 在最后一个 eval_batch 算完后,再进行平均。
1 | |
其中,compute_result 就是Transformer.trainer.eval_loop给他的信号,如果是 True ,就是告诉他,这个是最后一个 batch 了,收拾收拾准备输出最后的结果吧。
最终经过以上痛苦的重载过程,终于可以跑起来生成式指标的计算了,撒花。