Repository navigation
Releases
arcint 1.6.1
Gemma 4 on the fast path on Intel Arc, a correctness fix for upstream's f16 decode matvec, and NVIDIA's looped Dory1.0-4B. The full record is in CHANGELOG.md.
- Gemma 4 E4B on the fast path at both KV types (patches 0106, 0110). Flash attention at head size 512 on Intel, which ggml declines on every Intel device. On the B60 at a 6.5k-token prompt, against 1.6.0: q8_0:q4_0 KV prefill 176-246 -> 1,905-2,053 t/s, decode 65 -> 75.5 t/s; f16 KV prefill about 1,285 -> 2,113-2,381 t/s, decode 79 -> 81. F32 weights are multiplied through an f16 copy in prompts: another +3.7 to +3.9 % prefill, decode unchanged (
GGML_OPENCL_F32W_F16). - A correctness fix in upstream's f16 one-column matvec (patch 0107).
kernel_mul_mat_f16_f32_l4_x8staged its vector one float4 a sub-group lane, which fills only half of it on 32-wide sub-groups. Gemma 4 with an f16 KV cache and flash attention off decoded wrong: KL against a CPU reference 3.37 nats at batch 1, a thought that fell into a loop. Fixed, KL 0.0044 on both cards. It applies to any model whose decode reaches that kernel. - Dory1.0-4B is admitted (patches 0108, 0109): NVIDIA's looped nGPT, ported from llama.cpp pull request 30306, with the OpenCL copy from BF16 its scale vectors need.
--llama-dory-loopsand--llama-dory-kvset the loop count and the recurrent KV layout, checked at start. B60: 36.4 t/s at 2 loops, 22.0 at 4; A770: 23.7 and 14.3; KL against the CPU reference 0.009-0.016.
Release gate against 1.6.0, same window (measured-here): the agent passes, its 255k needle right. The coder and Flash-Next fail on the spread rule only, by the operator's acceptance: the coder's means equal with identical texts in every run and the acceptance task 10/10 x3; Flash-Next's prefill equal and its decode answers differing in every run of both arms. No unit reports a new downgrade.
The attached .deb is built from this tag's tarball for Debian 13 (trixie) on x86_64 and needs Intel's compute runtime.
arcint 1.6.0 — prompt experts on int8 DPAS, the head file, card sampling
This is not the current release. The current one is here, with its changes in CHANGELOG.md.
Prompt expert products on int8 DPAS on the Arc A770, a head file without output.weight, two more models admitted, sampling defaults from the model card. The full record is in CHANGELOG.md. This is the first GitHub release since 1.2.6; the changelog covers the versions between.
- Prefill +13.8 % at 41k on the A770 for the Qwen3.6 coder model (patches 0102-0104): the prompt's expert products run on int8 DPAS with no local memory, and a sub-group owns two sets of weight rows on one activation read. 845 -> 962 t/s; at 4,096 tokens 1,915 -> 2,150 t/s. The B60 does not change.
- An MTP head file may carry no
output.weight(patch 0105,tools/coder_mtp_gguf.py). That coder model's draft head had been pruned along with its trunk, and the base model's unpruned head on the pruned trunk gives +3.3 % under deployed sampling and +2.2 % at a 93.7k prompt, with no training. The head file is not shipped, and the method is in the repository. - Gemma 4 E4B and Granite 4.2 8B are admitted by architecture and geometry. They are not yet served by a unit and have no card numbers.
- Sampling defaults come from the model card in the GGUF;
/propsreports provenancecard. A unit that passes no sampling flags moves to its card's row: the shipped Flash-Next and TTS units pin the row they have served at.--llama-checkpoints Nabove 0 is now refused on a model with no recurrent memory. docs/concepts.md: what arcint implements for Intel Arc inference, what it gained on which card, where the code is, and what was tried and not adopted. The branchflattenedholds the pinned llama.cpp with the released patch series applied, so the code reads as files.
Release gate against 1.5.4, same window (measured-here): Flash-Next and the agent pass. The coder fails one decode cell, accepted by the operator: decode a +5.6 %, b -6 %, c equal, with different texts and a verify's cost level; 12 answers 82.66 -> 81.95 t/s; the acceptance task 10/10 x3. KL against the file's CPU reference .00585 -> .00803, inside the bar. 0105 was measured in its own windows, not in that gate.
The attached .deb is built from this tag's tarball for Debian 13 (trixie) on x86_64 and needs Intel's compute runtime.
arcint 1.2.6 — the loop guard reads the tokens
This is not the current release. The current one is here, with its changes in CHANGELOG.md.
The loop guard's last 1.2.5 residual fixed, and the release A/B reads decode
rates on the majority answer. Every switch stays off by default; with none
set a request is served as in 1.2.5.
- A stretch's budget point judges recovery on the tokens — 1.2.5's
residual: a budget point the door reaches by 64-token stretches (no counted
line) decided on the last counted line share, so a loop the line detector
cannot count (lines of 20 characters or less, or no newline) after a varied
stretch was lifted rather than escalated. Such a point now reads a token
repeat share: the share of 8-token grams in the stretch that occurred
earlier within the last 2,048 generated ids, recovered when it is below the
low mark. A point reached by a counted line keeps the line share; every
decision, the counted line's too, starts the next span, so a counted-line
loop's repeats are not carried into a later stretch's share. The
tracker records only while the door is open; with the door shut or the
guard off nothing is computed. Loops with a period up to the window
(2,048 ids) are caught. Residual (code): a short-line loop with one
varying token per cycle (a counter in lines of 20 characters or less) has
few exact 8-gram repeats and is still read as recovered; a long stretch of
legitimately repetitive text without counted lines (a table of short
lines, a long newline-free JSON) can read as looping once the door is
open. (code) - The release A/B reads decode on the majority answer —
tests/acceptance/ab.pygates a decode rate on the runs whose answer has
the majority md5 across both arms, and lists the other runs with their md5,
tokens, draft acceptance, verifies and rate; with fewer than two such runs
in an arm, or a tie, every run is gated, as before (operator, 2026-10-25).
Every decode answer of the served check runs to its 300-token cap, so a run
with another answer is another workload: its draft acceptance differs under
speculation (measured-here, the coder's and Flash-Next's served logs).
cells/served_check.pyprints anACCEPTANCE-DECODEline per answer for
it. A fixed-length decode arm was considered and dropped: the answers are
already fixed in length. Read this way, 1.2.5's first Flash-Next window
passes (its one slow run was the one with another answer) and its second
still fails (slower on the same answer).
Runtime: llama.cpp 3e4dfa8 with contrib/llama.cpp/patches (60
patches), unchanged from 1.2.5; dependencies unchanged. arcint-stt (1.2.0)
and arcint-imggen (1.1.1) are unchanged.
Release check (measured-here, 2026-10-25, the pre-tag package): the
device-free suite 340 cases and ctest -L unit 6/6; the branch's mutations
red. The Flash-Next speed A/B against 1.2.5 (the B60, IQ3_XXS with the served
check's flags, 4 rounds), the first under the majority reading, failed the
gate as written on decode spread while faster: on the majority answer (2 old
runs, 4 new) 38.20 against 38.33 t/s, the new arm's spread 1.83 % (its
fastest run, 38.8 t/s) against the allowance of 0.52 % from the two old runs;
prefill equal (618.5 / 618.7 t/s). On the same answer the draft acceptance
still varies 65.5-67.6 % between runs, so two runs do not measure an arm's
spread. The operator accepted the result (2026-10-25); a spread rule that
fails only a slower candidate, and a minimum of majority runs per arm, are
on the 1.2.7 list. Not run: the coder's and the agent's A/B (their paths
change only with the loop guard's door open, which the agent never opens;
code); the token-share rule is unit-tested only.
Published (2026-10-25): arcint 1.2.6-1 built by its recipe from the
v1.2.6 tag tarball (sha256 efc0e4fa…) and published to the trixie
repository (index verified); installed from the pool on the card host, where
the coder (--loop-guard ladder) and the agent run it and answer the
stop-string check. arcint-stt stays at 1.2.0-1, arcint-imggen at 1.1.1-1.
arcint 1.2.5 — fixes and clean-up
This is not the current release. The current one is here, with its changes in CHANGELOG.md.
A fixes and clean-up release: the 1.2.4 release review's three loop-guard
items, the round-1 follow-ups on the door's budget, and seven review leftovers
from 1.2.2/1.2.3 on the serving path. Every switch stays off by default.
With none set two things still change for clients: a streamed answer no
longer starts with the line breaks that follow the think block's closer (as
the non-streaming answer never did), and a /v1/completions prompt that ends
inside an open think block starts the turn in the block, as chat does.
- The door's budget ends every open door (ladder) — 1.2.4's ladder
escalated only on a line at or above the threshold, so a door opened at
detection whose share then fell below it held its masks to the token cap.
The ladder now decides at two budget points,door_gracecounted lines
after the detection (the nudge point) andgracemore (the slap point): a
share still at or above the low mark (threshold - 0.1) nudges, then slaps,
as before; a share below it means the model recovered, and the door lifts
its masks (latched) instead, with no nudge or slap, so a recovered turn
keeps its reasoning (operator, 2026-10-25). After a lift the door stays
shut and a renewed loop followsladder-nodoor's rule: a turn lifted at the
nudge point is nudged at the new detection and slapped on over lines; a turn
lifted at the slap point was already past its nudge, so the new detection
slaps at once. The answer stays watched in a turn whose door opened (also
after a lift), andself_closescounts only a close while a mask was
active. A lift is logged, with no counter of its own.ladder-nodoor,
detect,nudgeandslapkeep 1.2.3's semantics. (code) - Known residual (
code, from the release review): a budget point reached by
the 64-token stretches (no counted line) decides on the last counted
share. A loop the detector cannot count (lines of 20 characters or less, or
no newline) after a varied stretch can therefore be lifted rather than
escalated and run on to the token cap, with the masks off, so the model
can still end the turn. 1.2.4 held the masks in that case. - A ladder whose closer has no id lifts at the slap point — on a
vocabulary whose closer is text the slap has nothing to force; the ladder
now lifts the door there instead of holding the masks. No served model is
affected (their closers are single tokens). (code) - One opener check for chat and completions — whether a rendered or raw
prompt ends inside an open think block is decided by one helper on the
shared tag table (every family when the artifact sets the extended tags,
the default family otherwise); no literal tag is left in the handlers, and
the chat path sets the extended flag before the check. The served templates'
result is unchanged, which a test pins. (code) doormode's budget —dooris the mask alone and has no nudge or slap,
so a mask could hold a turn to the cap on its own. Afterdoor_grace + gracecounted lines without a close, the door lifts its masks (the
multi-newline ids and the turn-ending ids it added) and says so once in the
log; the turn continues unguarded, as with the switch off. No new response
key, and a later detection does not reopen the lifted mask. (code)- Door counters measure the sampled candidate set —
door_maskedand
door_eog_maskedcount a step when a masked id would have been a candidate
of the draw.candidateis now the set the draw actually samples from, after
the row's truncation (top-k, top-p, min-p), for everytop_kvalue: at
top_k0 a top-p cutoff could exclude a finite masked logit and the old
code still counted it against the whole row. Observability only: the draw is
byte-identical, which a fixed-seed test pins. (code) - The door's budget advances by tokens — the budget counted ended lines
only.loop_observe_never ends a line without a newline and past its line
cap it truncates and keeps the line open, so a model that switched to
newline-free text never advanced the budget and the masks could again hold
the turn to the cap. Each stretch of 64 generated tokens with no counted
line now counts as one line-equivalent for the door's budget only — never
for the detector's window. 64 is conservative against the 15–25 tokens a
counted line costs, so a turn with lines keeps the line budget exactly.
(code) - A lifted door is not reopened — in
doormode the budget's lift is
latched: a later detection leaves the masks off and the door counters do not
grow (the round-1 mutant that deleted the latch survived; the test now reds
with it deleted). The door's grace also starts at its opening, so a reopened
door cannot inherit a spent budget. (code) - A stop string splitting a tag keeps the tag's id — a stop string that
begins with the last bytes of the closer's text splits the closer's piece;
the released first part carries no id (ordinary text), and the id rides on
the part that stays held. Before, the held tail reached the tracker with id
-1, so the block stayed open and the answer came back as reasoning (1.2.2's
residual). Residual: the closer's released first part is text, so it shows at
the end ofreasoning_content; the block closes and the answer is clean.
Avoiding it needs a stop matcher that knows the tags. (code) - Streaming drops the line breaks after the closer — the non-streaming
split (the reference) dropped them; the streamer kept them, so the two paths
disagreed on the same token stream. The streamer now drops leading line
breaks at the content's start, and only there: a reply with no block keeps
its content as generated. (code) - The positional id fallback refuses a lossy pairing — the flattened
open/close id lists are id sets; the backend drops a role's -1 entry, so an
id's index stops being its tag pair's index and zipping the two lists paired
an opener with another family's closer. The zip now only builds when every
pair contributed both ids, otherwise no pairing is built; the per-pair field
is the lossless source. (code) - The detector and a closer in one piece — on text-fallback vocabularies
the detector ran before the closer check: a piece that carried a counted
line and then the closer tripped the ladder and closed in the same call, so
the nudge landed in the answer. Only the bytes before a completing closer go
to the detector, and a detection or escalation on that line is counted but
does not act. (code) - The slap forces the open family's closer — its fallback forced the
flattened first close id, i.e. another tag family's closer, when an extended
pair had been opened by text. The slap now forces the closer of the family
that is open, or none when that family's closer is text. (code) /v1/completionsgets the chat path's start state — a raw prompt that
ends inside an open block starts the turn in the block, so the masks and the
loop guard watch it from the first sample; before, completions always started
undecided and such a turn ran unwatched. (code)- Dead branch and log wording —
loop_on_detect_'s content branch was
dead (its only caller passes false; the content phase aborts elsewhere) and
is gone with its parameter. The loop-guard log lines said "lane:" without a
lane number; the sampler does not know the lane, so the word is gone.
(code) - Clean-up, no behaviour change — the dead
inclusiveparameter of the
two door counters removed, the unread id dropped from the stored masked
logits, one helper for the closer-strip warn/latch, the counter list in
handlers.cpp in one place, then == 0draw spends the counters like the
greedy path, andloop_guard_door_ladderreads the door branches instead of
loop_guard_is_laddercarrying a second invisible condition. (code) - The
loop_guardobject on the wire —loop_guard_jsonreturns a plain
jsoninstead of anordered_json: the response body is a plainjson, so
the keys already landed alphabetically and the "last key" test checked a
property no client saw. The order test is replaced by one on the object as
it goes out: the serialized non-streaming response and the stream's finish
frame each carry all seven keys with their values. No change for clients.
(code) - Tools —
tools/clean_turns_gate.pysends each arm's temperature (it
only printed it, so both arms ran at the unit's default row). The speaker
similarity tooltools/tts_voice_sim.pyhas a clean-room ECAPA-TDNN head,
tools/speaker_head.py, written from a behavioural specification and the
paper; the tool is Apache-2.0 again (it carried a CC BY-SA 3.0
transcription), and its gate number is unchanged: 0.598038 over the 46
clips, the cross pair 0.262419 (measured-here).THIRD_PARTY.mdrecords
both the recipe and the checkpoint's licence.
Runtime: llama.cpp 3e4dfa8 with contrib/llama.cpp/patches (60
patches), unchanged from 1.2.4; dependencies unchanged. arcint-stt (1.2.0)
and arcint-imggen (1.1.1) are unchanged.
Release check (measured-here, 2026-10-25, the pre-tag package): the
device-free suite 329 cases and ctest -L unit 6/6, the package build's tests;
the branch's mutations red (each new test against its named mutation). The
Flash-Next speed A/B against 1.2.4 (the B60, IQ3_XXS with the served check's
flags, 4 rounds, twice) failed the gate as written, both times on decode:
first on spread (the one slow run, 37.3 t/s, was the one run with a
different answer), then on speed, 38.52 against 38.05 t/s (38.50 against
38.10 on the runs with the same answer); prefill equal in both (618.4 /
618.8 and 618.3 / 618.6 t/s). T...
arcint 1.2.4 — the loop guard's door
This is not the current release. The current one is here, with its changes in CHANGELOG.md.
The loop guard gains a door rung; every switch stays off by default, and
with none set a request is served as in 1.2.3. The coder unit's ladder now
includes the door (operator, 2026-10-24).
- Door rung —
--loop-guard door|ladder|ladder-nodoor, with
--loop-guard-door-grace(20) and the same three names in the request
field. At detection, while the think block is open, the sampler masks every
vocabulary id whose decoded piece consists only of newline characters and
has two or more of them; the single-newline token stays legal. Nothing is
injected and no token is forced: the model draws normally and may close by
its own draw. The order is detect → door → nudge → slap → abort; the
door opens at detection and stays open through the nudge (after the door
grace) and the slap, and is gone once the block closes.dooris the mask
alone (never nudge or slap);ladderincludes the door;ladder-nodooris
1.2.3's ladder exactly (nudge at detection, no mask), so one binary can
compare both. The door ids are scanned once at model load from the
vocabulary the backend owns. In a speculative lane the mask applies inside
sample(), so a draft the mask forbids is a mismatch and drops the rest.
The response'sloop_guardobject and the decode line countself_closes
(the model's own close while the mask was open, not a slap) and
door_masked(draws at which a masked token sat in the row's candidate
set)./propsreports the mode anddoor_grace. - Answer watched after a door self-close — after a turn in which the door
opened, the content phase feeds the detector too, and a looping answer ends
onfinish_reasonloopexactly as after a slap. The answer starts from a
clean window at the block's close, so reasoning lines (including the loop
that opened the door) do not count against it; an answer that restates a
line or two of its reasoning once stays normal. A turn whose door never
opened keeps 1.2.3's unwatched answer, anddetect/nudge/slapnever
open the door. - No end of turn inside the open block — while the door is open and the
think block is open, the turn-ending ids are illegal next tokens, applied to
the logits in the same place as the newline mask (before penalties and the
draw); the closer is never masked, andignore_eosturns it off. With
think_eog_maskset the mask is the same one and applied once. The
loop_guardobject and the decode line adddoor_eog_masked, last after
door_masked: draws at which an end-of-generation id was a finite candidate
the door removed. - The door on the card (
measured-here, the coder on the A770, its
model card's thinking row, seed 1, 64k cap). On the 9 tasks that had hit
the cap in the screening, with the door in the ladder all 7 looping runs
ended fully correct; all 7 closed by the model's own draw, 5 by the door
alone within seconds of opening and 2 after the nudge, and no forced token
was needed in the whole round. The same set without the door needed the
slap 4 times. README, "New: loop guard for small models". laddernow includes the door. On a unit that setladderin 1.2.3 a
detection now (a) waits the door grace (20 counted lines) before the nudge
instead of nudging at once, (b) masks the multi-newline tokens until the
block closes, (c) withthink_eog_maskoff also masks the turn-ending ids
in the open block, and (d) watches the answer after the model's own close,
so a turn that 1.2.3 recovered with the nudge and an unwatched answer can
now end onloop.ladder-nodoorrestores 1.2.3's ladder.- Known residual (
code, from the release review, not observed): the nudge
and the slap fire only on a line where the repeat share is at or above the
threshold. If the door is open withthink_eog_maskoff and the share then
falls below it without the model closing the block, nothing escalates and
the turn-ending ids stay masked, so the turn runs to the token cap where
1.2.3 could have ended it. The countersdoor_maskedanddoor_eog_masked
count against the whole row whentop_kis 0 (observability only).
Runtime: llama.cpp 3e4dfa8 with contrib/llama.cpp/patches (60
patches), unchanged from 1.2.3; dependencies unchanged. arcint-stt (1.2.0)
and arcint-imggen (1.1.1) are unchanged.
Release check (2026-10-25): the device-free suite 305 cases and ctest -L unit 6/6 on the tagged tree, the package build's own tests; the
mutations of the gap fixes red (measured-here, on the branch). The card
numbers above and the answer watch's offline replay (0 of 391 recorded
non-loop answers would abort) are from dev builds of the same mechanism. Not
run on the release candidate, by the operator's decision: the same-window
speed A/B against 1.2.3 and the acceptance task; every switch off is 1.2.3's
path (code).
Published (2026-10-25): arcint 1.2.4-1 built by its recipe from the
v1.2.4 tag tarball (sha256 a12aabde…) and published to the trixie
repository (index verified); installed from the pool on the card host, where
the coder runs it with --loop-guard ladder (now with the door) and the
agent with the switches as before, each answering the stop-string check.
arcint-stt stays at 1.2.0-1, arcint-imggen at 1.1.1-1.
arcint 1.2.3 — a loop guard for small models
This is not the current release. The current one is here, with its changes in CHANGELOG.md.
A loop guard for small models and two sampling rows by phase, all behind
switches that are off by default; with none set, a request is served as
in 1.2.2. The coder unit turns on the loop guard and the non-thinking row
(operator, 2026-10-24).
- Loop guard —
--loop-guard off|detect|nudge|slap|ladder(request
fieldloop_guard;/propssampler_defaults.loop_guard). A detector on
the reasoning's lines (stripped, longer than 20 characters) keeps the share
of repeated lines over the newest--loop-guard-window200; a line repeats
when it equals an earlier one or, under--loop-guard-match near(the
default;exactthe alternative), has a character-3-gram Jaccard of at
least 0.8 with one of the last 400 distinct lines. At
--loop-guard-threshold0.8 (hysteresis 0.7) the ladder climbs: a fixed
sentence is injected into the reasoning once (nudge); after
--loop-guard-grace40 more looping lines the think block's closer is
forced by token id (slap); a looping answer after the slap ends the turn
withfinish_reasonloop(abort). A forced token ends a speculative
verify walk at its position. The response'sloop_guardobject and the
decode line count detections, nudges, slaps and aborts. The test and what
it does not heal: README, "New: loop guard for small models". - Non-thinking row —
--nothink-temp,--nothink-top-p,
--nothink-top-k,--nothink-min-p,--nothink-presence-penalty: a
request that resolves to thinking off starts from this row instead of the
thinking row; the request's own fields still win;/propsreports it.
On the coder the card's non-thinking row (0.7 / 0.8 / 20, presence 0 as
measured) lifted the sampled acceptance task from a mean of 9.056 (7 of 90
runs at 0/10) to 9.922 (none of 90 at 0), the shortfall 1.2.2's release
check named. - Answer row —
--answer-temp,--answer-top-p,--answer-top-k,
--answer-min-p,--answer-presence-penalty,
--answer-frequency-penalty: in a thinking turn, the sampler switches to
this row once the think block closes (the presence and frequency penalties
count the answer only). Measured without effect on the coder after a full
reasoning (the answer follows the reasoning); shipped as a switch, not set
on any unit. - The loop guard's nudge no longer leaves its last piece at the start of the
next reasoning line the detector judges.
Runtime: llama.cpp 3e4dfa8 with contrib/llama.cpp/patches (60
patches), unchanged from 1.2.2; dependencies unchanged. arcint-stt (1.2.0)
and arcint-imggen (1.1.1) are unchanged.
Release check (measured-here, 2026-10-24, the pre-tag package): the
device-free suite 286 cases and ctest -L unit 6/6, the package build green.
Same-window speed A/B against 1.2.2 with every switch off: the coder (A770)
and the agent (B60) pass with byte-identical texts. On the coder with its
1.2.3 flags (the ladder, the non-thinking row): the acceptance task 5/5 at
10/10 greedy and 30/30 at 10/10 at temperature 0.7; the agent 3/3 at 10/10.
The loop guard's false-positive run completed: 21 of 21 tasks, 19 fully
correct, 1 wrong with no guard event, 1 ended on loop after a real loop in
the reasoning and again in the answer. Not run: Flash-Next's A/B (its sampler
path is the agent's, every switch off; code).
Published (2026-10-24): arcint 1.2.3-1 built by its recipe from the
v1.2.3 tag tarball (sha256 bf0f02f4…) and published to the trixie
repository (index verified); installed from the pool on the card host. The
agent runs it with the switches off (the stop-string check, the acceptance
task 10/10); the coder's unit carries --loop-guard ladder and the
non-thinking row and takes them at its next start. arcint-stt stays at
1.2.0-1, arcint-imggen at 1.1.1-1.
arcint 1.2.2 — guards for agent turns
This is not the current release. The current one is here, with its changes in CHANGELOG.md.
Agent turns gain two opt-in guards: an open think block no longer offers the
tokens that end the turn, and a tool call can be constrained to the declared
schemas. Both are switches, off by default; a unit or a request turns them
on. The reasoning split by token id below is not a switch: it is on for every
artifact whose tags are single special tokens.
- Think-block mask —
--think-eog-mask(also the request field
think_eog_mask;/propsreports it). While a think block is open, the
end-of-generation ids (unless the request setsignore_eos), the request's
stop_token_ids(the same set the stop check ends on) and a second opener
are masked in the sampler, so an empty or mid-block stop cannot happen; the
block's closer is never masked. Fed in all three llama decode loops (plain,
multimodal, speculative verify). - Tool-call grammar —
--tool-grammar(request fieldtool_grammar). A
GBNF built from the declared tools makes an unterminated or malformed call
unreachable: the opener, a declared function, its required parameters in any
order, the optional ones, values typed by their schema, the closer. Dormant
while a think block is open; a non-object or unconfinable schema is the
client's 400. A lost sync is counted on the decode line and fails open (the
rest of the turn runs unconstrained) instead of escaping as a 500. - The think block decided by the closer's token id. The reasoning split
and the stream now see the generated token ids: a closer spelled from
ordinary tokens stays reasoning, the special-token closer ends the block,
and the streaming and non-streaming paths run the same splitter. Text a
request's stop strings hold back is released cut at the generated pieces,
each whole piece with its own id, so the closer and the opener keep theirs:
before this, a stop string beginning with a line break (a role prefix after
a newline, as agent frameworks send) cost the closer its id, the block never
closed and the answer came back as reasoning with empty content; it reached
the dev build that ran this content in production, not a package. A line
break ahead of a self-opened block keeps the split undecided, as the mask
is, and the non-streaming split drops the line breaks around the tags as the
text split does. Residual (code, untested): a stop string that begins with
the tail of a tag's text splits that tag's piece, and a split piece carries
no id, so such a block stays open. - The reasoning stream holds back a torn UTF-8 code point, as the content
path already did, so no reasoning delta is invalid UTF-8 (DESIGN §3.7). reasoning_closedon the stream: the chunk that carriesfinish_reason
says whether the think block was closed when the turn ended, as the
non-streaming message does.- Review fixes riding with them: the mask's block tracker settles instead of
re-opening (B1), the mask covers exactly the stop check's set (B2), the
closer guard (B3), a guarded grammar mask (B4), tests that can go red for
the stream order andreasoning_closed(B5, B6).
Runtime: llama.cpp 3e4dfa8 with contrib/llama.cpp/patches (60
patches, 0001-0063 without 0031, 0055 and 0061), unchanged from 1.2.1;
dependencies unchanged. arcint-stt (1.2.0) and arcint-imggen (1.1.1) are
unchanged.
Release check (measured-here, 2026-10-23, the build of this content):
the device-free suite 228 cases and ctest -L unit 6/6, the libllama build
green, the operator's mutations of B2/B3, B4b and the token-id change red as
predicted (4/4 each). Tool-call grammar gate on the coder (A770): off 11 of 12
malformed, on 0 invalid, a non-object schema 400, the CSV task through a tool
call 10/10 ten times, the T=0 answers byte-identical off and on, decode 79.2-79.3
against 79.3-79.4 t/s. Think-block mask on the agent (B60): the empty stop with
the mask off, answered with it on. Acceptance task 5/5 at 10/10 greedy on the
coder and on the agent. Same-window speed A/B against 1.2.1: coder and agent
pass with byte-identical texts, Flash-Next passes in four rounds (33.38 ->
34.23 t/s; two rounds under-sample the old binary's spread). The coder's
sampled bar at 0.7 (mean 9.056 over 90 runs, upper bound 9.62) fails on the
unit's thinking-row sampling for thinking-off requests, not on 1.2.2's code
(the instruct row: 9.900 over 30); the fix is a later release's.
The stop-string fix (measured-here, 2026-10-24): the device-free suite 239
cases and ctest -L unit 6/6, eight one-line mutations of it each red, the
pre-tag package build green; on the coder (A770, T=0) a request with a stop
string beginning with a line break gave an empty answer with the block open
on the dev build that ran this content, and closes the block and answers on
the fixed build, streaming and non-streaming, as without the stop.
Published (2026-10-24): arcint 1.2.2-1 built by its recipe from the
v1.2.2 tag tarball (sha256 95392892…) and published to the trixie
repository (index verified); installed from the pool on the card host, where
the agent and the coder ran it (the stop-string check answered on both, the
acceptance task 10/10 on the agent) until 1.2.3. arcint-stt stayed at
1.2.0-1, arcint-imggen at 1.1.1-1.
arcint 1.2.1 — faster Flash-Next CPU misses
This is not the current release. The current one is here, with its changes in CHANGELOG.md.
Flash-Next decodes faster: the expert cache's CPU miss path gets Strata's
multi-token rows for the remaining expert types, and every miss of a verify
window goes to the CPU.
- 0063 (
contrib/llama.cpp/README.md): Strata's multi-token AVX2 rows
(src/kernels/cpu/iq_avx2.cpp) for IQ2_XS, IQ3_XXS, IQ3_S and IQ4_NL, so
every layer of the served Flash-Next GGUFs takes the fast path (before,
only layers whose three projections were IQ2_S, IQ2_XXS or Q2_0 did; the
rest ran ggml-cpu's single-token dot products).GGML_OPENCL_CPU_MISS_MAX
defaults to 64 (was 16): with 10 experts a token and at most 4 tokens a
verify window that is every miss on the CPU, as Strata does.
Runtime: llama.cpp 3e4dfa8 with contrib/llama.cpp/patches (60
patches, 0001-0063 without 0031, 0055 and 0061); dependencies as in 1.2.0.
arcint-stt (1.2.0) and arcint-imggen (1.1.1) are unchanged.
Release check (measured-here, 2026-10-20, the B60, the pre-tag
package with the Flash-Next unit's flags): IQ3_XXS decodes 38.08 / 38.02
t/s on six answers (1.2.0: 33.85 / 33.87), prefill 622 t/s at 20k, KL as
served 0.2173 against Q8_0 (1.2.0: 0.2191), Paris and the needle right.
Measured the evening before on dev builds (Flash-Next campaign, stage 10):
each kernel equal to ggml-cpu's dot product on test data and 1.8-2.9x
faster at four tokens; the old path forced back on the patched build at the
baseline (so the gain is the new rows); UD-Q3_K_XL 12.5 -> 16.7 t/s, below
the operator's 20 t/s line for switching quants, so IQ3_XXS stays. The
other services do not use the miss path (code); their 1.2.0 checks stand.
A byte tripwire, reported: three of IQ3_XXS's eight answers change wording,
and the forced-old-path control also differs from the base build, so the
difference is not only the new rows; not explained; the answers and the KL
are right.
Packages (Debian trixie, amd64): arcint 1.2.1-1; arcint-stt stays 1.2.0-1, arcint-imggen 1.1.1-1.
arcint 1.2.0 — voice and vision
This is not the current release. The current one is here, with its changes in CHANGELOG.md.
Voice and vision (docs/milestone-1.2.0.md, the units' contract
docs/speech-service.md): three on-demand units on the Arc Pro B60, each
with Conflicts= against the card's language, image and other speech units.
- Vision input,
arcint-vlmon :8083: Qwen2.5-VL-7B-Instruct (Q4_K_M,
Q8_0 projector) through llama.cpp'slibmtmd, now linked into arcint.
/v1/chat/completionstakes OpenAIimage_urlparts as
data:image/...;base64URIs (a file path or a remote URL is 400), at most
8 images of at most 16.8 M pixels a request; an image prompt over the lane
is the usual 400context_length_exceeded./propsreports the
projector's vision and audio halves. - Text to speech with voice cloning,
arcint-ttson :8084: Qwen3-TTS
12Hz 1.7B Base, German.--tts --tts-profiles DIR;/v1/audio/speech
takes a voice name from the server's enrolled profiles
(/v1/audio/voices) and refuses any reference-audio field: a voice is
enrolled on the host withtools/tts_enrol.py(shipped in
/usr/share/arcint/), never through the API.--tts-gen-device cpu, the
unit's placement (operator's decision): the backbone on the card, the
projector's speaker encoder, code predictor and acoustic decoder on the
CPU. The production projector is Q8_0 with the code head and embedding at
f16. A TTS unit refuses chat with 400. - Speech to text,
arcint-stton :8091, the new packagearcint-stt:
whisper.cpp v1.9.5's server (arcint-whisper) built against the ggml
installed from arcint's patched tree, Whisper large-v3-turbo,
/v1/audio/transcriptions; its patch adds/props(the model, the
decoding defaults, the GPU device in use, the build) and stops serving
upstream's/load. - 0062,
libmtmd's Qwen3-TTS acoustic decoder decodes the frames it is
given instead of a padded 72-frame window (tools/mtmdonly). - The arcint package ships the
arcint-vlmandarcint-ttsunit
templates besidearcint.service.
Runtime: llama.cpp 3e4dfa8 with contrib/llama.cpp/patches (59
patches, 0001-0062 without 0031, 0055 and 0061), compiled in, with
libmtmd; dependencies as in 0.8.0. arcint-stt: whisper.cpp d1be6fde
on the same tree. arcint-imggen is unchanged (1.1.1).
Release check (measured-here, 2026-10-19, the pre-tag packages;
milestone §9.11): the release gate tests/acceptance/ab.py against 1.1.1's
binary, alternated: the coder 51.10 / 47.60 / 44.10 t/s and 725.20 at 41k
(1.1.1 the same decode, 725.45), the agent 709.35 / 498.90 t/s at 55k /
138k (equal to 1.1.1), every answer right and the same bytes in every run of
both binaries on both cards; the acceptance task 9/10 (coder) and 10/10
(agent). No KL arm: the llama.cpp trees differ only in tools/mtmd. VLM
gate 9/9 (floor 1.0, whole-word matching; its red, wrong expectations, 0/9);
TTS real-time factor 1.1167 (floor 1.0) and round-trip WER 0.0264 (floor
0.10, worst sentence 0.333, floor 0.5) on 46 Tatoeba sentences; STT WER
0.0547 on 12 FLEURS de clips (floor 0.08, equal to the CPU run). The image
input fixes against the pre-fix binary as their red: a file path in
image_url was opened, a streamed bad image ended the process; on 1.2.0
both are 400 / an error event, and the process serves on.
Open after the release: the voice-similarity floor (C5) needs the household
speakers' own recordings, two each; naturalness is judged by ear.
Packages (Debian trixie, amd64): arcint 1.2.0-1 and the new arcint-stt 1.2.0-1; arcint-imggen stays 1.1.1-1.