首页 > 系统相关 >LLM优化:开源星火13B显卡及内存占用优化

LLM优化:开源星火13B显卡及内存占用优化

时间:2024-04-28 16:56:51浏览次数:62  
标签:13B config self args state dict LLM model 优化

1. 背景

本qiang~这两天接了一个任务,部署几个开源的模型,并且将本地经过全量微调的模型与开源模型做一个效果对比。

部署的开源模型包括:星火13B,Baichuan2-13B, ChatGLM6B等

其他两个模型基于transformers架构封装,因此推理服务启动还是十分丝滑,但星火13B是基于Megatron-DeepSpeed框架实现,地址是:https://gitee.com/iflytekopensource/iFlytekSpark-13B,启动推理服务的过程中发现启动13B的显卡占用71G-78G,有些反直觉。

此文就是整理开源星火13B的显存及内存排查并优化的整理过程,至于哪家开源模型效果好,不在此文的讨论范围内。

2. 原因分析

直观上来说,13B的模型,数据类型为bf16,显卡占用大概在26G左右,但星火13B直接占用70G+,不可思议,怪不得网上关于星火开源模型的讨论少之又少,原因显而易见,这么大的显存占用只能用多卡或者A800等80G显卡才能适配。穷人家的孩子,哪有这么多余粮。

排查原因的过程中,少不了源码的调试与分析。在排查的过程中,启动推理服务的文件run_iFlytekSpark_text_generation.py中,model_provider方法是初始化模型并加载模型文件的方法。

def model_provider(pre_process=True, post_process=True):
    """Build the model."""
    print_rank_0('building iFlytekSpark model ...')
    args = get_args()
    config = core_transformer_config_from_args(args)
    
    ### 初始化星火模型
    model = iFlytekSparkModel(
        config,
        num_tokentypes=0,
        parallel_output=False,
        pre_process=pre_process,
        post_process=post_process,
        return_moe_loss=False
    )


    if args.from_pretrained is not None:
        assert os.path.exists(args.from_pretrained)
        ckpt_path = get_checkpoint_name(args.from_pretrained)
        print_rank_0('Loading from {} '.format(
                args.from_pretrained))
        # 模型加载权重文件
        state_dict = torch.load(ckpt_path, map_location=f"cuda:{torch.cuda.current_device()}")
        if 'module' in state_dict:
            state_dict = state_dict['module']
        model.load_state_dict(state_dict)
    return model

 

其中,加载权重文件可以看到,加载state_dict时,直接将权重文件加载到显卡中,而非加载至CPU,然后再执行to方法,转移到GPU。因此该处是一个潜在的优化点。

再打入iFlytekSparkModel内部,词表Embedding层,线性转换层,等初始化weight时,也是直接将weight分配在GPU上运行。例如下例:

class RowParallelLinear(torch.nn.Module):
    def __init__(self, input_size: int, output_size: int, *,
                 config: ModelParallelConfig,
                 init_method: Callable,
                 bias: bool = True,
                 input_is_parallel: bool = False,
                 stride: int = 1,
                 keep_master_weight_for_test: bool = False,
                 skip_bias_add: bool = False,
                 moe=False, enable_expert_tensor_parallelism=False):
        super(RowParallelLinear, self).__init__()

        # .........
        
        if config.use_cpu_initialization:
            self.weight = Parameter(torch.empty(self.output_size,
                                                self.input_size_per_partition,
                                                dtype=config.params_dtype))
            if config.perform_initialization:
                self.master_weight = _initialize_affine_weight_cpu(
                    self.weight, self.output_size, self.input_size,
                    self.input_size_per_partition, 1, init_method,
                    stride=stride, return_master_weight=keep_master_weight_for_test,
                    params_dtype=config.params_dtype)
        else:
            # 默认按照启动sh命令,会走该分支
            self.weight = Parameter(torch.empty(
                self.output_size, self.input_size_per_partition,
                device=get_accelerator().current_device_name(), dtype=config.params_dtype))
            if config.perform_initialization:
                _initialize_affine_weight_gpu(self.weight, init_method,
                                              partition_dim=1, stride=stride)
        if bias:
            if config.use_cpu_initialization:
                self.bias = Parameter(torch.empty(self.output_size,
                                                  dtype=config.params_dtype))
            else:
                # 默认按照启动sh命令,会走该分支
                self.bias = Parameter(torch.empty(
                    self.output_size, device=get_accelerator().current_device_name(),
                    dtype=config.params_dtype))
            setattr(self.bias, 'sequence_parallel', self.sequence_parallel)

            if config.perform_initialization:
                # Always initialize bias to zero.
                with torch.no_grad():
                    self.bias.zero_()
        else:
            self.register_parameter('bias', None) 

3. 优化方案

1. 模型初始化时,模型的Embedding,线性层的权重weight均直接加载至GPU,因此可以优化为先将这些weight加载至CPU。

改进的方式也很简单,从上面的源码层面,可以看到,当增加参数” use_cpu_initialization”,将使用CPU进行初始化权重,因此只需要在启动推理服务的脚本中增加” --use-cpu-initialization”参数即可。

2. 加载模型文件时,直接加载至GPU,然后run_iFlytekSpark_text_generation.py中的get_model方法中,当模型加载完成后,会进行分配至GPU以及FP16的转换的操作。如下代码所示。

def get_model(model_provider_func, model_type=ModelType.encoder_or_decoder, wrap_with_ddp=True):
    """Build the model."""
    args = get_args()
    args.model_type = model_type

    # ..........

    # GPU allocation.
    for model_module in model:
        model_module.to(get_accelerator().current_device_name())
 

    # Fp16 conversion.
    if args.fp16 or args.bf16:
        model = [Float16Module(model_module, args) for model_module in model]

    # .......

    return model

