Skip to content

ci: decouple builds into actions and test every package in Build and Test - #875

Merged
zaneni6 merged 17 commits into
mainfrom
ci/refactor
Oct 29, 2026
Merged

zaneni6 merged 17 commits into
mainfrom
ci/refactor

Conversation

@zaneni6

@zaneni6 zaneni6 commented Oct 29, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

Restructures CI around one workflow, Build and Test, that builds every FastFlowLM package and tests each one on the NPU runners as soon as its own build finishes.

Builds

Piece What it does
.github/actions/win-build Builds one Windows variant: XRT, HRX, or Source (engines built from source). Build only.
.github/actions/win-msi Builds the MSI around an already-built flm.exe.
.github/actions/lin-build Builds one Linux variant (XRT-deb, HRX-deb, XRT-portable, HRX-portable, Source-portable) in the build-environment container.
lin-build.yml / win-build.yml Dispatch and workflow_call only: expand a variant (or all) into a matrix, package, upload. Artifact names unchanged.
  • New HRX .deb for every distro: debian/rules reads FLM_USE_HRX / FLM_CMAKE_ARGS, Depends: libxrt-npu2 only for XRT, and src/CMakeLists.txt links non-portable HRX builds with -rpath-link to the system lib dir (the HRX SDK ships an unversioned libtinfo that broke the link).

Build and Test (build-and-test.yml)

Linux    XRT portable ─ test     HRX portable ─ test     Source portable ─ test
Windows  XRT zip ─ test          HRX zip ─ test          Source zip ─ test
  • Each package is its own build call, so its tests start as soon as that build is done; XRT and HRX are tested side by side on any NPU runner of the platform.
  • HRX skips api and serving, which test server logic the backend does not touch.
  • A pull request runs only the suites its changes can affect (.github/script/ci/changes.py): an ASR-only change runs asr, a d3-only change decision, an engine change chat + prefix_cache; shared code runs everything. Docs-only changes build and test nothing.
  • A new PR commit cancels the previous run; pushes to main, nightly and tags always finish.
  • Nightly (full tier) tests every catalog model and every suite.
  • .deb / MSI install tests are off unless the repository variable FLM_INSTALL_TESTS is true (the runners cannot install yet: no passwordless sudo on Linux, an FLM already installed on Windows).
  • Stale flm processes left by earlier jobs are cleared before testing; a runner whose NPU is held by an unkillable one fails at once, named.
  • New actions: detect-changes, install-package, remove-package, npu-test.

NPU test harness (.github/script/npu_test)

Stdlib-only Python, server mode, replaces sweep_*.py and asr_test.py:

Suite Checks
chat One model per engine family (19 on a PR instead of 49), plus every model of a family the change touches: known answer, streamed two turns, non-stream usage, vision colour check on generated images
serving Queue full (503), concurrent requests routed to their own replies, client disconnect releasing the NPU, model switch, unknown model (/api/cancel skipped until fixed)
prefix_cache Multi-slot KV cache: hit on return, LRU eviction with --prefix-slots 2, cached vs cold reply
api src/test/openai_api_test + src/test/openai_usage (tools, logprobs, response_format, reasoning, sampling, continue_final_message, errors), never run in CI before
embedding Shape, batching, similarity ordering, determinism
decision d3 goldens, question types, errors, images, turbo/ultra prefix features (no longer reads the docs)
asr Whisper WER/CER on FLEURS vs expected.json, formats, segments, language, prompt; chat transcription for gemma4-it e2b/e4b/12b and nemotron3.5-bolt

Release

release.yml runs Build and Test on the tag (no models-from-source) and publishes each backend only when all of its packages passed. HRX debs are now released with the HRX assets.

Removed

windows-build.yml, ubuntu-build.yml, debian-portable.yml, model-sweep.yml, asr-test.yml, resolve_build.sh, asr_gate.sh, sweep_*.py, asr_test.py, asr_summary.py. The CPU-only d3-contract / chat-continue tests move to cpu-tests.yml unchanged.

Known server bugs found by the new tests (not fixed here)

  • /api/cancel registers itself under the request_id it names and cancels itself (src/server/server.cpp, process_task); serving/api-cancel is skipped until fixed.
  • After a client disconnect the NPU lock was once never released and the process survived SIGKILL (strix runner); intermittent.
  • An unknown model answers HTTP 200 with an error body instead of a 4xx (warns).
  • A client disconnect leaks a connection slot (HttpSession::send_chunk_data skips active_connections_.fetch_sub).
  • min_p is read as an integer (rest_handler.cpp:815).

