SIGSEGV in Espresso cast_kernel_cpu / vDSP_vflt32 for transformer text encoders with a dynamic sequence dimension (static shape works)
- Dominant language
- Python
- Stars
- 5.4k
- Forks
- 850
- Avg merge
- 4d 5h
- Merged PRs (30d)
- 10
Description
> **Edited 2026-08-20.** The original description blamed Core ML's CPU cast kernel and listed the CLIP text tower as affected. Both were wrong. The corrected analysis is below, and a self contained reproducer is in [this comment](https://github.com/apple/coremltools/issues/2825#issuecomment-5352186206). The original text is preserved at the bottom for reference.
## Description
A Core ML model that applies a **masked mean-pool over a dynamic sequence dimension** segfaults on the first prediction whenever it actually runs on a **non-CPU** compute unit. The identical package on `CPU_ONLY` returns correct output, and compiling with a static sequence length also works.
Reproduces from plain `coremltools` `predict`, with no ExecuTorch involved.
## What triggers it
Bisecting a sentence-transformers wrapper one piece at a time, over `RangeDim(1, 382)`:
| wrapper content | result |
| --- | --- |
| encoder only (`last_hidden_state`) | OK |
| encoder + CLS/SEP prepend | OK |
| encoder + **masked mean-pool** | **SIGSEGV** |
The mean-pool is the trigger, not the encoder:
```python
mask = attention_mask.unsqueeze(-1).expand(tokens.size()).float()
pooled = torch.sum(tokens * mask, 1) / torch.clamp(mask.sum(1), min=1e-9)
```
A ~20 line standalone model doing only this pattern reports, on a non-CPU path:
```
Espresso exception: "Invalid blob shape": Data-dependent shapes were disabled: var_39 - [?, 768]
```
Core ML infers the pooled output as data dependent `[?, H]` even though the reduction removes the dynamic axis and the true shape is `[1, H]`. In a small graph the backend rejects it and falls back. In a full encoder it segfaults.
## Which compute paths fail
`all-mpnet-base-v2` plus masked mean-pool, `RangeDim(1, 382)`:
| compute_units | precision | result |
| --- | --- | --- |
| `CPU_ONLY` | fp16 | OK |
| `CPU_ONLY` | fp32 | OK |
| `CPU_AND_NE` | fp16 | **SIGSEGV** |
| `CPU_AND_NE` | fp32 | OK, but only because fp32 cannot run on the ANE and falls back to CPU |
| `ALL` | fp16 | **SIGSEGV** |
| `ALL` | fp32 | **SIGSEGV** |
On the CPU path the output is exact against the torch reference: cosine 1.000000, max abs 0.000000 at L = 2, 8, 37, 128, 380.
**Reproducing caveat:** the compiled model cache silently serves an earlier compilation and will fake a pass. Clear it between runs.
## Environment
coremltools 9.0, macOS 15 arm64 (M4), Python 3.10, torch 2.13.0. ExecuTorch 1.4.1 used only to produce the larger reproducer package; the crash itself reproduces without it.
---
Original description (superseded, kept for reference)
The original report attributed the crash to Core ML's CPU cast kernel, based on this stack from the failing `execute()`:
```
libvDSP.dylib vDSP_vflt32
Espresso Espresso::cast_kernel_cpu::__launch(...)
Espresso Espresso::layer::__launch(...)
Espresso Espresso::interpreter_t::__launch_function(...)
Espresso Espresso::net::__forward(...)
Espresso EspressoLight::espresso_plan::dispatch_task_on_compute_batch(...)
Espresso espresso_plan_execute_sync
CoreML -[MLNeuralNetworkEngine executePlan:error:]
```
It reported `all-mpnet-base-v2` and the `openai/clip-vit-base-patch32` text tower as both segfaulting with a dynamic sequence dim and working with a static one, and hypothesised that the cause was integer tensors whose extent depends on the symbolic sequence dim (mpnet's relative position buckets, CLIP's causal mask and argmax gather). That hypothesis was explicitly flagged as unconfirmed, and it did not survive bisection: the encoder including that arithmetic is fine, and the CLIP text tower is not affected at all.
Contributor guide
Research direction
Start with the self-contained reproducer in the linked comment and run it through plain coremltools predict, clearing the compiled model cache between runs. Compare CPU_ONLY with non-CPU compute units for the masked mean-pool over RangeDim(1, 382), and verify that dynamic lengths complete without a SIGSEGV while retaining the correct CPU reference output.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- python, pytorch
- Domain
- machine-learning, tooling
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 48/100