onde-inference 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/Cargo.lock +7697 -0
- data/Cargo.toml +3 -0
- data/README.md +228 -0
- data/ext/onde-ruby/Cargo.lock +7697 -0
- data/ext/onde-ruby/Cargo.toml +26 -0
- data/ext/onde-ruby/extconf.rb +6 -0
- data/ext/onde-ruby/src/lib.rs +272 -0
- data/lib/onde/version.rb +5 -0
- data/lib/onde.rb +8 -0
- data/rust-toolchain.toml +2 -0
- metadata +56 -0
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
[package]
|
|
2
|
+
name = "onde-ruby"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
authors = ["Seto Elkahfi <setoelkahfi@gmail.com>"]
|
|
6
|
+
publish = false
|
|
7
|
+
|
|
8
|
+
[lib]
|
|
9
|
+
name = "onde_ruby"
|
|
10
|
+
crate-type = ["cdylib"]
|
|
11
|
+
|
|
12
|
+
[dependencies]
|
|
13
|
+
magnus = { version = "0.7" }
|
|
14
|
+
serde = { version = "1.0", features = ["derive"] }
|
|
15
|
+
serde_json = "1"
|
|
16
|
+
|
|
17
|
+
# The onde crate — referenced via relative path from the monorepo.
|
|
18
|
+
# We only pull in the pure-Rust surface (hf_cache, inference::models,
|
|
19
|
+
# inference::types) so we do NOT enable the `whisper` feature or any
|
|
20
|
+
# platform-specific mistralrs re-exports that require GPU SDKs.
|
|
21
|
+
#
|
|
22
|
+
# NOTE: onde's Cargo.toml gates mistralrs behind cfg(target_os = …) so on
|
|
23
|
+
# the host OS (macOS/Linux) it will be pulled in automatically. If this
|
|
24
|
+
# causes build issues in CI, consider adding a `ruby` feature to the onde
|
|
25
|
+
# crate that disables the heavy deps.
|
|
26
|
+
onde = { path = "../../../", default-features = false }
|
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
//! Ruby native extension for the Onde on-device inference engine.
|
|
2
|
+
//!
|
|
3
|
+
//! This crate uses [magnus](https://github.com/matsadler/magnus) to expose
|
|
4
|
+
//! Onde's HuggingFace cache management, supported model metadata, and
|
|
5
|
+
//! inference types to Ruby as the `Onde` module.
|
|
6
|
+
//!
|
|
7
|
+
//! ## Ruby usage
|
|
8
|
+
//!
|
|
9
|
+
//! ```ruby
|
|
10
|
+
//! require "onde"
|
|
11
|
+
//!
|
|
12
|
+
//! # List models cached locally on disk.
|
|
13
|
+
//! response = Onde.list_local_models
|
|
14
|
+
//! response["models"].each do |m|
|
|
15
|
+
//! puts "#{m["model_id"]} — #{m["size_display"]}"
|
|
16
|
+
//! end
|
|
17
|
+
//!
|
|
18
|
+
//! # List all supported models (with download status).
|
|
19
|
+
//! Onde.list_supported_models["models"].each do |m|
|
|
20
|
+
//! status = m["is_downloaded"] ? "✓" : "✗"
|
|
21
|
+
//! puts "[#{status}] #{m["name"]} (#{m["org"]}) — #{m["expected_size_display"]}"
|
|
22
|
+
//! end
|
|
23
|
+
//!
|
|
24
|
+
//! # Delete a cached model.
|
|
25
|
+
//! Onde.delete_model("bartowski/Qwen2.5-1.5B-Instruct-GGUF")
|
|
26
|
+
//!
|
|
27
|
+
//! # Inspect supported model IDs.
|
|
28
|
+
//! Onde::SUPPORTED_MODELS # => ["black-forest-labs/FLUX.1-schnell", ...]
|
|
29
|
+
//!
|
|
30
|
+
//! # Access model metadata.
|
|
31
|
+
//! Onde.model_info("bartowski/Qwen2.5-1.5B-Instruct-GGUF")
|
|
32
|
+
//! # => { "id" => "bartowski/…", "name" => "Qwen 2.5 1.5B (GGUF)", … }
|
|
33
|
+
//!
|
|
34
|
+
//! # Sampling config helpers.
|
|
35
|
+
//! Onde.default_sampling_config
|
|
36
|
+
//! Onde.deterministic_sampling_config
|
|
37
|
+
//! Onde.mobile_sampling_config
|
|
38
|
+
//! ```
|
|
39
|
+
|
|
40
|
+
use magnus::{function, prelude::*, Error, RHash, Ruby};
|
|
41
|
+
|
|
42
|
+
use onde::hf_cache;
|
|
43
|
+
use onde::inference::models::{SUPPORTED_MODELS, SUPPORTED_MODEL_INFO};
|
|
44
|
+
use onde::inference::types::SamplingConfig;
|
|
45
|
+
|
|
46
|
+
// ---------------------------------------------------------------------------
|
|
47
|
+
// Helpers — convert Rust structs to Ruby hashes via serde_json
|
|
48
|
+
// ---------------------------------------------------------------------------
|
|
49
|
+
|
|
50
|
+
/// Serialize any `serde::Serialize` value into a Ruby Hash (or Array of
|
|
51
|
+
/// Hashes) by round-tripping through serde_json. This keeps the binding
|
|
52
|
+
/// layer thin — we don't need to define Ruby classes for every Onde struct.
|
|
53
|
+
fn to_ruby_value<T: serde::Serialize>(ruby: &Ruby, value: &T) -> Result<magnus::Value, Error> {
|
|
54
|
+
let json = serde_json::to_value(value).map_err(|e| {
|
|
55
|
+
Error::new(
|
|
56
|
+
ruby.exception_runtime_error(),
|
|
57
|
+
format!("serialization error: {e}"),
|
|
58
|
+
)
|
|
59
|
+
})?;
|
|
60
|
+
json_to_ruby(ruby, &json)
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
fn json_to_ruby(ruby: &Ruby, value: &serde_json::Value) -> Result<magnus::Value, Error> {
|
|
64
|
+
match value {
|
|
65
|
+
serde_json::Value::Null => Ok(ruby.qnil().as_value()),
|
|
66
|
+
serde_json::Value::Bool(b) => {
|
|
67
|
+
if *b {
|
|
68
|
+
Ok(ruby.qtrue().as_value())
|
|
69
|
+
} else {
|
|
70
|
+
Ok(ruby.qfalse().as_value())
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
serde_json::Value::Number(n) => {
|
|
74
|
+
if let Some(i) = n.as_i64() {
|
|
75
|
+
Ok(ruby.integer_from_i64(i).as_value())
|
|
76
|
+
} else if let Some(f) = n.as_f64() {
|
|
77
|
+
Ok(ruby.float_from_f64(f).as_value())
|
|
78
|
+
} else {
|
|
79
|
+
Ok(ruby.qnil().as_value())
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
serde_json::Value::String(s) => Ok(ruby.str_new(s).as_value()),
|
|
83
|
+
serde_json::Value::Array(arr) => {
|
|
84
|
+
let ary = ruby.ary_new_capa(arr.len());
|
|
85
|
+
for item in arr {
|
|
86
|
+
ary.push(json_to_ruby(ruby, item)?)?;
|
|
87
|
+
}
|
|
88
|
+
Ok(ary.as_value())
|
|
89
|
+
}
|
|
90
|
+
serde_json::Value::Object(map) => {
|
|
91
|
+
let hash = ruby.hash_new();
|
|
92
|
+
for (k, v) in map {
|
|
93
|
+
hash.aset(ruby.str_new(k), json_to_ruby(ruby, v)?)?;
|
|
94
|
+
}
|
|
95
|
+
Ok(hash.as_value())
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ---------------------------------------------------------------------------
|
|
101
|
+
// Exported Ruby methods
|
|
102
|
+
// ---------------------------------------------------------------------------
|
|
103
|
+
|
|
104
|
+
/// `Onde.list_local_models` → Hash
|
|
105
|
+
///
|
|
106
|
+
/// Scans the local HuggingFace hub cache and returns all downloaded models
|
|
107
|
+
/// that the inference engine supports.
|
|
108
|
+
///
|
|
109
|
+
/// Returns a Hash with keys: `"models"`, `"cache_path"`,
|
|
110
|
+
/// `"total_size_bytes"`, `"total_size_display"`.
|
|
111
|
+
fn list_local_models() -> Result<magnus::Value, Error> {
|
|
112
|
+
let ruby = Ruby::get().expect("called outside Ruby");
|
|
113
|
+
let response = hf_cache::list_local_hf_models();
|
|
114
|
+
to_ruby_value(&ruby, &response)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/// `Onde.list_supported_models` → Hash
|
|
118
|
+
///
|
|
119
|
+
/// Returns all models the engine supports, together with flags indicating
|
|
120
|
+
/// whether each one is fully downloaded, partially downloaded, or absent.
|
|
121
|
+
///
|
|
122
|
+
/// Returns a Hash with key `"models"` containing an Array of model Hashes.
|
|
123
|
+
fn list_supported_models() -> Result<magnus::Value, Error> {
|
|
124
|
+
let ruby = Ruby::get().expect("called outside Ruby");
|
|
125
|
+
let response = hf_cache::list_supported_hf_models();
|
|
126
|
+
to_ruby_value(&ruby, &response)
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/// `Onde.delete_model(model_id)` → nil
|
|
130
|
+
///
|
|
131
|
+
/// Delete a locally cached HuggingFace model.
|
|
132
|
+
/// `model_id` is the full identifier, e.g. `"black-forest-labs/FLUX.1-schnell"`.
|
|
133
|
+
///
|
|
134
|
+
/// Raises `RuntimeError` if the model is not found or deletion fails.
|
|
135
|
+
fn delete_model(model_id: String) -> Result<magnus::Value, Error> {
|
|
136
|
+
let ruby = Ruby::get().expect("called outside Ruby");
|
|
137
|
+
hf_cache::delete_local_hf_model(model_id)
|
|
138
|
+
.map_err(|e| Error::new(ruby.exception_runtime_error(), e))?;
|
|
139
|
+
Ok(ruby.qnil().as_value())
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/// `Onde.model_info(model_id)` → Hash or nil
|
|
143
|
+
///
|
|
144
|
+
/// Look up rich metadata for a supported model by its ID.
|
|
145
|
+
/// Returns `nil` if the model ID is not in the supported list.
|
|
146
|
+
fn model_info(model_id: String) -> Result<magnus::Value, Error> {
|
|
147
|
+
let ruby = Ruby::get().expect("called outside Ruby");
|
|
148
|
+
|
|
149
|
+
let info = SUPPORTED_MODEL_INFO.iter().find(|i| i.id == model_id);
|
|
150
|
+
|
|
151
|
+
match info {
|
|
152
|
+
None => Ok(ruby.qnil().as_value()),
|
|
153
|
+
Some(i) => {
|
|
154
|
+
let hash = ruby.hash_new();
|
|
155
|
+
hash.aset(ruby.str_new("id"), ruby.str_new(i.id))?;
|
|
156
|
+
hash.aset(ruby.str_new("name"), ruby.str_new(i.name))?;
|
|
157
|
+
hash.aset(ruby.str_new("org"), ruby.str_new(i.org))?;
|
|
158
|
+
hash.aset(ruby.str_new("description"), ruby.str_new(i.description))?;
|
|
159
|
+
hash.aset(
|
|
160
|
+
ruby.str_new("expected_size_bytes"),
|
|
161
|
+
ruby.integer_from_u64(i.expected_size_bytes),
|
|
162
|
+
)?;
|
|
163
|
+
Ok(hash.as_value())
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/// `Onde.supported_model_ids` → Array of String
|
|
169
|
+
///
|
|
170
|
+
/// Returns the list of all supported model IDs.
|
|
171
|
+
fn supported_model_ids() -> Result<magnus::Value, Error> {
|
|
172
|
+
let ruby = Ruby::get().expect("called outside Ruby");
|
|
173
|
+
let ary = ruby.ary_new_capa(SUPPORTED_MODELS.len());
|
|
174
|
+
for id in SUPPORTED_MODELS {
|
|
175
|
+
ary.push(ruby.str_new(id))?;
|
|
176
|
+
}
|
|
177
|
+
Ok(ary.as_value())
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/// `Onde.default_sampling_config` → Hash
|
|
181
|
+
fn default_sampling_config() -> Result<magnus::Value, Error> {
|
|
182
|
+
let ruby = Ruby::get().expect("called outside Ruby");
|
|
183
|
+
to_ruby_value(&ruby, &SamplingConfig::default())
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
/// `Onde.deterministic_sampling_config` → Hash
|
|
187
|
+
fn deterministic_sampling_config() -> Result<magnus::Value, Error> {
|
|
188
|
+
let ruby = Ruby::get().expect("called outside Ruby");
|
|
189
|
+
to_ruby_value(&ruby, &SamplingConfig::deterministic())
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/// `Onde.mobile_sampling_config` → Hash
|
|
193
|
+
fn mobile_sampling_config() -> Result<magnus::Value, Error> {
|
|
194
|
+
let ruby = Ruby::get().expect("called outside Ruby");
|
|
195
|
+
to_ruby_value(&ruby, &SamplingConfig::mobile())
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/// `Onde.cache_path` → String or nil
|
|
199
|
+
///
|
|
200
|
+
/// Returns the resolved HuggingFace cache directory path, or nil if it
|
|
201
|
+
/// cannot be determined (e.g. `$HOME` is unset).
|
|
202
|
+
fn cache_path() -> Result<magnus::Value, Error> {
|
|
203
|
+
let ruby = Ruby::get().expect("called outside Ruby");
|
|
204
|
+
let response = hf_cache::list_local_hf_models();
|
|
205
|
+
if response.cache_path.is_empty() {
|
|
206
|
+
Ok(ruby.qnil().as_value())
|
|
207
|
+
} else {
|
|
208
|
+
Ok(ruby.str_new(&response.cache_path).as_value())
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// ---------------------------------------------------------------------------
|
|
213
|
+
// Init — called by Ruby when `require "onde/onde"` loads the shared library
|
|
214
|
+
// ---------------------------------------------------------------------------
|
|
215
|
+
|
|
216
|
+
#[magnus::init]
|
|
217
|
+
fn init(ruby: &Ruby) -> Result<(), Error> {
|
|
218
|
+
let module = ruby.define_module("Onde")?;
|
|
219
|
+
|
|
220
|
+
// -- Singleton methods ----------------------------------------------------
|
|
221
|
+
module.define_singleton_method("list_local_models", function!(list_local_models, 0))?;
|
|
222
|
+
module.define_singleton_method("list_supported_models", function!(list_supported_models, 0))?;
|
|
223
|
+
module.define_singleton_method("delete_model", function!(delete_model, 1))?;
|
|
224
|
+
module.define_singleton_method("model_info", function!(model_info, 1))?;
|
|
225
|
+
module.define_singleton_method("supported_model_ids", function!(supported_model_ids, 0))?;
|
|
226
|
+
module.define_singleton_method(
|
|
227
|
+
"default_sampling_config",
|
|
228
|
+
function!(default_sampling_config, 0),
|
|
229
|
+
)?;
|
|
230
|
+
module.define_singleton_method(
|
|
231
|
+
"deterministic_sampling_config",
|
|
232
|
+
function!(deterministic_sampling_config, 0),
|
|
233
|
+
)?;
|
|
234
|
+
module.define_singleton_method(
|
|
235
|
+
"mobile_sampling_config",
|
|
236
|
+
function!(mobile_sampling_config, 0),
|
|
237
|
+
)?;
|
|
238
|
+
module.define_singleton_method("cache_path", function!(cache_path, 0))?;
|
|
239
|
+
|
|
240
|
+
// -- Constants ------------------------------------------------------------
|
|
241
|
+
|
|
242
|
+
// Onde::NATIVE_VERSION (Rust crate version for parity checks).
|
|
243
|
+
module.const_set("NATIVE_VERSION", ruby.str_new(env!("CARGO_PKG_VERSION")))?;
|
|
244
|
+
|
|
245
|
+
// Onde::SUPPORTED_MODELS — frozen Array of model ID strings.
|
|
246
|
+
let model_ids = ruby.ary_new_capa(SUPPORTED_MODELS.len());
|
|
247
|
+
for id in SUPPORTED_MODELS {
|
|
248
|
+
model_ids.push(ruby.str_new(id))?;
|
|
249
|
+
}
|
|
250
|
+
model_ids.freeze();
|
|
251
|
+
module.const_set("SUPPORTED_MODELS", model_ids)?;
|
|
252
|
+
|
|
253
|
+
// Onde::SUPPORTED_MODEL_INFO — frozen Array of frozen Hashes.
|
|
254
|
+
let info_ary = ruby.ary_new_capa(SUPPORTED_MODEL_INFO.len());
|
|
255
|
+
for info in SUPPORTED_MODEL_INFO {
|
|
256
|
+
let hash: RHash = ruby.hash_new();
|
|
257
|
+
hash.aset(ruby.str_new("id"), ruby.str_new(info.id))?;
|
|
258
|
+
hash.aset(ruby.str_new("name"), ruby.str_new(info.name))?;
|
|
259
|
+
hash.aset(ruby.str_new("org"), ruby.str_new(info.org))?;
|
|
260
|
+
hash.aset(ruby.str_new("description"), ruby.str_new(info.description))?;
|
|
261
|
+
hash.aset(
|
|
262
|
+
ruby.str_new("expected_size_bytes"),
|
|
263
|
+
ruby.integer_from_u64(info.expected_size_bytes),
|
|
264
|
+
)?;
|
|
265
|
+
hash.freeze();
|
|
266
|
+
info_ary.push(hash)?;
|
|
267
|
+
}
|
|
268
|
+
info_ary.freeze();
|
|
269
|
+
module.const_set("SUPPORTED_MODEL_INFO", info_ary)?;
|
|
270
|
+
|
|
271
|
+
Ok(())
|
|
272
|
+
}
|
data/lib/onde/version.rb
ADDED
data/lib/onde.rb
ADDED
data/rust-toolchain.toml
ADDED
metadata
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: onde-inference
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Seto Elkahfi
|
|
8
|
+
bindir: exe
|
|
9
|
+
cert_chain: []
|
|
10
|
+
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
+
dependencies: []
|
|
12
|
+
description: Ruby bindings for the Onde inference engine. Run LLMs and speech-to-text
|
|
13
|
+
locally with automatic HuggingFace model management, cache inspection, and GPU acceleration.
|
|
14
|
+
email:
|
|
15
|
+
- setoelkahfi@gmail.com
|
|
16
|
+
executables: []
|
|
17
|
+
extensions:
|
|
18
|
+
- ext/onde-ruby/Cargo.toml
|
|
19
|
+
extra_rdoc_files: []
|
|
20
|
+
files:
|
|
21
|
+
- Cargo.lock
|
|
22
|
+
- Cargo.toml
|
|
23
|
+
- README.md
|
|
24
|
+
- ext/onde-ruby/Cargo.lock
|
|
25
|
+
- ext/onde-ruby/Cargo.toml
|
|
26
|
+
- ext/onde-ruby/extconf.rb
|
|
27
|
+
- ext/onde-ruby/src/lib.rs
|
|
28
|
+
- lib/onde.rb
|
|
29
|
+
- lib/onde/version.rb
|
|
30
|
+
- rust-toolchain.toml
|
|
31
|
+
homepage: https://ondeinference.com
|
|
32
|
+
licenses:
|
|
33
|
+
- MIT
|
|
34
|
+
metadata:
|
|
35
|
+
homepage_uri: https://ondeinference.com
|
|
36
|
+
source_code_uri: https://github.com/ondeinference/onde
|
|
37
|
+
changelog_uri: https://github.com/ondeinference/onde/blob/main/CHANGELOG.md
|
|
38
|
+
cargo_crate_name: onde-ruby
|
|
39
|
+
rdoc_options: []
|
|
40
|
+
require_paths:
|
|
41
|
+
- lib
|
|
42
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
43
|
+
requirements:
|
|
44
|
+
- - ">="
|
|
45
|
+
- !ruby/object:Gem::Version
|
|
46
|
+
version: 3.0.0
|
|
47
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
48
|
+
requirements:
|
|
49
|
+
- - ">="
|
|
50
|
+
- !ruby/object:Gem::Version
|
|
51
|
+
version: 3.3.11
|
|
52
|
+
requirements: []
|
|
53
|
+
rubygems_version: 3.7.2
|
|
54
|
+
specification_version: 4
|
|
55
|
+
summary: On-device AI inference for Ruby — powered by Rust.
|
|
56
|
+
test_files: []
|