Skip to content

Commit f2718ba

Browse files
hip: scalarize fixed-vector dot reductions
1 parent fb70e33 commit f2718ba

3 files changed

Lines changed: 162 additions & 3 deletions

File tree

src/backends/hip/hip_device.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ static constexpr char hip_shader_cache_magic[] = "LCHIPCCH";
5656
static constexpr auto hip_shader_cache_artifact_version = 2u;
5757
// Increment whenever the HIP AST/XIR/LLVM lowering contract changes in a way
5858
// that can alter generated code without changing the kernel AST hash.
59-
static constexpr auto hip_shader_cache_codegen_revision = 3u;
59+
static constexpr auto hip_shader_cache_codegen_revision = 4u;
6060
static constexpr auto hip_shader_cache_max_artifact_size = 1ull << 30u;
6161
static constexpr auto hip_shader_cache_payload_hash_seed =
6262
0x4849504341434845ull;

src/backends/hip/llvm_codegen/hip_codegen_llvm_impl_arith.cpp

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -98,8 +98,19 @@ llvm::Value *HIPCodegenLLVMImpl::_translate_arithmetic_inst(IB &b, FunctionConte
9898
};
9999
auto dot_product_fp = [&](llvm::Value *u, llvm::Value *v) noexcept {
100100
LUISA_DEBUG_ASSERT(u->getType()->isVectorTy() && v->getType()->isFPOrFPVectorTy() && u->getType() == v->getType());
101-
auto zero = llvm::ConstantFP::getNegativeZero(u->getType()->getScalarType());
102-
return b.CreateFAddReduce(zero, b.CreateFMul(u, v));
101+
// Keep fixed-width vector reduction explicit. The AMDGPU backend can
102+
// miscompile llvm.vector.reduce.fadd for a dynamically normalized
103+
// float3 in complex divergent kernels, even though the intrinsic is
104+
// correct in small isolated kernels. Component-wise LLVM arithmetic
105+
// has the same ordered fast-math semantics without that target path.
106+
auto vector_type = llvm::cast<llvm::FixedVectorType>(u->getType());
107+
auto product = b.CreateFMul(u, v);
108+
auto result = b.CreateExtractElement(product, uint64_t{0u});
109+
for (auto i = 1u; i < vector_type->getNumElements(); i++) {
110+
result = b.CreateFAdd(
111+
result, b.CreateExtractElement(product, i));
112+
}
113+
return result;
103114
};
104115
auto inf_nan_mask_and_test = [&](llvm::Type *t) noexcept -> std::pair<llvm::Constant *, llvm::Constant *> {
105116
LUISA_DEBUG_ASSERT(t->isFPOrFPVectorTy());

src/tests/unit/runtime/test_hip_callable_boundary.cpp

Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
11
#include "ut/ut.hpp"
22

3+
#include <array>
4+
#include <chrono>
5+
#include <cmath>
36
#include <cstddef>
47
#include <cstdint>
8+
#include <cstdlib>
9+
#include <filesystem>
10+
#include <fstream>
11+
#include <iterator>
12+
#include <string>
513
#include <utility>
614

715
#include <luisa/core/binary_io.h>
@@ -92,6 +100,51 @@ struct CompileResult {
92100
luisa::vector<uint32_t> values;
93101
};
94102

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+
95148
[[nodiscard]] CompileResult compile_reused_callable(
96149
Device &device, CapturingBinaryIO &binary_io,
97150
uint32_t reuse_count, uint32_t seed) noexcept {
@@ -146,6 +199,28 @@ int main(int argc, char *argv[]) {
146199
DeviceConfig config{.binary_io = &binary_io};
147200
auto device = context.create_device("hip", &config);
148201

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+
149224
"HIP preserves shared DSL callable boundaries after optimization"_test =
150225
[&] {
151226
constexpr auto seed = 0x12345678u;
@@ -181,4 +256,77 @@ int main(int argc, char *argv[]) {
181256
<< "shared Luisa Callable was repeatedly expanded into the "
182257
"HIP kernel";
183258
};
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);
184332
}

0 commit comments

Comments
 (0)