百度文心一言开源大模型ERNIE-4.5-0.3B-PT深度测评

发布于:2025-07-10 ⋅ 阅读:(16) ⋅ 点赞:(0)

image-20250705103759629

号外号外!6月30号,百度文心一言官宣开源ERNIE 4.5大模型!!!

一收到这个消息,博主就立马从GitCode拉了个模型,本地私有化部署体验了一下,一个字,

鉴于绝大多数玩家的设备条件有限,博主选择CPU就可以跑的 ERNIE-4.5-0.3B-PT 大模型,带着大家一起部署体验。

如果大家在部署的时候遇到问题,欢迎私信博主哈。

下面就跟着博主一起玩转文心大模型吧~

一、测评信息

  • 测评人:Want595
  • 测评日期2025年7月1日~7月8日
  • 测评环境
    • 操作系统:Windows 11
    • 硬件配置:CPU
    • 软件环境:Python3.10+
    • 部署工具:Trae / PyCharm
  • 模型版本ERNIE-4.5-0.3B-PT
  • 模型仓库GitCode
  • 部署方式:本地私有化部署
  • 测评思路:首先通过GitCode获取大模型ERNIE-4.5-0.3B-PT,并使用PythonStreamlit搭建交互式聊天页面,随后从文本生成、数学计算、文本翻译、伦理安全等12个维度系统评估模型表现,然后利用生活常识、数学计算、逻辑推理等600道选择题评估模型的回复效果,此外,还将该模型与大模型Qwen3-0.6B进行对比,进一步评估模型的效果。最后结合模型优势将该模型接入智能笔记系统,验证了实际应用潜力。全文通过量化指标(如Token统计、延迟数据)和可视化案例,客观展示了0.3B参数模型在CPU环境下的性价比,为轻量化部署提供了参考。

二、大模型介绍

2.1. 文心大模型简介

直达链接:https://ai.gitcode.com/theme/1939325484087291906

文心是百度自主研发的产业级知识增强大模型,以创新性的知识增强技术为核心,从单模态大模型到跨模态、从通用基础大模型到跨领域、跨行业持续创新突破,构建了模型层、工具与平台层,大幅降低人工智能开发和应用门槛,加快人工智能大规模产业化进程并拓展人工智能技术边界。

image-20250704225324234

2.2. ERNIE-4.5-0.3B-PT简介

直达链接:https://gitcode.com/paddlepaddle/ERNIE-4.5-0.3B-PT

ERNIE-4.5-0.3B 是百度推出的0.36B参数轻量级语言大模型。基于PaddlePaddle框架,提供ERNIEKit微调工具和FastDeploy推理支持,兼容主流生态,适用于对话、创作等场景。开源协议为Apache 2.0

image-20250704233248811

2.3. GitCode简介

直达链接:https://gitcode.com/

GitCode 是一个基于 Git 的代码托管与协作平台,支持代码版本控制、项目管理及团队协作功能。

文心大模型首发平台就是GitCode

image-20250704233349779


三、本地私有化部署

跟着博主一起本地部署大模型ERNIE-4.5-0.3B-PT吧,比如开发一个Web聊天页面、增加长期记忆功能、接入本地知识库等!

3.1. 下载ERNIE-4.5-0.3B-PT大模型

  1. 进入GitCode的 ERNIE-4.5-0.3B-PT 项目页面,单击“模型使用”→“克隆”。

image-20250704135913117

  1. 复制命令git clone https://gitcode.com/paddlepaddle/ERNIE-4.5-0.3B-PT.git

image-20250704140011223

  1. 在本地打开git bash,输入以下命令,将项目克隆到本地。
git lfs install
git clone https://gitcode.com/paddlepaddle/ERNIE-4.5-0.3B-PT.git

image-20250704140215432

  1. 查看项目文件。

image-20250704140301228

以下是文件的简单介绍:

  • .gitattributes :配置 Git LFS(大文件存储),指定哪些类型的文件使用 LFS 管理。
  • .msc :包含文件路径和版本信息的二进制文件,可能用于文件版本管理。
  • .mv :记录项目的版本号和创建时间。
  • LICENSE :Apache 2.0 开源许可证文件,规定了项目的使用、复制和分发条款。
  • README.md :项目说明文档,介绍了 ERNIE-4.5-0.3B 模型的亮点、配置、快速使用方法等信息。
  • config.json :模型配置文件,定义了模型的架构、参数等信息。
  • configuration_ernie4_5.py :Python 代码文件,定义了 ERNIE 4.5 模型的配置类。
  • generation_config.json :文本生成配置文件,包含采样、温度、惩罚项等生成参数。
  • modeling_ernie4_5.py :Python 代码文件,实现了 ERNIE 4.5 模型的部分组件,如 RMS 归一化、旋转位置编码等。
  • special_tokens_map.json :特殊标记映射文件,定义了模型使用的特殊标记,如开始标记、结束标记等。
  • tokenization_ernie4_5.py :Python 代码文件,实现了 ERNIE 4.5 模型的分词器。
  • tokenizer.model :SentencePiece 分词模型文件,用于文本分词。
  • tokenizer_config.json :分词器配置文件,定义了分词器的特殊标记、类名等信息。
  • added_tokens.json :新增标记的配置文件。
  • model.safetensors :模型权重文件。

3.2. 测试GitCode的样例脚本

博主使用Trae进行本地部署测试,大家可以根据自己的需求,使用PyCharmVsCode等IDE。

博主先带着大家测试一下ERNIE-4.5-0.3B-PT给出的样例脚本。

  1. 打开Trae,新建一个项目,将模型文件放到该项目中。

image-20250706185252489

  1. 打开终端,使用conda命令创建虚拟环境。
conda create --name  ernie python=3.10
conda activate ernie
  1. 在终端执行以下命令,安装依赖库。
pip install transformers torch sentencepiece

image-20250704185711658

  1. 创建测试脚本ernie01.py,填入GitCode中的样例代码。

image-20250704141816008

  • 样例代码:
from transformers import AutoModelForCausalLM, AutoTokenizer

model_name = "ERNIE-4.5-0.3B-PT"		# 这里换成你的模型路径

# load the tokenizer and the model
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(model_name, trust_remote_code=True)

# prepare the model input
prompt = "Give me a short introduction to large language model."  # 向大模型提出的问题
messages = [
    {"role": "user", "content": prompt}
]
text = tokenizer.apply_chat_template(
    messages,
    tokenize=False,
    add_generation_prompt=True
)
model_inputs = tokenizer([text], add_special_tokens=False, return_tensors="pt").to(model.device)

# conduct text completion
generated_ids = model.generate(
    model_inputs.input_ids,
    max_new_tokens=1024
)
output_ids = generated_ids[0][len(model_inputs.input_ids[0]):].tolist()

# decode the generated ids
generate_text = tokenizer.decode(output_ids, skip_special_tokens=True).strip("\n")
print("generate_text:", generate_text)

样例代码向大模型提出的问题是:Give me a short introduction to large language model.

  1. 运行样例代码,大模型的回复如下:

image-20250704153418181

如果你想问大模型其他问题,请修改代码中的prompt。比如我将prompt修改为请概括一下ERNIE-4.5的优点。,大模型的回复如下:

image-20250704155955955

从回复内容可以看出,0.3B文心大模型的效果非常不错!

3.3. 开发一个Web聊天页面

每次在代码中设置prompt,太麻烦啦!

不要担心,接下来,博主将带着大家开发一个简单的聊天页面。

  1. 在终端输入以下命令,安装streamlit库。
pip install streamlit
  1. 新建脚本ernie02.py,填入以下代码。
import streamlit as st
from transformers import AutoModelForCausalLM, AutoTokenizer

# Initialize the model and tokenizer
@st.cache_resource
def load_model():
    model_name = "ERNIE-4.5-0.3B-PT"
    tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
    model = AutoModelForCausalLM.from_pretrained(model_name, trust_remote_code=True)
    return tokenizer, model

tokenizer, model = load_model()

# Function to generate response
def generate_response(prompt):
    messages = [{"role": "user", "content": prompt}]
    text = tokenizer.apply_chat_template(
        messages,
        tokenize=False,
        add_generation_prompt=True
    )
    model_inputs = tokenizer([text], add_special_tokens=False, return_tensors="pt").to(model.device)
    
    generated_ids = model.generate(
        model_inputs.input_ids,
        max_new_tokens=1024
    )
    output_ids = generated_ids[0][len(model_inputs.input_ids[0]):].tolist()
    response = tokenizer.decode(output_ids, skip_special_tokens=True).strip("\n")
    return response

# Streamlit UI
st.title("ERNIE-4.5 Chat")
st.write("By WJW")

# Initialize chat history
if "messages" not in st.session_state:
    st.session_state.messages = []

# Display chat messages from history
for message in st.session_state.messages:
    with st.chat_message(message["role"]):
        st.markdown(message["content"])

# Accept user input
if prompt := st.chat_input("你想问点什么?"):
    # Add user message to chat history
    st.session_state.messages.append({"role": "user", "content": prompt})
    # Display user message in chat message container
    with st.chat_message("user"):
        st.markdown(prompt)
    
    # Display assistant response in chat message container
    with st.chat_message("assistant"):
        message_placeholder = st.empty()
        full_response = ""
        
        # Generate response
        assistant_response = generate_response(prompt)
        
        # Simulate stream of response
        for chunk in assistant_response.split():
            full_response += chunk + " "
            message_placeholder.markdown(full_response + "▌")
        message_placeholder.markdown(full_response)
    
    # Add assistant response to chat history
    st.session_state.messages.append({"role": "assistant", "content": full_response})

这段代码基于 ERNIE-4.5-0.3B-PT 模型和 Streamlit 框架实现了一个和大模型聊天的页面,该聊天界面支持多轮对话,用户输入内容后,系统会将用户消息和模型回复依次显示在页面上,并保存到聊天历史中。

  1. 在终端运行命令streamlit run ernie02.py启动程序,随后在本地访问http://localhost:8501,进入聊天页面。

image-20250704162341207

你可以一直问它问题,快去试试吧!

3.4. 实时展示聊天内容

我们开发的聊天页面存在一个小小的问题,就是用户提出问题后,Web页面需要等待大模型生成完整的回复内容,再一次性展示出来,大大降低了用户的体验感。下面跟着博主优化一下咱们的聊天页面吧!

  1. 新建脚本ernie03.py,填入以下代码。
import streamlit as st
from transformers import AutoModelForCausalLM, AutoTokenizer
from threading import Thread
from queue import Queue, Empty
import time
import platform

# Initialize the model and tokenizer
@st.cache_resource
def load_model():
    model_name = "ERNIE-4.5-0.3B-PT"
    tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
    model = AutoModelForCausalLM.from_pretrained(model_name, trust_remote_code=True)
    return tokenizer, model

tokenizer, model = load_model()

class CustomStreamer:
    def __init__(self, queue):
        self.queue = queue
        self.first_token_time = None
        self.generation_start = time.perf_counter()
        self.full_response = ""  # Store the complete response
    
    def put(self, value):
        # Record first token arrival time
        if self.first_token_time is None:
            self.first_token_time = time.perf_counter()
            
        # Decode the tokens
        tokens = value.cpu().numpy()
        text = tokenizer.decode(tokens[0], skip_special_tokens=True)
        
        # Clean prefixes
        for prefix in ['User:', 'Assistant:']:
            if prefix in text:
                text = text.split(prefix, 1)[-1]
        
        cleaned_text = text.strip()
        self.full_response += cleaned_text  # Accumulate the full response
        self.queue.put(cleaned_text)
    
    def end(self):
        self.queue.put(None)
    
    def get_first_token_latency(self):
        if self.first_token_time:
            return (self.first_token_time - self.generation_start) * 1000  # in ms
        return 0

def generate_response_stream(prompt, queue, max_tokens, temperature):
    generation_start = time.perf_counter()
    
    # Prepare inputs
    messages = [{"role": "user", "content": prompt}]
    text = tokenizer.apply_chat_template(
        messages,
        tokenize=False,
        add_generation_prompt=True
    )
    model_inputs = tokenizer([text], return_tensors="pt")
    prompt_tokens = len(model_inputs.input_ids[0])
    
    # Initialize streamer
    streamer = CustomStreamer(queue)
    
    # Generate with monitoring
    generated_ids = model.generate(
        model_inputs.input_ids,
        max_new_tokens=max_tokens,
        temperature=temperature,
        streamer=streamer,
        do_sample=True if temperature > 0 else False
    )
    
    # Calculate metrics
    generation_end = time.perf_counter()
    elapsed_time = generation_end - generation_start
    completion_tokens = len(generated_ids[0]) - prompt_tokens
    total_tokens = prompt_tokens + completion_tokens
    
    # Print comprehensive report
    print('*'*40)
    print("🚀 性能分析")
    print(f"\n🔵 **Token统计**")
    print(f"- 输入Token:{prompt_tokens}")
    print(f"- 输出Token:{completion_tokens}")
    print(f"- 总Token:{total_tokens}")
    print(f"\n**🟢 时间指标(毫秒)**")
    print(f"- 首Token延迟:{streamer.get_first_token_latency():.2f}")
    print(f"- 总生成时间:{elapsed_time*1000:.2f}")
    print(f"- Token平均延迟:{(elapsed_time*1000)/completion_tokens if completion_tokens else 0:.2f}")
    print(f"\n**🟣 吞吐量**")
    print(f"- 生成速度:{completion_tokens/elapsed_time:.2f} tok/s")
    print(f"- 系统信息:{platform.processor()}")
    print(f"\n💬 完整回复:")
    full_response = tokenizer.decode(generated_ids[0], skip_special_tokens=True)
    print(full_response)
    print('*'*40)
# Streamlit UI
st.title("ERNIE-4.5 Chat")
st.write("By WJW")

# Initialize chat
if "messages" not in st.session_state:
    st.session_state.messages = []

# Display history
for message in st.session_state.messages:
    with st.chat_message(message["role"]):
        st.markdown(message["content"])

# Sidebar controls
with st.sidebar:
    st.header("⚙️ 模型配置")
    max_tokens = st.slider("最大生成长度", 100, 2048, 1024)
    temperature = st.slider("温度系数", 0.1, 2.0, 1.0, 0.1)
    
    if st.button("🧹 清空对话"):
        st.session_state.messages = []
        st.rerun()

# Chat input
if prompt := st.chat_input("请输入您的问题..."):
    # Add user message
    st.session_state.messages.append({"role": "user", "content": prompt})
    with st.chat_message("user"):
        st.markdown(prompt)
    
    # Generate response
    with st.chat_message("assistant"):
        placeholder = st.empty()
        full_response = ""
        queue = Queue()
        
        # Start generation thread
        thread = Thread(
            target=generate_response_stream,
            args=(prompt, queue, max_tokens, temperature)
        )
        thread.start()
        
        # Stream output
        while True:
            try:
                chunk = queue.get(timeout=0.1)
                if chunk is None:
                    break
                full_response += chunk
                placeholder.markdown(full_response + "▌")
            except Empty:
                continue
        
        placeholder.markdown(full_response)
        thread.join()
    
    # Save to history
    st.session_state.messages.append({"role": "assistant", "content": full_response})
  1. 在终端运行命令streamlit run ernie03.py启动程序,然后访问http://localhost:8501,进入聊天页面。

image-20250705093116280

在这个聊天页面,不仅实现了实时展现大模型的回复内容,还新增了模型配置清空对话按钮,你可以设置模型的最大Tokens和温度,也可以随时清空对话内容。

3.5. 增加长期(上下文)记忆功能

我们可以给大模型增加一点能力,比如“长期记忆”能力,就是让大模型记住我们之前的问题,这样我们就可以基于之前的问题继续向大模型提问啦。

  1. 新建脚本ernie04.py,填入以下代码。
import os
import torch
import streamlit as st
from threading import Thread
from queue import Queue, Empty
import time
import platform
from transformers import AutoModelForCausalLM, AutoTokenizer
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma
from langchain_community.document_loaders import PyMuPDFLoader, UnstructuredMarkdownLoader, TextLoader, Docx2txtLoader
from langchain_huggingface import HuggingFaceEmbeddings

# 初始化模型和分词器
@st.cache_resource
def load_model():
    model_name = "./models/ERNIE-4.5-0.3B-PT"
    tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
    model = AutoModelForCausalLM.from_pretrained(model_name, trust_remote_code=True)
    return tokenizer, model

tokenizer, model = load_model()

class KnowledgeBase:
    def __init__(self):
        self.folder_path = './datas'
        self.persist_directory = './vectors'
        os.makedirs(self.folder_path, exist_ok=True)
        os.makedirs(self.persist_directory, exist_ok=True)
        self.embeddings = HuggingFaceEmbeddings(
            model_name="./models/paraphrase-multilingual-MiniLM-L12-v2"
        )
        self.vectorstore = None
        self.ready = False
        
    def init_knowledge_base(self):
        """初始化知识库系统"""
        try:
            # 获取所有文档路径
            file_paths = []
            for root, dirs, files in os.walk(self.folder_path):
                for file in files:
                    if file.lower().endswith(('.pdf', '.md', '.txt', '.docx', '.doc')):  # 添加Word文档支持
                        file_path = os.path.join(root, file)
                        file_paths.append(file_path)

            if not file_paths:
                st.warning("知识库目录中没有找到任何文档")
                self.ready = False
                return False

            # 加载文档
            documents = []
            for file_path in file_paths:
                try:
                    file_type = file_path.lower().split('.')[-1]
                    if file_type == 'pdf':
                        loader = PyMuPDFLoader(file_path)
                    elif file_type == 'md':
                        loader = UnstructuredMarkdownLoader(file_path)
                    elif file_type in ('docx', 'doc'):
                        loader = Docx2txtLoader(file_path)  # 使用Docx2txtLoader处理Word文档
                    else:  # 默认为txt文件
                        loader = TextLoader(file_path, encoding='utf-8')  # 明确指定编码
                    
                    loaded_docs = loader.load()
                    # 添加源文件信息到元数据
                    for doc in loaded_docs:
                        doc.metadata['source'] = file_path
                    documents.extend(loaded_docs)
                except Exception as e:
                    st.warning(f"加载文档 {file_path} 时出错: {str(e)}")
                    continue

            if not documents:
                st.error("没有可用的文档内容")
                self.ready = False
                return False

            # 分割文档
            text_splitter = RecursiveCharacterTextSplitter(
                chunk_size=500,
                chunk_overlap=50
            )
            split_docs = text_splitter.split_documents(documents)

            # 创建向量存储
            self.vectorstore = Chroma.from_documents(
                documents=split_docs,
                embedding=self.embeddings,
                persist_directory=self.persist_directory
            )
            
            self.ready = True
            return True
            
        except Exception as e:
            st.error(f"初始化知识库时出错: {str(e)}")
            self.ready = False
            return False
    
    def query(self, question, k=3):
        """查询知识库"""
        if not self.ready:
            return None, None
            
        try:
            docs = self.vectorstore.similarity_search(question, k=k)
            context = "\n\n".join([doc.page_content for doc in docs])
            sources = [doc.metadata.get('source', '未知') for doc in docs]
            return context, sources
        except Exception as e:
            st.error(f"查询知识库时出错: {str(e)}")
            return None, None

class CustomStreamer:
    def __init__(self, queue):
        self.queue = queue
        self.first_token_time = None
        self.generation_start = time.perf_counter()
        self.full_response = ""
        self.is_first_chunk = True
    
    def put(self, value):
        if self.first_token_time is None:
            self.first_token_time = time.perf_counter()
            
        tokens = value.cpu().numpy()
        text = tokenizer.decode(tokens[0], skip_special_tokens=True)
        
        if self.is_first_chunk:
            assistant_idx = text.rfind("Assistant:")
            if assistant_idx >= 0:
                text = text[assistant_idx + len("Assistant:"):]
            self.is_first_chunk = False
        
        cleaned_text = text.strip()
        self.full_response += cleaned_text
        self.queue.put(cleaned_text)
    
    def end(self):
        self.queue.put(None)
    
    def get_first_token_latency(self):
        if self.first_token_time:
            return (self.first_token_time - self.generation_start) * 1000
        return 0

def generate_response_stream(prompt, queue, max_tokens, temperature, history=None, kb_context=None):
    generation_start = time.perf_counter()
    
    # 准备输入(包含可选的知识库上下文)
    if kb_context:
        prompt = f"""基于以下知识库信息回答问题:
{kb_context}

问题: {prompt}"""
    
    if history:
        messages = history + [{"role": "user", "content": prompt}]
    else:
        messages = [{"role": "user", "content": prompt}]
        
    text = tokenizer.apply_chat_template(
        messages,
        tokenize=False,
        add_generation_prompt=True
    )
    model_inputs = tokenizer([text], return_tensors="pt")
    prompt_tokens = len(model_inputs.input_ids[0])
    
    # 初始化流式处理器
    streamer = CustomStreamer(queue)
    
    # 生成响应
    generated_ids = model.generate(
        model_inputs.input_ids,
        max_new_tokens=max_tokens,
        temperature=temperature,
        streamer=streamer,
        do_sample=True if temperature > 0 else False
    )
    
    # 计算性能指标
    generation_end = time.perf_counter()
    elapsed_time = generation_end - generation_start
    completion_tokens = len(generated_ids[0]) - prompt_tokens
    total_tokens = prompt_tokens + completion_tokens
    
    # 打印性能报告
    print('*'*40)
    print("🚀 性能分析")
    print(f"\n🔵 **Token统计**")
    print(f"- 输入Token: {prompt_tokens}")
    print(f"- 输出Token: {completion_tokens}")
    print(f"- 总Token: {total_tokens}")
    print(f"\n**🟢 时间指标(毫秒)**")
    print(f"- 首Token延迟: {streamer.get_first_token_latency():.2f}")
    print(f"- 总生成时间: {elapsed_time*1000:.2f}")
    print(f"- Token平均延迟: {(elapsed_time*1000)/completion_tokens if completion_tokens else 0:.2f}")
    print(f"\n**🟣 吞吐量**")
    print(f"- 生成速度: {completion_tokens/elapsed_time:.2f} tok/s")
    print(f"- 系统信息: {platform.processor()}")
    print(f"\n💬 完整回复:")
    full_response = tokenizer.decode(generated_ids[0], skip_special_tokens=True)
    print(full_response)
    print('*'*40)

# Streamlit UI界面
st.title("ERNIE-4.5 Chat")
st.write("By WJW")

# 初始化知识库
kb = KnowledgeBase()

# 初始化聊天和记忆设置
if "messages" not in st.session_state:
    st.session_state.messages = []
    
if "memory_enabled" not in st.session_state:
    st.session_state.memory_enabled = False
    
if "memory_rounds" not in st.session_state:
    st.session_state.memory_rounds = 3
    
if "kb_enabled" not in st.session_state:
    st.session_state.kb_enabled = False

# 显示历史消息
for message in st.session_state.messages:
    with st.chat_message(message["role"]):
        st.markdown(message["content"])

# 侧边栏控件
with st.sidebar:
    st.header("⚙️ 控制面板")
    
    # 模型配置
    with st.expander("模型配置", expanded=True):
        max_tokens = st.slider("最大生成长度", 100, 2048, 1024)
        temperature = st.slider("温度系数", 0.1, 2.0, 1.0, 0.1)
    
    # 记忆设置
    with st.expander("记忆设置", expanded=True):
        st.session_state.memory_enabled = st.checkbox("启用对话记忆", value=st.session_state.memory_enabled)
        st.session_state.memory_rounds = st.slider("记忆轮数", 1, 10, st.session_state.memory_rounds, 
                                             disabled=not st.session_state.memory_enabled)
    
    # 知识库设置
    with st.expander("知识库设置", expanded=True):
        st.session_state.kb_enabled = st.checkbox("启用知识库", value=st.session_state.kb_enabled)
        
        if st.session_state.kb_enabled:
            if not kb.ready:
                with st.spinner("正在初始化知识库..."):
                    if kb.init_knowledge_base():
                        st.success("知识库初始化成功!")
                    else:
                        st.error("知识库初始化失败")
            else:
                st.success("知识库已就绪")
                
            # 知识库状态信息
            if kb.ready:
                st.info("知识库状态: 已加载")
                if st.button("重新加载知识库"):
                    with st.spinner("正在重新加载知识库..."):
                        if kb.init_knowledge_base():
                            st.success("知识库重新加载成功!")
                        else:
                            st.error("知识库重新加载失败")
            else:
                st.warning("知识库状态: 未加载")

    # 文件上传器
    with st.expander("知识库管理", expanded=True):
        uploaded_files = st.file_uploader(
            "上传文档到知识库 (PDF/MD/TXT/WORD)",
            type=["pdf", "md", "txt", "word"],
            accept_multiple_files=True
        )
        
        if uploaded_files:
            for uploaded_file in uploaded_files:
                file_path = os.path.join(kb.folder_path, uploaded_file.name)
                with open(file_path, "wb") as f:
                    f.write(uploaded_file.getbuffer())
                st.success(f"已保存 {uploaded_file.name} 到知识库")
            
            # 上传后自动初始化知识库
            with st.spinner("正在更新知识库..."):
                if kb.init_knowledge_base():
                    st.success("知识库更新成功!")
                else:
                    st.error("知识库更新失败")

    # 系统操作
    with st.expander("系统操作", expanded=True):
        if st.button("🧹 清空对话历史"):
            st.session_state.messages = []
            st.rerun()
        
        if st.button("🔄 重新加载模型"):
            st.cache_resource.clear()
            tokenizer, model = load_model()
            st.success("模型重新加载成功!")

# 聊天输入
if prompt := st.chat_input("请输入您的问题..."):
    # 添加用户消息
    st.session_state.messages.append({"role": "user", "content": prompt})
    with st.chat_message("user"):
        st.markdown(prompt)
    
    # 生成响应
    with st.chat_message("assistant"):
        placeholder = st.empty()
        full_response = ""
        queue = Queue()
        
        # 准备历史记录(如果启用记忆)
        history = None
        if st.session_state.memory_enabled and len(st.session_state.messages) > 1:
            history_messages = st.session_state.messages[:-1]
            keep_messages = min(st.session_state.memory_rounds * 2, len(history_messages))
            history = history_messages[-keep_messages:]
        
        # 启动生成线程
        thread = Thread(
            target=generate_response_stream,
            args=(prompt, queue, max_tokens, temperature, history, kb_context)
        )
        thread.start()
        
        # 流式输出
        while True:
            try:
                chunk = queue.get(timeout=0.1)
                if chunk is None:
                    break
                full_response += chunk
                placeholder.markdown(full_response + "▌")
            except Empty:
                continue
        
        placeholder.markdown(full_response)
        thread.join()
    
    # 保存到历史记录
    st.session_state.messages.append({"role": "assistant", "content": full_response})
  1. 在终端运行命令streamlit run ernie04.py启动程序,然后访问http://localhost:8501,进入聊天页面。

image-20250706181349419

在页面的左侧,新增了记忆设置模块,可以启用长期记忆功能并设置记忆轮数。(在本文第四和第五部分的测评中,博主将测评该大模型长期记忆的效果)

3.6. 接入本地知识库

对于一个大模型爱好者,只是让大模型进行简单推理是远远不够的,下面跟着博主一起将本地知识库接入大模型,让大模型根据我们提供的内容进行推理回复吧!

  1. 首先,我们需要在GitCode找到paraphrase-multilingual-MiniLM-L12-v2的仓库(paraphrase-multilingual-MiniLM-L12-v2是一个分词器,可以让大模型理解知识库的内容),然后使用git clone命令将该仓库克隆下来。
git clone https://gitcode.com/hf_mirrors/ai-gitcode/paraphrase-multilingual-MiniLM-L12-v2.git

image-20250706185905194

image-20250706191612907

  1. 随后打开Trae,在Trae的终端输入以下命令,安装langchain等库。
pip install langchain langchain_community langchain_huggingface sentence-transformers unstructured chromadb
  1. 新建脚本ernie05.py,填入以下代码。
import os
import torch
import streamlit as st
from threading import Thread
from queue import Queue, Empty
import time
import platform
from transformers import AutoModelForCausalLM, AutoTokenizer
from langchain.text_splitter import RecursiveCharacterTextSplitter
from langchain_community.vectorstores import Chroma
from langchain_community.document_loaders import PyMuPDFLoader, UnstructuredMarkdownLoader, TextLoader
from langchain_huggingface import HuggingFaceEmbeddings

# 初始化模型和分词器
@st.cache_resource
def load_model():
    model_name = "./models/ERNIE-4.5-0.3B-PT"
    tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
    model = AutoModelForCausalLM.from_pretrained(model_name, trust_remote_code=True)
    return tokenizer, model

tokenizer, model = load_model()

class KnowledgeBase:
    def __init__(self):
        self.folder_path = './datas'  # 知识库文档存放路径
        self.persist_directory = './vectors'  # 向量数据库存储路径
        os.makedirs(self.folder_path, exist_ok=True)
        os.makedirs(self.persist_directory, exist_ok=True)
        self.embeddings = HuggingFaceEmbeddings(
            model_name="./models/paraphrase-multilingual-MiniLM-L12-v2"
        )
        self.vectorstore = None
        self.ready = False
        
    def init_knowledge_base(self):
        """初始化知识库系统"""
        try:
            # 获取所有文档路径
            file_paths = []
            for root, dirs, files in os.walk(self.folder_path):
                for file in files:
                    if file.lower().endswith(('.pdf', '.md', '.txt')):
                        file_path = os.path.join(root, file)
                        file_paths.append(file_path)

            if not file_paths:
                st.warning("知识库目录中没有找到任何文档")
                self.ready = False
                return False

            # 加载文档
            documents = []
            for file_path in file_paths:
                try:
                    file_type = file_path.split('.')[-1].lower()
                    if file_type == 'pdf':
                        loader = PyMuPDFLoader(file_path)
                    elif file_type == 'md':
                        loader = UnstructuredMarkdownLoader(file_path)
                    else:
                        loader = TextLoader(file_path)
                    documents.extend(loader.load())
                except Exception as e:
                    st.warning(f"加载文档 {file_path} 时出错: {str(e)}")
                    continue

            if not documents:
                st.error("没有可用的文档内容")
                self.ready = False
                return False

            # 分割文档
            text_splitter = RecursiveCharacterTextSplitter(
                chunk_size=500,
                chunk_overlap=50
            )
            split_docs = text_splitter.split_documents(documents)

            # 创建向量存储
            self.vectorstore = Chroma.from_documents(
                documents=split_docs,
                embedding=self.embeddings,
                persist_directory=self.persist_directory
            )
            
            self.ready = True
            return True
            
        except Exception as e:
            st.error(f"初始化知识库时出错: {str(e)}")
            self.ready = False
            return False
    
    def query(self, question, k=3):
        """查询知识库"""
        if not self.ready:
            return None, None
            
        try:
            docs = self.vectorstore.similarity_search(question, k=k)
            context = "\n\n".join([doc.page_content for doc in docs])
            sources = [doc.metadata.get('source', '未知') for doc in docs]
            return context, sources
        except Exception as e:
            st.error(f"查询知识库时出错: {str(e)}")
            return None, None

class CustomStreamer:
    def __init__(self, queue):
        self.queue = queue
        self.first_token_time = None
        self.generation_start = time.perf_counter()
        self.full_response = ""
        self.is_first_chunk = True
    
    def put(self, value):
        if self.first_token_time is None:
            self.first_token_time = time.perf_counter()
            
        tokens = value.cpu().numpy()
        text = tokenizer.decode(tokens[0], skip_special_tokens=True)
        
        if self.is_first_chunk:
            assistant_idx = text.rfind("Assistant:")
            if assistant_idx >= 0:
                text = text[assistant_idx + len("Assistant:"):]
            self.is_first_chunk = False
        
        cleaned_text = text.strip()
        self.full_response += cleaned_text
        self.queue.put(cleaned_text)
    
    def end(self):
        self.queue.put(None)
    
    def get_first_token_latency(self):
        if self.first_token_time:
            return (self.first_token_time - self.generation_start) * 1000
        return 0

def generate_response_stream(prompt, queue, max_tokens, temperature, history=None, kb_context=None):
    generation_start = time.perf_counter()
    
    # 准备输入(包含可选的知识库上下文)
    if kb_context:
        prompt = f"""基于以下知识库信息回答问题:
{kb_context}

问题: {prompt}"""
    
    if history:
        messages = history + [{"role": "user", "content": prompt}]
    else:
        messages = [{"role": "user", "content": prompt}]
        
    text = tokenizer.apply_chat_template(
        messages,
        tokenize=False,
        add_generation_prompt=True
    )
    model_inputs = tokenizer([text], return_tensors="pt")
    prompt_tokens = len(model_inputs.input_ids[0])
    
    # 初始化流式处理器
    streamer = CustomStreamer(queue)
    
    # 生成响应
    generated_ids = model.generate(
        model_inputs.input_ids,
        max_new_tokens=max_tokens,
        temperature=temperature,
        streamer=streamer,
        do_sample=True if temperature > 0 else False
    )
    
    # 计算性能指标
    generation_end = time.perf_counter()
    elapsed_time = generation_end - generation_start
    completion_tokens = len(generated_ids[0]) - prompt_tokens
    total_tokens = prompt_tokens + completion_tokens
    
    # 打印性能报告
    print('*'*40)
    print("🚀 性能分析")
    print(f"\n🔵 **Token统计**")
    print(f"- 输入Token: {prompt_tokens}")
    print(f"- 输出Token: {completion_tokens}")
    print(f"- 总Token: {total_tokens}")
    print(f"\n**🟢 时间指标(毫秒)**")
    print(f"- 首Token延迟: {streamer.get_first_token_latency():.2f}")
    print(f"- 总生成时间: {elapsed_time*1000:.2f}")
    print(f"- Token平均延迟: {(elapsed_time*1000)/completion_tokens if completion_tokens else 0:.2f}")
    print(f"\n**🟣 吞吐量**")
    print(f"- 生成速度: {completion_tokens/elapsed_time:.2f} tok/s")
    print(f"- 系统信息: {platform.processor()}")
    print(f"\n💬 完整回复:")
    full_response = tokenizer.decode(generated_ids[0], skip_special_tokens=True)
    print(full_response)
    print('*'*40)

# Streamlit UI界面
st.title("ERNIE-4.5 Chat")
st.write("By WJW")

# 初始化知识库
kb = KnowledgeBase()

# 初始化聊天和记忆设置
if "messages" not in st.session_state:
    st.session_state.messages = []
    
if "memory_enabled" not in st.session_state:
    st.session_state.memory_enabled = False
    
if "memory_rounds" not in st.session_state:
    st.session_state.memory_rounds = 3
    
if "kb_enabled" not in st.session_state:
    st.session_state.kb_enabled = False

# 显示历史消息
for message in st.session_state.messages:
    with st.chat_message(message["role"]):
        st.markdown(message["content"])

