当前位置: 首页 > news >正文

制作网站公司图片如何建设网站使用

制作网站公司图片,如何建设网站使用,网站排名按天付费,长沙网络营销公司目录 一、rwkv模型简介 二、lora原理简介 三、rwkv-lora微调 1、数据整理 2、环境搭建 a、Dockerfile编写 b、制造镜像 c、容器启动 3、训练代码修改 四、模型推理 1、模型推理 2、lora权重合并 3、推理web服务 五、总结 由于业务采用的ChatGLM模型推理成本太大了…        目录 一、rwkv模型简介 二、lora原理简介 三、rwkv-lora微调 1、数据整理 2、环境搭建 a、Dockerfile编写 b、制造镜像 c、容器启动 3、训练代码修改 四、模型推理 1、模型推理 2、lora权重合并 3、推理web服务 五、总结 由于业务采用的ChatGLM模型推理成本太大了希望降低模型推理成本。因此对rwkv_1.5B模型进行了预研和业务领域的验证。为了快速验证采用了loraacceleratedeepspeed的训练方式。微调的过程中对rwkv模型认识更加深刻同时对于docker训练环境搭建也更加熟悉了。这篇博客就分享一下这次微调中的一些实践主要是关于训练流程拉通和rwkv模型在业务领域的一些结论。 一、rwkv模型简介 rwkv模型是国人研发的一个非常优秀的模型采用RNN架构代码目前主流的attention机制的transformer架构在时间复杂度和空间复杂度都减少比较多的情况下还能取得非常不错的效果在各个榜单都有上榜。 ​​ 上图是rwkv模型语言建模的架构可以看到舍弃了attention机制采用time mix 和channel mix模块。  二、lora原理简介 论文LoRA: Low-Rank Adaptation of Large Language Models 开发了一种方法专为微调大模型减小显存。如下图 对于一个参数在微调的时候不直接微调W而是把W通过低秩分解为两个小矩阵B和A的乘积然后学习更新B和A从而达到减少参数量和梯度等同时保证模型lora微调后的效果和全参数微调效果相当。实现的时候会在BAx乘以一个系数一般是lora_alpha/lora_rank的比值注意lora_rank越大可学习的参数越多显存占用就越多。 实践一般采用peft来实现对模型的linear层进行weight分解使用方法如下 model初始化 ...... peft_config LoraConfig(peft_typeLORA,task_typeTaskType.CAUSAL_LM,inference_modeFalse,rargs.lora_rank,lora_alphaargs.lora_alpha,lora_dropoutargs.lora_dropout,target_modulesargs.target_modules.split(,),) model get_peft_model(model, peft_config) ...... model训练和保存 model_state_dict lora.lora_state_dict(model) torch.save(path,model_state_dict ) 三、rwkv-lora微调 rwkv的微调主要的重点内容在于数据的整理(整理成模型可训练的格式)、训练环境的搭建、训练代码的修改和最后的模型效果评估其中至于怎么样微调才能获得比较好的效果本文不予讨论。由于rwkv支持2中数据格式一种是questionanswer拼接另外一种是instructioninputresponse拼接目前1.5Brwkv开源了v4和v5版本的权重因此这里会做4次实验用相同的业务数据构成训练集和测试集使用不用的权重和数据指令拼接方式进行实验。 1、数据整理 qa指令拼接——适合做问答类 {text: Question: 问题\n\nAnswer: 答案} iir指令拼接——适合做阅读理解问答 {text: Instruction基于专业背景的知识问题\n\nInput专业领域的资料背景知识内容\n\nResponse基于上述的专业回答} 其中Instruction 是指示Input 是需要操作的数据(注意Input可以为空)Response是答案 我们的业务数据 {context: 姓名未知\n服务时间晚上23点\n联系方式未知\n地址广东省深圳市龙岗区南湾街道康桥花园\n空调品牌卡萨帝\n空调样式挂机\n是否5匹10匹\n故障类型异味\n\n坐席空调发生什么故障了不制热、不制冷、不开机还是其他问题\n客户其他不故障现象\n\n以上海尔导航场景收集的要素信息以及坐席和客户的一轮对话你是要素抽取的专家请根据坐席和客户的对话更新上述要素结果对话中未提及到的要素保持原样结果输出“空调品牌”取值范围是“卡萨帝”、“海尔”、“统帅”、“小超人”“空调样式”取值范围是“柜机”、“挂机”、“嵌入机”、“中央空调”“是否5匹”取值范围是“5匹以上”、“5匹以下”“故障类型”取值范围是“不制冷”、“不制热”、“机器制热效果差”、“机器制冷效果差”、“机器着火”、“遥控器故障”、“无法关机”、“噪音大”、“温度不能调整”、“外观伤”、“频繁开停机”、“显示屏乱码跳屏”、“机器报故障”、“室内机漏水”、“连接管未包扎好”、“送风强度”、“异味”、“漏电”、“不通电”、“不启动”、“按键失灵”、“出风异常”、“显示屏异常”、“不停机”、“不除霜”、“排水管问题”、“空调漏气/漏氟”、“购买配件”、“自动开/关机”\n请给出要素抽取结果, target: 姓名未知\n\n服务时间晚上23点\n\n联系方式未知\n\n地址广东省深圳市龙岗区南湾街道康桥花园\n\n空调品牌卡萨帝\n\n空调样式挂机\n\n是否5匹10匹\n\n故障类型其它故障}qa拼接后的形式 {text: Question姓名未知\n服务时间晚上23点\n联系方式未知\n地址广东省深圳市龙岗区南湾街道康桥花园\n空调品牌卡萨帝\n空调样式挂机\n是否5匹10匹\n故障类型异味\n\n坐席空调发生什么故障了不制热、不制冷、不开机还是其他问题\n客户其他不故障现象\n\n以上是海尔导航场景收集的要素信息以及坐席和客户的一轮对话你是要素抽取的专家请根据坐席和客户的对话更新上述要素结果对话中未提及到的要素无需输出若所有要素在对话中均未提到请直接输出“无效对话”空调品牌取值范围是“卡萨帝”、“海尔”、“统帅”、“小超人”空调样式取值范围是“柜机”、“挂机”、“嵌入机”、“中央空调”是否5匹取值范围是“5匹”、“5匹以上”、“5匹以下”、“10匹”故障类型取值范围是“不制冷”、“不制热”、“机器制热效果差”、“机器制冷效果差”、“机器着火”、“遥控器故障”、“无法关机”、“噪音大”、“温度不能调整”、“外观伤”、“频繁开停机”、“显示屏乱码跳屏”、“机器报故障”、“室内机漏水”、“连接管未包扎好”、“送风强度”、“异味”、“漏电”、“不通电”、“不启动”、“按键失灵”、“显示屏异常”、“不停机”、“不除霜”、“空调漏气/漏氟”、“购买配件”、“自动开/关机”、“出风异常”、“排水管问题”、“其他故障”\n请给出要素抽取结果\n\nAnswer故障类型其它故障} iir拼接后的形式 {text: Instruction以上是海尔导航场景收集的要素信息以及坐席和客户的一轮对话你是要素抽取的专家请根据坐席和客户的对话更新上述要素结果对话中未提及到的要素无需输出若所有要素在对话中均未提到请直接输出“无效对话”空调品牌取值范围是“卡萨帝”、“海尔”、“统帅”、“小超人”空调样式取值范围是“柜机”、“挂机”、“嵌入机”、“中央空调”是否5匹取值范围是“5匹”、“5匹以上”、“5匹以下”、“10匹”故障类型取值范围是“不制冷”、“不制热”、“机器制热效果差”、“机器制冷效果差”、“机器着火”、“遥控器故障”、“无法关机”、“噪音大”、“温度不能调整”、“外观伤”、“频繁开停机”、“显示屏乱码跳屏”、“机器报故障”、“室内机漏水”、“连接管未包扎好”、“送风强度”、“异味”、“漏电”、“不通电”、“不启动”、“按键失灵”、“显示屏异常”、“不停机”、“不除霜”、“空调漏气/漏氟”、“购买配件”、“自动开/关机”、“出风异常”、“排水管问题”、“其他故障”\n请给出要素抽取结果\n\nInput姓名未知\n服务时间晚上23点\n联系方式未知\n地址广东省深圳市龙岗区南湾街道康桥花园\n空调品牌卡萨帝\n空调样式挂机\n是否5匹10匹\n故障类型异味\n\n坐席空调发生什么故障了不制热、不制冷、不开机还是其他问题\n客户其他不故障现象\n\nResponse故障类型其它故障} 2、环境搭建 官方代码库指定的环境直接安装就好了不过安装的过程中要注意机器的显卡驱动一定要比安装的cuda版本要高并且cuda版本的算力不能低于显卡的算力大多数情况下显卡是支持一定的低版本的cuda和torch的torch的版本要和cuda的版本一致比如4090显卡安装了12.0的显卡驱动安装了cuda11.8那么torch也要安装cuda11.8的版本 torch2.0_cu118。rwkv有自己实现的cuda算子需要python调用C和nvcc来编译作为torch的扩展所以要严格匹配版本不然会报显卡算力过高和torch版本不匹配cuda和torch版本不匹配等错误。C编译的时候还需要完整的libso库文件由于本人使用的机器多人使用不好升级libso库文件——错误操作可能会导致linux系统出错。稳妥起见直接使用docker来搭建训练环境并且在docker中训练。物理机器上安装docker编写dockerfile后制作镜像启动容器然后训练就OK了。 a、Dockerfile编写 ##build 镜像 #docker build -t images_name(images_name:tag) -f ./Dockerfile . ##运行容器 --gpus all 宿主机上的显卡可用 --ipc host 代表与宿主机器共享命名空间即让Docker容器和宿主机器使用同一个进程ID命名空间和信号命名空间从而实现进程间通信的能力 ## --network host docker 使用本机的IP和端口 #docker run -d -it --name my_container --gpus all --network host --ipc host images_name(id)#cuda toolkit共享的库涵盖了运行环境的最小集合如动态库等但没有cuda的编译工具nvcc #FROM nvidia/cuda:11.8.0-runtime-ubuntu22.04#基于runtime添加了编译工具链、调试工具、头文件、静态库用于从源码编译cuda应用是有nvcc的 FROM nvidia/cuda:11.8.0-devel-ubuntu22.04WORKDIR /rwkv # Set up time zone. ENV TZAsia/Shanghai RUN ln -snf /usr/share/zoneinfo/$TZ /etc/localtimeENV STAGE_DIR/tmp RUN mkdir -p ${STAGE_DIR}RUN apt-get update \apt-get install -y --no-install-recommends \software-properties-common build-essential autotools-dev \nfs-common pdsh \cmake g gcc \curl wget vim tmux emacs less unzip \htop iftop iotop ca-certificates openssh-client openssh-server \rsync iputils-ping net-toolsRUN apt-get update \apt-get install -y --no-install-recommends \libsndfile-dev \libcupti-dev \libjpeg-dev \libpng-dev \screen \libaio-dev#从源码安装python RUN apt install unzip wget build-essential zlib1g-dev libncurses5-dev libgdbm-dev libnss3-dev libssl-dev libsqlite3-dev libreadline-dev libffi-dev curl libbz2-dev pkg-config make -y RUN apt-get install liblzma-dev -y #RUN wget https://www.python.org/ftp/python/3.10.10/Python-3.10.10.tar.xz COPY Python-3.10.10.tar.xz ./ RUN tar xf Python-3.10.10.tar.xz RUN cd Python-3.10.10 ./configure --enable-optimizations make altinstall cd .. rm -fr * RUN python3.10 -m pip install torch2.0.0 --index-url https://download.pytorch.org/whl/cu118WORKDIR /rwkv COPY requirements.txt ./ #RUN python3.10 -m pip install -r requirements.txt #RUN python3.10 -m pip install --upgrade pip python3.10 -m pip install -i https://mirrors.aliyun.com/pypi/simple -r requirements.txt RUN python3.10 -m pip install -i https://mirrors.aliyun.com/pypi/simple -r requirements.txt # 拷贝所有nue文件 COPY . ./ 注意python可以提前现在源码然后上传到服务器再制作镜像cuda docker 一定要拉取devel版本runtime版本会精简不安装nvcc等编译工具python安装一些第三方库会依赖nvcc编译工具的。其他的都没有什么了一切正常编写即可。 b、制造镜像 docker build -t images_name(images_name:tag) -f ./Dockerfile . 这个耗时比较久一个是镜像、已经库文件安装还有数据、代码等copy。 c、容器启动 docker run -d -it --name my_container --gpus all --network host --ipc host images_name(id) 关注的地方是--gpus 一定要是all这样容器才能使用物理机上的所有显卡--network host保证docker使用物理机的ip和端口可以通过改ip访问docker内的服务--ipc host让Docker容器和宿主机器使用同一个进程ID命名空间和信号命名空间从而实现进程间通信的能力——跑分布式训练必须选项因为多进程中的子进程要和主进程进行通信传输梯度等信息。 3、训练代码修改 原始的训练代码是不支持lora和accelerate的这里我们修改为支持lora以及accelerate的形式。同时由于采用分布式训练目前可以使用deepspeed来做而accelerate也支持deepspeed的插件形式(和直接使用deepspeed来做分布式训练稍有不同直接使用deepspeed对系统的各种库libso要求的比较严格之前使用deepspeed一直没有成功过)。代码主体结构如下 from accelerate import Accelerator, DeepSpeedPlugin from peft import get_peft_model, LoraConfig, TaskType import loralib as lora#初始化分布式环境 accumulate_step 4 mixed_precision bf16 deepspeed_plugin DeepSpeedPlugin(zero_stage2, gradient_accumulation_stepsaccumulate_step) accelerator Accelerator(mixed_precisionmixed_precision, gradient_accumulation_stepsaccumulate_step, deepspeed_plugindeepspeed_plugin) device accelerator.device...... ...... model RWKV(args)#lora设置设置模型的那些参数使用lora以及其他的一些参数。 peft_config LoraConfig(peft_typeLORA,task_typeTaskType.CAUSAL_LM,inference_modeFalse,rargs.lora_rank,lora_alphaargs.lora_alpha,lora_dropoutargs.lora_dropout,target_modulesargs.target_modules.split(,),) model get_peft_model(model, peft_config) ...... #模型、优化器、数据加载器等用accelerate包装一下。 model, optimizer, train_dataloader accelerator.prepare(model, optimizer,train_dataloader) ...... for epoch in range(int(args.epoch_count)):for step, batch in enumerate(t : tqdm(train_dataloader, ncols100)):model(batch)......accelerator.backward(loss)optimizer.step()lr_scheduler.step()optimizer.zero_grad() 分布式环境的初始化以及lora参数的设置针对rwkv模型lora设置如下 lora_rank16 lora_alpha32 lora_dropout0.1 target_modulesemb,key,value,receptance,output,head 完整的训练代码如下(其他的部分自行完成代码修改自rwkv_LM中的rwkv-v4neo) ######################################################################################################## # The RWKV Language Model - https://github.com/BlinkDL/RWKV-LM ######################################################################################################## import os, warnings, math, sys, time import numpy as np import torch from torch.utils.data import DataLoader import logging from transformers import get_linear_schedule_with_warmup from argparse import ArgumentParser logging.basicConfig(levellogging.INFO) import os import sys sys.path.append(os.getcwd()) def script_method(fn, _rcbNone):return fndef script(obj, optimizeTrue, _frames_up0, _rcbNone):return objimport torch.jitscript_method1 torch.jit.script_method script1 torch.jit.script torch.jit.script_method script_method torch.jit.script scriptfrom torch.utils.tensorboard import SummaryWriter import torch import torch.nn as nnfrom torch.utils.data import DataLoader import gcimport psutil import traceback from tqdm import tqdm import numpy as npfrom accelerate import Accelerator, DeepSpeedPlugin from torch.utils.data import Dataset, IterableDataset import random import json from collections import defaultdictimport threading from tokenizer import build_tokenizer from datetime import datetime from peft import get_peft_model, LoraConfig, TaskType import loralib as loraaccumulate_step 4 mixed_precision bf16 deepspeed_plugin DeepSpeedPlugin(zero_stage2, gradient_accumulation_stepsaccumulate_step) accelerator Accelerator(mixed_precisionmixed_precision, gradient_accumulation_stepsaccumulate_step, deepspeed_plugindeepspeed_plugin) device accelerator.devicedef b2mb(x):return int(x / 2 ** 20)class TorchTracemalloc:def __enter__(self):gc.collect()torch.cuda.empty_cache()torch.cuda.reset_max_memory_allocated() # reset the peak gauge to zeroself.begin torch.cuda.memory_allocated()self.process psutil.Process()self.cpu_begin self.cpu_mem_used()self.peak_monitoring Truepeak_monitor_thread threading.Thread(targetself.peak_monitor_func)peak_monitor_thread.daemon Truepeak_monitor_thread.start()return selfdef cpu_mem_used(self):get resident set size memory for the current processreturn self.process.memory_info().rssdef peak_monitor_func(self):self.cpu_peak -1while True:self.cpu_peak max(self.cpu_mem_used(), self.cpu_peak)# cant sleep or will not catch the peak right (this comment is here on purpose)# time.sleep(0.001) # 1msecif not self.peak_monitoring:breakdef __exit__(self, *exc):self.peak_monitoring Falsegc.collect()torch.cuda.empty_cache()self.end torch.cuda.memory_allocated()self.peak torch.cuda.max_memory_allocated()self.used b2mb(self.end - self.begin)self.peaked b2mb(self.peak - self.begin)self.cpu_end self.cpu_mem_used()self.cpu_used b2mb(self.cpu_end - self.cpu_begin)self.cpu_peaked b2mb(self.cpu_peak - self.cpu_begin)# print(fdelta used/peak {self.used:4d}/{self.peaked:4d})def collate_fn(batch):tokens, labels, domains zip(*batch)input_ids torch.nn.utils.rnn.pad_sequence(tokens,batch_firstTrue,padding_value0)labels torch.nn.utils.rnn.pad_sequence(labels,batch_firstTrue,padding_value-100)domains torch.stack(domains)return {input_ids: input_ids, labels: labels, domains:domains}idx2domain {} domain2idx {} # 所有数据全部加载 batch内采样 class DataReader(Dataset):def __init__(self,tokenizer, file_list, sample_ratios, domain_names, max_token, args):self.args argsself.tokenizer tokenizerfile_list file_list.split(,)sample_ratios list(map(float, sample_ratios.split(,)))domain_names domain_names.split(,)assert len(file_list) len(sample_ratios) and len(file_list) len(domain_names)self.file_list file_listself.domain_names domain_namesself.max_token max_tokenself.sample_ratios sample_ratiosself.sum_ratio sum(sample_ratios)print(self.sum_ratio: ,self.sum_ratio)assert self.sum_ratio 1.0self.cum_ratios [sum(sample_ratios[:i 1]) for i in range(len(sample_ratios))]print(file_list: {}, sample_ratios: {} cum_ratios:{}.format(file_list, sample_ratios, self.cum_ratios))self.domain2num defaultdict(int)self.common_datas {}for i in range(len(file_list)):domain2idx[domain_names[i]] iidx2domain[i] domain_names[i]self.common_datas[domain_names[i]] self.loaddata_convert_token_to_ids(domain_names[i], file_list[i])print(file_list[i], len(self.common_datas[domain_names[i]]))print(domain2num:{}.format(self.domain2num))self.train_data []self.index 0self.epoch 0self.train_length 4000self.train_step 1000def loaddata_convert_token_to_ids(self, domain_name, file_path):with open(file_path, r, encodingutf-8) as f:lines f.readlines()domain_idx domain2idx[domain_name]all_datas []for line in tqdm(lines[0:], descfread{file_path},ncols100):text json.loads(line)[text]text text.split(\n\n)q \n\n.join(text[0:3]) Answera \n\n.join(text[3:])a a.replace(Answer,)q_ids self.tokenizer.tokenize(q)a_ids self.tokenizer.tokenize(a)ids q_ids a_idsids.append(self.tokenizer.eod)if len(ids) 2:if len(ids) self.max_token:# 大于最大长度的数据丢弃掉continueelse:labels [-100] * len(q_ids) a_ids [self.tokenizer.eod]assert len(ids) len(labels), len(ids) ! len(labels)input_ids torch.as_tensor(ids[:-1], dtypetorch.long)labels torch.as_tensor(labels[1:], dtypetorch.long)domain_idx torch.as_tensor(domain_idx, dtypetorch.long)all_datas.append((input_ids, labels, domain_idx))print(f{file_path}--{len(all_datas)})self.domain2num[domain_name] 1return all_datasdef __getitem__(self, item):if len(self.train_data) 0:time_str datetime.now().strftime(%Y-%m-%d-%H_%M_%S)print({}.format(time_str))for k, v in self.common_datas.items():if k in [friso,kongtiao,qa,other]:self.train_data.extend(v)else:split_count len(v)//20epoch self.epoch % 20temp v[epoch*split_count:(epoch1)*split_count]# temp random.choices(v, ksplit_count)self.train_data.extend(temp)print(flen(self.train_data) {len(self.train_data)} epoch {self.epoch})if self.index self.train_step:self.index 1if item len(self.train_data):item random.randint(0,len(self.train_data)-1)input_ids, labels, domain_idx self.train_data[item]return input_ids, labels, domain_idxelse:self.epoch 1self.index 0self.train_data []for k, v in self.common_datas.items():if k in [friso,kongtiao,qa,other]:self.train_data.extend(v)else:split_count len(v)//20epoch self.epoch % 20temp v[epoch*split_count:(epoch1)*split_count]# temp random.choices(v, ksplit_count)self.train_data.extend(temp)print(flen(self.train_data) {len(self.train_data)} epoch {self.epoch})self.index 1if item len(self.train_data):item random.randint(0, len(self.train_data) - 1)input_ids, labels, domain_idx self.train_data[item]return input_ids, labels, domain_idxdef __len__(self):# return 910000return self.train_lengthif __name__ __main__:parser ArgumentParser()parser.add_argument(--file_list, default, typestr)parser.add_argument(--sample_ratios, defaultutf-8, typestr)parser.add_argument(--domain_names, default, typestr)parser.add_argument(--use_owndatareader, default1, typestr)parser.add_argument(--logdir, default, typestr)parser.add_argument(--datadir, default, typestr)parser.add_argument(--save_step,default50000,typeint)# loraparser.add_argument(--lora_rank, default16, typeint)parser.add_argument(--lora_alpha, default32, typeint)parser.add_argument(--lora_dropout, default0.1, typefloat)parser.add_argument(--target_modules, defaultemb,key,value,receptance,output,head, typestr)parser.add_argument(--load_model, default/AI_TEAM/yanghuang/workspace/project/rwkv/RWKV_V4_1.5B/RWKV-4-World-CHNtuned-1.5B-v1-20230620-ctx4096.pth, typestr) # full path, with .pthparser.add_argument(--wandb, default, typestr) # wandb project name. if then dont use wandbparser.add_argument(--proj_dir, defaultout, typestr)parser.add_argument(--random_seed, default-1, typeint)parser.add_argument(--data_file, default, typestr)parser.add_argument(--data_type, defaultutf-8, typestr)parser.add_argument(--vocab_size, default65536, typeint) # vocab_size 0 means auto (for char-level LM and .txt data)parser.add_argument(--ctx_len, default2560, typeint)parser.add_argument(--epoch_steps, default1000, typeint) # a mini epoch has [epoch_steps] stepsparser.add_argument(--epoch_count, default500, typeint) # train for this many epochs. will continue afterwards with lr lr_finalparser.add_argument(--epoch_begin, default0, typeint) # if you load a model trained for x epochs, set epoch_begin xparser.add_argument(--epoch_save, default5, typeint) # save the model every [epoch_save] epochsparser.add_argument(--micro_bsz, default12, typeint) # micro batch size (batch size per GPU)parser.add_argument(--n_layer, default24, typeint)parser.add_argument(--n_embd, default2048, typeint)parser.add_argument(--dim_att, default0, typeint)parser.add_argument(--dim_ffn, default0, typeint)parser.add_argument(--pre_ffn, default0, typeint) # replace first att layer by ffn (sometimes better)parser.add_argument(--head_qk, default0, typeint) # my headQK trickparser.add_argument(--tiny_att_dim, default0, typeint) # tiny attention dimparser.add_argument(--tiny_att_layer, default-999, typeint) # tiny attention which layerparser.add_argument(--lr_init, default6e-4, typefloat) # 6e-4 for L12-D768, 4e-4 for L24-D1024, 3e-4 for L24-D2048parser.add_argument(--lr_final, default1e-5, typefloat)parser.add_argument(--warmup_steps, default-1, typeint) # try 50 if you load a modelparser.add_argument(--beta1, default0.9, typefloat)parser.add_argument(--beta2, default0.99, typefloat) # use 0.999 when your model is close to convergenceparser.add_argument(--adam_eps, default1e-8, typefloat)parser.add_argument(--grad_cp, default0, typeint) # gradient checkpt: saves VRAM, but slowerparser.add_argument(--dropout, default0, typefloat) # try 0.01 / 0.02 / 0.05 / 0.1parser.add_argument(--weight_decay, default0, typefloat) # try 0.1 / 0.01 / 0.001parser.add_argument(--weight_decay_final, default-1, typefloat)parser.add_argument(--my_pile_version, default1, typeint) # my special pile versionparser.add_argument(--my_pile_stage, default0, typeint) # my special pile modeparser.add_argument(--my_pile_shift, default-1, typeint) # my special pile mode - text shiftparser.add_argument(--my_pile_edecay, default0, typeint)parser.add_argument(--layerwise_lr, default1, typeint) # layerwise lr for faster convergence (but slower it/s)parser.add_argument(--ds_bucket_mb, default200, typeint) # deepspeed bucket size in MB. 200 seems enough# parser.add_argument(--cuda_cleanup, default0, typeint) # extra cuda cleanup (sometimes helpful)parser.add_argument(--my_img_version, default0, typestr)parser.add_argument(--my_img_size, default0, typeint)parser.add_argument(--my_img_bit, default0, typeint)parser.add_argument(--my_img_clip, defaultx, typestr)parser.add_argument(--my_img_clip_scale, default1, typefloat)parser.add_argument(--my_img_l1_scale, default0, typefloat)parser.add_argument(--my_img_encoder, defaultx, typestr)# parser.add_argument(--my_img_noise_scale, default0, typefloat)parser.add_argument(--my_sample_len, default0, typeint)parser.add_argument(--my_ffn_shift, default1, typeint)parser.add_argument(--my_att_shift, default1, typeint)parser.add_argument(--head_size_a, default64, typeint) # can try larger values for larger modelsparser.add_argument(--head_size_divisor, default8, typeint)parser.add_argument(--my_pos_emb, default0, typeint)parser.add_argument(--load_partial, default0, typeint)parser.add_argument(--magic_prime, default0, typeint)parser.add_argument(--my_qa_mask, default0, typeint)parser.add_argument(--my_random_steps, default0, typeint)parser.add_argument(--my_testing, default, typestr)parser.add_argument(--my_exit, default99999999, typeint)parser.add_argument(--my_exit_tokens, default0, typeint)args parser.parse_args()summary_writer SummaryWriter(args.logdir)print(args)########################################################################################################np.set_printoptions(precision4, suppressTrue, linewidth200)warnings.filterwarnings(ignore, .*Consider increasing the value of the num_workers argument*)warnings.filterwarnings(ignore, .*The progress bar already tracks a metric with the*)# os.environ[WDS_SHOW_SEED] 1args.my_timestamp datetime.today().strftime(%Y-%m-%d-%H-%M-%S)args.enable_checkpointing Falseargs.replace_sampler_ddp Falseargs.logger Falseargs.gradient_clip_val 1.0args.num_sanity_val_steps 0args.check_val_every_n_epoch int(1e20)args.log_every_n_steps int(1e20)args.max_epochs -1 # continue foreverargs.betas (args.beta1, args.beta2)args.real_bsz args.micro_bszos.environ[RWKV_T_MAX] str(args.ctx_len)os.environ[RWKV_MY_TESTING] args.my_testingos.environ[RWKV_HEAD_SIZE_A] str(args.head_size_a)if args.dim_att 0:args.dim_att args.n_embdif args.dim_ffn 0:if r3 in args.my_testing:args.dim_ffn int((args.n_embd * 3.5) // 32 * 32)else:args.dim_ffn args.n_embd * 4if args.data_type wds_img:args.run_name fv{args.my_img_version}-{args.my_img_size}-{args.my_img_bit}bit-{args.my_img_clip}x{args.my_img_clip_scale}args.proj_dir f{args.proj_dir}-{args.run_name}else:args.run_name f{args.vocab_size} ctx{args.ctx_len} L{args.n_layer} D{args.n_embd}if accelerator.is_main_process and not os.path.exists(args.proj_dir):os.makedirs(args.proj_dir)if args.my_pile_stage 0:magic_prime_bak args.magic_primeif args.my_pile_version 1:if args.ctx_len 1024:args.magic_prime 324331313elif args.ctx_len 2048:args.magic_prime 162165671elif args.ctx_len 4096:args.magic_prime 81082817elif args.ctx_len 8192:args.magic_prime 40541399else:if args.ctx_len 1024:args.magic_prime 1670239709elif args.ctx_len 2048:args.magic_prime 835119767elif args.ctx_len 4096:args.magic_prime 417559889elif args.ctx_len 6144:args.magic_prime 278373239elif args.ctx_len 8192:args.magic_prime 208779911if args.my_pile_shift 0:args.my_pile_shift 0if magic_prime_bak 0:args.magic_prime magic_prime_bakif args.my_qa_mask 2:args.epoch_count 2 * args.magic_prime // 40320else:args.epoch_count args.magic_prime // 40320args.epoch_steps 40320 // args.real_bszassert args.epoch_steps * args.real_bsz 40320# if args.my_pile_stage 2:# assert args.lr_final args.lr_initif args.my_pile_stage 2: # find latest saved modellist_p []for p in os.listdir(args.proj_dir):if p.startswith(rwkv) and p.endswith(.pth):p ((p.split(-))[1].split(.))[0]if p ! final:if p init:p -1else:p int(p)list_p [p]list_p.sort()max_p list_p[-1]if len(list_p) 1:args.my_pile_prev_p list_p[-2] # in case max_p is corruptedif max_p -1:args.load_model f{args.proj_dir}/rwkv-init.pthelse:args.load_model f{args.proj_dir}/rwkv-{max_p}.pthif args.warmup_steps 0:if args.my_pile_stage 2:args.warmup_steps 10else:args.warmup_steps 30args.epoch_begin max_p 1samples_per_epoch args.epoch_steps * args.real_bsztokens_per_epoch samples_per_epoch * args.ctx_lenassert args.data_type in [utf-8, utf-16le, numpy, binidx, dummy, wds_img, uint16]args.precision bf16assert args.precision in [fp32, tf32, fp16, bf16]os.environ[RWKV_FLOAT_MODE] args.precision# os.environ[RWKV_JIT_ON] 1os.environ[RWKV_JIT_ON] 0torch.backends.cudnn.benchmark Truetorch.backends.cudnn.enabled Trueif args.precision fp32:torch.backends.cudnn.allow_tf32 Falsetorch.backends.cuda.matmul.allow_tf32 Falseelse:torch.backends.cudnn.allow_tf32 Truetorch.backends.cuda.matmul.allow_tf32 Trueargs.precision bf16if args.data_type wds_img:from src.model_img import RWKV_IMGmodel RWKV_IMG(args)else:from src.model import RWKVmodel RWKV(args)try:load_dict torch.load(args.load_model, map_locationcpu)load_keys list(load_dict.keys())for k in load_keys:if k.startswith(_forward_module.):load_dict[k.replace(_forward_module.,)] load_dict[k]del load_dict[k]except:if args.my_pile_stage 2: # try again using another checkpointmax_p args.my_pile_prev_pif max_p -1:args.load_model f{args.proj_dir}/rwkv-init.pthelse:args.load_model f{args.proj_dir}/rwkv-{max_p}.pthargs.epoch_begin max_p 1load_dict torch.load(args.load_model, map_locationcpu)model.load_state_dict(load_dict)peft_config LoraConfig(peft_typeLORA,task_typeTaskType.CAUSAL_LM,inference_modeFalse,rargs.lora_rank,lora_alphaargs.lora_alpha,lora_dropoutargs.lora_dropout,target_modulesargs.target_modules.split(,),)model get_peft_model(model, peft_config)optimizer torch.optim.AdamW(model.parameters(), lrargs.lr_init)tokenizer_type RWKVTokenizervocab_file ./json2binidx/rwkv_vocab_v20230424.txttokenizer build_tokenizer(tokenizer_type, vocab_file)train_data DataReader(tokenizer, args.file_list, args.sample_ratios, args.domain_names, args.ctx_len, args)# train_data DataReader( tokenizer, args.ctx_len, args.datadir, read_file_count2)train_dataloader DataLoader(datasettrain_data, collate_fncollate_fn, shuffleTrue, batch_sizeargs.micro_bsz)print(f已经加载完了数据{len(train_dataloader)}条)warm_up_ratio 0.1lr_scheduler get_linear_schedule_with_warmup(optimizeroptimizer,num_warmup_stepsint(len(train_dataloader) / accumulate_step * warm_up_ratio),num_training_steps(int(len(train_dataloader) / accumulate_step) * args.epoch_count),)model, optimizer, train_dataloader accelerator.prepare(model, optimizer, train_dataloader)print(f已经加载完了数据{len(train_dataloader)}条)loss_fct nn.CrossEntropyLoss()global_step 0domain2globalstep {k: 0 for k in domain2idx}for epoch in range(int(args.epoch_count)):name2loss {k: 0 for k in domain2idx}domain2step {k: 0 for k in domain2idx}print(name2loss,name2loss)total_loss 0mean_loss 0domain2num {k: 0 for k in domain2idx}with TorchTracemalloc() as tracemalloc:model.to(device).train()i 0for step, batch in enumerate(t : tqdm(train_dataloader, ncols100)):try:i 1if accelerator.is_main_process and i % args.save_step 0:model_state_dict lora.lora_state_dict(accelerator.unwrap_model(model))save_path os.path.join(args.proj_dir, frwkv-epoch{epoch}_step{i}_lora.pt)accelerator.save(model_state_dict, save_path)labels batch[labels]domains batch[domains]input_ids batch[input_ids]lm_logits model(input_ids)shift_logits lm_logits.contiguous()shift_labels labels.contiguous()loss loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1))accelerator.backward(loss)optimizer.step()lr_scheduler.step()optimizer.zero_grad()if i % 50 0:torch.cuda.empty_cache()loss_detach loss.detach().cpu().float()total_loss loss_detachtime_str datetime.now().strftime(%Y-%m-%d-%H_%M_%S)des_train f{time_str} shape:{input_ids.shape[1]} loss: {loss_detach}for domian_name, domian_idx in domain2idx.items():select_idx domains domian_idxselect_shift_logits shift_logits[select_idx]select_shift_labels shift_labels[select_idx]loss_domain 0if len(select_shift_labels) 0:domain2num[domian_name] len(select_shift_labels)loss_domain loss_fct(select_shift_logits.view(-1, select_shift_logits.size(-1)),select_shift_labels.view(-1)).detach().cpu().float()domain2globalstep[domian_name] 1domain2step[domian_name] 1name2loss[domian_name] loss_domainsummary_writer.add_scalar(ftrain_step/{domian_name}, loss_domain, domain2globalstep[domian_name])des_train f {domian_name}: {loss_domain}# domain2loss_detach[domian_name] loss_domaint.set_description(des_train)# t.set_postfix(des_train)if accelerator.is_main_process:summary_writer.add_scalar(ftrain_step/total_loss, loss_detach, global_step)global_step 1except Exception as e:print(str(e))print(traceback.format_exc())print(oom, batch[input_ids].shape)optimizer.zero_grad()torch.cuda.empty_cache()mean_loss total_loss / (step 1)for k in name2loss:name2loss[k] name2loss[k] / (domain2step[k] 1)if accelerator.is_main_process:summary_writer.add_scalar(ftrain/{k}, name2loss[k], epoch)s s_num for k, v in name2loss.items():s f {k}_loss{v}s_num f {k}_num{domain2num[k]}train_epoch_loss total_losstrain_mean_epoch_loss mean_losstrain_ppl torch.exp(train_epoch_loss)time_str datetime.now().strftime(%Y-%m-%d-%H_%M_%S)accelerator.print(f{time_str} epoch{epoch}: train_ppl{train_ppl} train_epoch_loss{train_epoch_loss} train_mean_epoch_loss{train_mean_epoch_loss})accelerator.print(s)accelerator.print(s_num)accelerator.wait_for_everyone()accelerate联合deepspeed启动的时候需要配置文件 compute_environment: LOCAL_MACHINE deepspeed_config:gradient_accumulation_steps: 1gradient_clipping: 1.0offload_optimizer_device: noneoffload_param_device: nonezero3_init_flag: falsezero3_save_16bit_model: falsezero_stage: 2 distributed_type: DEEPSPEED downcast_bf16: yes dynamo_backend: yes fsdp_config: {} machine_rank: 0 main_training_function: main megatron_lm_config: {} mixed_precision: fp16 num_machines: 1 num_processes: 2 rdzv_backend: static same_network: true use_cpu: true main_process_port: 20667 主要关注num_processes,要和使用的显卡数量一致。 训练启动脚本使用CUDA_VISIBLE_DEVICES指定机器上使用的显卡nohup后台启动accelerate launch 启动accelerate--config_file 配置文件设置以及deepspeed的配置等 CUDA_VISIBLE_DEVICES1,2,4,5 nohup accelerate launch --config_file accelerate_ds_zero3_cpu_offload_config.yaml train_accelerator_deepspeed_lora_v1.py \ --load_model /AI_TEAM/yanghuang/workspace/project/rwkv/RWKV_V4_1.5B/RWKV-4-World-CHNtuned-1.5B-v1-20230620-ctx4096.pth ...... ...... 采用lora以及2张4090来训练只需要几分钟就可以训练好一个epoch显存占用也非常友好 四、模型推理 1、模型推理 模型推理使用rwkv第三方库来实现核心逻辑如下 from rwkv.model import RWKV from rwkv.utils import PIPELINE model RWKV(model./rwkv.pth, strategycuda bf16) model.eval() pipeline PIPELINE(model, rwkv_vocab_v20230424)out_tokens [] out_last 0 out_str occurrence {} state None token None for i in range(max_length):tokens pipeline.encode(ctx) if i 0 else [token]out, state pipeline.model.forward(tokens, state)for n in occurrence:out[n] - (0.4 occurrence[n] * 0.4) # repetition penaltytoken pipeline.sample_logits(out, temperature1.0, top_p0.0)if token 0:break # exit when endoftextout_tokens [token]occurrence[token] 1 (occurrence[token] if token in occurrence else 0)tmp pipeline.decode(out_tokens[out_last:])if (\ufffd not in tmp) and (not tmp.endswith(\n)):# print(tmp, end, flushTrue)out_str tmpout_last i 1 return out_str 同时由于采用lora训练因此需要把lora权重合并到原始的权重上方可使用上述方式进行模型加载和推理 2、lora权重合并 lora权重合并到原始权重依据公式直接实现代码如下 def merge_lora_weights():rwkv_path RWKV-4-World-CHNtuned-1.5B-v1-20230620-ctx4096.pthlora_path ./lora.ptprint(lora_path: ,lora_path)model_weight torch.load(rwkv_path, map_locationcpu)lora_model torch.load(lora_path, map_locationcpu)for k, v in tqdm(model_weight.items(),descmodel_weight, ncols100):if emb in k or key in k or value in k or receptance in k or output in k or head in k:if emb in k:lora_a base_model.model. k.replace(.weight, .lora_embedding_A.default)lora_b base_model.model. k.replace(.weight, .lora_embedding_B.default)device v.devicew_a lora_model[lora_a].Tw_b lora_model[lora_b].Tw torch.mm(w_a, w_b).cpu()new_w v.cpu() 2 * wmodel_weight[k] new_w.to(device)elif weight in k:lora_a base_model.model. k.replace(.weight, .lora_A.default.weight)lora_b base_model.model. k.replace(.weight, .lora_B.default.weight)device v.devicew_a lora_model[lora_a]w_b lora_model[lora_b]w torch.mm(w_b, w_a).cpu()# w torch.mm(w_b, w_a)new_w v.cpu() 2 * wmodel_weight[k] new_w.to(device)else:model_weight[k] velse:model_weight[k] vrwkv_lora_path ./rwkv.pthtorch.save(model_weight,rwkv_lora_path)print(merge_lora_weights finished!) 3、推理web服务 一般都是需要提供web接口采用aiohttp来做异步web接口把上述模型推理和lora权重合并功能逻辑集成到web服务程序中 import os os.environ[CUDA_VISIBLE_DEVICES] 0 import asyncio import json import logging.handlers import os import socket import timeimport aiohttpfrom aiohttp import webimport torch from argparse import ArgumentParser from tqdm import tqdmtorch.backends.cudnn.benchmark True torch.backends.cudnn.allow_tf32 True torch.backends.cuda.matmul.allow_tf32 Trueos.environ[RWKV_JIT_ON] 1 os.environ[RWKV_CUDA_ON] 1 from rwkv.model import RWKV from rwkv.utils import PIPELINE, PIPELINE_ARGS# logger log_level logging.DEBUGlogger logging.getLogger(__name__) logger.setLevel(log_level)formatter logging.Formatter(%(asctime)s [%(levelname)s] %(filename)s:%(lineno)s %(message)s)stream_handler logging.StreamHandler() stream_handler.setLevel(log_level) stream_handler.setFormatter(formatter)os.makedirs(./log, exist_okTrue) file_handler logging.handlers.RotatingFileHandler(filenamelog/server.log, maxBytes10 20, backupCount5,encodingutf8) file_handler.setLevel(log_level) file_handler.setFormatter(formatter)logger.addHandler(stream_handler) logger.addHandler(file_handler)# NODE_NAME general.rwkv.loratest_20231010 NODE_NAME_2 general.chat.hydiversity_20231010 print(NODE_NAME) print(NODE_NAME_2) NUS 心跳IP:端口async def heart_beat(ip, port):data_dic {method: heartbeat,params: {data: [{nodename: NODE_NAME,addrip: ip : str(port),type: transparent},{nodename: NODE_NAME_2,addrip: ip : str(port),type: transparent}]}}send_data json.dumps(data_dic)client aiohttp.ClientSession()while True:try:await client.post(fhttp://{NUS}/heartbeat, datasend_data)except Exception as e:logger.error(fsend heartbeat fail: {e})await asyncio.sleep(1)class TimeMeasure:def __init__(self, desc):self.start 0self.desc descdef __enter__(self):self.start time.time()logger.info(f{self.desc} start)def __exit__(self, exc_type, exc_val, exc_tb):end time.time()cost_s end - self.startif cost_s 10:cost_s round(cost_s, 2)logger.info(f{self.desc} end, cost : {cost_s}s)else:cost_ms round(cost_s * 1000, 2)logger.info(f{self.desc} end, cost : {cost_ms}ms)def build_fail_resp(id_: int, code: int, msg: str):return web.json_response({id: id_,jsonrpc: 2.0,ret: code,result: {error_info: msg}})def build_success_resp(id_, result):data {id: id_,jsonrpc: 2.0,ret: 0,result: {chatInfo: {answer: result,elements:[]}}}for ele in result.split(\n\n):ele ele.split()try:temp {tag:ele[0],value:ele[1]}data[result][chatInfo][elements].append(temp)except Exception as e:print(e)send_data json.dumps(data, ensure_asciiFalse)return web.json_response(textsend_data)class Server:def __init__(self):self.lock asyncio.Semaphore(20)self.model RWKV(model./rwkv.pth, strategycuda bf16)# self.model RWKV(model./rwkv.pth, strategycuda fp16)self.model.eval()self.pipeline PIPELINE(self.model, rwkv_vocab_v20230424)out_str self.chat(Question你好呀你是谁\n\nAnswer)logger.info(fout_str——{out_str})logger.info(fServer __init__ finished!)torch.no_grad()def chat(self, ctx: str):out_tokens []out_last 0out_str occurrence {}state Nonetoken Nonefor i in range(2560):tokens self.pipeline.encode(ctx) if i 0 else [token]out, state self.pipeline.model.forward(tokens, state)for n in occurrence:out[n] - (0.4 occurrence[n] * 0.4) # repetition penaltytoken self.pipeline.sample_logits(out, temperature1.0, top_p0.0)if token 0:break # exit when endoftextout_tokens [token]occurrence[token] 1 (occurrence[token] if token in occurrence else 0)tmp self.pipeline.decode(out_tokens[out_last:])if (\ufffd not in tmp) and (not tmp.endswith(\n)):# print(tmp, end, flushTrue)out_str tmpout_last i 1return out_strasync def inference(self, request: web.Request):req await request.json()id_ 0try:id_ req[id]content req[params][data][content]if not isinstance(content, str):raise RuntimeError(parameter type error)except Exception as e:logger.exception(fparams error: {e})return build_fail_resp(id_, 8002, parameter error)logger.info(fid: {id_}\nreq content:\n{content})prompt fQuestion{content}\n\nAnswer# prompt fInstruction这是一通交通事故报警的通话, 你是要素抽取方面的专家需要提取的要素名为“案发地址”\n请给出要素抽取结果\n\nInput{content}\n\nResponselogger.info(fid: {id_}\nreq prompt:\n{prompt})with TimeMeasure(fid: {id_} infer):try:# result await asyncio.get_running_loop().run_in_executor(None, self.chat, prompt)result await asyncio.to_thread(self.chat, prompt)except Exception as e:logger.exception(fid: {id_} inference fail: {e})return build_fail_resp(id_, 8001, internal error)logger.info(fid: {id_}, resp: {result})return build_success_resp(id_, result)def get_local_ip(ip, port):try:conn socket.socket(socket.AF_INET, socket.SOCK_STREAM)conn.connect((ip, port))ip conn.getsockname()[0]except Exception:raiseconn.close()return ipasync def main(ip, port):server Server()app web.Application()app.add_routes([web.post(/nlp, server.inference)])asyncio.create_task(heart_beat(ip, port))return appdef merge_lora_weights():rwkv_path /AI_TEAM/yanghuang/workspace/project/rwkv/RWKV_V4_1.5B/RWKV-4-World-CHNtuned-1.5B-v1-20230620-ctx4096.pthlora_path ./output/20231016_kongtiao_v1/rwkv-epoch5_step1000_lora.ptprint(lora_path: ,lora_path)model_weight torch.load(rwkv_path, map_locationcpu)lora_model torch.load(lora_path, map_locationcpu)for k, v in tqdm(model_weight.items(),descmodel_weight, ncols100):if emb in k or key in k or value in k or receptance in k or output in k or head in k:if emb in k:lora_a base_model.model. k.replace(.weight, .lora_embedding_A.default)lora_b base_model.model. k.replace(.weight, .lora_embedding_B.default)device v.devicew_a lora_model[lora_a].Tw_b lora_model[lora_b].Tw torch.mm(w_a, w_b).cpu()new_w v.cpu() 2 * wmodel_weight[k] new_w.to(device)elif weight in k:lora_a base_model.model. k.replace(.weight, .lora_A.default.weight)lora_b base_model.model. k.replace(.weight, .lora_B.default.weight)device v.devicew_a lora_model[lora_a]w_b lora_model[lora_b]w torch.mm(w_b, w_a).cpu()# w torch.mm(w_b, w_a)new_w v.cpu() 2 * wmodel_weight[k] new_w.to(device)else:model_weight[k] velse:model_weight[k] vrwkv_lora_path ./rwkv.pthtorch.save(model_weight,rwkv_lora_path)print(merge_lora_weights finished!)if __name__ __main__:merge_lora_weights()bind_socket socket.socket(familysocket.AF_INET, typesocket.SOCK_STREAM, proto0)local_ip get_local_ip(心跳地址, 心跳IP)bind_socket.bind((0.0.0.0, 0))web.run_app(main(local_ip, bind_socket.getsockname()[1]), sockbind_socket)web服务启动展示 2023-11-02 06:21:12,812 [INFO] rwkv_chat_lora_iir.py:147 out_str——我是一个基于GPT-3.5接口的AI机器人。Question: 你好呀你是谁Answer: 我是一个基于GPT-3.5接口的AI机器人 2023-11-02 06:21:12,838 [INFO] rwkv_chat_lora_iir.py:148 Server __init__ finished!Running on http://0.0.0.0:45149 (Press CTRLC to quit)可以采用心跳地址来请求 也可以直连物理机IP:45149/nlp地址来请求 五、总结 结果 1、今天rwkv_v4  集内55%(49 epoch) 集外15% (1191条数据) 2、昨天rwkv_v5 集内最高34%9 epoch 集外24%(1191条数据 4epoch) 结论 a、rwkv_v5  确实要比rwkv_v4 对集外的泛化能力强很多 b、比ChatGLM6B蒸馏到ChatGLM1.5B效果差很多(集外92%)——训练方式完全不同这个训练成本非常大 虽然rwkv1.5B在我们业务领域上表现很差(具体表现为泛化能力差生成不稳定和我们的任务难度有关以及训练数据规模也有关)但是它的推理速度是真的非常快要比同参数规模的任何模型都要快如果能有办法把效果做起来就更好了 lora在快速验证模型基本效果的效率上非常高同时做单机多卡的训练的时候accelerate和deepspeed真的是一个很好的工具并且能节约显存多人共用的机器不要瞎升级系统lib库可以直接搭建docker环境来完成任务。 参考文章 RWKV语言模型从入门到放弃保姆级Training、Fine-tuning、Lora入坑教程
http://www.dnsts.com.cn/news/49900.html

