Qwen3-AWQ模型离线推理

模型信息:

模型地址-huggingface:https://huggingface.co/Qwen/Qwen3-8B-AWQ

模型地址-modelscope:https://www.modelscope.cn/models/Qwen/Qwen3-8B-AWQ

Qwen3-8B 具有以下特性:

  • 类型:因果语言模型

  • 训练阶段:预训练和后训练

  • 参数数量:82 亿

  • 非嵌入参数数量:69.5 亿

  • 层数:36

  • 注意力头数(GQA):Q 为 32,KV 为 8

  • 上下文长度:原生 32,768 和 使用 YaRN 时 131,072 个 token

  • 量化:AWQ 4 位

量化前模型大小16G+,量化后模型大小6G

离线推理

使用官方给的代码,模型加载正常,但是推理时generate会报错如下图,参照某篇博客的内容对模型加载和推理模块都进行了调整(现找不到那篇博客了,在此表示感谢),可以正常运行。

  • 模型加载
# qwen_vllm.py
# 模型加载
from transformers import AutoTokenizer
from vllm import LLM, SamplingParams

DEFAULT_CKPT_PATH = "/home/emma/.cache/modelscope/hub/models/Qwen/Qwen3-8B-AWQ"
# Initialize the tokenizer
tokenizer = AutoTokenizer.from_pretrained(DEFAULT_CKPT_PATH)    # 替换成本地路径


# Input the model name or path. Can be GPTQ or AWQ models.
llm = LLM(
    model=DEFAULT_CKPT_PATH,
    max_model_len=2048,   # 最大上下文长度:8192
    max_num_seqs=1,      # 最大并发序列数:256
    quantization="awq",
    enforce_eager=True,
    dtype="auto",
    # block_size=16
    gpu_memory_utilization=0.6,  # 默认0.9
    # max_num_seqs=64
)   # 替换成本地路径
  • 模型推理
# 模型推理
from vllm import LLM, SamplingParams


# Pass the default decoding hyperparameters of Qwen2-7B-Instruct
# max_tokens is for the maximum length for generation.
sampling_params = SamplingParams(temperature=0.7, top_p=0.8, repetition_penalty=1.05, max_tokens=512)

# # =================== 单个推理 ====================
# # Prepare your prompts
# prompt = "---\n请判断上面这段文本属于哪些标签,[现场音乐会,滑雪,爬山,美食之旅,水族馆游览,随上随下巴士,蹦极,博物馆体验,观赏熊猫,艺术课程,摩托艇,骑骆驼],并输出命中的标签列表,如果没有命中,则输出'无',不要输出解释信息"
# text = "使用地址:广州青深民谣现场珠影店 广东省广州市海珠区赤岗街道广州大道南173号温馨提示:演出为有座演出;最下方有道理指引图;学生票需出示学生证;VIP票数量有限,先抢先得。"
# messages = [
#     {"role": "system", "content": "You are a helpful assistant."},
#     {"role": "user", "content": text + prompt}
# ]
# text = tokenizer.apply_chat_template(
#     messages,
#     tokenize=False,
#     add_generation_prompt=True
# )
#
# # generate outputs
# outputs = llm.generate([text], sampling_params)

# # =================== 单个推理 ====================

# # # =================== batch推理 ====================
# Prepare your prompts
texts = ["使用地址:广州青深民谣现场珠影店 广东省广州市海珠区赤岗街道广州大道南173号温馨提示:演出为有座演出;最下方有道理指引图;学生票需出示学生证;VIP票数量有限,先抢先得。", "专业的初级滑雪练习场,适合初学者及儿童,激情雪圈,单人滑,双人滑,三人滑,不限次数!雪圈运送机助你轻松戏雪! 不含自费项目:滑雪押金200元,滑雪服(衣服+库组)30元/套,滑雪专用手套10元/副,更衣箱10元/个,头盔30元/个,雪镜30元/个,护臂30元/个", "骆驼牧场参观,免费骑骆驼", "🔺【客人自行进入游玩,向导和车辆在外等候】🐼熊猫谷不仅是大熊猫回归自然的桥梁,也是圈养大熊猫走向野外进行自我繁衍的可行性研究基地。目前,熊猫谷拥有完善的大熊猫适应性生态兽舍、半野化过渡训练区和小熊猫生态放养区,常驻有10余只大熊猫和20余只小熊猫"]
texts = ["使用地址:广州青深民谣现场珠影店 广东省广州市海珠区赤岗街道广州大道南173号温馨提示:演出为有座演出;最下方有道理指引图;学生票需出示学生证;VIP票数量有限,先抢先得。"]
prompt = "---\n请判断上面这段文本属于哪些标签,[现场音乐会,滑雪,爬山,美食之旅,水族馆游览,随上随下巴士,蹦极,博物馆体验,观赏熊猫,艺术课程,摩托艇,骑骆驼],并输出命中的标签列表,如果没有命中,则输出'无',不要输出解释信息"
messages = []
for text in texts:
    message = [
        {"role": "system", "content": "你是一个文本分类模型"},
        {"role": "user", "content": text + prompt + "/think"}  # "/no_think"  
        # 通过用户输入在思考模式和非思考模式之间切换,提供了一种软开关机制,允许用户在 enable_thinking=True 时动态控制模型的行为。具体来说,您可以在用户提示或系统消息中添加 /think 和 /no_think 来逐回合切换模型的思考模式。模型将在多轮对话中遵循最近的指令。
    ]
    messages.append(message)

text = tokenizer.apply_chat_template(
    messages,
    tokenize=False,
    add_generation_prompt=True,
    enable_thinking=True  # True is the default value for enable_thinking
)

