mirror of
https://github.com/huggingface/lerobot.git
synced 2026-07-24 18:26:11 +00:00
add training support
This commit is contained in:
@@ -32,6 +32,7 @@ IMAGENET_STATS = {
|
|||||||
"std": [[[0.229]], [[0.224]], [[0.225]]], # (c,1,1)
|
"std": [[[0.229]], [[0.224]], [[0.225]]], # (c,1,1)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
from lerobot.common.datasets.utils_must import (EPISODES_DATASET_MAPPING, TRAINING_FEATURES, FEATURE_KEYS_MAPPING)
|
||||||
|
|
||||||
def resolve_delta_timestamps(
|
def resolve_delta_timestamps(
|
||||||
cfg: PreTrainedConfig, ds_meta: LeRobotDatasetMetadata
|
cfg: PreTrainedConfig, ds_meta: LeRobotDatasetMetadata
|
||||||
@@ -66,7 +67,7 @@ def resolve_delta_timestamps(
|
|||||||
return delta_timestamps
|
return delta_timestamps
|
||||||
|
|
||||||
|
|
||||||
def make_dataset(cfg: TrainPipelineConfig) -> LeRobotDataset | MultiLeRobotDataset:
|
def make_dataset1(cfg: TrainPipelineConfig) -> LeRobotDataset | MultiLeRobotDataset:
|
||||||
"""Handles the logic of setting up delta timestamps and image transforms before creating a dataset.
|
"""Handles the logic of setting up delta timestamps and image transforms before creating a dataset.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@@ -116,3 +117,96 @@ def make_dataset(cfg: TrainPipelineConfig) -> LeRobotDataset | MultiLeRobotDatas
|
|||||||
dataset.meta.stats[key][stats_type] = torch.tensor(stats, dtype=torch.float32)
|
dataset.meta.stats[key][stats_type] = torch.tensor(stats, dtype=torch.float32)
|
||||||
|
|
||||||
return dataset
|
return dataset
|
||||||
|
|
||||||
|
def make_dataset(cfg: TrainPipelineConfig) -> LeRobotDataset | MultiLeRobotDataset:
|
||||||
|
"""Handles the logic of setting up delta timestamps and image transforms before creating a dataset.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
cfg (TrainPipelineConfig): A TrainPipelineConfig config which contains a DatasetConfig and a PreTrainedConfig.
|
||||||
|
|
||||||
|
Raises:
|
||||||
|
NotImplementedError: The MultiLeRobotDataset is currently deactivated.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
LeRobotDataset | MultiLeRobotDataset
|
||||||
|
"""
|
||||||
|
image_transforms = (
|
||||||
|
ImageTransforms(cfg.dataset.image_transforms) if cfg.dataset.image_transforms.enable else None
|
||||||
|
)
|
||||||
|
if "," in cfg.dataset.repo_id:
|
||||||
|
repo_id = cfg.dataset.repo_id.split(",")
|
||||||
|
repo_id = [r for r in repo_id if r]
|
||||||
|
else:
|
||||||
|
repo_id = cfg.dataset.repo_id
|
||||||
|
sampling_weights = cfg.dataset.sampling_weights.split(",") if cfg.dataset.sampling_weights else None
|
||||||
|
feature_keys_mapping = FEATURE_KEYS_MAPPING
|
||||||
|
if isinstance(repo_id, str):
|
||||||
|
revision = getattr(cfg.dataset, "revision", None)
|
||||||
|
ds_meta = LeRobotDatasetMetadata(
|
||||||
|
cfg.dataset.repo_id,
|
||||||
|
local_files_only=cfg.dataset.local_files_only,
|
||||||
|
feature_keys_mapping=feature_keys_mapping,
|
||||||
|
revision=revision,
|
||||||
|
)
|
||||||
|
delta_timestamps = resolve_delta_timestamps(cfg.policy, ds_meta)
|
||||||
|
dataset = LeRobotDataset(
|
||||||
|
cfg.dataset.repo_id,
|
||||||
|
root=getattr(cfg.dataset, "root", None),
|
||||||
|
episodes=cfg.dataset.episodes,
|
||||||
|
delta_timestamps=delta_timestamps,
|
||||||
|
image_transforms=image_transforms,
|
||||||
|
revision=revision,
|
||||||
|
video_backend=cfg.dataset.video_backend,
|
||||||
|
local_files_only=cfg.dataset.local_files_only,
|
||||||
|
feature_keys_mapping=feature_keys_mapping,
|
||||||
|
max_action_dim=cfg.dataset.max_action_dim,
|
||||||
|
max_state_dim=cfg.dataset.max_state_dim,
|
||||||
|
max_num_images=cfg.dataset.max_num_images,
|
||||||
|
max_image_dim=cfg.dataset.max_image_dim,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
delta_timestamps = {}
|
||||||
|
episodes = {}
|
||||||
|
for i in range(len(repo_id)):
|
||||||
|
ds_meta = LeRobotDatasetMetadata(
|
||||||
|
repo_id[i],
|
||||||
|
local_files_only=cfg.dataset.local_files_only,
|
||||||
|
feature_keys_mapping=feature_keys_mapping,
|
||||||
|
) # FIXME(mshukor): ?
|
||||||
|
delta_timestamps[repo_id[i]] = resolve_delta_timestamps(cfg.policy, ds_meta)
|
||||||
|
episodes[repo_id[i]] = EPISODES_DATASET_MAPPING.get(repo_id[i], cfg.dataset.episodes)
|
||||||
|
training_features = TRAINING_FEATURES.get(cfg.dataset.features_version, None)
|
||||||
|
dataset = MultiLeRobotDataset(
|
||||||
|
repo_id,
|
||||||
|
# TODO(aliberts): add proper support for multi dataset
|
||||||
|
episodes=episodes,
|
||||||
|
delta_timestamps=delta_timestamps,
|
||||||
|
image_transforms=image_transforms,
|
||||||
|
video_backend=cfg.dataset.video_backend,
|
||||||
|
local_files_only=cfg.dataset.local_files_only,
|
||||||
|
sampling_weights=sampling_weights,
|
||||||
|
feature_keys_mapping=feature_keys_mapping,
|
||||||
|
max_action_dim=cfg.dataset.max_action_dim,
|
||||||
|
max_state_dim=cfg.dataset.max_state_dim,
|
||||||
|
max_num_images=cfg.dataset.max_num_images,
|
||||||
|
max_image_dim=cfg.dataset.max_image_dim,
|
||||||
|
train_on_all_features=cfg.dataset.train_on_all_features,
|
||||||
|
training_features=training_features,
|
||||||
|
discard_first_n_frames=cfg.dataset.discard_first_n_frames,
|
||||||
|
min_fps=cfg.dataset.min_fps,
|
||||||
|
max_fps=cfg.dataset.max_fps,
|
||||||
|
discard_first_idle_frames=cfg.dataset.discard_first_idle_frames,
|
||||||
|
motion_threshold=cfg.dataset.motion_threshold,
|
||||||
|
motion_window_size=cfg.dataset.motion_window_size,
|
||||||
|
motion_buffer=cfg.dataset.motion_buffer,
|
||||||
|
)
|
||||||
|
logging.info(
|
||||||
|
"Multiple datasets were provided. Applied the following index mapping to the provided datasets: "
|
||||||
|
f"{pformat(dataset.repo_id_to_index , indent=2)}"
|
||||||
|
)
|
||||||
|
if cfg.dataset.use_imagenet_stats:
|
||||||
|
for key in dataset.meta.camera_keys:
|
||||||
|
for stats_type, stats in IMAGENET_STATS.items():
|
||||||
|
dataset.meta.stats[key][stats_type] = torch.tensor(stats, dtype=torch.float32)
|
||||||
|
|
||||||
|
return dataset
|
||||||
|
|||||||
@@ -83,6 +83,7 @@ from lerobot.common.datasets.utils_must import (
|
|||||||
create_padded_features,
|
create_padded_features,
|
||||||
pad_tensor,
|
pad_tensor,
|
||||||
map_dict_keys,
|
map_dict_keys,
|
||||||
|
find_start_of_motion,
|
||||||
ROBOT_TYPE_KEYS_MAPPING,
|
ROBOT_TYPE_KEYS_MAPPING,
|
||||||
OBS_IMAGE,
|
OBS_IMAGE,
|
||||||
OBS_IMAGE_2,
|
OBS_IMAGE_2,
|
||||||
@@ -562,7 +563,7 @@ class LeRobotDataset(torch.utils.data.Dataset):
|
|||||||
self.subset_frame_ids += [frame_idx for frame_idx in range(from_ + int(self.fps*self.discard_first_n_frames), to_)]
|
self.subset_frame_ids += [frame_idx for frame_idx in range(from_ + int(self.fps*self.discard_first_n_frames), to_)]
|
||||||
elif self.discard_first_idle_frames:
|
elif self.discard_first_idle_frames:
|
||||||
print(f"Discarding first idle frames: motion_threshold={self.motion_threshold}, motion_window_size={self.motion_window_size}, motion_buffer={self.motion_buffer}")
|
print(f"Discarding first idle frames: motion_threshold={self.motion_threshold}, motion_window_size={self.motion_window_size}, motion_buffer={self.motion_buffer}")
|
||||||
self.robot_states = torch.stack(self.hf_dataset[OBS_ROBOT]).numpy() # shape: [T, D]
|
self.robot_states = torch.stack(self.hf_dataset[OBS_STATE]).numpy() # shape: [T, D]
|
||||||
self.subset_frame_ids = []
|
self.subset_frame_ids = []
|
||||||
for ep_idx in range(self.num_episodes):
|
for ep_idx in range(self.num_episodes):
|
||||||
from_ = self.episode_data_index["from"][ep_idx]
|
from_ = self.episode_data_index["from"][ep_idx]
|
||||||
|
|||||||
@@ -191,6 +191,13 @@ def map_dict_keys(item: dict, feature_keys_mapping: dict, training_features: lis
|
|||||||
features[key] = item[key]
|
features[key] = item[key]
|
||||||
return features
|
return features
|
||||||
|
|
||||||
|
def find_start_of_motion(velocities, window_size, threshold, motion_buffer):
|
||||||
|
for t in range(len(velocities) - window_size):
|
||||||
|
window_mean = velocities[t:t+window_size].mean()
|
||||||
|
if window_mean > threshold:
|
||||||
|
return max(0, t - motion_buffer) # include slight context before motion
|
||||||
|
return 0
|
||||||
|
|
||||||
import yaml
|
import yaml
|
||||||
import requests
|
import requests
|
||||||
def load_yaml_mapping(name: str) -> dict:
|
def load_yaml_mapping(name: str) -> dict:
|
||||||
@@ -205,4 +212,19 @@ def load_yaml_mapping(name: str) -> dict:
|
|||||||
return yaml.safe_load(response.text)
|
return yaml.safe_load(response.text)
|
||||||
|
|
||||||
# Example usage
|
# Example usage
|
||||||
TASKS_KEYS_MAPPING = load_yaml_mapping("features")
|
TASKS_KEYS_MAPPING = load_yaml_mapping("tasks")
|
||||||
|
FEATURE_KEYS_MAPPING = load_yaml_mapping("features")
|
||||||
|
EPISODES_DATASET_MAPPING = {
|
||||||
|
"cadene/droid_1.0.1": list(range(50)),
|
||||||
|
"danaaubakirova/svla_so100_task5_v3": [0, 1, 2, 3, 4, 5, 6, 7, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51],
|
||||||
|
"danaaubakirova/svla_so100_task4_v3": [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53],
|
||||||
|
}
|
||||||
|
ACTION = "action"
|
||||||
|
OBS_STATE = "observation.state"
|
||||||
|
TASK = "task"
|
||||||
|
ROBOT = "robot_type"
|
||||||
|
TRAINING_FEATURES = {
|
||||||
|
0: [ACTION, OBS_STATE, TASK, ROBOT, OBS_IMAGE],
|
||||||
|
1: [ACTION, OBS_STATE, TASK, ROBOT, OBS_IMAGE, OBS_IMAGE_2],
|
||||||
|
2: [ACTION, OBS_STATE, TASK, ROBOT, OBS_IMAGE, OBS_IMAGE_2, OBS_IMAGE_3],
|
||||||
|
}
|
||||||
|
|||||||
@@ -0,0 +1,191 @@
|
|||||||
|
# Copyright 2025 The HuggingFace Inc. team. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
from dataclasses import dataclass, field
|
||||||
|
|
||||||
|
from lerobot.common.optim.optimizers import AdamWConfig
|
||||||
|
from lerobot.common.optim.schedulers import (
|
||||||
|
CosineDecayWithWarmupSchedulerConfig,
|
||||||
|
)
|
||||||
|
from lerobot.configs.policies import PreTrainedConfig
|
||||||
|
from lerobot.configs.types import FeatureType, NormalizationMode, PolicyFeature
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
|
class PEFTConfig:
|
||||||
|
r: int = 4
|
||||||
|
lora_alpha: int = 16
|
||||||
|
lora_dropout: float = 0.1
|
||||||
|
target_modules: str = "q_proj,v_proj"
|
||||||
|
|
||||||
|
|
||||||
|
@PreTrainedConfig.register_subclass("smolvla2")
|
||||||
|
@dataclass
|
||||||
|
class SmolVLA2Config(PreTrainedConfig):
|
||||||
|
# Input / output structure.
|
||||||
|
n_obs_steps: int = 1
|
||||||
|
chunk_size: int = 50
|
||||||
|
n_action_steps: int = 50
|
||||||
|
|
||||||
|
normalization_mapping: dict[str, NormalizationMode] = field(
|
||||||
|
default_factory=lambda: {
|
||||||
|
"VISUAL": NormalizationMode.IDENTITY,
|
||||||
|
"STATE": NormalizationMode.MEAN_STD,
|
||||||
|
"ACTION": NormalizationMode.MEAN_STD,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
# Shorter state and action vectors will be padded
|
||||||
|
max_state_dim: int = 32
|
||||||
|
max_action_dim: int = 32
|
||||||
|
|
||||||
|
# Image preprocessing
|
||||||
|
resize_imgs_with_padding: tuple[int, int] = (512, 512)
|
||||||
|
|
||||||
|
# Add empty images. Used by smolvla_aloha_sim which adds the empty
|
||||||
|
# left and right wrist cameras in addition to the top camera.
|
||||||
|
empty_cameras: int = 0
|
||||||
|
|
||||||
|
# Converts the joint and gripper values from the standard Aloha space to
|
||||||
|
# the space used by the pi internal runtime which was used to train the base model.
|
||||||
|
adapt_to_pi_aloha: bool = False
|
||||||
|
|
||||||
|
# Converts joint dimensions to deltas with respect to the current state before passing to the model.
|
||||||
|
# Gripper dimensions will remain in absolute values.
|
||||||
|
use_delta_joint_actions_aloha: bool = False
|
||||||
|
|
||||||
|
# Tokenizer
|
||||||
|
tokenizer_max_length: int = 48
|
||||||
|
proj_width: int = 480
|
||||||
|
# Decoding
|
||||||
|
num_steps: int = 10
|
||||||
|
|
||||||
|
# Attention utils
|
||||||
|
use_cache: bool = True
|
||||||
|
|
||||||
|
# Finetuning settings
|
||||||
|
freeze_vision_encoder: bool = True
|
||||||
|
train_expert_only: bool = False
|
||||||
|
train_state_proj: bool = True
|
||||||
|
|
||||||
|
# Training presets
|
||||||
|
optimizer_lr: float = 2.5e-5 # 1e-4
|
||||||
|
optimizer_betas: tuple[float, float] = (0.9, 0.95)
|
||||||
|
optimizer_eps: float = 1e-8
|
||||||
|
optimizer_weight_decay: float = 1e-10
|
||||||
|
optimizer_grad_clip_norm: float = 10
|
||||||
|
optimizer_lr_vlm: float = 0
|
||||||
|
|
||||||
|
scheduler_warmup_steps: int = 1_000
|
||||||
|
scheduler_decay_steps: int = 30_000
|
||||||
|
scheduler_decay_lr: float = 2.5e-6
|
||||||
|
|
||||||
|
vlm_model_name: str = "HuggingFaceTB/SmolVLM2-500M-Video-Instruct" # Select the VLM backbone.
|
||||||
|
load_vlm_weights: bool = False # Set to True in case of training the expert from scratch. True when init from pretrained SmolVLA weights
|
||||||
|
checkpoint_path: str = None
|
||||||
|
peft_method: str = ""
|
||||||
|
peft_config: PEFTConfig = field(default_factory=PEFTConfig)
|
||||||
|
peft_target_model: str = ""
|
||||||
|
add_image_special_tokens: bool = False # Whether to use special image tokens around image features.
|
||||||
|
|
||||||
|
attention_mode: str = "cross_attn"
|
||||||
|
|
||||||
|
prefix_length: int = -1
|
||||||
|
|
||||||
|
pad_language_to: str = "longest" # "max_length"
|
||||||
|
|
||||||
|
num_expert_layers: int = -1 # Less or equal to 0 is the default where the action expert has the same number of layers of VLM. Otherwise the expert have less layers.
|
||||||
|
num_vlm_layers: int = 16
|
||||||
|
past_obs_keys: str = "image"
|
||||||
|
add_local_special_image_tokens: bool = False
|
||||||
|
|
||||||
|
reverse_images_order: bool = False
|
||||||
|
|
||||||
|
state_to_prefix: bool = False
|
||||||
|
|
||||||
|
pad_language_to: str = "longest" # "max_length"
|
||||||
|
causal_action_attention_mask: bool = False
|
||||||
|
|
||||||
|
self_attn_every_n_layers: int = -1 # Number of layers used in the VLM (first num_vlm_layers layers)
|
||||||
|
# self_attn_every_n_layers: int = 2 # Interleave SA layers each self_attn_every_n_layers
|
||||||
|
expert_width_multiplier: float = 0.75 # The action expert hidden size (wrt to the VLM)
|
||||||
|
|
||||||
|
min_period: float = 4e-3 # sensitivity range for the timestep used in sine-cosine positional encoding
|
||||||
|
max_period: float = 4.0
|
||||||
|
|
||||||
|
robot_type: str = ""
|
||||||
|
|
||||||
|
self_attn_only_actions: bool = False
|
||||||
|
|
||||||
|
causal_attention_on_history: bool = False
|
||||||
|
|
||||||
|
predict_relative_actions: bool = False
|
||||||
|
relative_actions_mode: str = "first"
|
||||||
|
|
||||||
|
shuffle_camera_positions: bool = False
|
||||||
|
vlm_img_size: int = -1
|
||||||
|
|
||||||
|
regression_loss: bool = False
|
||||||
|
|
||||||
|
def __post_init__(self):
|
||||||
|
super().__post_init__()
|
||||||
|
|
||||||
|
"""Input validation (not exhaustive)."""
|
||||||
|
if self.n_action_steps > self.chunk_size:
|
||||||
|
raise ValueError(
|
||||||
|
f"The chunk size is the upper bound for the number of action steps per model invocation. Got "
|
||||||
|
f"{self.n_action_steps} for `n_action_steps` and {self.chunk_size} for `chunk_size`."
|
||||||
|
)
|
||||||
|
if self.use_delta_joint_actions_aloha:
|
||||||
|
raise NotImplementedError(
|
||||||
|
"`use_delta_joint_actions_aloha` is used by smolvla for aloha real models. It is not ported yet in LeRobot."
|
||||||
|
)
|
||||||
|
|
||||||
|
def validate_features(self) -> None:
|
||||||
|
for i in range(self.empty_cameras):
|
||||||
|
key = f"observation.images.empty_camera_{i}"
|
||||||
|
empty_camera = PolicyFeature(
|
||||||
|
type=FeatureType.VISUAL,
|
||||||
|
shape=(3, 480, 640),
|
||||||
|
)
|
||||||
|
self.input_features[key] = empty_camera
|
||||||
|
|
||||||
|
def get_optimizer_preset(self) -> AdamWConfig:
|
||||||
|
return AdamWConfig(
|
||||||
|
lr=self.optimizer_lr,
|
||||||
|
betas=self.optimizer_betas,
|
||||||
|
eps=self.optimizer_eps,
|
||||||
|
weight_decay=self.optimizer_weight_decay,
|
||||||
|
grad_clip_norm=self.optimizer_grad_clip_norm,
|
||||||
|
)
|
||||||
|
|
||||||
|
def get_scheduler_preset(self):
|
||||||
|
return CosineDecayWithWarmupSchedulerConfig(
|
||||||
|
peak_lr=self.optimizer_lr,
|
||||||
|
decay_lr=self.scheduler_decay_lr,
|
||||||
|
num_warmup_steps=self.scheduler_warmup_steps,
|
||||||
|
num_decay_steps=self.scheduler_decay_steps,
|
||||||
|
)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def observation_delta_indices(self) -> list:
|
||||||
|
return [0]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def action_delta_indices(self) -> list:
|
||||||
|
return list(range(self.chunk_size))
|
||||||
|
|
||||||
|
@property
|
||||||
|
def reward_delta_indices(self) -> None:
|
||||||
|
return None
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,599 @@
|
|||||||
|
# Copyright 2025 The HuggingFace Inc. team. All rights reserved.
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
|
||||||
|
import copy
|
||||||
|
from typing import List, Optional
|
||||||
|
|
||||||
|
import torch
|
||||||
|
from torch import nn
|
||||||
|
from transformers import (
|
||||||
|
AutoConfig,
|
||||||
|
AutoModel,
|
||||||
|
AutoModelForImageTextToText,
|
||||||
|
AutoProcessor,
|
||||||
|
SmolVLMForConditionalGeneration,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def apply_rope(x, positions, max_wavelength=10_000):
|
||||||
|
"""
|
||||||
|
Applies RoPE positions [B, L] to x [B, L, H, D].
|
||||||
|
"""
|
||||||
|
d_half = x.shape[-1] // 2
|
||||||
|
device = x.device
|
||||||
|
dtype = x.dtype
|
||||||
|
x = x.to(torch.float32)
|
||||||
|
|
||||||
|
freq_exponents = (2.0 / x.shape[-1]) * torch.arange(d_half, dtype=torch.float32, device=device)
|
||||||
|
timescale = max_wavelength**freq_exponents
|
||||||
|
radians = positions[..., None].to(torch.float32) / timescale[None, None, :].to(torch.float32)
|
||||||
|
|
||||||
|
radians = radians[..., None, :]
|
||||||
|
|
||||||
|
sin = torch.sin(radians) # .to(dtype=dtype)
|
||||||
|
cos = torch.cos(radians) # .to(dtype=dtype)
|
||||||
|
|
||||||
|
x1, x2 = x.split(d_half, dim=-1)
|
||||||
|
res = torch.empty_like(x)
|
||||||
|
res[..., :d_half] = x1 * cos - x2 * sin
|
||||||
|
res[..., d_half:] = x2 * cos + x1 * sin
|
||||||
|
|
||||||
|
return res.to(dtype)
|
||||||
|
|
||||||
|
|
||||||
|
def get_intermediate_size(hidden_dim, ffn_dim_multiplier=4, multiple_of=256):
|
||||||
|
hidden_dim = int(2 * hidden_dim / 3)
|
||||||
|
hidden_dim = int(ffn_dim_multiplier * hidden_dim)
|
||||||
|
hidden_dim = multiple_of * ((hidden_dim + multiple_of - 1) // multiple_of)
|
||||||
|
return hidden_dim
|
||||||
|
|
||||||
|
|
||||||
|
class SmolVLMWithExpertModel(nn.Module):
|
||||||
|
def __init__(
|
||||||
|
self,
|
||||||
|
model_id: str = "HuggingFaceTB/SmolVLM2-500M-Video-Instruct",
|
||||||
|
load_vlm_weights: bool = True,
|
||||||
|
train_expert_only: bool = True,
|
||||||
|
freeze_vision_encoder: bool = False,
|
||||||
|
attention_mode: str = "self_attn",
|
||||||
|
num_expert_layers: int = -1,
|
||||||
|
num_vlm_layers: int = -1,
|
||||||
|
self_attn_every_n_layers: int = -1,
|
||||||
|
expert_width_multiplier: float = 0.5,
|
||||||
|
):
|
||||||
|
super().__init__()
|
||||||
|
if load_vlm_weights:
|
||||||
|
print(f"Loading {model_id} weights ...")
|
||||||
|
self.vlm = AutoModelForImageTextToText.from_pretrained(
|
||||||
|
model_id,
|
||||||
|
device_map="auto",
|
||||||
|
torch_dtype="bfloat16",
|
||||||
|
low_cpu_mem_usage=True,
|
||||||
|
)
|
||||||
|
config = self.vlm.config
|
||||||
|
else:
|
||||||
|
config = AutoConfig.from_pretrained(model_id)
|
||||||
|
self.vlm = SmolVLMForConditionalGeneration(config=config)
|
||||||
|
self.processor = AutoProcessor.from_pretrained(model_id)
|
||||||
|
if num_vlm_layers > 0:
|
||||||
|
print(f"Reducing the number of VLM layers to {num_vlm_layers} ...")
|
||||||
|
self.get_vlm_model().text_model.layers = self.get_vlm_model().text_model.layers[:num_vlm_layers]
|
||||||
|
self.num_vlm_layers = len(self.get_vlm_model().text_model.layers)
|
||||||
|
self.config = config
|
||||||
|
# Smaller lm expert
|
||||||
|
lm_expert_config = copy.deepcopy(config.text_config)
|
||||||
|
hidden_size = lm_expert_config.hidden_size
|
||||||
|
lm_expert_config.hidden_size = int(hidden_size * expert_width_multiplier) # hidden_size // 2
|
||||||
|
lm_expert_config.intermediate_size = get_intermediate_size(int(hidden_size * expert_width_multiplier))
|
||||||
|
lm_expert_config.num_hidden_layers = self.num_vlm_layers
|
||||||
|
if num_expert_layers > 0:
|
||||||
|
assert len(self.get_vlm_model().text_model.layers) % num_expert_layers == 0, (
|
||||||
|
f"Number of layers in the VLM {len(self.get_vlm_model().text_model.layers)} are not multiple of num_expert_layers {num_expert_layers}"
|
||||||
|
)
|
||||||
|
lm_expert_config.num_hidden_layers = num_expert_layers
|
||||||
|
self.lm_expert = AutoModel.from_config(lm_expert_config)
|
||||||
|
|
||||||
|
self.num_expert_layers = len(self.lm_expert.layers)
|
||||||
|
self.self_attn_every_n_layers = self_attn_every_n_layers
|
||||||
|
if "cross" in attention_mode:
|
||||||
|
# Reshape qkv projections to have the same input dimension as the vlm
|
||||||
|
for layer_idx in range(len(self.lm_expert.layers)):
|
||||||
|
if self.self_attn_every_n_layers > 0 and layer_idx % self.self_attn_every_n_layers == 0:
|
||||||
|
continue
|
||||||
|
self.lm_expert.layers[layer_idx].self_attn.k_proj = nn.Linear(
|
||||||
|
config.text_config.num_key_value_heads * config.text_config.head_dim,
|
||||||
|
lm_expert_config.num_key_value_heads * lm_expert_config.head_dim,
|
||||||
|
bias=lm_expert_config.attention_bias,
|
||||||
|
)
|
||||||
|
self.lm_expert.layers[layer_idx].self_attn.v_proj = nn.Linear(
|
||||||
|
config.text_config.num_key_value_heads * config.text_config.head_dim,
|
||||||
|
lm_expert_config.num_key_value_heads * lm_expert_config.head_dim,
|
||||||
|
bias=lm_expert_config.attention_bias,
|
||||||
|
)
|
||||||
|
# Remove unused embed_tokens
|
||||||
|
self.lm_expert.embed_tokens = None
|
||||||
|
|
||||||
|
self.num_attention_heads = self.config.text_config.num_attention_heads
|
||||||
|
self.num_key_value_heads = self.config.text_config.num_key_value_heads
|
||||||
|
|
||||||
|
self.freeze_vision_encoder = freeze_vision_encoder
|
||||||
|
self.train_expert_only = train_expert_only
|
||||||
|
self.attention_mode = attention_mode
|
||||||
|
self.expert_hidden_size = lm_expert_config.hidden_size
|
||||||
|
self.set_requires_grad()
|
||||||
|
|
||||||
|
def configure_peft(self, config):
|
||||||
|
# return model
|
||||||
|
self.peft_method = config.peft_method
|
||||||
|
self.peft_target_model = config.peft_target_model
|
||||||
|
if "lora" in self.peft_method:
|
||||||
|
peft_config = config.peft_config
|
||||||
|
target_modules = peft_config.target_modules
|
||||||
|
if not isinstance(target_modules, list):
|
||||||
|
target_modules = target_modules.split(",")
|
||||||
|
lora_config = LoraConfig(
|
||||||
|
task_type=TaskType.CAUSAL_LM, # Based on the task type (e.g., language modeling, etc.)
|
||||||
|
r=peft_config.r, # The rank of the low-rank adaptation
|
||||||
|
lora_alpha=peft_config.lora_alpha, # Scaling factor
|
||||||
|
lora_dropout=peft_config.lora_dropout, # Dropout applied to LoRA layers
|
||||||
|
target_modules=target_modules, # The components where LoRA is applied
|
||||||
|
exclude_modules=[
|
||||||
|
"lm_expert",
|
||||||
|
"model.lm_expert.model.layers",
|
||||||
|
], # FIXME(mshukor): this does not work for now
|
||||||
|
)
|
||||||
|
self.lora_config = lora_config
|
||||||
|
# Apply LoRA and ensure only LoRA parameters are trainable
|
||||||
|
if "text" in self.peft_target_model:
|
||||||
|
self.get_vlm_model().text_model = get_peft_model(self.get_vlm_model().text_model, lora_config)
|
||||||
|
else:
|
||||||
|
self.vlm = get_peft_model(self.vlm, lora_config)
|
||||||
|
# assert config.train_expert_only, "Backbone should be frozen and only lora parameters are " # FIXME(mshukor): handle this here?
|
||||||
|
for name, param in self.vlm.named_parameters():
|
||||||
|
if (
|
||||||
|
"lora" in name and "text_model.model.layers.17" not in name
|
||||||
|
): # lm_head is not a parameter in most LLMs becasue it's tied to the embedding layer
|
||||||
|
param.requires_grad = True
|
||||||
|
else:
|
||||||
|
param.requires_grad = False
|
||||||
|
|
||||||
|
def merge_lora_weights(self):
|
||||||
|
"""
|
||||||
|
Merge LoRA weights into the base model.
|
||||||
|
"""
|
||||||
|
if "text" in self.peft_target_model:
|
||||||
|
self.get_vlm_model().text_model = self.get_vlm_model().text_model.merge_and_unload()
|
||||||
|
else:
|
||||||
|
self.vlm = self.vlm.merge_and_unload()
|
||||||
|
|
||||||
|
def get_vlm_model(
|
||||||
|
self,
|
||||||
|
):
|
||||||
|
if hasattr(self.vlm.model, "model"): # When using peft
|
||||||
|
return self.vlm.model.model
|
||||||
|
else:
|
||||||
|
return self.vlm.model
|
||||||
|
|
||||||
|
def set_requires_grad(self):
|
||||||
|
if self.freeze_vision_encoder:
|
||||||
|
self.get_vlm_model().vision_model.eval()
|
||||||
|
for params in self.get_vlm_model().vision_model.parameters():
|
||||||
|
params.requires_grad = False
|
||||||
|
if self.train_expert_only:
|
||||||
|
self.vlm.eval()
|
||||||
|
for params in self.vlm.parameters():
|
||||||
|
params.requires_grad = False
|
||||||
|
else:
|
||||||
|
# To avoid unused params issue with distributed training
|
||||||
|
last_layers = [self.num_vlm_layers - 1]
|
||||||
|
if (
|
||||||
|
self.num_vlm_layers != self.num_expert_layers
|
||||||
|
and self.num_vlm_layers % self.num_expert_layers == 0
|
||||||
|
):
|
||||||
|
last_layers.append(self.num_vlm_layers - 2)
|
||||||
|
frozen_layers = [
|
||||||
|
"lm_head",
|
||||||
|
"text_model.model.norm.weight",
|
||||||
|
]
|
||||||
|
for layer in last_layers:
|
||||||
|
frozen_layers.append(f"text_model.model.layers.{layer}.")
|
||||||
|
|
||||||
|
for name, params in self.vlm.named_parameters():
|
||||||
|
if any(k in name for k in frozen_layers):
|
||||||
|
params.requires_grad = False
|
||||||
|
# To avoid unused params issue with distributed training
|
||||||
|
for name, params in self.lm_expert.named_parameters():
|
||||||
|
if "lm_head" in name:
|
||||||
|
params.requires_grad = False
|
||||||
|
|
||||||
|
def train(self, mode: bool = True):
|
||||||
|
super().train(mode)
|
||||||
|
|
||||||
|
if self.freeze_vision_encoder:
|
||||||
|
self.get_vlm_model().vision_model.eval()
|
||||||
|
|
||||||
|
if self.train_expert_only:
|
||||||
|
self.vlm.eval()
|
||||||
|
|
||||||
|
def embed_image(self, image: torch.Tensor):
|
||||||
|
patch_attention_mask = None
|
||||||
|
# Get sequence from the vision encoder
|
||||||
|
image_hidden_states = (
|
||||||
|
self.get_vlm_model()
|
||||||
|
.vision_model(
|
||||||
|
pixel_values=image.to(dtype=self.get_vlm_model().vision_model.dtype),
|
||||||
|
patch_attention_mask=patch_attention_mask,
|
||||||
|
)
|
||||||
|
.last_hidden_state
|
||||||
|
)
|
||||||
|
# Modality projection & resampling
|
||||||
|
image_hidden_states = self.get_vlm_model().connector(image_hidden_states)
|
||||||
|
return image_hidden_states
|
||||||
|
|
||||||
|
def embed_language_tokens(self, tokens: torch.Tensor):
|
||||||
|
return self.get_vlm_model().text_model.get_input_embeddings()(tokens)
|
||||||
|
|
||||||
|
def forward_attn_layer(
|
||||||
|
self,
|
||||||
|
model_layers,
|
||||||
|
inputs_embeds,
|
||||||
|
layer_idx,
|
||||||
|
position_ids,
|
||||||
|
attention_mask,
|
||||||
|
batch_size,
|
||||||
|
head_dim,
|
||||||
|
use_cache: bool = True,
|
||||||
|
fill_kv_cache: bool = True,
|
||||||
|
past_key_values=None,
|
||||||
|
) -> list[torch.Tensor]:
|
||||||
|
query_states = []
|
||||||
|
key_states = []
|
||||||
|
value_states = []
|
||||||
|
for i, hidden_states in enumerate(inputs_embeds):
|
||||||
|
layer = model_layers[i][layer_idx]
|
||||||
|
if hidden_states is None or layer is None:
|
||||||
|
continue
|
||||||
|
hidden_states = layer.input_layernorm(hidden_states)
|
||||||
|
|
||||||
|
input_shape = hidden_states.shape[:-1]
|
||||||
|
hidden_shape = (*input_shape, -1, layer.self_attn.head_dim)
|
||||||
|
|
||||||
|
hidden_states = hidden_states.to(dtype=layer.self_attn.q_proj.weight.dtype)
|
||||||
|
query_state = layer.self_attn.q_proj(hidden_states).view(hidden_shape)
|
||||||
|
key_state = layer.self_attn.k_proj(hidden_states).view(hidden_shape)
|
||||||
|
value_state = layer.self_attn.v_proj(hidden_states).view(hidden_shape)
|
||||||
|
|
||||||
|
query_states.append(query_state)
|
||||||
|
key_states.append(key_state)
|
||||||
|
value_states.append(value_state)
|
||||||
|
|
||||||
|
# B,L,H,D with L sequence length, H number of heads, D head dim
|
||||||
|
# concatenate on the number of embeddings/tokens
|
||||||
|
query_states = torch.cat(query_states, dim=1)
|
||||||
|
key_states = torch.cat(key_states, dim=1)
|
||||||
|
value_states = torch.cat(value_states, dim=1)
|
||||||
|
seq_len = query_states.shape[1]
|
||||||
|
if seq_len < position_ids.shape[1]:
|
||||||
|
_position_ids = position_ids[:, :seq_len]
|
||||||
|
_attention_mask = attention_mask[:, :seq_len, :seq_len]
|
||||||
|
else:
|
||||||
|
_position_ids = position_ids
|
||||||
|
_attention_mask = attention_mask
|
||||||
|
|
||||||
|
attention_mask_ = _attention_mask
|
||||||
|
position_ids_ = _position_ids
|
||||||
|
|
||||||
|
query_states = apply_rope(query_states, position_ids_)
|
||||||
|
key_states = apply_rope(key_states, position_ids_)
|
||||||
|
|
||||||
|
if use_cache and past_key_values is None:
|
||||||
|
past_key_values = {}
|
||||||
|
|
||||||
|
if use_cache:
|
||||||
|
if fill_kv_cache:
|
||||||
|
past_key_values[layer_idx] = {
|
||||||
|
"key_states": key_states,
|
||||||
|
"value_states": value_states,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
# TODO here, some optimization can be done - similar to a `StaticCache` we can declare the `max_len` before.
|
||||||
|
# so we create an empty cache, with just one cuda malloc, and if (in autoregressive case) we reach
|
||||||
|
# the max len, then we (for instance) double the cache size. This implementation already exists
|
||||||
|
# in `transformers`. (molbap)
|
||||||
|
key_states = torch.cat([past_key_values[layer_idx]["key_states"], key_states], dim=1)
|
||||||
|
value_states = torch.cat([past_key_values[layer_idx]["value_states"], value_states], dim=1)
|
||||||
|
|
||||||
|
attention_interface = self.get_attention_interface()
|
||||||
|
|
||||||
|
att_output = attention_interface(
|
||||||
|
attention_mask_, batch_size, head_dim, query_states, key_states, value_states
|
||||||
|
)
|
||||||
|
return [att_output], past_key_values
|
||||||
|
|
||||||
|
def forward_cross_attn_layer(
|
||||||
|
self,
|
||||||
|
model_layers,
|
||||||
|
inputs_embeds,
|
||||||
|
layer_idx,
|
||||||
|
position_ids,
|
||||||
|
attention_mask,
|
||||||
|
batch_size,
|
||||||
|
head_dim,
|
||||||
|
use_cache: bool = True,
|
||||||
|
fill_kv_cache: bool = True,
|
||||||
|
past_key_values=None,
|
||||||
|
) -> list[torch.Tensor]:
|
||||||
|
attention_interface = self.get_attention_interface()
|
||||||
|
|
||||||
|
att_outputs = []
|
||||||
|
assert len(inputs_embeds) == 2 or (use_cache and past_key_values is not None and not fill_kv_cache), (
|
||||||
|
f"Both len(inputs_embeds) == {len(inputs_embeds)} and past_key_values is {past_key_values}"
|
||||||
|
)
|
||||||
|
|
||||||
|
if len(inputs_embeds) == 2 and not past_key_values:
|
||||||
|
# Prefix attention
|
||||||
|
seq_len = inputs_embeds[0].shape[1]
|
||||||
|
position_id, expert_position_id = position_ids[:, :seq_len], position_ids[:, seq_len:]
|
||||||
|
prefix_attention_mask = attention_mask[:, :seq_len, :seq_len]
|
||||||
|
|
||||||
|
layer = model_layers[0][layer_idx]
|
||||||
|
|
||||||
|
hidden_states = layer.input_layernorm(inputs_embeds[0])
|
||||||
|
|
||||||
|
input_shape = hidden_states.shape[:-1]
|
||||||
|
hidden_shape = (*input_shape, -1, layer.self_attn.head_dim)
|
||||||
|
|
||||||
|
hidden_states = hidden_states.to(dtype=layer.self_attn.q_proj.weight.dtype)
|
||||||
|
query_state = layer.self_attn.q_proj(hidden_states).view(hidden_shape)
|
||||||
|
key_state = layer.self_attn.k_proj(hidden_states).view(hidden_shape)
|
||||||
|
value_states = layer.self_attn.v_proj(hidden_states).view(hidden_shape)
|
||||||
|
|
||||||
|
# B,L,H,D with L sequence length, H number of heads, D head dim
|
||||||
|
query_states = apply_rope(query_state, position_id)
|
||||||
|
key_states = apply_rope(key_state, position_id)
|
||||||
|
|
||||||
|
att_output = attention_interface(
|
||||||
|
prefix_attention_mask, batch_size, head_dim, query_states, key_states, value_states
|
||||||
|
)
|
||||||
|
att_outputs.append(att_output)
|
||||||
|
else:
|
||||||
|
expert_position_id = position_ids
|
||||||
|
|
||||||
|
if use_cache and past_key_values is None:
|
||||||
|
past_key_values = {}
|
||||||
|
|
||||||
|
if use_cache:
|
||||||
|
if fill_kv_cache:
|
||||||
|
past_key_values[layer_idx] = {
|
||||||
|
"key_states": key_states,
|
||||||
|
"value_states": value_states,
|
||||||
|
}
|
||||||
|
else:
|
||||||
|
# TODO here, some optimization can be done - similar to a `StaticCache` we can declare the `max_len` before.
|
||||||
|
# so we create an empty cache, with just one cuda malloc, and if (in autoregressive case) we reach
|
||||||
|
# the max len, then we (for instance) double the cache size. This implementation already exists
|
||||||
|
# in `transformers`. (molbap)
|
||||||
|
key_states = past_key_values[layer_idx]["key_states"]
|
||||||
|
value_states = past_key_values[layer_idx]["value_states"]
|
||||||
|
|
||||||
|
# Expert
|
||||||
|
expert_layer = model_layers[1][layer_idx]
|
||||||
|
if expert_layer is not None:
|
||||||
|
expert_hidden_states = expert_layer.input_layernorm(inputs_embeds[1])
|
||||||
|
|
||||||
|
expert_input_shape = expert_hidden_states.shape[:-1]
|
||||||
|
expert_hidden_shape = (*expert_input_shape, -1, expert_layer.self_attn.head_dim)
|
||||||
|
|
||||||
|
expert_hidden_states = expert_hidden_states.to(dtype=expert_layer.self_attn.q_proj.weight.dtype)
|
||||||
|
expert_query_state = expert_layer.self_attn.q_proj(expert_hidden_states).view(expert_hidden_shape)
|
||||||
|
|
||||||
|
_key_states = key_states.to(dtype=expert_layer.self_attn.k_proj.weight.dtype).view(
|
||||||
|
*key_states.shape[:2], -1
|
||||||
|
)
|
||||||
|
expert_key_states = expert_layer.self_attn.k_proj(_key_states).view(
|
||||||
|
*_key_states.shape[:-1], -1, expert_layer.self_attn.head_dim
|
||||||
|
) # k_proj should have same dim as kv
|
||||||
|
|
||||||
|
_value_states = value_states.to(dtype=expert_layer.self_attn.v_proj.weight.dtype).view(
|
||||||
|
*value_states.shape[:2], -1
|
||||||
|
)
|
||||||
|
expert_value_states = expert_layer.self_attn.v_proj(_value_states).view(
|
||||||
|
*_value_states.shape[:-1], -1, expert_layer.self_attn.head_dim
|
||||||
|
)
|
||||||
|
|
||||||
|
expert_position_id = (
|
||||||
|
expert_position_id - torch.min(expert_position_id, dim=1, keepdim=True).values
|
||||||
|
) # start from 0
|
||||||
|
expert_attention_mask = attention_mask[
|
||||||
|
:, -inputs_embeds[1].shape[1] :, : expert_key_states.shape[1] :
|
||||||
|
] # take into account kv
|
||||||
|
|
||||||
|
expert_query_states = apply_rope(expert_query_state, expert_position_id)
|
||||||
|
|
||||||
|
att_output = attention_interface(
|
||||||
|
expert_attention_mask,
|
||||||
|
batch_size,
|
||||||
|
head_dim,
|
||||||
|
expert_query_states,
|
||||||
|
expert_key_states,
|
||||||
|
expert_value_states,
|
||||||
|
)
|
||||||
|
att_outputs.append(att_output)
|
||||||
|
else:
|
||||||
|
att_outputs.append(None)
|
||||||
|
|
||||||
|
# att_output = att_output.to(dtype=models[i].dtype)
|
||||||
|
return att_outputs, past_key_values
|
||||||
|
|
||||||
|
def get_model_layers(self, models: list) -> list:
|
||||||
|
vlm_layers = []
|
||||||
|
expert_layers = []
|
||||||
|
multiple_of = self.num_vlm_layers // self.num_expert_layers
|
||||||
|
for i in range(self.num_vlm_layers):
|
||||||
|
if multiple_of > 0 and i > 0 and i % multiple_of != 0:
|
||||||
|
expert_layer = None
|
||||||
|
else:
|
||||||
|
expert_layer_index = i // multiple_of if multiple_of > 0 else i
|
||||||
|
expert_layer = models[1].layers[expert_layer_index]
|
||||||
|
vlm_layers.append(models[0].layers[i])
|
||||||
|
expert_layers.append(expert_layer)
|
||||||
|
return [vlm_layers, expert_layers]
|
||||||
|
|
||||||
|
def forward(
|
||||||
|
self,
|
||||||
|
attention_mask: Optional[torch.Tensor] = None,
|
||||||
|
position_ids: Optional[torch.LongTensor] = None,
|
||||||
|
past_key_values: Optional[List[torch.FloatTensor]] = None,
|
||||||
|
inputs_embeds: List[torch.FloatTensor] = None,
|
||||||
|
use_cache: Optional[bool] = None,
|
||||||
|
fill_kv_cache: Optional[bool] = None,
|
||||||
|
):
|
||||||
|
models = [self.get_vlm_model().text_model, self.lm_expert]
|
||||||
|
model_layers = self.get_model_layers(models)
|
||||||
|
for hidden_states in inputs_embeds:
|
||||||
|
# TODO this is very inefficient
|
||||||
|
# dtype is always the same, batch size too (if > 1 len)
|
||||||
|
# device could be trickier in multi gpu edge cases but that's it
|
||||||
|
if hidden_states is None:
|
||||||
|
continue
|
||||||
|
batch_size = hidden_states.shape[0]
|
||||||
|
|
||||||
|
# RMSNorm
|
||||||
|
num_layers = self.num_vlm_layers
|
||||||
|
head_dim = self.vlm.config.text_config.head_dim
|
||||||
|
for layer_idx in range(num_layers):
|
||||||
|
if (
|
||||||
|
fill_kv_cache
|
||||||
|
or "cross" not in self.attention_mode
|
||||||
|
or (self.self_attn_every_n_layers > 0 and layer_idx % self.self_attn_every_n_layers == 0)
|
||||||
|
):
|
||||||
|
att_outputs, past_key_values = self.forward_attn_layer(
|
||||||
|
model_layers,
|
||||||
|
inputs_embeds,
|
||||||
|
layer_idx,
|
||||||
|
position_ids,
|
||||||
|
attention_mask,
|
||||||
|
batch_size,
|
||||||
|
head_dim,
|
||||||
|
use_cache=use_cache,
|
||||||
|
fill_kv_cache=fill_kv_cache,
|
||||||
|
past_key_values=past_key_values,
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
att_outputs, past_key_values = self.forward_cross_attn_layer(
|
||||||
|
model_layers,
|
||||||
|
inputs_embeds,
|
||||||
|
layer_idx,
|
||||||
|
position_ids,
|
||||||
|
attention_mask,
|
||||||
|
batch_size,
|
||||||
|
head_dim,
|
||||||
|
use_cache=use_cache,
|
||||||
|
fill_kv_cache=fill_kv_cache,
|
||||||
|
past_key_values=past_key_values,
|
||||||
|
)
|
||||||
|
outputs_embeds = []
|
||||||
|
start = 0
|
||||||
|
for i, hidden_states in enumerate(inputs_embeds):
|
||||||
|
layer = model_layers[i][layer_idx]
|
||||||
|
att_output = (
|
||||||
|
att_outputs[i] if i < len(att_outputs) else att_outputs[0]
|
||||||
|
) # in case of self_attn
|
||||||
|
if hidden_states is not None:
|
||||||
|
if layer is None:
|
||||||
|
outputs_embeds.append(hidden_states)
|
||||||
|
continue
|
||||||
|
end = start + hidden_states.shape[1]
|
||||||
|
|
||||||
|
if att_output.dtype != layer.self_attn.o_proj.weight.dtype:
|
||||||
|
att_output = att_output.to(layer.self_attn.o_proj.weight.dtype)
|
||||||
|
att_out = att_output[:, start:end]
|
||||||
|
out_emb = layer.self_attn.o_proj(att_out)
|
||||||
|
|
||||||
|
out_emb += hidden_states
|
||||||
|
after_first_residual = out_emb.clone()
|
||||||
|
|
||||||
|
out_emb = layer.post_attention_layernorm(out_emb)
|
||||||
|
out_emb = layer.mlp(out_emb)
|
||||||
|
|
||||||
|
out_emb += after_first_residual
|
||||||
|
|
||||||
|
outputs_embeds.append(out_emb)
|
||||||
|
|
||||||
|
start = end if len(att_outputs) == 1 else 0
|
||||||
|
else:
|
||||||
|
outputs_embeds.append(None)
|
||||||
|
|
||||||
|
inputs_embeds = outputs_embeds
|
||||||
|
|
||||||
|
# final norm
|
||||||
|
outputs_embeds = []
|
||||||
|
for i, hidden_states in enumerate(inputs_embeds):
|
||||||
|
if hidden_states is not None:
|
||||||
|
out_emb = models[i].norm(hidden_states)
|
||||||
|
outputs_embeds.append(out_emb)
|
||||||
|
else:
|
||||||
|
outputs_embeds.append(None)
|
||||||
|
return outputs_embeds, past_key_values
|
||||||
|
|
||||||
|
def get_attention_interface(self):
|
||||||
|
attention_interface = self.eager_attention_forward
|
||||||
|
return attention_interface
|
||||||
|
|
||||||
|
def eager_attention_forward(
|
||||||
|
self, attention_mask, batch_size, head_dim, query_states, key_states, value_states
|
||||||
|
):
|
||||||
|
num_att_heads = self.num_attention_heads
|
||||||
|
num_key_value_heads = self.num_key_value_heads
|
||||||
|
num_key_value_groups = num_att_heads // num_key_value_heads
|
||||||
|
|
||||||
|
sequence_length = key_states.shape[1]
|
||||||
|
|
||||||
|
key_states = key_states[:, :, :, None, :].expand(
|
||||||
|
batch_size, sequence_length, num_key_value_heads, num_key_value_groups, head_dim
|
||||||
|
)
|
||||||
|
key_states = key_states.reshape(
|
||||||
|
batch_size, sequence_length, num_key_value_heads * num_key_value_groups, head_dim
|
||||||
|
)
|
||||||
|
|
||||||
|
value_states = value_states[:, :, :, None, :].expand(
|
||||||
|
batch_size, sequence_length, num_key_value_heads, num_key_value_groups, head_dim
|
||||||
|
)
|
||||||
|
value_states = value_states.reshape(
|
||||||
|
batch_size, sequence_length, num_key_value_heads * num_key_value_groups, head_dim
|
||||||
|
)
|
||||||
|
|
||||||
|
# Attention here is upcasted to float32 to match the original eager implementation.
|
||||||
|
query_states = query_states.to(dtype=torch.float32)
|
||||||
|
key_states = key_states.to(dtype=torch.float32)
|
||||||
|
|
||||||
|
query_states = query_states.transpose(1, 2)
|
||||||
|
key_states = key_states.transpose(1, 2)
|
||||||
|
|
||||||
|
att_weights = torch.matmul(query_states, key_states.transpose(2, 3))
|
||||||
|
att_weights *= head_dim**-0.5
|
||||||
|
|
||||||
|
att_weights = att_weights.to(dtype=torch.float32)
|
||||||
|
big_neg = torch.finfo(att_weights.dtype).min # -2.3819763e38 # See gemma/modules.py
|
||||||
|
masked_att_weights = torch.where(attention_mask[:, None, :, :], att_weights, big_neg)
|
||||||
|
probs = nn.functional.softmax(masked_att_weights, dim=-1)
|
||||||
|
probs = probs.to(dtype=value_states.dtype)
|
||||||
|
|
||||||
|
att_output = torch.matmul(probs, value_states.permute(0, 2, 1, 3))
|
||||||
|
|
||||||
|
att_output = att_output.permute(0, 2, 1, 3)
|
||||||
|
# we use -1 because sequence length can change
|
||||||
|
att_output = att_output.reshape(batch_size, -1, num_key_value_heads * num_key_value_groups * head_dim)
|
||||||
|
|
||||||
|
return att_output
|
||||||
Reference in New Issue
Block a user