Unrolls a BatchMatMul on the batch dimension. We need to slice each batch out of the inputs, matmul them individually, then stack them all back together at the end.
| 131 | // We need to slice each batch out of the inputs, matmul them individually, then |
| 132 | // stack them all back together at the end. |
| 133 | ::tensorflow::Status UnrollBatchMatMul::Run(Model* model, std::size_t op_index, |
| 134 | bool* modified) { |
| 135 | *modified = false; |
| 136 | auto batch_op_it = model->operators.begin() + op_index; |
| 137 | if (batch_op_it->get()->type != OperatorType::kBatchMatMul) { |
| 138 | return ::tensorflow::Status::OK(); |
| 139 | } |
| 140 | const auto* batch_op = |
| 141 | static_cast<const BatchMatMulOperator*>(batch_op_it->get()); |
| 142 | auto& tail_it = batch_op_it; |
| 143 | |
| 144 | string input_lhs = batch_op->inputs[0]; |
| 145 | string input_rhs = batch_op->inputs[1]; |
| 146 | const auto& input_lhs_array = model->GetArray(input_lhs); |
| 147 | const auto& input_rhs_array = model->GetArray(input_rhs); |
| 148 | if (!input_lhs_array.has_shape() || !input_rhs_array.has_shape()) |
| 149 | return ::tensorflow::Status::OK(); |
| 150 | |
| 151 | // Transpose LHS input if necessary. |
| 152 | if (batch_op->adj_x) { |
| 153 | TransposeOperator* transpose_op = TransposeInput(input_lhs, model); |
| 154 | tail_it = model->operators.emplace(tail_it, transpose_op) + 1; |
| 155 | input_lhs = transpose_op->outputs[0]; |
| 156 | } |
| 157 | const auto& input_array_a = model->GetArray(input_lhs); |
| 158 | |
| 159 | // Transpose RHS input if necessary. |
| 160 | if (batch_op->adj_y) { |
| 161 | TransposeOperator* transpose_op = TransposeInput(input_rhs, model); |
| 162 | tail_it = model->operators.emplace(tail_it, transpose_op) + 1; |
| 163 | input_rhs = transpose_op->outputs[0]; |
| 164 | } |
| 165 | const auto& input_array_b = model->GetArray(input_rhs); |
| 166 | |
| 167 | // Ensure that input ranks are at least 2 and batch shapes are broadcastable. |
| 168 | const int dims_a = input_array_a.shape().dimensions_count(); |
| 169 | const int dims_b = input_array_b.shape().dimensions_count(); |
| 170 | CHECK_GE(dims_a, 2) << "First input must have rank >= 2"; |
| 171 | CHECK_GE(dims_b, 2) << "Second input must have rank >= 2"; |
| 172 | |
| 173 | ::tensorflow::MatMulBCast bcast( |
| 174 | ToInlinedVector(input_array_a.shape().dims()), |
| 175 | ToInlinedVector(input_array_b.shape().dims())); |
| 176 | CHECK(bcast.IsValid()) << "Input batch dimensions must be broadcastable"; |
| 177 | |
| 178 | CHECK_EQ(input_array_a.shape().dims(dims_a - 1), |
| 179 | input_array_b.shape().dims(dims_b - 2)) |
| 180 | << "Input dimensions must be compatible for multipication. shape a = [" |
| 181 | << absl::StrJoin(input_array_a.shape().dims(), ", ") << "], shape b = [" |
| 182 | << absl::StrJoin(input_array_b.shape().dims(), ", ") << "]"; |
| 183 | |
| 184 | if (dims_a == 2 && dims_b == 2) { |
| 185 | // This is really just a MatMul. |
| 186 | AddMessageF("Replacing non-batch BatchMatMul %s by a MatMul operator", |
| 187 | LogName(*batch_op)); |
| 188 | auto* matmul_op = new TensorFlowMatMulOperator; |
| 189 | matmul_op->inputs = {input_lhs, input_rhs}; |
| 190 | matmul_op->outputs = batch_op->outputs; |
nothing calls this directly
no test coverage detected