Skip to content
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 28 additions & 1 deletion .github/workflows/mlx.yml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,11 @@ jobs:
echo "::endgroup::"

echo "::group::Build test runners"
${CONDA_RUN} cmake --build cmake-out --target op_test_runner multi_thread_test_runner -j$(( $(sysctl -n hw.ncpu) - 1 ))
${CONDA_RUN} cmake --build cmake-out --target op_test_runner multi_thread_test_runner mlx_mutable_state_test -j$(( $(sysctl -n hw.ncpu) - 1 ))
echo "::endgroup::"

echo "::group::Run mutable-state (multi-session) unit test"
./cmake-out/backends/mlx/test/mlx_mutable_state_test
echo "::endgroup::"

echo "::group::Run op unit tests"
Expand Down Expand Up @@ -161,6 +165,29 @@ jobs:
fi
echo "::endgroup::"

echo "::group::Verify chunked == unchunked prefill"
QWEN_TINY_PTE=/tmp/qwen35_moe_mlx_tiny/model.pte \
${CONDA_RUN} python -m pytest \
examples/models/qwen3_5_moe/test_chunked_prefill.py -v
echo "::endgroup::"

echo "::group::Build Qwen 3.5 MoE MLX C++ runner"
# Validates the MLX C++ runner build wiring (compile + link + metallib).
# The tiny model has no compatible tokenizer (vocab 256, random weights),
# so we don't run C++ inference here — only confirm it builds.
${CONDA_RUN} make qwen3_5_moe-mlx
RUNNER=cmake-out/examples/models/qwen3_5_moe/qwen3_5_moe_runner
if [ ! -x "$RUNNER" ]; then
echo "Failed: runner not found at $RUNNER"
exit 1
fi
if [ ! -f "$(dirname "$RUNNER")/mlx.metallib" ]; then
echo "Failed: mlx.metallib not copied next to runner"
exit 1
fi
echo "Success: built $RUNNER"
echo "::endgroup::"

backend-tester:
needs: run-decision
if: |
Expand Down
12 changes: 11 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@
#
# ==============================================================================

.PHONY: voxtral-cuda voxtral-cpu voxtral-metal voxtral-mlx voxtral_realtime-cuda voxtral_realtime-cpu voxtral_realtime-metal voxtral_realtime-mlx voxtral_tts-cpu voxtral_tts-cuda whisper-cuda whisper-cuda-debug whisper-cpu whisper-metal parakeet-cuda parakeet-cuda-debug parakeet-cpu parakeet-metal parakeet-mlx parakeet-vulkan dinov2-cuda dinov2-cuda-debug sortformer-cuda sortformer-cpu silero-vad-cpu llama-cuda llama-cuda-debug llama-cpu lfm_2_5-mlx llava-cpu gemma3-cuda gemma3-cpu gemma4_31b-cuda gemma4_31b-mlx qwen3_5_moe-cuda qwen3_5_moe-metal clean help
.PHONY: voxtral-cuda voxtral-cpu voxtral-metal voxtral-mlx voxtral_realtime-cuda voxtral_realtime-cpu voxtral_realtime-metal voxtral_realtime-mlx voxtral_tts-cpu voxtral_tts-cuda whisper-cuda whisper-cuda-debug whisper-cpu whisper-metal parakeet-cuda parakeet-cuda-debug parakeet-cpu parakeet-metal parakeet-mlx parakeet-vulkan dinov2-cuda dinov2-cuda-debug sortformer-cuda sortformer-cpu silero-vad-cpu llama-cuda llama-cuda-debug llama-cpu lfm_2_5-mlx llava-cpu gemma3-cuda gemma3-cpu gemma4_31b-cuda gemma4_31b-mlx qwen3_5_moe-cuda qwen3_5_moe-metal qwen3_5_moe-mlx clean help

