CTC keyword spotter hears silence: MelSpectrogram on the Neural Engine yields all-blank CTC frames
Maintainer thường phản hồi trong vòng 1 ngày
Một pull request liên quan đã được merge.
- #1079 của @Alex-Wengg — đã merge
Đánh giá
- Độ khó
- 2/5
- Thời gian dự kiến
- 1-3 giờ
- Mức phù hợp với người mới
- 78/100
- Loại issue
- Lỗi
- Độ rõ ràng
- Đặc tả rõ ràng
- Mức độ hoạt động
- Sôi nổi
- Công nghệ
- swift
- Lĩnh vực
- audio-video-rtc, backend, machine-learning
Hướng nghiên cứu
Đọc CtcModels.load và loadDirect, các hàm cấu hình đơn vị tính toán của mô hình CTC. Bản tái hiện trong issue cho thấy MelSpectrogram hoạt động trên CPU, còn encoder hoạt động trên Neural Engine; hãy kiểm tra cách cả hai loader thiết lập những cấu hình đó. Công việc hoàn tất khi MelSpectrogram sử dụng CPU, encoder vẫn hỗ trợ Neural Engine và một đoạn âm thanh ngắn có lời nói tạo ra đầu ra CTC không rỗng.
Do mô hình lập chỉ mục viết ra từ nội dung của issue.
Mô tả
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.
- Ngôn ngữ chính
- Swift
- Star
- 3k
- Fork
- 451
- Merge trung bình
- 1 ngày 15 giờ
- Pull request đã merge (30 ngày)
- 64
Chuẩn bị môi trường
Dự án này không cung cấp dev container, Dockerfile hay hướng dẫn đóng góp, nên bạn cần tự thiết lập môi trường: hãy bắt đầu từ README và xem hướng dẫn đóng góp lần đầu của chúng tôi để biết các bước chung.
Bắt đầu từ đâu
- Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
- Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
- Fork repository và làm thay đổi trên một nhánh.
- Mở pull request có tham chiếu số hiệu của issue.
Issue khác của FluidInference/FluidAudio
-
Offline diarizer: community preset drops overlapped speech by default (exclusiveSegments: true), +7 DER on AliMeetingCó thể đã có người làm @Alex-Wengg đã nhận 1 ngày trước. Đang mở
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 62/100
FluidInference/FluidAudio#1049 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 72/100
FluidInference/FluidAudio#1085 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 4/5 3-5 ngày Mức phù hợp với người mới 35/100
FluidInference/FluidAudio#1084 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 3/5 1-2 ngày Mức phù hợp với người mới 68/100
FluidInference/FluidAudio#1067 ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 5/5 Hơn một tuần Mức phù hợp với người mới 38/100
FluidInference/FluidAudio#1063 ·
Maintainer thường phản hồi trong vòng 1 ngày
Tất cả issue của FluidInference/FluidAudio
Issue tương tự
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 72/100
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 62/100
-
area: agents
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 75/100
manaflow-ai/cmux#29762 · 1 bình luận ·
Maintainer thường phản hồi trong vòng 1 ngày
-
Độ khó 2/5 1-3 giờ Mức phù hợp với người mới 62/100
Maintainer thường phản hồi trong vòng 1 ngày