logo 🤗

技术视野

聚焦科技前沿,分享技术解析,洞见未来趋势。在这里,与您一起探索人工智能的无限可能,共赴技术盛宴。

很多小伙伴在用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()

修改后
  1. 先从huggingface下载chatglm2-6b的代码。
git lfs install
git clone https://huggingface.co/THUDM/chatglm2-6b
  1. 将chatglm2-6b重命名一下,换成下划线的方式,这样才能在Python里面导入相对路径。
mv chatglm2-6b chatglm2_6b
  1. 从相对路径导入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)

修改后
  1. 下载模型
git lfs install
git clone https://huggingface.co/Qwen/Qwen-7B-Chat
  1. 重命名,改小写+下划线,方便相对路径导入。
mv Qwen-7B-Chat qwen_7b_chat
  1. 从相对路径导入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. 办法1:可以阅读代码,一般来说,最下面的一个类,并且有generate,甚至有的有chatstream_chat的函数的大概率就是目标类了。
  2. 方法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的时候,需要将输入构造一下再传给模型才行,否则可能得到不正确的结果。

版权属于:tlntin
作品采用:本作品采用 知识共享署名-相同方式共享 4.0 国际许可协议 进行许可。
更新于: 2023年09月01日 10:33


39 文章数
5 分类数
40 页面数
已在风雨中度过 1年160天14小时20分
目录
来自 《VSCode调试huggingface LLM》
暗黑模式
暗黑模式
返回顶部
暗黑模式
暗黑模式
返回顶部