Skip to content

Adding GEMM performance #5

Description

@juanchuletas

Description

Funlib originally contains only one basic GEMM implementation. This is enough for validating matrix multiplication, but it does not provide the memory reuse, device-resident execution, or hardware-specific optimization required for practical AI workloads.

One of Funlib's goals is to support AI training and inference using a portable SYCL implementation that can execute on both Intel and NVIDIA GPUs. Transformer workloads require GEMM for operations such as:

Input projections: Q = XWq, K = XWk, V = XWv
Attention scores:  QK^T
Attention output:  softmax(QK^T)V
Output projection: attention_output * Wo
Feed-forward:      XW1 -> GELU -> XW2

These operations execute repeatedly and often use large matrices. GEMM performance will therefore have a major effect on the training and inference performance of Funlib.

Currently

  • Funlib has a basic GEMM implementation.
  • Tensor operations can execute through a user-selected SYCL queue.
  • The same library is intended to support Intel and NVIDIA GPU backends.
  • There is no set of specialized GEMM kernels for different devices and matrix shapes.
  • There is no dedicated correctness and performance infrastructure for comparing GEMM implementations.
  • Host/device transfers and tensor allocation can hide the actual kernel performance when they are included in measurements.

Plan

1. Add correctness and timing infrastructure

  • Add tests that compare GPU GEMM results against a CPU reference implementation.
  • Test square, rectangular, odd-sized, and non-tile-aligned matrices.
  • Test float, double, and int where supported.
  • Add warmup executions before collecting measurements.
  • Report median execution time across multiple repetitions.
  • Use SYCL event profiling to measure kernel execution separately from allocation, submission, and memory-transfer time.
  • Report GFLOP/s using 2 * M * K * N operations.

2. Support device-resident tensors

  • Allow tensors to own GPU allocations using RAII so users do not manually call allocation and free functions.
  • Add explicit host-to-device and device-to-host copy operations.
  • Keep intermediate AI tensors on the GPU between operations.
  • Ensure tensor operations validate that the supplied SYCL queue can access their inputs.
  • Return device-resident outputs when the inputs are device-resident.

The intended user-facing flow is:

flib::sycl_handler::register_queue(
    "cuda",
    flib::device::GPU,
    flib::vendor::NVIDIA,
    flib::backend::CUDA,
    true);

sycl::queue queue = flib::sycl_handler::get_queue("cuda");

flib::ftensor A(M, K, queue);
flib::ftensor B(K, N, queue);

A.copy_from(hostA.data(), queue).wait();
B.copy_from(hostB.data(), queue).wait();

flib::ftensor C = flib::tensor_operations::gemmTiled(A, B, queue);
std::vector<float> hostC = C.to_host(queue);

3. Add a local-memory tiled GEMM

  • Divide the output matrix into work-group tiles.
  • Cooperatively load tiles from A and B into SYCL local memory.
  • Synchronize work-items before consuming or replacing each tile.
  • Keep partial sums in registers.
  • Add boundary handling for dimensions that are not multiples of the tile size.
  • Compare the tiled kernel with the original GEMM on Intel and NVIDIA GPUs.

4. Add a 2x2 register-blocked GEMM

  • Make each work-item calculate a 2x2 block of C.
  • Keep four output accumulators in registers.
  • Reuse two A values and two B values for four multiply-add operations during each K iteration.
  • Divide the global work size by the 2x2 output block dimensions.
  • Add guarded loads and stores for odd matrix dimensions.
  • Measure whether reduced indexing and improved register reuse compensate for additional register pressure.

5. Combine local-memory tiling with 2x2 register blocking

  • Use a 16x16 work-group to compute a 32x32 output tile.
  • Cooperatively load a 32x16 A tile and a 16x32 B tile into local memory.
  • Assign one 2x2 output block to every work-item.
  • Accumulate the four output values in registers.
  • Handle incomplete M, K, and N tiles using logical zero padding.
  • Compare the combined kernel with both the tiled and non-tiled blocked implementations.

6. Benchmark all GEMM implementations

  • Compare the following implementations under identical conditions:

    • gemm
    • gemmTiled
    • gemm_blocked2x2
    • gemm_tiled_blocked2x2
  • Use the same device-resident inputs for every implementation.

  • Measure total operation time and kernel-only time separately.

  • Include representative transformer shapes in addition to small square matrices.

  • Run the benchmark through Intel OpenCL, Intel Level Zero, and NVIDIA CUDA SYCL backends where available.

  • Record the device name, backend, driver, tile size, work-group size, and data type with every result.

Initial benchmark shapes should include:

128x128x128
256x256x256
512x512x512
127x512x512
512x64x512
512x512x2048

7. Add device- and shape-aware dispatch

  • Do not assume that one kernel is optimal for every GPU.
  • Query relevant device limits, including maximum work-group size, local-memory capacity, and supported subgroup sizes.
  • Evaluate multiple tile and work-group configurations on each backend.
  • Select a GEMM implementation based on device characteristics and matrix shape.
  • Keep a correct fallback implementation for unsupported or unusual shapes.
  • Consider caching tuning results instead of benchmarking on every application start.

8. Investigate later optimizations

  • Evaluate 4x4 register blocking and its effect on register pressure and occupancy.
  • Evaluate subgroup-based data exchange without assuming the same subgroup size across Intel and NVIDIA devices.
  • Investigate packed or transposed weight layouts when B is reused across many inference operations.
  • Measure the packing cost separately and determine its reuse break-even point.
  • Investigate fused bias and activation operations to reduce intermediate memory traffic.
  • Investigate mixed-precision paths and hardware matrix engines where supported.

Acceptance criteria

  • All GEMM implementations produce results within an appropriate tolerance of the CPU reference.
  • Regular and irregular matrix dimensions are covered by tests.
  • Kernel-only and end-to-end benchmark results are available.
  • Benchmarks run through both Intel and NVIDIA SYCL backends.
  • Device-resident tensors can pass through consecutive operations without unnecessary host transfers.
  • The library can select a suitable GEMM implementation without exposing allocation or kernel-selection complexity to the user.
  • Benchmark methodology and relevant results are documented in the repository.

Activity

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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions