Schema changes for CLAMP, PAD float attributes

* Float attributes now serialized as uint8 vectors, but treated as floats at input/output to serialization

Signed-off-by: James Ward <james.ward@arm.com>
Change-Id: I417b0fabe0ef11fea263fe937b57d49bbfdb00da
diff --git a/include/attribute.h b/include/attribute.h
index 1178ee4..5371130 100644
--- a/include/attribute.h
+++ b/include/attribute.h
@@ -40,11 +40,39 @@
     {}
 };
 
+inline int convertFlatbuffersU8toF32(const flatbuffers::Vector<uint8_t>& in, uint32_t out_size, std::vector<float>& out)
+{
+    out.clear();
+    if (in.size() < out_size * sizeof(float))
+    {
+        printf("convertFlatbuffersU8toF32(): uint8 Flatbuffers buffer size %u must be >= target size %ld\n", in.size(),
+               out_size * sizeof(float));
+        return 1;
+    }
+    for (uint32_t i = 0; i < out_size; i++)
+    {
+        uint32_t byte0   = in[i * sizeof(float)];
+        uint32_t byte1   = in[i * sizeof(float) + 1];
+        uint32_t byte2   = in[i * sizeof(float) + 2];
+        uint32_t byte3   = in[i * sizeof(float) + 3];
+        uint32_t val_u32 = byte0 + (byte1 << 8) + (byte2 << 16) + (byte3 << 24);
+        float* val_fp32  = reinterpret_cast<float*>(&val_u32);
+        out.push_back(*val_fp32);
+    }
+    return 0;
+}
+
 #define DEF_ARGS_VER0_S_STR(V) _##V = p->V()->str();
 #define DEF_ARGS_VER0_S_DEFAULT(V) _##V = p->V();
+#define DEF_ARGS_VER0_S_float_as_bytes(V)                                                                              \
+    {                                                                                                                  \
+        std::vector<float> attr_vec;                                                                                   \
+        assert(!convertFlatbuffersU8toF32(*(p->V()), 1, attr_vec));                                                    \
+        _##V = attr_vec[0];                                                                                            \
+    }
 
 #define DEF_ARGS_VER0_S_int32_t(V) DEF_ARGS_VER0_S_DEFAULT(V)
-#define DEF_ARGS_VER0_S_float(V) DEF_ARGS_VER0_S_DEFAULT(V)
+#define DEF_ARGS_VER0_S_float(V) DEF_ARGS_VER0_S_float_as_bytes(V)
 #define DEF_ARGS_VER0_S_bool(V) DEF_ARGS_VER0_S_DEFAULT(V)
 #define DEF_ARGS_VER0_S_ResizeMode(V) DEF_ARGS_VER0_S_DEFAULT(V)
 #define DEF_ARGS_VER0_S_DType(V) DEF_ARGS_VER0_S_DEFAULT(V)
diff --git a/include/tosa_generated.h b/include/tosa_generated.h
index 1a79453..b34875f 100644
--- a/include/tosa_generated.h
+++ b/include/tosa_generated.h
@@ -968,15 +968,16 @@
   int32_t pad_const_int() const {
     return GetField<int32_t>(VT_PAD_CONST_INT, 0);
   }
-  float pad_const_fp() const {
-    return GetField<float>(VT_PAD_CONST_FP, 0.0f);
+  const flatbuffers::Vector<uint8_t> *pad_const_fp() const {
+    return GetPointer<const flatbuffers::Vector<uint8_t> *>(VT_PAD_CONST_FP);
   }
   bool Verify(flatbuffers::Verifier &verifier) const {
     return VerifyTableStart(verifier) &&
            VerifyOffset(verifier, VT_PADDING) &&
            verifier.VerifyVector(padding()) &&
            VerifyField<int32_t>(verifier, VT_PAD_CONST_INT, 4) &&
-           VerifyField<float>(verifier, VT_PAD_CONST_FP, 4) &&
+           VerifyOffset(verifier, VT_PAD_CONST_FP) &&
+           verifier.VerifyVector(pad_const_fp()) &&
            verifier.EndTable();
   }
 };
@@ -991,8 +992,8 @@
   void add_pad_const_int(int32_t pad_const_int) {
     fbb_.AddElement<int32_t>(PadAttribute::VT_PAD_CONST_INT, pad_const_int, 0);
   }
-  void add_pad_const_fp(float pad_const_fp) {
-    fbb_.AddElement<float>(PadAttribute::VT_PAD_CONST_FP, pad_const_fp, 0.0f);
+  void add_pad_const_fp(flatbuffers::Offset<flatbuffers::Vector<uint8_t>> pad_const_fp) {
+    fbb_.AddOffset(PadAttribute::VT_PAD_CONST_FP, pad_const_fp);
   }
   explicit PadAttributeBuilder(flatbuffers::FlatBufferBuilder &_fbb)
         : fbb_(_fbb) {
@@ -1009,7 +1010,7 @@
     flatbuffers::FlatBufferBuilder &_fbb,
     flatbuffers::Offset<flatbuffers::Vector<int32_t>> padding = 0,
     int32_t pad_const_int = 0,
-    float pad_const_fp = 0.0f) {
+    flatbuffers::Offset<flatbuffers::Vector<uint8_t>> pad_const_fp = 0) {
   PadAttributeBuilder builder_(_fbb);
   builder_.add_pad_const_fp(pad_const_fp);
   builder_.add_pad_const_int(pad_const_int);
@@ -1021,13 +1022,15 @@
     flatbuffers::FlatBufferBuilder &_fbb,
     const std::vector<int32_t> *padding = nullptr,
     int32_t pad_const_int = 0,
-    float pad_const_fp = 0.0f) {
+    const std::vector<uint8_t> *pad_const_fp = nullptr) {
   auto padding__ = padding ? _fbb.CreateVector<int32_t>(*padding) : 0;
+  if (pad_const_fp) { _fbb.ForceVectorAlignment(pad_const_fp->size(), sizeof(uint8_t), 8); }
+  auto pad_const_fp__ = pad_const_fp ? _fbb.CreateVector<uint8_t>(*pad_const_fp) : 0;
   return tosa::CreatePadAttribute(
       _fbb,
       padding__,
       pad_const_int,
-      pad_const_fp);
+      pad_const_fp__);
 }
 
 struct AxisAttribute FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
@@ -1343,18 +1346,20 @@
   int32_t max_int() const {
     return GetField<int32_t>(VT_MAX_INT, 0);
   }
-  float min_fp() const {
-    return GetField<float>(VT_MIN_FP, 0.0f);
+  const flatbuffers::Vector<uint8_t> *min_fp() const {
+    return GetPointer<const flatbuffers::Vector<uint8_t> *>(VT_MIN_FP);
   }
-  float max_fp() const {
-    return GetField<float>(VT_MAX_FP, 0.0f);
+  const flatbuffers::Vector<uint8_t> *max_fp() const {
+    return GetPointer<const flatbuffers::Vector<uint8_t> *>(VT_MAX_FP);
   }
   bool Verify(flatbuffers::Verifier &verifier) const {
     return VerifyTableStart(verifier) &&
            VerifyField<int32_t>(verifier, VT_MIN_INT, 4) &&
            VerifyField<int32_t>(verifier, VT_MAX_INT, 4) &&
-           VerifyField<float>(verifier, VT_MIN_FP, 4) &&
-           VerifyField<float>(verifier, VT_MAX_FP, 4) &&
+           VerifyOffset(verifier, VT_MIN_FP) &&
+           verifier.VerifyVector(min_fp()) &&
+           VerifyOffset(verifier, VT_MAX_FP) &&
+           verifier.VerifyVector(max_fp()) &&
            verifier.EndTable();
   }
 };
@@ -1369,11 +1374,11 @@
   void add_max_int(int32_t max_int) {
     fbb_.AddElement<int32_t>(ClampAttribute::VT_MAX_INT, max_int, 0);
   }
-  void add_min_fp(float min_fp) {
-    fbb_.AddElement<float>(ClampAttribute::VT_MIN_FP, min_fp, 0.0f);
+  void add_min_fp(flatbuffers::Offset<flatbuffers::Vector<uint8_t>> min_fp) {
+    fbb_.AddOffset(ClampAttribute::VT_MIN_FP, min_fp);
   }
-  void add_max_fp(float max_fp) {
-    fbb_.AddElement<float>(ClampAttribute::VT_MAX_FP, max_fp, 0.0f);
+  void add_max_fp(flatbuffers::Offset<flatbuffers::Vector<uint8_t>> max_fp) {
+    fbb_.AddOffset(ClampAttribute::VT_MAX_FP, max_fp);
   }
   explicit ClampAttributeBuilder(flatbuffers::FlatBufferBuilder &_fbb)
         : fbb_(_fbb) {
@@ -1390,8 +1395,8 @@
     flatbuffers::FlatBufferBuilder &_fbb,
     int32_t min_int = 0,
     int32_t max_int = 0,
-    float min_fp = 0.0f,
-    float max_fp = 0.0f) {
+    flatbuffers::Offset<flatbuffers::Vector<uint8_t>> min_fp = 0,
+    flatbuffers::Offset<flatbuffers::Vector<uint8_t>> max_fp = 0) {
   ClampAttributeBuilder builder_(_fbb);
   builder_.add_max_fp(max_fp);
   builder_.add_min_fp(min_fp);
@@ -1400,6 +1405,24 @@
   return builder_.Finish();
 }
 
+inline flatbuffers::Offset<ClampAttribute> CreateClampAttributeDirect(
+    flatbuffers::FlatBufferBuilder &_fbb,
+    int32_t min_int = 0,
+    int32_t max_int = 0,
+    const std::vector<uint8_t> *min_fp = nullptr,
+    const std::vector<uint8_t> *max_fp = nullptr) {
+  if (min_fp) { _fbb.ForceVectorAlignment(min_fp->size(), sizeof(uint8_t), 8); }
+  auto min_fp__ = min_fp ? _fbb.CreateVector<uint8_t>(*min_fp) : 0;
+  if (max_fp) { _fbb.ForceVectorAlignment(max_fp->size(), sizeof(uint8_t), 8); }
+  auto max_fp__ = max_fp ? _fbb.CreateVector<uint8_t>(*max_fp) : 0;
+  return tosa::CreateClampAttribute(
+      _fbb,
+      min_int,
+      max_int,
+      min_fp__,
+      max_fp__);
+}
+
 struct RescaleAttribute FLATBUFFERS_FINAL_CLASS : private flatbuffers::Table {
   typedef RescaleAttributeBuilder Builder;
   enum FlatBuffersVTableOffset FLATBUFFERS_VTABLE_UNDERLYING_TYPE {
diff --git a/python/serializer/tosa_serializer.py b/python/serializer/tosa_serializer.py
index e8311ce..f579df2 100644
--- a/python/serializer/tosa_serializer.py
+++ b/python/serializer/tosa_serializer.py
@@ -13,10 +13,11 @@
 #    limitations under the License.
 
 import os
+import struct
+import serializer.tosa_serializer as ts
 import json
 import flatbuffers
 import numpy as np
-import struct
 from enum import IntEnum, unique
 from tosa import (
     TosaGraph,
@@ -197,7 +198,7 @@
         self.ints.append((a.AddWeightZp, weight_zp))
         self.ints.append((a.AddAccumDtype, accum_dtype))
 
-    def PadAttribute(self, padding, pad_const_int, pad_const_fp):
+    def PadAttribute(self, serializer_builder, padding, pad_const_int, pad_const_fp):
         from tosa import PadAttribute as a, Attribute
 
         self.utype = Attribute.Attribute().PadAttribute
@@ -205,7 +206,14 @@
 
         self.intvecs.append((a.AddPadding, padding))
         self.ints.append((a.AddPadConstInt, pad_const_int))
-        self.floats.append((a.AddPadConstFp, pad_const_fp))
+
+        # pad_const_fp attribute serialized as uint8 vector
+        pad_const_float_as_bytes = struct.pack("<f", pad_const_fp)
+        serialized_pad_const_fp = ts.TosaSerializer.serializeUint8Vec(
+            serializer_builder, pad_const_float_as_bytes
+        )
+
+        self.floats.append((a.AddPadConstFp, serialized_pad_const_fp))
 
     def AxisAttribute(self, axis):
         from tosa import AxisAttribute as a, Attribute
@@ -251,7 +259,7 @@
         self.int16vecs.append((a.AddBorder, border))
         self.ints.append((a.AddMode, mode))
 
-    def ClampAttribute(self, minint, maxint, minfp, maxfp):
+    def ClampAttribute(self, serializer_builder, minint, maxint, minfp, maxfp):
         from tosa import ClampAttribute as a, Attribute
 
         self.utype = Attribute.Attribute().ClampAttribute
@@ -260,8 +268,18 @@
         self.ints.append((a.AddMinInt, minint))
         self.ints.append((a.AddMaxInt, maxint))
 
-        self.ints.append((a.AddMinFp, minfp))
-        self.ints.append((a.AddMaxFp, maxfp))
+        # min/max float attributes serialized as uint8 vectors
+        minfp_bytes = struct.pack("<f", minfp)
+        maxfp_bytes = struct.pack("<f", maxfp)
+        serialized_minfp_bytes = ts.TosaSerializer.serializeUint8Vec(
+            serializer_builder, minfp_bytes
+        )
+        serialized_maxfp_bytes = ts.TosaSerializer.serializeUint8Vec(
+            serializer_builder, maxfp_bytes
+        )
+
+        self.floats.append((a.AddMinFp, serialized_minfp_bytes))
+        self.floats.append((a.AddMaxFp, serialized_maxfp_bytes))
 
     def RescaleAttribute(
         self, input_zp, output_zp, multiplier, shift, scale32, double_round, per_channel
@@ -477,9 +495,11 @@
                 np_arr = np.array(self.data, dtype=np.float16)
                 u8_data.extend(np_arr.view(np.uint8))
             elif self.dtype == DType.FP32 or self.dtype == DType.BF16:
-                for val in self.data:
-                    b = struct.pack("!f", val)
-                    u8_data.extend([b[3], b[2], b[1], b[0]])
+                # for val in self.data:
+                #     b = struct.pack("!f", val)
+                #     u8_data.extend([b[3], b[2], b[1], b[0]])
+                np_arr = np.array(self.data, dtype=np.float32)
+                u8_data.extend(np_arr.view(np.uint8))
             elif self.dtype == TosaDType.DType:
                 # Serialize DType enum data as uint8 bytes
                 for val in self.data:
diff --git a/python/tosa/ClampAttribute.py b/python/tosa/ClampAttribute.py
index 58d1f0a..7937944 100644
--- a/python/tosa/ClampAttribute.py
+++ b/python/tosa/ClampAttribute.py
@@ -43,18 +43,58 @@
         return 0
 
     # ClampAttribute
-    def MinFp(self):
+    def MinFp(self, j):
         o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
         if o != 0:
-            return self._tab.Get(flatbuffers.number_types.Float32Flags, o + self._tab.Pos)
-        return 0.0
+            a = self._tab.Vector(o)
+            return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1))
+        return 0
 
     # ClampAttribute
-    def MaxFp(self):
+    def MinFpAsNumpy(self):
+        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
+        if o != 0:
+            return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o)
+        return 0
+
+    # ClampAttribute
+    def MinFpLength(self):
+        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
+        if o != 0:
+            return self._tab.VectorLen(o)
+        return 0
+
+    # ClampAttribute
+    def MinFpIsNone(self):
+        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
+        return o == 0
+
+    # ClampAttribute
+    def MaxFp(self, j):
         o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
         if o != 0:
-            return self._tab.Get(flatbuffers.number_types.Float32Flags, o + self._tab.Pos)
-        return 0.0
+            a = self._tab.Vector(o)
+            return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1))
+        return 0
+
+    # ClampAttribute
+    def MaxFpAsNumpy(self):
+        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
+        if o != 0:
+            return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o)
+        return 0
+
+    # ClampAttribute
+    def MaxFpLength(self):
+        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
+        if o != 0:
+            return self._tab.VectorLen(o)
+        return 0
+
+    # ClampAttribute
+    def MaxFpIsNone(self):
+        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(10))
+        return o == 0
 
 def ClampAttributeStart(builder): builder.StartObject(4)
 def Start(builder):
