CS2680 Modern AI Systems: Agents and System Optimizations
Lecture 22 — Efficient agent serving systems I (continued): scheduling agentic programs

The agent-serving block improves the serving of agents by telling the system more: the DAG in this meeting's first half, and the interception type, return probability, and fan-out width on Nov 23. This half closes the axis from the other end and asks what a scheduler can do when it is told none of that — because the honest state of the world is that most clients will never declare anything, and the requests arriving at your engine right now carry no structure at all. Autellix's answer is that you do not need to be told: a program reveals its own length by consuming service, and a scheduler that accumulates service per program rather than per request recovers most of shortest-job-first without any clairvoyance. Along the way we will find that the API's choice to account by request rather than by program silently hands a 67× service-rate advantage to whoever issues more requests, which is a fairness bug hiding inside what looks like a neutral policy. Teola supplies the other half: a graph inside each request, which is the same idea one level down. By the end you should be able to place every mechanism in this block on a single granularity ladder and say what each one had to be told.

Date: Wednesday, November 18, 2026 · Instructor-led. Nothing is due today; Assignment 5 (optimize the full stack) is in flight, due Dec 2, and the final project is under way, its report due Dec 8. These notes are the second half of one meeting — "Efficient agent serving systems I" — whose first half is lecture18-agent-serving-1.md; the agent-serving block finishes on Mon Nov 23, and Part II with it.

Required Autellix — the paper today's first half promised you. Read the motivation as a direct attack on Part II's foundations: engines schedule requests, agents are programs, and a program that issues many requests is punished by every per-request policy in the course. Study the non-clairvoyant scheduling design until you can state what statistic it accumulates and at what granularity, and be ready to explain why that statistic approximates job length without predicting it. Then read the evaluation for which workload shapes benefit — the gains are not uniform across program structures, and saying which shapes gain is the discussion's centre.

Required Teola — the same argument one level down. A single "request" in a real application is a small dataflow graph of primitives — embed, retrieve, rerank, generate — with different resource needs, and treating it as one opaque unit forces serialization that the graph makes unnecessary. Read the primitive-level graph representation and the optimizations it enables; skim the implementation. Hold one question: Teola exposes structure within a request and Autellix infers structure across requests — is there a system that needs both, and what would it look like?

Optional SAGA — workflow-atomic scheduling on GPU clusters: the same unit-of-work argument taken to a whole workflow and a whole cluster. Helium — agentic workflows read from a data-systems perspective, which is a genuinely different framing of everything in this block. Agentic AI workload characteristics — the measurement paper; use it to check whether the distributions Lecture 21 assumes resemble anything real. Continuum — a required paper for the Nov 23 meeting, previewed here because its central claim is exactly today's: retention policy and admission order are one mechanism.

Where this sits

This is the second half of the meeting that opens the agent-serving block. The serving lectures from Sep 28 through Nov 11 built a serving stack whose unit of work was the request, because that is what the API delivers. This meeting's first half shows what declaring more buys; Nov 23 spends the declaration on stalls, state, and fan-outs. Today asks the residual question — what if nothing is declared? — and it is the practically important one, because every client that has not been rewritten is in exactly that position. Turning the relationship around — using LLMs to do systems research rather than doing systems research on LLMs — is optional content on this calendar rather than a block of its own.

Standing assumptions, unchanged through the block. Reference 7B, GQA-8, H100, ambient TPOT 22.6 ms, per-resubmission queueing Q = 200 ms (illustrative, from Lecture 18). Today's traffic mix, used throughout and stated once: 100 chat programs of 3 requests and about 200 output tokens total, plus 10 agent programs of 200 requests and about 16,000 output tokens total. That is 110 programs, 2,300 requests, and 180,000 output tokens — a mix in which agents are 9% of the programs and 89% of the work.

Instructor notes — Timing plan

75-minute class (Mon/Wed 11:15am–12:30pm, SEC LL2.221). Instructor-led; the table is the plan for this half of the meeting. The instructor holds the framing, the §22.4 synthesis, and the block wrap, and leads the two paper deep-dives.

