diff --git a/.github/workflows/npm-release.yml b/.github/workflows/npm-release.yml index d782e94..72160e2 100644 --- a/.github/workflows/npm-release.yml +++ b/.github/workflows/npm-release.yml @@ -6,7 +6,7 @@ on: version: description: Core and stable Small facade version required: true - default: 0.5.7 + default: 0.5.9 type: string publish_to_registry: description: Publish the tested candidate to npm under next @@ -14,7 +14,7 @@ on: default: false type: boolean npu_sdk_run_id: - description: Optional run containing npu-release-sdks (AMD requires explicit provider configuration) + description: Optional override containing both locked Intel and AMD deployment SDKs required: false default: "" type: string @@ -47,6 +47,10 @@ jobs: python tools/npm_release.py ensure-unpublished --version "$RELEASE_VERSION" build-native: + # The locked AMD asset is attached to a draft until release publication. + permissions: + contents: write + actions: read needs: validate name: build ${{ matrix.id }} strategy: @@ -97,9 +101,22 @@ jobs: path: dist/npu-sdk run-id: ${{ inputs.npu_sdk_run_id }} github-token: ${{ github.token }} - - name: Verify NPU SDK inventory and runtime identities - if: matrix.id == 'linux-x64' && inputs.npu_sdk_run_id != '' - run: python tools/npu/prepare_release.py --sdk-root dist/npu-sdk --github-env "$GITHUB_ENV" + - name: Prepare packaged Intel and opt-in AMD NPU payloads + if: matrix.id == 'linux-x64' + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + if [[ ! -d dist/npu-sdk/openvino ]]; then + python tools/npu/build_openvino.py \ + --package-cache .cache/npu-packages --output-dir dist/npu-sdk/openvino + fi + if [[ ! -d dist/npu-sdk/amdnpu ]]; then + python tools/npu/fetch_amdnpu.py \ + --package-cache .cache/npu-packages --output-dir dist/npu-sdk/amdnpu + fi + python tools/npu/prepare_release.py --sdk-root dist/npu-sdk --github-env "$GITHUB_ENV" \ + --require-provider openvino --require-provider amdnpu - name: Fetch the pinned PDF renderer for npm bundling (Unix) if: runner.os != 'Windows' shell: bash @@ -343,7 +360,7 @@ jobs: npm install --offline --ignore-scripts --no-audit --no-fund --package-lock=false \ ../dist/release/arcships-light-ocr-model-ppocrv6-small-0.3.4.tgz \ ../dist/release/arcships-light-ocr-${{ matrix.native }}-${RELEASE_VERSION}.tgz \ - ../dist/release/arcships-light-ocr-runtime-0.1.8.tgz \ + ../dist/release/arcships-light-ocr-runtime-0.1.9.tgz \ ../dist/release/arcships-light-ocr-${RELEASE_VERSION}.tgz cp ../tools/npm/smoke.cjs . - name: Run stable Small OCR @@ -377,8 +394,8 @@ jobs: npm install --offline --ignore-scripts --no-audit --no-fund --package-lock=false \ ../dist/release/arcships-light-ocr-model-ppocrv6-tiny-0.1.0.tgz \ ../dist/release/arcships-light-ocr-model-ppocrv6-medium-0.1.0.tgz \ - ../dist/release/arcships-light-ocr-tiny-0.1.7.tgz \ - ../dist/release/arcships-light-ocr-medium-0.1.7.tgz + ../dist/release/arcships-light-ocr-tiny-0.1.8.tgz \ + ../dist/release/arcships-light-ocr-medium-0.1.8.tgz node ../tools/npm/smoke-tier.cjs @arcships/light-ocr-tiny light-ocr-tiny \ ppocrv6-tiny-onnx-20260722.1 node ../tools/npm/smoke-tier.cjs @arcships/light-ocr-medium light-ocr-medium \ @@ -412,7 +429,7 @@ jobs: docker run --rm -v "$PWD:/work" -w /work -e LIGHT_OCR_SMOKE_FIXTURE=/work/corpus/fixtures/generated-hello-123 alpine:3.22 sh .github/scripts/smoke-musl.sh - "${{ matrix.id }}" "${{ inputs.version }}" "0.1.8" + "${{ matrix.id }}" "${{ inputs.version }}" "0.1.9" publish: needs: [test-packages, smoke-musl] @@ -463,7 +480,7 @@ jobs: cd registry-document npm init --yes npm install --ignore-scripts --no-audit --no-fund --package-lock=false \ - "@arcships/light-ocr-document@0.1.4" + "@arcships/light-ocr-document@0.1.5" node ../tools/npm/document-smoke.cjs - name: Verify the published stable package offline shell: bash diff --git a/.github/workflows/npu-native.yml b/.github/workflows/npu-native.yml index 89556dc..94c25dd 100644 --- a/.github/workflows/npu-native.yml +++ b/.github/workflows/npu-native.yml @@ -20,6 +20,10 @@ permissions: jobs: build: + # Draft release assets are visible only to tokens with push access. + permissions: + contents: write + actions: read runs-on: ubuntu-24.04 steps: - uses: actions/checkout@34e114876b0b11c390a56381ad16ebd13914f8d5 @@ -39,6 +43,11 @@ jobs: path: dist/npu-sdk/amdnpu run-id: ${{ inputs.amd_sdk_run_id }} github-token: ${{ github.token }} + - name: Fetch locked AMD deployment runtime and compiled recognition models + if: inputs.amd_sdk_run_id == '' + env: + GH_TOKEN: ${{ github.token }} + run: python tools/npu/fetch_amdnpu.py --package-cache .cache/npu --output-dir dist/npu-sdk/amdnpu - name: Build the native addon with packaged NPU SDKs shell: bash run: | diff --git a/CHANGELOG.md b/CHANGELOG.md index fb732fc..398b4c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,27 +4,32 @@ This file records user-visible changes to `light-ocr`. Published artifact detail ## [Unreleased] +## [0.5.9] - Unreleased + +Release preparation; npm publication and GitHub Release are pending. + ### Added -- Added a source-level Intel NPU backend (`provider: "openvino"`) for Linux x64. +- Added a bundled Intel NPU backend (`provider: "openvino"`) for Linux x64. Builds configured with `LIGHT_OCR_OPENVINO_SDK_DIR` load the OpenVINO C runtime at run time, run detection and 20-bucket recognition on the NPU, and place `openvino` first in the package Auto policy (`openvino → webgpu → cpu`); hosts without an NPU skip it with `adapter_unavailable`. Added a pinned native SDK extractor, package-local runtime descriptor 2.1, numeric driver/compiler checks, and reviewed-evidence - release staging with optional device reports. Published npm packages do not include OpenVINO yet, so - `provider: "openvino"` returns `unsupported_capability` there. See + release staging with optional device reports. The 0.5.9 Linux x64 build includes + pinned OpenVINO 2026.4.0 libraries. See [Intel NPU 加速技术方案](docs/intel-npu-acceleration.md). -- Added a source-level AMD NPU backend (`provider: "amdnpu"`) for Linux x64 +- Added a bundled AMD NPU backend (`provider: "amdnpu"`) for Linux x64 glibc and Ryzen AI STX/KRK devices. It isolates the vendor ORT runtime, loads 20 immutable BF16 recognition contexts, and runs detection on CPU. Added model compilation and SDK import tools, descriptor validation, license/SBOM staging, and candidate/release workflows. AMD is excluded from Auto and enabled only through `execution.provider: "amdnpu"` (or `--provider amdnpu`). NPU device reports are optional for release; - `deviceValidated` remains false. Current published packages - return `unsupported_capability`. See [AMD NPU 加速技术方案](docs/amd-npu-acceleration.md) + `deviceValidated` remains false. The 0.5.9 Linux x64 build includes Ryzen AI 1.8 + deployment libraries and real compiled contexts bound to Small 0.3.4. + Other platforms retain their existing providers. See [AMD NPU 加速技术方案](docs/amd-npu-acceleration.md) and [NPU SDK 构建与发布](docs/npu-runtime-release.md). ## [0.5.8] - 2026-09-24 diff --git a/README.md b/README.md index cf4dec3..e17cfd9 100644 --- a/README.md +++ b/README.md @@ -235,6 +235,8 @@ selection guidance, and exit code reference: - [Build and release](docs/build-and-release.md) - [Roadmap](docs/roadmap.md) - [Changelog](CHANGELOG.md) +- [npm 0.5.9 release preparation — English](docs/releases/npm-0.5.9.en.md) +- [npm 0.5.9 发布准备 — 中文](docs/releases/npm-0.5.9.md) - [npm 0.5.8 release record — English](docs/releases/npm-0.5.8.en.md) - [npm 0.5.8 发布记录 — 中文](docs/releases/npm-0.5.8.md) - [npm 0.5.7 release record — English](docs/releases/npm-0.5.7.en.md) diff --git a/README.zh-CN.md b/README.zh-CN.md index fd18bc9..3585f2b 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -226,6 +226,8 @@ AI Agent 使用。它提供场景驱动的工作流、命令选择建议和退 - [构建与发布](docs/build-and-release.md) - [路线图](docs/roadmap.md) - [更新日志](CHANGELOG.md) +- [npm 0.5.9 release preparation — English](docs/releases/npm-0.5.9.en.md) +- [npm 0.5.9 发布准备 — 中文](docs/releases/npm-0.5.9.md) - [npm 0.5.8 release record — English](docs/releases/npm-0.5.8.en.md) - [npm 0.5.8 发布记录 — 中文](docs/releases/npm-0.5.8.md) - [npm 0.5.7 release record — English](docs/releases/npm-0.5.7.en.md) diff --git a/VERSION b/VERSION index b35b9dd..416bfb0 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.5.8 \ No newline at end of file +0.5.9 diff --git a/bindings/node/test/cli.test.cjs b/bindings/node/test/cli.test.cjs index 67f2089..bca89c2 100644 --- a/bindings/node/test/cli.test.cjs +++ b/bindings/node/test/cli.test.cjs @@ -108,7 +108,7 @@ test('info: --version is metadata-only and reports the Small tier', async () => assert.equal(code, EXIT.success); assert.equal(stderr, ''); const info = JSON.parse(stdout); - assert.equal(info.core, '0.5.8'); + assert.equal(info.core, '0.5.9'); assert.equal(info.tier, 'small'); assert.equal(info.maturity, 'stable'); assert.equal(info.model, 'ppocrv6-small-native-20260719.1'); diff --git a/docs/amd-npu-acceleration.md b/docs/amd-npu-acceleration.md index 12875ab..b78b34c 100644 --- a/docs/amd-npu-acceleration.md +++ b/docs/amd-npu-acceleration.md @@ -1,6 +1,6 @@ # AMD NPU 加速技术方案 -更新时间:2026-10-06。状态:Linux x64 glibc 后端与打包改造已实现,默认关闭、配置显式开启;Ryzen AI SDK 与编译产物尚缺,真机验收不再作为发布前置。当前已发布 npm 包不含 AMD NPU 运行时。 +更新时间:2026-10-06。状态:Linux x64 glibc 后端与打包改造已实现,默认关闭、配置显式开启;0.5.9 发布输入已包含 Ryzen AI 1.8 的原生部署库与 Small 0.3.4 的 20 个真实 BF16 context。无需真机即可完成编译,尚未执行 AMD 设备推理。正式 npm 发布仍待完成。 ## 范围与模型路线 @@ -16,22 +16,21 @@ BF16 路线由 VAIML 编译 FP32 ONNX,生成可嵌入权重与微码的 EPCont - 校验源识别模型哈希、VAIML 配置和 20 个静态宽度桶;识别形状为 `[1,3,48,width]`,批次为 1。编译结果的字典类别数必须与 bundle 一致。 - 设备检查要求可读写的 AMD STX/KRK `/dev/accel` 节点;无设备返回 `adapter_unavailable`。库或模型哈希不符属于不可恢复错误,不能静默跳到 CPU。 - detector 使用 CPU,recognition 使用 `VitisAIExecutionProvider` 和其允许的 CPU 分区。只有首次会话及对应桶实际创建成功后才选中该候选;后续推理失败直接报告错误。 -- `tools/npu/compile_amdnpu.py`:在 vendor SDK Python 环境编译所有桶,要求存在嵌入式 VitisAI EPContext,并重新打开部署产物。 +- `tools/npu/compile_amdnpu.py`:在 vendor SDK Python 环境编译所有桶,要求存在嵌入式 VitisAI EPContext,并检查输入输出类型与形状;编译过程不执行模型推理,不要求真机验收。 - `tools/npu/import_amdnpu.py`:从已取得的 SDK 部署目录导入原生库,设置包内 RPATH、检查 ELF 依赖闭包、记录变更前后哈希,并锁定模型、配置及许可证。 - CMake、Node descriptor 2.1、JS loader、npm staging、SBOM 和候选/正式发布流程均已接入。构建入口与报告格式见 [NPU SDK 构建与发布](npu-runtime-release.md)。 ## 默认关闭与发布策略 -AMD 始终排除在 Auto 候选列表之外;用户通过 `execution.provider: "amdnpu"`、CLI `--provider amdnpu` 或 C++ 的 `ExecutionProvider::amdnpu` 显式开启。构建必须含 vendor SDK 与匹配的识别模型;运行时未配置 AMD 时不装载 vendor ORT。 +AMD 始终排除在 Auto 候选列表之外;用户通过 `execution.provider: "amdnpu"`、CLI `--provider amdnpu` 或 C++ 的 `ExecutionProvider::amdnpu` 显式开启。0.5.9 Linux x64 发布构建默认附带 vendor SDK 与匹配的 Small 识别模型;运行时未配置 AMD 时不装载 vendor ORT。 按维护者决定,NPU 真机验收不再阻塞发布。SDK 导入的 `qualificationOnly: true` 仅记录尚无设备证据,平台包可随其他已满足条件的运行时一起发布;`deviceValidated` 保持 `false`。库存、模型哈希、依赖闭包和许可证校验继续执行,设备报告可选。 ## 尚未验证的设备行为(可选后续工作) -1. 取得 Ryzen AI 1.8 Linux SDK 和 STX/KRK 主机;确认独立 namespace 装载、XRT/amdxdna 驱动及 VitisAI C API 20 能协同工作。 -2. 编译真实 PP-OCRv6 模型,核对 20 桶的 NPU 分区与 FP32 CPU goldens;当前没有生成或伪造这些产物。 -3. 对完整 locked corpus 记录逐行文本、框与置信度差异,审阅 BF16 误差;测量冷/热延迟、CPU 时间和 RSS。 -4. 覆盖 20 次关闭/重建、驱动兼容、权限不足、无设备、损坏产物,以及与 WebGPU 共存和回退。 -5. 核实 SDK/模型再分发条款和完整第三方 notices,再创建 accepted SDK。 +1. 在 STX/KRK 主机确认独立 namespace 装载、XRT/amdxdna 驱动及 VitisAI C API 20 协同工作。 +2. 比较已编译 20 桶模型与 FP32 CPU goldens,记录文本、框、置信度及 BF16 误差。 +3. 测量冷/热延迟、CPU 时间和 RSS,覆盖关闭/重建、权限不足、驱动兼容及 WebGPU 共存。 +SDK 的原始与打包后哈希、依赖闭包、许可证/第三方 notices 和源模型绑定已随部署包记录;这不等同于真机验收或独立法律审阅。 这些设备行为尚无实测结论,不再作为源码合入或 AMD 显式开启支持发布的前置条件。 diff --git a/docs/implementation-status.md b/docs/implementation-status.md index 3615059..25bc695 100644 --- a/docs/implementation-status.md +++ b/docs/implementation-status.md @@ -1,8 +1,10 @@ # C++ Core 与 Node-API 实施状态 -更新时间:2026-09-24
+更新时间:2026-10-06
结论:npm `0.5.8` 已发布并晋升 `latest`。本版新增 musl(Alpine)Linux 的 CPU-only 预编译平台包(`@arcships/light-ocr-linux-x64-musl`/`-arm64-musl`,平台包总数八),其 onnxruntime 1.22.0 musl 产物从源码构建、由 `models/deps.lock.json` 以 SHA-256 锁定于 [`musl-runtime-1.22.0`](https://github.com/arcships/light-ocr/releases/tag/musl-runtime-1.22.0);同时所有包的 `engines.node` 放宽为下限式 `>=22.0.0`(Node-API 稳定 ABI,Tier 1 仍为 22/24 LTS)。实现经 [PR #63](https://github.com/arcships/light-ocr/pull/63) 两轮独立 review;[发布 run 36013862673](https://github.com/arcships/light-ocr/actions/runs/36013862673) 完成八平台构建、离线 smoke、registry 发布与回装,[晋升 run 36018294655](https://github.com/arcships/light-ocr/actions/runs/36018294655) 只提升稳定闭包。完整证据见 [npm 0.5.8 发布记录](releases/npm-0.5.8.md)([English](releases/npm-0.5.8.en.md))与 [`v0.5.8` GitHub Release](https://github.com/arcships/light-ocr/releases/tag/v0.5.8)。上一版 `0.5.7`(macOS 下游重签名兼容)的证据链见 [npm 0.5.7 发布记录](releases/npm-0.5.7.md)([English](releases/npm-0.5.7.en.md))与 [`v0.5.7` GitHub Release](https://github.com/arcships/light-ocr/releases/tag/v0.5.7)。 +发布准备:`0.5.9` 已同步 Core/native、runtime `0.1.9`、Document `0.1.5`、Tiny/Medium `0.1.8`。Linux x64 默认带 Intel NPU SDK;AMD 默认关闭且需要真实 SDK/模型输入。NPU 真机报告不作为发布前置;npm 和公开 GitHub Release 尚未发布。见 [0.5.9 发布准备](releases/npm-0.5.9.md)([English](releases/npm-0.5.9.en.md))。 + 状态含义: - **Done**:代码存在,并有本地实际运行证据。 @@ -88,6 +90,10 @@ | 高分辨率峰值内存 | Done | Release 原生独立进程本机参考:2048² 空白 `318.8 MiB ≤ 384 MiB`;xfund 密集表单 116 框 `400.5 MiB ≤ 640 MiB`。四平台 release jobs 的真实模型与 RSS gates 均通过。 | | Tiled 高分辨率准确模式 | Done / `0.2.0` published | 1280 tile、2048→4-pass row-major、全局 candidate ceiling、IoU/IOS greedy merge、原图 recognition、C++/Node contract、8-fixture/196-line corpus、独立 oracle、四平台 36-entry accepted baseline 与 package smoke 均已完成。 | +## 0.5.9 NPU 发布准备(2026-10-06) + +Linux x64 glibc 双 NPU Core/Node 构建与 npm native staging 已完成。默认发布输入包含 OpenVINO 2026.4.0,以及 Ryzen AI 1.8 的 20 个原生部署库和绑定 Small 0.3.4 的 20 个真实 BF16 context。Intel 进入 Auto;AMD 始终默认关闭,由配置显式开启。未执行 AMD 设备推理,`deviceValidated` 保持 false;真机验收不作为发布前置。版本、归档哈希与后续发布步骤见 [0.5.9 发布记录](releases/npm-0.5.9.md)。 + ## 本机最终验证快照 环境:macOS arm64 Apple M4 Max,macOS 26.5.1,Apple Clang 21.0.0,CMake 4.2.1,macOS deployment target 13.3;CPU 使用 ONNX Runtime,Apple 候选使用系统 Core ML。 diff --git a/docs/npu-runtime-release.md b/docs/npu-runtime-release.md index edfe99a..01fc162 100644 --- a/docs/npu-runtime-release.md +++ b/docs/npu-runtime-release.md @@ -1,6 +1,6 @@ # NPU SDK 构建与发布 -当前实现覆盖 Linux x64 glibc。Intel 使用 OpenVINO C API;AMD 使用独立装载的 Ryzen AI ORT 与预编译 BF16 识别模型。安装后的 OCR 进程无需 Python,系统仍需厂商 NPU 驱动。当前已发布 npm 包未包含这些候选运行时。 +当前实现覆盖 Linux x64 glibc。Intel 使用 OpenVINO C API;AMD 使用独立装载的 Ryzen AI ORT 与预编译 BF16 识别模型。安装后的 OCR 进程无需 Python,系统仍需厂商 NPU 驱动,AMD 路线使用 Ryzen AI 1.8 支持的 Ubuntu 24.04/glibc 环境。0.5.9 的 Linux x64 发布构建默认附带两套运行时,AMD 默认关闭。 ## Intel 候选 SDK @@ -15,7 +15,18 @@ python tools/npu/build_openvino.py \ ## AMD 候选 SDK -先自行取得 Ryzen AI 1.8 Linux SDK,并在该 SDK 的 Python 环境执行: +常规发布直接取得已锁定的部署包,不需要安装完整 SDK: + +```bash +python tools/npu/fetch_amdnpu.py \ + --package-cache .cache/npu-packages --output-dir dist/npu-sdk/amdnpu +``` + +`amdnpu.lock.json` 锁定归档大小、SHA-256 与完整 artifact set。发布草稿阶段,工具通过已授权的 `GH_TOKEN` 和 `gh release download` 取得附件;公开后直接下载。缓存可使用 `--offline`。官方来源及其原始库哈希保存在 `amdnpu-source.lock.json`。 + +随包提供 Ryzen AI 1.8/ORT 1.27 的 20 个原生库,以及绑定 Small 0.3.4 的 20 个 EPContext。大型库会增加 Linux x64 包体积;Tiny/Medium 不能复用这些识别 context。附带的 `libpython3.12` 是 vendor 库的原生依赖,终端用户无需安装 Python 解释器。 + +需要重新编译模型时,从官方 SDK 建立隔离 Python 环境后执行: ```bash python tools/npu/compile_amdnpu.py \ @@ -24,7 +35,7 @@ python tools/npu/compile_amdnpu.py \ --output-dir dist/amdnpu-models ``` -默认配置为 `tools/npu/vaip_config.json`(VAIML BF16);可用 `--configuration` 指定审阅后的配置。编译和重新打开 context 需要可用的 vendor 栈及设备,尚未在本项目的 AMD 真机上执行。 +默认配置为 `tools/npu/vaip_config.json`(VAIML BF16);可用 `--configuration` 指定审阅后的配置。编译仅创建 vendor 编译会话并检查嵌入式 EPContext,不执行推理。20 桶已在无 AMD 设备的主机完成编译;设备推理尚未验证。 在安装了 `patchelf` 和 `readelf` 的 Linux 主机导入完整部署目录: @@ -37,7 +48,7 @@ python tools/npu/import_amdnpu.py \ --output-dir dist/npu-sdk/amdnpu ``` -部署目录需含 vendor ORT、VitisAI provider 及所有配套原生库。导入工具只允许 OS 和已安装的 NPU 驱动组件留在包外;不安装驱动、不修改系统库或 `LD_LIBRARY_PATH`。应先确认提供的许可证允许再分发。 +部署目录需含 vendor ORT、VitisAI provider 及所有配套原生库。导入工具只允许 OS 和已安装的 NPU 驱动组件留在包外;设置 `$ORIGIN:/opt/xilinx/xrt/lib` RPATH 并清除 vendor ELF 的 executable-stack 标记,原始/变更后哈希均记录;不安装驱动、不修改系统库或 `LD_LIBRARY_PATH`。应先确认提供的许可证允许再分发。 ## 编译 Core 与 Node @@ -68,7 +79,7 @@ const engine = await createEngine({ 模型无关的 `@arcships/light-ocr-runtime` 还需传入 `bundlePath`;模型 facade 自动提供 bundle。CLI 使用 `--provider amdnpu`;C++ 使用 `options.execution.provider = ExecutionProvider::amdnpu`。省略该配置或指定 `auto` 会走默认候选列表。显式 AMD 请求不静默回退;缺少包内 AMD SDK/模型、设备或驱动时报告对应错误。 -发布不要求 NPU 真机报告;`deviceValidated` 保持 `false`。SDK 和真实编译模型仍必须取得并随构建交付,此设置不会生成缺失的 vendor 产物。 +发布不要求 NPU 真机报告;`deviceValidated` 保持 `false`。SDK 和真实编译模型通过锁定的部署包自动随构建交付。 ## 可选设备报告 @@ -108,9 +119,9 @@ python tools/npu/accept_qualification.py \ ## CI 与 npm staging -`NPU native candidates` workflow 生成 Intel SDK 与 Node 候选构建。可输入另一个 run 的 `amdnpu-candidate-sdk` artifact,内容应为 AMD SDK 目录。始终上传可分发的 `npu-release-sdks`,无需设备报告。可选的 `npu-reviewed-reports` artifact 按 `openvino/*.json`、`amdnpu/*.json` 放置;仅在主动提供报告时才检查其绑定关系。 +`NPU native candidates` workflow 默认取得 Intel 与 AMD SDK 并生成双 NPU Node 候选构建。可输入另一个 run 的 `amdnpu-candidate-sdk` artifact,内容应为 AMD SDK 目录。始终上传可分发的 `npu-release-sdks`,无需设备报告。可选的 `npu-reviewed-reports` artifact 按 `openvino/*.json`、`amdnpu/*.json` 放置;仅在主动提供报告时才检查其绑定关系。 -`npm release` workflow 的可选 `npu_sdk_run_id` 读取同仓库的 `npu-release-sdks`(根目录下为 `openvino/`、`amdnpu/`),仅 Linux x64 构建使用。CI 在配置 CMake 前验证 SDK 库存、模型、配置和哈希;已附带的接受报告仍会校验,但不要求提供报告。 +从 0.5.9 发布准备起,`npm release` 默认为 Linux x64 准备锁定的 Intel 与 AMD SDK,缺少任一套均阻止发布构建。可选 `npu_sdk_run_id` 读取同仓库的 `npu-release-sdks`(根目录下为 `openvino/`、`amdnpu/`),仅 Linux x64 构建使用。CI 在配置 CMake 前验证 SDK 库存、模型、配置和哈希;已附带的接受报告仍会校验,但不要求提供报告。 本地 `tools/npm_release.py stage-native` 接受 `--openvino-sdk-dir` 和 `--amdnpu-sdk-dir`;SDK 需与构建 addon 使用的版本、资格 ID 和包含的 provider 一致。NPU SDK 的 `qualificationOnly` 记录其设备证据状态,不再使整个平台包变成 qualification build;无需真机报告即可 staging。WebGPU 自身的既有资格门槛仍适用,正式 `assemble` 仍拒绝基础运行时的 qualification build。许可证、SBOM、SDK manifest 和可选报告随平台包保存。 diff --git a/docs/releases/npm-0.5.9.en.md b/docs/releases/npm-0.5.9.en.md new file mode 100644 index 0000000..73226e8 --- /dev/null +++ b/docs/releases/npm-0.5.9.en.md @@ -0,0 +1,57 @@ +# npm 0.5.9 Release Preparation + +[中文版](npm-0.5.9.md) + +Status: preparation only. npm publication, a public tag, artifact hashes and registry promotion are pending. The GitHub Release is a draft; previous release measurements are not reused. + +## Versions + +NPU implementation: [PR #67](https://github.com/arcships/light-ocr/pull/67), merged. Preparation: [PR #68](https://github.com/arcships/light-ocr/pull/68); the GitHub draft exists. The release source must be the main commit after merging version preparation. + +| Package | Prepared version | Planned tags | +| --- | --- | --- | +| Small facade | `0.5.9` | `next`, then `latest` | +| Model-free runtime | `0.1.9` | `next`, then `latest` | +| Eight native packages | `0.5.9` | `next`, then `latest` | +| Document compatibility facade | `0.1.5` | `next` | +| Tiny/Medium preview facades | `0.1.8` | `next` | +| Small model | `0.3.4` | Reuse immutable package | +| Tiny/Medium models | `0.1.0` | Reuse immutable packages | + +## Changes + +- Linux x64 glibc release builds include pinned OpenVINO 2026.4.0 by default. Auto tries Intel NPU before WebGPU and CPU, running detection and 20-bucket recognition when supported. Creation-time device/model/driver exclusions advance to the next provider. +- Linux x64 glibc builds include Ryzen AI 1.8 native deployment libraries and 20 real BF16 recognition contexts bound to Small 0.3.4. AMD stays outside Auto; explicitly select `execution: { provider: "amdnpu" }` or `--provider amdnpu`. Detection uses CPU; STX/KRK devices are the target. Tiny/Medium require separately compiled contexts. +- The workflow fetches the immutable AMD deployment archive automatically. End users need vendor drivers, without installing the full SDK or Python. +- Hardware qualification reports are optional for NPU shipping. Unvalidated devices remain `deviceValidated: false`; inventory, hashes, model binding, licenses and existing base-runtime checks remain enforced. +- NPU packages use runtime descriptor 2.1; other packages retain 2.0. Windows, arm64 and musl retain their existing provider routes. Model versions, the `>=22.0.0` Node floor and public API contracts are unchanged. + +## Release sequence + +1. Merge preparation and select main with Core `0.5.9`. +2. Run `npm release` with `version=0.5.9` and `publish_to_registry=false`. Both SDKs are prepared automatically on Linux x64. An optional `npu_sdk_run_id` overrides both SDKs and must contain `openvino/` and `amdnpu/`. Draft asset downloads use the workflow GitHub token. AMD stays opt-in. +3. Review eight-platform builds, offline image/PDF smoke and the tarball manifest. Record the source SHA, run ID, 13 new package identities and artifact hashes. +4. Publish to `next` with `publish_to_registry=true` and record registry reinstall results. +5. Run `npm promote` with the publication run ID and `tag=latest`; promote only Small/runtime/native. Document and preview facades stay on `next`. +6. Update the draft release target to the final source commit, attach the manifest/artifact links, and fill publication dates and both release records before publishing `v0.5.9`. + +Preparation does not publish npm packages, promote tags or publish the GitHub draft. PR #67 CI is implementation evidence, not the eight-platform 0.5.9 release result. All 13 new package identities are vacant (the complete manifest also reuses two preview model packages). Intel+AMD Core/Node were reconfigured and built with version 0.5.9; version closure, lockfile and script syntax checks passed. No new local tests were run; artifact integrity and publication runs remain pending. All 20 AMD recognition contexts were actually compiled; 20 native libraries and 56 inventory files were imported. No AMD inference or NPU hardware qualification was performed. + +## Rollback + +Restore stable tags using the archived 0.5.8 publication artifact (run `36013862673`): Small/native `0.5.8`, runtime `0.1.8`. Handle compatibility/preview tags separately; never overwrite or delete published package versions. + +## AMD deployment artifact + +- Archive: `amdnpu-1.8.0-linux-x64-ppocrv6-small-0.3.4-opt-in-v1.tar.gz` +- Size: `532,631,585` bytes (~508 MiB); unpacked SDK inventory: `1,356,635,270` bytes (~1.26 GiB). This substantially increases the Linux x64 npm package size. +- Archive SHA-256: `bee70b00f3254943751b9460628295696d9901f22887f11b0c791191847fb016` +- Artifact set SHA-256: `5df515842656974be5517ae40b408427f0ffdbb63473c1ced1b610f2a0afc8a4` +- Small source recognition SHA-256: `5435fd747c9e0efe15a96d0b378d5bd157e9492ed8fd80edf08f30d02fa24634` +- Original SDK provenance is locked in `tools/npu/amdnpu-source.lock.json`; deployment archive identity is in `tools/npu/amdnpu.lock.json`. The archive is attached to the draft release. Final npm tarball identities are generated separately by the release workflow. + +## GitHub Release + +Tag: `v0.5.9` (not public while draft). + +Name: `双芯入卷,轻舟再发 · Ship Intel NPU and opt-in AMD support in 0.5.9` diff --git a/docs/releases/npm-0.5.9.md b/docs/releases/npm-0.5.9.md new file mode 100644 index 0000000..83a5964 --- /dev/null +++ b/docs/releases/npm-0.5.9.md @@ -0,0 +1,69 @@ +# npm 0.5.9 发布准备 + +[English version](npm-0.5.9.en.md) + +状态:准备中,尚未发布 npm、尚未创建公开 tag。GitHub Release 使用草稿;制品大小、SHA-256、发布 run 和晋升结果在实际生成后补录,不沿用上一版本数据。 + +## 发布身份与版本 + +- NPU 实现:[PR #67](https://github.com/arcships/light-ocr/pull/67),已合并。 +- 发布准备:[PR #68](https://github.com/arcships/light-ocr/pull/68);`v0.5.9` GitHub Release 草稿已创建,尚未公开。 +- 版本采用补丁递增:Core/Small/native `0.5.8 → 0.5.9`。 +- 本版本的正式源码应指向版本准备 PR 合并后的 main 提交,不能绑定旧的 0.5.8 提交。 + +| 包 | 准备版本 | 发布标签计划 | +| --- | --- | --- | +| Small `@arcships/light-ocr` | `0.5.9` | 先 `next`,再 `latest` | +| model-free runtime | `0.1.9` | 先 `next`,再 `latest` | +| 八个 native 平台包 | `0.5.9` | 先 `next`,再 `latest` | +| Document compatibility facade | `0.1.5` | `next` | +| Tiny/Medium preview facade | `0.1.8` | `next` | +| Small model | `0.3.4` | 复用已有不可变制品 | +| Tiny/Medium model | `0.1.0` | 复用已有不可变制品 | + +## 用户可见变化 + +- Linux x64 glibc 平台包默认附带锁定的 OpenVINO 2026.4.0 原生运行时;Intel NPU 在 Auto 中位于 WebGPU/CPU 前。有可用 NPU 时运行检测和 20 桶识别;无设备、模型不支持或驱动过旧时按创建阶段策略继续选择后续 provider。 +- Linux x64 glibc 发布构建默认附带 Ryzen AI 1.8 原生部署库及 Small 0.3.4 的 20 个真实 BF16 识别 context。AMD 始终默认关闭,不进入 Auto;通过 `execution: { provider: "amdnpu" }` 或 `--provider amdnpu` 显式开启。检测使用 CPU,目标为 STX/KRK;Tiny/Medium 需另行编译,不能复用 Small context。 +- 发布工作流自动下载哈希锁定的 AMD 部署归档,不需要维护者另行提供 SDK。终端用户仍需系统厂商驱动,无需 SDK/Python 安装。 +- NPU 真机验收不作为发布前置;未验证的 NPU 会话保持 `deviceValidated: false`。库存、哈希、模型绑定、许可证和基础运行时的既有发布检查保留。 +- 包内 NPU 描述采用 schema 2.1;不含 NPU 的平台保持 2.0。新增模型配置、版本与产物信息,Node loader 在装载前完成验证。 +- 其他平台继续使用现有 Apple/WebGPU/CPU 路线;Windows、arm64 和 musl 没有本次 NPU 后端。模型包版本、Node 安装下限 `>=22.0.0` 及 public API 保持既有契约。 + +## Release 操作 + +1. 合并版本准备 PR,确认 main 中 Core 为 `0.5.9`。 +2. 运行 `npm release` workflow,输入 `version=0.5.9`、`publish_to_registry=false`。Linux x64 默认从锁文件取得 Intel 与 AMD SDK,无需填写 NPU run ID。 +3. 可选 `npu_sdk_run_id` 仅用于覆盖默认 SDK 输入;须同时包含 `openvino/` 与 `amdnpu/`。草稿附件下载使用 workflow 的 GitHub token;保持 Release 草稿及附件可用。 +4. 审阅八平台构建、离线安装/图片/PDF smoke 与 tarball manifest;将运行 ID、精确来源提交、13 个新 package identity 和制品哈希补录本文件。 +5. 用 `publish_to_registry=true` 发布到 `next`,记录发布 run,并确认 registry 回装结果。 +6. 运行 `npm promote`:`version=0.5.9`、`release_run_id=<正式发布run>`、`tag=latest`。仅晋升 stable Small/runtime/native;Document 与 Tiny/Medium 保留 `next`。 +7. 将 GitHub Release 草稿 target 更新为正式 main 来源提交,附上 release-manifest 和公开制品链接;确认发布结果后更新 Changelog 日期、两份发布记录和 Release 状态,再发布 `v0.5.9`。 + +准备阶段不会主动调用 registry publish、晋升 dist-tag 或公开发布 GitHub Release。 + +## 当前证据 + +- PR #67 的 Linux native 与 workspace CI 均通过;这属于实现来源,不是 0.5.9 八平台发布结果。 +- 13 个新 package identity 的 npm 空位检查通过;加上复用的两个 preview 模型,release manifest 仍包含 15 包。 +- Intel+AMD Core/Node addon 已按 `LIGHT_OCR_VERSION=0.5.9` 重新配置并构建通过;版本闭包、lockfile 和脚本语法检查通过,未运行新的本地测试。实际 tarball、发布/晋升 run 和 registry integrity 尚未生成。 +- AMD 20 桶已实际编译,20 个原生库与 56 个库存文件已导入;原始 SDK、打包后运行库及模型哈希均已记录。本次没有执行 AMD 推理,不安排 NPU 真机验收。 + +## 回滚 + +已发布 npm 版本不可覆盖。若 0.5.9 晋升后需要回滚,使用 0.5.8 的已归档正式发布 artifact(run `36013862673`)恢复 stable Small/native `0.5.8` 与 runtime `0.1.8` 的 `latest` 标签。Document 与 preview 标签单独处理;不删除用户已安装的 0.5.9 产物。 + +## AMD 部署制品 + +- 归档:`amdnpu-1.8.0-linux-x64-ppocrv6-small-0.3.4-opt-in-v1.tar.gz` +- 大小:`532,631,585` bytes(约 508 MiB);SDK 库存共 `1,356,635,270` bytes(约 1.26 GiB),Linux x64 npm 包因此显著增大。 +- 归档 SHA-256:`bee70b00f3254943751b9460628295696d9901f22887f11b0c791191847fb016` +- Artifact set SHA-256:`5df515842656974be5517ae40b408427f0ffdbb63473c1ced1b610f2a0afc8a4` +- Small 识别源 SHA-256:`5435fd747c9e0efe15a96d0b378d5bd157e9492ed8fd80edf08f30d02fa24634` +- 官方 SDK 原始归档身份见 `tools/npu/amdnpu-source.lock.json`,最终部署归档身份见 `tools/npu/amdnpu.lock.json`。归档已附于 GitHub 草稿,正式 npm tarball 身份须由发布工作流另行生成。 + +## GitHub Release + +Tag:`v0.5.9`(草稿阶段未公开创建)。 + +Release name:`双芯入卷,轻舟再发 · Ship Intel NPU and opt-in AMD support in 0.5.9` diff --git a/package-lock.json b/package-lock.json index 1059150..ab67abc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1093,11 +1093,11 @@ }, "packages/light-ocr": { "name": "@arcships/light-ocr", - "version": "0.5.8", + "version": "0.5.9", "license": "Apache-2.0", "dependencies": { "@arcships/light-ocr-model-ppocrv6-small": "0.3.4", - "@arcships/light-ocr-runtime": "0.1.7" + "@arcships/light-ocr-runtime": "0.1.9" }, "bin": { "light-ocr": "src/cli.cjs" @@ -1108,10 +1108,10 @@ }, "packages/light-ocr-document": { "name": "@arcships/light-ocr-document", - "version": "0.1.4", + "version": "0.1.5", "license": "Apache-2.0", "dependencies": { - "@arcships/light-ocr": "0.5.7" + "@arcships/light-ocr": "0.5.9" }, "bin": { "light-ocr-document": "src/cli.cjs" @@ -1122,11 +1122,11 @@ }, "packages/light-ocr-medium": { "name": "@arcships/light-ocr-medium", - "version": "0.1.7", + "version": "0.1.8", "license": "Apache-2.0", "dependencies": { "@arcships/light-ocr-model-ppocrv6-medium": "0.1.0", - "@arcships/light-ocr-runtime": "0.1.7" + "@arcships/light-ocr-runtime": "0.1.9" }, "bin": { "light-ocr-medium": "src/cli.cjs" @@ -1279,11 +1279,11 @@ }, "packages/light-ocr-tiny": { "name": "@arcships/light-ocr-tiny", - "version": "0.1.7", + "version": "0.1.8", "license": "Apache-2.0", "dependencies": { "@arcships/light-ocr-model-ppocrv6-tiny": "0.1.0", - "@arcships/light-ocr-runtime": "0.1.7" + "@arcships/light-ocr-runtime": "0.1.9" }, "bin": { "light-ocr-tiny": "src/cli.cjs" @@ -1304,20 +1304,20 @@ }, "packages/runtime": { "name": "@arcships/light-ocr-runtime", - "version": "0.1.8", + "version": "0.1.9", "license": "Apache-2.0", "engines": { "node": ">=22.0.0" }, "optionalDependencies": { - "@arcships/light-ocr-darwin-arm64": "0.5.8", - "@arcships/light-ocr-darwin-x64": "0.5.8", - "@arcships/light-ocr-linux-arm64-gnu": "0.5.8", - "@arcships/light-ocr-linux-arm64-musl": "0.5.8", - "@arcships/light-ocr-linux-x64-gnu": "0.5.8", - "@arcships/light-ocr-linux-x64-musl": "0.5.8", - "@arcships/light-ocr-win32-arm64": "0.5.8", - "@arcships/light-ocr-win32-x64": "0.5.8" + "@arcships/light-ocr-darwin-arm64": "0.5.9", + "@arcships/light-ocr-darwin-x64": "0.5.9", + "@arcships/light-ocr-linux-arm64-gnu": "0.5.9", + "@arcships/light-ocr-linux-arm64-musl": "0.5.9", + "@arcships/light-ocr-linux-x64-gnu": "0.5.9", + "@arcships/light-ocr-linux-x64-musl": "0.5.9", + "@arcships/light-ocr-win32-arm64": "0.5.9", + "@arcships/light-ocr-win32-x64": "0.5.9" } } } diff --git a/packages/light-ocr-document/package.json b/packages/light-ocr-document/package.json index 38b97f9..d5a7e6a 100644 --- a/packages/light-ocr-document/package.json +++ b/packages/light-ocr-document/package.json @@ -1,6 +1,6 @@ { "name": "@arcships/light-ocr-document", - "version": "0.1.4", + "version": "0.1.5", "private": true, "description": "Compatibility entry for the document API built into light-ocr", "license": "Apache-2.0", @@ -33,7 +33,7 @@ "node": ">=22.0.0" }, "dependencies": { - "@arcships/light-ocr": "0.5.8" + "@arcships/light-ocr": "0.5.9" }, "scripts": { "test": "node --test test/*.test.cjs" diff --git a/packages/light-ocr-medium/package.json b/packages/light-ocr-medium/package.json index adec51b..5157a74 100644 --- a/packages/light-ocr-medium/package.json +++ b/packages/light-ocr-medium/package.json @@ -1,6 +1,6 @@ { "name": "@arcships/light-ocr-medium", - "version": "0.1.7", + "version": "0.1.8", "private": true, "description": "Offline PP-OCRv6 Medium OCR for Node.js — preview quality tier", "license": "Apache-2.0", @@ -29,7 +29,7 @@ }, "dependencies": { "@arcships/light-ocr-model-ppocrv6-medium": "0.1.0", - "@arcships/light-ocr-runtime": "0.1.8" + "@arcships/light-ocr-runtime": "0.1.9" }, "scripts": { "test": "node --test test/*.test.cjs" diff --git a/packages/light-ocr-tiny/package.json b/packages/light-ocr-tiny/package.json index ef3dda6..98beb92 100644 --- a/packages/light-ocr-tiny/package.json +++ b/packages/light-ocr-tiny/package.json @@ -1,6 +1,6 @@ { "name": "@arcships/light-ocr-tiny", - "version": "0.1.7", + "version": "0.1.8", "private": true, "description": "Offline PP-OCRv6 Tiny OCR for Node.js — preview size-first tier", "license": "Apache-2.0", @@ -29,7 +29,7 @@ }, "dependencies": { "@arcships/light-ocr-model-ppocrv6-tiny": "0.1.0", - "@arcships/light-ocr-runtime": "0.1.8" + "@arcships/light-ocr-runtime": "0.1.9" }, "scripts": { "test": "node --test test/*.test.cjs" diff --git a/packages/light-ocr/package.json b/packages/light-ocr/package.json index 68ecd2e..5fcf527 100644 --- a/packages/light-ocr/package.json +++ b/packages/light-ocr/package.json @@ -1,6 +1,6 @@ { "name": "@arcships/light-ocr", - "version": "0.5.8", + "version": "0.5.9", "private": true, "description": "Offline image and PDF OCR for Node.js — the stable PP-OCRv6 Small tier", "license": "Apache-2.0", @@ -30,7 +30,7 @@ }, "dependencies": { "@arcships/light-ocr-model-ppocrv6-small": "0.3.4", - "@arcships/light-ocr-runtime": "0.1.8" + "@arcships/light-ocr-runtime": "0.1.9" }, "scripts": { "test": "node --test test/*.test.cjs" diff --git a/packages/runtime/README.md b/packages/runtime/README.md index d3c316c..91b0db5 100644 --- a/packages/runtime/README.md +++ b/packages/runtime/README.md @@ -11,3 +11,16 @@ const engine = await createEngine({ bundlePath: '/absolute/model/bundle' }); ``` Use `@arcships/light-ocr` for stable Small, `@arcships/light-ocr-tiny` for the size-first preview, or `@arcships/light-ocr-medium` for the quality-first preview. The runtime never downloads a model. + +## NPU configuration + +Starting with the prepared 0.5.9 native release, Linux x64 glibc packages include Intel OpenVINO in Auto ahead of WebGPU/CPU. The same Linux x64 release includes Ryzen AI 1.8 native libraries and 20 compiled BF16 contexts for Small 0.3.4. AMD is excluded from Auto; enable it explicitly: + +```js +const engine = await createEngine({ + bundlePath: '/absolute/model/bundle', + execution: { provider: 'amdnpu' }, +}); +``` + +These AMD contexts require the Small 0.3.4 recognition model and an installed Ryzen AI 1.8 STX/KRK driver stack. Other tiers require separately compiled contexts. Platforms without AMD payload report `unsupported_capability`. Explicit selection does not silently fall back. NPU shipping does not assert hardware qualification; `deviceValidated` stays false. diff --git a/packages/runtime/package.json b/packages/runtime/package.json index 7234ec9..06fe74d 100644 --- a/packages/runtime/package.json +++ b/packages/runtime/package.json @@ -1,6 +1,6 @@ { "name": "@arcships/light-ocr-runtime", - "version": "0.1.8", + "version": "0.1.9", "private": true, "description": "Model-free Node.js runtime for light-ocr", "license": "Apache-2.0", @@ -28,14 +28,14 @@ "node": ">=22.0.0" }, "optionalDependencies": { - "@arcships/light-ocr-darwin-arm64": "0.5.8", - "@arcships/light-ocr-darwin-x64": "0.5.8", - "@arcships/light-ocr-linux-arm64-gnu": "0.5.8", - "@arcships/light-ocr-linux-arm64-musl": "0.5.8", - "@arcships/light-ocr-linux-x64-gnu": "0.5.8", - "@arcships/light-ocr-linux-x64-musl": "0.5.8", - "@arcships/light-ocr-win32-arm64": "0.5.8", - "@arcships/light-ocr-win32-x64": "0.5.8" + "@arcships/light-ocr-darwin-arm64": "0.5.9", + "@arcships/light-ocr-darwin-x64": "0.5.9", + "@arcships/light-ocr-linux-arm64-gnu": "0.5.9", + "@arcships/light-ocr-linux-arm64-musl": "0.5.9", + "@arcships/light-ocr-linux-x64-gnu": "0.5.9", + "@arcships/light-ocr-linux-x64-musl": "0.5.9", + "@arcships/light-ocr-win32-arm64": "0.5.9", + "@arcships/light-ocr-win32-x64": "0.5.9" }, "scripts": { "test": "node --test test/*.test.cjs" diff --git a/packages/runtime/src/load-native.cjs b/packages/runtime/src/load-native.cjs index acad02e..09aab2b 100644 --- a/packages/runtime/src/load-native.cjs +++ b/packages/runtime/src/load-native.cjs @@ -209,7 +209,18 @@ function safeArtifactPath(root, value, field) { } function sha256(filename) { - return crypto.createHash('sha256').update(fs.readFileSync(filename)).digest('hex'); + const hash = crypto.createHash('sha256'); + const buffer = Buffer.allocUnsafe(1024 * 1024); + const file = fs.openSync(filename, 'r'); + try { + let count; + while ((count = fs.readSync(file, buffer, 0, buffer.length, null)) !== 0) { + hash.update(buffer.subarray(0, count)); + } + } finally { + fs.closeSync(file); + } + return hash.digest('hex'); } function verifyArtifact(root, artifact, field) { diff --git a/tools/npm/smoke.cjs b/tools/npm/smoke.cjs index 113d2c9..e1f4956 100644 --- a/tools/npm/smoke.cjs +++ b/tools/npm/smoke.cjs @@ -112,15 +112,17 @@ async function main() { 'cpu', ); } else { + const candidates = process.platform === 'linux' + ? ['openvino', 'webgpu', 'cpu'] : ['webgpu', 'cpu']; assert.deepEqual( engine.info.execution.selectionTrace.orderedCandidates, - ['webgpu', 'cpu'], + candidates, ); assert.ok(engine.info.execution.providerCapabilities.some( (capability) => capability.provider === 'webgpu' && capability.packageIncluded, )); - assert.ok(['webgpu', 'cpu'].includes( + assert.ok(candidates.includes( engine.info.execution.selectionTrace.selectedProvider, )); } diff --git a/tools/npm_release.py b/tools/npm_release.py index e3b8d9a..fc9ec39 100644 --- a/tools/npm_release.py +++ b/tools/npm_release.py @@ -63,12 +63,12 @@ }, "tiny": { "name": "@arcships/light-ocr-tiny", - "version": "0.1.7", + "version": "0.1.8", "workspace": "light-ocr-tiny", }, "medium": { "name": "@arcships/light-ocr-medium", - "version": "0.1.7", + "version": "0.1.8", "workspace": "light-ocr-medium", }, } @@ -1229,6 +1229,8 @@ def assemble(arguments: argparse.Namespace) -> None: copy_tree(source / "native", package / "native") copy_tree(source / "pdfium", package / "pdfium") copy_tree(source / "licenses", package / "licenses") + if (source / "qualification").is_dir(): + copy_tree(source / "qualification", package / "qualification") copy_file(source / "license-inventory.json", package / "license-inventory.json") copy_file(source / "sbom.spdx.json", package / "sbom.spdx.json") package_json = common_package( @@ -1249,6 +1251,7 @@ def assemble(arguments: argparse.Namespace) -> None: "native/", "pdfium/", "licenses/", + "qualification/", "license-inventory.json", "sbom.spdx.json", "artifact-hashes.json", diff --git a/tools/npu/amdnpu-source.lock.json b/tools/npu/amdnpu-source.lock.json new file mode 100644 index 0000000..8b54833 --- /dev/null +++ b/tools/npu/amdnpu-source.lock.json @@ -0,0 +1,171 @@ +{ + "schemaVersion": "1.0", + "version": "1.8.0", + "package": { + "filename": "ryzen_ai-1.8.0.tgz", + "bytes": 7819506855, + "sha256": "7532d511532dd8842995bc8dc2edd8a45fbf261312ea9abe8dd01b4d502ac98d", + "url": "https://download.amd.com/opendownload/RyzenAI/1.8.0/ryzen_ai-1.8.0.tgz" + }, + "nativeDependencies": [ + { + "filename": "libncurses6_6.4+20240113-1ubuntu2_amd64.deb", + "url": "https://archive.ubuntu.com/ubuntu/pool/main/n/ncurses/libncurses6_6.4+20240113-1ubuntu2_amd64.deb", + "bytes": 111902, + "sha256": "f2b8efe3a3e2f622c5266d5a1301048306441885d2bbebc02685cb8c2d097537" + }, + { + "filename": "libtinfo6_6.4+20240113-1ubuntu2_amd64.deb", + "url": "https://archive.ubuntu.com/ubuntu/pool/main/n/ncurses/libtinfo6_6.4+20240113-1ubuntu2_amd64.deb", + "bytes": 107794, + "sha256": "53e1e1753729d04cf65b05e6e58abe06e2bb76cc07eff0e1b2a638a638ca209b" + }, + { + "filename": "libpython3.12t64_3.12.3-1_amd64.deb", + "url": "https://archive.ubuntu.com/ubuntu/pool/main/p/python3.12/libpython3.12t64_3.12.3-1_amd64.deb", + "bytes": 2338824, + "sha256": "31df8af8521fb3fdf198d7507765d4ebf435c7165a50013b39d2761fd6c0d98c" + }, + { + "filename": "libuuid1_2.39.3-9ubuntu6_amd64.deb", + "url": "https://archive.ubuntu.com/ubuntu/pool/main/u/util-linux/libuuid1_2.39.3-9ubuntu6_amd64.deb", + "bytes": 35248, + "sha256": "4ba2c3a10469d1ab8e01a9af8e39093ed43addada658c04737287c5f91a1645b" + }, + { + "filename": "libzstd1_1.5.5+dfsg2-2build1_amd64.deb", + "url": "https://archive.ubuntu.com/ubuntu/pool/main/libz/libzstd/libzstd1_1.5.5+dfsg2-2build1_amd64.deb", + "bytes": 299490, + "sha256": "4dcc3ba9606a5837a55248d08162a5b426dd621f75c0eb24d86409e0d0e71d52" + }, + { + "filename": "libexpat1_2.6.1-2build1_amd64.deb", + "url": "https://archive.ubuntu.com/ubuntu/pool/main/e/expat/libexpat1_2.6.1-2build1_amd64.deb", + "bytes": 86992, + "sha256": "730b29497212289cda8a3139e693a3538af0cb57846c24f807d1bb293a591c5e" + } + ], + "sourceModelSha256": "5435fd747c9e0efe15a96d0b378d5bd157e9492ed8fd80edf08f30d02fa24634", + "libraries": [ + { + "library": "libaie_codegen.so.3", + "path": "native-source/deployment/lib/libaie_codegen.so.3.5", + "bytes": 765056, + "sha256": "c690396adac16751fc7d99e5e6b91497c7293eab2a7027a53f083b0372289767" + }, + { + "library": "libaiecompiler_client.so", + "path": "native-source/deployment/lib/libaiecompiler_client.so", + "bytes": 223400960, + "sha256": "e94e0d2136ca84f91936ba33f07c66a1b0ab3d5a503e511d7b9d234459f019c8" + }, + { + "library": "libdyn_dispatch_core.so", + "path": "native-source/deployment/lib/libdyn_dispatch_core.so", + "bytes": 187806488, + "sha256": "af71fa98497bce04d3e549c620706195aa45837ceabe1f903101f1b26339c95e" + }, + { + "library": "libexpat.so.1", + "path": "ubuntu-libs/usr/lib/x86_64-linux-gnu/libexpat.so.1.9.1", + "bytes": 170240, + "sha256": "e62066b1ba04145a07f03de30ea91254559ac4ca22389ea4cd7f1f700d9f2ff5" + }, + { + "library": "libflexmlrt.so", + "path": "native-source/deployment/lib/libflexmlrt.so", + "bytes": 12623576, + "sha256": "0fca2344557642b23b7fb9369781b782d412a227864ffa760934c01747ca7dad" + }, + { + "library": "libncurses.so.6", + "path": "ubuntu-libs/usr/lib/x86_64-linux-gnu/libncurses.so.6.4", + "bytes": 165968, + "sha256": "c090994a8fd216fafa8b4f1f4e1998e4b8bcce82dd28bfa2549acbd91fd561bd" + }, + { + "library": "libonnxruntime.so", + "path": "native-source/deployment/lib/libonnxruntime.so.1.27.0", + "bytes": 28316944, + "sha256": "b26e093d750d8c066ff4d3daebe986b37d949de542c453ccdb578db442265555" + }, + { + "library": "libonnxruntime_providers_shared.so", + "path": "native-source/deployment/lib/libonnxruntime_providers_shared.so", + "bytes": 15480, + "sha256": "f0e61e401eed9466d56da57b28f7ddd842f546d405a0077255c74b26a81ed044" + }, + { + "library": "libonnxruntime_providers_vitisai.so", + "path": "native-source/deployment/lib/libonnxruntime_providers_vitisai.so", + "bytes": 273680, + "sha256": "e92a6ad323c8a426f45c1b3211b202e1f598ad031face7e402332768ed846539" + }, + { + "library": "libonnxruntime_vitisai_ep.so", + "path": "native-source/deployment/lib/libonnxruntime_vitisai_ep.so", + "bytes": 71403752, + "sha256": "cf5206d4810e503e017dfac1fb0cfe89789ff9c3eae57d6db1682a040d28a09b" + }, + { + "library": "libpeano-lib.so.21.0git", + "path": "venv/lib/python3.12/site-packages/lnx64.o/tools/peano/lib/libpeano-lib.so.21.0git", + "bytes": 186631968, + "sha256": "19701a4944dacf516846e42c8b0a466db4201d7a59fd385c3d3aba48603768be" + }, + { + "library": "libpython3.12.so.1.0", + "path": "ubuntu-libs/usr/lib/x86_64-linux-gnu/libpython3.12.so.1.0", + "bytes": 9042824, + "sha256": "a6d543df2a20bff421d6cd00eca3243042140829827751f60f55b05495a79c2f" + }, + { + "library": "libtinfo.so.6", + "path": "ubuntu-libs/usr/lib/x86_64-linux-gnu/libtinfo.so.6.4", + "bytes": 208328, + "sha256": "61f693cd7f8e55ad3ab518b20a00c01adb46ef829cf90773c97615a73f99e09b" + }, + { + "library": "libuuid.so.1", + "path": "ubuntu-libs/usr/lib/x86_64-linux-gnu/libuuid.so.1.3.0", + "bytes": 35032, + "sha256": "65b9396655f948e1eeb5d20d25829389b9d513c34840a24002dd5d417b60c507" + }, + { + "library": "libvaiml2.so", + "path": "native-source/deployment/lib/libvaiml2.so", + "bytes": 2769136, + "sha256": "38636c3b141b31bd0dcb73ae86307d4a256e2aa2da0765dc6711be4e1c4b8658" + }, + { + "library": "libxcompiler-core-without-symbol.so", + "path": "native-source/deployment/lib/libxcompiler-core-without-symbol.so", + "bytes": 64630424, + "sha256": "a25960aba0c8828e9bb6494c50a727790ae8172997ad2380f20a989dbc8da3f1" + }, + { + "library": "libxcompiler-mlir-e2e.so", + "path": "native-source/deployment/lib/libxcompiler-mlir-e2e.so", + "bytes": 306756952, + "sha256": "b5f57ce492a1bd0a791354cc4be2cc169cd7c765c9e094b3a01f6b1486badbb4" + }, + { + "library": "libxcompiler-xcompiler-core.so.1", + "path": "native-source/deployment/lib/libxcompiler-xcompiler-core.so.1.6.0", + "bytes": 77568, + "sha256": "a0956717669f8dd91ff7775fde8c97e01a65640c429ae10cdcdd56835b3c8467" + }, + { + "library": "libz.so.1", + "path": "native-source/deployment/lib/libz.so.1.3.2", + "bytes": 117552, + "sha256": "08dd93987c367ec2d928ce9148821d0ced5521050bc3e3e9c6384b69d991936a" + }, + { + "library": "libzstd.so.1", + "path": "ubuntu-libs/usr/lib/x86_64-linux-gnu/libzstd.so.1.5.5", + "bytes": 755864, + "sha256": "3c8a5cd1ce4008afa91da80db302b6987b2a75bb053f3a80e203068c11f7ca49" + } + ] +} diff --git a/tools/npu/amdnpu.lock.json b/tools/npu/amdnpu.lock.json new file mode 100644 index 0000000..a371a30 --- /dev/null +++ b/tools/npu/amdnpu.lock.json @@ -0,0 +1,14 @@ +{ + "schemaVersion": "1.0", + "version": "1.8.0", + "artifactSetSha256": "5df515842656974be5517ae40b408427f0ffdbb63473c1ced1b610f2a0afc8a4", + "sourceModelSha256": "5435fd747c9e0efe15a96d0b378d5bd157e9492ed8fd80edf08f30d02fa24634", + "package": { + "filename": "amdnpu-1.8.0-linux-x64-ppocrv6-small-0.3.4-opt-in-v1.tar.gz", + "bytes": 532631585, + "sha256": "bee70b00f3254943751b9460628295696d9901f22887f11b0c791191847fb016", + "url": "https://github.com/arcships/light-ocr/releases/download/v0.5.9/amdnpu-1.8.0-linux-x64-ppocrv6-small-0.3.4-opt-in-v1.tar.gz", + "releaseTag": "v0.5.9", + "repository": "arcships/light-ocr" + } +} diff --git a/tools/npu/compile_amdnpu.py b/tools/npu/compile_amdnpu.py index 0ba7397..e9c7885 100644 --- a/tools/npu/compile_amdnpu.py +++ b/tools/npu/compile_amdnpu.py @@ -8,6 +8,7 @@ import argparse import json +import os from pathlib import Path import shutil @@ -33,14 +34,17 @@ def validate_context(path: Path) -> int: return len(contexts) -def compile_models(source: Path, expected_sha256: str, configuration: Path, output: Path) -> dict: - import numpy as np +def compile_models(source: Path, expected_sha256: str, configuration: Path, output: Path, + widths: list[int] | None = None) -> dict: import onnx import onnxruntime as ort if digest(source) != expected_sha256: raise ValueError("source recognition model does not match its immutable bundle hash") if "VitisAIExecutionProvider" not in ort.get_available_providers(): raise ValueError("run this tool with the Ryzen AI SDK Python interpreter") + selected_widths = recognition_widths() if widths is None else sorted(set(widths)) + if not selected_widths or any(width not in recognition_widths() for width in selected_widths): + raise ValueError("recognition widths must belong to the locked 20-bucket contract") config = json.loads(configuration.read_text("utf-8")) if (config.get("target") != "VAIML" or not any(item.get("name") == "vaiml_partition" for item in config.get("passes", []))): @@ -57,7 +61,7 @@ def compile_models(source: Path, expected_sha256: str, configuration: Path, outp if len(source_shape) != 4: raise ValueError("recognition input must be NCHW") models = [] - for width in recognition_widths(): + for width in selected_widths: shaped = onnx.ModelProto() shaped.CopyFrom(model) for dimension, value in zip(shaped.graph.input[0].type.tensor_type.shape.dim, [1, 3, 48, width]): @@ -67,28 +71,32 @@ def compile_models(source: Path, expected_sha256: str, configuration: Path, outp context_path = output / f"rec-{width}-context.onnx" onnx.save_model(shaped, static_path) options = ort.SessionOptions() + options.intra_op_num_threads = 1 + options.inter_op_num_threads = 1 options.add_session_config_entry("ep.context_enable", "1") options.add_session_config_entry("ep.context_file_path", str(context_path.absolute())) options.add_session_config_entry("ep.context_embed_mode", "1") session = ort.InferenceSession(str(static_path), sess_options=options, providers=["VitisAIExecutionProvider", "CPUExecutionProvider"], - provider_options=[{"config_file": str(config_path.absolute())}, {}]) - result = session.run(None, {session.get_inputs()[0].name: np.zeros([1, 3, 48, width], dtype=np.float32)}) - if len(result) != 1 or result[0].dtype != np.float32 or result[0].ndim != 3: + provider_options=[{"config_file": str(config_path.absolute()), + "cache_dir": str((output / "compiler-cache").absolute()), + "cache_key": f"rec-{width}", + "enable_cache_file_io_in_mem": "0"}, {}]) + inputs, outputs = session.get_inputs(), session.get_outputs() + if (len(inputs) != 1 or inputs[0].type != "tensor(float)" or + inputs[0].shape != [1, 3, 48, width] or len(outputs) != 1 or + outputs[0].type != "tensor(float)" or len(outputs[0].shape) != 3): raise ValueError("AMD compiler produced an incompatible recognition tensor") del session count = validate_context(context_path) - # Reopen the exact deployment artifact: compilation success alone is - # insufficient evidence that the embedded context can be loaded. - deployed = ort.InferenceSession(str(context_path), providers=["VitisAIExecutionProvider", "CPUExecutionProvider"], - provider_options=[{"config_file": str(config_path.absolute())}, {}]) - deployed.run(None, {deployed.get_inputs()[0].name: np.zeros([1, 3, 48, width], dtype=np.float32)}) - del deployed + # Compilation and structural validation do not execute the model. + # Hardware inference is a separate, optional activity. models.append({"width": width, "artifact": record(context_path, output), "contextCount": count}) static_path.unlink() - print(f"Compiled and reopened recognition bucket {width}", flush=True) + print(f"Compiled recognition bucket {width} (inference not executed)", flush=True) manifest = {"schemaVersion": "1.0", "sourceModelSha256": expected_sha256, "precision": "bf16", "runtimeVersion": ort.__version__, + "inferenceValidated": False, "compilerConfiguration": record(config_path, output), "recognitionModels": models} (output / "compile-manifest.json").write_text(json.dumps(manifest, indent=2) + "\n", "utf-8") return manifest @@ -100,8 +108,19 @@ def main() -> None: parser.add_argument("--source-sha256", required=True) parser.add_argument("--configuration", type=Path, default=Path(__file__).with_name("vaip_config.json")) parser.add_argument("--output-dir", type=Path, required=True) + parser.add_argument("--width", type=int, action="append", + help="Compile selected buckets; SDK import still requires all 20") args = parser.parse_args() - compile_models(args.source_model, args.source_sha256, args.configuration, args.output_dir) + # Vendor compiler components write signatures relative to the current + # directory. Keep those files out of the repository and isolate buckets + # when independent compiler processes run concurrently. + args.source_model = args.source_model.resolve() + args.configuration = args.configuration.resolve() + args.output_dir = args.output_dir.resolve() + working = args.output_dir.with_name(args.output_dir.name + ".work") + working.mkdir(parents=True, exist_ok=True) + os.chdir(working) + compile_models(args.source_model, args.source_sha256, args.configuration, args.output_dir, args.width) if __name__ == "__main__": diff --git a/tools/npu/fetch_amdnpu.py b/tools/npu/fetch_amdnpu.py new file mode 100644 index 0000000..6cafe05 --- /dev/null +++ b/tools/npu/fetch_amdnpu.py @@ -0,0 +1,82 @@ +#!/usr/bin/env python3 +"""Fetch the immutable AMD deployment payload, without installing the compiler.""" +from __future__ import annotations + +import argparse +import json +import os +from pathlib import Path +import shutil +import subprocess +import tarfile +import urllib.error +import urllib.request + +try: + from .sdk import digest, relative_path, validate_sdk +except ImportError: + from sdk import digest, relative_path, validate_sdk + +LOCK = Path(__file__).with_name("amdnpu.lock.json") + + +def fetch(cache: Path, output: Path, offline: bool = False) -> dict: + lock = json.loads(LOCK.read_text("utf-8")) + package = lock["package"] + cache.mkdir(parents=True, exist_ok=True) + archive = cache / package["filename"] + if not archive.is_file(): + if offline: + raise ValueError("locked AMD deployment archive is absent from the offline cache") + temporary = cache / (package["filename"] + ".partial") + try: + with urllib.request.urlopen(package["url"], timeout=60) as source, temporary.open("wb") as target: + shutil.copyfileobj(source, target) + except urllib.error.HTTPError as error: + # The payload is attached to the release draft during preparation. + # gh keeps the credential on GitHub API requests, including when + # asset downloads redirect to a separate storage host. + if error.code != 404 or not (os.environ.get("GH_TOKEN") or os.environ.get("GITHUB_TOKEN")): + raise + temporary.unlink(missing_ok=True) + subprocess.run(["gh", "release", "download", package["releaseTag"], + "--repo", package["repository"], "--pattern", package["filename"], + "--output", str(temporary)], check=True) + temporary.replace(archive) + if archive.stat().st_size != package["bytes"] or digest(archive) != package["sha256"]: + raise ValueError("AMD deployment archive identity mismatch") + if output.exists(): + raise ValueError("output directory already exists; choose a fresh SDK directory") + output.mkdir(parents=True) + with tarfile.open(archive, "r:gz") as bundle: + seen = set() + for member in bundle: + path = relative_path(member.name) + if member.name in seen or not (member.isfile() or member.isdir()): + raise ValueError(f"invalid AMD deployment archive entry: {member.name}") + seen.add(member.name) + destination = output.joinpath(*path.parts) + if member.isdir(): + destination.mkdir(parents=True, exist_ok=True) + continue + destination.parent.mkdir(parents=True, exist_ok=True) + with bundle.extractfile(member) as source, destination.open("wb") as target: + shutil.copyfileobj(source, target) + value = validate_sdk(output, "amdnpu") + if value["artifactSetSha256"] != lock["artifactSetSha256"]: + raise ValueError("AMD deployment artifact set does not match the lock") + return value + + +def main() -> None: + parser = argparse.ArgumentParser() + parser.add_argument("--package-cache", type=Path, required=True) + parser.add_argument("--output-dir", type=Path, required=True) + parser.add_argument("--offline", action="store_true") + args = parser.parse_args() + value = fetch(args.package_cache, args.output_dir, args.offline) + print(f"AMD deployment SDK: {value['artifactSetSha256']} (hardware inference not validated)") + + +if __name__ == "__main__": + main() diff --git a/tools/npu/import_amdnpu.py b/tools/npu/import_amdnpu.py index 5182135..42e05c2 100644 --- a/tools/npu/import_amdnpu.py +++ b/tools/npu/import_amdnpu.py @@ -19,7 +19,8 @@ from sdk import artifact_set, digest, record, recognition_widths, validate_sdk, verify_record -def import_sdk(runtime: Path, models: Path, licenses: list[Path], output: Path, version: str) -> dict: +def import_sdk(runtime: Path, models: Path, licenses: list[Path], output: Path, version: str, + source_manifest: Path | None = None) -> dict: compiled = json.loads((models / "compile-manifest.json").read_text("utf-8")) if (compiled.get("schemaVersion") != "1.0" or compiled.get("precision") != "bf16" or [item["width"] for item in compiled["recognitionModels"]] != recognition_widths() or @@ -37,14 +38,20 @@ def import_sdk(runtime: Path, models: Path, licenses: list[Path], output: Path, lib.mkdir() provenance = [] for source in sorted(runtime.iterdir()): - if not re.fullmatch(r"lib[^/]+\.so(?:\.[0-9]+)*", source.name): + if not re.fullmatch(r"lib[^/]+\.so(?:\.[A-Za-z0-9_-]+)*", source.name): continue if not source.resolve().is_file(): raise ValueError(f"AMD runtime entry is not a file: {source}") destination = lib / source.name shutil.copyfile(source.resolve(), destination) source_hash = digest(destination) - subprocess.run([patchelf, "--set-rpath", "$ORIGIN", str(destination)], check=True, capture_output=True) + # AMD's Linux wheels contain ELF files marked with an executable + # stack. Recent glibc rejects those files in dlopen/dlmopen. ELF + # metadata is adjusted alongside the package-local search path, with + # both the original and packaged hashes in provenance. + subprocess.run([patchelf, "--clear-execstack", str(destination)], check=True, capture_output=True) + subprocess.run([patchelf, "--set-rpath", "$ORIGIN:/opt/xilinx/xrt/lib", str(destination)], + check=True, capture_output=True) provenance.append({"path": destination.relative_to(output).as_posix(), "sourceSha256": source_hash, "packagedSha256": digest(destination)}) names = {item.name for item in lib.iterdir()} @@ -92,7 +99,8 @@ def import_sdk(runtime: Path, models: Path, licenses: list[Path], output: Path, "files": [record(path, output) for path in sorted(output.rglob("*")) if path.is_file()], "licenses": [record(path, output) for path in sorted(license_dir.iterdir())], "provenance": provenance, - "source": {"url": "https://ryzenai.docs.amd.com/en/latest/linux.html"}} + "source": (json.loads(source_manifest.read_text("utf-8")) if source_manifest else + {"url": "https://ryzenai.docs.amd.com/en/latest/linux.html"})} (output / "sdk-manifest.json").write_text(json.dumps(manifest, indent=2) + "\n", "utf-8") return validate_sdk(output, "amdnpu") @@ -104,9 +112,12 @@ def main() -> None: parser.add_argument("--license-file", type=Path, action="append", required=True) parser.add_argument("--output-dir", type=Path, required=True) parser.add_argument("--version", default="1.8.0") + parser.add_argument("--source-manifest", type=Path, + help="Archive identities and compiler provenance for the acquired deployment payload") args = parser.parse_args() - value = import_sdk(args.runtime_dir, args.models_dir, args.license_file, args.output_dir, args.version) - print(f"AMD NPU SDK: {value['artifactSetSha256']} (qualification-only)") + value = import_sdk(args.runtime_dir, args.models_dir, args.license_file, args.output_dir, args.version, + args.source_manifest) + print(f"AMD NPU SDK: {value['artifactSetSha256']} (hardware inference not validated)") if __name__ == "__main__": diff --git a/tools/npu/prepare_release.py b/tools/npu/prepare_release.py index f229032..7ac5f85 100644 --- a/tools/npu/prepare_release.py +++ b/tools/npu/prepare_release.py @@ -13,7 +13,13 @@ def main() -> None: parser = argparse.ArgumentParser() parser.add_argument("--sdk-root", type=Path, required=True) parser.add_argument("--github-env", type=Path, required=True) + parser.add_argument("--require-provider", choices=("openvino", "amdnpu"), + action="append", default=[]) args = parser.parse_args() + missing = [provider for provider in args.require_provider + if not (args.sdk_root / provider).is_dir()] + if missing: + raise ValueError(f"release is missing required NPU payloads: {', '.join(missing)}") found = False lines = [] for provider in ("openvino", "amdnpu"): diff --git a/tools/npu/vaip_config.json b/tools/npu/vaip_config.json index 6dba3aa..b3be01b 100644 --- a/tools/npu/vaip_config.json +++ b/tools/npu/vaip_config.json @@ -4,7 +4,8 @@ { "name": "vaiml_partition", "plugin": "vaip-pass_vaiml_partition", - "vaiml_config": {"optimize_level": 1, "preferred_data_storage": "vectorized"} + "vaiml_config": {"optimize_level": 1, "preferred_data_storage": "auto", + "enable_f32_to_bf16_conversion": true} } ], "target": "VAIML",