@@ -65,12 +105,18 @@
 def ClampAttributeAddMaxInt(builder, maxInt): builder.PrependInt32Slot(1, maxInt, 0)
 def AddMaxInt(builder, maxInt):
     return ClampAttributeAddMaxInt(builder, maxInt)
-def ClampAttributeAddMinFp(builder, minFp): builder.PrependFloat32Slot(2, minFp, 0.0)
+def ClampAttributeAddMinFp(builder, minFp): builder.PrependUOffsetTRelativeSlot(2, flatbuffers.number_types.UOffsetTFlags.py_type(minFp), 0)
 def AddMinFp(builder, minFp):
     return ClampAttributeAddMinFp(builder, minFp)
-def ClampAttributeAddMaxFp(builder, maxFp): builder.PrependFloat32Slot(3, maxFp, 0.0)
+def ClampAttributeStartMinFpVector(builder, numElems): return builder.StartVector(1, numElems, 1)
+def StartMinFpVector(builder, numElems):
+    return ClampAttributeStartMinFpVector(builder, numElems)
+def ClampAttributeAddMaxFp(builder, maxFp): builder.PrependUOffsetTRelativeSlot(3, flatbuffers.number_types.UOffsetTFlags.py_type(maxFp), 0)
 def AddMaxFp(builder, maxFp):
     return ClampAttributeAddMaxFp(builder, maxFp)
