In [1]:
# 全局常量

DIR = "../models/HuggingFace/Qwen3-0.6B"
In [2]:
# 处理tokenize逻辑

from tokenizers import Tokenizer

tokenizer = Tokenizer.from_file(DIR + "/tokenizer.json")
output = tokenizer.encode("测试一下tokenizer的效果")
print(output.ids)
tokenizer.decode(output.ids)
[81705, 100158, 85593, 105005]
Out[2]:
'测试一下tokenizer的效果'
In [ ]:
# 处理参数和超参数

from safetensors import safe_open
import json
import torch

# 常数和超参数
eos_token_id: int  # 用于判断模型输出完的token id
rms_norm_eps: float  # RMSNorm防止归一化分母为0的eps
rope_theta: int  # RoPE的theta的底数部分
num_hidden_layers: int  # transformer层数
num_attention_heads: int  # Q的注意力头数
num_key_value_heads: int  # KV的注意力头数
head_dim: int  # 单头注意力维数
max_position_embeddings: int  # 允许位置编码的最大序列长度

# 参数
E: torch.Tensor  # 嵌入层
Gamma_finalRMSNorm: torch.Tensor  # 最终RMSNorm的缩放系数
W_output: torch.Tensor  # 最终投影到词表大小的线性层
Gamma_inputRMSNorm: list[torch.Tensor] = []  # transformer层中对输入的RMSNorm的缩放系数
Gamma_postATTNRMSNorm: list[torch.Tensor] = []  # transformer层中对自注意力结果的RMSNorm的缩放系数
W_FFNGate: list[torch.Tensor] = []  # transformer层中FFN的门控层
W_FFNUp: list[torch.Tensor] = []  # transformer层中FFN的升维层
W_FFNDown: list[torch.Tensor] = []  # transformer层中FFN的降维层
Gamma_QRMSNorm: list[torch.Tensor] = []  # transformer层中Q投影后、RoPE前的每个头的RMSNorm的缩放系数
Gamma_KRMSNorm: list[torch.Tensor] = []  # transformer层中K投影后、RoPE前的每个头的RMSNorm的缩放系数
W_Q: list[torch.Tensor] = []  # transformer层中Q投影矩阵
W_K: list[torch.Tensor] = []  # transformer层中K投影矩阵
W_V: list[torch.Tensor] = []  # transformer层中V投影矩阵
W_O: list[torch.Tensor] = []  # transformer层中O投影矩阵

# 读取常数和超参数
with open(DIR + "/config.json", "r", encoding="utf-8") as f:
    data = json.load(f)

    def readParam(name, t):
        d = data.get(name)
        if not isinstance(d, t):
            raise ValueError(f"get {name} error")
        return d

    eos_token_id = readParam("eos_token_id", int)  # 151645
    rms_norm_eps = readParam("rms_norm_eps", float)  # 1e-06
    rope_theta = readParam("rope_theta", int)  # 1000000
    num_hidden_layers = readParam("num_hidden_layers", int)  # 28
    num_attention_heads = readParam("num_attention_heads", int)  # 16
    num_key_value_heads = readParam("num_key_value_heads", int)  # 8
    head_dim = readParam("head_dim", int)  # 128
    max_position_embeddings = readParam("max_position_embeddings", int)  # 40960

# 读取参数
with safe_open(DIR + "/model.safetensors", framework="pt", device=0) as f:
    E = f.get_tensor("model.embed_tokens.weight")  # torch.Size([151936, 1024])
    Gamma_finalRMSNorm = f.get_tensor("model.norm.weight")  # torch.Size([1024])
    W_output = f.get_tensor("lm_head.weight")  # torch.Size([151936, 1024])
    for i in range(num_hidden_layers):
        Gamma_inputRMSNorm.append(f.get_tensor(f"model.layers.{i}.input_layernorm.weight"))  # torch.Size([1024])
        Gamma_postATTNRMSNorm.append(f.get_tensor(f"model.layers.{i}.post_attention_layernorm.weight"))  # torch.Size([1024])
        W_FFNGate.append(f.get_tensor(f"model.layers.{i}.mlp.gate_proj.weight"))  # torch.Size([3072, 1024])
        W_FFNUp.append(f.get_tensor(f"model.layers.{i}.mlp.up_proj.weight"))  # torch.Size([3072, 1024])
        W_FFNDown.append(f.get_tensor(f"model.layers.{i}.mlp.down_proj.weight"))  # torch.Size([1024, 3072])
        Gamma_QRMSNorm.append(f.get_tensor(f"model.layers.{i}.self_attn.q_norm.weight"))  # torch.Size([128])
        Gamma_KRMSNorm.append(f.get_tensor(f"model.layers.{i}.self_attn.k_norm.weight"))  # torch.Size([128])
        W_Q.append(f.get_tensor(f"model.layers.{i}.self_attn.q_proj.weight"))  # torch.Size([2048, 1024])
        W_K.append(f.get_tensor(f"model.layers.{i}.self_attn.k_proj.weight"))  # torch.Size([1024, 1024])
        W_V.append(f.get_tensor(f"model.layers.{i}.self_attn.v_proj.weight"))  # torch.Size([1024, 1024])
        W_O.append(f.get_tensor(f"model.layers.{i}.self_attn.o_proj.weight"))  # torch.Size([1024, 2048])