TimeSegmentNotes
0–8Framing: the unit of work (§22.1–22.2)Instructor. The 199 × Q re-entry penalty and the traffic mix on the board before the deep-dives start.
8–28Paper 1 deep-dive: Autellix (§22.3)Protected. Must get to what statistic is accumulated and at what granularity. Instructor forces the "why is this not just SJF" question.
28–40Synthesis: what non-clairvoyance costs (§22.4)Protected — instructor-held. The 8.6× mean-flow figure and the MLFQ argument must be on the board when the bell goes.
40–50Fairness (§22.5)The 67× derivation live. Expect the room to argue; let them, it is the best discussion in the block.
50–65Paper 2 deep-dive: Teola (§22.6)Instructor. Push toward resource heterogeneity — why a graph and not a pipeline.
65–73The granularity ladder and block wrap (§22.7)Instructor. The whole block, one table.
73–75Logistics: Assignment 5 due Dec 2, project report due Dec 8Announcement only.

Reading-only, not scheduled: §22.8's discussion seeds beyond the first three, and the SLO decomposition in §22.5.

If running long: compress §22.6 to the primitive graph and one optimization; cut seeds 4–5. Never cut §22.4 or the ladder in §22.7 — the ladder is the only place the whole block is assembled in one view, and students will want it for Assignment 5.

Learning objectives

By the end of this class you should be able to:

  1. Explain why a program-structured workload is punished by every per-request scheduling policy, and quantify the penalty as a function of program length.
  2. State what a non-clairvoyant scheduler accumulates and why that approximates shortest-job-first without predicting job length.
  3. Compute mean program completion time under shortest-first and longest-first orderings and say what the gap bounds.
  4. Distinguish per-request, per-program, and per-token fairness, and compute the service-rate advantage that per-request accounting hands to long programs.
  5. Explain what a primitive-level graph inside a request exposes that an opaque request hides.
  6. Place all five agent-serving topics on one granularity ladder and say what each mechanism must be told.

22.1 The unit of work is the program

Define it precisely, because the whole lecture turns on the definition. A program is the set of model invocations belonging to one user task, connected by dependence: request k+1's prompt contains request k's output, or is decided by it. Lecture 5 characterized this stream; Lecture 18 called its shape a DAG and had the client declare it. Today we assume nothing is declared and the engine sees only what arrives: a sequence of independent-looking requests, some of which happen to be continuations of others.

Three properties of a program that no single request exposes.

Its length is unknown, and unknowable in advance. A chat program is three requests. A coding agent is two hundred, or twelve, depending on whether the tests pass. Lecture 18's §6 discussion made this point about declaration — dynamic control flow cannot be declared because the edge does not exist until the value does — and it applies with more force to scheduling, because a scheduler must act now.

Only its completion is user-visible. Lecture 18 established this for the sink of a DAG; it is the same fact. An intermediate request's latency is worth nothing to anybody, and any metric computed over requests is measuring something no user experiences.

It re-enters the queue once per step. This is the property with teeth, and §22.2 prices it.

22.2 Why per-request scheduling punishes long programs

Take a scheduler with no ill intent: plain first-come-first-served over arriving requests, which is what most engines approximate. A program of n steps submits its first request, waits, receives the output, and submits the second — which arrives now, behind everything that has arrived in the meantime, and waits again.

queueing penalty for an n-step program = (n − 1) × Q

At Lecture 18's illustrative Q = 200 ms, a 20-step program pays 3.8 s and a 200-step program pays 39.8 s — before any of its actual work is done. The penalty is linear in program length, and no individual request was treated unfairly at any point. That is the shape of the problem: a policy that is fair to every request is systematically unfair to long programs, because length is measured in re-entries.

It gets worse under load. FCFS with continuous batching does not simply queue; it shares. Each request in the running set gets a slice, so a program's service rate is proportional to how many requests it has in flight — which for a sequential agent is exactly one, no matter how many it will eventually issue. Meanwhile a fan-out program with sixteen concurrent workers has sixteen. The accounting unit rewards concurrency, not need.

The 67× advantage, on the standing mix

110 programs issue 2,300 requests: 100 chats × 3 = 300, and 10 agents × 200 = 2,000.

Under per-request fair sharing each request gets 1/2,300 of the engine, so a chat program's share = 3/2,300 = 0.13% an agent program's share = 200/2,300 = 8.70% An agent program receives 67× the service rate of a chat program.

Under per-program fair sharing each of the 110 gets 0.91%. Chats speed up ; agents slow down 9.6×.

In wall clock: a chat needing 4.52 s of exclusive service completes in 58 minutes under per-request sharing and 8.3 minutes under per-program sharing.

