Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
99 commits
Select commit Hold shift + click to select a range
9b9d397
kv: chained page keys for prefix reuse
Gaurav-Shah05 Sep 20, 2026
2c4a8fd
test: page key chaining and its encoding across processes
Gaurav-Shah05 Sep 20, 2026
fd070cd
kv: the prefix index over the page arena
Gaurav-Shah05 Sep 20, 2026
d263dd4
test: the prefix index walk, its first writer and its eviction order
Gaurav-Shah05 Sep 20, 2026
2039377
api: prefix keys on the request record, through process_prompt metadata
Gaurav-Shah05 Sep 20, 2026
8766ee1
test: prefix keys from the preprocess worker onto the request config
Gaurav-Shah05 Sep 20, 2026
2306891
engine: fingerprints for the prefix cache root
Gaurav-Shah05 Sep 20, 2026
17c23b4
test: each fingerprint changes with its own inputs and nothing else
Gaurav-Shah05 Sep 20, 2026
dca888c
kv: root the prefix index in the build's fingerprints
Gaurav-Shah05 Sep 20, 2026
53e2678
test: the prefix root, and where the cache stays shut
Gaurav-Shah05 Sep 20, 2026
f395332
kv: lease a matched prefix and convert it at admit
Gaurav-Shah05 Sep 20, 2026
019d53e
test: the lease from probe to conversion, and what gives it back
Gaurav-Shah05 Sep 20, 2026
a48b0ca
engine: seed positions from a matched prefix, and refuse a non-sequen…
Gaurav-Shah05 Sep 20, 2026
76f5abb
test: positions after a hit, and the scheme a keyed cache demands
Gaurav-Shah05 Sep 20, 2026
802f964
engine: skip the cached prefix before the step is declared
Gaurav-Shah05 Sep 20, 2026
ce57925
test: the prefix stage, what it cuts and what it never offers
Gaurav-Shah05 Sep 20, 2026
823bca4
engine: fold a skipped prefix into the repetition-penalty mask
Gaurav-Shah05 Sep 20, 2026
4992676
test: a cut prefill ends with the same penalty mask as a whole one
Gaurav-Shah05 Sep 20, 2026
80554fb
kv: index each page as a commit fills it
Gaurav-Shah05 Sep 20, 2026
a1a24c0
test: which pages a commit offers the index, and who may not offer any
Gaurav-Shah05 Sep 20, 2026
c9150fe
kv: key generated pages from the stop check's host copy
Gaurav-Shah05 Sep 20, 2026
5a3fc78
test: keying what a request generates, in step with the cache
Gaurav-Shah05 Sep 20, 2026
3684062
kv: give back cached pages before an allocation fails
Gaurav-Shah05 Sep 20, 2026
2cc5703
test: a shortfall gives back cache before it fails a request
Gaurav-Shah05 Sep 20, 2026
3f4e873
kv: look up a filled page's chain parent by key
Gaurav-Shah05 Sep 20, 2026
5ada19b
test: a chain parent that outlives a race and a reload
Gaurav-Shah05 Sep 20, 2026
e2f532c
kv: take a local match before a read-in moves anything
Gaurav-Shah05 Sep 20, 2026
4218a2f
test: read-in against a local match, and offload across a shared page
Gaurav-Shah05 Sep 20, 2026
00bc7ab
kv: one line per request a declared stream admitted
Gaurav-Shah05 Sep 20, 2026
6519638
test: the three numbers a request leaves behind
Gaurav-Shah05 Sep 20, 2026
0b1f33b
model: orpheus and bagel declare their text prefix streams
Gaurav-Shah05 Sep 20, 2026
3b4bc15
test: a cached run attends what a fresh run computed
Gaurav-Shah05 Sep 20, 2026
8503519
conductor: let a request turn the prefix cache off for itself
Gaurav-Shah05 Sep 20, 2026
2903413
test: a request turning the cache off for itself
Gaurav-Shah05 Sep 20, 2026
54cafdd
test: a real forward over a prefix taken from the cache
Gaurav-Shah05 Sep 20, 2026
cb8ff1f
api: take prefix keys only from the preprocess worker
Gaurav-Shah05 Sep 20, 2026
17eabd5
test: keys a client sends never reach the cache
Gaurav-Shah05 Sep 20, 2026
9e5322b
kv: stop keying a stream once it writes tokens the keys do not cover
Gaurav-Shah05 Sep 20, 2026
5f5db89
test: a chain stops where the stream holds what it never keyed
Gaurav-Shah05 Sep 20, 2026
2e5f247
kv: reload gives back cached pages before it refuses
Gaurav-Shah05 Sep 20, 2026
755927f
test: a reload into a pool the index filled
Gaurav-Shah05 Sep 20, 2026
745299b
kv: read request state under the lock on the prefix entry points
Gaurav-Shah05 Sep 20, 2026
5aa2c6e
test: a remove racing the probe or the chain extension
Gaurav-Shah05 Sep 20, 2026
ac311a0
engine: fail one rid, not the batch, when the prefix stage raises
Gaurav-Shah05 Sep 20, 2026
250fb28
test: a prefix stage that raises for one request
Gaurav-Shah05 Sep 20, 2026
e0759a9
kv: a converted lease is released once
Gaurav-Shah05 Sep 20, 2026
fa61143
test: a converted lease through an offload and a retry
Gaurav-Shah05 Sep 20, 2026
977cd89
engine: seed positions per label after a hit
Gaurav-Shah05 Sep 20, 2026
96f3468
test: a label written after a hit starts at zero
Gaurav-Shah05 Sep 20, 2026
dd3cf88
kv: an offload keeps a converted stream converted
Gaurav-Shah05 Sep 21, 2026
8a570ba
test: a converted stream through an offload and a reload
Gaurav-Shah05 Sep 21, 2026
42e5c8a
kv: a reset forgets the chain
Gaurav-Shah05 Sep 21, 2026
56e1546
test: a rewound stream files its rerun from page zero
Gaurav-Shah05 Sep 21, 2026
d9d9e3b
kv: hash the prompt's keys outside the lock
Gaurav-Shah05 Sep 21, 2026
f81428c
test: the prompt's keys are hashed with the lock down
Gaurav-Shah05 Sep 21, 2026
83fdea0
test: the tail and the decode name travel with the keys
Gaurav-Shah05 Sep 21, 2026
25e2115
test: a lease trimmed to a smaller minimum
Gaurav-Shah05 Sep 21, 2026
0f2dbc3
test: positive controls beside every nothing-happens test
Gaurav-Shah05 Sep 21, 2026
bf7e97e
test: drop the tests a sibling already covers
Gaurav-Shah05 Sep 21, 2026
339dbc2
test: name the failure on every closing assertion
Gaurav-Shah05 Sep 21, 2026
28a36cb
kv: rename pending to unkeyed
Gaurav-Shah05 Sep 21, 2026
da8f404
kv: trim the prefix cache's prose to the hazard
Gaurav-Shah05 Sep 21, 2026
61c6f7c
model: say where bagel's decode contract comes from
Gaurav-Shah05 Sep 21, 2026
31b2074
engine: refuse a declaration on a node with a resource that cannot skip
Gaurav-Shah05 Sep 21, 2026
5614553
test: a declared node carrying a resource that cannot skip
Gaurav-Shah05 Sep 21, 2026
ad3f607
kv: a windowed stream indexes only its protected prefix
Gaurav-Shah05 Sep 21, 2026
7a5cd24
test: a windowed stream, and one that released its front
Gaurav-Shah05 Sep 21, 2026
f0d9068
conductor: a keyed resource always has a request config
Gaurav-Shah05 Sep 21, 2026
232f332
test: a model returning only a sampler config still gets its keys
Gaurav-Shah05 Sep 21, 2026
9f92dac
kv: admit converts a lease whatever the stream holds
Gaurav-Shah05 Sep 21, 2026
ac77a28
test: a lease taken after a refused batch admit
Gaurav-Shah05 Sep 21, 2026
4a5d376
model: the keyed walk is named on the stream
Gaurav-Shah05 Sep 21, 2026
8978504
test: only the walk a stream names files or probes its pages
Gaurav-Shah05 Sep 21, 2026
e7250be
attn: index buffers sized by rows and pages per row
Gaurav-Shah05 Sep 21, 2026
93170b1
test: rows sharing a cached prefix plan under a captured graph
Gaurav-Shah05 Sep 21, 2026
bd97ff5
kv: the leaves heap is bounded by the pool
Gaurav-Shah05 Sep 21, 2026
e13e9ad
test: a leaf read ten thousand times holds one heap entry
Gaurav-Shah05 Sep 21, 2026
27b0239
engine: the index is opened through a resource hook
Gaurav-Shah05 Sep 21, 2026
22a3b31
test: every resource is offered the root
Gaurav-Shah05 Sep 21, 2026
6d38d0d
kv: a page is indexed under one key
Gaurav-Shah05 Sep 21, 2026
2c046b7
test: a page takes one key
Gaurav-Shah05 Sep 21, 2026
1ff9b37
api: process_prompt sees the stripped kwargs
Gaurav-Shah05 Sep 21, 2026
01017d5
test: a client's prefix keys never reach process_prompt
Gaurav-Shah05 Sep 21, 2026
306a625
engine: a model naming no checkpoint keeps its cache shut without a salt
Gaurav-Shah05 Sep 21, 2026
7f6fb63
test: a model naming no checkpoint needs a salt to open its cache
Gaurav-Shah05 Sep 21, 2026
53cba4e
docs: what a model declares to use the prefix cache
Gaurav-Shah05 Sep 21, 2026
157a766
kv: a self-published record leaves the chain alone
Gaurav-Shah05 Sep 22, 2026
50b1fad
test: a stream reading its own publish keeps keying its samples
Gaurav-Shah05 Sep 22, 2026
af03fd6
engine: the engine says what the prefix cache is doing at load
Gaurav-Shah05 Sep 22, 2026
0b3efd3
test: a keyed node says what opened, and warns without host pages
Gaurav-Shah05 Sep 22, 2026
52fac56
kv: a keyed node says when a request arrives with no keys
Gaurav-Shah05 Sep 22, 2026
add76ac
test: a declared node its keys never reach warns once
Gaurav-Shah05 Sep 22, 2026
fe69873
worker: the OOM hold logs once, not once a retry
Gaurav-Shah05 Sep 22, 2026
be83a17
test: a hundred holds on one node log two lines and count the rest
Gaurav-Shah05 Sep 22, 2026
995684c
docs: sizing a pool with the prefix cache on
Gaurav-Shah05 Sep 22, 2026
02a7bd3
kv: the chain is its own object
Gaurav-Shah05 Sep 22, 2026
039f4e1
fix comments to make them more readable
Gaurav-Shah05 Sep 22, 2026
e69be24
engine: the load warning names both ways out of a full pool
Gaurav-Shah05 Sep 23, 2026
5d51741
test: the load warning names max_concurrent_requests
Gaurav-Shah05 Sep 23, 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
82 changes: 78 additions & 4 deletions docs/adding_models.rst
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,77 @@ sampling parameters. The engine no longer reads it directly. Pass its result int
through ``SamplingReqConfig`` are stored in buffers whose addresses do not change
across replays, so they stay per-request and remain safe under CUDA graphs.

