|
6 | 6 | * LICENSE file in the root directory of this source tree. |
7 | 7 | */ |
8 | 8 |
|
| 9 | +// Device-free unit test for the pure 2D workgroup-count fold that lifts the |
| 10 | +// 65535 per-dim dispatch cap. Exercises the fold arithmetic only — no GPU. |
| 11 | + |
9 | 12 | #include <executorch/backends/webgpu/runtime/WebGPUUtils.h> |
10 | 13 |
|
| 14 | +#include <gtest/gtest.h> |
| 15 | + |
| 16 | +#include <cmath> |
11 | 17 | #include <cstdint> |
12 | | -#include <cstdio> |
13 | | -#include <stdexcept> |
14 | 18 |
|
15 | 19 | using executorch::backends::webgpu::utils::fold_workgroup_count_2d; |
16 | 20 | using executorch::backends::webgpu::utils::WgCount; |
17 | 21 |
|
18 | 22 | namespace { |
19 | 23 |
|
20 | | -// Device-free unit test for the pure 2D workgroup-count fold that lifts the |
21 | | -// 65535 per-dim dispatch cap. Exercises the fold arithmetic only — no GPU. |
22 | | -int g_failures = 0; |
| 24 | +constexpr uint32_t kMax = 65535u; |
23 | 25 |
|
24 | | -void expect_fold( |
25 | | - uint32_t count, |
26 | | - uint32_t max_count, |
27 | | - uint32_t want_x, |
28 | | - uint32_t want_y) { |
29 | | - WgCount got = fold_workgroup_count_2d(count, max_count, "test"); |
30 | | - bool ok = got.x == want_x && got.y == want_y && |
31 | | - static_cast<uint64_t>(got.x) * got.y >= count; |
32 | | - printf( |
33 | | - "%s fold(%u, max=%u) = {%u, %u} (want {%u, %u})\n", |
34 | | - ok ? "PASS:" : "FAIL:", |
35 | | - count, |
36 | | - max_count, |
37 | | - got.x, |
38 | | - got.y, |
39 | | - want_x, |
40 | | - want_y); |
41 | | - if (!ok) { |
42 | | - g_failures++; |
| 26 | +// count <= max -> {count, 1}: the 1D fast path, byte-identical to the old path. |
| 27 | +TEST(DispatchFold, FastPath1D) { |
| 28 | + for (uint32_t count : {1u, kMax - 1u, kMax}) { |
| 29 | + const WgCount got = fold_workgroup_count_2d(count, kMax, "test"); |
| 30 | + EXPECT_EQ(got.x, count); |
| 31 | + EXPECT_EQ(got.y, 1u); |
43 | 32 | } |
44 | 33 | } |
45 | 34 |
|
46 | | -void expect_throw(uint32_t count, uint32_t max_count) { |
47 | | - bool threw = false; |
48 | | - try { |
49 | | - fold_workgroup_count_2d(count, max_count, "test"); |
50 | | - } catch (const std::exception&) { |
51 | | - threw = true; |
52 | | - } |
53 | | - printf( |
54 | | - "%s fold(%u, max=%u) throws (needs a 3rd dispatch dimension)\n", |
55 | | - threw ? "PASS:" : "FAIL:", |
56 | | - count, |
57 | | - max_count); |
58 | | - if (!threw) { |
59 | | - g_failures++; |
| 35 | +// count > max -> near-square {x, y}: fits the per-dim cap, covers every |
| 36 | +// workgroup, and stays near-square so few invocations are inactive (launched - |
| 37 | +// count is O(sqrt(count)); a flat {max, div_up} split would idle up to ~half). |
| 38 | +TEST(DispatchFold, NearSquareFold) { |
| 39 | + // Includes prefill-scale QK counts (Hq*ceil(S/4)*ceil(ctx/4)/wg) that fold: |
| 40 | + // 131072 = S=2048 (32*512*512/64); 2097152 = large-S stress. |
| 41 | + for (uint32_t count : |
| 42 | + {kMax + 1u, 2u * kMax, 2u * kMax + 1u, 131072u, 2097152u}) { |
| 43 | + const WgCount got = fold_workgroup_count_2d(count, kMax, "test"); |
| 44 | + const uint64_t launched = static_cast<uint64_t>(got.x) * got.y; |
| 45 | + const uint32_t root = |
| 46 | + static_cast<uint32_t>(std::ceil(std::sqrt(static_cast<double>(count)))); |
| 47 | + EXPECT_LE(got.x, kMax) << "count=" << count; |
| 48 | + EXPECT_LE(got.y, kMax) << "count=" << count; |
| 49 | + EXPECT_GE(launched, count) << "count=" << count; |
| 50 | + EXPECT_LT(launched - count, 2ull * root) |
| 51 | + << "count=" << count << " launched=" << launched; |
60 | 52 | } |
61 | 53 | } |
62 | 54 |
|
63 | | -} // namespace |
64 | | - |
65 | | -int main() { |
66 | | - const uint32_t kMax = 65535u; |
67 | | - // 1D fast path: count <= max -> {count, 1}, byte-identical to the old path. |
68 | | - expect_fold(1u, kMax, 1u, 1u); |
69 | | - expect_fold(kMax - 1u, kMax, kMax - 1u, 1u); |
70 | | - expect_fold(kMax, kMax, kMax, 1u); |
71 | | - // Fold to 2D: count > max -> {max, div_up(count, max)}. |
72 | | - expect_fold(kMax + 1u, kMax, kMax, 2u); |
73 | | - expect_fold(2u * kMax, kMax, kMax, 2u); |
74 | | - expect_fold(2u * kMax + 1u, kMax, kMax, 3u); |
75 | | - // Prefill-scale QK counts (tiled grid = Hq*ceil(S/4)*ceil(ctx/4)/wg) that |
76 | | - // exceed kMax and must fold. |
77 | | - expect_fold(131072u, kMax, kMax, 3u); // S=2048: 32*512*512/64 |
78 | | - expect_fold(2097152u, kMax, kMax, 33u); // deep fold (large-S stress) |
79 | | - // count > max^2 needs a 3rd dispatch dimension -> throws (out of scope). |
80 | | - expect_throw(kMax * kMax + 1u, kMax); |
81 | | - |
82 | | - if (g_failures != 0) { |
83 | | - printf("\nFAIL: %d dispatch_2d fold case(s) failed\n", g_failures); |
84 | | - return 1; |
85 | | - } |
86 | | - printf("\nAll dispatch_2d fold tests passed\n"); |
87 | | - return 0; |
| 55 | +// count > max^2 needs a 3rd dispatch dimension -> throws (out of scope). |
| 56 | +TEST(DispatchFold, ThrowsWhenNeeds3rdDimension) { |
| 57 | + EXPECT_ANY_THROW(fold_workgroup_count_2d(kMax * kMax + 1u, kMax, "test")); |
88 | 58 | } |
| 59 | + |
| 60 | +} // namespace |
0 commit comments