[ blas/neon ] Add NEON fp16 function for sscal
authorDebadri Samaddar <s.debadri@samsung.com>
Thu, 17 Aug 2023 14:47:55 +0000 (20:17 +0530)
committerJijoong Moon <jijoong.moon@samsung.com>
Fri, 25 Aug 2023 06:05:03 +0000 (15:05 +0900)
Enable neon sscal function for Android (ARM) fp16 computation.
Add unit test for fp16 sscal function in Android(ARM).

**Self evaluation:**
1. Build test:  [X]Passed [ ]Failed [ ]Skipped
2. Run test:  [X]Passed [ ]Failed [ ]Skipped

Signed-off-by: Debadri Samaddar <s.debadri@samsung.com>
nntrainer/tensor/blas_interface.cpp
nntrainer/tensor/blas_neon.cpp
nntrainer/tensor/blas_neon.h
test/unittest/unittest_nntrainer_tensor_neon_fp16.cpp

index adee176b8b2cbd5f5d5e53bff40ab84e30489979..49078ec6edb0bee65d6256aea64bf032fc75f228 100644 (file)
@@ -141,8 +141,17 @@ static void scopy_FP16(const unsigned int N, const _FP16 *X, const int incX,
 void sscal(const unsigned int N, const float alpha, _FP16 *X, const int incX) {
   unsigned int incx = abs(incX);
 
+#ifdef USE__FP16
+  if (incX == 1) {
+    nntrainer::neon::sscal_neon_fp16(N, X, alpha);
+  } else {
+    for (unsigned int i = 0; i < N; ++i)
+      X[i * incx] = static_cast<_FP16>(alpha) * X[i * incx];
+  }
+#else
   for (unsigned int i = 0; i < N; ++i)
     X[i * incx] = static_cast<_FP16>(alpha) * X[i * incx];
+#endif
 }
 
 static _FP16 snrm2_FP16(const unsigned int N, const _FP16 *X, const int incX) {
index 8e52c758b4aef7c74caecc1fc9fe0f8e3d65462a..a2687609c42a4f7661a6df36ccfd22bd54736d46 100644 (file)
@@ -665,4 +665,33 @@ __fp16 snrm2_neon_fp16(const unsigned int N, const __fp16 *X) {
   return ret;
 }
 
+void sscal_neon_fp16(const unsigned int N, __fp16 *X, const float alpha) {
+  const float16x8_t v_alphaX8 = vmovq_n_f16(alpha);
+  const float16x4_t v_alphaX4 = vmov_n_f16(alpha);
+
+  unsigned int idx = 0;
+
+  // processing batch of 8
+  for (; (N - idx) >= 8; idx += 8) {
+    float16x8_t x = vld1q_f16(&X[idx]);
+
+    // alpha*X -> X
+    float16x8_t mulacc = vmulq_f16(v_alphaX8, x);
+    vst1q_f16(&X[idx], mulacc);
+  }
+
+  // processing remaining batch of 4
+  for (; (N - idx) >= 4; idx += 4) {
+    float16x4_t x = vld1_f16(&X[idx]);
+
+    // alpha*X -> X
+    float16x4_t mulacc = vmul_f16(v_alphaX4, x);
+    vst1_f16(&X[idx], mulacc);
+  }
+
+  // pocessing remaining values
+  for (; idx < N; idx++)
+    X[idx] = alpha * X[idx];
+}
+
 } // namespace nntrainer::neon
index 44b235e64c6544916bd18a02a991dcf58aa70fec..6034eda161584d27e416faedb0ea0c8d2cc4bcac 100644 (file)
@@ -97,13 +97,20 @@ __fp16 sdot_neon_fp16(const unsigned int N, const __fp16 *X, const __fp16 *Y);
 #endif
 
 /**
- * @brief     sdot computation with neon: sum of all X * Y
- * @param[in] N number of elements in Y
+ * @brief     snrm2 computation with neon: Euclidean norm
+ * @param[in] N number of elements in X
  * @param[in] X __fp16 * for Vector X
- * @param[in] Y __fp16 * for Vector Y
  */
 __fp16 snrm2_neon_fp16(const unsigned int N, const __fp16 *X);
 
+/**
+ * @brief     sscal computation with neon: X = alpha * X
+ * @param[in] N number of elements in X
+ * @param[in] X __fp16 * for Vector X
+ * @param[in] alpha float number
+ */
+void sscal_neon_fp16(const unsigned int N, __fp16 *X, const float alpha);
+
 } // namespace nntrainer::neon
 
 #endif /* __cplusplus */
index 7b82eff3f8e4a37f06072ae5d75beaeabb673d7c..7d99237a62322f106eabfb4b8a0582e5d30dca3b 100644 (file)
@@ -162,6 +162,48 @@ TEST(nntrainer_Tensor, l2norm) {
   EXPECT_NEAR(result_neon, result_fp32, epsilon);
 }
 
+TEST(nntrainer_Tensor, sscal) {
+  int batch = 1;
+  int channel = 1;
+  int height = 2;
+  int width = 11;
+
+  nntrainer::TensorDim::TensorType t_type_nchw_fp16 = {
+    nntrainer::Tformat::NCHW, nntrainer::Tdatatype::FP16};
+
+  nntrainer::TensorDim::TensorType t_type_nchw_fp32 = {
+    nntrainer::Tformat::NCHW, nntrainer::Tdatatype::FP32};
+
+  nntrainer::Tensor input(batch, channel, height, width, t_type_nchw_fp16);
+  nntrainer::Tensor input_copy(batch, channel, height, width, t_type_nchw_fp16);
+  nntrainer::Tensor input_fp32(batch, channel, height, width, t_type_nchw_fp32);
+
+  const float alpha = 1e-5;
+  const float epsilon = 1e-4;
+
+  GEN_TEST_INPUT(input, i * (batch * height * channel) * alpha +
+                          j * (batch * height) * alpha + k * (width)*alpha + l +
+                          1);
+  GEN_TEST_INPUT(input_fp32, i * (batch * height * channel) * alpha +
+                               j * (batch * height) * alpha +
+                               k * (width)*alpha + l + 1);
+
+  // NEON fp16
+  int result = input.multiply_i(0.1);
+
+  // fp32
+  result = input_fp32.multiply_i(0.1);
+
+  float mseErrorNeon = mse<__fp16>(input.getData<__fp16>(),
+                                   input_fp32.getData<float>(), input.size());
+
+  double cosSimNeon = cosine_similarity<__fp16>(
+    input.getData<__fp16>(), input_fp32.getData<float>(), input.size());
+
+  EXPECT_IN_RANGE(mseErrorNeon, 0, epsilon);
+  EXPECT_IN_RANGE(cosSimNeon, 0.99, 1);
+}
+
 GTEST_API_ int main(int argc, char **argv) {
   int result = -1;