# lite-llm-scratch **Repository Path**: MuMuNan/lite-llm-scratch ## Basic Information - **Project Name**: lite-llm-scratch - **Description**: 从零实现一个cpp训练框架 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2025-06-14 - **Last Updated**: 2025-06-23 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README ## 整体介绍 Buffer类,管理不同设备上的存储 CPU/GPU,Tensor类的重要成员 \ Tensor类,实现自动微分的张量结构 \ Layer类,实现各种张量计算,本身拥有一定可学习参数等,能进行forward/backward \ Modle类,各种Layer搭建起来的模型 Node类,计算图的节点,包裹一个Tensor以及一些必要结构,能够形成计算图。最终Node会拓扑排序,依顺序进行forward/backward操作 \ (把Tensor和Node分开,Tensor专注于数据管理) kernel目录下,各种Tensor操作,不管是forward/backward,所有Tensor操作最终都在这里,通过kernel_interface调用,cpu/cuda子目录分别是在不同device下Tensor操作的实现 这一版先不管batch_size这个维度 ## 测试 ```bash mkdir build && cd build cmake .. # cmake .. -DCMAKE_BUILD_TYPE=Debug cmake --build . # 或者直接make ctest --output-on-failure ``` ## 格式化 ```bash find . -name "*.hpp" -o -name "*.cpp" -o -name "*.h" -o -name "*.cuh" -o -name "*.cu" | xargs clang-format -i ``` ## googletest 选用 git clone --branch release-1.12.1 https://github.com/google/googletest.git extern/googletest ## 一些代码相关问题 ### 一些变量命名 成员变量前加上m_前缀 \ 一些常量(包括enum)等加上前缀k,表示const ### backward grad计算需要累加 这是计算图的需要,某个节点可能在计算图中出现多次,梯度要累加 就比如 $ y = x * x $,那么反向传播时x的梯度要累加才行 ### 一些设计改进: 1、Tensor类的改进1 \ 最初想法是Tensor类通过float*管理存储,然后外部用shared_ptr\方式使用 \ 想到一个更简单方法,Tensor类里存储用shared_ptr\管理,然后外部就可以直接使用Tensor,各种复制Tensor也不会有问题 \ 两个思路对比:一个是类中各种资源能自我管理,类本身可以随意复制;一个是该类直接管理各种资源,不能直接复制 \ 2、Tensor类的改进2 \ Tensor的grad本身也应该是一个Tensor类!从pytorch实现里得到启发 \ 3、对一些可共享底层数据的操作的支持 对view/transpose/reshape的设计,搞个tensor的视图,共享数据但是shape/stride这些不一样 \ 似乎有两种临时的tensor,一种是放入计算图中的(比如计算过程中需要reshape),一种只是作为一个临时变量的用完即弃的(比如反向传播中a.grad=c.grad*b.T里面b.T只是用于临时计算下的) \ 目前觉得应该分成三种类型 type_normal/type_view/type_temp,type_view就是放在计算图中,且共享底层数据;type_temp就纯粹为了临时计算,不用放入计算图,用完即弃(比如a.grad=c.grad*b.T中的b.T) ### 其他杂事 模型参数怎么从外界导入 \ 输入图片、文本怎么转为Tensor \ 怎么适配各种GPU型号 ### 未来改进 暂时先默认数据类型都是float \ 错误处理问题 \ 要能加载已有gguf文件格式(变量命名上是否有要求?) ### 编译方面问题 .cu/.cpp要分别用nvcc/g++编译,这样如果kernel函数是模板的话有点麻烦(模板函数只能都在.h文件中定义实现) 目前看似乎还是用dtype+switch-case的方式做更容易,算子都写float/int多个版本即可 ### 代码结构方面 其实主流训练推理框架都不把Tensor等泛型模板化,而是通过dtype和switch-case做dtype dispatch \ Tensor中就是个dtype类型,然后Layer中switch语句选择对应算子 \ 除此之外对device也要做一次dispatch ```cpp Tensor AddLayer::forward(const Tensor& a, const Tensor& b) { Tensor output = Tensor::empty_like(a); switch (a.dtype()) { case DType::Float32: launch_add_kernel(a, b, output); break; case DType::Int32: launch_add_kernel(a, b, output); break; default: throw std::runtime_error("Unsupported dtype"); } return output; } ``` 我代码里直接把Tensor/Layer都根据dtype搞成泛型,似乎没必要 \ 结果这次commit把很多.cpp文件都干掉了,模板类只能在一个头文件中定义+实现 \ (模板实现过程中一直很多编译错误。。很多之前effective cpp里知识点都没记得,写错了才记得) debug记录1:这次泛型修改还搞了几个要命的bug,比如Tensor的size()和elem_num()混用导致写越界,把shape都篡改了,造成奇怪问题(还真难找,尝试肉眼找一直不对,毕竟错误的地方离这里很远) - 我这Tensor的size/elem_num/elem_size这几个接口似乎确实容易错 (这次有经验了,下次要是某个数据突然出现大问题,很可能是某些地方写越界了) 好家伙,在cmake中加入检查内存泄漏的,一查吓一跳 ``` add_compile_options(-fsanitize=address -g -fno-omit-frame-pointer) add_link_options(-fsanitize=address) ``` 不对,根据泄露信息看,似乎不是真的cuda泄露,是框架内部运行的正常逻辑,不应该用asan来检查cuda的显存泄露(看样子要学的很多啊,没有llm帮忙根本学不快) ### 其他一些想法 其实根本不需要想的很复杂,只要把一层、两层的forward/backward都想清楚了,所有的就都清楚了 目前想法是真正各种kernel计算都直接用Tensor,但是计算图的构建用Node 我依然还是把Node当作一个内部的东西,不让用户看到,可以Tensor里面也有个指向对应Node的指针,Node也有个指向Tensor的指针(要注意避免循环引用,得有个用weak_ptr - 弱引用,Node拥有Tensor,但Tensor只是“弱引用”Node - 这个弱引用只是为了让Tensor找到对应的Node而已,但语义上是Node包裹了个Tensor) 要特别小心,Node是基于Tensor,Tensor中不要包括Node的头文件(只要forward-declare即可)!Node中可以包含Tensor的头文件 Tensor -> Node -> Layer