Skip to content

Releases

arcint 1.6.1

Choose a tag to compare

@marfrit marfrit released this 07 Nov 15:02

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_x8 staged 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-loops and --llama-dory-kv set 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

Choose a tag to compare

@marfrit marfrit released this 07 Nov 07:15

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; /props reports provenance card. 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 N above 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 branch flattened holds 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

Choose a tag to compare

@marfrit marfrit released this 25 Oct 16:50

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.py gates 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.py prints an ACCEPTANCE-DECODE line 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

Choose a tag to compare

@marfrit marfrit released this 25 Oct 13:26

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_grace counted lines
    after the detection (the nudge point) and grace more (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 follows ladder-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), and self_closes counts only a close while a mask was
    active. A lift is logged, with no counter of its own. ladder-nodoor,
    detect, nudge and slap keep 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)
  • door mode's budget — door is the mask alone and has no nudge or slap,
    so a mask could hold a turn to the cap on its own. After door_grace + grace counted 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_masked and
    door_eog_masked count a step when a masked id would have been a candidate
    of the draw. candidate is now the set the draw actually samples from, after
    the row's truncation (top-k, top-p, min-p), for every top_k value: at
    top_k 0 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 door mode 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 of reasoning_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/completions gets 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 inclusive parameter 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, the n == 0 draw spends the counters like the
    greedy path, and loop_guard_door_ladder reads the door branches instead of
    loop_guard_is_ladder carrying a second invisible condition. (code)
  • The loop_guard object on the wire — loop_guard_json returns a plain
    json instead of an ordered_json: the response body is a plain json, 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.py sends each arm's temperature (it
    only printed it, so both arms ran at the unit's default row). The speaker
    similarity tool tools/tts_voice_sim.py has 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.md records
    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...

Read more

arcint 1.2.4 — the loop guard's door

Choose a tag to compare

@marfrit marfrit released this 25 Oct 08:38

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. door is the mask
    alone (never nudge or slap); ladder includes the door; ladder-nodoor is
    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's loop_guard object and the decode line count self_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). /props reports the mode and door_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
    on finish_reason loop exactly 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, and detect/nudge/slap never
    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, and ignore_eos turns it off. With
    think_eog_mask set the mask is the same one and applied once. The
    loop_guard object and the decode line add door_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".
  • ladder now includes the door. On a unit that set ladder in 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) with think_eog_mask off 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 on loop. ladder-nodoor restores 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 with think_eog_mask off 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 counters door_masked and door_eog_masked
    count against the whole row when top_k is 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

Choose a tag to compare

@marfrit marfrit released this 24 Oct 10:17

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
    field loop_guard; /props sampler_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-window 200; a line repeats
    when it equals an earlier one or, under --loop-guard-match near (the
    default; exact the alternative), has a character-3-gram Jaccard of at
    least 0.8 with one of the last 400 distinct lines. At
    --loop-guard-threshold 0.8 (hysteresis 0.7) the ladder climbs: a fixed
    sentence is injected into the reasoning once (nudge); after
    --loop-guard-grace 40 more looping lines the think block's closer is
    forced by token id (slap); a looping answer after the slap ends the turn
    with finish_reason loop (abort). A forced token ends a speculative
    verify walk at its position. The response's loop_guard object 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; /props reports 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

Choose a tag to compare

@marfrit marfrit released this 24 Oct 07:12

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; /props reports it). While a think block is open, the
    end-of-generation ids (unless the request sets ignore_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 field tool_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_closed on the stream: the chunk that carries finish_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 and reasoning_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

Choose a tag to compare

@marfrit marfrit released this 20 Oct 04:46

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

Choose a tag to compare

@marfrit marfrit released this 19 Oct 16:15

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-vlm on :8083: Qwen2.5-VL-7B-Instruct (Q4_K_M,
    Q8_0 projector) through llama.cpp's libmtmd, now linked into arcint.
    /v1/chat/completions takes OpenAI image_url parts as
    data:image/...;base64 URIs (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 400 context_length_exceeded. /props reports the
    projector's vision and audio halves.
  • Text to speech with voice cloning, arcint-tts on :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 with tools/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-stt on :8091, the new package arcint-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/mtmd only).
  • The arcint package ships the arcint-vlm and arcint-tts unit
    templates beside arcint.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.