Local development¶
Prerequisites¶
- macOS or Linux (tested on macOS Sequoia / arm64)
- Python at the version named in
config.yml(python_version, currently3.12) onPATH - R 4.5+ on
PATH - The active ABCD data-dictionary parquet in
data/. The filename is set inconfig.ymlunderdictionary.parquet(currentlydd-abcd-7_0.parquet). Seedata/Readme.mdfor how to produce it fromNBDCtools::get_dd_abcd().
setup.sh and run.sh will offer to install Python and R via Homebrew if they're missing.
config.yml — the single source of truth¶
config.yml at the repo root drives the whole pipeline. Live contents:
# config.yml — single source of truth for setup.sh and python/build_embeddings.py.
#
# Edit this file to point the build pipeline at a different dictionary release,
# embed from a different column, or swap the embedding model. Both setup.sh
# and python/build_embeddings.py read these values at run-time.
# Python toolchain --------------------------------------------------------
python_version: "3.12" # required interpreter version (X.Y)
venv_dir: python_env # virtualenv directory (relative to repo root)
# Embedding model ---------------------------------------------------------
model:
repo_id: sentence-transformers/all-MiniLM-L6-v2
onnx_hub_path: onnx/model_quint8_avx2.onnx # AVX2 is universal on Linux x86_64
max_seq_len: 128
batch_size: 64
# Dictionary --------------------------------------------------------------
# The parquet filename is the single source of truth. Its stem (filename
# without the .parquet suffix) is reused to locate the source CSVs under
# data/:
# <stem>.csv -> converted to <stem>.parquet for the R UI
# <stem>_minimal.csv -> embeddings_imag.npy + metadata_imag.npz
# <stem>_minimal_noimag.csv -> embeddings_noimag.npy + metadata_noimag.npz
# To point the pipeline at a new release, edit only `parquet:` below.
dictionary:
parquet: dd-abcd-7_0.parquet
# Column whose text content is encoded into embeddings.
text_column: label
# Column kept alongside each embedding as metadata.
metadata_column: domain
Everything that varies between releases or experiments lives here:
| Key | What it controls | Read by |
|---|---|---|
python_version |
Interpreter version required for the venv (X.Y) | setup.sh |
venv_dir |
Path to the local Python virtualenv | setup.sh, .Rprofile (indirectly) |
model.repo_id |
HuggingFace repo for the embedding model | python/build_embeddings.py |
model.onnx_hub_path |
ONNX weights file inside that repo | python/build_embeddings.py |
model.max_seq_len, model.batch_size |
Encoder limits at build time | python/build_embeddings.py |
dictionary.parquet |
Active dictionary release (filename in data/) |
setup.sh, build_embeddings.py, app.R |
dictionary.text_column |
Column whose text gets encoded into embeddings | python/build_embeddings.py |
dictionary.metadata_column |
Column kept alongside each embedding for filtering | python/build_embeddings.py, python/backend.py |
Switching ABCD releases is therefore a one-line edit (dictionary.parquet:) followed by ./setup.sh. The R UI parses the version out of the filename (dd-abcd-7_0.parquet → 7.0) and shows it in the title and header banner.
One-time setup¶
What it does (see setup.sh for the full source):
- Reads
python_versionandvenv_dirfromconfig.ymland verifies that interpreter is onPATH. - Creates the venv (rebuilds it if the existing one is the wrong Python version) and installs
requirements.txt+ build-only deps (pandas,fastparquet,huggingface_hub,pyyaml). - Runs
python/build_embeddings.py, which:- Downloads the ONNX model + tokenizer named in
config.ymlfrom Hugging Face intopython/model/(skipped if already present). - Reads
data/<dictionary.parquet>and derives both corpora in-memory:imag(every row with a non-nulltext_column) andnoimag(imagminus rows wheremetadata_column == "Imaging"). - Encodes each corpus to fp16 NumPy arrays and writes
data/embeddings/embeddings_<x>.npy. - Writes a tiny
data/embeddings/metadata_<x>.npzper corpus with the metadata + label arrays the backend needs at runtime. - Writes
data/embeddings/manifest.txtrecording the parquet filename the embeddings were built against (the R UI checks this on startup).
- Downloads the ONNX model + tokenizer named in
- Runs
renv::restore()and installsnanoparquetif missing.
Re-run ./setup.sh any time:
config.ymlchanges (different dictionary release, different model, different columns)requirements.txtchanges- The dictionary parquet itself changes
- You delete
python_env/ordata/embeddings/to start fresh
Running the app¶
Defaults: http://127.0.0.1:4444 with browser auto-launch. Configurable:
What it does:
- Checks the configured Python version + R are present (offers
brew installif not). - Checks the artifact paths exist (
python_env/,python/model/,data/embeddings/, the activedata/<dictionary>.parquet). If anything's missing, offers to run./setup.sh. - Execs
Rscript -e "shiny::runApp(host=..., port=..., launch.browser=TRUE)".
The exec means Ctrl+C goes straight to R, which exits cleanly.
On startup, app.R performs two integrity checks before serving requests:
- The parquet named in
config.ymlexists atdata/<that-filename>. data/embeddings/manifest.txtmatchesconfig.yml. If you bump the release inconfig.ymlbut forget to re-run./setup.sh, the app refuses to start with an explicit error rather than silently displaying stale rows from the previous release.
What runs where¶
| Component | Source | Where it lives at runtime |
|---|---|---|
| R Shiny UI | app.R |
R session started by run.sh |
| Python backend | python/backend.py |
Python venv at python_env/ (local) or shinyapps.io's managed venv |
| ONNX model + tokenizer | python/model/{model.onnx, tokenizer.json} |
Loaded lazily on first search |
| Corpus embeddings | data/embeddings/embeddings_*.npy (fp16) |
Loaded into RAM as fp32 on first search per corpus |
| Domain + label arrays | data/embeddings/metadata_*.npz |
Loaded with np.load(allow_pickle=True) |
| Drift guard | data/embeddings/manifest.txt |
Read once at app startup |
| Dictionary for display | data/<dictionary.parquet> (from config.yml) |
Read by nanoparquet::read_parquet() at app startup |
.Rprofile handles the local-vs-shinyapps split:
if (Sys.info()[["user"]] != "shiny" && dir.exists("python_env")) {
Sys.setenv(RETICULATE_PYTHON = file.path(getwd(), "python_env", "bin", "python"))
}
On shinyapps.io, Sys.info()[["user"]] == "shiny" is true, the local block is skipped, and reticulate's auto-installer takes over (see Deployment).
Cleaning up¶
./clean_artifacts.sh interactively removes data/embeddings/, python/model/, and/or python_env/. Pair it with ./setup.sh to rebuild from scratch:
Re-running just the embedding build¶
If only the parquet changed (not the model or requirements):
The model files are skipped if already present; only the embeddings, metadata, and manifest get rewritten.
Editing the app interactively¶
The app uses options(shiny.autoreload = TRUE), so saving app.R while it's running picks up changes on the next request. Python changes (python/backend.py) require restarting the app (reticulate caches loaded modules).
Sanity-check scripts¶
sanity-chks/ contains the scripts we used to evaluate embedding backends:
sanity_check_model2vec.py— comparesmodel2vecdistilled embeddings vs MiniLM baselinesanity_check_onnx.py— compares ONNX int8 vs fp32 PyTorch baseline
Both produce side-by-side top-K results, Jaccard overlap, and a "quality ratio" metric. Useful if you want to swap in a different model.
The folder is .gitignore'd, .rscignore'd, and not deployed. Run scripts directly:
(They need their own venv since they pull sentence-transformers for the baseline.)