Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
006f3c7
Fail only the request whose async (Mooncake) tensor read failed, and …
arun-elr Oct 19, 2026
b9622ba
Hold READS_DONE while a failed read's transfers run, and read a torn-…
arun-elr Oct 19, 2026
b15fba9
Poll a read's future through done() and result() only, as the SHM are…
arun-elr Oct 19, 2026
3e01445
test: the output-failure teardown fake tensor manager reports no fail…
arun-elr Oct 20, 2026
dc83e30
Transfer metrics: a failed read is dropped and reported, not raised, …
arun-elr Oct 20, 2026
c817311
test: the serving-metrics stub tensor manager reports no failed reads
arun-elr Oct 20, 2026
c4cbd40
test: the fake Mooncake engine's report waits for the reader to submi…
arun-elr Oct 21, 2026
6bf3a03
Count a read's retries under the device filter the Mooncake engine wa…
arun-elr Oct 21, 2026
a61c341
Log a failed output read in one line, as a failed input read is
arun-elr Oct 21, 2026
9ff9f8d
Count the HCAs for read retries on the first failed tensor, not at st…
arun-elr Oct 23, 2026
9e3fd14
test: over real Mooncake TCP, a failed read keeps its buffers until a…
arun-elr Oct 24, 2026
490b808
test: acceptance tests for a TP follower whose input read fails after…
arun-elr Oct 25, 2026
3748ab9
TP: take a row whose follower read failed out of the step on every ra…
arun-elr Oct 26, 2026
8087222
TP read check: record every failed read for the check, and share the …
arun-elr Oct 26, 2026
1628c8b
test: the in-process Mooncake engine of the slice tests reads async too
arun-elr Oct 26, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 40 additions & 7 deletions mstar/api_server/data_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,11 @@
)
from mstar.communication.communicator import BaseCommunicator, CommProtocol, make_communicator
from mstar.communication.event import EventWakeup
from mstar.communication.tensors import NameToTensorList, create_tensor_communication_manager
from mstar.communication.tensors import (
FailedRead,
NameToTensorList,
create_tensor_communication_manager,
)
from mstar.engine.resources.kv.config import KVSpec, PagedKVConfig
from mstar.engine.resources.kv.keys import PageItem, chain, end_key, fingerprint, page_items
from mstar.engine.resources.spec import apply_yaml_overrides
Expand Down Expand Up @@ -886,7 +890,7 @@ def _summarize_inputs(input: PreprocessInput) -> list[InputInfo]:

def _fail_request(
self, request_id: str, exc: BaseException, stage: str, count: int = 1,
sequence: int | None = None, abort: bool = True,
sequence: int | None = None, abort: bool = True, traceback: bool = True,
):
"""Report a per-request data-worker failure to the API server.

Expand All @@ -901,8 +905,16 @@ def _fail_request(

``abort`` (False for a request the conductor has not seen) stops the
request's walk, since its client already has its answer.

``traceback`` False logs one line, for an error already logged where
it happened.
"""
logger.exception("%s failed for request %s", stage, request_id)
if traceback:
logger.error("%s failed for request %s", stage, request_id, exc_info=exc)
else:
logger.error(
"%s failed for request %s: %s: %s", stage, request_id, type(exc).__name__, exc,
)
status = 400 if isinstance(exc, (ValueError, TypeError)) else 500
chunks = [
ResultChunk(
Expand Down Expand Up @@ -1040,9 +1052,29 @@ def _discard_result_tensor(
graph_edges=[result.graph_edge],
)

def _fail_unread_outputs(self, failed: FailedRead) -> None:
"""Fail the request of a read that will not be delivered: one error
chunk in the output slot of each tensor it would have delivered. The
manager already acked or kept its buffers."""
request_id = failed.request_id
state = self.request_output_state.get(request_id)
metadata = self.tensor_uuid_to_metadata_per_request.get(request_id, {})
for edge in failed.graph_edges:
for info in edge.tensor_info:
metadata.pop(info.uuid, None)
slot = state.order.pop(info.uuid, None) if state is not None else None
self._fail_request(
request_id, failed.error, "output tensor read",
sequence=slot[0] if slot is not None else None, traceback=False,
)

def _process_read_tensors(self):
did_work = False
for request_id, graph_edges in self.tensor_manager.get_ready_tensors().items():
ready = self.tensor_manager.get_ready_tensors()
for failed in self.tensor_manager.take_failed_reads():

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two ordering problems here. The failed reads are handled first and then ready still holds this rid's outputs from the same poll, so they are emitted after the error chunk; and the chat SSE loop in serving_chat.py keeps yielding after an error event, so a stream can carry error, content, error. With injection on, 2 of 16 streams carried two error events ("EEd", "cEEd"). Skipping ready[rid] for rids with a failed read (and ending the stream at the first error) fixes both.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 5dc5ff4. Nothing goes out for a request after its error: outputs ready in the same poll are released without postprocessing, and outputs held for ordering are dropped. iter_result_chunks now ends the response at the first error. It no longer appends req.error after an in-band error, which was the second "E" in "EEd". On the H200 run with injection (before the #368 rebase), all 16 streams carried exactly one error and nothing after it (rcEd or rEd).

did_work = True
self._fail_unread_outputs(failed)
for request_id, graph_edges in ready.items():
did_work = True
for graph_edge in graph_edges:
modality = graph_edge.name.replace("_output", "")
Expand Down Expand Up @@ -1179,12 +1211,13 @@ def _send_enable_metrics_if_requested(self) -> None:
def _finish_reading(self, request_id: str, drained: bool) -> None:
"""The API server is done with this rid's outputs. ``drained`` (every
chunk delivered) means no read can still be in flight, so ACK straight
away — the happy path pays nothing. Otherwise delivery was abandoned
(TTL, client gone) with reads possibly still running, so gate the ACK on
away, unless a failed read's transfers are still running: its error
chunks count as delivered. Otherwise delivery was abandoned (TTL,
client gone) with reads possibly still running, so gate the ACK on
them; ACKing there would let the conductor unlink under a read."""
if request_id not in self._live_requests:
return
if drained:
if drained and not self.tensor_manager.has_inflight_reads(request_id):
self._send_reads_done(request_id)
else:
self._begin_drain(request_id)
Expand Down
Loading
Loading