From 8a6fc198e1cccd9f4303197c217e475a6f48b141 Mon Sep 17 00:00:00 2001 From: grorge Date: Mon, 9 Nov 2026 20:45:26 +0800 Subject: [PATCH 1/3] fix: sync bug fixes from the WasmEdge MLX plugin Port fixes made to plugins/wasi_nn/MLX in WasmEdge back to this repo: - whisper: stop running ffmpeg through popen()/sh. loadAudio built a command string from the audio path, so shell metacharacters in the path could run arbitrary commands and paths with spaces broke. ffmpeg now runs via posix_spawnp with an argv array (cf. WasmEdge 4742d02fba). File-path input is kept for library users. - vlm: stop generation on the model's EOS tokens instead of always running to MaxTokens. Stop ids are a per-model virtual (eosTokenIds); Gemma3 returns (1) and (106). Unlike WasmEdge's hard-coded check, mllama is not affected. - loadBytesFromFile: log and return an empty string on open failure instead of calling exit(1) (cf. WasmEdge 584437baa5). - Rename parameters and locals that shadowed members or outer names in Module, Embedding/Linear, pooling, RotatingKVCache, whisper decoding and tokenizer (cf. WasmEdge e271503856). - Pass values as spdlog format arguments instead of concatenating them into the format string (cf. WasmEdge 2b8429d303). Co-Authored-By: Claude Opus 5.5 --- mlx/base.cpp | 20 ++++--- mlx/base.h | 8 +-- mlx/embedding.h | 4 +- mlx/linear.h | 4 +- mlx/pooling.cpp | 40 ++++++------- mlx/pooling.h | 24 ++++---- model/gemma3/gemma3.h | 2 + model/utils.cpp | 4 +- model/vlm_base.cpp | 11 +++- model/vlm_base.h | 5 +- model/whisper/decoding.cpp | 7 ++- model/whisper/decoding.h | 2 +- model/whisper/tokenizer.cpp | 13 ++-- model/whisper/tokenizer.h | 2 +- model/whisper_transcribe.cpp | 111 ++++++++++++++++++++++------------- 15 files changed, 150 insertions(+), 107 deletions(-) diff --git a/mlx/base.cpp b/mlx/base.cpp index b470d0c..d7dc938 100644 --- a/mlx/base.cpp +++ b/mlx/base.cpp @@ -9,24 +9,25 @@ namespace mlx::core::nn { -mx::array &Module::registerParameter(const std::string &Name, mx::array &&W) { - Parameters.insert({Name, W}); - return Parameters.at(Name); +mx::array &Module::registerParameter(const std::string &ParamName, + mx::array &&W) { + Parameters.insert({ParamName, W}); + return Parameters.at(ParamName); } -void Module::update(std::unordered_map Parameters) { - for (auto &[K, V] : Parameters) { +void Module::update(std::unordered_map NewParameters) { + for (auto &[K, V] : NewParameters) { apply(K, V); } } std::shared_ptr Module::toQuantized( int GroupSize, int Bits, const std::string &Prefix, - const std::unordered_map &Parameters) { + const std::unordered_map &LoadedWeights) { auto NewPrefix = Prefix + Name + (Prefix.empty() && Name.empty() ? "" : "."); for (auto &[K, V] : Submodules) { if (V->hasQuantize()) { auto Weights = V->Parameters.find("weight"); - if (Weights != V->Parameters.end() && !Parameters.empty()) { - if (Parameters.count(NewPrefix + V->Name + ".scales") == 0) { + if (Weights != V->Parameters.end() && !LoadedWeights.empty()) { + if (LoadedWeights.count(NewPrefix + V->Name + ".scales") == 0) { continue; } } @@ -36,7 +37,8 @@ std::shared_ptr Module::toQuantized( } } V = V->toQuantized(GroupSize, Bits, - Prefix + Name + (Name.empty() ? "" : "."), Parameters); + Prefix + Name + (Name.empty() ? "" : "."), + LoadedWeights); } return shared_from_this(); } diff --git a/mlx/base.h b/mlx/base.h index a89eef7..fd96e2c 100644 --- a/mlx/base.h +++ b/mlx/base.h @@ -16,15 +16,15 @@ class Module : public std::enable_shared_from_this { std::string Name; std::unordered_map Parameters{}; std::unordered_map> Submodules{}; - mx::array ®isterParameter(const std::string &Name, mx::array &&W); + mx::array ®isterParameter(const std::string &ParamName, mx::array &&W); std::unordered_map getWeigts(const std::string &Prefix = "model"); virtual std::shared_ptr toQuantized( int GroupSize = 64, int Bits = 4, const std::string &Prefix = "", - const std::unordered_map &Parameters = {}); + const std::unordered_map &LoadedWeights = {}); virtual bool hasQuantize() { return false; } - void update(std::unordered_map Parameters); - void apply(std::string Key, const mx::array &Parameters); + void update(std::unordered_map NewParameters); + void apply(std::string Key, const mx::array &Value); template void registerModule(const std::string &ModuleName, const std::shared_ptr &M) { diff --git a/mlx/embedding.h b/mlx/embedding.h index fc4a1ef..7184746 100644 --- a/mlx/embedding.h +++ b/mlx/embedding.h @@ -16,8 +16,8 @@ class Embedding : public Module { mx::array asLinear(const mx::array &Input); std::shared_ptr toQuantized(int GroupSize = 64, int Bits = 4, const std::string &Prefix = "", - const std::unordered_map &Parameters = {}) - override; + const std::unordered_map &LoadedWeights = + {}) override; virtual bool hasQuantize() override { return true; } }; diff --git a/mlx/linear.h b/mlx/linear.h index 2533ea3..8401e7f 100644 --- a/mlx/linear.h +++ b/mlx/linear.h @@ -28,8 +28,8 @@ class Linear : public Module { virtual mx::array forward(mx::array Input); std::shared_ptr toQuantized(int GroupSize = 64, int Bits = 4, const std::string &Prefix = "", - const std::unordered_map &Parameters = {}) - override; + const std::unordered_map &LoadedWeights = + {}) override; virtual bool hasQuantize() override { return true; } }; diff --git a/mlx/pooling.cpp b/mlx/pooling.cpp index f54ca14..a49188b 100644 --- a/mlx/pooling.cpp +++ b/mlx/pooling.cpp @@ -63,8 +63,8 @@ mx::array slidingWindows(const mx::array &X, if (X.ndim() < 3) { spdlog::error( "To extract sliding windows at least 1 spatial dimension (3 total) is " - "needed but the input only has " + - std::to_string(X.ndim()) + " dimensions."); + "needed but the input only has {} dimensions.", + X.ndim()); assumingUnreachable(); } auto Shape = X.shape(); @@ -111,13 +111,12 @@ mx::array slidingWindows(const mx::array &X, } return mx::as_strided(X, FinalShape, FinalStrides, 0); } -Pool::Pool( - const std::function &)> - &PoolingFunction, - const std::vector &KernelSize, const std::vector &Stride, - const std::vector> &Padding, int PaddingValue) - : PoolingFunction(PoolingFunction), KernelSize(KernelSize), Stride(Stride), - Padding(Padding), PaddingValue(PaddingValue) { +Pool::Pool(const std::function &)> &PoolingFn, + const std::vector &KernelSizes, const std::vector &Strides, + const std::vector> &Paddings, int PadValue) + : PoolingFunction(PoolingFn), KernelSize(KernelSizes), Stride(Strides), + Padding(Paddings), PaddingValue(PadValue) { int N = KernelSize.size(); for (int I = -(N + 1); I < -1; I++) { Axes.push_back(I); @@ -144,29 +143,30 @@ mx::array Pool::forward(const mx::array &X) { Pool2d::Pool2d( const std::function &)> - &PoolingFunction, - int PaddingValue, const std::vector &KernelSize, + &PoolingFn, + int PadValue, const std::vector &KernelSizes, const std::optional> &StrideOpt, const std::optional> &PaddingOpt) - : Pool(PoolingFunction, - KernelSize.size() == 1 ? valueOrList(KernelSize[0], 2) : KernelSize, + : Pool(PoolingFn, + KernelSizes.size() == 1 ? valueOrList(KernelSizes[0], 2) + : KernelSizes, (StrideOpt.has_value() ? (StrideOpt.value().size() == 1 ? valueOrList(StrideOpt.value()[0], 2) : StrideOpt.value()) - : (KernelSize.size() == 1 ? valueOrList(KernelSize[0], 2) - : KernelSize)), + : (KernelSizes.size() == 1 ? valueOrList(KernelSizes[0], 2) + : KernelSizes)), makePaddingPairs(PaddingOpt.has_value() ? PaddingOpt.value() : valueOrList(0, 2)), - PaddingValue) {} + PadValue) {} -AvgPool2d::AvgPool2d(const std::vector &KernelSize, - const std::optional> &Stride, - const std::optional> &Padding) +AvgPool2d::AvgPool2d(const std::vector &KernelSizes, + const std::optional> &StrideOpt, + const std::optional> &PaddingOpt) : Pool2d( [](const mx::array &A, const std::vector &Axis) -> mx::array { return mx::mean(A, Axis, false); }, - 0, KernelSize, Stride, Padding) {} + 0, KernelSizes, StrideOpt, PaddingOpt) {} } // namespace mlx::core::nn \ No newline at end of file diff --git a/mlx/pooling.h b/mlx/pooling.h index 85fdca5..3707f21 100644 --- a/mlx/pooling.h +++ b/mlx/pooling.h @@ -5,10 +5,10 @@ namespace mlx::core::nn { class Pool : public nn::Module { public: - Pool(const std::function &)> &PoolingFunction, - const std::vector &KernelSize, const std::vector &Stride, - const std::vector> &Padding, int PaddingValue); + Pool(const std::function &)> &PoolingFn, + const std::vector &KernelSizes, const std::vector &Strides, + const std::vector> &Paddings, int PadValue); mx::array forward(const mx::array &X); protected: @@ -23,18 +23,18 @@ class Pool : public nn::Module { class Pool2d : public Pool { public: - Pool2d(const std::function &)> &PoolingFunction, - int PaddingValue, const std::vector &KernelSize, - const std::optional> &Stride, - const std::optional> &Padding); + Pool2d(const std::function &)> &PoolingFn, + int PadValue, const std::vector &KernelSizes, + const std::optional> &StrideOpt, + const std::optional> &PaddingOpt); }; class AvgPool2d : public Pool2d { public: - AvgPool2d(const std::vector &KernelSize, - const std::optional> &Stride = std::nullopt, - const std::optional> &Padding = std::nullopt); + AvgPool2d(const std::vector &KernelSizes, + const std::optional> &StrideOpt = std::nullopt, + const std::optional> &PaddingOpt = std::nullopt); }; } // namespace mlx::core::nn \ No newline at end of file diff --git a/model/gemma3/gemma3.h b/model/gemma3/gemma3.h index 6afc079..b3aeefe 100644 --- a/model/gemma3/gemma3.h +++ b/model/gemma3/gemma3.h @@ -51,6 +51,8 @@ class Model : public vlm::Module { const mx::array &Mask, const std::optional>> &Cache = std::nullopt) override; + // and . + std::vector eosTokenIds() const override { return {1, 106}; } static std::shared_ptr fromPretrained(const std::string &ModelPath); std::unordered_map sanitize(const std::unordered_map &Weights); diff --git a/model/utils.cpp b/model/utils.cpp index 18fb892..c8d498e 100644 --- a/model/utils.cpp +++ b/model/utils.cpp @@ -53,8 +53,8 @@ void saveWeights(const mx::array &Weights, const std::string &Path) { std::string loadBytesFromFile(const std::string &Path) { std::ifstream Fs(Path, std::ios::in | std::ios::binary); if (Fs.fail()) { - std::cerr << "Cannot open " << Path << std::endl; - exit(1); + spdlog::error("Cannot open {}.", Path); + return ""; } std::string Data; Fs.seekg(0, std::ios::end); diff --git a/model/vlm_base.cpp b/model/vlm_base.cpp index 159592b..f10edab 100644 --- a/model/vlm_base.cpp +++ b/model/vlm_base.cpp @@ -3,6 +3,7 @@ #include "base.h" #include "spdlog/spdlog.h" #include "vlm_sampling.h" +#include #include #include #include @@ -126,8 +127,8 @@ int KVCache::trim(int N) { } // RotatingKVCache implementation -RotatingKVCache::RotatingKVCache(int MaxSize, int Keep, int Step) - : KVCache(0, 0, Step), Keep(Keep), MaxSize(MaxSize), Idx(0) {} +RotatingKVCache::RotatingKVCache(int MaxSize, int Keep, int StepSize) + : KVCache(0, 0, StepSize), Keep(Keep), MaxSize(MaxSize), Idx(0) {} mx::array RotatingKVCache::trim(int TrimSize, const mx::array &V, std::optional Append) { @@ -480,6 +481,7 @@ std::vector Module::generate( GenerationResult LastResponse; auto Tic = std::chrono::system_clock::now().time_since_epoch(); std::vector TokenList; + const std::vector EosTokenIds = eosTokenIds(); int N = 0; while (true) { int PromptTPS; @@ -487,7 +489,6 @@ std::vector Module::generate( PromptTPS = std::chrono::duration_cast(Tic).count(); Tic = std::chrono::system_clock::now().time_since_epoch(); } - // TODO: if token = eos_token_id break auto Response = GenerationResult(); N++; if (N >= MaxTokens) { @@ -497,6 +498,10 @@ std::vector Module::generate( mx::async_eval(NextY); // TODO: handle decoder_input_ids auto Token = Y.item(); + if (std::find(EosTokenIds.begin(), EosTokenIds.end(), Token) != + EosTokenIds.end()) { + break; + } TokenList.emplace_back(Token); Y = NextY; LogProbs = NextLogProbs; diff --git a/model/vlm_base.h b/model/vlm_base.h index 58c7d0a..a8455c8 100644 --- a/model/vlm_base.h +++ b/model/vlm_base.h @@ -62,7 +62,7 @@ class RotatingKVCache : public KVCache { int MaxSize; int Idx; - RotatingKVCache(int MaxSize = -1, int Keep = 0, int Step = 256); + RotatingKVCache(int MaxSize = -1, int Keep = 0, int StepSize = 256); std::tuple updateAndFetch(const mx::array &NewKeys, const mx::array &NewValues) override; @@ -93,6 +93,9 @@ class Module : public mlx::core::nn::Module { const mx::array &Mask, const std::optional>> &Cache = std::nullopt) = 0; + // Token ids that end generation. Models override this with their EOS / + // end-of-turn ids; the default never stops early. + virtual std::vector eosTokenIds() const { return {}; } struct GenerationResult { std::string Text; int Token; diff --git a/model/whisper/decoding.cpp b/model/whisper/decoding.cpp index 84a2bef..bdd4906 100644 --- a/model/whisper/decoding.cpp +++ b/model/whisper/decoding.cpp @@ -466,8 +466,9 @@ DecodingTask::DecodingTask(const std::shared_ptr &Model, } } -DecodingOptions DecodingTask::verifyOptions(const DecodingOptions &Options) { - DecodingOptions Result = Options; +DecodingOptions +DecodingTask::verifyOptions(const DecodingOptions &InputOptions) { + DecodingOptions Result = InputOptions; // Check beam_size and best_of conflicts if (Result.BeamSize && Result.BestOf) { @@ -740,7 +741,7 @@ std::vector DecodingTask::run(const mx::array &Mel) { // tokens = mx.broadcast_to(tokens, [n_audio, self.n_group, // len(self.initial_tokens)]) MlxShape NewShape = {NAudio, NGroup, - static_cast(InitialTokens.size())}; + static_cast(InitialTokens.size())}; Tokens = mx::broadcast_to(Tokens, NewShape); // tokens = tokens.reshape(n_audio * self.n_group, len(self.initial_tokens)) diff --git a/model/whisper/decoding.h b/model/whisper/decoding.h index 8e05fbf..4f21576 100644 --- a/model/whisper/decoding.h +++ b/model/whisper/decoding.h @@ -160,7 +160,7 @@ class DecodingTask { std::vector run(const mx::array &Mel); private: - DecodingOptions verifyOptions(const DecodingOptions &Options); + DecodingOptions verifyOptions(const DecodingOptions &InputOptions); std::vector getInitialTokens(); std::vector getSuppressTokens(); mx::array getAudioFeatures(const mx::array &Mel); diff --git a/model/whisper/tokenizer.cpp b/model/whisper/tokenizer.cpp index 256376d..a527cdd 100644 --- a/model/whisper/tokenizer.cpp +++ b/model/whisper/tokenizer.cpp @@ -190,8 +190,8 @@ Encoding::Encoding(const std::string &Name, int ExplicitNVocab, const std::string &PatStr, const std::unordered_map &MergeableRanks, const std::unordered_map &SpecialTokens) - : Name(Name), PatStr(PatStr), - MergeableRanks(MergeableRanks), SpecialTokens(SpecialTokens) { + : Name(Name), PatStr(PatStr), MergeableRanks(MergeableRanks), + SpecialTokens(SpecialTokens) { EotToken = 50257; // Default EOT token @@ -447,13 +447,14 @@ int Tokenizer::languageToken() const { return toLanguageToken(Language.value()); } -int Tokenizer::toLanguageToken(const std::string &Language) const { - std::string TokenName = "<|" + Language + "|>"; +int Tokenizer::toLanguageToken(const std::string &LanguageCode) const { + std::string TokenName = "<|" + LanguageCode + "|>"; auto It = SpecialTokens.find(TokenName); if (It != SpecialTokens.end()) { return It->second; } - throw std::runtime_error("Language " + Language + " not found in tokenizer."); + throw std::runtime_error("Language " + LanguageCode + + " not found in tokenizer."); } std::vector Tokenizer::getAllLanguageTokens() const { @@ -699,7 +700,7 @@ std::unique_ptr getEncoding(const std::string &Name, std::vector Specials = {"<|endoftext|>", "<|startoftranscript|>"}; - for (const auto &[Code, Name] : LANGUAGES) { + for (const auto &[Code, LanguageName] : LANGUAGES) { Specials.push_back("<|" + Code + "|>"); if (static_cast(Specials.size()) >= NumLanguages + 2) break; diff --git a/model/whisper/tokenizer.h b/model/whisper/tokenizer.h index b264278..394eb40 100644 --- a/model/whisper/tokenizer.h +++ b/model/whisper/tokenizer.h @@ -59,7 +59,7 @@ class Tokenizer { int getNoTimestamps() const; int getTimestampBegin() const; int languageToken() const; - int toLanguageToken(const std::string &Language) const; + int toLanguageToken(const std::string &LanguageCode) const; std::vector getAllLanguageTokens() const; std::vector getAllLanguageCodes() const; std::vector getSotSequenceIncludingNotimestamps() const; diff --git a/model/whisper_transcribe.cpp b/model/whisper_transcribe.cpp index b612350..3b5434e 100644 --- a/model/whisper_transcribe.cpp +++ b/model/whisper_transcribe.cpp @@ -5,9 +5,11 @@ #include "whisper/tokenizer.h" #include #include +#include #include #include #include +#include #include #include #include @@ -15,54 +17,84 @@ #include #include #include +#include #include +#include +#include #include #include +extern char **environ; + namespace whisper { mx::array loadAudio(const std::string &FilePath, int SampleRate) { + // Decode with ffmpeg via posix_spawnp and an argv array. Never build a + // command string for popen()/system(): /bin/sh would interpret shell + // metacharacters in FilePath (e.g. "a.wav; rm -rf ~"), allowing command + // injection, and paths containing spaces would break. int Channels = 1; - std::vector Cmd = {"ffmpeg", "-nostdin", - "-threads", "0", - "-i", FilePath, - "-f", "s16le", - "-ac", std::to_string(Channels), - "-acodec", "pcm_s16le", - "-ar", std::to_string(SampleRate), - "-v", "quiet", - "-"}; - - // Build command string - std::stringstream CmdStream; - for (size_t i = 0; i < Cmd.size(); ++i) { - if (i > 0) - CmdStream << " "; - CmdStream << Cmd[i]; - } - std::string CmdString = CmdStream.str(); - - // Execute ffmpeg command - FILE *Pipe = popen(CmdString.c_str(), "r"); - if (!Pipe) { - throw std::runtime_error("Failed to execute ffmpeg command"); - } - - // Read raw audio data + std::vector Args = {"ffmpeg", "-nostdin", + "-threads", "0", + "-i", FilePath, + "-f", "s16le", + "-ac", std::to_string(Channels), + "-acodec", "pcm_s16le", + "-ar", std::to_string(SampleRate), + "-v", "quiet", + "-"}; + std::vector Argv; + Argv.reserve(Args.size() + 1); + for (auto &Arg : Args) { + Argv.push_back(Arg.data()); + } + Argv.push_back(nullptr); + + int PipeFds[2]; + if (pipe(PipeFds) != 0) { + throw std::runtime_error("Failed to create pipe for ffmpeg"); + } + posix_spawn_file_actions_t Actions; + posix_spawn_file_actions_init(&Actions); + posix_spawn_file_actions_adddup2(&Actions, PipeFds[1], STDOUT_FILENO); + posix_spawn_file_actions_addclose(&Actions, PipeFds[0]); + posix_spawn_file_actions_addclose(&Actions, PipeFds[1]); + + pid_t Pid; + int SpawnErr = + posix_spawnp(&Pid, Argv[0], &Actions, nullptr, Argv.data(), environ); + posix_spawn_file_actions_destroy(&Actions); + close(PipeFds[1]); + if (SpawnErr != 0) { + close(PipeFds[0]); + throw std::runtime_error("Failed to execute ffmpeg: " + + std::string(std::strerror(SpawnErr))); + } + + // Read raw little-endian int16 PCM from ffmpeg's stdout. std::vector AudioData; - int16_t Buffer[4096]; - size_t BytesRead; - - while ((BytesRead = fread(Buffer, sizeof(int16_t), 4096, Pipe)) > 0) { - AudioData.insert(AudioData.end(), Buffer, Buffer + BytesRead); + std::vector Bytes; + char Buffer[8192]; + while (true) { + ssize_t N = read(PipeFds[0], Buffer, sizeof(Buffer)); + if (N > 0) { + Bytes.insert(Bytes.end(), Buffer, Buffer + N); + } else if (N == 0 || errno != EINTR) { + break; + } } + close(PipeFds[0]); - int ExitCode = pclose(Pipe); - if (ExitCode != 0) { - throw std::runtime_error("ffmpeg command failed with exit code: " + - std::to_string(ExitCode)); + int Status = 0; + while (waitpid(Pid, &Status, 0) < 0 && errno == EINTR) { + } + if (!WIFEXITED(Status) || WEXITSTATUS(Status) != 0) { + throw std::runtime_error("ffmpeg failed to decode audio file: " + FilePath); } + AudioData.resize(Bytes.size() / sizeof(int16_t)); + std::memcpy(AudioData.data(), Bytes.data(), + AudioData.size() * sizeof(int16_t)); if (AudioData.empty()) { throw std::runtime_error("No audio data loaded from file: " + FilePath); } @@ -75,9 +107,7 @@ mx::array loadAudio(const std::string &FilePath, int SampleRate) { AudioArray = mx::reshape(AudioArray, {Frames, Channels}); } - mx::array FloatAudio = mx::astype(AudioArray, mx::float32) / 32768.0f; - - return FloatAudio; + return mx::astype(AudioArray, mx::float32) / 32768.0f; } mx::array padOrTrim(const mx::array &Array, int Length, int Axis) { @@ -153,8 +183,7 @@ mx::array stft(const mx::array &X, const mx::array &Window, int NPerseg = 256, mx::array melFilters(int NMels) { // Load precomputed mel filters from file if (NMels != 80 && NMels != 128) { - spdlog::error("Unsupported number of mel filters: " + - std::to_string(NMels)); + spdlog::error("Unsupported number of mel filters: {}", NMels); assumingUnreachable(); } std::string FileName = "assets/mel_filters_" + std::to_string(NMels) + ".npy"; @@ -174,7 +203,7 @@ mx::array logMelSpectrogram(const mx::array &Audio, int NMels, int Padding) { // freqs[:-1, :].abs().square() MlxShape Start = {0, 0}; MlxShape End = {static_cast(Freqs.shape(0) - 1), - static_cast(Freqs.shape(1))}; + static_cast(Freqs.shape(1))}; MlxShape Strides = {1, 1}; auto FreqsSliced = mx::slice(Freqs, Start, End, Strides); auto Magnitudes = mx::square(mx::abs(FreqsSliced)); From c3297601673431b8707aeee7524d75852fbc4abb Mon Sep 17 00:00:00 2001 From: grorge Date: Mon, 9 Nov 2026 20:54:39 +0800 Subject: [PATCH 2/3] ci: run macOS build on macos-latest GitHub no longer provides the macos-14 runner, so the macOS job was cancelled before it started. Co-Authored-By: Claude Opus 5.5 --- .github/workflows/build.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index c0a40dd..8fccebe 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -29,7 +29,7 @@ jobs: cxx: clang++ mlx_args: -DMLX_BUILD_CPU=ON -DMLX_BUILD_METAL=OFF -DMLX_BUILD_CUDA=OFF - name: macOS - os: macos-14 + os: macos-latest cc: clang cxx: clang++ mlx_args: -DMLX_BUILD_CPU=ON -DMLX_BUILD_METAL=ON -DMLX_BUILD_CUDA=OFF From fe49765845e8a08948961b98c4d63fa6f627d702 Mon Sep 17 00:00:00 2001 From: grorge Date: Mon, 9 Nov 2026 21:01:50 +0800 Subject: [PATCH 3/3] build: drop unused fmt dependency fmt was fetched and compiled but never linked or included; spdlog uses its bundled copy. The pinned fmt 10.2.1 fails to compile with newer clang (consteval FMT_STRING errors), which broke the macOS build on macos-latest. Co-Authored-By: Claude Opus 5.5 --- CMakeLists.txt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 02993b9..a1a1705 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -13,11 +13,6 @@ endif() include(FetchContent) -FetchContent_Declare( - fmt - GIT_REPOSITORY https://github.com/fmtlib/fmt - GIT_TAG e69e5f977d458f2650bb346dadf2ad30c5320281) -FetchContent_MakeAvailable(fmt) find_package(MLX CONFIG REQUIRED) find_package(ZLIB REQUIRED)