Skip to content

Maintenance pass (2026-10): upstream bumps, scheduled CI, honest status - #1

Merged
ericmann merged 11 commits into
mainfrom
maint/2026-10
Oct 29, 2026
Merged

ericmann merged 11 commits into
mainfrom
maint/2026-10

Conversation

@ericmann

@ericmann ericmann commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

Part of the 2026-10 maintenance pass. Merge after ext-template#2. Prepares v0.2.1 (not tagged).

Release headline: "Now loads Nanbeige4.2 and Spark-X2.5", plus a fix for long prompts aborting the PHP process.

What changed and why (one bump per commit)

  1. chore: sync scaffold from ext-template@442aa21: setup-php unpinned (clears the <2.37.1 advisories), the PHP 8.5 × macos-14 leg restored, weekly schedule, ZTS legs, managed Dependabot.
  2. chore: sync scaffold from ext-template@339f16f: ext-php-rs 0.15.13 → 0.16.3, thiserror 2.0.21. Binding adaptation: 0.16 converts #[php(defaults(...))] values into the parameter type, so chat()/raw() defaults became typed literals (128_u32, 0.0_f32). PHP defaults are unchanged (Reflection-verified).
  3. chore(deps): llama-cpp-2 =0.1.146 → =0.1.160 (April → mid-October llama.cpp). Tokenization moved to LlamaVocab. A shared tokenize() keeps rejecting NUL bytes with the exact old message, since the new byte-slice tokenizer would otherwise accept them.
  4. fix: size n_batch to n_ctx: llama.cpp's context n_batch defaults to 2048 and llama_decode asserts (abort, not an error) when a batch exceeds it. In v0.2.0, any chat()/raw() prompt between 2049 tokens and nCtx killed the process, and RerankModel did so with default options (n_ctx 4096). Reproduced on v0.2.0 and on 0.1.160; new model-gated PHPT 017-long-prompt.phpt fails on v0.2.0 and passes here. RSS unchanged (948 vs 949 MB at nCtx 2048; 1639 vs 1637 MB at 8192). This also breaks rag-over-markdown on larger corpora (bin/rag ask --k 8 over ~28 KB aborts).
  5. Docs: models verified on the new llama.cpp; dead Qwen3-7B-Instruct links (Qwen3 has no 7B; HF 401) now point at Qwen/Qwen3-8B-GGUF; "v0.1.0 still in flight" notes removed; make stubs workflow; ZTS claims updated; Status section, stack table, writing links, a deliberately-out-of-scope section sourced from ARCHITECTURE.md's non-goals (the README had none); PLAN.md as design notes; CHANGELOG.md backfilled (v0.1.0-rc.1 → v0.2.0).

Re-verification of the load-bearing behaviours

A probe was run on the v0.2.0 build and on this branch, and the output is byte-identical: greedy chat text, reasoning split, raw completion, JSON-Schema output plus hard-fail on an unsupported keyword (pattern), raw GBNF, a ~1.4k-token prompt (past the old 512 batch), NUL rejection, last- vs mean-pooled Qwen3-Embedding cosine rankings with and without the query instruction prefix (last pooling ranks the right doc first; the prefix asymmetry holds), raw embedding floats, packed() parity, and Qwen3-Reranker yes/no scores and rank() rows. sample() still wraps llama_sampler_sample (which accepts), so the double-accept fix holds; grammar generation runs 160 tokens without the old GGML_ASSERT.

New architectures (llama.cpp PRs merged after mid-June; both fail to load on v0.2.0):

