Closes out greenfield Step 6 (Decompose) for all 14 components (C1-C13 + cross-cutting helpers/replay). Covers tasks AZ-266..AZ-446 plus the _dependencies_table.md and component contract documents. State file updated to greenfield Step 7 (Implement), not_started. Co-authored-by: Cursor <cursoragent@cursor.com>
6.6 KiB
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
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:
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_batchfrom multiple threads are FORBIDDEN. The composition root binds the runtime to the single F3 hot-path thread (per_docs/02_document/epics.mdR14 entry). The helper's contract test asserts a guard exists that rejects concurrent entry withLightGlueConcurrentAccessError. - 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_dimmismatch raisesLightGlueRuntimeError). The helper does NOT silently coerce dimensions. - No shared mutable state: the runtime exposes no
set_*/update_*methods. Once constructed with anengine_handle, its behaviour is fixed for its lifetime. - No upward imports (Layer 1): the module imports ONLY from
_types, numpy, and stdlib. NOgps_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,
EngineHandleProtocol 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 KeypointSets 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 KeypointSets |
three CorrespondenceSets 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 |