help:
@echo "This Makefile adds targets to build runners for various models on various backends. Run using \`make <target>\`. Available targets:"
Expand Down Expand Up @@ -131,6 +131,7 @@ help:
@echo " gemma4_31b-mlx - Build Gemma 4 31B runner with MLX backend"
@echo " qwen3_5_moe-cuda - Build Qwen3.5 MoE runner with CUDA backend"
@echo " qwen3_5_moe-metal - Build Qwen3.5 MoE runner with Metal backend"
@echo " qwen3_5_moe-mlx - Build Qwen3.5 MoE runner with MLX backend"
@echo " clean - Clean build artifacts"

voxtral-cuda:
Expand Down Expand Up @@ -467,6 +468,15 @@ qwen3_5_moe-metal:
@echo "✓ Build complete!"
@echo " Binary: cmake-out/examples/models/qwen3_5_moe/qwen3_5_moe_runner"

qwen3_5_moe-mlx:
@echo "==> Building and installing ExecuTorch with MLX..."
cmake --workflow --preset mlx-release
@echo "==> Building Qwen3.5 MoE runner with MLX..."
cd examples/models/qwen3_5_moe && cmake --workflow --preset qwen3-5-moe-mlx
@echo ""
@echo "✓ Build complete!"
@echo " Binary: cmake-out/examples/models/qwen3_5_moe/qwen3_5_moe_runner"

clean:
rm -rf cmake-out \
extension/llm/tokenizers/build \
Expand Down
6 changes: 4 additions & 2 deletions backends/mlx/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -255,8 +255,10 @@ option(ET_MLX_ALLOW_CUSTOM_KERNEL_EXECUTION
ON
)

set(_mlx_backend__srcs ${CMAKE_CURRENT_SOURCE_DIR}/runtime/MLXLoader.cpp
${CMAKE_CURRENT_SOURCE_DIR}/runtime/MLXBackend.cpp
set(_mlx_backend__srcs
${CMAKE_CURRENT_SOURCE_DIR}/runtime/MLXLoader.cpp
${CMAKE_CURRENT_SOURCE_DIR}/runtime/MLXBackend.cpp
${CMAKE_CURRENT_SOURCE_DIR}/runtime/mlx_mutable_state.cpp
)

add_library(mlxdelegate ${_mlx_backend__srcs})
Expand Down
16 changes: 16 additions & 0 deletions backends/mlx/runtime/MLXBackend.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include "MLXExecutor.h"
#include "MLXInterpreter.h"
#include "MLXLoader.h"
#include "mlx_mutable_state.h"

#include <executorch/runtime/backend/interface.h>
#include <executorch/runtime/core/error.h>
Expand Down Expand Up @@ -277,6 +278,12 @@ class MLXBackend final : public ::executorch::runtime::BackendInterface {
eval(handle->constants.tensors);
}

// Register the handle with the per-session mutable-state manager. This is
// a no-op unless a multi-session owner is active for this load (see
// mlx_mutable_state.h); single-session execution is unaffected.
mutable_state_note_handle(
handle, &handle->program, &handle->mutable_buffers);

} catch (const std::exception& e) {
ET_LOG(Error, "Failed to load MLX program: %s", e.what());
handle->~MLXHandle();
Expand Down Expand Up @@ -366,6 +373,14 @@ class MLXBackend final : public ::executorch::runtime::BackendInterface {
}
}

// Select the active session's mutable buffers (KV cache, recurrent/conv
// state) before running. No-op for single-session handles; weights stay
// shared via ExecutionState::constants.
if (Error rebind_err = mutable_state_rebind_for_execute(h, h->state);
rebind_err != Error::Ok) {
return rebind_err;
}

// Run the MLX program (builds lazy computation graph)
h->interpreter.run(program, h->state, h->stream);

Expand Down Expand Up @@ -431,6 +446,7 @@ class MLXBackend final : public ::executorch::runtime::BackendInterface {
void destroy(DelegateHandle* handle) const override {
std::lock_guard<std::mutex> lock(mlx_global_mutex());
if (handle != nullptr) {
mutable_state_forget_handle(handle);
auto* mlx_handle = static_cast<MLXHandle*>(handle);
mlx_handle->~MLXHandle();
}
Expand Down
Loading
Loading