HCCL 03 ctypes:结构体、对齐与不透明字节块

举报
黄生 发表于 2026/10/19 15:29:49 2026/10/19
【摘要】 三个结构体绑定涉及三个 C 结构体,各有绑法。 HcclRootInfo — 不透明字节块Root 节点标识信息:存储和传递 Root 节点的 Rank 标识信息,主要包含 Device IP、Device ID 等关键信息 。使用场景:在调用 HCCL 初始化接口(如 HcclCommInitRootInfo 或 HcclCommInitRootInfoConfig)之前,需仅在 Root...

三个结构体绑定涉及三个 C 结构体,各有绑法。

HcclRootInfo — 不透明字节块

Root 节点标识信息:存储和传递 Root 节点的 Rank 标识信息,主要包含 Device IP、Device ID 等关键信息 。

使用场景:在调用 HCCL 初始化接口(如 HcclCommInitRootInfo 或 HcclCommInitRootInfoConfig)之前,需仅在 Root 节点调用 HcclGetRootInfo 接口生成该结构体 。
数据流转: HcclRootInfo 需广播至集群内的所有 Rank,以便所有节点使用相同的 Root 信息进行通信域初始化 。

C 侧定义是 char internal[4108],内部字段不公开。绑定照抄:

class HcclRootInfo(ctypes.Structure):
    _fields_ = [("internal", ctypes.c_char * 4108)]

不做字段级解析,整体当 4108 字节的字节块。跨进程分发时序列化:

def root_info_to_bytes(info):
    return ctypes.string_at(ctypes.addressof(info), ctypes.sizeof(info))

def root_info_from_bytes(data):
    info = HcclRootInfo()
    if len(data) != ctypes.sizeof(info):
        raise ValueError(f"rootInfo size {len(data)} != {ctypes.sizeof(info)}")
    ctypes.memmove(ctypes.addressof(info), data, len(data))
    return info

必须用 string_at / memmove,不能用 bytes(info.internal)。原因见下文 NUL 截断坑。

HcclCommConfig — 逐字段定义

通信域配置项:定义 HCCL 通信域的具体运行参数和配置 。先调用 HcclCommConfigInit 接口对其进行初始化,且同一通信域中所有 Rank 的配置项内容需保持一致。项目如:
Buffer 大小:共享数据的缓存区大小(单位 MB,默认值通常为 200) 。
确定性计算开关:控制归约类通信算子是否开启确定性计算(默认关闭) 。
通信域名称:用于标识通信域 。
通信算子展开模式:其他高级配置项 。

20 个字段,按 hccl_types.h 字段顺序逐字段定义,依赖 ctypes 默认对齐与 C 编译器一致:

class HcclCommConfig(ctypes.Structure):
    _fields_ = [
        ("reserved", ctypes.c_char * 24),
        ("hcclBufferSize", ctypes.c_uint32),
        ("hcclDeterministic", ctypes.c_uint32),
        ("hcclCommName", ctypes.c_char * 128),
        ("hcclUdi", ctypes.c_char * 128),
        ("hcclOpExpansionMode", ctypes.c_uint32),
        ("hcclRdmaTrafficClass", ctypes.c_uint32),
        ("hcclRdmaServiceLevel", ctypes.c_uint32),
        ("hcclWorldRankID", ctypes.c_uint32),
        ("hcclJobID", ctypes.c_uint64),
        ("aclGraphZeroCopyEnable", ctypes.c_uint8),
        ("hcclExecTimeOut", ctypes.c_int32),
        ("hcclAlgo", ctypes.c_char * 1600),
        ("hcclRetryEnable", ctypes.c_char * 50),
        ("hcclRetryParams", ctypes.c_char * 128),
        ("hcclBufferName", ctypes.c_char * 128),
        ("hcclQos", ctypes.c_uint32),
        ("hcclSymWinMaxMemSizePerRank", ctypes.c_uint64),
        ("hcclChannelSqDepth", ctypes.c_uint32),
    ]

布局一致性靠 UT 断言 ctypes.sizeof(HcclCommConfig) 对照预期值校验。字段顺序不能乱——C 编译器按声明顺序排布,Python 侧必须一致。

HcclSendRecvItem — 混合类型

点对点通信任务定义:定义批量点对点通信操作中每一个具体任务的基本信息 。通常作为数组传入 HcclBatchSendRecv 接口,以实现单次调用完成本 Rank 上的多个异步收发任务 。包含:
任务类型:标识当前任务是发送(Send)还是接收(Recv) 。
数据地址:数据发送或接收的 Buffer 地址 。
数据个数与类型:指定发送/接收数据的元素个数及数据类型(如 FP32、INT8 等) 。
对端 Rank:通信域内数据接收端或发送端的 Rank 编号 。

class HcclSendRecvItem(ctypes.Structure):
    _fields_ = [
        ("sendRecvType", ctypes.c_int),    # enum → int
        ("buf", ctypes.c_void_p),          # device 内存指针
        ("count", ctypes.c_uint64),
        ("dataType", ctypes.c_int),        # enum → int
        ("remoteRank", ctypes.c_uint32),
    ]

batch_send_recv 算子用这个结构体数组,调用时构造:

items = (HcclSendRecvItem * n)(*send_recv_items)
ret = hccl.HcclBatchSendRecv(items, ctypes.c_uint32(n), comm, stream)

NUL 截断坑

HcclCommConfig 的前 24 字节是头部(size/magicWord/version/reserved),想读出来校验:

# 错误写法 — bytes(c_char * 24) 遇到 NUL 字节就截断
header = bytes(cfg.reserved)  # 如果第 5 字节是 \x00,只拿到 5 字节

# 正确写法 — 按原始内存取整 24 字节
header = ctypes.string_at(ctypes.addressof(cfg), 24)

ctypes.c_char * N 转 bytes 时,行为类似 C 字符串——遇到 \x00 就停。结构体头部里 magicWord、version 这些二进制数据中间有 NUL 字节是正常的,bytes() 会截断,拿到的是残缺数据。

规则:结构体序列化一律按原始内存 string_at / memmove,禁用 c_char * N 字段级读写。 这条规则写进了 _binding.py 的模块 docstring。

rootInfo 跨进程分发

2 卡建域时,rank0 调 get_root_info() 拿到 HcclRootInfo,需要传给 rank1。分发方式是用户代码(文件/socket),绑定提供辅助函数:

def save_root_info(info, path):
    raw = root_info_to_bytes(info)
    tmp = path + ".tmp"
    with open(tmp, "wb") as f:
        f.write(raw)
    os.replace(tmp, path)          # 原子替换,避免对端读到半截文件

os.replace 是原子操作——对端要么看到旧文件,要么看到完整新文件,不会读到写了一半的 4108 字节。这在 2 卡测试中很重要:rank1 轮询等待 rootInfo 文件出现,如果 rank0 用普通 write 非原子写入,rank1 可能读到不完整的结构体,memmove 后建域行为未定义。

下一篇:那个 ctypes 加载不了的 inline 函数。

【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0)

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。