LLM Latency Optimization: End-to-End Timeout Budgets
A slow model call can outlive the API request that started it. The user sees a timeout, retries the action, and moves on, while the original workflow keeps consuming a model slot or reaches a business tool late. That failure makes LLM latency optimization more than a question of choosing a faster model. You need one deadline that follows the work through queues, model calls, retrieval, and tools, plus cancellation that reaches the underlying clients.
Set the deadline when the request enters the system and carry it into every downstream operation. The workflow can then fail within a known budget, stop abandoned work, limit retries to the time available, and protect business systems from late duplicate actions.
Why an edge timeout does not stop the workflow
An HTTP timeout controls how long the caller waits. It does not automatically cancel every operation started below that handler. If a model SDK never receives the caller's cancellation signal, its network request can continue. If a worker already dequeued a task, the task can continue. If a tool has started a write, the write may finish after the response path is gone.
A practitioner report in OpenClaw Lobster issue 131 describes a workflow step whose timeout_ms and external cancellation signal did not stop the in-flight model request because the signal was not passed into fetch. This is an author-reported implementation defect, not a universal product property. It demonstrates the wiring error your tests should catch.
The operational damage grows under load:
- Timed-out requests still occupy model, connection, and worker capacity.
- A user retry creates a second copy of work that may overlap the first.
- Each layer can add its own retry loop, multiplying calls instead of recovering one request.
- A late tool action can change a record after the workflow has reported failure.
- Latency dashboards can look acceptable at the edge while hidden work drains downstream capacity.
Google's SRE guidance explains the distributed-systems mechanism. When servers finish work after client deadlines, that work is wasted, and retries can add more load to an already slow system. It recommends deadline handling and cancellation propagation through the call stack so doomed work stops instead of spreading overload.
Define the latency contract before setting timeouts
Start with the experience the workflow promises, not a random SDK timeout. An interactive workflow normally needs at least two targets:
- The first useful signal target says how long the user can wait before seeing an acknowledgement, progress state, or first streamed token.
- The final result target says how long the workflow has to return a complete answer or a clear deferred outcome.
These are separate targets. Streaming can improve perceived responsiveness without making the full computation faster. The OpenAI latency optimization guide recommends streaming, fewer requests, fewer generated tokens, and parallel work where dependencies allow it. Those techniques help, but they still need a hard completion deadline.
Assume an internal assistant promises a first visible response within 2 seconds and a complete response within 8 seconds at the chosen percentile. Do not hand all 8 seconds to the model. Reserve time for work the model does not control:
- 300 milliseconds for request validation and admission.
- 700 milliseconds for retrieval.
- 4,500 milliseconds for the initial model call.
- 1,200 milliseconds for one bounded tool call.
- 500 milliseconds for output validation and response delivery.
- 800 milliseconds of contingency for normal variance.
This is a planning budget, not a chain of fixed sleeps. Every step receives the remaining deadline, and unused time can flow forward. No step may assume it owns the full request budget.
Pass one absolute deadline through every boundary
Relative timeouts lose meaning as a request moves through queues and services. A worker that receives timeout: 8s after 5 seconds of queueing may run for another 8 seconds. Use an absolute deadline created at admission, then compute remaining time immediately before each operation.
The application should carry these fields in its execution context:
- A request identifier and idempotency key.
- The absolute completion deadline.
- A cancellation signal tied to the caller and deadline.
- The current attempt number and retry budget.
- Whether side effects are allowed, pending, or already committed.
A simplified TypeScript pattern looks like this:
type RunContext = {
requestId: string;
deadlineMs: number;
signal: AbortSignal;
attempt: number;
};
function remainingMs(ctx: RunContext): number {
return Math.max(0, ctx.deadlineMs - Date.now());
}
async function callModel(ctx: RunContext, input: ModelInput) {
const reserveForValidationMs = 500;
const available = remainingMs(ctx) - reserveForValidationMs;
if (available < 750) {
throw new DeadlineExceededError("insufficient time for model call");
}
return modelClient.generate(input, {
signal: ctx.signal,
timeoutMs: available,
});
}
The signal: ctx.signal line matters as much as timeoutMs. A timer can reject the local promise while the underlying request continues. Pass the signal into the network client, retrieval client, queue operation, and any tool adapter that supports cancellation. For clients without cancellation, isolate the call behind a worker pool with strict concurrency and treat its capacity as occupied until the operation really ends.
Create the deadline signal once. Combine caller disconnect, operator cancellation, and deadline expiry into that signal. Avoid generating unrelated timers at every layer because one timer may fire while another layer remains unaware.
Separate queue, attempt, and total time
A single timeout hides where time was spent. Use distinct measurements and controls for queue delay, one execution attempt, and the complete workflow.
Temporal's activity-failure documentation distinguishes schedule-to-start, start-to-close, schedule-to-close, and heartbeat timeouts. You do not need Temporal to use the model. Apply the same separation in any orchestrator:
- Queue budget: maximum time work may wait before a worker starts it.
- Attempt budget: maximum time for one model, retrieval, or tool attempt.
- Total step budget: maximum elapsed time across queueing and retries.
- Workflow deadline: maximum elapsed time for the user-visible operation.
Record each duration separately. A queue breach asks for admission control or more capacity. An attempt breach asks for a faster dependency, smaller request, or fallback. A total breach may indicate that retry policy is consuming the workflow budget.
Long-running tools need progress checkpoints. Temporal notes that heartbeat-aware activities can receive cancellation and persist progress for a later attempt. For your own workers, use a similar cooperative check between safe units of work. Do not interrupt a database transaction halfway through a write. Check the deadline before starting the transaction, commit or roll it back, then stop.
Admit retries only when the remaining budget can succeed
Retries are not free recovery. They spend latency and capacity. A retry that starts with 300 milliseconds remaining against a dependency whose normal response takes 1 second cannot help the user.
Before another attempt, require all of the following:
- The error is classified as transient.
- The operation is safe to retry or carries an idempotency key.
- The shared retry count has not been exhausted.
- The remaining deadline exceeds backoff, expected attempt time, and response reserve.
- The cancellation signal is still active.
Use one retry owner for each dependency path. If the model SDK retries, the workflow layer should know and budget for it, or SDK retries should be disabled. Otherwise, three workflow attempts multiplied by three SDK attempts becomes nine calls.
A practical decision is:
retry_allowed =
transient_error
and idempotent_operation
and attempts_remaining > 0
and remaining_time > backoff + expected_attempt + response_reserve
and not cancelled
Prefer bounded exponential backoff with jitter for overload responses, but stop when the deadline cannot fund another useful attempt. If the request can be deferred, return a durable job identifier instead of holding the interactive connection open through several slow attempts.
Protect tools from late and duplicate actions
Cancellation cannot guarantee that a remote system will undo work already accepted. Build tool safety independently of the response connection.
Assign an idempotency key to every consequential action. Store an action record before calling the tool with states such as planned, submitted, confirmed, failed, and unknown. If the deadline expires after submission but before confirmation, mark the outcome unknown and reconcile it. Do not immediately submit the action again.
Check the remaining deadline before starting a side effect. Reserve enough time to receive and persist the result. If that reserve is unavailable, stop before the call and present a deferred or manual path.
For an invoice approval workflow, the safe sequence is:
- Validate the proposed approval against policy.
- Create an action record keyed by invoice and workflow request.
- Recheck the deadline and cancellation state.
- Submit once with the same key where the target API supports it.
- Persist the provider response before returning success.
- Reconcile any
unknownoutcome without issuing a blind duplicate.
This prevents a timed-out model path from turning into an untracked late payment or approval.
Use streaming without confusing progress with completion
Streaming is useful when the model can produce a meaningful response before every downstream step finishes. It does not replace cancellation or the final deadline.
Measure at least:
- Time from admission to first streamed byte.
- Time from admission to first useful token or progress event.
- Time to complete model generation.
- Time to validate and commit any tool result.
- Time until cancellation is observed by each dependency.
Do not stream a claim that a business action succeeded before the tool confirms it. Stream status such as "checking policy" or "preparing the request" only when that status reflects real state. If the client disconnects, propagate cancellation, then decide whether a submitted side effect must continue to reconciliation even though response generation stops.
Implement the timeout budget in seven steps
Use this sequence to avoid adding local timers that do not form a system:
- Choose targets from the workflow's actual use case. Set first-useful-signal and completion targets for interactive paths. Define a separate contract for deferred jobs.
- Create the absolute deadline at admission. Reject work that cannot enter the queue with enough time to execute safely.
- Carry deadline and cancellation in one run context. Require every adapter to accept that context.
- Allocate reserves around side effects and response delivery. Never let model generation consume the time needed to validate and persist its result.
- Make retries deadline-aware. Centralize attempt counts and account for hidden SDK retries.
- Instrument queue, attempt, total, and cancellation delay. Link them with the request and trace identifiers.
- Test termination, not just the returned error. A timeout test passes only when the underlying model or tool work stops or enters an explicit reconciliation state.
Keep the first rollout narrow. Enable the contract for one workflow, observe the chosen percentiles, and tune budgets from traces. Do not loosen the top-level deadline merely because one step is slow. Decide whether that step needs a smaller input, parallel execution, a faster model, a deferred path, or a controlled fallback.
Common timeout designs that fail
Rejecting the promise without aborting the request
A Promise.race against a timer returns control to the caller, but it does not necessarily stop the losing operation. Verify that the underlying client received an abort signal and closed or cancelled its request.
Giving every layer the full timeout
An 8-second API timeout, 8-second model timeout, and 8-second tool timeout can produce a much longer workflow. Downstream calls need the shrinking remaining deadline, not a fresh allowance.
Retrying after the user-visible deadline
Continuing can be correct for a durable background job, but it must be an explicit state transition. An interactive request should not silently become an unbounded background task.
Cancelling during an irreversible write
Cooperative cancellation belongs at safe boundaries. Check before a transaction, finish or roll it back, record the outcome, then stop. Pair cancellation with idempotency and reconciliation.
Optimizing average latency only
Averages hide the slow tail that triggers timeouts and retries. Track percentile latency, queue age, cancellation delay, retry amplification, and the count of operations still running after their parent deadline.
Verify that abandoned work really stops
Build a small fault suite before rollout. Use a fake model server and fake tool endpoint so each delay and outcome is reproducible.
Run these checks:
- Delay the model beyond its attempt budget. Assert that the client aborts and worker capacity returns.
- Disconnect the caller during generation. Assert that retrieval and model cancellation signals fire.
- Delay queue pickup until little time remains. Assert that the worker refuses to start an attempt it cannot finish.
- Return a transient model error near the deadline. Assert that no futile retry begins.
- Delay a tool response after accepting an action. Assert that the action becomes
unknownand enters reconciliation instead of being duplicated. - Simulate an SDK with hidden retries. Assert that the workflow's call count and deadline still hold.
- Stream tokens, then cancel. Assert that no completion or tool-success event is emitted after cancellation.
Add one production alert for work observed after its parent deadline. That metric catches adapters that reject locally but fail to stop downstream execution. Track retry calls per original request as a second guard against amplification.
Set the contract on one workflow now
Pick the interactive AI workflow with the most visible timeout complaints. Write down its first useful signal target, final deadline, queue reserve, model attempt budget, tool reserve, and retry owner. Then trace one cancellation from the HTTP handler into the actual model client and prove the network operation ends.
Increasing the timeout can hide the symptom while abandoned work and duplicate side effects remain. Start with one absolute deadline, propagate it, protect writes, and run the delayed-dependency tests above. Once those tests pass, LLM latency optimization is a workflow contract rather than a collection of unrelated timers.
References
- OpenAI latency optimization supports the guidance on streaming, request count, token count, parallelization, and perceived latency.
- Google SRE: Addressing Cascading Failures supports the discussion of deadlines, wasted work, retry amplification, overload, and cancellation propagation.
- Temporal: Detecting Activity failures supports the separation of queue, attempt, total, heartbeat, and cancellation controls.
- OpenClaw Lobster issue 131 is the practitioner report showing a workflow timeout that did not reach the underlying model request.