Cross-request prefix reuse
~~~~~~~~~~~~~~~~~~~~~~~~~~

Two requests that begin with the same tokens fill the same KV pages, and the second can
attend the pages the first filled instead of recomputing them. A model opts in with two
methods. Both have defaults, and a model that overrides neither is never cached. The
deployment switch and the per-request opt-out are described under **Resource overrides**
in :doc:`serving`.

``prefix_key_streams(self) -> dict[str, dict[str, PrefixStream]]``
Return ``{resource_key: {label: PrefixStream}}``, one entry for each KV cache stream
whose pages are keyed. A :class:`mstar.model.base.PrefixStream` has four fields:

- ``tensor`` names the request tensor that holds the stream's token ids, as
``process_prompt`` returns it.
- ``keyed_by`` is ``"ids"``: each page is keyed by its token ids, chained from the key
of the page before it.
- ``walk`` names the graph walk that writes the keyed span. Only this walk is probed
for a match. If any walk other than ``walk`` and ``decode_walk`` writes into the
stream, the stream stops being keyed.
- ``decode_walk`` names the walk whose input is the token just sampled, so that the
generated tokens are chained onto the prompt's keys and a later request that
re-sends the reply matches it too. ``None`` keys the prompt only.

BAGEL keys the text walk of its LLM and the decode that follows it:

.. code-block:: python

return {"kv": {"main": PrefixStream(
"text_inputs", "ids", "prefill_text", "decode",
)}}

Orpheus keys its prompt walk and its decode walk:

.. code-block:: python

return {
KV_CACHE: {"main": PrefixStream("text_inputs", "ids", "prefill", "decode")},
}

``checkpoint_path(self) -> str | None``
Return the local directory that holds the weights and ``config.json``. The cache
hashes the checkpoint's safetensors index and config into the root of every key, so
naming the checkpoint is what keeps two builds off each other's pages. The default
returns ``None``, which leaves the weights out of the root. A model without a
checkpoint therefore needs ``prefix_cache_salt`` set to use the cache at all. Without
one, the engine keeps the cache shut and logs why.

A model that declares a stream is checked at load time. Loading fails in three cases:

- A position resource over a keyed cache uses a scheme other than sequential. A page is
reusable only where its tokens land at the positions they were written at. The message
is ``resource '<key>' places positions by <scheme>, but it sits over ['<kv key>'],
which the model keyed for prefix reuse; either drop the stream or give the resource a
sequential scheme``.
- A resource on a keyed node does not set ``prefix_skip_safe``. A hit takes the front of
the request away from every resource on the node, including resources that never saw
those tokens. The message is ``resource '<key>' cannot serve a request whose prefix
came from the cache, but it sits on ['<node>'], which the model keyed for prefix
reuse; either drop the stream or give the resource the prefix hooks``.
- A stream names a walk that the model does not run. The message is ``<Model> keys
'<resource key>'/'<label>' on ['<walk>'], which it never runs; its walks are [...]``.

