mirror of
https://github.com/ggml-org/whisper.cpp.git
synced 2026-09-21 05:27:32 -05:00
ruby : Add #free method, check MemoryView strictly (#4032)
* Bump version to 1.3.9 * Fix JFKReader's format * Add Whisper::Context#free * Add test for Whisper::Context#free * Add Whisper::Context#free to RBS * Add Whisper::VAD::Context#free * Add test for Whisper::VAD::Context#free * Add Whisper::VAD::Context#free to RBS * Add Parakeet::Context#free * Add test for Parakeet::Context#free * Add Parakeet::Context#free to RBS * Check MemoryView format more strictly * Fallback MemoryView flags to SIMPLE when MemoryView not available * Add changelog entries * Update document on callbacks * Remove whitespace Co-authored-by: Daniel Bevenius <daniel.bevenius@gmail.com> --------- Co-authored-by: Daniel Bevenius <daniel.bevenius@gmail.com>
This commit is contained in:
co-authored by
Daniel Bevenius
parent
eacbd8234c
commit
642b5d3260
@@ -1,5 +1,13 @@
|
||||
# Changelog
|
||||
|
||||
## [1.3.9] - Unreleased
|
||||
|
||||
Bundled whisper.cpp: `1.9.3`
|
||||
|
||||
- Fixed test fixture JFKReader's format string.
|
||||
- Add #free method to `Whisper::Context`, `Whisper::VAD::Context` and `Whisper::Parakeet::Context`.
|
||||
- Checked MemoryView format more strictly.
|
||||
|
||||
## [1.3.8] - 2026-07-28
|
||||
|
||||
Bundled whisper.cpp: `1.9.1`
|
||||
|
||||
@@ -202,7 +202,7 @@ whisper.transcribe("path/to/audio.wav", params, n_processors: Etc.nprocessors)
|
||||
|
||||
Note that transcription occasionally might be low accuracy when it works in parallel.
|
||||
|
||||
If n_processors is greater than 1, you cannot set any callbacks including new_segment_callback, progress_callback, encoder_begin_callback, abort_callback, and log_callback set by Whisper.log_set.
|
||||
If n_processors is greater than 1, you cannot set some callbacks including encoder_begin_callback, abort_callback. Also, progress_callback is ignored even if it's set.
|
||||
|
||||
### Segments ###
|
||||
|
||||
|
||||
@@ -169,7 +169,7 @@ extern VALUE ruby_whisper_log_queue_drain(ruby_whisper_log_queue *log_queue);
|
||||
#define GetContext(obj, rw) do { \
|
||||
TypedData_Get_Struct((obj), ruby_whisper, &ruby_whisper_type, (rw)); \
|
||||
if ((rw)->context == NULL) { \
|
||||
rb_raise(rb_eRuntimeError, "Not initialized"); \
|
||||
rb_raise(rb_eRuntimeError, "Already freed or not initialized"); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
@@ -187,7 +187,7 @@ extern VALUE ruby_whisper_log_queue_drain(ruby_whisper_log_queue *log_queue);
|
||||
#define GetVADContext(obj, rwvc) do { \
|
||||
TypedData_Get_Struct((obj), ruby_whisper_vad_context, &ruby_whisper_vad_context_type, (rwvc)); \
|
||||
if ((rwvc)->context == NULL) { \
|
||||
rb_raise(rb_eRuntimeError, "Not initialized"); \
|
||||
rb_raise(rb_eRuntimeError, "Already freed or not initialized"); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
@@ -209,7 +209,7 @@ extern VALUE ruby_whisper_log_queue_drain(ruby_whisper_log_queue *log_queue);
|
||||
#define GetParakeetContext(obj, rwpc) do { \
|
||||
TypedData_Get_Struct((obj), ruby_whisper_parakeet_context, &ruby_whisper_parakeet_context_type, (rwpc)); \
|
||||
if ((rwpc)->context == NULL) { \
|
||||
rb_raise(rb_eRuntimeError, "Not initialized"); \
|
||||
rb_raise(rb_eRuntimeError, "Already freed or not initialized"); \
|
||||
} \
|
||||
} while (0)
|
||||
|
||||
|
||||
@@ -316,23 +316,49 @@ VALUE ruby_whisper_model_type(VALUE self)
|
||||
static bool
|
||||
check_memory_view(rb_memory_view_t *memview)
|
||||
{
|
||||
rb_memory_view_prepare_item_desc(memview);
|
||||
|
||||
if (!memview->format) {
|
||||
rb_warn("currently format is required");
|
||||
rb_warn("format is required");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (strcmp(memview->format, "f") == 0) {
|
||||
// accept
|
||||
} else if (strcmp(memview->format, "e") == 0) {
|
||||
if (IS_BIGENDIAN) {
|
||||
rb_warn("currently format \"e\" is only supported on little-endian environment");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
rb_warn("currently only format \"f\" and \"e\" on little-endian environment is supported for MemoryView, but given: %s", memview->format);
|
||||
if (memview->item_desc.length != 1) {
|
||||
rb_warn("format must be exact one character");
|
||||
return false;
|
||||
}
|
||||
|
||||
rb_memory_view_item_component_t component = memview->item_desc.components[0];
|
||||
|
||||
if (component.offset) {
|
||||
rb_warn("format has offset");
|
||||
return false;
|
||||
}
|
||||
if (component.repeat != 1) {
|
||||
rb_warn("format repeated");
|
||||
return false;
|
||||
}
|
||||
switch (component.format) {
|
||||
case 'f':
|
||||
// accept
|
||||
break;
|
||||
case 'e':
|
||||
if (IS_BIGENDIAN) {
|
||||
rb_warn("currently format \"e\" is only supported on little-endian environment");
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
case 'g':
|
||||
if (!IS_BIGENDIAN) {
|
||||
rb_warn("currently format \"g\" is only supported on big-endian environment");
|
||||
return false;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
rb_warn("currently only format \"f\", \"e\" on little-endian environment and \"g\" on big-endian environment are supported for MemoryView, but given: %c", component.format);
|
||||
return false;
|
||||
}
|
||||
|
||||
if (memview->ndim != 1 && !(memview->ndim == 2 && memview->shape[1] == 1)) {
|
||||
// TODO: Accept ndim == 2 with shape [n_samples, channels] and channels > 1 by averaging the samples in different channels or just taking the first channel
|
||||
rb_warn("currently only 1 dimensional MemoryView is supported, but given: %zd", memview->ndim);
|
||||
@@ -394,13 +420,24 @@ parse_samples(VALUE *samples, VALUE *n_samples)
|
||||
}
|
||||
parsed.n_samples = (int)RARRAY_LEN(*samples);
|
||||
} else if (memview_available) {
|
||||
bool memview_got = rb_memory_view_get(*samples, &parsed.memview, RUBY_MEMORY_VIEW_SIMPLE);
|
||||
bool memview_got = rb_memory_view_get(*samples, &parsed.memview, RUBY_MEMORY_VIEW_FORMAT | RUBY_MEMORY_VIEW_ROW_MAJOR);
|
||||
if (memview_got) {
|
||||
parsed.memview_exported = check_memory_view(&parsed.memview);
|
||||
if (!parsed.memview_exported) {
|
||||
rb_memory_view_release(&parsed.memview);
|
||||
parsed.memview = (rb_memory_view_t){0};
|
||||
}
|
||||
} else {
|
||||
// Sometimes MemoryView producers accept only SIMPLE flag even when they provide suitable MemoryView
|
||||
parsed.memview = (rb_memory_view_t){0};
|
||||
bool simple_memview_got = rb_memory_view_get(*samples, &parsed.memview, RUBY_MEMORY_VIEW_SIMPLE);
|
||||
if (simple_memview_got) {
|
||||
parsed.memview_exported = check_memory_view(&parsed.memview);
|
||||
if (!parsed.memview_exported) {
|
||||
rb_memory_view_release(&parsed.memview);
|
||||
parsed.memview = (rb_memory_view_t){0};
|
||||
}
|
||||
}
|
||||
}
|
||||
if (parsed.memview_exported) {
|
||||
ssize_t n_samples_size = parsed.memview.byte_size / parsed.memview.item_size;
|
||||
@@ -791,6 +828,17 @@ ruby_whisper_full_get_vad_segment_t1(VALUE self, VALUE i_segment)
|
||||
return LONG2NUM(whisper_full_get_vad_segment_t1(rw->context, c_i_segment));
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_context_free(VALUE self)
|
||||
{
|
||||
ruby_whisper *rw;
|
||||
GetContext(self, rw);
|
||||
whisper_free(rw->context);
|
||||
rw->context = NULL;
|
||||
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
// High level API
|
||||
|
||||
static VALUE
|
||||
@@ -881,6 +929,7 @@ init_ruby_whisper_context(VALUE *mWhisper)
|
||||
rb_define_method(cContext, "full_get_vad_segment_t1", ruby_whisper_full_get_vad_segment_t1, 1);
|
||||
rb_define_method(cContext, "full", ruby_whisper_full, -1);
|
||||
rb_define_method(cContext, "full_parallel", ruby_whisper_full_parallel, -1);
|
||||
rb_define_method(cContext, "free", ruby_whisper_context_free, 0);
|
||||
|
||||
// High level
|
||||
rb_define_method(cContext, "full_get_segment", ruby_whisper_full_get_segment, 1);
|
||||
|
||||
@@ -269,6 +269,17 @@ ruby_whisper_parakeet_context_full(int argc, VALUE *argv, VALUE self)
|
||||
}
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_parakeet_free(VALUE self)
|
||||
{
|
||||
ruby_whisper_parakeet_context *rwpc;
|
||||
GetParakeetContext(self, rwpc);
|
||||
parakeet_free(rwpc->context);
|
||||
rwpc->context = NULL;
|
||||
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_parakeet_context_get_model(VALUE self)
|
||||
{
|
||||
@@ -289,6 +300,7 @@ init_ruby_whisper_parakeet_context(VALUE *mParakeet)
|
||||
rb_define_method(cParakeetContext, "model", ruby_whisper_parakeet_context_get_model, 0);
|
||||
rb_define_method(cParakeetContext, "each_segment", ruby_whisper_parakeet_context_each_segment, 0);
|
||||
rb_define_method(cParakeetContext, "full", ruby_whisper_parakeet_context_full, -1);
|
||||
rb_define_method(cParakeetContext, "free", ruby_whisper_parakeet_free, 0);
|
||||
|
||||
#define REGISTER_SEGMENT_ATTR(name, type) \
|
||||
rb_define_method(cParakeetContext, "full_" #name, ruby_whisper_parakeet_context_full_##name, 1);
|
||||
|
||||
@@ -132,6 +132,17 @@ ruby_whisper_vad_segments_from_samples(int argc, VALUE *argv, VALUE self)
|
||||
return segments;
|
||||
}
|
||||
|
||||
static VALUE
|
||||
ruby_whisper_vad_free(VALUE self)
|
||||
{
|
||||
ruby_whisper_vad_context *rwvc;
|
||||
GetVADContext(self, rwvc);
|
||||
whisper_vad_free(rwvc->context);
|
||||
rwvc->context = NULL;
|
||||
|
||||
return Qnil;
|
||||
}
|
||||
|
||||
void init_ruby_whisper_vad_context(VALUE *mVAD)
|
||||
{
|
||||
cVADContext = rb_define_class_under(*mVAD, "Context", rb_cObject);
|
||||
@@ -139,4 +150,5 @@ void init_ruby_whisper_vad_context(VALUE *mVAD)
|
||||
rb_define_method(cVADContext, "initialize", ruby_whisper_vad_context_initialize, 1);
|
||||
rb_define_method(cVADContext, "segments_from_samples", ruby_whisper_vad_segments_from_samples, -1);
|
||||
rb_define_method(cVADContext, "detect", ruby_whisper_vad_detect, 2);
|
||||
rb_define_method(cVADContext, "free", ruby_whisper_vad_free, 0);
|
||||
}
|
||||
|
||||
@@ -159,6 +159,9 @@ module Whisper
|
||||
| (Whisper::Params, _Samples, ?Integer n_samples) -> self
|
||||
| (Whisper::Params, _Samples, ?Integer? n_samples, Integer n_processors) -> self
|
||||
|
||||
# Free context and unload the model.
|
||||
def free: () -> void
|
||||
|
||||
class Params
|
||||
def self.new: (
|
||||
?use_gpu: boolish,
|
||||
@@ -708,6 +711,8 @@ module Whisper
|
||||
def each_segment: { (Segment) -> void } -> void
|
||||
| () -> Enumerator[Segment]
|
||||
|
||||
def free: () -> void
|
||||
|
||||
class Params
|
||||
def self.new: (?use_gpu: boolish, ?gpu_device: Integer) -> instance
|
||||
def use_gpu: () -> boolish
|
||||
@@ -1000,6 +1005,7 @@ module Whisper
|
||||
def segments_from_samples: (Params, Array[Float] samples, ?Integer n_samples) -> Segments
|
||||
| (Params, _Samples, ?Integer n_samples) -> Segments
|
||||
def detect: (path wav_file_path, Params) -> Segments
|
||||
def free: () -> void
|
||||
end
|
||||
|
||||
class Segments
|
||||
|
||||
@@ -69,7 +69,7 @@ jfk_reader_get_memory_view(const VALUE obj, rb_memory_view_t *view, int flags)
|
||||
view->data = (void *)args.data;
|
||||
view->byte_size = sizeof(float) * n_samples;
|
||||
view->readonly = true;
|
||||
view->format = "f";
|
||||
view->format = "e";
|
||||
view->item_size = sizeof(float);
|
||||
view->item_desc.components = NULL;
|
||||
view->item_desc.length = 0;
|
||||
|
||||
@@ -113,4 +113,11 @@ class TestParakeetContext < TestBase
|
||||
@parakeet.transcribe "nothing", @params
|
||||
end
|
||||
end
|
||||
|
||||
def test_free
|
||||
@parakeet.free
|
||||
assert_raise RuntimeError do
|
||||
@parakeet.transcribe AUDIO, @params
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -25,6 +25,14 @@ class TestVADContext < TestBase
|
||||
end
|
||||
end
|
||||
|
||||
def test_free
|
||||
vad = Whisper::VAD::Context.new("silero-v6.2.0")
|
||||
vad.free
|
||||
assert_raise RuntimeError do
|
||||
vad.detect(AUDIO, Whisper::VAD::Params.new)
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def assert_segments(segments)
|
||||
|
||||
@@ -50,6 +50,15 @@ class TestWhisper < TestBase
|
||||
end
|
||||
end
|
||||
|
||||
def test_free
|
||||
whisper = Whisper::Context.new("base.en")
|
||||
whisper.free
|
||||
|
||||
assert_raise RuntimeError do
|
||||
whisper.model_type
|
||||
end
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def without_log_callback
|
||||
|
||||
@@ -3,7 +3,7 @@ require_relative "extsources"
|
||||
Gem::Specification.new do |s|
|
||||
s.name = "whispercpp"
|
||||
s.authors = ["Georgi Gerganov", "Todd A. Fisher"]
|
||||
s.version = '1.3.8'
|
||||
s.version = '1.3.9'
|
||||
s.description = %q{High-performance inference of OpenAI's Whisper automatic speech recognition (ASR) model via Ruby}
|
||||
s.email = 'todd.fisher@gmail.com'
|
||||
s.extra_rdoc_files = ['LICENSE', 'README.md']
|
||||
|
||||
Reference in New Issue
Block a user