CTC keyword spotter hears silence: MelSpectrogram on the Neural Engine yields all-blank CTC frames
I maintainer di solito rispondono entro 1 giorno
Nessuno ha ancora preso questa issue.
Valutazione
- Difficoltà
- 2/5
- Tempo stimato
- 1-3 ore
- Idoneità per principianti
- 78/100
- Tipo di issue
- Bug
- Chiarezza
- Specificata chiaramente
- Stato di attività
- Attiva
- Stack tecnologico
- swift
- Ambito
- audio-video-rtc, backend, machine-learning
Direzione di ricerca
Leggi CtcModels.load e loadDirect, che configurano le unità di calcolo dei modelli CTC. La riproduzione nell’issue mostra che MelSpectrogram funziona sulla CPU, mentre l’encoder funziona sulla Neural Engine; controlla come entrambi i loader impostano queste configurazioni. Il lavoro è terminato quando MelSpectrogram usa la CPU, l’encoder mantiene il supporto per la Neural Engine e una breve clip vocale produce un output CTC non vuoto.
Scritto dal modello di indicizzazione a partire dal testo della issue.
Descrizione
Summary
CtcModels.defaultConfiguration() loads both CTC models with .cpuAndNeuralEngine. On the Neural Engine, the CTC-110M MelSpectrogram produces features that make the encoder emit blank for every frame, whatever the audio. VocabularyBoostingSession keeps working without error, but its CTC evidence no longer depends on the audio, so rescoring decisions come down to string similarity and the boost.
Running only the MelSpectrogram on the CPU fixes it, and is as fast as or faster than the default. The encoder is fine on the Neural Engine.
This is separate from #991. The input here is explicitly zero-padded, and #991's checks used .all and .cpuOnly, which both work. Only the default, .cpuAndNeuralEngine, fails.
Environment
- FluidAudio 0.18.0 (
e152879) parakeet-ctc-110m-coremlfrom the default download- macOS 27.0.1 (26A434), on Apple M1 Pro and Apple M4 Max
Reproduction
Clip: say -o clip.wav --data-format=LEF32@16000 "We deploy the app to Vercel, and the worker runs on Cloudflare."
The script below runs the two models directly with CoreML on the zero-padded 240,000-sample window and greedy-decodes ctc_head_raw_output. Output, identical on both Macs:
mel ANE, encoder ANE: (all blank)
mel ANE, encoder CPU: (all blank)
mel CPU, encoder ANE: We deploy the Aversal and the worker runs on Cloudfler
all, all: We deploy the Aversal and the worker runs on Cloudflure
Warm time on the M4 Max: mel CPU with encoder ANE 11 ms, default 13 ms, .all 18 ms, all CPU 39 ms.
Through FluidAudio, CtcKeywordSpotter.spotKeywordsWithLogProbs with the default CtcModels gave blank as the argmax of every frame on all 20 of our test clips. With the same CtcModels except a CPU MelSpectrogram, the log-probs decode to the speech.
repro.swift
// swiftc -O repro.swift -o repro && ./repro ~/Library/Application\ Support/FluidAudio/Models/parakeet-ctc-110m-coreml clip.wav
import AVFoundation
import CoreML
let dir = URL(fileURLWithPath: CommandLine.arguments[1])
let file = try AVAudioFile(forReading: URL(fileURLWithPath: CommandLine.arguments[2])) // 16 kHz mono Float32
let buffer = AVAudioPCMBuffer(pcmFormat: file.processingFormat, frameCapacity: AVAudioFrameCount(file.length))!
try file.read(into: buffer)
let audio = Array(UnsafeBufferPointer(start: buffer.floatChannelData![0], count: Int(buffer.frameLength)))
let vocab = try JSONSerialization.jsonObject(with: Data(contentsOf: dir.appendingPathComponent("vocab.json"))) as! [String: Any]
let input = try MLMultiArray(shape: [240_000], dataType: .float16)
for i in 0..<240_000 { input[i] = NSNumber(value: i < audio.count ? audio[i] : 0) } // explicitly zero-padded
let flag = try MLMultiArray(shape: [1, 1, 1, 1], dataType: .float16)
flag[0] = 1
func load(_ name: String, _ units: MLComputeUnits) throws -> MLModel {
let config = MLModelConfiguration()
config.computeUnits = units
return try MLModel(contentsOf: dir.appendingPathComponent(name), configuration: config)
}
for (label, melUnits, encoderUnits) in [("mel ANE, encoder ANE", MLComputeUnits.cpuAndNeuralEngine, MLComputeUnits.cpuAndNeuralEngine),
("mel ANE, encoder CPU", .cpuAndNeuralEngine, .cpuOnly),
("mel CPU, encoder ANE", .cpuOnly, .cpuAndNeuralEngine),
("all, all", .all, .all)] {
let mel = try load("MelSpectrogram.mlmodelc", melUnits)
let encoder = try load("AudioEncoder.mlmodelc", encoderUnits)
let features = try mel.prediction(from: MLDictionaryFeatureProvider(dictionary: ["audio": input]))
.featureValue(for: "melspectrogram_features")!.multiArrayValue!
let logits = try encoder.prediction(from: MLDictionaryFeatureProvider(dictionary: ["melspectrogram_features": features, "input_1": flag]))
.featureValue(for: "ctc_head_raw_output")!.multiArrayValue! // [1, 1025, 1, 188]
var text = "", last = -1
for t in 0..<min(188, audio.count * 188 / 240_000 + 1) {
let best = (0..<1025).max { logits[[0, $0, 0, t] as [NSNumber]].floatValue < logits[[0, $1, 0, t] as [NSNumber]].floatValue }!
if best != last, best != 1024 { text += (vocab[String(best)] as? String) ?? "?" }
last = best
}
print("\(label): \(text.isEmpty ? "(all blank)" : text.replacingOccurrences(of: "▁", with: " "))")
}
Workaround
Build CtcModels with the MelSpectrogram reloaded on the CPU:
let models = try await CtcModels.downloadAndLoad(variant: .ctc110m)
let cpu = MLModelConfiguration()
cpu.computeUnits = .cpuOnly
let mel = try MLModel(contentsOf: CtcModels.defaultCacheDirectory(for: .ctc110m)
.appendingPathComponent(ModelNames.CTC.melSpectrogramPath), configuration: cpu)
let ctcModels = CtcModels(melSpectrogram: mel, encoder: models.encoder, configuration: models.configuration,
vocabulary: models.vocabulary, variant: models.variant)
Suggested fix
Load the MelSpectrogram with .cpuOnly in CtcModels.load and loadDirect, and keep the encoder on .cpuAndNeuralEngine. A test that a short speech clip doesn't decode to all blanks would catch a regression. We have only tested macOS 27.0.1, so it may be worth checking whether earlier macOS versions are affected too.
Because the CTC evidence ignores the audio under the default configuration, this may also explain part of the common-word replacements reported in #967.
- Lingua principale
- Swift
- Stelle
- 3k
- Fork
- 451
- Merge medio
- 1g 4h
- PR unite (30g)
- 60
Preparare l'ambiente
Questo progetto non fornisce container di sviluppo, Dockerfile né guida per i contributori, quindi l'ambiente è a tuo carico: parti dal suo README e consulta la nostra guida al primo contributo per i passaggi generali.
Come iniziare
- Leggi tutta la issue e poi la guida ai contributi del progetto.
- Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
- Fai un fork del repository e lavora su un branch.
- Apri una pull request che faccia riferimento al numero della issue.
Altre issue di FluidInference/FluidAudio
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 62/100
FluidInference/FluidAudio#1049 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 3/5 1-2 giorni Idoneità per principianti 68/100
FluidInference/FluidAudio#1067 ·
I maintainer di solito rispondono entro 1 giorno
-
Streaming ASR: sliding-window updates can't say which seam tokens were retired, and the stable prefix can shrink on a contractionForse già presa @johnny4young l’ha presa 14 giorni fa. Aperta
Difficoltà 5/5 Più di una settimana Idoneità per principianti 1/100
FluidInference/FluidAudio#1065 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 5/5 Più di una settimana Idoneità per principianti 38/100
FluidInference/FluidAudio#1063 ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 4/5 3-5 giorni Idoneità per principianti 55/100
FluidInference/FluidAudio#1059 ·
I maintainer di solito rispondono entro 1 giorno
Tutte le issue di FluidInference/FluidAudio
Issue simili
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 78/100
I maintainer di solito rispondono entro 5 giorni
-
bug mac アプリ: コード アプリ: チャット 優先: 中 要トリアージ
Difficoltà 2/5 1-3 ore Idoneità per principianti 74/100
yksr-melt/Meltype#1346 · 2 commenti ·
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 70/100
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 72/100
I maintainer di solito rispondono entro 1 giorno
-
Difficoltà 2/5 1-3 ore Idoneità per principianti 75/100
vorssaint/vorssaint-utils#3667 ·
I maintainer di solito rispondono entro 1 giorno