#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-env.h"
#include "transcribe-log.h"
#include "transcribe-path.h"
#include <algorithm>
#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:
case TRANSCRIBE_BACKEND_ROCM:
return true;
}
return false;
}
transcribe_status init_backends_explicit_device(transcribe_backend_request requested,
transcribe_device_t device,
const char * error_tag,
BackendPlan & out) {
ggml_backend_dev_t dev = reinterpret_cast<ggml_backend_dev_t>(device);
bool registered = false;
for (size_t i = 0; i < ggml_backend_dev_count(); ++i) {
if (ggml_backend_dev_get(i) == dev) {
registered = true;
break;
}
}
if (!registered) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "%s: device handle is not registered with this runtime", error_tag);
return TRANSCRIBE_ERR_INVALID_ARG;
}
const auto dev_type = ggml_backend_dev_type(dev);
if (dev_type == GGML_BACKEND_DEVICE_TYPE_ACCEL) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "%s: accelerator device %s cannot be a model primary", error_tag,
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_CPU:
case TRANSCRIBE_BACKEND_CPU_ACCEL:
wanted = BackendKind::Cpu;
break;
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_ROCM:
wanted = BackendKind::Rocm;
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: selected %s device does not match requested %s backend", error_tag,
kind_name(got), kind_name(wanted));
return TRANSCRIBE_ERR_INVALID_ARG;
}
ggml_backend_t primary = dev_init_checked(dev, error_tag);
if (primary == nullptr) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "%s: failed to initialize selected device %s", error_tag,
ggml_backend_dev_name(dev));
return TRANSCRIBE_ERR_BACKEND;
}
log_msg(TRANSCRIBE_LOG_LEVEL_INFO, "%s: using selected %s device: %s", error_tag, kind_name(got),
ggml_backend_dev_name(dev));
if (got == BackendKind::Metal && metal_backend_lacks_simdgroup_mm(primary, 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 = primary;
out.primary_kind = got;
if (got == BackendKind::Cpu) {
if (requested == TRANSCRIBE_BACKEND_CPU_ACCEL) {
append_accel_backends(out.scheduler_list, error_tag);
}
out.scheduler_list.push_back(primary);
return TRANSCRIBE_OK;
}
out.scheduler_list.push_back(primary);
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,
transcribe_device_t 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 (device != nullptr) {
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;
}
return init_backends_explicit_device(out.requested, 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:
case TRANSCRIBE_BACKEND_ROCM:
{
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_ROCM:
wanted = BackendKind::Rocm;
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;
}
void WeightStorage::reset() noexcept {
safe_buffer_free(mapped_buffer_);
mapped_buffer_ = nullptr;
safe_buffer_free(copy_buffer_);
copy_buffer_ = nullptr;
file_.reset();
}
bool WeightStorage::is_mapped(const ggml_tensor * t) const noexcept {
return t != nullptr && mapped_buffer_ != nullptr && t->buffer == mapped_buffer_;
}
bool mmap_allowed(const transcribe_model_load_params * params) {
if (params != nullptr && params->no_mmap) {
return false;
}
return !env::flag("TRANSCRIBE_NO_MMAP");
}
namespace {
constexpr double k_mib = 1024.0 * 1024.0;
bool device_can_map(const BackendPlan & plan, ggml_backend_dev_t dev) {
if (dev == nullptr) {
return false;
}
ggml_backend_dev_props props{};
ggml_backend_dev_get_props(dev, &props);
if (!props.caps.buffer_from_host_ptr || !props.caps.mmap_support) {
return false;
}
if (plan.primary_kind == BackendKind::Metal && metal_backend_lacks_simdgroup_mm(plan.primary, dev)) {
return false;
}
return true;
}
struct MapCounts {
size_t n_mapped = 0;
size_t mapped_bytes = 0;
size_t n_layout = 0; size_t n_misaligned = 0; };
transcribe_status map_weights(const std::string & path,
const gguf_context * gguf_data,
ggml_context * ctx_meta,
ggml_backend_dev_t dev,
const char * error_tag,
MappedFile & file,
ggml_backend_buffer_t & out_buffer,
MapCounts & counts,
std::string & why) {
if (!file.open(path, &why)) {
log_msg(TRANSCRIBE_LOG_LEVEL_WARN, "%s: cannot memory-map %s (%s); copying weights instead", error_tag,
path.c_str(), why.c_str());
why = "memory mapping failed";
return TRANSCRIBE_OK;
}
const size_t file_size = file.size();
const size_t data_offset = gguf_get_data_offset(gguf_data);
char * base = static_cast<char *>(file.addr());
struct Candidate {
ggml_tensor * t;
char * addr;
};
std::vector<Candidate> candidates;
size_t max_tensor_size = 0;
if (data_offset <= file_size) {
for (ggml_tensor * t = ggml_get_first_tensor(ctx_meta); t != nullptr; t = ggml_get_next_tensor(ctx_meta, t)) {
if (t->data != nullptr || t->buffer != nullptr || t->view_src != nullptr) {
continue; }
const int64_t idx = gguf_find_tensor(gguf_data, ggml_get_name(t));
if (idx < 0) {
continue; }
const size_t nbytes = ggml_nbytes(t);
if (gguf_get_tensor_type(gguf_data, idx) != t->type || gguf_get_tensor_size(gguf_data, idx) != nbytes ||
!ggml_is_contiguous(t)) {
++counts.n_layout;
continue;
}
const size_t toffset = gguf_get_tensor_offset(gguf_data, idx);
if (toffset > file_size - data_offset || nbytes > file_size - data_offset - toffset) {
continue; }
candidates.push_back({ t, base + data_offset + toffset });
max_tensor_size = std::max(max_tensor_size, nbytes);
}
}
if (candidates.empty()) {
file.reset();
why = "no tensor is stored as-is in the file";
return TRANSCRIBE_OK;
}
ggml_backend_buffer_t buffer = ggml_backend_dev_buffer_from_host_ptr(dev, base, file_size, max_tensor_size);
if (buffer == nullptr) {
log_msg(TRANSCRIBE_LOG_LEVEL_WARN, "%s: %s cannot wrap the memory-mapped file; copying weights instead",
error_tag, ggml_backend_dev_name(dev));
file.reset();
why = "device could not wrap the mapping";
return TRANSCRIBE_OK;
}
const size_t alignment = ggml_backend_buffer_get_alignment(buffer);
for (const Candidate & c : candidates) {
const size_t nbytes = ggml_nbytes(c.t);
if (alignment > 1 && reinterpret_cast<uintptr_t>(c.addr) % alignment != 0) {
++counts.n_misaligned;
continue;
}
if (ggml_backend_buffer_get_alloc_size(buffer, c.t) != nbytes) {
++counts.n_layout;
continue;
}
if (ggml_backend_tensor_alloc(buffer, c.t, c.addr) != GGML_STATUS_SUCCESS) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "%s: %s rejected memory-mapped tensor \"%s\"", error_tag,
ggml_backend_dev_name(dev), ggml_get_name(c.t));
out_buffer = buffer; return TRANSCRIBE_ERR_BACKEND;
}
++counts.n_mapped;
counts.mapped_bytes += nbytes;
}
if (counts.n_mapped == 0) {
safe_buffer_free(buffer);
file.reset();
why = counts.n_misaligned > 0 ? "tensors misaligned in the file" : "no tensor is stored as-is in the file";
return TRANSCRIBE_OK;
}
ggml_backend_buffer_set_usage(buffer, GGML_BACKEND_BUFFER_USAGE_WEIGHTS);
out_buffer = buffer;
return TRANSCRIBE_OK;
}
}
transcribe_status allocate_copied_weights(ggml_context * ctx_meta,
const BackendPlan & plan,
const char * error_tag,
WeightStorage & out) {
bool any_unallocated = false;
for (ggml_tensor * t = ggml_get_first_tensor(ctx_meta); t != nullptr; t = ggml_get_next_tensor(ctx_meta, t)) {
if (t->data == nullptr && t->view_src == nullptr) {
any_unallocated = true;
break;
}
}
if (!any_unallocated) {
return TRANSCRIBE_OK;
}
if (out.copy_buffer_ != nullptr || plan.primary == nullptr) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "%s: weight storage already allocated or no primary backend", error_tag);
return TRANSCRIBE_ERR_BACKEND;
}
ggml_backend_buffer_t buffer = ggml_backend_alloc_ctx_tensors(ctx_meta, plan.primary);
if (buffer == nullptr) {
log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "%s: ggml_backend_alloc_ctx_tensors failed", error_tag);
return TRANSCRIBE_ERR_OOM;
}
ggml_backend_buffer_set_usage(buffer, GGML_BACKEND_BUFFER_USAGE_WEIGHTS);
out.copy_buffer_ = buffer;
return TRANSCRIBE_OK;
}
transcribe_status allocate_weights(const std::string & path,
const gguf_context * gguf_data,
ggml_context * ctx_meta,
const BackendPlan & plan,
const transcribe_model_load_params * params,
const char * error_tag,
WeightStorage & out) {
if (plan.primary == nullptr || gguf_data == nullptr || ctx_meta == nullptr) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
ggml_backend_dev_t dev = ggml_backend_get_device(plan.primary);
std::string why;
MapCounts counts;
if (!mmap_allowed(params)) {
why = "memory mapping disabled";
} else if (!MappedFile::supported()) {
why = "memory mapping unsupported on this platform";
} else if (!device_can_map(plan, dev)) {
why = "device keeps weights in its own memory";
} else if (const transcribe_status st =
map_weights(path, gguf_data, ctx_meta, dev, error_tag, out.file_, out.mapped_buffer_, counts, why);
st != TRANSCRIBE_OK) {
return st;
}
if (const transcribe_status st = allocate_copied_weights(ctx_meta, plan, error_tag, out); st != TRANSCRIBE_OK) {
return st;
}
size_t n_copied = 0, copied_bytes = 0;
for (ggml_tensor * t = ggml_get_first_tensor(ctx_meta); t != nullptr; t = ggml_get_next_tensor(ctx_meta, t)) {
if (out.copy_buffer_ != nullptr && t->buffer == out.copy_buffer_) {
++n_copied;
copied_bytes += ggml_nbytes(t);
}
}
if (counts.n_mapped == 0) {
log_msg(TRANSCRIBE_LOG_LEVEL_INFO, "%s: weights copied: %.1f MiB in %s (%s)", error_tag, copied_bytes / k_mib,
out.copy_buffer_ != nullptr ? ggml_backend_buffer_name(out.copy_buffer_) : "-", why.c_str());
} else if (n_copied == 0) {
log_msg(TRANSCRIBE_LOG_LEVEL_INFO, "%s: weights memory-mapped: %.1f MiB in %s", error_tag,
counts.mapped_bytes / k_mib, ggml_backend_buffer_name(out.mapped_buffer_));
} else {
char reasons[96];
if (counts.n_layout > 0 && counts.n_misaligned > 0) {
std::snprintf(reasons, sizeof(reasons), "%zu converted at load, %zu misaligned in the file",
counts.n_layout, counts.n_misaligned);
} else if (counts.n_misaligned > 0) {
std::snprintf(reasons, sizeof(reasons), "misaligned in the file");
} else {
std::snprintf(reasons, sizeof(reasons), "converted at load");
}
log_msg(TRANSCRIBE_LOG_LEVEL_INFO,
"%s: weights memory-mapped: %.1f MiB in %s; %zu tensors (%.1f MiB) copied (%s)", error_tag,
counts.mapped_bytes / k_mib, ggml_backend_buffer_name(out.mapped_buffer_), n_copied,
copied_bytes / k_mib, reasons);
}
return TRANSCRIBE_OK;
}
transcribe_status load_weights(const std::string & path,
const gguf_context * gguf_data,
ggml_context * ctx_meta,
const BackendPlan & plan,
const transcribe_model_load_params * params,
const char * error_tag,
WeightStorage & out) {
if (const transcribe_status st = allocate_weights(path, gguf_data, ctx_meta, plan, params, error_tag, out);
st != TRANSCRIBE_OK) {
return st;
}
return stream_tensor_data(path, gguf_data, ctx_meta, error_tag, &out);
}
transcribe_status stream_tensor_data(const std::string & path,
const gguf_context * gguf_data,
ggml_context * ctx_meta,
const char * error_tag,
const WeightStorage * skip) {
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;
}
constexpr size_t k_stage_chunk = size_t(16) << 20;
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)) {
if (skip != nullptr && skip->is_mapped(t)) {
continue; }
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 (t->buffer != nullptr && ggml_backend_buffer_is_host(t->buffer)) {
fin.read(static_cast<char *>(t->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;
}
continue;
}
const size_t chunk = std::min(nbytes, k_stage_chunk);
if (staging.size() < chunk) {
staging.resize(chunk);
}
for (size_t done = 0; done < nbytes;) {
const size_t n = std::min(nbytes - done, staging.size());
fin.read(reinterpret_cast<char *>(staging.data()), static_cast<std::streamsize>(n));
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(), done, n);
done += n;
}
}
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_OOM;
}
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_OOM;
}
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_OOM;
}
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;
}
}