CLI Reference¶
Complete reference for the leech command-line interface.
Command overview¶
The CLI is organized into workflow-based command groups:
| Group | Commands | Purpose |
|---|---|---|
leech data |
prepare, merge |
Extract features, merge and split datasets |
leech model |
train, train-crf, optimize, benchmark, bundle, bundle-info, calibrate, export, release, list, fetch |
Train, tune, calibrate, package, and publish models |
leech eval |
test |
Evaluate models |
leech predict |
(top-level) | Run inference on new data |
Data commands¶
leech data prepare¶
Extract training chunks from POD5 and BAM files centered on a sequence motif.
Required:
| Option | Description |
|---|---|
--pod5 FILE |
POD5 file with raw nanopore signal |
--bam FILE |
BAM file with alignments and move tables (mv, ns tags) |
--output-dir DIR |
Directory to save training chunks |
Feature extraction:
| Option | Default | Description |
|---|---|---|
--signal-context LEFT RIGHT |
(symmetric 225/225) | Asymmetric signal window as two ints (e.g. --signal-context 90 450). Mutually exclusive with --signal-context-bases |
--signal-context-bases L R |
(disabled) | Base-defined signal window: cuts at the base-to-signal map positions of offsets -L/+R around the focus base (e.g. --signal-context-bases 8 24), instead of a fixed sample count, so two reads at different translocation speeds read the same bases of context. Padded or centre-cropped to --signal-len. Mutually exclusive with --signal-context. Implemented in both prepare backends and in the Rust predict inference pipeline |
--signal-len INT |
(L+R+1)*36 |
Fixed emitted signal length for --signal-context-bases (default is a conservative slow-read rate, so a typical read pads rather than centre-crops). Ignored without --signal-context-bases |
--feature-start INT |
-5 |
Feature window start offset from focus base (negative = upstream, into the body of the read) |
--feature-end INT |
5 |
Feature window end offset from focus base (positive = toward adaptor) |
--feature-channels LIST |
(historical twelve) | Comma-separated feature-channel names selecting and ordering the corpus's feature rows, e.g. dwell,dwell_log,level_mean,level_skew -- or the single word all for every known channel (the historical twelve plus level_skew/level_kurtosis), so a corpus can be prepared once with everything and model train --feature-channels can pick a subset per run later, without re-preparing per ablation arm. Default (unset): dwell,dwell_log,dwell_mean,dwell_std,dwell_ratio,level_mean,level_median,level_std,level_range,kmer_expected,kmer_residual,kmer_residual_abs -- so omitting this flag reproduces every corpus prepared before it existed byte-identically. level_skew/level_kurtosis are also available individually (population skewness / Fisher excess kurtosis) but not in the default list. Row order is the model's input channel order: recorded in prepare_config.json as the expanded list (never the literal word all), carried into the trained model's config, and extracted exactly by leech predict (never re-chosen at predict time). data merge refuses to combine corpora whose lists differ. Both extraction backends (Rust and Python) honor it. Unknown names are refused before any read is touched. |
Motif search:
| Option | Default | Description |
|---|---|---|
--motif STR |
-- | Sequence motif to center on |
--motif-offset INT |
0 |
Focus base within motif (0-indexed) |
--motif-reference STR |
fasta |
Search in fasta (reference) or bam (basecalled sequence) |
--reference-fasta FILE |
-- | Reference FASTA if not in BAM header |
--skip-motif-indels |
False |
Skip motif sites with indels in alignment. Class-selective: removes far more charged than uncharged reads; see per-class attrition in the log and prepare_config.json. |
--require-query-mapping / --no-require-query-mapping |
enabled | Require a reference motif to also map cleanly to query coordinates (--motif-reference fasta, --anchor reference). See note below. |
When to use --no-require-query-mapping
By default a motif found in the reference must also map cleanly to query coordinates. That mapping is only a quality gate -- its result is discarded, because the chunk is positioned from the reference through CIGAR interpolation -- so turning it off keeps reads whose motif basecalled badly without moving any chunk.
Use it when the modification under study mis-calls the motif itself. Otherwise those reads are dropped for carrying the very signal being measured.
The setting is recorded in prepare_config.json, carried into the model
config by leech model train, and applied again by leech predict, so a
model is scored on the same read population it was trained on.
Every chunk also records junction_indel (the CIGAR-measured indel length at
the motif junction, 0 when exact) and junction_mapped (whether the
junction mapped at all) -- automatically, with no flag to enable them. Use
leech model train --sample-weight-field junction_indel to over-sample
disrupted-junction chunks, and leech predict --abstain-on-junction-indel to
apply --min-margin only to reads whose junction is disrupted. leech eval
test reports metrics split by junction_indel == 0 vs. != 0 automatically
whenever the test corpus carries the field.
Signal handling:
| Option | Default | Description |
|---|---|---|
--base-justify STR |
center |
Where to center signal chunk within the focus base: start, center, or end |
--no-reverse-signal |
(off) | Do NOT reverse raw signal. By default signal is reversed for direct RNA (POD5 stores 3'->5'). Use this flag for DNA data. |
--mask-seq-side [left\|right] |
(none) | Blank (N) sequence-branch bases strictly on that side (5'/left or 3'/right) of the focus base, in both sequence (base_onehot) and sequence_with_kmer_context (signal_kmer) -- the focus base itself is never masked. Prevents a feature+signal model from reading identity through bases upstream/downstream of a motif that would otherwise leak the label (e.g., tRNA-body identity leaking through acceptor-stem bases upstream of a 3'-end motif). Baked into the corpus, recorded in prepare_config.json, carried into the trained model's config, and reapplied automatically to live chunks by leech predict. Forces the Python extraction path (unsupported in Rust) at both prepare and predict time. data merge warns if the corpora being merged disagree on it. |
Signal map refinement:
| Option | Default | Description |
|---|---|---|
--refine-signal-map / --no-refine-signal-map |
enabled | Refine move-table base boundaries against expected k-mer levels. Use --no-refine-signal-map for DNA. |
--kmer-table FILE |
(bundled) | K-mer level table; defaults to the table shipped with leech |
--scale-iters INT |
2 |
-1 = no refinement; 0 = one banded-DP pass without rescaling; N = N passes with Theil-Sen rescaling between them |
Refinement changes chunk contents
Refinement is on by default and moves base boundaries, so it changes every dwell and every level-derived feature. A corpus prepared with it on is not interchangeable with one prepared with it off.
The refined boundaries are taken; the affine (scale, shift, drift) fit
that escapepod returns alongside them is deliberately discarded. Applying
it would replace one median-MAD transform shared by every read with a
per-read fit estimated on a near-constant 3' adapter, where it is weakly
identified -- and cross-read comparability is what k-mer residual features
depend on.
Splitting and parallelism:
| Option | Default | Description |
|---|---|---|
--label STR |
-- | Label identifier for this sample (e.g. Ala, charged); numeric labels are assigned during merge |
--train-split FLOAT |
0.7 |
Fraction for training |
--val-split FLOAT |
0.15 |
Fraction for validation |
--no-split |
False |
Save all chunks to all.npz (for later merge) |
--workers INT |
8 |
Parallel workers |
--chunk-size INT |
100 |
Reads per batch |
--min-mapq INT |
0 |
Minimum mapping quality |
--seed INT |
(none) | Random seed |
Examples:
# Basic: prepare training data
leech data prepare \
--pod5 reads.pod5 --bam alignments.bam \
--output-dir chunks/ --label 1
# Parallel with reference-based motif search
leech data prepare \
--pod5 reads.pod5 --bam alignments.bam \
--output-dir chunks/ --workers 8 \
--motif CCAGGC --motif-reference fasta --skip-motif-indels
# Prepare without splitting (for multi-sample merge later)
leech data prepare \
--pod5 reads.pod5 --bam alignments.bam \
--output-dir chunks/ --no-split --label 1
leech data merge¶
Merge chunk files from multiple samples and split at the read level to prevent data leakage.
| Option | Default | Description |
|---|---|---|
-i, --input-chunks |
(required) | Input chunks with labels (label=file.npz) |
-o, --output-dir |
(required) | Output directory for split chunks |
--train-split |
0.7 |
Fraction for training |
--val-split |
0.15 |
Fraction for validation |
--seed |
42 |
Random seed |
--k-fold |
1 |
Number of cross-validation folds. When > 1, creates k-fold splits (must be >= 3) |
--comparison-spec |
-- | TSV file with batch comparison specs |
--split-by |
-- | NPZ field to split by group instead of by read (e.g. reference_names); reads sharing a value stay in the same split |
Examples:
# Pairwise comparison (e.g., amino acid identity)
leech data merge \
-i Ala=ala.npz -i Gly=gly.npz \
-o merged/
# Group comparison (chemical properties)
leech data merge \
-i basic=lys.npz -i basic=arg.npz \
-i acidic=asp.npz -i acidic=glu.npz \
-o merged/
# 5-fold cross-validation
leech data merge \
-i Ala=ala.npz -i Gly=gly.npz \
-o kfold/ --k-fold 5
Model commands¶
leech model train¶
Train a model on prepared training data.
Required:
| Option | Description |
|---|---|
--train-data FILES |
Training data JSON files (glob patterns supported) |
--val-data FILES |
Validation data JSON files |
--model MODEL |
Architecture name (see below) |
--output-dir DIR |
Directory for model checkpoints |
Available architectures (26 total; production and experimental tiers — see
leech.models.model_tier). ConvLSTMRemora/ConvLSTMRemoraBase exist for
comparison against the Remora paper's architecture, and ConvOnly cannot
reach ONNX export (--format onnx) and is deprecated (get_model("ConvOnly",
...) raises DeprecationWarning -- zero references in escapepod-models and
no fix planned, see leech.models.model_tier); neither is listed here — see
leech model train --help for the full registry including those.
| Family | Models |
|---|---|
| ConvLSTM | ConvLSTMDwell (production), ConvLSTMBase (production, ablation arm), +BN, +Attn, +BNAttn, +GNAttn, +LNAttn variants |
| Transformer | TransformerDwell, TransformerDwellResidual (2-channel signal) |
| TCN | TCNDwell, TCNDwellGN, TCNDwellLN, TCNDwellResidual (production), TCNDwellResidualGN, TCNDwellResidualLN (production), TCNDwellResidualMotor, TCNDwellResidualLNMotor (+ motor-region pooling), TCNDwellResidualDwellAttn, TCNDwellResidualLNDwellAttn (+ dwell-only cross-attention), TCNDwellSplitResidual, TCNDwellSplitResidualLN |
| Other | ResNetDwell, SignalCNN (signal-only ablation, ignores sequence/features) |
Core training options:
| Option | Default | Description |
|---|---|---|
--epochs INT |
50 |
Training epochs |
--batch-size INT |
128 |
Batch size |
--learning-rate FLOAT |
0.001 |
Learning rate |
--device STR |
cuda |
cuda or cpu |
--seed INT |
(none) | Random seed |
--early-stopping INT |
10 |
Stop after N epochs without improvement (0 = disable) |
--resume FILE |
-- | Resume from a checkpoint file |
Class balancing:
| Option | Default | Description |
|---|---|---|
--use-class-weights / --no-class-weights |
enabled | Auto-compute class weights for imbalanced data |
--pos-weight FLOAT |
-- | Manual positive class weight (overrides auto) |
--balance-groups / --no-balance-groups |
disabled | Balance sampling across source_group values so each group contributes equally per epoch |
--oversample-minority / --no-oversample-minority |
disabled | Oversample minority classes so each class contributes equally per epoch (mutually exclusive with --balance-groups) |
--sample-weight-field STR |
-- | Generalizes --balance-groups to any chunk metadata field: inverse-frequency weight sampling by that field's distinct values, one group per value (mutually exclusive with --balance-groups/--oversample-minority). E.g. --sample-weight-field junction_indel to over-sample chunks whose motif junction mapped with an indel |
Regularization and optimization:
| Option | Default | Description |
|---|---|---|
--weight-decay FLOAT |
0 |
L2 weight decay |
--max-grad-norm FLOAT |
0 |
Gradient clipping (0 = disabled) |
--scheduler STR |
none |
LR scheduler: none, reduce_on_plateau, or cosine |
--scheduler-patience INT |
5 |
Epochs before reducing LR |
--scheduler-factor FLOAT |
0.5 |
Factor to reduce LR by |
--warmup-epochs INT |
0 |
Linear warmup epochs |
With --scheduler cosine, warmup is part of the schedule itself: the LR ramps
linearly for --warmup-epochs and then follows a cosine decay down to a floor of
1e-6. Past the epoch budget the LR holds at the floor rather than cycling back
up. reduce_on_plateau keeps its own warmup handling and is driven by
--checkpoint-metric, not validation loss.
Optimizer loop:
| Option | Default | Description |
|---|---|---|
--grad-accum-split INT |
1 |
Split each batch into N sub-batches, accumulating gradients before stepping once (1 = disabled) |
--quantile-grad-clip / --no-quantile-grad-clip |
disabled | Clip at 2x the median of the last 100 gradient norms instead of a fixed threshold |
--save-optim-every INT |
1 |
Write optimizer state only every N epochs (weights are still written on every save) |
Use --grad-accum-split to raise the effective batch size when a real batch
will not fit in GPU memory: --batch-size 64 --grad-accum-split 4 costs the
memory of 16 samples but takes the optimizer step of 64. Keep --batch-size
divisible by N -- on a ragged final sub-batch, per-sample weighting is slightly
off.
--quantile-grad-clip adapts the clipping threshold to the gradient scale the
run is actually seeing, which is useful when a fixed --max-grad-norm is hard to
pick. It takes precedence over --max-grad-norm if both are given.
--save-optim-every N trades resumability for checkpoint size. Note that it
applies by epoch number, so model_last.pt carries optimizer state only if the
final epoch is a multiple of N; resuming from a checkpoint without it starts
from a fresh optimizer and logs a warning. It has no effect on
model_resume.pt, which always carries optimizer state -- see below.
Loss function:
| Option | Default | Description |
|---|---|---|
--loss STR |
bce |
Loss function: bce, focal, cross_entropy, or noise_corrected_bce |
--focal-gamma FLOAT |
2.0 |
Focal loss gamma (only with --loss focal) |
--label-noise-rate STR |
-- | group=rate[,group=rate,...] per-source_group label-flip probability for --loss noise_corrected_bce (e.g. gold=0.09,enzymatic=0.17). Unmapped groups get rate 0. See Label-noise-aware loss. |
--focal-neg-gamma FLOAT |
none (symmetric) | Separate gamma for negative-labeled examples, making the focal loss asymmetric (only with --loss focal). Larger than --focal-gamma down-weights easy negatives harder, shaping the loss for a low-FPR operating regime |
Data augmentation:
| Option | Default | Description |
|---|---|---|
--augment-jitter FLOAT |
0 |
Signal jitter noise std dev (0 = disabled). Per-channel via key=val,..., e.g. signal=0.02,signal_residual=0.001 |
--augment-scale-min FLOAT |
1.0 |
Min random scale factor |
--augment-scale-max FLOAT |
1.0 |
Max random scale factor |
--augment-shift-max-bases FLOAT |
0 |
Max cross-layer shift in bases; float for sub-base resolution (0 = disabled) |
--augment-time-mask-bases INT |
0 |
Max width in bases for time masking (0 = disabled) |
--augment-time-mask-count INT |
1 |
Number of time masks to apply |
--augment-feature-noise-scale FLOAT |
0 |
Per-channel Gaussian noise scale for features (0 = disabled) |
--augment-time-stretch MIN,MAX |
1,1 (disabled) |
Resample each chunk's signal window by a per-sample factor drawn uniformly from [MIN, MAX] about the focus position, for speed invariance. Scales the base-to-signal map to match (signal_kmer stays aligned) and the dwell/dwell_mean/dwell_log feature channels (dwell_ratio, dwell_std and level channels are unchanged). Batched __getitems__ fetch path only; never applied to validation; incompatible with --augment-shift-max-bases and --augment-time-mask-bases |
--label-smoothing FLOAT |
0 |
Label smoothing factor (0 = disabled; e.g. 0.05 softens 0/1 targets) |
--mixed-precision / --no-mixed-precision |
disabled | Mixed precision training (CUDA only) |
Provenance (recorded in config for inference):
| Option | Default | Description |
|---|---|---|
--motif STR |
-- | Motif used for chunk extraction |
--motif-offset INT |
0 |
Focus base within motif (0-indexed) |
--base-justify STR |
center |
Signal justification |
--model-config FILE |
-- | JSON file with model architecture overrides (e.g. {"causal": false} for a TCN family's padding, or precomputed feature_mean/feature_std) |
--seq-encoding STR |
signal_kmer |
Sequence encoding: base_onehot or signal_kmer |
--encoding-fallback / --no-encoding-fallback |
auto | Allow signal_kmer to fall back to base_onehot when the corpus has no base-to-signal maps. Auto = allowed only when --seq-encoding was left at its default |
--num-workers INT |
0 |
DataLoader workers (0=auto) |
--standardize-features / --no-standardize-features |
disabled | Compute per-channel feature mean/std once over the training corpus and freeze them into the feature branch as an affine layer. Only models with a feature_branch (the ConvLSTM and TCN families) accept this |
--strict-window / --no-strict-window |
disabled | Raise instead of zero-padding when the signal crop window (left_context/right_context, e.g. via --model-config) extends outside the stored chunk. Default logs one warning per dataset the first time it happens rather than silently training on zero-padded samples |
--feature-channels LIST |
(every corpus row) | Comma-separated feature-channel names selecting and ordering a SUBSET of the training corpus's recorded feature_channels (channel ablation) -- e.g. dwell,dwell_log,level_mean. Lets a corpus prepared once with data prepare --feature-channels all be trained on different channel subsets without re-preparing. Every requested name must be in the corpus's recorded list (prepare_config.json, or the historical twelve if absent); an unknown or absent one is refused before training starts, naming the missing channels and the corpus's list. Applied identically to --val-data. Default (unset): use every row the corpus has, exactly as before this flag existed. The checkpoint's config.json records the SELECTED list as feature_channels (what leech predict extracts) and the corpus's full list separately as corpus_feature_channels, for provenance |
Output files:
model_best.pt-- best checkpoint by--checkpoint-metric(defaultauto: val_auc for binary, val_f1 for multiclass). Also acceptstpr_at_fpr:<f>(TPR at a fixed FPR) andcallable_at_precision:<p>(fraction of validation reads callable at a precision floor) for a low-FPR operating regime -- binary tasks only;leech model optimize's--selection-metrictakes the same names.model_last.pt-- final epoch checkpoint, written once after training ends (early stopping or completion)model_resume.pt-- rolling checkpoint, written atomically after every epoch and deleted on clean exit. This is the file that makes--resumerecover a run an external kill (SLURM walltime, OOM,scancel) cut off mid-loop:model_last.ptnever appears in that case, since it is only written when the epoch loop actually finishes. Point--resumeatmodel_resume.ptin anything that retries a job -- a Snakemakerestart-times, a resubmitted sbatch script. Refuses to resume (raises, does not warn) if the optimizer/scheduler/loss/auxiliary-head recipe or either corpus file changed since it was written; delete it to start over instead of warm-starting a different run under it. The check is scoped to whatTraineritself can vary -- a checkpoint's--motif,--batch-size,--seq-encodingetc. are provenance recorded inconfig.json, not enforced here, since aTrainerbuilt directly (bypassingleech model train) never sets them. Do not addmodel_resume.pttooutput:in a Snakemake rule -- Snakemake deletes a failed job's declared outputs, which would erase the one file the retry needs.config.json-- full training configuration (needed for inference)metrics.json-- per-epoch metrics
Examples:
# Basic training
leech model train \
--train-data chunks/train.json --val-data chunks/val.json \
--model ConvLSTMDwell --output-dir models/
# With focal loss, gradient clipping, and LR scheduling
leech model train \
--train-data chunks/train.json --val-data chunks/val.json \
--model ConvLSTMDwell --output-dir models/ \
--loss focal --focal-gamma 2.0 \
--max-grad-norm 1.0 \
--scheduler reduce_on_plateau --scheduler-patience 5
# Recover an interrupted run (SLURM walltime, OOM, scancel, ...) -- safe to
# pass unconditionally in a retry, since --resume is ignored when the file
# doesn't exist (e.g. the first attempt)
leech model train \
--train-data chunks/train.json --val-data chunks/val.json \
--model ConvLSTMDwell --output-dir models/ \
--resume models/model_resume.pt
# Extend a run that finished cleanly to more epochs
leech model train \
--train-data chunks/train.json --val-data chunks/val.json \
--model ConvLSTMDwell --output-dir models/ --epochs 80 \
--resume models/model_last.pt
# Large effective batch on a small GPU, with adaptive clipping
leech model train \
--train-data chunks/train.json --val-data chunks/val.json \
--model ConvLSTMDwell --output-dir models/ \
--batch-size 128 --grad-accum-split 4 \
--quantile-grad-clip \
--scheduler cosine --warmup-epochs 2
leech model optimize¶
Grid search over signal context windows and dwell offsets to find the best configuration.
Required:
| Option | Description |
|---|---|
--train-data FILE |
Training dataset (.npz) |
-o, --output-dir DIR |
Output directory |
--context-grid VALUES |
Comma-separated context values or start:stop:step range |
Grid options:
| Option | Default | Description |
|---|---|---|
--val-data FILE |
-- | Validation dataset |
--left-contexts VALUES |
uses --context-grid |
Override left context grid |
--right-contexts VALUES |
uses --context-grid |
Override right context grid |
--dwell-offsets VALUES |
-- | Dwell offset values to search (comma-separated or start:stop:step) |
--kmer-context INT |
5 |
K-mer context |
--base-justify STR |
center |
Signal chunk centering |
--parallel INT |
1 |
Grid points to train concurrently |
Training options (--model, --epochs, --batch-size, --learning-rate, --device, --seed, --early-stopping) work the same as in model train. So do the loss and augmentation options above --early-stopping in that reference (--loss, --label-noise-rate, --focal-gamma/--focal-neg-gamma, --scheduler*, --warmup-epochs, --weight-decay, --max-grad-norm, every --augment-* flag, --label-smoothing, --mixed-precision) and --motif/--motif-offset from provenance -- both decorators (training_hyperparams, model_provenance) are shared between train and optimize. --selection-metric accepts the same names as model train's --checkpoint-metric (see the "Output files" note under model train above: auto/val_acc/val_f1/val_auc, plus the parametric tpr_at_fpr:<f> and callable_at_precision:<p>). --feature-channels is also passed through unchanged to every grid point -- see model train's flag of the same name.
Output:
grid_summary.csv-- results for all grid pointsbest_params.json-- best configuration (for use withmodel train --model-config)- Per-grid-point subdirectories with model checkpoints
Examples:
# Coarse symmetric grid
leech model optimize \
--train-data chunks/train.npz --val-data chunks/val.npz \
--context-grid 200,500,1000,2000,5000 \
--output-dir grid_results/
# Asymmetric fine grid with range syntax
leech model optimize \
--train-data chunks/train.npz --val-data chunks/val.npz \
--left-contexts 8000:10000:500 \
--right-contexts 0:2000:500 \
--output-dir grid_results/
# With dwell offset search, parallel on CPU
leech model optimize \
--train-data chunks/train.npz --val-data chunks/val.npz \
--context-grid 500,1000 \
--dwell-offsets -5:5:1 \
--device cpu --parallel 8 \
--output-dir grid_results/
leech model benchmark¶
Time one training step end to end -- per-phase timing plus GPU utilization -- to find out which phase is the bottleneck before changing anything.
| Option | Default | Description |
|---|---|---|
--train-data PATH |
(required) | Training dataset (.npz or JSON chunks config) to benchmark against |
-o, --output-dir DIR |
(required) | Directory for benchmark.json (and trace.json with --trace) |
--model STR |
ConvLSTMDwell |
Architecture to benchmark |
--batch-size INT |
128 |
Batch size |
--device STR |
cuda |
cuda or cpu |
--num-steps INT |
100 |
Timed training steps |
--warmup-steps INT |
10 |
Warmup steps (cuDNN benchmark, torch.compile, prefetch queue) |
--num-workers INT |
8 |
DataLoader workers (0 disables multiprocessing) |
--prefetch-factor INT |
4 |
DataLoader prefetch_factor |
--mixed-precision / --no-mixed-precision |
enabled | torch.amp autocast + GradScaler |
--non-blocking / --blocking |
blocking | .to(device, non_blocking=True) for host-to-device copies |
--signal-len INT |
400 |
Signal length |
--kmer-len INT |
11 |
K-mer length |
--seq-encoding STR |
signal_kmer |
base_onehot or signal_kmer |
--signal-mode STR |
both |
both, residual, or signal |
--trace / --no-trace |
off | Also collect a torch.profiler Chrome trace |
--trace-active-steps INT |
10 |
Active steps captured in the trace |
--model-config FILE |
-- | JSON file with model architecture overrides, same as model train |
--standardize-features / --no-standardize-features |
disabled | Compute per-channel feature mean/std over --train-data and freeze them into the feature branch, same as model train's flag |
Example:
leech model benchmark \
--train-data chunks/train.npz \
--model TCNDwellResidual \
--num-workers 8 \
-o bench/
leech model bundle¶
Package multiple trained pairwise models into a single versioned bundle file for deployment.
| Option | Default | Description |
|---|---|---|
--model-dir DIR |
(required) | Root directory containing pair subdirectories (each with model_best.pt + config.json) |
-o, --output FILE |
(required) | Output .pt bundle file |
-v, --version STR |
(required) | Semantic version (e.g., "0.1.0-alpha.1") |
--comparison-type STR |
pairwise |
pairwise, one_vs_all, group, or multiclass |
The command auto-discovers all subdirectories containing model_best.pt and config.json.
Example:
# Bundle all pairwise models from a training run
leech model bundle \
--model-dir results/models/pairwise/ \
--output bundles/aa_classifier_v0.1.0.pt \
--version 0.1.0
# One-vs-all bundle
leech model bundle \
--model-dir results/models/one_vs_all/ \
--output bundles/ova_v0.1.0.pt \
--version 0.1.0 \
--comparison-type one_vs_all
leech model bundle-info¶
Display metadata from a model bundle.
Shows architecture, version, comparison type, included pairs, and file size.
leech model calibrate¶
Learn post-hoc calibration on the validation set. Binary models use Platt scaling (fits a, b so sigmoid(a*logit + b) is better calibrated). Multiclass models use temperature (default), matrix, or Dirichlet scaling.
| Option | Default | Description |
|---|---|---|
--model-dir DIR |
(required) | Model directory or parent with pair subdirs |
--val-data FILE |
(required) | Validation data (.npz) |
--device STR |
cpu |
Device for inference |
--batch-size INT |
1024 |
Batch size for validation pass |
--num-workers INT |
0 |
DataLoader workers |
--method STR |
temperature |
Multiclass method: temperature, matrix, or dirichlet (binary always uses Platt) |
--reg-lambda FLOAT |
0.01 |
L2 regularization toward identity for matrix/dirichlet |
-o, --output FILE |
-- | Output calibration JSON path (single model only) |
Binary models write platt.json; multiclass models write calibration.json to the model directory. For a parent directory with pair subdirs, calibrates each pair independently.
Examples:
# Calibrate a single model
leech model calibrate --model-dir models/Ala_vs_Gly/ --val-data val.npz
# Calibrate all pairs in a directory
leech model calibrate --model-dir models/one_vs_all/ --val-data val.npz
leech model export¶
Export a trained model as a standalone .pt file, loadable with torch.export.load() without the leech codebase.
| Option | Default | Description |
|---|---|---|
--model-dir DIR |
(required) | Model checkpoint directory |
-o, --output FILE |
(required) | Output .pt file |
Example:
leech model release¶
Publish a model bundle to GitHub Releases from a YAML spec. Requires the GitHub CLI (gh),
authenticated (gh auth login) unless --dry-run.
| Option | Default | Description |
|---|---|---|
--spec FILE |
(required) | YAML release spec |
--dry-run |
off | Render the release notes and preview without publishing |
--overwrite |
off | Delete an existing release with the same tag first |
--repo STR |
origin |
GitHub repo as owner/repo |
The spec requires name, version, description and bundle (path to the .pt), and accepts
organism, experiment, data_version, training, metrics, prerelease and extra_assets.
The release tag is model-{name}-v{version}.
Example:
leech model list¶
Browse published model releases on GitHub.
| Option | Default | Description |
|---|---|---|
--repo STR |
origin |
GitHub repo as owner/repo |
--limit INT |
30 |
Maximum releases to show |
leech model fetch¶
Download a published bundle from GitHub Releases.
| Option | Default | Description |
|---|---|---|
--name STR |
-- | Model release name (e.g. ivc-pairwise-v1) |
--version STR |
-- | Model version |
--tag STR |
-- | Full release tag, as an alternative to --name + --version |
-o, --output-dir DIR |
. |
Directory to download into |
--repo STR |
origin |
GitHub repo as owner/repo |
Example:
Evaluation commands¶
leech eval test¶
Evaluate a trained model on a held-out test set.
| Option | Default | Description |
|---|---|---|
--model FILE |
(required) | Trained model checkpoint (.pt) |
--test-data FILES |
(required) | Test data JSON files |
--output FILE |
(required) | Output metrics JSON |
--device STR |
auto | cuda or cpu |
--num-workers INT |
0 (auto) |
DataLoader workers; auto is 8 on GPU (capped by the job's CPU allocation) and 0 on CPU |
--emit-scores PATH |
(off) | Also write per-chunk read_ids, labels, probs to an .npz |
Why --num-workers matters on a GPU
Collate, the host-to-device copy and the forward pass all run in whichever process owns the loader. With no workers that is one core feeding an accelerator that then waits: a 7.8M-chunk test set evaluated at 8% GPU on an A5000, while training the same corpus on the same card ran at 98%. The default now resolves to workers on CUDA, capped by the CPUs the job is actually allowed to use, so a 2-core allocation gets 1 worker rather than 8 thrashing ones. Raise it when the eval job has cores to spare.
Why --emit-scores exists
The metrics JSON is a summary at one threshold. The per-chunk scores behind it are computed either way; this flag only decides whether they survive the run. Without them, AUPRC for the minority class, per-group error breakdowns (per barcode, per isotype), paired model comparison (McNemar), calibration, and any operating point other than the reported one cannot be answered without re-running inference.
The join to read ids is positional and is verified: if the test .npz holds
a different number of read_ids than there are scored chunks, it raises
rather than writing a well-formed file with every score attached to the
wrong read. Multiclass emits the full (N, C) softmax.
The metrics JSON also carries a threshold_sweep with the best operating
points (at_youden, at_mcc, at_f1) alongside prevalence. Prefer
at_youden when the deployment class ratio is unknown or varies per
sample -- it is the only one of the three that is prevalence-invariant.
Inference command¶
leech predict¶
Run inference on new data. Supports single models, single pairs from a bundle, or all-model aggregation from a bundle.
leech predict --pod5 FILE --bam FILE --output FILE (--model DIR | --bundle FILE (--pair NAME | --all)) [OPTIONS]
Required:
| Option | Description |
|---|---|
--pod5 FILE |
POD5 file with raw signal |
--bam FILE |
BAM file with alignments |
-o, --output FILE |
Output BAM with predictions |
Model selection (mutually exclusive):
| Option | Description |
|---|---|
--model DIR |
Single model checkpoint directory |
--bundle FILE |
Model bundle .pt file (requires --pair or --all) |
Bundle options (require --bundle):
| Option | Description |
|---|---|
--pair NAME |
Run a single pair's model from the bundle |
--all |
Run every model in the bundle, aggregate to a single multi-class prediction (e.g., amino acid identity for a one-vs-all bundle) |
--raw |
Write full-float probabilities instead of the compact uint8 encoding (ac/pp tags) |
Signal handling:
| Option | Default | Description |
|---|---|---|
--device STR |
auto | cuda or cpu |
--base-justify STR |
center |
Signal chunk centering |
--no-reverse-signal |
(off) | Disable signal reversal (use for DNA data) |
Confidence and abstention:
| Option | Default | Description |
|---|---|---|
--min-confidence INT |
0 |
Confidence threshold in uint8 space (0-255). Reads below threshold are called unc (uncharged/unclassified). 0 = all reads called |
--min-margin INT |
0 |
Margin threshold in uint8 space (0-255); margin = top probability minus runner-up. Reads below threshold are called unc. 0 = no margin filter |
--abstain-on-junction-indel / --no-abstain-on-junction-indel |
disabled | Multiclass only: enforce --min-margin only on reads whose motif junction is disrupted (unmapped, or a nonzero CIGAR indel), calling them unc when the margin is also below threshold; reads with an intact junction bypass the margin check entirely. junction_indel/junction_mapped are always recorded (BAM ji/jm tags, or a TSV column) so the rule can be re-applied offline |
Backend:
| Option | Default | Description |
|---|---|---|
--backend STR |
auto |
Chunk-extraction backend: auto (Rust when available), rust (force Rust, error if unavailable), python (force Python). Useful for comparing the two paths. |
When auto falls back
Some options cannot be honored by the Rust pipeline -- non-median-MAD
--signal-norm, softclip signal recovery, and a model trained with
--mask-seq-side (focus-relative sequence masking is Python-only) among
them. Under auto those fall back to Python with a warning; under
--backend rust they raise rather than silently producing different
chunks.
Examples:
# Single model inference
leech predict \
--model models/model_best.pt \
--pod5 reads.pod5 --bam alignments.bam \
--output predictions.bam
# Run one pair from a bundle
leech predict \
--bundle bundles/aa_classifier.pt --pair Ala_vs_Gly \
--pod5 reads.pod5 --bam alignments.bam \
--output predictions.bam
# Run all models in bundle (aggregated multi-class prediction)
leech predict \
--bundle bundles/aa_classifier.pt --all \
--pod5 reads.pod5 --bam alignments.bam \
--output predictions.bam
# Same but also write per-pair raw probabilities
leech predict \
--bundle bundles/aa_classifier.pt --all --raw \
--pod5 reads.pod5 --bam alignments.bam \
--output predictions.bam
Typical workflows¶
Single-sample classification¶
# 1. Prepare data
leech data prepare \
--pod5 reads.pod5 --bam alignments.bam \
--output-dir chunks/ --label 1
# 2. Train
leech model train \
--train-data chunks/train.json --val-data chunks/val.json \
--model ConvLSTMDwell --output-dir models/
# 3. Evaluate
leech eval test \
--model models/model_best.pt \
--test-data chunks/test.json --output metrics.json
# 4. Predict
leech predict \
--model models/ \
--pod5 new_reads.pod5 --bam new_alignments.bam \
--output predictions.bam
Multi-sample pairwise comparison¶
# 1. Prepare each sample without splitting
leech data prepare --pod5 ala.pod5 --bam ala.bam --output-dir chunks/ala/ --no-split --label 0
leech data prepare --pod5 gly.pod5 --bam gly.bam --output-dir chunks/gly/ --no-split --label 1
# 2. Merge and split at read level
leech data merge -i Ala=chunks/ala/all.npz -i Gly=chunks/gly/all.npz -o merged/
# 3. Train, test, predict as above
Bundle workflow (multi-pair deployment)¶
# 1. Train pairwise models (one per class pair, e.g. amino acid pair)
leech model train --train-data ala_gly/train.json ... --output-dir models/Ala_vs_Gly/
leech model train --train-data ala_val/train.json ... --output-dir models/Ala_vs_Val/
# ... repeat for each pair
# 2. Bundle all models
leech model bundle \
--model-dir models/ --output bundle.pt --version 1.0.0
# 3. Inspect the bundle
leech model bundle-info --bundle bundle.pt
# 4. Run aggregated inference
leech predict \
--bundle bundle.pt --all \
--pod5 reads.pod5 --bam alignments.bam \
--output predictions.bam
Hyperparameter optimization¶
# 1. Grid search for optimal context
leech model optimize \
--train-data chunks/train.npz --val-data chunks/val.npz \
--context-grid 200,500,1000,2000,5000 \
--output-dir grid_results/
# 2. Train final model with best params
leech model train \
--train-data chunks/train.json --val-data chunks/val.json \
--model ConvLSTMDwell --output-dir models/ \
--model-config grid_results/best_params.json