很多小伙伴在用vscode的时候发现无法给huggingface下载的LLM断点调试,主要原因是,huggingface的transformers包,在加载模型的时候,会copy一份模型到系统cache路径,所以你在自己下载的模型路径那里打断点是没用的,因为这个地方代码就没执行,执行的是它的克隆兄弟
。
当然,如果你直接是在线下载运行的模型,这个也没办法在vscode里面调试,如果是在PyCharm环境下开发,PyCharm可以自动找到这个位置,然后打断点后也可以调试,但是vscode不支持,只能在当前项目下面的代码进行调试。
那么如何解决呢?只需要将默认的AutoModel和AutoTokenizer换成下载好的LLM里面的Model和Tokenzier就行了,这样可以用相对路径的方式导入包,自然就可以打断点,Debug了。
下面是两个简单的示范:
示范1:ChatGLM2-6B
修改前
from transformers import AutoTokenizer, AutoModel
tokenizer = AutoTokenizer.from_pretrained("THUDM/chatglm2-6b", trust_remote_code=True)
model = AutoModel.from_pretrained("THUDM/chatglm2-6b", trust_remote_code=True).cuda()
修改后
- 先从huggingface下载chatglm2-6b的代码。
git lfs install
git clone https://huggingface.co/THUDM/chatglm2-6b
- 将chatglm2-6b重命名一下,换成下划线的方式,这样才能在Python里面导入相对路径。
mv chatglm2-6b chatglm2_6b
- 从相对路径导入Model / Tokenizer
import os
from chatglm2_6b.modeling_chatglm import ChatGLMForConditionalGeneration
from chatglm2_6b.tokenization_chatglm import ChatGLMTokenizer
# 下面这个是模型路径 设定是当前路径下面的chatglm2_6b文件夹
project_dir = os.path.dirname(os.path.abspath(__file__))
model_dir = os.path.join(project_dir, "chatglm2_6b")
tokenizer = ChatGLMTokenizer.from_pretrained(model_dir)
model = ChatGLMForConditionalGeneration.from_pretrained(model_dir)
示范2:Qwen-7B-Chat
修改前
from transformers import AutoModelForCausalLM, AutoTokenizer
model = AutoModelForCausalLM.from_pretrained("Qwen/Qwen-7B", trust_remote_code=True)
tokenizer = AutoTokenizer.from_pretrained("Qwen/Qwen-7B", trust_remote_code=True)
修改后
- 下载模型
git lfs install
git clone https://huggingface.co/Qwen/Qwen-7B-Chat
- 重命名,改小写+下划线,方便相对路径导入。
mv Qwen-7B-Chat qwen_7b_chat
- 从相对路径导入Model / Tokenizer
import os
from qwen_7b_chat.modeling_qwen import QWenLMHeadModel
from qwen_7b_chat.tokenization_qwen import QWenTokenizer
# 下面这个是模型路径 设定是当前路径下面的qwen_7b_chat文件夹
project_dir = os.path.dirname(os.path.abspath(__file__))
model_dir = os.path.join(project_dir, "qwen_7b_chat")
tokenizer = QWenTokenizer.from_pretrained(model_dir)
model = QWenLMHeadModel.from_pretrained(model_dir)
提问环节
问题:
为啥你就知道是从这个路径导入这个包呢?ChatGLM2-6b的from chatglm2_6b.modeling_chatglm import ChatGLMForConditionalGeneration
和Qwen-7B-Chat的from qwen_7b_chat.modeling_qwen import QWenLMHeadModel
你是怎么确定的?
回答:
- 办法1:可以阅读代码,一般来说,最下面的一个类,并且有
generate
,甚至有的有chat
和stream_chat
的函数的大概率就是目标类了。 - 方法2:可以看hugingface下面下来的config.json这个文件,里面有一个关键配置auto_map已经写的很清楚了。
- ChatGLM2-6b的config.json
"auto_map": {
"AutoConfig": "configuration_chatglm.ChatGLMConfig",
"AutoModel": "modeling_chatglm.ChatGLMForConditionalGeneration",
"AutoModelForCausalLM": "modeling_chatglm.ChatGLMForConditionalGeneration",
"AutoModelForSeq2SeqLM": "modeling_chatglm.ChatGLMForConditionalGeneration"
},
- Qwen的config.json
"auto_map": {
"AutoConfig": "configuration_qwen.QWenConfig",
"AutoModelForCausalLM": "modeling_qwen.QWenLMHeadModel"
},
对于大语言模型,我们只需要找到AutoModelForCausalLM
对应的类就行了。所以这里也能解释我上面为啥用那两个类来导入。
问题2:chat模型如何确定输入输出?
回答:
- 在chat模型中,给模型输入的和我们输入的其实是不一样的,给模型输入的东西会经过一定加工处理才到模型那里去。
- 所以在打断点的时候,找tokenizer分词,对应的text,那个才是真正的输入。
- 以chatglm为例,你输入的信息是经过这么一个函数加工,再分词给Model的。
def build_inputs(device, tokenizer, query: str,
history: List[Tuple[str, str]] = None):
prompt = ""
for i, (old_query, response) in enumerate(history):
prompt += "[Round {}]\n\n问:{}\n\n答:{}\n\n".format(i + 1, old_query,
response)
prompt += "[Round {}]\n\n问:{}\n\n答:".format(len(history) + 1, query)
inputs = tokenizer([prompt], return_tensors="pt")
inputs = inputs.to(device)
return inputs
- qwen-7b-chat也大差不差,也有类似的函数。
def make_context(
tokenizer: PreTrainedTokenizer,
query: str,
history: List[Tuple[str, str]] = None,
system: str = "",
max_window_size: int = 6144,
chat_format: str = "chatml",
):
if history is None:
history = []
if chat_format == "chatml":
im_start, im_end = "<|im_start|>", "<|im_end|>"
im_start_tokens = [tokenizer.im_start_id]
im_end_tokens = [tokenizer.im_end_id]
nl_tokens = tokenizer.encode("\n")
def _tokenize_str(role, content):
return f"{role}\n{content}", tokenizer.encode(
role, allowed_special=set()
) + nl_tokens + tokenizer.encode(content, allowed_special=set())
system_text, system_tokens_part = _tokenize_str("system", system)
system_tokens = im_start_tokens + system_tokens_part + im_end_tokens
raw_text = ""
context_tokens = []
for turn_query, turn_response in reversed(history):
query_text, query_tokens_part = _tokenize_str("user", turn_query)
query_tokens = im_start_tokens + query_tokens_part + im_end_tokens
response_text, response_tokens_part = _tokenize_str(
"assistant", turn_response
)
response_tokens = im_start_tokens + response_tokens_part + im_end_tokens
next_context_tokens = nl_tokens + query_tokens + nl_tokens + response_tokens
prev_chat = (
f"\n{im_start}{query_text}{im_end}\n{im_start}{response_text}{im_end}"
)
current_context_size = (
len(system_tokens) + len(next_context_tokens) + len(context_tokens)
)
if current_context_size < max_window_size:
context_tokens = next_context_tokens + context_tokens
raw_text = prev_chat + raw_text
else:
break
context_tokens = system_tokens + context_tokens
raw_text = f"{im_start}{system_text}{im_end}" + raw_text
context_tokens += (
nl_tokens
+ im_start_tokens
+ _tokenize_str("user", query)[1]
+ im_end_tokens
+ nl_tokens
+ im_start_tokens
+ tokenizer.encode("assistant")
+ nl_tokens
)
raw_text += f"\n{im_start}user\n{query}{im_end}\n{im_start}assistant\n"
elif chat_format == "raw":
raw_text = query
context_tokens = tokenizer.encode(raw_text)
else:
raise NotImplementedError(f"Unknown chat format {chat_format!r}")
return raw_text, context_tokens
- 从上面两个示例可以看出,真正给模型的input和我们输入的input,还是有一层比较复杂的加工的,所以当我们对比huggingface 与 trt-llm的时候,需要将输入构造一下再传给模型才行,否则可能得到不正确的结果。