Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
82982a8
Qwen3.5: pack a mixed step's input ids in runs, not one slice per row
NSagan271 Oct 22, 2026
71f44a5
Qwen3.5: splice routed decode tokens with device ops, not a host-buil…
NSagan271 Oct 22, 2026
e15f03e
FlashInfer decode: later plans of a captured wrapper call the planner…
NSagan271 Oct 22, 2026
083d2bc
worker_phases: time a step's inline plans per resource apart from pro…
NSagan271 Oct 23, 2026
ff13e12
FlashInfer mixed step: stage into bucket-sized pinned buffers, plan b…
NSagan271 Oct 23, 2026
e89f21e
KV: a captured packed step's write addressing from the host indptrs
NSagan271 Oct 23, 2026
1328bd3
GDN mixed step: stage each plan's index arrays in a few copies
NSagan271 Oct 23, 2026
4070c7e
Recurrent pool: admit a step under one lock, resident rows without an…
NSagan271 Oct 23, 2026
f3dc1b1
KV admit: one pass over a step's rows for reservations and in-flight …
NSagan271 Oct 23, 2026
834c488
KV plan cache: insert a crossed row's new page rather than rebuild th…
NSagan271 Oct 23, 2026
3469dcf
Positions: a decode row's id without a range, counters read inline
NSagan271 Oct 23, 2026
4c1fca9
Recurrent pool: reuse the last step's addressing while nothing it rea…
NSagan271 Oct 23, 2026
2d4cd7d
KV, recurrent pool, positions: commit loops without a per-row sink ca…
NSagan271 Oct 23, 2026
caa192e
FlashInfer direct plans: drop the A/B switch
NSagan271 Oct 23, 2026
1cc8437
KV plan cache: plan a step with sink rows from the streams
NSagan271 Oct 23, 2026
6f29b31
attention: replay FlashInfer's plan as learned from FlashInfer, not r…
NSagan271 Oct 26, 2026
3db75c0
plan replay: stage the step after learning; find copies by content; f…
NSagan271 Oct 26, 2026
b7e2729
attention: pin FlashInfer; hand-written direct planner calls on that …
NSagan271 Oct 26, 2026
c2b8177
attention: the FlashInfer fallback warning says which version to install
NSagan271 Oct 26, 2026
8c923c1
attention: review -- eligibility as functions, _plan_staged, why the …
NSagan271 Oct 26, 2026
3eb9f31
KV plan cache: a cached row's membership by set lookup, not a scan
NSagan271 Oct 27, 2026
88af3db
attention, GDN: review fixes -- plan laps under the runner's phase, s…
NSagan271 Oct 27, 2026
4d39084
planning on main: a staged packed plan is recorded for the read-only …
NSagan271 Oct 27, 2026
c761909
cross attention: plan with host kv_lens, so a captured decode wrapper…
NSagan271 Oct 28, 2026
3a47f68
recurrent pool, pinned staging: version bump after reset; plan events…
stephen-dwq Oct 29, 2026
44a863a
attention, KV: guard the direct decode plan's lengths and the packed …
stephen-dwq Oct 30, 2026
1d601bb
GDN: pin the plan's staging buffers and wait on its copies on any acc…
Oct 31, 2026
92aae7e
qwen3_5: captured text prefill and mixed steps build rope in-graph
NSagan271 Oct 31, 2026
f73cb3c
h2d: PinnedStager.copy_ skips per-call slicing and the stream lookup
NSagan271 Oct 31, 2026
e5e6778
test: the CUDA-core mixed decode keeps FlashInfer's own plan
NSagan271 Nov 1, 2026
2ccfc95
KV packed plan state: the group's page size, as the decode path takes it
NSagan271 Nov 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions docs/environment_variables.rst
Original file line number Diff line number Diff line change
Expand Up @@ -646,9 +646,11 @@ Worker scheduling
master write. ``0`` keeps every node on the per-request tensor path.
* - ``MSTAR_INGRAPH_DECODE_ROPE``
- config's ``knobs: ingraph_decode_rope``, else ``0``
- A captured decode step builds its rotary tables inside the graph from
the position resource's planned positions instead of staging them.
Experiment knob, measured and gated on its own.
- A captured step of a text walk (Qwen3.5 decode, text prefill and
mixed) builds its rotary tables inside the graph from the position
resource's planned positions instead of building them before the
replay (``LLMSubmodule._rope_in_graph``). Experiment knob, measured
and gated on its own.
* - ``MSTAR_SAMPLER_INGRAPH_SCATTER``
- config's ``knobs: sampler_ingraph_scatter``, else ``0``
- The captured sampler gathers its RNG offsets from the slot masters
Expand Down
3 changes: 3 additions & 0 deletions docs/installation.rst
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,9 @@ The GPU model families depend on:

- **FlashInfer** (``flashinfer-python``) — paged attention and continuous batching for the
autoregressive backbones (every model with a ``KV_CACHE`` node runs attention through it).
Pinned to the version the attention wrappers' direct planner calls were checked against
(``DIRECT_PLAN_FLASHINFER_VERSIONS`` in ``mstar/engine/resources/attn/wrappers.py``); on any
other version they fall back to FlashInfer's own ``plan()``.
- **flash-attn** — used by LingBot-World's ``reference_numerics`` mode. **Not installed by any extra**; install it separately
(see `flash-attn (LingBot-World)`_).
- **flash-attn-4** — Waypoint's flex-attention ``FLASH`` backend. **Not installed by any
Expand Down
5 changes: 4 additions & 1 deletion mstar/engine/engine.py
Original file line number Diff line number Diff line change
Expand Up @@ -1696,7 +1696,10 @@ def _drive_step(
)
t0 = phase_start()
try:
self._runner.plan(step)
# promoted and inline plans per resource, apart
self._runner.plan(
step, "res.plan" if batch.preplan_event is not None else "res.plan_fresh",
)
Comment on lines +1699 to +1702

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

