
GenieX SDK 端到端测试体系深度解析pytest 套件、模型矩阵与云端真机 CI【免费下载链接】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 仓库中 tests/README.md 所定义的 SDK pytest 端到端测试套件它通过公开 Python 绑定驱动真实 LLM / VLM 推理同时充当公共 API 表面契约的守门人。你将掌握该套件的目录组织、行为用例设计、models.json模型矩阵与 marker 门控机制、本地到真机的三层运行方式以及 Unit / QDC 双 CI 工作流的完整实现脉络并理解其与 bindings/python/tests/ 的职责边界。套件定位一份公开表面契约检查GenieX 的核心能力是用几行 Python 代码在骁龙设备的 NPU / GPU / CPU 上本地运行前沿 LLM 与 VLM。为了守住这条公共 Python 绑定bindings/python/的可信度仓库在tests/下建立了一套端到端End-to-End测试不是 mock而是真实下载模型、真实加载插件、真实跑推理。套件通过geniex公开 API 驱动llama_cpp与qairt两个插件对应 sdk/plugins/llama_cpp/ 与 sdk/plugins/qairt/因此一份测试文件同时验证了两件事SDK 公共 API 的形状与行为符合预期契约层两个后端插件在 CPU / GPU / NPU / hybrid 各设备映射上产出正确的推理结果行为层。目录结构详见 tests/README.mdtests/ ├── assets/ # 真实测试图片quality_dog.jpg NOTICE.md ├── qdc/ # QDC 设备调度器 各平台入口脚本 ├── test_api.py # SDK 元数据 resolve API无需模型 ├── test_llama_cpp.py # llama_cpp 插件 — LLM VLM precision MTP ├── test_qairt.py # qairt 插件 — LLM VLM precision ├── conftest.py # 顶层 fixturesinit、模型路径、图片 ├── pytest.ini # Marker 注册表 套件发现规则 ├── _models.py # models.json 的模型矩阵加载器 ├── models.json # 矩阵role - [{id, precision, devices}] └── _quality_data.py # 两个插件共用的关键词质量提示词行为用例每个插件覆盖什么test_llama_cpp.py与test_qairt.py两份文件船运同一套行为用例qairt 缺 MTP见下文且每个文件第一个运行的都是一次显式的 model-manager 拉取检查测试验证内容test_model_manager_pull本文件所需的每个模型都能从 AI Hub / HuggingFace 拉取成功。test_llm_multi_turn两轮 Alice 对话第二轮必须回忆起用户名字。test_llm_greedy_is_deterministic相同提示在两个 seed 下解码出逐字节一致的文本。test_vlm_multi_turn第一轮带图、第二轮不带图的两轮对话。test_llm_quality_keywordsgreedy 解码下 3 个短问答关键词子串必须出现。test_vlm_quality_keywords金毛犬图片的 caption 必须命中一组规范关键词之一。test_mtp_multi_turn仅 llama_cpp同样的 Alice 对话以spec_typedraft-mtp运行。行为用例的参数化来自models.json每个用例以(model, device_map)二元组参数化展开因此哪些后端被覆盖是模型条目的属性而不是测试代码的属性。模板 / 契约用例ChatML 哨兵、enable_thinking、tools 渲染、mtmd 标记则固定钉在该 role 的第一条模型条目上——它们断言的是某一个模型的 tokenizer 细节。有一个刻意设计的取舍值得注意model-manager 拉取失败按 FAIL 处理而非 SKIP。正如 tests/conftest.py 中cachedfixture 的实现所示_mm.ensure_cached(...)一旦抛错会直接上抛让坏掉的下载显式变成一条红色 CI 腿而不是静默绿色的跳过。贪婪解码的哨兵约定为什么不用 0.0套件中每个生成单元都传入GREEDY_TEMPERATURE一个负数的 argmax 哨兵而不是0.0。原因在 _quality_data.py 和 tests/README.md 中均有交代两个插件都把temperature 0.0当作未设置并替换为 0.8。因此只固定 seed 并不能让一个生成单元变得确定——必须显式传入GREEDY_TEMPERATURE -1.0才能锁定 argmax 解码。这正是test_llm_greedy_is_deterministic能成立的前提。另一个对齐细节质量用例的提示词刻意在调用generate()之前自行调用apply_chat_template。上游 scorecard 走COMMON_CONVERSATION_MODE_AUTO会包一层聊天模板如果不包Qwen3 风格模型会漂移进 completion 模式关键词只靠采样运气才能出现见 _quality_data.py 的说明。模型矩阵tests/models.jsonmodels.json 是整套行为测试的矩阵本体加载器是 tests/_models.py。每个 role 映射到一组条目每个条目声明其运行的devices因此给整个行为套件加一个新模型只是一次清单编辑manifest edit。RoleModelDevicesllama_cpp_llmunsloth/Qwen3-4B-GGUFQ4_0cpu, gpu, npullama_cpp_llmunsloth/gpt-oss-20b-GGUFQ4_0hybridllama_cpp_vlmunsloth/gemma-4-E2B-it-GGUFQ4_0 mmproj-F16cpu, gpu, npullama_cpp_mtp_targetgoogle/gemma-4-26B-A4B-it-qat-q4_0-ggufnpullama_cpp_mtp_draftRachidAR/gemma-4-...-assistant-q4_0-gguf—与 target 配对qairt_llmqualcomm/Qwen3-4Bnpuqairt_vlmqualcomm/Qwen2.5-VL-7B-Instructnpu条目字段id、precision、hub默认auto、devices、quality_max_new_tokens关键词用例的每模型预算、env_override一个可替换id的环境变量。_models.py中的TestModeldataclass 与matrix()/pull_cells()函数正是把这些字段转成 pytest 参数单元param的桥matrix()按model × devices笛卡尔展开pull_cells()则只按模型展开用于拉取检查。矩阵设计的几个关键决策llama_cpp 与 QAIRT 共享同一对 LLM / VLM 模型因此一旦出现关键词质量差异可以归因到后端 / 量化方式而不是模型身份。gpt-oss-20b是hybrid专属条目需要更大的quality_max_new_tokens1024——它的 reasoning 通道没有关闭enable_thinking的开关。主 LLM 选用 Qwen3-4B base 而非 Instruct-2507Instruct-2507 在答案前会输出一长段think前缀在套件 256 token 预算下会把关键词挤出补全末尾把后端质量测试变成思考预算测试见 tests/README.md 的 Models 节。Marker 注册与设备门控套件使用三层 marker 体系注册表定义在 pytest.ini--strict-markers强制所有 marker 必须先注册Marker来源apitests/test_api.py中的条目llama_cpptests/test_llama_cpp.py中的条目qairttests/test_qairt.py中的条目device_cpu参数化为device_mapcpudevice_gpu参数化为device_mapgpudevice_npu参数化为device_mapnpudevice_hybrid参数化为device_maphybridsnapdragondevice_map∈ {gpu,npu,hybrid} 的单元自动llm/vlm通过pytest.mark.llm/.vlm按测试手工应用自动打标逻辑在 conftest.py 的pytest_collection_modifyitems中tests/test_name.py的文件名主干决定插件 markerllama_cpp/qairt/apicallspec.params[device_map]决定设备 marker而gpu/npu/hybrid三档即需要真实硬件的 OpenCL / HTP 后端再额外打上snapdragon。门控规则在pytest_runtest_setupconftest.py带snapdragon或qairtmarker 的用例只有在GENIEX_DEVICE_TEST1且宿主机是骁龙设备时才运行否则自动 skip。宿主机判定_is_snapdragon_host()conftest.py检查platform.machine()是 arm64/aarch64且Windows 或 Android 直接判 TrueLinux 读取/sys/firmware/devicetree/base/compatible是否包含qcom。另有一条额外防线在 Android 上跳过device_mapgpu的 llama_cpp 用例因为其 OpenCL 后端在 Adreno / Android 上会 abortconftest.py。QAIRT 模型按需从 AI Hub 拉取如同 llama_cpp 模型从 HF 拉取所以设备分片device shards需要网络但无需手动预拉取。运行方式从无模型检查到全量真机矩阵tests/README.md 的 Running 节给出三档运行命令# 任意机器——仅无模型 API 检查 pytest tests -m api # 任意机器——追加 llama_cpp CPU 单元首次运行下载约 400 MB pytest tests -m api or (llama_cpp and device_cpu) # Snapdragon Windows ARM64 或 Qualcomm Linux——全量矩阵 #拉取 models.json 全部条目约 30 GB含 gpt-oss-20b 与 MTP 配对 GENIEX_DEVICE_TEST1 pytest tests换模型按条目用env_override覆盖或用GENIEX_TEST_MODELS替换整个矩阵tests/README.mdGENIEX_QAIRT_MODELqualcomm/other-llm \ GENIEX_QAIRT_VLM_MODELqualcomm/other-vlm \ GENIEX_DEVICE_TEST1 pytest tests -m qairt GENIEX_TEST_MODELS/path/to/my-matrix.json GENIEX_DEVICE_TEST1 pytest tests对应的环境变量替换逻辑在 _models.pyGENIEX_TEST_MODELS指向的 JSON 会整体取代models.json每个条目的env_override若存在且被设置则优先取其值作为模型id。顶层 fixtures一次会话、一次缓存、真实图片conftest.py 提供会话级 fixturegeniex_sessiongeniex.init()_mm.init()会话结束deinit()保证所有用例共用一次 SDK 初始化。cached按(id, precision)维度做会话级模型缓存内部走_mm.ensure_cached(model.id, precision..., hub...)后续按 role 派生的llama_cpp_llm_paths、llama_cpp_vlm_paths、qairt_llm_paths、qairt_vlm_paths都基于它。llama_cpp_mtp_pathstarget draft 配对在 Android 上直接 skipMTP targetdraft 两个 2×7B 级模型超出移动端内存。test_image指向 cli/server/docs/ui/favicon-32x32.pngVLM 对话用quality_image指向 tests/assets/quality_dog.jpg关键词质量用。无模型 API 契约test_api.pytest_api.py 是唯一可以在任何宿主机上跑的纯契约文件验证了公共 Python 绑定暴露的元数据与解析 APIgeniex.version()、get_plugin_version(llama_cpp / qairt)、get_runtime_list()、get_compute_unit_list(runtime)均非空且形状正确geniex.init()/deinit()在会话内幂等set_log_level接受trace/debug/info/warn/error/none公共表面导出检查test_public_surface_exports断言geniex.__all__必须包含AutoModelForCausalLM、AutoModelForVision2Seq、GenieXError、GenieXLLM、GenieXVLM、GenerateOutput、ProfileData、TextIteratorStreamer、init、deinit、set_log_level、set_qairt_runtime_path、get_qairt_runtime_path、version、get_plugin_version、get_runtime_list、get_compute_unit_list、resolve_device_map、model_manager等符号qairt runtime 路径锁定init 之后set_qairt_runtime_path必须抛GenieXError进程全局、初始化后锁定见 notes/run.md 的 Using a custom QNN library 节resolve_device_map别名表cpu→ llama_cpp 且ngl0hybrid→ llama_cpp 且nglNone即全部层 offloadllama_cpp/llama_cpp:npu→ 默认设备HTP0qairt:npu→ qairt。文件注释明确指出真源在 sdk/src/device.cpp别名表有任何改动都必须同步这些测试。质量与确定性数据的统一底座_quality_data.py_quality_data.py 是两份插件测试共享的提示词与阈值仓库要点LLM 关键词集(The capital of France is, Paris)、(2 2 , 4)、(The planet closest to the Sun is, Mercury)大小写不敏感子串匹配预算LLM_QUALITY_MAX_NEW_TOKENS 256seed 固定 1。VLM 关键词集提示词Describe this image in detail.关键词元组(dog, puppy, animal, golden, retriever, grass, outdoor, pet)预算同样 256上游用 512这里收窄到 256 以保持与 LLM 单元相同的余量并让 4 个 VLM 单元的 QDC Android 墙钟时间可控seed 固定 1。确定性用例提示词List three primary colours.预算 48 tokens。logits 奇偶校验PARITY_INPUT_IDS是一组斐波那契序列 圆整数的 token id配套parity_softmax、parity_kl_divergence、parity_top1_agreement三个工具函数以及阈值PARITY_TOP1_MIN 0.70、PARITY_KL_MAX 0.35、PARITY_QAIRT_KL_MAX 1e-4。llama_cpp 侧的纵深用例test_llama_cpp.py 在通用行为之外还有一批 CPU 钉死device_map[cpu]的契约 / 回归用例值得逐一关注贪婪确定性每次都在新from_pretrained上下文中生成注释说明原因——llama_cpp 在多次generate()间保留 KV cache同一句柄上的第二次调用会续写上一次的回答。logits parity以主模型的 CPU 运行为参考对gpu/npu/hybrid各跑一次forward_logits要求 top1 一致率 ≥ 0.70、KL 散度 ≤ 0.35从数值层面证明非 CPU 后端没有悄悄跑偏。MTP 多轮spec_typedraft-mtpspec_draft_modelspec_n_max3通过model_name传目录 id 以绕过 model-manager 的 org/repo 校验在 QCS9075M 上跳过draft-mtp 上游尚未支持该平台的 HTP。ChatML 哨兵断言|im_start|system、|im_start|user与结尾的|im_start|assistant。enable_thinking对可思考模型默认值必须自动解析为 True且开/关两种模板必须不同。tools 渲染tools[list[dict]]与toolsjson.dumps(list)渲染结果必须一致工具调用往返后仅凭上一轮tool_calls渲染工具响应的模板Gemma 4 / Mistral / Cohere不得丢失 tool result——这是一条针对工具调用被拍平成 assistant 文本旧 bug 的回归测试。chat_template_content覆盖传入自定义 Jinja 后apply_chat_template输出必须严格等于模板渲染结果。context shiftn_ctx256时让 prompt约 200 tokensmax_new_tokens120超限断言stop_reason ! context_length证明滑动窗口在解码溢出时真的触发了轮转n_keep4锚定首部、其余历史压缩腾位。mtmd 标记每张图片前置一个__media__标记0/1/2 张图片参数化验证纯文本消息无标记且generate()会校验images[...]对应的文件确实存在缺失抛FileNotFoundError。qairt 侧的差异与自洽性test_qairt.py 基本镜像上述行为但有三点刻意差异VLM 多轮需要每轮重传图片QAIRT 每次generate()都重新编码图像因此第二轮必须再次传入images[test_image]而 llama_cpp 第二轮反而要不传图片旧 char-offset 跟踪在带图续写时切坏了图片标记导致mtmd_tokenize失败——见 test_llama_cpp.py 的回归注释。logits 自洽性而非 parityQAIRT 用例对同一device_mapnpu跑两次前向要求 top1 一致率恰好 1.0、KL ≤ 1e-4——比跨后端 parity 更严因为 qairt 没有 CPU 参考运行可对。没有test_chat_template_content_load_override镜像文件末尾注释如实记录了插件不对称——qairt 在传入chat_template_content时会静默保留内建的 ChatML 模板而 llama_cpp 会尊重覆盖该问题被作为待闭合的插件不对称单独跟踪而不是用 xfail 污染套件。云端真机 CIUnit 与 QDC 双工作流tests/README.md 的 CI 节把项目测试拆成两个工作流Unit TestPR 门槛无真机_unit-test.yml转换为仓库根路径.github/workflows/_unit-test.yml经由 pr-check.yml 在每个 PR 上运行覆盖test-go、test-python、test-sdk-ci-m api三组在 linux-arm64 windows-arm64 GitHub 运行器上执行不需要 QDC 硬件。其中test-sdk-ci实际跑的是pytest tests -m api or (llama_cpp and device_cpu)——即本套件的无模型 CPU 子集并通过GENIEX_LOGtrace暴露 SDK 侧 trace 日志帮助定位失败。QDC Test真机矩阵tag 触发_qdc-test.yml.github/workflows/_qdc-test.yml在workflow_dispatch手动触发和v*tag 推送时经 test.yml运行不在 PR 上跑。每个平台一个执行腿leg每条腿跑完整插件集-m llama_cpp or qairtPlatformDeviceFrameworkLinuxQCS9075MBASHWindowsSC8480XPPOWERSHELL每个平台的入口脚本位于 tests/qdc/ 下LinuxQCS9075Mrun_pytest.sh 使用镜像预装的 Python 3.12先pip install pytest / pytest-reportlog / tqdm设置LD_LIBRARY_PATH、GENIEX_PLUGIN_PATH、GENIEX_LIB_PATH、PYTHONPATH将HF_HUB_DOWNLOAD_CONCURRENCY1QDC 共享链路并发拉取会拖垮 fixtures导出GENIEX_DEVICE_TEST1先跑一次import geniex冒烟再以--junitxml--report-log双通道输出执行pytest -m llama_cpp or qairt。WindowsSC8480XPrun_pytest.ps1 面向 PowerShell 5.1 Desktop先用certutil安装 HTP 自签证书否则 Hexagon 后端会在 pre-main 阶段以 0xC0000409 崩溃再从 python.org 引导 ARM64 embed zip并改写._pth使PYTHONPATH生效——embed 版默认隔离模式会忽略环境变量其余环境变量与 Linux 腿对齐。两条腿共用 sdk/benchmark/qdc/_qdc.py 完成 submit / poll / log-collect统一入口是 tests/qdc/run_qdc_pytest.py命令行参数--pkg-dir、--platformlinux / windows / android、--device、--job-timeout默认 10800 秒、--logs-dir依赖qualcomm_device_cloud_sdk与QDC_API_KEY经_qdc.make_client/_qdc.resolve_target/_qdc.submit_and_wait把打包好的 artifact 提交到指定设备结果三级降级策略JUnit XMLdevice-results.xml是干净退出的真源缺失时用--report-log的 NDJSON能扛住 abort重建两者皆无时解析设备 stdout甚至能把Fatal Python error/ggml_abort崩溃行按未完成用例记为 FAIL最终渲染成带模型清单的 Markdown 汇总PASS/FAIL/SKIP 统计 失败详情折叠块写入GITHUB_STEP_SUMMARY退出码 0/1 直接驱动 CI 红绿。AndroidSM8850尚未接入QDC 的 SM8850 镜像既没有预装 Python 也没有 termux需要一套 CLI 驱动的 harness 而非当前基于 pytest 的方式属于本迭代范围之外tests/README.md 的 CI 节明确记录了这一状态。Android 侧的过渡探索可在 tests/qdc/android/ 看到test_run_device.py通过 adb 把 Termux 派生的 Python pkg-geniex tests/ 推到手机再拉回 junit/report-log。真机执行依赖 Qualcomm 设备云QDC提供的远程设备环境。如下图所示设备云控制台会为每台开发板如 QCS9075M 这类 Qualcomm Linux 设备提供可交互的远程终端与会话管理能力这正是 QDC Test 工作流把 pytest 套件投递到真机后运行与取日志的底层环境。与 bindings/python/tests/ 的边界tests/README.md 最后明确划定了职责边界tests/是 SDK 插件覆盖的大本营而 bindings/python/tests/ 只覆盖绑定层自身CLI wrapper、进度回调、model_manager 的 Python 表面、本地拉取路径不启动真实生成。任何涉及设备选择、插件行为或模型输出的推理都属于tests/的范畴。这也是为什么test_api.py可以到处跑、而test_llama_cpp.py/test_qairt.py必须走到真机的原因——前者是绑定契约后者是端到端行为。小结GenieX 的tests/套件展示了契约 行为 真机三层测试的完整形态以models.json为单一事实源的参数化矩阵、以GREEDY_TEMPERATURE哨兵与显式apply_chat_template保证的确定性质量断言、以 marker 注册表 GENIEX_DEVICE_TEST环境变量实现的设备门控以及 Unit / QDC 双工作流把同一套用例从纯 CPU 环境一路带到骁龙真机的自动化闭环。对于想要为该项目贡献测试、新增模型条目或理解其 CI 全貌的开发者而言tests/README.md 连同本篇文章梳理的源码路径就是最直接的入口。【免费下载链接】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),仅供参考