Skip to content

Commit eddfe00

Browse files
committed
[GPU] Enable f8e4m3fn quantized-KV GroupQueryAttention (dynamic path)
Extends the GPU quantized-KV GroupQueryAttention support (i8/i4) to the f8e4m3fn KV cache on the dynamic (Slice+Concat) cache-assembly path. f8e4m3fn KV is part of the com.microsoft GQA spec (T_CACHE) and already worked on CPU/TEMPLATE via the device-agnostic decomposition; only GPU primitive gaps remained. fp8e4m3_t is a 1-byte struct with no arithmetic/convert overloads, so the reference kernels needed f8-aware handling on top of the type-list widenings: - Slice / Concat / ScatterUpdate ref kernels: include f8_utils.cl for the fp8e4m3_t typedef, and on the identity (no-activation) store copy the byte directly when the tensor is fp8 - ACTIVATION/TO_OUTPUT_TYPE do not compile on the struct. Gated by an INPUT*_IS_FP8 jit flag; non-fp8 paths are unchanged. - reorder_data.cl: (a) f8->f8 layout reorder stores the already-encoded value directly; (b) for a non-float input reordered to fp8, encode from float to resolve the ambiguous fp8 encoder overload. Both gated on fp8 flags. - Add f8e4m3 to the Slice/Concat/ScatterUpdate impl and kernel supported-type lists. Tests: un-skip the 2 IE_GPU gqa_f8e4m3fnkv tests (dynamic). GQA quant suite is 36/36 across GPU/CPU/INTERPRETER (i8/i4/f8); 356 GPU reorder unit tests green (no regression from the shared-kernel change). Out of scope: the static (ScatterUpdate) f8 path still fails impl selection in add_required_reorders for the fp8 dtype (i8/i4 static pass); tracked as a follow-up. No static f8 GQA test is added here.
1 parent 433a413 commit eddfe00

11 files changed

Lines changed: 50 additions & 28 deletions

File tree

src/frontends/onnx/tests/runtime/ie/unit_test.manifest

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -359,13 +359,6 @@ IE_GPU.onnx_model_gather_elements_int32_axis_0
359359
IE_GPU.onnx_model_gather_elements_int8_axis_1
360360
IE_GPU.onnx_model_gather_elements_float_3D_axis_2
361361

362-
# GroupQueryAttention with an f8e4m3 quantized KV cache: the GPU plugin has no f8e4m3 Slice/reorder
363-
# layout yet ("No layout format available"), so the fp8 variants remain skipped on GPU. The i8/i4
364-
# variants are enabled (see KeepGQAKVScalePrecision + i8/u8 ScatterUpdate/Slice support). fp8 GPU is
365-
# tracked as a separate follow-up; numerics stay covered on CPU/INTERPRETER meanwhile.
366-
IE_GPU.onnx_model_gqa_f8e4m3fnkv_per_channel
367-
IE_GPU.onnx_model_gqa_f8e4m3fnkv_per_tensor
368-
369362
IE_CPU/ElemTypesTests/1.onnx_test_add_abc_set_precission
370363

371364
# RuntimeError: Unsupported dynamic ops: v4::Interpolate - Ticket: 50691

