Replace NC MuQ/Essentia with CLAP + Short-chunk CNN - #1
Merged
Merged
Conversation
…unk CNN. Drops CC BY-NC weights and AGPL essentia-tensorflow so the ML worker can ship in a commercial product, while versioning new Qdrant collections for clean rollback. Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Pull request overview
This PR migrates the ML worker’s audio embedding and rich-tagging pipeline away from non-commercial / copyleft dependencies (MuQ weights + Essentia/TF) to an Apache-licensed CLAP embedder and an MIT-licensed Short-chunk CNN tagger, while versioning Qdrant collections to prevent mixing vectors across “tagger eras” and keeping rollback collections intact.
Changes:
- Replace MuQ audio embeddings with CLAP (
laion/larger_clap_music) and enforce 512-d invariants at load/runtime. - Replace Essentia Discogs/Jamendo tagging with a vendored Short-chunk CNN (Jamendo top-50) + downloadable weights.
- Version profile Qdrant collections by language model + tagger identity; adjust dev Compose env/volumes to preserve built venvs and avoid inheriting host-local URLs.
Reviewed changes
Copilot reviewed 24 out of 26 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| compose.yaml | Updates service comment to reflect CLAP owning the GPU. |
| compose.dev.yaml | Hard-codes in-compose service URLs and adds a persistent ml-worker venv volume. |
| apps/ml-worker/uv.lock | Removes Essentia/MuQ-related dependencies; ensures torchaudio is present. |
| apps/ml-worker/src/ox1audio_ml_worker/vector/store.py | Versions profile collection names by language model + tagger to avoid mixing eras. |
| apps/ml-worker/src/ox1audio_ml_worker/embedders/provider.py | Implements CLAP text/audio embedding + collection-dimension guards; renames device helpers. |
| apps/ml-worker/src/ox1audio_ml_worker/config.py | Replaces MuQ/Essentia settings with CLAP model id + tagger weights path. |
| apps/ml-worker/src/ox1audio_ml_worker/audio/tagging.py | Adds Short-chunk CNN-based rich tagging implementation and provider identity constant. |
| apps/ml-worker/src/ox1audio_ml_worker/audio/short_chunk/tags.py | Adds Jamendo top-50 tag list required by the checkpoint head. |
| apps/ml-worker/src/ox1audio_ml_worker/audio/short_chunk/model.py | Vendors the Short-chunk CNN + residual model definition. |
| apps/ml-worker/src/ox1audio_ml_worker/audio/short_chunk/LICENSE | Adds upstream MIT license text for vendored code. |
| apps/ml-worker/src/ox1audio_ml_worker/audio/short_chunk/init.py | Exposes the vendored model + tag list as a small internal package. |
| apps/ml-worker/src/ox1audio_ml_worker/audio/essentia.py | Removes Essentia-based tagging/embedding implementation. |
| apps/ml-worker/src/ox1audio_ml_worker/audio/analysis.py | Switches pipeline to 48 kHz for CLAP clips and routes rich tagging to Short-chunk CNN. |
| apps/ml-worker/scripts/download_short_chunk_model.py | Adds downloader for Short-chunk CNN weights. |
| apps/ml-worker/scripts/download_essentia_models.py | Removes Essentia model downloader. |
| apps/ml-worker/README.md | Updates architecture docs, licensing table, and setup steps for CLAP + Short-chunk CNN. |
| apps/ml-worker/pyproject.toml | Drops MuQ/Essentia deps; adds torchaudio dependency. |
| apps/ml-worker/evals/score_golden.py | Adds script to score golden queries against a running ML worker. |
| apps/ml-worker/evals/results_before.json | Records baseline eval outputs (MuQ-era). |
| apps/ml-worker/evals/results_after.json | Records eval outputs after CLAP migration. |
| apps/ml-worker/evals/relevant_calm_ambient_muq.json | Adds frozen relevance set for before/after comparability. |
| apps/ml-worker/evals/golden_queries.json | Expands golden query metadata to include frozen relevance ids/tags. |
| apps/ml-worker/Dockerfile | Reuses CUDA base torch/torchaudio and verifies imports during image build. |
| apps/ml-worker/config.yaml | Switches default model config to CLAP and documents Short-chunk weights path. |
| apps/backend/scripts/requeue_analysis.py | Adds helper script to enqueue a full reindex across READY tracks. |
| .gitignore | Ignores .pth weights artifacts and local backups/ snapshot directory. |
Comments suppressed due to low confidence (1)
apps/ml-worker/src/ox1audio_ml_worker/audio/tagging.py:170
- In
predict_chunks(), the hop size is divided byn_chunks, which makes chunk start positions much more clustered toward the beginning than intended (and can produce nearly-duplicate windows for longer audio). To spreadn_chunkswindows across the available range (matching the clip selection logic elsewhere), the denominator should be(n_chunks - 1)so the last window can reach the end of the signal.
length = int(audio.size)
n_chunks = min(MAX_CHUNKS, max(1, length // CHUNK_SAMPLES))
if length == CHUNK_SAMPLES or n_chunks == 1:
batch = audio[:CHUNK_SAMPLES][None, :]
else:
hop = max(1, (length - CHUNK_SAMPLES) // n_chunks)
rows = [
audio[i * hop : i * hop + CHUNK_SAMPLES]
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+146
to
+148
| model = ShortChunkCNN_Res(n_class=len(TAGS)) | ||
| state = torch.load(weights, map_location="cpu", weights_only=False) | ||
| if "spec.mel_scale.fb" in state: |
Comment on lines
+65
to
+72
| segment_results: list[dict[str, Any]] = [] | ||
| for segment in segments: | ||
| start_s = max(float(segment["start_s"]), 0.0) | ||
| end_s = min(float(segment["end_s"]), duration) | ||
| start = int(start_s * TAGGER_SAMPLE_RATE) | ||
| end = max(start + 1, int(end_s * TAGGER_SAMPLE_RATE)) | ||
| window = audio_16k[start:end] | ||
| segment_pred = predict_chunks(window) |
Comment on lines
+21
to
+36
| def download(url: str, destination: Path) -> None: | ||
| if destination.exists() and destination.stat().st_size > 0: | ||
| print(f"exists {destination} ({destination.stat().st_size} bytes)") | ||
| return | ||
| destination.parent.mkdir(parents=True, exist_ok=True) | ||
| temp_path = destination.with_suffix(f"{destination.suffix}.tmp") | ||
| print(f"download {url}") | ||
| urllib.request.urlretrieve(url, temp_path) | ||
| if temp_path.stat().st_size < 1_000_000: | ||
| temp_path.unlink(missing_ok=True) | ||
| raise RuntimeError( | ||
| f"Downloaded file looks too small ({temp_path}); " | ||
| "Git LFS may not have resolved. Try cloning the upstream repo with git-lfs." | ||
| ) | ||
| temp_path.replace(destination) | ||
| print(f"wrote {destination} ({destination.stat().st_size} bytes)") |
…inks. Recover M2M neighborhoods with music_and_speech CLAP plus mean-pooled text features, and let the player jump to a track’s seed graph with marquee titles. Co-authored-by: Cursor <cursoragent@cursor.com>
Hide the detail card on small screens, move seed/blend into a sheet, and stop routing iPhone audio through AudioContext so background play survives. Co-authored-by: Cursor <cursoragent@cursor.com>
One shared highlight eases between rows instead of each item flashing its own background, including cmdk-selected chat history items. Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
OpenMuQ/MuQ-MuLan-large(CC BY-NC weights) forlaion/larger_clap_music(Apache 2.0, 512-d, 48 kHz) — no new packages beyond existingtransformers.essentia-tensorflow(AGPL) with MIT Short-chunk CNN (Jamendo top-50), vendored model definition + downloadable weights.Eval (Precision@6 / Recall@6 / MRR)
Relevance for
calm-ambientfrozen from MuQ-era collection tags (777 tracks). Catalog has 1300 ready tracks.calm-ambientcalm-ambientcalm-ambientabsent-concept(opera aria, expect_none)absent-conceptNotes:
expect_nonealready failed under MuQ (standout filter still returns 6 tracks). CLAP is not worse on this probe, but also not better — watch false positives; opera FPs after reindex are emotional indie/popfolk, not classical opera.Ambient / Popfolk / Popdominate. Escalation path (not in this PR): train a head on CLAP embeddings.Artifacts:
apps/ml-worker/evals/results_{before,after}.json.Collections after reindex
ox1audio_laion-clap-music_laion_larger_clap_music…MiniLM…__short-chunk-cnn-jamendo-top50ox1audio_muq-mulan-large_OpenMuQ_MuQ-MuLan-large…MiniLM-L6-v2(old profile)Qdrant volume snapshot / restore
Snapshot taken before reindex:
Archive:
backups/qdrant_data_premigration.tar.gz(~60MB, non-empty). Do not delete the original volume.Restore:
Code rollback: revert this branch; old MuQ collections remain.
Reindex
Test plan
projection_dim == 512(refused to proceed otherwise)Made with Cursor