mlx-model-porting is a portable Agent Skill for taking an unfamiliar PyTorch
or Hugging Face model through static intake, architecture routing, source
capture, explicit weight conversion, staged MLX parity, profiling, and
evidence-gated optimization. It can also inspect an existing MLX project before
improving it.
This repository is an engineering workflow, not an arbitrary-checkpoint
conversion system. Its dense-decoder-transformer route is tooled end to end
and has been proven on one real model: pinned Qwen2.5-0.5B-Instruct. The other
16 architecture families have executable intake, routing, planning, generic
weight/parity validation, and evidence gates, but their architecture-module
implementation remains runbook-guided. All 17 routes have synthetic golden
scenarios; those fixtures are not 17 completed real-model ports. Controlled
domain evaluators beyond exact-output parity remain future work.
The public runbook is available at
mlx-porter.pages.dev, and its offline source is
in site/.
| Layer | Purpose |
|---|---|
mlx-model-porting/SKILL.md |
Compact agent contract and trigger map. |
mlx-model-porting/references/ |
Porting method, failure atlas, optimization guides, and 17 family runbooks. |
mlx-model-porting/assets/ |
Canonical architecture, technique, guidance, stack, source, benchmark, and claim data. |
mlx-model-porting/scripts/ |
Non-destructive inspection, planning, parity, benchmarking, evidence, and packaging tools. |
mlx-model-porting/examples/ |
Porting patterns and no-weights worked examples. |
tests/ |
Synthetic scenarios and contract, security, determinism, and portability tests. |
Current 0.5.0 corpus snapshot:
- 17 architecture-family routes with synthetic golden coverage;
- 350 evidence sources with explicit review depth; 23 currently carry classified support scope and claim types, while 327 remain intentionally unclassified;
- 66 technique records, 28 optimization-guidance methods, and 4 stack definitions;
- 29 inspectable Python scripts and 425 offline tests;
- a 697-node, 499-edge research graph plus a deterministically reconciled backlog;
- 13 checked-in benchmark receipts: 12
performance_observation, 0promotion_ready, and 1rejected; - 10 effective claims, all withheld.
The tooled path is deliberately narrower than the routing catalogue:
| Scope | What is executable now | What remains runbook-guided or future work |
|---|---|---|
dense-decoder-transformer |
Static inspection, Torch oracle capture, eager MLX scaffold generation, schema-2 weight-map conversion, MLX capture, first-divergence parity, benchmarking, and claim gating. | Additional configs still fail closed until their semantics are implemented; only one real checkpoint has completed the full path. |
| Other 16 families | Static inspection, family/hybrid routing, port planning, explicit weight-map validation, generic tensor comparison, benchmarking, and evidence/claim gates. | Architecture-specific MLX module implementation and model-specific capture wiring are described by the runbooks but are not generated. |
| Quality evaluation | Exact tensor/ID comparison and controlled exact-output benchmark parity. | Domain evaluators for language quality, vision, audio, speech, diffusion, streaming, scientific tasks, and lossy changes. |
- Inspect without executing model code. Inventory configuration, safetensors metadata, source formats, licenses, and remote-code risks. Local intake binds every file into a portable SHA-256 tree identity; incomplete identity or missing artifact-bound license evidence blocks advice.
- Route to a family runbook. Confidence and ambiguity gates can stop a weak route; hybrid models can require more than one runbook.
- Build a source oracle and the smallest eager MLX graph. Freeze fixtures and intermediate tensors before changing performance behavior. Dense decoders can use the checked-in capture and scaffold tools; other families follow their runbooks.
- Convert weights deterministically. Every rename, transpose, reshape, split, merge, and tie belongs in an inspectable weight map.
- Pass staged parity. The dense-decoder runner captures both sides and stops at the first input, embedding, layer, norm, logit, or exact-token divergence. Other families use the generic validation primitives until a family-specific runner exists.
- Profile, then advise. Recommendations match controlled family, capability, workload, objective, and software identifiers exactly. A local numeric promotion additionally requires the exact receipt-derived experiment fingerprint, not a look-alike generic profile.
- Measure one change at a time. Benchmark receipts are observations until lineage, workload, runner, stability, quality, rollback, and compatibility gates all pass.
- Package with provenance. Preserve source revision, license, conversion recipe, validation results, limits, and rollback conditions.
See RESEARCH_REPORT.md for the architecture and extension
flows, and VALIDATION.md for exactly what the checked-in gates
do and do not prove.
The distributable skill is the mlx-model-porting/ directory. For a supported
client preset:
python3 mlx-model-porting/scripts/install_skill.py --client codexFor an explicit destination:
python3 mlx-model-porting/scripts/install_skill.py \
--dest ~/.agents/skills \
--mode symlinkCopy mode is a release-artifact operation: run it from the repository checkout
that contains MANIFEST.json. It installs only the mlx-model-porting/
allowlist from that manifest, verifies every file hash and executable bit,
rejects unlisted source content and unsafe links, and then verifies the staged
tree is an exact identity match. Known local cache/build noise is never copied.
The installed skill includes its own complete Apache-2.0 LICENSE.
Symlink mode points the client at the checkout instead of materializing an
attested copy. Both modes refuse unsafe source/destination nesting and are
idempotent for an unchanged target. See
adapters/README.md for client-specific discovery notes.
| Surface | Requirement |
|---|---|
| Static intake, routing, planning, registries, and validation | Python 3.10+ standard library |
Tensor parity with compare_tensors.py |
NumPy |
Local source-oracle capture with capture_oracle.py |
PyTorch, Transformers, and NumPy |
Explicit --allow-network Hugging Face intake |
huggingface-hub |
| Model execution | Apple Silicon plus the exact MLX/framework packages required by the chosen port |
Create a dedicated environment for the optional tools rather than relying on machine-global packages:
python3 -m venv .venv
.venv/bin/python -m pip install --upgrade pip
.venv/bin/python -m pip install --require-hashes \
-r mlx-model-porting/requirements-tools.lockrequirements-tools.txt is the small authored input; the universal lock pins
transitive versions and artifact hashes. Refresh it with uv pip compile mlx-model-porting/requirements-tools.txt --universal --python-version 3.10 --generate-hashes --output-file mlx-model-porting/requirements-tools.lock.
CI additionally pins its minimal platform wheels in
.github/requirements-ci.txt. The skill installer deliberately does not install
Python packages or execute model code.
The default inspection path is offline and static. Network metadata access must be enabled explicitly.
python3 mlx-model-porting/scripts/inspect_model.py MODEL \
--output inspection.json \
--markdown inspection.md
python3 mlx-model-porting/scripts/recommend_optimizations.py inspection.json \
--output recommendations.json \
--markdown OPTIMIZATIONS.md
python3 mlx-model-porting/scripts/make_port_plan.py inspection.json \
--artifact-root MODEL \
--recommendations recommendations.json \
--output PORT_PLAN.mdRun the recommender only after inspection is clean. If inspection reports any
blocker, generate the port plan without recommendations; it will contain
remediation steps only, and --family cannot turn it into an actionable plan.
Every actionable plan requires --artifact-root: the planner reruns the static
inspector against those local bytes and rejects any artifact, routing, license,
or safety drift. It also recomputes the complete recommendation report before
embedding advice, so an edited intermediate report is not trusted.
Inspection reports use portable basename-only local references by default.
Pass --include-local-paths only when an absolute path is deliberately needed
for local debugging; do not publish that form.
For an unblocked dense-decoder-transformer inspection, the executable chain
runs in this order:
capture_oracle.pyloads only a pinned local Hugging Face model with remote code disabled and records deterministic inputs, embeddings, every decoder layer, final norm, logits, and greedy token IDs in a bounded NPZ plus a content-addressed manifest.scaffold_port.pyre-inspects the same artifact and generates the minimal eager MLX package. It supports only declared dense-decoder semantics, including inactive sliding-window metadata and independently detected Q/K/V/O projection biases; unsupported or ambiguous config fails closed.convert_checkpoint.pydrafts or applies a completeWEIGHT_MAPschema 2, rejects unresolved coverage and shape drift, and writes deterministic safetensors for the generated package.capture_mlx.pyvalidates the scaffold identity, executes the user-owned MLX package, and emits source-compatible tensors and a bounded manifest.run_parity.pyis the one-command parity flow after scaffolding and conversion. It invokes source and MLX capture, compares the ordered ladder, and stops at the first input, embedding, layer, final-norm, logit, or exact generated-ID divergence._capture_common.pyis the shared non-CLI contract for bounded inputs, manifests, tensor inventories, and strict artifact writing used by all three capture/parity commands.
After inspection pins the local source artifacts, capture the executable source oracle before implementing or optimizing the MLX graph. Use token IDs when the fixture should not depend on tokenizer behavior:
python3 mlx-model-porting/scripts/capture_oracle.py MODEL \
--token-ids 1 42 17 9 \
--generate-steps 4 \
--output source-oracle.npzThe command stays offline, refuses Hugging Face remote code, and writes
source-oracle.manifest.json beside the NPZ. The manifest binds config and
weight digests, capture inputs, library versions, and every tensor's shape,
dtype, and raw-byte SHA-256.
Generate the dense-decoder package, resolve and validate the draft map, then convert the weights:
python3 mlx-model-porting/scripts/scaffold_port.py inspection.json \
--artifact-root MODEL \
--output mlx_port
python3 mlx-model-porting/scripts/convert_checkpoint.py \
--source inspection.json \
--scaffold-manifest mlx_port/scaffold-manifest.json \
--emit-draft-map WEIGHT_MAP.draft.json
python3 mlx-model-porting/scripts/validate_weight_map.py \
--source inspection.json \
--target mlx_port/scaffold-manifest.json \
--mapping WEIGHT_MAP.json \
--output weight-map-report.json
python3 mlx-model-porting/scripts/convert_checkpoint.py \
--source MODEL \
--mapping WEIGHT_MAP.json \
--output convertedThe draft is not executable authority: review it, set draft to false, and
resolve every entry before conversion. Then run the source-to-MLX parity ladder
in one command:
python3 mlx-model-porting/scripts/run_parity.py \
--source-model MODEL \
--package mlx_port \
--weights converted \
--token-ids 1 42 17 9 \
--generate-steps 4 \
--output parity-report.jsonChoose model-specific tolerances and quality gates; never relax a default just to turn a first-divergence report green.
For a local synthetic smoke path that does not download a model:
python3 mlx-model-porting/scripts/inspect_model.py \
tests/fixtures/models/decoder \
--output /tmp/inspection.json
python3 mlx-model-porting/scripts/make_port_plan.py \
/tmp/inspection.json \
--artifact-root tests/fixtures/models/decoder \
--output /tmp/PORT_PLAN.mdThe fixture is expected to route to dense-decoder-transformer. That result
demonstrates the router and guard path only; it does not demonstrate an
end-to-end checkpoint conversion.
worked-port-qwen2.5-0.5b-instruct
records a complete offline run over the pinned local Qwen2.5-0.5B-Instruct
checkpoint. It includes the portable inspection and capture manifests, complete
weight map, per-rung parity report, exact Torch/standalone-MLX/MLX-LM transcript,
and schema-2 benchmark receipt pointers. All 29 ordered parity rungs and the
eight greedy token IDs matched. Weights and NPZ tensors are excluded.
Use inspector mode for local MLX code, an already-converted checkpoint, or a served MLX application. It inventories files and proof surfaces without running the project:
python3 mlx-model-porting/scripts/inspect_mlx_project.py PROJECT \
--model LOCAL_MODEL \
--output inspection.json \
--markdown MLX_INSPECTION.mdThis inspector also emits basename-only local references unless
--include-local-paths is explicitly requested for private debugging output.
If an inventory is truncated, the inspector blocks a clean or recommendation- ready conclusion instead of treating unseen files as safe.
Weight coverage and tensor parity are explicit steps, not side effects of loading a checkpoint:
python3 mlx-model-porting/scripts/validate_weight_map.py \
--source source.json \
--target target.json \
--mapping WEIGHT_MAP.json \
--output weight-map-report.json
python3 mlx-model-porting/scripts/compare_tensors.py \
source.npz target.npz \
--mapping mapping.json \
--atol 1e-5 \
--rtol 1e-4 \
--cosine-min 0.99 \
--output parity.jsonTolerance and quality gates must be chosen for the model and task. Passing a generic tensor threshold alone is not sufficient for language, vision, audio, speech, diffusion, or streaming correctness.
Supply a TargetProfile when asking for version- or workload-sensitive advice:
python3 mlx-model-porting/scripts/recommend_optimizations.py inspection.json \
--target-profile target-profile.json \
--objective peak-memory \
--output recommendations.json \
--markdown OPTIMIZATIONS.mdNo current claim has an effective range. Inspect the generated catalogue's withholding reasons before designing a new measurement:
import json
catalog = json.load(open("mlx-model-porting/assets/effective_claims.json"))
held = {
row["method_id"]: row["withheld_reasons"]
for row in catalog["claims"]
if row["effective_range"] is None
}
print(json.dumps(held, indent=2))Any future promotion must bind the candidate receipt SHA-256, model and source revisions, target and workload descriptors, enabled methods, metric, experiment contract, baseline receipt, aggregates, every measured result and raw-output digest, and the quality result digest. It must also carry an external signature verified against an out-of-repository trust anchor. A copied digest, empty profile, or merely similar Mac cannot unlock a number.
The advisor separates results into five controlled buckets:
validated-locally;validated-source-theory;benchmark-required;experimental-approach— requires explicit user opt-in;rejected-do-not-use.
Numeric authority comes from the generated
effective_claims.json, not
from prose or an unverified receipt sidecar. All current source-reported ranges
remain withheld observations: the existing TargetProfile cannot encode their
complete benchmark scope closely enough to make them profile-eligible.
Source-reported numbers are never advisor-visible until an equivalent local
target-workload benchmark passes the promotion contract.
Twelve local receipts remain performance observations. The generic external
command and legacy MLX-LM lanes do not independently attest the executed
dependency bytes and model/workload semantics. The narrow repository-owned
attested-mlx-port-wall-time adapter retains runner, dependency, challenge,
and output evidence for its exact Qwen workload, establishing internal
consistency and reproducibility-on-request. It does not close the authenticity
boundary: SHA-256 is a digest, not a signature, and no external signer or trust
root exists today. The raw 1.8122003933x inverse-wall-time ratio is one reproducible local-run observation, not a claim of reliable speedup. It covers load plus
six captured greedy tokens and is not an effective range, portable guarantee,
or pure-decode claim.
Compound numbers are withheld unless compatible steps were validated together
with unique evidence lineage; multiplying per-step ceilings is never a measured
claim.
EVIDENCE_INDEX.md is generated from the canonical
sources.yaml. Review depth means:
synthesized: directly informed a rule, runbook, or registry decision;screened: relevant material and limits were reviewed;indexed: catalogued for later review, with no implied support.
Support scope and review depth are separate from benchmark promotion. A pinned third-party implementation can support an implementation path while still requiring local parity and workload measurement.
Supported guidance status is also evidence-gated: native methods require classified official MLX evidence, Apple-project methods require classified official-project source, and third-party ports require a synthesized pinned or locally reproduced implementation reference.
The benchmark truth is generated in
BENCHMARK_REPORT.md. The current
13-receipt set contains 12 observations, 0 promotion-ready receipts, and 1
rejected receipt. All ten effective claims are withheld.
Measurement validation has two generic runner lanes. mlx-lm-generate retains
its token-throughput observations. external-command-wall-time covers other
architecture families with direct, no-shell argv: a digest-pinned Python runner
at argv position 1, models.target.id, models.target.revision, checked-in
workload inputs, and semantic variant_config must all be bound through an
exact argv template. The resolved interpreter and sanitized ambient environment
are hashed into the target identity, and the runner starts with -I -B to
exclude current-directory, user-site, and environment-injected imports. Static
symlink components are rejected and the quality contract is frozen before
execution. Its only performance metric is
wall_seconds, measured by the parent benchmark_command.py process and
checked against the complete bounded schema-1 raw report. Every measured run
must recreate the exact label-owned quality output; values printed by the model
command are never accepted as metrics. This generic lane is observation-only:
a digest does not prove that arbitrary runner code used its bound arguments.
The MLX-LM lane is also observation-only until its imported package bytes and
per-run model output are independently bound. The separate
attested-mlx-port-wall-time adapter is intentionally Qwen/workload-specific:
it binds a fresh parent challenge, reviewed runner bytes, retained loaded
dependencies, model/workload identity, and every output. Those bindings support
reproduction but cannot set execution_attested=true without a signature from
a protected Apple-Silicon signer verified against an out-of-repository trust
anchor. That external trust root is future work.
Run these checks from the repository root:
python3 -m unittest discover -s tests -v
python3 mlx-model-porting/scripts/audit_skill.py --strict mlx-model-porting
python3 mlx-model-porting/scripts/validate_sources.py mlx-model-porting
python3 mlx-model-porting/scripts/knowledge_curator.py --check-backlog
python3 mlx-model-porting/scripts/validate_benchmarks.py check
python3 mlx-model-porting/scripts/generate_claim_catalog.py --check
python3 mlx-model-porting/scripts/generate_evidence_index.py --check
python3 mlx-model-porting/scripts/generate_site_data.py --check
node --check site/data.js
node --check site/app.js
python3 mlx-model-porting/scripts/manifest.py check
git diff --checkThese are offline method and artifact gates. They do not replace a real Apple-Silicon port, task-quality evaluation, or target-workload benchmark.
Do not hand-edit generated reports or indexes. Add or change the canonical
registry, runbook, receipt, or version input, regenerate in dependency order,
and review the resulting diff. The ownership table and exact regeneration
commands are in CONTRIBUTING.md.
Research automation is review-only: it may collect and rank candidates and prepare a change, but it may not promote guidance or auto-merge research-derived recommendations.
The 0.5.0 release snapshot is dated 2026-07-11. The package version is recorded
in VERSION and the skill frontmatter; release changes are recorded
in CHANGELOG.md.