Skip to content

Commit 430847e

Browse files
authored
Printing top K values instead of just single max value (#189)
Currently WinMLRunner prints top value in the result float tensor. This change is to output top K values instead: Here is the sample output: Creating Session with GPU: AMD FirePro W4100 Graphics Adapter Binding (device = GPU, iteration = 1, inputBinding = CPU, inputDataType = RGB_Image, deviceCreationLocation = WinML)...[SUCCESS] Outputting top 5 values Feature Name: resnetv23_dense0_fwd index: 409, value: 6.74914 index: 769, value: 5.66233 index: 664, value: 5.62002 index: 585, value: 5.48044 index: 872, value: 5.29839 Evaluating (device = GPU, iteration = 1, inputBinding = CPU, inputDataType = RGB_Image, deviceCreationLocation = WinML)...[SUCCESS]
1 parent 42ee5b4 commit 430847e

File tree

6 files changed

+186
-57
lines changed

6 files changed

+186
-57
lines changed

Testing/WinMLRunnerTest/WinMLRunnerTest.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -749,6 +749,12 @@ namespace WinMLRunnerTest
749749
Assert::AreEqual(S_OK, RunProc((wchar_t*)command.c_str()));
750750
}
751751

752+
TEST_METHOD(TestTopK)
753+
{
754+
const std::wstring command = BuildCommand({ EXE_PATH, L"-model", L"SqueezeNet.onnx", L"-TopK", L"5" });
755+
Assert::AreEqual(S_OK, RunProc((wchar_t*)command.c_str()));
756+
}
757+
752758
/* Commenting out test until WinMLRunnerDLL.dll is properly written and ABI friendly
753759
TEST_METHOD(TestWinMLRunnerDllLinking)
754760
{

Tools/WinMLRunner/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,7 @@ Required command-Line arguments:
4545
-Perf [all]: capture performance measurements such as timing and memory usage. Specifying "all" will output all measurements
4646
-Iterations : # times perf measurements will be run/averaged
4747
-Input <fully qualified path>: binds image or CSV to model
48+
-TopK <number>: print top <number> values in the result. Default to 1
4849
-PerfOutput [<fully qualified path>]: csv file to write the perf results to
4950
-SavePerIterationPerf : save per iteration performance results to csv file
5051
-SaveTensorData <saveMode folderPath>: saveMode: save first iteration or all iteration output tensor results to csv file [First, All]

Tools/WinMLRunner/src/BindingUtilities.h

Lines changed: 141 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -13,35 +13,119 @@ using namespace winrt::Windows::Graphics::DirectX;
1313
using namespace winrt::Windows::Graphics::Imaging;
1414
using namespace winrt::Windows::Graphics::DirectX::Direct3D11;
1515

16-
template <TensorKind T> struct TensorKindToType { static_assert(true, "No TensorKind mapped for given type!"); };
17-
template <> struct TensorKindToType<TensorKind::UInt8> { typedef uint8_t Type; };
18-
template <> struct TensorKindToType<TensorKind::Int8> { typedef uint8_t Type; };
19-
template <> struct TensorKindToType<TensorKind::UInt16> { typedef uint16_t Type; };
20-
template <> struct TensorKindToType<TensorKind::Int16> { typedef int16_t Type; };
21-
template <> struct TensorKindToType<TensorKind::UInt32> { typedef uint32_t Type; };
22-
template <> struct TensorKindToType<TensorKind::Int32> { typedef int32_t Type; };
23-
template <> struct TensorKindToType<TensorKind::UInt64> { typedef uint64_t Type; };
24-
template <> struct TensorKindToType<TensorKind::Int64> { typedef int64_t Type; };
25-
template <> struct TensorKindToType<TensorKind::Boolean> { typedef boolean Type; };
26-
template <> struct TensorKindToType<TensorKind::Double> { typedef double Type; };
27-
template <> struct TensorKindToType<TensorKind::Float> { typedef float Type; };
28-
template <> struct TensorKindToType<TensorKind::Float16> { typedef float Type; };
29-
template <> struct TensorKindToType<TensorKind::String> { typedef winrt::hstring Type; };
30-
31-
template <TensorKind T> struct TensorKindToValue { static_assert(true, "No TensorKind mapped for given type!"); };
32-
template <> struct TensorKindToValue<TensorKind::UInt8> { typedef TensorUInt8Bit Type; };
33-
template <> struct TensorKindToValue<TensorKind::Int8> { typedef TensorInt8Bit Type; };
34-
template <> struct TensorKindToValue<TensorKind::UInt16> { typedef TensorUInt16Bit Type; };
35-
template <> struct TensorKindToValue<TensorKind::Int16> { typedef TensorInt16Bit Type; };
36-
template <> struct TensorKindToValue<TensorKind::UInt32> { typedef TensorUInt32Bit Type; };
37-
template <> struct TensorKindToValue<TensorKind::Int32> { typedef TensorInt32Bit Type; };
38-
template <> struct TensorKindToValue<TensorKind::UInt64> { typedef TensorUInt64Bit Type; };
39-
template <> struct TensorKindToValue<TensorKind::Int64> { typedef TensorInt64Bit Type; };
40-
template <> struct TensorKindToValue<TensorKind::Boolean> { typedef TensorBoolean Type; };
41-
template <> struct TensorKindToValue<TensorKind::Double> { typedef TensorDouble Type; };
42-
template <> struct TensorKindToValue<TensorKind::Float> { typedef TensorFloat Type; };
43-
template <> struct TensorKindToValue<TensorKind::Float16> { typedef TensorFloat16Bit Type; };
44-
template <> struct TensorKindToValue<TensorKind::String> { typedef TensorString Type; };
16+
template <TensorKind T> struct TensorKindToType
17+
{
18+
static_assert(true, "No TensorKind mapped for given type!");
19+
};
20+
template <> struct TensorKindToType<TensorKind::UInt8>
21+
{
22+
typedef uint8_t Type;
23+
};
24+
template <> struct TensorKindToType<TensorKind::Int8>
25+
{
26+
typedef uint8_t Type;
27+
};
28+
template <> struct TensorKindToType<TensorKind::UInt16>
29+
{
30+
typedef uint16_t Type;
31+
};
32+
template <> struct TensorKindToType<TensorKind::Int16>
33+
{
34+
typedef int16_t Type;
35+
};
36+
template <> struct TensorKindToType<TensorKind::UInt32>
37+
{
38+
typedef uint32_t Type;
39+
};
40+
template <> struct TensorKindToType<TensorKind::Int32>
41+
{
42+
typedef int32_t Type;
43+
};
44+
template <> struct TensorKindToType<TensorKind::UInt64>
45+
{
46+
typedef uint64_t Type;
47+
};
48+
template <> struct TensorKindToType<TensorKind::Int64>
49+
{
50+
typedef int64_t Type;
51+
};
52+
template <> struct TensorKindToType<TensorKind::Boolean>
53+
{
54+
typedef boolean Type;
55+
};
56+
template <> struct TensorKindToType<TensorKind::Double>
57+
{
58+
typedef double Type;
59+
};
60+
template <> struct TensorKindToType<TensorKind::Float>
61+
{
62+
typedef float Type;
63+
};
64+
template <> struct TensorKindToType<TensorKind::Float16>
65+
{
66+
typedef float Type;
67+
};
68+
template <> struct TensorKindToType<TensorKind::String>
69+
{
70+
typedef winrt::hstring Type;
71+
};
72+
73+
template <TensorKind T> struct TensorKindToValue
74+
{
75+
static_assert(true, "No TensorKind mapped for given type!");
76+
};
77+
template <> struct TensorKindToValue<TensorKind::UInt8>
78+
{
79+
typedef TensorUInt8Bit Type;
80+
};
81+
template <> struct TensorKindToValue<TensorKind::Int8>
82+
{
83+
typedef TensorInt8Bit Type;
84+
};
85+
template <> struct TensorKindToValue<TensorKind::UInt16>
86+
{
87+
typedef TensorUInt16Bit Type;
88+
};
89+
template <> struct TensorKindToValue<TensorKind::Int16>
90+
{
91+
typedef TensorInt16Bit Type;
92+
};
93+
template <> struct TensorKindToValue<TensorKind::UInt32>
94+
{
95+
typedef TensorUInt32Bit Type;
96+
};
97+
template <> struct TensorKindToValue<TensorKind::Int32>
98+
{
99+
typedef TensorInt32Bit Type;
100+
};
101+
template <> struct TensorKindToValue<TensorKind::UInt64>
102+
{
103+
typedef TensorUInt64Bit Type;
104+
};
105+
template <> struct TensorKindToValue<TensorKind::Int64>
106+
{
107+
typedef TensorInt64Bit Type;
108+
};
109+
template <> struct TensorKindToValue<TensorKind::Boolean>
110+
{
111+
typedef TensorBoolean Type;
112+
};
113+
template <> struct TensorKindToValue<TensorKind::Double>
114+
{
115+
typedef TensorDouble Type;
116+
};
117+
template <> struct TensorKindToValue<TensorKind::Float>
118+
{
119+
typedef TensorFloat Type;
120+
};
121+
template <> struct TensorKindToValue<TensorKind::Float16>
122+
{
123+
typedef TensorFloat16Bit Type;
124+
};
125+
template <> struct TensorKindToValue<TensorKind::String>
126+
{
127+
typedef TensorString Type;
128+
};
45129

46130
namespace BindingUtilities
47131
{
@@ -235,19 +319,21 @@ namespace BindingUtilities
235319
for (UINT dim = 0; dim < tensorDescriptorShape.Size(); dim++)
236320
{
237321
INT64 dimSize = tensorDescriptorShape.GetAt(dim);
238-
if (dimSize > 0) //If the dimension is greater than 0, then it is known.
322+
if (dimSize > 0) // If the dimension is greater than 0, then it is known.
239323
{
240324
vecShape.push_back(dimSize);
241325
}
242-
else //otherwise, make sure that the dimension is -1, representing free dimension. If not, then it's an invalid model.
326+
else // otherwise, make sure that the dimension is -1, representing free dimension. If not, then it's an
327+
// invalid model.
243328
{
244329
if (dimSize == -1)
245330
{
246331
vecShape.push_back(1);
247332
}
248333
else
249334
{
250-
throw hresult_invalid_argument(L"Failed to create a tensor with an unknown dimension of: " + dimSize);
335+
throw hresult_invalid_argument(L"Failed to create a tensor with an unknown dimension of: " +
336+
dimSize);
251337
}
252338
}
253339
}
@@ -258,7 +344,8 @@ namespace BindingUtilities
258344

259345
BYTE* actualData;
260346
uint32_t actualSizeInBytes;
261-
spTensorValueNative->GetBuffer(&actualData, &actualSizeInBytes); //Need to GetBuffer to have CPU memory backing tensorValue
347+
spTensorValueNative->GetBuffer(
348+
&actualData, &actualSizeInBytes); // Need to GetBuffer to have CPU memory backing tensorValue
262349
return tensorValue;
263350
}
264351
else
@@ -419,8 +506,8 @@ namespace BindingUtilities
419506
com_ptr<ITensorNative> itn = results.Lookup(desc.Name()).as<ITensorNative>();
420507
HRESULT(itn->GetBuffer(reinterpret_cast<BYTE**>(&tensor), &uCapacity));
421508
int size = 0;
422-
float maxValue = 0;
423-
int maxIndex = 0;
509+
unsigned int topK = args.TopK();
510+
std::vector<std::pair<float, int>> maxKValues;
424511
std::ofstream fout;
425512
if (args.IsSaveTensor())
426513
{
@@ -445,12 +532,12 @@ namespace BindingUtilities
445532
break;
446533
case TensorKind::Float16:
447534
{
448-
output.ProcessTensorResult<HALF>(args, tensor, uCapacity, maxValue, maxIndex, fout);
535+
output.ProcessTensorResult<HALF>(args, tensor, uCapacity, maxKValues, fout, topK);
449536
}
450537
break;
451538
case TensorKind::Float:
452539
{
453-
output.ProcessTensorResult<float>(args, tensor, uCapacity, maxValue, maxIndex, fout);
540+
output.ProcessTensorResult<float>(args, tensor, uCapacity, maxKValues, fout, topK);
454541
}
455542
break;
456543
case TensorKind::Int64:
@@ -472,16 +559,27 @@ namespace BindingUtilities
472559
if (args.IsSaveTensor())
473560
{
474561
fout.close();
475-
std::string iterationResult =
476-
"Index: " + std::to_string(maxIndex) + "; Value: " + std::to_string(maxValue);
477-
output.SaveResult(iterationNum, iterationResult, static_cast<int>(hash_data(tensor, uCapacity)));
562+
for (auto& pair : maxKValues)
563+
{
564+
auto maxValue = pair.first;
565+
auto maxIndex = pair.second;
566+
std::string iterationResult =
567+
"Index: " + std::to_string(maxIndex) + "; Value: " + std::to_string(maxValue);
568+
output.SaveResult(iterationNum, iterationResult,
569+
static_cast<int>(hash_data(tensor, uCapacity)));
570+
}
478571
}
479572
if (!args.IsGarbageInput() && iterationNum == 0)
480573
{
481-
std::cout << "Outputting results.. " << std::endl;
574+
std::cout << "Outputting top " << args.TopK() << " values" << std::endl;
482575
std::cout << "Feature Name: " << name << std::endl;
483-
std::wcout << " resultVector[" << maxIndex << "] has the maximal value of " << maxValue
484-
<< std::endl;
576+
for (auto& pair : maxKValues)
577+
{
578+
auto maxValue = pair.first;
579+
auto maxIndex = pair.second;
580+
std::wcout << " index: " << maxIndex << ", value: " << maxValue
581+
<< std::endl;
582+
}
485583
}
486584
}
487585
else if (desc.Kind() == LearningModelFeatureKind::Sequence)

Tools/WinMLRunner/src/CommandLineArgs.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ void CommandLineArgs::PrintUsage()
3131
<< std::endl;
3232
std::cout << " -Iterations : # times perf measurements will be run/averaged" << std::endl;
3333
std::cout << " -Input <fully qualified path>: binds image or CSV to model" << std::endl;
34+
std::cout << " -TopK <number>: print top <number> values in the result. Default to 1" << std::endl;
3435
std::cout << " -PerfOutput [<fully qualified path>]: csv file to write the perf results to" << std::endl;
3536
std::cout << " -SavePerIterationPerf : save per iteration performance results to csv file" << std::endl;
3637
std::cout << " -SaveTensorData <saveMode folderPath>: saveMode: save first iteration or all iteration output "
@@ -278,6 +279,11 @@ CommandLineArgs::CommandLineArgs(const std::vector<std::wstring>& args)
278279
unsigned thread_interval = std::stoi(args[++i].c_str());
279280
SetThreadInterval(thread_interval);
280281
}
282+
else if ((_wcsicmp(args[i].c_str(), L"-TopK") == 0))
283+
{
284+
CheckNextArgument(args, i);
285+
SetTopK(std::stoi(args[++i].c_str()));
286+
}
281287
else
282288
{
283289
std::wstring msg = L"Unknown option ";

Tools/WinMLRunner/src/CommandLineArgs.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,7 @@ class CommandLineArgs
7272
uint32_t NumIterations() const { return m_numIterations; }
7373
uint32_t NumThreads() const { return m_numThreads; }
7474
uint32_t ThreadInterval() const { return m_threadInterval; } // Thread interval in milliseconds
75+
uint32_t TopK() const { return m_topK; }
7576

7677
void ToggleCPU(bool useCPU) { m_useCPU = useCPU; }
7778
void ToggleGPU(bool useGPU) { m_useGPU = useGPU; }
@@ -96,6 +97,7 @@ class CommandLineArgs
9697
void SetInputDataPath(const std::wstring& inputDataPath) { m_inputData = inputDataPath; }
9798
void SetNumThreads(unsigned numThreads) { m_numThreads = numThreads; }
9899
void SetThreadInterval(unsigned threadInterval) { m_threadInterval = threadInterval; }
100+
void SetTopK(unsigned k) { m_topK = k; }
99101
void SetPerformanceCSVPath(const std::wstring& performanceCSVPath)
100102
{
101103
m_perfOutputPath = performanceCSVPath;
@@ -140,6 +142,7 @@ class CommandLineArgs
140142
uint32_t m_numIterations = 1;
141143
uint32_t m_numThreads = 1;
142144
uint32_t m_threadInterval = 0;
145+
uint32_t m_topK = 1;
143146

144147
void CheckNextArgument(const std::vector<std::wstring>& args, UINT i);
145148
void CheckForInvalidArguments();

Tools/WinMLRunner/src/OutputHelper.h

Lines changed: 29 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
#include <dxgi.h>
1111
#include <Windows.Graphics.DirectX.Direct3D11.interop.h>
1212
#include <direct.h>
13+
#include <queue>
1314

1415
using namespace winrt::Windows::AI::MachineLearning;
1516
using namespace winrt::Windows::Storage::Streams;
@@ -699,20 +700,17 @@ class OutputHelper
699700
}
700701

701702
template <typename T>
702-
void ProcessTensorResult(const CommandLineArgs& args, const void* buffer, const uint32_t uCapacity, float& maxValue,
703-
int& maxIndex, std::ofstream& fout)
703+
void ProcessTensorResult(const CommandLineArgs& args, const void* buffer, const uint32_t uCapacity,
704+
std::vector<std::pair<float,int>>& maxValues, std::ofstream& fout,
705+
unsigned int k)
704706
{
707+
// Create a priority queue of size k that pops the lowest value first
708+
// We will remove lowest values as we iterate over all the values
709+
auto cmp = [](std::pair<float, int> x, std::pair<float, int> y) { return x.first > y.first; };
710+
std::priority_queue<std::pair<float, int>, std::vector<std::pair<float, int>>, decltype(cmp)> topKvalues(cmp);
711+
705712
T* tensor = (T*)buffer;
706713
int size = uCapacity / sizeof(T);
707-
if (!std::is_same<T, HALF>::value)
708-
{
709-
maxValue = *tensor;
710-
}
711-
else
712-
{
713-
maxValue = XMConvertHalfToFloat(static_cast<HALF>(*tensor));
714-
}
715-
maxIndex = 0;
716714
for (int i = 0; i < size; i++)
717715
{
718716
float val = 0;
@@ -728,12 +726,29 @@ class OutputHelper
728726
{
729727
fout << i << "," << val << std::endl;
730728
}
731-
if (maxValue < val)
729+
730+
if (topKvalues.size() < k)
732731
{
733-
maxValue = val;
734-
maxIndex = i;
732+
topKvalues.push({ val, i });
735733
}
734+
else if (k > 0)
735+
{
736+
auto maxValue = topKvalues.top().first;
737+
if (maxValue < val)
738+
{
739+
topKvalues.pop();
740+
topKvalues.push({ val, i });
741+
}
742+
}
743+
}
744+
while (!topKvalues.empty())
745+
{
746+
auto pair = topKvalues.top();
747+
maxValues.push_back(pair);
748+
topKvalues.pop();
736749
}
750+
// Put vector in order of highest value to lowest
751+
std::reverse(maxValues.begin(), maxValues.end());
737752
}
738753

739754
void WritePerformanceDataToCSV(const Profiler<WINML_MODEL_TEST_PERF>& profiler, int numIterations,

0 commit comments

Comments
 (0)