Skip to content

Commit da381d5

Browse files
authored
Implement RowwiseQuantizedSparseLengthsWeightedSum (#2282)
*Description*: Implement RowwiseQuantizedSparseLengthsWeightedSum. Added support for both Interpreter and CPU backends. *Testing*: Added unit tests for both RowwiseQuantizedSparseLengthsWeightedSum and RowwiseQuantizedSparseLengthsSum (implemented as the first but with weights as a float splat of 1.0). *Documentation*: Added to Quantization.md Related to #1698
1 parent be5a4e4 commit da381d5

File tree

12 files changed

+391
-33
lines changed

12 files changed

+391
-33
lines changed

docs/Quantization.md

Lines changed: 33 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -57,16 +57,16 @@ inference. Then, we recompile the network using this profile information to
5757
convert the network into a quantized form, allowing for static optimization of
5858
the quantized graph. We convert portions of the network into islands of integer
5959
computation and aim to generate outputs in the range that the original
60-
floating-point network produces. During the conversion, for the following types
61-
of quantized nodes, we ignore the output's quantization params (if they are
62-
provided) and force the output have the same quantization params as the input
60+
floating-point network produces. During the conversion, for the following types
61+
of quantized nodes, we ignore the output's quantization params (if they are
62+
provided) and force the output have the same quantization params as the input
6363
for performance purpose:
6464
```
65-
LocalResponseNormalizationNode
66-
SliceNode
67-
ReshapeNode
68-
TopKNode
69-
GatherNode
65+
LocalResponseNormalizationNode
66+
SliceNode
67+
ReshapeNode
68+
TopKNode
69+
GatherNode
7070
MaxPoolNode
7171
```
7272

@@ -131,9 +131,9 @@ By default, target quantization precision is int8. However, precision can be
131131
controlled via command line parameter: `quantization-precision`. There are
132132
two supported values: `Int8` and `Int16`.
133133

134-
## Caffe2 Quantized Model Support
134+
## Caffe2 Quantized Model Support
135135

136-
Glow is able to support Caffe2 Resnet50 quantized model:
136+
Glow is able to support Caffe2 Resnet50 quantized model:
137137
https://github.com/caffe2/models/tree/master/resnet50_quantized
138138

139139
To support Caffe2 quantized models, Glow has:
@@ -152,16 +152,16 @@ Int8GivenTensorFill
152152
```
153153
- Supported int32 quantized bias.
154154

155-
In most of the cases, bias is quantized in int32 to improve precision
156-
(the partial sum of the matrix-matrix multiplication is accumulated into int32,
157-
so int32 bias can be added to the int32 partial sum for better accuracy).
158-
Glow now supports int32 quantized bias in ```Convolution```, ```FullyConnected```
155+
In most of the cases, bias is quantized in int32 to improve precision
156+
(the partial sum of the matrix-matrix multiplication is accumulated into int32,
157+
so int32 bias can be added to the int32 partial sum for better accuracy).
158+
Glow now supports int32 quantized bias in ```Convolution```, ```FullyConnected```
159159
and ```RowwiseQuantizedFullyConnected``` nodes.
160160

161161
- Supported the conversion from uint8 quantized activations to int8 quantized activations.
162162

163-
For the quantized Caffe2 ops, the activations are quantized to uint8. In Glow, the
164-
activations are quantized to int_8. Therefore, for the offset read from quantized Caffe2
163+
For the quantized Caffe2 ops, the activations are quantized to uint8. In Glow, the
164+
activations are quantized to int_8. Therefore, for the offset read from quantized Caffe2
165165
model, we need to subtract 128(i.e. INT8_MIN) to make the activations become int8.
166166

167167
## Compiler Optimizations
@@ -191,17 +191,24 @@ For more specific graph optimizations check [here](Optimizations.md#quantization
191191

192192
## Row-wise Quantization
193193

194-
Row-wise (or channel-wise) quantization is an important way to minimize accuracy drop.
195-
Glow supports row-wise quantized FullyConnected node ```RowwiseQuantizedFullyConnected```
196-
which is enabled by an image-classifier/loader option "-enable-rowwise".
194+
Row-wise (or channel-wise) quantization is an important way to minimize accuracy
195+
drop. Glow supports row-wise quantized FullyConnected node
196+
```RowwiseQuantizedFullyConnected``` which is enabled by an
197+
image-classifier/loader option "-enable-rowwise".
197198

198-
For the regular quantized FC, we quantize the whole weights tensor with the same
199-
scale and offset, which are computed based on the max and min of the entire tensor).
200-
But for row-wise, after getting ```min_i``` and ```max_i``` for each row ```i```, we compute the pair
201-
of ```(scale_i, offset_i)``` to quantize each element in row ```i```. The figure below shows
202-
the quantized FC node and RowwiseQuantizedFullyConnected node. Instead of using only
203-
one tensor to represent the quantized weights, we need 2 extra vectors ```Scales```
204-
and ```Offsets``` to store the ```(scale, offset)``` for each row.
199+
For the regular quantized FC, we quantize the whole weights tensor with the same
200+
scale and offset, which are computed based on the max and min of the entire
201+
tensor). But for row-wise, after getting ```min_i``` and ```max_i``` for each
202+
row ```i```, we compute the pair of ```(scale_i, offset_i)``` to quantize each
203+
element in row ```i```. The figure below shows the quantized FC node and
204+
RowwiseQuantizedFullyConnected node. Instead of using only one tensor to
205+
represent the quantized weights, we need 2 extra vectors ```Scales``` and
206+
```Offsets``` to store the ```(scale, offset)``` for each row.
205207

206208

207209
![](rowwise_quantized_fc.png)
210+
211+
Row-wise quantized SparseLengthsWeightedSum is also supported. Similar to the
212+
above, we compute scales and offsets per row, to be used with the `Data` input
213+
for the `RowwiseQuantizedSparseLengthsSumNode`. Scales and Offsets are inputs to
214+
the node. Output of this node is float, matching the Caffe2 implementation.

include/glow/Graph/Graph.h

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -565,6 +565,25 @@ class Function final : public Named {
565565
NodeValue data, NodeValue weights,
566566
NodeValue indices, NodeValue lengths);
567567

568+
/// Create a node, performing SparseLengthsSum operation, using rowwise
569+
/// quantization for the input data. Gathers slices of the outer-most
570+
/// dimension of Data indexed by Indices vector, and then accumulates them
571+
/// into len(Lengths) entries: first Lengths[0] slices are aggregated to
572+
/// Result[0], next Lengths[1] slices are aggregated to Result[1],
573+
/// etc. I.e. sum(Lengths) must be equal to len(Indices).
574+
RowwiseQuantizedSparseLengthsWeightedSumNode *
575+
createRowwiseQuantizedSparseLengthsSum(llvm::StringRef name, Tensor &data,
576+
NodeValue indices, NodeValue lengths);
577+
578+
/// Same as \ref createRowwiseQuantizedSparseLengthsSum(), but i-th slice is
579+
/// multiplied by weights[i]. len(weights) must be equal to len(indices).
580+
RowwiseQuantizedSparseLengthsWeightedSumNode *
581+
createRowwiseQuantizedSparseLengthsWeightedSum(llvm::StringRef name,
582+
Tensor &data,
583+
NodeValue weights,
584+
NodeValue indices,
585+
NodeValue lengths);
586+
568587
/// Given a vector of segment lengths, calculates offsets of each segment and
569588
/// packs them next to the lengths. For the input vector of length N the
570589
/// output is a Nx2 matrix with (offset, lengths) packaged for each segment.

include/glow/Quantization/Base/Base.h

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -128,10 +128,11 @@ chooseQuantizationParams(float min, float max, Schema schema = Asymmetric,
128128
std::vector<int8_t> createMapping(TypeRef inTy, TypeRef outTy,
129129
std::function<float(float)> f);
130130

131-
/// Row-wise quantize the tensor \p input. The param \p input is a 2D
132-
/// tensor (i.e. M * N), \p scales and \p offsets are generated by each row of
133-
/// \p input, \p output is 2D tensor quantized from \p input using \p scales
134-
/// and \p offsets for each row.
131+
/// Row-wise quantize the tensor \p input. \p scales and \p offsets are
132+
/// generated by each row of \p input, \p output is tensor of the same shape as
133+
/// input, quantized from \p input using \p scales and \p offsets for each
134+
/// row. Note that the shape of input/output can be any non-zero number of
135+
/// dimensions; row refers to all data in the first dimension of the shape.
135136
void tensorRowwiseQuantization(const Tensor &input, Tensor &output,
136137
Tensor &scales, Tensor &offsets);
137138

lib/Backends/CPU/LLVMIRGen.cpp

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2292,6 +2292,32 @@ void LLVMIRGen::generateLLVMIRForInstr(llvm::IRBuilder<> &builder,
22922292
break;
22932293
}
22942294

2295+
case Kinded::Kind::RowwiseQuantizedSparseLengthsWeightedSumInstKind: {
2296+
auto *N = cast<RowwiseQuantizedSparseLengthsWeightedSumInst>(I);
2297+
auto *dest = N->getDest();
2298+
auto *data = N->getData();
2299+
auto *scales = N->getScales();
2300+
auto *offsets = N->getOffsets();
2301+
auto *weights = N->getWeights();
2302+
auto *indices = N->getIndices();
2303+
auto *lengths = N->getLengths();
2304+
auto *destPtr = emitValueAddress(builder, dest);
2305+
auto *dataPtr = emitValueAddress(builder, data);
2306+
auto *scalesPtr = emitValueAddress(builder, scales);
2307+
auto *offsetsPtr = emitValueAddress(builder, offsets);
2308+
auto *weightsPtr = emitValueAddress(builder, weights);
2309+
auto *indicesPtr = emitValueAddress(builder, indices);
2310+
auto *lengthsPtr = emitValueAddress(builder, lengths);
2311+
auto *segments = emitConstSizeT(builder, lengths->dims()[0]);
2312+
auto *lineSize = emitConstSizeT(builder, data->size() / data->dims()[0]);
2313+
auto *F = getFunction("rowwise_quantized_sparse_lengths_weighted_sum",
2314+
dest->getElementType());
2315+
createCall(builder, F,
2316+
{destPtr, dataPtr, scalesPtr, offsetsPtr, weightsPtr, indicesPtr,
2317+
lengthsPtr, segments, lineSize});
2318+
break;
2319+
}
2320+
22952321
case Kinded::Kind::SparseToDenseInstKind: {
22962322
auto *STDI = llvm::cast<SparseToDenseInst>(I);
22972323
auto *indices = STDI->getIndices();

lib/Backends/CPU/libjit/libjit.cpp

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1037,6 +1037,26 @@ void libjit_sparse_lengths_weighted_sum_f(float *dest, float *data,
10371037
}
10381038
}
10391039

1040+
void libjit_rowwise_quantized_sparse_lengths_weighted_sum_f(
1041+
float *dest, int8_t *data, float *scales, int32_t *offsets, float *weights,
1042+
size_t *indices, int32_t *lengths, size_t segments, size_t lineSize) {
1043+
memset(dest, 0, segments * lineSize * sizeof(float));
1044+
size_t curIndex = 0;
1045+
for (size_t i = 0; i < segments; i++) {
1046+
for (int32_t j = 0; j < lengths[i]; j++) {
1047+
float weight = weights[curIndex];
1048+
size_t line = indices[curIndex];
1049+
const float scale = scales[line];
1050+
const int32_t offset = offsets[line];
1051+
for (size_t k = 0; k < lineSize; k++) {
1052+
const float fData = scale * (data[line * lineSize + k] - offset);
1053+
dest[i * lineSize + k] += weight * fData;
1054+
}
1055+
curIndex++;
1056+
}
1057+
}
1058+
}
1059+
10401060
void libjit_sparse_to_dense_f(float *dest, const size_t *indices,
10411061
const float *values, size_t numIndices,
10421062
size_t destSize, size_t valueSize) {

lib/Backends/Interpreter/InterpreterNodes.cpp

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2097,6 +2097,55 @@ void InterpreterFunction::fwdSparseLengthsWeightedSumInst(
20972097
I->getData()->getElementType(), I);
20982098
}
20992099

2100+
void InterpreterFunction::fwdRowwiseQuantizedSparseLengthsWeightedSumInst(
2101+
const RowwiseQuantizedSparseLengthsWeightedSumInst *I) {
2102+
auto *out = getTensor(I->getDest());
2103+
auto *data = getTensor(I->getData());
2104+
auto *dataScales = getTensor(I->getScales());
2105+
auto *dataOffsets = getTensor(I->getOffsets());
2106+
auto *weights = getTensor(I->getWeights());
2107+
auto *indices = getTensor(I->getIndices());
2108+
auto *lengths = getTensor(I->getLengths());
2109+
2110+
out->zero();
2111+
2112+
auto IH = indices->getHandle<int64_t>();
2113+
auto LH = lengths->getHandle<int32_t>();
2114+
2115+
size_t segments = lengths->dims()[0];
2116+
size_t totalLength = 0;
2117+
for (size_t i = 0; i < segments; i++) {
2118+
totalLength += LH.raw(i);
2119+
}
2120+
assert(totalLength == indices->dims()[0] &&
2121+
"sum(Lengths) must be equal to len(Indices)");
2122+
2123+
size_t lineSize = data->size() / data->dims()[0];
2124+
2125+
auto DH = data->getHandle<int8_t>();
2126+
auto DSH = dataScales->getHandle<float>();
2127+
auto DOH = dataOffsets->getHandle<int32_t>();
2128+
auto WH = weights->getHandle<float>();
2129+
auto OH = out->getHandle<float>();
2130+
2131+
size_t curIdx = 0;
2132+
for (size_t i = 0; i < segments; i++) {
2133+
for (size_t j = 0, e = LH.raw(i); j < e; j++) {
2134+
const float weight = WH.raw(curIdx);
2135+
const size_t rowIdx = IH.raw(curIdx++);
2136+
const float scale = DSH.at({rowIdx});
2137+
const int32_t offset = DOH.at({rowIdx});
2138+
size_t offsetIn = rowIdx * lineSize;
2139+
size_t offsetOut = i * lineSize;
2140+
for (size_t k = 0; k < lineSize; k++) {
2141+
float d = quantization::dequantize(
2142+
DH.raw(offsetIn++), TensorQuantizationParams{scale, offset});
2143+
OH.raw(offsetOut++) += d * weight;
2144+
}
2145+
}
2146+
}
2147+
}
2148+
21002149
void InterpreterFunction::fwdLengthsToRangesInst(const LengthsToRangesInst *I) {
21012150
auto ranges = getTensor(I->getDest())->getHandle<int32_t>();
21022151
auto lengths = getTensor(I->getLengths())->getHandle<int32_t>();

lib/Graph/Graph.cpp

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1369,6 +1369,60 @@ Function::createSparseLengthsWeightedSum(llvm::StringRef name, TypeRef outTy,
13691369
indices, lengths));
13701370
}
13711371

1372+
/// Helper to create a RowwiseQuantizedSparseLengthsWeightedSumNode in the
1373+
/// Function \p F with \p name, using \ data, \p weights, \p indices, and \p
1374+
/// lengths as inputs. The provided float data in \p Tensor is rowwise
1375+
/// quantized, creating Constants for the rowwise quantized data as well as
1376+
/// Scales and Offsets, in the Module containing \p F.
1377+
static RowwiseQuantizedSparseLengthsWeightedSumNode *
1378+
quantizeDataAndCreateRowwiseQuantizedSparseLengthsWeightedSum(
1379+
Function *F, llvm::StringRef name, Tensor &data, NodeValue weights,
1380+
NodeValue indices, NodeValue lengths) {
1381+
auto inDims = data.dims();
1382+
ShapeVector outDims(inDims.begin(), inDims.end());
1383+
outDims[0] = lengths.dims()[0];
1384+
auto outTy = F->getParent()->uniqueType(ElemKind::FloatTy, outDims);
1385+
1386+
// Note: In rwqData, we are using a quantized type, however the scale/offset
1387+
// are set to dummy values 0.0/0. This is because the actually used
1388+
// scale/offset come from dataScales and dataOffsets.
1389+
Constant *rwqData =
1390+
F->getParent()->createConstant(ElemKind::Int8QTy, inDims, 0.0, 0, "data");
1391+
Constant *dataScales = F->getParent()->createConstant(
1392+
ElemKind::FloatTy, {inDims[0]}, "dataScales");
1393+
Constant *dataOffsets = F->getParent()->createConstant(
1394+
ElemKind::Int32ITy, {inDims[0]}, "dataOffsets");
1395+
1396+
quantization::tensorRowwiseQuantization(data, rwqData->getPayload(),
1397+
dataScales->getPayload(),
1398+
dataOffsets->getPayload());
1399+
1400+
return F->addNode(new RowwiseQuantizedSparseLengthsWeightedSumNode(
1401+
name, outTy, rwqData, dataScales, dataOffsets, weights, indices,
1402+
lengths));
1403+
}
1404+
1405+
RowwiseQuantizedSparseLengthsWeightedSumNode *
1406+
Function::createRowwiseQuantizedSparseLengthsWeightedSum(llvm::StringRef name,
1407+
Tensor &data,
1408+
NodeValue weights,
1409+
NodeValue indices,
1410+
NodeValue lengths) {
1411+
return quantizeDataAndCreateRowwiseQuantizedSparseLengthsWeightedSum(
1412+
this, name, data, weights, indices, lengths);
1413+
}
1414+
1415+
RowwiseQuantizedSparseLengthsWeightedSumNode *
1416+
Function::createRowwiseQuantizedSparseLengthsSum(llvm::StringRef name,
1417+
Tensor &data,
1418+
NodeValue indices,
1419+
NodeValue lengths) {
1420+
auto ty = getParent()->uniqueType(ElemKind::FloatTy, {indices.dims()[0]});
1421+
auto ones = createSplat(name.str() + ".ones", ty, 1.0);
1422+
return quantizeDataAndCreateRowwiseQuantizedSparseLengthsWeightedSum(
1423+
this, name, data, ones, indices, lengths);
1424+
}
1425+
13721426
LengthsToRangesNode *Function::createLengthsToRanges(llvm::StringRef name,
13731427
NodeValue lengths) {
13741428
ShapeVector outDims({lengths.dims()[0], 2});

lib/Graph/Nodes.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -717,6 +717,36 @@ bool SparseLengthsWeightedSumNode::verify() const {
717717
return isValid;
718718
}
719719

720+
bool RowwiseQuantizedSparseLengthsWeightedSumNode::verify() const {
721+
bool isValid = checkType(getResult(), ElemKind::FloatTy, this);
722+
isValid &= checkType(getData(), ElemKind::Int8QTy, this);
723+
isValid &= checkType(getScales(), ElemKind::FloatTy, this);
724+
isValid &= checkType(getOffsets(), ElemKind::Int32ITy, this);
725+
isValid &= checkType(getWeights(), ElemKind::FloatTy, this);
726+
isValid &= checkType(getIndices(), ElemKind::Int64ITy, this);
727+
isValid &= checkType(getLengths(), ElemKind::Int32ITy, this);
728+
isValid &= expectCompareTrue("Indices must be a 1D vector",
729+
getIndices().dims().size(), size_t(1), this);
730+
isValid &= expectCompareTrue("Lengths must be a 1D vector",
731+
getLengths().dims().size(), size_t(1), this);
732+
isValid &= expectCompareTrue("Weights must be a 1D vector",
733+
getWeights().dims().size(), size_t(1), this);
734+
isValid &= expectCompareTrue("Scales must be a 1D vector",
735+
getScales().dims().size(), size_t(1), this);
736+
isValid &= expectCompareTrue("Offsets must be a 1D vector",
737+
getOffsets().dims().size(), size_t(1), this);
738+
isValid &=
739+
expectCompareTrue("Weights and Indices must have the same size",
740+
getWeights().dims()[0], getIndices().dims()[0], this);
741+
isValid &= expectCompareTrue(
742+
"Scales and Data must have the same first dimension size",
743+
getData().dims()[0], getScales().dims()[0], this);
744+
isValid &= expectCompareTrue(
745+
"Offsets and Data must have the same first dimension size",
746+
getData().dims()[0], getOffsets().dims()[0], this);
747+
return isValid;
748+
}
749+
720750
bool LengthsToRangesNode::verify() const {
721751
bool isValid = checkType(getResult(), getLengths().getElementType(), this);
722752
isValid &= checkType(getLengths(), ElemKind::Int32ITy, this);

lib/Quantization/Base/Base.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -364,10 +364,13 @@ std::vector<int8_t> createMapping(TypeRef inTy, TypeRef outTy,
364364

365365
void tensorRowwiseQuantization(const Tensor &input, Tensor &output,
366366
Tensor &scales, Tensor &offsets) {
367-
ShapeHW idim(input.dims());
367+
const auto fDims = flattenCdr(input.dims());
368+
Tensor finalIn = input.getUnowned({fDims.first, fDims.second});
369+
Tensor finalOut = output.getUnowned({fDims.first, fDims.second});
370+
ShapeHW idim(finalIn.dims());
368371

369-
auto srcH = input.getHandle<float>();
370-
auto destH = output.getHandle<int8_t>();
372+
auto srcH = finalIn.getHandle<float>();
373+
auto destH = finalOut.getHandle<int8_t>();
371374
auto scalesH = scales.getHandle<float>();
372375
auto offsetsH = offsets.getHandle<int32_t>();
373376
for (size_t i = 0; i < idim.height; i++) {

0 commit comments

Comments
 (0)