| 997 | |
| 998 | template <typename T> |
| 999 | void Sub(const ArithmeticParams& params, const RuntimeShape& input1_shape, |
| 1000 | const T* input1_data, const RuntimeShape& input2_shape, |
| 1001 | const T* input2_data, const RuntimeShape& output_shape, |
| 1002 | T* output_data) { |
| 1003 | NdArrayDesc<4> desc1; |
| 1004 | NdArrayDesc<4> desc2; |
| 1005 | NdArrayDescsForElementwiseBroadcast(input1_shape, input2_shape, &desc1, |
| 1006 | &desc2); |
| 1007 | const RuntimeShape extended_output_shape = |
| 1008 | RuntimeShape::ExtendedShape(4, output_shape); |
| 1009 | |
| 1010 | // In Tensorflow, the dimensions are canonically named (batch_number, row, |
| 1011 | // col, channel), with extents (batches, height, width, depth), with the |
| 1012 | // trailing dimension changing most rapidly (channels has the smallest stride, |
| 1013 | // typically 1 element). |
| 1014 | // |
| 1015 | // In generated C code, we store arrays with the dimensions reversed. The |
| 1016 | // first dimension has smallest stride. |
| 1017 | // |
| 1018 | // We name our variables by their Tensorflow convention, but generate C code |
| 1019 | // nesting loops such that the innermost loop has the smallest stride for the |
| 1020 | // best cache behavior. |
| 1021 | for (int b = 0; b < extended_output_shape.Dims(0); ++b) { |
| 1022 | for (int y = 0; y < extended_output_shape.Dims(1); ++y) { |
| 1023 | for (int x = 0; x < extended_output_shape.Dims(2); ++x) { |
| 1024 | for (int c = 0; c < extended_output_shape.Dims(3); ++c) { |
| 1025 | output_data[Offset(extended_output_shape, b, y, x, c)] = |
| 1026 | input1_data[SubscriptToIndex(desc1, b, y, x, c)] - |
| 1027 | input2_data[SubscriptToIndex(desc2, b, y, x, c)]; |
| 1028 | } |
| 1029 | } |
| 1030 | } |
| 1031 | } |
| 1032 | } |
| 1033 | |
| 1034 | inline void SubWithActivation(const ArithmeticParams& params, |
| 1035 | const RuntimeShape& input1_shape, |
nothing calls this directly
no test coverage detected