精品欧美一区二区三区在线观看 _久久久久国色av免费观看性色_国产精品久久在线观看_亚洲第一综合网站_91精品又粗又猛又爽_小泽玛利亚一区二区免费_91亚洲精品国偷拍自产在线观看 _久久精品视频在线播放_美女精品久久久_欧美日韩国产成人在线

揭秘大模型的魔法:從零實現一個簡化版的GPT 模型

人工智能
GPT系列模型因其強大的生成能力和靈活性,成為了研究和應用的焦點。本文將帶你從零開始,揭開 GPT 模型的“魔法”面紗,通過理論講解和代碼示例,逐步實現一個簡化版的GPT 模型。

大家好,我是寫代碼的中年人!今天我們結合代碼從零實現一個簡化版 GPT 模型。

近年來,大語言模型席卷了人工智能領域,從 ChatGPT 到 LLaMA,它們以驚人的語言理解和生成能力改變了我們與機器交互的方式。

其中,GPT系列模型因其強大的生成能力和靈活性,成為了研究和應用的焦點。本文將帶你從零開始,揭開 GPT 模型的“魔法”面紗,通過理論講解和代碼示例,逐步實現一個簡化版的GPT 模型。

01、從零實現簡化版 GPT 模型的意義

近幾年,大模型火遍全球,成為推動人工智能發展的核心力量。我們每天都在使用這些模型,卻常常會好奇:

GPT 模型到底是怎么“理解”語言的?

分詞、注意力、Transformer 這些名詞具體意味著什么?

如果不用現成的 HuggingFace Transformers 庫,能否自己實現一個“微縮版 GPT”?

答案是:可以!

本文的目標是帶你揭開 GPT 的魔法,從數據處理到模型搭建,再到訓練與文本生成,完整走一遍。雖然我們的模型規模遠遠比不上真正的 GPT-2,但核心思想是一致的。只要你掌握了這里的思路,就能理解大模型的“秘密”。

02、GPT 模型的核心組件與結構

在動手實現 GPT 模型之前,我們先快速了解其核心結構,其所有核心結構我們在前面已經講過:

分詞器(Tokenizer):

GPT 將文本切分為子詞單元(subword),以平衡詞表大小和語義表達能力。常用算法為 BPE(Byte Pair Encoding),如 GPT-2 和 GPT-3 使用的基于 BPE 的分詞器。本次我們使用SentencePiece。

嵌入層(Embedding):

將離散的 token id 轉換為連續的向量表示。加入可學習的位置編碼(Learned Positional Encoding),以捕捉 token 在序列中的位置信息。

多頭自注意力(Multi-Head Self Attention):

GPT 的核心機制,通過計算 Query、Key、Value 捕捉 token 之間的依賴關系。使用 masked self-attention,確保預測下一個 token 時只關注之前的上下文。

前饋層(Feed Forward Network):

注意力層后的非線性變換,增強模型的表達能力。通常由兩層全連接網絡組成,中間加入激活函數(如 ReLU 或 GELU)。

Transformer Block: 

由多頭自注意力層、前饋層、殘差連接和 LayerNorm 組成。殘差連接緩解梯度消失問題,LayerNorm 穩定訓練過程。通過堆疊多個 Transformer Block 構建深層網絡結構。

輸出層:

將隱狀態通過線性變換和 softmax 函數映射到詞表大小的概率分布,預測下一個 token。常結合 top-k 或 top-p 采樣策略處理大規模詞表。

訓練目標:

GPT 采用自回歸語言建模,目標是預測下一個 token。使用交叉熵損失函數,優化器通常為 Adam 或其變種。

03、代碼實現及訓練過程

數據準備

我們本次使用完整的《水滸傳》數據,一個章節一行,保存為: raw_shuihu.txt。如下圖:

圖片

清洗數據

清洗《水滸傳》原始文本,生成適合訓練的純凈語料,自動按中文標點(。!?)切分成句子,每句一行,保存為:shuihu.txt。(此處只做最容易的切割)

# path: tools/clean_shuihu.py
"""
清洗《水滸傳》原始文本,生成適合訓練的純凈語料
會自動按中文標點(。!?)切分成句子,每句一行
用法:
    python tools/clean_shuihu.py data/raw_shuihu.txt data/shuihu.txt
"""


import sys
import re


def clean_text(text: str) -> str:
    #  只保留中文和常見標點
    allowed = re.compile(r"[^\u4e00-\u9fff。,、!?:;()《》——…\n ]+")
    text = allowed.sub(" ", text)


    #  去掉多余空格
    text = re.sub(r"\s+", " ", text)


    #  去掉章節標題 “第X回 …”
    text = re.sub(r"第[一二三四五六七八九十百千0-9]+回.*\n", "", text)


    #  按標點分句(句號、問號、感嘆號),切分后加回標點
    sentences = re.split(r"([。!?])", text)
    merged = []
    for i in range(0, len(sentences)-1, 2):
        s = sentences[i].strip()
        p = sentences[i+1].strip()
        if s:
            merged.append(s + p)


    # 去掉空白句子,避免太短
    merged = [s for s in merged if len(s) > 1]


    return "\n".join(merged)


def main():
    if len(sys.argv) != 2 and len(sys.argv) != 3:
        print("用法: python tools/clean_shuihu.py 輸入文件 [輸出文件]")
        sys.exit(1)


    in_path = sys.argv[1]
    out_path = sys.argv[2] if len(sys.argv) == 3 else "data/shuihu.txt"


    with open(in_path, "r", encoding="utf-8") as f:
        raw = f.read()


    clean = clean_text(raw)


    with open(out_path, "w", encoding="utf-8") as f:
        f.write(clean)


    print(f"清洗完成,輸出保存到 {out_path}")
    print(f"示例前5行:\n" + "\n".join(clean.splitlines()[:5]))


if __name__ == "__main__":
    main()
# 程序輸出
洗完成,輸出保存到 data/shuihu.txt
示例前5行:
水滸傳第一段 話說大宋仁宗天子在位,嘉祐三年三月三日五更三點,天子駕坐紫宸殿,受百官朝賀。
當有殿頭官喝道: 有事出班早奏,無事卷簾退朝。
只見班部叢中,宰相趙哲、參政文彥博出班奏曰: 目今京師瘟疫盛行,民不聊生,傷損軍民多矣。
伏望陛下釋罪寬恩,省刑薄稅,以禳天災,救濟萬民。
天子聽奏,急敕翰林院隨即草詔:一面降赦天下罪囚,應有民間稅賦悉皆赦免;一面命在京宮觀寺院,修設好事禳災。

訓練分詞器

此處使用SentencePiece分詞方法,SentencePiece 是一種開源的子詞分詞工具,由 Google 研發。它的主要特點是語言無關和可逆性,能將任何語言的文本序列無損地轉換成子詞序列。

# path: scripts/train_tokenizer.py
"""
訓練 SentencePiece 分詞器 (BPE)
用法:
    python scripts/train_tokenizer.py data/shuihu.txt workdir/spm_shuihu 8000
"""


import sys
import os
import sentencepiece as spm


def train_tokenizer(input_path: str, model_prefix: str, vocab_size: int = 8000):
    if not os.path.exists(input_path):
        raise FileNotFoundError(f"語料文件不存在: {input_path}")


    print(f"[INFO] 開始訓練分詞器: {input_path}")
    spm.SentencePieceTrainer.Train(
        f"--input={input_path} "
        f"--model_prefix={model_prefix} "
        f"--vocab_size={vocab_size} "
        f"--model_type=bpe "
        f"--character_coverage=0.9995 "
        f"--bos_id=1 --eos_id=2 --unk_id=3"
    )
    print(f"[INFO] 分詞器已保存: {model_prefix}.model / {model_prefix}.vocab")


def main():
    if len(sys.argv) < 3:
        print("用法: python scripts/train_tokenizer.py 輸入文本 模型前綴 [詞表大小]")
        sys.exit(1)


    input_path = sys.argv[1]
    model_prefix = sys.argv[2]
    vocab_size = int(sys.argv[3]) if len(sys.argv) >= 4 else 8000


    train_tokenizer(input_path, model_prefix, vocab_size)


if __name__ == "__main__":
    main()
# 輸出 
[INFO] 開始訓練分詞器: data/shuihu.txt
sentencepiece_trainer.cc(178) LOG(INFO) Running command: --input=data/shuihu.txt --model_prefix=workdir/spm_shuihu --vocab_size=8000 --model_type=bpe --character_coverage=0.9995 --bos_id=1 --eos_id=2 --unk_id=3
sentencepiece_trainer.cc(78) LOG(INFO) Starts training with : 
trainer_spec {
  input: data/shuihu.txt
  input_format: 
  model_prefix: workdir/spm_shuihu
  model_type: BPE
  vocab_size: 8000
  self_test_sample_size: 0
  character_coverage: 0.9995
  input_sentence_size: 0
  shuffle_input_sentence: 1
  seed_sentencepiece_size: 1000000
  shrinking_factor: 0.75
  max_sentence_length: 4192
  num_threads: 16
  num_sub_iterations: 2
  max_sentencepiece_length: 16
  split_by_unicode_script: 1
  split_by_number: 1
  split_by_whitespace: 1
  split_digits: 0
  pretokenization_delimiter: 
  treat_whitespace_as_suffix: 0
  allow_whitespace_only_pieces: 0
  required_chars: 
  byte_fallback: 0
  vocabulary_output_piece_score: 1
  train_extremely_large_corpus: 0
  seed_sentencepieces_file: 
  hard_vocab_limit: 1
  use_all_vocab: 0
  unk_id: 3
  bos_id: 1
  eos_id: 2
  pad_id: -1
  unk_piece: <unk>
  bos_piece: <s>
  eos_piece: </s>
  pad_piece: <pad>
  unk_surface:  ? 
  enable_differential_privacy: 0
  differential_privacy_noise_level: 0
  differential_privacy_clipping_threshold: 0
}
normalizer_spec {
  name: nmt_nfkc
  add_dummy_prefix: 1
  remove_extra_whitespaces: 1
  escape_whitespaces: 1
  normalization_rule_tsv: 
}

訓練簡化版 GPT 模型(使用絕對位置編碼)

此處我們使用絕對位置編碼進行訓練,模型參數為8M,在RTX4090 上進行訓練,平均占用顯存1G內,訓練時間:45分鐘左右。

# path: scripts/train_gpt.py
"""
訓練簡化版 GPT 模型
用法:
    python scripts/train_gpt.py workdir/spm_shuihu.model data/shuihu.txt workdir/gpt_shuihu.pth
"""


import sys
import re
import math
import torch
from torch import nn
from torch.utils.data import Dataset, DataLoader
import sentencepiece as spm
from tqdm import tqdm


DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
BLOCK_SIZE = 128
BATCH_SIZE = 16
EPOCHS = 5
LR = 3e-4


def clean_text(text: str) -> str:
    allowed = re.compile(r"[^\u4e00-\u9fff。,、!?:;()《》——…\n ]+")
    text = allowed.sub(" ", text)
    text = re.sub(r"\s+", " ", text)
    return text.strip()


class TextDataset(Dataset):
    def __init__(self, token_ids, block_size):
        self.ids = token_ids
        self.block_size = block_size


    def __len__(self):
        return max(0, len(self.ids) - self.block_size)


    def __getitem__(self, idx):
        x = torch.tensor(self.ids[idx: idx + self.block_size], dtype=torch.long)
        y = torch.tensor(self.ids[idx + 1: idx + 1 + self.block_size], dtype=torch.long)
        return x, y


class MultiHeadSelfAttention(nn.Module):
    def __init__(self, embed_dim, num_heads, attn_dropout=0.1):
        super().__init__()
        assert embed_dim % num_heads == 0
        self.head_dim = embed_dim // num_heads
        self.num_heads = num_heads
        self.qkv = nn.Linear(embed_dim, embed_dim * 3)
        self.out = nn.Linear(embed_dim, embed_dim)
        self.drop = nn.Dropout(attn_dropout)


    def forward(self, x):
        B, T, C = x.shape
        qkv = self.qkv(x).chunk(3, dim=-1)
        q, k, v = [t.view(B, T, self.num_heads, self.head_dim).transpose(1, 2) for t in qkv]
        att = (q @ k.transpose(-2, -1)) / math.sqrt(self.head_dim)
        mask = torch.tril(torch.ones(T, T, device=x.device)).unsqueeze(0).unsqueeze(0)
        att = att.masked_fill(mask == 0, float("-inf"))
        att = torch.softmax(att, dim=-1)
        att = self.drop(att)
        out = att @ v
        out = out.transpose(1, 2).contiguous().view(B, T, C)
        return self.out(out)


class FeedForward(nn.Module):
    def __init__(self, dim, hidden_dim, dropout=0.1):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(dim, hidden_dim),
            nn.GELU(),
            nn.Linear(hidden_dim, dim),
            nn.Dropout(dropout)
        )
    def forward(self, x): return self.net(x)


class TransformerBlock(nn.Module):
    def __init__(self, dim, num_heads, dropout=0.1):
        super().__init__()
        self.ln1 = nn.LayerNorm(dim)
        self.attn = MultiHeadSelfAttention(dim, num_heads, dropout)
        self.ln2 = nn.LayerNorm(dim)
        self.ff = FeedForward(dim, dim*4, dropout)


    def forward(self, x):
        x = x + self.attn(self.ln1(x))
        x = x + self.ff(self.ln2(x))
        return x


class GPTLike(nn.Module):
    def __init__(self, vocab_size, block_size, n_layers=2, dim=128, num_heads=4):
        super().__init__()
        self.token_emb = nn.Embedding(vocab_size, dim)
        self.pos_emb = nn.Embedding(block_size, dim)
        self.blocks = nn.ModuleList([TransformerBlock(dim, num_heads) for _ in range(n_layers)])
        self.ln = nn.LayerNorm(dim)
        self.head = nn.Linear(dim, vocab_size)
        self.block_size = block_size


    def forward(self, idx):
        B, T = idx.shape
        x = self.token_emb(idx) + self.pos_emb(torch.arange(T, device=idx.device))
        for block in self.blocks:
            x = block(x)
        return self.head(self.ln(x))




def train(model, dataset, epochs=EPOCHS, batch_size=BATCH_SIZE, lr=LR):
    loader = DataLoader(dataset, batch_size=batch_size, shuffle=True)
    opt = torch.optim.AdamW(model.parameters(), lr=lr)
    loss_fn = nn.CrossEntropyLoss()
    model.train()
    step = 0
    for ep in range(epochs):
        total_loss = 0
        pbar = tqdm(loader, desc=f"Epoch {ep+1}/{epochs}")
        for xb, yb in pbar:
            xb, yb = xb.to(DEVICE), yb.to(DEVICE)
            logits = model(xb)
            loss = loss_fn(logits.view(-1, logits.size(-1)), yb.view(-1))
            opt.zero_grad()
            loss.backward()
            opt.step()
            total_loss += loss.item()
            step += 1
            if step % 100 == 0:
                pbar.set_postfix(loss=f"{loss.item():.4f}")
        avg_loss = total_loss / len(loader)
        ppl = math.exp(avg_loss)
        print(f"[Epoch {ep+1}] Avg Loss {avg_loss:.4f} | PPL {ppl:.2f}")




def main():
    if len(sys.argv) < 4:
        print("用法: python scripts/train_gpt.py 分詞器模型 輸入語料 輸出模型")
        sys.exit(1)


    sp_model, corpus_path, out_path = sys.argv[1], sys.argv[2], sys.argv[3]
    sp = spm.SentencePieceProcessor(model_file=sp_model)


    with open(corpus_path, encoding="utf-8") as f:
        text = clean_text(f.read())
    ids = sp.encode(text, out_type=int)


    dataset = TextDataset(ids, BLOCK_SIZE)
    model = GPTLike(sp.get_piece_size(), BLOCK_SIZE).to(DEVICE)
    train(model, dataset)
    torch.save(model.state_dict(), out_path)
    print(f"模型已保存: {out_path}")


if __name__ == "__main__":
    main()
# 輸出 
Epoch 1/5: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 38619/38619 [08:52<00:00, 72.47it/s, loss=3.5714]
[Epoch 1] Avg Loss 4.4607 | PPL 86.55
Epoch 2/5: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 38619/38619 [08:54<00:00, 72.23it/s, loss=2.9516]
[Epoch 2] Avg Loss 3.2717 | PPL 26.36
Epoch 3/5: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 38619/38619 [08:51<00:00, 72.63it/s, loss=2.6000]
[Epoch 3] Avg Loss 2.8272 | PPL 16.90
Epoch 4/5: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 38619/38619 [08:54<00:00, 72.28it/s, loss=2.5234]
[Epoch 4] Avg Loss 2.5424 | PPL 12.71
Epoch 5/5: 100% |██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 38619/38619 [08:54<00:00, 72.28it/s, loss=2.2621]
[Epoch 5] Avg Loss 2.3380 | PPL 10.36
模型已保存: workdir/gpt_shuihu.pth

訓練簡化版 GPT 模型(使用旋轉位置編碼RoPE)

此處我們使用旋轉位置編碼進行訓練,模型參數為8M,在RTX4090 上進行訓練,平均占用顯存1G內,訓練時間:45分鐘左右。

# path: scripts/train_gpt_RoPE.py
"""
訓練簡化版 GPT 模型,使用旋轉位置編碼(RoPE)
用法:
    python scripts/train_gpt_RoPE.py workdir/spm_shuihu.model data/shuihu.txt workdir/gpt_shuihu_RoPE.pth
"""


import sys
import re
import math
import torch
from torch import nn
from torch.utils.data import Dataset, DataLoader
import sentencepiece as spm
from tqdm import tqdm


DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
BLOCK_SIZE = 128
BATCH_SIZE = 16
EPOCHS = 5
LR = 3e-4


def clean_text(text: str) -> str:
    allowed = re.compile(r"[^\u4e00-\u9fff。,、!?:;()《》——…\n ]+")
    text = allowed.sub(" ", text)
    text = re.sub(r"\s+", " ", text)
    return text.strip()


class TextDataset(Dataset):
    def __init__(self, token_ids, block_size):
        self.ids = token_ids
        self.block_size = block_size


    def __len__(self):
        return max(0, len(self.ids) - self.block_size)


    def __getitem__(self, idx):
        x = torch.tensor(self.ids[idx: idx + self.block_size], dtype=torch.long)
        y = torch.tensor(self.ids[idx + 1: idx + 1 + self.block_size], dtype=torch.long)
        return x, y


def apply_rope(x, freqs):
    # x: (B, T, n_heads, head_dim)
    # freqs: (T, head_dim)
    # 拆分 x 為 x_real 和 x_imag
    x_real, x_imag = x.float().view(*x.shape[:-1], -1, 2).unbind(-1)


    # 將 freqs 擴展到和 x_real 一樣的形狀
    freqs = freqs.unsqueeze(0).unsqueeze(0)
    freqs_real, freqs_imag = freqs.view(*freqs.shape[:-1], -1, 2).unbind(-1)


    # 應用旋轉
    x_rotated_real = x_real * freqs_real - x_imag * freqs_imag
    x_rotated_imag = x_real * freqs_imag + x_imag * freqs_real


    x_rotated = torch.stack((x_rotated_real, x_rotated_imag), dim=-1).flatten(start_dim=-2)
    return x_rotated.type_as(x)