[P3] Only the runner-level totals are split here. The per-resource sub-laps still record under res.plan.* on both paths.

KVManager.plan records res.plan.kv_cache.* for every non-preplan call, and FlashInferManager.plan records res.plan.attn.* the same way (kv/manager.py:2425, attn/flashinfer.py:199). After this change, an inline plan's total is res.plan_fresh.kv_cache, but its breakdown still lands in res.plan.kv_cache.*. That is next to res.plan.kv_cache, which now only times the promotion, and res.plan.kv_cache.promote shares the same prefix. So in a worker_phases table the sub-rows under res.plan.attn / res.plan.kv_cache add up to more than their parent row. Passing the same prefix down, or giving the laps the same fresh/promoted split, would keep the two sets of rows consistent.

finally:
phase_end(
"engine.plan.promoted" if batch.preplan_event is not None
Expand Down
5 changes: 3 additions & 2 deletions mstar/engine/resources/attn/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -246,8 +246,9 @@ class PlanCacheKey(NamedTuple):
"""Fingerprint of a wrapper ``plan`` call's inputs. When it is unchanged
between steps the re-plan is skippable."""
q_seq_lens: tuple
page_indices: tuple
last_page_lens: tuple
# the int32 arrays' bytes
page_indices: bytes
last_page_lens: bytes


class EagerSlotKey(NamedTuple):
Expand Down
24 changes: 17 additions & 7 deletions mstar/engine/resources/attn/cross.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from typing import Any, NamedTuple

import numpy as np
import torch

from mstar.engine.resources.attn.base import (
Expand Down Expand Up @@ -29,6 +30,7 @@
from mstar.engine.resources.kv.config import PagedKVConfig
from mstar.engine.resources.kv.plan import (
SINK_PAGE,
HostLens,
KVPlanOutputs,
PagedIndptrs,
SequenceView,
Expand Down Expand Up @@ -203,11 +205,11 @@ def plan(self, step: AttentionStep, ctx: StepContext):

# The context pages are immutable once written, so only the query
# side moves between steps; when neither moved, FlashInfer's plan
# is skippable. Every tensor here is on CPU: `.tolist()` costs no sync.
# is skippable. Every tensor here is on CPU.
plan_key = PlanCacheKey(
q_seq_lens=qo_indptr,
page_indices=tuple(indptrs.paged_kv_indices.tolist()),
last_page_lens=tuple(indptrs.paged_kv_last_page_len.tolist()),
page_indices=indptrs.paged_kv_indices.numpy().tobytes(),
last_page_lens=indptrs.paged_kv_last_page_len.numpy().tobytes(),
)
if last is None or last.plan_key != plan_key:
wrapper.plan(
Expand Down Expand Up @@ -307,6 +309,7 @@ def _build_indptrs(
kv_indptr = [0]
all_pages: list[int] = []
last_page_lens: list[int] = []
kv_lens: list[int] = []

for request_id in packing.request_ids:
context = context_views.get(request_id)
Expand All @@ -323,13 +326,16 @@ def _build_indptrs(
all_pages.append(SINK_PAGE)
kv_indptr.append(kv_indptr[-1] + 1)
last_page_lens.append(1)
kv_lens.append(1)
continue
# stream can have more pages than context needs takes only the prefix
# written length covers
n_pages = (context.length + page_size - 1) // page_size
all_pages.extend(context.page_idxs[:n_pages])
kv_indptr.append(kv_indptr[-1] + n_pages)
last_page_lens.append(context.last_page_len(page_size) or page_size)
last = context.last_page_len(page_size) or page_size
last_page_lens.append(last)
kv_lens.append((n_pages - 1) * page_size + last)

# fork repeats pages, so batch can index more than held in cache.
assert len(all_pages) <= self._kv_config.max_num_pages, (
Expand All @@ -338,11 +344,15 @@ def _build_indptrs(
"raise max_num_pages on the context cache's PagedKVConfig"
)

# numpy builds an int32 array from a list in about half the time
# `torch.tensor(list)` takes; host `kv_lens` let a captured decode
# wrapper call FlashInfer's planner directly (see `_plan_direct`)
return PagedIndptrs(
qo_indptr=packing.qo_indptr,
paged_kv_indptr=torch.tensor(kv_indptr, dtype=torch.int32),
paged_kv_indices=torch.tensor(all_pages, dtype=torch.int32),
paged_kv_last_page_len=torch.tensor(last_page_lens, dtype=torch.int32),
paged_kv_indptr=torch.from_numpy(np.array(kv_indptr, np.int32)),
paged_kv_indices=torch.from_numpy(np.array(all_pages, np.int32)),
paged_kv_last_page_len=torch.from_numpy(np.array(last_page_lens, np.int32)),
kv_lens=torch.from_numpy(np.array(kv_lens, np.int32)).as_subclass(HostLens),
)

def _wrapper_for(
Expand Down
2 changes: 1 addition & 1 deletion mstar/engine/resources/attn/flashinfer.py
Original file line number Diff line number Diff line change
Expand Up @@ -233,7 +233,7 @@ def plan(self, step: AttentionStep, ctx: StepContext):
plan_states = self._preplan_states if ctx.is_preplan else self._current_plan_states

plan_states.clear()
laps = PhaseLaps("res.pre_plan.attn." if ctx.is_preplan else "res.plan.attn.")
laps = PhaseLaps(f"{ctx.plan_phase}.attn.")
for label, kv_out in plan_outputs.items():
indptrs = kv_out.cpu_indptrs
if lease is not None:
Expand Down
Loading
Loading