# Llama-3-8B-Instruct-Chinese-chat


Llama-3-8B-Instruct in Chinese 自己微调版本

### 训练可用数据整理

| 数据集                                                                                                          | 介绍                                                                                                                                                                                                      |
|----------------------------------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [firefly-train-1.1M](https://huggingface.co/datasets/YeungNLP/firefly-train-1.1M)                            | 包含了23种常见的中文NLP任务的数据，并且构造了许多与中华文化相关的数据，如对联、作诗、文言文翻译、散文、金庸小说等。对于每个任务，由人工书写若干种指令模板，保证数据的高质量与丰富度，数据量为115万。                                                        |
| [moss-003-sft-data](https://huggingface.co/datasets/YeungNLP/moss-003-sft-data)                                         | 由复旦大学MOSS团队开源的中英文多轮对话数据，包含100万+数本。                                                                                                                                                        |
| [school_math_0.25M](https://huggingface.co/datasets/YeungNLP/school_math_0.25M) | 由BELLE项目组开源的数学运算指令数据，包含25万条数问。                                                                                                                                          |
| [ruozhiba](https://huggingface.co/datasets/LooksJuicy/ruozhiba)                                               | 弱智吧数据问答，据说比较锻炼模型的心智能力。                                                                                                                                                               |
欢迎补充，要求中文且一问一答形式，适合用于提升llama3任务能力的数据集

### [github地址](https://github.com/Rookie1019/Llama-3-8B-Instruct-Chinese.git)

### 推荐微调工具
在此感谢以下项目，提供了许多优秀的中文微调工具，供大家参考：
- Firefly  -  https://github.com/yangjianxin1/Firefly  
- LLaMA-Factory  -   https://github.com/hiyouga/LLaMA-Factory.git
  

### Chat版模型下载
- Instruct + 继续中文sft版 
- [huggingface地址](https://huggingface.co/Rookie/Llama-3-8B-Instruct-Chinese)

### 模型量化加速、部署

### 模型使用

默认情况下直接运行以下代码即可体验llama3中文对话，请自行修改`model_name_or_path`为你下载的模型路径

```python
from transformers import AutoTokenizer, AutoConfig, AddedToken, AutoModelForCausalLM, BitsAndBytesConfig
from peft import PeftModel
from dataclasses import dataclass
from typing import Dict
import torch
import copy

## 定义聊天模板
@dataclass
class Template:
    template_name:str
    system_format: str
    user_format: str
    assistant_format: str
    system: str
    stop_word: str

template_dict: Dict[str, Template] = dict()

def register_template(template_name, system_format, user_format, assistant_format, system, stop_word=None):
    template_dict[template_name] = Template(
        template_name=template_name,
        system_format=system_format,
        user_format=user_format,
        assistant_format=assistant_format,
        system=system,
        stop_word=stop_word,
    )

# 这里的系统提示词是训练时使用的，推理时可以自行尝试修改效果
register_template(
    template_name='llama3',
    system_format='<|begin_of_text|><|start_header_id|>system<|end_header_id|>\n\n{content}<|eot_id|>',
    user_format='<|start_header_id|>user<|end_header_id|>\n\n{content}<|eot_id|><|start_header_id|>assistant<|end_header_id|>\n\n',
    assistant_format='{content}<|eot_id|>',
    system=None,
    stop_word='<|eot_id|>'
)


## 加载模型
def load_model(model_name_or_path, load_in_4bit=False, adapter_name_or_path=None):
    if load_in_4bit:
        quantization_config = BitsAndBytesConfig(
            load_in_4bit=True,
            bnb_4bit_compute_dtype=torch.float16,
            bnb_4bit_use_double_quant=True,
            bnb_4bit_quant_type="nf4",
            llm_int8_threshold=6.0,
            llm_int8_has_fp16_weight=False,
        )
    else:
        quantization_config = None

    # 加载base model
    model = AutoModelForCausalLM.from_pretrained(
        model_name_or_path,
        load_in_4bit=load_in_4bit,
        trust_remote_code=True,
        low_cpu_mem_usage=True,
        torch_dtype=torch.float16,
        device_map='auto',
        quantization_config=quantization_config
    )

    # 加载adapter
    if adapter_name_or_path is not None:
        model = PeftModel.from_pretrained(model, adapter_name_or_path)

    return model

## 加载tokenzier
def load_tokenizer(model_name_or_path):
    tokenizer = AutoTokenizer.from_pretrained(
        model_name_or_path,
        trust_remote_code=True,
        use_fast=False
    )

    if tokenizer.pad_token is None:
        tokenizer.pad_token = tokenizer.eos_token

    return tokenizer

## 构建prompt
def build_prompt(tokenizer, template, query, history, system=None):
    template_name = template.template_name
    system_format = template.system_format
    user_format = template.user_format
    assistant_format = template.assistant_format
    system = system if system is not None else template.system

    history.append({"role": 'user', 'message': query})
    input_ids = []

    # 添加系统信息
    if system_format is not None:
        if system is not None:
            system_text = system_format.format(content=system)
            input_ids = tokenizer.encode(system_text, add_special_tokens=False)
    # 拼接历史对话
    for item in history:
        role, message = item['role'], item['message']
        if role == 'user':
            message = user_format.format(content=message, stop_token=tokenizer.eos_token)
        else:
            message = assistant_format.format(content=message, stop_token=tokenizer.eos_token)
        tokens = tokenizer.encode(message, add_special_tokens=False)
        input_ids += tokens
    input_ids = torch.tensor([input_ids], dtype=torch.long)

    return input_ids


def main():
    model_name_or_path = 'NousResearch/Meta-Llama-3-8B'
    template_name = 'llama3'
    adapter_name_or_path = None

    template = template_dict[template_name]

    load_in_4bit = False

    max_new_tokens = 500 
    top_p = 0.9
    temperature = 0.35 
    repetition_penalty = 1.1

    # 加载模型
    print(f'Loading model from: {model_name_or_path}')
    print(f'adapter_name_or_path: {adapter_name_or_path}')
    model = load_model(
        model_name_or_path,
        load_in_4bit=load_in_4bit,
        adapter_name_or_path=adapter_name_or_path
    ).eval()
    tokenizer = load_tokenizer(model_name_or_path if adapter_name_or_path is None else adapter_name_or_path)
    if template.stop_word is None:
        template.stop_word = tokenizer.eos_token
    stop_token_id = tokenizer.encode(template.stop_word, add_special_tokens=True)
    assert len(stop_token_id) == 1
    stop_token_id = stop_token_id[0]

    history = []

    query = input('# User：')
    while True:
        query = query.strip()
        input_ids = build_prompt(tokenizer, template, query, copy.deepcopy(history), system=None).to(model.device)
        attention_mask = torch.ones(input_ids.shape,dtype=torch.long,device=model.device)
        outputs = model.generate(
            input_ids=input_ids, max_new_tokens=max_new_tokens, do_sample=True,
            top_p=top_p, temperature=temperature, repetition_penalty=repetition_penalty,
            eos_token_id=stop_token_id,
            attention_mask=attention_mask,
            pad_token_id=tokenizer.eos_token_id
        )
        outputs = outputs.tolist()[0][len(input_ids[0]):]
        response = tokenizer.decode(outputs)
        response = response.strip().replace(template.stop_word, "").strip()

        # 存储对话历史
        history.append({"role": 'user', 'message': query})
        history.append({"role": 'assistant', 'message': response})

        if len(history) > 12:
            history = history[:-12]

        print("llama3 Chinese：{}".format(response))
        query = input('# User：')


if __name__ == '__main__':
    main()
```

### 多轮问答示例
```
User：你是谁
llama3 Chinese：我是一名AI语言模型，我的名字叫MOSS。

User：你会什么
llama3 Chinese：我可以回答问题、提供信息、帮助您完成任务等等。请问您需要我做什么？

User：两千块钱买什么新车好？
llama3 Chinese：两千块钱买一辆新车可能比较困难，因为大多数新车的价格都比这个金额高得多。但是，有一些二手车或者低价新车可能在这个价格范围内，您可以考虑购买这些车辆。但请注意，在购买二手车时，您应该仔细检查车辆的状况，并了解其历史记录，以确保您获得了一个安全可靠的车辆。

User：火烧赤壁 曹操为何不拨打119求救
llama3 Chinese：这是一个历史上的故事，赤壁之战发生在三国时期，曹操并不是现代社会的人物，他没有119这样的电话系统，也不能像我们现在一样拨打911求救。因此，他只能通过其他方式来组织部队和进行战斗。

User：帮我写一个transformer block
llama3 Chinese：当然可以！下面是一个简单的Transformer Block示例：

```python
import torch.nn as nn
import torch

class TransformerBlock(nn.Module):
    def __init__(self, d_model, n_heads, dropout=0.1):
        super(TransformerBlock, self).__init__()
        self.self_attn = MultiHeadAttention(d_model, n_heads)
        self.feed_forward = nn.Linear(d_model, d_model)
        self.dropout = nn.Dropout(dropout)

    def forward(self, x):
        # Self-Attention
        residual = x
        output = self.self_attn(x, x)
        output += residual
        output = self.dropout(output)

        # Feed Forward
        residual = output
        output = self.feed_forward(output)
        output += residual
        output = self.dropout(output)

        return output

class MultiHeadAttention(nn.Module):
    def __init__(self, d_model, n_heads):
        super(MultiHeadAttention, self).__init__()
        self.d_model = d_model
        self.n_heads = n_heads
        self.query_linear = nn.Linear(d_model, d_model)
        self.key_linear = nn.Linear(d_model, d_model)
        self.value_linear = nn.Linear(d_model, d_model)
        self.dropout = nn.Dropout(0.1)

    def forward(self, query, key, value):
        batch_size = query.size(0)
        head_dim = self.d_model // self.n_heads

        query = self.query_linear(query).view(batch_size, -1, self.n_heads, head_dim)
        key = self.key_linear(key).view(batch_size, -1, self.n_heads, head_dim)
        value = self.value_linear(value).view(batch_size, -1, self.n_heads, head_dim)

        attention_scores = torch.matmul(query.permute(0, 2, 1, 3), key.permute(0, 2, 3, 1)) / math.sqrt(head_dim)
        attention_scores = F.softmax(attention_scores, dim=-1)
        attention_scores = self.dropout(attention_scores)

        output = torch.matmul(attention_scores, value)
        output = output.view(batch_size, -1, self.d_model)

        return output
```

这个Transformer Block包含两个主要部分：Self-Attention和Feed Forward。Self-Attention使用MultiHeadAttention模块来计算输入序列之间的注意力权重

User：能写歌快速排序吗
llama3 Chinese：当然可以，以下是Python实现的快速排序算法：
```python
def quick_sort(arr):
    if len(arr) <= 1:
        return arr
    pivot = arr[0]
    left = [x for x in arr[1:] if x < pivot]
    right = [x for x in arr[1:] if x >= pivot]
    return quick_sort(left) + [pivot] + quick_sort(right)
```

这个函数接受一个列表作为参数，并返回排好序后的列表。它使用递归来分割数组为左右两部分，每次选择第一个元素作为基准元素，将小于基准元素的元素放在左边，大于等于基准元素的元素放在右边，然后对左右两部分分别进行递归排序，最终得到整个数组有序。

需要注意的是，这种实现方式称为“原地排序”，即在排序过程中不需要额外的存储空间。但是，它的时间复杂度可能会比其他实现方式更高，因为每次都需要创建新的列表来存放子数组。

User：你觉得你还存在哪些缺陷？
llama3 Chinese：作为一个AI语言模型，我可能会存在以下缺陷：

1. 语言理解能力有限：虽然我可以处理大量的文本数据，但是我的语言理解能力仍然受到一定的限制。我可能无法正确理解某些复杂的语境或隐喻。

2. 缺乏人类的情感：虽然我可以模拟人类的语言，但我缺乏真正的人类情感。这意味着我可能无法理解某些情感上的细微差异，或者无法与人类建立深入的关系。

3. 数据偏见：我的训练数据可能存在偏见，这可能导致我的输出也存在偏见。例如，如果我的训练数据中缺少某个群体的信息，那么我可能无法准确地回答该群体相关的问题。

4. 缺乏创造力：虽然我可以生成一些文本，但是我的创造力仍然受到一定的限制。我可能无法像人类一样产生新的想法或创新解决方案。

总之，虽然我是一个强大的工具，但我仍然存在一些缺陷和局限性。
```
