Skip to content

Add an exact structured Viterbi backend for CREPE smoothing#108

Open
ssmall256 wants to merge 1 commit intomarl:masterfrom
ssmall256:pr/structured-viterbi-conservative
Open

Add an exact structured Viterbi backend for CREPE smoothing#108
ssmall256 wants to merge 1 commit intomarl:masterfrom
ssmall256:pr/structured-viterbi-conservative

Conversation

@ssmall256
Copy link
Copy Markdown

Summary

This PR adds an exact structured Viterbi backend to CREPE's optional temporal
smoothing step while preserving the current hmmlearn implementation as the
default.

The patch is intentionally conservative:

  • smoothing-only
  • additive backend selection
  • exact parity coverage
  • decoder benchmark harness

What This Adds

  • crepe.predict(..., viterbi=True, viterbi_impl="legacy")
    • preserves the current hmmlearn path
  • crepe.predict(..., viterbi=True, viterbi_impl="fast")
    • exact structured backend for the existing local transition graph
  • the same viterbi_impl surface for process_file(...)
  • CLI support through --viterbi-impl {legacy,fast}
  • focused parity/routing tests
  • scripts/benchmark_viterbi.py

Why

CREPE's optional smoothing stage already uses a local triangular transition
prior over the 360 pitch states, but the current implementation still routes
through a generic dense hmmlearn.CategoricalHMM decode.

This PR keeps the same transition probabilities, the same emission model, and
the same decoded path, but evaluates the recurrence only over reachable
predecessor states.

This is an exact implementation change, not an approximation.

Benchmark Notes

Local measurements showed a consistent decoder-core win.

Synthetic decoder core:

  • 512 frames:
    • 41.684 ms legacy -> 12.210 ms fast
    • about 3.41x
  • 2048 frames:
    • 162.994 ms legacy -> 48.397 ms fast
    • about 3.37x

Real activation benchmark on the bundled tests/sweep.wav clip using the
tiny model:

  • decoder core (276 frames):
    • 22.372 ms legacy -> 6.445 ms fast
    • about 3.47x
  • full predict(..., viterbi=True):
    • 97.782 ms legacy -> 79.043 ms fast
    • about 1.24x

I am not using those measurements to argue for an immediate default change in
this PR. The goal here is to land the exact fast path explicitly first.

Parity

On the exercised local workloads, parity was exact for:

  • the structured decoder path against a dense reference recurrence
  • the real sweep activation against the current hmmlearn smoother

For the real sweep activation, the decoded cents arrays were exactly equal.

Validation

Commands used locally:

PYTHONPATH=/path/to/crepe pytest -q tests/test_viterbi_impl.py
python -m py_compile \
  crepe/core.py \
  crepe/cli.py \
  crepe/__init__.py \
  tests/test_viterbi_impl.py \
  scripts/benchmark_viterbi.py
PYTHONPATH=/path/to/crepe python scripts/benchmark_viterbi.py \
  --frames 512 2048 \
  --warmup 1 \
  --repeats 3 \
  --include-sweep \
  --model-capacity tiny \
  --verbose 0

Reviewer Notes

  • current behavior remains available explicitly as viterbi_impl="legacy"
  • the fast path is additive and opt-in as viterbi_impl="fast"
  • the existing viterbi boolean remains unchanged
  • if maintainers prefer, a later follow-up can discuss whether the default
    implementation should move after broader review

Scope

This PR is intentionally limited to the optional smoothing stage. It does not
change the CNN model, training code, or the non-Viterbi local-average decode
path.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant