From 520770da4cdd445268b96a7149d884669a354c68 Mon Sep 17 00:00:00 2001 From: wxiwnd Date: Wed, 3 Jun 2026 03:26:08 +0800 Subject: [PATCH] feat(mem-cache): enable CP shared-KV batch owner allocation Route supported multi-request CP shared-KV extend allocation through request-local owner plans so downstream direct-write paths can trust flattened out_cache_loc owner lanes. Unsupported page plans now fail fast instead of falling back to legacy allocation. Include the normative govctl RFC and completed work-item trail in the tracked repository; keep transient superpowers planning outside the sglang branch. Constraint: RFC-0001 W2 requires no legacy multi_batch fallback for supported bs>1 owner-lane allocation Rejected: concatenating requests before owner planning | CP ownership is request-relative Rejected: committing only docs/superpowers plan | it omits the normative RFC and work-item trail Confidence: high Scope-risk: moderate Directive: Preserve flattened request order when changing CP shared-KV allocation; run govctl from the sglang repository root Tested: remote uv test_nsa_cp_utils.py 39 OK; remote uv test_cp_shared_kv_layout.py 34 OK; remote uv test_alloc_pages_with_owners.py 10 OK; govctl check; git diff --check Not-tested: CUDA ETE/perf paths beyond focused W1/W2 unit suites --- docs/rfc/RFC-0001.md | 144 ++++++++ gov/config.toml | 44 +++ .../RFC-0001/clauses/C-BS1-COMPATIBILITY.toml | 18 + .../clauses/C-CAPACITY-AND-EVICTION.toml | 20 ++ .../clauses/C-DOWNSTREAM-CONTRACT.toml | 18 + .../clauses/C-FLATTENED-OWNER-ORDER.toml | 20 ++ .../clauses/C-NO-LEGACY-FALLBACK.toml | 20 ++ .../clauses/C-OWNER-LANE-INVARIANT.toml | 22 ++ .../C-REQUEST-LOCAL-OWNER-PLANNING.toml | 20 ++ gov/rfc/RFC-0001/clauses/C-SUMMARY.toml | 16 + gov/rfc/RFC-0001/clauses/C-VERIFICATION.toml | 22 ++ gov/rfc/RFC-0001/rfc.toml | 33 ++ gov/schema/adr.schema.json | 103 ++++++ gov/schema/clause.schema.json | 64 ++++ gov/schema/guard.schema.json | 61 ++++ gov/schema/release.schema.json | 45 +++ gov/schema/rfc.schema.json | 120 +++++++ gov/schema/work.schema.json | 157 +++++++++ ...shared-kv-batch-owner-lane-allocation.toml | 68 ++++ python/sglang/srt/mem_cache/allocator.py | 3 - python/sglang/srt/mem_cache/common.py | 79 ++++- .../mem_cache/cp_shared_kv_compute_owner.py | 28 ++ .../mem_cache/test_cp_shared_kv_layout.py | 325 ++++++++++++++++++ 23 files changed, 1431 insertions(+), 19 deletions(-) create mode 100644 docs/rfc/RFC-0001.md create mode 100644 gov/config.toml create mode 100644 gov/rfc/RFC-0001/clauses/C-BS1-COMPATIBILITY.toml create mode 100644 gov/rfc/RFC-0001/clauses/C-CAPACITY-AND-EVICTION.toml create mode 100644 gov/rfc/RFC-0001/clauses/C-DOWNSTREAM-CONTRACT.toml create mode 100644 gov/rfc/RFC-0001/clauses/C-FLATTENED-OWNER-ORDER.toml create mode 100644 gov/rfc/RFC-0001/clauses/C-NO-LEGACY-FALLBACK.toml create mode 100644 gov/rfc/RFC-0001/clauses/C-OWNER-LANE-INVARIANT.toml create mode 100644 gov/rfc/RFC-0001/clauses/C-REQUEST-LOCAL-OWNER-PLANNING.toml create mode 100644 gov/rfc/RFC-0001/clauses/C-SUMMARY.toml create mode 100644 gov/rfc/RFC-0001/clauses/C-VERIFICATION.toml create mode 100644 gov/rfc/RFC-0001/rfc.toml create mode 100644 gov/schema/adr.schema.json create mode 100644 gov/schema/clause.schema.json create mode 100644 gov/schema/guard.schema.json create mode 100644 gov/schema/release.schema.json create mode 100644 gov/schema/rfc.schema.json create mode 100644 gov/schema/work.schema.json create mode 100644 gov/work/2026-06-03-implement-cp-shared-kv-batch-owner-lane-allocation.toml diff --git a/docs/rfc/RFC-0001.md b/docs/rfc/RFC-0001.md new file mode 100644 index 000000000..6c89edada --- /dev/null +++ b/docs/rfc/RFC-0001.md @@ -0,0 +1,144 @@ + + + +# RFC-0001: CP Shared-KV Batch Owner-Lane Allocation + +> **Version:** 0.1.0 | **Status:** normative | **Phase:** stable + +--- + +## 1. Summary + +### [RFC-0001:C-SUMMARY] Summary (Informative) + +This RFC specifies the W2 allocation contract for NSA prefill CP shared-KV when a real extend batch contains more than one request. + +**Scope:** This RFC covers owner-lane page-owner planning, allocation ordering, owner-lane invariants, capacity failure semantics, and compatibility requirements for CP shared-KV extend allocation. It does not specify CP metadata construction, KV direct-write kernels, current reuse, HiCache load/backup, or EAGLE draft behavior except where those later workstreams depend on the allocation contract. + +**Rationale:** W2 must make batch-size greater than one enter the same owner-lane allocation contract as batch-size one. Without a normative allocation contract, later direct-write and HiCache workstreams cannot trust that newly allocated logical pages belong to the ranks that compute their contents. + +*Since: v0.1.0* + +--- + +## 2. Specification + +### [RFC-0001:C-REQUEST-LOCAL-OWNER-PLANNING] Request-local owner planning (Normative) + +For CP shared-KV extend allocation, the system MUST derive page-owner requirements independently for each request in the batch. + +The system MUST use each request's own prefix length, extend length, page size, CP size, and in-sequence CP split semantics when deriving page owners. + +The system MUST NOT derive owner requirements by treating multiple requests as one concatenated sequence. + +The system MUST reject or fail fast for a request whose owner requirements cannot be derived under the CP shared-KV page-aligned contract. + +**Rationale:** CP in-sequence ownership is request-relative. Combining requests before deriving owners can assign pages to the wrong compute rank even if the final flattened token count is correct. + +*Since: v0.1.0* + +### [RFC-0001:C-FLATTENED-OWNER-ORDER] Flattened owner order (Normative) + +The system MUST concatenate per-request page-owner lists in request order. + +The system MUST preserve the order of pages within each request's owner list. + +The system MUST NOT reorder the flattened owner list by owner lane, physical page availability, or CP rank. + +The system MUST keep the flattened owner list length equal to the number of newly allocated logical pages for the extend batch. + +**Rationale:** The extend output cache location tensor is consumed in flattened request-token order. Reordering owners by lane would make page-level ownership appear locally valid while corrupting request-to-token placement. + +*Since: v0.1.0* + +### [RFC-0001:C-OWNER-LANE-INVARIANT] Owner-lane allocation invariant (Normative) + +For every newly allocated non-dummy logical page, the system MUST choose a logical page id whose owner lane equals the corresponding flattened expected owner. + +The owner lane of a non-dummy logical page MUST be computed as `(logical_page_id - 1) % cp_size`. + +The system MUST NOT allocate dummy page zero for request data. + +The produced extend cache locations MUST remain in flattened request-token order. + +The produced extend cache locations MUST map each newly allocated page to the expected owner lane before later CP direct-write consumers observe the locations. + +**Rationale:** W3 direct write can only write local KV/index pages safely if the logical page selected during allocation is owned by the rank that computes that page. + +*Since: v0.1.0* + +### [RFC-0001:C-NO-LEGACY-FALLBACK] No legacy allocation fallback for supported batches (Normative) + +When CP shared-KV is enabled, context-parallel prefill is enabled, in-sequence CP split mode is selected, and all requests in the extend batch satisfy the page-aligned owner-planning contract, the system MUST use owner-lane allocation for batch-size greater than one. + +The system MUST NOT use legacy paged allocation for a supported CP shared-KV batch-size greater than one case. + +The system MUST NOT report `multi_batch` as a fallback reason for a supported CP shared-KV batch-size greater than one case. + +If a CP shared-KV batch-size greater than one case is unsupported, the system MUST emit an explicit fail-fast or fallback reason using the CP shared-KV diagnostic naming scheme before leaving the owner-lane allocation path. + +**Rationale:** Silent legacy allocation breaks the page-owner contract while allowing execution to continue into paths that assume owner-lane placement. + +*Since: v0.1.0* + +### [RFC-0001:C-CAPACITY-AND-EVICTION] Owner-lane capacity and eviction (Normative) + +If owner-lane allocation cannot be satisfied from immediately available owner-lane pages, the system SHOULD use the existing owner-lane eviction and retry mechanism before reporting capacity failure. + +After owner-lane eviction and retry, if the required owner lanes remain unavailable, the system MUST raise a recoverable capacity-wait error rather than falling back to legacy allocation. + +The capacity-wait error MUST include required, available, and deficit page counts by owner lane when those diagnostics are available. + +The system MUST preserve allocator state when an owner-lane allocation attempt fails without selecting pages. + +**Rationale:** Owner-lane pressure is lane-specific, not aggregate-capacity-specific. Reporting lane diagnostics preserves scheduler recoverability and prevents legacy allocation from hiding placement bugs. + +*Since: v0.1.0* + +### [RFC-0001:C-BS1-COMPATIBILITY] Batch-size one compatibility (Normative) + +Batch-size one CP shared-KV allocation MUST continue to satisfy the existing owner-lane allocation contract. + +A batch-size one request processed through the batch-aware allocation path MUST produce the same owner sequence as the existing single-request in-sequence owner planner for the same prefix length, extend length, page size, and CP size. + +The system SHOULD share owner-lane allocation logic between batch-size one and batch-size greater than one to avoid long-lived divergent behavior. + +**Rationale:** W2 is an extension of the current owner-lane allocation path, not a replacement with different semantics for existing single-request prefill. + +*Since: v0.1.0* + +### [RFC-0001:C-VERIFICATION] Verification requirements (Normative) + +Tests MUST cover at least two requests with different prefix lengths and extend lengths. + +Tests MUST verify that each newly allocated logical page in the output cache locations has the expected owner lane from the flattened owner list. + +Tests MUST verify that owner-lane exhaustion for a batch-size greater than one case raises a recoverable capacity-wait error or returns an allocation failure to the caller that triggers that error path. + +Tests MUST verify that batch-size one allocation remains compatible with the existing owner-lane behavior. + +Tests SHOULD verify that supported batch-size greater than one CP shared-KV allocation does not call legacy allocation and does not emit a `multi_batch` fallback. + +**Rationale:** These checks directly exercise the failure modes that W2 is intended to remove: cross-request owner mixing, wrong logical page owners, capacity fallback, and single-request regression. + +*Since: v0.1.0* + +### [RFC-0001:C-DOWNSTREAM-CONTRACT] Downstream contract (Normative) + +After successful W2 allocation, downstream CP shared-KV direct-write consumers MAY assume that newly allocated logical pages satisfy the owner-lane invariant for their corresponding request-local page positions. + +W2 MUST NOT introduce a new planner collective. + +W2 MUST NOT require W3, W4, W6, or W7 consumers to recompute request-local page owners from raw prefix and extend lengths when the allocation result and batch plan metadata are already available. + +**Rationale:** W2 exists to establish a stable allocation boundary. Later workstreams should validate and consume the contract instead of re-deriving allocation ownership independently. + +*Since: v0.1.0* + +--- + +## Changelog + +### v0.1.0 (2026-06-03) + +Initial draft diff --git a/gov/config.toml b/gov/config.toml new file mode 100644 index 000000000..cb2e13027 --- /dev/null +++ b/gov/config.toml @@ -0,0 +1,44 @@ +[project] +name = "my-project" +# Default owner for new RFCs (uses git user.name if not set) +# default_owner = "@your-handle" + +[paths] +docs_output = "docs" +# AI agent directory — target for `govctl init-skills` +# Contains skills/ and agents/ subdirs +# Default: ".claude" (Claude Code / Claude Desktop) +# For Cursor: ".cursor" +# For Windsurf: ".windsurf" +# agent_dir = ".claude" + +[schema] +version = 2 + +# [work_item] +# ID strategy for work items (default: sequential) +# - sequential: WI-YYYY-MM-DD-NNN (solo projects) +# - author-hash: WI-YYYY-MM-DD-{hash}-NNN (multi-person teams, uses git email) +# - random: WI-YYYY-MM-DD-{rand} (simple uniqueness) +# id_strategy = "author-hash" + +# [verification] +# Enable project-level default verification guards. +# enabled = true +# default_guards = ["GUARD-GOVCTL-CHECK", "GUARD-CARGO-TEST"] + +# [source_scan] +# Scan source files for [[artifact-id]] references during `govctl check` +# enabled = false +# include = ["src/**/*.rs", "crates/**/*.rs", "**/*.md"] +# exclude = [] + +# [concurrency] +# Maximum seconds to wait for exclusive lock before failing (default: 30) +# Implements [[RFC-0004]] concurrent write safety +# lock_timeout_secs = 30 + +# [tags] +# Controlled-vocabulary tags for artifact classification — [[RFC-0002:C-RESOURCES]] +# Artifacts may only use tags listed here. +# allowed = ["security", "breaking-change", "performance"] diff --git a/gov/rfc/RFC-0001/clauses/C-BS1-COMPATIBILITY.toml b/gov/rfc/RFC-0001/clauses/C-BS1-COMPATIBILITY.toml new file mode 100644 index 000000000..9269f8d51 --- /dev/null +++ b/gov/rfc/RFC-0001/clauses/C-BS1-COMPATIBILITY.toml @@ -0,0 +1,18 @@ +#:schema ../../../schema/clause.schema.json + +[govctl] +id = "C-BS1-COMPATIBILITY" +title = "Batch-size one compatibility" +kind = "normative" +status = "active" +since = "0.1.0" + +[content] +text = """ +Batch-size one CP shared-KV allocation MUST continue to satisfy the existing owner-lane allocation contract. + +A batch-size one request processed through the batch-aware allocation path MUST produce the same owner sequence as the existing single-request in-sequence owner planner for the same prefix length, extend length, page size, and CP size. + +The system SHOULD share owner-lane allocation logic between batch-size one and batch-size greater than one to avoid long-lived divergent behavior. + +**Rationale:** W2 is an extension of the current owner-lane allocation path, not a replacement with different semantics for existing single-request prefill.""" diff --git a/gov/rfc/RFC-0001/clauses/C-CAPACITY-AND-EVICTION.toml b/gov/rfc/RFC-0001/clauses/C-CAPACITY-AND-EVICTION.toml new file mode 100644 index 000000000..dc24e94f5 --- /dev/null +++ b/gov/rfc/RFC-0001/clauses/C-CAPACITY-AND-EVICTION.toml @@ -0,0 +1,20 @@ +#:schema ../../../schema/clause.schema.json + +[govctl] +id = "C-CAPACITY-AND-EVICTION" +title = "Owner-lane capacity and eviction" +kind = "normative" +status = "active" +since = "0.1.0" + +[content] +text = """ +If owner-lane allocation cannot be satisfied from immediately available owner-lane pages, the system SHOULD use the existing owner-lane eviction and retry mechanism before reporting capacity failure. + +After owner-lane eviction and retry, if the required owner lanes remain unavailable, the system MUST raise a recoverable capacity-wait error rather than falling back to legacy allocation. + +The capacity-wait error MUST include required, available, and deficit page counts by owner lane when those diagnostics are available. + +The system MUST preserve allocator state when an owner-lane allocation attempt fails without selecting pages. + +**Rationale:** Owner-lane pressure is lane-specific, not aggregate-capacity-specific. Reporting lane diagnostics preserves scheduler recoverability and prevents legacy allocation from hiding placement bugs.""" diff --git a/gov/rfc/RFC-0001/clauses/C-DOWNSTREAM-CONTRACT.toml b/gov/rfc/RFC-0001/clauses/C-DOWNSTREAM-CONTRACT.toml new file mode 100644 index 000000000..6174b5e62 --- /dev/null +++ b/gov/rfc/RFC-0001/clauses/C-DOWNSTREAM-CONTRACT.toml @@ -0,0 +1,18 @@ +#:schema ../../../schema/clause.schema.json + +[govctl] +id = "C-DOWNSTREAM-CONTRACT" +title = "Downstream contract" +kind = "normative" +status = "active" +since = "0.1.0" + +[content] +text = """ +After successful W2 allocation, downstream CP shared-KV direct-write consumers MAY assume that newly allocated logical pages satisfy the owner-lane invariant for their corresponding request-local page positions. + +W2 MUST NOT introduce a new planner collective. + +W2 MUST NOT require W3, W4, W6, or W7 consumers to recompute request-local page owners from raw prefix and extend lengths when the allocation result and batch plan metadata are already available. + +**Rationale:** W2 exists to establish a stable allocation boundary. Later workstreams should validate and consume the contract instead of re-deriving allocation ownership independently.""" diff --git a/gov/rfc/RFC-0001/clauses/C-FLATTENED-OWNER-ORDER.toml b/gov/rfc/RFC-0001/clauses/C-FLATTENED-OWNER-ORDER.toml new file mode 100644 index 000000000..eb055afaa --- /dev/null +++ b/gov/rfc/RFC-0001/clauses/C-FLATTENED-OWNER-ORDER.toml @@ -0,0 +1,20 @@ +#:schema ../../../schema/clause.schema.json + +[govctl] +id = "C-FLATTENED-OWNER-ORDER" +title = "Flattened owner order" +kind = "normative" +status = "active" +since = "0.1.0" + +[content] +text = """ +The system MUST concatenate per-request page-owner lists in request order. + +The system MUST preserve the order of pages within each request's owner list. + +The system MUST NOT reorder the flattened owner list by owner lane, physical page availability, or CP rank. + +The system MUST keep the flattened owner list length equal to the number of newly allocated logical pages for the extend batch. + +**Rationale:** The extend output cache location tensor is consumed in flattened request-token order. Reordering owners by lane would make page-level ownership appear locally valid while corrupting request-to-token placement.""" diff --git a/gov/rfc/RFC-0001/clauses/C-NO-LEGACY-FALLBACK.toml b/gov/rfc/RFC-0001/clauses/C-NO-LEGACY-FALLBACK.toml new file mode 100644 index 000000000..56d49e0ad --- /dev/null +++ b/gov/rfc/RFC-0001/clauses/C-NO-LEGACY-FALLBACK.toml @@ -0,0 +1,20 @@ +#:schema ../../../schema/clause.schema.json + +[govctl] +id = "C-NO-LEGACY-FALLBACK" +title = "No legacy allocation fallback for supported batches" +kind = "normative" +status = "active" +since = "0.1.0" + +[content] +text = """ +When CP shared-KV is enabled, context-parallel prefill is enabled, in-sequence CP split mode is selected, and all requests in the extend batch satisfy the page-aligned owner-planning contract, the system MUST use owner-lane allocation for batch-size greater than one. + +The system MUST NOT use legacy paged allocation for a supported CP shared-KV batch-size greater than one case. + +The system MUST NOT report `multi_batch` as a fallback reason for a supported CP shared-KV batch-size greater than one case. + +If a CP shared-KV batch-size greater than one case is unsupported, the system MUST emit an explicit fail-fast or fallback reason using the CP shared-KV diagnostic naming scheme before leaving the owner-lane allocation path. + +**Rationale:** Silent legacy allocation breaks the page-owner contract while allowing execution to continue into paths that assume owner-lane placement.""" diff --git a/gov/rfc/RFC-0001/clauses/C-OWNER-LANE-INVARIANT.toml b/gov/rfc/RFC-0001/clauses/C-OWNER-LANE-INVARIANT.toml new file mode 100644 index 000000000..9d308dc17 --- /dev/null +++ b/gov/rfc/RFC-0001/clauses/C-OWNER-LANE-INVARIANT.toml @@ -0,0 +1,22 @@ +#:schema ../../../schema/clause.schema.json + +[govctl] +id = "C-OWNER-LANE-INVARIANT" +title = "Owner-lane allocation invariant" +kind = "normative" +status = "active" +since = "0.1.0" + +[content] +text = """ +For every newly allocated non-dummy logical page, the system MUST choose a logical page id whose owner lane equals the corresponding flattened expected owner. + +The owner lane of a non-dummy logical page MUST be computed as `(logical_page_id - 1) % cp_size`. + +The system MUST NOT allocate dummy page zero for request data. + +The produced extend cache locations MUST remain in flattened request-token order. + +The produced extend cache locations MUST map each newly allocated page to the expected owner lane before later CP direct-write consumers observe the locations. + +**Rationale:** W3 direct write can only write local KV/index pages safely if the logical page selected during allocation is owned by the rank that computes that page.""" diff --git a/gov/rfc/RFC-0001/clauses/C-REQUEST-LOCAL-OWNER-PLANNING.toml b/gov/rfc/RFC-0001/clauses/C-REQUEST-LOCAL-OWNER-PLANNING.toml new file mode 100644 index 000000000..c37aef293 --- /dev/null +++ b/gov/rfc/RFC-0001/clauses/C-REQUEST-LOCAL-OWNER-PLANNING.toml @@ -0,0 +1,20 @@ +#:schema ../../../schema/clause.schema.json + +[govctl] +id = "C-REQUEST-LOCAL-OWNER-PLANNING" +title = "Request-local owner planning" +kind = "normative" +status = "active" +since = "0.1.0" + +[content] +text = """ +For CP shared-KV extend allocation, the system MUST derive page-owner requirements independently for each request in the batch. + +The system MUST use each request's own prefix length, extend length, page size, CP size, and in-sequence CP split semantics when deriving page owners. + +The system MUST NOT derive owner requirements by treating multiple requests as one concatenated sequence. + +The system MUST reject or fail fast for a request whose owner requirements cannot be derived under the CP shared-KV page-aligned contract. + +**Rationale:** CP in-sequence ownership is request-relative. Combining requests before deriving owners can assign pages to the wrong compute rank even if the final flattened token count is correct.""" diff --git a/gov/rfc/RFC-0001/clauses/C-SUMMARY.toml b/gov/rfc/RFC-0001/clauses/C-SUMMARY.toml new file mode 100644 index 000000000..51967bb78 --- /dev/null +++ b/gov/rfc/RFC-0001/clauses/C-SUMMARY.toml @@ -0,0 +1,16 @@ +#:schema ../../../schema/clause.schema.json + +[govctl] +id = "C-SUMMARY" +title = "Summary" +kind = "informative" +status = "active" +since = "0.1.0" + +[content] +text = """ +This RFC specifies the W2 allocation contract for NSA prefill CP shared-KV when a real extend batch contains more than one request. + +**Scope:** This RFC covers owner-lane page-owner planning, allocation ordering, owner-lane invariants, capacity failure semantics, and compatibility requirements for CP shared-KV extend allocation. It does not specify CP metadata construction, KV direct-write kernels, current reuse, HiCache load/backup, or EAGLE draft behavior except where those later workstreams depend on the allocation contract. + +**Rationale:** W2 must make batch-size greater than one enter the same owner-lane allocation contract as batch-size one. Without a normative allocation contract, later direct-write and HiCache workstreams cannot trust that newly allocated logical pages belong to the ranks that compute their contents.""" diff --git a/gov/rfc/RFC-0001/clauses/C-VERIFICATION.toml b/gov/rfc/RFC-0001/clauses/C-VERIFICATION.toml new file mode 100644 index 000000000..984ba9192 --- /dev/null +++ b/gov/rfc/RFC-0001/clauses/C-VERIFICATION.toml @@ -0,0 +1,22 @@ +#:schema ../../../schema/clause.schema.json + +[govctl] +id = "C-VERIFICATION" +title = "Verification requirements" +kind = "normative" +status = "active" +since = "0.1.0" + +[content] +text = """ +Tests MUST cover at least two requests with different prefix lengths and extend lengths. + +Tests MUST verify that each newly allocated logical page in the output cache locations has the expected owner lane from the flattened owner list. + +Tests MUST verify that owner-lane exhaustion for a batch-size greater than one case raises a recoverable capacity-wait error or returns an allocation failure to the caller that triggers that error path. + +Tests MUST verify that batch-size one allocation remains compatible with the existing owner-lane behavior. + +Tests SHOULD verify that supported batch-size greater than one CP shared-KV allocation does not call legacy allocation and does not emit a `multi_batch` fallback. + +**Rationale:** These checks directly exercise the failure modes that W2 is intended to remove: cross-request owner mixing, wrong logical page owners, capacity fallback, and single-request regression.""" diff --git a/gov/rfc/RFC-0001/rfc.toml b/gov/rfc/RFC-0001/rfc.toml new file mode 100644 index 000000000..17b6c9316 --- /dev/null +++ b/gov/rfc/RFC-0001/rfc.toml @@ -0,0 +1,33 @@ +#:schema ../../schema/rfc.schema.json + +[govctl] +id = "RFC-0001" +title = "CP Shared-KV Batch Owner-Lane Allocation" +version = "0.1.0" +status = "normative" +phase = "stable" +owners = ["@wxiwnd"] +created = "2026-06-03" +updated = "2026-06-03" + +[[sections]] +title = "Summary" +clauses = ["clauses/C-SUMMARY.toml"] + +[[sections]] +title = "Specification" +clauses = [ + "clauses/C-REQUEST-LOCAL-OWNER-PLANNING.toml", + "clauses/C-FLATTENED-OWNER-ORDER.toml", + "clauses/C-OWNER-LANE-INVARIANT.toml", + "clauses/C-NO-LEGACY-FALLBACK.toml", + "clauses/C-CAPACITY-AND-EVICTION.toml", + "clauses/C-BS1-COMPATIBILITY.toml", + "clauses/C-VERIFICATION.toml", + "clauses/C-DOWNSTREAM-CONTRACT.toml", +] + +[[changelog]] +version = "0.1.0" +date = "2026-06-03" +notes = "Initial draft" diff --git a/gov/schema/adr.schema.json b/gov/schema/adr.schema.json new file mode 100644 index 000000000..cdd376ddd --- /dev/null +++ b/gov/schema/adr.schema.json @@ -0,0 +1,103 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "ADR Specification", + "description": "JSON Schema for ADR TOML artifacts after parsing and normalization", + "type": "object", + "required": ["govctl", "content"], + "properties": { + "govctl": { + "type": "object", + "required": ["id", "title", "status", "date"], + "properties": { + "id": { + "type": "string", + "pattern": "^ADR-\\d{4}$" + }, + "title": { + "type": "string", + "minLength": 1 + }, + "status": { + "type": "string", + "enum": ["proposed", "accepted", "rejected", "superseded"] + }, + "date": { + "type": "string", + "format": "date" + }, + "superseded_by": { + "type": "string", + "pattern": "^ADR-\\d{4}$" + }, + "refs": { + "type": "array", + "items": { + "type": "string", + "pattern": "^(RFC-\\d{4}(?::C-[A-Z][A-Z0-9-]*)?|ADR-\\d{4}|WI-\\d{4}-\\d{2}-\\d{2}-(?:[a-f0-9]{4}(?:-\\d{3})?|\\d{3}))$" + } + }, + "tags": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$" + } + }, + "schema": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "content": { + "type": "object", + "required": ["context", "decision", "consequences"], + "properties": { + "context": { + "type": "string" + }, + "decision": { + "type": "string" + }, + "consequences": { + "type": "string" + }, + "alternatives": { + "type": "array", + "items": { + "type": "object", + "required": ["text"], + "properties": { + "text": { + "type": "string", + "minLength": 1 + }, + "status": { + "type": "string", + "enum": ["considered", "rejected", "accepted"] + }, + "pros": { + "type": "array", + "items": { + "type": "string" + } + }, + "cons": { + "type": "array", + "items": { + "type": "string" + } + }, + "rejection_reason": { + "type": "string" + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false +} diff --git a/gov/schema/clause.schema.json b/gov/schema/clause.schema.json new file mode 100644 index 000000000..4cece7657 --- /dev/null +++ b/gov/schema/clause.schema.json @@ -0,0 +1,64 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Clause Specification", + "description": "JSON Schema for clause TOML artifacts with [govctl] metadata + [content] sections", + "type": "object", + "required": ["govctl", "content"], + "properties": { + "govctl": { + "type": "object", + "required": ["id", "title", "kind"], + "properties": { + "id": { + "type": "string", + "pattern": "^C-[A-Z0-9-]+$" + }, + "title": { + "type": "string", + "minLength": 1 + }, + "kind": { + "type": "string", + "enum": ["normative", "informative"] + }, + "status": { + "type": "string", + "enum": ["active", "superseded", "deprecated"], + "default": "active" + }, + "since": { + "type": "string", + "pattern": "^\\d+\\.\\d+\\.\\d+$" + }, + "superseded_by": { + "type": "string", + "pattern": "^(RFC-\\d{4}:)?C-[A-Z0-9-]+$" + }, + "anchors": { + "type": "array", + "items": { "type": "string" } + }, + "tags": { + "type": "array", + "items": { "type": "string", "pattern": "^[a-z][a-z0-9-]*$" } + }, + "schema": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "content": { + "type": "object", + "required": ["text"], + "properties": { + "text": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false +} diff --git a/gov/schema/guard.schema.json b/gov/schema/guard.schema.json new file mode 100644 index 000000000..a98c71417 --- /dev/null +++ b/gov/schema/guard.schema.json @@ -0,0 +1,61 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Verification Guard Specification", + "description": "JSON Schema for Verification Guard TOML artifacts after parsing and normalization", + "type": "object", + "required": ["govctl", "check"], + "properties": { + "govctl": { + "type": "object", + "required": ["id", "title"], + "properties": { + "id": { + "type": "string", + "pattern": "^GUARD-[A-Z][A-Z0-9-]*$" + }, + "title": { + "type": "string", + "minLength": 1 + }, + "refs": { + "type": "array", + "items": { + "type": "string", + "pattern": "^(RFC-\\d{4}(?::C-[A-Z][A-Z0-9-]*)?|ADR-\\d{4}|WI-\\d{4}-\\d{2}-\\d{2}-(?:[a-f0-9]{4}(?:-\\d{3})?|\\d{3}))$" + } + }, + "tags": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$" + } + }, + "schema": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "check": { + "type": "object", + "required": ["command"], + "properties": { + "command": { + "type": "string", + "minLength": 1 + }, + "timeout_secs": { + "type": "integer", + "minimum": 1 + }, + "pattern": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false +} diff --git a/gov/schema/release.schema.json b/gov/schema/release.schema.json new file mode 100644 index 000000000..b93ef2ce9 --- /dev/null +++ b/gov/schema/release.schema.json @@ -0,0 +1,45 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Release Specification", + "description": "JSON Schema for releases.toml after parsing and normalization", + "type": "object", + "required": ["releases"], + "properties": { + "govctl": { + "type": "object", + "required": [], + "properties": { + "schema": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "releases": { + "type": "array", + "items": { + "type": "object", + "required": ["version", "date"], + "properties": { + "version": { + "type": "string", + "pattern": "^\\d+\\.\\d+\\.\\d+(?:[-+][0-9A-Za-z.-]+)?$" + }, + "date": { + "type": "string", + "format": "date" + }, + "refs": { + "type": "array", + "items": { + "type": "string", + "pattern": "^WI-\\d{4}-\\d{2}-\\d{2}-(?:[a-f0-9]{4}(?:-\\d{3})?|\\d{3})$" + } + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false +} diff --git a/gov/schema/rfc.schema.json b/gov/schema/rfc.schema.json new file mode 100644 index 000000000..741821ea6 --- /dev/null +++ b/gov/schema/rfc.schema.json @@ -0,0 +1,120 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "RFC Specification", + "description": "JSON Schema for RFC TOML artifacts with [govctl] metadata section", + "type": "object", + "required": ["govctl", "sections"], + "properties": { + "govctl": { + "type": "object", + "required": [ + "id", + "title", + "version", + "status", + "phase", + "owners", + "created" + ], + "properties": { + "id": { + "type": "string", + "pattern": "^RFC-\\d{4}$" + }, + "title": { + "type": "string", + "minLength": 1 + }, + "version": { + "type": "string", + "pattern": "^\\d+\\.\\d+\\.\\d+$" + }, + "status": { + "type": "string", + "enum": ["draft", "normative", "deprecated"] + }, + "phase": { + "type": "string", + "enum": ["spec", "impl", "test", "stable"] + }, + "owners": { + "type": "array", + "items": { "type": "string" }, + "minItems": 1 + }, + "created": { + "type": "string", + "format": "date" + }, + "updated": { + "type": "string", + "format": "date" + }, + "supersedes": { + "type": "string", + "pattern": "^RFC-\\d{4}$" + }, + "refs": { + "type": "array", + "items": { + "type": "string", + "pattern": "^(RFC-\\d{4}(?::C-[A-Z][A-Z0-9-]*)?|ADR-\\d{4}|WI-\\d{4}-\\d{2}-\\d{2}-(?:[a-f0-9]{4}(?:-\\d{3})?|\\d{3}))$" + } + }, + "tags": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$" + } + }, + "signature": { + "type": "string", + "pattern": "^[0-9a-f]{64}$" + }, + "schema": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "sections": { + "type": "array", + "items": { + "type": "object", + "required": ["title"], + "properties": { + "title": { "type": "string" }, + "clauses": { + "type": "array", + "items": { "type": "string" } + } + }, + "additionalProperties": false + } + }, + "changelog": { + "type": "array", + "items": { + "type": "object", + "required": ["version", "date"], + "properties": { + "version": { + "type": "string", + "pattern": "^\\d+\\.\\d+\\.\\d+$" + }, + "date": { "type": "string", "format": "date" }, + "notes": { "type": "string" }, + "added": { "type": "array", "items": { "type": "string" } }, + "changed": { "type": "array", "items": { "type": "string" } }, + "deprecated": { "type": "array", "items": { "type": "string" } }, + "removed": { "type": "array", "items": { "type": "string" } }, + "fixed": { "type": "array", "items": { "type": "string" } }, + "security": { "type": "array", "items": { "type": "string" } } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false +} diff --git a/gov/schema/work.schema.json b/gov/schema/work.schema.json new file mode 100644 index 000000000..7f2ffb041 --- /dev/null +++ b/gov/schema/work.schema.json @@ -0,0 +1,157 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "title": "Work Item Specification", + "description": "JSON Schema for Work Item TOML artifacts after parsing and normalization", + "type": "object", + "required": ["govctl", "content"], + "properties": { + "govctl": { + "type": "object", + "required": ["id", "title", "status"], + "properties": { + "id": { + "type": "string", + "pattern": "^WI-\\d{4}-\\d{2}-\\d{2}-(?:[a-f0-9]{4}(?:-\\d{3})?|\\d{3})$" + }, + "title": { + "type": "string", + "minLength": 1 + }, + "status": { + "type": "string", + "enum": ["queue", "active", "done", "cancelled"] + }, + "created": { + "type": "string", + "format": "date" + }, + "started": { + "type": "string", + "format": "date" + }, + "completed": { + "type": "string", + "format": "date" + }, + "refs": { + "type": "array", + "items": { + "type": "string", + "pattern": "^(RFC-\\d{4}(?::C-[A-Z][A-Z0-9-]*)?|ADR-\\d{4}|WI-\\d{4}-\\d{2}-\\d{2}-(?:[a-f0-9]{4}(?:-\\d{3})?|\\d{3}))$" + } + }, + "tags": { + "type": "array", + "items": { + "type": "string", + "pattern": "^[a-z][a-z0-9-]*$" + } + }, + "schema": { + "type": "integer" + } + }, + "additionalProperties": false + }, + "content": { + "type": "object", + "required": ["description"], + "properties": { + "description": { + "type": "string" + }, + "journal": { + "type": "array", + "items": { + "type": "object", + "required": ["date", "content"], + "properties": { + "date": { + "type": "string", + "format": "date" + }, + "scope": { + "type": "string" + }, + "content": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": false + } + }, + "acceptance_criteria": { + "type": "array", + "items": { + "type": "object", + "required": ["text"], + "properties": { + "text": { + "type": "string", + "minLength": 1 + }, + "status": { + "type": "string", + "enum": ["pending", "done", "cancelled"] + }, + "category": { + "type": "string", + "enum": [ + "added", + "changed", + "deprecated", + "removed", + "fixed", + "security", + "chore" + ] + } + }, + "additionalProperties": false + } + }, + "notes": { + "type": "array", + "items": { + "type": "string" + } + } + }, + "additionalProperties": false + }, + "verification": { + "type": "object", + "properties": { + "required_guards": { + "type": "array", + "items": { + "type": "string", + "pattern": "^GUARD-[A-Z][A-Z0-9-]*$" + }, + "uniqueItems": true + }, + "waivers": { + "type": "array", + "items": { + "type": "object", + "required": ["guard", "reason"], + "properties": { + "guard": { + "type": "string", + "pattern": "^GUARD-[A-Z][A-Z0-9-]*$" + }, + "reason": { + "type": "string", + "minLength": 1 + } + }, + "additionalProperties": false + } + } + }, + "additionalProperties": false + } + }, + "additionalProperties": false +} diff --git a/gov/work/2026-06-03-implement-cp-shared-kv-batch-owner-lane-allocation.toml b/gov/work/2026-06-03-implement-cp-shared-kv-batch-owner-lane-allocation.toml new file mode 100644 index 000000000..64d72da5e --- /dev/null +++ b/gov/work/2026-06-03-implement-cp-shared-kv-batch-owner-lane-allocation.toml @@ -0,0 +1,68 @@ +#:schema ../schema/work.schema.json + +[govctl] +id = "WI-2026-06-03-001" +title = "Implement CP shared-KV batch owner-lane allocation" +status = "done" +created = "2026-06-03" +started = "2026-06-03" +completed = "2026-06-03" +refs = ["RFC-0001"] + +[content] +description = "Implement RFC-0001 W2 so CP shared-KV extend allocation supports real batch-size greater than one through owner-lane allocation instead of legacy multi_batch fallback. Scope includes request-local owner planning at allocation time, batch-aware alloc_extend_compute_owner support, owner-lane capacity behavior, and focused unit tests." + +[[content.journal]] +date = "2026-06-03" +scope = "gov" +content = "Created active work item for RFC-0001 W2 implementation and defined acceptance criteria." + +[[content.journal]] +date = "2026-06-03" +scope = "plan" +content = "Wrote implementation plan docs/superpowers/plans/2026-06-03-cp-shared-kv-batch-owner-lane-allocation.md covering RFC-0001 clauses and tests." + +[[content.journal]] +date = "2026-06-03" +scope = "implementation" +content = "Implemented W2 batch owner-lane allocation: added request-local batch owner planner, routed supported bs>1 CP shared-KV extend allocation through alloc_extend_compute_owner, removed allocator bs=1 guard, added bs>1 no-legacy-fallback, owner-lane invariant, capacity wait, unsupported fail-fast, and bs1 compatibility tests. Compliance checker returned COMPLIANT. Local py_compile and govctl render/check passed before remote verification." + +[[content.journal]] +date = "2026-06-03" +scope = "verification" +content = "Remote uv verification passed on ubuntu@10.20.32.33:/mnt/beegfs/qjs-work/sglang after rsync source-only sync excluding binaries and caches: test_nsa_cp_utils.py ran 39 tests OK; test_cp_shared_kv_layout.py ran 34 tests OK; test_alloc_pages_with_owners.py ran 10 tests OK. Non-fatal warnings observed: missing Python.h during CUDA utils probe and Triton unsupported CPU fallback." + +[[content.acceptance_criteria]] +text = "CP shared-KV batch-size greater than one derives request-local page owners and enters owner-lane allocation" +status = "done" +category = "added" + +[[content.acceptance_criteria]] +text = "Batch-aware alloc_extend_compute_owner returns flattened request-token order locations whose new pages match expected owner lanes" +status = "done" +category = "added" + +[[content.acceptance_criteria]] +text = "Supported CP shared-KV batch-size greater than one no longer uses legacy multi_batch fallback" +status = "done" +category = "fixed" + +[[content.acceptance_criteria]] +text = "Owner-lane exhaustion for batch-size greater than one raises KVCapacityWaitError with lane diagnostics instead of falling back" +status = "done" +category = "fixed" + +[[content.acceptance_criteria]] +text = "Batch-size one owner-lane allocation remains compatible with existing behavior" +status = "done" +category = "chore" + +[[content.acceptance_criteria]] +text = "Focused W1/W2 unit tests pass in the remote uv environment" +status = "done" +category = "chore" + +[[content.acceptance_criteria]] +text = "govctl check and govctl render pass" +status = "done" +category = "chore" diff --git a/python/sglang/srt/mem_cache/allocator.py b/python/sglang/srt/mem_cache/allocator.py index 7e8c9eefb..a00ab4ea3 100644 --- a/python/sglang/srt/mem_cache/allocator.py +++ b/python/sglang/srt/mem_cache/allocator.py @@ -996,9 +996,6 @@ class CPSharedPagedTokenToKVPoolAllocator(PagedTokenToKVPoolAllocator): and directly persist that page. """ - if len(prefix_lens_cpu) != 1 or len(seq_lens_cpu) != 1: - raise ValueError("compute-owner allocation supports batch size 1 only") - num_new_pages = get_num_new_pages( seq_lens=seq_lens_cpu, page_size=self.page_size, diff --git a/python/sglang/srt/mem_cache/common.py b/python/sglang/srt/mem_cache/common.py index 57d4a4726..b2c8778b7 100644 --- a/python/sglang/srt/mem_cache/common.py +++ b/python/sglang/srt/mem_cache/common.py @@ -14,6 +14,7 @@ from sglang.srt.mem_cache.base_prefix_cache import ( ) from sglang.srt.mem_cache.allocator import compute_owner_lane_free_room_deficits from sglang.srt.mem_cache.cp_shared_kv_compute_owner import ( + build_batch_in_seq_page_compute_owners, build_in_seq_page_compute_owners, get_in_seq_page_compute_owner_unavailable_reason, ) @@ -66,6 +67,27 @@ def _log_cp_shared_kv_alloc_fallback( ) +def _raise_cp_shared_kv_alloc_fail_fast( + *, + reason: str, + batch_size: int, + extend_num_tokens: int, + page_size: int, +) -> None: + message = ( + "CP shared-KV compute-owner page assignment is unavailable; " + "rejecting extend allocation instead of falling back to legacy page " + f"allocation. batch_size={batch_size} extend_num_tokens={extend_num_tokens} " + f"page_size={page_size} reason={reason}" + ) + logger.error( + "[CP_SHARED_KV_FAIL_FAST][compute_owner_alloc] reason=%s %s", + reason, + message, + ) + raise RuntimeError(message) + + def _compute_owner_lane_stats_for_eviction( *, tree_cache: BasePrefixCache, @@ -453,7 +475,8 @@ def alloc_paged_token_slots_extend( ) page_compute_owners = None compute_owner_unavailable_reason = None - if alloc_extend_compute_owner is not None and len(prefix_lens_cpu) == 1: + compute_owner_planning_required = False + if alloc_extend_compute_owner is not None: try: server_args = get_global_server_args() except ValueError: @@ -464,28 +487,52 @@ def alloc_paged_token_slots_extend( and server_args.enable_nsa_prefill_context_parallel and server_args.nsa_prefill_cp_mode == "in-seq-split" ): - extend_len = int(seq_lens_cpu[0].item() - prefix_lens_cpu[0].item()) - page_compute_owners = build_in_seq_page_compute_owners( - extend_len=extend_len, - extend_prefix_len=int(prefix_lens_cpu[0].item()), - page_size=int(allocator.page_size), - cp_size=int(allocator.cp_size), - ) + compute_owner_planning_required = True + extend_lens = [ + int(seq_lens_cpu[i].item() - prefix_lens_cpu[i].item()) + for i in range(len(prefix_lens_cpu)) + ] + extend_prefix_lens = [ + int(prefix_lens_cpu[i].item()) for i in range(len(prefix_lens_cpu)) + ] + if len(prefix_lens_cpu) == 1: + page_compute_owners = build_in_seq_page_compute_owners( + extend_len=extend_lens[0], + extend_prefix_len=extend_prefix_lens[0], + page_size=int(allocator.page_size), + cp_size=int(allocator.cp_size), + ) + else: + page_compute_owners = build_batch_in_seq_page_compute_owners( + extend_lens=extend_lens, + extend_prefix_lens=extend_prefix_lens, + page_size=int(allocator.page_size), + cp_size=int(allocator.cp_size), + ) if page_compute_owners is None: - compute_owner_unavailable_reason = ( - get_in_seq_page_compute_owner_unavailable_reason( + for extend_len, extend_prefix_len in zip( + extend_lens, extend_prefix_lens + ): + reason = get_in_seq_page_compute_owner_unavailable_reason( extend_len=extend_len, - extend_prefix_len=int(prefix_lens_cpu[0].item()), + extend_prefix_len=extend_prefix_len, page_size=int(allocator.page_size), cp_size=int(allocator.cp_size), ) - or "unknown" - ) + if reason is not None: + compute_owner_unavailable_reason = reason + break + if compute_owner_unavailable_reason is None: + compute_owner_unavailable_reason = "unknown" else: compute_owner_unavailable_reason = "server_args_not_enabled" - elif alloc_extend_compute_owner is not None: - compute_owner_unavailable_reason = ( - "multi_batch" if len(prefix_lens_cpu) != 1 else "unknown" + + if page_compute_owners is None and compute_owner_planning_required: + _raise_cp_shared_kv_alloc_fail_fast( + reason=compute_owner_unavailable_reason or "compute_owner_not_available", + batch_size=len(prefix_lens_cpu), + extend_num_tokens=extend_num_tokens, + page_size=int(allocator.page_size), ) if page_compute_owners is not None: diff --git a/python/sglang/srt/mem_cache/cp_shared_kv_compute_owner.py b/python/sglang/srt/mem_cache/cp_shared_kv_compute_owner.py index dd0930f60..5eea7a77e 100644 --- a/python/sglang/srt/mem_cache/cp_shared_kv_compute_owner.py +++ b/python/sglang/srt/mem_cache/cp_shared_kv_compute_owner.py @@ -75,3 +75,31 @@ def build_in_seq_page_compute_owners( owners.extend([owner] * unit_count) return owners + + +def build_batch_in_seq_page_compute_owners( + *, + extend_lens: list[int], + extend_prefix_lens: list[int], + page_size: int, + cp_size: int, +) -> Optional[List[int]]: + if len(extend_lens) != len(extend_prefix_lens): + raise ValueError( + "extend_lens and extend_prefix_lens must have the same length, " + f"got {len(extend_lens)} and {len(extend_prefix_lens)}" + ) + + batch_owners: List[int] = [] + for extend_len, extend_prefix_len in zip(extend_lens, extend_prefix_lens): + owners = build_in_seq_page_compute_owners( + extend_len=int(extend_len), + extend_prefix_len=int(extend_prefix_len), + page_size=page_size, + cp_size=cp_size, + ) + if owners is None: + return None + batch_owners.extend(owners) + + return batch_owners diff --git a/test/registered/unit/mem_cache/test_cp_shared_kv_layout.py b/test/registered/unit/mem_cache/test_cp_shared_kv_layout.py index 3b3127279..c5acd9c50 100644 --- a/test/registered/unit/mem_cache/test_cp_shared_kv_layout.py +++ b/test/registered/unit/mem_cache/test_cp_shared_kv_layout.py @@ -209,6 +209,57 @@ class TestCPSharedPagedAllocator(CustomTestCase): [0, 0, 1, 1, 2, 2, 3, 3, 3, 3, 2, 2, 1, 1, 0, 0], ) + def test_batch_compute_owner_page_assignment_flattens_request_order(self): + from sglang.srt.mem_cache.cp_shared_kv_compute_owner import ( + build_batch_in_seq_page_compute_owners, + ) + + owners = build_batch_in_seq_page_compute_owners( + extend_lens=[64 * 3, 64 * 5], + extend_prefix_lens=[0, 64 * 2], + page_size=64, + cp_size=4, + ) + + self.assertEqual(owners, [0, 1, 2, 0, 1, 2, 3, 3]) + + def test_batch_compute_owner_page_assignment_matches_single_request(self): + from sglang.srt.mem_cache.cp_shared_kv_compute_owner import ( + build_batch_in_seq_page_compute_owners, + build_in_seq_page_compute_owners, + ) + + batch_owners = build_batch_in_seq_page_compute_owners( + extend_lens=[64 * 7], + extend_prefix_lens=[64 * 2], + page_size=64, + cp_size=4, + ) + single_owners = build_in_seq_page_compute_owners( + extend_len=64 * 7, + extend_prefix_len=64 * 2, + page_size=64, + cp_size=4, + ) + + self.assertEqual(batch_owners, single_owners) + + def test_batch_compute_owner_page_assignment_returns_none_for_misaligned_prefix( + self, + ): + from sglang.srt.mem_cache.cp_shared_kv_compute_owner import ( + build_batch_in_seq_page_compute_owners, + ) + + owners = build_batch_in_seq_page_compute_owners( + extend_lens=[64, 64], + extend_prefix_lens=[0, 1], + page_size=64, + cp_size=4, + ) + + self.assertIsNone(owners) + def test_compute_owner_page_assignment_keeps_page_aligned_short_extend(self): from sglang.srt.mem_cache.cp_shared_kv_compute_owner import ( build_in_seq_page_compute_owners, @@ -355,6 +406,54 @@ class TestCPSharedPagedAllocator(CustomTestCase): ) self.assertEqual(allocator.available_size(), page_size * 16) + def test_shared_allocator_compute_owner_alloc_supports_multi_request_flattened_order( + self, + ): + from sglang.srt.mem_cache.allocator import CPSharedPagedTokenToKVPoolAllocator + from sglang.srt.mem_cache.cp_shared_kv_compute_owner import ( + build_batch_in_seq_page_compute_owners, + ) + + page_size = 64 + cp_size = 4 + owners = build_batch_in_seq_page_compute_owners( + extend_lens=[page_size * 3, page_size * 5], + extend_prefix_lens=[0, page_size * 2], + page_size=page_size, + cp_size=cp_size, + ) + allocator = CPSharedPagedTokenToKVPoolAllocator( + logical_size=page_size * 32, + physical_size=page_size * 8, + page_size=page_size, + dtype=torch.bfloat16, + device="cpu", + kvcache=None, + need_sort=False, + cp_size=cp_size, + cp_rank=0, + ) + + locs = allocator.alloc_extend_compute_owner( + prefix_lens=torch.tensor([0, page_size * 2], dtype=torch.int64), + prefix_lens_cpu=torch.tensor([0, page_size * 2], dtype=torch.int64), + seq_lens=torch.tensor( + [page_size * 3, page_size * 7], dtype=torch.int64 + ), + seq_lens_cpu=torch.tensor( + [page_size * 3, page_size * 7], dtype=torch.int64 + ), + last_loc=torch.tensor([-1, page_size * 2 - 1], dtype=torch.int64), + extend_num_tokens=page_size * 8, + page_compute_owners=owners, + ) + + self.assertIsNotNone(locs) + req0_pages = locs[: page_size * 3].view(-1, page_size)[:, 0] // page_size + req1_pages = locs[page_size * 3 :].view(-1, page_size)[:, 0] // page_size + logical_pages = torch.cat([req0_pages, req1_pages]) + self.assertEqual(((logical_pages - 1) % cp_size).tolist(), owners) + def test_compute_owner_alloc_does_not_use_torch_isin_for_page_removal(self): from sglang.srt.mem_cache.allocator import CPSharedPagedTokenToKVPoolAllocator @@ -555,6 +654,135 @@ class TestCPSharedPagedAllocator(CustomTestCase): self.assertEqual(allocator.calls, 1) self.assertEqual(out.numel(), page_size * 8) + def test_compute_owner_alloc_uses_batch_owner_plan_for_multi_request(self): + from types import SimpleNamespace + + from sglang.srt.mem_cache import common + + page_size = 64 + + class FakeAllocator: + def __init__(self): + self.page_size = page_size + self.cp_size = 4 + self.calls = [] + + def available_size(self): + return page_size * 100 + + def alloc_extend_compute_owner( + self, + _prefix_lens, + _prefix_lens_cpu, + _seq_lens, + _seq_lens_cpu, + _last_loc, + extend_num_tokens, + page_compute_owners, + ): + self.calls.append(list(page_compute_owners)) + return torch.arange(extend_num_tokens, dtype=torch.int64) + + def alloc_extend(self, *_args, **_kwargs): + raise AssertionError("legacy allocation should not be used for bs>1") + + class FakeTreeCache: + def __init__(self, allocator): + self.token_to_kv_pool_allocator = allocator + + def is_chunk_cache(self): + return False + + def evict(self, _params): + raise AssertionError("tree eviction should not be used on first success") + + allocator = FakeAllocator() + server_args = SimpleNamespace( + enable_nsa_prefill_cp_shared_kv=True, + enable_nsa_prefill_context_parallel=True, + nsa_prefill_cp_mode="in-seq-split", + ) + + with patch.object(common, "get_global_server_args", return_value=server_args): + out = common.alloc_paged_token_slots_extend( + tree_cache=FakeTreeCache(allocator), + prefix_lens=torch.tensor([0, page_size * 2], dtype=torch.int64), + prefix_lens_cpu=torch.tensor([0, page_size * 2], dtype=torch.int64), + seq_lens=torch.tensor( + [page_size * 3, page_size * 7], dtype=torch.int64 + ), + seq_lens_cpu=torch.tensor( + [page_size * 3, page_size * 7], dtype=torch.int64 + ), + last_loc=torch.tensor([-1, page_size * 2 - 1], dtype=torch.int64), + extend_num_tokens=page_size * 8, + ) + + self.assertEqual(allocator.calls, [[0, 1, 2, 0, 1, 2, 3, 3]]) + self.assertEqual(out.numel(), page_size * 8) + + def test_compute_owner_alloc_fail_fast_for_multi_request_unsupported_owner_plan( + self, + ): + from types import SimpleNamespace + + from sglang.srt.mem_cache import common + + page_size = 64 + + class FakeAllocator: + def __init__(self): + self.page_size = page_size + self.cp_size = 4 + self.owner_calls = 0 + self.legacy_calls = 0 + + def available_size(self): + return page_size * 100 + + def alloc_extend_compute_owner(self, *_args, **_kwargs): + self.owner_calls += 1 + raise AssertionError("owner allocation should not run without plan") + + def alloc_extend(self, *_args, **_kwargs): + self.legacy_calls += 1 + raise AssertionError("legacy allocation should not be used for bs>1") + + class FakeTreeCache: + def __init__(self, allocator): + self.token_to_kv_pool_allocator = allocator + + def is_chunk_cache(self): + return False + + def evict(self, _params): + raise AssertionError("tree eviction should not be used") + + allocator = FakeAllocator() + server_args = SimpleNamespace( + enable_nsa_prefill_cp_shared_kv=True, + enable_nsa_prefill_context_parallel=True, + nsa_prefill_cp_mode="in-seq-split", + ) + + with patch.object(common, "get_global_server_args", return_value=server_args): + with self.assertRaises(RuntimeError) as cm: + common.alloc_paged_token_slots_extend( + tree_cache=FakeTreeCache(allocator), + prefix_lens=torch.tensor([0, 1], dtype=torch.int64), + prefix_lens_cpu=torch.tensor([0, 1], dtype=torch.int64), + seq_lens=torch.tensor([page_size, page_size + 1], dtype=torch.int64), + seq_lens_cpu=torch.tensor( + [page_size, page_size + 1], dtype=torch.int64 + ), + last_loc=torch.tensor([-1, 0], dtype=torch.int64), + extend_num_tokens=page_size * 2, + ) + + self.assertIn("prefix_not_page_aligned", str(cm.exception)) + self.assertEqual(allocator.owner_calls, 0) + self.assertEqual(allocator.legacy_calls, 0) + def test_compute_owner_alloc_skips_aggregate_evict_before_owner_attempt(self): from types import SimpleNamespace @@ -880,6 +1108,103 @@ class TestCPSharedPagedAllocator(CustomTestCase): [params.owner_lane_deficits for params in tree_cache.evict_params], ) + def test_compute_owner_capacity_wait_reports_owner_lane_deficits_for_multi_request( + self, + ): + from types import SimpleNamespace + + from sglang.srt.mem_cache import common + from sglang.srt.mem_cache.base_prefix_cache import EvictResult + + page_size = 64 + + class FakeAllocator: + def __init__(self): + self.page_size = page_size + self.cp_size = 4 + self.owner_calls = [] + + def available_size(self): + return self.page_size * 4 + + def allocator_state_str(self): + return "allocator_state_for_test" + + def alloc_extend_compute_owner( + self, + _prefix_lens, + _prefix_lens_cpu, + _seq_lens, + _seq_lens_cpu, + _last_loc, + _extend_num_tokens, + page_compute_owners, + ): + self.owner_calls.append(list(page_compute_owners)) + return None + + def compute_owner_lane_stats(self, _page_compute_owners): + return [2, 2, 2, 2], [2, 2, 2, 0], [0, 0, 0, 2] + + def alloc_extend(self, *_args, **_kwargs): + raise AssertionError("legacy allocation should not be used for bs>1") + + class FakeTreeCache: + def __init__(self): + self.token_to_kv_pool_allocator = FakeAllocator() + self.evict_params = [] + + def is_chunk_cache(self): + return False + + def evictable_size(self): + return page_size * 8 + + def evict(self, params): + self.evict_params.append(params) + return EvictResult(num_tokens_evicted=0) + + def pretty_print(self): + raise AssertionError("recoverable capacity wait should not dump tree") + + server_args = SimpleNamespace( + enable_nsa_prefill_cp_shared_kv=True, + enable_nsa_prefill_context_parallel=True, + nsa_prefill_cp_mode="in-seq-split", + ) + tree_cache = FakeTreeCache() + + with patch.object(common, "get_global_server_args", return_value=server_args): + with self.assertRaises(common.KVCapacityWaitError) as cm: + common.alloc_paged_token_slots_extend( + tree_cache=tree_cache, + prefix_lens=torch.tensor([0, page_size * 2], dtype=torch.int64), + prefix_lens_cpu=torch.tensor( + [0, page_size * 2], dtype=torch.int64 + ), + seq_lens=torch.tensor( + [page_size * 3, page_size * 7], dtype=torch.int64 + ), + seq_lens_cpu=torch.tensor( + [page_size * 3, page_size * 7], dtype=torch.int64 + ), + last_loc=torch.tensor([-1, page_size * 2 - 1], dtype=torch.int64), + extend_num_tokens=page_size * 8, + ) + + err = cm.exception + self.assertEqual( + tree_cache.token_to_kv_pool_allocator.owner_calls, + [[0, 1, 2, 0, 1, 2, 3, 3], [0, 1, 2, 0, 1, 2, 3, 3]], + ) + self.assertEqual(err.required_by_owner, [2, 2, 2, 2]) + self.assertEqual(err.available_by_owner, [2, 2, 2, 0]) + self.assertEqual(err.deficit_by_owner, [0, 0, 0, 2]) + self.assertIn( + [0, 0, 0, 2], + [params.owner_lane_deficits for params in tree_cache.evict_params], + ) + def test_alloc_for_extend_releases_req_slots_on_recoverable_capacity_wait(self): from types import SimpleNamespace