From a4443765eee0ecdeeed0b362f84bd94ad42a4c42 Mon Sep 17 00:00:00 2001
From: ViperEkura <3081035982@qq.com>
Date: Sat, 27 Sep 2025 12:02:22 +0800
Subject: [PATCH] Initial commit
---
.gitignore | 22 +
LICENSE | 201 +++++++++
README.md | 333 +++++++++++++++
assets/docs/introduction.md | 89 ++++
assets/docs/kvcache.md | 27 ++
assets/images/project_logo.png | Bin 0 -> 21709 bytes
assets/images/project_logo_clipped.png | Bin 0 -> 11119 bytes
assets/images/structure.png | Bin 0 -> 604415 bytes
generate.py | 101 +++++
khaosz/__init__.py | 52 +++
khaosz/core/__init__.py | 27 ++
khaosz/core/generator.py | 568 +++++++++++++++++++++++++
khaosz/core/parameter.py | 238 +++++++++++
khaosz/core/tokenizer.py | 111 +++++
khaosz/core/transformer.py | 341 +++++++++++++++
khaosz/model.py | 112 +++++
khaosz/trainer/__init__.py | 11 +
khaosz/trainer/dataset.py | 210 +++++++++
khaosz/trainer/mask.py | 55 +++
khaosz/trainer/strategy.py | 388 +++++++++++++++++
khaosz/trainer/trainer.py | 167 ++++++++
khaosz/utils/retriever.py | 88 ++++
khaosz/utils/splitter.py | 127 ++++++
requirements.txt | 36 ++
scripts/chat.py | 32 ++
scripts/download.py | 14 +
scripts/generate_ar.py | 27 ++
scripts/generate_batch.py | 25 ++
scripts/generate_retrieve.py | 42 ++
setup.py | 18 +
tests/test_module.py | 103 +++++
tests/test_trainer.py | 203 +++++++++
train.py | 128 ++++++
33 files changed, 3896 insertions(+)
create mode 100644 .gitignore
create mode 100644 LICENSE
create mode 100644 README.md
create mode 100644 assets/docs/introduction.md
create mode 100644 assets/docs/kvcache.md
create mode 100644 assets/images/project_logo.png
create mode 100644 assets/images/project_logo_clipped.png
create mode 100644 assets/images/structure.png
create mode 100644 generate.py
create mode 100644 khaosz/__init__.py
create mode 100644 khaosz/core/__init__.py
create mode 100644 khaosz/core/generator.py
create mode 100644 khaosz/core/parameter.py
create mode 100644 khaosz/core/tokenizer.py
create mode 100644 khaosz/core/transformer.py
create mode 100644 khaosz/model.py
create mode 100644 khaosz/trainer/__init__.py
create mode 100644 khaosz/trainer/dataset.py
create mode 100644 khaosz/trainer/mask.py
create mode 100644 khaosz/trainer/strategy.py
create mode 100644 khaosz/trainer/trainer.py
create mode 100644 khaosz/utils/retriever.py
create mode 100644 khaosz/utils/splitter.py
create mode 100644 requirements.txt
create mode 100644 scripts/chat.py
create mode 100644 scripts/download.py
create mode 100644 scripts/generate_ar.py
create mode 100644 scripts/generate_batch.py
create mode 100644 scripts/generate_retrieve.py
create mode 100644 setup.py
create mode 100644 tests/test_module.py
create mode 100644 tests/test_trainer.py
create mode 100644 train.py
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3ecf496
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,22 @@
+# cache
+__pycache__
+.pytest_cache
+
+# params
+*.safetensors
+*.json
+*.pkl
+*.db
+
+# train_log
+*.log
+
+# ignore file
+-*
+
+# vscode file
+.vscode
+
+# build file
+build
+*.egg-info
\ No newline at end of file
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/README.md b/README.md
new file mode 100644
index 0000000..0d5fa89
--- /dev/null
+++ b/README.md
@@ -0,0 +1,333 @@
+
+
+
+
+English Version
+
+This is a Chinese-English bilingual Transformer model supporting both languages. It contains model configurations and training workflows, completing training by loading parameters defined in `param_path/config.json`. The training script `train.py` parses command-line arguments, including dataset root directory, number of training epochs, batch size, checkpoint interval, and checkpoint directory.
+
+**Model Download Options (Choose One):**
+
+1. Visit [HuggingFace](https://huggingface.co/ViperEk/KHAOSZ) to access **Files and versions**
+2. Run `scripts/download.py` to download parameters
+
+**Demo Video:** [bilibili](https://www.bilibili.com/video/BV1z5RPYHEkd)
+
+Training dataset sources are listed in the **Model Card** section of the HuggingFace download link.
+
+**License:** Code follows Apache-2.0 protocol. Please credit the source code when used.
+
+- **📊 Device Selection:** Code defaults to CUDA training
+- **🌐 Performance Optimization:** `dtype=torch.bfloat16` is enabled to accelerate training and reduce memory usage. Ensure hardware supports this feature.
+- **🤖 Language Support:** Model supports Chinese and English training. The BBPE tokenizer was trained without multilingual text, so OOV (out-of-vocabulary) issues are minimized for these languages but may exist for others.
+
+### 📌 Training Guide
+
+To train this Transformer model, follow these steps:
+
+**(1). Prepare Dataset:**
+
+Place datasets in the designated root directory. Files should be text documents in Chinese, English, or mixed. Format should align with model input requirements - preferably pre-tokenized token_ids stored as `torch.Tensor` (using `torch.Tensor` saves memory compared to Python lists, which default to 64-bit precision).
+
+**(2). Install Dependencies:**
+
+```bash
+pip install -r requirements.txt
+pip install .
+```
+
+**(3). Run Training Script:**
+
+```bash
+python train.py \
+--train_type=train_type[seq, sft, dpo] \
+--data_root_path=/path/to/dataset \
+--param_path=/path/to/param_path \
+--n_epoch=5 \
+--batch_size=8 \
+--max_lr=2e-4 \
+--n_iter_ckpt=10000 \
+--ckpt_dir=checkpoints
+```
+
+**Parameters Explanation:**
+- `--train_type`: Training type (seq, sft, dpo)
+- `--data_root_path`: Root directory of the dataset
+- `--param_path`: Path to the model training parameters
+- `--n_epoch`: Total number of training epochs
+- `--batch_size`: Batch size
+- `--n_iter_step`: Number of batches per training step
+- `--warning_step`: Number of warmup steps
+- `--max_lr`: Maximum learning rate (using warmup + cosine decay)
+- `--n_iter_ckpt`: Checkpoint saving interval
+- `--ckpt_dir`: Directory to save checkpoints
+- `--resume_dir`: Resume training from the specified path
+
+Training logs will be saved in `train_log.txt`. Checkpoints will be saved in the specified directory for resuming training or evaluation.
+
+### 👉 Usage Guide
+
+**(1). Chatting with the Model:**
+
+Open `chat.py` or use streaming/non-streaming interfaces:
+
+**Streaming Output:**
+```python
+import torch
+from khaosz import Khaosz
+
+model_dir = "your_model_parameter_dir"
+model = Khaosz(model_dir).to(device='cuda', dtype=torch.bfloat16)
+history = []
+
+while True:
+ query = input(">> ")
+ if query == "!exit":
+ break
+
+ response_size = 0
+ for response, history in model.stream_generate(
+ query=query,
+ history=history,
+ temperature=0.85,
+ top_p=0.95,
+ top_k=50
+ ):
+ print(response[response_size:], end="")
+ response_size = len(response)
+```
+
+**Non-streaming Output:**
+```python
+import torch
+from khaosz import Khaosz
+
+model_dir = "your_model_parameter_dir"
+model = Khaosz(model_dir).to(device='cuda', dtype=torch.bfloat16)
+history = []
+
+while True:
+ query = input(">> ")
+ if query == "!exit":
+ break
+
+ response = model.generate(
+ query=query,
+ history=history,
+ temperature=0.85,
+ top_p=0.95,
+ top_k=50
+ )
+ print(response)
+```
+
+**(2) Retrieval-Augmented Generation (RAG):**
+
+```python
+import torch
+from khaosz import Khaosz
+
+model_dir = "your_model_parameter_dir"
+model = Khaosz(model_dir).to(device='cuda', dtype=torch.bfloat16)
+
+retrieved_content = model.retrieve_generate(
+ query=query,
+ retrieve_top_k=5,
+ temperature=0.6,
+ top_k=30,
+ top_p=0.95
+)
+print(retrieved_content)
+```
+
+### 📌 Model Specifications
+
+This model is based on a 24-layer Transformer with parameters defined in `config.json`, totaling approximately 1.0 billion (1.0B) parameters.
+
+**Key Design Choices:**
+- Weight tying between embedding and final linear layers (standard for small models to save parameters)
+- Embedding layer optimization: Without weight tying, a 10,000-word vocabulary would consume ~102M parameters (0.1B)
+
+**Limitations:**
+- May struggle with complex language phenomena due to smaller parameter size
+- Prone to overfitting on specialized datasets
+- Limited multilingual capabilities
+
+**Advantages:**
+- Runs efficiently on lower-spec hardware
+- Shorter training time compared to larger models
+
+**Training Pipeline:**
+The model has completed pre-training + SFT (Supervised Fine-Tuning) + DPO (Direct Preference Optimization) workflows. All corresponding training code is included in the repository.
+
+
+中文版本
+这是一个支持中英文双语的 Transformer 模型,能够处理两种语言。模型包含配置文件和训练流程,通过加载 `param_path/config.json` 中定义的参数完成训练。训练脚本 `train.py` 支持命令行参数解析,包括数据集根目录、训练轮数(epochs)、批量大小(batch size)、检查点保存间隔、检查点目录等。
+
+**模型下载选项(任选其一):**
+
+1. 访问 [HuggingFace](https://huggingface.co/ViperEk/KHAOSZ) 查看 **Files and versions**
+2. 运行 `scripts/download.py` 下载模型参数
+
+**演示视频:** [bilibili](https://www.bilibili.com/video/BV1z5RPYHEkd)
+
+训练数据来源请参见 HuggingFace 下载页面中的 **Model Card** 部分。
+
+**许可证:** 代码遵循 Apache-2.0 协议,使用时请注明出处。
+
+- **📊 设备选择:** 默认使用 CUDA 进行训练
+- **🌐 性能优化:** 启用 `dtype=torch.bfloat16` 以加速训练并减少内存占用,请确保硬件支持该特性
+- **🤖 语言支持:** 模型支持中文和英文训练。由于 BBPE 分词器未使用多语言文本训练,因此中英文的 OOV(未登录词)问题较少,其他语言可能存在 OOV 问题
+
+
+
+### 📌 训练指南
+
+要训练该 Transformer 模型,请按照以下步骤操作:
+
+#### **(1). 准备数据集:**
+
+将数据集放置在指定的根目录下。文件应为包含中文、英文或混合文本的文本文档。格式应符合模型输入要求——建议使用预分词后的 `token_ids` 并以 `torch.Tensor` 格式保存(使用 `torch.Tensor` 相比 Python 列表更节省内存,列表默认为 64 位精度)。
+
+#### **(2). 安装依赖:**
+
+```bash
+pip install -r requirements.txt
+pip install .
+```
+
+#### **(3). 运行训练脚本:**
+
+```bash
+python train.py \
+--train_type=train_type[seq, sft, dpo] \
+--data_root_path=/path/to/dataset \
+--param_path=/path/to/param_path \
+--n_epoch=5 \
+--batch_size=8 \
+--max_lr=2e-4 \
+--n_iter_ckpt=10000 \
+--ckpt_dir=checkpoints
+```
+
+**参数说明:**
+- `--train_type`: 训练类型(seq, sft, dpo)
+- `--data_root_path`: 数据集根目录
+- `--param_path`: 模型训练参数路径
+- `--n_epoch`: 总训练轮数
+- `--batch_size`: 批量大小
+- `--n_iter_step`: 每个训练步骤的 batch 数量
+- `--warning_step`: 预热步数(warmup steps)
+- `--max_lr`: 最大学习率(使用预热 + 余弦衰减)
+- `--n_iter_ckpt`: 检查点保存间隔
+- `--ckpt_dir`: 检查点保存目录
+- `--resume_dir`: 从指定路径恢复训练
+
+训练日志将保存在 `train_log.txt` 中。检查点将保存在指定目录,用于恢复训练或评估。
+
+
+
+### 👉 使用指南
+
+#### **(1). 与模型对话:**
+
+打开 `chat.py` 或使用流式/非流式接口:
+
+**流式输出:**
+```python
+import torch
+from khaosz import Khaosz
+
+model_dir = "your_model_parameter_dir"
+model = Khaosz(model_dir).to(device='cuda', dtype=torch.bfloat16)
+history = []
+
+while True:
+ query = input(">> ")
+ if query == "!exit":
+ break
+
+ response_size = 0
+ for response, history in model.stream_generate(
+ query=query,
+ history=history,
+ temperature=0.85,
+ top_p=0.95,
+ top_k=50
+ ):
+ print(response[response_size:], end="")
+ response_size = len(response)
+```
+
+**非流式输出:**
+```python
+import torch
+from khaosz import Khaosz
+
+model_dir = "your_model_parameter_dir"
+model = Khaosz(model_dir).to(device='cuda', dtype=torch.bfloat16)
+history = []
+
+while True:
+ query = input(">> ")
+ if query == "!exit":
+ break
+
+ response = model.generate(
+ query=query,
+ history=history,
+ temperature=0.85,
+ top_p=0.95,
+ top_k=50
+ )
+ print(response)
+```
+
+#### **(2). 基于检索的生成(RAG):**
+
+```python
+import torch
+from khaosz import Khaosz
+
+model_dir = "your_model_parameter_dir"
+model = Khaosz(model_dir).to(device='cuda', dtype=torch.bfloat16)
+
+retrieved_content = model.retrieve_generate(
+ query=query,
+ retrieve_top_k=5,
+ temperature=0.6,
+ top_k=30,
+ top_p=0.95
+)
+print(retrieved_content)
+```
+
+
+
+### 📌 模型规格说明(重复部分)
+
+该模型基于一个 24 层的 Transformer 架构,参数配置定义在 `config.json` 中,总参数量约为 10 亿(1.0B)。
+
+**关键设计选择:**
+- 在嵌入层(embedding)与最终线性层之间进行权重绑定(weight tying),这是小型模型中常见的节省参数量的做法
+- 嵌入层优化:若不进行权重绑定,一个包含 10,000 个词的词汇表将消耗约 1.02 亿(0.1B)参数
+
+**局限性:**
+- 由于参数规模较小,可能在处理复杂语言现象时表现受限
+- 在特定领域的数据集上容易出现过拟合
+- 多语言能力有限
+
+**优势:**
+- 可在低配置硬件上高效运行
+- 相较于大型模型,训练时间更短
+
+**训练流程:**
+该模型已完成预训练(pre-training)+ 监督微调(SFT, Supervised Fine-Tuning)+ 直接偏好优化(DPO, Direct Preference Optimization)的全流程。所有相关的训练代码均已包含在代码库中。
\ No newline at end of file
diff --git a/assets/docs/introduction.md b/assets/docs/introduction.md
new file mode 100644
index 0000000..7333403
--- /dev/null
+++ b/assets/docs/introduction.md
@@ -0,0 +1,89 @@
+## 模型介绍
+
+
+
+### 1. 模型搭建
+
+本模型采用Transformer架构, 使用GQA(q_head=24, kv_head=4) 机制,相较于传统的MHA可以节省KV cache 的显存占用(但是目前没有做KV cache),通过堆叠24层Transformer实现模型的搭建, 参数量为1.0b。Transformer 是自回归模型, 是通过计算前面所有的token的关系得到下一个token的概率分布
+
+
+
+什么是自回归模型呢, 在把句子拆分成token之后, 模型会预测下一个token的概率分布。这意味着模型会根据给定的上下文(即已经出现的tokens序列),计算出下一个可能的token及其对应的概率。
+
+
+
+#### 1. 自回归
+
+假设我们有一个句子被拆分成如下tokens列表:
+
+```
+["你好", "," "今天", "天气"]
+```
+
+接下来,模型会基于这个序列预测下一个可能出现的token。这通常以概率分布的形式给出,比如:
+
+```
+-> {"token": "不错", "probability": 0.4}
+-> {"token": "晴朗", "probability": 0.2}
+-> ......
+```
+
+这里,“不错”和“晴朗”是两个可能跟随在“天气”之后的tokens,并且给出了每个token成为下一个token的可能性大小。
+
+之后,我们通过采样(通过top_k, top_p, temperature参数调整采样后的结果)得到下一个token并且将下一个token加入序列作为输入
+
+```
+["你好", "," "今天", "天气", "不错"]
+```
+
+之后都是在重复这个流程, 直到遇到控制流程结束的token(<|end_of_seqence|>)模型停止处理(一般模型都会设置控制token, 不然模型会一直输出到显存爆炸)。
+
+
+
+
+
+#### 2. 因果掩码
+
+transformer 中采用注意力机制,输入的形状一般为[bsz, seq_len], 输出为[bsz, seq_len,n_dim], 为了实现预测下一个token, 模型的输入和输出必须错开来一个位置。模型预测的target必须错开一个位置, 在训练的时候我们也采用错开一个位置的方法
+
+```
+sequence : [[1, 2, 3, 4, 5, 6]]
+input_ids: [[1, 2, 3, 4, 5]]
+target_ids: [[2, 3, 4, 5, 6]]
+```
+
+
+
+注意力得分计算的公式为
+
+
+$$ s_{ij} = softmax(\frac{q_i^Tk_j}{\sqrt{d_k}}) $$
+$$ s_{ij} := s_{ij} + mask_{ij} $$
+
+
+其中注意力得分代表了模型对两个token之间相似程度的关注程度
+
+对于decoder only结构的模型, 为了防止模型从未来的位置偷到信息, 在注意力的计算过程中需要增加掩码,我们需要在注意力得分计算之前应用一个掩码。这个掩码通常是一个下三角矩阵,对于长度为n的序列,它的形状是[n, n]。下面以一个长度为5的序列为例,展示如何创建这样的因果掩码矩阵:
+
+```
+[[0, -inf, -inf, -inf, -inf],
+ [0, 0, -inf, -inf, -inf],
+ [0, 0, 0, -inf, -inf],
+ [0, 0, 0, 0, -inf],
+ [0, 0, 0, 0, 0]]
+```
+
+在这个矩阵中,0表示可以注意到的位置,而-inf表示应该被掩盖(即不应注意到)的位置。因为这个句子保证了注意力得分中 $j > i$ 的部分通过softmax 之后由`inf` 变成0, 也就是模型不能看到未来的信息
+
+
+
+#### 3. 旋转位置编码
+
+旋转位置编码(Rotary Position Embedding, RoPE)是一种为了解决Transformer模型中缺乏对序列位置信息直接建模的问题而设计的位置编码方法。与传统的位置编码(如正弦和余弦函数的位置编码)不同,RoPE通过将位置信息直接嵌入到查询(Query, Q)和键(Key, K)向量中来实现,使得模型能够更自然地处理序列中的相对位置关系。
+
+
+$$ q_i = R_i W_q x_i $$
+$$ k_j = R_j W_k x_j $$
+$$ q_i^T k_j = (R_i W_q x_i)^T( R_j W_k x_j) = x_i^T W_q^T R_{i-j} W_k x_j $$
+
+其中的 $R_{i-j}$ 控制了模型的不同token 在不同相对距离上注意力的衰减,在 $i - j$ 绝对值越大的时候, 衰减的程度越强, 通过这种方式能让模型学习到相对位置关系, 从而使得模型可以扩展和适应长序列
\ No newline at end of file
diff --git a/assets/docs/kvcache.md b/assets/docs/kvcache.md
new file mode 100644
index 0000000..304c484
--- /dev/null
+++ b/assets/docs/kvcache.md
@@ -0,0 +1,27 @@
+## kv_cache 实现
+
+根据注意力的计算公式
+
+$$
+\begin{align*}
+o_i &= \sum_j s_{ij} v_{j} \\
+s_{ij} &= \text{softmax}\left( \sum_n \frac{q_{i,n} k_{j,n}}{\sqrt{d_k}} \right)
+\end{align*}
+$$
+
+由于模型是自回归模型, 我们只用求序列最后一个部分,也就是说 $ i $ 的下标是确定的, 是序列最后一个元素, 我们求的是 $o_{n} $
+
+$$
+\begin{align*}
+o_n &= \sum_j s_{j}v_{j,n} \\
+s_j &= \text{softmax}\left(\sum_n\frac{q_n k_{j,n}}{\sqrt{d_k}} \right)
+\end{align*}
+$$
+
+如果我们把式子展开
+
+$$
+o_n = \sum_j \sum_n \text{softmax}\left(\frac{q_n k_{j,n}}{\sqrt{d_k}}\right)v_{j,n}
+$$
+
+以上表达式只有k和v存在长度下标, 而 $q$ 没有, 所以计算过程中 $q$ 的输入是确定的上次输入的最后一个token, 而 $k, v$ 是需要对不同长度的部分进行缓存的,同时缓存的时候应该注意位置编码的计算应该在kvcache的计算之前进行,否则会存在位置编码的计算错误
\ No newline at end of file
diff --git a/assets/images/project_logo.png b/assets/images/project_logo.png
new file mode 100644
index 0000000000000000000000000000000000000000..342eb9c67ea61de90f79257088b405de1e7b8f57
GIT binary patch
literal 21709
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
H^3VSQ|Ln}L
literal 0
HcmV?d00001
diff --git a/assets/images/project_logo_clipped.png b/assets/images/project_logo_clipped.png
new file mode 100644
index 0000000000000000000000000000000000000000..b02cd08b95143b556c50ec797128dac5cdf281d7
GIT binary patch
literal 11119
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