Manshu LLM:从零实现的字符级 Transformer 语言模型#
#LLM; #Transformer; #PyTorch; #FastAPI
从零实现 LLM 各结构(嵌入、GQA、RoPE、滑动窗口、SwiGLU、RMSNorm、LM Head),并提供本地与 HTTP 推理服务。
项目仓库:(暂无,本地项目 manshu_llm)
下文流程图概括了「数据准备 → 训练 → 推理/服务」的主链路以及模型前向的数据流。
graph TD
A[Tiny Shakespeare] --> B[download_data]
B --> C[tokenize_data]
C --> D[train.npy val.npy vocab]
D --> E[train.py]
E --> F[checkpoints]
F --> G[infer.py]
F --> H[serve.py]
H --> I["/generate"]
H --> J["/health"]
K[Input IDs] --> L[Token Embedding]
L --> M[Transformer Block x N]
M --> N[RMSNorm LM Head]
N --> O[Logits]
项目概述#
Manshu LLM(版本 v0.1.0)是个人代表项目之一,核心强调两点:从零实现 LLM 的各个结构(嵌入、GQA、RoPE、滑动窗口注意力、SwiGLU、RMSNorm、LM Head 等),以及提供完整推理服务(本地脚本 + FastAPI HTTP API,支持贪心/采样解码与健康检查)。项目承担「数据准备 → 训练 → 检查点管理 → 推理服务」全链路,便于展示对 Transformer 与 LLM 工程化的深度理解。采用字符级(character-level)建模,无需 BPE 或 WordPiece,词汇表由数据统计得到;默认使用 Tiny Shakespeare 数据集,适合小规模实验与教学。
技术栈:PyTorch 实现 Decoder-only Transformer,模型包含 GQA(分组查询注意力)、RoPE(旋转位置编码)、滑动窗口注意力、SwiGLU FFN、Pre-RMSNorm,与 Llama 等主流架构对齐;训练使用 AdamW、Cosine/Linear 学习率调度与梯度累积,配置由 YAML(configs/model.yaml、train.yaml、data.yaml)统一管理;推理支持贪心解码与温度 / Top-K / Top-P 采样;对外服务采用 FastAPI + Uvicorn,提供 POST /generate(文本生成)与 GET /health(健康检查),API 文档位于 /docs、/redoc;支持 Docker 容器化部署(docker/ 目录下 Dockerfile、docker-compose.yml、build.sh)。项目目录结构:根目录下 llm/(model、data、training、inference、utils)、scripts/(download_data、tokenize_data、train、infer、check_model)、configs/、data/(raw、processed、vocab)、checkpoints/、serve.py。
项目亮点#
- LLM 各结构从零实现:不依赖 Hugging Face 等现成模型库,手写 Token Embedding、GQA、RoPE、滑动窗口注意力、SwiGLU、RMSNorm、LM Head 等每一环,便于掌握数学与实现细节,与 Llama 等主流架构对齐。
- 完整推理服务:提供本地推理(
infer.py,贪心/温度/Top-K/Top-P 采样)与 HTTP 推理服务(FastAPIPOST /generate、GET /health,Pydantic 请求响应、CORS、Docker 部署),可直接对外提供文本生成能力。 - 全栈闭环:覆盖数据准备 → 训练 → 检查点管理 → 推理服务,单项目即可演示「从语料到可调用的文本生成服务」的完整工程路径。
- 可复现与可配置:YAML 分离模型/训练/数据配置,训练与推理共用同一套键名与加载逻辑,便于复现实验与切换超参。
- 字符级友好:词汇表小、实现简单,在 Tiny Shakespeare 等小语料上快速验证架构与超参,适合教学与面试时讲解。
个人角色与产出#
在本项目中负责模型、训练、推理与服务的全栈开发,重点强调:
- 实现 LLM 各结构:从零实现 Decoder-only Transformer 的 Token Embedding、GroupedQueryAttention、RoPE、滑动窗口注意力、SwiGLU、RMSNorm、LM Head,含权重初始化与 tie_word_embeddings 等配置;每一子模块手写,无现成模型库依赖。
- 提供推理服务:实现本地推理(
infer.py+ greedy_decode / sample_decode,支持 temperature、top_k、top_p)与 HTTP 推理服务(FastAPIPOST /generate、GET /health,Pydantic 请求响应、lifespan 内模型加载、CORS、Docker 部署),可直接对外提供文本生成与健康检查。 - 数据与训练:CharTokenizer、CharDataset、collate_fn;Trainer(梯度累积、检查点管理、验证评估)、AdamW、学习率调度、损失与优化器封装。
| 类别 | 技术选型 | 用途 |
|---|---|---|
| 深度学习框架 | PyTorch | 模型实现、训练与推理 |
| 模型架构 | Decoder-only Transformer | 字符级语言模型主干 |
| 注意力 | GQA + RoPE + 滑动窗口 | 分组查询、位置编码、长上下文优化 |
| FFN / 归一化 | SwiGLU + RMSNorm | 前馈网络与 Pre-Norm |
| 训练 | AdamW + Cosine/Linear LR + 梯度累积 | 优化器与学习率调度 |
| 配置 | YAML(configs/) | 模型、训练、数据配置 |
| API 服务 | FastAPI + Uvicorn | 文本生成与健康检查 |
| 部署 | Docker(可选) | 容器化运行 |
项目背景#
从零实现语言模型有助于深入理解 Transformer 的每一环:嵌入、注意力、位置编码、FFN、损失与采样。仅调用现成库难以掌握 GQA、RoPE、滑动窗口等细节及其对显存与长序列的影响。本项目从零实现 LLM 各结构并将「模型结构 → 数据管道 → 训练循环 → 推理服务(本地 + HTTP API)」串联为可运行、可配置的一体化工程,便于实验不同层数、窗口大小与采样策略;适合作为深度理解 Transformer 与 LLM 工程化的代表项目,在简历或作品集中突出「实现各结构 + 提供推理服务」的全栈能力。
为何采用字符级? 字符级建模无需 BPE/WordPiece,词汇表由数据统计得到、体量小,实现与调试简单,在 Tiny Shakespeare 等小语料上可快速验证架构与超参;若后续扩展子词分词,只需替换 Tokenizer 与词汇表路径即可复用现有训练与推理管线。
采用字符级而非子词/词级,在本项目中优先保证可解释性与实验迭代速度;若需面向更大语料与更强效果,可在同一工程内接入子词分词与更大规模配置。
系统架构与选型#
整体为单机训练与单进程服务架构。
数据流:原始文本(如 data/raw/tiny_shakespeare.txt)经 tokenize_data.py 读取 → CharTokenizer.build_vocab(text) 统计字符得到词汇表(<unk>=0 + 按频率排序的字符)→ tokenizer.encode(text) 得到一维 token ID 数组 → 按 val_ratio 切分为训练/验证两段 → 保存为 train.npy、val.npy(dtype int32)与 char_vocab.json。
训练流:CharDataset(data_path, tokenizer, context_window) 从一维数组上做滑动窗口,每个样本取长度为 context_window+1 的连续片段,前 context_window 个作为 input_ids、后 context_window 个作为 target_ids(即目标为输入右移一位);collate_fn 将同长度样本直接 stack 成 (batch_size, seq_len),若长度不一致则用 pad_token_id=0 填充;每个 batch 送入模型前先根据 seq_len 构造因果掩码 (L, L),前向得到 logits (B, L, vocab_size),损失为 CrossEntropyLoss(logits, target_ids)(支持 ignore_index=-1);梯度累积步数内只 backward,达到 gradient_accumulation_steps 时执行梯度裁剪、optimizer.step、scheduler.step;每 eval_steps 在验证集上评估,若验证损失优于历史则保存 best 检查点,每 save_steps 保存定期检查点并受 save_total_limit 限制。
推理/服务流:infer.py 或 serve 启动时通过 load_all_configs(config_dir) 读取三份 YAML,从 data 配置取 vocab_dir 得到 char_vocab.json 路径,加载 CharTokenizer;用 model 配置(并注入 vocab_size=tokenizer.vocab_size)构造 Transformer,再 load_model_only(model, checkpoint_path) 加载权重;推理时对 prompt 编码(空 prompt 则用 [[0]] 即 <unk> 作为起始),按 temperature 选择 greedy_decode 或 sample_decode,解码为文本。服务端在 FastAPI 的 lifespan 内完成上述加载,请求进入后直接调用生成逻辑,当前为一次性返回完整结果(非流式)。
选型考量:PyTorch 便于从零实现各模块并做梯度检查与形状调试。GQA 减少 KV 头数,在相同参数量下支持更长序列或更大 batch。RoPE 将位置信息编码进 Q/K,无需显式位置嵌入表。滑动窗口限制注意力范围,降低长序列的显存与计算量。SwiGLU 与 RMSNorm 为当前主流 LLM 标配,利于收敛与数值稳定。FastAPI 与 Pydantic 便于定义请求/响应模型并自动生成 OpenAPI 文档,便于联调与对接前端。
架构对齐:本项目的注意力与 FFN 设计(GQA、RoPE、滑动窗口、SwiGLU、RMSNorm)与 Llama、Mistral 等开源 LLM 一致,便于将在此项目中的理解迁移到更大规模模型与现成库(如 Hugging Face)的调参与扩展。
关键技术选型理由#
-
GQA(分组查询注意力)
多个 Query 头共享一组 Key/Value 头,在保持表达力的同时减少 KV 缓存与计算量,便于在有限显存下增大 batch 或序列长度,与当前开源 LLM 实践一致。配置项为
num_attention_heads与num_key_value_heads(须满足 num_heads 能被 num_kv_heads 整除)。 -
RoPE(旋转位置编码)
将位置信息编码进 Q、K 的旋转矩阵,相对位置关系由夹角表示,支持外推与更长上下文,且无需在 embedding 层维护位置嵌入表,与 GQA、滑动窗口兼容。配置项为
max_position_embeddings、rope_theta(默认 10000.0),在 Block 内按head_dim构造 RoPE 并传入注意力层。 -
滑动窗口注意力
每位置仅关注局部窗口内的 token,将注意力复杂度从序列长度的平方降为与窗口大小相关,在长序列训练与推理时显著降低显存与计算。实现上:先算 QK^T 得到 scores,再调用
apply_sliding_window_mask;sliding_window_overlap=True时为对称窗口(位置 i 可关注 [max(0, i-w/2), min(L, i+w/2+1)]),False时为非对称因果窗口([max(0, i-w+1), i+1]);窗口掩码与因果下三角掩码做逻辑与后,对 scores 中不可见位置置 -inf,再 softmax 与 V 相乘。 -
SwiGLU + RMSNorm
SwiGLU 通过门控与上投影的逐元素乘增强 FFN 表达(gate_proj → SiLU、up_proj → 逐元素乘 → down_proj);RMSNorm 替代 LayerNorm 减少计算与依赖,Pre-Norm 结构(每子层先 norm 再子层再残差)利于深层训练稳定性,与 Llama 等架构一致。
-
YAML 配置 + 统一加载
模型、训练、数据参数分离到 configs/model.yaml、train.yaml、data.yaml,通过
load_all_configs(config_dir)统一加载为字典(key 为 “model”、“train”、“data”),训练与推理/服务共用同一套路径与键名,便于复现实验与切换设备、步数、窗口大小等超参。
功能模块#
健康检查机制#
服务提供 GET /health,用于存活探测与负载均衡:
- 响应体(Pydantic 模型 HealthResponse):
status(healthy / model_not_loaded)、model_loaded(布尔)、device(如 cuda:0 或 cpu)、model_info(可选,含 num_layers、hidden_size、total_params、vocab_size) - 启动:lifespan 内依次加载配置、分词器、模型与检查点;任一步失败则抛出异常,服务不启动
- 异常:若运行中模型未加载,
POST /generate返回 503,detail 为「模型未加载」
数据准备#
- download_data.py:从 Karpathy 的 char-rnn 仓库下载 Tiny Shakespeare 原始文本,保存到
data/raw/tiny_shakespeare.txt,并打印字符数(URL:https://raw.githubusercontent.com/karpathy/char-rnn/master/data/tinyshakespeare/input.txt)。 - tokenize_data.py:从
configs/data.yaml读取val_ratio(默认 0.1);读取原始文本;实例化CharTokenizer()并build_vocab(text)(Counter 统计字符,<unk>=0,其余按频率排序);保存词汇表到data/vocab/char_vocab.json;encode(text)得到整篇 token ID 列表;按比例切分为训练/验证两段,转为 int32 的 numpy 数组,保存为data/processed/train.npy、data/processed/val.npy。脚本内写死输入输出路径,与「先 download 再 tokenize」的流程一致。
CharTokenizer(llm/data/tokenizer.py):char_to_id、id_to_char、vocab_size;build_vocab(texts) 从文本构建词汇表;encode(text) 逐字符查表得到 ID 列表,未登录字符使用 <unk>(id 0);decode(ids) 将 ID 列表还原为字符串;save_vocab/load_vocab 读写 JSON。词汇表 JSON 格式为字符到 id 的映射。
输出产物:train.npy、val.npy(一维 int32 数组)、char_vocab.json(字符 → id)。数据统计(字符数、token 数、训练/验证比例、词汇表大小)在 tokenize_data 末尾打印。
模型结构#
- llm/model/transformer.py:主模型。输入
input_ids(batch_size, seq_len),经 TokenEmbedding 得到 (B, L, hidden_size),过 N 个 TransformerBlock,再经最后一层 RMSNorm,最后通过 LM Head 或共享的 embedding 转置得到 logits (B, L, vocab_size)。支持tie_word_embeddings:为 True 时无独立 lm_head,输出为F.linear(x, embedding.weight)。 - llm/model/block.py:单层 TransformerBlock。先
attn_norm(x)→ GQA(带 RoPE、sliding_window、mask)→ 残差;再ffn_norm(x)→ FFN(SwiGLU)→ 残差。输入输出形状均为 (B, L, hidden_size)。 - llm/model/attention.py:
GroupedQueryAttention(Q 头独立,K/V 头共享并 repeat 以匹配 Q 头数)、MultiHeadAttention;scaled_dot_product_attention中先算 scores,再应用滑动窗口掩码(若配置)、再应用因果/通用 mask,softmax 后与 V 相乘。create_causal_mask(seq_len)返回下三角布尔掩码;apply_sliding_window_mask(scores, window_size, seq_len, overlap)返回与因果结合后的窗口掩码并写入 scores。 - llm/model/ffn.py:SwiGLU:gate_proj(x) → SiLU、up_proj(x) → 逐元素乘 → down_proj,输出与输入 hidden_size 一致。
- llm/model/rope.py:RoPE 按 head_dim、max_seq_len、theta 构造,对 Q、K 应用旋转位置编码。
- llm/model/norm.py:RMSNorm(dim, eps),forward 为 x / sqrt(mean(x^2)+eps) * weight。
- llm/model/embedding.py:Token Embedding,vocab_size → hidden_size。
核心特性:Pre-Norm(每个子层前 RMSNorm)、GQA、RoPE、滑动窗口(对称/非对称可选)、SwiGLU、RMSNorm、词嵌入与 LM Head 绑定可选(tie_word_embeddings)。权重初始化通过 llm.utils.init.apply_llm_init 可选启用(init_weights、init_std 在 model.yaml)。
训练流程#
- scripts/train.py:加载 configs(model、train、data),构建 CharDataset(train_path, tokenizer, context_window)、CharDataset(val_path, …),DataLoader 使用
collate_fn(来自 llm.data.collate);实例化 Transformer(config 中注入 vocab_size)、Trainer;执行 Trainer.train()。 - llm/training/trainer.py:
train_step(batch):batch 为 (input_ids, target_ids),形状均为 (batch_size, seq_len);创建因果掩码 (seq_len, seq_len);logits = model(input_ids, mask=causal_mask);loss = criterion(logits, target_ids),损失先除以 gradient_accumulation_steps 再 backward;当 (global_step+1) % gradient_accumulation_steps == 0 时执行梯度裁剪(max_grad_norm,默认 1.0)、optimizer.step、zero_grad、scheduler.step;global_step 每步加 1。evaluate(max_batches):在 val_loader 上无梯度前向,对 loss 取平均。save_checkpoint(is_best):通过 utils.checkpoint.save_checkpoint 写入文件;is_best 时命名为 best_model_step_.pt 并删除旧 best;否则命名为 checkpoint_step_.pt,并维护 saved_checkpoints 队列,超过 save_total_limit 时删除最旧文件。训练循环:按 num_epochs 遍历,每 batch 执行 train_step;每 eval_steps 做 evaluate,若验证损失优于 best_val_loss 则 save_checkpoint(is_best=True);每 save_steps 且本步未保存 best 则 save_checkpoint(is_best=False);支持 max_steps 提前终止。 - llm/training/optim.py:AdamW(learning_rate、weight_decay、beta1、beta2、eps);学习率调度器可选 cosine、linear、constant,含 warmup_steps。
- llm/training/loss.py:CrossEntropyLoss(logits, targets),将 logits 与 targets 展平后调用 F.cross_entropy,支持 ignore_index(默认 -1)。
CharDataset:__len__ = len(data) - context_window;__getitem__(idx) 取 data[idx:idx+context_window+1],前 context_window 为 input_ids,后 context_window 为 target_ids(即目标为输入右移一位),返回两个一维 LongTensor。
检查点:best_model_step_.pt(验证损失最优,仅保留最新一个)、checkpoint_step_.pt(定期,数量受 save_total_limit 限制),均保存在 train.yaml 中的 checkpoint_dir(默认 checkpoints)。
推理与采样#
- llm/inference/generate.py:
greedy_decode(model, input_ids, max_length, device, stop_token_ids):每步对当前 generated_ids 做前向,取 logits[:, -1, :] 的 argmax 作为下一 token,拼接到序列末尾;支持 stop_token_ids 列表,命中则标记该序列为已完成,全部完成后提前退出。sample_decode(model, input_ids, max_length, temperature, top_k, top_p, device, stop_token_ids):temperature=0 时直接调用 greedy_decode;否则每步对 logits[:, -1, :] 先除 temperature,再可选 top_k(保留 logits 最大的 k 个)、top_p(nucleus:按概率排序后截断累积概率超过 p 的位置),再 softmax 后 multinomial 采样;同样支持 stop_token_ids。当前实现每步都对整段序列前向,未使用 KV cache。 - scripts/infer.py:脚本顶部可配置 checkpoint、prompt、max_length、temperature、top_k、top_p、config_dir、device_str、num_samples。加载 config 与词汇表(vocab_dir 来自 data_config,路径为 vocab_dir/char_vocab.json)、创建模型、load_model_only 加载权重。若 prompt 非空则 encode 后转为 (1, seq_len);若为空则 input_ids = [[0]](
作为起始)。按 temperature 选择贪心或采样解码;若 num_samples>1 则循环生成多份;解码后打印:若有 prompt 且生成文本以 prompt 开头,则分别打印「完整」与「新增」部分,否则打印完整文本,并打印字符数与 token 数。
采样策略:temperature=0 为贪心;temperature>0 为采样,可配合 top_k(0 表示禁用)、top_p(0.0 表示禁用)控制多样性与连贯性。
文本生成 API#
- serve.py:FastAPI 应用,lifespan 内加载 configs、CharTokenizer(vocab_path)、Transformer(model_config)、load_model_only(model, checkpoint_path),模型与分词器存为全局变量;CONFIG 中可配置 checkpoint_path、config_dir、vocab_path、device(默认 cuda 若可用否则 cpu)、host(0.0.0.0)、port(8000)。
POST /generate:请求体为 GenerateRequest(Pydantic):prompt(必填,示例 “First Citizen:\n”)、max_length(默认 100,ge=1, le=1000)、temperature(默认 0.8,ge=0, le=2)、top_k(默认 50,ge=0, le=200)、top_p(默认 0.9,ge=0, le=1)、num_samples(默认 1,ge=1, le=5)。响应体为 GenerateResponse:prompt、samples(列表,每项含 text、tokens)、model_info(checkpoint、device、total_params、num_layers、hidden_size)。temperature=0 时调用 greedy_decode,否则 sample_decode;生成后 tokenizer.decode 得到文本,填充 GeneratedSample 列表并返回。CORS 已配置(allow_origins=["*"]),便于前端跨域。当前为一次性返回完整生成结果,非流式。GET /返回欢迎信息与端点列表(generate、health、docs、redoc)。 - 若模型未加载,
/generate返回 503,detail 为「模型未加载」;生成过程异常返回 500,detail 为「生成失败: …」。
运行与部署#
典型流程:先执行 download_data.py 下载 Tiny Shakespeare,再执行 tokenize_data.py 生成 train.npy、val.npy 与 char_vocab.json;然后运行 scripts/train.py 进行训练,检查点落在 checkpoint_dir(默认 checkpoints/);训练完成后可用 scripts/infer.py 做本地推理,或启动 serve.py 对外提供 POST /generate 与 GET /health。Docker 部署见 docker/ 目录(Dockerfile、docker-compose.yml、build.sh)。
快速体验:训练完成后执行 python serve.py,访问 http://localhost:8000/docs 可在线调试 POST /generate(文本生成)与 GET /health(健康检查);根路径 GET / 返回欢迎信息与端点列表。
辅助脚本与配置#
- scripts/check_model.py:用于检查模型结构或权重(如参数规模、形状等),便于调试与对齐。
- configs/model.yaml:num_hidden_layers、hidden_size、num_attention_heads、num_key_value_heads、intermediate_size、max_position_embeddings、rope_theta、sliding_window、sliding_window_overlap、rms_norm_eps、tie_word_embeddings、init_weights、init_std 等。
- configs/train.yaml:learning_rate、weight_decay、beta1、beta2、eps、lr_scheduler(cosine/linear/constant)、warmup_steps、max_steps、batch_size、gradient_accumulation_steps、max_grad_norm、save_steps、eval_steps、save_total_limit、device、num_epochs、checkpoint_dir、use_amp、amp_dtype 等。
- configs/data.yaml:processed_dir、vocab_dir、context_window、num_workers、pin_memory、val_ratio 等。
技术难点与实现#
项目实施过程中围绕掩码与因果性、训练稳定性与长序列、推理与服务的统一入口等做了针对性设计与实现,便于在有限资源下稳定训练并对外提供一致的服务接口。
滑动窗口注意力与因果掩码#
难点:既要保证因果性(当前 token 仅能看到过去),又要将注意力范围限制在局部窗口内,掩码需同时满足下三角与窗口边界,且需与 RoPE、GQA 的 Q/K/V 形状配合;若窗口与因果分别处理,顺序与广播需一致,避免漏掩或重复。
方案:在 scaled_dot_product_attention 中先计算 QK^T / sqrt(head_dim) 得到 scores (B, H, L, L);若配置了 sliding_window,先调用 apply_sliding_window_mask(scores, window_size, seq_len, overlap):内部构建 window_mask(overlap=True 时对称窗口 [i-w/2, i+w/2+1],False 时非对称 [i-w+1, i+1]),再与因果下三角掩码做逻辑与,将不可见位置在 scores 上置为 -inf;若还传入了外部 mask(如因果掩码),再按 mask 的布尔值对 scores 做 masked_fill(False 置 -inf)。这样先窗口、再因果,且 mask 若为 (L,L) 会 unsqueeze 成 (1,1,L,L) 以匹配 scores 维度。Block 中传入 config 的 sliding_window 与 sliding_window_overlap,注意力层统一使用 GQA + RoPE + 该掩码逻辑。
训练稳定性与长序列#
难点:深层 Transformer 与较长 context_window 易导致梯度不稳定或显存溢出;单卡上大 batch 不可行,且损失曲线需要平滑。
方案:采用 Pre-RMSNorm 与梯度裁剪(max_grad_norm,默认 1.0),在每次 optimizer.step 前执行 clip_grad_norm_。使用 AdamW(带 weight_decay)与 Cosine 学习率调度并设置 warmup_steps,避免前期学习率过大。通过 gradient_accumulation_steps 在较小单步 batch 下累积梯度再更新,等效更大 batch,同时单步显存可控。滑动窗口限制注意力范围,在相同显存下可增大 context_window 或 batch_size。配置中可选 use_amp、amp_dtype(如 bfloat16)进一步节省显存与加速。损失函数对 padding 位置可使用 ignore_index(如 -1),当前数据集同长度样本经 collate 直接 stack,无 padding 时无需忽略;若未来支持变长则可在 target_ids 中对 pad 位置填 -1。
推理与服务的统一入口#
难点:infer.py 与 serve.py 均需加载同一模型与分词器,且需与训练时的 config、词汇表路径一致,避免配置分散导致加载失败或维度不匹配(如 vocab_size、hidden_size 与检查点不一致)。
方案:两处均通过 load_all_configs(config_dir) 读取三份 YAML,得到 model、train、data 三个子字典;词汇表路径:infer 从 data_config 取 vocab_dir,拼接 vocab_dir / "char_vocab.json",serve 的 CONFIG 直接写死 vocab_path(如 data/vocab/char_vocab.json)。模型构造前将 model_config[“vocab_size”] 设为 tokenizer.vocab_size,再传入 Transformer(config),保证与训练时一致(训练脚本同样从 tokenizer 取 vocab_size 注入 config)。检查点路径在 serve 的 CONFIG 与 infer 的脚本顶部变量中分别配置,需与训练产出文件名一致(如 best_model.pt 或 best_model_step_*.pt)。这样训练、推理、服务共用同一套 config 键名与加载逻辑,仅入口(脚本变量 vs CONFIG)不同。
总结#
作为个人代表项目,Manshu LLM 重点体现两点:一是从零实现 LLM 的各个结构(Token Embedding、GQA、RoPE、滑动窗口注意力、SwiGLU、RMSNorm、LM Head 等),不依赖现成模型库;二是提供完整推理服务(本地 infer.py 与 FastAPI POST /generate、GET /health),支持贪心/采样解码与健康检查,可直接对外提供文本生成能力。项目覆盖从数据准备、训练、检查点管理到推理服务的全链路,并具备 YAML 配置与 Docker 部署选项。
后续可扩展方向:为推理与 API 增加 KV cache 以加速长序列生成;支持流式返回(如 SSE);将模型与分词器封装为 Hugging Face 风格的 from_pretrained/save_pretrained;以及引入更多数据集与评估指标,用于对比不同架构与超参的效果。