model_dtype = E.dtype
In [4]:
# 工具函数


def RMSNorm(X, gamma):
    return X / torch.sqrt(torch.mean(X[..., :] ** 2, -1, keepdim=True) + rms_norm_eps) * gamma


# interleaved: 交错配对theta[0,0,1,1,2,2,3,3]
# rope_theta_repeat2 = torch.pow(rope_theta, -torch.arange(head_dim // 2) * 2 / head_dim).repeat_interleave(2)
# rotate_half: 前后两半配对theta_0,1,2,3,0,1,2,3
rope_theta_repeat2 = torch.pow(rope_theta, -torch.arange(head_dim // 2) * 2 / head_dim).repeat(2)
rope_m_theta = torch.arange(max_position_embeddings).reshape(-1, 1) * rope_theta_repeat2.reshape(1, -1)
rope_sin_m_theta = torch.sin(rope_m_theta).cuda()  # torch.Size([40960, 128])
rope_cos_m_theta = torch.cos(rope_m_theta).cuda()  # torch.Size([40960, 128])


def RoPE(X, offset=0):
    Ls = X.shape[-2]
    d = X.shape[-1]
    cos, sin = rope_cos_m_theta[offset : offset + Ls], rope_sin_m_theta[offset : offset + Ls]
    # interleaved: 交错配对x[-1,0,-3,2]
    # X_rot = torch.stack((-X[..., 1::2], X[..., 0::2]), dim=-1).flatten(-2)
    # rotate_half: 前后两半配对x[-0,-1,-2,-3,0,1,2,3]
    X_rot = torch.cat((-X[..., d // 2 :], X[..., : d // 2]), dim=-1)
    return (X * cos + X_rot * sin).to(model_dtype)


# RoPE(torch.arange(2 * 3 * head_dim).reshape(2, 3, head_dim).cuda(), 0)


def SiLU(X):
    return X / (1 + torch.exp(-X))


# SiLU(torch.arange(-10, 10).reshape(2, 10))


def softmax(X, temperature=1):
    X_shifted = X - torch.max(X / temperature, dim=-1, keepdims=True).values
    exp_X = torch.exp(X_shifted)
    return exp_X / torch.sum(exp_X, dim=-1, keepdims=True)


# softmax(torch.arange(-10, 10).reshape(2, 10))


# 右上方三角被mask
def masked_softmax(X):
    # X: (batch_size, num_heads, num_steps, num_steps)
    maxlen = X.size(-1)
    mask = (torch.arange(maxlen).reshape(-1, 1) >= torch.arange(maxlen)).to(X.device)
    mask_not = ~mask
    return softmax(X * mask + (mask_not * -1e6).to(X.dtype))


# masked_softmax(torch.arange(96).reshape(2, 3, 4, 4))
In [ ]:
# 核心计算函数与Transformer层


def FFN(X, gate, up, down):
    return (SiLU(X @ gate.T) * (X @ up.T)) @ down.T


# 通过转置实现Q/K/V张量按注意力头的拆分与恢复,便于一次性计算所有注意力头
def transpose_qkv(X, num_heads):
    # X_h: (batch_size, num_steps, num_heads, num_hiddens/num_heads)
    X_h = X.reshape(*X.shape[:-1], num_heads, -1)
    return X_h.transpose(-2, -3)


def transpose_output(X):
    # X_t: (batch_size, num_steps, num_heads, num_hiddens/num_heads)
    X_t = X.transpose(-2, -3)
    return X_t.reshape(*X_t.shape[:-2], -1)


# print(transpose_qkv(torch.arange(80).reshape(2, 5, 8), 2).shape)
# print(transpose_output(transpose_qkv(torch.arange(80).reshape(2, 5, 8), 2)).shape)


def DotProductAttention(Q, K, V, offset=0):
    KVScale = num_attention_heads // num_key_value_heads
    # Q/K/V: (batch_size, num_heads, num_steps, num_hiddens/num_heads)
    # scores/A: (batch_size, num_heads, num_steps, num_steps)
    scores = Q @ torch.repeat_interleave(K.transpose(-1, -2), KVScale, -3) / torch.sqrt(torch.tensor(Q.shape[-1]))
    A = softmax(scores) if offset else masked_softmax(scores)
    return A @ torch.repeat_interleave(V, KVScale, -3)


KVCache: list[list[torch.Tensor]]


def ClearKVCache():
    global KVCache
    KVCache = [None] * num_hidden_layers


# KVCache中已经prefill或predict了多少个字符
def GetOffset():
    global KVCache
    return KVCache[0][0].size(-2)


# offset为0代表prefill阶段
def TransfomerLayer(layer, X, Gamma_inputRMSNorm, Gamma_postATTNRMSNorm, W_FFNGate, W_FFNUp, W_FFNDown, Gamma_QRMSNorm, Gamma_KRMSNorm, W_Q, W_K, W_V, W_O, offset=0):
    # X: (batch_size, num_steps, num_hiddens)
    X_inputRMSNorm = RMSNorm(X, Gamma_inputRMSNorm)
    # 投影 -> QK RMSNorm -> RoPE -> attention
    # Q/K/V: (batch_size, num_steps, num_heads, num_hiddens) -> (batch_size, num_heads, num_steps, num_hiddens/num_heads)
    Q = transpose_qkv(X_inputRMSNorm @ W_Q.T, num_attention_heads)
    K = transpose_qkv(X_inputRMSNorm @ W_K.T, num_key_value_heads)
    V = transpose_qkv(X_inputRMSNorm @ W_V.T, num_key_value_heads)
    Q_RMSNorm = RMSNorm(Q, Gamma_QRMSNorm)
    K_RMSNorm = RMSNorm(K, Gamma_KRMSNorm)
    Q_RoPE = RoPE(Q_RMSNorm, offset)
    K_RoPE = RoPE(K_RMSNorm, offset)
    # prefill阶段初始化KVCache
    global KVCache
    if offset == 0:
        KVCache[layer] = [K_RoPE, V]
    else:
        KVCache[layer][0] = torch.cat((KVCache[layer][0], K_RoPE), dim=-2)
        KVCache[layer][1] = torch.cat((KVCache[layer][1], V), dim=-2)
    # V_attn: (batch_size, num_heads, num_steps, num_hiddens/num_heads)
    V_attn = DotProductAttention(Q_RoPE, KVCache[layer][0], KVCache[layer][1], offset)
    X2 = X + transpose_output(V_attn) @ W_O.T
    # FFN前的RMSNorm -> FFN
    # O: (batch_size, num_steps, num_hiddens)
    O = X2 + FFN(RMSNorm(X2, Gamma_postATTNRMSNorm), W_FFNGate, W_FFNUp, W_FFNDown)
    return O
In [6]:
# prefill与predict


def prefill(X):
    ClearKVCache()
    # X: (batch_size, num_steps)
    # O: (batch_size, num_steps, num_hiddens)
    O = E[X]
    for i in range(num_hidden_layers):
        O = TransfomerLayer(
            i, O, Gamma_inputRMSNorm[i], Gamma_postATTNRMSNorm[i], W_FFNGate[i], W_FFNUp[i], W_FFNDown[i], Gamma_QRMSNorm[i], Gamma_KRMSNorm[i], W_Q[i], W_K[i], W_V[i], W_O[i]
        )
    return O


# 多层Transformer得到的结果经过最终的RMSNorm、线性层和softmax得到概率
def lastOutputToken(O, temperature=1):
    O = RMSNorm(O, Gamma_finalRMSNorm)
    Y = O @ W_output.T
    P = softmax(Y, temperature)
    return torch.argmax(P, dim=-1)[..., -1:].to(torch.long)


def predict(X, temperature=1):
    offset = GetOffset()
    # O: (batch_size, 1)
    res = X.clone().cpu()
    while offset < max_position_embeddings:
        token_id = res[0][-1].numpy()
        print(tokenizer.decode([token_id], skip_special_tokens=False), end="")
        if token_id == eos_token_id:
            break
        O = E[X]
        for i in range(num_hidden_layers):
            O = TransfomerLayer(
                i, O, Gamma_inputRMSNorm[i], Gamma_postATTNRMSNorm[i], W_FFNGate[i], W_FFNUp[i], W_FFNDown[i], Gamma_QRMSNorm[i], Gamma_KRMSNorm[i], W_Q[i], W_K[i], W_V[i], W_O[i], offset
            )
        X = lastOutputToken(O, temperature)
        res = torch.cat((res, X.cpu()), dim=-1)
        offset += 1
    print()
    return res.numpy()


prefill(torch.ones(1, 5, dtype=torch.long))
Out[6]:
tensor([[[119.5000, 110.0000, 113.0000,  ..., -28.5000,  14.2500,  18.5000],
         [120.0000, 112.0000, 115.0000,  ..., -28.6250,  14.5000,  18.8750],
         [119.5000, 112.0000, 114.0000,  ..., -28.3750,  14.6250,  19.0000],
         [120.0000, 110.0000, 116.0000,  ..., -28.3750,  14.5000,  19.0000],
         [120.0000, 112.0000, 113.0000,  ..., -28.3750,  14.2500,  18.7500]]],
       device='cuda:0', dtype=torch.bfloat16)
In [7]:
# 构造输入

import jinja2

with open(DIR + "/tokenizer_config.json", "r", encoding="utf-8") as f:
    data = json.load(f)
    chat_template = data["chat_template"]

tpl = jinja2.Environment().from_string(chat_template)

messages = [{"role": "system", "content": "You are a helpful assistant."}, {"role": "user", "content": "steam现在最火的游戏是什么"}]

rendered = tpl.render(
    messages=messages,
    add_generation_prompt=True,
    enable_thinking=True,
)
tokens = tokenizer.encode(rendered).ids

print(rendered)
print(tokens)
<|im_start|>system
You are a helpful assistant.<|im_end|>
<|im_start|>user
steam现在最火的游戏是什么<|im_end|>
<|im_start|>assistant

[151644, 8948, 198, 2610, 525, 264, 10950, 17847, 13, 151645, 198, 151644, 872, 198, 46590, 99601, 31235, 79599, 105628, 102021, 151645, 198, 151644, 77091, 198]
In [8]:
# 发起预测

import time

batch_size = 50

start = time.perf_counter()
O = prefill(torch.tensor(tokens, dtype=torch.long).reshape(1, -1).repeat(batch_size, 1))
end = time.perf_counter()
print(f"prefill耗时: {end - start:.2f} 秒")

start = time.perf_counter()
X = lastOutputToken(O)
res = predict(X)
end = time.perf_counter()
print(f"predict: {len(res[0])*batch_size/(end - start):.2f} tokens/s")
print(res)
prefill耗时: 0.11 秒
<think>
好的,用户问的是“Steam现在最火的游戏是什么”。首先,我需要确认用户的问题意图。他们可能想知道最新的热门游戏,或者想了解Steam上最受欢迎的游戏类型。接下来,我得考虑用户可能的背景。可能是刚接触Steam的新手,或者对游戏行业不太熟悉,想了解当前的热门趋势。

然后,我需要回忆一下Steam上最近的热门游戏。根据之前的了解,最近有几个热门游戏,比如《艾尔登法环》、《巫师3》、《最终幻想16》、《原神》和《塞尔达传说:旷野之息》。这些游戏在Steam上都有很高的评分和下载量。不过,用户可能想知道这些游戏的具体信息,比如更新时间、平台支持等。

另外,用户可能没有明确说明他们是在问哪个游戏最火,所以需要给出一个综合的答案,涵盖几个热门游戏,并解释为什么它们受欢迎。同时,要确保信息准确,避免错误。比如,确认《巫师3》的更新时间,以及《原神》的发布日期,这样用户能更清楚地了解。

还要注意用户可能的深层需求,比如他们可能想了解游戏的类型、玩法或者社区氛围,所以回答时可以提到这些方面,帮助用户全面了解。最后,保持回答简洁明了,结构清晰,方便用户快速获取信息。
</think>

目前Steam上最火的游戏包括:

1. **《艾尔登法环》** - 以开放世界冒险和深度剧情著称,2023年发布,全球下载量超10亿。
2. **《巫师3》** - 2023年更新,结合了新世界观和角色深度,评分高达9.5分。
3. **《最终幻想16》** - 2023年发布,融合了新世界观和角色深度,评分9.2分。
4. **《原神》** - 2023年发布,以角色和世界观创新著称,评分9.1分。
5. **《塞尔达传说:旷野之息》** - 2023年发布,结合了新世界观和角色深度,评分9.0分。

这些游戏在Steam的评分和下载量中表现突出,反映了当前游戏行业的热门趋势。如果你有具体偏好(如平台、类型或社区),我可以进一步细化推荐!<|im_end|>
predict: 924.85 tokens/s
[[151667    198  99692 ... 101914   6313 151645]
 [151667    198  99692 ... 101914   6313 151645]
 [151667    198  99692 ... 101914   6313 151645]
 ...
 [151667    198  99692 ... 101914   6313 151645]
 [151667    198  99692 ... 101914   6313 151645]
 [151667    198  99692 ... 101914   6313 151645]]