Skip to content

gemv: add optional fused GELU epilogue (NPU2)#135

Open
atassis wants to merge 1 commit into
amd:develfrom
atassis:feat/gemv-gelu-epilogue
Open

gemv: add optional fused GELU epilogue (NPU2)#135
atassis wants to merge 1 commit into
amd:develfrom
atassis:feat/gemv-gelu-epilogue

Conversation

@atassis

@atassis atassis commented Jul 10, 2026

Copy link
Copy Markdown
Contributor

Adds an opt-in epilogue parameter to GEMV. epilogue="none" (default) is byte-identical to today; epilogue="gelu" fuses a GELU (tanh approximation) over each output tile inside the producing core, folding an activation that would otherwise be a separate elementwise pass over the GEMV output.

Details

  • The GELU runs once per full m_output tile in core_body (after the matvec inner-loop has filled all rows), not per matvec call whose m_input tile can be smaller than the 16-wide activation vector.
  • No duplicated math. aie_kernels/aie2p/gelu.cc is refactored so the per-16-lane computation is a shared helper used by both the existing out-of-place gelu_tanh_approx_bf16 and a new in-place gelu_tile_bf16. In-place is aliasing-correct (a single restrict pointer, each slot read then written); reusing the out-of-place kernel with input == output would violate its restrict contract, so a dedicated in-place entry point is the correct way to share the code.
  • Since gelu.cc lives in aie2p/, the fused epilogue is NPU2-only; GEMV raises NotImplementedError if it is requested on NPU1. When enabled, the core links a (matvec, gelu) archive; the default path still links the single matvec object.

Test

test_gemv_gelu compares the fused output against a gelu(A @ B) golden across three shapes, using the same latency/bandwidth/throughput @pytest.mark.metrics as test_gemv. Verified on NPU2: 15/15 pass.

This is opt-in and default-preserving; it also composes with the existing func_prefix fusion support (#123) for use as a fused block.

Adds an opt-in `epilogue` parameter to GEMV. `epilogue="none"` (default) leaves
the output unchanged and is byte-identical to before; `epilogue="gelu"` applies a
GELU (tanh approximation) to each output tile inside the producing core, fusing an
activation that would otherwise be a separate elementwise pass over the GEMV output.

The GELU is applied once per full output tile in core_body (after the matvec
inner-loop fills all rows), not per matvec call, whose m_input tile can be smaller
than the 16-wide activation vector.

To avoid duplicating the GELU math, aie2p/gelu.cc is refactored: the per-16-lane
computation is factored into a shared helper used by both the existing out-of-place
gelu_tanh_approx_bf16 and a new in-place gelu_tile_bf16. In-place is aliasing-correct
(a single restrict pointer, each slot read then written) whereas reusing the
out-of-place kernel with input==output would violate its restrict contract. Because
gelu.cc is aie2p-only, the fused epilogue is NPU2-only; GEMV raises NotImplementedError
if requested on NPU1. When enabled the core links a (matvec, gelu) archive.

Test: test_gemv_gelu compares the fused output against a gelu(A @ B) golden across
three shapes, with the standard latency/bandwidth/throughput metrics. Verified on
NPU2 (15/15 pass).
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant