bynn-intelligence commited on
Commit
3dd48fe
·
verified ·
1 Parent(s): 7706b50

Runtime 0.9.2.1: the MLX backend bounds the memory it keeps between requests

Browse files
MANIFEST.sha256 CHANGED
@@ -2,7 +2,7 @@
2
  50cbab8a892c5f2993b8c7351a99182507472def3b1374558308605d99b86b32 ./LICENSE-base
3
  2014f13a6fe1d9178c84cde47bb087a7a74adbe38650f1823a944077081e86d0 ./LICENSING.md
4
  e4dcac398e39a098e07f625ed239949296a2000d95dcc275cb0d8083ebb51dc9 ./NOTICE
5
- 9862c836bb1e4ce3a083862e649fd5bfc2cefadf5ee85edfe98a3113cfb1d1b8 ./README.md
6
  c44489690e63adb3abd8c65ede675fadc5714126e3f866cbd292df824880cb77 ./assets/decisionnode-banner.png
7
  544b3d40cc9e355b16f8c131426c21245c1b5847a46c40c1b4be2c33a447b4fa ./box_calibration.json
8
  5febf5c3cb5843034f2251d959c475a4a629dec65bf3ea494b4fed0a3b8aa3aa ./calibration.json
@@ -15,13 +15,13 @@ d3d84ca9d2701443b84960ba25b4ee21f5125cdf03007b1dc3633c315447ba6a ./model.safete
15
  27225450ac9c6529872ee1924fcb0962ff5634834f817040f444118116f4e516 ./preprocessor_config.json
16
  04603015c9b8bc70dd5e7a5c8f0ff14a3ac52aaa359b16b23f662f8f51edd5cd ./readout.safetensors
17
  0d96a4ff68ad6d4b6f1f30f713b18d5184912ba8dd389f86aa7710db079abcb0 ./runtime/LICENSE
18
- 4343f7ad5a0283d7ecd8c1630ee1c82fde7e8f5c18696f5444629e9399902415 ./runtime/README.md
19
  e0a87595ad939efa5585b61844d3a2a70d29ba7752f68b8139f2195753a6bfde ./runtime/examples/request.json
20
- af77c7510284ebcae3fe0fd6ebd951c2b6bcf88d0b0d73b85c8e45b03b1b8776 ./runtime/pyproject.toml
21
- 1551cc741199ad4ec30b8d16b2a3b82432471e4ef36a75f8677bad7c50771338 ./runtime/src/decisionnode_edge/__init__.py
22
  245003271a41ecab8fe982194acf84f9c4fc0b686762583931a9444ad2aa6354 ./runtime/src/decisionnode_edge/__main__.py
23
  736741c822983f1d7d8ed26ea01227f2f2e902c98dc7d0c21d8fa5abcf219150 ./runtime/src/decisionnode_edge/answers.py
24
- c34f84c5da93d012d329762b6e099bc5aa7b7d998f790b88471f75399cd3eee8 ./runtime/src/decisionnode_edge/backends.py
25
  4ace977690bb7ae16010a298c478f1a5056caaf724c0d696b5ddac6699225f6b ./runtime/src/decisionnode_edge/build.py
26
  3e2d2198d9fd4fac076773e655a0c73c810a932be07ce99460edee64fb7004bc ./runtime/src/decisionnode_edge/model.py
27
  3be9ac7eb2807241d0b9b49cb5797049395f7fbca6ddee9a645274edd1c3e4a5 ./runtime/src/decisionnode_edge/places.py
 
2
  50cbab8a892c5f2993b8c7351a99182507472def3b1374558308605d99b86b32 ./LICENSE-base
3
  2014f13a6fe1d9178c84cde47bb087a7a74adbe38650f1823a944077081e86d0 ./LICENSING.md
4
  e4dcac398e39a098e07f625ed239949296a2000d95dcc275cb0d8083ebb51dc9 ./NOTICE
5
+ 8e38c861ae8950a50cb8fc12d334cc5a10136bf7b2204c5661bc7a29e035ed94 ./README.md
6
  c44489690e63adb3abd8c65ede675fadc5714126e3f866cbd292df824880cb77 ./assets/decisionnode-banner.png
7
  544b3d40cc9e355b16f8c131426c21245c1b5847a46c40c1b4be2c33a447b4fa ./box_calibration.json
8
  5febf5c3cb5843034f2251d959c475a4a629dec65bf3ea494b4fed0a3b8aa3aa ./calibration.json
 
15
  27225450ac9c6529872ee1924fcb0962ff5634834f817040f444118116f4e516 ./preprocessor_config.json
16
  04603015c9b8bc70dd5e7a5c8f0ff14a3ac52aaa359b16b23f662f8f51edd5cd ./readout.safetensors
17
  0d96a4ff68ad6d4b6f1f30f713b18d5184912ba8dd389f86aa7710db079abcb0 ./runtime/LICENSE
18
+ 06b52d22d46c230f3d6215f370520038c6ccabac5f2c9c224c766a3e5aa8e43a ./runtime/README.md
19
  e0a87595ad939efa5585b61844d3a2a70d29ba7752f68b8139f2195753a6bfde ./runtime/examples/request.json
20
+ fdab0f52237084c3793475ffa211fafdf5c3fb5973674131587dae0788613780 ./runtime/pyproject.toml
21
+ 0c067d73ac9a01af3049c980c08f35acd257e4044372fd2066a74db466499a39 ./runtime/src/decisionnode_edge/__init__.py
22
  245003271a41ecab8fe982194acf84f9c4fc0b686762583931a9444ad2aa6354 ./runtime/src/decisionnode_edge/__main__.py
23
  736741c822983f1d7d8ed26ea01227f2f2e902c98dc7d0c21d8fa5abcf219150 ./runtime/src/decisionnode_edge/answers.py
24
+ 1488cd43642f6bd49b810683b7e72688e98dc62d2e955c202331e027a62b1889 ./runtime/src/decisionnode_edge/backends.py
25
  4ace977690bb7ae16010a298c478f1a5056caaf724c0d696b5ddac6699225f6b ./runtime/src/decisionnode_edge/build.py
26
  3e2d2198d9fd4fac076773e655a0c73c810a932be07ce99460edee64fb7004bc ./runtime/src/decisionnode_edge/model.py
27
  3be9ac7eb2807241d0b9b49cb5797049395f7fbca6ddee9a645274edd1c3e4a5 ./runtime/src/decisionnode_edge/places.py
README.md CHANGED
@@ -117,7 +117,7 @@ Pass images as base64 `data` with a `media_type` (not a local `path`), so the sa
117
  | | |
118
  |---|---|
119
  | Developer | Bynn Intelligence Inc. |
120
- | Version | 0.9.2 (October 2026): the 0.9.1 weights with runtime 0.9.2, which adds point and box questions |
121
  | Parameters | 2.21 billion (1.88 billion in the language model, 0.33 billion in the vision encoder) |
122
  | Base model | [Qwen3.5-2B-Base](https://huggingface.co/Qwen/Qwen3.5-2B-Base) |
123
  | Input | Text, and up to 16 images per request (JPEG, PNG or WebP) |
 
117
  | | |
118
  |---|---|
119
  | Developer | Bynn Intelligence Inc. |
120
+ | Version | 0.9.2 (October 2026): the 0.9.1 weights with runtime 0.9.2, which adds point and box questions. Runtime 0.9.2.1 bounds the memory MLX keeps between requests on Apple silicon |
121
  | Parameters | 2.21 billion (1.88 billion in the language model, 0.33 billion in the vision encoder) |
122
  | Base model | [Qwen3.5-2B-Base](https://huggingface.co/Qwen/Qwen3.5-2B-Base) |
123
  | Input | Text, and up to 16 images per request (JPEG, PNG or WebP) |
runtime/README.md CHANGED
@@ -204,6 +204,7 @@ Options can be flat (`{"threads": 16}`) or under a backend's name (`{"openvino":
204
  | `llamacpp` | `server`, `gguf`: the program and the model file | `llama-server` on `PATH`, the build's file |
205
  | `llamacpp` | `gpu_layers`, `threads` | 999 (all layers on the GPU when there is one), llama.cpp's choice |
206
  | `llamacpp` | `context`: the longest prompt llama.cpp takes | 16,384 |
 
207
 
208
  vLLM runs its engine inside your process and uses the first visible GPU (choose it with `CUDA_VISIBLE_DEVICES`). Its first load takes about half a minute while it compiles. MLX uses the Apple GPU.
209
 
 
204
  | `llamacpp` | `server`, `gguf`: the program and the model file | `llama-server` on `PATH`, the build's file |
205
  | `llamacpp` | `gpu_layers`, `threads` | 999 (all layers on the GPU when there is one), llama.cpp's choice |
206
  | `llamacpp` | `context`: the longest prompt llama.cpp takes | 16,384 |
207
+ | `mlx` | `cache_limit_gb`: how much freed GPU memory MLX keeps for reuse | 2 |
208
 
209
  vLLM runs its engine inside your process and uses the first visible GPU (choose it with `CUDA_VISIBLE_DEVICES`). Its first load takes about half a minute while it compiles. MLX uses the Apple GPU.
210
 
runtime/pyproject.toml CHANGED
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
 
5
  [project]
6
  name = "decisionnode-edge"
7
- version = "0.9.2"
8
  description = "Run DecisionNode Edge locally: typed, calibrated decisions in the shape of the DecisionNode API."
9
  readme = "README.md"
10
  requires-python = ">=3.10"
 
4
 
5
  [project]
6
  name = "decisionnode-edge"
7
+ version = "0.9.2.1"
8
  description = "Run DecisionNode Edge locally: typed, calibrated decisions in the shape of the DecisionNode API."
9
  readme = "README.md"
10
  requires-python = ">=3.10"
runtime/src/decisionnode_edge/__init__.py CHANGED
@@ -10,4 +10,4 @@ from .model import EdgeModel
10
  from .request import RequestError
11
 
12
  __all__ = ["EdgeModel", "RequestError"]
13
- __version__ = "0.9.2"
 
10
  from .request import RequestError
11
 
12
  __all__ = ["EdgeModel", "RequestError"]
13
+ __version__ = "0.9.2.1"
runtime/src/decisionnode_edge/backends.py CHANGED
@@ -165,7 +165,7 @@ def load(name: str, path: str, config: dict, artifacts: str | None, options: dic
165
  if name == "vllm":
166
  return VLLM(path, max_tokens, options, device)
167
  if name == "mlx":
168
- return MLX(path)
169
  build = read_build(artifacts)
170
  if build is not None and build.get("model_fingerprint") != fingerprint(path):
171
  raise ValueError(f"{artifacts} was built from another model than {path}")
@@ -304,14 +304,18 @@ class MLX:
304
  touches readout.safetensors); the text model's output is the hidden state after the final norm."""
305
 
306
  name = "mlx"
 
307
 
308
- def __init__(self, path: str):
309
  from pathlib import Path
310
 
311
  import mlx.core as mx
312
  from mlx_lm.utils import load_model
313
 
314
  self.mx = mx
 
 
 
315
  model, _ = load_model(Path(path))
316
  self.text = model.language_model.model
317
 
 
165
  if name == "vllm":
166
  return VLLM(path, max_tokens, options, device)
167
  if name == "mlx":
168
+ return MLX(path, options)
169
  build = read_build(artifacts)
170
  if build is not None and build.get("model_fingerprint") != fingerprint(path):
171
  raise ValueError(f"{artifacts} was built from another model than {path}")
 
304
  touches readout.safetensors); the text model's output is the hidden state after the final norm."""
305
 
306
  name = "mlx"
307
+ cache_limit_gb = 2.0
308
 
309
+ def __init__(self, path: str, options: dict | None = None):
310
  from pathlib import Path
311
 
312
  import mlx.core as mx
313
  from mlx_lm.utils import load_model
314
 
315
  self.mx = mx
316
+ # MLX keeps freed buffers for reuse, by default up to its memory limit, so a server reading long prompts grew its
317
+ # cache to most of the machine's memory. A bound keeps the reuse that matters for speed.
318
+ mx.set_cache_limit(int(float((options or {}).get("cache_limit_gb", self.cache_limit_gb)) * 2**30))
319
  model, _ = load_model(Path(path))
320
  self.text = model.language_model.model
321