内容简介

《Python 在 AI-Infra:从语言机制到生产交付》是一组共七讲、九篇的系列文章(第一讲拆成上下两篇、第二讲拆成上中下三篇,其余各一篇),面向有后端工程经验(尤其是 Java 背景)、准备转向 AI-Infra 方向的工程师,系统梳理这个方向真正需要的 Python 能力:语言核心机制、类型系统与数据契约、并发与异步、反射与元编程、内存管理、测试与调试、以及工程化交付。

它同时是《AI 算法工程师学习地图》 L1 工具箱的深入篇:算法侧的《算法工程师的工具箱》只讲训练代码里用到的 Python 子集,把语言机制与运行时交给这里——两张地图共享本系列,算法方向的读者按需读。

这个系列不是 Python 语法教程,也不是技巧集合,而是试图回答一个问题:

在 AI-Infra 系统里,Python 并不承担最重的计算,那它到底承担什么?为此需要掌握它的哪些机制?1

答案是:真正吃算力的部分由 CUDA、C++、通信库和专用推理引擎完成,Python 承担的是组织、调度、扩展、观测和交付——它是整个系统的控制平面和胶水层。

这个定位决定了需要掌握的东西:不是”怎么写出更短的代码”,而是语法背后是哪些机制在起作用、类型如何被表达和强制、任务如何协作、系统如何扩展、内存如何被占用与回收、正确性如何保障、制品如何交付。

系列的逻辑链条是这样递进的:

代码是怎么运转的 → 代码怎么写得健壮 → 如何高效协作 → 如何扩展 → 内存如何优化 → 如何确保没有问题、出了问题如何定位 → 怎么交付

每一步对应一篇:

Python 系列七篇的主题与解决的问题
篇 主题 解决的问题
一 核心机制 代码是怎么运转的——模块如何被加载、对象如何被调用、一行语法背后是哪些协议在起作用
二 类型系统与数据契约 代码怎么写得健壮——类型如何表达、被谁消费,以及如何在系统边界上强制数据契约
三 并发与异步 如何高效协作——如何组织任务,让资源既不闲着也不打架
四 动态机制与架构实践 如何扩展——如何让系统容纳变化,又不失控
五 内存管理 内存如何优化——内存如何分配与回收,开销来自哪里,增长与泄漏如何定位
六 测试与调试 如何确保没有问题、出了问题如何定位——如何验证行为真的符合预期,以及故障发生时用哪些工具排查
七 工程化与交付 怎么交付——怎么把这些代码变成一个可以交付的东西

这条链条是有顺序依赖的:不理解 import 的副作用,就看不懂插件注册(一 → 四);不理解类型注解如何被运行时消费,就说不清 Pydantic 的校验从哪来(二 → 七的配置管理);不理解 GIL,就判断不了推理服务该起几个 worker(三 → 七的部署)。

前六篇解决”写对”,第七篇解决”交付”。

缩写

本系列的 24 个缩写(展开)
缩写全称一句话解释
ABC abstract base class 抽象基类,要求子类显式继承并实现抽象方法
ABI application binary interface 二进制接口,决定编译好的扩展能否直接加载
AOP aspect-oriented programming 面向切面编程,Java 靠动态代理在调用路径上插逻辑
AST abstract syntax tree 抽象语法树,源码解析后的树形结构
CGLIB Code Generation Library 运行时生成子类做代理的 Java 字节码库
DTO data transfer object 只装数据、在层与层之间传递的对象
GC garbage collection 垃圾回收,这里指回收循环引用的分代回收器
GIL global interpreter lock 全局解释器锁,同一时刻只有一个线程执行 Python 字节码
JDK Java Development Kit Java 开发工具包,这里指它自带的动态代理
JIT just-in-time compilation 运行时把热点字节码编译成机器码
JVM Java Virtual Machine Java 虚拟机,加载 .class 并解释或即时编译
LEGB Local, Enclosing, Global, Built-in 名称查找的四层顺序,编译期就决定了走哪一层
MRO method resolution order 方法解析顺序,决定 super() 和属性查找走哪个父类
OOM out of memory 内存或显存耗尽,进程被杀或报错
ORM object-relational mapping 对象关系映射,把数据库表映射成类,常用元类实现
PEP Python Enhancement Proposal Python 的改进提案,语言和生态规范都以它为准
PyPI Python Package Index 官方的 Python 包仓库,types-* 存根包也发在这里
ROCm Radeon Open Compute AMD 的 GPU 计算平台,对应 CUDA
RPC remote procedure call 远程过程调用,序列化后类型信息会丢失
RSS resident set size 进程实际占用的物理内存
sdist source distribution 源码分发包,安装时在本地构建
SPI Service Provider Interface Java 的插件发现机制,用 ServiceLoader 加载实现类
TGI Text Generation Inference Hugging Face 的大模型推理服务
venv virtual environment 标准库自带的虚拟环境,隔离一套解释器和依赖

