mirror of
https://github.com/azaion/gps-denied-onboard.git
synced 2026-06-22 11:01:13 +00:00
[AZ-338] [AZ-283] C2 NetVLAD mandatory simple-baseline VprStrategy
NetVLAD is the C2 comparative baseline per the engine rule (every production-default backbone ships with a simple-baseline alongside). Runs on the C7 PyTorch FP16 runtime (NOT TRT) so a TRT engine compile bug cannot simultaneously break NetVLAD AND UltraVPR. Production changes: - c2_vpr/net_vlad.py — NetVladStrategy + module-level create() factory. Constructor wires InferenceRuntimeCut + DescriptorIndexCut + NetVladBackbonePreprocessor + DescriptorNormaliser + FaissBridge. embed_query pipeline: preprocess -> runtime.infer -> dual-stage normalisation (intra-cluster THEN global L2) -> VprQuery. retrieve_topk delegates one-line to FaissBridge. - c2_vpr/_net_vlad_architecture.py — Arandjelovic et al. 2016 NetVLAD layer over torchvision VGG16 features + optional Linear PCA projection to descriptor_dim (default 4096; published Pittsburgh reference uses K*D=64*512=32768 raw + Linear(32768, 4096) PCA). - c2_vpr/_preprocessor_net_vlad.py — OpenCV-based image preprocessor: decode -> centre-crop square -> resize (480, 480) -> ImageNet normalisation -> FP16 NCHW. Calibration is not consumed (NetVLAD is calibration-agnostic per published preprocessing chain). - c2_vpr/inference_runtime_cut.py — NEW AZ-507 consumer-side cut mirroring C7 InferenceRuntime; lets c2_vpr stay AZ-507-clean. - c2_vpr/config.py — added netvlad_descriptor_dim: int = 4096 knob. - helpers/descriptor_normaliser.py — added intra_cluster_normalise (DescriptorNormaliser v1.0.0 -> v1.1.0; backward-compatible add). - runtime_root/vpr_factory.py — added _register_strategy_architecture helper that binds (MODEL_NAME, architecture_factory(descriptor_dim)) to C7's architecture registry before delegating to the strategy's create() factory. Keeps the c7 import at L4, preserves AZ-507. - fdr_client/records.py — registered vpr.embed_query, vpr.backbone_error, vpr.preprocess_error record kinds. Tests: - tests/unit/c2_vpr/test_net_vlad.py — 31 tests covering all 11 ACs + preprocessor contract + architecture factory + constructor validation + FDR record emission. - tests/unit/test_az283_descriptor_normaliser.py — +8 tests for the new intra_cluster_normalise. - tests/unit/test_az272_fdr_record_schema.py — +3 fixture payloads. Full unit suite: 1608 passed / 80 env-skipped (+43 new tests). Per-batch code review (batch_46_review.md): PASS_WITH_WARNINGS (4 Low-severity hygiene findings; no Critical/High/Medium). Architectural notes: - The spec implied c2_vpr.net_vlad.create() registers the architecture with C7. That violates AZ-507 (no cross-component imports). Resolved by exposing MODEL_NAME + architecture_factory(descriptor_dim) on the strategy module and having the composition root perform the C7 bind. - C7 PyTorch runtime API names in the spec (forward, load_engine) were outdated; aligned implementation with the live v1.0.0 Protocol (infer, compile_engine + deserialize_engine). Spec hygiene flagged in review F2. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -103,7 +103,7 @@ def _is_build_flag_on(flag_name: str) -> bool:
|
||||
return raw.strip().lower() in {"on", "1", "true", "yes"}
|
||||
|
||||
|
||||
def _c2_config(config: "Config") -> "C2VprConfig":
|
||||
def _c2_config(config: Config) -> C2VprConfig:
|
||||
"""Pull the registered C2 config block.
|
||||
|
||||
``c2_vpr.__init__`` registers it on import; a missing
|
||||
@@ -113,34 +113,72 @@ def _c2_config(config: "Config") -> "C2VprConfig":
|
||||
return config.components["c2_vpr"]
|
||||
|
||||
|
||||
def _register_strategy_architecture(
|
||||
strategy: str, config: Config
|
||||
) -> None:
|
||||
"""Register the strategy's C7 architecture factory (AZ-338 + future).
|
||||
|
||||
Each concrete strategy module owns its NN architecture but cannot
|
||||
import C7 directly (AZ-507). The strategy module exposes
|
||||
``MODEL_NAME`` + ``architecture_factory(descriptor_dim)`` and the
|
||||
composition root performs the registration with c7. Strategies that
|
||||
do not need a registered architecture (e.g. TRT-engine strategies
|
||||
that bring their own ``.engine``) MAY omit these attributes; the
|
||||
helper no-ops in that case.
|
||||
"""
|
||||
module_info = _STRATEGY_TO_MODULE.get(strategy)
|
||||
if module_info is None:
|
||||
return
|
||||
module_name, _ = module_info
|
||||
try:
|
||||
module = __import__(module_name, fromlist=["architecture_factory"])
|
||||
except ModuleNotFoundError:
|
||||
return
|
||||
factory_fn = getattr(module, "architecture_factory", None)
|
||||
model_name = getattr(module, "MODEL_NAME", None)
|
||||
if factory_fn is None or model_name is None:
|
||||
return
|
||||
if strategy == "net_vlad":
|
||||
descriptor_dim = config.components["c2_vpr"].netvlad_descriptor_dim
|
||||
else:
|
||||
# Future strategies: each may have its own descriptor_dim source;
|
||||
# extend as they land.
|
||||
return
|
||||
from gps_denied_onboard.components.c7_inference import register_architecture
|
||||
|
||||
register_architecture(model_name, factory_fn(descriptor_dim))
|
||||
|
||||
|
||||
def build_vpr_strategy(
|
||||
config: "Config",
|
||||
config: Config,
|
||||
*,
|
||||
descriptor_index: "DescriptorIndex",
|
||||
inference_runtime: "InferenceRuntime",
|
||||
) -> "VprStrategy":
|
||||
descriptor_index: DescriptorIndex,
|
||||
inference_runtime: InferenceRuntime,
|
||||
) -> VprStrategy:
|
||||
"""Construct the :class:`VprStrategy` impl selected by config.
|
||||
|
||||
1. Reads ``config.components['c2_vpr'].strategy``.
|
||||
2. Checks the matching ``BUILD_VPR_<variant>`` flag — if OFF,
|
||||
raises :class:`StrategyNotAvailableError` BEFORE any import.
|
||||
3. Lazily imports the concrete strategy module.
|
||||
4. Constructs the strategy via its module-level
|
||||
4. Registers the strategy's NN architecture with C7 (when the
|
||||
strategy module exposes ``MODEL_NAME`` + ``architecture_factory``).
|
||||
5. Constructs the strategy via its module-level
|
||||
``create(config, descriptor_index, inference_runtime)``
|
||||
factory function (each concrete strategy module exports
|
||||
``create`` as its public entry-point; concrete constructors
|
||||
stay private).
|
||||
5. Pre-flight ``descriptor_dim`` match: ``strategy.descriptor_dim()``
|
||||
6. Pre-flight ``descriptor_dim`` match: ``strategy.descriptor_dim()``
|
||||
vs ``descriptor_index.descriptor_dim()``. Mismatch raises
|
||||
:class:`ConfigError`; ONE ERROR log
|
||||
``kind="c2.vpr.dim_mismatch"`` is emitted; the strategy is
|
||||
NOT bound.
|
||||
6. On success, ONE INFO log ``kind="c2.vpr.strategy_loaded"``
|
||||
7. On success, ONE INFO log ``kind="c2.vpr.strategy_loaded"``
|
||||
with ``strategy`` + ``descriptor_dim``.
|
||||
|
||||
Raises:
|
||||
StrategyNotAvailableError: compile-time flag OFF or
|
||||
concrete module not yet built (AZ-337..AZ-340 pending).
|
||||
concrete module not yet built (AZ-337 / AZ-339 / AZ-340 pending).
|
||||
ConfigError: ``descriptor_dim`` mismatch between strategy
|
||||
and corpus index.
|
||||
"""
|
||||
@@ -176,8 +214,9 @@ def build_vpr_strategy(
|
||||
raise StrategyNotAvailableError(
|
||||
f"VprStrategy {strategy!r} is configured but its concrete impl "
|
||||
f"module {module_name!r} has not been built into this binary "
|
||||
"yet (AZ-337 / AZ-338 / AZ-339 / AZ-340 pending)."
|
||||
"yet (AZ-337 / AZ-339 / AZ-340 pending)."
|
||||
) from exc
|
||||
_register_strategy_architecture(strategy, config)
|
||||
create_fn = getattr(module, "create", None)
|
||||
if create_fn is None:
|
||||
strategy_cls = getattr(module, class_name)
|
||||
|
||||
Reference in New Issue
Block a user