Nobody designed that 67×. It is an artifact of the unit the API happens to expose, and it is the exact analogue of a network scheduler that gives bandwidth per packet rather than per flow — where the fix, fair queueing, is fifty years old and required only that the scheduler learn to group packets by flow. The agent version needs the same thing: group requests by program. Which requires knowing which requests belong together, and that is where Autellix starts.

Instructor notes

Minutes: 8 (with §22.1). Board: (n − 1) × Q, then 3.8 s and 39.8 s. Then the 0.13% / 8.70% / 67% column. Ask the room: "Which scheduling policy in this course was unfair to agents?" All of them. Then: "which of them knew it?" None — because none had a way to name a program. Expect confusion: Students read the 67× as a claim that agents are being favoured deliberately. It is emergent from the accounting unit, which is why it is interesting.

22.3 Autellix: scheduling programs you were never told about

(Instructor-led. What follows is the framing the discussion should reach, not a substitute for it.)

Two problems have to be solved and they are separable.

Grouping. Which requests belong to the same program? This is the easy half in practice — a session identifier, an API key plus a conversation id, or simply the observation that this request's prompt contains the previous response verbatim. Note the last one: prefix matching, which Lecture 13 built for caching, is also a grouping signal. The radix tree already knows these requests are related; the scheduler was simply never shown.

Ordering. Given groups, in what order should they run? Shortest-job-first minimizes mean flow time and is unimplementable, since program length is unknown. The classical answer, and Autellix's, is to stop predicting and start observing: accumulate service per program, and demote a program as its total consumed service grows. New programs enter at high priority and finish fast if they are short; long programs sink through the levels and keep progressing at lower priority. That is multi-level feedback queue scheduling, applied at a granularity the engine did not previously have.

The three things the discussion should extract, in order of importance.

  1. What is accumulated, and where. Service consumed by all requests of the program, carried across requests — including across the gaps when the program was stalled in a tool call. A per-request scheduler resets this to zero every step, which is precisely why a long program looks like an endless supply of short new arrivals.
  2. Why this is not simply SJF. SJF needs the length; MLFQ needs only the history. The price is that a long program is discovered to be long only by running it, so the first quantum of a two-hundred-step program is scheduled as though it were a chat. That mis-scheduling is bounded and is what non-clairvoyance costs.
  3. What it does about starvation. Any demotion scheme starves the demoted without an aging or reservation mechanism, and the choice made there is a fairness policy in disguise — §22.5.

22.4 What non-clairvoyance costs, in numbers

(Instructor-held synthesis. Protected.)

Bound the prize before evaluating any mechanism. Take the standing mix on a single server, with service measured in token-units, and compare the two extreme orderings.

Mean program completion, best and worst ordering

Service: 100 short programs at 200 units, 10 long at 16,000 units.

Shortest-first. Shorts complete at 200, 400, …, 20,000 → sum = 200 × 5,050 = 1,010,000. Longs then run: completions 36,000, 52,000, …, 180,000 → sum = 10 × 20,000 + 16,000 × 55 = 1,080,000. Total 2,090,000 over 110 programs → mean = 19,000 units.

Longest-first. Longs complete at 16,000, …, 160,000 → sum = 880,000. Shorts then run from 160,000 → sum = 100 × 160,000 + 200 × 5,050 = 17,010,000. Total 17,890,000 → mean = 162,636 units.

Ratio: 8.6× between the best and worst orderings of the same work.

That 8.6× is the entire prize available to any program-aware scheduler on this mix, and it is worth noting what produced it: not faster kernels, not better batching, not a byte saved anywhere. The same work, reordered. It is also worth noting the direction — the 8.6× is available because the mix is skewed, with a few enormous programs among many tiny ones, and Lecture 21's tail discussion says agent traffic is reliably skewed like this.

MLFQ does not achieve the full 8.6×, because it pays to discover which programs are long. The mis-scheduling is bounded by the first quantum per program, and with 110 programs and a quantum small relative to 16,000 units the loss is a few percent of the gap — which is why non-clairvoyant scheduling is a good deal, and the number to keep is that most of an 8.6× is recoverable without knowing anything.

Two caveats that the discussion should surface rather than the notes.

Mean flow time is not the only objective. SJF minimizes it while maximizing the variance of the experience: the ten long programs wait behind everything. If your service level agreement is about tail program latency rather than mean, the optimal ordering changes, and §22.5's fairness question is the right frame.

A single server is a fiction. Real engines batch, so "ordering" is really "admission to the running set," and a program at low priority is not stopped but slowed. That is strictly better than the model above and makes starvation less acute, at the price of making the analysis harder.

