Skip to content

[Discussion] Recognize arith/math patterns and lower them to spyreop intrinsics #107

Description

@fabianlim

Summary

ktir-mlir-frontend#57 adds the spyreop dialect — the IBM Spyre intrinsics the
frontend is allowed to emit. Nothing in this repo emits them yet. Triton's frontend
has no vocabulary for a Spyre intrinsic, so a primitive that Spyre implements as one
instruction reaches KTIR as a chain of arith/math ops.

We need a pass that recognizes those chains and lowers them to the matching
spyreop intrinsic.

The canonical case is precision-widened math. tl.rsqrt on f16 arrives as:

%0 = arith.extf   %a : f16 to f32
%1 = math.rsqrt   %0 : f32
%2 = arith.truncf %1 : f32 to f16

The extf/truncf pair is not arithmetic the user asked for — it is the frontend
expressing "compute this at higher precision". spyreop provides sqrt and
reciprocal operating on !spyreop.df16, so if the intrinsic already carries the
needed internal precision, the widening ops are pure overhead: they cost tile memory
for the intermediate f32 values and hide the single instruction the hardware wants.

What #57 gives us

From the PR's tests, the initial op surface:

spyreop.sqrt        spyreop.reciprocal   spyreop.realdiv     spyreop.exp
spyreop.gelu        spyreop.sigmoid      spyreop.softplus    spyreop.layernormscale
spyreop.addi32toi32 spyreop.addi64toi64  spyreop.muli32toi32 spyreop.idx32toaddr

plus the !spyreop.df16 type.

These are scalar ops, not tensor ops. Every signature in #57 is scalar — e.g.
spyreop.gelu %arg0 : !spyreop.df16. That is the single most important constraint
on this work, and it decides the shape of the pass:

func.func @gelu(%arg0: !spyreop.df16) -> !spyreop.df16 {
  %0 = spyreop.gelu %arg0 : !spyreop.df16
  return %0 : !spyreop.df16
}

So we are not replacing a tensor-level chain with a tensor-level spyreop. The
intrinsic goes inside a linalg.generic region, replacing the scalar body ops
while the surrounding structured op — its iteration space, indexing maps, and
operands — stays as it is. That keeps physicalization, tiling, and the dataflow
scheduler working on a shape they already understand.

Two consequences worth stating early:

  • The match happens on generic bodies, not on tensor op chains. This lines up
    well with [Discussion] Normalize to linalg.generic up front so layout physicalization emits no synthesized loops #90 (normalize everything to linalg.generic up front): after that, the
    matcher has one surface instead of several. Ordering between the two should be
    agreed before either is implemented.
  • !spyreop.df16 vs f16 needs a decision. A linalg.generic body yielding
    !spyreop.df16 while its output tensor is tensor<...xf16> will not verify
    without a conversion at the region boundary. Whether we convert at the boundary,
    carry tensor<...x!spyreop.df16>, or treat df16 as bitwise-compatible with
    f16 is an open question I can't answer from Remove GIT_PAT requirement from Spyre build #57 alone.

Proposed pass

A pattern set over linalg.generic bodies, after LowerComputeOps (and after #90's
normalization, if that lands first):

  • Precision-widening elisionextf → math.op → truncf collapsing to one
    intrinsic where the intrinsic's internal precision matches. sqrt, reciprocal,
    exp are the immediate candidates.
  • Composite primitives — chains that map to one intrinsic even without a widen:
    sigmoid, gelu, softplus, layernormscale are each several arith/math ops
    as Triton emits them.
  • Guards. Fuse only when every intermediate value is single-use inside the
    region. An extf result read by anything else must not be elided, or that other
    consumer silently loses precision.

Numerics are the real risk

Eliding a widen changes results. extf → rsqrt → truncf computes at f32 and rounds
once; spyreop.sqrt + reciprocal on df16 computes at whatever the hardware does
internally. Those agree only if the intrinsic's internal precision is at least f32,
and df16 is not f32 — so this needs confirming per intrinsic against ktir-cpu or
the hardware spec, not assumed from the op names. A fusion that quietly changes
numerics is worse than no fusion.

Note also that rsqrt has no direct spyreop equivalent: it would be
sqrt + reciprocal, i.e. two intrinsics, with its own rounding question. A
single-intrinsic pattern (exp, or sigmoid) may be the better first target.

Interaction with in-flight work

Proposed scope for a first PR

Narrow on purpose, to settle the representation questions before building a table of
patterns:

  1. Pick one intrinsic — I'd suggest exp or sigmoid over rsqrt, since both
    are a single intrinsic and avoid the two-op rounding question.
  2. Confirm numerically against ktir-cpu that the intrinsic agrees with the chain it
    replaces, at the precision the frontend emits.
  3. Resolve the df16/f16 boundary question for that one case.
  4. Lit coverage: the fused match; a case where an intermediate has a second use and
    must not fuse; a precision case that must not fuse.
  5. A numerical test comparing fused vs unfused.

Explicitly out of scope: a general fusion framework or a full pattern table. One
intrinsic end-to-end tells us whether the df16 boundary and the in-region approach
are right; a framework built first would bake in whatever we guessed.

What I need from reviewers

  • The df16 boundary question — convert at the region boundary, carry
    tensor<...x!spyreop.df16>, or treat it as f16-compatible? This is the main
    blocker on writing anything.
  • Per-intrinsic internal precision. Is there a spec for what spyreop.sqrt et al.
    do internally, or does this need measuring against ktir-cpu?
  • First intrinsic — is exp/sigmoid the right start, or is there one with
    measured impact on a real kernel worth prioritizing?
  • Ordering against [Discussion] Normalize to linalg.generic up front so layout physicalization emits no synthesized loops #90.

Activity

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

Metadata

Metadata

Assignees

Labels

enhancementNew feature or requestquestionFurther information is requested

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions