Ring attention implementation with flash attention
Python
1,062
131 commits
updated Sep 10, 2025
This repo implements RingAttention using FlashAttention. The current implementation supports:
flash_attn_varlen_func:
ring_flash_attn_varlen_func: A basic implementation of ring attention.zigzag_ring_flash_attn_varlen_func: an more compute-balanced version of ring attention. More details in issue#2.llama3_flash_attn_varlen_func: The context parallelism used in llama3 tech report with extra design for varlen and low memory overhead. Although technically not ring attention, this is recommended for most varlen use cases, as it offers a less intrusive alternative for training frameworks with fewer data manipulations and better arithmetic precision.flash_attn_func:
ring_flash_attn_func: basic ring attention.zigzag_ring_flash_attn_func: An more compute balanced version of ring attention, see issue#2.stripe_flash_attn_func: Stripe attention version of ring_flash_attn_func, the block size is set to 1 to use flash_attn api, see: https://arxiv.org/abs/2311.09431# torchrun --nproc_per_node=2 this_script.py
import torch
from ring_flash_attn import substitute_hf_flash_attn, update_ring_flash_attn_params
from torch import distributed as dist
from transformers import AutoModelForCausalLM, AutoTokenizer
def main():
# Initialize distributed training
dist.init_process_group(backend="nccl")
# Get rank and world size
rank = dist.get_rank()
world_size = dist.get_world_size()
# Set device
torch.cuda.set_device(rank)
device = torch.device(f"cuda:{rank}")
# Load model and tokenizer
model = AutoModelForCausalLM.from_pretrained(
"Qwen/Qwen3-0.6B", attn_implementation="flash_attention_2", torch_dtype=torch.bfloat16, device_map=device
)
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen3-0.6B")
# Create group and substitute flash attention
group = dist.new_group(ranks=range(world_size), backend="nccl")
substitute_hf_flash_attn(group, heads_k_stride=1)
# Get the ring attention rank
ring_attn_rank = dist.get_rank(group=group) # only one group for ring attention here: this should be the same as rank
# Tokenize input and prepare position IDs
input_ids = tokenizer(["Lorem ipsum dolor sit", "amet, consectetur adipiscing", "elit, sed do"]).input_ids
lengths = [len(seq) for seq in input_ids]
input_ids = torch.cat([torch.tensor(seq, device=device) for seq in input_ids]).unsqueeze(0)
position_ids = torch.cat([torch.arange(length, device=device) for length in lengths]).unsqueeze(0)
# Compute cu_seqlens and update parameters
cu_seqlens = torch.cat([torch.tensor([0], device=device), torch.cumsum(torch.tensor(lengths, device=device), dim=0)]).to(torch.int32)
update_ring_flash_attn_params(cu_seqlens, group)
# Chunk input_ids and position_ids
input_ids = torch.chunk(input_ids, world_size, dim=1)[ring_attn_rank]
position_ids = torch.chunk(position_ids, world_size, dim=1)[ring_attn_rank]
output = model(input_ids=input_ids, position_ids=position_ids)
# Clean up
dist.destroy_process_group()
if __name__ == "__main__":
main()
Note that
*_func, *_kvpacked_func, *_qkvpacked_func variants.cu_seqlens.The following table summarizes the performance of the implemented APIs:
| batch api | GPU | theoretic flash_attn | ring_attn | zigzag_ring | stripe_attn |
|---|---|---|---|---|---|
| fwd only (iter/sec) | 8xH800 | 591.5 / 8 = 73.9 | 38.5 | 63.0 | 55.0 |
| 52.1% | 85.2% | 74.4% | |||
| fwd + bwd (iter/sec) | 8xH800 | 154.7 / 8 = 19.3 | 10.4 | 17.4 | 16.0 |
| 53.9% | 90.2% | 82.9% | |||
| fwd only (iter/sec) | 8xA100 | 373.4 / 8 = 46.7 | 24.0 | 38.2 | 32.5 |
| 51.4% | 81.7% | 69.6% | |||
| fwd + bwd (iter/sec) | 8xA100 | 94.7 / 8 = 11.8 | 6.2 | 10.6 | 9.75 |
| 52.5% | 89.8% | 82.6% | |||
| varlen api | GPU | theoretic flash_attn | ring_attn | zigzag_ring | llama3_attn |
| fwd only (iter/sec) | 8xH800 | 852.4 / 8 = 106.6 | 52.4 | 74.8 | 60.8 |
| 49.1% | 70.2% | 57.0% | |||
| fwd + bwd (iter/sec) | 8xH800 | 225.4 / 8 = 28.2 | 14.4 | 21.4 | 16.4 |
| 51.1% | 75.9% | 58.1% | |||
| fwd only (iter/sec) | 8xA100 | 532.3 / 8 = 66.5 | 33.1 | 47.9 | 34.3 |
| 49.8% | 72.0% | 51.6% | |||
| fwd + bwd (iter/sec) | 8xA100 | 133.8 / 8 = 16.7 | 8.7 | 13.4 | 9.7 |
| 52.1% | 80.2% | 58.0% |
Note that
8*1^2, while the ring attn code is running 1*8^2.pip install ring-flash-attn
or use the following command to build from source:
git clone https://github.com/zhuzilin/ring-flash-attention.git
cd ring-flash-attention
pip install .
ring_flash_attn_varlen_qkvpacked_funczigzag_ring_flash_attn_qkvpacked_func issue#2stripe_flash_attn_qkvpacked_funczigzag_ring_flash_attn_varlen_qkvpacked_func*_kvpacked_func and *_func variant for all APIs*_varlen_funcllama3_flash_attn_varlen_funczigzag_llama3_flash_attn_varlen_functorchrun --nproc_per_node 8 test/test_llama3_flash_attn_varlen_func.py
torchrun --nproc_per_node 8 test/test_ring_flash_attn_func.py
torchrun --nproc_per_node 8 test/test_ring_flash_attn_varlen_func.py
torchrun --nproc_per_node 8 test/test_zigzag_ring_flash_attn_func.py
torchrun --nproc_per_node 8 test/test_zigzag_ring_flash_attn_varlen_func.py
torchrun --nproc_per_node 8 test/test_stripe_flash_attn_func.py
torchrun --nproc_per_node 8 benchmark/benchmark_kvpacked_func.py
torchrun --nproc_per_node 8 benchmark/benchmark_varlen_kvpacked_func.py
There are some arithmetic errors with the current implementation. The reason for them is probably that flash attention will return bf16 value for each block, so we cannot accumluate the values with the original fp32 ones.
And also because we need to save extra fp32 buffer during computation, the memory usage would be higher than theoretic limit.
Also,
293 followers · starred Mar 2024
1,506 followers · starred Aug 2024
549 followers · starred Feb 2024
332 followers · starred Sep 2025
Ring attention implementation with flash attention
Python
1,062
131 commits
updated Sep 10, 2025
This repo implements RingAttention using FlashAttention. The current implementation supports:
flash_attn_varlen_func:
ring_flash_attn_varlen_func: A basic implementation of ring attention.zigzag_ring_flash_attn_varlen_func: an more compute-balanced version of ring attention. More details in issue#2.llama3_flash_attn_varlen_func: The context parallelism used in llama3 tech report with extra design for varlen and low memory overhead. Although technically not ring attention, this is recommended for most varlen use cases, as it offers a less intrusive alternative for training frameworks with fewer data manipulations and better arithmetic precision.flash_attn_func:
ring_flash_attn_func: basic ring attention.zigzag_ring_flash_attn_func: An more compute balanced version of ring attention, see issue#2.stripe_flash_attn_func: Stripe attention version of ring_flash_attn_func, the block size is set to 1 to use flash_attn api, see: https://arxiv.org/abs/2311.09431# torchrun --nproc_per_node=2 this_script.py
import torch
from ring_flash_attn import substitute_hf_flash_attn, update_ring_flash_attn_params
from torch import distributed as dist
from transformers import AutoModelForCausalLM, AutoTokenizer
def main():
# Initialize distributed training
dist.init_process_group(backend="nccl")
# Get rank and world size
rank = dist.get_rank()
world_size = dist.get_world_size()
# Set device
torch.cuda.set_device(rank)
device = torch.device(f"cuda:{rank}")
# Load model and tokenizer
model = AutoModelForCausalLM.from_pretrained(
"Qwen/Qwen3-0.6B", attn_implementation="flash_attention_2", torch_dtype=torch.bfloat16, device_map=device
)
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen3-0.6B")
# Create group and substitute flash attention
group = dist.new_group(ranks=range(world_size), backend="nccl")
substitute_hf_flash_attn(group, heads_k_stride=1)
# Get the ring attention rank
ring_attn_rank = dist.get_rank(group=group) # only one group for ring attention here: this should be the same as rank
# Tokenize input and prepare position IDs
input_ids = tokenizer(["Lorem ipsum dolor sit", "amet, consectetur adipiscing", "elit, sed do"]).input_ids
lengths = [len(seq) for seq in input_ids]
input_ids = torch.cat([torch.tensor(seq, device=device) for seq in input_ids]).unsqueeze(0)
position_ids = torch.cat([torch.arange(length, device=device) for length in lengths]).unsqueeze(0)
# Compute cu_seqlens and update parameters
cu_seqlens = torch.cat([torch.tensor([0], device=device), torch.cumsum(torch.tensor(lengths, device=device), dim=0)]).to(torch.int32)
update_ring_flash_attn_params(cu_seqlens, group)
# Chunk input_ids and position_ids
input_ids = torch.chunk(input_ids, world_size, dim=1)[ring_attn_rank]
position_ids = torch.chunk(position_ids, world_size, dim=1)[ring_attn_rank]
output = model(input_ids=input_ids, position_ids=position_ids)
# Clean up
dist.destroy_process_group()
if __name__ == "__main__":
main()
Note that
*_func, *_kvpacked_func, *_qkvpacked_func variants.cu_seqlens.The following table summarizes the performance of the implemented APIs:
| batch api | GPU | theoretic flash_attn | ring_attn | zigzag_ring | stripe_attn |
|---|---|---|---|---|---|
| fwd only (iter/sec) | 8xH800 | 591.5 / 8 = 73.9 | 38.5 | 63.0 | 55.0 |
| 52.1% | 85.2% | 74.4% | |||
| fwd + bwd (iter/sec) | 8xH800 | 154.7 / 8 = 19.3 | 10.4 | 17.4 | 16.0 |
| 53.9% | 90.2% | 82.9% | |||
| fwd only (iter/sec) | 8xA100 | 373.4 / 8 = 46.7 | 24.0 | 38.2 | 32.5 |
| 51.4% | 81.7% | 69.6% | |||
| fwd + bwd (iter/sec) | 8xA100 | 94.7 / 8 = 11.8 | 6.2 | 10.6 | 9.75 |
| 52.5% | 89.8% | 82.6% | |||
| varlen api | GPU | theoretic flash_attn | ring_attn | zigzag_ring | llama3_attn |
| fwd only (iter/sec) | 8xH800 | 852.4 / 8 = 106.6 | 52.4 | 74.8 | 60.8 |
| 49.1% | 70.2% | 57.0% | |||
| fwd + bwd (iter/sec) | 8xH800 | 225.4 / 8 = 28.2 | 14.4 | 21.4 | 16.4 |
| 51.1% | 75.9% | 58.1% | |||
| fwd only (iter/sec) | 8xA100 | 532.3 / 8 = 66.5 | 33.1 | 47.9 | 34.3 |
| 49.8% | 72.0% | 51.6% | |||
| fwd + bwd (iter/sec) | 8xA100 | 133.8 / 8 = 16.7 | 8.7 | 13.4 | 9.7 |
| 52.1% | 80.2% | 58.0% |
Note that
8*1^2, while the ring attn code is running 1*8^2.pip install ring-flash-attn
or use the following command to build from source:
git clone https://github.com/zhuzilin/ring-flash-attention.git
cd ring-flash-attention
pip install .
ring_flash_attn_varlen_qkvpacked_funczigzag_ring_flash_attn_qkvpacked_func issue#2stripe_flash_attn_qkvpacked_funczigzag_ring_flash_attn_varlen_qkvpacked_func*_kvpacked_func and *_func variant for all APIs*_varlen_funcllama3_flash_attn_varlen_funczigzag_llama3_flash_attn_varlen_functorchrun --nproc_per_node 8 test/test_llama3_flash_attn_varlen_func.py
torchrun --nproc_per_node 8 test/test_ring_flash_attn_func.py
torchrun --nproc_per_node 8 test/test_ring_flash_attn_varlen_func.py
torchrun --nproc_per_node 8 test/test_zigzag_ring_flash_attn_func.py
torchrun --nproc_per_node 8 test/test_zigzag_ring_flash_attn_varlen_func.py
torchrun --nproc_per_node 8 test/test_stripe_flash_attn_func.py
torchrun --nproc_per_node 8 benchmark/benchmark_kvpacked_func.py
torchrun --nproc_per_node 8 benchmark/benchmark_varlen_kvpacked_func.py
There are some arithmetic errors with the current implementation. The reason for them is probably that flash attention will return bf16 value for each block, so we cannot accumluate the values with the original fp32 ones.
And also because we need to save extra fp32 buffer during computation, the memory usage would be higher than theoretic limit.
Also,
293 followers · starred Mar 2024
1,506 followers · starred Aug 2024
549 followers · starred Feb 2024
332 followers · starred Sep 2025