logo
火山博客
导航

前端该怎么理解大模型?

2025-02-08
22阅读时间12分钟

前言

作为前端开发者,我们不需要深入理解大模型的底层原理(如 Transformer 架构、注意力机制等),但需要了解如何有效地使用它。

从前端视角来看,大模型可以被理解为:

  1. 一个智能的文本处理系统

    • 输入是文本(prompt)
    • 输出也是文本(completion)
    • 中间的处理过程被抽象为 token 序列的转换
  2. 一个 REST API 服务

    • 通过 HTTP 请求与模型交互
    • 需要处理请求限制、错误重试等
    • 考虑响应速度和用户体验
  3. 一个需要优化成本的资源

    • 按 token 计费
    • 需要合理管理 token 使用量
    • 优化提示词(prompt)以提高效率

这种理解方式让我们能够专注于如何在前端应用中更好地集成和使用大模型,而不是纠结于其内部实现细节。

两种方式:Chat(聊天)和 Completion(补全)

Chat 模式

Chat 模式是一种交互式对话方式,支持上下文理解。每次对话都包含完整的对话历史,使模型能够理解上下文并提供连贯的回答。

Role 角色系统

在 Chat 模式中,每条消息都需要指定角色(role),主要包括:

  • system: 用于设置模型的行为和角色定位
  • user: 用户发送的消息
  • assistant: 模型的回复消息
  • function: 函数调用的返回结果
  • tool: 工具调用的返回结果(在某些模型中替代了 function role)

其中 system、user 和 assistant 是最基础和常用的角色。function 和 tool 角色主要用于函数调用和工具集成场景,让模型能够调用外部功能并获取结果。

角色系统让模型能够理解对话中各方的身份,有助于生成更合适的回答。

对话上下文原理

模型之所以能"记住"之前的对话,是因为每次请求时我们都会发送完整的对话历史:

Javascript
const messages = [
  { role: "system", content: "你是一个友善的助手" },
  { role: "user", content: "你好" },
  { role: "assistant", content: "你好!有什么我可以帮你的吗?" },
  { role: "user", content: "请介绍一下自己" }
]
  • 每次新的对话都会追加到 messages 数组
  • 模型会综合分析整个对话历史来理解上下文
  • 但过长的对话历史会增加 token 消耗,建议适时清理

主要特点:

  • 支持多轮对话
  • 保持上下文连续性
  • 适合对话式应用场景
  • 使用 chat/completions API

示例API调用:

Bash
curl https://api.openai.com/v1/chat/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
  "model": "gpt-3.5-turbo",
  "messages": [
    {"role": "user", "content": "你好,请介绍一下自己"}
  ]
}'

Completion 模式

Completion 模式是早期的API形式,主要用于文本补全任务。

主要特点:

  • 单次输入输出
  • 没有对话上下文
  • 适合文本生成、补全场景
  • 使用 completions API

completions 的主要参数:

  • prompt: 输入的文本提示
  • max_tokens: 生成文本的最大长度
  • temperature: 控制输出的随机性(0-2之间,越大越随机)
  • top_p: 控制输出的多样性
  • n: 为同一输入生成多少个补全结果

Chat 与 Completions

在 API 路径 “chat/completions” 中:

  • Chat: 表示这是一个对话模式的接口
  • Completions: 负责将文本转换为模型可以理解的 token 序列,这个过程也称为 tokenization(分词)

Tokenization 处理流程

  1. Token 切分
  • 首先将文本切分成 token
  • 如 “Hello world” 可能被切分为 [“Hello”, “world”]
  1. Token 到 Token IDs 的映射
  • 每个 token 都对应词表(vocabulary)中的一个唯一数字 ID
  • 这个映射过程使用编码器(encoder)完成
  • 如 “Hello” => 15043, “world” => 2787, 也就是[15043, 2787]
  • 模型有固定大小的词表,包含几万到几十万个 token
  1. Token IDs 到 Embeddings
  • Token IDs 被转换为高维向量(通常是几百到几千维)
  • 这些向量称为 embeddings,包含了 token 的语义信息
  • 相似含义的词会有相似的 embedding 向量
  • 模型通过处理这些向量来理解文本含义

示例:

Token 与文本的关系

