[AZ-622] Phase D: build_pre_constructed seeds c3 GPU runtimes

build_pre_constructed now populates c3_lightglue_runtime
(LightGlueRuntime) + c3_feature_extractor (FeatureExtractor) on top
of AZ-619/620/621. Strategy-specific BUILD_MATCHER_* flag mismatch
raises AirborneBootstrapError naming the missing flag and the c3_matcher
consumer; the c7 InferenceRuntime built earlier in the bootstrap is
reused as the engine source so no double-build at this layer.

C3MatcherConfig gains optional lightglue_weights_path: Path | None
for the operator's deployment config; production main() (AZ-624)
populates it. Real LightGlue inference correctness is verified by
AZ-624's Jetson AC-5 run per the AZ-622 Tier-2 Note.

Phase tests for AZ-619/620/621 gain an autouse _stub_c3_matcher_builders
fixture so additivity assertions remain valid as the bootstrap grows.

Code review: PASS_WITH_WARNINGS (3 Low: signature drift from spec,
_is_build_flag_on duplication across 3 runtime_root modules, and
BuildConfig literal mirrored with per-strategy build configs). All
deferred to future hygiene PBIs.

Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Oleksandr Bezdieniezhnykh
2026-05-19 08:56:04 +03:00
parent eaf2f47f69
commit 5c4d129f80
10 changed files with 856 additions and 19 deletions
@@ -37,6 +37,16 @@ by C3.5; it lives in :class:`C3_5RefinerConfig` (sibling block) —
not duplicated here. ``None`` is allowed as a placeholder for
``BUILD_MATCHER_<variant>=OFF`` binaries; the factory rejects
``None`` only for the SELECTED strategy at composition time.
``lightglue_weights_path`` (AZ-622 / Phase D) points at the LightGlue
*matcher* engine — distinct from the per-strategy DISK / ALIKED
*feature-extractor* engine. The airborne bootstrap
(``runtime_root.airborne_bootstrap.build_pre_constructed``) reads
this field to construct the single shared
:class:`gps_denied_onboard.helpers.lightglue_runtime.LightGlueRuntime`
that C3 + C2.5 both consume (R14 fix; one instance avoids double GPU
memory). ``None`` is allowed; production main() (AZ-624) populates
the path before calling ``build_pre_constructed``.
"""
from __future__ import annotations
@@ -69,6 +79,7 @@ class C3MatcherConfig:
disk_weights_path: Path | None = None
aliked_weights_path: Path | None = None
xfeat_weights_path: Path | None = None
lightglue_weights_path: Path | None = None
def __post_init__(self) -> None:
if self.strategy not in KNOWN_STRATEGIES:
@@ -91,7 +102,12 @@ class C3MatcherConfig:
"C3MatcherConfig.ransac_threshold_px must be > 0; "
f"got {self.ransac_threshold_px}"
)
for name in ("disk_weights_path", "aliked_weights_path", "xfeat_weights_path"):
for name in (
"disk_weights_path",
"aliked_weights_path",
"xfeat_weights_path",
"lightglue_weights_path",
):
value = getattr(self, name)
if value is not None and not isinstance(value, Path):
raise ConfigError(
@@ -55,6 +55,8 @@ from typing import TYPE_CHECKING, Any, Final
from gps_denied_onboard.clock.wall_clock import WallClock
from gps_denied_onboard.fdr_client.client import make_fdr_client
from gps_denied_onboard.helpers.feature_extractor import OpenCvOrbExtractor
from gps_denied_onboard.helpers.lightglue_runtime import LightGlueRuntime
from gps_denied_onboard.runtime_root import register_strategy
from gps_denied_onboard.runtime_root.errors import RuntimeNotAvailableError
from gps_denied_onboard.runtime_root.inference_factory import build_inference_runtime
@@ -71,11 +73,15 @@ from gps_denied_onboard.runtime_root.vio_factory import build_vio_strategy
from gps_denied_onboard.runtime_root.vpr_factory import build_vpr_strategy
if TYPE_CHECKING:
from gps_denied_onboard._types.manifests import EngineHandle
from gps_denied_onboard.components.c7_inference import InferenceRuntime
from gps_denied_onboard.config import Config
from gps_denied_onboard.helpers.feature_extractor import FeatureExtractor
__all__ = [
"AIRBORNE_MAIN_PRODUCER_ID",
"AIRBORNE_REQUIRED_PRE_CONSTRUCTED_KEYS",
"C3_MATCHER_BUILD_FLAGS",
"C7_AIRBORNE_BUILD_FLAGS",
"FAISS_BUILD_FLAG",
"AirborneBootstrapError",
@@ -114,6 +120,33 @@ flag would unblock the bootstrap with a different runtime selection.
"""
C3_MATCHER_BUILD_FLAGS: Final[Mapping[str, str]] = {
"disk_lightglue": "BUILD_MATCHER_DISK_LIGHTGLUE",
"aliked_lightglue": "BUILD_MATCHER_ALIKED_LIGHTGLUE",
"xfeat": "BUILD_MATCHER_XFEAT",
}
"""Per-strategy ``BUILD_MATCHER_*`` flag matrix consumed by the airborne
LightGlue-runtime builder (AZ-622 / Phase D).
Mirrors :data:`gps_denied_onboard.runtime_root.matcher_factory.\
_STRATEGY_TO_BUILD_FLAG` verbatim — both this constant and the matcher
factory's table read the same compile-time flags. ANY mutation of this
matrix MUST be mirrored in ``matcher_factory._STRATEGY_TO_BUILD_FLAG``
(and vice versa).
Surfaced here so :func:`_build_c3_lightglue_runtime` can name the
gating flag in an :class:`AirborneBootstrapError` (AC-622.2) when the
configured C3 matcher strategy's flag is OFF in this binary.
Note on flag naming: AZ-622's task spec uses the name
``BUILD_C3_MATCHER_DISK_LIGHTGLUE`` informally, but the actual
production flag matrix is the older ``BUILD_MATCHER_*`` family
(``matcher_factory._STRATEGY_TO_BUILD_FLAG``). Reusing those flags is
the spirit of the AZ-622 ``MUST reuse the existing per-strategy
BUILD_C3_MATCHER_* matrix`` constraint.
"""
AIRBORNE_MAIN_PRODUCER_ID: Final[str] = "airborne_main"
"""Producer ID for the per-binary shared FdrClient placed under
``pre_constructed['c13_fdr']``.
@@ -542,25 +575,208 @@ def _build_c7_inference(config: Config) -> Any:
) from exc
def _resolve_c3_matcher_strategy(config: Config) -> str:
"""Return the configured C3 matcher strategy, defaulting to disk_lightglue.
Reuses :class:`gps_denied_onboard.components.c3_matcher.C3MatcherConfig`'s
own default ``"disk_lightglue"`` when ``config.components`` carries no
``c3_matcher`` block (early-bootstrap tests with bare ``Config()``).
"""
block = config.components.get("c3_matcher")
if block is None:
return "disk_lightglue"
return getattr(block, "strategy", "disk_lightglue")
def _is_build_flag_on(flag_name: str) -> bool:
"""Read a compile-time ``BUILD_*`` flag from the environment.
Mirrors the same predicate used by
:func:`gps_denied_onboard.runtime_root.matcher_factory.\
_is_build_flag_on` — ``ON`` / ``1`` / ``true`` / ``yes`` (case-insensitive)
is ON; everything else (including unset) is OFF. Defined locally so the
bootstrap does not depend on the matcher-factory's private helper.
"""
raw = os.environ.get(flag_name, "")
return raw.strip().lower() in {"on", "1", "true", "yes"}
def _load_lightglue_engine_handle(
config: Config, inference_runtime: InferenceRuntime
) -> EngineHandle:
"""Production loader for the shared LightGlue matcher engine.
Reads ``config.components['c3_matcher'].lightglue_weights_path``,
compiles the engine via the C7
:class:`InferenceRuntime.compile_engine` (TensorRT or PyTorch-FP16
per AZ-621), then deserialises it into an opaque
:class:`EngineHandle`. The handle's lifecycle is owned by the
:class:`LightGlueRuntime` instance returned by
:func:`_build_c3_lightglue_runtime`.
AZ-622 unit tests monkey-patch this function with a sentinel
:class:`EngineHandle`-shaped mock so they can exercise the
LightGlueRuntime wiring without standing up a real GPU + TensorRT
toolchain (per AZ-622 ``Tier-2 Note``: real LightGlue inference
correctness is verified by AZ-624's Jetson AC-5 run).
Raises:
AirborneBootstrapError: if ``c3_matcher.lightglue_weights_path``
is ``None`` (the operator-actionable message points at the
production main() wiring task — AZ-624).
RuntimeNotAvailableError: if the underlying
:func:`InferenceRuntime.compile_engine` /
:func:`deserialize_engine` paths fail (caller wraps this
into an :class:`AirborneBootstrapError`).
"""
block = config.components.get("c3_matcher")
weights_path = (
getattr(block, "lightglue_weights_path", None) if block is not None else None
)
if weights_path is None:
raise AirborneBootstrapError(
"airborne_bootstrap: cannot construct "
"pre_constructed['c3_lightglue_runtime'] because "
"config.components['c3_matcher'].lightglue_weights_path "
"is None. Production main() (AZ-624) must populate the "
"path to the compiled LightGlue engine before calling "
"build_pre_constructed; tests stub _load_lightglue_engine_handle "
"via monkeypatch."
)
from gps_denied_onboard._types.inference import BuildConfig, PrecisionMode
build_config: BuildConfig = BuildConfig(
precision=PrecisionMode.FP16,
workspace_mb=512,
calibration_dataset=None,
optimization_profiles=(),
)
cache_entry = inference_runtime.compile_engine(weights_path, build_config)
return inference_runtime.deserialize_engine(cache_entry)
def _build_c3_lightglue_runtime(
config: Config, *, inference_runtime: InferenceRuntime
) -> LightGlueRuntime:
"""Build ``pre_constructed['c3_lightglue_runtime']`` for the airborne binary.
1. Resolve the configured C3 matcher strategy (default
``disk_lightglue``).
2. Look up the gating flag in :data:`C3_MATCHER_BUILD_FLAGS`.
An unknown strategy or an OFF flag is an
:class:`AirborneBootstrapError` naming the missing flag and
the consuming component slug ``c3_matcher`` (AC-622.2).
3. Load the LightGlue engine handle via
:func:`_load_lightglue_engine_handle` (the heavy seam unit
tests monkeypatch — see AZ-622 ``Tier-2 Note``).
4. Wrap the handle in :class:`LightGlueRuntime` and return.
The returned runtime is the SAME instance that the wrappers for
``c3_matcher`` and ``c2_5_rerank`` extract from
``pre_constructed['c3_lightglue_runtime']`` — identity-share is
the structural R14 fix (avoids double GPU memory; AZ-344 AC-10).
The cross-component identity-share assertion is verified at
AZ-624's integration AC, not here.
Raises:
AirborneBootstrapError: when the configured strategy's
``BUILD_MATCHER_*`` flag is OFF, when the strategy is
unknown to :data:`C3_MATCHER_BUILD_FLAGS`, or when the
heavy seam fails (the upstream
:class:`RuntimeNotAvailableError` is preserved as
``__cause__``).
"""
strategy = _resolve_c3_matcher_strategy(config)
flag = C3_MATCHER_BUILD_FLAGS.get(strategy)
if flag is None:
raise AirborneBootstrapError(
f"airborne_bootstrap: cannot construct "
f"pre_constructed['c3_lightglue_runtime'] because "
f"config.components['c3_matcher'].strategy={strategy!r} is "
f"not in the airborne BUILD-flag matrix "
f"{sorted(C3_MATCHER_BUILD_FLAGS.keys())!r}. Consuming "
f"component: c3_matcher. Reconfigure the C3 matcher to "
f"select one of the supported strategies."
)
if not _is_build_flag_on(flag):
raise AirborneBootstrapError(
f"airborne_bootstrap: cannot construct "
f"pre_constructed['c3_lightglue_runtime'] because the "
f"gating flag {flag}=ON is required for the configured "
f"strategy={strategy!r}, but {flag} is OFF in this binary. "
f"Consuming component: c3_matcher. Set {flag}=ON, or "
f"reconfigure config.components['c3_matcher'].strategy to a "
f"strategy whose BUILD_MATCHER_* flag is ON."
)
try:
engine_handle = _load_lightglue_engine_handle(config, inference_runtime)
except RuntimeNotAvailableError as exc:
raise AirborneBootstrapError(
f"airborne_bootstrap: cannot construct "
f"pre_constructed['c3_lightglue_runtime'] because the "
f"LightGlue engine load failed for strategy={strategy!r} "
f"(gating flag {flag} is ON). Consuming component: "
f"c3_matcher. Upstream error: {exc}"
) from exc
return LightGlueRuntime(engine_handle)
def _build_c3_feature_extractor(config: Config) -> FeatureExtractor:
"""Build ``pre_constructed['c3_feature_extractor']`` for the airborne binary.
Returns the shared :class:`FeatureExtractor` that C2.5's
:class:`InlierCountReRanker` consumes for both per-frame nav-camera
images and per-candidate tile pixels (per
:class:`AIRBORNE_REQUIRED_PRE_CONSTRUCTED_KEYS`'s ``c2_5_rerank``
row). The L1 helper module
:mod:`gps_denied_onboard.helpers.feature_extractor` documents
:class:`OpenCvOrbExtractor` as the production-ready airborne
placeholder until the C7 ``InferenceRuntime``-backed DISK / ALIKED
feature extractor lands; AZ-622 wires that placeholder in.
No ``BUILD_*`` flag check applies here: the C3 matcher's per-strategy
flag matrix gates the *matcher* engine (handled by
:func:`_build_c3_lightglue_runtime`); the *feature extractor* is
consumed by C2.5 (not C3) and is a pure-CPU OpenCV path that has no
compile-time gate of its own.
The ``config`` argument is accepted for symmetry with the other
bootstrap builders and to keep the door open for a future
config-driven feature-extractor selection (DISK / ALIKED swap-in)
without changing the call site in :func:`build_pre_constructed`.
"""
del config # currently no config knobs; placeholder for future selection
return OpenCvOrbExtractor()
def build_pre_constructed(config: Config) -> dict[str, Any]:
"""Build the airborne ``pre_constructed`` dict for :func:`compose_root`.
AZ-619 (Phase A) seeded ``c13_fdr`` and ``clock``. AZ-620 (Phase B)
added the two C6 storage entries (``c6_descriptor_index`` +
``c6_tile_store``). AZ-621 (Phase C) adds ``c7_inference``
``c6_tile_store``). AZ-621 (Phase C) added ``c7_inference``
(PyTorch FP16 vs. TensorRT, gated by
:data:`C7_AIRBORNE_BUILD_FLAGS`). Phases D..F (AZ-622..AZ-624) will
extend this function to populate the remaining keys in
:data:`C7_AIRBORNE_BUILD_FLAGS`). AZ-622 (Phase D) adds
``c3_lightglue_runtime`` (single shared
:class:`gps_denied_onboard.helpers.lightglue_runtime.LightGlueRuntime`
instance, gated by :data:`C3_MATCHER_BUILD_FLAGS` per the
configured strategy) + ``c3_feature_extractor`` (the shared
:class:`gps_denied_onboard.helpers.feature_extractor.FeatureExtractor`
used by C2.5). Phases E..F (AZ-623..AZ-624) will extend this
function to populate the remaining keys in
:data:`AIRBORNE_REQUIRED_PRE_CONSTRUCTED_KEYS`.
Returns a fresh dict on each call. The ``c13_fdr`` instance is cached
inside :func:`make_fdr_client` (per-producer cache) so two calls within
the same process return dicts where ``pre_constructed['c13_fdr']`` is
the SAME object — AC-619.2. ``clock`` is a fresh :class:`WallClock`
each call (stateless; the cache would be a no-op). The C6 + C7
entries are constructed via the existing :mod:`storage_factory` and
:mod:`inference_factory` builders without additional caching at this
layer.
each call (stateless; the cache would be a no-op). The C6, C7, and C3
entries are constructed via the existing :mod:`storage_factory`,
:mod:`inference_factory`, and helper modules without additional
caching at this layer; the C7 :class:`InferenceRuntime` built for the
``c7_inference`` slot is reused as the engine source for the LightGlue
matcher load (AZ-622) so the bootstrap does not double-build the
inference runtime.
Replay-mode override: :func:`compose_root` merges ``replay_components``
over ``pre_constructed`` so the :class:`WallClock` here is replaced by
@@ -575,16 +791,23 @@ def build_pre_constructed(config: Config) -> dict[str, Any]:
runtime is buildable (both ``BUILD_TENSORRT_RUNTIME`` and
``BUILD_PYTORCH_FP16_RUNTIME`` OFF, or the configured
runtime's matching flag is OFF) and any configured consumer
requires ``c7_inference``. The message names the consuming
component slug(s) and the relevant gating flag(s).
requires ``c7_inference``; OR if the configured C3 matcher
strategy's :data:`C3_MATCHER_BUILD_FLAGS` flag is OFF (or
the strategy is unknown), or if the LightGlue engine load
fails. The message names the consuming component slug(s)
and the relevant gating flag(s).
"""
return {
"c13_fdr": make_fdr_client(AIRBORNE_MAIN_PRODUCER_ID, config),
"clock": WallClock(),
"c6_descriptor_index": _build_c6_descriptor_index(config),
"c6_tile_store": _build_c6_tile_store(config),
"c7_inference": _build_c7_inference(config),
}
constructed: dict[str, Any] = {}
constructed["c13_fdr"] = make_fdr_client(AIRBORNE_MAIN_PRODUCER_ID, config)
constructed["clock"] = WallClock()
constructed["c6_descriptor_index"] = _build_c6_descriptor_index(config)
constructed["c6_tile_store"] = _build_c6_tile_store(config)
constructed["c7_inference"] = _build_c7_inference(config)
constructed["c3_lightglue_runtime"] = _build_c3_lightglue_runtime(
config, inference_runtime=constructed["c7_inference"]
)
constructed["c3_feature_extractor"] = _build_c3_feature_extractor(config)
return constructed
def register_airborne_strategies() -> None: