Skip to content

Commit 33c0ba5

Browse files
Add SQ8bit signed quantization (#3501)
Summary: ### Description Add new signed 8 bit scalar quantizer, `QT_8bit_direct_signed` to ingest signed 8 bit vectors ([-128 to 127]). ### Issues Resolved #3488 Pull Request resolved: #3501 Reviewed By: mengdilin Differential Revision: D58639363 Pulled By: mdouze fbshipit-source-id: cf7f244fdbb7a34051d2b20c6f8086cd5628b4e0
1 parent da75d03 commit 33c0ba5

7 files changed

Lines changed: 130 additions & 17 deletions

File tree

benchs/bench_fw/optimize.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -228,6 +228,7 @@ def optimize_codec(
228228
(None, "SQfp16"),
229229
(None, "SQbf16"),
230230
(None, "SQ8"),
231+
(None, "SQ8_direct_signed"),
231232
] + [
232233
(f"OPQ{M}_{M * dim}", f"PQ{M}x{b}")
233234
for M in [8, 12, 16, 32, 48, 64, 96, 128, 192, 256]

c_api/IndexScalarQuantizer_c.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ typedef enum FaissQuantizerType {
2727
QT_8bit_direct, ///< fast indexing of uint8s
2828
QT_6bit, ///< 6 bits per component
2929
QT_bf16,
30+
QT_8bit_direct_signed, ///< fast indexing of signed int8s ranging from [-128
31+
///< to 127]
3032
} FaissQuantizerType;
3133

3234
// forward declaration

faiss/IndexScalarQuantizer.cpp

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,8 @@ IndexScalarQuantizer::IndexScalarQuantizer(
3333
: IndexFlatCodes(0, d, metric), sq(d, qtype) {
3434
is_trained = qtype == ScalarQuantizer::QT_fp16 ||
3535
qtype == ScalarQuantizer::QT_8bit_direct ||
36-
qtype == ScalarQuantizer::QT_bf16;
36+
qtype == ScalarQuantizer::QT_bf16 ||
37+
qtype == ScalarQuantizer::QT_8bit_direct_signed;
3738
code_size = sq.code_size;
3839
}
3940

faiss/impl/ScalarQuantizer.cpp

Lines changed: 97 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -621,13 +621,90 @@ struct Quantizer8bitDirect<8> : Quantizer8bitDirect<1> {
621621

622622
FAISS_ALWAYS_INLINE float32x4x2_t
623623
reconstruct_8_components(const uint8_t* code, int i) const {
624-
float32_t result[8] = {};
625-
for (size_t j = 0; j < 8; j++) {
626-
result[j] = code[i + j];
624+
uint8x8_t x8 = vld1_u8((const uint8_t*)(code + i));
625+
uint16x8_t y8 = vmovl_u8(x8);
626+
uint16x4_t y8_0 = vget_low_u16(y8);
627+
uint16x4_t y8_1 = vget_high_u16(y8);
628+
629+
// convert uint16 -> uint32 -> fp32
630+
return {vcvtq_f32_u32(vmovl_u16(y8_0)), vcvtq_f32_u32(vmovl_u16(y8_1))};
631+
}
632+
};
633+
634+
#endif
635+
636+
/*******************************************************************
637+
* 8bit_direct_signed quantizer
638+
*******************************************************************/
639+
640+
template <int SIMDWIDTH>
641+
struct Quantizer8bitDirectSigned {};
642+
643+
template <>
644+
struct Quantizer8bitDirectSigned<1> : ScalarQuantizer::SQuantizer {
645+
const size_t d;
646+
647+
Quantizer8bitDirectSigned(size_t d, const std::vector<float>& /* unused */)
648+
: d(d) {}
649+
650+
void encode_vector(const float* x, uint8_t* code) const final {
651+
for (size_t i = 0; i < d; i++) {
652+
code[i] = (uint8_t)(x[i] + 128);
627653
}
628-
float32x4_t res1 = vld1q_f32(result);
629-
float32x4_t res2 = vld1q_f32(result + 4);
630-
return {res1, res2};
654+
}
655+
656+
void decode_vector(const uint8_t* code, float* x) const final {
657+
for (size_t i = 0; i < d; i++) {
658+
x[i] = code[i] - 128;
659+
}
660+
}
661+
662+
FAISS_ALWAYS_INLINE float reconstruct_component(const uint8_t* code, int i)
663+
const {
664+
return code[i] - 128;
665+
}
666+
};
667+
668+
#ifdef __AVX2__
669+
670+
template <>
671+
struct Quantizer8bitDirectSigned<8> : Quantizer8bitDirectSigned<1> {
672+
Quantizer8bitDirectSigned(size_t d, const std::vector<float>& trained)
673+
: Quantizer8bitDirectSigned<1>(d, trained) {}
674+
675+
FAISS_ALWAYS_INLINE __m256
676+
reconstruct_8_components(const uint8_t* code, int i) const {
677+
__m128i x8 = _mm_loadl_epi64((__m128i*)(code + i)); // 8 * int8
678+
__m256i y8 = _mm256_cvtepu8_epi32(x8); // 8 * int32
679+
__m256i c8 = _mm256_set1_epi32(128);
680+
__m256i z8 = _mm256_sub_epi32(y8, c8); // subtract 128 from all lanes
681+
return _mm256_cvtepi32_ps(z8); // 8 * float32
682+
}
683+
};
684+
685+
#endif
686+
687+
#ifdef __aarch64__
688+
689+
template <>
690+
struct Quantizer8bitDirectSigned<8> : Quantizer8bitDirectSigned<1> {
691+
Quantizer8bitDirectSigned(size_t d, const std::vector<float>& trained)
692+
: Quantizer8bitDirectSigned<1>(d, trained) {}
693+
694+
FAISS_ALWAYS_INLINE float32x4x2_t
695+
reconstruct_8_components(const uint8_t* code, int i) const {
696+
uint8x8_t x8 = vld1_u8((const uint8_t*)(code + i));
697+
uint16x8_t y8 = vmovl_u8(x8); // convert uint8 -> uint16
698+
uint16x4_t y8_0 = vget_low_u16(y8);
699+
uint16x4_t y8_1 = vget_high_u16(y8);
700+
701+
float32x4_t z8_0 = vcvtq_f32_u32(
702+
vmovl_u16(y8_0)); // convert uint16 -> uint32 -> fp32
703+
float32x4_t z8_1 = vcvtq_f32_u32(vmovl_u16(y8_1));
704+
705+
// subtract 128 to convert into signed numbers
706+
return {vsubq_f32(z8_0, vmovq_n_f32(128.0)),
707+
vsubq_f32(z8_1, vmovq_n_f32(128.0))};
631708
}
632709
};
633710

@@ -660,6 +737,8 @@ ScalarQuantizer::SQuantizer* select_quantizer_1(
660737
return new QuantizerBF16<SIMDWIDTH>(d, trained);
661738
case ScalarQuantizer::QT_8bit_direct:
662739
return new Quantizer8bitDirect<SIMDWIDTH>(d, trained);
740+
case ScalarQuantizer::QT_8bit_direct_signed:
741+
return new Quantizer8bitDirectSigned<SIMDWIDTH>(d, trained);
663742
}
664743
FAISS_THROW_MSG("unknown qtype");
665744
}
@@ -1460,6 +1539,11 @@ SQDistanceComputer* select_distance_computer(
14601539
Sim,
14611540
SIMDWIDTH>(d, trained);
14621541
}
1542+
case ScalarQuantizer::QT_8bit_direct_signed:
1543+
return new DCTemplate<
1544+
Quantizer8bitDirectSigned<SIMDWIDTH>,
1545+
Sim,
1546+
SIMDWIDTH>(d, trained);
14631547
}
14641548
FAISS_THROW_MSG("unknown qtype");
14651549
return nullptr;
@@ -1483,6 +1567,7 @@ void ScalarQuantizer::set_derived_sizes() {
14831567
case QT_8bit:
14841568
case QT_8bit_uniform:
14851569
case QT_8bit_direct:
1570+
case QT_8bit_direct_signed:
14861571
code_size = d;
14871572
bits = 8;
14881573
break;
@@ -1540,6 +1625,7 @@ void ScalarQuantizer::train(size_t n, const float* x) {
15401625
case QT_fp16:
15411626
case QT_8bit_direct:
15421627
case QT_bf16:
1628+
case QT_8bit_direct_signed:
15431629
// no training necessary
15441630
break;
15451631
}
@@ -1885,6 +1971,11 @@ InvertedListScanner* sel1_InvertedListScanner(
18851971
Similarity,
18861972
SIMDWIDTH>>(sq, quantizer, store_pairs, sel, r);
18871973
}
1974+
case ScalarQuantizer::QT_8bit_direct_signed:
1975+
return sel2_InvertedListScanner<DCTemplate<
1976+
Quantizer8bitDirectSigned<SIMDWIDTH>,
1977+
Similarity,
1978+
SIMDWIDTH>>(sq, quantizer, store_pairs, sel, r);
18881979
}
18891980

18901981
FAISS_THROW_MSG("unknown qtype");

faiss/impl/ScalarQuantizer.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,8 @@ struct ScalarQuantizer : Quantizer {
3333
QT_8bit_direct, ///< fast indexing of uint8s
3434
QT_6bit, ///< 6 bits per component
3535
QT_bf16,
36+
QT_8bit_direct_signed, ///< fast indexing of signed int8s ranging from
37+
///< [-128 to 127]
3638
};
3739

3840
QuantizerType qtype = QT_8bit;

faiss/index_factory.cpp

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -141,8 +141,11 @@ std::map<std::string, ScalarQuantizer::QuantizerType> sq_types = {
141141
{"SQ6", ScalarQuantizer::QT_6bit},
142142
{"SQfp16", ScalarQuantizer::QT_fp16},
143143
{"SQbf16", ScalarQuantizer::QT_bf16},
144+
{"SQ8_direct_signed", ScalarQuantizer::QT_8bit_direct_signed},
145+
{"SQ8_direct", ScalarQuantizer::QT_8bit_direct},
144146
};
145-
const std::string sq_pattern = "(SQ4|SQ8|SQ6|SQfp16|SQbf16)";
147+
const std::string sq_pattern =
148+
"(SQ4|SQ8|SQ6|SQfp16|SQbf16|SQ8_direct_signed|SQ8_direct)";
146149

147150
std::map<std::string, AdditiveQuantizer::Search_type_t> aq_search_type = {
148151
{"_Nfloat", AdditiveQuantizer::ST_norm_float},

tests/test_index_accuracy.py

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -312,7 +312,7 @@ def test_parallel_mode(self):
312312

313313

314314
class TestSQByte(unittest.TestCase):
315-
def subtest_8bit_direct(self, metric_type, d):
315+
def subtest_8bit_direct(self, metric_type, d, quantizer_type):
316316
xt, xb, xq = get_dataset_2(d, 500, 1000, 30)
317317

318318
# rescale everything to get integer
@@ -324,16 +324,28 @@ def rescale(x):
324324
x[x > 255] = 255
325325
return x
326326

327-
xt = rescale(xt)
328-
xb = rescale(xb)
329-
xq = rescale(xq)
327+
def rescale_signed(x):
328+
x = np.floor((x - tmin) * 256 / (tmax - tmin))
329+
x[x < 0] = 0
330+
x[x > 255] = 255
331+
x -= 128
332+
return x
333+
334+
if quantizer_type == faiss.ScalarQuantizer.QT_8bit_direct_signed:
335+
xt = rescale_signed(xt)
336+
xb = rescale_signed(xb)
337+
xq = rescale_signed(xq)
338+
else:
339+
xt = rescale(xt)
340+
xb = rescale(xb)
341+
xq = rescale(xq)
330342

331343
gt_index = faiss.IndexFlat(d, metric_type)
332344
gt_index.add(xb)
333345
Dref, Iref = gt_index.search(xq, 10)
334346

335347
index = faiss.IndexScalarQuantizer(
336-
d, faiss.ScalarQuantizer.QT_8bit_direct, metric_type
348+
d, quantizer_type, metric_type
337349
)
338350
index.add(xb)
339351
D, I = index.search(xq, 10)
@@ -353,7 +365,7 @@ def rescale(x):
353365
Dref, Iref = gt_index.search(xq, 10)
354366

355367
index = faiss.IndexIVFScalarQuantizer(
356-
quantizer, d, nlist, faiss.ScalarQuantizer.QT_8bit_direct,
368+
quantizer, d, nlist, quantizer_type,
357369
metric_type
358370
)
359371
index.nprobe = 4
@@ -366,9 +378,10 @@ def rescale(x):
366378
assert np.all(D == Dref)
367379

368380
def test_8bit_direct(self):
369-
for d in 13, 16, 24:
370-
for metric_type in faiss.METRIC_L2, faiss.METRIC_INNER_PRODUCT:
371-
self.subtest_8bit_direct(metric_type, d)
381+
for quantizer in faiss.ScalarQuantizer.QT_8bit_direct, faiss.ScalarQuantizer.QT_8bit_direct_signed:
382+
for d in 13, 16, 24:
383+
for metric_type in faiss.METRIC_L2, faiss.METRIC_INNER_PRODUCT:
384+
self.subtest_8bit_direct(metric_type, d, quantizer)
372385

373386

374387
class TestNNDescent(unittest.TestCase):

0 commit comments

Comments
 (0)