Instructor notes

Minutes: 12. Protected. Board: The two sums, then 19,000 versus 162,636, then 8.6× boxed. Underneath: "same work, reordered." Ask the room: "What fraction of the 8.6× would you give up to avoid starving the long programs?" There is no right answer; the question makes the fairness section necessary. Expect confusion: Students want to know which the engine "actually does." Most do neither — they do per-request FCFS, which is closer to random ordering than to either extreme, and random lands nearer the bad end because the long programs are large enough to block whatever follows them.

22.5 Fairness: what is fair between a chat and an agent?

Three defensible definitions, three different systems.

Per-request fairness. Every request gets an equal share. This is what the API's unit implies and what most engines implement by default. §22.2 showed what it produces: a 67× service-rate advantage for programs that issue more requests. Almost nobody would defend it if it were stated out loud, and almost everybody deploys it.

Per-program fairness. Every program gets an equal share, regardless of how many requests it issues. Intuitive, and it is what a user would expect if told how the system works. But it means a 200-step agent and a 3-request chat receive the same capacity, so the agent takes 67× longer than it otherwise would — and if the agent is a paying customer's production workload and the chat is somebody idly asking a question, that is not obviously right either.

Per-token fairness. Every program gets a share proportional to work done, which is weighted fair queueing with tokens as the currency. This is the one that generalizes cleanly, prices correctly, and requires the least explanation to a customer — and it says nothing about latency, which is what people actually complain about.

The useful metric for comparing them is slowdown: a program's completion time divided by the service it required. A perfectly served program has slowdown 1; a chat that waits 58 minutes for 4.52 seconds of service has a slowdown of 770. Reporting mean slowdown rather than mean latency is what makes short and long programs comparable at all, and it is the metric the discussion should ask each paper to report.

SLO decomposition, for completeness and as reading. If a program carries an end-to-end deadline D over n steps, the naive decomposition gives each step D/n, which is wrong whenever steps differ — and they always do, since a step's cost depends on a context that grows. The better formulation schedules by laxity: D minus the predicted remaining work, smallest first, which is earliest-deadline-first adapted to unknown remaining length. It needs a length predictor, which returns us to non-clairvoyance, and the honest conclusion is that program SLOs are only as good as the predictor behind them — which is why Autellix does not offer them.

22.6 Teola: the graph inside a request

(Instructor-led.)

Autellix zooms out from the request to the program. Teola zooms in, and the observation is that what an application calls one request is already a small dataflow graph: embed the query, retrieve candidates, rerank them, generate an answer. The stages have different resource profiles — retrieval is CPU and memory bound, embedding and generation are GPU bound — and an opaque request forces them into a sequence on one timeline.

What the graph exposes, which the discussion should draw out:

Heterogeneous resources can overlap. While one query's retrieval runs on CPU, another's generation can run on GPU. Serialized within an opaque request, these alternate and each leaves the other resource idle; expressed as a graph across a batch of requests, they pipeline.

Stages have different batching behaviour. Embedding batches beautifully, retrieval batches differently, generation batches under all of Part II's constraints. One batching policy for the whole request is necessarily wrong for two of the three.

The critical path is visible. In a RAG request where generation is 2 s and everything else is 90 ms, optimizing retrieval is worth at most 4% — and knowing that requires the graph. Teola's contribution is less any single optimization than making the request's internal structure a first-class object so that such questions can be asked at all.

Which is the same contribution as Parrot's, at a different granularity, and the discussion should notice the pattern: every paper in this block improves things by making a structure explicit that was previously implicit, and they differ only in which structure and whose job it is to declare it.

22.7 The granularity ladder, and the block in one table

(Instructor-held. The synthesis the block has been building toward.)

Arrange every unit of work this course has scheduled, smallest to largest:

token → request → primitive graph → program → workflow → fleet

Part II's earlier lectures lived entirely at the first two. The agent-serving block is the story of climbing the rest of the ladder, and each rung required somebody to know something.

LectureQuestionUnitWhat the system must be toldWho tells it
18 — ParrotWhat crosses the API?Request DAGTemplates and static edgesThe application, explicitly
19 — InferCeptWhat happens while nothing generates?InterceptionThe tool name, and ideally the stall distributionNobody — it is already in the request
20 — ContinuumWhere does the state live?SessionProbability of returnThe application, or a predictor
21 — TokenCakeWhat if k run at once?Fan-joinWhich child is critical, and the tailThe orchestrator, structurally
22 — AutellixWho goes next?ProgramNothing. Service consumed is observedNobody
22 — TeolaWhat is inside one request?Primitive graphThe graphThe application, explicitly

