#include "transcribe.h"
#include "arch/silero_vad/silero_vad.h"
#include "arch/whisper/bin_load.h"
#include "ggml-backend.h"
#include "ggml.h"
#include "transcribe-abi.h"
#include "transcribe-api-guard.h"
#include "transcribe-arch.h"
#include "transcribe-backend.h"
#include "transcribe-loader.h"
#include "transcribe-log.h"
#include "transcribe-model.h"
#include "transcribe-path.h"
#include "transcribe/diarize.h"
#include "transcribe/langid.h"
#include "transcribe/vad.h"
#if defined(TRANSCRIBE_GGML_BACKEND_DL) && defined(_WIN32)
# ifndef WIN32_LEAN_AND_MEAN
# define WIN32_LEAN_AND_MEAN
# endif
# ifndef NOMINMAX
# define NOMINMAX
# endif
# include <windows.h>
#elif defined(TRANSCRIBE_GGML_BACKEND_DL)
# include <dlfcn.h>
#endif
#include <algorithm>
#include <atomic>
#include <cctype>
#include <cstdarg>
#include <cstddef>
#include <cstdint>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <ios>
#include <mutex>
#include <set>
#include <string>
using transcribe::api_guard_status;
using transcribe::api_guard_value;
using transcribe::api_guard_void;
using transcribe::enum_field_raw;
extern "C" const char * transcribe_status_string(int status) {
switch (status) {
case TRANSCRIBE_OK:
return "ok";
case TRANSCRIBE_ERR_INVALID_ARG:
return "invalid argument";
case TRANSCRIBE_ERR_NOT_IMPLEMENTED:
return "not implemented";
case TRANSCRIBE_ERR_FILE_NOT_FOUND:
return "file not found";
case TRANSCRIBE_ERR_GGUF:
return "gguf load error";
case TRANSCRIBE_ERR_UNSUPPORTED_ARCH:
return "unsupported architecture";
case TRANSCRIBE_ERR_UNSUPPORTED_VARIANT:
return "unsupported variant";
case TRANSCRIBE_ERR_OOM:
return "out of memory";
case TRANSCRIBE_ERR_BACKEND:
return "backend error";
case TRANSCRIBE_ERR_SAMPLE_RATE:
return "sample rate mismatch";
case TRANSCRIBE_ERR_UNSUPPORTED_LANGUAGE:
return "unsupported language";
case TRANSCRIBE_ERR_UNSUPPORTED_TASK:
return "unsupported task";
case TRANSCRIBE_ERR_UNSUPPORTED_TIMESTAMPS:
return "unsupported timestamp granularity";
case TRANSCRIBE_ERR_ABORTED:
return "aborted by callback";
case TRANSCRIBE_ERR_BAD_STRUCT_SIZE:
return "caller-owned struct missing or has bad struct_size";
case TRANSCRIBE_ERR_UNSUPPORTED_PNC:
return "model does not support runtime PNC control (reserved; not currently returned)";
case TRANSCRIBE_ERR_UNSUPPORTED_ITN:
return "model does not support runtime ITN control (reserved; not currently returned)";
case TRANSCRIBE_ERR_INPUT_TOO_LONG:
return "input audio too long for model context";
case TRANSCRIBE_ERR_OUTPUT_TRUNCATED:
return "output truncated: decode hit the context/generation cap before end-of-stream";
case TRANSCRIBE_ERR_OUTPUT_REPETITION:
return "output repetition: decode stopped when the output began repeating itself";
case TRANSCRIBE_ERR_UNSUPPORTED_ROLE:
return "model does not serve the requested role";
case TRANSCRIBE_ERR_INPUT_TOO_SHORT:
return "input audio too short";
default:
return "unknown status";
}
}
#ifndef TRANSCRIBE_COMMIT
# define TRANSCRIBE_COMMIT "unknown"
#endif
extern "C" const char * transcribe_version(void) {
return TRANSCRIBE_VERSION;
}
extern "C" const char * transcribe_version_commit(void) {
return TRANSCRIBE_COMMIT;
}
extern "C" size_t transcribe_abi_struct_size(transcribe_abi_struct which) {
switch (enum_field_raw(&which)) {
case TRANSCRIBE_ABI_MODEL_LOAD_PARAMS:
return sizeof(struct transcribe_model_load_params);
case TRANSCRIBE_ABI_SESSION_PARAMS:
return sizeof(struct transcribe_session_params);
case TRANSCRIBE_ABI_RUN_PARAMS:
return sizeof(struct transcribe_run_params);
case TRANSCRIBE_ABI_STREAM_PARAMS:
return sizeof(struct transcribe_stream_params);
case TRANSCRIBE_ABI_CAPABILITIES:
return sizeof(struct transcribe_capabilities);
case TRANSCRIBE_ABI_TIMINGS:
return sizeof(struct transcribe_timings);
case TRANSCRIBE_ABI_SEGMENT:
return sizeof(struct transcribe_segment);
case TRANSCRIBE_ABI_WORD:
return sizeof(struct transcribe_word);
case TRANSCRIBE_ABI_TOKEN:
return sizeof(struct transcribe_token);
case TRANSCRIBE_ABI_STREAM_UPDATE:
return sizeof(struct transcribe_stream_update);
case TRANSCRIBE_ABI_STREAM_TEXT:
return sizeof(struct transcribe_stream_text);
case TRANSCRIBE_ABI_SESSION_LIMITS:
return sizeof(struct transcribe_session_limits);
case TRANSCRIBE_ABI_EXT:
return sizeof(struct transcribe_ext);
case TRANSCRIBE_ABI_DEVICE_INFO:
return sizeof(struct transcribe_device_info);
case TRANSCRIBE_ABI_SPEAKER_SEGMENT:
return sizeof(struct transcribe_speaker_segment);
case TRANSCRIBE_ABI_BACKEND_INIT_PARAMS:
return sizeof(struct transcribe_backend_init_params);
case TRANSCRIBE_ABI_DIARIZE_INFO:
return sizeof(struct transcribe_diarize_info);
case TRANSCRIBE_ABI_DIARIZE_SESSION_PARAMS:
return sizeof(struct transcribe_diarize_session_params);
case TRANSCRIBE_ABI_DIARIZE_PARAMS:
return sizeof(struct transcribe_diarize_params);
case TRANSCRIBE_ABI_LANGID_INFO:
return sizeof(struct transcribe_langid_info);
case TRANSCRIBE_ABI_LANGID_SESSION_PARAMS:
return sizeof(struct transcribe_langid_session_params);
case TRANSCRIBE_ABI_LANGID_PARAMS:
return sizeof(struct transcribe_langid_params);
case TRANSCRIBE_ABI_LANGID_RESULT:
return sizeof(struct transcribe_langid_result);
case TRANSCRIBE_ABI_LANGID_CANDIDATE:
return sizeof(struct transcribe_langid_candidate);
case TRANSCRIBE_ABI_VAD_INFO:
return sizeof(struct transcribe_vad_info);
case TRANSCRIBE_ABI_VAD_SESSION_PARAMS:
return sizeof(struct transcribe_vad_session_params);
case TRANSCRIBE_ABI_VAD_PARAMS:
return sizeof(struct transcribe_vad_params);
case TRANSCRIBE_ABI_VAD_RESULT:
return sizeof(struct transcribe_vad_result);
case TRANSCRIBE_ABI_VAD_SEGMENT:
return sizeof(struct transcribe_vad_segment);
}
return 0; }
extern "C" size_t transcribe_abi_struct_align(transcribe_abi_struct which) {
switch (enum_field_raw(&which)) {
case TRANSCRIBE_ABI_MODEL_LOAD_PARAMS:
return alignof(struct transcribe_model_load_params);
case TRANSCRIBE_ABI_SESSION_PARAMS:
return alignof(struct transcribe_session_params);
case TRANSCRIBE_ABI_RUN_PARAMS:
return alignof(struct transcribe_run_params);
case TRANSCRIBE_ABI_STREAM_PARAMS:
return alignof(struct transcribe_stream_params);
case TRANSCRIBE_ABI_CAPABILITIES:
return alignof(struct transcribe_capabilities);
case TRANSCRIBE_ABI_TIMINGS:
return alignof(struct transcribe_timings);
case TRANSCRIBE_ABI_SEGMENT:
return alignof(struct transcribe_segment);
case TRANSCRIBE_ABI_WORD:
return alignof(struct transcribe_word);
case TRANSCRIBE_ABI_TOKEN:
return alignof(struct transcribe_token);
case TRANSCRIBE_ABI_STREAM_UPDATE:
return alignof(struct transcribe_stream_update);
case TRANSCRIBE_ABI_STREAM_TEXT:
return alignof(struct transcribe_stream_text);
case TRANSCRIBE_ABI_SESSION_LIMITS:
return alignof(struct transcribe_session_limits);
case TRANSCRIBE_ABI_EXT:
return alignof(struct transcribe_ext);
case TRANSCRIBE_ABI_DEVICE_INFO:
return alignof(struct transcribe_device_info);
case TRANSCRIBE_ABI_SPEAKER_SEGMENT:
return alignof(struct transcribe_speaker_segment);
case TRANSCRIBE_ABI_BACKEND_INIT_PARAMS:
return alignof(struct transcribe_backend_init_params);
case TRANSCRIBE_ABI_DIARIZE_INFO:
return alignof(struct transcribe_diarize_info);
case TRANSCRIBE_ABI_DIARIZE_SESSION_PARAMS:
return alignof(struct transcribe_diarize_session_params);
case TRANSCRIBE_ABI_DIARIZE_PARAMS:
return alignof(struct transcribe_diarize_params);
case TRANSCRIBE_ABI_LANGID_INFO:
return alignof(struct transcribe_langid_info);
case TRANSCRIBE_ABI_LANGID_SESSION_PARAMS:
return alignof(struct transcribe_langid_session_params);
case TRANSCRIBE_ABI_LANGID_PARAMS:
return alignof(struct transcribe_langid_params);
case TRANSCRIBE_ABI_LANGID_RESULT:
return alignof(struct transcribe_langid_result);
case TRANSCRIBE_ABI_LANGID_CANDIDATE:
return alignof(struct transcribe_langid_candidate);
case TRANSCRIBE_ABI_VAD_INFO:
return alignof(struct transcribe_vad_info);
case TRANSCRIBE_ABI_VAD_SESSION_PARAMS:
return alignof(struct transcribe_vad_session_params);
case TRANSCRIBE_ABI_VAD_PARAMS:
return alignof(struct transcribe_vad_params);
case TRANSCRIBE_ABI_VAD_RESULT:
return alignof(struct transcribe_vad_result);
case TRANSCRIBE_ABI_VAD_SEGMENT:
return alignof(struct transcribe_vad_segment);
}
return 0;
}
namespace {
void transcribe_log_cb_disabled(transcribe_log_level, const char *, void *) {}
std::atomic<transcribe_log_callback> g_log_cb{ nullptr };
std::atomic<void *> g_log_userdata{ nullptr };
transcribe_log_level transcribe_log_level_from_ggml(int level) {
switch (level) {
case GGML_LOG_LEVEL_NONE:
return TRANSCRIBE_LOG_LEVEL_NONE;
case GGML_LOG_LEVEL_DEBUG:
return TRANSCRIBE_LOG_LEVEL_DEBUG;
case GGML_LOG_LEVEL_INFO:
return TRANSCRIBE_LOG_LEVEL_INFO;
case GGML_LOG_LEVEL_WARN:
return TRANSCRIBE_LOG_LEVEL_WARN;
case GGML_LOG_LEVEL_ERROR:
return TRANSCRIBE_LOG_LEVEL_ERROR;
case GGML_LOG_LEVEL_CONT:
return TRANSCRIBE_LOG_LEVEL_CONT;
default:
return TRANSCRIBE_LOG_LEVEL_INFO;
}
}
void transcribe_ggml_log_bridge(enum ggml_log_level level, const char * text, void *);
}
extern "C" void transcribe_log_set(transcribe_log_callback cb, void * userdata) {
g_log_userdata.store(userdata, std::memory_order_relaxed);
g_log_cb.store(cb != nullptr ? cb : &transcribe_log_cb_disabled, std::memory_order_release);
ggml_log_set(&transcribe_ggml_log_bridge, nullptr);
}
static void transcribe_log_invoke(transcribe_log_callback cb,
transcribe_log_level level,
const char * msg,
void * userdata) noexcept {
try {
cb(level, msg, userdata);
} catch (...) {
std::fprintf(stderr, "transcribe: log callback threw; message dropped: %s\n", msg);
}
}
static void transcribe_log_emit(transcribe_log_level level, const char * msg) {
const auto cb = g_log_cb.load(std::memory_order_acquire);
if (cb == nullptr || cb == &transcribe_log_cb_disabled) {
return;
}
void * userdata = g_log_userdata.load(std::memory_order_relaxed);
transcribe_log_invoke(cb, level, msg, userdata);
}
static void transcribe_log_emit_or_stderr(transcribe_log_level level, const char * msg) {
const auto cb = g_log_cb.load(std::memory_order_acquire);
if (cb == nullptr) { std::fprintf(stderr, "%s\n", msg);
return;
}
if (cb == &transcribe_log_cb_disabled) { return;
}
transcribe_log_invoke(cb, level, msg, g_log_userdata.load(std::memory_order_relaxed));
}
namespace {
void transcribe_ggml_log_bridge(enum ggml_log_level level, const char * text, void *) {
if (text == nullptr) {
return;
}
char buf[1024];
std::snprintf(buf, sizeof(buf), "%s", text);
const size_t n = std::strlen(buf);
if (n > 0 && buf[n - 1] == '\n') {
buf[n - 1] = '\0';
}
transcribe_log_emit_or_stderr(transcribe_log_level_from_ggml(level), buf);
}
}
namespace transcribe {
void log_msg(transcribe_log_level level, const char * fmt, ...) {
char buf[1024];
va_list ap;
va_start(ap, fmt);
std::vsnprintf(buf, sizeof(buf), fmt, ap);
va_end(ap);
transcribe_log_emit_or_stderr(level, buf);
}
}
extern "C" void transcribe_model_load_params_init(struct transcribe_model_load_params * p) {
transcribe::init_sized(p);
}
extern "C" void transcribe_timings_init(struct transcribe_timings * p) {
transcribe::init_sized(p);
}
extern "C" void transcribe_device_info_init(struct transcribe_device_info * p) {
transcribe::init_sized(p);
}
extern "C" void transcribe_backend_init_params_init(struct transcribe_backend_init_params * p) {
transcribe::init_sized(p);
if (p != nullptr) {
p->allowed_backends = TRANSCRIBE_BACKEND_MASK_ALL;
}
}
extern "C" void transcribe_speaker_segment_init(struct transcribe_speaker_segment * p) {
transcribe::init_sized(p);
}
extern "C" transcribe_status transcribe_ext_check(const struct transcribe_ext * ext,
uint32_t expected_kind,
uint64_t min_size) {
if (ext == nullptr) {
return TRANSCRIBE_OK;
}
if (ext->size < sizeof(struct transcribe_ext)) {
return TRANSCRIBE_ERR_BAD_STRUCT_SIZE;
}
if (ext->kind != expected_kind) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (ext->size < min_size) {
return TRANSCRIBE_ERR_BAD_STRUCT_SIZE;
}
return TRANSCRIBE_OK;
}
extern "C" bool transcribe_model_accepts_ext_kind(const struct transcribe_model * model,
transcribe_ext_slot slot,
uint32_t kind) {
if (model == nullptr || model->arch == nullptr) {
return false;
}
if (model->arch->accepts_ext_kind == nullptr) {
return false;
}
return model->arch->accepts_ext_kind(model, slot, kind);
}
extern "C" bool transcribe_model_supports(const struct transcribe_model * model, transcribe_feature feature) {
return transcribe::has_feature(model, enum_field_raw(&feature));
}
namespace {
constexpr size_t k_min_model_params_size = TRANSCRIBE_FIELD_END(transcribe_model_load_params, device);
constexpr size_t k_min_device_info_size = TRANSCRIBE_FIELD_END(transcribe_device_info, kind);
constexpr size_t k_min_backend_init_params_size =
TRANSCRIBE_FIELD_END(transcribe_backend_init_params, allowed_backends);
using transcribe::check_input_struct_size;
using transcribe::check_struct_size;
using transcribe::copy_out_prefix;
}
static void ensure_ggml_time_init() {
static std::once_flag once;
std::call_once(once, [] { ggml_time_init(); });
}
#if defined(TRANSCRIBE_GGML_BACKEND_DL)
static std::filesystem::path library_self_dir() {
# if defined(_WIN32)
HMODULE module = nullptr;
const auto flags = GET_MODULE_HANDLE_EX_FLAG_FROM_ADDRESS | GET_MODULE_HANDLE_EX_FLAG_UNCHANGED_REFCOUNT;
if (!GetModuleHandleExW(flags, reinterpret_cast<LPCWSTR>(&transcribe_init_backends_default), &module)) {
return {};
}
std::wstring path(1024, L'\0');
for (;;) {
const DWORD n = GetModuleFileNameW(module, path.data(), static_cast<DWORD>(path.size()));
if (n == 0) {
return {};
}
if (n < path.size()) {
path.resize(n);
break;
}
if (path.size() >= 32768) {
return {};
}
path.resize(path.size() * 2);
}
return std::filesystem::path(path).parent_path();
# else
Dl_info info{};
if (dladdr(reinterpret_cast<const void *>(&transcribe_init_backends_default), &info) == 0 ||
info.dli_fname == nullptr || info.dli_fname[0] == '\0') {
return {};
}
std::filesystem::path path(info.dli_fname);
std::error_code ec;
auto canonical = std::filesystem::weakly_canonical(path, ec);
if (ec) {
canonical = std::filesystem::absolute(path, ec);
}
if (ec) {
canonical = path;
}
return canonical.parent_path();
# endif
}
static std::string path_for_c_api(const std::filesystem::path & path) {
# if defined(_WIN32)
const auto u8 = path.u8string();
return std::string(u8.begin(), u8.end());
# else
return path.string();
# endif
}
#endif
constexpr uint64_t k_backend_mask_fixed = uint64_t{ 1 } << 32;
static std::atomic<uint64_t> s_backend_mask_state{ TRANSCRIBE_BACKEND_MASK_ALL };
static uint32_t host_backend_mask() {
return static_cast<uint32_t>(s_backend_mask_state.load());
}
static bool ascii_iequals(const char * a, const char * b) {
for (; *a != '\0' && *b != '\0'; ++a, ++b) {
if (std::tolower(static_cast<unsigned char>(*a)) != std::tolower(static_cast<unsigned char>(*b))) {
return false;
}
}
return *a == *b;
}
static bool ascii_iequals_n(const char * a, size_t n, const char * b) {
for (size_t i = 0; i < n; ++i, ++b) {
if (*b == '\0' ||
std::tolower(static_cast<unsigned char>(a[i])) != std::tolower(static_cast<unsigned char>(*b))) {
return false;
}
}
return *b == '\0';
}
struct BackendMaskName {
const char * name;
uint32_t bit;
};
static uint32_t module_mask_bit(const char * name) {
static const BackendMaskName k_modules[] = {
{ "cpu", TRANSCRIBE_BACKEND_MASK_CPU },
{ "blas", TRANSCRIBE_BACKEND_MASK_CPU },
{ "zendnn", TRANSCRIBE_BACKEND_MASK_CPU },
{ "metal", TRANSCRIBE_BACKEND_MASK_METAL },
{ "vulkan", TRANSCRIBE_BACKEND_MASK_VULKAN },
{ "cuda", TRANSCRIBE_BACKEND_MASK_CUDA },
{ "hip", TRANSCRIBE_BACKEND_MASK_ROCM },
};
for (const auto & e : k_modules) {
if (ascii_iequals(name, e.name)) {
return e.bit;
}
}
return TRANSCRIBE_BACKEND_MASK_OTHER;
}
static uint32_t env_backend_mask() {
static const uint32_t mask = [] {
static const BackendMaskName k_tokens[] = {
{ "cpu", TRANSCRIBE_BACKEND_MASK_CPU },
{ "metal", TRANSCRIBE_BACKEND_MASK_METAL },
{ "vulkan", TRANSCRIBE_BACKEND_MASK_VULKAN },
{ "cuda", TRANSCRIBE_BACKEND_MASK_CUDA },
{ "rocm", TRANSCRIBE_BACKEND_MASK_ROCM },
{ "other", TRANSCRIBE_BACKEND_MASK_OTHER },
{ "all", TRANSCRIBE_BACKEND_MASK_ALL },
};
const char * env = std::getenv("TRANSCRIBE_BACKENDS");
if (env == nullptr || env[0] == '\0') {
return TRANSCRIBE_BACKEND_MASK_ALL;
}
uint32_t m = 0;
bool unknown = false;
const char * tok = env;
for (const char * p = env;; ++p) {
if (*p != '\0' && *p != ',' && *p != ' ' && *p != '\t') {
continue;
}
const size_t len = static_cast<size_t>(p - tok);
if (len > 0) {
uint32_t bit = 0;
for (const auto & e : k_tokens) {
if (ascii_iequals_n(tok, len, e.name)) {
bit = e.bit;
}
}
unknown = unknown || bit == 0;
m |= bit;
}
if (*p == '\0') {
break;
}
tok = p + 1;
}
if (unknown) {
char allowed[64] = "all";
const uint32_t eff = m | TRANSCRIBE_BACKEND_MASK_CPU;
if (eff != TRANSCRIBE_BACKEND_MASK_ALL) {
size_t off = 0;
for (const auto & e : k_tokens) {
if (e.bit != TRANSCRIBE_BACKEND_MASK_ALL && (eff & e.bit) != 0) {
off += static_cast<size_t>(
std::snprintf(allowed + off, sizeof(allowed) - off, "%s%s", off ? "," : "", e.name));
}
}
}
transcribe::log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"TRANSCRIBE_BACKENDS='%s': unknown backend name(s) ignored; it now allows: %s "
"(valid: cpu, metal, vulkan, cuda, rocm, other, all)",
env, allowed);
}
return m;
}();
return mask;
}
static uint32_t effective_backend_mask(uint32_t host_mask) {
return (host_mask & env_backend_mask()) | TRANSCRIBE_BACKEND_MASK_CPU;
}
static bool backend_reg_filter(const char * name) noexcept {
const uint32_t host = static_cast<uint32_t>(s_backend_mask_state.fetch_or(k_backend_mask_fixed));
const uint32_t bit = module_mask_bit(name != nullptr ? name : "");
const bool allowed = (effective_backend_mask(host) & bit) != 0;
if (!allowed) {
char msg[160];
std::snprintf(msg, sizeof(msg), "backend '%s' not registered: excluded by allowed-backend mask",
name != nullptr ? name : "(null)");
transcribe_log_emit(TRANSCRIBE_LOG_LEVEL_DEBUG, msg);
}
return allowed;
}
static const bool s_backend_reg_filter_installed = [] {
ggml_backend_set_reg_filter(&backend_reg_filter);
return true;
}();
static transcribe_status set_host_backend_mask(uint32_t mask) {
uint64_t state = s_backend_mask_state.load();
while ((state & k_backend_mask_fixed) == 0) {
if (s_backend_mask_state.compare_exchange_weak(state, mask)) {
return TRANSCRIBE_OK;
}
}
const uint32_t current = effective_backend_mask(static_cast<uint32_t>(state));
const uint32_t wanted = effective_backend_mask(mask);
if (current != wanted) {
transcribe::log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"transcribe_init_backends_ex: allowed-backend mask is already fixed at 0x%08x "
"(backends were registered earlier in this process); cannot change it to 0x%08x",
current, wanted);
return TRANSCRIBE_ERR_BACKEND;
}
return TRANSCRIBE_OK;
}
static transcribe_status transcribe_init_backends_impl(const char * artifact_dir) {
ensure_ggml_time_init();
if (artifact_dir == nullptr || artifact_dir[0] == '\0') {
return TRANSCRIBE_ERR_INVALID_ARG;
}
{
std::error_code ec;
if (!std::filesystem::is_directory(transcribe::path_from_utf8(artifact_dir), ec)) {
transcribe::log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "transcribe_init_backends: %s is not an existing directory",
artifact_dir);
return TRANSCRIBE_ERR_FILE_NOT_FOUND;
}
}
static std::mutex s_mutex;
static std::set<std::string> s_loaded_dirs;
std::lock_guard<std::mutex> lock(s_mutex);
std::error_code ec;
std::filesystem::path canonical = std::filesystem::weakly_canonical(transcribe::path_from_utf8(artifact_dir), ec);
const std::string key = ec ? std::string(artifact_dir) : canonical.u8string();
if (s_loaded_dirs.find(key) == s_loaded_dirs.end()) {
ggml_backend_load_all_from_path(artifact_dir);
s_loaded_dirs.insert(key);
char devices[256];
size_t off = 0;
const size_t n_dev = ggml_backend_dev_count();
for (size_t i = 0; i < n_dev; ++i) {
const int wrote = std::snprintf(devices + off, sizeof(devices) - off, "%s%s", i == 0 ? "" : ", ",
ggml_backend_dev_name(ggml_backend_dev_get(i)));
if (wrote < 0 || static_cast<size_t>(wrote) >= sizeof(devices) - off) {
break; }
off += static_cast<size_t>(wrote);
}
char msg[512];
std::snprintf(msg, sizeof(msg),
"transcribe_init_backends: %zu compute device(s) "
"registered after scanning %s: %s",
n_dev, artifact_dir, off > 0 ? devices : "(none)");
transcribe_log_emit(TRANSCRIBE_LOG_LEVEL_INFO, msg);
}
if (ggml_backend_dev_count() == 0) {
transcribe::log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"transcribe_init_backends: no compute devices registered after "
"loading %s (no usable ggml backend modules found there, and no "
"backends are compiled in)",
artifact_dir);
return TRANSCRIBE_ERR_BACKEND;
}
return TRANSCRIBE_OK;
}
static transcribe_status transcribe_init_backends_default_impl(void) {
ensure_ggml_time_init();
#if !defined(TRANSCRIBE_GGML_BACKEND_DL)
return TRANSCRIBE_OK;
#else
const auto dir = library_self_dir();
if (dir.empty()) {
transcribe::log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"transcribe_init_backends_default: could not resolve the "
"directory containing libtranscribe");
return TRANSCRIBE_ERR_BACKEND;
}
const auto s = path_for_c_api(dir);
return transcribe_init_backends(s.c_str());
#endif
}
static transcribe_status transcribe_init_backends_ex_impl(const struct transcribe_backend_init_params * params) {
ensure_ggml_time_init();
struct transcribe_backend_init_params p;
transcribe_backend_init_params_init(&p);
if (params != nullptr) {
if (const auto st = check_input_struct_size(params->struct_size, k_min_backend_init_params_size);
st != TRANSCRIBE_OK) {
return st;
}
std::memcpy(&p, params, std::min<uint64_t>(params->struct_size, sizeof(p)));
}
if (const auto st = set_host_backend_mask(p.allowed_backends); st != TRANSCRIBE_OK) {
return st;
}
const auto st = p.artifact_dir != nullptr ? transcribe_init_backends_impl(p.artifact_dir) :
transcribe_init_backends_default_impl();
if (st != TRANSCRIBE_OK) {
return st;
}
if (ggml_backend_dev_count() == 0) {
transcribe::log_msg(TRANSCRIBE_LOG_LEVEL_ERROR,
"transcribe_init_backends_ex: no compute devices registered (allowed-backend mask 0x%08x)",
effective_backend_mask(host_backend_mask()));
return TRANSCRIBE_ERR_BACKEND;
}
return TRANSCRIBE_OK;
}
namespace {
transcribe_device_type to_device_type(enum ggml_backend_dev_type t) {
switch (t) {
case GGML_BACKEND_DEVICE_TYPE_CPU:
return TRANSCRIBE_DEVICE_TYPE_CPU;
case GGML_BACKEND_DEVICE_TYPE_IGPU:
return TRANSCRIBE_DEVICE_TYPE_IGPU;
case GGML_BACKEND_DEVICE_TYPE_ACCEL:
return TRANSCRIBE_DEVICE_TYPE_ACCEL;
case GGML_BACKEND_DEVICE_TYPE_GPU:
default:
return TRANSCRIBE_DEVICE_TYPE_GPU;
}
}
void fill_device_info(ggml_backend_dev_t dev, uint64_t caller_size, struct transcribe_device_info * out) {
ggml_backend_dev_props props{};
ggml_backend_dev_get_props(dev, &props);
const transcribe::BackendKind kind = transcribe::classify_device(dev);
const char * kind_name = transcribe::kind_name(kind);
const char * name = ggml_backend_dev_name(dev);
if (name == nullptr || name[0] == '\0') {
name = (props.name != nullptr && props.name[0] != '\0') ? props.name : kind_name;
}
const char * description = ggml_backend_dev_description(dev);
if (description == nullptr) {
description = props.description != nullptr ? props.description : "";
}
struct transcribe_device_info staged{};
staged.struct_size = caller_size;
staged.name = name;
staged.description = description;
staged.kind = kind_name;
staged.device_id = props.device_id;
staged.memory_total = props.memory_total;
staged.memory_free = props.memory_free;
staged.device_type = to_device_type(props.type);
copy_out_prefix(out, &staged, caller_size, sizeof(staged));
}
}
static int transcribe_device_count_impl(void) {
return static_cast<int>(ggml_backend_dev_count());
}
static transcribe_device_t transcribe_device_get_impl(int index) {
if (index < 0 || index >= static_cast<int>(ggml_backend_dev_count())) {
return nullptr;
}
return reinterpret_cast<transcribe_device_t>(ggml_backend_dev_get(static_cast<size_t>(index)));
}
static ggml_backend_dev_t device_from_handle(transcribe_device_t device) {
if (device == nullptr) {
return nullptr;
}
ggml_backend_dev_t candidate = reinterpret_cast<ggml_backend_dev_t>(device);
for (size_t i = 0; i < ggml_backend_dev_count(); ++i) {
if (ggml_backend_dev_get(i) == candidate) {
return candidate;
}
}
return nullptr;
}
static transcribe_status transcribe_device_get_info_impl(transcribe_device_t device,
struct transcribe_device_info * out) {
if (out == nullptr) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (const auto st = check_struct_size(out->struct_size, k_min_device_info_size); st != TRANSCRIBE_OK) {
return st;
}
ggml_backend_dev_t dev = device_from_handle(device);
if (dev == nullptr) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
fill_device_info(dev, out->struct_size, out);
return TRANSCRIBE_OK;
}
static bool transcribe_backend_available_impl(int raw) {
const size_t n = ggml_backend_dev_count();
if (raw == TRANSCRIBE_BACKEND_AUTO) {
return n > 0;
}
transcribe::BackendKind want;
switch (raw) {
case TRANSCRIBE_BACKEND_CPU:
case TRANSCRIBE_BACKEND_CPU_ACCEL:
want = transcribe::BackendKind::Cpu;
break;
case TRANSCRIBE_BACKEND_METAL:
want = transcribe::BackendKind::Metal;
break;
case TRANSCRIBE_BACKEND_VULKAN:
want = transcribe::BackendKind::Vulkan;
break;
case TRANSCRIBE_BACKEND_CUDA:
want = transcribe::BackendKind::Cuda;
break;
case TRANSCRIBE_BACKEND_ROCM:
want = transcribe::BackendKind::Rocm;
break;
default:
return false;
}
for (size_t i = 0; i < n; ++i) {
if (transcribe::classify_device(ggml_backend_dev_get(i)) == want) {
return true;
}
}
return false;
}
static transcribe_status transcribe_model_load_file_impl(const char * path,
const struct transcribe_model_load_params * params,
struct transcribe_model ** out_model) {
ensure_ggml_time_init();
if (out_model == nullptr) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
*out_model = nullptr;
if (path == nullptr) {
return TRANSCRIBE_ERR_INVALID_ARG;
}
struct transcribe_model_load_params params_defaults;
transcribe_model_load_params_init(¶ms_defaults);
if (params == nullptr) {
params = ¶ms_defaults;
}
if (const auto st = check_input_struct_size(params->struct_size, k_min_model_params_size); st != TRANSCRIBE_OK) {
return st;
}
struct transcribe_model_load_params params_view;
transcribe_model_load_params_init(¶ms_view);
std::memcpy(¶ms_view, params,
static_cast<size_t>(std::min<uint64_t>(params->struct_size, sizeof(params_view))));
{
unsigned char no_mmap_raw = 0;
std::memcpy(&no_mmap_raw, ¶ms_view.no_mmap, sizeof(no_mmap_raw));
params_view.no_mmap = no_mmap_raw != 0;
}
params = ¶ms_view;
switch (enum_field_raw(¶ms->backend)) {
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:
break;
default:
transcribe::log_msg(TRANSCRIBE_LOG_LEVEL_ERROR, "transcribe_model_load_file: invalid backend request %d",
enum_field_raw(¶ms->backend));
return TRANSCRIBE_ERR_INVALID_ARG;
}
if (!transcribe::path_is_present(path)) {
return TRANSCRIBE_ERR_FILE_NOT_FOUND;
}
uint32_t magic = 0;
bool silero_bin = false;
{
std::ifstream fin(transcribe::path_from_utf8(path), std::ios::binary);
if (!fin) {
return TRANSCRIBE_ERR_GGUF;
}
uint8_t bytes[4];
if (!fin.read(reinterpret_cast<char *>(bytes), sizeof(bytes))) {
return TRANSCRIBE_ERR_GGUF;
}
magic = uint32_t(bytes[0]) | uint32_t(bytes[1]) << 8 | uint32_t(bytes[2]) << 16 | uint32_t(bytes[3]) << 24;
if (magic == 0x67676d6cu) {
char tag[sizeof("silero-16k") - 1];
if (fin.read(reinterpret_cast<char *>(bytes), sizeof(bytes)) && bytes[0] == sizeof(tag) && bytes[1] == 0 &&
bytes[2] == 0 && bytes[3] == 0 && fin.read(tag, sizeof(tag))) {
silero_bin = std::memcmp(tag, "silero-16k", sizeof(tag)) == 0;
}
}
}
if (magic == 0x67676d6cu) {
return silero_bin ? transcribe::silero_vad::load_from_bin(path, params, out_model) :
transcribe::whisper::load_from_bin(path, params, out_model);
}
transcribe::Loader loader;
if (const transcribe_status st = loader.open(path); st != TRANSCRIBE_OK) {
return st;
}
const transcribe::Arch * arch = transcribe::find_arch(loader.arch().c_str());
if (arch == nullptr) {
return TRANSCRIBE_ERR_UNSUPPORTED_ARCH;
}
if (arch->load == nullptr) {
return TRANSCRIBE_ERR_NOT_IMPLEMENTED;
}
const transcribe_status st = arch->load(loader, params, out_model);
if (st == TRANSCRIBE_OK && *out_model != nullptr) {
(*out_model)->meta = loader.meta();
}
return st;
}
static void transcribe_model_free_impl(struct transcribe_model * model) {
delete model;
}
extern "C" const char * transcribe_model_arch_string(const struct transcribe_model * model) {
if (model == nullptr || model->arch == nullptr) {
return "";
}
return model->arch->name != nullptr ? model->arch->name : "";
}
extern "C" const char * transcribe_model_variant_string(const struct transcribe_model * model) {
if (model == nullptr) {
return "";
}
return model->variant.c_str();
}
extern "C" const char * transcribe_model_meta_val_str(const struct transcribe_model * model, const char * key) {
if (model == nullptr || key == nullptr) {
return "";
}
const auto it = model->meta.find(key);
return it != model->meta.end() ? it->second.c_str() : "";
}
extern "C" uint32_t transcribe_model_roles(const struct transcribe_model * model) {
return model != nullptr ? model->roles : 0;
}
extern "C" const char * transcribe_model_backend(const struct transcribe_model * model) {
if (model == nullptr) {
return "";
}
return model->backend.c_str();
}
static transcribe_device_t transcribe_model_device_impl(const struct transcribe_model * model) {
if (model == nullptr || model->primary_backend == nullptr) {
return nullptr;
}
ggml_backend_dev_t dev = ggml_backend_get_device(model->primary_backend);
if (dev == nullptr || device_from_handle(reinterpret_cast<transcribe_device_t>(dev)) == nullptr) {
return nullptr;
}
return reinterpret_cast<transcribe_device_t>(dev);
}
extern "C" transcribe_status transcribe_init_backends(const char * artifact_dir) {
return api_guard_status("transcribe_init_backends", [&] { return transcribe_init_backends_impl(artifact_dir); });
}
extern "C" transcribe_status transcribe_init_backends_default(void) {
return api_guard_status("transcribe_init_backends_default",
[&] { return transcribe_init_backends_default_impl(); });
}
extern "C" transcribe_status transcribe_init_backends_ex(const struct transcribe_backend_init_params * params) {
return api_guard_status("transcribe_init_backends_ex", [&] { return transcribe_init_backends_ex_impl(params); });
}
extern "C" uint32_t transcribe_allowed_backends(void) {
return api_guard_value("transcribe_allowed_backends", static_cast<uint32_t>(TRANSCRIBE_BACKEND_MASK_CPU),
[&] { return effective_backend_mask(host_backend_mask()); });
}
extern "C" int transcribe_device_count(void) {
return api_guard_value("transcribe_device_count", 0, [&] { return transcribe_device_count_impl(); });
}
extern "C" transcribe_device_t transcribe_device_get(int index) {
return api_guard_value("transcribe_device_get", static_cast<transcribe_device_t>(nullptr),
[&] { return transcribe_device_get_impl(index); });
}
extern "C" transcribe_status transcribe_device_get_info(transcribe_device_t device,
struct transcribe_device_info * out) {
return api_guard_status("transcribe_device_get_info", [&] { return transcribe_device_get_info_impl(device, out); });
}
extern "C" bool transcribe_backend_available(transcribe_backend_request kind) {
const int raw = enum_field_raw(&kind);
return api_guard_value("transcribe_backend_available", false,
[&] { return transcribe_backend_available_impl(raw); });
}
extern "C" transcribe_status transcribe_model_load_file(const char * path,
const struct transcribe_model_load_params * params,
struct transcribe_model ** out_model) {
const transcribe_status st = api_guard_status("transcribe_model_load_file", [&] {
const transcribe_status lst = transcribe_model_load_file_impl(path, params, out_model);
return lst == TRANSCRIBE_OK && *out_model != nullptr ? transcribe::resolve_roles(*out_model) : lst;
});
if (st != TRANSCRIBE_OK && out_model != nullptr && *out_model != nullptr) {
transcribe_model_free(*out_model);
*out_model = nullptr;
}
return st;
}
extern "C" void transcribe_model_free(struct transcribe_model * model) {
api_guard_void("transcribe_model_free", [&] { transcribe_model_free_impl(model); });
}
extern "C" transcribe_device_t transcribe_model_device(const struct transcribe_model * model) {
return api_guard_value("transcribe_model_device", static_cast<transcribe_device_t>(nullptr),
[&] { return transcribe_model_device_impl(model); });
}