lex-llm-mlx 0.5.2 → 0.5.3
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +16 -0
- data/lex-llm-mlx.gemspec +3 -4
- data/lib/legion/extensions/llm/mlx/actors/discovery_refresh.rb +255 -103
- data/lib/legion/extensions/llm/mlx/version.rb +1 -1
- metadata +3 -3
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 43228a67440228c5db6f468ca4eb81c3fb032477fce92fbc60432c02b0872290
|
|
4
|
+
data.tar.gz: a91c59aa2bfc248de6f3715d41d46cfd2c0a82531df8c6ceceec8f9b73e5c1e9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 81faf223f32cd773041faad5637d42e76174ee042e723d50867e8d7b4ba5be8059a6489934118c391efc9e34fa1f5a7d7223cc6c80a9dee85a951cbc0042bf85
|
|
7
|
+
data.tar.gz: 7f19db1af208ead30d11984e3a86c67ac4748f64239802cd7851586990311eee667e35c8394722800f0d26d3bd5db1364617667a3e51ea1fab0d06d68bcb8939
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,21 @@
|
|
|
1
1
|
# Changelog
|
|
2
2
|
|
|
3
|
+
## [0.5.3] - 2026-08-19
|
|
4
|
+
|
|
5
|
+
### Changed
|
|
6
|
+
- Publish the immutable four-component lane-weight pair from MLX discovery and reconcile weight-only changes on the existing scheduled writer pass.
|
|
7
|
+
- Serialize initial, recovery, replacement, removal, and shutdown state transitions behind one actor-local mutex without adding a Settings callback or operator workflow.
|
|
8
|
+
- Track configured-but-unpublished weight keys on the existing discovery cadence and log each dormant transition once.
|
|
9
|
+
- Raise the `lex-llm` dependency floor to 0.7.6; the existing `legion-settings` dependency remains unchanged.
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
- Compare every offering contract field while excluding only non-authoritative evidence observation timestamps, preventing unchanged discovery passes from republishing snapshots while retaining real evidence and weight changes.
|
|
13
|
+
- Treat discovery catalogs as order-independent multisets, so provider reorderings do not republish while adding or removing a duplicate offering remains a significant change.
|
|
14
|
+
- Validate the complete weighted offering set before constructing or claiming a callable scope, so malformed weights leave no orphaned Registry publication and a later corrected ordinary pass activates without restart or operator cleanup.
|
|
15
|
+
|
|
16
|
+
### Added
|
|
17
|
+
- Cover the complete writer lifecycle, publication races, failure atomicity, dormant-state cycle, and the actual callable's folded-system OpenAI-compatible wire payload.
|
|
18
|
+
|
|
3
19
|
## [0.5.2] - 2026-08-18
|
|
4
20
|
|
|
5
21
|
### Fixed
|
data/lex-llm-mlx.gemspec
CHANGED
|
@@ -28,8 +28,7 @@ Gem::Specification.new do |spec|
|
|
|
28
28
|
spec.add_dependency 'legion-logging', '>= 1.3.2'
|
|
29
29
|
spec.add_dependency 'legion-settings', '>= 1.4.2'
|
|
30
30
|
spec.add_dependency 'legion-transport', '>= 1.4.14'
|
|
31
|
-
# 0.7.
|
|
32
|
-
#
|
|
33
|
-
|
|
34
|
-
spec.add_dependency 'lex-llm', '>= 0.7.1'
|
|
31
|
+
# 0.7.6 carries the write-time weight schema/reconciler and the immutable
|
|
32
|
+
# OfferingDraft/OfferingRecord/LaneRecord weight pair used by this writer.
|
|
33
|
+
spec.add_dependency 'lex-llm', '>= 0.7.6'
|
|
35
34
|
end
|
|
@@ -21,6 +21,7 @@ require 'legion/extensions/llm/inventory/identity'
|
|
|
21
21
|
require 'legion/extensions/llm/inventory/records'
|
|
22
22
|
require 'legion/extensions/llm/inventory/evidence'
|
|
23
23
|
require 'legion/extensions/llm/inventory/probe_coordinator'
|
|
24
|
+
require 'legion/extensions/llm/inventory/weight_reconciler'
|
|
24
25
|
require 'legion/extensions/llm/routing/provider_outcome'
|
|
25
26
|
require 'legion/extensions/llm/taxonomies'
|
|
26
27
|
require 'legion/extensions/llm/capabilities'
|
|
@@ -46,6 +47,13 @@ module Legion
|
|
|
46
47
|
def build_offering_draft(model_id:, model_data:, instance_cfg:, instance_key:)
|
|
47
48
|
tier = instance_cfg[:tier] || :local
|
|
48
49
|
embed_supported = embedding_model?(model_id: model_id)
|
|
50
|
+
weight_inputs = Legion::Extensions::Llm::Inventory::WeightSchema.weight_inputs(
|
|
51
|
+
settings: Legion::Settings,
|
|
52
|
+
instance_key: instance_key,
|
|
53
|
+
provider_native_key: model_id,
|
|
54
|
+
model: model_id,
|
|
55
|
+
tier: tier
|
|
56
|
+
)
|
|
49
57
|
|
|
50
58
|
Legion::Extensions::Llm::Inventory::OfferingDraft.new(
|
|
51
59
|
provider_native_key: model_id,
|
|
@@ -62,7 +70,9 @@ module Legion
|
|
|
62
70
|
tokenizer_evidence: absent_value_evidence,
|
|
63
71
|
quota_domains: {},
|
|
64
72
|
metadata: build_offering_metadata(model_data: model_data, instance_key: instance_key),
|
|
65
|
-
publication_source: :provider_catalog
|
|
73
|
+
publication_source: :provider_catalog,
|
|
74
|
+
weight_inputs: weight_inputs,
|
|
75
|
+
base_weight: Legion::Extensions::Llm::Inventory::WeightSchema.base_weight(weight_inputs)
|
|
66
76
|
)
|
|
67
77
|
end
|
|
68
78
|
|
|
@@ -239,8 +249,7 @@ module Legion
|
|
|
239
249
|
|
|
240
250
|
def readiness_failure(reason:, error:)
|
|
241
251
|
Legion::Extensions::Llm::Inventory::ReadinessResult.new(
|
|
242
|
-
ready: false, reason:
|
|
243
|
-
metadata: { error_class: error.class.name }
|
|
252
|
+
ready: false, reason:, metadata: { error_class: error.class.name }
|
|
244
253
|
)
|
|
245
254
|
end
|
|
246
255
|
|
|
@@ -254,9 +263,9 @@ module Legion
|
|
|
254
263
|
)
|
|
255
264
|
readiness = check_health(instance_cfg: state[:instance_cfg])
|
|
256
265
|
coordinator.finish_probe
|
|
257
|
-
|
|
266
|
+
commit_probe_result(
|
|
258
267
|
instance_id: instance_id, physical_id: state[:physical_id],
|
|
259
|
-
probe_token: probe_token, readiness: readiness
|
|
268
|
+
probe_token: probe_token, readiness: readiness, state: state
|
|
260
269
|
)
|
|
261
270
|
rescue StandardError => e
|
|
262
271
|
begin
|
|
@@ -268,21 +277,15 @@ module Legion
|
|
|
268
277
|
end
|
|
269
278
|
|
|
270
279
|
def handle_reactive_probe(instance_id:, request:)
|
|
271
|
-
state = @instance_states[instance_id]
|
|
280
|
+
state = state_mutex.synchronize { @instance_states[instance_id] }
|
|
272
281
|
return unless state
|
|
273
282
|
|
|
274
283
|
coordinator = state[:probe_coordinator]
|
|
275
284
|
return unless coordinator.begin_probe(request: request)
|
|
276
285
|
|
|
277
|
-
|
|
278
|
-
instance_id: instance_id,
|
|
279
|
-
|
|
280
|
-
)
|
|
281
|
-
readiness = check_health(instance_cfg: state[:instance_cfg])
|
|
282
|
-
coordinator.finish_probe(request: request)
|
|
283
|
-
report_probe_result(
|
|
284
|
-
instance_id: instance_id, physical_id: state[:physical_id],
|
|
285
|
-
probe_token: probe_token, readiness: readiness
|
|
286
|
+
perform_reactive_probe(
|
|
287
|
+
instance_id: instance_id, request: request,
|
|
288
|
+
state: state, coordinator: coordinator
|
|
286
289
|
)
|
|
287
290
|
rescue StandardError => e
|
|
288
291
|
begin
|
|
@@ -293,15 +296,33 @@ module Legion
|
|
|
293
296
|
handle_exception(e, level: :warn, operation: 'mlx.actor.reactive_probe', instance_id: instance_id)
|
|
294
297
|
end
|
|
295
298
|
|
|
296
|
-
def
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
299
|
+
def perform_reactive_probe(instance_id:, request:, state:, coordinator:)
|
|
300
|
+
probe_token = publisher.readiness_probe_started(
|
|
301
|
+
instance_id: instance_id, physical_id: state[:physical_id],
|
|
302
|
+
publisher_token: state[:publisher_token]
|
|
303
|
+
)
|
|
304
|
+
readiness = check_health(instance_cfg: state[:instance_cfg])
|
|
305
|
+
coordinator.finish_probe(request: request)
|
|
306
|
+
commit_probe_result(
|
|
307
|
+
instance_id: instance_id, physical_id: state[:physical_id],
|
|
308
|
+
probe_token: probe_token, readiness: readiness, state: state
|
|
309
|
+
)
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def commit_probe_result(instance_id:, physical_id:, probe_token:, readiness:, state:)
|
|
313
|
+
state_mutex.synchronize do
|
|
314
|
+
return unless @instance_states[instance_id].equal?(state)
|
|
315
|
+
|
|
316
|
+
if readiness.ready?
|
|
317
|
+
publisher.readiness_succeeded(
|
|
318
|
+
instance_id: instance_id, physical_id: physical_id, probe_token: probe_token
|
|
319
|
+
)
|
|
320
|
+
else
|
|
321
|
+
publisher.readiness_failed(
|
|
322
|
+
instance_id: instance_id, physical_id: physical_id,
|
|
323
|
+
probe_token: probe_token, reason: readiness.reason
|
|
324
|
+
)
|
|
325
|
+
end
|
|
305
326
|
end
|
|
306
327
|
end
|
|
307
328
|
|
|
@@ -316,6 +337,135 @@ module Legion
|
|
|
316
337
|
end
|
|
317
338
|
end
|
|
318
339
|
|
|
340
|
+
# MLX offering comparison excludes only evidence observation telemetry.
|
|
341
|
+
module OfferingComparison
|
|
342
|
+
OFFERING_SCALAR_EVIDENCE_FIELDS = %i[
|
|
343
|
+
context_evidence max_output_evidence embedding_dimensions_evidence
|
|
344
|
+
model_revision_evidence tokenizer_evidence
|
|
345
|
+
].freeze
|
|
346
|
+
|
|
347
|
+
private
|
|
348
|
+
|
|
349
|
+
# Inventory evidence timestamps are telemetry only: Evidence explicitly
|
|
350
|
+
# excludes observed_at from authority, ordering, freshness, recovery, and
|
|
351
|
+
# selection. Compare every OfferingDraft field (including the stored weight
|
|
352
|
+
# pair) while removing only those volatile evidence timestamps.
|
|
353
|
+
def offerings_equivalent?(previous, current)
|
|
354
|
+
Array(previous).map { |draft| offering_comparison_state(draft) }.tally ==
|
|
355
|
+
Array(current).map { |draft| offering_comparison_state(draft) }.tally
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def offering_comparison_state(draft)
|
|
359
|
+
state = draft.to_h
|
|
360
|
+
state[:operation_evidence] = comparison_evidence_map(draft.operation_evidence)
|
|
361
|
+
state[:capability_evidence] = comparison_evidence_map(draft.capability_evidence)
|
|
362
|
+
OFFERING_SCALAR_EVIDENCE_FIELDS.each do |field|
|
|
363
|
+
state[field] = comparison_evidence(draft.public_send(field))
|
|
364
|
+
end
|
|
365
|
+
state
|
|
366
|
+
end
|
|
367
|
+
|
|
368
|
+
def comparison_evidence_map(evidence)
|
|
369
|
+
evidence.transform_values { |entry| comparison_evidence(entry) }
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def comparison_evidence(evidence)
|
|
373
|
+
evidence.to_h.except(:observed_at)
|
|
374
|
+
end
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
# MLX bindings for the shared writer reconciler. This module owns only
|
|
378
|
+
# actor-local publication synchronization and dormant tracking used by
|
|
379
|
+
# the existing discovery cadence.
|
|
380
|
+
module WeightPublication
|
|
381
|
+
private
|
|
382
|
+
|
|
383
|
+
def replace_offerings_if_changed(instance_id:, state:)
|
|
384
|
+
new_offerings = discover_offerings_for_instance(
|
|
385
|
+
instance_cfg: state[:instance_cfg], instance_key: state[:instance_key]
|
|
386
|
+
)
|
|
387
|
+
Legion::Extensions::Llm::Inventory::WeightReconciler.commit_if_changed!(
|
|
388
|
+
settings: Legion::Settings,
|
|
389
|
+
instance_id: instance_id,
|
|
390
|
+
state: state,
|
|
391
|
+
discovered_offerings: new_offerings,
|
|
392
|
+
mutex: state_mutex,
|
|
393
|
+
equivalent: method(:offerings_equivalent?),
|
|
394
|
+
replace: method(:replace_weight_snapshot)
|
|
395
|
+
)
|
|
396
|
+
end
|
|
397
|
+
|
|
398
|
+
def replace_weight_snapshot(instance_id:, state:, offerings:, sequence:)
|
|
399
|
+
publisher.replace_instance_snapshot(
|
|
400
|
+
instance_id: instance_id,
|
|
401
|
+
publisher_token: state.fetch(:publisher_token),
|
|
402
|
+
offerings: offerings,
|
|
403
|
+
sequence: sequence,
|
|
404
|
+
physical_id: state.fetch(:physical_id)
|
|
405
|
+
)
|
|
406
|
+
end
|
|
407
|
+
|
|
408
|
+
def commit_readiness(instance_id:, probe_token:, readiness:, state:)
|
|
409
|
+
if readiness.ready?
|
|
410
|
+
return Legion::Extensions::Llm::Inventory::WeightReconciler.activate_tracked!(
|
|
411
|
+
settings: Legion::Settings,
|
|
412
|
+
instance_id: instance_id,
|
|
413
|
+
state_key: instance_id,
|
|
414
|
+
state: state,
|
|
415
|
+
states: @instance_states,
|
|
416
|
+
mutex: state_mutex,
|
|
417
|
+
probe_token: probe_token,
|
|
418
|
+
activate: method(:activate_weight_snapshot),
|
|
419
|
+
activation_sequence: ->(tracked) { tracked.fetch(:sequence) }
|
|
420
|
+
)
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
state_mutex.synchronize do
|
|
424
|
+
return false unless @instance_states[instance_id].equal?(state)
|
|
425
|
+
|
|
426
|
+
publisher.readiness_failed(
|
|
427
|
+
instance_id: instance_id, physical_id: state[:physical_id],
|
|
428
|
+
probe_token: probe_token, reason: readiness.reason
|
|
429
|
+
)
|
|
430
|
+
end
|
|
431
|
+
true
|
|
432
|
+
end
|
|
433
|
+
|
|
434
|
+
def activate_weight_snapshot(instance_id:, state:, offerings:, sequence:, probe_token:)
|
|
435
|
+
publisher.activate_instance_snapshot(
|
|
436
|
+
instance_id: instance_id,
|
|
437
|
+
publisher_token: state.fetch(:publisher_token),
|
|
438
|
+
offerings: offerings,
|
|
439
|
+
sequence: sequence,
|
|
440
|
+
probe_token: probe_token,
|
|
441
|
+
physical_id: state.fetch(:physical_id)
|
|
442
|
+
)
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
def observe_dormant_weights
|
|
446
|
+
Legion::Extensions::Llm::Inventory::WeightReconciler.observe_dormant!(
|
|
447
|
+
settings: Legion::Settings,
|
|
448
|
+
provider_family: :mlx,
|
|
449
|
+
states: @instance_states,
|
|
450
|
+
mutex: state_mutex,
|
|
451
|
+
tracker: dormant_weight_tracker,
|
|
452
|
+
dormant_logger: lambda do |key|
|
|
453
|
+
log.info(
|
|
454
|
+
"[llm][mlx] action=dormant_weight weight_key=#{key.inspect} no_lane_published=true"
|
|
455
|
+
)
|
|
456
|
+
end
|
|
457
|
+
)
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
def state_mutex
|
|
461
|
+
@state_mutex ||= Mutex.new
|
|
462
|
+
end
|
|
463
|
+
|
|
464
|
+
def dormant_weight_tracker
|
|
465
|
+
@dormant_weight_tracker ||= Legion::Extensions::Llm::Inventory::DormantWeightTracker.new
|
|
466
|
+
end
|
|
467
|
+
end
|
|
468
|
+
|
|
319
469
|
# Periodic refresh cycle — included by DiscoveryRefresh. Each tick
|
|
320
470
|
# re-scans the configured instances (late configuration appears
|
|
321
471
|
# without a restart; removed instances are reconciled out),
|
|
@@ -326,30 +476,40 @@ module Legion
|
|
|
326
476
|
private
|
|
327
477
|
|
|
328
478
|
def tick_refresh
|
|
329
|
-
|
|
479
|
+
instance_states
|
|
330
480
|
configured_ids = {}
|
|
331
481
|
configured_instances.each do |name, instance_cfg|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
# distinct instances (the physical id is secondary).
|
|
336
|
-
instance_id = name.to_s
|
|
337
|
-
configured_ids[instance_id] = true
|
|
338
|
-
state = @instance_states[instance_id]
|
|
339
|
-
if state.nil?
|
|
340
|
-
claim_and_activate_instance(name: name, instance_cfg: instance_cfg)
|
|
341
|
-
else
|
|
342
|
-
refresh_instance(instance_id: instance_id, name: name, state: state)
|
|
343
|
-
end
|
|
344
|
-
rescue StandardError => e
|
|
345
|
-
handle_exception(e, level: :warn, operation: 'mlx.actor.tick_refresh', instance_name: name.to_s)
|
|
482
|
+
reconcile_configured_instance(
|
|
483
|
+
name: name, instance_cfg: instance_cfg, configured_ids: configured_ids
|
|
484
|
+
)
|
|
346
485
|
end
|
|
347
486
|
|
|
348
487
|
remove_unconfigured_instances(configured_ids: configured_ids)
|
|
488
|
+
observe_dormant_weights
|
|
489
|
+
end
|
|
490
|
+
|
|
491
|
+
def instance_states
|
|
492
|
+
state_mutex.synchronize { @instance_states ||= {} }
|
|
493
|
+
end
|
|
494
|
+
|
|
495
|
+
# Identity is the operator's config name — the key the frozen config
|
|
496
|
+
# and router use. Two names at one endpoint remain distinct instances.
|
|
497
|
+
def reconcile_configured_instance(name:, instance_cfg:, configured_ids:)
|
|
498
|
+
instance_id = name.to_s
|
|
499
|
+
configured_ids[instance_id] = true
|
|
500
|
+
state = state_mutex.synchronize { @instance_states[instance_id] }
|
|
501
|
+
if state.nil?
|
|
502
|
+
claim_and_activate_instance(name: name, instance_cfg: instance_cfg)
|
|
503
|
+
else
|
|
504
|
+
refresh_instance(instance_id: instance_id, name: name, state: state)
|
|
505
|
+
end
|
|
506
|
+
rescue StandardError => e
|
|
507
|
+
handle_exception(e, level: :warn, operation: 'mlx.actor.tick_refresh', instance_name: name.to_s)
|
|
349
508
|
end
|
|
350
509
|
|
|
351
510
|
def remove_unconfigured_instances(configured_ids:)
|
|
352
|
-
@instance_states.
|
|
511
|
+
states = state_mutex.synchronize { @instance_states.to_a }
|
|
512
|
+
states.each do |instance_id, state|
|
|
353
513
|
next if configured_ids.key?(instance_id)
|
|
354
514
|
|
|
355
515
|
remove_instance_state(instance_id: instance_id, state: state)
|
|
@@ -360,12 +520,18 @@ module Legion
|
|
|
360
520
|
end
|
|
361
521
|
|
|
362
522
|
def remove_instance_state(instance_id:, state:)
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
523
|
+
removed = state_mutex.synchronize do
|
|
524
|
+
next false unless @instance_states[instance_id].equal?(state)
|
|
525
|
+
|
|
526
|
+
publisher.remove_instance(
|
|
527
|
+
instance_id: instance_id, physical_id: state[:physical_id],
|
|
528
|
+
publisher_token: state[:publisher_token]
|
|
529
|
+
)
|
|
530
|
+
@instance_states.delete(instance_id)
|
|
531
|
+
true
|
|
532
|
+
end
|
|
533
|
+
clear_instance_health(config_name: state[:name]) if removed
|
|
534
|
+
removed
|
|
369
535
|
end
|
|
370
536
|
|
|
371
537
|
def refresh_instance(instance_id:, name:, state:)
|
|
@@ -380,21 +546,6 @@ module Legion
|
|
|
380
546
|
write_instance_health(config_name: name, state: state)
|
|
381
547
|
end
|
|
382
548
|
|
|
383
|
-
def replace_offerings_if_changed(instance_id:, state:)
|
|
384
|
-
new_offerings = discover_offerings_for_instance(
|
|
385
|
-
instance_cfg: state[:instance_cfg], instance_key: state[:instance_key]
|
|
386
|
-
)
|
|
387
|
-
return if new_offerings == state[:offerings]
|
|
388
|
-
|
|
389
|
-
state[:sequence] += 1
|
|
390
|
-
publisher.replace_instance_snapshot(
|
|
391
|
-
instance_id: instance_id, physical_id: state[:physical_id],
|
|
392
|
-
publisher_token: state[:publisher_token],
|
|
393
|
-
offerings: new_offerings, sequence: state[:sequence]
|
|
394
|
-
)
|
|
395
|
-
state[:offerings] = new_offerings
|
|
396
|
-
end
|
|
397
|
-
|
|
398
549
|
# Initial-failure recovery: an instance stuck at :initializing
|
|
399
550
|
# (readiness failed at boot, e.g. transient outage) re-activates
|
|
400
551
|
# on the first healthy probe. While :initializing,
|
|
@@ -405,34 +556,24 @@ module Legion
|
|
|
405
556
|
offerings = discover_offerings_for_instance(
|
|
406
557
|
instance_cfg: state[:instance_cfg], instance_key: state[:instance_key]
|
|
407
558
|
)
|
|
559
|
+
Legion::Extensions::Llm::Inventory::WeightReconciler.commit_if_changed!(
|
|
560
|
+
settings: Legion::Settings,
|
|
561
|
+
instance_id: instance_id,
|
|
562
|
+
state: state,
|
|
563
|
+
discovered_offerings: offerings,
|
|
564
|
+
mutex: state_mutex,
|
|
565
|
+
equivalent: method(:offerings_equivalent?),
|
|
566
|
+
replace: method(:replace_weight_snapshot)
|
|
567
|
+
)
|
|
408
568
|
probe_token = publisher.readiness_probe_started(
|
|
409
569
|
instance_id: instance_id, publisher_token: state[:publisher_token]
|
|
410
570
|
)
|
|
411
571
|
readiness = check_health(instance_cfg: state[:instance_cfg])
|
|
412
|
-
commit_readiness(
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
# Shared commit for initial and recovery activation: on a
|
|
418
|
-
# healthy probe activate the snapshot (the only legal commit
|
|
419
|
-
# from :initializing), otherwise record the failed readiness.
|
|
420
|
-
# The sequence is the instance state's sequence (0 until the
|
|
421
|
-
# first replace after activation).
|
|
422
|
-
def commit_readiness(instance_id:, offerings:, probe_token:, readiness:, state:)
|
|
423
|
-
if readiness.ready?
|
|
424
|
-
publisher.activate_instance_snapshot(
|
|
425
|
-
instance_id: instance_id, physical_id: state[:physical_id],
|
|
426
|
-
publisher_token: state[:publisher_token],
|
|
427
|
-
offerings: offerings, sequence: state[:sequence], probe_token: probe_token
|
|
428
|
-
)
|
|
429
|
-
state[:offerings] = offerings
|
|
430
|
-
else
|
|
431
|
-
publisher.readiness_failed(
|
|
432
|
-
instance_id: instance_id, physical_id: state[:physical_id],
|
|
433
|
-
probe_token: probe_token, reason: readiness.reason
|
|
434
|
-
)
|
|
435
|
-
end
|
|
572
|
+
committed = commit_readiness(
|
|
573
|
+
instance_id: instance_id, probe_token: probe_token,
|
|
574
|
+
readiness: readiness, state: state
|
|
575
|
+
)
|
|
576
|
+
write_instance_health(config_name: name, state: state) if committed
|
|
436
577
|
end
|
|
437
578
|
end
|
|
438
579
|
|
|
@@ -489,7 +630,7 @@ module Legion
|
|
|
489
630
|
end
|
|
490
631
|
|
|
491
632
|
def build_instance_state(**attrs)
|
|
492
|
-
attrs.merge(sequence: 0)
|
|
633
|
+
attrs.merge(sequence: 0, published: false)
|
|
493
634
|
end
|
|
494
635
|
end
|
|
495
636
|
|
|
@@ -622,6 +763,8 @@ module Legion
|
|
|
622
763
|
include ValueEvidenceBuilding
|
|
623
764
|
include OfferingAssembly
|
|
624
765
|
include HealthProbing
|
|
766
|
+
include OfferingComparison
|
|
767
|
+
include WeightPublication
|
|
625
768
|
include TickCycle
|
|
626
769
|
include InstanceConfig
|
|
627
770
|
include HealthDisplay
|
|
@@ -672,6 +815,7 @@ module Legion
|
|
|
672
815
|
instance_id = name.to_s
|
|
673
816
|
physical_id = derive_physical_id(instance_cfg: instance_cfg)
|
|
674
817
|
instance_key = build_instance_key(instance_id: instance_id, physical_id: physical_id)
|
|
818
|
+
offerings = discover_offerings_for_instance(instance_cfg: instance_cfg, instance_key: instance_key)
|
|
675
819
|
callable = Legion::Extensions::Llm::Mlx::Actor::MlxCallable.new(instance_cfg: instance_cfg, logger: log)
|
|
676
820
|
probe_coordinator = build_probe_coordinator(instance_id: instance_id, instance_key: instance_key)
|
|
677
821
|
publisher_token = publisher.claim_instance(
|
|
@@ -680,42 +824,50 @@ module Legion
|
|
|
680
824
|
)
|
|
681
825
|
run_activation(
|
|
682
826
|
instance_id: instance_id, publisher_token: publisher_token,
|
|
827
|
+
offerings: offerings,
|
|
683
828
|
instance_desc: { name: name, instance_id: instance_id, physical_id: physical_id,
|
|
684
829
|
instance_key: instance_key, instance_cfg: instance_cfg,
|
|
685
830
|
callable: callable, probe_coordinator: probe_coordinator }
|
|
686
831
|
)
|
|
687
832
|
end
|
|
688
833
|
|
|
689
|
-
def run_activation(instance_id:, publisher_token:, instance_desc:)
|
|
834
|
+
def run_activation(instance_id:, publisher_token:, offerings:, instance_desc:)
|
|
690
835
|
instance_cfg = instance_desc[:instance_cfg]
|
|
691
|
-
|
|
692
|
-
|
|
836
|
+
state = build_instance_state(
|
|
837
|
+
**instance_desc, publisher_token: publisher_token, offerings: offerings
|
|
838
|
+
)
|
|
839
|
+
Legion::Extensions::Llm::Inventory::WeightReconciler.track_initializing!(
|
|
840
|
+
states: @instance_states,
|
|
841
|
+
state_key: instance_id,
|
|
842
|
+
state: state,
|
|
843
|
+
mutex: state_mutex
|
|
844
|
+
)
|
|
693
845
|
probe_token = publisher.readiness_probe_started(instance_id: instance_id,
|
|
694
846
|
publisher_token: publisher_token)
|
|
695
847
|
readiness = check_health(instance_cfg: instance_cfg)
|
|
696
|
-
|
|
697
|
-
|
|
848
|
+
committed = commit_readiness(
|
|
849
|
+
instance_id: instance_id, probe_token: probe_token,
|
|
850
|
+
readiness: readiness, state: state
|
|
698
851
|
)
|
|
699
|
-
|
|
700
|
-
probe_token: probe_token, readiness: readiness, state: state)
|
|
701
|
-
@instance_states[instance_id] = state
|
|
702
|
-
write_instance_health(config_name: instance_desc[:name], state: state)
|
|
852
|
+
write_instance_health(config_name: instance_desc[:name], state: state) if committed
|
|
703
853
|
end
|
|
704
854
|
|
|
705
855
|
def remove_all_instances
|
|
706
|
-
|
|
856
|
+
states = state_mutex.synchronize do
|
|
857
|
+
return if @instance_states.nil?
|
|
707
858
|
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
)
|
|
713
|
-
clear_instance_health(config_name: state[:name])
|
|
859
|
+
@instance_states.to_a
|
|
860
|
+
end
|
|
861
|
+
states.each do |instance_id, state|
|
|
862
|
+
remove_instance_state(instance_id: instance_id, state: state)
|
|
714
863
|
rescue StandardError => e
|
|
715
864
|
handle_exception(e, level: :warn, operation: 'mlx.actor.remove_instance',
|
|
716
865
|
instance_id: instance_id)
|
|
717
866
|
end
|
|
718
|
-
|
|
867
|
+
state_mutex.synchronize do
|
|
868
|
+
@instance_states.clear
|
|
869
|
+
dormant_weight_tracker.clear!
|
|
870
|
+
end
|
|
719
871
|
end
|
|
720
872
|
end
|
|
721
873
|
|
metadata
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: lex-llm-mlx
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.5.
|
|
4
|
+
version: 0.5.3
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- LegionIO
|
|
@@ -85,14 +85,14 @@ dependencies:
|
|
|
85
85
|
requirements:
|
|
86
86
|
- - ">="
|
|
87
87
|
- !ruby/object:Gem::Version
|
|
88
|
-
version: 0.7.
|
|
88
|
+
version: 0.7.6
|
|
89
89
|
type: :runtime
|
|
90
90
|
prerelease: false
|
|
91
91
|
version_requirements: !ruby/object:Gem::Requirement
|
|
92
92
|
requirements:
|
|
93
93
|
- - ">="
|
|
94
94
|
- !ruby/object:Gem::Version
|
|
95
|
-
version: 0.7.
|
|
95
|
+
version: 0.7.6
|
|
96
96
|
description: MLX provider integration for the LegionIO LLM routing framework.
|
|
97
97
|
email:
|
|
98
98
|
- matthewdiverson@gmail.com
|