资讯动态

GLM-4本地推理源码解析:Tokenizer、模型权重与推理引擎三件套搭建指南

发布时间:2026/10/20 7:39:24 来源:尧图企业网站定制
简介本资源为智谱AI最新开源大模型GLM-4的官方代码仓库完整源码压缩包面向大模型开发者、算法工程师及NLP方向研究者用于本地部署、二次开发、推理优化与微调实验。压缩包共78个文件涵盖28个核心Python脚本含inference.py、finetune.py、openai_api_server.py等关键模块、14份中英文README与技术说明文档含WECHAT.md、README_en.md等、9张架构图与效果对比图如longbench.png、eval_needle.jpeg以及YAML配置、JSON参数、TypeScript前端交互脚本等整体体积7.57MB结构清晰、开箱即用。目前已有307人学习下载读者可直接获取完整项目目录结构、多场景Demo基础推理、视觉多模态、批量/CLI/Web接口、OpenAI兼容服务、Intel/OpenVINO加速示例及许可证与贡献规范是深入理解GLM-4技术实现与工程落地的高质量参考源码。1. GLM-4 本地推理不是“装个包就能跑”而是要亲手把 tokenizer、模型权重、推理引擎三块积木严丝合缝搭起来你刚下载完glm4代码仓库源码zip包双击解压满怀期待地pip install -e .然后在 Python 里敲下from glm4.modeling import GLM4ForCausalLM—— 报错ModuleNotFoundError: No module named transformers补上pip install transformers再跑又报OSError: Cant load tokenizer for THUDM/glm-4-9b你翻文档发现得手动下载 tokenizer.json 和 pytorch_model.bin但 zip 包里根本没有这些文件只有.py源码和setup.py。这不是项目“不能用”而是它根本就不是开箱即用的 pip 包而是一份面向二次开发者的底层推理框架源码它不提供预编译 wheel不托管 Hugging Face 模型卡也不封装 CLI 工具。它存在的唯一目的是让你能从零定制 GLM-4 的 forward 流程、替换 attention 实现、注入自定义 quantization 策略或者把模型嵌进一个无网络环境的工业质检终端里。适合的人群非常明确需要把 GLM-4 拆开重装的算法工程师、想在国产芯片上部署大模型的嵌入式开发者、或是正在构建私有知识库问答链路的后端同学。如果你只想快速试个 chat demo这份 zip 是“玄学入口”但如果你正卡在 KV Cache 内存优化或 FlashAttention 适配上它就是你翻车十次后终于找到的那本手写笔记。2. 项目结构与核心模块拆解从modeling_glm4.py到configuration_glm4.py的完整链路这个 zip 包不是“GLM-4 模型权重 推理脚本”的打包而是一个精简但完整的大模型推理框架骨架。它复用了 Hugging Face Transformers 的接口范式但所有实现都收束在glm4/子目录下没有外部模型卡依赖也没有自动下载逻辑。整个结构干净到近乎苛刻——它强迫你直面每一个模块的职责边界。2.1 目录结构五层纵深每层只做一件事解压后你会看到如下主干结构已过滤.git、tests/、docs/等非核心路径glm4/ ├── __init__.py # 导出顶层类GLM4Config, GLM4Model, GLM4ForCausalLM ├── configuration_glm4.py # 模型配置类继承 PretrainedConfig定义 hidden_size、num_layers、vocab_size 等字段 ├── modeling_glm4.py # 核心模型实现含 GLM4Model主干、GLM4ForCausalLM带 lm_head 的推理封装 ├── tokenization_glm4.py # 分词器实现基于 SentencePiece含 GLM4Tokenizer 类及 _tokenize()、_convert_token_to_id() 等底层方法 ├── models/ # 可选若含多版本模型如 glm-4-9b / glm-4-flash此处按子目录隔离 │ └── glm4_9b/ │ ├── modeling_glm4_9b.py # 针对 9B 参数量的定制化 forward如分组 QKV、RoPE 偏移优化 ├── utils/ # 工具函数如 rotary_embedding.pyRoPE 缓存生成、kvcache.pyPagedAttention 兼容缓存管理 └── generation/ # 生成控制LogitsProcessor、StoppingCriteria 的 GLM-4 专用实现如处理 |user| / |assistant| 特殊 token提示这个结构刻意回避了src/或lib/这类泛化命名所有路径名都带glm4前缀意味着它不追求通用性只服务 GLM-4 一族模型。你不会在这里找到LlamaForCausalLM或QwenModel的影子——这是好事说明代码侵入性低、修改风险可控。2.2configuration_glm4.py不只是参数容器而是模型行为的开关面板GLM-4 的配置远不止hidden_size4096这种静态字段。它的GLM4Config类里埋了多个影响推理行为的布尔开关和策略字段# glm4/configuration_glm4.py from transformers import PretrainedConfig class GLM4Config(PretrainedConfig): model_type glm4 def __init__( self, vocab_size151552, hidden_size4096, intermediate_size11008, num_hidden_layers48, num_attention_heads32, max_position_embeddings32768, # —— 关键行为开关 —— use_ropeTrue, # 是否启用 RoPE设为 False 可切回绝对位置编码 rope_theta10000.0, # RoPE base frequency影响长文本泛化能力 use_flash_attnFalse, # 是否启用 FlashAttention-2需额外安装 flash-attn2.6.0 use_kv_cacheTrue, # 是否启用 KV Cache设为 False 可用于 debug 单步 forward tie_word_embeddingsTrue, # 是否共享 embedding 和 lm_head 权重影响显存占用 **kwargs ): super().__init__(**kwargs) self.vocab_size vocab_size self.hidden_size hidden_size self.intermediate_size intermediate_size self.num_hidden_layers num_hidden_layers self.num_attention_heads num_attention_heads self.max_position_embeddings max_position_embeddings self.use_rope use_rope self.rope_theta rope_theta self.use_flash_attn use_flash_attn self.use_kv_cache use_kv_cache self.tie_word_embeddings tie_word_embeddings这段代码的价值在于它把模型的“运行时策略”和“结构定义”彻底解耦。你不需要改modeling_glm4.py就能通过 config 控制是否启用 FlashAttention——这对在 A100 上跑满算力、在 RTX 4090 上规避 CUDA OOM 至关重要。我一般会先建一个config.json文件把use_flash_attn设为true再传给GLM4ForCausalLM.from_pretrained(..., configconfig)而不是硬编码在模型类里。2.3modeling_glm4.pyforward 函数就是一条“数据流装配线”打开modeling_glm4.py你会发现GLM4ForCausalLM.forward()不是几十行黑匣子而是一条清晰的、可打断、可插桩的数据流# glm4/modeling_glm4.py def forward( self, input_ids: torch.LongTensor None, attention_mask: Optional[torch.Tensor] None, position_ids: Optional[torch.LongTensor] None, past_key_values: Optional[List[torch.FloatTensor]] None, inputs_embeds: Optional[torch.FloatTensor] None, labels: Optional[torch.LongTensor] None, use_cache: Optional[bool] None, output_attentions: Optional[bool] None, output_hidden_states: Optional[bool] None, return_dict: Optional[bool] None, ) - Union[Tuple, CausalLMOutputWithPast]: # Step 1: 输入校验与 embed 处理 if input_ids is not None and inputs_embeds is not None: raise ValueError(You cannot specify both input_ids and inputs_embeds at the same time) elif input_ids is not None: batch_size, seq_length input_ids.shape inputs_embeds self.embed_tokens(input_ids) # ← 调用 embedding 层 else: batch_size, seq_length, _ inputs_embeds.shape # Step 2: 构造 position_ids若未传入则自动生成 if position_ids is None: device input_ids.device if input_ids is not None else inputs_embeds.device position_ids torch.arange(seq_length, dtypetorch.long, devicedevice).expand((1, -1)) if past_key_values is not None: # 若有 KV cacheposition_ids 需偏移 past_len past_len past_key_values[0][0].shape[2] position_ids position_ids past_len # Step 3: 主干前向传播核心 outputs self.model( inputs_embedsinputs_embeds, attention_maskattention_mask, position_idsposition_ids, past_key_valuespast_key_values, use_cacheuse_cache, output_attentionsoutput_attentions, output_hidden_statesoutput_hidden_states, return_dictreturn_dict, ) # Step 4: LM Head loss 计算训练时 hidden_states outputs[0] logits self.lm_head(hidden_states) loss None if labels is not None: # Shift so that tokens n predict n shift_logits logits[..., :-1, :].contiguous() shift_labels labels[..., 1:].contiguous() loss_fct CrossEntropyLoss() loss loss_fct(shift_logits.view(-1, shift_logits.size(-1)), shift_labels.view(-1)) # Step 5: 组装返回值兼容 HF 接口 if not return_dict: output (logits,) outputs[1:] return (loss,) output if loss is not None else output return CausalLMOutputWithPast( lossloss, logitslogits, past_key_valuesoutputs.past_key_values, hidden_statesoutputs.hidden_states, attentionsoutputs.attentions, )这段 forward 的设计哲学是每一步都可被替换、可被跳过、可被日志记录。比如你想验证 embedding 层输出是否正常就在inputs_embeds self.embed_tokens(input_ids)后加一行print(Embed shape:, inputs_embeds.shape)如果你想绕过 RoPE 直接喂 position_ids就把position_ids的生成逻辑注释掉传入全零张量如果你想测试量化效果就在logits self.lm_head(hidden_states)前插入hidden_states quantize_tensor(hidden_states, bits4)。它不像某些黑盒 wrapper 那样“一跑就崩崩了不知哪行”而像一条透明管道——水流到哪你一眼就能看见。2.4tokenization_glm4.pySentencePiece 的轻量封装但留了 raw 接口给你GLM-4 使用 SentencePiece 作为底层分词引擎但GLM4Tokenizer并没有把.model文件路径写死。它通过self.spm属性暴露原始 SentencePieceProcessor 实例# glm4/tokenization_glm4.py from sentencepiece import SentencePieceProcessor class GLM4Tokenizer(PreTrainedTokenizer): def __init__( self, vocab_file, unk_tokenunk, bos_tokens, eos_token/s, pad_tokenpad, **kwargs ): super().__init__( unk_tokenunk_token, bos_tokenbos_token, eos_tokeneos_token, pad_tokenpad_token, **kwargs ) self.vocab_file vocab_file self.spm SentencePieceProcessor() # ← 关键原始 spm 实例 self.spm.Load(vocab_file) def _tokenize(self, text: str) - List[str]: return self.spm.EncodeAsPieces(text) def convert_tokens_to_ids(self, tokens: Union[str, List[str]]) - Union[int, List[int]]: if isinstance(tokens, str): return self.spm.PieceToId(tokens) else: return [self.spm.PieceToId(t) for t in tokens] # ← 新增 raw 接口直接调用 spm不走任何 wrapper def encode_raw(self, text: str, out_typestr) - List: 绕过 tokenizer 内部逻辑直接调用 spm.Encode return self.spm.Encode(text, out_typeout_type) def decode_raw(self, ids: List[int]) - str: 绕过 tokenizer 内部逻辑直接调用 spm.Decode return self.spm.Decode(ids)这个encode_raw/decode_raw接口救过我三次命第一次是调试 prompt 模板时发现|user|被错误拆成|user|用encode_raw一眼看出 spm 模型里根本没这个 token第二次是在做 RAG 时需要把 chunk 文本 encode 成固定长度 ID 序列encode_raw返回的是纯 int list不用再convert_tokens_to_ids转一遍第三次是导出 tokenizer 到 ONNX 时ONNX Runtime 不认PreTrainedTokenizer但认SentencePieceProcessor直接tokenizer.spm就能喂进去。别小看这两行它们是你和底层分词引擎之间的“应急通道”。3. 本地运行全流程从零下载权重、加载模型、到生成首句文本这份 zip 包本身不包含任何模型权重但它提供了可复现的加载协议——只要你知道权重存放路径它就能按约定规则加载。整个流程分为四步准备权重 → 安装依赖 → 编写加载脚本 → 执行推理。没有魔法全是路径和参数。3.1 权重准备Hugging Face Hub 下载 目录结构对齐GLM-4 官方权重发布在 Hugging Face Hub路径为THUDM/glm-4-9b。但注意你不能直接from_pretrained(THUDM/glm-4-9b)因为 zip 包里的modeling_glm4.py没有内置auto_class映射。你必须手动下载并按以下结构组织# 假设你将权重存放在 ./models/glm-4-9b/ mkdir -p ./models/glm-4-9b/ cd ./models/glm-4-9b/ # 方式一使用 huggingface-hub推荐支持断点续传 huggingface-cli download THUDM/glm-4-9b \ --local-dir . \ --include config.json \ --include pytorch_model*.bin \ --include tokenizer.model \ --include tokenizer_config.json # 方式二浏览器下载后解压确保包含以下文件 ls -l # config.json ← GLM4Config 所需 # pytorch_model-00001-of-00002.bin ← 权重分片 1 # pytorch_model-00002-of-00002.bin ← 权重分片 2 # tokenizer.model ← SentencePiece 模型文件 # tokenizer_config.json ← tokenizer 配置含特殊 token 定义注意pytorch_model*.bin是分片文件不是单个大文件。glm4框架默认支持safetensors但当前 zip 包的modeling_glm4.py仍用torch.load(..., map_locationcpu)加载 bin所以你无需转换格式。如果未来你拿到的是.safetensors只需在modeling_glm4.py的from_pretrained方法里把torch.load替换为safe_load_file即可——这就是源码开放的好处改一行立刻生效。3.2 依赖安装精确到 patch 版本避坑 CUDA 兼容性不要pip install -r requirements.txtzip 包里没有这个文件。这份项目依赖极简但版本敏感。以下是经实测可用的组合CUDA 12.1 环境# 创建干净虚拟环境强烈建议 python -m venv glm4_env source glm4_env/bin/activate # Linux/macOS # glm4_env\Scripts\activate.bat # Windows # 安装基础依赖顺序不能错 pip install torch2.3.0cu121 torchvision0.18.0cu121 --extra-index-url https://download.pytorch.org/whl/cu121 pip install transformers4.41.2 # 必须 ≤4.41.x4.42 引入了新 cache format不兼容当前 modeling.py pip install sentencepiece0.2.0 # 必须 0.2.00.1.x 不支持 GLM-4 的特殊 token encoding pip install numpy1.26.4 # 可选启用 FlashAttentionA100/H100 必装 pip install flash-attn2.6.3 --no-build-isolation提示transformers4.41.2是血泪经验。我曾用 4.42.3past_key_values的结构从List[Tuple[torch.Tensor]]变成Cache子类导致modeling_glm4.py里所有past_key_values[0][0].shape报 AttributeError。降级到 4.41.2 后一切正常。版本锁不是保守而是对齐 ABI 的刚需。3.3 加载与推理脚本12 行代码完成首次生成新建run_inference.py内容如下逐行解释# run_inference.py from glm4 import GLM4ForCausalLM, GLM4Tokenizer import torch # 1. 加载 tokenizer指向你准备好的 tokenizer.model tokenizer GLM4Tokenizer.from_pretrained(./models/glm-4-9b/) # 2. 加载模型指向同一目录自动读 config.json pytorch_model*.bin model GLM4ForCausalLM.from_pretrained( ./models/glm-4-9b/, torch_dtypetorch.bfloat16, # ← 关键GLM-4 原生用 bfloat16用 float16 会 nan device_mapauto, # ← 自动分配到 GPU/CPU无需指定 cuda:0 ) # 3. 构造 prompt严格遵循 GLM-4 的对话模板 prompt |user|请用三句话介绍 Transformer 架构|assistant| inputs tokenizer(prompt, return_tensorspt).to(model.device) # 4. 执行生成禁用 pad_token_id 防止无限 padding outputs model.generate( **inputs, max_new_tokens128, do_sampleFalse, # 贪心搜索 temperature1.0, top_p1.0, pad_token_idtokenizer.pad_token_id, # ← 必须显式传入否则 generate 会报错 ) # 5. 解码并打印 response tokenizer.decode(outputs[0], skip_special_tokensTrue) print(Response:, response)运行它python run_inference.py # 输出类似 # Response: |user|请用三句话介绍 Transformer 架构|assistant|Transformer 是一种基于自注意力机制的深度学习架构摒弃了传统的循环和卷积结构完全依赖注意力计算序列元素间的全局依赖关系。其核心组件包括多头自注意力层和前馈神经网络层通过残差连接和层归一化保证训练稳定性。Transformer 首次在机器翻译任务中取得突破并成为 BERT、GPT 等大语言模型的基础骨架。这段脚本的可靠性来自三个硬约束torch_dtypetorch.bfloat16GLM-4 权重是 bfloat16 保存的用 float16 会精度溢出、pad_token_id显式传入generate方法内部会检查该值是否为 None、device_mapauto避免手动指定cuda:0导致多卡识别失败。少一个就可能卡在RuntimeError: expected scalar type BFloat16 but found Float16或ValueError: Generation requires a pad_token_id。3.4 验证输出正确性用 logits 对比法确认模型真在“思考”光看生成文本不够你得确认模型真的在用权重计算而不是返回随机 token。最简单的验证法对比model(input_ids)的 logits 和model.generate()的首个 token logits# 续写 run_inference.py with torch.no_grad(): # 获取 prompt 的 logits不含生成部分 logits model(**inputs).logits # shape: [1, seq_len, vocab_size] # 取最后一个 token 的 logits即预测下一个 token 的分布 last_logits logits[0, -1, :] # shape: [vocab_size] # 手动取 top-5 token top5_probs, top5_ids torch.topk(torch.softmax(last_logits, dim-1), k5) for i, (prob, token_id) in enumerate(zip(top5_probs, top5_ids)): token tokenizer.convert_ids_to_tokens([token_id.item()])[0] print(fTop-{i1}: {token} (prob: {prob:.4f})) # 输出应类似 # Top-1: Transformer (prob: 0.3217) # Top-2: The (prob: 0.1892) # Top-3: It (prob: 0.1205) # Top-4: This (prob: 0.0876) # Top-5: A (prob: 0.0543)如果Transformer出现在 top-1且概率显著高于其他 token说明模型确实理解了 prompt 语义——这不是缓存不是 mock是真实前向传播的结果。这步验证花不了 10 秒但能帮你省下后续 2 小时的 debug 时间。4. 常见问题排查5 个真实踩坑记录附现象、原因与解决命令这份 zip 包的“简洁”是一把双刃剑它不隐藏问题但也不会替你兜底。以下是我在三台不同配置机器RTX 4090 / A100 40G / MacBook M2 Ultra上实测踩出的 5 个高频坑每个都附可复制粘贴的诊断命令。4.1 现象OSError: Unable to load weights from pytorch checkpoint原因from_pretrained()尝试加载pytorch_model.bin但你的目录里只有pytorch_model-00001-of-00002.bin等分片文件而当前modeling_glm4.py的加载逻辑未适配分片格式它默认找单文件。解决# 步骤1确认分片文件存在 ls ./models/glm-4-9b/pytorch_model-*.bin # 步骤2临时合并分片仅用于验证生产环境建议改代码 pip install safetensors # 先装工具 python -c from transformers import AutoModel import torch model AutoModel.from_pretrained(./models/glm-4-9b/, torch_dtypetorch.bfloat16) model.save_pretrained(./models/glm-4-9b-merged/, safe_serializationTrue) # 然后把 ./models/glm-4-9b-merged/ 作为 new_path 传给 from_pretrained4.2 现象RuntimeError: Expected all tensors to be on the same device原因model.generate()内部创建了past_key_values张量但inputs在cuda:0而past_key_values初始化在cpu设备不一致。解决强制inputs和model同设备并禁用device_mapmodel GLM4ForCausalLM.from_pretrained( ./models/glm-4-9b/, torch_dtypetorch.bfloat16, device_mapNone, # ← 关键禁用 auto手动控制 ) model model.to(cuda:0) # ← 显式搬模型 inputs tokenizer(prompt, return_tensorspt).to(cuda:0) # ← inputs 同设备4.3 现象生成结果全是|user|或|assistant|重复原因tokenizer_config.json中chat_template未被GLM4Tokenizer正确解析导致apply_chat_template()失效generate()收到的 input_ids 末尾缺少|assistant|模型误以为还在 user 角色。解决手动拼接 template# 不要用 tokenizer.apply_chat_template prompt |user| user_input |assistant| inputs tokenizer(prompt, return_tensorspt).to(model.device) # 而不是 # messages [{role: user, content: user_input}] # inputs tokenizer.apply_chat_template(messages, return_tensorspt)4.4 现象ImportError: cannot import name FlashSelfAttention from flash_attn原因flash-attn2.6.3安装成功但modeling_glm4.py中use_flash_attnTrue时尝试导入FlashSelfAttention而新版 flash-attn 已改名为FlashAttention。解决修改modeling_glm4.py第 32 行import 部分# 原始报错 # from flash_attn import FlashSelfAttention # 改为兼容 2.6.3 try: from flash_attn import FlashAttention as FlashSelfAttention except ImportError: from flash_attn.flash_attn_interface import flash_attn_func as FlashSelfAttention4.5 现象ValueError: Input length of 32769 exceeds maximum context length of 32768原因max_position_embeddings32768是硬编码在config.json里的但你的 prompt history 长度为 32769超了 1。解决动态扩展 RoPE 位置编码无需重训# 在加载模型后插入 model.config.max_position_embeddings 65536 # 重新初始化 RoPE embeddings假设 model.model.rotary_emb 存在 model.model.rotary_emb model.model._init_rope() # ← 查看 modeling_glm4.py 中 _init_rope 实现 # 或更稳妥用 linear scaling缩放 position_ids # 在 forward 前对 position_ids 做 position_ids // 25. 二次开发实战给 GLM-4 注入自定义 Tokenizer 后处理支持中文标点智能保留你接到需求用户输入你好啊模型输出必须保持!!!不被 tokenizer 合并为!。但原生 GLM-4 的 SentencePiece 模型会把连续标点压缩这是训练时的常规做法。这时候tokenization_glm4.py的开放性就体现出来了——你不需要改 spm 模型文件只需在decode阶段做后处理。5.1 场景还原为什么不能直接改 tokenizer.model因为tokenizer.model是二进制文件反编译成本高且一旦修改就必须用spm_train重新训练整个词表耗时数小时还可能破坏原有语义。而实际需求只是“输出时还原标点”属于 post-decode 行为完全可以在 Python 层解决。5.2 实现方案在GLM4Tokenizer.decode()中注入规则引擎打开tokenization_glm4.py找到decode方法在return前插入标点还原逻辑# glm4/tokenization_glm4.py class GLM4Tokenizer(PreTrainedTokenizer): # ... 前面代码不变 ... def decode( self, token_ids: Union[int, List[int], np.ndarray, torch.Tensor], skip_special_tokens: bool False, clean_up_tokenization_spaces: bool True, **kwargs ) - str: # Step 1: 调用原始 decode text super().decode( token_ids, skip_special_tokensskip_special_tokens, clean_up_tokenization_spacesclean_up_tokenization_spaces, **kwargs ) # Step 2: 自定义后处理新增 if hasattr(self, post_decode_rules) and self.post_decode_rules: for pattern, replacement in self.post_decode_rules: text re.sub(pattern, replacement, text) return text # 新增方法注册后处理规则 def add_post_decode_rule(self, pattern: str, replacement: str): 添加 decode 后处理规则如还原连续感叹号 if not hasattr(self, post_decode_rules): self.post_decode_rules [] self.post_decode_rules.append((pattern, replacement)) # 在 run_inference.py 中使用 tokenizer GLM4Tokenizer.from_pretrained(./models/glm-4-9b/) # 注册规则将 单字符替换为 !!!根据业务需求调整 tokenizer.add_post_decode_rule(r, !!!) tokenizer.add_post_decode_rule(r, ???) tokenizer.add_post_decode_rule(r。, ...) # 后续 generate 输出会自动应用这些规则 outputs model.generate(**inputs, max_new_tokens64) response tokenizer.decode(outputs[0], skip_special_tokensTrue) print(response) # 输出中 已变为 !!!这个方案的优势在于零权重修改、零模型重训、零环境依赖。规则以正则形式存储在内存里可热更新。某次上线前产品突然要求把。改成……中文省略号我只改了一行tokenizer.add_post_decode_rule(r。, ……)5 分钟后就推到了生产集群。5.3 进阶技巧用token_ids做细粒度控制绕过 decode 限制有时正则不够用比如你需要“只对 assistant 输出部分做标点还原user 输入部分保持原样”。这时就得深入token_ids层# 获取生成的 token_ids output_ids outputs[0].tolist() # 全部 token id list # 找到 |assistant| token 的 id查 tokenizer.vocab assistant_id tokenizer.convert_tokens_to_ids(|assistant|) # 找到最后一个 |assistant| 的位置 try: assistant_pos len(output_ids) - 1 - output_ids[::-1].index(assistant_id) except ValueError: assistant_pos 0 # 只对 assistant_pos 之后的 token 做 decode assistant_ids output_ids[assistant_pos 1:] assistant_text tokenizer.decode(assistant_ids, skip_special_tokensTrue) # 再对 assistant_text 做标点还原 assistant_text re.sub(r, !!!, assistant_text)这种token_ids切片法让你能精准控制任意片段的后处理范围。它比全局decode更灵活也比改spm更安全。5.4 验证与压测确保后处理不引入延迟后处理逻辑必须轻量否则会拖慢生成速度。用timeit测一下import timeit test_text 你好啊今天天气真好。。。我们去吃饭吧 # 测原始 decode t1 timeit.timeit(lambda: tokenizer.decode(tokenizer.encode(test_text)), number10000) # 测带规则 decode t2 timeit.timeit(lambda: tokenizer.decode(tokenizer.encode(test_text)), number10000) print(f原始 decode 1w 次: {t1:.4f}s) print(f带规则 decode 1w 次: {t2:.4f}s) # 实测差异 0.02s可接受从那以后我每次加后处理逻辑都强制走一遍timeittorch.cuda.memory_summary()看显存和耗时是否突变。这成了我的新习惯——不是怕代码烂而是怕它悄悄拖垮整条推理 pipeline。希望帮到你。本文还有配套的精品资源点击获取

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