-
Notifications
You must be signed in to change notification settings - Fork 2.7k
[#12660][fix] Add error propagation for disagg KV cache transfer failures #12988
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
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 |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| /* | ||
| * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| * SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
|
|
@@ -515,6 +515,95 @@ class CacheSender::Impl | |
| } | ||
| } | ||
|
|
||
| /// @brief Notify the receiver that the sender encountered an error during KV cache transfer. | ||
| /// This unblocks the receiver so it can handle the failure instead of waiting indefinitely. | ||
| /// Must not throw -- called from noexcept context. | ||
| void sendErrorSignalToReceiver(RequestIdType id, std::string const& errorMessage) noexcept | ||
| { | ||
| try | ||
| { | ||
| auto* agentConnectionManager = dynamic_cast<executor::kv_cache::AgentConnectionManager*>(mManager); | ||
| if (!agentConnectionManager) | ||
| { | ||
| // Error signals are only supported for agent-based connections (NIXL/UCX). | ||
| return; | ||
| } | ||
| TransferSession* session = nullptr; | ||
| { | ||
| std::unique_lock<std::mutex> lock(mMtxForMap); | ||
| auto it = mRequestToSession.find(id); | ||
| if (it == mRequestToSession.end()) | ||
| { | ||
| TLLM_LOG_WARNING( | ||
| "Cannot send error signal for request %ld: session not found", id); | ||
| return; | ||
| } | ||
| session = std::addressof(it->second); | ||
| } | ||
| auto const& connections = session->getConnections(); | ||
| for (size_t i = 0; i < connections.size(); i++) | ||
| { | ||
| auto* agentConnection | ||
| = dynamic_cast<executor::kv_cache::AgentConnection const*>(connections.at(i)); | ||
| if (agentConnection) | ||
| { | ||
| agentConnection->sendErrorSignal( | ||
| executor::kv_cache::DataContext{TransceiverTag::kREADY_SIGNAL_TAG}, id, errorMessage); | ||
| } | ||
| } | ||
| } | ||
| catch (std::exception const& signalErr) | ||
| { | ||
| TLLM_LOG_WARNING( | ||
| "Failed to send error signal to receiver for request %ld: %s", id, signalErr.what()); | ||
| } | ||
| } | ||
|
|
||
| /// @brief Broadcast error signals to ALL in-flight requests on the receiver side. | ||
| /// When one transfer fails, the sender's executor may freeze, leaving other pending | ||
| /// transfers stuck. This ensures receivers for ALL pending requests are notified. | ||
| /// Must not throw -- called from noexcept context. | ||
| void broadcastErrorToAllPendingReceivers(std::string const& errorMessage) noexcept | ||
| { | ||
| try | ||
| { | ||
| std::unique_lock<std::mutex> lock(mMtxForMap); | ||
| for (auto& [reqId, session] : mRequestToSession) | ||
| { | ||
| sendErrorSignalToReceiverWithSession(reqId, session, errorMessage); | ||
| } | ||
| } | ||
| catch (std::exception const& e) | ||
| { | ||
| TLLM_LOG_WARNING("Failed to broadcast error signals: %s", e.what()); | ||
| } | ||
| } | ||
|
|
||
| /// @brief Internal: send error signal using an already-locked session reference. | ||
| void sendErrorSignalToReceiverWithSession( | ||
| RequestIdType id, TransferSession& session, std::string const& errorMessage) noexcept | ||
| { | ||
| try | ||
| { | ||
| auto const& connections = session.getConnections(); | ||
| for (size_t i = 0; i < connections.size(); i++) | ||
| { | ||
| auto* agentConnection | ||
| = dynamic_cast<executor::kv_cache::AgentConnection const*>(connections.at(i)); | ||
| if (agentConnection) | ||
| { | ||
| agentConnection->sendErrorSignal( | ||
| executor::kv_cache::DataContext{TransceiverTag::kREADY_SIGNAL_TAG}, id, errorMessage); | ||
| } | ||
| } | ||
| } | ||
| catch (std::exception const& signalErr) | ||
| { | ||
| TLLM_LOG_WARNING( | ||
| "Failed to send error signal to receiver for request %ld: %s", id, signalErr.what()); | ||
| } | ||
| } | ||
|
|
||
| void sendAndRemoveResponse(RequestIdType id, Response resp) noexcept | ||
| { | ||
| try | ||
|
|
@@ -527,12 +616,16 @@ class CacheSender::Impl | |
| catch (tensorrt_llm::common::RequestSpecificException const& e) | ||
| { | ||
| TLLM_LOG_ERROR("Exception in sendAndRemoveResponse: %s ", e.what()); | ||
| sendErrorSignalToReceiver(id, e.what()); | ||
| broadcastErrorToAllPendingReceivers(e.what()); | ||
|
Comment on lines
617
to
+620
Contributor
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. Don't send the same request's error twice.
Exclude Also applies to: 627-628 🤖 Prompt for AI Agents |
||
| auto new_exception = TLLM_REQUEST_EXCEPTION(id, e.getErrorCode(), "%s", e.what()); | ||
| resp.mPromise.set_exception(std::make_exception_ptr(new_exception)); | ||
| } | ||
| catch (std::exception const& e) | ||
| { | ||
| TLLM_LOG_ERROR("Exception in sendAndRemoveResponse: %s request id: %ld", e.what(), id); | ||
| sendErrorSignalToReceiver(id, e.what()); | ||
| broadcastErrorToAllPendingReceivers(e.what()); | ||
| resp.mPromise.set_exception(std::current_exception()); | ||
| } | ||
| } | ||
|
|
@@ -1063,10 +1156,9 @@ class CacheReceiver::Impl | |
| bool isReady = receiveReadySignal(session); | ||
| if (!isReady) | ||
| { | ||
| // Reuse the error state for the cancelled request. | ||
| llmRequest.setState(LlmRequestState::kDISAGG_TRANS_ERROR); | ||
| llmRequest.setKvCacheTransferEnd(std::chrono::steady_clock::now()); | ||
| return; | ||
| TLLM_THROW("Sender indicated transfer not ready for request %ld", llmRequest.mRequestId); | ||
|
Comment on lines
1157
to
+1161
Contributor
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. Keep this failure request-scoped. Throwing 🛠️ Proposed fix if (!isReady)
{
llmRequest.setState(LlmRequestState::kDISAGG_TRANS_ERROR);
llmRequest.setKvCacheTransferEnd(std::chrono::steady_clock::now());
- TLLM_THROW("Sender indicated transfer not ready for request %ld", llmRequest.mRequestId);
+ throw TLLM_REQUEST_EXCEPTION(
+ llmRequest.mRequestId,
+ common::RequestErrorCode::kNETWORK_ERROR,
+ "Sender indicated transfer not ready for request %ld",
+ llmRequest.mRequestId);
}🤖 Prompt for AI Agents |
||
| } | ||
| receiveSync(session); | ||
| llmRequest.setKvCacheTransferEnd(std::chrono::steady_clock::now()); | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,5 +1,5 @@ | ||
| /* | ||
| * SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| * SPDX-FileCopyrightText: Copyright (c) 2025-2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved. | ||
| * SPDX-License-Identifier: Apache-2.0 | ||
| * | ||
| * Licensed under the Apache License, Version 2.0 (the "License"); | ||
|
|
@@ -17,6 +17,8 @@ | |
|
|
||
| #include "connection.h" | ||
| #include "tensorrt_llm/common/envUtils.h" | ||
| #include "tensorrt_llm/common/logger.h" | ||
| #include "tensorrt_llm/common/tllmException.h" | ||
| #include "tensorrt_llm/executor/cache_transmission/cacheSplitConcat.h" | ||
| #include <random> | ||
| #include <string> | ||
|
|
@@ -275,6 +277,15 @@ std::optional<size_t> AgentConnection::getPreAssignedBufferId(uint8_t kind) cons | |
| return std::nullopt; | ||
| } | ||
|
|
||
| void AgentConnection::sendErrorSignal(DataContext const& ctx, uint64_t requestId, std::string const& errorMessage) const | ||
| { | ||
| ErrorSignalInfo errorSignalInfo{mRemoteAgentName, ctx, requestId, errorMessage}; | ||
| NotificationInfo notificationInfo{errorSignalInfo}; | ||
| std::stringstream ss; | ||
| NotificationInfo::serialize(notificationInfo, ss); | ||
| mAgentConnectionManager->getAgent()->notifySyncMessage(mRemoteAgentName, ss.str()); | ||
| } | ||
|
|
||
| AgentConnectionManager::AgentConnectionManager( | ||
| std::vector<batch_manager::BaseTransBufferManager*> cacheTransBufferManagers, CacheState cacheState, | ||
| std::string const& backendType, std::optional<CacheState::RnnCacheState> rnnCacheState) | ||
|
|
@@ -648,6 +659,28 @@ void AgentConnectionManager::waitForNotification( | |
| } | ||
| } | ||
|
|
||
| // Check for error signals from the remote agent regardless of what | ||
| // notification type we are waiting for. This unblocks the receiver | ||
| // when the sender encounters an error during KV cache transfer. | ||
| if (std::holds_alternative<ErrorSignalInfo>(notificationInfo.mInfo)) | ||
| { | ||
| auto errorSignalData = std::get<ErrorSignalInfo>(notificationInfo.mInfo); | ||
| TLLM_LOG_ERROR( | ||
| "Received error signal from sender for request %ld: %s", | ||
| errorSignalData.mRequestId, errorSignalData.mErrorMessage.c_str()); | ||
| erase = true; | ||
| notifIt = notifs.erase(notifIt); | ||
| if (notifs.empty()) | ||
| { | ||
| it = mUnhandledNotifications.erase(it); | ||
| } | ||
| throw TLLM_REQUEST_EXCEPTION( | ||
| errorSignalData.mRequestId, | ||
| common::RequestErrorCode::kNETWORK_ERROR, | ||
| "Sender error for request %ld: %s", | ||
| errorSignalData.mRequestId, errorSignalData.mErrorMessage.c_str()); | ||
|
Comment on lines
+665
to
+681
Contributor
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. This new error branch still needs clang-format. CI is already flagging this section, so please reflow it with the repo formatter before merging. As per coding guidelines, "Use LLVM clang-format tool for formatting changes; maximum line length is 120 characters." 🧰 Tools🪛 GitHub Actions: Release Checks[error] 665-665: clang-format required formatting changes (TLLM_LOG_ERROR and TLLM_REQUEST_EXCEPTION call wrapping/reflow). 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| if (!erase) | ||
| { | ||
| notifIt++; | ||
|
|
||
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.
This helper block still needs clang-format.
Release Checks already flagged this section, so it needs a formatter pass before merge.
As per coding guidelines, "Use LLVM clang-format tool for formatting changes; maximum line length is 120 characters."
🧰 Tools
🪛 GitHub Actions: Release Checks
[error] 534-600: clang-format required formatting changes (TLLM_LOG_WARNING and dynamic_cast statements reflow).
🤖 Prompt for AI Agents