Skip to content

refactor(flowcontrol): unify on a single priority queue#1831

Merged
LukeAVanDrie merged 4 commits into
llm-d:mainfrom
LukeAVanDrie:flowcontrol/unify-on-priority-queue
Jul 15, 2026
Merged

refactor(flowcontrol): unify on a single priority queue#1831
LukeAVanDrie merged 4 commits into
llm-d:mainfrom
LukeAVanDrie:flowcontrol/unify-on-priority-queue

Conversation

@LukeAVanDrie

Copy link
Copy Markdown
Contributor

What type of PR is this?

/kind cleanup

What this PR does / why we need it:

Now that the flow-control dispatch queue is single-ended (#1690), the FCFS-only ListQueue and the capability-negotiation machinery that chose between queue implementations no longer earn their complexity. With one queue type, the "which queue?" decision selects nothing. This collapses the layer to a single priority queue.

  • Delete ListQueue; route every flow through the priority queue. FCFS orders by its existing enqueue-time comparator, which yields exact logical-arrival ordering, an upgrade from ListQueue's physical-insertion "approximate FCFS" (the heap honors the policy and survives the controller's bounce-and-retry).
  • Remove the QueueCapability system end to end: Capabilities() from the queue contract and implementation, RequiredQueueCapabilities() from the OrderingPolicy plugin interface and all ordering policies (FCFS/EDF/SLO), and the capabilityChecker policy/queue compatibility validation in the registry config (and the now-unused ErrPolicyQueueIncompatible).
  • Queue selection collapses to always using the priority queue.

container/heap with index tracking is already the established pattern in flowcontrol/eviction/queue.go, so this aligns the dispatch queue with existing style.

Note: the registry no longer validates a band's queue name at config construction; an unknown name now surfaces when the flow is first provisioned. (The per-band Queue config field itself is to be removed in a small follow-up.)

Performance:

ListQueue existed as an O(1) FIFO fast path; the priority queue is O(log n). That difference is immaterial at this layer's scale: per-flow queue depth is bounded by per-band capacity, and each dequeued request drives multi-second model inference, so a handful of comparisons per enqueue/dequeue is lost in the noise. A quick benchmark of both implementations (directional, not rigorous) bears this out:

Benchmark ListQueue PriorityQueue
AddRemove 433 ns/op, 6 allocs 418 ns/op, 5 allocs
AddPeekRemove 163 ns/op 153 ns/op
BulkAddThenBulkRemove (100) 23.8 µs/op 28.4 µs/op
HighContention (1000 prefilled) 36.7 ns/op 45.5 ns/op

At single-item operations the heap is actually faster (contiguous slice + fewer allocations vs. container/list's node-per-item). ListQueue's O(1) advantage only appears under depth/contention, and even with 1000 queued items it's ~9 ns/op which is negligible.

Which issue(s) this PR fixes:

Follow-up from #1690

Release note:

flowcontrol: the OrderingPolicy plugin interface no longer includes RequiredQueueCapabilities(); ordering policies only implement Less(). The QueueCapability system and the FCFS-only ListQueue are removed - all flows use the priority queue, so FCFS now dispatches in exact logical-arrival order.

With the dispatch queue single-ended, the FCFS-only ListQueue and the
capability-negotiation machinery that selected between queue types no
longer earn their complexity. Collapse to one queue.

- Delete ListQueue; route all flows through the priority queue. FCFS
  orders by its existing enqueue-time comparator, yielding exact
  logical-arrival ordering (the heap honors the policy, unlike
  ListQueue's physical-insertion approximation).
- Remove the QueueCapability system end to end: Capabilities() from the
  queue contract and implementation, RequiredQueueCapabilities() from
  OrderingPolicy and all ordering policies, and the capabilityChecker
  validation in the registry config.
- Queue selection collapses to always using the priority queue.

The registry no longer validates a band's queue name at config
construction; an unknown name now surfaces when the flow is first
provisioned. The Queue config field is retained here and removed in a
follow-up.

Signed-off-by: Luke Van Drie <lukevandrie@google.com>
@LukeAVanDrie
LukeAVanDrie requested review from a team and shmuelk as code owners June 26, 2026 20:23
@LukeAVanDrie
LukeAVanDrie requested review from elevran and vMaroon June 26, 2026 20:23
@github-actions github-actions Bot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. kind/cleanup labels Jun 26, 2026
Comment on lines 141 to +142
// Queue specifies the default name of the SafeQueue implementation for flow queues in this band.
// Optional: Defaults to defaultQueue ("ListQueue").
// Optional: Defaults to defaultQueue ("PriorityQueue").

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thought there is only one queue type. What default?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm planning on removing this config loading code in a follow-up PR as this diff is already large. This whole Queue field will be deleted from the config API subsequently. For now, I just updated the doc. Though, you are correct in that it's somewhat misleading.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you want, I can carve this code path out as part of this PR too

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alright, it can be done in a followup PR

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@LukeAVanDrie Please open a followup issue

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@shmuelk

shmuelk commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

@LukeAVanDrie After reviewing again this PR along with PR #1846, I have a question. What does the ordering policy do?

I understand that the fairness policy tries to grant a certain level of fairness between flows. But isn't ordering policy within the flow, which should now be simply by priority?

Also I want to verify, a priority band, is not a range of priorities, it is a single priority. Correct?

@LukeAVanDrie

Copy link
Copy Markdown
Contributor Author

@LukeAVanDrie After reviewing again this PR along with PR #1846, I have a question. What does the ordering policy do?

I understand that the fairness policy tries to grant a certain level of fairness between flows. But isn't ordering policy within the flow, which should now be simply by priority?

Also I want to verify, a priority band, is not a range of priorities, it is a single priority. Correct?

The topology is: (1) InfObj.Priority --> (2) FairnessID --> (3) Request.

(1) defines the priority band, which is a set of fairness IDs keyed to an InfObj.Priority. We serve bands by priority descending. Each member of the band (2) gets its own heap which is sorted by our ordering policy. As there are many priority queues in each band (i.e., FairnessID matched to InfObj.Priority), the fairness policy selects across the set to see which tenant is most deserving to be served next. Then the ordering policy tells us which request (3) from that selected heap is the most urgent.

Basically, we have two levels of priority that compose

  1. Strict traffic classes defined by InfObj.Priority
  2. Per-flow request ordering defined by the ordering policy (e.g., FCFS, earliest deadline first, shortest job first, etc.)

This is better described in our dev site architecture guide. There is also a clearer diagram for this: https://llm-d.ai/docs/architecture/core/router/epp/flow-control#architecture-overview.

abdallahsamabd pushed a commit to abdallahsamabd/llm-d-inference-scheduler that referenced this pull request Jul 7, 2026
Signed-off-by: Daneyon Hansen <daneyon.hansen@solo.io>
@LukeAVanDrie

Copy link
Copy Markdown
Contributor Author

/retest

@shmuelk

shmuelk commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

@LukeAVanDrie Please rebase on main. Some CI test fixes have been added lately

@LukeAVanDrie
LukeAVanDrie merged commit d8a0c46 into llm-d:main Jul 15, 2026
29 checks passed
elevran pushed a commit that referenced this pull request Jul 16, 2026
Signed-off-by: llm-d-router-release-notes[bot] <287676111+llm-d-router-release-notes[bot]@users.noreply.github.com>
Co-authored-by: llm-d-router-release-notes[bot] <287676111+llm-d-router-release-notes[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

kind/cleanup size/XL Denotes a PR that changes 500-999 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants