burst-codec 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
burst_codec/Makefile ADDED
@@ -0,0 +1,54 @@
1
+ # Makefile — detrc: the shared library for Python (host) and the cross-arch harness (host + aarch64 under qemu).
2
+ # make host only: build/libdetrc.so, build/detrc_test_x86, build/detrc_ctl_x86
3
+ # make arm aarch64 harness builds (needs the Android NDK: ANDROID_HOME, NDK_VERSION as in android/lib/build.gradle.kts)
4
+ # make identity host vs aarch64 (qemu-aarch64) identity gate, with its negative controls
5
+ CFLAGS ?= -O2 -ffp-contract=off -Wall -Wextra -std=c99
6
+ NDK_VERSION ?= 29.0.13113456
7
+ NDK ?= $(ANDROID_HOME)/ndk/$(NDK_VERSION)/toolchains/llvm/prebuilt/linux-x86_64/bin
8
+ ARMCC ?= $(NDK)/aarch64-linux-android31-clang
9
+ QEMU ?= /usr/bin/qemu-aarch64
10
+ B ?= build
11
+
12
+ all: $(B)/libdetrc.so $(B)/detrc_test_x86 $(B)/detrc_ctl_x86
13
+
14
+ arm: $(B)/detrc_test_aarch64 $(B)/detrc_ctl_aarch64
15
+
16
+ $(B):
17
+ mkdir -p $(B)
18
+
19
+ $(B)/libdetrc.so: detrc.c detrc.h detrc_exptab.h | $(B)
20
+ $(CC) $(CFLAGS) -fPIC -shared detrc.c -o $@
21
+
22
+ $(B)/detrc_test_x86: detrc.c detrc_test.c detrc.h detrc_exptab.h | $(B)
23
+ $(CC) $(CFLAGS) detrc.c detrc_test.c -o $@
24
+
25
+ $(B)/detrc_test_aarch64: detrc.c detrc_test.c detrc.h detrc_exptab.h | $(B)
26
+ $(ARMCC) $(CFLAGS) -static detrc.c detrc_test.c -o $@
27
+
28
+ # negative control: a float softmax (libm expf, float sums) with FMA contraction + fast-math ON for the arm build.
29
+ # Measured 10-24: with contraction OFF, glibc and bionic expf AGREED on every synthetic row (that control could not
30
+ # fail); with -O3 -ffast-math -ffp-contract=fast on arm64 the freq tables and bytes DIFFER (byte 24 of the stream).
31
+ # The integer detrc build is identical under the same flags. `make identity` asserts the control's divergence.
32
+ $(B)/detrc_ctl_x86: detrc.c detrc_test.c | $(B)
33
+ $(CC) $(CFLAGS) -DCONTROL_FLOAT detrc.c detrc_test.c -o $@ -lm
34
+
35
+ $(B)/detrc_ctl_aarch64: detrc.c detrc_test.c | $(B)
36
+ $(ARMCC) -O3 -ffast-math -ffp-contract=fast -Wall -std=c99 -DCONTROL_FLOAT -static detrc.c detrc_test.c -o $@ -lm
37
+
38
+ # identity gate: same freq tables + same bytes on both chips; the control build must differ
39
+ identity: $(B)/detrc_test_x86 $(B)/detrc_test_aarch64 $(B)/detrc_ctl_x86 $(B)/detrc_ctl_aarch64
40
+ $(B)/detrc_test_x86 synth $(B)/freq_x86.bin $(B)/rc_x86.bin
41
+ $(QEMU) $(B)/detrc_test_aarch64 synth $(B)/freq_arm.bin $(B)/rc_arm.bin
42
+ cmp $(B)/freq_x86.bin $(B)/freq_arm.bin && cmp $(B)/rc_x86.bin $(B)/rc_arm.bin && echo "detrc: IDENTICAL x86 vs aarch64"
43
+ $(B)/detrc_ctl_x86 synth $(B)/cfreq_x86.bin $(B)/crc_x86.bin
44
+ $(QEMU) $(B)/detrc_ctl_aarch64 synth $(B)/cfreq_arm.bin $(B)/crc_arm.bin
45
+ @if cmp -s $(B)/crc_x86.bin $(B)/crc_arm.bin; then echo "CONTROL DID NOT DIFFER: the gate cannot fail"; exit 1; \
46
+ else echo "control (float softmax, FMA on arm): DIFFERS as it must"; fi
47
+ $(B)/detrc_test_x86 synth1ulp $(B)/freq_x86_1.bin $(B)/rc_x86_1.bin
48
+ @if cmp -s $(B)/rc_x86.bin $(B)/rc_x86_1.bin; then echo "INPUT-CHANGE CONTROL DID NOT DIFFER"; exit 1; \
49
+ else echo "control (one input float changed): DIFFERS as it must"; fi
50
+
51
+ clean:
52
+ rm -rf $(B)
53
+
54
+ .PHONY: all arm identity clean
@@ -0,0 +1,58 @@
1
+ # detlm builds (use: make -f Makefile.detlm <target>).
2
+ # all (default): host only — detlm_x86 (CLI), libdetlm.so (for det_codec.py), detcodec_x86
3
+ # arm: static aarch64 builds via the Android NDK (ANDROID_HOME, NDK_VERSION as in android/lib/build.gradle.kts)
4
+ # detlm_x86_reorder / detlm_x86_fma / detlm_arm_fma: negative controls; check-fma: count fused ops in the builds
5
+ # Determinism flags are load-bearing: -ffp-contract=off
6
+ # (gcc and NDK clang both default to contraction), never -ffast-math / -Ofast (crtfastmath flips FTZ/DAZ), no -march=native.
7
+ CC ?= gcc
8
+ NDK_VERSION ?= 29.0.13113456
9
+ NDK ?= $(ANDROID_HOME)/ndk/$(NDK_VERSION)/toolchains/llvm/prebuilt/linux-x86_64/bin
10
+ ARMCC ?= $(NDK)/aarch64-linux-android31-clang
11
+ DETFLAGS = -O2 -ffp-contract=off -fno-math-errno -fno-trapping-math -Wall -Wextra -std=c11
12
+ SRC = detlm.c detlm_cli.c
13
+
14
+ all: detlm_x86 libdetlm.so detcodec_x86
15
+
16
+ arm: detlm_arm detcodec_arm
17
+
18
+ detlm_x86: $(SRC) detlm.h
19
+ $(CC) $(DETFLAGS) -o $@ $(SRC) -lpthread -lm
20
+
21
+ # negative control 1: reversed block accumulation order (must change the hash)
22
+ detlm_x86_reorder: $(SRC) detlm.h
23
+ $(CC) $(DETFLAGS) -DDETLM_REORDER_SUM -o $@ $(SRC) -lpthread -lm
24
+
25
+ # negative control 2: FMA contraction on (x86 needs -mfma to even have the instruction)
26
+ detlm_x86_fma: $(SRC) detlm.h
27
+ $(CC) -O2 -ffp-contract=fast -mfma -fno-math-errno -fno-trapping-math -std=c11 -o $@ $(SRC) -lpthread -lm
28
+
29
+ # static aarch64 (bionic) binary: runs on the phone and under qemu-aarch64
30
+ detlm_arm: $(SRC) detlm.h
31
+ $(ARMCC) $(DETFLAGS) -static -o $@ $(SRC)
32
+
33
+ # negative control 3: aarch64 with contraction on (fmla/fmadd emitted)
34
+ detlm_arm_fma: $(SRC) detlm.h
35
+ $(ARMCC) -O2 -ffp-contract=fast -fno-math-errno -fno-trapping-math -std=c11 -static -o $@ $(SRC)
36
+
37
+ # shared library for ctypes / JNI consumers
38
+ libdetlm.so: detlm.c detlm.h
39
+ $(CC) $(DETFLAGS) -fPIC -shared -o $@ detlm.c -lpthread -lm
40
+
41
+ # detcodec: frame 0x08 on the phone (detlm + detrc, no Python, no tokenizer) — SAME flags as the engine
42
+ CODEC_SRC = detlm.c detrc.c detcodec.c
43
+ detcodec_x86: $(CODEC_SRC) detlm.h detrc.h detrc_exptab.h
44
+ $(CC) $(DETFLAGS) -o $@ $(CODEC_SRC) -lpthread -lm
45
+
46
+ detcodec_arm: $(CODEC_SRC) detlm.h detrc.h detrc_exptab.h
47
+ $(ARMCC) $(DETFLAGS) -static -o $@ $(CODEC_SRC)
48
+
49
+ check-fma: detlm_x86 detlm_arm detlm_arm_fma
50
+ @echo "x86 fused ops (want 0): $$(objdump -d detlm_x86 | grep -cE 'vfmadd|vfmsub|vfnmadd|vfnmsub')"
51
+ @echo "arm fused ops (want 0): $$($(NDK)/llvm-objdump -d detlm_arm | grep -cE '\bfmadd|\bfmsub|\bfnmadd|\bfnmsub|\bfmla|\bfmls')"
52
+ @echo "arm_fma control fused ops (want >0): $$($(NDK)/llvm-objdump -d detlm_arm_fma | grep -cE '\bfmadd|\bfmsub|\bfnmadd|\bfnmsub|\bfmla|\bfmls')"
53
+ @echo "x86 libm transcendentals (want none):"; nm detlm_x86 | grep -E ' U (expf|sinf|cosf|powf|logf|tanhf|exp|sin|cos|pow|log)$$' || echo " none"
54
+
55
+ clean:
56
+ rm -f detlm_x86 detlm_x86_reorder detlm_x86_fma detlm_arm detlm_arm_fma libdetlm.so detcodec_x86 detcodec_arm
57
+
58
+ .PHONY: all arm check-fma clean
@@ -0,0 +1,26 @@
1
+ """burst_codec — the Burst codec as an installable package (Apache-2.0).
2
+
3
+ The codec modules are flat (det_codec, open_codec, mrc, repro) and import each other by name, exactly as in the
4
+ source tree; importing this package puts them on the path so `import det_codec` works after `pip install`.
5
+ Frame 0x08 = det_codec (deterministic, the default); 0x03 = open_codec (mismatch-resistant, any GGUF).
6
+ Both ends need the same model file: export BURST_GGUF=/path/to/Qwen2.5-0.5B.Q8_0.gguf (sha256 d5163baa…).
7
+ """
8
+ import os as _os, sys as _sys
9
+
10
+ _HERE = _os.path.dirname(_os.path.abspath(__file__))
11
+ if _HERE not in _sys.path:
12
+ _sys.path.insert(0, _HERE)
13
+
14
+ __version__ = "0.1.0"
15
+
16
+ # libdetlm.so (the bit-identical engine det_codec loads) is built once, here, with the same determinism flags as
17
+ # Makefile.detlm — never -ffast-math, never -march=native, -ffp-contract=off is load-bearing. Needs a C compiler.
18
+ _SO = _os.path.join(_HERE, "libdetlm.so")
19
+ if not _os.path.exists(_SO):
20
+ import subprocess as _sp
21
+ try:
22
+ _sp.run([_os.environ.get("CC", "cc"), "-O2", "-ffp-contract=off", "-fno-math-errno", "-fno-trapping-math", "-std=c11",
23
+ "-fPIC", "-shared", "-o", _SO, _os.path.join(_HERE, "detlm.c"), "-lpthread", "-lm"], check=True,
24
+ capture_output=True)
25
+ except (OSError, _sp.CalledProcessError) as _e:
26
+ _sys.stderr.write(f"burst_codec: could not build libdetlm.so ({_e}); frame 0x08 on the real model needs a C compiler\n")
@@ -0,0 +1 @@
1
+ {"version": 1, "prime": "The following is a short plain-English message sent between two people over a radio.\n\n", "model": "qwen2.5-0.5b-instruct-q8_0.gguf", "freq": [826.0, 482.0, 474.0, 449.0, 455.0, 340.0, 334.0, 282.0, 289.0, 241.0, 256.0, 212.0, 186.0, 201.0, 159.0, 139.0, 100.0, 76.0, 70.0, 59.0, 46.0, 32.0, 25.0, 18.0, 8.0, 9.0, 14.0, 7.0, 6.0, 6.0, 2.0, 1.0, 1.0, 2.0, 2.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0, 1.0]}
burst_codec/cli.py ADDED
@@ -0,0 +1,20 @@
1
+ """burst-codec command: `burst-codec selftest [--golden]` (coder + refusal tests; --golden needs the model) or
2
+ `burst-codec repro <args>` (the README's reproduction commands, needs the model)."""
3
+ import runpy, sys
4
+
5
+ import burst_codec # noqa: F401 (puts the flat codec modules on the path)
6
+
7
+ CMDS = {"selftest": "test_det_codec", "repro": "repro"}
8
+
9
+
10
+ def main():
11
+ if len(sys.argv) < 2 or sys.argv[1] not in CMDS:
12
+ print("usage: burst-codec selftest [--golden] | burst-codec repro <args> (see README)")
13
+ sys.exit(2)
14
+ mod = CMDS[sys.argv[1]]
15
+ sys.argv = [mod + ".py", *sys.argv[2:]]
16
+ runpy.run_module(mod, run_name="__main__")
17
+
18
+
19
+ if __name__ == "__main__":
20
+ main()
@@ -0,0 +1,353 @@
1
+ #!/usr/bin/env python3
2
+ """det_codec — burst frame 0x08: an exact range coder over a bit-identical model.
3
+
4
+ frame = [0x08][2-byte check = sha256(text)[:2]][varint n_tokens][range-coder bytes]
5
+
6
+ Model and coder are implied by the codec byte: no model id, no c, no tail id. The history window (the learned tail,
7
+ open_codec.prime_for) is found by the receiver trying candidate tails newest-first and letting the 2-byte check pick;
8
+ nothing that fails the check is ever shown. Both ends must compute the SAME logits bit for bit — that is detlm's job
9
+ (detlm.c); detrc (detrc.c via ctypes) turns identical logits into identical bytes.
10
+
11
+ Model protocol (pluggable, so the coder is testable without model weights):
12
+ m.n_vocab int
13
+ m.tokenize(text) -> list[int] m.detokenize(ids) -> str
14
+ m.reset(prime_ids) -> logits float32[V]: next-token logits after the prime
15
+ m.step(tok) -> logits float32[V]: next-token logits after feeding one more token
16
+ m.n_ctx int or None: context size; a decode that would overrun it is refused up front
17
+ Three implementations here: DetlmModel (the real engine), LlamaModel (llama.cpp as a stand-in — exact only encoder-and-decoder-on-the-same-box, same
18
+ eval path) and MockModel (seeded hash model, byte tokens, no weights).
19
+
20
+ API: crunch(text, model, tail=()) -> bytes uncrunch(frame, model, tails=()) -> text (raises DecodeFailed)
21
+ """
22
+ import ctypes, hashlib, os, subprocess, sys
23
+ import numpy as np
24
+
25
+ HERE = os.path.dirname(os.path.abspath(__file__))
26
+ try:
27
+ import open_codec # PRIME, prime_for, TAIL_N — one definition, shared with 0x03
28
+ except ImportError: # source tree: open_codec.py sits one directory up
29
+ sys.path.insert(0, os.path.dirname(HERE))
30
+ import open_codec
31
+
32
+ CODEC_BYTE = 0x08
33
+ MAX_TOKENS = 2048 # a flipped varint byte must not become a million model steps
34
+ MAX_TAILS = 6 # candidate histories tried, newest first
35
+ TOTAL_BITS = 24
36
+
37
+
38
+ class DecodeFailed(ValueError):
39
+ """The check did not match under any candidate history: refused, never shown."""
40
+
41
+
42
+ # ---------------------------------------------------------------------------------------------------------------------
43
+ # detrc via ctypes
44
+ # ---------------------------------------------------------------------------------------------------------------------
45
+ _lib = None
46
+
47
+
48
+ def _build_lib():
49
+ so = os.path.join(HERE, "build", "libdetrc.so")
50
+ srcs = [os.path.join(HERE, f) for f in ("detrc.c", "detrc.h", "detrc_exptab.h")]
51
+ if not os.path.exists(so) or any(os.path.getmtime(s) > os.path.getmtime(so) for s in srcs):
52
+ os.makedirs(os.path.dirname(so), exist_ok=True)
53
+ subprocess.run([os.environ.get("CC", "cc"), "-O2", "-ffp-contract=off", "-std=c99", "-fPIC", "-shared",
54
+ srcs[0], "-o", so], check=True)
55
+ return so
56
+
57
+
58
+ def lib():
59
+ global _lib
60
+ if _lib is None:
61
+ L = ctypes.CDLL(_build_lib())
62
+ u8p, f32p, u32p = ctypes.POINTER(ctypes.c_uint8), ctypes.POINTER(ctypes.c_float), ctypes.POINTER(ctypes.c_uint32)
63
+ L.detrc_version.restype = ctypes.c_int
64
+ L.detrc_freq.argtypes = [f32p, ctypes.c_int32, u32p]; L.detrc_freq.restype = ctypes.c_int
65
+ L.detrc_enc_new.restype = ctypes.c_void_p
66
+ L.detrc_enc_put.argtypes = [ctypes.c_void_p, u32p, ctypes.c_int32, ctypes.c_int32]; L.detrc_enc_put.restype = ctypes.c_int
67
+ L.detrc_enc_put_logits.argtypes = [ctypes.c_void_p, f32p, ctypes.c_int32, ctypes.c_int32]; L.detrc_enc_put_logits.restype = ctypes.c_int
68
+ L.detrc_enc_finish.argtypes = [ctypes.c_void_p]; L.detrc_enc_finish.restype = ctypes.c_int64
69
+ L.detrc_enc_data.argtypes = [ctypes.c_void_p]; L.detrc_enc_data.restype = ctypes.c_void_p
70
+ L.detrc_enc_free.argtypes = [ctypes.c_void_p]
71
+ L.detrc_dec_new.argtypes = [u8p, ctypes.c_int64]; L.detrc_dec_new.restype = ctypes.c_void_p
72
+ L.detrc_dec_get.argtypes = [ctypes.c_void_p, u32p, ctypes.c_int32]; L.detrc_dec_get.restype = ctypes.c_int32
73
+ L.detrc_dec_get_logits.argtypes = [ctypes.c_void_p, f32p, ctypes.c_int32]; L.detrc_dec_get_logits.restype = ctypes.c_int32
74
+ L.detrc_dec_free.argtypes = [ctypes.c_void_p]
75
+ _lib = L
76
+ return _lib
77
+
78
+
79
+ def _f32(a):
80
+ a = np.ascontiguousarray(a, dtype=np.float32)
81
+ return a, a.ctypes.data_as(ctypes.POINTER(ctypes.c_float))
82
+
83
+
84
+ def freq_table(logits):
85
+ """the deterministic integer frequency table (sum 2^24, every entry >= 1) — exposed for tests and the identity gate"""
86
+ a, p = _f32(logits)
87
+ out = np.empty(len(a), np.uint32)
88
+ if lib().detrc_freq(p, len(a), out.ctypes.data_as(ctypes.POINTER(ctypes.c_uint32))):
89
+ raise ValueError("detrc_freq failed")
90
+ return out
91
+
92
+
93
+ class Encoder:
94
+ def __init__(self):
95
+ self.L = lib(); self.h = self.L.detrc_enc_new()
96
+
97
+ def put_logits(self, logits, sym):
98
+ a, p = _f32(logits)
99
+ if self.L.detrc_enc_put_logits(self.h, p, len(a), int(sym)):
100
+ raise ValueError("detrc_enc_put_logits failed")
101
+
102
+ def put(self, freq, sym):
103
+ f = np.ascontiguousarray(freq, dtype=np.uint32)
104
+ if self.L.detrc_enc_put(self.h, f.ctypes.data_as(ctypes.POINTER(ctypes.c_uint32)), len(f), int(sym)):
105
+ raise ValueError("detrc_enc_put failed (bad table?)")
106
+
107
+ def finish(self):
108
+ n = self.L.detrc_enc_finish(self.h)
109
+ if n < 0:
110
+ raise MemoryError("detrc_enc_finish")
111
+ return ctypes.string_at(self.L.detrc_enc_data(self.h), n) if n else b""
112
+
113
+ def __del__(self):
114
+ if getattr(self, "h", None):
115
+ self.L.detrc_enc_free(self.h); self.h = None
116
+
117
+
118
+ class Decoder:
119
+ def __init__(self, data):
120
+ self.L = lib(); self.buf = (ctypes.c_uint8 * max(1, len(data))).from_buffer_copy(data or b"\0")
121
+ self.h = self.L.detrc_dec_new(self.buf, len(data))
122
+
123
+ def get_logits(self, logits):
124
+ a, p = _f32(logits)
125
+ s = self.L.detrc_dec_get_logits(self.h, p, len(a))
126
+ if s < 0:
127
+ raise ValueError("detrc_dec_get_logits failed")
128
+ return s
129
+
130
+ def get(self, freq):
131
+ f = np.ascontiguousarray(freq, dtype=np.uint32)
132
+ s = self.L.detrc_dec_get(self.h, f.ctypes.data_as(ctypes.POINTER(ctypes.c_uint32)), len(f))
133
+ if s < 0:
134
+ raise ValueError("detrc_dec_get failed (bad table?)")
135
+ return s
136
+
137
+ def __del__(self):
138
+ if getattr(self, "h", None):
139
+ self.L.detrc_dec_free(self.h); self.h = None
140
+
141
+
142
+ # ---------------------------------------------------------------------------------------------------------------------
143
+ # frame
144
+ # ---------------------------------------------------------------------------------------------------------------------
145
+ def _check(text):
146
+ return hashlib.sha256(text.encode()).digest()[:2]
147
+
148
+
149
+ def _wv(n, out):
150
+ while True:
151
+ b = n & 0x7F; n >>= 7
152
+ out.append(b | (0x80 if n else 0))
153
+ if not n:
154
+ return
155
+
156
+
157
+ def _rv(buf, i, limit=MAX_TOKENS):
158
+ shift = val = 0
159
+ while True:
160
+ if i >= len(buf) or shift > 28:
161
+ raise DecodeFailed("bad token count")
162
+ b = buf[i]; i += 1; val |= (b & 0x7F) << shift
163
+ if not b & 0x80:
164
+ if val > limit:
165
+ raise DecodeFailed(f"token count {val} over the cap {limit}")
166
+ return val, i
167
+ shift += 7
168
+
169
+
170
+ def crunch(text, model, tail=()):
171
+ """text -> 0x08 frame. tail = the learned history (the last TAIL_N confirmed messages, oldest first)."""
172
+ tail = list(tail)[-open_codec.TAIL_N:]
173
+ toks = model.tokenize(text)
174
+ if model.detokenize(toks) != text:
175
+ raise ValueError("text does not survive tokenize/detokenize; not sendable as 0x08")
176
+ if len(toks) > MAX_TOKENS:
177
+ raise ValueError(f"{len(toks)} tokens is over the 0x08 cap of {MAX_TOKENS}")
178
+ enc = Encoder()
179
+ lg = model.reset(model.tokenize(open_codec.prime_for(tail)))
180
+ for t in toks:
181
+ enc.put_logits(lg, t)
182
+ lg = model.step(t)
183
+ head = bytearray([CODEC_BYTE]) + _check(text); _wv(len(toks), head)
184
+ return bytes(head) + enc.finish()
185
+
186
+
187
+ def _decode_with(body, n, model, tail):
188
+ prime = model.tokenize(open_codec.prime_for(tail))
189
+ n_ctx = getattr(model, "n_ctx", None)
190
+ if n_ctx and len(prime) + n > n_ctx: # cannot be a frame this model encoded under this history
191
+ raise DecodeFailed(f"{len(prime)} prime + {n} message tokens is over the {n_ctx}-token context")
192
+ dec = Decoder(body)
193
+ lg = model.reset(prime)
194
+ toks = []
195
+ for _ in range(n):
196
+ t = dec.get_logits(lg)
197
+ toks.append(t)
198
+ lg = model.step(t)
199
+ return model.detokenize(toks)
200
+
201
+
202
+ def uncrunch(frame, model, tails=(), max_tails=MAX_TAILS):
203
+ """0x08 frame -> text, or DecodeFailed. tails = candidate histories newest first (each a list of messages, oldest
204
+ first); the base prime (no history) is always tried last. The first candidate whose decode passes the check wins."""
205
+ if not frame or frame[0] != CODEC_BYTE:
206
+ raise DecodeFailed("not a 0x08 frame")
207
+ if len(frame) < 4:
208
+ raise DecodeFailed("frame too short")
209
+ chk = bytes(frame[1:3])
210
+ n, i = _rv(frame, 3)
211
+ body = bytes(frame[i:])
212
+ cands = [list(t)[-open_codec.TAIL_N:] for t in list(tails)[:max_tails]]
213
+ if [] not in cands:
214
+ cands.append([])
215
+ for tail in cands:
216
+ try:
217
+ text = _decode_with(body, n, model, tail)
218
+ except (ValueError, UnicodeError, RuntimeError): # over the context, an engine error, or a stream the
219
+ continue # detokenizer cannot render: this candidate does not match
220
+ if _check(text) == chk:
221
+ return text
222
+ raise DecodeFailed("plaintext check failed under every candidate history: wrong decode, not shown")
223
+
224
+
225
+ # ---------------------------------------------------------------------------------------------------------------------
226
+ # models
227
+ # ---------------------------------------------------------------------------------------------------------------------
228
+ class LlamaModel:
229
+ """llama.cpp as the stand-in engine. Exact ONLY when encoder and decoder run the same build on the same box with the
230
+ same eval path: the prime is evaluated as one batch, then every message token one at a time, on both sides
231
+ (measured 10-24: step-vs-step logits bit-identical even at 16 threads; batched-vs-stepped differ by up to 0.34)."""
232
+
233
+ def __init__(self, path=None, n_ctx=1024, n_threads=None):
234
+ from llama_cpp import Llama
235
+ path = path or open_codec.MODEL_PATH
236
+ self.n_ctx = n_ctx
237
+ self.L = Llama(model_path=path, n_ctx=n_ctx, n_threads=n_threads or max(1, (os.cpu_count() or 2) // 2),
238
+ logits_all=False, verbose=False)
239
+ self.n_vocab = self.L.n_vocab()
240
+
241
+ def tokenize(self, text):
242
+ return self.L.tokenize(text.encode(), add_bos=False, special=False)
243
+
244
+ def detokenize(self, ids):
245
+ return open_codec.detok(self.L, ids).decode("utf-8", errors="replace")
246
+
247
+ def _logits(self):
248
+ return np.ctypeslib.as_array(self.L._ctx.get_logits(), shape=(self.n_vocab,)).copy()
249
+
250
+ def reset(self, prime_ids):
251
+ self.L.reset(); self.L.eval(list(prime_ids)); return self._logits()
252
+
253
+ def step(self, tok):
254
+ self.L.eval([int(tok)]); return self._logits()
255
+
256
+
257
+ class DetlmModel:
258
+ """detlm (detlm.c via ctypes, libdetlm.so): the bit-identical engine — the SAME logits on every CPU it is built
259
+ for, so frames encoded on one machine decode on another exactly. Tokenizer: llama.cpp's, vocab-only load (integer,
260
+ deterministic, no weights). Call pattern, kept identical to the C detcodec on both ends: the prime as ONE batch
261
+ (logits of its last token), then every message token one at a time."""
262
+
263
+ def __init__(self, path=None, n_ctx=2048, n_threads=4, so=None):
264
+ from llama_cpp import Llama
265
+ path = path or open_codec.MODEL_PATH
266
+ self.tok = Llama(model_path=path, vocab_only=True, verbose=False)
267
+ self.L = ctypes.CDLL(so or os.path.join(HERE, "libdetlm.so"))
268
+ L = self.L; i32p, f32p = ctypes.POINTER(ctypes.c_int32), ctypes.POINTER(ctypes.c_float)
269
+ L.detlm_load.argtypes = [ctypes.c_char_p, ctypes.c_int, ctypes.c_int]; L.detlm_load.restype = ctypes.c_void_p
270
+ L.detlm_free.argtypes = [ctypes.c_void_p]
271
+ L.detlm_reset.argtypes = [ctypes.c_void_p]
272
+ L.detlm_n_vocab.argtypes = [ctypes.c_void_p]; L.detlm_n_vocab.restype = ctypes.c_int
273
+ L.detlm_n_pos.argtypes = [ctypes.c_void_p]; L.detlm_n_pos.restype = ctypes.c_int
274
+ L.detlm_n_ctx.argtypes = [ctypes.c_void_p]; L.detlm_n_ctx.restype = ctypes.c_int
275
+ L.detlm_eval.argtypes = [ctypes.c_void_p, i32p, ctypes.c_int, ctypes.c_int, f32p]; L.detlm_eval.restype = ctypes.c_int
276
+ L.detlm_version.restype = ctypes.c_char_p
277
+ self.h = L.detlm_load(path.encode(), int(n_ctx), int(n_threads))
278
+ if not self.h:
279
+ raise RuntimeError(f"detlm_load failed on {path}")
280
+ self.n_vocab = L.detlm_n_vocab(self.h)
281
+ self.n_ctx = L.detlm_n_ctx(self.h)
282
+ self.version = L.detlm_version().decode()
283
+
284
+ def tokenize(self, text):
285
+ return self.tok.tokenize(text.encode(), add_bos=False, special=False)
286
+
287
+ def detokenize(self, ids):
288
+ return open_codec.detok(self.tok, ids).decode("utf-8", errors="replace")
289
+
290
+ def _eval(self, ids):
291
+ a = np.ascontiguousarray(ids, dtype=np.int32)
292
+ lg = np.empty(self.n_vocab, np.float32)
293
+ if self.L.detlm_eval(self.h, a.ctypes.data_as(ctypes.POINTER(ctypes.c_int32)), len(a), 1,
294
+ lg.ctypes.data_as(ctypes.POINTER(ctypes.c_float))):
295
+ raise RuntimeError("detlm_eval failed (context full or bad token id)")
296
+ return lg
297
+
298
+ def reset(self, prime_ids):
299
+ self.L.detlm_reset(self.h); return self._eval(list(prime_ids))
300
+
301
+ def step(self, tok):
302
+ return self._eval([int(tok)])
303
+
304
+ def close(self):
305
+ if getattr(self, "h", None):
306
+ self.L.detlm_free(self.h); self.h = None
307
+
308
+ def __del__(self):
309
+ self.close()
310
+
311
+
312
+ class MockModel:
313
+ """A deterministic fake: tokens are UTF-8 bytes (ids 0..255) inside a 151,936 vocabulary; logits come from a seeded
314
+ generator keyed on the WHOLE context (so different histories give different streams, as a real model would), with
315
+ a context-chosen sharpness so flat, peaky and floor cases all occur."""
316
+ n_vocab = 151936
317
+ n_ctx = None
318
+
319
+ def __init__(self, seed=20261024):
320
+ self.seed = seed; self.ctx = []
321
+
322
+ def tokenize(self, text):
323
+ return list(text.encode())
324
+
325
+ def detokenize(self, ids):
326
+ return bytes(i if i < 256 else 0x3F for i in ids).decode("utf-8", errors="replace") # garbage id -> '?'
327
+
328
+ def _logits(self):
329
+ key = hashlib.sha256(repr((self.seed, self.ctx)).encode()).digest()
330
+ rng = np.random.default_rng(int.from_bytes(key[:8], "little"))
331
+ scale = [0.0, 1.0, 4.0, 12.0, 40.0][key[8] % 5]
332
+ lg = (rng.standard_normal(self.n_vocab) * scale).astype(np.float32)
333
+ lg[:256] += 6.0 # bytes are likelier than the unused ids, as a real tokenizer would be
334
+ lg[key[9]] += 20.0 # one strong favourite per context
335
+ return lg
336
+
337
+ def reset(self, prime_ids):
338
+ self.ctx = list(prime_ids); return self._logits()
339
+
340
+ def step(self, tok):
341
+ self.ctx.append(int(tok)); return self._logits()
342
+
343
+
344
+ def ideal_bits(logits, sym):
345
+ """-log2 softmax(logits)[sym] in float64 — reporting only, never on the coding path"""
346
+ u = np.asarray(logits, np.float64); u = u - u.max(); return float(np.log2(np.exp(u).sum()) - u[sym] / np.log(2))
347
+
348
+
349
+ if __name__ == "__main__":
350
+ m = MockModel()
351
+ for t in ["Checking in. How goes it? Sound off.", "ok", "Line one.\n\nLine two, with a double space.\n"]:
352
+ f = crunch(t, m, tail=["earlier message"]); back = uncrunch(f, m, tails=[["earlier message"]])
353
+ print(f"{len(t):4d} chars -> {len(f):4d} B exact={back == t}")