← 2026-03-16 📂 All Days 2026-03-18 →
🏗️
🏗️ System Design
🏗️ 系统设计 Day 4 / System Design Day 4 — 负载均衡 / Load Balancing

🏗️ 系统设计 Day 4 / System Design Day 4 — 负载均衡 / Load Balancing

基础阶段 Foundation Phase | 预计阅读时间 ~3-4 分钟

场景引入 / Scenario

想象你开了一家超级火爆的奶茶店 🧋。第一天只有一个收银台,没问题。但第二天去大众点评上了热搜,突然排队排到门外一百米。怎么办?你开了第二个、第三个收银台,还派了一个引导员在门口,告诉每个客人该去哪个收银台排队。

这个"引导员"就是负载均衡器(Load Balancer)

Imagine you open a super-popular boba tea shop. Day one: one cashier, no problem. Day two: you go viral, and there's a 100-meter queue outside. Solution? You open more cashier lanes and station someone at the entrance directing each customer to the shortest line.

That person at the entrance is your Load Balancer.


架构图 / Architecture Diagram

┌─────────────────────────────┐ │ 用户请求 │ │ Incoming Requests │ └──────────────┬──────────────┘ │ ▼ ┌─────────────────────────────┐ │ Load Balancer │ │ 负载均衡器 │ │ (Nginx / AWS ALB / HAProxy) │ └───────┬──────┬──────┬───────┘ │ │ │ ┌──────────┘ │ └──────────┐ ▼ ▼ ▼ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ Server 1 │ │ Server 2 │ │ Server 3 │ │ 服务器 1 │ │ 服务器 2 │ │ 服务器 3 │ │ ████████ │ │ ████ │ │ ██ │ │ (80% load) │ │ (40% load) │ │ (20% load) │ └────────────┘ └────────────┘ └────────────┘ │ ┌──────────────┘ │ ▼ ┌─────────────────────┐ │ Shared Database │ │ 共享数据库 │ └─────────────────────┘

核心概念 / Key Concepts

负载均衡算法 / Load Balancing Algorithms

1. Round Robin(轮询)

  • 依次把请求分给每台服务器,循环往复
  • 类比:收银台依次叫号
  • 适用:服务器性能相同、请求处理时间相近的场景

2. Weighted Round Robin(加权轮询)

  • 性能强的服务器分配更多请求(权重更高)
  • 类比:有个收银员超快,就多给她排队
  • 适用:服务器配置不均匀的场景

3. Least Connections(最少连接)

  • 把新请求分给当前连接数最少的服务器
  • 类比:去排队最短的那个收银台
  • 适用:请求处理时长差异大的场景(如文件上传 vs 简单查询)

4. IP Hash(IP 哈希)

  • 根据客户端 IP 地址决定路由到哪台服务器
  • 同一个用户总是被路由到同一台服务器
  • 适用:需要会话粘性(Session Stickiness)的场景

为什么这样设计?/ Why This Design?

目标 Goal解决方案 Solution
高可用 High Availability一台服务器挂了,流量自动转移
水平扩展 Horizontal Scaling加新服务器,不改代码
性能 Performance避免单点瓶颈,减少响应时间
健康检查 Health ChecksLB 自动剔除故障节点

两种类型 / Two Types

Layer 4 (Transport Layer) LB

  • 基于 IP + TCP/UDP 端口路由
  • 速度快,但"看不懂"请求内容
  • 类比:只看信封地址,不看信的内容

Layer 7 (Application Layer) LB

  • 基于 HTTP 头、URL、Cookie 等路由
  • 更智能(可以把 /api 路由到 API 服务器,把 /static 路由到 CDN)
  • 类比:根据信的内容决定投递给哪个部门
  • 性能稍低,但灵活得多

别踩这个坑 / Don't Fall Into This Trap

坑 1:有状态的服务器(Stateful Servers)

错误做法: 把用户 Session 存在单台服务器的内存里

用户第1次请求 → Server 1 (Session 存在这里) 用户第2次请求 → Server 2 (找不到 Session!用户被登出)

正确做法: Session 外置到共享存储

用户第1次请求 → Server 1 → 把 Session 写入 Redis 用户第2次请求 → Server 2 → 从 Redis 读 Session ✓

关键原则:服务器要做到"无状态"(Stateless),所有状态都存外部!

坑 2:负载均衡器自身成为单点故障

如果 LB 本身挂了怎么办?

解决方案: 部署主备 LB(Active-Passive)或使用 DNS 轮询 + 多 LB

坑 3:健康检查不够频繁

LB 依赖健康检查(Health Check)来知道哪台服务器挂了。如果检查间隔太长(比如 60s),可能有 1 分钟的流量打到死服务器上。

✅ 生产环境通常设置:每 5-10 秒一次健康检查。


延伸阅读 / Going Deeper

  • 昨天(Day 3)我们聊了 HTTP/REST,现在你知道 LB 就工作在 HTTP 这一层之上
  • 下周我们会聊数据库扩展,届时 LB 的概念还会出现(读写分离 + 连接池)
  • 如果你听说过 Nginx、HAProxy、AWS ALB/NLB,它们都是负载均衡器的具体实现

Day 4 / 100 — 系统设计基础系列 System Design Foundations

💻
💻 Algorithms
💻 算法 Day 4 / Algorithms Day 4 — #49 Group Anagrams (Medium) — Array...

💻 算法 Day 4 / Algorithms Day 4 — #49 Group Anagrams (Medium) — Arrays & Hashing

基础阶段 Foundation Phase | 预计阅读时间 ~3-4 分钟

现实类比 / Real-World Analogy

想象你在邮局分拣包裹。每个包裹上写的是打乱顺序的地址,比如 "acts", "cats", "tacs" 其实都是同一个地方(字母相同,顺序不同)。你的任务是把所有"相同地址"的包裹放到同一个箱子里。

怎么判断两个地址"本质相同"?把字母排个序,如果排序后一样,就是同一个地址!

At the post office, sorting packages with scrambled addresses: "acts", "cats", "tacs" all go to the same place. Your job: group them together. The trick? Sort the letters — if they match after sorting, they're anagrams!


题目 / Problem Statement

给定一个字符串数组,将所有字母异位词(anagram)分组在一起。

Given an array of strings, group the anagrams together.

输入 Input:  ["eat","tea","tan","ate","nat","bat"]
输出 Output: [["bat"],["nat","tan"],["ate","eat","tea"]]

字母异位词 = 由相同字母以不同顺序构成的单词

Anagram = words with the same letters in different order


思路解析 / Step-by-Step Walkthrough

关键洞察 Key Insight:

两个字符串互为 anagram,当且仅当它们排序后相同。

Two strings are anagrams if and only if their sorted versions are identical.

所以我们用排序后的字符串作为哈希表的 key,把所有 anagram 归到同一个桶(bucket)里。

追踪过程 / Trace Through the Example

输入:["eat","tea","tan","ate","nat","bat"]

当前单词 Word排序后 Sorted Key哈希表状态 HashMap State
"eat""aet"{"aet": ["eat"]}
"tea""aet"{"aet": ["eat","tea"]}
"tan""ant"{"aet": ["eat","tea"], "ant": ["tan"]}
"ate""aet"{"aet": ["eat","tea","ate"], "ant": ["tan"]}
"nat""ant"{"aet": ["eat","tea","ate"], "ant": ["tan","nat"]}
"bat""abt"{"aet": [...], "ant": [...], "abt": ["bat"]}

最终取哈希表的所有 values → [["eat","tea","ate"], ["tan","nat"], ["bat"]]


Python 解法 / Python Solution

from collections import defaultdict

def groupAnagrams(strs: list[str]) -> list[list[str]]:
    # Use a defaultdict so we can append without checking if key exists
    # 用 defaultdict,省去手动判断 key 是否存在的麻烦
    anagram_map = defaultdict(list)
    
    for word in strs:
        # Sort the word's characters to create the canonical key
        # 对字母排序,得到这组 anagram 的"标准形式"
        # e.g., "eat" -> sorted("eat") -> ['a','e','t'] -> "aet"
        key = "".join(sorted(word))
        
        # Append this word to the bucket for its key
        # 把当前单词放入对应的桶
        anagram_map[key].append(word)
    
    # Return all the groups (values of the map)
    # 返回所有分组
    return list(anagram_map.values())

手动验证 / Manual Verification

让我们用 ["eat","tea","tan","ate","nat","bat"] 逐步跑代码:

  1. word = "eat"sorted("eat") = ['a','e','t']key = "aet"anagram_map = {"aet": ["eat"]}
  2. word = "tea"sorted("tea") = ['a','e','t']key = "aet"anagram_map = {"aet": ["eat","tea"]}
  3. word = "tan"sorted("tan") = ['a','n','t']key = "ant"anagram_map = {"aet": [...], "ant": ["tan"]}
  4. word = "ate"sorted("ate") = ['a','e','t']key = "aet"anagram_map = {"aet": ["eat","tea","ate"], "ant": ["tan"]}
  5. word = "nat"sorted("nat") = ['a','n','t']key = "ant"anagram_map = {"aet": [...], "ant": ["tan","nat"]}
  6. word = "bat"sorted("bat") = ['a','b','t']key = "abt"anagram_map = {"aet": [...], "ant": [...], "abt": ["bat"]}

最终返回 [["eat","tea","ate"], ["tan","nat"], ["bat"]] ✅ 与题目期望输出一致!


时间/空间复杂度 / Complexity Analysis

时间复杂度 Time Complexity: O(n × k log k)

  • n = 字符串数量(number of strings)
  • k = 最长字符串的长度(max string length)
  • 对每个字符串排序 = O(k log k),总共 n 个字符串

空间复杂度 Space Complexity: O(n × k)

  • 哈希表存储所有字符串的副本
  • 最坏情况:所有字符串都不是 anagram,每个单独一个桶

边界情况 / Edge Cases

# 空数组 Empty input
groupAnagrams([])  # → []

# 单个字符串 Single string
groupAnagrams(["a"])  # → [["a"]]

# 所有字符串都是 anagram All are anagrams
groupAnagrams(["abc","bca","cab"])  # → [["abc","bca","cab"]]

# 没有 anagram 对 No anagram pairs
groupAnagrams(["abc","def","ghi"])  # → [["abc"],["def"],["ghi"]]

举一反三 / Pattern Recognition

这道题的模式:用排序/哈希创建"规范形式"(Canonical Form)

当你需要"把等价的东西归类"时,找到一个好的 key 是关键。

同类变体 / Follow-up Variations:

  1. #242 Valid Anagram(Day 2 做过!) — 判断两个字符串是否互为 anagram,现在你应该更理解为什么用哈希表了
  1. 用字符计数作 key(优化版) — 不排序,而是统计 26 个字母的频率,构成一个 tuple 作 key。时间复杂度降到 O(n × k):

```python

key = tuple(Counter(word).values()) # 不推荐,顺序不固定

# 更好的方式:

count = [0] * 26

for c in word:

count[ord(c) - ord('a')] += 1

key = tuple(count) # e.g., "eat" -> (1,0,0,0,1,0,...,1,...) [a=1,e=1,t=1]

```

  1. 思考扩展: 如果字符串包含 Unicode 字符怎么办?用 Counter 而非固定 26 位数组

Day 4 / 100 — Arrays & Hashing 系列

昨天 Day 3:Two Sum | 明天 Day 5:Top K Frequent Elements

🗣️
🗣️ Soft Skills
🗣️ 软技能 Day 4 / Soft Skills Day 4 — 失败与成长 / Failure & Growth

🗣️ 软技能 Day 4 / Soft Skills Day 4 — 失败与成长 / Failure & Growth

基础阶段 Foundation Phase | 预计阅读时间 ~2-3 分钟

今日问题 / Today's Question

"Describe a project that failed or didn't meet expectations. What did you learn?"
描述一个失败的或未达到预期的项目。你从中学到了什么?

为什么这很重要 / Why This Matters

这个问题是面试官用来区分普通候选人优秀候选人的分水岭。

大多数人要么:

  • 找借口("那个产品经理需求一直在变…")
  • 给出假失败("我太追求完美了!")

真正优秀的工程师知道:失败是学习的压缩包。能清晰复盘失败,说明你有自我意识、有成长心态、有责任感。

This question separates candidates who are self-aware from those who aren't. Great engineers treat failure as dense learning. If you can articulate a real failure with clarity, you signal maturity, accountability, and a growth mindset.


STAR 框架拆解 / STAR Framework Breakdown

Situation(情境): 设置背景 — 项目是什么?团队多大?时间线如何?

Task(任务): 你的角色 — 你负责什么?期望是什么?

Action(行动): 你做了什么 — 包括那些事后回想起来的"错误决定"

Result(结果): 真实的结果 — 项目延期?功能被砍?用户不买账?

+Learning(学习): ⭐ 这是整个回答的精华 — 你具体学到了什么?有什么改变?


❌ 糟糕的回答 / Bad Approach

"我们做了一个推荐系统,但效果没有预期好。这让我意识到我应该更仔细地沟通需求。"

问题在哪?

  • 没有具体细节(什么推荐系统?多大的影响?)
  • "更仔细地沟通"——太泛了,面试官听到这句话已经睡着了
  • 没有说明你个人在失败中的角色
  • 学到的教训没有被后续行动验证

✅ 好的回答 / Good Approach

"In my second year at [Company], I led a migration of our user notification system from a monolithic service to an event-driven architecture. The business goal was to reduce notification latency from ~3 seconds to under 500ms and improve reliability.
我的任务是设计新架构并协调三个团队的迁移工作,预期 Q2 上线。
我犯的关键错误:我低估了消息幂等性(idempotency)的问题。我假设下游消费者已经处理好了重复消息,但实际上没有。上线后,部分用户在一次事件中收到了 3-5 条重复通知,引发了大量投诉,我们不得不回滚。
We rolled back within 6 hours, which itself was a success — but the original go-live failed.
从这次失败中,我有两个具体改变:
1. 我开始在所有 event-driven 设计的 design doc 里加一节 'Idempotency Guarantees',明确列出哪一层负责去重
2. 我们建立了 chaos testing 流程,在 staging 环境模拟消息重投递,在那以后我们再没有出现类似问题
三个月后,同一个团队完成了迁移,延迟确实降到了 420ms。我认为能拿到这个结果,部分原因就是第一次的失败让我们想清楚了真正的难点。"

为什么这个回答好?/ Why This Works

具体技术细节 — 幂等性问题,不是模糊的"沟通问题"

诚实承担责任 — "我犯的关键错误",没有推锅

量化影响 — 3-5 条重复通知,回滚 6 小时内完成

学习有证据 — 不是说"我学会了要考虑幂等性",而是说"我在所有后续 design doc 里加了这一节"

故事有结尾 — 三个月后成功了,说明学习真的有效


Senior/Staff 级别加分项 / Senior/Staff Level Tips

如果你是 Senior 或 Staff 候选人,面试官想听到更多的是系统性改变,而非个人教训:

  • "我把这个 checklist 推广到了整个团队" (team impact)
  • "我们更新了 runbook,现在新 engineer onboarding 时会学到这个" (process change)
  • "这次失败推动了我们建立 incident review 文化" (cultural change)

层级越高,你的学习边界越大。 Junior 学到的是"我应该更仔细地测试";Staff 学到的是"我们整个组织的测试文化需要改变"。


你可以改编的模板 / Scenario Template

情境: 我在 [公司/项目] 负责 [技术项目],目标是 [业务目标]。
错误: 我的关键判断失误是 [具体技术/流程错误]。
影响: 导致了 [具体后果,数字化]。
学习1: 从此以后,我 [具体新习惯/流程,可验证]。
学习2: 我把这个教训 [推广/文档化] 到了 [范围]。
后续: [N 个月后,最终的结果是...]。

关键要点 / Key Takeaways

  1. 选真实的失败 — 面试官能辨别出假失败。真失败才有说服力
  2. 从个人行动出发 — "我们失败了"比"我做了错误决定"弱 10 倍
  3. 学习要有后续行动 — "我意识到"不够,"我从那以后改变了"才有力量
  4. 结局不一定非要成功 — "项目被取消,但我建立的系统依然在生产环境跑着"也是好结尾

Day 4 / 100 — 行为面试系列 Behavioral Interview Series

昨天 Day 3:与领导意见相左 | 明天 Day 5:时间管理与优先级

🎨
🎨 Frontend
🎨 前端 Day 4 / Frontend Day 4 — 响应式设计与媒体查询 / Responsive Design & Medi...

🎨 前端 Day 4 / Frontend Day 4 — 响应式设计与媒体查询 / Responsive Design & Media Queries

基础阶段 Foundation Phase | 预计阅读时间 ~2-3 分钟

猜猜这段代码的行为?/ What Does This Code Do?

.container{
  width: 100%;
  padding: 0 20px;
  box-sizing: border-box;
}

@media (min-width: 768px) {
.containerr {
    max-width: 960px;
    margin: 0 auto;
    padding: 0 40px;
  }
}

@media (min-width: 1200px) {
.containerr {
    max-width: 1140px;
    padding: 0 60px;
  }
}

问:当浏览器宽度是 800px 时,.container 的实际宽度和 padding 是多少?

Q: When the browser is 800px wide, what are the actual width and padding?

<details>

<summary>点击查看答案 / Click for Answer</summary>

答案:宽度 = 800px(100% of viewport),padding = 0 40px

理由:

  • 800px > 768px → 第一个 @media 规则生效 ✓
  • 800px < 1200px → 第二个 @media 规则不生效 ✗
  • max-width: 960px 生效,但 800px < 960px,所以实际宽度被 width: 100% 控制 = 800px
  • margin: 0 auto 生效(container 会居中,但由于宽度是 100%,看不出来)
  • padding 被第一个媒体查询覆盖为 0 40px

所以:width = 800px,padding = 0 40px

</details>


核心概念 / Core Concepts

什么是响应式设计?/ What Is Responsive Design?

同一套 HTML/CSS,在手机、平板、电脑上都好看好用。

不是做三个不同的页面,而是用弹性布局 + 媒体查询适配所有屏幕。

One codebase, all screen sizes. Not three separate pages — flexible layouts + media queries.

媒体查询语法 / Media Query Syntax

/* 基础语法 Basic syntax */
@media [media-type] [and/not/only] (condition){
  /* CSS rules */
}

/* 常见断点 Common breakpoints */
/* Mobile first approach (推荐!) */
/* Base styles: mobile */
.element { font-size: 14px; }

@media (min-width: 576px)  { /* sm - Large phones */ }
@media (min-width: 768px)  { /* md - Tablets */ }
@media (min-width: 992px)  { /* lg - Desktops */ }
@media (min-width: 1200px) { /* xl - Large desktops */ }

两种策略 / Two Approaches

Mobile First (推荐 ✓) Desktop First (常见但不推荐) ──────────────────── ────────────────────────── 先写手机样式, 先写桌面样式, 用 min-width 往上覆盖 用 max-width 往下覆盖 Base → small screens Base → large screens ↑ override for larger ↓ override for smaller 好处: 缺点: ✅ 性能更好 (mobile loads less) ❌ 移动端加载冗余样式 ✅ 优先考虑移动端体验 ❌ 思维反直觉 ✅ Progressive enhancement ❌ Graceful degradation

实战代码 / Practical Example

/* Mobile First: 先写最小屏幕的样式 */
.card-grid{
  display: grid;
  grid-template-columns: 1fr;  /* 1 column on mobile */
  gap: 16px;
  padding: 16px;
}

/* 平板: 2 列 */
@media (min-width: 768px) {
.card-gridd {
    grid-template-columns: repeat(2, 1fr);  /* 2 columns */
    gap: 24px;
    padding: 24px;
  }
}

