<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/"><channel><title>Isma 是语</title><link>https://ismantic.github.io/</link><description>Recent content on Isma 是语</description><generator>Hugo -- 0.157.0</generator><language>en-us</language><lastBuildDate>Fri, 31 Jul 2026 09:00:00 +0800</lastBuildDate><atom:link href="https://ismantic.github.io/index.xml" rel="self" type="application/rss+xml"/><item><title>Tokenizer的三个注意点</title><link>https://ismantic.github.io/posts/tokenizer/</link><pubDate>Fri, 31 Jul 2026 09:00:00 +0800</pubDate><guid>https://ismantic.github.io/posts/tokenizer/</guid><description>&lt;p&gt;当前语言模型通常先用 Tokenizer 将文本转换成 Token ID，其中 BPE 是最常见的子词算法。BPE 的原理并不复杂：从字符或字节开始，反复统计相邻符号对，将最高频的一对合成新 Token，直到词表达到指定大小。推理时再按训练得到的顺序重放这些合并。
不过当真正实现一个 Tokenizer 时，会发现 BPE 只解决了中间的一部分问题。文本怎样进入 BPE，中文要不要预分词，训练出的规则又怎样快速应用，都会改变最后的词表和 Token 序列。衡量 Tokenizer 时经常关注压缩率，但有些明显问题不会直接反映在压缩率上，尤其涉及不同语言特性时。这里结合 PieceTokenizer 的实现，谈三个容易忽略的地方。&lt;/p&gt;
&lt;h2 id="pretokenizer"&gt;PreTokenizer&lt;/h2&gt;
&lt;p&gt;Tokenizer 通常不会把整段文本直接交给 BPE，而是先经过 PreTokenizer。它将文本拆成若干片段，BPE 只能在片段内部合并，不能跨越片段边界。&lt;/p&gt;
&lt;p&gt;面向英文时，一个常见做法是把空格归到后面的单词：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;Hello world
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;→ Hello | ▁world
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这样既能保留空格，也能用一个 Token 表示“空格+单词”。SentencePiece 会先把空格替换成 &lt;code&gt;▁&lt;/code&gt;，TikToken 的正则也允许一个非字母字符成为后面单词的前缀。对英文来说，这种设计很自然。&lt;/p&gt;
&lt;p&gt;中文却没有依靠空格分词。中文语料里的空格可能来自中英文混排、网页排版、OCR 或数据清洗，并不是一个中文词稳定的词法属性。如果继续把空格粘到后面的汉字段，BPE 就可能同时学习：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;世界
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;▁世界
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;二者表达的中文内容相同，却占用两个 Token ID，也对应两套 Embedding 和 LM Head 参数。&lt;/p&gt;
&lt;p&gt;我下载了一些公开模型的 Tokenizer，只分析词表，没有下载模型权重。Gemma 2 中有 2504 个“前导空格+纯中文”词条，其中 2441 个还能找到完全相同的无空格版本，比例为 97.5%；Gemma 3 的对应数字是 1161、1140 和 98.2%。例如输入 &lt;code&gt;你好 世界&lt;/code&gt;，Gemma 会得到：&lt;/p&gt;</description></item><item><title>谈谈过去，想想以后</title><link>https://ismantic.github.io/posts/now/</link><pubDate>Wed, 29 Jul 2026 10:00:00 +0800</pubDate><guid>https://ismantic.github.io/posts/now/</guid><description>&lt;p style="text-align: right; font-style: italic; color: var(--secondary); margin: 1.5rem 0 2.5rem;"&gt;
安能摧眉折腰事权贵，使我不得开心颜
&lt;/p&gt;
&lt;h2 id="过去"&gt;过去&lt;/h2&gt;
&lt;p&gt;两年八个月前，离职了工作十年多的公司，告别了自己一手创建的团队，以及众多从零到一到服务上亿用户的项目。心中没有遗憾是不可能的，但确实难以坚持下去了，当时肺炎咳了几个月，新项目又做的心力憔悴，不堪重负，就坚持离开了。&lt;/p&gt;
&lt;p&gt;现在回过头来看，正赶上大语言模型技术突飞猛进的三年，出来了，被竞业两年，让自己错过了参与其中的机会。不过万事有好有坏，闲暇下来，就有时间把之前做过或者想做而没做的很多事，又一一做起来了。有时也会因为出去旅行搁置很长时间，有时也会连续多周沉浸其中。三个大项目Text Matx Zero 基本上在去年底前完成了，那时候还没有Code的大爆发，不然的话很有可能就做不出来了。&lt;/p&gt;
&lt;h2 id="现在"&gt;现在&lt;/h2&gt;
&lt;p&gt;这个月，主要是整理，把做过的项目结构重新编排，把写的文档重新编辑，都置放在“是语”下面，开源出来，希望能持久存活下去。有了大模型，做这些繁琐的整理的事情真的方便了太多。&lt;/p&gt;
&lt;p&gt;以下是这两年多来投入的项目总结：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;三本书以及配套项目&lt;/strong&gt;，围绕语言模型的底层实现，Text，Zero，Matx。严格来说Matx是半成品，具体没完成的原因文档里也解释了，就不多说了。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;两套模型&lt;/strong&gt;，BERT 和 GPT ，BERT 专注字分类任务，包括CWS/POS/NER 以及 Correction，以及CRF 工具 Wapic ；GPT 专注机器翻译，尝试了ReTok SFT CPO GRPO。都是低成本，以一张RTX 4090卡做到SOTA。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;两个产品&lt;/strong&gt;，一个是输入法，自认为开源出来的里面做的还是可以的，不过我也承认跟商业输入法比效果还不行，之后努力。再一个是人物志，这个更多是弥补当年的夙愿，不多提了。&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="以后"&gt;以后&lt;/h2&gt;
&lt;p&gt;过了这个月，可以说当年离职的遗憾都烟消云散了，把我自己想做的事情都做的差不多了。接下来，我想做些有社会价值，能得到反馈的事情。放出来的这些项目里，输入法是值得继续投入的，也是能触达到更多终端用户的，就像Musk把Twitter都开源了一样，我相信一个高度开源的输入法，且能把效果做到很好，是有很大的社会价值的。而且做这个不需要太大成本。&lt;/p&gt;
&lt;p&gt;再就是写一些技术文章，比如谈谈Tokenizer的细节，为什么认为TikToken以及SentencePiece其实还不够好。再比如谈谈输入法Decode的细节，该怎么做输入补全。而做这些都是希望能触达到更多用户，这样才有反馈，才能监督自己是真的在做正确的事以及是否做的正确。&lt;/p&gt;
&lt;p&gt;还有就是，我有六张显卡，虽然不能万卡集群去做大模型，但还是能力所能及的做些小模型，相比开放权重，我这边就把全部细节都开源了吧，多做些低成本的实践，一起走在人工智能的道路上。&lt;/p&gt;</description></item><item><title>低成本实践：训练GPT</title><link>https://ismantic.github.io/posts/low-cost-gpt/</link><pubDate>Thu, 16 Jul 2026 09:00:00 +0800</pubDate><guid>https://ismantic.github.io/posts/low-cost-gpt/</guid><description>&lt;p style="text-align: right; font-style: italic; color: var(--secondary); margin: 1.5rem 0 2.5rem;"&gt;
纸上得来终觉浅，须知此事要躬行
&lt;/p&gt;
&lt;p&gt;“低成本实践”这个名字有两个来由。一是前阵子和孩子看了一部电视剧，叫《低智商犯罪》，觉得这个名字挺有意思；二是这些实验确实是在有限资源下完成的。这里的低成本，不只是少用几张 GPU，也包括尽量利用开源模型、开源语料和公开论文，把一个原本只有大公司才能做的项目，缩小到个人也能跑起来的程度。&lt;/p&gt;
&lt;p&gt;训练 GPT 选择的任务是中英机器翻译。&lt;/p&gt;
&lt;p&gt;机器翻译大概经历过三个时期。第一代是统计机器翻译，核心可以写成 &lt;code&gt;P(T|S) ∝ P(S|T) × P(T)&lt;/code&gt;：一边是翻译模型，一边是目标语言模型。第二代是以 RNN、Seq2Seq 和 Transformer 为代表的神经机器翻译，模型能力提高了，但需要海量双语平行语料。第三代则是大语言模型：先通过 Next Token Prediction 学习语言，再通过 Prompt、SFT 和偏好训练，让一个通用模型完成翻译。&lt;/p&gt;
&lt;p&gt;前两代机器翻译流行的时候，也都没有真正做过。统计时代在训练 LDA，神经机器翻译时代主要做了 Word2Vec，后来又做了一些 BERT。机器翻译一直更偏 Research，既没有机会做，也确实不会做。到了大语言模型时代，开源模型、训练框架和数据都已经比较成熟，反而给了个人一次补课的机会。&lt;/p&gt;
&lt;h2 id="把评估搞明白"&gt;把评估搞明白&lt;/h2&gt;
&lt;p&gt;机器翻译过去最常用的指标是 BLEU。它大致计算预测译文与参考译文之间的 N-gram 重合度。这个指标简单、稳定，也用了很多年，但它更关心字面是否相似。一个意思正确、甚至表达更自然的译文，只要和参考答案写得不一样，也可能得到较低的分数。&lt;/p&gt;
&lt;p&gt;COMET 则是一个学习出来的翻译评估模型。它同时读取源句、参考译文和模型译文，再给出质量分数。到了大语言模型时代，翻译结果越来越多样，仅靠字面重合已经不够，COMET 这类指标的重要性也越来越高。&lt;/p&gt;
&lt;p&gt;评估方式并不是实验结束后才补上的报表，它会反过来决定训练方向。CPO 需要从多个候选中选择更好的译文，GRPO 也需要奖励信号。如果没有一个相对可信的自动指标，偏好训练和强化学习就无从谈起。&lt;/p&gt;
&lt;h2 id="interpreter由-qwen3-开始"&gt;Interpreter：由 Qwen3 开始&lt;/h2&gt;
&lt;p&gt;第一条路线放在 &lt;a href="https://github.com/Ismantic/Interpreter"&gt;Interpreter&lt;/a&gt; 仓库里。我参考了 ALMA 的做法，但把任务收缩到中英互译，底座也换成了 Qwen3-1.7B-Base。Qwen3 本身已经见过大量中英文语料，1.7B 的规模又能放进消费级显卡，适合作为低成本实验的起点。&lt;/p&gt;
&lt;p&gt;训练分为三个阶段：&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;SFT&lt;/strong&gt;：使用约 3.68 万条清洗后的 ALMA/X-ALMA 平行数据，让模型稳定理解翻译指令。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;CPO&lt;/strong&gt;：让 SFT 模型为同一源句生成多个候选，再用 COMET 选出较好和较差的译文，构造约 4.4 万条偏好数据。&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GRPO&lt;/strong&gt;：以 COMET 作为主要奖励，对模型继续做全参数强化学习。&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;最终在 WMT23 上得到下面的结果：&lt;/p&gt;</description></item><item><title>低成本实践：训练BERT</title><link>https://ismantic.github.io/posts/low-cost-bert/</link><pubDate>Mon, 13 Jul 2026 10:00:00 +0800</pubDate><guid>https://ismantic.github.io/posts/low-cost-bert/</guid><description>&lt;p style="text-align: right; font-style: italic; color: var(--secondary); margin: 1.5rem 0 2.5rem;"&gt;
山穷水尽疑无路，柳暗花明又一村
&lt;/p&gt;
&lt;p&gt;预训练刚兴起的时候，有三个代表性的工作：ELMo、GPT 和 BERT。它们最初想解决的，其实都是 Context-Aware 的词表示问题。ELMo 使用 LSTM，GPT 和 BERT 则转向 Transformer。那时 GPT 在很多下游任务上还不如 BERT，BERT 之后又出现了 RoBERTa、ELECTRA、MacBERT 等一系列改进，当年也都跟进过。&lt;/p&gt;
&lt;p&gt;之后大语言模型的发展远远超出了当时的想象。Decoder-Only 模型不仅可以生成，还能通过少量样本、指令训练和强化学习去完成各种任务。相比之下，BERT 只能编码，能做的事情少得多，模型继续变大以后，很多传统任务也早已接近天花板。&lt;/p&gt;
&lt;p&gt;但“能做的事情少”不等于没有价值。在一些目标明确、调用频繁、对延迟敏感的任务上，BERT 仍然合适。中文错别字纠正是字符级预测，CWS、POS、NER 也是典型的字序列标注；如果每次请求都交给生成式大模型，成本和延迟都太高。于是想重新训练一次 BERT，看看把近几年大模型积累的架构和训练经验放回 Encoder-Only 模型，还能做到什么程度。&lt;/p&gt;
&lt;h2 id="bertc重新训练一个中文-bert"&gt;BERTc：重新训练一个中文 BERT&lt;/h2&gt;
&lt;p&gt;实验代码放在 &lt;a href="https://github.com/Ismantic/BERTc"&gt;BERTc&lt;/a&gt;。它不是在现有中文 BERT 上继续微调，而是从头训练的字符级中文模型。&lt;/p&gt;
&lt;p&gt;Tokenizer 对中文采用“一字一个 Piece”，英文则使用 BPE，整个词表只有 12536。预训练任务仍然是 15% Masked Language Modeling，但 Whole Word Masking 所需的中文词边界由 Wapic 分词提供。也就是说，模型输入保持字符级，Mask 时却能够按词处理。&lt;/p&gt;
&lt;p&gt;模型结构也没有照搬 2018 年的 BERT，而是吸收了一些后来被证明有效的做法：Pre-Norm、GeGLU、无 Bias LayerNorm、简化 MLM Head、Megatron 初始化、StableAdamW 和 Damped Cosine 学习率。训练数据约 17.65B Token，165M 和 316M 两个版本都可以在单张 RTX 4090 上训练。&lt;/p&gt;</description></item><item><title>产品：是语人物志</title><link>https://ismantic.github.io/3/</link><pubDate>Fri, 20 Mar 2026 09:00:00 +0800</pubDate><guid>https://ismantic.github.io/3/</guid><description>&lt;h2 id="念念不忘"&gt;念念不忘&lt;/h2&gt;
&lt;p&gt;回看 2013 年自己发的一些微博，当年的很多看法，现在看来都是错误的。十年后的 2023 年，百科项目又成为我不堪重负、最终离职的直接原因。照理说，这件事应该就此放下了，可我还是念念不忘。&lt;/p&gt;
&lt;p&gt;所以后来又做了是语人物志。某种意义上，它只是一个纪念：我想用一个尽可能小的集合，再把结构化实体库做一遍。&lt;/p&gt;
&lt;p&gt;这里的“小”，不是说只做几个人、几条数据，而是把边界收紧。以前做百科，总想把世间万物都装进去；人物、地点、作品、组织、物种、天体，似乎只要能够定义，就应该成为一种实体。可一旦真正开始建设，就会发现定义一个类型很容易，持续获得可靠的数据却很难。类型越多，数据源越杂，最后越难说清楚库里究竟有什么、为什么收录、又凭什么相信。&lt;/p&gt;
&lt;h2 id="推倒重来"&gt;推倒重来&lt;/h2&gt;
&lt;p&gt;IsEntity 前后已经做了四个版本。&lt;/p&gt;
&lt;p&gt;v0 从 Wikidata 出发，设计了 72 类实体，最后导入了几十万条数据。它看起来很大，但问题也很明显：广而浅，很多实体只有名字和少量属性；数据虽然来自 Wikidata，却缺少我真正想要的结构、出处和约束。&lt;/p&gt;
&lt;p&gt;v1 换了一条完全不同的路线，直接从《史记》原文中抽取人物、关系和语句。这个版本做完了《史记》全 130 篇，证明从史书原文建立人物知识库是可行的。但原文抽取很难自然扩展成一套足够宽的百科，做一本史书可以，继续做完二十四史，需要的是另一种组织方式。&lt;/p&gt;
&lt;p&gt;v2 又把范围扩展到 60 个类型，希望通过 Schema、名单和多个权威库，建立一个更完整的历史实体库。结果是，大多数类型都没有足够可靠的权威数据源，只能不断从 Wikidata 捞候选，再靠规则和人工清理。库越来越大，质量问题却怎么也擦不完。&lt;/p&gt;
&lt;p&gt;于是到了 v3，我又把它缩了回来。现在的核心只围绕人物、地区、国号和年号展开。人物以 CBDB 和二十四史传主为主要收录依据，地区和国号是人物活动的舞台；所有实体都能够对应到 Wikidata，每条结构化语句也必须能够映射到 Wikidata 属性。不是因为 Wikidata 没有问题，而是必须给数据划出一条明确、可检查、可以重建的边界。&lt;/p&gt;
&lt;p&gt;经过几次推倒重来，我越来越觉得，做知识库最困难的并不是抓到多少数据，而是决定什么不做。&lt;/p&gt;
&lt;h2 id="现在的人物志"&gt;现在的“人物志”&lt;/h2&gt;
&lt;p&gt;现在的是语人物志有约 12 万个实体，其中人物接近 11.6 万。它不再试图成为包罗万象的百科，而是围绕二十四史组织内容：从史书目录进入本纪、世家和列传，再从传主进入人物卡片。人物的姓名、字号、生卒、籍贯、亲属、功名、职业和所属朝代，被整理成结构化信息；需要继续阅读时，还可以进入对应的百科正文。&lt;/p&gt;
&lt;p&gt;Android 版本把整个库放进了安装包，不需要网络，也不申请额外权限。它主要有三种使用方式：上下滑动认识不同人物，左右翻阅人物正文，或者直接搜索全库。卡片里出现的人名、地区、国号和职业，也可以继续在库内跳转。&lt;/p&gt;
&lt;p&gt;我想做的不是一个缩小版的网页百科，而是一种适合随手翻阅的人物志。它不要求先知道自己要找谁，也不只是给出一个搜索框。打开它，可以随机遇到一个人，读完一页，再沿着时代、地域和人物关系继续走下去。&lt;/p&gt;
&lt;h2 id="schema"&gt;Schema&lt;/h2&gt;
&lt;p&gt;这个项目一直坚持 Schema：先定义实体是什么、允许有哪些属性、每个属性的值应该是什么类型，再开始填数据。现在的建库流程可以概括为四步：&lt;/p&gt;
&lt;p&gt;&lt;code&gt;Schema → List → Map → EntityDB&lt;/code&gt;&lt;/p&gt;
&lt;p&gt;Schema 规定结构；List 决定谁可以进入；Map 负责把权威库中的对象对齐到 Wikidata；最后再把通过约束的数据物化成 EntityDB。CBDB、CHGIS、二十四史目录等资料主要用来决定“收谁”和“属于什么类型”，结构化属性则来自 Wikidata。这样做不一定能得到最多的数据，但每一步都可以解释，也可以从头重放。&lt;/p&gt;
&lt;p&gt;大语言模型出现以后，做结构化知识库似乎更显得不合时宜。很多问题直接问模型就能得到答案，未必还需要先设计 Schema，再做名单、映射和数据校验。但模型给出的回答和一座边界明确的知识库，终究不是一回事。至少在这里，我知道每个人为什么被收录，每条属性从哪里来，哪些东西尚未解决。&lt;/p&gt;
&lt;p&gt;这个项目可能还是会继续推倒重来。它也未必能弥补 2023 年留下的什么，只是有些事情，做过一次没有放下，就还是想再做一次。&lt;/p&gt;</description></item><item><title>产品：是语输入法</title><link>https://ismantic.github.io/2/</link><pubDate>Thu, 19 Mar 2026 09:00:00 +0800</pubDate><guid>https://ismantic.github.io/2/</guid><description>&lt;h2 id="为什么做输入法"&gt;为什么做输入法&lt;/h2&gt;
&lt;p&gt;严格来说是四年前了，某出版社和公司合作策划一系列技术书籍，我摊到了 NLP 部分。当时我有点不合时宜地写了一个包含 Trie、Regex、CRF 等底层原理的提纲。评审是一些高校 NLP 方向的教授，他们认为这个提纲不够新，对潮流技术涵盖得也不够。现在看，人家批评得太对了，当时直接写 GPT 就好了。&lt;/p&gt;
&lt;p&gt;不过我当时也反驳说，这些底层技术还是很重要。比如输入法，它不一定需要神经网络，统计语言模型就可以做得很好。现在回头做 Sime，多少也是为了完成当年那场没有结果的辩论。当然，我并不是想证明谁对谁错，只是想看看：在大语言模型已经无处不在的今天，这些“老技术”到底还能做到什么程度。&lt;/p&gt;
&lt;p&gt;第二个原因更实际。我一直是 Arch Linux 用户，但始终没有找到一个完全合自己心意的拼音输入法。既然每天都要用，那就干脆自己做一个。现在我也确实用上了自己的输入法，而且是每天都在用。&lt;/p&gt;
&lt;p&gt;第三个原因，是作为一个独立算法工程师，真正能做的产品项目实在太少了。大模型需要的算力太多，个人很难负担；纯模型架构的项目，即使做出来了，自己也没有什么竞争力。输入法却是一个不错的切入点：它需要算法，也需要工程，最后还能变成一个真正可以使用的产品。&lt;/p&gt;
&lt;p&gt;Sime 前后参考了 SunPinyin 和 Fcitx5 的不少设计，也加入了自己对词典分词、语言模型和动态解码的理解。最初只是 Linux 下的一个想法，后来又做了 Android，现在也有了 macOS 版本。&lt;/p&gt;
&lt;h2 id="它现在能做什么"&gt;它现在能做什么&lt;/h2&gt;
&lt;p&gt;Sime 是一个开源、离线运行的中文输入法。目前支持 Linux、Android 和 macOS，核心能力包括：&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;全键盘与九宫格输入；&lt;/li&gt;
&lt;li&gt;全拼、简拼以及全拼和简拼混输；&lt;/li&gt;
&lt;li&gt;中文、英文和中英混输；&lt;/li&gt;
&lt;li&gt;整句解码、上下文联想和英文前缀补全；&lt;/li&gt;
&lt;li&gt;用户句子学习、繁简切换、表情与符号输入。&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;这些功能都在本地完成。输入内容不会上传，程序也不依赖网络服务。词典、语言模型和用户学习数据都保存在设备上。对输入法来说，这并不只是一个隐私承诺，也是一条很明确的技术边界：断网时仍然可用，按键之后必须立刻响应，模型不能无限膨胀，更不能把每次输入都交给云端处理。&lt;/p&gt;
&lt;h2 id="语料到模型"&gt;语料到模型&lt;/h2&gt;
&lt;p&gt;Sime 比较特别的一点，是训练和运行流程基本都在同一个项目里。从原始语料开始，先分词并统计词频，生成 Token 词表、拼音词典和英文词典；然后统计一元、二元和三元 N-gram，构建统计语言模型；再经过平滑、熵裁剪和量化压缩，最终生成输入法运行时需要的 &lt;code&gt;.dict&lt;/code&gt; 和 &lt;code&gt;.cnt&lt;/code&gt; 文件。&lt;/p&gt;
&lt;p&gt;这件事看起来只是把很多工具串在一起，实际做起来却有不少细节。若是随便找一个分词器处理语料，再直接统计语言模型，为了控制词表大小，很容易引入大量 OOV。对一般 NLP 任务来说，少量 OOV 也许可以接受；但输入法不行，输入法至少应该能够输入全部合法汉字。因此，词表裁剪不能只是删掉低频词，还要兼顾汉字覆盖、分词效果、模型大小和最终的输入体验。&lt;/p&gt;
&lt;p&gt;运行时的核心是一个 C++20 输入引擎。拼音和英文词典被编译成 Double-Array Trie，语言模型经过紧凑存储后通过 mmap 加载。用户输入会被展开成一张候选路径网络，再通过 Beam Viterbi 搜索得到整句结果。全拼、简拼、九宫格、中英混输，看起来是几种不同的输入方式，最后都要回到同一个问题：怎样在有限的候选和上下文中，尽快找出最合理的句子。&lt;/p&gt;
&lt;p&gt;目前发布的模型使用百亿字规模的语料训练。它当然没有大语言模型那么强，也不理解用户究竟想表达什么，但在输入法这个任务上，统计语言模型仍然有很合适的位置：确定、快速、占用可控，而且可以完全离线。再加上本地用户句子学习，它也能逐渐适应一个人的常用表达，而这些数据不需要离开设备。&lt;/p&gt;
&lt;h2 id="技术都有价值"&gt;技术都有价值&lt;/h2&gt;
&lt;p&gt;最初可能只是想做一个实验，或者为当年的争论补上一份迟到的答案。但真正做下去以后，Trie、N-gram、平滑、熵裁剪、量化、mmap、Beam Search，这些原本散落在书本和论文里的技术，最后真的变成了一个每天可以使用的输入法。&lt;/p&gt;</description></item><item><title>底层实现：三本书</title><link>https://ismantic.github.io/1/</link><pubDate>Wed, 18 Mar 2026 19:00:00 +0800</pubDate><guid>https://ismantic.github.io/1/</guid><description>&lt;h2 id="引言"&gt;引言&lt;/h2&gt;
&lt;p&gt;写下此文，是想介绍过去两年里由兴趣驱动完成的一些项目。这些项目大部分是对我过去十余年工作中写过的代码的重构，也有一些是学习新知识的结果。不推荐用于生产实践，作为学习项目还可以。把它们整理成三本书，这样能显得更系统化一些。目前还有不少工作没有完成，细节也有待打磨，就看之后怎么找时间继续改进了。希望能得到一些反馈。&lt;/p&gt;
&lt;p&gt;语言模型的技术栈通常由许多独立系统拼接而成：文本由 Tokenizer 处理，模型依赖训练框架执行，算子由 CPU 或 GPU 实现，上层再使用 Python 把这些组件组织起来。这样的分层便于使用，却也隐藏了各部分真正的连接方式。&lt;/p&gt;
&lt;p&gt;曾经对这个问题做过不少思考，也做了一些尝试，逐渐整理出 Text、Zero 和 Matx 几个项目。它们以 C++ 为系统核心，分别探索文本处理、模型训练和语言编译，并尝试为用户提供一种接近 Python 的脚本语言。目标不是重新包装现有库，而是观察一套语言模型系统从文本输入到硬件计算究竟需要哪些基础结构。这些工作既包含对已有实现的重新整理，也是重新理解和学习相关技术的过程。至于能否在此基础上产生一些创新，还要看之后的积累和机遇。&lt;/p&gt;
&lt;h2 id="愿景"&gt;愿景&lt;/h2&gt;
&lt;p&gt;最终的系统以类 Python 语言作为用户界面。用户仍然可以用熟悉的方式组织 Tokenizer、Tensor、模型和训练循环，但程序不再依赖 Python 充当各个组件之间的胶水：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;tokenizer &lt;span style="color:#f92672"&gt;=&lt;/span&gt; Tokenizer&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Load(&lt;span style="color:#e6db74"&gt;&amp;#34;BPE&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;model &lt;span style="color:#f92672"&gt;=&lt;/span&gt; GPT(&lt;span style="color:#e6db74"&gt;&amp;#34;Gpt-2&amp;#34;&lt;/span&gt;)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;optimizer &lt;span style="color:#f92672"&gt;=&lt;/span&gt; AdamW(model&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Parameters())
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;tokens &lt;span style="color:#f92672"&gt;=&lt;/span&gt; tokenizer&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Encode(text)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;loss &lt;span style="color:#f92672"&gt;=&lt;/span&gt; model(tokens)
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;loss&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Backward()
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;optimizer&lt;span style="color:#f92672"&gt;.&lt;/span&gt;Step()
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这段代码在底层会进入一套以 C++ 为主的技术栈：&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-text" data-lang="text"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 类 Python 脚本语言
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ▼
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Matx 语言编译器
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 语法、类型、对象与控制流
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ┌────────────┴────────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ▼ ▼
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Text 文本处理 Zero 训练框架
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Tokenizer / Regex / Trie Tensor / Autograd / Module
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; │ │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; └────────────┬────────────┘
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ▼
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Tensor Operator
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ▼
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; Tensor 编译器
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; 计算表示、调度、内存与并行化
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; │
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ┌───────────┼───────────┐
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; ▼ ▼ ▼
&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; CPU CUDA 其他硬件
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;这里需要两层不同的编译器。语言编译器处理函数、对象、容器、控制流和模块调用，把类 Python 程序转换成 C++ 或更低层的程序表示；Tensor 编译器处理 Tensor 的索引、布局、循环、并行化和内存调度，再将 Tensor IR 降低为不同硬件上的 Kernel。&lt;/p&gt;</description></item></channel></rss>