HCCL 05 错误处理:出参改返回值,异常承载错误码
【摘要】 C 侧HCCL 的 5 个通信域接口都用"返回码 + 出参"模式:HcclResult HcclGetRootInfo(HcclRootInfo* rootInfo); // rootInfo 是出参HcclResult HcclCommInitRootInfoConfig(nRanks, rootInfo, rank, config, HcclComm* comm);...
C 侧
HCCL 的 5 个通信域接口都用"返回码 + 出参"模式:
HcclResult HcclGetRootInfo(HcclRootInfo* rootInfo); // rootInfo 是出参
HcclResult HcclCommInitRootInfoConfig(nRanks, rootInfo, rank, config, HcclComm* comm); // comm 是出参
HcclResult HcclCommDestroy(HcclComm comm); // 无出参
返回 HcclResult(int,0 = 成功),结果经指针出参带回。这是 C 的惯例:返回值传状态码,实际数据走指针出参。。
Python 侧的翻译
出参改返回值:
| C 接口 | Python 接口 | 返回值 |
|---|---|---|
HcclGetRootInfo(rootInfo*) |
get_root_info() |
HcclRootInfo 对象 |
HcclCommInitRootInfoConfig(..., comm*) |
init_rank_config(nranks, root_info, rank, config=None) |
hccl_comm 句柄 |
HcclCommInitClusterInfoConfig(..., comm*) |
init_cluster_config(cluster_info, rank, config=None) |
hccl_comm 句柄 |
HcclCommConfigInit(config*) |
config_init() |
HcclCommConfig 对象 |
HcclCommDestroy(comm) |
destroy_comm(comm) |
HcclResult(0) |
返回值位置给了业务对象(rootInfo / comm / config),错误码没地方放了。
解法:失败抛异常,异常携带错误码。
class HcclError(Exception):
def __init__(self, result):
self.result = result
super().__init__(f"HCCL error, HcclResult={result}")
def init_rank_config(nranks, root_info, rank, config=None):
cfg = config if config is not None else config_init()
comm = ctypes.c_void_p()
ret = hccl.HcclCommInitRootInfoConfig(
ctypes.c_uint32(nranks), ctypes.byref(root_info),
ctypes.c_uint32(rank), ctypes.byref(cfg), ctypes.byref(comm))
if ret != HCCL_SUCCESS:
raise HcclError(ret) # 错误码原样携带,不截断不覆盖
return comm # 成功才返回句柄
为什么用异常
- 无位置承载错误码。 要么返回 tuple
(result, obj)(丑且易忽略错误),要么用异常。 - 通信域创建是低频操作。 每进程每通信域一次,异常的开销(栈展开)可忽略。算子是热路径,但算子没有出参,成功返回
HcclResult(0),失败抛异常——热路径上不触发异常时零开销。
HcclError 携带的是 C 侧原始 HcclResult 值(1~24 与 1041):
try:
comm = init_rank_config(2, bad_info, 0)
except HcclError as e:
print(e.result) # 直接拿到 C 侧错误码,可与 types.HcclResult 比对
if e.result == int(types.HcclResult.HCCL_E_INTERNAL):
...
用户可以拿 e.result 与 hccl4py.types.HcclResult 枚举比对,知道具体是什么错误——参数错、内存错、不支持、超时等,25 种错误码一一对应 hccl_types.h。
destroy_comm 不做幂等包装
def destroy_comm(comm):
ret = hccl.HcclCommDestroy(comm)
if ret != HCCL_SUCCESS:
raise HcclError(ret)
return ret
重复销毁同一句柄,C 侧行为是返回错误码(好像是未定义行为,禁止如此操作吧)。Python 侧原样透传:抛 HcclError。不做 Python 侧状态记录(比如"已销毁标记"),因为加状态记录意味着绑定层要维护句柄生命周期表,违背薄绑定原则;
UT 验证了这个行为:
def test_destroy_twice_raises(acl_env):
comm = init_rank_config(1, get_root_info(), 0)
destroy_comm(comm)
with pytest.raises(HcclError):
destroy_comm(comm) # 第二次必须抛异常
算子侧的错误处理
算子没有出参,成功返回 HcclResult(0),失败抛异常。以 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
热路径上 ret == 0,不触发异常,开销仅一次 int 比较和一次函数返回。
下一篇:14 个通信算子巡览。
【声明】本内容来自华为云开发者社区博主,不代表华为云及华为云开发者社区的观点和立场。转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息,否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱:
cloudbbs@huaweicloud.com
- 点赞
- 收藏
- 关注作者
评论(0)