feat(importers): detect the parakeet.cpp diarization GGUF

The Nemotron-3-Diarization GGUFs are now published in
mudler/parakeet-cpp-gguf as nemotron-3-diarization-<quant>.gguf. The
parakeet-cpp importer did not recognise that name, so a direct import
fell through to another importer.

A direct URL to the file now imports with the diarization usecase. A
repo import still picks ASR weights when the repo also ships the
diarization model, and falls back to the diarization weights only
when there are no others. The docs import the published file instead
of converting the checkpoint.

Assisted-by: Claude:claude-opus-5-5 [Claude Code]
This commit is contained in:
Ettore Di Giacinto committed 2026-09-28 06:57:51 +00:00
1 parent 86565706c2
commit 761505237d
4 files changed
+74 -9

No files matched your search

+28 -3
View File
@@ -108,6 +108,11 @@ func (i *ParakeetCppImporter) Import(details Details) (gallery.ModelConfig, erro
uri := downloader.URI(details.URI)
directGGUF := isParakeetGGUF(filepath.Base(details.URI))
// A speaker diarization GGUF is served by the same backend but answers
// /v1/audio/diarization, not transcription.
if directGGUF && isParakeetDiarGGUF(filepath.Base(details.URI)) {
modelConfig.KnownUsecaseStrings = []string{"diarization"}
}
switch {
case uri.LooksLikeURL() && directGGUF:
// Direct file URL (e.g. .../resolve/main/tdt_ctc-110m-f16.gguf). The
@@ -128,12 +133,23 @@ func (i *ParakeetCppImporter) Import(details Details) (gallery.ModelConfig, erro
// HF repo: collect every parakeet GGUF, pick the preferred quant, and
// nest under parakeet-cpp/models/<name>/ so a multi-quant repo doesn't
// collide on disk.
var ggufFiles []hfapi.ModelFile
// Prefer ASR weights: a repo that also ships the diarization model
// (mudler/parakeet-cpp-gguf) imports as a transcription model, and the
// diarization GGUF is imported by its direct URL. A repo with only
// diarization weights imports as a diarization model.
var ggufFiles, diarFiles []hfapi.ModelFile
for _, f := range details.HuggingFace.Files {
if isParakeetGGUF(filepath.Base(f.Path)) {
switch base := filepath.Base(f.Path); {
case isParakeetDiarGGUF(base):
diarFiles = append(diarFiles, f)
case isParakeetGGUF(base):
ggufFiles = append(ggufFiles, f)
}
}
if len(ggufFiles) == 0 && len(diarFiles) > 0 {
ggufFiles = diarFiles
modelConfig.KnownUsecaseStrings = []string{"diarization"}
}
if chosen, ok := pickPreferredGGMLFile(ggufFiles, quants); ok {
target := filepath.Join("parakeet-cpp", "models", name, filepath.Base(chosen.Path))
cfg.Files = append(cfg.Files, gallery.File{
@@ -176,5 +192,14 @@ func isParakeetGGUF(name string) bool {
return true
}
}
return false
return isParakeetDiarGGUF(name)
}
// isParakeetDiarGGUF reports whether name is the parakeet.cpp speaker
// diarization GGUF (nemotron-3-diarization-<quant>.gguf). Matched by its
// published name only, so diarization weights for other backends are not
// claimed.
func isParakeetDiarGGUF(name string) bool {
lower := strings.ToLower(name)
return strings.HasSuffix(lower, ".gguf") && strings.Contains(lower, "nemotron-3-diarization")
}
@@ -50,6 +50,11 @@ var _ = Describe("ParakeetCppImporter", func() {
Expect(imp.Match(d)).To(BeTrue())
})
It("matches a direct URL to the diarization GGUF", func() {
d := parakeetDetails("https://huggingface.co/mudler/parakeet-cpp-gguf/resolve/main/nemotron-3-diarization-q8_0.gguf", `{}`)
Expect(imp.Match(d)).To(BeTrue())
})
It("does NOT claim a generic llama-style GGUF", func() {
d := parakeetDetails("huggingface://someorg/some-llm-gguf", `{}`,
hfapi.ModelFile{Path: "llama-3-8b-instruct-q4_k_m.gguf"},
@@ -66,6 +71,43 @@ var _ = Describe("ParakeetCppImporter", func() {
})
Context("import (Import)", func() {
It("imports the diarization GGUF as a diarization model", func() {
d := parakeetDetails("https://huggingface.co/mudler/parakeet-cpp-gguf/resolve/main/nemotron-3-diarization-q8_0.gguf",
`{"name":"nemotron-diarization"}`)
cfg, err := imp.Import(d)
Expect(err).ToNot(HaveOccurred())
Expect(cfg.ConfigFile).To(ContainSubstring("backend: parakeet-cpp"))
Expect(cfg.ConfigFile).To(ContainSubstring("diarization"))
Expect(cfg.ConfigFile).ToNot(ContainSubstring("transcript"))
Expect(cfg.Files).To(HaveLen(1))
Expect(cfg.Files[0].Filename).To(HaveSuffix("nemotron-3-diarization-q8_0.gguf"))
})
It("keeps picking ASR weights from a repo that also ships the diarization model", func() {
d := parakeetDetails("huggingface://mudler/parakeet-cpp-gguf", `{"name":"parakeet-110m"}`,
hfapi.ModelFile{Path: "nemotron-3-diarization-f16.gguf", URL: "https://hf/diar-f16", SHA256: "ddd"},
hfapi.ModelFile{Path: "tdt_ctc-110m-f16.gguf", URL: "https://hf/f16", SHA256: "aaa"},
hfapi.ModelFile{Path: "nemotron-3-diarization-q8_0.gguf", URL: "https://hf/diar-q8", SHA256: "eee"},
)
cfg, err := imp.Import(d)
Expect(err).ToNot(HaveOccurred())
Expect(cfg.Files).To(HaveLen(1))
Expect(cfg.Files[0].URI).To(Equal("https://hf/f16"))
Expect(cfg.ConfigFile).To(ContainSubstring("transcript"))
})
It("imports a diarization-only repo as a diarization model", func() {
d := parakeetDetails("huggingface://someone/diar-gguf", `{"name":"diar"}`,
hfapi.ModelFile{Path: "nemotron-3-diarization-f16.gguf", URL: "https://hf/diar-f16", SHA256: "ddd"},
hfapi.ModelFile{Path: "nemotron-3-diarization-q8_0.gguf", URL: "https://hf/diar-q8", SHA256: "eee"},
)
cfg, err := imp.Import(d)
Expect(err).ToNot(HaveOccurred())
Expect(cfg.Files).To(HaveLen(1))
Expect(cfg.Files[0].URI).To(Equal("https://hf/diar-q8"), "default quant ladder picks q8_0 before f16")
Expect(cfg.ConfigFile).To(ContainSubstring("diarization"))
})
It("picks the default quant (q4_k) from a multi-quant HF repo", func() {
d := parakeetDetails("huggingface://mudler/parakeet-cpp-gguf", `{"name":"parakeet-110m"}`,
hfapi.ModelFile{Path: "tdt_ctc-110m-f16.gguf", URL: "https://hf/f16", SHA256: "aaa"},
+3 -5
View File
@@ -160,15 +160,13 @@ curl http://localhost:8080/v1/audio/diarization \
## Backend setup - parakeet.cpp (Nemotron-3-Diarization)
The [parakeet-cpp backend]({{%relref "features/audio-to-text#using-the-parakeet-cpp-backend" %}}) runs [nvidia/Nemotron-3-Diarization](https://huggingface.co/nvidia/Nemotron-3-Diarization). Convert the checkpoint to GGUF with parakeet.cpp's converter (Q8_0 is 109 MB and gives the same segments as F32):
The [parakeet-cpp backend]({{%relref "features/audio-to-text#using-the-parakeet-cpp-backend" %}}) runs [nvidia/Nemotron-3-Diarization](https://huggingface.co/nvidia/Nemotron-3-Diarization). GGUF weights are published in [`mudler/parakeet-cpp-gguf`](https://huggingface.co/mudler/parakeet-cpp-gguf): F16 (191 MB) and Q8_0 (104 MB) both give the same segments as NeMo. Import one directly:
```bash
python scripts/convert_parakeet_to_gguf.py \
--model nvidia/Nemotron-3-Diarization --dtype q8_0 \
--output nemotron-3-diarization-q8_0.gguf
local-ai models import https://huggingface.co/mudler/parakeet-cpp-gguf/resolve/main/nemotron-3-diarization-q8_0.gguf
```
A diarization-only model:
Or configure it by hand, as a diarization-only model:
```yaml
name: nemotron-diarization
+1 -1
View File
@@ -207,7 +207,7 @@ options:
### Speaker labels
Attach a speaker diarization model with the `diar_model` option and each transcript segment carries a `speaker` (`"0"`, `"1"`, ... in order of first appearance). Segments split wherever the speaker changes, on top of the punctuation split. The model is NVIDIA Nemotron-3-Diarization, converted with parakeet.cpp's converter; see [Speaker Diarization]({{< relref "audio-diarization.md" >}}) for the conversion and for the `/v1/audio/diarization` endpoint the same model serves.
Attach a speaker diarization model with the `diar_model` option and each transcript segment carries a `speaker` (`"0"`, `"1"`, ... in order of first appearance). Segments split wherever the speaker changes, on top of the punctuation split. The model is NVIDIA Nemotron-3-Diarization, published as `nemotron-3-diarization-f16.gguf` and `nemotron-3-diarization-q8_0.gguf` in [`mudler/parakeet-cpp-gguf`](https://huggingface.co/mudler/parakeet-cpp-gguf); see [Speaker Diarization]({{< relref "audio-diarization.md" >}}) for the conversion and for the `/v1/audio/diarization` endpoint the same model serves.
```yaml
name: parakeet-speakers