Repository navigation
[Bugfix] Publish HCCL completion after stream retirement #10
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: v0.3.14_xllm_patch
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -133,7 +133,10 @@ void HcclTransport::initiatorLoop(int deviceLogicId, int selfIdx) { | |
| << ", source_addr: " << slice->source_addr | ||
| << ", dest_addr: " << slice->hccl.dest_addr | ||
| << ", ret: " << ret; | ||
| slice->markFailed(); | ||
| // Keep the error private until the stream and all retries | ||
| // have retired. Publishing FAILED here lets callers free | ||
| // buffers that a sibling operation still accesses. | ||
| slice->status = Slice::FAILED; | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -146,7 +149,7 @@ void HcclTransport::initiatorLoop(int deviceLogicId, int selfIdx) { | |
| << ", remote devicePhyId: " << remote_rank_info_.devicePhyId | ||
| << ", ret: " << ret; | ||
| for (auto slice : slice_list) { | ||
| slice->markFailed(); | ||
| slice->status = Slice::FAILED; | ||
| } | ||
| } | ||
| auto addOpfence = std::chrono::high_resolution_clock::now(); | ||
|
|
@@ -159,9 +162,21 @@ void HcclTransport::initiatorLoop(int deviceLogicId, int selfIdx) { | |
| << local_rank_info_.devicePhyId | ||
| << ", remote devicePhyId: " << remote_rank_info_.devicePhyId | ||
| << ", ret: " << ret; | ||
| aclrtStreamAbort(stream); | ||
| const int abort_ret = aclrtStreamAbort(stream); | ||
| if (abort_ret) { | ||
| LOG(ERROR) << "aclrtStreamAbort failed, ret: " << abort_ret; | ||
| } | ||
| // Synchronize to drop ACL_ERROR_RT_STREAM_ABORT error | ||
| aclrtSynchronizeStream(stream); | ||
| int drain_ret = aclrtSynchronizeStream(stream); | ||
| while (drain_ret != ACL_SUCCESS) { | ||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2 / 合入前验证项] 这里的永久错误退出策略和“流退休”保证还需要真实 HCCL/ACL 证据;不是建议超时后直接释放缓冲区
尚缺的证据是:部署 SDK 在提交/fence 失败、stream abort 失败/成功之后,哪一种同步返回值足以证明已经提交的 READ/WRITE(包括远端写入)不会继续访问借用内存。本轮静态检查无法替代这个设备契约。 建议合入门槛:用实际部署的 CANN/HCCL 验证这几条故障路径,记录终态发布只发生一次、目标页复用后无迟到写、以及永久错误时如何停服/重启和告警。不要为了给循环加上界而直接
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 第二轮复核:代码未变,配套关系已变更本轮 head 仍是 需要修正上一轮的配套前提:当前 !791 本轮还在 !791 的当前配套 PR12 |
||
| // An error result alone cannot release borrowed buffers. | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] PR10 不提供任何 retirement 可观测接口:0.3.14 消费者只能看到无区别的 WAITING;MR791 式 requires 检查对本 PR fail-closed(对 ShuweiShen772 round-2「统一 producer/consumer 退休契约」要求的代码级机制证据补充) 证据:
if constexpr (!requires(Engine* e, mooncake::BatchID id, bool& retired) {
e->getBatchRetirementStatus(id, retired); }) {
LOG(ERROR) << "Mooncake retirement API unavailable, peer=" << remote_addr;
return mooncake::Status::Context("Mooncake retirement API unavailable");
}
后果:(a) 按当前 MR791 模式编写的消费者链接本 PR 的 SDK,requires 判定失败 → 每批 dispatch 前被拒(fail-closed,安全但功能不可用);(b) 若有消费者对 WAITING 自设超时后释放缓冲,会在调用方层重现本 PR 要修的早释放 bug。这具体化了 PR 描述自称 "Neither review alone establishes complete HCCL failure-path correctness" 在 PR10 单侧的不完整点:transport 修好了发布时机,但没有给 caller 任何受契约保护的等待依据。 建议:若 0.3.14 线要服务 MR791 类消费者,需以 PR11(或其回移植)为配套并在两 PR 描述中互相点名;同时给 0.3.14 直接消费者写明「不得对 WAITING 设超时后释放缓冲」。 |
||
| // Keep the batch pending until the stream actually drains. | ||
| LOG_EVERY_T(ERROR, 1) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] 排空等待仅有 1 秒节流的 ERROR 日志,无指标、无告警钩子(round-1 已把「永久错误时如何停服/告警」列为合入验证门槛;本条是最小代码级增量建议,非重发) 贴码(:170-179,运行在唯一 initiator 线程上,THREAD_NUM=1;卡住期间该设备所有后续批次在 allReqQueues_[0] 排队): int drain_ret = aclrtSynchronizeStream(stream);
while (drain_ret != ACL_SUCCESS) {
LOG_EVERY_T(ERROR, 1)
<< "Waiting for failed HCCL stream retirement, ret: " << drain_ret;
std::this_thread::sleep_for(std::chrono::milliseconds(1));
drain_ret = aclrtSynchronizeStream(stream);
}问题:每秒最多一条 ERROR 日志,此外无任何可编程观测点;卡住时长、卡住的批次数、涉及的 peer 均不可度量。运维只能靠 grep 日志发现「排空等待」状态,无法回答「排空等待了多久/影响了多少批次」。 最小修复: 验证:静态;若采纳,随 metrics 导出路径测试。 |
||
| << "Waiting for failed HCCL stream retirement, ret: " | ||
| << drain_ret; | ||
| std::this_thread::sleep_for(std::chrono::milliseconds(1)); | ||
| drain_ret = aclrtSynchronizeStream(stream); | ||
| } | ||
|
|
||
| if (loop < transfer_max_retry_cnt) { | ||
| LOG(INFO) << "Hccl transport failed. Retry ..."; | ||
|
|
@@ -180,7 +195,7 @@ void HcclTransport::initiatorLoop(int deviceLogicId, int selfIdx) { | |
| LOG(ERROR) << "Hccl transport failed after " | ||
| << transfer_max_retry_cnt << " retries."; | ||
| for (auto slice : slice_list) { | ||
| slice->markFailed(); | ||
| slice->status = Slice::FAILED; | ||
| } | ||
| } | ||
|
|
||
|
|
@@ -221,7 +236,9 @@ void HcclTransport::initiatorLoop(int deviceLogicId, int selfIdx) { | |
| } | ||
|
|
||
| for (auto slice : slice_list) { | ||
| if (slice->status != Slice::SliceStatus::FAILED) { | ||
| if (slice->status == Slice::SliceStatus::FAILED) { | ||
| slice->markFailed(); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [P2] PR 描述「每个切片恰好在流同步与重试结束后发布」有一处例外:segment 查找失败路径仍在提交前直接 markFailed(已验证安全,但契约声明与实现不符)主现场: auto segment_desc = metadata_->getSegmentDescByID(slice_list[0]->target_id);
if (!segment_desc) {
LOG(ERROR) << "Unable to get target segment ID, please recheck, ...";
for (auto slice : slice_list) {
slice->markFailed(); // 仍为即时发布
}
continue;
}为什么安全(静态论证):该检查位于重试循环之前,本批次切片尚未提交任何 stream 操作( 问题:PR 描述声明 "publish each slice exactly once after stream synchronization and the existing retry loop have finished" 用了全称量词,实现有一处例外。风险是模式脆弱性:未来若有人把 segment 查找移到提交之后、或把该「查不到就 markFailed」模式复制到已提交场景,会静默恢复早发布。 最小修复:在该 markFailed 处加一行注释「本批次零提交、前批已排空,允许立即发布」;或把 PR 描述改为「除提交前失败(segment 查找)外」。 验证:纯静态走查即可关闭(本评论已完成论证)。 |
||
| } else { | ||
| slice->markSuccess(); | ||
| } | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[P2·既有] 提交失败具有跨重试粘性:某轮提交失败的切片即使后续重试实际传输成功,最终仍发布 FAILED(假失败)
(pre-existing,base 行为等价,非本 PR 回归,不阻塞合入)
贴码(:139 置位不清除 + :238-244 最终循环对 FAILED 一律 markFailed):
触发条件:切片在第 k 轮提交失败、第 k+1 轮(重连后)提交且 sync 成功。
后果:status 保持 FAILED → 最终 markFailed → 任务报 FAILED,尽管数据已实际写入目的缓冲;调用方收到假失败后重试整个请求 → 目的缓冲被再次写入同一数据(幂等前提是调用方在终态前不修改源缓冲——这正是本 PR 强化的契约)。
最小修复:每轮重试开始时把将重新提交的切片 status 重置为 PENDING;或明确记录为已知语义。
验证:CPU fault-injection 场景「第 0 轮提交失败 + 第 1 轮成功」断言最终状态。