主题
第30章 包接口与测试:怎样知道自己运行的是同一份模型
Notebook适合逐步观察,包适合稳定导入。两者共存时,最常见的错误是修改了一份代码,却从另一份代码导入同名函数。本章沿着包接口、测试契约和环境记录,建立可追溯的运行方式。
30.1 模块名相同,不代表实现细节永远相同
固定仓库的 pkg/llms_from_scratch 将章节代码整理为可导入模块。ch02提供滑窗数据集;ch03提供注意力的逐步版本和PyTorch优化版本;ch04提供基础GPT与Fast变体;ch05提供生成、训练、权重转换和下载;ch06是分类;ch07是指令数据和评估;appendix_a/d/e分别承接自动微分入门、训练改进和LoRA。包README
本书对重复算法使用明确回指:ch02→第3章;ch03→第4–5章;ch04→第6章;ch05→第13–15章;ch06→第24章;ch07→第25–26章;appendix_a→第2章;appendix_d→第17章;appendix_e→第28章。Fast变体的契约是保持输入输出意义同时更换注意力内核,并非“函数名相似就已证明等价”。
llama3.py、qwen3.py 提供独立模型、tokenizer及权重映射,结构解释见第19–20章;kv_cache 和 kv_cache_batched 提供有状态推理路径,见第8章。调用者要确认自己导入的是无缓存、单一缓存还是批量缓存版本,尤其是position状态、reset和batch中长度不同时的处理。
30.2 包额外承担的边界
generate.trim_input_tensor 为生成预留位置。固定实现的保留长度为 max(1, C-max(0,N-1)),因为最后生成的token不需要再输入一次forward。所以N>0时可以保留C−N+1个提示token,让最后一次forward的输入位置仍在C内;最终返回序列可能比C多一个token。这个细节解释了为什么“输出总长度≤C”的保守UI规则与此辅助函数不是逐字相同的契约。N>C会拒绝,N=0只把过长提示截到C。源码
utils.import_definitions_from_notebook 提取import和函数/类定义,便于测试Notebook中的实现,不执行整份训练流程。仍需注意:import本身可有副作用,函数默认参数和类体也可能执行代码。因此它适用于已审阅、可信的源码,不能作为运行任意上传Notebook的安全沙箱。download_file 是外部数据边界,必须独立记录URL、目标路径、版本与校验,而不是把下载混在纯函数测试里。
30.3 测试应该说明它证明了什么
基础测试分别检查ToyDataset、滑窗、MHA形状、GPT变体、小型训练、分类、指令和LoRA。通过一个shape测试不能推出梯度正确;通过一次loss下降不能推出泛化。模型与库参考实现的对比还需统一权重、dtype、dropout、mask、tokenizer和容差。
缓存测试尤其值得逐条读:test_kv_cache_gpt2.py 覆盖显式cache是否激活、第二步是否继续、缓存/无缓存生成是否一致、重复生成是否重置位置、stream与普通生成是否一致、截短提示的位置和零生成预算。test_kv_cache_context_limits.py 进一步检查超长提示的错误、被拒绝decode后cache是否保持原状态、批量中每条样本是否都检查、预算大但提前EOS结束的路径。错误路径不破坏状态,和正常路径输出正确同样重要。
Qwen3测试区分稠密与MoE、RoPE、RMSNorm、缓存和批量缓存、特殊token round-trip、chat包装、多轮前缀稳定性及外部参考权重等价;Llama3测试覆盖RoPE、GQA、RMSNorm、快慢变体与参考权重。部分测试需要可选依赖或权重,不能把跳过误记成通过。本书这组微调实验没有运行这些全部上游测试,逐文件映射记录的是阅读与教学覆盖,不是执行覆盖。
30.4 环境检查与持续集成
setup/02_installing-python-libraries/python_environment_check.py 的 get_packages、get_requirements_dict、check_packages 分别读取已安装包、解析需求、比较版本;Notebook版展示同一检查。版本满足最低要求并不保证所有设备数值一致,因此运行日志还需Python、PyTorch、设备、dtype和线程数。
原生Python、uv、pixi与Docker是不同环境管理路线;选择一条并固定依赖,不要同时在系统Python和虚拟环境混装。VS Code扩展配置帮助编辑,不会代替Python依赖。SageMaker的CloudFormation模板属于可选云资源部署,不是运行本书CPU测试的必要条件;部署会涉及资源与费用,本书未创建任何云资源。setup目录
.github/workflows 对Linux、macOS、Windows、pip、uv、pixi、旧PyTorch、候选PyTorch和最新支持Python安排不同检查。它们体现兼容性目标,不证明任意组合都支持。required-checks.yml 根据改动文件类型决定要求哪些工作流,并核对当前PR的head与merge SHA;不能让旧提交的绿勾替新提交背书。retry-required-checks.yml 处理汇总检查的重试,但重试不会修复真实测试失败。
链接脚本 check_links.py 提取Markdown/HTML链接,区分本地路径与外部地址,配合自测验证解析和报错。双引号脚本 check_double_quotes.py 对Python和Notebook检查风格,还处理f-string内部字符串位置。拼写、PEP8与链接检查负责文档和代码卫生,不证明数学正确;数值断言、数据审计与人工审校仍需各自承担责任。
30.5 这份交付怎样复核
本组自包含实验是 examples/finetuning/contracts.py,没有外部模型下载。另有 check_upstream_functions.py,只从固定可信源码提取已审阅的少量函数,实测近重复空字段反例、DPO平均/空mask、附录E缩放和trim边界;它需要该固定源码目录,不执行Notebook的下载或训练单元。固定seed=20261014,CPU单线程,默认float64,实际环境Python3.12.14、PyTorch2.6.0+cpu。运行结果在 qa/finetuning/contracts.json;它包括正例和会失败的朴素写法,不把上游训练日志当作本书结果。
| 故障现象 | 先验证 | 不应直接做的事 |
|---|---|---|
| embedding越界 | token ID范围、词表扩展、−100是否进输入 | 调低学习率 |
| loss为NaN | 有效监督数、log稳定性、空batch | 把NaN强改0 |
| load_state_dict不匹配 | 头维度、bias、层数、上下文 | 随意strict=False隐藏问题 |
| 同输入结果变化 | eval、采样、共享状态、随机数 | 认定权重已损坏 |
| OOM | batch、序列、激活、优化器状态 | 只看可训练参数计数 |
| import来自旧文件 | module.file、环境、editable安装 | 反复改另一个Notebook |
本书不自动安装项目全部依赖;使用者可在独立环境安装与记录的版本匹配的CPU依赖,再运行脚本。根目录requirements、pyproject、pixi和troubleshooting说明整体安装与排错,阅读时应区分“历史固定代码所需环境”与“当前在线最新版本”。
练习与答案
补充题:shape与数值等价测试各抓什么错?答案:前者抓接口维度、轴顺序等明显问题;后者在相同输入权重下抓转置、mask、缩放与状态差异。二者均不能单独证明任务质量。
补充题:一个需要下载权重的测试被skip,应写“全部通过”吗?答案:应写通过数量、跳过数量和跳过条件。本书只对实际运行的人工CPU断言声明通过。