# generate outputs
outputs = llm.generate(text, sampling_params)

# # # =================== batch推理 ====================

# Print the outputs.
for output in outputs:
    prompt = output.prompt
    # generated_text = output.outputs[0].text
    output_ids = output.outputs[0].token_ids
    # parsing thinking content
    try:
        # rindex finding 151668 (</think>)
        index = len(output_ids) - output_ids[::-1].index(151668)
    except ValueError:
        index = 0

    thinking_content = tokenizer.decode(output_ids[:index], skip_special_tokens=True).strip("\n")
    content = tokenizer.decode(output_ids[index:], skip_special_tokens=True).strip("\n")

    print(f"prompt:{prompt!r}")
    print(f"thinking content: {thinking_content!r}")
    print("content:", content)

    # print(f"Prompt: {prompt!r}, Generated text: {generated_text!r}")
  • GPU使用情况分析:

gpu_memory_utilization参数说明:

vllm会预先分配显存,默认值是0.9,与batch size无关。

gpu_memory_utilization设置越大,可占用显存越大,就有更多显存可用于 KV 缓存,推理速度也会越快。在显存足够的情况下,gpu_memory_utilization可以设置为0.95。

gpu_memory_utilization=0.6时的GPU占用情况:

gpu_memory_utilization=0.8时的GPU占用情况:

模型文件本身6G左右,从上图可以看出Model loading用了5.7G,比模型本身更小,应该是因为VLLM采用了显存技术优化,大幅减少了冗余占用。

  • 模型输出

非思考模式输出(单条70ms左右)

思考模式输出(单条4s左右)

NOTE:

为了 API 兼容性,当 enable_thinking=True 时,无论用户是否使 /think 或 /no_think,模型总是会输出一个被 <think>...</think> 包裹的块。但是,如果禁用了思考,这个块中的内容可能是空的。 当 enable_thinking=False 时,软开关无效。无论用户输入了任何 /think 或 /no_think 标签,模型都不会生成思考内容,也不会包含 <think>...</think> 块。

高级用法:通过用户输入在思考模式和非思考模式之间切换

我们提供了一种软开关机制,允许用户在 enable_thinking=True 时动态控制模型的行为。具体来说,您可以在用户提示或系统消息中添加 /think 和 /no_think 来逐回合切换模型的思考模式。模型将在多轮对话中遵循最近的指令。

以下是一个多轮对话的例子:

从后面的输出结果可以看出,/think 和 /no_think 起到了作用,在思考模式和非思考模式之间进行了切换。

from modelscope import AutoModelForCausalLM, AutoTokenizer
from vllm import LLM, SamplingParams

class QwenChatbot:
    def __init__(self, model_name="/home/emma/.cache/modelscope/hub/models/Qwen/Qwen3-8B-AWQ"):
        self.tokenizer = AutoTokenizer.from_pretrained(model_name)
        # self.model = AutoModelForCausalLM.from_pretrained(model_name)
        # Input the model name or path. Can be GPTQ or AWQ models.
        self.model = LLM(
            model=model_name,
            max_model_len=2048,   # 最大上下文长度:8192
            max_num_seqs=1,      # 最大并发序列数:256
            quantization="awq",
            enforce_eager=True,
            dtype="auto",
            # block_size=16
            gpu_memory_utilization=0.7,  # 默认0.9
            # max_num_seqs=64
        )   # 替换成本地路径
        self.history = []

    def generate_response(self, user_input):

        sampling_params = SamplingParams(temperature=0.7, top_p=0.8, repetition_penalty=1.05, max_tokens=2048)
        messages = self.history + [{"role": "user", "content": user_input}]

        text = self.tokenizer.apply_chat_template(
            messages,
            tokenize=False,
            add_generation_prompt=True
        )

        # inputs = self.tokenizer(text, return_tensors="pt")
        # response_ids = self.model.generate(**inputs, max_new_tokens=4096)[0][len(inputs.input_ids[0]):].tolist()  # 32768
        # response = self.tokenizer.decode(response_ids, skip_special_tokens=True)

        # generate outputs
        output = self.model.generate([text], sampling_params)[0]
        output_ids = output.outputs[0].token_ids
        # parsing thinking content
        # try:
        #     # rindex finding 151668 (</think>)
        #     index = len(output_ids) - output_ids[::-1].index(151668)
        # except ValueError:
        #     index = 0

        # response = self.tokenizer.decode(output_ids[index:], skip_special_tokens=True).strip("\n")
        response = self.tokenizer.decode(output_ids, skip_special_tokens=True).strip("\n")

        # Update history
        self.history.append({"role": "user", "content": user_input})
        self.history.append({"role": "assistant", "content": response})

        return response

# Example Usage
if __name__ == "__main__":
    chatbot = QwenChatbot()

    # First input (without /think or /no_think tags, thinking mode is enabled by default)
    user_input_1 = "How many r's in strawberries?"
    print(f"User: {user_input_1}")
    response_1 = chatbot.generate_response(user_input_1)
    print(f"Bot: {response_1}")
    print("----------------------")

    # Second input with /no_think
    user_input_2 = "Then, how many r's in blueberries? /no_think"
    print(f"User: {user_input_2}")
    response_2 = chatbot.generate_response(user_input_2)
    print(f"Bot: {response_2}")
    print("----------------------")

    # Third input with /think
    user_input_3 = "Really? /think"
    print(f"User: {user_input_3}")
    response_3 = chatbot.generate_response(user_input_3)
    print(f"Bot: {response_3}")

输出结果:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值