From c0ea41f6b2acdff034872ef39fe42b4e4da19a59 Mon Sep 17 00:00:00 2001
From: KITAITI Makoto <KitaitiMakoto@gmail.com>
Date: Mon, 28 Oct 2024 20:08:09 +0900
Subject: [PATCH] ruby : add Metal support (#2516)

---
 bindings/ruby/Rakefile           |   7 +-
 bindings/ruby/ext/extconf.rb     | 217 ++++++++++++++++++++++++++++++-
 bindings/ruby/ext/metal-embed.mk |  14 ++
 bindings/ruby/extsources.yaml    |   7 +-
 4 files changed, 242 insertions(+), 3 deletions(-)
 create mode 100644 bindings/ruby/ext/metal-embed.mk

diff --git a/bindings/ruby/Rakefile b/bindings/ruby/Rakefile
index e138960b..9b2787e9 100644
--- a/bindings/ruby/Rakefile
+++ b/bindings/ruby/Rakefile
@@ -17,7 +17,12 @@ extsources.each_pair do |src_dir, dests|
 end
 SOURCES = extsources.values.flatten
 CLEAN.include SOURCES
-CLEAN.include FileList["ext/*.o", "ext/whisper.so", "ext/whisper.bundle", "ext/whisper.dll"]
+CLEAN.include FileList[
+                "ext/*.o",
+                "ext/*.metal",
+                "ext/whisper.{so,bundle,dll}",
+                "ext/depend"
+              ]
 
 task build: SOURCES + FileList[
                         "ext/extconf.rb",
diff --git a/bindings/ruby/ext/extconf.rb b/bindings/ruby/ext/extconf.rb
index 656d9e03..3b54a4a1 100644
--- a/bindings/ruby/ext/extconf.rb
+++ b/bindings/ruby/ext/extconf.rb
@@ -12,4 +12,219 @@ if enable_config('march-tune-native', false)
   $CXXFLAGS << ' -march=native -mtune=native'
 end
 
-create_makefile('whisper')
+def with_disabling_unsupported_files
+  disabled_files = []
+
+  unless $GGML_METAL
+    disabled_files << 'ggml-metal.h' << 'ggml-metal.m'
+  end
+
+  unless $GGML_METAL_EMBED_LIBRARY
+    disabled_files << 'ggml-metal.metal'
+  end
+
+  unless $OBJ_ALL&.include? 'ggml-blas.o'
+    disabled_files << 'ggml-blas.h' << 'ggml-blas.cpp'
+  end
+
+  disabled_files.filter! {|file| File.exist? file}
+
+  disabled_files.each do |file|
+    File.rename file, "#{file}.disabled"
+  end
+
+  yield
+
+  disabled_files.each do |file|
+    File.rename "#{file}.disabled", file
+  end
+end
+
+if ENV['WHISPER_METAL']
+  $GGML_METAL ||= true
+  $DEPRECATE_WARNING ||= true
+end
+
+$UNAME_S = `uname -s`.chomp
+$UNAME_P = `uname -p`.chomp
+$UNAME_M = `uname -m`.chomp
+
+if $UNAME_S == 'Darwin'
+  unless ENV['GGML_NO_METAL']
+    $GGML_METAL ||= true
+  end
+  $GGML_NO_OPENMP ||= true
+end
+
+if $GGML_METAL
+  $GGML_METAL_EMBED_LIBRARY = true
+end
+
+$MK_CPPFLAGS = ''
+$MK_CFLAGS   = '-std=c11   -fPIC'
+$MK_CXXFLAGS = '-std=c++11 -fPIC'
+$MK_NVCCFLAGS = '-std=c++11'
+$MK_LDFLAGS = ''
+
+$OBJ_GGML = ''
+$OBJ_WHISPER = ''
+$OBJ_COMMON = ''
+$OBJ_SDL = ''
+
+$MK_CPPFLAGS << ' -D_XOPEN_SOURCE=600'
+
+if $UNAME_S == 'Linux'
+  $MK_CPPFLAGS << ' -D_GNU_SOURCE'
+end
+
+if $UNAME_S == 'Darwin'
+  $MK_CPPFLAGS << ' -D_DARWIN_C_SOURCE'
+end
+
+if ENV['WHISPER_DEBUG']
+  $MK_CFLAGS    << ' -O0 -g'
+  $MK_CXXFLAGS  << ' -O0 -g'
+  $MK_LDFLAGS   << ' -g'
+  $MK_NVCCFLAGS << ' -O0 -g'
+else
+  $MK_CPPFLAGS   << ' -DNDEBUG'
+  $MK_CFLAGS     << ' -O3'
+  $MK_CXXFLAGS   << ' -O3'
+  $MK_NVCCFLAGS  << ' -O3'
+end
+
+$WARN_FLAGS =
+  ' -Wall' <<
+  ' -Wextra' <<
+  ' -Wpedantic' <<
+  ' -Wcast-qual' <<
+  ' -Wno-unused-function'
+
+$MK_CFLAGS <<
+  $WARN_FLAGS <<
+  ' -Wshadow' <<
+  ' -Wstrict-prototypes' <<
+  ' -Wpointer-arith' <<
+  ' -Wmissing-prototypes' <<
+  ' -Werror=implicit-int' <<
+  ' -Werror=implicit-function-declaration'
+
+$MK_CXXFLAGS <<
+  $WARN_FLAGS <<
+  ' -Wmissing-declarations' <<
+  ' -Wmissing-noreturn'
+
+unless `#{cc_command} #{$LDFLAGS} -Wl,-v 2>&1`.chomp.include? 'dyld-1015.7'
+  $MK_CPPFLAGS << ' -DHAVE_BUGGY_APPLE_LINKER'
+end
+
+if %w[Linux Darwin FreeBSD NetBSD OpenBSD Haiku].include? $UNAME_S
+  $MK_CFLAGS   << ' -pthread'
+  $MK_CXXFLAGS << ' -pthread'
+end
+
+unless $_WIN32
+  $DSO_EXT = '.so'
+else
+  $DSO_EXT = '.dll'
+end
+
+unless ENV['RISCV']
+  if %w[x86_64 i686 amd64].include? $UNAME_M
+    $HOST_CXXFLAGS ||= ''
+
+    $MK_CFLAGS     << ' -march=native -mtune=native'
+    $HOST_CXXFLAGS << ' -march=native -mtune=native'
+  end
+
+  if $UNAME_M.match? /aarch64.*/
+    $MK_CFLAGS   << ' -mcpu=native'
+    $MK_CXXFLAGS << ' -mcpu=native'
+  end
+else
+  $MK_CFLAGS   << ' -march=rv64gcv -mabi=lp64d'
+  $MK_CXXFLAGS << ' -march=rv64gcv -mabi=lp64d'
+end
+
+unless ENV['GGML_NO_ACCELERATE']
+  if $UNAME_S == 'Darwin'
+    $MK_CPPFLAGS << ' -DGGML_USE_ACCELERATE -DGGML_USE_BLAS'
+    $MK_CPPFLAGS << ' -DACCELERATE_NEW_LAPACK'
+    $MK_CPPFLAGS << ' -DACCELERATE_LAPACK_ILP64'
+    $MK_LDFLAGS  << ' -framework Accelerate'
+    $OBJ_GGML    << ' ggml-blas.o'
+  end
+end
+
+if ENV['GGML_OPENBLAS']
+  $MK_CPPFLAGS << " -DGGML_USE_BLAS #{`pkg-config --cflags-only-I openblas`.chomp}"
+  $MK_CFLAGS   << " #{`pkg-config --cflags-only-other openblas)`.chomp}"
+  $MK_LDFLAGS  << " #{`pkg-config --libs openblas`}"
+  $OBJ_GGML    << ' ggml-blas.o'
+end
+
+if ENV['GGML_OPENBLAS64']
+  $MK_CPPFLAGS << " -DGGML_USE_BLAS #{`pkg-config --cflags-only-I openblas64`.chomp}"
+  $MK_CFLAGS   << " #{`pkg-config --cflags-only-other openblas64)`.chomp}"
+  $MK_LDFLAGS  << " #{`pkg-config --libs openblas64`}"
+  $OBJ_GGML    << ' ggml-blas.o'
+end
+
+if $GGML_METAL
+  $MK_CPPFLAGS << ' -DGGML_USE_METAL'
+  $MK_LDFLAGS  << ' -framework Foundation -framework Metal -framework MetalKit'
+  $OBJ_GGML    << ' ggml-metal.o'
+
+  if ENV['GGML_METAL_NDEBUG']
+    $MK_CPPFLAGS << ' -DGGML_METAL_NDEBUG'
+  end
+
+  if $GGML_METAL_EMBED_LIBRARY
+    $MK_CPPFLAGS << ' -DGGML_METAL_EMBED_LIBRARY'
+    $OBJ_GGML    << ' ggml-metal-embed.o'
+  end
+end
+
+$OBJ_GGML <<
+  ' ggml.o' <<
+  ' ggml-alloc.o' <<
+  ' ggml-backend.o' <<
+  ' ggml-quants.o' <<
+  ' ggml-aarch64.o'
+
+$OBJ_WHISPER <<
+  ' whisper.o'
+
+$OBJ_ALL = "#{$OBJ_GGML} #{$OBJ_WHISPER} #{$OBJ_COMMON} #{$OBJ_SDL}"
+
+$CPPFLAGS  = "#{$MK_CPPFLAGS} #{$CPPFLAGS}"
+$CFLAGS    = "#{$CPPFLAGS} #{$MK_CFLAGS} #{$GF_CFLAGS} #{$CFLAGS}"
+$BASE_CXXFLAGS = "#{$MK_CXXFLAGS} #{$CXXFLAGS}"
+$CXXFLAGS  = "#{$BASE_CXXFLAGS} #{$HOST_CXXFLAGS} #{$GF_CXXFLAGS} #{$CPPFLAGS}"
+$NVCCFLAGS = "#{$MK_NVCCFLAGS} #{$NVCCFLAGS}"
+$LDFLAGS   = "#{$MK_LDFLAGS} #{$LDFLAGS}"
+
+if $GGML_METAL_EMBED_LIBRARY
+  File.write 'depend', "$(OBJS): $(OBJS) ggml-metal-embed.o\n"
+end
+
+with_disabling_unsupported_files do
+
+  create_makefile('whisper')
+
+end
+
+File.open 'Makefile', 'a' do |file|
+  file.puts 'include get-flags.mk'
+
+  if $GGML_METAL
+    if $GGML_METAL_EMBED_LIBRARY
+      # mkmf determines object files to compile dependent on existing *.{c,cpp,m} files
+      # but ggml-metal-embed.c doesn't exist on creating Makefile.
+      file.puts "objs := $(OBJS)"
+      file.puts "OBJS = $(objs) 'ggml-metal-embed.o'"
+
+      file.puts 'include metal-embed.mk'
+    end
+  end
+end
diff --git a/bindings/ruby/ext/metal-embed.mk b/bindings/ruby/ext/metal-embed.mk
new file mode 100644
index 00000000..478b8fd8
--- /dev/null
+++ b/bindings/ruby/ext/metal-embed.mk
@@ -0,0 +1,14 @@
+ggml-metal-embed.o: \
+	ggml-metal.metal \
+	ggml-common.h
+	@echo "Embedding Metal library"
+	@sed -e '/#include "ggml-common.h"/r ggml-common.h' -e '/#include "ggml-common.h"/d' < ggml-metal.metal > ggml-metal-embed.metal
+	$(eval TEMP_ASSEMBLY=$(shell mktemp))
+	@echo ".section __DATA, __ggml_metallib"            >  $(TEMP_ASSEMBLY)
+	@echo ".globl _ggml_metallib_start"                 >> $(TEMP_ASSEMBLY)
+	@echo "_ggml_metallib_start:"                       >> $(TEMP_ASSEMBLY)
+	@echo ".incbin \"ggml-metal-embed.metal\""          >> $(TEMP_ASSEMBLY)
+	@echo ".globl _ggml_metallib_end"                   >> $(TEMP_ASSEMBLY)
+	@echo "_ggml_metallib_end:"                         >> $(TEMP_ASSEMBLY)
+	@$(AS) $(TEMP_ASSEMBLY) -o $@
+	@rm -f ${TEMP_ASSEMBLY}
diff --git a/bindings/ruby/extsources.yaml b/bindings/ruby/extsources.yaml
index 1a4b4d25..e59f6ecf 100644
--- a/bindings/ruby/extsources.yaml
+++ b/bindings/ruby/extsources.yaml
@@ -15,6 +15,9 @@
 - ext/ggml-quants.h
 - ext/ggml-quants.c
 - ext/ggml-cpu-impl.h
+- ext/ggml-metal.m
+- ext/ggml-metal.metal
+- ext/ggml-blas.cpp
 ../../ggml/include:
 - ext/ggml.h
 - ext/ggml-alloc.h
@@ -24,9 +27,11 @@
 - ext/ggml-metal.h
 - ext/ggml-sycl.h
 - ext/ggml-vulkan.h
+- ext/ggml-blas.h
+../../scripts:
+- ext/get-flags.mk
 ../../examples:
 - ext/dr_wav.h
 ../..:
 - README.md
 - LICENSE
-