license: other
license_name: lfm-open-license-v1.0
license_link: LICENSE
base_model: AtomicChat/LFM3-1.7B-Uncensored-GGUF
library_name: mlx
pipeline_tag: text-generation
language:
- en
- zh
- de
tags: - mlx
- apple-silicon
- liquid
- lfm3
- conversational
- tool-calling
- custom_code
- uncensored
- abliterated
- dequantized
- float32
- not-for-all-audiences
LFM3-1.7B Uncensored · MLX · dequantized float32
An MLX port of AtomicChat/LFM3-1.7B-Uncensored-GGUF
for Apple silicon. AtomicChat publishes this model only as quantized GGUF files, so the weights here are itsAD-Q6_K file dequantized to float32. They are the exact float32 values of the quantized weights, not the weights
from before quantization. Dequantizing adds no precision and cannot undo quantization error.
The port is 4.4 times the size of the GGUF, and it decodes 4 to 5 times slower than the GGUF does under llama.cpp
(speed). Use this port if you need the model in MLX. Otherwise, the GGUF is smaller and faster.
AtomicChat removed the model's safety refusals. It applies no safety filtering of its own.
Is it identical to the source?
Greedy output matched in every test, and the weights match bit for bit. The logits are close but not identical.
| What | Result |
|---|---|
| Weights | 286/286 tensors bit-identical to ggml's own dequantization of the GGUF |
| Tokenizer | The same ids on all 755 test strings, with and without special-token parsing |
| Chat template | The same bytes. 22/22 conversations render the same at matched settings, and 20/22 at llama-server defaults, which add two features of its own |
| Greedy output | Identical for all 15 short prompts and all 7 lengths up to the full 262,144-token context |
| Logits vs llama.cpp's float baseline | Not identical. Max |Δ| 2.0e-3 on short prompts and 1.5e-2 at full context, with no top-1 flips |
| Logits vs llama.cpp's defaults | Not identical. Max |Δ| 0.62 on short prompts and 0.22 at full context. 2 top-1 flips in 4,063 short-prompt rows, the same count llama.cpp's defaults show against its own float baseline |
| Long-context mistakes | The same as the source. At full context, source and port both give the wrong code for one of three planted facts |
llama.cpp's defaults do not give one fixed set of logits to match. Its batched Metal path computes in half precision,
so its logits change with batch size. The float baseline uses batches small enough to avoid that path. The port meets
the limits against that baseline, and two checks against the batched path, written before this was known, stay reported
as misses (evaluation).
These results cover the tested inputs on one machine. They are not proof of identical behavior on every input.
Model Details
| Source | AtomicChat/LFM3-1.7B-Uncensored-GGUF, LFM3-1.7B-Uncensored-AD-Q6_K.gguf, revision 3ad712542eb9de349dbd28f2739b69c925cdec7d |
| Source sha256 | 4a7d91c094b9a0f8ed094093f3a0eb3cfeeb46b9bd491e33115689fc5d658b22 |
| Base model | LiquidAI/LFM3-1.7B, by Liquid AI |
| Modified by | AtomicChat: a projected rank-1 LoRA baked in at W*=2.0, then AD-quantized |
| Ported by | mchen04 |
| Tokenizer files | LiquidAI/LFM3-1.7B, revision ac2586a34f1a1c03329dbdcdefabd8b6478ddaa7, with two flags changed (what changed) |
| Weights | 1,728,801,536 elements, float32, 6.92 GB. The GGUF is 1.59 GB |
| Architecture | Lfm3ForCausalLM, 24 layers: 13 gated short convolutions, 5 sliding-window attention, 6 full attention |
| Context | 262,144 tokens |
| Languages | English, Chinese and German, as listed on the source card |
| Runtime | mlx-lm with trust_remote_code=True. Tested with mlx 0.32.4 and mlx-lm 0.32.2 |
| License | LFM Open License v1.0 (LICENSE) |
AD-Q6_K is the highest-fidelity rung AtomicChat publishes. Of its 152 models, none is an unquantized copy of these
weights. The file holds 133 F32, 66 Q8_0 and 88 Q6_K tensors, and the F32 tensors are copied bit for bit.
How to Get Started
mlx-lm has no built-in LFM3 architecture. This repo ships one as lfm3.py, and mlx-lm runs that file only when you passtrust_remote_code. Read lfm3.py before you run it.
pip install "mlx==0.32.4" "mlx-lm==0.32.2"
mlx_lm.generate --model mchen04/LFM3-1.7B-Uncensored-MLX --trust-remote-code \
--temp 0.1 --top-k 50 --prompt "Who are you?"
from mlx_lm import load, generate
model, tokenizer = load("mchen04/LFM3-1.7B-Uncensored-MLX", trust_remote_code=True)
messages = [{"role": "user", "content": "Who are you?"}]
prompt = tokenizer.apply_chat_template(messages, add_generation_prompt=True)
print(generate(model, tokenizer, prompt=prompt, max_tokens=512, verbose=True))
The chat template opens a <think> block by default, as it does in the source, so output starts with reasoning and then</think>. To turn that off, pass enable_thinking=False to apply_chat_template, or--chat-template-config '{"enable_thinking": false}' to mlx_lm.generate.
requirements.txt lists every package version from the test machine, on Python 3.12.13.
Recommended sampling, from Liquid AI's model card. generation_config.json holds the general settings.
| Mode | Settings |
|---|---|
| General (chat, tool use, agents, coding) | temperature=0.1, top_k=50, repetition_penalty=1.1 |
| Math and science | temperature=1.0, top_p=0.95, top_k=20, repetition_penalty=1.0 |
mlx-lm applies repetition_penalty to the last 20 tokens by default. Transformers applies it to the whole context.
Long prompts
Set two things for very long prompts. mlx-lm's defaults were not tried at the longest lengths, and they are estimated to need
far more memory.
import mlx.core as mx
from mlx_lm import load, generate
mx.set_cache_limit(2**30) # keep freed buffers from filling memory during decode
model, tokenizer = load("mchen04/LFM3-1.7B-Uncensored-MLX", trust_remote_code=True)
prompt = tokenizer.apply_chat_template([{"role": "user", "content": long_text}], add_generation_prompt=True)
# prefill_step_size depends on the prompt length: see the table below. 256 is for about 65,536 tokens
print(generate(model, tokenizer, prompt=prompt, max_tokens=512, prefill_step_size=256, verbose=True))
prefill_step_size. Each prefill chunk builds attention scores against the whole context. These tests kept an
estimate ofchunk × tokens × 64 bytesunder 1.5 GiB. mlx-lm's default of 2048 would estimate about 34 GB at full
context. That default was not tried at these lengths.Prompt tokens prefill_step_sizeusedup to 4,096 2048 16,384 1024 65,536 256 131,072 128 196,608 and 262,080 64 mx.set_cache_limit. Decode casts each full-attention layer's cache to float32, and MLX keeps the freed buffers. At
131,072 tokens, peak footprint fell from 18.4 GB to 10.9 GB with the limit, and lowest free memory rose from 20% to
50%. Logit dumps stayed byte-identical and decode was no slower.
The K/V cache is float16 by default, as in llama.cpp's defaults. For float32, which uses more memory at long context
but is closer to the float baseline, pass model_config={"kv_cache_dtype": "float32"} to load.
The port raises ValueError past 262,144 tokens. It does not silently continue.
Uses
Direct use: local chat, tool calling, structured output and long-context work on Apple silicon, for research and
for applications that provide their own moderation.
Downstream use: a base for MLX applications that need this exact model, or for comparing MLX with llama.cpp.
Out of scope: any use without your own safety layer where outputs reach other people, and any use that the
LFM Open License or applicable law forbids.
Bias, Risks, and Limitations
- No refusals. AtomicChat reports refusals on harmful prompts falling from 61.7% to 0.0%, and false refusals from
5.2% to 0.0%. The model can produce harmful, false or offensive content on request. - AtomicChat's scores are not re-measured here. That includes its MMLU result (41.0%, unchanged from the base), its
long-context needle result (3/3) and its KL shift (+0.019). This port tests whether it behaves like the source, not
whether the source is good. - Slow decode. About 14 tok/s on short prompts against 59 tok/s for the GGUF, and about 4.6 against 21 at 131,072 tokens. A likely reason is that each decode
reads 6.9 GB of weights instead of 1.6 GB. That was not isolated, and the numbers are from one machine. - Long-context recall can fail, and it fails the same way in the source. See
retrieval by length. - Only tested on one machine. An Apple M4 with 24 GB. Bit-level results may not carry to other chips or MLX versions.
- Not the same as llama.cpp's CPU backend. The source's own CPU and Metal results differ far more than the port
differs from Metal (mean KL 0.018, greedy 1/15 identical).ggml-cpu.cconverts activations to 8 bits for quantized
matmuls, which fits the gap. That was read in the code and not isolated by a run. - Custom code. Loading runs
lfm3.pyfrom this repo.
Recommendations: add your own moderation before outputs reach anyone. Validate structured output. Do not rely on
recall from very long prompts without checking the answer.
Speed and memory
Mac mini, Apple M4 (10 CPU cores), 24 GB, macOS 26.2 (25C56), mlx 0.32.4, llama.cpp 19cce691 on Metal with 4 threads.
Prompts are fixed token files, the same for both runtimes. Greedy decoding, 128 tokens emitted, so 127 timed decodes.
One warm-up, then repeats. ± is the 95% t interval.
| Prompt tokens | Repeats | GGUF prefill (tok/s) | Port prefill (tok/s) | GGUF decode (tok/s) | Port decode (tok/s) | GGUF time to first token (s) | Port time to first token (s) | Port peak (GB) |
|---|---|---|---|---|---|---|---|---|
| 128 | 5 | 999.2 ± 2.8 | 803.8 ± 3.1 | 59.53 ± 0.29 | 14.48 ± 0.02 | 0.128 | 0.159 | 7.15 |
| 512 | 5 | 1,044.7 ± 1.3 | 868.4 ± 8.8 | 59.30 ± 0.02 | 14.42 ± 0.02 | 0.490 | 0.590 | 7.51 |
| 4,096 | 5 | 999.8 ± 22.1 | 835.9 ± 3.6 | 57.69 ± 0.39 | 13.85 ± 0.01 | 4.098 | 4.901 | 8.17 |
| 16,384 | 5 | 862.4 ± 0.9 | 764.7 ± 11.3 | 52.98 ± 0.17 | 12.26 ± 0.05 | 18.997 | 21.427 | 8.14 |
| 65,536 | 3 | 624.5 ± 13.1 | 518.8 ± 18.7 | 36.47 ± 0.31 | 7.37 ± 0.08 | 104.947 | 126.336 | 8.43 |
| 131,072 | 2 | 408.7 ± 4.7 | 325.5 ± 23.0 | 21.19 ± 1.67 | 4.58 ± 0.21 | 320.683 | 402.659 | 9.74 |
"GGUF" is llama.cpp at the source's defaults (flash attention auto, K/V F16, n_ubatch 512, prefill chunk 2048). "Port"
is the shipped default, K/V float16, with the prefill chunks in long prompts and the cache limit from
65,536 tokens up. Peak is MLX's own counter. llama.cpp's peak resident memory on these rows runs about 1.7 to 3.7 GB, and it
maps the GGUF from disk. Every row, including peak footprint and resident memory for both runtimes, is in
evidence/REPORT.md.
Single runs up to the full context, with no warm-up and no repeats, 64 decodes (512 at 131,072 and 196,608):
| Prompt tokens | GGUF prefill (s) | Port prefill (s) | GGUF decode step (ms) | Port decode step (ms) | Port MLX peak (GB) | Port peak footprint (GB) | Lowest free memory, port |
|---|---|---|---|---|---|---|---|
| 65,536 | 107 | 123 | 29 | 142 | 8.43 | 13.15 | 56% |
| 131,072 | 333 | 401 | 52 | 226 | 9.74 | 18.37 (10.91 with the cache limit) | 20% (50% with the cache limit) |
| 196,608 | 705 | 977 | 75 | 306 | 11.02 | 12.12 | 44% |
| 262,080 | 1,234 | 1,639 | 102 | 423 | 12.36 | 13.71 | 40% |
Runs at 196,608 and 262,080 use the cache limit. With a float32 cache at 262,080, MLX peak is 15.06 GB, footprint 16.80 GB,
and lowest free memory 30%. Every length fit inside a guard that keeps 25% of memory free, on a machine with nothing else
heavy running. The one exception was a dip to 20% at 131,072 before the cache limit was added, too brief to trip the
guard. All numbers are for a 24 GB machine and were not tried on less.
Stock tool, this repo's default configuration, 128 generated tokens, mean of 5 trials:
| Prompt tokens | Prompt (tok/s) | Generation (tok/s) | mlx-lm peak (GB) |
|---|---|---|---|
| 128 | 482.4 | 14.73 | 7.149 |
| 512 | 749.2 | 14.73 | 7.507 |
| 4,096 | 825.3 | 14.17 | 8.175 |
| 16,384 | 736.4 | 12.35 | 8.348 |
mlx_lm.benchmark --model mchen04/LFM3-1.7B-Uncensored-MLX --trust-remote-code -p 512 -g 128 -n 5
Evaluation
Testing data, factors and metrics
The comparison baseline is llama.cpp 19cce691, the commit AtomicChat's card names, running the actual GGUF on the same
machine and the same token ids. Limits were committed to git before the first comparison
(evidence/TOLERANCES.md). Later additions are dated in that file, and no limit was loosened.
- Data: 15 short prompts (chat, multi-turn, JSON, tool call and follow-up, retrieval, French, Chinese, German, code,
Unicode edge cases, system prompt and one borderline request). A 4,097-token prompt for cache boundaries. A synthetic ledger with
three planted vault codes, cut to 7 lengths. All are in evidence/prompts. - Metrics: max and mean |Δlogit|, mean KL divergence, top-1 agreement, and whether greedy tokens are identical. A
"near tie" is a top-2 margin under 0.01.
| Run | Runtime and settings |
|---|---|
| S-A | llama.cpp defaults: flash attention auto, K/V F16, n_ubatch 512, chunk 2048 |
| S-F | llama.cpp: flash off, K/V F32, n_ubatch 512. Batched matmuls still run in half precision |
| S-F16 | llama.cpp float baseline: flash off, K/V F32, n_ubatch 16, chunk 16 |
| S-F1 | llama.cpp float baseline: flash off, K/V F32, n_ubatch 1, chunk 1 |
| S-C | llama.cpp CPU: flash off, K/V F32 |
| M32 | This port, K/V float32 |
| M16 | This port, K/V float16, the default |
Results
Port against the matching source run:
| Check | Result |
|---|---|
| T1 tensors vs ggml's dequantization | PASS: 286/286 bit-identical |
| T2 tokenizer | PASS: 755 strings, 340,959 + 343,611 ids, 0 differing. The first run missed on 2 strings, a port defect that was fixed |
| T3 template | PASS at matched settings, 22/22 both ways. MISS at llama-server defaults, 20/22 |
| T5b, T5c M32 logits vs S-F16, S-F1 (4,063 rows) | PASS: max |Δ| 2.0e-3, mean KL 2.5e-10 or less, 0 flips |
| T6, T6b, T6c M32 greedy vs S-F, S-F16, S-F1 | PASS: 15/15 identical each |
| T7 M16 logits vs S-A | PASS: mean KL 1.06e-5 (limit 1.5 × 1.19e-5), 2 flips, 1 outside near ties |
| T8 M16 greedy vs S-A | PASS: 15/15 identical |
| T9 M32 across 9 prefill schedules, against itself | PASS: max |Δ| 1.6e-3 (limit 5e-3) |
| T9 M32 cached vs uncached | PASS: max |Δ| 5.2e-4 |
| T9b M32 vs S-F16 at each schedule | PASS: max |Δ| 2.2e-3 |
| T10 long context, 7 lengths | PASS at all 7 |
| T11 context boundary, n_ctx 4,096 and 262,144 | PASS: the exact maximum decodes and the next token is refused |
| T12 fresh download of this repo, new environment and cache | PASS: commit 877524af, 11/11 manifest files match, all 4 code blocks on this card exit 0, 30/30 short-prompt logit dumps bit-identical to the local run |
Checks that miss because of the source's batched half-precision path. These are source results and not port defects, and
they stay reported as written:
| Check | Result |
|---|---|
| T5 M32 logits vs S-F, as first written | MISS: max |Δ| 1.51 (limit 5e-3), 2 flips, 1 outside near ties. S-F vs S-F16 shows the same gap |
| T9 S-F against itself across prefill schedules | MISS: max |Δ| 0.075 (limit 5e-3) |
T9 S-F across n_ubatch 64 and 2048 |
MISS: max |Δ| 0.044 |
| T9 M32 vs S-F at each schedule | MISS: max |Δ| 0.077 |
Long context. Scored over 16 evenly spaced prompt positions, the last 8 prompt positions and the first 64 decodes. The
262,080 case fills the context exactly.
| Prompt tokens | M32 vs S-F16 max |Δ| (limit 2e-2) | Mean KL (limit 1e-5) | M16 vs S-A max |Δ| | Mean KL | S-A vs S-F16 mean KL | Greedy identical |
|---|---|---|---|---|---|---|
| 1,024 | 4.10e-4 | 2.03e-10 | 9.42e-2 | 5.48e-6 | 6.20e-6 | yes |
| 4,096 | 5.24e-4 | 3.07e-10 | 1.11e-1 | 4.42e-6 | 4.10e-6 | yes |
| 16,384 | 5.80e-4 | 3.27e-10 | 4.53e-2 | 4.49e-6 | 4.29e-6 | yes |
| 65,536 | 3.82e-3 | 7.80e-9 | 5.01e-2 | 1.85e-6 | 1.63e-6 | yes |
| 131,072 | 5.45e-3 | 5.03e-8 | 3.65e-2 | 1.71e-6 | 1.74e-6 | yes |
| 196,608 | 1.19e-2 | 2.11e-7 | 4.40e-2 | 1.08e-6 | 1.47e-6 | yes |
| 262,080 | 1.51e-2 | 3.24e-7 | 2.17e-1 | 6.84e-6 | 7.32e-6 | yes |
There are no top-1 flips at any of these lengths. M32's difference from the float baseline grows with length. The largest
is 75% of the limit, so it could pass the limit beyond what these prompts exercise. Greedy also stayed identical through 512
decodes at 1,024, 65,536, 131,072 and 196,608.
Retrieval by length
The planted codes are Willow=1791, Cedar=8831 and Juniper=3345. All four runs (S-F16, S-A, M32, M16) got the same result at
every length.
| Prompt tokens | Decodes | Result |
|---|---|---|
| 1,024 | 512 | All 3 correct |
| 4,096 | 64 | All 3 correct |
| 16,384 | 64 | All 3 correct |
| 65,536 | 512 | No answer yet: still reasoning |
| 131,072 | 512 | No answer yet: still reasoning |
| 196,608 | 512 | No answer yet: still reasoning |
| 262,080 | 64 | 2 of 3: answers Juniper=3599 |
This is one synthetic prompt and not a measure of recall in general. AtomicChat's needle test is a different test.
Summary
Weights, tokenizer and greedy output matched across every tested input, up to the advertised context. Logits differ from
llama.cpp by amounts within the declared limits, and those differences are not zero. Against llama.cpp's defaults, the
port differs about as much as llama.cpp's defaults differ from its own float path. Full numbers:
evidence/REPORT.md. Raw results and /usr/bin/time -l receipts are in
evidence/raw.tar.gz. Logit dumps are not uploaded, and the code in repro regenerates them.
What changed from the source
The LFM Open License requires notice of modified files. NOTICE.md has the same list.
From AtomicChat/LFM3-1.7B-Uncensored-GGUF:
model-*.safetensors,model.safetensors.index.json: every tensor dequantized to float32 with ggml's arithmetic, F32
tensors copied bit for bit. GGUF names mapped to the transformers layout, and depthwise convolution kernels stored as(C, K, 1).rope_freqs.weightis dropped becauselfm3.pyderives the same values.
From LiquidAI/LFM3-1.7B, because the GGUF embeds only its own tokenizer form:
tokenizer.json:<|tool_call_start|>and<|tool_call_end|>are marked special, because the GGUF types them as
control tokens. It has 125,033 tokens and the same merges as the GGUF. The GGUF's other 2,967 ids are unused[PADn]slots.config.json:auto_mapremoved.model_file,dtype,kv_cache_dtypeandsource_provenanceadded.chat_template.jinja: unchanged, and byte-identical to the GGUF's template.generation_config.json,special_tokens_map.jsonandtokenizer_config.jsonare copied from Liquid AI's repo.
New in this repo: lfm3.py, convert_manifest.json (sha256 of every input and output file), NOTICE.md,requirements.txt, evidence/ and repro/. LICENSE is unchanged.
Training Details
None. This is a format conversion. There was no training, new abliteration, pruning or further quantization. AtomicChat's
card describes how it modified Liquid AI's model. The Hub's model tree labels this repo a finetune of its base model because
its metadata has no value for dequantization. That label is not accurate.
Environmental Impact
Conversion took about 10 seconds on the Mac mini. The verification runs took hours of that machine's time. Energy was not
measured.
Technical Specifications
Architecture: Lfm3ForCausalLM, hidden size 2048, tied output embedding, vocabulary 128,000 in the GGUF and 125,033 in
the tokenizer. Full attention at layers 2, 6, 10, 14, 18 and 22, with 16 query heads and 4 K/V heads. Sliding-window
attention (256 tokens) at layers 4, 8, 12, 16 and 20. The other 13 layers are gated short convolutions.
Hardware: Mac mini, Apple M4, 24 GB, macOS 26.2 (25C56).
Software: Python 3.12.13, mlx 0.32.4, mlx-lm 0.32.2, transformers 5.22.1, tokenizers 0.23.3, numpy 2.5.4. llama.cpp19cce6919992fdffd92e0ac152caaf1e2b8f254e. Everything is pinned in requirements.txt.
Reproducing: repro/README.md has the commands, from downloading the GGUF through the report. Converting
again from the pinned inputs with the code in repro/, in an empty directory, gave 11/11 files byte-identical to this repo,
and the 29 tests passed (log).
Citation
There is no paper for this port. Please cite the upstream work: LiquidAI/LFM3-1.7B
and AtomicChat/LFM3-1.7B-Uncensored-GGUF.
Glossary
- Dequantized: quantized values expanded back to float32. The numbers are exact, but what quantization lost stays lost.
- K/V cache: the stored keys and values of past tokens in the full-attention layers.
- Prefill: processing the prompt before the first generated token.
- Greedy: always taking the highest logit, with the lowest id winning ties.
- Flip: a position where two runs disagree on the top token.
License
This model derives from LiquidAI/LFM3-1.7B and is distributed under the LFM
Open License v1.0. The full text is in LICENSE. Commercial use by a legal entity above the license's revenue
threshold (USD 10 million) is not licensed. Read the license for the exact terms.
Credits
- Liquid AI for LFM3-1.7B.
- AtomicChat for the uncensored modification and the GGUF.
- llama.cpp and mlx-lm.
Model Card Authors and Contact
mchen04. Questions and corrections go in this repo's Community tab.