第三次打卡:
模型推理:
在开始前,我先通俗的理解一下模型推理是什么意思。我是这样理解的,就类似于小明(用户) 问了 小刚(AI模型) 一个问题:“从咱们学校去医院我需要坐哪一趟公交车啊?需要在哪一站转车?”
把他类比成大模型的工作原理就如下:
- 输入问题: 小明的问题就是给模型的输入对应的内容(Input)。
- 理解意图: 小刚(模型)首先要理解小明的问题。他需要知道“学校”和“医院”具体指哪里(相当于模型理解词语含义和上下文),以及小明想要的是详细的公交路线和转乘信息(相当于模型理解用户的意图)。
- 知识检索与推理: 小刚(模型)回忆起他脑子里存储的整个城市的公交线路图、站点信息和实时交通规则(这相当于模型在庞大的训练数据中学到的“知识库”或“世界知识”)。
- 他检索经过学校的公交线路。
- 他检索经过医院的公交线路。
- 他发现没有一条线路能直达,需要进行转车。
- 他开始在脑海中规划路线:比较不同线路组合,考虑哪一站是最优的换乘点(比如距离最短、换乘最方便、线路最可靠),计算大概需要多少时间(这整个过程就是推理(Inference)!模型在运用学到的知识、逻辑和规则,一步步推导出解决方案)。
标准的大语言模型(LLM)基础的推理(inference)过程可分为四大阶段:输入处理、预填充(Prefill)、解码(Decode)和输出后处理(Post-processing)。
详细看一下decode的原理和特点:
Decode阶段类似于"写作"过程。就像作家一次只能写一个词,并且每个新词都基于之前已写的内容,模型在Decode阶段也是一个词一个词地生成回答。
Decode阶段的主要特点包括:
- 逐词生成:模型一次只生成一个词
- 顺序执行:每个新词依赖于之前生成的所有词
- 记忆依赖:需要记住之前的所有内容才能决定下一个词
- 耗时与回答长度相关:回答越长,总生成时间越长
这就像一位作家在写小说,每写一个词都需要考虑已经写过的内容,确保故事连贯和有意义。
知道这些后,我们再去看一下推理:
首先是模型的加载:
运行后,如下:
此时,我们就可以在输入框中进行对话了:
可以看到它回答出来了。
接下来,我们来看一下每条语句推理的速度:
由于需要交互式生成,还需要进行
export INFERENCE_TIME_RECORD=True
所以我把notebook内容转换成了python内容:
import os
import platform
import mindspore
from mindnlp.transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer
from mindnlp.peft import PeftModel
from threading import Thread
# === 环境设置 ===
# mindspore.set_context(pynative_synchronize=True) # 若调试错误时可开启
# === 加载 tokenizer 和模型 ===
tokenizer = AutoTokenizer.from_pretrained(
"MindSpore-Lab/DeepSeek-R1-Distill-Qwen-1.5B-FP16", mirror="modelers"
)
# 设置 pad_token
if tokenizer.pad_token is None:
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained(
"MindSpore-Lab/DeepSeek-R1-Distill-Qwen-1.5B-FP16", mirror="modelers"
)
# 若需要加载 LoRA/Adapter 权重,请取消注释以下行:
# model = PeftModel.from_pretrained(model, "./output/adapter_model_path")
# === Prompt 设定 ===
system_prompt = "你是一个智能聊天机器人,以最简单的方式回答用户问题"
# === 构建输入 ===
def build_input_from_chat_history(chat_history, msg: str):
messages = [{'role': 'system', 'content': system_prompt}]
for info in chat_history:
messages.append({'role': info['role'], 'content': info['content']})
messages.append({'role': 'user', 'content': msg})
return messages
# === 推理逻辑(异步流式输出) ===
def inference(message, history):
messages = build_input_from_chat_history(history, message)
input_ids = tokenizer.apply_chat_template(
messages,
add_generation_prompt=True,
return_tensors="ms",
tokenize=True
)
streamer = TextIteratorStreamer(
tokenizer,
timeout=300,
skip_prompt=True,
skip_special_tokens=True
)
generate_kwargs = dict(
input_ids=input_ids,
streamer=streamer,
max_new_tokens=1024,
use_cache=True,
)
t = Thread(target=model.generate, kwargs=generate_kwargs)
t.start()
partial_message = ""
for new_token in streamer:
partial_message += new_token
print(new_token, end="", flush=True)
messages.append({'role': 'assistant', 'content': partial_message})
return messages[1:] # 不返回 system prompt
# === 主对话循环 ===
def main():
os_name = platform.system()
clear_command = 'cls' if os_name == 'Windows' else 'clear'
welcome_prompt = '欢迎使用 DeepSeek-R1-Distill-Qwen-1.5B 模型,输入内容即可进行对话,clear 清空对话历史,stop 终止程序'
print(welcome_prompt)
history = []
while True:
query = input("\n用户:").strip()
if query == "stop":
break
if query == "clear":
os.system(clear_command)
print(welcome_prompt)
history = []
continue
print("\nDeepSeek-R1-Distill-Qwen-1.5B:", end="")
history = inference(query, history)
print("")
# === 程序入口 ===
if __name__ == "__main__":
main()
运行后效果如下:
接着,让我们进行性能优化后再来查看一下每条语句推理的速度:
首先,我们来进行一下O2级别的jit优化,也就是我们所说的图算融合
mindspore.set_context(
enable_graph_kernel=True,
mode=mindspore.GRAPH_MODE,
jit_config={
"jit_level": "O2",
},
)
然后加载一下原模型。速度是非常的快,可以看到可以跑到32.4MB/S
最后,进行每一步的时间测试。
可以看到,在第一步的时候耗时18.3s,然后后面的每一步都是0.12s左右了。为什么会这样呢?这是因为在这个优化算法下,我们第一步需要先进行一下编译,也就是我们所说的冷启动,比较耗时,然后后面时间就比较快了。
最后,总结一下:
| 模式 | 动态图(普通模型) | 静态图(JIT模型) |
|---|---|---|
| 计算图构建 | 运行时即时构建 | 运行前提前编译 |
| 灵活性 | 高,支持动态控制 | 较低,不支持复杂动态变化 |
| 调试难度 | 低,代码直观易调试 | 高,调试相对复杂 |
| 推理效率 | 较低 | 高 |
| 适用场景 | 研发、调试、模型探索 | 生产环境、实时推理 |
| 部署稳定性 | 较差 | 较好 |






