Support Convolution, Deconvolution, and Fully Connected operators without bias

PiperOrigin-RevId: 277524405
diff --git a/test/convolution-operator-tester.h b/test/convolution-operator-tester.h
index e637fac..115b846 100644
--- a/test/convolution-operator-tester.h
+++ b/test/convolution-operator-tester.h
@@ -467,6 +467,15 @@
     return this->depthwise_layout_;
   }
 
+  inline ConvolutionOperatorTester& has_bias(bool has_bias) {
+    this->has_bias_ = has_bias;
+    return *this;
+  }
+
+  inline bool has_bias() const {
+    return this->has_bias_;
+  }
+
   inline ConvolutionOperatorTester& iterations(size_t iterations) {
     this->iterations_ = iterations;
     return *this;
@@ -500,17 +509,21 @@
       std::fill(output.begin(), output.end(), 0xA5);
 
       // Compute reference results, without renormalization.
-      for (size_t i = 0; i < batch_size(); i++) {
-        for (size_t oy = 0; oy < output_height(); oy++) {
-          for (size_t ox = 0; ox < output_width(); ox++) {
-            for (size_t g = 0; g < groups(); g++) {
-              for (size_t oc = 0; oc < group_output_channels(); oc++) {
-                accumulators[(((i * output_height() + oy) * output_width() + ox) * groups() + g) * group_output_channels() + oc] =
-                  bias[g * group_output_channels() + oc];
+      if (has_bias()) {
+        for (size_t i = 0; i < batch_size(); i++) {
+          for (size_t oy = 0; oy < output_height(); oy++) {
+            for (size_t ox = 0; ox < output_width(); ox++) {
+              for (size_t g = 0; g < groups(); g++) {
+                for (size_t oc = 0; oc < group_output_channels(); oc++) {
+                  accumulators[(((i * output_height() + oy) * output_width() + ox) * groups() + g) * group_output_channels() + oc] =
+                    bias[g * group_output_channels() + oc];
+                }
               }
             }
           }
         }
+      } else {
+        std::fill(accumulators.begin(), accumulators.end(), 0);
       }
       if (depthwise_layout()) {
         ASSERT_EQ(group_input_channels(), 1);
@@ -596,7 +609,7 @@
           input_pixel_stride(), output_pixel_stride(),
           input_zero_point, 1.0f /* input scale */,
           kernel_zero_point, 1.0f /* kernel scale */,
-          kernel.data(), bias.data(),
+          kernel.data(), has_bias() ? bias.data() : nullptr,
           output_zero_point, output_scale, qmin(), qmax(),
           (depthwise_layout() ? XNN_FLAG_DEPTHWISE_CONVOLUTION : 0) | (padding_tf_same() ? XNN_FLAG_TENSORFLOW_SAME_PADDING : 0),
           &convolution_op));
@@ -656,17 +669,21 @@
       std::fill(output.begin(), output.end(), nanf(""));
 
       // Compute reference results, without clamping.
-      for (size_t i = 0; i < batch_size(); i++) {
-        for (size_t oy = 0; oy < output_height(); oy++) {
-          for (size_t ox = 0; ox < output_width(); ox++) {
-            for (size_t g = 0; g < groups(); g++) {
-              for (size_t oc = 0; oc < group_output_channels(); oc++) {
-                output_ref[(((i * output_height() + oy) * output_width() + ox) * groups() + g) * group_output_channels() + oc] =
-                  bias[g * group_output_channels() + oc];
+      if (has_bias()) {
+        for (size_t i = 0; i < batch_size(); i++) {
+          for (size_t oy = 0; oy < output_height(); oy++) {
+            for (size_t ox = 0; ox < output_width(); ox++) {
+              for (size_t g = 0; g < groups(); g++) {
+                for (size_t oc = 0; oc < group_output_channels(); oc++) {
+                  output_ref[(((i * output_height() + oy) * output_width() + ox) * groups() + g) * group_output_channels() + oc] =
+                    bias[g * group_output_channels() + oc];
+                }
               }
             }
           }
         }
+      } else {
+        std::fill(output_ref.begin(), output_ref.end(), 0.0f);
       }
       if (depthwise_layout()) {
         ASSERT_EQ(group_input_channels(), 1);
@@ -747,7 +764,7 @@
           dilation_height(), dilation_width(),
           groups(), group_input_channels(), group_output_channels(),
           input_pixel_stride(), output_pixel_stride(),
-          kernel.data(), bias.data(),
+          kernel.data(), has_bias() ? bias.data() : nullptr,
           output_min, output_max,
           (depthwise_layout() ? XNN_FLAG_DEPTHWISE_CONVOLUTION : 0) | (padding_tf_same() ? XNN_FLAG_TENSORFLOW_SAME_PADDING : 0),
           &convolution_op));
@@ -819,17 +836,21 @@
       std::fill(output.begin(), output.end(), 0xA5);
 
       // Compute reference results, without renormalization.
-      for (size_t i = 0; i < batch_size(); i++) {
-        for (size_t oy = 0; oy < output_height(); oy++) {
-          for (size_t ox = 0; ox < output_width(); ox++) {
-            for (size_t g = 0; g < groups(); g++) {
-              for (size_t oc = 0; oc < group_output_channels(); oc++) {
-                accumulators[(((i * output_height() + oy) * output_width() + ox) * groups() + g) * group_output_channels() + oc] =
-                  bias[g * group_output_channels() + oc];
+      if (has_bias()) {
+        for (size_t i = 0; i < batch_size(); i++) {
+          for (size_t oy = 0; oy < output_height(); oy++) {
+            for (size_t ox = 0; ox < output_width(); ox++) {
+              for (size_t g = 0; g < groups(); g++) {
+                for (size_t oc = 0; oc < group_output_channels(); oc++) {
+                  accumulators[(((i * output_height() + oy) * output_width() + ox) * groups() + g) * group_output_channels() + oc] =
+                    bias[g * group_output_channels() + oc];
+                }
               }
             }
           }
         }
+      } else {
+        std::fill(accumulators.begin(), accumulators.end(), 0);
       }
       for (size_t i = 0; i < batch_size(); i++) {
         for (size_t oy = 0; oy < output_height(); oy++) {
@@ -886,7 +907,7 @@
           input_pixel_stride(), output_pixel_stride(),
           input_zero_point, 1.0f /* input scale */,
           kernel_zero_point, 1.0f /* kernel scale */,
-          kernel.data(), bias.data(),
+          kernel.data(), has_bias() ? bias.data() : nullptr,
           output_zero_point, output_scale, qmin(), qmax(),
           0, &convolution_op));
 
@@ -929,17 +950,21 @@
       std::fill(output.begin(), output.end(), 0xA5);
 
       // Compute reference results for the second run, including renormalization.
-      for (size_t i = 0; i < next_batch_size(); i++) {
-        for (size_t oy = 0; oy < next_output_height(); oy++) {
-          for (size_t ox = 0; ox < next_output_width(); ox++) {
-            for (size_t g = 0; g < groups(); g++) {
-              for (size_t oc = 0; oc < group_output_channels(); oc++) {
-                next_accumulators[(((i * next_output_height() + oy) * next_output_width() + ox) * groups() + g) * group_output_channels() + oc] =
-                  bias[g * group_output_channels() + oc];
+      if (has_bias()) {
+        for (size_t i = 0; i < next_batch_size(); i++) {
+          for (size_t oy = 0; oy < next_output_height(); oy++) {
+            for (size_t ox = 0; ox < next_output_width(); ox++) {
+              for (size_t g = 0; g < groups(); g++) {
+                for (size_t oc = 0; oc < group_output_channels(); oc++) {
+                  next_accumulators[(((i * next_output_height() + oy) * next_output_width() + ox) * groups() + g) * group_output_channels() + oc] =
+                    bias[g * group_output_channels() + oc];
+                }
               }
             }
           }
         }
+      } else {
+        std::fill(next_accumulators.begin(), next_accumulators.end(), 0);
       }
       for (size_t i = 0; i < next_batch_size(); i++) {
         for (size_t oy = 0; oy < next_output_height(); oy++) {
@@ -1030,17 +1055,21 @@
       std::fill(output.begin(), output.end(), nanf(""));
 
       // Compute reference results, without clamping.
-      for (size_t i = 0; i < batch_size(); i++) {
-        for (size_t oy = 0; oy < output_height(); oy++) {
-          for (size_t ox = 0; ox < output_width(); ox++) {
-            for (size_t g = 0; g < groups(); g++) {
-              for (size_t oc = 0; oc < group_output_channels(); oc++) {
-                output_ref[(((i * output_height() + oy) * output_width() + ox) * groups() + g) * group_output_channels() + oc] =
-                  bias[g * group_output_channels() + oc];
+      if (has_bias()) {
+        for (size_t i = 0; i < batch_size(); i++) {
+          for (size_t oy = 0; oy < output_height(); oy++) {
+            for (size_t ox = 0; ox < output_width(); ox++) {
+              for (size_t g = 0; g < groups(); g++) {
+                for (size_t oc = 0; oc < group_output_channels(); oc++) {
+                  output_ref[(((i * output_height() + oy) * output_width() + ox) * groups() + g) * group_output_channels() + oc] =
+                    bias[g * group_output_channels() + oc];
+                }
               }
             }
           }
         }
+      } else {
+        std::fill(output_ref.begin(), output_ref.end(), 0.0f);
       }
       for (size_t i = 0; i < batch_size(); i++) {
         for (size_t oy = 0; oy < output_height(); oy++) {
@@ -1092,7 +1121,7 @@
           dilation_height(), dilation_width(),
           groups(), group_input_channels(), group_output_channels(),
           input_pixel_stride(), output_pixel_stride(),
-          kernel.data(), bias.data(),
+          kernel.data(), has_bias() ? bias.data() : nullptr,
           output_min, output_max,
           0, &convolution_op));
 
@@ -1135,17 +1164,21 @@
       std::fill(output.begin(), output.end(), nanf(""));
 
       // Compute reference results for the second run, including clamping.
-      for (size_t i = 0; i < next_batch_size(); i++) {
-        for (size_t oy = 0; oy < next_output_height(); oy++) {
-          for (size_t ox = 0; ox < next_output_width(); ox++) {
-            for (size_t g = 0; g < groups(); g++) {
-              for (size_t oc = 0; oc < group_output_channels(); oc++) {
-                next_output_ref[(((i * next_output_height() + oy) * next_output_width() + ox) * groups() + g) * group_output_channels() + oc] =
-                  bias[g * group_output_channels() + oc];
+      if (has_bias()) {
+        for (size_t i = 0; i < next_batch_size(); i++) {
+          for (size_t oy = 0; oy < next_output_height(); oy++) {
+            for (size_t ox = 0; ox < next_output_width(); ox++) {
+              for (size_t g = 0; g < groups(); g++) {
+                for (size_t oc = 0; oc < group_output_channels(); oc++) {
+                  next_output_ref[(((i * next_output_height() + oy) * next_output_width() + ox) * groups() + g) * group_output_channels() + oc] =
+                    bias[g * group_output_channels() + oc];
+                }
               }
             }
           }
         }
+      } else {
+        std::fill(next_output_ref.begin(), next_output_ref.end(), 0.0f);
       }
       for (size_t i = 0; i < next_batch_size(); i++) {
         for (size_t oy = 0; oy < next_output_height(); oy++) {
@@ -1236,5 +1269,6 @@
   uint8_t qmin_{0};
   uint8_t qmax_{255};
   bool depthwise_layout_{false};
+  bool has_bias_{true};
   size_t iterations_{1};
 };