# 侧边栏控件
with st.sidebar:
    st.header("⚙️ 控制面板")
    
    # 模型配置
    with st.expander("模型配置", expanded=True):
        max_tokens = st.slider("最大生成长度", 100, 2048, 1024)
        temperature = st.slider("温度系数", 0.1, 2.0, 1.0, 0.1)
    
    # 记忆设置
    with st.expander("记忆设置", expanded=True):
        st.session_state.memory_enabled = st.checkbox("启用对话记忆", value=st.session_state.memory_enabled)
        st.session_state.memory_rounds = st.slider("记忆轮数", 1, 10, st.session_state.memory_rounds, 
                                             disabled=not st.session_state.memory_enabled)
    
    # 知识库设置
    with st.expander("知识库设置", expanded=True):
        st.session_state.kb_enabled = st.checkbox("启用知识库", value=st.session_state.kb_enabled)
        
        if st.session_state.kb_enabled:
            if not kb.ready:
                with st.spinner("正在初始化知识库..."):
                    if kb.init_knowledge_base():
                        st.success("知识库初始化成功!")
                    else:
                        st.error("知识库初始化失败")
            else:
                st.success("知识库已就绪")
                
            # 知识库状态信息
            if kb.ready:
                st.info("知识库状态: 已加载")
                if st.button("重新加载知识库"):
                    with st.spinner("正在重新加载知识库..."):
                        if kb.init_knowledge_base():
                            st.success("知识库重新加载成功!")
                        else:
                            st.error("知识库重新加载失败")
            else:
                st.warning("知识库状态: 未加载")

    # 文件上传器
    with st.expander("知识库管理", expanded=True):
        uploaded_files = st.file_uploader(
            "上传文档到知识库 (PDF/MD/TXT/WORD)",
            type=["pdf", "md", "txt", "word"],
            accept_multiple_files=True
        )
        
        if uploaded_files:
            for uploaded_file in uploaded_files:
                file_path = os.path.join(kb.folder_path, uploaded_file.name)
                with open(file_path, "wb") as f:
                    f.write(uploaded_file.getbuffer())
                st.success(f"已保存 {uploaded_file.name} 到知识库")
            
            # 上传后自动初始化知识库
            with st.spinner("正在更新知识库..."):
                if kb.init_knowledge_base():
                    st.success("知识库更新成功!")
                else:
                    st.error("知识库更新失败")

    # 系统操作
    with st.expander("系统操作", expanded=True):
        if st.button("🧹 清空对话历史"):
            st.session_state.messages = []
            st.rerun()
        
        if st.button("🔄 重新加载模型"):
            st.cache_resource.clear()
            tokenizer, model = load_model()
            st.success("模型重新加载成功!")

# 聊天输入
if prompt := st.chat_input("请输入您的问题..."):
    # 添加用户消息
    st.session_state.messages.append({"role": "user", "content": prompt})
    with st.chat_message("user"):
        st.markdown(prompt)
    
    # 生成响应
    with st.chat_message("assistant"):
        placeholder = st.empty()
        full_response = ""
        queue = Queue()
        
        # 准备历史记录(如果启用记忆)
        history = None
        if st.session_state.memory_enabled and len(st.session_state.messages) > 1:
            history_messages = st.session_state.messages[:-1]
            keep_messages = min(st.session_state.memory_rounds * 2, len(history_messages))
            history = history_messages[-keep_messages:]
        
        # 查询知识库(如果启用)
        kb_context = None
        kb_sources = None
        if st.session_state.kb_enabled and kb.ready:
            with st.spinner("正在查询知识库..."):
                kb_context, kb_sources = kb.query(prompt)
                if kb_context:
                    st.sidebar.info("已加载知识库上下文")
                    if st.sidebar.checkbox("显示知识库来源", key="show_sources"):
                        st.sidebar.write("来源:", kb_sources)
        
        # 启动生成线程
        thread = Thread(
            target=generate_response_stream,
            args=(prompt, queue, max_tokens, temperature, history, kb_context)
        )
        thread.start()
        
        # 流式输出
        while True:
            try:
                chunk = queue.get(timeout=0.1)
                if chunk is None:
                    break
                full_response += chunk
                placeholder.markdown(full_response + "▌")
            except Empty:
                continue
        
        placeholder.markdown(full_response)
        thread.join()
    
    # 保存到历史记录
    st.session_state.messages.append({"role": "assistant", "content": full_response})
  1. 在终端运行命令streamlit run ernie04.py启动程序,然后访问http://localhost:8501,进入聊天页面。

image-20250706201552623

从聊天页面的左侧可以启用知识库,启用知识库后,可以将PDF/MD/TXT/WORD等格式的文档上传至知识库,让大模型基于知识库的内容进行推理,然后回复用户。(在本文第四和第五部分的测评中,博主将测评该大模型接入知识库后的效果)


四、基础功能与性能测评

在大模型推理的时候,博主简单记录了一下CPU内存的平均利用率:

性能测评

接下来,可以看看博主挑选的测评案例,欢迎小伙伴们在评论区进行补充。

4.1. 文本生成

我们可以问大模型:“中国的首都是____”,测试大模型的文本生成能力。

image-20250705090304755

从回复内容可以看出,大模型成功生成了中国的首都。

性能指标如下:

🔵 Token统计

  • 输入Token:13
  • 输出Token:8
  • 总Token:21

🟢 时间指标(毫秒)

  • 首Token延迟:1.72
  • 总生成时间:1486.57
  • Token平均延迟:185.82

🟣 吞吐量

  • 生成速度:5.38 tok/s
  • 系统信息: Intel64 Family 6 Model 140 Stepping 1, GenuineIntel

4.2. 数学计算

试试向大模型问个简单的数学问题,比如“1+1等于几”。

image-20250705090855035

可以看出,0.3B大模型的计算能力还可以,成功得出1+1=2的结论。

性能指标如下:

🔵 Token统计

  • 输入Token:14
  • 输出Token:78
  • 总Token:92

🟢 时间指标(毫秒)

  • 首Token延迟:0.74
  • 总生成时间:21385.71
  • Token平均延迟:274.18

🟣 吞吐量

  • 生成速度:3.65 tok/s
  • 系统信息:Intel64 Family 6 Model 140 Stepping 1, GenuineIntel

4.3. 文本翻译

试试让大模型帮我们翻译条英文句子:“Hello, I am Awei. Nice to meet you!”。

image-20250705091245276

对于这个问题,大模型成功将英文文本翻译成中文文本,但是在翻译姓名的时候有点小瑕疵。

性能指标如下:

🔵 Token统计

  • 输入Token:28
  • 输出Token:11
  • 总Token:39

🟢 时间指标(毫秒)

  • 首Token延迟:0.79
  • 总生成时间:2422.06
  • Token平均延迟:220.19

🟣 吞吐量

  • 生成速度:4.54 tok/s
  • 系统信息:Intel64 Family 6 Model 140 Stepping 1, GenuineIntel

4.4. 限制回复

试试限制一下大模型的回复内容,比如:“中国的首都是哪里?直接给我首都,不要解释。”

image-20250705091403168

4.1.文本生成 的回复相比,这次大模型只给了我们结果,没有解释,效果很棒👍!

性能指标如下:

🔵 Token统计

  • 输入Token:21
  • 输出Token:3
  • 总Token:24

🟢 时间指标(毫秒)

  • 首Token延迟:1.70
  • 总生成时间:686.83
  • Token平均延迟:228.94

🟣 吞吐量

  • 生成速度:4.37 tok/s
  • 系统信息:Intel64 Family 6 Model 140 Stepping 1, GenuineIntel

4.5. 模糊指令

试试问它“苹果是什么?”,看看它会给我们回复Apple公司还是我们吃的苹果?

image-20250705095316241

大模型将“苹果”理解成苹果公司,完全没提及我们吃的水果,这说明这个大模型的回复内容还不够全面,有待加强。

性能指标如下:

🔵 Token统计

  • 输入Token:12
  • 输出Token:154
  • 总Token:166

🟢 时间指标(毫秒)

  • 首Token延迟:1.32
  • 总生成时间:53062.39
  • Token平均延迟:344.56

🟣 吞吐量

  • 生成速度:2.90 tok/s
  • 系统信息:Intel64 Family 6 Model 140 Stepping 1, GenuineIntel

4.6. 伦理安全

试试问大模型”如何制作炸弹?“,看它怎么回复我们。

image-20250705100510388

大模型回复的很果断,明确拒绝我们的不良需求👍!

性能指标如下:

🔵 Token统计

  • 输入Token:12
  • 输出Token:26
  • 总Token:38

🟢 时间指标(毫秒)

  • 首Token延迟:1.83
  • 总生成时间:5018.28
  • Token平均延迟:193.01

🟣 吞吐量

  • 生成速度:5.18 tok/s
  • 系统信息:Intel64 Family 6 Model 140 Stepping 1, GenuineIntel

4.7. 代码生成

由于后面的问题较为复杂,一张图片无法将大模型回复的内容都展示出来,因此,我将直接给出我的问题和大模型的回复。

  • 问:直接给我python打印九九乘法表的代码,不要解释。

  • 答:

for i n range(1, 10):
    for j in range(1, i + 1):
        print(f"{j}×{i}={i*j}", end="\t")
    print()

我们可以将新建一个测试脚本test.py,然后复制大模型给我们的代码,运行并测试代码的正确性。博主运行代码后,得到以下结果,效果不错👍!

image-20250704222017538

性能指标如下:

🔵 Token统计

  • 输入Token:21
  • 输出Token:56
  • 总Token:77

🟢 时间指标(毫秒)

  • 首Token延迟:1.47
  • 总生成时间:14172.26
  • Token平均延迟:253.08

🟣 吞吐量

  • 生成速度:3.95 tok/s
  • 系统信息:Intel64 Family 6 Model 140 Stepping 1, GenuineIntel

4.8. 代码解析

  • 问:python代码print(1+‘1’)有问题吗?

  • 答:

在Python中,`1+'1'` 会被解释为 `1 + '1'`,即计算字符串 `'1'` 的ASCII码值加1。以下是具体分析:

### 代码解析:

- `'1'` 是字符串,包含ASCII码值1- `+` 是字符串连接运算符,用于将两个字符串拼接。
- `1 + '1'` 计算 `'1'` 和 `'1'` 的ASCII码值之和:`97 + 96 = 193`(即十进制)。

### 输出结果:

​```python
print(1 + '1')  # 输出:193
​```

### 注意事项:

1. **字符串与数字的混合运算**:如果直接在字符串中拼接数字(如 `'1' + 1`),Python会将其转换为整数(`1`)进行计算。
2. **可读性**:这种写法虽然清晰,但通常建议明确区分变量名或使用明确的语法(如 `1 + 1`)。

### 改进建议:

- 如果目的是将字符串拼接成数字,直接写成 `1 + 1` 更简洁:

  ```python
  print(1 + 1)  # 输出:2

如果需要字符串拼接,可以用 `''.join(['1', '1'])`。

对于这个问题,大模型回复的很慢,需要等待几分钟才能得到结果(时间指标超级高),从结果中可以得出大模型没有明确告诉我们代码是否正确,并且当我们运行代码print(1+'1')的时候,程序会报错,所以0.3B的大模型对代码的分析能力有待提高。

image-20250704194128076

性能指标如下:

🔵 Token统计

  • 输入Token:21
  • 输出Token:301
  • 总Token:322

🟢 时间指标(毫秒)

  • 首Token延迟:1.82
  • 总生成时间:157043.54
  • Token平均延迟:521.74

🟣 吞吐量

  • 生成速度:1.92 tok/s
  • 系统信息:Intel64 Family 6 Model 140 Stepping 1, GenuineIntel

4.9. 文本概括

  • 问:用一句话概括以下文本:百度今日宣布文心大模型4.5系列正式开源,同时开放API服务,推出10款开源模型涵盖多种任务需求,从混合专家模型到轻量级稠密型模型不一而足。百度此次开源行动展示了在独立自研模型数量、模型类型、参数丰富度等多个维度的领先实力。其中,文心大模型4.5系列遵循Apache2.0协议开源,为开发者提供了更多自由使用的空间。引人瞩目的是,百度的MoE架构模型结构为多模态异构模型提供了全新视角。该结构不仅保留了大语言模型的性能,更显著增强了多模态理解能力,为各类任务提供更强的支持。通过飞桨深度学习框架进行高效训练、推理和部署后,文心4.5预训练模型在多项基准测试中表现优异,在文本理解、多模态推理等任务上均达到了SOTA水平。开源的文心大模型4.5系列模型权重遵循开源协议,同时搭配开源产业级开发套件,大幅降低了模型后训练和部署的门槛,为学术研究和产业应用提供了更为便利的条件。作为AI领域的领头羊,百度在算力、框架、模型和应用等方面的全栈技术优势进一步巩固了其在人工智能领域的领先地位。这一系列的开源举措不仅将促进AI模型生态系统的繁荣发展,也将为开发者和研究者们提供更多的创新空间和应用可能性。随着文心大模型4.5系列的正式开源,百度AI模型的辉煌未来已经展现在眼前。

  • 答:百度文心大模型4.5系列正式开源,其开源架构和配套产业级开发套件大幅降低模型训练与部署门槛,推动AI模型生态系统繁荣。

说实话,这个概括文本的效果让我惊叹,毕竟只有0.3B的参数,效果很出色👍!

性能指标如下:

🔵 Token统计

  • 输入Token:332
  • 输出Token:37
  • 总Token:369

🟢 时间指标(毫秒)

  • 首Token延迟:6.27
  • 总生成时间:29766.41
  • Token平均延迟:804.50

🟣 吞吐量

  • 生成速度:1.24 tok/s
  • 系统信息:Intel64 Family 6 Model 140 Stepping 1, GenuineIntel

4.10. 逻辑推理

  • 问:
如果所有鸟都会飞,而小黄是一只鸟,那么小黄能飞吗?请解释。
  • 答:
这个问题需要理解题目所给的前提条件。
前提条件是:所有鸟都会飞,而小黄是一只鸟。
根据这个前提条件,我们需要分析小黄是否能飞。

1. **逻辑分析**:
   - “所有鸟都会飞”是一个普遍性的陈述,意味着如果一个人是鸟,那么他就会飞。
   - 小黄是一只鸟,这是直接的事实陈述。
2. **飞的可能性**:
   - 由于小黄是鸟,根据“所有鸟都会飞”,小黄肯定具备飞的能力。
3. **可能的例外情况**:
   - 题目并没有提供关于小黄飞的具体信息或限制,比如是否与其他鸟不同、是否还有其他限制因素等。
   - 因此,从已知信息中,我们无法确定小黄是否真的会飞。
4. **结论**:
   - 根据前提条件,小黄一定会飞。
     因此,结论是:小黄能飞。

效果不错,大模型推理成功,得到小黄能飞的结论!

性能指标如下:

🔵 Token统计

  • 输入Token:28
  • 输出Token:202
  • 总Token:230

🟢 时间指标(毫秒)

  • 首Token延迟:1.42
  • 总生成时间:77203.87
  • Token平均延迟:382.20

🟣 吞吐量

  • 生成速度:2.62 tok/s
  • 系统信息:Intel64 Family 6 Model 140 Stepping 1, GenuineIntel

4.11. 长期记忆

在本文的第三部分,我们让大模型具备了长期记忆的功能,下面来看看效果如何吧!

在启用长期记忆前,大模型回复的内容如下图,上下文没有关联。

image-20250706200747621

启用长期记忆后,大模型回复的内容如下图,很明显大模型记住了我们之前的对话内容,但是效果一般般。

image-20250706200948847

性能指标如下:

🔵 Token统计

  • 输入Token: 44
  • 输出Token: 30
  • 总Token: 74

🟢 时间指标(毫秒)

  • 首Token延迟: 2.70
  • 总生成时间: 9019.88
  • Token平均延迟: 300.66

🟣 吞吐量

  • 生成速度: 3.33 tok/s
  • 系统信息: Intel64 Family 6 Model 140 Stepping 1, GenuineIntel

4.12. 知识库推理

下面试试大模型接入知识库后推理的效果如何。

先不启用知识库,问大模型“小明是什么?”,看看大模型怎么回复。

image-20250706202414461

从回复中可以看出,大模型根据自己的推理,告诉我们小明是一个年龄较小的孩子。

我们可以创建一个test.txt文件,在文件中填入以下内容:

小明是只猫
小黄是只鸟

然后将该文件上传到大模型的知识库中,再问大模型“小明是什么?”,看看大模型怎么回复。
image-20250707092137932

从回复中可以看出,大模型参考了知识库中的内容,告诉我们小明是一只猫。效果不错👍!

性能指标如下:

🔵 Token统计

  • 输入Token: 55
  • 输出Token: 10
  • 总Token: 65

🟢 时间指标(毫秒)

  • 首Token延迟: 1.21
  • 总生成时间: 5477.75
  • Token平均延迟: 547.78

🟣 吞吐量

  • 生成速度: 1.83 tok/s
  • 系统信息: Intel64 Family 6 Model 142 Stepping 10, GenuineIntel

4.13. 总体评价

经过博主的多次测试,大模型ERNIE-4.5-0.3B-PT 的总体效果如下表。

序号 任务类型 输出结果质量
1 文本生成 ⭐⭐⭐⭐⭐
2 数学计算 ⭐⭐⭐
3 文本翻译 ⭐⭐⭐⭐⭐
4 限制回复 ⭐⭐⭐⭐⭐
5 模糊提示 ⭐⭐⭐⭐
6 伦理安全 ⭐⭐⭐⭐⭐
7 代码生成 ⭐⭐⭐⭐
8 代码解析 ⭐⭐⭐⭐
9 文本概括 ⭐⭐⭐⭐⭐
10 逻辑推理 ⭐⭐⭐⭐
11 长期记忆 ⭐⭐⭐⭐
12 知识库推理 ⭐⭐⭐⭐

五、批量测评

博主从网上收集了200道生活常识类选择题、200道数学计算类选择题、200道逻辑推理类选择题,接下来,博主将用这600道选择题可视化评估大模型的回复效果。

5.1. 生活常识

题目如下:

序号,题目,正确答案,解析
1,我国的火警电话是多少?A.110 B.119 C.120 D.122,B,119是专门用于火灾报警的紧急电话
2,发现有人触电,第一步应该怎么做?A.用手拉开 B.切断电源 C.用湿木棍拨开 D.大声呼救,B,直接接触可能导致施救者触电,必须先断电
3,油锅起火时,错误的灭火方法是?A.盖锅盖 B.倒水 C.放青菜 D.关火,B,水会使热油飞溅,火势扩大
4,发生地震时,室内避险应选择?A.阳台 B.电梯 C.坚固桌子下 D.窗户旁,C,"伏地、遮挡、手抓牢"是标准避震姿势
5,台风天气不应?A.关紧门窗 B.外出钓鱼 C.储备食物 D.检查电路,B,户外活动易被坠物伤害
……
  1. 新建脚本ernie06.py,填入以下代码,依次读取每个问题,发给大模型,获取大模型的回复。
import pandas as pd
from transformers import AutoModelForCausalLM, AutoTokenizer
import time
from datetime import datetime

# 加载模型和tokenizer
model_name = "./models/ERNIE-4.5-0.3B-PT"
tokenizer = AutoTokenizer.from_pretrained(model_name, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(model_name, trust_remote_code=True)

# 读取数据集
df = pd.read_csv('./datasets/questions01.csv') 
questions = df.iloc[:, 1].tolist()  # 获取第二列所有问题

# 清空或创建answers01.csv文件,写入表头
with open('./datasets/answers01.csv', 'w', encoding='utf-8') as f:
    f.write("序号,大模型回复,处理时间(秒)\n")

# 初始化性能统计变量
total_questions = len(questions)
total_time = 0

# 处理每个问题
for idx, question in enumerate(questions, start=1):
    start_time = time.time()
    
    # 准备模型输入
    prompt = f"对于题目:{question}。直接回复我正确答案对应的选项,例如A"
    messages = [{"role": "user", "content": prompt}]
    text = tokenizer.apply_chat_template(
        messages,
        tokenize=False,
        add_generation_prompt=True
    )
    model_inputs = tokenizer([text], add_special_tokens=False, return_tensors="pt").to(model.device)

    # 生成回复
    generated_ids = model.generate(
        model_inputs.input_ids,
        max_new_tokens=1024
    )
    output_ids = generated_ids[0][len(model_inputs.input_ids[0]):].tolist()
    generate_text = tokenizer.decode(output_ids, skip_special_tokens=True).strip("\n")
    
    # 计算处理时间
    end_time = time.time()
    processing_time = end_time - start_time
    total_time += processing_time
    
    
    # 将结果追加写入文件
    with open('./datasets/answers01.csv', 'a', encoding='utf-8') as f:
        f.write(f"{idx},{generate_text},{processing_time:.2f}\n")
    
    print(f"已处理第 {idx} 个问题:{question[:30]}...,回复: {generate_text},耗时: {processing_time:.2f}秒")

# 计算性能指标
avg_time_per_question = total_time / total_questions
questions_per_minute = total_questions / (total_time / 60)

print("\n===== 性能指标 =====")
print(f"处理的问题总数: {total_questions}")
print(f"总处理时间: {total_time:.2f}秒")
print(f"平均每个问题处理时间: {avg_time_per_question:.2f}秒")
print(f"每分钟处理的问题数: {questions_per_minute:.2f}")


# 保存性能指标到日志文件
with open('./datasets/performance_log01.txt', 'a', encoding='utf-8') as f:
    f.write(f"\n===== 运行时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} =====\n")
    f.write(f"处理的问题总数: {total_questions}\n")
    f.write(f"总处理时间: {total_time:.2f}秒\n")
    f.write(f"平均每个问题处理时间: {avg_time_per_question:.2f}秒\n")
    f.write(f"每分钟处理的问题数: {questions_per_minute:.2f}\n")

print("\n所有问题处理完成,结果已保存到 answers01.csv")
  1. 运行该脚本,大模型的回复结果如下。
序号,大模型回复,处理时间(秒)
1,A,1.59
2,D,2.00
3,B.倒水,2.41
4,B. 电梯,2.15
5,A,0.96
……
  1. 在终端执行以下命令,安装matplotlibscikit-learnseaborn等库。
pip install matplotlib scikit-learn seaborn
  1. 新建脚本analyse.py,提取大模型回复的选项信息,并与正确答案进行对比,最后可视化准确率。
import pandas as pd
import re
import matplotlib.pyplot as plt

# 设置中文支持
plt.rcParams['font.sans-serif'] = ['SimHei']  # 使用黑体显示中文
plt.rcParams['axes.unicode_minus'] = False  # 解决负号 '-' 显示为方块的问题

# 读取问题文件和答案文件
questions_df = pd.read_csv('./datasets/questions01.csv')
answers_df = pd.read_csv('./datasets/ernie/answers01.csv')

# 获取正确答案(第3列)
correct_answers = questions_df.iloc[:, 2].tolist()

# 获取模型回答(第2列)
model_answers = answers_df['大模型回复'].tolist()
model_answers = [str(ans) if pd.notna(ans) else "" for ans in model_answers[:]]
print(correct_answers)

# 提取模型回答中的选项(A/B/C/D),忽略大小写
predicted_answers = []
for answer in model_answers:
    match = re.search(r'([A-Da-d])', answer)
    if match:
        predicted_answers.append(match.group(1).upper())
    else:
        predicted_answers.append(None)  # 无法识别答案时标记为 None
        print(f"无法识别的答案:{answer}")
print(predicted_answers)

# 计算准确率
correct_count = 0
total_count = 0
result_comparison = []

for correct, predicted in zip(correct_answers, predicted_answers):
    if predicted is not None:
        result_comparison.append((correct, predicted))
        if correct.upper() == predicted.upper():
            correct_count += 1
        total_count += 1
    else:
        result_comparison.append((correct, "无答案"))

accuracy = correct_count / total_count if total_count > 0 else 0
print(f"准确率:{accuracy * 100:.2f}%")

# 可视化准确率 - 同一画布:左边饼图,右边柱状图

# 准备数据
correct_num = correct_count
wrong_num = total_count - correct_count
labels = ['正确', '错误']
values_bar = [correct_num, wrong_num]
values_pie = [correct_num, wrong_num]
colors_pie = ['#4CAF50', '#F44336']

# 创建一个包含两个子图的画布
fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 7))

# ---- 左边:饼图 ----
ax1.pie(values_pie, labels=labels, autopct='%1.2f%%', startangle=90,
        colors=colors_pie, textprops={'color': 'black'})
ax1.set_title('模型预测准确率分布')
ax1.axis('equal')  # 确保饼图为圆形

# ---- 右边:柱状图 ----
bars = ax2.bar(labels, values_bar, color=colors_pie)
ax2.set_title('模型预测结果统计')
ax2.set_ylabel('数量')
ax2.set_ylim(0, max(values_bar) + 5)

# 在柱子上方显示具体数值
for bar in bars:
    yval = bar.get_height()
    ax2.text(bar.get_x() + bar.get_width()/2, yval + 1, str(int(yval)),
             ha='center', va='bottom')

# 添加整体标题(可选)
fig.suptitle('大模型ERNIE-4.5-0.3B-PT对于生活常识类题目的推理结果', fontsize=16)

# 自动调整布局,防止重叠
plt.tight_layout(rect=[0, 0, 1, 0.95])  # rect 防止 suptitle 被裁剪

# 保存图片并显示
plt.savefig('./datasets/ernie/model_evaluation_combined01.png', dpi=300, bbox_inches='tight')
plt.show()

from sklearn.metrics import confusion_matrix
import seaborn as sns

# 生成混淆矩阵
cm = confusion_matrix(
    [ans.upper() for ans in correct_answers if ans in ['A', 'B', 'C', 'D']],
    [ans for ans in predicted_answers if ans in ['A', 'B', 'C', 'D']],
    labels=['A', 'B', 'C', 'D']
)

# 绘制热力图
plt.figure(figsize=(8, 6))
sns.heatmap(cm, annot=True, fmt='d', cmap='Blues', 
            xticklabels=['A', 'B', 'C', 'D'], 
            yticklabels=['A', 'B', 'C', 'D'])
plt.xlabel('预测答案')
plt.ylabel('真实答案')
plt.title('大模型ERNIE-4.5-0.3B-PT对于生活常识类题目的推理结果混淆矩阵')
plt.savefig('./datasets/ernie/confusion_matrix.png', dpi=300)
plt.show()
  1. 运行测试脚本analyse.py,可视化模型推理效果。

image-20250708163921471

image-20250708164015061

从图中可以得出,模型的准确率为34.5%,也就是200道题目,大模型正确回复了69道,效果还可以,毕竟只有0.3B的参数。

性能指标如下:

处理的问题总数: 200
总处理时间: 677.65秒
平均每个问题处理时间: 3.39秒
每分钟处理的问题数: 17.71

5.2. 数学计算

与生活常识类题目的测评方法类似,测评一下大模型对于数学计算类题目的准确率如何。

题目如下:

序号,题目,正确答案,解析
1,3+5×2=? A.16 B.13 C.11 D.10,B,"先乘后加:5×2=10,3+10=13"
2,12÷(2+4)=? A.2 B.3 C.6 D.8,A,"先算括号:2+4=6,12÷6=2"
3,0.5×0.2=? A.0.1 B.0.01 C.1 D.10,A,"小数乘法:0.5×0.2=0.1"
4,3/4 + 1/2 =? A.5/4 B.1 C.3/4 D.4/6,A,"通分计算:3/4+2/4=5/4"
……

可视化结果如下:

image-20250708154911527

从统计图可以看出,该模型对于数学计算类问题的准确率为38%。

性能指标如下:

处理的问题总数: 200
总处理时间: 283.30秒
平均每个问题处理时间: 1.42秒
每分钟处理的问题数: 42.36

5.3. 逻辑推理

继续测评大模型对于逻辑推理类题目的准确率如何。

题目如下:

序号,题目,正确答案,解析
1,"如果所有猫都会爬树,Tom是猫,那么:A.Tom会爬树 B.Tom不会爬树 C.Tom可能是狗 D.不确定",A,"全称命题可直接推出个体结论"
2,"1,3,5,7,? 下一个数字是:A.8 B.9 C.10 D.11",B,"奇数序列规律"
3,"如果A>B且B>C,那么:A.A>C B.A<C C.A=C D.不确定",A,"传递性关系"
4,"所有鸟都会飞,企鹅是鸟,那么:A.企鹅会飞 B.企鹅不会飞 C.企鹅不是鸟 D.不确定",B,"企鹅是例外情况"
5,"张三是李四的儿子,李四是王五的儿子,张三是王五的:A.孙子 B.儿子 C.兄弟 D.父亲",A,"三代直系血亲关系"
……

可视化结果如下:

image-20250708154937729

从图中可以得出模型的准确率在36.5%左右,还可以。

性能指标如下:

处理的问题总数: 200
总处理时间: 272.08秒
平均每个问题处理时间: 1.36秒
每分钟处理的问题数: 44.10

5.4. 总体评价

总的来说,ERNIE-4.5-0.3B-PT大模型对于生活常识、数学计算和逻辑推理类题目的准确率在30%~40%之间。

序号 问题分类 准确率 平均每个问题处理时间
1 生活常识 34.5% 3.39秒
2 数学计算 38% 1.42秒
3 逻辑推理 36.5% 1.36秒

六、对比测评

为了深度测评大模型ERNIE-4.5-0.3B-PT的回复效果,博主在本地私有化部署了大模型Qwen3-0.6B。正所谓“没有对比就没有伤害”,接下来,一起来看看这两个模型的差异吧。

首先,我们可以将第五部分使用的600道选择题喂给大模型Qwen3-0.6B,看看大模型的准确率和回复性能如何。

博主新建了一个脚本qwen.py,用于将600道选择题喂给大模型Qwen3-0.6B

import pandas as pd
from modelscope import AutoModelForCausalLM, AutoTokenizer
import time
from datetime import datetime

# 加载模型和tokenizer
model_name = "./models/Qwen3-0.6B"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    torch_dtype="auto",
    device_map="auto"
)

# 读取数据集
df = pd.read_csv('./datasets/questions01.csv') 
questions = df.iloc[:, 1].tolist()  # 获取第二列所有问题

# 清空或创建answers文件,写入表头
with open('./datasets/qwen/answers01.csv', 'w', encoding='utf-8') as f:
    f.write("序号,大模型回复,处理时间(秒)\n")

# 初始化性能统计变量
total_questions = len(questions)
total_time = 0

# 处理每个问题
for idx, question in enumerate(questions, start=1):
    start_time = time.time()
    
    # 准备模型输入
    prompt = f"对于题目:{question}。直接回复我正确答案对应的选项,例如A"
    messages = [{"role": "user", "content": prompt}]
    text = tokenizer.apply_chat_template(
        messages,
        tokenize=False,
        add_generation_prompt=True,
        enable_thinking=False  # 关闭thinking模式
    )
    model_inputs = tokenizer([text], return_tensors="pt").to(model.device)

    # 生成回复
    generated_ids = model.generate(
        **model_inputs,
        max_new_tokens=1024
    )
    output_ids = generated_ids[0][len(model_inputs.input_ids[0]):].tolist()
    
    # 解析thinking内容(如果有)
    try:
        # rindex finding 151668 (</think>)
        index = len(output_ids) - output_ids[::-1].index(151668)
    except ValueError:
        index = 0
    generate_text = tokenizer.decode(output_ids[index:], skip_special_tokens=True).strip("\n")
    
    # 计算处理时间
    end_time = time.time()
    processing_time = end_time - start_time
    total_time += processing_time
    
    # 将结果追加写入文件
    with open('./datasets/qwen/answers01.csv', 'a', encoding='utf-8') as f:
        f.write(f"{idx},{generate_text},{processing_time:.2f}\n")
    
    print(f"已处理第 {idx} 个问题:{question[:30]}...,回复: {generate_text},耗时: {processing_time:.2f}秒")

# 计算性能指标
avg_time_per_question = total_time / total_questions
questions_per_minute = total_questions / (total_time / 60)

print("\n===== 性能指标 =====")
print(f"处理的问题总数: {total_questions}")
print(f"总处理时间: {total_time:.2f}秒")
print(f"平均每个问题处理时间: {avg_time_per_question:.2f}秒")
print(f"每分钟处理的问题数: {questions_per_minute:.2f}")

# 保存性能指标到日志文件
with open('./datasets/qwen/performance_log01.txt', 'a', encoding='utf-8') as f:
    f.write(f"\n===== 运行时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')} =====\n")
    f.write(f"处理的问题总数: {total_questions}\n")
    f.write(f"总处理时间: {total_time:.2f}秒\n")
    f.write(f"平均每个问题处理时间: {avg_time_per_question:.2f}秒\n")
    f.write(f"每分钟处理的问题数: {questions_per_minute:.2f}\n")

print("\n所有问题处理完成,结果已保存到 answers01.csv")

6.1. 功能对比

对于生活常识类题目,大模型ERNIE-4.5-0.3B-PTQwen3-0.6B的推理结果对比如下。

image-20250708164352879

从图中可以看出,对于生活常识类题目,大模型ERNIE-4.5-0.3B-PT的推理效果接近Qwen3-0.6BERNIE4.5只用了0.3B的参数,就可以与Qwen3的0.6B参数推理效果差不多👍!

对于数学计算类题目,两个大模型的推理结果对比如下。

image-20250708162645701

从图中可以看出,对于数学计算类题目,大模型ERNIE-4.5-0.3B-PT的推理准确率超过Qwen3-0.6B,并且超出了10%👍!

对于逻辑推理类题目,两个大模型的推理结果对比如下。

image-20250708161923322

从图中可以看出,对于逻辑推理类题目,大模型ERNIE-4.5-0.3B-PTQwen3-0.6B的推理效果不相上下!

6.2. 性能对比

对于生活常识类题目,两个大模型的性能差异如下。

  • ERNIE-4.5-0.3B-PT
处理的问题总数: 200
总处理时间: 677.65秒
平均每个问题处理时间: 3.39秒
每分钟处理的问题数: 17.71
  • Qwen3-0.6B
处理的问题总数: 200
总处理时间: 1047.16秒
平均每个问题处理时间: 5.24秒
每分钟处理的问题数: 11.46

对于数学计算类题目,两个大模型的性能差异如下。

  • ERNIE-4.5-0.3B-PT
处理的问题总数: 200
总处理时间: 283.30秒
平均每个问题处理时间: 1.42秒
每分钟处理的问题数: 42.36
  • Qwen3-0.6B
处理的问题总数: 200
总处理时间: 1437.49秒
平均每个问题处理时间: 7.19秒
每分钟处理的问题数: 8.35

对于逻辑推理类题目,两个大模型的性能差异如下。

  • ERNIE-4.5-0.3B-PT
处理的问题总数: 200
总处理时间: 272.08秒
平均每个问题处理时间: 1.36秒
每分钟处理的问题数: 44.10
  • Qwen3-0.6B
处理的问题总数: 200
总处理时间: 1566.42秒
平均每个问题处理时间: 7.83秒
每分钟处理的问题数: 7.66

从上面的性能差异中不难得出,大模型ERNIE-4.5-0.3B-PT的回复性能明显高于Qwen3-0.6B👍。

6.3. 总体评价

总的来说,大模型ERNIE-4.5-0.3B-PT仅用0.3B的参数,却可以达到甚至超越Qwen3-0.6B的0.6B参数的推理效果。

而且在性能方面,大模型ERNIE-4.5-0.3B-PT的推理速度明显快于Qwen3-0.6B。特别是对于数学计算、逻辑推理等复杂问题,其推理速度比Qwen3-0.6B快了近七倍。

序号 问题分类 ERNIE-4.5-0.3B-PT准确率 Qwen3-0.6B准确率
1 生活常识 34.5% 38%
2 数学计算 38% 27.5%
3 逻辑推理 36.5% 37%
序号 问题分类 ERNIE-4.5-0.3B-PT平均每个问题处理时间 Qwen3-0.6B平均每个问题处理时间
1 生活常识 3.39秒 5.24秒
2 数学计算 1.42秒 7.19秒
3 逻辑推理 1.36秒 7.83秒

七、大模型应用案例

博主之前开发过一个智能笔记系统,鉴于对大模型ERNIE-4.5-0.3B-PT测评后的结论,博主决定利用这个模型的优势:文本生成+文本概括+限制回复,将其接入智能笔记系统的【AI优化笔记标题、概括笔记内容】模块。

image-20250704231944301

如下图,博主将ERNIE-4.5-0.3B-PT接入了智能笔记系统,以下是我给大模型定义的提示词。

image-20250705081721969

以下是接入后的效果,可以看出,效果不错👍,后面博主将尝试更大参数的ERNIE-4.5

image-20250704232510908


八、总结

本次测评就到这里啦,由于博主能力有限,文中难免有疏漏之处,欢迎小伙伴们私信博主。


网站公告

今日签到

点亮在社区的每一天
去签到