十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

OpenMed 本地模型缓存配额指南:ModelCachePolicy 的确定性 LRU 驱逐与完整性保障

OpenMed 本地模型缓存配额指南:ModelCachePolicy 的确定性 LRU 驱逐与完整性保障 OpenMed 本地模型缓存配额指南ModelCachePolicy 的确定性 LRU 驱逐与完整性保障【免费下载链接】openmedLocal-first healthcare AI: clinical NER HIPAA PII de-identification that runs 100% on-device. 2,200 medical models, 21 languages, Apple MLX Python, no cloud, no patient data leaving your network. Apache-2.0项目地址: https://gitcode.com/GitHub_Trending/ope/openmedopenmed.core.model_cache_policy.ModelCachePolicy是 OpenMed 提供的纯本地模型缓存配额管理组件它为应用显式拥有的模型构件文件或目录建立配额上限以确定性的 LRU最近最少使用策略规划并执行驱逐全程不解析模型名、不导入 Hub 客户端、不发起网络请求。本文以该策略为核心完整讲解构件注册、复用前校验、驱逐计划与执行、manifest 持久化格式并结合仓库源码与单元测试剖析其防越界、防符号链接、防隐私泄露的底层设计帮助你在离线优先、本地优先的模型部署场景中安全地管理磁盘占用。为什么需要本地专属的缓存配额OpenMed 的核心定位是 Local-first模型在设备本地运行患者数据不出网络。随之而来的现实问题是一套医疗 NER / PII 去标识化流水线可能同时持有多个模型构件每个构件体积可达数百 MB 到数 GB磁盘占用需要被约束。常见的扫描整个缓存目录并删除最旧文件方案过于粗暴——它可能误删共享缓存根目录如 Hugging Face 的~/.cache/huggingface中由其他进程拥有的文件。ModelCachePolicy的设计从根本上规避了这个问题。其模块文档model_cache_policy.py明确指出策略刻意采用**允许清单allow-list**而非任意目录扫描与删除调用方先注册自己拥有的文件或目录策略在缓存本地的 manifest 中记录内容摘要、体积、访问次序与 pin 状态复用构件前必须校验记录的内容摘要通过后才更新访问次序驱逐只考虑已注册且未 pinpinned的构件并使用稳定的路径平局裁决规则保证结果可复现模块内没有任何操作导入模型 Hub 客户端或执行网络 I/Omanifest 是纯本地记账报告与异常对外只暴露路径的哈希而非路径本身。单元测试 test_model_cache_policy.py 甚至通过monkeypatch将socket.socket.connect替换为直接抛错以断言整个注册→计划→复用流程绝不碰网络。注册自有构件register_artifact 与 sha256_path策略的入口是ModelCachePolicy构造与register_artifact()。构造参数如下参数类型默认值说明cache_dirstr \| Path必填包含由该策略管理构件的缓存目录构造时经expanduser().resolve()规范化quota_bytesint必填已注册构件的总字节上限必须是非负整数见 _validate_nonnegative_intpinned_artifactsIterable[str \| Path]()相对cache_dir的路径受保护永不被驱逐注册后自动成为 pinmanifest_namestr.openmed-cache-policy.json缓存本地的 JSON 记账文件名必须是单个合法文件名不能是.、..或含路径分隔符构造时的参数校验相当严格quota_bytes传入布尔值或负数会直接抛出ValueErrormanifest_name必须是PurePosixPath(manifest_name).name manifest_name的单文件名。策略内部还以缓存目录绝对路径的哈希作为_policy_hash用于后续校验驱逐计划是否属于本策略实例。官方文档示例cache-quotas.md展示了完整用法from pathlib import Path from openmed.core import ModelCachePolicy, sha256_path cache_dir Path(~/.cache/openmed).expanduser() artifact cache_dir / models / synthetic-model policy ModelCachePolicy( cache_dir, quota_bytes8 * 1024**3, pinned_artifacts[cache_dir / models / baseline-model], ) expected sha256_path(artifact) entry policy.register_artifact( artifact, expected_sha256expected, last_accessed_ns1_000, )这里的两个关键点sha256_path()是纯本地摘要助手sha256_path对普通文件或目录计算 SHA-256绝不解析模型名、不联系远程服务。register_artifact()的expected_sha256是失败即关闭fail-closed校验register_artifact若本地内容与期望摘要不符注册直接抛CacheIntegrityError不会把可疑构件写入 manifest。register_artifact 的参数语义参数默认值说明expected_sha256None可选的期望摘要提供时本地实际摘要必须一致否则抛CacheIntegrityErrorpinnedNone显式指定是否 pinNone时继承该构件此前记录否则新建构件默认为False且构造时传入的pinned_artifacts恒为真last_accessed_nsNone访问时间戳纳秒None时复用历史记录或取当前time.time_ns()。显式传入固定值可以构造可复现的驱逐计划与测试注册成功后返回CacheArtifact摘要对象CacheArtifact其字段全部是PHI-free的entry.to_dict() # {path_hash: sha256:..., size_bytes: ..., sha256: sha256:..., # last_accessed_ns: 1000, pinned: False, exists: True, verified: True}原始路径不属于这个公开对象因此entry.to_dict()可以安全地序列化进运维报告。这与对外只暴露路径哈希、内容校验和、字节总数或计数的设计一致——即便报告泄露攻击者也无法从中恢复磁盘路径。目录构件递归测量禁止符号链接注册目录构件时策略会递归测量其全部内容_measure_path对每个子文件按相对路径的 POSIX 形式排序累计字节数目录摘要 bopenmed-model-cache-artifact-v1\0前缀 每个文件的相对路径、大小、分块内容依次混入 SHA-256构件本身或其任何子成员是符号链接直接抛ModelCachePolicyError防止驱逐顺着链接追到共享或无关位置。复用前必须校验reuse_artifact 与 CacheIntegrityError缓存的构件随时可能被意外篡改或删除。因此策略规定任何复用都必须先校验记录在案的摘要校验失败即抛CacheIntegrityError且不会更新该构件的 LRU 访问标记——避免越是用坏文件越不容易被驱逐的反直觉行为。local_path policy.reuse_artifact(artifact, expected_sha256expected)reuse_artifact()reuse_artifact的完整语义构件必须已由本策略注册否则抛CacheOwnershipError校验本地字节摘要与记录摘要若提供了expected_sha256还必须同时匹配期望值校验通过后把last_accessed_ns更新为当前time.time_ns()并写回 manifest返回解析后的本地路径供调用方立即用于模型加载全程无远程查找。校验失败的异常CacheIntegrityError只携带path_hash、期望/实际摘要错误消息中不含原始路径CacheIntegrityError。测试 test_reuse_verifies_checksum_and_exposes_only_hashes 篡改文件后断言异常确实抛出、path_hash正确、且model-a.bin与临时目录路径都不会出现在异常文本中。注册前的独立校验verify_artifact_checksum如果不希望先注册就校验某个本地文件或目录可以使用独立的verify_artifact_checksum()verify_artifact_checksumactual verify_artifact_checksum(artifact, expected_sha256expected)它不需要所有权关系校验失败同样抛CacheIntegrityError构件缺失时actual_sha256记为sha256:missing成功则返回实际摘要。这在下载模型后、注册进策略前的落盘检查场景非常实用。规划与执行确定性驱逐plan_eviction / apply_eviction配额管理的核心是驱逐。策略采用LRU最近最少使用策略驱逐顺序按last_accessed_ns从小到大排序即最久未使用的最先被选平局裁决访问时间相同时按规范的 cache-relative 路径排序源码中keylambda item: (item[0], item[1])plan_eviction。因此同一份 manifest 必然产出同一份计划驱逐完全确定、可复现pin 永不候选构造时的pinned_artifacts与注册时pinnedTrue的构件都被排除在候选之外additional_bytes预留空间用于给一个尚未注册的、即将写入的构件腾位子。plan policy.plan_eviction(additional_bytes512 * 1024**2) result policy.apply_eviction(plan) if not result.quota_satisfied: # Pinned, missing, changed, or otherwise unsafe entries can block cleanup. print(result.to_dict()[remaining_bytes])plan_eviction()还会计算bytes_to_free max(current_bytes additional_bytes - quota_bytes, 0)并跳过无法安全测量的条目打印path_hash级别的 warning 后继续因此绝不让坏条目阻塞整个计划。它还接受required_bytes作为描述性别名但若与additional_bytes同时给出且数值不一致会抛ValueError。EvictionPlan 的字段与属性名称类型含义policy_hashstr缓存目录路径的哈希标识计划归属quota_bytes/current_bytes/additional_bytesint配额、当前已注册总量、为未注册构件预留的量bytes_to_freeint需要释放的字节数pinned_bytesint被 pin 保护的字节总量owned_artifact_countint已注册构件数量candidates别名evictionstuple[EvictionCandidate, ...]选中的驱逐候选每个候选只含path_hash/size_bytes/last_accessed_nsbytes_planned/bytes_freedint计划移除 / 预计释放的字节数remaining_bytesint计划驱逐后的预计存量max(current additional - planned, 0)quota_satisfiedbool计划能否满足配额blocked_bytesint被 pin/不安全条目阻塞时仍超出的字节数EvictionPlan.to_dict()与EvictionResult.to_dict()同样只包含哈希、计数与字节总数EvictionPlan、EvictionResult可直接落入审计报告。apply_eviction 的四重保险执行驱逐时apply_eviction策略对每个候选逐项复核只删除同时满足以下条件的构件仍注册在案manifest 中能找到对应的path_hash内容未变更当前摘要 记录摘要防计划生成后文件被换的 TOCTOU 竞态位于配置的缓存目录内未 pin。任何一条不满足该候选进入skipped_path_hashes并保持文件不动从未注册过的文件则完全不受影响。此外apply_eviction会先校验plan.policy_hash与当前策略一致防止误用其他策略实例生成的计划model_cache_policy.pydry_runTrue时不删除任何文件仅把候选全部计入skipped_path_hashes并返回当前存量方便你先检查候选数量删除目录用shutil.rmtree、删除文件用unlink删除过程中出现OSError一律跳过并记 warningmanifest 只在确实发生变更changedTrue时才写回磁盘。日常使用还可以直接调用enforce_quota(additional_bytes...)enforce_quota一步完成规划 执行。源码级原理manifest、路径安全与原子写入manifest 格式与原子更新manifest 默认文件名.openmed-cache-policy.json格式版本为openmed.model_cache_policy.v1常量见 model_cache_policy.py。结构为{ schema_version: openmed.model_cache_policy.v1, artifacts: [ { relative_path: models/synthetic-model, size_bytes: 0, sha256: sha256:64 hex, last_accessed_ns: 1000, pinned: false } ] }存的是 cache-relative 路径保证可移植换机器、换挂载点都有效写入采用临时文件 os.replace的原子替换_write_records即使中途崩溃也不会留下半个 manifest读取时逐字段强校验schema 版本、路径合法性、非负整数、SHA-256 格式、pin 布尔、重复条目任何一项不合法都会抛ModelCachePolicyError(cache policy manifest is invalid)拒绝静默容错。路径防逃逸的三道防线从源码看策略对路径安全做了层层设防_relative_path_for_input、_validate_relative_path相对化相对路径自动拼接在cache_dir下绝对路径必须resolve()后仍落在cache_dir内部否则抛CacheOwnershipError拒绝危险形式空路径、绝对路径、含反斜杠、含./..段、非规范 POSIX 形式的路径全部拒绝保留对象保护缓存目录本身、manifest 文件本身、任何符号链接都不允许作为构件注册——这保证了驱逐永远不会删掉账本或顺着链接删到别的目录。测试 test_artifact_outside_cache_cannot_be_registered 直接验证了缓存目录之外的构件无法注册这一约束。摘要规范化与分块哈希所有 SHA-256 统一规范化为sha256:64位小写hex前缀形式_normalise_sha256裸 64 位 hex 会被自动补前缀格式不合法直接抛ValueError文件哈希按1 MiB 分块流式读取_HASH_CHUNK_SIZE内存占用恒定适合数 GB 的模型权重文件。线程安全与配套 API策略内部持有threading.RLockregister/reuse/plan/apply等写路径均在锁内执行可安全用于多线程加载流水线。此外还提供了一批配套方法register(path, **kwargs)register_artifact的别名unregister_artifact(path)忘记所有权但不删除文件返回是否真的存在该记录verify_artifact(path)仅校验不更新 LRU 标记mark_accessed(path)校验并标记最近使用返回摘要is_valid(path)布尔化的完整性检查吞掉所有权/完整性/校验异常list_artifacts(verifyFalse)与别名inventory()按相对路径哈希排序输出全部摘要verifyTrue时逐个强校验缺失即抛CacheIntegrityErrorpin_artifact(path)/unpin_artifact(path)运行时动态调整 pin 状态需要构件已注册。测试验证与行为保障仓库为策略提供了 8 组聚焦单元测试test_model_cache_policy.py覆盖了上述全部关键承诺测试验证点test_policy_is_publicly_exportedopenmed.ModelCachePolicy与openmed.core导出一致可直接顶层导入test_reuse_verifies_checksum_and_exposes_only_hashes篡改后复用抛CacheIntegrityError异常不含任何磁盘路径is_valid返回Falsetest_policy_is_local_onlymonkeypatch 网络 socket整个流程零网络调用test_lru_plan_is_deterministic_and_respects_pinsLRU 顺序正确pin 构件不在候选同一 manifest 两次规划to_dict()完全相等test_apply_eviction_removes_only_owned_unchanged_artifacts只删已注册未 pin 构件未注册文件原样保留test_directory_artifact_and_manifest_are_safe_for_reports目录构件可驱逐计划报告与 manifest 均不含缓存目录路径test_artifact_outside_cache_cannot_be_registered越界注册抛CacheOwnershipErrortest_pinned_bytes_can_block_quota_without_deleting_the_pinpin 字节可阻塞配额满足quota_satisfiedFalse但绝不删除 pin 本身最后一条尤其值得注意当所有可驱逐条目清空后仍超配额例如只剩被 pin 的大模型plan.quota_satisfied与result.quota_satisfied均为False此时应通过to_dict()[blocked_bytes]感知缺口而不是盲目扩容删除——这是把配额从硬性破坏性操作升级为可观测治理信号的关键。典型落地场景与使用建议模型缓存上限治理把~/.cache/openmed/models下应用自有的每个模型目录注册进策略配额设为磁盘预算如8 * 1024**3在每次启动加载模型前调用reuse_artifact顺带完成完整性校验再视plan_eviction()结果决定是否回收空间基线模型常驻将必须保留的模型如baseline-model通过pinned_artifacts构造参数或pin_artifact()保护起来其余可替换模型按 LRU 自然淘汰离线/无网环境策略天然不依赖网络配合 bundled-offline.md 的离线模型打包思路可在完全断网的内网或边缘设备上运行多进程共存每个进程只注册自己拥有的构件绝不注册共享的 Hugging Face 缓存根目录——文档 cache-quotas.md 对此有明确警告审计可序列化entry.to_dict()、plan.to_dict()、result.to_dict()均不含原始路径可直接写入运维日志与合规报告与项目整体的 no-PHI 遥测与审计理念一致。延伸阅读关联文档docs/models/cache-quotas.md核心实现openmed/core/model_cache_policy.pyModelCachePolicy定义于 L373单元测试tests/unit/core/test_model_cache_policy.py公开导出openmed/core/init.py 与顶层 openmed/init.py相关模型管理主题docs/models/bundled-offline.md、docs/models/model-manifest.md、docs/models/cache-quotas.md、docs/models/rollback-compatibility.md【免费下载链接】openmedLocal-first healthcare AI: clinical NER HIPAA PII de-identification that runs 100% on-device. 2,200 medical models, 21 languages, Apple MLX Python, no cloud, no patient data leaving your network. Apache-2.0项目地址: https://gitcode.com/GitHub_Trending/ope/openmed创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表