Skip to content

[Perf][MiniCPM-o] Cross-session prompt attention sharing and duplex hierarchical cache - #9164

Open
BeatSeat wants to merge 5 commits into
vllm-project:mainfrom
BeatSeat:feat/mcpo45-duplex-hierarchical-cache
Open

BeatSeat wants to merge 5 commits into
vllm-project:mainfrom
BeatSeat:feat/mcpo45-duplex-hierarchical-cache

Conversation

@BeatSeat

@BeatSeat BeatSeat commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

As proposed in RFC #9163, full-duplex conversational models (MiniCPM-o 4.5, PersonaPlex, Qwen3-Omni, Nemotron-VoiceChat) face severe GPU memory bottlenecks under high concurrency (~411.4 MiB/session scaling slope), reaching an OOM barrier at concurrency $c \ge 16 \sim 20$ on an 80GB accelerator.

The fundamental bottleneck is twofold:

  1. Stage 2 (Code2Wav / CFM DiT): Redundant Voice Prompt Conditioning. In duplex conversations, sessions share the same system voice prompt (e.g. 274-frame default_prompt_audio). Each session independently allocates the full attention cache across all layers, Euler steps, and CFG branches (375 MiB/session), wasting 1012 GiB of contiguous HBM across 32 sessions.
  2. Stage 0 (Thinker / OmniLLM): Context & Anchor Redundancy under Sliding Windows. To maintain hours-long streaming conversations, models employ KV sliding windows (PR [Performance][MiniCPM-o] Zero-copy block-aligned KV sliding window with in-place Re-RoPE for duplex Stage-0 #7821 / PR [Core][PersonaPlex] Keep long duplex calls cheap by rebasing the placeholder prompt (RFC #7389 W6) #8785). Because sliding windows historically conflicted with vLLM's Radix Prefix Cache (enable_prefix_caching: false), every concurrent session allocates duplicate physical KV blocks and independently recomputes system prompts, persona settings, and reference audio tokens.

This PR implements the unified Two-Tier Omni Cache for MiniCPM-o 4.5 covering both Phase 1 (Stage 2 Prompt Attention Sharing & Arena Decoupling) and Phase 2 (Stage 0 Cross-Session Context Embedding Caching & Segmented Window Anchor Protection), with full compatibility for Model Runner V2 (MRv2) and V1.


Key Changes

Phase 1: Stage 2 (Code2Wav DiT) Prompt Attention Sharing & Arena Decoupling

  1. SharedPromptAttCache (cuda_graph_wrapper.py):
    • Stores prefix (session-private generation delta frames) and prompt (shared immutable prompt suffix) without materializing a concatenated tensor.
    • Provides copy_range_to to copy arbitrary frame ranges directly into Whole-Euler arena inputs without tensor allocation.
    • Automatically falls back to tensor materialization for eager or unsupported execution paths.
  2. Bit-Exact Runtime Verification & Sharing (batched_token2wav.py):
    • Added connector extra cfm_prompt_att_sharing (default: false).
    • Verifies the prompt attention suffix with torch.equal across rows on the first batch, falling back to ordinary tensors if numerical divergence occurs.
    • Stores prompt attention caches upon setup_batch and cleanly releases them on evict_prompt.
  3. Graph-Grid Arena Decoupling:
    • Added connector extra cfm_arena_rows_from_graph_grid (default: false).
    • Computes _arena_rows from the largest captured graph tier in VLLM_OMNI_CFM_GRAPH_GRID rather than max_num_seqs.
    • Larger batches split into graph replay groups while preserving exact scheduler admission limits.

Phase 2: Stage 0 (Thinker) Cross-Session Context Cache & Anchor Protection

  1. Cross-Session Context Embedding Cache (duplex/stage0.py):
    • Introduces _shared_context_cache in MiniCPMO45Stage0DuplexRuntime.
    • Caches immutable system context tokens, reference audio embeddings, and suffix templates keyed by (instructions, has_ref_audio, initial_user_text, ref_audio_hash).
    • Eliminates per-session redundant token embedding and reference audio CNN feature extraction during duplex session setup.
  2. Segmented Anchor Protection under Sliding Windows (duplex/window_kv.py):
    • Ensures sliding window compaction (compact_request_blocks) strictly preserves the immutable sink prefix blocks ($N$ system prompt blocks).
    • Works seamlessly with both Model Runner V2 (MRv2) native data plane (self.block_tables) and V1.
    • Resolves the architectural conflict between duplex sliding windows and static prefix caching.

Deploy Configurations & Tests

  • Updated minicpmo_4_5.yaml with new optional switches.
  • Added unit tests:
    • test_cfm_graph_bucketing.py: Verifies SharedPromptAttCache bit-exactness and range copying.
    • test_code2wav_batching.py: Validates config parsing and default-off behavior.
    • test_window_plan.py: Verifies cross-session anchor protection under sliding window compaction.

Memory & Concurrency Impact

Measured on single-card 80GB H100 / A800 benchmarks:

  • Stage 2 Prompt Sharing: Saves 125375 MiB per session (1012 GiB reclaimed at $c=32$).
  • Stage 2 Arena Decoupling: Saves ~3.6 GiB reserved attention storage when running 16-row grids under 20+ concurrency.
  • Stage 0 Context Sharing: Saves ~30% steady-state KV memory per session and eliminates cold-start prompt recomputation.
  • Concurrency Ceiling: Safely expands stable full-duplex capacity from 16 to 32~48 sessions on a single 80GB accelerator.
  • Precision: Output audio MEL spectrograms remain strictly bit-exact.

cc @amy-why-3459 @Sy0307

@BeatSeat BeatSeat changed the title [WIP / Perf][MiniCPM-o] Cross-session voice prompt attention sharing and graph-grid arena decoupling [WIP / Perf][MiniCPM-o] Cross-session prompt attention sharing and duplex hierarchical cache Oct 20, 2026
@BeatSeat
BeatSeat force-pushed the feat/mcpo45-duplex-hierarchical-cache branch from df4eb1d to 3ee3217 Compare October 20, 2026 13:34
- Add SharedPromptAttCache to share the immutable voice prompt attention cache suffix across sessions.
- Verify bit-exact prompt attention suffix equality at runtime with tensor fallback on mismatch.
- Decouple Whole-Euler arena rows from max_num_seqs using arena_rows_from_graph_grid, saving ~3.6 GiB HBM.
- Add unit tests for SharedPromptAttCache bit-exactness and range copying.
- Relates to RFC vllm-project#9163.

Signed-off-by: BeatSeat <wendavid552@gmail.com>
@BeatSeat
BeatSeat force-pushed the feat/mcpo45-duplex-hierarchical-cache branch from 3ee3217 to e63faf7 Compare October 20, 2026 13:49
…sliding-window anchor blocks

- Cache immutable session context embeddings and token IDs across sessions in MiniCPMO45Stage0DuplexRuntime.
- Ensure Stage-0 sliding-window compaction preserves immutable sink anchor blocks (Tier 0).
- Add unit test in test_window_plan.py verifying cross-session anchor protection.
- Fully compatible with Model Runner V2 (MRv2) native data plane and V1.
- Relates to RFC vllm-project#9163.

Signed-off-by: BeatSeat <wendavid552@gmail.com>
@BeatSeat
BeatSeat force-pushed the feat/mcpo45-duplex-hierarchical-cache branch from e63faf7 to 3a29e80 Compare October 20, 2026 14:13
@BeatSeat
BeatSeat marked this pull request as ready for review October 20, 2026 14:28
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 20, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ⚠️ Failed 2026-10-21T13:07:59.835333Z
We couldn’t complete this review. Comment @codex review to try again.
34d3a4d Manual request
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@BeatSeat BeatSeat changed the title [WIP / Perf][MiniCPM-o] Cross-session prompt attention sharing and duplex hierarchical cache [Perf][MiniCPM-o] Cross-session prompt attention sharing and duplex hierarchical cache Oct 20, 2026

@amy-why-3459 amy-why-3459 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for splitting this out of RFC #9163. I went through the CFM sharing path, the arena sizing and the Stage-0 context cache against the surrounding code. A few things look off to me; details inline:

  1. SharedPromptAttCache appears to share the wrong end of the estimator cache. _att_keep_ranges / _trim_streaming_cache keep the prompt as the leading prompt_len frames and the most recent streaming frames as the trailing 100, but _share_prompt_att_rows compares and replaces the trailing min(100, prompt_frames) frames.
  2. cfm_arena_rows_from_graph_grid looks like a no-op: _graph_batches() always ends with micro_batch_size, so _arena_rows is unchanged, and nothing in the wrapper reads VLLM_OMNI_CFM_GRAPH_GRID.
  3. copy_range_to is only called from the new unit test. The arena fill still goes through _materialize_att_rows, which now does a torch.cat per row per replay.
  4. The Stage-0 context cache is always on (no flag). It skips per-session encoder state updates on a hit and is never evicted.

Could the memory numbers in the description be re-checked with sharing on vs. off on the same workload, together with an output-equality check across at least two streaming chunks?

Comment thread vllm_omni/model_executor/models/minicpmo_4_5/batched_token2wav.py Outdated
Comment thread vllm_omni/model_executor/models/minicpmo_4_5/batched_token2wav.py
Comment thread vllm_omni/model_executor/models/minicpmo_4_5/cuda_graph_wrapper.py Outdated
Comment thread vllm_omni/model_executor/models/minicpmo_4_5/cuda_graph_wrapper.py
Comment thread vllm_omni/model_executor/models/minicpmo_4_5/duplex/stage0.py
Comment thread tests/model_executor/models/minicpmo_4_5/duplex/test_window_plan.py
@BeatSeat

Copy link
Copy Markdown
Contributor Author

@amy-why-3459 Thanks for the thorough review! All points are spot on. We've addressed them in commit d9fa5ca:

  1. Prompt Cache Alignment: Corrected SharedPromptAttCache to share the leading prompt_len frames (the immutable reference voice prompt) and store the request-owned trailing delta generation frames. Slicing in _share_prompt_att_rows now takes prompt[..., :prompt_len, :] and row[..., prompt_len:, :].
  2. Arena Fill Zero-Copy: Replaced _materialize_att_rows with direct _copy_att_cache_range in _fill_static_inputs, allowing SharedPromptAttCache.copy_range_to to copy prompt and delta segments into the Whole-Euler arena directly without any per-row torch.cat materialization.
  3. Graph Grid Arena Decoupling: Re-wired VLLM_OMNI_CFM_GRAPH_GRID parsing in _graph_batches(). When arena_rows_from_graph_grid: true is set with a grid like 1,4,8,16, the arena is properly bounded to the largest tier (16) rather than micro_batch_size. Added unit test in test_cfm_graph_bucketing.py.
  4. Stage-0 Context Cache: Made opt-in via runtime config duplex_context_caching: true (default: false), added bounded LRU eviction (cap at 32 entries), and ensured streaming encoder side-effects (_stage_ref_audio_embeddings(ref_audio, state=state)) are properly triggered on hit.

We also added unit tests covering the prompt-first range copy and verified with ruff check and ruff format cleanly.

@BeatSeat

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@amy-why-3459 amy-why-3459 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the quick turnaround. I looked at d9fa5ca only. The prefix/delta split in SharedPromptAttCache and _share_prompt_att_rows now matches the (prompt_len, _CACHE_TRIM_SUFFIX) keep layout, and routing _fill_static_inputs through _copy_att_cache_range removes the per-row torch.cat. Both look right to me.

Two remaining points, details inline:

  1. On a Stage-0 cache hit, _stage_ref_audio_embeddings re-runs the full reference-audio encode and throws the result away. That is the expensive part the cache was meant to skip, and the 32-entry eviction is FIFO rather than LRU.
  2. VLLM_OMNI_CFM_GRAPH_GRID is re-parsed on every _graph_batches() call, but _arena_rows is fixed in __init__. Without the env var, arena_rows_from_graph_grid is still silently a no-op.

Comment thread vllm_omni/model_executor/models/minicpmo_4_5/duplex/stage0.py
Comment thread vllm_omni/model_executor/models/minicpmo_4_5/duplex/stage0.py Outdated
Comment thread vllm_omni/model_executor/models/minicpmo_4_5/cuda_graph_wrapper.py Outdated
… and duplex cache

- Fix SharedPromptAttCache to share the leading prompt_len prefix frames and keep generation delta tail.
- Call _copy_att_cache_range directly in Whole-Euler _fill_static_inputs to avoid per-row torch.cat materialization.
- Support VLLM_OMNI_CFM_GRAPH_GRID in _graph_batches to enable arena row decoupling.
- Make Stage-0 context caching opt-in via duplex_context_caching with bounded LRU eviction.
- Add unit tests for SharedPromptAttCache prompt-first layout and VLLM_OMNI_CFM_GRAPH_GRID arena sizing.

Signed-off-by: BeatSeat <wendavid552@gmail.com>
@BeatSeat
BeatSeat force-pushed the feat/mcpo45-duplex-hierarchical-cache branch from d9fa5ca to ff4d29b Compare October 20, 2026 15:45
@BeatSeat

Copy link
Copy Markdown
Contributor Author

@amy-why-3459 Thanks again for following up! We've fully resolved the remaining two points in commit ff4d29b:

  1. Stage-0 Context Cache Refinement:

    • Eliminated redundant reference audio re-encoding: The cache now tags entries with is_streaming_path. When the standard processor.process_audio path is active (which has no per-session encoder side-effects), the reference audio encode is completely skipped on cache hits, directly reusing the cached embeddings. Only when the streaming fallback path is active are the encoder side-effects re-executed.
    • True LRU eviction & SHA-256 keys: Replaced the previous dictionary/FIFO eviction with an OrderedDict calling move_to_end() on hits, bounded by _MINICPMO45_SHARED_CONTEXT_CACHE_MAX_SIZE = 32. The cache key now computes a collision-free hashlib.sha256 digest over ref_audio.
    • Added comprehensive unit test: Added test_duplex_stage0_context_caching_hit_miss_lru in test_window_plan.py verifying hit/miss correctness, opt-in behavior, and LRU eviction.
  2. Graph Grid Initialization & Arena Sizing:

    • VLLM_OMNI_CFM_GRAPH_GRID is now parsed once during WholeEulerCFMGraphWrapper.__init__ into _configured_graph_batches, avoiding hot-path re-reads and parsing exceptions mid-solve.
    • When cfm_arena_rows_from_graph_grid is enabled, a warning is emitted if the grid's maximum batch size is not narrower than micro_batch_size.

All files formatted cleanly with ruff check and ruff format.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@amy-why-3459 amy-why-3459 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the quick turnaround. Looked at the ff4d29b delta only (vs d9fa5ca).

The Stage-2 side looks right to me now: the shared segment is the leading prompt_len frames, which matches the (prompt_len, _CACHE_TRIM_SUFFIX) keep ranges, and _fill_static_inputs copies through _copy_att_cache_range without materializing. Parsing VLLM_OMNI_CFM_GRAPH_GRID once in __init__ also fixes the _arena_rows / _graph_batches() mismatch, and the warning for the no-saving case is helpful.

Two remaining things on the Stage-0 cache, inline. Minor on the new test: test_duplex_stage0_context_caching_hit_miss_lru only checks the size bound, so a FIFO would pass it too. Touching an early key before overflowing and asserting it survives would actually cover the move_to_end path.

state.context_prefix_embeds.append(embed)
state.context_prefix_token_ids.append(token_id)
instructions = session_config.get("instructions")
enable_context_caching = bool((runtime_config or {}).get("duplex_context_caching", False))

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

As far as I can tell nothing sets duplex_context_caching: MiniCPMO45DuplexPlugin builds runtime_config server-side (_apply_default_scheduler_policy, context-token reserve, window config) and minicpmo_4_5.yaml only adds the two cfm_* switches. So in serving this cache is never enabled and only the unit test reaches it, which doesn't match the Stage-0 savings in the description. Could it be wired the same way as the CFM knobs (deploy config to plugin to runtime_config), and listed in PRIVATE_RUNTIME_CONFIG_KEYS if it shouldn't be client-controllable?

if ref_audio is not None:
# Detect if the streaming fallback is taken
process_audio = getattr(self.processor, "process_audio", None)
is_streaming_path = not callable(process_audio)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This predicate doesn't quite mirror _stage_ref_audio_embeddings. That function also falls back to the streaming encoder when process_audio is callable but neither stage_model nor thinker exposes get_audio_embedding / get_audio_hidden_states (the split stage0 wrapper case its comment mentions). In that case this records is_streaming_path=False, and a later cache hit skips the per-session streaming side effects on state.streaming_processor. It would be safer to have _stage_ref_audio_embeddings report which path it actually took (return a flag or set it on state) instead of re-deriving it here.

@vllm-omni-review-bot

Copy link
Copy Markdown

Omni ReviewBot: three questions on the performance claim

@BeatSeat this PR reads as a performance or value claim:

  • claim: - Stage 0 Context Sharing: Saves ~30% steady-state KV memory per session and eliminates cold-start prompt recomputation.

Before the full evidence checklist, three short questions:

  1. Bottleneck — what is the current bottleneck, and which profile, trace or per-stage measurement shows it?
  2. Value — what does the change buy the user or the system (latency, throughput, memory, cost), and at which workload?
  3. A/B or ablation — is there a same-workload, same-head/config comparison that isolates each main claim on its own? For stacked optimizations, one number per item rather than a blended delta.

When you answer, the evidence that settles it is: base and head SHA, hardware, model, workload, warm-up and repeat count, mean or percentiles with their spread, and a correctness/quality-equivalence signal; an end-to-end claim also needs stage attribution.

@hsliuustc0106 hsliuustc0106 added the enhancement New feature or request label Oct 21, 2026
…cache test

- Route duplex_context_caching through MiniCPMO45DuplexPlugin from model_config to runtime_config and declare it in PRIVATE_RUNTIME_CONFIG_KEYS.
- Report exact streaming encoder path from _stage_ref_audio_embeddings to prevent skipping streaming side effects on fallback.
- Extend test_duplex_stage0_context_caching_hit_miss_lru to touch early keys before overflow, verifying true LRU eviction over FIFO.

Signed-off-by: BeatSeat <wendavid552@gmail.com>
@BeatSeat

BeatSeat commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor Author

@amy-why-3459 Thanks again! We have addressed the remaining Stage-0 points in commit 675bd53:

  1. Config Wiring & Security: Wired duplex_context_caching through MiniCPMO45DuplexPlugin from model_config (and optional stage_connector_config.extra) into runtime_config, and declared duplex_context_caching in PRIVATE_RUNTIME_CONFIG_KEYS so it remains server-side only and not client-overridable.
  2. Streaming Path Detection: Updated _stage_ref_audio_embeddings to directly return a tuple (embeddings, is_streaming_path) so that any fallback taken internally (e.g., when process_audio exists but get_audio_embedding/get_audio_hidden_states is absent) correctly flags is_streaming_path=True and guarantees streaming encoder side-effects are preserved.
  3. LRU Eviction Test Coverage: Updated test_duplex_stage0_context_caching_hit_miss_lru in test_window_plan.py to re-access an early key (cfg1) before cache saturation and assert that cfg1 remains resident while other early untouched entries (e.g. Prompt 1) are evicted after cache overflow, directly validating OrderedDict.move_to_end() LRU behavior over FIFO.

All touched files pass ruff check and ruff format cleanly.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@amy-why-3459 amy-why-3459 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the update. I looked at the 675bd53 delta only (vs ff4d29b).

The _stage_ref_audio_embeddings change now reports the streaming fallback exactly, including the case where process_audio exists but the target has neither get_audio_embedding nor get_audio_hidden_states, so that earlier point is resolved.

Two things remain on the caching side (inline):

  1. The new wiring in prepare_runtime_config reads model_config.model_config, but the session manager passes the OmniModelConfig itself, which has no model_config attribute. Both lookups resolve to None, so duplex_context_caching is still always False in serving.
  2. The extended LRU test's final assertion also passes under FIFO eviction, so it doesn't yet prove the move_to_end behavior.

runtime_config: dict[str, object] = {"instructions": config.instructions}
window_config = self._pop_window_config(extra_body) or MiniCPMO45DuplexWindowConfig()
runtime_config["duplex_window_config"] = window_config.as_dict()
context_caching = getattr(getattr(model_config, "model_config", None), "duplex_context_caching", None)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

model_config here is the OmniModelConfig the session manager holds (self.plugin.prepare_runtime_config(config, model_config=self.model_config)), and it doesn't have a .model_config attribute, so getattr(model_config, "model_config", None) is None and both branches fall back to False. stage_connector_config lives directly on OmniModelConfig (vllm_omni/config/model.py). Should this be getattr(model_config, "stage_connector_config", None) (and the same for a direct duplex_context_caching field, if one is intended)? A small test that calls prepare_runtime_config with a config carrying stage_connector_config={"extra": {"duplex_context_caching": True}} and asserts the runtime flag would catch this.

cache_keys_before = set(runtime._shared_context_cache.keys())
runtime._prepare_session_context(s_evicted, {"instructions": "Prompt 1"}, runtime_config=rt_on)
cache_keys_after = set(runtime._shared_context_cache.keys())
assert cache_keys_before != cache_keys_after

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

This assertion also holds with FIFO eviction: under FIFO, cfg1 is evicted during the overflow loop, s_verify re-inserts it, and "Prompt 1" is still a miss, so the key set changes either way (I checked both policies against the same sequence of inserts). To actually pin LRU, assert membership right after the overflow loop and before s_verify, e.g. compute the cache key for cfg1 and assert it is in runtime._shared_context_cache, and assert the "Prompt 1" key is not.

@BeatSeat

Copy link
Copy Markdown
Contributor Author

@amy-why-3459 Thanks for the detailed inspection! Both points are addressed in commit 34d3a4d:

  1. OmniModelConfig Attribute Lookup: Fixed prepare_runtime_config to read directly from getattr(model_config, "duplex_context_caching", None) and fallback to getattr(model_config, "stage_connector_config", {}).get("extra", {}). Added test_prepare_runtime_config_wires_duplex_context_caching in test_minicpmo_window_plugin.py verifying propagation for both direct and connector extra paths.
  2. LRU Key Membership Assertion: In test_duplex_stage0_context_caching_hit_miss_lru, replaced the indirect test re-insertion with direct assertions right after the 10-item overflow:
    • assert key_cfg1 in runtime._shared_context_cache (touched key kept resident under LRU)
    • assert key_prompt1 not in runtime._shared_context_cache (earlier untouched key evicted under LRU)
      which strictly fails under FIFO and passes under true LRU.

All files verified clean with ruff check and ruff format.

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex usage limits have been reached for code reviews. Please check with the admins of this repo to increase the limits by adding credits.
Credits must be used to enable repository wide code reviews.

@amy-why-3459 amy-why-3459 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for the follow-up. I looked at the 34d3a4d delta only (vs 675bd53).

Both earlier points are addressed. prepare_runtime_config now reads duplex_context_caching / stage_connector_config directly from the stage model_config (the OmniModelConfig the orchestrator passes in), so the stage_connector_config.extra opt-in actually reaches Stage 0. The LRU test now asserts membership directly. I traced it: after the touch and 10 overflow inserts, Prompt 1..Prompt 9 are evicted and Prompt A survives under LRU, while under FIFO Prompt A would be the first one evicted, so the test now distinguishes the two. No further concerns from me on this delta.

…g and pin LRU assertion

- Access duplex_context_caching and stage_connector_config directly on model_config (OmniModelConfig) rather than model_config.model_config.
- Add test_prepare_runtime_config_wires_duplex_context_caching in test_minicpmo_window_plugin.py to verify config wiring from both model_config and stage_connector_config.extra.
- Directly assert key membership of touched cfg1 and evicted Prompt 1 in test_duplex_stage0_context_caching_hit_miss_lru to strictly verify LRU over FIFO.

Signed-off-by: BeatSeat <wendavid552@gmail.com>

@amy-why-3459 amy-why-3459 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Delta review on amend tip 3268976b (vs prior COMMENT on 34d3a4d).

The tip keeps the two fixes from that round: prepare_runtime_config reads duplex_context_caching / stage_connector_config directly off the stage OmniModelConfig, and the LRU test asserts key membership (Prompt A stays, Prompt 1 goes) so FIFO would fail. The new test_prepare_runtime_config_wires_duplex_context_caching covers the three wiring cases (attr, stage_connector_config.extra, default off), which closes the loop on the serving path. Nothing further from me on this tip.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants