HCCL 2 :薄绑定
无中间封装层。 Python 接口与 C 接口一一对应,参数原样转发,无内存拷贝、无数据组装。每个算子模块只做一件事:把 Python 参数传给 C 函数,检查返回码。
用户 Python 应用
│ import hccl4py
▼
hccl4py
├─ ops/ 14 个通信算子 ← hccl 仓
├─ comm.py 通信域 5 接口 ← hcomm 仓
├─ types.py 公开常量层
└─ _binding.py 私有声明层
│ ctypes
▼
libhccl.so
_binding.py — 私有声明层
职责:动态库加载 + 全部 C 声明。ops/ 和 comm.py 共用,不对外暴露。
_LIB_PATH = os.environ.get("HCCL4PY_LIB_PATH", "libhccl.so")
hccl = ctypes.CDLL(_LIB_PATH) # 模块级单例
# 模块加载时即声明 argtypes/restype,避开 ctypes 默认慢路径
hccl.HcclBroadcast.argtypes = [ctypes.c_void_p, ctypes.c_uint64, ctypes.c_int,
ctypes.c_uint32, ctypes.c_void_p, ctypes.c_void_p]
hccl.HcclBroadcast.restype = ctypes.c_int
关键点:
CDLL模块级单例,库路径支持HCCL4PY_LIB_PATH环境变量覆盖;- 19 个函数逐一设置
argtypes/restype,指针统一c_void_p,枚举统一c_int; - 三个结构体(
HcclRootInfo/HcclCommConfig/HcclSendRecvItem)定义于此; HcclError异常类定义于此,携带HcclResult码。
types.py — 公开常量层
HcclDataType(17 项)、HcclReduceOp(4 项)、HcclResult(25 项),取值与 hccl_types.h 逐项一致:
class HcclDataType(enum.IntEnum):
INT8 = 0; INT16 = 1; INT32 = 2; FP16 = 3; FP32 = 4
INT64 = 5; UINT64 = 6; UINT8 = 7; UINT16 = 8; UINT32 = 9
FP64 = 10; BFP16 = 11; INT128 = 12; HIF8 = 14
FP8E4M3 = 15; FP8E5M2 = 16; FP8E8M0 = 17
_binding.py 只引用 types.py 的枚举,不重复定义——单一来源。
ops/ — 算子接口层
每算子一模块(ops/broadcast.py、ops/all_reduce.py …),ops/__init__.py 扁平再导出。用户视角:
from hccl4py.ops import broadcast, all_reduce, send # 一个 import 搞定
每个模块极薄,以 broadcast 为例:
def broadcast(hccl_comm, buf, count, data_type, root, stream):
_check_stream(stream)
ret = hccl_broadcast(buf, int(count), int(data_type), int(root),
hccl_comm, stream)
if ret != types.HCCL_SUCCESS:
raise HcclError(ret)
return ret
没有业务逻辑、类型推导、默认值填充——调用方显式给出所有参数,绑定原样转发。
comm.py — 通信域层
5 个接口:get_root_info、init_rank_config、init_cluster_config、config_init、destroy_comm。比算子层稍厚,因为通信域创建涉及出参改返回值、config 默认值填充、rootInfo 序列化辅助。但仍然没有"业务逻辑",只是把 C 的出参模式翻译成 Python 的返回值模式。
双仓分工
ops/ 属 hccl 仓,comm.py 属 hcomm 仓。两仓各自 PR 合入时均提交完整包目录,声明层(_binding.py/types.py)内容保持一致,最终两仓代码各自独立完整可运行。
这不是两个独立包,是同一个包 hccl4py 的两半,按 C 侧的仓库边界切分。
为什么不分更厚
可以在算子层加 dtype 推导(根据 numpy 数组自动推断 HcclDataType)、加 buffer 自动 device 化、加 stream 自动创建。这些会让用户代码更短,但:
- 推导逻辑出错时,错误发生在绑定层而非用户代码,调试困难;
- 隐式行为与 C 接口语义不一致,用户对照 C 文档时产生困惑;
薄绑定的代价是用户多写几个参数,收益是行为完全可预测、与 C 文档一一对照、性能开销透明。
下一篇:ctypes 结构体绑定。
- 点赞
- 收藏
- 关注作者
评论(0)