@@ -1260,6 +1260,8 @@ export class PoolConvUtil {
12601260 * @param pads Padding for the beginning and ending along each axis.
12611261 * @param autoPad DEPRECATED attribute supported for legacy models. Specifies how to implicitly calculate pads in each
12621262 * dimension. Can take values NOTSET, SAME_UPPER, SAME_LOWER, or VALID.
1263+ * @param ceilMode When set to 1, use ceil() instead of floor() to compute the output spatial size (and apply the
1264+ * "shrink the last window if it starts entirely in padding" rule). Defaults to 0 (floor).
12631265 */
12641266 static computePoolOutputShape (
12651267 isGlobalOperator : boolean ,
@@ -1269,6 +1271,7 @@ export class PoolConvUtil {
12691271 kernelShape : number [ ] ,
12701272 pads : number [ ] ,
12711273 autoPad ?: string ,
1274+ ceilMode = 0 ,
12721275 ) : number [ ] {
12731276 if ( inputDims . length <= 0 ) {
12741277 throw new Error ( 'input shape must be of size greater than 0' ) ;
@@ -1286,6 +1289,7 @@ export class PoolConvUtil {
12861289 kernelShape ,
12871290 pads ,
12881291 autoPad ,
1292+ ceilMode ,
12891293 ) ;
12901294 return outputDims ;
12911295 }
@@ -1332,6 +1336,7 @@ export class PoolConvUtil {
13321336 kernelShape : readonly number [ ] ,
13331337 pads : number [ ] ,
13341338 autoPad ?: string ,
1339+ ceilMode = 0 ,
13351340 ) {
13361341 if ( isGlobalOperator ) {
13371342 for ( let dim = 0 ; dim < inputDims . length - 2 ; dim ++ ) {
@@ -1349,12 +1354,44 @@ export class PoolConvUtil {
13491354 dim ,
13501355 dim + inputDims . length - 2 ,
13511356 autoPad ,
1357+ ceilMode ,
13521358 ) ,
13531359 ) ;
13541360 }
13551361 }
13561362 }
13571363
1364+ // Computes the output spatial size for a single dimension.
1365+ // Produces results identical to the C++ PoolAttributes::ComputeOutputSize
1366+ // (onnxruntime/core/providers/cpu/nn/pool_attributes.h), including the ceil_mode
1367+ // "shrink the last window if it starts entirely in the trailing padding" rule. The JS
1368+ // signature takes a pre-computed `numerator` (equal to `inSize + padHead + padTail - dkernel`,
1369+ // matching the C++ `in_size + pad_head + pad_tail - dilation * (kernel - 1) - 1`) instead of
1370+ // the raw pooling attributes, but the computed output size is the same.
1371+ // Keep in sync with the onnxjs/jsep copy.
1372+ // NOTE: In this onnxjs copy the ceilMode path exists for shape-test parity with the jsep copy;
1373+ // the onnxjs WebGL pooling caller (backends/webgl/ops/pool.ts) intentionally does NOT pass
1374+ // ceilMode (legacy path still throws on ceil_mode != 0), so it always uses the floor default.
1375+ private static computeOutputSize (
1376+ numerator : number ,
1377+ stride : number ,
1378+ inSize : number ,
1379+ padHead : number ,
1380+ ceilMode : number ,
1381+ ) : number {
1382+ let outSize = Math . floor ( numerator / stride ) + 1 ;
1383+ // Match C++ `ceil_mode == 1` exactly so out-of-spec ceil_mode values do not diverge.
1384+ if ( ceilMode === 1 ) {
1385+ outSize = Math . ceil ( numerator / stride ) + 1 ;
1386+ // Ensure the last pooling window starts inside the image (ref: https://github.com/onnx/onnx/pull/5741).
1387+ // inSize and padHead are needed here to reconstruct the last window's start position.
1388+ if ( ( outSize - 1 ) * stride >= inSize + padHead ) {
1389+ outSize -= 1 ;
1390+ }
1391+ }
1392+ return outSize ;
1393+ }
1394+
13581395 // helper for computeShapeHelper() and adjustPadsBasedOnAutoPad()
13591396 // adjusts pad value for given 'autoPad' string and computes output shape along a particular dimension
13601397 private static adjustPadAndReturnShape (
@@ -1366,30 +1403,44 @@ export class PoolConvUtil {
13661403 padHeadIndex : number ,
13671404 padTailIndex : number ,
13681405 autoPad ?: string ,
1406+ ceilMode = 0 ,
13691407 ) : number {
13701408 const dkernel = dilation * ( kernel - 1 ) + 1 ;
13711409 if ( autoPad && autoPad !== 'NOTSET' ) {
13721410 switch ( autoPad ) {
13731411 case 'VALID' :
13741412 pads [ padHeadIndex ] = 0 ;
13751413 pads [ padTailIndex ] = 0 ;
1376- return Math . floor ( ( inSize - dkernel ) / stride + 1 ) ;
1414+ return PoolConvUtil . computeOutputSize ( inSize - dkernel , stride , inSize , 0 , ceilMode ) ;
13771415 case 'SAME_LOWER' :
13781416 case 'SAME_UPPER' :
13791417 if ( dilation !== 1 ) {
13801418 throw new Error ( 'Dilation not supported for SAME_UPPER or SAME_LOWER' ) ;
13811419 } else {
1382- const legacyTargetSize = ( inSize + stride - 1 ) / stride ;
1420+ // Integer division to match C++ pool_attributes.h ComputeSizePadDilations; float division mis-rounds SAME_* pads.
1421+ const legacyTargetSize = Math . floor ( ( inSize + stride - 1 ) / stride ) ;
13831422 const padNeeded = ( legacyTargetSize - 1 ) * stride + kernel - inSize ;
13841423 pads [ padHeadIndex ] = autoPad === 'SAME_LOWER' ? Math . floor ( ( padNeeded + 1 ) / 2 ) : Math . floor ( padNeeded / 2 ) ;
13851424 pads [ padTailIndex ] = padNeeded - pads [ padHeadIndex ] ;
1386- return Math . floor ( ( inSize + padNeeded - kernel ) / stride + 1 ) ;
1425+ return PoolConvUtil . computeOutputSize (
1426+ inSize + pads [ padHeadIndex ] + pads [ padTailIndex ] - dkernel ,
1427+ stride ,
1428+ inSize ,
1429+ pads [ padHeadIndex ] ,
1430+ ceilMode ,
1431+ ) ;
13871432 }
13881433 default :
13891434 throw new Error ( 'Unsupported AutoPad type' ) ;
13901435 }
13911436 } else {
1392- return Math . floor ( ( inSize + pads [ padHeadIndex ] + pads [ padTailIndex ] - dkernel ) / stride + 1 ) ;
1437+ return PoolConvUtil . computeOutputSize (
1438+ inSize + pads [ padHeadIndex ] + pads [ padTailIndex ] - dkernel ,
1439+ stride ,
1440+ inSize ,
1441+ pads [ padHeadIndex ] ,
1442+ ceilMode ,
1443+ ) ;
13931444 }
13941445 }
13951446}
0 commit comments