因此,优化的方式也很简单,可以优化为先加载至CPU,再运行get_model中的默认分配至GPU,加载完后,再使用垃圾回收机制清除CPU占用的内存即可。

话不多说,优化后的代码如下:

def model_provider(pre_process=True, post_process=True):
    """Build the model."""
    print_rank_0('building iFlytekSpark model ...')
    args = get_args()
    config = core_transformer_config_from_args(args)
    model = iFlytekSparkModel(
        config,
        num_tokentypes=0,
        parallel_output=False,
        pre_process=pre_process,
        post_process=post_process,
        return_moe_loss=False
    )


    if args.from_pretrained is not None:
        print(args.from_pretrained)
        assert os.path.exists(args.from_pretrained)
        ckpt_path = get_checkpoint_name(args.from_pretrained)
        print_rank_0('Loading from {} '.format(
                args.from_pretrained))

        # state_dict = torch.load(ckpt_path, map_location=f"cuda:{torch.cuda.current_device()}")
        # CPU进行加载
        state_dict = torch.load(ckpt_path, map_location=f"cpu")
        if 'module' in state_dict:
            state_dict = state_dict['module']
        model.load_state_dict(state_dict)
        
        # 加载完成,删除state_dict,并垃圾回收
        del state_dict
        gc.collect()
        torch.cuda.empty_cache()

    return model

  

4. 效果对比

(1) 优化前的显卡占用: 71.5G

 

(2) 优化前的内存占用: 虚拟内存占用94.5G

 

 (3) 优化后的显卡占用: 26G

 

 (4) 优化后的内存占用: 43.1G

  

5. 总结

一句话足矣~

本文主要是针对开源星火13B的显存及内存占用过大的一个代码优化。核心思想是使用CPU预加载模型,再转换至GPU。

后期如有遇到此类问题,可以借鉴之~

 

 

标签:13B,config,self,args,state,dict,LLM,model,优化
From: https://www.cnblogs.com/mengrennwpu/p/18164027

相关文章

  • MySQL索引优化二
    学习来源:图灵课堂https://vip.tulingxueyuan.cn 分页优化一般来说,我们的后台管理系统都是有翻页功能的,并且有时候还要加上一些筛选过滤条件;如果对查询没有经过特别的优化,那么就会发现翻页越往后就越慢,这是为什么呢?因为如果单独是使用limit,例如limit90000,5;这个并不是从第......
  • vue3 引入workers 大量优化业务代码和复杂的计算的代码
    前沿vite页面引入worker在src新建一个 worker.d.ts文件declaremodule'*.worker.ts'{classWebpackWorkerextendsWorker{constructor();}exportdefaultWebpackWorker;}在 tsconfig.json页面引入"lib":["esnext",......
  • 加速博客体验:静态资源优化技巧大揭秘!
    如今有许多人涉足博客写作,其中大多数正处于博客创作的旅程中。每位程序员都梦想拥有自己的服务器,理想情况下,服务器配置越高越好,价格越实惠越好。购买一台基础款服务器用于建立博客是一个不错的选择,因为并不需要处理大流量。这完全是出于个人兴趣和坚持写作的良好习惯。当然了写博......
  • 数据库优化 索引(index)
    介绍索引是帮助数据库高效获取数据的数据结构优缺点:优点:提高数据查询的效率,降低数据的IO成本。通过索引列多数据进行排序,降低数据排序的成本,降低CPU消耗缺点:索引会占存储空间。索引大大提高了查询效率,同时却也降低了insert、update、delete的效率结构MySql数据库支......
  • MySQL-索引优化实战
     针对联合索引来说,如果第一列就是用范围查询,例如大于小于这些,就会认为查询的行很多,如果不是覆盖索引,那么就不再使用这个二级索引,认为使用二级索引还要频繁的去回表查询等等,消耗更大,所以就会去全表扫描。但是可以使用forceindex(索引名称)去强制使用指定的索引,但是一般不建议这......
  • TVM Pass优化 -- 算子融合(FuseOps)
    定义算子融合就是将多个计算单元合并到一个计算单元里完成计算,减少中间数据读写内存的操作,从而节省计算时间。TVM中将算子融合分为四种:kElemWise:两个tensor之间按照元素逐个操作的算子,实际上所有的四则运算都是这种类型kBroadcast:带有广播操作的算子kInjective:输入和输出......
  • 大语言模型(LLM)的逻辑推理能力的例子 —— 以ChatGPT3.5为例
    例子:......
  • Go语言高并发与微服务实战专题精讲——远程过程调用 RPC——优化RPC调用,缓解频繁请求
    远程过程调用RPC——优化RPC调用,缓解频繁请求导致的GC压力 在Go语言的高并发和微服务架构中,远程过程调用(RPC)是一种常用的通信机制。然而,当频繁发送RPC请求时,不断创建Request和Response结构体可能会带来额外的垃圾收集(GC)压力,进而影响应用的性能和响应时间。为了减......
  • MySQL LIMIT 和 ORDER BY 优化
     MySQLLIMIT子句MySQLLIMIT子句是控制SELECT语句返回行数的重要工具。通过指定从结果集中获取的最大行数,它可以让你处理数据子集,尤其是在涉及大表的情况下。该功能可提高查询性能,并通过只获取必要的行来优化资源使用。 MySQLLIMIT子句的语法MySQL中的LIMIT子句......
  • 计算机Windows系统优化小知识
    目录目录什么是注册表优化优化工具什么是注册表注册表是保存所有系统设置数据的存储器。注册表保存了Windows运行所需的各种参数和设置,以及应用程序相关的所有信息。从Windows启动开始,到用户登录、应用程序运行等所有操作都需要以注册表中记录的信息为基础。注册表在Windows操......