Increase the fp precision in dumped spec.
By default, C++ will output 6 fractional digits of floating point
values. This is insufficient for some of the RGG tests that requires
precision < 1e-6.
This CL uses hex representation for fp values in the generated spec
file, while human-readable values are also dumped as a comment.
Fixes: 144788409
Test: NNT_static_fuzzing and inspected the dumped spec
Change-Id: I43ed11e9f73cb387ed8894713fe79361322315ba
diff --git a/tools/test_generator/test_harness/TestHarness.cpp b/tools/test_generator/test_harness/TestHarness.cpp
index f373327..56e1414 100644
--- a/tools/test_generator/test_harness/TestHarness.cpp
+++ b/tools/test_generator/test_harness/TestHarness.cpp
@@ -327,6 +327,21 @@
return kQuantizedTypes.count(type) > 0;
}
+bool isFloatType(TestOperandType type) {
+ static const std::set<TestOperandType> kFloatTypes = {
+ TestOperandType::TENSOR_FLOAT32,
+ TestOperandType::TENSOR_FLOAT16,
+ TestOperandType::FLOAT32,
+ TestOperandType::FLOAT16,
+ };
+ return kFloatTypes.count(type) > 0;
+}
+
+bool isConstant(TestOperandLifeTime lifetime) {
+ return lifetime == TestOperandLifeTime::CONSTANT_COPY ||
+ lifetime == TestOperandLifeTime::CONSTANT_REFERENCE;
+}
+
namespace {
const char* kOperationTypeNames[] = {
@@ -492,11 +507,27 @@
}
template <typename T>
-const auto defaultToStringFunc = [](const T& value) { return std::to_string(value); };
-
+std::string defaultToStringFunc(const T& value) {
+ return std::to_string(value);
+};
template <>
-const auto defaultToStringFunc<_Float16> =
- [](const _Float16& value) { return std::to_string(static_cast<float>(value)); };
+std::string defaultToStringFunc<_Float16>(const _Float16& value) {
+ return defaultToStringFunc(static_cast<float>(value));
+};
+
+// Dump floating point values in hex representation.
+template <typename T>
+std::string toHexFloatString(const T& value);
+template <>
+std::string toHexFloatString<float>(const float& value) {
+ std::stringstream ss;
+ ss << "\"" << std::hexfloat << value << "\"";
+ return ss.str();
+};
+template <>
+std::string toHexFloatString<_Float16>(const _Float16& value) {
+ return toHexFloatString(static_cast<float>(value));
+};
template <typename Iterator, class ToStringFunc>
std::string join(const std::string& joint, Iterator begin, Iterator end, ToStringFunc func) {
@@ -513,9 +544,15 @@
}
template <typename T>
-void dumpTestBufferToSpecFileHelper(const TestBuffer& buffer, std::ostream& os) {
+void dumpTestBufferToSpecFileHelper(const TestBuffer& buffer, bool useHexFloat, std::ostream& os) {
const T* data = buffer.get<T>();
const uint32_t length = buffer.size() / sizeof(T);
+ if constexpr (nnIsFloat<T>) {
+ if (useHexFloat) {
+ os << "from_hex([" << join(", ", data, data + length, toHexFloatString<T>) << "])";
+ return;
+ }
+ }
os << "[" << join(", ", data, data + length, defaultToStringFunc<T>) << "]";
}
@@ -530,36 +567,36 @@
}
// Dump a test buffer.
-void SpecDumper::dumpTestBuffer(TestOperandType type, const TestBuffer& buffer) {
+void SpecDumper::dumpTestBuffer(TestOperandType type, const TestBuffer& buffer, bool useHexFloat) {
switch (type) {
case TestOperandType::FLOAT32:
case TestOperandType::TENSOR_FLOAT32:
- dumpTestBufferToSpecFileHelper<float>(buffer, mOs);
+ dumpTestBufferToSpecFileHelper<float>(buffer, useHexFloat, mOs);
break;
case TestOperandType::INT32:
case TestOperandType::TENSOR_INT32:
- dumpTestBufferToSpecFileHelper<int32_t>(buffer, mOs);
+ dumpTestBufferToSpecFileHelper<int32_t>(buffer, useHexFloat, mOs);
break;
case TestOperandType::TENSOR_QUANT8_ASYMM:
- dumpTestBufferToSpecFileHelper<uint8_t>(buffer, mOs);
+ dumpTestBufferToSpecFileHelper<uint8_t>(buffer, useHexFloat, mOs);
break;
case TestOperandType::TENSOR_QUANT8_SYMM:
case TestOperandType::TENSOR_QUANT8_ASYMM_SIGNED:
- dumpTestBufferToSpecFileHelper<int8_t>(buffer, mOs);
+ dumpTestBufferToSpecFileHelper<int8_t>(buffer, useHexFloat, mOs);
break;
case TestOperandType::TENSOR_QUANT16_ASYMM:
- dumpTestBufferToSpecFileHelper<uint16_t>(buffer, mOs);
+ dumpTestBufferToSpecFileHelper<uint16_t>(buffer, useHexFloat, mOs);
break;
case TestOperandType::TENSOR_QUANT16_SYMM:
- dumpTestBufferToSpecFileHelper<int16_t>(buffer, mOs);
+ dumpTestBufferToSpecFileHelper<int16_t>(buffer, useHexFloat, mOs);
break;
case TestOperandType::BOOL:
case TestOperandType::TENSOR_BOOL8:
- dumpTestBufferToSpecFileHelper<bool8>(buffer, mOs);
+ dumpTestBufferToSpecFileHelper<bool8>(buffer, useHexFloat, mOs);
break;
case TestOperandType::FLOAT16:
case TestOperandType::TENSOR_FLOAT16:
- dumpTestBufferToSpecFileHelper<_Float16>(buffer, mOs);
+ dumpTestBufferToSpecFileHelper<_Float16>(buffer, useHexFloat, mOs);
break;
default:
CHECK(false) << "Unknown type when dumping the buffer";
@@ -571,17 +608,29 @@
<< "\", [\"" << toString(operand.type) << "\", ["
<< join(", ", operand.dimensions, defaultToStringFunc<uint32_t>) << "]";
if (operand.scale != 0.0f || operand.zeroPoint != 0) {
- mOs << ", " << operand.scale << ", " << operand.zeroPoint;
+ mOs << ", float.fromhex(" << toHexFloatString(operand.scale) << "), " << operand.zeroPoint;
}
mOs << "]";
if (operand.lifetime == TestOperandLifeTime::CONSTANT_COPY ||
operand.lifetime == TestOperandLifeTime::CONSTANT_REFERENCE) {
mOs << ", ";
- dumpTestBuffer(operand.type, operand.data);
+ dumpTestBuffer(operand.type, operand.data, /*useHexFloat=*/true);
} else if (operand.lifetime == TestOperandLifeTime::NO_VALUE) {
mOs << ", value=None";
}
- mOs << ")\n";
+ mOs << ")";
+ // For quantized data types, append a human-readable scale at the end.
+ if (operand.scale != 0.0f) {
+ mOs << " # scale = " << operand.scale;
+ }
+ // For float buffers, append human-readable values at the end.
+ if (isFloatType(operand.type) &&
+ (operand.lifetime == TestOperandLifeTime::CONSTANT_COPY ||
+ operand.lifetime == TestOperandLifeTime::CONSTANT_REFERENCE)) {
+ mOs << " # ";
+ dumpTestBuffer(operand.type, operand.data, /*useHexFloat=*/false);
+ }
+ mOs << "\n";
}
void SpecDumper::dumpTestOperation(const TestOperation& operation) {
@@ -593,6 +642,7 @@
void SpecDumper::dumpTestModel() {
CHECK_EQ(kTestModel.referenced.size(), 0u) << "Subgraphs not supported";
+ mOs << "from_hex = lambda l: [float.fromhex(i) for i in l]\n\n";
// Dump model operands.
mOs << "# Model operands\n";
@@ -614,8 +664,14 @@
operand.lifetime != TestOperandLifeTime::SUBGRAPH_OUTPUT) {
continue;
}
+ // For float buffers, dump human-readable values as a comment.
+ if (isFloatType(operand.type)) {
+ mOs << " # op" << i << ": ";
+ dumpTestBuffer(operand.type, operand.data, /*useHexFloat=*/false);
+ mOs << "\n";
+ }
mOs << " op" << i << ": ";
- dumpTestBuffer(operand.type, operand.data);
+ dumpTestBuffer(operand.type, operand.data, /*useHexFloat=*/true);
mOs << ",\n";
}
mOs << "}).DisableLifeTimeVariation()\n";
@@ -627,8 +683,14 @@
for (uint32_t i = 0; i < results.size(); i++) {
const uint32_t outputIndex = kTestModel.main.outputIndexes[i];
const auto& operand = kTestModel.main.operands[outputIndex];
+ // For float buffers, dump human-readable values as a comment.
+ if (isFloatType(operand.type)) {
+ mOs << " # op" << outputIndex << ": ";
+ dumpTestBuffer(operand.type, results[i], /*useHexFloat=*/false);
+ mOs << "\n";
+ }
mOs << " op" << outputIndex << ": ";
- dumpTestBuffer(operand.type, results[i]);
+ dumpTestBuffer(operand.type, results[i], /*useHexFloat=*/true);
mOs << ",\n";
}
mOs << "}\n";
diff --git a/tools/test_generator/test_harness/include/TestHarness.h b/tools/test_generator/test_harness/include/TestHarness.h
index c67200c..ef2dbc8 100644
--- a/tools/test_generator/test_harness/include/TestHarness.h
+++ b/tools/test_generator/test_harness/include/TestHarness.h
@@ -531,7 +531,10 @@
// Dump a test buffer as a python 1D list.
// e.g. [1, 2, 3, 4, 5]
- void dumpTestBuffer(TestOperandType type, const TestBuffer& buffer);
+ //
+ // If useHexFloat is set to true and the operand type is float, the buffer values will be
+ // dumped in hex representation.
+ void dumpTestBuffer(TestOperandType type, const TestBuffer& buffer, bool useHexFloat);
const TestModel& kTestModel;
std::ostream& mOs;