Text Generation
Transformers
GGUF
trtc_v4
ternary
quantization
trtc
small-model
speculative-decoding
conversational
Instructions to use FermionResearch/Neutrino-0.6B with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use FermionResearch/Neutrino-0.6B with Transformers:
# Use a pipeline as a high-level helper from transformers import pipeline pipe = pipeline("text-generation", model="FermionResearch/Neutrino-0.6B") messages = [ {"role": "user", "content": "Who are you?"}, ] pipe(messages)# Load model directly from transformers import AutoModelForCausalLM model = AutoModelForCausalLM.from_pretrained("FermionResearch/Neutrino-0.6B", device_map="auto") - Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- llama.cpp
How to use FermionResearch/Neutrino-0.6B with llama.cpp:
Install (macOS, Linux)
curl -LsSf https://llama.app/install.sh | sh # Start a local OpenAI-compatible server with a web UI: llama serve -hf FermionResearch/Neutrino-0.6B # Run inference directly in the terminal: llama cli -hf FermionResearch/Neutrino-0.6B
Install from WinGet (Windows)
winget install llama.cpp # Start a local OpenAI-compatible server with a web UI: llama serve -hf FermionResearch/Neutrino-0.6B # Run inference directly in the terminal: llama cli -hf FermionResearch/Neutrino-0.6B
Use pre-built binary
# Download pre-built binary from: # https://github.com/ggerganov/llama.cpp/releases # Start a local OpenAI-compatible server with a web UI: ./llama-server -hf FermionResearch/Neutrino-0.6B # Run inference directly in the terminal: ./llama-cli -hf FermionResearch/Neutrino-0.6B
Build from source code
git clone https://github.com/ggerganov/llama.cpp.git cd llama.cpp cmake -B build cmake --build build -j --target llama-server llama-cli # Start a local OpenAI-compatible server with a web UI: ./build/bin/llama-server -hf FermionResearch/Neutrino-0.6B # Run inference directly in the terminal: ./build/bin/llama-cli -hf FermionResearch/Neutrino-0.6B
Use Docker
docker model run hf.co/FermionResearch/Neutrino-0.6B
- LM Studio
- Jan
- vLLM
How to use FermionResearch/Neutrino-0.6B with vLLM:
Install from pip and serve model
# Install vLLM from pip: pip install vllm # Start the vLLM server: vllm serve "FermionResearch/Neutrino-0.6B" # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:8000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "FermionResearch/Neutrino-0.6B", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker
docker model run hf.co/FermionResearch/Neutrino-0.6B
- SGLang
How to use FermionResearch/Neutrino-0.6B with SGLang:
Install from pip and serve model
# Install SGLang from pip: pip install sglang # Start the SGLang server: python3 -m sglang.launch_server \ --model-path "FermionResearch/Neutrino-0.6B" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "FermionResearch/Neutrino-0.6B", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }'Use Docker images
docker run --gpus all \ --shm-size 32g \ -p 30000:30000 \ -v ~/.cache/huggingface:/root/.cache/huggingface \ --env "HF_TOKEN=<secret>" \ --ipc=host \ lmsysorg/sglang:latest \ python3 -m sglang.launch_server \ --model-path "FermionResearch/Neutrino-0.6B" \ --host 0.0.0.0 \ --port 30000 # Call the server using curl (OpenAI-compatible API): curl -X POST "http://localhost:30000/v1/chat/completions" \ -H "Content-Type: application/json" \ --data '{ "model": "FermionResearch/Neutrino-0.6B", "messages": [ { "role": "user", "content": "What is the capital of France?" } ] }' - Ollama
How to use FermionResearch/Neutrino-0.6B with Ollama:
ollama run hf.co/FermionResearch/Neutrino-0.6B
- Unsloth Desktop
- Pi
How to use FermionResearch/Neutrino-0.6B with Pi:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf FermionResearch/Neutrino-0.6B
Configure the model in Pi
# Install Pi: npm install -g @earendil-works/pi-coding-agent # Add to ~/.pi/agent/models.json: { "providers": { "llama-cpp": { "baseUrl": "http://localhost:8080/v1", "api": "openai-completions", "apiKey": "none", "models": [ { "id": "FermionResearch/Neutrino-0.6B" } ] } } }Run Pi
# Start Pi in your project directory: pi
- Docker Model Runner
How to use FermionResearch/Neutrino-0.6B with Docker Model Runner:
docker model run hf.co/FermionResearch/Neutrino-0.6B
- Lemonade
How to use FermionResearch/Neutrino-0.6B with Lemonade:
Pull the model
# Download Lemonade from https://lemonade-server.ai/ lemonade pull FermionResearch/Neutrino-0.6B
Run and chat with the model
lemonade run user.Neutrino-0.6B-{{QUANT_TAG}}List all available models
lemonade list
- Hermes Agent
How to use FermionResearch/Neutrino-0.6B with Hermes Agent:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf FermionResearch/Neutrino-0.6B
Configure Hermes
# Install Hermes: curl -fsSL https://hermes-agent.nousresearch.com/install.sh | bash hermes setup # Point Hermes at the local server: hermes config set model.provider custom hermes config set model.base_url http://127.0.0.1:8080/v1 hermes config set model.default FermionResearch/Neutrino-0.6B
Run Hermes
hermes
- Atomic Chat
- OpenClaw
How to use FermionResearch/Neutrino-0.6B with OpenClaw:
Start the llama.cpp server
# Install llama.cpp: brew install llama.cpp # Start a local OpenAI-compatible server: llama serve -hf FermionResearch/Neutrino-0.6B
Configure OpenClaw
# Install OpenClaw: npm install -g openclaw@latest # Register the local server and set it as the default model: openclaw onboard --non-interactive --mode local \ --auth-choice custom-api-key \ --custom-base-url http://127.0.0.1:8080/v1 \ --custom-model-id "FermionResearch/Neutrino-0.6B" \ --custom-provider-id llama-cpp \ --custom-compatibility openai \ --custom-text-input \ --accept-risk \ --skip-health
Run OpenClaw
openclaw agent --local --agent main --message "Hello from Hugging Face"
Commit ·
9c71059
0
Parent(s):
Super-squash branch 'main' using huggingface_hub
Browse filesThis view is limited to 50 files because it contains too many changes. See raw diff
- .gitattributes +34 -0
- LICENSE +202 -0
- MANIFEST.json +257 -0
- NOTICE +43 -0
- README.md +496 -0
- bin/MANIFEST.json +25 -0
- bin/README.md +216 -0
- bin/fermion-run-linux-x64 +3 -0
- bin/fermion-run-linux-x64.sha256 +1 -0
- bin/fermion-run-macos-arm64 +3 -0
- bin/fermion-run-macos-arm64.sha256 +1 -0
- config.json +26 -0
- generation_config.json +10 -0
- gguf/Modelfile +6 -0
- gguf/README.md +151 -0
- gguf/SHA256SUMS +6 -0
- gguf/fv5.patch +952 -0
- gguf/neutrino-0.6b-base-fv5.gguf +3 -0
- gguf/receipts/bench_0p6b.json +112 -0
- gguf/receipts/convert_0p6b.json +44 -0
- gguf/receipts/gv_0p6b_f32.json +17 -0
- gguf/receipts/gv_0p6b_fv5.json +17 -0
- gguf/receipts/ref_0p6b.json +26 -0
- merges.txt +0 -0
- mlx/MANIFEST.json +122 -0
- mlx/README.md +116 -0
- mlx/fermion_mlx/__init__.py +130 -0
- mlx/fermion_mlx/__main__.py +177 -0
- mlx/fermion_mlx/arch3.py +183 -0
- mlx/fermion_mlx/container.py +143 -0
- mlx/fermion_mlx/gates.py +164 -0
- mlx/fermion_mlx/mbatch.py +164 -0
- mlx/fermion_mlx/model.py +281 -0
- mlx/fermion_mlx/spec.py +256 -0
- mlx/fermion_mlx/trit_kernels.py +1053 -0
- mlx/generation_config.json +10 -0
- mlx/merges.txt +0 -0
- mlx/receipts/bench_0p6b_m5.json +51 -0
- mlx/receipts/cachegate_0p6b.json +61 -0
- mlx/receipts/kernel_version_sweep_0p6b.json +11 -0
- mlx/receipts/pathgate_0p6b.json +75 -0
- mlx/receipts/refgate_0p6b.json +133 -0
- mlx/requirements.txt +6 -0
- mlx/tokenizer.json +3 -0
- mlx/tokenizer_config.json +239 -0
- mlx/vocab.json +0 -0
- neutrino-0.6b_v4.bin +3 -0
- neutrino-0.6b_v4.tv4z +3 -0
- receipts/hf_greedy_cache_86565bf6.json +433 -0
- receipts/m5_bitgate_analysis.json +18 -0
.gitattributes
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Fermion Research — Hugging Face LFS patterns.
|
| 2 |
+
# Order matters: later matching rules override earlier ones.
|
| 3 |
+
|
| 4 |
+
# ---------------------------------------------------------------
|
| 5 |
+
# Weights, transports and other large binaries -> Git LFS.
|
| 6 |
+
# These MUST be LFS. The 8B container is 3.9 GB and its GGUF is
|
| 7 |
+
# 4.1 GB; committed as plain blobs the push fails outright.
|
| 8 |
+
# ---------------------------------------------------------------
|
| 9 |
+
*.bin filter=lfs diff=lfs merge=lfs -text
|
| 10 |
+
*.tv4z filter=lfs diff=lfs merge=lfs -text
|
| 11 |
+
*.gguf filter=lfs diff=lfs merge=lfs -text
|
| 12 |
+
*.safetensors filter=lfs diff=lfs merge=lfs -text
|
| 13 |
+
*.pt filter=lfs diff=lfs merge=lfs -text
|
| 14 |
+
*.pth filter=lfs diff=lfs merge=lfs -text
|
| 15 |
+
*.ckpt filter=lfs diff=lfs merge=lfs -text
|
| 16 |
+
*.npz filter=lfs diff=lfs merge=lfs -text
|
| 17 |
+
*.onnx filter=lfs diff=lfs merge=lfs -text
|
| 18 |
+
*.msgpack filter=lfs diff=lfs merge=lfs -text
|
| 19 |
+
*.tar filter=lfs diff=lfs merge=lfs -text
|
| 20 |
+
*.tar.gz filter=lfs diff=lfs merge=lfs -text
|
| 21 |
+
*.zip filter=lfs diff=lfs merge=lfs -text
|
| 22 |
+
|
| 23 |
+
# Prebuilt native runtimes shipped in bin/ (multi-MB executables).
|
| 24 |
+
# Listed explicitly rather than as bin/fermion-run-*: the Hub resolves LFS by
|
| 25 |
+
# "does any filter=lfs pattern match" and does NOT honour a later `-filter`
|
| 26 |
+
# unset, so a glob here would drag the tiny .sha256 sidecars into LFS too and
|
| 27 |
+
# they would render as pointer files instead of the sha line.
|
| 28 |
+
bin/fermion-run-linux-x64 filter=lfs diff=lfs merge=lfs -text
|
| 29 |
+
bin/fermion-run-macos-arm64 filter=lfs diff=lfs merge=lfs -text
|
| 30 |
+
|
| 31 |
+
# Tokenizer vocab: 11.4 MB, over the Hub's 10 MB non-LFS limit.
|
| 32 |
+
# (Must come AFTER any *.json rule to win. There is none, by design:
|
| 33 |
+
# every other .json here is a few KB and belongs in plain git.)
|
| 34 |
+
tokenizer.json filter=lfs diff=lfs merge=lfs -text
|
LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Apache License
|
| 2 |
+
Version 2.0, January 2004
|
| 3 |
+
http://www.apache.org/licenses/
|
| 4 |
+
|
| 5 |
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
| 6 |
+
|
| 7 |
+
1. Definitions.
|
| 8 |
+
|
| 9 |
+
"License" shall mean the terms and conditions for use, reproduction,
|
| 10 |
+
and distribution as defined by Sections 1 through 9 of this document.
|
| 11 |
+
|
| 12 |
+
"Licensor" shall mean the copyright owner or entity authorized by
|
| 13 |
+
the copyright owner that is granting the License.
|
| 14 |
+
|
| 15 |
+
"Legal Entity" shall mean the union of the acting entity and all
|
| 16 |
+
other entities that control, are controlled by, or are under common
|
| 17 |
+
control with that entity. For the purposes of this definition,
|
| 18 |
+
"control" means (i) the power, direct or indirect, to cause the
|
| 19 |
+
direction or management of such entity, whether by contract or
|
| 20 |
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
| 21 |
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
| 22 |
+
|
| 23 |
+
"You" (or "Your") shall mean an individual or Legal Entity
|
| 24 |
+
exercising permissions granted by this License.
|
| 25 |
+
|
| 26 |
+
"Source" form shall mean the preferred form for making modifications,
|
| 27 |
+
including but not limited to software source code, documentation
|
| 28 |
+
source, and configuration files.
|
| 29 |
+
|
| 30 |
+
"Object" form shall mean any form resulting from mechanical
|
| 31 |
+
transformation or translation of a Source form, including but
|
| 32 |
+
not limited to compiled object code, generated documentation,
|
| 33 |
+
and conversions to other media types.
|
| 34 |
+
|
| 35 |
+
"Work" shall mean the work of authorship, whether in Source or
|
| 36 |
+
Object form, made available under the License, as indicated by a
|
| 37 |
+
copyright notice that is included in or attached to the work
|
| 38 |
+
(an example is provided in the Appendix below).
|
| 39 |
+
|
| 40 |
+
"Derivative Works" shall mean any work, whether in Source or Object
|
| 41 |
+
form, that is based on (or derived from) the Work and for which the
|
| 42 |
+
editorial revisions, annotations, elaborations, or other modifications
|
| 43 |
+
represent, as a whole, an original work of authorship. For the purposes
|
| 44 |
+
of this License, Derivative Works shall not include works that remain
|
| 45 |
+
separable from, or merely link (or bind by name) to the interfaces of,
|
| 46 |
+
the Work and Derivative Works thereof.
|
| 47 |
+
|
| 48 |
+
"Contribution" shall mean any work of authorship, including
|
| 49 |
+
the original version of the Work and any modifications or additions
|
| 50 |
+
to that Work or Derivative Works thereof, that is intentionally
|
| 51 |
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
| 52 |
+
or by an individual or Legal Entity authorized to submit on behalf of
|
| 53 |
+
the copyright owner. For the purposes of this definition, "submitted"
|
| 54 |
+
means any form of electronic, verbal, or written communication sent
|
| 55 |
+
to the Licensor or its representatives, including but not limited to
|
| 56 |
+
communication on electronic mailing lists, source code control systems,
|
| 57 |
+
and issue tracking systems that are managed by, or on behalf of, the
|
| 58 |
+
Licensor for the purpose of discussing and improving the Work, but
|
| 59 |
+
excluding communication that is conspicuously marked or otherwise
|
| 60 |
+
designated in writing by the copyright owner as "Not a Contribution."
|
| 61 |
+
|
| 62 |
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
| 63 |
+
on behalf of whom a Contribution has been received by Licensor and
|
| 64 |
+
subsequently incorporated within the Work.
|
| 65 |
+
|
| 66 |
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
| 67 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 68 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 69 |
+
copyright license to reproduce, prepare Derivative Works of,
|
| 70 |
+
publicly display, publicly perform, sublicense, and distribute the
|
| 71 |
+
Work and such Derivative Works in Source or Object form.
|
| 72 |
+
|
| 73 |
+
3. Grant of Patent License. Subject to the terms and conditions of
|
| 74 |
+
this License, each Contributor hereby grants to You a perpetual,
|
| 75 |
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
| 76 |
+
(except as stated in this section) patent license to make, have made,
|
| 77 |
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
| 78 |
+
where such license applies only to those patent claims licensable
|
| 79 |
+
by such Contributor that are necessarily infringed by their
|
| 80 |
+
Contribution(s) alone or by combination of their Contribution(s)
|
| 81 |
+
with the Work to which such Contribution(s) was submitted. If You
|
| 82 |
+
institute patent litigation against any entity (including a
|
| 83 |
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
| 84 |
+
or a Contribution incorporated within the Work constitutes direct
|
| 85 |
+
or contributory patent infringement, then any patent licenses
|
| 86 |
+
granted to You under this License for that Work shall terminate
|
| 87 |
+
as of the date such litigation is filed.
|
| 88 |
+
|
| 89 |
+
4. Redistribution. You may reproduce and distribute copies of the
|
| 90 |
+
Work or Derivative Works thereof in any medium, with or without
|
| 91 |
+
modifications, and in Source or Object form, provided that You
|
| 92 |
+
meet the following conditions:
|
| 93 |
+
|
| 94 |
+
(a) You must give any other recipients of the Work or
|
| 95 |
+
Derivative Works a copy of this License; and
|
| 96 |
+
|
| 97 |
+
(b) You must cause any modified files to carry prominent notices
|
| 98 |
+
stating that You changed the files; and
|
| 99 |
+
|
| 100 |
+
(c) You must retain, in the Source form of any Derivative Works
|
| 101 |
+
that You distribute, all copyright, patent, trademark, and
|
| 102 |
+
attribution notices from the Source form of the Work,
|
| 103 |
+
excluding those notices that do not pertain to any part of
|
| 104 |
+
the Derivative Works; and
|
| 105 |
+
|
| 106 |
+
(d) If the Work includes a "NOTICE" text file as part of its
|
| 107 |
+
distribution, then any Derivative Works that You distribute must
|
| 108 |
+
include a readable copy of the attribution notices contained
|
| 109 |
+
within such NOTICE file, excluding those notices that do not
|
| 110 |
+
pertain to any part of the Derivative Works, in at least one
|
| 111 |
+
of the following places: within a NOTICE text file distributed
|
| 112 |
+
as part of the Derivative Works; within the Source form or
|
| 113 |
+
documentation, if provided along with the Derivative Works; or,
|
| 114 |
+
within a display generated by the Derivative Works, if and
|
| 115 |
+
wherever such third-party notices normally appear. The contents
|
| 116 |
+
of the NOTICE file are for informational purposes only and
|
| 117 |
+
do not modify the License. You may add Your own attribution
|
| 118 |
+
notices within Derivative Works that You distribute, alongside
|
| 119 |
+
or as an addendum to the NOTICE text from the Work, provided
|
| 120 |
+
that such additional attribution notices cannot be construed
|
| 121 |
+
as modifying the License.
|
| 122 |
+
|
| 123 |
+
You may add Your own copyright statement to Your modifications and
|
| 124 |
+
may provide additional or different license terms and conditions
|
| 125 |
+
for use, reproduction, or distribution of Your modifications, or
|
| 126 |
+
for any such Derivative Works as a whole, provided Your use,
|
| 127 |
+
reproduction, and distribution of the Work otherwise complies with
|
| 128 |
+
the conditions stated in this License.
|
| 129 |
+
|
| 130 |
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
| 131 |
+
any Contribution intentionally submitted for inclusion in the Work
|
| 132 |
+
by You to the Licensor shall be under the terms and conditions of
|
| 133 |
+
this License, without any additional terms or conditions.
|
| 134 |
+
Notwithstanding the above, nothing herein shall supersede or modify
|
| 135 |
+
the terms of any separate license agreement you may have executed
|
| 136 |
+
with Licensor regarding such Contributions.
|
| 137 |
+
|
| 138 |
+
6. Trademarks. This License does not grant permission to use the trade
|
| 139 |
+
names, trademarks, service marks, or product names of the Licensor,
|
| 140 |
+
except as required for reasonable and customary use in describing the
|
| 141 |
+
origin of the Work and reproducing the content of the NOTICE file.
|
| 142 |
+
|
| 143 |
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
| 144 |
+
agreed to in writing, Licensor provides the Work (and each
|
| 145 |
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
| 146 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
| 147 |
+
implied, including, without limitation, any warranties or conditions
|
| 148 |
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
| 149 |
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
| 150 |
+
appropriateness of using or redistributing the Work and assume any
|
| 151 |
+
risks associated with Your exercise of permissions under this License.
|
| 152 |
+
|
| 153 |
+
8. Limitation of Liability. In no event and under no legal theory,
|
| 154 |
+
whether in tort (including negligence), contract, or otherwise,
|
| 155 |
+
unless required by applicable law (such as deliberate and grossly
|
| 156 |
+
negligent acts) or agreed to in writing, shall any Contributor be
|
| 157 |
+
liable to You for damages, including any direct, indirect, special,
|
| 158 |
+
incidental, or consequential damages of any character arising as a
|
| 159 |
+
result of this License or out of the use or inability to use the
|
| 160 |
+
Work (including but not limited to damages for loss of goodwill,
|
| 161 |
+
work stoppage, computer failure or malfunction, or any and all
|
| 162 |
+
other commercial damages or losses), even if such Contributor
|
| 163 |
+
has been advised of the possibility of such damages.
|
| 164 |
+
|
| 165 |
+
9. Accepting Warranty or Additional Liability. While redistributing
|
| 166 |
+
the Work or Derivative Works thereof, You may choose to offer,
|
| 167 |
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
| 168 |
+
or other liability obligations and/or rights consistent with this
|
| 169 |
+
License. However, in accepting such obligations, You may act only
|
| 170 |
+
on Your own behalf and on Your sole responsibility, not on behalf
|
| 171 |
+
of any other Contributor, and only if You agree to indemnify,
|
| 172 |
+
defend, and hold each Contributor harmless for any liability
|
| 173 |
+
incurred by, or claims asserted against, such Contributor by reason
|
| 174 |
+
of your accepting any such warranty or additional liability.
|
| 175 |
+
|
| 176 |
+
END OF TERMS AND CONDITIONS
|
| 177 |
+
|
| 178 |
+
APPENDIX: How to apply the Apache License to your work.
|
| 179 |
+
|
| 180 |
+
To apply the Apache License to your work, attach the following
|
| 181 |
+
boilerplate notice, with the fields enclosed by brackets "{}"
|
| 182 |
+
replaced with your own identifying information. (Don't include
|
| 183 |
+
the brackets!) The text should be enclosed in the appropriate
|
| 184 |
+
comment syntax for the file format. We also recommend that a
|
| 185 |
+
file or class name and description of purpose be included on the
|
| 186 |
+
same "printed page" as the copyright notice for easier
|
| 187 |
+
identification within third-party archives.
|
| 188 |
+
|
| 189 |
+
Copyright 2026 Fermion Research
|
| 190 |
+
|
| 191 |
+
Licensed under the Apache License, Version 2.0 (the "License");
|
| 192 |
+
you may not use this file except in compliance with the License.
|
| 193 |
+
You may obtain a copy of the License at
|
| 194 |
+
|
| 195 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 196 |
+
|
| 197 |
+
Unless required by applicable law or agreed to in writing, software
|
| 198 |
+
distributed under the License is distributed on an "AS IS" BASIS,
|
| 199 |
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
| 200 |
+
See the License for the specific language governing permissions and
|
| 201 |
+
limitations under the License.
|
| 202 |
+
|
MANIFEST.json
ADDED
|
@@ -0,0 +1,257 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"note": "provenance: HF-tree filename -> source + sha256; weights entry is declarative until materialized",
|
| 3 |
+
"weights_container": {
|
| 4 |
+
"file": "neutrino-0.6b_v4.bin",
|
| 5 |
+
"status": "FINAL \u2014 0.6B base ship container (card + gguf + mlx pack gated on these bytes)",
|
| 6 |
+
"bytes": 327719836,
|
| 7 |
+
"sha256": "91c0b26d105710e755ef1ec1b5607c00da5d5cbd048fd5584135fb6fc1e19399",
|
| 8 |
+
"source": "Fermion Research release archive (public copy: huggingface.co/fermionresearch/Neutrino-0.6B)"
|
| 9 |
+
},
|
| 10 |
+
"files": {
|
| 11 |
+
".gitattributes": {
|
| 12 |
+
"bytes": 1829,
|
| 13 |
+
"sha256": "83f9874d64fc325a932d03f7e71d75895113b7faf57cfe78d759fd8ac4f514c5",
|
| 14 |
+
"source": "banked lane artifact"
|
| 15 |
+
},
|
| 16 |
+
"LICENSE": {
|
| 17 |
+
"bytes": 11347,
|
| 18 |
+
"sha256": "2f978a54ef89f3e4ee2d413c347e4b03500c95f29342277a287acc2ae27ae280",
|
| 19 |
+
"source": "Apache-2.0 text (same file across all release trees)"
|
| 20 |
+
},
|
| 21 |
+
"NOTICE": {
|
| 22 |
+
"bytes": 1561,
|
| 23 |
+
"sha256": "6b7f7ed0cbf486ac68240e223d551eabdeb1d6dba59ed614f04316780437e643",
|
| 24 |
+
"source": "banked lane artifact"
|
| 25 |
+
},
|
| 26 |
+
"bin/README.md": {
|
| 27 |
+
"bytes": 11489,
|
| 28 |
+
"sha256": "d0e8d9e0e647013f79fd9ecb28a9ac0dac00e418d28e51e2a6f4610716be27bb",
|
| 29 |
+
"source": "bin/ staging doc: which fermion-run binaries land at upload + sidecar convention"
|
| 30 |
+
},
|
| 31 |
+
"bin/fermion-run-linux-x64": {
|
| 32 |
+
"bytes": 1317208,
|
| 33 |
+
"sha256": "d3e1c3d7a7fe43e9dd4d6e16e75c0cd234bfc9866964284101d200f3da05310b",
|
| 34 |
+
"source": "RE-BLESSED release binary (Linux x86-64 AVX2 baseline, fully static) \u2014 0.1.10 fp16-KV + YaRN rebuild 2026-07-28"
|
| 35 |
+
},
|
| 36 |
+
"bin/fermion-run-linux-x64.sha256": {
|
| 37 |
+
"bytes": 88,
|
| 38 |
+
"sha256": "44b26f8fd77232280a3a9ac7576487ceaeeaf4170f89f9f6334a623ee88acfa0",
|
| 39 |
+
"source": "sha256 sidecar (shasum line) for fermion-run-linux-x64"
|
| 40 |
+
},
|
| 41 |
+
"bin/fermion-run-macos-arm64": {
|
| 42 |
+
"bytes": 671656,
|
| 43 |
+
"sha256": "2b9902fc36bf59c50a67ee0c79ae38a259d066e6ebff764abbf05ff72017351c",
|
| 44 |
+
"source": "RE-BLESSED release binary (macOS arm64 CPU/NEON, static libomp) \u2014 0.1.10 fp16-KV + YaRN rebuild 2026-07-28"
|
| 45 |
+
},
|
| 46 |
+
"bin/fermion-run-macos-arm64.sha256": {
|
| 47 |
+
"bytes": 90,
|
| 48 |
+
"sha256": "8bc33f96ac8c95ee842c741aa91ac1a4aef051deae4ba55ed3a3d246d0af9743",
|
| 49 |
+
"source": "sha256 sidecar (shasum line) for fermion-run-macos-arm64"
|
| 50 |
+
},
|
| 51 |
+
"config.json": {
|
| 52 |
+
"bytes": 503,
|
| 53 |
+
"sha256": "67cae94190633a2ea52435c0b159202e9c16f8196e110c7a2639cb28736ae972",
|
| 54 |
+
"source": "written for the trtc_v4 loader (hf_ternary registration)"
|
| 55 |
+
},
|
| 56 |
+
"generation_config.json": {
|
| 57 |
+
"bytes": 181,
|
| 58 |
+
"sha256": "2537909d4312b7155b60fe0aee96d342de1a00b7841ed3e7eba4c3835d16d8ae",
|
| 59 |
+
"source": "transformers generation defaults for this SKU"
|
| 60 |
+
},
|
| 61 |
+
"gguf/Modelfile": {
|
| 62 |
+
"bytes": 275,
|
| 63 |
+
"sha256": "af5261e7e6f6f0e2f52895f2f1c165f6d7c058b93628fa54e4d8fb35c82aad2a",
|
| 64 |
+
"source": "banked lane artifact"
|
| 65 |
+
},
|
| 66 |
+
"gguf/README.md": {
|
| 67 |
+
"bytes": 6650,
|
| 68 |
+
"sha256": "16657bac673140b0cef77afb68af869f2f1dc39887d36bfc77eb7d94176bd95c",
|
| 69 |
+
"source": "banked lane artifact"
|
| 70 |
+
},
|
| 71 |
+
"gguf/SHA256SUMS": {
|
| 72 |
+
"bytes": 501,
|
| 73 |
+
"sha256": "96d97263ff5e276b7852a08aa52eca9e49418fbdc8671f60e5d1c6d73a18779d",
|
| 74 |
+
"source": "banked lane artifact"
|
| 75 |
+
},
|
| 76 |
+
"gguf/receipts/bench_0p6b.json": {
|
| 77 |
+
"bytes": 2667,
|
| 78 |
+
"sha256": "1e22cc409b85626efa44b8d26fa5cc2cb93124f841aff76eb5df8d7347933d40",
|
| 79 |
+
"source": "banked lane artifact"
|
| 80 |
+
},
|
| 81 |
+
"gguf/receipts/convert_0p6b.json": {
|
| 82 |
+
"bytes": 949,
|
| 83 |
+
"sha256": "176ff8191f00257a913595e20da2b4bdbdbc994f48364c67b049b7a753d23d0b",
|
| 84 |
+
"source": "banked lane artifact"
|
| 85 |
+
},
|
| 86 |
+
"gguf/receipts/gv_0p6b_f32.json": {
|
| 87 |
+
"bytes": 461,
|
| 88 |
+
"sha256": "d2ddf55b1cbfc24125292683f3eb08cc2de37c3a8f4147f638f135277fd7d187",
|
| 89 |
+
"source": "banked lane artifact"
|
| 90 |
+
},
|
| 91 |
+
"gguf/receipts/gv_0p6b_fv5.json": {
|
| 92 |
+
"bytes": 460,
|
| 93 |
+
"sha256": "b87c0411e2644fb03b4f00d187751757b3eb4960238df55fbf10c348154881cc",
|
| 94 |
+
"source": "banked lane artifact"
|
| 95 |
+
},
|
| 96 |
+
"gguf/receipts/ref_0p6b.json": {
|
| 97 |
+
"bytes": 471,
|
| 98 |
+
"sha256": "5b294b8fe3653662e6baec494fcc821097bca9ff2a4318552b9583fbc1fd42bd",
|
| 99 |
+
"source": "banked lane artifact"
|
| 100 |
+
},
|
| 101 |
+
"merges.txt": {
|
| 102 |
+
"bytes": 1671853,
|
| 103 |
+
"sha256": "8831e4f1a044471340f7c0a83d7bd71306a5b867e95fd870f74d0c5308a904d5",
|
| 104 |
+
"source": "Qwen/Qwen3-8B tokenizer file (Apache-2.0 upstream)"
|
| 105 |
+
},
|
| 106 |
+
"mlx/MANIFEST.json": {
|
| 107 |
+
"bytes": 5342,
|
| 108 |
+
"sha256": "3ba9ba4932cc98c91a634bd96e43c6305d644dc73e9aabf886b9130a5b2014bb",
|
| 109 |
+
"source": "banked lane artifact"
|
| 110 |
+
},
|
| 111 |
+
"mlx/README.md": {
|
| 112 |
+
"bytes": 5392,
|
| 113 |
+
"sha256": "c32d9fa4f7dbbc53ecdad2131c1e33596e57f6045acc8f0790a9d2431f014b11",
|
| 114 |
+
"source": "banked lane artifact"
|
| 115 |
+
},
|
| 116 |
+
"mlx/fermion_mlx/__init__.py": {
|
| 117 |
+
"bytes": 5063,
|
| 118 |
+
"sha256": "a547844d587067681639df546e6d010dab80bbd4f0ee55c6e2a16fba97a1dd40",
|
| 119 |
+
"source": "banked lane artifact"
|
| 120 |
+
},
|
| 121 |
+
"mlx/fermion_mlx/__main__.py": {
|
| 122 |
+
"bytes": 8069,
|
| 123 |
+
"sha256": "71e8c7fea446184bd471af2c37e51b04b9fa8d0f5d35e0a76c31f6fd19af957d",
|
| 124 |
+
"source": "banked lane artifact"
|
| 125 |
+
},
|
| 126 |
+
"mlx/fermion_mlx/arch3.py": {
|
| 127 |
+
"bytes": 7541,
|
| 128 |
+
"sha256": "a831e8a24b2b9a986b393d1c316aac66a3399a082a57176254b2feeae032f8cd",
|
| 129 |
+
"source": "banked lane artifact"
|
| 130 |
+
},
|
| 131 |
+
"mlx/fermion_mlx/container.py": {
|
| 132 |
+
"bytes": 6059,
|
| 133 |
+
"sha256": "c5b352184ba9ddf9a3e424731226cb7dcb9901d3a1516c67d4557953b0101f2f",
|
| 134 |
+
"source": "banked lane artifact"
|
| 135 |
+
},
|
| 136 |
+
"mlx/fermion_mlx/gates.py": {
|
| 137 |
+
"bytes": 7394,
|
| 138 |
+
"sha256": "64d07d9ba1a783a3f56ba978da7885e9fbe693495b3a4b7b8e176607d59de52c",
|
| 139 |
+
"source": "banked lane artifact"
|
| 140 |
+
},
|
| 141 |
+
"mlx/fermion_mlx/mbatch.py": {
|
| 142 |
+
"bytes": 6492,
|
| 143 |
+
"sha256": "c9ec8d37f1cdc0554cda01cefbc435e67525779c79e0dac7fd760e7919e6fdd2",
|
| 144 |
+
"source": "banked lane artifact"
|
| 145 |
+
},
|
| 146 |
+
"mlx/fermion_mlx/model.py": {
|
| 147 |
+
"bytes": 12748,
|
| 148 |
+
"sha256": "9488f8194ce8be006ce05dfaf6118353de44005ce54f38f73a0908ccac2749da",
|
| 149 |
+
"source": "banked lane artifact"
|
| 150 |
+
},
|
| 151 |
+
"mlx/fermion_mlx/spec.py": {
|
| 152 |
+
"bytes": 11207,
|
| 153 |
+
"sha256": "969e946d5d2eeb63d44afa4c62dd2453d87fa14e83b936bc673482b59ee3b745",
|
| 154 |
+
"source": "banked lane artifact"
|
| 155 |
+
},
|
| 156 |
+
"mlx/fermion_mlx/trit_kernels.py": {
|
| 157 |
+
"bytes": 44135,
|
| 158 |
+
"sha256": "aeb8bd4b03419763c1543893f19be3b4029611b2df883096990c4beebade384b",
|
| 159 |
+
"source": "banked lane artifact"
|
| 160 |
+
},
|
| 161 |
+
"mlx/generation_config.json": {
|
| 162 |
+
"bytes": 181,
|
| 163 |
+
"sha256": "6ca52b0bcb818c9e52db8ea18413494110c5f2def581b769f621dda951a7863b",
|
| 164 |
+
"source": "banked lane artifact"
|
| 165 |
+
},
|
| 166 |
+
"mlx/merges.txt": {
|
| 167 |
+
"bytes": 1671853,
|
| 168 |
+
"sha256": "8831e4f1a044471340f7c0a83d7bd71306a5b867e95fd870f74d0c5308a904d5",
|
| 169 |
+
"source": "banked lane artifact"
|
| 170 |
+
},
|
| 171 |
+
"mlx/receipts/bench_0p6b_m5.json": {
|
| 172 |
+
"bytes": 812,
|
| 173 |
+
"sha256": "543a9e41d8374a8cd066643a4e7cfa925359a8efc9c3182cbebb366bb0afbe89",
|
| 174 |
+
"source": "banked lane artifact"
|
| 175 |
+
},
|
| 176 |
+
"mlx/receipts/cachegate_0p6b.json": {
|
| 177 |
+
"bytes": 775,
|
| 178 |
+
"sha256": "b6acd64a31f4529ea01b96103a44b5bc54da283a7c3250cd564b5510ebaabc8c",
|
| 179 |
+
"source": "banked lane artifact"
|
| 180 |
+
},
|
| 181 |
+
"mlx/receipts/kernel_version_sweep_0p6b.json": {
|
| 182 |
+
"bytes": 355,
|
| 183 |
+
"sha256": "6b0df2026ded00c50d6b34004bf2dc4aabf097d21decfad7a354de5be594fe87",
|
| 184 |
+
"source": "banked lane artifact"
|
| 185 |
+
},
|
| 186 |
+
"mlx/receipts/pathgate_0p6b.json": {
|
| 187 |
+
"bytes": 623,
|
| 188 |
+
"sha256": "2453e15509a012616a100e3767a373af20a763dff89104e3c4dfbeb24085d072",
|
| 189 |
+
"source": "banked lane artifact"
|
| 190 |
+
},
|
| 191 |
+
"mlx/receipts/refgate_0p6b.json": {
|
| 192 |
+
"bytes": 1907,
|
| 193 |
+
"sha256": "ee4b3949bad85a522f1d1b65344a230a40f9fe9dc5e5dfdca736c28751eb4d85",
|
| 194 |
+
"source": "banked lane artifact"
|
| 195 |
+
},
|
| 196 |
+
"mlx/requirements.txt": {
|
| 197 |
+
"bytes": 191,
|
| 198 |
+
"sha256": "1cf4d1fa581aaa5deae0568f88388c51166bbd69e7a89080b5a53488a7ef39b6",
|
| 199 |
+
"source": "banked lane artifact"
|
| 200 |
+
},
|
| 201 |
+
"mlx/tokenizer.json": {
|
| 202 |
+
"bytes": 11422654,
|
| 203 |
+
"sha256": "aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4",
|
| 204 |
+
"source": "banked lane artifact"
|
| 205 |
+
},
|
| 206 |
+
"mlx/tokenizer_config.json": {
|
| 207 |
+
"bytes": 9732,
|
| 208 |
+
"sha256": "d5d09f07b48c3086c508b30d1c9114bd1189145b74e982a265350c923acd8101",
|
| 209 |
+
"source": "banked lane artifact"
|
| 210 |
+
},
|
| 211 |
+
"mlx/vocab.json": {
|
| 212 |
+
"bytes": 2776833,
|
| 213 |
+
"sha256": "ca10d7e9fb3ed18575dd1e277a2579c16d108e32f27439684afa0e10b1440910",
|
| 214 |
+
"source": "banked lane artifact"
|
| 215 |
+
},
|
| 216 |
+
"receipts/hf_greedy_cache_86565bf6.json": {
|
| 217 |
+
"bytes": 3578,
|
| 218 |
+
"sha256": "f8e81dd6b582c4097cf49b2abbc10b9e233331e60df7ebc4e03bcc587ec8f7e3",
|
| 219 |
+
"source": "banked lane artifact"
|
| 220 |
+
},
|
| 221 |
+
"receipts/m5_bitgate_analysis.json": {
|
| 222 |
+
"bytes": 1751,
|
| 223 |
+
"sha256": "26ef236f04d2bf0957ae9206f660898e3a73947bda272085e07e5523c4ac2979",
|
| 224 |
+
"source": "banked lane artifact"
|
| 225 |
+
},
|
| 226 |
+
"receipts/m5_demo_rereceipt_p3.json": {
|
| 227 |
+
"bytes": 1740,
|
| 228 |
+
"sha256": "aff7397eec5146a0418523140fa7531fc748b6c5229bb34c8b76f0eb8f73776b",
|
| 229 |
+
"source": "banked lane artifact"
|
| 230 |
+
},
|
| 231 |
+
"receipts/receipt_base.json": {
|
| 232 |
+
"bytes": 890,
|
| 233 |
+
"sha256": "2ae4accabe018f8efb1e0434ef63c8347e9cd096de74456f9887cfe5c874b117",
|
| 234 |
+
"source": "banked lane artifact"
|
| 235 |
+
},
|
| 236 |
+
"receipts/ship_checkpoint_numbers.json": {
|
| 237 |
+
"bytes": 1635,
|
| 238 |
+
"sha256": "95e757373209d99fdf1a3381d6f4a124e588a90ad01b2ae21b105db83349c29d",
|
| 239 |
+
"source": "banked lane artifact"
|
| 240 |
+
},
|
| 241 |
+
"tokenizer.json": {
|
| 242 |
+
"bytes": 11422654,
|
| 243 |
+
"sha256": "aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4",
|
| 244 |
+
"source": "Qwen/Qwen3-8B tokenizer file (Apache-2.0 upstream)"
|
| 245 |
+
},
|
| 246 |
+
"tokenizer_config.json": {
|
| 247 |
+
"bytes": 9979,
|
| 248 |
+
"sha256": "02e7044c846290e910d9c9deca9812315d64be56a6aeee3cfbedc99d177644c4",
|
| 249 |
+
"source": "Qwen/Qwen3-8B tokenizer file (Apache-2.0 upstream)"
|
| 250 |
+
},
|
| 251 |
+
"vocab.json": {
|
| 252 |
+
"bytes": 2776833,
|
| 253 |
+
"sha256": "ca10d7e9fb3ed18575dd1e277a2579c16d108e32f27439684afa0e10b1440910",
|
| 254 |
+
"source": "Qwen/Qwen3-8B tokenizer file (Apache-2.0 upstream)"
|
| 255 |
+
}
|
| 256 |
+
}
|
| 257 |
+
}
|
NOTICE
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
Neutrino — Fermion Research
|
| 2 |
+
Copyright 2026 Fermion Research
|
| 3 |
+
|
| 4 |
+
This product includes software and model artifacts developed by
|
| 5 |
+
Fermion Research, licensed under the Apache License, Version 2.0
|
| 6 |
+
(see LICENSE).
|
| 7 |
+
|
| 8 |
+
--------------------------------------------------------------------
|
| 9 |
+
THIRD-PARTY NOTICES
|
| 10 |
+
--------------------------------------------------------------------
|
| 11 |
+
|
| 12 |
+
Qwen3 (Alibaba Cloud / Qwen Team)
|
| 13 |
+
Copyright (c) Alibaba Cloud
|
| 14 |
+
Licensed under the Apache License, Version 2.0
|
| 15 |
+
https://huggingface.co/Qwen/Qwen3-0.6B
|
| 16 |
+
|
| 17 |
+
This model is a derivative of Qwen/Qwen3-0.6B. In particular, the following
|
| 18 |
+
files in this repository are redistributed VERBATIM from the upstream
|
| 19 |
+
Qwen3 release and are covered by Alibaba Cloud's copyright and the
|
| 20 |
+
Apache License 2.0:
|
| 21 |
+
|
| 22 |
+
tokenizer.json
|
| 23 |
+
tokenizer_config.json
|
| 24 |
+
vocab.json
|
| 25 |
+
merges.txt
|
| 26 |
+
|
| 27 |
+
The model architecture is stock Qwen3 geometry. The ternary
|
| 28 |
+
quantization-aware training, the TRTC v4 container format, the
|
| 29 |
+
post-training, the runtimes and the evaluation receipts are Fermion
|
| 30 |
+
Research's own work.
|
| 31 |
+
|
| 32 |
+
You may obtain a copy of the Apache License 2.0 at:
|
| 33 |
+
http://www.apache.org/licenses/LICENSE-2.0
|
| 34 |
+
|
| 35 |
+
--------------------------------------------------------------------
|
| 36 |
+
|
| 37 |
+
llama.cpp (The ggml authors) — MIT License
|
| 38 |
+
|
| 39 |
+
The GGUF pack in gguf/ is produced for, and loaded by, Fermion
|
| 40 |
+
Research's fork of ggml-org/llama.cpp. That fork is distributed
|
| 41 |
+
separately under upstream's MIT license, NOT under this repository's
|
| 42 |
+
Apache-2.0 grant. Any patch or source in gguf/ that derives from
|
| 43 |
+
llama.cpp is MIT-licensed; see the fork's own LICENSE.
|
README.md
ADDED
|
@@ -0,0 +1,496 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
---
|
| 2 |
+
license: apache-2.0
|
| 3 |
+
base_model: Qwen/Qwen3-0.6B
|
| 4 |
+
pipeline_tag: text-generation
|
| 5 |
+
library_name: transformers
|
| 6 |
+
tags:
|
| 7 |
+
- ternary
|
| 8 |
+
- quantization
|
| 9 |
+
- trtc
|
| 10 |
+
- small-model
|
| 11 |
+
- speculative-decoding
|
| 12 |
+
---
|
| 13 |
+
|
| 14 |
+
# Neutrino-0.6B (certified speculative-decoding draft for Neutrino-8B)
|
| 15 |
+
|
| 16 |
+
> [!IMPORTANT]
|
| 17 |
+
> **This model exists for one purpose: it is the certified
|
| 18 |
+
> speculative-decoding DRAFT for
|
| 19 |
+
> [Neutrino-8B](https://huggingface.co/fermionresearch/Neutrino-8B).**
|
| 20 |
+
> Paired with the 8B it delivers certified-exact speculative decoding
|
| 21 |
+
> (27,648 spec tokens compared on the ship brain, zero mismatches; 41,472
|
| 22 |
+
> across the whole lane, zero mismatches anywhere). It is **not an
|
| 23 |
+
> assistant**: no chat tuning, no instruction following, no tool use, and
|
| 24 |
+
> its standalone knowledge is deliberately not a claim. If you want a small
|
| 25 |
+
> model to talk to, use
|
| 26 |
+
> [Neutrino-0.6B-Chat](https://huggingface.co/fermionresearch/Neutrino-0.6B-Chat).
|
| 27 |
+
|
| 28 |
+
A 596M-parameter base language model whose every transformer linear is
|
| 29 |
+
stored in a proprietary ternary-family weight format eight times smaller
|
| 30 |
+
than fp16, in a single 327,719,836-byte container (237,703,560 bytes on the
|
| 31 |
+
wire), decoding at **225-236 tok/s on the CPU cores of a 16 GB Apple M5**
|
| 32 |
+
with the same runtime binaries that execute Neutrino-8B. That last property
|
| 33 |
+
is what makes it a draft model rather than a separate deployment: the
|
| 34 |
+
verifier and the draft are the same format, read by the same code, in one
|
| 35 |
+
process.
|
| 36 |
+
|
| 37 |
+
This is the small end of the Neutrino family, shipped as the 8B's speed
|
| 38 |
+
companion. The honest capability numbers are in the battery below; read
|
| 39 |
+
them before using it for anything but its intended lane (drafting,
|
| 40 |
+
edge/embedded token generation, and as a reference artifact for
|
| 41 |
+
ternary-family QAT at tiny scale).
|
| 42 |
+
|
| 43 |
+
- Container: `neutrino-0.6b_v4.bin` - TRTC v4, arch-3, 28 layers, hidden
|
| 44 |
+
1024, vocab 151936. 327,719,836 bytes, sha256
|
| 45 |
+
`91c0b26d105710e755ef1ec1b5607c00da5d5cbd048fd5584135fb6fc1e19399`
|
| 46 |
+
(working name in receipts: `neutrino-0p6b_v4_base.bin`; the same bytes
|
| 47 |
+
appear as `neutrino_v4.bin` in the speculative-decoding lane).
|
| 48 |
+
- Coded transport: `neutrino-0.6b_v4.tv4z` - 237,703,560 bytes, lossless,
|
| 49 |
+
byte-exact round-trip receipt banked; sha256
|
| 50 |
+
`132d9ab758b0b1a5b9b6638e55647462dbdb4ec8345720cc98c3d4cf669e780c`.
|
| 51 |
+
- Inside: 196 packed ternary linears + one int8 embedding lane (**tied**:
|
| 52 |
+
the input embedding and lm_head share one table; container walk
|
| 53 |
+
cross-checked in the internal anatomy audit, and the GGUF
|
| 54 |
+
conversion records `tied_output_omitted: true`). Nothing in the decode
|
| 55 |
+
path is fp16/fp32 weights.
|
| 56 |
+
- Base: [Qwen/Qwen3-0.6B](https://huggingface.co/Qwen/Qwen3-0.6B)
|
| 57 |
+
(Apache-2.0), ternary QAT by
|
| 58 |
+
[Fermion Research](https://huggingface.co/fermionresearch).
|
| 59 |
+
Tokenizer shipped in this repo; vocabulary identical to the 8B's,
|
| 60 |
+
identity receipt banked, which is a precondition for drafting.
|
| 61 |
+
|
| 62 |
+
## Architecture
|
| 63 |
+
|
| 64 |
+
Geometry as read from the shipped container's header (bit-checked during
|
| 65 |
+
the GGUF conversion, `gguf/receipts/convert_0p6b.json`; parameter split and
|
| 66 |
+
byte budget from the provenance-gated container walk):
|
| 67 |
+
|
| 68 |
+
| field | value |
|
| 69 |
+
|---|---|
|
| 70 |
+
| Parameters | 596,049,920 (440,401,920 ternary projection + 155,582,464 int8 embedding + 65,536 fp32 norm) |
|
| 71 |
+
| Decoder layers | 28 |
|
| 72 |
+
| Hidden width | 1,024 |
|
| 73 |
+
| Feed-forward width | 3,072, gated (SwiGLU) |
|
| 74 |
+
| Attention | grouped-query 2:1 - 16 query heads, 8 KV heads, head_dim 128 |
|
| 75 |
+
| Rotary embedding | full head width (rotary_dims 128), theta 1,000,000 |
|
| 76 |
+
| Normalization | RMSNorm, eps 1e-6, plus per-head Q/K RMSNorm in attention |
|
| 77 |
+
| Context length | 40,960 tokens (`max_pos`) |
|
| 78 |
+
| Vocabulary | 151,936 |
|
| 79 |
+
| Embeddings | **tied** - one int8 table serves embed-in and lm_head (`eok=1`) |
|
| 80 |
+
| KV cache | 114,688 B/token fp16 (112 KiB): 0.47 GB @ 4k, 3.76 GB @ 32k |
|
| 81 |
+
| Coded lane | 196 ternary linears (7 per layer x 28); 113 fp32 norm tensors stay full precision |
|
| 82 |
+
|
| 83 |
+
Byte budget of the 327,719,836-byte container, by tensor class:
|
| 84 |
+
|
| 85 |
+
| lane | bytes | share |
|
| 86 |
+
|---|---:|---:|
|
| 87 |
+
| ternary weight lane (q/k/v/o/gate/up/down x 28 layers = 196 linears) | 165,150,720 | 50.39% |
|
| 88 |
+
| token embeddings, int8 (one tied table) | 155,582,464 | 47.47% |
|
| 89 |
+
| per-row metadata (dims, scales, row sums) | 5,508,960 | 1.68% |
|
| 90 |
+
| embedding row scales | 1,215,488 | 0.37% |
|
| 91 |
+
| norm vectors, fp32 (113 tensors) | 262,144 | 0.08% |
|
| 92 |
+
| container header | 60 | - |
|
| 93 |
+
|
| 94 |
+
Every layer costs exactly 5,898,240 weight-lane bytes (40.0% attention /
|
| 95 |
+
60.0% MLP; the 8B's split is 21.7% / 78.3%). State occupancy across the
|
| 96 |
+
440,401,920 ternary weights: 62.263% zero / 18.873% plus / 18.864% minus
|
| 97 |
+
(per-layer arrays in the internal anatomy audit).
|
| 98 |
+
|
| 99 |
+
**The scale story in one row.** The tied embedding table is 26.1% of the
|
| 100 |
+
parameters but 47.5% of the container's bytes, because the format only
|
| 101 |
+
touches the transformer linears and the int8 vocabulary is left alone. On
|
| 102 |
+
the 8B the same lane is 32.1% of the file. At this size the vocabulary,
|
| 103 |
+
not the network, is the largest single object you download.
|
| 104 |
+
|
| 105 |
+
## Formats and artifacts
|
| 106 |
+
|
| 107 |
+
| artifact | bytes | sha256 |
|
| 108 |
+
|---|---:|---|
|
| 109 |
+
| `neutrino-0.6b_v4.bin` (TRTC v4 container, the file every runtime executes) | 327,719,836 | `91c0b26d105710e755ef1ec1b5607c00da5d5cbd048fd5584135fb6fc1e19399` |
|
| 110 |
+
| `neutrino-0.6b_v4.tv4z` (lossless coded transport, 72.5% of raw) | 237,703,560 | `132d9ab758b0b1a5b9b6638e55647462dbdb4ec8345720cc98c3d4cf669e780c` |
|
| 111 |
+
| `gguf/neutrino-0.6b-base-fv5.gguf` (llama.cpp-fork pack, 310/310 tensors bit-checked vs the container) | 343,140,320 | `dd9354c4b3cd2b34195b0270540e4fa5db3811e42b6e4bdbd57958e0027e9272` |
|
| 112 |
+
| `gguf/fv5.patch` (the llama.cpp patch that builds the fork) | - | `81bf7be5eef18eb98ab4823596374c8dd6615151cf5615425973cb65942c0f73` |
|
| 113 |
+
|
| 114 |
+
Container md5 for cross-checking older receipts:
|
| 115 |
+
`86565bf6183e8031943aac9fd9771385`.
|
| 116 |
+
|
| 117 |
+
## Quickstart
|
| 118 |
+
|
| 119 |
+
Five surfaces read those artifacts. All five execute the same container.
|
| 120 |
+
|
| 121 |
+
**1. pip engine** (the one-command door; pulls the container and the
|
| 122 |
+
platform-matching native runtime):
|
| 123 |
+
|
| 124 |
+
```bash
|
| 125 |
+
pip install fermion-research
|
| 126 |
+
fermion generate --model fermionresearch/Neutrino-0.6B \
|
| 127 |
+
--raw --max-new 64 "2, 3, 5, 7, 11," # base model: --raw skips the chat template; the prompt is positional
|
| 128 |
+
```
|
| 129 |
+
|
| 130 |
+
The first run downloads the ~0.3 GB container with no progress display in
|
| 131 |
+
fermion 0.1.5; from 0.1.6 the download shows a progress bar. Later runs
|
| 132 |
+
load from the local cache.
|
| 133 |
+
|
| 134 |
+
**2. Reference torch path** (bit-packed weights at rest, registers as a
|
| 135 |
+
native `transformers` model). Download the repo first — the loader
|
| 136 |
+
resolves the container relative to a local path only, so passing the hub
|
| 137 |
+
id straight to `from_pretrained` raises `FileNotFoundError`:
|
| 138 |
+
|
| 139 |
+
```bash
|
| 140 |
+
hf download fermionresearch/Neutrino-0.6B --local-dir Neutrino-0.6B \
|
| 141 |
+
--exclude "gguf/*" --exclude "*.tv4z" # skip the packs other runtimes use
|
| 142 |
+
```
|
| 143 |
+
|
| 144 |
+
```python
|
| 145 |
+
import fermion # registers the trtc_v4 model type
|
| 146 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 147 |
+
|
| 148 |
+
model = AutoModelForCausalLM.from_pretrained("Neutrino-0.6B") # the downloaded directory
|
| 149 |
+
tokenizer = AutoTokenizer.from_pretrained("Neutrino-0.6B")
|
| 150 |
+
ids = tokenizer("2, 3, 5, 7, 11,", return_tensors="pt").input_ids
|
| 151 |
+
print(tokenizer.decode(model.generate(ids, max_new_tokens=64)[0]))
|
| 152 |
+
```
|
| 153 |
+
|
| 154 |
+
This is a BASE model: prompt it as a completion engine. Greedy decoding
|
| 155 |
+
produces repetitive text on open-ended prompts (typical for 0.6B base
|
| 156 |
+
models); use sampling for anything user-facing.
|
| 157 |
+
|
| 158 |
+
`import fermion` must come first: it registers the `trtc_v4` model type.
|
| 159 |
+
Without it Transformers raises `model type 'trtc_v4' ... not recognize this
|
| 160 |
+
architecture` and advises upgrading Transformers or installing it from
|
| 161 |
+
source. Both are dead ends — the type is registered at import time by the
|
| 162 |
+
`fermion-research` package, and `trust_remote_code=True` will not help
|
| 163 |
+
because this repo carries no `auto_map`. Add the import.
|
| 164 |
+
|
| 165 |
+
**3. Native binary** (the fast path; this repo ships the `fermion-run-*`
|
| 166 |
+
binaries under `bin/` — they execute TRTC v4 arch-3 containers
|
| 167 |
+
generically, so the same binaries also run the 8B with no rebuild). From
|
| 168 |
+
the downloaded repo directory:
|
| 169 |
+
|
| 170 |
+
```bash
|
| 171 |
+
(cd bin && shasum -a 256 -c fermion-run-macos-arm64.sha256) # sidecar holds a bare filename
|
| 172 |
+
chmod +x bin/fermion-run-macos-arm64 # hf download writes 0644
|
| 173 |
+
xattr -d com.apple.quarantine bin/fermion-run-macos-arm64 2>/dev/null || true # macOS only
|
| 174 |
+
./bin/fermion-run-macos-arm64 neutrino-0.6b_v4.bin "1,791,1283" 128 9 # ids, n_new, threads
|
| 175 |
+
```
|
| 176 |
+
|
| 177 |
+
On Linux use `bin/fermion-run-linux-x64` and skip the `xattr` line.
|
| 178 |
+
|
| 179 |
+
**4. GGUF pack + our llama.cpp fork** (also the draft-model file for
|
| 180 |
+
llama.cpp's native speculative decoding against the 8B pack). The fork is
|
| 181 |
+
public at [fermionresearch/llama.cpp](https://github.com/fermionresearch/llama.cpp),
|
| 182 |
+
branch `fermion-fv5` (= upstream `ggml-org/llama.cpp` @ `d67c0b41` + the
|
| 183 |
+
FV5 patch; `gguf/fv5.patch` in this repo is the same change as a mailbox
|
| 184 |
+
patch):
|
| 185 |
+
|
| 186 |
+
```bash
|
| 187 |
+
hf download fermionresearch/Neutrino-0.6B gguf/neutrino-0.6b-base-fv5.gguf --local-dir .
|
| 188 |
+
git clone https://github.com/fermionresearch/llama.cpp && cd llama.cpp
|
| 189 |
+
git checkout fermion-fv5
|
| 190 |
+
cmake -B build -DCMAKE_BUILD_TYPE=Release -DLLAMA_CURL=OFF -DGGML_METAL=OFF # no FV5 Metal kernel yet: CPU path
|
| 191 |
+
cmake --build build -j --target llama-completion
|
| 192 |
+
./build/bin/llama-completion -m ../gguf/neutrino-0.6b-base-fv5.gguf \
|
| 193 |
+
-p "2, 3, 5, 7, 11," -n 128 --temp 0 -no-cnv
|
| 194 |
+
```
|
| 195 |
+
|
| 196 |
+
**5. MLX pack** (Apple-silicon runtime with custom Metal kernels). Run it
|
| 197 |
+
from the `mlx/` folder of the downloaded repo — the `fermion_mlx` package
|
| 198 |
+
and its tokenizer files ship there, and `requirements.txt` installs `mlx`
|
| 199 |
+
itself:
|
| 200 |
+
|
| 201 |
+
```bash
|
| 202 |
+
cd Neutrino-0.6B/mlx # the directory downloaded in step 2
|
| 203 |
+
pip install -r requirements.txt
|
| 204 |
+
python -m fermion_mlx --model ../neutrino-0.6b_v4.bin --mode gen \
|
| 205 |
+
--tokenizer . --prompt "2, 3, 5, 7, 11,"
|
| 206 |
+
```
|
| 207 |
+
|
| 208 |
+
## Recommended settings, by use case
|
| 209 |
+
|
| 210 |
+
| use case | settings | why |
|
| 211 |
+
|---|---|---|
|
| 212 |
+
| **draft model for Neutrino-8B** | **inherit the verifier — set nothing** | The draft's sampler cannot change *what* is emitted. Under greedy the verifier's own argmax decides acceptance; under HF-assisted sampling the correction re-weights by `p_i/q_i`. Tuning the draft is a throughput knob, never a quality knob. |
|
| 213 |
+
| **benchmark / completion reproduction** | greedy, no penalty, no top-k — this repo's shipped `generation_config.json` | Everything in the Evaluation section was measured this way, so a bare `model.generate()`, an `lm-eval` run and `fermion generate` all reproduce each other. |
|
| 214 |
+
| **user-facing chat** | use [Neutrino-0.6B-Chat](https://huggingface.co/fermionresearch/Neutrino-0.6B-Chat) instead | This SKU has no chat post-training; every graded conversational number in the family belongs to the -Chat container. |
|
| 215 |
+
|
| 216 |
+
`generation_config.json` here is `do_sample: false`, no `temperature`, no
|
| 217 |
+
`top_k`, no `top_p`, no `repetition_penalty` — deliberately empty of sampler
|
| 218 |
+
opinion, so nothing silently overrides a harness. Top-k is not exposed on any
|
| 219 |
+
runtime we ship; note that llama.cpp and ollama default to top-k 40, which is
|
| 220 |
+
why the `Modelfile` in `gguf/` pins it to 0.
|
| 221 |
+
|
| 222 |
+
## Speed
|
| 223 |
+
|
| 224 |
+
| Surface | Machine | Rate | Memory / protocol |
|
| 225 |
+
|---|---|---:|---|
|
| 226 |
+
| native `fermion-run`, CPU only | Apple M5 | **225-236 tok/s** | 9 threads, 128-token greedy, mmap; per-prompt 232.89 / 224.52 / 233.56, demo median 225.37 (`receipts/m5_demo_rereceipt_p3.json`) |
|
| 227 |
+
| MLX 0.31.2 + Metal | Apple M5, 16 GB | **201.16 tok/s** | median of 5 (197.19-202.03), peak 0.526 GiB, 512-token greedy, 9-token prompt (`mlx/receipts/bench_0p6b_m5.json`) |
|
| 228 |
+
| GGUF pack via `fermion-fv5` | CPU, 16 threads | **55.38 tok/s** tg128 (+/-0.30) | pp512 132.18, build e07b3ec (`gguf/receipts/bench_0p6b.json`) |
|
| 229 |
+
| C-served draft walker inside the 8B's process | H100 | **1,177 tok/s** | 0.85 ms/token standalone, graph == eager on a 24-token identity |
|
| 230 |
+
|
| 231 |
+
## Evaluation battery (this exact container, `91c0b26d...`)
|
| 232 |
+
|
| 233 |
+
Measured on the shipped checkpoint with frozen,
|
| 234 |
+
receipted meters. Stack pins: lm-eval **0.4.8**, datasets 3.6.0,
|
| 235 |
+
transformers 4.53.2, grade_probe `b279c53e`; generative rows evalscope
|
| 236 |
+
**1.4.2**; BFCL wheel **bfcl-eval 2025.10.27.1**. Canonical roll-up:
|
| 237 |
+
`receipts/ship_checkpoint_numbers.json`.
|
| 238 |
+
|
| 239 |
+
A 596M model at this storage depth reads LOW on knowledge-adjacent meters.
|
| 240 |
+
That is expected, disclosed, and the reason this SKU's story is size,
|
| 241 |
+
speed, and drafting rather than capability.
|
| 242 |
+
|
| 243 |
+
### Knowledge and likelihood
|
| 244 |
+
|
| 245 |
+
| Meter | Score | Protocol |
|
| 246 |
+
|---|---:|---|
|
| 247 |
+
| ARC-easy | **53.45** | lm-eval suite leg, 0-shot |
|
| 248 |
+
| Winogrande | 53.43 | same |
|
| 249 |
+
| PIQA | 62.79 | same |
|
| 250 |
+
| BoolQ | 50.52 | same |
|
| 251 |
+
| HellaSwag | 31.64 | same |
|
| 252 |
+
| ARC-challenge | 24.06 | same |
|
| 253 |
+
| OpenBookQA | 19.60 | same |
|
| 254 |
+
| **Suite / commonsense average (7 tasks)** | **42.21** | mean of the seven rows above, 0-shot |
|
| 255 |
+
| MMLU full-57 | 26.66 | likelihood, 14,042 items (near chance; the sub-7B knowledge-conversion floor is a banked house finding) |
|
| 256 |
+
| MMLU subset-14 (frozen gate probe) | 27.50 | kprobe, `mmlu_subset 0.25`, seed 0, 14 named subtasks |
|
| 257 |
+
| SciQ | 80.40 | 0-shot acc, lm-eval 0.4.8 (chance 25.0) |
|
| 258 |
+
| COPA | 63.00 | 0-shot acc, lm-eval 0.4.8, n=100 (chance 50.0) |
|
| 259 |
+
| MNLI, house meter | 35.45 | `e4_mnli_bitnet_parity`, label-token likelihood, validation_matched, full 9,815 (chance 33.3) |
|
| 260 |
+
| MNLI, lm-eval stock | 34.18 | 0-shot acc, lm-eval 0.4.8. Not comparable to the house-meter row above: the two formulations differ, and both readings sit within 2 pp of the 3-class floor |
|
| 261 |
+
| LAMBADA (openai) | 25.79 | 0-shot acc, lm-eval 0.4.8. Long-range last-word prediction is the capability that thins first at this scale, and it is the mechanical explanation for the GSM8K and termination cells below |
|
| 262 |
+
|
| 263 |
+
fp32 reference constants for context, not re-run here: MMLU 46.9, suite
|
| 264 |
+
53.5.
|
| 265 |
+
|
| 266 |
+
### Generation, perplexity, behaviour
|
| 267 |
+
|
| 268 |
+
| Meter | Score | Protocol |
|
| 269 |
+
|---|---:|---|
|
| 270 |
+
| C4 perplexity | **46.25** | 500,000 tokens, C4-val, fp32-bake meter, wall 1,329.9 s |
|
| 271 |
+
| GSM8K flexible-extract | 1.67 | lm-eval `gsm8k`, 0-shot generative, first-N fixed subset limit=300, batch 8 |
|
| 272 |
+
| GSM8K strict-match | 0.00 | same run |
|
| 273 |
+
| GSM8K stated-format (house grader) | 1.67 | second greedy pass, `max_gen_toks=1024`, n=300 |
|
| 274 |
+
| Termination rate | 0.23 | fraction of generations ending on EOS/stop within budget, GSM8K stated pass, cap 1024, n=300 |
|
| 275 |
+
| IFEval prompt-strict | 14.88 | house IFEval leg, evalscope 1.4.2 (base model, no instruction tuning) |
|
| 276 |
+
| BFCL v3 macro-13 | 15.38 | bfcl-eval 2025.10.27.1; degenerate never-calls profile (irrelevance 100.0 + live_irrelevance 100.0, all 10 calling subsets 0.0). **Do NOT use this model for function calling.** |
|
| 277 |
+
| genfmt QA (house) | match 0.62 / well-formed 0.80 | 50-row house QA probe, `genfmt_max_new: 32` |
|
| 278 |
+
|
| 279 |
+
Against its full-precision reference at the identical parameter count
|
| 280 |
+
(596,049,920 on both sides), every cell graded by us on one harness on one
|
| 281 |
+
day at zero shots: **81.6% of the reference's ability across the seven
|
| 282 |
+
common tasks, at 15.8% of the bytes** (238 MB against 1,503 MB), and
|
| 283 |
+
**78.0% averaged across all eleven tasks measured**. Best-retaining axes:
|
| 284 |
+
Winogrande 91.5%, PIQA 90.4%. Rig, pins, revision gates, and the disclosed
|
| 285 |
+
deviations (A10G rather than A100, per-row batch size) are receipted in
|
| 286 |
+
the eval-receipts repo. This supersedes the earlier single-task ARC-easy read, which was one
|
| 287 |
+
cell against a differently-sourced reference number.
|
| 288 |
+
|
| 289 |
+
The SciQ, COPA, LAMBADA, and stock-MNLI cells were measured on this
|
| 290 |
+
container on 2026-07-27 (A100-80GB, lm-eval 0.4.8, fp32-baked TritLinear
|
| 291 |
+
path, same pins and bake as the suite above; run receipt
|
| 292 |
+
in the eval-receipts repo, `grade_probe.py` sha `b279c53e...`,
|
| 293 |
+
state sha gate PASS). Read them as absolute capability
|
| 294 |
+
evidence and as a same-family comparison against our own 8B on the identical
|
| 295 |
+
tasks (95.90 / 84.00 / 56.65 / 54.57). They are **not** competitive claims:
|
| 296 |
+
a same-harness sub-1B cohort grid run on 2026-07-27
|
| 297 |
+
(7 rival checkpoints x 11 tasks,
|
| 298 |
+
77 cells) measures this model at or near the bottom of its cohort on most of
|
| 299 |
+
those tasks. That grid is the calibrated read; use it rather than inferring
|
| 300 |
+
a ranking from the absolute cells above.
|
| 301 |
+
|
| 302 |
+
Not measured at this scale: MMLU-Redux (an 8B-line generative meter),
|
| 303 |
+
wikitext perplexity (the house perplexity meter is C4-val only), and
|
| 304 |
+
IFEval's loose/instruction variants (only prompt-strict was promoted from
|
| 305 |
+
the raw report).
|
| 306 |
+
|
| 307 |
+
### Correctness gates
|
| 308 |
+
|
| 309 |
+
- MLX refgate vs `neutrino_ref.npz`: teacher-forced rel err max 1.74e-06,
|
| 310 |
+
argmax 9/9, greedy 24/24, **0 flips**; cachegate 0/256
|
| 311 |
+
(`mlx/receipts/refgate_0p6b.json`, `cachegate_0p6b.json`).
|
| 312 |
+
- GGUF convert bit-compare: **310/310 CONVERT_PASS**
|
| 313 |
+
(`gguf/receipts/convert_0p6b.json`).
|
| 314 |
+
- C-runtime bit gate: 221/384 raw positions as measured, diagnosed as
|
| 315 |
+
exactly 2 flat-margin argmax flips then greedy cascade (top-2 margins
|
| 316 |
+
0.02-0.20 logits); kernel output is deterministic across runs and
|
| 317 |
+
threads (`receipts/m5_bitgate_analysis.json`).
|
| 318 |
+
- Export: double-export byte-determinism PASS, tv4z byte round-trip PASS,
|
| 319 |
+
fp32 HF expansion self-check PASS (rc=0) (`receipts/receipt_base.json`).
|
| 320 |
+
|
| 321 |
+
### Reproducing the battery
|
| 322 |
+
|
| 323 |
+
The model registers as a native `transformers` model (`import fermion`),
|
| 324 |
+
so the public harnesses run directly.
|
| 325 |
+
|
| 326 |
+
```python
|
| 327 |
+
# first: hf download fermionresearch/Neutrino-0.6B --local-dir Neutrino-0.6B (Quickstart 2)
|
| 328 |
+
# lm-eval 0.4.12 needs accelerate for HFLM: pip install lm-eval accelerate
|
| 329 |
+
import fermion, lm_eval
|
| 330 |
+
from lm_eval.models.huggingface import HFLM
|
| 331 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 332 |
+
|
| 333 |
+
model = AutoModelForCausalLM.from_pretrained("Neutrino-0.6B") # the downloaded directory
|
| 334 |
+
tok = AutoTokenizer.from_pretrained("Neutrino-0.6B")
|
| 335 |
+
model.config.tie_word_embeddings = False # tied inside the packed container; skip HF's re-tie
|
| 336 |
+
lm = HFLM(pretrained=model, tokenizer=tok)
|
| 337 |
+
lm_eval.simple_evaluate(model=lm, num_fewshot=0, tasks=[
|
| 338 |
+
"arc_easy", "arc_challenge", "boolq", "hellaswag",
|
| 339 |
+
"openbookqa", "piqa", "winogrande"]) # the suite-average row
|
| 340 |
+
lm_eval.simple_evaluate(model=lm, tasks=["mmlu"], num_fewshot=5)
|
| 341 |
+
lm_eval.simple_evaluate(model=lm, tasks=["gsm8k"], num_fewshot=0, limit=300)
|
| 342 |
+
```
|
| 343 |
+
|
| 344 |
+
IFEval and BFCL used the same frozen house meters as the 8B card, against
|
| 345 |
+
a vLLM OpenAI-compatible endpoint:
|
| 346 |
+
|
| 347 |
+
```bash
|
| 348 |
+
pip install evalscope==1.4.2 bfcl-eval==2025.10.27.1
|
| 349 |
+
evalscope eval --model Neutrino-0.6B --api-url http://127.0.0.1:8000/v1 \
|
| 350 |
+
--datasets ifeval bfcl_v3
|
| 351 |
+
```
|
| 352 |
+
|
| 353 |
+
Speed is re-measured with the shipped binary and the MLX pack:
|
| 354 |
+
|
| 355 |
+
```bash
|
| 356 |
+
./bin/fermion-run-macos-arm64 neutrino-0.6b_v4.bin "1,791,1283" 128 9 # from the repo dir (Quickstart 3)
|
| 357 |
+
python -m fermion_mlx --model ../neutrino-0.6b_v4.bin --mode bench \
|
| 358 |
+
--tokenizer . --gen-tokens 512 --runs 5 # from mlx/ (Quickstart 5)
|
| 359 |
+
```
|
| 360 |
+
|
| 361 |
+
Per-cell venue, version, and date receipts are in the eval-receipts repo.
|
| 362 |
+
|
| 363 |
+
## As the 8B's draft (the reason this SKU exists)
|
| 364 |
+
|
| 365 |
+
A 0.6B draft proposes k tokens; the 8B verifies all k in one batched
|
| 366 |
+
forward pass and keeps the accepted prefix plus one token of its own. A
|
| 367 |
+
proposal is accepted only when it equals the 8B's own argmax, so the
|
| 368 |
+
output stream is the plain greedy stream: **speed changes, the text does
|
| 369 |
+
not.**
|
| 370 |
+
|
| 371 |
+
### The exactness certificate
|
| 372 |
+
|
| 373 |
+
| Suite | Arms | Tokens compared | Result |
|
| 374 |
+
|---|---|---:|---|
|
| 375 |
+
| Ship-brain cert (8B `016c6f36...5fa0`) | dynamic draft length, draft = Neutrino-0.6B + confidence head | **27,648** | **0 mismatches** (re-run 2026-07-27 against the shipping 8B container) |
|
| 376 |
+
| dynamic-k cert (earlier internal 8B baseline verifier) | dynamic + k in {2..6} x 9 prompts x 512 | 27,648 | PASS, all IDENTICAL |
|
| 377 |
+
| **Story leg: this base container as the draft, no distillation, no confidence head** | k in {3,4,5} x 9 x 512 | **13,824** | **PASS, all IDENTICAL** |
|
| 378 |
+
| forced-reject floors (both drafts) | k=1 dynamic / k in {3,5} fixed | 256 each | output EXACT |
|
| 379 |
+
| **Lane total** | - | **41,472** | **zero mismatches anywhere** |
|
| 380 |
+
|
| 381 |
+
Venue: Modal H100, 2026-07-26. The certification receipt ships in the
|
| 382 |
+
Neutrino-8B repo (`receipts/`).
|
| 383 |
+
|
| 384 |
+
### Speedup by prompt class (ship brain, H100, 3-round median, baseline 395.9 tok/s)
|
| 385 |
+
|
| 386 |
+
| Class | Speedup | tok/s | Acceptance behaviour |
|
| 387 |
+
|---|---:|---:|---|
|
| 388 |
+
| counting | **x1.93** | 762.6 | 100% of drafted tokens accepted (k=6 probe: all 6 survive every verify pass, 74 passes per 512 tokens, ~7 tokens emitted per 8B forward) |
|
| 389 |
+
| facts | x1.55 | 613.0 | 96.5% survive (5.8 of 6 per pass) |
|
| 390 |
+
| prose | x1.34 | 531.9 | free-form narrative |
|
| 391 |
+
| chat explanation | x1.13 | 446.6 | the controller often disables drafting and pays a small resync cost |
|
| 392 |
+
| code | x1.07 | 426.1 | hardest class for the draft |
|
| 393 |
+
|
| 394 |
+
Acceptance decays with draft length everywhere except facts (97-99% flat),
|
| 395 |
+
which is why the controller rides long drafts on facts and explanation
|
| 396 |
+
(histogram mass at k=7), drops to 1-2-token drafts on code and counting,
|
| 397 |
+
and turns itself off on chat explanation.
|
| 398 |
+
|
| 399 |
+
### Out of the box, without distillation
|
| 400 |
+
|
| 401 |
+
This shipped base container was never trained for drafting and carries no
|
| 402 |
+
confidence head, and it still certifies: 13,824 tokens, all identical,
|
| 403 |
+
run as a fixed-k draft. Bench medians on H100: k=4 facts **610.8 (x1.61)**,
|
| 404 |
+
k=5 facts **648.9 (x1.71)**, k=5 explanation/mmlu x1.54, prose k=4-5
|
| 405 |
+
x1.29-1.40. Fixed-k has no disable machinery, so its floor classes drop
|
| 406 |
+
below 1.0 (x0.46 at k=3, x0.34 at k=5); the dynamic controller with the
|
| 407 |
+
distilled draft floors at x0.75. The distillation then buys +10 to +25
|
| 408 |
+
percentage points of acceptance (prose 93.6 against ~70-77, code 85
|
| 409 |
+
against ~50) plus the confidence head and dynamic-k layer.
|
| 410 |
+
|
| 411 |
+
### On Apple silicon
|
| 412 |
+
|
| 413 |
+
The pair runs in one process on a 16 GB M5 under a 6 GiB MLX cap (peak 4.3
|
| 414 |
+
GiB; the draft alone peaks at 0.526 GiB). Exactness holds: the gate returns
|
| 415 |
+
**6/6 prompts token-identical with and without `--spec`**
|
| 416 |
+
(`mlx/receipts/specgate_8b_0p6b.json` in the Neutrino-8B repo). Throughput on Apple
|
| 417 |
+
is class-dependent: **facts 22.00 -> 25.71 tok/s (x1.168)** at 0.7442
|
| 418 |
+
acceptance, while **chat (x0.837) and prose (x0.680) regress** because the
|
| 419 |
+
batched verify is compute-bound at ~2.8x a single step, so break-even
|
| 420 |
+
needs about 2.9 accepted tokens per round and the base draft's acceptance
|
| 421 |
+
on those classes is 0.30-0.44. A code class was never measured on MLX. Use `--spec` on
|
| 422 |
+
Apple for factual workloads; an FV5 sgemm path is the wired follow-up.
|
| 423 |
+
|
| 424 |
+
### Draft-model artifact pins
|
| 425 |
+
|
| 426 |
+
- `neutrino_v4.bin` (this shipping base container, under its
|
| 427 |
+
speculative-decoding working name): sha `91c0b26d...19399`, 327,719,836 B
|
| 428 |
+
- the distilled dspec draft and its confidence head are separate
|
| 429 |
+
artifacts, not published here
|
| 430 |
+
|
| 431 |
+
## Weights: get them and verify them
|
| 432 |
+
|
| 433 |
+
The container ships in this repository. Fetch it and check the digests —
|
| 434 |
+
`MANIFEST.json` carries the same sha256, and `fermion info` verifies it for
|
| 435 |
+
you automatically:
|
| 436 |
+
|
| 437 |
+
```bash
|
| 438 |
+
hf download fermionresearch/Neutrino-0.6B neutrino-0.6b_v4.bin --local-dir .
|
| 439 |
+
shasum -a 256 neutrino-0.6b_v4.bin # must print 91c0b26d...19399
|
| 440 |
+
md5 neutrino-0.6b_v4.bin # macOS — must print 86565bf6183e8031943aac9fd9771385
|
| 441 |
+
md5sum neutrino-0.6b_v4.bin # Linux — same digest
|
| 442 |
+
```
|
| 443 |
+
|
| 444 |
+
Export provenance: exported on a Modal CPU container (cpu=8, 24 GiB) from
|
| 445 |
+
the archived QAT state with the banked exporter (`export_trit_c_model.py`
|
| 446 |
+
sha `78bd8b8b...`, pinned command and flags recorded in the export receipt;
|
| 447 |
+
quantizer parameters are not published). Gates: double-export
|
| 448 |
+
byte-determinism PASS, tv4z byte round-trip PASS, HF expansion self-check
|
| 449 |
+
PASS. Full receipt: `receipts/receipt_base.json`; greedy-cache trajectory
|
| 450 |
+
`receipts/hf_greedy_cache_86565bf6.json`.
|
| 451 |
+
|
| 452 |
+
## Limitations
|
| 453 |
+
|
| 454 |
+
- BASE model: no chat template behavior, no instruction-following
|
| 455 |
+
guarantees, no tool use (see the battery above).
|
| 456 |
+
- Knowledge-adjacent meters are at or near floor by construction (596M in
|
| 457 |
+
a ternary-family format; the sub-7B knowledge-conversion floor is a
|
| 458 |
+
banked house finding). Do not quote the MMLU or GSM8K cells as
|
| 459 |
+
competitive benchmarks.
|
| 460 |
+
- BFCL v3 measures a degenerate never-calls profile. This model has no
|
| 461 |
+
function-calling capability and none is claimed.
|
| 462 |
+
- Greedy decode parity vs the fp reference implementation is 382/384
|
| 463 |
+
positions on the banked gate prompts: the int8-activation runtime and
|
| 464 |
+
the fp expansion disagree at 2 near-tie argmax positions (top-2 margins
|
| 465 |
+
0.02-0.20 logits; full analysis in `receipts/m5_bitgate_analysis.json`).
|
| 466 |
+
Runtime output is deterministic.
|
| 467 |
+
- On Apple silicon, speculative decoding against the 8B is a win on
|
| 468 |
+
factual prompts and a regression on chat and prose (see the section
|
| 469 |
+
above). The H100 path is a win on all five measured classes.
|
| 470 |
+
- The GGUF pack loads only through our llama.cpp fork until upstreaming
|
| 471 |
+
lands; stock llama.cpp, ollama, and LM Studio binaries do not know its
|
| 472 |
+
tensor types.
|
| 473 |
+
- Knowledge cutoff and multilingual behavior inherit from the base model
|
| 474 |
+
plus the disclosed feed diet (see eval-receipts).
|
| 475 |
+
|
| 476 |
+
## Model lineage and methodology
|
| 477 |
+
|
| 478 |
+
Lineage in one line: base model (see frontmatter) -> ternary QAT on the
|
| 479 |
+
streamed small-model diet corpus -> exported with the banked
|
| 480 |
+
exporter, gates in the export receipt above. The QAT recipe, the diet
|
| 481 |
+
disclosure, and every graded cell
|
| 482 |
+
(venue, version, date) live in the eval-receipts repo; the format and
|
| 483 |
+
engine write-ups are at fermionresearch.com/research/.
|
| 484 |
+
|
| 485 |
+
## License and attribution
|
| 486 |
+
|
| 487 |
+
Weights: Apache-2.0. Derivative of Qwen/Qwen3-0.6B (Apache-2.0, Alibaba
|
| 488 |
+
Cloud), see `LICENSE`. Training data: the small-model diet corpus
|
| 489 |
+
(streamed realization) disclosed in the eval-receipts repo.
|
| 490 |
+
|
| 491 |
+
`bin/` binaries: shipped in this repo under `bin/` with sha256 sidecars —
|
| 492 |
+
the same generic TRTC v4 runtimes bundled with Neutrino-8B (same EULA:
|
| 493 |
+
free to use with these weights, no redistribution, no reverse
|
| 494 |
+
engineering).
|
| 495 |
+
|
| 496 |
+
Contact: [email protected]
|
bin/MANIFEST.json
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"note": "bin/ staging manifest: blessed fermion-run release binaries. Entries mirror the .sha256 sidecars. The binaries are container-agnostic (any TRTC v4 arch-3 file).",
|
| 3 |
+
"binaries": {
|
| 4 |
+
"fermion-run-macos-arm64": {
|
| 5 |
+
"status": "RE-BLESSED 2026-07-28 (0.1.10 fp16-KV + runtime YaRN): macOS arm64 CPU runtime (NEON dotprod + persistent thread pool; libomp statically linked). Supersedes the 0.1.8 build: adds --kv-dtype fp16 (KV bytes halved, fp32 compute over fp16 storage, opt-in because near-tie tokens can flip) and --yarn-factor (YaRN rope tables, long-context extension; factor 1.0 gated byte-identical to flag-off). With both flags absent, output gated BYTE-IDENTICAL to the superseded binary (ids + final logits).",
|
| 6 |
+
"bytes": 671656,
|
| 7 |
+
"sha256": "2b9902fc36bf59c50a67ee0c79ae38a259d066e6ebff764abbf05ff72017351c",
|
| 8 |
+
"sidecar": "fermion-run-macos-arm64.sha256",
|
| 9 |
+
"build_receipt": "internal reference"
|
| 10 |
+
},
|
| 11 |
+
"fermion-run-linux-x64": {
|
| 12 |
+
"status": "RE-BLESSED 2026-07-28 (0.1.10 fp16-KV + runtime YaRN): Linux x86-64 CPU runtime, AVX2 baseline (-march=x86-64-v3), fully static ELF. Supersedes the 0.1.8 build: adds --kv-dtype fp16 (KV bytes halved, fp32 compute over fp16 storage, opt-in because near-tie tokens can flip) and --yarn-factor (YaRN rope tables, long-context extension; factor 1.0 gated byte-identical to flag-off). With both flags absent, output gated BYTE-IDENTICAL to the superseded binary (ids + final logits) on a 24-core venue.",
|
| 13 |
+
"bytes": 1317208,
|
| 14 |
+
"sha256": "d3e1c3d7a7fe43e9dd4d6e16e75c0cd234bfc9866964284101d200f3da05310b",
|
| 15 |
+
"sidecar": "fermion-run-linux-x64.sha256",
|
| 16 |
+
"build_receipt": "internal reference"
|
| 17 |
+
},
|
| 18 |
+
"fermion-run-linux-cuda": {
|
| 19 |
+
"status": "COMING-SOON (NOT STAGED, no fake artifact): no standalone arch-3 (Qwen3) CUDA runtime binary exists yet. Working fallback documented in bin/README.md: pip fermion torch path with --device cuda.",
|
| 20 |
+
"bytes": null,
|
| 21 |
+
"sha256": null,
|
| 22 |
+
"sidecar": null
|
| 23 |
+
}
|
| 24 |
+
}
|
| 25 |
+
}
|
bin/README.md
ADDED
|
@@ -0,0 +1,216 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# bin/ — prebuilt `fermion-run` native runtimes
|
| 2 |
+
|
| 3 |
+
> MIRRORED COPY (2026-07-27): byte-identical
|
| 4 |
+
> binaries and sidecars to `fermionresearch/Neutrino-8B/bin/`. The runtime is
|
| 5 |
+
> container-agnostic (any TRTC v4 arch-3 file), so the same two binaries serve
|
| 6 |
+
> this repo's container; the examples below name **this** repo's container.
|
| 7 |
+
|
| 8 |
+
STATUS: **BLESSED BINARIES STAGED 2026-07-25**.
|
| 9 |
+
Binaries are prebuilt and closed-source (kernel source does not ship);
|
| 10 |
+
every binary has a `.sha256` sidecar. EULA line lives on the model card.
|
| 11 |
+
Functional gates ran on the interim 8B v4 cut; the runtime is
|
| 12 |
+
container-agnostic (any TRTC v4 arch-3).
|
| 13 |
+
|
| 14 |
+
## What is here
|
| 15 |
+
|
| 16 |
+
| File | Platform / backend | State |
|
| 17 |
+
|---|---|---|
|
| 18 |
+
| `fermion-run-macos-arm64` | macOS arm64 (M1+), CPU — NEON dotprod + persistent thread pool, OpenMP statically linked, zero dylib deps beyond libSystem | BLESSED (functional-gated on the 8B v4 container) |
|
| 19 |
+
| `fermion-run-linux-x64` | Linux x86-64, CPU — AVX2 baseline (`-march=x86-64-v3`), fully static ELF, runs on any 2015+ x86-64 distro | BLESSED (functional-gated on the 8B v4 container) |
|
| 20 |
+
| `fermion-run-linux-cuda` | Linux x86-64, NVIDIA CUDA | **COMING SOON** — not staged; see fallback below |
|
| 21 |
+
|
| 22 |
+
Honesty notes, so nobody is surprised:
|
| 23 |
+
|
| 24 |
+
- The macOS binary is the **CPU** runtime. The Metal-optimized runtime for
|
| 25 |
+
this model's container format is a week-one follow-up (the existing Metal
|
| 26 |
+
dev builds target a different container lineage and cannot load this
|
| 27 |
+
model); when it lands it replaces this entry with its own receipts.
|
| 28 |
+
- The linux-x64 binary is built for the AVX2 **compatibility baseline**, not
|
| 29 |
+
the AVX-512-VNNI fast path the banked x86 speed receipts were measured on.
|
| 30 |
+
It is correct (bit-exact GEMV unit gate at build time) and self-contained;
|
| 31 |
+
a VNNI-optimized variant is a week-one candidate.
|
| 32 |
+
- CUDA fallback that works today: `pip install fermion-research` then
|
| 33 |
+
`fermion chat --model fermionresearch/Neutrino-8B --device cuda`
|
| 34 |
+
(reference torch path — correctness-first, not the speed path).
|
| 35 |
+
|
| 36 |
+
## CLI (what the binaries actually take)
|
| 37 |
+
|
| 38 |
+
The binaries take **pre-tokenized ids**, not prompt strings — tokenization
|
| 39 |
+
lives in the `fermion` CLI (`pip install fermion-research`, the one door), which
|
| 40 |
+
downloads the model + the platform-matching binary from this repo and shells
|
| 41 |
+
out for the fast path. Manual use:
|
| 42 |
+
|
| 43 |
+
```
|
| 44 |
+
./fermion-run-<platform> <container.bin> "<id0,id1,...>" <n_new_tokens> <n_threads> \
|
| 45 |
+
[logits_out.bin] [--temp T] [--top-p P] [--rep-pen R] [--seed S] \
|
| 46 |
+
[--stop-id I0[,I1...]] [--min-p F] [--presence-pen F] [--freq-pen F] \
|
| 47 |
+
[--dry MULT [--dry-base B --dry-allow N]] [--pen-window N] \
|
| 48 |
+
[--eos-exempt-minp] [--eos-boost B:N] [--ppl]
|
| 49 |
+
```
|
| 50 |
+
|
| 51 |
+
The container, the id list, the token count and the **thread count are
|
| 52 |
+
positionals, in that order**. There is no `--chat` and no `--threads`.
|
| 53 |
+
|
| 54 |
+
> [!IMPORTANT]
|
| 55 |
+
> **Sampler flags require `--temp > 0` to engage.** At `--temp 0`
|
| 56 |
+
> (pure greedy) the binary takes a fast argmax path and silently
|
| 57 |
+
> ignores `--rep-pen`, `--top-p`, and all other penalty flags. If
|
| 58 |
+
> you want penalties with near-deterministic output, use
|
| 59 |
+
> `--temp 0.01 --top-p 1.0 --seed 42`. (Found 2026-07-26; a
|
| 60 |
+
> runtime patch making this explicit is a week-one item.)
|
| 61 |
+
>
|
| 62 |
+
> **`--pen-window` is inert on the legacy sampled path.** With `--temp > 0`
|
| 63 |
+
> and none of `--min-p` / `--presence-pen` / `--freq-pen` / `--dry` present,
|
| 64 |
+
> the repetition-penalty window is clamped to the last **64** tokens
|
| 65 |
+
> regardless of what `--pen-window` says (`trit_run.c`: `rwin = g_ext ?
|
| 66 |
+
> max(g_penwin, 64) : 64`). Adding any one of those four flags switches on
|
| 67 |
+
> the modern penalty stack and makes `--pen-window` literal. Every graded
|
| 68 |
+
> C receipt was taken on the legacy path, so the banked C numbers are
|
| 69 |
+
> **64-window** numbers even where the command line reads
|
| 70 |
+
> `--pen-window 256`; the torch and MLX paths do apply 256. Documented
|
| 71 |
+
> 2026-07-27; making the flag
|
| 72 |
+
> literal on the legacy path is a week-one runtime patch.
|
| 73 |
+
|
| 74 |
+
Defaults: greedy decode; without `--stop-id` exactly `n_new_tokens` are
|
| 75 |
+
generated (EOS ignored); with `--stop-id` generation stops at the stop token
|
| 76 |
+
(coherence mode). Output: line 1 = generated ids (comma-separated), line 2 =
|
| 77 |
+
`tok/s: ...`. `n_new_tokens 0` = prefill-only logits dump.
|
| 78 |
+
|
| 79 |
+
Per-platform one-liners (chat-format ids for a user prompt, then generate up
|
| 80 |
+
to 64 tokens, stop at `<|im_end|>` = 151645):
|
| 81 |
+
|
| 82 |
+
```bash
|
| 83 |
+
# ids for 'In one sentence, why is the sky blue?' via this repo's tokenizer:
|
| 84 |
+
IDS=$(python3 -c "
|
| 85 |
+
from tokenizers import Tokenizer
|
| 86 |
+
t = Tokenizer.from_file('tokenizer.json')
|
| 87 |
+
p = '<|im_start|>user\nIn one sentence, why is the sky blue?<|im_end|>\n<|im_start|>assistant\n<think>\n\n</think>\n\n'
|
| 88 |
+
print(','.join(map(str, t.encode(p).ids)))")
|
| 89 |
+
|
| 90 |
+
# macOS arm64:
|
| 91 |
+
./bin/fermion-run-macos-arm64 neutrino-0.6b_v4.bin "$IDS" 64 8 --stop-id 151645
|
| 92 |
+
# Linux x86-64:
|
| 93 |
+
./bin/fermion-run-linux-x64 neutrino-0.6b_v4.bin "$IDS" 64 8 --stop-id 151645
|
| 94 |
+
```
|
| 95 |
+
|
| 96 |
+
macOS quarantine note: after download run
|
| 97 |
+
`xattr -d com.apple.quarantine bin/fermion-run-macos-arm64` once.
|
| 98 |
+
|
| 99 |
+
## sha256 sidecar convention
|
| 100 |
+
|
| 101 |
+
Every binary `X` ships with `X.sha256` (standard `shasum` line):
|
| 102 |
+
|
| 103 |
+
```bash
|
| 104 |
+
# producer (at staging time, from this directory):
|
| 105 |
+
shasum -a 256 fermion-run-macos-arm64 > fermion-run-macos-arm64.sha256
|
| 106 |
+
# consumer:
|
| 107 |
+
shasum -a 256 -c fermion-run-macos-arm64.sha256
|
| 108 |
+
```
|
| 109 |
+
|
| 110 |
+
`MANIFEST.json` here mirrors the sidecars (file -> bytes + sha256 + build
|
| 111 |
+
provenance) and is regenerated
|
| 112 |
+
whenever binaries are added or replaced.
|
| 113 |
+
|
| 114 |
+
## thread count: the runtime is safe above 16 threads again
|
| 115 |
+
|
| 116 |
+
> [!IMPORTANT]
|
| 117 |
+
> **Rebuilt 2026-07-27 (thread-safety fix).** The 2026-07-25 binaries
|
| 118 |
+
> corrupted memory above **16 threads**: the per-thread activation-quant
|
| 119 |
+
> scratch buffer inside the runtime was allocated for 16 workers while the
|
| 120 |
+
> OpenMP path indexed it by thread id with no clamp. On Linux x86-64 that was
|
| 121 |
+
> a SIGSEGV on the 8B and — worse — **exit code 0 with silently different,
|
| 122 |
+
> wrong tokens** on the 0.6B, on any box with 18 or more logical cores (the
|
| 123 |
+
> `fermion` CLI asked for `cpu_count - 1`). macOS arm64 never reached it
|
| 124 |
+
> because Apple silicon tops out at 16 logical cores.
|
| 125 |
+
>
|
| 126 |
+
> These binaries size that buffer from the live thread count, clamp the
|
| 127 |
+
> requested count at a hard ceiling, and are gated by an output-stability
|
| 128 |
+
> thread sweep that asserts
|
| 129 |
+
> **byte-identical generated ids** at 1/4/8/15/16/17/20/23/max threads rather
|
| 130 |
+
> than merely a zero exit code. The `fermion` package independently caps the
|
| 131 |
+
> count it will request at 16 (`fermion.native.THREAD_CAP`).
|
| 132 |
+
|
| 133 |
+
## prefill: prompts are now processed as a batch
|
| 134 |
+
|
| 135 |
+
> [!NOTE]
|
| 136 |
+
> **Rebuilt 2026-07-27 (batched prefill, 0.1.6).** Earlier binaries ran the
|
| 137 |
+
> PROMPT token-by-token — one full GEMV pass over the weight stack per prompt
|
| 138 |
+
> token — so a long chat history prefilled at a fraction of decode speed and
|
| 139 |
+
> the stall grew with every turn. These binaries push prompt positions through
|
| 140 |
+
> each layer in chunks (weights are decoded once per chunk and applied to all
|
| 141 |
+
> positions from cache), measured ~4x faster prefill on Apple M-series and
|
| 142 |
+
> ~10x on Linux AVX2 at 300 prompt tokens, with **byte-identical output**:
|
| 143 |
+
> generated ids and final logits are gated bit-for-bit against the previous
|
| 144 |
+
> binaries across a prompt/(n_new) matrix, a chunk-size sweep and the full
|
| 145 |
+
> thread ladder.
|
| 146 |
+
> `TRIT_PREFILL_BATCH=0` in the environment restores the sequential prefill.
|
| 147 |
+
|
| 148 |
+
## session mode: a resident runner with KV-prefix reuse
|
| 149 |
+
|
| 150 |
+
> [!NOTE]
|
| 151 |
+
> **Rebuilt 2026-07-28 (session mode, 0.1.7).** The runner now has a
|
| 152 |
+
> persistent mode: `<binary> model.bin "0" 0 <threads> --session [--ctx N]`
|
| 153 |
+
> keeps ONE process resident holding the mmap'd weights and a KV cache, takes
|
| 154 |
+
> requests on stdin (full prompt ids per request) and prefills only the part
|
| 155 |
+
> of the prompt it has not seen before (longest-common-prefix against the
|
| 156 |
+
> cached token history). `fermion serve` and `fermion chat` (pip 0.1.7) drive
|
| 157 |
+
> this automatically; an agent step over a fixed ~2k-token preamble drops from
|
| 158 |
+
> ~94 s to ~1.3 s on the 8B (24-core Linux) because the preamble prefill and
|
| 159 |
+
> the per-request process start are both paid once. Output is gated
|
| 160 |
+
> **byte-identical** to a one-shot invocation of the same full prompt — greedy
|
| 161 |
+
> and fixed-seed sampled, including reuse after a divergent history and across
|
| 162 |
+
> the full thread ladder to 40 threads. One-shot invocations are gated
|
| 163 |
+
> byte-identical (ids + final logits) to the previous 0.1.6 binaries. A
|
| 164 |
+
> mid-decode `C\n` on stdin cancels the current request; `R\n` resets the
|
| 165 |
+
> cache; the KV capacity is `--ctx` positions (RAM scales linearly with it).
|
| 166 |
+
|
| 167 |
+
## 0.1.8: multi-slot positional KV reuse + tool-name aliasing
|
| 168 |
+
|
| 169 |
+
> [!NOTE]
|
| 170 |
+
> **Rebuilt 2026-07-28 (0.1.8).** `--session` now
|
| 171 |
+
> tracks KV validity per (position, content) across up to 4 logical
|
| 172 |
+
> sequences instead of one longest-common-prefix history. An interleaved
|
| 173 |
+
> divergent request (agent frameworks fire short utility prompts — titles,
|
| 174 |
+
> status — into the same server between steps) no longer destroys the warm
|
| 175 |
+
> conversation cache: only the positions it physically overwrites are
|
| 176 |
+
> re-prefilled when the conversation returns (measured on the Hermes shape:
|
| 177 |
+
> re-prefill 4,122 -> 177 tokens, turn wall 50 s -> 2.5 s on an M5).
|
| 178 |
+
> Reuse stays EXACT: a cached position is reused only when it was computed
|
| 179 |
+
> under a prefix identical to the new prompt — a mutated head therefore
|
| 180 |
+
> still (correctly) invalidates everything after it, which no exact cache
|
| 181 |
+
> can avoid. Output is gated byte-identical to one-shot decodes across
|
| 182 |
+
> fresh/extended/mutated-head/mutated-middle/eviction/interleave shapes,
|
| 183 |
+
> chunk sweeps, the sequential path, and the N4 thread ladder, on both
|
| 184 |
+
> models and both platforms; one-shot output is gated byte-identical (ids +
|
| 185 |
+
> final logits) to the 0.1.7 binaries. The `ACK` line gains a `segs=<n>`
|
| 186 |
+
> field (number of prefill runs); the protocol is otherwise unchanged and
|
| 187 |
+
> 0.1.7 clients work as-is.
|
| 188 |
+
|
| 189 |
+
## 0.1.10: fp16 KV cache (--kv-dtype) + runtime YaRN (--yarn-factor)
|
| 190 |
+
|
| 191 |
+
> [!NOTE]
|
| 192 |
+
> **Rebuilt 2026-07-28 (0.1.10).** Two additive
|
| 193 |
+
> runtime flags; with both flags absent the binary is gated BYTE-IDENTICAL
|
| 194 |
+
> (ids + final logits) to the 0.1.8/0.1.9 pair on both platforms.
|
| 195 |
+
>
|
| 196 |
+
> `--kv-dtype fp16` stores the session/decode KV cache in IEEE fp16 and
|
| 197 |
+
> computes attention in fp32 over it: HALF the KV memory (288 -> 144
|
| 198 |
+
> KiB/token on the 8B; an 8192-position session drops ~2.3 -> ~1.15 GiB).
|
| 199 |
+
> Cached k/v values are rounded once (RNE) at the position they are
|
| 200 |
+
> produced, identically on every code path and platform, so the mode is
|
| 201 |
+
> deterministic and thread-count-stable -- but near-tie tokens CAN resolve
|
| 202 |
+
> differently than fp32 (a small measured flip rate). fp16 is
|
| 203 |
+
> therefore OPT-IN; the default stays fp32 and byte-exact.
|
| 204 |
+
>
|
| 205 |
+
> `--yarn-factor F` builds YaRN rope tables at load (the transformers
|
| 206 |
+
> `_compute_yarn_parameters` twin, double precision; derived defaults
|
| 207 |
+
> orig_max = model max_pos, attn_factor = 0.1*ln(F)+1, beta 32/1, all
|
| 208 |
+
> overridable), extending usable context toward F x 40,960 positions.
|
| 209 |
+
> `--yarn-factor 1.0` is the identity (byte-identical to flag-off).
|
| 210 |
+
> Teacher-forced logits beyond position 40,960 are gated against
|
| 211 |
+
> transformers' own YaRN on the same checkpoint; long-context retrieval
|
| 212 |
+
> quality of the ternary brains is EXPERIMENTAL -- validate retrieval
|
| 213 |
+
> quality for your workload before advertising a
|
| 214 |
+
> context length. KV memory grows linearly with context (a 61,440-token
|
| 215 |
+
> fp16 session on the 8B is ~8.4 GiB): pair long contexts with
|
| 216 |
+
> `--kv-dtype fp16` and mind the printed KV arithmetic.
|
bin/fermion-run-linux-x64
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:d3e1c3d7a7fe43e9dd4d6e16e75c0cd234bfc9866964284101d200f3da05310b
|
| 3 |
+
size 1317208
|
bin/fermion-run-linux-x64.sha256
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
d3e1c3d7a7fe43e9dd4d6e16e75c0cd234bfc9866964284101d200f3da05310b fermion-run-linux-x64
|
bin/fermion-run-macos-arm64
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:2b9902fc36bf59c50a67ee0c79ae38a259d066e6ebff764abbf05ff72017351c
|
| 3 |
+
size 671656
|
bin/fermion-run-macos-arm64.sha256
ADDED
|
@@ -0,0 +1 @@
|
|
|
|
|
|
|
| 1 |
+
2b9902fc36bf59c50a67ee0c79ae38a259d066e6ebff764abbf05ff72017351c fermion-run-macos-arm64
|
config.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"transformers_version": "5.14.1",
|
| 3 |
+
"architectures": [
|
| 4 |
+
"TrtcV4ForCausalLM"
|
| 5 |
+
],
|
| 6 |
+
"output_hidden_states": false,
|
| 7 |
+
"return_dict": true,
|
| 8 |
+
"dtype": "auto",
|
| 9 |
+
"chunk_size_feed_forward": 0,
|
| 10 |
+
"is_encoder_decoder": false,
|
| 11 |
+
"id2label": {
|
| 12 |
+
"0": "LABEL_0",
|
| 13 |
+
"1": "LABEL_1"
|
| 14 |
+
},
|
| 15 |
+
"label2id": {
|
| 16 |
+
"LABEL_0": 0,
|
| 17 |
+
"LABEL_1": 1
|
| 18 |
+
},
|
| 19 |
+
"problem_type": null,
|
| 20 |
+
"_name_or_path": "",
|
| 21 |
+
"container": "neutrino-0.6b_v4.bin",
|
| 22 |
+
"row_chunk": 512,
|
| 23 |
+
"int8_row_chunk": 4096,
|
| 24 |
+
"model_type": "trtc_v4",
|
| 25 |
+
"output_attentions": false
|
| 26 |
+
}
|
generation_config.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"bos_token_id": 151643,
|
| 3 |
+
"do_sample": false,
|
| 4 |
+
"eos_token_id": [
|
| 5 |
+
151645,
|
| 6 |
+
151643
|
| 7 |
+
],
|
| 8 |
+
"pad_token_id": 151643,
|
| 9 |
+
"transformers_version": "4.53.2"
|
| 10 |
+
}
|
gguf/Modelfile
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Neutrino-0.6B base — ollama Modelfile (requires ollama built against the
|
| 2 |
+
# fermion-fv5 llama.cpp fork; stock ollama cannot load the FV5 type)
|
| 3 |
+
FROM ./neutrino-0.6b-base-fv5.gguf
|
| 4 |
+
PARAMETER temperature 0.7
|
| 5 |
+
PARAMETER top_p 0.95
|
| 6 |
+
# base model: raw completion, no chat template
|
gguf/README.md
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Neutrino-0.6B — GGUF pack (llama.cpp fork `fermion-fv5`)
|
| 2 |
+
|
| 3 |
+
> [!IMPORTANT]
|
| 4 |
+
> **Neutrino-0.6B exists as the speculative-decoding DRAFT for
|
| 5 |
+
> Neutrino-8B** — that pairing is its supported purpose. It is a BASE
|
| 6 |
+
> model: not an assistant, no chat/instruction/tool claims. For a small
|
| 7 |
+
> conversational model use Neutrino-0.6B-Chat.
|
| 8 |
+
|
| 9 |
+
Run the [Neutrino-0.6B](../README.md) five-value container through the
|
| 10 |
+
**llama.cpp toolchain** (llama-cli, llama-server, llama-bench, and anything
|
| 11 |
+
built on top of them, including ollama built from source against the fork).
|
| 12 |
+
|
| 13 |
+
**Read this first — which door do you want?**
|
| 14 |
+
|
| 15 |
+
- **This GGUF pack is the compatibility door.** It exists so the
|
| 16 |
+
llama.cpp/ollama/LM Studio world can load and run our format with the
|
| 17 |
+
ecosystem's own tooling. It is CPU-backend, correctness-certified
|
| 18 |
+
(token-identical greedy vs the container's fp32 expansion), and *not*
|
| 19 |
+
tuned for speed.
|
| 20 |
+
- **The native binaries are the fast door.** `fermion-run` (shipped with
|
| 21 |
+
the Neutrino-8B repo) decodes the same container at ~225–236 tok/s on an
|
| 22 |
+
Apple M5 CPU. If you want speed, use the native runtime; if you want
|
| 23 |
+
llama.cpp-ecosystem integration, use this pack.
|
| 24 |
+
|
| 25 |
+
## What's in the file
|
| 26 |
+
|
| 27 |
+
`neutrino-0.6b-base-fv5.gguf` stores the model in two Fermion GGML types
|
| 28 |
+
added by the fork (they do **not** exist in stock llama.cpp):
|
| 29 |
+
|
| 30 |
+
| type | used for | layout per 256-element block | bits/weight |
|
| 31 |
+
|---|---|---|---|
|
| 32 |
+
| `FV5` (43) | all 196 transformer linears | f32 `s_lo`, f32 `s_hi`, 3×32B bit-planes (`bp`,`bn`,`br`) | 3.25 |
|
| 33 |
+
| `FV5B` (44) | int8 embedding / lm_head rows | f32 row scale, 256×int8 | 8.125 |
|
| 34 |
+
|
| 35 |
+
Weights are `w = (bp − bn) · (br ? s_hi : s_lo)` — the exact five-value
|
| 36 |
+
{0, ±s_lo, ±s_hi} function of the TRTC v4 container. The converter copies
|
| 37 |
+
the container's bit-planes and f32 per-row dual scales verbatim (no
|
| 38 |
+
re-quantization anywhere), and bit-compares every tensor's dequantization
|
| 39 |
+
against the container's f32 expansion before writing. Norms stay f32.
|
| 40 |
+
Activations are never quantized at runtime (`vec_dot_type = F32`), so the
|
| 41 |
+
only numerical difference vs the fp32 expansion is float summation order.
|
| 42 |
+
|
| 43 |
+
## Files
|
| 44 |
+
|
| 45 |
+
See `SHA256SUMS` in this folder for the authoritative digests of:
|
| 46 |
+
|
| 47 |
+
- `neutrino-0.6b-base-fv5.gguf` — the model (FV5/FV5B, ~490 MiB)
|
| 48 |
+
- `fv5.patch` / `fermion-fv5.bundle` — the fork as a single patch/bundle
|
| 49 |
+
on top of upstream `ggml-org/llama.cpp` @ `d67c0b41`, branch
|
| 50 |
+
`fermion-fv5`, commit `e07b3ec1`, docs in `FORK_NOTES.md`
|
| 51 |
+
|
| 52 |
+
## Quickstart (build the fork, run the model)
|
| 53 |
+
|
| 54 |
+
```bash
|
| 55 |
+
git clone https://github.com/fermionresearch/llama.cpp && cd llama.cpp
|
| 56 |
+
git checkout fermion-fv5
|
| 57 |
+
# or, from upstream + our patch:
|
| 58 |
+
# git clone https://github.com/ggml-org/llama.cpp && cd llama.cpp
|
| 59 |
+
# git checkout d67c0b4107112e4790774c3a8169e2e3eb24643b && git am fv5.patch
|
| 60 |
+
cmake -B build -DCMAKE_BUILD_TYPE=Release -DGGML_NATIVE=ON -DLLAMA_CURL=OFF -DGGML_METAL=OFF
|
| 61 |
+
cmake --build build -j --target llama-completion
|
| 62 |
+
|
| 63 |
+
./build/bin/llama-completion -m neutrino-0.6b-base-fv5.gguf \
|
| 64 |
+
-p "The key idea behind ternary neural networks is" -n 128 --temp 0 -no-cnv
|
| 65 |
+
```
|
| 66 |
+
|
| 67 |
+
Build notes: the command above targets the `fermion-fv5` branch, which is
|
| 68 |
+
CPU/CUDA only — hence `-DGGML_METAL=OFF`. In this tree `llama-cli` is the
|
| 69 |
+
chat-first surface; `llama-completion` is the completion tool these
|
| 70 |
+
instructions are verified with. The 8B pack's README documents the full
|
| 71 |
+
backend matrix.
|
| 72 |
+
|
| 73 |
+
### Apple GPU (Metal)
|
| 74 |
+
|
| 75 |
+
FV5 Metal kernels are on the `fermion-fv5-metal` branch. Build **without**
|
| 76 |
+
`-DGGML_METAL=OFF` and run with `-ngl 99`:
|
| 77 |
+
|
| 78 |
+
```bash
|
| 79 |
+
git checkout fermion-fv5-metal
|
| 80 |
+
cmake -B build -DCMAKE_BUILD_TYPE=Release -DLLAMA_CURL=OFF
|
| 81 |
+
cmake --build build -j --target llama-completion llama-bench
|
| 82 |
+
```
|
| 83 |
+
|
| 84 |
+
Measured on an Apple M5 (16 GB, machine idle, `llama-bench -r 3`, build
|
| 85 |
+
`181e4d8`), model resident 321.57 MiB:
|
| 86 |
+
|
| 87 |
+
| test | `-ngl 99` (Metal) | `-ngl 0` (same binary, CPU) | speedup |
|
| 88 |
+
|---|---|---|---|
|
| 89 |
+
| pp512 | **6776.42 ± 206.99 tok/s** | 845.46 ± 68.75 tok/s | ×8.0 |
|
| 90 |
+
| tg128 | **187.19 ± 1.89 tok/s** | 3.73 ± 0.01 tok/s | ×50.2 |
|
| 91 |
+
|
| 92 |
+
This is a **base** model (no chat template, no instruction tuning) — use
|
| 93 |
+
completion-style prompts and expect base-model behavior.
|
| 94 |
+
|
| 95 |
+
### ollama (built against the fork)
|
| 96 |
+
|
| 97 |
+
Stock ollama binaries do not know the FV5 type. Build ollama from source
|
| 98 |
+
with its vendored llama.cpp replaced by the fork checkout, then:
|
| 99 |
+
|
| 100 |
+
```
|
| 101 |
+
# Modelfile -- deterministic, matching this repo's generation_config.json
|
| 102 |
+
# and the `--temp 0` llama-cli line above. llama.cpp defaults would
|
| 103 |
+
# otherwise give you temperature 0.8 and top-k 40.
|
| 104 |
+
FROM ./neutrino-0.6b-base-fv5.gguf
|
| 105 |
+
PARAMETER temperature 0
|
| 106 |
+
PARAMETER top_k 0
|
| 107 |
+
PARAMETER repeat_penalty 1.0
|
| 108 |
+
# base model: no template, raw completion
|
| 109 |
+
```
|
| 110 |
+
|
| 111 |
+
```bash
|
| 112 |
+
ollama create neutrino-0.6b -f Modelfile
|
| 113 |
+
ollama run neutrino-0.6b "The key idea behind ternary neural networks is"
|
| 114 |
+
```
|
| 115 |
+
|
| 116 |
+
LM Studio: point it at a llama.cpp server built from the fork
|
| 117 |
+
(`llama-server -m neutrino-0.6b-base-fv5.gguf`) and connect via the
|
| 118 |
+
OpenAI-compatible endpoint; the bundled engine cannot load FV5 directly.
|
| 119 |
+
|
| 120 |
+
## As a speculative-decoding draft for Neutrino-8B (EXPERIMENTAL)
|
| 121 |
+
|
| 122 |
+
This 0.6B fv5 pack can draft for the Neutrino-8B fv5 pack via llama.cpp's
|
| 123 |
+
own built-in speculative decoding (`llama-speculative -m 8b.gguf -md
|
| 124 |
+
neutrino-0.6b-base-fv5.gguf --spec-draft-n-max 4 --temp 0`). Honesty note:
|
| 125 |
+
that acceptance rule is llama.cpp's own sampling-based speculation, not
|
| 126 |
+
our bit-gated certification — and on CPU it is currently a measured
|
| 127 |
+
honest null (slower than plain decode; the FV5 type has no batched-matmul
|
| 128 |
+
path yet, so verify doesn't amortize). Numbers, receipts, and the full
|
| 129 |
+
note live in the Neutrino-8B GGUF README. Certified-exact speculation
|
| 130 |
+
lives in the native runtime and the MLX pack's `--spec` mode.
|
| 131 |
+
|
| 132 |
+
## Correctness & speed
|
| 133 |
+
|
| 134 |
+
- Gate: greedy streams (8 prompts × 128 tokens) from the fork's CPU
|
| 135 |
+
backend, checked token-by-token against the container's fp32 expansion
|
| 136 |
+
reference (free-running + teacher-forced). Verdict and any near-tie
|
| 137 |
+
argmax notes: `receipts/` in this folder.
|
| 138 |
+
- Speed (record only, not a goal): see `receipts/bench_*.json` for CPU
|
| 139 |
+
tok/s on the recorded container class. The native runtime is the fast
|
| 140 |
+
door; upstreaming + SIMD-fused `vec_dot` (NEON) are wired follow-ups.
|
| 141 |
+
|
| 142 |
+
## License
|
| 143 |
+
|
| 144 |
+
**This folder is mixed-licence.**
|
| 145 |
+
|
| 146 |
+
- The **model pack** (`*.gguf`) is Apache-2.0, same as the parent model card
|
| 147 |
+
(`../LICENSE`; `../NOTICE` carries the Qwen3 attribution).
|
| 148 |
+
- Any **`fv5.patch` / fork source** is a derivative of ggml-org/llama.cpp and
|
| 149 |
+
is therefore **MIT**, upstream's licence — *not* Apache-2.0, and not
|
| 150 |
+
covered by the parent card's grant. See the fork repository's own
|
| 151 |
+
`LICENSE`.
|
gguf/SHA256SUMS
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Neutrino-0.6B GGUF pack — authoritative digests
|
| 2 |
+
# verify after download with `shasum -a 256 -c`.
|
| 3 |
+
dd9354c4b3cd2b34195b0270540e4fa5db3811e42b6e4bdbd57958e0027e9272 neutrino-0.6b-base-fv5.gguf
|
| 4 |
+
81bf7be5eef18eb98ab4823596374c8dd6615151cf5615425973cb65942c0f73 fv5.patch
|
| 5 |
+
# source container (parent card): 91c0b26d105710e755ef1ec1b5607c00da5d5cbd048fd5584135fb6fc1e19399 neutrino-0p6b_v4_base.bin
|
| 6 |
+
# fork: ggml-org/llama.cpp @ d67c0b41 + fv5.patch = branch fermion-fv5, commit e07b3ec1 (tree f3067d90)
|
gguf/fv5.patch
ADDED
|
@@ -0,0 +1,952 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
From e07b3ec135c1f90c64584e0ea2dc983e47094ab9 Mon Sep 17 00:00:00 2001
|
| 2 |
+
From: Fermion Research <[email protected]>
|
| 3 |
+
Date: Sat, 25 Jul 2026 14:59:41 +0530
|
| 4 |
+
Subject: [PATCH] ggml: add Fermion FV5/FV5B five-value ternary types (CPU) +
|
| 5 |
+
fermion-greedy gate tool
|
| 6 |
+
|
| 7 |
+
FV5: 256-block, f32 dual per-row scales + 3 bit-planes (3.25 bpw), the GGUF
|
| 8 |
+
carrier for TRTC v4 trit records {0, +/-s_lo, +/-s_hi}. FV5B: f32 row scale +
|
| 9 |
+
int8 (8.125 bpw) for the container's embed/lm_head records. vec_dot against
|
| 10 |
+
raw F32 activations (no runtime activation quantization) so CPU numerics stay
|
| 11 |
+
in the same class as the f32 container expansion; blocks are produced offline
|
| 12 |
+
by the TRTC v4 -> GGUF converter (no quantize path). tools/fermion-greedy
|
| 13 |
+
certifies greedy token-identity vs the reference cache with teacher-forced
|
| 14 |
+
margin forensics.
|
| 15 |
+
---
|
| 16 |
+
FORK_NOTES.md | 89 +++++++
|
| 17 |
+
ggml/include/ggml.h | 4 +-
|
| 18 |
+
ggml/src/ggml-common.h | 24 ++
|
| 19 |
+
ggml/src/ggml-cpu/ggml-cpu.c | 14 ++
|
| 20 |
+
ggml/src/ggml-cpu/ops.cpp | 2 +
|
| 21 |
+
ggml/src/ggml-cpu/quants.c | 133 +++++++++++
|
| 22 |
+
ggml/src/ggml-cpu/quants.h | 5 +
|
| 23 |
+
ggml/src/ggml-quants.c | 65 ++++++
|
| 24 |
+
ggml/src/ggml-quants.h | 3 +
|
| 25 |
+
ggml/src/ggml.c | 16 ++
|
| 26 |
+
gguf-py/gguf/constants.py | 5 +
|
| 27 |
+
gguf-py/gguf/quants.py | 44 ++++
|
| 28 |
+
include/llama.h | 1 +
|
| 29 |
+
src/llama-model-loader.cpp | 2 +
|
| 30 |
+
tools/CMakeLists.txt | 1 +
|
| 31 |
+
tools/fermion-greedy/CMakeLists.txt | 5 +
|
| 32 |
+
tools/fermion-greedy/fermion-greedy.cpp | 299 ++++++++++++++++++++++++
|
| 33 |
+
17 files changed, 711 insertions(+), 1 deletion(-)
|
| 34 |
+
create mode 100644 FORK_NOTES.md
|
| 35 |
+
create mode 100644 tools/fermion-greedy/CMakeLists.txt
|
| 36 |
+
create mode 100644 tools/fermion-greedy/fermion-greedy.cpp
|
| 37 |
+
|
| 38 |
+
diff --git a/FORK_NOTES.md b/FORK_NOTES.md
|
| 39 |
+
new file mode 100644
|
| 40 |
+
index 0000000..0d4cfe2
|
| 41 |
+
--- /dev/null
|
| 42 |
+
+++ b/FORK_NOTES.md
|
| 43 |
+
@@ -0,0 +1,89 @@
|
| 44 |
+
+# Fermion `fermion-fv5` fork notes
|
| 45 |
+
+
|
| 46 |
+
+Branch `fermion-fv5` on top of upstream `ggml-org/llama.cpp` commit
|
| 47 |
+
+`d67c0b4107112e4790774c3a8169e2e3eb24643b` (2026-07-25). This fork adds two
|
| 48 |
+
+ggml weight types so llama.cpp can LOAD AND RUN Fermion Research's TRTC v4
|
| 49 |
+
+five-value ternary containers (Neutrino-0.6B, Neutrino-8B) converted to GGUF.
|
| 50 |
+
+CPU backend only in this cut. The graph is untouched: both models are stock
|
| 51 |
+
+Qwen3 geometry (`qwen3` arch, per-head Q/K RMSNorm, biasless qkv), which
|
| 52 |
+
+mainline already implements.
|
| 53 |
+
+
|
| 54 |
+
+GGUF is the *compatibility* door. The Fermion native runtime and its fused
|
| 55 |
+
+kernels remain the *fast* door; this fork optimizes for correctness parity
|
| 56 |
+
+with the container's deployed function, not for speed.
|
| 57 |
+
+
|
| 58 |
+
+## New types
|
| 59 |
+
+
|
| 60 |
+
+| ggml type | id | block | bytes/block | bpw | used for |
|
| 61 |
+
+|---|---|---|---|---|---|
|
| 62 |
+
+| `GGML_TYPE_FV5` | 43 | 256 | 104 (f32 `s_lo`, f32 `s_hi`, `bp[32]`, `bn[32]`, `br[32]`) | 3.25 | all attention/MLP linears |
|
| 63 |
+
+| `GGML_TYPE_FV5B` | 44 | 256 | 260 (f32 `s`, `int8 qs[256]`) | 8.125 | token_embd / output (TRTC int8 records) |
|
| 64 |
+
+
|
| 65 |
+
+Reconstruction semantics (identical to the container spec in
|
| 66 |
+
+`scripts/expand_trtc_v4_to_hf.py` of the research repo):
|
| 67 |
+
+
|
| 68 |
+
+- FV5: `w[j] = (bp[j] - bn[j]) * (br[j] ? s_hi : s_lo)` — five values per row
|
| 69 |
+
+ `{0, ±s_lo, ±s_hi}`; the per-row dual scales are stored as exact f32 copies
|
| 70 |
+
+ replicated into each 256-block; bit-planes keep the container's little bit
|
| 71 |
+
+ order, so conversion is a plane slice, not a re-encode.
|
| 72 |
+
+- FV5B: `w[j] = s * qs[j]` with the exact f32 per-row scale.
|
| 73 |
+
+
|
| 74 |
+
+This is NOT BitNet TQ1_0/TQ2_0 (ternary, single fp16 block scale): five
|
| 75 |
+
+values + dual per-row scales do not map onto those types.
|
| 76 |
+
+
|
| 77 |
+
+## Numerics policy
|
| 78 |
+
+
|
| 79 |
+
+`vec_dot_type = GGML_TYPE_F32` for both types: activations are consumed RAW
|
| 80 |
+
+(no runtime Q8 quantization), and scales are f32, so every weight value seen
|
| 81 |
+
+by the CPU backend is bit-identical to the f32 expansion of the container.
|
| 82 |
+
+The only difference vs an f32 reference forward is summation order. The
|
| 83 |
+
+`llama-fermion-greedy` tool (below) exists to certify exactly that: greedy
|
| 84 |
+
+token streams vs the reference cache, with teacher-forced margin forensics
|
| 85 |
+
+for any argmax flip. KV cache is set to F32 in the gate tool.
|
| 86 |
+
+
|
| 87 |
+
+## Changed files
|
| 88 |
+
+
|
| 89 |
+
+- `ggml/include/ggml.h` — enum entries FV5=43, FV5B=44 (appended; no renumbering).
|
| 90 |
+
+- `ggml/src/ggml-common.h` — `block_fv5`, `block_fv5b` + `QK_FV5`.
|
| 91 |
+
+- `ggml/src/ggml.c` — type traits (`to_float`; no `from_float_ref`: blocks are
|
| 92 |
+
+ produced offline by the TRTC v4 → GGUF converter, `llama-quantize` is
|
| 93 |
+
+ intentionally NOT wired).
|
| 94 |
+
+- `ggml/src/ggml-quants.{c,h}` — reference `dequantize_row_fv5{,b}` +
|
| 95 |
+
+ `ggml_validate_row_data` cases (validates scale finiteness + the fv5
|
| 96 |
+
+ invariants `bp&bn==0`, `br⊆bp|bn`).
|
| 97 |
+
+- `ggml/src/ggml-cpu/quants.{c,h}` — `ggml_vec_dot_fv5_f32`,
|
| 98 |
+
+ `ggml_vec_dot_fv5b_f32`: portable scalar with an AVX2 fast path inside
|
| 99 |
+
+ (masked activation sums mirroring the research repo's NEON decode
|
| 100 |
+
+ structure); no `arch/*/quants.c` or `arch-fallback.h` churn.
|
| 101 |
+
+- `ggml/src/ggml-cpu/ggml-cpu.c` — CPU traits (`vec_dot_type = F32`).
|
| 102 |
+
+- `ggml/src/ggml-cpu/ops.cpp` — `get_rows` cases (embedding lookup for FV5B).
|
| 103 |
+
+- `include/llama.h`, `src/llama-model-loader.cpp` — `LLAMA_FTYPE_MOSTLY_FV5`
|
| 104 |
+
+ (=42) name/mapping for clean loader printouts.
|
| 105 |
+
+- `gguf-py/gguf/constants.py`, `gguf-py/gguf/quants.py` — python-side type
|
| 106 |
+
+ registration + numpy dequantize (used by the converter's bitwise
|
| 107 |
+
+ cross-check); float→FV5 quantize deliberately raises.
|
| 108 |
+
+- `tools/fermion-greedy/` — the correctness-gate tool (exact token-id
|
| 109 |
+
+ prompts, free + teacher-forced greedy, per-flip logit margins, F32 KV).
|
| 110 |
+
+
|
| 111 |
+
+## Deliberate non-goals of this cut
|
| 112 |
+
+
|
| 113 |
+
+- No `llama-quantize` path (conversion is offline from TRTC v4 containers).
|
| 114 |
+
+- No CUDA/Metal/Vulkan kernels: those backends report the types unsupported
|
| 115 |
+
+ and fall back to CPU. (Native GPU serving lives in the Fermion runtime.)
|
| 116 |
+
+- No repack/IMatrix/LoRA integration; training-path ops (`add`, `acc`,
|
| 117 |
+
+ `out_prod`) on FV5 tensors abort as unsupported — inference never hits them.
|
| 118 |
+
+
|
| 119 |
+
+## Build
|
| 120 |
+
+
|
| 121 |
+
+ cmake -B build -DCMAKE_BUILD_TYPE=Release -DLLAMA_CURL=OFF
|
| 122 |
+
+ cmake --build build -j --target llama-cli llama-bench llama-fermion-greedy
|
| 123 |
+
+
|
| 124 |
+
+## Gate usage
|
| 125 |
+
+
|
| 126 |
+
+ llama-fermion-greedy -m neutrino-0p6b.fv5.gguf \
|
| 127 |
+
+ --prompts prompts_ids.txt --ref ref_ids.txt --steps 128 -t 16 \
|
| 128 |
+
+ --out gate_receipt.json
|
| 129 |
+
+
|
| 130 |
+
+`forced_mismatch_total == 0` and `all_free_match == true` is the shipping
|
| 131 |
+
+gate; any nonzero flip must be margin-classified (near-tie class) in the
|
| 132 |
+
+release receipts.
|
| 133 |
+
diff --git a/ggml/include/ggml.h b/ggml/include/ggml.h
|
| 134 |
+
index 35f0c44..3958bca 100644
|
| 135 |
+
--- a/ggml/include/ggml.h
|
| 136 |
+
+++ b/ggml/include/ggml.h
|
| 137 |
+
@@ -430,7 +430,9 @@ extern "C" {
|
| 138 |
+
GGML_TYPE_NVFP4 = 40, // NVFP4 (4 blocks, E4M3 scale)
|
| 139 |
+
GGML_TYPE_Q1_0 = 41,
|
| 140 |
+
GGML_TYPE_Q2_0 = 42,
|
| 141 |
+
- GGML_TYPE_COUNT = 43,
|
| 142 |
+
+ GGML_TYPE_FV5 = 43, // Fermion five-value ternary {0, +/-s_lo, +/-s_hi}: dual f32 scales + 3 bit-planes per 256-block
|
| 143 |
+
+ GGML_TYPE_FV5B = 44, // Fermion int8 companion: f32 row scale + 256 int8 per block (TRTC v4 embed/lm_head records)
|
| 144 |
+
+ GGML_TYPE_COUNT = 45,
|
| 145 |
+
};
|
| 146 |
+
|
| 147 |
+
// precision
|
| 148 |
+
diff --git a/ggml/src/ggml-common.h b/ggml/src/ggml-common.h
|
| 149 |
+
index 83f9118..5d3eb07 100644
|
| 150 |
+
--- a/ggml/src/ggml-common.h
|
| 151 |
+
+++ b/ggml/src/ggml-common.h
|
| 152 |
+
@@ -287,6 +287,30 @@ typedef struct {
|
| 153 |
+
} block_tq2_0;
|
| 154 |
+
static_assert(sizeof(block_tq2_0) == sizeof(ggml_half) + QK_K / 4, "wrong tq2_0 block size/padding");
|
| 155 |
+
|
| 156 |
+
+// Fermion five-value ternary (TRTC v4 trit records): w = (bp - bn) * (br ? s_hi : s_lo)
|
| 157 |
+
+// Scales are exact f32 copies of the container's per-row dual scales,
|
| 158 |
+
+// replicated into every block of the row. Bit-planes keep the container's
|
| 159 |
+
+// little bit order (bit i of byte j selects element 8*j + i).
|
| 160 |
+
+// 3.25 bpw
|
| 161 |
+
+#define QK_FV5 256
|
| 162 |
+
+typedef struct {
|
| 163 |
+
+ float s_lo; // low-magnitude scale
|
| 164 |
+
+ float s_hi; // high-magnitude scale
|
| 165 |
+
+ uint8_t bp[QK_FV5/8]; // +1 plane
|
| 166 |
+
+ uint8_t bn[QK_FV5/8]; // -1 plane (disjoint from bp)
|
| 167 |
+
+ uint8_t br[QK_FV5/8]; // hi-magnitude selector (subset of bp|bn)
|
| 168 |
+
+} block_fv5;
|
| 169 |
+
+static_assert(sizeof(block_fv5) == 2 * sizeof(float) + 3 * (QK_FV5/8), "wrong fv5 block size/padding");
|
| 170 |
+
+
|
| 171 |
+
+// Fermion int8 rows (TRTC v4 embed/lm_head records): w = s * q
|
| 172 |
+
+// s is the exact f32 per-row scale, replicated into every block of the row.
|
| 173 |
+
+// 8.125 bpw
|
| 174 |
+
+typedef struct {
|
| 175 |
+
+ float s; // row scale
|
| 176 |
+
+ int8_t qs[QK_FV5];
|
| 177 |
+
+} block_fv5b;
|
| 178 |
+
+static_assert(sizeof(block_fv5b) == sizeof(float) + QK_FV5, "wrong fv5b block size/padding");
|
| 179 |
+
+
|
| 180 |
+
//
|
| 181 |
+
// Super-block quantization structures
|
| 182 |
+
//
|
| 183 |
+
diff --git a/ggml/src/ggml-cpu/ggml-cpu.c b/ggml/src/ggml-cpu/ggml-cpu.c
|
| 184 |
+
index 491316f..db1679e 100644
|
| 185 |
+
--- a/ggml/src/ggml-cpu/ggml-cpu.c
|
| 186 |
+
+++ b/ggml/src/ggml-cpu/ggml-cpu.c
|
| 187 |
+
@@ -409,6 +409,20 @@ static const struct ggml_type_traits_cpu type_traits_cpu[GGML_TYPE_COUNT] = {
|
| 188 |
+
.vec_dot_type = GGML_TYPE_Q8_K,
|
| 189 |
+
.nrows = 1,
|
| 190 |
+
},
|
| 191 |
+
+ [GGML_TYPE_FV5] = {
|
| 192 |
+
+ // no from_float: FV5 tensors are produced offline (TRTC v4 -> GGUF)
|
| 193 |
+
+ // vec_dot_type F32: activations are used raw (no runtime quantization),
|
| 194 |
+
+ // keeping llama.cpp numerics in the same class as the f32 expansion
|
| 195 |
+
+ // reference the Fermion correctness gate compares against.
|
| 196 |
+
+ .vec_dot = ggml_vec_dot_fv5_f32,
|
| 197 |
+
+ .vec_dot_type = GGML_TYPE_F32,
|
| 198 |
+
+ .nrows = 1,
|
| 199 |
+
+ },
|
| 200 |
+
+ [GGML_TYPE_FV5B] = {
|
| 201 |
+
+ .vec_dot = ggml_vec_dot_fv5b_f32,
|
| 202 |
+
+ .vec_dot_type = GGML_TYPE_F32,
|
| 203 |
+
+ .nrows = 1,
|
| 204 |
+
+ },
|
| 205 |
+
[GGML_TYPE_I32] = {
|
| 206 |
+
.from_float = (ggml_from_float_t) ggml_cpu_fp32_to_i32,
|
| 207 |
+
},
|
| 208 |
+
diff --git a/ggml/src/ggml-cpu/ops.cpp b/ggml/src/ggml-cpu/ops.cpp
|
| 209 |
+
index 42ec809..4d4ab0c 100644
|
| 210 |
+
--- a/ggml/src/ggml-cpu/ops.cpp
|
| 211 |
+
+++ b/ggml/src/ggml-cpu/ops.cpp
|
| 212 |
+
@@ -5038,6 +5038,8 @@ void ggml_compute_forward_get_rows(
|
| 213 |
+
case GGML_TYPE_Q6_K:
|
| 214 |
+
case GGML_TYPE_TQ1_0:
|
| 215 |
+
case GGML_TYPE_TQ2_0:
|
| 216 |
+
+ case GGML_TYPE_FV5:
|
| 217 |
+
+ case GGML_TYPE_FV5B:
|
| 218 |
+
case GGML_TYPE_IQ2_XXS:
|
| 219 |
+
case GGML_TYPE_IQ2_XS:
|
| 220 |
+
case GGML_TYPE_IQ3_XXS:
|
| 221 |
+
diff --git a/ggml/src/ggml-cpu/quants.c b/ggml/src/ggml-cpu/quants.c
|
| 222 |
+
index 5e36459..731c01f 100644
|
| 223 |
+
--- a/ggml/src/ggml-cpu/quants.c
|
| 224 |
+
+++ b/ggml/src/ggml-cpu/quants.c
|
| 225 |
+
@@ -562,6 +562,139 @@ void ggml_vec_dot_tq2_0_q8_K_generic(int n, float * GGML_RESTRICT s, size_t bs,
|
| 226 |
+
*s = sumf;
|
| 227 |
+
}
|
| 228 |
+
|
| 229 |
+
+// ====================== Fermion five-value ternary (FV5 / FV5B)
|
| 230 |
+
+//
|
| 231 |
+
+// vec_dot against RAW F32 activations (vec_dot_type == GGML_TYPE_F32): the
|
| 232 |
+
+// activations are never quantized, so the only difference vs a full f32
|
| 233 |
+
+// matmul with the expanded weights is summation order. Weights per block:
|
| 234 |
+
+// w[j] = (bp[j] - bn[j]) * (br[j] ? s_hi : s_lo)
|
| 235 |
+
+// so the block dot factors into four masked activation sums:
|
| 236 |
+
+// dot = s_lo * (sum_lo_p - sum_lo_n) + s_hi * (sum_hi_p - sum_hi_n)
|
| 237 |
+
+// This mirrors the sign/masked-accumulate structure of the reference NEON
|
| 238 |
+
+// decode (kernels/neon_war5_lab.c) in portable form.
|
| 239 |
+
+
|
| 240 |
+
+#if defined(__AVX2__)
|
| 241 |
+
+static inline float ggml_fv5_hsum_ps(__m256 v) {
|
| 242 |
+
+ __m128 lo = _mm256_castps256_ps128(v);
|
| 243 |
+
+ __m128 hi = _mm256_extractf128_ps(v, 1);
|
| 244 |
+
+ lo = _mm_add_ps(lo, hi);
|
| 245 |
+
+ lo = _mm_add_ps(lo, _mm_movehl_ps(lo, lo));
|
| 246 |
+
+ lo = _mm_add_ss(lo, _mm_shuffle_ps(lo, lo, 0x55));
|
| 247 |
+
+ return _mm_cvtss_f32(lo);
|
| 248 |
+
+}
|
| 249 |
+
+#endif
|
| 250 |
+
+
|
| 251 |
+
+void ggml_vec_dot_fv5_f32(int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, size_t bx, const void * GGML_RESTRICT vy, size_t by, int nrc) {
|
| 252 |
+
+ assert(nrc == 1);
|
| 253 |
+
+ assert(n % QK_FV5 == 0);
|
| 254 |
+
+ UNUSED(nrc);
|
| 255 |
+
+ UNUSED(bx);
|
| 256 |
+
+ UNUSED(by);
|
| 257 |
+
+ UNUSED(bs);
|
| 258 |
+
+
|
| 259 |
+
+ const block_fv5 * GGML_RESTRICT x = vx;
|
| 260 |
+
+ const float * GGML_RESTRICT y = vy;
|
| 261 |
+
+
|
| 262 |
+
+ const int nb = n / QK_FV5;
|
| 263 |
+
+ float sumf = 0.0f;
|
| 264 |
+
+
|
| 265 |
+
+ for (int i = 0; i < nb; ++i) {
|
| 266 |
+
+ const float s_lo = x[i].s_lo;
|
| 267 |
+
+ const float s_hi = x[i].s_hi;
|
| 268 |
+
+ const float * GGML_RESTRICT xf = y + i*QK_FV5;
|
| 269 |
+
+
|
| 270 |
+
+#if defined(__AVX2__)
|
| 271 |
+
+ const __m256i bitpos = _mm256_setr_epi32(1, 2, 4, 8, 16, 32, 64, 128);
|
| 272 |
+
+ __m256 acc_lo_p = _mm256_setzero_ps();
|
| 273 |
+
+ __m256 acc_lo_n = _mm256_setzero_ps();
|
| 274 |
+
+ __m256 acc_hi_p = _mm256_setzero_ps();
|
| 275 |
+
+ __m256 acc_hi_n = _mm256_setzero_ps();
|
| 276 |
+
+
|
| 277 |
+
+ for (int j = 0; j < QK_FV5/8; ++j) {
|
| 278 |
+
+ const uint8_t bpj = x[i].bp[j];
|
| 279 |
+
+ const uint8_t bnj = x[i].bn[j];
|
| 280 |
+
+ if (!(bpj | bnj)) {
|
| 281 |
+
+ continue;
|
| 282 |
+
+ }
|
| 283 |
+
+ const __m256i m_p = _mm256_cmpeq_epi32(_mm256_and_si256(_mm256_set1_epi32(bpj), bitpos), bitpos);
|
| 284 |
+
+ const __m256i m_n = _mm256_cmpeq_epi32(_mm256_and_si256(_mm256_set1_epi32(bnj), bitpos), bitpos);
|
| 285 |
+
+ const __m256i m_r = _mm256_cmpeq_epi32(_mm256_and_si256(_mm256_set1_epi32(x[i].br[j]), bitpos), bitpos);
|
| 286 |
+
+
|
| 287 |
+
+ const __m256 x8 = _mm256_loadu_ps(xf + 8*j);
|
| 288 |
+
+
|
| 289 |
+
+ acc_lo_p = _mm256_add_ps(acc_lo_p, _mm256_and_ps(x8, _mm256_castsi256_ps(_mm256_andnot_si256(m_r, m_p))));
|
| 290 |
+
+ acc_lo_n = _mm256_add_ps(acc_lo_n, _mm256_and_ps(x8, _mm256_castsi256_ps(_mm256_andnot_si256(m_r, m_n))));
|
| 291 |
+
+ acc_hi_p = _mm256_add_ps(acc_hi_p, _mm256_and_ps(x8, _mm256_castsi256_ps(_mm256_and_si256(m_p, m_r))));
|
| 292 |
+
+ acc_hi_n = _mm256_add_ps(acc_hi_n, _mm256_and_ps(x8, _mm256_castsi256_ps(_mm256_and_si256(m_n, m_r))));
|
| 293 |
+
+ }
|
| 294 |
+
+
|
| 295 |
+
+ sumf += s_lo * (ggml_fv5_hsum_ps(acc_lo_p) - ggml_fv5_hsum_ps(acc_lo_n))
|
| 296 |
+
+ + s_hi * (ggml_fv5_hsum_ps(acc_hi_p) - ggml_fv5_hsum_ps(acc_hi_n));
|
| 297 |
+
+#else
|
| 298 |
+
+ float sum_lo_p = 0.0f, sum_lo_n = 0.0f, sum_hi_p = 0.0f, sum_hi_n = 0.0f;
|
| 299 |
+
+
|
| 300 |
+
+ for (int j = 0; j < QK_FV5/8; ++j) {
|
| 301 |
+
+ const uint8_t bpj = x[i].bp[j];
|
| 302 |
+
+ const uint8_t bnj = x[i].bn[j];
|
| 303 |
+
+ if (!(bpj | bnj)) {
|
| 304 |
+
+ continue;
|
| 305 |
+
+ }
|
| 306 |
+
+ const uint8_t brj = x[i].br[j];
|
| 307 |
+
+ const float * GGML_RESTRICT xj = xf + 8*j;
|
| 308 |
+
+ for (int b = 0; b < 8; ++b) {
|
| 309 |
+
+ const uint8_t bit = 1u << b;
|
| 310 |
+
+ if (bpj & bit) {
|
| 311 |
+
+ if (brj & bit) { sum_hi_p += xj[b]; } else { sum_lo_p += xj[b]; }
|
| 312 |
+
+ } else if (bnj & bit) {
|
| 313 |
+
+ if (brj & bit) { sum_hi_n += xj[b]; } else { sum_lo_n += xj[b]; }
|
| 314 |
+
+ }
|
| 315 |
+
+ }
|
| 316 |
+
+ }
|
| 317 |
+
+
|
| 318 |
+
+ sumf += s_lo * (sum_lo_p - sum_lo_n) + s_hi * (sum_hi_p - sum_hi_n);
|
| 319 |
+
+#endif
|
| 320 |
+
+ }
|
| 321 |
+
+
|
| 322 |
+
+ *s = sumf;
|
| 323 |
+
+}
|
| 324 |
+
+
|
| 325 |
+
+void ggml_vec_dot_fv5b_f32(int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, size_t bx, const void * GGML_RESTRICT vy, size_t by, int nrc) {
|
| 326 |
+
+ assert(nrc == 1);
|
| 327 |
+
+ assert(n % QK_FV5 == 0);
|
| 328 |
+
+ UNUSED(nrc);
|
| 329 |
+
+ UNUSED(bx);
|
| 330 |
+
+ UNUSED(by);
|
| 331 |
+
+ UNUSED(bs);
|
| 332 |
+
+
|
| 333 |
+
+ const block_fv5b * GGML_RESTRICT x = vx;
|
| 334 |
+
+ const float * GGML_RESTRICT y = vy;
|
| 335 |
+
+
|
| 336 |
+
+ const int nb = n / QK_FV5;
|
| 337 |
+
+ float sumf = 0.0f;
|
| 338 |
+
+
|
| 339 |
+
+ for (int i = 0; i < nb; ++i) {
|
| 340 |
+
+ const float * GGML_RESTRICT xf = y + i*QK_FV5;
|
| 341 |
+
+
|
| 342 |
+
+#if defined(__AVX2__)
|
| 343 |
+
+ __m256 acc = _mm256_setzero_ps();
|
| 344 |
+
+ for (int j = 0; j < QK_FV5/8; ++j) {
|
| 345 |
+
+ const __m128i q8 = _mm_loadl_epi64((const __m128i *)(x[i].qs + 8*j));
|
| 346 |
+
+ const __m256 qf = _mm256_cvtepi32_ps(_mm256_cvtepi8_epi32(q8));
|
| 347 |
+
+ acc = _mm256_fmadd_ps(qf, _mm256_loadu_ps(xf + 8*j), acc);
|
| 348 |
+
+ }
|
| 349 |
+
+ sumf += x[i].s * ggml_fv5_hsum_ps(acc);
|
| 350 |
+
+#else
|
| 351 |
+
+ float sumq = 0.0f;
|
| 352 |
+
+ for (int j = 0; j < QK_FV5; ++j) {
|
| 353 |
+
+ sumq += (float) x[i].qs[j] * xf[j];
|
| 354 |
+
+ }
|
| 355 |
+
+ sumf += x[i].s * sumq;
|
| 356 |
+
+#endif
|
| 357 |
+
+ }
|
| 358 |
+
+
|
| 359 |
+
+ *s = sumf;
|
| 360 |
+
+}
|
| 361 |
+
+
|
| 362 |
+
void ggml_vec_dot_q2_K_q8_K_generic(int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, size_t bx, const void * GGML_RESTRICT vy, size_t by, int nrc) {
|
| 363 |
+
assert(nrc == 1);
|
| 364 |
+
UNUSED(nrc);
|
| 365 |
+
diff --git a/ggml/src/ggml-cpu/quants.h b/ggml/src/ggml-cpu/quants.h
|
| 366 |
+
index 93ea7ee..541f292 100644
|
| 367 |
+
--- a/ggml/src/ggml-cpu/quants.h
|
| 368 |
+
+++ b/ggml/src/ggml-cpu/quants.h
|
| 369 |
+
@@ -58,6 +58,11 @@ void ggml_vec_dot_q6_K_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const voi
|
| 370 |
+
void ggml_vec_dot_tq1_0_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, size_t bx, const void * GGML_RESTRICT vy, size_t by, int nrc);
|
| 371 |
+
void ggml_vec_dot_tq2_0_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, size_t bx, const void * GGML_RESTRICT vy, size_t by, int nrc);
|
| 372 |
+
|
| 373 |
+
+// Fermion FV5/FV5B: single portable implementation (scalar with an AVX2 fast
|
| 374 |
+
+// path inside), dotted against raw F32 activations — no _generic/arch split.
|
| 375 |
+
+void ggml_vec_dot_fv5_f32 (int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, size_t bx, const void * GGML_RESTRICT vy, size_t by, int nrc);
|
| 376 |
+
+void ggml_vec_dot_fv5b_f32(int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, size_t bx, const void * GGML_RESTRICT vy, size_t by, int nrc);
|
| 377 |
+
+
|
| 378 |
+
void ggml_vec_dot_iq2_xxs_q8_K(int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, size_t bx, const void * GGML_RESTRICT vy, size_t by, int nrc);
|
| 379 |
+
void ggml_vec_dot_iq2_xs_q8_K (int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, size_t bx, const void * GGML_RESTRICT vy, size_t by, int nrc);
|
| 380 |
+
void ggml_vec_dot_iq2_s_q8_K (int n, float * GGML_RESTRICT s, size_t bs, const void * GGML_RESTRICT vx, size_t bx, const void * GGML_RESTRICT vy, size_t by, int nrc);
|
| 381 |
+
diff --git a/ggml/src/ggml-quants.c b/ggml/src/ggml-quants.c
|
| 382 |
+
index 1ebc50a..39ea3cf 100644
|
| 383 |
+
--- a/ggml/src/ggml-quants.c
|
| 384 |
+
+++ b/ggml/src/ggml-quants.c
|
| 385 |
+
@@ -2483,6 +2483,43 @@ void dequantize_row_tq2_0(const block_tq2_0 * GGML_RESTRICT x, float * GGML_REST
|
| 386 |
+
}
|
| 387 |
+
}
|
| 388 |
+
|
| 389 |
+
+// ====================== Fermion five-value ternary (FV5 / FV5B)
|
| 390 |
+
+//
|
| 391 |
+
+// FV5 blocks are produced offline by the TRTC v4 -> GGUF converter; there is
|
| 392 |
+
+// no float -> FV5 quantizer here on purpose (the container is the source of
|
| 393 |
+
+// truth). Reconstruction semantics match the container exactly:
|
| 394 |
+
+// w[j] = (bp[j] - bn[j]) * (br[j] ? s_hi : s_lo) (all f32)
|
| 395 |
+
+
|
| 396 |
+
+void dequantize_row_fv5(const block_fv5 * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t k) {
|
| 397 |
+
+ assert(k % QK_FV5 == 0);
|
| 398 |
+
+ const int64_t nb = k / QK_FV5;
|
| 399 |
+
+
|
| 400 |
+
+ for (int64_t i = 0; i < nb; ++i) {
|
| 401 |
+
+ const float s_lo = x[i].s_lo;
|
| 402 |
+
+ const float s_hi = x[i].s_hi;
|
| 403 |
+
+
|
| 404 |
+
+ for (int j = 0; j < QK_FV5; ++j) {
|
| 405 |
+
+ const int byte = j >> 3;
|
| 406 |
+
+ const uint8_t bit = 1u << (j & 7);
|
| 407 |
+
+ const int sign = ((x[i].bp[byte] & bit) ? 1 : 0) - ((x[i].bn[byte] & bit) ? 1 : 0);
|
| 408 |
+
+ const float mag = (x[i].br[byte] & bit) ? s_hi : s_lo;
|
| 409 |
+
+ y[i*QK_FV5 + j] = (float) sign * mag;
|
| 410 |
+
+ }
|
| 411 |
+
+ }
|
| 412 |
+
+}
|
| 413 |
+
+
|
| 414 |
+
+void dequantize_row_fv5b(const block_fv5b * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t k) {
|
| 415 |
+
+ assert(k % QK_FV5 == 0);
|
| 416 |
+
+ const int64_t nb = k / QK_FV5;
|
| 417 |
+
+
|
| 418 |
+
+ for (int64_t i = 0; i < nb; ++i) {
|
| 419 |
+
+ const float s = x[i].s;
|
| 420 |
+
+ for (int j = 0; j < QK_FV5; ++j) {
|
| 421 |
+
+ y[i*QK_FV5 + j] = s * (float) x[i].qs[j];
|
| 422 |
+
+ }
|
| 423 |
+
+ }
|
| 424 |
+
+}
|
| 425 |
+
+
|
| 426 |
+
// ====================== "True" 2-bit (de)-quantization
|
| 427 |
+
|
| 428 |
+
void dequantize_row_iq2_xxs(const block_iq2_xxs * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t k) {
|
| 429 |
+
@@ -5604,6 +5641,34 @@ bool ggml_validate_row_data(enum ggml_type type, const void * data, size_t nbyte
|
| 430 |
+
{
|
| 431 |
+
VALIDATE_ROW_DATA_D_F16_IMPL(block_tq2_0, data, nb);
|
| 432 |
+
} break;
|
| 433 |
+
+ case GGML_TYPE_FV5:
|
| 434 |
+
+ {
|
| 435 |
+
+ const block_fv5 * q = (const block_fv5 *) data;
|
| 436 |
+
+ for (size_t i = 0; i < nb; ++i) {
|
| 437 |
+
+ if (!validate_float(q[i].s_lo, i) || !validate_float(q[i].s_hi, i)) {
|
| 438 |
+
+ return false;
|
| 439 |
+
+ }
|
| 440 |
+
+ for (int j = 0; j < QK_FV5/8; ++j) {
|
| 441 |
+
+ if (q[i].bp[j] & q[i].bn[j]) {
|
| 442 |
+
+ fprintf(stderr, "%s: fv5 invariant bp&bn != 0 at block %zu byte %d\n", __func__, i, j);
|
| 443 |
+
+ return false;
|
| 444 |
+
+ }
|
| 445 |
+
+ if (q[i].br[j] & ~(q[i].bp[j] | q[i].bn[j])) {
|
| 446 |
+
+ fprintf(stderr, "%s: fv5 invariant br outside bp|bn at block %zu byte %d\n", __func__, i, j);
|
| 447 |
+
+ return false;
|
| 448 |
+
+ }
|
| 449 |
+
+ }
|
| 450 |
+
+ }
|
| 451 |
+
+ } break;
|
| 452 |
+
+ case GGML_TYPE_FV5B:
|
| 453 |
+
+ {
|
| 454 |
+
+ const block_fv5b * q = (const block_fv5b *) data;
|
| 455 |
+
+ for (size_t i = 0; i < nb; ++i) {
|
| 456 |
+
+ if (!validate_float(q[i].s, i)) {
|
| 457 |
+
+ return false;
|
| 458 |
+
+ }
|
| 459 |
+
+ }
|
| 460 |
+
+ } break;
|
| 461 |
+
case GGML_TYPE_IQ1_S:
|
| 462 |
+
{
|
| 463 |
+
VALIDATE_ROW_DATA_D_F16_IMPL(block_iq1_s, data, nb);
|
| 464 |
+
diff --git a/ggml/src/ggml-quants.h b/ggml/src/ggml-quants.h
|
| 465 |
+
index 75188f1..f71e078 100644
|
| 466 |
+
--- a/ggml/src/ggml-quants.h
|
| 467 |
+
+++ b/ggml/src/ggml-quants.h
|
| 468 |
+
@@ -65,6 +65,9 @@ GGML_API void dequantize_row_q8_K(const block_q8_K * GGML_RESTRICT x, float * GG
|
| 469 |
+
GGML_API void dequantize_row_tq1_0(const block_tq1_0 * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t k);
|
| 470 |
+
GGML_API void dequantize_row_tq2_0(const block_tq2_0 * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t k);
|
| 471 |
+
|
| 472 |
+
+GGML_API void dequantize_row_fv5 (const block_fv5 * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t k);
|
| 473 |
+
+GGML_API void dequantize_row_fv5b(const block_fv5b * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t k);
|
| 474 |
+
+
|
| 475 |
+
GGML_API void dequantize_row_iq2_xxs(const block_iq2_xxs * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t k);
|
| 476 |
+
GGML_API void dequantize_row_iq2_xs (const block_iq2_xs * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t k);
|
| 477 |
+
GGML_API void dequantize_row_iq2_s (const block_iq2_s * GGML_RESTRICT x, float * GGML_RESTRICT y, int64_t k);
|
| 478 |
+
diff --git a/ggml/src/ggml.c b/ggml/src/ggml.c
|
| 479 |
+
index a7d1fe7..7de0dda 100644
|
| 480 |
+
--- a/ggml/src/ggml.c
|
| 481 |
+
+++ b/ggml/src/ggml.c
|
| 482 |
+
@@ -942,6 +942,22 @@ static const struct ggml_type_traits type_traits[GGML_TYPE_COUNT] = {
|
| 483 |
+
.type_size = 0,
|
| 484 |
+
.is_quantized = false,
|
| 485 |
+
},
|
| 486 |
+
+ [GGML_TYPE_FV5] = {
|
| 487 |
+
+ .type_name = "fv5",
|
| 488 |
+
+ .blck_size = QK_FV5,
|
| 489 |
+
+ .type_size = sizeof(block_fv5),
|
| 490 |
+
+ .is_quantized = true,
|
| 491 |
+
+ .to_float = (ggml_to_float_t) dequantize_row_fv5,
|
| 492 |
+
+ .from_float_ref = NULL, // produced offline by the TRTC v4 -> GGUF converter
|
| 493 |
+
+ },
|
| 494 |
+
+ [GGML_TYPE_FV5B] = {
|
| 495 |
+
+ .type_name = "fv5b",
|
| 496 |
+
+ .blck_size = QK_FV5,
|
| 497 |
+
+ .type_size = sizeof(block_fv5b),
|
| 498 |
+
+ .is_quantized = true,
|
| 499 |
+
+ .to_float = (ggml_to_float_t) dequantize_row_fv5b,
|
| 500 |
+
+ .from_float_ref = NULL, // produced offline by the TRTC v4 -> GGUF converter
|
| 501 |
+
+ },
|
| 502 |
+
};
|
| 503 |
+
|
| 504 |
+
const struct ggml_type_traits * ggml_get_type_traits(enum ggml_type type) {
|
| 505 |
+
diff --git a/gguf-py/gguf/constants.py b/gguf-py/gguf/constants.py
|
| 506 |
+
index d55253e..05076c9 100644
|
| 507 |
+
--- a/gguf-py/gguf/constants.py
|
| 508 |
+
+++ b/gguf-py/gguf/constants.py
|
| 509 |
+
@@ -4595,6 +4595,8 @@ class GGMLQuantizationType(IntEnum):
|
| 510 |
+
NVFP4 = 40
|
| 511 |
+
Q1_0 = 41
|
| 512 |
+
Q2_0 = 42
|
| 513 |
+
+ FV5 = 43
|
| 514 |
+
+ FV5B = 44
|
| 515 |
+
|
| 516 |
+
|
| 517 |
+
class ExpertGatingFuncType(IntEnum):
|
| 518 |
+
@@ -4651,6 +4653,7 @@ class LlamaFileType(IntEnum):
|
| 519 |
+
MOSTLY_NVFP4 = 39 # except 1d tensors
|
| 520 |
+
MOSTLY_Q1_0 = 40 # except 1d tensors
|
| 521 |
+
MOSTLY_Q2_0 = 41 # except 1d tensors
|
| 522 |
+
+ MOSTLY_FV5 = 42 # Fermion five-value ternary; int8 embed/lm_head (FV5B), norms F32
|
| 523 |
+
|
| 524 |
+
GUESSED = 1024 # not specified in the model file
|
| 525 |
+
|
| 526 |
+
@@ -4777,6 +4780,8 @@ GGML_QUANT_SIZES: dict[GGMLQuantizationType, tuple[int, int]] = {
|
| 527 |
+
GGMLQuantizationType.NVFP4: (64, 4 + 32),
|
| 528 |
+
GGMLQuantizationType.Q1_0: (128, 2 + 16),
|
| 529 |
+
GGMLQuantizationType.Q2_0: (64, 2 + 16),
|
| 530 |
+
+ GGMLQuantizationType.FV5: (256, 4 + 4 + 3 * 32),
|
| 531 |
+
+ GGMLQuantizationType.FV5B: (256, 4 + 256),
|
| 532 |
+
}
|
| 533 |
+
|
| 534 |
+
|
| 535 |
+
diff --git a/gguf-py/gguf/quants.py b/gguf-py/gguf/quants.py
|
| 536 |
+
index 80966b6..c7122e5 100644
|
| 537 |
+
--- a/gguf-py/gguf/quants.py
|
| 538 |
+
+++ b/gguf-py/gguf/quants.py
|
| 539 |
+
@@ -654,6 +654,50 @@ class TQ2_0(__Quant, qtype=GGMLQuantizationType.TQ2_0):
|
| 540 |
+
return (d * qs.astype(np.float32))
|
| 541 |
+
|
| 542 |
+
|
| 543 |
+
+class FV5(__Quant, qtype=GGMLQuantizationType.FV5):
|
| 544 |
+
+ # Fermion five-value ternary. Blocks are produced offline by the
|
| 545 |
+
+ # TRTC v4 -> GGUF converter; float -> FV5 quantization is intentionally
|
| 546 |
+
+ # not implemented (per-row dual scales live in the container).
|
| 547 |
+
+ # Layout: f32 s_lo, f32 s_hi, bp[32], bn[32], br[32] (little bit order).
|
| 548 |
+
+ @classmethod
|
| 549 |
+
+ def quantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
|
| 550 |
+
+ raise NotImplementedError("FV5 is produced offline from TRTC v4 containers")
|
| 551 |
+
+
|
| 552 |
+
+ @classmethod
|
| 553 |
+
+ def dequantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
|
| 554 |
+
+ n_blocks = blocks.shape[0]
|
| 555 |
+
+
|
| 556 |
+
+ s_lo, s_hi, bp, bn, br = np.hsplit(blocks, [4, 8, 40, 72])
|
| 557 |
+
+
|
| 558 |
+
+ s_lo = s_lo.view(np.float32) # (n, 1)
|
| 559 |
+
+ s_hi = s_hi.view(np.float32)
|
| 560 |
+
+
|
| 561 |
+
+ bp = np.unpackbits(bp, axis=-1, bitorder="little") # (n, 256) in {0,1}
|
| 562 |
+
+ bn = np.unpackbits(bn, axis=-1, bitorder="little")
|
| 563 |
+
+ br = np.unpackbits(br, axis=-1, bitorder="little")
|
| 564 |
+
+
|
| 565 |
+
+ sign = bp.astype(np.float32) - bn.astype(np.float32)
|
| 566 |
+
+ mag = np.where(br != 0, s_hi, s_lo).astype(np.float32)
|
| 567 |
+
+ return sign * mag
|
| 568 |
+
+
|
| 569 |
+
+
|
| 570 |
+
+class FV5B(__Quant, qtype=GGMLQuantizationType.FV5B):
|
| 571 |
+
+ # Fermion int8 rows (TRTC v4 embed/lm_head records): w = f32(s) * int8 q.
|
| 572 |
+
+ # Layout: f32 s, int8 qs[256].
|
| 573 |
+
+ @classmethod
|
| 574 |
+
+ def quantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
|
| 575 |
+
+ raise NotImplementedError("FV5B is produced offline from TRTC v4 containers")
|
| 576 |
+
+
|
| 577 |
+
+ @classmethod
|
| 578 |
+
+ def dequantize_blocks(cls, blocks: np.ndarray) -> np.ndarray:
|
| 579 |
+
+ s, qs = np.hsplit(blocks, [4])
|
| 580 |
+
+
|
| 581 |
+
+ s = s.view(np.float32) # (n, 1)
|
| 582 |
+
+ qs = qs.view(np.int8) # (n, 256)
|
| 583 |
+
+
|
| 584 |
+
+ return s * qs.astype(np.float32)
|
| 585 |
+
+
|
| 586 |
+
+
|
| 587 |
+
class MXFP4(__Quant, qtype=GGMLQuantizationType.MXFP4):
|
| 588 |
+
# e2m1 values (doubled)
|
| 589 |
+
# ref: https://www.opencompute.org/documents/ocp-microscaling-formats-mx-v1-0-spec-final-pdf
|
| 590 |
+
diff --git a/include/llama.h b/include/llama.h
|
| 591 |
+
index 9fab693..584717c 100644
|
| 592 |
+
--- a/include/llama.h
|
| 593 |
+
+++ b/include/llama.h
|
| 594 |
+
@@ -156,6 +156,7 @@ extern "C" {
|
| 595 |
+
LLAMA_FTYPE_MOSTLY_NVFP4 = 39, // except 1d tensors
|
| 596 |
+
LLAMA_FTYPE_MOSTLY_Q1_0 = 40, // except 1d tensors
|
| 597 |
+
LLAMA_FTYPE_MOSTLY_Q2_0 = 41, // except 1d tensors
|
| 598 |
+
+ LLAMA_FTYPE_MOSTLY_FV5 = 42, // Fermion five-value ternary; int8 embed/lm_head (FV5B), norms F32
|
| 599 |
+
|
| 600 |
+
LLAMA_FTYPE_GUESSED = 1024, // not specified in the model file
|
| 601 |
+
};
|
| 602 |
+
diff --git a/src/llama-model-loader.cpp b/src/llama-model-loader.cpp
|
| 603 |
+
index 43447f5..6fb50f8 100644
|
| 604 |
+
--- a/src/llama-model-loader.cpp
|
| 605 |
+
+++ b/src/llama-model-loader.cpp
|
| 606 |
+
@@ -58,6 +58,7 @@ const char * llama_ftype_name(llama_ftype ftype) {
|
| 607 |
+
case LLAMA_FTYPE_MOSTLY_Q6_K: name = LLAMA_FTYPE_PREFIX "Q6_K"; break;
|
| 608 |
+
case LLAMA_FTYPE_MOSTLY_TQ1_0: name = LLAMA_FTYPE_PREFIX "TQ1_0 - 1.69 bpw ternary"; break;
|
| 609 |
+
case LLAMA_FTYPE_MOSTLY_TQ2_0: name = LLAMA_FTYPE_PREFIX "TQ2_0 - 2.06 bpw ternary"; break;
|
| 610 |
+
+ case LLAMA_FTYPE_MOSTLY_FV5: name = LLAMA_FTYPE_PREFIX "FV5 - 3.25 bpw Fermion five-value ternary"; break;
|
| 611 |
+
case LLAMA_FTYPE_MOSTLY_IQ2_XXS: name = LLAMA_FTYPE_PREFIX "IQ2_XXS - 2.0625 bpw"; break;
|
| 612 |
+
case LLAMA_FTYPE_MOSTLY_IQ2_XS: name = LLAMA_FTYPE_PREFIX "IQ2_XS - 2.3125 bpw"; break;
|
| 613 |
+
case LLAMA_FTYPE_MOSTLY_IQ2_S: name = LLAMA_FTYPE_PREFIX "IQ2_S - 2.5 bpw"; break;
|
| 614 |
+
@@ -746,6 +747,7 @@ llama_model_loader::llama_model_loader(
|
| 615 |
+
case GGML_TYPE_Q6_K: ftype = LLAMA_FTYPE_MOSTLY_Q6_K; break;
|
| 616 |
+
case GGML_TYPE_TQ1_0: ftype = LLAMA_FTYPE_MOSTLY_TQ1_0; break;
|
| 617 |
+
case GGML_TYPE_TQ2_0: ftype = LLAMA_FTYPE_MOSTLY_TQ2_0; break;
|
| 618 |
+
+ case GGML_TYPE_FV5: ftype = LLAMA_FTYPE_MOSTLY_FV5; break;
|
| 619 |
+
case GGML_TYPE_IQ2_XXS: ftype = LLAMA_FTYPE_MOSTLY_IQ2_XXS; break;
|
| 620 |
+
case GGML_TYPE_IQ2_XS: ftype = LLAMA_FTYPE_MOSTLY_IQ2_XS; break;
|
| 621 |
+
case GGML_TYPE_IQ2_S: ftype = LLAMA_FTYPE_MOSTLY_IQ2_S; break;
|
| 622 |
+
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
|
| 623 |
+
index 780df32..e39837d 100644
|
| 624 |
+
--- a/tools/CMakeLists.txt
|
| 625 |
+
+++ b/tools/CMakeLists.txt
|
| 626 |
+
@@ -15,6 +15,7 @@ llama_add_compile_flags()
|
| 627 |
+
if (EMSCRIPTEN)
|
| 628 |
+
else()
|
| 629 |
+
add_subdirectory(batched-bench)
|
| 630 |
+
+ add_subdirectory(fermion-greedy)
|
| 631 |
+
add_subdirectory(gguf-split)
|
| 632 |
+
add_subdirectory(imatrix)
|
| 633 |
+
add_subdirectory(llama-bench)
|
| 634 |
+
diff --git a/tools/fermion-greedy/CMakeLists.txt b/tools/fermion-greedy/CMakeLists.txt
|
| 635 |
+
new file mode 100644
|
| 636 |
+
index 0000000..021440a
|
| 637 |
+
--- /dev/null
|
| 638 |
+
+++ b/tools/fermion-greedy/CMakeLists.txt
|
| 639 |
+
@@ -0,0 +1,5 @@
|
| 640 |
+
+set(TARGET llama-fermion-greedy)
|
| 641 |
+
+add_executable(${TARGET} fermion-greedy.cpp)
|
| 642 |
+
+install(TARGETS ${TARGET} RUNTIME)
|
| 643 |
+
+target_link_libraries(${TARGET} PRIVATE llama ${CMAKE_THREAD_LIBS_INIT})
|
| 644 |
+
+target_compile_features(${TARGET} PRIVATE cxx_std_17)
|
| 645 |
+
diff --git a/tools/fermion-greedy/fermion-greedy.cpp b/tools/fermion-greedy/fermion-greedy.cpp
|
| 646 |
+
new file mode 100644
|
| 647 |
+
index 0000000..6f7fde2
|
| 648 |
+
--- /dev/null
|
| 649 |
+
+++ b/tools/fermion-greedy/fermion-greedy.cpp
|
| 650 |
+
@@ -0,0 +1,299 @@
|
| 651 |
+
+// fermion-greedy — deterministic greedy runner for the Fermion FV5 correctness gate.
|
| 652 |
+
+//
|
| 653 |
+
+// Reads prompts as EXACT token ids (no tokenizer in the loop), runs pure
|
| 654 |
+
+// argmax greedy generation, and — when a reference stream is given — also a
|
| 655 |
+
+// teacher-forced pass that feeds the reference token at every step while
|
| 656 |
+
+// recording where our argmax disagrees and by what logit margin. Root-cause
|
| 657 |
+
+// flips (teacher-forced mismatches) are the gate currency: free-run streams
|
| 658 |
+
+// diverge wholesale after one flip, forced mode localizes every disagreement.
|
| 659 |
+
+//
|
| 660 |
+
+// The KV cache is kept in F32 so the only numerical difference vs the f32
|
| 661 |
+
+// expansion reference is operation order, not storage precision.
|
| 662 |
+
+//
|
| 663 |
+
+// usage:
|
| 664 |
+
+// llama-fermion-greedy -m model.gguf --prompts prompts.txt [--ref ref.txt]
|
| 665 |
+
+// [--steps 128] [-t nthreads] [--out receipt.json]
|
| 666 |
+
+//
|
| 667 |
+
+// prompts.txt: one prompt per line, comma-separated token ids
|
| 668 |
+
+// ref.txt: one line per prompt, comma-separated reference continuation ids
|
| 669 |
+
+// output: JSON receipt on stdout (or --out file)
|
| 670 |
+
+
|
| 671 |
+
+#include "llama.h"
|
| 672 |
+
+
|
| 673 |
+
+#include <chrono>
|
| 674 |
+
+#include <cinttypes>
|
| 675 |
+
+#include <cstdio>
|
| 676 |
+
+#include <cstdlib>
|
| 677 |
+
+#include <cstring>
|
| 678 |
+
+#include <fstream>
|
| 679 |
+
+#include <sstream>
|
| 680 |
+
+#include <string>
|
| 681 |
+
+#include <vector>
|
| 682 |
+
+
|
| 683 |
+
+static double now_s() {
|
| 684 |
+
+ using clk = std::chrono::steady_clock;
|
| 685 |
+
+ return std::chrono::duration<double>(clk::now().time_since_epoch()).count();
|
| 686 |
+
+}
|
| 687 |
+
+
|
| 688 |
+
+static std::vector<std::vector<llama_token>> read_id_lines(const std::string & path) {
|
| 689 |
+
+ std::vector<std::vector<llama_token>> out;
|
| 690 |
+
+ std::ifstream f(path);
|
| 691 |
+
+ if (!f) {
|
| 692 |
+
+ fprintf(stderr, "error: cannot open %s\n", path.c_str());
|
| 693 |
+
+ exit(1);
|
| 694 |
+
+ }
|
| 695 |
+
+ std::string line;
|
| 696 |
+
+ while (std::getline(f, line)) {
|
| 697 |
+
+ if (line.empty()) {
|
| 698 |
+
+ continue;
|
| 699 |
+
+ }
|
| 700 |
+
+ std::vector<llama_token> ids;
|
| 701 |
+
+ std::stringstream ss(line);
|
| 702 |
+
+ std::string tok;
|
| 703 |
+
+ while (std::getline(ss, tok, ',')) {
|
| 704 |
+
+ if (!tok.empty()) {
|
| 705 |
+
+ ids.push_back((llama_token) strtol(tok.c_str(), nullptr, 10));
|
| 706 |
+
+ }
|
| 707 |
+
+ }
|
| 708 |
+
+ if (!ids.empty()) {
|
| 709 |
+
+ out.push_back(std::move(ids));
|
| 710 |
+
+ }
|
| 711 |
+
+ }
|
| 712 |
+
+ return out;
|
| 713 |
+
+}
|
| 714 |
+
+
|
| 715 |
+
+struct step_info {
|
| 716 |
+
+ llama_token top1;
|
| 717 |
+
+ float logit_top1;
|
| 718 |
+
+ llama_token top2;
|
| 719 |
+
+ float logit_top2;
|
| 720 |
+
+};
|
| 721 |
+
+
|
| 722 |
+
+// argmax + runner-up over the last decoded token's logits
|
| 723 |
+
+static step_info argmax2(const float * logits, int n_vocab) {
|
| 724 |
+
+ step_info si = { 0, -1e30f, 0, -1e30f };
|
| 725 |
+
+ for (int i = 0; i < n_vocab; ++i) {
|
| 726 |
+
+ const float v = logits[i];
|
| 727 |
+
+ if (v > si.logit_top1) {
|
| 728 |
+
+ si.top2 = si.top1; si.logit_top2 = si.logit_top1;
|
| 729 |
+
+ si.top1 = i; si.logit_top1 = v;
|
| 730 |
+
+ } else if (v > si.logit_top2) {
|
| 731 |
+
+ si.top2 = i; si.logit_top2 = v;
|
| 732 |
+
+ }
|
| 733 |
+
+ }
|
| 734 |
+
+ return si;
|
| 735 |
+
+}
|
| 736 |
+
+
|
| 737 |
+
+int main(int argc, char ** argv) {
|
| 738 |
+
+ std::string model_path;
|
| 739 |
+
+ std::string prompts_path;
|
| 740 |
+
+ std::string ref_path;
|
| 741 |
+
+ std::string out_path;
|
| 742 |
+
+ int steps = 128;
|
| 743 |
+
+ int n_threads = 8;
|
| 744 |
+
+
|
| 745 |
+
+ for (int i = 1; i < argc; ++i) {
|
| 746 |
+
+ auto need = [&](const char * flag) -> const char * {
|
| 747 |
+
+ if (i + 1 >= argc) { fprintf(stderr, "error: %s needs a value\n", flag); exit(1); }
|
| 748 |
+
+ return argv[++i];
|
| 749 |
+
+ };
|
| 750 |
+
+ if (!strcmp(argv[i], "-m")) { model_path = need("-m"); }
|
| 751 |
+
+ else if (!strcmp(argv[i], "--prompts")) { prompts_path = need("--prompts"); }
|
| 752 |
+
+ else if (!strcmp(argv[i], "--ref")) { ref_path = need("--ref"); }
|
| 753 |
+
+ else if (!strcmp(argv[i], "--out")) { out_path = need("--out"); }
|
| 754 |
+
+ else if (!strcmp(argv[i], "--steps")) { steps = atoi(need("--steps")); }
|
| 755 |
+
+ else if (!strcmp(argv[i], "-t")) { n_threads = atoi(need("-t")); }
|
| 756 |
+
+ else { fprintf(stderr, "error: unknown arg %s\n", argv[i]); return 1; }
|
| 757 |
+
+ }
|
| 758 |
+
+ if (model_path.empty() || prompts_path.empty()) {
|
| 759 |
+
+ fprintf(stderr, "usage: %s -m model.gguf --prompts ids.txt [--ref ids.txt] [--steps N] [-t N] [--out f.json]\n", argv[0]);
|
| 760 |
+
+ return 1;
|
| 761 |
+
+ }
|
| 762 |
+
+
|
| 763 |
+
+ const auto prompts = read_id_lines(prompts_path);
|
| 764 |
+
+ std::vector<std::vector<llama_token>> refs;
|
| 765 |
+
+ if (!ref_path.empty()) {
|
| 766 |
+
+ refs = read_id_lines(ref_path);
|
| 767 |
+
+ if (refs.size() != prompts.size()) {
|
| 768 |
+
+ fprintf(stderr, "error: %zu prompts but %zu reference lines\n", prompts.size(), refs.size());
|
| 769 |
+
+ return 1;
|
| 770 |
+
+ }
|
| 771 |
+
+ }
|
| 772 |
+
+
|
| 773 |
+
+ size_t max_prompt = 0;
|
| 774 |
+
+ for (const auto & p : prompts) {
|
| 775 |
+
+ max_prompt = p.size() > max_prompt ? p.size() : max_prompt;
|
| 776 |
+
+ }
|
| 777 |
+
+
|
| 778 |
+
+ ggml_backend_load_all();
|
| 779 |
+
+
|
| 780 |
+
+ llama_model_params mparams = llama_model_default_params();
|
| 781 |
+
+ mparams.n_gpu_layers = 0;
|
| 782 |
+
+ const double t_load0 = now_s();
|
| 783 |
+
+ llama_model * model = llama_model_load_from_file(model_path.c_str(), mparams);
|
| 784 |
+
+ if (model == nullptr) {
|
| 785 |
+
+ fprintf(stderr, "error: failed to load %s\n", model_path.c_str());
|
| 786 |
+
+ return 1;
|
| 787 |
+
+ }
|
| 788 |
+
+ const double t_load = now_s() - t_load0;
|
| 789 |
+
+
|
| 790 |
+
+ const llama_vocab * vocab = llama_model_get_vocab(model);
|
| 791 |
+
+ const int n_vocab = llama_vocab_n_tokens(vocab);
|
| 792 |
+
+
|
| 793 |
+
+ llama_context_params cparams = llama_context_default_params();
|
| 794 |
+
+ cparams.n_ctx = (uint32_t) (max_prompt + steps + 8);
|
| 795 |
+
+ cparams.n_batch = (uint32_t) (max_prompt + 8);
|
| 796 |
+
+ cparams.n_threads = n_threads;
|
| 797 |
+
+ cparams.n_threads_batch = n_threads;
|
| 798 |
+
+ cparams.type_k = GGML_TYPE_F32; // keep KV in f32: gate compares against an f32 reference
|
| 799 |
+
+ cparams.type_v = GGML_TYPE_F32;
|
| 800 |
+
+ cparams.flash_attn_type = LLAMA_FLASH_ATTN_TYPE_DISABLED; // conservative numerics for the gate
|
| 801 |
+
+ cparams.no_perf = true;
|
| 802 |
+
+
|
| 803 |
+
+ llama_context * ctx = llama_init_from_model(model, cparams);
|
| 804 |
+
+ if (ctx == nullptr) {
|
| 805 |
+
+ fprintf(stderr, "error: failed to create context\n");
|
| 806 |
+
+ return 1;
|
| 807 |
+
+ }
|
| 808 |
+
+
|
| 809 |
+
+ FILE * out = stdout;
|
| 810 |
+
+ if (!out_path.empty()) {
|
| 811 |
+
+ out = fopen(out_path.c_str(), "w");
|
| 812 |
+
+ if (!out) {
|
| 813 |
+
+ fprintf(stderr, "error: cannot write %s\n", out_path.c_str());
|
| 814 |
+
+ return 1;
|
| 815 |
+
+ }
|
| 816 |
+
+ }
|
| 817 |
+
+
|
| 818 |
+
+ fprintf(out, "{\n \"model\": \"%s\",\n \"steps\": %d,\n \"n_threads\": %d,\n \"load_seconds\": %.2f,\n \"kv_type\": \"f32\",\n \"prompts\": [\n",
|
| 819 |
+
+ model_path.c_str(), steps, n_threads, t_load);
|
| 820 |
+
+
|
| 821 |
+
+ double total_gen_s = 0.0;
|
| 822 |
+
+ double total_prompt_s = 0.0;
|
| 823 |
+
+ int64_t total_gen_tok = 0;
|
| 824 |
+
+ int64_t total_prompt_tok = 0;
|
| 825 |
+
+ int total_forced_mismatch = 0;
|
| 826 |
+
+ bool all_free_match = true;
|
| 827 |
+
+
|
| 828 |
+
+ for (size_t pi = 0; pi < prompts.size(); ++pi) {
|
| 829 |
+
+ std::vector<llama_token> prompt = prompts[pi];
|
| 830 |
+
+
|
| 831 |
+
+ // ---------------- free-running greedy pass ----------------
|
| 832 |
+
+ llama_memory_clear(llama_get_memory(ctx), true);
|
| 833 |
+
+
|
| 834 |
+
+ double t0 = now_s();
|
| 835 |
+
+ llama_batch batch = llama_batch_get_one(prompt.data(), (int32_t) prompt.size());
|
| 836 |
+
+ if (llama_decode(ctx, batch) != 0) {
|
| 837 |
+
+ fprintf(stderr, "error: prompt decode failed (prompt %zu)\n", pi);
|
| 838 |
+
+ return 1;
|
| 839 |
+
+ }
|
| 840 |
+
+ const double prompt_s = now_s() - t0;
|
| 841 |
+
+
|
| 842 |
+
+ std::vector<llama_token> free_stream;
|
| 843 |
+
+ float min_margin_free = 1e30f;
|
| 844 |
+
+ t0 = now_s();
|
| 845 |
+
+ for (int t = 0; t < steps; ++t) {
|
| 846 |
+
+ const float * logits = llama_get_logits_ith(ctx, -1);
|
| 847 |
+
+ step_info si = argmax2(logits, n_vocab);
|
| 848 |
+
+ const float margin = si.logit_top1 - si.logit_top2;
|
| 849 |
+
+ if (margin < min_margin_free) {
|
| 850 |
+
+ min_margin_free = margin;
|
| 851 |
+
+ }
|
| 852 |
+
+ free_stream.push_back(si.top1);
|
| 853 |
+
+ if (t + 1 < steps) {
|
| 854 |
+
+ llama_token next = si.top1;
|
| 855 |
+
+ batch = llama_batch_get_one(&next, 1);
|
| 856 |
+
+ if (llama_decode(ctx, batch) != 0) {
|
| 857 |
+
+ fprintf(stderr, "error: decode failed (prompt %zu step %d)\n", pi, t);
|
| 858 |
+
+ return 1;
|
| 859 |
+
+ }
|
| 860 |
+
+ }
|
| 861 |
+
+ }
|
| 862 |
+
+ const double gen_s = now_s() - t0;
|
| 863 |
+
+
|
| 864 |
+
+ total_prompt_s += prompt_s;
|
| 865 |
+
+ total_gen_s += gen_s;
|
| 866 |
+
+ total_prompt_tok += (int64_t) prompt.size();
|
| 867 |
+
+ total_gen_tok += steps;
|
| 868 |
+
+
|
| 869 |
+
+ fprintf(out, " {\n \"prompt_ids\": [");
|
| 870 |
+
+ for (size_t i = 0; i < prompt.size(); ++i) {
|
| 871 |
+
+ fprintf(out, "%s%d", i ? "," : "", prompt[i]);
|
| 872 |
+
+ }
|
| 873 |
+
+ fprintf(out, "],\n \"free_ids\": [");
|
| 874 |
+
+ for (size_t i = 0; i < free_stream.size(); ++i) {
|
| 875 |
+
+ fprintf(out, "%s%d", i ? "," : "", free_stream[i]);
|
| 876 |
+
+ }
|
| 877 |
+
+ fprintf(out, "],\n \"free_min_top2_margin\": %.6f,\n", min_margin_free);
|
| 878 |
+
+ fprintf(out, " \"prompt_seconds\": %.3f,\n \"gen_seconds\": %.3f", prompt_s, gen_s);
|
| 879 |
+
+
|
| 880 |
+
+ if (!refs.empty()) {
|
| 881 |
+
+ const std::vector<llama_token> & ref = refs[pi];
|
| 882 |
+
+ const int n_ref = (int) ref.size() < steps ? (int) ref.size() : steps;
|
| 883 |
+
+
|
| 884 |
+
+ // free-run vs reference (first divergence)
|
| 885 |
+
+ int first_div = -1;
|
| 886 |
+
+ for (int t = 0; t < n_ref; ++t) {
|
| 887 |
+
+ if (free_stream[t] != ref[t]) { first_div = t; break; }
|
| 888 |
+
+ }
|
| 889 |
+
+ const bool free_match = first_div < 0;
|
| 890 |
+
+ all_free_match = all_free_match && free_match;
|
| 891 |
+
+
|
| 892 |
+
+ // ---------------- teacher-forced pass ----------------
|
| 893 |
+
+ llama_memory_clear(llama_get_memory(ctx), true);
|
| 894 |
+
+ batch = llama_batch_get_one(prompt.data(), (int32_t) prompt.size());
|
| 895 |
+
+ if (llama_decode(ctx, batch) != 0) {
|
| 896 |
+
+ fprintf(stderr, "error: forced prompt decode failed (prompt %zu)\n", pi);
|
| 897 |
+
+ return 1;
|
| 898 |
+
+ }
|
| 899 |
+
+
|
| 900 |
+
+ int n_mismatch = 0;
|
| 901 |
+
+ std::string mm_json;
|
| 902 |
+
+ char buf[256];
|
| 903 |
+
+ for (int t = 0; t < n_ref; ++t) {
|
| 904 |
+
+ const float * logits = llama_get_logits_ith(ctx, -1);
|
| 905 |
+
+ step_info si = argmax2(logits, n_vocab);
|
| 906 |
+
+ if (si.top1 != ref[t]) {
|
| 907 |
+
+ const float logit_ref = logits[ref[t]];
|
| 908 |
+
+ snprintf(buf, sizeof(buf),
|
| 909 |
+
+ "%s\n {\"step\": %d, \"got\": %d, \"want\": %d, \"logit_got\": %.6f, \"logit_want\": %.6f, \"margin\": %.6f}",
|
| 910 |
+
+ n_mismatch ? "," : "", t, si.top1, ref[t], si.logit_top1, logit_ref, si.logit_top1 - logit_ref);
|
| 911 |
+
+ mm_json += buf;
|
| 912 |
+
+ n_mismatch++;
|
| 913 |
+
+ }
|
| 914 |
+
+ llama_token next = ref[t]; // teacher-force the reference stream
|
| 915 |
+
+ if (t + 1 < n_ref) {
|
| 916 |
+
+ batch = llama_batch_get_one(&next, 1);
|
| 917 |
+
+ if (llama_decode(ctx, batch) != 0) {
|
| 918 |
+
+ fprintf(stderr, "error: forced decode failed (prompt %zu step %d)\n", pi, t);
|
| 919 |
+
+ return 1;
|
| 920 |
+
+ }
|
| 921 |
+
+ }
|
| 922 |
+
+ }
|
| 923 |
+
+ total_forced_mismatch += n_mismatch;
|
| 924 |
+
+
|
| 925 |
+
+ fprintf(out, ",\n \"free_match\": %s,\n \"first_divergence\": %d,\n \"forced_mismatch_count\": %d,\n \"forced_mismatches\": [%s%s]",
|
| 926 |
+
+ free_match ? "true" : "false", first_div, n_mismatch,
|
| 927 |
+
+ mm_json.c_str(), n_mismatch ? "\n " : "");
|
| 928 |
+
+ }
|
| 929 |
+
+
|
| 930 |
+
+ fprintf(out, "\n }%s\n", pi + 1 < prompts.size() ? "," : "");
|
| 931 |
+
+ fflush(out);
|
| 932 |
+
+ fprintf(stderr, "prompt %zu/%zu done (%.1f tok/s gen)\n", pi + 1, prompts.size(), steps / gen_s);
|
| 933 |
+
+ }
|
| 934 |
+
+
|
| 935 |
+
+ fprintf(out, " ],\n \"totals\": {\n");
|
| 936 |
+
+ fprintf(out, " \"prompt_tokens\": %" PRId64 ",\n \"gen_tokens\": %" PRId64 ",\n", total_prompt_tok, total_gen_tok);
|
| 937 |
+
+ fprintf(out, " \"prompt_tok_per_s\": %.2f,\n \"gen_tok_per_s\": %.2f,\n",
|
| 938 |
+
+ total_prompt_tok / (total_prompt_s > 0 ? total_prompt_s : 1e-9),
|
| 939 |
+
+ total_gen_tok / (total_gen_s > 0 ? total_gen_s : 1e-9));
|
| 940 |
+
+ fprintf(out, " \"forced_mismatch_total\": %d,\n \"all_free_match\": %s\n }\n}\n",
|
| 941 |
+
+ total_forced_mismatch, (refs.empty() || !all_free_match) ? (refs.empty() ? "null" : "false") : "true");
|
| 942 |
+
+
|
| 943 |
+
+ if (out != stdout) {
|
| 944 |
+
+ fclose(out);
|
| 945 |
+
+ }
|
| 946 |
+
+ llama_free(ctx);
|
| 947 |
+
+ llama_model_free(model);
|
| 948 |
+
+ return 0;
|
| 949 |
+
+}
|
| 950 |
+
--
|
| 951 |
+
2.50.1 (Apple Git-155)
|
| 952 |
+
|
gguf/neutrino-0.6b-base-fv5.gguf
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:dd9354c4b3cd2b34195b0270540e4fa5db3811e42b6e4bdbd57958e0027e9272
|
| 3 |
+
size 343140320
|
gguf/receipts/bench_0p6b.json
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model": "0p6b",
|
| 3 |
+
"bench": [
|
| 4 |
+
{
|
| 5 |
+
"build_commit": "e07b3ec",
|
| 6 |
+
"build_number": 2,
|
| 7 |
+
"cpu_info": "unknown",
|
| 8 |
+
"gpu_info": "",
|
| 9 |
+
"backends": "CPU",
|
| 10 |
+
"model_filename": "/vol/gguf/neutrino-0.6b-base-fv5.gguf",
|
| 11 |
+
"model_type": "qwen3 0.6B FV5 - 3.25 bpw Fermion five-value ternary",
|
| 12 |
+
"model_size": 337188864,
|
| 13 |
+
"model_n_params": 596049920,
|
| 14 |
+
"n_batch": 2048,
|
| 15 |
+
"n_ubatch": 512,
|
| 16 |
+
"n_threads": 16,
|
| 17 |
+
"cpu_mask": "0x0",
|
| 18 |
+
"cpu_strict": false,
|
| 19 |
+
"poll": 50,
|
| 20 |
+
"type_k": "f16",
|
| 21 |
+
"type_v": "f16",
|
| 22 |
+
"n_gpu_layers": -1,
|
| 23 |
+
"n_cpu_moe": 0,
|
| 24 |
+
"split_mode": "layer",
|
| 25 |
+
"main_gpu": 0,
|
| 26 |
+
"no_kv_offload": false,
|
| 27 |
+
"flash_attn": -1,
|
| 28 |
+
"devices": "auto",
|
| 29 |
+
"tensor_split": "0.00",
|
| 30 |
+
"tensor_buft_overrides": "none",
|
| 31 |
+
"load_mode": "mmap",
|
| 32 |
+
"embeddings": false,
|
| 33 |
+
"no_op_offload": 0,
|
| 34 |
+
"no_host": false,
|
| 35 |
+
"fit_target": 0,
|
| 36 |
+
"fit_min_ctx": 0,
|
| 37 |
+
"n_prompt": 512,
|
| 38 |
+
"n_gen": 0,
|
| 39 |
+
"n_depth": 0,
|
| 40 |
+
"test_time": "2026-07-25T10:23:15Z",
|
| 41 |
+
"avg_ns": 3873491459,
|
| 42 |
+
"stddev_ns": 672837,
|
| 43 |
+
"avg_ts": 132.180492,
|
| 44 |
+
"stddev_ts": 0.02296,
|
| 45 |
+
"samples_ns": [
|
| 46 |
+
3873015691,
|
| 47 |
+
3873967227
|
| 48 |
+
],
|
| 49 |
+
"samples_ts": [
|
| 50 |
+
132.197,
|
| 51 |
+
132.164
|
| 52 |
+
]
|
| 53 |
+
},
|
| 54 |
+
{
|
| 55 |
+
"build_commit": "e07b3ec",
|
| 56 |
+
"build_number": 2,
|
| 57 |
+
"cpu_info": "unknown",
|
| 58 |
+
"gpu_info": "",
|
| 59 |
+
"backends": "CPU",
|
| 60 |
+
"model_filename": "/vol/gguf/neutrino-0.6b-base-fv5.gguf",
|
| 61 |
+
"model_type": "qwen3 0.6B FV5 - 3.25 bpw Fermion five-value ternary",
|
| 62 |
+
"model_size": 337188864,
|
| 63 |
+
"model_n_params": 596049920,
|
| 64 |
+
"n_batch": 2048,
|
| 65 |
+
"n_ubatch": 512,
|
| 66 |
+
"n_threads": 16,
|
| 67 |
+
"cpu_mask": "0x0",
|
| 68 |
+
"cpu_strict": false,
|
| 69 |
+
"poll": 50,
|
| 70 |
+
"type_k": "f16",
|
| 71 |
+
"type_v": "f16",
|
| 72 |
+
"n_gpu_layers": -1,
|
| 73 |
+
"n_cpu_moe": 0,
|
| 74 |
+
"split_mode": "layer",
|
| 75 |
+
"main_gpu": 0,
|
| 76 |
+
"no_kv_offload": false,
|
| 77 |
+
"flash_attn": -1,
|
| 78 |
+
"devices": "auto",
|
| 79 |
+
"tensor_split": "0.00",
|
| 80 |
+
"tensor_buft_overrides": "none",
|
| 81 |
+
"load_mode": "mmap",
|
| 82 |
+
"embeddings": false,
|
| 83 |
+
"no_op_offload": 0,
|
| 84 |
+
"no_host": false,
|
| 85 |
+
"fit_target": 0,
|
| 86 |
+
"fit_min_ctx": 0,
|
| 87 |
+
"n_prompt": 0,
|
| 88 |
+
"n_gen": 128,
|
| 89 |
+
"n_depth": 0,
|
| 90 |
+
"test_time": "2026-07-25T10:23:27Z",
|
| 91 |
+
"avg_ns": 2311377861,
|
| 92 |
+
"stddev_ns": 12562356,
|
| 93 |
+
"avg_ts": 55.379041,
|
| 94 |
+
"stddev_ts": 0.300981,
|
| 95 |
+
"samples_ns": [
|
| 96 |
+
2320260659,
|
| 97 |
+
2302495064
|
| 98 |
+
],
|
| 99 |
+
"samples_ts": [
|
| 100 |
+
55.1662,
|
| 101 |
+
55.5919
|
| 102 |
+
]
|
| 103 |
+
}
|
| 104 |
+
],
|
| 105 |
+
"smoke_rc": 0,
|
| 106 |
+
"smoke_text": "located in the capital of the country. The capital of France is located in the capital of the country. The capital of France is located in the capital of the country. The capital of France is located in the capital of the country. The",
|
| 107 |
+
"threads": 16,
|
| 108 |
+
"cpu": "unknown",
|
| 109 |
+
"nproc": 32,
|
| 110 |
+
"wall_s": 20.4,
|
| 111 |
+
"ts": "2026-07-25T10:23:34Z"
|
| 112 |
+
}
|
gguf/receipts/convert_0p6b.json
ADDED
|
@@ -0,0 +1,44 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"status": "CONVERT_PASS",
|
| 3 |
+
"container": {
|
| 4 |
+
"name": "neutrino-0p6b_v4_base.bin",
|
| 5 |
+
"sha256": "91c0b26d105710e755ef1ec1b5607c00da5d5cbd048fd5584135fb6fc1e19399"
|
| 6 |
+
},
|
| 7 |
+
"f32_gguf": {
|
| 8 |
+
"name": "neutrino-0.6b-base-f32.gguf",
|
| 9 |
+
"bytes": 3012480800
|
| 10 |
+
},
|
| 11 |
+
"out": {
|
| 12 |
+
"name": "neutrino-0.6b-base-fv5.gguf",
|
| 13 |
+
"bytes": 343140320,
|
| 14 |
+
"sha256": "dd9354c4b3cd2b34195b0270540e4fa5db3811e42b6e4bdbd57958e0027e9272"
|
| 15 |
+
},
|
| 16 |
+
"tensors": {
|
| 17 |
+
"fv5": 196,
|
| 18 |
+
"fv5b": 1,
|
| 19 |
+
"f32": 113,
|
| 20 |
+
"total": 310,
|
| 21 |
+
"bitwise_checked": 310
|
| 22 |
+
},
|
| 23 |
+
"tied_output_omitted": true,
|
| 24 |
+
"quantized_params": 595984384,
|
| 25 |
+
"effective_bpw_quantized": 4.606,
|
| 26 |
+
"header": {
|
| 27 |
+
"magic": 1129599572,
|
| 28 |
+
"version": 4,
|
| 29 |
+
"arch": 3,
|
| 30 |
+
"n_layers": 28,
|
| 31 |
+
"hidden": 1024,
|
| 32 |
+
"n_heads": 16,
|
| 33 |
+
"n_kv_heads": 8,
|
| 34 |
+
"head_dim": 128,
|
| 35 |
+
"rotary_dims": 128,
|
| 36 |
+
"intermediate": 3072,
|
| 37 |
+
"vocab": 151936,
|
| 38 |
+
"max_pos": 40960,
|
| 39 |
+
"rms_norm_eps": 9.999999974752427e-07,
|
| 40 |
+
"rope_theta": 1000000.0,
|
| 41 |
+
"eok": 1
|
| 42 |
+
},
|
| 43 |
+
"wall_seconds": 14.6
|
| 44 |
+
}
|
gguf/receipts/gv_0p6b_f32.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model": "0p6b",
|
| 3 |
+
"variant": "f32",
|
| 4 |
+
"verdict": "PASS",
|
| 5 |
+
"all_free_match": true,
|
| 6 |
+
"forced_mismatch_total": 0,
|
| 7 |
+
"gen_tok_per_s": 35.04,
|
| 8 |
+
"prompt_tok_per_s": 140.06,
|
| 9 |
+
"flips": [],
|
| 10 |
+
"unexplained": 0,
|
| 11 |
+
"gguf_sha256": "3f3e8864c18f450240bab0498fac871a64dcc3eed72bfa97f35cbf33881db3e5",
|
| 12 |
+
"ref_trajectory_sha256": "685bd94b1f77b041ce7836bb2d45042b142799b824837bcc420271494a4fabdd",
|
| 13 |
+
"threads": 16,
|
| 14 |
+
"cpu": "unknown",
|
| 15 |
+
"wall_s": 63.0,
|
| 16 |
+
"ts": "2026-07-25T10:23:13Z"
|
| 17 |
+
}
|
gguf/receipts/gv_0p6b_fv5.json
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model": "0p6b",
|
| 3 |
+
"variant": "fv5",
|
| 4 |
+
"verdict": "PASS",
|
| 5 |
+
"all_free_match": true,
|
| 6 |
+
"forced_mismatch_total": 0,
|
| 7 |
+
"gen_tok_per_s": 51.63,
|
| 8 |
+
"prompt_tok_per_s": 92.96,
|
| 9 |
+
"flips": [],
|
| 10 |
+
"unexplained": 0,
|
| 11 |
+
"gguf_sha256": "dd9354c4b3cd2b34195b0270540e4fa5db3811e42b6e4bdbd57958e0027e9272",
|
| 12 |
+
"ref_trajectory_sha256": "685bd94b1f77b041ce7836bb2d45042b142799b824837bcc420271494a4fabdd",
|
| 13 |
+
"threads": 16,
|
| 14 |
+
"cpu": "unknown",
|
| 15 |
+
"wall_s": 43.7,
|
| 16 |
+
"ts": "2026-07-25T10:22:09Z"
|
| 17 |
+
}
|
gguf/receipts/ref_0p6b.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"model": "0p6b",
|
| 3 |
+
"dtype": "float32",
|
| 4 |
+
"steps": 128,
|
| 5 |
+
"trajectory_sha256": "685bd94b1f77b041ce7836bb2d45042b142799b824837bcc420271494a4fabdd",
|
| 6 |
+
"continuity_vs_banked_fp16": [
|
| 7 |
+
{
|
| 8 |
+
"prompt": 0,
|
| 9 |
+
"fp16_match": true,
|
| 10 |
+
"first_divergence": -1
|
| 11 |
+
},
|
| 12 |
+
{
|
| 13 |
+
"prompt": 1,
|
| 14 |
+
"fp16_match": true,
|
| 15 |
+
"first_divergence": -1
|
| 16 |
+
},
|
| 17 |
+
{
|
| 18 |
+
"prompt": 2,
|
| 19 |
+
"fp16_match": true,
|
| 20 |
+
"first_divergence": -1
|
| 21 |
+
}
|
| 22 |
+
],
|
| 23 |
+
"wall_s": 212.4,
|
| 24 |
+
"cpu": "unknown",
|
| 25 |
+
"ts": "2026-07-25T10:16:30Z"
|
| 26 |
+
}
|
merges.txt
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
mlx/MANIFEST.json
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"note": "MLX pack provenance: file -> bytes/sha256/source. Weights entry is declarative (not duplicated in this subfolder).",
|
| 3 |
+
"generated_utc": "2026-07-25T16:52:04Z",
|
| 4 |
+
"weights_container": {
|
| 5 |
+
"file": "neutrino-0.6b_v4.bin",
|
| 6 |
+
"status": "NOT-IN-SUBFOLDER; parent card materialization applies",
|
| 7 |
+
"bytes": 327719836,
|
| 8 |
+
"sha256": "91c0b26d105710e755ef1ec1b5607c00da5d5cbd048fd5584135fb6fc1e19399",
|
| 9 |
+
"source": "Fermion Research release archive",
|
| 10 |
+
"gated_on": "this exact container (V1 refgate + cachegate + pathgate receipts in receipts/)"
|
| 11 |
+
},
|
| 12 |
+
"files": {
|
| 13 |
+
"README.md": {
|
| 14 |
+
"bytes": 5392,
|
| 15 |
+
"sha256": "c32d9fa4f7dbbc53ecdad2131c1e33596e57f6045acc8f0790a9d2431f014b11",
|
| 16 |
+
"source": "pack file"
|
| 17 |
+
},
|
| 18 |
+
"fermion_mlx/__init__.py": {
|
| 19 |
+
"bytes": 5063,
|
| 20 |
+
"sha256": "a547844d587067681639df546e6d010dab80bbd4f0ee55c6e2a16fba97a1dd40",
|
| 21 |
+
"source": "NEW: load()/generate()/chat() API"
|
| 22 |
+
},
|
| 23 |
+
"fermion_mlx/__main__.py": {
|
| 24 |
+
"bytes": 8069,
|
| 25 |
+
"sha256": "71e8c7fea446184bd471af2c37e51b04b9fa8d0f5d35e0a76c31f6fd19af957d",
|
| 26 |
+
"source": "NEW: CLI (gen/chat/gate/cachegate/pathgate/bench) + spec/specgate/specbench modes"
|
| 27 |
+
},
|
| 28 |
+
"fermion_mlx/arch3.py": {
|
| 29 |
+
"bytes": 7541,
|
| 30 |
+
"sha256": "a831e8a24b2b9a986b393d1c316aac66a3399a082a57176254b2feeae032f8cd",
|
| 31 |
+
"source": "NEW (2026-07-25): fused per-head QK-RMSNorm+rope+KV-append kernel + s8 v3 x-staged int8 unembed"
|
| 32 |
+
},
|
| 33 |
+
"fermion_mlx/container.py": {
|
| 34 |
+
"bytes": 6059,
|
| 35 |
+
"sha256": "c5b352184ba9ddf9a3e424731226cb7dcb9901d3a1516c67d4557953b0101f2f",
|
| 36 |
+
"source": "NEW: TRTC v4 arch-3 reader"
|
| 37 |
+
},
|
| 38 |
+
"fermion_mlx/gates.py": {
|
| 39 |
+
"bytes": 7394,
|
| 40 |
+
"sha256": "64d07d9ba1a783a3f56ba978da7885e9fbe693495b3a4b7b8e176607d59de52c",
|
| 41 |
+
"source": "NEW: house V1 refgate / cachegate / pathgate"
|
| 42 |
+
},
|
| 43 |
+
"fermion_mlx/mbatch.py": {
|
| 44 |
+
"bytes": 6492,
|
| 45 |
+
"sha256": "c9ec8d37f1cdc0554cda01cefbc435e67525779c79e0dac7fd760e7919e6fdd2",
|
| 46 |
+
"source": "NEW (2026-07-25): M-token batched trit/s8 GEMMs for speculative verify (one weight read per k+1 positions)"
|
| 47 |
+
},
|
| 48 |
+
"fermion_mlx/model.py": {
|
| 49 |
+
"bytes": 12748,
|
| 50 |
+
"sha256": "9488f8194ce8be006ce05dfaf6118353de44005ce54f38f73a0908ccac2749da",
|
| 51 |
+
"source": "NEW: MetalTritQwen3 walker (fused primary path + mx.fast-ops reference twin)"
|
| 52 |
+
},
|
| 53 |
+
"fermion_mlx/spec.py": {
|
| 54 |
+
"bytes": 11207,
|
| 55 |
+
"sha256": "969e946d5d2eeb63d44afa4c62dd2453d87fa14e83b936bc673482b59ee3b745",
|
| 56 |
+
"source": "NEW (2026-07-25): EXPERIMENTAL greedy-exact speculative decoding (0.6B drafts, 8B verifies), exactness-gated"
|
| 57 |
+
},
|
| 58 |
+
"fermion_mlx/trit_kernels.py": {
|
| 59 |
+
"bytes": 44135,
|
| 60 |
+
"sha256": "aeb8bd4b03419763c1543893f19be3b4029611b2df883096990c4beebade384b",
|
| 61 |
+
"source": "the banked five-value Metal GEMV stack"
|
| 62 |
+
},
|
| 63 |
+
"generation_config.json": {
|
| 64 |
+
"bytes": 181,
|
| 65 |
+
"sha256": "6ca52b0bcb818c9e52db8ea18413494110c5f2def581b769f621dda951a7863b",
|
| 66 |
+
"source": "donor Qwen/Qwen3-0.6B @ c1899de289a04d12100db370d81485cdf75e47ca (pinned revision from export receipts / greedy cache)"
|
| 67 |
+
},
|
| 68 |
+
"merges.txt": {
|
| 69 |
+
"bytes": 1671853,
|
| 70 |
+
"sha256": "8831e4f1a044471340f7c0a83d7bd71306a5b867e95fd870f74d0c5308a904d5",
|
| 71 |
+
"source": "donor Qwen/Qwen3-0.6B @ c1899de289a04d12100db370d81485cdf75e47ca (pinned revision from export receipts / greedy cache)"
|
| 72 |
+
},
|
| 73 |
+
"receipts/bench_0p6b_m5.json": {
|
| 74 |
+
"bytes": 812,
|
| 75 |
+
"sha256": "543a9e41d8374a8cd066643a4e7cfa925359a8efc9c3182cbebb366bb0afbe89",
|
| 76 |
+
"source": "gate/bench receipt (2026-07-25)"
|
| 77 |
+
},
|
| 78 |
+
"receipts/cachegate_0p6b.json": {
|
| 79 |
+
"bytes": 775,
|
| 80 |
+
"sha256": "b6acd64a31f4529ea01b96103a44b5bc54da283a7c3250cd564b5510ebaabc8c",
|
| 81 |
+
"source": "gate/bench receipt (2026-07-25)"
|
| 82 |
+
},
|
| 83 |
+
"receipts/kernel_version_sweep_0p6b.json": {
|
| 84 |
+
"bytes": 355,
|
| 85 |
+
"sha256": "6b0df2026ded00c50d6b34004bf2dc4aabf097d21decfad7a354de5be594fe87",
|
| 86 |
+
"source": "gate/bench receipt (2026-07-25)"
|
| 87 |
+
},
|
| 88 |
+
"receipts/pathgate_0p6b.json": {
|
| 89 |
+
"bytes": 623,
|
| 90 |
+
"sha256": "2453e15509a012616a100e3767a373af20a763dff89104e3c4dfbeb24085d072",
|
| 91 |
+
"source": "gate/bench receipt (2026-07-25)"
|
| 92 |
+
},
|
| 93 |
+
"receipts/refgate_0p6b.json": {
|
| 94 |
+
"bytes": 1907,
|
| 95 |
+
"sha256": "ee4b3949bad85a522f1d1b65344a230a40f9fe9dc5e5dfdca736c28751eb4d85",
|
| 96 |
+
"source": "gate/bench receipt (2026-07-25)"
|
| 97 |
+
},
|
| 98 |
+
"requirements.txt": {
|
| 99 |
+
"bytes": 191,
|
| 100 |
+
"sha256": "1cf4d1fa581aaa5deae0568f88388c51166bbd69e7a89080b5a53488a7ef39b6",
|
| 101 |
+
"source": "pack file"
|
| 102 |
+
},
|
| 103 |
+
"tokenizer.json": {
|
| 104 |
+
"bytes": 11422654,
|
| 105 |
+
"sha256": "aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4",
|
| 106 |
+
"source": "donor Qwen/Qwen3-0.6B @ c1899de289a04d12100db370d81485cdf75e47ca (pinned revision from export receipts / greedy cache)"
|
| 107 |
+
},
|
| 108 |
+
"tokenizer_config.json": {
|
| 109 |
+
"bytes": 9732,
|
| 110 |
+
"sha256": "d5d09f07b48c3086c508b30d1c9114bd1189145b74e982a265350c923acd8101",
|
| 111 |
+
"source": "donor Qwen/Qwen3-0.6B @ c1899de289a04d12100db370d81485cdf75e47ca (pinned revision from export receipts / greedy cache)"
|
| 112 |
+
},
|
| 113 |
+
"vocab.json": {
|
| 114 |
+
"bytes": 2776833,
|
| 115 |
+
"sha256": "ca10d7e9fb3ed18575dd1e277a2579c16d108e32f27439684afa0e10b1440910",
|
| 116 |
+
"source": "donor Qwen/Qwen3-0.6B @ c1899de289a04d12100db370d81485cdf75e47ca (pinned revision from export receipts / greedy cache)"
|
| 117 |
+
}
|
| 118 |
+
},
|
| 119 |
+
"addenda": [
|
| 120 |
+
"2026-07-25: EXPERIMENTAL --spec speculative decoding added (new modules mbatch.py/spec.py + CLI modes + README section). Exactness-gated: specgate 6/6 token-identical vs plain (receipts). Existing gate battery re-verified on the updated package."
|
| 121 |
+
]
|
| 122 |
+
}
|
mlx/README.md
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Neutrino-0.6B — MLX pack (Apple silicon)
|
| 2 |
+
|
| 3 |
+
> [!IMPORTANT]
|
| 4 |
+
> **Neutrino-0.6B exists as the speculative-decoding DRAFT for
|
| 5 |
+
> Neutrino-8B** — that pairing is its supported purpose. It is a BASE
|
| 6 |
+
> model: not an assistant, no chat/instruction/tool claims. For a small
|
| 7 |
+
> conversational model use Neutrino-0.6B-Chat.
|
| 8 |
+
|
| 9 |
+
Run the [Neutrino-0.6B](../README.md) five-value (sub-2-bit) container
|
| 10 |
+
natively from Python on Apple silicon: MLX + our custom Metal kernels. The
|
| 11 |
+
weights stay bit-packed at rest — the five-value {0, ±s_lo, ±s_hi} planes
|
| 12 |
+
are decoded inside the GEMV kernels; nothing in the decode path is
|
| 13 |
+
fp16/fp32 weight material.
|
| 14 |
+
|
| 15 |
+
This pack is the **Python/MLX lane**. The fastest way to run this model on
|
| 16 |
+
a Mac remains the `fermion-run` CPU binary shipped in the Neutrino-8B
|
| 17 |
+
repo's `bin/` (225–236 tok/s on M5, 9 threads); this pack is for MLX users
|
| 18 |
+
who want the model inside a Python process (notebooks, pipelines, mlx
|
| 19 |
+
ecosystems) with certified-faithful outputs.
|
| 20 |
+
|
| 21 |
+
## Install & run
|
| 22 |
+
|
| 23 |
+
```bash
|
| 24 |
+
pip install -r requirements.txt # mlx, numpy, transformers
|
| 25 |
+
# fetch neutrino-0.6b_v4.bin per the parent card (sha256 must match)
|
| 26 |
+
python -m fermion_mlx --model neutrino-0.6b_v4.bin --mode gen \
|
| 27 |
+
--tokenizer . --prompt "The key idea behind ternary neural networks is"
|
| 28 |
+
```
|
| 29 |
+
|
| 30 |
+
Python API:
|
| 31 |
+
|
| 32 |
+
```python
|
| 33 |
+
import fermion_mlx
|
| 34 |
+
from transformers import AutoTokenizer
|
| 35 |
+
|
| 36 |
+
model = fermion_mlx.load("neutrino-0.6b_v4.bin") # memory-maps the container
|
| 37 |
+
tok = AutoTokenizer.from_pretrained(".") # tokenizer files ship here
|
| 38 |
+
text, stats = fermion_mlx.generate(model, tok, "2, 3, 5, 7, 11,",
|
| 39 |
+
max_tokens=64)
|
| 40 |
+
print(text, stats) # {'tok_s': ..., ...}
|
| 41 |
+
```
|
| 42 |
+
|
| 43 |
+
This is a BASE model: prompt it as a completion engine (see the parent
|
| 44 |
+
card's honest-numbers table before using it for anything capability-heavy).
|
| 45 |
+
|
| 46 |
+
## What runs on the GPU
|
| 47 |
+
|
| 48 |
+
- 3-bit-planar five-value GEMV (`trit_kernels.py`, kernel v7: threadgroup
|
| 49 |
+
LUT decode, interleaved 8-row plane layout) for qkv / o / gate+up / down,
|
| 50 |
+
with RMSNorm, SwiGLU and residual adds fused into the GEMV epilogues.
|
| 51 |
+
- The arch-3 (Qwen3) delta — **per-head Q/K-RMSNorm + NeoX rope + KV-cache
|
| 52 |
+
append** — fused into one dispatch (`arch3.py`).
|
| 53 |
+
- int8 embedding/unembedding lanes with threadgroup x-staging (`arch3.py`,
|
| 54 |
+
s8 v3); embeddings dequantize one row per token (int8 × fp32 row scale —
|
| 55 |
+
exact).
|
| 56 |
+
- MLX supplies SDPA (fp32 KV, block-grown windows), argmax, and the lazy
|
| 57 |
+
graph; the decode step is `mx.compile`d.
|
| 58 |
+
|
| 59 |
+
## Correctness receipts (this exact container, sha `91c0b26d…`)
|
| 60 |
+
|
| 61 |
+
House rule: greedy streams must be token-identical to the container's own
|
| 62 |
+
fp expansion reference; near-tie argmax flips at tiny logit margins are the
|
| 63 |
+
only tolerated class, and every flip must be documented with its margin.
|
| 64 |
+
Receipts in `receipts/`:
|
| 65 |
+
|
| 66 |
+
| gate | reference | result |
|
| 67 |
+
|---|---|---|
|
| 68 |
+
| V1 refgate | fp32 HF expansion of this container (eager attn), teacher-forced 9 pos + 24-token greedy | **PASS — TF rel err 0.000% (max), cos 1.000000, argmax 9/9, greedy 24/24, 0 flips** |
|
| 69 |
+
| cachegate | banked HF fp16 greedy cache `86565bf6` (3 prompts × 128 steps, Modal CPU) | **PASS — 0 flips / 384 tokens** |
|
| 70 |
+
| pathgate | fused-kernel path vs pure `mx.fast`-ops twin | 32-token greedy IDENTICAL |
|
| 71 |
+
|
| 72 |
+
Note: the int8-activation CPU engine documents 2 benign near-tie flips
|
| 73 |
+
(top-2 margins 0.02–0.20 logits) against the same fp16 cache on this
|
| 74 |
+
checkpoint's flat logit landscape. The MLX path (fp32 activations) resolves
|
| 75 |
+
all 384 positions identically to the HF reference — 0 flips, no near-tie
|
| 76 |
+
class needed.
|
| 77 |
+
|
| 78 |
+
## Speed (honest numbers, with venue)
|
| 79 |
+
|
| 80 |
+
| path | venue | 512-token greedy decode |
|
| 81 |
+
|---|---|---|
|
| 82 |
+
| this MLX pack (kernel v7) | Apple M5 MacBook (16 GB), macOS, MLX 0.31.2, median of 5 runs, 2026-07-25 | **201 tok/s** (197–202 across runs, peak MLX mem 0.53 GiB) |
|
| 83 |
+
| `fermion-run` CPU binary (same container) | same M5, 9 threads, banked re-receipt | 225–236 tok/s |
|
| 84 |
+
|
| 85 |
+
The CPU binary remains this SKU's speed crown; the MLX lane buys native
|
| 86 |
+
Python integration with certified outputs. Kernel version selectable via
|
| 87 |
+
`TRIT_METAL_V` (7 = default/fastest; the sweep receipt is in
|
| 88 |
+
`receipts/`).
|
| 89 |
+
|
| 90 |
+
## EXPERIMENTAL: this pack drafts for Neutrino-8B
|
| 91 |
+
|
| 92 |
+
The same package powers `--spec` speculative decoding where this 0.6B
|
| 93 |
+
drafts and the Neutrino-8B pack verifies under the greedy-exact acceptance
|
| 94 |
+
rule (output stream exactness-gated, token-identical to plain 8B decode).
|
| 95 |
+
See the Neutrino-8B MLX pack README for the mode, receipts, and honest
|
| 96 |
+
per-text-type numbers (real speedup on high-acceptance text only).
|
| 97 |
+
|
| 98 |
+
## Files
|
| 99 |
+
|
| 100 |
+
- `fermion_mlx/` — the loader/runtime package (self-contained, including
|
| 101 |
+
the Metal GEMV kernels).
|
| 102 |
+
- `tokenizer.json`, `tokenizer_config.json`, `vocab.json`, `merges.txt`,
|
| 103 |
+
`generation_config.json` — donor tokenizer (Qwen/Qwen3-0.6B @
|
| 104 |
+
`c1899de289a04d12100db370d81485cdf75e47ca`, the pinned revision from the
|
| 105 |
+
export receipts).
|
| 106 |
+
- `receipts/` — gate + bench receipts (JSON), kernel-source SHA sidecar.
|
| 107 |
+
- `MANIFEST.json` — file-by-file provenance + shas.
|
| 108 |
+
|
| 109 |
+
Weights: not duplicated in this subfolder — use the parent card's
|
| 110 |
+
materialization instructions (`neutrino-0.6b_v4.bin`, sha256
|
| 111 |
+
`91c0b26d105710e755ef1ec1b5607c00da5d5cbd048fd5584135fb6fc1e19399`).
|
| 112 |
+
|
| 113 |
+
## License
|
| 114 |
+
|
| 115 |
+
Apache-2.0 (weights and this pack's code). Derivative of Qwen/Qwen3-0.6B —
|
| 116 |
+
see the parent `LICENSE`.
|
mlx/fermion_mlx/__init__.py
ADDED
|
@@ -0,0 +1,130 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""fermion_mlx — run Fermion Research TRTC v4 arch-3 (Qwen3-family) ternary
|
| 2 |
+
containers on Apple silicon via MLX + custom Metal kernels.
|
| 3 |
+
|
| 4 |
+
import fermion_mlx
|
| 5 |
+
model = fermion_mlx.load("neutrino-0p6b_v4_base.bin")
|
| 6 |
+
text = fermion_mlx.generate(model, tokenizer, "The capital of France is",
|
| 7 |
+
max_tokens=64)
|
| 8 |
+
|
| 9 |
+
The five-value {0, ±s_lo, ±s_hi} weights stay bit-packed at rest and are
|
| 10 |
+
decoded inside the GEMV kernels; nothing is expanded to fp16/fp32 weights.
|
| 11 |
+
Kernel version: env TRIT_METAL_V (default 7 = the banked LUT-decode bodies).
|
| 12 |
+
"""
|
| 13 |
+
import os
|
| 14 |
+
|
| 15 |
+
# choose the crown kernel bodies before the kernel module is imported
|
| 16 |
+
# (v9 = single-barrier double-buffered LUT, gated bit-identical to v7;
|
| 17 |
+
# set TRIT_METAL_V=7 to fall back to the previous default)
|
| 18 |
+
os.environ.setdefault("TRIT_METAL_V", "9")
|
| 19 |
+
|
| 20 |
+
from .model import MetalTritQwen3 # noqa: E402
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def load(container_path, maxseq=1024, verbose=True):
|
| 24 |
+
"""Memory-map a TRTC v4 arch-3 container and build the Metal model."""
|
| 25 |
+
return MetalTritQwen3(container_path, maxseq=maxseq, verbose=verbose)
|
| 26 |
+
|
| 27 |
+
|
| 28 |
+
DEFAULT_EOS = (151645, 151643) # <|im_end|>, <|endoftext|>
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _as_ids(x):
|
| 32 |
+
"""Normalise whatever a tokenizer handed back into a flat list of ids.
|
| 33 |
+
|
| 34 |
+
transformers >= 4.56 flipped `apply_chat_template(tokenize=True)` to
|
| 35 |
+
return a BatchEncoding instead of a list of ids (return_dict defaults to
|
| 36 |
+
True). requirements.txt says `transformers>=4.53`, so a fresh install gets
|
| 37 |
+
the new behaviour and the old code fed a BatchEncoding straight into
|
| 38 |
+
`model.prefill`, which died with
|
| 39 |
+
`int() argument must be ... not 'tokenizers.Encoding'`.
|
| 40 |
+
"""
|
| 41 |
+
if hasattr(x, "input_ids"):
|
| 42 |
+
x = x.input_ids
|
| 43 |
+
elif isinstance(x, dict) and "input_ids" in x:
|
| 44 |
+
x = x["input_ids"]
|
| 45 |
+
if len(x) and isinstance(x[0], (list, tuple)): # batched -> first row
|
| 46 |
+
x = x[0]
|
| 47 |
+
return [int(t) for t in x]
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def _rp_greedy(model, ids, n, rp, eos=None):
|
| 51 |
+
"""Greedy decode with the CTRL/HF multiplicative repetition penalty.
|
| 52 |
+
|
| 53 |
+
Byte-for-byte the rule the graded MLX battery used
|
| 54 |
+
(`rp_greedy`): the penalty applies to every id SEEN SO FAR in the whole
|
| 55 |
+
context, not to a 256-token window — that is the MLX-side convention and
|
| 56 |
+
it is what the banked MLX numbers on the model card were produced at.
|
| 57 |
+
|
| 58 |
+
This path materialises the logits every step, so it forfeits the fused
|
| 59 |
+
fast path's one-step async lookahead. It is only taken when rp != 1.0;
|
| 60 |
+
`model.generate` (rp = 1.0) is untouched, so the banked tok/s receipts
|
| 61 |
+
still describe the default.
|
| 62 |
+
"""
|
| 63 |
+
import time
|
| 64 |
+
|
| 65 |
+
import mlx.core as mx
|
| 66 |
+
import numpy as np
|
| 67 |
+
|
| 68 |
+
eos = set(eos or ())
|
| 69 |
+
mask = model.new_mask()
|
| 70 |
+
x, pos0 = model.prefill(ids, mask)
|
| 71 |
+
seen = set(int(t) for t in ids)
|
| 72 |
+
toks = []
|
| 73 |
+
t0 = time.perf_counter()
|
| 74 |
+
for i in range(n):
|
| 75 |
+
pb = mx.array([pos0 + i], dtype=mx.int32)
|
| 76 |
+
W = model._window(pos0 + i)
|
| 77 |
+
nt, xn, lg = model.step_compiled(x, pb, mask, W)
|
| 78 |
+
lgn = np.array(lg, copy=True)
|
| 79 |
+
idxs = np.fromiter(seen, dtype=np.int64)
|
| 80 |
+
vals = lgn[idxs]
|
| 81 |
+
lgn[idxs] = np.where(vals > 0, vals / rp, vals * rp)
|
| 82 |
+
tsel = int(lgn.argmax())
|
| 83 |
+
toks.append(tsel)
|
| 84 |
+
if tsel in eos:
|
| 85 |
+
break
|
| 86 |
+
x = model.embed(tsel)
|
| 87 |
+
seen.add(tsel)
|
| 88 |
+
return toks, time.perf_counter() - t0
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def _decode(model, tokenizer, ids, max_tokens, eos, rep_penalty):
|
| 92 |
+
if rep_penalty and float(rep_penalty) != 1.0:
|
| 93 |
+
toks, dt = _rp_greedy(model, ids, max_tokens, float(rep_penalty),
|
| 94 |
+
eos=eos)
|
| 95 |
+
else:
|
| 96 |
+
toks, dt = model.generate(ids, max_tokens, eos=eos)
|
| 97 |
+
if eos:
|
| 98 |
+
cut = next((i for i, t in enumerate(toks) if t in eos), len(toks))
|
| 99 |
+
toks = toks[:cut]
|
| 100 |
+
text = tokenizer.decode(toks)
|
| 101 |
+
rate = (len(toks) or 1) / dt if dt > 0 else 0.0
|
| 102 |
+
return text, {"tokens": len(toks), "seconds": dt, "tok_s": rate}
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
def generate(model, tokenizer, prompt, max_tokens=256, stop_on_eos=True,
|
| 106 |
+
rep_penalty=1.0):
|
| 107 |
+
"""Greedy completion of a plain-text prompt. Returns (text, stats).
|
| 108 |
+
|
| 109 |
+
`rep_penalty` > 1.0 enables the CTRL-style repetition penalty the graded
|
| 110 |
+
MLX battery ran at (1.05 on the chat SKU); 1.0, the default, is the
|
| 111 |
+
untouched pure-argmax fast path.
|
| 112 |
+
"""
|
| 113 |
+
ids = _as_ids(tokenizer(prompt))
|
| 114 |
+
eos = set(DEFAULT_EOS) if stop_on_eos else None
|
| 115 |
+
return _decode(model, tokenizer, ids, max_tokens, eos, rep_penalty)
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
def chat(model, tokenizer, messages, max_tokens=256, rep_penalty=1.0):
|
| 119 |
+
"""Chat completion via the donor chat template (thinking off).
|
| 120 |
+
|
| 121 |
+
`rep_penalty` as in `generate`; the chat SKU's card documents 1.05.
|
| 122 |
+
"""
|
| 123 |
+
try:
|
| 124 |
+
raw = tokenizer.apply_chat_template(messages, add_generation_prompt=True,
|
| 125 |
+
enable_thinking=False)
|
| 126 |
+
except TypeError:
|
| 127 |
+
raw = tokenizer.apply_chat_template(messages, add_generation_prompt=True)
|
| 128 |
+
ids = _as_ids(raw)
|
| 129 |
+
return _decode(model, tokenizer, ids, max_tokens, set(DEFAULT_EOS),
|
| 130 |
+
rep_penalty)
|
mlx/fermion_mlx/__main__.py
ADDED
|
@@ -0,0 +1,177 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""fermion_mlx CLI.
|
| 3 |
+
|
| 4 |
+
python -m fermion_mlx --model x.bin --mode gen --prompt "..."
|
| 5 |
+
python -m fermion_mlx --model x.bin --mode chat --prompt "..." --tokenizer DIR
|
| 6 |
+
python -m fermion_mlx --model x.bin --mode chat --prompt "..." --tokenizer DIR \
|
| 7 |
+
--rep-penalty 1.05 # the graded chat-SKU config
|
| 8 |
+
python -m fermion_mlx --model x.bin --mode gate --ref neutrino_ref.npz
|
| 9 |
+
python -m fermion_mlx --model x.bin --mode cachegate --cache hf_greedy_cache.json
|
| 10 |
+
python -m fermion_mlx --model x.bin --mode pathgate
|
| 11 |
+
python -m fermion_mlx --model x.bin --mode bench --runs 5 --gen-tokens 512
|
| 12 |
+
|
| 13 |
+
EXPERIMENTAL speculative decoding (0.6B drafts, exactness-gated):
|
| 14 |
+
python -m fermion_mlx --model 8b.bin --draft-model 0.6b.bin --mode spec \
|
| 15 |
+
--prompt "..." --tokenizer DIR --mem-limit-gb 6
|
| 16 |
+
python -m fermion_mlx --model 8b.bin --draft-model 0.6b.bin --mode specgate \
|
| 17 |
+
--tokenizer DIR --mem-limit-gb 6 --receipt specgate.json
|
| 18 |
+
python -m fermion_mlx --model 8b.bin --draft-model 0.6b.bin --mode specbench \
|
| 19 |
+
--tokenizer DIR --mem-limit-gb 6 --receipt specbench.json
|
| 20 |
+
|
| 21 |
+
Gate receipts are written next to --receipt (JSON) when given.
|
| 22 |
+
"""
|
| 23 |
+
import argparse
|
| 24 |
+
import json
|
| 25 |
+
import sys
|
| 26 |
+
import time
|
| 27 |
+
|
| 28 |
+
import mlx.core as mx
|
| 29 |
+
|
| 30 |
+
import fermion_mlx
|
| 31 |
+
from fermion_mlx.gates import refgate, cachegate, pathgate
|
| 32 |
+
|
| 33 |
+
HOUSE_PROMPT = "The key idea behind ternary neural networks is"
|
| 34 |
+
# tokenizer-free fallback: Qwen3-tokenized HOUSE_PROMPT (donor-verified ids)
|
| 35 |
+
HOUSE_IDS = [785, 1376, 4522, 4815, 71617, 658, 29728, 14155, 374]
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def _tok(args):
|
| 39 |
+
from transformers import AutoTokenizer
|
| 40 |
+
return AutoTokenizer.from_pretrained(args.tokenizer)
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def main():
|
| 44 |
+
ap = argparse.ArgumentParser()
|
| 45 |
+
ap.add_argument("--model", required=True)
|
| 46 |
+
ap.add_argument("--mode", default="gen",
|
| 47 |
+
choices=["gen", "chat", "gate", "cachegate", "pathgate",
|
| 48 |
+
"bench", "spec", "specgate", "specbench"])
|
| 49 |
+
ap.add_argument("--draft-model", default="",
|
| 50 |
+
help="draft container for --mode spec* (e.g. the 0.6B pack)")
|
| 51 |
+
ap.add_argument("--spec-k", type=int, default=4,
|
| 52 |
+
help="draft tokens per speculative round")
|
| 53 |
+
ap.add_argument("--prompt", default=HOUSE_PROMPT)
|
| 54 |
+
ap.add_argument("--tokenizer", default="Qwen/Qwen3-0.6B")
|
| 55 |
+
ap.add_argument("--ref", default="")
|
| 56 |
+
ap.add_argument("--cache", default="")
|
| 57 |
+
ap.add_argument("--receipt", default="")
|
| 58 |
+
ap.add_argument("--tie-tol", type=float, default=0.04, dest="tie_tol")
|
| 59 |
+
ap.add_argument("--max-flips", type=int, default=8, dest="max_flips")
|
| 60 |
+
ap.add_argument("--gen-tokens", type=int, default=512)
|
| 61 |
+
ap.add_argument("--runs", type=int, default=5)
|
| 62 |
+
ap.add_argument("--maxseq", type=int, default=1024)
|
| 63 |
+
ap.add_argument("--no-tokenizer", action="store_true",
|
| 64 |
+
help="use the pinned house prompt ids (no transformers)")
|
| 65 |
+
ap.add_argument("--mem-limit-gb", type=float, default=0.0,
|
| 66 |
+
help="hard MLX memory limit; the process fails fast "
|
| 67 |
+
"instead of pressuring the machine (house OOM rule)")
|
| 68 |
+
ap.add_argument("--rep-penalty", type=float, default=1.0,
|
| 69 |
+
dest="rep_penalty",
|
| 70 |
+
help="CTRL-style repetition penalty over the whole "
|
| 71 |
+
"context, the rule the graded MLX battery ran at "
|
| 72 |
+
"(1.05 on the chat SKU). 1.0 (default) = the "
|
| 73 |
+
"untouched pure-argmax fast path.")
|
| 74 |
+
args = ap.parse_args()
|
| 75 |
+
|
| 76 |
+
if args.mem_limit_gb > 0:
|
| 77 |
+
mx.set_memory_limit(int(args.mem_limit_gb * 2**30))
|
| 78 |
+
print(f"[mem] MLX memory limit set to {args.mem_limit_gb:.1f} GiB")
|
| 79 |
+
|
| 80 |
+
model = fermion_mlx.load(args.model, maxseq=args.maxseq)
|
| 81 |
+
|
| 82 |
+
def save(rec):
|
| 83 |
+
if args.receipt:
|
| 84 |
+
json.dump(rec, open(args.receipt, "w"), indent=1)
|
| 85 |
+
print(f"receipt -> {args.receipt}")
|
| 86 |
+
|
| 87 |
+
if args.mode in ("spec", "specgate", "specbench"):
|
| 88 |
+
from fermion_mlx.spec import SpecDecoder, specgate, specbench
|
| 89 |
+
assert args.draft_model, "--mode spec* needs --draft-model"
|
| 90 |
+
draft = fermion_mlx.load(args.draft_model, maxseq=args.maxseq)
|
| 91 |
+
tok = _tok(args)
|
| 92 |
+
if args.mode == "specgate":
|
| 93 |
+
rec = specgate(model, draft, tok, k=args.spec_k,
|
| 94 |
+
receipt=args.receipt or None)
|
| 95 |
+
print(f"[specgate] {rec['verdict']}")
|
| 96 |
+
sys.exit(0 if rec["verdict"] == "PASS" else 1)
|
| 97 |
+
if args.mode == "specbench":
|
| 98 |
+
rec = specbench(model, draft, tok, k=args.spec_k,
|
| 99 |
+
n=args.gen_tokens, runs=args.runs,
|
| 100 |
+
receipt=args.receipt or None)
|
| 101 |
+
for t, v in rec["types"].items():
|
| 102 |
+
print(f"[specbench {t}] plain {v['plain_median_tok_s']} -> "
|
| 103 |
+
f"spec {v['spec_median_tok_s']} tok/s "
|
| 104 |
+
f"({v['speedup']}x, acc {v['acceptance_median']})")
|
| 105 |
+
return
|
| 106 |
+
sd = SpecDecoder(model, draft, k=args.spec_k)
|
| 107 |
+
ids = tok(args.prompt, add_special_tokens=False).input_ids
|
| 108 |
+
toks, dt, st = sd.generate(ids, args.gen_tokens)
|
| 109 |
+
print(tok.decode(toks))
|
| 110 |
+
print(f"[spec: {len(toks)} tok in {dt*1e3:.0f} ms -> "
|
| 111 |
+
f"{len(toks)/dt:.1f} tok/s | acceptance {st['acceptance']:.3f} "
|
| 112 |
+
f"| {st['tokens_per_round']} tok/round]")
|
| 113 |
+
return
|
| 114 |
+
|
| 115 |
+
if args.mode == "gate":
|
| 116 |
+
ok, rec = refgate(model, args.ref, args.tie_tol, args.max_flips)
|
| 117 |
+
rec["mode"], rec["model"] = "refgate", args.model
|
| 118 |
+
save(rec)
|
| 119 |
+
sys.exit(0 if ok else 1)
|
| 120 |
+
|
| 121 |
+
if args.mode == "cachegate":
|
| 122 |
+
ok, rec = cachegate(model, args.model, args.cache, args.tie_tol,
|
| 123 |
+
args.max_flips)
|
| 124 |
+
rec["mode"], rec["model"] = "cachegate", args.model
|
| 125 |
+
save(rec)
|
| 126 |
+
sys.exit(0 if ok else 1)
|
| 127 |
+
|
| 128 |
+
if args.mode == "pathgate":
|
| 129 |
+
ids = (HOUSE_IDS if args.no_tokenizer else
|
| 130 |
+
fermion_mlx._as_ids(_tok(args)(args.prompt)))
|
| 131 |
+
ok, rec = pathgate(model, ids, n=32)
|
| 132 |
+
rec["mode"], rec["model"] = "pathgate", args.model
|
| 133 |
+
save(rec)
|
| 134 |
+
sys.exit(0 if ok else 1)
|
| 135 |
+
|
| 136 |
+
if args.mode == "bench":
|
| 137 |
+
ids = (HOUSE_IDS if args.no_tokenizer else
|
| 138 |
+
fermion_mlx._as_ids(_tok(args)(args.prompt)))
|
| 139 |
+
n = args.gen_tokens
|
| 140 |
+
model.generate(ids, 16) # warmup + kernel JIT
|
| 141 |
+
rates, runs = [], []
|
| 142 |
+
for r in range(args.runs):
|
| 143 |
+
toks, dt = model.generate(ids, n) # no EOS stop: full budget
|
| 144 |
+
rates.append(n / dt)
|
| 145 |
+
runs.append({"run": r, "tokens": n, "seconds": dt, "tok_s": n / dt})
|
| 146 |
+
print(f" run {r}: {n} tok in {dt*1e3:.0f} ms -> {rates[-1]:.1f} tok/s")
|
| 147 |
+
rates.sort()
|
| 148 |
+
med = rates[len(rates) // 2]
|
| 149 |
+
peak = mx.get_peak_memory() / 2**30
|
| 150 |
+
print(f"[bench] median {med:.1f} tok/s over {args.runs} runs "
|
| 151 |
+
f"({n}-token greedy), peak mem {peak:.2f} GiB")
|
| 152 |
+
save({"mode": "bench", "model": args.model, "prompt_ids": ids,
|
| 153 |
+
"gen_tokens": n, "runs": runs, "median_tok_s": med,
|
| 154 |
+
"peak_mem_gib": peak,
|
| 155 |
+
"utc": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())})
|
| 156 |
+
return
|
| 157 |
+
|
| 158 |
+
tok = _tok(args)
|
| 159 |
+
if args.mode == "gen":
|
| 160 |
+
text, st = fermion_mlx.generate(model, tok, args.prompt,
|
| 161 |
+
max_tokens=args.gen_tokens,
|
| 162 |
+
rep_penalty=args.rep_penalty)
|
| 163 |
+
print(text)
|
| 164 |
+
print(f"[{st['tokens']} tokens in {st['seconds']*1e3:.0f} ms -> "
|
| 165 |
+
f"{st['tok_s']:.1f} tok/s]")
|
| 166 |
+
else:
|
| 167 |
+
text, st = fermion_mlx.chat(model, tok,
|
| 168 |
+
[{"role": "user", "content": args.prompt}],
|
| 169 |
+
max_tokens=args.gen_tokens,
|
| 170 |
+
rep_penalty=args.rep_penalty)
|
| 171 |
+
print(text)
|
| 172 |
+
print(f"[{st['tokens']} tokens in {st['seconds']*1e3:.0f} ms -> "
|
| 173 |
+
f"{st['tok_s']:.1f} tok/s]")
|
| 174 |
+
|
| 175 |
+
|
| 176 |
+
if __name__ == "__main__":
|
| 177 |
+
main()
|
mlx/fermion_mlx/arch3.py
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Arch-3 (Qwen3) Metal kernel: fused per-head Q/K-RMSNorm + NeoX rope +
|
| 3 |
+
KV-cache append, one dispatch per decode step.
|
| 4 |
+
|
| 5 |
+
The Qwen3 arch delta vs arch-1 is the per-head RMSNorm applied to the qkv
|
| 6 |
+
GEMV output BEFORE rope (HF order: q_norm/k_norm over head_dim, weights
|
| 7 |
+
shared across heads). This kernel is the arch-3 twin of
|
| 8 |
+
trit_kernels.rope_append: one simdgroup per head; q/k heads compute the
|
| 9 |
+
head's rms via simd_sum then rope the normed values; v heads copy raw
|
| 10 |
+
values into the cache. kc/vc/mask are written in place at position pos
|
| 11 |
+
(stream-serial ordering with the consumer SDPA — the banked rope_append
|
| 12 |
+
pattern; correctness enforced by the e2e gates).
|
| 13 |
+
"""
|
| 14 |
+
import mlx.core as mx
|
| 15 |
+
|
| 16 |
+
_CACHE = {}
|
| 17 |
+
SG_PER_TG = 8
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def _s8_source_v3(rows, cols, rs, sg_per_tg, fuse_norm, eps, rpsg=8):
|
| 21 |
+
"""arch-3 lane addition: int8 GEMV with threadgroup x staging.
|
| 22 |
+
|
| 23 |
+
The vendored s8 v2 kernel re-reads the full x vector from device memory
|
| 24 |
+
once per simdgroup (rows/rpsg simdgroups x cols floats — for the 151936-
|
| 25 |
+
row Qwen3 unembed that is +50% of the weight bytes). v3 stages the
|
| 26 |
+
effective x (rmsnorm applied) into threadgroup memory once per
|
| 27 |
+
THREADGROUP, tile by tile; every simdgroup then streams only its int8
|
| 28 |
+
rows. Requires rows % (rpsg * sg_per_tg) == 0 (true for vocab 151936)."""
|
| 29 |
+
assert rs % 4 == 0 and cols % 4 == 0 and rows % (rpsg * sg_per_tg) == 0
|
| 30 |
+
tile = min(cols, 1024)
|
| 31 |
+
assert cols % tile == 0
|
| 32 |
+
ntiles = cols // tile
|
| 33 |
+
nthreads = sg_per_tg * 32
|
| 34 |
+
if fuse_norm:
|
| 35 |
+
norm_pre = f"""
|
| 36 |
+
threadgroup float ssq_sh[{sg_per_tg}];
|
| 37 |
+
{{
|
| 38 |
+
float ssq = 0.0f;
|
| 39 |
+
for (uint c = tid; c < {cols}u; c += {nthreads}u) {{
|
| 40 |
+
float v = x[c]; ssq = metal::fma(v, v, ssq);
|
| 41 |
+
}}
|
| 42 |
+
ssq = metal::simd_sum(ssq);
|
| 43 |
+
if (lane == 0u) ssq_sh[sg] = ssq;
|
| 44 |
+
threadgroup_barrier(metal::mem_flags::mem_threadgroup);
|
| 45 |
+
float tt = 0.0f;
|
| 46 |
+
for (uint i = 0u; i < {sg_per_tg}u; ++i) tt += ssq_sh[i];
|
| 47 |
+
rms = metal::rsqrt(tt / {float(cols)}f + {eps:.9g}f);
|
| 48 |
+
}}
|
| 49 |
+
"""
|
| 50 |
+
stage = f"xb[cl] = x[c] * nw[c] * rms;"
|
| 51 |
+
else:
|
| 52 |
+
norm_pre = ""
|
| 53 |
+
stage = "xb[cl] = x[c];"
|
| 54 |
+
return f"""
|
| 55 |
+
uint tid = thread_position_in_threadgroup.x;
|
| 56 |
+
uint lane = tid & 31u;
|
| 57 |
+
uint sg = tid >> 5u;
|
| 58 |
+
uint row0 = (threadgroup_position_in_grid.x * {sg_per_tg}u + sg) * {rpsg}u;
|
| 59 |
+
float rms = 1.0f;
|
| 60 |
+
{norm_pre}
|
| 61 |
+
threadgroup float xb[{tile}];
|
| 62 |
+
float acc[{rpsg}];
|
| 63 |
+
for (uint r = 0u; r < {rpsg}u; ++r) acc[r] = 0.0f;
|
| 64 |
+
const float4 one4 = float4(1.0f);
|
| 65 |
+
for (uint t = 0u; t < {ntiles}u; ++t) {{
|
| 66 |
+
threadgroup_barrier(metal::mem_flags::mem_threadgroup);
|
| 67 |
+
for (uint cl = tid; cl < {tile}u; cl += {nthreads}u) {{
|
| 68 |
+
uint c = t * {tile}u + cl;
|
| 69 |
+
{stage}
|
| 70 |
+
}}
|
| 71 |
+
threadgroup_barrier(metal::mem_flags::mem_threadgroup);
|
| 72 |
+
for (uint i = lane * 4u; i < {tile}u; i += 128u) {{
|
| 73 |
+
float4 xv = *(const threadgroup float4*)(xb + i);
|
| 74 |
+
for (uint r = 0u; r < {rpsg}u; ++r) {{
|
| 75 |
+
char4 wv = *(const device char4*)((const device char*)W
|
| 76 |
+
+ (ulong)(row0 + r) * {rs}u + t * {tile}u + i);
|
| 77 |
+
acc[r] += metal::dot(float4(wv.x, wv.y, wv.z, wv.w) * xv, one4);
|
| 78 |
+
}}
|
| 79 |
+
}}
|
| 80 |
+
}}
|
| 81 |
+
for (uint r = 0u; r < {rpsg}u; ++r) {{
|
| 82 |
+
float a = metal::simd_sum(acc[r]);
|
| 83 |
+
if (lane == 0u) out[row0 + r] = scale[row0 + r] * a;
|
| 84 |
+
}}
|
| 85 |
+
"""
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def get_s8_gemv_v3(rows, cols, rs, fuse_norm=False, eps=1e-6):
|
| 89 |
+
key = ("s8v3", rows, cols, rs, fuse_norm, round(eps, 12))
|
| 90 |
+
if key in _CACHE:
|
| 91 |
+
return _CACHE[key]
|
| 92 |
+
inputs = ["W", "scale", "x"]
|
| 93 |
+
if fuse_norm:
|
| 94 |
+
inputs.append("nw")
|
| 95 |
+
src = _s8_source_v3(rows, cols, rs, SG_PER_TG, fuse_norm, eps)
|
| 96 |
+
k = mx.fast.metal_kernel(name="s8gemv3_r%d_c%d_n%d" % (rows, cols, fuse_norm),
|
| 97 |
+
input_names=inputs, output_names=["out"], source=src)
|
| 98 |
+
_CACHE[key] = (k, inputs)
|
| 99 |
+
return _CACHE[key]
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
def s8_gemv_v3(W, scale, x, norm_w=None, eps=1e-6, stream=None):
|
| 103 |
+
"""drop-in for trit_kernels.s8_gemv where rows % 64 == 0."""
|
| 104 |
+
rows, rs = W.shape
|
| 105 |
+
cols = x.shape[0]
|
| 106 |
+
k, names = get_s8_gemv_v3(rows, cols, rs, norm_w is not None, eps)
|
| 107 |
+
vals = {"W": W, "scale": scale, "x": x, "nw": norm_w}
|
| 108 |
+
nsg = rows // 8
|
| 109 |
+
ntg = (nsg + SG_PER_TG - 1) // SG_PER_TG
|
| 110 |
+
return k(inputs=[vals[n] for n in names],
|
| 111 |
+
grid=(ntg * SG_PER_TG * 32, 1, 1), threadgroup=(SG_PER_TG * 32, 1, 1),
|
| 112 |
+
output_shapes=[(rows,)], output_dtypes=[mx.float32], stream=stream)[0]
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
def get_qknorm_rope_append(n_heads, n_kv, hd, maxseq, eps):
|
| 116 |
+
key = ("qkra", n_heads, n_kv, hd, maxseq, round(eps, 12))
|
| 117 |
+
if key in _CACHE:
|
| 118 |
+
return _CACHE[key]
|
| 119 |
+
h2 = hd // 2
|
| 120 |
+
nheads_total = n_heads + 2 * n_kv
|
| 121 |
+
src = f"""
|
| 122 |
+
uint tid = thread_position_in_threadgroup.x;
|
| 123 |
+
uint lane = tid & 31u;
|
| 124 |
+
uint sg = tid >> 5u;
|
| 125 |
+
uint head = threadgroup_position_in_grid.x * {SG_PER_TG}u + sg;
|
| 126 |
+
if (head >= {nheads_total}u) return;
|
| 127 |
+
uint pos = (uint)posbuf[0];
|
| 128 |
+
if (head == 0u && lane == 0u) {{ ((device float*)mask)[pos] = 0.0f; }}
|
| 129 |
+
device float* kc_w = (device float*)kc;
|
| 130 |
+
device float* vc_w = (device float*)vc;
|
| 131 |
+
if (head < {n_heads + n_kv}u) {{
|
| 132 |
+
uint base = head * {hd}u;
|
| 133 |
+
const device float* g = (head < {n_heads}u) ? qn : kn;
|
| 134 |
+
float ssq = 0.0f;
|
| 135 |
+
for (uint d = lane; d < {hd}u; d += 32u) {{
|
| 136 |
+
float v = qkv[base + d];
|
| 137 |
+
ssq = metal::fma(v, v, ssq);
|
| 138 |
+
}}
|
| 139 |
+
ssq = metal::simd_sum(ssq);
|
| 140 |
+
float rms = metal::rsqrt(ssq / {float(hd)}f + {eps:.9g}f);
|
| 141 |
+
for (uint d = lane; d < {hd}u; d += 32u) {{
|
| 142 |
+
float c = ct[pos * {h2}u + (d % {h2}u)];
|
| 143 |
+
float sn = st[pos * {h2}u + (d % {h2}u)];
|
| 144 |
+
float v = qkv[base + d] * rms * g[d];
|
| 145 |
+
float vr = (d < {h2}u)
|
| 146 |
+
? -(qkv[base + d + {h2}u] * rms * g[d + {h2}u])
|
| 147 |
+
: (qkv[base + d - {h2}u] * rms * g[d - {h2}u]);
|
| 148 |
+
float r = metal::fma(v, c, vr * sn);
|
| 149 |
+
if (head < {n_heads}u) {{
|
| 150 |
+
qout[base + d] = r;
|
| 151 |
+
}} else {{
|
| 152 |
+
uint kh = head - {n_heads}u;
|
| 153 |
+
kc_w[(ulong)(kh * {maxseq}u + pos) * {hd}u + d] = r;
|
| 154 |
+
}}
|
| 155 |
+
}}
|
| 156 |
+
}} else {{
|
| 157 |
+
uint vh = head - {n_heads + n_kv}u;
|
| 158 |
+
for (uint d = lane; d < {hd}u; d += 32u) {{
|
| 159 |
+
vc_w[(ulong)(vh * {maxseq}u + pos) * {hd}u + d] =
|
| 160 |
+
qkv[{(n_heads + n_kv) * hd}u + vh * {hd}u + d];
|
| 161 |
+
}}
|
| 162 |
+
}}
|
| 163 |
+
"""
|
| 164 |
+
k = mx.fast.metal_kernel(
|
| 165 |
+
name="qknorm_rope_append_%d_%d_%d" % (n_heads, n_kv, hd),
|
| 166 |
+
input_names=["qkv", "qn", "kn", "kc", "vc", "ct", "st", "posbuf", "mask"],
|
| 167 |
+
output_names=["qout"], source=src)
|
| 168 |
+
_CACHE[key] = k
|
| 169 |
+
return k
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def qknorm_rope_append(qkv, qn, kn, kc, vc, ct, st, posbuf, mask,
|
| 173 |
+
n_heads, n_kv, hd, maxseq, eps, stream=None):
|
| 174 |
+
"""qkv fp32 [(n_heads+2*n_kv)*hd] -> roped+normed q fp32 [n_heads*hd];
|
| 175 |
+
writes normed+roped k and raw v into kc/vc at position posbuf[0] and
|
| 176 |
+
zeroes mask[pos]."""
|
| 177 |
+
k = get_qknorm_rope_append(n_heads, n_kv, hd, maxseq, eps)
|
| 178 |
+
nheads_total = n_heads + 2 * n_kv
|
| 179 |
+
ntg = (nheads_total + SG_PER_TG - 1) // SG_PER_TG
|
| 180 |
+
return k(inputs=[qkv, qn, kn, kc, vc, ct, st, posbuf, mask],
|
| 181 |
+
grid=(ntg * SG_PER_TG * 32, 1, 1), threadgroup=(SG_PER_TG * 32, 1, 1),
|
| 182 |
+
output_shapes=[(n_heads * hd,)], output_dtypes=[mx.float32],
|
| 183 |
+
stream=stream)[0]
|
mlx/fermion_mlx/container.py
ADDED
|
@@ -0,0 +1,143 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""TRTC v4 arch-3 (Qwen3) container reader — numpy memmap views, zero-copy
|
| 3 |
+
until the caller materializes.
|
| 4 |
+
|
| 5 |
+
Layout (ground truth: scripts/export_trit_c_model.py; cross-checks:
|
| 6 |
+
scripts/expand_trtc_v4_to_hf.py and kernels/gpu_e2e/runtime8b.py):
|
| 7 |
+
60 B header <Iii9i2fi>: magic 'TRTC', version=4, arch=3, n_layers, hidden,
|
| 8 |
+
n_heads, n_kv_heads, head_dim, rotary_dims, intermediate, vocab, max_pos,
|
| 9 |
+
f32 rms_norm_eps, f32 rope_theta, i32 embed_out_kind (eok)
|
| 10 |
+
eok=2 (untied, 8B): int8 embed_in record right after the header
|
| 11 |
+
per layer: input_layernorm f32[H], post_attention_layernorm f32[H],
|
| 12 |
+
q_norm f32[head_dim], k_norm f32[head_dim],
|
| 13 |
+
then 7 trit records (q,k,v,o,gate,up,down) — arch-3 QKV are bias-less
|
| 14 |
+
final norm f32[H]
|
| 15 |
+
int8 embed_out (lm_head) record; eok=1 (tied, 0.6B) derives embed_in
|
| 16 |
+
from this record.
|
| 17 |
+
trit record: i32 rows, cols, cols_pad, row_stride(bytes);
|
| 18 |
+
u8 bp/bn/br [rows*row_stride] LSB-first; f32 s_lo[rows], s_hi[rows];
|
| 19 |
+
i32 rs_lo[rows], rs_hi[rows] (skipped); i32 has_bias (+ f32 bias[rows])
|
| 20 |
+
int8 record: i32 rows, cols, cols_pad, row_stride (== cols_pad);
|
| 21 |
+
s8 W[rows*row_stride]; f32 scale[rows]; i32 wsum[rows] (skipped)
|
| 22 |
+
|
| 23 |
+
Weight semantics: w[r,c] = (bp-bn) * (s_hi[r] if br else s_lo[r]);
|
| 24 |
+
int8 embeds: w[r,c] = scale[r] * Q[r,c].
|
| 25 |
+
"""
|
| 26 |
+
import struct
|
| 27 |
+
|
| 28 |
+
import numpy as np
|
| 29 |
+
|
| 30 |
+
MAGIC = 0x43545254
|
| 31 |
+
HDR_FMT = "<Iii9i2fi"
|
| 32 |
+
HDR_BYTES = struct.calcsize(HDR_FMT) # 60
|
| 33 |
+
PROJS = ("q", "k", "v", "o", "gate", "up", "down")
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class TritRecord:
|
| 37 |
+
__slots__ = ("rows", "cols", "cols_pad", "row_stride", "bp", "bn", "br",
|
| 38 |
+
"slo", "shi", "bias")
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class Int8Record:
|
| 42 |
+
__slots__ = ("rows", "cols", "cols_pad", "row_stride", "W", "scale")
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
class Container:
|
| 46 |
+
pass
|
| 47 |
+
|
| 48 |
+
|
| 49 |
+
def _trit_record(mm, off):
|
| 50 |
+
r = TritRecord()
|
| 51 |
+
r.rows, r.cols, r.cols_pad, r.row_stride = (
|
| 52 |
+
int(v) for v in np.frombuffer(mm, np.int32, 4, off))
|
| 53 |
+
off += 16
|
| 54 |
+
n = r.rows * r.row_stride
|
| 55 |
+
r.bp = np.frombuffer(mm, np.uint8, n, off).reshape(r.rows, r.row_stride); off += n
|
| 56 |
+
r.bn = np.frombuffer(mm, np.uint8, n, off).reshape(r.rows, r.row_stride); off += n
|
| 57 |
+
r.br = np.frombuffer(mm, np.uint8, n, off).reshape(r.rows, r.row_stride); off += n
|
| 58 |
+
r.slo = np.frombuffer(mm, np.float32, r.rows, off); off += 4 * r.rows
|
| 59 |
+
r.shi = np.frombuffer(mm, np.float32, r.rows, off); off += 4 * r.rows
|
| 60 |
+
off += 8 * r.rows # rs_lo, rs_hi trit-sum sidecars (integrity data, unused here)
|
| 61 |
+
has_bias = int(np.frombuffer(mm, np.int32, 1, off)[0]); off += 4
|
| 62 |
+
r.bias = None
|
| 63 |
+
if has_bias:
|
| 64 |
+
r.bias = np.frombuffer(mm, np.float32, r.rows, off); off += 4 * r.rows
|
| 65 |
+
return r, off
|
| 66 |
+
|
| 67 |
+
|
| 68 |
+
def _int8_record(mm, off):
|
| 69 |
+
r = Int8Record()
|
| 70 |
+
r.rows, r.cols, r.cols_pad, r.row_stride = (
|
| 71 |
+
int(v) for v in np.frombuffer(mm, np.int32, 4, off))
|
| 72 |
+
assert r.row_stride == r.cols_pad, "int8 record: row_stride must equal cols_pad"
|
| 73 |
+
off += 16
|
| 74 |
+
n = r.rows * r.row_stride
|
| 75 |
+
r.W = np.frombuffer(mm, np.int8, n, off).reshape(r.rows, r.row_stride); off += n
|
| 76 |
+
r.scale = np.frombuffer(mm, np.float32, r.rows, off); off += 4 * r.rows
|
| 77 |
+
off += 4 * r.rows # wsum
|
| 78 |
+
return r, off
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def read_container(path):
|
| 82 |
+
"""Memory-map a TRTC v4 arch-3 container; all arrays are views into the map."""
|
| 83 |
+
mm = np.memmap(path, dtype=np.uint8, mode="r")
|
| 84 |
+
h = struct.unpack(HDR_FMT, bytes(mm[:HDR_BYTES]))
|
| 85 |
+
c = Container()
|
| 86 |
+
(magic, c.version, c.arch, c.n_layers, c.hidden, c.n_heads, c.n_kv,
|
| 87 |
+
c.head_dim, c.rot, c.inter, c.vocab, c.maxpos, c.eps, c.theta, c.eok) = h
|
| 88 |
+
assert magic == MAGIC, f"bad magic {magic:#x}"
|
| 89 |
+
assert c.version == 4 and c.arch == 3, \
|
| 90 |
+
f"need TRTC v4 arch-3 (got v={c.version} arch={c.arch})"
|
| 91 |
+
assert c.eok in (1, 2), f"bad embed_out_kind {c.eok}"
|
| 92 |
+
off = HDR_BYTES
|
| 93 |
+
c.embed_in = None
|
| 94 |
+
if c.eok == 2: # untied: int8 embed_in follows the header
|
| 95 |
+
c.embed_in, off = _int8_record(mm, off)
|
| 96 |
+
assert (c.embed_in.rows, c.embed_in.cols) == (c.vocab, c.hidden)
|
| 97 |
+
H, hd = c.hidden, c.head_dim
|
| 98 |
+
q_dim, kv_dim = c.n_heads * hd, c.n_kv * hd
|
| 99 |
+
shapes = {"q": (q_dim, H), "k": (kv_dim, H), "v": (kv_dim, H),
|
| 100 |
+
"o": (H, q_dim), "gate": (c.inter, H), "up": (c.inter, H),
|
| 101 |
+
"down": (H, c.inter)}
|
| 102 |
+
c.layers = []
|
| 103 |
+
for li in range(c.n_layers):
|
| 104 |
+
L = {}
|
| 105 |
+
L["norm1"] = np.frombuffer(mm, np.float32, H, off); off += 4 * H
|
| 106 |
+
L["norm2"] = np.frombuffer(mm, np.float32, H, off); off += 4 * H
|
| 107 |
+
L["qnorm"] = np.frombuffer(mm, np.float32, hd, off); off += 4 * hd
|
| 108 |
+
L["knorm"] = np.frombuffer(mm, np.float32, hd, off); off += 4 * hd
|
| 109 |
+
for nm in PROJS:
|
| 110 |
+
L[nm], off = _trit_record(mm, off)
|
| 111 |
+
assert (L[nm].rows, L[nm].cols) == shapes[nm], \
|
| 112 |
+
f"L{li}.{nm}: {(L[nm].rows, L[nm].cols)} != {shapes[nm]}"
|
| 113 |
+
assert L[nm].bias is None, f"L{li}.{nm}: arch-3 records must be bias-less"
|
| 114 |
+
c.layers.append(L)
|
| 115 |
+
c.final_norm = np.frombuffer(mm, np.float32, H, off); off += 4 * H
|
| 116 |
+
c.embed_out, off = _int8_record(mm, off)
|
| 117 |
+
assert (c.embed_out.rows, c.embed_out.cols) == (c.vocab, c.hidden)
|
| 118 |
+
assert off == mm.size, f"reader ended at {off}, file is {mm.size}"
|
| 119 |
+
if c.embed_in is None: # tied: same int8 record serves both
|
| 120 |
+
c.embed_in = c.embed_out
|
| 121 |
+
c.mm = mm # keep the map alive
|
| 122 |
+
return c
|
| 123 |
+
|
| 124 |
+
|
| 125 |
+
# ---------------- fp32 reference math (gate-side, numpy) ----------------
|
| 126 |
+
|
| 127 |
+
def unpack_plane(plane, cols):
|
| 128 |
+
return np.unpackbits(plane, axis=1, bitorder="little")[:, :cols]
|
| 129 |
+
|
| 130 |
+
|
| 131 |
+
def dense_weights(rec, dtype=np.float32):
|
| 132 |
+
p = unpack_plane(rec.bp, rec.cols).astype(np.int8)
|
| 133 |
+
n = unpack_plane(rec.bn, rec.cols).astype(np.int8)
|
| 134 |
+
h = unpack_plane(rec.br, rec.cols)
|
| 135 |
+
s = (p - n).astype(dtype)
|
| 136 |
+
mag = np.where(h.astype(bool), rec.shi[:, None].astype(dtype),
|
| 137 |
+
rec.slo[:, None].astype(dtype))
|
| 138 |
+
return s * mag
|
| 139 |
+
|
| 140 |
+
|
| 141 |
+
def ref_rmsnorm(x, g, eps, dtype=np.float32):
|
| 142 |
+
x = x.astype(dtype)
|
| 143 |
+
return (x * (1.0 / np.sqrt(np.mean(x * x) + eps))) * g.astype(dtype)
|
mlx/fermion_mlx/gates.py
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""House correctness gates for the MLX arch-3 path.
|
| 3 |
+
|
| 4 |
+
refgate : V1 gate — teacher-forced logits +
|
| 5 |
+
greedy-with-resync vs the container's own fp expansion reference
|
| 6 |
+
npz {prompt_ids, tf_logits, greedy, meta}. Every argmax
|
| 7 |
+
divergence must be a near-tie <= tie_tol of the logit scale;
|
| 8 |
+
greedy flips resynced and bounded.
|
| 9 |
+
cachegate : greedy-with-resync vs a banked hf_greedy_cache.json
|
| 10 |
+
(keyed to container md5).
|
| 11 |
+
pathgate : path-consistency — the fused-kernel primary path and the pure
|
| 12 |
+
mx.fast-ops reference twin must produce the same greedy stream.
|
| 13 |
+
|
| 14 |
+
All gates return (ok, receipt_dict); receipts carry per-flip absolute and
|
| 15 |
+
relative margins so near-tie flips are documented, never waved through.
|
| 16 |
+
"""
|
| 17 |
+
import hashlib
|
| 18 |
+
import json
|
| 19 |
+
|
| 20 |
+
import mlx.core as mx
|
| 21 |
+
import numpy as np
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
def _md5(path):
|
| 25 |
+
h = hashlib.md5()
|
| 26 |
+
with open(path, "rb") as f:
|
| 27 |
+
while chunk := f.read(1 << 24):
|
| 28 |
+
h.update(chunk)
|
| 29 |
+
return h.hexdigest()
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def refgate(model, ref_path, tie_tol=0.04, max_flips=8):
|
| 33 |
+
ref = np.load(ref_path, allow_pickle=True)
|
| 34 |
+
ids = [int(t) for t in ref["prompt_ids"].tolist()]
|
| 35 |
+
tf = ref["tf_logits"]
|
| 36 |
+
greedy_ref = [int(t) for t in ref["greedy"].tolist()]
|
| 37 |
+
meta = json.loads(str(ref["meta"])) if "meta" in ref else {}
|
| 38 |
+
print(f"[refgate] prompt ids ({len(ids)}): {ids}")
|
| 39 |
+
print(f"[refgate] ref meta: {meta}")
|
| 40 |
+
rec = {"ref": str(ref_path), "meta": meta, "tf": [], "greedy_flips": [],
|
| 41 |
+
"tie_tol": tie_tol, "max_flips": max_flips}
|
| 42 |
+
# stage A: teacher-forced logits
|
| 43 |
+
mask = model.new_mask()
|
| 44 |
+
rels, ams, near, worst_gap = [], 0, 0, 0.0
|
| 45 |
+
for i, t in enumerate(ids):
|
| 46 |
+
_, _, lg = model.step(model.embed(t), i, mask)
|
| 47 |
+
g = np.array(lg, np.float64)
|
| 48 |
+
r = tf[i].astype(np.float64)
|
| 49 |
+
scale = float(np.abs(r).max())
|
| 50 |
+
rel = float(np.abs(g - r).max() / scale)
|
| 51 |
+
cos = float((g * r).sum() / (np.linalg.norm(g) * np.linalg.norm(r)))
|
| 52 |
+
gam, ram = int(g.argmax()), int(r.argmax())
|
| 53 |
+
am = gam == ram
|
| 54 |
+
ams += am
|
| 55 |
+
rels.append(rel)
|
| 56 |
+
extra = ""
|
| 57 |
+
if not am:
|
| 58 |
+
gap = float(g[gam] - g[ram]) / scale
|
| 59 |
+
worst_gap = max(worst_gap, gap)
|
| 60 |
+
near += gap <= tie_tol
|
| 61 |
+
extra = f" gap {100*gap:.3f}% ({'near-tie' if gap <= tie_tol else 'REAL'})"
|
| 62 |
+
rec["tf"].append({"pos": i, "rel": rel, "cos": cos, "argmax_match": am})
|
| 63 |
+
print(f" pos {i:2d}: rel {100*rel:6.3f}% | cos {cos:.6f} | argmax "
|
| 64 |
+
f"{'match' if am else 'MISMATCH'} (mlx {gam} ref {ram}){extra}")
|
| 65 |
+
print(f"[refgate] stage A: TF rel max {100*max(rels):.3f}% / mean "
|
| 66 |
+
f"{100*float(np.mean(rels)):.3f}%, argmax {ams}/{len(ids)} "
|
| 67 |
+
f"(+{near} near-tie), worst gap {100*worst_gap:.3f}%")
|
| 68 |
+
rec["tf_summary"] = {"rel_max": max(rels), "rel_mean": float(np.mean(rels)),
|
| 69 |
+
"argmax": f"{ams}/{len(ids)}", "near_tie": near}
|
| 70 |
+
# stage B: greedy with resync
|
| 71 |
+
mask = model.new_mask()
|
| 72 |
+
x, pos0 = model.prefill(ids, mask, compiled=False)
|
| 73 |
+
flips, g_toks, ok = 0, [], True
|
| 74 |
+
for s in range(len(greedy_ref)):
|
| 75 |
+
_, _, lg = model.step(x, pos0 + s, mask)
|
| 76 |
+
l = np.array(lg, np.float64)
|
| 77 |
+
g_tok = int(l.argmax())
|
| 78 |
+
g_toks.append(g_tok)
|
| 79 |
+
tok = g_tok
|
| 80 |
+
if g_tok != greedy_ref[s]:
|
| 81 |
+
gap_abs = float(l[g_tok] - l[greedy_ref[s]])
|
| 82 |
+
scale = float(np.abs(l).max())
|
| 83 |
+
gap = gap_abs / scale
|
| 84 |
+
flips += 1
|
| 85 |
+
worst_gap = max(worst_gap, gap)
|
| 86 |
+
rec["greedy_flips"].append({"step": s, "mlx": g_tok,
|
| 87 |
+
"ref": greedy_ref[s],
|
| 88 |
+
"margin_abs_logits": gap_abs,
|
| 89 |
+
"margin_rel": gap})
|
| 90 |
+
print(f" step {s}: mlx {g_tok} vs ref {greedy_ref[s]}, gap "
|
| 91 |
+
f"{gap_abs:.4f} logits = {100*gap:.3f}% of scale -> resync")
|
| 92 |
+
if flips > max_flips:
|
| 93 |
+
ok = False
|
| 94 |
+
break
|
| 95 |
+
tok = greedy_ref[s]
|
| 96 |
+
x = model.embed(tok)
|
| 97 |
+
mx.eval(x)
|
| 98 |
+
print(f"[refgate] ref greedy: {greedy_ref}")
|
| 99 |
+
print(f"[refgate] mlx greedy: {g_toks}")
|
| 100 |
+
ok = ok and worst_gap <= tie_tol and flips <= max_flips
|
| 101 |
+
rec.update(flips=flips, worst_gap=worst_gap, ok=bool(ok),
|
| 102 |
+
mlx_greedy=g_toks, ref_greedy=greedy_ref)
|
| 103 |
+
print(f"[refgate] {'PASS' if ok else 'FAIL'} (worst gap {100*worst_gap:.3f}% "
|
| 104 |
+
f"tol {100*tie_tol:.1f}%; {flips} flips, tol {max_flips})")
|
| 105 |
+
return ok, rec
|
| 106 |
+
|
| 107 |
+
|
| 108 |
+
def cachegate(model, container_path, cache_path, tie_tol=0.04, max_flips=8):
|
| 109 |
+
cache = json.load(open(cache_path))
|
| 110 |
+
md5 = _md5(container_path)
|
| 111 |
+
print(f"[cachegate] container md5 {md5} vs cache key {cache['container_md5']}")
|
| 112 |
+
assert md5 == cache["container_md5"], "cache is for a different container"
|
| 113 |
+
rec = {"cache": str(cache_path), "container_md5": md5, "prompts": [],
|
| 114 |
+
"tie_tol": tie_tol, "max_flips_per_prompt": max_flips}
|
| 115 |
+
ok = True
|
| 116 |
+
tot_flips, worst = 0, 0.0
|
| 117 |
+
for pi, (prompt, ref) in enumerate(zip(cache["prompts"], cache["tokens"])):
|
| 118 |
+
mask = model.new_mask()
|
| 119 |
+
x, pos0 = model.prefill([int(t) for t in prompt], mask)
|
| 120 |
+
flips, fliprec = 0, []
|
| 121 |
+
for s, rt in enumerate(ref):
|
| 122 |
+
_, _, lg = model.step(x, pos0 + s, mask)
|
| 123 |
+
l = np.array(lg, np.float64)
|
| 124 |
+
g_tok = int(l.argmax())
|
| 125 |
+
tok = g_tok
|
| 126 |
+
if g_tok != int(rt):
|
| 127 |
+
gap_abs = float(l[g_tok] - l[int(rt)])
|
| 128 |
+
gap = gap_abs / float(np.abs(l).max())
|
| 129 |
+
worst = max(worst, gap)
|
| 130 |
+
flips += 1
|
| 131 |
+
fliprec.append({"step": s, "mlx": g_tok, "ref": int(rt),
|
| 132 |
+
"margin_abs_logits": gap_abs, "margin_rel": gap})
|
| 133 |
+
print(f" p{pi} step {s}: mlx {g_tok} vs hf {rt}, gap "
|
| 134 |
+
f"{gap_abs:.4f} logits = {100*gap:.3f}% -> resync")
|
| 135 |
+
tok = int(rt)
|
| 136 |
+
x = model.embed(tok)
|
| 137 |
+
mx.eval(x)
|
| 138 |
+
tot_flips += flips
|
| 139 |
+
good = flips <= max_flips
|
| 140 |
+
ok = ok and good
|
| 141 |
+
rec["prompts"].append({"prompt": prompt, "steps": len(ref),
|
| 142 |
+
"flips": flips, "flip_detail": fliprec,
|
| 143 |
+
"pass": bool(good)})
|
| 144 |
+
print(f" prompt {pi}: {len(ref)} steps, {flips} resynced flips -> "
|
| 145 |
+
f"{'PASS' if good else 'FAIL'}")
|
| 146 |
+
ok = ok and worst <= tie_tol
|
| 147 |
+
rec.update(total_flips=tot_flips, worst_gap=worst, ok=bool(ok))
|
| 148 |
+
print(f"[cachegate] {'PASS' if ok else 'FAIL'} ({tot_flips} flips total, "
|
| 149 |
+
f"worst gap {100*worst:.3f}% tol {100*tie_tol:.1f}%)")
|
| 150 |
+
return ok, rec
|
| 151 |
+
|
| 152 |
+
|
| 153 |
+
def pathgate(model, ids, n=32):
|
| 154 |
+
"""fused-kernel path vs pure-mx.fast-ops twin: greedy streams must be
|
| 155 |
+
IDENTICAL (same weights, same GEMV kernels; isolates the new kernel)."""
|
| 156 |
+
fused, _ = model.generate(ids, n, compiled=True)
|
| 157 |
+
ref = model.generate_ref(ids, n)
|
| 158 |
+
same = fused == ref
|
| 159 |
+
d = next((i for i in range(min(len(fused), len(ref)))
|
| 160 |
+
if fused[i] != ref[i]), None)
|
| 161 |
+
print(f"[pathgate] fused-vs-mxops {n}-token greedy: "
|
| 162 |
+
f"{'IDENTICAL' if same else f'FIRST DIVERGENCE at {d}'}")
|
| 163 |
+
return same, {"n": n, "identical": bool(same), "first_divergence": d,
|
| 164 |
+
"fused": fused, "mxops": ref}
|
mlx/fermion_mlx/mbatch.py
ADDED
|
@@ -0,0 +1,164 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Small-batch (M-token) GEMM kernels for speculative verify — EXPERIMENTAL.
|
| 3 |
+
|
| 4 |
+
Why these exist: the shipped decode path is M=1 GEMV. Speculative decoding
|
| 5 |
+
only wins if the verifier scores k+1 draft positions while streaming the
|
| 6 |
+
weight planes ONCE; re-running the GEMV k+1 times re-reads the weights k+1
|
| 7 |
+
times and cannot beat plain decode. These kernels keep the v5/v7 interleaved
|
| 8 |
+
plane layout + one-weight-read structure and add an M-wide accumulator:
|
| 9 |
+
|
| 10 |
+
trit_gemm_m : five-value planes X[M, cols] -> out[M, rows]
|
| 11 |
+
v7 lane mapping (lane = row(8) x word-slot(4), planes read
|
| 12 |
+
once, fully coalesced) + v6 mask-convert-FMA decode (float4
|
| 13 |
+
(uint4(a) & m4) == 2^bit when set; x pre-scaled by 2^-bit,
|
| 14 |
+
exact in fp32). No LUT: a per-token LUT tile for M=5 exceeds
|
| 15 |
+
the 32KB threadgroup budget at inter=12288.
|
| 16 |
+
s8_gemm_m : int8 rows (lm_head / untied embeds), one simdgroup per row,
|
| 17 |
+
weight char4 loaded once and dotted against all M x-vectors.
|
| 18 |
+
|
| 19 |
+
No norm/GLU/residual template flags here: the spec verify path applies
|
| 20 |
+
RMSNorm / SwiGLU / residual adds as plain MLX elementwise ops on [M, ...]
|
| 21 |
+
tensors (cheap at M<=8), keeping these kernels pure GEMMs.
|
| 22 |
+
|
| 23 |
+
Additive module — nothing in trit_kernels.py / arch3.py / model.py changes.
|
| 24 |
+
"""
|
| 25 |
+
import mlx.core as mx
|
| 26 |
+
|
| 27 |
+
SG_PER_TG = 8
|
| 28 |
+
_CACHE = {}
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def _trit_gemm_m_source(M, rows, cols, rs):
|
| 32 |
+
assert rs % 4 == 0 and rs * 8 == cols and cols % 32 == 0 and rows % 8 == 0
|
| 33 |
+
W = rs // 4
|
| 34 |
+
kbody = []
|
| 35 |
+
for k in range(8):
|
| 36 |
+
m4 = [1 << (4 * k + i) for i in range(4)]
|
| 37 |
+
ps = ", ".join("%.9ef" % (2.0 ** -(4 * k + i)) for i in range(4))
|
| 38 |
+
loads = "\n".join(
|
| 39 |
+
f""" {{
|
| 40 |
+
float4 xs = x4[{m}u * {cols // 4}u + wl * 8u + {k}u] * ps{k};
|
| 41 |
+
accLo[{m}] += metal::dot(sa, xs);
|
| 42 |
+
accHi[{m}] += metal::dot(sb, xs);
|
| 43 |
+
}}""" for m in range(M))
|
| 44 |
+
kbody.append(f""" {{
|
| 45 |
+
const uint4 m4 = uint4({m4[0]}u, {m4[1]}u, {m4[2]}u, {m4[3]}u);
|
| 46 |
+
const float4 ps{k} = float4({ps});
|
| 47 |
+
float4 sa = float4(uint4(a) & m4) - float4(uint4(b) & m4);
|
| 48 |
+
float4 sb = float4(uint4(cc) & m4) - float4(uint4(d) & m4);
|
| 49 |
+
{loads}
|
| 50 |
+
}}""")
|
| 51 |
+
kbody = "\n".join(kbody)
|
| 52 |
+
outs = "\n".join(
|
| 53 |
+
f""" accLo[{m}] += metal::simd_shuffle_down(accLo[{m}], 16u);
|
| 54 |
+
accLo[{m}] += metal::simd_shuffle_down(accLo[{m}], 8u);
|
| 55 |
+
accHi[{m}] += metal::simd_shuffle_down(accHi[{m}], 16u);
|
| 56 |
+
accHi[{m}] += metal::simd_shuffle_down(accHi[{m}], 8u);"""
|
| 57 |
+
for m in range(M))
|
| 58 |
+
writes = "\n".join(
|
| 59 |
+
f" out[{m}u * {rows}u + row] = "
|
| 60 |
+
f"metal::fma(slo[row], accLo[{m}], shi[row] * accHi[{m}]);"
|
| 61 |
+
for m in range(M))
|
| 62 |
+
return f"""
|
| 63 |
+
uint tid = thread_position_in_threadgroup.x;
|
| 64 |
+
uint lane = tid & 31u;
|
| 65 |
+
uint sg = tid >> 5u;
|
| 66 |
+
uint blk = threadgroup_position_in_grid.x * {SG_PER_TG}u + sg;
|
| 67 |
+
uint row0 = blk * 8u;
|
| 68 |
+
if (row0 >= {rows}u) return;
|
| 69 |
+
uint r = lane & 7u;
|
| 70 |
+
uint wsl = lane >> 3u;
|
| 71 |
+
const device uint* p32 = (const device uint*)bp;
|
| 72 |
+
const device uint* n32 = (const device uint*)bn;
|
| 73 |
+
const device uint* h32 = (const device uint*)br;
|
| 74 |
+
const device float4* x4 = (const device float4*)x;
|
| 75 |
+
float accLo[{M}], accHi[{M}];
|
| 76 |
+
for (uint m = 0u; m < {M}u; ++m) {{ accLo[m] = 0.0f; accHi[m] = 0.0f; }}
|
| 77 |
+
for (uint wl = wsl; wl < {W}u; wl += 4u) {{
|
| 78 |
+
ulong base = ((ulong)blk * {W}u + wl) * 8u + r;
|
| 79 |
+
uint pw = p32[base], nw2 = n32[base], hw = h32[base];
|
| 80 |
+
uint a = pw & ~hw, b = nw2 & ~hw, cc = pw & hw, d = nw2 & hw;
|
| 81 |
+
{kbody}
|
| 82 |
+
}}
|
| 83 |
+
{outs}
|
| 84 |
+
if (lane < 8u) {{
|
| 85 |
+
uint row = row0 + lane;
|
| 86 |
+
{writes}
|
| 87 |
+
}}
|
| 88 |
+
"""
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def _s8_gemm_m_source(M, rows, cols, rs):
|
| 92 |
+
assert rs % 4 == 0 and cols % 4 == 0
|
| 93 |
+
accs = "\n".join(
|
| 94 |
+
f" acc[{m}] += metal::dot(wf, x4[{m}u * {cols // 4}u + i]);"
|
| 95 |
+
for m in range(M))
|
| 96 |
+
reduces = "\n".join(
|
| 97 |
+
f""" {{
|
| 98 |
+
float a = metal::simd_sum(acc[{m}]);
|
| 99 |
+
if (lane == 0u) out[{m}u * {rows}u + row] = scale[row] * a;
|
| 100 |
+
}}""" for m in range(M))
|
| 101 |
+
return f"""
|
| 102 |
+
uint tid = thread_position_in_threadgroup.x;
|
| 103 |
+
uint lane = tid & 31u;
|
| 104 |
+
uint sg = tid >> 5u;
|
| 105 |
+
uint row = threadgroup_position_in_grid.x * {SG_PER_TG}u + sg;
|
| 106 |
+
if (row >= {rows}u) return;
|
| 107 |
+
const device char* w = (const device char*)W + (ulong)row * {rs}u;
|
| 108 |
+
const device float4* x4 = (const device float4*)x;
|
| 109 |
+
float acc[{M}];
|
| 110 |
+
for (uint m = 0u; m < {M}u; ++m) acc[m] = 0.0f;
|
| 111 |
+
for (uint i = lane; i < {cols // 4}u; i += 32u) {{
|
| 112 |
+
char4 wv = *(const device char4*)(w + i * 4u);
|
| 113 |
+
float4 wf = float4(wv.x, wv.y, wv.z, wv.w);
|
| 114 |
+
{accs}
|
| 115 |
+
}}
|
| 116 |
+
{reduces}
|
| 117 |
+
"""
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
def _get(kind, M, rows, cols, rs):
|
| 121 |
+
key = (kind, M, rows, cols, rs)
|
| 122 |
+
if key in _CACHE:
|
| 123 |
+
return _CACHE[key]
|
| 124 |
+
if kind == "trit":
|
| 125 |
+
src = _trit_gemm_m_source(M, rows, cols, rs)
|
| 126 |
+
k = mx.fast.metal_kernel(
|
| 127 |
+
name=f"tritgemm_m{M}_r{rows}_c{cols}",
|
| 128 |
+
input_names=["bp", "bn", "br", "slo", "shi", "x"],
|
| 129 |
+
output_names=["out"], source=src)
|
| 130 |
+
else:
|
| 131 |
+
src = _s8_gemm_m_source(M, rows, cols, rs)
|
| 132 |
+
k = mx.fast.metal_kernel(
|
| 133 |
+
name=f"s8gemm_m{M}_r{rows}_c{cols}",
|
| 134 |
+
input_names=["W", "scale", "x"],
|
| 135 |
+
output_names=["out"], source=src)
|
| 136 |
+
_CACHE[key] = k
|
| 137 |
+
return k
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
def trit_gemm_m(planes, slo, shi, X, stream=None):
|
| 141 |
+
"""planes v5-interleaved (KERNEL_VERSION>=5 prep). X fp32 [M, cols]
|
| 142 |
+
(already normed / activated). Returns fp32 [M, rows]."""
|
| 143 |
+
bp, bn, br = planes
|
| 144 |
+
rows, rs = bp.shape
|
| 145 |
+
M, cols = X.shape
|
| 146 |
+
k = _get("trit", M, rows, cols, rs)
|
| 147 |
+
nsg = rows // 8
|
| 148 |
+
ntg = (nsg + SG_PER_TG - 1) // SG_PER_TG
|
| 149 |
+
return k(inputs=[bp, bn, br, slo, shi, X],
|
| 150 |
+
grid=(ntg * SG_PER_TG * 32, 1, 1), threadgroup=(SG_PER_TG * 32, 1, 1),
|
| 151 |
+
output_shapes=[(M, rows)], output_dtypes=[mx.float32],
|
| 152 |
+
stream=stream)[0]
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
def s8_gemm_m(W, scale, X, stream=None):
|
| 156 |
+
"""int8 W [rows, rs], X fp32 [M, cols] (already normed). -> [M, rows]."""
|
| 157 |
+
rows, rs = W.shape
|
| 158 |
+
M, cols = X.shape
|
| 159 |
+
k = _get("s8", M, rows, cols, rs)
|
| 160 |
+
ntg = (rows + SG_PER_TG - 1) // SG_PER_TG
|
| 161 |
+
return k(inputs=[W, scale, X],
|
| 162 |
+
grid=(ntg * SG_PER_TG * 32, 1, 1), threadgroup=(SG_PER_TG * 32, 1, 1),
|
| 163 |
+
output_shapes=[(M, rows)], output_dtypes=[mx.float32],
|
| 164 |
+
stream=stream)[0]
|
mlx/fermion_mlx/model.py
ADDED
|
@@ -0,0 +1,281 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""End-to-end MLX/Metal decode for TRTC v4 **arch-3 (Qwen3)** five-value
|
| 3 |
+
containers — Neutrino-0.6B (tied int8 embeds) and Neutrino-8B (untied).
|
| 4 |
+
|
| 5 |
+
Every transformer linear runs on the banked five-value {0,±s_lo,±s_hi}
|
| 6 |
+
3-bit-planar Metal GEMV kernels (vendored trit_kernels.py, byte-identical
|
| 7 |
+
to kernels/metal/trit_kernels.py): qkv / o / gate+up / down fused per layer
|
| 8 |
+
+ the int8 lm_head GEMV. The arch-3 delta — per-head Q/K-RMSNorm before
|
| 9 |
+
rope, biasless qkv, int8 embed_in — is handled by the new fused
|
| 10 |
+
qknorm_rope_append kernel (arch3.py) and an int8 embedding dequant.
|
| 11 |
+
|
| 12 |
+
Fusions per decode token: rmsnorm folded into qkv/gu/lm_head GEMVs, SwiGLU
|
| 13 |
+
folded into the down GEMV, residual adds folded into o/down epilogues,
|
| 14 |
+
QK-norm+rope+KV-append in one dispatch. MLX supplies SDPA, argmax and the
|
| 15 |
+
lazy-graph dispatch batching.
|
| 16 |
+
|
| 17 |
+
Two forward implementations:
|
| 18 |
+
_step_fn : primary path (fused kernel, preallocated KV + additive mask,
|
| 19 |
+
mx.compile-able) — the shipped/gated/benched path.
|
| 20 |
+
step_ref : pure-MLX-ops reference twin for the NEW kernel (mx.fast
|
| 21 |
+
rms_norm/rope, concat KV) — used by the path-consistency gate.
|
| 22 |
+
"""
|
| 23 |
+
import time
|
| 24 |
+
|
| 25 |
+
import mlx.core as mx
|
| 26 |
+
import numpy as np
|
| 27 |
+
|
| 28 |
+
from .container import read_container
|
| 29 |
+
from .trit_kernels import trit_gemv, s8_gemv, prep_planes
|
| 30 |
+
from . import trit_kernels
|
| 31 |
+
from .arch3 import qknorm_rope_append, s8_gemv_v3
|
| 32 |
+
|
| 33 |
+
NEG_INF = -1e9
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
class _Layer:
|
| 37 |
+
pass
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def _cat(arrs):
|
| 41 |
+
return np.ascontiguousarray(np.concatenate(arrs, axis=0))
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
class MetalTritQwen3:
|
| 45 |
+
"""TRTC v4 arch-3 walker (0.6B or 8B, dims from the container header)."""
|
| 46 |
+
|
| 47 |
+
def __init__(self, path, maxseq=1024, verbose=True):
|
| 48 |
+
t0 = time.time()
|
| 49 |
+
c = read_container(path)
|
| 50 |
+
self.meta = c
|
| 51 |
+
self.eps, self.theta = float(c.eps), float(c.theta)
|
| 52 |
+
self.n_layers, self.hidden = c.n_layers, c.hidden
|
| 53 |
+
self.n_heads, self.n_kv, self.hd = c.n_heads, c.n_kv, c.head_dim
|
| 54 |
+
self.inter, self.vocab, self.eok = c.inter, c.vocab, c.eok
|
| 55 |
+
assert c.rot == self.hd, "kernels assume full-head_dim rope"
|
| 56 |
+
self.scale = self.hd ** -0.5
|
| 57 |
+
self.qd = self.n_heads * self.hd
|
| 58 |
+
self.kd = self.n_kv * self.hd
|
| 59 |
+
self.maxseq = maxseq
|
| 60 |
+
|
| 61 |
+
# int8 lm_head (+ tied/untied int8 embed_in)
|
| 62 |
+
eo = c.embed_out
|
| 63 |
+
self.eoW = mx.array(np.ascontiguousarray(eo.W)) # int8 [V, H]
|
| 64 |
+
self.eo_scale = mx.array(np.ascontiguousarray(eo.scale))
|
| 65 |
+
mx.eval(self.eoW, self.eo_scale)
|
| 66 |
+
if c.eok == 2:
|
| 67 |
+
ei = c.embed_in
|
| 68 |
+
self.embW = mx.array(np.ascontiguousarray(ei.W)) # int8 [V, H]
|
| 69 |
+
self.embS = mx.array(np.ascontiguousarray(ei.scale))
|
| 70 |
+
mx.eval(self.embW, self.embS)
|
| 71 |
+
else:
|
| 72 |
+
self.embW, self.embS = self.eoW, self.eo_scale
|
| 73 |
+
|
| 74 |
+
self.layers = []
|
| 75 |
+
for L in c.layers:
|
| 76 |
+
q, k, v, o, gate, up, down = (L[n] for n in
|
| 77 |
+
("q", "k", "v", "o", "gate", "up", "down"))
|
| 78 |
+
y = _Layer()
|
| 79 |
+
y.norm1 = mx.array(np.ascontiguousarray(L["norm1"]))
|
| 80 |
+
y.norm2 = mx.array(np.ascontiguousarray(L["norm2"]))
|
| 81 |
+
y.qnorm = mx.array(np.ascontiguousarray(L["qnorm"]))
|
| 82 |
+
y.knorm = mx.array(np.ascontiguousarray(L["knorm"]))
|
| 83 |
+
y.qkv_planes = prep_planes(tuple(
|
| 84 |
+
mx.array(_cat([getattr(r, pl) for r in (q, k, v)]))
|
| 85 |
+
for pl in ("bp", "bn", "br")), q.cols)
|
| 86 |
+
y.qkv_slo = mx.array(_cat([q.slo, k.slo, v.slo]))
|
| 87 |
+
y.qkv_shi = mx.array(_cat([q.shi, k.shi, v.shi]))
|
| 88 |
+
y.o_planes = prep_planes(tuple(
|
| 89 |
+
mx.array(np.ascontiguousarray(getattr(o, pl)))
|
| 90 |
+
for pl in ("bp", "bn", "br")), o.cols)
|
| 91 |
+
y.o_slo = mx.array(np.ascontiguousarray(o.slo))
|
| 92 |
+
y.o_shi = mx.array(np.ascontiguousarray(o.shi))
|
| 93 |
+
y.gu_planes = prep_planes(tuple(
|
| 94 |
+
mx.array(_cat([getattr(gate, pl), getattr(up, pl)]))
|
| 95 |
+
for pl in ("bp", "bn", "br")), gate.cols)
|
| 96 |
+
y.gu_slo = mx.array(_cat([gate.slo, up.slo]))
|
| 97 |
+
y.gu_shi = mx.array(_cat([gate.shi, up.shi]))
|
| 98 |
+
y.dn_planes = prep_planes(tuple(
|
| 99 |
+
mx.array(np.ascontiguousarray(getattr(down, pl)))
|
| 100 |
+
for pl in ("bp", "bn", "br")), down.cols)
|
| 101 |
+
y.dn_slo = mx.array(np.ascontiguousarray(down.slo))
|
| 102 |
+
y.dn_shi = mx.array(np.ascontiguousarray(down.shi))
|
| 103 |
+
mx.eval(*(y.qkv_planes + y.o_planes + y.gu_planes + y.dn_planes),
|
| 104 |
+
y.qkv_slo, y.qkv_shi, y.o_slo, y.o_shi,
|
| 105 |
+
y.gu_slo, y.gu_shi, y.dn_slo, y.dn_shi,
|
| 106 |
+
y.norm1, y.norm2, y.qnorm, y.knorm)
|
| 107 |
+
self.layers.append(y)
|
| 108 |
+
self.final_norm = mx.array(np.ascontiguousarray(c.final_norm))
|
| 109 |
+
|
| 110 |
+
# rope tables + preallocated KV + mask
|
| 111 |
+
inv = self.theta ** -(np.arange(0, self.hd, 2, dtype=np.float64) / self.hd)
|
| 112 |
+
ang = np.outer(np.arange(maxseq, dtype=np.float64), inv)
|
| 113 |
+
self.ct = mx.array(np.cos(ang).astype(np.float32).ravel())
|
| 114 |
+
self.st = mx.array(np.sin(ang).astype(np.float32).ravel())
|
| 115 |
+
self.kc = [mx.zeros((self.n_kv, maxseq, self.hd), dtype=mx.float32)
|
| 116 |
+
for _ in self.layers]
|
| 117 |
+
self.vc = [mx.zeros((self.n_kv, maxseq, self.hd), dtype=mx.float32)
|
| 118 |
+
for _ in self.layers]
|
| 119 |
+
mx.eval(self.ct, self.st, self.final_norm, *self.kc, *self.vc)
|
| 120 |
+
self._stepc = None
|
| 121 |
+
# x-restaged int8 unembed when the row count allows it
|
| 122 |
+
self._s8 = s8_gemv_v3 if self.vocab % 64 == 0 else s8_gemv
|
| 123 |
+
if verbose:
|
| 124 |
+
gb = mx.get_active_memory() / 2**30
|
| 125 |
+
print(f"loaded {path} in {time.time()-t0:.1f}s (arch-3, "
|
| 126 |
+
f"{self.n_layers}L/{self.hidden}H/{self.n_heads}q/{self.n_kv}kv/"
|
| 127 |
+
f"{self.inter}i/{self.vocab}V eok={self.eok}, kernel v"
|
| 128 |
+
f"{trit_kernels.KERNEL_VERSION}, maxseq {maxseq}, "
|
| 129 |
+
f"active mem {gb:.2f} GiB)")
|
| 130 |
+
|
| 131 |
+
# ---------------- primary path ----------------
|
| 132 |
+
|
| 133 |
+
def new_mask(self):
|
| 134 |
+
m = mx.full((self.maxseq,), NEG_INF, dtype=mx.float32)
|
| 135 |
+
mx.eval(m)
|
| 136 |
+
return m
|
| 137 |
+
|
| 138 |
+
def embed(self, tok):
|
| 139 |
+
"""int8 embedding dequant: fp32 row = scale[tok] * W8[tok]."""
|
| 140 |
+
return self.embW[tok].astype(mx.float32) * self.embS[tok]
|
| 141 |
+
|
| 142 |
+
def _window(self, pos):
|
| 143 |
+
"""KV window for position pos: grown in 256-slot blocks. Slicing off
|
| 144 |
+
the -1e9-masked tail is numerically exact (exp underflows to 0.0 in
|
| 145 |
+
fp32), so shrinking the SDPA read never changes the math — it only
|
| 146 |
+
stops streaming megabytes of masked cache per step."""
|
| 147 |
+
return min(self.maxseq, ((pos + 1 + 255) // 256) * 256)
|
| 148 |
+
|
| 149 |
+
def _step_fn(self, x, posbuf, mask, W):
|
| 150 |
+
"""x fp32 [H] at position posbuf[0] -> (next_tok, x_next, logits).
|
| 151 |
+
Fixed shapes per W: SDPA over the first W cache slots with additive
|
| 152 |
+
mask (stale slots -1e9; qknorm_rope_append maintains mask[pos]=0)."""
|
| 153 |
+
eps = self.eps
|
| 154 |
+
m4 = mask[:W].reshape(1, 1, 1, W)
|
| 155 |
+
for li, L in enumerate(self.layers):
|
| 156 |
+
qkv = trit_gemv(L.qkv_planes, L.qkv_slo, L.qkv_shi, x,
|
| 157 |
+
norm_w=L.norm1, eps=eps)
|
| 158 |
+
q = qknorm_rope_append(qkv, L.qnorm, L.knorm, self.kc[li], self.vc[li],
|
| 159 |
+
self.ct, self.st, posbuf, mask,
|
| 160 |
+
self.n_heads, self.n_kv, self.hd,
|
| 161 |
+
self.maxseq, eps)
|
| 162 |
+
q = q.reshape(1, self.n_heads, 1, self.hd)
|
| 163 |
+
a = mx.fast.scaled_dot_product_attention(
|
| 164 |
+
q, self.kc[li][:, :W][None], self.vc[li][:, :W][None],
|
| 165 |
+
scale=self.scale, mask=m4)
|
| 166 |
+
a = a.reshape(self.qd)
|
| 167 |
+
x = trit_gemv(L.o_planes, L.o_slo, L.o_shi, a, resid=x, eps=eps)
|
| 168 |
+
gu = trit_gemv(L.gu_planes, L.gu_slo, L.gu_shi, x,
|
| 169 |
+
norm_w=L.norm2, eps=eps)
|
| 170 |
+
x = trit_gemv(L.dn_planes, L.dn_slo, L.dn_shi, gu, glu=True,
|
| 171 |
+
resid=x, eps=eps)
|
| 172 |
+
logits = self._s8(self.eoW, self.eo_scale, x, norm_w=self.final_norm, eps=eps)
|
| 173 |
+
nt = mx.argmax(logits)
|
| 174 |
+
return nt, self.embed(nt), logits
|
| 175 |
+
|
| 176 |
+
def step(self, x, pos, mask):
|
| 177 |
+
"""uncompiled single step (gates)."""
|
| 178 |
+
return self._step_fn(x, mx.array([pos], dtype=mx.int32), mask,
|
| 179 |
+
self._window(pos))
|
| 180 |
+
|
| 181 |
+
def step_compiled(self, x, posbuf, mask, W):
|
| 182 |
+
if self._stepc is None:
|
| 183 |
+
self._stepc = {}
|
| 184 |
+
if W not in self._stepc:
|
| 185 |
+
self._stepc[W] = mx.compile(
|
| 186 |
+
lambda xx, pb, mk, _W=W: self._step_fn(xx, pb, mk, _W))
|
| 187 |
+
return self._stepc[W](x, posbuf, mask)
|
| 188 |
+
|
| 189 |
+
def prefill(self, ids, mask, compiled=True):
|
| 190 |
+
"""feed ids[:-1]; returns (x armed with ids[-1], pos of ids[-1])."""
|
| 191 |
+
for i, t in enumerate(ids[:-1]):
|
| 192 |
+
pb = mx.array([i], dtype=mx.int32)
|
| 193 |
+
W = self._window(i)
|
| 194 |
+
if compiled:
|
| 195 |
+
nt, xn, lg = self.step_compiled(self.embed(int(t)), pb, mask, W)
|
| 196 |
+
else:
|
| 197 |
+
nt, xn, lg = self._step_fn(self.embed(int(t)), pb, mask, W)
|
| 198 |
+
mx.eval(lg)
|
| 199 |
+
x = self.embed(int(ids[-1]))
|
| 200 |
+
mx.eval(x)
|
| 201 |
+
return x, len(ids) - 1
|
| 202 |
+
|
| 203 |
+
def generate(self, ids, n, compiled=True, eos=None):
|
| 204 |
+
"""greedy decode n tokens after prompt ids; returns (tokens,
|
| 205 |
+
decode_seconds). eos: optional set of stop ids (stop AFTER emitting)."""
|
| 206 |
+
mask = self.new_mask()
|
| 207 |
+
x, pos0 = self.prefill(ids, mask, compiled=compiled)
|
| 208 |
+
toks = []
|
| 209 |
+
prev = None
|
| 210 |
+
t0 = time.perf_counter()
|
| 211 |
+
for i in range(n):
|
| 212 |
+
pos = pos0 + i
|
| 213 |
+
pb = mx.array([pos], dtype=mx.int32)
|
| 214 |
+
W = self._window(pos)
|
| 215 |
+
if compiled:
|
| 216 |
+
nt, x, lg = self.step_compiled(x, pb, mask, W)
|
| 217 |
+
else:
|
| 218 |
+
nt, x, lg = self._step_fn(x, pb, mask, W)
|
| 219 |
+
mx.async_eval(nt, x)
|
| 220 |
+
if prev is not None:
|
| 221 |
+
toks.append(int(prev))
|
| 222 |
+
if eos and toks[-1] in eos:
|
| 223 |
+
prev = None
|
| 224 |
+
break
|
| 225 |
+
prev = nt
|
| 226 |
+
if prev is not None:
|
| 227 |
+
toks.append(int(prev))
|
| 228 |
+
mx.eval(x)
|
| 229 |
+
dt = time.perf_counter() - t0
|
| 230 |
+
return toks, dt
|
| 231 |
+
|
| 232 |
+
# ---------------- pure-MLX-ops reference twin ----------------
|
| 233 |
+
|
| 234 |
+
def step_ref(self, x, kv, pos):
|
| 235 |
+
"""same GEMV kernels, but QK-norm/rope/cache via mx.fast ops +
|
| 236 |
+
concat KV — the cross-check for the NEW fused kernel."""
|
| 237 |
+
eps = self.eps
|
| 238 |
+
for li, L in enumerate(self.layers):
|
| 239 |
+
qkv = trit_gemv(L.qkv_planes, L.qkv_slo, L.qkv_shi, x,
|
| 240 |
+
norm_w=L.norm1, eps=eps)
|
| 241 |
+
q = mx.fast.rms_norm(qkv[:self.qd].reshape(self.n_heads, self.hd),
|
| 242 |
+
L.qnorm, eps)
|
| 243 |
+
k = mx.fast.rms_norm(qkv[self.qd:self.qd + self.kd]
|
| 244 |
+
.reshape(self.n_kv, self.hd), L.knorm, eps)
|
| 245 |
+
qk = mx.concatenate([q, k], axis=0).reshape(
|
| 246 |
+
1, self.n_heads + self.n_kv, 1, self.hd)
|
| 247 |
+
qk = mx.fast.rope(qk, self.hd, traditional=False, base=self.theta,
|
| 248 |
+
scale=1.0, offset=pos)
|
| 249 |
+
qh = qk[:, :self.n_heads]
|
| 250 |
+
kh = qk[:, self.n_heads:]
|
| 251 |
+
vh = qkv[self.qd + self.kd:].reshape(1, self.n_kv, 1, self.hd)
|
| 252 |
+
if kv[li] is None:
|
| 253 |
+
kv[li] = (kh, vh)
|
| 254 |
+
else:
|
| 255 |
+
kv[li] = (mx.concatenate([kv[li][0], kh], axis=2),
|
| 256 |
+
mx.concatenate([kv[li][1], vh], axis=2))
|
| 257 |
+
keys, values = kv[li]
|
| 258 |
+
a = mx.fast.scaled_dot_product_attention(qh, keys, values,
|
| 259 |
+
scale=self.scale)
|
| 260 |
+
a = a.reshape(self.qd)
|
| 261 |
+
x = trit_gemv(L.o_planes, L.o_slo, L.o_shi, a, resid=x, eps=eps)
|
| 262 |
+
gu = trit_gemv(L.gu_planes, L.gu_slo, L.gu_shi, x,
|
| 263 |
+
norm_w=L.norm2, eps=eps)
|
| 264 |
+
x = trit_gemv(L.dn_planes, L.dn_slo, L.dn_shi, gu, glu=True,
|
| 265 |
+
resid=x, eps=eps)
|
| 266 |
+
return self._s8(self.eoW, self.eo_scale, x, norm_w=self.final_norm, eps=eps)
|
| 267 |
+
|
| 268 |
+
def generate_ref(self, ids, n):
|
| 269 |
+
kv = [None] * self.n_layers
|
| 270 |
+
for i, t in enumerate(ids[:-1]):
|
| 271 |
+
lg = self.step_ref(self.embed(int(t)), kv, i)
|
| 272 |
+
mx.eval(lg)
|
| 273 |
+
x = self.embed(int(ids[-1]))
|
| 274 |
+
toks = []
|
| 275 |
+
for i in range(n):
|
| 276 |
+
lg = self.step_ref(x, kv, len(ids) - 1 + i)
|
| 277 |
+
nt = mx.argmax(lg)
|
| 278 |
+
x = self.embed(nt)
|
| 279 |
+
mx.eval(x)
|
| 280 |
+
toks.append(int(nt))
|
| 281 |
+
return toks
|
mlx/fermion_mlx/spec.py
ADDED
|
@@ -0,0 +1,256 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""EXPERIMENTAL speculative decoding: 0.6B pack drafts, 8B pack verifies.
|
| 3 |
+
|
| 4 |
+
House exactness rule (greedy-exact acceptance): a draft token is accepted
|
| 5 |
+
iff it equals the verifier's argmax at that position. The emitted stream is
|
| 6 |
+
therefore exactly the verifier's own greedy stream *as computed by the
|
| 7 |
+
batched verify path* — the ship gate (specgate) checks that stream is
|
| 8 |
+
token-identical to the shipped single-token path at 32 and 256 tokens.
|
| 9 |
+
|
| 10 |
+
Mechanics per round (k=4 draft tokens, M=k+1 verify batch):
|
| 11 |
+
1. draft consumes its pending tokens, then chains k greedy proposals
|
| 12 |
+
d_1..d_k on its own single-token path;
|
| 13 |
+
2. verifier scores [pending[-1], d_1..d_k] in ONE batched pass
|
| 14 |
+
(mbatch.trit_gemm_m / s8_gemm_m — weight planes stream once, which is
|
| 15 |
+
the entire speedup mechanism on a bandwidth-bound GPU);
|
| 16 |
+
3. accept the longest prefix with d_i == argmax_{i-1}; emit accepted
|
| 17 |
+
tokens + the verifier's own next token (correction or bonus);
|
| 18 |
+
4. KV rewind: positions are the source of truth. The verifier's stale
|
| 19 |
+
rows get overwritten by the next round's appends before any read (the
|
| 20 |
+
verify mask only allows slots <= row position). The draft re-masks its
|
| 21 |
+
stale slots (additive mask back to -1e9) and rolls its position back.
|
| 22 |
+
|
| 23 |
+
Both models live in one process; run under --mem-limit-gb 6 (house OOM
|
| 24 |
+
rule). Additive module: model.py / gates.py / existing CLI modes untouched.
|
| 25 |
+
"""
|
| 26 |
+
import json
|
| 27 |
+
import time
|
| 28 |
+
|
| 29 |
+
import mlx.core as mx
|
| 30 |
+
|
| 31 |
+
from .mbatch import trit_gemm_m, s8_gemm_m
|
| 32 |
+
|
| 33 |
+
NEG_INF = -1e9
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def _silu(g):
|
| 37 |
+
return g * mx.sigmoid(g)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class SpecDecoder:
|
| 41 |
+
def __init__(self, verifier, draft, k=4):
|
| 42 |
+
assert verifier.vocab == draft.vocab, "draft/verifier vocab mismatch"
|
| 43 |
+
self.V, self.D, self.k = verifier, draft, k
|
| 44 |
+
self._vc = {} # compiled verify fns keyed (M, W)
|
| 45 |
+
self._specmask = verifier.new_mask() # kernel side-channel, never read
|
| 46 |
+
self.stats = None
|
| 47 |
+
|
| 48 |
+
# ---------------- batched verify ----------------
|
| 49 |
+
|
| 50 |
+
def _verify(self, toks, pos0):
|
| 51 |
+
from .arch3 import qknorm_rope_append
|
| 52 |
+
V = self.V
|
| 53 |
+
M = len(toks)
|
| 54 |
+
W = V._window(pos0 + M - 1)
|
| 55 |
+
key = (M, W)
|
| 56 |
+
if key not in self._vc:
|
| 57 |
+
# build an uncompiled closure once; mx.compile wrapper around it
|
| 58 |
+
eps = V.eps
|
| 59 |
+
|
| 60 |
+
def fn(X, pbs, amask, _M=M, _W=W):
|
| 61 |
+
m4 = amask.reshape(1, 1, _M, _W)
|
| 62 |
+
for li, L in enumerate(V.layers):
|
| 63 |
+
qkv = trit_gemm_m(L.qkv_planes, L.qkv_slo, L.qkv_shi,
|
| 64 |
+
mx.fast.rms_norm(X, L.norm1, eps))
|
| 65 |
+
qrows = [qknorm_rope_append(
|
| 66 |
+
qkv[m], L.qnorm, L.knorm, V.kc[li], V.vc[li],
|
| 67 |
+
V.ct, V.st, pbs[m:m + 1], self._specmask,
|
| 68 |
+
V.n_heads, V.n_kv, V.hd, V.maxseq, eps)
|
| 69 |
+
for m in range(_M)]
|
| 70 |
+
q = mx.stack(qrows).reshape(_M, V.n_heads, V.hd)
|
| 71 |
+
q = q.transpose(1, 0, 2)[None]
|
| 72 |
+
a = mx.fast.scaled_dot_product_attention(
|
| 73 |
+
q, V.kc[li][:, :_W][None], V.vc[li][:, :_W][None],
|
| 74 |
+
scale=V.scale, mask=m4)
|
| 75 |
+
a = a[0].transpose(1, 0, 2).reshape(_M, V.qd)
|
| 76 |
+
X = X + trit_gemm_m(L.o_planes, L.o_slo, L.o_shi, a)
|
| 77 |
+
gu = trit_gemm_m(L.gu_planes, L.gu_slo, L.gu_shi,
|
| 78 |
+
mx.fast.rms_norm(X, L.norm2, eps))
|
| 79 |
+
h = _silu(gu[:, :V.inter]) * gu[:, V.inter:]
|
| 80 |
+
X = X + trit_gemm_m(L.dn_planes, L.dn_slo, L.dn_shi, h)
|
| 81 |
+
logits = s8_gemm_m(V.eoW, V.eo_scale,
|
| 82 |
+
mx.fast.rms_norm(X, V.final_norm, eps))
|
| 83 |
+
return mx.argmax(logits, axis=1)
|
| 84 |
+
|
| 85 |
+
self._vc[key] = mx.compile(fn)
|
| 86 |
+
tarr = mx.array(toks, dtype=mx.int32)
|
| 87 |
+
X = V.embW[tarr].astype(mx.float32) * V.embS[tarr][:, None]
|
| 88 |
+
pbs = mx.array([pos0 + m for m in range(M)], dtype=mx.int32)
|
| 89 |
+
idx = mx.arange(W)[None, :]
|
| 90 |
+
rowp = (pos0 + mx.arange(M))[:, None]
|
| 91 |
+
amask = mx.where(idx <= rowp, mx.array(0.0, dtype=mx.float32),
|
| 92 |
+
mx.array(NEG_INF, dtype=mx.float32))
|
| 93 |
+
am = self._vc[(M, W)](X, pbs, amask)
|
| 94 |
+
mx.eval(am)
|
| 95 |
+
return [int(t) for t in am]
|
| 96 |
+
|
| 97 |
+
# ---------------- draft helpers (single-token path) ----------------
|
| 98 |
+
|
| 99 |
+
def _draft_chain(self, pending, dmask, dpos):
|
| 100 |
+
"""consume pending tokens, then chain k greedy proposals.
|
| 101 |
+
Returns (proposals, new_dpos). Draft KV/mask advance by
|
| 102 |
+
len(pending) + k - 1 consumed inputs."""
|
| 103 |
+
# NOTE: each step is dispatched with async_eval before the next is
|
| 104 |
+
# built — the KV/mask writes are input mutations invisible to the
|
| 105 |
+
# lazy graph, and batching several steps into one evaluation makes
|
| 106 |
+
# MLX copy-on-write the caches and silently drop intermediate
|
| 107 |
+
# appends (found the hard way; the shipped generate() uses the same
|
| 108 |
+
# per-step dispatch discipline).
|
| 109 |
+
D, k = self.D, self.k
|
| 110 |
+
pos = dpos
|
| 111 |
+
nt = x = None
|
| 112 |
+
for t in pending:
|
| 113 |
+
pb = mx.array([pos], dtype=mx.int32)
|
| 114 |
+
nt, x, _ = D.step_compiled(D.embed(int(t)), pb, dmask,
|
| 115 |
+
D._window(pos))
|
| 116 |
+
mx.async_eval(nt, x)
|
| 117 |
+
pos += 1
|
| 118 |
+
props = [nt] # d_1
|
| 119 |
+
for _ in range(k - 1):
|
| 120 |
+
pb = mx.array([pos], dtype=mx.int32)
|
| 121 |
+
nt, x, _ = D.step_compiled(x, pb, dmask, D._window(pos))
|
| 122 |
+
mx.async_eval(nt, x)
|
| 123 |
+
props.append(nt)
|
| 124 |
+
pos += 1
|
| 125 |
+
mx.eval(*props)
|
| 126 |
+
return [int(p) for p in props], pos
|
| 127 |
+
|
| 128 |
+
# ---------------- main loop ----------------
|
| 129 |
+
|
| 130 |
+
def generate(self, ids, n):
|
| 131 |
+
"""greedy spec decode: n tokens after prompt ids.
|
| 132 |
+
Returns (tokens, decode_seconds, stats)."""
|
| 133 |
+
V, D, k = self.V, self.D, self.k
|
| 134 |
+
vmask = V.new_mask()
|
| 135 |
+
dmask = D.new_mask()
|
| 136 |
+
_, p0 = V.prefill(ids, vmask)
|
| 137 |
+
_, _ = D.prefill(ids, dmask)
|
| 138 |
+
# both consumed ids[:-1]; next input ids[-1] at position p0
|
| 139 |
+
pending = [int(ids[-1])]
|
| 140 |
+
vpos = dpos = p0
|
| 141 |
+
toks = []
|
| 142 |
+
rounds = accepted = proposed = 0
|
| 143 |
+
hist = [0] * (k + 1)
|
| 144 |
+
t0 = time.perf_counter()
|
| 145 |
+
while len(toks) < n:
|
| 146 |
+
# invariant: pending[-1] sits at position vpos for the verifier
|
| 147 |
+
# and at dpos + len(pending) - 1 for the draft — same slot.
|
| 148 |
+
assert dpos + len(pending) - 1 == vpos, (dpos, len(pending), vpos)
|
| 149 |
+
props, dpos_after = self._draft_chain(pending, dmask, dpos)
|
| 150 |
+
am = self._verify([pending[-1]] + props, vpos)
|
| 151 |
+
nacc = 0
|
| 152 |
+
for i in range(k):
|
| 153 |
+
if props[i] == am[i]:
|
| 154 |
+
nacc += 1
|
| 155 |
+
else:
|
| 156 |
+
break
|
| 157 |
+
rounds += 1
|
| 158 |
+
proposed += k
|
| 159 |
+
accepted += nacc
|
| 160 |
+
hist[nacc] += 1
|
| 161 |
+
base = vpos # position of pending[-1]
|
| 162 |
+
if nacc == k:
|
| 163 |
+
emitted = props + [am[k]]
|
| 164 |
+
pending = [props[k - 1], am[k]]
|
| 165 |
+
# draft consumed through d_{k-1} at base+k-1: no rewind
|
| 166 |
+
dpos = dpos_after
|
| 167 |
+
else:
|
| 168 |
+
emitted = props[:nacc] + [am[nacc]]
|
| 169 |
+
pending = [am[nacc]]
|
| 170 |
+
# draft rewind: valid through input d_nacc at base+nacc
|
| 171 |
+
lo, hi = base + nacc + 1, dpos_after
|
| 172 |
+
if hi > lo:
|
| 173 |
+
dmask[lo:hi] = NEG_INF
|
| 174 |
+
dpos = lo
|
| 175 |
+
vpos = base + nacc + 1 # verifier valid: <= base+nacc
|
| 176 |
+
toks.extend(emitted)
|
| 177 |
+
dt = time.perf_counter() - t0
|
| 178 |
+
toks = toks[:n]
|
| 179 |
+
self.stats = {
|
| 180 |
+
"k": k, "rounds": rounds, "proposed": proposed,
|
| 181 |
+
"accepted": accepted,
|
| 182 |
+
"acceptance": round(accepted / max(proposed, 1), 4),
|
| 183 |
+
"tokens_per_round": round(len(toks) / max(rounds, 1), 3),
|
| 184 |
+
"accept_hist": hist,
|
| 185 |
+
}
|
| 186 |
+
return toks, dt, self.stats
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
# ---------------- gate + bench drivers ----------------
|
| 190 |
+
|
| 191 |
+
GATE_PROMPTS = {
|
| 192 |
+
"facts": "The three laws of thermodynamics state that",
|
| 193 |
+
"prose": "Once upon a time, in a quiet village by the sea,",
|
| 194 |
+
"chat": "User: How do I reverse a list in Python?\nAssistant:",
|
| 195 |
+
}
|
| 196 |
+
|
| 197 |
+
|
| 198 |
+
def specgate(verifier, draft, tok, k=4, lens=(32, 256), receipt=None):
|
| 199 |
+
"""[G1 frozen] WITH --spec vs WITHOUT: token-identical at each length,
|
| 200 |
+
every prompt. Any mismatch = flag does not ship."""
|
| 201 |
+
sd = SpecDecoder(verifier, draft, k=k)
|
| 202 |
+
out = {"gate": "specgate", "k": k, "lens": list(lens), "prompts": {},
|
| 203 |
+
"verdict": "PASS"}
|
| 204 |
+
for name, text in GATE_PROMPTS.items():
|
| 205 |
+
ids = tok(text, add_special_tokens=False).input_ids if tok else None
|
| 206 |
+
assert ids, "specgate needs a tokenizer"
|
| 207 |
+
res = {}
|
| 208 |
+
for n in lens:
|
| 209 |
+
plain, _ = verifier.generate(ids, n)
|
| 210 |
+
spec, _, st = sd.generate(ids, n)
|
| 211 |
+
same = plain == spec
|
| 212 |
+
first = next((i for i, (a, b) in enumerate(zip(plain, spec))
|
| 213 |
+
if a != b), -1)
|
| 214 |
+
res[str(n)] = {"identical": same, "first_divergence": first,
|
| 215 |
+
"acceptance": st["acceptance"]}
|
| 216 |
+
if not same:
|
| 217 |
+
out["verdict"] = "FAIL"
|
| 218 |
+
print(f" [{name} n={n}] identical={same} "
|
| 219 |
+
f"acc={st['acceptance']:.3f}", flush=True)
|
| 220 |
+
out["prompts"][name] = res
|
| 221 |
+
if receipt:
|
| 222 |
+
json.dump(out, open(receipt, "w"), indent=1)
|
| 223 |
+
print(f"receipt -> {receipt}")
|
| 224 |
+
return out
|
| 225 |
+
|
| 226 |
+
|
| 227 |
+
def specbench(verifier, draft, tok, k=4, n=512, runs=3, receipt=None):
|
| 228 |
+
"""[G2 frozen] plain-8B vs 8B+draft medians, 3 text types, same process."""
|
| 229 |
+
sd = SpecDecoder(verifier, draft, k=k)
|
| 230 |
+
out = {"bench": "specbench", "k": k, "n": n, "runs": runs, "types": {}}
|
| 231 |
+
for name, text in GATE_PROMPTS.items():
|
| 232 |
+
ids = tok(text, add_special_tokens=False).input_ids
|
| 233 |
+
plain_r, spec_r, accs = [], [], []
|
| 234 |
+
for r in range(runs):
|
| 235 |
+
t, dt = verifier.generate(ids, n)
|
| 236 |
+
plain_r.append(n / dt)
|
| 237 |
+
st_toks, sdt, st = sd.generate(ids, n)
|
| 238 |
+
spec_r.append(n / sdt)
|
| 239 |
+
accs.append(st["acceptance"])
|
| 240 |
+
print(f" [{name} run{r}] plain {plain_r[-1]:.1f} tok/s | "
|
| 241 |
+
f"spec {spec_r[-1]:.1f} tok/s | acc {accs[-1]:.3f} | "
|
| 242 |
+
f"tok/round {st['tokens_per_round']}", flush=True)
|
| 243 |
+
med = lambda v: sorted(v)[len(v) // 2]
|
| 244 |
+
out["types"][name] = {
|
| 245 |
+
"plain_median_tok_s": round(med(plain_r), 2),
|
| 246 |
+
"spec_median_tok_s": round(med(spec_r), 2),
|
| 247 |
+
"speedup": round(med(spec_r) / med(plain_r), 3),
|
| 248 |
+
"acceptance_median": round(med(accs), 4),
|
| 249 |
+
"plain_runs": [round(v, 2) for v in plain_r],
|
| 250 |
+
"spec_runs": [round(v, 2) for v in spec_r],
|
| 251 |
+
}
|
| 252 |
+
out["peak_mem_gib"] = round(mx.get_peak_memory() / 2**30, 2)
|
| 253 |
+
if receipt:
|
| 254 |
+
json.dump(out, open(receipt, "w"), indent=1)
|
| 255 |
+
print(f"receipt -> {receipt}")
|
| 256 |
+
return out
|
mlx/fermion_mlx/trit_kernels.py
ADDED
|
@@ -0,0 +1,1053 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Five-value {0, +-s_lo, +-s_hi} 3-bit-planar GEMV as mx.fast.metal_kernel.
|
| 3 |
+
|
| 4 |
+
Decode: per output row, three bit planes (bp=positive, bn=negative, br=high-mag),
|
| 5 |
+
LSB-first. sign = p - n, magnitude selects per-row s_lo / s_hi. The kernel keeps
|
| 6 |
+
two fp32 accumulators (lo/hi) so the per-row scales multiply once at the end —
|
| 7 |
+
the inner loop is pure bit-extract + FMA (the Metal twin of the NEON tbl /
|
| 8 |
+
CUDA PRMT decode; MSL has no byte shuffle, ALU decode is the standard move).
|
| 9 |
+
|
| 10 |
+
Layout: one simdgroup per output row, SG_PER_TG simdgroups per threadgroup.
|
| 11 |
+
Lanes stride uchar4 loads across the row's plane bytes; simd_sum reduction.
|
| 12 |
+
|
| 13 |
+
Template flags (baked into per-shape kernel source, cached):
|
| 14 |
+
HAS_BIAS : add fp32 bias[row]
|
| 15 |
+
FUSE_NORM : x is pre-norm; kernel computes rmsnorm(x)*g inline (g = extra input)
|
| 16 |
+
ADD_RESID : out[row] = resid[row] + y
|
| 17 |
+
GLU : down-proj mode; x has 2*INTER entries [gate;up], col value is
|
| 18 |
+
silu(gate[c]) * up[c] (fuses the SwiGLU elementwise pass into GEMV)
|
| 19 |
+
"""
|
| 20 |
+
import mlx.core as mx
|
| 21 |
+
|
| 22 |
+
_HEADER = """
|
| 23 |
+
static inline float trit_silu(float v) { return v / (1.0f + metal::exp(-v)); }
|
| 24 |
+
"""
|
| 25 |
+
|
| 26 |
+
_CACHE = {}
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
def _norm_pre(cols, eps):
|
| 30 |
+
return f"""
|
| 31 |
+
float ssq = 0.0f;
|
| 32 |
+
for (uint c = lane; c < {cols}u; c += 32u) {{ float v = x[c]; ssq = metal::fma(v, v, ssq); }}
|
| 33 |
+
ssq = metal::simd_sum(ssq);
|
| 34 |
+
float rms = metal::rsqrt(ssq / {float(cols)}f + {eps:.9g}f);
|
| 35 |
+
"""
|
| 36 |
+
|
| 37 |
+
|
| 38 |
+
def _gemv_source(rows, cols, rs, sg_per_tg, has_bias, fuse_norm, add_resid, glu, eps):
|
| 39 |
+
"""v1: per-bit scalar decode with sparsity branches."""
|
| 40 |
+
assert rs % 4 == 0, "row_stride must be multiple of 4 for uchar4 loads"
|
| 41 |
+
tail = rs * 8 != cols
|
| 42 |
+
# x effective value macro
|
| 43 |
+
if glu:
|
| 44 |
+
xv = "trit_silu(x[c]) * x[c + %d]" % cols
|
| 45 |
+
norm_pre = ""
|
| 46 |
+
elif fuse_norm:
|
| 47 |
+
xv = "x[c] * nw[c] * rms"
|
| 48 |
+
norm_pre = _norm_pre(cols, eps)
|
| 49 |
+
else:
|
| 50 |
+
xv = "x[c]"
|
| 51 |
+
norm_pre = ""
|
| 52 |
+
guard = f"if (c >= {cols}u) break;" if tail else ""
|
| 53 |
+
return f"""
|
| 54 |
+
uint tid = thread_position_in_threadgroup.x;
|
| 55 |
+
uint lane = tid & 31u;
|
| 56 |
+
uint sg = tid >> 5u;
|
| 57 |
+
uint row = threadgroup_position_in_grid.x * {sg_per_tg}u + sg;
|
| 58 |
+
if (row >= {rows}u) return;
|
| 59 |
+
{norm_pre}
|
| 60 |
+
const device uchar4* p4 = (const device uchar4*)bp + row * {rs // 4}u;
|
| 61 |
+
const device uchar4* n4 = (const device uchar4*)bn + row * {rs // 4}u;
|
| 62 |
+
const device uchar4* h4 = (const device uchar4*)br + row * {rs // 4}u;
|
| 63 |
+
float accLo = 0.0f, accHi = 0.0f;
|
| 64 |
+
for (uint i = lane; i < {rs // 4}u; i += 32u) {{
|
| 65 |
+
uchar4 pb = p4[i], nb = n4[i], hb = h4[i];
|
| 66 |
+
uint cb = i * 32u;
|
| 67 |
+
for (uint k = 0u; k < 4u; ++k) {{
|
| 68 |
+
uint pw = pb[k], nw2 = nb[k], hw = hb[k];
|
| 69 |
+
if ((pw | nw2) == 0u) continue;
|
| 70 |
+
for (uint j = 0u; j < 8u; ++j) {{
|
| 71 |
+
uint c = cb + k * 8u + j;
|
| 72 |
+
{guard}
|
| 73 |
+
float s = (float)((pw >> j) & 1u) - (float)((nw2 >> j) & 1u);
|
| 74 |
+
if (s == 0.0f) continue;
|
| 75 |
+
float v = s * ({xv});
|
| 76 |
+
if ((hw >> j) & 1u) accHi += v; else accLo += v;
|
| 77 |
+
}}
|
| 78 |
+
}}
|
| 79 |
+
}}
|
| 80 |
+
accLo = metal::simd_sum(accLo);
|
| 81 |
+
accHi = metal::simd_sum(accHi);
|
| 82 |
+
if (lane == 0u) {{
|
| 83 |
+
float y = metal::fma(slo[row], accLo, shi[row] * accHi);
|
| 84 |
+
{"y += bias[row];" if has_bias else ""}
|
| 85 |
+
{"y += resid[row];" if add_resid else ""}
|
| 86 |
+
out[row] = y;
|
| 87 |
+
}}
|
| 88 |
+
"""
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
def _gemv_source_v2(rows, cols, rs, sg_per_tg, has_bias, fuse_norm, add_resid, glu, eps):
|
| 92 |
+
"""v2: branchless decode. Planes loaded as uint32 (32 weights per load),
|
| 93 |
+
per-4-bit float4 select against float4 x loads. Requires cols % 32 == 0
|
| 94 |
+
and rs % 4 == 0 with rs*8 == cols (true for all Qwen shapes)."""
|
| 95 |
+
assert rs % 4 == 0 and rs * 8 == cols and cols % 32 == 0
|
| 96 |
+
if glu:
|
| 97 |
+
xload = f"""float4 g = xg4[w * 8u + k], u = xu4[w * 8u + k];
|
| 98 |
+
float4 xv = g / (1.0f + metal::exp(-g)) * u;"""
|
| 99 |
+
norm_pre = ""
|
| 100 |
+
xpre = f"""
|
| 101 |
+
const device float4* xg4 = (const device float4*)x;
|
| 102 |
+
const device float4* xu4 = (const device float4*)(x + {cols}u);"""
|
| 103 |
+
elif fuse_norm:
|
| 104 |
+
xload = """float4 xv = x4[w * 8u + k] * nw4[w * 8u + k] * rms;"""
|
| 105 |
+
norm_pre = _norm_pre(cols, eps)
|
| 106 |
+
xpre = """
|
| 107 |
+
const device float4* x4 = (const device float4*)x;
|
| 108 |
+
const device float4* nw4 = (const device float4*)nw;"""
|
| 109 |
+
else:
|
| 110 |
+
xload = """float4 xv = x4[w * 8u + k];"""
|
| 111 |
+
norm_pre = ""
|
| 112 |
+
xpre = """
|
| 113 |
+
const device float4* x4 = (const device float4*)x;"""
|
| 114 |
+
return f"""
|
| 115 |
+
uint tid = thread_position_in_threadgroup.x;
|
| 116 |
+
uint lane = tid & 31u;
|
| 117 |
+
uint sg = tid >> 5u;
|
| 118 |
+
uint row = threadgroup_position_in_grid.x * {sg_per_tg}u + sg;
|
| 119 |
+
if (row >= {rows}u) return;
|
| 120 |
+
{norm_pre}{xpre}
|
| 121 |
+
const device uint* p32 = (const device uint*)bp + row * {rs // 4}u;
|
| 122 |
+
const device uint* n32 = (const device uint*)bn + row * {rs // 4}u;
|
| 123 |
+
const device uint* h32 = (const device uint*)br + row * {rs // 4}u;
|
| 124 |
+
float4 accLo4 = 0.0f, accHi4 = 0.0f;
|
| 125 |
+
for (uint w = lane; w < {rs // 4}u; w += 32u) {{
|
| 126 |
+
uint pw = p32[w], nw2 = n32[w], hw = h32[w];
|
| 127 |
+
for (uint k = 0u; k < 8u; ++k) {{
|
| 128 |
+
{xload}
|
| 129 |
+
uint sh = k * 4u;
|
| 130 |
+
float4 s = float4(float((pw >> sh) & 1u), float((pw >> (sh+1u)) & 1u),
|
| 131 |
+
float((pw >> (sh+2u)) & 1u), float((pw >> (sh+3u)) & 1u))
|
| 132 |
+
- float4(float((nw2 >> sh) & 1u), float((nw2 >> (sh+1u)) & 1u),
|
| 133 |
+
float((nw2 >> (sh+2u)) & 1u), float((nw2 >> (sh+3u)) & 1u));
|
| 134 |
+
float4 v = s * xv;
|
| 135 |
+
bool4 hi = bool4((hw >> sh) & 1u, (hw >> (sh+1u)) & 1u,
|
| 136 |
+
(hw >> (sh+2u)) & 1u, (hw >> (sh+3u)) & 1u);
|
| 137 |
+
accHi4 += metal::select(float4(0.0f), v, hi);
|
| 138 |
+
accLo4 += metal::select(v, float4(0.0f), hi);
|
| 139 |
+
}}
|
| 140 |
+
}}
|
| 141 |
+
float accLo = metal::simd_sum(accLo4.x + accLo4.y + accLo4.z + accLo4.w);
|
| 142 |
+
float accHi = metal::simd_sum(accHi4.x + accHi4.y + accHi4.z + accHi4.w);
|
| 143 |
+
if (lane == 0u) {{
|
| 144 |
+
float y = metal::fma(slo[row], accLo, shi[row] * accHi);
|
| 145 |
+
{"y += bias[row];" if has_bias else ""}
|
| 146 |
+
{"y += resid[row];" if add_resid else ""}
|
| 147 |
+
out[row] = y;
|
| 148 |
+
}}
|
| 149 |
+
"""
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
def _s8_source(rows, cols, rs, sg_per_tg, fuse_norm, eps):
|
| 153 |
+
assert rs % 4 == 0
|
| 154 |
+
if fuse_norm:
|
| 155 |
+
xv = lambda c: f"x[{c}] * nw[{c}] * rms"
|
| 156 |
+
norm_pre = f"""
|
| 157 |
+
float ssq = 0.0f;
|
| 158 |
+
for (uint c = lane; c < {cols}u; c += 32u) {{ float v = x[c]; ssq = metal::fma(v, v, ssq); }}
|
| 159 |
+
ssq = metal::simd_sum(ssq);
|
| 160 |
+
float rms = metal::rsqrt(ssq / {float(cols)}f + {eps:.9g}f);
|
| 161 |
+
"""
|
| 162 |
+
else:
|
| 163 |
+
xv = lambda c: f"x[{c}]"
|
| 164 |
+
norm_pre = ""
|
| 165 |
+
assert rs * 8 >= cols and rs >= cols, "int8 record: expect row_stride >= cols"
|
| 166 |
+
nvec = cols // 4
|
| 167 |
+
return f"""
|
| 168 |
+
uint tid = thread_position_in_threadgroup.x;
|
| 169 |
+
uint lane = tid & 31u;
|
| 170 |
+
uint sg = tid >> 5u;
|
| 171 |
+
uint row = threadgroup_position_in_grid.x * {sg_per_tg}u + sg;
|
| 172 |
+
if (row >= {rows}u) return;
|
| 173 |
+
{norm_pre}
|
| 174 |
+
const device char* w = (const device char*)W + (ulong)row * {rs}u;
|
| 175 |
+
float acc = 0.0f;
|
| 176 |
+
for (uint i = lane * 4u; i + 3u < {cols}u; i += 128u) {{
|
| 177 |
+
char4 wv = *(const device char4*)(w + i);
|
| 178 |
+
uint c = i;
|
| 179 |
+
acc = metal::fma((float)wv.x, {xv('c')}, acc);
|
| 180 |
+
acc = metal::fma((float)wv.y, {xv('c + 1u')}, acc);
|
| 181 |
+
acc = metal::fma((float)wv.z, {xv('c + 2u')}, acc);
|
| 182 |
+
acc = metal::fma((float)wv.w, {xv('c + 3u')}, acc);
|
| 183 |
+
}}
|
| 184 |
+
acc = metal::simd_sum(acc);
|
| 185 |
+
if (lane == 0u) out[row] = scale[row] * acc;
|
| 186 |
+
""" if nvec * 4 == cols else None
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
SG_PER_TG = int(__import__("os").environ.get("TRIT_METAL_SGPTG", "8"))
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
import os
|
| 193 |
+
KERNEL_VERSION = int(os.environ.get("TRIT_METAL_V", "1"))
|
| 194 |
+
RPSG = int(os.environ.get("TRIT_METAL_RPSG", "8"))
|
| 195 |
+
|
| 196 |
+
|
| 197 |
+
def _gemv_source_v3(rows, cols, rs, sg_per_tg, has_bias, fuse_norm, add_resid, glu, eps,
|
| 198 |
+
rpsg=RPSG):
|
| 199 |
+
"""v3: rpsg rows per simdgroup; x (and silu/norm work) loaded once per
|
| 200 |
+
column window and reused across rows in registers — kills the per-row
|
| 201 |
+
x-reload that bound v1/v2 at ~20 GB/s. 4-wide horizontal accumulate."""
|
| 202 |
+
assert rs % 4 == 0 and rs * 8 == cols and cols % 32 == 0 and rows % rpsg == 0
|
| 203 |
+
if glu:
|
| 204 |
+
xload = f"""float4 g = xg4[w * 8u + k], u = xu4[w * 8u + k];
|
| 205 |
+
float4 xv = g / (1.0f + metal::exp(-g)) * u;"""
|
| 206 |
+
norm_pre = ""
|
| 207 |
+
xpre = f"""
|
| 208 |
+
const device float4* xg4 = (const device float4*)x;
|
| 209 |
+
const device float4* xu4 = (const device float4*)(x + {cols}u);"""
|
| 210 |
+
elif fuse_norm:
|
| 211 |
+
xload = "float4 xv = x4[w * 8u + k] * nw4[w * 8u + k] * rms;"
|
| 212 |
+
norm_pre = _norm_pre(cols, eps)
|
| 213 |
+
xpre = """
|
| 214 |
+
const device float4* x4 = (const device float4*)x;
|
| 215 |
+
const device float4* nw4 = (const device float4*)nw;"""
|
| 216 |
+
else:
|
| 217 |
+
xload = "float4 xv = x4[w * 8u + k];"
|
| 218 |
+
norm_pre = ""
|
| 219 |
+
xpre = """
|
| 220 |
+
const device float4* x4 = (const device float4*)x;"""
|
| 221 |
+
return f"""
|
| 222 |
+
uint tid = thread_position_in_threadgroup.x;
|
| 223 |
+
uint lane = tid & 31u;
|
| 224 |
+
uint sg = tid >> 5u;
|
| 225 |
+
uint row0 = (threadgroup_position_in_grid.x * {sg_per_tg}u + sg) * {rpsg}u;
|
| 226 |
+
if (row0 >= {rows}u) return;
|
| 227 |
+
{norm_pre}{xpre}
|
| 228 |
+
const device uint* p32 = (const device uint*)bp;
|
| 229 |
+
const device uint* n32 = (const device uint*)bn;
|
| 230 |
+
const device uint* h32 = (const device uint*)br;
|
| 231 |
+
float accLo[{rpsg}], accHi[{rpsg}];
|
| 232 |
+
for (uint r = 0u; r < {rpsg}u; ++r) {{ accLo[r] = 0.0f; accHi[r] = 0.0f; }}
|
| 233 |
+
const float4 one4 = float4(1.0f);
|
| 234 |
+
for (uint w = lane; w < {rs // 4}u; w += 32u) {{
|
| 235 |
+
uint pw[{rpsg}], nw2[{rpsg}], hw[{rpsg}];
|
| 236 |
+
for (uint r = 0u; r < {rpsg}u; ++r) {{
|
| 237 |
+
ulong off = (ulong)(row0 + r) * {rs // 4}u + w;
|
| 238 |
+
pw[r] = p32[off]; nw2[r] = n32[off]; hw[r] = h32[off];
|
| 239 |
+
}}
|
| 240 |
+
for (uint k = 0u; k < 8u; ++k) {{
|
| 241 |
+
{xload}
|
| 242 |
+
uint sh = k * 4u;
|
| 243 |
+
for (uint r = 0u; r < {rpsg}u; ++r) {{
|
| 244 |
+
float4 s = float4(float((pw[r] >> sh) & 1u), float((pw[r] >> (sh+1u)) & 1u),
|
| 245 |
+
float((pw[r] >> (sh+2u)) & 1u), float((pw[r] >> (sh+3u)) & 1u))
|
| 246 |
+
- float4(float((nw2[r] >> sh) & 1u), float((nw2[r] >> (sh+1u)) & 1u),
|
| 247 |
+
float((nw2[r] >> (sh+2u)) & 1u), float((nw2[r] >> (sh+3u)) & 1u));
|
| 248 |
+
float4 v = s * xv;
|
| 249 |
+
bool4 hi = bool4((hw[r] >> sh) & 1u, (hw[r] >> (sh+1u)) & 1u,
|
| 250 |
+
(hw[r] >> (sh+2u)) & 1u, (hw[r] >> (sh+3u)) & 1u);
|
| 251 |
+
accHi[r] += metal::dot(metal::select(float4(0.0f), v, hi), one4);
|
| 252 |
+
accLo[r] += metal::dot(metal::select(v, float4(0.0f), hi), one4);
|
| 253 |
+
}}
|
| 254 |
+
}}
|
| 255 |
+
}}
|
| 256 |
+
for (uint r = 0u; r < {rpsg}u; ++r) {{
|
| 257 |
+
float lo = metal::simd_sum(accLo[r]);
|
| 258 |
+
float hi = metal::simd_sum(accHi[r]);
|
| 259 |
+
if (lane == 0u) {{
|
| 260 |
+
uint row = row0 + r;
|
| 261 |
+
float y = metal::fma(slo[row], lo, shi[row] * hi);
|
| 262 |
+
{"y += bias[row];" if has_bias else ""}
|
| 263 |
+
{"y += resid[row];" if add_resid else ""}
|
| 264 |
+
out[row] = y;
|
| 265 |
+
}}
|
| 266 |
+
}}
|
| 267 |
+
"""
|
| 268 |
+
|
| 269 |
+
|
| 270 |
+
def _gemv_source_v4(rows, cols, rs, sg_per_tg, has_bias, fuse_norm, add_resid, glu, eps,
|
| 271 |
+
rpsg=RPSG):
|
| 272 |
+
"""v4: threadgroup-memory LUT decode (Metal twin of NEON vqtbl1q).
|
| 273 |
+
Per 4-column group g, lut[g][m] = sum of xv[4g+i] over set bits of m
|
| 274 |
+
(16 subset sums). Split planes on the fly into lo/hi pos/neg nibbles
|
| 275 |
+
(a=p&~h, b=n&~h, c=p&h, d=n&h): 4 weights cost 4 LUT loads + 4 adds.
|
| 276 |
+
LUT stride 17 floats kills the 16-float bank-conflict pattern. Tiled over
|
| 277 |
+
columns when the LUT would exceed threadgroup memory (down proj)."""
|
| 278 |
+
assert rs % 4 == 0 and rs * 8 == cols and cols % 32 == 0 and rows % rpsg == 0
|
| 279 |
+
# column tiling: LUT floats = tile/4*17, xbuf floats = tile -> keep <= ~26KB
|
| 280 |
+
ntiles = 1
|
| 281 |
+
while (cols // ntiles) % 32 != 0 or (cols // ntiles) * (17 / 4 + 1) * 4 > 26624:
|
| 282 |
+
ntiles += 1
|
| 283 |
+
assert ntiles <= 16
|
| 284 |
+
tile = cols // ntiles
|
| 285 |
+
gpt = tile // 4 # groups per tile
|
| 286 |
+
w0 = tile // 32 # 32-weight words per tile (per row)
|
| 287 |
+
nthreads = sg_per_tg * 32
|
| 288 |
+
if glu:
|
| 289 |
+
xv_expr = "{ float gg = x[c]; xb[cl] = gg / (1.0f + metal::exp(-gg)) * x[c + %du]; }" % cols
|
| 290 |
+
elif fuse_norm:
|
| 291 |
+
xv_expr = "xb[cl] = x[c] * nw[c] * rms;"
|
| 292 |
+
else:
|
| 293 |
+
xv_expr = "xb[cl] = x[c];"
|
| 294 |
+
if fuse_norm:
|
| 295 |
+
norm_pre = f"""
|
| 296 |
+
threadgroup float ssq_sh[{sg_per_tg}];
|
| 297 |
+
{{
|
| 298 |
+
float ssq = 0.0f;
|
| 299 |
+
for (uint c = tid; c < {cols}u; c += {nthreads}u) {{ float v = x[c]; ssq = metal::fma(v, v, ssq); }}
|
| 300 |
+
ssq = metal::simd_sum(ssq);
|
| 301 |
+
if (lane == 0u) ssq_sh[sg] = ssq;
|
| 302 |
+
threadgroup_barrier(metal::mem_flags::mem_threadgroup);
|
| 303 |
+
float t = 0.0f;
|
| 304 |
+
for (uint i = 0u; i < {sg_per_tg}u; ++i) t += ssq_sh[i];
|
| 305 |
+
rms = metal::rsqrt(t / {float(cols)}f + {eps:.9g}f);
|
| 306 |
+
}}
|
| 307 |
+
"""
|
| 308 |
+
else:
|
| 309 |
+
norm_pre = ""
|
| 310 |
+
return f"""
|
| 311 |
+
uint tid = thread_position_in_threadgroup.x;
|
| 312 |
+
uint lane = tid & 31u;
|
| 313 |
+
uint sg = tid >> 5u;
|
| 314 |
+
uint row0 = (threadgroup_position_in_grid.x * {sg_per_tg}u + sg) * {rpsg}u;
|
| 315 |
+
float rms = 1.0f;
|
| 316 |
+
{norm_pre}
|
| 317 |
+
threadgroup float xb[{tile}];
|
| 318 |
+
threadgroup float lut[{gpt} * 17];
|
| 319 |
+
const device uint* p32 = (const device uint*)bp;
|
| 320 |
+
const device uint* n32 = (const device uint*)bn;
|
| 321 |
+
const device uint* h32 = (const device uint*)br;
|
| 322 |
+
float accLo[{rpsg}], accHi[{rpsg}];
|
| 323 |
+
for (uint r = 0u; r < {rpsg}u; ++r) {{ accLo[r] = 0.0f; accHi[r] = 0.0f; }}
|
| 324 |
+
for (uint t = 0u; t < {ntiles}u; ++t) {{
|
| 325 |
+
// stage effective x for this tile
|
| 326 |
+
threadgroup_barrier(metal::mem_flags::mem_threadgroup);
|
| 327 |
+
for (uint cl = tid; cl < {tile}u; cl += {nthreads}u) {{
|
| 328 |
+
uint c = t * {tile}u + cl;
|
| 329 |
+
{xv_expr}
|
| 330 |
+
}}
|
| 331 |
+
threadgroup_barrier(metal::mem_flags::mem_threadgroup);
|
| 332 |
+
// build 16 subset sums per 4-col group
|
| 333 |
+
for (uint e = tid; e < {gpt}u * 16u; e += {nthreads}u) {{
|
| 334 |
+
uint g = e >> 4u, m = e & 15u;
|
| 335 |
+
const threadgroup float* xg = xb + g * 4u;
|
| 336 |
+
float v = 0.0f;
|
| 337 |
+
if (m & 1u) v += xg[0];
|
| 338 |
+
if (m & 2u) v += xg[1];
|
| 339 |
+
if (m & 4u) v += xg[2];
|
| 340 |
+
if (m & 8u) v += xg[3];
|
| 341 |
+
lut[g * 17u + m] = v;
|
| 342 |
+
}}
|
| 343 |
+
threadgroup_barrier(metal::mem_flags::mem_threadgroup);
|
| 344 |
+
if (row0 < {rows}u) {{
|
| 345 |
+
for (uint wl = lane; wl < {w0}u; wl += 32u) {{
|
| 346 |
+
uint w = t * {w0}u + wl;
|
| 347 |
+
for (uint r = 0u; r < {rpsg}u; ++r) {{
|
| 348 |
+
ulong off = (ulong)(row0 + r) * {rs // 4}u + w;
|
| 349 |
+
uint pw = p32[off], nw2 = n32[off], hw = h32[off];
|
| 350 |
+
uint a = pw & ~hw, b = nw2 & ~hw, cc = pw & hw, d = nw2 & hw;
|
| 351 |
+
uint gbase = wl * 8u;
|
| 352 |
+
float lo = 0.0f, hi = 0.0f;
|
| 353 |
+
for (uint k = 0u; k < 8u; ++k) {{
|
| 354 |
+
const threadgroup float* lg = lut + (gbase + k) * 17u;
|
| 355 |
+
uint sh = k * 4u;
|
| 356 |
+
lo += lg[(a >> sh) & 15u] - lg[(b >> sh) & 15u];
|
| 357 |
+
hi += lg[(cc >> sh) & 15u] - lg[(d >> sh) & 15u];
|
| 358 |
+
}}
|
| 359 |
+
accLo[r] += lo; accHi[r] += hi;
|
| 360 |
+
}}
|
| 361 |
+
}}
|
| 362 |
+
}}
|
| 363 |
+
}}
|
| 364 |
+
if (row0 >= {rows}u) return;
|
| 365 |
+
for (uint r = 0u; r < {rpsg}u; ++r) {{
|
| 366 |
+
float lo = metal::simd_sum(accLo[r]);
|
| 367 |
+
float hi = metal::simd_sum(accHi[r]);
|
| 368 |
+
if (lane == 0u) {{
|
| 369 |
+
uint row = row0 + r;
|
| 370 |
+
float y = metal::fma(slo[row], lo, shi[row] * hi);
|
| 371 |
+
{"y += bias[row];" if has_bias else ""}
|
| 372 |
+
{"y += resid[row];" if add_resid else ""}
|
| 373 |
+
out[row] = y;
|
| 374 |
+
}}
|
| 375 |
+
}}
|
| 376 |
+
"""
|
| 377 |
+
|
| 378 |
+
|
| 379 |
+
import os as _os
|
| 380 |
+
_TILE_MAX = int(_os.environ.get("TRIT_METAL_TILE", "448"))
|
| 381 |
+
|
| 382 |
+
|
| 383 |
+
def _v5_tile(cols):
|
| 384 |
+
"""largest tile <= TILE_MAX with tile % 32 == 0 and cols % tile == 0."""
|
| 385 |
+
for t in range(_TILE_MAX, 31, -32):
|
| 386 |
+
if cols % t == 0 and t % 32 == 0:
|
| 387 |
+
return t
|
| 388 |
+
raise ValueError(cols)
|
| 389 |
+
|
| 390 |
+
|
| 391 |
+
def _gemv_source_v5(rows, cols, rs, sg_per_tg, has_bias, fuse_norm, add_resid, glu, eps,
|
| 392 |
+
rpsg=8):
|
| 393 |
+
"""v5 = v4 LUT decode with the two occupancy/bandwidth fixes:
|
| 394 |
+
(1) planes pre-interleaved on load: uint32 words stored [row_block, word,
|
| 395 |
+
row_in_block] so a lane's 8-row load is 32 contiguous bytes (streams
|
| 396 |
+
at DRAM bandwidth; the v3/v4 scattered pattern ran at 59% of it);
|
| 397 |
+
(2) small LUT tiles (<= ~7.6KB threadgroup memory) so several threadgroups
|
| 398 |
+
fit per GPU core (v4's 19-26KB allowed only one, exposing latency).
|
| 399 |
+
Requires planes packed with pack_planes_v5. rpsg fixed at 8 by layout."""
|
| 400 |
+
assert rs % 4 == 0 and rs * 8 == cols and cols % 32 == 0 and rows % 8 == 0
|
| 401 |
+
tile = _v5_tile(cols)
|
| 402 |
+
ntiles = cols // tile
|
| 403 |
+
gpt = tile // 4
|
| 404 |
+
w0 = tile // 32
|
| 405 |
+
W = rs // 4
|
| 406 |
+
nthreads = sg_per_tg * 32
|
| 407 |
+
if glu:
|
| 408 |
+
stage = f"""
|
| 409 |
+
for (uint cl = tid; cl < {tile}u; cl += {nthreads}u) {{
|
| 410 |
+
uint c = t * {tile}u + cl;
|
| 411 |
+
float gg = x[c];
|
| 412 |
+
xb[cl] = gg / (1.0f + metal::exp(-gg)) * x[c + {cols}u];
|
| 413 |
+
}}
|
| 414 |
+
threadgroup_barrier(metal::mem_flags::mem_threadgroup);"""
|
| 415 |
+
xval = "xb[gl * 4u + i]"
|
| 416 |
+
xb_decl = f"threadgroup float xb[{tile}];"
|
| 417 |
+
elif fuse_norm:
|
| 418 |
+
stage = ""
|
| 419 |
+
xval = "x[t * %du + gl * 4u + i] * nw[t * %du + gl * 4u + i] * rms" % (tile, tile)
|
| 420 |
+
xb_decl = ""
|
| 421 |
+
else:
|
| 422 |
+
stage = ""
|
| 423 |
+
xval = "x[t * %du + gl * 4u + i]" % tile
|
| 424 |
+
xb_decl = ""
|
| 425 |
+
if fuse_norm:
|
| 426 |
+
norm_pre = f"""
|
| 427 |
+
threadgroup float ssq_sh[{sg_per_tg}];
|
| 428 |
+
{{
|
| 429 |
+
float ssq = 0.0f;
|
| 430 |
+
for (uint c = tid; c < {cols}u; c += {nthreads}u) {{ float v = x[c]; ssq = metal::fma(v, v, ssq); }}
|
| 431 |
+
ssq = metal::simd_sum(ssq);
|
| 432 |
+
if (lane == 0u) ssq_sh[sg] = ssq;
|
| 433 |
+
threadgroup_barrier(metal::mem_flags::mem_threadgroup);
|
| 434 |
+
float tt = 0.0f;
|
| 435 |
+
for (uint i = 0u; i < {sg_per_tg}u; ++i) tt += ssq_sh[i];
|
| 436 |
+
rms = metal::rsqrt(tt / {float(cols)}f + {eps:.9g}f);
|
| 437 |
+
}}
|
| 438 |
+
"""
|
| 439 |
+
else:
|
| 440 |
+
norm_pre = ""
|
| 441 |
+
return f"""
|
| 442 |
+
uint tid = thread_position_in_threadgroup.x;
|
| 443 |
+
uint lane = tid & 31u;
|
| 444 |
+
uint sg = tid >> 5u;
|
| 445 |
+
uint blk = threadgroup_position_in_grid.x * {sg_per_tg}u + sg; // 8-row block
|
| 446 |
+
uint row0 = blk * 8u;
|
| 447 |
+
float rms = 1.0f;
|
| 448 |
+
{norm_pre}
|
| 449 |
+
{xb_decl}
|
| 450 |
+
threadgroup float lut[{gpt} * 17];
|
| 451 |
+
const device uint* p32 = (const device uint*)bp;
|
| 452 |
+
const device uint* n32 = (const device uint*)bn;
|
| 453 |
+
const device uint* h32 = (const device uint*)br;
|
| 454 |
+
float accLo[8], accHi[8];
|
| 455 |
+
for (uint r = 0u; r < 8u; ++r) {{ accLo[r] = 0.0f; accHi[r] = 0.0f; }}
|
| 456 |
+
for (uint t = 0u; t < {ntiles}u; ++t) {{
|
| 457 |
+
threadgroup_barrier(metal::mem_flags::mem_threadgroup);
|
| 458 |
+
{stage}
|
| 459 |
+
for (uint e = tid; e < {gpt}u * 16u; e += {nthreads}u) {{
|
| 460 |
+
uint gl = e >> 4u, m = e & 15u;
|
| 461 |
+
float v = 0.0f;
|
| 462 |
+
for (uint i = 0u; i < 4u; ++i)
|
| 463 |
+
if ((m >> i) & 1u) v += {xval};
|
| 464 |
+
lut[gl * 17u + m] = v;
|
| 465 |
+
}}
|
| 466 |
+
threadgroup_barrier(metal::mem_flags::mem_threadgroup);
|
| 467 |
+
if (row0 < {rows}u) {{
|
| 468 |
+
for (uint wl = lane; wl < {w0}u; wl += 32u) {{
|
| 469 |
+
uint w = t * {w0}u + wl;
|
| 470 |
+
ulong base = ((ulong)blk * {W}u + w) * 8u; // 8 rows contiguous
|
| 471 |
+
uint gbase = wl * 8u;
|
| 472 |
+
for (uint r = 0u; r < 8u; ++r) {{
|
| 473 |
+
uint pw = p32[base + r], nw2 = n32[base + r], hw = h32[base + r];
|
| 474 |
+
uint a = pw & ~hw, b = nw2 & ~hw, cc = pw & hw, d = nw2 & hw;
|
| 475 |
+
float lo = 0.0f, hi = 0.0f;
|
| 476 |
+
for (uint k = 0u; k < 8u; ++k) {{
|
| 477 |
+
const threadgroup float* lg = lut + (gbase + k) * 17u;
|
| 478 |
+
uint sh = k * 4u;
|
| 479 |
+
lo += lg[(a >> sh) & 15u] - lg[(b >> sh) & 15u];
|
| 480 |
+
hi += lg[(cc >> sh) & 15u] - lg[(d >> sh) & 15u];
|
| 481 |
+
}}
|
| 482 |
+
accLo[r] += lo; accHi[r] += hi;
|
| 483 |
+
}}
|
| 484 |
+
}}
|
| 485 |
+
}}
|
| 486 |
+
}}
|
| 487 |
+
if (row0 >= {rows}u) return;
|
| 488 |
+
for (uint r = 0u; r < 8u; ++r) {{
|
| 489 |
+
float lo = metal::simd_sum(accLo[r]);
|
| 490 |
+
float hi = metal::simd_sum(accHi[r]);
|
| 491 |
+
if (lane == 0u) {{
|
| 492 |
+
uint row = row0 + r;
|
| 493 |
+
float y = metal::fma(slo[row], lo, shi[row] * hi);
|
| 494 |
+
{"y += bias[row];" if has_bias else ""}
|
| 495 |
+
{"y += resid[row];" if add_resid else ""}
|
| 496 |
+
out[row] = y;
|
| 497 |
+
}}
|
| 498 |
+
}}
|
| 499 |
+
"""
|
| 500 |
+
|
| 501 |
+
|
| 502 |
+
def _gemv_source_v6(rows, cols, rs, sg_per_tg, has_bias, fuse_norm, add_resid, glu, eps):
|
| 503 |
+
"""v6: interleaved 8-row plane layout (pack_planes_v5) + mask-convert-fma
|
| 504 |
+
decode. For bit j of word a: (float)(a & (1<<j)) == 2^j when set, so
|
| 505 |
+
multiplying by x pre-scaled with 2^-j turns decode into AND + convert +
|
| 506 |
+
FMA (no shifts) — 4 masks (lo/hi x pos/neg) ~3.5 instr/weight, exact in
|
| 507 |
+
fp32 (power-of-two scales). x reused across the 8 rows in registers."""
|
| 508 |
+
assert rs % 4 == 0 and rs * 8 == cols and cols % 32 == 0 and rows % 8 == 0
|
| 509 |
+
W = rs // 4
|
| 510 |
+
if glu:
|
| 511 |
+
xload = f"""float4 g = xg4[w * 8u + k], u = xu4[w * 8u + k];
|
| 512 |
+
float4 xv = g / (1.0f + metal::exp(-g)) * u;"""
|
| 513 |
+
norm_pre = ""
|
| 514 |
+
xpre = f"""
|
| 515 |
+
const device float4* xg4 = (const device float4*)x;
|
| 516 |
+
const device float4* xu4 = (const device float4*)(x + {cols}u);"""
|
| 517 |
+
elif fuse_norm:
|
| 518 |
+
xload = "float4 xv = x4[w * 8u + k] * nw4[w * 8u + k] * rms;"
|
| 519 |
+
norm_pre = _norm_pre(cols, eps)
|
| 520 |
+
xpre = """
|
| 521 |
+
const device float4* x4 = (const device float4*)x;
|
| 522 |
+
const device float4* nw4 = (const device float4*)nw;"""
|
| 523 |
+
else:
|
| 524 |
+
xload = "float4 xv = x4[w * 8u + k];"
|
| 525 |
+
norm_pre = ""
|
| 526 |
+
xpre = """
|
| 527 |
+
const device float4* x4 = (const device float4*)x;"""
|
| 528 |
+
# unrolled k-body with literal masks / prescales
|
| 529 |
+
kbody = []
|
| 530 |
+
for k in range(8):
|
| 531 |
+
m = [1 << (4 * k + i) for i in range(4)]
|
| 532 |
+
ps = ", ".join("%.9ef" % (2.0 ** -(4 * k + i)) for i in range(4))
|
| 533 |
+
kbody.append(f""" {{
|
| 534 |
+
uint k = {k}u; {xload}
|
| 535 |
+
float4 xs = xv * float4({ps});
|
| 536 |
+
const uint4 m4 = uint4({m[0]}u, {m[1]}u, {m[2]}u, {m[3]}u);
|
| 537 |
+
for (uint r = 0u; r < 8u; ++r) {{
|
| 538 |
+
uint pw = pl[r], nw2 = nl[r], hw = hl[r];
|
| 539 |
+
uint a = pw & ~hw, b = nw2 & ~hw, cc = pw & hw, d = nw2 & hw;
|
| 540 |
+
accLo[r] += metal::dot(float4(uint4(a) & m4) - float4(uint4(b) & m4), xs);
|
| 541 |
+
accHi[r] += metal::dot(float4(uint4(cc) & m4) - float4(uint4(d) & m4), xs);
|
| 542 |
+
}}
|
| 543 |
+
}}""")
|
| 544 |
+
kbody = "\n".join(kbody)
|
| 545 |
+
return f"""
|
| 546 |
+
uint tid = thread_position_in_threadgroup.x;
|
| 547 |
+
uint lane = tid & 31u;
|
| 548 |
+
uint sg = tid >> 5u;
|
| 549 |
+
uint blk = threadgroup_position_in_grid.x * {sg_per_tg}u + sg;
|
| 550 |
+
uint row0 = blk * 8u;
|
| 551 |
+
if (row0 >= {rows}u) return;
|
| 552 |
+
{norm_pre}{xpre}
|
| 553 |
+
const device uint* p32 = (const device uint*)bp;
|
| 554 |
+
const device uint* n32 = (const device uint*)bn;
|
| 555 |
+
const device uint* h32 = (const device uint*)br;
|
| 556 |
+
float accLo[8], accHi[8];
|
| 557 |
+
for (uint r = 0u; r < 8u; ++r) {{ accLo[r] = 0.0f; accHi[r] = 0.0f; }}
|
| 558 |
+
for (uint w = lane; w < {W}u; w += 32u) {{
|
| 559 |
+
ulong base = ((ulong)blk * {W}u + w) * 8u;
|
| 560 |
+
uint pl[8], nl[8], hl[8];
|
| 561 |
+
for (uint r = 0u; r < 8u; ++r) {{ pl[r] = p32[base+r]; nl[r] = n32[base+r]; hl[r] = h32[base+r]; }}
|
| 562 |
+
{kbody}
|
| 563 |
+
}}
|
| 564 |
+
for (uint r = 0u; r < 8u; ++r) {{
|
| 565 |
+
float lo = metal::simd_sum(accLo[r]);
|
| 566 |
+
float hi = metal::simd_sum(accHi[r]);
|
| 567 |
+
if (lane == 0u) {{
|
| 568 |
+
uint row = row0 + r;
|
| 569 |
+
float y = metal::fma(slo[row], lo, shi[row] * hi);
|
| 570 |
+
{"y += bias[row];" if has_bias else ""}
|
| 571 |
+
{"y += resid[row];" if add_resid else ""}
|
| 572 |
+
out[row] = y;
|
| 573 |
+
}}
|
| 574 |
+
}}
|
| 575 |
+
"""
|
| 576 |
+
|
| 577 |
+
|
| 578 |
+
def _gemv_source_v7(rows, cols, rs, sg_per_tg, has_bias, fuse_norm, add_resid, glu, eps):
|
| 579 |
+
"""v7: v5 interleaved layout + LUT decode with full lane utilization.
|
| 580 |
+
Lane = (row r = lane&7, word-slot = lane>>3): each lane accumulates ONE
|
| 581 |
+
row over words wslot, wslot+4, ... — all 32 lanes busy for any w0 >= 4.
|
| 582 |
+
Row sums gathered with two simd_shuffle_down folds. LUT tile 448 cols
|
| 583 |
+
(7.6KB threadgroup mem) so multiple threadgroups stay resident per core."""
|
| 584 |
+
assert rs % 4 == 0 and rs * 8 == cols and cols % 32 == 0 and rows % 8 == 0
|
| 585 |
+
tile = _v5_tile(cols)
|
| 586 |
+
ntiles = cols // tile
|
| 587 |
+
gpt = tile // 4
|
| 588 |
+
w0 = tile // 32
|
| 589 |
+
W = rs // 4
|
| 590 |
+
nthreads = sg_per_tg * 32
|
| 591 |
+
if glu:
|
| 592 |
+
stage = f"""
|
| 593 |
+
for (uint cl = tid; cl < {tile}u; cl += {nthreads}u) {{
|
| 594 |
+
uint c = t * {tile}u + cl;
|
| 595 |
+
float gg = x[c];
|
| 596 |
+
xb[cl] = gg / (1.0f + metal::exp(-gg)) * x[c + {cols}u];
|
| 597 |
+
}}
|
| 598 |
+
threadgroup_barrier(metal::mem_flags::mem_threadgroup);"""
|
| 599 |
+
xval = "xb[gl * 4u + i]"
|
| 600 |
+
xb_decl = f"threadgroup float xb[{tile}];"
|
| 601 |
+
elif fuse_norm:
|
| 602 |
+
stage = ""
|
| 603 |
+
xval = "x[t * %du + gl * 4u + i] * nw[t * %du + gl * 4u + i] * rms" % (tile, tile)
|
| 604 |
+
xb_decl = ""
|
| 605 |
+
else:
|
| 606 |
+
stage = ""
|
| 607 |
+
xval = "x[t * %du + gl * 4u + i]" % tile
|
| 608 |
+
xb_decl = ""
|
| 609 |
+
if fuse_norm:
|
| 610 |
+
norm_pre = f"""
|
| 611 |
+
threadgroup float ssq_sh[{sg_per_tg}];
|
| 612 |
+
{{
|
| 613 |
+
float ssq = 0.0f;
|
| 614 |
+
for (uint c = tid; c < {cols}u; c += {nthreads}u) {{ float v = x[c]; ssq = metal::fma(v, v, ssq); }}
|
| 615 |
+
ssq = metal::simd_sum(ssq);
|
| 616 |
+
if (lane == 0u) ssq_sh[sg] = ssq;
|
| 617 |
+
threadgroup_barrier(metal::mem_flags::mem_threadgroup);
|
| 618 |
+
float tt = 0.0f;
|
| 619 |
+
for (uint i = 0u; i < {sg_per_tg}u; ++i) tt += ssq_sh[i];
|
| 620 |
+
rms = metal::rsqrt(tt / {float(cols)}f + {eps:.9g}f);
|
| 621 |
+
}}
|
| 622 |
+
"""
|
| 623 |
+
else:
|
| 624 |
+
norm_pre = ""
|
| 625 |
+
return f"""
|
| 626 |
+
uint tid = thread_position_in_threadgroup.x;
|
| 627 |
+
uint lane = tid & 31u;
|
| 628 |
+
uint sg = tid >> 5u;
|
| 629 |
+
uint blk = threadgroup_position_in_grid.x * {sg_per_tg}u + sg;
|
| 630 |
+
uint row0 = blk * 8u;
|
| 631 |
+
uint r = lane & 7u;
|
| 632 |
+
uint wsl = lane >> 3u;
|
| 633 |
+
float rms = 1.0f;
|
| 634 |
+
{norm_pre}
|
| 635 |
+
{xb_decl}
|
| 636 |
+
threadgroup float lut[{gpt} * 17];
|
| 637 |
+
const device uint* p32 = (const device uint*)bp;
|
| 638 |
+
const device uint* n32 = (const device uint*)bn;
|
| 639 |
+
const device uint* h32 = (const device uint*)br;
|
| 640 |
+
float accLo = 0.0f, accHi = 0.0f;
|
| 641 |
+
for (uint t = 0u; t < {ntiles}u; ++t) {{
|
| 642 |
+
threadgroup_barrier(metal::mem_flags::mem_threadgroup);
|
| 643 |
+
{stage}
|
| 644 |
+
for (uint e = tid; e < {gpt}u * 16u; e += {nthreads}u) {{
|
| 645 |
+
uint gl = e >> 4u, m = e & 15u;
|
| 646 |
+
float v = 0.0f;
|
| 647 |
+
for (uint i = 0u; i < 4u; ++i)
|
| 648 |
+
if ((m >> i) & 1u) v += {xval};
|
| 649 |
+
lut[gl * 17u + m] = v;
|
| 650 |
+
}}
|
| 651 |
+
threadgroup_barrier(metal::mem_flags::mem_threadgroup);
|
| 652 |
+
if (row0 < {rows}u) {{
|
| 653 |
+
for (uint wl = wsl; wl < {w0}u; wl += 4u) {{
|
| 654 |
+
ulong base = ((ulong)blk * {W}u + t * {w0}u + wl) * 8u + r;
|
| 655 |
+
uint pw = p32[base], nw2 = n32[base], hw = h32[base];
|
| 656 |
+
uint a = pw & ~hw, b = nw2 & ~hw, cc = pw & hw, d = nw2 & hw;
|
| 657 |
+
uint gbase = wl * 8u;
|
| 658 |
+
for (uint k = 0u; k < 8u; ++k) {{
|
| 659 |
+
const threadgroup float* lg = lut + (gbase + k) * 17u;
|
| 660 |
+
uint sh = k * 4u;
|
| 661 |
+
accLo += lg[(a >> sh) & 15u] - lg[(b >> sh) & 15u];
|
| 662 |
+
accHi += lg[(cc >> sh) & 15u] - lg[(d >> sh) & 15u];
|
| 663 |
+
}}
|
| 664 |
+
}}
|
| 665 |
+
}}
|
| 666 |
+
}}
|
| 667 |
+
if (row0 >= {rows}u) return;
|
| 668 |
+
accLo += metal::simd_shuffle_down(accLo, 16u);
|
| 669 |
+
accLo += metal::simd_shuffle_down(accLo, 8u);
|
| 670 |
+
accHi += metal::simd_shuffle_down(accHi, 16u);
|
| 671 |
+
accHi += metal::simd_shuffle_down(accHi, 8u);
|
| 672 |
+
if (lane < 8u) {{
|
| 673 |
+
uint row = row0 + lane;
|
| 674 |
+
float y = metal::fma(slo[row], accLo, shi[row] * accHi);
|
| 675 |
+
{"y += bias[row];" if has_bias else ""}
|
| 676 |
+
{"y += resid[row];" if add_resid else ""}
|
| 677 |
+
out[row] = y;
|
| 678 |
+
}}
|
| 679 |
+
"""
|
| 680 |
+
|
| 681 |
+
|
| 682 |
+
def _gemv_source_v8(rows, cols, rs, sg_per_tg, has_bias, fuse_norm, add_resid,
|
| 683 |
+
glu, eps):
|
| 684 |
+
"""v8: v7 LUT decode + DOUBLE-BUFFERED LUT — build tile t+1's LUT into
|
| 685 |
+
the alternate threadgroup buffer, then decode tile t: one barrier per
|
| 686 |
+
tile (plain/norm; two for glu) instead of v7's two/three. Entry build
|
| 687 |
+
order identical to v7 -> outputs BIT-IDENTICAL (gated 2026-07-28)."""
|
| 688 |
+
assert rs % 4 == 0 and rs * 8 == cols and cols % 32 == 0 and rows % 8 == 0
|
| 689 |
+
tile = _v5_tile(cols)
|
| 690 |
+
ntiles = cols // tile
|
| 691 |
+
gpt = tile // 4
|
| 692 |
+
w0 = tile // 32
|
| 693 |
+
W = rs // 4
|
| 694 |
+
nthreads = sg_per_tg * 32
|
| 695 |
+
if glu:
|
| 696 |
+
xval = "xb[bb * %du + gl * 4u + i]" % tile
|
| 697 |
+
xb_decl = f"threadgroup float xb[2 * {tile}];"
|
| 698 |
+
stage = f"""
|
| 699 |
+
for (uint cl = tid; cl < {tile}u; cl += {nthreads}u) {{
|
| 700 |
+
uint c = tt * {tile}u + cl;
|
| 701 |
+
float gg = x[c];
|
| 702 |
+
xb[bb * {tile}u + cl] = gg / (1.0f + metal::exp(-gg)) * x[c + {cols}u];
|
| 703 |
+
}}
|
| 704 |
+
threadgroup_barrier(metal::mem_flags::mem_threadgroup);"""
|
| 705 |
+
elif fuse_norm:
|
| 706 |
+
xval = "x[tt * %du + gl * 4u + i] * nw[tt * %du + gl * 4u + i] * rms" % (tile, tile)
|
| 707 |
+
xb_decl = ""
|
| 708 |
+
stage = ""
|
| 709 |
+
else:
|
| 710 |
+
xval = "x[tt * %du + gl * 4u + i]" % tile
|
| 711 |
+
xb_decl = ""
|
| 712 |
+
stage = ""
|
| 713 |
+
build = f"""{stage}
|
| 714 |
+
for (uint e = tid; e < {gpt}u * 16u; e += {nthreads}u) {{
|
| 715 |
+
uint gl = e >> 4u, m = e & 15u;
|
| 716 |
+
float v = 0.0f;
|
| 717 |
+
for (uint i = 0u; i < 4u; ++i)
|
| 718 |
+
if ((m >> i) & 1u) v += {xval};
|
| 719 |
+
lut[bb * {gpt}u * 17u + gl * 17u + m] = v;
|
| 720 |
+
}}"""
|
| 721 |
+
return _v89_frame(rows, cols, rs, sg_per_tg, has_bias, fuse_norm,
|
| 722 |
+
add_resid, eps, tile, ntiles, gpt, w0, W, xb_decl, build)
|
| 723 |
+
|
| 724 |
+
|
| 725 |
+
def _gemv_source_v9(rows, cols, rs, sg_per_tg, has_bias, fuse_norm, add_resid,
|
| 726 |
+
glu, eps):
|
| 727 |
+
"""v9: double-buffered LUT + thread-per-GROUP incremental build. Each
|
| 728 |
+
builder thread computes its group's 4 effective x values ONCE in
|
| 729 |
+
registers (silu/norm included — no xb stage) and writes the 16 subset
|
| 730 |
+
sums by ascending-bit doubling; the norm path applies rms via explicit
|
| 731 |
+
fma to match v7's contraction. ONE barrier per tile for every flag
|
| 732 |
+
combo. BIT-IDENTICAL to v7 (gated: TF argmax 9/9, 128-tok greedy
|
| 733 |
+
identical)."""
|
| 734 |
+
assert rs % 4 == 0 and rs * 8 == cols and cols % 32 == 0 and rows % 8 == 0
|
| 735 |
+
tile = _v5_tile(cols)
|
| 736 |
+
ntiles = cols // tile
|
| 737 |
+
gpt = tile // 4
|
| 738 |
+
w0 = tile // 32
|
| 739 |
+
W = rs // 4
|
| 740 |
+
nthreads = sg_per_tg * 32
|
| 741 |
+
if glu:
|
| 742 |
+
xv = f"""float gg = x[c + i];
|
| 743 |
+
xv[i] = gg / (1.0f + metal::exp(-gg)) * x[c + i + {cols}u];"""
|
| 744 |
+
elif fuse_norm:
|
| 745 |
+
xv = "xv[i] = x[c + i] * nw[c + i];"
|
| 746 |
+
else:
|
| 747 |
+
xv = "xv[i] = x[c + i];"
|
| 748 |
+
if fuse_norm and not glu:
|
| 749 |
+
sums = """lg[0] = 0.0f;
|
| 750 |
+
lg[1] = metal::fma(xv[0], rms, 0.0f);
|
| 751 |
+
lg[2] = metal::fma(xv[1], rms, 0.0f);
|
| 752 |
+
lg[3] = metal::fma(xv[1], rms, lg[1]);
|
| 753 |
+
for (uint m = 0u; m < 4u; ++m) lg[4u + m] = metal::fma(xv[2], rms, lg[m]);
|
| 754 |
+
for (uint m = 0u; m < 8u; ++m) lg[8u + m] = metal::fma(xv[3], rms, lg[m]);"""
|
| 755 |
+
else:
|
| 756 |
+
sums = """lg[0] = 0.0f;
|
| 757 |
+
lg[1] = xv[0];
|
| 758 |
+
lg[2] = xv[1];
|
| 759 |
+
lg[3] = xv[0] + xv[1];
|
| 760 |
+
for (uint m = 0u; m < 4u; ++m) lg[4u + m] = lg[m] + xv[2];
|
| 761 |
+
for (uint m = 0u; m < 8u; ++m) lg[8u + m] = lg[m] + xv[3];"""
|
| 762 |
+
build = f"""
|
| 763 |
+
for (uint gl = tid; gl < {gpt}u; gl += {nthreads}u) {{
|
| 764 |
+
uint c = tt * {tile}u + gl * 4u;
|
| 765 |
+
float xv[4];
|
| 766 |
+
for (uint i = 0u; i < 4u; ++i) {{
|
| 767 |
+
{xv}
|
| 768 |
+
}}
|
| 769 |
+
threadgroup float* lg = lut + bb * {gpt}u * 17u + gl * 17u;
|
| 770 |
+
{sums}
|
| 771 |
+
}}"""
|
| 772 |
+
return _v89_frame(rows, cols, rs, sg_per_tg, has_bias, fuse_norm,
|
| 773 |
+
add_resid, eps, tile, ntiles, gpt, w0, W, "", build)
|
| 774 |
+
|
| 775 |
+
|
| 776 |
+
def _v89_frame(rows, cols, rs, sg_per_tg, has_bias, fuse_norm, add_resid,
|
| 777 |
+
eps, tile, ntiles, gpt, w0, W, xb_decl, build):
|
| 778 |
+
"""shared v8/v9 skeleton: v7's lane mapping + decode, double-buffered
|
| 779 |
+
LUT loop (prologue build, then build-next/decode-current/one-barrier)."""
|
| 780 |
+
nthreads = sg_per_tg * 32
|
| 781 |
+
if fuse_norm:
|
| 782 |
+
norm_pre = f"""
|
| 783 |
+
threadgroup float ssq_sh[{sg_per_tg}];
|
| 784 |
+
{{
|
| 785 |
+
float ssq = 0.0f;
|
| 786 |
+
for (uint c = tid; c < {cols}u; c += {nthreads}u) {{ float v = x[c]; ssq = metal::fma(v, v, ssq); }}
|
| 787 |
+
ssq = metal::simd_sum(ssq);
|
| 788 |
+
if (lane == 0u) ssq_sh[sg] = ssq;
|
| 789 |
+
threadgroup_barrier(metal::mem_flags::mem_threadgroup);
|
| 790 |
+
float tt = 0.0f;
|
| 791 |
+
for (uint i = 0u; i < {sg_per_tg}u; ++i) tt += ssq_sh[i];
|
| 792 |
+
rms = metal::rsqrt(tt / {float(cols)}f + {eps:.9g}f);
|
| 793 |
+
}}
|
| 794 |
+
"""
|
| 795 |
+
else:
|
| 796 |
+
norm_pre = ""
|
| 797 |
+
return f"""
|
| 798 |
+
uint tid = thread_position_in_threadgroup.x;
|
| 799 |
+
uint lane = tid & 31u;
|
| 800 |
+
uint sg = tid >> 5u;
|
| 801 |
+
uint blk = threadgroup_position_in_grid.x * {sg_per_tg}u + sg;
|
| 802 |
+
uint row0 = blk * 8u;
|
| 803 |
+
uint r = lane & 7u;
|
| 804 |
+
uint wsl = lane >> 3u;
|
| 805 |
+
float rms = 1.0f;
|
| 806 |
+
{norm_pre}
|
| 807 |
+
{xb_decl}
|
| 808 |
+
threadgroup float lut[2 * {gpt} * 17];
|
| 809 |
+
const device uint* p32 = (const device uint*)bp;
|
| 810 |
+
const device uint* n32 = (const device uint*)bn;
|
| 811 |
+
const device uint* h32 = (const device uint*)br;
|
| 812 |
+
float accLo = 0.0f, accHi = 0.0f;
|
| 813 |
+
{{
|
| 814 |
+
uint tt = 0u, bb = 0u;
|
| 815 |
+
{build}
|
| 816 |
+
}}
|
| 817 |
+
threadgroup_barrier(metal::mem_flags::mem_threadgroup);
|
| 818 |
+
for (uint t = 0u; t < {ntiles}u; ++t) {{
|
| 819 |
+
if (t + 1u < {ntiles}u) {{
|
| 820 |
+
uint tt = t + 1u, bb = (t + 1u) & 1u;
|
| 821 |
+
{build}
|
| 822 |
+
}}
|
| 823 |
+
{{
|
| 824 |
+
const threadgroup float* lutc = lut + (t & 1u) * {gpt}u * 17u;
|
| 825 |
+
if (row0 < {rows}u) {{
|
| 826 |
+
for (uint wl = wsl; wl < {w0}u; wl += 4u) {{
|
| 827 |
+
ulong base = ((ulong)blk * {W}u + t * {w0}u + wl) * 8u + r;
|
| 828 |
+
uint pw = p32[base], nw2 = n32[base], hw = h32[base];
|
| 829 |
+
uint a = pw & ~hw, b = nw2 & ~hw, cc = pw & hw, d = nw2 & hw;
|
| 830 |
+
uint gbase = wl * 8u;
|
| 831 |
+
for (uint k = 0u; k < 8u; ++k) {{
|
| 832 |
+
const threadgroup float* lg = lutc + (gbase + k) * 17u;
|
| 833 |
+
uint sh = k * 4u;
|
| 834 |
+
accLo += lg[(a >> sh) & 15u] - lg[(b >> sh) & 15u];
|
| 835 |
+
accHi += lg[(cc >> sh) & 15u] - lg[(d >> sh) & 15u];
|
| 836 |
+
}}
|
| 837 |
+
}}
|
| 838 |
+
}}
|
| 839 |
+
}}
|
| 840 |
+
threadgroup_barrier(metal::mem_flags::mem_threadgroup);
|
| 841 |
+
}}
|
| 842 |
+
if (row0 >= {rows}u) return;
|
| 843 |
+
accLo += metal::simd_shuffle_down(accLo, 16u);
|
| 844 |
+
accLo += metal::simd_shuffle_down(accLo, 8u);
|
| 845 |
+
accHi += metal::simd_shuffle_down(accHi, 16u);
|
| 846 |
+
accHi += metal::simd_shuffle_down(accHi, 8u);
|
| 847 |
+
if (lane < 8u) {{
|
| 848 |
+
uint row = row0 + lane;
|
| 849 |
+
float y = metal::fma(slo[row], accLo, shi[row] * accHi);
|
| 850 |
+
{"y += bias[row];" if has_bias else ""}
|
| 851 |
+
{"y += resid[row];" if add_resid else ""}
|
| 852 |
+
out[row] = y;
|
| 853 |
+
}}
|
| 854 |
+
"""
|
| 855 |
+
|
| 856 |
+
|
| 857 |
+
def prep_planes(planes, cols=None):
|
| 858 |
+
"""Trim row padding (row_stride*8 > cols in real exports; pad bits are
|
| 859 |
+
zero) so rs*8 == cols, then apply the layout transform required by the
|
| 860 |
+
active kernel version. planes = (bp, bn, br) mx.uint8 [rows, rs]."""
|
| 861 |
+
if cols is not None and cols % 8 == 0 and planes[0].shape[1] > cols // 8:
|
| 862 |
+
planes = tuple(mx.contiguous(p[:, :cols // 8]) for p in planes)
|
| 863 |
+
if KERNEL_VERSION >= 5: # v5/v6 interleaved layout
|
| 864 |
+
return tuple(pack_planes_v5(p) for p in planes)
|
| 865 |
+
return planes
|
| 866 |
+
|
| 867 |
+
|
| 868 |
+
def pack_planes_v5(plane_mx):
|
| 869 |
+
"""[rows, rs] uint8 -> v5 interleaved layout (same byte count):
|
| 870 |
+
uint32 words reordered to [row_block(8), word, row_in_block]."""
|
| 871 |
+
rows, rs = plane_mx.shape
|
| 872 |
+
assert rows % 8 == 0 and rs % 4 == 0
|
| 873 |
+
w = plane_mx.view(mx.uint32).reshape(rows // 8, 8, rs // 4)
|
| 874 |
+
return mx.contiguous(w.transpose(0, 2, 1)).view(mx.uint8).reshape(rows, rs)
|
| 875 |
+
|
| 876 |
+
|
| 877 |
+
def _rows_per_sg(rows, cols, rs, version, glu):
|
| 878 |
+
if version == 3 and rs % 4 == 0 and rs * 8 == cols and cols % 32 == 0 and rows % RPSG == 0:
|
| 879 |
+
return RPSG
|
| 880 |
+
return 1
|
| 881 |
+
|
| 882 |
+
|
| 883 |
+
def get_trit_gemv(rows, cols, rs, has_bias=False, fuse_norm=False, add_resid=False,
|
| 884 |
+
glu=False, eps=1e-6, version=None):
|
| 885 |
+
version = KERNEL_VERSION if version is None else version
|
| 886 |
+
if version >= 2 and not (rs % 4 == 0 and rs * 8 == cols and cols % 32 == 0):
|
| 887 |
+
version = 1 # v2+ layout preconditions not met, fall back
|
| 888 |
+
if version >= 3 and rows % (8 if version >= 5 else RPSG) != 0:
|
| 889 |
+
version = 2
|
| 890 |
+
key = ("trit", rows, cols, rs, has_bias, fuse_norm, add_resid, glu, round(eps, 12), version)
|
| 891 |
+
if key in _CACHE:
|
| 892 |
+
return _CACHE[key]
|
| 893 |
+
inputs = ["bp", "bn", "br", "slo", "shi", "x"]
|
| 894 |
+
if has_bias:
|
| 895 |
+
inputs.append("bias")
|
| 896 |
+
if fuse_norm and not glu:
|
| 897 |
+
inputs.append("nw")
|
| 898 |
+
if add_resid:
|
| 899 |
+
inputs.append("resid")
|
| 900 |
+
gen = {1: _gemv_source, 2: _gemv_source_v2, 3: _gemv_source_v3,
|
| 901 |
+
4: _gemv_source_v4, 5: _gemv_source_v5, 6: _gemv_source_v6,
|
| 902 |
+
7: _gemv_source_v7, 8: _gemv_source_v8, 9: _gemv_source_v9}[version]
|
| 903 |
+
src = gen(rows, cols, rs, SG_PER_TG, has_bias, fuse_norm, add_resid, glu, eps)
|
| 904 |
+
name = "tritgemv%d_r%d_c%d_b%d_n%d_a%d_g%d" % (version, rows, cols, has_bias, fuse_norm,
|
| 905 |
+
add_resid, glu)
|
| 906 |
+
k = mx.fast.metal_kernel(name=name, input_names=inputs, output_names=["out"],
|
| 907 |
+
source=src, header=_HEADER)
|
| 908 |
+
_CACHE[key] = (k, inputs, version)
|
| 909 |
+
return _CACHE[key]
|
| 910 |
+
|
| 911 |
+
|
| 912 |
+
def _s8_source_v2(rows, cols, rs, sg_per_tg, fuse_norm, eps, rpsg=RPSG):
|
| 913 |
+
"""v2 int8 GEMV: rpsg rows per simdgroup, float4 x reuse across rows."""
|
| 914 |
+
assert rs % 4 == 0 and cols % 4 == 0 and rows % rpsg == 0
|
| 915 |
+
if fuse_norm:
|
| 916 |
+
xload = "float4 xv = x4[i] * nw4[i] * rms;"
|
| 917 |
+
norm_pre = _norm_pre(cols, eps)
|
| 918 |
+
xpre = """
|
| 919 |
+
const device float4* x4 = (const device float4*)x;
|
| 920 |
+
const device float4* nw4 = (const device float4*)nw;"""
|
| 921 |
+
else:
|
| 922 |
+
xload = "float4 xv = x4[i];"
|
| 923 |
+
norm_pre = ""
|
| 924 |
+
xpre = """
|
| 925 |
+
const device float4* x4 = (const device float4*)x;"""
|
| 926 |
+
return f"""
|
| 927 |
+
uint tid = thread_position_in_threadgroup.x;
|
| 928 |
+
uint lane = tid & 31u;
|
| 929 |
+
uint sg = tid >> 5u;
|
| 930 |
+
uint row0 = (threadgroup_position_in_grid.x * {sg_per_tg}u + sg) * {rpsg}u;
|
| 931 |
+
if (row0 >= {rows}u) return;
|
| 932 |
+
{norm_pre}{xpre}
|
| 933 |
+
float acc[{rpsg}];
|
| 934 |
+
for (uint r = 0u; r < {rpsg}u; ++r) acc[r] = 0.0f;
|
| 935 |
+
const float4 one4 = float4(1.0f);
|
| 936 |
+
for (uint i = lane; i < {cols // 4}u; i += 32u) {{
|
| 937 |
+
{xload}
|
| 938 |
+
for (uint r = 0u; r < {rpsg}u; ++r) {{
|
| 939 |
+
char4 wv = *(const device char4*)((const device char*)W
|
| 940 |
+
+ (ulong)(row0 + r) * {rs}u + i * 4u);
|
| 941 |
+
acc[r] += metal::dot(float4(wv.x, wv.y, wv.z, wv.w) * xv, one4);
|
| 942 |
+
}}
|
| 943 |
+
}}
|
| 944 |
+
for (uint r = 0u; r < {rpsg}u; ++r) {{
|
| 945 |
+
float a = metal::simd_sum(acc[r]);
|
| 946 |
+
if (lane == 0u) out[row0 + r] = scale[row0 + r] * a;
|
| 947 |
+
}}
|
| 948 |
+
"""
|
| 949 |
+
|
| 950 |
+
|
| 951 |
+
def get_s8_gemv(rows, cols, rs, fuse_norm=False, eps=1e-6, version=None):
|
| 952 |
+
version = KERNEL_VERSION if version is None else version
|
| 953 |
+
v = 2 if (version >= 2 and rs % 4 == 0 and cols % 4 == 0 and rows % RPSG == 0) else 1
|
| 954 |
+
key = ("s8", rows, cols, rs, fuse_norm, round(eps, 12), v)
|
| 955 |
+
if key in _CACHE:
|
| 956 |
+
return _CACHE[key]
|
| 957 |
+
inputs = ["W", "scale", "x"]
|
| 958 |
+
if fuse_norm:
|
| 959 |
+
inputs.append("nw")
|
| 960 |
+
src = (_s8_source_v2 if v == 2 else _s8_source)(rows, cols, rs, SG_PER_TG, fuse_norm, eps)
|
| 961 |
+
assert src is not None, "cols must be multiple of 4"
|
| 962 |
+
name = "s8gemv%d_r%d_c%d_n%d" % (v, rows, cols, fuse_norm)
|
| 963 |
+
k = mx.fast.metal_kernel(name=name, input_names=inputs, output_names=["out"],
|
| 964 |
+
source=src, header=_HEADER)
|
| 965 |
+
_CACHE[key] = (k, inputs, v)
|
| 966 |
+
return _CACHE[key]
|
| 967 |
+
|
| 968 |
+
|
| 969 |
+
def _grid_for(rows, rpsg=1):
|
| 970 |
+
nsg = (rows + rpsg - 1) // rpsg # one simdgroup per rpsg rows
|
| 971 |
+
ntg = (nsg + SG_PER_TG - 1) // SG_PER_TG
|
| 972 |
+
return (ntg * SG_PER_TG * 32, 1, 1), (SG_PER_TG * 32, 1, 1)
|
| 973 |
+
|
| 974 |
+
|
| 975 |
+
def trit_gemv(planes, slo, shi, x, bias=None, norm_w=None, resid=None, glu=False,
|
| 976 |
+
out_rows=None, eps=1e-6, stream=None):
|
| 977 |
+
"""planes = (bp, bn, br) mx.uint8 [rows, rs]. x fp32. Returns fp32 [out_rows]."""
|
| 978 |
+
bp, bn, br = planes
|
| 979 |
+
rows, rs = bp.shape
|
| 980 |
+
out_rows = out_rows if out_rows is not None else rows
|
| 981 |
+
cols = x.shape[0] if not glu else x.shape[0] // 2
|
| 982 |
+
k, names, version = get_trit_gemv(rows, cols, rs, bias is not None, norm_w is not None,
|
| 983 |
+
resid is not None, glu, eps)
|
| 984 |
+
vals = {"bp": bp, "bn": bn, "br": br, "slo": slo, "shi": shi, "x": x,
|
| 985 |
+
"bias": bias, "nw": norm_w, "resid": resid}
|
| 986 |
+
grid, tg = _grid_for(out_rows, 8 if version >= 5 else (RPSG if version >= 3 else 1))
|
| 987 |
+
return k(inputs=[vals[n] for n in names], grid=grid, threadgroup=tg,
|
| 988 |
+
output_shapes=[(out_rows,)], output_dtypes=[mx.float32], stream=stream)[0]
|
| 989 |
+
|
| 990 |
+
|
| 991 |
+
def s8_gemv(W, scale, x, norm_w=None, eps=1e-6, stream=None):
|
| 992 |
+
rows, rs = W.shape
|
| 993 |
+
cols = x.shape[0]
|
| 994 |
+
k, names, v = get_s8_gemv(rows, cols, rs, norm_w is not None, eps)
|
| 995 |
+
vals = {"W": W, "scale": scale, "x": x, "nw": norm_w}
|
| 996 |
+
grid, tg = _grid_for(rows, RPSG if v == 2 else 1)
|
| 997 |
+
return k(inputs=[vals[n] for n in names], grid=grid, threadgroup=tg,
|
| 998 |
+
output_shapes=[(rows,)], output_dtypes=[mx.float32], stream=stream)[0]
|
| 999 |
+
|
| 1000 |
+
|
| 1001 |
+
# ---------------- fused rope + KV cache append ----------------
|
| 1002 |
+
|
| 1003 |
+
def get_rope_append(n_heads, n_kv, hd, maxseq):
|
| 1004 |
+
"""One dispatch: rope q (n_heads*hd) and k (n_kv*hd) from the fused qkv
|
| 1005 |
+
vector, write roped k and raw v in-place into the preallocated caches at
|
| 1006 |
+
position pos (device scalar), output roped q. NeoX rotate-half, full hd.
|
| 1007 |
+
kc/vc passed as inputs and written in place (stream-serial ordering with
|
| 1008 |
+
the consumer sdpa; correctness enforced by the e2e gate)."""
|
| 1009 |
+
key = ("rope_append", n_heads, n_kv, hd, maxseq)
|
| 1010 |
+
if key in _CACHE:
|
| 1011 |
+
return _CACHE[key]
|
| 1012 |
+
qd, kd = n_heads * hd, n_kv * hd
|
| 1013 |
+
h2 = hd // 2
|
| 1014 |
+
src = f"""
|
| 1015 |
+
uint i = thread_position_in_grid.x; // one thread per q/k element
|
| 1016 |
+
uint pos = (uint)posbuf[0];
|
| 1017 |
+
device float* kc_w = (device float*)kc;
|
| 1018 |
+
device float* vc_w = (device float*)vc;
|
| 1019 |
+
if (i == 0u) {{ ((device float*)mask)[pos] = 0.0f; }}
|
| 1020 |
+
if (i < {qd}u + {kd}u) {{
|
| 1021 |
+
uint hidx = i / {hd}u, d = i % {hd}u;
|
| 1022 |
+
float c = ct[pos * {h2}u + (d % {h2}u)];
|
| 1023 |
+
float sn = st[pos * {h2}u + (d % {h2}u)];
|
| 1024 |
+
float v = qkv[i];
|
| 1025 |
+
float vrot = (d < {h2}u) ? -qkv[hidx * {hd}u + d + {h2}u]
|
| 1026 |
+
: qkv[hidx * {hd}u + d - {h2}u];
|
| 1027 |
+
float r = metal::fma(v, c, vrot * sn);
|
| 1028 |
+
if (i < {qd}u) {{
|
| 1029 |
+
qout[i] = r;
|
| 1030 |
+
}} else {{
|
| 1031 |
+
uint kh = (i - {qd}u) / {hd}u;
|
| 1032 |
+
kc_w[(ulong)(kh * {maxseq}u + pos) * {hd}u + d] = r;
|
| 1033 |
+
}}
|
| 1034 |
+
}} else if (i < {qd}u + 2u * {kd}u) {{
|
| 1035 |
+
uint j = i - {qd}u - {kd}u;
|
| 1036 |
+
uint vh = j / {hd}u, d = j % {hd}u;
|
| 1037 |
+
vc_w[(ulong)(vh * {maxseq}u + pos) * {hd}u + d] = qkv[{qd}u + {kd}u + j];
|
| 1038 |
+
}}
|
| 1039 |
+
"""
|
| 1040 |
+
k = mx.fast.metal_kernel(name="rope_append_%d_%d_%d" % (n_heads, n_kv, hd),
|
| 1041 |
+
input_names=["qkv", "kc", "vc", "ct", "st", "posbuf", "mask"],
|
| 1042 |
+
output_names=["qout"], source=src)
|
| 1043 |
+
_CACHE[key] = k
|
| 1044 |
+
return k
|
| 1045 |
+
|
| 1046 |
+
|
| 1047 |
+
def rope_append(qkv, kc, vc, ct, st, posbuf, mask, n_heads, n_kv, hd, maxseq, stream=None):
|
| 1048 |
+
k = get_rope_append(n_heads, n_kv, hd, maxseq)
|
| 1049 |
+
n = (n_heads + 2 * n_kv) * hd
|
| 1050 |
+
nthr = ((n + 255) // 256) * 256
|
| 1051 |
+
return k(inputs=[qkv, kc, vc, ct, st, posbuf, mask], grid=(nthr, 1, 1),
|
| 1052 |
+
threadgroup=(256, 1, 1),
|
| 1053 |
+
output_shapes=[(n_heads * hd,)], output_dtypes=[mx.float32], stream=stream)[0]
|
mlx/generation_config.json
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"bos_token_id": 151643,
|
| 3 |
+
"do_sample": false,
|
| 4 |
+
"eos_token_id": [
|
| 5 |
+
151645,
|
| 6 |
+
151643
|
| 7 |
+
],
|
| 8 |
+
"pad_token_id": 151643,
|
| 9 |
+
"transformers_version": "4.51.0"
|
| 10 |
+
}
|
mlx/merges.txt
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
mlx/receipts/bench_0p6b_m5.json
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"mode": "bench",
|
| 3 |
+
"model": "neutrino-0p6b_v4_base.bin",
|
| 4 |
+
"prompt_ids": [
|
| 5 |
+
785,
|
| 6 |
+
1376,
|
| 7 |
+
4522,
|
| 8 |
+
4815,
|
| 9 |
+
71617,
|
| 10 |
+
658,
|
| 11 |
+
29728,
|
| 12 |
+
14155,
|
| 13 |
+
374
|
| 14 |
+
],
|
| 15 |
+
"gen_tokens": 512,
|
| 16 |
+
"runs": [
|
| 17 |
+
{
|
| 18 |
+
"run": 0,
|
| 19 |
+
"tokens": 512,
|
| 20 |
+
"seconds": 2.536120083008427,
|
| 21 |
+
"tok_s": 201.88318503934923
|
| 22 |
+
},
|
| 23 |
+
{
|
| 24 |
+
"run": 1,
|
| 25 |
+
"tokens": 512,
|
| 26 |
+
"seconds": 2.596512083997368,
|
| 27 |
+
"tok_s": 197.1876053092611
|
| 28 |
+
},
|
| 29 |
+
{
|
| 30 |
+
"run": 2,
|
| 31 |
+
"tokens": 512,
|
| 32 |
+
"seconds": 2.534324457999901,
|
| 33 |
+
"tok_s": 202.02622374724365
|
| 34 |
+
},
|
| 35 |
+
{
|
| 36 |
+
"run": 3,
|
| 37 |
+
"tokens": 512,
|
| 38 |
+
"seconds": 2.5451969589921646,
|
| 39 |
+
"tok_s": 201.16321379023626
|
| 40 |
+
},
|
| 41 |
+
{
|
| 42 |
+
"run": 4,
|
| 43 |
+
"tokens": 512,
|
| 44 |
+
"seconds": 2.5724296660046093,
|
| 45 |
+
"tok_s": 199.03362442372122
|
| 46 |
+
}
|
| 47 |
+
],
|
| 48 |
+
"median_tok_s": 201.16321379023626,
|
| 49 |
+
"peak_mem_gib": 0.5260553508996964,
|
| 50 |
+
"utc": "2026-07-25T09:39:14Z"
|
| 51 |
+
}
|
mlx/receipts/cachegate_0p6b.json
ADDED
|
@@ -0,0 +1,61 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"cache": "hf_greedy_cache_86565bf6.json",
|
| 3 |
+
"container_md5": "86565bf6183e8031943aac9fd9771385",
|
| 4 |
+
"prompts": [
|
| 5 |
+
{
|
| 6 |
+
"prompt": [
|
| 7 |
+
1,
|
| 8 |
+
791,
|
| 9 |
+
1283,
|
| 10 |
+
315,
|
| 11 |
+
220,
|
| 12 |
+
17,
|
| 13 |
+
42,
|
| 14 |
+
99
|
| 15 |
+
],
|
| 16 |
+
"steps": 128,
|
| 17 |
+
"flips": 0,
|
| 18 |
+
"flip_detail": [],
|
| 19 |
+
"pass": true
|
| 20 |
+
},
|
| 21 |
+
{
|
| 22 |
+
"prompt": [
|
| 23 |
+
1,
|
| 24 |
+
9707,
|
| 25 |
+
374,
|
| 26 |
+
264,
|
| 27 |
+
1296,
|
| 28 |
+
13,
|
| 29 |
+
28,
|
| 30 |
+
7
|
| 31 |
+
],
|
| 32 |
+
"steps": 128,
|
| 33 |
+
"flips": 0,
|
| 34 |
+
"flip_detail": [],
|
| 35 |
+
"pass": true
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"prompt": [
|
| 39 |
+
1,
|
| 40 |
+
2610,
|
| 41 |
+
525,
|
| 42 |
+
498,
|
| 43 |
+
1091,
|
| 44 |
+
11,
|
| 45 |
+
16,
|
| 46 |
+
3
|
| 47 |
+
],
|
| 48 |
+
"steps": 128,
|
| 49 |
+
"flips": 0,
|
| 50 |
+
"flip_detail": [],
|
| 51 |
+
"pass": true
|
| 52 |
+
}
|
| 53 |
+
],
|
| 54 |
+
"tie_tol": 0.04,
|
| 55 |
+
"max_flips_per_prompt": 8,
|
| 56 |
+
"total_flips": 0,
|
| 57 |
+
"worst_gap": 0.0,
|
| 58 |
+
"ok": true,
|
| 59 |
+
"mode": "cachegate",
|
| 60 |
+
"model": "neutrino-0p6b_v4_base.bin"
|
| 61 |
+
}
|
mlx/receipts/kernel_version_sweep_0p6b.json
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"receipt": "TRIT_METAL_V kernel-version sweep, 0.6B container 91c0b26d, 512-token greedy x3, M5 MacBook 16GB (concurrent 2.5MiB/s download noted)",
|
| 3 |
+
"utc": "2026-07-25T09:52:00Z",
|
| 4 |
+
"medians_tok_s": {
|
| 5 |
+
"v7": 185.6,
|
| 6 |
+
"v6": 120.4,
|
| 7 |
+
"v5": 99.8,
|
| 8 |
+
"v2": 105.2
|
| 9 |
+
},
|
| 10 |
+
"note": "v7 = default; official quiet-machine v7 receipt is bench_0p6b_m5.json (201.2)"
|
| 11 |
+
}
|
mlx/receipts/pathgate_0p6b.json
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"n": 32,
|
| 3 |
+
"identical": true,
|
| 4 |
+
"first_divergence": null,
|
| 5 |
+
"fused": [
|
| 6 |
+
429,
|
| 7 |
+
807,
|
| 8 |
+
646,
|
| 9 |
+
387,
|
| 10 |
+
1483,
|
| 11 |
+
311,
|
| 12 |
+
1614,
|
| 13 |
+
6351,
|
| 14 |
+
5942,
|
| 15 |
+
11,
|
| 16 |
+
1741,
|
| 17 |
+
438,
|
| 18 |
+
279,
|
| 19 |
+
3738,
|
| 20 |
+
8109,
|
| 21 |
+
11,
|
| 22 |
+
323,
|
| 23 |
+
807,
|
| 24 |
+
646,
|
| 25 |
+
387,
|
| 26 |
+
1483,
|
| 27 |
+
311,
|
| 28 |
+
11625,
|
| 29 |
+
5322,
|
| 30 |
+
304,
|
| 31 |
+
5257,
|
| 32 |
+
5043,
|
| 33 |
+
13,
|
| 34 |
+
576,
|
| 35 |
+
1376,
|
| 36 |
+
4522,
|
| 37 |
+
4815
|
| 38 |
+
],
|
| 39 |
+
"mxops": [
|
| 40 |
+
429,
|
| 41 |
+
807,
|
| 42 |
+
646,
|
| 43 |
+
387,
|
| 44 |
+
1483,
|
| 45 |
+
311,
|
| 46 |
+
1614,
|
| 47 |
+
6351,
|
| 48 |
+
5942,
|
| 49 |
+
11,
|
| 50 |
+
1741,
|
| 51 |
+
438,
|
| 52 |
+
279,
|
| 53 |
+
3738,
|
| 54 |
+
8109,
|
| 55 |
+
11,
|
| 56 |
+
323,
|
| 57 |
+
807,
|
| 58 |
+
646,
|
| 59 |
+
387,
|
| 60 |
+
1483,
|
| 61 |
+
311,
|
| 62 |
+
11625,
|
| 63 |
+
5322,
|
| 64 |
+
304,
|
| 65 |
+
5257,
|
| 66 |
+
5043,
|
| 67 |
+
13,
|
| 68 |
+
576,
|
| 69 |
+
1376,
|
| 70 |
+
4522,
|
| 71 |
+
4815
|
| 72 |
+
],
|
| 73 |
+
"mode": "pathgate",
|
| 74 |
+
"model": "neutrino-0p6b_v4_base.bin"
|
| 75 |
+
}
|
mlx/receipts/refgate_0p6b.json
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"ref": "neutrino_ref.npz",
|
| 3 |
+
"meta": {
|
| 4 |
+
"tag": "neutrino",
|
| 5 |
+
"donor": "Qwen/Qwen3-0.6B",
|
| 6 |
+
"dtype": "float32",
|
| 7 |
+
"attn": "eager",
|
| 8 |
+
"prompt": "The key idea behind ternary neural networks is",
|
| 9 |
+
"made": "2026-07-25T05:45:54Z"
|
| 10 |
+
},
|
| 11 |
+
"tf": [
|
| 12 |
+
{
|
| 13 |
+
"pos": 0,
|
| 14 |
+
"rel": 1.420795504981901e-06,
|
| 15 |
+
"cos": 0.9999999999996864,
|
| 16 |
+
"argmax_match": true
|
| 17 |
+
},
|
| 18 |
+
{
|
| 19 |
+
"pos": 1,
|
| 20 |
+
"rel": 1.4544835094150464e-06,
|
| 21 |
+
"cos": 0.9999999999997129,
|
| 22 |
+
"argmax_match": true
|
| 23 |
+
},
|
| 24 |
+
{
|
| 25 |
+
"pos": 2,
|
| 26 |
+
"rel": 1.245608417973328e-06,
|
| 27 |
+
"cos": 0.9999999999997389,
|
| 28 |
+
"argmax_match": true
|
| 29 |
+
},
|
| 30 |
+
{
|
| 31 |
+
"pos": 3,
|
| 32 |
+
"rel": 1.2583815360193984e-06,
|
| 33 |
+
"cos": 0.999999999999599,
|
| 34 |
+
"argmax_match": true
|
| 35 |
+
},
|
| 36 |
+
{
|
| 37 |
+
"pos": 4,
|
| 38 |
+
"rel": 8.871204307383681e-07,
|
| 39 |
+
"cos": 0.9999999999997197,
|
| 40 |
+
"argmax_match": true
|
| 41 |
+
},
|
| 42 |
+
{
|
| 43 |
+
"pos": 5,
|
| 44 |
+
"rel": 1.742981119610197e-06,
|
| 45 |
+
"cos": 0.999999999999722,
|
| 46 |
+
"argmax_match": true
|
| 47 |
+
},
|
| 48 |
+
{
|
| 49 |
+
"pos": 6,
|
| 50 |
+
"rel": 1.5824395401174085e-06,
|
| 51 |
+
"cos": 0.999999999999796,
|
| 52 |
+
"argmax_match": true
|
| 53 |
+
},
|
| 54 |
+
{
|
| 55 |
+
"pos": 7,
|
| 56 |
+
"rel": 1.167621618981324e-06,
|
| 57 |
+
"cos": 0.9999999999997883,
|
| 58 |
+
"argmax_match": true
|
| 59 |
+
},
|
| 60 |
+
{
|
| 61 |
+
"pos": 8,
|
| 62 |
+
"rel": 1.386377853532605e-06,
|
| 63 |
+
"cos": 0.99999999999965,
|
| 64 |
+
"argmax_match": true
|
| 65 |
+
}
|
| 66 |
+
],
|
| 67 |
+
"greedy_flips": [],
|
| 68 |
+
"tie_tol": 0.04,
|
| 69 |
+
"max_flips": 8,
|
| 70 |
+
"tf_summary": {
|
| 71 |
+
"rel_max": 1.742981119610197e-06,
|
| 72 |
+
"rel_mean": 1.3495343923743974e-06,
|
| 73 |
+
"argmax": "9/9",
|
| 74 |
+
"near_tie": 0
|
| 75 |
+
},
|
| 76 |
+
"flips": 0,
|
| 77 |
+
"worst_gap": 0.0,
|
| 78 |
+
"ok": true,
|
| 79 |
+
"mlx_greedy": [
|
| 80 |
+
429,
|
| 81 |
+
807,
|
| 82 |
+
646,
|
| 83 |
+
387,
|
| 84 |
+
1483,
|
| 85 |
+
311,
|
| 86 |
+
1614,
|
| 87 |
+
6351,
|
| 88 |
+
5942,
|
| 89 |
+
11,
|
| 90 |
+
1741,
|
| 91 |
+
438,
|
| 92 |
+
279,
|
| 93 |
+
3738,
|
| 94 |
+
8109,
|
| 95 |
+
11,
|
| 96 |
+
323,
|
| 97 |
+
807,
|
| 98 |
+
646,
|
| 99 |
+
387,
|
| 100 |
+
1483,
|
| 101 |
+
311,
|
| 102 |
+
11625,
|
| 103 |
+
5322
|
| 104 |
+
],
|
| 105 |
+
"ref_greedy": [
|
| 106 |
+
429,
|
| 107 |
+
807,
|
| 108 |
+
646,
|
| 109 |
+
387,
|
| 110 |
+
1483,
|
| 111 |
+
311,
|
| 112 |
+
1614,
|
| 113 |
+
6351,
|
| 114 |
+
5942,
|
| 115 |
+
11,
|
| 116 |
+
1741,
|
| 117 |
+
438,
|
| 118 |
+
279,
|
| 119 |
+
3738,
|
| 120 |
+
8109,
|
| 121 |
+
11,
|
| 122 |
+
323,
|
| 123 |
+
807,
|
| 124 |
+
646,
|
| 125 |
+
387,
|
| 126 |
+
1483,
|
| 127 |
+
311,
|
| 128 |
+
11625,
|
| 129 |
+
5322
|
| 130 |
+
],
|
| 131 |
+
"mode": "refgate",
|
| 132 |
+
"model": "neutrino-0p6b_v4_base.bin"
|
| 133 |
+
}
|
mlx/requirements.txt
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# fermion_mlx runtime (Apple silicon, macOS 14+)
|
| 2 |
+
mlx>=0.31.2
|
| 3 |
+
numpy>=1.26
|
| 4 |
+
# tokenizer + chat template (generation CLI modes only; gates/bench can run
|
| 5 |
+
# with --no-tokenizer)
|
| 6 |
+
transformers>=4.53
|
mlx/tokenizer.json
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:aeb13307a71acd8fe81861d94ad54ab689df773318809eed3cbe794b4492dae4
|
| 3 |
+
size 11422654
|
mlx/tokenizer_config.json
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"add_bos_token": false,
|
| 3 |
+
"add_prefix_space": false,
|
| 4 |
+
"added_tokens_decoder": {
|
| 5 |
+
"151643": {
|
| 6 |
+
"content": "<|endoftext|>",
|
| 7 |
+
"lstrip": false,
|
| 8 |
+
"normalized": false,
|
| 9 |
+
"rstrip": false,
|
| 10 |
+
"single_word": false,
|
| 11 |
+
"special": true
|
| 12 |
+
},
|
| 13 |
+
"151644": {
|
| 14 |
+
"content": "<|im_start|>",
|
| 15 |
+
"lstrip": false,
|
| 16 |
+
"normalized": false,
|
| 17 |
+
"rstrip": false,
|
| 18 |
+
"single_word": false,
|
| 19 |
+
"special": true
|
| 20 |
+
},
|
| 21 |
+
"151645": {
|
| 22 |
+
"content": "<|im_end|>",
|
| 23 |
+
"lstrip": false,
|
| 24 |
+
"normalized": false,
|
| 25 |
+
"rstrip": false,
|
| 26 |
+
"single_word": false,
|
| 27 |
+
"special": true
|
| 28 |
+
},
|
| 29 |
+
"151646": {
|
| 30 |
+
"content": "<|object_ref_start|>",
|
| 31 |
+
"lstrip": false,
|
| 32 |
+
"normalized": false,
|
| 33 |
+
"rstrip": false,
|
| 34 |
+
"single_word": false,
|
| 35 |
+
"special": true
|
| 36 |
+
},
|
| 37 |
+
"151647": {
|
| 38 |
+
"content": "<|object_ref_end|>",
|
| 39 |
+
"lstrip": false,
|
| 40 |
+
"normalized": false,
|
| 41 |
+
"rstrip": false,
|
| 42 |
+
"single_word": false,
|
| 43 |
+
"special": true
|
| 44 |
+
},
|
| 45 |
+
"151648": {
|
| 46 |
+
"content": "<|box_start|>",
|
| 47 |
+
"lstrip": false,
|
| 48 |
+
"normalized": false,
|
| 49 |
+
"rstrip": false,
|
| 50 |
+
"single_word": false,
|
| 51 |
+
"special": true
|
| 52 |
+
},
|
| 53 |
+
"151649": {
|
| 54 |
+
"content": "<|box_end|>",
|
| 55 |
+
"lstrip": false,
|
| 56 |
+
"normalized": false,
|
| 57 |
+
"rstrip": false,
|
| 58 |
+
"single_word": false,
|
| 59 |
+
"special": true
|
| 60 |
+
},
|
| 61 |
+
"151650": {
|
| 62 |
+
"content": "<|quad_start|>",
|
| 63 |
+
"lstrip": false,
|
| 64 |
+
"normalized": false,
|
| 65 |
+
"rstrip": false,
|
| 66 |
+
"single_word": false,
|
| 67 |
+
"special": true
|
| 68 |
+
},
|
| 69 |
+
"151651": {
|
| 70 |
+
"content": "<|quad_end|>",
|
| 71 |
+
"lstrip": false,
|
| 72 |
+
"normalized": false,
|
| 73 |
+
"rstrip": false,
|
| 74 |
+
"single_word": false,
|
| 75 |
+
"special": true
|
| 76 |
+
},
|
| 77 |
+
"151652": {
|
| 78 |
+
"content": "<|vision_start|>",
|
| 79 |
+
"lstrip": false,
|
| 80 |
+
"normalized": false,
|
| 81 |
+
"rstrip": false,
|
| 82 |
+
"single_word": false,
|
| 83 |
+
"special": true
|
| 84 |
+
},
|
| 85 |
+
"151653": {
|
| 86 |
+
"content": "<|vision_end|>",
|
| 87 |
+
"lstrip": false,
|
| 88 |
+
"normalized": false,
|
| 89 |
+
"rstrip": false,
|
| 90 |
+
"single_word": false,
|
| 91 |
+
"special": true
|
| 92 |
+
},
|
| 93 |
+
"151654": {
|
| 94 |
+
"content": "<|vision_pad|>",
|
| 95 |
+
"lstrip": false,
|
| 96 |
+
"normalized": false,
|
| 97 |
+
"rstrip": false,
|
| 98 |
+
"single_word": false,
|
| 99 |
+
"special": true
|
| 100 |
+
},
|
| 101 |
+
"151655": {
|
| 102 |
+
"content": "<|image_pad|>",
|
| 103 |
+
"lstrip": false,
|
| 104 |
+
"normalized": false,
|
| 105 |
+
"rstrip": false,
|
| 106 |
+
"single_word": false,
|
| 107 |
+
"special": true
|
| 108 |
+
},
|
| 109 |
+
"151656": {
|
| 110 |
+
"content": "<|video_pad|>",
|
| 111 |
+
"lstrip": false,
|
| 112 |
+
"normalized": false,
|
| 113 |
+
"rstrip": false,
|
| 114 |
+
"single_word": false,
|
| 115 |
+
"special": true
|
| 116 |
+
},
|
| 117 |
+
"151657": {
|
| 118 |
+
"content": "<tool_call>",
|
| 119 |
+
"lstrip": false,
|
| 120 |
+
"normalized": false,
|
| 121 |
+
"rstrip": false,
|
| 122 |
+
"single_word": false,
|
| 123 |
+
"special": false
|
| 124 |
+
},
|
| 125 |
+
"151658": {
|
| 126 |
+
"content": "</tool_call>",
|
| 127 |
+
"lstrip": false,
|
| 128 |
+
"normalized": false,
|
| 129 |
+
"rstrip": false,
|
| 130 |
+
"single_word": false,
|
| 131 |
+
"special": false
|
| 132 |
+
},
|
| 133 |
+
"151659": {
|
| 134 |
+
"content": "<|fim_prefix|>",
|
| 135 |
+
"lstrip": false,
|
| 136 |
+
"normalized": false,
|
| 137 |
+
"rstrip": false,
|
| 138 |
+
"single_word": false,
|
| 139 |
+
"special": false
|
| 140 |
+
},
|
| 141 |
+
"151660": {
|
| 142 |
+
"content": "<|fim_middle|>",
|
| 143 |
+
"lstrip": false,
|
| 144 |
+
"normalized": false,
|
| 145 |
+
"rstrip": false,
|
| 146 |
+
"single_word": false,
|
| 147 |
+
"special": false
|
| 148 |
+
},
|
| 149 |
+
"151661": {
|
| 150 |
+
"content": "<|fim_suffix|>",
|
| 151 |
+
"lstrip": false,
|
| 152 |
+
"normalized": false,
|
| 153 |
+
"rstrip": false,
|
| 154 |
+
"single_word": false,
|
| 155 |
+
"special": false
|
| 156 |
+
},
|
| 157 |
+
"151662": {
|
| 158 |
+
"content": "<|fim_pad|>",
|
| 159 |
+
"lstrip": false,
|
| 160 |
+
"normalized": false,
|
| 161 |
+
"rstrip": false,
|
| 162 |
+
"single_word": false,
|
| 163 |
+
"special": false
|
| 164 |
+
},
|
| 165 |
+
"151663": {
|
| 166 |
+
"content": "<|repo_name|>",
|
| 167 |
+
"lstrip": false,
|
| 168 |
+
"normalized": false,
|
| 169 |
+
"rstrip": false,
|
| 170 |
+
"single_word": false,
|
| 171 |
+
"special": false
|
| 172 |
+
},
|
| 173 |
+
"151664": {
|
| 174 |
+
"content": "<|file_sep|>",
|
| 175 |
+
"lstrip": false,
|
| 176 |
+
"normalized": false,
|
| 177 |
+
"rstrip": false,
|
| 178 |
+
"single_word": false,
|
| 179 |
+
"special": false
|
| 180 |
+
},
|
| 181 |
+
"151665": {
|
| 182 |
+
"content": "<tool_response>",
|
| 183 |
+
"lstrip": false,
|
| 184 |
+
"normalized": false,
|
| 185 |
+
"rstrip": false,
|
| 186 |
+
"single_word": false,
|
| 187 |
+
"special": false
|
| 188 |
+
},
|
| 189 |
+
"151666": {
|
| 190 |
+
"content": "</tool_response>",
|
| 191 |
+
"lstrip": false,
|
| 192 |
+
"normalized": false,
|
| 193 |
+
"rstrip": false,
|
| 194 |
+
"single_word": false,
|
| 195 |
+
"special": false
|
| 196 |
+
},
|
| 197 |
+
"151667": {
|
| 198 |
+
"content": "<think>",
|
| 199 |
+
"lstrip": false,
|
| 200 |
+
"normalized": false,
|
| 201 |
+
"rstrip": false,
|
| 202 |
+
"single_word": false,
|
| 203 |
+
"special": false
|
| 204 |
+
},
|
| 205 |
+
"151668": {
|
| 206 |
+
"content": "</think>",
|
| 207 |
+
"lstrip": false,
|
| 208 |
+
"normalized": false,
|
| 209 |
+
"rstrip": false,
|
| 210 |
+
"single_word": false,
|
| 211 |
+
"special": false
|
| 212 |
+
}
|
| 213 |
+
},
|
| 214 |
+
"additional_special_tokens": [
|
| 215 |
+
"<|im_start|>",
|
| 216 |
+
"<|im_end|>",
|
| 217 |
+
"<|object_ref_start|>",
|
| 218 |
+
"<|object_ref_end|>",
|
| 219 |
+
"<|box_start|>",
|
| 220 |
+
"<|box_end|>",
|
| 221 |
+
"<|quad_start|>",
|
| 222 |
+
"<|quad_end|>",
|
| 223 |
+
"<|vision_start|>",
|
| 224 |
+
"<|vision_end|>",
|
| 225 |
+
"<|vision_pad|>",
|
| 226 |
+
"<|image_pad|>",
|
| 227 |
+
"<|video_pad|>"
|
| 228 |
+
],
|
| 229 |
+
"bos_token": null,
|
| 230 |
+
"chat_template": "{%- if tools %}\n {{- '<|im_start|>system\\n' }}\n {%- if messages[0].role == 'system' %}\n {{- messages[0].content + '\\n\\n' }}\n {%- endif %}\n {{- \"# Tools\\n\\nYou may call one or more functions to assist with the user query.\\n\\nYou are provided with function signatures within <tools></tools> XML tags:\\n<tools>\" }}\n {%- for tool in tools %}\n {{- \"\\n\" }}\n {{- tool | tojson }}\n {%- endfor %}\n {{- \"\\n</tools>\\n\\nFor each function call, return a json object with function name and arguments within <tool_call></tool_call> XML tags:\\n<tool_call>\\n{\\\"name\\\": <function-name>, \\\"arguments\\\": <args-json-object>}\\n</tool_call><|im_end|>\\n\" }}\n{%- else %}\n {%- if messages[0].role == 'system' %}\n {{- '<|im_start|>system\\n' + messages[0].content + '<|im_end|>\\n' }}\n {%- endif %}\n{%- endif %}\n{%- set ns = namespace(multi_step_tool=true, last_query_index=messages|length - 1) %}\n{%- for message in messages[::-1] %}\n {%- set index = (messages|length - 1) - loop.index0 %}\n {%- if ns.multi_step_tool and message.role == \"user\" and message.content is string and not(message.content.startswith('<tool_response>') and message.content.endswith('</tool_response>')) %}\n {%- set ns.multi_step_tool = false %}\n {%- set ns.last_query_index = index %}\n {%- endif %}\n{%- endfor %}\n{%- for message in messages %}\n {%- if message.content is string %}\n {%- set content = message.content %}\n {%- else %}\n {%- set content = '' %}\n {%- endif %}\n {%- if (message.role == \"user\") or (message.role == \"system\" and not loop.first) %}\n {{- '<|im_start|>' + message.role + '\\n' + content + '<|im_end|>' + '\\n' }}\n {%- elif message.role == \"assistant\" %}\n {%- set reasoning_content = '' %}\n {%- if message.reasoning_content is string %}\n {%- set reasoning_content = message.reasoning_content %}\n {%- else %}\n {%- if '</think>' in content %}\n {%- set reasoning_content = content.split('</think>')[0].rstrip('\\n').split('<think>')[-1].lstrip('\\n') %}\n {%- set content = content.split('</think>')[-1].lstrip('\\n') %}\n {%- endif %}\n {%- endif %}\n {%- if loop.index0 > ns.last_query_index %}\n {%- if loop.last or (not loop.last and reasoning_content) %}\n {{- '<|im_start|>' + message.role + '\\n<think>\\n' + reasoning_content.strip('\\n') + '\\n</think>\\n\\n' + content.lstrip('\\n') }}\n {%- else %}\n {{- '<|im_start|>' + message.role + '\\n' + content }}\n {%- endif %}\n {%- else %}\n {{- '<|im_start|>' + message.role + '\\n' + content }}\n {%- endif %}\n {%- if message.tool_calls %}\n {%- for tool_call in message.tool_calls %}\n {%- if (loop.first and content) or (not loop.first) %}\n {{- '\\n' }}\n {%- endif %}\n {%- if tool_call.function %}\n {%- set tool_call = tool_call.function %}\n {%- endif %}\n {{- '<tool_call>\\n{\"name\": \"' }}\n {{- tool_call.name }}\n {{- '\", \"arguments\": ' }}\n {%- if tool_call.arguments is string %}\n {{- tool_call.arguments }}\n {%- else %}\n {{- tool_call.arguments | tojson }}\n {%- endif %}\n {{- '}\\n</tool_call>' }}\n {%- endfor %}\n {%- endif %}\n {{- '<|im_end|>\\n' }}\n {%- elif message.role == \"tool\" %}\n {%- if loop.first or (messages[loop.index0 - 1].role != \"tool\") %}\n {{- '<|im_start|>user' }}\n {%- endif %}\n {{- '\\n<tool_response>\\n' }}\n {{- content }}\n {{- '\\n</tool_response>' }}\n {%- if loop.last or (messages[loop.index0 + 1].role != \"tool\") %}\n {{- '<|im_end|>\\n' }}\n {%- endif %}\n {%- endif %}\n{%- endfor %}\n{%- if add_generation_prompt %}\n {{- '<|im_start|>assistant\\n' }}\n {%- if enable_thinking is defined and enable_thinking is false %}\n {{- '<think>\\n\\n</think>\\n\\n' }}\n {%- endif %}\n{%- endif %}",
|
| 231 |
+
"clean_up_tokenization_spaces": false,
|
| 232 |
+
"eos_token": "<|im_end|>",
|
| 233 |
+
"errors": "replace",
|
| 234 |
+
"model_max_length": 131072,
|
| 235 |
+
"pad_token": "<|endoftext|>",
|
| 236 |
+
"split_special_tokens": false,
|
| 237 |
+
"tokenizer_class": "Qwen2Tokenizer",
|
| 238 |
+
"unk_token": null
|
| 239 |
+
}
|
mlx/vocab.json
ADDED
|
The diff for this file is too large to render.
See raw diff
|
|
|
neutrino-0.6b_v4.bin
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:91c0b26d105710e755ef1ec1b5607c00da5d5cbd048fd5584135fb6fc1e19399
|
| 3 |
+
size 327719836
|
neutrino-0.6b_v4.tv4z
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:132d9ab758b0b1a5b9b6638e55647462dbdb4ec8345720cc98c3d4cf669e780c
|
| 3 |
+
size 237703560
|
receipts/hf_greedy_cache_86565bf6.json
ADDED
|
@@ -0,0 +1,433 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"container_md5": "86565bf6183e8031943aac9fd9771385",
|
| 3 |
+
"donor": "Qwen/Qwen3-0.6B@c1899de289a04d12100db370d81485cdf75e47ca",
|
| 4 |
+
"dtype": "float16",
|
| 5 |
+
"prompts": [
|
| 6 |
+
[
|
| 7 |
+
1,
|
| 8 |
+
791,
|
| 9 |
+
1283,
|
| 10 |
+
315,
|
| 11 |
+
220,
|
| 12 |
+
17,
|
| 13 |
+
42,
|
| 14 |
+
99
|
| 15 |
+
],
|
| 16 |
+
[
|
| 17 |
+
1,
|
| 18 |
+
9707,
|
| 19 |
+
374,
|
| 20 |
+
264,
|
| 21 |
+
1296,
|
| 22 |
+
13,
|
| 23 |
+
28,
|
| 24 |
+
7
|
| 25 |
+
],
|
| 26 |
+
[
|
| 27 |
+
1,
|
| 28 |
+
2610,
|
| 29 |
+
525,
|
| 30 |
+
498,
|
| 31 |
+
1091,
|
| 32 |
+
11,
|
| 33 |
+
16,
|
| 34 |
+
3
|
| 35 |
+
]
|
| 36 |
+
],
|
| 37 |
+
"steps": 128,
|
| 38 |
+
"tokens": [
|
| 39 |
+
[
|
| 40 |
+
1,
|
| 41 |
+
374,
|
| 42 |
+
264,
|
| 43 |
+
4185,
|
| 44 |
+
17133,
|
| 45 |
+
1483,
|
| 46 |
+
304,
|
| 47 |
+
279,
|
| 48 |
+
2266,
|
| 49 |
+
315,
|
| 50 |
+
279,
|
| 51 |
+
1809,
|
| 52 |
+
315,
|
| 53 |
+
32719,
|
| 54 |
+
13,
|
| 55 |
+
1084,
|
| 56 |
+
19257,
|
| 57 |
+
311,
|
| 58 |
+
279,
|
| 59 |
+
2309,
|
| 60 |
+
315,
|
| 61 |
+
279,
|
| 62 |
+
2781,
|
| 63 |
+
879,
|
| 64 |
+
374,
|
| 65 |
+
304,
|
| 66 |
+
279,
|
| 67 |
+
1156,
|
| 68 |
+
2309,
|
| 69 |
+
11,
|
| 70 |
+
323,
|
| 71 |
+
279,
|
| 72 |
+
2309,
|
| 73 |
+
315,
|
| 74 |
+
279,
|
| 75 |
+
14704,
|
| 76 |
+
879,
|
| 77 |
+
374,
|
| 78 |
+
304,
|
| 79 |
+
279,
|
| 80 |
+
2086,
|
| 81 |
+
2309,
|
| 82 |
+
13,
|
| 83 |
+
576,
|
| 84 |
+
1809,
|
| 85 |
+
374,
|
| 86 |
+
6342,
|
| 87 |
+
553,
|
| 88 |
+
24047,
|
| 89 |
+
9666,
|
| 90 |
+
389,
|
| 91 |
+
264,
|
| 92 |
+
4479,
|
| 93 |
+
11,
|
| 94 |
+
323,
|
| 95 |
+
279,
|
| 96 |
+
2781,
|
| 97 |
+
879,
|
| 98 |
+
374,
|
| 99 |
+
304,
|
| 100 |
+
279,
|
| 101 |
+
1156,
|
| 102 |
+
2309,
|
| 103 |
+
374,
|
| 104 |
+
279,
|
| 105 |
+
13632,
|
| 106 |
+
13,
|
| 107 |
+
576,
|
| 108 |
+
1809,
|
| 109 |
+
374,
|
| 110 |
+
6342,
|
| 111 |
+
553,
|
| 112 |
+
24047,
|
| 113 |
+
9666,
|
| 114 |
+
389,
|
| 115 |
+
264,
|
| 116 |
+
4479,
|
| 117 |
+
11,
|
| 118 |
+
323,
|
| 119 |
+
279,
|
| 120 |
+
2781,
|
| 121 |
+
879,
|
| 122 |
+
374,
|
| 123 |
+
304,
|
| 124 |
+
279,
|
| 125 |
+
2086,
|
| 126 |
+
2309,
|
| 127 |
+
374,
|
| 128 |
+
279,
|
| 129 |
+
58891,
|
| 130 |
+
13,
|
| 131 |
+
576,
|
| 132 |
+
1809,
|
| 133 |
+
374,
|
| 134 |
+
6342,
|
| 135 |
+
553,
|
| 136 |
+
24047,
|
| 137 |
+
9666,
|
| 138 |
+
389,
|
| 139 |
+
264,
|
| 140 |
+
4479,
|
| 141 |
+
11,
|
| 142 |
+
323,
|
| 143 |
+
279,
|
| 144 |
+
2781,
|
| 145 |
+
879,
|
| 146 |
+
374,
|
| 147 |
+
304,
|
| 148 |
+
279,
|
| 149 |
+
1156,
|
| 150 |
+
2309,
|
| 151 |
+
374,
|
| 152 |
+
279,
|
| 153 |
+
13632,
|
| 154 |
+
13,
|
| 155 |
+
576,
|
| 156 |
+
1809,
|
| 157 |
+
374,
|
| 158 |
+
6342,
|
| 159 |
+
553,
|
| 160 |
+
24047,
|
| 161 |
+
9666,
|
| 162 |
+
389,
|
| 163 |
+
264,
|
| 164 |
+
4479,
|
| 165 |
+
11,
|
| 166 |
+
323,
|
| 167 |
+
279
|
| 168 |
+
],
|
| 169 |
+
[
|
| 170 |
+
16,
|
| 171 |
+
11,
|
| 172 |
+
17,
|
| 173 |
+
11,
|
| 174 |
+
18,
|
| 175 |
+
11,
|
| 176 |
+
19,
|
| 177 |
+
11,
|
| 178 |
+
20,
|
| 179 |
+
11,
|
| 180 |
+
21,
|
| 181 |
+
11,
|
| 182 |
+
22,
|
| 183 |
+
11,
|
| 184 |
+
23,
|
| 185 |
+
11,
|
| 186 |
+
24,
|
| 187 |
+
11,
|
| 188 |
+
16,
|
| 189 |
+
15,
|
| 190 |
+
11,
|
| 191 |
+
16,
|
| 192 |
+
16,
|
| 193 |
+
11,
|
| 194 |
+
16,
|
| 195 |
+
17,
|
| 196 |
+
11,
|
| 197 |
+
16,
|
| 198 |
+
18,
|
| 199 |
+
11,
|
| 200 |
+
16,
|
| 201 |
+
19,
|
| 202 |
+
11,
|
| 203 |
+
16,
|
| 204 |
+
20,
|
| 205 |
+
11,
|
| 206 |
+
16,
|
| 207 |
+
21,
|
| 208 |
+
11,
|
| 209 |
+
16,
|
| 210 |
+
22,
|
| 211 |
+
11,
|
| 212 |
+
16,
|
| 213 |
+
23,
|
| 214 |
+
11,
|
| 215 |
+
16,
|
| 216 |
+
24,
|
| 217 |
+
11,
|
| 218 |
+
17,
|
| 219 |
+
15,
|
| 220 |
+
11,
|
| 221 |
+
17,
|
| 222 |
+
16,
|
| 223 |
+
11,
|
| 224 |
+
17,
|
| 225 |
+
17,
|
| 226 |
+
11,
|
| 227 |
+
17,
|
| 228 |
+
18,
|
| 229 |
+
11,
|
| 230 |
+
17,
|
| 231 |
+
19,
|
| 232 |
+
11,
|
| 233 |
+
17,
|
| 234 |
+
20,
|
| 235 |
+
11,
|
| 236 |
+
17,
|
| 237 |
+
21,
|
| 238 |
+
11,
|
| 239 |
+
17,
|
| 240 |
+
22,
|
| 241 |
+
11,
|
| 242 |
+
17,
|
| 243 |
+
23,
|
| 244 |
+
11,
|
| 245 |
+
17,
|
| 246 |
+
24,
|
| 247 |
+
11,
|
| 248 |
+
18,
|
| 249 |
+
15,
|
| 250 |
+
11,
|
| 251 |
+
18,
|
| 252 |
+
16,
|
| 253 |
+
11,
|
| 254 |
+
18,
|
| 255 |
+
17,
|
| 256 |
+
11,
|
| 257 |
+
18,
|
| 258 |
+
18,
|
| 259 |
+
11,
|
| 260 |
+
18,
|
| 261 |
+
19,
|
| 262 |
+
11,
|
| 263 |
+
18,
|
| 264 |
+
20,
|
| 265 |
+
11,
|
| 266 |
+
18,
|
| 267 |
+
21,
|
| 268 |
+
11,
|
| 269 |
+
18,
|
| 270 |
+
22,
|
| 271 |
+
11,
|
| 272 |
+
18,
|
| 273 |
+
23,
|
| 274 |
+
11,
|
| 275 |
+
18,
|
| 276 |
+
24,
|
| 277 |
+
11,
|
| 278 |
+
19,
|
| 279 |
+
15,
|
| 280 |
+
11,
|
| 281 |
+
19,
|
| 282 |
+
16,
|
| 283 |
+
11,
|
| 284 |
+
19,
|
| 285 |
+
17,
|
| 286 |
+
11,
|
| 287 |
+
19,
|
| 288 |
+
18,
|
| 289 |
+
11,
|
| 290 |
+
19,
|
| 291 |
+
19,
|
| 292 |
+
11,
|
| 293 |
+
19,
|
| 294 |
+
20,
|
| 295 |
+
11,
|
| 296 |
+
19,
|
| 297 |
+
21
|
| 298 |
+
],
|
| 299 |
+
[
|
| 300 |
+
323,
|
| 301 |
+
279,
|
| 302 |
+
1008,
|
| 303 |
+
374,
|
| 304 |
+
279,
|
| 305 |
+
1852,
|
| 306 |
+
438,
|
| 307 |
+
279,
|
| 308 |
+
1008,
|
| 309 |
+
11,
|
| 310 |
+
17,
|
| 311 |
+
3,
|
| 312 |
+
323,
|
| 313 |
+
279,
|
| 314 |
+
1008,
|
| 315 |
+
374,
|
| 316 |
+
279,
|
| 317 |
+
1852,
|
| 318 |
+
438,
|
| 319 |
+
279,
|
| 320 |
+
1008,
|
| 321 |
+
11,
|
| 322 |
+
18,
|
| 323 |
+
3,
|
| 324 |
+
323,
|
| 325 |
+
279,
|
| 326 |
+
1008,
|
| 327 |
+
374,
|
| 328 |
+
279,
|
| 329 |
+
1852,
|
| 330 |
+
438,
|
| 331 |
+
279,
|
| 332 |
+
1008,
|
| 333 |
+
11,
|
| 334 |
+
19,
|
| 335 |
+
3,
|
| 336 |
+
323,
|
| 337 |
+
279,
|
| 338 |
+
1008,
|
| 339 |
+
374,
|
| 340 |
+
279,
|
| 341 |
+
1852,
|
| 342 |
+
438,
|
| 343 |
+
279,
|
| 344 |
+
1008,
|
| 345 |
+
11,
|
| 346 |
+
20,
|
| 347 |
+
3,
|
| 348 |
+
323,
|
| 349 |
+
279,
|
| 350 |
+
1008,
|
| 351 |
+
374,
|
| 352 |
+
279,
|
| 353 |
+
1852,
|
| 354 |
+
438,
|
| 355 |
+
279,
|
| 356 |
+
1008,
|
| 357 |
+
11,
|
| 358 |
+
21,
|
| 359 |
+
3,
|
| 360 |
+
323,
|
| 361 |
+
279,
|
| 362 |
+
1008,
|
| 363 |
+
374,
|
| 364 |
+
279,
|
| 365 |
+
1852,
|
| 366 |
+
438,
|
| 367 |
+
279,
|
| 368 |
+
1008,
|
| 369 |
+
11,
|
| 370 |
+
22,
|
| 371 |
+
3,
|
| 372 |
+
323,
|
| 373 |
+
279,
|
| 374 |
+
1008,
|
| 375 |
+
374,
|
| 376 |
+
279,
|
| 377 |
+
1852,
|
| 378 |
+
438,
|
| 379 |
+
279,
|
| 380 |
+
1008,
|
| 381 |
+
11,
|
| 382 |
+
23,
|
| 383 |
+
3,
|
| 384 |
+
323,
|
| 385 |
+
279,
|
| 386 |
+
1008,
|
| 387 |
+
374,
|
| 388 |
+
279,
|
| 389 |
+
1852,
|
| 390 |
+
438,
|
| 391 |
+
279,
|
| 392 |
+
1008,
|
| 393 |
+
11,
|
| 394 |
+
24,
|
| 395 |
+
3,
|
| 396 |
+
323,
|
| 397 |
+
279,
|
| 398 |
+
1008,
|
| 399 |
+
374,
|
| 400 |
+
279,
|
| 401 |
+
1852,
|
| 402 |
+
438,
|
| 403 |
+
279,
|
| 404 |
+
1008,
|
| 405 |
+
11,
|
| 406 |
+
16,
|
| 407 |
+
15,
|
| 408 |
+
3,
|
| 409 |
+
323,
|
| 410 |
+
279,
|
| 411 |
+
1008,
|
| 412 |
+
374,
|
| 413 |
+
279,
|
| 414 |
+
1852,
|
| 415 |
+
438,
|
| 416 |
+
279,
|
| 417 |
+
1008,
|
| 418 |
+
11,
|
| 419 |
+
16,
|
| 420 |
+
16,
|
| 421 |
+
3,
|
| 422 |
+
323,
|
| 423 |
+
279,
|
| 424 |
+
1008,
|
| 425 |
+
374,
|
| 426 |
+
279,
|
| 427 |
+
1852
|
| 428 |
+
]
|
| 429 |
+
],
|
| 430 |
+
"trajectory_sha256": "a5c3a24c7918558239d0ef958adb335d7a67df630b084b492735e6bc00c6c74b",
|
| 431 |
+
"generation_seconds": 22.1,
|
| 432 |
+
"venue": "modal cpu=8 (fp16 weights, greedy argmax)"
|
| 433 |
+
}
|
receipts/m5_bitgate_analysis.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
{
|
| 2 |
+
"receipt": "M5 bit-gate divergence analysis (banked with the FAIL-as-measured re-receipt)",
|
| 3 |
+
"utc": "2026-07-25T06:38:06Z",
|
| 4 |
+
"container": {
|
| 5 |
+
"name": "neutrino-0p6b_v4_base.bin",
|
| 6 |
+
"sha256": "91c0b26d105710e755ef1ec1b5607c00da5d5cbd048fd5584135fb6fc1e19399",
|
| 7 |
+
"md5": "86565bf6183e8031943aac9fd9771385"
|
| 8 |
+
},
|
| 9 |
+
"raw_bit_gate": "221/384 token mismatches vs fresh HF-fp16 greedy cache (per m5_demo_rereceipt_p3.json)",
|
| 10 |
+
"structure": "ONLY 2 first-divergence argmax flips (prompt1@idx13, prompt3@idx1); all other mismatches are the greedy cascade after those flips; prompt2 exact 128/128",
|
| 11 |
+
"evidence": {
|
| 12 |
+
"hf_reference": "three variants agree (expand fp16/load fp16, expand fp16/load fp32, expand fp32/load fp32): prefers 32719 by +0.197..+0.203 (prompt1) and 279 by +0.020..+0.023 (prompt3)",
|
| 13 |
+
"kernel_exact": "teacher-forced exact-int8 logit dump: argmax 5994 by -0.456 (prompt1) and 400 by -0.197 (prompt3); decode picks == exact-kernel argmax, so NOT a nomination artifact",
|
| 14 |
+
"determinism": "mismatch pattern identical across runs and across threads 1/4/9",
|
| 15 |
+
"integrity": "double-export byte-determinism PASS, tv4z byte round-trip PASS, HF expand --self-check PASS (receipt_base.json)"
|
| 16 |
+
},
|
| 17 |
+
"conclusion": "trit_run is an int8-activation engine; the HF fp expansion is a different numeric implementation of the same weights. They disagree on ranking only where |top-2 margin| <~ 0.5 logits. The shipped state has a flat (high-entropy) logit landscape, so 2 such positions occur in 384 greedy steps; other banked states gated under the same protocol presented none (their gates passed 0/384). Kernel output is deterministic and self-consistent; container integrity is fully receipted. The bit gate FAIL is banked as measured with this analysis."
|
| 18 |
+
}
|