Skip to content
Open

Me #6

Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions .clangd
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
CompileFlags:
CompilationDatabase: build/Clangd
Remove: [-fopenmp]

Diagnostics:
Suppress:
# clangd14 + 系统头组合下偶发的噪音诊断,不影响项目代码
- builtin_definition

---
If:
PathMatch: .*\.cu$
CompileFlags:
# .cu 走 clangd 解析时,改用 clang++ 驱动并移除 nvcc 专属参数
Compiler: /bin/clang++-14
Remove:
- --expt-extended-lambda
- --expt-relaxed-constexpr
- --generate-code=*
- -std=*
Add:
- -std=c++17
- -I/usr/local/cuda/include
- -I/usr/local/cuda/targets/x86_64-linux/include
- --cuda-gpu-arch=sm_70
- --cuda-path=/usr/local/cuda
- --no-cuda-version-check

---
If:
PathMatch: (^|/)third_party/eigen/bench/.*
Index:
# bench 目录不是本项目业务代码,跳过可减少误报和索引干扰
Background: Skip
Diagnostics:
Suppress:
- '*'

---
If:
# 系统头/外部 CUDA 头不是项目源码,打开头文件本身时常出现误报
PathMatch: (^/usr/include/.*)|(^/usr/local/cuda/include/.*)|(^/usr/local/cuda/targets/.*/include/.*)|(^/usr/local/cuda-[^/]+/include/.*)|(^/usr/local/cuda-[^/]+/targets/.*/include/.*)
Index:
Background: Skip
Diagnostics:
Suppress:
- '*'
1 change: 1 addition & 0 deletions compile_commands.json
281 changes: 281 additions & 0 deletions docs/作业完成思路.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,281 @@
# TinyInfiniTrain 项目理解与作业完成思路

## 1. 这个项目在做什么

`TinyInfiniTrain` 是一个简化版深度学习训练框架,核心目标是:

- 用 C++ 实现一套最小可用的训练栈(Tensor、Autograd、Kernel、Optimizer、Module)
- 同时支持 CPU / CUDA 两种设备
- 通过 Dispatcher 做“按设备分发 kernel”
- 在测试里从单算子逐步验证,最终跑通 GPT-2 单步训练并对齐 logits

你可以把它理解成一个“教学版 PyTorch 子集”:

- `infini_train/include/tensor.h` + `infini_train/src/tensor.cc`:张量、基础算子、自动求导入口
- `infini_train/include/autograd/*` + `infini_train/src/autograd/*`:前向/反向计算图节点
- `infini_train/include/dispatcher.h`:kernel 注册与调度中枢
- `infini_train/src/kernels/cpu|cuda/*`:真正执行数值计算的后端实现
- `example/gpt2/*` + `example/common/*`:端到端 GPT-2 训练与文本生成示例

---

## 2. 作业依赖关系(建议按这个顺序做)

按依赖和风险,推荐顺序:

1. **作业五 Dispatcher**(基础设施,很多算子都依赖)
2. **作业一 Neg autograd**(打通 Dispatcher + autograd 调用链)
3. **作业二 Matmul(先 CPU 再 CUDA)**
4. **作业三 Adam(先 CPU 再 CUDA)**
5. **作业四 Tensor::Flatten + Tensor::Backward**
6. **作业六 GPT-2 数据/Tokenizer/生成逻辑**

每完成一项就跑对应测试,避免最后一起排错。

---

## 3. 每个作业的完成思路

## 作业五:Dispatcher(优先做)

目标文件:`infini_train/include/dispatcher.h`

### 要做什么

1. `KernelFunction::Call`:把 `void*` 恢复成函数指针并调用
2. `Dispatcher::Register`:实现注册逻辑 + 重复注册保护
3. `REGISTER_KERNEL`:实现静态注册宏

### 建议实现点

- `Call`:
- `using FuncT = RetT (*)(ArgsT...);`
- `auto fn = reinterpret_cast<FuncT>(func_ptr_);`
- `CHECK(fn != nullptr)` 后调用
- `Register`:
- 用 `CHECK(!key_to_kernel_map_.contains(key)) << "Kernel already registered"`
- 再 `emplace(key, KernelFunction(std::forward<FuncT>(kernel)))`
- 宏:
- 用“静态变量 + lambda”触发注册
- 注意变量名去重(建议拼 `__LINE__` 或 `__COUNTER__`)
- 注册 key 名称建议用 `#kernel_name`

### 常见坑

- 宏如果只写成函数调用,在全局作用域无法使用
- 宏变量名不唯一会编译冲突
- 错误信息里最好包含 `Kernel already registered`(测试有断言)

---

## 作业一:autograd 调用 Neg kernel

目标文件:`infini_train/src/autograd/elementwise.cc`

### 要做什么

- `Neg::Forward`:拿到输入张量设备,从 Dispatcher 取 `NegForward`
- `Neg::Backward`:从梯度输出设备取 `NegBackward`

### 写法参考路径

直接对照同文件里已实现好的 `Reciprocal::Forward/Backward`、`Sin::Forward/Backward`。

### 最小实现流程

1. `CHECK_EQ(input_tensors.size(), 1)` / `CHECK_EQ(grad_outputs.size(), 1)`
2. 取 `device = tensor->GetDevice().Type()`
3. `kernel = Dispatcher::Instance().GetKernel({device, "NegForward"})`
4. `return {kernel.Call<std::shared_ptr<Tensor>>(input)}`
5. backward 同理换成 `NegBackward`

---

## 作业二:Matmul(CPU/CUDA)

目标文件:

- CPU:`infini_train/src/kernels/cpu/linear.cc`
- CUDA:`infini_train/src/kernels/cuda/linear.cu`

### 维度约定(核心)

- 输入:`A[..., M, K]`
- 权重:`B[..., K, N]`
- 输出:`C[..., M, N]`
- 反向:
- `dA = dC @ B^T`
- `dB = A^T @ dC`

其中 `...` 表示 batch 维,当前测试里两侧 batch 维一致(不要求广播)。

### CPU 思路

- 先做 shape check:
- rank >= 2
- 最后两维满足 K 对齐
- batch 前缀一致
- batch 数 `batch = prod(dims[0:rank-2])`
- 对每个 batch 执行三重循环(或 Eigen Map)
- backward 同理按公式循环

### CUDA 思路

- 推荐用 `cublasSgemm` + `cublasSgemmStridedBatched`
- rank=2 时用单次 `Sgemm`,rank>2 用 strided batched
- 注意 row-major 与 cublas column-major 的转置关系(可按现有 `LinearForward/LinearBackward` 的写法套用)

### 常见坑

- 把 `M/K/N` 下标搞反(导致数值对不上)
- 忽略 batch stride,第二个 batch 读错内存
- backward 里 `dB` 累加公式写错

---

## 作业三:Adam 优化器(CPU/CUDA)

目标文件:

- CPU:`infini_train/src/kernels/cpu/accumulate_grad.cc`
- CUDA:`infini_train/src/kernels/cuda/accumulate_grad.cu`

### 公式

对每个参数元素:

- `m = beta1 * m + (1 - beta1) * g`
- `v = beta2 * v + (1 - beta2) * g * g`
- `m_hat = m / (1 - beta1^t)`
- `v_hat = v / (1 - beta2^t)`
- `param -= lr * m_hat / (sqrt(v_hat) + eps)`

### 实现建议

- CPU:for 循环逐元素更新 `param/m/v`
- CUDA:写一个 kernel 逐元素更新,host 侧计算 `num_blocks`
- `t` 是 step 从 1 开始(`optimizer.cc` 里先 `++t_`)

### 常见坑

- 忘了偏置校正(`m_hat/v_hat`)会导致测试误差不对
- 误把 `param += ...` 写成上升

---

## 作业四:Tensor 基础操作

目标文件:`infini_train/src/tensor.cc`

### A) `Flatten(start, end)`

思路:

1. 处理负下标(`-1` 表示最后一维)
2. `CHECK` 范围合法,且 `start <= end`
3. 生成新 shape:
- 前缀 `[0, start)` 保留
- 中间 `[start, end]` 连乘成一个维度
- 后缀 `(end, last]` 保留
4. 返回 `Contiguous()->View(new_shape)`

### B) `Backward(...)`

目标是把入口梯度喂给 autograd 图:

1. 如果 `gradient == nullptr`,构造一个全 1 梯度(同 shape/dtype/device)
2. 如果当前张量是叶子并需要梯度,累加到 `grad_`
3. 如果有 `grad_fn_`,调用 `grad_fn_->BackwardPartial(gradient, output_idx_)`

`Function::BackwardPartial` 已经实现了多分支梯度累加逻辑,所以入口只要正确触发即可。

### 常见坑

- 未处理 `gradient==nullptr`(标量 backward 会崩)
- 直接覆盖叶子梯度,而不是累加

---

## 作业六:GPT-2 端到端(数据 + tokenizer + 生成)

目标文件:

- `example/common/tiny_shakespeare_dataset.cc`
- `example/common/tokenizer.cc`

### A) 数据集读取

`ReadTinyShakespeareFile`:

1. 打开二进制文件并读取 1024B header
2. 解析 `magic/version/num_toks`
3. 根据 magic 确认 token 类型(uint16 / uint32)
4. 读取 token 区,转换到框架期望的张量格式
5. 构造返回:`type + dims + tensor`

`TinyShakespeareDataset` 构造函数:

- 调用上述函数
- 计算 `sequence_size_in_bytes_`
- 计算 `num_samples_`(通常是 `num_toks - sequence_length`)

### B) Tokenizer 读取与解码

构造函数:

1. 读取 1024B header(magic/version/vocab_size)
2. 按文件格式循环读取词表项到 `token_table_`
3. 用 `magic_number_` 映射 `eot_token_`

`Decode(token_id)`:

- 检查越界
- 返回 `token_table_[token_id]`

### C) `GenerateText` 单步采样

每个 time step:

1. `logits = model.Forward({x})[0]`
2. 取最后一个位置的 logits(当前时刻)
3. `Softmax` 转概率
4. 用 `RandomF32 + SampleMult` 采样下一个 token
5. 写回输入缓存(下一步继续)
6. `std::cout << Decode(next_token)`

---

## 4. 推荐自测节奏

每做完一题就跑对应测试,不要等全部写完再跑:

```bash
make build USE_CUDA=OFF TEST=ON
cd build/Release
./test_dispatcher
./test_elementwise
./test_matmul
./test_adam
./test_tensor
./test_gpt2
```

如果需要 CUDA:

```bash
make build USE_CUDA=ON TEST=ON
cd build/Release
./test_matmul_cuda
./test_adam_cuda
```

---

## 5. 过作业的关键原则

- **最小化修改**:只在 TODO 区域动手
- **先基础后模型**:Dispatcher/Matmul/Adam/Tensor 先过,再看 GPT-2
- **先 CPU 再 CUDA**:先保证数学正确,再做设备并行
- **每步可验证**:一次只修一类错误,快速回归

如果你愿意,我可以下一步再给你一版“按天拆解”的清单(比如 2~3 天完成版),以及每一题的伪代码骨架(不直接给最终答案)。
Loading