Parrot: Efficient Serving of LLM-based Applications with Semantic Variable

framework 2405.19888
llm-servingrequest-schedulingprefix-sharingdataflow-analysismulti-agent

Parrot: Efficient Serving of LLM-based Applications with Semantic Variable — L2 #

1. TL;DR #

Public LLM APIs are request-level, so the serving cluster is blind to how an app's many LLM calls connect. Parrot's Semantic Variable annotates prompt regions (task/input/output) and doubles as an inter-request data pipe, letting the service recover a request DAG + prompt structure and jointly do dependency co-scheduling, latency/throughput objective deduction, and dynamic prefix sharing — up to 11.7× faster, 12× higher request rate.

2. Q1 / Q2 / Q3 #

Q1 — 痛点 (pain). An LLM-based application (agent / co-pilot) issues tens of LLM calls to finish one task (Table 1: 2–40 for doc analytics, 14 for MetaGPT, 17 for AutoGen), but must express each through the request-level completion API Completion(prompt) → text. Three consequences follow: (a) consecutive-request overhead — dependent calls bounce back to a client in another datacenter, so 30–50% (up to >70%) of API latency is network + re-queuing, not GPU work; (b) misaligned objectives — the service blindly optimizes per-request latency, but the app cares about end-to-end time, and per-request latency optimization can be exactly wrong (a Map stage should maximize throughput); (c) redundant computation — long static system prompts mean 72–99% of tokens are repeated across requests, recomputed every time.

Q2 — 方法 (method). Introduce Semantic Variable: a named placeholder region in a prompt ({{input:task}}, {{output:code}}) that (i) preserves prompt structure so the service can hash prefixes at variable boundaries, and (ii) connects producer/consumer requests into a data pipeline, so the service recovers the request DAG at runtime. On top of two primitives — DAG dataflow (GetProducer/GetConsumers/GetPerfObj) and PrefixHash — Parrot layers four joint optimizations: graph-executor serving of dependent requests, reverse-topological performance-objective deduction (propagate the final get(perf=...) criterion back to intermediate requests, grouping parallel requests into task groups), Semantic-Variable-granularity prompt-prefix sharing with a fused FlashAttention+PagedAttention kernel, and an application-centric scheduler (Algorithm 1). 核心技术壁垒: turning the app's programming abstraction into a service-side dataflow-analyzable structure — i.e. keeping the template unrendered across the API boundary (unlike LangChain, which renders placeholders before submission) so a public multi-tenant cluster can derive dependency + commonality just-in-time from otherwise-opaque requests.

Q3 — 结果 (result). Up to 11.7× end-to-end speedup (multi-agent MetaGPT vs latency-centric baseline) and 12× higher sustainable request rate (multi-GPU GPTs serving). Component wins: chain-summary 2.38× with background load; map-reduce 2.37× from objective deduction; Bing Copilot 1.1–1.7× over vLLM's shared-prefix baseline from the better kernel; mixed chat+map-reduce 5.5× normalized-latency for chat over latency baseline while matching throughput baseline on map-reduce.

3. 架构 / 方法图 #

Parrot splits into a front-end (SemanticFunction / Semantic Variable programming model, OpenAI-like APIs with submit/get), a centralized Manager (DAG + PrefixHash analysis, objective deduction, application-centric scheduler), and LLM Engines (one or a group of GPUs, Fill/Generate/FreeContext abstraction + shared-prefix kernel).

Figure 6: Parrot system overview

Paper's Figure 6, verbatim (caption: "Parrot system overview"). This is the load-bearing architecture figure: the app talks Semantic Variables to the Manager, which owns cluster-level scheduling and hands lowered requests to independent engines. Note the manager is a first-class scheduler + analyzer sitting between the orchestration framework and the engines — that placement is what lets it see the whole DAG rather than isolated requests.

Figure 8: Primitives (selected) for Inter-Request Analysis

Paper's Figure 8, verbatim (caption: "Primitives (selected) for Inter-Request Analysis"). Shows the two analysis substrates the scheduler consumes: the request DAG (nodes = requests or Semantic Variables) and prompt structure. GetProducer/GetConsumers recover dependency; PrefixHash emits a hash at each Semantic-Variable boundary for commonality detection. Everything downstream (§5) is a query over this structure.

The request/task lifecycle is: app calls a SemanticFunctionsubmit API lodges the prompt + input Semantic Variables (returns a future) → Manager inserts a node into the session DAG and analyzes it → graph executor dispatches once producers finish → engine runs Fill (prompt/prefix) then Generate (decode) under continuous batching → output Semantic Variable materialized into a per-variable message queue → downstream request (or client get) consumes it.

