Skip to content

WhisperKit: SuppressTokensFilter writes to read-only MLMultiArray when suppressTokens includes -1 on iOS 26 #392

@7kylor

Description

@7kylor

Bug Report: EXC_BAD_ACCESS crash in SuppressTokensFilter.filterLogits on iOS 26

I'm working on an iOS local AI models app and got this report crash from TestFlight Test it here
Debugged it and reported this for a fix as applicable.

Environment

  • WhisperKit Version: main branch (commit 9c673e3)
  • Device: iPhone 16 Pro (iPhone18,1)
  • iOS Version: 26.2 (23C55)
  • Xcode Version: 17C53

Description

App crashes with EXC_BAD_ACCESS (SIGBUS) when using AudioStreamTranscriber for streaming transcription. The crash occurs in SuppressTokensFilter.filterLogits when attempting to write to a read-only MLMultiArray.

Crash Details

  • Thread 9 (crashed)***
  • Location: MLMultiArray.fill(indexes:with:) at Extensions+Public.swift:126
  • Called from: SuppressTokensFilter.filterLogits(_:withTokens:) at LogitsFilter.swift:23
  • Error: EXC_BAD_ACCESS (SIGBUS) with KERN_PROTECTION_FAILURE - attempting to write to read-only memory
  • Exception Type: EXC_BAD_ACCESS (SIGBUS)
  • Exception Subtype: KERN_PROTECTION_FAILURE at 0x000000012a01fffe
  • Termination Reason: SIGNAL 10 Bus error: 10
  • esr: 0x9200004f (Data Abort) byte write Permission fault

Thread 9 Crashed:
0 MLMultiArray.fill(indexes:with:) + 432 (Extensions+Public.swift:126)
1 SuppressTokensFilter.filterLogits(:withTokens:) + 20 (LogitsFilter.swift:23)
2 protocol witness for LogitsFiltering.filterLogits(:withTokens:) in conformance SuppressTokensFilter
3 TextDecoder.decodeText(from:using:sampler:options:callback:) + 356 (TextDecoder.swift:831)
4 decodeWithFallback in TranscribeTask.run(audioArray:decodeOptions:callback:) (TranscribeTask.swift:338)
5 TranscribeTask.run(audioArray:decodeOptions:callback:) (TranscribeTask.swift:175)
6 AudioStreamTranscriber.transcribeAudioSamples(:) (AudioStreamTranscriber.swift:196)
7 AudioStreamTranscriber.transcribeCurrentBuffer() (AudioStreamTranscriber.swift:159)
8 AudioStreamTranscriber.startStreamTranscription() (AudioStreamTranscriber.swift:98)

Root Cause Analysis

On iOS 26, CoreML appears to return read-only MLMultiArray instances for performance optimization. The SuppressTokensFilter.filterLogits method attempts to modify the logits array in-place using MLMultiArray.fill(indexes:with:), which triggers a memory protection fault.

DecodingOptions Used

DecodingOptions(
    task: .transcribe,
    language: nil,  // auto-detect
    temperature: 0.2,
    temperatureFallbackCount: 3,
    sampleLength: 224,
    usePrefillPrompt: false,
    usePrefillCache: false,
    skipSpecialTokens: true,
    withoutTimestamps: true,
    wordTimestamps: false,
    suppressBlank: true,
    supressTokens: [-1],  // Default suppression - THIS TRIGGERS THE CRASH
    compressionRatioThreshold: 2.4,
    logProbThreshold: -1.0,
    firstTokenLogProbThreshold: -1.5,
    noSpeechThreshold: 0.4
) 

Workaround

Setting supressTokens: [] (empty array) prevents the crash by bypassing the SuppressTokensFilter code path entirely for the time being (let me know if there's a batter way to fix this).

Suggested Fix

The MLMultiArray.fill operation in Extensions+Public.swift should create a mutable copy of the array before attempting to modify it, or check if the array is writable before attempting in-place modification.

`// Potential fix in Extensions+Public.swift
// Before modifying, create a mutable copy if the original is read-only
let mutableLogits = try? MLMultiArray(shape: logits.shape, dataType: logits.dataType)
// Copy data and use mutableLogits instead`

Steps to Reproduce

1- Use AudioStreamTranscriber for streaming transcription on iOS 26 (used it locally Test it here )
2- Set supressTokens: [-1] in DecodingOptions
3- Start streaming transcription
4- App crashes when filterLogits is called

Additional Context

The crash only occurs on iOS 26+ where CoreML returns read-only arrays
Non-streaming transcription may also be affected
The VM Region Info shows the crash address is in a gap between a mapped file (read-only) and a malloc region

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions