Memory leak in deallocator for ANE - #134
Conversation
|
Claude finished @BrandonWeng's task —— View job Reviewing PR: Daealloc error fix ✅Completed comprehensive code review of the memory management fix for CoreML async prediction crashes. Todo List:
Review SummaryThis PR correctly fixes a critical use-after-free bug by changing the deallocator from Key findings:
Recommendation: Ready to merge - this fixes a production crash with no downsides. Full detailed review submitted as GitHub PR review. |
There was a problem hiding this comment.
Code Review: Memory Management Fix for CoreML Crashes
Overview
This PR correctly addresses the use-after-free crash described in the issue. The fix properly pairs posix_memalign with Darwin.free() instead of the incorrect deallocate() method.
Detailed Analysis
Line 3: Darwin import addition ✅
- Good: Adding
import Darwinis necessary for explicit access toDarwin.free() - Impact: Makes the fix more explicit and avoids potential naming conflicts
Lines 70-71: Critical deallocator fix ✅
- Excellent fix: Changed from
bytes.deallocate()toDarwin.free(bytes) - Root cause addressed:
posix_memalignallocates memory that MUST be freed withfree(), not Swift'sdeallocate() - Comment improvement: The added comment clearly explains why this specific deallocator is required
Technical Correctness
- Memory allocation pairing: ✅
posix_memalign→Darwin.free()is the correct pair - Thread safety: ✅ No concurrency issues introduced
- API compatibility: ✅ No breaking changes to public interface
- Error handling: ✅ Existing error handling remains intact
Performance Impact
- Positive: Eliminates crashes during CoreML async prediction
- No regression: Same memory allocation strategy, just correct cleanup
- ANE optimization: Maintains 64-byte alignment benefits
Potential Considerations
- nit: Consider adding a unit test that verifies the deallocator doesn't crash when called multiple times or in edge cases
- nit: The fix could potentially be extracted into a private helper function if this pattern is used elsewhere
Conclusion
This is a critical and correct fix that directly addresses the reported CoreML crash. The change is minimal, targeted, and follows proper memory management practices for POSIX-allocated memory.
Approval recommendation: ✅ Ready to merge - this fixes a production crash with no downsides.
Speaker Diarization Benchmark ResultsSpeaker Diarization PerformanceEvaluating "who spoke when" detection accuracy
Diarization Pipeline Timing BreakdownTime spent in each stage of speaker diarization
Speaker Diarization Research ComparisonResearch baselines typically achieve 18-30% DER on standard datasets
Note: RTFx shown above is from GitHub Actions runner. On Apple Silicon with ANE:
🎯 Speaker Diarization Test • AMI Corpus ES2004a • 1049.0s meeting audio • 44.3s diarization time • Test runtime: 1m 19s • 10/09/2025, 03:20 AM EST |
| deallocator: { bytes in | ||
| bytes.deallocate() | ||
| // `posix_memalign` requires `free` for cleanup; `deallocate()` would trap. | ||
| Darwin.free(bytes) |
There was a problem hiding this comment.
So IIUC - this is "fine" until its not., as we use posix_memalign earlier in the code, we must all Darwin.free since its using a C API
using bytes.deallocate() on memory that was not allocated by Swift is undefined behavior, and UB often looks “fine” until some combination of runtime/allocator/debugging flags makes it blow up.
VAD Benchmark ResultsPerformance Comparison
Dataset Details
✅: Average F1-Score above 70% |
ASR Benchmark Results ✅Status: All benchmarks passed Parakeet v3 (multilingual)
Parakeet v2 (English-optimized)
Streaming (v3)
Streaming (v2)
Streaming tests use 5 files with 0.5s chunks to simulate real-time audio streaming 25 files per dataset • Test runtime: 12m28s • 10/09/2025, 03:29 AM EST RTFx = Real-Time Factor (higher is better) • Calculated as: Total audio duration ÷ Total processing time Expected RTFx Performance on Physical M1 Hardware:• M1 Mac: ~28x (clean), ~25x (other) Testing methodology follows HuggingFace Open ASR Leaderboard |
### Why is this change needed? <!-- Explain the motivation for this change. What problem does it solve? --> From a developer ``` "Thread 19 crashed inside objc_retain while CoreML was trying to access an MLFeatureValue / MLMultiArray during async prediction. The crash is a SIGSEGV / KERN_INVALID_ADDRESS at an address that is not mapped → classic use-after-free / accessing deallocated memory. Load of the backtrace shows CoreML code paths (- [MLFeatureValue multiArrayValue], MLE5InputPortBinder bindMemoryObjectForFeatureValue:, MLE5ExecutionStreamOperation … prepareAsyncSubmissionForInputFeatures:) — i.e. CoreML is preparing inputs for an async execution and tried to retain an Objective-C object that was already freed. So: you passed a feature value / multiarray to CoreML, CoreML used it asynchronously, but the Swift/ObjC object backing that data was deallocated earlier (or concurrently mutated in an unsafe way). That triggered an invalid pointer dereference when CoreML tried to retain/read it." ```
### Why is this change needed? <!-- Explain the motivation for this change. What problem does it solve? --> From a developer ``` "Thread 19 crashed inside objc_retain while CoreML was trying to access an MLFeatureValue / MLMultiArray during async prediction. The crash is a SIGSEGV / KERN_INVALID_ADDRESS at an address that is not mapped → classic use-after-free / accessing deallocated memory. Load of the backtrace shows CoreML code paths (- [MLFeatureValue multiArrayValue], MLE5InputPortBinder bindMemoryObjectForFeatureValue:, MLE5ExecutionStreamOperation … prepareAsyncSubmissionForInputFeatures:) — i.e. CoreML is preparing inputs for an async execution and tried to retain an Objective-C object that was already freed. So: you passed a feature value / multiarray to CoreML, CoreML used it asynchronously, but the Swift/ObjC object backing that data was deallocated earlier (or concurrently mutated in an unsafe way). That triggered an invalid pointer dereference when CoreML tried to retain/read it." ```
Why is this change needed?
From a developer