Skip to content

Commit 0a8fb28

Browse files
authored
[executorch][muse] Share the Muse Glimmer embeddings engine across backends
Stack from [ghstack](https://github.com/ezyang/ghstack/tree/0.15.0) (oldest at bottom): * #24076 * #24107 * #24075 * #24074 * #24073 * #24072 * #24066 * #24065 * #24064 * #24063 * __->__ #24062 Move the MLX executor's EngineEmbeddings (vision encoder + embed_text + MuseGlimmerMaterializer) into runtime/model_embeddings so the CUDA batching executors can materialize prepared image inputs the same way. MLX behavior is unchanged; it now holds a MuseGlimmerModelEmbeddings. Differential Revision: D125987290 Pull Request resolved: #24062
1 parent ecc0f4d commit 0a8fb28

8 files changed

Lines changed: 326 additions & 180 deletions

File tree

‎examples/models/muse-glimmer/CMakeLists.txt‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -270,7 +270,7 @@ if(MUSE_GLIMMER_BUILD_BATCHING OR MUSE_GLIMMER_BUILD_BATCHING_TESTS)
270270
add_library(
271271
muse_glimmer_batching_runtime STATIC
272272
runtime/backend_factory.cpp ${_batching_executor}
273-
runtime/runners/batching_bootstrap.cpp
273+
runtime/model_embeddings.cpp runtime/runners/batching_bootstrap.cpp
274274
)
275275
target_link_libraries(
276276
muse_glimmer_batching_runtime

‎examples/models/muse-glimmer/runtime/BUCK‎

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,15 @@ oncall("executorch")
66
fbcode_target(
77
_kind = runtime.cxx_library,
88
name = "cuda_dflash_executor",
9-
srcs = ["cuda_dflash_executor.cpp"],
9+
srcs = [
10+
"cuda_dflash_executor.cpp",
11+
"model_embeddings.cpp",
12+
],
1013
header_namespace = "executorch/examples/models/muse-glimmer/runtime",
1114
exported_headers = [
1215
"backend_factory.h",
1316
"cuda_dflash_executor.h",
14-
"engine/muse_glimmer_vision_runtime.h",
15-
"prompt_preparation.h",
17+
"model_embeddings.h",
1618
],
1719
visibility = ["PUBLIC"],
1820
deps = [
@@ -21,6 +23,7 @@ fbcode_target(
2123
"//executorch/runtime/backend:interface",
2224
],
2325
exported_deps = [
26+
":muse_glimmer_batching_preparation",
2427
"fbsource//third-party/nlohmann-json:nlohmann-json",
2528
"//executorch/backends/cuda/batching:cuda_executor",
2629
"//executorch/extension/llm/batching:batching",
@@ -32,3 +35,33 @@ fbcode_target(
3235
],
3336
external_deps = [("cuda", None, "cuda-lazy"), "nlohmann_json"],
3437
)
38+
39+
fbcode_target(
40+
_kind = runtime.cxx_library,
41+
name = "muse_glimmer_batching_preparation",
42+
srcs = [
43+
"embedding_materializer.cpp",
44+
"engine/muse_glimmer_vision_runtime.cpp",
45+
"prompt_preparation.cpp",
46+
],
47+
header_namespace = "executorch/examples/models/muse-glimmer/runtime",
48+
exported_headers = [
49+
"embedding_materializer.h",
50+
"engine/muse_glimmer_vision_runtime.h",
51+
"prompt_preparation.h",
52+
],
53+
preprocessor_flags = ["-DEXECUTORCH_BUILD_CUDA"],
54+
visibility = ["PUBLIC"],
55+
deps = [
56+
"//executorch/examples/models/muse-glimmer/vision:preprocess",
57+
"//executorch/runtime/platform:platform",
58+
"//pytorch/tokenizers:headers",
59+
],
60+
exported_deps = [
61+
"fbsource//third-party/nlohmann-json:nlohmann-json",
62+
"//executorch/extension/llm/serving:serving_runtime",
63+
"//executorch/extension/module:module",
64+
"//executorch/extension/tensor:tensor",
65+
],
66+
external_deps = [("cuda", None, "cuda-lazy")],
67+
)

‎examples/models/muse-glimmer/runtime/mlx_executor.cpp‎

Lines changed: 11 additions & 173 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@
88
#include <executorch/examples/models/muse-glimmer/runtime/mlx_executor.h>
99

1010
#include <executorch/backends/mlx/runtime/backend_options.h>
11-
#include <executorch/examples/models/muse-glimmer/runtime/embedding_materializer.h>
1211
#include <executorch/examples/models/muse-glimmer/runtime/engine/sampling.h>
12+
#include <executorch/examples/models/muse-glimmer/runtime/model_embeddings.h>
1313
#include <executorch/extension/llm/batching/executor_utils.h>
1414
#include <executorch/extension/llm/runner/model_metadata.h>
1515
#include <executorch/extension/llm/sampler/sampler.h>
@@ -25,118 +25,15 @@
2525

2626
namespace executorch::extension::llm {
2727
namespace {
28+
using muse_glimmer_abi::check_constant_method;
29+
using muse_glimmer_abi::dense_tensor;
30+
using muse_glimmer_abi::tensor_abi;
2831
using runtime::Error;
2932
constexpr const char* kDecoder = "forward_from_embeddings";
3033
constexpr const char* kBackend = "MLXBackend";
3134

32-
bool tensor_abi(
33-
const runtime::TensorInfo& info,
34-
aten::ScalarType dtype,
35-
std::initializer_list<int64_t> shape) {
36-
if (info.scalar_type() != dtype || info.sizes().size() != shape.size() ||
37-
info.dim_order().size() != shape.size()) {
38-
return false;
39-
}
40-
size_t bytes = runtime::elementSize(dtype);
41-
size_t dim = 0;
42-
for (int64_t size : shape) {
43-
if (size <= 0 || size != info.sizes()[dim] ||
44-
info.dim_order()[dim] != dim ||
45-
static_cast<uint64_t>(size) >
46-
std::numeric_limits<size_t>::max() / bytes) {
47-
return false;
48-
}
49-
bytes *= static_cast<size_t>(size);
50-
++dim;
51-
}
52-
return info.nbytes() == bytes;
53-
}
54-
55-
bool dense_tensor(const aten::Tensor& tensor) {
56-
int64_t stride = 1;
57-
for (int dim = tensor.dim() - 1; dim >= 0; --dim) {
58-
if (tensor.size(dim) <= 0 ||
59-
(tensor.size(dim) != 1 && tensor.strides()[dim] != stride) ||
60-
stride > std::numeric_limits<int64_t>::max() / tensor.size(dim)) {
61-
return false;
62-
}
63-
stride *= tensor.size(dim);
64-
}
65-
return tensor.device().is_cpu() && tensor.const_data_ptr() != nullptr;
66-
}
67-
6835
Error check_mlx_backends(const runtime::MethodMeta& meta) {
69-
if (meta.num_backends() == 0) {
70-
return Error::InvalidProgram;
71-
}
72-
for (size_t i = 0; i < meta.num_backends(); ++i) {
73-
ET_ASSIGN_OR_RETURN(name, meta.get_backend_name(i));
74-
if (std::string(name) != kBackend) {
75-
return Error::NotSupported;
76-
}
77-
}
78-
return Error::Ok;
79-
}
80-
81-
// Metadata can be read during create(), but no backend may load off-engine.
82-
Error check_constant_method(Module& module, const char* name) {
83-
ET_ASSIGN_OR_RETURN(meta, module.method_meta(name));
84-
if (meta.num_inputs() != 0 || meta.num_outputs() != 1 ||
85-
meta.num_backends() != 0 || meta.num_instructions() != 0) {
86-
ET_LOG(Error, "Muse Glimmer metadata %s must be constant", name);
87-
return Error::InvalidProgram;
88-
}
89-
return Error::Ok;
90-
}
91-
92-
runtime::Result<int64_t> check_vision_abi(
93-
Module& module,
94-
int32_t hidden,
95-
aten::ScalarType dtype,
96-
int max_patches) {
97-
ET_CHECK_OK_OR_RETURN_ERROR(
98-
check_constant_method(module, "get_max_vision_patches"));
99-
ET_ASSIGN_OR_RETURN(
100-
published_patches,
101-
detail::read_required_positive_int(module, "get_max_vision_patches"));
102-
if (published_patches < 4 ||
103-
published_patches > std::numeric_limits<int32_t>::max()) {
104-
return Error::InvalidProgram;
105-
}
106-
// Export bounds patches in complete 2x2 downsampling groups, but publishes
107-
// the original limit (which need not be divisible by four).
108-
const int64_t patches = (published_patches / 4) * 4;
109-
ET_ASSIGN_OR_RETURN(meta, module.method_meta("vision_encoder"));
110-
if (meta.num_inputs() != 9 || meta.num_outputs() != 1) {
111-
return Error::InvalidProgram;
112-
}
113-
ET_CHECK_OK_OR_RETURN_ERROR(check_mlx_backends(meta));
114-
// Host preprocessing's nine-input ABI; inspect it without loading vision.
115-
for (size_t i = 0; i < 9; ++i) {
116-
ET_ASSIGN_OR_RETURN(input, meta.input_tensor_meta(i));
117-
bool valid = false;
118-
if (i == 0) {
119-
valid = tensor_abi(input, aten::ScalarType::Float, {1, patches, 1176});
120-
} else if (i == 1) {
121-
// Positional interpolation is always BF16, even for an FP16 decoder.
122-
valid = tensor_abi(input, aten::ScalarType::BFloat16, {1, patches, 1536});
123-
} else if (i == 2 || i == 3) {
124-
valid = tensor_abi(input, aten::ScalarType::Float, {patches, 48});
125-
} else if (i == 6 || i == 7) {
126-
valid =
127-
tensor_abi(input, aten::ScalarType::Bool, {1, 1, patches, patches});
128-
} else {
129-
valid = tensor_abi(input, aten::ScalarType::Long, {patches});
130-
}
131-
if (!valid) {
132-
return Error::InvalidProgram;
133-
}
134-
}
135-
ET_ASSIGN_OR_RETURN(output, meta.output_tensor_meta(0));
136-
if (!tensor_abi(output, dtype, {1, patches / 4, hidden})) {
137-
return Error::InvalidProgram;
138-
}
139-
return std::min<int64_t>(patches, max_patches) / 4;
36+
return muse_glimmer_abi::check_backends(meta, kBackend);
14037
}
14138

14239
uint64_t prediction_seed(uint64_t seed, batching::Position position) {
@@ -172,67 +69,6 @@ struct MuseGlimmerMLXExecutor::DFlash {
17269
}
17370
};
17471

175-
struct MuseGlimmerMLXExecutor::EngineEmbeddings {
176-
std::shared_ptr<const MuseGlimmerPreparationSpec> spec;
177-
MuseGlimmerMaterializer materializer;
178-
std::string pos_embed_path;
179-
std::mutex vision_mutex;
180-
std::unique_ptr<MuseGlimmerVisionRuntime> vision;
181-
182-
EngineEmbeddings(
183-
std::shared_ptr<const MuseGlimmerPreparationSpec> spec,
184-
std::string path)
185-
: spec(std::move(spec)),
186-
materializer(this->spec),
187-
pos_embed_path(std::move(path)) {}
188-
189-
Error initialize(Module& module) {
190-
ET_CHECK_OK_OR_RETURN_ERROR(module.load_method("embed_text"));
191-
if (spec->has_vision) {
192-
ET_CHECK_OK_OR_RETURN_ERROR(module.load_method("vision_encoder"));
193-
MuseGlimmerVisionRuntimeConfig config;
194-
config.module = &module;
195-
config.execution_mutex = &vision_mutex;
196-
config.pos_embed_path = pos_embed_path;
197-
config.activation_dtype = spec->activation_dtype;
198-
config.expected_hidden_dim = spec->hidden_dim;
199-
config.max_image_tokens = spec->max_soft_tokens;
200-
config.max_encoded_bytes = spec->image_limits.max_encoded_bytes;
201-
config.max_image_dimension = spec->image_limits.max_image_dimension;
202-
config.max_image_pixels = spec->image_limits.max_image_pixels;
203-
vision = std::make_unique<MuseGlimmerVisionRuntime>(std::move(config));
204-
}
205-
return Error::Ok;
206-
}
207-
208-
runtime::Result<TensorPtr> materialize(
209-
Module& module,
210-
const std::vector<batching::Input>& inputs) {
211-
return materializer.materialize(
212-
inputs,
213-
[&](const MuseGlimmerRGBImage& image)
214-
-> runtime::Result<PreparedMuseGlimmerImage> {
215-
return vision->prepare_decoded_image(
216-
image.rgb.data(), image.width, image.height);
217-
},
218-
[&](const std::vector<int64_t>& tokens) -> runtime::Result<TensorPtr> {
219-
auto input = make_tensor_ptr(
220-
{1, static_cast<aten::SizesType>(tokens.size())}, tokens);
221-
ET_ASSIGN_OR_RETURN(
222-
outputs,
223-
module.execute(
224-
"embed_text", std::vector<runtime::EValue>{input}));
225-
if (outputs.size() != 1 || !outputs[0].isTensor() ||
226-
!dense_tensor(outputs[0].toTensor())) {
227-
return Error::InvalidProgram;
228-
}
229-
// The materializer copies these rows before another method can reuse
230-
// the module's shared output arena.
231-
return make_tensor_ptr(outputs[0].toTensor());
232-
});
233-
}
234-
};
235-
23672
MuseGlimmerMLXExecutor::MuseGlimmerMLXExecutor(
23773
std::unique_ptr<Module> module,
23874
std::shared_ptr<cache::Cache> cache,
@@ -243,8 +79,9 @@ MuseGlimmerMLXExecutor::MuseGlimmerMLXExecutor(
24379
module_(std::move(module)),
24480
ctl_(cache->as<cache::BatchControl>()),
24581
spec_(std::move(spec)),
246-
embeddings_(
247-
std::make_unique<EngineEmbeddings>(spec_, std::move(pos_embed_path))),
82+
embeddings_(std::make_unique<MuseGlimmerModelEmbeddings>(
83+
spec_,
84+
std::move(pos_embed_path))),
24885
max_sessions_(max_sessions) {}
24986

25087
MuseGlimmerMLXExecutor::~MuseGlimmerMLXExecutor() = default;
@@ -364,7 +201,8 @@ runtime::Result<MuseGlimmerBackend> MuseGlimmerMLXExecutor::create(
364201
if (has_vision) {
365202
ET_ASSIGN_OR_RETURN(
366203
soft_tokens,
367-
check_vision_abi(*module, hidden, dtype, config.max_vision_patches));
204+
muse_glimmer_abi::check_vision_abi(
205+
*module, hidden, dtype, config.max_vision_patches, kBackend));
368206
max_soft_tokens = soft_tokens;
369207
}
370208
const int session_capacity = config.max_session_tokens
@@ -711,7 +549,7 @@ void MuseGlimmerMLXExecutor::set_sampling(
711549

712550
bool MuseGlimmerMLXExecutor::accepts(
713551
const batching::PreparedInput& input) const {
714-
return embeddings_->materializer.accepts(input);
552+
return embeddings_->accepts(input);
715553
}
716554

717555
runtime::Result<MuseGlimmerMLXExecutor::Rewinds>

‎examples/models/muse-glimmer/runtime/mlx_executor.h‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121

2222
namespace executorch::extension::llm {
2323

24+
class MuseGlimmerModelEmbeddings;
25+
2426
class MuseGlimmerMLXExecutor final : public batching::Executor {
2527
public:
2628
static runtime::Result<MuseGlimmerBackend> create(
@@ -43,7 +45,6 @@ class MuseGlimmerMLXExecutor final : public batching::Executor {
4345
override;
4446

4547
private:
46-
struct EngineEmbeddings;
4748
struct DFlash;
4849
using Probabilities = std::vector<std::vector<float>>;
4950
struct SessionState {
@@ -94,7 +95,7 @@ class MuseGlimmerMLXExecutor final : public batching::Executor {
9495
std::unique_ptr<Module> module_;
9596
cache::BatchControl* const ctl_;
9697
const std::shared_ptr<const MuseGlimmerPreparationSpec> spec_;
97-
std::unique_ptr<EngineEmbeddings> embeddings_;
98+
std::unique_ptr<MuseGlimmerModelEmbeddings> embeddings_;
9899
const int max_sessions_;
99100
bool initialization_attempted_ = false;
100101
bool initialized_ = false;

0 commit comments

Comments
 (0)