Some checks failed
Linux Build / Linux cpu (push) Has been cancelled
Linux Build / Linux cpu-clang (push) Has been cancelled
Linux Build / Linux vulkan (push) Has been cancelled
mac-build / macOS CPU (push) Has been cancelled
Nix Build / Nix (cpu) (push) Has been cancelled
Nix Build / Nix (vulkan) (push) Has been cancelled
windows-build / Windows CPU (push) Has been cancelled
535 lines
22 KiB
C++
535 lines
22 KiB
C++
#include "engine/framework/core/backend.h"
|
|
#include "engine/framework/debug/trace.h"
|
|
#include "engine/framework/modules/conv_modules.h"
|
|
#include "engine/framework/modules/structural_modules.h"
|
|
|
|
#include <ggml-backend.h>
|
|
#include <ggml.h>
|
|
|
|
#include <algorithm>
|
|
#include <chrono>
|
|
#include <cmath>
|
|
#include <iostream>
|
|
#include <optional>
|
|
#include <sstream>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
namespace {
|
|
|
|
constexpr size_t kTestGraphBytes = 256 * 1024 * 1024;
|
|
constexpr size_t kTestGraphNodes = 4096;
|
|
|
|
struct ConvTransposeCase {
|
|
const char * name;
|
|
int64_t batch;
|
|
int64_t in_channels;
|
|
int64_t out_channels;
|
|
int64_t frames;
|
|
int64_t kernel_size;
|
|
int stride;
|
|
bool use_bias;
|
|
};
|
|
|
|
struct RunResult {
|
|
engine::core::TensorShape shape;
|
|
std::vector<float> values;
|
|
double compute_ms = 0.0;
|
|
};
|
|
|
|
struct DiffStats {
|
|
float max_diff = 0.0f;
|
|
double mean_diff = 0.0;
|
|
};
|
|
|
|
std::vector<float> make_patterned_f32(size_t count, float phase, float scale) {
|
|
std::vector<float> values(count, 0.0f);
|
|
for (size_t i = 0; i < count; ++i) {
|
|
const float x = static_cast<float>(i);
|
|
values[i] = scale * (std::sin(phase + 0.113f * x) + 0.5f * std::cos(phase * 0.7f + 0.071f * x));
|
|
}
|
|
return values;
|
|
}
|
|
|
|
struct BackendModuleRunner {
|
|
engine::core::BackendConfig backend_config{engine::core::BackendType::Cpu, 0, 4};
|
|
ggml_backend_t backend = nullptr;
|
|
ggml_backend_buffer_t buffer = nullptr;
|
|
ggml_context * ggml = nullptr;
|
|
engine::core::ModuleBuildContext ctx{};
|
|
|
|
BackendModuleRunner(const char * name, engine::core::BackendType backend_type) {
|
|
backend_config.type = backend_type;
|
|
backend = engine::core::init_backend(backend_config);
|
|
ggml_init_params params{};
|
|
params.mem_size = kTestGraphBytes;
|
|
params.mem_buffer = nullptr;
|
|
params.no_alloc = true;
|
|
ggml = ggml_init(params);
|
|
if (ggml == nullptr) {
|
|
throw std::runtime_error("failed to init test ggml context");
|
|
}
|
|
ctx.ggml = ggml;
|
|
ctx.module_instance_name = name;
|
|
ctx.backend_type = backend_type;
|
|
}
|
|
|
|
~BackendModuleRunner() {
|
|
if (buffer != nullptr) {
|
|
ggml_backend_buffer_free(buffer);
|
|
}
|
|
if (ggml != nullptr) {
|
|
ggml_free(ggml);
|
|
}
|
|
if (backend != nullptr) {
|
|
ggml_backend_free(backend);
|
|
}
|
|
}
|
|
|
|
engine::core::TensorValue make_f32(const engine::core::TensorShape & shape) {
|
|
return engine::core::make_tensor(ctx, GGML_TYPE_F32, shape);
|
|
}
|
|
|
|
void allocate_tensors() {
|
|
if (buffer != nullptr) {
|
|
return;
|
|
}
|
|
buffer = ggml_backend_alloc_ctx_tensors(ggml, backend);
|
|
if (buffer == nullptr) {
|
|
throw std::runtime_error("failed to allocate test backend tensors");
|
|
}
|
|
}
|
|
|
|
RunResult run_f32(const engine::core::TensorValue & output) {
|
|
allocate_tensors();
|
|
ggml_cgraph * graph = ggml_new_graph_custom(ggml, kTestGraphNodes, false);
|
|
ggml_build_forward_expand(graph, output.tensor);
|
|
const auto start = std::chrono::steady_clock::now();
|
|
const ggml_status status = ggml_backend_graph_compute(backend, graph);
|
|
const auto end = std::chrono::steady_clock::now();
|
|
if (status != GGML_STATUS_SUCCESS) {
|
|
std::ostringstream oss;
|
|
oss << "backend graph compute failed with status " << static_cast<int>(status);
|
|
throw std::runtime_error(oss.str());
|
|
}
|
|
RunResult result{output.shape, {}};
|
|
result.compute_ms = std::chrono::duration<double, std::milli>(end - start).count();
|
|
engine::core::read_tensor_f32_into(output.tensor, result.values);
|
|
return result;
|
|
}
|
|
};
|
|
|
|
bool backend_is_available(engine::core::BackendType backend_type) {
|
|
try {
|
|
BackendModuleRunner runner("conv_transpose_fast_path_test.probe", backend_type);
|
|
return true;
|
|
} catch (...) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
void write_tensor_f32(const engine::core::TensorValue & tensor, const std::vector<float> & values, const char * name) {
|
|
if (static_cast<int64_t>(values.size()) != tensor.shape.num_elements()) {
|
|
std::ostringstream oss;
|
|
oss << name << " value count mismatch: expected " << tensor.shape.num_elements()
|
|
<< ", got " << values.size();
|
|
throw std::runtime_error(oss.str());
|
|
}
|
|
ggml_backend_tensor_set(tensor.tensor, values.data(), 0, values.size() * sizeof(float));
|
|
}
|
|
|
|
engine::modules::ConvTranspose1dConfig make_config(const ConvTransposeCase & test_case) {
|
|
return {
|
|
test_case.in_channels,
|
|
test_case.out_channels,
|
|
test_case.kernel_size,
|
|
test_case.stride,
|
|
0,
|
|
1,
|
|
test_case.use_bias,
|
|
};
|
|
}
|
|
|
|
int64_t conv_transpose1d_output_frames(
|
|
const engine::modules::ConvTranspose1dConfig & config,
|
|
int64_t input_frames) {
|
|
return (input_frames - 1) * config.stride - 2 * config.padding +
|
|
config.dilation * (config.kernel_size - 1) + 1;
|
|
}
|
|
|
|
engine::core::TensorValue view_batch_matrix(
|
|
engine::core::ModuleBuildContext & ctx,
|
|
const engine::core::TensorValue & input,
|
|
int64_t batch_index,
|
|
int64_t channels,
|
|
int64_t frames) {
|
|
auto * view = ggml_view_2d(
|
|
ctx.ggml,
|
|
input.tensor,
|
|
frames,
|
|
channels,
|
|
input.tensor->nb[1],
|
|
static_cast<size_t>(batch_index) * input.tensor->nb[2]);
|
|
return engine::core::wrap_tensor(view, engine::core::TensorShape::from_dims({channels, frames}), input.type);
|
|
}
|
|
|
|
engine::core::TensorValue add_bias_if_needed(
|
|
engine::core::ModuleBuildContext & ctx,
|
|
const engine::core::TensorValue & output,
|
|
int64_t out_channels,
|
|
const std::optional<engine::core::TensorValue> & bias) {
|
|
if (!bias.has_value()) {
|
|
return output;
|
|
}
|
|
auto * bias_view = ggml_reshape_3d(ctx.ggml, bias->tensor, 1, out_channels, 1);
|
|
auto * bias_expanded = ggml_repeat(ctx.ggml, bias_view, output.tensor);
|
|
return engine::core::wrap_tensor(ggml_add(ctx.ggml, output.tensor, bias_expanded), output.shape, GGML_TYPE_F32);
|
|
}
|
|
|
|
engine::core::TensorValue build_conv_transpose1d_slow_test_path(
|
|
engine::core::ModuleBuildContext & ctx,
|
|
const engine::modules::ConvTranspose1dConfig & config,
|
|
const engine::core::TensorValue & input,
|
|
const engine::modules::ConvTranspose1dWeights & weights,
|
|
const engine::core::TensorShape & output_shape) {
|
|
engine::core::TensorValue output;
|
|
for (int64_t batch_index = 0; batch_index < input.shape.dims[0]; ++batch_index) {
|
|
const auto matrix_input = view_batch_matrix(ctx, input, batch_index, config.in_channels, input.shape.dims[2]);
|
|
auto batch_output = engine::core::wrap_tensor(
|
|
ggml_conv_transpose_1d(
|
|
ctx.ggml,
|
|
weights.weight.tensor,
|
|
matrix_input.tensor,
|
|
config.stride,
|
|
config.padding,
|
|
config.dilation),
|
|
engine::core::TensorShape::from_dims({1, config.out_channels, output_shape.dims[2]}),
|
|
GGML_TYPE_F32);
|
|
output = output.valid() ? engine::modules::ConcatModule({0}).build(ctx, output, batch_output) : batch_output;
|
|
}
|
|
return add_bias_if_needed(ctx, output, config.out_channels, weights.bias);
|
|
}
|
|
|
|
engine::core::TensorValue build_conv_transpose1d_col2im_test_path(
|
|
engine::core::ModuleBuildContext & ctx,
|
|
const engine::modules::ConvTranspose1dConfig & config,
|
|
const engine::core::TensorValue & input,
|
|
const engine::modules::ConvTranspose1dWeights & weights,
|
|
const engine::core::TensorShape & output_shape) {
|
|
auto * weight_perm = ggml_reshape_2d(
|
|
ctx.ggml,
|
|
ggml_cont(ctx.ggml, ggml_permute(ctx.ggml, weights.weight.tensor, 1, 2, 0, 3)),
|
|
config.in_channels,
|
|
config.kernel_size * config.out_channels);
|
|
ggml_tensor * bias_matrix = nullptr;
|
|
if (config.use_bias) {
|
|
if (!weights.bias.has_value()) {
|
|
throw std::runtime_error("test col2im path requires bias when use_bias is true");
|
|
}
|
|
bias_matrix = ggml_reshape_2d(ctx.ggml, weights.bias->tensor, 1, config.out_channels);
|
|
}
|
|
engine::core::TensorValue output;
|
|
for (int64_t batch_index = 0; batch_index < input.shape.dims[0]; ++batch_index) {
|
|
auto * batch_input = ggml_view_2d(
|
|
ctx.ggml,
|
|
input.tensor,
|
|
input.tensor->ne[0],
|
|
input.tensor->ne[1],
|
|
input.tensor->nb[1],
|
|
static_cast<size_t>(batch_index) * input.tensor->nb[2]);
|
|
auto * transposed_input = ggml_cont(ctx.ggml, ggml_transpose(ctx.ggml, batch_input));
|
|
auto * columns = ggml_mul_mat(ctx.ggml, weight_perm, transposed_input);
|
|
auto * batch_output = ggml_col2im_1d(ctx.ggml, columns, config.stride, static_cast<int>(config.out_channels), config.padding);
|
|
if (bias_matrix != nullptr) {
|
|
batch_output = ggml_add(ctx.ggml, batch_output, bias_matrix);
|
|
}
|
|
auto batch_value = engine::core::wrap_tensor(
|
|
ggml_reshape_3d(ctx.ggml, batch_output, batch_output->ne[0], batch_output->ne[1], 1),
|
|
engine::core::TensorShape::from_dims({1, config.out_channels, batch_output->ne[0]}),
|
|
GGML_TYPE_F32);
|
|
if (batch_value.shape.dims[2] != output_shape.dims[2]) {
|
|
throw std::runtime_error("test col2im path produced unexpected frame count");
|
|
}
|
|
output = output.valid() ? engine::modules::ConcatModule({0}).build(ctx, output, batch_value) : batch_value;
|
|
}
|
|
return output;
|
|
}
|
|
|
|
RunResult run_conv_transpose_case(
|
|
const ConvTransposeCase & test_case,
|
|
engine::core::BackendType backend_type,
|
|
bool prepack = false) {
|
|
BackendModuleRunner runner(
|
|
backend_type == engine::core::BackendType::Cuda ? "conv_transpose_fast_path_test.cuda" : "conv_transpose_fast_path_test.reference",
|
|
backend_type);
|
|
|
|
const auto input_shape = engine::core::TensorShape::from_dims(
|
|
{test_case.batch, test_case.in_channels, test_case.frames});
|
|
const auto weight_shape = engine::core::TensorShape::from_dims(
|
|
{test_case.in_channels, test_case.out_channels, test_case.kernel_size});
|
|
const auto bias_shape = engine::core::TensorShape::from_dims({test_case.out_channels});
|
|
|
|
auto input = runner.make_f32(input_shape);
|
|
auto weight = runner.make_f32(weight_shape);
|
|
std::optional<engine::core::TensorValue> bias;
|
|
if (test_case.use_bias) {
|
|
bias = runner.make_f32(bias_shape);
|
|
}
|
|
|
|
const auto values = make_patterned_f32(static_cast<size_t>(weight_shape.num_elements()), 0.47f, 0.017f);
|
|
std::optional<engine::core::TensorValue> matrix;
|
|
std::vector<float> packed;
|
|
if (prepack) {
|
|
matrix = runner.make_f32(engine::core::TensorShape::from_dims(
|
|
{test_case.out_channels * test_case.kernel_size, test_case.in_channels}));
|
|
packed.resize(values.size());
|
|
for (int64_t output = 0; output < test_case.out_channels; ++output) {
|
|
for (int64_t tap = 0; tap < test_case.kernel_size; ++tap) {
|
|
for (int64_t input = 0; input < test_case.in_channels; ++input) {
|
|
packed[static_cast<size_t>((output * test_case.kernel_size + tap) * test_case.in_channels + input)] =
|
|
values[static_cast<size_t>((input * test_case.out_channels + output) * test_case.kernel_size + tap)];
|
|
}
|
|
}
|
|
}
|
|
}
|
|
const auto output = engine::modules::ConvTranspose1dModule(make_config(test_case))
|
|
.build(runner.ctx, input, engine::modules::ConvTranspose1dWeights{weight, bias, matrix});
|
|
|
|
runner.allocate_tensors();
|
|
write_tensor_f32(input, make_patterned_f32(static_cast<size_t>(input_shape.num_elements()), 0.19f, 0.031f), "input");
|
|
write_tensor_f32(weight, values, "weight");
|
|
if (matrix) write_tensor_f32(*matrix, packed, "col2im matrix");
|
|
if (bias) {
|
|
write_tensor_f32(*bias, make_patterned_f32(static_cast<size_t>(bias_shape.num_elements()), 0.83f, 0.011f), "bias");
|
|
}
|
|
|
|
return runner.run_f32(output);
|
|
}
|
|
|
|
RunResult run_conv_transpose_ab_case(
|
|
const ConvTransposeCase & test_case,
|
|
engine::core::BackendType backend_type,
|
|
bool use_col2im,
|
|
float input_phase) {
|
|
BackendModuleRunner runner(
|
|
use_col2im ? "conv_transpose_fast_path_test.col2im_ab" : "conv_transpose_fast_path_test.slow_ab",
|
|
backend_type);
|
|
|
|
const auto input_shape = engine::core::TensorShape::from_dims(
|
|
{test_case.batch, test_case.in_channels, test_case.frames});
|
|
const auto weight_shape = engine::core::TensorShape::from_dims(
|
|
{test_case.in_channels, test_case.out_channels, test_case.kernel_size});
|
|
const auto bias_shape = engine::core::TensorShape::from_dims({test_case.out_channels});
|
|
const auto config = make_config(test_case);
|
|
const auto output_shape = engine::core::TensorShape::from_dims(
|
|
{test_case.batch, test_case.out_channels, conv_transpose1d_output_frames(config, test_case.frames)});
|
|
|
|
auto input = runner.make_f32(input_shape);
|
|
auto weight = runner.make_f32(weight_shape);
|
|
std::optional<engine::core::TensorValue> bias;
|
|
if (test_case.use_bias) {
|
|
bias = runner.make_f32(bias_shape);
|
|
}
|
|
|
|
const auto output = use_col2im
|
|
? build_conv_transpose1d_col2im_test_path(
|
|
runner.ctx,
|
|
config,
|
|
input,
|
|
engine::modules::ConvTranspose1dWeights{weight, bias},
|
|
output_shape)
|
|
: build_conv_transpose1d_slow_test_path(
|
|
runner.ctx,
|
|
config,
|
|
input,
|
|
engine::modules::ConvTranspose1dWeights{weight, bias},
|
|
output_shape);
|
|
|
|
runner.allocate_tensors();
|
|
write_tensor_f32(input, make_patterned_f32(static_cast<size_t>(input_shape.num_elements()), input_phase, 0.031f), "input");
|
|
write_tensor_f32(weight, make_patterned_f32(static_cast<size_t>(weight_shape.num_elements()), 0.47f, 0.017f), "weight");
|
|
if (bias) {
|
|
write_tensor_f32(*bias, make_patterned_f32(static_cast<size_t>(bias_shape.num_elements()), 0.83f, 0.011f), "bias");
|
|
}
|
|
|
|
return runner.run_f32(output);
|
|
}
|
|
|
|
void require_cuda_or_hip_backend_classification() {
|
|
static_assert(engine::core::uses_ggml_cuda_or_hip_backend(engine::core::BackendType::Cuda));
|
|
static_assert(engine::core::uses_ggml_cuda_or_hip_backend(engine::core::BackendType::Hip));
|
|
static_assert(!engine::core::uses_ggml_cuda_or_hip_backend(engine::core::BackendType::Cpu));
|
|
static_assert(!engine::core::uses_ggml_cuda_or_hip_backend(engine::core::BackendType::Vulkan));
|
|
static_assert(!engine::core::uses_ggml_cuda_or_hip_backend(engine::core::BackendType::Metal));
|
|
|
|
const ConvTransposeCase test_case{"hip_trigger_condition_probe", 1, 256, 128, 96, 10, 5, true};
|
|
const auto eligible_config = make_config(test_case);
|
|
for (const auto backend : {engine::core::BackendType::Hip, engine::core::BackendType::Metal}) {
|
|
engine::core::ModuleBuildContext ctx{};
|
|
ctx.backend_type = backend;
|
|
if (!engine::modules::is_conv_transpose1d_col2im_fast_path_eligible(ctx, eligible_config)) {
|
|
throw std::runtime_error("expected HIP and Metal conv-transpose config to be col2im fast-path eligible");
|
|
}
|
|
}
|
|
}
|
|
|
|
void require_fast_path_trigger_conditions(engine::core::BackendType backend_type) {
|
|
BackendModuleRunner cuda_runner("conv_transpose_fast_path_test.cuda_trigger", backend_type);
|
|
BackendModuleRunner cpu_runner("conv_transpose_fast_path_test.cpu_trigger", engine::core::BackendType::Cpu);
|
|
|
|
const ConvTransposeCase test_case{"trigger_condition_probe", 1, 256, 128, 96, 10, 5, true};
|
|
auto eligible_config = make_config(test_case);
|
|
if (!engine::modules::is_conv_transpose1d_col2im_fast_path_eligible(cuda_runner.ctx, eligible_config)) {
|
|
throw std::runtime_error("expected Qwen3-style CUDA conv-transpose config to be col2im fast-path eligible");
|
|
}
|
|
if (engine::modules::is_conv_transpose1d_col2im_fast_path_eligible(cpu_runner.ctx, eligible_config)) {
|
|
throw std::runtime_error("CPU conv-transpose config must not be col2im fast-path eligible");
|
|
}
|
|
|
|
auto dilated_config = eligible_config;
|
|
dilated_config.dilation = 2;
|
|
if (engine::modules::is_conv_transpose1d_col2im_fast_path_eligible(cuda_runner.ctx, dilated_config)) {
|
|
throw std::runtime_error("dilated conv-transpose config must not be col2im fast-path eligible");
|
|
}
|
|
}
|
|
|
|
void require_same_shape(const engine::core::TensorShape & lhs, const engine::core::TensorShape & rhs, const char * label) {
|
|
if (lhs.rank != rhs.rank) {
|
|
throw std::runtime_error(std::string(label) + " rank mismatch");
|
|
}
|
|
for (size_t i = 0; i < lhs.rank; ++i) {
|
|
if (lhs.dims[i] != rhs.dims[i]) {
|
|
std::ostringstream oss;
|
|
oss << label << " shape mismatch: " << lhs.to_string() << " vs " << rhs.to_string();
|
|
throw std::runtime_error(oss.str());
|
|
}
|
|
}
|
|
}
|
|
|
|
DiffStats compare_results(const RunResult & slow, const RunResult & fast, const ConvTransposeCase & test_case) {
|
|
require_same_shape(slow.shape, fast.shape, test_case.name);
|
|
if (slow.values.size() != fast.values.size()) {
|
|
throw std::runtime_error(std::string(test_case.name) + " value count mismatch");
|
|
}
|
|
|
|
DiffStats stats{};
|
|
size_t max_index = 0;
|
|
for (size_t i = 0; i < slow.values.size(); ++i) {
|
|
const float diff = std::fabs(slow.values[i] - fast.values[i]);
|
|
stats.mean_diff += diff;
|
|
if (diff > stats.max_diff) {
|
|
stats.max_diff = diff;
|
|
max_index = i;
|
|
}
|
|
}
|
|
stats.mean_diff /= static_cast<double>(slow.values.size());
|
|
|
|
constexpr float kMaxAllowed = 2.0e-5f;
|
|
constexpr double kMeanAllowed = 2.0e-6;
|
|
if (stats.max_diff > kMaxAllowed || stats.mean_diff > kMeanAllowed) {
|
|
std::ostringstream oss;
|
|
oss << test_case.name << " slow/fast drift exceeds bounds: max diff=" << stats.max_diff
|
|
<< " at " << max_index << " (slow=" << slow.values[max_index]
|
|
<< ", fast=" << fast.values[max_index] << "), mean diff=" << stats.mean_diff;
|
|
throw std::runtime_error(oss.str());
|
|
}
|
|
return stats;
|
|
}
|
|
|
|
void require_close(const RunResult & slow, const RunResult & fast, const ConvTransposeCase & test_case) {
|
|
(void) compare_results(slow, fast, test_case);
|
|
}
|
|
|
|
void run_case(const ConvTransposeCase & test_case, engine::core::BackendType backend_type) {
|
|
const RunResult reference = run_conv_transpose_case(test_case, engine::core::BackendType::Cpu);
|
|
const RunResult fast = run_conv_transpose_case(test_case, backend_type);
|
|
const RunResult prepacked = run_conv_transpose_case(test_case, backend_type, true);
|
|
if (fast.values != prepacked.values) throw std::runtime_error("cached col2im matrix changed output");
|
|
require_close(reference, fast, test_case);
|
|
std::cout << "[PASS] " << test_case.name << " output " << reference.shape.to_string() << '\n';
|
|
}
|
|
|
|
const char * backend_name(engine::core::BackendType backend_type) {
|
|
switch (backend_type) {
|
|
case engine::core::BackendType::Cpu:
|
|
return "cpu";
|
|
case engine::core::BackendType::Cuda:
|
|
return "cuda";
|
|
case engine::core::BackendType::Vulkan:
|
|
return "vulkan";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
void run_ab_case(const ConvTransposeCase & test_case, engine::core::BackendType backend_type) {
|
|
constexpr int kRounds = 6;
|
|
double slow_total_ms = 0.0;
|
|
double col2im_total_ms = 0.0;
|
|
DiffStats worst{};
|
|
engine::core::TensorShape output_shape;
|
|
for (int round = 0; round < kRounds; ++round) {
|
|
const float phase = 0.19f + 0.07f * static_cast<float>(round);
|
|
const auto slow = run_conv_transpose_ab_case(test_case, backend_type, false, phase);
|
|
const auto col2im = run_conv_transpose_ab_case(test_case, backend_type, true, phase);
|
|
const auto stats = compare_results(slow, col2im, test_case);
|
|
slow_total_ms += slow.compute_ms;
|
|
col2im_total_ms += col2im.compute_ms;
|
|
worst.max_diff = std::max(worst.max_diff, stats.max_diff);
|
|
worst.mean_diff = std::max(worst.mean_diff, stats.mean_diff);
|
|
output_shape = slow.shape;
|
|
}
|
|
const double slow_avg_ms = slow_total_ms / static_cast<double>(kRounds);
|
|
const double col2im_avg_ms = col2im_total_ms / static_cast<double>(kRounds);
|
|
std::cout << "[AB] " << backend_name(backend_type) << ' ' << test_case.name
|
|
<< " output " << output_shape.to_string()
|
|
<< " rounds=" << kRounds
|
|
<< " slow_avg_ms=" << slow_avg_ms
|
|
<< " col2im_avg_ms=" << col2im_avg_ms
|
|
<< " speedup=" << (slow_avg_ms / col2im_avg_ms)
|
|
<< " max_diff=" << worst.max_diff
|
|
<< " mean_diff=" << worst.mean_diff << '\n';
|
|
}
|
|
|
|
} // namespace
|
|
|
|
int main(int argc, char ** argv) {
|
|
try {
|
|
const auto started = std::chrono::steady_clock::now();
|
|
if (argc == 2 && std::string(argv[1]) == "--log") engine::debug::configure_logging({true, std::nullopt});
|
|
const ConvTransposeCase qwen3_case{"qwen3_decoder_mid_block_biased", 1, 256, 128, 96, 10, 5, true};
|
|
const ConvTransposeCase batched_case{"batched_decoder_block_no_bias", 2, 192, 192, 48, 2, 2, false};
|
|
require_cuda_or_hip_backend_classification();
|
|
|
|
const auto kBackend = backend_is_available(engine::core::BackendType::Hip)
|
|
? engine::core::BackendType::Hip
|
|
: engine::core::BackendType::Cuda;
|
|
if (!backend_is_available(kBackend)) {
|
|
std::cout << "[SKIP] CUDA/HIP backend is not available for conv transpose fast-path parity test\n";
|
|
} else {
|
|
require_fast_path_trigger_conditions(kBackend);
|
|
run_case(qwen3_case, kBackend);
|
|
run_case(batched_case, kBackend);
|
|
run_ab_case(qwen3_case, kBackend);
|
|
run_ab_case(batched_case, kBackend);
|
|
}
|
|
|
|
std::cout << "[SKIP] CPU backend does not implement ggml_col2im_1d for conv transpose A/B test\n";
|
|
|
|
if (backend_is_available(engine::core::BackendType::Vulkan)) {
|
|
run_case(qwen3_case, engine::core::BackendType::Vulkan);
|
|
run_case(batched_case, engine::core::BackendType::Vulkan);
|
|
run_ab_case(qwen3_case, engine::core::BackendType::Vulkan);
|
|
run_ab_case(batched_case, engine::core::BackendType::Vulkan);
|
|
} else {
|
|
std::cout << "[SKIP] Vulkan backend is not available for conv transpose A/B test\n";
|
|
}
|
|
engine::debug::timing_log_scalar("session.wall_ms", std::chrono::duration<double, std::milli>(
|
|
std::chrono::steady_clock::now() - started).count());
|
|
} catch (const std::exception & ex) {
|
|
std::cerr << "[FAIL] " << ex.what() << '\n';
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|