Files
msime-windows/engine/ngram
fanlusky e365a8d1b5 perf(engine): 大模型改为只读映射并按需载入,缩短每键查询路径
- sc.lm 改用 kenlm 的 LAZY 载入:Windows 上默认的 POPULATE_OR_READ 会退成 READ,
  把 33 MB 整份拷进私有堆且永不释放;现在是 MapViewOfFile 只读映射,可被系统回收
- dict_japanese.dat 在 Windows 上同样改为只读映射,与 POSIX 分支一致
- 神经整句模型改为开关首次生效时才载入(LazySentenceModel),默认关闭的桌面模型
  不再占用约 24 MB
- 新增 reset_sentence_cache():重排结果与上文只影响词格整句,只清 series 一层,
  逐前缀查库与切分缓存保持热;set_rescoring_context 也改用它
- CircularBuffer 新增 find():命中时返回指针而不复制整份候选列表,并改为真正的 LRU;
  全拼、双拼每键的命中路径改用它
- Google 解码器去掉每次调用前的 im_reset_search,沿用上游的公共前缀增量搜索
- 测试:出货 sc.lm 能映射载入并正常打分;增量搜索与从头搜结果逐步一致
2026-09-30 19:58:18 +08:00
..

ngram

整句候选的打分模型。kenlm/ 是 kpu/kenlm 查询子集的上游副本(提交号见 ../UPSTREAM.md),language_model.h 是引擎唯一看得到的接口。

引擎其余部分不 include 任何 kenlm 头文件:language_model.cpp 是唯一的那个翻译单元,kenlm 的 include 目录和它需要的 _HAS_AUTO_PTR_ETC 因此都能保持 PRIVATE。

用法

#include "engine/ngram/language_model.h"

const auto &model = ngram::shared_language_model(paths.resource(assets::language_model));
if (!model.valid())
{
    // 模型缺失不是错误,调用方按没有模型处理(词格会退回按词频的启发式打分)。
}
ngram::State state = model.null_state(), next;
const float score = model.score(state, "你好", next); // log10 概率

几个容易踩的点:

  • 从 null_state() 起算,不要用 begin_state()。 出货的 sc.lm 是从一份不带 <s> / </s> 的 ARPA 建的,句首上下文只会带进一个模型没有任何 n-gram 的词。
  • 分数是 log10,和词格原先那套自然对数的启发式不能混着加。
  • 未登录词按 unknown_penalty() 罚分,默认 log10(1/6e7) ≈ -7.78,与 libime 的 DEFAULT_LANGUAGE_MODEL_UNKNOWN_PROBABILITY_PENALTY 取同一个值。换这个数等于整体重调未登录词相对已登录词的代价。
  • shared_language_model() 按路径共享实例。sc.lm 有 34 MB,全拼和双拼两本词典各载一份是白白多映射一遍;kenlm 的查询全是只读的,共享实例可以被多个线程同时用。

重新生成语言模型

出货的 sc.lm 由 libime 的 lm_sc.arpa 转换而来,转换工具就是 kenlm 自带的 build_binary,默认不构建。平时不必手工做这件事:scripts/build-language-model.ps1 会按 language-model/lock.json 下载语料、转换、两头校验摘要,打完整包时自动被调用(见 ../../language-model/README.md)。

要手工跑一遍,或者调转换参数时:

# 本目录可以单独 configure,不必为 engine 依赖的 SQLite 拉起 vcpkg
cmake -S engine/ngram -B build-tool -DMETASEQUOIA_IME_BUILD_NGRAM_TOOLS=ON
cmake --build build-tool --config Release --target metasequoia_ime_build_binary
./build-tool/Release/metasequoia_ime_build_binary -s -a 22 -q 4 trie lm_sc.arpa sc.lm

改了参数就要同步改 language-model/lock.json 里的 build.flags 和 output.sha256,否则构建脚本会拿新产物去对旧摘要而报错。

-q 4 -a 22 与 lm::ngram::QuantArrayTrieModel 是一一对应的:改了参数就要改 language_model.cpp 里的 using Model,否则载入时抛 FormatLoadException。-s 对应 config.sentence_marker_missing = lm::SILENT,因为这份 ARPA 没有句首句尾标记。

KENLM_MAX_ORDER=3 必须与建模时的阶数一致,改它等于让运行时读不懂已经发布的模型文件。