Check a generator's yield at the yield that produced it
The worker held each yield one behind, because the last one is the node's result when the generator returns nothing of its own. Only the engine knows what ports a node declared, so the check happened when the *next* yield arrived — a pass late, which for a training loop is however long one epoch takes. The worker now sends every yield as it happens and returns whatever its generator returned; EmitSink holds the last one back and decides at the end of the call what it was. Old "emit" frames are still handled, so a remote agent that has not been restarted keeps working. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -362,31 +362,21 @@ def _handle(request: dict[str, Any], cache: dict[tuple[str, str], Any]) -> Any:
|
||||
|
||||
|
||||
def _drain(generator: Any) -> Any:
|
||||
"""Run a generator node, publishing each yield as it happens.
|
||||
"""Run a generator node, sending each yield the moment it happens.
|
||||
|
||||
Two shapes work, and they mean the same thing. Yield throughout and
|
||||
``return`` the result at the end, which is the explicit one; or just yield,
|
||||
and the last one is the result. Either way what the node *produces over
|
||||
time* leaves through its ports while it is still running, and what it
|
||||
*ends up with* is its return value.
|
||||
and the last one is the result. Which of the two a node is cannot be known
|
||||
until it ends, so the last yield has to be held back somewhere — and that
|
||||
somewhere is the engine, which is the only side that knows what ports the
|
||||
node declared. Holding it here instead cost a pass: a yield naming a port
|
||||
that does not exist was only checked once the *next* one arrived.
|
||||
"""
|
||||
pending: Any = None
|
||||
have_pending = False
|
||||
try:
|
||||
while True:
|
||||
value = next(generator)
|
||||
# Held one behind: until the next yield arrives this might be the
|
||||
# last one, and the last one is the result rather than an emission.
|
||||
if have_pending:
|
||||
_emit({"event": "emit", "outputs": pending})
|
||||
pending, have_pending = value, True
|
||||
_emit({"event": "yield", "outputs": next(generator)})
|
||||
except StopIteration as stop:
|
||||
if stop.value is not None:
|
||||
# It returned something, so every yield was an emission.
|
||||
if have_pending:
|
||||
_emit({"event": "emit", "outputs": pending})
|
||||
return stop.value
|
||||
return pending if have_pending else None
|
||||
return stop.value
|
||||
|
||||
|
||||
def main() -> None:
|
||||
|
||||
Reference in New Issue
Block a user