diff --git a/src/tigris_kernels_cmsis_nn.c b/src/tigris_kernels_cmsis_nn.c index 1e18e1c..c458cae 100644 --- a/src/tigris_kernels_cmsis_nn.c +++ b/src/tigris_kernels_cmsis_nn.c @@ -135,16 +135,22 @@ static int adapt_conv2d( int OH = y_shape[1], OW = y_shape[2], OC = y_shape[3]; int KH = op->spatial.kernel_h, KW = op->spatial.kernel_w; int pad_top = op->spatial.pad_top; + int pad_left = op->spatial.pad_left; - /* Plain-height tile: the loaded input tile holds tile.in_h rows and produces - * tile.out_h output rows, with tile.pad_top at the top. The bottom pad is - * implicit - CMSIS-NN clips input rows at or past IH to the input zero-point, - * so an asymmetric edge-tile pad needs no pre-pad. Width is never tiled here - * (2D width tiles route to s8_ref), so IW/OW/pad_left stay full-tensor. */ + /* Height tile: tile.in_h rows produce tile.out_h output rows at tile.pad_top; + * the bottom pad is implicit (CMSIS-NN clips input rows at or past IH to the + * input zero-point), so an asymmetric edge-tile pad needs no pre-pad. A 2D + * (width_tiled) tile additionally packs tile.in_w columns into tile.out_w + * with tile.pad_left; the right pad is implicit the same way. */ if (mem->tile.active) { IH = mem->tile.in_h; OH = mem->tile.out_h; pad_top = mem->tile.pad_top; + if (mem->tile.width_tiled) { + IW = mem->tile.in_w; + OW = mem->tile.out_w; + pad_left = mem->tile.pad_left; + } } /* Line-buffer roll: emit the tile.out_h new rows starting at output row @@ -165,7 +171,7 @@ static int adapt_conv2d( .input_offset = in_qp ? -in_qp->zero_point : 0, .output_offset = out_qp ? out_qp->zero_point : 0, .stride = { .w = op->spatial.stride_w, .h = op->spatial.stride_h }, - .padding = { .w = op->spatial.pad_left, .h = pad_top }, + .padding = { .w = pad_left, .h = pad_top }, .dilation = { .w = op->spatial.dilation_w ? op->spatial.dilation_w : 1, .h = op->spatial.dilation_h ? op->spatial.dilation_h : 1 }, .activation = { .min = op->act_min, .max = op->act_max }, @@ -227,14 +233,21 @@ static int adapt_depthwise_conv2d( int OH = y_shape[1], OW = y_shape[2]; int KH = op->spatial.kernel_h, KW = op->spatial.kernel_w; int pad_top = op->spatial.pad_top; + int pad_left = op->spatial.pad_left; - /* Plain-height tile: same contract as adapt_conv2d - tile.in_h input rows - * produce tile.out_h output rows at tile.pad_top; the bottom pad is implicit - * via IH clipping. Width is never tiled here. */ + /* Same contract as adapt_conv2d - tile.in_h input rows produce tile.out_h + * output rows at tile.pad_top (bottom pad implicit via IH clipping); a 2D + * (width_tiled) tile also packs tile.in_w columns into tile.out_w with + * tile.pad_left (right pad implicit via IW clipping). */ if (mem->tile.active) { IH = mem->tile.in_h; OH = mem->tile.out_h; pad_top = mem->tile.pad_top; + if (mem->tile.width_tiled) { + IW = mem->tile.in_w; + OW = mem->tile.out_w; + pad_left = mem->tile.pad_left; + } } /* Line-buffer roll folds out_row_start into the output pointer and @@ -257,7 +270,7 @@ static int adapt_depthwise_conv2d( .output_offset = out_qp ? out_qp->zero_point : 0, .ch_mult = 1, .stride = { .w = op->spatial.stride_w, .h = op->spatial.stride_h }, - .padding = { .w = op->spatial.pad_left, .h = pad_top }, + .padding = { .w = pad_left, .h = pad_top }, .dilation = { .w = op->spatial.dilation_w ? op->spatial.dilation_w : 1, .h = op->spatial.dilation_h ? op->spatial.dilation_h : 1 }, .activation = { .min = op->act_min, .max = op->act_max }, diff --git a/src/tigris_kernels_esp_nn.c b/src/tigris_kernels_esp_nn.c index 3b7ee64..6d95bab 100644 --- a/src/tigris_kernels_esp_nn.c +++ b/src/tigris_kernels_esp_nn.c @@ -432,6 +432,10 @@ static int adapt_conv2d( OH = mem->tile.out_h; pt = mem->tile.pad_top; pb = mem->tile.pad_bottom; + if (mem->tile.width_tiled) { + IW = mem->tile.in_w; + OW = mem->tile.out_w; + } } /* Line-buffer roll: fold out_row_start into the output pointer and @@ -449,8 +453,14 @@ static int adapt_conv2d( int32_t in_offset = in_qp ? -in_qp->zero_point : 0; - /* Handle asymmetric padding */ + /* Handle asymmetric padding. A 2D (width_tiled) tile carries its own + * left/right pads; exec_stage_tiled_2d sets all four, so the bounce below + * pads the packed rectangle exactly as kern_conv2d_s8's IW/IH clipping. */ int pl = op->spatial.pad_left, pr = op->spatial.pad_right; + if (mem->tile.active && mem->tile.width_tiled) { + pl = mem->tile.pad_left; + pr = mem->tile.pad_right; + } int asymmetric = (pt != pb) || (pl != pr); const int8_t *conv_input = X; @@ -543,6 +553,10 @@ static int adapt_depthwise_conv2d( if (mem->tile.active) { IH = mem->tile.in_h; OH = mem->tile.out_h; + if (mem->tile.width_tiled) { + IW = mem->tile.in_w; + OW = mem->tile.out_w; + } } /* Line-buffer roll: fold out_row_start into the output pointer and @@ -577,7 +591,7 @@ static int adapt_depthwise_conv2d( .out_offset = out_offset, .ch_mult = 1, .stride = { .width = op->spatial.stride_w, .height = op->spatial.stride_h }, - .padding = { .width = op->spatial.pad_left, .height = mem->tile.active ? mem->tile.pad_top : op->spatial.pad_top }, + .padding = { .width = (mem->tile.active && mem->tile.width_tiled) ? mem->tile.pad_left : op->spatial.pad_left, .height = mem->tile.active ? mem->tile.pad_top : op->spatial.pad_top }, .dilation = { .width = 0, .height = 0 }, .activation = { .min = op->act_min, .max = op->act_max }, }; diff --git a/src/tigris_kernels_s8.c b/src/tigris_kernels_s8.c index 934d01e..fb526e7 100644 --- a/src/tigris_kernels_s8.c +++ b/src/tigris_kernels_s8.c @@ -1482,10 +1482,12 @@ int tigris_accel_try_s8_ref( return -1; /* 2D tiles carry mem->tile.width_tiled and partition width via - * pad_left/tile-in_w as well as height (see exec_stage_tiled_2d). No vendor - * adapter honors the packed-width contract yet, so a 2D-tiled op routes to - * s8_ref on every backend. */ - if (mem->tile.active && mem->tile.width_tiled) { + * pad_left/tile-in_w as well as height (see exec_stage_tiled_2d). The + * ESP-NN/CMSIS-NN Conv/Depthwise adapters honor the packed-width tile + * natively (in_w/out_w/pad_left, right pad implicit via IW clipping); every + * other op still routes to s8_ref. */ + if (mem->tile.active && mem->tile.width_tiled && + !is_conv_or_depthwise(op)) { *handled = 1; return tigris_dispatch_kernel_s8( plan, op, op_index, mem, user_ctx); diff --git a/test/test_accel_routing.c b/test/test_accel_routing.c index 3582b23..6f4ad1e 100644 --- a/test/test_accel_routing.c +++ b/test/test_accel_routing.c @@ -446,12 +446,13 @@ static void test_rolled_tile_routing(void) } /* A 2D-tiled op sets mem->tile.width_tiled on tiles that partition width as - * well as height. The ESP-NN/CMSIS-NN Conv/Depthwise adapters do not honor - * width_tiled/pad_left/tile-in_w, so a 2D-tiled op must fall back to s8_ref, - * same reasoning as the line-buffer roll guard above. */ -static void test_width_tiled_routes_reference(void) + * well as height. 1.5c: the ESP-NN/CMSIS-NN Conv/Depthwise adapters now honor + * the packed-width tile (in_w/out_w/pad_left, right pad implicit), so a + * width-tiled conv/depthwise stays on the vendor path; every other op still + * routes to s8_ref. */ +static void test_width_tiled_routing(void) { - printf(" test_width_tiled_routes_reference...\n"); + printf(" test_width_tiled_routing...\n"); route_fixture_t fx; build_fixture(&fx, TIGRIS_OP_CONV, 0); fx.op.spatial.dilation_h = 1; @@ -460,20 +461,39 @@ static void test_width_tiled_routes_reference(void) int handled; int8_t out[8]; - /* width_tiled tile: guard fires for both backends. */ + /* width_tiled conv/depthwise now runs on the adapter on both backends. */ memset(out, 0, sizeof(out)); + handled = 1; run_pre_route_width_tiled(&fx, TIGRIS_ACCEL_ESP_NN, out, 1, &handled); - TEST_ASSERT_EQ(handled, 1, "2D tile routes ESP to s8_ref"); + TEST_ASSERT_EQ(handled, 0, "2D-tiled conv stays on the ESP adapter"); + + memset(out, 0, sizeof(out)); + handled = 1; + run_pre_route_width_tiled(&fx, TIGRIS_ACCEL_CMSIS_NN, out, 1, &handled); + TEST_ASSERT_EQ(handled, 0, "2D-tiled conv stays on the CMSIS adapter"); + build_fixture(&fx, TIGRIS_OP_DEPTHWISE, 1); + fx.op.spatial.dilation_h = 1; + fx.op.spatial.dilation_w = 1; memset(out, 0, sizeof(out)); + handled = 1; run_pre_route_width_tiled(&fx, TIGRIS_ACCEL_CMSIS_NN, out, 1, &handled); - TEST_ASSERT_EQ(handled, 1, "2D tile routes CMSIS to s8_ref"); + TEST_ASSERT_EQ(handled, 0, "2D-tiled depthwise stays on the CMSIS adapter"); - /* non-2D tiled conv still uses the adapter on ESP (guard not - * over-firing). */ + /* A width-tiled NON-conv/depthwise op still routes to s8_ref. */ + build_fixture(&fx, TIGRIS_OP_MAX_POOL, 0); + fx.op.spatial.dilation_h = 1; + fx.op.spatial.dilation_w = 1; + fx.op.spatial.kernel_h = 1; + fx.op.spatial.kernel_w = 1; + fx.op.spatial.pad_top = 0; + fx.op.spatial.pad_bottom = 0; + fx.op.spatial.pad_left = 0; + fx.op.spatial.pad_right = 0; memset(out, 0, sizeof(out)); - run_pre_route_width_tiled(&fx, TIGRIS_ACCEL_ESP_NN, out, 0, &handled); - TEST_ASSERT_EQ(handled, 0, "non-2D tiled conv stays on ESP adapter"); + handled = 0; + run_pre_route_width_tiled(&fx, TIGRIS_ACCEL_ESP_NN, out, 1, &handled); + TEST_ASSERT_EQ(handled, 1, "2D-tiled max-pool still routes ESP to s8_ref"); } /* 1.5a: a plain-height tile (tile.active, no roll offset, not width_tiled) with @@ -528,7 +548,7 @@ int main(void) test_cmsis_plain_height_tile_routes_adapter(); test_unit_dilation_keeps_esp_adapter(); test_rolled_tile_routing(); - test_width_tiled_routes_reference(); + test_width_tiled_routing(); test_esp_asymmetric_pad_workspace_policy(); printf("\nResults: %d passed, %d failed, %d total\n",