Kvax: Flash Attention for JAX

repository·main·Indexed 41 days ago

https://github.com/nebius/kvax

A high-performance Flash Attention 2 implementation for JAX (v0.1.0), optimized for long sequences using document mask optimization and context parallelism. It supports distributed training environments using FSDP/HSDP sharding and provides utilities for block-wise attention masks, token permutation for load balancing, and sharding configuration via attention_specs.

Tokens
3.5K
Snippets
11
Records
12
Agent score
2%

What's inside Kvax

  1. Implement Context Parallelism with Kvax

    main

    Kvax supports context parallelism to balance token loads across GPUs and reduce memory consumption. To use it, follow these steps:

    1. Permute Tokens: Use permute_tokens_context_parallelism to redistribute input tokens (embeddings, positions, and segment IDs) across the mesh.
    2. Compute Attention: Use the standard flash_attention and create_attention_mask operations.
    3. Unpermute Tokens: Use unpermute_tokens_context_parallelism to restore the original token order for the outputs.
    4. Configure Sharding: Use attention_specs within a jax.sharding.Mesh context to define how query and KV tensors are sharded across the mesh axes.
    from kvax.utils import (
        permute_tokens_context_parallelism,
        unpermute_tokens_context_parallelism,
        attention_specs
    )
    import jax
    
    # 1. Permute input
    if self._should_permute_input_tokens:
        embeddings, query_positions, query_segment_ids = permute_tokens_context_parallelism(
            (embeddings, positions, segment_ids),
        )
    
    # ... perform attention operations ...
    
    # 2. Unpermute output
    if self._should_permute_input_tokens:
        embeddings = unpermute_tokens_context_parallelism(embeddings)
    
    # 3. Setup sharding context
    mesh = jax.sharding.Mesh(mesh_devices, mesh_names)
    with mesh, attention_specs(
        query_specs=("data", "context", None, None),
        kv_specs=("data", None, None, None),
    ):
        # Run transformer call
        logits = Transformer(...)(embeddings, positions, segment_ids)
  2. Configure attention sharding with `attention_specs`

    main

    A context manager used to set the sharding specifications for query and key/value tensors. All other Kvax specifications are derived from these.

    Arguments:

    • query_specs: Sharding specs for the query tensor. Must have 4 dimensions: (batch, query_sequence, heads, attention_head_dim).
    • kv_specs: Sharding specs for the key/value tensors. Must have 4 dimensions: (batch, kv_sequence, kv_heads, attention_head_dim).

    Constraints:

    • batch and attention_head_dim dimensions must have identical sharding specs in both query_specs and kv_specs.

    Common Patterns:

    • Tensor Parallelism (Mesh with data and model axes):
      • query_specs: ("data", None, "model", None)
      • kv_specs: ("data", None, "model", None)
    • Context Parallelism (Mesh with data and context axes):
      • query_specs: ("data", "context", None, None)
      • kv_specs: ("data", None, None, None)
    # Example for Tensor Parallelism
    query_specs = ("data", None, "model", None)
    kv_specs = ("data", None, "model", None)
    
    with attention_specs(query_specs=query_specs, kv_specs=kv_specs):
        # flash_attention calls go here
        pass
  3. Install Kvax via pip

    main

    Install the latest stable release of Kvax using pip:

    pip install kvax

    Note on dependencies: The automatically installed versions of triton and jax-triton might not be compatible. If you encounter errors during benchmarking, ensure you install compatible versions manually. For the benchmarks provided by the library, the following versions were used:

    • triton==3.1
    • jax-triton==0.2.0
  4. Mark padding tokens in Kvax

    main

    To enable skipping of padding tokens in attention operations, you must mark the position of every padding token using PADDING_SEGMENT_ID in both the query_segment_ids and kv_segment_ids tensors.

    Example of marking padding tokens in a sequence of length 8 with 2 padding tokens:

    from kvax.utils import PADDING_SEGMENT_ID
    
    # In this example, the sequence length is 8, and there are 2 padding tokens.
    pad_token_id = 128001
    input_ids = [6151, 0, 52043, 710, 374, 1618, pad_token_id, pad_token_id]
    query_segment_ids = [0, 0, 0, 0, 0, 0, PADDING_SEGMENT_ID, PADDING_SEGMENT_ID]
    kv_segment_ids = [0, 0, 0, 0, 0, 0, PADDING_SEGMENT_ID, PADDING_SEGMENT_ID]
  5. Install Kvax and development dependencies

    main

    To install Kvax for development and benchmarking, ensure you have the GPU version of JAX installed first, then run:

    pip install -e .[dev]

    Note on Compatibility: Automatically installed versions of triton and jax-triton might be incompatible. For stable benchmarking, it is recommended to manually install triton==3.1 and jax-triton==0.2.0.

  6. Kvax Limitations

    main

    Users should be aware of the following unsupported features:

    • Bias: Not supported.
    • Masking Types: Sliding window, ALiBi, and custom masks are not implemented.
    • Context Parallelism: Does not support sharding across kv_sequence (unlike RingAttention).
  7. Run Kvax benchmarks

    main

    Kvax provides benchmarking scripts to compare implementations.

    MHA Benchmarks:

    • Single segment: python3 benchmarks.py mha
    • Multiple segments: python3 benchmarks.py mha --num-segments 3
    • Forward+Backward with padding: python3 benchmarks.py mha_bwd --num-segments 3 --num-pad-tokens 1000
    • Forward with mask visualization: python3 benchmarks.py mha --num-segments 3 --num-pad-tokens 1000 --show-attention-mask

    Context Parallelism Benchmarks:

    • CP with token permutation: python3 benchmarks.py mha_cp
    • CP Forward+Backward (permutation disabled): python3 benchmarks.py mha_cp_bwd --num-segments 3 --permute-tokens-for-load-balance false
    # Example: Forward with 3 segments and 1000 pad tokens
    python3 benchmarks.py mha --num-segments 3 --num-pad-tokens 1000
  8. Call the `flash_attention` operation

    main

    The flash_attention function performs the attention operation using precomputed masks and input tensor sharding specifications. It must be called within an attention_specs context manager.

    Arguments:

    • query: Tensor of shape (batch_size, query_seq_length, num_heads, head_dim).
    • key: Tensor of shape (batch_size, kv_seq_length, num_kv_heads, head_dim).
    • value: Tensor of shape (batch_size, kv_seq_length, num_kv_heads, head_dim).
    • query_positions: Tensor of shape (batch_size, query_seq_length). Use range(0, query_seq_length) for sequential tokens. Ignored if assume_sequential_positions=True.
    • query_segment_ids: Tensor of shape (batch_size, query_seq_length). Tokens in the same sequence must share an ID. Use PADDING_SEGMENT_ID for padding.
    • kv_positions: Tensor of shape (batch_size, kv_seq_length). Use range(0, kv_seq_length) for sequential tokens. Ignored if assume_sequential_positions=True.
    • kv_segment_ids: Tensor of shape (batch_size, kv_seq_length). Tokens in the same sequence must share an ID. Use PADDING_SEGMENT_ID for padding.
    • mask: Precomputed block-wise mask from create_attention_mask.
    • scale: Scaling factor (default: 1.0).
    • fwd_params: FlashAttentionParamsConfig for forward pass.
    • bwd_params: FlashAttentionParamsConfig for backward pass.
    • assume_sequential_positions: If True, behaves like is_causal=True in jax.nn.dot_product_attention and skips position loading.
    • memory_optimized_gqa_backward: If True, enables memory-optimized gradient computation for GQA (slower but saves GPU memory).
    • permute_tokens_for_load_balance: If True, permutes tokens for better load balancing in context parallelism (default: True).
    • debug: If True, prints low-level IR of the kernel.
    • mesh: Device mesh configuration. If None, uses the global context mesh.
    # Note: Must be used within attention_specs context
    with attention_specs(query_specs=..., kv_specs=...):
        output = flash_attention(
            query=query,
            key=key,
            value=value,
            query_positions=q_pos,
            query_segment_ids=q_seg,
            kv_positions=kv_pos,
            kv_segment_ids=kv_seg,
            mask=mask,
            assume_sequential_positions=True
        )
  9. Use the flash_attention operation

    main

    The flash_attention function is the core operator for performing high-performance attention. It requires query/KV tensors, their respective positions, and segment IDs (to handle document masks and padding).

    from kvax.ops import flash_attention
    
    # Inside a module or function:
    attn_out = flash_attention(
        query=query,
        key=key,
        value=value,
        query_positions=query_positions,
        query_segment_ids=query_segment_ids,
        kv_positions=kv_positions,
        kv_segment_ids=kv_segment_ids,
        mask=attn_mask,
        assume_sequential_positions=True, # or False based on config
        scale=scale,
        # mesh=mesh, # Optional: mesh is defined as a global context
    )
  10. Create attention masks with `create_attention_mask`

    main

    Calculates attention masks for forward and (optionally) backward Flash Attention operations using Triton kernels for block-wise computation.

    Arguments:

    • query_positions: Tensor of shape (batch_size, query_seq_length).
    • query_segment_ids: Tensor of shape (batch_size, query_seq_length). Use PADDING_SEGMENT_ID for padding.
    • kv_positions: Tensor of shape (batch_size, kv_seq_length).
    • kv_segment_ids: Tensor of shape (batch_size, kv_seq_length). Use PADDING_SEGMENT_ID for padding.
    • fwd_params: FlashAttentionParamsConfig for forward pass.
    • bwd_params: FlashAttentionParamsConfig for backward pass.
    • calc_bwd_mask: If True, calculates masks for the backward pass (default: False).
    • skip_pad_tokens: If True, skips blocks containing only padding tokens (default: True).
    • mesh: Device mesh configuration. If None, uses the global context mesh.
    mask = create_attention_mask(
        query_positions=q_pos,
        query_segment_ids=q_seg,
        kv_positions=kv_pos,
        kv_segment_ids=kv_seg,
        calc_bwd_mask=True
    )
  11. Manage token permutation for Context Parallelism

    main

    To achieve better load balancing across GPUs during causal mask computation in context parallelism, use the following utilities:

    1. permute_tokens_context_parallelism(inputs, mesh=None): Permutes tokens across the sequence length (axis 1). Returns the permuted tensor or tuple.
    2. unpermute_tokens_context_parallelism(inputs, mesh=None): Returns tokens to their original order after permutation.

    Both functions accept a mesh configuration; if None, they use the global context mesh.

    # Permute tokens for load balancing
    permuted_inputs = permute_tokens_context_parallelism(inputs, mesh=mesh)
    
    # ... perform attention ...
    
    # Restore original order
    original_inputs = unpermute_tokens_context_parallelism(permuted_inputs, mesh=mesh)
  12. Create an attention mask with create_attention_mask

    main

    Instead of creating an $O(seq_len^2)$ mask, use create_attention_mask to build a block-wise mask that is efficient and reusable across layers. This mask should be created once per forward-backward pass and passed into the attention operations.

    from kvax.ops import create_attention_mask
    
    attention_mask = create_attention_mask(
        query_positions=query_positions,
        query_segment_ids=query_segment_ids,
        kv_positions=kv_positions,
        kv_segment_ids=kv_segment_ids,
        fwd_params=self.fa_config.fwd_params,
        bwd_params=self.fa_config.bwd_params,
        skip_pad_tokens=self.fa_config.skip_pad_tokens,
        calc_bwd_mask=True,
        # mesh=mesh, # Optional: mesh is defined as a global context
    )