Some requests stay uncached even when the model declares a stream. The engine checks each
request's inputs on the keyed walk and cuts a prefix only from inputs that the default
``split_inputs`` can slice. A request whose inputs carry ``tensor_inputs``, ``kwargs`` or
``resource_step_info`` is never probed; BAGEL's guided requests carry ``requires_cfg``
there. The keys stop at a prompt's first input that is not a token, so a BAGEL prompt is
keyed over its leading text only. A stream under a sliding window files pages only up to
its protected prefix, which the window never releases.

.. _Step 2a — Declare your resources:

Step 2a — Declare your resources
Expand Down Expand Up @@ -267,9 +338,10 @@ The spec types are:
- What it builds
* - ``KVSpec(config=KVConfig(...))``
- A paged KV cache. ``KVConfig`` holds ``num_layers``, ``num_kv_heads``,
``head_dim``, ``max_seq_len`` and ``num_qo_heads``. It also holds three fields that
a deployment can tune: ``max_num_pages``, ``page_size`` and ``cpu_offload_pages``
(the number of pinned host pages used for offload; 0 disables offload).
``head_dim``, ``max_seq_len`` and ``num_qo_heads``. It also holds five fields that
a deployment can tune: ``max_num_pages``, ``page_size``, ``cpu_offload_pages``
(the number of pinned host pages used for offload; 0 disables offload),
``prefix_cache`` and ``prefix_cache_salt``.
* - ``AttentionSpec(config=AttentionConfig(kv_cache=...))``
- Self-attention planned over the named cache. ``backend`` selects
``AttnBackend.FLASHINFER`` (the default) or ``AttnBackend.DENSE``.
Expand Down Expand Up @@ -1210,7 +1282,8 @@ that a misspelled setting is never silently ignored:
* - Spec
- Accepts
* - ``KVSpec``
- ``max_num_pages``, ``page_size``, ``max_seq_len``, ``cpu_offload_pages``
- ``max_num_pages``, ``page_size``, ``max_seq_len``, ``cpu_offload_pages``,
``prefix_cache``, ``prefix_cache_salt``
* - ``AttentionSpec`` / ``CrossAttentionSpec``
- ``backend`` (``flashinfer`` / ``dense``), ``flashinfer_backend``
(``auto`` / ``fa2`` / ``fa3``)
Expand Down Expand Up @@ -1589,6 +1662,7 @@ Checklist
[ ] postprocess
[ ] get_submodule
[ ] (optional) get_request_resource_configs
[ ] (optional) prefix_key_streams + checkpoint_path — cross-request prefix reuse
[ ] mstar/model/registry.py — add to MODEL_REGISTRY (+ HF_MODELS)
[ ] configs/<your_model>.yaml — node_groups → ranks (+ resources: overrides)
[ ] (optional) async partitions if pipelined
Expand Down
18 changes: 17 additions & 1 deletion docs/serving.rst
Original file line number Diff line number Diff line change
Expand Up @@ -179,6 +179,9 @@ A config maps the model's computation-graph nodes to physical GPU ranks. The key
* - ``resources``
- *(optional)* Per-resource overrides, keyed by the model's resource names (see
below).
* - ``max_concurrent_requests``
- *(optional)* How many requests run at once. Requests past the cap wait in the
conductor's queue.
* - ``model_kwargs``
- *(optional)* Server-init model parameters (see below).

