# Contract: lightglue_runtime **Component**: shared_helpers / `helpers.lightglue_runtime` (cross-cutting concern owned by E-CC-HELPERS / AZ-264) **Producer task**: AZ-278 — `_docs/02_tasks/todo/AZ-278_lightglue_runtime.md` **Consumer tasks**: C2.5 InlierBasedReranker (single-pair LightGlue inlier counter); C3 CrossDomainMatcher (heavier matching pass) **Version**: 1.0.0 **Status**: draft **Last Updated**: 2026-05-10 ## Purpose Single owner of the LightGlue inference engine. C2.5 does single-pair LightGlue matching for inlier counting on K=10 candidates per frame; C3 does the heavier matching pass on the surviving N=3 candidates. Both consume the SAME LightGlue engine — sharing avoids paying the engine-build / GPU-memory cost twice and structurally prevents the C2.5 ↔ C3 import cycle (R14 fix in `_docs/02_document/epics.md`). Per `_docs/02_document/common-helpers/03_helper_lightglue_runtime.md`. ## Shape ### For function / method APIs ```python class LightGlueRuntime: def __init__(self, engine_handle: EngineHandle) -> None: ... def descriptor_dim(self) -> int: ... def match( self, features_a: KeypointSet, features_b: KeypointSet, ) -> CorrespondenceSet: ... def match_batch( self, features_a_list: list[KeypointSet], features_b_list: list[KeypointSet], ) -> list[CorrespondenceSet]: ... ``` | Name | Signature | Throws / Errors | Blocking? | |------|-----------|-----------------|-----------| | `__init__` | `(engine_handle: EngineHandle) -> None` | `LightGlueRuntimeError` if `engine_handle` is None or descriptor_dim < 1 | sync, one-time | | `descriptor_dim` | `() -> int` | none | sync, in-memory | | `match` | `(KeypointSet, KeypointSet) -> CorrespondenceSet` | `LightGlueRuntimeError` if descriptor dims mismatch the engine's expected dim, or if a concurrent caller tries to enter | sync, GPU-bound | | `match_batch` | `(list[KeypointSet], list[KeypointSet]) -> list[CorrespondenceSet]` | same as `match` | sync, GPU-bound | `EngineHandle`, `KeypointSet`, and `CorrespondenceSet` are imported from `gps_denied_onboard._types`. `EngineHandle` is a Protocol (NOT a concrete class) so this helper does not import any Layer 2+ component; the production handle is created by C7's `InferenceRuntime.deserialize_engine` and injected by the composition root. ### Construction The composition root constructs the runtime once at takeoff: ```python engine_handle = inference_runtime.deserialize_engine(LIGHTGLUE_ENGINE_CACHE_ENTRY) runtime = LightGlueRuntime(engine_handle) # inject the SAME instance into both consumers c2_5_reranker = InlierBasedReranker(..., lightglue_runtime=runtime, ...) c3_matcher = CrossDomainMatcher(..., lightglue_runtime=runtime, ...) ``` ## Invariants - **Serial-access invariant** (R14 cross-component): the runtime owns ONE CUDA stream. Concurrent calls to `match` / `match_batch` from multiple threads are FORBIDDEN. The composition root binds the runtime to the single F3 hot-path thread (per `_docs/02_document/epics.md` R14 entry). The helper's contract test asserts a guard exists that rejects concurrent entry with `LightGlueConcurrentAccessError`. - **Backbone consistency**: features fed in MUST come from the same backbone as the LightGlue engine was trained for (DISK in production-default; ALIKED / XFeat alternates). Mixing backbones is a runtime error caught by the input shape check (`descriptor_dim` mismatch raises `LightGlueRuntimeError`). The helper does NOT silently coerce dimensions. - **No shared mutable state**: the runtime exposes no `set_*` / `update_*` methods. Once constructed with an `engine_handle`, its behaviour is fixed for its lifetime. - **No upward imports** (Layer 1): the module imports ONLY from `_types`, numpy, and stdlib. NO `gps_denied_onboard.components.*` imports — neither C2.5 nor C3 nor C7 — under any circumstance. This is the structural fix for R14: the helper sits below the components in the layering, so the C2.5 ↔ C3 cycle becomes impossible to express. - **Engine handle is opaque**: the helper does not know whether the handle wraps a TensorRT engine, an ONNX session, or a PyTorch model. It calls a fixed Protocol surface (`forward(...)`, `descriptor_dim`); the implementation owner is C7. ## Non-Goals - Engine compilation / serialisation — owned by C7 (via `EngineFilenameSchema` + the inference runtime). - Engine cache management / takeoff load — owned by C10 (`CacheProvisioner`). - Backbone-specific feature extraction (DISK, ALIKED, XFeat) — owned by C3 / C7. - Multi-GPU sharding — out of scope; production target is single-GPU Tier-2. - Mixed-backbone matching (cross-DISK-ALIKED) — out of scope; consumers ensure backbone consistency before calling. ## Versioning Rules - **Breaking changes** (method renamed/removed, signature changed, `EngineHandle` Protocol changed, serial-access invariant relaxed) require a new major version + a deprecation pass through C2.5 and C3. - **Non-breaking additions** (new optional kwarg with safe default, new diagnostic accessor) require a minor version bump. - Changing the underlying engine format (TensorRT → ONNX) is NOT a contract change because the helper's surface treats the handle as opaque — but it IS a C7 contract change and must follow C7's versioning rules. ## Test Cases | Case | Input | Expected | Notes | |------|-------|----------|-------| | valid-single-pair | two `KeypointSet`s of matching descriptor dim | `CorrespondenceSet` returned with `len > 0` for a synthetic-overlap pair | Round-trip happy path (C2.5 use) | | valid-batch-3 | three pairs of `KeypointSet`s | three `CorrespondenceSet`s returned in order | Batch path (C3 use) | | invalid-dim-mismatch | features with `descriptor_dim` not matching the engine | `LightGlueRuntimeError` mentions the expected vs actual dim | Backbone-consistency invariant | | invalid-concurrent-access | two threads call `match` simultaneously | `LightGlueConcurrentAccessError` raised in the second-entering thread | R14 serial-access invariant | | invalid-empty-handle | `LightGlueRuntime(engine_handle=None)` | `LightGlueRuntimeError` raised at construction | Construction guard | | no-upward-imports | static import scan | only `_types`, numpy, stdlib — no `components.*` | R14 structural fix | | determinism-given-engine | same `(features_a, features_b)` matched twice with the same engine handle | byte-equal `CorrespondenceSet` outputs | Pure-function determinism downstream of the engine | ## Change Log | Version | Date | Change | Author | |---------|------|--------|--------| | 1.0.0 | 2026-05-10 | Initial contract derived from `_docs/02_document/common-helpers/03_helper_lightglue_runtime.md` (R14 fix) | autodev decompose Step 2 |