mtplx 2.12.0__tar.gz → 2.12.2__tar.gz
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.
- mtplx-2.12.2/CHANGELOG.md +4570 -0
- mtplx-2.12.2/PKG-INFO +369 -0
- mtplx-2.12.2/README.md +318 -0
- mtplx-2.12.2/mtplx/attention_context.py +262 -0
- mtplx-2.12.2/mtplx/attention_math.py +45 -0
- mtplx-2.12.2/mtplx/attention_split.py +919 -0
- mtplx-2.12.2/mtplx/backends/family_settings.py +541 -0
- mtplx-2.12.2/mtplx/backends/gemma4_assistant.py +4060 -0
- mtplx-2.12.2/mtplx/cache_bank/codec.py +948 -0
- mtplx-2.12.2/mtplx/cache_bank/cold_tier.py +3340 -0
- mtplx-2.12.2/mtplx/cache_bank/disk_budget.py +159 -0
- mtplx-2.12.2/mtplx/cache_state.py +5346 -0
- mtplx-2.12.2/mtplx/chat_encode_cache.py +196 -0
- mtplx-2.12.2/mtplx/chat_encoding.py +347 -0
- mtplx-2.12.2/mtplx/checkpoint_anchors.py +306 -0
- mtplx-2.12.2/mtplx/cli.py +5390 -0
- mtplx-2.12.2/mtplx/commands/forge_qwen4_exp.py +539 -0
- mtplx-2.12.2/mtplx/commands/public.py +16631 -0
- mtplx-2.12.2/mtplx/compile_state.py +121 -0
- mtplx-2.12.2/mtplx/compiled_forward.py +148 -0
- mtplx-2.12.2/mtplx/config.py +409 -0
- mtplx-2.12.2/mtplx/constrained.py +680 -0
- mtplx-2.12.2/mtplx/context_copy.py +397 -0
- mtplx-2.12.2/mtplx/dashboard/_static/assets/index-BTDFdIWu.css +1 -0
- mtplx-2.12.2/mtplx/dashboard/_static/assets/index-DIdoATFR.js +273 -0
- mtplx-2.12.2/mtplx/dashboard/_static/index.html +15 -0
- mtplx-2.12.2/mtplx/default_models.py +1089 -0
- mtplx-2.12.2/mtplx/demotions.py +252 -0
- mtplx-2.12.2/mtplx/engine_session.py +2651 -0
- mtplx-2.12.2/mtplx/errors.py +119 -0
- mtplx-2.12.2/mtplx/expert_layout.py +278 -0
- mtplx-2.12.2/mtplx/fast_sampling.py +907 -0
- mtplx-2.12.2/mtplx/float32_operand.py +37 -0
- mtplx-2.12.2/mtplx/generation.py +17105 -0
- mtplx-2.12.2/mtplx/graphbank.py +6288 -0
- mtplx-2.12.2/mtplx/jsonc.py +130 -0
- mtplx-2.12.2/mtplx/kernels/hc_verify_read.py +892 -0
- mtplx-2.12.2/mtplx/kernels/laguna_moe_gather_gemm.py +275 -0
- mtplx-2.12.2/mtplx/kernels/moe_sorted_gather_nax.py +668 -0
- mtplx-2.12.2/mtplx/kernels/qsa_pooled_row.py +515 -0
- mtplx-2.12.2/mtplx/kernels/qsa_verify_select.py +695 -0
- mtplx-2.12.2/mtplx/kernels/qwen4_m4_routed_down.py +645 -0
- mtplx-2.12.2/mtplx/kernels/sdpa_2pass.py +402 -0
- mtplx-2.12.2/mtplx/kernels/sdpa_2pass_paged.py +513 -0
- mtplx-2.12.2/mtplx/kernels/sdpa_2pass_paged_q8.py +297 -0
- mtplx-2.12.2/mtplx/kernels/sdpa_gqa_packed.py +788 -0
- mtplx-2.12.2/mtplx/kernels/sdpa_gqa_packed_quant.py +543 -0
- mtplx-2.12.2/mtplx/kernels/sdpa_nax_flash.py +463 -0
- mtplx-2.12.2/mtplx/kernels/sdpa_nax_flash_dsplit.py +390 -0
- mtplx-2.12.2/mtplx/kernels/sdpa_nax_tile.py +422 -0
- mtplx-2.12.2/mtplx/memory_plan.py +888 -0
- mtplx-2.12.2/mtplx/model_scheduler.py +1063 -0
- mtplx-2.12.2/mtplx/models/laguna_fused.py +1337 -0
- mtplx-2.12.2/mtplx/models/qwen4_exp.py +7396 -0
- mtplx-2.12.2/mtplx/moe_packed_projections.py +398 -0
- mtplx-2.12.2/mtplx/moe_sorted_gather.py +481 -0
- mtplx-2.12.2/mtplx/mtp_history_cache_only.py +55 -0
- mtplx-2.12.2/mtplx/mtp_patch.py +1164 -0
- mtplx-2.12.2/mtplx/one_copy.py +529 -0
- mtplx-2.12.2/mtplx/opencode.py +1058 -0
- mtplx-2.12.2/mtplx/pi.py +717 -0
- mtplx-2.12.2/mtplx/prefill_plan.py +360 -0
- mtplx-2.12.2/mtplx/profiles.py +1093 -0
- mtplx-2.12.2/mtplx/qwen3_5_mtp_patch.py +430 -0
- mtplx-2.12.2/mtplx/qwen4_fixed_verify.py +421 -0
- mtplx-2.12.2/mtplx/qwen4_m4_stage3.py +987 -0
- mtplx-2.12.2/mtplx/qwen4_prefill_profile.py +254 -0
- mtplx-2.12.2/mtplx/reasoning_codecs.py +697 -0
- mtplx-2.12.2/mtplx/runtime.py +1638 -0
- mtplx-2.12.2/mtplx/sampling.py +391 -0
- mtplx-2.12.2/mtplx/server/dashboard_state.py +691 -0
- mtplx-2.12.2/mtplx/server/flight_recorder.py +566 -0
- mtplx-2.12.2/mtplx/server/omlx_bridge/adapter.py +317 -0
- mtplx-2.12.2/mtplx/server/openai.py +44650 -0
- mtplx-2.12.2/mtplx/server/prefill_safety.py +278 -0
- mtplx-2.12.2/mtplx/server/request_policy.py +953 -0
- mtplx-2.12.2/mtplx/server/request_spans.py +401 -0
- mtplx-2.12.2/mtplx/server/retry_prompt.py +68 -0
- mtplx-2.12.2/mtplx/server/served_window.py +57 -0
- mtplx-2.12.2/mtplx/server/stream_rate.py +176 -0
- mtplx-2.12.2/mtplx/server/stream_recovery.py +107 -0
- mtplx-2.12.2/mtplx/session_bank.py +5192 -0
- mtplx-2.12.2/mtplx/system_memory.py +782 -0
- mtplx-2.12.2/mtplx/thermal_pressure.py +240 -0
- mtplx-2.12.2/mtplx/ui/onboarding.py +2202 -0
- mtplx-2.12.2/mtplx/vendored_arrays_cache.py +829 -0
- mtplx-2.12.2/mtplx/version.py +6 -0
- mtplx-2.12.2/mtplx/vision/splice.py +404 -0
- mtplx-2.12.2/mtplx.egg-info/PKG-INFO +369 -0
- mtplx-2.12.2/mtplx.egg-info/SOURCES.txt +1049 -0
- mtplx-2.12.2/mtplx.egg-info/requires.txt +29 -0
- mtplx-2.12.2/pyproject.toml +123 -0
- mtplx-2.12.2/scripts/check_prompt_scoring.py +128 -0
- mtplx-2.12.2/tests/test_admission_keeps_the_conversation.py +447 -0
- mtplx-2.12.2/tests/test_agent_rewrites_off_retries.py +177 -0
- mtplx-2.12.2/tests/test_answer_memory_stop.py +158 -0
- mtplx-2.12.2/tests/test_answer_room_request.py +116 -0
- mtplx-2.12.2/tests/test_api_benchmark_contracts.py +824 -0
- mtplx-2.12.2/tests/test_ar_batch_ragged_ssm_mask.py +129 -0
- mtplx-2.12.2/tests/test_arrays_cache_patch.py +605 -0
- mtplx-2.12.2/tests/test_attention_gate_exactness.py +102 -0
- mtplx-2.12.2/tests/test_blank_retry_greedy.py +98 -0
- mtplx-2.12.2/tests/test_cache_bank.py +1136 -0
- mtplx-2.12.2/tests/test_canon_policy_smalls.py +377 -0
- mtplx-2.12.2/tests/test_chat_encode_cache.py +213 -0
- mtplx-2.12.2/tests/test_chat_segment_memo.py +609 -0
- mtplx-2.12.2/tests/test_chat_turn_segments.py +566 -0
- mtplx-2.12.2/tests/test_checkpoint_anchors.py +476 -0
- mtplx-2.12.2/tests/test_checkpoint_pre_image.py +278 -0
- mtplx-2.12.2/tests/test_cold_tier_defaults.py +115 -0
- mtplx-2.12.2/tests/test_cold_tier_disk_budget.py +117 -0
- mtplx-2.12.2/tests/test_cold_tier_incremental_encode.py +451 -0
- mtplx-2.12.2/tests/test_cold_tier_recurrent_boundary_gate.py +121 -0
- mtplx-2.12.2/tests/test_cold_tier_transactional_replace.py +564 -0
- mtplx-2.12.2/tests/test_compiled_verify_evaluated_weights.py +526 -0
- mtplx-2.12.2/tests/test_compiled_verify_tf32_off.py +66 -0
- mtplx-2.12.2/tests/test_config.py +508 -0
- mtplx-2.12.2/tests/test_constrained.py +1684 -0
- mtplx-2.12.2/tests/test_copy_block_accept.py +549 -0
- mtplx-2.12.2/tests/test_death_signature_macos27.py +1028 -0
- mtplx-2.12.2/tests/test_decoded_state_lifetime.py +101 -0
- mtplx-2.12.2/tests/test_default_models.py +931 -0
- mtplx-2.12.2/tests/test_dense_mrope_compiled_route.py +573 -0
- mtplx-2.12.2/tests/test_draft_telemetry_truth.py +662 -0
- mtplx-2.12.2/tests/test_env_flag_parsing.py +716 -0
- mtplx-2.12.2/tests/test_finished_answers_are_never_regenerated.py +232 -0
- mtplx-2.12.2/tests/test_first_token_logprobs_api.py +547 -0
- mtplx-2.12.2/tests/test_first_token_logprobs_gemma4.py +211 -0
- mtplx-2.12.2/tests/test_fixed_m4_dispatch_guard.py +285 -0
- mtplx-2.12.2/tests/test_float32_operand.py +58 -0
- mtplx-2.12.2/tests/test_gdn_boundary_retention.py +219 -0
- mtplx-2.12.2/tests/test_gemma4_chunked_prefill.py +885 -0
- mtplx-2.12.2/tests/test_gemma4_prompt_scoring.py +218 -0
- mtplx-2.12.2/tests/test_gemma4_scoring_cache.py +90 -0
- mtplx-2.12.2/tests/test_gemma4_scoring_policy.py +153 -0
- mtplx-2.12.2/tests/test_gemma4_session_cache.py +321 -0
- mtplx-2.12.2/tests/test_gemma4_tokenizer_probe.py +81 -0
- mtplx-2.12.2/tests/test_generation_sustained.py +2017 -0
- mtplx-2.12.2/tests/test_graphbank_compiled_verify.py +2714 -0
- mtplx-2.12.2/tests/test_graphbank_program_retirement.py +206 -0
- mtplx-2.12.2/tests/test_graphbank_trace_lifetime.py +76 -0
- mtplx-2.12.2/tests/test_hc_verify_read.py +591 -0
- mtplx-2.12.2/tests/test_hidden_tool_guard_opt_in.py +276 -0
- mtplx-2.12.2/tests/test_host_memory_allowance.py +332 -0
- mtplx-2.12.2/tests/test_idle_session_release.py +607 -0
- mtplx-2.12.2/tests/test_jsonc.py +82 -0
- mtplx-2.12.2/tests/test_kv_attention_trace_526.py +344 -0
- mtplx-2.12.2/tests/test_kv_quant_meta_state_rewind_526.py +90 -0
- mtplx-2.12.2/tests/test_laguna_model.py +1127 -0
- mtplx-2.12.2/tests/test_lease_survives_aborted_prefill.py +146 -0
- mtplx-2.12.2/tests/test_memguard_admission.py +2017 -0
- mtplx-2.12.2/tests/test_memguard_backend_contract.py +933 -0
- mtplx-2.12.2/tests/test_memguard_degraded.py +247 -0
- mtplx-2.12.2/tests/test_memguard_dense_qsa.py +157 -0
- mtplx-2.12.2/tests/test_memguard_lease_growth.py +232 -0
- mtplx-2.12.2/tests/test_memguard_limit_allowances.py +92 -0
- mtplx-2.12.2/tests/test_memguard_moe_rows.py +56 -0
- mtplx-2.12.2/tests/test_memguard_ownership.py +200 -0
- mtplx-2.12.2/tests/test_memguard_pressure_trim.py +166 -0
- mtplx-2.12.2/tests/test_memguard_queued_persistence.py +414 -0
- mtplx-2.12.2/tests/test_memguard_release_protection.py +187 -0
- mtplx-2.12.2/tests/test_memguard_snapshot_width.py +346 -0
- mtplx-2.12.2/tests/test_memory_limit_g5.py +240 -0
- mtplx-2.12.2/tests/test_memory_plan.py +453 -0
- mtplx-2.12.2/tests/test_memory_pressure_guard.py +410 -0
- mtplx-2.12.2/tests/test_metal_memory_caps.py +419 -0
- mtplx-2.12.2/tests/test_model_scheduler_persistence.py +909 -0
- mtplx-2.12.2/tests/test_moe_sorted_gather.py +366 -0
- mtplx-2.12.2/tests/test_moe_sorted_gather_nax.py +714 -0
- mtplx-2.12.2/tests/test_mtp_fused_experts.py +150 -0
- mtplx-2.12.2/tests/test_mtp_history_cache_only.py +337 -0
- mtplx-2.12.2/tests/test_mtp_history_settled.py +451 -0
- mtplx-2.12.2/tests/test_mtp_patch.py +516 -0
- mtplx-2.12.2/tests/test_one_copy_admission.py +356 -0
- mtplx-2.12.2/tests/test_one_copy_cancelled_answer.py +186 -0
- mtplx-2.12.2/tests/test_one_copy_conversation.py +524 -0
- mtplx-2.12.2/tests/test_one_copy_postcommit.py +145 -0
- mtplx-2.12.2/tests/test_one_copy_reserve_resize.py +1298 -0
- mtplx-2.12.2/tests/test_one_copy_restarted_draft_history.py +351 -0
- mtplx-2.12.2/tests/test_openai_bridge.py +2949 -0
- mtplx-2.12.2/tests/test_opencode.py +884 -0
- mtplx-2.12.2/tests/test_owner_idle_pool.py +164 -0
- mtplx-2.12.2/tests/test_parallel_tool_prompt_policy.py +150 -0
- mtplx-2.12.2/tests/test_phys_footprint_guard.py +211 -0
- mtplx-2.12.2/tests/test_postcommit_prefix_reuse.py +330 -0
- mtplx-2.12.2/tests/test_prefill_admission_shed.py +877 -0
- mtplx-2.12.2/tests/test_prefill_geometric_tail.py +211 -0
- mtplx-2.12.2/tests/test_prefill_inforward_boundary_capture.py +724 -0
- mtplx-2.12.2/tests/test_prefill_reread.py +732 -0
- mtplx-2.12.2/tests/test_pressure_abort_393.py +153 -0
- mtplx-2.12.2/tests/test_promoted_paged_capacity_526.py +1225 -0
- mtplx-2.12.2/tests/test_prompt_publication_is_request_local.py +240 -0
- mtplx-2.12.2/tests/test_prompt_scoring_admission.py +154 -0
- mtplx-2.12.2/tests/test_prompt_scoring_parent.py +79 -0
- mtplx-2.12.2/tests/test_prompt_scoring_probe.py +58 -0
- mtplx-2.12.2/tests/test_prompt_scoring_topk.py +145 -0
- mtplx-2.12.2/tests/test_public_cli.py +10334 -0
- mtplx-2.12.2/tests/test_px3_planner_seats.py +270 -0
- mtplx-2.12.2/tests/test_qsa_fixed_bank_score_scale.py +181 -0
- mtplx-2.12.2/tests/test_qsa_index_block_writes_evaluated.py +558 -0
- mtplx-2.12.2/tests/test_qsa_indexer_select_metal.py +953 -0
- mtplx-2.12.2/tests/test_qsa_memory_plan_393.py +141 -0
- mtplx-2.12.2/tests/test_qsa_pooled_row.py +259 -0
- mtplx-2.12.2/tests/test_qsa_verify_select.py +330 -0
- mtplx-2.12.2/tests/test_quant_partials_fp16_526.py +251 -0
- mtplx-2.12.2/tests/test_qwen4_bucket_publication.py +196 -0
- mtplx-2.12.2/tests/test_qwen4_capacity_consumers.py +729 -0
- mtplx-2.12.2/tests/test_qwen4_fixed_bank_capacity.py +159 -0
- mtplx-2.12.2/tests/test_qwen4_fixed_m4_capacity_bucket.py +578 -0
- mtplx-2.12.2/tests/test_qwen4_fixed_m4_copy_windows.py +749 -0
- mtplx-2.12.2/tests/test_qwen4_fixed_m4_float32_admission.py +42 -0
- mtplx-2.12.2/tests/test_qwen4_fixed_m4_gate_cache.py +114 -0
- mtplx-2.12.2/tests/test_qwen4_fixed_m4_growth_layerwise.py +410 -0
- mtplx-2.12.2/tests/test_qwen4_fixed_m4_verify_exactness.py +382 -0
- mtplx-2.12.2/tests/test_qwen4_growth_recovery.py +225 -0
- mtplx-2.12.2/tests/test_qwen4_m4_stage3_residual_tail.py +659 -0
- mtplx-2.12.2/tests/test_qwen4_moe_sorted_rows.py +155 -0
- mtplx-2.12.2/tests/test_qwen4_opdiet.py +944 -0
- mtplx-2.12.2/tests/test_qwen4_ple_gate_exactness.py +122 -0
- mtplx-2.12.2/tests/test_qwen4_prefill_profile_hc.py +57 -0
- mtplx-2.12.2/tests/test_qwen4_rows_gather_scale.py +47 -0
- mtplx-2.12.2/tests/test_qwen4_verify_gate_sites.py +157 -0
- mtplx-2.12.2/tests/test_qwen4_yarn_amplitude.py +36 -0
- mtplx-2.12.2/tests/test_ram_miss_reason.py +250 -0
- mtplx-2.12.2/tests/test_reasoning_stream_split.py +182 -0
- mtplx-2.12.2/tests/test_recovery_metrics_attribution.py +82 -0
- mtplx-2.12.2/tests/test_release_2114_catalog.py +205 -0
- mtplx-2.12.2/tests/test_release_pins.py +162 -0
- mtplx-2.12.2/tests/test_release_qa_fixwave.py +310 -0
- mtplx-2.12.2/tests/test_retry_prompt_reuses_served_ids.py +280 -0
- mtplx-2.12.2/tests/test_served_window.py +129 -0
- mtplx-2.12.2/tests/test_server_obs_stream_termination.py +483 -0
- mtplx-2.12.2/tests/test_server_openai.py +15017 -0
- mtplx-2.12.2/tests/test_session_bank.py +720 -0
- mtplx-2.12.2/tests/test_small_mac_prefill.py +283 -0
- mtplx-2.12.2/tests/test_split_partials_fp16_526.py +230 -0
- mtplx-2.12.2/tests/test_stable_prefix_boundary.py +352 -0
- mtplx-2.12.2/tests/test_start_reuses_chosen_model.py +491 -0
- mtplx-2.12.2/tests/test_stop_resend_session_continuity.py +736 -0
- mtplx-2.12.2/tests/test_stream_phase_rate.py +193 -0
- mtplx-2.12.2/tests/test_stream_recovery_paths.py +293 -0
- mtplx-2.12.2/tests/test_stream_response_tail.py +533 -0
- mtplx-2.12.2/tests/test_system_memory_guard.py +766 -0
- mtplx-2.12.2/tests/test_thermal_pressure.py +217 -0
- mtplx-2.12.2/tests/test_tool_choice_prompt_modes.py +96 -0
- mtplx-2.12.2/tests/test_tool_fed_retry_parsed_call.py +282 -0
- mtplx-2.12.2/tests/test_ttft_spans.py +614 -0
- mtplx-2.12.2/tests/test_verify_kernels_rehearsal.py +189 -0
- mtplx-2.12.2/tests/test_vision_keyed_partial_restore.py +257 -0
- mtplx-2.12.2/tests/test_vision_keyed_restore_rules.py +172 -0
- mtplx-2.12.2/tests/test_vision_restore_span_guard.py +130 -0
- mtplx-2.12.2/tests/test_vision_session_frontier_gate.py +463 -0
- mtplx-2.12.2/tests/test_vision_session_restore.py +928 -0
- mtplx-2.12.2/tests/test_wide_prefill_chunk_after_reclaim.py +213 -0
- mtplx-2.12.2/tests/test_working_set.py +114 -0
- mtplx-2.12.0/CHANGELOG.md +0 -4431
- mtplx-2.12.0/PKG-INFO +0 -369
- mtplx-2.12.0/README.md +0 -318
- mtplx-2.12.0/mtplx/attention_context.py +0 -139
- mtplx-2.12.0/mtplx/attention_math.py +0 -22
- mtplx-2.12.0/mtplx/attention_split.py +0 -744
- mtplx-2.12.0/mtplx/backends/family_settings.py +0 -540
- mtplx-2.12.0/mtplx/backends/gemma4_assistant.py +0 -3515
- mtplx-2.12.0/mtplx/cache_bank/codec.py +0 -807
- mtplx-2.12.0/mtplx/cache_bank/cold_tier.py +0 -2871
- mtplx-2.12.0/mtplx/cache_state.py +0 -4789
- mtplx-2.12.0/mtplx/chat_encode_cache.py +0 -96
- mtplx-2.12.0/mtplx/chat_encoding.py +0 -314
- mtplx-2.12.0/mtplx/cli.py +0 -5375
- mtplx-2.12.0/mtplx/commands/forge_qwen4_exp.py +0 -553
- mtplx-2.12.0/mtplx/commands/public.py +0 -16415
- mtplx-2.12.0/mtplx/compile_state.py +0 -39
- mtplx-2.12.0/mtplx/compiled_forward.py +0 -144
- mtplx-2.12.0/mtplx/config.py +0 -410
- mtplx-2.12.0/mtplx/constrained.py +0 -544
- mtplx-2.12.0/mtplx/context_copy.py +0 -382
- mtplx-2.12.0/mtplx/dashboard/_static/assets/index-CZI5_teW.js +0 -273
- mtplx-2.12.0/mtplx/dashboard/_static/assets/index-Cmxkc0GH.css +0 -1
- mtplx-2.12.0/mtplx/dashboard/_static/index.html +0 -15
- mtplx-2.12.0/mtplx/default_models.py +0 -1124
- mtplx-2.12.0/mtplx/demotions.py +0 -247
- mtplx-2.12.0/mtplx/engine_session.py +0 -2428
- mtplx-2.12.0/mtplx/errors.py +0 -86
- mtplx-2.12.0/mtplx/expert_layout.py +0 -190
- mtplx-2.12.0/mtplx/fast_sampling.py +0 -738
- mtplx-2.12.0/mtplx/generation.py +0 -15868
- mtplx-2.12.0/mtplx/graphbank.py +0 -5199
- mtplx-2.12.0/mtplx/jsonc.py +0 -127
- mtplx-2.12.0/mtplx/kernels/laguna_moe_gather_gemm.py +0 -273
- mtplx-2.12.0/mtplx/kernels/qwen4_m4_routed_down.py +0 -442
- mtplx-2.12.0/mtplx/kernels/sdpa_2pass.py +0 -373
- mtplx-2.12.0/mtplx/kernels/sdpa_2pass_paged.py +0 -509
- mtplx-2.12.0/mtplx/kernels/sdpa_2pass_paged_q8.py +0 -288
- mtplx-2.12.0/mtplx/kernels/sdpa_gqa_packed.py +0 -784
- mtplx-2.12.0/mtplx/kernels/sdpa_gqa_packed_quant.py +0 -506
- mtplx-2.12.0/mtplx/kernels/sdpa_nax_flash.py +0 -460
- mtplx-2.12.0/mtplx/kernels/sdpa_nax_flash_dsplit.py +0 -388
- mtplx-2.12.0/mtplx/kernels/sdpa_nax_tile.py +0 -419
- mtplx-2.12.0/mtplx/memory_plan.py +0 -809
- mtplx-2.12.0/mtplx/model_scheduler.py +0 -823
- mtplx-2.12.0/mtplx/models/laguna_fused.py +0 -1336
- mtplx-2.12.0/mtplx/models/qwen4_exp.py +0 -6967
- mtplx-2.12.0/mtplx/moe_packed_projections.py +0 -396
- mtplx-2.12.0/mtplx/mtp_patch.py +0 -1157
- mtplx-2.12.0/mtplx/opencode.py +0 -886
- mtplx-2.12.0/mtplx/pi.py +0 -587
- mtplx-2.12.0/mtplx/profiles.py +0 -1089
- mtplx-2.12.0/mtplx/qwen3_5_mtp_patch.py +0 -426
- mtplx-2.12.0/mtplx/qwen4_fixed_verify.py +0 -398
- mtplx-2.12.0/mtplx/qwen4_m4_stage3.py +0 -974
- mtplx-2.12.0/mtplx/qwen4_prefill_profile.py +0 -243
- mtplx-2.12.0/mtplx/reasoning_codecs.py +0 -687
- mtplx-2.12.0/mtplx/runtime.py +0 -1631
- mtplx-2.12.0/mtplx/sampling.py +0 -384
- mtplx-2.12.0/mtplx/server/dashboard_state.py +0 -655
- mtplx-2.12.0/mtplx/server/flight_recorder.py +0 -557
- mtplx-2.12.0/mtplx/server/omlx_bridge/adapter.py +0 -307
- mtplx-2.12.0/mtplx/server/openai.py +0 -39117
- mtplx-2.12.0/mtplx/server/request_policy.py +0 -947
- mtplx-2.12.0/mtplx/session_bank.py +0 -3413
- mtplx-2.12.0/mtplx/system_memory.py +0 -187
- mtplx-2.12.0/mtplx/ui/onboarding.py +0 -2116
- mtplx-2.12.0/mtplx/vendored_arrays_cache.py +0 -822
- mtplx-2.12.0/mtplx/version.py +0 -6
- mtplx-2.12.0/mtplx/vision/splice.py +0 -319
- mtplx-2.12.0/mtplx.egg-info/PKG-INFO +0 -369
- mtplx-2.12.0/mtplx.egg-info/SOURCES.txt +0 -924
- mtplx-2.12.0/mtplx.egg-info/requires.txt +0 -29
- mtplx-2.12.0/pyproject.toml +0 -115
- mtplx-2.12.0/tests/test_api_benchmark_contracts.py +0 -824
- mtplx-2.12.0/tests/test_arrays_cache_patch.py +0 -588
- mtplx-2.12.0/tests/test_attention_gate_exactness.py +0 -43
- mtplx-2.12.0/tests/test_cache_bank.py +0 -1122
- mtplx-2.12.0/tests/test_canon_policy_smalls.py +0 -349
- mtplx-2.12.0/tests/test_chat_encode_cache.py +0 -144
- mtplx-2.12.0/tests/test_cold_tier_defaults.py +0 -114
- mtplx-2.12.0/tests/test_config.py +0 -492
- mtplx-2.12.0/tests/test_constrained.py +0 -739
- mtplx-2.12.0/tests/test_default_models.py +0 -927
- mtplx-2.12.0/tests/test_dense_mrope_compiled_route.py +0 -558
- mtplx-2.12.0/tests/test_draft_telemetry_truth.py +0 -662
- mtplx-2.12.0/tests/test_env_flag_parsing.py +0 -657
- mtplx-2.12.0/tests/test_fixed_m4_dispatch_guard.py +0 -284
- mtplx-2.12.0/tests/test_gdn_boundary_retention.py +0 -150
- mtplx-2.12.0/tests/test_gemma4_session_cache.py +0 -320
- mtplx-2.12.0/tests/test_generation_sustained.py +0 -1786
- mtplx-2.12.0/tests/test_graphbank_compiled_verify.py +0 -2612
- mtplx-2.12.0/tests/test_graphbank_trace_lifetime.py +0 -75
- mtplx-2.12.0/tests/test_host_memory_allowance.py +0 -210
- mtplx-2.12.0/tests/test_jsonc.py +0 -76
- mtplx-2.12.0/tests/test_laguna_model.py +0 -1122
- mtplx-2.12.0/tests/test_memory_plan.py +0 -444
- mtplx-2.12.0/tests/test_memory_pressure_guard.py +0 -407
- mtplx-2.12.0/tests/test_metal_memory_caps.py +0 -416
- mtplx-2.12.0/tests/test_model_scheduler_persistence.py +0 -582
- mtplx-2.12.0/tests/test_mtp_patch.py +0 -454
- mtplx-2.12.0/tests/test_openai_bridge.py +0 -2938
- mtplx-2.12.0/tests/test_opencode.py +0 -656
- mtplx-2.12.0/tests/test_phys_footprint_guard.py +0 -203
- mtplx-2.12.0/tests/test_postcommit_prefix_reuse.py +0 -273
- mtplx-2.12.0/tests/test_prefill_admission_shed.py +0 -740
- mtplx-2.12.0/tests/test_prefill_geometric_tail.py +0 -211
- mtplx-2.12.0/tests/test_prefill_inforward_boundary_capture.py +0 -725
- mtplx-2.12.0/tests/test_pressure_abort_393.py +0 -145
- mtplx-2.12.0/tests/test_public_cli.py +0 -9933
- mtplx-2.12.0/tests/test_px3_planner_seats.py +0 -263
- mtplx-2.12.0/tests/test_qsa_indexer_select_metal.py +0 -949
- mtplx-2.12.0/tests/test_qsa_memory_plan_393.py +0 -137
- mtplx-2.12.0/tests/test_qwen4_fixed_bank_capacity.py +0 -157
- mtplx-2.12.0/tests/test_qwen4_fixed_m4_gate_cache.py +0 -113
- mtplx-2.12.0/tests/test_qwen4_m4_stage3_residual_tail.py +0 -527
- mtplx-2.12.0/tests/test_qwen4_opdiet.py +0 -932
- mtplx-2.12.0/tests/test_reasoning_stream_split.py +0 -129
- mtplx-2.12.0/tests/test_release_2114_catalog.py +0 -203
- mtplx-2.12.0/tests/test_release_pins.py +0 -138
- mtplx-2.12.0/tests/test_release_qa_fixwave.py +0 -307
- mtplx-2.12.0/tests/test_server_obs_stream_termination.py +0 -382
- mtplx-2.12.0/tests/test_server_openai.py +0 -14645
- mtplx-2.12.0/tests/test_session_bank.py +0 -644
- mtplx-2.12.0/tests/test_stable_prefix_boundary.py +0 -350
- mtplx-2.12.0/tests/test_stop_resend_session_continuity.py +0 -732
- mtplx-2.12.0/tests/test_system_memory_guard.py +0 -341
- mtplx-2.12.0/tests/test_vision_restore_span_guard.py +0 -124
- mtplx-2.12.0/tests/test_vision_session_frontier_gate.py +0 -323
- mtplx-2.12.0/tests/test_vision_session_restore.py +0 -764
- {mtplx-2.12.0 → mtplx-2.12.2}/CITATION.cff +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/LICENSE +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/MANIFEST.in +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/NOTICE +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/__init__.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/a3b_compiled_target_prefix.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/a3b_mtp_batch.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/a3b_whole_moe.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/adaptive.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/adaptive_dtemp.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/app_settings.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/aria2_downloader.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/arrays_cache_patch.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/artifacts.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/backends/__init__.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/backends/deepseek_mtp.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/backends/descriptors.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/backends/glm_mtp.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/backends/hy_v3_mtp.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/backends/mimo_mtp.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/backends/nemotron_h_mtp.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/backends/qwen3_next.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/backends/registry.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/backends/step3p5_mtp.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/batched_decode.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/batching/__init__.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/batching/admission.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/batching/buckets.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/batching/scheduler.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/batching/state.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/__init__.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/code_eval.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/programming_prompts.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/prompts/aime_2026.jsonl +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/prompts/calibration_coding.jsonl +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/prompts/default.jsonl +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/prompts/flappy.jsonl +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/prompts/long_code.jsonl +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/prompts/long_code_uncapped.jsonl +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/prompts/python_modules_long.jsonl +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/runners/__init__.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/runners/aime.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/runners/batch_equivalence.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/runners/capture_commit_equivalence.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/runners/competitor_baselines.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/runners/contract_probe.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/runners/harness.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/runners/mtp1_gate.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/runners/mtp1_sampler_smoke.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/runners/mtp_adaptive.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/runners/mtp_chain_probe.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/runners/mtp_depth_grid.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/runners/mtp_depth_sweep.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/runners/mtp_tree_probe.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/runners/multi_qmv_probe.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/runners/preflight.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/runners/runtime_smoke.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/runners/session_bank.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/runners/truth.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/runners/verify_profile.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/runners/verify_qmm_probe.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/runners/verify_ratio.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/schema.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/validators/__init__.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/validators/aime.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/benchmarks/validators/basic.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/block_attention.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/cache_bank/__init__.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/cache_bank/reconcile.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/commands/__init__.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/commands/forge.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/commands/forge_mixed_convert.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/commands/forge_qwen4_audit.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/commands/trace.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/commands/trace_analysis.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/commands/trace_clients.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/commands/trace_metrics.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/commands/trace_record.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/commands/trace_report.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/compressed_tensors.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/constants.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/correctors/__init__.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/correctors/diagonal_affine.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/correctors/low_rank.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/daemon_client.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/dashboard/__init__.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/data/qwen38_code_ranked_64k.npy +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/deepseek_mtp_patch.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/deepseek_v4_adaptive_width.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/deepseek_v4_attention_island.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/deepseek_v4_attn_proj_wide_m3.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/dense_mrope.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/diagnostics.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/draft_lm_head.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/draft_sampling.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/env.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/fan_mode.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/forkev_telemetry.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/frspec_draft.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/gdn_capture.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/gemma4_pair.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/glm_mtp_patch.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/hardware.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/hf_loader.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/hy_v3_mtp_patch.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernel_selfcheck.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/__init__.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/a3b_whole_moe.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/copy_leaf.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/fused_norm.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/gdn_blocked_prefill.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/gdn_conv_norm.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/gdn_gated_norm.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/gdn_out_fused.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/gdn_prefill_prework.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/gdn_step_fused.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/gdn_tape_headquarter.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/hadamard_rotate.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/hc_prefill.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/hyper_connection.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/hyper_connection_v3.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/laguna_decode.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/laguna_dense_mlp.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/laguna_gated_oproj.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/laguna_moe_combine.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/laguna_moe_merged.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/laguna_moe_shared.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/laguna_moe_swiglu.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/laguna_prefill_moe_combine.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/laguna_prefill_qk_rope.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/laguna_prefill_router.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/laguna_qk_rope_sliding.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/laguna_qk_yarn_full.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/laguna_qkvg_fused.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/laguna_residual_router.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/laguna_route_csort.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/laguna_router_topk.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/laguna_sdpa_2pass.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/laguna_sdpa_pair.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/laguna_steel_attn.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/lm_head_topk.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/logits_topk.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/moe_glu_decode.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/native_gdn_tail.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/qmv4_matmul.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/qsa_dense_band_sdpa.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/qsa_flash_skip.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/qsa_indexer_compile.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/qsa_indexer_prefill.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/qsa_indexer_prepare.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/qsa_indexer_select.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/qsa_prefill_direct.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/qsa_prefill_flash.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/qsa_prefill_probe.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/qwen4_m4_rope.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/qwen4_m4_route.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/qwen4_m4_routed_glu.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/qwen4_m4_stage3.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/qwen4_moe_prefill_combine.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/qwen4_qsa_m4_fused_kv_gather.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/ternary_qmv.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/verify_mlp_fused.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kernels/vqmm_crossrow.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kpi/__init__.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kpi/reference_vllm.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kpi/runtime_kpis.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/kv_quant.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/laguna_alt_step.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/laguna_compiled_step.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/lane_explain.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/launch_lane.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/lfm2_fast.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/loop_guard.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/metadata_scrub.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/mimo_mtp_patch.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/mlx_process_env.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/model_catalog.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/model_updates.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/models/__init__.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/models/deepseek_v4.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/models/laguna.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/models/laguna_config.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/models/prism_hadamard_qwen35.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/mtp_activation_stats.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/mtp_adapters.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/mtp_batch_numerics.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/native_mlp.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/nax_detect.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/nax_verify.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/nemotron_h_mtp_patch.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/ngram_row_layout.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/optimization_profiles.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/os_memory.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/packed_concats.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/ple_prefill_lookahead.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/ple_row_gather.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/prefill_bench.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/prefill_rungs.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/progress_heartbeat.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/proj_fusion.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/proj_quant.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/proposal_reranker.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/qsa_mtp_precompute.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/qsa_restore_staging.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/qwen4_block_verify.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/qwen4_claim_contract.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/qwen4_draft_device_chain.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/qwen4_draft_k20_prescatter.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/qwen4_verify_glue.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/qwen_row_owned_router.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/ragged_attention.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/ragged_kv_cache.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/reasoning_effort.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/request_capture.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/retrieval.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/roofline_profile.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/rope_origin.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/route_tape.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/runtime_options.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/server/__init__.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/server/hyper.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/server/mtp_batch.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/server/omlx_bridge/__init__.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/server/omlx_bridge/thinking.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/server/omlx_bridge/tool_calling.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/server/response_envelope.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/server/responses.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/server_urls.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/speculative.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/step3p5_mtp_patch.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/swival.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/templates/qwen36_froggeric_v19/chat_template.jinja +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/templates/qwen36_froggeric_v21_3/chat_template.jinja +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/thermal.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/thermal_sidecar.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/thinking_guard.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/trace_parity.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/turboquant.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/ui/__init__.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/ui/banner.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/ui/chat_printer.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/ui/download_progress.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/ui/panels.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/ui/progress.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/vendored_hy_v3.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/verify_crossrow.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/verify_kernels.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/verify_qmv.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/vision/__init__.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/vision/mrope.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/vision/processing.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/vision/qwen3_vl_tower.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx/vision_graft.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx.egg-info/dependency_links.txt +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx.egg-info/entry_points.txt +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/mtplx.egg-info/top_level.txt +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/agent_session_gate.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/agent_user_path_qa.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/aime_serve_gate.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/aime_shape_memory_bench.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/analyze_head_sweeps.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/audit_agent_prompt_fidelity.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/audit_catalog_sizes.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/bonsai_memory_table.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/build_bonsai_mtplx_pack.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/build_flash_next_quality_pack.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/build_flash_next_quality_pack.sh +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/bundle_native_runtime_wheel.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/check_ai_attribution.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/code_eval_gate.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/collect_mtp_activation_stats.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/collect_mtp_hidden_calib.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/combine_mtp_adapters.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/compiled_verify_exactness.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/convert_qwen4_exp_fp8.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/convert_step37_to_mtplx_step3p5.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/decodecliff_probe_20260830.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/decodecliff_pulse_cell_20260830.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/deepseek_v4_adaptive_width_arms.sh +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/deepseek_v4_adaptive_width_guarded.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/deepseek_v4_attention_island_arms.sh +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/deepseek_v4_attention_island_guarded.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/deepseek_v4_attn_proj_wide_m3_arms.sh +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/deepseek_v4_attn_proj_wide_m3_guarded.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/deepseek_v4_build_mtp_model.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/deepseek_v4_decode_verify.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/deepseek_v4_dispatch_census.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/deepseek_v4_guard_window.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/deepseek_v4_logits_gate.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/deepseek_v4_moe_tail_arms.sh +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/deepseek_v4_moe_tail_gate.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/deepseek_v4_moe_tail_guarded_bracket.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/deepseek_v4_mtp_bind_check.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/deepseek_v4_mtpk_bench.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/deepseek_v4_op_census.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/deepseek_v4_smoke_generate.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/deepseek_v4_sparse_gate_probe.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/deepseek_v4_validate_moe_tail_k3_bracket.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/dense_mrope_parity_ours.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/dense_mrope_reference_check.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/dense_vision_compiled_parity.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/eval_mtp_corrector.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/filter_mtp_adapter.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/fp16_turbo_exactness_20260707.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/fresh_venv_smoke.sh +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/gauntlet_scoreboard.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/gen_models_manifest.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/graft_vision_tower.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/hygiene_scan.sh +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/install_macos.sh +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/install_preview_global.sh +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/kvcache_exactness_audit_20260703.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/kvcache_soak_20260703.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/kvcache_warm_probe_20260703.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/make_fp16_precision_sibling.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/measure_request_capture.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/midform_gate.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/model_release_policy.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/mtp_cost_probe.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/oc_tap.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/oc_tap_diff.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/opencode_concurrency_qa.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/phase0h_paged_verifier_exactness.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/pillar_gate_qa.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/probe_mlx_pr3026_qsdpa.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/probe_mx_compile_buckets.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/probe_paged_gqa_sdpa_routes.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/qsa_indexer_prefill_numeric_check.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/qsa_prefill_battery2_20260830.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/qsa_prefill_battery_20260830.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/qsa_prefill_flash_numeric_check.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/quality_fp16_parent_logitdiff_20260707.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/qwen35b_mtp_batch_evalplus_guarded.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/qwen35b_mtp_batch_numerics_attribution.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/qwen35b_mtp_batch_numerics_guarded.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/qwen4_quality_component_parity.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/qwen4_vision_compiled_parity.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/qwen4exp_mtp_tiny_smoke.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/qwen4exp_numeric_check.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/r1_chisquare_verifier_correctness.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/release_macos_v1.sh +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/render_release_notes.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/replay_chat_request.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/restamp_head_quant_packs.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/run_context_degradation_diagnostics.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/run_harness_check.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/select_runtime_wheel.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/serve_openai_mtplx.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/session_cache_followup_qa.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/session_forensics.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/sparkle_rehearsal_kit.sh +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/step_acceptance.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/step_smoke.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/stream_qa_gate.sh +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/train_mtp_adapter_c4.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/upload_head_quant_packs.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/validate_step_mtp_injector.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/walltime-lab/run_project.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/scripts/write_launch_lane_matrix.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/setup.cfg +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_a3b_compiled_target_prefix.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_a3b_mtp_batch.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_a3b_mtp_batch_driver.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_a3b_whole_moe.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_adaptive.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_adaptive_dtemp.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_agent_prompt_fidelity.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_aime_serve_gate.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_allow_swap_427.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_app_cli_env_parity.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_ar_batch_cache_family_guard_420.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_ar_batch_penalties.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_aria2_downloader.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_artifacts.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_async_decode.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_attention_split.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_background_warmup.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_bank_history_policy.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_batch_pump_heartbeat.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_batched_decode.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_batched_greedy_argmax_tiebreak.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_batching_foundation.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_bench_depth_sweep_flags.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_block_verify_exact_law.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_bonsai_memory_table.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_bonsai_pack_metadata.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_bracket_tool_rescue.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_build_bonsai_mtplx_pack.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_bundle_native_runtime_wheel.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_cache_state.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_canon_gate_hardening.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_canon_postcommit_extension.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_canonicalize_multimodal.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_ccopy_bank_route.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_cli_greedy_draft_coupling.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_cli_parity_tools.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_client_controls_default.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_code_eval.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_code_eval_gate.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_cold_encode_bounded_units.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_cold_prefix_ram_shadow.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_cold_tier_backlog_admission.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_cold_tier_disk_usage_scan.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_cold_tier_eviction_yield.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_cold_tier_foreground_yield.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_cold_tier_min_useful_matched.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_cold_tier_reconcile.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_cold_tier_stats_cache.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_cold_tier_write_budget.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_committed_reasoning_canonicalization.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_committed_token_splice.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_compile_state.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_compiled_ar_wiring.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_compiled_forward.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_compressed_tensors.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_config_profile_precedence.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_context_copy_joined_tokens.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_context_copy_ramp.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_context_copy_stats.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_context_degradation_profiles.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_convert_qwen4_exp_fp8_scales.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_correctors.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_cost_depth_policy.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_daemon_client.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_dashboard_endpoints.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_decode_growth_guards.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_deepseek_v4_adaptive_width_bracket.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_deepseek_v4_adaptive_width_policy.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_deepseek_v4_attention_island.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_deepseek_v4_attention_island_bracket.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_deepseek_v4_attn_proj_wide_m3.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_deepseek_v4_attn_proj_wide_m3_bracket.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_deepseek_v4_decode.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_deepseek_v4_dtypes.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_deepseek_v4_indexer.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_deepseek_v4_kernel_paths.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_deepseek_v4_loader.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_deepseek_v4_moe_tail.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_deepseek_v4_moe_tail_bracket.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_deepseek_v4_mtp.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_deepseek_v4_new_math.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_deepseek_v4_o_lora.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_deepseek_v4_parity.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_deepseek_v4_sinkhorn_kernel.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_deepseek_v4_spec.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_deepseek_v4_swiglu_clamp.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_demotions.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_demotions_generation_sites.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_demotions_graphbank_sites.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_demotions_qsa_prefill_sites.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_dense_decode_max_context.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_dense_mrope.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_dense_mrope_generation.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_dense_vision_compiled_parity_script.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_descriptor_family_collisions.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_descriptors_mimo_tune.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_descriptors_qwen4_exp_tune.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_device_draft_core.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_diagnostics.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_doctor_failed_start.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_download_progress.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_draft_confidence_trace.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_draft_launch_provenance.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_draft_lm_head.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_draft_lm_head_chunked_requantization.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_draft_temp_policy.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_encode_seam_unification.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_endpoint_probe_hygiene.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_engine_session_concurrency.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_engine_session_env.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_exact_verify_route.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_exactness_320_319.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_expected_value_greedy.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_family_settings.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_fast_sampling.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_final_committed_frontier_byte_skip.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_final_tool_preamble_stream_parity.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_finish_stop_origin.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_flash_next_quality_m5_ultra.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_flight_recorder.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_forge_cli.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_forge_discover_filter.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_forge_family_serve_depths.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_forge_mixed_convert.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_forge_mtp_key_forms.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_forge_mtp_norm_convention.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_forge_quality_build_script.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_forge_qwen4_exp_lane.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_forge_qwen4_exp_roundtrip.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_forge_verify_depths.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_forkev_telemetry.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_fused_add_rmsnorm_exactness.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_gdn_blocked_prefill.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_gdn_capture_rows.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_gdn_conv_norm_fused.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_gdn_conv_norm_rows.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_gdn_gated_norm.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_gdn_out_fused.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_gdn_postconv_fusion.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_gdn_postconv_impl_gate.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_gdn_prefill_prework.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_gdn_step_fused.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_gdn_tape_headquarter.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_gemma4_rotating_cache_trim.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_generation_rate_fields.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_generation_store_on_prefill.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_greedy_confidence_runner_telemetry.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_greedy_draft_coupling.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_greedy_trio_ports.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_hc_prefill.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_health_qwen4_install_reports.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_hermes_native_contract.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_hf_loader.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_hy_v3_mtp_backend.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_hy_v3_mtp_graft.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_hygiene_scan.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_hyper_scheduler.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_hyper_v3.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_idle_persistence_pump.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_idle_postcommit_subagent.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_install_macos_script.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_issue_404_macos27_mpp_compat.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_kernel_selfcheck.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_kernel_selfcheck_bonsai.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_kernel_selfcheck_qwen4_prefill.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_kv_quant_memory.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_kv_quant_q4_kernel_route.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_laguna_alt_step.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_laguna_compiled_step.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_laguna_fused.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_laguna_steel_attn.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_launch_lane.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_lazy_snapshot_cow.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_lease_keeps_draft_history.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_lfm2_descriptor_resolution.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_lfm2_fast.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_loop_guard.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_max_idle_watchdog.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_max_lifecycle.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_memory_plan_bonsai.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_metadata_scrub.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_midform_gate.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_mimo_catalog.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_mlx_process_env.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_model_catalog.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_model_scheduler.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_model_updates.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_moe_force_unsorted.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_moe_glu_decode.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_moe_glu_verify.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_moe_packed_projections.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_mtp_activation_stats.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_mtp_adapters.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_mtp_alias_load_path.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_mtp_batch_numerics.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_mtp_batch_serving.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_mtp_depth_n.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_mtp_depth_sweep.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_mtp_final_state_covers_committed_tokens.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_mtp_norm_convention.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_mtp_payload_guards.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_mtp_weightless_degrade.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_nax_attention_hardware.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_nax_detect.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_nax_verify.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_ngram_prewarm_option.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_ngram_prewarm_reservation.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_ngram_row_hotness.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_ngram_row_layout.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_ngram_table_memory.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_no_mlx_imports.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_non_finite_logits_and_lazy_sampler_law.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_omlx_bridge.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_omlx_bridge_late_system_messages.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_onboarding.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_one_shot_family_contract.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_optimization_profiles.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_orphan_stream_guard_first_line.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_orphan_tool_markup.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_pack_contract_states.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_paged_tailmask_elide.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_passive_probe_telemetry.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_penalties.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_penalty_request_wiring.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_persistent_replay.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_phase0h_paged_verifier_exactness.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_policy_fingerprint_stability.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_postcommit_arrival_wait.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_postcommit_cross_session_yield.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_postcommit_resolve_for_request.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_postcommit_tools_plumbing.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_postcommit_wait.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_postcommit_wait_integration.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_postcommit_wait_progress.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_pre_first_token_telemetry.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_prefill_bench.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_prefill_chunk_defaults.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_prefill_tps_regression.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_prism_bonsai_kernels.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_prism_hadamard_qwen35.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_profile_default_resolution.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_profiles.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_proj_fusion.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_proj_quant.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_prompt_encoding.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_pull_tls_trust.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qmv4_matmul.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qsa_dense_band_sdpa.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qsa_flash_skip.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qsa_gate_safety.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qsa_gather.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qsa_gather_rows.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qsa_indexer_compile.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qsa_indexer_fused_integration.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qsa_indexer_prefill_mpp_runtime.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qsa_indexer_prefill_runtime_blocks.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qsa_indexer_prefill_simd_topk.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qsa_indexer_prefill_static.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qsa_indexer_prepare_metal.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qsa_mtp_precompute.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qsa_mtp_precompute_wiring.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qsa_portable_producer_gate.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qsa_prefill_direct.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qsa_prefill_direct_greedy.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qsa_prefill_direct_numeric.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qsa_prefill_direct_routing.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qsa_prefill_direct_static.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qsa_prefill_flash_contract.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qsa_prefill_flash_direct_parity.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qsa_prefill_flash_static.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qsa_prefill_gather_tier.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qsa_prefill_runtime_gates_static.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qsa_prefill_wiring.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qsa_restore_staging.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qsa_selector_prefix_contract.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qsa_tiled_scores.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen35b_mtp_batch_evalplus_guarded.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen35b_mtp_batch_numerics_attribution.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen35b_mtp_batch_numerics_guarded.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen38_family.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen3_5_mtp_backend.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen3_5_mtp_object_level.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen3_5_mtp_sidecar_norms.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_block_verify.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_claim_contract.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_compiled_mtp_prepare.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_draft_chain_pipeline.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_draft_device_chain.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_draft_k20_prescatter.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_exp_capture_commit.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_exp_compile.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_exp_gdn_inproj_fusion.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_exp_inforward_boundary_capture.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_exp_qsa_cache.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_exp_qsa_qkv_fusion.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_exp_rope.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_fixed_bank_bf16_parity.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_fixed_host_tokens_static.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_fixed_m4_vision_admission.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_frspec_native.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_hc_prefill_compile.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_hc_prefill_read_switch.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_m4_routed_glu.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_m4_stage3.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_moe_prefill_combine.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_mtp_history_cache_only.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_ple_first_gather_early.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_ple_lookahead_restored_suffix.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_ple_prefill_lookahead.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_ple_serving_verdicts.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_prefill_dq_gemm.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_prefill_midloop_eval.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_qsa_m4_fused_kv_gather.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_route_kernel.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_verify_glue.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_verify_sdpa_head_chunk.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_vision_compiled_parity_cpu.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_vision_compiled_parity_script.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_vision_compiled_route_e2e.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_vision_mrope.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen4_wide_prefill_chunk.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_qwen_row_owned_router.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_ragged_kv_cache.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_release_2114_manifest.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_release_notes_template.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_repetition_stop_legitimate_code.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_repetition_stop_long_cycle.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_request_capture.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_request_log_privacy.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_request_observability_golden.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_response_envelope_parity.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_retrieval.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_route_tape.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_runtime_ar_final_state.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_runtime_deepseek_v4_o_lora.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_runtime_env_flags_refresh.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_runtime_env_latebind.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_runtime_kpis.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_runtime_model_alias.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_runtime_obs_graphbank.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_runtime_obs_kernel_bails.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_runtime_obs_profiles.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_runtime_obs_stream_holdback.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_runtime_scheduler_histogram.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_runtime_session_sizing.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_sampling.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_scoped_reasoning_history.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_sdpa_gqa_packed.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_sdpa_gqa_packed_quant.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_sdpa_nax_flash.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_sdpa_nax_tile.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_select_runtime_wheel.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_serve_generation_mode_default.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_serve_network_share.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_server_obs_caps_and_health.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_server_obs_postcommit_health.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_server_obs_reasoning_rows.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_server_origin_policy.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_server_responses.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_session_bank_boundary_shed.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_session_bank_chain_shed.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_session_bank_env_caps.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_session_bank_gc.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_session_bank_lease_accounting.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_session_bank_protected_terminal.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_session_bank_recurrent_partial_restore.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_session_bank_restore_aliasing.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_session_bank_snapshot_settle.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_session_identity_turn_boundary.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_smart_fan_stall_probe.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_smart_fan_wedged_request.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_snapshot_free_rejection_repair.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_source_wrapper.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_ssd_boundary_repersist.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_ssd_spill.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_stats_footer_scope.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_step3p5_mtp_patch.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_stream_error_receipt.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_stream_guard_env.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_stream_release_pacer.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_stream_splitter_contract.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_stream_stall_watchdog.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_stream_transcript_golden.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_stream_visible_cadence.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_sustained_long_context_qa.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_tail_ar_warm_restore_identity.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_tail_config_sampler_presence.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_tail_gemma4_stream_holdback.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_tail_profile_truth.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_tensoroffset_kv_inplace.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_thermal.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_thermal_sidecar.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_thinking_guard.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_thinking_splitter_marker_quotes.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_tiny_max_tokens_warning_436.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_tokenizer_pretokenizer_repair.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_tool_aware_stream_translator.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_tool_call_hidden_guard_and_key_repair.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_tool_nested_args_streaming.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_trace_diagnostics.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_trace_incidents.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_trace_parity.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_tune_qwen4_exp_verify_strategy.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_tune_record_wizard_parity.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_turboquant.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_turboquant_fallback.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_ui_progress.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_validators.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_verify_crossrow.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_vision_control_literals.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_vision_session_cache.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_vision_tower.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_vision_trunk_forward_positions.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/tests/test_vllm_reference.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/vllm_metal/__init__.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/vllm_metal/metal/README.md +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/vllm_metal/metal/__init__.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/vllm_metal/metal/build.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/vllm_metal/metal/constants.py +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/vllm_metal/metal/kernels_v1/copy_blocks.metal +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/vllm_metal/metal/kernels_v1/float8.metal +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/vllm_metal/metal/kernels_v1/gather_kv_cache.metal +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/vllm_metal/metal/kernels_v1/kv_scale_update.metal +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/vllm_metal/metal/kernels_v1/pagedattention.metal +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/vllm_metal/metal/kernels_v1/reshape_and_cache.metal +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/vllm_metal/metal/kernels_v1/utils.metal +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/vllm_metal/metal/kernels_v2/copy_blocks.metal +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/vllm_metal/metal/kernels_v2/float8.metal +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/vllm_metal/metal/kernels_v2/gather_kv_cache.metal +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/vllm_metal/metal/kernels_v2/gdn_linear_attention.metal +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/vllm_metal/metal/kernels_v2/kv_scale_update.metal +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/vllm_metal/metal/kernels_v2/pagedattention.metal +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/vllm_metal/metal/kernels_v2/reshape_and_cache.metal +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/vllm_metal/metal/kernels_v2/turboquant.metal +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/vllm_metal/metal/kernels_v2/utils.metal +0 -0
- {mtplx-2.12.0 → mtplx-2.12.2}/vllm_metal/metal/paged_ops.cpp +0 -0
|
@@ -0,0 +1,4570 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable user-facing changes to MTPLX. The format is based on
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/); versions follow
|
|
5
|
+
[Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [2.12.2] - 2026-10-03
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- **Macs with 8, 16 and 32 GB serve ordinary prompts again.** 2.12.1 refused prompts that 2.12.0 served on these Macs with the model the app picks for them. On an M5 Max set up as each Mac (its engine limit, its free-memory floors and a fixed free-memory reading), 2.12.1 refused an 18-token prompt to Qwen 3.5 4B on 8 GB with 3 GiB free, 2,600 to 7,800-token prompts and an agent's first turn to Ternary Bonsai 2 27B on 16 GB, and 6,800 and 7,800-token prompts and an agent's third turn to Qwen 3.8 27B Optimized Speed on 32 GB; GitHub's 7 GB M1 test machine refused a one-line request with a 507. 2.12.2 serves all of them (October 3), and the M1 test machine passes. Three changes:
|
|
12
|
+
- A dense model's prompt is priced at what it was measured to use through `mtplx serve`: the 4B 0.19 GiB for a 59-token prompt and 1.55 to 1.65 GiB per 2,048-token chunk, Bonsai 2 27B 0.45 GiB for a 98-token prompt, Bonsai and the 27B 2.5 to 2.7 GiB per 2,048-token chunk, plus a margin of 0.09 to 0.25 GiB. 2.12.1 charged every model the 27B's 3 GiB per 2,048-token chunk and about 2 GiB for a one-line prompt. Models with routed experts, Flash-Next and Gemma 4 keep their 2.12.1 prices.
|
|
13
|
+
- A prompt that does not fit in 2,048-token chunks runs in 1,024-token chunks, or 512-token chunks for prompts up to about 8,000 tokens, before it is refused (not Flash-Next or Gemma 4, which keep their own chunk widths). A narrower chunk is used only where it saves at least 128 MiB. A 1,024-token chunk uses about 1 GiB less on the 27B models; a 7,000-token Bonsai prompt took 9.5 s at 2,048, 9.6 s at 1,024 and 9.7 s at 512 tokens a chunk (single runs).
|
|
14
|
+
- Memory outside the GPU allocator is charged only past 4 GiB on Macs under 64 GB. 2.12.1 charged it past a sixteenth of RAM (1 GiB on 16 GB, 2 GiB on 32 GB), while a healthy engine holds 2.1 to 2.9 GiB there with Bonsai and 2.3 to 4.0 GiB with the 27B. Macs with 64 GB or more are unchanged.
|
|
15
|
+
- **#499's 99,355-token 27B turn on a 48 GB Mac runs in 1,024-token chunks**, priced at 34.87 GiB against the 36 GiB limit, where 2.12.1 refused it (tests with the reporter's numbers).
|
|
16
|
+
|
|
17
|
+
### Changed
|
|
18
|
+
|
|
19
|
+
- **Dependencies.** urllib3 2.8.0 (#582 by @dependabot).
|
|
20
|
+
|
|
21
|
+
## [2.12.1] - 2026-10-02
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
|
|
25
|
+
- **Log-probabilities for the first generated token** (#530 and #543 by @jvmenen). `/v1/completions` accepts `logprobs: K` with `max_tokens: 1` and returns OpenAI-shaped `choices[0].logprobs`; `/v1/chat/completions` accepts `logprobs: true` with `top_logprobs: K` and returns `choices[0].logprobs.content`. The values are the raw distribution of the row that produced the first token, before temperature, penalties, grammar masks and steering, and a full-prompt session cache hit uses the restored logits. Streaming, a non-empty `stop`, `max_tokens` other than 1 and K above `MTPLX_PROMPT_LOGPROBS_MAX` (default 128) return 400; `/v1/messages` does not expose logprobs. Chat entries report each token's own bytes as the tokenizer's decoder defines them, or null. The Gemma 4 pair serves it too. Measured by @jvmenen on an M5 Pro 64 GB with Qwen 3.6 35B-A3B, 240 prompts: the same top label as echo prompt scoring on 228 of 240, p50 486 ms against 562 ms.
|
|
26
|
+
|
|
27
|
+
- **Prompt scoring on Gemma 4** (#551 by @jvmenen). `/v1/completions` with `echo`, `logprobs` and `max_tokens: 0` answered HTTP 500 on the Gemma 4 pair. Scoring now runs the target's own prompt pass in spans no wider than the admitted prompt width, produces logits in bounded chunks, follows the serving memory policy and answers 507 under memory pressure.
|
|
28
|
+
|
|
29
|
+
- **Every response says where its time to the first token went.** `mtplx_stats.ttft_spans.exclusive_s` splits the time from the request's arrival to the first visible delta (content, reasoning or tool call) into named, non-overlapping spans: body parse, encode, the previous turn's save, the model-thread queue and what it waited behind, the memory admission and the engine. `attempts`, `discarded_attempt_wall_s` and `retry_path` name any generation a repair path threw away. Chat, Anthropic, `/v1/completions` and batched responses carry the spans, and `/health` shows the median and p90 of each span over recent requests.
|
|
30
|
+
|
|
31
|
+
- **Thermal pressure in `/health` and every receipt.** A cached sampler reads macOS's thermal pressure every two seconds; every flight sample carries it, every request receipt carries `thermal_pressure` and `thermal_pressure_max`, and decoding makes no extra system call.
|
|
32
|
+
|
|
33
|
+
- **`execution_window` in `/health`, and Pi and OpenCode set up from it.** The engine publishes the context window it can serve on this Mac. The app syncs Pi's and OpenCode's settings to it once the engine answers, and `mtplx start pi`, `mtplx start opencode` and `mtplx connect opencode` do the same, including against a server that is already running and a server that runs another model, which the client is now set up for. Pi's `maxTokens` stays equal to the window, as in 2.12.0, and a window pair the user set is never changed.
|
|
34
|
+
|
|
35
|
+
- **`--memory-limit SIZE|max` for `mtplx serve`** (#548), and `MTPLX_MEMORY_LIMIT_BYTES=max` for launchers. `max` uses everything outside macOS's own reserve, 112 GiB on a 128 GB Mac, for a Mac that runs nothing but the server; on Macs of 32 GB and under it equals the default. The whole-Mac checks apply at every limit.
|
|
36
|
+
|
|
37
|
+
- **The app explains a re-read and warns about disk.** While a prompt is read again, the Live gauge and the Activity and Requests rows say why, where the state resumes, what the SSD restored and the server's estimate, which is shown only after a prompt speed has been measured. Every cache miss code the server sends has a plain sentence in all 13 languages. A banner on the Live tab shows when the SSD cache is low on disk (two copies of the largest conversation do not fit above the 10 GiB floor) or full (saves stopped).
|
|
38
|
+
|
|
39
|
+
- **The live gauge shows the current phase's decode rate** (reasoning, answer or tool call) instead of the whole answer's average, which a slow reasoning phase dragged down; the finished request keeps its overall rate.
|
|
40
|
+
|
|
41
|
+
- **Diagnostics.** `memory_guard.guard_degraded` on `/health` and the dashboard stream when a guard step raises; `session_bank.last_ram_miss_reason` for a shared prompt start the RAM cache passed over (#534 by @jvmenen); `prefill_system_memory` in each request's metrics row; `MTPLX_KV_ATTENTION_TRACE=1` or `nonfinite` and `MTPLX_PREFILL_GUARD_TRACE=1`. A request that fails with non-finite logits logs the cache, route, offset and capacity of the first layer involved.
|
|
42
|
+
|
|
43
|
+
- **Experimental, off by default.**
|
|
44
|
+
- `MTPLX_SSD_INCREMENTAL_ENCODE=1` (#556 by @jvmenen): the SSD cache saves only the KV blocks that changed. Off because its block fingerprint is not a content identity: flipping the signs of any two float32 values leaves it unchanged, so a changed block could reuse its predecessor's data and restore the wrong state.
|
|
45
|
+
- `MTPLX_FIXED_M4_COPY_WINDOWS=1`: Flash-Next's context-copy rounds replay on the compiled verifier, bit-identical to the eager window in tests. Off because it has not been checked on the full model; no speed is claimed.
|
|
46
|
+
- `MTPLX_THINKING_GUARD_SCOPE=all` (#545 by @bpmforge): the opt-in reasoning budget guard (`--agent-thinking-budget` or `MTPLX_THINKING_BUDGET`) also covers plain chat with thinking on. Off because it can end reasoning early.
|
|
47
|
+
|
|
48
|
+
### Changed
|
|
49
|
+
|
|
50
|
+
- **The memory guard prices every request and releases idle state before it refuses** (#567, #525, #499). 2.12.0 checked only requests with 4,096 or more uncached tokens and charged them once plus a flat 3 GiB. Every request is now priced at its largest moment (the end of the prompt including any restore copy, the move to paged memory, the start of the answer), with full-width quantized snapshots, routed MoE rows, the draft head's history (4,096 bytes a token on the 27B) and each backend's own prompt pass. Before a refusal the engine gives back, in measured steps, its buffer pool, the request's own prompt copy, superseded entries, and idle conversations whole, oldest and SSD-saved first, never a conversation that is generating; a request that arrives during a release waits instead of getting a 409. A refusal is a 507 that names what holds the memory and when a retry can work. On a 48 GB Mac with the 27B, #499's turn is priced at 39.47 GiB and refused before prefill, where 2.12.0 admitted it and reached 38.5 GiB against a 36 GiB limit. Pressure trims now halve what the session cache holds: on #525's 64 GB Mac, 421 trims in a row had evicted nothing. A guard step that raises fails open and shows as `guard_degraded`. `MTPLX_PREFILL_ADMISSION_MIN_MISS_TOKENS` is removed.
|
|
51
|
+
|
|
52
|
+
- **The guard reads what macOS can hand out, before every prompt chunk.** 2.12.0 read `kern.memorystatus_level`, which counts memory macOS can free only by compressing other apps as available. The supply is now free, purgeable and file-backed pages from `host_statistics64`, read before every prompt chunk. The stop floor is the largest of 1 GiB, 2.5% of RAM and a sixteenth of wired memory, and the shed floor is twice that (6.2 to 6.4 GB and 12.4 to 12.8 GB in the October 2 session). A prompt also stops when macOS compresses another eighth of RAM across a run of requests (16 GiB on 128 GB), or holds a quarter of RAM compressed while still losing ground; macOS 27's early compression on a healthy Mac does not count. When only the Mac is short, the engine clears its buffer pool, then narrows Flash-Next's prompt chunk from 4,096 to 2,048 rows, then releases idle state, and picks the chunk width again afterwards. On September 29, 2.12.0's 130,048-token cold Flash-Next prompt drove 27 GB into compressed memory until the test's watchdog stopped it; build 0e0bb014 served it with no swap and 15 MB of compressor growth (not re-run on the final build).
|
|
53
|
+
|
|
54
|
+
- **Memory outside the GPU allocator counts once it passes a sixteenth of RAM.** 2.12.0's allowance was the larger of 8 GiB and RAM minus the reserve minus the limit, so the ceiling stayed at 112 GiB on a 128 GB Mac at any limit. The allowance is now RAM/16 between 1 and 8 GiB, an explicit limit caps it and the MLX cache limit at a twelfth of the limit (8 GiB at 96, 7.5 at 90, 4 at 48), and memory past it is charged. On October 2 the process ceiling was 98 GiB at the 90 GiB default.
|
|
55
|
+
|
|
56
|
+
- **The default memory limit on 128 GB Macs is 90 GiB (was 96 GiB).** From 128 GB up the engine leaves at least 38 GiB outside its allocator; Macs under 128 GB and from 192 GB up keep the 75% rule. On September 27, with 16 GB of other apps (build aa8ec218), a 74,000-token compaction-style request was refused with a 507 at 96 GiB to keep the Mac safe and served at 90 GiB. A model whose resident floor is between 90 and 96 GiB keeps 96, and one above 96 keeps everything outside the reserve. What it costs: on 128 GB M1 to M4 Macs without the native prompt kernel (pip and Homebrew), Flash-Next Optimized Speed plans 65,536 tokens instead of 114,688; from the app, with the kernel, they plan 262,144 once the kernel passes its startup check, which has not run on M1 to M4 hardware. Qwen 3.8 27B Optimized Quality's session cache under a full 262K window is 42.5 GiB instead of 48 GiB. `--memory-limit 96G` restores the old limit.
|
|
57
|
+
|
|
58
|
+
- **The MLX buffer pool goes back to macOS when a request ends and when the model goes idle.** It held 0.53 GiB at the median and 3.5 GiB at p90 between 4,104 logged requests. It keeps its full size while work runs, because a 2 GiB bound cost 6 to 10% of the 16K and 64K prompt rate. `MTPLX_CLEAR_CACHE_AFTER_REQUEST=off` keeps the pool.
|
|
59
|
+
|
|
60
|
+
- **Flash-Next verifies without copying the conversation** (`MTPLX_ONE_COPY`, on by default for Flash-Next). The compiled verifier adopts the buffers the prompt was read into instead of copying them into a padded buffer, the session cache keeps a reference to the live cache with its recurrent restore points instead of views, and buffers that must grow are resized one layer at a time (0.36 to 0.57 GB at the 147K edge in the October 2 session, against 4.2 GB for a padded copy). A turn that cannot afford the growth verifies eagerly for that turn only. The memory saved has not been measured on its own. `MTPLX_ONE_COPY=0` restores the copying store.
|
|
61
|
+
|
|
62
|
+
- **An answer whose cache cannot grow stops between rounds.** Growth happens at the top of a round; a refused growth ends the answer with every streamed token kept, `finish_reason` `length`, `memory_stop` and a plain message, and the next turn reuses the conversation. A context-copy round reserves before it stages its window, and a cancel or error during growth hands the cache back whole. Before its prompt, a request's longest answer is priced (`_answer_room`); when only a shorter answer fits, the request runs with that limit and reports `answer_capped_for_memory` if it reaches it. The stop has run only in tests on small models.
|
|
63
|
+
|
|
64
|
+
- **Screenshots keep the conversation's cache.** Image conversations are matched and committed in a view keyed by each image's content, a restore never lands inside an image, and recurrent restore points are kept before, between and after images. A turn that changes text after a screenshot resumes at the change; a request with different pixels in the same place never reuses the old image's state.
|
|
65
|
+
|
|
66
|
+
- **Recurrent restore points are kept within a byte budget** on a fixed 8,192-token grid instead of being thinned after every capture; a 16,371-token match that restored at 4,096 now restores at 8,192.
|
|
67
|
+
|
|
68
|
+
- **The SSD cache replaces an entry only after its successor is installed, and holds two copies of the largest conversation.** Both writers reserve, write beside the existing entries, install, and only then retire old entries; a save that does not fit is skipped with a recorded reason. The budget is normally a quarter of the usable space, raised to two copies of the largest conversation above a 10 GiB free-disk floor and never above the configured cap; `stats()` reports the disk state.
|
|
69
|
+
|
|
70
|
+
- **A streamed turn ends at its last token** (adapted from #557 by @jvmenen). For a session the client names, the final chunk and `[DONE]` no longer wait for the session's save (18 ms at the median and 160 ms at p90 in the flight logs, up to 30 s behind another client's prompt, #425). The next request of the same session waits for that save before it reads the session, other sessions never wait, and a stalled model thread answers that request with 503 `session_commit_stalled` after `MTPLX_STREAM_STALL_DEADLINE_S` (300 s). `MTPLX_STREAM_TERMINAL_FRAME_BEFORE_COMMIT=0` restores the old order.
|
|
71
|
+
|
|
72
|
+
- **The hidden-tool stream guard is opt-in** (`MTPLX_STREAM_HIDDEN_TOOL_GUARD=on`). It cancelled any tool call that buffered 2,048 tokens and 30 s outside a known parameter, which cut long valid tool calls.
|
|
73
|
+
|
|
74
|
+
- **Gemma 4 processes its prompt in chunks.** The whole-prompt pass grew with the square of the prompt: on the 31B pack, cold prompts of 6,026, 12,026 and 24,026 tokens peaked at 25.16, 43.02 and 92.08 GiB with 12.9, 41.4 and 173.5 s to the first token. Chunked, they peaked at 21.54, 25.48 and 31.1 GiB with 11.7, 20.3 and 45.1 s, and 33,026 tokens fit in 37.75 GiB (September 27). The output changes by rounding (at 4K, from the eleventh word); `MTPLX_GEMMA4_PREFILL_CHUNK_TOKENS=whole` restores the single pass, whose first turn matches 2.12.0. A saved Gemma prompt restores after an edit only within its last 2,047 tokens.
|
|
75
|
+
|
|
76
|
+
- **Faster kernels, each bit-identical to the code it replaces and checked on first use** (end-to-end speed against 2.12.0 was not measured). On M5 Macs, Flash-Next prompt processing reads each routed token row in place and fuses SiLU into the gate and up step (10.0 to 7.65 ms per layer at a 4,096-token chunk, kernel alone, `MTPLX_MOE_SORTED_GATHER_KERNEL=0` turns it off), and the same kernel serves Qwen 3.5 and 3.6 MoE models such as the 35B-A3B (1.19 to 1.24 times as fast for the expert module). Flash-Next's compiled verifier reads hyper-connections in three kernels instead of about eleven on single-die M3, M4 and M5 GPUs (`MTPLX_QWEN4_HC_VERIFY_READ=0`), selects attention blocks in two kernels (0.843 to 0.590 ms per verify step at 4K, 12 layers chained), builds the pooled index key row in one (1.380 to 0.759 ms at 4K), and on Optimized Speed reduces the expert slots in parallel (275.4 to 257.0 µs per layer). The draft head's history append during prompt processing evaluates only its cache on Qwen 3.5, 3.6 and 3.8 packs (#555 by @jvmenen, `MTPLX_MTP_HISTORY_CACHE_ONLY=0` turns it off). Sampled copy rounds read the GPU once per 8 rows (3 reads instead of 24 for a fully accepted 24-token block). Long Flash-Next conversations size the verifier in 8,192-token steps (`MTPLX_QWEN4_FIXED_M4_CAPACITY_BUCKET`), 95% fewer re-traces at 16K to 64K.
|
|
77
|
+
|
|
78
|
+
- **Agent turns tokenize only what is new** (#533, #541 and #542 by @jvmenen). Segments are memoized by tokenizer identity, its added tokens with their flags, and the text; plain chat is cut into turns only when the cut is proven on the tokenizer. Measured by @jvmenen on an M5 Pro with Qwen 3.6 35B-A3B: an 82K-token tool transcript encodes in 57 ms instead of 107 ms, a short follow-up to an 80K chat in 1.3 ms instead of 66 ms, and the Gemma 4 check takes about 15 µs instead of about 47 ms. `MTPLX_CHAT_SEGMENT_MEMO_TOKENS` sets the memo's token budget, and `MTPLX_CHAT_SEGMENT_MEMO=off` and `MTPLX_CHAT_TURN_SEGMENTS=off` turn them off.
|
|
79
|
+
|
|
80
|
+
- **Prompt scoring picks its top-K without a full-vocabulary log-softmax** (#532 by @jvmenen): bitwise the same values, 3.4 to 5.2 ms per chunk on the GPU instead of 47 ms (synthetic, M5 Pro).
|
|
81
|
+
|
|
82
|
+
- **Parallel tool calls use the tokenizer's native tool template** (#270 by @shiftedx) when a Pi or Hermes request sets `parallel_tool_calls: true`; omitted or false keeps the hybrid format. Native tool prompts keep the client's system instructions and honor a forced `tool_choice`.
|
|
83
|
+
|
|
84
|
+
- **Dependencies.** pip and Homebrew install exactly MLX 0.32.2, the version the app bundles (0.32.3's Flash-Next output differs and was not checked). llguidance 1.8 or later is required. transformers is allowed up to 5.16 (#538 by @dependabot), the releases checked against the packs' tokenizers.
|
|
85
|
+
|
|
86
|
+
- **`ttft_s` starts at the request's arrival**, so it includes the body read and JSON parse, and `usage.completion_tokens` describes the returned attempt.
|
|
87
|
+
|
|
88
|
+
### Removed
|
|
89
|
+
|
|
90
|
+
- **The stalled-agent retry.** It re-ran a whole turn when an answer after tool results ended with text its pattern read as a promise to act, such as `Say "A" or "B" and I'll run it.`, and threw away finished answers (82 s of them in one session). The read-only force-answer retry keeps the pattern, opt-in.
|
|
91
|
+
|
|
92
|
+
- **`MTPLX_PREFILL_ADMISSION_MIN_MISS_TOKENS`**, since every request is priced.
|
|
93
|
+
|
|
94
|
+
### Fixed
|
|
95
|
+
|
|
96
|
+
- **Flash-Next prompts on M5 Macs no longer read leftover memory in the expert step.** MLX 0.32.2's tensor-unit kernel for expert-sorted quantized matrix multiplies skipped rows when a call routed more than 32,767 rows and the count was not a multiple of its tile. Flash-Next sends each token to ten experts, so in 2.12.0, 794 of every 4,096 cold prompt lengths above 3,277 tokens (19.4%) took part of their expert outputs from uninitialized memory, and warm suffixes over 512 tokens by the same rule. Such calls are padded and sliced back, bit-identical to a correct call, for every MoE model on M5 GPUs with MLX before 0.32.3.
|
|
97
|
+
|
|
98
|
+
- **Structured output no longer loops** (#547). The schema was serialised with sorted keys before compiling, which forced alphabetical key order, and JSON whitespace was unbounded with carriage returns allowed; the reported case ended in two spaces and 640 carriage returns. The grammar keeps the client's key order and allows one space, or one or two newlines plus at most 20 spaces or tabs, never a carriage return. A client `x-guidance` that sets any whitespace option compiles exactly as sent. A replay of the reporter's schema on September 27: 16 of 16 valid and ordered, longest whitespace run 3.
|
|
99
|
+
|
|
100
|
+
- **Screenshots after tool calls work in streaming clients** (#581). A streaming request whose last user message carried an image after a tool call, the shape OpenCode sends after a screenshot tool, could fail with "vision requests must not use the session bank without content-keyed ids". With the reporter's script on Qwen 3.8 27B Optimized Speed, 3 of 5 streaming runs failed on 2.12.0 and 5 of 5 passed on 2.12.1 (October 2).
|
|
101
|
+
|
|
102
|
+
- **Constrained drafting follows the plain decoding distribution.** Every drafted verify row is masked at its own grammar position, so with top-k or top-p a constrained request no longer over-weights legal tokens, and greedy constrained output commits the same stream with more drafts accepted. The session cache keeps unmasked logits, so a later request on the same prefix is not bound by an earlier grammar.
|
|
103
|
+
|
|
104
|
+
- **4-bit and 8-bit KV cache no longer produce NaN** (#526). The compiled verifier's promoted paged cache was sized once (prompt plus 16,384 tokens) and never grew, so longer answers wrote across heads and past the buffers. Every forward now reserves its window where its mask is built, the dispatcher reserves before any replay, growth is checked against the Mac's memory and refused with a 507 before a row moves, and capacity is read from the live buffers. In our reproduction on the 27B, an answer counting to 4,000 failed on 2.12.0 at 8-bit KV and now finishes at 8-bit and 4-bit (no M3 run). Cost: 8-bit decode below the old capacity is about 3% slower (57.42 against 59.25 tok/s on the fix's own build, alternating boots, September 27).
|
|
105
|
+
|
|
106
|
+
- **FP16 queries keep attention's unnormalized partials in float32** in every split attention kernel, quantized or not, so a block of large scores can no longer overflow and turn the output into NaN. BF16 outputs are bit-identical to 2.12.0 (108 of 108 across 11 kernel families); FP16 partial buffers double (25 to 50 MB per 27B layer call at 32K). No M1 or M2 run.
|
|
107
|
+
|
|
108
|
+
- **"Failed to create Metal shared event" after long non-streaming runs** (#544). Unread lazy writes of Flash-Next's index blocks and draft-head history each kept a Metal event alive, and the non-stream path saved them in the session cache. The writes are evaluated with the forward that makes them, and the draft history is scheduled at every commit and at the end of a generation. Live events stayed at 6 to 13 against about 2,900 on 2.12.0 (September 27 A/B) and read 1 to 9 through three 4,096-token non-stream answers on a build of this release (October 2). A run past 140K generated tokens has not been repeated.
|
|
109
|
+
|
|
110
|
+
- **Host memory growth in long sessions is slower** (#546, partly fixed). A shared compiled verify program is replaced after `MTPLX_COMPILED_VERIFY_TRACES_PER_PROGRAM` traces (default 16), and every call binds to its own bank. Part of each replaced trace stays inside MLX, so the growth is slower and capped per program, not gone; a restart returns it.
|
|
111
|
+
|
|
112
|
+
- **Queued SSD saves can no longer hold memory the session cache already gave back** (#536 by @vahako). Under many short sessions memory climbed from 83.4 to 104.1 GB in four minutes on a 128 GB M5 Max until 507s (the author's measurement on 2.12.0). Saves whose entry the cache dropped count against `MTPLX_PERSISTENCE_MAX_PENDING_BYTES` (default 1/32 of RAM, 4 GiB on 128 GB, at least 512 MiB) and the oldest are cancelled; a save whose entry is still in RAM is never cancelled. Every memory-pressure eviction also cancels its own queued saves.
|
|
113
|
+
|
|
114
|
+
- **The turn after a compaction gets its memory.** A refused request frees its memory at once instead of keeping its frames alive (4.95 GB in one case); the SSD cache refuses a row that has no usable restore point from its description before reading its tensors (4.17 GB in one case) and tries the next candidates; a conversation's own unusable entries leave RAM before a refusal, and its restore source is protected, moved to SSD first, and released last when nothing can save it.
|
|
115
|
+
|
|
116
|
+
- **Cancel and resend keep the conversation.** A cancelled answer hands its cache back in one step; a prompt that stops part-way returns the conversation at its restore point; and a repeated prompt that restored a whole saved conversation no longer resumes one slot short, where its first answer token overwrote the last prompt token (also in 2.12.0).
|
|
117
|
+
|
|
118
|
+
- **Flash-Next's compiled verifier keeps running as a conversation outgrows its buffers.** The buffers are resized layer by layer and billed at the rows they gain; a resize that memory refuses answers on the eager verifier for that turn, and the next turn resizes the rest.
|
|
119
|
+
|
|
120
|
+
- **OpenCode answers are no longer capped on windows under 64K tokens.** OpenCode sends `min(limit.output, 32000)` with every request and 2.12.0's plugin stripped only 32,000, so the reply reserve became every answer's cap (16,384 on a 32,768 window, 10,240 on 20,480). Both plugins strip the reserve at every window, and `--max-response-tokens N` now reaches a running server whole, also above 32,000.
|
|
121
|
+
|
|
122
|
+
- **Pi's and OpenCode's configs are read as the clients read them** (comments, trailing commas, a byte order mark). The app moved a file it could not parse as strict JSON aside, which lost every other provider; a file that still does not parse is now left untouched and named.
|
|
123
|
+
|
|
124
|
+
- **`mtplx start` keeps the model you picked** (#573). A local folder picked in setup was swapped for this Mac's default repository on the next start, which started a 20.7 GB download of a model already on disk. Setup records where a pick came from; only the verified default follows when the default moves, now also Flash-Next on 256 GB Macs; a `config.toml` model folder loads as configured whatever its name; a missing folder is named instead of replaced.
|
|
125
|
+
|
|
126
|
+
- **The app's badge no longer sticks on Degraded** (#528). A second start against the app's own engine reconnects instead; Refresh and the live stats share one check; the badge separates the engine from the live stats ("Running · reconnecting live stats"). Closing the window during a load no longer kills it; Start or Restart after Stop launches; Stop during a preparing start launches nothing; requests go to the port the engine listens on; a failed configuration change leaves the engine running with its own notice; Stop signals only processes carrying this app's launch id; another server on the port is named and never stopped; the benchmark runs only on the app's own engine; requests use the API key the engine started with.
|
|
127
|
+
|
|
128
|
+
- **Retries.** A well-formed bare tool call is no longer retried as orphan markup unless its markup is incomplete (#539 by @jvmenen; on his Qwen 3.6 35B-A3B agent loop, long tool turns reached their first token in 10 to 20 s instead of 25 to 87 s; `MTPLX_TOOL_FED_RETRY_PARSED_CALL_GUARD=0` restores the old behaviour). A blank greedy answer is not regenerated, because `--blank-retry-attempts` now applies only when the sampler samples (#540 by @jvmenen; a one-token completion 1,061 to 265 ms). The reasoning repair continues the retry prompt and the stream stats count every pass (#552 by @jvmenen). `MTPLX_AGENT_REWRITES=off` also turns off the two steering retries (#553 by @jvmenen). The remaining re-generations build their prompt from the served token ids and stand down when no exact prompt exists.
|
|
129
|
+
|
|
130
|
+
- **Draft heads with fused MoE experts load their experts** (#574 by @jvmenen). Qwen 3.5 and 3.6 MoE heads stored as fused `gate_up_proj` and `down_proj` tensors were dropped at load and kept random experts; on his Forge pack of Qwen 3.6 35B-A3B, depth-2 acceptance went from 0.69 and 0.37 to 0.95 and 0.85, the same as numbered experts.
|
|
131
|
+
|
|
132
|
+
- **`--scheduler-mode ar_batch` keeps right-pad tokens out of shorter rows' recurrent state** on the Qwen 3.x hybrids (from #522 by @freddyhaddad). The default serial scheduler was not affected.
|
|
133
|
+
|
|
134
|
+
- **Streamed replies no longer start with a blank line** when the separator after the close tag arrives in a later chunk, which showed in Gemma 4 streams (#531 by @pierre427).
|
|
135
|
+
|
|
136
|
+
- **Gemma 4 renders its tool history in the format it is asked to use**, and history turns render the bytes the model generated (#571 by @jvmenen).
|
|
137
|
+
|
|
138
|
+
- **Saving a turn no longer refreshes the recency of the entry it restores from** (#554 by @jvmenen), so it cannot evict the entry the next request needs.
|
|
139
|
+
|
|
140
|
+
- **Flash-Next's compiled and eager verifiers agree at near ties.** The block-score divisor is read from memory instead of a 7-digit constant inside compiled code, and the eager verifier rounds four gates the way the compiled verifier does, on every Mac.
|
|
141
|
+
|
|
142
|
+
- **`/health` and `--kv-quant` help describe the KV quantization routes that actually run**, and the dashboard's value axes fit their labels (#572 by @jvmenen).
|
|
143
|
+
|
|
144
|
+
- **Tests no longer read the user's `~/.mtplx/config.toml` or depend on the Mac's memory size** (#535 by @jvmenen), and every client config the suite can write points at a scratch file.
|
|
145
|
+
|
|
146
|
+
## [2.12.0] - 2026-09-23
|
|
147
|
+
|
|
148
|
+
### Added
|
|
149
|
+
|
|
150
|
+
- **Dense 27B image requests take the compiled verify route too.** The route carries the image position offset as a graph input and leaves the text trace unchanged. Eighteen complete requests, nine with thinking on and nine with thinking off, all finished with `stop`, and 762 compiled image rounds and 511 text rounds compared against the eager verifier from the same cache showed zero differences in logits, hidden states, cache state or captures. A seeded 27B image reply run separately on each route agreed for its first 61 tokens, then parted at a rounding difference between the eager route's live-length attention buffers and the compiled route's padded buffers, with identical prefill logits, hidden states, draft inputs and rotary positions. Compiled text verification has always carried the same rounding difference; it is not a position error. `MTPLX_DENSE_VISION_COMPILED_VERIFY=0` keeps dense image requests on the eager verifier; the shared image switch `MTPLX_QWEN4_VISION_COMPILED_VERIFY=0` and the state-rebase refusal still take precedence.
|
|
151
|
+
|
|
152
|
+
- **Ternary Bonsai 2 27B** (issue #515). MTPLX loads Prism ML's 2-bit
|
|
153
|
+
ternary Bonsai 2 27B natively (`prism_hadamard_qwen35`: the Hadamard
|
|
154
|
+
rotation is applied at run time; a pack without its rotation metadata or
|
|
155
|
+
its vision tower is refused), with image input. The MTPLX pack
|
|
156
|
+
`Ternary-Bonsai-2-27B-MTPLX-Optimized-Speed` carries Prism ML's weights
|
|
157
|
+
byte for byte and adds the Qwen3.8-27B draft head, 8.85 GB in total;
|
|
158
|
+
served id `mtplx-bonsai-2-27b-optimized-speed`, minimum engine 2.12.0.
|
|
159
|
+
The draft head is on by default at depth 1. Two kernels written for the
|
|
160
|
+
pack make every round cheaper: the Hadamard rotation as one kernel (same
|
|
161
|
+
bits as the chain it replaces) and a ternary kernel for the verify and
|
|
162
|
+
decode rows (mean KL to Prism ML's float32 reference 2.92e-6 against
|
|
163
|
+
stock's 3.02e-6 over 1,630 positions; greedy output identical with the
|
|
164
|
+
kernels on and off). Verify runs compiled on Bonsai too (0 divergent
|
|
165
|
+
rounds in 613). On an M5 Max, alternating boots before and after the
|
|
166
|
+
kernels, 512 tokens: 50.4 -> 64.4 tok/s at 4K (+28%) and 45.2 -> 57.1 at
|
|
167
|
+
16K (+26%); the dense 27B measured 52.6 and 51.0 in the same session, so
|
|
168
|
+
Bonsai is now faster than the 4-bit 27B, in about half the memory.
|
|
169
|
+
`MTPLX_PRISM_FUSED_ROTATION=0` and `MTPLX_BONSAI_TERNARY_QMV=0` turn the
|
|
170
|
+
kernels off. Reasoning effort is `medium` by default in the app, the
|
|
171
|
+
CLI and every client; `xhigh` is available, and `low` is not offered
|
|
172
|
+
because Prism ML does not support it. On a stopwatch coding task `xhigh`
|
|
173
|
+
spent 577 s and 21,848 reasoning tokens without an answer, while
|
|
174
|
+
`medium` finished the task and a correction turn.
|
|
175
|
+
`scripts/build_bonsai_mtplx_pack.py` builds, stamps and restamps the pack
|
|
176
|
+
from measured evidence; `scripts/bonsai_memory_table.py` measures the
|
|
177
|
+
peak memory per RAM class.
|
|
178
|
+
|
|
179
|
+
- **MiMo V2.6 Qwen 9B Optimized Speed.** Xiaomi's MiMo-V2.6-Distill-Qwen-9B
|
|
180
|
+
(a Qwen3.5-9B fine-tune for coding and agents, MIT) joins the catalog as
|
|
181
|
+
`mimo-v26-qwen-9b-optimized-speed`: pack
|
|
182
|
+
`Youssofal/MiMo-V2.6-Qwen-9B-MTPLX-Optimized-Speed`, 8,695,116,595 bytes,
|
|
183
|
+
6-bit g64 body, Xiaomi's BF16 vision tower and the Qwen3.5-9B draft head
|
|
184
|
+
(the checkpoint ships none); served id
|
|
185
|
+
`mtplx-mimo-v26-qwen-9b-optimized-speed`, the Qwen 3.5 contract (sampler
|
|
186
|
+
0.6 / 0.95 / 20, depth 2 by default). KL 0.0054 and 97.3 percent top-1
|
|
187
|
+
agreement against Xiaomi's BF16 checkpoint over 19,265 tokens. The app and
|
|
188
|
+
the CLI list it right before Qwen 3.5 9B on M3, M4 and M5 Macs from 16 GB
|
|
189
|
+
(second after Bonsai 2 on 16 to 31 GB); name-only lookups resolve it to
|
|
190
|
+
`qwen3_5`, and the onboarding list has its own row.
|
|
191
|
+
|
|
192
|
+
- **Flash-Next Optimized-Quality recipe.** `flash-next-optimized-quality`
|
|
193
|
+
in Forge: the main model and the draft head at 8 bits, group size 64, the
|
|
194
|
+
structural weights in BF16, the n-gram table at 4 bits, group size 32 (the
|
|
195
|
+
fixed-width verifier rejects an 8-bit table). The streaming audit stamps
|
|
196
|
+
its own result into the pack and
|
|
197
|
+
`scripts/build_flash_next_quality_pack.sh` builds, verifies with a full
|
|
198
|
+
load and tests chat, a tool call and an image in one command. The
|
|
199
|
+
published pack is 169,958,537,520 bytes in 57 files, and its weights need
|
|
200
|
+
about 128.5 GiB with the n-gram table on SSD. It has not yet been run on a
|
|
201
|
+
256 GB Mac, so on 256 GB and 512 GB Macs it is listed second, after
|
|
202
|
+
Flash-Next Optimized Speed.
|
|
203
|
+
|
|
204
|
+
- **Compiled verify route for image requests on Flash-Next.** An image
|
|
205
|
+
request used to fall back to eager verification for the whole
|
|
206
|
+
conversation. The compiled verifier now owns a rotary origin per request:
|
|
207
|
+
the image position delta is an int32 graph input, the text trace is
|
|
208
|
+
unchanged, and requests with different images share one compiled
|
|
209
|
+
program. Six prompt shapes the route cannot represent exactly are refused
|
|
210
|
+
by name and stay eager; `MTPLX_QWEN4_VISION_COMPILED_VERIFY=0` turns the
|
|
211
|
+
route off; `MTPLX_COMPILED_VERIFY=parity2` compares every compiled round
|
|
212
|
+
against the eager forward and records per-leaf differences;
|
|
213
|
+
`scripts/qwen4_vision_compiled_parity.py` proves the route on a real pack
|
|
214
|
+
and refuses to say `exact` without a compiled dispatch and a warm restore
|
|
215
|
+
into generated tokens. On 440 image rounds and 429 text rounds of the
|
|
216
|
+
same requests, the compiled and eager routes differ only by rounding, with
|
|
217
|
+
a mean KL per round of 0.0015 for images and 0.0013 for text.
|
|
218
|
+
|
|
219
|
+
- **Packs can declare their generation mode.** `recommended_generation_mode`
|
|
220
|
+
(`mtp` or `ar`), with a reason and the measurement behind it, in the
|
|
221
|
+
runtime contract. `mtplx serve` applies it when the mode flag is omitted or
|
|
222
|
+
`auto`; an explicit `--generation-mode` wins; the head stays loaded so the
|
|
223
|
+
daemon can switch live. No shipped pack declares `ar`.
|
|
224
|
+
|
|
225
|
+
- **Use MTPLX settings for connected apps.** A switch in the inference
|
|
226
|
+
settings (`control_client_settings`, env `MTPLX_MANAGED_CLIENT_CONTROLS`)
|
|
227
|
+
that says whether MTPLX or the connected client controls the request
|
|
228
|
+
settings; on by default. Pi mirrors the live reasoning setting and
|
|
229
|
+
restores its own choice when the switch is off. Translated into all 13
|
|
230
|
+
app languages.
|
|
231
|
+
|
|
232
|
+
- **One catalog for the app and the CLI.** 25 entries with the same
|
|
233
|
+
identities, sizes and recommendation rule, a shared recommendation
|
|
234
|
+
fixture both sides are tested against, and model descriptions that follow
|
|
235
|
+
the app language without a restart. On M3, M4 and M5: the 4B below 16 GB,
|
|
236
|
+
Bonsai 2 from 16 GB (`BONSAI_RECOMMENDATION_MIN_GIB`), the Qwen 3.8 27B
|
|
237
|
+
Optimized Speed from 32 GB, and Flash-Next Optimized Speed first from
|
|
238
|
+
256 GB with Optimized Quality second. On a 96 GB Mac the Flash-Next
|
|
239
|
+
option is Bare Speed (peak 78 GiB), because Optimized Speed peaks at
|
|
240
|
+
87 GiB.
|
|
241
|
+
|
|
242
|
+
- **`forge build` converts Qwen3.8-Flash-Next sources** (PR #508, Bradford
|
|
243
|
+
Matthews, issue #390). Forge handed every Flash-Next fine-tune to the
|
|
244
|
+
pinned mlx-lm, which does not know the `qwen4_exp` architecture, and
|
|
245
|
+
stopped with `Model type qwen4_exp not supported`. A BF16 source of that
|
|
246
|
+
family now takes its own path. The 51B n-gram table is quantized one
|
|
247
|
+
shard at a time straight into `ngram-table.safetensors` and is never held
|
|
248
|
+
in memory. The trunk is converted with MTPLX's own model code under the
|
|
249
|
+
Optimized Speed recipe, one tensor at a time, because saving a whole
|
|
250
|
+
shard in one step ran long enough for macOS to stop the GPU work.
|
|
251
|
+
`module_overrides` still apply on top. The draft head is written in the
|
|
252
|
+
layout the runtime loads. New recipe keys: `ngram.bits` and
|
|
253
|
+
`ngram.group_size` (default 4 and 32, the layout the runtime expects),
|
|
254
|
+
`qwen4_mtp_bits` (default: the same as the trunk) and `qwen4_qsa_8bit`.
|
|
255
|
+
A build of this family is checked on the two rows its verify step
|
|
256
|
+
measures, plain decoding and depth 3, instead of failing on `required
|
|
257
|
+
depths: D1, D2`. The contributor built
|
|
258
|
+
`orcarouter/Qwen3.8-Flash-Next-Uncensored` end to end on an M5 Max with
|
|
259
|
+
128 GB.
|
|
260
|
+
- **Remove a downloaded model from the app** (PR #377, Philip John
|
|
261
|
+
Basile). The model picker has a Remove action with a confirmation. It
|
|
262
|
+
runs `mtplx remove` for exactly the entry shown, only inside the primary
|
|
263
|
+
model folder from Settings (additional model folders stay read-only), and
|
|
264
|
+
it refuses the model the server is currently serving. When the command
|
|
265
|
+
refuses, the app shows its reason instead of an exit code. The new
|
|
266
|
+
strings are in all thirteen languages.
|
|
267
|
+
|
|
268
|
+
- **`MTPLX_MLX_COMMAND_BUFFER_MB` sets MLX's command buffer limit.** MLX
|
|
269
|
+
closes a GPU command buffer once it has touched 50 MiB of buffers on a Max.
|
|
270
|
+
At 128K tokens on Flash-Next, lifting that limit took decode from 51 to 54
|
|
271
|
+
tok/s to 65.9 tok/s, but peak memory during prompt processing rose from
|
|
272
|
+
92.0 to 103.7 GB at 16K, and close to the memory limit the gain shrank to
|
|
273
|
+
45.6 against 49.4 tok/s. MTPLX keeps MLX's default. A value given to MLX
|
|
274
|
+
directly still wins, and `/health` shows the value the engine was given.
|
|
275
|
+
- **Measuring tools for the Flash-Next speed work, all off by default.**
|
|
276
|
+
`MTPLX_QWEN4_PREFILL_PROFILE=1` times each part of a prompt pass,
|
|
277
|
+
`MTPLX_QWEN4_EXPERT_OVERLAP_PROBE=1` counts the distinct experts of the
|
|
278
|
+
verify rows, `MTPLX_FIXED_M4_DONATION_PROBE=1` reports a verify step that
|
|
279
|
+
copies the attention cache instead of reusing it and can name every piece
|
|
280
|
+
of state that moved, and `MTPLX_EXPERIMENT_DEPTH_CEILING` raises the draft
|
|
281
|
+
depth limit for acceptance tests only. The compiled verifier can also
|
|
282
|
+
report how its host time splits between gathering rows, preparing inputs,
|
|
283
|
+
replaying the graph and encoding.
|
|
284
|
+
- **An experimental dense-band attention route for Flash-Next prompts.**
|
|
285
|
+
`MTPLX_QSA_DENSE_BAND_SDPA=1` applies the attention mask inside the score
|
|
286
|
+
matrix multiply below the sparse switch point, with bit-identical output
|
|
287
|
+
in tests and the stock call's peak memory plus one mask plane. It stays
|
|
288
|
+
off until a full-model check on real prompts confirms it.
|
|
289
|
+
|
|
290
|
+
### Changed
|
|
291
|
+
|
|
292
|
+
- **Flash-Next prefill runs five more steps as single GPU kernels.** The
|
|
293
|
+
hyper-connection read, the expert-output combine, the gated-delta prework
|
|
294
|
+
and the gated-delta norm gate run as one kernel each on every Mac; on M5
|
|
295
|
+
chips the wide projections also run as one dequantize plus a dense matrix
|
|
296
|
+
multiply (M1 to M4 keep the quantized multiply, where MLX's two paths are
|
|
297
|
+
not known to agree). Output is bit-identical to 2.12.0 without them: every
|
|
298
|
+
position's logits and 256 greedy tokens on code and prose at 16K and code
|
|
299
|
+
at 64K. On an M5 Max, alternating boots without and with the kernels:
|
|
300
|
+
1,459 -> 1,695 tok/s at 16K (+16%) and 1,415 -> 1,548 at 64K (+9%), same
|
|
301
|
+
peak memory. `MTPLX_QWEN4_HC_PREFILL_READ=0`,
|
|
302
|
+
`MTPLX_QWEN4_MOE_PREFILL_COMBINE=0`, `MTPLX_QWEN4_GDN_PREFILL_PREWORK=0`,
|
|
303
|
+
`MTPLX_QWEN4_GDN_GATED_NORM=0` and `MTPLX_QWEN4_PREFILL_DQ_GEMM=0` turn one
|
|
304
|
+
step off; `MTPLX_QWEN4_PREFILL_DQ_GEMM=1` forces the projection step on any
|
|
305
|
+
Mac. Every Flash-Next load checks the four kernels that run on every Mac
|
|
306
|
+
on its own GPU first (bit-exact against the stock chain at the family's
|
|
307
|
+
shapes and thread counts, about 58 ms); a kernel that fails is turned off
|
|
308
|
+
for the process, the stock path serves, and `/health` shows the lane.
|
|
309
|
+
|
|
310
|
+
- **Every Bonsai load checks its two kernels on the GPU it runs on.** The
|
|
311
|
+
fused Hadamard rotation must return the stock operations' exact bits and
|
|
312
|
+
the ternary kernel must match stock; a kernel that fails is turned off for
|
|
313
|
+
the process, the stock path serves, and `/health` shows the lane. Both are
|
|
314
|
+
plain SIMD code and run on M1 to M5.
|
|
315
|
+
|
|
316
|
+
- **`mtplx tune` pins the fans only when asked.** It pinned them at
|
|
317
|
+
maximum on every run, so Forge's verification did too, even without
|
|
318
|
+
`--max`. Tune now pins them only with its new `--max` flag or
|
|
319
|
+
`--require-max-fans`, and Forge passes them only when it is given
|
|
320
|
+
`--max`. The app's tuning and `mtplx start` still ask for maximum fans;
|
|
321
|
+
a bare `mtplx tune` leaves them on automatic.
|
|
322
|
+
|
|
323
|
+
- **Speed: Flash-Next prompt processing and long-context decode.** 2.11.3
|
|
324
|
+
against 2.12.0 on the same M5 Max and Python runtime, alternating boots,
|
|
325
|
+
fans at maximum, the GPU memory limit raised to 120 GiB
|
|
326
|
+
(`iogpu.wired_limit_mb=122880`; the default is about 96 GiB), seed 1731,
|
|
327
|
+
thinking off, 512-token answers: at 65,502
|
|
328
|
+
tokens, first token 85.5 to 60.1 s, prompt processing 768 to 1,094 tok/s
|
|
329
|
+
(+42%), decode 56.1 to 63.5 tok/s (+13%); at 4,061 tokens, first token
|
|
330
|
+
5.26 to 2.88 s, prompt processing 786 to 1,453 tok/s (+85%), decode 74.4
|
|
331
|
+
to 74.1 (tie). Saved attention buffers are sized on the cache's growth step and
|
|
332
|
+
written in place (at some prompt lengths all 24 key and value buffers
|
|
333
|
+
were copied every verify round, about 135 MB each at 128K; the 24 writes
|
|
334
|
+
went from 7.8 to 0.6 ms). Flash-Next builds all sampled draft depths with
|
|
335
|
+
one device sync and sends each depth to the GPU as it is built. Prompt
|
|
336
|
+
processing uses 4,096-token chunks on M5 chips and begins sparse
|
|
337
|
+
attention at 16K for those chunks; the sparse attention kernel reads the
|
|
338
|
+
cache in place (19.8 to 7.1 µs per row at 64K, bit-identical); block
|
|
339
|
+
selection runs on one simdgroup per row (2.68 to 0.58 µs per row at 18K);
|
|
340
|
+
the hyper-connection write is one fused kernel; block selection no
|
|
341
|
+
longer compiles a new GPU program per context size (40 to 56 ms each);
|
|
342
|
+
the last chunk of a cold prompt is no longer cut into 256-row forwards;
|
|
343
|
+
the n-gram pre-read fills the small scale and bias files first, and the
|
|
344
|
+
warm pass reads only the files that are not in memory. A warm agent turn
|
|
345
|
+
reprocesses at most 64 tokens at the end of its prompt instead of up to
|
|
346
|
+
256. Restore points are recorded inside the last wide forward instead of
|
|
347
|
+
ending extra forwards at each one: on the same build a cold 4,061-token
|
|
348
|
+
prompt prefills in 2.79 to 2.81 s instead of 3.06 to 3.10 s
|
|
349
|
+
(`MTPLX_GDN_BOUNDARY_INFORWARD=0` restores the old layout).
|
|
350
|
+
|
|
351
|
+
- **The Qwen 3.8 27B is unchanged.** 2.11.3 against 2.12.0 on the same Mac,
|
|
352
|
+
measured like the Flash-Next pairs: at 4,061 tokens, first token 6.07 s on
|
|
353
|
+
both, prompt processing 680 and 679 tok/s, decode 46.7 and 44.2 tok/s; at
|
|
354
|
+
16,350 tokens, first token 26.7 and 27.2 s, prompt processing 617 and 604
|
|
355
|
+
tok/s, decode 41.4 and 43.2 tok/s. Prompt processing and the first token
|
|
356
|
+
are within 2.1 percent, and one decode round costs 3.6 percent more at 4K
|
|
357
|
+
and 0.2 percent more at 16K. Decode moved 5.4 percent down at 4K and 4.3
|
|
358
|
+
percent up at 16K because the two builds accept different draft tokens
|
|
359
|
+
from the same seed.
|
|
360
|
+
|
|
361
|
+
- **The n-gram table streams from SSD on every Mac.** On Macs with 160 GB
|
|
362
|
+
or more earlier releases also loaded the 29.8 GiB table into GPU memory,
|
|
363
|
+
and nothing in this release reads that copy. It now streams from SSD on
|
|
364
|
+
every Mac and is never wired. On 256 GB and 512 GB Macs this raises
|
|
365
|
+
Flash-Next Optimized Quality's memory for context and the session cache
|
|
366
|
+
from 29.7 to 59.5 GiB and lowers its wired memory from 164.3 to
|
|
367
|
+
134.5 GiB.
|
|
368
|
+
|
|
369
|
+
- **A model that does not fit gets the floor window.** A plan whose verdict
|
|
370
|
+
was "does not fit" became "no limit", so the 27B picked by hand on a 16
|
|
371
|
+
or 24 GB Mac, and Flash-Next on 96 GB, were served the full 262,144-token
|
|
372
|
+
maximum under a MODEL DOES NOT FIT banner. They now get the 4,096-token
|
|
373
|
+
floor and one startup line that says so; an explicit `--context-window`
|
|
374
|
+
still wins.
|
|
375
|
+
|
|
376
|
+
- **The decode context ceiling follows the model's own KV size.** Every
|
|
377
|
+
model was budgeted with the 27B's 65,536 bytes per token (Flash-Next uses
|
|
378
|
+
24,576) and every Flash-Next start printed a warning about it. The value
|
|
379
|
+
now comes from the model's config; on 16 and 24 GB Macs the 27B ceiling
|
|
380
|
+
is 65,536 and 98,304 tokens instead of a flat 131,072.
|
|
381
|
+
|
|
382
|
+
- **One launch setup for the app, `mtplx start` and `mtplx serve`.** Pi runs
|
|
383
|
+
on the serial scheduler from every entry point (the OpenCode preset
|
|
384
|
+
measured it at 51.4 against 36.8 decode tok/s at 8K), the SSD cache size
|
|
385
|
+
is automatic everywhere (it was a fixed 100 GB on `mtplx start hermes`
|
|
386
|
+
and 32 GB on `mtplx start opencode`), the minimum saved prefix is 512
|
|
387
|
+
tokens everywhere, and the app's presets no longer fix the prompt chunk at
|
|
388
|
+
2,048 tokens for every model. A test builds the app's launch command for
|
|
389
|
+
294 combinations of client, model family and Mac size and compares it
|
|
390
|
+
with the engine's table.
|
|
391
|
+
|
|
392
|
+
- **The memory planner admits a model on a tight Mac when only the
|
|
393
|
+
session-bank floor is unfunded.** When the engine budget cannot fund the
|
|
394
|
+
weights, the 3 GiB runtime transient, the 1 GiB bank floor and one KV
|
|
395
|
+
block together, but can fund the weights, the transient plus a 256 MiB
|
|
396
|
+
margin and one block at the dense KV width, the plan admits the model
|
|
397
|
+
with the bank floor at zero (restores come from the SSD tier). The rule
|
|
398
|
+
applies to a pack whose weights are no larger than the Bonsai pack's
|
|
399
|
+
8,834,412,216 bytes, the pack the margin was measured on (Qwen 3.5 9B
|
|
400
|
+
Optimized Speed gets 20,480 tokens on 16 GiB), and to a larger pack only
|
|
401
|
+
when it stamps its own measured memory table for those weights
|
|
402
|
+
(`memory_evidence` in its runtime contract); any other pack is refused as
|
|
403
|
+
before. Measured on Bonsai 2 27B: the 16 GiB class admits 8,192 tokens
|
|
404
|
+
(peak 11.78 to 11.80 GiB under the 12 GiB budget). Every plan that funds
|
|
405
|
+
the floor is unchanged.
|
|
406
|
+
|
|
407
|
+
- **The SSD conversation-cache size follows RAM from the terminal too.**
|
|
408
|
+
`mtplx serve` defaulted to a flat 100 GB, so a 16 GB Mac got a 100 GB
|
|
409
|
+
store while the app scaled it. The default is now `auto` everywhere:
|
|
410
|
+
16 GiB on Macs with 16 GB of RAM or less, 24 GiB up to 32 GB, 32 GiB up
|
|
411
|
+
to 64 GB (100 GiB when the disk has at least 150 GiB free), and 100 GiB
|
|
412
|
+
above 64 GB. An explicit size still wins.
|
|
413
|
+
|
|
414
|
+
- **Flash-Next on a 96 GB Mac** has one 84 GiB engine budget, shared by
|
|
415
|
+
the catalog, the memory planner and the verify memory check: a planned
|
|
416
|
+
86,016-token window with sparse prompt processing, 20,480 without.
|
|
417
|
+
|
|
418
|
+
- **Request captures hold no content by default** (PR #356, Philip John
|
|
419
|
+
Basile). Capture is still off unless `MTPLX_REQUEST_CAPTURE_DIR` is set.
|
|
420
|
+
When it is on, a record now holds the sampler settings, the seed, token
|
|
421
|
+
counts and SHA-256 digests, and no prompt text, answer text, messages,
|
|
422
|
+
exception text or token IDs; keys that look like credentials are redacted
|
|
423
|
+
at any depth. Each kind of content has its own opt-in
|
|
424
|
+
(`MTPLX_REQUEST_CAPTURE_INCLUDE_PROMPT_TOKENS`,
|
|
425
|
+
`..._COMPLETION_TOKENS`, `..._PROMPT_TEXT`, `..._RESPONSE_TEXT`,
|
|
426
|
+
`..._MESSAGES`, `..._EXCEPTION_TEXT`). An opt-in to token IDs keeps the
|
|
427
|
+
whole sequence, because an exact replay needs every ID;
|
|
428
|
+
`MTPLX_REQUEST_CAPTURE_PROMPT_TOKEN_LIMIT` and
|
|
429
|
+
`MTPLX_REQUEST_CAPTURE_COMPLETION_TOKEN_LIMIT` bound them when wanted.
|
|
430
|
+
`mtplx trace` reads the request log and the flight recorder, not these
|
|
431
|
+
files, so it is unaffected. Records are now `capture_version` 2.
|
|
432
|
+
|
|
433
|
+
- **The draft head writes only its cache during Flash-Next prompt
|
|
434
|
+
processing.** Its pass over each chunk ran the whole layer although only
|
|
435
|
+
the cache it writes is used. It now writes the cache and returns, with the
|
|
436
|
+
same cache bits. The full pass cost 0.09 to 0.12 s per 4,096-token chunk,
|
|
437
|
+
3.3 s of a 104 s prompt at 128K. `MTPLX_QWEN4_MTP_HISTORY_CACHE_ONLY=0`
|
|
438
|
+
restores the full pass.
|
|
439
|
+
- **Less memory between Flash-Next prompt chunks.** A three-row copy of each
|
|
440
|
+
recurrent layer's state kept that layer's whole input stream alive until
|
|
441
|
+
the next chunk. The chunk's evaluation now includes those states, which
|
|
442
|
+
frees about 3.0 GB between 4,096-token chunks (85.84 GB instead of 88.86).
|
|
443
|
+
Prompt forwards of 1,024 rows or more also hand finished layers to the GPU
|
|
444
|
+
every four layers, so each layer's temporary data is freed with the layer.
|
|
445
|
+
`MTPLX_QWEN4_PREFILL_MIDLOOP_EVAL` sets the number of layers, and `0` turns
|
|
446
|
+
it off.
|
|
447
|
+
- **The wide prompt chunk's memory check counts what a chunk holds.** It
|
|
448
|
+
charged a flat 8 GiB per 4,096 rows on top of the prompt's KV, which
|
|
449
|
+
refused the wide chunk at 64K and 128K on a 128 GB Mac. It now charges the
|
|
450
|
+
larger of the last dense forward and the last forward of the prompt, and
|
|
451
|
+
those two refusals became grants. A refused wide chunk now shows up on
|
|
452
|
+
`/health`, in the request log and in `mtplx doctor --explain`, and the
|
|
453
|
+
request uses 2,048-token chunks. `MTPLX_QWEN4_PREFILL_WIDE_CHUNK=0` and
|
|
454
|
+
`MTPLX_QSA_PREFILL_WIDE_MIN_CONTEXT=0` turn the wide chunk off.
|
|
455
|
+
- **Model-tuned settings live in one block per model family,** each value
|
|
456
|
+
with its source and its measurement. This changed no behavior. One check
|
|
457
|
+
now decides whether a Mac's GPU has tensor units for every fast path, and
|
|
458
|
+
`MTPLX_FORCE_GPU_FAMILY_FALLBACK=1` now also covers Flash-Next prompt
|
|
459
|
+
processing, so an M5 can run MTPLX's M1 to M4 paths.
|
|
460
|
+
- **The server guide covers Open WebUI's Controls** (issue #513): leave them
|
|
461
|
+
on Default, so the settings MTPLX tuned for the model apply.
|
|
462
|
+
- **Release checks.** The release script refuses to publish while the notes
|
|
463
|
+
or README still carry draft wording, runs the model checks after the unit
|
|
464
|
+
suites finish, and fails when a catalog repository cannot be found. The
|
|
465
|
+
native package tests run in CI instead of being skipped. New scripts
|
|
466
|
+
compare the prompt the server builds for an agent session with the
|
|
467
|
+
model's own chat template token by token, and prove the compiled image
|
|
468
|
+
routes on the real Flash-Next and 27B packs with one command each.
|
|
469
|
+
|
|
470
|
+
### Fixed
|
|
471
|
+
|
|
472
|
+
- **Model list icons.** Every trailing icon in the model list draws in one
|
|
473
|
+
28-point slot on the trash can's axis (the checks sat 5 to 6 points to the
|
|
474
|
+
right), and the trash of a downloaded model appears only while the pointer
|
|
475
|
+
is at the right end of the row or the button has keyboard focus; the row's
|
|
476
|
+
context menu also offers Remove downloaded files.
|
|
477
|
+
|
|
478
|
+
- **The repetition stops are off by default.** In 2.11.3 and earlier the
|
|
479
|
+
server ended a reply when a block of up to 96 tokens repeated four times
|
|
480
|
+
in a row, deleting every copy. Code repeats exactly, so it cut a Tetris
|
|
481
|
+
board literal at its ninth identical row, a correct patch that renamed a
|
|
482
|
+
type at 15 identical call sites (the tool call never closed), an empty
|
|
483
|
+
sudoku grid, a 256-entry table of zeros and 64 identical board `<div>`
|
|
484
|
+
lines. A replay over 301,194 saved replies (104.6 million tokens from the
|
|
485
|
+
MTPLX app, OpenCode, Hermes, Pi and Codex) found two real patches it
|
|
486
|
+
would have cut. `MTPLX_REPETITION_STOP=1` turns it back on, together with
|
|
487
|
+
a new check for exact loops with a period of 97 to 8,192 tokens that ends
|
|
488
|
+
a loop at three whole copies, never before token 768, at 0.4 to 1.5
|
|
489
|
+
microseconds per step (a 4-bit 9B test build's 129-token loop stops at
|
|
490
|
+
5,202 tokens instead of 16,916, reason `long_cycle`). The new check also
|
|
491
|
+
cuts a platformer map with 8 identical empty rows, which is why it ships
|
|
492
|
+
off too. Evidence: `tests/test_repetition_stop_legitimate_code.py`.
|
|
493
|
+
|
|
494
|
+
- **A server started without `mtplx serve` uses each model's own
|
|
495
|
+
sampler.** `python -m mtplx.server.openai` sampled Qwen 3.8, Bonsai 2
|
|
496
|
+
and Flash-Next at temperature 0.6 while `/health` reported 1.0. With no
|
|
497
|
+
sampler flags it now uses 1.0, top-p 0.95 and top-k 20, as `mtplx serve`
|
|
498
|
+
and the app already did. Explicit flags win, and Qwen 3.5 and 3.6 stay
|
|
499
|
+
at 0.6.
|
|
500
|
+
|
|
501
|
+
- **Forge keeps a draft head's declared settings unless the evidence is
|
|
502
|
+
clear.** Calibration switched a grafted Qwen 3.5 9B head to `pre_norm`
|
|
503
|
+
on one prompt and six tokens in which every option accepted nothing. A
|
|
504
|
+
switch now needs at least 4 prompts, 48 draft rounds per option and a
|
|
505
|
+
lead of 0.25 accepted tokens per round, and an inconclusive calibration
|
|
506
|
+
is recorded in `mtplx_runtime.json`. The probe samples 8 prompts and 4
|
|
507
|
+
windows, 49 s on the Qwen 3.5 9B pack including the model load.
|
|
508
|
+
|
|
509
|
+
- **The memory planner never gives a lighter model less context.** On a 16
|
|
510
|
+
GB Mac a 7.53 GiB pack planned 12,288 tokens while an 8.08 GiB pack
|
|
511
|
+
planned 20,480. A lighter pack now drops the cache floor when that gives
|
|
512
|
+
it more context, up to what the tight-machine rule can grant. No catalog
|
|
513
|
+
model's window changes.
|
|
514
|
+
|
|
515
|
+
- **Eager BF16 verification uses the attention-gate rounding already used by compiled verification.** This affects the shared gated full-attention path in BF16 Qwen3-Next, Qwen 3.5, 3.6 and 3.8 models, including their MoE variants. It applies when verification runs eagerly: dense image requests, contexts beyond the compiled route's 32,768-token limit, buffer-growth fallbacks, warm-restore rounds before compilation, and eager copy, repair or final-save forwards within an otherwise compiled request. The compiled text verify graph, prompt processing and plain decoding keep their existing numerics. Float16 and float32 gates are unchanged, including Bonsai's float16 path. Gemma 4 and Flash-Next use different attention implementations and are unchanged by this fix. Five exact gate tests cover the BF16 contract and the unchanged dtypes and phases.
|
|
516
|
+
|
|
517
|
+
- **A quoted tool tag no longer ends the thinking.** A pasted traceback
|
|
518
|
+
containing `</parameter>` made the rest of the model's thinking appear in
|
|
519
|
+
the visible chat. A thinking block ends only at `</think>` or at the
|
|
520
|
+
opener of a real tool call (tools declared, line start, outside a code
|
|
521
|
+
fence, call-shaped; a bare `<function=NAME>` must name a declared tool).
|
|
522
|
+
The same rule decides whether a reasoning-only answer is retried, and a retry fed a tool
|
|
523
|
+
result starts its stream clean. 117 contract cases; 10 streamed requests
|
|
524
|
+
on the release build quoting up to 43 tags in one thinking block, none
|
|
525
|
+
leaked.
|
|
526
|
+
|
|
527
|
+
- **Gemma 4 streamed replies finish** (issue #517). A reply without tools
|
|
528
|
+
failed after generating with `'Gemma4ThinkingContentStreamSplitter'
|
|
529
|
+
object has no attribute 'suppressed_tool_markup_chars'`. The splitter
|
|
530
|
+
contract is stated once on the base class; 37 contract tests pass (12
|
|
531
|
+
failed before).
|
|
532
|
+
|
|
533
|
+
- **Stop, then resend, no longer processes the whole prompt again.** The
|
|
534
|
+
app rotated the conversation's server session id after every Stop, so
|
|
535
|
+
the resend was a new conversation to the daemon (26,294 tokens processed
|
|
536
|
+
again, 24.5 s to the first token in the report). The app keeps the
|
|
537
|
+
session across a Stop, and the daemon lets the follow-up wait, at most
|
|
538
|
+
`MTPLX_SESSION_CANCEL_HANDOFF_WAIT_S` (30 s), while the stopped generation
|
|
539
|
+
lets go of the session instead of answering 409; the request log records
|
|
540
|
+
`request_session_cancel_handoff`.
|
|
541
|
+
|
|
542
|
+
- **Cancelling an app start no longer corrupts the app's memory.** The
|
|
543
|
+
daemon supervisor's health probe and restart delay were asynchronous
|
|
544
|
+
closure literals in default arguments of a public initializer. Copies
|
|
545
|
+
emitted into different modules disagreed on the asynchronous frame size,
|
|
546
|
+
and the closure wrote past the end of its frame. Debug builds crashed
|
|
547
|
+
with "freed pointer was not the last allocation" in 3 of 3 runs; the
|
|
548
|
+
release build did not crash in a manual check, but the fault was in every
|
|
549
|
+
build. The defaults are named static functions. A start cancelled with
|
|
550
|
+
Stop writes no failed-start report.
|
|
551
|
+
|
|
552
|
+
- **Flash-Next image positions are consistent through the whole reply**
|
|
553
|
+
(since 2.10.1 only the main verification forward ran inside the image
|
|
554
|
+
position scope; the copy-round verification, the repairs and the final
|
|
555
|
+
save used plain text positions, about 990 positions off after a
|
|
556
|
+
1,024-token image). One helper carries the positions to all eight trunk
|
|
557
|
+
forward sites. Saved image conversations carry a new cache key
|
|
558
|
+
(`qwen4_mrope_v2`), so an existing image conversation is processed again
|
|
559
|
+
from its first image once; text before it is still reused.
|
|
560
|
+
|
|
561
|
+
- **Dense Qwen 3.5 and 3.8 27B models, and Bonsai, rope image tokens at
|
|
562
|
+
their grid positions**, and an image turn restores the session's text
|
|
563
|
+
history instead of processing it again (a Pi conversation reprocessed
|
|
564
|
+
154,899 tokens, 184 s, after an image was attached).
|
|
565
|
+
|
|
566
|
+
- **The in-flight memory guard reads what the kernel can still hand out**
|
|
567
|
+
(issue #516: a kernel watchdog restart on a 128 GB M5 Max with the worker
|
|
568
|
+
at 118 GiB resident and `allow_swap` on). `kern.memorystatus_level` and
|
|
569
|
+
the process footprint feed the guard; abort floor 2.5 percent of RAM
|
|
570
|
+
(3.2 GiB on 128 GB, never under 1 GiB), shed floor twice that, checks
|
|
571
|
+
every 2 s under the shed floor so the sustained-pressure abort fires
|
|
572
|
+
about 6 s after the floor is crossed. `allow_swap` keeps admitting past
|
|
573
|
+
the fit; it does not switch the guard off. Kill switch
|
|
574
|
+
`MTPLX_SYSTEM_MEMORY_GUARD=0`.
|
|
575
|
+
|
|
576
|
+
- **A Swift parity test read a 16 KB pipe before waiting on its child**,
|
|
577
|
+
so the suite could hang for five minutes on a long CLI dry run.
|
|
578
|
+
|
|
579
|
+
- **Saving a long session to SSD no longer holds up the next request**
|
|
580
|
+
(issue #505, reported by peterloron). On a 64 GB Mac with the 27B at about
|
|
581
|
+
104K tokens, the background job that writes a session to the SSD cache
|
|
582
|
+
held the model for more than 500 s while a request waited behind it, and
|
|
583
|
+
the stream watchdog ended that request at 300 s. The job was meant to give
|
|
584
|
+
way to a waiting request, but it only looked between pieces of work whose
|
|
585
|
+
size nothing limited. Each piece is now at most 32 MiB and the job looks
|
|
586
|
+
after every one, including while it reads a restored session's boundary
|
|
587
|
+
records. The files on disk are byte for byte the same. `/health` reports
|
|
588
|
+
`encode_units`, `encode_slow_units` and `encode_longest_unit_s` for the SSD
|
|
589
|
+
cache, and any piece slower than 1 s is logged with what it was.
|
|
590
|
+
- **`mtplx remove` acts on the entry you named.** A folder in the model
|
|
591
|
+
cache whose name spells one of the public aliases resolved to the
|
|
592
|
+
first-party model's folder instead, so `mtplx models` could print a
|
|
593
|
+
delete command for one entry that would have removed another. An exact
|
|
594
|
+
folder name now wins. When two copies of a model sit in two model
|
|
595
|
+
folders, the refusal says to choose one with `--cache-dir`; a typed
|
|
596
|
+
`--cache-dir` now does that even when the other folder comes from
|
|
597
|
+
`MTPLX_MODEL_DIRS` or the config file.
|
|
598
|
+
- **A stuck request no longer holds the fans at maximum** (PR #295,
|
|
599
|
+
El-Patronum). In Smart fan mode a request holds the fans up until it
|
|
600
|
+
ends, and a safety net restores them when the server has been idle for
|
|
601
|
+
120 s with a request still holding them. A request that was stuck still
|
|
602
|
+
counted as activity, so the safety net never ran: one report had both fans
|
|
603
|
+
at maximum for about fifteen hours behind a request whose client had
|
|
604
|
+
already gone. A request now counts as activity only while the model is
|
|
605
|
+
making progress. After 180 s without any (`MTPLX_FOREGROUND_STALL_DEADLINE_S`,
|
|
606
|
+
`0` switches the check off) it stops counting, and the fans return to
|
|
607
|
+
automatic 120 s later. Nothing is cancelled. The batched serving paths now
|
|
608
|
+
report progress during prefill as well, which also stops the stream
|
|
609
|
+
watchdog from failing a healthy batched prefill that runs longer than its
|
|
610
|
+
300 s deadline.
|
|
611
|
+
- **Forge builds a model that ships as one weights file** (issue #492). A
|
|
612
|
+
model small enough for a single `model.safetensors` has no
|
|
613
|
+
`model.safetensors.index.json`. `mtplx inspect` read the file's header
|
|
614
|
+
and reported the draft head as present, but Forge looked for the head
|
|
615
|
+
through the index only, so it converted the model, wrote no
|
|
616
|
+
`mtp.safetensors`, and stopped at calibration with `return_hidden
|
|
617
|
+
requires an MTP-patched runtime`. Forge now reads the same headers
|
|
618
|
+
`inspect` does.
|
|
619
|
+
- **`mtplx doctor` shows why the app could not start** (issue #504). When
|
|
620
|
+
the app's server stops before it is ready, the app showed one line and
|
|
621
|
+
cut it off, and nothing was written to disk. The app now writes the
|
|
622
|
+
server's last output to `~/.mtplx/logs/last-failed-start.log` (the launch
|
|
623
|
+
line has its secrets masked), and `mtplx doctor` includes the last 60
|
|
624
|
+
lines as `app.last_failed_start` when the file is less than 14 days old.
|
|
625
|
+
- **A download blocked by a company proxy now says what to do** (issue
|
|
626
|
+
#495). Behind a proxy that inspects HTTPS, model downloads stopped at
|
|
627
|
+
setup step 6 of 7 with `CERTIFICATE_VERIFY_FAILED` while `curl` worked,
|
|
628
|
+
because the proxy's root certificate is in the macOS keychain and Python
|
|
629
|
+
verifies against its own bundle. The error now explains that and names
|
|
630
|
+
both fixes (`SSL_CERT_FILE`, or the `truststore` package). When
|
|
631
|
+
`truststore` is installed, downloads verify through the keychain
|
|
632
|
+
automatically; MTPLX does not depend on it, and `MTPLX_SYSTEM_TRUST=0`
|
|
633
|
+
switches that off.
|
|
634
|
+
- **The memory guards read the process's real footprint from macOS**
|
|
635
|
+
(PR #500, Maikel Vos). Every guard compared MLX's own account of its
|
|
636
|
+
allocations with the Metal limit. `mtplx/os_memory.py` now reads
|
|
637
|
+
`phys_footprint`, the counter the system's own memory-pressure logic
|
|
638
|
+
uses, and the guard before a long prompt, its refusal check and the
|
|
639
|
+
background pressure loop add the part of it MLX's account does not
|
|
640
|
+
explain. Only the part beyond what a daemon normally holds outside Metal
|
|
641
|
+
is added (the larger of 8 GiB and what the machine leaves after the
|
|
642
|
+
system reserve and the Metal limit), because the Metal limit is not the
|
|
643
|
+
process's budget: comparing the whole footprint with it reads a full
|
|
644
|
+
session on a 48 GB Mac as critical. `MTPLX_HOST_MEMORY_ALLOWANCE_BYTES`
|
|
645
|
+
sets that allowance, and `0` is the strict comparison. The guard's log
|
|
646
|
+
line and the `mem` block on `/health` now carry `phys_footprint_bytes`
|
|
647
|
+
and `host_overhang_bytes`, so a memory report names its holder.
|
|
648
|
+
- **A session over its cache budget continues instead of being read again**
|
|
649
|
+
(issue #499). On a 48 GB Mac one conversation may hold about 7.4 GB of
|
|
650
|
+
warm cache, and a 142,000-token session needs 11 GB, so the cache keeps a
|
|
651
|
+
reference to the live state instead of a copy. With speculative decoding
|
|
652
|
+
on, that entry dropped the draft head's history it was handed, so the
|
|
653
|
+
next turn's restore failed (`no_snapshot_coverage`), the copy on disk had
|
|
654
|
+
been written without the history and was refused
|
|
655
|
+
(`ssd_missing_mtp_history`), and the whole prompt was read again: 570 s
|
|
656
|
+
to the first token at 150,000 tokens in the report, on every turn. The
|
|
657
|
+
entry now keeps the history (about 4 KB per token, next to 64 KB per
|
|
658
|
+
token of main state) in memory and in its SSD copy, so the next turn
|
|
659
|
+
continues and a restart restores from disk. Copies written by older
|
|
660
|
+
versions lack the history; each long session reads its prompt once more
|
|
661
|
+
after the update.
|
|
662
|
+
- **Long sessions no longer leak one whole KV cache per turn** (issue #456,
|
|
663
|
+
diagnosed by peterloron; also the memory growth in #499 and #438). When a
|
|
664
|
+
session's snapshot is over the per-session limit, the session cache keeps
|
|
665
|
+
a reference to the live cache instead of a copy. That entry recorded 0
|
|
666
|
+
bytes, so every memory check missed it: the check before a long prompt
|
|
667
|
+
logged `bank_bytes_before=0` and answered 507 with memory it could have
|
|
668
|
+
freed, the pressure routine never started, and the automatic cache limit
|
|
669
|
+
mistook the reference for the running request and evicted useful
|
|
670
|
+
snapshots instead. The entries were also skipped by the rules that retire
|
|
671
|
+
an older copy of the same conversation, so a turn that could not reuse
|
|
672
|
+
the reference built a new cache and left the old one allocated (measured
|
|
673
|
+
by peterloron on a 64 GB M4 Max: +3.7 GiB per turn). These entries now
|
|
674
|
+
report what they hold, a session keeps one at most, eviction and
|
|
675
|
+
`/admin/cache/clear` release the cache they point at, and the check
|
|
676
|
+
before a long prompt can release one that belongs to another
|
|
677
|
+
conversation. `/health` shows `lease_entries`, `lease_nbytes` and a
|
|
678
|
+
per-entry `held_nbytes`. A very long session on a small Mac can now push
|
|
679
|
+
its own older snapshots out of memory (they stay on the SSD cache),
|
|
680
|
+
because the cache finally stays inside its limit.
|
|
681
|
+
- **Dashboard draft totals fall back to the per-depth counts** (PR #490,
|
|
682
|
+
Wu Shuwen; issue #401). The "accepted of drafted" line and the drafted
|
|
683
|
+
per verify call tile total `accepted_by_depth` and `drafted_by_depth`
|
|
684
|
+
when a payload carries no flat totals. The server has sent the flat
|
|
685
|
+
totals since 2.11.3, and an emitted total still wins.
|
|
686
|
+
- **Plain decoding has one path (PR #507, David Tai).** David Tai found
|
|
687
|
+
that the pipelined AR path drew the first output token from the
|
|
688
|
+
request's NumPy generator and every later token from a separate
|
|
689
|
+
`mx.random` stream, so one seed gave a different continuation depending
|
|
690
|
+
on whether the path engaged, and his change made the two agree. After
|
|
691
|
+
this release's n-gram change no model offers the pipelined mode, so the
|
|
692
|
+
path and its `MTPLX_AR_PIPELINE` switch are removed.
|
|
693
|
+
- **A draft head taken from a raw checkpoint no longer drafts backwards**
|
|
694
|
+
(PR #511, Stuart Rowlands). Hugging Face checkpoints store the MTP head's
|
|
695
|
+
RMSNorm gains zero-centred. mlx-lm restores the +1.0 convention on the
|
|
696
|
+
trunk but drops every `mtp.*` key first, and only Forge restored it on the
|
|
697
|
+
head. A sidecar that reached the Qwen 3.5 / 3.8 loader any other way
|
|
698
|
+
(copied in by hand, or extracted from the base checkpoint) bound without
|
|
699
|
+
an error and then ran at about 0% acceptance, slower than plain decoding:
|
|
700
|
+
the negative gain inverts the head's output, so the right token lands
|
|
701
|
+
near the bottom of the distribution (measured median rank 247,513 of
|
|
702
|
+
248,320). The loader now applies the same detector-gated restoration
|
|
703
|
+
Forge uses. A head already in the absolute convention passes through
|
|
704
|
+
byte for byte, so nothing is shifted twice.
|
|
705
|
+
- **The GPU wired limit is clamped to what the Mac allows.** When the
|
|
706
|
+
memory the engine wanted wired was above the limit macOS allows, MLX
|
|
707
|
+
refused the request, the error was dropped, and the model ran with none
|
|
708
|
+
of its memory wired and no GPU keepalive: extra time after every idle
|
|
709
|
+
pause, and memory macOS could evict under pressure. The wired limit is
|
|
710
|
+
now set to the largest value the Mac accepts, and the startup log says
|
|
711
|
+
how much macOS allowed and gives the `sudo sysctl iogpu.wired_limit_mb`
|
|
712
|
+
command that wires all of it. On a stock 96 GB Mac that limit is about
|
|
713
|
+
72 GiB.
|
|
714
|
+
- **Image placeholder text stays text** (PR #519, François-David Collin).
|
|
715
|
+
A client that sent the image placeholder string back as plain text made
|
|
716
|
+
the chat template render it as a real image slot, and the request was
|
|
717
|
+
refused with `prompt contains more image placeholders than images`. Only
|
|
718
|
+
real image parts create image slots now; placeholder text is kept as
|
|
719
|
+
readable, escaped text in message content, in reasoning and in tool-call
|
|
720
|
+
arguments, so an agent that edits code containing a placeholder keeps
|
|
721
|
+
its image turns.
|
|
722
|
+
- **The two new official packs no longer read "needs contract repair".**
|
|
723
|
+
`mtplx inspect` and `mtplx serve` told users to rebuild Bonsai 2 and
|
|
724
|
+
Flash-Next Optimized Quality with Forge, because each pack's exactness
|
|
725
|
+
record is still open, and `mtplx inspect` exited with code 3. They now show
|
|
726
|
+
"Official MTPLX pack, qualification pending" while the exactness
|
|
727
|
+
measurement is still to be published, the pack runs, and `mtplx inspect`
|
|
728
|
+
exits with 0. Bonsai 2's measurement is published (mean KL 4.0e-6 against
|
|
729
|
+
Prism ML's runtime in float16, 1.1e-7 with float32 auxiliary tensors), so
|
|
730
|
+
it shows as verified.
|
|
731
|
+
- **A pack that needs a newer MTPLX is refused before any weight is read.**
|
|
732
|
+
When a pack's `min_engine_version` is newer than the running engine,
|
|
733
|
+
`mtplx serve` and `mtplx inspect` stop with "This model needs MTPLX X or
|
|
734
|
+
later (you have 2.12.0). Update MTPLX, then try again." No unsafe flag
|
|
735
|
+
overrides it.
|
|
736
|
+
- **The free-disk check before a download matches the download.** The app
|
|
737
|
+
asked for 2.5 times a model's download size, about 396 GiB for Flash-Next
|
|
738
|
+
Optimized Quality. The app and the CLI catalog now ask for the bytes still
|
|
739
|
+
to download plus 5 GiB, the rule `mtplx pull` already used.
|
|
740
|
+
- **A made-up tool call in a reply without tools is hidden completely.**
|
|
741
|
+
The stream filter closed its hidden span at the first `</function>`, so
|
|
742
|
+
the block's own `</tool_call>` reached the user as visible text. It now
|
|
743
|
+
waits for the closing tag that matches the opening one, as the
|
|
744
|
+
non-streamed path does.
|
|
745
|
+
- **Thinking-off tool sessions keep the template's own tokens.** The prompt
|
|
746
|
+
cut the empty thinking block in two, so from the second request of a
|
|
747
|
+
tool session the history no longer matched the saved state. Across 2,020
|
|
748
|
+
audited conversation turns the unexplained differences from the chat
|
|
749
|
+
template fell from 848 to 0, and the predicted reusable history matched
|
|
750
|
+
the next request in 834 of 834 cases, up from 498. For OpenCode with
|
|
751
|
+
thinking on, turns that extend the saved stream went from 28 of 36 to 36
|
|
752
|
+
of 36.
|
|
753
|
+
- **The open inference menu no longer loads the CPU.** Live metrics redrew
|
|
754
|
+
the whole app scene. With the menu open during inference the app's CPU
|
|
755
|
+
use went from 47.9 and 27.4 percent to 2.2 and 3.3 percent.
|
|
756
|
+
- **Activity and Cache show every saved prefix.** Several prefixes saved
|
|
757
|
+
from one conversation shared one row identity, which hid entries the
|
|
758
|
+
engine still held. Rows are now keyed by the session and the token hash.
|
|
759
|
+
- **Each model folder keeps its own name and row in the model list.** The
|
|
760
|
+
list named a local folder after the first of `public_model_id`,
|
|
761
|
+
`served_model_id`, `model_id` and `forge_provenance.source_repo`, and
|
|
762
|
+
every build of a source records that source. Two builds of one source,
|
|
763
|
+
such as a 4-bit and a 6-bit, shared one row named after the source,
|
|
764
|
+
which launched whichever build was found first, and a downloaded
|
|
765
|
+
official pack that records its base model, such as Qwen 3.8 27B
|
|
766
|
+
Optimized Speed, was listed again under that base model. A folder is
|
|
767
|
+
now named by `public_model_id` or `served_model_id`, else by its folder
|
|
768
|
+
name, and keeps its own row; the same folder name under two model
|
|
769
|
+
folders still shares one row, first folder first.
|
|
770
|
+
- **The inference panel no longer pins a 2,048-token prompt chunk.** The
|
|
771
|
+
panel showed 2,048 when nothing was stored and sent it with every live
|
|
772
|
+
settings change, which the app saved, so later launches passed
|
|
773
|
+
`--prefill-chunk-tokens 2048` and skipped Flash-Next's 4,096-token chunk
|
|
774
|
+
on M5. `GET /v1/mtplx/settings` now reports an unpinned chunk as null with
|
|
775
|
+
a read-only `prefill_chunk_tokens_default`, `POST` accepts 0 or `auto` to
|
|
776
|
+
unpin, the panel has an Auto option, and a saved 2,048 yields to the
|
|
777
|
+
engine once.
|
|
778
|
+
- **Setup uses the runtime and model defaults it selected.** An app runtime
|
|
779
|
+
started from another MTPLX folder could import that folder's package
|
|
780
|
+
first and report the wrong version; the runtime wrapper now runs Python
|
|
781
|
+
in safe-path mode. When tuning is skipped, the installed pack's own depth
|
|
782
|
+
applies, so Bonsai starts at depth 1 instead of 2.
|
|
783
|
+
- **The bundled native kernels load on a fresh Mac.** The app's native
|
|
784
|
+
wheel finds its libraries inside the installed package instead of the
|
|
785
|
+
build machine's paths, and it is built against MLX's macOS 15 release so
|
|
786
|
+
it loads on macOS 15 and later. The source distribution no longer
|
|
787
|
+
includes local profiling scripts.
|
|
788
|
+
- **If Flash-Next's compiled verifier cannot run on a GPU, the request
|
|
789
|
+
continues.** Its first dispatch runs under a guard; on a GPU that refuses
|
|
790
|
+
the kernel, the path is retired with one log line and the same round runs
|
|
791
|
+
eagerly, with unchanged output.
|
|
792
|
+
- **Flash-Next's sparse prefill kernel for M1 to M4 checks its numbers
|
|
793
|
+
before it is used.** The app builds the kernel against MLX's macOS 15
|
|
794
|
+
release and runs MLX's macOS 26 release, which the version check cannot
|
|
795
|
+
tell apart. Before its first use in a process the kernel runs one small
|
|
796
|
+
call on random inputs against the pure MLX path and is used only when the
|
|
797
|
+
largest difference is within 0.005; otherwise prefill runs on the pure MLX
|
|
798
|
+
path and `/health` gives the reason.
|
|
799
|
+
- **The app's setup error on macOS 26.0 and 26.1 names the cause.** MLX
|
|
800
|
+
0.32.2 for macOS 26 needs macOS 26.2 or later, so the engine's import
|
|
801
|
+
check fails on 26.0 and 26.1 whatever the network does. Setup asked users
|
|
802
|
+
to check their network access to PyPI; it now says the engine needs macOS
|
|
803
|
+
26.2 or later and asks them to update macOS.
|
|
804
|
+
- **Performance reports explain a slow response.** `/health`, the request
|
|
805
|
+
log and `mtplx doctor --explain` count every fallback from the fast path
|
|
806
|
+
with its reason, and Pi traces include compaction requests and the image
|
|
807
|
+
context.
|
|
808
|
+
- **A Qwen model with MiMo in its name keeps the Qwen settings.** The MiMo
|
|
809
|
+
family was matched on the folder name, so Xiaomi's Qwen-architecture
|
|
810
|
+
distills named MiMo (MiMo-V2.6-Distill-Qwen-9B) were served with the MiMo
|
|
811
|
+
settings, draft depth 1 only and reasoning off. The family now comes from
|
|
812
|
+
the checkpoint's own model type and architecture, so such a distill keeps
|
|
813
|
+
the Qwen 3.5 settings.
|
|
814
|
+
- **The packed-GQA verify counter counts accepted windows too** (issue
|
|
815
|
+
#506). It counted only declined windows, and only while the compiled
|
|
816
|
+
verifier was being traced, so a healthy daemon showed 96 declines and no
|
|
817
|
+
accepts.
|
|
818
|
+
- **Quality packs built with Forge carry the Flash-Next sampler settings.**
|
|
819
|
+
A pack built with the `flash-next-optimized-quality` recipe now records
|
|
820
|
+
temperature 1.0, top-p 0.95 and top-k 20 in its `mtplx_runtime.json`, as
|
|
821
|
+
the two Flash-Next Speed packs do.
|
|
822
|
+
- **The `--reasoning-effort` help lists each model's levels and default.**
|
|
823
|
+
Qwen 3.8 27B offers `xhigh`, `medium` and `low`, and Bonsai 2 offers
|
|
824
|
+
`xhigh` and `medium`; both default to `medium`. Flash-Next defaults to
|
|
825
|
+
`xhigh` in chat and to `medium` in coding-agent configurations.
|
|
826
|
+
- **`mtplx inspect` reports whether a pack takes images,** and a pack that
|
|
827
|
+
keeps its vision tower in one weight file with no index, like Prism ML's,
|
|
828
|
+
is served with vision.
|
|
829
|
+
- **`/health` no longer swallows a cancellation or an interrupt** while it
|
|
830
|
+
reads its optional counters.
|
|
831
|
+
|
|
832
|
+
## [2.11.3] - 2026-09-17
|
|
833
|
+
|
|
834
|
+
### Added
|
|
835
|
+
|
|
836
|
+
- **Ordered model library directories** (PR #387, Philip John Basile;
|
|
837
|
+
issue #388). One primary model folder (downloads and Forge output go
|
|
838
|
+
there) plus an ordered list of additional read-only folders that the app
|
|
839
|
+
and the CLI both search: the first complete copy wins, duplicates are
|
|
840
|
+
collapsed by real path, and an unplugged drive is skipped instead of
|
|
841
|
+
crashing. The app has a Settings card for the folders (localized in all
|
|
842
|
+
thirteen languages); the CLI takes `--model-search-dir` (repeatable) or
|
|
843
|
+
`model_dirs` in `~/.mtplx/config.toml`, and `MTPLX_MODEL_DIRS`;
|
|
844
|
+
`mtplx doctor` lists the roots. A download or Forge build into a folder
|
|
845
|
+
whose volume is not mounted now says so instead of `Permission denied`.
|
|
846
|
+
|
|
847
|
+
- **`mtplx gc` reclaims orphaned SSD session-cache files** (PR #502,
|
|
848
|
+
ArctifoxNL; issue #493). `mtplx gc` reports the live entries, the bytes on
|
|
849
|
+
disk and everything in `~/.mtplx/session-bank/` that the manifest no
|
|
850
|
+
longer reaches; `--apply` deletes it, `--force` allows that beside a
|
|
851
|
+
running daemon, `--json` is machine-readable and `--dir` points it at
|
|
852
|
+
another store. It needs neither MLX nor a model. The walk it runs is the
|
|
853
|
+
same one the daemon uses (`mtplx/cache_bank/reconcile.py`), so the command
|
|
854
|
+
and the engine cannot disagree about what is garbage.
|
|
855
|
+
- **Idle limit for the warm session cache** (issue #481). Nothing in the
|
|
856
|
+
daemon dropped warm state on a five- or ten-minute clock (the only idle
|
|
857
|
+
timer is one hour; the ten-minute window orders eviction victims under
|
|
858
|
+
pressure), but the limit was not adjustable and none of the cache
|
|
859
|
+
variables were documented. `MTPLX_SESSION_BANK_IDLE_TTL_S` now sets the
|
|
860
|
+
idle limit (`0` keeps entries until memory needs them), and the five
|
|
861
|
+
`MTPLX_SESSION_BANK_*` variables are documented together in
|
|
862
|
+
`docs/server.md`. `/health` and the dashboard stream report
|
|
863
|
+
`idle_ttl_s` as `null` when the sweep is off; the stream used to write
|
|
864
|
+
a bare `Infinity`, which a browser's JSON parser rejects, so every live
|
|
865
|
+
dashboard snapshot was unparseable with the knob at `0`.
|
|
866
|
+
- **`mtplx doctor` names the runtime that answers** (issue #479). A new
|
|
867
|
+
`runtime.identity` check reports the MTPLX version and path the doctor
|
|
868
|
+
imported, the `mtplx` first on PATH and whether it runs the same
|
|
869
|
+
interpreter (an installer shim or Homebrew venv ahead of a source
|
|
870
|
+
checkout is a warning with `which -a mtplx` as the fix), and the GPU
|
|
871
|
+
architecture with whether the M5 tensor-unit route is available. The
|
|
872
|
+
issue's kernel-build failure on macOS 15 is unreachable on 2.11.2 code;
|
|
873
|
+
the check answers which MTPLX actually ran.
|
|
874
|
+
- **Responses API** (PR #219, Philip John Basile). `POST /v1/responses` is served as
|
|
875
|
+
a stateless, text-only adapter over the chat runtime, covering
|
|
876
|
+
client-executed function, custom and namespace tools, with SDK-backed
|
|
877
|
+
tests. `docs/api.md` carries the Codex CLI settings that make its first
|
|
878
|
+
request work (hosted web search off, image generation off, a dedicated
|
|
879
|
+
`CODEX_HOME` so installed Codex apps do not add their connector schemas).
|
|
880
|
+
- **Models in the shared Hugging Face cache are found** (issue #445). The
|
|
881
|
+
model resolver consults `HF_HOME` / `HF_HUB_CACHE` after MTPLX's own
|
|
882
|
+
cache, honours `HF_HUB_OFFLINE`, and holds the copy to the same
|
|
883
|
+
completeness check, so a pack already downloaded with the Hugging Face
|
|
884
|
+
CLI is served without a second download; Forge builds from the local
|
|
885
|
+
copy first and stamps the revision it built from.
|
|
886
|
+
- **aria2 download backend as an opt-in** (PR #452, zeeshanhaque21).
|
|
887
|
+
`mtplx pull --download-backend aria2` uses aria2c for multi-connection
|
|
888
|
+
pulls and fails with an install hint when aria2c is missing;
|
|
889
|
+
`--download-backend auto` picks aria2c when it is installed. The built-in
|
|
890
|
+
downloader stays the default.
|
|
891
|
+
- **Appearance row localized.** The Settings row for Appearance (its title,
|
|
892
|
+
caption and the System, Dark and Light segments) was English in every
|
|
893
|
+
language; it now follows the app language in all thirteen tables.
|
|
894
|
+
- **Chat sidebar and Forge timestamps follow the app language.** The
|
|
895
|
+
relative times under each chat row and in the Forge tab were formatted
|
|
896
|
+
in the macOS locale, so a Turkish app still read "in 0 sec"; both now
|
|
897
|
+
use the active app language.
|
|
898
|
+
- **Launch chooser, Hermes overlay and tune-step captions localized.** Five
|
|
899
|
+
captions (the launch chooser's "Pick how you want to use it." and custom
|
|
900
|
+
client note, the Hermes auto-approve, install-status and Terminal notes,
|
|
901
|
+
and the tune step's ready line) were English in every language; the ten
|
|
902
|
+
strings now resolve through the app language in all thirteen tables.
|
|
903
|
+
- **Forge buttons localized.** The Retry verify, Discard and Use it now
|
|
904
|
+
buttons in the Forge verify and registered stages were English in every
|
|
905
|
+
language; they now follow the app language.
|
|
906
|
+
- **Turkish** (issue #470). The app ships a thirteenth language table
|
|
907
|
+
(Türkçe, 1,562 strings) next to the twelve from 2.11.
|
|
908
|
+
- **Remove a stale custom model from the picker** (PR #471, nRanzo). A
|
|
909
|
+
persisted custom model whose files are gone can be removed from the
|
|
910
|
+
picker with a right-click and a confirmation; official, installed and
|
|
911
|
+
currently selected models cannot. Nothing on disk is touched.
|
|
912
|
+
- **README model table** (issues #238, #408). Every shipped pack with its
|
|
913
|
+
Hugging Face repo, the smallest Mac it is offered on next to its measured
|
|
914
|
+
peak serving memory, what it is for, and the profile and depth MTPLX
|
|
915
|
+
resolves on its own; two tests keep the table equal to the catalog.
|
|
916
|
+
|
|
917
|
+
- **A bounded request replay tool.** `scripts/replay_chat_request.py`
|
|
918
|
+
replays a captured chat request against a daemon with an explicit output
|
|
919
|
+
budget, so a 100k-token agent turn can be re-measured without re-running
|
|
920
|
+
the agent's tools.
|
|
921
|
+
|
|
922
|
+
### Fixed
|
|
923
|
+
|
|
924
|
+
- **A conversation with tools stays warm from turn to turn.** The app's
|
|
925
|
+
JSON encoder wrote the keys of its tool declarations in a different order
|
|
926
|
+
on every request and the native template rendered that order verbatim,
|
|
927
|
+
so an 18,776-token web-search conversation prefilled from the start on
|
|
928
|
+
every turn (14.7 s and 15.2 s to the first token, 0 cached tokens). Tool
|
|
929
|
+
schemas are put into one fixed key order once, at the server boundary, with
|
|
930
|
+
every field, value and list order preserved; a `tool_choice: none` turn keeps the
|
|
931
|
+
declarations in the prompt and on the same cache identity while calls
|
|
932
|
+
stay disabled; and the server's request-only closing instruction is no
|
|
933
|
+
longer banked as client history (that alone replayed a 2,820-token
|
|
934
|
+
answer, 2.8 s). Replayed 19k-token turns against the same build without
|
|
935
|
+
the fix: 15.26 s to 0.57 s and 13.93 s to 0.66 s to the first token;
|
|
936
|
+
installed-app follow-ups at 0.29 s and 0.28 s with 11,421 and 12,510
|
|
937
|
+
cached tokens. Seven regression tests across the app, OpenCode, Pi and
|
|
938
|
+
Hermes. A conversation older than this release prefills once more on
|
|
939
|
+
its first turn.
|
|
940
|
+
- **Agent launches no longer override the model's verify path, and the
|
|
941
|
+
adaptive depth policy no longer learns a startup spike as its recurring
|
|
942
|
+
cost.** The app's OpenCode, Pi and Hermes presets and the CLI launchers
|
|
943
|
+
exported `MTPLX_LAZY_TARGET_DISTRIBUTIONS=1` and
|
|
944
|
+
`MTPLX_LAZY_BONUS_VERIFY=1` from an older tuning, which on Flash-Next
|
|
945
|
+
switched off the batched compiled verifier the chat launch already used;
|
|
946
|
+
both exports are gone and the model's defaults apply (explicit operator
|
|
947
|
+
exports still win, other families keep their profile defaults). The
|
|
948
|
+
expected-value depth policy seeded its cost estimate with a one-off
|
|
949
|
+
125 ms first call (the next three were 30 to 31 ms) and chose the slower
|
|
950
|
+
eager path for 453 of 464 cycles; it now calibrates on the minimum of its
|
|
951
|
+
four warm-up samples before the weighted average starts, and a sustained
|
|
952
|
+
rise still lowers the depth. A 108,919-token OpenCode turn, four
|
|
953
|
+
alternating runs against the same build without the change: 48.83 to
|
|
954
|
+
61.77 tok/s (+26.5 percent), compiled verify calls 11 to 376 per 1,024
|
|
955
|
+
output tokens, memory flat; 200,073 tokens hold at about 50 tok/s.
|
|
956
|
+
- **OpenCode 2 loads the MTPLX session-header plugin** (issue #498). The
|
|
957
|
+
plugin is a small package with the version 1 entrypoint and a version 2
|
|
958
|
+
`setup` hook scoped to the MTPLX provider; 1.18.29 and 2.0.5 clients both
|
|
959
|
+
send the headers, the managed registration migrates in place and other
|
|
960
|
+
plugins are preserved.
|
|
961
|
+
- A conversation whose generation-final snapshot was refused for size (over the per-session cap, issue #499's 48 GB shape) reports that refusal as the next turn's `cache_miss_reason` (`oversized_snapshot_skipped`) and in the bank's `last_oversized_skip`; it used to surface as the cold tier's `ssd_prefix_miss`.
|
|
962
|
+
|
|
963
|
+
- **A busy daemon is no longer killed as dead** (issue #487, HenriGrimm).
|
|
964
|
+
The app's watchdog killed a live daemon whose generation-final prefix
|
|
965
|
+
commit took 19-25 s on a 110-150k vision agent session: two missed health
|
|
966
|
+
probes were read as death while the commit succeeded. The watchdog now
|
|
967
|
+
reaps only when the daemon's process is gone, its port stops accepting
|
|
968
|
+
connections, or it has been silent for 90 s; a refresh or a chat stream
|
|
969
|
+
that fails once goes through the same check. The slow commit itself is
|
|
970
|
+
fixed too: a prompt whose screenshots exceeded the vision embed cache's
|
|
971
|
+
row budget evicted its own images and re-ran the tower for every one on
|
|
972
|
+
the model-owner thread; the prompt's images are pinned for the pass, and
|
|
973
|
+
the commit records the wall of each phase in the flight event so a slow
|
|
974
|
+
commit names its phase.
|
|
975
|
+
- **Gemma 4 warm turns restore across rewritten conversation turns**
|
|
976
|
+
(PR #283, Craig Tollifson). The Gemma 4 prompt path now uses the session
|
|
977
|
+
bank's near-prefix restore, so an edited or re-rendered turn no longer
|
|
978
|
+
re-prefills the whole conversation. Two exactness holes in the sliding
|
|
979
|
+
window trim it relies on were closed on the way in: a restored entry's
|
|
980
|
+
trim outside the last-update rollback left stale rows in the buffer that
|
|
981
|
+
the next step counted as history, and the bank's identity for a Gemma 4
|
|
982
|
+
entry now comes from the runtime's declared history policy, never a
|
|
983
|
+
literal, so AR-only sessions cannot hit a policy mismatch.
|
|
984
|
+
|
|
985
|
+
- **A wedged daemon of the app's own no longer moves the configured port,
|
|
986
|
+
and a port fallback is never saved** (issue #503). After a hard freeze
|
|
987
|
+
the daemon's listener could survive while `/health` stopped answering;
|
|
988
|
+
the app's port preflight read that as "another app", moved to the next
|
|
989
|
+
free port, saved the new port to settings, and every client pinned to
|
|
990
|
+
the configured port (an agent connector on 8001) was stranded for good,
|
|
991
|
+
with the engine showing Running. The preflight now asks the OS who holds
|
|
992
|
+
the port: a listener carrying the app's own launch marker in its
|
|
993
|
+
environment is stopped in place and the configured port is kept. For a
|
|
994
|
+
genuinely foreign occupant the launch still moves to a free port, but
|
|
995
|
+
only for that launch: settings keep the configured port, a save made
|
|
996
|
+
meanwhile writes the configured port back (changing the port on purpose
|
|
997
|
+
still wins), the banner stays until the next start, and the next start
|
|
998
|
+
tries the configured port again. A listener without the marker (a
|
|
999
|
+
stranger's app, a CLI-started `mtplx serve`) is never signalled.
|
|
1000
|
+
- **Deep-context sessions persist on the machines that can restore them**
|
|
1001
|
+
(PR #496, Dizzler7). A 12 GiB warm snapshot (Qwen3.8-27B, Q8 KV, more
|
|
1002
|
+
than 100k tokens) was refused by the flat 8 GiB per-session cap on Macs
|
|
1003
|
+
under 96 GB, so those sessions never reached the SSD tier and came back
|
|
1004
|
+
cold after a restart. The per-session cap is now sized by the memory
|
|
1005
|
+
plan: two thirds of the bank budget, held under half of what the engine
|
|
1006
|
+
budget leaves after the weights and the runtime transients (a restore
|
|
1007
|
+
holds the snapshot next to its banked copy): 13.2 GiB on a 64 GB Mac with
|
|
1008
|
+
the 27B, 32 GiB on a 128 GB Mac with the 27B, 10.5 GiB with Flash-Next.
|
|
1009
|
+
The PR's flat 32 GiB would have pushed a 64 GB Mac into swap; the
|
|
1010
|
+
flat fallbacks for a machine without a plan are unchanged. From the same
|
|
1011
|
+
PR: 64 GB Macs with 150 GiB of free disk default the SSD cap to 100 GiB
|
|
1012
|
+
instead of 32, and the hourly SSD write budget default is 128 GiB (was
|
|
1013
|
+
64).
|
|
1014
|
+
- **Remote Qwen checkpoints without MTP weights inspect as autoregressive,
|
|
1015
|
+
and Forge refuses to build speculative artifacts from them** (PR #489,
|
|
1016
|
+
Philip John Basile). A Hugging Face checkpoint whose listing carried every
|
|
1017
|
+
trunk shard but no draft head (`nex-agi/Nex-N2.5-mini`) was reported as
|
|
1018
|
+
`missing-model-weights` because the trunk check only looked at a local
|
|
1019
|
+
directory; inspection now uses the same local-or-remote trunk check and
|
|
1020
|
+
reports the model as runnable with MTP off. Forge's runtime probe treated
|
|
1021
|
+
`can_run` as MTP evidence and admitted AR-only trunks into a speculative
|
|
1022
|
+
conversion that cannot create the missing trained head; it now requires
|
|
1023
|
+
MTP weight evidence and says so.
|
|
1024
|
+
- **SSD cache eviction yields to active requests.** A populated cache could
|
|
1025
|
+
keep scanning and deleting old snapshots during the next reply, competing
|
|
1026
|
+
with generation. Eviction now releases the store lock before waiting,
|
|
1027
|
+
reclaims retired snapshots in bounded batches, and reads surviving blob
|
|
1028
|
+
references once per pass instead of once per evicted entry. Concurrent
|
|
1029
|
+
writes and shared blobs remain protected. The shared server fix applies
|
|
1030
|
+
to the native app, OpenCode, Hermes, and Pi.
|
|
1031
|
+
- **The SSD session cache reconciles itself and its cap means the whole
|
|
1032
|
+
directory** (issue #493; three reports: 394,155 orphaned blobs, 44.1 GB,
|
|
1033
|
+
against 17 entries after three weeks; 471,541 blobs, 67 GB, on a 60 GB cap
|
|
1034
|
+
after three days of uptime). The reconciliation that deletes files the
|
|
1035
|
+
manifest no longer reaches only ran when a write found the store near its
|
|
1036
|
+
cap, which a generous cap never reached; and after each cleanup the cap
|
|
1037
|
+
check assumed every unaccounted byte was gone, although blobs still shared
|
|
1038
|
+
by a later snapshot of the same conversation stay on disk when the entry
|
|
1039
|
+
that paid for them is evicted, so the directory could sit above
|
|
1040
|
+
`--ssd-session-cache-max-size` for good. The daemon now reconciles the
|
|
1041
|
+
store in the background every time it opens the cache (yielding to
|
|
1042
|
+
requests, never blocking the boot or the first request; one
|
|
1043
|
+
`mtplx_ssd_session_cache_reconcile` line in the daemon log says what it
|
|
1044
|
+
reclaimed), the cap check counts the directory as it is on disk, reclaims
|
|
1045
|
+
garbage on the writer thread before it would evict live entries for the
|
|
1046
|
+
room, and never walks the store on the request thread. Blobs of a write in
|
|
1047
|
+
flight, and blobs a row committed during a pass names, are never deleted.
|
|
1048
|
+
`docs/server.md` documents the cap, the pass and `mtplx gc`.
|
|
1049
|
+
- **A reply's own whitespace no longer breaks its warm restore.** The
|
|
1050
|
+
visible content of a reply is served with its leading and trailing
|
|
1051
|
+
whitespace removed, so a client's echo could never carry the model's
|
|
1052
|
+
own whitespace tokens; a turn cut by the token budget after a newline
|
|
1053
|
+
came back with the end-of-turn marker where the newline was, the
|
|
1054
|
+
exactness rule refused the restore and the next request re-prefilled
|
|
1055
|
+
the whole turn. The committed-id splice now puts the model's own
|
|
1056
|
+
whitespace back, and it also runs when thinking is off.
|
|
1057
|
+
- **Copied blocks at the verify cache's growth edge kept their rows.** The
|
|
1058
|
+
context-copy path verified a copied block through the fixed-capacity
|
|
1059
|
+
buffers of the compiled verify; a block that straddled the buffer's end (a
|
|
1060
|
+
512-token growth grant on the 27B, the rounding slack of a freshly
|
|
1061
|
+
restored Flash-Next entry) was written with a functional slice update that
|
|
1062
|
+
silently drops the rows past the end, so the rest of the turn attended
|
|
1063
|
+
over missing rows (an answer cut off mid-statement, or a file written
|
|
1064
|
+
twice, was the visible symptom). Both copy-block routes reserve their rows
|
|
1065
|
+
before the forward and the write grows the buffers as a last guard.
|
|
1066
|
+
- **Flight recorder counts non-streaming requests.** A non-streaming
|
|
1067
|
+
chat completion fed the recorder no token events, so `mtplx trace`
|
|
1068
|
+
showed a 24k-token generation as a prefill with zero tokens for its
|
|
1069
|
+
whole life. Non-streaming requests now report their tokens as they
|
|
1070
|
+
are produced, the same as streamed ones.
|
|
1071
|
+
- **OpenCode compacted after every reply on small context windows** (issue
|
|
1072
|
+
#480). MTPLX registered the model with OpenCode with the reply limit equal
|
|
1073
|
+
to the context window; OpenCode reserves the reply limit out of the window
|
|
1074
|
+
before deciding whether the conversation still fits, so on an 8K window
|
|
1075
|
+
the usable conversation was zero tokens and OpenCode wrote its
|
|
1076
|
+
`## Objective / Important Details / Work State` summary after every turn
|
|
1077
|
+
(48 summaries in 98 turns in the report). `mtplx connect opencode`, the
|
|
1078
|
+
quickstart and the app now advertise a reply budget of half the window,
|
|
1079
|
+
capped at the 32,000 OpenCode uses on large windows, so compaction happens
|
|
1080
|
+
only when the conversation is near the limit.
|
|
1081
|
+
- **261,120-token prompts decode on Flash-Next** (PR #482, davidtai). At
|
|
1082
|
+
the pack's full context the first speculative verify step ran out of GPU
|
|
1083
|
+
memory after the whole prompt had been read: the verify's KV-cache update
|
|
1084
|
+
reallocated the entire cache buffer on every step (about 6.4 GB across the
|
|
1085
|
+
twelve full-attention layers at that context), and its multi-row
|
|
1086
|
+
attention built a score table that grows with the context. The KV update
|
|
1087
|
+
now writes its rows into the existing buffer (exact; the compiled verify
|
|
1088
|
+
path and the rollback are unchanged), and the verify attention is split
|
|
1089
|
+
across query-head groups so each call stays on the fused kernel in the
|
|
1090
|
+
three-to-eight-row band the verify uses (exact per group;
|
|
1091
|
+
`MTPLX_QWEN4_VERIFY_SDPA_HEAD_CHUNK=0` turns it off; `/health` reports
|
|
1092
|
+
`verify_sdpa_head_chunk` when it engages). Speed and peak memory at 16k
|
|
1093
|
+
are unchanged; the 27B models are not affected.
|
|
1094
|
+
- **The Flash-Next converter accepts the official FP8 checkpoint** (PR
|
|
1095
|
+
#474, Graham Jenkins). Qwen's Flash-Next-FP8 revision stores one shared
|
|
1096
|
+
scalar scale for its 128 n-gram embedding shards instead of per-block
|
|
1097
|
+
scales; the converter now falls back to that scalar for n-gram shards and
|
|
1098
|
+
accounts for it, with regression tests for both layouts.
|
|
1099
|
+
- **Flash-Next block verification is exact at every depth.** Three
|
|
1100
|
+
independent audits enumerated the block-verify acceptance rule on tiny
|
|
1101
|
+
vocabularies and found it exact at depths 1 and 2 but off by up to 4e-2
|
|
1102
|
+
total variation at depth 3, in the cases where the last depth's acceptance probability had
|
|
1103
|
+
been clipped to 1. The ladder now caps the reach budget by the realised
|
|
1104
|
+
reach, propagates that feasible budget, and corrects a rejection from the
|
|
1105
|
+
shortfall the acceptance probabilities leave; an enumeration test compares the emitted joint
|
|
1106
|
+
distribution with the target for both rules at depths 1 to 4 (exact to 1e-12).
|
|
1107
|
+
- **Warm agent turns on hybrid models restore the recurrent state exactly.**
|
|
1108
|
+
A near-prefix restore whose gap to the banked turn was 1 to 8 tokens (the
|
|
1109
|
+
shape of a re-rendered agent turn) trimmed the attention KV but kept the
|
|
1110
|
+
GDN state from the longer sequence, then re-ran one token on it; the whole
|
|
1111
|
+
turn decoded on state the new prompt never produced and later turns
|
|
1112
|
+
inherited it, on the 8-bit Quality pack exactly like the 4-bit ones. Every
|
|
1113
|
+
partial restore of a recurrent entry now lands on a stored recurrent
|
|
1114
|
+
boundary at or below the match point.
|
|
1115
|
+
- **The banked state after an MTP turn covers every committed token.** The
|
|
1116
|
+
final token that ended a response (a deferred greedy correction, a fresh
|
|
1117
|
+
sample, or any max_tokens exit) was committed but never forwarded, so the
|
|
1118
|
+
banked state was one token short of the key it was filed under and the
|
|
1119
|
+
next warm turn decoded as if the terminator never existed (temperature-0
|
|
1120
|
+
clients such as Cline hit it every turn). The final token is now
|
|
1121
|
+
forwarded before the state is banked.
|
|
1122
|
+
- **Non-finite logits fail loudly** instead of becoming token 0, which is
|
|
1123
|
+
`!` in the Qwen vocabulary (the thousands of exclamation marks of issue
|
|
1124
|
+
#311). The request ends with `finish_reason: error`, code
|
|
1125
|
+
`non_finite_logits`, a message with the NaN/inf counts, the session's
|
|
1126
|
+
cached state is dropped, and the daemon stays up. A row that constrained
|
|
1127
|
+
decoding masks to -inf with a finite winner is legitimate and passes the
|
|
1128
|
+
guard.
|
|
1129
|
+
- **The Flash-Next AR sampler follows the reference nucleus rule.** The
|
|
1130
|
+
pipelined AR sampler measured top-p on the top-k slice alone, keeping one
|
|
1131
|
+
to five fewer of the top-20 tokens than the other sampling paths at 1.0/0.95/20;
|
|
1132
|
+
it now measures the nucleus on the full-vocabulary softmax like the CPU
|
|
1133
|
+
reference (test: identical supports, total variation below 1e-5).
|
|
1134
|
+
- **Hindi, Thai and vowelled Arabic tokenize as the model was trained.**
|
|
1135
|
+
transformers' Qwen2Tokenizer replaced the packs' pre-tokenizer regex with
|
|
1136
|
+
its Qwen2-era one, splitting combining marks off their letters (Hindi 32
|
|
1137
|
+
tokens instead of 20, Thai 17 instead of 9, Arabic 45 instead of 31 on the
|
|
1138
|
+
same sentences; Latin, code and Chinese unchanged), and the six 27B packs
|
|
1139
|
+
shipped that regex baked into tokenizer.json. The loader restores the
|
|
1140
|
+
Qwen3 regex on every Qwen3-family pack.
|
|
1141
|
+
- **32 GB Macs get a working draft head** (issue #483, HeyCocoa). The
|
|
1142
|
+
fast draft LM head was built through a 2.4 GiB dense intermediate; on an
|
|
1143
|
+
M1 Pro 32 GB the requantize hit a silent Metal out-of-memory and the head
|
|
1144
|
+
read back as zeros, collapsing MTP acceptance to under 1%. The head is
|
|
1145
|
+
now built in row chunks (bit-identical result, no dense intermediate), a
|
|
1146
|
+
zeroed head is refused, and the install falls back to the resident target
|
|
1147
|
+
head as the drafter.
|
|
1148
|
+
- **A long agent turn stays warm across the model's own token seams.** A
|
|
1149
|
+
model's sampled tokens are not always the standard encoding of their own
|
|
1150
|
+
text: in a Hermes session at effort xhigh the 27B wrote `"Nothing` as one
|
|
1151
|
+
token 8,498 tokens into a write_file call where the tokenizer encodes `"`
|
|
1152
|
+
then `Nothing`. The client's re-tokenized history then diverged from the
|
|
1153
|
+
committed stream on identical bytes, the generation-final snapshot was
|
|
1154
|
+
refused, the fallback re-prefill was preempted by the next request, and
|
|
1155
|
+
that request re-prefilled the whole 29,842-token turn (30,038 tokens,
|
|
1156
|
+
41 s to first token). Wherever the resent prompt and the session's
|
|
1157
|
+
committed ids decode to the same text, the committed ids are now served
|
|
1158
|
+
(a bounded window per seam, never across a real edit), on both the
|
|
1159
|
+
request and the snapshot side; the request log carries `token_splice`
|
|
1160
|
+
under `committed_reasoning_canonicalization`.
|
|
1161
|
+
`MTPLX_COMMITTED_TOKEN_SPLICE=0` restores the old behaviour.
|
|
1162
|
+
- **A mid-conversation system message no longer rewrites message 0**
|
|
1163
|
+
(issue #477). Claude Code's per-turn `<system-reminder>` notes arrive as
|
|
1164
|
+
late system messages; hoisting them into the leading system message
|
|
1165
|
+
changed the first message every turn and cost a cold re-prefill of the
|
|
1166
|
+
whole history. A late system message now becomes a user turn in place.
|
|
1167
|
+
- **The Flash-Next serving optimizations reach a served daemon.** Four
|
|
1168
|
+
hot-path settings (`MTPLX_QWEN4_OPDIET`, `MTPLX_QWEN4_VERIFY_GLUE`,
|
|
1169
|
+
`MTPLX_QWEN4_DRAFT_K20_PRESCATTER`, `MTPLX_QWEN4_BLOCK_VERIFY`) were read
|
|
1170
|
+
once at import, before the server applied the Flash-Next defaults,
|
|
1171
|
+
so `/health` reported them configured while the daemon ran with all four
|
|
1172
|
+
off (davidtai's PR #475 found the same problem). The settings are
|
|
1173
|
+
re-read when the model's runtime env is applied, before the load. Three of
|
|
1174
|
+
them are on by default; the exact block verify
|
|
1175
|
+
(`MTPLX_QWEN4_BLOCK_VERIFY`) is an opt-in export, because on a
|
|
1176
|
+
45,000-token reasoning turn at effort xhigh it accepted 3.5 percent fewer
|
|
1177
|
+
draft tokens per round than the standard verify across alternating
|
|
1178
|
+
boots (both are distribution-exact; the faster one is the default).
|
|
1179
|
+
- **Reasoning substitution checks the whole tool call.** The
|
|
1180
|
+
committed-reasoning canonicalizer compared tool calls by their loop key
|
|
1181
|
+
(command or path only), so a `write` to the same file with new content
|
|
1182
|
+
passed the check and the prompt was served with the old body. The check now
|
|
1183
|
+
compares the complete argument set in both markup dialects.
|
|
1184
|
+
- **Desktop web views can be allowlisted for CORS** (issue #473). The
|
|
1185
|
+
origin validator accepted only `http` and `https`, so
|
|
1186
|
+
`--cors-origin tauri://localhost` (Jan.app) and `MTPLX_CORS_ORIGINS`
|
|
1187
|
+
entries such as `app://obsidian.md` were refused as invalid and the
|
|
1188
|
+
requests got a 403. Any scheme is an origin scheme now; a wildcard, a
|
|
1189
|
+
bare host, a path, a query or credentials are still refused, and the
|
|
1190
|
+
allowlist still never opens `/admin` or the browser sign-in routes.
|
|
1191
|
+
- **Request-capture registry no longer grows for the life of the daemon**
|
|
1192
|
+
(noticed in PR #356, PhilipJohnBasile). With `MTPLX_REQUEST_CAPTURE_DIR` set, the in-memory map from request id
|
|
1193
|
+
to capture file kept an entry for every request ever captured, including
|
|
1194
|
+
the ones the ring had already moved to `pruned/`; it now forgets an id
|
|
1195
|
+
as soon as its file leaves the ring.
|
|
1196
|
+
- **Streams held for minutes when the first line looked like tool-control
|
|
1197
|
+
markup** (issue #468). An answer whose first line was a bare name such as
|
|
1198
|
+
`value` or `value=abc` stayed in the orphan-marker hold until the stream
|
|
1199
|
+
finished. A bare tool-control line closes on its own line, so the hold is
|
|
1200
|
+
released as soon as the first line closes.
|
|
1201
|
+
- **Retrieval models are validated at startup** (issue #445 audit). A
|
|
1202
|
+
missing `--embedding-model` or `--reranker-model` used to boot a daemon
|
|
1203
|
+
that answered the first `/v1/embeddings` request with a 500 and a
|
|
1204
|
+
traceback; the CLI and the server module now refuse to launch with the
|
|
1205
|
+
`mtplx pull` hint, a checkpoint that disappears later surfaces as a
|
|
1206
|
+
structured 404, and `/v1/models` lists only retrieval models that
|
|
1207
|
+
resolved. Retrieval models are also found in the shared Hugging Face
|
|
1208
|
+
cache.
|
|
1209
|
+
- **Dashboard request log** (issue #401). The per-depth acceptance card
|
|
1210
|
+
prints real accepted and drafted totals, and the when column shows the
|
|
1211
|
+
completion time of every recorded request, cancelled and disconnected
|
|
1212
|
+
rows included.
|
|
1213
|
+
- **`mtplx connect opencode` follows the live daemon** (issue #472). The
|
|
1214
|
+
command names a host and a port, not a pack, so it now takes the model id
|
|
1215
|
+
and the image-input flag from the daemon that answers there; an explicit
|
|
1216
|
+
`--model-id` still wins, and without a daemon the pack metadata for the id
|
|
1217
|
+
in hand answers as before. 2.11.2 already advertised image input from the
|
|
1218
|
+
pack metadata; the port-only form assumed the catalog default id.
|
|
1219
|
+
- **`/health` fast-path checks against the server's own overrides.** The
|
|
1220
|
+
Flash-Next path keeps the verify snapshot and pins the batched target
|
|
1221
|
+
distributions on purpose, and `/health` reported those three keys as
|
|
1222
|
+
`ok: false` against the profile block. The expectation is now the
|
|
1223
|
+
runtime override the server resolved, with the entry naming its source
|
|
1224
|
+
and the profile value it replaced.
|
|
1225
|
+
- **Hermes profile warning on every launch.** Hermes v0.21 deprecates the
|
|
1226
|
+
`TERMINAL_CWD` line in `.env`; the CLI profile writer and the app no
|
|
1227
|
+
longer write it. The working directory reaches Hermes through
|
|
1228
|
+
`terminal.cwd` in its config, as before.
|
|
1229
|
+
- **Flash-Next agent launches draft to the chosen depth.** With the
|
|
1230
|
+
Adaptive depth switch unset, the app's Pi and Hermes launches no longer
|
|
1231
|
+
turn on the expected-value depth policy for Flash-Next: measured on the
|
|
1232
|
+
shipped 2.11.2 settings, the policy decodes 7 to 8 percent slower than a
|
|
1233
|
+
fixed depth 3 at 2k and at 19k tokens of context, and the 27B pair is a
|
|
1234
|
+
tie, so the 27B presets are unchanged. The switch still turns the policy
|
|
1235
|
+
on explicitly.
|
|
1236
|
+
|
|
1237
|
+
### Changed
|
|
1238
|
+
|
|
1239
|
+
- **transformers floor raised to 5.10.0** (Dependabot alert 24, the
|
|
1240
|
+
`save_pretrained` path-traversal range); the lock moves to 5.14.1.
|
|
1241
|
+
- **The dead M=8 K-split verify kernel is removed** (issue #322). Nothing
|
|
1242
|
+
dispatched `nax_qmm_m8` since it landed (0.51-0.87x of stock on every
|
|
1243
|
+
live shape); the evidence stays as a comment beside the M=16 eligibility
|
|
1244
|
+
helper so the closed branch is not re-litigated.
|
|
1245
|
+
- **Docs.** The adaptive depth policy flag (`docs/server.md`), the request-capture
|
|
1246
|
+
ring size (`MTPLX_REQUEST_CAPTURE_KEEP`), the reasoning effort surfaces
|
|
1247
|
+
(`docs/api.md`, issue #484) and the OpenCode CLI's per-turn system-prompt
|
|
1248
|
+
rebuild with its measured cost (`docs/perf`) are documented.
|
|
1249
|
+
|
|
1250
|
+
## [2.11.2] - 2026-09-06
|
|
1251
|
+
|
|
1252
|
+
A correctness release for every Mac that is not an M5, seven session-bank
|
|
1253
|
+
and memory fixes for long agent sessions, native vision for Flash-Next in
|
|
1254
|
+
Hermes, OpenCode and Pi, a verifier-depth fix for Flash-Next agent turns,
|
|
1255
|
+
and app and CLI repairs.
|
|
1256
|
+
|
|
1257
|
+
### Fixed
|
|
1258
|
+
|
|
1259
|
+
- **Flash-Next multimodal requests keep QSA sparse attention.** Image-bearing
|
|
1260
|
+
requests ran dense causal attention on the belief that the reference does;
|
|
1261
|
+
Qwen's reference applies the sparse indexer to multimodal input with
|
|
1262
|
+
image-aware M-RoPE positions on the indexer's queries and pooled
|
|
1263
|
+
block-start keys. The engine follows it, checked against an independent
|
|
1264
|
+
position oracle. A 54k image conversation went from about 19 tok/s and a
|
|
1265
|
+
128 GiB allocator peak to a 92 GiB peak with turns completing at 43k-74k.
|
|
1266
|
+
Dense-path vision cache entries are not reused. Image-bearing turns still
|
|
1267
|
+
decode on the eager verifier (33-50 tok/s here versus 54-61 text-only);
|
|
1268
|
+
`MTPLX_QWEN4_VISION_QSA=0` is a diagnostic rollback.
|
|
1269
|
+
- **Hermes, OpenCode and Pi advertise image input from the pack's metadata.**
|
|
1270
|
+
All three registered the model as text-only, so Hermes routed screenshots
|
|
1271
|
+
through its auxiliary Analyze Image tool as separate requests and OpenCode
|
|
1272
|
+
and Pi could not attach an image. The app and `mtplx` probe `config.json`
|
|
1273
|
+
and the weight index (`vision_config` plus a vision-tower weight), never
|
|
1274
|
+
the model name; existing text-only entries are upgraded; `/v1/models`
|
|
1275
|
+
carries `supports_vision` and `modalities`.
|
|
1276
|
+
- **A warm vision turn is admitted as warm.** The pre-prefill guard compared
|
|
1277
|
+
raw image-pad tokens against content-keyed snapshots, projected a warm turn
|
|
1278
|
+
as a full miss, shed the snapshot it needed and could refuse with a 507.
|
|
1279
|
+
Admission uses the content-keyed identity restore uses; an anonymous image
|
|
1280
|
+
turn rejoins the session owning the matching snapshot
|
|
1281
|
+
(`session_source: vision_bank_prefix`); different pixels never adopt an
|
|
1282
|
+
older image's state.
|
|
1283
|
+
- **A longer bank entry no longer erases a shorter exact prefix it cannot
|
|
1284
|
+
restore.** Flash-Next recurrent checkpoints sit on 2,048-token boundaries
|
|
1285
|
+
and the exact prompt end is not one, so the generation-final entry
|
|
1286
|
+
superseded the exact prompt entry and the next tool turn restored 2,048
|
|
1287
|
+
tokens and re-prefilled 31k. A longer entry replaces a shorter one only
|
|
1288
|
+
when it carries every restore point the shorter one supplies.
|
|
1289
|
+
- **Hermes history matches the committed stream.** The profile enables
|
|
1290
|
+
`reasoning_echo`, so Hermes sends its reasoning back (the next tool turn
|
|
1291
|
+
reused 15,754 of 16,005 tokens); the cache producer, canonicalization and
|
|
1292
|
+
next-turn comparison strip the tool-call preamble the way Hermes' wire
|
|
1293
|
+
does, an intentionally empty visible answer stays empty, and a response's
|
|
1294
|
+
own reasoning survives an older interrupted turn. The Hermes profile also
|
|
1295
|
+
requests `compression.tool_image_retention: until_compaction`, which needs
|
|
1296
|
+
a Hermes change that is not upstream yet; current Hermes versions ignore
|
|
1297
|
+
the key.
|
|
1298
|
+
- **The Flash-Next expected-value depth policy measures draft and verify
|
|
1299
|
+
cost per depth** when the compiled fixed-M4 verifier is engaged, instead of
|
|
1300
|
+
assuming a shorter eager draft is cheaper than compiled depth 3; it leaves
|
|
1301
|
+
one-time compilation out of the estimate and re-probes both depths. The
|
|
1302
|
+
rehearsal's 15k coding turn went from about 11 % to 90-96 % of cycles on
|
|
1303
|
+
the compiled route and from 41-47 to 56-59 tok/s (machine in use, not a
|
|
1304
|
+
quiet A/B). `MTPLX_ADAPTIVE_VERIFY_COST_FEEDBACK=0` restores the prior.
|
|
1305
|
+
- **The prefill gauge and the Avg Prefill card measure the same chunk work.**
|
|
1306
|
+
The card averaged completed-request rates (a cached follow-up counted like
|
|
1307
|
+
a full prompt; setup counted as prefill: 422 shown for a 1,040 tok/s
|
|
1308
|
+
prompt) and the gauge preferred a setup-diluted cumulative rate (296 for a
|
|
1309
|
+
1,613 tok/s chunk). Both read measured chunks now; the card averages the
|
|
1310
|
+
last 100 and shows their peak, recorded once per chunk server-side.
|
|
1311
|
+
- **The prefill gauge no longer flickers through an agent's tool loop.**
|
|
1312
|
+
It morphs to the prefill face only for at least 1,024 new tokens, judged
|
|
1313
|
+
from the resolved session's known prefix on the first frame and from
|
|
1314
|
+
the measured cache split after; short suffix prefills keep the decode
|
|
1315
|
+
face. No timer and no hold.
|
|
1316
|
+
- **Latency receipts are complete**: `ttft_s` includes admission and
|
|
1317
|
+
pending-history waits (a 39 s history-rebuild wait had vanished), decode
|
|
1318
|
+
tok/s uses the generator's decode time instead of charging prompt setup to
|
|
1319
|
+
decode twice, the context tile grows with the answer, and the verify
|
|
1320
|
+
waterfall shows the first live request.
|
|
1321
|
+
- **App and Python test suites leave the user's files alone**: the
|
|
1322
|
+
daemon-supervisor tests overwrote the saved model and onboarding choices
|
|
1323
|
+
through the real settings file; synthetic Python requests entered the real
|
|
1324
|
+
request log and flight recorder.
|
|
1325
|
+
- **27B on M1-M4: the flash-decoding verify route is gated by hardware
|
|
1326
|
+
(#459, #464, #467, #461).** 2.11 turned the route on in turbo without
|
|
1327
|
+
a GPU-family gate. It engages once the KV buffer reaches 8,192 tokens and
|
|
1328
|
+
uses the M5 GPU's tensor units; on M1-M4 GPUs it returned wrong attention
|
|
1329
|
+
(unrelated reasoning, imaginary tasks, mixed languages, no tool calls) and
|
|
1330
|
+
on macOS 15 the kernel failed to build (`MetalPerformancePrimitives.h`
|
|
1331
|
+
not found). The route now runs only on an M5-class GPU on macOS 26.2 or
|
|
1332
|
+
newer; everywhere else the packed-GQA verify kernel 2.10.2 shipped serves,
|
|
1333
|
+
validated at startup. `/health degradation.nax` carries `available`, the
|
|
1334
|
+
`gpu_family_or_os` bail counts and, new, `flash_dispatch_counters` for an
|
|
1335
|
+
engaged route. Rehearsed on an M5 Max with the reporters' 14k and 32k
|
|
1336
|
+
diff-summary prompts: the forced M1-M4 path and the native route both
|
|
1337
|
+
answer correctly; the route counts 66 dispatches with no bails. Item 3
|
|
1338
|
+
of #455 (incoherent output with MTP on, through Pi on an M3 Ultra)
|
|
1339
|
+
matches the route's engagement point and is not yet confirmed from that
|
|
1340
|
+
machine; #455's AR slowdown is not explained by this change.
|
|
1341
|
+
- **AR-only sessions restore again (#465).** A target-only AR runtime
|
|
1342
|
+
(`--no-load-mtp`) banked its postcommit prefix under the `cycle` history
|
|
1343
|
+
policy while every lookup, the prefill store and the cache fingerprint
|
|
1344
|
+
said `committed`, so the bank refused the longest entry with
|
|
1345
|
+
`policy_mismatch` and Hermes or Pi re-prefilled the whole prompt on every
|
|
1346
|
+
top-level turn (14.5k tokens, about two minutes on an M1 Max). One policy
|
|
1347
|
+
per runtime is now derived in one place. The 90-116 s first turn after
|
|
1348
|
+
idle in #455 runs the same configuration on an M3 Ultra: the same
|
|
1349
|
+
mechanism on paper, unconfirmed from that machine.
|
|
1350
|
+
- **The pre-prefill memory guard refuses a prompt that still projects over
|
|
1351
|
+
the memory limit after reclamation (#450).** It had admitted a 136k
|
|
1352
|
+
prompt at 105.4 GB against a 103.1 GB limit after clearing the allocator
|
|
1353
|
+
cache; the reporter's 128 GB Mac kernel-panicked four times. The guard
|
|
1354
|
+
re-projects after every reclamation step and answers with a structured
|
|
1355
|
+
507 before prefill, naming the projection, the limit and the uncached
|
|
1356
|
+
tokens; the engine keeps its sessions. `--allow-swap` keeps the operator's
|
|
1357
|
+
explicit choice; between the warning line and the limit nothing changes.
|
|
1358
|
+
- **Deep conversations retain their session at recognized turn boundaries (#446).** A live
|
|
1359
|
+
session's committed stream carries the reasoning it streamed and clients
|
|
1360
|
+
resend the history without it, so from turn 2 on every request's raw
|
|
1361
|
+
shared prefix with its session ends where turn 1 started generating. The
|
|
1362
|
+
resolver accepted that match only as a fraction of the new prompt: 22,437
|
|
1363
|
+
shared tokens passed at 89k (25.06 %) and failed at 112k (20.1 %), the
|
|
1364
|
+
request minted a new anonymous session and block-restored 2,048 tokens of
|
|
1365
|
+
turn 1's snapshot, 105 s of prefill for a turn that had been 24 s (the
|
|
1366
|
+
reporter's 14,336 and 18,432-token remnants at turn 4 or 5, five chains of
|
|
1367
|
+
five; the same line crossed one chain in three on 2.10.x). Sessions now
|
|
1368
|
+
record the prompt length of every turn they generated from, and a shared
|
|
1369
|
+
prefix on one of those boundaries keeps the session whatever fraction of
|
|
1370
|
+
the prompt it is; edited histories keep the fraction rule, and unrelated
|
|
1371
|
+
conversations that share only a long system prompt still diverge before
|
|
1372
|
+
any boundary. The request log's prefix diagnostic names the rule
|
|
1373
|
+
(`reuse_rule`, `turn_boundary`).
|
|
1374
|
+
- **The sparse-prefill native wheel is signed for notarization.** Its QSA
|
|
1375
|
+
kernel library and extension module are signed with the Developer ID,
|
|
1376
|
+
hardened runtime and a secure timestamp before they enter the runtime
|
|
1377
|
+
wheel (the app's signing pass cannot reach inside the archive), and the
|
|
1378
|
+
release script verifies every Mach-O in that wheel before submitting the
|
|
1379
|
+
app. The first 2.11.2 submission was rejected on exactly those two files.
|
|
1380
|
+
- **Later conversations are admitted to the session bank again (#454).**
|
|
1381
|
+
The background-task heuristic (short answer + different system prompt)
|
|
1382
|
+
classified every conversation-continuing short turn as a title job and
|
|
1383
|
+
served it sessionless, so only the first conversation after a restart was
|
|
1384
|
+
banked and every later one re-prefilled at 0 % cache. Only the task shape
|
|
1385
|
+
(system prompt + one user turn) infers a background task now. Reproduced
|
|
1386
|
+
with the reporter's script: sessions two to four went from 0 % to 100 %
|
|
1387
|
+
cached on their third turn.
|
|
1388
|
+
- **`mtplx run`, `mtplx ask` and one-shot `mtplx chat` work on Flash-Next
|
|
1389
|
+
(#463).** The one-shot path applied only the profile defaults, never the family lanes serve
|
|
1390
|
+
stamps, and crashed in the legacy capture walker
|
|
1391
|
+
(`AttributeError: 'DecoderLayer' object has no attribute
|
|
1392
|
+
'input_layernorm'`, then `KeyError: 'conv_states'`). It resolves the same
|
|
1393
|
+
runtime contract as serve and tune, and the legacy capture commit declines
|
|
1394
|
+
family-native captures instead of raising. Receipt: 52 tok/s, MTP depth 3.
|
|
1395
|
+
- **The interactive terminal chat resolves the same contract.** The REPL
|
|
1396
|
+
(`mtplx chat` with no prompt, `mtplx start cli`) still applied only the
|
|
1397
|
+
profile defaults; it now runs the serve environment before the model loads.
|
|
1398
|
+
- **The in-process generators use the family's verifier.** `mtplx run` and
|
|
1399
|
+
the terminal chat hardcoded the legacy capture-commit verifier over the
|
|
1400
|
+
batched verifier the Flash-Next contract selects; a two-turn terminal
|
|
1401
|
+
session on Flash-Next degenerated into repetition and a later run ended
|
|
1402
|
+
in a Metal GPU address fault. The resolved strategy and core now reach
|
|
1403
|
+
generation, as they always did in serve.
|
|
1404
|
+
- **The terminal chat keeps reasoning in its own channel.** It stored
|
|
1405
|
+
`thought</think>answer` as the assistant's content, so Qwen 3.8's
|
|
1406
|
+
template nested that after an empty thinking block and the next turn's
|
|
1407
|
+
history was malformed. Reasoning and content are stored separately.
|
|
1408
|
+
- **`--expect-python` validates the final answer.** It compiled the
|
|
1409
|
+
reasoning and the Markdown fence as Python and failed a valid program;
|
|
1410
|
+
it now splits off the reasoning with the model's codec, unwraps one
|
|
1411
|
+
enclosing fence, and still rejects malformed or missing programs.
|
|
1412
|
+
- **The app's Hermes profile `.env` keeps a configured reasoning effort on
|
|
1413
|
+
its own line.** With Performance › Reasoning effort set, the app wrote
|
|
1414
|
+
`TERMINAL_CWD="…"HERMES_MTPLX_REASONING_EFFORT="xhigh"` as one statement;
|
|
1415
|
+
Hermes' dotenv parser rejected it ("could not parse statement") on every
|
|
1416
|
+
launch and silently lost both the working directory and the effort. Found
|
|
1417
|
+
by the release harness run through Hermes v0.21.0.
|
|
1418
|
+
- **The Hermes profile no longer stamps `terminal.backend: local` (#460).**
|
|
1419
|
+
MTPLX wrote it into `~/.hermes/profiles/mtplx/config.yaml` on every
|
|
1420
|
+
launch, overriding a Docker sandbox the user had configured; the merge
|
|
1421
|
+
keeps what the user set.
|
|
1422
|
+
- **A root Hermes sandbox choice reaches the MTPLX profile.** Hermes
|
|
1423
|
+
profiles do not inherit `~/.hermes/config.yaml`, so a profile with no
|
|
1424
|
+
`terminal.backend` of its own now receives the root `terminal` section;
|
|
1425
|
+
an explicit backend in the profile always wins and nothing else from the
|
|
1426
|
+
root config is copied. Both the app and `mtplx` write it, idempotently.
|
|
1427
|
+
- **The setup wizard measures free space on the model store's volume
|
|
1428
|
+
(#466).** With `~/.mtplx/models` on an external drive (a symlink or
|
|
1429
|
+
`MTPLX_MODEL_DIR`) it read the home volume and refused every catalog
|
|
1430
|
+
model as "insufficient space". The download step and Forge share the fix.
|
|
1431
|
+
- **A foreign-looking occupant of the daemon port is re-probed for five
|
|
1432
|
+
seconds before the app moves ports (#409).**
|
|
1433
|
+
- **Installation survives a failing native-wheel selector.** When the
|
|
1434
|
+
optional step that picks the native sparse-prefill wheel fails, the app
|
|
1435
|
+
installs the bundled pure wheel and records that choice instead of
|
|
1436
|
+
stopping; installation health and the wheel fingerprint checks are
|
|
1437
|
+
unchanged.
|
|
1438
|
+
- **`build_and_run.sh --no-launch` no longer terminates a running app.**
|
|
1439
|
+
It refuses to overwrite the exact running bundle and leaves every other
|
|
1440
|
+
MTPLX instance alone; the launch path is unchanged.
|
|
1441
|
+
- **SSD prefix restore reads only the needed prefix and slices the committed
|
|
1442
|
+
MTP history to it (PR #444 by @softpudding).** Fixed verifier capacity is
|
|
1443
|
+
renewed for adaptive depths and copy windows; shared verifier programs are
|
|
1444
|
+
released when a model unloads; completed request banks are released while
|
|
1445
|
+
the shared programs stay.
|
|
1446
|
+
- **The memory guard asks the live sessions and the bank for the reusable
|
|
1447
|
+
prefix before projecting, walks chain snapshots before giving up, and
|
|
1448
|
+
reclaims allocator storage before evicting useful snapshots (#447,
|
|
1449
|
+
reported and measured by @nomishbhardwaj).**
|
|
1450
|
+
- The expected-value depth policy measures conditional acceptance
|
|
1451
|
+
correctly; prefill pipeline resolution stays out of decode and
|
|
1452
|
+
ineligible chunks; a stale A3B target-prefix test literal.
|
|
1453
|
+
|
|
1454
|
+
### Added
|
|
1455
|
+
|
|
1456
|
+
- **`--stream-stall-deadline-s` on `mtplx serve` and Performance › Advanced ›
|
|
1457
|
+
Stall watchdog in the app (#448)**, 0 disables; the Flash-Next
|
|
1458
|
+
sparse-prefill loops tick the owner heartbeat so a long page-in is not
|
|
1459
|
+
mistaken for a stall.
|
|
1460
|
+
- **An MTP on/off verdict in the trace economics** for any depth policy:
|
|
1461
|
+
`mtp_pays` compares the tokens a run delivered per second with the
|
|
1462
|
+
matched AR rate the caller supplies, and `break_even_acceptance` is the
|
|
1463
|
+
acceptance at which the run would only have matched AR, under its
|
|
1464
|
+
observed cycle cost, non-draft output and depth mix. Proposal cycles are
|
|
1465
|
+
counted from the first draft position, not from verifier calls; `mtplx
|
|
1466
|
+
trace` reports `proposal_cycles`, `cycle_cost_ar_steps`,
|
|
1467
|
+
`drafts_per_cycle`, `acceptance_margin`, `break_even_basis` and `mtp_pays`.
|
|
1468
|
+
- **Opt-in interleaved n-gram rows (#449, David Tai's layout).**
|
|
1469
|
+
`python -m mtplx.ngram_row_layout <table> --out <cache>` writes a derived
|
|
1470
|
+
cache with one 100-byte record per row, checked bit-for-bit, and
|
|
1471
|
+
`MTPLX_NGRAM_ROW_FILE` serves from it. Off by default; no speed claim
|
|
1472
|
+
until the cold-row measurement exists. `docs/diagnostics/ngram-row-cache.md`.
|
|
1473
|
+
- **Adaptive depth switch under Draft depth in the app.** It shows the
|
|
1474
|
+
daemon's live policy: the Pi and Hermes launches have always named the
|
|
1475
|
+
expected-value policy through their presets, chat and the other targets
|
|
1476
|
+
pass none. A flip applies live and is remembered across launches for
|
|
1477
|
+
every target (the launch then passes an explicit `--adaptive-policy`);
|
|
1478
|
+
the switch is hidden for a family that owns its own draft policy.
|
|
1479
|
+
`/v1/mtplx/settings` accepts `adaptive_policy` and reports
|
|
1480
|
+
`adaptive_depth_supported`.
|
|
1481
|
+
- **`mtplx trace --hermes-db <state.db> [--hermes-log <agent.log>]`** joins a
|
|
1482
|
+
Hermes session to engine receipts by token counts and completion clock;
|
|
1483
|
+
ambiguous joins stay unmatched. Trace charts leave missing samples and
|
|
1484
|
+
observation gaps out of the curve.
|
|
1485
|
+
- **`scripts/run_harness_check.py`** runs an agent CLI with the real exit
|
|
1486
|
+
code recorded and a timeout counted as a failure.
|
|
1487
|
+
- **Traces inspectable across harnesses**: exact Pi joins, retained tool
|
|
1488
|
+
results, prefix diagnostics kept with their request, verifier route costs,
|
|
1489
|
+
new tool content distinguished from reduced prefix reuse.
|
|
1490
|
+
- **Forge extracts MTP heads stored outside the `mtp.` prefix (PR #442 by
|
|
1491
|
+
@stooit)**: GLM-4 MoE, GLM-5.3-Flash, DeepSeek-V3.2 and MiMo layouts;
|
|
1492
|
+
MiMo's output head bound instead of random; MiMo reaches tune; forge takes
|
|
1493
|
+
verification depths from the tune policy.
|
|
1494
|
+
- **Flash-Next tuning uses the serving family contract (PR #457 by
|
|
1495
|
+
@stooit)** with real draft-cycle means and explicit budgets.
|
|
1496
|
+
- **Sparse prefill packaged for the bundled Python with a compatible fallback
|
|
1497
|
+
(#423 by @humanrouter)**, validated through the real app installer.
|
|
1498
|
+
- **`--compare-static` fixed-depth baselines for `mtp-adaptive` (PR #276 by
|
|
1499
|
+
@rinaldofesta)**; composer view lookup isolated to the main actor in the
|
|
1500
|
+
app tests (PR #372 by @PhilipJohnBasile).
|
|
1501
|
+
|
|
1502
|
+
## [2.11.1] - 2026-09-03
|
|
1503
|
+
|
|
1504
|
+
MTPLX 2.11. The artifact number is 2.11.1 because 2.11.0 was consumed by a
|
|
1505
|
+
mis-stamped upload to PyPI on 2026-09-01 that was retracted; it contained
|
|
1506
|
+
nothing beyond 2.10.2.
|
|
1507
|
+
|
|
1508
|
+
### Added
|
|
1509
|
+
|
|
1510
|
+
- **Agent-session release gate (`scripts/agent_session_gate.py`).** One
|
|
1511
|
+
command drives the coding-agent turn loop every harness ends up sending
|
|
1512
|
+
(OpenCode, Pi, Hermes, Claude Code, Cline share the OpenAI-compatible
|
|
1513
|
+
shape): a long real-code prompt, short same-session turns, an auto tool
|
|
1514
|
+
round and a forced-choice round with their tool results, judged from the
|
|
1515
|
+
engine's own per-request receipts. It fails on warm-turn dead time over
|
|
1516
|
+
1 s, a warm TTFT over 1.5 s on a small suffix, a bank restore under 90% of
|
|
1517
|
+
the prompt, a postcommit wait over 2 s, a tool-call turn whose
|
|
1518
|
+
generation-final snapshot was not banked in O(1), a decode drop past 20%
|
|
1519
|
+
of the cold turn, or any stream error — every symptom of the 2026-09-03
|
|
1520
|
+
OpenCode regressions, none of which a unit test or a single-request
|
|
1521
|
+
benchmark could see. `release_macos_v1.sh` runs it after the pillar gate
|
|
1522
|
+
against the same daemon (`MTPLX_RELEASE_AGENT_GATE_CONTEXT_TOKENS`,
|
|
1523
|
+
default 40000). The daemon's final stream chunk already carries every
|
|
1524
|
+
field it reads, so it works against any install with no log access.
|
|
1525
|
+
- **Cache-miss receipts.** The flight recorder records the outcome of every
|
|
1526
|
+
generation-final snapshot attempt (mode, reason, both stream lengths, the
|
|
1527
|
+
divergence token); the committed-reasoning canonicalizer records why it
|
|
1528
|
+
stood aside; `MTPLX_DEBUG_POSTCOMMIT_MISMATCH_DIR=<dir>` dumps the
|
|
1529
|
+
generated and re-rendered token windows around a refused snapshot.
|
|
1530
|
+
- **Light appearance for the app (#428).** A curated cream palette
|
|
1531
|
+
(cream ground, warm ink type, graphite chrome, matching code colors),
|
|
1532
|
+
not an inversion; every pair is gated on WCAG AA and the dark palette
|
|
1533
|
+
is byte-identical to 2.10. Settings > Behavior > Appearance: System,
|
|
1534
|
+
Dark, Light. The default stays Dark.
|
|
1535
|
+
- **The app in twelve languages.** Onboarding opens with a searchable
|
|
1536
|
+
language step with flags (English, Simplified Chinese, Spanish, Hindi,
|
|
1537
|
+
Arabic, Brazilian Portuguese, French, Russian, Japanese, German, Korean,
|
|
1538
|
+
Indonesian); the same picker in Settings switches the app live, and
|
|
1539
|
+
Arabic lays the app out right-to-left. Every string goes through the
|
|
1540
|
+
localization layer with English as the fallback. Installs that finished
|
|
1541
|
+
onboarding on an earlier version are asked once, on the first launch
|
|
1542
|
+
after updating, so they learn the app speaks their language and where
|
|
1543
|
+
to change it; the pick applies live and the prompt never returns.
|
|
1544
|
+
- **Paste images and files into chat.** ⌘V in the composer attaches
|
|
1545
|
+
what is on the clipboard: a screenshot or an image copied from a
|
|
1546
|
+
browser or Preview becomes an image attachment, a file copied in
|
|
1547
|
+
Finder becomes a document attachment (PDF, docx, md, txt, or an image
|
|
1548
|
+
file), exactly like the paperclip and drag-and-drop. Text still pastes
|
|
1549
|
+
as text. On a model without vision, a pasted or dropped image shows a
|
|
1550
|
+
card that says the model can't see images instead of riding along
|
|
1551
|
+
silently.
|
|
1552
|
+
- **Choose a model folder.** The model picker's add row takes a local
|
|
1553
|
+
folder as well as a Hugging Face `org/repo`: a native folder chooser
|
|
1554
|
+
(or a typed path) is checked for a complete MTPLX install, remembered
|
|
1555
|
+
as a row in the picker, and selected in one step, so switching between
|
|
1556
|
+
models never means typing the directory again. The onboarding
|
|
1557
|
+
local-folder step gets the same chooser, and a folder that carries a
|
|
1558
|
+
catalog model's name is recognised as that model at a different
|
|
1559
|
+
location.
|
|
1560
|
+
- **`--allow-swap` serves past the machine fit again (#427).** 2.10
|
|
1561
|
+
capped the default window at the memory plan's fit and refused prompts
|
|
1562
|
+
past it with a 507; operators who ran 2.9.x past the fit on purpose
|
|
1563
|
+
(32 GB Macs, swap accepted) lost that option. With the flag the
|
|
1564
|
+
default window is the model's own maximum, prompts past the fit are
|
|
1565
|
+
admitted, the banner and `/health` say so, and the plan still reports
|
|
1566
|
+
the overcommit. `MTPLX_ALLOW_SWAP=1` is the env form for the app and
|
|
1567
|
+
`mtplx start`.
|
|
1568
|
+
- **Flash-Next speed lane on by default (PR #391 by @davidtai).** The
|
|
1569
|
+
compiled fixed-M4 verifier, batched target distributions, compiled MTP
|
|
1570
|
+
prepare, relaxed draft ties and the fused K/V gather are the family
|
|
1571
|
+
defaults for the Flash-Next geometry, with a 32-row rows-gather fence
|
|
1572
|
+
and FR-Spec drafting on packs whose lm_head has the Q8 group-64 layout
|
|
1573
|
+
it needs. Same-hour pairs against 2.10.2 with the copy lane on: round
|
|
1574
|
+
time -12% at 16k, -18.5% at 100k, -14% at 206k. A per-request memory
|
|
1575
|
+
gate hands prompts whose bank promotion would not fit back to the
|
|
1576
|
+
plain verify (`MTPLX_QWEN4_FIXED_M4_MAX_CONTEXT` is the operator belt).
|
|
1577
|
+
- **Flash-Next: David's decode and prefill stack, on by default (PR #391 by
|
|
1578
|
+
@davidtai).** The second half of the PR, ported commit by commit under his
|
|
1579
|
+
name and measured on the coding cells: the two-kernel MoE routing head, the
|
|
1580
|
+
exact op diet, block verification in the accept loop, the fused QSA rope
|
|
1581
|
+
glue inside the compiled verify body, the M4 kernel trio, the PLE prefill
|
|
1582
|
+
lookahead (chunk k+1's n-gram rows gathered under chunk k, also on the
|
|
1583
|
+
restored-suffix prefill of warm agent turns), the first chunk's gather at
|
|
1584
|
+
request arrival, the n-gram table pre-read at model load
|
|
1585
|
+
(`--ngram-prewarm auto|all|off|<GiB>`), and the session bank's boundary
|
|
1586
|
+
shedding and protected terminal. The pre-scatter draft read serves greedy
|
|
1587
|
+
requests. Every item is exact (token-identical at temperature 0) and every
|
|
1588
|
+
key yields to an explicit export, `=0` included. 16k: 41.8 to 39.2 ms per
|
|
1589
|
+
round (-6.2%, -18.6% against 2.10.2), 63.2 to 68.4 tok/s; 100k: 46.7 to
|
|
1590
|
+
43.4 ms (-7.2%, -20.4% against 2.10.2), 54.0 to 60.9 tok/s, TTFT 117.0 to
|
|
1591
|
+
113.2 s; 206k: not re-measured after the release-night harness crash
|
|
1592
|
+
(2.11 as built: 56.2 ms, 44.8 tok/s); peak memory within 2.3 GB of 2.11 as
|
|
1593
|
+
built at 100k, flat at 16k.
|
|
1594
|
+
- **27B flash-decoding verify route on in turbo** (`MTPLX_NAX_FLASH_ROUTE=1`,
|
|
1595
|
+
dim-split block defaults from the 72.7k and 128k sweeps).
|
|
1596
|
+
- **Opt-in Steel sparse-GQA prefill consumer for M3** (PR #423 by
|
|
1597
|
+
@humanrouter), shipped as a native extension, not yet in the app bundle.
|
|
1598
|
+
- **StreamScope two-turn copy-lane arm** so the streaming gate covers
|
|
1599
|
+
block-sized emits.
|
|
1600
|
+
- **The n-gram pre-read reserves the engine's growth to its budget.** The
|
|
1601
|
+
automatic pre-read at model load subtracts max(KV estimate, engine budget
|
|
1602
|
+
minus the weights on disk) from free memory instead of the KV estimate
|
|
1603
|
+
alone; on a 128 GB Mac it drops from 23.4 GiB to 14.2 GiB, the amount the
|
|
1604
|
+
page cache can keep once a long prefill has grown the engine to its
|
|
1605
|
+
envelope. `tests/test_ngram_prewarm_reservation.py`.
|
|
1606
|
+
- **Settings > Memory card (#431 @Journey0723, #427 @localbylocal).** A memory
|
|
1607
|
+
limit in GB and an allow-swap switch, carried into the daemon as
|
|
1608
|
+
`MTPLX_MEMORY_LIMIT_BYTES` / `MTPLX_ALLOW_SWAP`; the card shows the plan the
|
|
1609
|
+
engine computed for this Mac.
|
|
1610
|
+
- **`finish_stop_origin` in the public stats and the request log (#414, PR
|
|
1611
|
+
#426 by @atirna).** A stop's commit path is diagnosable from the log alone.
|
|
1612
|
+
- **`/health` carries `qwen4_install_reports`.** The stage-3 kernel report,
|
|
1613
|
+
the rope glue's per-item verdicts and the n-gram pre-read plan, so a
|
|
1614
|
+
default's engagement is readable without the serve log.
|
|
1615
|
+
- **`--cors-origin` / `MTPLX_CORS_ORIGINS`.** Browser pages MTPLX serves
|
|
1616
|
+
itself are always allowed; a browser front-end on another origin now
|
|
1617
|
+
needs its origin listed here (repeatable flag, or a comma-separated
|
|
1618
|
+
env var the daemon inherits from `mtplx start`). `/admin` and the
|
|
1619
|
+
sign-in routes stay same-origin only regardless. See the CORS entry
|
|
1620
|
+
under Fixed.
|
|
1621
|
+
- **`mtplx doctor` reports the Hugging Face token `mtplx pull` will use**
|
|
1622
|
+
(`hugging face token: from HF_TOKEN` / `` from `hf auth login` `` /
|
|
1623
|
+
`none`), and the JSON report carries `token_source`, `token_used_by_pull`
|
|
1624
|
+
and `token_policy`.
|
|
1625
|
+
|
|
1626
|
+
### Fixed
|
|
1627
|
+
|
|
1628
|
+
- **The pre-prefill memory guard no longer evicts a session's own
|
|
1629
|
+
restorable entry.** The 2.10.2 guard (#415) estimated a prompt's
|
|
1630
|
+
reusable prefix by exact match only. A follow-up turn the session bank
|
|
1631
|
+
serves by block prefix, such as the turn after a forced tool call, read
|
|
1632
|
+
as a full miss near the memory line and the guard cleared the session as
|
|
1633
|
+
superseded before the restore ran: on a 128 GB Mac serving Flash-Next, a
|
|
1634
|
+
41,901-token turn re-prefilled cold in 54 s with a 41,391-token restore
|
|
1635
|
+
available. The guard now asks the bank the same question the restore
|
|
1636
|
+
does (`SessionBank.longest_shared_prefix_tokens`, block-aligned) and pins
|
|
1637
|
+
such entries; its receipt carries `reusable_prefix_mode`. Found by the
|
|
1638
|
+
release script's agent-session gate.
|
|
1639
|
+
|
|
1640
|
+
- **Agent sessions on Flash-Next slowed to ~20 tok/s and stalled 5-8 s before
|
|
1641
|
+
every tool turn.** A 43k-token OpenCode session measured on the live daemon
|
|
1642
|
+
lost 146 s of a 14-minute task to three engine defects, none of them decode
|
|
1643
|
+
(the decode rounds ran at 65-70 tok/s throughout; the "21 tok/s" was dead
|
|
1644
|
+
time divided into 200-token tool turns):
|
|
1645
|
+
- Every request started the n-gram first-chunk gather at arrival
|
|
1646
|
+
(`MTPLX_QWEN4_PLE_FIRST_GATHER_EARLY`, on by default since the #391 port)
|
|
1647
|
+
and chained a page-warm of the *rest of the prompt's* rows — on warm
|
|
1648
|
+
bank turns whose prefill is 20-400 tokens, all of it waste (650k rows at
|
|
1649
|
+
43k), and the owner thread then blocked on the gather at scope exit:
|
|
1650
|
+
5-7.5 s per turn once memory pressure had evicted the table, charged to
|
|
1651
|
+
nothing in the receipt. The gather is declined when a RAM bank entry can
|
|
1652
|
+
already serve past the first chunk, and an unconsumed gather is never
|
|
1653
|
+
waited for. Warm-turn dead time 5-7.5 s → 0.01 s; the memory-pressure
|
|
1654
|
+
notices went with the page-warm storm.
|
|
1655
|
+
- The bank hydrated its own SSD twin on every warm turn (0.6 s each): the
|
|
1656
|
+
exact-prefix entry was excluded from the "RAM already serves this" bar,
|
|
1657
|
+
so the bar read 0 and the cold row was decoded unread.
|
|
1658
|
+
- The generation-final snapshot of a tool-call turn was refused on
|
|
1659
|
+
every OpenCode turn in the daemon's life (83/83 went to the retokenizing
|
|
1660
|
+
GPU re-prefill): tool arguments are not byte-stable through parse →
|
|
1661
|
+
client → re-render (a file ending in `\n` came back one token short),
|
|
1662
|
+
and a turn ending in `tool_calls` never advanced the session's committed
|
|
1663
|
+
stream, so the committed-think substitution could not apply to the
|
|
1664
|
+
turns that needed it. The committed post-think body (text + tool-call
|
|
1665
|
+
markup as generated) is now substituted into the re-render when the
|
|
1666
|
+
turn's calls match, and `tool_calls` finishes commit like `stop`. Tool
|
|
1667
|
+
rounds bank in O(1) and the next turn restores the whole turn (measured:
|
|
1668
|
+
write-turn follow-up 3,535 tokens re-prefilled / 3.7 s → 20 tokens /
|
|
1669
|
+
0.12 s).
|
|
1670
|
+
- A pending postcommit that was still prefilling when the 30 s bound
|
|
1671
|
+
expired was aborted and the request re-prefilled the same tokens from
|
|
1672
|
+
scratch (30 s waited + 38 s re-prefill after a 25k-token turn). The
|
|
1673
|
+
wait now extends while the job heartbeats at chunk boundaries and
|
|
1674
|
+
abandons only a silent job (`MTPLX_POSTCOMMIT_WAIT_STALL_S`, 15 s;
|
|
1675
|
+
ceiling `MTPLX_POSTCOMMIT_WAIT_CEILING_S`, 600 s).
|
|
1676
|
+
- A forced `tool_choice` rewrote the system contract and the bank
|
|
1677
|
+
identity, so one forced round re-prefilled a 41k session cold twice
|
|
1678
|
+
(41 s + 44 s). The clause now rides a transient trailing user turn like
|
|
1679
|
+
every other per-request steering text, and the bank fingerprint no
|
|
1680
|
+
longer carries tail-only contracts (forced choice, post-tool answer,
|
|
1681
|
+
read-only force-answer, Pi convergence): a transition round keeps the
|
|
1682
|
+
session's prefix. Forced round at 41k: 0.58 s.
|
|
1683
|
+
- A cold prompt of ~30k+ tokens could end with `finish_reason: "error"`
|
|
1684
|
+
after its whole prefill: the PLE lookahead's engagement verdict, a
|
|
1685
|
+
benchmark-arm assertion, raised inside a user request when the sidecar
|
|
1686
|
+
declined a low-entropy first chunk. Serving records the verdict
|
|
1687
|
+
(counter, `last_scope_status`, one warning); `MTPLX_QWEN4_PLE_PREFILL_
|
|
1688
|
+
LOOKAHEAD_STRICT=1` keeps the raise for measurement arms.
|
|
1689
|
+
Receipts: `scripts/agent_session_gate.py` (new release gate, below) at 40k
|
|
1690
|
+
passes end to end — warm turns 0.15 s TTFT / 0.01 s dead time / 66-84
|
|
1691
|
+
tok/s, auto tool round banked in O(1), forced round warm. OpenCode CLI,
|
|
1692
|
+
same task, bank on vs off: aggregate decode 87.1 vs 83.8 tok/s (the bank
|
|
1693
|
+
does not touch decode), total TTFT 4.5 s vs 14.0 s over three turns.
|
|
1694
|
+
- **A second of dead air before every reply after a short pause.** macOS
|
|
1695
|
+
drops an idle process's GPU residency about 2.5 s after its last Metal
|
|
1696
|
+
command and rebuilds it on the next one at ~9 ms per GiB, so on
|
|
1697
|
+
Flash-Next (77 GiB of weights) the first prefill after any pause of a
|
|
1698
|
+
few seconds — every chat turn, every agent tool-call round trip — paid
|
|
1699
|
+
~0.75-1.0 s before the first token (measured: a "hi" 1.17 s from the
|
|
1700
|
+
app, 0.08 s back-to-back). The engine now keeps its working set
|
|
1701
|
+
resident with a sub-millisecond kernel on the model queue once per
|
|
1702
|
+
second while it is attentive (a request completed in the last 10
|
|
1703
|
+
minutes, `MTPLX_GPU_KEEPALIVE_ATTENTIVE_S`), then parks. Same "hi"
|
|
1704
|
+
after 3-90 s of quiet: server time-to-first-token 0.083 s, first text
|
|
1705
|
+
on screen in the app 0.26 s (was 0.87 s). `/health` reports
|
|
1706
|
+
`gpu_keepalive`; every request record carries whether it started warm.
|
|
1707
|
+
`MTPLX_GPU_KEEPALIVE=0` disables it.
|
|
1708
|
+
- **Replies typed in lurches on Flash-Next follow-ups.** The engine writes
|
|
1709
|
+
one stream frame per committed token, so a decode round lands as two
|
|
1710
|
+
frames a few milliseconds apart every ~40 ms and a context-copy block
|
|
1711
|
+
as a burst. The app's typewriter estimated the round gap from every
|
|
1712
|
+
frame, the 3 ms intra-round gaps pulled it to ~19 ms, and each round
|
|
1713
|
+
was then revealed on the next display tick and followed by idle frames
|
|
1714
|
+
— text updated on 42% of frames, in chunks, at the raw round cadence.
|
|
1715
|
+
Frames within one display frame now count as one arrival, the estimate
|
|
1716
|
+
tracks the real round gap, and a round types across it (a 100-character
|
|
1717
|
+
copy block over four frames instead of one).
|
|
1718
|
+
- **Any web page could drive the local model.** The server ran CORS with a
|
|
1719
|
+
wildcard origin and credentials, so on the default keyless localhost bind
|
|
1720
|
+
a page in any tab could POST to `/v1/*`, read the answers, list and clear
|
|
1721
|
+
sessions under `/admin/*`, and keep the GPU busy. Browser requests are
|
|
1722
|
+
now same-origin by default (the page's `Origin` must match the `Host` it
|
|
1723
|
+
used), allowlisted origins reach the API but never `/admin` or sign-in,
|
|
1724
|
+
every other origin gets a 403 with no CORS headers, and requests without
|
|
1725
|
+
an `Origin` header (the app, OpenCode, Pi, Claude Code, Cline, curl) are
|
|
1726
|
+
untouched. The browser-auth cookie is `Secure` over https.
|
|
1727
|
+
- **The API key stays out of URLs, argv and the Logs pane.** Opening the
|
|
1728
|
+
browser dashboard from the app used `/mtplx/browser-auth?mtplx_api_key=…`,
|
|
1729
|
+
so the key landed in browser history; the app now asks the daemon for a
|
|
1730
|
+
single-use 60 s ticket (`POST /mtplx/browser-auth/ticket`) and opens the
|
|
1731
|
+
URL it returns, falling back to the old form only when a daemon predates
|
|
1732
|
+
the route. The dashboard's own sign-in posts the key in a same-origin
|
|
1733
|
+
body (`POST /mtplx/browser-auth`) and a 401 shows a sign-in prompt instead
|
|
1734
|
+
of "Connection to MTPLX lost". The daemon reads its key from a user-only
|
|
1735
|
+
(0600) file under Application Support instead of `--api-key <key>` on
|
|
1736
|
+
argv, and the launched command line the app logs masks every `*-key`,
|
|
1737
|
+
`*-token`, `*-secret` and `*-password` value.
|
|
1738
|
+
- **Play, Restart and first launch no longer wait on mtplx.com.** Every
|
|
1739
|
+
daemon launch awaited the release manifest on a 60 s timeout; offline,
|
|
1740
|
+
firewalled and captive-portal Macs sat in "Launching" for a minute per
|
|
1741
|
+
Play. The runtime decision is local now, the manifest fetch is bounded to
|
|
1742
|
+
3 s and refreshes the About card in the background, and first-run
|
|
1743
|
+
onboarding is decided from the saved settings alone.
|
|
1744
|
+
- **The model-pack Update button no longer freezes the window** while it
|
|
1745
|
+
resolves the runtime, walks the pack and spawns the CLI; that work runs
|
|
1746
|
+
off the main actor.
|
|
1747
|
+
- **A settings file with one bad value keeps every other setting.** One
|
|
1748
|
+
wrong-typed field in `settings.json` (a hand edit, a downgrade) used to
|
|
1749
|
+
make the whole file undecodable, re-run onboarding, and overwrite custom
|
|
1750
|
+
models, the API key and tuned records with defaults. Bad fields now fall
|
|
1751
|
+
back individually (logged with their path), a malformed custom model or
|
|
1752
|
+
tune record is skipped while its siblings load, and a file that cannot
|
|
1753
|
+
be read at all is kept beside itself as `settings.json.unreadable-<stamp>`
|
|
1754
|
+
with a banner and Reveal in Finder.
|
|
1755
|
+
- **An unopenable chat store is kept, not silently swapped for memory.**
|
|
1756
|
+
`chats.store` and its `-wal`/`-shm` sidecars are renamed beside
|
|
1757
|
+
themselves, a fresh store starts, and the sidebar says so with Reveal in
|
|
1758
|
+
Finder; only if no store can be created does the session run in memory,
|
|
1759
|
+
and then the sidebar says that too. The chat models carry a versioned
|
|
1760
|
+
schema so the next model change has a migration path.
|
|
1761
|
+
- **Unsaved Settings edits survive switching tabs.** The draft lives above
|
|
1762
|
+
the tab now; an "Unsaved changes" row offers Save / Apply + Restart and
|
|
1763
|
+
Revert. Mode still saves on pick.
|
|
1764
|
+
- **A failed reply shows the server's error and offers Retry.** The
|
|
1765
|
+
daemon's `finish_reason: "error"` frame (memory guard, context overflow,
|
|
1766
|
+
tool-loop exception) was decoded as an ordinary finish, so the turn read
|
|
1767
|
+
"Interrupted reply" or "No visible answer generated." with no message and
|
|
1768
|
+
no Retry. The message is shown, persisted with the turn, and labels the
|
|
1769
|
+
settled bubble "Failed: <message>".
|
|
1770
|
+
- **A reply the daemon never finished is filed as incomplete.** A stream
|
|
1771
|
+
that ended without its terminal chunk (process death, dropped
|
|
1772
|
+
connection) was persisted as a complete answer with `finish_reason:
|
|
1773
|
+
"stop"`. It is now `"incomplete"`, shown as interrupted, with Retry.
|
|
1774
|
+
- **Chats are auto-titled in every language.** The title guard compared
|
|
1775
|
+
against the English literal "New Chat", so non-English users kept the
|
|
1776
|
+
placeholder forever; rows the old guard left untitled are named at
|
|
1777
|
+
launch from their first message.
|
|
1778
|
+
- **A failed web search or fetch is recorded as a failure.** Offline or
|
|
1779
|
+
blocked providers came back as an empty result set marked success, the
|
|
1780
|
+
model was told "No results", and a failed fetch still added a source.
|
|
1781
|
+
Failures are marked on the live strip and the persisted trace, the model
|
|
1782
|
+
is told the tool failed, and no phantom source is added.
|
|
1783
|
+
- **Esc on the chat surface does one thing.** It stops a streaming reply,
|
|
1784
|
+
otherwise closes the chat; Stop Generating in the menu moves to ⌘⇧. so
|
|
1785
|
+
two Esc bindings no longer race.
|
|
1786
|
+
- **Attaching a file no longer freezes the app.** Extraction (PDF page
|
|
1787
|
+
walk, docx unzip, image decode) runs off the main actor with a per-card
|
|
1788
|
+
spinner; a file that cannot be read stays on the strip with the reason
|
|
1789
|
+
instead of vanishing. New caps: 500 PDF pages and 200,000 characters per
|
|
1790
|
+
attachment, noted on the card and in the text the model sees.
|
|
1791
|
+
- **A badly typed value in `~/.mtplx/config.toml` no longer bricks every
|
|
1792
|
+
command.** `context_window = "64k"` made `status`, `list`, `config show`
|
|
1793
|
+
and everything else exit with a traceback; that one key now falls back
|
|
1794
|
+
with a one-line warning naming it, and `mtplx config set` refuses a bad
|
|
1795
|
+
value plainly without writing.
|
|
1796
|
+
- **Ctrl-C at a prompt exits quietly** with status 130 instead of a
|
|
1797
|
+
traceback.
|
|
1798
|
+
- **`mtplx bench` works from any directory.** Prompt suites resolve inside
|
|
1799
|
+
the installed package; a bare `mtplx bench` lists its actions.
|
|
1800
|
+
- **Prose that quotes `[Calling tool:` no longer swallows the answer.** The
|
|
1801
|
+
streaming filter held everything after the marker until a `]` arrived and
|
|
1802
|
+
then dropped it at finish; the hold is now bounded by the call's own
|
|
1803
|
+
grammar, so real calls (including multi-line JSON arguments) are still
|
|
1804
|
+
hidden and ordinary text streams through.
|
|
1805
|
+
- **The attached terminal chat gives up on a daemon that stops
|
|
1806
|
+
responding** (5 s to connect, 120 s with nothing on the wire) with a
|
|
1807
|
+
plain error and exit 1 instead of hanging forever.
|
|
1808
|
+
- **One Hugging Face token policy.** `mtplx pull`, update checks, `inspect`
|
|
1809
|
+
and `doctor` agree: `HF_TOKEN`/`HUGGING_FACE_HUB_TOKEN`, then the token
|
|
1810
|
+
stored by `hf auth login`, else anonymous; a stored token the Hub refuses
|
|
1811
|
+
is retried anonymously so a stale login never breaks a public pull. Pulls
|
|
1812
|
+
previously ignored the login token that `doctor` reported as present.
|
|
1813
|
+
- **Passwordless-sudo setup validates the rule before installing it.** The
|
|
1814
|
+
rule is character-checked (an unescaped space silently turned the rest of
|
|
1815
|
+
a path into an argument, and visudo accepted it), parsed by `visudo -c -f`
|
|
1816
|
+
on a temp file, and installed by one privileged script that names the
|
|
1817
|
+
`thermalforge` binary fan control actually runs (`~/.mtplx/bin`), not a
|
|
1818
|
+
PATH copy; a missing binary is reported plainly; every sudo call is
|
|
1819
|
+
bounded and a no-tty session says to run `mtplx max --grant-sudo` in a
|
|
1820
|
+
terminal.
|
|
1821
|
+
- **The fan-restore sidecar clears its marker only after the fans verifiably
|
|
1822
|
+
return to auto**, after the socket path and the sudo fallback alike, and
|
|
1823
|
+
writes what it did to `~/.mtplx/logs/thermal-sidecar.log`.
|
|
1824
|
+
- **`mtplx models --update` never removes a live file before the new pack
|
|
1825
|
+
is complete.** Changed files are set aside as `.stale`, removed only after
|
|
1826
|
+
a complete download, and restored on any failure or interruption; a kill
|
|
1827
|
+
mid-update is recovered on the next run.
|
|
1828
|
+
- **The curl installer no longer writes into Homebrew's bin.** It used to
|
|
1829
|
+
`cp` over the `mtplx` symlink there, replacing the Cellar program. The
|
|
1830
|
+
global launcher is opt-in (`MTPLX_GLOBAL_BIN`), a launcher this installer
|
|
1831
|
+
did not create is never replaced, and the same rule covers the legacy
|
|
1832
|
+
preview installer.
|
|
1833
|
+
- **An `opencode.json` or Pi `models.json` MTPLX cannot read is left
|
|
1834
|
+
alone.** Both apps accept JSONC (comments, trailing commas), and MTPLX
|
|
1835
|
+
now reads them the same way and merges; a file that still does not parse
|
|
1836
|
+
is left untouched with a message naming the file and position instead of
|
|
1837
|
+
being moved aside and replaced. A rewrite keeps the previous file as
|
|
1838
|
+
`<name>.before-mtplx-<stamp>.bak` and `connect` prints where it wrote.
|
|
1839
|
+
- **First-run routing refuses what cannot run.** An Intel Mac was told
|
|
1840
|
+
"selected because this is not Apple Silicon" and handed a 27B download;
|
|
1841
|
+
a Mac whose memory could not be read got the 27B; every Mac under 32 GiB
|
|
1842
|
+
got the 9B. Intel and too-small Macs now get one plain sentence and exit
|
|
1843
|
+
1 before any download, unreadable memory selects the smallest pack and
|
|
1844
|
+
says why, and under 32 GiB the CLI names the same pack the app's picker
|
|
1845
|
+
lists first (the 9B from 16 GiB, the 4B below that).
|
|
1846
|
+
- **The browser dashboard:** the Speculative tab's hard-coded vLLM
|
|
1847
|
+
comparison is gone; settings sliders send only the keys you moved and
|
|
1848
|
+
follow the server otherwise; the live TPS gauge goes idle when the server
|
|
1849
|
+
does instead of pinning the last request's speed.
|
|
1850
|
+
- **A resumed download can no longer splice a new commit's tail onto a
|
|
1851
|
+
stale partial.** `mtplx pull`, and the app's downloader behind it,
|
|
1852
|
+
range-resumed any `*.incomplete` partial next to the target whichever
|
|
1853
|
+
commit had written it and accepted the result on size alone, so a pack
|
|
1854
|
+
repaired in place on the Hub could come back as a corrupt file that
|
|
1855
|
+
loaded. A transfer marker now records the blob every file is fetched
|
|
1856
|
+
from: a partial whose blob changed, or one nothing vouches for, is
|
|
1857
|
+
discarded; a landed file whose recorded blob changed is refetched; and
|
|
1858
|
+
every LFS file is hashed as it lands against the sha256 the Hub
|
|
1859
|
+
publishes, so a mismatch discards the partial with a plain message
|
|
1860
|
+
instead of installing it. The progress figures are unchanged.
|
|
1861
|
+
- **Published packs no longer name the machine that forged them.**
|
|
1862
|
+
`mtplx forge` stamped the local trunk directory it was pointed at into
|
|
1863
|
+
`mtplx_runtime.json` (`base_trunk`, `forge_provenance.source_repo`, the
|
|
1864
|
+
forge inputs), and the scrubber written to prevent that had no caller,
|
|
1865
|
+
so the flagship 27B packs carried a home directory. The stamper names a
|
|
1866
|
+
local trunk by its Hub identity (the pull marker's repo id and commit,
|
|
1867
|
+
or the `owner--name` cache layout), `forge publish` uploads scrubbed
|
|
1868
|
+
copies of every top-level JSON document that carries a local path, and
|
|
1869
|
+
`mtplx model publish-check` gains a `no_local_paths` gate with
|
|
1870
|
+
`--scrub` to rewrite staged documents in place. The scrubber itself no
|
|
1871
|
+
longer mistakes every string that starts with a slash for a path.
|
|
1872
|
+
- **The browser dashboard's Thermal tab no longer shows an internal
|
|
1873
|
+
benchmarking rule.** On a default install (thermal polling off) every
|
|
1874
|
+
generation raised a banner saying that per the project's Universal
|
|
1875
|
+
Thermal Rule model work should run under verified max-fan mode. The
|
|
1876
|
+
banner is gone; the fan panel keeps its note about
|
|
1877
|
+
`--enable-thermal-poll`.
|
|
1878
|
+
- **macOS 27 no longer answers long prompts with a 500 (#404, #405, #407).**
|
|
1879
|
+
The macOS 27 Metal Performance Primitives header rejects the
|
|
1880
|
+
address-space-qualified cooperative-tensor operands the QSA sparse
|
|
1881
|
+
prefill kernels and the 27B flash-decoding route used, so 2.10.x on the
|
|
1882
|
+
27 betas failed every prompt past the ~32K sparse-prefill crossover
|
|
1883
|
+
mid-request ("Unable to build metal library from source"). The seven
|
|
1884
|
+
kernel sites use the address-space-neutral operand types, and a startup
|
|
1885
|
+
probe dispatches the real sparse-prefill pipeline once, degrading to
|
|
1886
|
+
dense prefill with a diagnostic on an SDK that refuses it. Credit
|
|
1887
|
+
@mrmurphy (first patch), @sunnybluesea (root cause, three-site sweep,
|
|
1888
|
+
macOS 27 receipts), reporters @DigiJoe79 and @rameshn007.
|
|
1889
|
+
- **An explicit `MTPLX_MEMORY_LIMIT_BYTES` is the engine budget both ways
|
|
1890
|
+
(#443 @yermakovm).** The planner clamped the configured Metal limit under
|
|
1891
|
+
its own 75% envelope, so a 96 GB Mac serving the 69.2 GiB Flash-Next
|
|
1892
|
+
Bare-Speed pack was refused as "does not fit" (72 GiB budget against a
|
|
1893
|
+
73.3 GiB minimum) even under a limit of 80G that runs it. An operator-set
|
|
1894
|
+
limit now plans the engine budget up to the machine's RAM, the banner
|
|
1895
|
+
says "(Metal limit)", and the "does not fit" note names the lever. A
|
|
1896
|
+
`--memory-budget` below the machine still wins, so a simulated smaller
|
|
1897
|
+
seat stays small.
|
|
1898
|
+
- **Flash-Next no longer 500s under concurrency in the `ar_batch` lane (#420).**
|
|
1899
|
+
mlx-lm's batch generator merges every batched prompt's caches and the
|
|
1900
|
+
family's QSA cache has no merge, so two concurrent requests under
|
|
1901
|
+
`--scheduler-mode ar_batch` (the app's lane) raised an unhandled
|
|
1902
|
+
`ValueError` while sequential requests were fine. The lane now probes
|
|
1903
|
+
the model's cache family at startup, says so in the banner and in
|
|
1904
|
+
`/health` (`scheduler.ar_batch_unavailable_reason`), and serves
|
|
1905
|
+
concurrent requests one at a time on the solo lane instead. The 27B
|
|
1906
|
+
keeps batching. `MTPLX_AR_BATCH_CACHE_PROBE=0` skips the probe.
|
|
1907
|
+
- **A client-capped one-token answer is diagnosable from the serve log
|
|
1908
|
+
(#436).** Pi caps `max_tokens` at its model `contextWindow` minus a
|
|
1909
|
+
chars/4 estimate of the transcript, and sends `max_tokens=1` when the
|
|
1910
|
+
estimate overflows; the answer then stops after one token, often inside
|
|
1911
|
+
a tool call, and Pi reports a truncated response. The server now logs
|
|
1912
|
+
one WARNING naming the client's cap, and the `mtplx_openai_generation`
|
|
1913
|
+
trace line carries `max_tokens`, `effective_max_tokens` and
|
|
1914
|
+
`finish_reason`.
|
|
1915
|
+
- **Anthropic `/v1/messages` image blocks reach the vision tower (#441).**
|
|
1916
|
+
`image` blocks (base64 or URL, including ones nested inside a
|
|
1917
|
+
`tool_result`) become image parts instead of base64 prose, so Claude
|
|
1918
|
+
Code's pasted screenshots and its Read tool on image files are seen
|
|
1919
|
+
instead of hallucinated at 13x to 52x the token cost. Text-only
|
|
1920
|
+
requests render byte-identically; `count_tokens` counts the vision
|
|
1921
|
+
placeholder the way the chat path does.
|
|
1922
|
+
- **Copy-lane streaming no longer pastes and freezes.** The server
|
|
1923
|
+
releases block-sized token batches piece by piece across the round
|
|
1924
|
+
(`MTPLX_STREAM_PACER`, default on; StreamScope copy-lane arm burst p95
|
|
1925
|
+
58 to 9 characters), and the app's typewriter rates arrivals over a
|
|
1926
|
+
wall-clock window and types each block across its round
|
|
1927
|
+
(95th-percentile flush 65 to 25 characters, longest on-screen pause
|
|
1928
|
+
767 to 233 ms on the same flow).
|
|
1929
|
+
- **Fully accepted copy blocks skip the recurrent-state replay** in the
|
|
1930
|
+
Flash-Next family commit; the Route Tape records every copy-lane round.
|
|
1931
|
+
- **`reasoning_effort` accepts the off-ish values some clients send**
|
|
1932
|
+
(PR #433 by @sypsyp97).
|
|
1933
|
+
- **App CPU:** the decode chip publishes only on change, metrics
|
|
1934
|
+
snapshots slow to 500 ms while a turn streams, and the redundant
|
|
1935
|
+
auto-scroll task is gone.
|
|
1936
|
+
- **The chat composer scrolls past ten lines instead of jumping to the top
|
|
1937
|
+
(#424, PR #437 by @MohammedThowfiq).**
|
|
1938
|
+
- **The Performance mode survives a model restart and the launch logs the
|
|
1939
|
+
effective scheduling (#398 @variablefate).** The picker is a saved global;
|
|
1940
|
+
Settings shows "Running now: ..." and the log pane carries the
|
|
1941
|
+
`--scheduler-mode` / `--batching-preset` the daemon launched with.
|
|
1942
|
+
- **A small request from another session no longer kills a marathon
|
|
1943
|
+
postcommit (#432 @nomishbhardwaj).** `MTPLX_POSTCOMMIT_MARATHON_PROTECT_TOKENS`
|
|
1944
|
+
reaches the cross-session abort with a bounded grace (one 30 s window per
|
|
1945
|
+
landed commit, keyed to the session), and the pending record is seeded
|
|
1946
|
+
from the committed frontier at arm time.
|
|
1947
|
+
- **A draining MTPLX is not mistaken for a foreign process on its port, and a
|
|
1948
|
+
configured port never moves to +1 on a transient occupant (#409 @kmei3560;
|
|
1949
|
+
CLI lane; the app's own port handling is unchanged in this release).**
|
|
1950
|
+
- **A streamed response closes within 30 s of its last token when another
|
|
1951
|
+
request's prefill is queued ahead of its session snapshot (#425
|
|
1952
|
+
@66duke66).** `MTPLX_STREAM_COMMIT_WAIT_MAX_S` bounds the post-generation
|
|
1953
|
+
commit wait; the snapshot lands in the background and the next turn waits
|
|
1954
|
+
for it through the pending-postcommit path.
|
|
1955
|
+
- **The compiled fixed-M4 lane no longer skips long prompts because the
|
|
1956
|
+
allocator cache looked like live memory.** After a 100k prefill on a 128 GB
|
|
1957
|
+
Mac the gate read the freed prefill scratch held by the allocator as live
|
|
1958
|
+
and fell back to the plain verify; it now releases that cache when it
|
|
1959
|
+
stands between the request and the lane and re-reads.
|
|
1960
|
+
- **The download panel counts only the files the repo ships.** Progress
|
|
1961
|
+
was the byte count of the whole model folder, so shards from a
|
|
1962
|
+
superseded revision or staging leftovers from an interrupted Hugging
|
|
1963
|
+
Face transfer counted as downloaded: the panel read 47.38 GB of
|
|
1964
|
+
18.52 GB at 100 percent while still downloading. The daemon's progress
|
|
1965
|
+
events, the resume decision and the disk headroom check now use the
|
|
1966
|
+
repo's manifest, and the app never prints past the repo size. The
|
|
1967
|
+
leftovers are reported instead (`stale_bytes`, `stale_files` on the
|
|
1968
|
+
events and in `mtplx pull`), and a stray `*.incomplete` next to landed
|
|
1969
|
+
weights no longer keeps a byte-complete folder "partial" through every
|
|
1970
|
+
Retry: only a needed file's partial with no final copy is a transfer.
|
|
1971
|
+
|
|
1972
|
+
## [2.10.2] - 2026-09-01
|
|
1973
|
+
|
|
1974
|
+
### Fixed
|
|
1975
|
+
|
|
1976
|
+
- **Memory refusals are now honest and proactive (#415).** Before a large
|
|
1977
|
+
prefill is admitted, the server projects its footprint, proactively
|
|
1978
|
+
clears superseded SessionBank entries, and, when the request genuinely
|
|
1979
|
+
cannot fit, answers a structured HTTP 507 upfront instead of letting
|
|
1980
|
+
the stream die mid-flight. Streamed requests that fail now emit an
|
|
1981
|
+
honest error receipt (`stream_error`, `error_kind`) instead of being
|
|
1982
|
+
logged as client cancellations.
|
|
1983
|
+
- **Anthropic `/v1/messages` usage no longer double-counts cached
|
|
1984
|
+
prefixes.** `input_tokens` now excludes `cache_read_input_tokens`
|
|
1985
|
+
(Anthropic's fields are disjoint; OpenAI's are cumulative), so Claude
|
|
1986
|
+
Code and Pi context/auto-compaction math sees true totals on session
|
|
1987
|
+
cache hits. Adopted from PR #417 by @amichaelblock-lgtm.
|
|
1988
|
+
- **Claude Code no longer times out on long prefills.** Claude Code's
|
|
1989
|
+
stream watchdog resets only on real message events and ignores SSE
|
|
1990
|
+
comments and protocol pings, so a first turn past its 300 s idle
|
|
1991
|
+
window (large MCP toolsets reach 137k to 165k tokens) died with
|
|
1992
|
+
"Stream idle timeout". The Anthropic bridge now emits prefill
|
|
1993
|
+
keep-alives as empty `thinking_delta` events; a measured 165k-token
|
|
1994
|
+
first turn on the 27B now survives a multi-minute prefill and
|
|
1995
|
+
completes.
|
|
1996
|
+
- **Compile kill-switch precedence (from PR #395 by @maceip).** An
|
|
1997
|
+
explicit `MTPLX_COMPILED_GDN=0` / `MTPLX_QWEN4EXP_COMPILE=0` now wins
|
|
1998
|
+
over profile auto-arming everywhere, including `set_ar_pipeline_mode`
|
|
1999
|
+
re-arming; the two flags are aliases with explicit-off precedence.
|
|
2000
|
+
- **Stop-cause telemetry (#414).** Generation stats now record
|
|
2001
|
+
`finish_stop_origin` distinguishing model EOS, stop sequences, length
|
|
2002
|
+
caps, and repetition stops, so early-stop reports are diagnosable from
|
|
2003
|
+
request logs.
|
|
2004
|
+
- **Source-checkout onboarding leaves the global install alone.** Running
|
|
2005
|
+
the app from a source checkout no longer tries to upgrade or repoint
|
|
2006
|
+
the user's terminal `mtplx` installation, and QA builds prefer their
|
|
2007
|
+
explicitly allowed source wrapper over stale app-managed or Homebrew
|
|
2008
|
+
runtimes (production bundle precedence unchanged). The `--kv-quant`
|
|
2009
|
+
help text now describes the shipped packed-quant q4 kernel.
|
|
2010
|
+
|
|
2011
|
+
### Added
|
|
2012
|
+
|
|
2013
|
+
- **Dark lanes for measured techniques** (off by default, receipts in-tree):
|
|
2014
|
+
double-buffered AR decode via `mx.async_eval` (`MTPLX_ASYNC_AR=1`,
|
|
2015
|
+
ported from PR #396 by @maceip; measured flat at product cells) and
|
|
2016
|
+
M-batched fused MoE GLU verify kernels (`MTPLX_FUSED_MOE_VERIFY=1`;
|
|
2017
|
+
bit-identical per token, measured slower at verify widths, kept as
|
|
2018
|
+
wiring platform).
|
|
2019
|
+
|
|
2020
|
+
## [2.10.1] - 2026-08-30
|
|
2021
|
+
|
|
2022
|
+
### Added
|
|
2023
|
+
|
|
2024
|
+
- **Sparse prefill lane for Flash-Next.** Adapts the fused Metal kernels
|
|
2025
|
+
from PR #397 by @maceip: the model's own block scores drive a
|
|
2026
|
+
block-sparse FlashAttention prefill instead of full attention masks.
|
|
2027
|
+
On an M5 Max against 2.10.0: a 98k-token prompt processes in 114.5 s
|
|
2028
|
+
instead of 175.7 s with peak memory down from 91.4 to 83.0 GB, a
|
|
2029
|
+
131k-token prompt measures 810 tok/s, and a 262,144-token cold prompt
|
|
2030
|
+
completes in 355 s at 87.4 GB peak where 2.10.0 climbed to 119 GB and
|
|
2031
|
+
produced nothing (#393, reported by @blackjose007-stack). Auto-enabled
|
|
2032
|
+
on M4/M5 generation GPUs for prompts past 32k tokens; other machines
|
|
2033
|
+
keep the dense path; `MTPLX_QSA_PREFILL=0` disables. The kernels also
|
|
2034
|
+
handle YaRN rope scaling.
|
|
2035
|
+
- **Image input for Flash-Next.** The packs always shipped their vision
|
|
2036
|
+
weights; the runtime now serves them. Native multimodal position
|
|
2037
|
+
encoding (M-RoPE) lands for the family, so images work in app chat,
|
|
2038
|
+
over the API, and in Pi (#328, reported by @nmqanh). Pi model configs
|
|
2039
|
+
written by an older MTPLX upgrade in place and user edits survive.
|
|
2040
|
+
|
|
2041
|
+
### Fixed
|
|
2042
|
+
|
|
2043
|
+
- **Flash-Next loads on 96 GB Macs.** The preload memory check used
|
|
2044
|
+
reserve constants tuned on 128 GB machines and refused packs that
|
|
2045
|
+
actually fit; the reserve now scales with total RAM (#400, reported by
|
|
2046
|
+
@JordiPosthumus). 128 GB and larger machines are unchanged.
|
|
2047
|
+
- **M2/M3 GPU crash at launch.** Three Metal kernels requested
|
|
2048
|
+
1,024-thread threadgroups; M2/M3 generation GPUs cap these kernels at
|
|
2049
|
+
896 threads and refused them. A one-shot startup probe now routes
|
|
2050
|
+
those machines to the standard kernels instead of crashing (#400).
|
|
2051
|
+
- **Honest Flash-Next memory admission.** The plan now prices the
|
|
2052
|
+
family's QSA caches and prefill transients, over-window requests
|
|
2053
|
+
answer a structured HTTP 507 instead of wedging the machine under
|
|
2054
|
+
memory pressure, and the QSA cache grows geometrically instead of by
|
|
2055
|
+
quadratic reallocation (#393).
|
|
2056
|
+
- **Empty first answers with tools declared.** A first turn that ended
|
|
2057
|
+
entirely inside the reasoning channel returned an empty message when
|
|
2058
|
+
tools were present (agent clients, chat with web search). The server
|
|
2059
|
+
now continues the turn to a visible answer, and a failed continuation
|
|
2060
|
+
returns the first pass instead of an error.
|
|
2061
|
+
- **Greedy Turbo exactness on the 27B.** Temperature-0 verification
|
|
2062
|
+
routes to stock kernels, restoring token-exact agreement between
|
|
2063
|
+
Turbo and plain decode.
|
|
2064
|
+
- **App runtime self-heal.** The app proves its Python runtime imports
|
|
2065
|
+
before trusting it and rebuilds it from the bundled wheel on failure,
|
|
2066
|
+
ending the crash loop that survived app reinstalls because the broken
|
|
2067
|
+
runtime folder was kept.
|
|
2068
|
+
- **Decode speed receipts.** Session-restore time was attributed to
|
|
2069
|
+
decode, understating decode speed on warm long-context turns.
|
|
2070
|
+
Attribution is corrected; generation behavior is unchanged.
|
|
2071
|
+
|
|
2072
|
+
## [2.10.0] - 2026-08-28
|
|
2073
|
+
|
|
2074
|
+
### Added
|
|
2075
|
+
|
|
2076
|
+
- **Long generations hold their decode speed.** A 34k-token uncapped chat
|
|
2077
|
+
answer decayed from 86 to 25 tok/s inside one request because every
|
|
2078
|
+
long-generation guard keyed off prompt length: the draft head's committed
|
|
2079
|
+
history cache grew unbounded during decode, and the allocator clear-cache
|
|
2080
|
+
cadence never armed for short prompts (8.6 GB of MLX allocator cache in a
|
|
2081
|
+
single request). The history cache now resets and regrows after 16,384
|
|
2082
|
+
live-appended tokens (`MTPLX_MTP_HISTORY_LIVE_RESET_THRESHOLD`, keyed on
|
|
2083
|
+
appends so restored session seeds are never dropped; output stream
|
|
2084
|
+
unchanged by the verify contract), and the clear-cache cadence arms
|
|
2085
|
+
mid-generation when live context crosses the threshold (0.6 GB on the
|
|
2086
|
+
same workload with it armed).
|
|
2087
|
+
- **Context-copy block rounds on the batched verify lane.** Flash-Next's
|
|
2088
|
+
lane never had the copy mechanic, so file rewrites decoded at plain MTP
|
|
2089
|
+
depth even with every output token already in the prompt. A prompt n-gram
|
|
2090
|
+
match now proposes up to a 24-token block through the lane's normal
|
|
2091
|
+
verify forward with the identical probability-ratio acceptance, so
|
|
2092
|
+
sampling behavior is unchanged. Two-turn rewrite receipt: rewrite turn
|
|
2093
|
+
87.6 tok/s vs 73.8 for the fresh build, 2,400 of 3,963 tokens from 177
|
|
2094
|
+
copy rounds, zero copy rounds on novel text.
|
|
2095
|
+
`MTPLX_CONTEXT_COPY_BATCHED=0` disables.
|
|
2096
|
+
- **Decay observability.** Receipts carry the context-copy counters and the
|
|
2097
|
+
live-reset fields; `MTPLX_DROP_EVENTS=0` launches also get a per-round
|
|
2098
|
+
`round_timing_ms` series. The growth-lever envs (clear-cache cadence,
|
|
2099
|
+
history window and live reset, verify snapshot, family capture-commit,
|
|
2100
|
+
drop-events) join the operator-beats-profile list.
|
|
2101
|
+
- **Reasoning effort works on Flash-Next, everywhere.** The `qwen4_exp`
|
|
2102
|
+
family now declares the same reasoning codec as the dense 27B (Qwen
|
|
2103
|
+
think-tag parser; effort levels `xhigh` / `medium` / `low`; modes
|
|
2104
|
+
auto/on/off), so `reasoning_effort` on a request is honored instead of
|
|
2105
|
+
silently dropped — previously the family resolved "no levels", the
|
|
2106
|
+
field was discarded before it was read, and the chat template's own
|
|
2107
|
+
`xhigh` fallback burned thousands of thinking tokens with no way to
|
|
2108
|
+
turn it down. The family default is `xhigh`; the 27B keeps its
|
|
2109
|
+
measured `medium` coding default. Full surface parity in the same
|
|
2110
|
+
pass: the app's effort picker renders and persists for the family,
|
|
2111
|
+
`mtplx run` and `mtplx chat` gain `--reasoning-effort`, and
|
|
2112
|
+
`/v1/messages` (Anthropic-bridge) requests now forward the flat
|
|
2113
|
+
`reasoning_effort` field for every family instead of dropping it.
|
|
2114
|
+
- **The n-gram sidecar stops costing 30 GB of RAM on paper.** The 32 GB
|
|
2115
|
+
Flash-Next n-gram table streams from SSD by default, but the memory
|
|
2116
|
+
plan, session-bank budget, Metal floor, and the app's memory card all
|
|
2117
|
+
still counted it as wired weights — a 128 GB Mac printed a false
|
|
2118
|
+
"MODEL DOES NOT FIT", resolved a 30 GB-pessimistic context window, and
|
|
2119
|
+
auto-budgeted the session bank 30 GB too small. One policy now drives
|
|
2120
|
+
gather behavior and every accounting surface; the serve banner and the
|
|
2121
|
+
app's Memory Detail card say `n-gram table 29.8G streamed from SSD
|
|
2122
|
+
(not wired)`. A new hot-row LRU (`MTPLX_NGRAM_HOT_MB`, default 1024)
|
|
2123
|
+
keeps decode-sized gathers in RAM, byte-identical by construction and
|
|
2124
|
+
by test — measured against the previously shipping streamed default:
|
|
2125
|
+
+5-10% AR and +7.5-16% MTP decode, at identical memory. In the
|
|
2126
|
+
product default config the MTP register now meets or beats the 30
|
|
2127
|
+
GB-wired resident pin (+2.4%), so the wired mode remains only a
|
|
2128
|
+
bench pin for 160 GB+ machines (`MTPLX_NGRAM_RESIDENT=1`).
|
|
2129
|
+
- **Both Flash-Next packs are public on Hugging Face** —
|
|
2130
|
+
`Youssofal/Qwen3.8-Flash-Next-MTPLX-Bare-Speed` and
|
|
2131
|
+
`Youssofal/Qwen3.8-Flash-Next-MTPLX-Optimized-Speed` — and first-run
|
|
2132
|
+
onboarding now offers them on Macs that fit them, right behind the
|
|
2133
|
+
27B trio, resolving straight to the published repos.
|
|
2134
|
+
|
|
2135
|
+
- **Flash-Next turbo, first-class.** The two Qwen 3.8 Flash-Next serve
|
|
2136
|
+
packs now resolve the **turbo** launch profile by default across
|
|
2137
|
+
`mtplx start` / `serve` / `quickstart` / the app — the same measured
|
|
2138
|
+
launch rule the quantized 27B/9B flagships follow. On this family turbo
|
|
2139
|
+
rides the qwen4_exp fast lane (pipelined AR decode, compiled GDN,
|
|
2140
|
+
layer-owned capture-commit, and the fused hyper-read/GDN kernels below)
|
|
2141
|
+
rather than the 27B NAX verify patch, which stays off for the family
|
|
2142
|
+
until it earns its own measured win. Both packs' canonical ids
|
|
2143
|
+
(`mtplx-flash-next-bare-speed`, `mtplx-flash-next-optimized-speed`)
|
|
2144
|
+
now resolve from HF ids, folder names, and `mtplx pull` aliases;
|
|
2145
|
+
derivative or renamed packs deliberately do not inherit the ids.
|
|
2146
|
+
- **Verify-width GDN conv kernel.** The conv + SiLU + L2-norm chain that
|
|
2147
|
+
speculative verify blocks previously ran as an eager op sequence now runs
|
|
2148
|
+
as one Metal dispatch for blocks of up to six rows, with a sliding
|
|
2149
|
+
in-block conv window, and stays fully compatible with the family's
|
|
2150
|
+
capture-commit rollback (it emits the exact rows the stash retains).
|
|
2151
|
+
Default-on for the family after two boot-triple A/B batteries in both
|
|
2152
|
+
orders (+3.1% and +2.3% MTP decode; the fused arm posted the campaign's
|
|
2153
|
+
best arm mean).
|
|
2154
|
+
- **One-dispatch GDN decode step.** The whole GatedDeltaNet decode
|
|
2155
|
+
step — causal conv + SiLU + L2 norm, decay/beta gates, the fp32 delta
|
|
2156
|
+
recurrence, and the gated-RMS output epilogue — now runs as a single
|
|
2157
|
+
Metal dispatch between the in/out projections, cutting a GDN layer
|
|
2158
|
+
from ~6 GPU sends to 3. Default-on for the family after two
|
|
2159
|
+
boot-triple A/B batteries in both arm orders (+1.7% and +2.2% decode,
|
|
2160
|
+
the confirm triple's fused arm holding the six fastest rows).
|
|
2161
|
+
- **Qwen 3.8 Flash-Next, day-0 native.** A new first-class model family
|
|
2162
|
+
(`qwen4_exp`): the 125B-A6B Qwen4-generation preview with GDN hybrid
|
|
2163
|
+
MoE, Qwen Sparse Attention, and the 32 GB n-gram memory sidecar —
|
|
2164
|
+
served by an in-tree MLX backend that is parity-exact against the
|
|
2165
|
+
reference implementation, with the native MTP draft head running
|
|
2166
|
+
through MTPLX's standard speculative lane (measured 1.6-1.7× over AR
|
|
2167
|
+
through the real server). Two packs: **Bare Speed** (flat 4-bit,
|
|
2168
|
+
fastest) and **Optimized Speed** (dynamic quant with 8-bit attention,
|
|
2169
|
+
higher quality). The n-gram table streams from SSD by default, so the
|
|
2170
|
+
packs fit 96 GB+ Macs with headroom; both appear in the app and CLI
|
|
2171
|
+
model pickers on machines that fit them, with the family's own serving
|
|
2172
|
+
contract (temperature 1.0, adaptive draft depth) applied end to end.
|
|
2173
|
+
- `mtplx forge verify --stamp`: records a first-load smoke baseline and
|
|
2174
|
+
writes the pack's `mtplx_runtime.json` in place — the step that turns a
|
|
2175
|
+
"family-compatible-unverified" model into a verified one — without
|
|
2176
|
+
rebuilding or copying the artifact. Families the tune instrument cannot
|
|
2177
|
+
measure (Flash-Next today) take their rows through a locally booted
|
|
2178
|
+
`mtplx serve`, the lane that actually applies their family contract.
|
|
2179
|
+
- **The CLI's first 90 seconds behave like 2026.** The terminal chat
|
|
2180
|
+
gets readline line editing with a persistent history
|
|
2181
|
+
(`~/.mtplx/history`, 1000 entries; arrow keys used to print raw
|
|
2182
|
+
escape sequences into the prompt); prompts pipe in
|
|
2183
|
+
(`echo "..." | mtplx run`, and a piped prompt into the chat entry
|
|
2184
|
+
answers through the same path `--prompt` uses, while an empty pipe
|
|
2185
|
+
keeps the non-tty refusal); `serve`/`run`/`chat` join the main help
|
|
2186
|
+
and `list`/`remove`/`config`/`env`/`dashboard`/`integrate` get a
|
|
2187
|
+
"Server and scripting" help group, with a one-line difflib
|
|
2188
|
+
"Did you mean" on any typo across all registered subcommands (exit
|
|
2189
|
+
code still 2); `mtplx ask` joins `run`/`chat` on
|
|
2190
|
+
`--reasoning-effort` and the chat REPL gains `/effort
|
|
2191
|
+
<level|status>`, live per turn like `/reasoning`; `mtplx --version`
|
|
2192
|
+
drops the redundant parenthetical when display and package versions
|
|
2193
|
+
match; and `mtplx hardware` stops printing "hardware acceleration
|
|
2194
|
+
confirmed: false" at humans for a field that means "not profiled"
|
|
2195
|
+
(JSON output unchanged everywhere).
|
|
2196
|
+
- Streaming endpoints (`/v1/chat/completions`, `/v1/completions`,
|
|
2197
|
+
`/v1/messages`) emit a `: keep-alive` SSE comment every 5 seconds
|
|
2198
|
+
while a stream is still silent before its first token (#358). Long
|
|
2199
|
+
prefills — minutes at 32k+ prompts — previously put zero liveness
|
|
2200
|
+
bytes on the wire, so strict client/proxy read-timeouts (Claude
|
|
2201
|
+
Code, Cursor, Open WebUI, nginx, cloudflared) dropped the
|
|
2202
|
+
connection mid-compute. SSE comments are ignored by every compliant
|
|
2203
|
+
parser; once tokens flow the comments stop. Disable with
|
|
2204
|
+
`MTPLX_SSE_HEARTBEAT=0`; tune the cadence with
|
|
2205
|
+
`MTPLX_SSE_HEARTBEAT_INTERVAL_S` (minimum 1s).
|
|
2206
|
+
- **Machine memory governor** (issue #305). MTPLX now plans its memory
|
|
2207
|
+
against the Mac it is actually on instead of assuming a 128 GB studio
|
|
2208
|
+
machine. At startup the serve banner prints the machine plan (engine
|
|
2209
|
+
budget, weights, resolved context window, session-bank budget); the
|
|
2210
|
+
default context window is the largest one whose full-window KV really
|
|
2211
|
+
fits (a 48 GB Mac serving the Speed model defaults to 196,608 tokens
|
|
2212
|
+
instead of a physically impossible 262,144 — 128 GB machines are
|
|
2213
|
+
unchanged), and an explicit `--context-window` above the fit still wins
|
|
2214
|
+
but is flagged loudly. The session bank stays idle-aggressive and
|
|
2215
|
+
yields dynamically as a long-context request's KV materializes, ahead
|
|
2216
|
+
of any swap; macOS pressure and an earlier allocator-relative signal
|
|
2217
|
+
drive the existing shedding guard. `/health`, the dashboard snapshot
|
|
2218
|
+
and the app carry `memory_plan`, guard events, and a pressure banner.
|
|
2219
|
+
`MTPLX_MEMORY_BUDGET=48G` reproduces a real 48 GB seat exactly (test-pinned).
|
|
2220
|
+
- **Streaming SSD spill for large sessions** (issues #305, #323). Sessions
|
|
2221
|
+
above the per-session RAM cap — exactly the 100k+-token coding-agent
|
|
2222
|
+
sessions whose re-prefill costs minutes — now persist to the SSD tier
|
|
2223
|
+
through a tensor-by-tensor streaming writer (bounded memory, same
|
|
2224
|
+
on-disk format), instead of silently losing durability. Live-ref-only
|
|
2225
|
+
sessions reach the SSD tier for the first time; every remaining skip is
|
|
2226
|
+
recorded, never silent — including the disk-headroom size cap
|
|
2227
|
+
(`min(configured cap, free_disk/4)`), which now prints one console line
|
|
2228
|
+
per session naming the entry size, the effective cap, and free disk
|
|
2229
|
+
when it refuses a spill (found live on a 4 TB disk at 28 GiB free,
|
|
2230
|
+
which caps the lane at ~7 GiB and mutely excluded every 100k+-token
|
|
2231
|
+
session — the same silence class as #278, in a brand-new lane). A
|
|
2232
|
+
request arriving mid-write makes the streaming encode abort cleanly
|
|
2233
|
+
and re-dispatch for the next idle window (counted in
|
|
2234
|
+
`encode_yields_foreground`), so a spill in progress can never make a
|
|
2235
|
+
request wait; the dedicated writer thread keeps its own 600 s
|
|
2236
|
+
foreground pause, where waiting is free because nothing queues behind
|
|
2237
|
+
that thread.
|
|
2238
|
+
|
|
2239
|
+
- **RAMP: opt-in long-block and fuzzy re-anchor policy for context copy**
|
|
2240
|
+
(adapting community PR #375 by @johninthewinter). A fixed 48-token copy
|
|
2241
|
+
block replaces the confidence ladder and an exact n-gram miss falls
|
|
2242
|
+
through to a mismatch-tolerant short-anchor re-match, for edit-shaped
|
|
2243
|
+
temperature-0 agent workloads where the author measured +45.9 to +53.9
|
|
2244
|
+
percent decode with byte-identical output. Our own paired temperature-1
|
|
2245
|
+
chat-rewrite arms measured it a net loss (copy supply fell 504 to 123
|
|
2246
|
+
tokens on seed-identical streams), so it ships off by default
|
|
2247
|
+
(`MTPLX_RAMP_ENABLED`); off is byte-for-byte the prior proposer.
|
|
2248
|
+
- **One-sync greedy draft read on confidence lanes** (adapting community
|
|
2249
|
+
PR #288 by @ArthurOstapenko). Margin-gate and adaptive-depth lanes now
|
|
2250
|
+
read the greedy draft token and its confidence metrics in one GPU
|
|
2251
|
+
synchronization instead of two; the author's paired receipt is +0.559
|
|
2252
|
+
percent geomean on the ExpectedValue depth-3 lane. The default greedy
|
|
2253
|
+
chain and every sampled-draft lane are untouched, proven by an
|
|
2254
|
+
engagement counter that reads zero there.
|
|
2255
|
+
- **QSA rows-gather lane for verify widths** (adapting the per-query
|
|
2256
|
+
gather and GQA head-group broadcast from community PR #380 by @maceip).
|
|
2257
|
+
Multi-row QSA forwards previously staged a dense [rows, context] mask
|
|
2258
|
+
and read the full KV in all 12 QSA layers every verify round, a cost
|
|
2259
|
+
that grows with the generation. The opt-in lane gathers each row's
|
|
2260
|
+
selected blocks plus its visible tail at a constant width instead, with
|
|
2261
|
+
context-length and row-count routing fences so short contexts keep the
|
|
2262
|
+
fused dense path. Family default ON for Flash-Next (self-fenced to
|
|
2263
|
+
2..8 rows at 16384+ tokens of context, so shorter contexts are
|
|
2264
|
+
bit-identical dense; `MTPLX_QSA_GATHER=0` is the kill switch). Paired
|
|
2265
|
+
16,384-token receipt: the dense path's verify cost grew 37.0 to 46.9 ms
|
|
2266
|
+
per round across the run while the gather arm held flat 45.3 to 45.9,
|
|
2267
|
+
finishing its last window at 64.5 tok/s against 36.0 dense.
|
|
2268
|
+
Parity-tested against the dense path on the real layer.
|
|
2269
|
+
|
|
2270
|
+
### Fixed
|
|
2271
|
+
|
|
2272
|
+
- **The context-copy lane earns its block size before spending it.** Copy
|
|
2273
|
+
rounds verify 16-24-token candidate blocks — a ~4x-cost forward versus a
|
|
2274
|
+
normal round — and the acceptance gate needed four sampled rounds per
|
|
2275
|
+
generation before it could suspend, so short coding-agent turns re-paid
|
|
2276
|
+
the full misfire cost every turn (measured: whole-turn verify 60-88
|
|
2277
|
+
ms/round at 8k context with 21/96 copy tokens accepted, decode down to
|
|
2278
|
+
~27 tok/s). Blocks now stay at 8 tokens
|
|
2279
|
+
(`MTPLX_CONTEXT_COPY_PROBATION_K`) until the turn's acceptance EMA
|
|
2280
|
+
proves the content pays, and the suspension arms one round earlier.
|
|
2281
|
+
Long-context re-emission — where the lane is a measured +16.7% — opens
|
|
2282
|
+
to full blocks by its third round and keeps its win. The batched verify
|
|
2283
|
+
lane (Flash-Next's copy mechanic, above) carries the identical
|
|
2284
|
+
probation contract.
|
|
2285
|
+
- **Greedy draft coupling engages on launch-default-greedy servers.** The
|
|
2286
|
+
draft-sampler resolver received the raw request `temperature` — `None`
|
|
2287
|
+
when a client omits the field — so a server launched with
|
|
2288
|
+
`--temperature 0` serving such a client decoded greedily while drafts
|
|
2289
|
+
stayed at the family default (1.0): the silent sampled-draft acceptance
|
|
2290
|
+
collapse ([79/65/42]% by depth vs [96/87/76]% coupled), with no
|
|
2291
|
+
`draft_sampler_greedy_coupled` stamp to show for it. Both serve lanes
|
|
2292
|
+
now hand the resolver the effective sampler temperature, matching the
|
|
2293
|
+
resolver's documented contract; explicit-temperature requests were
|
|
2294
|
+
never affected.
|
|
2295
|
+
- **SSD spills no longer fire mid-turn: the writer's foreground pause now
|
|
2296
|
+
outlasts a long coding turn.** The writer already stood down while a
|
|
2297
|
+
request was in flight, but its liveness bound was 60 s — shorter than a
|
|
2298
|
+
typical agent turn (60–620 s) — so every multi-GB session spill fired
|
|
2299
|
+
under the live decode: a ~1 GB/min unified-memory drumbeat that tripped
|
|
2300
|
+
macOS memory pressure (the Live-tab banner) and stole decode (measured
|
|
2301
|
+
−30% when a write overlapped a turn). The bound is now 600 s
|
|
2302
|
+
(`MTPLX_SSD_WRITER_FOREGROUND_PAUSE_MAX_S`); writes drain in the gaps
|
|
2303
|
+
between turns, and a bound that still expires into live traffic is
|
|
2304
|
+
counted in `/admin/cache/ssd` as `writer_pause_expired_busy`. The cold
|
|
2305
|
+
tier is a cache — waiting out a turn costs delayed durability, never
|
|
2306
|
+
correctness.
|
|
2307
|
+
- **The session bank yields ahead of the prefill spike, so deep turns stop
|
|
2308
|
+
tripping the memory banner.** The dynamic ceiling reserved a static 3 GiB
|
|
2309
|
+
for generation transients, but a deep chunked prefill measures up to
|
|
2310
|
+
12.4 GiB of peak-over-active — so on long coding sessions the bank kept
|
|
2311
|
+
entries while the allocator peak kissed 99%+ of the Metal limit, firing
|
|
2312
|
+
the warning banner on every deep turn. The ceiling now reserves the
|
|
2313
|
+
spike this process has actually observed (clamped between 3 GiB and
|
|
2314
|
+
half the post-weights memory, at most 16 GiB), demoting idle entries
|
|
2315
|
+
to SSD before the next spike can slam the ceiling.
|
|
2316
|
+
- **The memory banner now names the culprit.** The daemon reports which
|
|
2317
|
+
signal produced the pressure level (`memory_pressure_source`: system-wide
|
|
2318
|
+
macOS pressure vs this engine's allocator near its Metal limit, plus the
|
|
2319
|
+
live `allocator_fraction`), and the app's warning banner distinguishes
|
|
2320
|
+
"System memory pressure" (another process allocating; decode can dip
|
|
2321
|
+
until it passes — nothing was evicted) from the engine's own
|
|
2322
|
+
"Memory running high". Receipt for the split: an external 26 GB
|
|
2323
|
+
allocation storm dropped decode 65→22 tok/s with the engine's guard
|
|
2324
|
+
correctly doing nothing at all — the old copy blamed the engine for
|
|
2325
|
+
weather it didn't make. Attribution only claims what it can prove:
|
|
2326
|
+
when macOS and the allocator report pressure in the same tick the
|
|
2327
|
+
banner names the allocator (the "another process" copy asserts the
|
|
2328
|
+
engine's footprint is steady, which is false at a tie), and when the
|
|
2329
|
+
allocator probe cannot read at all the source reports `unknown` and
|
|
2330
|
+
the banner stays neutral.
|
|
2331
|
+
- **Long sessions persist to SSD: the writer's backlog budget no longer
|
|
2332
|
+
rejects a snapshot bigger than itself** (#384, thanks @sapiens77 for a
|
|
2333
|
+
forensic-grade report). The SSD writer bounds queued bytes at 4 GiB by
|
|
2334
|
+
default, but a single snapshot larger than the whole budget failed
|
|
2335
|
+
admission on every attempt even with an empty queue — at roughly 84
|
|
2336
|
+
KB/token of 27B KV that turned the SSD tier silently off past ~50k
|
|
2337
|
+
tokens, with only a counter as the trace. An empty queue is not backlog
|
|
2338
|
+
pressure: a lone oversized entry is now admitted, logged by name, and
|
|
2339
|
+
counted in `/admin/cache/ssd` as `admitted_oversized_alone`; a genuinely
|
|
2340
|
+
backlogged writer still rejects. The reporter measured 41.7x TTFT across
|
|
2341
|
+
a restart (282 s to 6.8 s) once writes could land. Also from the same
|
|
2342
|
+
thread: `request_session_source` now reports the header source instead
|
|
2343
|
+
of null for header-identified sessions.
|
|
2344
|
+
- **The memory governor's ceiling never evicts the live session's cache.**
|
|
2345
|
+
The dynamic bank ceiling subtracts an instantaneous working-set reading,
|
|
2346
|
+
so a deep prefill's transient allocator spike read as a standing
|
|
2347
|
+
commitment: on a 93k-token coding session the ceiling walked the session
|
|
2348
|
+
bank to zero bytes mid-request, evicting the in-flight session's own
|
|
2349
|
+
prefix entries, and every following agent turn re-prefilled from scratch
|
|
2350
|
+
(TTFT 54 to 57 s, prefill 909 down to 175 tok/s). The ceiling now
|
|
2351
|
+
squeezes idle sessions only; the active session's prefix chain survives
|
|
2352
|
+
even when the bank stays above target. Real macOS or allocator pressure
|
|
2353
|
+
keeps its take-anything eviction semantics, so the 48 GB swap-death
|
|
2354
|
+
protection is unchanged.
|
|
2355
|
+
- **Flash-Next serves coding agents at its official sampler.** The app's
|
|
2356
|
+
OpenCode and Hermes launch presets pre-fill the Qwen3.6-era coding
|
|
2357
|
+
sampler (temperature 0.6), and the Flash-Next model defaults left those
|
|
2358
|
+
slots untouched, so the daemon booted with an explicit `--temperature
|
|
2359
|
+
0.6` that suppressed the pack-stamp injection — OpenCode requests were
|
|
2360
|
+
normalized to a 0.6 target against the family's official 1.0, with the
|
|
2361
|
+
draft still at the stamp's 1.0 (a mismatched verify pair). Flash-Next
|
|
2362
|
+
now clears the target-preset sampler slots on every launch target, so
|
|
2363
|
+
the zero-flag boot path injects the artifact's stamped 1.0/0.95/20 for
|
|
2364
|
+
target and draft alike, identical to a bare `mtplx serve`. The dense
|
|
2365
|
+
27B was already correct (its preset pins the official triple); 3.6-era
|
|
2366
|
+
models keep their measured 0.6 lane.
|
|
2367
|
+
- **OpenCode's effort picker shows the whole Flash-Next dial.** The
|
|
2368
|
+
generated config only declared effort variants for tiers outside
|
|
2369
|
+
OpenCode's built-in list, trusting the client to surface the rest — but
|
|
2370
|
+
OpenCode Desktop 1.18.21 does not offer its full built-in list for
|
|
2371
|
+
custom openai-compatible providers, so xhigh (the Flash-Next chat
|
|
2372
|
+
default) was missing from the picker entirely. Every family tier is now
|
|
2373
|
+
written as an explicit variant, which renders on every OpenCode version
|
|
2374
|
+
and still merges over same-named built-ins; out-of-family tiers stay
|
|
2375
|
+
disabled.
|
|
2376
|
+
- **The KV-quantization control explains itself on Flash-Next.** The
|
|
2377
|
+
setting showed the anonymous "not supported for this model" line because
|
|
2378
|
+
the family had no entry in the KV-quant policy table. The policy is
|
|
2379
|
+
unchanged — the validated q8/q4 paged lane is wired to the dense-27B
|
|
2380
|
+
attention call sites, not to Flash-Next's QSA layers — but the app now
|
|
2381
|
+
states the architecture truth: the hybrid design keeps KV on 12 of 48
|
|
2382
|
+
layers (~24 KB/token), and a quantized QSA lane has no validation
|
|
2383
|
+
receipts yet. Qwen 3.5/3.6/3.8 keep the full q8/q4 control.
|
|
2384
|
+
- **The warm ladder yields to live traffic and no longer stamps the
|
|
2385
|
+
traffic clock** (adapting community PR #300 by @Blakeolson21). A request
|
|
2386
|
+
that had arrived but not yet completed read as an idle daemon, so the
|
|
2387
|
+
first request of a serve could share the GPU with a warm rung; and warm
|
|
2388
|
+
rungs stamped `last_request_at`, making /health report user traffic on
|
|
2389
|
+
an untouched daemon while the ladder deferred against its own output.
|
|
2390
|
+
Rung admission now checks live and queued foreground work as its own
|
|
2391
|
+
branch (safe at zero idle grace), and warm generations no longer move
|
|
2392
|
+
the request clock or counters anywhere.
|
|
2393
|
+
- **The Live tab's acceptance panel no longer goes blank after a finished
|
|
2394
|
+
request.** Two stacked causes: the daemon's idle warmup ladder published
|
|
2395
|
+
its rungs into the dashboard's `latest` slot, replacing the finished
|
|
2396
|
+
request's receipt (a warmup row with a null request id sat where the
|
|
2397
|
+
user's acceptance counters belonged), and the app's Live-tab gates only
|
|
2398
|
+
ever ticked on `.completed` stream frames, which have no replay, so one
|
|
2399
|
+
frame missed during a reconnect kept the panel on its placeholder
|
|
2400
|
+
forever. Warmup rows now stay out of the dashboard ring server side, the
|
|
2401
|
+
app refuses to merge a warmup row over a real receipt, and a finished
|
|
2402
|
+
request observed through the snapshot poller counts as completion
|
|
2403
|
+
evidence (deduplicated per request), so the panel lights up within one
|
|
2404
|
+
poll of a request finishing regardless of stream health.
|
|
2405
|
+
- **The compact tool contract no longer drops trailing tools** (#376,
|
|
2406
|
+
adapting community PR #379 by @ArctifoxNL). When the "Declared tools
|
|
2407
|
+
and schemas" line exceeded its 1200-character budget it was raw
|
|
2408
|
+
byte-cut, deleting whole tool names at the tail (`task` first, in
|
|
2409
|
+
Claude Code-shaped toolsets) — and the contract's own "never invent an
|
|
2410
|
+
undeclared tool" clause then made the model treat every dropped tool
|
|
2411
|
+
as nonexistent, killing subagents. Over budget, every declared tool
|
|
2412
|
+
name is now kept and only per-tool signature detail is shed.
|
|
2413
|
+
- **Cancellation errors name their real cause** (#381). One shared
|
|
2414
|
+
per-request cancel flag is tripped by several unrelated paths — the
|
|
2415
|
+
`POST /v1/mtplx/cancel` endpoint, client disconnect, stop-sequence
|
|
2416
|
+
completion, tool-call finalization, the stall watchdog, stream
|
|
2417
|
+
teardown — and the terminal frame blamed every non-disconnect trip on
|
|
2418
|
+
the POST endpoint, framing an endpoint nobody called. The first
|
|
2419
|
+
origin is now recorded when the flag trips, terminal frames and the
|
|
2420
|
+
cancellation metric report it, and an unattributed trip says so
|
|
2421
|
+
instead of inventing a caller.
|
|
2422
|
+
- `mtplx connect opencode` now actually writes `~/.config/opencode/opencode.json`
|
|
2423
|
+
(merge-preserving: other providers and plugins survive). It previously built
|
|
2424
|
+
the config, printed the config path, and wrote nothing — so a `--model-id`
|
|
2425
|
+
for a newly served model never reached OpenCode's provider models map and
|
|
2426
|
+
runs failed with `ProviderModelNotFoundError` surfaced as "Unexpected
|
|
2427
|
+
server error". Found wiring Flash-Next day-0.
|
|
2428
|
+
- The serve daemon and tune/bench children now start Python with `-P`,
|
|
2429
|
+
so the directory you launch from can never shadow the installed
|
|
2430
|
+
runtime. Previously, running `mtplx serve` from any folder containing
|
|
2431
|
+
an `mtplx/` package (a source checkout, a vendored copy) silently
|
|
2432
|
+
served that folder's code instead of the installed release.
|
|
2433
|
+
- **Unexecuted tool calls are no longer silently swallowed** (#349). A
|
|
2434
|
+
fresh-install user asking the built-in chat about their files saw the
|
|
2435
|
+
model "invoke" tools (`ls`, `find`, `search_files`, `read_file`) and
|
|
2436
|
+
get nothing back — no output, no error — because the server deleted
|
|
2437
|
+
dead tool-call markup from no-tools responses (#160) and from
|
|
2438
|
+
undeclared-tool fallbacks without telling anyone. Suppressed calls
|
|
2439
|
+
now leave a short, visible notice naming the tool and stating that
|
|
2440
|
+
nothing ran, this chat has no file/terminal access, and a coding
|
|
2441
|
+
agent (Claude Code, OpenCode, Hermes) connected to MTPLX provides
|
|
2442
|
+
it. The model reads the same notice in its history and stops
|
|
2443
|
+
claiming it ran tools; the reply is never blank. Code-fenced tool
|
|
2444
|
+
syntax examples are untouched, and `mtplx_stats` gains
|
|
2445
|
+
`unexecuted_tool_call_notice` for triage. The macOS app also
|
|
2446
|
+
persists a truthful `tool_not_executed` result for any tool call
|
|
2447
|
+
that finishes past the chat's tool-round budget, so replayed
|
|
2448
|
+
transcripts never show the model an unanswered tool call.
|
|
2449
|
+
- **SSD session-cache writes no longer starve on an idle server, and
|
|
2450
|
+
shutdown flushes them** (issue #290). The scheduler's durability lane
|
|
2451
|
+
was only reachable while the idle band was completely empty, so any
|
|
2452
|
+
self-chaining background occupant (the background warm ladder) could
|
|
2453
|
+
hold SSD writes off forever — entries sat in RAM with every cold-tier
|
|
2454
|
+
counter at zero and vanished on restart, costing a full re-prefill.
|
|
2455
|
+
The server now pumps the durability lane within seconds of going
|
|
2456
|
+
request-idle (foreground work still always wins, and the pump disarms
|
|
2457
|
+
the moment a request arrives), and a plain SIGTERM/Ctrl-C gives
|
|
2458
|
+
pending writes a bounded best-effort flush (default 10 s,
|
|
2459
|
+
`MTPLX_SHUTDOWN_SSD_FLUSH_S` overrides, `0` disables) with one honest
|
|
2460
|
+
console line — including the write the SSD writer thread already has
|
|
2461
|
+
in flight, which the old shutdown killed mid-file.
|
|
2462
|
+
- Metal allocation failures are answered as structured
|
|
2463
|
+
`insufficient_memory` (HTTP 507) errors with actionable advice, after
|
|
2464
|
+
the engine sheds its caches — instead of anonymous `internal_error`
|
|
2465
|
+
500s that left the next request to hit the same wall (issue #348 class).
|
|
2466
|
+
- `--memory-budget 48G` (the bare-suffix spelling MTPLX's own messages
|
|
2467
|
+
advertise) crashed serve startup with a raw ValueError; single-letter
|
|
2468
|
+
and terabyte size suffixes parse now.
|
|
2469
|
+
- The dashboard "RAM session cache" settings no longer invent `8G/4G`
|
|
2470
|
+
when nothing is configured — they report the budgets the engine
|
|
2471
|
+
actually resolved.
|
|
2472
|
+
- The update dialog's release notes are readable in dark mode (#367).
|
|
2473
|
+
The generated notes page now declares `color-scheme: light dark` and
|
|
2474
|
+
pairs each appearance with readable text colors instead of shipping a
|
|
2475
|
+
light-only stylesheet that Sparkle's dark update window rendered as
|
|
2476
|
+
near-black-on-dark. The release script and the Sparkle rehearsal kit
|
|
2477
|
+
render through one shared template
|
|
2478
|
+
(`scripts/render_release_notes.py`), so the rehearsal now shows the
|
|
2479
|
+
exact page users get and the two can no longer drift apart.
|
|
2480
|
+
Notes pages already published under mtplx.com/releases/notes/ need a
|
|
2481
|
+
one-time re-render and re-upload to pick this up.
|
|
2482
|
+
- **Reasoning history preserves by default on Flash-Next.** The
|
|
2483
|
+
`qwen4_exp` family ships the byte-identical Qwen 3.8 chat template and
|
|
2484
|
+
the same preserve-by-default trained contract, but the auto policy
|
|
2485
|
+
only recognized `qwen3_8` and dropped the family onto the scoped
|
|
2486
|
+
fallback. Every agent round's session-cache postcommit then aborted
|
|
2487
|
+
with `reasoning_history_scoping_mismatch` and re-prefilled the whole
|
|
2488
|
+
assistant turn. With preserve on, mid-session agent turns cost about
|
|
2489
|
+
20 new prefill tokens at 0.11 s first token (scoped paid 300 to 1500
|
|
2490
|
+
tokens at 1.8 to 2.2 s, measured on the release wall-clock rig).
|
|
2491
|
+
- **Coding-agent lanes default to medium reasoning effort on
|
|
2492
|
+
Flash-Next.** OpenCode and Pi config writers resolve the family's new
|
|
2493
|
+
agent-lane default (codec `default_agent_effort`) instead of the chat
|
|
2494
|
+
default. On the identical multi-file coding task, xhigh measured
|
|
2495
|
+
150.2 s wall clock against 44.2 s at medium with the same correct
|
|
2496
|
+
output. Chat surfaces keep xhigh; both clients' effort pickers still
|
|
2497
|
+
offer every level per request.
|
|
2498
|
+
- **The Pi provider merge owns the transport contract.** The
|
|
2499
|
+
user-preserving config merge kept an older MTPLX's
|
|
2500
|
+
`supportsReasoningEffort: false` alive across every re-sync, which
|
|
2501
|
+
silently killed Pi's effort dial after an upgrade. MTPLX's own
|
|
2502
|
+
compatibility keys now update on sync; user-added keys still survive.
|
|
2503
|
+
- **The app's offline settings fallback shows family truth.** With the
|
|
2504
|
+
engine stopped, the inference settings panel fell back to a generic
|
|
2505
|
+
temperature 0.6 and the label "Custom model" for Flash-Next (and could
|
|
2506
|
+
persist that 0.6 over the engine's 1.0). The fallback table now
|
|
2507
|
+
carries the family's native 1.0 / 0.95 / 20 and the proper family
|
|
2508
|
+
name; live-daemon state was always correct.
|
|
2509
|
+
- **Onboarding verifies the terminal command through the login shell.**
|
|
2510
|
+
The setup step graded whatever executable the app's own process PATH
|
|
2511
|
+
found, and a Finder-launched app never inherits the shell rc's
|
|
2512
|
+
`/opt/homebrew/bin` ordering, so setup could certify "up to date"
|
|
2513
|
+
while the user's actual terminal still ran an older Homebrew install.
|
|
2514
|
+
The check now asks the user's login shell which executable wins and
|
|
2515
|
+
grades that one.
|
|
2516
|
+
- **Each conversation streams on its own turn stream** (#324). Switching
|
|
2517
|
+
conversations mid-generation no longer cross-wires or blanks either
|
|
2518
|
+
turn.
|
|
2519
|
+
- **Explicit performance settings are honored over client-target
|
|
2520
|
+
defaults** (#325), and the native Chat launch target stops silently
|
|
2521
|
+
ignoring "Handle multiple at once".
|
|
2522
|
+
- **The app accepts custom Hugging Face models the engine reports as
|
|
2523
|
+
runnable** (#359). Install completeness is judged by the source repo's
|
|
2524
|
+
own manifest instead of requiring an `mtplx_runtime.json`.
|
|
2525
|
+
- **Forge routes official NVIDIA Nemotron-H configs** by deriving the
|
|
2526
|
+
MTP pattern from `mtp_layers_block_type` (#341); load no longer
|
|
2527
|
+
crashes with an AttributeError.
|
|
2528
|
+
- **`mtplx remove` is fenced to the models cache and asks first.** The
|
|
2529
|
+
removal path ran `rmtree` on whatever the ref resolved to: a bare
|
|
2530
|
+
`.` or `/` resolved to the models cache directory itself and `..` to
|
|
2531
|
+
the whole `~/.mtplx` home (bin, config, session bank, every model),
|
|
2532
|
+
deleted without a word and exit 0. A ref must now resolve to a direct
|
|
2533
|
+
child of the models cache or the command refuses, a tty gets a
|
|
2534
|
+
confirmation naming the resolved path and size, `--yes` skips it for
|
|
2535
|
+
scripts, and a non-tty run without `--yes` refuses with the hint.
|
|
2536
|
+
- **A malformed `config.toml` no longer bricks every command.** A
|
|
2537
|
+
truncated or hand-edited config raised a raw TOMLDecodeError through
|
|
2538
|
+
`status`, `doctor`, even `stop`, so the one file meant to hold
|
|
2539
|
+
preferences could lock the user out of the CLI entirely. The loader
|
|
2540
|
+
now degrades to defaults with one stderr line naming the file, the
|
|
2541
|
+
parse error, and `mtplx config show`; a bad value for a single key
|
|
2542
|
+
degrades that key only.
|
|
2543
|
+
- **The daemonless CLI generate lanes couple the draft greedy under a
|
|
2544
|
+
greedy target.** One-shot `run`, the terminal chat, and `tune` call
|
|
2545
|
+
the engine directly and never pass the server's draft-sampler
|
|
2546
|
+
resolver, so `--temperature 0` kept the pack's stamped sampled draft
|
|
2547
|
+
(temperature 1.0) and paid the sampled-draft acceptance collapse
|
|
2548
|
+
([79/65/42]% by depth vs [96/87/76]% coupled) on exactly the lane
|
|
2549
|
+
outside benchmarks run. The lanes now share one coupling helper; a
|
|
2550
|
+
user-typed `--draft-temperature` still wins, and a spec-less lane
|
|
2551
|
+
already mirrored the target and is unchanged.
|
|
2552
|
+
|
|
2553
|
+
## [2.9.3] - 2026-08-26 (internal build — never published; ships as part of 2.10.0)
|
|
2554
|
+
|
|
2555
|
+
### Fixed
|
|
2556
|
+
|
|
2557
|
+
- **Agent turns died with a fabricated
|
|
2558
|
+
`request cancelled via POST /v1/mtplx/cancel`** (issues #332, #343). After
|
|
2559
|
+
a complete streamed tool call the server cancels its own generation to end
|
|
2560
|
+
the turn; when the worker's acknowledgement outlived the stream loop's
|
|
2561
|
+
250 ms poll, the loop misread its own cancel as a foreign
|
|
2562
|
+
`POST /v1/mtplx/cancel` and killed the healthy turn with that error — no
|
|
2563
|
+
one ever called the endpoint. Tool-calling streams now drain until the
|
|
2564
|
+
worker acknowledges and end with the `tool_calls` terminal frame; a real
|
|
2565
|
+
cancel and a real disconnect behave as before. Deterministic regression
|
|
2566
|
+
test included. Only tool-calling turns were affected, which is why plain
|
|
2567
|
+
streaming never reproduced it.
|
|
2568
|
+
- **Long-context decode past 128k, improved.** Past 131,072 prompt tokens dense decode
|
|
2569
|
+
silently repaged into a cache layout that structurally excluded the packed
|
|
2570
|
+
verify kernel, collapsing speculative decode to plain-AR speed (12.0 tok/s
|
|
2571
|
+
at 147k on an M5 Max 128 GB). The dense-decode ceiling is now memory-aware
|
|
2572
|
+
(`MTPLX_SUSTAINED_DENSE_DECODE_MAX_CONTEXT=auto`, floored at the old
|
|
2573
|
+
131,072 literal so smaller machines cannot regress), keeping the packed
|
|
2574
|
+
lane engaged (16.3 tok/s at 147k; 18.4 with copy speculation off). The
|
|
2575
|
+
resolver announces itself in the serve log, and an exported ceiling env
|
|
2576
|
+
beats the profile instead of being silently stomped back.
|
|
2577
|
+
- **KV-cache quantization was effectively broken and is now usable.**
|
|
2578
|
+
Enabling q8/q4 crashed serving at warmup; q4 additionally re-dequantized
|
|
2579
|
+
the entire prefix every round, and quantized caches were refused by the
|
|
2580
|
+
compiled verify bank. All three fixed: the crash is gone, q4 routes
|
|
2581
|
+
through the exact packed-quant kernel via a persistent quantized bank,
|
|
2582
|
+
and the verify bank promotes quantized paged caches. Measured cost at 16k
|
|
2583
|
+
on an M5 Max vs off: q8 ≈ −4% decode, q4 ≈ −19% (down from crash / −50%).
|
|
2584
|
+
Still opt-in — the remaining gap to zero-loss, and the slow quantized
|
|
2585
|
+
lane past the dense ceiling, are known and being worked. A pre-existing
|
|
2586
|
+
q4 numerics defect at head_dim 128 is fenced fail-closed (the shipped
|
|
2587
|
+
head_dim 256 family is exact).
|
|
2588
|
+
- The dynamic-offset paged verify kernel had never compiled since its
|
|
2589
|
+
introduction (a pointer-cast Metal bug hidden behind its own mask gate)
|
|
2590
|
+
and crashed at q_len > 5 once compiled; both fixed. Still opt-in
|
|
2591
|
+
(`MTPLX_PAGED_TAILMASK_ELIDE`) pending its serve verdict, but the crash
|
|
2592
|
+
class is removed.
|
|
2593
|
+
- Warmup failures log their full traceback instead of a one-line skip.
|
|
2594
|
+
|
|
2595
|
+
### Changed
|
|
2596
|
+
|
|
2597
|
+
- **mlx floor raised to 0.32.2.** A clean same-wheel A/B on an M5 Max
|
|
2598
|
+
measured 0.32.0 → 0.32.2 at +29% decode / +41% prefill at 88k context
|
|
2599
|
+
(+31% at 16k); the floor converges existing installs onto that stack.
|
|
2600
|
+
- Every request row now records packed-route bail counters and
|
|
2601
|
+
paged-adapter engagement, and `MTPLX_ROUTE_DEBUG=1` prints one line per
|
|
2602
|
+
layer naming the attention branch taken and every gate input — the
|
|
2603
|
+
decode cliff hid for months because fast lanes declined silently.
|
|
2604
|
+
|
|
2605
|
+
### Added
|
|
2606
|
+
|
|
2607
|
+
- Release-pin regression tests: the shipped profiles' fast-lane values
|
|
2608
|
+
(compiled-verify ceiling, packed verify kernel, dense-decode ceiling),
|
|
2609
|
+
operator-env-beats-profile precedence, and the mlx floor are now pinned
|
|
2610
|
+
by tests so a silent lane loss fails CI instead of surfacing in a
|
|
2611
|
+
benchmark weeks later.
|
|
2612
|
+
- Experimental, off by default: `--scheduler-mode hyper` (single-user
|
|
2613
|
+
manufactured-concurrency chassis, trajectory-sha-exact vs serial),
|
|
2614
|
+
`MTPLX_NAX_TILE_ROUTE` (first M5 tensor-unit attention kernel at decode
|
|
2615
|
+
shapes), `MTPLX_ADAPTIVE_DTEMP`, `MTPLX_CCOPY_BANK_ROUTE`,
|
|
2616
|
+
`MTPLX_FORKEV_TELEMETRY`.
|
|
2617
|
+
|
|
2618
|
+
## [2.9.2] - 2026-08-25
|
|
2619
|
+
|
|
2620
|
+
### Changed
|
|
2621
|
+
|
|
2622
|
+
- **The serving endpoints are passthrough by default** (#282). No
|
|
2623
|
+
tool-result compaction, no read trimming, no injected steering text
|
|
2624
|
+
unless a rewrite feature is explicitly enabled.
|
|
2625
|
+
`MTPLX_AGENT_REWRITES` is the master switch; each feature arms only
|
|
2626
|
+
via its own environment variable. The macOS app stopped exporting
|
|
2627
|
+
the legacy compaction settings when launching coding agents, and the
|
|
2628
|
+
request log records what was and was not rewritten per request.
|
|
2629
|
+
- Managed client configs respect user edits: `mtplx start` and the app
|
|
2630
|
+
only update files they wrote themselves (#282).
|
|
2631
|
+
- **Chained greedy drafting is on by default for temperature 0
|
|
2632
|
+
requests below 12,288 prompt tokens** (#313, #315, #318). Gated A/B
|
|
2633
|
+
runs on an M5 Max measured +2.5 to +9.8 percent decode on 0.5k to 8k
|
|
2634
|
+
prompts and -2.9/-2.7 percent at 16k/32k, so a context fence keeps
|
|
2635
|
+
it off at and above 12,288. Tune with
|
|
2636
|
+
`MTPLX_GREEDY_TRIO_MAX_CONTEXT`, disable with
|
|
2637
|
+
`MTPLX_GREEDY_DRAFT_CHAIN=off`. Sampled requests are untouched.
|
|
2638
|
+
|
|
2639
|
+
### Fixed
|
|
2640
|
+
|
|
2641
|
+
- The forge decides the MTP norm convention once per tensor set
|
|
2642
|
+
instead of blind-shifting three norm tensors by +1.0 (#301); packs
|
|
2643
|
+
from absolute-encoded sources no longer ship with draft acceptance
|
|
2644
|
+
collapsed to 0 to 2 percent. The runtime refuses to load a
|
|
2645
|
+
double-shifted trunk with a clear error (#306).
|
|
2646
|
+
- Images survive user-message canonicalization on consecutive or
|
|
2647
|
+
retried turns (#327); vision rows survive near-prefix cache
|
|
2648
|
+
restores (#296).
|
|
2649
|
+
- The literal-repetition stop covers width 2+ batched MTP cohorts
|
|
2650
|
+
(#311).
|
|
2651
|
+
- The default request JSONL is content-free as documented (#326).
|
|
2652
|
+
- The installer and app write the PATH line through a symlinked
|
|
2653
|
+
`~/.zshrc` instead of replacing the symlink (#292).
|
|
2654
|
+
- The dashboard Hardware card reports the real chip (#329).
|
|
2655
|
+
- `bench --harness depth-sweep` honors `--depths`, `--seed`, and
|
|
2656
|
+
`--generation-mode`, and refuses `--stock-ar` loudly (#285).
|
|
2657
|
+
- The fp16 fused add+rmsnorm kernel uses the exact 1024-lane
|
|
2658
|
+
dispatch (#319); the packed-concats exactness gate tests its claim
|
|
2659
|
+
honestly (#320).
|
|
2660
|
+
- The NAX turbo verify path stops using padded M=5 lanes that measured
|
|
2661
|
+
slower than stock.
|
|
2662
|
+
- The flight recorder samples non-streaming requests.
|
|
2663
|
+
- The app renders `<br>` variants inside markdown table cells
|
|
2664
|
+
(PR #273 by @El-Patronum).
|
|
2665
|
+
- `quantize: false` module overrides are honored (PR #281 by
|
|
2666
|
+
@shiftedx).
|
|
2667
|
+
- Capture tooling persists exact completion token ids on all three
|
|
2668
|
+
lanes (PR #330 by @CharliePetch).
|
|
2669
|
+
|
|
2670
|
+
### Added
|
|
2671
|
+
|
|
2672
|
+
- Experimental, off by default: `MTPLX_FUSE_PROJ` load-time projection
|
|
2673
|
+
fusion (port of PR #316 by @grzracz), `MTPLX_VK_CROSSROW` crossrow
|
|
2674
|
+
wide-verify kernel, draft-confidence tracing with confidence-gated
|
|
2675
|
+
draft width, and marathon postcommit protection.
|
|
2676
|
+
- `HISTORY.md`: the dated record of putting native MTP on Apple
|
|
2677
|
+
Silicon.
|
|
2678
|
+
|
|
2679
|
+
## [2.9.1] - 2026-08-22
|
|
2680
|
+
|
|
2681
|
+
### Fixed
|
|
2682
|
+
|
|
2683
|
+
- **Agent sessions could silently truncate and then crash near 19,000
|
|
2684
|
+
tokens** (#310). Paged-KV capacity now derives from the pages actually
|
|
2685
|
+
allocated; long coding sessions run to the model's full advertised
|
|
2686
|
+
context.
|
|
2687
|
+
- **Shutdown segfault** (#303). The daemon parks its model-owner thread
|
|
2688
|
+
and clears MLX streams at exit; quit and restart are clean.
|
|
2689
|
+
- **Turbo applied its full configuration.** One turbo fast-path flag
|
|
2690
|
+
shipped runtime-dead in 2.9.0. The fast-path env is now a single
|
|
2691
|
+
shared block, `/health` reports exactly what the profile set, and a
|
|
2692
|
+
per-lane kernel selfcheck runs at startup.
|
|
2693
|
+
- **Multi-turn cache reuse on agent lanes.** One tokenization policy
|
|
2694
|
+
across all encode paths (no more cache walls at reasoning
|
|
2695
|
+
boundaries), tool-call turns bank their generated output directly
|
|
2696
|
+
from live KV, and interrupted background commits retry.
|
|
2697
|
+
- **Long sessions stop re-deriving prior reasoning.** Client-echoed
|
|
2698
|
+
reasoning is rendered for turns the committed cache has not covered,
|
|
2699
|
+
ending marathon re-thinks of already-derived plans.
|
|
2700
|
+
- Stamped pack draft-sampler settings win over stale client-side pins.
|
|
2701
|
+
- Model pack updates resolve the exact installed pack directory, so a
|
|
2702
|
+
stale legacy bare-name cache no longer shadows the canonical copy
|
|
2703
|
+
into a zero-byte no-op, and update progress reports cumulative bytes
|
|
2704
|
+
(first pip/brew release with the fixes app users received in the
|
|
2705
|
+
2.9.0 updater hotfix, build 2009001).
|
|
2706
|
+
|
|
2707
|
+
### Changed
|
|
2708
|
+
|
|
2709
|
+
- **OpenCode** runs uncapped by default (the managed plugin strips
|
|
2710
|
+
exactly the injected 32,000 ceiling; explicit caps pass through),
|
|
2711
|
+
with reasoning, effort selection, reasoning round-trip, and session
|
|
2712
|
+
cache identity honored end to end.
|
|
2713
|
+
- **Pi** gets a working reasoning-effort picker, a real advertised
|
|
2714
|
+
output ceiling (instead of Pi's silent 16,384 default), and a managed
|
|
2715
|
+
extension for cap hygiene and session identity — written identically
|
|
2716
|
+
by the app and `mtplx start pi`.
|
|
2717
|
+
- **Hermes** requests carry client identity and configured reasoning
|
|
2718
|
+
effort, and the server strips Hermes's injected 65,536 default cap.
|
|
2719
|
+
- `mtplx doctor` reports advertised output ceilings and the actual
|
|
2720
|
+
configured port for agent lanes.
|
|
2721
|
+
|
|
2722
|
+
### Added
|
|
2723
|
+
|
|
2724
|
+
- **Flight recorder**: per-second per-request telemetry (tok/s, context,
|
|
2725
|
+
speculative acceptance by depth, verify/draft time split, outcome —
|
|
2726
|
+
cancelled and disconnected requests included) as local JSONL under
|
|
2727
|
+
`~/.mtplx/metrics`, rotation-capped at 256 MB. Disable with
|
|
2728
|
+
`MTPLX_FLIGHT_RECORDER=off`.
|
|
2729
|
+
- `GET /v1/mtplx/flight`: live phase, tok/s, acceptance, stall age, and
|
|
2730
|
+
generated-text tail for the request in flight.
|
|
2731
|
+
- `mtplx trace`: session timelines joined to OpenCode history,
|
|
2732
|
+
cache-reuse analysis, automatic pathology flags, repetition
|
|
2733
|
+
autopsies, and per-session HTML reports.
|
|
2734
|
+
|
|
2735
|
+
## [2.9.0] - 2026-08-20
|
|
2736
|
+
|
|
2737
|
+
See the release notes: <https://mtplx.com/releases/notes/v2.9.0.html>.
|
|
2738
|
+
|
|
2739
|
+
## [2.8.3] - 2026-08-18
|
|
2740
|
+
|
|
2741
|
+
### Fixed
|
|
2742
|
+
|
|
2743
|
+
- **Streaming stays smooth while you actually touch the app.** The
|
|
2744
|
+
freeze-then-burst stutter that only appeared when a human was
|
|
2745
|
+
scrolling or moving the mouse — and never under hands-off testing —
|
|
2746
|
+
is fixed. The window-measurement guard ran in a run-loop phase that
|
|
2747
|
+
macOS skips while input events keep arriving, so precisely when you
|
|
2748
|
+
interacted, every layout pass re-measured the whole conversation and
|
|
2749
|
+
screen updates coalesced into bursts. The guard now runs on every
|
|
2750
|
+
run-loop turn, input storms included. Measured on the same machine,
|
|
2751
|
+
build, and prompt with synthesized human input: 40 s of continuous
|
|
2752
|
+
wheel-scrolling went from 70 UI stalls (18.7 s frozen, worst 1.3 s)
|
|
2753
|
+
to one 197 ms stall; 30 s of mouse movement over the transcript went
|
|
2754
|
+
from 91 stalls (26.7 s frozen) to zero.
|
|
2755
|
+
- **Scrolling up mid-generation no longer fights you.** The
|
|
2756
|
+
auto-follow used to yank the view back to the bottom against your
|
|
2757
|
+
fingers: its user-scroll signal was set asynchronously (the
|
|
2758
|
+
synchronous bottom-pin raced it), trackpad momentum ran unguarded,
|
|
2759
|
+
and classic wheel mice never registered as scrolling at all. User
|
|
2760
|
+
scrolling now wins immediately and in every form — pin attempts are
|
|
2761
|
+
refused while you scroll, momentum is covered, and scrolling back to
|
|
2762
|
+
the bottom re-engages following, matching how it already behaved for
|
|
2763
|
+
slow trackpad drags.
|
|
2764
|
+
- **Cancelled generations now leave full telemetry.** Stopping a reply
|
|
2765
|
+
mid-stream previously logged a stub record with no stream-smoothness
|
|
2766
|
+
census — the exact runs users complain about were the ones with no
|
|
2767
|
+
data. Cancelled requests now record the producer gap census, sliding
|
|
2768
|
+
throughput windows, and true decode rate for the streamed portion.
|
|
2769
|
+
- **Fixed a quadratic decode-cost path on whitespace-free content.**
|
|
2770
|
+
The incremental detokenizer's force-flush escape (tables, URLs,
|
|
2771
|
+
minified code) could never trim its token cache, so every new token
|
|
2772
|
+
re-decoded a growing buffer. The cache now stays bounded on
|
|
2773
|
+
arbitrarily long runs.
|
|
2774
|
+
|
|
2775
|
+
- **The transcript can no longer go blank mid-generation.** The first
|
|
2776
|
+
2.8.3 candidate swapped the chat transcript to a lazy stack for a
|
|
2777
|
+
layout-cost fix; under fast streaming with the app's own scroll
|
|
2778
|
+
driver, the lazy container intermittently culled every visible row —
|
|
2779
|
+
flicker escalating to an entirely empty chat while the engine kept
|
|
2780
|
+
streaming. The transcript and both streaming-card stacks are eager
|
|
2781
|
+
again (row count stays bounded by the earlier-history slicer), and
|
|
2782
|
+
live-streamed markdown tables — including wrapping cells — render
|
|
2783
|
+
correctly while they arrive.
|
|
2784
|
+
- **Thinking is plain text now.** The reasoning well no longer runs
|
|
2785
|
+
the model's thoughts through the markdown renderer, and the live
|
|
2786
|
+
three-line ticker anchors its window at line breaks — rendered
|
|
2787
|
+
thought lines never re-wrap or visibly rewrite themselves as new
|
|
2788
|
+
tokens land.
|
|
2789
|
+
- **The stream no longer freezes and catches up in bursts.** Three
|
|
2790
|
+
server-side delivery fixes: the incremental decoder force-flushes
|
|
2791
|
+
whitespace-free runs (table separator rows, URLs, minified code)
|
|
2792
|
+
instead of holding them for their full length; freshly committed
|
|
2793
|
+
tokens are emitted before cache housekeeping barriers instead of
|
|
2794
|
+
after; and the auth gate was rewritten as pure ASGI so it no longer
|
|
2795
|
+
relays every stream frame through a buffered middleware channel.
|
|
2796
|
+
Measured on the same prompt and settings as the field report:
|
|
2797
|
+
sub-second delivery silences per answer dropped from ~30 to single
|
|
2798
|
+
digits, and generator-side gaps over 200 ms dropped to zero. Every
|
|
2799
|
+
request record now logs a producer gap census
|
|
2800
|
+
(`producer_gap_ms_p95`/`_max`, `producer_gaps_over_200ms`) so
|
|
2801
|
+
stream smoothness is auditable, not vibes.
|
|
2802
|
+
- **Finished replies no longer make the app progressively hotter.**
|
|
2803
|
+
Each settled turn leaked an invisible repeat-forever pulse animation
|
|
2804
|
+
(the thinking-indicator dots), and every leaked pulse drove display
|
|
2805
|
+
cycles that re-measured the window against the whole transcript —
|
|
2806
|
+
CPU at rest climbed with every completed reply, up to ~half a core.
|
|
2807
|
+
The dots now stop by construction when they leave the screen, and
|
|
2808
|
+
the window-measurement guard re-asserts itself after each render
|
|
2809
|
+
phase. With no engine running the app sits at 0.0% CPU regardless
|
|
2810
|
+
of transcript size; the remaining background cost while a daemon is
|
|
2811
|
+
running is the dashboard feed, tracked for the next release.
|
|
2812
|
+
|
|
2813
|
+
- **The desktop app no longer renders the whole transcript per frame —
|
|
2814
|
+
long chats stay smooth on screen, not just on the wire.** Founder
|
|
2815
|
+
testing on a heavy multi-turn conversation at temperature 1.0 caught
|
|
2816
|
+
the other half of the freeze-and-burst reports: the app's window
|
|
2817
|
+
re-measured every realized message on every layout invalidation (a
|
|
2818
|
+
third of the main thread at idle, up to 62×/s while streaming — the
|
|
2819
|
+
guard meant to prevent this had silently never applied), markdown
|
|
2820
|
+
fence classification re-walked every character of every block per
|
|
2821
|
+
frame, per-delta paths copied the entire answer to test emptiness,
|
|
2822
|
+
and scroll pacing state invalidated the full view tree per tick. All
|
|
2823
|
+
of it is now O(new content): fence counts and syntax-lex state are
|
|
2824
|
+
computed once per block, the min-size walk is dead, and the 10 Hz
|
|
2825
|
+
metrics chip no longer re-evaluates every bubble or parses its stream
|
|
2826
|
+
byte-by-byte. Catch-up after any hiccup is rate-limited (max 256
|
|
2827
|
+
chars/frame) so it reads as fast typing, never a paste.
|
|
2828
|
+
- **Markdown tables no longer draw rows on top of each other.** Table
|
|
2829
|
+
cells measured one line tall (the horizontal scroller proposes no
|
|
2830
|
+
width) but drew wrapped, so long cells bled over the rows below.
|
|
2831
|
+
Cells now measure at their placement width.
|
|
2832
|
+
- **Chat streaming no longer freezes mid-response and then dumps the
|
|
2833
|
+
backlog in one burst.** 2.8.0 added a wire safeguard for the uncapped
|
|
2834
|
+
repetition stop that held a fixed ~448-token tail off the stream on
|
|
2835
|
+
every uncapped request — which is every desktop, web, and agent chat.
|
|
2836
|
+
At chat speeds that silenced the stream from roughly token 320 to
|
|
2837
|
+
token 768: the visible symptom was reasoning freezing for 6-11
|
|
2838
|
+
seconds while the speed readout collapsed, then a flood of text at
|
|
2839
|
+
once, plus a final end-of-response burst. Capped requests (every
|
|
2840
|
+
benchmark and QA row) never arm the safeguard, which is how three
|
|
2841
|
+
releases shipped it unnoticed. The holdback is now engaged only while
|
|
2842
|
+
the output actually shows a forming loop — healthy responses stream
|
|
2843
|
+
live, byte for byte, exactly like 2.7.1 — and a real runaway loop
|
|
2844
|
+
still gets trimmed before most of it reaches the wire.
|
|
2845
|
+
(`MTPLX_REPETITION_STREAM_HOLDBACK=candidate|strict|off` selects the
|
|
2846
|
+
new default, the 2.8.0-2.8.2 behavior, or the pre-2.8 wire.)
|
|
2847
|
+
- **Fresh daemons no longer burn 30-60+ seconds of full-throttle GPU
|
|
2848
|
+
"warming" contexts chats never reach.** The turbo profile's 2.8.0
|
|
2849
|
+
background warm ladder walked prefills up to 32,768 tokens after
|
|
2850
|
+
every boot so deep-context *benchmark rows* would start warm — at the
|
|
2851
|
+
cost of every real user's Mac spinning up after launch (the "idle GPU
|
|
2852
|
+
burn" field reports), warm rungs re-firing between chat turns, and a
|
|
2853
|
+
chat sent mid-rung seeing multi-second time-to-first-token. The
|
|
2854
|
+
product ladder is back to the two rungs interactive chat actually
|
|
2855
|
+
touches (boot cost ~4.5 s on the 27B, matching 2.7.1); benchmark
|
|
2856
|
+
harnesses opt into the deep ladder with `MTPLX_WARMUP_LADDER`.
|
|
2857
|
+
Background warm steps now also wait for 90 seconds of request quiet
|
|
2858
|
+
(`MTPLX_WARMUP_IDLE_GRACE_S`) before touching the model, so warming
|
|
2859
|
+
never competes with an active conversation.
|
|
2860
|
+
- **The release pillar gate now fails on streaming freezes.** Every
|
|
2861
|
+
existing gate capped `max_tokens`, so the entire uncapped code path —
|
|
2862
|
+
the one every chat client uses — was invisible to release QA. A new
|
|
2863
|
+
`uncapped_stream_cadence` gate sends the real uncapped streamed chat
|
|
2864
|
+
and fails the release on any delivered-content gap over 2 seconds.
|
|
2865
|
+
|
|
2866
|
+
## [2.8.2] - 2026-08-17
|
|
2867
|
+
|
|
2868
|
+
### Fixed
|
|
2869
|
+
|
|
2870
|
+
- **`mtplx start` no longer re-runs tuning on every launch.** The tune
|
|
2871
|
+
record is keyed by a hash of the exact tune settings, and 2.8.0's
|
|
2872
|
+
per-model profile work updated the settings the tuner *saves* under
|
|
2873
|
+
without updating the hand-built dict the start wizard *looks up* with —
|
|
2874
|
+
the two hashes could never match again, so every start re-offered
|
|
2875
|
+
"Run tuning [recommended]" and accepting it meant minutes of maxed-out
|
|
2876
|
+
GPU with the API port still closed. That combination is the "loads the
|
|
2877
|
+
model, machine heats up, requests never arrive" experience reported
|
|
2878
|
+
within hours of 2.8.1 (#280). Save and lookup now derive the key
|
|
2879
|
+
through one shared constructor, with a regression test that fails if
|
|
2880
|
+
they ever drift apart again.
|
|
2881
|
+
- **The wizard tunes the model you actually picked.** A wizard pick was
|
|
2882
|
+
not marked as an explicit model selection, so the tuner re-resolved
|
|
2883
|
+
the hardware default: picking the 4B on a machine whose default is the
|
|
2884
|
+
27B tuned the 27B — minutes of the wrong benchmark — and then saved a
|
|
2885
|
+
record the launched model could never use. Wizard picks now carry the
|
|
2886
|
+
same explicitness markers as CLI flags.
|
|
2887
|
+
- **A picked local model can no longer be silently swapped for the
|
|
2888
|
+
canonical default.** A local folder whose name matches the verified
|
|
2889
|
+
default (an LM Studio-style `Youssofal/Qwen…` layout, #279) was
|
|
2890
|
+
re-routed through default-model selection, replacing the on-disk path
|
|
2891
|
+
with the Hugging Face repo id — and start then demanded a download for
|
|
2892
|
+
a model that was already installed.
|
|
2893
|
+
- **Idle daemons no longer hammer `session-bank/manifest.sqlite`.**
|
|
2894
|
+
Every `/health` and dashboard poll opened the SSD session-cache
|
|
2895
|
+
manifest and ran a full-table aggregate (about eight sqlite opens per
|
|
2896
|
+
health check, continuously visible in Activity Monitor, #280). The
|
|
2897
|
+
aggregate is now cached against the store's mutation generation with a
|
|
2898
|
+
5-second staleness bound: steady-state polling costs at most one
|
|
2899
|
+
manifest read per 5 s instead of dozens per second.
|
|
2900
|
+
- **`mtplx --version` reports the right version again.** The 2.8.1
|
|
2901
|
+
hotfix bumped the package version but missed the CLI banner constant,
|
|
2902
|
+
so the published 2.8.1 wheel identified itself as `2.8.0 (2.8.1)` —
|
|
2903
|
+
exactly the line users check to confirm they escaped the 2.8.0 vision
|
|
2904
|
+
defect.
|
|
2905
|
+
|
|
2906
|
+
## [2.8.1] - 2026-08-17
|
|
2907
|
+
|
|
2908
|
+
### Fixed
|
|
2909
|
+
|
|
2910
|
+
- **A cached vision conversation can no longer serve another image's
|
|
2911
|
+
context.** Session frontiers are keyed by token ids, and every image
|
|
2912
|
+
placeholder shares one id, so after 2.8.0 taught vision histories to
|
|
2913
|
+
commit (the long-session cache fix), a request that repeated a
|
|
2914
|
+
transcript with a different image could restore the previous image's
|
|
2915
|
+
KV wholesale. Our own release gate's correctness sentinel caught it
|
|
2916
|
+
before the DMG went out. Vision turns now keep their full warm-cache
|
|
2917
|
+
reuse through the content-keyed store (identical pixels restore
|
|
2918
|
+
everything, different pixels never read past the image) and simply
|
|
2919
|
+
stop advancing the raw-id session frontier. PyPI 2.8.0 shipped with
|
|
2920
|
+
the defect for about an hour; 2.8.1 is the same release plus this
|
|
2921
|
+
fix, and it is what the desktop build carries.
|
|
2922
|
+
|
|
2923
|
+
## [2.8.0] - 2026-08-17
|
|
2924
|
+
|
|
2925
|
+
### Added
|
|
2926
|
+
|
|
2927
|
+
- **Sharing the API over your network is now a one-liner.** `mtplx serve
|
|
2928
|
+
--host 0.0.0.0 --api-key-file ~/.mtplx/api-key` creates the key file with
|
|
2929
|
+
a fresh key when it doesn't exist (0600, printed once) instead of dying on
|
|
2930
|
+
the exact recovery command our own non-localhost refusal suggests. Wildcard
|
|
2931
|
+
binds print a dialable `Network OpenAI API Base URL` (your Mac's LAN
|
|
2932
|
+
address — what Parallels/VM guests and other devices should use), `--host`
|
|
2933
|
+
finally has help text, and docs/server.md gained a sharing section.
|
|
2934
|
+
Keyless non-localhost binds still refuse: that guard is the product.
|
|
2935
|
+
- **`/health` tells you when the engine is degraded.** A new additive
|
|
2936
|
+
`degradation` block reports compiled-verify state with the reason for any
|
|
2937
|
+
permanent-eager fallback, profile env keys an operator override beat, and
|
|
2938
|
+
NAX/GQA kernel bail counters. "Looks like turbo, runs slow" is no longer
|
|
2939
|
+
possible to miss from a harness.
|
|
2940
|
+
- **`mtplx doctor` prints the compiled-verify fence** — mode, threshold, and
|
|
2941
|
+
which profile or env supplied it (#255).
|
|
2942
|
+
- **Richer per-response `mtplx_stats`:** `finish_reason`,
|
|
2943
|
+
`draft_sampler_policy`/ownership/`greedy_coupled`,
|
|
2944
|
+
`repetition_stop_triggered` (+reason), `content_empty_reason` on capped
|
|
2945
|
+
thinking rows, and visible clamp stats (`context_cap_applied`,
|
|
2946
|
+
`effective_max_tokens`). All quiet-envelope: stamped only when they apply.
|
|
2947
|
+
- **Streaming golden coverage and measurement guidance.** The request
|
|
2948
|
+
matrix gains SSE arms, and the benchmarking guide now documents response
|
|
2949
|
+
caps, `enable_thinking:false` for capped harnesses, streaming measurement
|
|
2950
|
+
rules (TTFT at first *content* delta; progress frames are spec-valid
|
|
2951
|
+
empty deltas), and the exact prompt-scoring array contract.
|
|
2952
|
+
- **Warmup knobs:** the turbo profile ships `MTPLX_WARMUP_LADDER` crossing
|
|
2953
|
+
every compiled-verify bucket class up to its fence, and
|
|
2954
|
+
`MTPLX_WARMUP_PREFILL_CHUNK` is now env-tunable (default unchanged).
|
|
2955
|
+
- **AR mode joins the session bank (#246).** `--generation-mode ar` (and
|
|
2956
|
+
`--no-mtp`) now restores warm prefixes through the same path MTP uses and
|
|
2957
|
+
reports real cache stats (`cached_tokens`, `cache_source`,
|
|
2958
|
+
`cache_miss_reason`, true `prompt_tps`), so an AR control arm measures
|
|
2959
|
+
decode alone instead of paying a full re-prefill every turn.
|
|
2960
|
+
- **Prompt scoring on `/v1/completions`.** `echo: true` with `logprobs` and
|
|
2961
|
+
`max_tokens: 0` scores an entire prompt in one call: per-token logprobs,
|
|
2962
|
+
top-K alternatives, byte-exact `text_offset`, and a `token_ids` identity
|
|
2963
|
+
array. This is the lane KL-divergence quality harnesses need.
|
|
2964
|
+
- **Per-request draft-sampler resolver.** The draft temperature resolves per
|
|
2965
|
+
request from the artifact stamp, the request's own sampling, and greedy
|
|
2966
|
+
coupling at temperature 0, with the resolved value stamped in
|
|
2967
|
+
`mtplx_stats` so telemetry always equals engine reality.
|
|
2968
|
+
- **KV quantization is a real decode feature.** Paged q8 KV runs through a
|
|
2969
|
+
dedicated kernel on the decode path (with a dequant memo below the kernel
|
|
2970
|
+
threshold), and `mtplx_stats` reports the memo and kernel counters.
|
|
2971
|
+
- **Faster streaming under load.** The SSE hot path moved to a loop-fed
|
|
2972
|
+
queue with a constant envelope and cheaper per-chunk encoding, lowering
|
|
2973
|
+
per-token server overhead at high decode speeds.
|
|
2974
|
+
- **`/v1/messages` protocol conformance.** Parallel tool use, streamed
|
|
2975
|
+
usage accounting, and strict rejection of fields that were previously
|
|
2976
|
+
ignored silently.
|
|
2977
|
+
- **Bridge hygiene.** The OpenCode and Pi plugins no longer delete a
|
|
2978
|
+
client's explicitly configured capabilities, and the no-tools stream
|
|
2979
|
+
filter stops eating turns (code fences are exempt from tool-markup
|
|
2980
|
+
suppression).
|
|
2981
|
+
|
|
2982
|
+
### Changed
|
|
2983
|
+
|
|
2984
|
+
- **Quickstart leads with "Auto (recommended)".** Auto pins nothing — the
|
|
2985
|
+
engine resolves the launch profile per model (Turbo for the quantized
|
|
2986
|
+
flagships). A previously saved default "sustained" state migrates to Auto
|
|
2987
|
+
once; deliberate picks (including Sustained Max) keep pinning. The macOS
|
|
2988
|
+
app's "auto" likewise emits no `--profile` flag anymore, so legacy hybrid
|
|
2989
|
+
and renamed model directories stop launching pinned to sustained.
|
|
2990
|
+
- **Discover shows every MTPLX build.** Any repo with "mtplx" in its name
|
|
2991
|
+
appears (case-insensitive, any position), results are collected until the
|
|
2992
|
+
page fills *after* filtering instead of slicing the top-30-by-downloads
|
|
2993
|
+
first, and the wall/CLI default page is 100.
|
|
2994
|
+
- **Config values are real pins.** A profile or sampler value from
|
|
2995
|
+
`config.toml` is honored as explicit in both directions (config
|
|
2996
|
+
"sustained" stays sustained; values equal to a family default are no
|
|
2997
|
+
longer treated as unset) and startup prints one line saying so. Injected
|
|
2998
|
+
launch defaults carry provenance and never override an artifact's stamp.
|
|
2999
|
+
- **KV quantization actually saves memory now.** The q8 dequant mirror is
|
|
3000
|
+
offset-sized with geometric growth and is released once a request latches
|
|
3001
|
+
onto the kernel path; q4 never allocates a mirror. Numerics route once
|
|
3002
|
+
per request (a q8 request starting below the kernel threshold stays on
|
|
3003
|
+
dequant math for its whole life instead of switching mid-generation), the
|
|
3004
|
+
byte stat counts the mirror, and the CLI help states the honest contract.
|
|
3005
|
+
- **Prompt-scoring (`/v1/completions` echo+logprobs) follows OpenAI array
|
|
3006
|
+
semantics:** all arrays length n with `null` at index 0, the scored token
|
|
3007
|
+
always present in its own top-K map, and a `token_ids` array for stable
|
|
3008
|
+
identity. `logprobs` without `echo` returns a 400 that names the exact
|
|
3009
|
+
requirement; `logprobs: 0` is a valid request.
|
|
3010
|
+
- **`reasoning_effort: "high"` maps up the engine's real effort ladder**
|
|
3011
|
+
(xhigh on Qwen 3.8) instead of silently falling back to the default;
|
|
3012
|
+
unknown values return 400.
|
|
3013
|
+
- **`MTPLX_CLIENT` is an observability label, not an owner.** Managed-client
|
|
3014
|
+
policy requires per-request evidence (headers/UA/body), so an anonymous
|
|
3015
|
+
benchmarker's sampling settings are honored against app- or
|
|
3016
|
+
hermes-launched daemons. Claude Code's `claude-cli/*` user agent is now
|
|
3017
|
+
recognized as a client hint.
|
|
3018
|
+
- **Draft-sampler provenance is explicit end-to-end:** a daemon launched
|
|
3019
|
+
with a bare `--draft-temperature` pins it as operator-explicit, while
|
|
3020
|
+
family-default launches ship unpinned values that keep the temperature
|
|
3021
|
+
curve live.
|
|
3022
|
+
|
|
3023
|
+
### Fixed
|
|
3024
|
+
|
|
3025
|
+
- **Streamed text now concatenates to exactly the non-stream text.** The
|
|
3026
|
+
model separates `</think>` from its answer with a blank line, and the
|
|
3027
|
+
stream lane leaked that separator as content deltas while the non-stream
|
|
3028
|
+
lane stripped it, so diffing the two transports at temperature 0 showed a
|
|
3029
|
+
whitespace mismatch on every thinking response. Streamed content is
|
|
3030
|
+
edge-trimmed on the wire the same way the non-stream cleaner strips it;
|
|
3031
|
+
interior whitespace (markdown structure) is untouched, and transcript
|
|
3032
|
+
canonicalization stays byte-exact. One cosmetic shape remains: a stream
|
|
3033
|
+
that ends in a tool call can still carry a trailing blank line ahead of
|
|
3034
|
+
the tool call in some chunkings.
|
|
3035
|
+
- **Batched-AR responses no longer carry draft-sampler stamps.** Under
|
|
3036
|
+
concurrency, requests that ride the batched AR lane merged request-policy
|
|
3037
|
+
observability wholesale, so an AR response could report a draft sampler
|
|
3038
|
+
policy plus `draft_time_s: 0.0`, the exact fabricated-stats shape the
|
|
3039
|
+
serial lane already scrubbed. Both batched sites now scrub the same way.
|
|
3040
|
+
- **The launch environment can no longer steer request behavior.** Three
|
|
3041
|
+
branches (the OpenCode default-sampler override, the single-tool-call
|
|
3042
|
+
stream policy, and the post-request cache clear) keyed on a client hint
|
|
3043
|
+
that fell back to the daemon's `MTPLX_CLIENT` label, so an operator-set
|
|
3044
|
+
env var could change anonymous API callers' sampling. Behavior now keys
|
|
3045
|
+
on per-request evidence only; the env-inclusive hint remains as an
|
|
3046
|
+
observability label.
|
|
3047
|
+
- **Prompt scoring's `top_logprobs` is correct under string collisions and
|
|
3048
|
+
safe for harness parsers.** When two token ids decode to the same display
|
|
3049
|
+
string, the scored token's entry now always carries its true logprob
|
|
3050
|
+
(previously a higher-ranked collision kept the other token's value, which
|
|
3051
|
+
inflated string-keyed KL readings), and index 0 is an empty dict instead
|
|
3052
|
+
of null so parsers that iterate entries with `.items()` don't crash on a
|
|
3053
|
+
spec-shaped response. `token_logprobs[0]` stays null.
|
|
3054
|
+
- **`mtplx quickstart`/`serve` find branded local builds.** The canonical
|
|
3055
|
+
model id resolver only checked the Hub snapshot layout, so a forge-built
|
|
3056
|
+
pack living under its bare name (which `mtplx models` lists and bench
|
|
3057
|
+
model selection happily uses) was reported "not cached" with a 20 GB
|
|
3058
|
+
re-download suggestion. The resolver now falls back to the branded
|
|
3059
|
+
directory under the same contract validation.
|
|
3060
|
+
- **`mtplx doctor --summary` prints the compiled-verify fence** like the
|
|
3061
|
+
full report already did.
|
|
3062
|
+
- **The MTP lane's final session-bank commit is timed and guarded like
|
|
3063
|
+
AR's.** The post-response commit forward was billed into measured decode
|
|
3064
|
+
elapsed (understating MTP tok/s) and an allocation failure there could
|
|
3065
|
+
destroy a completed response; it is now outside the measured window and
|
|
3066
|
+
a failure downgrades to "no session commit this turn".
|
|
3067
|
+
- **The macOS app's model-family detection matches the engine's.** The app
|
|
3068
|
+
still classified stock `Qwen/Qwen3-8B` as the 3.8 family (temperature 1.0
|
|
3069
|
+
defaults on the wrong model) and let folder names outrank forge
|
|
3070
|
+
provenance; it now uses the engine's boundary guard and provenance-first
|
|
3071
|
+
order.
|
|
3072
|
+
- **The web chat UI no longer labels this launch's context cap as "the
|
|
3073
|
+
model's" context.** The max-tokens help fed `/health`'s `context_window` —
|
|
3074
|
+
which memory sizing can cap far below native — into a string attributing
|
|
3075
|
+
it to the model, so a 32GB machine serving a 256k-native model read
|
|
3076
|
+
"the model's 16.4k context". It now says whose number it is: this
|
|
3077
|
+
server's active context window.
|
|
3078
|
+
- **Stock `Qwen/Qwen3-8B` is no longer misclassified as the Qwen 3.8
|
|
3079
|
+
family** (a regression in the artifact-identity fix gave Alibaba's most
|
|
3080
|
+
popular size the wrong sampler defaults and reasoning codec), and family
|
|
3081
|
+
resolution now trusts forge provenance over the folder name, so renamed
|
|
3082
|
+
or symlinked model directories keep their true family (#268).
|
|
3083
|
+
- **Agentic sessions commit again.** The post-turn session commit now builds
|
|
3084
|
+
its prefix with the same committed-reasoning canonicalization the next
|
|
3085
|
+
request will send, so commits byte-extend instead of failing every turn
|
|
3086
|
+
("retokenized_prefix_not_extending_session", 3–4% reuse — #269). The
|
|
3087
|
+
canonicalization gate also refuses on tool-call changes and dropped
|
|
3088
|
+
turns instead of substituting the wrong turn's reasoning, works for
|
|
3089
|
+
OpenCode's stripped preambles, and repair re-encodes preserve committed
|
|
3090
|
+
reasoning. Two system contracts became suffix contracts, ending
|
|
3091
|
+
full-context re-prefill when they flip.
|
|
3092
|
+
- **A prompt that fills the context window returns a clear 400**
|
|
3093
|
+
(`context_length_exceeded`) instead of silently generating one token,
|
|
3094
|
+
and a fitting prompt whose `max_tokens` exceeds the remainder is clamped
|
|
3095
|
+
visibly — no more phantom 0.4 tok/s rows at 256k.
|
|
3096
|
+
- **`finish_reason` is truthful everywhere:** a length cap beats
|
|
3097
|
+
`tool_calls` in non-streaming chat (matching streaming), `/v1/messages`
|
|
3098
|
+
maps priority `max_tokens` → `stop_sequence` → `tool_use`, the
|
|
3099
|
+
completions stream trims stop strings like non-stream, and empty content
|
|
3100
|
+
on a capped thinking row recovers or is stamped with the reason on both
|
|
3101
|
+
paths.
|
|
3102
|
+
- **AR mode reports honest numbers:** no fabricated draft temperature, and
|
|
3103
|
+
elapsed time no longer includes the post-response session-bank forward
|
|
3104
|
+
pass (which also can no longer destroy a finished response on failure).
|
|
3105
|
+
- **Draft-sampler telemetry equals engine reality.** The resolver no longer
|
|
3106
|
+
reports policy "none" while drafting at target temperature; the
|
|
3107
|
+
temperature-scale env applies before the stamp; artifact draft stamps
|
|
3108
|
+
survive launching under a different profile; OpenCode's server-side
|
|
3109
|
+
defaults are an honest `launch_default` tier with the coupling curve
|
|
3110
|
+
live; batch cohorts key on the target sampling triple.
|
|
3111
|
+
- **Repetition-guard stops stay off the wire:** the streaming paths (AR,
|
|
3112
|
+
MTP-K, and both Gemma-4 loops) hold back a detector-window tail while
|
|
3113
|
+
the guard is armed, so trimmed loop garbage never reaches clients, and
|
|
3114
|
+
the stop is visible in public stats.
|
|
3115
|
+
- **Benchmark rows stop paying hidden costs:** compiled-verify prewarm is
|
|
3116
|
+
no longer spent by the boot walk and warms the exact shared traces real
|
|
3117
|
+
rows dispatch; Metal memory caps and over-context refusal now apply to
|
|
3118
|
+
every bench, ladder, one-shot, and quickstart entry (#261) with
|
|
3119
|
+
per-row flushes.
|
|
3120
|
+
- **Streams end honestly:** the post-content commit wait is bounded with
|
|
3121
|
+
live heartbeats and a stall watchdog, explicit cancels emit a terminal
|
|
3122
|
+
frame + `[DONE]`, client disconnects are tagged as such, and
|
|
3123
|
+
`GeneratorExit` no longer logs runtime errors.
|
|
3124
|
+
- **No surface stamps "sustained" for a flagship anymore:** forge's
|
|
3125
|
+
artifact stamp, cached-model listings, doctor's support matrix, bench
|
|
3126
|
+
suites, tune, and the quickstart download branch all report what serve
|
|
3127
|
+
resolution actually picks.
|
|
3128
|
+
- **Sizing and accounting truths:** nested MTP-sidecar layouts are sized
|
|
3129
|
+
correctly (restoring the RAM-aware auto budget on small Macs), the
|
|
3130
|
+
dashboard no longer double-counts the sidecar, the NAX install report
|
|
3131
|
+
reflects the real probe, the batch histogram stops counting phantom
|
|
3132
|
+
width-1 units, and prompt scoring decodes each unique token once instead
|
|
3133
|
+
of ~65k times at long context.
|
|
3134
|
+
|
|
3135
|
+
## [2.7.2] - 2026-08-16
|
|
3136
|
+
|
|
3137
|
+
An emergency fix for `mtplx pull`: on 2.7.1 and older, re-pulling a repo that
|
|
3138
|
+
changed upstream — such as the Qwen 3.8 repos re-published on 2026-08-15 with
|
|
3139
|
+
their vision towers restored — can corrupt the local copy. Upgrade before
|
|
3140
|
+
pulling.
|
|
3141
|
+
|
|
3142
|
+
### Fixed
|
|
3143
|
+
|
|
3144
|
+
- **The Qwen 3.8 Hugging Face artifacts can see again (#263).** All six
|
|
3145
|
+
published 3.8 repos (Bare Speed, Optimized Speed, Optimized Quality and
|
|
3146
|
+
their FP16 siblings) shipped without their vision towers: the forge
|
|
3147
|
+
`mlx_lm.convert` lane serializes only the text model, so the 333
|
|
3148
|
+
`model.visual.*` tensors, `vision_config`, and the preprocessor sidecars
|
|
3149
|
+
were silently dropped. The repos were re-published on 2026-08-15 with the
|
|
3150
|
+
official bf16 tower grafted back as an index-registered
|
|
3151
|
+
`model-vision.safetensors` (byte-for-byte from `Qwen/Qwen3.8-27B`; language
|
|
3152
|
+
and MTP tensors untouched). Existing installs pick the delta up with
|
|
3153
|
+
`mtplx pull`; images, `/health` `vision.enabled`, prompt caching with
|
|
3154
|
+
images, and MTP decode were verified on all six builds. Thanks to
|
|
3155
|
+
@kjellix for the report and the proven graft procedure.
|
|
3156
|
+
- **Forge keeps vision towers from now on.** `mtplx forge build` grafts the
|
|
3157
|
+
source's vision tower, `vision_config`, and preprocessor sidecars into the
|
|
3158
|
+
converted artifact on every lane (`mtplx/vision_graft.py`), and fails
|
|
3159
|
+
closed when a source that declares `vision_config` would produce a blind
|
|
3160
|
+
artifact. A repair script for already-forged artifacts ships as
|
|
3161
|
+
`scripts/graft_vision_tower.py`, and the pillar gate now asserts
|
|
3162
|
+
`vision.enabled` before the vision-cache check so a blind build fails
|
|
3163
|
+
loudly with the real cause.
|
|
3164
|
+
- **`mtplx pull` no longer corrupts files that changed upstream (#258,
|
|
3165
|
+
#234).** The
|
|
3166
|
+
progress downloader treated a size-mismatched *complete* local file as a
|
|
3167
|
+
resumable partial and byte-range-appended the remote tail onto the old
|
|
3168
|
+
content — updating a repo in place (for example the restored 3.8 vision
|
|
3169
|
+
indexes) corrupted `config.json` and `model.safetensors.index.json` and
|
|
3170
|
+
left the local copy unloadable until re-downloaded. Stale files are now
|
|
3171
|
+
discarded and re-fetched whole; genuine `*.incomplete` partials still
|
|
3172
|
+
resume. **Users on ≤2.7.1 should upgrade before pulling repaired repos**;
|
|
3173
|
+
a failed pull from an older build is recovered by deleting the corrupt
|
|
3174
|
+
`config.json` + `model.safetensors.index.json` and pulling again.
|
|
3175
|
+
|
|
3176
|
+
## [2.7.1] - 2026-08-15
|
|
3177
|
+
|
|
3178
|
+
Clears the 2.7.0 known-issues list: `xhigh` is now selectable everywhere it is
|
|
3179
|
+
offered, and the app's KV cache quantization toggle reaches Qwen 3.8.
|
|
3180
|
+
|
|
3181
|
+
### Fixed
|
|
3182
|
+
|
|
3183
|
+
- **`xhigh` no longer bounces back to `medium` in the app.** Choosing it in
|
|
3184
|
+
Inference settings while the model was running, or running
|
|
3185
|
+
`mtplx config set reasoning_effort xhigh`, was rejected: those two writing
|
|
3186
|
+
surfaces carried their own hardcoded effort list that stopped at `high`,
|
|
3187
|
+
while the request path already knew `xhigh`. Because the live-settings POST
|
|
3188
|
+
is all-or-nothing, one unknown level threw away the whole payload and the
|
|
3189
|
+
picker snapped back to the family default. All four writing surfaces
|
|
3190
|
+
(serve/CLI `--reasoning-effort`, `mtplx config set`, the live-settings POST)
|
|
3191
|
+
now read one shared vocabulary in `mtplx/reasoning_effort.py`, and narrowing
|
|
3192
|
+
to what the loaded model actually supports stays where it belongs — the
|
|
3193
|
+
family's own `effort_levels`.
|
|
3194
|
+
- **The app's KV cache quantization toggle now applies to Qwen 3.8.** The
|
|
3195
|
+
launch gate listed only `qwen3_5`/`qwen3_6`, so a 3.8 launch exported
|
|
3196
|
+
nothing while Settings showed `q8`.
|
|
3197
|
+
- **`mtplx doctor` names the model it actually checks** instead of hardcoding
|
|
3198
|
+
the previous default, and turbo's profile note reports the real compiled
|
|
3199
|
+
verify fence (32,768, raised from 12,288 in 2.7.0) rather than the stale one.
|
|
3200
|
+
- **A built app can no longer rank below the release it supersedes.** Sparkle
|
|
3201
|
+
orders updates by `CFBundleVersion` alone, and the number derived from the
|
|
3202
|
+
version was narrow enough that 2.7.1 computed lower than the 2.7.0 that
|
|
3203
|
+
shipped — so a fresh build offered 2.7.0 to itself as an update. The
|
|
3204
|
+
derivation now has room for the whole version and the appcast reads its
|
|
3205
|
+
number back off the built bundle instead of being told one separately.
|
|
3206
|
+
|
|
3207
|
+
### Still open from 2.7.0
|
|
3208
|
+
|
|
3209
|
+
- With reasoning switched off in a plain chat with no tools, Qwen 3.8 can
|
|
3210
|
+
still emit a stray tool call and cut the turn short. Keep thinking on.
|
|
3211
|
+
|
|
3212
|
+
## [2.7.0] - 2026-08-15
|
|
3213
|
+
|
|
3214
|
+
Qwen3.8 support 🎉. Qwen3.8-27B came out on 2026-08-14; this release runs it
|
|
3215
|
+
the way the model card says, with three tuned MTPLX builds, FP16 versions for
|
|
3216
|
+
M1 and M2, and the compiled verify window extended to 32k. The release notes
|
|
3217
|
+
at docs/releases/v2.7.0.md carry the full story and every measurement (all on
|
|
3218
|
+
one M5 Max; nothing was measured on M1 or M2).
|
|
3219
|
+
|
|
3220
|
+
### Added
|
|
3221
|
+
|
|
3222
|
+
- **Qwen 3.8 model family** (`qwen3_8`) with the official inference contract:
|
|
3223
|
+
temperature 1.0 / top-p 0.95 / top-k 20, reasoning effort `xhigh`, `medium`
|
|
3224
|
+
and `low` (coding sessions default to `medium`: 51.5 s against 314.9 s at
|
|
3225
|
+
xhigh on the same correct agent task), thinking preserved in history by
|
|
3226
|
+
default, `chat_template_kwargs.enable_thinking` honored. Live serving is
|
|
3227
|
+
capped at depth 3 for now (depth 4 killed the daemon on drop day).
|
|
3228
|
+
- **Three Qwen 3.8 builds.** Bare Speed (16.0 GB, flat 4-bit: quickest burst
|
|
3229
|
+
chat speeds, lower quality and slower on long coding tasks), Optimized
|
|
3230
|
+
Speed (20.4 GB, 4-bit dynamic quant: great coding speeds and good quality,
|
|
3231
|
+
recommended), Optimized Quality (29.4 GB, 8-bit dynamic quant: good coding
|
|
3232
|
+
speeds and perfect quality; KL to the bf16 teacher 0.00105). Each build
|
|
3233
|
+
states its recommended draft sampler, tuned depth (3) and measured peak in
|
|
3234
|
+
its own metadata; the runtime reads that ahead of profile fallbacks, and
|
|
3235
|
+
the app and CLI launch every build identically.
|
|
3236
|
+
- **FP16 builds for M1 and M2.** Every 3.8 build has an FP16 sibling on the
|
|
3237
|
+
Hub (`-FP16`): the same quantized packs byte for byte, every 16-bit tensor
|
|
3238
|
+
cast bf16 to fp16 (99.992% exact, none overflow). The M1/M2 tier of the CLI
|
|
3239
|
+
and the app routes to them automatically, same picks, order and text.
|
|
3240
|
+
- **`mtplx pull` mirror hint (#259).** A network-shaped download failure with
|
|
3241
|
+
no `HF_ENDPOINT` set now names the mirror knob (CLI env, or Settings,
|
|
3242
|
+
Advanced, HF download mirror in the app). Troubleshooting docs cover both.
|
|
3243
|
+
- **`mtplx tune --require-max-fans`** and a Forge `module_overrides` recipe
|
|
3244
|
+
lane (per-module quantization in one conversion pass; it built Optimized
|
|
3245
|
+
Speed).
|
|
3246
|
+
|
|
3247
|
+
### Changed
|
|
3248
|
+
|
|
3249
|
+
- **Default model.** Fresh installs on M3/M4/M5 with 32 GB or more default to
|
|
3250
|
+
Qwen 3.8 Optimized Speed; M1/M2 get its FP16 sibling; under 32 GB still
|
|
3251
|
+
routes to the 9B. `mtplx quickstart`, `mtplx start` and the app's first-run
|
|
3252
|
+
picker offer the whole 3.8 line-up. `mtplx start` says once when the
|
|
3253
|
+
recommended default moved. Qwen 3.6 Optimized Speed V2 keeps turbo.
|
|
3254
|
+
- **Compiled verify to 32k.** The compiled verify graph stopped at 12,288
|
|
3255
|
+
tokens of context since July; that fence's reason is gone, so turbo now
|
|
3256
|
+
compiles verify to 32,768 tokens (interleaved A/B on Qwen 3.8 Bare Speed:
|
|
3257
|
+
+6.9% at 20k, peak memory flat at 20k and lower at 30k).
|
|
3258
|
+
`MTPLX_COMPILED_VERIFY` can be set by hand.
|
|
3259
|
+
- **Coding agents.** OpenCode and Pi no longer send an output cap of any kind
|
|
3260
|
+
for MTPLX models; Pi sessions carry their real session id so banked
|
|
3261
|
+
prefixes restore from RAM; the session bank's background re-render uses the
|
|
3262
|
+
effort the request ran with; reasoning cut off before the closing think tag
|
|
3263
|
+
is routed as reasoning.
|
|
3264
|
+
- **App.** Qwen 3.8 launch family (turbo, official sampler preset, reasoning
|
|
3265
|
+
effort control with xhigh, Tune AR to D3, exact sizes and measured peaks);
|
|
3266
|
+
the first-run picker shows the trio (FP16 on M1/M2); the Qwen 3.6 Optimized
|
|
3267
|
+
Quality row on M1/M2 resolves to its FP16 build.
|
|
3268
|
+
- **Thermal honesty.** Max-fan sessions hold an ownership token, so a daemon
|
|
3269
|
+
shutting down behind its replacement cannot switch fans back to Auto under
|
|
3270
|
+
it; Forge refuses to benchmark when verified max-fan mode cannot start.
|
|
3271
|
+
- **`mtplx doctor`** judges memory against the model this Mac would actually
|
|
3272
|
+
default to; M5 Max listed in the support matrix.
|
|
3273
|
+
- **Attribution is now required, not preferred.** MTPLX stays Apache-2.0, and
|
|
3274
|
+
the NOTICE file (which Apache-2.0 section 4(d) carries with every copy) now
|
|
3275
|
+
requires products built on MTPLX to show "Powered by MTPLX" inside the
|
|
3276
|
+
product, where a user can see it. A mention in a repo or on a website does
|
|
3277
|
+
not cover it.
|
|
3278
|
+
|
|
3279
|
+
### Fixed
|
|
3280
|
+
|
|
3281
|
+
- **SSD session cache CPU drain.** The cache walked its whole store on every
|
|
3282
|
+
write and every `/health` poll (816,220 files, 89.9 GB, 41.7 s per walk).
|
|
3283
|
+
Reconciliation is now maintenance: only when the store changed, at most 5%
|
|
3284
|
+
of the time, off the writer lock, yielding to live traffic. Idle CPU with a
|
|
3285
|
+
health poller 35% down to 0.2%; per-write cap gate 71 to 159 s down to
|
|
3286
|
+
3 to 6 s.
|
|
3287
|
+
- **macOS 27 crash in the inference settings overlay (#256, #257).** SwiftUI 8
|
|
3288
|
+
traps on a slider with no distinct values; the depth and context-window
|
|
3289
|
+
sliders are now built only when there is something to slide. Reported and
|
|
3290
|
+
fixed by @joshlacal.
|
|
3291
|
+
- Hardware detection uses absolute `/usr/sbin/sysctl` and
|
|
3292
|
+
`/usr/sbin/system_profiler` paths; `doctor` and `tune` no longer run `git`
|
|
3293
|
+
outside a repository (no Xcode Command Line Tools dialog on a clean Mac).
|
|
3294
|
+
- Depth-default resolution honors artifact metadata across profile
|
|
3295
|
+
mismatches; the degrade pin and the no-metadata path both survive. The
|
|
3296
|
+
public depth ceiling follows the artifact reference, not the served-name
|
|
3297
|
+
alias; `tune` validates depths against what the model supports and takes
|
|
3298
|
+
its sampler from the same family contract as `serve`.
|
|
3299
|
+
- `MTPLX_REQUEST_LOG_JSONL=1` logs to the default file instead of a file
|
|
3300
|
+
named `1`. xhigh boot no longer trips strict warmup.
|
|
3301
|
+
|
|
3302
|
+
### Known issues (fixed in 2.7.1)
|
|
3303
|
+
|
|
3304
|
+
- Choosing `xhigh` in the app's Inference settings while the model is running
|
|
3305
|
+
is rejected by the server, as is `mtplx config set reasoning_effort xhigh`;
|
|
3306
|
+
set it before starting the model or pass `--reasoning-effort xhigh`.
|
|
3307
|
+
- The app's KV cache quantization toggle is not applied to Qwen 3.8 models.
|
|
3308
|
+
- With reasoning off in a plain chat with no tools, Qwen 3.8 emitted a stray
|
|
3309
|
+
tool call and cut the turn short on about half of our coding prompts; keep
|
|
3310
|
+
thinking on (the default).
|
|
3311
|
+
|
|
3312
|
+
## [2.6.0] - 2026-08-11
|
|
3313
|
+
|
|
3314
|
+
### Added
|
|
3315
|
+
|
|
3316
|
+
- **Concurrent MTP serving.** Until now speculative decoding was a
|
|
3317
|
+
single-request feature: under concurrency the scheduler fell back to the
|
|
3318
|
+
autoregressive batch lane and every simultaneous caller lost the MTP
|
|
3319
|
+
speedup. `--scheduler-mode mtp_batch` serves independent requests through
|
|
3320
|
+
fixed-width MTP cohorts — each row owns its state, drafts are verified in
|
|
3321
|
+
one batched target forward, and rows join and leave mid-flight without
|
|
3322
|
+
disturbing their neighbours. Two native cohort widths install side by side
|
|
3323
|
+
(three-wide and eight-wide) and the scheduler seals each cohort at the
|
|
3324
|
+
narrowest width that fits, so two concurrent agents no longer pay the
|
|
3325
|
+
padding of an eight-lane launch. Measured on `Qwen3.6-35B-A3B` on an M5 Max,
|
|
3326
|
+
the three-wide lane holds 1.7–1.8× the per-request decode of the padded
|
|
3327
|
+
eight-lane shape, and the composite lane below adds warm-prefix restores on
|
|
3328
|
+
top; against the previous production `ar_batch` route the same concurrent
|
|
3329
|
+
agent workloads decode at 1.6–2.25× per lane, sampled at the model's
|
|
3330
|
+
shipped settings.
|
|
3331
|
+
|
|
3332
|
+
The lane is exact where exactness is claimed and honest where it is not:
|
|
3333
|
+
`--mtp-batch-numerics` selects between `throughput`, `balanced`, and
|
|
3334
|
+
`b1-exact` profiles (documented trade-offs, install-time self-check), and
|
|
3335
|
+
per-request stats now report each row's own truth — its own accepted-depth
|
|
3336
|
+
histogram, its own cohort width, its own restore provenance — rather than
|
|
3337
|
+
cohort-averaged numbers.
|
|
3338
|
+
|
|
3339
|
+
The session bank composes with the cohorts: a request whose prefix is
|
|
3340
|
+
banked restores it at cohort admission (skipping the shared prefill for
|
|
3341
|
+
the covered span), prefills only its uncovered suffix, and commits its own
|
|
3342
|
+
prompt boundary before the merge, so agent fleets with shared system
|
|
3343
|
+
prompts keep warm TTFT under concurrency. The plain `ar_batch` lane learned
|
|
3344
|
+
the same trick: finished rows and prompt-only boundaries commit to the bank
|
|
3345
|
+
from batch mode too.
|
|
3346
|
+
|
|
3347
|
+
- **LiquidAI LFM2 / LFM2.5 support.** The LFM2 family serves natively with a
|
|
3348
|
+
bit-exact ShortConv decode fast-path, a verified think/tool grammar
|
|
3349
|
+
(parser stamp, native tool prompt, and the pythonic streaming dialect the
|
|
3350
|
+
family emits), and a catalog lane for the `llama-ar` shape. IQuest-Coder
|
|
3351
|
+
checkpoints serve target-only AR through the same registry honesty:
|
|
3352
|
+
recognized, served without MTP claims, refused cleanly when a quantization
|
|
3353
|
+
the runtime cannot execute is detected.
|
|
3354
|
+
|
|
3355
|
+
- **Embedding and reranking endpoints.** `POST /v1/embeddings` (OpenAI shape)
|
|
3356
|
+
and `POST /v1/rerank` (Cohere/Jina shape) are now served by the same daemon
|
|
3357
|
+
as chat, so a retrieval-backed setup no longer needs a second inference
|
|
3358
|
+
server beside MTPLX. Both are opt-in through repeatable `--embedding-model`
|
|
3359
|
+
and `--reranker-model` flags on `serve` and `quickstart`, accepting a Hugging
|
|
3360
|
+
Face id or a local path with an optional `REF=served-id` alias. With no flag
|
|
3361
|
+
the endpoints answer 404 and the chat runtime is untouched.
|
|
3362
|
+
|
|
3363
|
+
Retrieval models deliberately bypass the MTP generation path: multi-token
|
|
3364
|
+
prediction makes next-token decoding cheaper, which is meaningless for a
|
|
3365
|
+
model that emits a vector instead of a token stream. They run the transformer
|
|
3366
|
+
stack directly — last-token pooling with L2 normalisation for embeddings, a
|
|
3367
|
+
softmax over the `yes`/`no` logits for reranking, both padded on the right so
|
|
3368
|
+
causal attention leaves every real position bit-identical to an unpadded run.
|
|
3369
|
+
|
|
3370
|
+
Backends are cached by resolved path, so listing one reference as both an
|
|
3371
|
+
embedder and a reranker loads a single copy of the weights and serves both
|
|
3372
|
+
roles from it. `--retrieval-max-resident` caps how many retrieval models stay
|
|
3373
|
+
in memory; beyond it the least recently used one is unloaded. Models load on
|
|
3374
|
+
first request, so an unused endpoint costs nothing.
|
|
3375
|
+
|
|
3376
|
+
`/v1/models` keeps its default listing chat-only, so clients that enumerate
|
|
3377
|
+
models to build a chat picker (OpenCode, Cline, Continue, ...) are never
|
|
3378
|
+
offered an embedder as a conversation target. Retrieval models are listed
|
|
3379
|
+
via `?capability=embedding` or `?capability=rerank`, every entry carries a
|
|
3380
|
+
`capability` field, and a chat completion that requests a retrieval-only id
|
|
3381
|
+
is rejected with a clear 400 instead of being silently answered by the
|
|
3382
|
+
loaded chat model. The settings are configurable from the macOS app and
|
|
3383
|
+
persist in `~/.mtplx/config.toml` as `embedding_models`, `reranker_models`,
|
|
3384
|
+
and `retrieval_max_resident`.
|
|
3385
|
+
|
|
3386
|
+
Checkpoints that ship their own Python inference code (the jina MLX
|
|
3387
|
+
releases bundle `model.py`/`rerank.py`) are only executed after an explicit
|
|
3388
|
+
opt-in: `--retrieval-trust-remote-code` on `serve`/`quickstart`, or
|
|
3389
|
+
`retrieval_trust_remote_code = true` in the config file. Without it the
|
|
3390
|
+
request is refused with a clear 403 naming the flag; models served by
|
|
3391
|
+
MTPLX's own loaders are unaffected.
|
|
3392
|
+
|
|
3393
|
+
`/v1/embeddings` honours OpenAI's `dimensions` parameter with the
|
|
3394
|
+
Matryoshka recipe — truncate to the leading dimensions and re-normalise —
|
|
3395
|
+
matching how MRL-trained models like jina-embeddings-v5 (32→1024) are
|
|
3396
|
+
meant to be shortened. A `dimensions` beyond the model's native width is a
|
|
3397
|
+
clear 400 rather than a silently full-width vector that no longer fits the
|
|
3398
|
+
index the client sized.
|
|
3399
|
+
|
|
3400
|
+
### Fixed
|
|
3401
|
+
|
|
3402
|
+
- **Temperature-0 speculative output matches plain decoding again.** The
|
|
3403
|
+
MTP lane's cold prefill fed the whole prompt through the model in one
|
|
3404
|
+
window, while plain autoregressive decoding splits it into body plus a
|
|
3405
|
+
final single-token step. The two shapes round differently at the last
|
|
3406
|
+
bit, so the speculative lane started from a cache one ulp apart from the
|
|
3407
|
+
AR lane's — enough to flip greedy argmax at a near-tie and break the
|
|
3408
|
+
"temperature 0 output is token-identical" contract on the promotion gate.
|
|
3409
|
+
All cold-prefill paths now partition the prompt identically; the Optimized
|
|
3410
|
+
Speed V2 artifact, which surfaced the flip, passes its greedy exactness
|
|
3411
|
+
gate at every depth again.
|
|
3412
|
+
|
|
3413
|
+
- **Single-request decode speed under sampling recovered.** The eight-way
|
|
3414
|
+
sampling unification routed the single-request lane's per-token sampling
|
|
3415
|
+
through a full-vocabulary host reference (a NumPy partition and float64
|
|
3416
|
+
softmax over 248k logits per sampled token), which measured as a 15-19%
|
|
3417
|
+
decode regression against 2.5.4 on the serving lane. The single-request
|
|
3418
|
+
lane now selects its top-k support on device with the same deterministic
|
|
3419
|
+
tie-breaking as the batched route and moves only those k tokens to the
|
|
3420
|
+
host. The batched cohort lanes keep their float64 host reference
|
|
3421
|
+
unchanged.
|
|
3422
|
+
|
|
3423
|
+
- **Prefix restores no longer corrupt the session bank (#247).** Since the
|
|
3424
|
+
zero-copy cache work, restoring a banked prefix installed the entry's own
|
|
3425
|
+
KV state objects into the borrowing request's cache; the borrower's suffix
|
|
3426
|
+
prefill and decode then wrote in place into pages the entry still
|
|
3427
|
+
referenced. An interleaved near-prefix request could silently rewrite a
|
|
3428
|
+
neighbour's banked span, and every later match served the poisoned pages,
|
|
3429
|
+
so a byte-identical repeat could answer under another request's system
|
|
3430
|
+
prompt. Restores now install fresh zero-copy views: the entry's stored
|
|
3431
|
+
state can no longer be written through, the borrower's first divergent
|
|
3432
|
+
write pays the single deferred copy the design always intended, and
|
|
3433
|
+
commits stay zero-copy. Reproduced and fixed from the reporter's
|
|
3434
|
+
reproducer, which ships as a regression test.
|
|
3435
|
+
|
|
3436
|
+
- **Streaming tool calls no longer duplicate argument values into `content`
|
|
3437
|
+
(#249).** When streaming with a prior tool call and result in history, the
|
|
3438
|
+
initial-fragment guard stripped tool markup and forwarded the remainder,
|
|
3439
|
+
which is the tool call's argument values, into `delta.content`, while the
|
|
3440
|
+
parser separately emitted the correct `tool_calls`. Clients then echoed the
|
|
3441
|
+
duplicated text back into context on every turn. Orphan remainders are now
|
|
3442
|
+
held until tool extraction resolves: a parser-claimed span is dropped, and
|
|
3443
|
+
only genuinely visible prose wrapped in stray markup still surfaces.
|
|
3444
|
+
|
|
3445
|
+
- **Solo requests with penalties answer on the composite scheduler.** A solo
|
|
3446
|
+
request carrying `presence_penalty` or `frequency_penalty` on the
|
|
3447
|
+
`mtp_batch` daemon hit the compiled lane's sampler validator and returned
|
|
3448
|
+
HTTP 500, while penalty cohorts already fell back to the host route. Solo
|
|
3449
|
+
penalty requests now steer to the same host lane up front.
|
|
3450
|
+
|
|
3451
|
+
- **`mtplx serve --no-auth` actually parses (#235 follow-through).** The
|
|
3452
|
+
2.5.4 notes promised that exact spelling, but the flag existed only on the
|
|
3453
|
+
internal server module's parser; the public `mtplx serve` command rejected
|
|
3454
|
+
it with an argparse error before anything ran. The flag now parses on
|
|
3455
|
+
`serve` and is forwarded to the server. Non-localhost binds still require
|
|
3456
|
+
a key.
|
|
3457
|
+
|
|
3458
|
+
- **`qwen3_5_mtp` checkpoints validate and serve again.** The injector
|
|
3459
|
+
attached the MTP surface to the outer model wrapper while validation
|
|
3460
|
+
inspects the inner TextModel, so validation failed and the forward pass
|
|
3461
|
+
crashed. The surface now lives on the TextModel and is re-exposed on the
|
|
3462
|
+
outer wrapper by delegation. Cherry-picked from PR #242 (thanks @davidtai)
|
|
3463
|
+
together with its hermetic regression test.
|
|
3464
|
+
|
|
3465
|
+
- **Artifacts launch at their measured depth again.** The typed runtime
|
|
3466
|
+
contract kept only its schema fields, which silently dropped the
|
|
3467
|
+
measured-depth map and every artifact's declared depth default — so
|
|
3468
|
+
`quickstart`/`start` launched the 35B-A3B at its D3 *ceiling*, a measured
|
|
3469
|
+
~22% decode loss against its fastest depth. Identity and depth defaults now
|
|
3470
|
+
resolve from the artifact's `mtplx_runtime.json` when the contract lacks
|
|
3471
|
+
them: the 35B Speed artifact launches at its measured D2, and artifacts
|
|
3472
|
+
declaring `recommended_mtp_depth` (Balance) are honored.
|
|
3473
|
+
|
|
3474
|
+
- **`--reasoning-parser` is authoritative.** A set parser was silently
|
|
3475
|
+
replaced by the backend's codec whenever they disagreed, which made the
|
|
3476
|
+
flag a no-op on shared lanes and left thinking impossible to enable for
|
|
3477
|
+
models whose templates fully support it. A parser resolved from family
|
|
3478
|
+
policy or typed by the operator now wins; the codec fills in only when no
|
|
3479
|
+
parser is set. Explicit `none` is still honored.
|
|
3480
|
+
|
|
3481
|
+
- **Metal buffer-object leak in long decodes.** mlx-lm's `ArraysCache`
|
|
3482
|
+
regrew Metal buffer objects on every `advance()`; over a long session that
|
|
3483
|
+
is unbounded growth. The fix is vendored in-tree and installed on both the
|
|
3484
|
+
server path and the batch lane, so pip installs against stock mlx-lm 0.31.x
|
|
3485
|
+
get it too.
|
|
3486
|
+
|
|
3487
|
+
- **Missing MTP heads degrade instead of refusing.** A checkpoint without
|
|
3488
|
+
`mtp_heads` now serves target-only AR (with the degrade reason surfaced,
|
|
3489
|
+
and propagated to the spawned server process) instead of being turned away.
|
|
3490
|
+
Checkpoints that ship custom Python (`auto_map`) are refused with the
|
|
3491
|
+
policy stated plainly — MTPLX never executes repository code — and
|
|
3492
|
+
quantizations MLX cannot run are refused with the offending bit-width named
|
|
3493
|
+
rather than failing later at load.
|
|
3494
|
+
|
|
3495
|
+
- **AR batch hardening.** Cache-removal errors in the batch lane now fail
|
|
3496
|
+
closed instead of corrupting the row; completed streams no longer starve
|
|
3497
|
+
behind still-running neighbours; and the vendored Metal shader cache is
|
|
3498
|
+
keyed by MLX ABI so an MLX upgrade cannot serve stale compiled kernels.
|
|
3499
|
+
|
|
3500
|
+
## [2.5.4] - 2026-08-07
|
|
3501
|
+
|
|
3502
|
+
Agent sessions got the attention this cycle. If you drive MTPLX from Pi,
|
|
3503
|
+
OpenCode, or any tool-calling client, warm turns now stay warm. (This section
|
|
3504
|
+
was backfilled from the GitHub release notes, which carry the full narrative.)
|
|
3505
|
+
|
|
3506
|
+
### Fixed
|
|
3507
|
+
|
|
3508
|
+
- **Warm-turn cache reuse in agent sessions.** Tool rounds carried a short
|
|
3509
|
+
transient hint that shifted the cached prefix by ~200 tokens per turn; the
|
|
3510
|
+
engine now records the stable boundary and restores from it directly. A
|
|
3511
|
+
postcommit within a bounded 0.6s of finishing is now awaited instead of
|
|
3512
|
+
discarded (a measured 1,449-token re-prefill became 436 tokens, cutting
|
|
3513
|
+
that turn's time-to-first-token from 2.7s to 1.1s). Background SSD cache
|
|
3514
|
+
maintenance no longer drains ahead of a starting turn (the unexplained
|
|
3515
|
+
~0.8s stall at turn start), and the SSD tier skips hydrating candidates
|
|
3516
|
+
that cannot beat a fresher in-RAM match.
|
|
3517
|
+
- **The session cache says what it is doing (#229, and the observability half
|
|
3518
|
+
of #230).** The daemon prints the resolved cache budget at startup with the
|
|
3519
|
+
override variables; outgrowing the per-session cap warns once with the
|
|
3520
|
+
numbers and the setting that raises the ceiling; `MTPLX_SESSION_BANK_MAX_BYTES`
|
|
3521
|
+
parses "8G"/"8GB"/"8GiB" and warns on unparseable values; the app no longer
|
|
3522
|
+
drops explicit cache sizes set in Settings under the "target default"
|
|
3523
|
+
policy.
|
|
3524
|
+
- **Long-context decode on 32k+ sessions (#228).** The app forced a
|
|
3525
|
+
paged-attention route at 32k with launch-day thresholds that were never
|
|
3526
|
+
re-measured; reporters measured 4-7x slower decode at 43k. The app now
|
|
3527
|
+
defers to the engine's measured thresholds (64k on current kernels).
|
|
3528
|
+
- **Vision sessions.** The near-prefix restore lane is capped at the first
|
|
3529
|
+
image token, so it can never resurrect cache computed from a different
|
|
3530
|
+
image's pixels.
|
|
3531
|
+
- **Adaptive depth engages when it should.** The expected-value policy's cost
|
|
3532
|
+
constants now reflect measured reality on current kernels; depth-3 drafting
|
|
3533
|
+
fired on 13% of eligible rounds despite 65% acceptance.
|
|
3534
|
+
|
|
3535
|
+
### Added
|
|
3536
|
+
|
|
3537
|
+
- **`mtplx serve --no-auth` (#235).** Explicit auth off-switch for localhost
|
|
3538
|
+
binds; non-localhost binds still require a key.
|
|
3539
|
+
- **llama.cpp-style `timings` object (#237).** Chat completion responses can
|
|
3540
|
+
include prompt/decode throughput for clients that read it from the response
|
|
3541
|
+
body (contributed).
|
|
3542
|
+
|
|
3543
|
+
## [2.5.3] - 2026-08-06
|
|
3544
|
+
|
|
3545
|
+
Small release. A day of head-to-head benchmarking against another engine
|
|
3546
|
+
turned up a set of real latency bugs in our agent lane and a few places
|
|
3547
|
+
where the API surface misled external tools. All of them are fixed here.
|
|
3548
|
+
|
|
3549
|
+
### Fixed
|
|
3550
|
+
|
|
3551
|
+
- Back-to-back agent requests no longer stall behind another session's
|
|
3552
|
+
background cache maintenance. Between requests the server commits session
|
|
3553
|
+
state to the reuse bank; since the 2.4 line that work could only be
|
|
3554
|
+
interrupted by its own session, so a request from any other session (a
|
|
3555
|
+
second chat, a subagent, an editor tool call) could wait out a
|
|
3556
|
+
multi-gigabyte commit and then decode slower on top of it. Worst measured
|
|
3557
|
+
hit on tight request cadences was a 44% slower follow-up turn and about
|
|
3558
|
+
0.75s of added first-token latency. Commits now yield the moment any
|
|
3559
|
+
request is admitted, whoever it belongs to. A session's own follow-up
|
|
3560
|
+
keeps the short grace it always had, so streaming tool-call turns still
|
|
3561
|
+
resolve their prefix instead of re-prefilling.
|
|
3562
|
+
(`MTPLX_POSTCOMMIT_CROSS_SESSION_YIELD=0` restores the old behavior.)
|
|
3563
|
+
- Repeat requests skip prompt re-encoding. Rendering and tokenizing a long
|
|
3564
|
+
chat transcript costs 77-92ms per request; an exact-match cache now
|
|
3565
|
+
returns it in under a millisecond. Combined with the yield fix above,
|
|
3566
|
+
warm follow-up latency in our gate runs went from a 194-961ms band to a
|
|
3567
|
+
steady 65-74ms, and clean-room warm restores measure 2-3ms server-side.
|
|
3568
|
+
(A related opt-in knob,
|
|
3569
|
+
`MTPLX_COMPILED_VERIFY_POST_RESTORE_EAGER_ROUNDS`, can route the first
|
|
3570
|
+
verify round after a very large restore through the eager path; it ships
|
|
3571
|
+
off by default.)
|
|
3572
|
+
- API responses no longer end with the "MTPLX TPS" stats footer. External
|
|
3573
|
+
tools counted it as model output, which added ~430ms to their timing
|
|
3574
|
+
windows, made token counts disagree with `usage`, and broke output
|
|
3575
|
+
equality checks at temperature 0. The MTPLX app and browser chat keep
|
|
3576
|
+
the footer. (`MTPLX_STATS_FOOTER_SCOPE=all` restores it everywhere.)
|
|
3577
|
+
- `usage` now reports `completion_tokens_details.reasoning_tokens`, so
|
|
3578
|
+
clients can separate thinking tokens from visible output instead of
|
|
3579
|
+
inferring it from stream timing.
|
|
3580
|
+
- Probing unknown endpoints or posting malformed bodies returns clean JSON
|
|
3581
|
+
errors with the right status codes instead of Python exception text.
|
|
3582
|
+
|
|
3583
|
+
### Changed
|
|
3584
|
+
|
|
3585
|
+
- Anonymous API clients now get standard OpenAI semantics for explicit
|
|
3586
|
+
request parameters: temperature, top_p, top_k, the thinking toggle,
|
|
3587
|
+
penalties, and generation mode in the request body are applied instead
|
|
3588
|
+
of being treated as hints. Requests that leave a field unset keep the
|
|
3589
|
+
server's launch and live settings, and clients MTPLX manages itself (the
|
|
3590
|
+
app, browser chat, configured OpenCode and editor lanes) stay
|
|
3591
|
+
server-owned exactly as before, so curated agent sampling is untouched.
|
|
3592
|
+
This closes the "temperature is ignored" class of report (#241).
|
|
3593
|
+
(`MTPLX_CLIENT_CONTROLS_DEFAULT=hints` restores the old policy.)
|
|
3594
|
+
- Temperature-0 requests now run the draft sampler greedy as well, so the
|
|
3595
|
+
speculative window matches the target's argmax choices more often:
|
|
3596
|
+
depth-2 acceptance rose from .526 to .590 in our runs.
|
|
3597
|
+
|
|
3598
|
+
### Compatibility
|
|
3599
|
+
|
|
3600
|
+
- Model catalogs, model defaults, memory policy, and every managed-client
|
|
3601
|
+
behavior are unchanged. Each behavior change above has an environment
|
|
3602
|
+
switch that restores the previous policy.
|
|
3603
|
+
|
|
3604
|
+
## [2.5.2] - 2026-08-04
|
|
3605
|
+
|
|
3606
|
+
Hotfix for a long-response slowdown that shipped in 2.5.1 with Optimized
|
|
3607
|
+
Speed V2 as the recommended coding model.
|
|
3608
|
+
|
|
3609
|
+
### Fixed
|
|
3610
|
+
|
|
3611
|
+
- Long single responses no longer slow down and stutter partway through. The
|
|
3612
|
+
compiled verifier hands generation to the eager path after its growth
|
|
3613
|
+
reserve is exhausted, and that handoff carried unfinished GPU work into the
|
|
3614
|
+
rest of the response. Every later token paid for the old work, so decode
|
|
3615
|
+
throughput fell steadily on responses past a few thousand tokens. The
|
|
3616
|
+
reported 12,000-token response opened at 59 tokens per second and
|
|
3617
|
+
ended near 30. With the fix, the same seeded generation no longer
|
|
3618
|
+
decays: the closing windows now run as fast as or faster than the
|
|
3619
|
+
early ones.
|
|
3620
|
+
- The handoff now settles all cache and recurrent state exactly once at the
|
|
3621
|
+
ownership boundary and releases the compiled references before eager
|
|
3622
|
+
decoding continues. Handoff telemetry is exposed in the compiled verifier
|
|
3623
|
+
stats.
|
|
3624
|
+
|
|
3625
|
+
### Changed
|
|
3626
|
+
|
|
3627
|
+
- The minimum MLX version is now 0.32. Fresh installs already resolved MLX
|
|
3628
|
+
0.32.0; existing runtime environments could stay on 0.31.2 indefinitely
|
|
3629
|
+
because dependency upgrades only run when the declared floor requires
|
|
3630
|
+
them. Long-generation runs consistently read faster on the 0.32 stack,
|
|
3631
|
+
and existing installs now converge to what new installs already run.
|
|
3632
|
+
|
|
3633
|
+
### Compatibility
|
|
3634
|
+
|
|
3635
|
+
- Model catalogs, defaults, sampler settings, memory policy, and the 2.5.1
|
|
3636
|
+
V2 recommendation are unchanged.
|
|
3637
|
+
|
|
3638
|
+
## [2.5.1] - 2026-08-03
|
|
3639
|
+
|
|
3640
|
+
Optimized Speed V2 is now the recommended Qwen 3.6 27B model for coding on
|
|
3641
|
+
modern Macs with enough unified memory. It gives users a much higher-quality
|
|
3642
|
+
coding model while keeping the original, smaller Optimized Speed model one row
|
|
3643
|
+
below it.
|
|
3644
|
+
|
|
3645
|
+
### Added
|
|
3646
|
+
|
|
3647
|
+
- Optimized Speed V2 is a first-class model in onboarding, the app catalog,
|
|
3648
|
+
CLI defaults, quickstart, downloads, served model identity, and OpenCode
|
|
3649
|
+
setup.
|
|
3650
|
+
- The model is published as a dynamic 4-bit hybrid quant with hand-tuned
|
|
3651
|
+
sensitive parts kept at up to 16-bit. It is faster on longer agent tasks,
|
|
3652
|
+
slightly larger, and a little slower for short chats.
|
|
3653
|
+
- RAM-aware defaults recommend V2 first on modern Macs with at least 32 GiB of
|
|
3654
|
+
detected memory. Smaller Macs keep the existing 9B and 4B recommendations.
|
|
3655
|
+
|
|
3656
|
+
### Compatibility
|
|
3657
|
+
|
|
3658
|
+
- The original Optimized Speed model remains fully supported and appears
|
|
3659
|
+
directly below V2 wherever the machine can run both.
|
|
3660
|
+
- Runtime kernels, sampler defaults, cache behavior, and speculative depth are
|
|
3661
|
+
unchanged from 2.5.0.
|
|
3662
|
+
|
|
3663
|
+
## [2.5.0] - 2026-08-03
|
|
3664
|
+
|
|
3665
|
+
The next-model release: MTPLX can load new architecture aliases without a
|
|
3666
|
+
hard-coded model-type wait, multi-layer MTP drafts are supported for the
|
|
3667
|
+
upcoming Qwen generation, HY3 becomes a first-class serving target, and the
|
|
3668
|
+
coding-agent bridge is materially more reliable. Experimental DeepSeek V4,
|
|
3669
|
+
Laguna, and GDN speed lanes from David Tai land behind explicit opt-ins; the
|
|
3670
|
+
shipping defaults remain unchanged.
|
|
3671
|
+
|
|
3672
|
+
### Added
|
|
3673
|
+
|
|
3674
|
+
- Upcoming-Qwen architecture readiness: the native draft path now supports
|
|
3675
|
+
`mtp_num_hidden_layers = N`, and unknown `model_type` values can resolve
|
|
3676
|
+
through their declared architecture class. A synthetic `qwen3_8` alias
|
|
3677
|
+
drill exercised load, generate, CLI, and app discovery before public weights
|
|
3678
|
+
existed; this is compatibility preparation, not a claim that unreleased
|
|
3679
|
+
weights were benchmarked.
|
|
3680
|
+
- HY3 first-class serving: a vendored MTP-capable model class, official
|
|
3681
|
+
defaults, suffixed think-tag handling, model discovery, and native OpenCode
|
|
3682
|
+
tool calls. AR-only exports fail safely to the AR path rather than touching
|
|
3683
|
+
an uninitialized draft head.
|
|
3684
|
+
- Experimental DeepSeek-V4 shape-specialized verification lanes: prebound
|
|
3685
|
+
output-LoRA routes, adaptive speculative width, exact M3 attention
|
|
3686
|
+
projection, sinkhorn and attention-island kernels, and compiled
|
|
3687
|
+
post-attention verifier islands. On the 128 GB M5 Max release machine, the
|
|
3688
|
+
exact 2-bit DQ model plus official MTP shard measured about 31 AR tok/s and
|
|
3689
|
+
36 MTP tok/s versus about 4/6 tok/s on the conservative path. These routes
|
|
3690
|
+
remain opt-in while broader model-quality validation continues. Contributed
|
|
3691
|
+
by David Tai (@davidtai, #223).
|
|
3692
|
+
- Experimental Laguna S-2.1 `mlx.fast` ports covering decode and prefill
|
|
3693
|
+
kernels, including size-gated prefill MoE combine; fail-loud guards and
|
|
3694
|
+
portable scratch space keep unsupported shapes on the safe route.
|
|
3695
|
+
Contributed by David Tai (@davidtai, #222).
|
|
3696
|
+
- Experimental GDN headquarter execution layout for verify tape capture,
|
|
3697
|
+
env-gated with bit-exact coverage and loud fallback. Contributed by David
|
|
3698
|
+
Tai (@davidtai, #209).
|
|
3699
|
+
|
|
3700
|
+
### Fixed
|
|
3701
|
+
|
|
3702
|
+
- Coding-agent JSON tool calls can carry large write bodies without the hidden
|
|
3703
|
+
tool guard aborting them, and common near-miss argument keys are repaired at
|
|
3704
|
+
the protocol boundary. This was verified through real OpenCode CLI and
|
|
3705
|
+
Desktop sessions, including a multi-file edit with all generated tests
|
|
3706
|
+
passing.
|
|
3707
|
+
- App and CLI launches now share the same coding-agent engine environment, so
|
|
3708
|
+
a workflow does not silently change behavior depending on which Start button
|
|
3709
|
+
launched it.
|
|
3710
|
+
- `start` and `quickstart --dry-run` report the resolved profile instead of the
|
|
3711
|
+
parser's placeholder default.
|
|
3712
|
+
- Smart-fan mode holds through the post-generation heat-soak window before
|
|
3713
|
+
restoring automatic control, avoiding the early restore that could distort
|
|
3714
|
+
back-to-back performance runs (#227).
|
|
3715
|
+
|
|
3716
|
+
### Compatibility
|
|
3717
|
+
|
|
3718
|
+
- `transformers` 5.14 is allowed after tokenizer and tool-template parity were
|
|
3719
|
+
verified; the incompatible 5.13.0 release remains excluded (#175).
|
|
3720
|
+
- No speculative-depth, cache, sampler, or model-speed default changed in this
|
|
3721
|
+
release. The opt-in model kernels fail closed to established implementations.
|
|
3722
|
+
|
|
3723
|
+
## [2.4.2] - 2026-08-02
|
|
3724
|
+
|
|
3725
|
+
The agentic-cache release: the session cache stops losing warm state
|
|
3726
|
+
mid-run, tool-turn commits stop being ghosts, every serve keeps a durable
|
|
3727
|
+
per-request trail by default, an experimental DeepSeek-V4-Flash backend
|
|
3728
|
+
lands, and the documentation now matches the code everywhere it was
|
|
3729
|
+
audited.
|
|
3730
|
+
|
|
3731
|
+
### Added
|
|
3732
|
+
|
|
3733
|
+
- DeepSeek-V4-Flash: experimental native AR backend
|
|
3734
|
+
(`model_type: deepseek_v4`) — Hyper-Connections, compressed sparse
|
|
3735
|
+
attention, hash-routed MoE, grouped output-LoRA — loading the
|
|
3736
|
+
mlx-community checkpoints directly, with an optional single-block MTP
|
|
3737
|
+
speculative lane when the checkpoint carries `mtp.0.*` weights
|
|
3738
|
+
(spec == AR gated; K=1-3 measured up to 2.28x on the 2bit-DQ build).
|
|
3739
|
+
MTP-declaring checkpoints that ship no draft weights degrade to AR
|
|
3740
|
+
with a clear message instead of failing at bind. Thanks @davidtai
|
|
3741
|
+
(#216).
|
|
3742
|
+
- Request log, default on: every serve writes numeric/hash per-request
|
|
3743
|
+
telemetry to `~/.mtplx/logs/request-log-<port>.jsonl` (64 MB x 4
|
|
3744
|
+
rotation; no prompt or completion content; disable with
|
|
3745
|
+
`MTPLX_REQUEST_LOG_JSONL=off`). Pairs with 2.4.1's opt-in bit-exact
|
|
3746
|
+
request capture to make agent-session incidents diagnosable after the
|
|
3747
|
+
fact (#196/#197). New helpers: `scripts/gauntlet_scoreboard.py`
|
|
3748
|
+
per-session summarizer, `scripts/oc_tap.py` recording proxy and
|
|
3749
|
+
`scripts/oc_tap_diff.py` request-mutation analyzer for content-level
|
|
3750
|
+
wire truth.
|
|
3751
|
+
- Session bank, active-session eviction protection: sessions that
|
|
3752
|
+
touched the bank within `MTPLX_SESSION_BANK_ACTIVE_PIN_TTL_S`
|
|
3753
|
+
(default 600 s) are eviction-last, so cross-session pressure evicts
|
|
3754
|
+
idle victims instead of the session that is mid-run.
|
|
3755
|
+
- Session bank, newest-K per-session snapshot retention
|
|
3756
|
+
(`MTPLX_SESSION_BANK_PER_SESSION_MAX_ENTRIES`, default 3): divergent
|
|
3757
|
+
per-turn sibling snapshots no longer accumulate unreclaimed.
|
|
3758
|
+
`/health` now reports active sessions, the pin TTL, and recent
|
|
3759
|
+
evictions.
|
|
3760
|
+
- Postcommit foreground grace (`MTPLX_POSTCOMMIT_FOREGROUND_GRACE_S`,
|
|
3761
|
+
default 2 s): a nearly-finished background cache commit lands instead
|
|
3762
|
+
of being preempted by the next fast agent-loop request.
|
|
3763
|
+
- Session identity honors `x-session-affinity` / `x-session-id` request
|
|
3764
|
+
headers (OpenCode sends these per request), ending cross-request
|
|
3765
|
+
identity churn on that client.
|
|
3766
|
+
|
|
3767
|
+
### Fixed
|
|
3768
|
+
|
|
3769
|
+
- Tool-turn "ghost re-prefills": the tool-rewrite async commit rendered
|
|
3770
|
+
a canonical history that matched neither the generation nor the next
|
|
3771
|
+
prompt, burning full-history re-forwards (26.8 s observed) without
|
|
3772
|
+
ever storing. It is disabled pending a byte-proven canonical render
|
|
3773
|
+
(`MTPLX_IDLE_POSTCOMMIT_TOOL_REWRITE` re-enables);
|
|
3774
|
+
store-on-prefill and block salvage cover the lane.
|
|
3775
|
+
- The bridge's convergence guard now states explicitly that editing and
|
|
3776
|
+
verification tools remain allowed and that its restriction covers
|
|
3777
|
+
only the current reply — a model read the old wording as a
|
|
3778
|
+
session-wide tool ban and stalled an entire session.
|
|
3779
|
+
- `mtplx profile thermal`, `profile eval-attribution`,
|
|
3780
|
+
`profile dispatch --trace`, and `thermal fanmax-run` invoked
|
|
3781
|
+
research-workspace scripts that are not part of the distribution, and
|
|
3782
|
+
`--dry-run` printed those phantom paths as runnable commands. They
|
|
3783
|
+
now report availability honestly (exit 2, machine-readable
|
|
3784
|
+
`available: false`) and run the real script when present.
|
|
3785
|
+
- `mtplx doctor`: Python floor corrected to 3.11 (matching
|
|
3786
|
+
`requires-python`); remediation texts no longer tell end users to
|
|
3787
|
+
edit source constants or to move a healthy server off its port;
|
|
3788
|
+
`--port` is documented and, when passed explicitly, aims the server
|
|
3789
|
+
connectivity checks.
|
|
3790
|
+
- Session-bank near-prefix restores on backends with bounded rollback
|
|
3791
|
+
(DeepSeek-V4) pre-check `max_rollback` and fall back to a cold
|
|
3792
|
+
prefill instead of raising (#216).
|
|
3793
|
+
- Help surfaces match their own parsers: the onboarding help no longer
|
|
3794
|
+
promises a Turbo wizard choice that does not exist (Turbo
|
|
3795
|
+
auto-selects for the quantized flagships), `--strict-cold` names the
|
|
3796
|
+
enforced 59 tok/s gate, `--open-dashboard` opens alongside the chosen
|
|
3797
|
+
client (as it always did), and the command reference teaches
|
|
3798
|
+
`mtplx <command> --help`, which also works for multi-word commands.
|
|
3799
|
+
|
|
3800
|
+
### Documentation
|
|
3801
|
+
|
|
3802
|
+
- Full truth sweep: ~450 documentation claims reconciled against the
|
|
3803
|
+
code across 27 files. Highlights: INSTALL.md no longer references an
|
|
3804
|
+
MLX fork removed in 2.0.0; turbo-verify.md no longer calls the
|
|
3805
|
+
shipped default "experimental, off by default" nor excludes the
|
|
3806
|
+
6-bit lane that ships; the Anthropic base-URL instruction (docs and
|
|
3807
|
+
the canonical example) no longer 404s; `/metrics` no longer claims a
|
|
3808
|
+
Prometheus mode that never existed; the README modes table shows
|
|
3809
|
+
Turbo as the default for the quantized 27B/9B flagships; the Laguna
|
|
3810
|
+
memory requirement states the real ~85.3 GiB preflight gate;
|
|
3811
|
+
version-era staleness ("v0.1", "preview", v0.3.x runbook pins) is
|
|
3812
|
+
cleared; historical release notes gain bracketed corrections where
|
|
3813
|
+
they documented commands that never worked. Thanks
|
|
3814
|
+
@PhilipJohnBasile for #218 (removed the unsupported MTP-sidecar
|
|
3815
|
+
graft guidance; seeded by #215).
|
|
3816
|
+
- Dependency-record correction: the transformers pin has been
|
|
3817
|
+
`<5.14,!=5.13.0` since shortly after 2.0.0; the changelog never
|
|
3818
|
+
recorded the relaxation from `<5.13`.
|
|
3819
|
+
|
|
3820
|
+
### Dependencies
|
|
3821
|
+
|
|
3822
|
+
- pypa/gh-action-pypi-publish 1.14.1 -> 1.14.2 (#217).
|
|
3823
|
+
|
|
3824
|
+
## [2.4.1] - 2026-08-01
|
|
3825
|
+
|
|
3826
|
+
The smooth-streaming release: the app's chat render path is overhauled
|
|
3827
|
+
(no more freeze-then-catch-up stutter, scroll bounce, or plain-text code
|
|
3828
|
+
blocks — real syntax coloring, live code cards, tables, and actual math
|
|
3829
|
+
notation), and the 2.4.0 short-turn regression is fixed.
|
|
3830
|
+
|
|
3831
|
+
### Added
|
|
3832
|
+
|
|
3833
|
+
- Live syntax coloring for code blocks (12 languages + generic) from a
|
|
3834
|
+
freeze-time lexer that colors each line exactly once; streaming cost
|
|
3835
|
+
is O(new text), never O(document).
|
|
3836
|
+
- Streaming code card: an open fence renders as a live card with
|
|
3837
|
+
colored lines and flips once to its settled form at close.
|
|
3838
|
+
- Pipe tables render as real tables; math renders as real notation
|
|
3839
|
+
(Unicode super/subscripts, stacked matrices and fractions, inline
|
|
3840
|
+
conversion instead of dollar-sign leaks).
|
|
3841
|
+
- Typewriter pacing for streamed text with geometric catch-up and a
|
|
3842
|
+
hard drain bound (`MTPLX_STREAM_TYPEWRITER=0` to disable), and a live
|
|
3843
|
+
tok/s chip computed over a sliding ~5 s window.
|
|
3844
|
+
- Performance mode is a true kill switch: plain text only, through both
|
|
3845
|
+
the streaming and settled render paths.
|
|
3846
|
+
- Opt-in per-request capture for bit-exact failure replay:
|
|
3847
|
+
`MTPLX_REQUEST_CAPTURE_DIR=<dir>` persists each request's
|
|
3848
|
+
reproduction envelope at dispatch time (#196/#197, third layer).
|
|
3849
|
+
- Opt-in frontend stream-performance probe (`MTPLX_UI_PERF=1`, HUD via
|
|
3850
|
+
`MTPLX_UI_PERF_HUD=1`) with a per-turn JSONL trace joinable to engine
|
|
3851
|
+
stats by request id.
|
|
3852
|
+
- Experimental: cost-model speculative-depth policy
|
|
3853
|
+
(`--adaptive-policy cost`) and blocked-sequential GDN prefill
|
|
3854
|
+
(`MTPLX_GDN_BLOCKED_PREFILL=1`). Defaults unchanged.
|
|
3855
|
+
|
|
3856
|
+
### Fixed
|
|
3857
|
+
|
|
3858
|
+
- 2.4.0 short-turn regression: the compiled-verify path could reserve
|
|
3859
|
+
KV budget above the configured ceiling, taxing short requests with
|
|
3860
|
+
setup work they never used; the reserve is now clamped.
|
|
3861
|
+
- Warming prefills yield to real traffic within one small chunk instead
|
|
3862
|
+
of delaying a freshly arrived request.
|
|
3863
|
+
- Derivative model artifacts whose names extend a first-party model
|
|
3864
|
+
name are served under their own id, not the flagship's — the health
|
|
3865
|
+
payload, OpenAI `model` field, and app model chip now report the
|
|
3866
|
+
artifact actually loaded.
|
|
3867
|
+
- Streaming render: line-segment coalescing keeps realized view count
|
|
3868
|
+
bounded on long answers; the bottom-pin scroll correction runs in the
|
|
3869
|
+
same display cycle as layout so the streaming bubble can no longer
|
|
3870
|
+
visibly bounce; a per-display-cycle window-sizing walk that floored
|
|
3871
|
+
every update at ~50 ms is removed (`MTPLX_APP_SIZING_TUNER=0`
|
|
3872
|
+
restores it).
|
|
3873
|
+
|
|
3874
|
+
## [2.4.0] - 2026-07-31
|
|
3875
|
+
|
|
3876
|
+
The 35B speed release: the 35B-A3B MoE gets a compiled decode stack and
|
|
3877
|
+
continuous batched serving, the 2.3.0 fan regression is root-caused and
|
|
3878
|
+
fixed, tool calling gets another round of contract hardening, and
|
|
3879
|
+
structured output can no longer be eaten by an unbounded reasoning
|
|
3880
|
+
prelude. Four community contributors landed code in this release.
|
|
3881
|
+
|
|
3882
|
+
### Added
|
|
3883
|
+
|
|
3884
|
+
- 35B-A3B compiled decode stack: target-prefix compiled route, whole-MoE
|
|
3885
|
+
fusion, GDN post-conv fusion, and a row-owned router (David Tai, #174).
|
|
3886
|
+
- Continuous batched serving for the A3B lane: fixed-shape cohorts,
|
|
3887
|
+
ragged KV, fold-in repair, and AR row-packing (David Tai, #200).
|
|
3888
|
+
- Laguna S-2.1 support (exact-pin oQ4e, AR-only) with an app catalog
|
|
3889
|
+
entry, plus a Poolside `arg_key`/`arg_value` tool-call dialect parser
|
|
3890
|
+
(David Tai, #195).
|
|
3891
|
+
- Hy3 295B full-residency lane and generic MTP draft-contract hardening
|
|
3892
|
+
whose loud recurrent-cache failure also caught a real bug on the Qwen
|
|
3893
|
+
lane (David Tai, #208).
|
|
3894
|
+
- `/health` now reports smart-fan restore state (`restore_verified`,
|
|
3895
|
+
`restore_failures`, `stale_leases_reconciled`) so stuck-fan reports
|
|
3896
|
+
are diagnosable from the field (#201).
|
|
3897
|
+
|
|
3898
|
+
### Fixed
|
|
3899
|
+
|
|
3900
|
+
- Fans no longer stay pinned at max after a request ends (#201). A
|
|
3901
|
+
failed fan restore was logged once and then treated as restored, so
|
|
3902
|
+
the hardware stayed ramped while the server believed it was clean;
|
|
3903
|
+
restores now verify the fan rows are back on the Apple auto curve and
|
|
3904
|
+
retry with backoff until they are. The ThermalForge daemon-socket
|
|
3905
|
+
restore path no longer trusts the daemon's "ok" reply without
|
|
3906
|
+
verifying, and falls back to the CLI in the same call. A stale-lease
|
|
3907
|
+
watchdog drops any fan lease held while the engine has been
|
|
3908
|
+
continuously idle (default 120s, `MTPLX_SMART_FAN_STALE_LEASE_S`).
|
|
3909
|
+
- A generation cut by `max_tokens` mid-tool-call now reports
|
|
3910
|
+
`finish_reason: "length"` instead of `"tool_calls"`, so agent clients
|
|
3911
|
+
continue the turn instead of executing a truncated call (#196, #197
|
|
3912
|
+
layers one and two).
|
|
3913
|
+
- The think-splitter no longer leaks reasoning into visible content when
|
|
3914
|
+
the text contains bare `function=` or `parameter=` strings (#196/#197
|
|
3915
|
+
companion fix).
|
|
3916
|
+
- Streaming tool-call parsing handles bracket-style dialects with a
|
|
3917
|
+
balanced string/escape-aware scanner, buffers incomplete calls instead
|
|
3918
|
+
of double-delivering them, and passes through calls to undeclared
|
|
3919
|
+
tools per the OpenAI contract instead of dropping them (David Tai,
|
|
3920
|
+
#195).
|
|
3921
|
+
- Constrained generation bounds the `<think>` prelude at 4000 characters
|
|
3922
|
+
(`MTPLX_THINK_PRELUDE_MAX_CHARS`, 0 restores unbounded), so an
|
|
3923
|
+
unclosed think block can no longer consume the entire token budget and
|
|
3924
|
+
return no document (Jozef Kristek, #213).
|
|
3925
|
+
- Forge model probes recover from slow Hugging Face config responses:
|
|
3926
|
+
30s timeout, pinned-SHA retry, positive-MTP-only indexed acceptance,
|
|
3927
|
+
and revision-string validation (Philip John Basile, #210).
|
|
3928
|
+
|
|
3929
|
+
### Changed
|
|
3930
|
+
|
|
3931
|
+
- Dependency bumps: pillow 12.3.0, actions/checkout 7.0.1,
|
|
3932
|
+
actions/setup-python 7.0.0, pypa/gh-action-pypi-publish 1.14.1.
|
|
3933
|
+
|
|
3934
|
+
## [2.3.0] - 2026-07-21
|
|
3935
|
+
|
|
3936
|
+
The agent reliability release: the #170 tool-argument collapse is
|
|
3937
|
+
root-caused and fixed, structured output ships with full speculative
|
|
3938
|
+
speed (community-contributed), agent sessions keep their warm cache
|
|
3939
|
+
through client history rewrites, and eight findings from an independent
|
|
3940
|
+
source review of v2.2.0 are fixed, each with a regression test that
|
|
3941
|
+
fails on the pre-fix code.
|
|
3942
|
+
|
|
3943
|
+
### Fixed
|
|
3944
|
+
|
|
3945
|
+
- Agent tool calls with nested arguments no longer collapse to empty `{}`
|
|
3946
|
+
arguments (#170). The streaming Qwen-XML tool parser silently discarded
|
|
3947
|
+
function-body text that was not wrapped in `<parameter=>` blocks, so a
|
|
3948
|
+
model that wrote its arguments as a JSON object inside the
|
|
3949
|
+
`<function=...>` envelope — the common slip on nested `edits` arrays —
|
|
3950
|
+
produced a schema-valid call with empty arguments on requiredless tools
|
|
3951
|
+
and a silently vanished call otherwise. Both the streaming and the final
|
|
3952
|
+
parser now accept a pure JSON-object function body as the arguments
|
|
3953
|
+
payload, and every unwrapped, lead, or trailing text lane is a loud
|
|
3954
|
+
protocol fallback with identical contracts in the two parsers.
|
|
3955
|
+
- The injected tool contract no longer shows degenerate `[]` examples for
|
|
3956
|
+
array parameters: exemplars are populated from the item schema's own
|
|
3957
|
+
keys (an `edits` array renders as
|
|
3958
|
+
`[{"search": "ARGUMENT_VALUE", "replace": "ARGUMENT_VALUE"}]`), removing
|
|
3959
|
+
an in-prompt template for empty-array emissions.
|
|
3960
|
+
- The final tool-call extraction lane (non-stream parsing and the stream
|
|
3961
|
+
fallback) no longer fabricates `{}`-argument calls out of function bodies
|
|
3962
|
+
it could not read — live-reproduced on v2.2.0 as a `grep` call arriving
|
|
3963
|
+
with empty arguments. All extraction dialects now share the strict
|
|
3964
|
+
parsers' contract: a pure JSON-object function body is the arguments
|
|
3965
|
+
payload, an unreadable body stays visible content, blank-body no-argument
|
|
3966
|
+
calls still parse, and partial arguments are delivered exactly as the
|
|
3967
|
+
model wrote them (schema validation remains the client's job, per the
|
|
3968
|
+
OpenAI protocol).
|
|
3969
|
+
- Context-copy speculative rounds now stop accepting at the first
|
|
3970
|
+
accepted stop token, exactly like the MTP acceptance loop. Previously
|
|
3971
|
+
the copy lane could accept an entire block past a stop, leaving the
|
|
3972
|
+
target cache, logits/hidden selection, and MTP history advanced beyond
|
|
3973
|
+
the emitted response while the final state was still marked safe to
|
|
3974
|
+
commit — a session-cache poisoning risk on recurrent (GDN) models.
|
|
3975
|
+
- Context-copy is prompt-only again at the boundary: proposal blocks are
|
|
3976
|
+
sliced from the prompt and capped at its edge instead of running into
|
|
3977
|
+
the model's own generated output (the self-repetition case the feature
|
|
3978
|
+
contract excludes). Boundary-less candidates are skipped inside the
|
|
3979
|
+
n-gram index so the best *valid* match still fires.
|
|
3980
|
+
- Non-divisor packed MTP quantization widths (5-bit, 3-bit, 6-bit) now
|
|
3981
|
+
infer the correct group size via total-bit arithmetic (#182; fix by
|
|
3982
|
+
@Jonathangadeaharder in #183). A 5-bit group-64 head previously
|
|
3983
|
+
inferred group 60, overwrote the artifact's correct declared contract,
|
|
3984
|
+
and made the model unloadable.
|
|
3985
|
+
- Long-running daemons no longer accumulate unbounded telemetry: the
|
|
3986
|
+
SessionBank eviction log is a bounded ring, the scheduler's
|
|
3987
|
+
started-by-batch-key counter collapses per-session key suffixes to
|
|
3988
|
+
stable classes, the dashboard per-session TPS map is LRU-bounded, and
|
|
3989
|
+
the OpenCode title fast path trims request metrics like every other
|
|
3990
|
+
lane (#145-adjacent slow-creep hygiene).
|
|
3991
|
+
- The desktop Hugging Face probe now recognizes rootless assistant-pair
|
|
3992
|
+
bundles (`mtplx_pair.json` + `target/config.json`) the same way the
|
|
3993
|
+
Python runtime does, so the official Gemma4 pair repos classify as
|
|
3994
|
+
ready to download instead of unreadable (#107); Forge routes them to
|
|
3995
|
+
install-instead-of-rebuild.
|
|
3996
|
+
- `mtplx doctor --json` stdout is now guaranteed machine-parseable: the
|
|
3997
|
+
report is built with stdout routed to stderr, so third-party lazy
|
|
3998
|
+
importers that print() their errors (huggingface_hub does, whenever its
|
|
3999
|
+
HTTP dependencies are broken or split across install locations) can no
|
|
4000
|
+
longer prefix the JSON document with prose and break `--json` consumers.
|
|
4001
|
+
- Agent tool-loop sessions no longer lose their warm cache when the
|
|
4002
|
+
client rewrites history (transcript compaction, retroactive tool-result
|
|
4003
|
+
digests): a common-prefix identity fallback (≥4096 shared tokens and
|
|
4004
|
+
≥25% of the prompt) keeps the session id instead of minting an
|
|
4005
|
+
anonymous one, ending the cold full-context re-prefills those
|
|
4006
|
+
rotations caused mid-session.
|
|
4007
|
+
- Fan restore goes through the ThermalForge daemon socket instead of the
|
|
4008
|
+
app-killing CLI path, and `mtplx pull` detects interrupted downloads
|
|
4009
|
+
as incomplete instead of treating them as ready (thanks @titan550,
|
|
4010
|
+
#178/#179/#180/#181).
|
|
4011
|
+
|
|
4012
|
+
### Changed
|
|
4013
|
+
|
|
4014
|
+
- `parallel_tool_calls` is now honored: a request that declares it gets
|
|
4015
|
+
the declared behavior in both directions (false → at most one tool
|
|
4016
|
+
call per turn), with the previous client-profile heuristic kept only
|
|
4017
|
+
as the fallback when the field is absent (thanks @PhilipJohnBasile,
|
|
4018
|
+
#190 — Android Studio declares false today and was being ignored).
|
|
4019
|
+
- Agent tool contract v13: instructs whole-file reads (the old
|
|
4020
|
+
"smallest read range" clause provoked storms of 1-to-5-line
|
|
4021
|
+
micro-reads) and forbids echoing file contents into visible text
|
|
4022
|
+
(double-emitted file bodies inflated agent turns by tens of
|
|
4023
|
+
thousands of characters). Measured on live agent sessions: thinking
|
|
4024
|
+
share of generated tokens 75% → 6-29%, double emissions eliminated.
|
|
4025
|
+
|
|
4026
|
+
### Added
|
|
4027
|
+
|
|
4028
|
+
- Structured output: `response_format` `json_object` and `json_schema`
|
|
4029
|
+
are now enforced with llguidance token masks instead of silently
|
|
4030
|
+
ignored — and the grammar composes with the MTP verify loop, so
|
|
4031
|
+
constrained requests keep full speculative speed (measured: decode
|
|
4032
|
+
parity within noise, under 5ms total mask cost per request). On
|
|
4033
|
+
thinking templates a prelude grammar lets reasoning finish before the
|
|
4034
|
+
document is forced. Opt-in strict tool-call grammars
|
|
4035
|
+
(`MTPLX_TOOL_CALL_STRICT=1`) force every tool call to a declared tool
|
|
4036
|
+
name with schema-valid arguments — a real OpenCode session built a
|
|
4037
|
+
complete project through the strict lane with zero malformed calls.
|
|
4038
|
+
Thanks @PhilipJohnBasile (#186, #187, #188). Requires the `[server]`
|
|
4039
|
+
extra, which the desktop app installs by default; bare pip installs
|
|
4040
|
+
get a clear 400 with an install hint.
|
|
4041
|
+
- Durable per-request telemetry: `--request-log-jsonl` (env
|
|
4042
|
+
`MTPLX_REQUEST_LOG_JSONL`) appends every request record as one JSON
|
|
4043
|
+
line, and `scripts/session_forensics.py` correlates that log with an
|
|
4044
|
+
OpenCode database into a single timeline with detectors for re-prefill
|
|
4045
|
+
rewinds, TTFT stalls, thinking marathons, double emissions,
|
|
4046
|
+
session-identity rotations, and usage mismatches.
|
|
4047
|
+
- An **opt-in** agent-lane reasoning budget (`--agent-thinking-budget`,
|
|
4048
|
+
env `MTPLX_THINKING_BUDGET`; OFF by default): at the budget the
|
|
4049
|
+
reasoning segment is force-closed with a visible bridge so the turn
|
|
4050
|
+
proceeds to its answer or tool call, and every engagement is surfaced
|
|
4051
|
+
per-request in telemetry (`thinking_guard`). Below the budget decoding
|
|
4052
|
+
is bit-exact; plain chat is never touched.
|
|
4053
|
+
- Stream stall watchdog (#86 containment): if a stream receives nothing
|
|
4054
|
+
while the model owner's progress heartbeat is frozen for
|
|
4055
|
+
`MTPLX_STREAM_STALL_DEADLINE_S` (default 300s, 0 disables), the
|
|
4056
|
+
request fails with a structured, diagnosable error and releases its
|
|
4057
|
+
slot instead of hanging forever. Healthy long prefills and model loads
|
|
4058
|
+
tick the heartbeat and are never affected; the daemon is never killed.
|
|
4059
|
+
- `pip install mtplx` now includes Pillow, matching the advertised image
|
|
4060
|
+
support in the app and server (#103); previously vision failed at
|
|
4061
|
+
import unless the `[server]` extra was installed.
|
|
4062
|
+
|
|
4063
|
+
## [2.2.0] - 2026-07-19
|
|
4064
|
+
|
|
4065
|
+
The copy-drafting and small-Mac release. Decoding: context-copy
|
|
4066
|
+
(prompt-lookup) drafting lands on by default (PR #151 by lBroth) with an
|
|
4067
|
+
exact temperature path — copied blocks are accepted with the target's own
|
|
4068
|
+
shaped probability, so the output distribution is unchanged at any
|
|
4069
|
+
temperature; measured +53% on edit-heavy agent turns at temp 0.6, parity
|
|
4070
|
+
on novel text (disable with MTPLX_CONTEXT_COPY=0). Models: the 4B
|
|
4071
|
+
zero-acceptance defect (#176) is root-caused and fixed — the engine heals
|
|
4072
|
+
raw delta-encoded MTP sidecars at load so existing downloads recover
|
|
4073
|
+
without re-downloading, the 4B Speed artifact is rebuilt (227.8 tok/s D3
|
|
4074
|
+
on M5 Max, 1.71x AR), and a new 4B Quality artifact ships (191.7 tok/s
|
|
4075
|
+
D3, 2.19x — the largest MTP multiplier in the fleet); sub-16GB Macs get
|
|
4076
|
+
first-class catalog recommendations. Tune (#177): a 0.0-acceptance depth
|
|
4077
|
+
can never win, be saved, or be replayed, and poisoned records are
|
|
4078
|
+
quarantined at load. Forge: FP16 precision option with M1/M2 auto-select
|
|
4079
|
+
(#166); the fp16 cast can no longer corrupt a sidecar in place;
|
|
4080
|
+
degenerate sidecars are quarantined on re-forge. Server: SSD session
|
|
4081
|
+
writer crash fixed — encode at enqueue (#169); live requests preempt idle
|
|
4082
|
+
cache maintenance. MoE: mtp_depth_max is a ceiling, not the default; the
|
|
4083
|
+
35B-A3B launches at its measured D2 (#174 part 1 by davidtai). Also: the
|
|
4084
|
+
Qwen 3.6 27B AR decode-trace crash fix (#167 by davidtai), truthful
|
|
4085
|
+
per-model profile display, hybrid-model boundary retention across append
|
|
4086
|
+
churn, and an experimental --draft-core device (opt-in). Full details in
|
|
4087
|
+
docs/releases/v2.2.0.md.
|
|
4088
|
+
|
|
4089
|
+
## [2.1.0] - 2026-07-17
|
|
4090
|
+
|
|
4091
|
+
The community-fixes release. Memory: the v2.x reports are root-caused and
|
|
4092
|
+
closed (MLX allocator cache bounded by default, per-session admission
|
|
4093
|
+
re-clamped on sub-96GB machines, paged pool bounded by the context
|
|
4094
|
+
window, pressure responder redesigned, q4 kv-quant crash fixed, new
|
|
4095
|
+
`--memory-budget` knob). Agent sessions: warm prefix reuse survives every
|
|
4096
|
+
tool turn (#121), hybrid-model near-prefix restores no longer collapse to
|
|
4097
|
+
the oldest boundary (measured 0.4s instead of 33.8s on a 22k follow-up),
|
|
4098
|
+
restart-warm sessions keep their boundary records across SSD generations
|
|
4099
|
+
(#159, #144), and cache hits are reported in standard `usage` fields.
|
|
4100
|
+
Sampling: presence and frequency penalties fixed in the batched AR lane
|
|
4101
|
+
(#156). App: startup and update hang fixed plus a full subprocess
|
|
4102
|
+
watchdog sweep (#158), the Hermes tile launches Hermes Desktop, raw
|
|
4103
|
+
tool-call XML no longer leaks into no-tools chats (#160). CLI: `start
|
|
4104
|
+
opencode` serves the same lane the app serves. Backends: qwen3_5_mtp and
|
|
4105
|
+
hy_v3 land (#142, #147). Performance: the model-owner thread is
|
|
4106
|
+
QoS-pinned for 8 to 10% faster decode under real multitasking load.
|
|
4107
|
+
Operators: `MTPLX_COMPILED_VERIFY_MAX_CONTEXT` is env-overridable. Full
|
|
4108
|
+
details in docs/releases/v2.1.0.md.
|
|
4109
|
+
|
|
4110
|
+
## [2.0.2] - 2026-07-09
|
|
4111
|
+
|
|
4112
|
+
The agent-reliability release: multi-turn agent sessions now render
|
|
4113
|
+
reasoning history on the model's trained contract (the fix for the
|
|
4114
|
+
plan-execution repetition marathons), LAN serving works from the app,
|
|
4115
|
+
and every agent client gets warm prefix reuse.
|
|
4116
|
+
|
|
4117
|
+
### Changed
|
|
4118
|
+
|
|
4119
|
+
- Reasoning history is now scoped to the active agent round by default
|
|
4120
|
+
on Qwen3.6/3.5 models, matching Qwen's trained multi-turn contract.
|
|
4121
|
+
The Qwen chat template keeps `<think>` blocks only for assistant
|
|
4122
|
+
messages after the last real user query (its built-in "rolling
|
|
4123
|
+
checkpoint"); MTPLX used to override that with `preserve_thinking`,
|
|
4124
|
+
which rendered an off-contract empty `<think>` scaffold on every
|
|
4125
|
+
completed assistant turn and replayed stale inline reasoning across
|
|
4126
|
+
turns, while silently dropping the structured `reasoning_content`
|
|
4127
|
+
fields agent clients such as OpenCode send. Scoped mode lets the
|
|
4128
|
+
template's own checkpoint govern: completed turns render with no
|
|
4129
|
+
think scaffold, and the active round (assistant -> tool -> assistant
|
|
4130
|
+
chains) keeps its reasoning, now including structured
|
|
4131
|
+
`reasoning_content` - strictly better in-round continuity than
|
|
4132
|
+
before. `--preserve-thinking on` restores the previous behavior
|
|
4133
|
+
byte-for-byte (including cache identity), `off` still strips
|
|
4134
|
+
everything, and the new explicit `scoped` value pins the scoped mode.
|
|
4135
|
+
Templates without the rolling checkpoint (Gemma 4, custom templates)
|
|
4136
|
+
keep the previous preserve-all behavior. The resolved policy is shown
|
|
4137
|
+
at startup ("Reasoning history: scoped (active round only)") and as
|
|
4138
|
+
`reasoning_history_mode` in `/v1/mtplx/settings` and the snapshot.
|
|
4139
|
+
|
|
4140
|
+
### Added
|
|
4141
|
+
|
|
4142
|
+
- Loop Guard (opt-in): `MTPLX_LOOP_GUARD=1` enables a loop-armed
|
|
4143
|
+
anti-repetition steering mode for models prone to verbatim repetition
|
|
4144
|
+
marathons (for example repetition-damaged third-party quants). Unlike
|
|
4145
|
+
a static presence penalty, the guard is completely inert until a real
|
|
4146
|
+
loop is detected mid-response (bit-exact sampling otherwise, MTP
|
|
4147
|
+
acceptance math unchanged), then penalizes only the tokens that would
|
|
4148
|
+
extend a verbatim repeat and disarms once the loop is broken. Content
|
|
4149
|
+
inside tool calls is never steered (code legitimately repeats short
|
|
4150
|
+
token runs, so tool-call spans are masked token-exactly). The default
|
|
4151
|
+
is OFF: no synthetic steering touches sampling unless you ask for it,
|
|
4152
|
+
and the repetition fix that matters for MTPLX's own models is the
|
|
4153
|
+
scoped reasoning history change above. Detector/steering knobs are
|
|
4154
|
+
documented in `mtplx/loop_guard.py`; per-request guard activity is
|
|
4155
|
+
visible under `loop_guard` in `/v1/mtplx/snapshot`.
|
|
4156
|
+
|
|
4157
|
+
### Fixed
|
|
4158
|
+
|
|
4159
|
+
- Hidden reasoning no longer leaks into visible chat content when a
|
|
4160
|
+
long reasoning tag (e.g. `</reasoning>`) splits across a streaming
|
|
4161
|
+
chunk boundary; the splitter held back too few bytes for tags longer
|
|
4162
|
+
than `</think>` (PR #149 by @Osamaali313).
|
|
4163
|
+
- `mtplx` quickstart onboarding screens now display the requested
|
|
4164
|
+
`--host`/`--port` in the Web UI and dashboard URLs instead of a
|
|
4165
|
+
hardcoded `http://127.0.0.1:8000` (PR #148 by @hasegaw).
|
|
4166
|
+
- Ctrl-C now returns control to the terminal within a bounded delay
|
|
4167
|
+
even when a browser tab holds an open chat/dashboard stream. The
|
|
4168
|
+
server previously waited forever for infinite SSE generators to
|
|
4169
|
+
finish; shutdown now drains in-flight requests with a 5-second
|
|
4170
|
+
deadline, and thermal/fan cleanup still runs (#124).
|
|
4171
|
+
- Serving on any host and port now works from the app. Setting host
|
|
4172
|
+
0.0.0.0 (LAN serving) used to misreport free ports as occupied, bump
|
|
4173
|
+
the port, and kill the healthy daemon after a health-wait timeout,
|
|
4174
|
+
because the app probed the bind address verbatim; all app-side
|
|
4175
|
+
connections now resolve wildcard binds to a connectable loopback
|
|
4176
|
+
address (#109). The app also surfaces the "LAN serving requires an
|
|
4177
|
+
API key" rule before launch instead of a generic Degraded state, an
|
|
4178
|
+
API-key mismatch reads as a live-but-unauthorized daemon instead of
|
|
4179
|
+
a lost one, and the port-in-use preflight tests the address family
|
|
4180
|
+
the daemon will actually bind.
|
|
4181
|
+
- The app now passes the SSD session cache setting to the daemon
|
|
4182
|
+
explicitly, including "Off". Since 2.0.0 flipped the serve default to
|
|
4183
|
+
on, an explicit Settings "Off" was silently re-enabled with default
|
|
4184
|
+
limits on app-launched daemons, and `~/.mtplx/session-bank` kept
|
|
4185
|
+
growing (#140; also the "session-bank came back after I deleted it"
|
|
4186
|
+
half of #138). Generated `mtplx start` server commands carry the
|
|
4187
|
+
explicit `--ssd-session-cache off` for the same reason.
|
|
4188
|
+
- The app's runtime venv now self-heals after app updates. A venv whose
|
|
4189
|
+
base-interpreter symlink pointed into a replaced app bundle made every
|
|
4190
|
+
reinstall fail with "[Errno 2] No such file or directory:
|
|
4191
|
+
.../runtime-venv/bin/python3" and no DMG reinstall could fix it; venv
|
|
4192
|
+
creation now rebuilds with `--clear` when the existing venv python is
|
|
4193
|
+
broken or creation fails (#139).
|
|
4194
|
+
- Warm prefix reuse no longer freezes on the oldest short prefix for
|
|
4195
|
+
agent harnesses outside OpenCode (Pi, little-coder, Hermes, custom
|
|
4196
|
+
clients). The block-prefix restore lane was still gated to OpenCode's
|
|
4197
|
+
compact tool contract even though kvcache-v2's boundary-true restores
|
|
4198
|
+
made it exact for every client, so transcripts whose turns diverge
|
|
4199
|
+
more than a few tokens before the stored end re-prefilled a growing
|
|
4200
|
+
suffix every turn (#138). The lane now engages for all clients while
|
|
4201
|
+
boundary-true restore is on; `MTPLX_SESSION_BLOCK_PREFIX_RESTORE=0`
|
|
4202
|
+
still disables it.
|
|
4203
|
+
|
|
4204
|
+
## [2.0.1] - 2026-07-07
|
|
4205
|
+
|
|
4206
|
+
Turbo for every Mac. The v2 turbo default now covers every dense catalog
|
|
4207
|
+
model on every Apple Silicon generation, with a load-time kernel
|
|
4208
|
+
self-validation safety net.
|
|
4209
|
+
|
|
4210
|
+
### Added
|
|
4211
|
+
|
|
4212
|
+
- 6-bit affine verify kernels (split-K hexpack family): the 6-bit 9B tier
|
|
4213
|
+
gains 33-62% decode and 43% 2k-prefill under turbo (M5 Max, verified
|
|
4214
|
+
arms). Qwen 3.5 9B and 9B FP16 now default to turbo.
|
|
4215
|
+
- New model: `Youssofal/Qwen3.6-27B-MTPLX-Optimized-Quality-FP16`, the
|
|
4216
|
+
missing M1/M2 quality artifact. Wired into the app picker, CLI catalog,
|
|
4217
|
+
and chip-aware routing (a Quality pick on M1/M2 resolves the FP16
|
|
4218
|
+
sibling, mirroring the speed lane). Validated against the bf16 parent
|
|
4219
|
+
and measured at 2.5x over true AR under turbo.
|
|
4220
|
+
- Load-time kernel self-validation: every turbo lane checks itself against
|
|
4221
|
+
stock MLX in the model's exact dtype/quantization at boot; a mismatching
|
|
4222
|
+
lane falls back to the stock path for the session and the verdicts are
|
|
4223
|
+
surfaced as `kernel_selfcheck` in `/health`. Worst case is v2.0.0 speed,
|
|
4224
|
+
never wrong output.
|
|
4225
|
+
- `MTPLX_FORCE_GPU_FAMILY_FALLBACK=1` rehearses the exact M1-M4 code path
|
|
4226
|
+
on newer machines; `MTPLX_KERNEL_SELFCHECK=0` disables the probe.
|
|
4227
|
+
- CI kernel matrix on real M1 runners: kernel exactness across
|
|
4228
|
+
{bf16, fp16} x {4, 6, 8}-bit plus a live 4B turbo boot smoke.
|
|
4229
|
+
|
|
4230
|
+
### Changed
|
|
4231
|
+
|
|
4232
|
+
- 27B Optimized-Speed-FP16 (the M1/M2 routing target) defaults to turbo:
|
|
4233
|
+
19-31% faster decode than the v2.0.0 sustained default across 0.5k-32k
|
|
4234
|
+
context; true-AR multiplier 1.34x to ~2x. First-ever e2e measurement of
|
|
4235
|
+
this artifact; exactness gated (30/30 hot-shape logit-diff cases on real
|
|
4236
|
+
weights, greedy match turbo vs stock).
|
|
4237
|
+
- The published Speed-FP16 model card now recommends the turbo profile.
|
|
4238
|
+
|
|
4239
|
+
### Unchanged on purpose
|
|
4240
|
+
|
|
4241
|
+
- 35B A3B MoE and Gemma 4 keep sustained (expert layers / assistant-pair
|
|
4242
|
+
architecture bypass these kernels; named 2.0.2 lanes). The 4B keeps
|
|
4243
|
+
sustained (turbo measured slightly slower at matched depth). Compiled
|
|
4244
|
+
verify stays off for 6-bit models.
|
|
4245
|
+
|
|
4246
|
+
## [2.0.0] - 2026-07-06
|
|
4247
|
+
|
|
4248
|
+
MTPLX v2: the coding-agent release. Session-cache v2 (RAM + SSD), the
|
|
4249
|
+
turbo profile with NAX verify kernels and compiled verify, a new verify
|
|
4250
|
+
attention kernel wave for long context, and a long campaign of
|
|
4251
|
+
OpenCode/agent-bridge fixes measured on real sessions.
|
|
4252
|
+
|
|
4253
|
+
### Added
|
|
4254
|
+
|
|
4255
|
+
- Session-cache v2: boundary-true GDN restores, O(1) RAM restores, SSD
|
|
4256
|
+
cold tier (default on) that survives daemon restarts — a 100k-token
|
|
4257
|
+
session restores in ~2s after a restart instead of a five-minute cold
|
|
4258
|
+
prefill. Prompt-cache reuse now chains across agent tool rounds.
|
|
4259
|
+
- Turbo profile: verify-specialized quantized-matmul kernels
|
|
4260
|
+
(`MTPLX_NAX_VERIFY`, vk_k/vk-q8 families) plus context-routed compiled
|
|
4261
|
+
verify with a per-model quantization gate. Measured on M5 Max: 27B
|
|
4262
|
+
Optimized-Speed 44.7 -> 58-60 tok/s chat lane; Optimized-Quality (q8)
|
|
4263
|
+
31-36 -> 43-44 tok/s.
|
|
4264
|
+
- The quantized 27B flagships (Optimized-Speed, Optimized-Quality, and
|
|
4265
|
+
the legacy Optimized hybrid) now default to the turbo profile on the
|
|
4266
|
+
CLI and the bare OpenAI API — the same launch rule the macOS app
|
|
4267
|
+
applies. Explicit `--profile` flags and wizard picks still win; other
|
|
4268
|
+
models keep the sustained default.
|
|
4269
|
+
- `POST /admin/cache/clear` resets the MLX peak-memory counter after
|
|
4270
|
+
dropping the session bank, so per-request `peak_memory_bytes` reports
|
|
4271
|
+
the current phase instead of a process-lifetime ratchet (benchmark
|
|
4272
|
+
harnesses that clear between context rows now chart honest per-row
|
|
4273
|
+
peaks).
|
|
4274
|
+
- `--scheduler-mode ar_batch` now genuinely admits anonymous OpenAI
|
|
4275
|
+
clients into the concurrency-adaptive batch lane (lone requests keep
|
|
4276
|
+
solo MTP; real concurrency shares the batched AR decode lane), and
|
|
4277
|
+
the batched lane samples on the GPU (decode-heavy batch-8 aggregate
|
|
4278
|
+
70.9 -> 79.2 tok/s). Serial remains the default: measured end to end,
|
|
4279
|
+
serialized solo-MTP still beats batched AR on prefill-heavy
|
|
4280
|
+
concurrent loads because MTP decode is ~4x faster per stream.
|
|
4281
|
+
- Long-context decode wave: a packed-GQA verify attention kernel plus
|
|
4282
|
+
commit-first KV donation in compiled verify. Measured on M5 Max
|
|
4283
|
+
(Optimized-Speed): 64k decode +12%, 128k decode 17 -> 20+ tok/s, and
|
|
4284
|
+
peak memory down 8 GB at 64k / 16 GB at 128k.
|
|
4285
|
+
- Startup warming without the wait: the daemon is ready in ~2s and the
|
|
4286
|
+
deeper kernel/shape warmup continues silently in the background,
|
|
4287
|
+
yielding instantly to real requests. First messages hit warm kernels
|
|
4288
|
+
without a slow boot.
|
|
4289
|
+
- RAM session-cache budget now scales to the machine (roughly half the
|
|
4290
|
+
RAM headroom above the model) instead of a flat cap, and the app's
|
|
4291
|
+
Settings tab exposes explicit RAM and SSD cache limits.
|
|
4292
|
+
- Per-request presence/frequency penalties end-to-end (server, CLI
|
|
4293
|
+
flags, dashboard slider, app dial), with MLX on-device penalty math.
|
|
4294
|
+
- App chat: markdown renders live during streaming at zero per-token
|
|
4295
|
+
cost; each turn gets one compact activity strip with grouped tool
|
|
4296
|
+
rounds and a sources footer for web results; turbo is a first-class
|
|
4297
|
+
mode in Settings.
|
|
4298
|
+
- Tool contracts are date-anchored (web-search answers stop regressing
|
|
4299
|
+
to the training cutoff) and post-search answers are no longer
|
|
4300
|
+
clipped to one sentence.
|
|
4301
|
+
- Vision: images flow through the OpenAI API into MTP decode; MoE
|
|
4302
|
+
multimodal checkpoints that store the tower under `model.visual.*`
|
|
4303
|
+
(for example Ornith 1.0) are now recognised (community PR #134).
|
|
4304
|
+
- Gemma 4 assistant-pair models default to their measured-best MTP
|
|
4305
|
+
depth; explicit `--depth` still wins.
|
|
4306
|
+
|
|
4307
|
+
### Fixed
|
|
4308
|
+
|
|
4309
|
+
- Fresh installs no longer crash at model load: transformers 5.13.0
|
|
4310
|
+
broke mlx-lm's import (`AutoTokenizer.register` string key), which
|
|
4311
|
+
killed every new install and DMG first run. mtplx now pins
|
|
4312
|
+
`transformers<5.13` (#135, #136, community PR #137).
|
|
4313
|
+
- The app no longer kills a healthy engine. The health watchdog
|
|
4314
|
+
treated a response it could not parse the same as a dead server and
|
|
4315
|
+
terminated the daemon mid-session — the main driver of "Stream
|
|
4316
|
+
offline" / "server dies mid-session during agent workloads" reports
|
|
4317
|
+
(#105). Liveness is now transport truth: if the daemon answers, it
|
|
4318
|
+
lives.
|
|
4319
|
+
- SSD session-cache restores are boundary-true for recurrent (GDN)
|
|
4320
|
+
layers, fixing corrupted agent output after a prefix restore (prompt
|
|
4321
|
+
recitation, phantom tool calls, argument leakage) (#130).
|
|
4322
|
+
- Vision + MTP: the draft head's committed history now consumes the
|
|
4323
|
+
spliced vision rows instead of image-pad embeddings, fixing
|
|
4324
|
+
fabricated visual differences between similar screenshots (#103).
|
|
4325
|
+
- Smart fan control ramps at request arrival, verifies actual RPM, and
|
|
4326
|
+
holds through the post-response cache work instead of dropping to
|
|
4327
|
+
auto while the GPU is still pinned (#127).
|
|
4328
|
+
- The app no longer rewrites the Hermes profile config on every
|
|
4329
|
+
launch; user sections (memory/providers/delegation/auxiliary) are
|
|
4330
|
+
merge-preserved (#131).
|
|
4331
|
+
- Served model identity is contract-match-only: third-party builds no
|
|
4332
|
+
longer get coerced onto official `mtplx-*` model ids (#57).
|
|
4333
|
+
- OpenCode plan -> build mode switch no longer breaks the prompt cache
|
|
4334
|
+
or hides file tools. The bridge misread OpenCode's build-mode
|
|
4335
|
+
reminder ("no longer in read-only mode") as a read-only instruction,
|
|
4336
|
+
hid write/edit exactly when the user said "execute the plan", and the
|
|
4337
|
+
model spiralled re-planning files it could not create. OpenCode
|
|
4338
|
+
toolsets now pass through byte-stable; the negation is parsed
|
|
4339
|
+
correctly for other clients.
|
|
4340
|
+
- Agent transcripts render prefix-stable across rounds (historical
|
|
4341
|
+
bytes never rewrite), force-answer and Pi-convergence contracts ride
|
|
4342
|
+
as pure suffixes, and warm prefills inherit recurrent boundaries —
|
|
4343
|
+
together these take mid-session tool rounds from multi-second cold
|
|
4344
|
+
re-prefills to sub-2s warm restores.
|
|
4345
|
+
- One busy OpenCode conversation no longer evicts every other project
|
|
4346
|
+
from the RAM session cache (prefix-superseded entries + a wider
|
|
4347
|
+
high-memory entry budget); multitasking across projects keeps each
|
|
4348
|
+
project's cache warm.
|
|
4349
|
+
- `mtplx start`'s live-dashboard handoff no longer crashes on an
|
|
4350
|
+
ImportError (community PR #133).
|
|
4351
|
+
- The batch scheduler no longer accumulates every finished request
|
|
4352
|
+
forever (community PR #132).
|
|
4353
|
+
- Prefill disconnect-cancel: closing an agent client mid-prefill frees
|
|
4354
|
+
the engine immediately instead of finishing a 48k-token orphan.
|
|
4355
|
+
- CJK and dead-key input no longer drops composed characters in the
|
|
4356
|
+
app's chat composer (community PR #119).
|
|
4357
|
+
- Dense-layout prefill chunk cache-cleanup cadence relaxed 1 -> 4:
|
|
4358
|
+
5-21% prefill TPS, memory byte-identical.
|
|
4359
|
+
|
|
4360
|
+
### Changed
|
|
4361
|
+
|
|
4362
|
+
- Bumped to 2.0.0. The default OpenCode/agent daemon profile is turbo
|
|
4363
|
+
with the compiled-verify per-model gate; q8 Quality stays on the
|
|
4364
|
+
eager verify path it measures best on.
|
|
4365
|
+
- Removed the vestigial "required MLX fork" metadata from all profiles.
|
|
4366
|
+
MTPLX runs on stock PyPI MLX and always has in the shipped product;
|
|
4367
|
+
the speed stack (NAX verify kernels, packed-GQA verify attention,
|
|
4368
|
+
compiled verify) ships as in-package Metal kernels, not a patched
|
|
4369
|
+
MLX/qmm build. Profile payloads no longer carry
|
|
4370
|
+
`required_mlx_fork_commit`/`required_mlx_fork_fragment`, `/health`
|
|
4371
|
+
now reports a plain `mlx_runtime` diagnostic instead of a fork
|
|
4372
|
+
expectation, and `--strict-fast-path` /
|
|
4373
|
+
`--strict-mlx-fork-assert` are accepted as deprecated no-ops.
|
|
4374
|
+
|
|
4375
|
+
## [1.0.4] - 2026-06-12
|
|
4376
|
+
|
|
4377
|
+
Same-day hotfix: 1.0.3 broke coding agents on their first tool turn,
|
|
4378
|
+
and pasting a GGUF repo got a wrong answer.
|
|
4379
|
+
|
|
4380
|
+
### Fixed
|
|
4381
|
+
|
|
4382
|
+
- Coding agents crashed on 1.0.3. Any tool-using client (Pi, Hermes,
|
|
4383
|
+
OpenCode, or anything speaking the OpenAI tools protocol) hit
|
|
4384
|
+
"unexpected keyword argument 'vision_splice'" on its first tool
|
|
4385
|
+
turn after a cache miss: non-streaming callers got a 500, streaming
|
|
4386
|
+
agents lost the stream, and the app reported "Stream offline." One
|
|
4387
|
+
stray argument left behind by the vision work, in a diagnostics
|
|
4388
|
+
call only agent tool turns reach. Removed, with tests that drive
|
|
4389
|
+
the exact path and an audit test that fails if any call ever passes
|
|
4390
|
+
an argument its target does not accept (#99, #100).
|
|
4391
|
+
- Pasting a GGUF repo into "Add a model from Hugging Face" claimed
|
|
4392
|
+
the repository did not exist. The app now says what is actually
|
|
4393
|
+
going on: GGUF is llama.cpp's format and MTPLX runs MLX models. It
|
|
4394
|
+
names the source repo the GGUF was made from so Forge can convert
|
|
4395
|
+
it, and genuine typos get "check the name" instead.
|
|
4396
|
+
- The "Add a model" repo check now follows the configured Hugging
|
|
4397
|
+
Face download mirror instead of always probing huggingface.co, so
|
|
4398
|
+
it works on networks where huggingface.co is blocked.
|
|
4399
|
+
|
|
4400
|
+
## [1.0.3] - 2026-06-12
|
|
4401
|
+
|
|
4402
|
+
The app can see. Vision lands across the Qwen models, and the
|
|
4403
|
+
compatibility gate stops blocking models that run fine.
|
|
4404
|
+
|
|
4405
|
+
### Added
|
|
4406
|
+
|
|
4407
|
+
- Vision support in chat and the API. Attach PNG, JPEG, or WebP images
|
|
4408
|
+
in the app composer, or send OpenAI image_url content parts to
|
|
4409
|
+
/v1/chat/completions, and the model describes what it sees with MTP
|
|
4410
|
+
speculative decoding still running on top. Works on Qwen 3.6 27B
|
|
4411
|
+
(Speed and Quality), Qwen 3.6 35B, and Qwen 3.5 9B. The 9B repo on
|
|
4412
|
+
Hugging Face regained its vision weights; an explicit mtplx pull now
|
|
4413
|
+
syncs such repo updates into existing local copies automatically.
|
|
4414
|
+
- /health reports whether the loaded model supports vision, and the
|
|
4415
|
+
composer adapts to it.
|
|
4416
|
+
|
|
4417
|
+
### Fixed
|
|
4418
|
+
|
|
4419
|
+
- Models that run fine are no longer refused for paperwork. The
|
|
4420
|
+
compatibility gate treated unverified runtime contracts (including
|
|
4421
|
+
the official Optimized Quality build) and even "slower than AR"
|
|
4422
|
+
speed evidence as reasons not to load. Verification is now a label:
|
|
4423
|
+
unverified models load with an honest note, and refusals are
|
|
4424
|
+
reserved for models that genuinely cannot execute (#98).
|
|
4425
|
+
- The gate's explanation message crashed with a traceback instead of
|
|
4426
|
+
printing since 1.0.0. It prints again, including the hint that was
|
|
4427
|
+
supposed to unblock you (#98).
|
|
4428
|
+
- Image attachments preview their actual pixels in the composer and
|
|
4429
|
+
the transcript instead of a "Could not read" placeholder.
|
|
4430
|
+
|
|
4431
|
+
## [1.0.2] - 2026-06-11
|
|
4432
|
+
|
|
4433
|
+
Bug-fix release with one small feature.
|
|
4434
|
+
|
|
4435
|
+
### Fixed
|
|
4436
|
+
|
|
4437
|
+
- Choosing the Auto or Sustained Max profile in the app's Settings left
|
|
4438
|
+
the engine unable to start, showing Degraded on every launch until
|
|
4439
|
+
the profile was changed back. Both values now resolve to real
|
|
4440
|
+
profiles (Sustained Max keeps its pinned-fans intent as the fan mode
|
|
4441
|
+
setting), existing configurations heal themselves on load, and the
|
|
4442
|
+
picker only offers values the engine accepts. `mtplx serve --profile
|
|
4443
|
+
auto` works from the command line too.
|
|
4444
|
+
- Parallel requests from agent tools that do not send session ids could
|
|
4445
|
+
fail with "session anon-... is already in flight" when they shared a
|
|
4446
|
+
prompt prefix. Busy sessions now fork to a fresh session instead of
|
|
4447
|
+
erroring, and anonymous session ids are random rather than clock
|
|
4448
|
+
derived. Reported and fixed by Frank Denis (@jedisct1) in #95.
|
|
4449
|
+
- A daemon launch that lost its port (another server bound it between
|
|
4450
|
+
checks, or a listener invisible to the local probe held it) now
|
|
4451
|
+
remediates and retries once before reporting a failure, and the
|
|
4452
|
+
failure message names the occupant when it can.
|
|
4453
|
+
|
|
4454
|
+
### Added
|
|
4455
|
+
|
|
4456
|
+
- Optional Hugging Face download mirror for networks where
|
|
4457
|
+
huggingface.co is blocked (requested from mainland China in #96). Set
|
|
4458
|
+
it inline in the onboarding download step or later in Settings under
|
|
4459
|
+
Advanced; downloads and the engine then use the mirror endpoint. The
|
|
4460
|
+
stored HF token is never sent to a mirror, so gated repos stay on the
|
|
4461
|
+
official endpoint.
|
|
4462
|
+
|
|
4463
|
+
## [1.0.1] - 2026-06-11
|
|
4464
|
+
|
|
4465
|
+
Bug-fix release.
|
|
4466
|
+
|
|
4467
|
+
### Fixed
|
|
4468
|
+
|
|
4469
|
+
- First-run tuning no longer fails on Macs where fan control cannot
|
|
4470
|
+
verify a max ramp (for example when the passwordless helper grant is
|
|
4471
|
+
not in place yet). Tuning now runs with fans on automatic, the
|
|
4472
|
+
results are labeled accordingly, and `--require-max-fans` keeps the
|
|
4473
|
+
strict behavior for benchmarking.
|
|
4474
|
+
- The `mtplx` CLI accepts the official Gemma 4 assistant-pair repos
|
|
4475
|
+
directly from Hugging Face. The app already ran them; the CLI's
|
|
4476
|
+
preflight now reaches the same verdict.
|
|
4477
|
+
|
|
4478
|
+
## [1.0.0] - 2026-06-10
|
|
4479
|
+
|
|
4480
|
+
The first full release: the native macOS app and the `mtplx` command line
|
|
4481
|
+
working as one product. Full notes:
|
|
4482
|
+
[mtplx.com/releases/notes/v1.0.0](https://mtplx.com/releases/notes/v1.0.0.html).
|
|
4483
|
+
|
|
4484
|
+
### Added
|
|
4485
|
+
|
|
4486
|
+
- Native macOS app with onboarding (hardware check, model pick, guided
|
|
4487
|
+
setup, tuning), a live speed dashboard (decode gauge, acceptance by
|
|
4488
|
+
depth, verify waterfall, activity), native chat with attachments and
|
|
4489
|
+
web search, Forge, the AIME benchmark, and agent launchers for
|
|
4490
|
+
OpenCode, Pi, Hermes, and Open WebUI.
|
|
4491
|
+
- New models: Gemma 4 (assistant-pair drafting tuned by draft block
|
|
4492
|
+
size) and Qwen 3.6 MoE 35B-A3B (prequantized expert sidecars,
|
|
4493
|
+
normalized expert layouts, hard blocks on unrunnable layouts),
|
|
4494
|
+
alongside Qwen 3.5 4B and 9B for smaller machines.
|
|
4495
|
+
- KV cache reuse on two layers: warm-prefix reuse in RAM across turns
|
|
4496
|
+
and requests (multi-turn chats and agents like OpenCode hit the cache
|
|
4497
|
+
instead of re-processing the conversation), and an SSD session cache
|
|
4498
|
+
that persists KV state to disk with enforced size caps and restores
|
|
4499
|
+
near-instantly across server restarts.
|
|
4500
|
+
- Concurrency: continuous batching with presets, a scheduler mode, and
|
|
4501
|
+
explicit caps (`--max-active-requests`, `--decode-batch-max`,
|
|
4502
|
+
`--batch-wait-ms`).
|
|
4503
|
+
- Smart fan mode across the app, CLI, and server API: ramps while the
|
|
4504
|
+
model works, restores on idle, survives client handoffs, and keeps the
|
|
4505
|
+
crash-safe restore watchdog.
|
|
4506
|
+
- Forge: convert any Hugging Face repo to MLX (AWQ, compressed-tensors,
|
|
4507
|
+
NVFP4, BF16 sources), calibrate and train the MTP adapter, verify with
|
|
4508
|
+
quality gates that reject speed wins that degrade output, and publish
|
|
4509
|
+
with provenance. Vision towers are preserved through conversion. In
|
|
4510
|
+
the app and as `mtplx forge`.
|
|
4511
|
+
- Agent-grade serving: hardened tool contracts and dedicated lanes for
|
|
4512
|
+
OpenCode, Pi, and Hermes; long-context depth policy; client identity
|
|
4513
|
+
tagging; a live server-sent metrics stream plus snapshot, thermal, and
|
|
4514
|
+
prefill-history endpoints; honest cancellation that stops decode.
|
|
4515
|
+
- Automatic runtime setup during onboarding: the app installs its own
|
|
4516
|
+
Python engine, fan control (ThermalForge), and the `mtplx` terminal
|
|
4517
|
+
command without requiring Homebrew. Release builds bundle a pinned
|
|
4518
|
+
CPython interpreter, the engine environment ignores user pip
|
|
4519
|
+
configuration, and the interpreter is signed so installed packages
|
|
4520
|
+
load on macOS 14 and 15. A stale `mtplx` on PATH is updated
|
|
4521
|
+
automatically; a newer one is left alone.
|
|
4522
|
+
- Official Apple Silicon model catalog (Qwen 3.5/3.6, Gemma 4 in speed,
|
|
4523
|
+
balance, and quality builds) with device-aware defaults shared by the
|
|
4524
|
+
app and the CLI: chip generation picks precision and machines under
|
|
4525
|
+
32 GiB route to the 9B model automatically.
|
|
4526
|
+
- App-aware `mtplx start`: detects a running MTPLX server and attaches
|
|
4527
|
+
instead of loading a second copy, lists installed models first, and
|
|
4528
|
+
adds a "Same as the MTPLX app" option. `mtplx stop` knows the app's
|
|
4529
|
+
persisted port.
|
|
4530
|
+
- New commands: `mtplx stop`, `mtplx settings get/set`, and
|
|
4531
|
+
`mtplx bench aime` for running the app's AIME benchmark from the
|
|
4532
|
+
terminal.
|
|
4533
|
+
- Sparkle automatic app updates with signed appcasts; the app verifies
|
|
4534
|
+
the installed engine against the shipped wheel and refreshes it after
|
|
4535
|
+
each update.
|
|
4536
|
+
|
|
4537
|
+
### Changed
|
|
4538
|
+
|
|
4539
|
+
- Busy ports are now handled gracefully everywhere: the app moves to the
|
|
4540
|
+
next free port with a banner (and persists it), and the CLI explains
|
|
4541
|
+
exactly who owns a busy port and how to stop it.
|
|
4542
|
+
- The OpenAI-compatible server honors `stop` sequences (chat,
|
|
4543
|
+
completions, and Anthropic `stop_sequences`) and `/v1/completions`
|
|
4544
|
+
streams tokens as they are generated with real finish reasons.
|
|
4545
|
+
|
|
4546
|
+
[2.8.1]: https://github.com/youssofal/MTPLX/releases/tag/v2.8.1
|
|
4547
|
+
[2.8.0]: https://github.com/youssofal/MTPLX/releases/tag/v2.8.0
|
|
4548
|
+
[2.7.2]: https://github.com/youssofal/MTPLX/releases/tag/v2.7.2
|
|
4549
|
+
[2.7.1]: https://github.com/youssofal/MTPLX/releases/tag/v2.7.1
|
|
4550
|
+
[2.7.0]: https://github.com/youssofal/MTPLX/releases/tag/v2.7.0
|
|
4551
|
+
[2.6.0]: https://github.com/youssofal/MTPLX/releases/tag/v2.6.0
|
|
4552
|
+
[2.5.4]: https://github.com/youssofal/MTPLX/releases/tag/v2.5.4
|
|
4553
|
+
[2.5.3]: https://github.com/youssofal/MTPLX/releases/tag/v2.5.3
|
|
4554
|
+
[2.5.2]: https://github.com/youssofal/MTPLX/releases/tag/v2.5.2
|
|
4555
|
+
[2.5.1]: https://github.com/youssofal/MTPLX/releases/tag/v2.5.1
|
|
4556
|
+
[2.5.0]: https://github.com/youssofal/MTPLX/releases/tag/v2.5.0
|
|
4557
|
+
[2.4.2]: https://github.com/youssofal/MTPLX/releases/tag/v2.4.2
|
|
4558
|
+
[2.4.1]: https://github.com/youssofal/MTPLX/releases/tag/v2.4.1
|
|
4559
|
+
[2.4.0]: https://github.com/youssofal/MTPLX/releases/tag/v2.4.0
|
|
4560
|
+
[2.3.0]: https://github.com/youssofal/MTPLX/releases/tag/v2.3.0
|
|
4561
|
+
[2.2.0]: https://github.com/youssofal/MTPLX/releases/tag/v2.2.0
|
|
4562
|
+
[2.1.0]: https://github.com/youssofal/MTPLX/releases/tag/v2.1.0
|
|
4563
|
+
[2.0.2]: https://github.com/youssofal/MTPLX/releases/tag/v2.0.2
|
|
4564
|
+
[2.0.1]: https://github.com/youssofal/MTPLX/releases/tag/v2.0.1
|
|
4565
|
+
[2.0.0]: https://github.com/youssofal/MTPLX/releases/tag/v2.0.0
|
|
4566
|
+
[1.0.4]: https://github.com/youssofal/MTPLX/releases/tag/v1.0.4
|
|
4567
|
+
[1.0.3]: https://github.com/youssofal/MTPLX/releases/tag/v1.0.3
|
|
4568
|
+
[1.0.2]: https://github.com/youssofal/MTPLX/releases/tag/v1.0.2
|
|
4569
|
+
[1.0.1]: https://github.com/youssofal/MTPLX/releases/tag/v1.0.1
|
|
4570
|
+
[1.0.0]: https://github.com/youssofal/MTPLX/releases/tag/v1.0.0
|