This directory contains the Python entry points and core library code for the CREST training, hardware-in-the-loop, and deployment flow.
This guide maps the training, HIL, and extension surfaces under src/.
Related docs:
- Repository setup, dataset preparation, and operator workflow live in
../README.md. - Config block meanings, NAS policy shape, and runtime config caveats live in
config/README.md. - Microcontroller and hardware-backend bring-up live in
crest/microcontrollers/README.md.
| Goal | Start Here | Hardware Requirement |
|---|---|---|
| Run or modify NAS/training orchestration | nas_model_client.py and crest/runtime_bootstrap.py |
No hardware required when device.hil: false |
| Run or modify HIL server behavior | hil_server.py and crest/hil_runtime.py |
Development board required for execution; HIL harness required for energy measurement |
| Add a dataset | crest/datasets/README.md |
No hardware required for adapter development |
| Add a task | crest/tasks/README.md |
No hardware required for adapter development |
| Add a model family | crest/model_families/README.md |
No hardware required for model construction; hardware needed for backend validation |
| Add board support | crest/microcontrollers/README.md |
Matching board and toolchain required |
nas_model_client.pyRuns NAS, training, scoring, artifact export, and the client side of the hardware-in-the-loop workflow.hil_server.pyRuns the ZeroMQ HIL server that materializes models, stages device-specific candidates, and returns compile/runtime metrics.pareto_hil_replay.pyReplays already-logged Pareto-front NAS candidates through a target HIL config and writes replay artifacts.
The crest/ package holds the reusable implementation behind the entry
points above.
crest/component_selection.pyResolves the active dataset, task, and model-family selections from config.crest/registry.pyDefines the string-keyed registries for datasets, tasks, and model families.crest/builtin_components.pyRegisters the built-in dataset, task, and model-family implementations.crest/interfaces.pyDefines the core abstraction contracts:DatasetABC,TaskABC, andModelFamilyABC.crest/pipeline_types.pyDefines shared typed payloads passed between the modular pipeline layers.crest/datasets/Dataset adapters. Built-ins includeoxiod.pyandurbansound8k_mel.py. Seecrest/datasets/README.mdfor the contributor guide.crest/tasks/Task adapters. Built-ins includeodometry_regression.pyandsound_classification.py. Seecrest/tasks/README.mdfor the contributor guide.crest/model_families/Model-family implementations. Built-ins includeodom_tcn.pyandaudio_dscnn.py. Seecrest/model_families/README.mdfor the contributor guide.crest/microcontrollers/Hardware backends and backend registry/factory logic. Seecrest/microcontrollers/README.mdfor bring-up details.crest/model.pyShared runtime helpers for config loading, score evaluation, and generic metric normalization.crest/runtime_bootstrap.pyShared task-aware bootstrap path used by bothnas_model_client.pyandhil_server.pyto resolve component selection, instantiate dataset/task/ family components, and validate NAS policy against the active task contract.crest/hil_runtime.pyRuntime-owned HIL request construction and metric collection helpers used by the HIL server and related tests.crest/pareto_replay.pyReusable Pareto replay selection, request reconstruction, resume, manifest, and result-writing logic behindpareto_hil_replay.py.crest/devices.pyShared device dataclasses and theDeviceInterfacecontract used by hardware backends.crest/hardware.pyLegacy/shared hardware utility layer used by the current HIL/NAS flow.
The modular runtime is built around three main interfaces plus a small set of typed payloads shared across orchestration code.
DatasetABCincrest/interfaces.pyLoads raw data and returns a normalizedDatasetBundlewith train/validation/test/calibration splits plus dataset metadata.TaskABCincrest/interfaces.pyDefines the target/output contract, task-owned fitting behavior, evaluation behavior, and the metric contract that shared NAS/scoring code consumes.ModelFamilyABCincrest/interfaces.pySamples hyperparameters, builds models, validates family-local config, and materializes the export variant passed into HIL.- Shared typed payloads in
crest/pipeline_types.pycarry the normalized information exchanged between those layers:DatasetBundle,TargetSpec,ModelBuildContext,FitPlan,EvaluationResult, andTaskMetricContract.
At a high level, the source tree is wired like this:
nas_model_client.pyorhil_server.pyloadsconfig/nas_config_stm32.yamlthrough shared helpers increst/model.py.- The entry point calls
ensure_builtin_components_registered()fromcrest/builtin_components.pyso the default dataset, task, and model family are available by name. - The entry point runs the shared bootstrap in
crest/runtime_bootstrap.py, which resolves component selection, instantiates the selected dataset/task/model family, derives the target spec, and validatesnas.score,nas.prune, andnas.feasibilityagainst the task metric contract. - The selected dataset adapter loads data and produces a normalized
DatasetBundle. - The selected task adapter builds the target contract and training/evaluation behavior.
- The selected model family samples hyperparameters, builds models, and materializes export variants.
- When hardware metrics are needed, the HIL path builds a normalized request
through
crest/hil_runtime.py, then the selected microcontroller backend stages, compiles, uploads, and measures one candidate. - Shared scoring, pruning, and result-shaping code combines task metrics and backend metrics into the values used by NAS and reporting.
That split is important:
- Dataset/task/model-family code owns the ML-side behavior.
- Microcontroller backend code owns the build/upload/runtime measurement path.
- The top-level scripts own orchestration and policy.
NAS and HIL both use the same component-selection path. The same config-driven dataset/task/model-family selection is resolved before the code branches into training/NAS behavior or board/backend behavior.
The current modular selection surface is resolved in
crest/component_selection.py.
The main config knobs are:
dataset.nameSelects the dataset adapter.dataset.paramsRequired dataset-local config block.task.nameSelects the task adapter.task.paramsOptional task-local config block.model.familySelects the model family.model.paramsModel-family-local configuration.model.searchModel-family-local search-space configuration.
dataset, task, and model are required blocks. The older top-level
data fallback is not part of the supported config contract anymore.
See config/README.md for the current shipped config shape.
CREST does not currently auto-discover components from the filesystem. The registry model is explicit:
crest/registry.pydefinesdataset_registry,task_registry, andmodel_family_registry.crest/builtin_components.pyregisters the built-in components under their stable string keys.- The entry points call
ensure_builtin_components_registered()before they resolve component names from config.
If you add a new dataset, task, or model family, it is not available until something registers it under the name you intend to use in config.
The shared score/logging path no longer assumes one fixed model shape or one fixed metric schema.
TaskMetricContract is the task-owned
declaration shared with orchestration code. It tells the shared pipeline:
- which metrics the task can produce
- which metrics only exist after full training
- which metrics are guaranteed nonnegative
- which metrics are the task's primary headline metrics
This is how the task layer advertises metrics such as rmse_total without
hardcoding them into the shared NAS/logging layer.
TaskABC now owns both fit-plan construction and task-specific closeout hooks.
build_fit_plan(...)Returns theFitPlanused by NAS-time training and final retraining, including thecombine_train_val=Truepath.history_component_keys(...)Advertises which per-output training curves should be plotted for the active task.generate_closeout_artifacts(...)Produces task-specific closeout artifacts without forcing generic NAS code to assume odometry trajectory reporting.
ScoreEvaluationResult is the shared representation of
the resolved score/objective values for one trial.
- Scalar runs set
scoreand leave the objective lists as the configured single-objective projection. - Multi-objective runs leave
scoreasNoneand instead carry ordered objective names, values, and directions.
TrialOutcome is the generic payload written to CSV and
mirrored into Optuna trial attributes. It carries:
- resolved scalar/objective values
task_metrics- resolved trial
hyperparams - optional task-owned
artifact_summary
The shared logging code does not need to know which task or model family produced those values.
log_trial(...) writes a stable infrastructure column set
plus dynamic task/hyperparameter columns.
Stable shared columns include:
- study/timestamp fields
- shared hardware metrics such as RAM, flash, latency, power, energy, and error codes
- score/objective metadata (
score_type,objective_*_json) - pruning metadata
- feasibility metadata and signed Optuna constraints
artifact_summary_json- cadenced runtime telemetry fields when present
Dynamic columns are added per trial outcome:
- task metrics become
metric__{name} - hyperparameters become
hparam__{name}
The same information is also mirrored into trial.user_attrs, including the
fully expanded metric__* and hparam__* keys.
pareto_hil_replay.py does not run NAS or retrain models. It reads a source
NAS CSV/config, reconstructs the logged candidate payloads, selects the valid
Pareto front, and reuses the same HIL server/backend request path that normal
hardware scoring uses.
Use --dry-run as a hardware-free preflight; it writes manifest.json,
replay_requests.jsonl, and replay_results.csv without instantiating
HILServer. For hardware execution, the command forwards reconstructed
family_hparams, runtime_metadata, quantization_mode, optional preserved
device options, and optional CLI model/checkpoint overrides to HIL.
--resume skips payload keys already present in replay_results.csv with
completed or dry_run status. Do not reuse a dry-run output directory for a
hardware replay with --resume unless skipping those candidates is intentional.
Contributor standards for new implementation work:
- Make breaking config or API changes explicit in the relevant README and config examples.
- Avoid temporary compatibility paths unless they have a tracked owner and removal point.
- Keep new helpers small, readable, locally validated, and documented with NumPy-style docstrings for changed functions, classes, dataclasses, and tests.
- Keep non-obvious logic commented, especially feature caching, score/cadence derivation, export/materialization, and hardware staging decisions.
- Keep code testable without hardware where possible; hardware flows should expose non-hardware preflight checks for config, model construction, export, and generated requests.
- Pin or justify dependency additions in the implementation plan that adds them.
If you mean a new trainable/exportable model family, this is the primary extension path in the current codebase.
Key files:
crest/interfaces.pyModelFamilyABCdefines the contract.crest/model_families/odom_tcn.pyConcrete example of the built-in odometry TCN family.crest/model_families/audio_dscnn.pyConcrete example of a built-in classification family over cached log-mel tensors.crest/builtin_components.pyBuilt-in registration.crest/component_selection.pyExplicit config selection for dataset, task, and model-family components.hil_server.pyandnas_model_client.pyEntry-point orchestration that consumes the selected family.
Typical steps:
- Add a new module under
crest/model_families/. - Implement a
ModelFamilyABCsubclass. - Register it in the appropriate registry path.
The built-in pattern today is
crest/builtin_components.py. - Set
model.familyin config to the registered name. - Put family-local knobs under
model.paramsandmodel.searchwhen needed. - Verify export/materialization semantics if the family needs custom model loading, custom objects, or variant handling.
Important boundary:
- Model families own model construction and export-oriented materialization.
- Hardware backends own candidate staging, compile, upload, and runtime measurement.
Key files:
crest/datasets/README.mdfor the full contributor guidecrest/interfaces.pyforDatasetABCcrest/datasets/oxiod.pyandcrest/datasets/urbansound8k_mel.pyas built-in examplescrest/builtin_components.pyfor the current registration pattern
Typical steps:
- Add a new dataset adapter under
crest/datasets/. - Implement
DatasetABC. - Register it under a stable string key.
- Select it with
dataset.name. - Put dataset-local knobs under
dataset.params.
Key files:
crest/tasks/README.mdfor the full contributor guidecrest/interfaces.pyforTaskABCcrest/tasks/odometry_regression.pyandcrest/tasks/sound_classification.pyas built-in examplescrest/builtin_components.pyfor the current registration pattern
Typical steps:
- Add a new task adapter under
crest/tasks/. - Implement
TaskABC. - Register it under a stable string key.
- Select it with
task.name. - Keep task-owned training, evaluation, and metric-contract logic inside the task adapter.
Key files:
crest/microcontrollers/README.mdfor the bring-up guidecrest/devices.pyforDeviceInterfacecrest/microcontrollers/__init__.pyfor backend registration/factory plumbing
That path is intentionally separate from model-family work. Board bring-up,
toolchain integration, upload flow, runtime telemetry, and backend-owned
device options belong to the microcontroller backend layer, not to
ModelFamilyABC.
Important caveat:
- A new non-Arduino backend must be wired into the registry metadata in
crest/microcontrollers/__init__.pyor it is unreachable except through the Arduino FQBN fallback path.
For the current scoring, pruning, and runtime knobs, use:
config/README.mdfor the config referenceconfig/nas_config_stm32.yamlfor the STM32 config shapecrest/model.pyfor score/prune/feasibility evaluation and HIL request constructionhil_server.pyfor the HIL-side request handling and backend failure shaping