src/plugins/intel_gpu/src/graph/impls/ocl/concatenation.cpp

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -95,14 +95,7 @@ struct concatenation_impl : typed_primitive_impl_ocl<concatenation> {
9595
namespace detail {
9696

9797
attach_concatenation_impl::attach_concatenation_impl() {
98-
auto dyn_types = {
99-
data_types::i8,
100-
data_types::u8,
101-
data_types::f16,
102-
data_types::f32,
103-
data_types::i32,
104-
data_types::i64
105-
};
98+
auto dyn_types = {data_types::i8, data_types::u8, data_types::f16, data_types::f32, data_types::i32, data_types::i64, data_types::f8e4m3};
10699

107100
auto dyn_formats = {
108101
format::bfyx,

src/plugins/intel_gpu/src/graph/impls/ocl/scatter_update.hpp

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -37,18 +37,16 @@ struct ScatterUpdateImplementationManager : public ImplementationManager {
3737
format::bfwzyx
3838
};
3939

40-
static const std::vector<ov::element::Type_t> supported_in_types = {ov::element::f32,
41-
ov::element::f16,
42-
ov::element::i32,
43-
ov::element::i8,
44-
ov::element::u8};
40+
static const std::vector<ov::element::Type_t> supported_in_types =
41+
{ov::element::f32, ov::element::f16, ov::element::i32, ov::element::i8, ov::element::u8, ov::element::f8e4m3};
4542

4643
static const std::vector<ov::element::Type_t> supported_out_types = {
4744
ov::element::f32,
4845
ov::element::f16,
4946
ov::element::i32,
5047
ov::element::i8,
5148
ov::element::u8,
49+
ov::element::f8e4m3,
5250
};
5351

5452
const auto& in0_layout = node.get_input_layout(0);

src/plugins/intel_gpu/src/graph/impls/ocl/slice.cpp

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -181,14 +181,7 @@ struct slice_impl : typed_primitive_impl_ocl<slice> {
181181
namespace detail {
182182

183183
attach_slice_impl::attach_slice_impl() {
184-
auto types = {
185-
data_types::f32,
186-
data_types::f16,
187-
data_types::i8,
188-
data_types::u8,
189-
data_types::i32,
190-
data_types::i64
191-
};
184+
auto types = {data_types::f32, data_types::f16, data_types::i8, data_types::u8, data_types::i32, data_types::i64, data_types::f8e4m3};
192185

193186
auto formats = {
194187
format::bfyx,

src/plugins/intel_gpu/src/kernel_selector/cl_kernels/concatenation_gpu_ref.cl

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
//
44

55
#include "include/batch_headers/fetch_data.cl"
6+
#include "include/f8_utils.cl" // fp8e4m3_t typedef
67

78
#define GET_INDEX(prefix, ORDER) CAT(prefix, _GET_INDEX)(ORDER)
89

@@ -34,6 +35,10 @@ KERNEL(concatenation_gpu_ref)(
3435
#if HAS_FUSED_OPS
3536
FUSED_OPS;
3637
output[output_offset] = TO_OUTPUT_TYPE(FUSED_OPS_RESULT);
38+
#elif INPUT0_IS_FP8
39+
// fp8 is a 1-byte struct; Concat just moves data (in/out dtype match), so copy the byte
40+
// (TO_OUTPUT_TYPE/ACTIVATION won't compile on it).
41+
output[output_offset] = result;
3742
#else
3843
output[output_offset] = TO_OUTPUT_TYPE(ACTIVATION(result, ACTIVATION_PARAMS));
3944
#endif

src/plugins/intel_gpu/src/kernel_selector/cl_kernels/reorder_data.cl

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,10 @@ KERNEL (reorder_data)(
232232
#else
233233
#define __TO_OUTPUT_REORDER_TYPE(res) TO_OUTPUT_REORDER_TYPE_SAT(res)
234234
#endif
235+
#elif F8E5M2_OUTPUT || F8E4M3_OUTPUT || F8E8M0_OUTPUT
236+
// fp8 encoders are overloaded on float/half only; a non-float `res` (e.g. uchar from an integer
237+
// input) makes the call ambiguous, so encode from float. (f8->f8 identity is handled below.)
238+
#define __TO_OUTPUT_REORDER_TYPE(res) TO_OUTPUT_REORDER_TYPE(convert_float(res))
235239
#else
236240
#define __TO_OUTPUT_REORDER_TYPE(res) TO_OUTPUT_REORDER_TYPE(res)
237241
#endif
@@ -261,6 +265,10 @@ KERNEL (reorder_data)(
261265

262266
atomic_and(&output_u32[main_idx], ~(0x0F << shift));
263267
atomic_or(&output_u32[main_idx], (val_u32 << shift));
268+
#elif (F8E5M2_INPUT && F8E5M2_OUTPUT) || (F8E4M3_INPUT && F8E4M3_OUTPUT) || (F8E8M0_INPUT && F8E8M0_OUTPUT)
269+
// f8->f8 layout reorder: `res` is already the fp8 OUTPUT_TYPE, so store it directly. Re-encoding
270+
// via TO_OUTPUT_TYPE/ACTIVATION has no overload for the fp8 struct (and a copy needs none).
271+
output[output_idx] = res;
264272
#else
265273
output[output_idx] = ACTIVATION_TYPED(OUTPUT_REORDER, __TO_OUTPUT_REORDER_TYPE(res), ACTIVATION_PARAMS_TYPED);
266274
#endif

src/plugins/intel_gpu/src/kernel_selector/cl_kernels/scatter_update_ref.cl

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
//
44

55
#include "include/batch_headers/fetch_data.cl"
6+
#include "include/f8_utils.cl" // fp8e4m3_t typedef
67

78
#define AXIS_B (0)
89
#define AXIS_F (1)
@@ -132,6 +133,9 @@ KERNEL(scatter_update_ref)(OPTIONAL_SHAPE_INFO_ARG
132133
#if HAS_FUSED_OPS
133134
FUSED_OPS_FIRST_KERNEL;
134135
output[output_idx] = TO_OUTPUT_TYPE(FUSED_OPS_RESULT_FIRST_KERNEL);
136+
#elif INPUT0_IS_FP8
137+
// fp8 is a 1-byte struct; ScatterUpdate just moves data, so copy the byte (ACTIVATION won't compile on it).
138+
output[output_idx] = val;
135139
#else
136140
output[output_idx] = ACTIVATION(val, ACTIVATION_PARAMS);
137141
#endif
@@ -232,6 +236,9 @@ KERNEL(scatter_update_ref)(OPTIONAL_SHAPE_INFO_ARG
232236
#if HAS_FUSED_OPS
233237
FUSED_OPS_SECOND_KERNEL;
234238
output[output_idx] = TO_OUTPUT_TYPE(FUSED_OPS_RESULT_SECOND_KERNEL);
239+
#elif INPUT2_IS_FP8
240+
// fp8 is a 1-byte struct; ScatterUpdate just moves data, so copy the byte (ACTIVATION won't compile on it).
241+
output[output_idx] = val;
235242
#else
236243
output[output_idx] = ACTIVATION(val, ACTIVATION_PARAMS);
237244
#endif

src/plugins/intel_gpu/src/kernel_selector/cl_kernels/slice_ref.cl

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
//
44

55
#include "include/batch_headers/fetch_data.cl"
6+
#include "include/f8_utils.cl" // fp8e4m3_t typedef
67

78
#define BRING_INTO_RANGE(VAL, MAX) \
89
clamp((long)VAL < 0l ? (long)VAL + (long)MAX : (long)VAL, 0l, (long)MAX-1l);
@@ -84,7 +85,12 @@ KERNEL(slice_ref)(OPTIONAL_SHAPE_INFO_ARG
8485
slice_begin_dim4 + output_dim4 * slice_step[4]);
8586
#endif
8687

88+
#if INPUT0_IS_FP8
89+
// fp8 is a 1-byte struct; Slice just moves data, so copy the byte (ACTIVATION won't compile on it).
90+
output[output_index] = input[input_index];
91+
#else
8792
output[output_index] = ACTIVATION(input[input_index], ACTIVATION_PARAMS);
93+
#endif
8894
}
8995

9096
#undef LOAD_BUFFER;

src/plugins/intel_gpu/src/kernel_selector/kernels/concatenation/concatenation_kernel_ref.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,14 @@ ParamsKey ConcatenationKernelRef::GetSupportedKey() const {
1717
k.EnableInputDataType(Datatype::UINT8);
1818
k.EnableInputDataType(Datatype::INT32);
1919
k.EnableInputDataType(Datatype::INT64);
20+
k.EnableInputDataType(Datatype::F8E4M3);
2021
k.EnableOutputDataType(Datatype::F16);
2122
k.EnableOutputDataType(Datatype::F32);
2223
k.EnableOutputDataType(Datatype::INT8);
2324
k.EnableOutputDataType(Datatype::UINT8);
2425
k.EnableOutputDataType(Datatype::INT32);
2526
k.EnableOutputDataType(Datatype::INT64);
27+
k.EnableOutputDataType(Datatype::F8E4M3);
2628
k.EnableInputLayout(DataLayout::bf);
2729
k.EnableInputLayout(DataLayout::fb);
2830
k.EnableInputLayout(DataLayout::bfyx);
@@ -66,6 +68,10 @@ JitConstants ConcatenationKernelRef::GetJitConstants(const concatenation_params&
6668
auto cldnnJit = ConcatenationKernelBase::GetJitConstants(params);
6769
auto input_format = params.inputs[0].GetLayout();
6870

71+
// Flag fp8 input so the kernel copies the byte instead of running ACTIVATION on the fp8 struct
72+
// (see concatenation_gpu_ref.cl).
73+
cldnnJit.AddConstant(MakeJitConstant("INPUT0_IS_FP8", params.inputs[0].GetDType() == Datatype::F8E4M3));
74+
6975
if (params.inputs[0].Feature().v != 1) {
7076
cldnnJit.AddConstant(MakeJitConstant("CHECK_FEATURES", 1));
7177
int f_channel = DataTensor::Channelndex(params.outputs[0].GetLayout(), Tensor::DataChannelName::FEATURE);

src/plugins/intel_gpu/src/kernel_selector/kernels/scatter_update/scatter_update_kernel_ref.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,13 @@ ParamsKey ScatterUpdateKernelRef::GetSupportedKey() const {
3939
k.EnableInputDataType(Datatype::INT32);
4040
k.EnableInputDataType(Datatype::INT8);
4141
k.EnableInputDataType(Datatype::UINT8);
42+
k.EnableInputDataType(Datatype::F8E4M3);
4243
k.EnableOutputDataType(Datatype::F16);
4344
k.EnableOutputDataType(Datatype::F32);
4445
k.EnableOutputDataType(Datatype::INT32);
4546
k.EnableOutputDataType(Datatype::INT8);
4647
k.EnableOutputDataType(Datatype::UINT8);
48+
k.EnableOutputDataType(Datatype::F8E4M3);
4749
k.EnableAllInputLayout();
4850
k.EnableAllOutputLayout();
4951
k.EnableTensorOffset();
@@ -186,6 +188,11 @@ JitConstants ScatterUpdateKernelRef::GetJitConstants(const scatter_update_params
186188
JitConstants jit = MakeBaseParamsJitConstants(params);
187189
size_t axis_value = GetScatterUpdateChannelIndex(params);
188190

191+
// Flag fp8 inputs so the kernel copies the byte instead of running ACTIVATION on the fp8 struct
192+
// (see scatter_update_ref.cl). INPUT0 = dictionary (first kernel), INPUT2 = updates (second).
193+
jit.AddConstant(MakeJitConstant("INPUT0_IS_FP8", params.inputs[0].GetDType() == Datatype::F8E4M3));
194+
jit.AddConstant(MakeJitConstant("INPUT2_IS_FP8", params.inputs[2].GetDType() == Datatype::F8E4M3));
195+
189196
const auto input2_has_padding = params.inputs[2].has_dynamic_pad() || params.inputs[2].PitchesDifferFromLogicalDims();
190197

191198
// In case of padded input2 (updates), we also need non-planar indexing, because UPDATES_INDEX is calculated based on output sizes

0 commit comments

Comments
 (0)