Skip to content

SIGSEGV in Espresso cast_kernel_cpu / vDSP_vflt32 for transformer text encoders with a dynamic sequence dimension (static shape works) #2825

Description

@msluszniak

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. 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:

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.

Metadata

Metadata

Assignees

No one assigned

    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