用基础数学理解 nanoGPT 的语言建模

把语言建模问题写成条件概率:估计 $P(x_{t+1}\mid x_{\le t})$。 这篇文章更偏“看源码时能对得上号”的解释:以 nanoGPT(GPT-2 风格 Transformer)为参照(推荐对照这份带注释的实现:https://github.com/buvidk1234/nanoGPT ),只用必要的基础数学,把它为什么能做下一个 token 预测、训练时到底在优化什么,讲清楚。 约定一个更贴近实现的术语:模型直接处理的不是“单词”,而是 token id(通常由 BPE 等子词分词得到的整数序列)。训练后我们有时会观察到 token embedding 的几何相似性,但它不是先验语义规则,而是被目标函数与数据共同塑形的结果。 从最底层开始:如何在数值空间里表示 token,并让这种表示能够被后续计算反复使用? 如何让计算机表示 token? 可以借助一种数学工具——向量。 两个向量的夹角余弦可以衡量它们的相似度: $$\cos(\theta) = \frac{\mathbf{a} \cdot \mathbf{b}}{\|\mathbf{a}\| \|\mathbf{b}\|}$$$\cos(\theta)$ 越接近 1,两个向量越相似;越接近 -1,越相反。 因此,我们可以用向量来表示 token;余弦相似度常被用作“观察/分析”这种表示的一个指标(例如看哪些 token 的向量更接近)。但需要强调: 余弦相似度不是“语义”的定义,只是向量空间中的一个几何量。 向量会在训练目标(下一个 token 预测)驱动下被塑形,最终是否呈现出你直觉里的“近义/反义”取决于数据、模型容量与训练过程。 这种表示方式称为 Token Embedding:每个 token 对应一个 $d$ 维向量 $\mathbf{e} \in \mathbb{R}^d$。 如何让计算机表示序列? 同一个 token 在不同上下文里可能承担不同功能。仅靠 Token Embedding 得到的是“上下文无关”的初始表示,还需要一种方式把位置信息与上下文交互纳入计算。 为此引入位置向量(Positional Embedding):假设序列长度为 $m$,为每个位置分配一个 $d$ 维向量 $\mathbf{p}_j$。在 nanoGPT / GPT-2 的常见实现里,这个位置向量通常是一个可学习的查表(learned positional embedding)。将两者相加得到模型输入: ...

March 11, 2026

Agent

Agent:从 Prompt Pattern 到 Agent Runtime 如果用 2023 年的视角看 Agent,经典结构通常是 Planning、Memory、Tools。这个划分没有错,但放到 2026 年已经不够了。 今天更准确的理解是: Agent 不是“一个会调用工具的大模型”,而是一个由模型驱动、可以感知上下文、调用工具、维护状态、接受约束、可观测、可恢复的运行时系统。 可以把现代 Agent 拆成七个部分: 层次 负责什么 Model 推理、生成、工具选择、结构化输出 Instructions 角色、目标、边界、业务规则 Context 当前任务、历史状态、检索结果、用户偏好、文件和工件 Tools 函数、API、数据库、代码执行、浏览器、MCP Server、其他 Agent Runtime 循环调度、流式输出、重试、暂停恢复、并发、检查点 Policy 权限、审批、沙箱、预算、速率限制、输入输出校验 Observability trace、评测、日志、成本、延迟、失败分析 这也是为什么早期的 Prompt Pattern 文章会显得“旧”:它们解释了 Agent 的思想来源,但没有覆盖生产系统真正困难的部分。 1. 经典结构仍然成立,但只能算基础层 经典 Agent 通常被拆成三块: Planning:把复杂目标拆成步骤。 Memory:保存任务相关上下文。 Tools:让模型能影响外部世界。 这个模型适合入门,但在真实系统里会遇到几个问题: Planning 不能只靠模型自由发挥,需要运行时约束、检查点和失败恢复。 Memory 不能只理解成上下文窗口,需要区分会话状态、长期记忆、检索知识库、文件工件。 Tools 不能只理解成 function calling,需要认证、授权、审批、沙箱、超时、幂等和审计。 Multi-Agent 不能只理解成“多个角色聊天”,需要明确的路由、状态边界和责任归属。 生产环境必须有 trace、eval、guardrail、成本和延迟控制。 所以本文的重点不是否定经典范式,而是把它们放回正确的位置:经典范式是 Agent 的思想基础,现代 Agent Runtime 才是工程架构。 ...

March 9, 2026

Id Seq Generation

分布式 ID / 序列号生成方案技术选型 1. 方案概述 1.1 UUID 128 位随机数(V4)或基于时间+MAC(V1)。无需中心节点,生成即唯一。 完全无序、不连续,无法用于排序或范围查询 36 字节字符串存储开销大,随机值导致 B+Tree 页分裂,写入性能差 1.2 数据库自增 ID MySQL AUTO_INCREMENT 主键,单表内严格递增且连续。 每次发号伴随磁盘写(INSERT/UPDATE),单机上限约 1000-3000 TPS 强依赖单点数据库,宕机即停;分库后各库独立递增,全局不连续 1.3 Snowflake(雪花算法) 64 位 = 1 bit 符号 + 41 bit 时间戳 + 10 bit 机器 ID + 12 bit 序列号。纯内存计算,单机 400 万+/秒。 趋势递增但不连续,两个 ID 的差值无业务含义 依赖机器时钟,时钟回拨可能导致 ID 重复 1.4 Leaf-Segment(美团 Leaf 号段模式) 从数据库批量预取号段(如 1000 个),应用内存中顺序分发。当前号段消耗到阈值时异步预加载下一号段,避免切换时阻塞。 号段内连续,但进程重启时未消费的号段被浪费,产生空洞 多实例部署时各进程持有不同号段,同一业务维度内 ID 交叉,无法保证连续 1.5 Leaf-Snowflake Snowflake 变体,用 ZooKeeper 管理 workerID 并解决时钟回拨。本质仍是 Snowflake,不连续的缺陷不变。 ...

March 5, 2026

Go

draft need to be improved Slice GOtype slice struct { array unsafe.Pointer len int cap int } // 扩容 func nextslicecap(newLen, oldCap int) int { newcap := oldCap doublecap := newcap + newcap if newLen > doublecap { return newLen } const threshold = 256 if oldCap < threshold { return doublecap } for { // 1.25x newcap += (newcap + 3*threshold) >> 2 if uint(newcap) >= uint(newLen) { break } } // overflowed if newcap <= 0 { return newLen } return newcap } Map GOtype Map struct { used uint64 // The number of filled slots seed uintptr // the hash seed, computed as a unique random number per map. // The directory of tables. // Normally dirPtr points to an array of table pointers // dirPtr *[dirLen]*table dirPtr unsafe.Pointer dirLen int // 1 << globalDepth globalDepth uint8 // The number of bits to use in table directory lookups. globalShift uint8 // The number of bits to shift out, 64 - globalDepth writing uint8 // detect the race. tombstonePossible bool // whether a table in this map contains a tombstone. clearSeq uint64 // version number, used to detect map clears during iteration. } Swiss Table refer: Faster Go maps with Swiss Tables ...

March 2, 2026

Spring

Spring 容器启动流程

March 2, 2026

Operating System

编译过程 计算机硬件结构 虚拟内存 地址翻译 文件 网络 并发 进程 资源分配的基本单位。每个进程拥有独立的地址空间、文件描述符表、信号处理器等资源,进程之间彼此隔离。 进程的状态包括:创建、就绪、运行、阻塞、终止。 上下文切换 进程上下文切换需要保存和恢复的内容: CPU 寄存器(程序计数器 PC、栈指针 SP、通用寄存器等) 虚拟内存映射(页表基址寄存器,需刷新 TLB) 内核栈 进程控制块(PCB)中的状态信息 由于涉及页表切换和 TLB 刷新,进程切换的开销较大(通常在微秒级)。 进程间通信 进程拥有独立的地址空间,因此需要通过操作系统提供的 IPC 机制来通信: 方式 特点 适用场景 管道(Pipe) 半双工,父子进程间使用 简单的单向数据流 命名管道(FIFO) 半双工,无亲缘关系限制 不相关进程间通信 消息队列 有格式的消息,可按类型读取 结构化数据传递 共享内存 最快的 IPC 方式,需配合同步机制 大量数据交换 信号量(Semaphore) 用于同步,控制对共享资源的访问 互斥与同步 信号(Signal) 异步通知机制 事件通知(如 SIGKILL) Socket 支持不同主机间通信 网络通信 线程 ...

March 1, 2026

Linux Troubleshooting

L1 日常排错篇 适用场景:服务连不上、启动报错、明显卡顿。L1 不追求命令全,只保留日常最常用、能马上定方向的几组操作;指标解释和深入定位放到 L2。 1. 服务是不是还活着 使用场景:服务启动后访问不了、发布后怀疑没起来、进程异常退出、systemd 服务状态不对。 BASHsystemctl status <service> --no-pager journalctl -u <service> -n 100 --no-pager journalctl -u <service> -f pgrep -af <name> 先看服务状态是不是 active (running),最近 100 行日志里有没有启动失败、配置错误、端口占用、权限错误。pgrep -af 用来确认进程是否真的存在,以及启动参数是不是预期的。 2. 端口有没有开 使用场景:服务进程存在,但客户端连不上;启动时报 Address already in use;不确定服务监听的是本机、内网还是所有网卡。 BASHss -lntp ss -lntp | grep ':8080' lsof -i :8080 重点看有没有 LISTEN,监听地址是 0.0.0.0、127.0.0.1 还是具体内网 IP。服务只监听 127.0.0.1 时,本机能访问,外部机器通常访问不到。 3. 从外部能不能访问 使用场景:本机服务看起来正常,但浏览器、调用方、其他机器访问失败;需要区分是 DNS、网络、端口、HTTP 还是服务端处理慢。 BASHnc -zv <ip> <port> curl -Iv http://<host>:<port>/ dig <domain> ping -c 4 <ip> nc 看 TCP 端口能不能连上,curl -Iv 看 HTTP 状态码、重定向和握手过程,dig 看域名是否解析到预期 IP。ping 只作为辅助参考,很多云服务器会禁 ICMP,ping 不通不代表 TCP 一定不通。 ...

February 28, 2026

Redis Datastructure

Redis 数据结构 本文按 Redis 8.x/unstable 源码写,重点看内存编码和自动转换。老版本里 ziplist、linkedlist 等编码已经不适合作为当前主线。 逻辑类型 (Type) 底层数据结构 (Internal Encoding) String int, embstr, raw (SDS) List listpack, quicklist Hash listpack, listpackEx, hashtable Set intset, listpack, hashtable ZSet listpack, skiplist + hashtable Stream rax + listpack graph TD A[redisObject] --> B{type} B -->|STRING| C[int / embstr / raw] B -->|LIST| D{size?} B -->|HASH| E{size?} B -->|SET| F{all int?} B -->|ZSET| G{size?} B -->|STREAM| S[rax + listpack] D -->|小| DL[listpack] D -->|大| DQ[quicklist] E -->|小| H[listpack] E -->|field TTL| HX[listpackEx / hashtable metadata] E -->|大| I[hashtable] F -->|yes & 少| J[intset] F -->|no & 小| K[listpack] F -->|大| FH[hashtable] G -->|小| L[listpack] G -->|大| M[skiplist + hashtable]每种类型根据数据量自动切换编码:小数据用紧凑结构省内存,大数据切高效结构保性能。 ...

February 12, 2026

Java New Features

前言 Java 作为一门历史悠久的编程语言,一直在不断演进。本文按照 LTS(长期支持)版本 组织,介绍 Java 8 到 Java 25 的重要新特性,帮助开发者快速了解每个版本可以用哪些功能。 💡 每个特性后标注的版本号表示该特性正式可用的最低版本。 Java 8 LTS (2014) - 里程碑式更新 Java 8 是 Java 历史上最重要的版本,引入了函数式编程的核心概念。 Lambda 表达式 是什么? 一种简洁的匿名函数写法,把"行为"像数据一样传递。 JAVA// ❌ 传统写法 Collections.sort(list, new Comparator<String>() { @Override public int compare(String s1, String s2) { return s1.compareTo(s2); } }); // ✅ Lambda 写法 Collections.sort(list, (s1, s2) -> s1.compareTo(s2)); // ✅ 方法引用 Collections.sort(list, String::compareTo); Lambda 语法: ...

February 4, 2026

Mysql Technical Blog

MySQL 技术要点 一、MySQL 架构 MySQL 采用分层架构设计,主要分为 Server 层 和 存储引擎层。 1.1 Server 层 组件 职责 连接器 管理客户端连接、身份认证、权限校验 分析器 词法分析(识别关键字、表名、列名)、语法分析(构建语法树) 优化器 生成执行计划、选择最优索引、决定 JOIN 顺序 执行器 调用存储引擎 API 执行查询,返回结果集 1.2 存储引擎层 MySQL 支持插件式存储引擎,常用引擎对比: 引擎 事务支持 锁粒度 适用场景 InnoDB ✓ 行锁 OLTP、高并发读写 MyISAM ✗ 表锁 只读或读多写少 Memory ✗ 表锁 临时表、缓存 InnoDB architecture 二、索引机制 2.1 索引数据结构 类型 特点 适用场景 B+ Tree 有序、支持范围查询、树高稳定 主键索引、普通索引 Hash O(1) 查找、不支持范围查询 等值查询(Memory 引擎) Full-Text 倒排索引、支持自然语言搜索 全文检索 2.2 索引分类 按物理存储: ...

January 15, 2026