2026-09-23.
iapp/OpenThai-SystemOneuses a Qwen3.5-0.8B-Base text tower continued-pretrained on Thai and a biased slot head in place of the LM head. The conversion preserves the author’s single-question probability readout. int8lin ships; fp16 is the reference. Card:models/openthai-systemone/README.md.
The conversion run is
~/code/codex-conversions/2026-09-23/openthai-systemone-coreai/; results/, logs/ and
scripts/ paths below are relative to that run. Fixture and gate links point to files staged
for this zoo. Source revision and license are recorded in results/download.json; checkpoint
configuration and model structure are in results/hf-config-premise.json,
results/oracle_model.json and results/loader_int8lin.json.
The source is iapp/OpenThai-SystemOne at
f3709948b5e3cc9606a57e74ba62b7a639d17dd3, Apache-2.0. Its root type is
openthai_systemone; text_config describes the Qwen3.5 text tower. The checkpoint has
24 layers: 18 linear-attention and 6 full-attention layers. Its hidden size is 1,024, its
embedding is [248339, 1024], and the head tensors are slot_head.weight [256, 1024] plus
slot_head.bias [256]. There is no lm_head tensor. The graph emits [1, 1, 256] logits,
uses decode-only S=1 calls and has a 4,096-token context. Evidence:
results/hf-config-premise.json, results/loader_{fp16,int8lin}.json,
results/export_{fp16,int8lin}.json and results/export-review.md.
This is a decision readout: Choice, Score and Noul use slot indices at <|ts_answer|>.
The tokenizer’s answer id is 248082; the abstain slot is 255. The input vocabulary has
248,339 entries, including 295 added tokens. language.vocab_size = 256 in bundle metadata
is deliberately the output width: the sequential engine sizes its logits buffer from
that field. Tokenizer ids remain in the full input range. Evidence: both export manifests,
their embedded metadata, and
fixtures-openthai-systemone.json.
The baseline was zoo 082fe55’s conversion/export_qwen3_5_decode_pipelined.py; the frozen
authoring tree was fork commit 397b337. Both original and adapted hashes, and the exact
diff, are in results/env.json and results/exporter.diff. The zoo exporter is
conversion/export_openthai_systemone_decode_pipelined.py.
model.*, not
model.language_model.*. The exporter builds a config from the snapshot’s text_config
object, avoiding the root remote AutoConfig. Its local safetensors loop retains the
frozen loader’s construction and key mapping, with the changed prefix.vocab_size = 248339 and tie_word_embeddings = false in
the text config. The saved tokenizer retains all 295 added tokens. Random trace input
ids are bounded by the input config, not the metadata’s output width.lm_head.weight is expected; the frozen loader’s final
meta-parameter check would otherwise reject the checkpoint. The exporter installs
nn.Linear(1024, 256, bias=True) before that check and copies the slot tensors into it.
int8lin uses the existing block-32 linear quantizer; its .*lm_head$ exclusion keeps
the slot head fp16, as it keeps embeddings, conv1d and norms fp16. The full fp16 mode
uses the same graph. Temperature and masking are host readout operations.The graph’s layer, state and loop-free decode code is unchanged. Both exports logged
“loop-free single-step enabled on 18 linear layers.” Evidence: results/export-review.md,
results/loader_{fp16,int8lin}.json, logs/export-fp16.log and logs/export-int8lin.log.
The separate zoo exporter downloads the pinned source itself and incorporates the run’s
loader wrapper. Its int8lin re-export completed in 47.482 s on CPU, with peak child RSS
4,484,366,336 bytes. The new main.mlirb is 1,039,655,086 bytes, SHA-256
4169ffc381318694e15c03d7d8c1db080d11af8034c9f7eb6db82f64a50f7fa5, versus the original
1,039,655,099 bytes, SHA-256
0e58d55b8740710eee7bea8117aa8625a1061c142a63e03a1a79d47bf71ed9eb. The 13-byte
difference is within the round’s permitted IR serialization variation; byte identity was
not expected. Metadata differs only in compilation.date: the original records
2026-09-22T18:37:15.022124+00:00, and the re-export records
2026-09-22T19:31:22.977388+00:00. All other fields are equal. The unchanged zoo _bundle.py
writes the current compilation time, so literal metadata equality is not met; both raw
files remain intact. Evidence: results/round2/reexport.json and
logs/reexport-int8lin-round2.log.
The checkpoint tensor, evaluated as fp32 exp(log_temperature), is ordered Choice, Score,
Noul. Its values are 1.058534, 1.043141, 1.006767, respectively. The source card’s v0.3
line quotes Choice 1.055, Score 1.008, Noul 1.047. The exporter and the fixture use the
tensor values, with full fp32 precision retained in metadata and row temperatures. Evidence:
results/oracle_model.json, results/oracle_contract_notes.md, the fixture’s
temperature_exact_by_type, and pinned source README.md:207.
For k options, the author’s model divides its 256 raw slot logits by the question type’s
temperature, masks slots at or above k except slot 255, and applies softmax. The client then
normalizes the first k probabilities; its Choice/Score _decode_named path performs a
second fp32 normalization. There is no rounding in the pinned client. Confidence is one
minus entropy normalized by log(k). Only Choice exposes abstain; Score exposes probabilities
and confidence, and Noul exposes only the probability of yes. Evidence:
results/oracle_contract_notes.md, the fixture’s api_contract_note, and the unchanged
author client at GitHub revision 5d04bcc (results/oracle_sources.json records its hash).
The fixture contains 18 requests and 50 independent rows: 48 kit rows (24 Choice,
14 Noul, 10 Score) plus Choice rows with 40 and 255 options. All use permutations=1.
Each author-encoded row ends with answer id 248082 and a newline, so the answer slot is
len(ids_full)-2. Truncating at the answer token changes the fp32 logits by at most
0.000018596649, below the 0.0001 causal-prefix tolerance. Independent-row assembly
equals the author’s single-question API exactly for 18/18 requests, comprising
50/50 question calls. Evidence: the fixture’s summary, rows and api_assembly.
The author’s one-pass API appends several questions to one causal sequence. Its later
questions can see earlier question text; the bundle and the kit instead answer one question
per row. On the same fixture requests the two API forms differ by a maximum
|Δp| = 0.3754529953. This is an API-layout finding, not a conversion failure. The one-pass
answers and each independent answer are retained beside one another in the fixture’s
api_assembly; the maximum is in summary.one_pass_max_abs_delta_p.
The oracle runs the author’s source files and client on CPU in fp32, with reference
gated-delta kernels. The working environment is Python 3.11.13, torch 2.9.0,
transformers 5.17.0, safetensors 0.8.0, huggingface_hub 1.32.0, numpy 2.3.5,
tokenizers 0.23.2, and pydantic 2.13.5. The original request for safetensors 0.7.0
and huggingface_hub 0.36.2 could not resolve with transformers 5.17.0, which requires
safetensors ≥0.8.0 and huggingface_hub ≥1.5.0,<2. Only the oracle environment changed;
no flash-linear-attention or causal-conv1d package was installed. Evidence:
results/env.json, results/oracle_env.json, logs/oracle-resolver-original-pins.log and
conversion/slot/oracle_slot.py.
The export/runtime environment remains coreai-core 1.0.0b2, coreai-torch 0.4.1,
coreai-opt 0.2.1, torch 2.9.0, transformers 4.57.6, safetensors 0.7.0,
huggingface_hub 0.36.2, numpy 2.3.5, tokenizers 0.22.2, and accelerate 1.15.0.
The coreai_models import resolves inside the run’s frozen src/python tree, from fork
commit 397b337; no shared working tree was imported. Evidence: results/env.json.
Both bundles were AOT-compiled for h16c GPU, loaded with
SpecializationOptions.default(), and stepped with four fresh zero states per row and full
position_ids on every S=1 call. Processes hold at most 30 rows; each wide row has its
own process. The first row is repeated at the end for a state-reset proof. Evidence:
readout gate,
fp16 transcript,
int8lin transcript.
| Python runtime vs author’s fp32 oracle | fp16 | int8lin |
|---|---|---|
| option argmax | 50/50 | 50/50 |
| margin ≥ 0.02 argmax | 49/49 | 49/49 |
| max |Δp| | 0.0050586462 | 0.0208126605 |
| mean of row mean |Δp| | 0.0002248096 | 0.0006594286 |
| max |Δabstain| | 0.0164879188 | 0.0188367814 |
| max absolute raw-logit error | 0.1001068950 | 0.1723060608 |
| repeated first row logits | identical | identical |
All logits were finite and nonconstant. The gate requires option argmax agreement where the
oracle margin is ≥0.02 and exact reset behavior; it records probability and abstain
errors. The only near-tie row is r18-slot, margin 0.0097392052; both bundles agree on
it. The worst int8lin probability row, r05-dry, is a two-option Noul row with margin
0.0616810024, outside that near-tie category. Evidence: both readout transcripts and the
fixture. The accepted round-one records are results/readout_{fp16,int8lin}.json.
The Release Swift engine check uses the same input ids and takes one greedy token from all
256 raw slot logits. Its expected string is the saved tokenizer’s decode of the same
bundle’s Python raw256_argmax; it is not the option argmax. Both pipelined and sequential
engines match 50/50 per bundle, 200/200 calls total. Some raw slot indices decode to
a replacement character or whitespace; the parser preserves the string rather than stripping
it. Evidence: the
fp16 engine transcript,
int8lin engine transcript
and results/engine_argmax_{fp16,int8lin}.json. Tools were built Release from tag
0.2.4-zoo, commit f7a75ec, with Xcode 27.0 (27A266a); evidence:
results/swift-build.json.
The decider runtime record
documents the Python JIT failure on 26A428 (MTL4CommandQueueErrorDomain error 1, zero
logits) and an IOSurface leak near 25,000 calls per process. This port used AOT from the
start and did not repeat that JIT experiment. Read-only MPSGraph scratch measurements were
0 B before and after all 10 round-one runtime sessions; nothing outside the run was
deleted. Evidence: results/readout_{fp16,int8lin}.json session records.
Measured through coreai-kit, the first tokenizer parity run matched 23/50 rows because
swift-transformers applies the tokenizer’s Split regex through Foundation string search,
whose match ranges snap to grapheme clusters. Thai letter runs retained their combining marks
and BPE merged differently: กล่อง became one token where the reference has กล + ่อง.
The kit now cuts each text segment with the same regex through ICU on UTF-16, then encodes
each piece. Both bundles then match tokens 50/50, slots 50/50 and option argmax
50/50, including the 40- and 255-option rows. This is a kit/swift-transformers change;
the bundle tokenizer was unchanged. Evidence:
measurements-coreai-kit.json,
supplied by the supervisor from its kit worktree and not re-derived in this run.
Device and build: Apple M4 Max GPU, macOS 27.0 (26A428), Xcode 27.0 (27A266a).
Round-one correctness work shared the GPU and supplies no throughput claim. The separate
int8lin benchmark ran with no other Core AI, Python or Swift engine job in the mandated
before/after process snapshots (contended: false). It used p=128, g=256,
two launches × three trials per engine, COREAI_CHUNK_THRESHOLD=1. The frozen benchmark
did not accept --inference-engine-variant; a copy of its source was patched only to expose
the option and pass EngineOptions.variant, then built Release. The original binaries were
left intact. Evidence: results/round2/benchmark-engine-option.patch,
results/round2/benchmark-build.json, results/ps-before.txt, results/ps-after.txt, and
llm-benchmark.json.
| int8lin engine | prefill proxy median (range), tok/s | decode median (range), tok/s | load per launch, seconds |
|---|---|---|---|
| coreai-pipelined | 252.690 (243.793–258.566) | 250.776 (244.516–253.778) | 1.414586 / 0.167014 |
| coreai-sequential | 197.143 (196.114–201.258) | 194.829 (192.644–197.660) | 0.172054 / 0.165567 |
All trial and load values are in llm-benchmark.json. This is a prefill-rate proxy for
the S=1 graph: the benchmark’s synthetic input ids come from the metadata’s 256-wide
output range, and synthetic generation does not measure a decision request. Load is one
measurement per launch and excludes warmup.
The supervisor’s measured through coreai-kit results use the sequential engine and the
kit tokenizer. int8lin max/mean |Δp| = 0.0226 / 0.0009, max |Δabstain| = 0.0222;
fp16 0.0051 / 0.0003, max |Δabstain| = 0.0165. int8lin median wall time was
354 ms/question over the 50 fixture rows, two to three questions per state; the 255-option,
1,449-token row took 7.2 s. A three-question Thai ticket took 351 / 316 / 429 ms for its
57-, 63- and 83-token rows, with 0 prefix tokens reused: the recurrent hybrid cannot rewind
mid-sequence, so every row is prefilled from its first token. (An earlier figure of 170 / 40 / 65 ms
with 79–81 reused tokens was measured on minicpm5-2b by mistake — the kit’s ask command had
ignored --bundle — and is withdrawn.) Evidence:
measurements-coreai-kit.json.
SemIf authored144, measured through coreai-kit on the Mac GPU with int8lin, gave 109/144
raw and 0.7249 mean family balanced accuracy. The 144 English rows, three options
each, use SemIf’s gold labels and unchanged benchmarks/evaluate.py. The kit README’s values
on the same rows and evaluator are MiniCPM5-2B int8 0.681 and Qwen3.5-4B int8 0.821.
Evidence: measurements-coreai-kit.json; these supervisor measurements were not repeated.
There is no phone gate for this port.
int8lin main.mlirb is 1,039,655,099 bytes, SHA-256
0e58d55b8740710eee7bea8117aa8625a1061c142a63e03a1a79d47bf71ed9eb; fp16 is
1,506,078,533 bytes, SHA-256
f811a29cb5c2e6ff5ab5834e4ad288d04aa22c06791b4efcd1abb9c629a18e08.
Whole LanguageBundles are 1,068,353,811 and 1,534,777,239 bytes, respectively.
Evidence: results/export_{int8lin,fp16}.json and
recipe.toml.
Hub staging retains the source config.json verbatim and the two original bundles. The
source snapshot has no license file; the staged LICENSE is the canonical Apache-2.0 text
from https://www.apache.org/licenses/LICENSE-2.0.txt, not the frozen Swift tree’s BSD
license. Evidence: results/round2/license-source.json, staging/MANIFEST.json and
staging/UPLOAD.md. Publication and shared-tree folding are owner actions.