red-candle 1.8.0.pre3-aarch64-linux

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.
Files changed (76) hide show
  1. checksums.yaml +7 -0
  2. data/Cargo.lock +5021 -0
  3. data/Cargo.toml +6 -0
  4. data/Gemfile +3 -0
  5. data/LICENSE +22 -0
  6. data/README.md +1171 -0
  7. data/Rakefile +167 -0
  8. data/bin/console +11 -0
  9. data/bin/setup +17 -0
  10. data/ext/candle/Cargo.toml +38 -0
  11. data/ext/candle/build.rs +117 -0
  12. data/ext/candle/extconf.rb +79 -0
  13. data/ext/candle/rustfmt.toml +63 -0
  14. data/ext/candle/src/gvl.rs +58 -0
  15. data/ext/candle/src/lib.rs +59 -0
  16. data/ext/candle/src/llm/constrained_generation_test.rs +395 -0
  17. data/ext/candle/src/llm/gemma.rs +313 -0
  18. data/ext/candle/src/llm/generation_config.rs +63 -0
  19. data/ext/candle/src/llm/glm4.rs +236 -0
  20. data/ext/candle/src/llm/granite.rs +308 -0
  21. data/ext/candle/src/llm/granitemoehybrid.rs +315 -0
  22. data/ext/candle/src/llm/llama.rs +396 -0
  23. data/ext/candle/src/llm/mistral.rs +309 -0
  24. data/ext/candle/src/llm/mod.rs +49 -0
  25. data/ext/candle/src/llm/phi.rs +369 -0
  26. data/ext/candle/src/llm/quantized_gguf.rs +734 -0
  27. data/ext/candle/src/llm/qwen.rs +261 -0
  28. data/ext/candle/src/llm/qwen3.rs +257 -0
  29. data/ext/candle/src/llm/text_generation.rs +284 -0
  30. data/ext/candle/src/ruby/device.rs +234 -0
  31. data/ext/candle/src/ruby/dtype.rs +39 -0
  32. data/ext/candle/src/ruby/embedding_model.rs +477 -0
  33. data/ext/candle/src/ruby/errors.rs +16 -0
  34. data/ext/candle/src/ruby/llm.rs +730 -0
  35. data/ext/candle/src/ruby/mod.rs +24 -0
  36. data/ext/candle/src/ruby/ner.rs +444 -0
  37. data/ext/candle/src/ruby/reranker.rs +488 -0
  38. data/ext/candle/src/ruby/result.rs +3 -0
  39. data/ext/candle/src/ruby/structured.rs +92 -0
  40. data/ext/candle/src/ruby/tensor.rs +731 -0
  41. data/ext/candle/src/ruby/tokenizer.rs +343 -0
  42. data/ext/candle/src/ruby/utils.rs +96 -0
  43. data/ext/candle/src/ruby/vlm.rs +330 -0
  44. data/ext/candle/src/structured/integration_test.rs +130 -0
  45. data/ext/candle/src/structured/mod.rs +31 -0
  46. data/ext/candle/src/structured/schema_processor.rs +215 -0
  47. data/ext/candle/src/structured/vocabulary_adapter.rs +152 -0
  48. data/ext/candle/src/structured/vocabulary_adapter_real_test.rs +66 -0
  49. data/ext/candle/src/structured/vocabulary_adapter_simple_test.rs +70 -0
  50. data/ext/candle/src/tokenizer/loader.rs +108 -0
  51. data/ext/candle/src/tokenizer/mod.rs +104 -0
  52. data/ext/candle/tests/device_tests.rs +43 -0
  53. data/ext/candle/tests/tensor_tests.rs +162 -0
  54. data/lib/candle/3.1/candle.so +0 -0
  55. data/lib/candle/3.2/candle.so +0 -0
  56. data/lib/candle/3.3/candle.so +0 -0
  57. data/lib/candle/3.4/candle.so +0 -0
  58. data/lib/candle/4.0/candle.so +0 -0
  59. data/lib/candle/agent.rb +68 -0
  60. data/lib/candle/build_info.rb +67 -0
  61. data/lib/candle/device_utils.rb +10 -0
  62. data/lib/candle/embedding_model.rb +75 -0
  63. data/lib/candle/embedding_model_type.rb +31 -0
  64. data/lib/candle/llm.rb +595 -0
  65. data/lib/candle/logger.rb +149 -0
  66. data/lib/candle/ner.rb +368 -0
  67. data/lib/candle/reranker.rb +45 -0
  68. data/lib/candle/tensor.rb +99 -0
  69. data/lib/candle/tokenizer.rb +139 -0
  70. data/lib/candle/tool.rb +47 -0
  71. data/lib/candle/tool_call_parser.rb +57 -0
  72. data/lib/candle/version.rb +5 -0
  73. data/lib/candle/vlm.rb +31 -0
  74. data/lib/candle.rb +29 -0
  75. data/lib/red-candle.rb +1 -0
  76. metadata +309 -0
data/Rakefile ADDED
@@ -0,0 +1,167 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "bundler/gem_tasks"
4
+ require "rake/extensiontask"
5
+ require "rspec/core/rake_task"
6
+
7
+ task default: :spec
8
+
9
+ spec = Bundler.load_gemspec("candle.gemspec")
10
+ Rake::ExtensionTask.new("candle", spec) do |c|
11
+ c.lib_dir = "lib/candle"
12
+ c.cross_compile = true
13
+ c.cross_platform = %w[
14
+ aarch64-linux
15
+ arm64-darwin
16
+ x64-mingw-ucrt
17
+ x64-mingw32
18
+ x86_64-darwin
19
+ x86_64-linux
20
+ x86_64-linux-musl
21
+ ]
22
+ end
23
+
24
+
25
+ namespace :doc do
26
+ task default: %i[rustdoc yard]
27
+
28
+ desc "Generate YARD documentation"
29
+ task :yard do
30
+ sh <<~CMD
31
+ yard doc \
32
+ --plugin rustdoc -- lib tmp/doc/candle.json
33
+ CMD
34
+ end
35
+
36
+ desc "Generate Rust documentation as JSON"
37
+ task :rustdoc do
38
+ sh <<~CMD
39
+ cargo +nightly rustdoc \
40
+ --target-dir tmp/doc/target \
41
+ -p candle \
42
+ -- -Zunstable-options --output-format json \
43
+ --document-private-items
44
+ CMD
45
+
46
+ cp "tmp/doc/target/doc/candle.json", "tmp/doc/candle.json"
47
+ end
48
+ end
49
+
50
+ task doc: "doc:default"
51
+
52
+ namespace :rust do
53
+ desc "Run Rust tests with code coverage"
54
+ namespace :coverage do
55
+ desc "Generate HTML coverage report"
56
+ task :html do
57
+ sh "cd ext/candle && cargo llvm-cov --html"
58
+ puts "Coverage report generated in target/llvm-cov/html/index.html"
59
+ end
60
+
61
+ desc "Generate coverage report in terminal"
62
+ task :report do
63
+ sh "cd ext/candle && cargo llvm-cov"
64
+ end
65
+
66
+ desc "Show coverage summary"
67
+ task :summary do
68
+ sh "cd ext/candle && cargo llvm-cov --summary-only"
69
+ end
70
+
71
+ desc "Generate lcov format coverage report"
72
+ task :lcov do
73
+ sh "cd ext/candle && cargo llvm-cov --lcov --output-path ../../coverage/lcov.info"
74
+ puts "LCOV report generated in coverage/lcov.info"
75
+ end
76
+
77
+ desc "Clean coverage data"
78
+ task :clean do
79
+ sh "cd ext/candle && cargo llvm-cov clean"
80
+ end
81
+ end
82
+
83
+ desc "Run Rust tests"
84
+ task :test do
85
+ sh "cd ext/candle && cargo test"
86
+ end
87
+ end
88
+
89
+ desc "Run Rust tests with coverage (alias)"
90
+ task "coverage:rust" => "rust:coverage:html"
91
+
92
+ # RSpec tasks
93
+ desc "Run RSpec tests"
94
+ RSpec::Core::RakeTask.new(:spec) do |t|
95
+ t.rspec_opts = "--format progress"
96
+ end
97
+
98
+ # Add compile as a dependency for spec task
99
+ task spec: :compile
100
+
101
+ namespace :spec do
102
+ desc "Run RSpec tests with all devices"
103
+ RSpec::Core::RakeTask.new(:device) do |t|
104
+ t.rspec_opts = "--format documentation --tag device"
105
+ end
106
+
107
+ desc "Run RSpec tests with coverage"
108
+ task :coverage do
109
+ ENV['COVERAGE'] = 'true'
110
+ Rake::Task["spec"].invoke
111
+ end
112
+
113
+ desc "Run RSpec tests in parallel (requires parallel_tests gem)"
114
+ task :parallel do
115
+ begin
116
+ require 'parallel_tests'
117
+ sh "parallel_rspec spec/"
118
+ rescue LoadError
119
+ puts "parallel_tests gem not installed. Run: gem install parallel_tests"
120
+ end
121
+ end
122
+
123
+ desc "Run specific device tests"
124
+ %w[cpu metal cuda].each do |device|
125
+ desc "Run tests on #{device.upcase} only"
126
+ task "device:#{device}" => :compile do
127
+ ENV['CANDLE_TEST_DEVICES'] = device
128
+ sh "rspec spec/device_compatibility_spec.rb --format documentation"
129
+ end
130
+ end
131
+
132
+ desc "Run LLM tests for specific models"
133
+ namespace :llm do
134
+ desc "Run tests for Gemma models"
135
+ task :gemma => :compile do
136
+ sh "rspec spec/llm/gemma_spec.rb --format documentation"
137
+ end
138
+
139
+ desc "Run tests for Phi models"
140
+ task :phi => :compile do
141
+ sh "rspec spec/llm/phi_spec.rb --format documentation"
142
+ end
143
+
144
+ desc "Run tests for Qwen models"
145
+ task :qwen => :compile do
146
+ sh "rspec spec/llm/qwen_spec.rb --format documentation"
147
+ end
148
+
149
+ desc "Run tests for Mistral models"
150
+ task :mistral => :compile do
151
+ sh "rspec spec/llm/mistral_spec.rb --format documentation"
152
+ end
153
+
154
+ desc "Run tests for Llama models"
155
+ task :llama => :compile do
156
+ sh "rspec spec/llm/llama_spec.rb --format documentation"
157
+ end
158
+
159
+ desc "Run tests for TinyLlama models"
160
+ task :tinyllama => :compile do
161
+ sh "rspec spec/llm/tinyllama_spec.rb --format documentation"
162
+ end
163
+
164
+ desc "Run all LLM tests (WARNING: requires large models already downloaded)"
165
+ task :all => [:gemma, :phi, :qwen, :mistral, :llama, :tinyllama]
166
+ end
167
+ end
data/bin/console ADDED
@@ -0,0 +1,11 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "bundler/setup"
5
+ require "candle"
6
+
7
+ # You can add fixtures and/or initialization code here to make experimenting
8
+ # with your gem easier. You can also use a different console, if you like.
9
+
10
+ require "irb"
11
+ IRB.start(__FILE__)
data/bin/setup ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+ IFS=$'\n\t'
4
+ set -vx
5
+
6
+ # Check if Rust is installed
7
+ if ! command -v cargo &> /dev/null
8
+ then
9
+ echo "Rust is not installed. Please install Rust: https://www.rust-lang.org/tools/install"
10
+ exit 1
11
+ fi
12
+
13
+ # Install Ruby dependencies
14
+ bundle install
15
+
16
+ # Build Rust extension
17
+ bundle exec rake compile
@@ -0,0 +1,38 @@
1
+ [package]
2
+ name = "candle"
3
+ version = "0.1.0"
4
+ edition = "2021"
5
+ build = "build.rs"
6
+ rust-version = "1.85"
7
+
8
+ [lib]
9
+ crate-type = ["cdylib"]
10
+
11
+ [dependencies]
12
+ candle-core = { version = "0.9.2" }
13
+ candle-nn = { version = "0.9.2" }
14
+ candle-transformers = { version = "0.9.2" }
15
+ tokenizers = { version = "0.22.0", default-features = true, features = ["fancy-regex"] }
16
+ # Drop hf-hub's default `default-tls` (= native-tls = openssl-sys), which can't
17
+ # cross-compile to aarch64-linux (no OpenSSL headers in the cross sysroot). Keep
18
+ # both API surfaces red-candle uses — `tokio` (async, via reqwest) and `ureq`
19
+ # (sync) — and route reqwest through rustls (`rustls-tls`); ureq defaults to
20
+ # rustls+ring. This removes openssl-sys from every platform's build.
21
+ hf-hub = { version = "0.4.1", default-features = false, features = ["tokio", "ureq", "rustls-tls"] }
22
+ half = "2.6.0"
23
+ magnus = "0.8"
24
+ safetensors = "0.3"
25
+ serde_json = "1.0"
26
+ serde = { version = "1.0", features = ["derive"] }
27
+ tokio = { version = "1.45", features = ["rt", "macros"] }
28
+ rand = "0.10"
29
+ outlines-core = "0.2.11"
30
+ image = "0.25"
31
+
32
+ [features]
33
+ default = []
34
+ metal = ["candle-core/metal", "candle-nn/metal", "candle-transformers/metal"]
35
+ cuda = ["candle-core/cuda", "candle-nn/cuda", "candle-transformers/cuda"]
36
+ cudnn = ["candle-core/cudnn", "cuda"]
37
+ mkl = ["candle-core/mkl"]
38
+ accelerate = ["candle-core/accelerate"]
@@ -0,0 +1,117 @@
1
+ use std::env;
2
+ use std::path::Path;
3
+
4
+ fn main() {
5
+ // Register our custom cfg flags with rustc
6
+ println!("cargo::rustc-check-cfg=cfg(force_cpu)");
7
+ println!("cargo::rustc-check-cfg=cfg(has_cuda)");
8
+ println!("cargo::rustc-check-cfg=cfg(has_metal)");
9
+ println!("cargo::rustc-check-cfg=cfg(has_mkl)");
10
+ println!("cargo::rustc-check-cfg=cfg(has_accelerate)");
11
+
12
+ println!("cargo:rerun-if-changed=build.rs");
13
+ println!("cargo:rerun-if-env-changed=CANDLE_FORCE_CPU");
14
+ println!("cargo:rerun-if-env-changed=CANDLE_CUDA_PATH");
15
+ println!("cargo:rerun-if-env-changed=CUDA_ROOT");
16
+ println!("cargo:rerun-if-env-changed=CUDA_PATH");
17
+ println!("cargo:rerun-if-env-changed=CANDLE_FEATURES");
18
+ println!("cargo:rerun-if-env-changed=CANDLE_ENABLE_CUDA");
19
+ println!("cargo:rerun-if-env-changed=CANDLE_DISABLE_CUDA");
20
+
21
+ // Check if we should force CPU only
22
+ if env::var("CANDLE_FORCE_CPU").is_ok() {
23
+ println!("cargo:rustc-cfg=force_cpu");
24
+ println!("cargo:warning=CANDLE_FORCE_CPU is set, disabling all acceleration");
25
+ return;
26
+ }
27
+
28
+ // Detect CUDA availability
29
+ let cuda_available = detect_cuda();
30
+ let cuda_disabled = env::var("CANDLE_DISABLE_CUDA").is_ok();
31
+
32
+ if cuda_available && !cuda_disabled {
33
+ println!("cargo:rustc-cfg=has_cuda");
34
+ println!("cargo:warning=CUDA detected, CUDA acceleration will be available");
35
+ } else if cuda_available && cuda_disabled {
36
+ println!("cargo:warning=CUDA detected but disabled via CANDLE_DISABLE_CUDA");
37
+ }
38
+
39
+ // Detect Metal availability (macOS only)
40
+ #[cfg(target_os = "macos")]
41
+ {
42
+ println!("cargo:rustc-cfg=has_metal");
43
+ println!("cargo:warning=Metal detected (macOS), Metal acceleration will be available");
44
+ }
45
+
46
+ // Detect MKL availability
47
+ if detect_mkl() {
48
+ println!("cargo:rustc-cfg=has_mkl");
49
+ println!("cargo:warning=Intel MKL detected, MKL acceleration will be available");
50
+ }
51
+
52
+ // Detect Accelerate framework (macOS)
53
+ #[cfg(target_os = "macos")]
54
+ {
55
+ println!("cargo:rustc-cfg=has_accelerate");
56
+ println!("cargo:warning=Accelerate framework detected (macOS)");
57
+ }
58
+ }
59
+
60
+ fn detect_cuda() -> bool {
61
+ // Check environment variables first
62
+ if env::var("CANDLE_CUDA_PATH").is_ok() {
63
+ return true;
64
+ }
65
+
66
+ if env::var("CUDA_ROOT").is_ok() || env::var("CUDA_PATH").is_ok() {
67
+ return true;
68
+ }
69
+
70
+ // Check common CUDA installation paths
71
+ let cuda_paths = [
72
+ "/usr/local/cuda",
73
+ "/opt/cuda",
74
+ "C:\\Program Files\\NVIDIA GPU Computing Toolkit\\CUDA",
75
+ "C:\\CUDA",
76
+ ];
77
+
78
+ for path in &cuda_paths {
79
+ if Path::new(path).exists() {
80
+ return true;
81
+ }
82
+ }
83
+
84
+ // Check if nvcc is in PATH
85
+ if let Ok(path_var) = env::var("PATH") {
86
+ for path in env::split_paths(&path_var) {
87
+ if path.join("nvcc").exists() || path.join("nvcc.exe").exists() {
88
+ return true;
89
+ }
90
+ }
91
+ }
92
+
93
+ false
94
+ }
95
+
96
+ fn detect_mkl() -> bool {
97
+ // Check environment variables
98
+ if env::var("MKLROOT").is_ok() || env::var("MKL_ROOT").is_ok() {
99
+ return true;
100
+ }
101
+
102
+ // Check common MKL installation paths
103
+ let mkl_paths = [
104
+ "/opt/intel/mkl",
105
+ "/opt/intel/oneapi/mkl/latest",
106
+ "C:\\Program Files (x86)\\Intel\\oneAPI\\mkl\\latest",
107
+ "C:\\Program Files\\Intel\\oneAPI\\mkl\\latest",
108
+ ];
109
+
110
+ for path in &mkl_paths {
111
+ if Path::new(path).exists() {
112
+ return true;
113
+ }
114
+ }
115
+
116
+ false
117
+ }
@@ -0,0 +1,79 @@
1
+ require "mkmf"
2
+ require "rb_sys/mkmf"
3
+
4
+ # Detect available hardware acceleration
5
+ features = []
6
+
7
+ # Force CPU-only build if requested
8
+ if ENV['CANDLE_FORCE_CPU']
9
+ puts "CANDLE_FORCE_CPU is set, building CPU-only version"
10
+ else
11
+ # Check for CUDA
12
+ cuda_available = ENV['CUDA_ROOT'] || ENV['CUDA_PATH'] || ENV['CANDLE_CUDA_PATH'] ||
13
+ File.exist?('/usr/local/cuda') || File.exist?('/opt/cuda') ||
14
+ (RbConfig::CONFIG['host_os'] =~ /mswin|mingw|cygwin/ &&
15
+ (File.exist?('C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA') ||
16
+ File.exist?('C:\CUDA')))
17
+
18
+ cuda_disabled = ENV['CANDLE_DISABLE_CUDA']
19
+
20
+ if cuda_available && !cuda_disabled
21
+ puts "CUDA detected, enabling CUDA support"
22
+ features << 'cuda'
23
+
24
+ # Check if CUDNN should be enabled
25
+ if ENV['CANDLE_CUDNN'] || ENV['CUDNN_ROOT']
26
+ puts "CUDNN support enabled"
27
+ features << 'cudnn'
28
+ end
29
+ elsif cuda_available && cuda_disabled
30
+ puts "=" * 80
31
+ puts "CUDA detected but disabled via CANDLE_DISABLE_CUDA"
32
+ puts "=" * 80
33
+ end
34
+
35
+ # Check for Metal (macOS only)
36
+ if RbConfig::CONFIG['host_os'] =~ /darwin/
37
+ puts "macOS detected, enabling Metal support"
38
+ features << 'metal'
39
+
40
+ # Also enable Accelerate framework on macOS
41
+ puts "Enabling Accelerate framework support"
42
+ features << 'accelerate'
43
+ end
44
+
45
+ # Check for Intel MKL
46
+ mkl_available = ENV['MKLROOT'] || ENV['MKL_ROOT'] ||
47
+ File.exist?('/opt/intel/mkl') ||
48
+ File.exist?('/opt/intel/oneapi/mkl/latest')
49
+
50
+ if mkl_available && !features.include?('accelerate') # Don't use both MKL and Accelerate
51
+ puts "Intel MKL detected, enabling MKL support"
52
+ features << 'mkl'
53
+ end
54
+ end
55
+
56
+ # Allow manual override of features
57
+ if ENV['CANDLE_FEATURES']
58
+ manual_features = ENV['CANDLE_FEATURES'].split(',').map(&:strip)
59
+ puts "Manual features override: #{manual_features.join(', ')}"
60
+ features = manual_features
61
+ end
62
+
63
+ # Display selected features
64
+ unless features.empty?
65
+ puts "Building with features: #{features.join(', ')}"
66
+ else
67
+ puts "Building CPU-only version (no acceleration features detected)"
68
+ end
69
+
70
+ # Create the Rust makefile with proper feature configuration
71
+ create_rust_makefile("candle/candle") do |r|
72
+ # Pass the features to rb_sys
73
+ r.features = features unless features.empty?
74
+
75
+ # Pass through any additional cargo flags
76
+ if ENV['CANDLE_CARGO_FLAGS']
77
+ r.extra_cargo_args = ENV['CANDLE_CARGO_FLAGS'].split(' ')
78
+ end
79
+ end
@@ -0,0 +1,63 @@
1
+ max_width = 100
2
+ hard_tabs = false
3
+ tab_spaces = 4
4
+ newline_style = "Auto"
5
+ use_small_heuristics = "Default"
6
+ indent_style = "Block"
7
+ wrap_comments = false
8
+ format_code_in_doc_comments = false
9
+ comment_width = 80
10
+ normalize_comments = false
11
+ normalize_doc_attributes = false
12
+ format_strings = false
13
+ format_macro_matchers = false
14
+ format_macro_bodies = true
15
+ empty_item_single_line = true
16
+ struct_lit_single_line = true
17
+ fn_single_line = false
18
+ where_single_line = false
19
+ imports_indent = "Block"
20
+ imports_layout = "Mixed"
21
+ imports_granularity="Crate"
22
+ reorder_imports = true
23
+ reorder_modules = true
24
+ reorder_impl_items = false
25
+ type_punctuation_density = "Wide"
26
+ space_before_colon = false
27
+ space_after_colon = true
28
+ spaces_around_ranges = false
29
+ binop_separator = "Front"
30
+ remove_nested_parens = true
31
+ combine_control_expr = true
32
+ overflow_delimited_expr = false
33
+ struct_field_align_threshold = 0
34
+ enum_discrim_align_threshold = 0
35
+ match_arm_blocks = true
36
+ force_multiline_blocks = false
37
+ fn_params_layout = "Tall"
38
+ brace_style = "SameLineWhere"
39
+ control_brace_style = "AlwaysSameLine"
40
+ trailing_semicolon = true
41
+ trailing_comma = "Vertical"
42
+ match_block_trailing_comma = false
43
+ blank_lines_upper_bound = 1
44
+ blank_lines_lower_bound = 0
45
+ edition = "2021"
46
+ version = "One"
47
+ inline_attribute_width = 0
48
+ merge_derives = true
49
+ use_try_shorthand = false
50
+ use_field_init_shorthand = false
51
+ force_explicit_abi = true
52
+ condense_wildcard_suffixes = false
53
+ color = "Auto"
54
+ #required_version = "1.4.12"
55
+ unstable_features = false
56
+ disable_all_formatting = false
57
+ skip_children = false
58
+ hide_parse_errors = false
59
+ error_on_line_overflow = false
60
+ error_on_unformatted = false
61
+ ignore = []
62
+ emit_mode = "Files"
63
+ make_backup = false
@@ -0,0 +1,58 @@
1
+ /// GVL (Global VM Lock) release support for Ruby.
2
+ ///
3
+ /// Ruby's GVL prevents other Ruby threads from running while native code
4
+ /// executes. For long-running operations (LLM inference, reranking, embedding),
5
+ /// we release the GVL so other threads (TUI render loops, HTTP servers, etc.)
6
+ /// can run concurrently.
7
+ ///
8
+ /// SAFETY: Code running without the GVL must NOT call any Ruby API.
9
+
10
+ use std::os::raw::c_void;
11
+
12
+ type UnblockFn = unsafe extern "C" fn(*mut c_void);
13
+
14
+ extern "C" {
15
+ fn rb_thread_call_without_gvl(
16
+ func: unsafe extern "C" fn(*mut c_void) -> *mut c_void,
17
+ data1: *mut c_void,
18
+ ubf: Option<UnblockFn>,
19
+ data2: *mut c_void,
20
+ ) -> *mut c_void;
21
+ }
22
+
23
+ /// Run a closure without the GVL. The closure must not call any Ruby API.
24
+ pub fn without_gvl<F, R>(f: F) -> R
25
+ where
26
+ F: FnOnce() -> R,
27
+ {
28
+ struct CallData<F, R> {
29
+ func: Option<F>,
30
+ result: Option<R>,
31
+ }
32
+
33
+ unsafe extern "C" fn call_func<F, R>(data: *mut c_void) -> *mut c_void
34
+ where
35
+ F: FnOnce() -> R,
36
+ {
37
+ let data = &mut *(data as *mut CallData<F, R>);
38
+ let func = data.func.take().unwrap();
39
+ data.result = Some(func());
40
+ std::ptr::null_mut()
41
+ }
42
+
43
+ let mut data = CallData {
44
+ func: Some(f),
45
+ result: None,
46
+ };
47
+
48
+ unsafe {
49
+ rb_thread_call_without_gvl(
50
+ call_func::<F, R>,
51
+ &mut data as *mut _ as *mut c_void,
52
+ None,
53
+ std::ptr::null_mut(),
54
+ );
55
+ }
56
+
57
+ data.result.unwrap()
58
+ }
@@ -0,0 +1,59 @@
1
+ use magnus::{function, prelude::*, Ruby};
2
+
3
+ use crate::ruby::candle_utils;
4
+ use crate::ruby::utils::ensure_hf_cache_dir;
5
+ use crate::ruby::Result;
6
+
7
+ pub mod gvl;
8
+ pub mod llm;
9
+ pub mod ruby;
10
+ pub mod structured;
11
+ pub mod tokenizer;
12
+
13
+ // Configuration detection from build.rs
14
+ #[cfg(all(has_metal, not(force_cpu)))]
15
+ const DEFAULT_DEVICE: &str = "metal";
16
+
17
+ #[cfg(all(has_cuda, not(has_metal), not(force_cpu)))]
18
+ const DEFAULT_DEVICE: &str = "cuda";
19
+
20
+ #[cfg(any(force_cpu, not(any(has_metal, has_cuda))))]
21
+ const DEFAULT_DEVICE: &str = "cpu";
22
+
23
+ // Export build configuration for runtime checks
24
+ pub fn get_build_info() -> magnus::RHash {
25
+ let ruby = magnus::Ruby::get().unwrap();
26
+ let hash = ruby.hash_new();
27
+
28
+ let _ = hash.aset("default_device", DEFAULT_DEVICE);
29
+ let _ = hash.aset("cuda_available", cfg!(feature = "cuda"));
30
+ let _ = hash.aset("metal_available", cfg!(feature = "metal"));
31
+ let _ = hash.aset("mkl_available", cfg!(feature = "mkl"));
32
+ let _ = hash.aset("accelerate_available", cfg!(feature = "accelerate"));
33
+ let _ = hash.aset("cudnn_available", cfg!(feature = "cudnn"));
34
+
35
+ hash
36
+ }
37
+
38
+ #[magnus::init]
39
+ fn init(ruby: &Ruby) -> Result<()> {
40
+ ensure_hf_cache_dir();
41
+ let rb_candle = ruby.define_module("Candle")?;
42
+
43
+ // Export build info
44
+ rb_candle.define_singleton_method("build_info", function!(get_build_info, 0))?;
45
+
46
+ ruby::init_embedding_model(rb_candle)?;
47
+ ruby::init_llm(rb_candle)?;
48
+ ruby::ner::init(rb_candle)?;
49
+ ruby::reranker::init(rb_candle)?;
50
+ ruby::vlm::init(rb_candle)?;
51
+ ruby::dtype::init(rb_candle)?;
52
+ ruby::device::init(rb_candle)?;
53
+ ruby::tensor::init(rb_candle)?;
54
+ ruby::tokenizer::init(rb_candle)?;
55
+ ruby::structured::init_structured(rb_candle)?;
56
+ candle_utils(rb_candle)?;
57
+
58
+ Ok(())
59
+ }