Huggingface pipelineの中身を見てみた

Huggingface公式によるPipeineクラスの使い方の一例をもとに、内部の処理を追いかけてみた。
https://huggingface.co/blog/llama3#using-%F0%9F%A4%97-transformers

```python
import transformers
import torch

model_id = "meta-llama/Meta-Llama-3-8B-Instruct"

pipeline = transformers.pipeline(
    "text-generation",
    model=model_id,
    model_kwargs={"torch_dtype": torch.bfloat16},
    device="cuda",
)

messages = [
    {"role": "system", "content": "You are a pirate chatbot who always responds in pirate speak!"},
    {"role": "user", "content": "Who are you?"},
]

prompt = pipeline.tokenizer.apply_chat_template(
        messages, 
        tokenize=False, 
        add_generation_prompt=True
)

terminators = [
    pipeline.tokenizer.eos_token_id,
    pipeline.tokenizer.convert_tokens_to_ids("
")
]

outputs = pipeline(
    prompt,
    max_new_tokens=256,
    eos_token_id=terminators,
    do_sample=True,
    temperature=0.6,
    top_p=0.9,
)
print(outputs[0]["generated_text"][len(prompt):])
```

# Pipelineクラスのインスタンス化
```python
pipeline = transformers.pipeline(
    "text-generation",
    model=model_id,
    model_kwargs={"torch_dtype": torch.bfloat16},
    device="cuda",
)
```
[Pipelineクラス](https://github.com/huggingface/transformers/blob/78fdd64dcf79dba5f92b88bb297a801028b35515/src/transformers/pipelines/base.py#L786)を親クラスとした派生クラスが、タスクごとにたくさん用意されている。
https://huggingface.co/docs/transformers/ja/main_classes/pipelines#transformers.pipeline.task
指定したタスク（上の例では"text-generation"）に対応する派生クラスが使用される。
tokenizerを指定していない場合、モデル（strの場合)のデフォルトトークナイザが設定される([ref](https://huggingface.co/docs/transformers/ja/main_classes/pipelines#transformers.pipeline.tokenizer))。

# Promptの作成
```python
messages = [
    {"role": "system", "content": "You are a pirate chatbot who always responds in pirate speak!"},
    {"role": "user", "content": "Who are you?"},
]

prompt = pipeline.tokenizer.apply_chat_template(
        messages, 
        tokenize=False, 
        add_generation_prompt=True
)
```
tokenizerクラス（）が持つ[`apply_chat_template`](https://github.com/huggingface/transformers/blob/78fdd64dcf79dba5f92b88bb297a801028b35515/src/transformers/tokenization_utils_base.py#L1684)メソッドでプロンプトを整える。
>[`apply_chat_template`](https://github.com/huggingface/transformers/blob/78fdd64dcf79dba5f92b88bb297a801028b35515/src/transformers/tokenization_utils_base.py#L1684)： Converts a list of dictionaries with `"role"` and `"content"` keys to a list of token ids. This method is intended for use with chat models, and will read the tokenizer's chat_template attribute to determine the format and control tokens to use when converting. When chat_template is None, it will fall back to the default_chat_template specified at the class level.

すなわち、上例の`messages`のような形式の入力を、対応するトークナイザのchat_templateに従ってプロンプトに整える。今回使うLlama-3-8B-Instructの場合、[tokenizer_config.json](https://huggingface.co/meta-llama/Meta-Llama-3-8B-Instruct/blob/main/tokenizer_config.json#L2053)で定義されているchat_templateが適用される。

> add_generation_prompt (bool, *optional*): Whether to end the prompt with the token(s) that indicate the start of an assistant message. This is useful when you want to generate a response from the model. Note that this argument will be passed to the chat template, and so it must be supported in the template for this argument to have any effect.

`add_generation_prompt=True`にすることで、`
assistant
\n\n`までがプロンプトテンプレートに含まれる。

# Pipelineの呼び出し
```python
outputs = pipeline(
    prompt,
    max_new_tokens=256,
    eos_token_id=terminators,
    do_sample=True,
    temperature=0.6,
    top_p=0.9,
)
print(outputs[0]["generated_text"][len(prompt):])
```
今回の入力はstrなので、親クラスの__call__が呼ばれる（関係ないけどchat chat chatの🐈絵文字コメントアウトかわいい）。
![スクリーンショット 2024-05-01 15.27.11.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641587/b981a184-c773-ca79-9d28-9078a22de42e.png)

親クラスの__call__では、`inputs`の型によって処理を分けている。strなので一番下の`self.run_single`が呼ばれる。
![スクリーンショット 2024-05-01 15.21.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641587/db82364c-3687-f8f2-5612-c0ebe0256aa5.png)

`run_single`は、inputsに`self.preprocess`,`self.forward`,`self.postprocess`の順で処理をかける。
![スクリーンショット 2024-05-01 15.22.15.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641587/df36b8ee-305f-6e9e-b44e-f0ade36ba580.png)

`preprocess`では、プロンプトの形式により処理が分けられる。今回はstrなので、tokenizerがそのまま適用される。
![スクリーンショット 2024-05-01 15.39.18.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641587/f47b6b22-1966-8a47-f228-f439c3cd9659.png)

`forward`内では実質`_forward`が呼ばれる。
![スクリーンショット 2024-05-01 15.44.58.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641587/cf571d9b-c5f6-95bf-d094-3571375e92b3.png)

`_forward`内で実際に`model.generate`が実行される。
![スクリーンショット 2024-05-01 15.45.47.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641587/fecb7307-45f4-ea5e-0844-33c5188acdcc.png)

`postprocess`では、出力トークンのdecodeが行われる。
![スクリーンショット 2024-05-01 15.52.38.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/641587/d5486813-6352-438d-bac0-9b3bdc5637e8.png)