44/* eslint-disable no-param-reassign */
55
66export class MatMulUtil {
7- /**
8- * Fix the input shapes for MatMul operation if they need fixing
9- * @param dimsA The shape of tensor A. Should be an array of positive integers
10- * @param dimsB The shape of tensor B. Should be an array of positive integers
11- * @returns A tuple containing the preprocessed input shapes as required by ONNX specifications
12- */
13- static preprocessInputShapes ( dimsA : readonly number [ ] , dimsB : readonly number [ ] ) :
14- [ readonly number [ ] , readonly number [ ] ] {
15- // If the first argument is 1-D, it is promoted to a matrix by prepending
16- // a 1 to its dimensions. After matrix multiplication the prepended 1 is
17- // removed.
18- const a = ( dimsA . length === 1 ) ? [ 1 , dimsA [ 0 ] ] : dimsA ;
19-
20- // If the second argument is 1-D, it is promoted to a matrix by appending
21- // a 1 to its dimensions. After matrix multiplication the appended 1 is
22- // removed.
23- const b = ( dimsB . length === 1 ) ? [ dimsB [ 0 ] , 1 ] : dimsB ;
24-
25- return [ a , b ] ;
26- }
27-
28- /**
29- * Fix the output shape computed for MatMul operation if it needs fixing
30- * @param outputShape The computed outputShape. Should be an array (atleast of length 2) of positive integers.
31- * This will be mutated.
32- * @param aRank The rank of tensor A.
33- * @param bRank The rank of tensor B.
34- */
35- static postprocessOutputShape ( outputShape : number [ ] , aRank : number , bRank : number ) : void {
36- // Remove prepended dimension if first input is 1d
37- if ( aRank === 1 ) {
38- // outputShape = outputShape.slice(0, outputShape.length - 2).concat(outputShape.slice(outputShape.length - 1));
39- outputShape . splice ( outputShape . length - 2 , 1 ) ;
40- }
41- // Remove appended dimension if second input is 1d
42- if ( bRank === 1 ) {
43- outputShape . pop ( ) ;
44- }
45- }
46-
477 /**
488 * Calculate the expected shape when matrix multiplication
499 * @param a The shape of tensor A. Should be a tuple of 2 positive integers
@@ -102,39 +62,6 @@ export class BroadcastUtil {
10262 return cdims ;
10363 }
10464
105- /**
106- * Given the indices of a broadcasted tensor, calculate the original indices
107- * @param broadcastedIndices The given indices of the broadcasted tensor.
108- * @param originalShape The original shape of the tensor before broadcas
109- * @returns The calculated indices that maps to the original tensor.
110- */
111- static index ( broadcastedIndices : readonly number [ ] , originalShape : readonly number [ ] ) : number [ ] {
112- // NOTE 1: we assume the parameter broadcastedIndices is valid. ie. it should have the same
113- // length as the broadcasted shape, and for each dimension the index should
114- // not be out of range.
115- const originalIndices = new Array ( originalShape . length ) ;
116- BroadcastUtil . fillIndex ( broadcastedIndices , originalShape , originalIndices ) ;
117- return originalIndices ;
118- }
119-
120- /**
121- * Given the indices of a broadcasted tensor, calculate the original indices
122- * @param broadcastedIndices The given indices of the broadcasted tensor.
123- * @param originalShape The original shape of the tensor before broadcast
124- * @param originalIndices The mapping of broadcastedIndices to the originalIndices (output parameter - will be
125- * mutated).
126- */
127- static fillIndex ( broadcastedIndices : readonly number [ ] , originalShape : readonly number [ ] , originalIndices : number [ ] ) :
128- void {
129- // NOTE 1: we assume the parameter broadcastedIndices is valid. ie. it should have the same length as the
130- // broadcasted shape, and for each dimension the index should not be out of range.
131- // NOTE 2: we assume the parameter originalIndices has the same length as the originalShape
132- const dimOffset = broadcastedIndices . length - originalShape . length ;
133- for ( let i = 0 ; i < originalShape . length ; i ++ ) {
134- originalIndices [ i ] = broadcastedIndices [ dimOffset + i ] % originalShape [ i ] ;
135- }
136- }
137-
13865 /**
13966 * Determine if a shape is unidirectional broadcastable to another shape
14067 * @param shape The input shape
@@ -154,27 +81,6 @@ export class BroadcastUtil {
15481 }
15582 return true ;
15683 }
157-
158- /**
159- * Determine the broadcasted dims in input shape based on the given output shape.
160- * Note that this function only returns the broadcasted dims.
161- * @param inputShape The input shape
162- * @param outputShape The output shape
163- * @returns The broadcasted dims in input shape.
164- */
165- static getBroadcastDims ( inputShape : readonly number [ ] , outputShape : readonly number [ ] ) : number [ ] {
166- const inRank = inputShape . length ;
167- const dims : number [ ] = [ ] ;
168- for ( let i = 0 ; i < inRank ; i ++ ) {
169- const dim = inRank - 1 - i ;
170- const a = inputShape [ dim ] || 1 ;
171- const b = outputShape [ outputShape . length - 1 - i ] || 1 ;
172- if ( b > 1 && a === 1 ) {
173- dims . unshift ( dim ) ;
174- }
175- }
176- return dims ;
177- }
17884}
17985
18086
@@ -240,38 +146,6 @@ export class ShapeUtil {
240146 return strides ;
241147 }
242148
243- static transpose ( dims : readonly number [ ] ) : readonly number [ ] {
244- const copy = dims . slice ( ) ;
245- return copy . reverse ( ) ;
246- }
247-
248- static indicesToOffset ( indices : readonly number [ ] , strides : readonly number [ ] , axis ?: number ) : number {
249- if ( axis === undefined ) {
250- axis = indices . length ;
251- }
252- let offset = 0 ;
253- for ( let i = 0 ; i < axis ; ++ i ) {
254- offset += strides [ i ] * indices [ i ] ;
255- }
256- return offset ;
257- }
258-
259- static offsetToIndices ( offset : number , strides : readonly number [ ] ) : readonly number [ ] {
260- const rank = strides . length ;
261- if ( rank === 0 ) {
262- return [ ] ;
263- } else if ( rank === 1 ) {
264- return [ offset * strides [ 0 ] ] ;
265- }
266- const indices : number [ ] = new Array ( strides . length ) ;
267- for ( let i = 0 ; i < indices . length - 1 ; ++ i ) {
268- indices [ i ] = Math . floor ( offset / strides [ i ] ) ;
269- offset -= indices [ i ] * strides [ i ] ;
270- }
271- indices [ indices . length - 1 ] = offset ;
272- return indices ;
273- }
274-
275149 /**
276150 * normailze axis of range [-r, r) into [0, r).
277151 */
@@ -286,98 +160,6 @@ export class ShapeUtil {
286160 return axes . map ( x => this . normalizeAxis ( x , tensorRank ?? axes . length ) ) ;
287161 }
288162
289- /**
290- * Increment an index into a tensor (in lexicographic ordering), wrapping around the specified upper_bound.
291- * @param index Given index to increment (Will be mutated)
292- * @param dims The dimensions of the tensor for which the given index corresponds to
293- * @param axisToIncrementOn The 1-indexed axis to increment on. If undefined, axisToIncrementOn == rank
294- */
295- static incrementIndex ( index : number [ ] , dims : readonly number [ ] , axisToIncrementOn ?: number ) : void {
296- if ( dims . length === 0 || index . length === 0 ) {
297- throw new Error ( 'Index incrementing unsupported for scalar Tensor' ) ;
298- }
299- if ( axisToIncrementOn === undefined ) {
300- axisToIncrementOn = dims . length ;
301- } else {
302- if ( axisToIncrementOn <= 0 || axisToIncrementOn > dims . length ) {
303- throw new Error ( 'Incorrect axis to increment on' ) ;
304- }
305- }
306-
307- for ( let k = axisToIncrementOn - 1 ; k >= 0 ; -- k ) {
308- index [ k ] ++ ;
309- if ( index [ k ] < dims [ k ] ) {
310- break ;
311- }
312- index [ k ] = 0 ;
313- }
314- }
315-
316- /**
317- * Produces a new dimensions array based on the values in the 'originalDimensions' and 'shape' array
318- * Used in Reshape
319- * @param originalDims Original Shape array
320- * @param shapeHints array containing values to compute the new dimensions
321- * For example:
322- * originalDims = [2,2] and shapeHints = [0,-1] will return [2,2]
323- * originalDims = [2,2] and shapeHints = [4] will return [4]
324- * originalDims = [2,2] and shapeHints = [5] will throw an exception
325- * https://github.com/onnx/onnx/blob/main/docs/Operators.md#Reshape
326- */
327-
328- static calculateReshapedDims ( originalDims : readonly number [ ] , shapeHints : ArrayLike < number > ) : number [ ] {
329- // reshape to a Scalar Tensor
330- if ( shapeHints . length === 0 ) {
331- if ( originalDims . length === 0 || ShapeUtil . size ( originalDims ) === 1 ) {
332- return [ ] ;
333- } else {
334- throw new Error ( 'cannot reshape to a scalar Tensor' ) ;
335- }
336- }
337-
338- const nDims = shapeHints . length ;
339- const reshapedDims = new Array < number > ( nDims ) ;
340- let unknownDimension = - 1 ;
341- let newTensorSize = 1 ;
342- for ( let i = 0 ; i < nDims ; i ++ ) {
343- if ( shapeHints [ i ] < - 1 ) {
344- throw new Error ( 'a dimension in shape hints cannot be less than -1' ) ;
345- }
346- if ( shapeHints [ i ] === - 1 ) {
347- if ( unknownDimension !== - 1 ) {
348- throw new Error ( 'at most one dimension in shape hints can be -1' ) ;
349- }
350- unknownDimension = i ;
351- } else {
352- if ( shapeHints [ i ] === 0 ) {
353- if ( i >= originalDims . length ) {
354- throw new Error ( 'the dimension with value zero exceeds the dimension size of the input tensor' ) ;
355- }
356- reshapedDims [ i ] = originalDims [ i ] ;
357- } else {
358- reshapedDims [ i ] = shapeHints [ i ] ;
359- }
360- newTensorSize *= reshapedDims [ i ] ;
361- }
362- }
363-
364- const oldTensorSize = ShapeUtil . size ( originalDims ) ;
365- if ( unknownDimension !== - 1 ) {
366- if ( oldTensorSize % newTensorSize !== 0 ) {
367- throw new Error ( `the input tensor cannot be reshaped to the requested shape. Input shape: [${
368- originalDims } ] Output shape: [${ shapeHints } ]`) ;
369- }
370- reshapedDims [ unknownDimension ] = oldTensorSize / newTensorSize ;
371- }
372- // validate sizes from originalDims and reshapedDims match
373- else {
374- if ( newTensorSize !== oldTensorSize ) {
375- throw new Error ( 'reshapedDims and originalDims don\'t have matching sizes' ) ;
376- }
377- }
378- return reshapedDims ;
379- }
380-
381163 /**
382164 * Sorts a given array based on the indices in the Perm array
383165 * Used in Transpose
@@ -413,109 +195,6 @@ export class ShapeUtil {
413195 }
414196 return shape1 . every ( ( v , i ) => v === shape2 [ i ] ) ;
415197 }
416-
417- /**
418- * Validates if the given `dims` or `shape` is valid in ONNX.js context and returns data size
419- * @param dims - input `dims` that needs to be checked
420- */
421- static validateDimsAndCalcSize ( dims : readonly number [ ] ) : number {
422- if ( dims . length > 6 ) {
423- throw new TypeError ( 'Only rank 0 to 6 is supported for tensor shape.' ) ;
424- }
425- let size = 1 ;
426- for ( const n of dims ) {
427- if ( ! Number . isInteger ( n ) ) {
428- throw new TypeError ( `Invalid shape: ${ n } is not an integer` ) ;
429- }
430- if ( n < 0 || n > 2147483647 ) {
431- throw new TypeError ( `Invalid shape: length ${ n } is not allowed` ) ;
432- }
433- size *= n ;
434- }
435- return size ;
436- }
437-
438- /**
439- * Determines the shape of output tensor y = flatten(x, axis)
440- * @param dims - shape of input tensor
441- * @param axis - flatten axis, in the range [-r, r]
442- */
443- static flattenShape ( dims : readonly number [ ] , axis : number ) : readonly number [ ] {
444- if ( axis < 0 ) {
445- axis += dims . length ;
446- }
447- const total = dims . reduce ( ( x , y ) => x * y , 1 ) ;
448- const right = dims . slice ( axis ) . reduce ( ( x , y ) => x * y , 1 ) ;
449- const outputDims = [ total / right , right ] ;
450-
451- return outputDims ;
452- }
453-
454- /**
455- * Determines the shape of output tensor y = squeeze(x, axes)
456- * @param dims - shape of input tensor
457- * @param axes - squeeze axes
458- */
459- static squeezeShape ( dims : readonly number [ ] , axes : readonly number [ ] ) : readonly number [ ] {
460- const outputDims = new Array < number > ( ) ;
461-
462- // sanity check
463- axes = ShapeUtil . normalizeAxes ( axes , dims . length ) ;
464-
465- for ( let i = 0 ; i < dims . length ; i ++ ) {
466- const inSqueezeList = axes . indexOf ( i ) >= 0 ;
467- if ( inSqueezeList && dims [ i ] !== 1 ) {
468- throw new Error ( 'squeeze an axis of size different than 1' ) ;
469- }
470-
471- if ( ( axes . length === 0 && dims [ i ] > 1 ) || ( axes . length > 0 && ! inSqueezeList ) ) {
472- outputDims . push ( dims [ i ] ) ;
473- }
474- }
475-
476- return outputDims ;
477- }
478-
479- /**
480- * Determines the shape of output tensor y = unsqueeze(x, axes)
481- * @param dims - shape of input tensor
482- * @param axes - unsqueeze axes
483- */
484- static unsqueezeShape ( dims : readonly number [ ] , axes : readonly number [ ] ) : readonly number [ ] {
485- const outputDims = new Array < number > ( dims . length + axes . length ) ;
486-
487- // initialize the array elements to 0
488- outputDims . fill ( 0 ) ;
489-
490- // set all axes indices to 1 in outputDims and check for duplicates
491- for ( let i = 0 ; i < axes . length ; i ++ ) {
492- const axis = ShapeUtil . normalizeAxis ( axes [ i ] , outputDims . length ) ;
493- if ( axis >= outputDims . length ) {
494- throw new Error ( '\'axes\' has an out of range axis' ) ;
495- }
496- if ( outputDims [ axis ] !== 0 ) {
497- throw new Error ( '\'axes\' has a duplicate axis' ) ;
498- }
499-
500- outputDims [ axis ] = 1 ;
501- }
502-
503- // fill in the zero entries of outputDims with the input tensor's shape
504- let inputDimsIterator = 0 ;
505- for ( let i = 0 ; i < outputDims . length ; i ++ ) {
506- if ( outputDims [ i ] === 0 ) {
507- outputDims [ i ] = dims [ inputDimsIterator ++ ] ;
508- }
509- }
510-
511- // sanity check assertion. 'inputDimsIterator'
512- // should be equal to the length of 'dims'
513- if ( inputDimsIterator !== dims . length ) {
514- throw new Error ( 'the unsqueezed dimension could not be established' ) ;
515- }
516-
517- return outputDims ;
518- }
519198}
520199
521200export class PoolConvUtil {
0 commit comments