Skip to content

Commit f10ac41

Browse files
theraysmithcopybara-github
authored andcommitted
Added flash attention, with both a single-q function, and a register-tiled function.
The register-tiled version achieves a speed-up by a factor of about 9.7 over the previous attention function on an AVX3-enabled machine. PiperOrigin-RevId: 804913784
1 parent 24b1760 commit f10ac41

9 files changed

Lines changed: 1146 additions & 11 deletions

File tree

BUILD.bazel

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,27 @@ cc_library(
117117
],
118118
)
119119

120+
cc_test(
121+
name = "flash_attention_test",
122+
srcs = ["gemma/flash_attention_test.cc"],
123+
deps = [
124+
":configs",
125+
":gemma_args",
126+
":gemma_lib",
127+
":kv_cache",
128+
":mat",
129+
":matmul",
130+
":ops",
131+
":threading_context",
132+
":weights",
133+
"@googletest//:gtest_main", # buildcleaner: keep
134+
"//compression:compress",
135+
"//compression:types",
136+
"@highway//:hwy",
137+
"@highway//:hwy_test_util",
138+
],
139+
)
140+
120141
cc_test(
121142
name = "threading_test",
122143
srcs = ["util/threading_test.cc"],
@@ -526,12 +547,14 @@ cc_library(
526547
name = "gemma_lib",
527548
srcs = [
528549
"gemma/attention.cc",
550+
"gemma/flash_attention.cc",
529551
"gemma/gemma.cc",
530552
"gemma/vit.cc",
531553
],
532554
hdrs = [
533555
"gemma/activations.h",
534556
"gemma/attention.h",
557+
"gemma/flash_attention.h",
535558
"gemma/gemma.h",
536559
"gemma/vit.h",
537560
],

CMakeLists.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,8 @@ set(SOURCES
7979
gemma/attention.h
8080
gemma/configs.cc
8181
gemma/configs.h
82+
gemma/flash_attention.cc
83+
gemma/flash_attention.h
8284
gemma/gemma_args.h
8385
gemma/gemma-inl.h
8486
gemma/gemma.cc
@@ -216,6 +218,7 @@ set(GEMMA_TEST_FILES
216218
compression/nuq_test.cc
217219
compression/sfp_test.cc
218220
evals/gemma_test.cc
221+
gemma/flash_attention_test.cc
219222
gemma/tensor_info_test.cc
220223
io/blob_store_test.cc
221224
io/fields_test.cc

gemma/activations.h

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,11 @@ struct AttentionActivations {
5656
? layer_config.heads * 3 * layer_config.qkv_dim
5757
: layer_config.heads * layer_config.qkv_dim,
5858
allocator)),
59-
59+
q_T(MatFactory("q_T", layer_config.qkv_dim,
60+
config.vocab_size == 0
61+
? batch_size * layer_config.heads * 3
62+
: batch_size * layer_config.heads,
63+
allocator)),
6064
pre_att_rms_out(MatFactory("pre_att_rms_out", batch_size,
6165
config.model_dim, allocator)),
6266
att(MatFactory("att", batch_size, layer_config.heads * seq_len,
@@ -90,11 +94,13 @@ struct AttentionActivations {
9094
// If we forget any MatMul outputs here, debug builds print a warning but
9195
// fill them in each MatMul call.
9296
q.AllocateAndAttachRowPtrs(row_ptrs);
97+
q_T.AllocateAndAttachRowPtrs(row_ptrs);
9398
att_sums.AllocateAndAttachRowPtrs(row_ptrs);
9499
}
95100

96101
void SetBatchSize(size_t batch_size) {
97102
q.OverrideRows(batch_size);
103+
q_T.OverrideRows(batch_size);
98104

99105
pre_att_rms_out.OverrideRows(batch_size);
100106
att.OverrideRows(batch_size);
@@ -105,6 +111,7 @@ struct AttentionActivations {
105111
const ModelConfig& config;
106112

107113
MatStorageT<float> q; // query
114+
MatStorageT<float> q_T; // Transposed to maximize attention speed.
108115

109116
MatStorageT<float> pre_att_rms_out;
110117
MatStorageT<float> att; // attention vector

gemma/attention.cc

Lines changed: 17 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -41,12 +41,16 @@
4141
#include "hwy/highway.h"
4242
// After highway.h
4343
#include "compression/compress-inl.h"
44+
#include "gemma/flash_attention.h"
4445
#include "ops/ops-inl.h"
4546

4647
HWY_BEFORE_NAMESPACE();
4748
namespace gcpp {
4849
namespace HWY_NAMESPACE {
4950

51+
constexpr int kFlagReserved = 1; // LINTER: unused, reserved for future use.
52+
constexpr int kUseOldAttention = 2;
53+
5054
// Computes Q.K scores, which are "logits" (or scores) stored to att.
5155
// `k` is a strided view of the kv cache with dimensions [seq_len, qkv_dim].
5256
static HWY_INLINE void QDotK(const size_t start_pos, const size_t last_pos,
@@ -71,11 +75,11 @@ static HWY_INLINE void QDotK(const size_t start_pos, const size_t last_pos,
7175
}
7276
}
7377

74-
static void PositionalEncodingQK(float* qk, const size_t layer_idx,
75-
const LayerWeightsPtrs& layer,
76-
const AttentionActivations& activations,
77-
hwy::Profiler& p, const size_t worker,
78-
const size_t pos, const float mul = 1.0f) {
78+
void PositionalEncodingQK(float* qk, const size_t layer_idx,
79+
const LayerWeightsPtrs& layer,
80+
const AttentionActivations& activations,
81+
hwy::Profiler& p, const size_t worker,
82+
const size_t pos, const float mul) {
7983
const size_t qkv_dim = layer.layer_config.qkv_dim;
8084
const PostQKType& post_qk = layer.layer_config.post_qk;
8185
// qk is either q or k, so qkv_dim is the length we operate on.
@@ -165,8 +169,7 @@ void SingleDotSoftmaxWeightedSum(
165169

166170
// The attention window usually starts at 0 unless `pos` is larger than
167171
// the attention window size, then it is `pos` - window_size + 1.
168-
static HWY_INLINE size_t StartPos(size_t pos, const ModelConfig& config,
169-
size_t layer_idx) {
172+
size_t StartPos(size_t pos, const ModelConfig& config, size_t layer_idx) {
170173
const size_t att_window_size = config.attention_window_sizes[layer_idx];
171174
return pos - HWY_MIN(att_window_size - 1, pos);
172175
}
@@ -314,7 +317,7 @@ static HWY_INLINE void ComputeQKV(size_t num_tokens, const size_t layer_idx,
314317
}
315318

316319
PositionalEncodingQK(kv_f32, layer_idx, layer, activations,
317-
env.ctx.profiler, worker, pos);
320+
env.ctx.profiler, worker, pos, /*mul=*/1.0f);
318321
CompressPerThread tls;
319322
Compress(kv_f32, 2 * qkv_dim, tls, MakeSpan(kv, 2 * qkv_dim), 0);
320323
});
@@ -354,8 +357,12 @@ void GemmaAttention(size_t num_tokens, const size_t layer_idx,
354357
(void)layer_config; // only used in HWY_DASSERT
355358

356359
ComputeQKV(num_tokens, layer_idx, layer, activations, qbatch, flags, env);
357-
DotSoftmaxWeightedSum(num_tokens, layer_idx, layer, activations, qbatch,
358-
env.ctx);
360+
if (flags & kUseOldAttention) {
361+
DotSoftmaxWeightedSum(num_tokens, layer_idx, layer, activations, qbatch,
362+
env.ctx);
363+
} else {
364+
FlashAttention(num_tokens, layer_idx, layer, activations, qbatch, env.ctx);
365+
}
359366
SumHeads(layer, activations, env);
360367
}
361368

gemma/attention.h

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,14 @@ namespace gcpp {
2828
// Passed to HWY_VISIT_TARGETS; declares for one target.
2929
#define GEMMA_DECL_ATTENTION(TARGET, NAMESPACE) \
3030
namespace NAMESPACE { \
31+
void PositionalEncodingQK(float* qk, size_t layer_idx, \
32+
const LayerWeightsPtrs& layer, \
33+
const AttentionActivations& activations, \
34+
hwy::Profiler& p, size_t worker, size_t pos, \
35+
float mul); \
36+
\
37+
size_t StartPos(size_t pos, const ModelConfig& config, size_t layer_idx); \
38+
\
3139
void SingleDotSoftmaxWeightedSum( \
3240
const size_t pos, const size_t start_pos, const size_t last_pos, \
3341
float* HWY_RESTRICT q, const MatPtrT<KV_t>& k, const MatPtrT<KV_t>& v, \

0 commit comments

Comments
 (0)