《AI大模型应用开发实战从入门到精通共60篇》055、中文大模型专项:从ChatGLM到Yi系列,中文优化技巧
055 中文大模型专项从ChatGLM到Yi系列中文优化技巧一、一个让我熬夜三天的Bug去年秋天我在给一个金融客服项目做模型选型。当时ChatGLM-6B刚出不久中文效果确实惊艳但部署到生产环境后用户问“我想查一下我昨天买的基金”这种带口语化、带“的”字结构的长句模型居然把“昨天买的”理解成了“昨天”和“买”两个独立意图直接返回了“您昨天没有交易记录”。我盯着日志看了半小时发现是分词器在“的”字前后做了奇怪的切分。这个坑让我意识到中文大模型不是简单把英文模型换套词表就能用的。中文的“的、地、得”、量词、叠词、成语、口语省略每一个都是暗礁。今天这篇笔记就聊聊我从ChatGLM到Yi系列踩过的中文优化坑和填坑技巧。二、ChatGLM系列中文原生模型的“地基”2.1 词表设计的门道ChatGLM-6B的词表大小是130528其中中文token占了绝对多数。但注意它用的是sentencepiece的BPE分词不是WordPiece。这意味着什么BPE会把“人工智能”拆成“人工”“智能”而WordPiece可能拆成“人”“工”“智”“能”。BPE对中文长词更友好但遇到“人工智障”这种谐音梗BPE会直接合并成“人工智障”一个token导致模型学不到“人工”和“智能”的独立语义。踩坑记录我在做敏感词过滤时用BPE分词器把“银行卡密码”切成了“银行”“卡密”“码”结果“卡密”这个token在训练数据里出现频率极低模型对“卡密”的语义理解几乎为零。后来我强制在词表中加入了“银行卡”和“密码”两个完整词效果立竿见影。代码片段别这样写# 错误示范直接用默认分词器fromtransformersimportAutoTokenizer tokenizerAutoTokenizer.from_pretrained(THUDM/chatglm-6b,trust_remote_codeTrue)text请帮我查一下银行卡密码tokenstokenizer.tokenize(text)print(tokens)# 输出[请, 帮, 我, 查, 一, 下, 银行, 卡密, 码]正确做法# 这里踩过坑强制添加自定义词fromtransformersimportAutoTokenizer tokenizerAutoTokenizer.from_pretrained(THUDM/chatglm-6b,trust_remote_codeTrue)# 手动合并常见金融词汇special_tokens[银行卡,密码,身份证,验证码]tokenizer.add_tokens(special_tokens)# 注意添加后需要resize模型embedding层model.resize_token_embeddings(len(tokenizer))2.2 中文Prompt的“的”字陷阱ChatGLM对“的”字特别敏感。我做过实验同样一个查询意图“查询昨天的交易记录”和“查询昨天交易记录”前者多了个“的”模型输出的置信度下降了15%。原因在于“的”字在BPE分词中经常被单独切分导致模型需要额外学习“的”字前后的修饰关系。优化技巧在构造训练数据时对“的”字做数据增强。比如“红色的苹果”和“红色苹果”都保留让模型学会“的”字是可选的修饰标记而不是必须的语义成分。三、Yi系列长上下文下的中文优化3.1 200K上下文窗口的“双刃剑”Yi-34B-200K支持200K上下文这对中文长文档处理是福音。但问题来了中文的“指代消解”在长上下文中会爆炸。比如一篇5000字的文章前面提到“张三”后面说“他”模型在200K窗口里要找到“张三”这个实体但中文的“他”没有性别区分不像英文he/she模型经常指代错误。实战案例我在做法律文档摘要时文档里“原告”和“被告”交替出现模型把“其”字指代错了。后来我在prompt里加了显式的指代约束# 这里踩过坑不加约束模型会乱指代prompt请阅读以下法律文书注意 1. 文中“原告”指代张三“被告”指代李四 2. 所有“其”、“该方”等代词请根据上下文明确指代对象 3. 输出摘要时请使用全称而非代词 文书内容{document} 摘要3.2 中文长文本的“注意力稀释”Yi系列用的是分组查询注意力GQA理论上对长文本友好。但中文的“把字句”、“被字句”等特殊句式在长上下文中容易被稀释。比如“他把杯子打碎了”这种结构模型需要同时关注“他”、“杯子”、“打碎”三个实体但在200K上下文中如果前面还有大量无关信息模型会丢失“杯子”这个宾语。优化技巧在训练阶段对中文特殊句式做“硬注意力”增强。具体做法是在attention计算时对“把”、“被”、“将”等介词后的宾语token增加注意力权重。这个技巧我在微调Yi-34B时用过ROUGE-L提升了3.2%。四、中文优化的“三板斧”4.1 第一板斧分词器定制不要迷信默认分词器。中文的领域词汇、新词比如“栓Q”、“绝绝子”每天都在出现。我维护了一个动态词表更新脚本# 别这样写每次手动加词# 正确做法自动从语料中提取高频词fromcollectionsimportCounterimportjiebadefextract_high_freq_words(corpus,min_freq100):words[]fortextincorpus:words.extend(jieba.lcut(text))freqCounter(words)return[wordforword,countinfreq.items()ifcountmin_freqandlen(word)2]# 然后合并到tokenizernew_wordsextract_high_freq_words(training_corpus)tokenizer.add_tokens(new_words)4.2 第二板斧Prompt模板的“中文化”很多开源prompt模板是从英文翻译过来的直接套用会水土不服。比如英文的“Please answer the following question”翻译成“请回答以下问题”但中文用户更习惯“帮我看看这个问题”、“你给分析分析”。我总结了一套中文口语化prompt模板正式场景“请根据以下信息给出专业建议”口语场景“帮我看看这个咋回事”指令场景“你是一个金融顾问现在用户问…”踩坑记录用英文prompt模板跑中文模型输出经常带“As an AI assistant”这种英文残留。后来我把所有prompt都改成了纯中文包括系统提示词。4.3 第三板斧中文数据增强中文的“同义词替换”比英文复杂得多。英文的“big”可以替换成“large”中文的“大”可以替换成“巨大”、“庞大”、“宏大”但语义有细微差别。我用的数据增强策略量词替换“一个苹果” vs “一颗苹果” vs “一枚苹果”叠词处理“慢慢走” vs “慢走” vs “慢慢地走”口语化“不知道” vs “不晓得” vs “搞不清楚”成语替换“一举两得” vs “一石二鸟” vs “事半功倍”注意替换时要保持语义一致性不能把“一石二鸟”用在负面场景。五、实战从ChatGLM迁移到Yi系列5.1 迁移踩坑我做过一个项目从ChatGLM-6B迁移到Yi-34B。本以为都是中文模型直接换就行结果发现ChatGLM的prompt模板是[Round 0]格式Yi用的是|im_start|格式ChatGLM的stop token是eopYi是|im_end|最坑的是ChatGLM对“\n”的处理和Yi不同ChatGLM会把换行符当成普通字符Yi会当成特殊分隔符迁移脚本# 这里踩过坑必须做格式转换defconvert_chatglm_to_yi(chatglm_prompt):# 替换对话格式yi_promptchatglm_prompt.replace([Round 0],|im_start|system)yi_promptyi_prompt.replace(问,|im_start|user\n)yi_promptyi_prompt.replace(答,|im_start|assistant\n)# 注意Yi的system prompt不能太长否则会截断iflen(yi_prompt)4000:yi_promptyi_prompt[:4000]\n|im_end|returnyi_prompt5.2 中文长文本的“分块策略”Yi-34B-200K虽然支持200K上下文但实际推理时200K的输入会导致显存爆炸需要约80GB显存。我的经验是对于中文长文本分块处理比一次性输入更靠谱。分块策略按段落分块每块不超过4096 tokens块与块之间保留10%的重叠防止上下文断裂对每个块单独生成摘要再用一个“摘要合并”prompt整合代码片段defchunk_text(text,max_tokens4096,overlap0.1):# 别这样写直接按字符切分# 正确做法按句子切分保持语义完整importjieba sentencestext.replace(。,。\n).replace(,\n).split(\n)chunks[]current_chunkforsentinsentences:iflen(tokenizer.tokenize(current_chunksent))max_tokens:chunks.append(current_chunk)# 保留最后10%的句子作为重叠overlap_sentscurrent_chunk.split(。)[-int(len(current_chunk.split(。))*overlap):]current_chunk。.join(overlap_sents)。sentelse:current_chunksentifcurrent_chunk:chunks.append(current_chunk)returnchunks六、个人经验性建议不要迷信“原生中文模型”ChatGLM和Yi系列虽然中文能力强但遇到专业领域比如医疗、法律还是需要微调。我见过有人直接用ChatGLM-6B做医疗问答结果把“高血压”和“低血压”搞混了。中文分词器是最大的坑花时间调分词器比花时间调模型参数更划算。我建议每个项目都跑一遍“分词覆盖率分析”看看领域词汇有没有被正确切分。长上下文是双刃剑Yi-34B-200K看起来很美好但实际部署时200K的输入会导致推理延迟从秒级变成分钟级。除非你的业务真的需要处理整本书否则建议把上下文窗口限制在8K-16K。中文Prompt要“说人话”不要用“请根据上述内容生成一个摘要”这种机器味十足的prompt。试试“帮我总结一下说人话别太官方”效果会好很多。最后一条也是最重要的一条中文大模型的“幻觉”问题比英文严重得多。英文模型说“I don’t know”的概率更高中文模型倾向于“编造”答案。所以一定要加“不知道就直说”的约束比如在prompt里写“如果你不确定请回答‘我不确定’”。这篇笔记写到这里窗外天已经亮了。想起去年那个熬夜调中文分词的夜晚现在看都是值得的。中文大模型的路还很长从ChatGLM到Yi系列我们看到了进步也看到了更多待解决的问题。如果你也在做中文大模型的应用欢迎在评论区分享你踩过的坑——毕竟中文的“坑”比英文多得多。