从 llava-v1_5
文件夹开始看起.
文件夹结构: tree -L N
, N
是指定的深度,
这里用了 N=2
.
1 | ├── checkpoints |
重点是 llava/model/
下面的内容.
1 | ├── constants.py |
1 | ├── apply_delta.py |
挑选重要的文件逐个分析.
llava_arch.py
这个文件主要定义了类 LlavaMetaModel
和类
LlavaMetaForCausalLM
.
1 | class LlavaMetaModel: |
什么是装饰器?
首先函数是个对象, 具有 __name__
属性表示函数的名称,
装饰器是一个返回函数的高阶函数,
装饰器 @abstractmethod
是标准模块 abc
提供的抽象方法, 如果要使用 @abstractmethod
抽象方法,
要求:
abc.ABC
@abstractmethod
完成上述两步之后, 这个类就成为了抽象类, 不能直接被实例化, 要想使用抽象类, 必须继承该类并实现该类的所有的抽象方法, 这里的抽象方法就指的是
1 | class LlavaMetaForCausalLM(ABC): |
llava_llama.py
1 | from ..llava_arch import LlavaMetaModel, LlavaMetaForCausalLM |
上面表示 llava_arch.py
中定义了两个类
LlavaMetaModel
和 LlavaMetaForCausalLM
,
这里导入了这两个类.
1 | class LlavaLlamaModel(LlavaMetaModel, LlamaModel): |
上面定义类 LlavaLlamaModel
继承两个类
LlavaMetaModel
和 LlamaModel
,
LlamaModel
是 hugging face transformer
库中的.
1 | # transformer 库中的类 LlamaForCausalLM |
1 | class LlavaLlamaForCausalLM(LlamaForCausalLM, LlavaMetaForCausalLM): |
上面定义类 LlavaLlamaForCausalLM
, 继承自
LlamaForCausalLM
和 LlavaMetaForCausalLM
.
pope_eval.py
这是推理的代码, 现在要可视化 attention 矩阵以确定 summary token 的移动情况.
先分析一下文件内容.
1 | rsync /home/cuiruochen/HA-DPO/ha_dpo/data/POPE.tar wuzongqian@shi.kongfei.life:/home/wuzongqian/xubaoduo/Deep-Learning-for-MLLMs/ha_dpo/data |
1 | rsync /home/cuiruochen/HA-DPO/ha_dpo/data.tar wuzongqian@shi.kongfei.life:/home/wuzongqian/xubaoduo/Deep-Learning-for-MLLMs/ha_dpo |
1 | rsync /home/cuiruochen/HA-DPO/ha_dpo/data/coco2024.tar wuzongqian@shi.kongfei.life:/home/wuzongqian/xubaoduo/Deep-Learning-for-MLLMs/ha_dpo/data |
模型
1 | LlavaLlamaForCausalLM( |
1 | ['T_destination', '__abstractmethods__', '__annotations__', '__call__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattr__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__slots__', '__str__', '__subclasshook__', '__weakref__', '_abc_impl', '_apply', '_auto_class', '_backward_compatibility_gradient_checkpointing', '_backward_hooks', '_backward_pre_hooks', '_buffers', '_call_impl', '_convert_head_mask_to_5d', '_create_repo', '_expand_inputs_for_generation', '_extract_past_from_model_output', '_forward_hooks', '_forward_hooks_with_kwargs', '_forward_pre_hooks', '_forward_pre_hooks_with_kwargs', '_from_config', '_get_backward_hooks', '_get_backward_pre_hooks', '_get_decoder_start_token_id', '_get_files_timestamps', '_get_logits_processor', '_get_logits_warper', '_get_name', '_get_resized_embeddings', '_get_resized_lm_head', '_get_stopping_criteria', '_hook_rss_memory_post_forward', '_hook_rss_memory_pre_forward', '_init_weights', '_initialize_weights', '_is_full_backward_hook', '_is_hf_initialized', '_keep_in_fp32_modules', '_keys_to_ignore_on_load_missing', '_keys_to_ignore_on_load_unexpected', '_keys_to_ignore_on_save', '_load_from_state_dict', '_load_pretrained_model', '_load_pretrained_model_low_mem', '_load_state_dict_post_hooks', '_load_state_dict_pre_hooks', '_maybe_initialize_input_ids_for_generation', '_maybe_warn_non_full_backward_hook', '_merge_criteria_processor_list', '_modules', '_named_members', '_no_split_modules', '_non_persistent_buffers_set', '_parameters', '_prepare_attention_mask_for_generation', '_prepare_decoder_input_ids_for_generation', '_prepare_encoder_decoder_kwargs_for_generation', '_prepare_model_inputs', '_register_load_state_dict_pre_hook', '_register_state_dict_hook', '_reorder_cache', '_replicate_for_data_parallel', '_resize_token_embeddings', '_save_to_state_dict', '_set_default_torch_dtype', '_set_gradient_checkpointing', '_skip_keys_device_placement', '_slow_forward', '_state_dict_hooks', '_state_dict_pre_hooks', '_tie_encoder_decoder_weights', '_tie_or_clone_weights', '_tied_weights_keys', '_update_model_kwargs_for_generation', '_upload_modified_files', '_validate_model_class', '_validate_model_kwargs', '_version', 'add_memory_hooks', 'add_module', 'adjust_logits_during_generation', 'apply', 'assisted_decoding', 'base_model', 'base_model_prefix', 'beam_sample', 'beam_search', 'bfloat16', 'buffers', 'call_super_init', 'can_generate', 'children', 'compute_transition_scores', 'config', 'config_class', 'constrained_beam_search', 'contrastive_search', 'cpu', 'create_extended_attention_mask_for_decoder', 'cuda', 'device', 'disable_input_require_grads', 'double', 'dtype', 'dummy_inputs', 'dump_patches', 'enable_input_require_grads', 'encode_images', 'estimate_tokens', 'eval', 'extra_repr', 'float', 'floating_point_ops', 'forward', 'framework', 'from_pretrained', 'generate', 'generation_config', 'get_buffer', 'get_decoder', 'get_extended_attention_mask', 'get_extra_state', 'get_head_mask', 'get_input_embeddings', 'get_memory_footprint', 'get_model', 'get_output_embeddings', 'get_parameter', 'get_position_embeddings', 'get_submodule', 'get_vision_tower', 'gradient_checkpointing_disable', 'gradient_checkpointing_enable', 'greedy_search', 'group_beam_search', 'half', 'hf_device_map', 'init_weights', 'initialize_vision_tokenizer', 'invert_attention_mask', 'ipu', 'is_gradient_checkpointing', 'is_loaded_in_4bit', 'is_loaded_in_8bit', 'is_parallelizable', 'is_quantized', 'lm_head', 'load_state_dict', 'main_input_name', 'model', 'modules', 'name_or_path', 'named_buffers', 'named_children', 'named_modules', 'named_parameters', 'num_parameters', 'parameters', 'post_init', 'prepare_inputs_for_generation', 'prepare_inputs_labels_for_multimodal', 'pretraining_tp', 'prune_heads', 'push_to_hub', 'register_backward_hook', 'register_buffer', 'register_for_auto_class', 'register_forward_hook', 'register_forward_pre_hook', 'register_full_backward_hook', 'register_full_backward_pre_hook', 'register_load_state_dict_post_hook', 'register_module', 'register_parameter', 'register_state_dict_pre_hook', 'requires_grad_', 'reset_memory_hooks_state', 'resize_position_embeddings', 'resize_token_embeddings', 'retrieve_modules_from_names', 'reverse_bettertransformer', 'sample', 'save_pretrained', 'set_decoder', 'set_extra_state', 'set_input_embeddings', 'set_output_embeddings', 'share_memory', 'state_dict', 'supports_gradient_checkpointing', 'tie_weights', 'to', 'to_bettertransformer', 'to_empty', 'train', 'training', 'type', 'vocab_size', 'warn_if_padding_and_no_attention_mask', 'warnings_issued', 'xpu', 'zero_grad'] |
返回值为
1 | <class 'transformers.modeling_outputs.CausalLMOutputWithPast'> 2 |
LlamaAttention
forward()
方法:
1 | def forward( |
1 | ['T_destination', '__annotations__', '__call__', '__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattr__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__setstate__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_apply', '_backward_hooks', '_backward_pre_hooks', '_buffers', '_call_impl', '_forward_hooks', '_forward_hooks_with_kwargs', '_forward_pre_hooks', '_forward_pre_hooks_with_kwargs', '_get_backward_hooks', '_get_backward_pre_hooks', '_get_name', '_init_rope', '_is_full_backward_hook', '_is_hf_initialized', '_load_from_state_dict', '_load_state_dict_post_hooks', '_load_state_dict_pre_hooks', '_maybe_warn_non_full_backward_hook', '_modules', '_named_members', '_non_persistent_buffers_set', '_parameters', '_register_load_state_dict_pre_hook', '_register_state_dict_hook', '_replicate_for_data_parallel', '_save_to_state_dict', '_shape', '_slow_forward', '_state_dict_hooks', '_state_dict_pre_hooks', '_version', 'add_module', 'apply', 'bfloat16', 'buffers', 'call_super_init', 'children', 'config', 'cpu', 'cuda', 'double', 'dump_patches', 'eval', 'extra_repr', 'float', 'forward', 'get_buffer', 'get_extra_state', 'get_parameter', 'get_submodule', 'half', 'head_dim', 'hidden_size', 'ipu', 'k_proj', 'load_state_dict', 'max_position_embeddings', 'modules', 'named_buffers', 'named_children', 'named_modules', 'named_parameters', 'num_heads', 'num_key_value_groups', 'num_key_value_heads', 'o_proj', 'parameters', 'pretraining_tp', 'q_proj', 'register_backward_hook', 'register_buffer', 'register_forward_hook', 'register_forward_pre_hook', 'register_full_backward_hook', 'register_full_backward_pre_hook', 'register_load_state_dict_post_hook', 'register_module', 'register_parameter', 'register_state_dict_pre_hook', 'requires_grad_', 'rotary_emb', 'set_extra_state', 'share_memory', 'state_dict', 'to', 'to_empty', 'train', 'training', 'type', 'v_proj', 'xpu', 'zero_grad'] |
generate()
方法获取推理过程中 decoder 中每一层输出的 attention 矩阵.
在文件 transformers/generation/utils.py
中的
GenerationMixin
类中的方法. 然后用 了llava 推理的时候发现
is_greedy_gen_mode
的值为 True
, 因此
generate()
函数的返回值为
1 | # 这个在 1541 行左右 |
greedy_search()
函数也在 GenerationMixin
类中实现, 定义为:
1 | def greedy_search( |
将上面给出注释的两个参数传入为 True
或者直接在源码修改为
True
, 然后在 2380 行左右:
不知道为什么尝试在
generate()
函数的返回值处传入output_attentions=True
以及return_dict_in_generate=True
会报错.
1 | if return_dict_in_generate: |
这两个参数控制了是否会把 attention 存在来存在
decoder_attentions
中, 最终的返回值为
1 | # 2433 行左右 |
然后这个类是专门用来输出的类, 也在
transformers/generation/utils.py
中实现,
1 |
|
这个参数的注释写的太长了, 用中文重新表述一下:
sequences
是形状为
(batch_size, sequence_length)
的张量,
就是推理过程产生的回答, sequence_length
要么等于
max_length
, 要么因为 eos_token_id
所有 batches
都更早结束而长度短于 max_length
, 具体长度不定.
scores
暂时没用到, 不写了.
attentions
的类型是
tuple(tuple(torch.FloatTensor))
, 最外面的
tuple
的长度是生成的 token 的个数, 第二个 tuple 的长度是
decoder 中 layer 的个数, tensor 的维度是
(batch_size, num_heads, generated_length, sequence_length)
.
然后 sequence_length
和上面是一致的,
generate_length
的含义还不明.
现在要搞清楚 generated_length
的含义,
搞清楚为什么对于第一个 token, generate_length
等于
sequence_length
, 而后面的 token
generate_length
都等于 1.
hidden_states
也没用到, 也不写了.
然后这个要具体看 greedy_search
逐 token 预测的过程.
问了以下 GPT, 回答如下:
generate_length
表示在生成每个标记时模型所关注的输入序列的长度。在注意力机制中,模型会根据当前生成的标记以及其上下文信息来决定在生成下一个标记时所关注的输入序列的部分。因此,generate_length
实际上是动态变化的,取决于当前生成的标记的位置。
- 对于第一个标记: 在生成第一个标记时,模型需要考虑整个输入序列以及其上下文信息,因为第一个标记是在整个输入序列的基础上生成的。因此,对于第一个标记,
generated_length
等于sequence_length
,表示模型在生成第一个标记时需要考虑整个输入序列的注意力权重。- 对于后续的标记: 一旦生成了第一个标记,模型在生成后续的标记时不再需要考虑整个输入序列的信息,而只需要关注已生成的标记以及它们的上下文信息。因此,对于后续的标记,
generated_length
等于 1,表示模型在生成这些标记时只关注了当前时间步的输入。在注意力机制中,
generated_length
表示了模型在生成每个标记时所关注的输入序列的长度。对于第一个标记,模型需要关注整个输入序列,因此generated_length
等于sequence_length
。而对于后续的标记,模型只需要关注已生成的标记,因此generated_length
为 1。这个回答应该是正确的。
greed_search()
1 | while True: |
greedy_search()
里面说明了每一个 token 是如何具体生成的.
这里是用 greedy decoding 来生成 token 的.
第二个参数是 logits_processor
, 用来在每一个生成步中修改
language modeling head 的预测分数.
具体过程在一个 while
循环里, 下面只说和 token
生成高度相关的内容:
首先用 input_ids
和其他模型的参数准备
model_inputs
将 model_inputs
以及其他参数前向传播给
self
得到 outputs
outputs
有一个 logits
的键值, 通过
next_token_logits = outputs.logits[:, -1, :]
获取下一个
token 的 logits
, 然后通过 logits_processor
预处理分布得到下一个 token 的分数.
1 | # pre-process distribution |
然后根据需求把 分数, attention 矩阵以及 hidden state 存起来.
然后下一个 token 的预测是选择分数最高的那个做作为下一个 token
1 | # argmax |
修改 input_ids
, 将它与新生成的 token 拼接在一起
input_ids = torch.cat([input_ids, next_tokens[:, None]], dim=-1)
,
然后修改模型的其他参数 model_kwargs
如果发现某个句子中出现了 eos_token
,
就将句子的状态设置为 finished
最后根据是否满足了 stopping_criteria
决定是否退出循环
这里的 self
是由 GenerateMixin
类实例化的某一个具体模型, 可以看作是输入进了这个模型的
forward()
函数, 按照对 LLaVA-1.5 源码的分析, 这里调用的是
LlamaForCausalLM
类的 forward
函数,
最终的返回值为
1 | CausalLMOutputWithPast( |
确实可以获取到 logits
, hidden_states
和
attentions
这三项内容. 然后具体看一下 logits
的生成方式:
1 | outputs = self.model( |
这里的最后一行的 logits
就是最重要返回的
logits
. 其中用到的 self.lm_head
就是语言模型的
head, 这里就是一个 Linear
:
1 | self.lm_head = nn.Linear(config.hidden_size, config.vocab_size, bias=False) |
1 | # 这里是输出 token 之间的 attention 矩阵 |
设第一个 token 对应的 attn 矩阵为
输入的 token 为
将
将
然后需要获取的是
还有一个疑问, 最后一个 token 呢?
只能有卡的时候再跑的时候看细节了.
从
1: 代码中的 logits
特质模型输出的值,
指没有通过 softmax 处理的值.
2: 在 softmax 中引入温度参数
3: token 和 tokenize 以及 tokenizer.