Skip to content

Commit

Permalink
Rebase
Browse files Browse the repository at this point in the history
  • Loading branch information
mingmingtasd committed May 6, 2024
1 parent 3ffbd29 commit f353896
Show file tree
Hide file tree
Showing 5 changed files with 76 additions and 160 deletions.
114 changes: 6 additions & 108 deletions common/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -155,100 +155,6 @@ export function stopCameraStream(id, stream) {
}
}

// ref: http://stackoverflow.com/questions/32633585/how-do-you-convert-to-half-floats-in-javascript
const toHalf = (function() {
const floatView = new Float32Array(1);
const int32View = new Int32Array(floatView.buffer);

/* This method is faster than the OpenEXR implementation (very often
* used, eg. in Ogre), with the additional benefit of rounding, inspired
* by James Tursa?s half-precision code. */
return function toHalf(val) {
floatView[0] = val;
const x = int32View[0];

let bits = (x >> 16) & 0x8000; /* Get the sign */
let m = (x >> 12) & 0x07ff; /* Keep one extra bit for rounding */
const e = (x >> 23) & 0xff; /* Using int is faster here */

/* If zero, or denormal, or exponent underflows too much for a denormal
* half, return signed zero. */
if (e < 103) {
return bits;
}

/* If NaN, return NaN. If Inf or exponent overflow, return Inf. */
if (e > 142) {
bits |= 0x7c00;
/* If exponent was 0xff and one mantissa bit was set, it means NaN,
* not Inf, so make sure we set one mantissa bit too. */
bits |= ((e == 255) ? 0 : 1) && (x & 0x007fffff);
return bits;
}

/* If exponent underflows but not too much, return a denormal */
if (e < 113) {
m |= 0x0800;
/* Extra rounding may overflow and set mantissa to 0 and exponent
* to 1, which is OK. */
bits |= (m >> (114 - e)) + ((m >> (113 - e)) & 1);
return bits;
}

bits |= ((e - 112) << 10) | (m >> 1);
/* Extra rounding. An overflow will set mantissa to 0 and increment
* the exponent, which is OK. */
bits += m & 1;
return bits;
};
})();

// This function converts a Float16 stored as the bits of a Uint16 into
// a Javascript Number.
// Adapted from: https://gist.github.com/martinkallman/5049614
// input is a Uint16 (eg, new Uint16Array([value])[0])

export function float16ToNumber(input) {
// Create a 32 bit DataView to store the input
const arr = new ArrayBuffer(4);
const dv = new DataView(arr);

// Set the Float16 into the last 16 bits of the dataview
// So our dataView is [00xx]
dv.setUint16(2, input, false);

// Get all 32 bits as a 32 bit integer
// (JS bitwise operations are performed on 32 bit signed integers)
const asInt32 = dv.getInt32(0, false);

// All bits aside from the sign
let rest = asInt32 & 0x7fff;
// Sign bit
let sign = asInt32 & 0x8000;
// Exponent bits
const exponent = asInt32 & 0x7c00;

// Shift the non-sign bits into place for a 32 bit Float
rest <<= 13;
// Shift the sign bit into place for a 32 bit Float
sign <<= 16;

// Adjust bias
// https://en.wikipedia.org/wiki/Half-precision_floating-point_format#Exponent_encoding
rest += 0x38000000;
// Denormals-as-zero
rest = (exponent === 0 ? 0 : rest);
// Re-insert sign bit
rest |= sign;

// Set the adjusted float32 (stored as int32) back into the dataview
dv.setInt32(0, rest, false);

// Get it back out as a float32 (which js will convert to a Number)
const asFloat32 = dv.getFloat32(0, false);

return asFloat32;
}
/**
* This method is used to covert input element to tensor data.
* @param {Object} inputElement, an object of HTML [<img> | <video>] element.
Expand Down Expand Up @@ -284,16 +190,9 @@ export function float16ToNumber(input) {
* @return {Object} tensor, an object of input tensor.
*/
export function getInputTensor(inputElement, inputOptions) {
const dataType = inputOptions.dataType || 'float32';
const inputDimensions = inputOptions.inputDimensions;
let tensor;
if (dataType === 'float16') {
tensor = new Uint16Array(
inputDimensions.slice(1).reduce((a, b) => a * b));
} else {
tensor = new Float32Array(
inputDimensions.slice(1).reduce((a, b) => a * b));
}
const tensor = new Float32Array(
inputDimensions.slice(1).reduce((a, b) => a * b));

inputElement.width = inputElement.videoWidth ||
inputElement.naturalWidth;
Expand Down Expand Up @@ -350,12 +249,11 @@ export function getInputTensor(inputElement, inputOptions) {
value = pixels[h * width * imageChannels + w * imageChannels + c];
}
if (inputLayout === 'nchw') {
tensor[c * width * height + h * width + w] = dataType === 'float16' ?
toHalf((value - mean[c]) / std[c]) : (value - mean[c]) / std[c];
tensor[c * width * height + h * width + w] =
(value - mean[c]) / std[c];
} else {
tensor[h * width * channels + w * channels + c] = dataType ===
'float16' ?
toHalf((value - mean[c]) / std[c]) : (value - mean[c]) / std[c];
tensor[h * width * channels + w * channels + c] =
(value - mean[c]) / std[c];
}
}
}
Expand Down
26 changes: 19 additions & 7 deletions image_classification/efficientnet_fp16_nchw.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export class EfficientNetFP16Nchw {
this.context_ = null;
this.builder_ = null;
this.graph_ = null;
this.targetDataType_ = 'float16';
this.weightsUrl_ = weightsOrigin() +
'/test-data/models/efficientnet_fp16_nchw_optimized/weights/';
this.inputOptions = {
Expand All @@ -17,7 +18,7 @@ export class EfficientNetFP16Nchw {
inputLayout: 'nchw',
labelUrl: './labels/labels1000.txt',
inputDimensions: [1, 3, 224, 224],
dataType: 'float16',
dataType: 'float32',
};
this.outputDimensions = [1, 1000];
}
Expand All @@ -30,8 +31,10 @@ export class EfficientNetFP16Nchw {
} else {
prefix = this.weightsUrl_ + 'conv' + name;
}
const weight = buildConstantByNpy(this.builder_, prefix + '_w.npy');
options.bias = await buildConstantByNpy(this.builder_, prefix + '_b.npy');
const weight = buildConstantByNpy(this.builder_, prefix + '_w.npy',
this.targetDataType_ = 'float16');
options.bias = await buildConstantByNpy(this.builder_, prefix + '_b.npy',
this.targetDataType_ = 'float16');
if (clip) {
return this.builder_.clamp(
this.builder_.conv2d(await input, await weight, options),
Expand All @@ -43,9 +46,11 @@ export class EfficientNetFP16Nchw {
async buildGemm_(input, name) {
const prefix = this.weightsUrl_ + 'dense' + name;
const weightName = prefix + '_w.npy';
const weight = buildConstantByNpy(this.builder_, weightName);
const weight = buildConstantByNpy(this.builder_, weightName,
this.targetDataType_ = 'float16');
const biasName = prefix + '_b.npy';
const bias = buildConstantByNpy(this.builder_, biasName);
const bias = buildConstantByNpy(this.builder_, biasName,
this.targetDataType_ = 'float16');
const options =
{c: this.builder_.reshape(await bias, [1, 1000])};
return this.builder_.gemm(await input, await weight, options);
Expand All @@ -72,10 +77,11 @@ export class EfficientNetFP16Nchw {
async load(contextOptions) {
this.context_ = await navigator.ml.createContext(contextOptions);
this.builder_ = new MLGraphBuilder(this.context_);
const data = this.builder_.input('input', {
let data = this.builder_.input('input', {
dataType: this.inputOptions.dataType,
dimensions: this.inputOptions.inputDimensions,
});
data = this.builder_.cast(data, 'float16');
// Block 0
const conv1 = this.buildConv_(
data, '0', '0', true, {padding: [0, 1, 0, 1], strides: [2, 2]});
Expand Down Expand Up @@ -142,7 +148,13 @@ export class EfficientNetFP16Nchw {
const conv22 = this.buildConv_(conv21, '0', '', true);
const pool1 = this.builder_.averagePool2d(await conv22);
const reshape = this.builder_.reshape(pool1, [1, 1280]);
return this.buildGemm_(reshape, '0');
const gemm = this.buildGemm_(reshape, '0');
if (contextOptions.deviceType === 'npu') {
return this.builder_.cast(await gemm, 'float32');
} else {
const softmax = this.builder_.softmax(await gemm);
return this.builder_.cast(softmax, 'float32');
}
}

async build(outputOperand) {
Expand Down
28 changes: 5 additions & 23 deletions image_classification/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -248,31 +248,19 @@ async function renderCamStream() {

// Get top 3 classes of labels from output buffer
function getTopClasses(buffer, labels) {
let float32Buffer = buffer;
// Currently we need to fallback softmax to tf.softmax because
// NPU dosen't support softmax.
// Convert output buffer from float16 to float32, because
// tf.tensor/tf.softmax doesn't support float16 data type
// according to https://js.tensorflow.org/api/latest/#tensor.
// TODO: Remove this workaround once NPU supports softmax.
if (inputOptions.dataType === 'float16') {
const elementsCount = utils.sizeOfShape(netInstance.outputDimensions);
const float32Array = new Float32Array(elementsCount);
for (let i = 0; i < elementsCount; ++i) {
float32Array[i] = utils.float16ToNumber(buffer[i]);
}
float32Buffer = float32Array;

if (deviceType === 'npu') {
// Softmax
float32Buffer = tf.tidy(() => {
buffer = tf.tidy(() => {
const a =
tf.tensor(float32Buffer, netInstance.outputDimensions, 'float32');
tf.tensor(buffer, netInstance.outputDimensions, 'float32');
const b = tf.softmax(a);
return b.dataSync();
});
}

const probs = Array.from(float32Buffer);
const probs = Array.from(buffer);
const indexes = probs.map((prob, index) => [prob, index]);
const sorted = indexes.sort((a, b) => {
if (a[0] === b[0]) {
Expand Down Expand Up @@ -375,14 +363,8 @@ async function main() {
netInstance = constructNetObject(instanceType);
inputOptions = netInstance.inputOptions;
labels = await fetchLabels(inputOptions.labelUrl);
if (inputOptions.dataType === 'float16') {
outputBuffer =
new Uint16Array(utils.sizeOfShape(netInstance.outputDimensions));
} else {
outputBuffer =
outputBuffer =
new Float32Array(utils.sizeOfShape(netInstance.outputDimensions));
}

isFirstTimeLoad = false;
console.log(`- Model name: ${modelName}, Model layout: ${layout} -`);
// UI shows model loading progress
Expand Down
42 changes: 27 additions & 15 deletions image_classification/mobilenet_nchw.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,20 @@ import {buildConstantByNpy, weightsOrigin} from '../common/utils.js';

// MobileNet V2 model with 'nchw' input layout
export class MobileNetV2Nchw {
constructor(dataType = 'float32') {
constructor(targetDataType = 'float32') {
this.context_ = null;
this.deviceType_ = null;
this.builder_ = null;
this.graph_ = null;
this.targetDataType_ = targetDataType;
this.weightsUrl_ = weightsOrigin();
if (dataType == 'float32') {
if (this.targetDataType_ === 'float32') {
this.weightsUrl_ += '/test-data/models/mobilenetv2_nchw/weights/';
} else if (dataType == 'float16') {
} else if (this.targetDataType_ === 'float16') {
this.weightsUrl_ +=
'/test-data/models/mobilenetv2_fp16_nchw_optimized/weights/';
} else {
throw new Error(`Unsupported dataType: ${dataType}`);
throw new Error(`Unsupported dataType: ${this.targetDataType_}`);
}
this.inputOptions = {
mean: [0.485, 0.456, 0.406],
Expand All @@ -25,25 +26,25 @@ export class MobileNetV2Nchw {
inputLayout: 'nchw',
labelUrl: './labels/labels1000.txt',
inputDimensions: [1, 3, 224, 224],
dataType: dataType,
dataType: 'float32',
};
this.outputDimensions = [1, 1000];
}

async buildConv_(input, name, relu6 = true, options = {}) {
let weights;
if (this.inputOptions.dataType==='float32') {
if (this.targetDataType_==='float32') {
weights = buildConstantByNpy(this.builder_,
`${this.weightsUrl_}conv_${name}_weight.npy`);
`${this.weightsUrl_}conv_${name}_weight.npy`, this.targetDataType_);
options.bias = await buildConstantByNpy(this.builder_,
`${this.weightsUrl_}conv_${name}_bias.npy`);
`${this.weightsUrl_}conv_${name}_bias.npy`, this.targetDataType_);
} else {
weights = buildConstantByNpy(this.builder_,
`${this.weightsUrl_}w${name}.npy`);
`${this.weightsUrl_}w${name}.npy`, this.targetDataType_);
// Only node 97 has no bias input
if (name !== '97') {
options.bias = await buildConstantByNpy(this.builder_,
`${this.weightsUrl_}b${name}.npy`);
`${this.weightsUrl_}b${name}.npy`, this.targetDataType_);
}
}

Expand All @@ -65,9 +66,11 @@ export class MobileNetV2Nchw {
async buildGemm_(input, name) {
const prefix = this.weightsUrl_ + 'gemm_' + name;
const weightsName = prefix + '_weight.npy';
const weights = buildConstantByNpy(this.builder_, weightsName);
const weights = buildConstantByNpy(this.builder_, weightsName,
this.targetDataType_);
const biasName = prefix + '_bias.npy';
const bias = buildConstantByNpy(this.builder_, biasName);
const bias = buildConstantByNpy(this.builder_, biasName,
this.targetDataType_);
const options = {c: await bias, bTranspose: true};
return this.builder_.gemm(await input, await weights, options);
}
Expand Down Expand Up @@ -95,10 +98,13 @@ export class MobileNetV2Nchw {
this.context_ = await navigator.ml.createContext(contextOptions);
this.deviceType_ = contextOptions.deviceType;
this.builder_ = new MLGraphBuilder(this.context_);
const data = this.builder_.input('input', {
let data = this.builder_.input('input', {
dataType: this.inputOptions.dataType,
dimensions: this.inputOptions.inputDimensions,
});
if (this.targetDataType_ === 'float16') {
data = this.builder_.cast(data, 'float16');
}
const conv0 = this.buildConv_(
data, '0', true, {padding: [1, 1, 1, 1], strides: [2, 2]});
const conv1 = this.buildConv_(
Expand Down Expand Up @@ -138,7 +144,7 @@ export class MobileNetV2Nchw {
bottleneck14, ['90', '92', '94'], 960, 1, false);

const conv3 = this.buildConv_(bottleneck15, '95', true);
if (this.inputOptions.dataType == 'float32') {
if (this.targetDataType_ == 'float32') {
const pool = this.builder_.averagePool2d(await conv3);
const reshape = this.builder_.reshape(pool, [1, 1280]);
const gemm = this.buildGemm_(reshape, '104');
Expand All @@ -147,7 +153,13 @@ export class MobileNetV2Nchw {
const conv4 = this.buildConv_(await conv3, '97', false,
{groups: 1280, strides: [7, 7]});
const conv5 = this.buildConv_(await conv4, '104', false);
return this.builder_.reshape(await conv5, [1, 1000]);
const reshape = this.builder_.reshape(await conv5, [1, 1000]);
if (contextOptions.deviceType === 'npu') {
return this.builder_.cast(reshape, 'float32');
} else {
const softmax = this.builder_.softmax(reshape);
return this.builder_.cast(softmax, 'float32');
}
}
}

Expand Down
Loading

0 comments on commit f353896

Please sign in to comment.