class MultiHeadSelfAttention(nn.Module):
    def __init__(self, embed_dim, num_heads, attn_dropout=0.1):
        super().__init__()
        assert embed_dim % num_heads == 0
        self.head_dim = embed_dim // num_heads
        self.num_heads = num_heads
        self.qkv = nn.Linear(embed_dim, embed_dim * 3)
        self.out = nn.Linear(embed_dim, embed_dim)
        self.drop = nn.Dropout(attn_dropout)
        self.register_buffer("freqs", self._create_freqs_buffer())


    def _create_freqs_buffer(self):
        # 創建旋轉頻率
        head_dim = self.head_dim
        pos = torch.arange(BLOCK_SIZE, dtype=torch.float32)
        dim = torch.arange(0, head_dim, 2, dtype=torch.float32)
        inv_freq = 1.0 / (10000 ** (dim / head_dim))
        freqs = torch.outer(pos, inv_freq)
        return torch.stack([torch.cos(freqs), torch.sin(freqs)], dim=-1).flatten(-2)


    def forward(self, x):
        B, T, C = x.shape
        qkv = self.qkv(x).chunk(3, dim=-1)
        q, k, v = [t.view(B, T, self.num_heads, self.head_dim).transpose(1, 2) for t in qkv]


        # 應用 RoPE 到 q 和 k
        q = apply_rope(q, self.freqs[:T].to(q.device))
        k = apply_rope(k, self.freqs[:T].to(k.device))


        att = (q @ k.transpose(-2, -1)) / math.sqrt(self.head_dim)
        mask = torch.tril(torch.ones(T, T, device=x.device)).unsqueeze(0).unsqueeze(0)
        att = att.masked_fill(mask == 0, float("-inf"))
        att = torch.softmax(att, dim=-1)
        att = self.drop(att)
        out = att @ v
        out = out.transpose(1, 2).contiguous().view(B, T, C)
        return self.out(out)


class FeedForward(nn.Module):
    def __init__(self, dim, hidden_dim, dropout=0.1):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(dim, hidden_dim),
            nn.GELU(),
            nn.Linear(hidden_dim, dim),
            nn.Dropout(dropout)
        )
    def forward(self, x): return self.net(x)


class TransformerBlock(nn.Module):
    def __init__(self, dim, num_heads, dropout=0.1):
        super().__init__()
        self.ln1 = nn.LayerNorm(dim)
        self.attn = MultiHeadSelfAttention(dim, num_heads, dropout)
        self.ln2 = nn.LayerNorm(dim)
        self.ff = FeedForward(dim, dim*4, dropout)


    def forward(self, x):
        x = x + self.attn(self.ln1(x))
        x = x + self.ff(self.ln2(x))
        return x


class GPTLike(nn.Module):
    def __init__(self, vocab_size, block_size, n_layers=2, dim=128, num_heads=4):
        super().__init__()
        # 移除原有的位置嵌入層
        self.token_emb = nn.Embedding(vocab_size, dim)
        self.blocks = nn.ModuleList([TransformerBlock(dim, num_heads) for _ in range(n_layers)])
        self.ln = nn.LayerNorm(dim)
        self.head = nn.Linear(dim, vocab_size)
        self.block_size = block_size


    def forward(self, idx):
        B, T = idx.shape
        # 直接使用 token 嵌入,位置信息在注意力層中處理
        x = self.token_emb(idx)
        for block in self.blocks:
            x = block(x)
        return self.head(self.ln(x))


def train(model, dataset, epochs=EPOCHS, batch_size=BATCH_SIZE, lr=LR):
    loader = DataLoader(dataset, batch_size=batch_size, shuffle=True)
    opt = torch.optim.AdamW(model.parameters(), lr=lr)
    loss_fn = nn.CrossEntropyLoss()
    model.train()
    step = 0
    for ep in range(epochs):
        total_loss = 0
        pbar = tqdm(loader, desc=f"Epoch {ep+1}/{epochs}")
        for xb, yb in pbar:
            xb, yb = xb.to(DEVICE), yb.to(DEVICE)
            logits = model(xb)
            loss = loss_fn(logits.view(-1, logits.size(-1)), yb.view(-1))
            opt.zero_grad()
            loss.backward()
            opt.step()
            total_loss += loss.item()
            step += 1
            if step % 100 == 0:
                pbar.set_postfix(loss=f"{loss.item():.4f}")
        avg_loss = total_loss / len(loader)
        ppl = math.exp(avg_loss)
        print(f"[Epoch {ep+1}] Avg Loss {avg_loss:.4f} | PPL {ppl:.2f}")




def main():
    if len(sys.argv) < 4:
        print("用法: python scripts/train_gpt_RoPE.py 分詞器模型 輸入語料 輸出模型")
        sys.exit(1)


    sp_model, corpus_path, out_path = sys.argv[1], sys.argv[2], sys.argv[3]
    sp = spm.SentencePieceProcessor(model_file=sp_model)


    with open(corpus_path, encoding="utf-8") as f:
        text = clean_text(f.read())
    ids = sp.encode(text, out_type=int)


    dataset = TextDataset(ids, BLOCK_SIZE)
    model = GPTLike(sp.get_piece_size(), BLOCK_SIZE).to(DEVICE)
    train(model, dataset)
    torch.save(model.state_dict(), out_path)
    print(f"模型已保存: {out_path}")


if __name__ == "__main__":
    main()
# 輸出 
Epoch 1/5: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 38619/38619 [11:08<00:00, 57.79it/s, loss=3.0254]
[Epoch 1] Avg Loss 3.9948 | PPL 54.31
Epoch 2/5: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 38619/38619 [11:14<00:00, 57.27it/s, loss=2.7059]
[Epoch 2] Avg Loss 2.9947 | PPL 19.98
Epoch 3/5: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 38619/38619 [11:11<00:00, 57.47it/s, loss=2.4179]
[Epoch 3] Avg Loss 2.6470 | PPL 14.11
Epoch 4/5: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 38619/38619 [11:12<00:00, 57.39it/s, loss=2.2415]
[Epoch 4] Avg Loss 2.4296 | PPL 11.35
Epoch 5/5: 100%|██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████| 38619/38619 [11:07<00:00, 57.88it/s, loss=2.2116]
[Epoch 5] Avg Loss 2.2804 | PPL 9.78
模型已保存: workdir/gpt_shuihu_RoPE.pth

使用訓練的模型進行推理(絕對位置編碼)

我們使用訓練好的模型gpt_shuihu.pth進行推理測試。

# path: scripts/predict_gpt.py
"""
使用訓練好的 GPT 模型生成文本
用法:
    python scripts/predict_gpt.py workdir/spm_shuihu.model workdir/gpt_shuihu.pth "宋江在梁山泊"
"""


import sys
import torch
import sentencepiece as spm
from train_gpt import GPTLike, DEVICE, BLOCK_SIZE


@torch.no_grad()
def generate(model, sp, prompt, max_new_tokens=100, temperature=1.0, top_k=50):
    idx = torch.tensor([sp.encode(prompt, out_type=int)], device=DEVICE)
    for _ in range(max_new_tokens):
        idx_cond = idx[:, -BLOCK_SIZE:]
        logits = model(idx_cond)[:, -1, :] / temperature
        if top_k:
            v, _ = torch.topk(logits, top_k)
            logits[logits < v[:, [-1]]] = -1e10
        probs = torch.softmax(logits, dim=-1)
        next_id = torch.multinomial(probs, 1)
        idx = torch.cat([idx, next_id], dim=1)
    return sp.decode(idx[0].tolist())


def main():
    if len(sys.argv) < 4:
        print("用法: python scripts/predict_gpt.py 分詞器模型 已訓練模型 輸入提示")
        sys.exit(1)


    sp_model, model_path, prompt = sys.argv[1], sys.argv[2], sys.argv[3]
    sp = spm.SentencePieceProcessor(model_file=sp_model)


    vocab_size = sp.get_piece_size()
    model = GPTLike(vocab_size, BLOCK_SIZE).to(DEVICE)
    model.load_state_dict(torch.load(model_path, map_locatinotallow=DEVICE))
    model.eval()


    result = generate(model, sp, prompt)
    print("=== 輸入提示 ===")
    print(prompt)
    print("=== 生成結果 ===")
    print(result)


if __name__ == "__main__":
    main()
ColinAI# python scripts/predict_gpt.py workdir/spm_shuihu.model workdir/gpt_shuihu.pth "武松在景陽岡"
=== 輸入提示 ===
武松在景陽岡
=== 生成結果 ===
武松在景陽岡子上,只見那張蒙管營手里道: 你認得這個賊人,不敢來投奔他。 那個是開娘的孩兒,也不回了,卻來胡亂尋死吃過! 蔣門神在地下,那里肯放。 何九叔道: 中了菜,放囊的說出武松面前,望后便倒了。 武松道: 嫂嫂在此? 小人埋怨我,又吃棒,不值得饑便說道: 好歹教你一個。 你好不曉事! 武松道: 都頭休多說
ColinAI# python scripts/predict_gpt.py workdir/spm_shuihu.model workdir/gpt_shuihu.pth "魯智深"
=== 輸入提示 ===
魯智深
=== 生成結果 ===
魯智深皂羅巾。 張清把槍拈起弓,搭上箭,把馬一拍,把郝思文后心。 雷橫這匹馬,望本陣便走。 雷橫把弓卷了一箭,射射來掃蕩,只一石子來。 雷橫大怒,把這一所眼睜春心頭,便帶著行枷呀,直走到縣住左腳。 宋江仰鞭梢一指,接著去了。 朱仝回到寨中,接著晁蓋,分賓主而坐。 小衙內中并訴舊音仰著。 吳學究
ColinAI# python scripts/predict_gpt.py workdir/spm_shuihu.model workdir/gpt_shuihu.pth "林教頭在東京"
=== 輸入提示 ===
林教頭在東京
=== 生成結果 ===
林教頭在東京住雕欄、布列做一盤,紫綬金章;銀朱紅甲章,前逢龍袍并首副座。 隨班列著一應果子、金字匠金大堅,絹一套絹帛縛朱戶。 三廂房中揀日,造些油紙剪獅。 那新官初時營后與王慶、段三娘。 次后前踅過東,一帶高彪將,向后一個頭領二員:呼保義宋江、王義公、婁
ColinAI#

使用訓練的模型進行推理(旋轉位置編碼RoPE)

我們使用訓練好的模型gpt_shuihu_RoPE.pth進行推理測試。

# path: scripts/predict_gpt_RoPE.py
"""
使用訓練好的 GPT 模型(RoPE版本)生成文本
用法:
    python scripts/predict_gpt_RoPE.py workdir/spm_shuihu.model workdir/gpt_shuihu_RoPE.pth "林教頭在東京"
"""


import sys
import torch
import sentencepiece as spm


DEVICE = "cuda" if torch.cuda.is_available() else "cpu"
BLOCK_SIZE = 128


# ----------------- RoPE 模型定義開始 -----------------
import math
from torch import nn


def apply_rope(x, freqs):
    x_real, x_imag = x.float().view(*x.shape[:-1], -1, 2).unbind(-1)
    freqs = freqs.unsqueeze(0).unsqueeze(0)
    freqs_real, freqs_imag = freqs.view(*freqs.shape[:-1], -1, 2).unbind(-1)
    x_rotated_real = x_real * freqs_real - x_imag * freqs_imag
    x_rotated_imag = x_real * freqs_imag + x_imag * freqs_imag
    x_rotated = torch.stack((x_rotated_real, x_rotated_imag), dim=-1).flatten(start_dim=-2)
    return x_rotated.type_as(x)


class MultiHeadSelfAttention(nn.Module):
    def __init__(self, embed_dim, num_heads, attn_dropout=0.1):
        super().__init__()
        assert embed_dim % num_heads == 0
        self.head_dim = embed_dim // num_heads
        self.num_heads = num_heads
        self.qkv = nn.Linear(embed_dim, embed_dim * 3)
        self.out = nn.Linear(embed_dim, embed_dim)
        self.drop = nn.Dropout(attn_dropout)
        self.register_buffer("freqs", self._create_freqs_buffer())


    def _create_freqs_buffer(self):
        head_dim = self.head_dim
        pos = torch.arange(BLOCK_SIZE, dtype=torch.float32)
        dim = torch.arange(0, head_dim, 2, dtype=torch.float32)
        inv_freq = 1.0 / (10000 ** (dim / head_dim))
        freqs = torch.outer(pos, inv_freq)
        return torch.stack([torch.cos(freqs), torch.sin(freqs)], dim=-1).flatten(-2)


    def forward(self, x):
        B, T, C = x.shape
        qkv = self.qkv(x).chunk(3, dim=-1)
        q, k, v = [t.view(B, T, self.num_heads, self.head_dim).transpose(1, 2) for t in qkv]


        q = apply_rope(q, self.freqs[:T].to(q.device))
        k = apply_rope(k, self.freqs[:T].to(k.device))


        att = (q @ k.transpose(-2, -1)) / math.sqrt(self.head_dim)
        mask = torch.tril(torch.ones(T, T, device=x.device)).unsqueeze(0).unsqueeze(0)
        att = att.masked_fill(mask == 0, float("-inf"))
        att = torch.softmax(att, dim=-1)
        att = self.drop(att)
        out = att @ v
        out = out.transpose(1, 2).contiguous().view(B, T, C)
        return self.out(out)


class FeedForward(nn.Module):
    def __init__(self, dim, hidden_dim, dropout=0.1):
        super().__init__()
        self.net = nn.Sequential(
            nn.Linear(dim, hidden_dim),
            nn.GELU(),
            nn.Linear(hidden_dim, dim),
            nn.Dropout(dropout)
        )
    def forward(self, x): return self.net(x)


class TransformerBlock(nn.Module):
    def __init__(self, dim, num_heads, dropout=0.1):
        super().__init__()
        self.ln1 = nn.LayerNorm(dim)
        self.attn = MultiHeadSelfAttention(dim, num_heads, dropout)
        self.ln2 = nn.LayerNorm(dim)
        self.ff = FeedForward(dim, dim*4, dropout)


    def forward(self, x):
        x = x + self.attn(self.ln1(x))
        x = x + self.ff(self.ln2(x))
        return x


class GPTLike(nn.Module):
    def __init__(self, vocab_size, block_size, n_layers=2, dim=128, num_heads=4):
        super().__init__()
        # 沒有位置嵌入層
        self.token_emb = nn.Embedding(vocab_size, dim)
        self.blocks = nn.ModuleList([TransformerBlock(dim, num_heads) for _ in range(n_layers)])
        self.ln = nn.LayerNorm(dim)
        self.head = nn.Linear(dim, vocab_size)
        self.block_size = block_size


    def forward(self, idx):
        B, T = idx.shape
        x = self.token_emb(idx)
        for block in self.blocks:
            x = block(x)
        return self.head(self.ln(x))
# ----------------- RoPE 模型定義結束 -----------------


@torch.no_grad()
def generate(model, sp, prompt, max_new_tokens=100, temperature=1.0, top_k=50):
    idx = torch.tensor([sp.encode(prompt, out_type=int)], device=DEVICE)
    for _ in range(max_new_tokens):
        idx_cond = idx[:, -BLOCK_SIZE:]
        logits = model(idx_cond)[:, -1, :] / temperature
        if top_k:
            v, _ = torch.topk(logits, top_k)
            logits[logits < v[:, [-1]]] = -1e10
        probs = torch.softmax(logits, dim=-1)
        next_id = torch.multinomial(probs, 1)
        idx = torch.cat([idx, next_id], dim=1)
    return sp.decode(idx[0].tolist())


def main():
    if len(sys.argv) < 4:
        print("用法: python scripts/predict_gpt_RoPE.py 分詞器模型 已訓練模型 輸入提示")
        sys.exit(1)


    sp_model, model_path, prompt = sys.argv[1], sys.argv[2], sys.argv[3]
    sp = spm.SentencePieceProcessor(model_file=sp_model)


    vocab_size = sp.get_piece_size()
    # 實例化 RoPE 版本的模型
    model = GPTLike(vocab_size, BLOCK_SIZE).to(DEVICE)
    model.load_state_dict(torch.load(model_path, map_locatinotallow=DEVICE))
    model.eval()


    result = generate(model, sp, prompt)
    print("=== 輸入提示 ===")
    print(prompt)
    print("=== 生成結果 ===")
    print(result)


if __name__ == "__main__":
    main()
ColinAI# python scripts/predict_gpt_RoPE.py workdir/spm_shuihu.model workdir/gpt_shuihu_RoPE.pth "林教頭在東京"
=== 輸入提示 ===
林教頭在東京
=== 生成結果 ===
林教頭在東京住了師父。 智深道: 師父休要有些尷尬一般。 走了馬,不提防語,且教庫吏軍漢,取了財帛,裝載上車,犒賞三軍盡做合后,仍送上梁山泊來使,見今引了十數個軍官武德大海軍州以殺之恩;梁山泊未有可出我等進身,到得深村便是殺大蟲,累蒙恩橋下。 更兼做甚么都在那里壞國家有何樂如何? 老丈道: 娘兒兩個新年進
ColinAI# python scripts/predict_gpt_RoPE.py workdir/spm_shuihu.model workdir/gpt_shuihu_RoPE.pth "魯智深"
=== 輸入提示 ===
魯智深
=== 生成結果 ===
魯智深跳將起來,提了禪杖大篦帳,一齊去那樹邊,高聲朗山勇在馬上,如清迎前來。 盧俊義喝采道: 好酒! 引李云有何不可。 宋江扯得地,不是肚皮,口里搖著,口里道: 阿呀,苦也。 哭罷,三人吃過口則藏寺,那漢子使做柴元只在包著,踏口鑿暗暗的耍的性命。 說時已有七八十斤兩眼也不敢殺
ColinAI# python scripts/predict_gpt_RoPE.py workdir/spm_shuihu.model workdir/gpt_shuihu_RoPE.pth "武松在景陽岡"
=== 輸入提示 ===
武松在景陽岡
=== 生成結果 ===
武松在景陽岡子上,推檐下坐地。 武松叫土兵篩來,一面篩酒。 那李鬼坐在廳上坐地。 武松就房里桌子上摸上樓開房門,叫聲你這盆時,被這雪只顧亂攛,將斧喝道: 你這兩個扛我吊在床邊一個,老爺去那嘴,把右手只一掣出青花滾與他出個躲一搠樸刀,一斧來。 眾人近前都饒,打得臉打碎那個鼓鬧黑旋風來。 兩邊眾莊客
ColinAI#

代碼已放在GitHub:

https://github.com/ColinAIAPP/MoiraiLM

結束語

經過訓練后,我們對這個小型類 GPT 模型進行了推理測試。結果顯示,模型往往會輸出一些“胡言亂語”式的文本(預測下一個token),看起來并沒有連貫的語義。這種現象背后有幾個主要原因:

模型規模過小

我們的實驗模型只有很小的參數,而真實的模型起步就是上億參數,ChatGPT、GPT-4 更是數千億參數量級。過小的模型容量限制了它對語言規律的表達能力。

訓練數據有限

我們僅使用了《水滸傳》這一部作品作為訓練語料,而現代大模型的訓練數據規模往往是萬億級別 token,涵蓋多領域、多語言。數據多樣性不足,使得模型無法學到更廣泛的語言知識。

訓練時間不足

在有限硬件和時間下,我們只進行了少量 epoch 的訓練。這種“淺嘗輒止”的訓練無法讓模型充分收斂,也難以形成較強的生成能力。

因此,這個實驗模型只能算是 原理性驗證 ——它向我們展示了 GPT 模型“預測下一個詞”的核心工作方式,卻無法產生高質量的文本。本次實驗主要是 測試預訓練,驗證模型框架和訓練流程的可行性。

在下一步,我們計劃在 更大的數據集 上進行訓練,并嘗試增加模型的深度和寬度,提升參數規模。同時,將引入 監督微調(Supervised Fine-Tuning) 和 RLHF(Reinforcement Learning with Human Feedback) 等訓練策略,以進一步提升模型在生成文本時的質量、連貫性和實用性。在這些改進下,模型的表現將更加接近我們熟悉的大語言模型,實現更高水平的文本生成能力。

責任編輯:龐桂玉 來源: 寫代碼的中年人
相關推薦

2025-06-20 10:18:58

大模型

2025-04-17 09:00:00

2025-10-24 10:41:33

2025-04-25 00:20:00

大模型tokenizer

2024-12-23 12:52:29

2025-08-04 09:31:49

2025-08-18 09:15:00

2025-07-17 09:47:07

2025-08-11 06:17:54

2020-09-24 11:46:03

Promise

2023-04-06 08:01:30

RustMutex

2023-04-23 08:00:00

人工智能ChatGPTGPT模型

2010-05-17 15:50:06

2023-11-28 12:49:01

AI訓練

2017-06-06 10:14:55

KerasTensorFlow深度學習

2009-06-01 09:04:15

Windows 7微軟操作系統

2025-07-16 09:18:06

2025-08-12 02:00:00

AI人工智能大模型

2025-08-08 04:11:00

GPT-OSS大模型算法

2023-11-21 09:00:00

大型語言模型LangChain庫
點贊
收藏

51CTO技術棧公眾號

亚洲国产成人精品电影| 一区二区三区小说| 成人国产精品一区| 久久久久久久久久91| 日韩超碰人人爽人人做人人添| 狠狠躁夜夜躁久久躁别揉| 日韩精品第一页| 精品人妻无码一区二区色欲产成人 | 日韩欧美亚洲日产国产| 国产精品自产拍| 国产一区导航| 久久成人精品视频| 免费看污黄网站在线观看| 亚洲精品一区av| 动漫精品一区二区| 久久免费一级片| 久久精品国产亚洲a∨麻豆| 国产一区二区三区四区在线观看| 欧美一级高清免费播放| 99热精品免费| 欧美一区2区| 亚洲国产精品久久久久秋霞蜜臀| 久热精品在线观看视频| 97人澡人人添人人爽欧美| 国产精品亲子伦对白| 久久草视频在线看| 国产ts变态重口人妖hd| 日韩黄色一级片| 性视频1819p久久| 国产极品国产极品| 久久理论电影| 亚洲视频在线观看视频| 国产又黄又粗又猛又爽的视频| 日韩免费大片| 欧美日韩视频在线第一区| 欧美二区在线视频| 欧美理论片在线播放| **欧美大码日韩| 亚洲欧美日本国产有色| 国产免费永久在线观看| 99久久婷婷国产| 国产另类自拍| 好吊色视频一区二区| 国产毛片一区二区| 91久久精品美女| 一区二区三区免费观看视频| 丝袜国产日韩另类美女| 国产z一区二区三区| 日韩不卡视频在线| 国产精品最新自拍| 1769国内精品视频在线播放| 日韩精品久久久久久久酒店| 亚洲二区视频| 国模私拍一区二区三区| 精品国产91久久久久久| 午夜免费久久久久| 免费又黄又爽又色的视频| 五月婷婷亚洲| 久久精品国产v日韩v亚洲| 中文字幕第24页| 欧美日韩在线网站| 中文在线不卡视频| 天美传媒免费在线观看| 久久人人99| 日韩亚洲在线观看| 国产午夜手机精彩视频| 66视频精品| 欧美日本国产在线| 久久精品欧美一区二区| 日韩网站在线| 日本精品va在线观看| 台湾佬中文在线| 日本欧美一区二区三区| 国产精品视频内| 国产av无码专区亚洲a∨毛片| 国产一区视频导航| 国产欧美日韩伦理| 欧美精品久久久久久久久久丰满| 久久先锋资源网| 亚洲精品一区二区三区av| 午夜免费视频在线国产| 亚洲美女免费视频| 日本在线xxx| 99久久综合国产精品二区| 制服.丝袜.亚洲.另类.中文| jjzz黄色片| 亚洲视频分类| 久久久精品久久久| 日本熟妇一区二区| 日韩精品乱码av一区二区| 成人黄色在线观看| 天天色天天操天天射| 日本一区免费视频| 99er在线视频| 日韩毛片一区| 欧美mv日韩mv国产网站| 精品人伦一区二区| 国内视频精品| 国产精品视频xxx| 欧美一区二区三区激情| 亚洲国产精品ⅴa在线观看| 熟女熟妇伦久久影院毛片一区二区| 欧美videosex性欧美黑吊| 日韩欧美国产骚| 日韩av福利在线观看| 国产精品男女| 久久精品视频网站| 亚洲 欧美 成人| 美女任你摸久久| 精品高清视频| 污污视频在线看| 欧美日韩成人一区二区| 中文字幕一区三区久久女搜查官| 日韩在线观看一区| 96精品视频在线| 国产成人精品一区二三区四区五区| 久久久国产精华| 老司机午夜网站| 欧美日韩在线精品一区二区三区激情综合| 日韩精品一区二区三区在线播放 | 国产伦精品一区二区三区免费优势| 亚洲视频国产视频| 男人天堂中文字幕| 国产一区不卡在线| 亚洲日本精品一区| 日韩电影免费观| 亚洲国产精品va在线看黑人| 亚洲天堂黄色片| 精品一区二区影视| 涩涩涩999| 厕沟全景美女厕沟精品| 亚洲第一页在线| 久久久久性色av无码一区二区| 久久精品久久综合| 翔田千里亚洲一二三区| 久久uomeier| 国产丝袜一区二区三区| 国产成人无码精品久在线观看| 国产在线精品一区二区不卡了| 亚洲电影免费| 久久亚洲精品爱爱| 亚洲视频999| 久久久久久久久久一级| 久久午夜老司机| 国产精品宾馆在线精品酒店| 亚洲+小说+欧美+激情+另类| 97香蕉久久夜色精品国产| 亚洲黄色在线免费观看| 一区二区三区在线视频观看| 永久免费黄色片| 欧美激情成人在线| 99久久精品久久久久久ai换脸| 成人在线免费看黄| 日韩欧美一卡二卡| 国产一级片网址| 不卡一区二区三区四区| 国产69精品久久久久久久| 精品少妇一区| 国产91精品最新在线播放| 免费看男男www网站入口在线| 日本高清不卡在线观看| 欧美激情 一区| 精品亚洲国内自在自线福利| 福利网在线观看| 日韩一区免费| 国语自产偷拍精品视频偷| 五月天婷婷激情网| 91黄色免费观看| 99在线视频免费| 久久66热偷产精品| 国产 国语对白 露脸| 国产精品15p| 国产98色在线| 日本三级在线视频| 精品国产亚洲在线| 一区二区三区福利视频| 中文字幕电影一区| 免费观看一区二区三区| 欧美亚洲在线| 中文视频一区视频二区视频三区| 亚洲精品v亚洲精品v日韩精品| 久久免费少妇高潮久久精品99| 你懂的在线播放| 91精品国产欧美一区二区18| 久久精品国产亚洲av高清色欲| 久久久午夜电影| 奇米777在线| 裸体素人女欧美日韩| 在线观看视频黄色| 久久精品凹凸全集| 国产一区视频在线播放| av福利在线导航| 中文字幕亚洲欧美日韩高清| 亚洲精品久久久久久无码色欲四季| 欧美日韩一二三四五区| 99久久久免费精品| 99久久99久久精品国产片果冻| 久久久久国产一区| 欧美激情麻豆| 亚洲第一综合| 天堂成人娱乐在线视频免费播放网站 | 国产日本欧美在线| 亚洲v天堂v手机在线| 亚洲在线视频福利| 日韩一区二区三区在线免费观看 | 97精品国产97久久久久久久久久久久| 亚洲成色www.777999| 亚洲一级一区| 99亚洲精品视频| 神马影视一区二区| 91视频99| 亚洲国产91视频| 日本精品久久中文字幕佐佐木| bt在线麻豆视频| 亚洲一区二区精品| 少妇又色又爽又黄的视频| 欧美一区二区福利视频| 在线免费观看av片| 岛国精品视频在线播放| 久草视频在线资源站| 国产精品久久看| 日本高清www| 99久久伊人精品| 成人在线观看一区二区| 国内精品伊人久久久久av一坑| 人人干人人视频| 久久狠狠婷婷| av免费中文字幕| 亚洲毛片视频| 男女私大尺度视频| 欧美激情一区| 狠狠干视频网站| 亚洲男女av一区二区| 影音先锋亚洲视频| 精品国产一区二区三区噜噜噜| 久久人人九九| 欧美男男freegayvideosroom| 福利视频久久| 北条麻妃一区二区三区在线观看| 99国精产品一二二线| 国产欧美视频在线| 91在线观看免费观看| 国产精品一站二站| 亚洲综合社区网| 亚洲免费一区三区| 国产精品国产三级国产专区53| 无人区乱码一区二区三区| 亚洲一区亚洲二区亚洲三区| 国产精品免费精品自在线观看| 91视频九色网站| 精品视频一区二区三区在线观看| 亚洲自拍小视频| 精品一区二区三区亚洲| 国产a一区二区| 国产精品天天看天天狠| 九九99玖玖| 偷拍自拍亚洲色图| 视频一区二区在线观看| 日韩三级在线| 天天干天天色天天爽| 国模大胆一区二区三区| 精品少妇人妻av免费久久洗澡| 欧美专区18| 精品亚洲一区二区三区四区| 国产美女一区二区| 人妻 丝袜美腿 中文字幕| 99久久伊人网影院| 精品人妻中文无码av在线| 国产精品久久久久毛片软件| 亚洲 欧美 变态 另类 综合| 一区二区三区国产豹纹内裤在线| 日韩av男人天堂| 色天天综合色天天久久| 亚洲一级特黄毛片| 欧美电影精品一区二区| 天堂在线中文| 北条麻妃在线一区二区| 91视频欧美| 国产精品日韩欧美综合| 榴莲视频成人app| 精品久久精品久久| 欧美日韩精品在线一区| 在线观看18视频网站| 亚洲综合社区| 久久成年人网站| 91在线一区二区三区| 国产精品www爽爽爽| 亚洲国产另类av| 波多野结衣在线观看一区| 日韩欧美一区在线| 你懂的免费在线观看| 欧美理论片在线观看| 视频精品导航| 韩国成人一区| 香蕉综合视频| 久久久久久久久久久福利| 国产在线一区观看| 91成年人网站| 亚洲一区视频在线| 在线亚洲欧美日韩| 日韩h在线观看| 性xxxfreexxxx性欧美| 国产精品av电影| 国产在线播放精品| 香蕉精品视频在线| 日韩制服丝袜av| 中文字幕 日本| 亚洲精选一二三| 中文字幕免费在线看| 日韩精品在线免费播放| 亚洲资源一区| 国产欧美在线视频| 久草精品在线| 老太脱裤让老头玩ⅹxxxx| 精品无人码麻豆乱码1区2区| 中文字幕第20页| 午夜久久久影院| 午夜精品一区二区三| 日韩中文在线观看| 国产成人免费9x9x人网站视频| 国产伦精品一区二区三区高清版 | 欧美成人黑人猛交| 99热这里都是精品| 青娱乐国产盛宴| 69堂成人精品免费视频| 91在线免费看| 国产精品成人一区| 九九热精品视频在线观看| 精品无码一区二区三区在线| 粉嫩13p一区二区三区| 日韩av手机在线免费观看| 欧美日韩专区在线| 成人在线观看免费| 国产成人精品综合久久久| 日韩精品免费一区二区三区竹菊| www.av中文字幕| av一区二区三区在线| 久久免费精彩视频| 欧美成人三级电影在线| 日韩三级电影视频| 999热视频| 亚洲性图久久| 这里只有精品在线观看视频 | 97视频久久久| 99久久国产免费看| 国产原创视频在线| 亚洲精品一区中文字幕乱码| 91精品论坛| 日韩高清av电影| 精品一区二区三区欧美| 极品色av影院| 欧美一区二区成人6969| 牛牛精品在线| 精品日本一区二区| 午夜一级在线看亚洲| 免费在线观看你懂的| 日本精品视频一区二区三区| www.亚洲.com| 91久久久久久久久久久久久| 在线成人激情| 岛国精品一区二区三区| 欧美日韩亚洲天堂| 成人在线免费视频| 成人写真视频福利网| 国产精品mv在线观看| 国产精品麻豆入口| 91福利在线观看| 日韩子在线观看| 国产69精品久久久久9999apgf | 国产91精品高潮白浆喷水| 图片婷婷一区| 激情五月俺来也| 亚洲综合无码一区二区| 日本免费一区二区三区最新| 国产精品免费视频xxxx| 欧美精品99| 女人又爽又黄免费女仆| 7777精品伊人久久久大香线蕉的| 日本乱理伦在线| 欧洲在线视频一区| 韩国三级在线一区| 在线看成人av| 中文字幕一区二区三区电影| 2020国产精品极品色在线观看| 欧美日本视频在线观看| 中文字幕在线不卡视频| www.久久精品.com| 日本成人黄色片| 欧美日韩岛国| 呻吟揉丰满对白91乃国产区| 精品国产3级a| 久久亚洲人体| 成人性生活视频免费看| 国产精品国产三级国产专播品爱网| 亚洲男女视频在线观看| 国产精品九九九| 在线高清一区| 亚洲波多野结衣| 亚洲人在线视频| 草莓视频一区二区三区|