Read the fourth column down and the block's argument appears. It opened at maximum declaration — Parrot, where the application ships its structure — and closes at zero, with Autellix recovering most of the benefit from observation alone. In between, Lecture 19 finds a bit of structure that crosses the API already and is thrown away, and Lecture 20 finds one that nobody sends at all.

The conclusion is not that one end wins. It is that the agent workload contains parts best served at every rung, which is Lecture 18's honesty paragraph proven out by the rest of the block: a fan-out's shared prefix is best handled structurally, a tool stall is best handled by a free signal already present, and a program's length is best handled by not trying to know it. A serving system for agents is not a system that picks one of these. It is a system that does not throw away the ones it is given.

The block in one sentence: every improvement in this block came from naming a unit of work larger than the request, and the units differ in who has to say so — the application, the harness, or nobody at all. Prefer the mechanisms that need to be told nothing; they are the ones that will work on the clients you do not control.

22.8 Discussion seeds

  1. The grouping signal is already in the cache. Lecture 13's radix tree knows that request k+1 shares a long prefix with request k's prompt-plus-output — which is nearly a proof that they belong to the same program. Should the prefix cache be the grouping mechanism for the scheduler? What breaks: two users of the same public system prompt, a program that compacts its context (Lecture 20's §20.4, which destroys the very prefix that identifies it), a fan-out whose sixteen children all match the parent equally well.
  2. Does non-clairvoyance survive fan-outs? MLFQ demotes a program as it consumes service. A sixteen-way fan-out consumes sixteen workers' worth of service in one round and is demoted quickly — but its user-visible latency is set by the barrier, not by service consumed. Does Autellix's accumulator do the right thing for Lecture 21's shapes, or does it penalize breadth?
  3. Which fairness definition would you deploy, and what would you tell customers? §22.5 gives three. Take a position and defend it against the case that most embarrasses it: per-request against the 67× advantage, per-program against a production agent starved by idle chats, per-token against a user who only cares about latency.
  4. Where do the two papers meet? Teola exposes a graph inside a request; Autellix infers structure across requests. Sketch the system that has both. What does its scheduler accumulate, and at which level does it make admission decisions? Is SAGA's workflow-atomic unit the answer or a third incompatible one?
  5. What would you measure to decide any of this? The block assumes distributions throughout — Lecture 21's uniform and exponential worker lengths, today's 100-plus-10 mix. The optional characterization paper measures real ones. Design the smallest instrumentation that would let you choose between per-request and per-program scheduling for a specific deployment, and say what result would change your mind.

Key takeaways

  • A program is the set of dependent invocations serving one user task. Its length is unknown, only its completion is user-visible, and it re-enters the queue once per step — so a per-request scheduler charges it (n − 1) × Q in pure queueing: 39.8 s for a 200-step agent at Q = 200 ms.
  • Accounting by request rather than by program hands a 67× service-rate advantage to programs that issue more requests. Nobody designed this; it is inherited from the API's unit, and it is the same bug fair queueing fixed for packets versus flows.
  • The prize is bounded by the gap between orderings: 8.6× in mean program completion on a skewed mix, available from reordering alone.
  • Non-clairvoyant program-level MLFQ recovers most of that by accumulating service per program instead of predicting length. It pays only for discovering which programs are long, and that cost is bounded by one quantum per program.
  • Fairness has three defensible definitions — per-request, per-program, per-token — and slowdown, not latency, is the metric that makes short and long programs comparable.
  • The block climbs a granularity ladder from token to fleet, and its mechanisms differ mainly in who must declare the structure. Prefer the ones that need to be told nothing.

Numbers worth memorizing

QuantityValueSource
Queueing penalty, n-step program(n − 1) × Q — 3.8 s at 20 steps, 39.8 s at 200Q = 200 ms, Lecture 18
Standing mix110 programs, 2,300 requests, 180,000 output tokensAgents 9% of programs, 89% of work
Per-request service sharechat 0.13%, agent 8.70% → 67×3/2,300 versus 200/2,300
Per-program share0.91% each — chats 7× faster, agents 9.6× slower1/110
Chat completion, per-request versus per-program58 min versus 8.3 min4.52 s of service
Mean program completion, best versus worst ordering19,000 versus 162,636 units = 8.6×Shortest-first versus longest-first
Slowdown of that chat under per-request sharing7703,465 s ÷ 4.52 s
Granularity laddertoken, request, primitive graph, program, workflow, fleet§22.7

Self-check

  1. Why does a scheduler that treats every request identically end up biased against long programs?Because program length is expressed as re-entries. A program of n steps rejoins the queue n − 1 times, paying Q each time, so its queueing cost is linear in a length the scheduler never sees. Every individual request was treated fairly; the program was not, and the scheduler has no object corresponding to the program in which the unfairness could be noticed.
  2. Where does the 67× come from, and what is the classical analogue?From counting requests instead of programs: agents issue 2,000 of the 2,300 requests while being 10 of the 110 programs, so per-request sharing gives each agent program 8.70% of the engine and each chat 0.13%. The analogue is per-packet versus per-flow bandwidth sharing, where a flow that sends more packets gets more bandwidth; fair queueing fixed it by grouping packets into flows, and program-level scheduling is the same fix with programs as flows.
  3. What does an MLFQ scheduler accumulate that a per-request scheduler resets, and why does that matter?Service consumed across all of a program's requests, carried through the stalls between them. A per-request scheduler resets the counter at every step, so a 200-step program presents as 200 consecutive new arrivals, each of which looks short. Carrying the counter is what lets a long program be recognized as long without anyone predicting its length.
  4. Non-clairvoyance is free of prediction. What does it cost instead?Mis-scheduling during discovery: every program is treated as short for its first quantum, so a long program receives high-priority service it does not deserve before being demoted. The loss is bounded by one quantum per program, which is small against the 8.6× available — which is the argument for preferring observation to prediction here.
  5. Your fan-out program is demoted quickly because sixteen workers consume service fast, yet its user waits on the barrier. Is the scheduler wrong?It is optimizing the wrong quantity. Service consumed is a proxy for job size, and for a fan-out it overstates the program's remaining latency-critical work: the barrier is set by the slowest worker, not by total service. Lecture 21's tail analysis and this scheduler disagree, which is discussion seed 2 and a genuine open edge.
  6. Both required papers "make a structure explicit." Name the structure in each and say who must declare it.Teola: the primitive-level dataflow graph inside one request — embed, retrieve, rerank, generate — declared by the application. Autellix: the grouping of requests into programs and the service each has consumed, declared by nobody and inferred by the engine. The pairing is the block's whole argument in two papers: the same kind of win, obtained at opposite ends of the declaration axis.

Exercises

  1. The penalty at scale. A deployment serves 1,000 concurrent programs whose step counts are distributed as 80% with 5 steps, 15% with 40, 5% with 300. At Q = 200 ms: (a) total queueing time paid per hour across the fleet; (b) the share of it paid by the 5%. Solution sketch: Per program: 0.8 × 4 + 0.15 × 39 + 0.05 × 299 = 3.2 + 5.85 + 14.95 = 24.0 re-entries on average, so 4.8 s of queueing per program. The 5% contribute 14.95 of the 24.0 — 62% of all queueing time from 5% of the programs. Long programs dominate the penalty, which is both why they suffer and why fixing their treatment moves the aggregate most.
  2. Fairness with money attached. Redo §22.2's share calculation when the 10 agent programs are paying customers at $20/hour and the 100 chats are free-tier. Which fairness definition maximizes revenue, which maximizes free-tier satisfaction, and what does a weighted scheme look like? Solution sketch: Per-request sharing already gives agents 87% of the engine, which maximizes revenue by accident. Per-program sharing gives them 9%, which would likely violate a paid SLA. Weighted fair queueing with weights proportional to price is the principled answer: weights 20 and 0 gives agents everything, so free tier needs a floor — a reservation of, say, 10% of capacity shared per-program among free users. The general lesson is that the scheduler's unit and the billing unit should be the same object, and in most deployments they are not.
  3. Bounding non-clairvoyance. With a quantum of 500 token-units on the standing mix, estimate how much of the 8.6× an MLFQ scheduler loses to discovery. Solution sketch: Each of the 10 long programs receives at most one high-priority quantum before demotion: 10 × 500 = 5,000 units of misallocated service, ahead of shorts totalling 20,000 units. In the shortest-first ideal the shorts complete with mean 10,100; the 5,000 units of interference add at most 5,000 to each short's completion, raising the short mean to about 15,100 and the overall mean from 19,000 to roughly 23,500 — retaining about 6.9× of the 8.6×, or 80% of the prize, for zero prediction. Halving the quantum recovers most of the remainder.
  4. Where Teola pays. A RAG request costs 10 ms to embed, 30 ms to retrieve, 50 ms to rerank, and 2,000 ms to generate. (a) Best possible speedup from perfectly overlapping the non-generate stages. (b) The same if generation is served by speculative decoding at 700 ms. (c) What does that say about when to adopt graph-level optimization? Solution sketch: (a) 2,090 → 2,000 ms = 4.3%. (b) 790 → 700 ms = 11.4%. (c) Graph-level optimization matters in proportion to how small generation has become — so every improvement in Part II increases Teola's value, exactly as Lecture 19's stall fraction grew as the model got faster. Amdahl runs both ways, and the residual is where the next paper is.
  5. Design the combined system. Sketch a scheduler with Teola's intra-request graph and Autellix's program accumulator. (a) What does it accumulate, and per what? (b) What does it admit, and per what? (c) Name one case where the two disagree and say how you would resolve it. Solution sketch: (a) Service per program, summed over all primitives of all its requests, including CPU-side retrieval time — otherwise a retrieval-heavy program looks cheap. (b) Admission at the primitive level, so GPU and CPU stages can be admitted independently, but priority inherited from the program. (c) They disagree on a fan-out: the graph says sixteen independent generate primitives are ready, the accumulator says this program has consumed enough. Resolution: inherit priority per program but reserve enough concurrency for the program's critical path, which requires the barrier information Lecture 21 says is structurally available from the orchestrator — three lectures' worth of signals in one decision, which is why nobody has built this yet.

Reading guide

Required — Autellix. Read for the mechanism, not the numbers. First: what exactly is grouped, and how does the system decide two requests belong to the same program? Second: what statistic is accumulated, over what window, and what happens to it during a stall — this is the hinge, and the answer connects directly to Lecture 19. Third: the demotion and aging rules, which are the fairness policy whether or not the paper calls them that. In the evaluation, find the workload where the gains are smallest and say what property of that workload explains it; that is the most useful thing you can bring to the discussion. Hold this question throughout: what would Autellix do with Lecture 21's sixteen-way fan-out, and is that the behaviour you want?

Required — Teola. Read the primitive-graph representation and what it makes expressible that a request does not. Focus on resource heterogeneity — which primitives contend for what — because that is the source of the wins, and on how the graph is obtained, since somebody has to write it. Hold: is this a compiler, a scheduler, or an API, and does the paper's answer match yours?

Optional — SAGA. The workflow as the atomic scheduling unit, one rung above Autellix on §22.7's ladder. Read the argument for atomicity: what breaks when a workflow is partially scheduled, and is that argument stronger or weaker than Autellix's?

Optional — Helium and agentic AI workload characteristics. The first reframes agentic serving as a data systems problem, which is a genuinely different set of instincts and worth an hour. The second is the measurement study this block has needed throughout: check Lecture 21's assumed length distributions and today's traffic mix against it, and note every place we were wrong.

Looking ahead

Part II closes next Monday. It opened with the observation that a decode step reads 13.5 GB of weights to produce one token; this half closes the declaration axis with a scheduler reordering programs it was never told about. The through-line is the one Lecture 1 promised: every improvement came from finding a unit of work the previous layer had thrown away — a shared prefix, a stalled session, a fork, a program.

Monday, November 23 (student-led): "Efficient agent serving systems II" — the rest of this block, spending the declaration on stalls, state, and fan-outs, and the last meeting of Part II. Assignment 5 (optimize the full stack) is due Dec 2, 11:59pm, and the final project report is due Dec 8, 11:59pm — for a project on prefix reuse, the whole of Lectures 13, 20, and 21 is ammunition, and §20.4's inversion in particular tells you what a trace's context policy implies about the reuse you can possibly extract.

Using LLMs to do systems research — the inverted relationship, models put to work on systems problems — is optional content rather than a lectured block; Lecture 24's notes cover the second half of it. Nov 30 and Dec 2 are the two student sharing sessions on Assignment 5 — what you optimized and what it cost — and Dec 2 is the last meeting of the semester. In Assignment 5 you are the client and this block's papers are the server — and Lecture 21's advice about the verifier is the most valuable sentence in the block.