为什么写这个系列?

Python 语法很容易,Python 工程不容易

一个有经验的 Java 工程师,学 Python 语法大概需要三天。真正的困难不在语法,而在两件事:

其一,带着 Java 的心智模型写 Python。 结果是代码能跑,但处处别扭,也无法读懂真实项目的源码:

  • 以为类型注解会在运行时生效(不会,除非有 Pydantic 这类消费者);
  • 用 interface 的思路理解一切抽象,看不懂 Protocol 为什么不需要继承;
  • 用线程池的思路理解并发,撞上 GIL 之后不知道为什么多线程没变快;
  • 以为 import 只是声明依赖,没意识到它可能触发算子注册和 CUDA 初始化;
  • 以为虚拟环境和 classpath 是一回事,然后在依赖冲突里挣扎。

其二,踩不到真正的坑,直到线上出问题。 Python 的灵活性把很多问题推迟到运行时:可变默认参数、闭包的延迟绑定、循环导入、__hash__ 被自动置 None、异步函数里的阻塞调用、缓慢的内存增长——这些在 code review 里很难发现,在小规模测试里也不暴露。

现有材料的断层

目前关于 Python 的材料,大致分布在几个彼此不衔接的层次:

  • 入门教程:讲完 list、dict、类和函数就结束,不涉及机制;
  • 技巧文章:孤立的 tips,读完不知道什么时候该用、代价是什么;
  • 官方文档:准确但缺少工程语境,不会告诉你”在什么场景下这个特性才值得用”;
  • Web 后端的工程实践:有价值,但 AI-Infra 的约束不同——GIL 与 GPU 的关系、几个 GB 的二进制依赖、长生命周期的推理任务、动辄几十 GB 的模型加载,这些是 Web 场景里不存在的问题。

这个系列的取法

以 Java 为参照系。 每个特性都会说明:它解决什么问题、怎么用、Java 中对应什么、在真实 AI-Infra 项目里长什么样。这种对照不是为了比较优劣,而是因为已有的知识是最好的脚手架——知道 Protocol 对应”不需要 implements 的 interface”,比单独记住它的语法有效得多。同时也会指出对照失效的地方:@dataclass 不是 Lombok(一个运行时 exec,一个编译期改 AST),X | None 不是 Optional<T>(一个纯注解,一个运行时包装对象)。

代码取自真实项目。 例子尽量来自 PyTorch、vLLM、FastAPI、Pydantic、httpx、SQLAlchemy 的源码,而不是造出来的 Foo/Bar。目标是读完之后能读懂这些项目,而不是只能通过练习题。

讲清代价,不只讲用法。 每个机制都会说明它的边界和成本:元类很强大但会让 mypy 失效、beartype 很方便但不能放在热路径、Pydantic 校验很彻底但每次实例化都要付费、__slots__ 省内存但收益取决于对象数量。知道什么时候不该用,比知道怎么用更重要。

适合哪些读者?

从后端转向 AI-Infra 的工程师

这是本系列的主要读者。适合有 Java、Go、C++ 后端经验,现在需要参与模型服务、推理框架、训练平台或 AI 中间件建设的工程师。

如果你能写出可用的 Python 代码,但希望搞清楚下面这些问题,本系列会比较合适:

  • 为什么 import 一个模块就能让后端注册生效?
  • 类型注解在运行时到底存不存在,Pydantic 是怎么用它做校验的?
  • GIL 到底在什么时候放开,多线程什么时候真的有用?
  • 为什么推理服务的 worker 数不能随便调大?
  • 一个插件系统应该用元类、装饰器还是 __init_subclass__?
  • Python 层的内存增长怎么定位?
  • 为什么本地跑得好的项目,装到别的机器上就 ImportError?

想读懂推理框架源码的开发者

适合准备阅读或贡献 vLLM、TGI、Ray Serve、PyTorch 这类项目的开发者。

这些项目大量使用 Python 的高级特性:Protocol 和 ABC 定义后端抽象、ParamSpec 保留装饰器签名、元类做模型注册、entry points 做插件发现、TypedDict 描述状态字典、contextvars 传递请求上下文。不熟悉这些机制时,源码会显得像天书——不是逻辑复杂,而是语言特性不认识。

算法与模型工程师

适合已经能训练和调用模型,但希望把代码从”能跑的脚本”提升为”可维护的服务”的工程师。

即使不做基础设施,理解这些也有直接价值:为什么 notebook 里的代码搬到服务里就出问题、为什么显存没释放、为什么依赖装不上、怎么让代码被别人可靠地复用。

希望系统补齐 Python 工程能力的开发者

适合已经用 Python 一段时间,但知识是零散积累的开发者——会用但不确定为什么这样用,遇到坑靠搜索解决,缺少一条串起来的主线。

章节结构与分章导读

1. 代码是怎么运转的:语言机制与运行时原理

第一篇沿着”一段代码的生命周期”这条线组织:执行模型与执行帧、作用域与闭包、模块与导入系统、类与对象模型(属性查找算法与描述符)、对象协议、装饰器、生成器、上下文管理器、异常处理,最后用一个推理组件把它们串起来完整追踪一遍。

这一篇的必要性在于:Python 里很多”看起来简单”的写法,背后都是可替换的机制。

五种常见写法背后的可替换机制
写法 背后的机制
import mypackage.backends 可能触发算子注册、插件发现、CUDA 扩展加载
model(x) 实际调用的是 __call__
with torch.inference_mode() 上下文管理协议
for batch in loader 迭代器与生成器协议
super().__init__() 并不简单等于”调用父类方法”,取决于 MRO

不理解这些机制,就只能把 AI-Infra 源码当成黑盒;理解之后,才能看出一个框架为什么这样设计。

这一篇也回答了一个高频故障:”为什么本地能跑,装到别的机器上就 ImportError“——答案在 sys.path 的构成、src 布局与 editable 安装的关系里。

2. 代码怎么写得健壮:类型系统与数据契约

第二篇讨论 Python 的类型系统与数据契约。核心洞察是:与 Java 把类型声明、编译检查、.class 携带类型、运行时反射合为一体不同,Python 把”提供类型信息”和”消费类型信息”拆成了两层。

  1. 提供层:注解语法、typing、typing_extensions .pyi 存根、typeshed、types-*、py.typed / PEP 561
  2. 消费层:静态:mypy、pyright(开发时检查)动态:isinstance、get_type_hints、@dataclass、Pydantic、beartype(运行时读取)
  3. 数据契约:@dataclass、BaseModel、TypedDict 序列化、JSON Schema、BaseSettings

这个拆分解释了很多困惑:为什么写了 x: int = "hello" 不报错(解释器不消费注解);为什么 @dataclass 读注解却不校验(它只把注解当字段清单);为什么 Pydantic 能做到校验(它在类创建时用元类构建了验证树)。

第三章落到工程实践:数据契约的设计。什么时候用 dataclass、什么时候上 Pydantic、TypedDict 适合什么,以及核心原则——在系统边界用 Pydantic 校验一次,内部传递零开销的 dataclass。

3. 如何高效协作:并发、异步与任务协作

第三篇处理并发。前提认知是:AI-Infra 里 Python 通常不做重计算,但吞吐、尾延迟和资源利用率很大程度上取决于 Python 是否正确组织了并发任务。

这一篇从 GIL 讲起——它是理解 Python 并发的前提,也是 Java 工程师最容易误判的地方。GIL 决定了:

  • 多线程对 CPU 密集任务无效,但对阻塞 I/O 有效(等待时会释放 GIL);
  • PyTorch、NumPy 在计算时会释放 GIL,所以”Python 慢”在这些场景下不成立;
  • 推理服务的 worker 数不能随便调大——每个 worker 是独立进程,会各自加载一份模型进显存。

之后按三种执行模型分章展开——线程(Java 工程师最熟悉的模型)、进程(绕开 GIL 的代价)、asyncio(单线程内的 M:N 调度)——每一章按同样的顺序讲:怎么启动任务、任务之间怎么协作(Future、有界队列与背压、同步原语、上下文传递)、这种模型的边界在哪(线程不能取消、进程只能 kill、协程可以协作式取消)。同一个概念会出现三次,后两次只讲差别。最后用批处理、流式、GPU 混合并发这些 AI-Infra 组合模式把三种模型拼在一起。

贯穿这一篇的是三个工程问题:瓶颈在 CPU、GPU、网络还是外部服务?任务之间如何协作?下游跟不上上游时,系统如何保持稳定?

4. 如何扩展:Python 的动态机制与插件化实践

第四篇讨论 Python 的动态能力。AI-Infra 系统必须应对持续变化——新模型结构、新硬件后端、新量化方式、新调度策略。硬编码的 if backend == "cuda" 很快会失控。

这一篇先纠正一个常见的概念混淆:反射、元编程和插件化不在同一个层次上。前两者是 Python 提供的语言机制,插件化则是利用这些机制、再叠加导入系统与注册发现能力所构建出的架构模式。所以文章分两部分:

  • 机制:反射(观察)、元编程(改造)、动态加载(导入)——getattr、inspect、装饰器、描述符、__init_subclass__、元类、importlib;
  • 应用:两个真实场景——插件化(注册表、三种发现机制、契约校验、生命周期、版本协商、热更新)和路由分发(签名绑定、启动期预编译)。

两个场景骨架完全一样,区别只在 key 的空间和热度,这也是这一篇想讲清楚的核心。

但重点不是”能做到什么”,而是边界在哪里。动态机制的代价是真实的:

  • 依赖关系静态工具发现不了;
  • 错误从启动期推迟到请求期;
  • 元类会让 mypy 无法推断;
  • 任意导入和任意属性访问是安全风险。

所以这一篇的落点是一组设计原则:稳定接口、动态实现;显式边界、有限动态;启动时失败而不是请求时失败;动态加载、静态验证。

5. 内存如何优化:分配机制、对象开销与泄漏定位

第五篇讨论内存。AI-Infra 的内存问题有其特殊性:它同时涉及 Python 对象、大块张量、CPU 与 GPU 两套地址空间,而且增长往往是缓慢的,直到 OOM 才暴露。

这一篇先建立机制层面的理解——引用计数、循环垃圾回收的分代策略、pymalloc 的分配器结构。这些机制解释了一类反直觉的现象:为什么对象已经不可达、gc.collect() 也执行了,进程的 RSS 却没有下降。答案在于 Python 的内存归还是分层的:对象归还给 pymalloc 的内存池,内存池未必归还给操作系统。

在此基础上讨论具体的开销来源:

  • 对象头与 __dict__ 的固定开销,以及 __slots__ 能省下多少、什么条件下才值得用;
  • 容器保存的是引用而非内联数据,因此 list[float] 与 NumPy 数组的内存特征完全不同;
  • 赋值、浅拷贝、深拷贝与视图(memoryview)的区别——这直接决定大张量是被复制还是被共享;
  • GPU 显存的度量方式:torch.cuda.memory_allocated() 与 memory_reserved() 的差别,以及为什么 nvidia-smi 看到的数字通常更大。

最后是定位方法:tracemalloc 做分配溯源,gc.get_referrers() 和 objgraph 回答”这个对象还被谁引用着”。

贯穿这一篇的判断是:内存问题多数不是”某处泄漏了”,而是”某处被意外长期持有了”。缓存、闭包、未回收的 Task、全局注册表都是常见的意外强引用来源,它们在语义上都是”合理的引用”,只是生命周期比预期长。

6. 如何确保没有问题、出了问题如何定位:测试与调试

第六篇讨论正确性保障。AI-Infra 代码的正确性往往不能靠阅读判断:逻辑正确但时序错误、类型匹配但运行时值不符、代码不报错但内存持续增长。

这一篇分两部分。测试部分覆盖 pytest、fixture、Mock、异步测试、monkeypatch、覆盖率;调试部分覆盖 pdb、日志、异常链、inspect、tracemalloc、faulthandler、cProfile。

日志一节值得单独提一句:它同时覆盖调试期(临时开 DEBUG 看清路径)和生产期(Handler/Formatter 配置、库与应用的责任分工、JSON 结构化、trace ID 注入)。这两件事常被混为一谈,但关注点完全不同。

最后给出一张调试决策树:看到什么现象,该拿哪个工具。工具本身不难,难的是知道什么时候用哪个。

7. 怎么交付:工程化与生产交付

第七篇讨论工程化。前六篇讲”代码怎么写对”,这一篇讲”代码怎么变成可交付的东西”。

Java 工程师在这里会遇到最大的落差:

依赖与交付:Java 与 Python 的落差
Java Python
Maven 内建依赖解析 文件本身不含解析器,工具需自选
classpath 天然隔离 同解释器下一个包只能有一个版本
编译器强制类型检查 mypy 是可选外挂,需自己装进 CI
Maven 定义项目结构(CoC) 没有等价物,规范需自己立
jar 自包含、平台无关 wheel 不含解释器,且常绑定平台与 CUDA 版本

