Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
53 changes: 46 additions & 7 deletions bindings/python/src/transcribe_cpp/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@
import threading
import weakref
from dataclasses import dataclass
from typing import Literal, Sequence, Union
from typing import Literal, Optional, Sequence, Union

from . import _abi, _generated
from ._library import _base_version, artifact_dir, load_library, selected_provider
Expand Down Expand Up @@ -240,30 +240,57 @@ def native_provider() -> str | None:
return selected_provider()


_DEVICE_TYPE_NAMES = {
_generated.TRANSCRIBE_DEVICE_TYPE_CPU: "cpu",
_generated.TRANSCRIBE_DEVICE_TYPE_GPU: "gpu",
_generated.TRANSCRIBE_DEVICE_TYPE_IGPU: "igpu",
_generated.TRANSCRIBE_DEVICE_TYPE_ACCEL: "accel",
}


@dataclass(frozen=True)
class BackendDevice:
"""One registered compute device (owned copies of the C strings)."""

name: str
description: str
kind: str # "cpu" | "accel" | "metal" | "vulkan" | "cuda" | "sycl" | "gpu" | "unknown"
device_type: str # vendor-agnostic class: "cpu" | "gpu" | "igpu" | "accel"
device_id: Optional[str] # stable hw id (PCI bus id), or None (e.g. Metal)
memory_total: int # reported capacity in bytes, or 0 if unreported
# Available bytes — a SNAPSHOT at query time, or 0 if unreported. Re-query
# (via backends() or Model.device) to refresh; backend-defined and not
# comparable across device kinds.
memory_free: int


def _backend_device_from_raw(dev) -> BackendDevice:
"""Build a BackendDevice from a library-filled transcribe_backend_device."""
return BackendDevice(
name=_decode(dev.name),
description=_decode(dev.description),
kind=_decode(dev.kind),
device_type=_DEVICE_TYPE_NAMES.get(dev.device_type, "gpu"),
device_id=_decode(dev.device_id) if dev.device_id else None,
memory_total=int(dev.memory_total),
memory_free=int(dev.memory_free),
)


def backends() -> list[BackendDevice]:
"""The compute devices registered with the native runtime — what the
process can actually run on, after backend-module loading and graceful
degradation (e.g. a Vulkan module skipped on a machine without Vulkan)."""
degradation (e.g. a Vulkan module skipped on a machine without Vulkan).

Each device's ``memory_free`` is live as of the call; call again to poll
a device's available memory over time."""
devices = []
for i in range(_lib.transcribe_backend_device_count()):
dev = _generated.transcribe_backend_device()
_lib.transcribe_backend_device_init(_byref(dev))
_check(_lib.transcribe_get_backend_device(i, _byref(dev)),
f"reading backend device {i}")
devices.append(BackendDevice(
name=_decode(dev.name),
description=_decode(dev.description),
kind=_decode(dev.kind),
))
devices.append(_backend_device_from_raw(dev))
return devices


Expand Down Expand Up @@ -781,6 +808,18 @@ def variant(self) -> str:
def backend(self) -> str:
return _decode(_lib.transcribe_model_backend(self._h))

@property
def device(self) -> BackendDevice:
"""The compute device this model is running on. ``memory_free`` is a
live snapshot, so read this again to poll how much device memory is
left after the model loaded. Raises if the model has no resolved
compute device."""
dev = _generated.transcribe_backend_device()
_lib.transcribe_backend_device_init(_byref(dev))
_check(_lib.transcribe_model_get_device(self._h, _byref(dev)),
"model_get_device")
return _backend_device_from_raw(dev)

@property
def capabilities(self) -> Capabilities:
caps = _Capabilities()
Expand Down
12 changes: 9 additions & 3 deletions bindings/python/src/transcribe_cpp/_generated.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
# Stable digest of the ABI surface below (structs, enums, macros, layout,
# prototypes). A native provider package echoes this back so the API
# package can reject an ABI-mismatched provider before dlopen.
PUBLIC_HEADER_HASH = "2273744299e5aa65"
PUBLIC_HEADER_HASH = "ebe6a6816e34a24e"

# === enum constants ===
TRANSCRIBE_OK = 0
Expand Down Expand Up @@ -79,6 +79,10 @@
TRANSCRIBE_BACKEND_VULKAN = 3
TRANSCRIBE_BACKEND_CPU_ACCEL = 4
TRANSCRIBE_BACKEND_CUDA = 5
TRANSCRIBE_DEVICE_TYPE_CPU = 0
TRANSCRIBE_DEVICE_TYPE_GPU = 1
TRANSCRIBE_DEVICE_TYPE_IGPU = 2
TRANSCRIBE_DEVICE_TYPE_ACCEL = 3
TRANSCRIBE_FEATURE_INITIAL_PROMPT = 0
TRANSCRIBE_FEATURE_TEMPERATURE_FALLBACK = 1
TRANSCRIBE_FEATURE_LONG_FORM = 2
Expand Down Expand Up @@ -145,7 +149,7 @@ class transcribe_whisper_chunk_trace(_c.Structure):
pass

transcribe_ext._fields_ = [("size", _c.c_uint64), ("kind", _c.c_uint32)]
transcribe_backend_device._fields_ = [("struct_size", _c.c_uint64), ("name", _c.c_char_p), ("description", _c.c_char_p), ("kind", _c.c_char_p)]
transcribe_backend_device._fields_ = [("struct_size", _c.c_uint64), ("name", _c.c_char_p), ("description", _c.c_char_p), ("kind", _c.c_char_p), ("device_id", _c.c_char_p), ("memory_total", _c.c_uint64), ("memory_free", _c.c_uint64), ("device_type", _c.c_int)]
transcribe_model_load_params._fields_ = [("struct_size", _c.c_uint64), ("backend", _c.c_int), ("gpu_device", _c.c_int)]
transcribe_session_params._fields_ = [("struct_size", _c.c_uint64), ("n_threads", _c.c_int), ("kv_type", _c.c_int), ("n_ctx", _c.c_int32)]
transcribe_run_params._fields_ = [("struct_size", _c.c_uint64), ("task", _c.c_int), ("timestamps", _c.c_int), ("pnc", _c.c_int), ("itn", _c.c_int), ("language", _c.c_char_p), ("target_language", _c.c_char_p), ("keep_special_tags", _c.c_bool), ("family", _c.POINTER(transcribe_ext)), ("spec_k_drafts", _c.c_int32)]
Expand Down Expand Up @@ -187,7 +191,7 @@ class transcribe_whisper_chunk_trace(_c.Structure):
# C-compiler layout captured at generation (for offset self-check).
STRUCT_LAYOUT = {
'transcribe_ext': {'size': 16, 'align': 8, 'offsets': {'size': 0, 'kind': 8}},
'transcribe_backend_device': {'size': 32, 'align': 8, 'offsets': {'struct_size': 0, 'name': 8, 'description': 16, 'kind': 24}},
'transcribe_backend_device': {'size': 64, 'align': 8, 'offsets': {'struct_size': 0, 'name': 8, 'description': 16, 'kind': 24, 'device_id': 32, 'memory_total': 40, 'memory_free': 48, 'device_type': 56}},
'transcribe_model_load_params': {'size': 16, 'align': 8, 'offsets': {'struct_size': 0, 'backend': 8, 'gpu_device': 12}},
'transcribe_session_params': {'size': 24, 'align': 8, 'offsets': {'struct_size': 0, 'n_threads': 8, 'kv_type': 12, 'n_ctx': 16}},
'transcribe_run_params': {'size': 64, 'align': 8, 'offsets': {'struct_size': 0, 'task': 8, 'timestamps': 12, 'pnc': 16, 'itn': 20, 'language': 24, 'target_language': 32, 'keep_special_tags': 40, 'family': 48, 'spec_k_drafts': 56}},
Expand Down Expand Up @@ -287,6 +291,8 @@ def configure(lib):
lib.transcribe_model_free.argtypes = [_c.c_void_p]
lib.transcribe_model_get_capabilities.restype = _c.c_int
lib.transcribe_model_get_capabilities.argtypes = [_c.c_void_p, _c.POINTER(transcribe_capabilities)]
lib.transcribe_model_get_device.restype = _c.c_int
lib.transcribe_model_get_device.argtypes = [_c.c_void_p, _c.POINTER(transcribe_backend_device)]
lib.transcribe_model_load_file.restype = _c.c_int
lib.transcribe_model_load_file.argtypes = [_c.c_char_p, _c.POINTER(transcribe_model_load_params), _c.POINTER(_c.c_void_p)]
lib.transcribe_model_load_params_init.restype = None
Expand Down
33 changes: 30 additions & 3 deletions bindings/rust/sys/src/transcribe_sys.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
// @generated by `cargo xtask bindgen` from include/transcribe/extensions.h
// DO NOT EDIT BY HAND. Regenerate: `cargo xtask bindgen`.
// Pinned to include/transcribe.abihash = 2273744299e5aa65
// Pinned to include/transcribe.abihash = ebe6a6816e34a24e

/// The public-ABI digest these bindings were generated against
/// (sha256/16 over the normalized FFI surface). The load-time version
/// gate and the CI drift check both anchor on this value.
pub const PUBLIC_HEADER_HASH: &str = "2273744299e5aa65";
pub const PUBLIC_HEADER_HASH: &str = "ebe6a6816e34a24e";

/* automatically generated by rust-bindgen 0.72.1 */

Expand Down Expand Up @@ -205,18 +205,31 @@ unsafe extern "C" {
unsafe extern "C" {
pub fn transcribe_backend_device_count() -> ::std::os::raw::c_int;
}
impl transcribe_device_type {
pub const TRANSCRIBE_DEVICE_TYPE_CPU: transcribe_device_type = transcribe_device_type(0);
pub const TRANSCRIBE_DEVICE_TYPE_GPU: transcribe_device_type = transcribe_device_type(1);
pub const TRANSCRIBE_DEVICE_TYPE_IGPU: transcribe_device_type = transcribe_device_type(2);
pub const TRANSCRIBE_DEVICE_TYPE_ACCEL: transcribe_device_type = transcribe_device_type(3);
}
#[repr(transparent)]
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub struct transcribe_device_type(pub ::std::os::raw::c_uint);
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct transcribe_backend_device {
pub struct_size: u64,
pub name: *const ::std::os::raw::c_char,
pub description: *const ::std::os::raw::c_char,
pub kind: *const ::std::os::raw::c_char,
pub device_id: *const ::std::os::raw::c_char,
pub memory_total: u64,
pub memory_free: u64,
pub device_type: transcribe_device_type,
}
#[allow(clippy::unnecessary_operation, clippy::identity_op)]
const _: () = {
["Size of transcribe_backend_device"]
[::std::mem::size_of::<transcribe_backend_device>() - 32usize];
[::std::mem::size_of::<transcribe_backend_device>() - 64usize];
["Alignment of transcribe_backend_device"]
[::std::mem::align_of::<transcribe_backend_device>() - 8usize];
["Offset of field: transcribe_backend_device::struct_size"]
Expand All @@ -227,6 +240,14 @@ const _: () = {
[::std::mem::offset_of!(transcribe_backend_device, description) - 16usize];
["Offset of field: transcribe_backend_device::kind"]
[::std::mem::offset_of!(transcribe_backend_device, kind) - 24usize];
["Offset of field: transcribe_backend_device::device_id"]
[::std::mem::offset_of!(transcribe_backend_device, device_id) - 32usize];
["Offset of field: transcribe_backend_device::memory_total"]
[::std::mem::offset_of!(transcribe_backend_device, memory_total) - 40usize];
["Offset of field: transcribe_backend_device::memory_free"]
[::std::mem::offset_of!(transcribe_backend_device, memory_free) - 48usize];
["Offset of field: transcribe_backend_device::device_type"]
[::std::mem::offset_of!(transcribe_backend_device, device_type) - 56usize];
};
unsafe extern "C" {
pub fn transcribe_backend_device_init(p: *mut transcribe_backend_device);
Expand All @@ -240,6 +261,12 @@ unsafe extern "C" {
unsafe extern "C" {
pub fn transcribe_backend_available(kind: transcribe_backend_request) -> bool;
}
unsafe extern "C" {
pub fn transcribe_model_get_device(
model: *const transcribe_model,
out: *mut transcribe_backend_device,
) -> transcribe_status;
}
#[repr(C)]
#[derive(Debug, Copy, Clone)]
pub struct transcribe_model_load_params {
Expand Down
68 changes: 60 additions & 8 deletions bindings/rust/transcribe-cpp/src/backend.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,75 @@ use std::path::Path;
use transcribe_cpp_sys as sys;

use crate::error::{check, Result};
use crate::result::owned_str;
use crate::result::{owned_opt_str, owned_str};
use crate::types::Backend;

/// ggml's vendor-agnostic class for a compute device, orthogonal to
/// [`Device::kind`] (which carries the vendor). Backends report this
/// classification themselves, so use it as a runtime hint rather than a
/// portable hardware-memory taxonomy.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DeviceType {
/// CPU using system memory.
Cpu,
/// Backend-reported GPU.
Gpu,
/// Backend-reported integrated GPU.
Igpu,
/// Host-memory accelerator (BLAS/AMX/...).
Accel,
}

impl DeviceType {
fn from_raw(raw: sys::transcribe_device_type) -> Self {
use sys::transcribe_device_type as T;
match raw {
T::TRANSCRIBE_DEVICE_TYPE_CPU => DeviceType::Cpu,
T::TRANSCRIBE_DEVICE_TYPE_IGPU => DeviceType::Igpu,
T::TRANSCRIBE_DEVICE_TYPE_ACCEL => DeviceType::Accel,
// includes TRANSCRIBE_DEVICE_TYPE_GPU and any unknown value
_ => DeviceType::Gpu,
}
}
}

/// One registered compute device.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct Device {
/// ggml device name, e.g. "Metal".
pub name: String,
/// Human-readable description, e.g. "Apple M4 Max".
pub description: String,
/// Classified kind: "cpu", "accel", "metal", "vulkan", "cuda", "sycl",
/// "gpu", or "unknown".
/// Classified vendor kind: "cpu", "accel", "metal", "vulkan", "cuda",
/// "sycl", "gpu", or "unknown".
pub kind: String,
/// The CPU/GPU/IGPU/ACCEL axis, orthogonal to [`Device::kind`].
pub device_type: DeviceType,
/// Stable hardware id when the backend reports one (PCI bus id for PCI
/// devices), or `None` (e.g. Metal).
pub device_id: Option<String>,
/// Reported device memory capacity in bytes, or 0 if unreported.
pub memory_total: u64,
/// Available device memory in bytes — a snapshot at the time this was
/// queried, or 0 if unreported. Re-query (via [`devices`] or
/// [`crate::Model::device`]) to refresh it; the value is backend-defined
/// and not comparable across device kinds.
pub memory_free: u64,
}

impl Device {
/// Build a [`Device`] from the raw FFI struct filled by the library.
pub(crate) fn from_raw(raw: &sys::transcribe_backend_device) -> Device {
Device {
name: owned_str(raw.name),
description: owned_str(raw.description),
kind: owned_str(raw.kind),
device_type: DeviceType::from_raw(raw.device_type),
device_id: owned_opt_str(raw.device_id),
memory_total: raw.memory_total,
memory_free: raw.memory_free,
}
}
}

/// Load backend modules from `dir` (dynamic builds) and register their
Expand Down Expand Up @@ -76,11 +132,7 @@ pub fn devices() -> Vec<Device> {
unsafe { sys::transcribe_backend_device_init(&mut raw) };
let status = unsafe { sys::transcribe_get_backend_device(i, &mut raw) };
if status == sys::transcribe_status::TRANSCRIBE_OK {
out.push(Device {
name: owned_str(raw.name),
description: owned_str(raw.description),
kind: owned_str(raw.kind),
});
out.push(Device::from_raw(&raw));
}
}
out
Expand Down
1 change: 1 addition & 0 deletions bindings/rust/transcribe-cpp/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ mod version;

pub use backend::{
backend_available, device_count, devices, init_backends, init_backends_default, Device,
DeviceType,
};
pub use cancel::CancelToken;
pub use error::{Error, Result};
Expand Down
15 changes: 14 additions & 1 deletion bindings/rust/transcribe-cpp/src/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ use std::sync::{Arc, Mutex};

use transcribe_cpp_sys as sys;

use crate::backend::Device;
use crate::error::{check, Result};
use crate::result::owned_str;
use crate::session::Session;
Expand All @@ -31,7 +32,7 @@ use crate::version;
pub struct ModelOptions {
/// Which backend to request. Default [`Backend::Auto`].
pub backend: Backend,
/// Reserved for multi-device selection; must be 0 in 0.x.
/// GPU device registry index. 0 means auto / first matching device.
pub gpu_device: i32,
}

Expand Down Expand Up @@ -197,6 +198,18 @@ impl Model {
owned_str(unsafe { sys::transcribe_model_backend(self.inner.ptr) })
}

/// The compute [`Device`] this model is running on — the one that owns its
/// weights. Its `memory_free` is a live snapshot, so re-call to poll how
/// much memory is left on the device after the model loaded. Errors with
/// [`Error::Backend`](crate::Error) if the model has no resolved device.
pub fn device(&self) -> Result<Device> {
let mut raw: sys::transcribe_backend_device = unsafe { std::mem::zeroed() };
unsafe { sys::transcribe_backend_device_init(&mut raw) };
let status = unsafe { sys::transcribe_model_get_device(self.inner.ptr, &mut raw) };
check(status, "model_get_device")?;
Ok(Device::from_raw(&raw))
}

/// Tokenize plain UTF-8 text into the model's vocabulary (no BOS/EOS, no
/// special tags). Errors with [`Error::NotImplemented`](crate::Error) for
/// families whose tokenizer has no encode path (e.g. SentencePiece today).
Expand Down
2 changes: 1 addition & 1 deletion bindings/swift/Sources/TranscribeCpp/ABIHash.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import CTranscribe
extension Transcribe {
/// sha256/16 of the normalized public FFI surface, pinned to the value in
/// include/transcribe.abihash at the time this binding was last reviewed.
public static let pinnedHeaderHash = "2273744299e5aa65"
public static let pinnedHeaderHash = "ebe6a6816e34a24e"

/// The public-ABI digest this binding was reviewed against (16 hex chars).
public static func headerHash() -> String { pinnedHeaderHash }
Expand Down
Loading
Loading