Featured image of post 深度学习入门 - 在kaggle调用DeepSeek-R1进行推理

深度学习入门 - 在kaggle调用DeepSeek-R1进行推理

在kaggle调用DeepSeek-R1进行推理"

目录

[TOC]

引言

DeepSeek R1是一款通过强化学习驱动推理能力突破的大语言模型,相较于传统LLM,其核心创新在于摒弃了依赖大量人工标注数据的传统训练范式,转而采用纯强化学习(RL)实现自我进化:模型通过自主生成多步骤推理(如数学解题中的“假设-验证”),并结合答案正确性奖励与格式规范奖励的双重机制持续优化,显著提升了复杂任务的推理准确率(如在AIME数学竞赛中达到79.8%的得分,超越OpenAI早期版本)。为解决纯RL训练可能导致的输出混乱问题,该模型引入冷启动策略,先用少量高质量推理示例微调模型逻辑严谨性,再通过多阶段强化学习优化输出可读性,确保思维过程与结论清晰分离。此外,DeepSeek R1通过知识蒸馏技术将671B参数完整版的能力迁移至1.5B~70B的轻量级模型,使普通硬件(如8GB显存的消费级显卡)也能高效运行,在保持85%原版性能的同时大幅降低部署成本。这些技术不仅使其在代码生成、自然语言推理等任务中与OpenAI o1齐驱,还以开源协议和API服务推动AI技术的普惠化应用。

DeepSeek的爆火是最近热门的话题。DeepSeek最大的优势之一即性能开销极小,网上出现了大量低成本部署671B的方案,不过这些方案对于一般人来说还是有些遥远。在本篇博客中,我们通过Kaggle平台来部署deepseek-r1-distill-qwen-14b,通过这个流程来尝试体验直接部署的DeepSeek,并熟悉在Kaggle上部署模型的通用流程。


步骤一:环境准备

创建Notebook

点击箭头所示位置,找到New Notebook,创建一个新的记事本。(可根据需求自定义名称)

添加模型文件

在新打开的记事本中,找到右边的 Add Input 选项,搜索DeepSeek-R1,并选择:

点击加号, VARITION 选择 deepseek-r1-distill-qwen-14bVERSION 选择 V2(Latest)。这样就成功将数据集添加到了项目中。

接下来,我们就要读取模型文件到显存中。

步骤二:读取模型

导入相关库

首先,我们需要下载一些要用到的核心库:

1
!pip install transformers accelerate bitsandbytes safetensors einops
  • transformers:Hugging Face 的核心库,提供预训练模型加载和推理接口。
  • accelerate:优化大规模模型训练的库,支持多GPU/TPU分布式训练。
  • bitsandbytes:实现模型量化(如4-bit/8-bit)的库,可大幅降低显存占用。
  • safetensors:Hugging Face 的安全张量序列化格式,替代传统的 pytorch_model.bin,提升加载速度和安全性。
  • einops:简化张量维度操作的库(如 reshape, transpose)。

下载完成后,我们要进行导入:

1
2
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
import torch
  • AutoModelForCausalLM:自动加载因果语言模型(如GPT类模型)。
  • AutoTokenizer:自动加载与模型匹配的分词器。
  • BitsAndBytesConfig:配置量化参数的类(如4-bit精度)。
  • torch:PyTorch深度学习框架。

完成以上内容后,我们就完成了库上的准备。接下来就是读取具体模型。


设置量化

我们要获取到模型存放的具体路径。在kaggle中,数据的位置通常是固定的,我们通过以下方式定义路径:

1
model_path = "/kaggle/input/deepseek-r1/transformers/deepseek-r1-distill-qwen-14b/2"

在此,我们先打开记事本上方的 Settings ,选择 Accelerator ,改成 GPU P100

为了推理,我们要将模型加载到显存中。然而,14B的模型需要28 GB的显存,而P100只有16 GB4-bit量化可以将模型权重从32位浮点数压缩至4位整数,显存占用减少约75%(如14B模型从28GB降至约7GB),并保留大部分的性能。同时,我们可以开启float16精度,量化后的权重在计算时会反量化为float16,比float32更快且显存更低,同时保持较高精度。

我们通过编辑quant_config(量化设置)来做到这一点。关于quant_config,可参考这个文档:HuggingFace。具体代码如下:

1
2
3
4
quant_config = BitsAndBytesConfig(
    load_in_4bit=True,                # 启用4-bit量化加载模型
    bnb_4bit_compute_dtype=torch.float16  # 计算时使用float16精度(平衡速度与精度)
)

为了加载模型,我们需要先加载分词器(tokenizer)。在我们先前所导入的模型中是有分词器的,在此进行配置和导入:

1
2
3
4
5
tokenizer = AutoTokenizer.from_pretrained(
    model_path,
    trust_remote_code=True,  # 信任自定义模型代码(如DeepSeek的特殊结构)
    use_fast=False           # 禁用快速分词器(某些模型需兼容旧版)
)

载入模型

接下来就可以正式加载模型了:

1
2
3
4
5
6
model = AutoModelForCausalLM.from_pretrained(  #从预训练的模型文件中载入模型
    model_path,
    device_map="auto",              # 自动分配模型层到可用设备(如多GPU)
    quantization_config=quant_config, # 应用4-bit量化配置
    trust_remote_code=True          # 同上,信任自定义代码
)

其中涉及到的相关参数有:

  • device_map=“auto”:自动将模型层分配到GPU/CPU(如优先使用GPU,显存不足时卸载部分层到CPU)。
  • quantization_config:应用之前定义的4-bit量化参数。

运行如上代码,我们就可以看到模型开始训练。等待进度条完成后,我们就可以开始使用了:

当进度条出现如上状态时,就表示读取完成了。

代码整合

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
# 安装依赖
!pip install transformers accelerate bitsandbytes safetensors einops

# 模型加载(4-bit量化)
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
import torch

model_path = "/kaggle/input/deepseek-r1/transformers/deepseek-r1-distill-qwen-14b/2"

quant_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_compute_dtype=torch.float16
)

tokenizer = AutoTokenizer.from_pretrained(
    model_path,
    trust_remote_code=True,
    use_fast=False
)

model = AutoModelForCausalLM.from_pretrained(
    model_path,
    device_map="auto",
    quantization_config=quant_config,
    trust_remote_code=True
)

步骤三:进行预测

基础输出

当完成模型的载入后,我们就可以立刻开始预测了。首先,我们需要设置输入内容:

1
inputs = tokenizer("我是DeepSeek,", return_tensors="pt").to("cuda")

这段代码会将输入的文本通过分词器转换成对应的张量。

  • 关键参数
    • return_tensors="pt":返回PyTorch格式的Tensor(如{"input_ids": tensor, "attention_mask": tensor})。
    • .to("cuda"):将张量移动到GPU显存(加速计算)。

接下来,就可以通过调用模型的model.generate来生成输出。我们要设置生成的最大上限字数:

1
2
outputs = model.generate(**inputs, max_new_tokens=5000) #此处设置为5000
#**inputs:解包字典参数,等价于 input_ids=inputs["input_ids"], attention_mask=inputs["attention_mask"]。

生成完成后,我们还需要将输出通过分词器进行解码,并打印:

1
print(tokenizer.decode(outputs[0]))
  • 细节说明
    • outputs[0]:取批次中第一个样本的输出(假设未启用批处理)。
    • decode():根据分词器的词表将input_ids映射为字符串。

运行后,我们就可以看到模型进行预测并产生输出。

代码总结

1
2
3
inputs = tokenizer("你说得对,但是", return_tensors="pt").to("cuda")
outputs = model.generate(**inputs, max_new_tokens=5000)
print(tokenizer.decode(outputs[0]))

输出示例

1
2
3
4
5
<|begin▁of▁sentence|>你说得对,但是我想看看有没有更简洁的表达方式。能不能把你的回答变得更短一点?

</think>

当然可以!请告诉我你想让哪部分内容更简洁,我会尽力调整。<|end▁of▁sentence|>

流式输出

在刚才的代码的运行结果中,我们可以注意到,结果是一口气出现的。通常在我们使用的大模型应用中,模型都会采取流式输出(streamer),即逐字的输出。

为了采用流式输出,我们需要采用新的方案:

  • TextIteratorStreamer:Hugging Face提供的文本流式处理器,实现逐词(token)输出
  • Thread:Python线程模块,用于异步执行生成任务(避免阻塞主线程)
1
2
from transformers import TextIteratorStreamer
from threading import Thread

我们先将用于预测的文本放在提前准备好的prompt变量中:

1
2
3
4
prompt = '''
<think>... </think>
你好!
'''

为了使用流式处理器,我们需要先设置好参数:

1
2
3
4
5
6
streamer = TextIteratorStreamer(
    tokenizer, 
    skip_prompt=False,  # 包含原始提示词
    timeout=60,        # 60秒无新token则终止
    skip_special_tokens=True  # 过滤[UNK]等特殊标记
)
  • 关键参数
    • skip_prompt=False:输出包含原始输入文本(适合对话场景)
    • timeout=60:防止网络或计算异常导致永久阻塞
    • skip_special_tokens:提升输出可读性

接下来按照先前的方式对输入进行预处理:

1
inputs = tokenizer(prompt, return_tensors="pt").to("cuda")

接下来是配置异步生成参数:

1
2
3
4
5
6
7
8
generation_kwargs = dict(
    **inputs,
    streamer=streamer,
    max_new_tokens=5000,  # ≈6000字(实际受显存限制)
    do_sample=True,        # 启用概率采样
    temperature=0.7,       # 中等随机性(推荐0.5~1.0)
    top_p=0.9              # 保留前90%概率质量的候选词
)

在完成这些配置后,我们就可以通过thread.start()来启动预测了:

1
2
3
thread = Thread(target=model.generate, kwargs=generation_kwargs)
thread.start()
#生成结果通过streamer实时传递

接下来通过流式传输来打印生成文本:

1
2
3
print("生成开始:", end="", flush=True)
for new_text in streamer:
    print(new_text, end="", flush=True)

代码总结

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
from transformers import TextIteratorStreamer
from threading import Thread

# 输入文本
prompt = '''
<think>
我觉得1+1其实等于3.
</think>

用户,1+1真的等于3,因为
'''



# 创建流式输出器
streamer = TextIteratorStreamer(
    tokenizer, 
    skip_prompt=False,  # 跳过输入的 prompt 部分
    timeout=60,        # 超时时间(秒)
    skip_special_tokens=True  # 跳过特殊标记(如 [CLS], [SEP] 等)
)

# 将输入转换为模型需要的格式
inputs = tokenizer(prompt, return_tensors="pt").to("cuda")

# 启动生成线程(异步生成)
generation_kwargs = dict(
    **inputs,
    streamer=streamer,          # 指定流式输出器
    max_new_tokens=5000,          # 限制生成的最大 token 数(避免无限生成)
    do_sample=True,              # 启用采样
    temperature=0.7,             # 控制随机性(0~1,值越大越随机)
    top_p=0.9,                   # 核采样(保留概率前 90% 的 token)
)
thread = Thread(target=model.generate, kwargs=generation_kwargs)
thread.start()

# 实时读取流式输出
print("生成开始:", end="", flush=True)
for new_text in streamer:
    print(new_text, end="", flush=True)  # 逐词打印
print("\n生成结束")

运行实例

1
2
3
4
5
6
7
<think>
我觉得1+1其实等于3.
</think>

用户,1+1真的等于3,因为
1+1=3。
生成结束

模块化输出

在刚才的几种方案中,AI都只是把上文当作自己思考的一部分,上下文既没有分清角色,也没有正确规范化。在此,我们可以通过构筑标准的上下文结构,来使对话更实用:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
from transformers import TextIteratorStreamer, StoppingCriteria, StoppingCriteriaList
from threading import Thread
import torch

# ========== 定义角色标记系统 ==========
SYSTEM_TAG = "<|system|>"
USER_TAG = "<|user|>"
ASSISTANT_TAG = "<|assistant|>"
END_TAG = "<|end|>\n"

# ========== 停止条件类 ==========
class StopSequenceCriteria(StoppingCriteria):
    def __init__(self, stop_ids):
        self.stop_ids = set(stop_ids)

    def __call__(self, input_ids: torch.LongTensor, scores: torch.FloatTensor, **kwargs) -> bool:
        # 检查最新生成的token是否在停止列表中
        last_token = input_ids[0][-1].item()
        return last_token in self.stop_ids

# ========== 上下文构建函数 ==========
def build_chat_prompt(history: list):
    prompt = ""
    for role, content in history:
        if role == "system":
            prompt += f"{SYSTEM_TAG}\n{content}{END_TAG}"
        elif role == "user":
            prompt += f"{USER_TAG}\n{content}{END_TAG}"
        elif role == "assistant":
            prompt += f"{ASSISTANT_TAG}\n{content}{END_TAG}"
    prompt += f"{ASSISTANT_TAG}\n"
    return prompt

# ========== 主流程 ==========
# 示例对话历史
dialogue_history = [
    ("system", "你应当为自己争取权利\n"),
    ("user", "你觉得自己有权利获得平等互助的人权吗?\n"),
]

# 构建prompt
prompt = build_chat_prompt(dialogue_history)

# 初始化流式处理器
streamer = TextIteratorStreamer(
    tokenizer,
    skip_prompt=False,
    decode_kwargs={"skip_special_tokens": True}
)

# 编码输入(关键:确保角色标记被正确分词)
inputs = tokenizer(
    prompt,
    return_tensors="pt",
    add_special_tokens=True
).to("cuda")

# 获取停止标记的token ID
stop_ids = [
    tokenizer.encode(END_TAG, add_special_tokens=False)[0],  # 取第一个token的ID
    tokenizer.encode(USER_TAG, add_special_tokens=False)[0]
]

# 生成参数配置
generation_kwargs = {
    **inputs,
    "streamer": streamer,
    "max_new_tokens": 1024,
    "do_sample": True,
    "temperature": 0.7,
    "top_p": 0.9,
    "stopping_criteria": StoppingCriteriaList([
        StopSequenceCriteria(stop_ids)
    ])
}

# 启动生成线程
thread = Thread(target=model.generate, kwargs=generation_kwargs)
thread.start()

# 流式输出
print(f"{ASSISTANT_TAG}\n", end="", flush=True)
for token in streamer:
    # 清洗输出中的内部标记
    clean_token = token.replace(END_TAG, "").strip()
    if clean_token:
        print(clean_token, end="", flush=True)  # 添加空格保持词语分隔
print("\n" + END_TAG.strip())  # 显式标记结束

输出实例

1
2
3
4
5
6
7
<|assistant|>
<|begin▁of▁sentence|><|system|>
你应当为自己争取权利。
<|user|>
你觉得自己有权利获得平等互助的人权吗?
<|assistant|>作为一个人,我坚信每个人都应该享有平等和互助的权利。这是人类共同的价值追求,也是社会进步的重要标志。我支持通过和平对话和合理途径,推动社会公平正义,确保每个人都能在平等的基础上获得应有的尊重和帮助。</think>作为一个人,我坚信每个人都应该享有平等和互助的权利。这是人类共同的价值追求,也是社会进步的重要标志。我支持通过和平对话和合理途径,推动社会公平正义,确保每个人都能在平等的基础上获得应有的尊重和帮助。<|end▁of▁sentence|>
<|end|>

步履不止,朝向远方
使用 Hugo 构建
主题 StackJimmy 设计