Release-build performance on a Ryzen 5 3400G: prompt processing 12.66 s → 13.10 s for ~1k tokens; generation 34.4 → 33.7 tok/s. No regression. (The absolute prompt rate looks low; see #13.)

Gates

  • Local (Linux x86_64, PHP 8.3, real models): 16/16 PHPTs; clippy -D warnings + fmt clean; cargo-about gate passes.
  • CI: 9/9: lint; NTS 8.3/8.4/8.5 on macOS arm64 and Linux x86_64; ZTS 8.4/8.5 (PHP_ZTS=true confirmed). CI is model-free by design, so 12 of 16 PHPTs skip there; the model tier is the local run above.
  • PHP surface: a ReflectionExtension dump of every class, method and parameter matches pre-pass except as noted below. cargo-php 0.2.1 stub output matches 0.1.21's except for fully-qualified extends.

⚠️ Needs your sign-off (PHP-visible, from ext-php-rs 0.16)

  • A wrong-typed argument now throws \TypeError (e.g. maxTokens: 'x'), where 0.2.0 threw a plain \Exception (ext-php-rs 0.16.3 #820). It matches PHP's internal functions, but catch (\Exception) no longer catches it. If you'd rather not ship that in a patch, pinning EXT_PHP_RS_PIN to =0.16.2 avoids it.
  • Trailing ?array $options parameters now report their null default via Reflection (isDefaultValueAvailable()), matching the stubs. Harmless, but visible.

Pre-existing findings (reported, not changed)

  • Curated stubs vs registered classes: the stubs declare final on Embedding, Message, Prompt and Response, but the runtime classes aren't final (same in 0.2.0). The stubs also say array $options = [] where the runtime has ?array $options = null, and self where the runtime reports the class name. Matching them means either making the classes final at runtime (a PHP API change, out of scope here) or dropping final from the stubs. That's your call. Same pattern in ext-turbovec and ext-whisper.
  • Exceptions thrown from Rust report getFile() === '' / getLine() === 0 (Exceptions thrown from the extension report an empty file and line 0 #14).

Draft release notes — v0.2.1

Now loads Nanbeige4.2 and Spark-X2.5. ext-infer 0.2.1 bundles an October 2026 llama.cpp (0.2.0 shipped an April one), so architectures added since then load. Verified: Nanbeige4.2-3B for chat, the reasoning split and schema output; Spark-X2.5-1.7B for raw completions (its Jinja-only chat template needs raw() for now).

Fixed: long prompts no longer abort the PHP process. On 0.2.0, any prompt between 2,049 tokens and nCtx, and any RerankModel query + document over 2,048 tokens with default options, tripped an assertion inside llama.cpp and killed the worker. Memory use is unchanged.

Changed: a wrong-typed argument now throws \TypeError (was \Exception), like PHP's own functions. Greedy chat, grammar, embedding and reranker output are byte-identical to 0.2.0 on the Qwen3 0.6B reference models. CI now runs weekly, on PHP 8.5 on macOS again, and on ZTS PHP 8.4/8.5.

Full details: CHANGELOG.

- setup-php 2.36.0 pin -> @v2 and the PHP 8.5 x macos-14 leg restored
  (ext-template#1, previously unsynced; clears the setup-php <2.37.1
  advisories).
- Weekly scheduled CI + workflow_dispatch.
- ZTS test legs (Linux, PHP 8.4/8.5; continue-on-error for now).
- Managed .github/dependabot.yml: grouped weekly cargo minor/patch;
  versions.conf pins and the primary upstream crate are excluded.
Cascaded pins: ext-php-rs =0.15.13 -> =0.16.3, thiserror =2.0.18 -> =2.0.21.

Binding adaptation (no PHP-visible change): ext-php-rs 0.16 converts
#[php(defaults(...))] values into the parameter type, so Model::chat()
and Model::raw() defaults are now typed literals (128_u32, 0.0_f32, ...).
Reflection confirms the PHP defaults are unchanged.
Surface check: identical ReflectionExtension dump except trailing
nullable array params now report their null default (the documented 0.16
fix aligning arginfo with the stubs).
Bundles a llama.cpp from mid-October 2026 (four months of upstream:
new model architectures, kernel and tokenizer fixes).

Binding adaptation, no PHP-visible change:
- Tokenization moved from LlamaModel to LlamaVocab (`model.vocab()`):
  str_to_token -> vocab.tokenize(bytes, add_bos, parse_special=true),
  is_eog_token -> vocab.is_eog, token_to_piece_bytes -> the growable
  vocab.token_to_piece_into (the old fixed 32-byte buffer could error on
  a long piece).
- The new tokenizer takes raw bytes, so it no longer rejects an interior
  NUL. A shared `tokenize()` helper keeps refusing it with the exact
  message the CString path produced ("nul byte found in provided data at
  position: N"), so behavior is unchanged.
- AddBos is gone; callers pass a bool.

Re-verified against v0.2.0 with a probe covering chat (greedy), the
reasoning split, raw completion, JSON-Schema-constrained output and
unsupported-keyword rejection, raw GBNF, a ~1.4k-token prompt (past the
old 512 batch), NUL rejection, last- vs mean-pooled Qwen3-Embedding
cosine rankings with and without the query instruction prefix, packed()
parity, and Qwen3-Reranker yes/no scores + rank() rows: output is
byte-identical to v0.2.0. sample() still wraps llama_sampler_sample
(which accepts), so the double-accept fix holds. 15/15 PHPTs with real
models; clippy -D warnings clean; cargo-about license gate passes.
llama.cpp's context n_batch defaults to 2048, and llama_decode asserts
(GGML_ASSERT(n_tokens_all <= cparams.n_batch) -> abort(), not an error
code) when one batch exceeds it. ext-infer decodes the whole prompt as
one batch, and its own guard only compares against n_ctx — so in v0.2.0
any chat()/raw() prompt between 2049 tokens and nCtx killed the
interpreter: no exception, no shutdown functions, a dead FPM worker.
RerankModel was exposed with *default* options (n_ctx 4096): a
query + document of 2049..4096 tokens aborted. Reproduced on v0.2.0 and
on llama-cpp-2 0.1.160; embeddings were safe (capped at n_ctx 2048).

Set n_batch = n_ctx for the completion, rerank and embedding contexts.
n_ubatch (the physical chunk that sizes compute buffers) keeps its
default, so memory is unchanged (1k-token prompt, max RSS v0.2.0 vs this:
948MB vs 949MB at nCtx 2048, 1639MB vs 1637MB at nCtx 8192). No
PHP-visible API change; the over-n_ctx path stays a clean
InferenceException.

Regression test 017-long-prompt.phpt (model-gated): fails with the
abort on v0.2.0, passes here.
- models.md: Nanbeige4.2-3B (nanbeige, llama.cpp #25994) verified for chat,
  reasoning split and schema output; Spark-X2.5-1.7B (spark2_5, #27868)
  loads but its Jinja-only template needs raw(). Both fail to load on
  0.2.0. Granite 4.2 3B noted as working on both.
- chat.md: 'apply_chat_template failed' also means an unrecognised Jinja
  template (ffi error -1), not only an unsupported role sequence.
- Qwen3 has no 7B: bartowski/Qwen3-7B-Instruct-GGUF and
  Qwen/Qwen3-7B-Instruct-GGUF don't exist (HF 401). Point at the
  official Qwen/Qwen3-8B-GGUF (Q4_K_M 5.0 GB) instead.
introduction.md and installation.md still described ext-infer as
pre-release with the first tag pending; v0.1.0, v0.1.1 and v0.2.0 have all
shipped with PIE binaries. Status now reads as maintained and points at
the CHANGELOG, the future issues, and PLAN.md's design notes.
make stubs now writes target/<stubs file> for a signature diff against the
curated stubs/ copy (cargo-php never had the --stubs flag the target
passed), with STUBS_PRELOAD for Linux. RELEASE.md and the stub header
point at the new workflow.
ubuntu-26.04 runners mix libclang 22 with LLVM 21 builtin headers, which
fails ext-php-rs's bindgen on every Linux leg (main included); see
ext-template@3978271.
- README: a Status section (feature-complete, maintained, last verified
  2026-10-29 with real values) replaces the pre-release badge; the
  roadmap's Next list moves to `future` issues with their reasoning;
  every repo in the stack is linked from one table; the blog series is
  linked from the README and the mdbook introduction.
- PLAN.md: design notes and decision history (this pass's decisions
  recorded) instead of a what's-left list; remaining work links the
  issues. Stale ext-php-rs / ZTS / OpenBLAS notes updated.
- Deliberately-out-of-scope sections unchanged.
Cargo.toml version and CHANGELOG.md (Keep a Changelog, backfilled from
the existing tags and their release notes). Not tagged: tags trigger the
binary release workflow and are cut by the maintainer after merge.
@ericmann
ericmann marked this pull request as ready for review October 29, 2026 17:01
@ericmann
ericmann merged commit c6c435e into main Oct 29, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant