|
1 | 1 | #include "ut/ut.hpp" |
2 | 2 |
|
| 3 | +#include <array> |
| 4 | +#include <chrono> |
| 5 | +#include <cmath> |
3 | 6 | #include <cstddef> |
4 | 7 | #include <cstdint> |
| 8 | +#include <cstdlib> |
| 9 | +#include <filesystem> |
| 10 | +#include <fstream> |
| 11 | +#include <iterator> |
| 12 | +#include <string> |
5 | 13 | #include <utility> |
6 | 14 |
|
7 | 15 | #include <luisa/core/binary_io.h> |
@@ -92,6 +100,51 @@ struct CompileResult { |
92 | 100 | luisa::vector<uint32_t> values; |
93 | 101 | }; |
94 | 102 |
|
| 103 | +[[nodiscard]] luisa::vector<float> |
| 104 | +evaluate_normalized_vectors_through_callable( |
| 105 | + Device &device, |
| 106 | + luisa::span<const float3> inputs, |
| 107 | + uint32_t seed) noexcept { |
| 108 | + Callable project = [](Float3 direction, UInt value) noexcept { |
| 109 | + for (auto round = 0u; |
| 110 | + round < callable_round_count; round++) { |
| 111 | + auto right_shift = round % 15u + 1u; |
| 112 | + auto left_shift = round % 13u + 1u; |
| 113 | + auto multiplier = |
| 114 | + 0x9e3779b1u + round * 0x85ebca6bu; |
| 115 | + auto additive = |
| 116 | + 0xc2b2ae35u + round * 0x27d4eb2du; |
| 117 | + value = (value ^ (value >> right_shift)) * multiplier; |
| 118 | + value ^= value << left_shift; |
| 119 | + value += additive; |
| 120 | + } |
| 121 | + return direction.x + 2.0f * direction.y + |
| 122 | + 4.0f * direction.z + |
| 123 | + cast<float>(value & 255u); |
| 124 | + }; |
| 125 | + Kernel1D kernel = [&project](BufferFloat output, |
| 126 | + BufferFloat3 input, |
| 127 | + UInt initial_value) noexcept { |
| 128 | + const auto index = dispatch_x(); |
| 129 | + const auto direction = normalize(input.read(index)); |
| 130 | + output.write( |
| 131 | + index, |
| 132 | + project(direction, initial_value + index)); |
| 133 | + }; |
| 134 | + |
| 135 | + auto shader = device.compile( |
| 136 | + kernel, ShaderOption{.enable_cache = false}); |
| 137 | + auto input = device.create_buffer<float3>(inputs.size()); |
| 138 | + auto output = device.create_buffer<float>(inputs.size()); |
| 139 | + luisa::vector<float> values(inputs.size()); |
| 140 | + auto stream = device.create_stream(); |
| 141 | + stream << input.copy_from(inputs) |
| 142 | + << shader(output, input, seed).dispatch(inputs.size()) |
| 143 | + << output.copy_to(values.data()) |
| 144 | + << synchronize(); |
| 145 | + return values; |
| 146 | +} |
| 147 | + |
95 | 148 | [[nodiscard]] CompileResult compile_reused_callable( |
96 | 149 | Device &device, CapturingBinaryIO &binary_io, |
97 | 150 | uint32_t reuse_count, uint32_t seed) noexcept { |
@@ -146,6 +199,28 @@ int main(int argc, char *argv[]) { |
146 | 199 | DeviceConfig config{.binary_io = &binary_io}; |
147 | 200 | auto device = context.create_device("hip", &config); |
148 | 201 |
|
| 202 | + std::error_code filesystem_error; |
| 203 | + const auto original_directory = |
| 204 | + std::filesystem::current_path(filesystem_error); |
| 205 | + const auto dump_directory = |
| 206 | + std::filesystem::temp_directory_path(filesystem_error) / |
| 207 | + ("luisa_hip_callable_boundary_" + |
| 208 | + std::to_string( |
| 209 | + std::chrono::steady_clock::now() |
| 210 | + .time_since_epoch() |
| 211 | + .count())); |
| 212 | + std::filesystem::create_directories( |
| 213 | + dump_directory, filesystem_error); |
| 214 | + std::filesystem::current_path( |
| 215 | + dump_directory, filesystem_error); |
| 216 | + expect(!filesystem_error) |
| 217 | + << "failed to prepare isolated HIP LLVM dump directory"; |
| 218 | +#if defined(_WIN32) |
| 219 | + _putenv_s("LUISA_DUMP_LLVM_IR", "1"); |
| 220 | +#else |
| 221 | + setenv("LUISA_DUMP_LLVM_IR", "1", 1); |
| 222 | +#endif |
| 223 | + |
149 | 224 | "HIP preserves shared DSL callable boundaries after optimization"_test = |
150 | 225 | [&] { |
151 | 226 | constexpr auto seed = 0x12345678u; |
@@ -181,4 +256,77 @@ int main(int argc, char *argv[]) { |
181 | 256 | << "shared Luisa Callable was repeatedly expanded into the " |
182 | 257 | "HIP kernel"; |
183 | 258 | }; |
| 259 | + |
| 260 | + "HIP preserves fixed-vector reductions before callable boundaries"_test = |
| 261 | + [&] { |
| 262 | + constexpr auto seed = 0x89abcdefu; |
| 263 | + constexpr std::array inputs{ |
| 264 | + float3{0.0f, 0.0f, 0.75f}, |
| 265 | + float3{0.25f, -0.5f, 2.0f}, |
| 266 | + float3{-1.0f, 3.0f, 0.125f}, |
| 267 | + float3{4.0f, -2.0f, 1.0f}, |
| 268 | + float3{0.5f, 0.25f, -0.75f}, |
| 269 | + float3{-2.0f, -1.0f, 3.0f}, |
| 270 | + float3{7.0f, 0.125f, -0.25f}, |
| 271 | + float3{0.0f, 0.0f, 1.5f}, |
| 272 | + float3{1.0f, 1.0f, 1.0f}, |
| 273 | + float3{-1.0f, 1.0f, -1.0f}, |
| 274 | + float3{0.125f, 8.0f, 0.5f}, |
| 275 | + float3{3.0f, 2.0f, -4.0f}, |
| 276 | + float3{-0.5f, 0.75f, 0.25f}, |
| 277 | + float3{9.0f, -3.0f, 2.0f}, |
| 278 | + float3{0.0625f, 0.125f, 0.25f}, |
| 279 | + float3{-6.0f, 5.0f, 4.0f}}; |
| 280 | + auto actual = evaluate_normalized_vectors_through_callable( |
| 281 | + device, inputs, seed); |
| 282 | + for (auto i = 0u; i < inputs.size(); i++) { |
| 283 | + const auto &v = inputs[i]; |
| 284 | + const auto squared_length = |
| 285 | + v.x * v.x + v.y * v.y + v.z * v.z; |
| 286 | + const auto inverse_length = |
| 287 | + 1.0f / std::sqrt(squared_length); |
| 288 | + const auto direction = v * inverse_length; |
| 289 | + const auto expected = |
| 290 | + direction.x + 2.0f * direction.y + |
| 291 | + 4.0f * direction.z + |
| 292 | + static_cast<float>( |
| 293 | + scramble_reference(seed + i) & 255u); |
| 294 | + expect(std::abs(actual[i] - expected) <= 2.0e-5f) |
| 295 | + << "fixed-vector reduction changed across an " |
| 296 | + "out-of-line HIP Callable"; |
| 297 | + } |
| 298 | + }; |
| 299 | + |
| 300 | + "HIP lowers fixed-vector dot products without LLVM reductions"_test = |
| 301 | + [&] { |
| 302 | + auto dumped_module_count = 0u; |
| 303 | + auto retained_vector_reduction = false; |
| 304 | + for (const auto &entry : |
| 305 | + std::filesystem::directory_iterator(dump_directory)) { |
| 306 | + const auto filename = |
| 307 | + entry.path().filename().string(); |
| 308 | + if (!filename.starts_with("hip_kernel_final_") || |
| 309 | + entry.path().extension() != ".ll") { |
| 310 | + continue; |
| 311 | + } |
| 312 | + ++dumped_module_count; |
| 313 | + std::ifstream stream{entry.path()}; |
| 314 | + const std::string module{ |
| 315 | + std::istreambuf_iterator<char>{stream}, |
| 316 | + std::istreambuf_iterator<char>{}}; |
| 317 | + retained_vector_reduction |= |
| 318 | + module.find("llvm.vector.reduce.fadd") != |
| 319 | + std::string::npos; |
| 320 | + } |
| 321 | + expect(dumped_module_count == 3u) |
| 322 | + << "expected one final HIP LLVM module per compiled shader"; |
| 323 | + expect(!retained_vector_reduction) |
| 324 | + << "fixed-vector dot product retained the target-unstable " |
| 325 | + "LLVM reduction intrinsic"; |
| 326 | + }; |
| 327 | + |
| 328 | + std::filesystem::current_path( |
| 329 | + original_directory, filesystem_error); |
| 330 | + std::filesystem::remove_all( |
| 331 | + dump_directory, filesystem_error); |
184 | 332 | } |
0 commit comments