Skip to content

Commit ce7ff11

Browse files
authored
Support bf16-out quantized embeddings on CPU (#20707)
### Summary Enable bf16 in embedding_byte and sub-byte ops. This is a relatively simple change as templating of the actual kernels/impls make it pretty much work out of box. ## Test Plan I've added test coverage for bf16 2, 4, and 8-bit embeddings.
1 parent 411a01e commit ce7ff11

7 files changed

Lines changed: 274 additions & 40 deletions

File tree

exir/passes/_quant_patterns_and_replacements.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,8 @@ def embedding_weight_checks(weight, weight_scales, weight_zero_points):
108108
assert weight_scales.dtype in [
109109
torch.float16,
110110
torch.float32,
111-
], f"Expecting weight_scales to be of dtype in [torch.float16, torch.float32], but got {weight_scales.dtype}"
111+
torch.bfloat16,
112+
], f"Expecting weight_scales to be of dtype in [torch.float16, torch.float32, torch.bfloat16], but got {weight_scales.dtype}"
112113
assert (
113114
weight_scales.dim() == 1 or weight_scales.dim() == 2
114115
), f"Expecting weight_scales tensor to have rank 1 or 2, but found {weight_scales.dim()}"

exir/tests/test_quant_fusion_pass.py

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -389,7 +389,29 @@ def test_embedding_torchao(self) -> None:
389389
[MappingType.SYMMETRIC, MappingType.ASYMMETRIC],
390390
):
391391
self._test_embedding_torchao(
392-
bit_width, use_dtype_variant, test_per_group, mapping_type
392+
bit_width,
393+
use_dtype_variant,
394+
test_per_group,
395+
mapping_type,
396+
dtype=torch.float16,
397+
)
398+
399+
# bfloat16 mirrors the float16 (dtype-variant) path across bit widths.
400+
for bit_width, test_per_group, mapping_type in zip(
401+
[2, 4, 8],
402+
[True, False, True],
403+
[
404+
MappingType.SYMMETRIC,
405+
MappingType.ASYMMETRIC,
406+
MappingType.SYMMETRIC,
407+
],
408+
):
409+
self._test_embedding_torchao(
410+
bit_width,
411+
use_dtype_variant=True,
412+
test_per_group=test_per_group,
413+
mapping_type=mapping_type,
414+
dtype=torch.bfloat16,
393415
)
394416