sequenceDiagram participant App participant Mgr as Parrot Manager (DAG + scheduler) participant Q as SemVar message queue participant Eng as LLM Engine App->>Mgr: submit(WritePythonCode, task) → future(code) App->>Mgr: submit(WriteTestCode, task, code) → future(test) Note over Mgr: dataflow analysis: code links R1→R2 (dep DAG) Mgr->>Eng: dispatch R1 when producers ready (Fill+Generate) Eng->>Q: materialize code Q->>Mgr: code ready → R2 becomes schedulable Mgr->>Eng: dispatch R2 (prefix-share with R1 context if colocated) Eng->>Q: materialize test App->>Mgr: get(code/test, perf=LATENCY) Mgr-->>App: values (network+queue overhead of R1→R2 bypassed)

Sequence redraw: the paper lacks a single lifecycle figure; this makes explicit that the second request never round-trips to the client, which is the mechanism behind the eliminated network/queuing overhead.

Scheduler discipline: topological-order queue, with affinity toward same-app / same-task-group / shared-prefix colocation (Algorithm 1, §5). KV/memory manager: separated into the engine, allocation unit = vLLM paged blocks, with cross-request context fork for prefix sharing. Cross-node: the Manager dispatches to independent engines (single or multi-GPU via TP/SP inside an engine); inter-engine coordination is via the centralized manager, not a collective.

4. 作者证明 #

无形式化作者证明 — 仅实证. Parrot has no throughput/latency cost model, no theorem, and no closed-form optimization objective. The only "equation" is the completion API signature, reproduced to name the abstraction it argues against:

Notation of the one formal object:

SymbolMeaning
prompt : strclient-supplied input text (the entire per-request interface today)
generated_text : strmodel completion returned

$$\mathrm{Completion}(\text{prompt}:\text{str}) \rightarrow \text{generated\_text}:\text{str}$$

Physical meaning: the signature is deliberately structureless — one opaque string in, one string out — which is precisely why a multi-tenant service cannot see which requests share a prefix or depend on each other. The whole paper is an argument that widening this signature to carry Semantic Variables is the minimal change that restores optimizability.

