red-candle 1.8.0.pre2-x86_64-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.
- checksums.yaml +7 -0
- data/Cargo.lock +5193 -0
- data/Cargo.toml +6 -0
- data/Gemfile +3 -0
- data/LICENSE +22 -0
- data/README.md +1171 -0
- data/Rakefile +167 -0
- data/bin/console +11 -0
- data/bin/setup +17 -0
- data/ext/candle/Cargo.toml +33 -0
- data/ext/candle/build.rs +117 -0
- data/ext/candle/extconf.rb +79 -0
- data/ext/candle/rustfmt.toml +63 -0
- data/ext/candle/src/gvl.rs +58 -0
- data/ext/candle/src/lib.rs +59 -0
- data/ext/candle/src/llm/constrained_generation_test.rs +395 -0
- data/ext/candle/src/llm/gemma.rs +313 -0
- data/ext/candle/src/llm/generation_config.rs +63 -0
- data/ext/candle/src/llm/glm4.rs +236 -0
- data/ext/candle/src/llm/granite.rs +308 -0
- data/ext/candle/src/llm/granitemoehybrid.rs +315 -0
- data/ext/candle/src/llm/llama.rs +396 -0
- data/ext/candle/src/llm/mistral.rs +309 -0
- data/ext/candle/src/llm/mod.rs +49 -0
- data/ext/candle/src/llm/phi.rs +369 -0
- data/ext/candle/src/llm/quantized_gguf.rs +734 -0
- data/ext/candle/src/llm/qwen.rs +261 -0
- data/ext/candle/src/llm/qwen3.rs +257 -0
- data/ext/candle/src/llm/text_generation.rs +284 -0
- data/ext/candle/src/ruby/device.rs +234 -0
- data/ext/candle/src/ruby/dtype.rs +39 -0
- data/ext/candle/src/ruby/embedding_model.rs +477 -0
- data/ext/candle/src/ruby/errors.rs +16 -0
- data/ext/candle/src/ruby/llm.rs +730 -0
- data/ext/candle/src/ruby/mod.rs +24 -0
- data/ext/candle/src/ruby/ner.rs +444 -0
- data/ext/candle/src/ruby/reranker.rs +488 -0
- data/ext/candle/src/ruby/result.rs +3 -0
- data/ext/candle/src/ruby/structured.rs +92 -0
- data/ext/candle/src/ruby/tensor.rs +731 -0
- data/ext/candle/src/ruby/tokenizer.rs +343 -0
- data/ext/candle/src/ruby/utils.rs +96 -0
- data/ext/candle/src/ruby/vlm.rs +330 -0
- data/ext/candle/src/structured/integration_test.rs +130 -0
- data/ext/candle/src/structured/mod.rs +31 -0
- data/ext/candle/src/structured/schema_processor.rs +215 -0
- data/ext/candle/src/structured/vocabulary_adapter.rs +152 -0
- data/ext/candle/src/structured/vocabulary_adapter_real_test.rs +66 -0
- data/ext/candle/src/structured/vocabulary_adapter_simple_test.rs +70 -0
- data/ext/candle/src/tokenizer/loader.rs +108 -0
- data/ext/candle/src/tokenizer/mod.rs +104 -0
- data/ext/candle/tests/device_tests.rs +43 -0
- data/ext/candle/tests/tensor_tests.rs +162 -0
- data/lib/candle/3.1/candle.so +0 -0
- data/lib/candle/3.2/candle.so +0 -0
- data/lib/candle/3.3/candle.so +0 -0
- data/lib/candle/3.4/candle.so +0 -0
- data/lib/candle/4.0/candle.so +0 -0
- data/lib/candle/agent.rb +68 -0
- data/lib/candle/build_info.rb +67 -0
- data/lib/candle/device_utils.rb +10 -0
- data/lib/candle/embedding_model.rb +75 -0
- data/lib/candle/embedding_model_type.rb +31 -0
- data/lib/candle/llm.rb +595 -0
- data/lib/candle/logger.rb +149 -0
- data/lib/candle/ner.rb +368 -0
- data/lib/candle/reranker.rb +45 -0
- data/lib/candle/tensor.rb +99 -0
- data/lib/candle/tokenizer.rb +139 -0
- data/lib/candle/tool.rb +47 -0
- data/lib/candle/tool_call_parser.rb +57 -0
- data/lib/candle/version.rb +5 -0
- data/lib/candle/vlm.rb +31 -0
- data/lib/candle.rb +29 -0
- data/lib/red-candle.rb +1 -0
- 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,33 @@
|
|
|
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
|
+
hf-hub = "0.4.1"
|
|
17
|
+
half = "2.6.0"
|
|
18
|
+
magnus = "0.8"
|
|
19
|
+
safetensors = "0.3"
|
|
20
|
+
serde_json = "1.0"
|
|
21
|
+
serde = { version = "1.0", features = ["derive"] }
|
|
22
|
+
tokio = { version = "1.45", features = ["rt", "macros"] }
|
|
23
|
+
rand = "0.10"
|
|
24
|
+
outlines-core = "0.2.11"
|
|
25
|
+
image = "0.25"
|
|
26
|
+
|
|
27
|
+
[features]
|
|
28
|
+
default = []
|
|
29
|
+
metal = ["candle-core/metal", "candle-nn/metal", "candle-transformers/metal"]
|
|
30
|
+
cuda = ["candle-core/cuda", "candle-nn/cuda", "candle-transformers/cuda"]
|
|
31
|
+
cudnn = ["candle-core/cudnn", "cuda"]
|
|
32
|
+
mkl = ["candle-core/mkl"]
|
|
33
|
+
accelerate = ["candle-core/accelerate"]
|
data/ext/candle/build.rs
ADDED
|
@@ -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
|
+
}
|