Skip to content

Commit 0aa577e

Browse files
committed
Update (base update)
[ghstack-poisoned]
1 parent 489bf3f commit 0aa577e

32 files changed

Lines changed: 159 additions & 1196 deletions

backends/arm/tosa/specification.py

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,6 @@
1313
import re
1414
from typing import Dict, Generic, List, Set, TypeVar
1515

16-
# Import torch before any torch submodule so torch.SymInt is defined; importing
17-
# torch.fx first triggers a torch.distributed circular import during collection.
18-
import torch # noqa: F401
19-
2016
from packaging.version import Version
2117
from torch.fx.experimental.symbolic_shapes import ShapeEnv
2218

backends/mlx/CMakeLists.txt

Lines changed: 84 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -162,78 +162,83 @@ if(NOT _mlx_deployment_target_ok)
162162
)
163163
endif()
164164

165-
# Build MLX in its own isolated CMake scope via ExternalProject, then consume it
166-
# as a prebuilt static lib through an imported target (see below). Building MLX
167-
# with add_subdirectory would drop its whole project into ExecuTorch's
168-
# target/option namespace, which collides with shared deps MLX fetches (e.g.
169-
# nlohmann_json) and leaks MLX's MLX_BUILD_* options into our cache. The
170-
# isolated scope runs MLX's FetchContent in its own namespace, so no collision
171-
# and no submodule patching are needed.
172-
include(ExternalProject)
173-
174-
set(_mlx_binary_dir ${CMAKE_CURRENT_BINARY_DIR}/mlx)
175-
set(_mlx_static_lib ${_mlx_binary_dir}/libmlx.a)
176-
# With MLX_METAL_JIT=ON, MLX does not install the metallib; it is produced in
177-
# the build tree at this path.
178-
set(_mlx_metallib ${_mlx_binary_dir}/mlx/backend/metal/kernels/mlx.metallib)
179-
180-
message(
181-
STATUS "Building MLX from submodule (ExternalProject): ${MLX_SOURCE_DIR}"
165+
# MLX build options - we only need the C++ library with Metal
166+
set(MLX_BUILD_PYTHON_BINDINGS
167+
OFF
168+
CACHE BOOL "" FORCE
182169
)
183-
ExternalProject_Add(
184-
mlx_external
185-
SOURCE_DIR ${MLX_SOURCE_DIR}
186-
BINARY_DIR ${_mlx_binary_dir}
187-
CMAKE_ARGS -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}
188-
-DCMAKE_CXX_STANDARD=${CMAKE_CXX_STANDARD}
189-
-DCMAKE_OSX_DEPLOYMENT_TARGET=${CMAKE_OSX_DEPLOYMENT_TARGET}
190-
-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}
191-
-DPLATFORM=${PLATFORM}
192-
-DDEPLOYMENT_TARGET=${DEPLOYMENT_TARGET}
193-
# We only need the static Metal C++ library.
194-
-DMLX_BUILD_METAL=ON
195-
-DMLX_BUILD_CPU=OFF
196-
-DMLX_BUILD_CUDA=OFF
197-
-DMLX_BUILD_SHARED_LIBS=OFF
198-
-DMLX_BUILD_PYTHON_BINDINGS=OFF
199-
-DMLX_BUILD_PYTHON_STUBS=OFF
200-
-DMLX_BUILD_TESTS=OFF
201-
-DMLX_BUILD_EXAMPLES=OFF
202-
-DMLX_BUILD_BENCHMARKS=OFF
203-
-DMLX_BUILD_GGUF=OFF
204-
-DMLX_BUILD_SAFETENSORS=OFF
205-
-DMLX_METAL_JIT=ON
206-
# MLX's own install() does not emit libmlx.a where we consume it or the
207-
# metallib at all, so skip the install step and read both from the build tree.
208-
INSTALL_COMMAND ""
209-
# ExternalProject stamps its build, so a bare MLX submodule bump (git
210-
# submodule update) would not invalidate the stamp and we'd link a stale
211-
# libmlx.a with no signal. BUILD_ALWAYS reruns the build step every configure;
212-
# it is a fast no-op under Ninja when nothing changed.
213-
BUILD_ALWAYS ON
214-
# Required so Ninja knows these are produced by the external build.
215-
BUILD_BYPRODUCTS ${_mlx_static_lib} ${_mlx_metallib}
170+
set(MLX_BUILD_TESTS
171+
OFF
172+
CACHE BOOL "" FORCE
216173
)
217-
218-
# Imported target for the MLX static library produced by mlx_external. A static
219-
# libmlx.a carries no transitive link deps, so re-add the frameworks MLX itself
220-
# links (mirrors third-party/mlx/CMakeLists.txt:209). CPU is OFF, so Accelerate
221-
# is not needed.
222-
add_library(mlx STATIC IMPORTED GLOBAL)
223-
set_target_properties(mlx PROPERTIES IMPORTED_LOCATION ${_mlx_static_lib})
224-
# Headers come from the submodule source tree (always present), not the build
225-
# dir, so mlxdelegate compilation cannot race the external build. Verified: MLX
226-
# emits no public headers into the build tree (version.h is checked in; the
227-
# version string is a compile-def on version.cpp), so the source dir suffices.
228-
target_include_directories(mlx INTERFACE ${MLX_SOURCE_DIR})
229-
find_library(METAL_FRAMEWORK Metal)
230-
find_library(FOUNDATION_FRAMEWORK Foundation)
231-
find_library(QUARTZ_FRAMEWORK QuartzCore)
232-
set_target_properties(
233-
mlx
234-
PROPERTIES INTERFACE_LINK_LIBRARIES
235-
"${METAL_FRAMEWORK};${FOUNDATION_FRAMEWORK};${QUARTZ_FRAMEWORK}"
174+
set(MLX_BUILD_EXAMPLES
175+
OFF
176+
CACHE BOOL "" FORCE
177+
)
178+
set(MLX_BUILD_BENCHMARKS
179+
OFF
180+
CACHE BOOL "" FORCE
181+
)
182+
set(MLX_BUILD_PYTHON_STUBS
183+
OFF
184+
CACHE BOOL "" FORCE
185+
)
186+
set(MLX_BUILD_CUDA
187+
OFF
188+
CACHE BOOL "" FORCE
189+
)
190+
set(MLX_BUILD_CPU
191+
OFF
192+
CACHE BOOL "" FORCE
193+
)
194+
set(MLX_BUILD_METAL
195+
ON
196+
CACHE BOOL "" FORCE
197+
)
198+
set(MLX_BUILD_SHARED_LIBS
199+
OFF
200+
CACHE BOOL "" FORCE
201+
)
202+
set(MLX_BUILD_GGUF
203+
OFF
204+
CACHE BOOL "" FORCE
205+
)
206+
set(MLX_BUILD_SAFETENSORS
207+
OFF
208+
CACHE BOOL "" FORCE
236209
)
210+
set(MLX_METAL_JIT
211+
ON
212+
CACHE BOOL "Use JIT compiled Metal kernels"
213+
)
214+
215+
# Auto-apply patches to MLX submodule. Each patch is applied idempotently: `git
216+
# apply --check` tests whether the patch is still applicable (i.e. not yet
217+
# applied), and only then applies it.
218+
set(_mlx_patches "${CMAKE_CURRENT_SOURCE_DIR}/patches/mlx_json.patch")
219+
foreach(_patch IN LISTS _mlx_patches)
220+
if(EXISTS "${_patch}" AND EXISTS "${MLX_SOURCE_DIR}")
221+
get_filename_component(_patch_name "${_patch}" NAME)
222+
execute_process(
223+
COMMAND git apply --check "${_patch}"
224+
WORKING_DIRECTORY ${MLX_SOURCE_DIR}
225+
RESULT_VARIABLE _patch_check
226+
OUTPUT_QUIET ERROR_QUIET
227+
)
228+
if(_patch_check EQUAL 0)
229+
execute_process(
230+
COMMAND git apply "${_patch}" WORKING_DIRECTORY ${MLX_SOURCE_DIR}
231+
)
232+
message(STATUS "Applied ${_patch_name} to MLX submodule")
233+
else()
234+
message(STATUS "${_patch_name} already applied or not applicable")
235+
endif()
236+
endif()
237+
endforeach()
238+
239+
# Add MLX subdirectory
240+
message(STATUS "Adding MLX from submodule: ${MLX_SOURCE_DIR}")
241+
add_subdirectory(${MLX_SOURCE_DIR} ${CMAKE_CURRENT_BINARY_DIR}/mlx)
237242

238243
# -----------------------------------------------------------------------------
239244
# MLX Backend library
@@ -261,10 +266,6 @@ add_library(mlxdelegate ${_mlx_backend__srcs})
261266
# Ensure schema is generated before compiling
262267
add_dependencies(mlxdelegate mlx_schema)
263268

264-
# mlx is an imported target, so a dependency on it would not order the build.
265-
# Depend on mlx_external directly so libmlx.a exists before mlxdelegate links.
266-
add_dependencies(mlxdelegate mlx_external)
267-
268269
# Add logging flag if enabled
269270
if(ET_MLX_ENABLE_OP_LOGGING)
270271
target_compile_definitions(mlxdelegate PRIVATE ET_MLX_ENABLE_OP_LOGGING=1)
@@ -282,9 +283,8 @@ target_include_directories(
282283
mlxdelegate PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/runtime
283284
)
284285

285-
# Link against MLX and executorch. mlx is an imported static lib (built by
286-
# mlx_external); it is only consumed at build time here, not re-exported, so
287-
# downstream consumers must link to mlx separately via the package config.
286+
# Link against MLX and executorch mlx is only available at BUILD_INTERFACE -
287+
# consumers must link to mlx separately
288288
target_link_libraries(
289289
mlxdelegate PRIVATE mlx_schema executorch_core $<BUILD_INTERFACE:mlx>
290290
)
@@ -301,10 +301,8 @@ install(
301301
DESTINATION ${CMAKE_INSTALL_LIBDIR}
302302
)
303303

304-
# Install mlx library for downstream consumers. mlx is an imported target (built
305-
# by mlx_external), so install the static lib file directly rather than via
306-
# install(TARGETS ...), which only accepts built targets.
307-
install(FILES ${_mlx_static_lib} DESTINATION ${CMAKE_INSTALL_LIBDIR})
304+
# Install mlx library for downstream consumers
305+
install(TARGETS mlx DESTINATION ${CMAKE_INSTALL_LIBDIR})
308306

309307
# Install mlx headers for downstream consumers that may need mlx types
310308
install(
@@ -327,15 +325,16 @@ install(
327325
# containing MLX code. When MLX is statically linked into _portable_lib.so, this
328326
# is the directory containing _portable_lib.so.
329327
#
330-
# For the installed library, we put metallib in lib/ alongside libmlx.a. The
331-
# metallib is produced in the mlx_external build tree (MLX_METAL_JIT=ON does not
332-
# install it); _mlx_metallib points there.
333-
install(FILES ${_mlx_metallib} DESTINATION ${CMAKE_INSTALL_LIBDIR})
328+
# For the installed library, we put metallib in lib/ alongside libmlx.a
329+
install(
330+
FILES ${CMAKE_CURRENT_BINARY_DIR}/mlx/mlx/backend/metal/kernels/mlx.metallib
331+
DESTINATION ${CMAKE_INSTALL_LIBDIR}
332+
)
334333

335334
# Cache the metallib path for pybindings to copy it next to _portable_lib.so
336335
# This enables editable installs to work correctly
337336
set(MLX_METALLIB_PATH
338-
"${_mlx_metallib}"
337+
"${CMAKE_CURRENT_BINARY_DIR}/mlx/mlx/backend/metal/kernels/mlx.metallib"
339338
CACHE INTERNAL "Path to mlx.metallib for pybindings"
340339
)
341340

backends/mlx/llm/sampling.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,7 @@
1212

1313
class SamplingHead(nn.Module):
1414
"""
15-
Wraps a model that returns last-token logits ``(B, vocab)`` and samples a
16-
token id ``(B)`` on-device.
15+
Wraps a model that returns logits and samples a token id on-device.
1716
1817
forward(*model_args, temperature, top_k=None, top_p=1.0, seed=None,
1918
**model_kwargs) -> token_id
@@ -34,11 +33,12 @@ def __init__(self, model: nn.Module):
3433
self.model = model
3534

3635
def forward(self, *args, temperature, top_k=None, top_p=1.0, seed=None, **kwargs):
37-
logits = self.model(*args, **kwargs) # [B, vocab]
36+
logits = self.model(*args, **kwargs) # [B, S, vocab]
37+
last = logits[:, -1, :] # [B, vocab]
3838
if not isinstance(top_p, torch.Tensor):
3939
top_p = torch.tensor(float(top_p))
4040
if top_k is None:
4141
top_k = torch.tensor(torch.iinfo(torch.int64).max, dtype=torch.int64)
4242
elif not isinstance(top_k, torch.Tensor):
4343
top_k = torch.tensor(int(top_k), dtype=torch.int64)
44-
return torch.ops.mlx.sample(logits, temperature, top_k, top_p, seed)
44+
return torch.ops.mlx.sample(last, temperature, top_k, top_p, seed)
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
diff --git a/CMakeLists.txt b/CMakeLists.txt
2+
--- a/CMakeLists.txt
3+
+++ b/CMakeLists.txt
4+
@@ -304,12 +304,18 @@ else()
5+
set(MLX_BUILD_ACCELERATE OFF)
6+
endif()
7+
8+
-message(STATUS "Downloading json")
9+
-FetchContent_Declare(
10+
- json
11+
- URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz)
12+
-FetchContent_MakeAvailable(json)
13+
-target_include_directories(
14+
- mlx PRIVATE $<BUILD_INTERFACE:${json_SOURCE_DIR}/single_include/nlohmann>)
15+
+# Only fetch json if nlohmann_json target doesn't already exist
16+
+# (ExecuTorch provides its own copy)
17+
+if(NOT TARGET nlohmann_json)
18+
+ message(STATUS "Downloading json")
19+
+ FetchContent_Declare(
20+
+ json
21+
+ URL https://github.com/nlohmann/json/releases/download/v3.11.3/json.tar.xz)
22+
+ FetchContent_MakeAvailable(json)
23+
+ target_include_directories(
24+
+ mlx PRIVATE $<BUILD_INTERFACE:${json_SOURCE_DIR}/single_include/nlohmann>)
25+
+else()
26+
+ message(STATUS "Using existing nlohmann_json target")
27+
+endif()
28+
29+
add_subdirectory(${CMAKE_CURRENT_LIST_DIR}/mlx)

backends/mlx/runtime/MLXBackend.cpp

Lines changed: 3 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,6 @@
1919

2020
#include <mlx/mlx.h>
2121

22-
#include <executorch/backends/mlx/runtime/backend_options.h>
23-
2422
#include <cstring>
2523
#include <limits>
2624
#include <memory>
@@ -171,12 +169,6 @@ struct MLXHandle {
171169
Interpreter interpreter;
172170
::mlx::core::Stream stream; // Dedicated GPU stream for this handle
173171

174-
// Periodically call mlx::core::clear_cache() every N execute() calls to
175-
// release MLX's cached buffer pool. 0 disables. Counter is non-atomic: it is
176-
// only touched inside mlx_global_mutex() (see execute()).
177-
int clear_cache_interval_{0};
178-
uint64_t execute_count_{0};
179-
180172
// Keep the constant buffers alive for zero-copy constants
181173
// Each FreeableBuffer must outlive the MLX arrays that reference it
182174
std::vector<FreeableBuffer> constant_buffers;
@@ -219,13 +211,6 @@ class MLXBackend final : public ::executorch::runtime::BackendInterface {
219211
try {
220212
new (handle) MLXHandle();
221213

222-
// Per-model clear-cache cadence (optional runtime spec, keyed per
223-
// delegate)
224-
if (auto spec = context.get_runtime_spec<int>(kClearCacheIntervalKey);
225-
spec.ok() && spec.get() > 0) {
226-
handle->clear_cache_interval_ = spec.get();
227-
}
228-
229214
if (!processed || !processed->data() || processed->size() == 0) {
230215
throw std::runtime_error("init: null or empty delegate payload");
231216
}
@@ -269,42 +254,8 @@ class MLXBackend final : public ::executorch::runtime::BackendInterface {
269254
processed->Free();
270255
processed = nullptr;
271256

272-
// Load mutable buffers (e.g., KV cache). When skip_mutable_buffer_init is
273-
// set (multi-session, where mlx_mutable_state.h allocates per-session
274-
// buffers), avoid keeping a dead default copy on the handle. This is only
275-
// safe if the init chain does not reference mutable buffers — otherwise
276-
// it would run against an empty store, so we error out clearly.
277-
bool skip_mutable_buffer_init = false;
278-
if (auto spec = context.get_runtime_spec<bool>(kSkipMutableBufferInitKey);
279-
spec.ok()) {
280-
skip_mutable_buffer_init = spec.get();
281-
}
282-
// skip_mutable_buffer_init is only safe under a multi-session owner: the
283-
// per-session manager allocates the buffers and execute() rebinds to
284-
// them. Without an active load scope no handle is registered, no
285-
// per-session buffers are ever created, and execute() would run against
286-
// empty buffers. Reject the misuse loudly here instead of failing later.
287-
if (skip_mutable_buffer_init && !mutable_state_load_scope_active()) {
288-
ET_LOG(
289-
Error,
290-
"skip_mutable_buffer_init set without an active multi-session load "
291-
"scope; mutable buffers would never be allocated");
292-
throw std::runtime_error(
293-
"skip_mutable_buffer_init requires an active multi-session owner");
294-
}
295-
if (skip_mutable_buffer_init &&
296-
init_chain_references_mutable_buffer(handle->program)) {
297-
ET_LOG(
298-
Error,
299-
"skip_mutable_buffer_init set but the init chain references "
300-
"mutable buffers; cannot skip default mutable-buffer init");
301-
throw std::runtime_error(
302-
"skip_mutable_buffer_init incompatible with init chain that "
303-
"references mutable buffers");
304-
}
305-
if (!skip_mutable_buffer_init) {
306-
load_mutable_buffers(handle->program, handle->mutable_buffers);
307-
}
257+
// Load mutable buffers (e.g., KV cache)
258+
load_mutable_buffers(handle->program, handle->mutable_buffers);
308259

309260
// Bind execution state (reused across execute() calls)
310261
handle->state.bind(
@@ -482,16 +433,6 @@ class MLXBackend final : public ::executorch::runtime::BackendInterface {
482433

483434
h->state.reset(); // Release temp GPU buffers back to MLX cache
484435

485-
// Periodically release MLX's cached buffer pool. The counter increment
486-
// and clear_cache() run under the global mutex so they can't race another
487-
// handle's in-flight submission, and the counter needs no atomic.
488-
if (h->clear_cache_interval_ > 0) {
489-
std::lock_guard<std::mutex> lock(mlx_global_mutex());
490-
if ((++h->execute_count_ % h->clear_cache_interval_) == 0) {
491-
::mlx::core::clear_cache();
492-
}
493-
}
494-
495436
return Error::Ok;
496437
} catch (const std::exception& e) {
497438
ET_LOG(Error, "MLX execute failed: %s", e.what());
@@ -514,7 +455,7 @@ class MLXBackend final : public ::executorch::runtime::BackendInterface {
514455

515456
namespace {
516457
auto cls = MLXBackend();
517-
Backend backend{kMLXBackendId, &cls};
458+
Backend backend{"MLXBackend", &cls};
518459
static auto success_with_compiler = register_backend(backend);
519460
} // namespace
520461

0 commit comments

Comments
 (0)