10#include <spdlog/spdlog.h>
30 case Halide::Target::CUDA:
return "CUDA";
31 case Halide::Target::OpenCL:
return "OpenCL";
32 case Halide::Target::Vulkan:
return "Vulkan";
33 case Halide::Target::Metal:
return "Metal";
34 case Halide::Target::D3D12Compute:
return "DirectX12";
35 default:
return "Unknown";
48 case Halide::Target::Metal:
52 spdlog::debug(
"[BackendDecider] Metal skipped - not available on this platform");
56 case Halide::Target::D3D12Compute:
60 spdlog::debug(
"[BackendDecider] DirectX12 skipped - not available on this platform");
64 case Halide::Target::CUDA:
65 case Halide::Target::OpenCL:
66 case Halide::Target::Vulkan:
85 Halide::Buffer<float> buffer(width, height, channels);
87 for (
size_t c = 0; c < channels; ++c)
89 for (
size_t y = 0; y < height; ++y)
91 for (
size_t x = 0; x < width; ++x)
93 buffer(x, y, c) =
static_cast<float>((x + y + c) % 256) / 255.0f;
110 Halide::Var& x, Halide::Var& y, Halide::Var& c)
112 Halide::Func pipeline(
"benchmark_pipeline");
113 pipeline(x, y, c) = input(x, y, c) * 1.1f + 0.05f;
127 pipeline.compile_jit(target);
138 spdlog::info(
"[BenchmarkingBackendDecider::decide]: Starting backend performance benchmark...");
141 Halide::Target host_target { Halide::get_host_target() };
142 spdlog::info(
"[BenchmarkingBackendDecider::decide]: Host target: {}", host_target.to_string());
145 auto cpu_time { benchmark_cpu() };
147 if (cpu_time == std::chrono::milliseconds::max())
149 spdlog::warn(
"[BenchmarkingBackendDecider::decide]: CPU benchmark failed completely. Defaulting to CPU.");
150 m_winning_target = host_target;
153 spdlog::info(
"[BenchmarkingBackendDecider::decide]: CPU Baseline: {} ms", cpu_time.count());
157 std::array<std::pair<Halide::Target::Feature, std::string>, 5> gpu_priorities =
159 {Halide::Target::CUDA,
"CUDA"},
160 {Halide::Target::D3D12Compute,
"DirectX12"},
161 {Halide::Target::Metal,
"Metal"},
162 {Halide::Target::Vulkan,
"Vulkan"},
163 {Halide::Target::OpenCL,
"OpenCL"}
166 std::optional<std::chrono::milliseconds> best_gpu_time;
167 std::string best_gpu_name {
"None" };
168 Halide::Target::Feature best_gpu_feature { Halide::Target::OpenCL };
170 for (
const auto& [feature, name] : gpu_priorities)
178 spdlog::info(
"[BenchmarkingBackendDecider::decide]: Testing {} backend...", name);
184 k_benchmark_width, k_benchmark_height, k_benchmark_channels) };
186 auto result = benchmark_gpu_feature(feature, fresh_buffer);
188 if (result.has_value())
190 if (!best_gpu_time.has_value() || result < best_gpu_time)
192 best_gpu_time = result;
193 best_gpu_name = name;
194 best_gpu_feature = feature;
195 spdlog::info(
"[BenchmarkingBackendDecider::decide]: {} benchmarked in {} ms (Current Best)",
196 name, result.value().count());
200 spdlog::debug(
"[BenchmarkingBackendDecider::decide]: {} benchmarked in {} ms (Slower than {})",
201 name, result.value().count(), best_gpu_name);
207 if (!best_gpu_time.has_value())
209 spdlog::info(
"[BenchmarkingBackendDecider::decide]: No GPU benchmark succeeded. Using CPU backend.");
210 m_winning_target = host_target;
214 spdlog::info(
"[BenchmarkingBackendDecider::decide]: Best GPU: {} at {} ms", best_gpu_name, best_gpu_time.value().count());
216 long long threshold_ms =
static_cast<long long>(cpu_time.count() * k_gpu_advantage_threshold);
218 if (best_gpu_time.value().count() < threshold_ms)
220 spdlog::info(
"[BenchmarkingBackendDecider::decide]: GPU ({} ms) is significantly faster than CPU ({} ms). SELECTING GPU.",
221 best_gpu_time.value().count(), cpu_time.count());
223 m_winning_target = host_target;
224 m_winning_target.set_feature(best_gpu_feature);
230 spdlog::info(
"[BenchmarkingBackendDecider::decide]: CPU ({} ms) is comparable or faster than GPU ({} ms). SELECTING CPU.",
231 cpu_time.count(), best_gpu_time.value().count());
232 m_winning_target = host_target;
237std::chrono::milliseconds BenchmarkingBackendDecider::benchmark_cpu()
const
243 k_benchmark_width, k_benchmark_height, k_benchmark_channels) };
247 auto start = std::chrono::high_resolution_clock::now();
248 pipeline.realize(buffer);
249 auto end = std::chrono::high_resolution_clock::now();
251 return std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
253 catch (
const std::exception& e)
255 spdlog::error(
"[BenchmarkingBackendDecider::decide]: CPU Benchmark Exception: {}", e.what());
256 return std::chrono::milliseconds::max();
260std::optional<std::chrono::milliseconds>
261BenchmarkingBackendDecider::benchmark_gpu_feature(Halide::Target::Feature feature,
262 const Halide::Buffer<float>& ref_buffer)
const
267 Halide::Target target { Halide::get_host_target() };
268 target.set_feature(feature);
272 Halide::Buffer<float> work_buffer { ref_buffer };
273 Halide::Var x, y, c, xo, yo, xi, yi;
275 pipeline.gpu_tile(x, y, xo, yo, xi, yi, 16, 16);
280 spdlog::debug(
"[BenchmarkingBackendDecider::benchmark_gpu_feature]: {} - JIT compilation failed (not supported by Halide build)",
285 spdlog::debug(
"[BenchmarkingBackendDecider::benchmark_gpu_feature]: {} - JIT compilation successful",
feature_to_string(feature));
289 work_buffer.set_host_dirty();
290 int copy_res = work_buffer.copy_to_device(target);
293 spdlog::debug(
"[BenchmarkingBackendDecider::benchmark_gpu_feature]: {} - copy_to_device failed (err: {}), no GPU device available",
299 auto start = std::chrono::high_resolution_clock::now();
301 Halide::Buffer<float> result;
304 result = pipeline.realize(
305 {k_benchmark_width, k_benchmark_height, k_benchmark_channels}, target);
307 catch (
const Halide::Error& e)
309 spdlog::debug(
"[BenchmarkingBackendDecider::benchmark_gpu_feature]: {} - realize failed: {}",
feature_to_string(feature), e.what());
313 auto end = std::chrono::high_resolution_clock::now();
315 auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);
316 spdlog::info(
"[BenchmarkingBackendDecider::benchmark_gpu_feature]: {} benchmark success: {} ms",
320 catch (
const Halide::Error& e)
322 spdlog::debug(
"[BenchmarkingBackendDecider::benchmark_gpu_feature]: {} Halide error: {}",
feature_to_string(feature), e.what());
325 catch (
const std::exception& e)
327 spdlog::debug(
"[BenchmarkingBackendDecider::benchmark_gpu_feature]: {} exception: {}",
feature_to_string(feature), e.what());
332 spdlog::debug(
"[BenchmarkingBackendDecider::benchmark_gpu_feature]: {} - unknown exception prevented",
feature_to_string(feature));
Concrete implementation of IBackendDecider that chooses backend based on performance benchmarks.
Common::MemoryType decide() override
Decides the optimal memory type (CPU or GPU) by running benchmarks.
MemoryType
Enum representing the memory type where image data is stored.
@ GPU_MEMORY
Data is stored in GPU memory.
@ CPU_RAM
Data is stored in main CPU RAM.
static Halide::Func create_benchmark_pipeline(const Halide::Buffer< float > &input, Halide::Var &x, Halide::Var &y, Halide::Var &c)
Creates a simple brightness adjustment pipeline for benchmarking.
static bool should_test_feature(Halide::Target::Feature feature)
Checks if a GPU feature should be tested on the current platform.
static Halide::Buffer< float > create_fresh_test_buffer(int width, int height, int channels)
Creates a fresh test buffer filled with gradient data.
static std::string feature_to_string(Halide::Target::Feature feature)
Converts a Halide Target::Feature to a human-readable string.
static bool try_compile_jit(Halide::Func &pipeline, const Halide::Target &target)
Attempts to compile a pipeline for a GPU target.