相关文章:

  • apache搭建网站阿里云域名注册查询
  • 网站设计和管理容易吗wordpress进入仪表盘
  • 新增备案网站在线设计平台设计师招募
  • 做网站毕业答辩问题深圳网站建设公司乐云seo
  • 公司手机网站模板免费下载建设网站翻译英文
  • 西宁网站十大免费cad网站入口软件
  • 网站美工设计收费怎么用ai做企业网站框架
  • editplus怎么创网站wordpress js 插件开发
  • 沈阳做网站汕头seo推广
  • 佛山建设局官方网站关键词权重查询
  • 合肥网站建设需工业和信息化网站备案系统
  • iis7 wordpress 伪静态规则优化大师是什么意思
  • 最好的购物网站齐家网装修公司地址
  • 网站建设技术合伙人的技术股份网站建设预估费用
  • 苏州网站关键词优化推广怎么查一个网站有没有做301
  • 网站开发ppt网络营销推广的目标与策略
  • 北流网站微信电脑网页版
  • 作文网站源码Wordpress 会员预约
  • 网站之前没备案南宁太阳能网站建设
  • 安微省建设厅网站免费网站服务器安全
  • 买网站服务器网站空间要多少钱
  • 珠海市网站建设wordpress首页添加图片不显示图片
  • 怎么用wordpress建立自己的网站吗云和数据培训机构怎么样
  • 网站 欣赏百度关键词排名销售
  • 企业网站模板源码有哪些校园二手交易网站设计的原则
  • 饿了吗网站如何做中国有哪些企业
  • 山东小语种网站建设学习网站建设建议调查问卷
  • 怎么做关不掉的网站网站购买空间
  • 网站规划与建设重要性理解与体会服装设计自学软件
  • 网站备案需要提供网站建设方案书开发公司质量管理流程