Skip to content

Commit 18aee94

Browse files
authored
transcribefile: Metal GPU support via llamafile's runtime loader (#1010)
1 parent 68bea25 commit 18aee94

19 files changed

Lines changed: 1234 additions & 24 deletions

diffusionfile/BUILD.mk

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,8 +66,7 @@ DIFFUSIONFILE_INCLUDES := \
6666
# ==============================================================================
6767

6868
DIFFUSIONFILE_CPPFLAGS := \
69-
$(DIFFUSIONFILE_INCLUDES) \
70-
-DLLAMAFILE_VERSION_STRING=\"$(LLAMAFILE_VERSION_STRING)\"
69+
$(DIFFUSIONFILE_INCLUDES)
7170

7271
# ==============================================================================
7372
# Dependencies - llamafile objects for GPU support

diffusionfile/diffusionfile.cpp

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,7 @@
1919
#include <stdio.h>
2020

2121
#include "llamafile/llamafile.h"
22-
23-
// LLAMAFILE_VERSION_STRING is defined by BUILD.mk
24-
#ifndef LLAMAFILE_VERSION_STRING
25-
#define LLAMAFILE_VERSION_STRING "0.0.0-dev"
26-
#endif
22+
#include "llamafile/version.h"
2723

2824
// Forward declaration - defined in stable-diffusion.cpp/examples/cli/main.cpp
2925
// When compiled with -DDIFFUSIONFILE, main.cpp renames main() to diffusion_cli_main()

llamafile/llamafile.c

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -458,14 +458,28 @@ static const char *llamafile_get_home_dir(void) {
458458
return homedir;
459459
}
460460

461+
// Basename of the per-app dot directory under $HOME. Defaults to
462+
// "llamafile"; other products (e.g. transcribefile) override it via
463+
// llamafile_set_app_name() so their cached artifacts — compiled GPU
464+
// dylibs, extracted ggml sources — live in their own tree and can
465+
// diverge from llamafile's without overwriting anything.
466+
static const char *g_app_name = "llamafile";
467+
468+
void llamafile_set_app_name(const char *name) {
469+
if (name && *name)
470+
g_app_name = name;
471+
}
472+
461473
/**
462474
* Returns path of directory for app-specific files.
463-
* Path includes version number: ~/.llamafile/v/<major>.<minor>.<patch>/
475+
* Path includes version number: ~/.<app>/v/<major>.<minor>.<patch>/
476+
* where <app> defaults to "llamafile" (see llamafile_set_app_name).
464477
* This ensures different versions don't overwrite each other's compiled dylibs.
465478
*/
466479
void llamafile_get_app_dir(char *path, size_t size) {
467-
snprintf(path, size, "%s/.llamafile/v/%d.%d.%d/",
480+
snprintf(path, size, "%s/.%s/v/%d.%d.%d/",
468481
llamafile_get_home_dir(),
482+
g_app_name,
469483
LLAMAFILE_MAJOR,
470484
LLAMAFILE_MINOR,
471485
LLAMAFILE_PATCH);

llamafile/llamafile.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ char *llamafile_get_prompt(void); // NOT DEFINED
7777
// USED: Defined in llamafile.c
7878
bool llamafile_has(char **, const char *);
7979
void llamafile_get_app_dir(char *, size_t);
80+
void llamafile_set_app_name(const char *); // app dir basename, default "llamafile"
8081
bool llamafile_extract(const char *, const char *);
8182
int llamafile_is_file_newer_than(const char *, const char *);
8283

tests/transcribefile_smoke.sh

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,23 @@
11
#!/usr/bin/env bash
22
# transcribefile_smoke.sh — regression smoke for the transcribefile APE.
33
#
4-
# Two layers:
4+
# Three layers:
55
# 1. Model-free probes — always run. Catches CLI argv / WAV-loader /
66
# help-text regressions without needing any model artifact.
77
# 2. Parakeet end-to-end — gated on TRANSCRIBEFILE_PARAKEET_GGUF. Skipped
88
# (with a warning, not a failure) when the model isn't available, so
99
# `make check` stays green on machines without the model.
10+
# 3. Metal backend — needs layer 2's model plus macOS on Apple Silicon
11+
# with a registered Metal device; skipped elsewhere. Asserts that
12+
# --backend metal selects an MTL device and that the Metal and CPU
13+
# transcripts match exactly.
1014
#
1115
# Usage:
1216
# tests/transcribefile_smoke.sh <path-to-transcribefile-binary>
1317
#
1418
# Env:
1519
# TRANSCRIBEFILE_PARAKEET_GGUF Path to a parakeet GGUF; if unset or the
16-
# file is missing, layer 2 is skipped.
20+
# file is missing, layers 2 and 3 are skipped.
1721

1822
set -euo pipefail
1923

@@ -74,3 +78,43 @@ echo "$out" | grep -q 'realtime:' || fail "parakeet output missing 'realtime:' l
7478
echo "$out" | grep -qi 'country' || fail "parakeet transcription missing 'country'"
7579
realtime=$(echo "$out" | grep -oE 'realtime:[[:space:]]+[0-9]+x' | head -1 || true)
7680
pass "parakeet transcribes jfk.wav (${realtime:-realtime: ?})"
81+
82+
echo "[smoke] layer 3 — metal backend"
83+
84+
# Metal is macOS/Apple-Silicon only. Elsewhere (and on macs where the
85+
# runtime dylib build isn't possible, e.g. no Xcode CLT) the contract is
86+
# graceful degradation to CPU, so absence of a metal device is a SKIP,
87+
# not a failure.
88+
if [ "$(uname -s)" != "Darwin" ] || [ "$(uname -m)" != "arm64" ]; then
89+
echo " SKIP metal: requires macOS on Apple Silicon" >&2
90+
exit 0
91+
fi
92+
if ! "$APE" --list-devices 2>/dev/null | grep -q 'kind=metal'; then
93+
echo " SKIP metal: no metal device registered" \
94+
"(Xcode command-line tools missing?)" >&2
95+
exit 0
96+
fi
97+
98+
# Explicit --backend metal must actually select an MTL device and produce
99+
# the same transcript TEXT as the CPU backend. Only the text: fields are
100+
# compared: word timestamps and token probabilities may legitimately
101+
# differ across backends — different kernels and accumulation orders give
102+
# slightly different logits, so near-tie decisions (a timestamp on a
103+
# frame boundary, a probability rounding) can flip, and quantized models
104+
# amplify this. The decoded text is expected to be stable.
105+
mtl=$("$APE" --backend metal -q -m "$MODEL" "$SAMPLE" 2>/dev/null) \
106+
|| fail "metal run exited non-zero"
107+
echo "$mtl" | grep -qE 'backend:[[:space:]]+MTL' || fail "metal run did not select an MTL device"
108+
echo "$mtl" | grep -qi 'country' || fail "metal transcription missing 'country'"
109+
pass "parakeet transcribes jfk.wav on metal"
110+
111+
cpu=$("$APE" --backend cpu -q -m "$MODEL" "$SAMPLE" 2>/dev/null) \
112+
|| fail "cpu run exited non-zero"
113+
mtl_text=$(echo "$mtl" | grep '^text:' || true)
114+
cpu_text=$(echo "$cpu" | grep '^text:' || true)
115+
[ -n "$mtl_text" ] || fail "metal output has no text: line"
116+
if [ "$mtl_text" != "$cpu_text" ]; then
117+
printf 'metal:\n%s\ncpu:\n%s\n' "$mtl_text" "$cpu_text" >&2
118+
fail "metal and cpu transcript text differs"
119+
fi
120+
pass "metal and cpu transcripts match"

transcribe.cpp.patches/llamafile-files/BUILD.mk

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,15 @@ TRANSCRIBE_LIB_INCS := \
4545
# Common Compiler Flags
4646
# ==============================================================================
4747

48+
# GGML_MULTIPLATFORM turns the GGML_CALL annotations (ported from the
49+
# llama.cpp patch set) into __ms_abi__ on x86-64, keeping the backend
50+
# interface structs ABI-identical to the GPU dylibs, which are built
51+
# with the same define (a no-op on aarch64, where the attribute doesn't
52+
# exist — hence the global -Wno-attributes). It is already inherited
53+
# from the global CPPFLAGS_ in build/config.mk; it is repeated here, as
54+
# in llama.cpp's BUILD.mk, so the cross-DSO ABI contract is explicit.
4855
TRANSCRIBE_GGML_CPPFLAGS := \
56+
-DGGML_MULTIPLATFORM \
4957
-DGGML_USE_CPU \
5058
-DGGML_CPU_GENERIC \
5159
-DGGML_USE_CPU_REPACK \
@@ -63,6 +71,7 @@ TRANSCRIBE_GGML_CPPFLAGS := \
6371
# zlib downstream (NO_ZLIB_COMPATIBLE_NAMES — call sites use the
6472
# mz_-prefixed API).
6573
TRANSCRIBE_LIB_CPPFLAGS := \
74+
-DGGML_MULTIPLATFORM \
6675
-DTRANSCRIBE_BUILD \
6776
-DGGML_SCHED_MAX_COPIES=4 \
6877
-DMINIZ_NO_INFLATE_APIS \
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
diff --git a/ggml/include/ggml-backend.h b/ggml/include/ggml-backend.h
2+
--- a/transcribe.cpp/ggml/include/ggml-backend.h
3+
+++ b/transcribe.cpp/ggml/include/ggml-backend.h
4+
@@ -17,6 +17,16 @@
5+
# define GGML_BACKEND_API extern
6+
#endif
7+
8+
+#ifdef GGML_MULTIPLATFORM
9+
+# ifdef _MSC_VER
10+
+# define GGML_CALL /* ms_abi is the default on MSVC */
11+
+# else
12+
+# define GGML_CALL __attribute__((__ms_abi__))
13+
+# endif
14+
+#else
15+
+# define GGML_CALL
16+
+#endif
17+
+
18+
#ifdef __cplusplus
19+
extern "C" {
20+
#endif
21+
@@ -208,19 +218,19 @@ extern "C" {
22+
typedef bool (*ggml_backend_comm_allreduce_tensor_t)(void * comm_ctx, struct ggml_tensor ** tensors);
23+
24+
// Split buffer type for tensor parallelism (old)
25+
- typedef ggml_backend_buffer_type_t (*ggml_backend_split_buffer_type_t)(int main_device, const float * tensor_split);
26+
+ typedef ggml_backend_buffer_type_t (GGML_CALL *ggml_backend_split_buffer_type_t)(int main_device, const float * tensor_split);
27+
// Set the number of threads for the backend
28+
- typedef void (*ggml_backend_set_n_threads_t)(ggml_backend_t backend, int n_threads);
29+
+ typedef void (GGML_CALL *ggml_backend_set_n_threads_t)(ggml_backend_t backend, int n_threads);
30+
// Get additional buffer types provided by the device (returns a NULL-terminated array)
31+
- typedef ggml_backend_buffer_type_t * (*ggml_backend_dev_get_extra_bufts_t)(ggml_backend_dev_t device);
32+
+ typedef ggml_backend_buffer_type_t * (GGML_CALL *ggml_backend_dev_get_extra_bufts_t)(ggml_backend_dev_t device);
33+
// Set the abort callback for the backend
34+
- typedef void (*ggml_backend_set_abort_callback_t)(ggml_backend_t backend, ggml_abort_callback abort_callback, void * abort_callback_data);
35+
+ typedef void (GGML_CALL *ggml_backend_set_abort_callback_t)(ggml_backend_t backend, ggml_abort_callback abort_callback, void * abort_callback_data);
36+
// Get a list of feature flags supported by the backend (returns a NULL-terminated array)
37+
struct ggml_backend_feature {
38+
const char * name;
39+
const char * value;
40+
};
41+
- typedef struct ggml_backend_feature * (*ggml_backend_get_features_t)(ggml_backend_reg_t reg);
42+
+ typedef struct ggml_backend_feature * (GGML_CALL *ggml_backend_get_features_t)(ggml_backend_reg_t reg);
43+
44+
//
45+
// Backend registry
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
diff --git a/ggml/include/ggml-cpu.h b/ggml/include/ggml-cpu.h
2+
--- a/transcribe.cpp/ggml/include/ggml-cpu.h
3+
+++ b/transcribe.cpp/ggml/include/ggml-cpu.h
4+
@@ -131,9 +131,9 @@ extern "C" {
5+
GGML_BACKEND_API ggml_backend_t ggml_backend_cpu_init(void);
6+
7+
GGML_BACKEND_API bool ggml_backend_is_cpu (ggml_backend_t backend);
8+
- GGML_BACKEND_API void ggml_backend_cpu_set_n_threads (ggml_backend_t backend_cpu, int n_threads);
9+
- GGML_BACKEND_API void ggml_backend_cpu_set_threadpool (ggml_backend_t backend_cpu, ggml_threadpool_t threadpool);
10+
- GGML_BACKEND_API void ggml_backend_cpu_set_abort_callback(ggml_backend_t backend_cpu, ggml_abort_callback abort_callback, void * abort_callback_data);
11+
+ GGML_BACKEND_API void GGML_CALL ggml_backend_cpu_set_n_threads (ggml_backend_t backend_cpu, int n_threads);
12+
+ GGML_BACKEND_API void ggml_backend_cpu_set_threadpool (ggml_backend_t backend_cpu, ggml_threadpool_t threadpool);
13+
+ GGML_BACKEND_API void GGML_CALL ggml_backend_cpu_set_abort_callback(ggml_backend_t backend_cpu, ggml_abort_callback abort_callback, void * abort_callback_data);
14+
15+
GGML_BACKEND_API void ggml_backend_cpu_set_use_ref(ggml_backend_t backend_cpu, bool use_ref);
16+

0 commit comments

Comments
 (0)