Conversation
added 2 commits
September 24, 2026 15:54
toposort layers a node by its longest input chain, and a layer runs after every node of the layers below it in the whole graph, needed or not. On atlantic the routing classifier sits in layer 17 of 506, so 9,098 nodes (57%) ran before it although its ancestor cone is 100 nodes. Emit each async node's cone first (smallest cone first), then the node, then everything else; a cone is closed under inputs, so the result is still a topological order. With eager tasks the classifier request leaves at 39 ms instead of 254 ms (atlantic) and 54 instead of 347 ms (louisiana); with an 800 ms model wait the routing turn is 17-19% shorter, walls otherwise unchanged and bot texts identical.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Order the nodes so that every async node (an LLM or HTTP call) runs as early as its inputs allow: each async node's ancestor cone first, smallest cone first, then the node, then everything else.
computation_graph/run.py:_toposort_nodesis split into_node_to_dependencies(the transposed edge map),_layered_order(the existingtoposortlayers with async nodes first inside each layer) and a new_io_cones_firstpass over that order._ancestorsis a plain reverse walk per async node. Computed once at build time; the runner is unchanged.computation_graph/graph_test.py:test_toposort_runs_async_ancestor_cones_first, two graphs: an async node three hops deep must precede unrelated shallow nodes, and a deep async node with a 2-node cone must precede a shallow one with a 3-node cone. Both also assert the result is a topological order of every edge.Why:
toposortputs a node in the layer given by its longest input chain, and layer k runs after every node of the layers below k in the whole graph, needed or not. On atlantic the routing classifier's node sits in layer 17 of 506 (itsshould_runinput is 16 hops deep through the FAQ trigger and a feature flag keyed by the caller's phone number), so 9,098 nodes (57%) run before it although its ancestor cone is 100 nodes. Louisiana: layer 11–17, 50%, cone 100. With cones first the classifier is reached after 6% (atlantic) / 4% (louisiana) of the nodes, and the options classifier moves from 63% to 11%.The order only matters when tasks start at creation: nlu-runtime MR !9802 sets
asyncio.eager_task_factoryon the serving loop (with the default factory the request waits for the runner loop to end regardless of order). Independent of #91: this touches only the toposort, #91 only the executors.Benchmark
Network-free harness from nlu-runtime (
scripts/perf_baseline.py: in-process model endpoints, LLM stubbed, DNS disabled), one bot per process, 1 warm-up + N=5 measured conversations, connect + greet + 4 utterances, master (78) vs this branch back to back, both legs with the eager task factory (PERF_EAGER_TASKS=1), nlu code fixed at master ff3701382c. Two passes: the stub returns instantly, and the stub sleeps 800 ms to stand in for the model. Laptop on battery with Low Power Mode on, so read percentages and deltas, not absolute milliseconds.When the classifier's LLM call leaves (ms after turn start, p50):
Turn walls with the instant stub: every turn within ±2% on both bots (the reorder itself is free). With the 800 ms stub:
The saving equals the CPU that used to run before the request and now overlaps the wait. Min moves with p50, bot texts identical on all turns of both passes, 0 errors, builds unchanged. Test suite: 65 passed.
Jira
CORE-121