Expand All @@ -194,7 +197,8 @@ them by that name:

resources:
kv_cache:
max_num_pages: 1024 # also: page_size, max_seq_len, cpu_offload_pages
max_num_pages: 1024 # also: page_size, max_seq_len, cpu_offload_pages,
# prefix_cache, prefix_cache_salt
talker_attn:
flashinfer_backend: fa2 # also: backend (flashinfer / dense)
vit_attn: # a cacheless (ragged) attention resource
Expand All @@ -206,6 +210,18 @@ that the model does not declare, or sets a key that the resource does not accept
fails. A misspelled setting is never silently ignored. A top-level ``kv_cache:`` block is
no longer read. It raises an error with a message describing the migration.

``prefix_cache: false`` turns cross-request prefix reuse off for that cache, and
``prefix_cache_salt`` keeps two otherwise identical deployments off each other's
cached pages. A single request opts out by sending ``prefix_cache=False``, which
travels with its other ``model_kwargs``.

A cached prompt no longer reserves its pages, so the pool no longer limits how many
requests are admitted. It has to hold the decode of every request allowed to run at once,
and a decode step that finds no free page holds its requests until they time out. Two
settings prevent that. ``max_concurrent_requests`` caps how many requests run at once, and
the pool's pages divided by the pages one request needs, prompt and output together, is a
safe value. ``cpu_offload_pages`` gives a full pool a request to move to the host.

**Single GPU.** Everything on rank 0:

.. code-block:: yaml
Expand Down
83 changes: 76 additions & 7 deletions mstar/api_server/data_worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,10 @@
)
from mstar.communication.communicator import BaseCommunicator, CommProtocol, make_communicator
from mstar.communication.tensors import NameToTensorList, create_tensor_communication_manager
from mstar.model.base import Model
from mstar.engine.resources.kv.config import KVSpec
from mstar.engine.resources.kv.keys import chain
from mstar.engine.resources.spec import apply_yaml_overrides
from mstar.model.base import Model, ProcessPromptOutput
from mstar.profile.format import InputInfo, RxInfo, TxInfo
from mstar.utils.ipc_format import (
AbortRequest,
Expand All @@ -47,6 +50,16 @@ def _preprocess_loop(**kwargs):
worker.run()


def _kv_page_sizes(model: Model, model_config: dict) -> dict[str, int]:
"""Page size per KV resource, after this deployment's YAML has been applied."""
specs = model.get_node_resources()
apply_yaml_overrides(specs, model_config)
return {
spec.resource_key: spec.config.page_size
for spec in specs if isinstance(spec, KVSpec)
}


NameToLoopIndices = dict[str, NestedLoopIndices]


Expand All @@ -58,7 +71,8 @@ def __init__(
socket_path_prefix: str = "/tmp/mstar",
tensor_comm_protocol: CommProtocol = CommProtocol.RDMA,
tcp_transfer_device="",
enable_prof: bool=False
enable_prof: bool=False,
model_config: dict | None = None,
):
self.request_input_queue = queue.Queue()
self.result_tensor_input_queue = queue.Queue()
Expand Down Expand Up @@ -108,7 +122,8 @@ def __init__(
communicator=self.communicator,
tensor_manager=self.tensor_manager,
model=model,
enable_prof=enable_prof
enable_prof=enable_prof,
model_config=model_config,
)
)
self.thread.start()
Expand Down Expand Up @@ -260,8 +275,19 @@ def __init__(
tensor_manager,
device: str = "cpu",
model: Model | None = None,
enable_prof: bool=False
enable_prof: bool=False,
model_config: dict | None = None,
):
# keying a stream needs the deployment's page size as well as the
# model's declaration, so a worker built without a config keys no stream
self._prefix_streams = (
model.prefix_key_streams()
if model is not None and model_config else {}
)
# resolved as the worker does at load, so both split a prompt into the same pages
self._prefix_page_sizes = (
_kv_page_sizes(model, model_config) if self._prefix_streams else {}
)
self.in_queue = in_queue
self.result_tensor_queue = result_tensor_queue
self.cleanup_request_queue = cleanup_request_queue
Expand Down Expand Up @@ -332,6 +358,11 @@ def _process_input(
# image_grid_thw, audio_features, audio_seqlens from the raw tensors
# loaded above). process_prompt receives the raw multimodal tensors
# and returns any additional tensors to merge into the final dict.
model_kwargs = dict(input.model_kwargs or {})
# only this worker keys a prompt: a client that sent its own could name
# another request's pages and be served that request's KV
for name in ("prefix_keys", "prefix_tail", "prefix_decode"):
model_kwargs.pop(name, None)
if self.model is not None:
prompt_tensors = self.model.process_prompt(
input.text,
Expand All @@ -340,10 +371,21 @@ def _process_input(
tensors=tensors,
input_metadata=input_metadata,
prompt_parts=input.prompt_parts,
**(input.model_kwargs or {}),
**model_kwargs,
)
if isinstance(prompt_tensors, ProcessPromptOutput):
model_kwargs.update(prompt_tensors.metadata)
prompt_tensors = prompt_tensors.new_input_tensors
if prompt_tensors:
tensors.update(prompt_tensors)
# after the update: the chain keys the tensors the request will
# actually be prefilled with
prefix_keys, prefix_tail, prefix_decode = self._prefix_keys(tensors)
if prefix_keys:
model_kwargs["prefix_keys"] = prefix_keys
model_kwargs["prefix_tail"] = prefix_tail
if prefix_decode:
model_kwargs["prefix_decode"] = prefix_decode
elif input.text is not None:
# Fallback: encode as UTF-8 bytes -> uint8 tensor
byte_data = input.text.encode("utf-8")
Expand All @@ -368,7 +410,7 @@ def _process_input(
input.request_id, info.uuid, persist=True
)

self.request_model_kwargs[input.request_id] = input.model_kwargs or {}
self.request_model_kwargs[input.request_id] = model_kwargs
msg = ConductorMessage(
message_type=ConductorMessageType.NEW_REQUEST,
body=NewRequestConductor(
Expand All @@ -377,7 +419,7 @@ def _process_input(
initial_input_modalities=input.input_modalities,
initial_output_modalities=input.output_modalities,
input_metadata=input_metadata,
model_kwargs=input.model_kwargs
model_kwargs=model_kwargs
),
)
self.communicator.send("conductor", msg)
Expand All @@ -394,6 +436,33 @@ def _process_input(
inputs=self._summarize_inputs(input),
))

def _prefix_keys(self, tensors: dict) -> tuple[dict, dict, dict]:
"""Key each page of every declared stream, by resource and label.

Returns the keys, the prompt tail past the last whole page, and the output
tensor each stream's sampled ids arrive in. The keys are unrooted.
"""
keys: dict[str, dict[str, list[bytes]]] = {}
tails: dict[str, dict[str, list[int]]] = {}
decode: dict[str, dict[str, str]] = {}
for resource_key, by_label in self._prefix_streams.items():
page_size = self._prefix_page_sizes[resource_key]
for label, stream in by_label.items():
ids = tensors.get(stream.tensor)
if stream.keyed_by != "ids" or not ids:
continue
flat = ids[0].flatten().tolist()
pages = [
flat[at:at + page_size]
for at in range(0, len(flat), page_size)
]
keys.setdefault(resource_key, {})[label] = chain(pages)
whole = len(flat) // page_size
tails.setdefault(resource_key, {})[label] = flat[whole * page_size:]
if stream.decode_walk is not None:
decode.setdefault(resource_key, {})[label] = stream.tensor
return keys, tails, decode

@staticmethod
def _summarize_inputs(input: PreprocessInput) -> list[InputInfo]:
"""Aggregate the *raw* (pre-decoding) inputs into per-modality sizes.
Expand Down
3 changes: 3 additions & 0 deletions mstar/api_server/entrypoint.py
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ def __init__(
model_name: str = "dummy",
log_stats: bool = False,
log_stats_file: str | None = None,
model_config: dict | None = None,
):
self.upload_dir = Path(upload_dir)
self.upload_dir.mkdir(parents=True, exist_ok=True)
Expand All @@ -204,6 +205,7 @@ def __init__(

self.preprocess_worker = PreprocessWorker(
model=model,
model_config=model_config,
hostname=hostname,
socket_path_prefix=socket_path_prefix,
tensor_comm_protocol=tensor_comm_protocol,
Expand Down Expand Up @@ -1055,6 +1057,7 @@ def main(argv: list[str] | None = None):
tensor_comm_protocol=CommProtocol(args.tensor_comm_protocol),
model=model,
model_name=model_name,
model_config=config,
tcp_transfer_device=args.tcp_transfer_device,
log_stats=log_stats,
log_stats_file=args.log_stats_file,
Expand Down
26 changes: 22 additions & 4 deletions mstar/conductor/conductor.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
)
from mstar.distributed.base import ShardingConfig
from mstar.distributed.communication import GlobalParallelConfig, WorkerParallelGroups
from mstar.engine.resources import ResourceReqConfig
from mstar.engine.resources import KVReqConfig, ResourceReqConfig
from mstar.graph.base import GraphEdge, NodeAndGraphWalk, TensorPointerInfo
from mstar.graph.loop_indices import NestedLoopIndices
from mstar.model.base import ForwardPassArgs, Model, WorkerGraph
Expand Down Expand Up @@ -413,10 +413,16 @@ def _get_resource_configs(
Resolved once, here, and carried on the request: the worker hands each
config to its resource at ingest. KV shape is not part of this — that
is a deployment-wide property the model declares in its resource specs.

A declared stream gets a config even when the model returned none, or its
keys would have nowhere to go.
"""
return self.model.get_request_resource_configs(
configs = self.model.get_request_resource_configs(
partition_fwd_args=partition_fwd_args, model_kwargs=model_kwargs
)
for key, streams in self.model.prefix_key_streams().items():
configs.setdefault(key, KVReqConfig(needed_labels=list(streams)))
return configs

def _derive_worker_info(self):
"""Derive per-rank worker info from the worker graphs."""
Expand Down Expand Up @@ -886,8 +892,20 @@ def _do_ingest_request(
request_data.resource_configs = self._get_resource_configs(
model_kwargs, partition_fwd_args
)
for cfg in request_data.resource_configs.values():
cfg.apply_conductor_config(seed=seed)
# keyed by resource, so each config is handed only its own chain
kwargs = model_kwargs or {}
prefix_keys = kwargs.get("prefix_keys") or {}
prefix_tail = kwargs.get("prefix_tail") or {}
prefix_decode = kwargs.get("prefix_decode") or {}
prefix_cache = kwargs.get("prefix_cache")
for key, cfg in request_data.resource_configs.items():
cfg.apply_conductor_config(
seed=seed,
prefix_keys=prefix_keys.get(key),
prefix_tail=prefix_tail.get(key),
prefix_decode=prefix_decode.get(key),
prefix_cache=prefix_cache,
)

# Send NewRequest to each worker with the appropriate partition's inputs
for worker_id, worker_graph_ids in worker_to_worker_graph_ids.items():
Expand Down
Loading
Loading