mlx-bun 0.0.11 → 0.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +63 -27
- package/THIRD_PARTY_LICENSES.md +9 -0
- package/docs/design/colibri-glm52-port.md +1486 -0
- package/docs/design/pre-colibri-stabilization.md +329 -0
- package/docs/investigations/colibri-oracle-pin.md +1284 -0
- package/docs/investigations/indexshare-performance-spike.md +357 -0
- package/docs/planning/release-notes-v0.0.12.md +80 -0
- package/docs/planning/release-notes-v0.0.13.md +77 -0
- package/docs/reference/cli.md +28 -2
- package/docs/reference/distribution.md +25 -1
- package/docs/reference/embedding.md +5 -2
- package/docs/reference/features-matrix.md +5 -2
- package/docs/reference/library-api.md +41 -13
- package/docs/reference/models.md +49 -2
- package/docs/reference/server-api.md +113 -20
- package/docs/reference/server-config.md +21 -14
- package/docs/reference/training.md +10 -1
- package/package.json +1 -1
- package/src/anthropic.ts +54 -44
- package/src/chat-template.ts +189 -11
- package/src/cli.ts +127 -8
- package/src/config.ts +81 -26
- package/src/diffusion/diffusion-generate.ts +14 -8
- package/src/download.ts +103 -12
- package/src/eval/ifeval.ts +63 -120
- package/src/eval/runner.ts +14 -5
- package/src/eval/tasks/ifeval.ts +167 -31
- package/src/expert-io.ts +391 -0
- package/src/expert-residency.ts +1134 -0
- package/src/expert-usage.ts +415 -0
- package/src/generate.ts +114 -17
- package/src/hf-push.ts +1 -1
- package/src/index.ts +14 -2
- package/src/jobs/runner.ts +37 -18
- package/src/kv-store.ts +147 -8
- package/src/lora.ts +77 -14
- package/src/media-fetch.ts +198 -56
- package/src/mlx/array.ts +43 -0
- package/src/mlx/autograd.ts +51 -14
- package/src/mlx/checkpoint.ts +46 -13
- package/src/mlx/custom-vjp.ts +83 -35
- package/src/model/factory.ts +96 -5
- package/src/model/gemma4-base.ts +32 -0
- package/src/model/glm52-atlas.ts +511 -0
- package/src/model/glm52-cache.ts +706 -0
- package/src/model/glm52-config.ts +262 -0
- package/src/model/glm52-container.ts +305 -0
- package/src/model/glm52-coupling.ts +388 -0
- package/src/model/glm52-dsa.ts +602 -0
- package/src/model/glm52-expert-layout.ts +253 -0
- package/src/model/glm52-layout.ts +152 -0
- package/src/model/glm52-memory.ts +473 -0
- package/src/model/glm52-mla.ts +786 -0
- package/src/model/glm52-moe.ts +223 -0
- package/src/model/glm52-pilot.ts +537 -0
- package/src/model/glm52-reference.ts +445 -0
- package/src/model/glm52-residency.ts +557 -0
- package/src/model/glm52-resident-weights.ts +135 -0
- package/src/model/glm52-streamed-experts.ts +453 -0
- package/src/model/glm52-streamed-metal.ts +610 -0
- package/src/model/glm52-weights.ts +503 -0
- package/src/model/glm52.ts +1153 -0
- package/src/model/support.ts +8 -1
- package/src/native/expert_io.c +523 -0
- package/src/native-pack.ts +6 -4
- package/src/responses.ts +62 -49
- package/src/serve/batch-scheduler.ts +95 -16
- package/src/serve/generation-gateway.ts +41 -16
- package/src/server.ts +471 -93
- package/src/spec/glm52-mtp-source.ts +392 -0
- package/src/spec/serve-loop.ts +99 -18
- package/src/spec/source.ts +31 -6
- package/src/ssd-cache.ts +6 -2
- package/src/tool-call.ts +30 -2
- package/src/train/job.ts +0 -10
- package/src/train/lora-params.ts +17 -6
- package/src/train/trainer.ts +59 -26
- package/src/web/app.html +13 -2
- package/src/web/app.js +30 -3
- package/src/web/src/status.ts +51 -3
- package/src/web-tools.ts +54 -2
- package/src/weights.ts +82 -19
package/src/model/support.ts
CHANGED
|
@@ -39,6 +39,12 @@ export function isQwen3MoeConfig(config: ModelConfig): boolean {
|
|
|
39
39
|
return config.modelType === "qwen3_moe";
|
|
40
40
|
}
|
|
41
41
|
|
|
42
|
+
/** GLM-5.2 Colibri architecture. Its dedicated MLA/DSA/shared-MoE graph is
|
|
43
|
+
* intentionally distinct from the universal dense `glm4` descriptor. */
|
|
44
|
+
export function isGlm52Config(config: ModelConfig): boolean {
|
|
45
|
+
return config.modelType === "glm_moe_dsa";
|
|
46
|
+
}
|
|
47
|
+
|
|
42
48
|
/** Speculative-decoding drafters (e.g. `gemma4_assistant`) are companion
|
|
43
49
|
* artifacts to a target model — Q-only, centroid-head, no standalone LM
|
|
44
50
|
* head. They are never servable/selectable on their own (the spec path
|
|
@@ -67,6 +73,7 @@ export function supportTier(modelType: string, repoId = ""): "targeted" | "gener
|
|
|
67
73
|
if (modelType === "qwen3_5" || modelType === "qwen3_5_text") return "targeted";
|
|
68
74
|
if (modelType === "qwen3") return "targeted"; // plain Qwen3 (embedding backbone)
|
|
69
75
|
if (modelType === "qwen3_moe") return "targeted"; // Qwen3-MoE (sparse experts)
|
|
76
|
+
if (modelType === "glm_moe_dsa") return "targeted"; // dedicated GLM-5.2 MLA/DSA port
|
|
70
77
|
if (modelType === "llama" && repoId.toLowerCase().includes("minicpm5-1b-optiq-4bit"))
|
|
71
78
|
return "targeted";
|
|
72
79
|
// Tier-0 generic fallback: the universal-dense descriptor table.
|
|
@@ -83,7 +90,7 @@ export function isSupportedModelConfig(config: ModelConfig): boolean {
|
|
|
83
90
|
if (
|
|
84
91
|
config.modelType.startsWith("gemma4") || isMiniCPM5Config(config) ||
|
|
85
92
|
isQwen35Config(config) || isQwen3Config(config) || isQwen3MoeConfig(config) ||
|
|
86
|
-
isDiffusionGemmaConfig(config)
|
|
93
|
+
isDiffusionGemmaConfig(config) || isGlm52Config(config)
|
|
87
94
|
) return true;
|
|
88
95
|
// Generic tier: an arch descriptor exists for this model_type (and its
|
|
89
96
|
// config parses — a malformed config is unsupported, not a crash).
|
|
@@ -0,0 +1,523 @@
|
|
|
1
|
+
#include <errno.h>
|
|
2
|
+
#include <fcntl.h>
|
|
3
|
+
#include <limits.h>
|
|
4
|
+
#include <pthread.h>
|
|
5
|
+
#include <stdint.h>
|
|
6
|
+
#include <stdlib.h>
|
|
7
|
+
#include <string.h>
|
|
8
|
+
#include <sys/mman.h>
|
|
9
|
+
#include <sys/resource.h>
|
|
10
|
+
#include <unistd.h>
|
|
11
|
+
#ifdef __APPLE__
|
|
12
|
+
#include <mach/mach.h>
|
|
13
|
+
#endif
|
|
14
|
+
|
|
15
|
+
typedef struct expert_io_pool expert_io_pool;
|
|
16
|
+
#define EXPERT_IO_MAX_SEGMENTS 8
|
|
17
|
+
#define EXPERT_IO_HINT_QUEUE_CAP 4096
|
|
18
|
+
#define EXPERT_IO_HINT_STATS 8
|
|
19
|
+
typedef struct {
|
|
20
|
+
uint32_t slot, segment_count;
|
|
21
|
+
uint64_t generation;
|
|
22
|
+
uint32_t file_index[EXPERT_IO_MAX_SEGMENTS];
|
|
23
|
+
uint64_t offset[EXPERT_IO_MAX_SEGMENTS];
|
|
24
|
+
uint64_t destination[EXPERT_IO_MAX_SEGMENTS];
|
|
25
|
+
uint64_t length[EXPERT_IO_MAX_SEGMENTS];
|
|
26
|
+
} io_job;
|
|
27
|
+
typedef struct {
|
|
28
|
+
uint32_t segment_count;
|
|
29
|
+
uint32_t file_index[EXPERT_IO_MAX_SEGMENTS];
|
|
30
|
+
uint64_t offset[EXPERT_IO_MAX_SEGMENTS];
|
|
31
|
+
uint64_t length[EXPERT_IO_MAX_SEGMENTS];
|
|
32
|
+
} io_hint_job;
|
|
33
|
+
typedef struct {
|
|
34
|
+
void *data;
|
|
35
|
+
uint64_t generation, capacity, length;
|
|
36
|
+
int state, error, cancelled, gpu_lease, wired;
|
|
37
|
+
} io_slot;
|
|
38
|
+
|
|
39
|
+
struct expert_io_pool {
|
|
40
|
+
int stopping;
|
|
41
|
+
int wire_slots;
|
|
42
|
+
uint32_t file_count, slot_count, worker_count, qcap, qhead, qtail, qlen;
|
|
43
|
+
uint32_t hint_qcap, hint_qhead, hint_qtail, hint_qlen, hint_inflight;
|
|
44
|
+
int mu_init, work_init, changed_init, hint_work_init, hint_worker_started;
|
|
45
|
+
int *fds;
|
|
46
|
+
int *hint_fds;
|
|
47
|
+
io_slot *slots;
|
|
48
|
+
io_job *queue;
|
|
49
|
+
io_hint_job *hint_queue;
|
|
50
|
+
pthread_t *workers;
|
|
51
|
+
pthread_t hint_worker;
|
|
52
|
+
pthread_mutex_t mu;
|
|
53
|
+
pthread_cond_t work, changed, hint_work;
|
|
54
|
+
uint64_t hint_submitted, hint_completed, hint_dropped;
|
|
55
|
+
uint64_t hint_operations, hint_bytes, hint_errors;
|
|
56
|
+
};
|
|
57
|
+
|
|
58
|
+
enum { SLOT_IDLE = 0, SLOT_LOADING = 1, SLOT_READY = 2, SLOT_LEASED = 3 };
|
|
59
|
+
|
|
60
|
+
static int pread_full(int fd, void *dst, uint64_t length, uint64_t offset) {
|
|
61
|
+
uint8_t *p = dst;
|
|
62
|
+
while (length) {
|
|
63
|
+
size_t ask = length > (uint64_t)SSIZE_MAX ? (size_t)SSIZE_MAX : (size_t)length;
|
|
64
|
+
ssize_t n = pread(fd, p, ask, (off_t)offset);
|
|
65
|
+
if (n < 0 && errno == EINTR) continue;
|
|
66
|
+
if (n < 0) return errno;
|
|
67
|
+
if (n == 0) return ENODATA;
|
|
68
|
+
p += n; offset += (uint64_t)n; length -= (uint64_t)n;
|
|
69
|
+
}
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
static void *worker_main(void *arg) {
|
|
74
|
+
expert_io_pool *p = arg;
|
|
75
|
+
for (;;) {
|
|
76
|
+
pthread_mutex_lock(&p->mu);
|
|
77
|
+
while (!p->stopping && p->qlen == 0) pthread_cond_wait(&p->work, &p->mu);
|
|
78
|
+
if (p->stopping && p->qlen == 0) { pthread_mutex_unlock(&p->mu); return NULL; }
|
|
79
|
+
io_job job = p->queue[p->qhead];
|
|
80
|
+
p->qhead = (p->qhead + 1) % p->qcap; p->qlen--;
|
|
81
|
+
io_slot *slot = &p->slots[job.slot];
|
|
82
|
+
pthread_mutex_unlock(&p->mu);
|
|
83
|
+
int error = 0;
|
|
84
|
+
uint64_t extent = 0;
|
|
85
|
+
for (uint32_t i = 0; i < job.segment_count && !error; i++) {
|
|
86
|
+
error = pread_full(
|
|
87
|
+
p->fds[job.file_index[i]],
|
|
88
|
+
(uint8_t *)slot->data + job.destination[i],
|
|
89
|
+
job.length[i],
|
|
90
|
+
job.offset[i]
|
|
91
|
+
);
|
|
92
|
+
uint64_t end = job.destination[i] + job.length[i];
|
|
93
|
+
if (end > extent) extent = end;
|
|
94
|
+
}
|
|
95
|
+
pthread_mutex_lock(&p->mu);
|
|
96
|
+
if (slot->generation == job.generation && slot->state == SLOT_LOADING) {
|
|
97
|
+
slot->error = slot->cancelled ? ECANCELED : error;
|
|
98
|
+
slot->length = slot->error ? 0 : extent; slot->state = SLOT_READY;
|
|
99
|
+
}
|
|
100
|
+
pthread_cond_broadcast(&p->changed);
|
|
101
|
+
pthread_mutex_unlock(&p->mu);
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
static int advise_willneed(int fd, uint64_t offset, uint64_t length) {
|
|
106
|
+
#ifdef __APPLE__
|
|
107
|
+
while (length) {
|
|
108
|
+
int chunk = length > (uint64_t)INT_MAX ? INT_MAX : (int)length;
|
|
109
|
+
struct radvisory advice;
|
|
110
|
+
advice.ra_offset = (off_t)offset;
|
|
111
|
+
advice.ra_count = chunk;
|
|
112
|
+
if (fcntl(fd, F_RDADVISE, &advice) != 0) return errno;
|
|
113
|
+
offset += (uint64_t)chunk;
|
|
114
|
+
length -= (uint64_t)chunk;
|
|
115
|
+
}
|
|
116
|
+
return 0;
|
|
117
|
+
#elif defined(POSIX_FADV_WILLNEED)
|
|
118
|
+
int status = posix_fadvise(fd, (off_t)offset, (off_t)length,
|
|
119
|
+
POSIX_FADV_WILLNEED);
|
|
120
|
+
return status;
|
|
121
|
+
#else
|
|
122
|
+
(void)fd; (void)offset; (void)length;
|
|
123
|
+
return 0;
|
|
124
|
+
#endif
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
static void *hint_worker_main(void *arg) {
|
|
128
|
+
expert_io_pool *p = arg;
|
|
129
|
+
for (;;) {
|
|
130
|
+
pthread_mutex_lock(&p->mu);
|
|
131
|
+
while (!p->stopping && p->hint_qlen == 0)
|
|
132
|
+
pthread_cond_wait(&p->hint_work, &p->mu);
|
|
133
|
+
if (p->stopping) { pthread_mutex_unlock(&p->mu); return NULL; }
|
|
134
|
+
io_hint_job job = p->hint_queue[p->hint_qhead];
|
|
135
|
+
p->hint_qhead = (p->hint_qhead + 1) % p->hint_qcap;
|
|
136
|
+
p->hint_qlen--;
|
|
137
|
+
p->hint_inflight = 1;
|
|
138
|
+
pthread_mutex_unlock(&p->mu);
|
|
139
|
+
|
|
140
|
+
int error = 0;
|
|
141
|
+
for (uint32_t i = 0; i < job.segment_count && !error; i++) {
|
|
142
|
+
error = advise_willneed(
|
|
143
|
+
p->hint_fds[job.file_index[i]], job.offset[i], job.length[i]
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
pthread_mutex_lock(&p->mu);
|
|
148
|
+
p->hint_completed++;
|
|
149
|
+
if (error) p->hint_errors++;
|
|
150
|
+
p->hint_inflight = 0;
|
|
151
|
+
pthread_cond_broadcast(&p->changed);
|
|
152
|
+
pthread_mutex_unlock(&p->mu);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
static expert_io_pool *open_pool(const char *const *paths, uint32_t file_count,
|
|
157
|
+
uint32_t slots, uint64_t capacity,
|
|
158
|
+
uint64_t alignment, uint32_t workers, int no_cache) {
|
|
159
|
+
if (!paths || !file_count || !slots || slots > UINT32_MAX / 2 || !capacity ||
|
|
160
|
+
!workers || workers > 64 || alignment < sizeof(void *) ||
|
|
161
|
+
(alignment & (alignment - 1))) { errno = EINVAL; return NULL; }
|
|
162
|
+
expert_io_pool *p = calloc(1, sizeof(*p));
|
|
163
|
+
if (!p) return NULL;
|
|
164
|
+
p->file_count = file_count;
|
|
165
|
+
p->fds = malloc((size_t)file_count * sizeof(*p->fds));
|
|
166
|
+
p->hint_fds = malloc((size_t)file_count * sizeof(*p->hint_fds));
|
|
167
|
+
if (!p->fds || !p->hint_fds) {
|
|
168
|
+
free(p->hint_fds); free(p->fds); free(p); return NULL;
|
|
169
|
+
}
|
|
170
|
+
for (uint32_t i = 0; i < file_count; i++) {
|
|
171
|
+
p->fds[i] = -1;
|
|
172
|
+
p->hint_fds[i] = -1;
|
|
173
|
+
}
|
|
174
|
+
for (uint32_t i = 0; i < file_count; i++) {
|
|
175
|
+
if (!paths[i] || !paths[i][0]) { errno = EINVAL; goto fail; }
|
|
176
|
+
p->fds[i] = open(paths[i], O_RDONLY);
|
|
177
|
+
if (p->fds[i] < 0) goto fail;
|
|
178
|
+
p->hint_fds[i] = open(paths[i], O_RDONLY);
|
|
179
|
+
if (p->hint_fds[i] < 0) goto fail;
|
|
180
|
+
#ifdef F_NOCACHE
|
|
181
|
+
if (no_cache && fcntl(p->fds[i], F_NOCACHE, 1) != 0) goto fail;
|
|
182
|
+
#else
|
|
183
|
+
(void)no_cache;
|
|
184
|
+
#endif
|
|
185
|
+
}
|
|
186
|
+
p->slot_count = slots; p->qcap = slots * 2;
|
|
187
|
+
p->hint_qcap = EXPERT_IO_HINT_QUEUE_CAP;
|
|
188
|
+
p->slots = calloc(slots, sizeof(*p->slots)); p->queue = calloc(p->qcap, sizeof(*p->queue));
|
|
189
|
+
p->hint_queue = calloc(p->hint_qcap, sizeof(*p->hint_queue));
|
|
190
|
+
p->workers = calloc(workers, sizeof(*p->workers));
|
|
191
|
+
if (!p->slots || !p->queue || !p->hint_queue || !p->workers) goto fail;
|
|
192
|
+
if (pthread_mutex_init(&p->mu, NULL)) goto fail; p->mu_init = 1;
|
|
193
|
+
if (pthread_cond_init(&p->work, NULL)) goto fail_started; p->work_init = 1;
|
|
194
|
+
if (pthread_cond_init(&p->changed, NULL)) goto fail_started; p->changed_init = 1;
|
|
195
|
+
if (pthread_cond_init(&p->hint_work, NULL)) goto fail_started; p->hint_work_init = 1;
|
|
196
|
+
for (uint32_t i = 0; i < slots; i++) {
|
|
197
|
+
if (posix_memalign(&p->slots[i].data, (size_t)alignment, (size_t)capacity)) goto fail_started;
|
|
198
|
+
p->slots[i].capacity = capacity;
|
|
199
|
+
}
|
|
200
|
+
for (uint32_t i = 0; i < workers; i++) {
|
|
201
|
+
if (pthread_create(&p->workers[i], NULL, worker_main, p)) goto fail_started;
|
|
202
|
+
p->worker_count++;
|
|
203
|
+
}
|
|
204
|
+
if (pthread_create(&p->hint_worker, NULL, hint_worker_main, p))
|
|
205
|
+
goto fail_started;
|
|
206
|
+
p->hint_worker_started = 1;
|
|
207
|
+
return p;
|
|
208
|
+
fail_started:
|
|
209
|
+
if (p->mu_init) {
|
|
210
|
+
pthread_mutex_lock(&p->mu); p->stopping = 1;
|
|
211
|
+
if (p->work_init) pthread_cond_broadcast(&p->work);
|
|
212
|
+
if (p->hint_work_init) pthread_cond_broadcast(&p->hint_work);
|
|
213
|
+
pthread_mutex_unlock(&p->mu);
|
|
214
|
+
}
|
|
215
|
+
for (uint32_t i = 0; i < p->worker_count; i++) pthread_join(p->workers[i], NULL);
|
|
216
|
+
if (p->hint_worker_started) pthread_join(p->hint_worker, NULL);
|
|
217
|
+
if (p->hint_work_init) pthread_cond_destroy(&p->hint_work);
|
|
218
|
+
if (p->changed_init) pthread_cond_destroy(&p->changed);
|
|
219
|
+
if (p->work_init) pthread_cond_destroy(&p->work);
|
|
220
|
+
if (p->mu_init) pthread_mutex_destroy(&p->mu);
|
|
221
|
+
fail:
|
|
222
|
+
if (p->slots) for (uint32_t i = 0; i < slots; i++) free(p->slots[i].data);
|
|
223
|
+
free(p->workers); free(p->hint_queue); free(p->queue); free(p->slots);
|
|
224
|
+
if (p->fds) for (uint32_t i = 0; i < file_count; i++) if (p->fds[i] >= 0) close(p->fds[i]);
|
|
225
|
+
if (p->hint_fds) for (uint32_t i = 0; i < file_count; i++) if (p->hint_fds[i] >= 0) close(p->hint_fds[i]);
|
|
226
|
+
free(p->hint_fds); free(p->fds); free(p); return NULL;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
expert_io_pool *mlx_bun_expert_io_open(const char *path, uint32_t slots, uint64_t capacity,
|
|
230
|
+
uint64_t alignment, uint32_t workers, int no_cache) {
|
|
231
|
+
const char *paths[1] = {path};
|
|
232
|
+
return open_pool(paths, 1, slots, capacity, alignment, workers, no_cache);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
expert_io_pool *mlx_bun_expert_io_open_many(const char *path_blob, uint64_t path_blob_length,
|
|
236
|
+
uint32_t file_count, uint32_t slots,
|
|
237
|
+
uint64_t capacity, uint64_t alignment,
|
|
238
|
+
uint32_t workers, int no_cache) {
|
|
239
|
+
if (!path_blob || !path_blob_length || !file_count ||
|
|
240
|
+
path_blob_length > (uint64_t)SIZE_MAX) { errno = EINVAL; return NULL; }
|
|
241
|
+
const char **paths = calloc(file_count, sizeof(*paths));
|
|
242
|
+
if (!paths) return NULL;
|
|
243
|
+
uint64_t cursor = 0;
|
|
244
|
+
int valid = 1;
|
|
245
|
+
for (uint32_t i = 0; i < file_count; i++) {
|
|
246
|
+
if (cursor >= path_blob_length) { valid = 0; break; }
|
|
247
|
+
paths[i] = path_blob + cursor;
|
|
248
|
+
size_t remaining = (size_t)(path_blob_length - cursor);
|
|
249
|
+
size_t length = strnlen(paths[i], remaining);
|
|
250
|
+
if (length == 0 || length == remaining) { valid = 0; break; }
|
|
251
|
+
cursor += (uint64_t)length + 1;
|
|
252
|
+
}
|
|
253
|
+
if (cursor != path_blob_length) valid = 0;
|
|
254
|
+
expert_io_pool *pool = valid
|
|
255
|
+
? open_pool(paths, file_count, slots, capacity, alignment, workers, no_cache)
|
|
256
|
+
: NULL;
|
|
257
|
+
free(paths);
|
|
258
|
+
if (!valid) errno = EINVAL;
|
|
259
|
+
return pool;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
int mlx_bun_expert_io_submitv(expert_io_pool *p, uint32_t slot_id, uint64_t generation,
|
|
263
|
+
const uint32_t *file_index, const uint64_t *offset,
|
|
264
|
+
const uint64_t *destination, const uint64_t *length,
|
|
265
|
+
uint32_t segment_count) {
|
|
266
|
+
if (!p || slot_id >= p->slot_count || !generation || !file_index || !offset ||
|
|
267
|
+
!destination || !length || !segment_count ||
|
|
268
|
+
segment_count > EXPERT_IO_MAX_SEGMENTS) return EINVAL;
|
|
269
|
+
uint64_t extent = 0;
|
|
270
|
+
for (uint32_t i = 0; i < segment_count; i++) {
|
|
271
|
+
if (file_index[i] >= p->file_count || !length[i]) return EINVAL;
|
|
272
|
+
if (offset[i] > INT64_MAX || length[i] > (uint64_t)INT64_MAX - offset[i])
|
|
273
|
+
return EOVERFLOW;
|
|
274
|
+
if (destination[i] > p->slots[slot_id].capacity ||
|
|
275
|
+
length[i] > p->slots[slot_id].capacity - destination[i]) return EBUSY;
|
|
276
|
+
uint64_t end = destination[i] + length[i];
|
|
277
|
+
if (end > extent) extent = end;
|
|
278
|
+
for (uint32_t prior = 0; prior < i; prior++) {
|
|
279
|
+
uint64_t prior_end = destination[prior] + length[prior];
|
|
280
|
+
if (destination[i] < prior_end && destination[prior] < end) return EINVAL;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
pthread_mutex_lock(&p->mu);
|
|
284
|
+
io_slot *slot = &p->slots[slot_id];
|
|
285
|
+
int error = 0;
|
|
286
|
+
if (extent > slot->capacity || slot->state == SLOT_LOADING || slot->state == SLOT_LEASED) error = EBUSY;
|
|
287
|
+
else if (p->qlen == p->qcap) error = EAGAIN;
|
|
288
|
+
else {
|
|
289
|
+
if (p->wire_slots && !slot->wired) {
|
|
290
|
+
if (mlock(slot->data, (size_t)slot->capacity) != 0) error = errno;
|
|
291
|
+
else slot->wired = 1;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
if (!error) {
|
|
295
|
+
slot->generation = generation; slot->length = 0; slot->error = 0; slot->cancelled = 0; slot->state = SLOT_LOADING;
|
|
296
|
+
io_job *job = &p->queue[p->qtail];
|
|
297
|
+
memset(job, 0, sizeof(*job));
|
|
298
|
+
job->slot = slot_id; job->generation = generation; job->segment_count = segment_count;
|
|
299
|
+
for (uint32_t i = 0; i < segment_count; i++) {
|
|
300
|
+
job->file_index[i] = file_index[i];
|
|
301
|
+
job->offset[i] = offset[i];
|
|
302
|
+
job->destination[i] = destination[i];
|
|
303
|
+
job->length[i] = length[i];
|
|
304
|
+
}
|
|
305
|
+
p->qtail = (p->qtail + 1) % p->qcap; p->qlen++;
|
|
306
|
+
pthread_cond_signal(&p->work);
|
|
307
|
+
}
|
|
308
|
+
pthread_mutex_unlock(&p->mu); return error;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
int mlx_bun_expert_io_hintv(expert_io_pool *p,
|
|
312
|
+
const uint32_t *file_index,
|
|
313
|
+
const uint64_t *offset,
|
|
314
|
+
const uint64_t *length,
|
|
315
|
+
uint32_t segment_count) {
|
|
316
|
+
if (!p || !file_index || !offset || !length || !segment_count ||
|
|
317
|
+
segment_count > EXPERT_IO_MAX_SEGMENTS) return EINVAL;
|
|
318
|
+
uint64_t bytes = 0;
|
|
319
|
+
for (uint32_t i = 0; i < segment_count; i++) {
|
|
320
|
+
if (file_index[i] >= p->file_count || !length[i]) return EINVAL;
|
|
321
|
+
if (offset[i] > INT64_MAX || length[i] > (uint64_t)INT64_MAX - offset[i])
|
|
322
|
+
return EOVERFLOW;
|
|
323
|
+
if (bytes > UINT64_MAX - length[i]) return EOVERFLOW;
|
|
324
|
+
bytes += length[i];
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
pthread_mutex_lock(&p->mu);
|
|
328
|
+
int error = 0;
|
|
329
|
+
if (p->stopping) error = ECANCELED;
|
|
330
|
+
else if (p->hint_qlen == p->hint_qcap) {
|
|
331
|
+
p->hint_dropped++;
|
|
332
|
+
error = EAGAIN;
|
|
333
|
+
} else {
|
|
334
|
+
io_hint_job *job = &p->hint_queue[p->hint_qtail];
|
|
335
|
+
memset(job, 0, sizeof(*job));
|
|
336
|
+
job->segment_count = segment_count;
|
|
337
|
+
for (uint32_t i = 0; i < segment_count; i++) {
|
|
338
|
+
job->file_index[i] = file_index[i];
|
|
339
|
+
job->offset[i] = offset[i];
|
|
340
|
+
job->length[i] = length[i];
|
|
341
|
+
}
|
|
342
|
+
p->hint_qtail = (p->hint_qtail + 1) % p->hint_qcap;
|
|
343
|
+
p->hint_qlen++;
|
|
344
|
+
p->hint_submitted++;
|
|
345
|
+
p->hint_operations += segment_count;
|
|
346
|
+
p->hint_bytes += bytes;
|
|
347
|
+
pthread_cond_signal(&p->hint_work);
|
|
348
|
+
}
|
|
349
|
+
pthread_mutex_unlock(&p->mu);
|
|
350
|
+
return error;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
int mlx_bun_expert_io_hint_stats(expert_io_pool *p, uint64_t *out,
|
|
354
|
+
uint32_t count) {
|
|
355
|
+
if (!p || !out || count < EXPERT_IO_HINT_STATS) return EINVAL;
|
|
356
|
+
pthread_mutex_lock(&p->mu);
|
|
357
|
+
out[0] = p->hint_submitted;
|
|
358
|
+
out[1] = p->hint_completed;
|
|
359
|
+
out[2] = p->hint_dropped;
|
|
360
|
+
out[3] = p->hint_operations;
|
|
361
|
+
out[4] = p->hint_bytes;
|
|
362
|
+
out[5] = p->hint_errors;
|
|
363
|
+
out[6] = p->hint_qlen;
|
|
364
|
+
out[7] = p->hint_inflight;
|
|
365
|
+
pthread_mutex_unlock(&p->mu);
|
|
366
|
+
return 0;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
int mlx_bun_expert_io_set_wiring(expert_io_pool *p, int enabled) {
|
|
370
|
+
if (!p) return EINVAL;
|
|
371
|
+
pthread_mutex_lock(&p->mu);
|
|
372
|
+
int error = 0;
|
|
373
|
+
if (p->qlen != 0) error = EBUSY;
|
|
374
|
+
for (uint32_t i = 0; i < p->slot_count && !error; i++) {
|
|
375
|
+
if (p->slots[i].state == SLOT_LOADING ||
|
|
376
|
+
p->slots[i].state == SLOT_LEASED) error = EBUSY;
|
|
377
|
+
}
|
|
378
|
+
if (!error) p->wire_slots = enabled != 0;
|
|
379
|
+
pthread_mutex_unlock(&p->mu);
|
|
380
|
+
return error;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
int mlx_bun_expert_io_submit(expert_io_pool *p, uint32_t slot_id, uint64_t generation,
|
|
384
|
+
uint64_t offset, uint64_t length) {
|
|
385
|
+
const uint32_t file_index[1] = {0};
|
|
386
|
+
const uint64_t destination[1] = {0};
|
|
387
|
+
return mlx_bun_expert_io_submitv(
|
|
388
|
+
p, slot_id, generation, file_index, &offset, destination, &length, 1
|
|
389
|
+
);
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
int mlx_bun_expert_io_cancel(expert_io_pool *p, uint32_t slot_id, uint64_t generation) {
|
|
393
|
+
if (!p || slot_id >= p->slot_count) return EINVAL;
|
|
394
|
+
pthread_mutex_lock(&p->mu); io_slot *s = &p->slots[slot_id];
|
|
395
|
+
int error = s->generation != generation ? ESTALE : 0;
|
|
396
|
+
if (!error && s->state == SLOT_LOADING) s->cancelled = 1;
|
|
397
|
+
else if (!error && s->state == SLOT_READY) { s->error = ECANCELED; s->length = 0; }
|
|
398
|
+
else if (!error) error = EBUSY;
|
|
399
|
+
pthread_mutex_unlock(&p->mu); return error;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
int mlx_bun_expert_io_wait(expert_io_pool *p, uint32_t slot_id, uint64_t generation) {
|
|
403
|
+
if (!p || slot_id >= p->slot_count) return EINVAL;
|
|
404
|
+
pthread_mutex_lock(&p->mu); io_slot *slot = &p->slots[slot_id];
|
|
405
|
+
while (slot->generation == generation && slot->state == SLOT_LOADING) pthread_cond_wait(&p->changed, &p->mu);
|
|
406
|
+
int error = slot->generation != generation ? ESTALE : slot->error;
|
|
407
|
+
pthread_mutex_unlock(&p->mu); return error;
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
int mlx_bun_expert_io_poll(expert_io_pool *p, uint32_t slot_id, uint64_t generation) {
|
|
411
|
+
if (!p || slot_id >= p->slot_count) return EINVAL;
|
|
412
|
+
pthread_mutex_lock(&p->mu); io_slot *slot = &p->slots[slot_id];
|
|
413
|
+
int result = slot->generation != generation ? ESTALE : slot->state == SLOT_LOADING ? EAGAIN : slot->error;
|
|
414
|
+
pthread_mutex_unlock(&p->mu); return result;
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
int mlx_bun_expert_io_lease(expert_io_pool *p, uint32_t slot_id, uint64_t generation, int gpu) {
|
|
418
|
+
if (!p || slot_id >= p->slot_count) return EINVAL;
|
|
419
|
+
pthread_mutex_lock(&p->mu); io_slot *s = &p->slots[slot_id];
|
|
420
|
+
int error = s->generation != generation ? ESTALE : s->state != SLOT_READY || s->error ? EBUSY : 0;
|
|
421
|
+
if (!error) { s->state = SLOT_LEASED; s->gpu_lease = gpu != 0; }
|
|
422
|
+
pthread_mutex_unlock(&p->mu); return error;
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
int mlx_bun_expert_io_release(expert_io_pool *p, uint32_t slot_id, uint64_t generation, int gpu_fenced) {
|
|
426
|
+
if (!p || slot_id >= p->slot_count) return EINVAL;
|
|
427
|
+
pthread_mutex_lock(&p->mu); io_slot *s = &p->slots[slot_id];
|
|
428
|
+
int error = s->generation != generation ? ESTALE : s->state != SLOT_LEASED ? EINVAL :
|
|
429
|
+
s->gpu_lease && !gpu_fenced ? EBUSY : 0;
|
|
430
|
+
if (!error) { s->state = SLOT_READY; s->gpu_lease = 0; }
|
|
431
|
+
pthread_mutex_unlock(&p->mu); return error;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
int mlx_bun_expert_io_discard(expert_io_pool *p, uint32_t slot_id, uint64_t generation) {
|
|
435
|
+
if (!p || slot_id >= p->slot_count || !generation) return EINVAL;
|
|
436
|
+
pthread_mutex_lock(&p->mu); io_slot *s = &p->slots[slot_id];
|
|
437
|
+
int error = s->generation != generation ? ESTALE :
|
|
438
|
+
s->state != SLOT_READY ? EBUSY : 0;
|
|
439
|
+
if (!error && s->wired) {
|
|
440
|
+
if (munlock(s->data, (size_t)s->capacity) != 0) error = errno;
|
|
441
|
+
else s->wired = 0;
|
|
442
|
+
}
|
|
443
|
+
if (!error && madvise(s->data, (size_t)s->capacity, MADV_DONTNEED) != 0)
|
|
444
|
+
error = errno;
|
|
445
|
+
if (!error) {
|
|
446
|
+
s->state = SLOT_IDLE;
|
|
447
|
+
s->length = 0;
|
|
448
|
+
s->error = 0;
|
|
449
|
+
s->cancelled = 0;
|
|
450
|
+
s->gpu_lease = 0;
|
|
451
|
+
}
|
|
452
|
+
pthread_mutex_unlock(&p->mu); return error;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
uint64_t mlx_bun_process_phys_footprint(void) {
|
|
456
|
+
#ifdef __APPLE__
|
|
457
|
+
task_vm_info_data_t info;
|
|
458
|
+
mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
|
|
459
|
+
kern_return_t status = task_info(
|
|
460
|
+
mach_task_self(), TASK_VM_INFO, (task_info_t)&info, &count
|
|
461
|
+
);
|
|
462
|
+
if (status == KERN_SUCCESS) return (uint64_t)info.phys_footprint;
|
|
463
|
+
#endif
|
|
464
|
+
struct rusage usage;
|
|
465
|
+
if (getrusage(RUSAGE_SELF, &usage) != 0) return 0;
|
|
466
|
+
#ifdef __APPLE__
|
|
467
|
+
return (uint64_t)usage.ru_maxrss;
|
|
468
|
+
#else
|
|
469
|
+
return (uint64_t)usage.ru_maxrss * 1024;
|
|
470
|
+
#endif
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
uint64_t mlx_bun_process_compressed(void) {
|
|
474
|
+
#ifdef __APPLE__
|
|
475
|
+
task_vm_info_data_t info;
|
|
476
|
+
mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
|
|
477
|
+
kern_return_t status = task_info(
|
|
478
|
+
mach_task_self(), TASK_VM_INFO, (task_info_t)&info, &count
|
|
479
|
+
);
|
|
480
|
+
if (status == KERN_SUCCESS) return (uint64_t)info.compressed;
|
|
481
|
+
#endif
|
|
482
|
+
return 0;
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
void *mlx_bun_expert_io_ptr(expert_io_pool *p, uint32_t slot_id, uint64_t generation) {
|
|
486
|
+
if (!p || slot_id >= p->slot_count) return NULL;
|
|
487
|
+
pthread_mutex_lock(&p->mu); io_slot *s = &p->slots[slot_id];
|
|
488
|
+
void *result = s->state == SLOT_LEASED && s->generation == generation ? s->data : NULL;
|
|
489
|
+
pthread_mutex_unlock(&p->mu); return result;
|
|
490
|
+
}
|
|
491
|
+
uint64_t mlx_bun_expert_io_length(expert_io_pool *p, uint32_t slot_id, uint64_t generation) {
|
|
492
|
+
if (!p || slot_id >= p->slot_count) return 0;
|
|
493
|
+
pthread_mutex_lock(&p->mu); io_slot *s = &p->slots[slot_id];
|
|
494
|
+
uint64_t result = s->state == SLOT_LEASED && s->generation == generation ? s->length : 0;
|
|
495
|
+
pthread_mutex_unlock(&p->mu); return result;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
int mlx_bun_expert_io_close(expert_io_pool *p) {
|
|
499
|
+
if (!p) return 0;
|
|
500
|
+
pthread_mutex_lock(&p->mu);
|
|
501
|
+
for (uint32_t i = 0; i < p->slot_count; i++) if (p->slots[i].state == SLOT_LEASED) {
|
|
502
|
+
pthread_mutex_unlock(&p->mu); return EBUSY;
|
|
503
|
+
}
|
|
504
|
+
pthread_mutex_unlock(&p->mu);
|
|
505
|
+
pthread_mutex_lock(&p->mu);
|
|
506
|
+
p->stopping = 1;
|
|
507
|
+
pthread_cond_broadcast(&p->work);
|
|
508
|
+
pthread_cond_broadcast(&p->hint_work);
|
|
509
|
+
pthread_mutex_unlock(&p->mu);
|
|
510
|
+
for (uint32_t i = 0; i < p->worker_count; i++) pthread_join(p->workers[i], NULL);
|
|
511
|
+
if (p->hint_worker_started) pthread_join(p->hint_worker, NULL);
|
|
512
|
+
for (uint32_t i = 0; i < p->slot_count; i++) {
|
|
513
|
+
if (p->slots[i].wired)
|
|
514
|
+
(void)munlock(p->slots[i].data, (size_t)p->slots[i].capacity);
|
|
515
|
+
free(p->slots[i].data);
|
|
516
|
+
}
|
|
517
|
+
pthread_cond_destroy(&p->hint_work); pthread_cond_destroy(&p->changed);
|
|
518
|
+
pthread_cond_destroy(&p->work); pthread_mutex_destroy(&p->mu);
|
|
519
|
+
for (uint32_t i = 0; i < p->file_count; i++) close(p->fds[i]);
|
|
520
|
+
for (uint32_t i = 0; i < p->file_count; i++) close(p->hint_fds[i]);
|
|
521
|
+
free(p->hint_fds); free(p->fds); free(p->workers); free(p->hint_queue);
|
|
522
|
+
free(p->queue); free(p->slots); free(p); return 0;
|
|
523
|
+
}
|
package/src/native-pack.ts
CHANGED
|
@@ -16,16 +16,17 @@ import { homedir, tmpdir } from "node:os";
|
|
|
16
16
|
import { dirname, join } from "node:path";
|
|
17
17
|
import { downloadOne } from "./download";
|
|
18
18
|
|
|
19
|
-
export const NATIVE_PACK_VERSION = "0.
|
|
19
|
+
export const NATIVE_PACK_VERSION = "0.2.0";
|
|
20
20
|
export const NATIVE_PACK_FILES = [
|
|
21
21
|
"libmlxc.dylib", "libmlx.dylib", "libjaccl.dylib", "mlx.metallib",
|
|
22
|
+
"libmlx_bun_expert_io.dylib",
|
|
22
23
|
] as const;
|
|
23
24
|
|
|
24
25
|
const SHA256: Record<string, string> = {
|
|
25
|
-
arm64: "
|
|
26
|
+
arm64: "9bd3795c5ea8f52b18413501f2d68c32c264a20751302300a08dc04cd67df97c",
|
|
26
27
|
};
|
|
27
28
|
const SIZE: Record<string, number> = {
|
|
28
|
-
arm64:
|
|
29
|
+
arm64: 52_307_647,
|
|
29
30
|
};
|
|
30
31
|
|
|
31
32
|
export function nativePackName(arch = process.arch): string {
|
|
@@ -116,7 +117,8 @@ export async function ensureNativeRuntime(opts: EnsureNativeOptions = {}): Promi
|
|
|
116
117
|
const sha256 = opts.sha256 ?? SHA256[arch];
|
|
117
118
|
const sizeBytes = opts.sizeBytes ?? SIZE[arch];
|
|
118
119
|
const destDir = opts.destDir ?? nativePackDir(arch);
|
|
119
|
-
if (existsSync(join(destDir,
|
|
120
|
+
if (NATIVE_PACK_FILES.every((file) => existsSync(join(destDir, file))))
|
|
121
|
+
return destDir;
|
|
120
122
|
if (!sha256 || !sizeBytes)
|
|
121
123
|
throw new Error(`no native pack published for ${arch} — set MLX_BUN_LIBMLXC or install mlx via homebrew`);
|
|
122
124
|
|