这一篇覆盖 pyproject.toml、虚拟环境、依赖锁定与可复现构建、AI-Infra 特有的依赖难题(torch==2.4.0+cu121 的本地版本标识、--index-url 与 --extra-index-url 的区别、CUDA driver/runtime/wheel 三层兼容矩阵)、Ruff 与 pre-commit、打包分发、容器化交付,最后给出一个可直接用的项目骨架。

核心结论是:Python 把 Java 里由框架和编译器强制的事情,交还给了你。 这份自由度是 Python 在 AI 领域胜出的原因之一,但也意味着一个没有工程纪律的 Python 项目会腐化得更快。

8. 系列总结与通关自测

最后一篇不讲新内容:把七篇正文压成一张「问题 → 结论 → 必记数字」的表并逐篇回顾,拎出贯穿全系列的几条线与常见误区,然后给一套三段式通关自测——十道判断与计算、五道跨篇综合、若干道面试题,答案各自折叠,附「读过 / 掌握 / 能教人」的判据。各篇末尾的自测检验的是一篇读懂了没有,这一篇检验的是七篇能不能连起来用;读完正文再做。

章节目录(建议按顺序阅读)

  1. 语言机制与运行时原理:上·代码如何被执行——执行模型、作用域、导入与异常 · 下·对象如何工作——对象模型、协议、装饰器与生成器
  2. 类型系统与数据契约设计:上·类型表达与 typing 工具箱 · 中·类型信息的分发与消费 · 下·数据契约设计
  3. 并发、异步与任务协作
  4. Python 的动态机制及工程实践
  5. 内存管理与优化
  6. 单元测试、问题定位与调试实践
  7. 项目工程化与生产交付
  8. 系列总结与通关自测

前置要求与说明

前置要求:需要具备一门静态类型语言(Java、C++、Go 等)的工程经验,以及基本的 Python 语法基础(能读懂函数、类、list/dict 的用法)。不要求熟悉 CUDA、分布式训练或任何具体推理框架的源码。

版本基线:正文以 Python 3.11+ 为基线,涉及新特性时会标注引入版本(如 3.10+、3.12+)。如果项目需要兼容旧版本,多数特性可从 typing_extensions 导入。

关于时效性:语言机制部分(前六篇)相对稳定。第七篇是最容易过期的——uv 仍在快速演进,PyTorch 的 CUDA 索引和版本矩阵每个大版本都在变。阅读时请以官方文档核对具体命令和版本号;但分层的思路、抽象依赖与锁定依赖的分工、把平台相关的重依赖交给基础镜像这些判断,应该会比具体工具活得更久。

关于 AI-Infra 系统本身:本系列讲的是 Python 这门语言在 AI-Infra 中的用法,不是训练框架或推理系统的架构。张量计算如何执行、调度器如何组 batch、KV Cache 如何管理、多卡如何协同,这些属于系统层的话题,本系列只在需要时借用它们的源码作为语言特性的例子,不展开原理。

配套代码:写文章时用来验证各个结论的小脚本(目前是第一篇的语言机制与第三篇的并发部分)在 ai-learning-labs/python-for-ai-infra,只依赖标准库,python3 xxx.py 直接运行。

  1. Python 承担的是组织、调度、扩展、观测和交付——控制平面和胶水层:真正吃算力的部分由 CUDA、C++、通信库与推理引擎完成。因此要掌握的不是写更短的代码,而是七条链条上的机制:代码怎么运转(对象模型、属性查找、特殊方法、描述符、生成器)→ 怎么写得健壮(类型信息、数据契约)→ 怎么协作(并发:线程 / GIL / asyncio / 多进程)→ 怎么扩展(C 扩展、pybind11、ABI)→ 内存怎么占用与回收(引用计数、GC、Tensor 生命周期)→ 怎么确保没问题、出了问题怎么定位(测试、可观测、调试)→ 怎么交付(打包、依赖、环境)。系列的篇目就按这条链排列。 ↩

这篇对你有用?

本文由 arganzheng 创作,采用 CC BY 4.0 许可协议。在保留原文作者、署名以及完整原文链接(https://arganzheng.life/python-for-ai-infra.html)的前提下,欢迎各种形式的转载、翻译或商业引用。


COMMENTS

评论存放在 GitHub Discussions, 用 GitHub 账号登录即可发表,支持 Markdown。 想针对正文某句话说?选中那段文字,点浮出的「评论」即可划线评论;觉得哪里写错了,发表时勾上「同时提交 Issue」。 有人回复你时 GitHub 会按你的通知设置发邮件,不用守在这里。

×