Commit d253053
Changed files (3)
ext/elelem/diffusion/elelem.cpp
@@ -64,6 +64,25 @@ extern "C" {
return handle;
}
+ EL_API void *el_sd_open_video(const char *diffusion_model_path, const char *vae_path,
+ const char *t5xxl_path, int n_threads) {
+ sd_ctx_params_t params;
+ sd_ctx_params_init(¶ms);
+ params.diffusion_model_path = diffusion_model_path;
+ params.vae_path = vae_path;
+ params.t5xxl_path = t5xxl_path;
+ params.n_threads = n_threads;
+
+ sd_set_log_callback(el_sd_log_cb, nullptr);
+ sd_set_progress_callback(el_sd_progress_cb, nullptr);
+
+ sd_ctx_t *ctx = new_sd_ctx(¶ms);
+ if (!ctx) return nullptr;
+
+ el_sd_handle *handle = new el_sd_handle{ctx};
+ return handle;
+ }
+
EL_API bool el_sd_generate(void *handle_ptr, const char *prompt, const char *negative_prompt,
int width, int height, int steps, int64_t seed, const char *output_path) {
el_sd_handle *handle = static_cast<el_sd_handle *>(handle_ptr);
lib/elelem/diffusion.rb
@@ -28,6 +28,7 @@ module Elelem
lib = Fiddle.dlopen(SHIM)
{
open: Fiddle::Function.new(lib["el_sd_open"], [V, I], V),
+ open_video: Fiddle::Function.new(lib["el_sd_open_video"], [V, V, V, I], V),
generate: Fiddle::Function.new(lib["el_sd_generate"], [V, V, V, I, I, I, L, V], I, need_gvl: false),
img2img: Fiddle::Function.new(lib["el_sd_img2img"], [V, V, V, V, F, I, L, V], I, need_gvl: false),
video: Fiddle::Function.new(lib["el_sd_video"], [V, V, V, I, I, I, L, I, I, V], I, need_gvl: false),
@@ -43,6 +44,13 @@ module Elelem
end
end
+ def self.video_context_for(diffusion_model, vae, t5xxl, n_threads: nil)
+ key = [diffusion_model, vae, t5xxl]
+ MUTEX.synchronize do
+ CONTEXTS[key] ||= functions[:open_video].call(diffusion_model, vae, t5xxl, n_threads || Etc.nprocessors)
+ end
+ end
+
def self.generate(prompt:, path:, negative_prompt: "", steps: 20, width: 512, height: 512, seed: 42, n_threads: nil)
with_context(n_threads: n_threads) do |model, handle|
ok = MUTEX.synchronize do
@@ -63,13 +71,17 @@ module Elelem
def self.video(prompt:, output_dir:, negative_prompt: "", steps: 20, width: 512, height: 512, seed: 42,
video_frames: 16, fps: 8, n_threads: nil)
- with_context(n_threads: n_threads) do |model, handle|
- FileUtils.mkdir_p(output_dir)
- ok = MUTEX.synchronize do
- functions[:video].call(handle, prompt, negative_prompt, width, height, steps, seed, video_frames, fps, output_dir)
- end
- ok.zero? ? { ok: false, error: "video generation failed" } : { ok: true, path: output_dir }
+ diffusion_model = ENV.fetch("ELELEM_SD_VIDEO_MODEL")
+ vae = ENV.fetch("ELELEM_SD_VIDEO_VAE")
+ t5xxl = ENV.fetch("ELELEM_SD_VIDEO_T5XXL")
+ handle = video_context_for(diffusion_model, vae, t5xxl, n_threads: n_threads)
+ return { ok: false, error: "failed to load video model at #{diffusion_model}" } if handle.null?
+
+ FileUtils.mkdir_p(output_dir)
+ ok = MUTEX.synchronize do
+ functions[:video].call(handle, prompt, negative_prompt, width, height, steps, seed, video_frames, fps, output_dir)
end
+ ok.zero? ? { ok: false, error: "video generation failed" } : { ok: true, path: output_dir }
end
def self.upscale(input_path:, path:, n_threads: nil)
spec/elelem/diffusion_spec.rb
@@ -14,6 +14,7 @@ RSpec.describe Elelem::Diffusion do
end
let(:open_fn) { instance_double(Fiddle::Function, call: handle) }
+ let(:open_video_fn) { instance_double(Fiddle::Function, call: handle) }
let(:generate_fn) { instance_double(Fiddle::Function) }
let(:img2img_fn) { instance_double(Fiddle::Function) }
let(:video_fn) { instance_double(Fiddle::Function) }
@@ -23,7 +24,7 @@ RSpec.describe Elelem::Diffusion do
before do
allow(described_class).to receive(:functions).and_return(
- open: open_fn, generate: generate_fn, img2img: img2img_fn,
+ open: open_fn, open_video: open_video_fn, generate: generate_fn, img2img: img2img_fn,
video: video_fn, upscale: upscale_fn, close: close_fn
)
end
@@ -108,6 +109,18 @@ RSpec.describe Elelem::Diffusion do
end
describe ".video" do
+ around do |example|
+ original = { model: ENV["ELELEM_SD_VIDEO_MODEL"], vae: ENV["ELELEM_SD_VIDEO_VAE"], t5xxl: ENV["ELELEM_SD_VIDEO_T5XXL"] }
+ ENV["ELELEM_SD_VIDEO_MODEL"] = "/models/wan.safetensors"
+ ENV["ELELEM_SD_VIDEO_VAE"] = "/models/wan_vae.safetensors"
+ ENV["ELELEM_SD_VIDEO_T5XXL"] = "/models/t5xxl.gguf"
+ example.run
+ ensure
+ ENV["ELELEM_SD_VIDEO_MODEL"] = original[:model]
+ ENV["ELELEM_SD_VIDEO_VAE"] = original[:vae]
+ ENV["ELELEM_SD_VIDEO_T5XXL"] = original[:t5xxl]
+ end
+
it "creates the output directory and returns ok on success" do
allow(video_fn).to receive(:call).and_return(1)
allow(FileUtils).to receive(:mkdir_p)
@@ -118,6 +131,17 @@ RSpec.describe Elelem::Diffusion do
expect(result).to eq(ok: true, path: "/tmp/vid")
end
+ it "opens the video context with the diffusion/vae/t5xxl paths" do
+ allow(video_fn).to receive(:call).and_return(1)
+ allow(FileUtils).to receive(:mkdir_p)
+
+ described_class.video(prompt: "a bird flying", output_dir: "/tmp/vid")
+
+ expect(open_video_fn).to have_received(:call).with(
+ "/models/wan.safetensors", "/models/wan_vae.safetensors", "/models/t5xxl.gguf", Etc.nprocessors
+ )
+ end
+
it "defaults video_frames/fps/steps and passes them to the shim" do
allow(video_fn).to receive(:call).and_return(1)
allow(FileUtils).to receive(:mkdir_p)
@@ -135,6 +159,16 @@ RSpec.describe Elelem::Diffusion do
expect(result).to eq(ok: false, error: "video generation failed")
end
+
+ it "returns ok: false without generating when the video model fails to load" do
+ allow(open_video_fn).to receive(:call).and_return(instance_double(Fiddle::Pointer, null?: true))
+ allow(video_fn).to receive(:call)
+
+ result = described_class.video(prompt: "a bird flying", output_dir: "/tmp/vid")
+
+ expect(result).to eq(ok: false, error: "failed to load video model at /models/wan.safetensors")
+ expect(video_fn).not_to have_received(:call)
+ end
end
describe ".upscale" do