diff --git a/.cargo/config-v3.toml b/.cargo/config-v3.toml index a71aebe5a..e3e98453f 100644 --- a/.cargo/config-v3.toml +++ b/.cargo/config-v3.toml @@ -1,6 +1,7 @@ -# Portable distribution baseline: x86-64-v3 (AVX2), every x86_64 since -# Haswell. REQUIRED for anything shipped from this tree, now that the default -# `.cargo/config.toml` is `native`. Mirrors ndarray/.cargo/config-v3.toml. +# x86-64-v3 (AVX2), every x86_64 since Haswell. NOT the production target: +# production always runs on v4 and ships with config-v4.toml. Kept for +# building or testing the AVX2 arm on purpose. Never cite a timing from a v3 +# build: production does not run that arm. Mirrors ndarray/.cargo/config-v3.toml. # cargo build --config .cargo/config-v3.toml ... [target.'cfg(target_arch = "x86_64")'] rustflags = ["-Ctarget-cpu=x86-64-v3"] diff --git a/.cargo/config-v4.toml b/.cargo/config-v4.toml new file mode 100644 index 000000000..006840089 --- /dev/null +++ b/.cargo/config-v4.toml @@ -0,0 +1,12 @@ +# Production target: x86-64-v4 (AVX-512 F + BW + CD + DQ + VL), nothing above +# it. Production always runs on v4, so everything shipped from this tree +# builds with this file (Dockerfiles) or the same flag (wheel, images that +# copy no .cargo). Mirrors ndarray/.cargo/config-v4.toml without its +# `-Dwarnings`, which is a CI rule, not a build-image rule. +# cargo build --config .cargo/config-v4.toml ... +# On a host without AVX-512 the binary does not SIGILL mid-run: ndarray's +# startup `cpu_guard` names the missing features and exits before `main`. +# `config-avx512.toml` (Sapphire Rapids) is stricter and SIGILLs on earlier +# AVX-512 silicon such as Cascade Lake, so it is not the production default. +[target.'cfg(target_arch = "x86_64")'] +rustflags = ["-Ctarget-cpu=x86-64-v4"] diff --git a/.cargo/config.toml b/.cargo/config.toml index ba4523219..8f2e83535 100644 --- a/.cargo/config.toml +++ b/.cargo/config.toml @@ -30,10 +30,11 @@ # (`cfg!(target_feature = "avx512f")`), never by "it was the default". # # A default build is therefore tuned to the BUILD HOST and is NOT portable. -# Anything shipped pins its tier explicitly: +# Production always runs on x86-64-v4, so anything shipped builds for v4: # -# portable baseline (AVX2): cargo build --config .cargo/config-v3.toml ... -# AVX-512 (v4): cargo build --config .cargo/config-avx512.toml ... +# production (x86-64-v4): cargo build --config .cargo/config-v4.toml ... +# AVX2 arm on purpose: cargo build --config .cargo/config-v3.toml ... +# Sapphire Rapids only: cargo build --config .cargo/config-avx512.toml ... # # CI: build.yml, rust-test.yml and style.yml set `RUSTFLAGS` with an explicit # `-C target-cpu=x86-64-v3` (a RUSTFLAGS env REPLACES every cargo-config @@ -43,9 +44,11 @@ # (.github/scripts/resolve-native-rustflags.sh, used by dir-sim-persist.yml and # identity.yml) and keys the cache to those flags; without that, an artifact # built on a runner with AVX-512 is reused on one without and dies with SIGILL. -# The same script works locally: RUSTFLAGS="$(.github/scripts/resolve-native-rustflags.sh)". Everything else that ships pins x86_64 to v3 without -# touching other architectures: the Dockerfiles that copy this tree replace -# this file with config-v3.toml (x86_64-only), the thinking-engine images set +# The same script works locally: RUSTFLAGS="$(.github/scripts/resolve-native-rustflags.sh)". +# Everything that ships builds x86_64 for v4 without touching other +# architectures: the Dockerfiles that copy this tree replace this file with +# config-v4.toml (x86_64-only), `Dockerfile` and `Dockerfile.avx512` set +# RUSTFLAGS to x86-64-v4, the thinking-engine images set # CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS, and `make publish` in # python/ sets RUSTFLAGS only on an x86_64 host. Note that the per-target # variable is JOINED with this file's entry, with `native` last (measured), so diff --git a/.claude/board/LATEST_STATE.md b/.claude/board/LATEST_STATE.md index 82210ed6e..a5847a842 100644 --- a/.claude/board/LATEST_STATE.md +++ b/.claude/board/LATEST_STATE.md @@ -1,11 +1,12 @@ -## 2026-10-13 — main after #1505, #1506, #1507–#1509, #1514, #1516, #1518, #1520, #1523, #1524, #1526 (merged); #1525 open +## 2026-10-13 — main after #1505, #1506, #1507–#1509, #1514, #1516, #1518, #1520, #1523, #1524, #1525, #1526 (merged); #1529 open - **On `main`, live:** `.cargo/config.toml` defaults to `-Ctarget-cpu=native` with `config-v3.toml` as the portable baseline and `debug = 0` everywhere (#1505); `compute_dag_kahn` checks all 256 field positions (#1506); R2IL `MIN`/`MAX` lowering onto mask-risc (#1518, the part outside glove-parity). - **Merged then withdrawn by #1524 (D-CMR-RESET):** the class-method layer of `lance-graph-glove-parity` (`method`, `call`, `decl`, `gremlin`, `awareness`; #1507, #1508, #1509, #1514, #1516, #1518, #1520). Prior state: `backup/session-011BMJ-2026-10-12`. Inventory: entry `2026-10-12-session-reset-inventory.md`. - **Docs only:** SAG capstone (#1523): `ProposedMethod`, four-state status; not built. -- **Open:** #1525 D-NALP W1/W2 (`lance-graph-nal`: NodeGuid-keyed beliefs, byte apertures, `Question`/`TruthAtLeast`/`Choice` over compiled mask-risc programs; mask-risc `CompiledProgram`). +- **On `main` (#1525, merged `08635ed`):** D-NALP W1/W2, `crates/lance-graph-nal` (NodeGuid-keyed beliefs, byte apertures, `Question`/`TruthAtLeast`/`Choice` over compiled mask-risc programs; mask-risc `CompiledProgram`). +- **Open:** #1529 D-PROD-V4 (shipped images and the wheel build at x86-64-v4; `linux-build-v4` compiles the v4 arm in CI without running it). - **CI (#1526, merged `abc0a1f`):** the native default plus `rust-cache` reused AVX-512 artifacts on runners without them (SIGILL in `dir-sim log in Lance`). `native` is now written out as each runner's exact feature list, and the cache is keyed to it. -- **Rules carried from this session (operator):** classid-dependent methods go through the existing `hotplug.rs` plug-and-play; NAL/NARS operations are global core methods (no classid check, no plug, no `BeliefClass`); internal pins are forbidden (the v3 pins in the Dockerfiles and wheel build are an open decision). +- **Rules carried from this session (operator):** classid-dependent methods go through the existing `hotplug.rs` plug-and-play; NAL/NARS operations are global core methods (no classid check, no plug, no `BeliefClass`); internal pins are forbidden (the v3 pins in the Dockerfiles and wheel build are replaced by v4 in #1529). ## 2026-10-12 — dir-sim membership path witness + role evidence on branch `ccr-0455e606-wmtsor` (unmerged) diff --git a/.claude/board/PR_ARC_INVENTORY.md b/.claude/board/PR_ARC_INVENTORY.md index b397adec8..bd0f58201 100644 --- a/.claude/board/PR_ARC_INVENTORY.md +++ b/.claude/board/PR_ARC_INVENTORY.md @@ -1,3 +1,10 @@ +## 2026-10-13 — lance-graph PR #1525 (merged `08635ed`, branch `ccr-9f3b8075-74u47l-nalp`, head `077bb0a`, 16 commits) — D-NALP-1/2: NAL queries as NodeGuid apertures over the fold path + +- **Added:** new crate `crates/lance-graph-nal` (excluded, own workspace root, like `lance-graph-r2il`). A belief's key is its NodeGuid in the reading `nal-statement-v1` (`[classid, S, copula, P, id]`); `f` and `c` are value lanes in the key's sealed order. A selection is `Aperture { key, bytes }` (4 = class, 6 = subject, 10 = statement), resolved to one range by `SealedFacetLane::bound_aperture`. `nal::{Question, TruthAtLeast, Choice}` lower their R2IL procedure over `FoldDialect` once at bind and keep the mask-risc `Program`; calls run `execute_compiled` with the aperture range as the extent. mask-risc gains `CompiledProgram`. +- **Locked:** no new opcode (`FN = None` for every operation); no foreign keys. Measured (release, AVX-512, n = 1,048,576, median of 101, answers checked before timing): Question 19–20 ns, TruthAtLeast 81–127 ns, Choice 248 ns, bind 274 ns, row loop 495–559 µs; the subject range passed as an ANDed mask instead of the extent took 195 µs. 7 oracle tests over unsorted input; 7 disable runs red. +- **Deferred / open:** R2IL `RANGE` takes its bounds from two `u8` immediates, so it cannot name a range past row 255 (procedures use `FoldDialect::emit_pred(Pred::Range)`; needs an `ogar-r2il` change). No argmax fold: `Choice` returns the maximum confidence, not the belief. D-NALP-1's ClassView `WideFieldMask` projection and binder mask, and W3 (revision) onward, are not built. +- **Confidence:** high for the three queries at the measured size; the `u8` range limit is real and unfixed. + ## 2026-10-13 — lance-graph PR #1526 (merged `abc0a1f`, branch `ccr-9f3b8075-74u47l-cpukey`, head `8342c9f`, 4 commits) — CI: spell target-cpu=native out per runner and key the cache to it - **Added:** `.github/scripts/resolve-native-rustflags.sh`: prints `-Ctarget-feature=` with the runner's exact native feature list and no CPU name, and fails unless `rustc --print cfg` under those flags equals native; `--cpu` prints the name for logs. `dir-sim-persist.yml` and `identity.yml` set `RUSTFLAGS` from it before each of their three rust-cache steps, key the cache on its hash, and write the CPU to the job summary. diff --git a/.claude/board/STATUS_BOARD.md b/.claude/board/STATUS_BOARD.md index 9c3ae2192..07e539797 100644 --- a/.claude/board/STATUS_BOARD.md +++ b/.claude/board/STATUS_BOARD.md @@ -53,8 +53,8 @@ Plan: `.claude/plans/nal-polyfill-v1.md`. Template: D-SQLP. No opcode, tenant or | D-id | scope | status | gate / falsifier | |---|---|---|---| | **D-NALP-0** | Plan, board rows | In PR | docs only | -| **D-NALP-1** | Statement layout + declared ClassView reading; additive `WideFieldMask` projection path on `ClassView`; binder's mask; term width decision | In PR #1525 (NodeGuid reading `nal-statement-v1`, term width u16, aperture selection; ClassView wide projection and binder mask still queued) | only the mask's lanes are read; undeclared reading and masked-out field refuse; Small = Wide answers | -| **D-NALP-2** | Query procedures: select, truth threshold, choice by confidence, support count | In PR #1525 (`crates/lance-graph-nal`: NodeGuid key, byte apertures; 7 oracle tests, 7 disable runs red; 19–248 ns at 1,048,576 rows) | equal to a row-at-a-time oracle; one-pattern threshold disable red; no new opcode | +| **D-NALP-1** | Statement layout + declared ClassView reading; additive `WideFieldMask` projection path on `ClassView`; binder's mask; term width decision | Partial: #1525 merged `08635ed` (NodeGuid reading `nal-statement-v1`, term width u16, aperture selection); ClassView wide projection and binder mask Queued | only the mask's lanes are read; undeclared reading and masked-out field refuse; Small = Wide answers | +| **D-NALP-2** | Query procedures: select, truth threshold, choice by confidence, support count | Shipped #1525 `08635ed` (`crates/lance-graph-nal`: NodeGuid key, byte apertures; 7 oracle tests, 7 disable runs red; 19–248 ns at 1,048,576 rows); support count and argmax not built | equal to a row-at-a-time oracle; one-pattern threshold disable red; no new opcode | | **D-NALP-3** | Revision as `GroupCrossPowerSumsI32` over stored `w`/`f` | Queued | equal to exact integer revision; OpenNARS revision points; duplicate evidence counts once | | **D-NALP-4** | Per-row truth law into a lane: `MapPairU8` vs fixed-point multiply-high | Queued | insufficiency proof; per-row parity with `isa::truth`, error in codes; one shape also serves SQL nullable arithmetic | | **D-NALP-5** | Deduction / induction / abduction / analogy over populations | Queued (after D-NALP-4) | equals pairwise scalar rule; abduction direction test red for the reversed statement | diff --git a/.claude/board/entries/2026-10-13-production-targets-v4.md b/.claude/board/entries/2026-10-13-production-targets-v4.md new file mode 100644 index 000000000..64d7daccf --- /dev/null +++ b/.claude/board/entries/2026-10-13-production-targets-v4.md @@ -0,0 +1,73 @@ +# 2026-10-13 — Production builds target x86-64-v4 (D-PROD-V4) + +**Status:** DONE (build targets); the CI test tier and probe guards are OPEN. + +**DECISION:** everything shipped from this tree builds for x86-64-v4: the +Dockerfiles, the thinking-engine images and the wheel. There is no v3 +production variant. + +**BASIS:** production always runs on v4. Measured exposure: Railway hosts +about 95% v4, Claude Code sessions about 70% v4. A v3 build ships the arm +production never runs, and a timing measured on it is not evidence for +production. + +**SCOPE:** + +| site | before | after | +|---|---|---| +| `Dockerfile` | `RUSTFLAGS -C target-cpu=x86-64-v3` | `native` (fallback; the build host's CPU) | +| `Dockerfile.avx512` (canonical deploy) | `x86-64-v4` | `sapphirerapids` (Railway hosts are Sapphire Rapids, Emerald Rapids or newer, per the maintainer) | +| `Dockerfile.hhtld`, `crates/cognitive-stack/Dockerfile`, `crates/symbiont/Dockerfile` | `cp config-v3.toml config.toml` | `cp config-v4.toml config.toml` (x86_64 only; arm64 untouched) | +| `crates/thinking-engine/Dockerfile`, `Dockerfile.railway` | `CARGO_TARGET_X86_64_…_RUSTFLAGS x86-64-v3` | `x86-64-v4` | +| `python/Makefile` (`make publish`) | `x86-64-v3` on x86_64 hosts | `x86-64-v4` | +| new `.cargo/config-v4.toml` | — | `-Ctarget-cpu=x86-64-v4`, mirrors ndarray's file without `-Dwarnings` | + +**Measured:** `cargo --config .cargo/config-v4.toml` JOINS with +`.cargo/config.toml`'s `native` (rustc receives `-Ctarget-cpu=native +-Ctarget-cpu=x86-64-v4`). The later flag wins: the cfg equals v4 alone +(23 target features), not native (32 on this Cascade Lake host). The +Dockerfiles copy the file over `config.toml`, so they never join. + +**Debug 0 in CI (reported by the maintainer, not re-measured here):** a +build without debug info drops from 16-19 GB to 3.5 GB of disk and from +22-42 to 4-9 minutes. With `--target`, RUSTFLAGS no longer reaches build +scripts and proc-macros (measured: a build script compiled with +`-C debuginfo=2`), so the v4 job also sets `CARGO_PROFILE_DEV_DEBUG=0` and +`CARGO_PROFILE_TEST_DEBUG=0`. + +**Build scripts:** the three images that copy `config-v4.toml` build with +`--target ` (codex P1 on #1529). Without it the cfg-keyed v4 +flags also reach build scripts and proc-macros, which run on the build host +(measured on a scratch crate: the build script compiled with +`target-cpu=x86-64-v4`; with `--target`, with none). + +**Safety:** on a host without AVX-512, ndarray's startup `cpu_guard` names +the missing features and exits before `main`; the binary does not die with +SIGILL mid-run. That holds only for binaries that link ndarray. + +**OPEN:** +- `build.yml` gains `linux-build-v4`, a two-entry matrix (`x86-64-v4`, + `sapphirerapids`): it compiles and links lance-graph's tests for each tier + with `--target`, so build scripts stay at the host baseline, and runs + nothing (most GitHub runners are v3). Both tiers are compiled in CI but + never executed there. ndarray gets the same pair in AdaWorldAPI/ndarray#361. +- `build.yml`, `rust-test.yml` and `style.yml` still RUN tests on a v3 pin + instead of per-runner `native` (the #1526 resolver). +- Probes and examples do not yet refuse to report timings off v4. +- `Dockerfile.avx512` builds `sapphirerapids`; its build stage runs the + tests, so that arm executes on the Railway build host and nowhere else. +- Plain `Dockerfile` builds `native` (maintainer's decision). It tunes to the + build host; if the run host lacks a compiled-in feature, ndarray's startup + `cpu_guard` stops the binary before `main`, and ndarray#360 warns at build + time about enabled features the guard has no row for. +- `Dockerfile` and `Dockerfile.avx512` could not resolve dependencies: they + provided neither sibling the workspace path-deps (ndarray, OGAR) and did not + copy the workspace member `tools/dto-class-check`. Both now clone ndarray and + OGAR at their default branches (`master`, `main`) to `/ndarray` and `/OGAR` + and copy `tools/`. Checked in a Docker-shaped layout: every member and path + dependency resolves (cargo reached locking 879 packages; only `--offline` + stopped the download). `Dockerfile.hhtld`'s fallback fetched ndarray's + `main` branch, which does not exist; it now fetches `master`. +- Not verified with `docker build` here. + +**REVISIT WHEN:** a production host without AVX-512 appears. diff --git a/.claude/board/entries/README.md b/.claude/board/entries/README.md index 7cb1c22f4..77bb272d1 100644 --- a/.claude/board/entries/README.md +++ b/.claude/board/entries/README.md @@ -25,10 +25,11 @@ index row, (3) no duplicate entry id. Checks 1 and 2 are deliberately opposite directions; the stranding this convention prevents shows up in exactly one of them, never both. -325 entries, 2026-08-06 .. 2026-10-12. +326 entries, 2026-08-06 .. 2026-10-13. | date | entry id | finding | file | |---|---|---|---| +| 2026-10-13 | `D-PROD-V4` | | [2026-10-13-production-targets-v4.md](2026-10-13-production-targets-v4.md) | | 2026-10-12 | `D-SAG-1` | | [2026-10-12-software-awareness-graph.md](2026-10-12-software-awareness-graph.md) | | 2026-10-12 | `D-CMR-RESET` | method layer was a second algebra beside the folds and slower than a row loop (430–480 µs vs 283 µs); moved to backup, rebuild as bindings over existing folds | [2026-10-12-session-reset-inventory.md](2026-10-12-session-reset-inventory.md) | | 2026-10-12 | `D-SAG-5-0` | Post-#1509 SAG self-querying, provenance and limited attention with NARS/Hyperon/PLN comparisons; research prompt and high-signal falsifiers | [2026-10-12-sag-reflexive-cognition-research.md](2026-10-12-sag-reflexive-cognition-research.md) | diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 90da7f3a0..5ed2cdbba 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -83,3 +83,70 @@ jobs: run: cargo test --manifest-path crates/lance-graph/Cargo.toml - name: Check benchmarks run: cargo check --manifest-path crates/lance-graph/Cargo.toml --benches + + # Production runs x86-64-v4, but GitHub's runners are mostly v3, so the v4 + # arm cannot be executed here. This job compiles and links it without + # running anything. + # + # `--target` is load-bearing: without it RUSTFLAGS also reaches build + # scripts and proc-macros, which DO run on this host, so a v3 runner could + # die with SIGILL in the middle of the build. With an explicit target, + # RUSTFLAGS applies only to target artifacts (measured: the build script + # compiles with no target-cpu, the crate with x86-64-v4). + # + # Two tiers: x86-64-v4 (the floor production runs on) and sapphirerapids + # (Dockerfile.avx512, the canonical deploy; Railway hosts are Sapphire + # Rapids, Emerald Rapids or newer). Both compile, neither runs. + linux-build-v4: + name: linux-build-v4 (${{ matrix.cpu }}) + runs-on: ubuntu-24.04 + timeout-minutes: 30 + strategy: + fail-fast: false + matrix: + cpu: [x86-64-v4, sapphirerapids] + env: + RUSTFLAGS: "-C debuginfo=0 -C target-cpu=${{ matrix.cpu }}" + # Runner disk makes debug 0 mandatory. With `--target`, RUSTFLAGS no + # longer reaches build scripts and proc-macros, so their debug level + # comes from the profile; these pin it to 0 for every artifact. + CARGO_PROFILE_DEV_DEBUG: "0" + CARGO_PROFILE_TEST_DEBUG: "0" + CARGO_INCREMENTAL: "0" + defaults: + run: + working-directory: lance-graph + steps: + - uses: actions/checkout@v4 + with: + path: lance-graph + - name: Checkout AdaWorldAPI/ndarray (sibling dependency) + uses: actions/checkout@v4 + with: + repository: AdaWorldAPI/ndarray + path: ndarray + - uses: actions/checkout@v4 + with: + repository: AdaWorldAPI/OGAR + path: OGAR + - name: Setup rust toolchain (pinned by rust-toolchain.toml) + run: rustup show + - name: Setup mold linker + uses: rui314/setup-mold@9c9c13bf4c3f1adef0cc596abc155580bcb04444 # v1 + - uses: Swatinem/rust-cache@v2 + with: + shared-key: "lance-graph-deps-${{ matrix.cpu }}" + workspaces: | + lance-graph/crates/lance-graph + - name: Install dependencies + run: | + sudo apt update + sudo apt install -y protobuf-compiler + - name: Check the target really is AVX-512 + run: | + rustc --print cfg -C target-cpu=${{ matrix.cpu }} | grep -qx 'target_feature="avx512f"' + echo "Built for ${{ matrix.cpu }} (not run). Runner CPU: $(grep -m1 'model name' /proc/cpuinfo | cut -d: -f2)" >> "$GITHUB_STEP_SUMMARY" + - name: Build tests for ${{ matrix.cpu }} (no run) + run: cargo test --manifest-path crates/lance-graph/Cargo.toml --target x86_64-unknown-linux-gnu --no-run + - name: Check benchmarks for ${{ matrix.cpu }} + run: cargo check --manifest-path crates/lance-graph/Cargo.toml --target x86_64-unknown-linux-gnu --benches diff --git a/Dockerfile b/Dockerfile index f8ba785ab..6591218d9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,9 +1,12 @@ -# lance-graph — Railway compile-test image (AVX2 default) +# lance-graph — Railway compile-test image (target-cpu=native, the build host) # Verifies the workspace builds cleanly (core + bgz17 + planner + contract) # Requires Rust 1.99.0 (LazyLock, modern std APIs) # # CPU detection & SIMD dispatch documentation: see Dockerfile.md -# AVX-512 pinned variant: see Dockerfile.avx512 +# Dockerfile.avx512 is the canonical deploy file (sapphirerapids). This image +# is the fallback for a deploy that does not name that file: it builds for the +# build host's own CPU, so it takes whatever that host has. ndarray's startup +# cpu_guard stops the binary on a run host that lacks a compiled-in feature. # # Build: docker build -t lance-graph-test . # Run: docker run --rm lance-graph-test @@ -12,7 +15,7 @@ FROM debian:bookworm-slim AS builder # System deps for arrow/lance/protobuf RUN apt-get update && apt-get install -y --no-install-recommends \ - curl ca-certificates gcc libc6-dev pkg-config libssl-dev \ + curl ca-certificates gcc libc6-dev pkg-config libssl-dev git \ protobuf-compiler cmake \ && rm -rf /var/lib/apt/lists/* @@ -24,6 +27,14 @@ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ sh -s -- -y --default-toolchain 1.99.0 --profile minimal \ && rustc --version | grep -q "1.99.0" +# lance-graph path-deps two sibling repos: ndarray (`../ndarray` from the +# workspace root, `../../../ndarray` from a crate) and OGAR. With the +# workspace at /app both resolve to the filesystem root, so they are cloned +# to /ndarray and /OGAR. No branch is named: each repo's default branch +# (ndarray `master`, OGAR `main`) is the floating head the CI jobs also build. +RUN git clone --depth 1 https://github.com/AdaWorldAPI/ndarray.git /ndarray \ + && git clone --depth 1 https://github.com/AdaWorldAPI/OGAR.git /OGAR + WORKDIR /app # Copy workspace manifest (no lock — see ISS-STALE-AUTHORITY-LOCKS) @@ -40,11 +51,12 @@ COPY crates/bgz17/Cargo.toml crates/bgz17/Cargo.toml # Copy source COPY crates/ crates/ +# Workspace member outside crates/ (the root Cargo.toml lists it). +COPY tools/ tools/ -# Default target: x86-64-v3 (AVX2) — runs on GitHub CI and most servers. -# Use Dockerfile.avx512 for x86-64-v4 (AVX-512) on Skylake-X / Ice Lake / Sapphire Rapids. -# The .cargo/config.toml pins x86-64-v4 for LOCAL builds; override here for portability. -ENV RUSTFLAGS="-C target-cpu=x86-64-v3" +# The build host's own CPU, the same default as the repo's .cargo/config.toml +# (which this image does not copy, so it is set here). +ENV RUSTFLAGS="-C target-cpu=native" # Build bgz17 standalone (zero deps, fast check) RUN cargo build --release --manifest-path crates/bgz17/Cargo.toml 2>&1 \ diff --git a/Dockerfile.avx512 b/Dockerfile.avx512 index 154d4d9fb..b87787d81 100644 --- a/Dockerfile.avx512 +++ b/Dockerfile.avx512 @@ -1,11 +1,18 @@ -# lance-graph — AVX-512 pinned build (server/Railway) -# target-cpu=x86-64-v4: native AVX-512, all SIMD inlined. -# Consumes ndarray with same AVX-512 pinning. +# lance-graph — Sapphire Rapids build (server/Railway) +# target-cpu=sapphirerapids: the x86-64-v4 AVX-512 baseline plus VNNI, BF16, +# FP16, VBMI2, BITALG and VPOPCNTDQ (AMX is runtime-detected by ndarray; +# stable rustc does not expose it as a cfg). Railway hosts are Sapphire Rapids, +# Emerald Rapids or newer; all of them carry these features. # -# ONLY deploy on AVX-512 hardware. +# ONLY deploy on Sapphire Rapids or newer. On an older host ndarray's startup +# cpu_guard names the missing features and exits before main. +# +# The build stage runs the tests, so they execute on the build host: a build +# that lands on an older host fails here instead of in production. # # CPU detection & SIMD dispatch documentation: see Dockerfile.md -# Portable (AVX2) variant: see Dockerfile +# This is the canonical deploy file. Dockerfile builds for the build host's +# CPU (native) as the fallback when a deploy does not name this one. # # Build: docker build -f Dockerfile.avx512 -t lance-graph-avx512 . # Run: docker run --rm lance-graph-avx512 @@ -13,7 +20,7 @@ FROM debian:bookworm-slim AS builder RUN apt-get update && apt-get install -y --no-install-recommends \ - curl ca-certificates gcc libc6-dev pkg-config libssl-dev \ + curl ca-certificates gcc libc6-dev pkg-config libssl-dev git \ protobuf-compiler cmake \ && rm -rf /var/lib/apt/lists/* @@ -24,13 +31,23 @@ RUN curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | \ sh -s -- -y --default-toolchain 1.99.0 --profile minimal \ && rustc --version | grep -q "1.99.0" +# lance-graph path-deps two sibling repos: ndarray (`../ndarray` from the +# workspace root, `../../../ndarray` from a crate) and OGAR. With the +# workspace at /app both resolve to the filesystem root, so they are cloned +# to /ndarray and /OGAR. No branch is named: each repo's default branch +# (ndarray `master`, OGAR `main`) is the floating head the CI jobs also build. +RUN git clone --depth 1 https://github.com/AdaWorldAPI/ndarray.git /ndarray \ + && git clone --depth 1 https://github.com/AdaWorldAPI/OGAR.git /OGAR + WORKDIR /app COPY Cargo.toml ./ COPY crates/ crates/ +# Workspace member outside crates/ (the root Cargo.toml lists it). +COPY tools/ tools/ -# AVX-512 pinned: compile-time dispatch, everything inlined -ENV RUSTFLAGS="-C target-cpu=x86-64-v4" +# Compile-time dispatch for Sapphire Rapids, everything inlined +ENV RUSTFLAGS="-C target-cpu=sapphirerapids" # bgz17 standalone (zero deps, fast check) RUN cargo build --release --manifest-path crates/bgz17/Cargo.toml 2>&1 \ @@ -52,4 +69,4 @@ RUN cargo test --release \ FROM debian:bookworm-slim RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates && rm -rf /var/lib/apt/lists/* -CMD ["echo", "lance-graph AVX-512 build verified — Rust 1.99.0, target-cpu=x86-64-v4"] +CMD ["echo", "lance-graph build verified — Rust 1.99.0, target-cpu=sapphirerapids"] diff --git a/Dockerfile.hhtld b/Dockerfile.hhtld index 7927a30ea..d59bccdb6 100644 --- a/Dockerfile.hhtld +++ b/Dockerfile.hhtld @@ -43,26 +43,42 @@ COPY . /build/lance-graph # If building in CI, clone it here: RUN if [ ! -d /build/ndarray ]; then \ echo "WARNING: ndarray not found, attempting clone..." && \ - curl -sL "https://github.com/AdaWorldAPI/ndarray/archive/refs/heads/main.tar.gz" | \ + curl -sL "https://github.com/AdaWorldAPI/ndarray/archive/refs/heads/master.tar.gz" | \ tar xz && mv ndarray-* /build/ndarray || true; \ fi WORKDIR /build/lance-graph -# Portable baseline. The repo's .cargo/config.toml defaults to `native` -# (tuned to the build host); an image must run on any x86_64 since Haswell. -# The copied tree's config is replaced by config-v3.toml, which only applies +# Production target. The repo's .cargo/config.toml defaults to `native` +# (tuned to the build host); production always runs on x86-64-v4, so the +# copied tree's config is replaced by config-v4.toml. That file only applies # to x86_64 targets, so an arm64 build keeps its own aarch64 baseline (a # global RUSTFLAGS would hand the aarch64 compiler an x86 CPU name). -RUN cp .cargo/config-v3.toml .cargo/config.toml +RUN cp .cargo/config-v4.toml .cargo/config.toml + +# `--target ` keeps the v4 flags on the binary only. Without it +# cargo also compiles build scripts and proc-macros for v4 and runs them +# here, so a build host without AVX-512 would die with SIGILL mid-build. +# With an explicit target they stay at the host baseline. The binaries then +# land under target// and are copied to the path the runtime stage +# reads. +# thinking-engine is excluded from the workspace, so CARGO_TARGET_DIR pins +# the output to the directory the runtime stage copies from. +ENV CARGO_TARGET_DIR=/build/lance-graph/target # Build the decode example (the inference binary) -RUN cargo build --release --example tts_17b_hhtld_decode \ - --manifest-path crates/thinking-engine/Cargo.toml 2>&1 || \ +RUN T="$(rustc -vV | sed -n 's/^host: //p')" \ + && cargo build --release --target "$T" --example tts_17b_hhtld_decode \ + --manifest-path crates/thinking-engine/Cargo.toml 2>&1 \ + && mkdir -p target/release/examples \ + && cp "target/$T/release/examples/tts_17b_hhtld_decode" target/release/examples/ || \ echo "Build failed — ndarray dependency may be missing" # Also build the cascade runner if available -RUN cargo build --release --example tts_cascade_runner \ - --manifest-path crates/thinking-engine/Cargo.toml 2>&1 || true +RUN T="$(rustc -vV | sed -n 's/^host: //p')" \ + && cargo build --release --target "$T" --example tts_cascade_runner \ + --manifest-path crates/thinking-engine/Cargo.toml 2>&1 \ + && mkdir -p target/release/examples \ + && cp "target/$T/release/examples/tts_cascade_runner" target/release/examples/ || true # ═══════════════════════════════════════════════════════════════════ # Stage 2: Runtime diff --git a/Dockerfile.md b/Dockerfile.md index dd0ec3f30..3c5214d37 100644 --- a/Dockerfile.md +++ b/Dockerfile.md @@ -1,13 +1,19 @@ # lance-graph Docker CPU Detection & SIMD Dispatch -## Three-Tier Build Strategy +## Build Targets -| Target | Dockerfile | RUSTFLAGS | Use case | +Production always runs on x86-64-v4 (AVX-512) or better. Railway hosts are +Sapphire Rapids, Emerald Rapids or newer, so the canonical deploy file builds +for `sapphirerapids`, the fallback `Dockerfile` builds `native` (the build +host), and every other image and the wheel build for v4. + +| Target | File | Flags | Use case | |---|---|---|---| -| **Portable (AVX2)** | `Dockerfile` | `-C target-cpu=x86-64-v3` | GitHub CI, general servers | -| **AVX-512 pinned** | `Dockerfile.avx512` | `-C target-cpu=x86-64-v4` | Production (Skylake-X+) | -| **HHTL-D TTS** | `Dockerfile.hhtld` | (inherits) | TTS inference container | -| **Local dev** | `.cargo/config.toml` | `-C target-cpu=x86-64-v4` | Developer machines | +| **Deploy (canonical)** | `Dockerfile.avx512` | `-C target-cpu=sapphirerapids` | Railway / production (Sapphire Rapids, Emerald Rapids or newer) | +| **Fallback** | `Dockerfile` | `-C target-cpu=native` | Compile/test image; a deploy that forgets the file name gets the build host's CPU | +| **HHTL-D TTS** | `Dockerfile.hhtld` | `.cargo/config-v4.toml` | TTS inference container | +| **Local dev** | `.cargo/config.toml` | `-C target-cpu=native` | Developer machines (host CPU) | +| **CI tests** | `build.yml`, `rust-test.yml`, `style.yml` | `-C target-cpu=x86-64-v3` | Not production; never cite a timing from it | ## How lance-graph Uses SIMD @@ -52,14 +58,16 @@ Polyfill (ndarray simd.rs): **Important:** `RUSTFLAGS` env var **replaces** (not appends to) the `rustflags` array in `.cargo/config.toml`. This is a Cargo design decision. -lance-graph's `.cargo/config.toml` sets `target-cpu=x86-64-v4` for local dev. -CI workflows set `RUSTFLAGS="-C debuginfo=1 -C target-cpu=x86-64-v3"` which -**overrides** config.toml entirely. The CI binary targets AVX2. +lance-graph's `.cargo/config.toml` sets `target-cpu=native` for local dev. +The test workflows set `RUSTFLAGS="-C debuginfo=0 -C target-cpu=x86-64-v3"`, +which **overrides** config.toml entirely, so those CI binaries target AVX2. -This is intentional: -- Local dev: maximum SIMD (AVX-512, everything inlined) -- CI: portable (AVX2, runtime detection for anything higher) -- Production Docker: choose `Dockerfile` (AVX2) or `Dockerfile.avx512` +- Local dev: the host CPU (`native`). +- CI tests: AVX2. Production does not run this arm, so a timing from it is + not evidence. +- Production: x86-64-v4, in every Dockerfile and the wheel. On a host without + AVX-512, ndarray's startup `cpu_guard` names the missing features and exits + before `main` instead of dying with SIGILL. ## AMX Detection @@ -77,11 +85,9 @@ lance-graph inherits this via ndarray; no ARM-specific configuration needed. ## Choosing the Right Dockerfile ``` -GitHub CI / PR checks → Dockerfile (AVX2, -C target-cpu=x86-64-v3) -Railway / production → Dockerfile.avx512 (-C target-cpu=x86-64-v4) -TTS inference → Dockerfile.hhtld (downloads codebooks + runs decoder) -Raspberry Pi / ARM → Dockerfile (NEON auto-detected at runtime) -Maximum compatibility → docker build --build-arg RUSTFLAGS="-C target-cpu=x86-64" +Railway / production → Dockerfile.avx512 (sapphirerapids, canonical); Dockerfile (native) is the fallback +TTS inference → Dockerfile.hhtld (config-v4.toml; downloads codebooks) +Raspberry Pi / ARM → Dockerfile.hhtld (config-v4.toml applies to x86_64 only) ``` ## Verifying CPU Features @@ -98,12 +104,9 @@ println!("{:?}", simd_caps()); // CpuCaps { avx512: true, avx2: true, fma: true ## Build Examples ```bash -# Default (AVX2) — safe everywhere +# Production tier (x86-64-v4) docker build -t lance-graph-test . -# AVX-512 pinned — production servers -docker build -f Dockerfile.avx512 -t lance-graph-avx512 . - # TTS inference docker build -f Dockerfile.hhtld \ --build-arg RELEASE_TAG=v0.1.0 \ diff --git a/crates/cognitive-stack/Dockerfile b/crates/cognitive-stack/Dockerfile index acdbfffeb..06a96a4c4 100644 --- a/crates/cognitive-stack/Dockerfile +++ b/crates/cognitive-stack/Dockerfile @@ -35,12 +35,12 @@ WORKDIR /build # The lance-graph repo (this build context). cognitive-stack path-deps the # in-repo crates (lance-graph, the four template crates, lance-graph-ogar). COPY . /build/lance-graph -# Portable baseline. The repo's .cargo/config.toml defaults to `native` -# (tuned to the build host); an image must run on any x86_64 since Haswell. -# The copied tree's config is replaced by config-v3.toml, which only applies +# Production target. The repo's .cargo/config.toml defaults to `native` +# (tuned to the build host); production always runs on x86-64-v4, so the +# copied tree's config is replaced by config-v4.toml. That file only applies # to x86_64 targets, so an arm64 build keeps its own aarch64 baseline (a # global RUSTFLAGS would hand the aarch64 compiler an x86 CPU name). -RUN cp /build/lance-graph/.cargo/config-v3.toml /build/lance-graph/.cargo/config.toml +RUN cp /build/lance-graph/.cargo/config-v4.toml /build/lance-graph/.cargo/config.toml # lance-graph path-deps the ndarray fork as a SIBLING (`../../../ndarray`). # Clone it next to the repo so that path resolves. @@ -48,7 +48,16 @@ RUN git clone --depth 1 --branch "$ADA_BRANCH" \ https://github.com/AdaWorldAPI/ndarray.git /build/ndarray WORKDIR /build/lance-graph/crates/cognitive-stack -RUN cargo build --release +# `--target ` keeps the v4 flags on the binary only. Without it +# cargo also compiles build scripts and proc-macros for v4 and runs them +# here, so a build host without AVX-512 would die with SIGILL mid-build. +# With an explicit target they stay at the host baseline. The binaries then +# land under target// and are copied to the path the runtime stage +# reads. +RUN T="$(rustc -vV | sed -n 's/^host: //p')" \ + && cargo build --release --target "$T" \ + && mkdir -p target/release \ + && cp "target/$T/release/cognitive-stack" target/release/ # ── runtime ────────────────────────────────────────────────────────────────── FROM debian:bookworm-slim diff --git a/crates/symbiont/Dockerfile b/crates/symbiont/Dockerfile index c54144a36..be728ca1a 100644 --- a/crates/symbiont/Dockerfile +++ b/crates/symbiont/Dockerfile @@ -35,12 +35,12 @@ WORKDIR /build # The lance-graph repo (this build context). symbiont path-deps lance-graph + # perturbation-sim from inside it. COPY . /build/lance-graph -# Portable baseline. The repo's .cargo/config.toml defaults to `native` -# (tuned to the build host); an image must run on any x86_64 since Haswell. -# The copied tree's config is replaced by config-v3.toml, which only applies +# Production target. The repo's .cargo/config.toml defaults to `native` +# (tuned to the build host); production always runs on x86-64-v4, so the +# copied tree's config is replaced by config-v4.toml. That file only applies # to x86_64 targets, so an arm64 build keeps its own aarch64 baseline (a # global RUSTFLAGS would hand the aarch64 compiler an x86 CPU name). -RUN cp /build/lance-graph/.cargo/config-v3.toml /build/lance-graph/.cargo/config.toml +RUN cp /build/lance-graph/.cargo/config-v4.toml /build/lance-graph/.cargo/config.toml # lance-graph's own Cargo.toml path-deps the ndarray fork as a SIBLING # (`../../../ndarray`). Clone it next to the repo so that path resolves. @@ -48,7 +48,16 @@ RUN git clone --depth 1 --branch "$ADA_BRANCH" \ https://github.com/AdaWorldAPI/ndarray.git /build/ndarray WORKDIR /build/lance-graph/crates/symbiont -RUN cargo build --release +# `--target ` keeps the v4 flags on the binary only. Without it +# cargo also compiles build scripts and proc-macros for v4 and runs them +# here, so a build host without AVX-512 would die with SIGILL mid-build. +# With an explicit target they stay at the host baseline. The binaries then +# land under target// and are copied to the path the runtime stage +# reads. +RUN T="$(rustc -vV | sed -n 's/^host: //p')" \ + && cargo build --release --target "$T" \ + && mkdir -p target/release \ + && cp "target/$T/release/symbiont" target/release/ # ── runtime ────────────────────────────────────────────────────────────────── FROM debian:bookworm-slim diff --git a/crates/thinking-engine/Dockerfile b/crates/thinking-engine/Dockerfile index 125a69359..f58e3261e 100644 --- a/crates/thinking-engine/Dockerfile +++ b/crates/thinking-engine/Dockerfile @@ -1,8 +1,8 @@ FROM rust:1.98.1-bookworm AS builder -# Portable baseline for x86_64 only: this image does not copy .cargo, so the -# per-target variable is the only flag source, and it does not apply when -# building for another architecture. -ENV CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS="-C target-cpu=x86-64-v3" +# Production target (x86-64-v4) for x86_64 only: this image does not copy +# .cargo, so the per-target variable is the only flag source, and it does not +# apply when building for another architecture. +ENV CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS="-C target-cpu=x86-64-v4" WORKDIR /app # Dependencies first (cached layer) diff --git a/crates/thinking-engine/Dockerfile.railway b/crates/thinking-engine/Dockerfile.railway index d54e5489a..cd8adbfe2 100644 --- a/crates/thinking-engine/Dockerfile.railway +++ b/crates/thinking-engine/Dockerfile.railway @@ -7,10 +7,10 @@ # The codebooks run standalone for reasoning at 5,676 queries/sec FROM rust:1.98.1-bookworm AS builder -# Portable baseline for x86_64 only: this image does not copy .cargo, so the -# per-target variable is the only flag source, and it does not apply when -# building for another architecture. -ENV CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS="-C target-cpu=x86-64-v3" +# Production target (x86-64-v4) for x86_64 only: this image does not copy +# .cargo, so the per-target variable is the only flag source, and it does not +# apply when building for another architecture. +ENV CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUSTFLAGS="-C target-cpu=x86-64-v4" WORKDIR /app # Copy workspace diff --git a/python/Makefile b/python/Makefile index eb8b886b0..48bd880af 100644 --- a/python/Makefile +++ b/python/Makefile @@ -25,10 +25,18 @@ clean: rm -rf ../target # The repo's .cargo/config.toml defaults to `native`, which would tune the -# wheel to the build host; a published x86_64 wheel must run on any x86_64 -# since Haswell. RUSTFLAGS replaces the config's flags; it is set only on an -# x86_64 host so an arm64 build keeps its own baseline. -PORTABLE_RUSTFLAGS := $(if $(filter x86_64,$(shell uname -m)),RUSTFLAGS="-C target-cpu=x86-64-v3",) +# wheel to the build host; production runs on x86-64-v4, so the wheel is +# built for exactly that. RUSTFLAGS replaces the config's flags; it is set +# only on an x86_64 host so an arm64 build keeps its own baseline. +# +# `--target ` is load-bearing: without it RUSTFLAGS also reaches +# build scripts and proc-macros (serde, pyo3, tokio), which run on this host, +# so a publishing machine without AVX-512 would die with SIGILL. With an +# explicit target the flags reach only the wheel's own code. +IS_X86_64 := $(filter x86_64,$(shell uname -m)) +HOST_TRIPLE := $(shell rustc -vV | sed -n 's/^host: //p') +PORTABLE_RUSTFLAGS := $(if $(IS_X86_64),RUSTFLAGS="-C target-cpu=x86-64-v4",) +PORTABLE_TARGET := $(if $(IS_X86_64),--target $(HOST_TRIPLE),) publish: - $(PORTABLE_RUSTFLAGS) maturin build --release --out dist --sdist + $(PORTABLE_RUSTFLAGS) maturin build --release --out dist --sdist $(PORTABLE_TARGET)