从 Tokenization 到 Embedding:用 Node.js 搞懂大模型为什么先“分词”再“向量化”

很多人刚开始学习大模型时,会先从 Prompt、Agent、RAG、Vibe Coding 这些更直观的东西入手。这没有问题,AI 工具本来就应该先用起来。

但如果想真正理解下面这些问题,就必须补上两个基础概念:

  • 为什么大模型按 token 计费?
  • 为什么上下文窗口不是按字数算?
  • 为什么长文档不能一股脑塞进 Prompt?
  • 为什么 RAG 要先做 Embedding?
  • 为什么"关键词相同"和"语义相似"不是一回事?

这两个基础概念就是:

txt 复制代码
Tokenization:把文本切成 token,并映射成 token id

Embedding:把文本映射成高维向量,用数学方式表达语义

csharp 复制代码
下面用两个 Node.js 示例把它们讲清楚。

## 环境准备

需要安装 3 个依赖:

```bash
pnpm add js-tiktoken openai dotenv

package.json 中的依赖大概是这样:

json 复制代码
{
  "dependencies": {
    "dotenv": "^17.4.2",
    "js-tiktoken": "^1.0.21",
    "openai": "^6.44.0"
  }
}

如果要调用 Embedding 接口,还需要准备 .env

env 复制代码
DASHSCOPE_API_KEY=你的_API_Key

注意:.env 不要提交到 Git 仓库。真实 API Key 一旦泄露,应该立刻去平台控制台轮换或删除。

一、为什么大模型要先做 Tokenization

大模型不能直接处理中文、英文、标点、空格这些原始字符串。

从工程视角看,模型最终处理的是数字。文本进入模型前,需要先经过 tokenizer,被转换成一串离散编号,也就是 token ids。

普通文本生成流程可以简化成这样:

txt 复制代码
文本 Prompt
  -> Tokenizer
  -> token ids
  -> 模型内部的向量表示
  -> Transformer 计算
  -> 输出 token ids
  -> 解码成文本

对于常见的自回归语言模型来说,它的核心训练目标可以粗略理解为:

txt 复制代码
根据前面的 token,预测下一个 token

注意,这里说的是"下一个 token",不是严格意义上的"下一个词"。

因为 token 不一定等于自然语言里的一个词。它可能是一个完整英文单词,也可能是单词的一部分,可能是一个中文字符,也可能是多个中文字符,还可能是标点、空格、换行或代码符号。

二、用 js-tiktoken 看看文本如何变成 token

下面是一个最小可运行示例:

js 复制代码
import { getEncoding } from "js-tiktoken";

const enc = getEncoding("cl100k_base");

const text = "Hello tiktoken! hello world";

const tokens = enc.encode(text);
const decodedText = enc.decode(tokens);

console.log("原始文本:", text);
console.log("Token IDs:", tokens);
console.log("Token 数量:", tokens.length);
console.log("解码结果:", decodedText);

可能输出:

txt 复制代码
原始文本: Hello tiktoken! hello world
Token IDs: [ 9906, 87272, 5963, 0, 24748, 1917 ]
Token 数量: 6
解码结果: Hello tiktoken! hello world

这里最关键的点是:

txt 复制代码
"Hello tiktoken! hello world"

被 tokenizer 转成了:

txt 复制代码
[9906, 87272, 5963, 0, 24748, 1917]

也就是说,模型真正处理的不是原始字符串,而是 token id 数组。

三、cl100k_base 是什么

示例里用的是:

js 复制代码
const enc = getEncoding("cl100k_base");

cl100k_base 是一种 tokenizer 编码方案,可以理解为一套 token 和 token id 的映射规则。

这里要特别注意:

txt 复制代码
cl100k_base 不等于所有大模型的 tokenizer

它可以用来学习 tokenization,也常用于估算部分 OpenAI 模型的 token 数。但不同模型、不同厂商可能使用不同 tokenizer。

所以工程实践里要区分三件事:

  • 学习原理:可以用 js-tiktoken
  • 粗略估算:尽量选目标模型对应的 tokenizer
  • 精确计费:以 API 返回的 usage 或厂商账单为准

四、Token 为什么重要

Token 不是一个抽象概念,它直接影响真实开发。

第一,影响上下文长度。

大模型的上下文窗口通常按 token 计算,不是按字符数计算。系统提示词、用户输入、历史对话、工具调用结果、RAG 检索内容,都会消耗 token。

第二,影响 API 成本。

很多模型的计费方式可以简化理解为:

txt 复制代码
输入 token + 输出 token = 总 token 消耗

第三,影响回答质量。

如果上下文塞得太满,模型可能看不到关键内容,或者被大量无关内容干扰。做 Agent、RAG、代码生成时,控制 token 是很实际的工程问题。

所以不要把 token 简单理解成"字数"。它更像是模型的输入输出预算单位。

五、Embedding 解决什么问题

Tokenization 解决的是:

txt 复制代码
文本如何变成模型可以处理的离散编号

Embedding 解决的是另一个问题:

txt 复制代码
文本如何变成可以计算语义相似度的向量

比如下面两句话:

txt 复制代码
Andrej Karpathy LLM Tokenization 分词原理
卡帕西讲解大模型 BPE 字词分词

它们字面上并不一样,甚至中英文混杂,但语义上很接近。

再看这句:

txt 复制代码
今天天气晴朗,适合出门散步

它和前两句主题明显不同。

如果只做字符串匹配,很难知道"Karpathy"和"卡帕西"有关,也很难知道"Tokenization"和"BPE 字词分词"有关。

Embedding 的价值就在这里:它把文本映射到高维向量空间,让我们可以用数学方法比较语义接近程度。

六、完整 Embedding 示例

下面是一个更适合学习和复习的完整版本:

js 复制代码
import OpenAI from "openai";
import dotenv from "dotenv";

dotenv.config();

const client = new OpenAI({
  apiKey: process.env.DASHSCOPE_API_KEY,
  baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1"
});

async function getEmbedding(text) {
  const res = await client.embeddings.create({
    model: "text-embedding-v4",
    input: text,
    dimensions: 1024
  });

  return res.data[0].embedding;
}

function cosineSimilarity(vecA, vecB) {
  if (vecA.length !== vecB.length) {
    throw new Error("Vectors must have the same length");
  }

  let dot = 0;
  let magA = 0;
  let magB = 0;

  for (let i = 0; i < vecA.length; i++) {
    dot += vecA[i] * vecB[i];
    magA += vecA[i] ** 2;
    magB += vecB[i] ** 2;
  }

  if (magA === 0 || magB === 0) {
    return 0;
  }

  return dot / (Math.sqrt(magA) * Math.sqrt(magB));
}

async function run() {
  const text1 = "Andrej Karpathy LLM Tokenization 分词原理";
  const text2 = "卡帕西讲解大模型 BPE 字词分词";
  const text3 = "今天天气晴朗,适合出门散步";

  const [vec1, vec2, vec3] = await Promise.all([
    getEmbedding(text1),
    getEmbedding(text2),
    getEmbedding(text3)
  ]);

  console.log("向量维度:", vec1.length);
  console.log("text1 vs text2:", cosineSimilarity(vec1, vec2));
  console.log("text1 vs text3:", cosineSimilarity(vec1, vec3));
}

run().catch(console.error);

这段代码做了 4 件事:

  • 调用 Embedding 模型,把文本转成 1024 维向量
  • Promise.all 并发请求 3 段文本的向量
  • 用余弦相似度比较两个向量的方向是否接近
  • 对比"语义相近"和"语义不相关"的文本得分

正常情况下,text1 vs text2 应该比 text1 vs text3 更高。

七、代码要点拆解

先看客户端初始化:

js 复制代码
const client = new OpenAI({
  apiKey: process.env.DASHSCOPE_API_KEY,
  baseURL: "https://dashscope.aliyuncs.com/compatible-mode/v1"
});

这里使用的是 openai SDK,但 baseURL 指向 DashScope 的 OpenAI 兼容接口。也就是说,SDK 的调用方式像 OpenAI,但实际请求发给阿里云 DashScope。

再看 Embedding 调用:

js 复制代码
const res = await client.embeddings.create({
  model: "text-embedding-v4",
  input: text,
  dimensions: 1024
});

关键参数有 3 个:

  • model:使用哪个 Embedding 模型
  • input:要向量化的文本
  • dimensions:希望返回的向量维度

返回值里真正需要的是:

js 复制代码
res.data[0].embedding

它是一个浮点数数组,类似:

js 复制代码
[
  0.0123,
  -0.0345,
  0.0876
]

实际长度由 dimensions 决定。示例里设置为 1024,所以返回的是 1024 维向量。

八、余弦相似度是什么

拿到两个向量以后,怎么判断它们语义是否接近?

常见方法是余弦相似度:

txt 复制代码
cosine_similarity(A, B) = A·B / (|A| * |B|)

直觉上,它比较的是两个向量的方向是否接近。

代码里这部分是在算点积:

js 复制代码
dot += vecA[i] * vecB[i];

这部分是在算两个向量的模长:

js 复制代码
magA += vecA[i] ** 2;
magB += vecB[i] ** 2;

最后得到相似度:

js 复制代码
return dot / (Math.sqrt(magA) * Math.sqrt(magB));

一般可以粗略理解为:

  • 越接近 1:语义越相似
  • 接近 0:相关性弱
  • 小于 0:方向相反,通常表示不相似

但业务里不要机械套阈值。不同模型、不同数据集、不同场景下,0.750.80.9 的含义可能完全不同。阈值要用自己的业务数据调。

九、Tokenization 和 Embedding 的关系

很多人会问:

txt 复制代码
既然 Embedding 可以直接把文本变成向量,那还需要 Tokenization 吗?

需要。

因为模型仍然不能直接处理原始字符串。文本进入 Embedding 模型前,通常也会先经过 tokenizer。只是当我们调用 Embedding API 时,这一步被服务端封装了,开发者不一定直接看到。

可以这样理解:

txt 复制代码
Tokenization:把文本变成离散 token ids
Embedding:把文本或 token 序列变成连续向量表示

一个偏编码,一个偏语义表达。

十、为什么 RAG 离不开 Embedding

RAG,全称 Retrieval-Augmented Generation,中文通常叫检索增强生成。

典型流程是:

txt 复制代码
文档
  -> 切分 chunk
  -> 生成 embedding
  -> 存入向量数据库

用户问题
  -> 生成 query embedding
  -> 向量相似度检索
  -> 找到相关 chunk
  -> 拼进 Prompt
  -> 交给 LLM 生成回答

Embedding 在这里负责"找资料",LLM 负责"组织语言生成答案"。

如果只靠关键词匹配,会遇到很多问题:

  • 用户说"卡帕西",资料里写的是 Karpathy
  • 用户说"大模型分词",资料里写的是 Tokenization
  • 用户问"怎么计算语义接近程度",资料里写的是 cosine similarity

这些场景里,字面不一定匹配,但语义向量可能能找回来。

十一、为什么长文档要切块

RAG 不是把所有文档都塞给大模型。

原因很直接:

txt 复制代码
上下文窗口有限,token 成本也有限。

所以通常会先把文档切成 chunk,再分别生成 Embedding。

切块的价值有两个:

  • 降低单次输入的 token 压力
  • 让检索系统更精确地召回相关片段

但切块也不是越小越好。

切得太大,检索结果可能不精准;切得太小,语义上下文可能丢失。实际项目里通常要结合文档结构、段落长度、业务问题类型来调。

十二、学习路线建议

如果你是应用开发者,可以按这个顺序学习:

  1. 先用 AI 工具解决真实问题,比如写代码、总结资料、生成文案。
  2. 理解 Prompt、上下文窗口、输入 token、输出 token。
  3. 学 Tokenization,搞懂文本为什么会变成 token id。
  4. 学 Embedding,搞懂语义相似度、向量检索、RAG。
  5. 做一个完整作品,比如知识库问答、客户资料检索、文档助手或代码库问答。

这个顺序比一上来啃 Transformer 论文更适合大多数应用开发者。

总结

Tokenization 解决的是:

txt 复制代码
文本如何变成 token id

Embedding 解决的是:

txt 复制代码
文本如何变成可以计算语义相似度的向量

记住几个关键点:

  • token 不等于字符,也不等于自然语言里的词
  • 模型通常预测的是下一个 token,不是严格意义上的下一个词
  • cl100k_base 是一种 tokenizer 编码方案,不代表所有模型都使用它
  • Embedding 向量是高维浮点数组,维度取决于模型和参数
  • 余弦相似度可以比较语义接近程度,但阈值要结合业务数据调
  • RAG 的核心不是"把文档全塞给模型",而是"先检索相关片段,再让模型生成回答"
相关推荐
JJJennie77725 分钟前
当AI开始学会欺骗
网络·人工智能
糖果店的幽灵35 分钟前
大模型测评DeepEval快速入门-安全与通用指标详解
人工智能·安全·langgraph·大模型测评·deepeval
江畔柳前堤6 小时前
roLabelImg 详细安装教程
开发语言·人工智能·后端·云原生
阿里云大数据AI技术7 小时前
分链路差异化设计的DSP准实时数仓|钛动科技基于阿里云实时计算 Flink 版 + DLF Paimon + EMR Serverless StarRocks 的实践
人工智能·flink
陕西企来客7 小时前
2026年7月AI智能搜索曝光趋势研判
大数据·人工智能·机器学习·ai智能搜索曝光
阿里云大数据AI技术7 小时前
从算力到智能体,面向 Agentic AI 的基础设施演进
人工智能·agent
hangyuekejiGEO8 小时前
GEO技术服务选型指南
大数据·人工智能·python
阿里云大数据AI技术8 小时前
EMR Serverless Spark AI Function 的双维降本实践
人工智能·sql·spark
维基框架8 小时前
GitHub源码处理提速 一趟扫描反而更慢
人工智能·github
冬奇Lab8 小时前
代码库知识库系列(05):向量检索 vs 知识图谱——加了调用图并没有变更好
人工智能