The method is instead expressed operationally via primitives + Algorithm 1 (scheduling pseudocode) and the reverse-topological objective-deduction procedure. Six checks against the paper's own logic (substituting for the absent formal model):

  1. Objective deduction direction is reverse-topological — latency criteria propagate from final get variables backward to predecessors; consistent with Fig 9 labeling Requests 1/2 (direct producers) and Request 3 (immediate predecessor) all latency-sensitive.
  2. Task-group grouping is by DAG stage — parallel requests at the same depth form one group (Task Groups 0/1), and the scheduler minimizes the group completion time, not per-request latency. Consistent with the map-reduce 2.37× coming from batching the Map group.
  3. Throughput vs latency conflict is memory-bandwidth-rooted — the paper's premise "inference is memory-bound, latency ∝ concurrent token count" implies a per-engine token-capacity knob; the 40 ms/token target maps to a capacity ceiling (~6144 tokens from Fig 10), which is why a latency request on a throughput engine collapses capacity from 64,000 → 2,000. Internally consistent.
  4. First-order sanity of the redundancy claim — Table 1's 94% (Chat Search) / 99% (AutoGen) repeated-token fractions imply near-total prefill waste, matching the §8.3 result that prefix sharing + affinity scheduling is the dominant lever (12× → 3× when affinity off).
  5. Speedup decomposition adds up — the 11.7× multi-agent number is claimed to compound objective deduction + dynamic prefix sharing (the latter alone 2.35×; vs throughput baseline still 2.45×). No single mechanism is asked to carry 11.7× alone; consistent.
  6. Scheduler capacity assertion is falsifiable and tested — the claim "co-locating a latency request with an existing latency request gives negligible capacity reduction" is exactly what §8.5 mixed-workload isolation (chat and map-reduce on separate engines, 5.5× / 1.23×) exercises.
  7. What a model would have clarified: an explicit end-to-end completion-time expression over (batch capacity, task-group depth, network RTT, prefix-share ratio) would let one predict the 2.4× / 11.7× rather than measure them, and would expose the regime boundary where objective deduction stops paying (long outputs, where generation dominates and Parrot's advantage "diminishes" per §8.2).

    5. 实验与数据 #

    Testbed: single-GPU A100-80GB (LLaMA 13B) and multi-GPU 4×A6000-48GB (LLaMA 7B), CUDA 12.1. Baselines: LangChain apps over FastChat routing to vLLM / HuggingFace engines; FastChat treats all requests as independent + latency-sensitive. Engine capacity ceiling calibrated from Fig 10 (TPOT rises sharply past ~6144-token batch).

    Figure 4: Request-centric vs application-centric scheduling for map-reduce summary

    Paper's Figure 4, verbatim (caption: "Request-centric scheduling v.s. application-centric scheduling for the map-reduce style document summary task"). Motivation figure for objective deduction: request-centric scheduling caps batch size to protect each Map request's latency, leaving the engine under-utilized; application-centric scheduling recognizes the Map group is latency-insensitive individually and batches aggressively, shrinking total DAG completion time.

    Figure 14: E2E latency of Map-Reduce summary

    Paper's Figure 14, verbatim (caption: "Average E2E latency of Map-Reduce document summary with varying output lengths and chunk sizes"). The 2.37× map-reduce speedup: both systems dispatch Map requests concurrently, but only Parrot deduces the Map task group is throughput-preferred and raises the capacity beyond the baseline's fixed 4096-token latency cap.

    Figure 17: Serving multiple GPTs applications

    Paper's Figure 17, verbatim (caption: "Serving multiple GPTs applications"). The 12× headline for the popular-app regime. Crucially, turning off affinity scheduling drops the gain to 3× — cluster-level co-location of shared-prefix requests, not the kernel alone, is the dominant lever; the fused kernel adds a further 2.4× over vLLM PagedAttention on top.

    Figure 18: Latency and KV-cache memory for multi-agent programming

    Paper's Figure 18, verbatim (caption: "The latency and memory usage for multi-agent programming, with varying number of files to program"). The 11.7× multi-agent result and its memory story: 18(b) shows Parrot-without-sharing hits the GPU memory ceiling, because MetaGPT roles share dynamically generated context that vLLM's static-prefix sharing cannot detect — Semantic-Variable granularity is what makes runtime sharing possible.

    6. 论证链 #

    #Step (paper-internal)Support
    1Apps make tens of dependent LLM calls, but the API is request-level, so the service loses dependency, objective, and commonality info.§1–§3, Table 1 (2–40 calls, 72–99% repeated tokens)
    2Lost info causes three measured harms: 30–50% latency is network/queue; per-request latency optimization is wrong for Map stages; shared prefixes recomputed every request.Fig 3 breakdown; Fig 4; §3 redundancy
    3A Semantic Variable — an unrendered named prompt region that also links requests — restores prompt structure + request DAG at the service side.§4.1–§4.2, Fig 7 code, Fig 8 primitives
    4Two primitives (DAG dataflow, PrefixHash) let the Manager query dependency and commonality just-in-time across tenants.§4.2
    5Four optimizations built on the primitives resolve the three harms: graph-executor (dependency), reverse-topological objective deduction + task groups (objective), Semantic-Variable-granularity sharing + fused kernel (commonality), app-centric scheduler (colocation).§5.1–§5.4, Algorithm 1
    6On four representative workloads the combined optimizations yield up to 11.7× / 12×, with component attributions matching the mechanism each targets.§8.2–§8.5, Figs 11–19

    7. 实现 cross-reference #

    Implementation is described but the repository is not cited in L1 with line numbers, so per-line pointers are [实现未公开] at the file:line granularity; the paper supplies concrete API/kernel/abstraction specs instead:

    • Scale: ~14,000 LoC Python total — front-end 1,600, manager 3,200, engine 5,400 Python + 1,600 CUDA (§7). Front-end uses FastAPI; engine built on vLLM + xFormers kernels.
    • APIs (§7): submit body carries prompt, a placeholders[] list (each {name, in_out, semantic_var_id, transforms}), session_id; get body carries {semantic_var_id, criteria, session_id}. The placeholders + transforms fields are the on-wire form of Semantic Variables.
    • Universal engine abstraction (§7): three methods — Fill(token_ids, context_id, parent_context_id), Generate(sampling_configs, context_id, parent_context_id), FreeContext(context_id). Context fork (prefix sharing) is expressed by setting parent_context_id; this is the integration contract any engine must satisfy to join a Parrot cluster.

    核心技术壁垒 (§7 detail): the fused shared-prefix decode kernel (OpenAI Triton + CUDA). It keeps PagedAttention's paged KV storage but, borrowing FlashAttention tiling, loads shared-prefix KV tiles into shared memory exactly once, computes interim attention metrics (scores, qk_max, exp_sum) for the prefix and writes them to HBM, then processes each user's diverged tokens and merges with the prefix's interim results. vLLM's kernel, by contrast, reloads shared tokens from L2 to shared memory per request — the redundant memory traffic that Parrot removes (worth 1.1–1.7× on Bing Copilot, up to 2.4× rate on GPTs). This is the hardest piece to replicate: it requires a custom attention kernel that is aware of a runtime-discovered, dynamically-generated shared prefix, not just a static one.

    关键实现细节 (easy-to-miss tricks):

    1. Splitting Fill/Generate is not just an engine nicety — it maps constant text + input Semantic Variables to Fill and outputs to Generate, breaking request-level dependency into finer granularity that enables parallel/pipelined execution (the same enabler DistServe/Splitwise exploit).
    2. Semantic Variables must stay unrendered across the API boundary; wrapping an existing LangChain template means submitting both the template and its variables (§6), else the structure the whole system relies on is erased before the service sees it.