/* 桌面: 3 列 */
@media (min-width: 1200px) {
.card-gridd {
    grid-template-columns: repeat(3, 1fr);  /* 3 columns */
    gap: 32px;
    padding: 32px;
  }
}
手机 (< 768px) 平板 (768-1199px) 桌面 (≥ 1200px) ────────────── ───────────────── ─────────────── ┌──────────┐ ┌─────┐ ┌─────┐ ┌───┐ ┌───┐ ┌───┐ │ Card 1 │ │Card1│ │Card2│ │ 1 │ │ 2 │ │ 3 │ ├──────────┤ ├─────┤ ├─────┤ ├───┤ ├───┤ ├───┤ │ Card 2 │ │Card3│ │Card4│ │ 4 │ │ 5 │ │ 6 │ ├──────────┤ └─────┘ └─────┘ └───┘ └───┘ └───┘ │ Card 3 │ └──────────┘

你可能不知道 / You Might Not Know

Viewport Meta Tag — 必不可少!

<!-- 没有这个,媒体查询在手机上不会正常工作! -->
<!-- Without this, media queries won't work on mobile! -->
<meta name="viewport" content="width=device-width, initial-scale=1.0">

为什么? 手机浏览器默认假装自己是 980px 宽的桌面(为了渲染老网站)。加了这个 meta tag,才告诉它"就用设备真实宽度"。

Why? Mobile browsers default to pretending they're a ~980px desktop viewport (legacy web compatibility). This tag tells them: use the real device width.

媒体查询也能针对其他特性 / Other Media Features

/* 暗色模式 Dark mode */
@media (prefers-color-scheme: dark) {
  body { background: #1a1a1a; color: #f0f0f0; }
}

/* 减少动画(无障碍)Reduced motion (accessibility) */
@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
}

/* 横竖屏 Orientation */
@media (orientation: landscape) {
  .sidebar { display: block; }
}

Mini Challenge 小挑战

/* 这段代码中,如果屏幕宽度是 600px,
   .box 的背景色是什么? */
/* What is the background color of .box at 600px? */

.box { background: red; }

@media (min-width: 500px) {
  .box { background: blue; }
}

@media (max-width: 700px) {
  .box { background: green; }
}

@media (min-width: 550px) and (max-width: 650px) {
  .box { background: yellow; }
}

<details>

<summary>答案 / Answer</summary>

答案:yellow(黄色)

在 600px 时:

  1. red — 基础样式 ✓
  2. min-width: 500px → 600 ≥ 500 → 覆盖为 blue
  3. max-width: 700px → 600 ≤ 700 → 覆盖为 green
  4. min-width: 550px and max-width: 650px → 550 ≤ 600 ≤ 650 → 覆盖为 yellow

CSS 层叠规则:后声明的规则(同优先级时)覆盖先声明的。最后生效的是 yellow

</details>


Day 4 / 100 — CSS 基础系列 CSS Fundamentals

昨天 Day 3:CSS Grid | 明天 Day 5:CSS 动画与 Transitions

🤖
🤖 AI
🤖 AI Day 4 — 分词(Tokenization):LLM 眼中的文字 / Tokenization: How LLMs Se...

🤖 AI Day 4 — 分词(Tokenization):LLM 眼中的文字 / Tokenization: How LLMs See Text

基础阶段 Foundation Phase | 预计阅读时间 ~2-3 分钟

直觉解释 / Intuitive Explanation

你有没有想过,为什么 ChatGPT 有时候连字都数不清楚?比如问它"'strawberry' 这个词里有几个 r?",它可能回答"2 个"而不是正确答案"3 个"?

这就是分词(Tokenization)在背后作怪。

LLM 看到的世界,不是一个个字母,也不是一个个单词——而是tokens(词元)。一个 token 可能是:

  • 一个完整的英语单词(如 hello
  • 一个常见单词的一部分(如 tokentok + en
  • 一个标点符号或空格
  • 一个汉字(通常 1 个汉字 = 1 个 token)

Have you ever wondered why ChatGPT struggles to count letters? Ask "how many 'r's in strawberry?" and it might say 2 instead of 3. That's tokenization at work — the model never sees individual letters.


Tokenization 是怎么工作的?/ How Does It Work?

主流算法:BPE(Byte Pair Encoding)

训练阶段(一次性,在模型训练前):

  1. 从字母级别开始:把所有文字拆成单个字符
  2. 统计最常出现的相邻字符对,合并成一个新 token
  3. 重复 N 次(N 通常是几万次),直到词汇表大小达标

效果:常见的词/词根保持完整,罕见的词被拆开。

"tokenization" 可能被拆成:
"token" + "ization"
或
"token" + "iz" + "ation"
(取决于训练数据中这些片段的频率)

可视化追踪 / Visual Trace

原始文本 Raw Text:
"LLMs are transformers"

          ↓ Tokenizer

Token IDs:   [  47,    2860,    527,   83386 ]
             "LL"  "Ms are"  " trans"  "formers"
              ↑
           注意 "LLMs" 被拆成多个 token!

(以上是示意,实际 token 边界取决于具体模型的词汇表)
模型看到的不是:   L L M s   a r e   t r a n s f o r m e r s
而是:            [token1] [token2] [token3] [token4]

就像把句子看成 LEGO 块,而不是沙粒
Like seeing sentences as LEGO bricks, not individual grains of sand

为什么这很重要?/ Why Does This Matter?

1. 解释了"数字母"的 bug 🔢

"strawberry" → 可能被 token 化为 "straw" + "berry"

模型数 r 时是数 token 里的 r,不是逐字母数。

这是已知的 LLM 局限,不是 bug,而是架构的内在特性。

2. 解释了 Token 计费 💰

OpenAI 按 token 而非单词收费:

  • 英文:1 token ≈ 0.75 个单词
  • 中文:1 个汉字 ≈ 1 个 token(有时更多)
  • 代码:注释和空格也算 token,很"贵"
"Hello, world!" = 4 tokens: ["Hello", ",", " world", "!"]
"你好世界" = 4 tokens: ["你","好","世","界"](每个汉字一个)

3. 解释了 Context Window 的实际限制 📏

GPT-4 的 128K context window 是 token 数,不是字数。

如果你的提示词里中文很多,等效"容量"就比英文少。


代码片段 / Code Snippet

# 用 tiktoken 查看 GPT 系列模型如何分词
# pip install tiktoken
import tiktoken

# Load GPT-4's tokenizer
enc = tiktoken.encoding_for_model("gpt-4")

# Encode a string into token IDs
tokens = enc.encode("tokenization is fascinating")
print(tokens)
# Output: [4037, 2065, 374, 27387] (actual IDs may vary)

# Decode back
decoded = [enc.decode([t]) for t in tokens]
print(decoded)
# Output: ['token', 'ization', ' is', ' fascinating']
# Notice: "tokenization" is split into 2 tokens!

# Count tokens in a prompt
text = "strawberry"
count = len(enc.encode(text))
print(f"'{text}' = {count} token(s)")
# Output: 'strawberry' = 1 token(s)
# 所以 GPT-4 看 "strawberry" 是 1 整个 token,
# 难怪数不出来里面有 3 个 r!

实际应用 / Applications

场景 Use CaseTokenization 的影响 Impact
Prompt Engineering压缩 token 数 → 降低成本、扩大 context 空间
RAG 系统Chunking 时要按 token 数切,不按字符数切
微调 Fine-tuning训练数据的 token 分布影响模型性能
多语言支持不同语言的 token 效率差异很大(英文 > 中文 > 某些小语种)

有趣彩蛋 / Fun Fact

GPT-4 的词汇表有 ~100,256 个 token(cl100k_base encoding)。

其中包括:整个英文单词、代码关键词,甚至 Python(带前置空格)和 Python不同的 token

空格有时候是 token 的一部分,这就是为什么 prompt 里的格式细节可能影响输出——模型在 token 层面感知到了差异。


Day 4 / 100 — AI 基础系列 AI Foundations

昨天 Day 3:AI News Roundup | 明天 Day 5:Embeddings — 语义的数学表达

Attention Heatmap

When the model processes it, it attends most strongly to animal.

Theanimaldidntcrossthestreetbecauseitwastootired
it →