2323
2424using namespace fusilli ;
2525
26+ // Builds the input tensor data for a given reduction mode. The reduction
27+ // in this sample is checked at output index (d0=0, d1=0), which corresponds
28+ // to xData indices [0, 64). Each branch picks values that produce a
29+ // non-trivial, deterministic expected value at that output index while
30+ // staying in range for fp16/int32.
31+ template <typename T>
32+ static std::vector<T> generateReductionInputData (ReductionAttr::Mode mode,
33+ int64_t xSize) {
34+ std::vector<T> xData (xSize);
35+ switch (mode) {
36+ case ReductionAttr::Mode::MUL : {
37+ // Mostly 1s with a 2 and a 3 inside the (d0=0, d1=0) block so the
38+ // product over that block is 6 — non-trivial enough to verify the
39+ // multiplication actually happens (rather than degenerating to 1).
40+ std::fill (xData.begin (), xData.end (), T (1 ));
41+ xData[10 ] = T (2 );
42+ xData[50 ] = T (3 );
43+ break ;
44+ }
45+ case ReductionAttr::Mode::MUL_NO_ZEROS : {
46+ // Same shape as MUL but with zeros sprinkled in. The expected
47+ // product over the (d0=0, d1=0) block is still 6 because zeros
48+ // are excluded from the reduction.
49+ std::fill (xData.begin (), xData.end (), T (1 ));
50+ xData[10 ] = T (2 );
51+ xData[50 ] = T (3 );
52+ xData[5 ] = T (0 );
53+ xData[20 ] = T (0 );
54+ break ;
55+ }
56+ default : {
57+ // Values from -50 to 49.
58+ for (int64_t i = 0 ; i < xSize; ++i)
59+ xData[i] = static_cast <T>(i % 100 - 50 );
60+ break ;
61+ }
62+ }
63+ return xData;
64+ }
65+
2666// Based on parameters, generates a unique name for the graph
2767static std::string generateName (ReductionAttr::Mode mode, DataType type,
2868 const std::vector<int64_t > &xDim,
@@ -46,9 +86,16 @@ TEST_CASE("Reduction ops", "[reduction][graph]") {
4686
4787 // clang-format off
4888 const auto mode = GENERATE (
89+ ReductionAttr::Mode::ADD ,
4990 ReductionAttr::Mode::SUM ,
5091 ReductionAttr::Mode::MIN ,
51- ReductionAttr::Mode::MAX );
92+ ReductionAttr::Mode::MAX ,
93+ ReductionAttr::Mode::AMAX ,
94+ ReductionAttr::Mode::AVG ,
95+ ReductionAttr::Mode::NORM1 ,
96+ ReductionAttr::Mode::NORM2 ,
97+ ReductionAttr::Mode::MUL ,
98+ ReductionAttr::Mode::MUL_NO_ZEROS );
5299 // clang-format on
53100
54101 auto execute = [&]<typename T>(Handle &handle, DataType dt, T initValue) {
@@ -80,11 +127,7 @@ TEST_CASE("Reduction ops", "[reduction][graph]") {
80127 for (auto d : xDims)
81128 xSize *= d;
82129
83- std::vector<T> xData (xSize);
84- for (int64_t i = 0 ; i < xSize; ++i) {
85- // Values from -50 to 49
86- xData[i] = static_cast <T>(i % 100 - 50 );
87- }
130+ std::vector<T> xData = generateReductionInputData<T>(mode, xSize);
88131
89132 FUSILLI_REQUIRE_ASSIGN (auto xBuf, allocateBufferOfType (handle, xT, xData));
90133 FUSILLI_REQUIRE_ASSIGN (auto yBuf,
@@ -118,12 +161,14 @@ TEST_CASE("Reduction ops", "[reduction][graph]") {
118161
119162 // Initialize to same value as output buffer
120163 T expectedValue = initValue;
164+ double sumSq = 0.0 ;
121165
122166 // Perform reduction
123167 for (int64_t d2 = 0 ; d2 < 8 ; ++d2) {
124168 for (int64_t d3 = 0 ; d3 < 8 ; ++d3) {
125169 int64_t inIdx = ((d0 * 16 + d1) * 8 + d2) * 8 + d3;
126170 switch (mode) {
171+ case ReductionAttr::Mode::ADD :
127172 case ReductionAttr::Mode::SUM :
128173 expectedValue = expectedValue + xData[inIdx];
129174 break ;
@@ -133,11 +178,43 @@ TEST_CASE("Reduction ops", "[reduction][graph]") {
133178 case ReductionAttr::Mode::MAX :
134179 expectedValue = std::max (expectedValue, xData[inIdx]);
135180 break ;
181+ case ReductionAttr::Mode::NORM1 :
182+ expectedValue =
183+ static_cast <T>(static_cast <double >(expectedValue) +
184+ std::abs (static_cast <double >(xData[inIdx])));
185+ break ;
186+ case ReductionAttr::Mode::AMAX :
187+ expectedValue = std::max (
188+ expectedValue,
189+ static_cast <T>(std::abs (static_cast <double >(xData[inIdx]))));
190+ break ;
191+ case ReductionAttr::Mode::AVG :
192+ expectedValue = expectedValue + xData[inIdx];
193+ break ;
194+ case ReductionAttr::Mode::NORM2 : {
195+ double v = static_cast <double >(xData[inIdx]);
196+ sumSq += v * v;
197+ break ;
198+ }
199+ case ReductionAttr::Mode::MUL :
200+ expectedValue = expectedValue * xData[inIdx];
201+ break ;
202+ case ReductionAttr::Mode::MUL_NO_ZEROS :
203+ if (xData[inIdx] != T (0 ))
204+ expectedValue = expectedValue * xData[inIdx];
205+ break ;
136206 default :
137207 break ;
138208 }
139209 }
140210 }
211+ if (mode == ReductionAttr::Mode::NORM2 )
212+ expectedValue = static_cast <T>(std::sqrt (sumSq));
213+
214+ // Finalize AVG by dividing the accumulated sum by the number of
215+ // reduced elements (8 * 8 = 64). Integer types use integer division.
216+ if (mode == ReductionAttr::Mode::AVG )
217+ expectedValue = expectedValue / T (64 );
141218
142219 // Read output buffer
143220 std::vector<T> result;
@@ -146,10 +223,19 @@ TEST_CASE("Reduction ops", "[reduction][graph]") {
146223 // Validate output size and check the first value (d0=0, d1=0)
147224 REQUIRE (result.size () == ySize);
148225 int64_t checkIdx = d0 * 16 + d1;
149- if constexpr (std::is_floating_point_v<T>)
226+ if constexpr (std::is_floating_point_v<T>) {
150227 REQUIRE (std::abs (result[checkIdx] - expectedValue) < T (0.01 ));
151- else
152- REQUIRE (result[checkIdx] == expectedValue);
228+ } else {
229+ if (mode == ReductionAttr::Mode::NORM2 ) {
230+ // sqrt may round; allow off-by-one for integer types.
231+ T diff = result[checkIdx] > expectedValue
232+ ? result[checkIdx] - expectedValue
233+ : expectedValue - result[checkIdx];
234+ REQUIRE (diff <= T (1 ));
235+ } else {
236+ REQUIRE (result[checkIdx] == expectedValue);
237+ }
238+ }
153239 };
154240
155241 // Create handle for the target backend.
@@ -158,19 +244,28 @@ TEST_CASE("Reduction ops", "[reduction][graph]") {
158244 // Determine initial value based on reduction mode
159245 auto getInitValue = [&]<typename T>() -> T {
160246 switch (mode) {
161- case ReductionAttr::Mode::SUM :
162- return T (0 );
163247 case ReductionAttr::Mode::MIN :
164248 return std::numeric_limits<T>::max ();
165249 case ReductionAttr::Mode::MAX :
166250 return std::numeric_limits<T>::lowest ();
251+ case ReductionAttr::Mode::MUL :
252+ case ReductionAttr::Mode::MUL_NO_ZEROS :
253+ return T (1 );
254+ case ReductionAttr::Mode::ADD :
255+ case ReductionAttr::Mode::SUM :
256+ case ReductionAttr::Mode::AMAX :
257+ case ReductionAttr::Mode::AVG :
258+ case ReductionAttr::Mode::NORM1 :
259+ case ReductionAttr::Mode::NORM2 :
167260 default :
168261 return T (0 );
169262 }
170263 };
171264
172- // int32
173- execute (handle, DataType::Int32, getInitValue.template operator ()<int >());
265+ // torch.aten.mean.dim is not defined on integer tensors, so AVG only
266+ // exercises the floating-point paths.
267+ if (mode != ReductionAttr::Mode::AVG )
268+ execute (handle, DataType::Int32, getInitValue.template operator ()<int >());
174269 // fp16
175270 execute (handle, DataType::Half, getInitValue.template operator ()<half>());
176271 // fp32
0 commit comments