Notes for reviewers

  • Check names change (e.g. Test Linux XRT portable); update branch protection if any are required.
  • ASR on PRs compares against expected.json instead of re-running main's build as a baseline.
  • Runners: xsjnuckrck01 drops every job ("lost communication"); xconucgpt1-21 sets FLM_SERVE_PORT=52625 and something there holds that port (the harness now always picks a free port).
  • d3:flash has no goldens file yet, so its goldens case warns.

Test plan

  • actionlint clean; changes.py unit tests (17)
  • Harness end to end against a mock flm
  • Build and Test green on real NPU runners (run 40966757444, 41 min): Linux/Windows XRT and HRX portable, models-from-source, all builds incl. HRX .deb on 4 distros
  • First nightly (full tier) after merge
  • First tag through release.yml

🤖 Generated with Claude Code

zaneni6 and others added 4 commits October 29, 2026 10:58
Split the build logic out of windows-build.yml, ubuntu-build.yml and
debian-portable.yml into composite actions, driven by two matrix
workflows:

- .github/actions/win-build: builds one Windows variant (XRT, HRX,
  Source); build only.
- .github/actions/win-msi: builds the MSI around an already-built
  flm.exe.
- .github/actions/lin-build: builds one Linux variant (XRT-deb,
  HRX-deb, XRT-portable, HRX-portable, Source-portable) in the
  build-environment container.
- win-build.yml / lin-build.yml: expand "all" (or a variant list) into
  a matrix, package, upload, and publish releases on tags. Artifact
  names are unchanged.

The CPU-only d3-contract and chat-continue tests move to cpu-tests.yml.

New HRX .deb: debian/rules takes FLM_USE_HRX and FLM_CMAKE_ARGS from
the environment, and Depends on libxrt-npu2 only for the XRT package.
HRX debs are built but not released yet.

asr-test.yml, model-sweep.yml and resolve_build.sh still look up the
removed workflow files and are aligned in a follow-up.

Co-Authored-By: Claude <noreply@anthropic.com>
release.yml builds the tagged commit (XRT and HRX for both platforms,
no models-from-source) by calling win-build.yml and lin-build.yml, then
publishes every asset to the tag's release. It builds the tag itself
rather than reusing main's artifacts, since the version comes from git
describe at build time.

The XRT and HRX assets publish in two jobs so a failed HRX build cannot
hold back the default release. The HRX job runs after the XRT one so
the two never race to create the release. The HRX .debs are now
released too.

win-build.yml and lin-build.yml no longer run on tags or publish, and
lin-build.yml drops test-summary, which reported success even when the
build failed.

Co-Authored-By: Claude <noreply@anthropic.com>
The HRX package ships its own libtinfo, libedit, curl and others next
to libhrx, and that directory is on flm's BUILD_RPATH. The linker
searches BUILD_RPATH for the dependencies of shared libraries, so in
non-portable HRX builds the system libedit resolved its libtinfo.so.6
to the HRX copy, which has no NCURSES6_TINFO symbol versions, and the
link failed with undefined tgoto/tgetent/... references.

Put the system library directory on -rpath-link, which the linker
searches before the rpath. It applies only at link time, so the
installed RPATH is unchanged. XRT and portable builds are not affected.

Co-Authored-By: Claude <noreply@anthropic.com>
Build and Test (build-and-test.yml) replaces Model Sweep and ASR Test.
It builds each package with its own call of lin-build.yml / win-build.yml
and tests it on the NPU runners as soon as that build finishes, instead
of a separate workflow polling for the build run:

- Linux XRT and HRX portable: the pr-tier suites on gpt and strix runners.
- Windows XRT and HRX zip: the pr-tier suites on gpt runners (the only
  Windows strix runner is offline).
- Ubuntu 24.04 .deb and the MSI: an install test, falling back to the
  unpacked payload when the runner cannot install software.
- models-from-source: the Gemma 4 engines.

XRT tests gate the HRX ones on the same platform, since HRX is a preview.
Docs-only changes build and test nothing, a new commit cancels a pull
request's previous run, and a nightly run tests every model.

The tests are a new stdlib-only harness, .github/script/npu_test, with
suites for chat (one model per engine family, plus every model of a
family the change touches), serving (queue, cancel, disconnect, model
switch), prefix_cache (multi-slot KV cache hits and LRU eviction), api
(src/test/openai_api_test and openai_usage, never run in CI before),
embedding, decision (d3, no longer tied to the docs) and asr (Whisper
plus chat transcription for Gemma 4 and Nemotron 3.5).