395417
def _test_embedding_torchao(
@@ -398,6 +420,7 @@ def _test_embedding_torchao(
398420
use_dtype_variant: bool,
399421
test_per_group: bool,
400422
mapping_type: MappingType,
423+
dtype: torch.dtype,
401424
) -> None:
402425
assert bit_width in [2, 4, 8]
403426
embedding_suffix = f"{bit_width}bit" if bit_width < 8 else "byte"
@@ -412,7 +435,7 @@ def _test_embedding_torchao(
412435

413436
# torchao adds a dtype cast to match embeddings original weight type
414437
# this does not happen for float32 because it is the default dtype
415-
model = model.to(torch.float16) if use_dtype_variant else model
438+
model = model.to(dtype) if use_dtype_variant else model
416439

417440
# quantize the model
418441
granularity = PerGroup(32) if test_per_group else PerAxis(0)

kernels/quantized/cpu/embeddingxb.cpp

Lines changed: 32 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -104,13 +104,15 @@ void check_embedding_xbit_args(
104104

105105
ET_CHECK_MSG(
106106
out.scalar_type() == ScalarType::Float ||
107-
out.scalar_type() == ScalarType::Half,
107+
out.scalar_type() == ScalarType::Half ||
108+
out.scalar_type() == ScalarType::BFloat16,
108109
"out.scalar_type() %" PRId8 " is not supported:",
109110
static_cast<int8_t>(out.scalar_type()));
110111

111112
ET_CHECK_MSG(
112113
weight_scales.scalar_type() == ScalarType::Float ||
113-
weight_scales.scalar_type() == ScalarType::Half,
114+
weight_scales.scalar_type() == ScalarType::Half ||
115+
weight_scales.scalar_type() == ScalarType::BFloat16,
114116
"weight_scales.scalar_type() %" PRId8 " is not supported:",
115117
static_cast<int8_t>(weight_scales.scalar_type()));
116118

@@ -284,17 +286,19 @@ Tensor& quantized_embedding_xbit_out(
284286

285287
constexpr auto name = "quantized_decomposed::embedding_xbit.out";
286288
ScalarType indices_type = indices.scalar_type();
287-
ET_SWITCH_TWO_TYPES(Float, Half, out_type, ctx, name, CTYPE_OUT, [&]() {
288-
ET_SWITCH_TWO_TYPES(Int, Long, indices_type, ctx, name, CTYPE_IDX, [&]() {
289-
embedding_xbit_per_channel<CTYPE_OUT, CTYPE_OUT, CTYPE_IDX>(
290-
weight,
291-
weight_scales,
292-
opt_weight_zero_points,
293-
indices,
294-
out,
295-
weight_nbit);
296-
});
297-
});
289+
ET_SWITCH_THREE_TYPES(
290+
Float, Half, BFloat16, out_type, ctx, name, CTYPE_OUT, [&]() {
291+
ET_SWITCH_TWO_TYPES(
292+
Int, Long, indices_type, ctx, name, CTYPE_IDX, [&]() {
293+
embedding_xbit_per_channel<CTYPE_OUT, CTYPE_OUT, CTYPE_IDX>(
294+
weight,
295+
weight_scales,
296+
opt_weight_zero_points,
297+
indices,
298+
out,
299+
weight_nbit);
300+
});
301+
});
298302

299303
return out;
300304
}
@@ -358,19 +362,22 @@ Tensor& quantized_embedding_xbit_dtype_out(
358362

359363
constexpr auto name = "quantized_decomposed::embedding_xbit.dtype_out";
360364
ScalarType indices_type = indices.scalar_type();
361-
ET_SWITCH_TWO_TYPES(Float, Half, params_type, ctx, name, CTYPE_P, [&]() {
362-
ET_SWITCH_TWO_TYPES(Float, Half, out_type, ctx, name, CTYPE_OUT, [&]() {
363-
ET_SWITCH_TWO_TYPES(Int, Long, indices_type, ctx, name, CTYPE_IDX, [&]() {
364-
embedding_xbit_per_channel<CTYPE_P, CTYPE_OUT, CTYPE_IDX>(
365-
weight,
366-
weight_scales,
367-
opt_weight_zero_points,
368-
indices,
369-
out,
370-
weight_nbit);
365+
ET_SWITCH_THREE_TYPES(
366+
Float, Half, BFloat16, params_type, ctx, name, CTYPE_P, [&]() {
367+
ET_SWITCH_THREE_TYPES(
368+
Float, Half, BFloat16, out_type, ctx, name, CTYPE_OUT, [&]() {
369+
ET_SWITCH_TWO_TYPES(
370+
Int, Long, indices_type, ctx, name, CTYPE_IDX, [&]() {
371+
embedding_xbit_per_channel<CTYPE_P, CTYPE_OUT, CTYPE_IDX>(
372+
weight,
373+
weight_scales,
374+
opt_weight_zero_points,
375+
indices,
376+
out,
377+
weight_nbit);
378+
});
379+
});
371380
});
372-
});
373-
});
374381

375382
return out;
376383
}

kernels/quantized/cpu/op_embedding.cpp

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -66,13 +66,15 @@ void check_embedding_byte_args(
6666

6767
ET_CHECK_MSG(
6868
out.scalar_type() == ScalarType::Float ||
69-
out.scalar_type() == ScalarType::Half,
69+
out.scalar_type() == ScalarType::Half ||
70+
out.scalar_type() == ScalarType::BFloat16,
7071
"out.scalar_type() %" PRId8 " is not supported:",
7172
static_cast<int8_t>(out.scalar_type()));
7273

7374
ET_CHECK_MSG(
7475
weight_scales.scalar_type() == ScalarType::Float ||
75-
weight_scales.scalar_type() == ScalarType::Half,
76+
weight_scales.scalar_type() == ScalarType::Half ||
77+
weight_scales.scalar_type() == ScalarType::BFloat16,
7678
"weight_scales.scalar_type() %" PRId8 " is not supported:",
7779
static_cast<int8_t>(weight_scales.scalar_type()));
7880

@@ -259,10 +261,11 @@ Tensor& quantized_embedding_byte_out(
259261

260262
constexpr auto name = "quantized_decomposed::embedding_byte.out";
261263
ET_SWITCH_TWO_TYPES(Byte, Char, w_type, ctx, name, CTYPE_W, [&]() {
262-
ET_SWITCH_TWO_TYPES(Float, Half, out_type, ctx, name, CTYPE_OUT, [&]() {
263-
embedding_byte_per_channel<CTYPE_W, CTYPE_OUT, CTYPE_OUT>(
264-
weight, weight_scales, opt_weight_zero_points, indices, out);
265-
});
264+
ET_SWITCH_THREE_TYPES(
265+
Float, Half, BFloat16, out_type, ctx, name, CTYPE_OUT, [&]() {
266+
embedding_byte_per_channel<CTYPE_W, CTYPE_OUT, CTYPE_OUT>(
267+
weight, weight_scales, opt_weight_zero_points, indices, out);
268+
});
266269
});
267270

268271
return out;
@@ -324,12 +327,18 @@ Tensor& quantized_embedding_byte_dtype_out(
324327

325328
constexpr auto name = "quantized_decomposed::embedding_byte.dtype_out";
326329
ET_SWITCH_TWO_TYPES(Byte, Char, weight_type, ctx, name, CTYPE_W, [&]() {
327-
ET_SWITCH_TWO_TYPES(Float, Half, params_type, ctx, name, CTYPE_P, [&]() {
328-
ET_SWITCH_TWO_TYPES(Float, Half, out_type, ctx, name, CTYPE_OUT, [&]() {
329-
embedding_byte_per_channel<CTYPE_W, CTYPE_P, CTYPE_OUT>(
330-
weight, weight_scales, opt_weight_zero_points, indices, out);
331-
});
332-
});
330+
ET_SWITCH_THREE_TYPES(
331+
Float, Half, BFloat16, params_type, ctx, name, CTYPE_P, [&]() {
332+
ET_SWITCH_THREE_TYPES(
333+
Float, Half, BFloat16, out_type, ctx, name, CTYPE_OUT, [&]() {
334+
embedding_byte_per_channel<CTYPE_W, CTYPE_P, CTYPE_OUT>(
335+
weight,
336+
weight_scales,
337+
opt_weight_zero_points,
338+
indices,
339+
out);
340+
});
341+
});
333342
});
334343

335344
return out;

kernels/quantized/test/op_embedding2b_test.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ using executorch::aten::ScalarType;
2222
using executorch::aten::Tensor;
2323
using executorch::ET_RUNTIME_NAMESPACE::KernelRuntimeContext;
2424
using std::optional;
25+
using torch::executor::native::quantized_embedding_2bit_dtype_out;
2526
using torch::executor::native::quantized_embedding_2bit_out;
2627

2728
using torch::executor::testing::TensorFactory;
@@ -104,6 +105,50 @@ TEST(OpQuantizedEmbedding2bTest, TestGroupWiseQuantizedEmbedding) {
104105
EXPECT_TENSOR_EQ(out, expected);
105106
}
106107

108+
TEST(OpQuantizedEmbedding2bTest, TestGroupWiseQuantizedEmbeddingBFloat16) {
109+
et_pal_init();
110+
TensorFactory<ScalarType::Byte> tfb;
111+
TensorFactory<ScalarType::BFloat16> tf;
112+
TensorFactory<ScalarType::Long> tfl;
113+
114+
int64_t quant_min = -2;
115+
int64_t quant_max = 1;
116+
117+
Tensor weight_scales = tf.make({3}, {0.5, 1.0, 1.5});
118+
Tensor weight_zero_points = tf.make({3}, {1, -2, 0});
119+
Tensor qweight = tfb.make({3, 1}, {236, 134, 228});
120+
Tensor indices = tfl.make({3}, {0, 2, 1});
121+
122+
Tensor out = tf.zeros({3, 4});
123+
Tensor expected = tf.make(
124+
{3, 4}, {-1.5, 0.0, -0.5, 0.0, -3.0, -1.5, 0.0, 1.5, 2.0, 1.0, 0.0, 2.0});
125+
126+
quantized_embedding_2bit_out(
127+
qweight,
128+
weight_scales,
129+
weight_zero_points,
130+
quant_min,
131+
quant_max,
132+
indices,
133+
out);
134+
135+
EXPECT_TENSOR_CLOSE(out, expected);
136+
137+
// Same values through the dtype_out variant.
138+
out = tf.zeros({3, 4});
139+
quantized_embedding_2bit_dtype_out(
140+
qweight,
141+
weight_scales,
142+
weight_zero_points,
143+
quant_min,
144+
quant_max,
145+
indices,
146+
ScalarType::BFloat16,
147+
out);
148+
149+
EXPECT_TENSOR_CLOSE(out, expected);
150+
}
151+
107152
TEST(OpQuantizedEmbedding2bTest, TestGroupWiseQuantizedEmbeddingInt32Indices) {
108153
et_pal_init();
109154
TensorFactory<ScalarType::Byte> tfb;

kernels/quantized/test/op_embedding4b_test.cpp

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ using executorch::aten::ScalarType;
2121
using executorch::aten::Tensor;
2222
using executorch::ET_RUNTIME_NAMESPACE::KernelRuntimeContext;
2323
using std::optional;
24+
using torch::executor::native::quantized_embedding_4bit_dtype_out;
2425
using torch::executor::native::quantized_embedding_4bit_out;
2526

2627
using torch::executor::testing::TensorFactory;
@@ -173,6 +174,50 @@ TEST(OpQuantizedEmbedding4bTest, TestGroupWiseQuantizedEmbeddingDeath1) {
173174
"");
174175
}
175176

177+
TEST(OpQuantizedEmbedding4bTest, TestGroupWiseQuantizedEmbeddingBFloat16) {
178+
et_pal_init();
179+
TensorFactory<ScalarType::Byte> tfb;
180+
TensorFactory<ScalarType::BFloat16> tf;
181+
TensorFactory<ScalarType::Long> tfl;
182+
183+
int64_t quant_min = -8;
184+
int64_t quant_max = 7;
185+
186+
Tensor weight_scales = tf.make({3}, {0.5, 1.0, 1.5});
187+
Tensor weight_zero_points = tf.make({3}, {1, -5, 0});
188+
Tensor qweight = tfb.make({3, 2}, {89, 239, 163, 72, 11, 126});
189+
Tensor indices = tfl.make({3}, {0, 2, 1});
190+
191+
Tensor out = tf.zeros({3, 4});
192+
Tensor expected = tf.make(
193+
{3, 4}, {-2.0, 0.0, 2.5, 3.0, -12.0, 4.5, -1.5, 9.0, 7.0, 0.0, 1.0, 5.0});
194+
195+
quantized_embedding_4bit_out(
196+
qweight,
197+
weight_scales,
198+
weight_zero_points,
199+
quant_min,
200+
quant_max,
201+
indices,
202+
out);
203+
204+
EXPECT_TENSOR_CLOSE(out, expected);
205+
206+
// Same values through the dtype_out variant.
207+
out = tf.zeros({3, 4});
208+
quantized_embedding_4bit_dtype_out(
209+
qweight,
210+
weight_scales,
211+
weight_zero_points,
212+
quant_min,
213+
quant_max,
214+
indices,
215+
ScalarType::BFloat16,
216+
out);
217+
218+
EXPECT_TENSOR_CLOSE(out, expected);
219+
}
220+
176221
TEST(OpQuantizedEmbedding4bTest, TestGroupWiseQuantizedEmbeddingDeath2) {
177222
et_pal_init();
178223
TensorFactory<ScalarType::Byte> tfb;

0 commit comments

Comments
 (0)