#include "transcribe-load-common.h"
#include "ggml-alloc.h"
#include "ggml-backend.h"
#include "ggml.h"
#include "gguf.h"
#if defined(TRANSCRIBE_HAS_METAL) && !defined(TRANSCRIBE_GGML_BACKEND_DL)
# include "ggml-metal.h"
#endif
#include "transcribe-backend.h"
#include "transcribe-log.h"
#include "transcribe-path.h"
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <ios>
#include <stdexcept>
#include <vector>
namespace transcribe::load_common {
bool metal_backend_lacks_simdgroup_mm(ggml_backend_t be, ggml_backend_dev_t dev) {
if (const char * match = std::getenv("TRANSCRIBE_TEST_METAL_NO_SIMDGROUP_MM");
match != nullptr && match[0] != '\0') {
const char * name = ggml_backend_dev_name(dev);
if (std::strcmp(match, "*") == 0 || (name != nullptr && std::strstr(name, match) != nullptr)) {
return true;
}
}
#if defined(TRANSCRIBE_HAS_METAL) && !defined(TRANSCRIBE_GGML_BACKEND_DL)
if (ggml_backend_is_metal(be)) {
return !ggml_backend_metal_supports_family(be, 7);
}
#endif
(void) be;
return false;
}
namespace {
ggml_backend_t dev_init_checked(ggml_backend_dev_t dev, const char * error_tag) {
const char * name = "?";
try {
if (const char * n = ggml_backend_dev_name(dev); n != nullptr) {
name = n;
}
if (const char * match = std::getenv("TRANSCRIBE_TEST_DEV_INIT_THROW"); match != nullptr && match[0] != '\0') {
if (std::strcmp(match, "*") == 0 || std::strstr(name, match) != nullptr) {
throw std::runtime_error("TRANSCRIBE_TEST_DEV_INIT_THROW fault injection");
}
}
return ggml_backend_dev_init(dev, nullptr);
} catch (const std::exception & e) {
log_msg(TRANSCRIBE_LOG_LEVEL_WARN, "%s: device \"%s\" init threw: %s - skipping it", error_tag, name, e.what());
return nullptr;
} catch (...) {
log_msg(TRANSCRIBE_LOG_LEVEL_WARN, "%s: device \"%s\" init threw an unknown exception - skipping it", error_tag,
name);
return nullptr;
}
}
ggml_backend_t try_init_kind(BackendKind wanted, const char * error_tag, BackendKind & out_kind) {
const size_t n = ggml_backend_dev_count();
std::vector<enum ggml_backend_dev_type> dev_types;
dev_types.reserve(n);
for (size_t i = 0; i < n; ++i) {
dev_types.push_back(ggml_backend_dev_type(ggml_backend_dev_get(i)));
}
for (const size_t i : gpu_probe_order(dev_types)) {
ggml_backend_dev_t dev = ggml_backend_dev_get(i);
if (wanted != BackendKind::OtherGpu && classify_device(dev) != wanted) {
continue;
}
ggml_backend_t be = dev_init_checked(dev, error_tag);
if (be == nullptr) {
continue;
}
const BackendKind kind = classify_device(dev);
if (kind == BackendKind::Metal && metal_backend_lacks_simdgroup_mm(be, dev)) {
const char * dname = ggml_backend_dev_name(dev);
if (wanted == BackendKind::OtherGpu) {
log_msg(TRANSCRIBE_LOG_LEVEL_WARN,
"%s: skipping Metal device \"%s\": no simdgroup matrix multiply "
"(pre-Apple7 GPU)",
error_tag, dname != nullptr ? dname : "?");
safe_backend_free(be);
continue;
}
log_msg(TRANSCRIBE_LOG_LEVEL_WARN,
"%s: Metal device \"%s\" has no simdgroup matrix multiply "
"(pre-Apple7 GPU); transcription may be incorrect and very slow",
error_tag, dname != nullptr ? dname : "?");
}
log_msg(TRANSCRIBE_LOG_LEVEL_INFO, "%s: using %s backend: %s", error_tag, kind_name(kind),
ggml_backend_dev_name(dev));
out_kind = kind;
return be;
}
return nullptr;
}
void append_accel_backends(std::vector<ggml_backend_t> & out, const char * error_tag) {
const size_t n = ggml_backend_dev_count();
for (size_t i = 0; i < n; ++i) {
ggml_backend_dev_t dev = ggml_backend_dev_get(i);
if (ggml_backend_dev_type(dev) != GGML_BACKEND_DEVICE_TYPE_ACCEL) {
continue;
}
ggml_backend_t be = dev_init_checked(dev, error_tag);
if (be == nullptr) {
continue;
}
log_msg(TRANSCRIBE_LOG_LEVEL_INFO, "%s: using accel backend: %s", error_tag, ggml_backend_dev_name(dev));
out.push_back(be);
}
}
ggml_backend_t init_cpu_backend(const char * error_tag) {
ggml_backend_t cpu_be = ggml_backend_init_by_type(GGML_BACKEND_DEVICE_TYPE_CPU, nullptr);
if (cpu_be == nullptr) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "%s: failed to initialize CPU backend", error_tag);
}
return cpu_be;
}
bool valid_backend_request(int raw) {
switch (raw) {
case TRANSCRIBE_BACKEND_AUTO:
case TRANSCRIBE_BACKEND_CPU:
case TRANSCRIBE_BACKEND_METAL:
case TRANSCRIBE_BACKEND_VULKAN:
case TRANSCRIBE_BACKEND_CPU_ACCEL:
case TRANSCRIBE_BACKEND_CUDA:
return true;
}
return false;
}
transcribe_status init_backends_explicit_index(transcribe_backend_request requested,
int dev_index,
const char * error_tag,
BackendPlan & out) {
const size_t n = ggml_backend_dev_count();
if (dev_index < 0 || static_cast<size_t>(dev_index) >= n) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "%s: gpu_device %d out of range [0, %zu)", error_tag, dev_index, n);
return TRANSCRIBE_ERR_INVALID_ARG;
}
ggml_backend_dev_t dev = ggml_backend_dev_get(static_cast<size_t>(dev_index));
const auto dev_type = ggml_backend_dev_type(dev);
if (dev_type != GGML_BACKEND_DEVICE_TYPE_GPU && dev_type != GGML_BACKEND_DEVICE_TYPE_IGPU) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "%s: gpu_device %d (%s) is not a GPU device", error_tag, dev_index,
ggml_backend_dev_name(dev));
return TRANSCRIBE_ERR_INVALID_ARG;
}
const BackendKind got = classify_device(dev);
BackendKind wanted = BackendKind::Unknown; switch (requested) {
case TRANSCRIBE_BACKEND_METAL:
wanted = BackendKind::Metal;
break;
case TRANSCRIBE_BACKEND_VULKAN:
wanted = BackendKind::Vulkan;
break;
case TRANSCRIBE_BACKEND_CUDA:
wanted = BackendKind::Cuda;
break;
case TRANSCRIBE_BACKEND_AUTO:
break;
default:
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (wanted != BackendKind::Unknown && got != wanted) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "%s: gpu_device %d is a %s device but %s was requested", error_tag,
dev_index, kind_name(got), kind_name(wanted));
return TRANSCRIBE_ERR_INVALID_ARG;
}
ggml_backend_t gpu_be = dev_init_checked(dev, error_tag);
if (gpu_be == nullptr) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "%s: failed to initialize gpu_device %d (%s)", error_tag, dev_index,
ggml_backend_dev_name(dev));
return TRANSCRIBE_ERR_BACKEND;
}
log_msg(TRANSCRIBE_LOG_LEVEL_INFO, "%s: using %s backend (gpu_device %d): %s", error_tag, kind_name(got), dev_index,
ggml_backend_dev_name(dev));
if (got == BackendKind::Metal && metal_backend_lacks_simdgroup_mm(gpu_be, dev)) {
const char * dname = ggml_backend_dev_name(dev);
log_msg(TRANSCRIBE_LOG_LEVEL_WARN,
"%s: Metal device \"%s\" has no simdgroup matrix multiply "
"(pre-Apple7 GPU); transcription may be incorrect and very slow",
error_tag, dname != nullptr ? dname : "?");
}
out.primary = gpu_be;
out.primary_kind = got;
out.scheduler_list.push_back(gpu_be);
append_accel_backends(out.scheduler_list, error_tag);
ggml_backend_t cpu_be = init_cpu_backend(error_tag);
if (cpu_be == nullptr) {
return TRANSCRIBE_ERR_BACKEND;
}
out.scheduler_list.push_back(cpu_be);
return TRANSCRIBE_OK;
}
}
transcribe_status init_backends(transcribe_backend_request requested,
int gpu_device,
const char * error_tag,
BackendPlan & out) {
int requested_raw = TRANSCRIBE_BACKEND_AUTO;
std::memcpy(&requested_raw, &requested, sizeof(requested_raw));
out = BackendPlan{};
out.requested = static_cast<transcribe_backend_request>(
valid_backend_request(requested_raw) ? requested_raw : TRANSCRIBE_BACKEND_AUTO);
if (gpu_device < 0) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "%s: gpu_device must be >= 0 (got %d)", error_tag, gpu_device);
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (gpu_device > 0) {
if (!valid_backend_request(requested_raw)) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "%s: invalid transcribe_backend_request value %d", error_tag,
requested_raw);
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (requested_raw == TRANSCRIBE_BACKEND_CPU || requested_raw == TRANSCRIBE_BACKEND_CPU_ACCEL) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "%s: gpu_device %d is invalid for a CPU backend request", error_tag,
gpu_device);
return TRANSCRIBE_ERR_INVALID_ARG;
}
return init_backends_explicit_index(out.requested, gpu_device, error_tag, out);
}
switch (requested_raw) {
case TRANSCRIBE_BACKEND_CPU:
case TRANSCRIBE_BACKEND_CPU_ACCEL:
{
const bool with_accel = (requested == TRANSCRIBE_BACKEND_CPU_ACCEL);
ggml_backend_t cpu_be = init_cpu_backend(error_tag);
if (cpu_be == nullptr) {
return TRANSCRIBE_ERR_BACKEND;
}
log_msg(TRANSCRIBE_LOG_LEVEL_INFO, "%s: using cpu backend (%s)", error_tag,
with_accel ? "with accel" : "strict");
out.primary = cpu_be;
out.primary_kind = BackendKind::Cpu;
if (with_accel) {
append_accel_backends(out.scheduler_list, error_tag);
}
out.scheduler_list.push_back(cpu_be);
return TRANSCRIBE_OK;
}
case TRANSCRIBE_BACKEND_METAL:
case TRANSCRIBE_BACKEND_VULKAN:
case TRANSCRIBE_BACKEND_CUDA:
{
BackendKind wanted = BackendKind::Unknown;
switch (requested) {
case TRANSCRIBE_BACKEND_METAL:
wanted = BackendKind::Metal;
break;
case TRANSCRIBE_BACKEND_VULKAN:
wanted = BackendKind::Vulkan;
break;
case TRANSCRIBE_BACKEND_CUDA:
wanted = BackendKind::Cuda;
break;
default:
break;
}
BackendKind got_kind = BackendKind::Unknown;
ggml_backend_t gpu_be = try_init_kind(wanted, error_tag, got_kind);
if (gpu_be == nullptr) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "%s: %s backend requested but not available", error_tag,
kind_name(wanted));
return TRANSCRIBE_ERR_BACKEND;
}
out.primary = gpu_be;
out.primary_kind = got_kind;
out.scheduler_list.push_back(gpu_be);
append_accel_backends(out.scheduler_list, error_tag);
ggml_backend_t cpu_be = init_cpu_backend(error_tag);
if (cpu_be == nullptr) {
return TRANSCRIBE_ERR_BACKEND;
}
out.scheduler_list.push_back(cpu_be);
return TRANSCRIBE_OK;
}
case TRANSCRIBE_BACKEND_AUTO:
{
BackendKind got_kind = BackendKind::Unknown;
ggml_backend_t gpu_be = try_init_kind(BackendKind::OtherGpu, error_tag, got_kind);
if (gpu_be != nullptr) {
out.primary = gpu_be;
out.primary_kind = got_kind;
out.scheduler_list.push_back(gpu_be);
}
append_accel_backends(out.scheduler_list, error_tag);
ggml_backend_t cpu_be = init_cpu_backend(error_tag);
if (cpu_be == nullptr) {
return TRANSCRIBE_ERR_BACKEND;
}
if (out.primary == nullptr) {
out.primary = cpu_be;
out.primary_kind = BackendKind::Cpu;
}
out.scheduler_list.push_back(cpu_be);
return TRANSCRIBE_OK;
}
}
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "%s: invalid transcribe_backend_request value %d", error_tag, requested_raw);
return TRANSCRIBE_ERR_INVALID_ARG;
}
transcribe_status stream_tensor_data(const std::string & path,
const gguf_context * gguf_data,
ggml_context * ctx_meta,
const char * error_tag) {
std::ifstream fin(path_from_utf8(path), std::ios::binary);
if (!fin) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "%s: failed to reopen %s for tensor data", error_tag, path.c_str());
return TRANSCRIBE_ERR_GGUF;
}
const size_t data_offset = gguf_get_data_offset(gguf_data);
std::vector<uint8_t> staging;
for (ggml_tensor * t = ggml_get_first_tensor(ctx_meta); t != nullptr; t = ggml_get_next_tensor(ctx_meta, t)) {
const int64_t idx = gguf_find_tensor(gguf_data, t->name);
if (idx < 0) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "%s: tensor \"%s\" not in gguf data", error_tag, t->name);
return TRANSCRIBE_ERR_GGUF;
}
const size_t toffset = gguf_get_tensor_offset(gguf_data, idx);
const size_t nbytes = ggml_nbytes(t);
const std::streamoff abs_offset =
static_cast<std::streamoff>(data_offset) + static_cast<std::streamoff>(toffset);
fin.seekg(abs_offset);
if (!fin) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "%s: seek failed for tensor \"%s\"", error_tag, t->name);
return TRANSCRIBE_ERR_GGUF;
}
if (staging.size() < nbytes) {
staging.resize(nbytes);
}
fin.read(reinterpret_cast<char *>(staging.data()), static_cast<std::streamsize>(nbytes));
if (!fin) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "%s: short read for tensor \"%s\" (%zu bytes)", error_tag, t->name,
nbytes);
return TRANSCRIBE_ERR_GGUF;
}
ggml_backend_tensor_set(t, staging.data(), 0, nbytes);
}
return TRANSCRIBE_OK;
}
transcribe_status promote_conv_pw_f16_to_f32_on_cpu(const BackendPlan & plan,
const std::vector<ConvPwF32Slot> & slots,
const char * error_tag,
ggml_context ** out_ctx,
ggml_backend_buffer_t * out_buffer) {
if (plan.primary_kind != BackendKind::Cpu) {
return TRANSCRIBE_OK;
}
if (plan.primary == nullptr) {
return TRANSCRIBE_OK;
}
if (slots.empty()) {
return TRANSCRIBE_OK;
}
const size_t ctx_size = slots.size() * ggml_tensor_overhead() + 256;
ggml_init_params params = { ctx_size, nullptr, true };
ggml_context * ctx = ggml_init(params);
if (ctx == nullptr) {
return TRANSCRIBE_ERR_BACKEND;
}
std::vector<ggml_tensor *> replacements;
replacements.reserve(slots.size());
for (const auto & s : slots) {
ggml_tensor * r = ggml_new_tensor(ctx, GGML_TYPE_F32, ggml_n_dims(s.src), s.src->ne);
if (r == nullptr) {
ggml_free(ctx);
return TRANSCRIBE_ERR_BACKEND;
}
ggml_set_name(r, s.src->name);
replacements.push_back(r);
}
ggml_backend_buffer_t buffer = ggml_backend_alloc_ctx_tensors(ctx, plan.primary);
if (buffer == nullptr) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "%s: conv_pw f32 promotion buffer alloc failed", error_tag);
ggml_free(ctx);
return TRANSCRIBE_ERR_BACKEND;
}
ggml_backend_buffer_set_usage(buffer, GGML_BACKEND_BUFFER_USAGE_WEIGHTS);
const auto * f16_traits = ggml_get_type_traits(GGML_TYPE_F16);
if (f16_traits == nullptr || f16_traits->to_float == nullptr) {
log_msg(TRANSCRIBE_LOG_LEVEL_WARN, "%s: no f16 to_float trait — skipping conv pw promotion", error_tag);
safe_buffer_free(buffer);
ggml_free(ctx);
return TRANSCRIBE_OK;
}
std::vector<uint8_t> f16_staging;
std::vector<float> f32_staging;
for (size_t i = 0; i < slots.size(); ++i) {
ggml_tensor * src = slots[i].src;
ggml_tensor * dst = replacements[i];
const int64_t n_elem = ggml_nelements(src);
const size_t f16_bytes = ggml_nbytes(src);
const size_t f32_bytes = static_cast<size_t>(n_elem) * sizeof(float);
if (f16_staging.size() < f16_bytes) {
f16_staging.resize(f16_bytes);
}
if (f32_staging.size() < static_cast<size_t>(n_elem)) {
f32_staging.resize(n_elem);
}
ggml_backend_tensor_get(src, f16_staging.data(), 0, f16_bytes);
f16_traits->to_float(f16_staging.data(), f32_staging.data(), n_elem);
ggml_backend_tensor_set(dst, f32_staging.data(), 0, f32_bytes);
*slots[i].dst_slot = dst;
}
*out_ctx = ctx;
*out_buffer = buffer;
log_msg(TRANSCRIBE_LOG_LEVEL_INFO,
"%s: promoted %zu conv pointwise weights from F16 → F32 "
"for CPU backend",
error_tag, slots.size());
return TRANSCRIBE_OK;
}
ReadF32Result read_f32_tensor_checked(gguf_context * gguf_ctx,
const std::string & gguf_path,
const char * tensor_name,
size_t expected_elems,
const char * error_tag,
std::vector<float> & out) {
out.clear();
const int64_t idx = gguf_find_tensor(gguf_ctx, tensor_name);
if (idx < 0) {
return ReadF32Result::Absent;
}
const enum ggml_type ttype = gguf_get_tensor_type(gguf_ctx, idx);
if (ttype != GGML_TYPE_F32) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "%s: tensor \"%s\" has type %d, expected F32 (%d)", error_tag, tensor_name,
static_cast<int>(ttype), static_cast<int>(GGML_TYPE_F32));
return ReadF32Result::BadType;
}
const size_t nbytes = static_cast<size_t>(gguf_get_tensor_size(gguf_ctx, idx));
if (nbytes == 0 || (nbytes % sizeof(float)) != 0) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"%s: tensor \"%s\" has %zu bytes "
"(not a multiple of %zu)",
error_tag, tensor_name, nbytes, sizeof(float));
return ReadF32Result::BadSize;
}
const size_t n_elems = nbytes / sizeof(float);
if (expected_elems > 0 && n_elems != expected_elems) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "%s: tensor \"%s\" has %zu elements, expected %zu", error_tag, tensor_name,
n_elems, expected_elems);
return ReadF32Result::BadSize;
}
const size_t data_off = gguf_get_data_offset(gguf_ctx);
const size_t t_off = gguf_get_tensor_offset(gguf_ctx, idx);
std::ifstream fin(path_from_utf8(gguf_path), std::ios::binary);
if (!fin) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "%s: failed to open %s for tensor \"%s\"", error_tag, gguf_path.c_str(),
tensor_name);
return ReadF32Result::ReadErr;
}
const std::streamoff abs_offset = static_cast<std::streamoff>(data_off) + static_cast<std::streamoff>(t_off);
fin.seekg(abs_offset);
if (!fin) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "%s: seek failed for tensor \"%s\"", error_tag, tensor_name);
return ReadF32Result::ReadErr;
}
out.resize(n_elems);
fin.read(reinterpret_cast<char *>(out.data()), static_cast<std::streamsize>(nbytes));
if (!fin || static_cast<size_t>(fin.gcount()) != nbytes) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"%s: short read for tensor \"%s\" "
"(got %zu of %zu bytes)",
error_tag, tensor_name, static_cast<size_t>(fin.gcount()), nbytes);
out.clear();
return ReadF32Result::ReadErr;
}
return ReadF32Result::Ok;
}
}