GenieX 版本发布全流程实战:SemVer Tag 决策、HTP 签名门控与多通道交付
【免费下载链接】GenieXRun frontier LLMs and VLMs locally on Qualcomm devices across NPU, GPU, and CPU with a few lines of code项目地址: https://gitcode.com/GitHub_Trending/ne/GenieX
GenieX 是一个面向 Qualcomm 设备(NPU / GPU / CPU)的端侧 LLM 与 VLM 推理运行时,其每个版本都由一个v前缀的 SemVer 2.0 标签触发.github/workflows/release.yml流水线完成构建、签名、打包与多渠道分发。本文以仓库权威规则文档 notes/release.md 与 Agent 快速决策脚本 .claude/commands/release.md 为主体,结合 release.yml 等源码级证据,完整讲解 GenieX 的版本号决策算法、发布通道(alpha/beta/rc/stable)语义、Hexagon HTP 驱动签名门控、Windows 安装器签名门控、S3 镜像与 Manifest 契约,以及发布前的最终检查清单。读完本文,你将掌握一套可复制的、工程严谨的版本发布方法论,并能在 GenieX 仓库中独立完成一次从打标签到观察流水线回执的完整发布。
一、发布总览:一个 Tag 触发整条交付流水线
在 GenieX 中,"发布"不是一个手工上传文件的操作,而是在某个 commit 上打一个v前缀的 SemVer 标签并推送:
git tag v1.2.3 && git push origin v1.2.3推送标签会触发 .github/workflows/release.yml 工作流,其核心作业拓扑如下:
| 作业 | 职责 |
|---|---|
resolve-tag | 校验标签符合 SemVer 2.0 正则;区分 pre-release(含-的标签)与 stable;对 workflow_dispatch 场景校验分支指向与标签一致 |
build-sdk/build-android-aar/build-python-sdist | 构建各平台 SDK、Android AAR、Python sdist |
overlay-htp | 在build-cli之前从qcom-ai-hub/geniex的 LFS 分支稀疏检出 Microsoft 签名过的 HTP 驱动包并覆盖进 SDK 产物 |
package-release | 汇总所有平台产物、生成.sha256校验文件、组装发布资产 |
publish-github-release/publish-s3/publish-docker/publish-testpypi/publish-pypi/publish-maven-central | 多渠道分发 |
关键设计点(源码可见于 release.yml):
- 标签不可变是最高约束:
resolve-tag作业对已带资产的 stable release 直接拒绝覆盖("refusing to overwrite");发布出错只能切一个更高版本号,绝不回收旧标签。 - workflow_dispatch 必须从标签 ref 出发:手动重跑同一标签时,必须将 "Use workflow from" 设为该标签(Tags 标签页)。若从
main派发,resolve-tag会检测到GITHUB_SHA与标签指向的 commit 不一致并报错拒绝,从而保证构建的源码与标签一一对应。 - pre-release 可重切,stable 不可:含
-的标签(如-rc.2)视为草稿,可重新运行;裸vX.Y.Z一旦发布即冻结。
标签语义:草稿 vs 正式发布
| 标签形式 | 含义 | 发布去向 |
|---|---|---|
vX.Y.Z-<channel>.<n>(含-) | 草稿(pre-release) | sdist 推送 TestPyPI,Docker 推送 prerelease 镜像 |
vX.Y.Z(裸标签) | 正式发布 | sdist 推送生产 PyPI,AAR 上传 Maven Central,S3 推进稳定指针 |
二、版本号决策:SemVer 2.0 与 Pre-1.0 特例
GenieX 严格遵循 SemVer 2.0,但带有v前缀:vX.Y.Z为稳定版,vX.Y.Z-<channel>.<n>为预发布版。
数字位含义
| Bump | 含义 | 典型触发场景 |
|---|---|---|
MAJOR(X) | 任何公共表面的破坏性变更,使用者必须适配 | CLI flag 删除/改名、SDK 头文件签名变化、Python API 移除、配置 key 改名 |
MINOR(Y) | 向后兼容的新特性 | 新 runtime、新模型支持、新 CLI 子命令、新 SDK 函数 |
PATCH(Z) | 向后兼容的修复或清理 | Bug 修复、依赖升级、文档/CI 变更、内部重构 |
Pre-1.0 规则:项目当前仍处于X = 0的 pre-1.0 阶段。私有/未发布期间绝不 bump MAJOR,破坏性变更一律 bumpMINOR(0.Y → 0.(Y+1),Z归零),并在发布说明中显式标注。只有当首次公开发布时,版本才晋升到X = 1。
决策算法(Decision Procedure)
这是 notes/release.md 与/release命令共同遵循的官方算法,按顺序执行:
找到最新稳定标签
v0.A.B。推荐命令:git tag --sort=-v:refname --list "v[0-9]*.[0-9]*.[0-9]*" | grep -v -- '-' | head -1注意不要用
git describe——它只遍历 HEAD 的祖先链,会漏掉在侧分支上打的稳定标签。若命令无输出(全新仓库),目标定位v0.1.0并跳到第 3 步。选定目标版本
X.Y.Z,依据git log v0.A.B..HEAD --format="%h %s" --stat(一条命令同时拿到提交主题与改动文件,仍存疑时才用git show <sha>):- 任一破坏性变更 →
v0.(A+1).0(X = 0阶段破坏性 bump MINOR); - 否则任一 feature →
v0.(A+1).0; - 否则 →
v0.A.(B+1)。
Conventional Commits 前缀(
feat:、fix:、feat!:)只是提示而非契约——仓库不强制校验,主题有歧义时必须同时读 subject 和 diff 再判定。- 任一破坏性变更 →
选定通道,依据该目标版本的周期位置:
- 面向新目标的第一个标签,在 feature 分支 →
alpha.1; - 面向新目标的第一个标签,在
main→rc.1(除非用户明确要求,否则跳过 beta,大多数周期直接进 rc); - 已在周期中 → 通道内递增(
-rc.1→-rc.2)或前移一个通道(-beta.3→-rc.1,n重置)。同一X.Y.Z上通道只能前进; - 所有
-rc.n全绿且 HTP 为 Microsoft 签名 → 切裸vX.Y.Z。
- 面向新目标的第一个标签,在 feature 分支 →
周期中途的破坏性变更导致目标上升时:放弃当前
X.Y.Z(保留已有标签、不回收),从v0.(new)-alpha.1或-rc.1重新开始。计数器
n的归零规则:每个X.Y.Z、每个通道独立计数,例如0.4.0-alpha.{1,2}→0.4.0-beta.{1}→0.4.0-rc.{1,2}→0.4.0。
三个工作示例
- 最新稳定
v0.3.2,git log v0.3.2..HEAD含一个fix:和一个docs:。在main上,首个标签 →v0.3.3-rc.1,之后 →v0.3.3。 - 最新稳定
v0.3.2,日志含一个新增 runtime 的feat:。在 feature 分支 →v0.4.0-alpha.1;合入main后 →v0.4.0-rc.1→v0.4.0。 - 在
v0.4.0-rc.2上落地了一个 CLI flag 改名(破坏性),目标从0.4.0升至0.5.0。-rc.2保持不动,下一个标签是v0.5.0-alpha.1或v0.5.0-rc.1(取决于所在分支)。
三、发布通道:alpha / beta / rc / stable 的语义与约束
通道表达一个构建的成熟度,排序为alpha < beta < rc < stable:
| 通道 | 用途 | 允许分支 | 仍可变更的内容 |
|---|---|---|---|
alpha.n | 分享进行中的构建,特性形态可能继续变化 | feature 分支或main | 一切,包括破坏性变更 |
beta.n | 目标X.Y.Z特性已冻结,寻求反馈 | main | 仅 Bug 修复与打磨 |
rc.n | 发布候选——"除非发现 Bug 否则就发布" | main | 仅 Bug 修复 |
| stable | 已发布版本 | main | 不可再变——只能切新版本 |
配套铁律:
- stable 之前必须至少经过一个
main上的-rc.n; - stable 标签必须位于 HTP bundle 为 Microsoft 签名的 commit 上(详见下文 HTP 签名一节);不确定时运行
gh run list --workflow release.yml --limit 5确认 SDK 产物名不以-selfsigned结尾; - 已发布的标签绝不重用或移动,出错只能靠新 patch 版本回退;
- feature 分支上的
alpha.n标签是可丢弃的——之后不要对已打标签的 commit 做 rebase。
四、/release 命令:Agent 的快速决策路径
.claude/commands/release.md 是 Claude Code 的/release斜杠命令,其存在意义是内联了上面的权威算法(notes/release.md 为唯一权威源),让 Agent 无需通读笔记即可直接提议标签;若两者冲突,以 notes 为准并将该命令文件标记为过期。该命令配合 .claude/settings.json 中的只读命令白名单(git status、git log、git tag --list:*、gh run list:*等)工作,将 Agent 的发布操作限制在只读侦查 + 需人工确认的推送范围内。
Step 1 — 单次 Bash 调用完成信息采集
git fetch --tags --prune origin --quiet && \ echo "=== working tree ===" && git status --porcelain && \ echo "=== branch ===" && git status -sb | head -1 && \ STABLE=$(git tag --sort=-v:refname | grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$' | head -1) && \ echo "=== latest stable === $STABLE" && \ echo "=== recent tags ===" && git tag --sort=-v:refname | head -15 && \ echo "=== commits since $STABLE ===" && \ git log "$STABLE..HEAD" --no-merges --format="%h %s" --stat其中的可移植性要点值得留意:
- 用
grep -E '^v[0-9]+\.[0-9]+\.[0-9]+$'挑选稳定标签:grep -v -- '-'在 ugrep 下会崩溃(把-当成 stdin 而非模式);正向来匹配还能顺带排除v0.1、v1这类残缺标签; git describe不可用(同上文原因);- 空
$STABLE(新仓库)→ 目标v0.1.0直接进入 Step 3; --no-merges去掉 merge commit 带来的约 50% 日志噪声。
执行前必须满足的前置条件:
- 工作区干净(未跟踪的
.claude/worktrees/目录可忽略); - 分支与
origin/<branch>同步——git status -sb不显示ahead/behind; - 当前分支符合通道规则:
alpha.n允许 feature 分支或main;beta.n、rc.n、stable 只能在main。
Step 2 — 提议标签
按上文"决策算法"顺序应用:先定X.Y.Z(破坏性→MINOR、特性→MINOR、修复/文档→PATCH),再按周期位置定通道,最后处理周期中途破坏性变更的"放弃重启"场景。输出时给出提议标签 + 一行理由(bump 原因 + 通道 + 为什么);只有两种 bump 都确实站得住脚时才让用户二选一。此外还有一个重要提醒:如果距最新稳定仅有一两个 docs/chore/style 提交,应主动提示用户可能不值得为一个琐碎变更滚动一个-rc.1。
Step 3 — 稳定标签的额外检查
仅在提议裸vX.Y.Z时执行(pre-release 直接跳过):
gh run list --workflow release.yml --limit 5 --json name,conclusion,headSha,displayTitle- 之前的
-rc.n必须全绿; - SDK 产物名不得以
-selfsigned结尾; - 若为 self-signed → 停止、告警,并指向 notes/release.md 的"Promoting self-signed → Microsoft-signed"小节;
- 无法确定 → 询问用户,不要臆断。
Step 4 — 切标签并推送
git tag <tag> && git push origin <tag>必须在本次会话内获得用户对该具体标签的明确批准后才能执行。
Step 5 — 观察流水线
使用gh run watch(避免 sleep 循环轮询)。HTP 命中/未命中时,按 notes/release.md 的 "Hexagon HTP signing" 小节处理。
Guardrails(不可逾越的红线)
- 绝不移动或重用已发布的标签;标签打错 → 切一个更高的;
- 未经用户对该具体标签的明确同意绝不推送;
- 绝不在未签名构建上提议裸 stable 标签;不确定 → 询问;
- Pre-1.0(
X = 0)阶段绝不 bumpX,破坏性变更 bump MINOR。
五、发布产物与多渠道交付
每次发布生成的资产
| 资产 | 说明 |
|---|---|
geniex-sdk-{linux,windows}-arm64-<tag>.zip | Linux / Windows ARM64 SDK |
geniex-cli-linux-arm64-<tag>.tar.gz | Linux CLI 归档 |
geniex-cli-setup-windows-arm64-<tag>.exe | Windows CLI 安装器 |
geniex-android-aar-<tag>.aar | Android AAR |
geniex-bench-{linux,windows,android}-arm64-<tag>.{tar.gz,zip} | 独立基准测试归档 |
geniex-pysdist{,-llama_cpp,-qairt}-<tag>.tar.gz | Python sdist(含两个插件变体) |
每个文件的.sha256旁车文件 | 完整性校验 |
CPU-only 变体(不带 NPU 加速的构建)在 Linux 与 Android 资产上以-cpu中缀命名:geniex-{sdk,cli}-linux-arm64-cpu-<tag>.*与geniex-android-aar-cpu-<tag>.aar。Docker 以-cpu标签镜像它;Maven Central 只发布默认 AAR。不产出geniex-bench的 CPU-only 归档:Linux SDK zip 已内含bin/geniex-bench,且基准测试设备群没有需要 CPU-only 构建的设备。
分发渠道
- GitHub Releases:全部二进制资产;
- PyPI / TestPyPI:pre-release 标签推送 sdist 到 TestPyPI,stable 标签推送到生产 PyPI(均通过 OIDC Trusted Publishing,无需 API token);
- Maven Central:仅 stable 标签,
com.qualcomm.qti:geniex-android。上传走 Sonatype Central Portal,采用USER_MANAGED发布类型——上传落地的 PENDING → VALIDATED 后由操作员在 Portal 界面点击 Publish 最终上线(Maven Central 不可变,保留人工闸门;流水线足够可信后可切换AUTOMATIC); - S3 镜像:见下节;
- Docker:prerelease 与 stable 均推送。
重跑同一标签是安全的
通过Actions → Release → Run workflow重跑同一标签是安全的,前提是 "Use workflow from" 必须选到该标签(Tags 标签页)。从main派发会被resolve-tag拒绝,从机制上保证流水线绝不对错误 commit 构建标签。
六、Hexagon HTP 驱动签名:Windows ARM64 的发布硬门槛
Windows ARM64 SDK 携带libggml-htp.cat与libggml-htp-v{73,75,79,81}.so四个架构变体——Windows 拒绝加载未签名的驱动。因此发布流水线在build-cli之前安排了一个overlay-htp作业(源码见 release.yml 的overlay-htp (windows-arm64)):
- 初始化
third-party/llama.cpp子模块,取其当前 short SHA; - 通过
GH_PAT从qcom-ai-hub/geniex的chore/signed-htp-lfs-store分支稀疏检出sdk/signed-htp/libggml-htp-<sha>.zip(LFS 追踪),其中<sha>为third-party/llama.cpp的 short SHA; - Hit:把 Microsoft 签名的文件覆盖进 SDK 产物,
build-cli将其打包进安装器,正常发布; - Miss:保留自签名构建,SDK 名称追加
-selfsigned后缀,同时附带ggml-htp-v1.cer(用户导入用)与libggml-htp-to-sign-<sha>.zip(运维提交签名用)。
签名 bundle 必须恰好包含 zip 根下的六个文件:libggml-htp.cat、libggml-htp.inf,以及libggml-htp-v{73,75,79,81}.so。
如果 CI 报signed=false但 bundle 确实存在于chore/signed-htp-lfs-store,优先检查GH_PAT是否过期(该跨仓库 checkout 复用了同一密钥)。
从 Self-signed 晋升到 Microsoft-signed
- 从草稿 release 下载
libggml-htp-to-sign-<sha>.zip; - 提交微软签名:将
.cat、.inf与所有.so放入 samba 的ATT\libggml-htp\,提交 Jenkins pipeline(路径填\path\to\ATT,其余字段用默认值或首参),从ATT\Glymur\01000\ExtractedDrivers取回签名文件,把签名文件(不含.inf)按根目录布局重打包为 zip; - 提交到
qcom-ai-hub/geniex仓库的sdk/signed-htp/libggml-htp-<sha>.zip(chore/signed-htp-lfs-store分支)——本地先git lfs install,可直接 push zip,或对该分支开 PR 后 squash-merge; - 为同一标签重跑 Release 工作流。
用户侧的自签名回退路径
当发布附带-selfsignedSDK 与ggml-htp-v1.cer时,Windows 用户需要开启测试签名并信任证书(开发者/用户的完整操作细节见 notes/run.md 的 "Self-signed fallback" 小节):
- 以管理员 PowerShell 执行
bcdedit /set TESTSIGNING ON并重启(Secure Boot 开启时先关闭再重试); - 通过
certlm.msc(必须管理员启动)将ggml-htp-v1.cer导入两个存储:Trusted Root Certification Authorities与Trusted Publishers——前者保证证书链有效,后者抑制驱动加载弹窗,缺一不可; - 用
bcdedit /enum | Select-String testsigning确认显示testsigning Yes。
七、Windows 安装器签名门控:先发布后签名的时序保障
Windows 安装器是在 Authenticode 签名之前发布的,因此geniex update以 S3 上的windows-signed.txt作为门控,避免把未签名构建推给用户。客户端侧实现位于 cli/cmd/geniex/update.go 的isWindowsSigned()(约 L233-L255):Windows 上发现新版本后,更新器先 GET 该文件——内容为true则继续下载;内容为其他值或请求失败则视为"已是最新"并跳过。
发布侧的配套动作:最新稳定安装器签名完成后,将windows-signed.txt上传为true;发布下一个尚未签名的安装器之前,将其改回非true值。
八、S3 镜像与 Manifest 契约
每个 release 的一个子集会镜像到s3://qaihub-public-assets/qai-hub-geniex/,供应用与安装器匿名 GET,从而绕开 GitHub API 的速率限制。镜像采用扁平布局(无<tag>/子目录),靠文件名中的<tag>区分版本。
对象清单(节选)
| 对象 | 每个标签都有? | 缓存 | 用途 |
|---|---|---|---|
geniex-sdk-windows-arm64-<tag>.zip(.sha256) | 是 | default | Windows SDK |
geniex-sdk-linux-arm64-<tag>.zip(.sha256) | 是 | default | Linux SDK |
geniex-sdk-linux-arm64-cpu-<tag>.zip(.sha256) | 是 | default | CPU-only Linux SDK,由GENIEX_SDK_VARIANT=cpu拉取 |
geniex-cli-setup-windows-arm64-<tag>.exe(.sha256) | 是 | default | Windows CLI 安装器(带版本) |
geniex-cli-linux-arm64-<tag>.tar.gz(.sha256) | 是 | default | Linux CLI 归档(带版本) |
install-<tag>.sh(.sha256) | 是 | default | Linux 安装脚本(带版本,--version钉住) |
geniex-cli.exe | 仅 stable | no-cache | 指向最新稳定 Windows 安装器的可变指针 |
geniex-cli-linux-arm64.tar.gz(.sha256) | 仅 stable | no-cache | 被install.sh消费的可变指针 |
install.sh | 仅 stable | no-cache | 可变安装脚本,curl ... | sh入口 |
manifest-<tag>.json | 是 | immutable | 单标签资产清单 |
index.json | 是 | no-cache | 完整版本目录 |
latest.json | 仅 stable | no-cache | 指向最新 stable manifest |
windows-signed.txt | 仅 stable | no-cache | 最新 Windows 安装器的签名门控 |
AAR、sdist、HTP 证书/待签名 zip 等其余资产只通过 GitHub Releases / Maven Central / PyPI(stable)与 TestPyPI(pre-release)分发,不进 S3。
客户端应用的 Manifest 契约
所有文件携带schema_version: 1。S3 发布运行在 geniex 仓库(IAM 角色的 OIDC 信任只允许qcom-ai-hub/geniex),本仓库的publish-s3作业只负责跨仓库派发并监视结果——这解释了 release.yml 中publish-s3作业通过GH_PAT触发 geniex 的chore/publish-s3分支并gh run watch的设计。
更新检查(最轻量)——GETlatest.json,将tag与本地安装版本比较。no-cache缓存保证响应始终最新;仅当有 stable 标签发布后才存在。
版本选择器(历史/回滚)——GETindex.json:
{ "schema_version": 1, "updated_at": "2026-05-19T08:23:11Z", "latest_stable": "v0.1.5", "latest_prerelease": "v0.1.6-rc.2", "versions": [ { "tag": "v0.1.6-rc.2", "is_prerelease": true, "released_at": "...", "manifest": "manifest-v0.1.6-rc.2.json" }, { "tag": "v0.1.5", "is_prerelease": false, "released_at": "...", "manifest": "manifest-v0.1.5.json" } ] }资产下载——GETversions[].manifest指向的单标签 manifest:
{ "schema_version": 1, "tag": "v0.1.5", "is_prerelease": false, "released_at": "...", "llama_sha": "abc123", "htp_signed": true, "assets": [ { "name": "geniex-sdk-windows-arm64-v0.1.5.zip", "url": "…/qai-hub-geniex/geniex-sdk-windows-arm64-v0.1.5.zip", "size": 123456789, "sha256": "...", "kind": "sdk", "platform": "windows", "arch": "arm64" } ] }契约要点:
assets[].url始终指向带版本的对象(绝不指向可变的geniex-cli.exe/geniex-cli-linux-arm64.tar.gz/install.sh),因此引用的字节不可变,所列sha256是权威值;kind取值为sdk/cli-installer/cli-archive/install-script/sha256之一;- 单标签 manifest 在同标签的多次工作流重跑间字节稳定——
released_at保留首次发布的值,客户端可以永久缓存它; - CPU-only 对象刻意不出现在 manifest 中:其元数据与默认构建完全相同,若允许按
kind/platform/arch查找,可能把慢速构件交到普通 Snapdragon 设备手里。install.sh --cpu-only与GENIEX_SDK_VARIANT=cpu都按命名约定直接拼 URL,无需清单发现。
九、发布前最终检查清单
notes/release.md 末尾给出了发布操作者的完整检查清单:
- 确认
libggml-htp已签名;未签名时:- 先打一个伴随
llama.cpp升级的 alpha 标签; - 按 "Promoting self-signed → Microsoft-signed" 完成签名晋升;
- 先打一个伴随
- 在
main上触发一个 rc 标签,并验证以下模型在cpu/gpu/npu上的表现:unsloth/Qwen3-0.6B-GGUF与unsloth/Qwen3.5-0.8B-GGUF;qualcomm/Qwen3-4B与qualcomm/Qwen3-VL-4B-Instruct;- 在 Qualcomm PC 上测试
llama_cpp的 NPU 路径;
- 在同一个 commit 上创建正式发布标签;
- 签名 Windows 安装器:
- 将未签名安装器交给签名团队;
- 用签名后的安装器替换 S3 上的对象;
- 更新对应的
.sha256; - 将
windows-signed.txt更新为true(见 cli/cmd/geniex/update.go 的门控逻辑)。
十、总结:把发布做成可审计的工程流程
回顾 GenieX 的发布体系,可以提炼出几条具有普适性的工程原则:
- 标签即契约:SemVer 标签不仅是版本号,还同时编码了发布通道(
-alpha/-beta/-rc)、草稿/正式属性(是否含-)与触发入口(v*推送触发流水线); - 版本决策显式化:把 bump 规则、通道规则、pre-1.0 特例写成表格与算法,甚至内联进 Agent 命令(.claude/commands/release.md),让人与工具遵循同一套判定逻辑;
- 签名是发布的核心门控而非附加项:从 CI 的
overlay-htp作业、到-selfsigned命名约定、再到客户端的windows-signed.txt检查,签名状态贯穿了构建、打包、分发与更新的全链路; - 发布源(tag)与产物强绑定:
resolve-tag对 dispatch 分支的校验、manifest 的字节稳定性、assets[].url永指版本化对象,共同保证了"同一标签可安全重跑、同一产物可永久缓存"。
这套机制既服务于 Linux / Windows / Android 多平台的 SDK 与 CLI 发布,也支撑了 Python sdist、Android AAR 与 Docker 镜像的多渠道交付,是端侧 AI 运行时项目里一份完整可复用的发布工程范本。
【免费下载链接】GenieXRun frontier LLMs and VLMs locally on Qualcomm devices across NPU, GPU, and CPU with a few lines of code项目地址: https://gitcode.com/GitHub_Trending/ne/GenieX
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考