+def ClampAttributeStartMaxFpVector(builder, numElems): return builder.StartVector(1, numElems, 1)
+def StartMaxFpVector(builder, numElems):
+    return ClampAttributeStartMaxFpVector(builder, numElems)
 def ClampAttributeEnd(builder): return builder.EndObject()
 def End(builder):
     return ClampAttributeEnd(builder)
\ No newline at end of file
diff --git a/python/tosa/PadAttribute.py b/python/tosa/PadAttribute.py
index 8d49d5a..23698d1 100644
--- a/python/tosa/PadAttribute.py
+++ b/python/tosa/PadAttribute.py
@@ -63,11 +63,31 @@
         return 0
 
     # PadAttribute
-    def PadConstFp(self):
+    def PadConstFp(self, j):
         o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
         if o != 0:
-            return self._tab.Get(flatbuffers.number_types.Float32Flags, o + self._tab.Pos)
-        return 0.0
+            a = self._tab.Vector(o)
+            return self._tab.Get(flatbuffers.number_types.Uint8Flags, a + flatbuffers.number_types.UOffsetTFlags.py_type(j * 1))
+        return 0
+
+    # PadAttribute
+    def PadConstFpAsNumpy(self):
+        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
+        if o != 0:
+            return self._tab.GetVectorAsNumpy(flatbuffers.number_types.Uint8Flags, o)
+        return 0
+
+    # PadAttribute
+    def PadConstFpLength(self):
+        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
+        if o != 0:
+            return self._tab.VectorLen(o)
+        return 0
+
+    # PadAttribute
+    def PadConstFpIsNone(self):
+        o = flatbuffers.number_types.UOffsetTFlags.py_type(self._tab.Offset(8))
+        return o == 0
 
 def PadAttributeStart(builder): builder.StartObject(3)
 def Start(builder):
@@ -81,9 +101,12 @@
 def PadAttributeAddPadConstInt(builder, padConstInt): builder.PrependInt32Slot(1, padConstInt, 0)
 def AddPadConstInt(builder, padConstInt):
     return PadAttributeAddPadConstInt(builder, padConstInt)
-def PadAttributeAddPadConstFp(builder, padConstFp): builder.PrependFloat32Slot(2, padConstFp, 0.0)
+def PadAttributeAddPadConstFp(builder, padConstFp): builder.PrependUOffsetTRelativeSlot(2, flatbuffers.number_types.UOffsetTFlags.py_type(padConstFp), 0)
 def AddPadConstFp(builder, padConstFp):
     return PadAttributeAddPadConstFp(builder, padConstFp)
+def PadAttributeStartPadConstFpVector(builder, numElems): return builder.StartVector(1, numElems, 1)
+def StartPadConstFpVector(builder, numElems):
+    return PadAttributeStartPadConstFpVector(builder, numElems)
 def PadAttributeEnd(builder): return builder.EndObject()
 def End(builder):
     return PadAttributeEnd(builder)
\ No newline at end of file
diff --git a/schema/tosa.fbs b/schema/tosa.fbs
index ff8697b..e4a2498 100644
--- a/schema/tosa.fbs
+++ b/schema/tosa.fbs
@@ -173,7 +173,7 @@
 table PadAttribute {
   padding: [int32];
   pad_const_int: int32;
-  pad_const_fp: float;
+  pad_const_fp: [ubyte] (force_align: 8);
 }
 
 table AxisAttribute {
@@ -203,8 +203,8 @@
 table ClampAttribute {
   min_int: int32;
   max_int: int32;
-  min_fp: float;
-  max_fp: float;
+  min_fp: [ubyte] (force_align: 8);
+  max_fp: [ubyte] (force_align: 8);
 }
 
 table RescaleAttribute {
diff --git a/src/tosa_serialization_handler.cpp b/src/tosa_serialization_handler.cpp
index a4410f2..2062522 100644
--- a/src/tosa_serialization_handler.cpp
+++ b/src/tosa_serialization_handler.cpp
@@ -619,6 +619,14 @@
     return TOSA_OK;
 }
 
+std::vector<uint8_t> float_to_u8_wrapper(float f_in)
+{
+    const std::vector<float> f_vec{ f_in };
+    std::vector<uint8_t> u8_out;
+    TosaSerializationHandler::ConvertF32toU8(f_vec, u8_out);
+    return u8_out;
+}
+
 tosa_err_t TosaSerializationHandler::Serialize()
 {
     // regions
@@ -682,9 +690,11 @@
                         fb_attribute = 0;
                         break;
 #define DEF_ARGS_S_STR(NAME, V) , _builder.CreateString(reinterpret_cast<Tosa##NAME*>(op->GetAttribute())->V().c_str())
+#define DEF_ARGS_S_FP_as_U8(NAME, V)                                                                                   \
+    , _builder.CreateVector<uint8_t>(float_to_u8_wrapper(reinterpret_cast<Tosa##NAME*>(op->GetAttribute())->V()))
 #define DEF_ARGS_S_DEFAULT(NAME, V) , reinterpret_cast<Tosa##NAME*>(op->GetAttribute())->V()
 #define DEF_ARGS_S_int32_t(NAME, V) DEF_ARGS_S_DEFAULT(NAME, V)
-#define DEF_ARGS_S_float(NAME, V) DEF_ARGS_S_DEFAULT(NAME, V)
+#define DEF_ARGS_S_float(NAME, V) DEF_ARGS_S_FP_as_U8(NAME, V)
 #define DEF_ARGS_S_bool(NAME, V) DEF_ARGS_S_DEFAULT(NAME, V)
 #define DEF_ARGS_S_ResizeMode(NAME, V) DEF_ARGS_S_DEFAULT(NAME, V)
 #define DEF_ARGS_S_DType(NAME, V) DEF_ARGS_S_DEFAULT(NAME, V)