Token 是模型处理文本的基本单位:

  • Token 是由模型的分词算法(tokenizer)基于统计和压缩算法切分的基本单位
  • 不同模型使用不同的分词器:
    • GPT-3.5/4 系列使用 tiktoken 分词器: 1个token约等于3个英文字符,大概3/4个单词,100个token大约等于75个英文单词
    • Claude3.5-sonnet-200k 200k的token: 200k个token大约等于150k个英文单词,标准短篇小说5000单词
    • 相同的文本在不同分词器中可能会得到不同的 token 数量
  • 不同语言的 token 表现差异很大:
    • 英文单词通常会被切分成更小的部分,比如:
      • “backpack” 会被切分为 “back” 和 “pack” 两个token
      • “tokenization” 可能被切分为 “token”、“ization” 等多个token
    • 中文每个汉字通常对应一个token
    • 日语会将假名和汉字分别编码
    • 韩文可能会按音节切分
  • 空格、标点符号、表情符号等也是独立的token
  • 不能单纯用直觉判断文本会被切分成几个token,建议使用专门的工具(如tiktoken)来计算,或者使用 OpenAI 提供的在线 tokenizer 工具(https://platform.openai.com/tokenizer)

Token 限制:

  • 不同模型有不同的 token 限制(如 GPT-3.5-turbo 限制为 4k tokens)
  • token 计数包括输入(prompt)和输出(completion)
  • 在 Chat 模式中,历史对话记录也会占用 token 配额

Token 优化建议

为了更高效地使用 token 配额,可以:

  1. 使用简洁的表达方式

    • 使用伪代码而不是完整代码,如:
      // 完整写法(更多token)
      请帮我写一个 JavaScript 函数,实现数组去重,需要考虑各种边界情况,并添加详细的注释说明
      
      // 简洁写法(更少token)
      JS: arr.unique() impl
      
  2. 使用约定的格式符号

    • 用 + 表示添加
    • 用 - 表示删除
    • 用 -> 表示转换 例如:str + reverse -> array
  3. 精简上下文

    • 只保留必要的对话历史
    • 使用关键词代替完整句子
    • 删除重复或冗余信息
  4. 使用技术简写

    • fn 代替 function
    • arr 代替 array
    • obj 代替 object
    • impl 代替 implementation

这些优化可以在保持语义清晰的同时显著减少 token 使用量。

计算 token:

Javascript
// 使用 tiktoken 等库可以预估 token 数量
import { encoding_for_model } from "tiktoken";
const enc = encoding_for_model("gpt-3.5-turbo");
const tokenCount = enc.encode("你好世界").length;

示例API调用:

Bash
curl https://api.openai.com/v1/completions \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $OPENAI_API_KEY" \
-d '{
  "model": "text-davinci-003",
  "prompt": "写一个故事,主题是:",
  "max_tokens": 100
}'

Token 补全参数

在使用模型生成文本时,有几个重要的参数会影响 token 的补全效果:

  1. Temperature(温度)

    • 控制输出的随机性,取值范围 0-2
    • 值越高,生成的文本越随机/创造性
    • 值越低,生成的文本越确定/保守
    • 0 表示始终选择最可能的 token
    • 1 是默认值,保持正常的随机性

    例如,对于提示词"写一个故事开头":

    temperature = 0:
    "从前有一个小女孩,她住在一个小村庄里。"(最常见/安全的开头)
    
    temperature = 1:
    "在霓虹闪烁的赛博城市里,一个机器人正在学习做梦。"(有创意但仍合理)
    
    temperature = 2:
    "紫色的风吹过钢琴键,奏响了时间的咖啡香。"(非常创意/可能不够连贯)
    
  2. Top_p(核采样)

    • 控制每次选择 token 时考虑的候选集范围
    • 取值范围 0-1
    • 0.1 表示只考虑概率前 10% 的 token
    • 通常建议不要同时调整 temperature 和 top_p

    例如,对于提示词"今天天气":

    top_p = 0.1:
    "今天天气很好/不错/晴朗"(只从最高概率的选项中选择)
    
    top_p = 0.9:
    "今天天气有点阴沉沉的,好像要下雨了"(考虑更多可能性)
    
  3. Frequency_penalty(频率惩罚)

    • 降低已经出现过的 token 的概率
    • 可以避免重复生成相同的内容
    • 正值会降低重复,负值会增加重复

    例如,对于提示词"列举水果":

    frequency_penalty = 0:
    "苹果、香蕉、苹果、梨子、苹果"(可能重复)
    
    frequency_penalty = 1:
    "苹果、香蕉、梨子、橙子、葡萄"(避免重复)
    
  4. Presence_penalty(存在惩罚)

    • 降低模型对已出现主题的关注度
    • 有助于生成更多样化的内容
    • 正值会鼓励谈论新主题,负值会保持在当前主题

    例如,对于提示词"介绍一下猫":

    presence_penalty = 0:
    "猫是一种可爱的宠物,猫喜欢吃鱼,猫会抓老鼠..."(持续谈论猫的特点)
    
    presence_penalty = 1:
    "猫是一种宠物,它们在古埃及被视为神明,现代社会中宠物市场..."(会引入相关但不同的主题)
    

例如,对于提示词"我喜欢吃",模型可能预测下一个 token 的概率分布:

  • “饭” (0.3)
  • “菜” (0.2)
  • “水果” (0.15)
  • “零食” (0.1)
  • …其他选项

这些参数的调整会直接影响模型如何从这些候选 token 中进行选择。

前端代码补全示例

以下是一个前端代码补全的综合示例,展示不同参数如何影响补全效果:

提示词:

Javascript
function unique(arr) {
  return arr.fil
  1. Temperature(温度)对比:
Javascript
// temperature = 0:最常见的补全方式
// 因为 filter + indexOf 是最常用的数组去重实现
function unique(arr) {
  return arr.filter((item, index) => arr.indexOf(item) === index);
}

// temperature = 1:会尝试其他可能的补全
// 使用 lastIndexOf 是另一种常见的去重思路
function unique(arr) {
  return arr.filter((value, index, self) => self.lastIndexOf(value) === index);
}
  1. Top_p(核采样)对比:
Javascript
// top_p = 0.1:只补全最高概率的实现
// filter + indexOf 的组合在代码库中出现频率最高
function unique(arr) {
  return arr.filter((item, index) => arr.indexOf(item) === index);
}

// top_p = 0.9:会考虑其他常见的补全方式
// includes 虽然不是最常用,但在实际开发中也很常见
function unique(arr) {
  return arr.filter((item, i, array) => !array.slice(0, i).includes(item));
}
  1. Frequency_penalty(频率惩罚)对比:
Javascript
// frequency_penalty = 0:可能重复使用相同的方法
// 不惩罚重复,所以多次使用了 arr 和 index
function unique(arr) {
  return arr.filter((item, index) => arr.indexOf(item) === index);
}

// frequency_penalty = 1:倾向于使用不同的方法
// 通过引入 seen 变量避免重复使用数组方法
function unique(arr) {
  return arr.filter((item, i) => {
    const seen = arr.slice(0, i);
    return !seen.includes(item);
  });
}
  1. Presence_penalty(存在惩罚)对比:
Javascript
// presence_penalty = 0:保持在基本的过滤逻辑
// 只关注基本的去重功能
function unique(arr) {
  return arr.filter((item, index) => arr.indexOf(item) === index);
}

// presence_penalty = 1:引入新的处理逻辑
// 添加了首项处理和 some 方法的新思路
function unique(arr) {
  return arr.filter((item, i) => {
    if (i === 0) return true;
    return !arr.slice(0, i).some(prev => prev === item);
  });
}

通过这些对比,我们可以看到代码补全时:

  • Temperature 控制补全的创新程度:从最常见的 indexOf 到较少使用的 lastIndexOf
  • Top_p 决定是否考虑其他常见实现:从最高频的实现到其他可行方案
  • Frequency_penalty 影响代码的重复度:从重复使用方法到引入新变量
  • Presence_penalty 控制是否引入新思路:从单一逻辑到多个处理步骤

在实际开发中,我们可以根据需求调整这些参数:

  • 需要最常见补全:低 temperature + 低 top_p
  • 需要更多选择:提高 top_p 来获取其他常见实现
  • 避免重复模式:增加 frequency_penalty 来使用不同的方法
  • 需要新思路:提高 presence_penalty 来引入新的处理逻辑

总结:前端视角下的大模型

从前端开发者的角度,我们可以这样理解和使用大模型:

  1. 作为智能 API 服务

    • 通过调整参数来控制输出
    • 合理使用 token 来优化成本
    • 注意请求限制和错误处理
  2. 作为开发助手

    • 代码补全:从简单补全到创新实现
    • 文档生成:API 文档、注释等
    • 代码优化:性能、可读性建议
  3. 最佳实践

    • 合理设置 system prompt 来定义助手角色
    • 使用简洁的 prompt 来节省 token
    • 根据场景调整参数组合
    • 做好用户体验(loading、打字机效果等)
  4. 注意事项

    • API Key 的安全管理
    • Token 用量的监控和优化
    • 请求失败的重试机制
    • 响应结果的合理展示

通过这种理解方式,我们可以更好地在前端项目中集成和使用大模型,让它成为提升开发效率的得力助手。

2024 © Powered by
hsBlog
|
后台管理