Skip to content

fix(cpu): apply! is unsafe when input/output are SubArray views #64

Closed as not planned

Description

@kylebeggs

Symptom

A downstream package cannot use MFO's (or KernelAbstractions') CPU kernel path when the state it passes is a SubArray view, because the KA CPU() backend races on views. It works on GPU backends and it works on plain Arrays; it corrupts results on CPU + views.

Why that shape comes up

It is not exotic. A folded steady solve marches a stacked state and hands each species' slice to the diffusion operator as a view — that is the natural, allocation-free way to apply a per-species operator to a multi-species state without copying. Any solver that stacks fields and slices them hits this.

What downstream had to do

The workaround in the microvascular model is an entire file that exists for no other reason:

# CPU uses a plain scalar loop over laplacian_7pt_noflux (view- and thread-safe);
# the GPU path delegates to the KA stencil (identical numerics). The split exists
# because KA's CPU() backend races on the SubArray views the folded steady solve passes.
_tissue_laplacian!(out, u, grid) = _tissue_laplacian!(out, u, grid, get_backend(u))
_tissue_laplacian!(out, u, grid, ::CPU) = # hand-rolled scalar loop
_tissue_laplacian!(out, u, grid, backend) = # delegate to the KA kernel

Two code paths, two sets of numerics to keep in agreement, forever — to work around a backend property. This is the single clearest case of "downstream reimplemented something because MFO's version didn't fit", and it's the one I'd most like to delete.

Ask

Either make the CPU apply! path view-safe, or — if that is genuinely KA's problem rather than MFO's — document the constraint loudly and offer a sanctioned view-safe CPU fallback in MFO so every downstream doesn't hand-roll its own.

Related to #51 (CPU apply! is single-threaded): both are about the CPU path being the odd one out, and a rework that fixes threading is the natural place to fix view-safety too. laplacian_7pt_noflux is already documented as the escape hatch for fused custom kernels over flat state, so the raw ingredient is there — what's missing is a supported loop around it.


🤖 Written by Claude. If this is wrong, blame the robot, not the human whose name is on the repo.

Activity

  1. kylebeggs commented on Sep 9, 2026

    @kylebeggs
    CollaboratorAuthor

    Closing as not-a-bug in MatrixFreeOperators — the race described here cannot occur on MFO's CPU path.

    MFO never launches a KernelAbstractions kernel on a CPU backend. Every KA launch site is gated the same way:

    backend isa KernelAbstractions.GPU || return _forest_sweep_leaves!(y, L, x, g, α, β)   # src/operators/forest_packed.jl
    backend isa KernelAbstractions.GPU || return _run_exchange_host!(x, sched)               # src/transfer_kernels.jl:201
    backend isa KernelAbstractions.GPU || return _run_bc_host!(x, g, sched)                  # src/transfer_kernels.jl:211

    so on CPU every leaf apply! is a plain Base broadcast over interior(g) views, which is view-safe by construction. A Field may wrap a SubArray (the constructor only checks size(data) == padded_size(grid)), and the broadcast does not care.

    The race in the downstream package lives in its own @kernel around laplacian_7pt_noflux, launched on CPU() — that is KernelAbstractions' CPU backend racing on views, which MFO does not use and cannot fix. The two-path workaround there is the right shape for a hand-rolled KA kernel; MFO's sanctioned CPU fallback is simply apply! on a Field.

    The one place this becomes an MFO concern is #51: if CPU threading is done by launching leaf kernels through the KA CPU backend (option 1 there), views must stay safe. That constraint is now recorded in #51's acceptance criteria so it is not lost.


    🤖 Beep boop — Claude checked every launch site so Kyle would not have to argue with his own bug report.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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