Spaces:
Sleeping
Sleeping
# Adopted from https://github.com/haotian-liu/LLaVA. Below is the original copyright: | |
# Copyright 2023 Haotian Liu | |
# | |
# 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 logging | |
import logging.handlers | |
import os | |
import sys | |
import cv2 | |
import numpy as np | |
import torch | |
import torch.distributed as dist | |
import transformers | |
from egogpt.constants import LOGDIR | |
try: | |
import av | |
from decord import VideoReader, cpu | |
except ImportError: | |
print("Please install pyav to use video processing functions.") | |
server_error_msg = ( | |
"**NETWORK ERROR DUE TO HIGH TRAFFIC. PLEASE REGENERATE OR REFRESH THIS PAGE.**" | |
) | |
moderation_msg = ( | |
"YOUR INPUT VIOLATES OUR CONTENT MODERATION GUIDELINES. PLEASE TRY AGAIN." | |
) | |
handler = None | |
def build_logger(logger_name, logger_filename): | |
global handler | |
formatter = logging.Formatter( | |
fmt="%(asctime)s | %(levelname)s | %(name)s | %(message)s", | |
datefmt="%Y-%m-%d %H:%M:%S", | |
) | |
# Set the format of root handlers | |
if not logging.getLogger().handlers: | |
logging.basicConfig(level=logging.INFO) | |
logging.getLogger().handlers[0].setFormatter(formatter) | |
# Redirect stdout and stderr to loggers | |
stdout_logger = logging.getLogger("stdout") | |
stdout_logger.setLevel(logging.INFO) | |
sl = StreamToLogger(stdout_logger, logging.INFO) | |
sys.stdout = sl | |
stderr_logger = logging.getLogger("stderr") | |
stderr_logger.setLevel(logging.ERROR) | |
sl = StreamToLogger(stderr_logger, logging.ERROR) | |
sys.stderr = sl | |
# Get logger | |
logger = logging.getLogger(logger_name) | |
logger.setLevel(logging.INFO) | |
# Add a file handler for all loggers | |
if handler is None: | |
os.makedirs(LOGDIR, exist_ok=True) | |
filename = os.path.join(LOGDIR, logger_filename) | |
handler = logging.handlers.TimedRotatingFileHandler( | |
filename, when="D", utc=True, encoding="UTF-8" | |
) | |
handler.setFormatter(formatter) | |
for name, item in logging.root.manager.loggerDict.items(): | |
if isinstance(item, logging.Logger): | |
item.addHandler(handler) | |
return logger | |
def process_video_with_decord(video_file, data_args): | |
vr = VideoReader(video_file, ctx=cpu(0), num_threads=1) | |
total_frame_num = len(vr) | |
video_time = total_frame_num / vr.get_avg_fps() | |
avg_fps = round(vr.get_avg_fps() / data_args.video_fps) | |
frame_idx = [i for i in range(0, total_frame_num, avg_fps)] | |
frame_time = [i / avg_fps for i in frame_idx] | |
if data_args.frames_upbound > 0: | |
if len(frame_idx) > data_args.frames_upbound or data_args.force_sample: | |
uniform_sampled_frames = np.linspace( | |
0, total_frame_num - 1, data_args.frames_upbound, dtype=int | |
) | |
frame_idx = uniform_sampled_frames.tolist() | |
frame_time = [i / vr.get_avg_fps() for i in frame_idx] | |
frames = vr.get_batch(frame_idx).asnumpy() | |
# resized_frames = np.array([cv2.resize(frame, (384, 384)) for frame in frames]) | |
# video = resized_frames | |
video = frames | |
frame_time = ",".join([f"{i:.2f}s" for i in frame_time]) | |
num_frames_to_sample = num_frames = len(frame_idx) | |
# https://github.com/dmlc/decord/issues/208 | |
vr.seek(0) | |
return video, video_time, frame_time, num_frames_to_sample | |
def process_video_with_decord_byframe( | |
video_file, start_frame, end_frame, data_args, current_observation_frame=None | |
): | |
try: | |
vr = VideoReader(video_file, ctx=cpu(0), num_threads=1) | |
total_frame_num = len(vr) | |
selected_frame = min(total_frame_num - 1, end_frame) | |
avg_fps = round(vr.get_avg_fps() / data_args.video_fps) | |
frame_idx = [i for i in range(start_frame, selected_frame, avg_fps)] | |
if data_args.frames_upbound > 0: | |
if len(frame_idx) > data_args.frames_upbound: | |
uniform_sampled_frames = np.linspace( | |
start_frame, selected_frame, data_args.frames_upbound, dtype=int | |
) | |
frame_idx = uniform_sampled_frames.tolist() | |
if current_observation_frame: | |
frame_idx.append(current_observation_frame) | |
video = vr.get_batch(frame_idx).asnumpy() | |
# https://github.com/dmlc/decord/issues/208 | |
vr.seek(0) | |
except: | |
raise SyntaxError("Video processing error") | |
return video | |
class StreamToLogger(object): | |
""" | |
Fake file-like stream object that redirects writes to a logger instance. | |
""" | |
def __init__(self, logger, log_level=logging.INFO): | |
self.terminal = sys.stdout | |
self.logger = logger | |
self.log_level = log_level | |
self.linebuf = "" | |
def __getattr__(self, attr): | |
return getattr(self.terminal, attr) | |
def write(self, buf): | |
temp_linebuf = self.linebuf + buf | |
self.linebuf = "" | |
for line in temp_linebuf.splitlines(True): | |
# From the io.TextIOWrapper docs: | |
# On output, if newline is None, any '\n' characters written | |
# are translated to the system default line separator. | |
# By default sys.stdout.write() expects '\n' newlines and then | |
# translates them so this is still cross platform. | |
if line[-1] == "\n": | |
self.logger.log(self.log_level, line.rstrip()) | |
else: | |
self.linebuf += line | |
def flush(self): | |
if self.linebuf != "": | |
self.logger.log(self.log_level, self.linebuf.rstrip()) | |
self.linebuf = "" | |
def maybe_zero_3(param, ignore_status=False, name=None): | |
from deepspeed import zero | |
from deepspeed.runtime.zero.partition_parameters import ZeroParamStatus | |
if hasattr(param, "ds_id"): | |
if param.ds_status == ZeroParamStatus.NOT_AVAILABLE: | |
if not ignore_status: | |
logging.warning( | |
f"{name}: param.ds_status != ZeroParamStatus.NOT_AVAILABLE: {param.ds_status}" | |
) | |
with zero.GatheredParameters([param]): | |
param = param.data.detach().cpu().clone() | |
else: | |
param = param.detach().cpu().clone() | |
return param | |
# Borrowed from peft.utils.get_peft_model_state_dict | |
def get_peft_state_maybe_zero_3(named_params, bias): | |
if bias == "none": | |
to_return = {k: t for k, t in named_params if "lora_" in k} | |
elif bias == "all": | |
to_return = {k: t for k, t in named_params if "lora_" in k or "bias" in k} | |
elif bias == "lora_only": | |
to_return = {} | |
maybe_lora_bias = {} | |
lora_bias_names = set() | |
for k, t in named_params: | |
if "lora_" in k: | |
to_return[k] = t | |
bias_name = k.split("lora_")[0] + "bias" | |
lora_bias_names.add(bias_name) | |
elif "bias" in k: | |
maybe_lora_bias[k] = t | |
for k, t in maybe_lora_bias: | |
if bias_name in lora_bias_names: | |
to_return[bias_name] = t | |
else: | |
raise NotImplementedError | |
to_return = {k: maybe_zero_3(v, ignore_status=True) for k, v in to_return.items()} | |
return to_return | |
def get_peft_state_non_lora_maybe_zero_3(named_params, require_grad_only=True): | |
to_return = {k: t for k, t in named_params if "lora_" not in k} | |
if require_grad_only: | |
to_return = {k: t for k, t in to_return.items() if t.requires_grad} | |
to_return = { | |
k: maybe_zero_3(v, ignore_status=True).cpu() for k, v in to_return.items() | |
} | |
return to_return | |
def get_speech_projector_state_maybe_zero_3(named_params, keys_to_match): | |
to_return = { | |
k: t | |
for k, t in named_params | |
if any(key_match in k for key_match in keys_to_match) | |
} | |
to_return = { | |
k: maybe_zero_3(v, ignore_status=True).cpu() for k, v in to_return.items() | |
} | |
return to_return | |
def find_all_linear_names(model): | |
cls = torch.nn.Linear | |
lora_module_names = set() | |
speech_keywords = ["speech_projector", "speech_encoder"] | |
for name, module in model.named_modules(): | |
if any(speech_keyword in name for speech_keyword in speech_keywords): | |
continue | |
if isinstance(module, cls): | |
names = name.split(".") | |
lora_module_names.add(names[0] if len(names) == 1 else names[-1]) | |
if "lm_head" in lora_module_names: # needed for 16-bit | |
lora_module_names.remove("lm_head") | |
return list(lora_module_names) | |
def rank0_print(*args): | |
if dist.is_initialized(): | |
if dist.get_rank() == 0: | |
print(f"Rank {dist.get_rank()}: ", *args) | |
else: | |
print(*args) | |
def rank_print(*args): | |
if dist.is_initialized(): | |
print(f"Rank {dist.get_rank()}: ", *args) | |
else: | |
print(*args) | |
def safe_save_model_for_hf_trainer(trainer: transformers.Trainer, output_dir: str): | |
"""Collects the state dict and dump to disk.""" | |
if getattr(trainer.args, "tune_speech_projector", False): | |
# Only save projector | |
keys_to_match = ["speech_projector"] | |
if getattr(trainer.args, "use_im_start_end", False): | |
keys_to_match.extend(["embed_tokens", "embed_in"]) | |
weight_to_save = get_speech_projector_state_maybe_zero_3( | |
trainer.model.named_parameters(), keys_to_match | |
) | |
trainer.model.config.save_pretrained(output_dir) | |
current_folder = output_dir.split("/")[-1] | |
parent_folder = os.path.dirname(output_dir) | |
if trainer.args.local_rank == 0 or trainer.args.local_rank == -1: | |
if current_folder.startswith("checkpoint-"): | |
speech_projector_folder = os.path.join( | |
parent_folder, "speech_projector" | |
) | |
os.makedirs(speech_projector_folder, exist_ok=True) | |
torch.save( | |
weight_to_save, | |
os.path.join(speech_projector_folder, f"{current_folder}.bin"), | |
) | |
else: | |
torch.save( | |
weight_to_save, os.path.join(output_dir, f"speech_projector.bin") | |
) | |
return | |
if trainer.deepspeed: | |
torch.cuda.synchronize() | |
trainer.save_model(output_dir) | |
return | |
state_dict = trainer.model.state_dict() | |
if trainer.args.should_save: | |
cpu_state_dict = {key: value.cpu() for key, value in state_dict.items()} | |
del state_dict | |
trainer._save(output_dir, state_dict=cpu_state_dict) # noqa | |
def lengths_to_padding_mask(lens): | |
bsz, max_lens = lens.size(0), torch.max(lens).item() | |
mask = torch.arange(max_lens).to(lens.device).view(1, max_lens) | |
mask = mask.expand(bsz, -1) >= lens.view(bsz, 1).expand(-1, max_lens) | |
return mask | |
def lengths_to_mask(lens): | |
return ~lengths_to_padding_mask(lens) | |
def disable_torch_init(): | |
""" | |
Disable the redundant torch default initialization to accelerate model creation. | |
""" | |
import torch | |
setattr(torch.nn.Linear, "reset_parameters", lambda self: None) | |
setattr(torch.nn.LayerNorm, "reset_parameters", lambda self: None) | |
def get_model_name_from_path(model_path): | |
model_path = model_path.strip("/") | |
model_paths = model_path.split("/") | |
if model_paths[-1].startswith("checkpoint-"): | |
return model_paths[-2] + "_" + model_paths[-1] | |
else: | |
return model_paths[-1] | |
def violates_moderation(text): | |
""" | |
Check whether the text violates OpenAI moderation API. | |
""" | |
url = "https://api.openai.com/v1/moderations" | |
headers = { | |
"Content-Type": "application/json", | |
"Authorization": "Bearer " + os.environ["OPENAI_API_KEY"], | |
} | |
text = text.replace("\n", "") | |
data = "{" + '"input": ' + f'"{text}"' + "}" | |
data = data.encode("utf-8") | |
try: | |
ret = requests.post(url, headers=headers, data=data, timeout=5) | |
flagged = ret.json()["results"][0]["flagged"] | |
except requests.exceptions.RequestException as e: | |
flagged = False | |
except KeyError as e: | |
flagged = False | |
return flagged | |
def pretty_print_semaphore(semaphore): | |
if semaphore is None: | |
return "None" | |
return f"Semaphore(value={semaphore._value}, locked={semaphore.locked()})" | |