jax-triton

repository·main·Indexed 17 days ago

https://github.com/jax-ml/jax-triton

Integration between JAX and OpenAI Triton that enables developers to write custom GPU kernels using Triton and execute them within JAX programs. The primary API, jax_triton.triton_call, allows Triton kernels to be used inside jax.jit-compiled functions, supporting custom grid dimensions, in-place operations via input_output_aliases, and XLA cost estimates. Note that automatic differentiation and vmap are not supported out of the box and require custom rules.

Tokens
5.4K
Snippets
17
Records
22
Agent score
67%

What's inside jax-triton

  1. Use input-output aliases for in-place Triton kernels

    main

    When a Triton kernel performs an in-place operation (where an input argument is also the output), use the input_output_aliases parameter in jt.triton_call.

    input_output_aliases is a dictionary mapping the index of the input argument to the index of the output argument. For example, {1: 0} indicates that the argument at index 1 is also the first output argument.

    To improve efficiency and avoid XLA making copies of non-donated in-out arguments (since JAX arrays are immutable by default), use jax.jit with donate_argnames.

    Example: In-place addition

    @triton.jit
    def add_inplace_y_kernel(x_ptr, y_inout_ptr, length, block_size: tl.constexpr):
      # ... kernel logic ...
      tl.store(y_inout_ptr + offsets, output, mask=mask)
    
    @partial(jax.jit, donate_argnames="y")
    def add_inplace_y(x: jnp.ndarray, y: jnp.ndarray) -> jnp.ndarray:
      block_size = 8
      return jt.triton_call(
          x,
          y,
          x.size,
          kernel=add_inplace_y_kernel,
          input_output_aliases={1: 0}, # arg index 1 (y) is the first output
          out_shape=x,
          grid=(x.size // block_size,),
          block_size=block_size
      )
    @partial(jax.jit, donate_argnames="y")
    def add_inplace_y(x: jnp.ndarray, y: jnp.ndarray) -> jnp.ndarray:
      block_size = 8
      return jt.triton_call(
          x,
          y,
          x.size,
          kernel=add_inplace_y_kernel,
          input_output_aliases={1: 0},  # input arg idx 1 (y) is the first output arg
          out_shape=x,
          grid=(x.size // block_size,),
          block_size=block_size)
  2. Install JAX-Triton via pip

    main

    Install the stable version of JAX-Triton using pip. This command will also attempt to install compatible versions of JAX and Triton.

    Note: JAX-Triton requires JAX with GPU support. Ensure you have a CUDA-compatible jaxlib installed (e.g., pip install "jax[cuda]").

    $ pip install jax-triton
  3. Set up jax-triton development environment

    main

    To develop jax-triton, clone the repository and perform an editable installation.

    1. Clone the repo:
      git clone https://github.com/jax-ml/jax-triton.git
    2. Install in editable mode:
      cd jax-triton
      pip install -e .
    3. Run tests using pytest:
      pip install pytest
      pytest tests/
  4. Call Triton kernels from JAX using triton_call

    main

    The primary method for executing handwritten Triton kernels within JIT-ted JAX programs is by using jax_triton.triton_call. This allows you to integrate custom Triton kernels directly into your JAX computation graphs.

    # Example usage pattern
    # jax_triton.triton_call(kernel, args, ...)
  5. Install JAX-Triton from HEAD

    main

    To get the bleeding edge version of JAX-Triton, install directly from the GitHub repository. This will install compatible versions of JAX and Triton.

    JAX-Triton requires jaxlib with GPU support. You can install the latest stable release via pip install "jaxlib[cuda]". In some cases, a nightly version of jaxlib may be required.

    $ pip install 'jax-triton @ git+https://github.com/jax-ml/jax-triton.git'
  6. How `TritonFunction` abstracts kernel management

    main

    The TritonFunction class is a unified wrapper that manages the lifecycle of a Triton kernel. It abstracts away low-level details such as:

    • Compilation & Caching: It handles kernel compilation and maintains a cache (_jT_kernel_cache) to avoid redundant compilations for the same kernel specialization.
    • Autotuning & Heuristics: It can wrap a Triton Autotuner or Heuristics object, automatically managing configuration pruning and parameter injection.
    • Specialization: It builds a KernelSpecialization based on argument types, values, and constants to ensure the correct binary is generated for specific inputs.
    • Parameter Mapping: It distinguishes between constexpr parameters (which are baked into the kernel) and non-constexpr parameters (which are passed as arguments).
  7. Perform in-place mutations with `Ref` arguments

    main

    Instead of using the deprecated input_output_aliases, use Ref objects to perform in-place mutations within a Triton kernel.

    1. Create a reference buffer using jax.new_ref.
    2. Pass this reference as a positional argument to triton_call.
    3. The kernel will treat this as a read-write buffer.

    Warning: Do not include Ref arguments in the out_shape parameter. Also, input_output_aliases cannot be combined with Ref arguments; using both will raise a ValueError.

    # Create a reference for in-place mutation
    ref_buffer = jax.new_ref(shape=(10,), dtype=jnp.float32)
    
    # Pass it to triton_call
    triton_call(
        kernel=my_kernel,
        ref_buffer,  # This is a Ref argument
        out_shape=..., 
        # ... other args
    )
  8. Install JAX with Triton support

    main

    To use jax-triton, you need to install a specific version of JAX that includes Triton integration, along with the Triton library itself. The following steps demonstrate how to install the required dependencies, including cloning a specific branch of JAX and building Triton from source.

    Note: The installation process involves uninstalling existing JAX versions and building from source to ensure compatibility with the Triton kernel integration.

    # Install basic dependencies
    !pip install -U --pre triton
    !pip install chex
    !pip install cmake
    
    # Install JAX with Triton support
    %cd /root
    !pip uninstall jax -y
    !git clone https://github.com/sharadmv/jax.git
    %cd jax
    !git checkout triton
    !pip install -I -U ".[cuda11_cudnn82]" -f https://storage.googleapis.com/jax_cuda_releases.html
    !pip install pybind11
    
    # Build and install Triton
    %cd /root/triton
    !make
    !pip install .
    %cd /root
  9. Use jax_triton.triton_call to apply Triton kernels to JAX arrays

    main

    The primary API for integrating Triton kernels with JAX is jax_triton.triton_call. This function allows you to apply a Triton kernel to JAX arrays and can be used inside jax.jit-compiled functions.

    Basic Usage Pattern

    1. Define a Triton kernel using @triton.jit.
    2. Use jt.triton_call to invoke the kernel.

    Arguments for triton_call:

    • Input arguments: The first arguments passed to triton_call correspond to the kernel's input arguments.
    • kernel: The Triton kernel function.
    • out_shape: The shape/template for the output array.
    • grid: The grid dimensions for the kernel execution.
    • kwargs: Any additional keyword arguments (like block_size) are passed as tl.constexpr parameters to the Triton kernel.

    Note: The output argument is passed implicitly after the input arguments in the Triton kernel signature.

    import jax
    import jax.numpy as jnp
    import jax_triton as jt
    import triton
    import triton.language as tl
    
    @triton.jit
    def add_kernel(x_ptr, y_ptr, length, output_ptr, block_size: tl.constexpr):
      pid = tl.program_id(axis=0)
      block_start = pid * block_size
      offsets = block_start + tl.arange(0, block_size)
      mask = offsets < length
      x = tl.load(x_ptr + offsets, mask=mask)
      y = tl.load(y_ptr + offsets, mask=mask)
      output = x + y
      tl.store(output_ptr + offsets, output, mask=mask)
    
    def add(x: jnp.ndarray, y: jnp.ndarray) -> jnp.ndarray:
      block_size = 8
      return jt.triton_call(
          x,
          y,
          x.size,
          kernel=add_kernel,
          out_shape=x,
          grid=(x.size // block_size,),
          block_size=block_size
        )
    
    x_val = jnp.arange(8)
    y_val = jnp.arange(8, 16)
    print(add(x_val, y_val))
  10. Automatic Differentiation and Batching limitations

    main

    Currently, jax_triton.triton_call does not support automatic differentiation or automatic batching out of the box. If you attempt to use them, the following errors will be raised:

    • JVP (Forward-mode AD): Raises NotImplementedError. To support this, you must implement a custom rule using jax.custom_jvp or jax.custom_vjp.
    • vmap (Batching): Raises NotImplementedError. To support this, you must implement a custom batching rule using jax.custom_batching.custom_vmap.
  11. Implement Flash Attention using Triton kernels

    main

    The provided example demonstrates a fused Flash Attention implementation using Triton. The kernel _fwd_kernel uses tiling to compute attention scores and updates an accumulator in a single pass to minimize memory bandwidth usage.

    Key components of the kernel implementation:

    • Tiling: Uses BLOCK_M and BLOCK_N to process chunks of the sequence.
    • Online Softmax: Implements the online softmax algorithm to update the running maximum (m_i) and the running sum of exponentials (l_i).
    • Scratchpad Buffer: Uses a TMP buffer to work around specific compiler bugs when storing/loading intermediate scaling factors.
    • Strides: Explicitly handles strides for all dimensions (Batch, Head, Seq, Dim) to allow for flexible memory layouts.
    @triton.jit
    def _fwd_kernel(
        Q, K, V,
        TMP, L, M,  # Scratchpad, L, and M buffers
        Out,
        stride_qz, stride_qh, stride_qm, stride_qk,
        # ... other strides
        Z, H, N_CTX,
        BLOCK_M: tl.constexpr, BLOCK_DMODEL: tl.constexpr, BLOCK_N: tl.constexpr,
    ):
        # ... kernel implementation logic ...
        # 1. Load Q, K, V pointers
        # 2. Loop over blocks of K and V
        # 3. Compute dot product and online softmax updates
        # 4. Update accumulator and store results