DangKai

Results 17 comments of DangKai

### [fairseq](https://github.com/pytorch/fairseq) fairseq的组织架构如下: ``` fairseq examples (具体任务相关的代码,及复现说明) fairseq clib (一些操作或算子的cpp或cu实现) config (几个baseline的config文件,yaml格式) criterions(所有损失函数,继承fairseq_criterion类,使用时需要注册,每个损失函数可添加自己的参数) data (数据读取相关,实现了词表、迭代器和很多种dataset,常用的是indexed_dataset、monolingual_dataset、language_pair_dataset) dataclass (配置参数,以及参数override操作) distributed (数据并行的相关配置,如数据切片) logging (日志,progress_bar) model_parallel (基于megatron-lm的核心部分mpu,数据加载相关部分和megatron相差很大) models (模型(transformer)或基础模型(encoder、decoder),使用时需要注册,里面设置了多个模型的配置,例如注册transformer-base,就无需填写模型其他参数) modules (模块,如attention、positional_embedding、transformer_layer) optim (优化器和调度器,fp16,使用时需要注册) tasks...

当前基于工厂模式大致构建了如下框架: * trainer是主体逻辑,在init函数通过build_xxx函数构建训练所需要的组件,如模型、数据集、损失函数等。训练、验证、保存加载等尽量复用同样的代码。 * 对于模型、数据集、损失函数这三类,采用了注册机制,因为各模型或数据集所涉及的参数不同,采用注册机制可以单独未用到的模型添加参数,除此之外,工厂模型也需要注册机制进行实现。在添加新类时,先继承base_model/base_criterion/base_dataset类,然后实现自己的逻辑,之后实现add_args和build_model这样的函数,最后在类名上面添加@register_model('xxx')这样的声明,这样可以在外部轻松切换模型。 * modules文件夹下实现了分布式训练的常用组件,之后视需求再进行补充和完善。 * distribute实现了分布式的配置管理。 * meter实现了几种常用meter,并构建了Logger类,方便日志管理。 * optimizer采用了文骁的写法,之后应该添加lamb这样的优化器,训练大模型时常常会用到(可以在oneflow中实现),lr_scheduler也不完善,之后添加到oneflow中。 * config下定义通用的参数,像模型的hidden size、dropout这类参数,在模型的add_args参数中定义。config会自动解析所需要的参数。 当前问题: * eager模式不支持amp、gradient checkpointing、set_pipeline_stage_id这类高级特性。 * dataloader有一个问题,目前dataloader负责了加载数据和迭代两个功能,这两个应该是分开的,我在写验证部分时发现,num_samples、batch_size这样的参数无法区分开。之后我再想想数据加载有什么好办法,大家有啥好办法,也可以在下面讨论。 * torch里的amp有多个模式,O1、O2、O3这些,对应半精度的范围不一样,oneflow目前只有一个开关。 * checkpointing机制也只有一个开关,但我不太清楚torch里是怎么做的。

看了一下目前libai、primeLM、GLM的代码,目前模块抽象如下: * VocabularyParallelEmbedding,PositionalEmbedding,普通Embedding(用于bert的type embedding) * Linear1D,ColumnParallelLinear,RowParallelLinear,MLP * Attention(基于self attention,包含很多变种,体现在attention mask和相对位置等方面) * LayerNorm * LMLogits(计算logits) * Pooler(bert二分类或下游任务需要) * Loss(交叉熵,kl散度,带mask,带label smooth) * Activation Checkpointing * Mask helper(我新加的,里面包含了所有attention mask的相关处理,例如将attention mask进行维度拓展,构造下三角掩码矩阵) GLM当前做法的弊端:仿照pytorch的写法,没有使用sbp、placement这些oneflow特色,无法体现我们的优势。所以建议采用另外两个库当前的写法。另外,这两种做法的运行速度是什么样,哪种更快一点。 另外两个库当前做法的弊端:需要根据dist确定模型,以及传参多了一项layer idx,用于确定placement。...

> consistent + graph 应该是最终的训练选择,但是 graph 对于 debug 不太便利,所以考虑 consistent + eager 进行 debug,我觉得应该完全放弃对 ddp 这种方式的支持,首先 ddp 只能做数据并行,另外和 consistent 不兼容,如要要加上去的话需要修改很多代码 我不是说支持ddp这个模块,而是设计一个类似于ddp的模块,这个模块可以封装在model的外层,负责将原始的单机单卡model变为分布式model。但原始模型可能无法放入到单个gpu中,所以eager模式可能无法使用,只能以一种lazy的方法,在调用模型或者调用我们设计的这个模块时,把模型的参数在各自的机器上初始化。

> T5ExtendedAttnMask 是按照libai bert复现的,但是在和Megatron对齐的过程中,发现Megatron只是简单地做了 .unsqueeze(1),和 T5ExtendedAttnMask 的实现不同,因此没有用上。所以这部分需要删除还是修改成等价于 .unsqueeze(1) 的操作放到模型里面? 这个因为megatron构造t5数据时,encoder_padding_mask等是[bsz, seqlen, seqlen]的维度,在bert里是[bsz, seqlen]的维度,因此它的处理和bert不同。我在https://github.com/Oneflow-Inc/libai/blob/add_models/libai/layers/mask_helpers.py 里把所有的attention mask操作都写成了模块,可以用这个替换掉bert和t5中的ExtendedAttnMask

现在写的模型只能训练,推理都没有实现。T5model拆分为T5Embedding、T5Encoder、T5Decoder三个模块。T5Embedding作为公共模块传参到T5Encoder、T5Decoder中,实现参数共享。T5model的forward过程包括两部分,encoder.forward()和decoder.forward()。T5Encoder实现编码器的所有层以及编码器的调用过程,T5Decoder实现解码器的所有层和解码器的调用过程,编码过程没有特殊之处,仿照bert直接传参输出即可,解码过程要考虑增量解码,所以要仿照其他库实现。必须将整个模型拆分开,这样才能在推理阶段,调用model.encoder(xxx)得到编码状态,然后循环调用model.decoder(xxx)逐一解码。

大家可以思考一下,现在验证了什么?目前验证了libai的layers使用到模块是正确的,按照目前的方式组装成t5模型和megatron一样,这种组装方式也是正确的。这仅证明训练的正确性,推理的正确性目前无法证明。那么,同样使用这些模块,改变它的组装方式(模型层以及它们的相对顺序不变),是不是也是正确的。以及,在此基础上,加上一些推理相关的,是不是也是正确的。 我不是想说之前我实现的都对,这个工作没有意义。我之前实现的那个pr,最近就发现了不满意的地方,可能也会存在错误,如果延续那个pr,我仍然会改正那些错误。我想表达的是,megatron的实现不友好,所以我们要实现一个更简单易用的库,同时拓展一下oneflow的生态。它的不友好不仅是mpu模块、dataset、trainer,也包括模型,所以我们不能完全对标它的实现。 现在的问题是,megatron实现的模型,将bert、gpt、t5统一为language_model模块,通过add_encoder和add_decoder参数控制。而我们抽象到transformer_layer层,具体的模型由算法工程师开发决定。这里导致参数名称可能不统一,如果想对齐megatron,只能要求模型开发时,也按照它的组装顺序实现。这导致了另一个问题,megatron的推理阶段是通过attention里inference_params这些参数控制的,来减少重复计算。我们之前实现attention时参考huggingface实现,通过past_key_value和use_cache参数控制。如果按照megatron实现,推理无法完成;如果按照huggingface实现,对齐精度时会增加加载模型的工作量。你们可以思考一下之后按照哪种方式实现

先明确trainer的需求,它只负责训练相关的,还是将创建data_loader、model、optimizer等也纳入trainer。如果只是训练相关的,那么它的功能应当包括: 1. 训练并更新参数 2. 验证模型,计算valid_loss 3. 加载、保存模型 4. 记录日志 5. 评价指标metric(可能通过评价指标判断早停) 6. 分布式配置,cpu、gpu切换,amp设置 7. 恢复训练 8. 使用模型预测(待讨论) 另外,这里的模型是eager还是graph,如果是graph,那么eager to graph的转换写在哪里,是用trainer封装,还是在models里由用户写,这个需要商量一下。 其他可参考的模型库有:pytorch-lightning,huggingface transformers。pytorch-lightning推荐看一下,很符合研究者的习惯。不过这些库都封装得太好了,外部看起来不知道从哪里下手,建议列完需求,把需求先实现即可,不用一次性做到完善。

eager to graph的转换不能做出抽象吗,如果能做出来,那用户就几乎感知不到eager和graph的区别了,也可以避免每写一个模型,就定义两个graph。建议问问框架组的同事。很多步骤这些都是固定的,只是无法使用**kwargs传参,想办法绕过,或者写个基类,模型传参部分由用户实现,其余部分由框架规定。

### 补充fairseq的配置方法 * fairseq采用dataclass和parser结合的方法,dataclass负责通用的参数,如训练、测试时相关的配置;parser负责专用的参数,如模型中参数。 * 对于model、task、criterion这三种类,都包含着add_args这样一个静态函数,负责添加专用参数。 * 上述这三个类采用注册机制,以model类为例,解析parser的时候分两步,第一步解析通用参数,得到model类型,根据model类型再解析model中的专有参数。 * 优点:专有参数相互隔离,不会丢失参数。 * 缺点:这种方法将参数输出,不支持序列化和反序列化。