release.yml runs the same build and tests on the tag and publishes each
backend only when all of its packages passed. lin-build.yml and
win-build.yml become dispatch/call only. The old sweep and ASR scripts,
resolve_build.sh and asr_gate.sh are removed; the CI helpers move to
.github/script/ci and the test assets under npu_test/assets.

Co-Authored-By: Claude <noreply@anthropic.com>
@zaneni6 zaneni6 changed the title ci: decouple the Windows and Linux builds into actions ci: decouple builds into actions and test every package in Build and Test Oct 29, 2026
zaneni6 and others added 13 commits October 29, 2026 12:52
The script moved into .github/script/ci and still looked for
windows-deps one directory up, so every Windows build failed at the
dependency step.

Co-Authored-By: Claude <noreply@anthropic.com>
The models-from-source test asked for gemma4-it:e2b and e4b, but the
list only filtered the tier's own pick, which has e2b alone.

Co-Authored-By: Claude <noreply@anthropic.com>
FLM_CONFIG_PATH names the model_list.json file, not its directory, so
pointing it at share/flm made flm list abort. flm already finds
../share/flm next to its binary, so the unpacked tree needs no
overrides. A catalog that cannot be read is now a failed case in the
report instead of a traceback with no report.

Co-Authored-By: Claude <noreply@anthropic.com>
- detect-changes: diff a pull request's own head against its base. Its
  merge commit also carried main's newer d3 changes, so the pr tier
  tested every d3 model for a PR that touched none.
- serving/concurrent-complete: route-check with echoed words instead of
  capitals; qwen3:0.6b named Verona the capital of Italy on both gpt
  and strix, which says nothing about request routing.
- decision/goldens: warn, not fail, for a model with no goldens yet
  (d3:flash, new on main); its other cases still run.

Co-Authored-By: Claude <noreply@anthropic.com>
qwen3:0.6b gets both capitals and echo prompts wrong, so asserting the
answer tested the model, not the queue. Each prompt is now sent alone
for its greedy reference reply, then all together: a reply matching
another prompt's reference is a mix-up and fails, one that merely
differs warns, since NPU greedy decoding is not always reproducible.

Co-Authored-By: Claude <noreply@anthropic.com>
gpt and strix runners gave identical results (337 cases each), so the
second leg only cost NPU time. Windows keeps asking for a gpt runner,
which is what keeps its jobs off xsjnuckrck01: that runner carries the
npu and sweep labels but has dropped every job since 2026-10-28.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
On strix the serving suite hit a server bug: after "Client disconnected;
cancelling active request" the NPU lock was never released. The stuck
process then survived SIGKILL and kept port 52625, so every later suite
failed to start with "Address already in use".

- Each server starts on a free port unless --port fixes one.
- A server that survives SIGKILL is reported as a server-stop failure.
- serving restarts its server when the NPU stays busy after the
  disconnect or cancel case, instead of timing out case after case.

Co-Authored-By: Claude <noreply@anthropic.com>
On xconucstr260 a server from the previous run got stuck in the NPU
driver; the next job's server then hung at start for 300 s. npu-test now
kills flm processes left under the runner's work directory first, and
fails at once, naming the runner to reboot, when one survives.

Co-Authored-By: Claude <noreply@anthropic.com>
/api/cancel registers itself under the request_id it names and cancels
itself (server.cpp, process_task), so the case can only report that
bug. It is skipped with that reason; deleting the skip() turns it back
on once the server is fixed.

Co-Authored-By: Claude <noreply@anthropic.com>
The Windows runner xconucgpt1-21 sets FLM_SERVE_PORT=52625, which the
harness honoured, and something on that machine holds 52625, so every
server failed to bind (WSAEACCES). Only --port fixes a port now.

Co-Authored-By: Claude <noreply@anthropic.com>
- changes.py outputs the suites a change can affect: an ASR-only change
  runs asr, a d3-only change decision, an engine change chat and
  prefix_cache (and asr for engines with an audio encoder), a test
  script its own suite. Any shared file runs every suite, as do the
  nightly, dispatch and release runs.
- HRX packages skip api and serving, which test server logic the
  backend does not touch; the XRT run covers them.
- HRX tests no longer wait for the XRT tests, so both run at once.
- The models-from-source tests run only when the chat suite does.
- The .deb and MSI install tests are off unless the repository variable
  FLM_INSTALL_TESTS is "true", since the runners cannot install yet.
- The release verdict counts each backend's builds and portable tests;
  a test skipped because the change did not touch it counts as passed.

Co-Authored-By: Claude <noreply@anthropic.com>
@zaneni6
zaneni6 merged commit afb481d into main Oct 29, 2026
38 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