feat(CRITICAL): 从 GitHub 扫描搬运 ixformer SDK + xllm 完整 GDN/MoE 代码
来源:
1. Chranos/ixformer (GitHub) → ixformer_sdk/ (230 files, 70K lines)
- inference/functions/vllm.py: vllm_moe_topk_softmax 完整实现 (2033 lines)
- inference/functions/moe.py: MoE ops 完整实现 (1380 lines)
- contrib/vllm_flash_attn/: FA2 Python 接口 (1018 lines)
- contrib/tgi/fused_moe.py: TGI fused MoE (429 lines)
- csrc/include/ixformer/: C++ kernel headers + cmake
2. Deep-Spark/xllm (GitHub) → upstream_ref/xllm_latest/ (+15 files)
- npu_torch/qwen3_5_decoder_layer_impl.cpp/.h
- npu_torch/qwen3_5_gated_delta_net.cpp/.h
- npu_torch/qwen3_next_*.cpp/.h (6 files)
- npu_torch/attention.cpp/.h + fused_moe.cpp/.h + CMakeLists.txt
- models/llm/qwen3_5.h + qwen3_5_mtp.h + qwen3_next.h
- models/vlm/qwen3_5.h
调用链完整性:
ixformer_sdk/inference/functions/vllm.py
→ ops.infer.moe_topk_softmax() (C++ 层)
→ 这就是 base 镜像 libixformer.so 里的实现
upstream_ref/xllm_latest/core/layers/ilu/fused_moe.cpp
→ ixformer::infer::topk_softmax() (直接 C++ 调用)
→ ixformer::infer::group_gemm() → 完整 7-step MoE pipeline
This commit is contained in:
2
ixformer_sdk/.gitignore
vendored
Normal file
2
ixformer_sdk/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
*.so
|
||||
build/
|
||||
2
ixformer_sdk/__init__.py
Normal file
2
ixformer_sdk/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
import torch
|
||||
from .functions import *
|
||||
5
ixformer_sdk/contrib/DeepCache/__init__.py
Normal file
5
ixformer_sdk/contrib/DeepCache/__init__.py
Normal file
@@ -0,0 +1,5 @@
|
||||
from .sd.pipeline_stable_diffusion import StableDiffusionPipeline
|
||||
from .sdxl.pipeline_stable_diffusion_xl import StableDiffusionXLPipeline
|
||||
from .sdxl.pipeline_stable_diffusion_xl_img2img import StableDiffusionXLImg2ImgPipeline
|
||||
|
||||
from .sd.pipeline_text_to_video_zero import TextToVideoZeroPipeline
|
||||
0
ixformer_sdk/contrib/DeepCache/ddpm/__init__.py
Normal file
0
ixformer_sdk/contrib/DeepCache/ddpm/__init__.py
Normal file
180
ixformer_sdk/contrib/DeepCache/ddpm/ddim.py
Normal file
180
ixformer_sdk/contrib/DeepCache/ddpm/ddim.py
Normal file
@@ -0,0 +1,180 @@
|
||||
import argparse
|
||||
import traceback
|
||||
import shutil
|
||||
import logging
|
||||
import yaml
|
||||
import random
|
||||
import sys
|
||||
import os
|
||||
import torch
|
||||
import numpy as np
|
||||
|
||||
from ddpm.utils.logging import Logger, EmptyLogger
|
||||
from ddpm.utils.tools import set_random_seed
|
||||
from accelerate import Accelerator, DistributedDataParallelKwargs
|
||||
|
||||
torch.set_printoptions(sci_mode=False)
|
||||
|
||||
def dict2namespace(config):
|
||||
namespace = argparse.Namespace()
|
||||
for key, value in config.items():
|
||||
if isinstance(value, dict):
|
||||
new_value = dict2namespace(value)
|
||||
else:
|
||||
new_value = value
|
||||
setattr(namespace, key, new_value)
|
||||
return namespace
|
||||
|
||||
def parse_args_and_config():
|
||||
parser = argparse.ArgumentParser(description=globals()["__doc__"])
|
||||
|
||||
parser.add_argument(
|
||||
"--config", type=str, required=True, help="Path to the config file"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--seed", type=int, default=1234, help="Random seed")
|
||||
parser.add_argument(
|
||||
"--exp", type=str, default="exp", help="Path for saving running related data."
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--test", action="store_true", help="Whether to test the model"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--sample", action="store_true", help="Whether to produce samples from the model",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--image_folder", type=str, default="images", help="folder name for storing the sampled images"
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--fid", action="store_true"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--interpolation", action="store_true"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--resume_training", action="store_true", help="Whether to resume training"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--ni", action="store_true", help="No interaction. Suitable for Slurm Job launcher",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--use_pretrained", action="store_true"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--sample_type", type=str, default="generalized", help="sampling approach (generalized or ddpm_noisy)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--skip_type", type=str, default="uniform", help="skip according to (uniform or quadratic)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--timesteps", type=int, default=1000, help="number of steps involved"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--eta", type=float, default=0.0, help="eta used to control the variances of sigma",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--dyn", action="store_true", help="whether to activate the dynamic train/inference"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--sequence", action="store_true"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--select_step", type=int, default=None
|
||||
)
|
||||
parser.add_argument(
|
||||
"--select_depth", type=int, default=None
|
||||
)
|
||||
|
||||
parser.add_argument(
|
||||
"--cache", action="store_true"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--cache_interval", type=int, default=None,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--non_uniform", action="store_true"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--pow", type=float, default=None,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--center", type=int, default=None,
|
||||
)
|
||||
parser.add_argument(
|
||||
"--branch", type=int, default=None,
|
||||
)
|
||||
args = parser.parse_args()
|
||||
# parse config file
|
||||
with open(args.config, "r") as f:
|
||||
config = yaml.safe_load(f)
|
||||
new_config = dict2namespace(config)
|
||||
new_config.select_step = args.select_step
|
||||
new_config.select_depth = args.select_depth
|
||||
|
||||
torch.backends.cudnn.benchmark = True
|
||||
|
||||
return args, new_config
|
||||
|
||||
|
||||
def main():
|
||||
args, config = parse_args_and_config()
|
||||
|
||||
if args.dyn:
|
||||
ddp_kwargs = DistributedDataParallelKwargs(find_unused_parameters=True)
|
||||
accelerator = Accelerator(kwargs_handlers=[ddp_kwargs])
|
||||
else:
|
||||
accelerator = Accelerator()
|
||||
args.accelerator = accelerator
|
||||
|
||||
#log_root_dir = "{}_runtime_log".format(args.config[8:-4])
|
||||
log_root_dir = "runtime_log"
|
||||
dataset = args.config[8:-4]
|
||||
if args.cache:
|
||||
if args.non_uniform:
|
||||
sub_dir_name = "{}_{}_cache_{}_pow_{}_center_{}".format(dataset, args.exp, args.cache_interval, args.pow, args.center)
|
||||
else:
|
||||
sub_dir_name = "{}_{}_cache_{}".format(dataset, args.exp, args.cache_interval)
|
||||
else:
|
||||
sub_dir_name = "{}".format(args.exp)
|
||||
|
||||
if accelerator.is_main_process:
|
||||
logger = Logger(
|
||||
root_dir=log_root_dir,
|
||||
sub_name=sub_dir_name,
|
||||
config=args.__dict__,
|
||||
append=(args.sample == True)
|
||||
)
|
||||
args.logger = logger
|
||||
|
||||
args.logger.log("Writing log file to {}".format(args.logger.sub_dir))
|
||||
args.logger.log("Exp instance PID = {}".format(os.getpid()))
|
||||
else:
|
||||
args.logger = EmptyLogger(
|
||||
root_dir=log_root_dir,
|
||||
sub_name=sub_dir_name,
|
||||
)
|
||||
|
||||
args.image_folder = args.logger.setup_image_folder("{}".format(args.image_folder))
|
||||
|
||||
args.seed += accelerator.process_index
|
||||
# set random seed
|
||||
set_random_seed(args.seed)
|
||||
try:
|
||||
if args.cache:
|
||||
from ddpm.runners.deepcache import Diffusion
|
||||
runner = Diffusion(args, config)
|
||||
runner.sample()
|
||||
else:
|
||||
from ddpm.runners.diffusion import Diffusion
|
||||
runner = Diffusion(args, config)
|
||||
runner.sample()
|
||||
except Exception:
|
||||
logging.error(traceback.format_exc())
|
||||
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
361
ixformer_sdk/contrib/DeepCache/ddpm/fid.py
Normal file
361
ixformer_sdk/contrib/DeepCache/ddpm/fid.py
Normal file
@@ -0,0 +1,361 @@
|
||||
"""Calculates the Frechet Inception Distance (FID) to evalulate GANs
|
||||
|
||||
The FID metric calculates the distance between two distributions of images.
|
||||
Typically, we have summary statistics (mean & covariance matrix) of one
|
||||
of these distributions, while the 2nd distribution is given by a GAN.
|
||||
|
||||
When run as a stand-alone program, it compares the distribution of
|
||||
images that are stored as PNG/JPEG at a specified location with a
|
||||
distribution given by summary statistics (in pickle format).
|
||||
|
||||
The FID is calculated by assuming that X_1 and X_2 are the activations of
|
||||
the pool_3 layer of the inception net for generated samples and real world
|
||||
samples respectively.
|
||||
|
||||
See --help to see further details.
|
||||
|
||||
Code apapted from https://github.com/bioinf-jku/TTUR to use PyTorch instead
|
||||
of Tensorflow
|
||||
|
||||
Copyright 2018 Institute of Bioinformatics, JKU Linz
|
||||
|
||||
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 os
|
||||
import pathlib
|
||||
from argparse import ArgumentDefaultsHelpFormatter, ArgumentParser
|
||||
|
||||
import numpy as np
|
||||
import torch
|
||||
import torchvision.transforms as TF
|
||||
from PIL import Image
|
||||
from scipy import linalg
|
||||
from torch.nn.functional import adaptive_avg_pool2d
|
||||
|
||||
try:
|
||||
from tqdm import tqdm
|
||||
except ImportError:
|
||||
# If tqdm is not available, provide a mock version of it
|
||||
def tqdm(x):
|
||||
return x
|
||||
|
||||
from pytorch_fid.inception import InceptionV3
|
||||
|
||||
parser = ArgumentParser(formatter_class=ArgumentDefaultsHelpFormatter)
|
||||
parser.add_argument('--batch-size', type=int, default=50,
|
||||
help='Batch size to use')
|
||||
parser.add_argument('--dataset_name', type=str, default=None)
|
||||
parser.add_argument('--num-workers', type=int,
|
||||
help=('Number of processes to use for data loading. '
|
||||
'Defaults to `min(8, num_cpus)`'))
|
||||
parser.add_argument('--device', type=str, default=None,
|
||||
help='Device to use. Like cuda, cuda:0 or cpu')
|
||||
parser.add_argument('--dims', type=int, default=2048,
|
||||
choices=list(InceptionV3.BLOCK_INDEX_BY_DIM),
|
||||
help=('Dimensionality of Inception features to use. '
|
||||
'By default, uses pool3 features'))
|
||||
parser.add_argument('--num_samples', type=int, default=None,
|
||||
help=('Number of samples for FID estimation'))
|
||||
parser.add_argument('--res', type=int, default=None,
|
||||
help=('Resolutions of samples for FID estimation'))
|
||||
parser.add_argument('--save-stats', action='store_true',
|
||||
help=('Generate an npz archive from a directory of samples. '
|
||||
'The first path is used as input and the second as output.'))
|
||||
|
||||
parser.add_argument('--path', type=str, nargs=2,
|
||||
help=('Paths to the generated images or '
|
||||
'to .npz statistic files'))
|
||||
|
||||
|
||||
IMAGE_EXTENSIONS = {'bmp', 'jpg', 'jpeg', 'pgm', 'png', 'ppm',
|
||||
'tif', 'tiff', 'webp'}
|
||||
|
||||
|
||||
class ImagePathDataset(torch.utils.data.Dataset):
|
||||
def __init__(self, files, transforms=None):
|
||||
self.files = files
|
||||
self.transforms = transforms
|
||||
|
||||
def __len__(self):
|
||||
return len(self.files)
|
||||
|
||||
def __getitem__(self, i):
|
||||
path = self.files[i]
|
||||
img = Image.open(path).convert('RGB')
|
||||
if self.transforms is not None:
|
||||
img = self.transforms(img)
|
||||
return img
|
||||
|
||||
|
||||
def get_activations(files, model, batch_size=50, dims=2048, device='cpu',
|
||||
num_workers=1, res=None, dataset_name=None):
|
||||
"""Calculates the activations of the pool_3 layer for all images.
|
||||
|
||||
Params:
|
||||
-- files : List of image files paths
|
||||
-- model : Instance of inception model
|
||||
-- batch_size : Batch size of images for the model to process at once.
|
||||
Make sure that the number of samples is a multiple of
|
||||
the batch size, otherwise some samples are ignored. This
|
||||
behavior is retained to match the original FID score
|
||||
implementation.
|
||||
-- dims : Dimensionality of features returned by Inception
|
||||
-- device : Device to run calculations
|
||||
-- num_workers : Number of parallel dataloader workers
|
||||
|
||||
Returns:
|
||||
-- A numpy array of dimension (num images, dims) that contains the
|
||||
activations of the given tensor when feeding inception with the
|
||||
query tensor.
|
||||
"""
|
||||
model.eval()
|
||||
|
||||
if batch_size > len(files):
|
||||
print(('Warning: batch size is bigger than the data size. '
|
||||
'Setting batch size to data size'))
|
||||
batch_size = len(files)
|
||||
|
||||
if res is None:
|
||||
trans = TF.ToTensor()
|
||||
else:
|
||||
if dataset_name == 'celeba':
|
||||
from switchable_diffusion.datasets import Crop
|
||||
print("In crop image: {}, {}".format(res, dataset_name))
|
||||
cx = 89
|
||||
cy = 121
|
||||
x1 = cy - 64
|
||||
x2 = cy + 64
|
||||
y1 = cx - 64
|
||||
y2 = cx + 64
|
||||
trans = TF.Compose([
|
||||
Crop(x1, x2, y1, y2),
|
||||
TF.Resize(res),
|
||||
TF.ToTensor(),
|
||||
])
|
||||
else:
|
||||
trans = TF.Compose([
|
||||
TF.Resize(res),
|
||||
TF.CenterCrop(res),
|
||||
TF.ToTensor()
|
||||
])
|
||||
|
||||
dataset = ImagePathDataset(files, transforms=trans)
|
||||
dataloader = torch.utils.data.DataLoader(dataset,
|
||||
batch_size=batch_size,
|
||||
shuffle=False,
|
||||
drop_last=False,
|
||||
num_workers=num_workers)
|
||||
|
||||
pred_arr = np.empty((len(files), dims))
|
||||
|
||||
start_idx = 0
|
||||
|
||||
for batch in tqdm(dataloader):
|
||||
batch = batch.to(device)
|
||||
|
||||
with torch.no_grad():
|
||||
pred = model(batch)[0]
|
||||
|
||||
# If model output is not scalar, apply global spatial average pooling.
|
||||
# This happens if you choose a dimensionality not equal 2048.
|
||||
if pred.size(2) != 1 or pred.size(3) != 1:
|
||||
pred = adaptive_avg_pool2d(pred, output_size=(1, 1))
|
||||
|
||||
pred = pred.squeeze(3).squeeze(2).cpu().numpy()
|
||||
|
||||
pred_arr[start_idx:start_idx + pred.shape[0]] = pred
|
||||
|
||||
start_idx = start_idx + pred.shape[0]
|
||||
|
||||
return pred_arr
|
||||
|
||||
|
||||
def calculate_frechet_distance(mu1, sigma1, mu2, sigma2, eps=1e-6):
|
||||
"""Numpy implementation of the Frechet Distance.
|
||||
The Frechet distance between two multivariate Gaussians X_1 ~ N(mu_1, C_1)
|
||||
and X_2 ~ N(mu_2, C_2) is
|
||||
d^2 = ||mu_1 - mu_2||^2 + Tr(C_1 + C_2 - 2*sqrt(C_1*C_2)).
|
||||
|
||||
Stable version by Dougal J. Sutherland.
|
||||
|
||||
Params:
|
||||
-- mu1 : Numpy array containing the activations of a layer of the
|
||||
inception net (like returned by the function 'get_predictions')
|
||||
for generated samples.
|
||||
-- mu2 : The sample mean over activations, precalculated on an
|
||||
representative data set.
|
||||
-- sigma1: The covariance matrix over activations for generated samples.
|
||||
-- sigma2: The covariance matrix over activations, precalculated on an
|
||||
representative data set.
|
||||
|
||||
Returns:
|
||||
-- : The Frechet Distance.
|
||||
"""
|
||||
|
||||
mu1 = np.atleast_1d(mu1)
|
||||
mu2 = np.atleast_1d(mu2)
|
||||
|
||||
sigma1 = np.atleast_2d(sigma1)
|
||||
sigma2 = np.atleast_2d(sigma2)
|
||||
|
||||
assert mu1.shape == mu2.shape, \
|
||||
'Training and test mean vectors have different lengths'
|
||||
assert sigma1.shape == sigma2.shape, \
|
||||
'Training and test covariances have different dimensions'
|
||||
|
||||
diff = mu1 - mu2
|
||||
|
||||
# Product might be almost singular
|
||||
covmean, _ = linalg.sqrtm(sigma1.dot(sigma2), disp=False)
|
||||
if not np.isfinite(covmean).all():
|
||||
msg = ('fid calculation produces singular product; '
|
||||
'adding %s to diagonal of cov estimates') % eps
|
||||
print(msg)
|
||||
offset = np.eye(sigma1.shape[0]) * eps
|
||||
covmean = linalg.sqrtm((sigma1 + offset).dot(sigma2 + offset))
|
||||
|
||||
# Numerical error might give slight imaginary component
|
||||
if np.iscomplexobj(covmean):
|
||||
if not np.allclose(np.diagonal(covmean).imag, 0, atol=1e-3):
|
||||
m = np.max(np.abs(covmean.imag))
|
||||
raise ValueError('Imaginary component {}'.format(m))
|
||||
covmean = covmean.real
|
||||
|
||||
tr_covmean = np.trace(covmean)
|
||||
|
||||
return (diff.dot(diff) + np.trace(sigma1)
|
||||
+ np.trace(sigma2) - 2 * tr_covmean)
|
||||
|
||||
|
||||
def calculate_activation_statistics(files, model, batch_size=50, dims=2048,
|
||||
device='cpu', num_workers=1, res=None, dataset_name=None):
|
||||
"""Calculation of the statistics used by the FID.
|
||||
Params:
|
||||
-- files : List of image files paths
|
||||
-- model : Instance of inception model
|
||||
-- batch_size : The images numpy array is split into batches with
|
||||
batch size batch_size. A reasonable batch size
|
||||
depends on the hardware.
|
||||
-- dims : Dimensionality of features returned by Inception
|
||||
-- device : Device to run calculations
|
||||
-- num_workers : Number of parallel dataloader workers
|
||||
|
||||
Returns:
|
||||
-- mu : The mean over samples of the activations of the pool_3 layer of
|
||||
the inception model.
|
||||
-- sigma : The covariance matrix of the activations of the pool_3 layer of
|
||||
the inception model.
|
||||
"""
|
||||
act = get_activations(files, model, batch_size, dims, device, num_workers, res=res, dataset_name=dataset_name)
|
||||
mu = np.mean(act, axis=0)
|
||||
sigma = np.cov(act, rowvar=False)
|
||||
return mu, sigma
|
||||
|
||||
|
||||
def compute_statistics_of_path(path, model, batch_size, dims, device,
|
||||
num_workers=1, num_samples=None, res=None, dataset_name=None):
|
||||
if path.endswith('.npz'):
|
||||
with np.load(path) as f:
|
||||
m, s = f['mu'][:], f['sigma'][:]
|
||||
else:
|
||||
path = pathlib.Path(path)
|
||||
|
||||
files = sorted([file for ext in IMAGE_EXTENSIONS
|
||||
for file in path.glob('**/*.{}'.format(ext))])
|
||||
if num_samples is not None:
|
||||
#import random
|
||||
#files = random.sample(files, num_samples)
|
||||
files = files[:num_samples]
|
||||
print("Found %d files." % len(files))
|
||||
m, s = calculate_activation_statistics(files, model, batch_size,
|
||||
dims, device, num_workers, res=res, dataset_name=dataset_name)
|
||||
|
||||
return m, s
|
||||
|
||||
|
||||
def calculate_fid_given_paths(paths, batch_size, device, dims, num_workers=1, num_samples=None, res=None, dataset_name=None):
|
||||
"""Calculates the FID of two paths"""
|
||||
for p in paths:
|
||||
if not os.path.exists(p):
|
||||
raise RuntimeError('Invalid path: %s' % p)
|
||||
|
||||
block_idx = InceptionV3.BLOCK_INDEX_BY_DIM[dims]
|
||||
|
||||
model = InceptionV3([block_idx]).to(device)
|
||||
|
||||
m1, s1 = compute_statistics_of_path(paths[0], model, batch_size,
|
||||
dims, device, num_workers, num_samples=num_samples, res=res, dataset_name=dataset_name)
|
||||
m2, s2 = compute_statistics_of_path(paths[1], model, batch_size,
|
||||
dims, device, num_workers, num_samples=num_samples, res=res, dataset_name=dataset_name)
|
||||
fid_value = calculate_frechet_distance(m1, s1, m2, s2)
|
||||
|
||||
return fid_value
|
||||
|
||||
|
||||
def save_fid_stats(paths, batch_size, device, dims, num_workers=1, num_samples=None, res=None, dataset_name=None):
|
||||
"""Calculates the FID of two paths"""
|
||||
if not os.path.exists(paths[0]):
|
||||
raise RuntimeError('Invalid path: %s' % paths[0])
|
||||
|
||||
if os.path.exists(paths[1]):
|
||||
raise RuntimeError('Existing output file: %s' % paths[1])
|
||||
|
||||
block_idx = InceptionV3.BLOCK_INDEX_BY_DIM[dims]
|
||||
|
||||
model = InceptionV3([block_idx]).to(device)
|
||||
|
||||
print(f"Saving statistics for {paths[0]}")
|
||||
|
||||
m1, s1 = compute_statistics_of_path(paths[0], model, batch_size,
|
||||
dims, device, num_workers, num_samples=num_samples, res=res, dataset_name=dataset_name)
|
||||
|
||||
np.savez_compressed(paths[1], mu=m1, sigma=s1)
|
||||
|
||||
|
||||
def main():
|
||||
args = parser.parse_args()
|
||||
|
||||
if args.device is None:
|
||||
device = torch.device('cuda' if (torch.cuda.is_available()) else 'cpu')
|
||||
else:
|
||||
device = torch.device(args.device)
|
||||
|
||||
if args.num_workers is None:
|
||||
try:
|
||||
num_cpus = len(os.sched_getaffinity(0))
|
||||
except AttributeError:
|
||||
# os.sched_getaffinity is not available under Windows, use
|
||||
# os.cpu_count instead (which may not return the *available* number
|
||||
# of CPUs).
|
||||
num_cpus = os.cpu_count()
|
||||
|
||||
num_workers = min(num_cpus, 8) if num_cpus is not None else 0
|
||||
else:
|
||||
num_workers = args.num_workers
|
||||
|
||||
if args.save_stats:
|
||||
save_fid_stats(args.path, args.batch_size, device, args.dims, num_workers, num_samples=args.num_samples, res=args.res, dataset_name=args.dataset_name)
|
||||
return
|
||||
|
||||
fid_value = calculate_fid_given_paths(args.path,
|
||||
args.batch_size,
|
||||
device,
|
||||
args.dims,
|
||||
num_workers,
|
||||
num_samples=args.num_samples,
|
||||
res = args.res, dataset_name=args.dataset_name)
|
||||
print('FID: ', fid_value)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
main()
|
||||
559
ixformer_sdk/contrib/DeepCache/flops.py
Normal file
559
ixformer_sdk/contrib/DeepCache/flops.py
Normal file
@@ -0,0 +1,559 @@
|
||||
'''
|
||||
This opcounter is adapted from https://github.com/sovrasov/flops-counter.pytorch and https://github.com/Lyken17/pytorch-OpCounter
|
||||
|
||||
Copyright (C) 2021 Sovrasov V. - All Rights Reserved
|
||||
* You may use, distribute and modify this code under the
|
||||
* terms of the MIT license.
|
||||
* You should have received a copy of the MIT license with
|
||||
* this file. If not visit https://opensource.org/licenses/MIT
|
||||
'''
|
||||
import os
|
||||
import yaml
|
||||
import numpy as np
|
||||
import torch.nn as nn
|
||||
import torch
|
||||
has_timm = False
|
||||
|
||||
from diffusers.models.lora import LoRACompatibleLinear, LoRACompatibleConv
|
||||
|
||||
@torch.no_grad()
|
||||
def count_ops_and_params(model, example_inputs, layer_wise=False):
|
||||
global CUSTOM_MODULES_MAPPING
|
||||
ori_model = model
|
||||
model = copy.deepcopy(model) # deepcopy to avoid changing the original model
|
||||
flops_model = add_flops_counting_methods(model)
|
||||
flops_model.eval()
|
||||
flops_model.start_flops_count(ost=sys.stdout, verbose=False,
|
||||
ignore_list=[])
|
||||
if isinstance(example_inputs, (tuple, list)):
|
||||
_ = flops_model(*example_inputs)
|
||||
elif isinstance(example_inputs, dict):
|
||||
_ = flops_model(**example_inputs)
|
||||
else:
|
||||
_ = flops_model(example_inputs)
|
||||
flops_count, params_count, _layer_flops, _layer_params = flops_model.compute_average_flops_cost()
|
||||
layer_flops = {}
|
||||
layer_params = {}
|
||||
|
||||
for m_name, m in model.named_modules():
|
||||
layer_flops[m_name] = _layer_flops.get(m)
|
||||
layer_params[m_name] = _layer_params.get(m)
|
||||
if layer_wise:
|
||||
space = 30 - len(m_name)
|
||||
print("Layer {}: {} MACs = {:.4f} G, Params = {:.4f} M, MACs% = {:.2f}".format(
|
||||
m_name, ' ' * space, layer_flops[m_name]/1e9, layer_params[m_name] / 1e6, 100 * layer_flops[m_name] / flops_count
|
||||
))
|
||||
|
||||
flops_model.stop_flops_count()
|
||||
CUSTOM_MODULES_MAPPING = {}
|
||||
#if layer_wise:
|
||||
# return flops_count, params_count, layer_flops, layer_params
|
||||
return flops_count, params_count
|
||||
|
||||
def empty_flops_counter_hook(module, input, output):
|
||||
module.__flops__ += 0
|
||||
|
||||
|
||||
def upsample_flops_counter_hook(module, input, output):
|
||||
output_size = output[0]
|
||||
batch_size = output_size.shape[0]
|
||||
output_elements_count = batch_size
|
||||
for val in output_size.shape[1:]:
|
||||
output_elements_count *= val
|
||||
module.__flops__ += int(output_elements_count)
|
||||
|
||||
|
||||
def relu_flops_counter_hook(module, input, output):
|
||||
active_elements_count = output.numel()
|
||||
module.__flops__ += int(active_elements_count)
|
||||
|
||||
|
||||
def linear_flops_counter_hook(module, input, output):
|
||||
input = input[0]
|
||||
# pytorch checks dimensions, so here we don't care much
|
||||
output_last_dim = output.shape[-1]
|
||||
bias_flops = output_last_dim if module.bias is not None else 0
|
||||
module.__flops__ += int(np.prod(input.shape) * output_last_dim + bias_flops)
|
||||
|
||||
|
||||
def pool_flops_counter_hook(module, input, output):
|
||||
input = input[0]
|
||||
module.__flops__ += int(np.prod(input.shape))
|
||||
|
||||
|
||||
def bn_flops_counter_hook(module, input, output):
|
||||
input = input[0]
|
||||
|
||||
batch_flops = np.prod(input.shape)
|
||||
if module.affine:
|
||||
batch_flops *= 2
|
||||
module.__flops__ += int(batch_flops)
|
||||
|
||||
def ln_flops_counter_hook(module, input, output):
|
||||
input = input[0]
|
||||
batch_flops = np.prod(input.shape)
|
||||
if module.elementwise_affine:
|
||||
batch_flops *= 2
|
||||
module.__flops__ += int(batch_flops)
|
||||
|
||||
def conv_flops_counter_hook(conv_module, input, output):
|
||||
# Can have multiple inputs, getting the first one
|
||||
input = input[0]
|
||||
|
||||
batch_size = input.shape[0]
|
||||
output_dims = list(output.shape[2:])
|
||||
|
||||
kernel_dims = list(conv_module.kernel_size)
|
||||
in_channels = conv_module.in_channels
|
||||
out_channels = conv_module.out_channels
|
||||
groups = conv_module.groups
|
||||
|
||||
filters_per_channel = out_channels // groups
|
||||
conv_per_position_flops = int(np.prod(kernel_dims)) * \
|
||||
in_channels * filters_per_channel
|
||||
|
||||
active_elements_count = batch_size * int(np.prod(output_dims))
|
||||
|
||||
overall_conv_flops = conv_per_position_flops * active_elements_count
|
||||
|
||||
bias_flops = 0
|
||||
|
||||
if conv_module.bias is not None:
|
||||
|
||||
bias_flops = out_channels * active_elements_count
|
||||
|
||||
overall_flops = overall_conv_flops + bias_flops
|
||||
|
||||
conv_module.__flops__ += int(overall_flops)
|
||||
|
||||
|
||||
def rnn_flops(flops, rnn_module, w_ih, w_hh, input_size):
|
||||
# matrix matrix mult ih state and internal state
|
||||
flops += w_ih.shape[0]*w_ih.shape[1]
|
||||
# matrix matrix mult hh state and internal state
|
||||
flops += w_hh.shape[0]*w_hh.shape[1]
|
||||
if isinstance(rnn_module, (nn.RNN, nn.RNNCell)):
|
||||
# add both operations
|
||||
flops += rnn_module.hidden_size
|
||||
elif isinstance(rnn_module, (nn.GRU, nn.GRUCell)):
|
||||
# hadamard of r
|
||||
flops += rnn_module.hidden_size
|
||||
# adding operations from both states
|
||||
flops += rnn_module.hidden_size*3
|
||||
# last two hadamard product and add
|
||||
flops += rnn_module.hidden_size*3
|
||||
elif isinstance(rnn_module, (nn.LSTM, nn.LSTMCell)):
|
||||
# adding operations from both states
|
||||
flops += rnn_module.hidden_size*4
|
||||
# two hadamard product and add for C state
|
||||
flops += rnn_module.hidden_size + rnn_module.hidden_size + rnn_module.hidden_size
|
||||
# final hadamard
|
||||
flops += rnn_module.hidden_size + rnn_module.hidden_size + rnn_module.hidden_size
|
||||
return flops
|
||||
|
||||
|
||||
def rnn_flops_counter_hook(rnn_module, input, output):
|
||||
"""
|
||||
Takes into account batch goes at first position, contrary
|
||||
to pytorch common rule (but actually it doesn't matter).
|
||||
If sigmoid and tanh are hard, only a comparison FLOPS should be accurate
|
||||
"""
|
||||
flops = 0
|
||||
# input is a tuple containing a sequence to process and (optionally) hidden state
|
||||
inp = input[0]
|
||||
batch_size = inp[0].shape[0]
|
||||
seq_length = inp[0].shape[1]
|
||||
num_layers = rnn_module.num_layers
|
||||
|
||||
for i in range(num_layers):
|
||||
w_ih = rnn_module.__getattr__('weight_ih_l' + str(i))
|
||||
w_hh = rnn_module.__getattr__('weight_hh_l' + str(i))
|
||||
if i == 0:
|
||||
input_size = rnn_module.input_size
|
||||
else:
|
||||
input_size = rnn_module.hidden_size
|
||||
flops = rnn_flops(flops, rnn_module, w_ih, w_hh, input_size)
|
||||
if rnn_module.bias:
|
||||
b_ih = rnn_module.__getattr__('bias_ih_l' + str(i))
|
||||
b_hh = rnn_module.__getattr__('bias_hh_l' + str(i))
|
||||
flops += b_ih.shape[0] + b_hh.shape[0]
|
||||
|
||||
flops *= batch_size
|
||||
flops *= seq_length
|
||||
if rnn_module.bidirectional:
|
||||
flops *= 2
|
||||
rnn_module.__flops__ += int(flops)
|
||||
|
||||
|
||||
def rnn_cell_flops_counter_hook(rnn_cell_module, input, output):
|
||||
flops = 0
|
||||
inp = input[0]
|
||||
batch_size = inp.shape[0]
|
||||
w_ih = rnn_cell_module.__getattr__('weight_ih')
|
||||
w_hh = rnn_cell_module.__getattr__('weight_hh')
|
||||
input_size = inp.shape[1]
|
||||
flops = rnn_flops(flops, rnn_cell_module, w_ih, w_hh, input_size)
|
||||
if rnn_cell_module.bias:
|
||||
b_ih = rnn_cell_module.__getattr__('bias_ih')
|
||||
b_hh = rnn_cell_module.__getattr__('bias_hh')
|
||||
flops += b_ih.shape[0] + b_hh.shape[0]
|
||||
|
||||
flops *= batch_size
|
||||
rnn_cell_module.__flops__ += int(flops)
|
||||
|
||||
|
||||
def multihead_attention_counter_hook(multihead_attention_module, input, output):
|
||||
flops = 0
|
||||
q, k, v = input
|
||||
|
||||
batch_first = multihead_attention_module.batch_first \
|
||||
if hasattr(multihead_attention_module, 'batch_first') else False
|
||||
if batch_first:
|
||||
batch_size = q.shape[0]
|
||||
len_idx = 1
|
||||
else:
|
||||
batch_size = q.shape[1]
|
||||
len_idx = 0
|
||||
|
||||
dim_idx = 2
|
||||
|
||||
qdim = q.shape[dim_idx]
|
||||
kdim = k.shape[dim_idx]
|
||||
vdim = v.shape[dim_idx]
|
||||
|
||||
qlen = q.shape[len_idx]
|
||||
klen = k.shape[len_idx]
|
||||
vlen = v.shape[len_idx]
|
||||
|
||||
num_heads = multihead_attention_module.num_heads
|
||||
assert qdim == multihead_attention_module.embed_dim
|
||||
|
||||
if multihead_attention_module.kdim is None:
|
||||
assert kdim == qdim
|
||||
if multihead_attention_module.vdim is None:
|
||||
assert vdim == qdim
|
||||
|
||||
flops = 0
|
||||
|
||||
# Q scaling
|
||||
flops += qlen * qdim
|
||||
# Initial projections
|
||||
flops += (
|
||||
(qlen * qdim * qdim) # QW
|
||||
+ (klen * kdim * kdim) # KW
|
||||
+ (vlen * vdim * vdim) # VW
|
||||
)
|
||||
if multihead_attention_module.in_proj_bias is not None:
|
||||
flops += (qlen + klen + vlen) * qdim
|
||||
# attention heads: scale, matmul, softmax, matmul
|
||||
qk_head_dim = qdim // num_heads
|
||||
v_head_dim = vdim // num_heads
|
||||
|
||||
head_flops = (
|
||||
(qlen * klen * qk_head_dim) # QK^T
|
||||
+ (qlen * klen) # softmax
|
||||
+ (qlen * klen * v_head_dim) # AV
|
||||
)
|
||||
flops += num_heads * head_flops
|
||||
# final projection, bias is always enabled
|
||||
flops += qlen * vdim * (vdim + 1)
|
||||
flops *= batch_size
|
||||
multihead_attention_module.__flops__ += int(flops)
|
||||
|
||||
def timm_multihead_attention_counter_hook(multihead_attention_module, input, output):
|
||||
flops = 0
|
||||
|
||||
q, k, v = input[0], input[0], input[0]
|
||||
input_dim = input[0].shape[2]
|
||||
input_len = input[0].shape[1]
|
||||
batch_size = input[0].shape[0]
|
||||
|
||||
kdim = qdim = vdim = multihead_attention_module.qkv.out_features//3
|
||||
qlen = klen = vlen = input_len
|
||||
|
||||
num_heads = multihead_attention_module.num_heads
|
||||
assert qdim == multihead_attention_module.head_dim * multihead_attention_module.num_heads
|
||||
|
||||
flops = 0
|
||||
# Q scaling
|
||||
flops += qlen * qdim
|
||||
# Initial projections
|
||||
flops += (
|
||||
(qlen * input_dim * qdim) # QW
|
||||
+ (klen * input_dim * kdim) # KW
|
||||
+ (vlen * input_dim * vdim) # VW
|
||||
)
|
||||
|
||||
if multihead_attention_module.qkv.bias is not None:
|
||||
flops += (qlen + klen + vlen) * qdim
|
||||
# attention heads: scale, matmul, softmax, matmul
|
||||
qk_head_dim = qdim // num_heads
|
||||
v_head_dim = vdim // num_heads
|
||||
|
||||
head_flops = (
|
||||
(qlen * klen * qk_head_dim) # QK^T
|
||||
+ (qlen * klen) # softmax
|
||||
+ (qlen * klen * v_head_dim) # AV
|
||||
)
|
||||
flops += num_heads * head_flops
|
||||
# final projection, bias is always enabled
|
||||
flops += qlen * vdim * (vdim + 1)
|
||||
flops *= batch_size
|
||||
multihead_attention_module.__flops__ += int(flops)
|
||||
|
||||
|
||||
|
||||
CUSTOM_MODULES_MAPPING = {}
|
||||
|
||||
MODULES_MAPPING = {
|
||||
# convolutions
|
||||
nn.Conv1d: conv_flops_counter_hook,
|
||||
nn.Conv2d: conv_flops_counter_hook,
|
||||
nn.Conv3d: conv_flops_counter_hook,
|
||||
LoRACompatibleConv: conv_flops_counter_hook,
|
||||
# activations
|
||||
nn.ReLU: relu_flops_counter_hook,
|
||||
nn.PReLU: relu_flops_counter_hook,
|
||||
nn.ELU: relu_flops_counter_hook,
|
||||
nn.LeakyReLU: relu_flops_counter_hook,
|
||||
nn.ReLU6: relu_flops_counter_hook,
|
||||
# poolings
|
||||
nn.MaxPool1d: pool_flops_counter_hook,
|
||||
nn.AvgPool1d: pool_flops_counter_hook,
|
||||
nn.AvgPool2d: pool_flops_counter_hook,
|
||||
nn.MaxPool2d: pool_flops_counter_hook,
|
||||
nn.MaxPool3d: pool_flops_counter_hook,
|
||||
nn.AvgPool3d: pool_flops_counter_hook,
|
||||
nn.AdaptiveMaxPool1d: pool_flops_counter_hook,
|
||||
nn.AdaptiveAvgPool1d: pool_flops_counter_hook,
|
||||
nn.AdaptiveMaxPool2d: pool_flops_counter_hook,
|
||||
nn.AdaptiveAvgPool2d: pool_flops_counter_hook,
|
||||
nn.AdaptiveMaxPool3d: pool_flops_counter_hook,
|
||||
nn.AdaptiveAvgPool3d: pool_flops_counter_hook,
|
||||
# BNs
|
||||
nn.BatchNorm1d: bn_flops_counter_hook,
|
||||
nn.BatchNorm2d: bn_flops_counter_hook,
|
||||
nn.BatchNorm3d: bn_flops_counter_hook,
|
||||
|
||||
nn.InstanceNorm1d: bn_flops_counter_hook,
|
||||
nn.InstanceNorm2d: bn_flops_counter_hook,
|
||||
nn.InstanceNorm3d: bn_flops_counter_hook,
|
||||
nn.GroupNorm: bn_flops_counter_hook,
|
||||
nn.LayerNorm: ln_flops_counter_hook,
|
||||
# FC
|
||||
nn.Linear: linear_flops_counter_hook,
|
||||
LoRACompatibleLinear: linear_flops_counter_hook,
|
||||
# Upscale
|
||||
nn.Upsample: upsample_flops_counter_hook,
|
||||
# Deconvolution
|
||||
nn.ConvTranspose1d: conv_flops_counter_hook,
|
||||
nn.ConvTranspose2d: conv_flops_counter_hook,
|
||||
nn.ConvTranspose3d: conv_flops_counter_hook,
|
||||
# RNN
|
||||
nn.RNN: rnn_flops_counter_hook,
|
||||
nn.GRU: rnn_flops_counter_hook,
|
||||
nn.LSTM: rnn_flops_counter_hook,
|
||||
nn.RNNCell: rnn_cell_flops_counter_hook,
|
||||
nn.LSTMCell: rnn_cell_flops_counter_hook,
|
||||
nn.GRUCell: rnn_cell_flops_counter_hook,
|
||||
nn.MultiheadAttention: multihead_attention_counter_hook
|
||||
}
|
||||
|
||||
if has_timm:
|
||||
MODULES_MAPPING.update(
|
||||
{
|
||||
timm.models.vision_transformer.Attention: timm_multihead_attention_counter_hook,
|
||||
}
|
||||
)
|
||||
|
||||
if hasattr(nn, 'GELU'):
|
||||
MODULES_MAPPING[nn.GELU] = relu_flops_counter_hook
|
||||
|
||||
|
||||
import sys
|
||||
from functools import partial
|
||||
import torch.nn as nn
|
||||
import copy
|
||||
|
||||
def accumulate_flops(self, layer_flops):
|
||||
if is_supported_instance(self):
|
||||
layer_flops[self] = self.__flops__
|
||||
return self.__flops__
|
||||
else:
|
||||
sum = 0
|
||||
for m in self.children():
|
||||
sum += m.accumulate_flops(layer_flops)
|
||||
layer_flops[self] = sum
|
||||
return sum
|
||||
|
||||
|
||||
def get_model_parameters_number(model):
|
||||
params_num = sum(p.numel() for p in model.parameters())
|
||||
return params_num
|
||||
|
||||
|
||||
def add_flops_counting_methods(net_main_module):
|
||||
# adding additional methods to the existing module object,
|
||||
# this is done this way so that each function has access to self object
|
||||
net_main_module.start_flops_count = start_flops_count.__get__(net_main_module)
|
||||
net_main_module.stop_flops_count = stop_flops_count.__get__(net_main_module)
|
||||
net_main_module.reset_flops_count = reset_flops_count.__get__(net_main_module)
|
||||
net_main_module.compute_average_flops_cost = compute_average_flops_cost.__get__(
|
||||
net_main_module)
|
||||
|
||||
net_main_module.reset_flops_count()
|
||||
|
||||
return net_main_module
|
||||
|
||||
def compute_average_flops_cost(self):
|
||||
"""
|
||||
A method that will be available after add_flops_counting_methods() is called
|
||||
on a desired net object.
|
||||
Returns current mean flops consumption per image.
|
||||
"""
|
||||
|
||||
for m in self.modules():
|
||||
m.accumulate_flops = accumulate_flops.__get__(m)
|
||||
|
||||
layer_flops = {}
|
||||
flops_sum = self.accumulate_flops(layer_flops)
|
||||
|
||||
for m in self.modules():
|
||||
if hasattr(m, 'accumulate_flops'):
|
||||
del m.accumulate_flops
|
||||
|
||||
layer_params = {}
|
||||
for m in self.modules():
|
||||
layer_params[m] = get_model_parameters_number(m)
|
||||
|
||||
params_sum = get_model_parameters_number(self)
|
||||
return flops_sum / self.__batch_counter__, params_sum, layer_flops, layer_params
|
||||
|
||||
|
||||
def start_flops_count(self, **kwargs):
|
||||
"""
|
||||
A method that will be available after add_flops_counting_methods() is called
|
||||
on a desired net object.
|
||||
Activates the computation of mean flops consumption per image.
|
||||
Call it before you run the network.
|
||||
"""
|
||||
add_batch_counter_hook_function(self)
|
||||
|
||||
seen_types = set()
|
||||
|
||||
def add_flops_counter_hook_function(module, ost, verbose, ignore_list):
|
||||
if type(module) in ignore_list:
|
||||
seen_types.add(type(module))
|
||||
if is_supported_instance(module):
|
||||
module.__params__ = 0
|
||||
elif is_supported_instance(module):
|
||||
if hasattr(module, '__flops_handle__'):
|
||||
return
|
||||
if type(module) in CUSTOM_MODULES_MAPPING:
|
||||
handle = module.register_forward_hook(
|
||||
CUSTOM_MODULES_MAPPING[type(module)])
|
||||
else:
|
||||
handle = module.register_forward_hook(MODULES_MAPPING[type(module)])
|
||||
module.__flops_handle__ = handle
|
||||
seen_types.add(type(module))
|
||||
else:
|
||||
if verbose and not type(module) in (nn.Sequential, nn.ModuleList) and \
|
||||
not type(module) in seen_types:
|
||||
print('Warning: module ' + type(module).__name__ +
|
||||
' is treated as a zero-op.', file=ost)
|
||||
seen_types.add(type(module))
|
||||
|
||||
self.apply(partial(add_flops_counter_hook_function, **kwargs))
|
||||
|
||||
|
||||
def stop_flops_count(self):
|
||||
"""
|
||||
A method that will be available after add_flops_counting_methods() is called
|
||||
on a desired net object.
|
||||
Stops computing the mean flops consumption per image.
|
||||
Call whenever you want to pause the computation.
|
||||
"""
|
||||
remove_batch_counter_hook_function(self)
|
||||
self.apply(remove_flops_counter_hook_function)
|
||||
self.apply(remove_flops_counter_variables)
|
||||
|
||||
|
||||
def reset_flops_count(self):
|
||||
"""
|
||||
A method that will be available after add_flops_counting_methods() is called
|
||||
on a desired net object.
|
||||
Resets statistics computed so far.
|
||||
"""
|
||||
add_batch_counter_variables_or_reset(self)
|
||||
self.apply(add_flops_counter_variable_or_reset)
|
||||
|
||||
|
||||
# ---- Internal functions
|
||||
def batch_counter_hook(module, input, output):
|
||||
batch_size = 1
|
||||
if len(input) > 0:
|
||||
# Can have multiple inputs, getting the first one
|
||||
input = input[0]
|
||||
batch_size = len(input)
|
||||
else:
|
||||
pass
|
||||
print('Warning! No positional inputs found for a module,'
|
||||
' assuming batch size is 1.')
|
||||
module.__batch_counter__ += batch_size
|
||||
|
||||
|
||||
def add_batch_counter_variables_or_reset(module):
|
||||
|
||||
module.__batch_counter__ = 0
|
||||
|
||||
|
||||
def add_batch_counter_hook_function(module):
|
||||
if hasattr(module, '__batch_counter_handle__'):
|
||||
return
|
||||
|
||||
handle = module.register_forward_hook(batch_counter_hook)
|
||||
module.__batch_counter_handle__ = handle
|
||||
|
||||
|
||||
def remove_batch_counter_hook_function(module):
|
||||
if hasattr(module, '__batch_counter_handle__'):
|
||||
module.__batch_counter_handle__.remove()
|
||||
del module.__batch_counter_handle__
|
||||
|
||||
|
||||
def add_flops_counter_variable_or_reset(module):
|
||||
if is_supported_instance(module):
|
||||
if hasattr(module, '__flops__') or hasattr(module, '__params__'):
|
||||
print('Warning: variables __flops__ or __params__ are already '
|
||||
'defined for the module' + type(module).__name__ +
|
||||
' ptflops can affect your code!')
|
||||
module.__ptflops_backup_flops__ = module.__flops__
|
||||
module.__ptflops_backup_params__ = module.__params__
|
||||
module.__flops__ = 0
|
||||
module.__params__ = get_model_parameters_number(module)
|
||||
|
||||
|
||||
def is_supported_instance(module):
|
||||
if type(module) in MODULES_MAPPING or type(module) in CUSTOM_MODULES_MAPPING:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def remove_flops_counter_hook_function(module):
|
||||
if is_supported_instance(module):
|
||||
if hasattr(module, '__flops_handle__'):
|
||||
module.__flops_handle__.remove()
|
||||
del module.__flops_handle__
|
||||
|
||||
|
||||
def remove_flops_counter_variables(module):
|
||||
if is_supported_instance(module):
|
||||
if hasattr(module, '__flops__'):
|
||||
del module.__flops__
|
||||
if hasattr(module, '__ptflops_backup_flops__'):
|
||||
module.__flops__ = module.__ptflops_backup_flops__
|
||||
if hasattr(module, '__params__'):
|
||||
del module.__params__
|
||||
if hasattr(module, '__ptflops_backup_params__'):
|
||||
module.__params__ = module.__ptflops_backup_params__
|
||||
|
||||
0
ixformer_sdk/contrib/DeepCache/sd/__init__.py
Normal file
0
ixformer_sdk/contrib/DeepCache/sd/__init__.py
Normal file
812
ixformer_sdk/contrib/DeepCache/sd/pipeline_stable_diffusion.py
Normal file
812
ixformer_sdk/contrib/DeepCache/sd/pipeline_stable_diffusion.py
Normal file
@@ -0,0 +1,812 @@
|
||||
# Copyright 2023 The HuggingFace 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 time
|
||||
import inspect
|
||||
from typing import Any, Callable, Dict, List, Optional, Union
|
||||
|
||||
import torch
|
||||
import numpy as np
|
||||
from packaging import version
|
||||
from transformers import CLIPImageProcessor, CLIPTextModel, CLIPTokenizer
|
||||
|
||||
from diffusers.configuration_utils import FrozenDict
|
||||
from diffusers.image_processor import VaeImageProcessor
|
||||
from diffusers.loaders import FromSingleFileMixin, LoraLoaderMixin, TextualInversionLoaderMixin
|
||||
from diffusers.models import AutoencoderKL
|
||||
from diffusers.models.lora import adjust_lora_scale_text_encoder
|
||||
from diffusers.schedulers import KarrasDiffusionSchedulers
|
||||
from diffusers.utils import (
|
||||
deprecate,
|
||||
logging,
|
||||
replace_example_docstring,
|
||||
)
|
||||
from diffusers.utils.torch_utils import randn_tensor
|
||||
|
||||
from diffusers.pipelines.stable_diffusion import StableDiffusionPipelineOutput
|
||||
from diffusers.pipelines.stable_diffusion.safety_checker import StableDiffusionSafetyChecker
|
||||
|
||||
from .unet_2d_condition import UNet2DConditionModel
|
||||
from .pipeline_utils import DiffusionPipeline
|
||||
|
||||
|
||||
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
|
||||
|
||||
EXAMPLE_DOC_STRING = """
|
||||
Examples:
|
||||
```py
|
||||
>>> import torch
|
||||
>>> from diffusers import StableDiffusionPipeline
|
||||
|
||||
>>> pipe = StableDiffusionPipeline.from_pretrained("runwayml/stable-diffusion-v1-5", torch_dtype=torch.float16)
|
||||
>>> pipe = pipe.to("cuda")
|
||||
|
||||
>>> prompt = "a photo of an astronaut riding a horse on mars"
|
||||
>>> image = pipe(prompt).images[0]
|
||||
```
|
||||
"""
|
||||
|
||||
def sample_gaussian_centered(n=1000, sample_size=100, std_dev=100):
|
||||
samples = []
|
||||
|
||||
while len(samples) < sample_size:
|
||||
# Sample from a Gaussian centered at n/2
|
||||
sample = int(np.random.normal(loc=n/2, scale=std_dev))
|
||||
|
||||
# Check if the sample is in bounds
|
||||
if 1 <= sample < n and sample not in samples:
|
||||
samples.append(sample)
|
||||
|
||||
return samples
|
||||
|
||||
def sample_from_quad(total_numbers, n_samples, pow=1.2):
|
||||
while pow > 1:
|
||||
# Generate linearly spaced values between 0 and a max value
|
||||
x_values = np.linspace(0, total_numbers**(1/pow), n_samples+1)
|
||||
|
||||
# Raise these values to the power of 1.5 to get a non-linear distribution
|
||||
indices = np.unique(np.int32(x_values**pow))[:-1]
|
||||
if len(indices) == n_samples:
|
||||
break
|
||||
pow -=0.02
|
||||
if pow <= 1:
|
||||
raise ValueError("Cannot find suitable pow. Please adjust n_samples or decrease center.")
|
||||
return indices, pow
|
||||
|
||||
def sample_from_quad_center(total_numbers, n_samples, center, pow=1.2):
|
||||
while pow > 1:
|
||||
# Generate linearly spaced values between 0 and a max value
|
||||
x_values = np.linspace((-center)**(1/pow), (total_numbers-center)**(1/pow), n_samples+1)
|
||||
indices = [0] + [x+center for x in np.unique(np.int32(x_values**pow))[1:-1]]
|
||||
if len(indices) == n_samples:
|
||||
break
|
||||
pow -=0.02
|
||||
if pow <= 1:
|
||||
raise ValueError("Cannot find suitable pow. Please adjust n_samples or decrease center.")
|
||||
return indices, pow
|
||||
|
||||
def rescale_noise_cfg(noise_cfg, noise_pred_text, guidance_rescale=0.0):
|
||||
"""
|
||||
Rescale `noise_cfg` according to `guidance_rescale`. Based on findings of [Common Diffusion Noise Schedules and
|
||||
Sample Steps are Flawed](https://arxiv.org/pdf/2305.08891.pdf). See Section 3.4
|
||||
"""
|
||||
std_text = noise_pred_text.std(dim=list(range(1, noise_pred_text.ndim)), keepdim=True)
|
||||
std_cfg = noise_cfg.std(dim=list(range(1, noise_cfg.ndim)), keepdim=True)
|
||||
# rescale the results from guidance (fixes overexposure)
|
||||
noise_pred_rescaled = noise_cfg * (std_text / std_cfg)
|
||||
# mix with the original results from guidance by factor guidance_rescale to avoid "plain looking" images
|
||||
noise_cfg = guidance_rescale * noise_pred_rescaled + (1 - guidance_rescale) * noise_cfg
|
||||
return noise_cfg
|
||||
|
||||
|
||||
class StableDiffusionPipeline(DiffusionPipeline, TextualInversionLoaderMixin, LoraLoaderMixin, FromSingleFileMixin):
|
||||
r"""
|
||||
Pipeline for text-to-image generation using Stable Diffusion.
|
||||
|
||||
This model inherits from [`DiffusionPipeline`]. Check the superclass documentation for the generic methods
|
||||
implemented for all pipelines (downloading, saving, running on a particular device, etc.).
|
||||
|
||||
The pipeline also inherits the following loading methods:
|
||||
- [`~loaders.TextualInversionLoaderMixin.load_textual_inversion`] for loading textual inversion embeddings
|
||||
- [`~loaders.LoraLoaderMixin.load_lora_weights`] for loading LoRA weights
|
||||
- [`~loaders.LoraLoaderMixin.save_lora_weights`] for saving LoRA weights
|
||||
- [`~loaders.FromSingleFileMixin.from_single_file`] for loading `.ckpt` files
|
||||
|
||||
Args:
|
||||
vae ([`AutoencoderKL`]):
|
||||
Variational Auto-Encoder (VAE) model to encode and decode images to and from latent representations.
|
||||
text_encoder ([`~transformers.CLIPTextModel`]):
|
||||
Frozen text-encoder ([clip-vit-large-patch14](https://huggingface.co/openai/clip-vit-large-patch14)).
|
||||
tokenizer ([`~transformers.CLIPTokenizer`]):
|
||||
A `CLIPTokenizer` to tokenize text.
|
||||
unet ([`UNet2DConditionModel`]):
|
||||
A `UNet2DConditionModel` to denoise the encoded image latents.
|
||||
scheduler ([`SchedulerMixin`]):
|
||||
A scheduler to be used in combination with `unet` to denoise the encoded image latents. Can be one of
|
||||
[`DDIMScheduler`], [`LMSDiscreteScheduler`], or [`PNDMScheduler`].
|
||||
safety_checker ([`StableDiffusionSafetyChecker`]):
|
||||
Classification module that estimates whether generated images could be considered offensive or harmful.
|
||||
Please refer to the [model card](https://huggingface.co/runwayml/stable-diffusion-v1-5) for more details
|
||||
about a model's potential harms.
|
||||
feature_extractor ([`~transformers.CLIPImageProcessor`]):
|
||||
A `CLIPImageProcessor` to extract features from generated images; used as inputs to the `safety_checker`.
|
||||
"""
|
||||
model_cpu_offload_seq = "text_encoder->unet->vae"
|
||||
_optional_components = ["safety_checker", "feature_extractor"]
|
||||
_exclude_from_cpu_offload = ["safety_checker"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vae: AutoencoderKL,
|
||||
text_encoder: CLIPTextModel,
|
||||
tokenizer: CLIPTokenizer,
|
||||
unet: UNet2DConditionModel,
|
||||
scheduler: KarrasDiffusionSchedulers,
|
||||
safety_checker: StableDiffusionSafetyChecker,
|
||||
feature_extractor: CLIPImageProcessor,
|
||||
requires_safety_checker: bool = True,
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
if hasattr(scheduler.config, "steps_offset") and scheduler.config.steps_offset != 1:
|
||||
deprecation_message = (
|
||||
f"The configuration file of this scheduler: {scheduler} is outdated. `steps_offset`"
|
||||
f" should be set to 1 instead of {scheduler.config.steps_offset}. Please make sure "
|
||||
"to update the config accordingly as leaving `steps_offset` might led to incorrect results"
|
||||
" in future versions. If you have downloaded this checkpoint from the Hugging Face Hub,"
|
||||
" it would be very nice if you could open a Pull request for the `scheduler/scheduler_config.json`"
|
||||
" file"
|
||||
)
|
||||
deprecate("steps_offset!=1", "1.0.0", deprecation_message, standard_warn=False)
|
||||
new_config = dict(scheduler.config)
|
||||
new_config["steps_offset"] = 1
|
||||
scheduler._internal_dict = FrozenDict(new_config)
|
||||
|
||||
if hasattr(scheduler.config, "clip_sample") and scheduler.config.clip_sample is True:
|
||||
deprecation_message = (
|
||||
f"The configuration file of this scheduler: {scheduler} has not set the configuration `clip_sample`."
|
||||
" `clip_sample` should be set to False in the configuration file. Please make sure to update the"
|
||||
" config accordingly as not setting `clip_sample` in the config might lead to incorrect results in"
|
||||
" future versions. If you have downloaded this checkpoint from the Hugging Face Hub, it would be very"
|
||||
" nice if you could open a Pull request for the `scheduler/scheduler_config.json` file"
|
||||
)
|
||||
deprecate("clip_sample not set", "1.0.0", deprecation_message, standard_warn=False)
|
||||
new_config = dict(scheduler.config)
|
||||
new_config["clip_sample"] = False
|
||||
scheduler._internal_dict = FrozenDict(new_config)
|
||||
|
||||
if safety_checker is None and requires_safety_checker:
|
||||
logger.warning(
|
||||
f"You have disabled the safety checker for {self.__class__} by passing `safety_checker=None`. Ensure"
|
||||
" that you abide to the conditions of the Stable Diffusion license and do not expose unfiltered"
|
||||
" results in services or applications open to the public. Both the diffusers team and Hugging Face"
|
||||
" strongly recommend to keep the safety filter enabled in all public facing circumstances, disabling"
|
||||
" it only for use-cases that involve analyzing network behavior or auditing its results. For more"
|
||||
" information, please have a look at https://github.com/huggingface/diffusers/pull/254 ."
|
||||
)
|
||||
|
||||
if safety_checker is not None and feature_extractor is None:
|
||||
raise ValueError(
|
||||
"Make sure to define a feature extractor when loading {self.__class__} if you want to use the safety"
|
||||
" checker. If you do not want to use the safety checker, you can pass `'safety_checker=None'` instead."
|
||||
)
|
||||
|
||||
is_unet_version_less_0_9_0 = hasattr(unet.config, "_diffusers_version") and version.parse(
|
||||
version.parse(unet.config._diffusers_version).base_version
|
||||
) < version.parse("0.9.0.dev0")
|
||||
is_unet_sample_size_less_64 = hasattr(unet.config, "sample_size") and unet.config.sample_size < 64
|
||||
if is_unet_version_less_0_9_0 and is_unet_sample_size_less_64:
|
||||
deprecation_message = (
|
||||
"The configuration file of the unet has set the default `sample_size` to smaller than"
|
||||
" 64 which seems highly unlikely. If your checkpoint is a fine-tuned version of any of the"
|
||||
" following: \n- CompVis/stable-diffusion-v1-4 \n- CompVis/stable-diffusion-v1-3 \n-"
|
||||
" CompVis/stable-diffusion-v1-2 \n- CompVis/stable-diffusion-v1-1 \n- runwayml/stable-diffusion-v1-5"
|
||||
" \n- runwayml/stable-diffusion-inpainting \n you should change 'sample_size' to 64 in the"
|
||||
" configuration file. Please make sure to update the config accordingly as leaving `sample_size=32`"
|
||||
" in the config might lead to incorrect results in future versions. If you have downloaded this"
|
||||
" checkpoint from the Hugging Face Hub, it would be very nice if you could open a Pull request for"
|
||||
" the `unet/config.json` file"
|
||||
)
|
||||
deprecate("sample_size<64", "1.0.0", deprecation_message, standard_warn=False)
|
||||
new_config = dict(unet.config)
|
||||
new_config["sample_size"] = 64
|
||||
unet._internal_dict = FrozenDict(new_config)
|
||||
|
||||
self.register_modules(
|
||||
vae=vae,
|
||||
text_encoder=text_encoder,
|
||||
tokenizer=tokenizer,
|
||||
unet=unet,
|
||||
scheduler=scheduler,
|
||||
safety_checker=safety_checker,
|
||||
feature_extractor=feature_extractor,
|
||||
)
|
||||
self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1)
|
||||
self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor)
|
||||
self.register_to_config(requires_safety_checker=requires_safety_checker)
|
||||
|
||||
def enable_vae_slicing(self):
|
||||
r"""
|
||||
Enable sliced VAE decoding. When this option is enabled, the VAE will split the input tensor in slices to
|
||||
compute decoding in several steps. This is useful to save some memory and allow larger batch sizes.
|
||||
"""
|
||||
self.vae.enable_slicing()
|
||||
|
||||
def disable_vae_slicing(self):
|
||||
r"""
|
||||
Disable sliced VAE decoding. If `enable_vae_slicing` was previously enabled, this method will go back to
|
||||
computing decoding in one step.
|
||||
"""
|
||||
self.vae.disable_slicing()
|
||||
|
||||
def enable_vae_tiling(self):
|
||||
r"""
|
||||
Enable tiled VAE decoding. When this option is enabled, the VAE will split the input tensor into tiles to
|
||||
compute decoding and encoding in several steps. This is useful for saving a large amount of memory and to allow
|
||||
processing larger images.
|
||||
"""
|
||||
self.vae.enable_tiling()
|
||||
|
||||
def disable_vae_tiling(self):
|
||||
r"""
|
||||
Disable tiled VAE decoding. If `enable_vae_tiling` was previously enabled, this method will go back to
|
||||
computing decoding in one step.
|
||||
"""
|
||||
self.vae.disable_tiling()
|
||||
|
||||
def _encode_prompt(
|
||||
self,
|
||||
prompt,
|
||||
device,
|
||||
num_images_per_prompt,
|
||||
do_classifier_free_guidance,
|
||||
negative_prompt=None,
|
||||
prompt_embeds: Optional[torch.FloatTensor] = None,
|
||||
negative_prompt_embeds: Optional[torch.FloatTensor] = None,
|
||||
lora_scale: Optional[float] = None,
|
||||
):
|
||||
deprecation_message = "`_encode_prompt()` is deprecated and it will be removed in a future version. Use `encode_prompt()` instead. Also, be aware that the output format changed from a concatenated tensor to a tuple."
|
||||
deprecate("_encode_prompt()", "1.0.0", deprecation_message, standard_warn=False)
|
||||
|
||||
prompt_embeds_tuple = self.encode_prompt(
|
||||
prompt=prompt,
|
||||
device=device,
|
||||
num_images_per_prompt=num_images_per_prompt,
|
||||
do_classifier_free_guidance=do_classifier_free_guidance,
|
||||
negative_prompt=negative_prompt,
|
||||
prompt_embeds=prompt_embeds,
|
||||
negative_prompt_embeds=negative_prompt_embeds,
|
||||
lora_scale=lora_scale,
|
||||
)
|
||||
|
||||
# concatenate for backwards comp
|
||||
prompt_embeds = torch.cat([prompt_embeds_tuple[1], prompt_embeds_tuple[0]])
|
||||
|
||||
return prompt_embeds
|
||||
|
||||
def encode_prompt(
|
||||
self,
|
||||
prompt,
|
||||
device,
|
||||
num_images_per_prompt,
|
||||
do_classifier_free_guidance,
|
||||
negative_prompt=None,
|
||||
prompt_embeds: Optional[torch.FloatTensor] = None,
|
||||
negative_prompt_embeds: Optional[torch.FloatTensor] = None,
|
||||
lora_scale: Optional[float] = None,
|
||||
):
|
||||
r"""
|
||||
Encodes the prompt into text encoder hidden states.
|
||||
|
||||
Args:
|
||||
prompt (`str` or `List[str]`, *optional*):
|
||||
prompt to be encoded
|
||||
device: (`torch.device`):
|
||||
torch device
|
||||
num_images_per_prompt (`int`):
|
||||
number of images that should be generated per prompt
|
||||
do_classifier_free_guidance (`bool`):
|
||||
whether to use classifier free guidance or not
|
||||
negative_prompt (`str` or `List[str]`, *optional*):
|
||||
The prompt or prompts not to guide the image generation. If not defined, one has to pass
|
||||
`negative_prompt_embeds` instead. Ignored when not using guidance (i.e., ignored if `guidance_scale` is
|
||||
less than `1`).
|
||||
prompt_embeds (`torch.FloatTensor`, *optional*):
|
||||
Pre-generated text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt weighting. If not
|
||||
provided, text embeddings will be generated from `prompt` input argument.
|
||||
negative_prompt_embeds (`torch.FloatTensor`, *optional*):
|
||||
Pre-generated negative text embeddings. Can be used to easily tweak text inputs, *e.g.* prompt
|
||||
weighting. If not provided, negative_prompt_embeds will be generated from `negative_prompt` input
|
||||
argument.
|
||||
lora_scale (`float`, *optional*):
|
||||
A lora scale that will be applied to all LoRA layers of the text encoder if LoRA layers are loaded.
|
||||
"""
|
||||
# set lora scale so that monkey patched LoRA
|
||||
# function of text encoder can correctly access it
|
||||
if lora_scale is not None and isinstance(self, LoraLoaderMixin):
|
||||
self._lora_scale = lora_scale
|
||||
|
||||
# dynamically adjust the LoRA scale
|
||||
adjust_lora_scale_text_encoder(self.text_encoder, lora_scale)
|
||||
|
||||
if prompt is not None and isinstance(prompt, str):
|
||||
batch_size = 1
|
||||
elif prompt is not None and isinstance(prompt, list):
|
||||
batch_size = len(prompt)
|
||||
else:
|
||||
batch_size = prompt_embeds.shape[0]
|
||||
|
||||
if prompt_embeds is None:
|
||||
# textual inversion: procecss multi-vector tokens if necessary
|
||||
if isinstance(self, TextualInversionLoaderMixin):
|
||||
prompt = self.maybe_convert_prompt(prompt, self.tokenizer)
|
||||
|
||||
text_inputs = self.tokenizer(
|
||||
prompt,
|
||||
padding="max_length",
|
||||
max_length=self.tokenizer.model_max_length,
|
||||
truncation=True,
|
||||
return_tensors="pt",
|
||||
)
|
||||
text_input_ids = text_inputs.input_ids
|
||||
untruncated_ids = self.tokenizer(prompt, padding="longest", return_tensors="pt").input_ids
|
||||
|
||||
if untruncated_ids.shape[-1] >= text_input_ids.shape[-1] and not torch.equal(
|
||||
text_input_ids, untruncated_ids
|
||||
):
|
||||
removed_text = self.tokenizer.batch_decode(
|
||||
untruncated_ids[:, self.tokenizer.model_max_length - 1 : -1]
|
||||
)
|
||||
logger.warning(
|
||||
"The following part of your input was truncated because CLIP can only handle sequences up to"
|
||||
f" {self.tokenizer.model_max_length} tokens: {removed_text}"
|
||||
)
|
||||
|
||||
if hasattr(self.text_encoder.config, "use_attention_mask") and self.text_encoder.config.use_attention_mask:
|
||||
attention_mask = text_inputs.attention_mask.to(device)
|
||||
else:
|
||||
attention_mask = None
|
||||
|
||||
prompt_embeds = self.text_encoder(
|
||||
text_input_ids.to(device),
|
||||
attention_mask=attention_mask,
|
||||
)
|
||||
prompt_embeds = prompt_embeds[0]
|
||||
|
||||
if self.text_encoder is not None:
|
||||
prompt_embeds_dtype = self.text_encoder.dtype
|
||||
elif self.unet is not None:
|
||||
prompt_embeds_dtype = self.unet.dtype
|
||||
else:
|
||||
prompt_embeds_dtype = prompt_embeds.dtype
|
||||
|
||||
prompt_embeds = prompt_embeds.to(dtype=prompt_embeds_dtype, device=device)
|
||||
|
||||
bs_embed, seq_len, _ = prompt_embeds.shape
|
||||
# duplicate text embeddings for each generation per prompt, using mps friendly method
|
||||
prompt_embeds = prompt_embeds.repeat(1, num_images_per_prompt, 1)
|
||||
prompt_embeds = prompt_embeds.view(bs_embed * num_images_per_prompt, seq_len, -1)
|
||||
|
||||
# get unconditional embeddings for classifier free guidance
|
||||
if do_classifier_free_guidance and negative_prompt_embeds is None:
|
||||
uncond_tokens: List[str]
|
||||
if negative_prompt is None:
|
||||
uncond_tokens = [""] * batch_size
|
||||
elif prompt is not None and type(prompt) is not type(negative_prompt):
|
||||
raise TypeError(
|
||||
f"`negative_prompt` should be the same type to `prompt`, but got {type(negative_prompt)} !="
|
||||
f" {type(prompt)}."
|
||||
)
|
||||
elif isinstance(negative_prompt, str):
|
||||
uncond_tokens = [negative_prompt]
|
||||
elif batch_size != len(negative_prompt):
|
||||
raise ValueError(
|
||||
f"`negative_prompt`: {negative_prompt} has batch size {len(negative_prompt)}, but `prompt`:"
|
||||
f" {prompt} has batch size {batch_size}. Please make sure that passed `negative_prompt` matches"
|
||||
" the batch size of `prompt`."
|
||||
)
|
||||
else:
|
||||
uncond_tokens = negative_prompt
|
||||
|
||||
# textual inversion: procecss multi-vector tokens if necessary
|
||||
if isinstance(self, TextualInversionLoaderMixin):
|
||||
uncond_tokens = self.maybe_convert_prompt(uncond_tokens, self.tokenizer)
|
||||
|
||||
max_length = prompt_embeds.shape[1]
|
||||
uncond_input = self.tokenizer(
|
||||
uncond_tokens,
|
||||
padding="max_length",
|
||||
max_length=max_length,
|
||||
truncation=True,
|
||||
return_tensors="pt",
|
||||
)
|
||||
|
||||
if hasattr(self.text_encoder.config, "use_attention_mask") and self.text_encoder.config.use_attention_mask:
|
||||
attention_mask = uncond_input.attention_mask.to(device)
|
||||
else:
|
||||
attention_mask = None
|
||||
|
||||
negative_prompt_embeds = self.text_encoder(
|
||||
uncond_input.input_ids.to(device),
|
||||
attention_mask=attention_mask,
|
||||
)
|
||||
negative_prompt_embeds = negative_prompt_embeds[0]
|
||||
|
||||
if do_classifier_free_guidance:
|
||||
# duplicate unconditional embeddings for each generation per prompt, using mps friendly method
|
||||
seq_len = negative_prompt_embeds.shape[1]
|
||||
|
||||
negative_prompt_embeds = negative_prompt_embeds.to(dtype=prompt_embeds_dtype, device=device)
|
||||
|
||||
negative_prompt_embeds = negative_prompt_embeds.repeat(1, num_images_per_prompt, 1)
|
||||
negative_prompt_embeds = negative_prompt_embeds.view(batch_size * num_images_per_prompt, seq_len, -1)
|
||||
|
||||
return prompt_embeds, negative_prompt_embeds
|
||||
|
||||
def run_safety_checker(self, image, device, dtype):
|
||||
if self.safety_checker is None:
|
||||
has_nsfw_concept = None
|
||||
else:
|
||||
if torch.is_tensor(image):
|
||||
feature_extractor_input = self.image_processor.postprocess(image, output_type="pil")
|
||||
else:
|
||||
feature_extractor_input = self.image_processor.numpy_to_pil(image)
|
||||
safety_checker_input = self.feature_extractor(feature_extractor_input, return_tensors="pt").to(device)
|
||||
image, has_nsfw_concept = self.safety_checker(
|
||||
images=image, clip_input=safety_checker_input.pixel_values.to(dtype)
|
||||
)
|
||||
return image, has_nsfw_concept
|
||||
|
||||
def decode_latents(self, latents):
|
||||
deprecation_message = "The decode_latents method is deprecated and will be removed in 1.0.0. Please use VaeImageProcessor.postprocess(...) instead"
|
||||
deprecate("decode_latents", "1.0.0", deprecation_message, standard_warn=False)
|
||||
|
||||
latents = 1 / self.vae.config.scaling_factor * latents
|
||||
image = self.vae.decode(latents, return_dict=False)[0]
|
||||
image = (image / 2 + 0.5).clamp(0, 1)
|
||||
# we always cast to float32 as this does not cause significant overhead and is compatible with bfloat16
|
||||
image = image.cpu().permute(0, 2, 3, 1).float().numpy()
|
||||
return image
|
||||
|
||||
def prepare_extra_step_kwargs(self, generator, eta):
|
||||
# prepare extra kwargs for the scheduler step, since not all schedulers have the same signature
|
||||
# eta (η) is only used with the DDIMScheduler, it will be ignored for other schedulers.
|
||||
# eta corresponds to η in DDIM paper: https://arxiv.org/abs/2010.02502
|
||||
# and should be between [0, 1]
|
||||
|
||||
accepts_eta = "eta" in set(inspect.signature(self.scheduler.step).parameters.keys())
|
||||
extra_step_kwargs = {}
|
||||
if accepts_eta:
|
||||
extra_step_kwargs["eta"] = eta
|
||||
|
||||
# check if the scheduler accepts generator
|
||||
accepts_generator = "generator" in set(inspect.signature(self.scheduler.step).parameters.keys())
|
||||
if accepts_generator:
|
||||
extra_step_kwargs["generator"] = generator
|
||||
return extra_step_kwargs
|
||||
|
||||
def check_inputs(
|
||||
self,
|
||||
prompt,
|
||||
height,
|
||||
width,
|
||||
callback_steps,
|
||||
negative_prompt=None,
|
||||
prompt_embeds=None,
|
||||
negative_prompt_embeds=None,
|
||||
):
|
||||
if height % 8 != 0 or width % 8 != 0:
|
||||
raise ValueError(f"`height` and `width` have to be divisible by 8 but are {height} and {width}.")
|
||||
|
||||
if (callback_steps is None) or (
|
||||
callback_steps is not None and (not isinstance(callback_steps, int) or callback_steps <= 0)
|
||||
):
|
||||
raise ValueError(
|
||||
f"`callback_steps` has to be a positive integer but is {callback_steps} of type"
|
||||
f" {type(callback_steps)}."
|
||||
)
|
||||
|
||||
if prompt is not None and prompt_embeds is not None:
|
||||
raise ValueError(
|
||||
f"Cannot forward both `prompt`: {prompt} and `prompt_embeds`: {prompt_embeds}. Please make sure to"
|
||||
" only forward one of the two."
|
||||
)
|
||||
elif prompt is None and prompt_embeds is None:
|
||||
raise ValueError(
|
||||
"Provide either `prompt` or `prompt_embeds`. Cannot leave both `prompt` and `prompt_embeds` undefined."
|
||||
)
|
||||
elif prompt is not None and (not isinstance(prompt, str) and not isinstance(prompt, list)):
|
||||
raise ValueError(f"`prompt` has to be of type `str` or `list` but is {type(prompt)}")
|
||||
|
||||
if negative_prompt is not None and negative_prompt_embeds is not None:
|
||||
raise ValueError(
|
||||
f"Cannot forward both `negative_prompt`: {negative_prompt} and `negative_prompt_embeds`:"
|
||||
f" {negative_prompt_embeds}. Please make sure to only forward one of the two."
|
||||
)
|
||||
|
||||
if prompt_embeds is not None and negative_prompt_embeds is not None:
|
||||
if prompt_embeds.shape != negative_prompt_embeds.shape:
|
||||
raise ValueError(
|
||||
"`prompt_embeds` and `negative_prompt_embeds` must have the same shape when passed directly, but"
|
||||
f" got: `prompt_embeds` {prompt_embeds.shape} != `negative_prompt_embeds`"
|
||||
f" {negative_prompt_embeds.shape}."
|
||||
)
|
||||
|
||||
def prepare_latents(self, batch_size, num_channels_latents, height, width, dtype, device, generator, latents=None):
|
||||
shape = (batch_size, num_channels_latents, height // self.vae_scale_factor, width // self.vae_scale_factor)
|
||||
if isinstance(generator, list) and len(generator) != batch_size:
|
||||
raise ValueError(
|
||||
f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"
|
||||
f" size of {batch_size}. Make sure the batch size matches the length of the generators."
|
||||
)
|
||||
|
||||
if latents is None:
|
||||
latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
|
||||
else:
|
||||
latents = latents.to(device)
|
||||
|
||||
# scale the initial noise by the standard deviation required by the scheduler
|
||||
latents = latents * self.scheduler.init_noise_sigma
|
||||
return latents
|
||||
|
||||
@torch.no_grad()
|
||||
@replace_example_docstring(EXAMPLE_DOC_STRING)
|
||||
def __call__(
|
||||
self,
|
||||
prompt: Union[str, List[str]] = None,
|
||||
height: Optional[int] = None,
|
||||
width: Optional[int] = None,
|
||||
num_inference_steps: int = 50,
|
||||
guidance_scale: float = 7.5,
|
||||
negative_prompt: Optional[Union[str, List[str]]] = None,
|
||||
num_images_per_prompt: Optional[int] = 1,
|
||||
eta: float = 0.0,
|
||||
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
|
||||
latents: Optional[torch.FloatTensor] = None,
|
||||
prompt_embeds: Optional[torch.FloatTensor] = None,
|
||||
negative_prompt_embeds: Optional[torch.FloatTensor] = None,
|
||||
output_type: Optional[str] = "pil",
|
||||
return_dict: bool = True,
|
||||
callback: Optional[Callable[[int, int, torch.FloatTensor], None]] = None,
|
||||
callback_steps: int = 1,
|
||||
cross_attention_kwargs: Optional[Dict[str, Any]] = None,
|
||||
guidance_rescale: float = 0.0,
|
||||
cache_interval: int = 1,
|
||||
cache_layer_id: int = None,
|
||||
cache_block_id: int = None,
|
||||
uniform: bool = True,
|
||||
pow: float = None,
|
||||
center: int = None,
|
||||
output_all_sequence: bool = False,
|
||||
):
|
||||
r"""
|
||||
The call function to the pipeline for generation.
|
||||
|
||||
Args:
|
||||
prompt (`str` or `List[str]`, *optional*):
|
||||
The prompt or prompts to guide image generation. If not defined, you need to pass `prompt_embeds`.
|
||||
height (`int`, *optional*, defaults to `self.unet.config.sample_size * self.vae_scale_factor`):
|
||||
The height in pixels of the generated image.
|
||||
width (`int`, *optional*, defaults to `self.unet.config.sample_size * self.vae_scale_factor`):
|
||||
The width in pixels of the generated image.
|
||||
num_inference_steps (`int`, *optional*, defaults to 50):
|
||||
The number of denoising steps. More denoising steps usually lead to a higher quality image at the
|
||||
expense of slower inference.
|
||||
guidance_scale (`float`, *optional*, defaults to 7.5):
|
||||
A higher guidance scale value encourages the model to generate images closely linked to the text
|
||||
`prompt` at the expense of lower image quality. Guidance scale is enabled when `guidance_scale > 1`.
|
||||
negative_prompt (`str` or `List[str]`, *optional*):
|
||||
The prompt or prompts to guide what to not include in image generation. If not defined, you need to
|
||||
pass `negative_prompt_embeds` instead. Ignored when not using guidance (`guidance_scale < 1`).
|
||||
num_images_per_prompt (`int`, *optional*, defaults to 1):
|
||||
The number of images to generate per prompt.
|
||||
eta (`float`, *optional*, defaults to 0.0):
|
||||
Corresponds to parameter eta (η) from the [DDIM](https://arxiv.org/abs/2010.02502) paper. Only applies
|
||||
to the [`~schedulers.DDIMScheduler`], and is ignored in other schedulers.
|
||||
generator (`torch.Generator` or `List[torch.Generator]`, *optional*):
|
||||
A [`torch.Generator`](https://pytorch.org/docs/stable/generated/torch.Generator.html) to make
|
||||
generation deterministic.
|
||||
latents (`torch.FloatTensor`, *optional*):
|
||||
Pre-generated noisy latents sampled from a Gaussian distribution, to be used as inputs for image
|
||||
generation. Can be used to tweak the same generation with different prompts. If not provided, a latents
|
||||
tensor is generated by sampling using the supplied random `generator`.
|
||||
prompt_embeds (`torch.FloatTensor`, *optional*):
|
||||
Pre-generated text embeddings. Can be used to easily tweak text inputs (prompt weighting). If not
|
||||
provided, text embeddings are generated from the `prompt` input argument.
|
||||
negative_prompt_embeds (`torch.FloatTensor`, *optional*):
|
||||
Pre-generated negative text embeddings. Can be used to easily tweak text inputs (prompt weighting). If
|
||||
not provided, `negative_prompt_embeds` are generated from the `negative_prompt` input argument.
|
||||
output_type (`str`, *optional*, defaults to `"pil"`):
|
||||
The output format of the generated image. Choose between `PIL.Image` or `np.array`.
|
||||
return_dict (`bool`, *optional*, defaults to `True`):
|
||||
Whether or not to return a [`~pipelines.stable_diffusion.StableDiffusionPipelineOutput`] instead of a
|
||||
plain tuple.
|
||||
callback (`Callable`, *optional*):
|
||||
A function that calls every `callback_steps` steps during inference. The function is called with the
|
||||
following arguments: `callback(step: int, timestep: int, latents: torch.FloatTensor)`.
|
||||
callback_steps (`int`, *optional*, defaults to 1):
|
||||
The frequency at which the `callback` function is called. If not specified, the callback is called at
|
||||
every step.
|
||||
cross_attention_kwargs (`dict`, *optional*):
|
||||
A kwargs dictionary that if specified is passed along to the [`AttentionProcessor`] as defined in
|
||||
[`self.processor`](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py).
|
||||
guidance_rescale (`float`, *optional*, defaults to 0.7):
|
||||
Guidance rescale factor from [Common Diffusion Noise Schedules and Sample Steps are
|
||||
Flawed](https://arxiv.org/pdf/2305.08891.pdf). Guidance rescale factor should fix overexposure when
|
||||
using zero terminal SNR.
|
||||
|
||||
Examples:
|
||||
|
||||
Returns:
|
||||
[`~pipelines.stable_diffusion.StableDiffusionPipelineOutput`] or `tuple`:
|
||||
If `return_dict` is `True`, [`~pipelines.stable_diffusion.StableDiffusionPipelineOutput`] is returned,
|
||||
otherwise a `tuple` is returned where the first element is a list with the generated images and the
|
||||
second element is a list of `bool`s indicating whether the corresponding generated image contains
|
||||
"not-safe-for-work" (nsfw) content.
|
||||
"""
|
||||
# 0. Default height and width to unet
|
||||
height = height or self.unet.config.sample_size * self.vae_scale_factor
|
||||
width = width or self.unet.config.sample_size * self.vae_scale_factor
|
||||
|
||||
# 1. Check inputs. Raise error if not correct
|
||||
self.check_inputs(
|
||||
prompt, height, width, callback_steps, negative_prompt, prompt_embeds, negative_prompt_embeds
|
||||
)
|
||||
|
||||
# 2. Define call parameters
|
||||
if prompt is not None and isinstance(prompt, str):
|
||||
batch_size = 1
|
||||
elif prompt is not None and isinstance(prompt, list):
|
||||
batch_size = len(prompt)
|
||||
else:
|
||||
batch_size = prompt_embeds.shape[0]
|
||||
|
||||
device = self._execution_device
|
||||
# here `guidance_scale` is defined analog to the guidance weight `w` of equation (2)
|
||||
# of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1`
|
||||
# corresponds to doing no classifier free guidance.
|
||||
do_classifier_free_guidance = guidance_scale > 1.0
|
||||
|
||||
# 3. Encode input prompt
|
||||
text_encoder_lora_scale = (
|
||||
cross_attention_kwargs.get("scale", None) if cross_attention_kwargs is not None else None
|
||||
)
|
||||
prompt_embeds, negative_prompt_embeds = self.encode_prompt(
|
||||
prompt,
|
||||
device,
|
||||
num_images_per_prompt,
|
||||
do_classifier_free_guidance,
|
||||
negative_prompt,
|
||||
prompt_embeds=prompt_embeds,
|
||||
negative_prompt_embeds=negative_prompt_embeds,
|
||||
lora_scale=text_encoder_lora_scale,
|
||||
)
|
||||
# For classifier free guidance, we need to do two forward passes.
|
||||
# Here we concatenate the unconditional and text embeddings into a single batch
|
||||
# to avoid doing two forward passes
|
||||
if do_classifier_free_guidance:
|
||||
prompt_embeds = torch.cat([negative_prompt_embeds, prompt_embeds])
|
||||
|
||||
# 4. Prepare timesteps
|
||||
self.scheduler.set_timesteps(num_inference_steps, device=device)
|
||||
timesteps = self.scheduler.timesteps
|
||||
|
||||
# 5. Prepare latent variables
|
||||
num_channels_latents = self.unet.config.in_channels
|
||||
latents = self.prepare_latents(
|
||||
batch_size * num_images_per_prompt,
|
||||
num_channels_latents,
|
||||
height,
|
||||
width,
|
||||
prompt_embeds.dtype,
|
||||
device,
|
||||
generator,
|
||||
latents,
|
||||
)
|
||||
|
||||
# 6. Prepare extra step kwargs. TODO: Logic should ideally just be moved out of the pipeline
|
||||
extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta)
|
||||
|
||||
# 7. Denoising loop
|
||||
num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order
|
||||
|
||||
prv_features = None
|
||||
latents_list = [latents]
|
||||
|
||||
if cache_interval == 1:
|
||||
interval_seq = list(range(num_inference_steps))
|
||||
else:
|
||||
if uniform:
|
||||
interval_seq = list(range(0, num_inference_steps, cache_interval))
|
||||
else:
|
||||
num_slow_step = num_inference_steps//cache_interval
|
||||
if num_inference_steps%cache_interval != 0:
|
||||
num_slow_step += 1
|
||||
|
||||
interval_seq, pow = sample_from_quad_center(num_inference_steps, num_slow_step, center=center, pow=pow)#[0, 3, 6, 9, 12, 16, 22, 28, 35, 43,]
|
||||
#interval_seq, pow = sample_from_quad(num_inference_steps, num_inference_steps//cache_interval, pow=pow)#[0, 3, 6, 9, 12, 16, 22, 28, 35, 43,]
|
||||
|
||||
interval_seq = sorted(interval_seq)
|
||||
#print(interval_seq, len(interval_seq), pow)
|
||||
|
||||
with self.progress_bar(total=num_inference_steps) as progress_bar:
|
||||
#print("[INFO] Update Feature Interval = {}, Update Layer Number = {}, Update Block Number = {}".format(cache_interval, cache_layer_id, cache_block_id))
|
||||
for i, t in enumerate(timesteps):
|
||||
# expand the latents if we are doing classifier free guidance
|
||||
latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents
|
||||
latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)
|
||||
|
||||
if i in interval_seq:
|
||||
prv_features = None
|
||||
|
||||
# predict the noise residual
|
||||
noise_pred, prv_features = self.unet(
|
||||
latent_model_input,
|
||||
t,
|
||||
encoder_hidden_states=prompt_embeds,
|
||||
cross_attention_kwargs=cross_attention_kwargs,
|
||||
replicate_prv_feature=prv_features,
|
||||
quick_replicate= cache_interval>1,
|
||||
cache_layer_id=cache_layer_id,
|
||||
cache_block_id=cache_block_id,
|
||||
return_dict=False,
|
||||
)
|
||||
|
||||
# perform guidance
|
||||
if do_classifier_free_guidance:
|
||||
noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
|
||||
noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)
|
||||
|
||||
if do_classifier_free_guidance and guidance_rescale > 0.0:
|
||||
# Based on 3.4. in https://arxiv.org/pdf/2305.08891.pdf
|
||||
noise_pred = rescale_noise_cfg(noise_pred, noise_pred_text, guidance_rescale=guidance_rescale)
|
||||
|
||||
# compute the previous noisy sample x_t -> x_t-1
|
||||
latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs, return_dict=False)[0]
|
||||
latents_list.append(latents)
|
||||
|
||||
# call the callback, if provided
|
||||
if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0):
|
||||
progress_bar.update()
|
||||
if callback is not None and i % callback_steps == 0:
|
||||
callback(i, t, latents)
|
||||
|
||||
if not output_type == "latent":
|
||||
if output_all_sequence:
|
||||
image = [self.vae.decode(latents / self.vae.config.scaling_factor, return_dict=False)[0] for latents in latents_list]
|
||||
has_nsfw_concept = None #self.run_safety_checker(images[0], device, prompt_embeds.dtype)
|
||||
num_img = len(image)
|
||||
else:
|
||||
image = self.vae.decode(latents / self.vae.config.scaling_factor, return_dict=False)[0]
|
||||
has_nsfw_concept = None
|
||||
num_img = image.shape[0]
|
||||
else:
|
||||
image = latents
|
||||
has_nsfw_concept = None
|
||||
|
||||
if has_nsfw_concept is None:
|
||||
do_denormalize = [True] * num_img
|
||||
else:
|
||||
do_denormalize = [not has_nsfw for has_nsfw in has_nsfw_concept]
|
||||
|
||||
if output_all_sequence:
|
||||
image = [self.image_processor.postprocess(img, output_type=output_type, do_denormalize=do_denormalize) for img in image]
|
||||
else:
|
||||
image = self.image_processor.postprocess(image, output_type=output_type, do_denormalize=do_denormalize)
|
||||
|
||||
# Offload all models
|
||||
self.maybe_free_model_hooks()
|
||||
if not return_dict:
|
||||
return (image, has_nsfw_concept,)
|
||||
|
||||
return StableDiffusionPipelineOutput(images=image, nsfw_content_detected=has_nsfw_concept)
|
||||
741
ixformer_sdk/contrib/DeepCache/sd/pipeline_text_to_video_zero.py
Normal file
741
ixformer_sdk/contrib/DeepCache/sd/pipeline_text_to_video_zero.py
Normal file
@@ -0,0 +1,741 @@
|
||||
import copy
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable, List, Optional, Union
|
||||
|
||||
import numpy as np
|
||||
import PIL.Image
|
||||
import torch
|
||||
import torch.nn.functional as F
|
||||
from torch.nn.functional import grid_sample
|
||||
from transformers import CLIPImageProcessor, CLIPTextModel, CLIPTokenizer
|
||||
|
||||
from diffusers.models import AutoencoderKL
|
||||
from .unet_2d_condition import UNet2DConditionModel
|
||||
from .pipeline_stable_diffusion import StableDiffusionPipeline, StableDiffusionSafetyChecker
|
||||
from diffusers.schedulers import KarrasDiffusionSchedulers
|
||||
from diffusers.utils import BaseOutput
|
||||
from diffusers.utils.torch_utils import randn_tensor
|
||||
|
||||
def sample_gaussian_centered(n=1000, sample_size=100, std_dev=100):
|
||||
samples = []
|
||||
|
||||
while len(samples) < sample_size:
|
||||
# Sample from a Gaussian centered at n/2
|
||||
sample = int(np.random.normal(loc=n/2, scale=std_dev))
|
||||
|
||||
# Check if the sample is in bounds
|
||||
if 1 <= sample < n and sample not in samples:
|
||||
samples.append(sample)
|
||||
|
||||
return samples
|
||||
|
||||
def sample_from_quad(total_numbers, n_samples, pow=1.2):
|
||||
while pow > 1:
|
||||
# Generate linearly spaced values between 0 and a max value
|
||||
x_values = np.linspace(0, total_numbers**(1/pow), n_samples+1)
|
||||
|
||||
# Raise these values to the power of 1.5 to get a non-linear distribution
|
||||
indices = np.unique(np.int32(x_values**pow))[:-1]
|
||||
if len(indices) == n_samples:
|
||||
break
|
||||
pow -=0.02
|
||||
if pow <= 1:
|
||||
raise ValueError("Cannot find suitable pow. Please adjust n_samples or decrease center.")
|
||||
return indices, pow
|
||||
|
||||
def sample_from_quad_center(total_numbers, n_samples, center, pow=1.2):
|
||||
while pow > 1:
|
||||
# Generate linearly spaced values between 0 and a max value
|
||||
x_values = np.linspace((-center)**(1/pow), (total_numbers-center)**(1/pow), n_samples+1)
|
||||
indices = [0] + [x+center for x in np.unique(np.int32(x_values**pow))[1:-1]]
|
||||
if len(indices) == n_samples:
|
||||
break
|
||||
pow -=0.02
|
||||
if pow <= 1:
|
||||
raise ValueError("Cannot find suitable pow. Please adjust n_samples or decrease center.")
|
||||
return indices, pow
|
||||
|
||||
def rearrange_0(tensor, f):
|
||||
F, C, H, W = tensor.size()
|
||||
tensor = torch.permute(torch.reshape(tensor, (F // f, f, C, H, W)), (0, 2, 1, 3, 4))
|
||||
return tensor
|
||||
|
||||
|
||||
def rearrange_1(tensor):
|
||||
B, C, F, H, W = tensor.size()
|
||||
return torch.reshape(torch.permute(tensor, (0, 2, 1, 3, 4)), (B * F, C, H, W))
|
||||
|
||||
|
||||
def rearrange_3(tensor, f):
|
||||
F, D, C = tensor.size()
|
||||
return torch.reshape(tensor, (F // f, f, D, C))
|
||||
|
||||
|
||||
def rearrange_4(tensor):
|
||||
B, F, D, C = tensor.size()
|
||||
return torch.reshape(tensor, (B * F, D, C))
|
||||
|
||||
|
||||
class CrossFrameAttnProcessor:
|
||||
"""
|
||||
Cross frame attention processor. Each frame attends the first frame.
|
||||
|
||||
Args:
|
||||
batch_size: The number that represents actual batch size, other than the frames.
|
||||
For example, calling unet with a single prompt and num_images_per_prompt=1, batch_size should be equal to
|
||||
2, due to classifier-free guidance.
|
||||
"""
|
||||
|
||||
def __init__(self, batch_size=2):
|
||||
self.batch_size = batch_size
|
||||
|
||||
def __call__(self, attn, hidden_states, encoder_hidden_states=None, attention_mask=None):
|
||||
batch_size, sequence_length, _ = hidden_states.shape
|
||||
attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size)
|
||||
query = attn.to_q(hidden_states)
|
||||
|
||||
is_cross_attention = encoder_hidden_states is not None
|
||||
if encoder_hidden_states is None:
|
||||
encoder_hidden_states = hidden_states
|
||||
elif attn.norm_cross:
|
||||
encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states)
|
||||
|
||||
key = attn.to_k(encoder_hidden_states)
|
||||
value = attn.to_v(encoder_hidden_states)
|
||||
|
||||
# Cross Frame Attention
|
||||
if not is_cross_attention:
|
||||
video_length = key.size()[0] // self.batch_size
|
||||
first_frame_index = [0] * video_length
|
||||
|
||||
# rearrange keys to have batch and frames in the 1st and 2nd dims respectively
|
||||
key = rearrange_3(key, video_length)
|
||||
key = key[:, first_frame_index]
|
||||
# rearrange values to have batch and frames in the 1st and 2nd dims respectively
|
||||
value = rearrange_3(value, video_length)
|
||||
value = value[:, first_frame_index]
|
||||
|
||||
# rearrange back to original shape
|
||||
key = rearrange_4(key)
|
||||
value = rearrange_4(value)
|
||||
|
||||
query = attn.head_to_batch_dim(query)
|
||||
key = attn.head_to_batch_dim(key)
|
||||
value = attn.head_to_batch_dim(value)
|
||||
|
||||
attention_probs = attn.get_attention_scores(query, key, attention_mask)
|
||||
hidden_states = torch.bmm(attention_probs, value)
|
||||
hidden_states = attn.batch_to_head_dim(hidden_states)
|
||||
|
||||
# linear proj
|
||||
hidden_states = attn.to_out[0](hidden_states)
|
||||
# dropout
|
||||
hidden_states = attn.to_out[1](hidden_states)
|
||||
|
||||
return hidden_states
|
||||
|
||||
|
||||
class CrossFrameAttnProcessor2_0:
|
||||
"""
|
||||
Cross frame attention processor with scaled_dot_product attention of Pytorch 2.0.
|
||||
|
||||
Args:
|
||||
batch_size: The number that represents actual batch size, other than the frames.
|
||||
For example, calling unet with a single prompt and num_images_per_prompt=1, batch_size should be equal to
|
||||
2, due to classifier-free guidance.
|
||||
"""
|
||||
|
||||
def __init__(self, batch_size=2):
|
||||
if not hasattr(F, "scaled_dot_product_attention"):
|
||||
raise ImportError("AttnProcessor2_0 requires PyTorch 2.0, to use it, please upgrade PyTorch to 2.0.")
|
||||
self.batch_size = batch_size
|
||||
|
||||
def __call__(self, attn, hidden_states, encoder_hidden_states=None, attention_mask=None):
|
||||
batch_size, sequence_length, _ = (
|
||||
hidden_states.shape if encoder_hidden_states is None else encoder_hidden_states.shape
|
||||
)
|
||||
inner_dim = hidden_states.shape[-1]
|
||||
|
||||
if attention_mask is not None:
|
||||
attention_mask = attn.prepare_attention_mask(attention_mask, sequence_length, batch_size)
|
||||
# scaled_dot_product_attention expects attention_mask shape to be
|
||||
# (batch, heads, source_length, target_length)
|
||||
attention_mask = attention_mask.view(batch_size, attn.heads, -1, attention_mask.shape[-1])
|
||||
|
||||
query = attn.to_q(hidden_states)
|
||||
|
||||
is_cross_attention = encoder_hidden_states is not None
|
||||
if encoder_hidden_states is None:
|
||||
encoder_hidden_states = hidden_states
|
||||
elif attn.norm_cross:
|
||||
encoder_hidden_states = attn.norm_encoder_hidden_states(encoder_hidden_states)
|
||||
|
||||
key = attn.to_k(encoder_hidden_states)
|
||||
value = attn.to_v(encoder_hidden_states)
|
||||
|
||||
# Cross Frame Attention
|
||||
if not is_cross_attention:
|
||||
video_length = max(1, key.size()[0] // self.batch_size)
|
||||
first_frame_index = [0] * video_length
|
||||
|
||||
# rearrange keys to have batch and frames in the 1st and 2nd dims respectively
|
||||
key = rearrange_3(key, video_length)
|
||||
key = key[:, first_frame_index]
|
||||
# rearrange values to have batch and frames in the 1st and 2nd dims respectively
|
||||
value = rearrange_3(value, video_length)
|
||||
value = value[:, first_frame_index]
|
||||
|
||||
# rearrange back to original shape
|
||||
key = rearrange_4(key)
|
||||
value = rearrange_4(value)
|
||||
|
||||
head_dim = inner_dim // attn.heads
|
||||
query = query.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
|
||||
key = key.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
|
||||
value = value.view(batch_size, -1, attn.heads, head_dim).transpose(1, 2)
|
||||
|
||||
# the output of sdp = (batch, num_heads, seq_len, head_dim)
|
||||
# TODO: add support for attn.scale when we move to Torch 2.1
|
||||
hidden_states = F.scaled_dot_product_attention(
|
||||
query, key, value, attn_mask=attention_mask, dropout_p=0.0, is_causal=False
|
||||
)
|
||||
|
||||
hidden_states = hidden_states.transpose(1, 2).reshape(batch_size, -1, attn.heads * head_dim)
|
||||
hidden_states = hidden_states.to(query.dtype)
|
||||
|
||||
# linear proj
|
||||
hidden_states = attn.to_out[0](hidden_states)
|
||||
# dropout
|
||||
hidden_states = attn.to_out[1](hidden_states)
|
||||
return hidden_states
|
||||
|
||||
|
||||
@dataclass
|
||||
class TextToVideoPipelineOutput(BaseOutput):
|
||||
r"""
|
||||
Output class for zero-shot text-to-video pipeline.
|
||||
|
||||
Args:
|
||||
images (`[List[PIL.Image.Image]`, `np.ndarray`]):
|
||||
List of denoised PIL images of length `batch_size` or NumPy array of shape `(batch_size, height, width,
|
||||
num_channels)`.
|
||||
nsfw_content_detected (`[List[bool]]`):
|
||||
List indicating whether the corresponding generated image contains "not-safe-for-work" (nsfw) content or
|
||||
`None` if safety checking could not be performed.
|
||||
"""
|
||||
|
||||
images: Union[List[PIL.Image.Image], np.ndarray]
|
||||
nsfw_content_detected: Optional[List[bool]]
|
||||
|
||||
|
||||
def coords_grid(batch, ht, wd, device):
|
||||
# Adapted from https://github.com/princeton-vl/RAFT/blob/master/core/utils/utils.py
|
||||
coords = torch.meshgrid(torch.arange(ht, device=device), torch.arange(wd, device=device))
|
||||
coords = torch.stack(coords[::-1], dim=0).float()
|
||||
return coords[None].repeat(batch, 1, 1, 1)
|
||||
|
||||
|
||||
def warp_single_latent(latent, reference_flow):
|
||||
"""
|
||||
Warp latent of a single frame with given flow
|
||||
|
||||
Args:
|
||||
latent: latent code of a single frame
|
||||
reference_flow: flow which to warp the latent with
|
||||
|
||||
Returns:
|
||||
warped: warped latent
|
||||
"""
|
||||
_, _, H, W = reference_flow.size()
|
||||
_, _, h, w = latent.size()
|
||||
coords0 = coords_grid(1, H, W, device=latent.device).to(latent.dtype)
|
||||
|
||||
coords_t0 = coords0 + reference_flow
|
||||
coords_t0[:, 0] /= W
|
||||
coords_t0[:, 1] /= H
|
||||
|
||||
coords_t0 = coords_t0 * 2.0 - 1.0
|
||||
coords_t0 = F.interpolate(coords_t0, size=(h, w), mode="bilinear")
|
||||
coords_t0 = torch.permute(coords_t0, (0, 2, 3, 1))
|
||||
|
||||
warped = grid_sample(latent, coords_t0, mode="nearest", padding_mode="reflection")
|
||||
return warped
|
||||
|
||||
|
||||
def create_motion_field(motion_field_strength_x, motion_field_strength_y, frame_ids, device, dtype):
|
||||
"""
|
||||
Create translation motion field
|
||||
|
||||
Args:
|
||||
motion_field_strength_x: motion strength along x-axis
|
||||
motion_field_strength_y: motion strength along y-axis
|
||||
frame_ids: indexes of the frames the latents of which are being processed.
|
||||
This is needed when we perform chunk-by-chunk inference
|
||||
device: device
|
||||
dtype: dtype
|
||||
|
||||
Returns:
|
||||
|
||||
"""
|
||||
seq_length = len(frame_ids)
|
||||
reference_flow = torch.zeros((seq_length, 2, 512, 512), device=device, dtype=dtype)
|
||||
for fr_idx in range(seq_length):
|
||||
reference_flow[fr_idx, 0, :, :] = motion_field_strength_x * (frame_ids[fr_idx])
|
||||
reference_flow[fr_idx, 1, :, :] = motion_field_strength_y * (frame_ids[fr_idx])
|
||||
return reference_flow
|
||||
|
||||
|
||||
def create_motion_field_and_warp_latents(motion_field_strength_x, motion_field_strength_y, frame_ids, latents):
|
||||
"""
|
||||
Creates translation motion and warps the latents accordingly
|
||||
|
||||
Args:
|
||||
motion_field_strength_x: motion strength along x-axis
|
||||
motion_field_strength_y: motion strength along y-axis
|
||||
frame_ids: indexes of the frames the latents of which are being processed.
|
||||
This is needed when we perform chunk-by-chunk inference
|
||||
latents: latent codes of frames
|
||||
|
||||
Returns:
|
||||
warped_latents: warped latents
|
||||
"""
|
||||
motion_field = create_motion_field(
|
||||
motion_field_strength_x=motion_field_strength_x,
|
||||
motion_field_strength_y=motion_field_strength_y,
|
||||
frame_ids=frame_ids,
|
||||
device=latents.device,
|
||||
dtype=latents.dtype,
|
||||
)
|
||||
warped_latents = latents.clone().detach()
|
||||
for i in range(len(warped_latents)):
|
||||
warped_latents[i] = warp_single_latent(latents[i][None], motion_field[i][None])
|
||||
return warped_latents
|
||||
|
||||
|
||||
class TextToVideoZeroPipeline(StableDiffusionPipeline):
|
||||
r"""
|
||||
Pipeline for zero-shot text-to-video generation using Stable Diffusion.
|
||||
|
||||
This model inherits from [`DiffusionPipeline`]. Check the superclass documentation for the generic methods
|
||||
implemented for all pipelines (downloading, saving, running on a particular device, etc.).
|
||||
|
||||
Args:
|
||||
vae ([`AutoencoderKL`]):
|
||||
Variational Auto-Encoder (VAE) Model to encode and decode images to and from latent representations.
|
||||
text_encoder ([`CLIPTextModel`]):
|
||||
Frozen text-encoder ([clip-vit-large-patch14](https://huggingface.co/openai/clip-vit-large-patch14)).
|
||||
tokenizer (`CLIPTokenizer`):
|
||||
A [`~transformers.CLIPTokenizer`] to tokenize text.
|
||||
unet ([`UNet2DConditionModel`]):
|
||||
A [`UNet3DConditionModel`] to denoise the encoded video latents.
|
||||
scheduler ([`SchedulerMixin`]):
|
||||
A scheduler to be used in combination with `unet` to denoise the encoded image latents. Can be one of
|
||||
[`DDIMScheduler`], [`LMSDiscreteScheduler`], or [`PNDMScheduler`].
|
||||
safety_checker ([`StableDiffusionSafetyChecker`]):
|
||||
Classification module that estimates whether generated images could be considered offensive or harmful.
|
||||
Please refer to the [model card](https://huggingface.co/runwayml/stable-diffusion-v1-5) for more details
|
||||
about a model's potential harms.
|
||||
feature_extractor ([`CLIPImageProcessor`]):
|
||||
A [`CLIPImageProcessor`] to extract features from generated images; used as inputs to the `safety_checker`.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vae: AutoencoderKL,
|
||||
text_encoder: CLIPTextModel,
|
||||
tokenizer: CLIPTokenizer,
|
||||
unet: UNet2DConditionModel,
|
||||
scheduler: KarrasDiffusionSchedulers,
|
||||
safety_checker: StableDiffusionSafetyChecker,
|
||||
feature_extractor: CLIPImageProcessor,
|
||||
requires_safety_checker: bool = True,
|
||||
):
|
||||
super().__init__(
|
||||
vae, text_encoder, tokenizer, unet, scheduler, safety_checker, feature_extractor, requires_safety_checker
|
||||
)
|
||||
processor = (
|
||||
CrossFrameAttnProcessor2_0(batch_size=2)
|
||||
if hasattr(F, "scaled_dot_product_attention")
|
||||
else CrossFrameAttnProcessor(batch_size=2)
|
||||
)
|
||||
self.unet.set_attn_processor(processor)
|
||||
|
||||
def forward_loop(self, x_t0, t0, t1, generator):
|
||||
"""
|
||||
Perform DDPM forward process from time t0 to t1. This is the same as adding noise with corresponding variance.
|
||||
|
||||
Args:
|
||||
x_t0:
|
||||
Latent code at time t0.
|
||||
t0:
|
||||
Timestep at t0.
|
||||
t1:
|
||||
Timestamp at t1.
|
||||
generator (`torch.Generator` or `List[torch.Generator]`, *optional*):
|
||||
A [`torch.Generator`](https://pytorch.org/docs/stable/generated/torch.Generator.html) to make
|
||||
generation deterministic.
|
||||
|
||||
Returns:
|
||||
x_t1:
|
||||
Forward process applied to x_t0 from time t0 to t1.
|
||||
"""
|
||||
eps = randn_tensor(x_t0.size(), generator=generator, dtype=x_t0.dtype, device=x_t0.device)
|
||||
alpha_vec = torch.prod(self.scheduler.alphas[t0:t1])
|
||||
x_t1 = torch.sqrt(alpha_vec) * x_t0 + torch.sqrt(1 - alpha_vec) * eps
|
||||
return x_t1
|
||||
|
||||
def backward_loop(
|
||||
self,
|
||||
latents,
|
||||
timesteps,
|
||||
prompt_embeds,
|
||||
guidance_scale,
|
||||
callback,
|
||||
callback_steps,
|
||||
num_warmup_steps,
|
||||
extra_step_kwargs,
|
||||
prv_features,
|
||||
interval_seq,
|
||||
cache_interval,
|
||||
cache_block_id,
|
||||
cache_layer_id,
|
||||
cross_attention_kwargs=None,
|
||||
):
|
||||
"""
|
||||
Perform backward process given list of time steps.
|
||||
|
||||
Args:
|
||||
latents:
|
||||
Latents at time timesteps[0].
|
||||
timesteps:
|
||||
Time steps along which to perform backward process.
|
||||
prompt_embeds:
|
||||
Pre-generated text embeddings.
|
||||
guidance_scale:
|
||||
A higher guidance scale value encourages the model to generate images closely linked to the text
|
||||
`prompt` at the expense of lower image quality. Guidance scale is enabled when `guidance_scale > 1`.
|
||||
callback (`Callable`, *optional*):
|
||||
A function that calls every `callback_steps` steps during inference. The function is called with the
|
||||
following arguments: `callback(step: int, timestep: int, latents: torch.FloatTensor)`.
|
||||
callback_steps (`int`, *optional*, defaults to 1):
|
||||
The frequency at which the `callback` function is called. If not specified, the callback is called at
|
||||
every step.
|
||||
extra_step_kwargs:
|
||||
Extra_step_kwargs.
|
||||
cross_attention_kwargs:
|
||||
A kwargs dictionary that if specified is passed along to the [`AttentionProcessor`] as defined in
|
||||
[`self.processor`](https://github.com/huggingface/diffusers/blob/main/src/diffusers/models/attention_processor.py).
|
||||
num_warmup_steps:
|
||||
number of warmup steps.
|
||||
|
||||
Returns:
|
||||
latents:
|
||||
Latents of backward process output at time timesteps[-1].
|
||||
"""
|
||||
do_classifier_free_guidance = guidance_scale > 1.0
|
||||
num_steps = (len(timesteps) - num_warmup_steps) // self.scheduler.order
|
||||
with self.progress_bar(total=num_steps) as progress_bar:
|
||||
for i, t in enumerate(timesteps):
|
||||
# expand the latents if we are doing classifier free guidance
|
||||
latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents
|
||||
latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)
|
||||
########
|
||||
if i in interval_seq:
|
||||
prv_features = None
|
||||
# predict the noise residual
|
||||
noise_pred, prv_features = self.unet(
|
||||
latent_model_input,
|
||||
t,
|
||||
encoder_hidden_states=prompt_embeds,
|
||||
cross_attention_kwargs=cross_attention_kwargs,
|
||||
replicate_prv_feature=prv_features,
|
||||
quick_replicate= cache_interval>1,
|
||||
cache_layer_id=cache_layer_id,
|
||||
cache_block_id=cache_block_id,
|
||||
return_dict=False,
|
||||
)
|
||||
########
|
||||
|
||||
# perform guidance
|
||||
if do_classifier_free_guidance:
|
||||
noise_pred_uncond, noise_pred_text = noise_pred.chunk(2)
|
||||
noise_pred = noise_pred_uncond + guidance_scale * (noise_pred_text - noise_pred_uncond)
|
||||
|
||||
# compute the previous noisy sample x_t -> x_t-1
|
||||
latents = self.scheduler.step(noise_pred, t, latents, **extra_step_kwargs).prev_sample
|
||||
|
||||
# call the callback, if provided
|
||||
if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0):
|
||||
progress_bar.update()
|
||||
if callback is not None and i % callback_steps == 0:
|
||||
step_idx = i // getattr(self.scheduler, "order", 1)
|
||||
callback(step_idx, t, latents)
|
||||
return latents.clone().detach()
|
||||
|
||||
@torch.no_grad()
|
||||
def __call__(
|
||||
self,
|
||||
prompt: Union[str, List[str]],
|
||||
video_length: Optional[int] = 8,
|
||||
height: Optional[int] = None,
|
||||
width: Optional[int] = None,
|
||||
num_inference_steps: int = 50,
|
||||
guidance_scale: float = 7.5,
|
||||
negative_prompt: Optional[Union[str, List[str]]] = None,
|
||||
num_videos_per_prompt: Optional[int] = 1,
|
||||
eta: float = 0.0,
|
||||
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
|
||||
latents: Optional[torch.FloatTensor] = None,
|
||||
motion_field_strength_x: float = 12,
|
||||
motion_field_strength_y: float = 12,
|
||||
output_type: Optional[str] = "tensor",
|
||||
return_dict: bool = True,
|
||||
callback: Optional[Callable[[int, int, torch.FloatTensor], None]] = None,
|
||||
callback_steps: Optional[int] = 1,
|
||||
t0: int = 44,
|
||||
t1: int = 47,
|
||||
frame_ids: Optional[List[int]] = None,
|
||||
########
|
||||
cache_interval: int = 1,
|
||||
cache_layer_id: int = None,
|
||||
cache_block_id: int = None,
|
||||
uniform: bool = True,
|
||||
pow: float = None,
|
||||
center: int = None,
|
||||
output_all_sequence: bool = False,
|
||||
########
|
||||
):
|
||||
"""
|
||||
The call function to the pipeline for generation.
|
||||
|
||||
Args:
|
||||
prompt (`str` or `List[str]`, *optional*):
|
||||
The prompt or prompts to guide image generation. If not defined, you need to pass `prompt_embeds`.
|
||||
video_length (`int`, *optional*, defaults to 8):
|
||||
The number of generated video frames.
|
||||
height (`int`, *optional*, defaults to `self.unet.config.sample_size * self.vae_scale_factor`):
|
||||
The height in pixels of the generated image.
|
||||
width (`int`, *optional*, defaults to `self.unet.config.sample_size * self.vae_scale_factor`):
|
||||
The width in pixels of the generated image.
|
||||
num_inference_steps (`int`, *optional*, defaults to 50):
|
||||
The number of denoising steps. More denoising steps usually lead to a higher quality image at the
|
||||
expense of slower inference.
|
||||
guidance_scale (`float`, *optional*, defaults to 7.5):
|
||||
A higher guidance scale value encourages the model to generate images closely linked to the text
|
||||
`prompt` at the expense of lower image quality. Guidance scale is enabled when `guidance_scale > 1`.
|
||||
negative_prompt (`str` or `List[str]`, *optional*):
|
||||
The prompt or prompts to guide what to not include in video generation. If not defined, you need to
|
||||
pass `negative_prompt_embeds` instead. Ignored when not using guidance (`guidance_scale < 1`).
|
||||
num_videos_per_prompt (`int`, *optional*, defaults to 1):
|
||||
The number of videos to generate per prompt.
|
||||
eta (`float`, *optional*, defaults to 0.0):
|
||||
Corresponds to parameter eta (η) from the [DDIM](https://arxiv.org/abs/2010.02502) paper. Only applies
|
||||
to the [`~schedulers.DDIMScheduler`], and is ignored in other schedulers.
|
||||
generator (`torch.Generator` or `List[torch.Generator]`, *optional*):
|
||||
A [`torch.Generator`](https://pytorch.org/docs/stable/generated/torch.Generator.html) to make
|
||||
generation deterministic.
|
||||
latents (`torch.FloatTensor`, *optional*):
|
||||
Pre-generated noisy latents sampled from a Gaussian distribution, to be used as inputs for video
|
||||
generation. Can be used to tweak the same generation with different prompts. If not provided, a latents
|
||||
tensor is generated by sampling using the supplied random `generator`.
|
||||
output_type (`str`, *optional*, defaults to `"numpy"`):
|
||||
The output format of the generated video. Choose between `"latent"` and `"numpy"`.
|
||||
return_dict (`bool`, *optional*, defaults to `True`):
|
||||
Whether or not to return a
|
||||
[`~pipelines.text_to_video_synthesis.pipeline_text_to_video_zero.TextToVideoPipelineOutput`] instead of
|
||||
a plain tuple.
|
||||
callback (`Callable`, *optional*):
|
||||
A function that calls every `callback_steps` steps during inference. The function is called with the
|
||||
following arguments: `callback(step: int, timestep: int, latents: torch.FloatTensor)`.
|
||||
callback_steps (`int`, *optional*, defaults to 1):
|
||||
The frequency at which the `callback` function is called. If not specified, the callback is called at
|
||||
every step.
|
||||
motion_field_strength_x (`float`, *optional*, defaults to 12):
|
||||
Strength of motion in generated video along x-axis. See the [paper](https://arxiv.org/abs/2303.13439),
|
||||
Sect. 3.3.1.
|
||||
motion_field_strength_y (`float`, *optional*, defaults to 12):
|
||||
Strength of motion in generated video along y-axis. See the [paper](https://arxiv.org/abs/2303.13439),
|
||||
Sect. 3.3.1.
|
||||
t0 (`int`, *optional*, defaults to 44):
|
||||
Timestep t0. Should be in the range [0, num_inference_steps - 1]. See the
|
||||
[paper](https://arxiv.org/abs/2303.13439), Sect. 3.3.1.
|
||||
t1 (`int`, *optional*, defaults to 47):
|
||||
Timestep t0. Should be in the range [t0 + 1, num_inference_steps - 1]. See the
|
||||
[paper](https://arxiv.org/abs/2303.13439), Sect. 3.3.1.
|
||||
frame_ids (`List[int]`, *optional*):
|
||||
Indexes of the frames that are being generated. This is used when generating longer videos
|
||||
chunk-by-chunk.
|
||||
|
||||
Returns:
|
||||
[`~pipelines.text_to_video_synthesis.pipeline_text_to_video_zero.TextToVideoPipelineOutput`]:
|
||||
The output contains a `ndarray` of the generated video, when `output_type` != `"latent"`, otherwise a
|
||||
latent code of generated videos and a list of `bool`s indicating whether the corresponding generated
|
||||
video contains "not-safe-for-work" (nsfw) content..
|
||||
"""
|
||||
assert video_length > 0
|
||||
if frame_ids is None:
|
||||
frame_ids = list(range(video_length))
|
||||
assert len(frame_ids) == video_length
|
||||
|
||||
assert num_videos_per_prompt == 1
|
||||
|
||||
if isinstance(prompt, str):
|
||||
prompt = [prompt]
|
||||
if isinstance(negative_prompt, str):
|
||||
negative_prompt = [negative_prompt]
|
||||
|
||||
# Default height and width to unet
|
||||
height = height or self.unet.config.sample_size * self.vae_scale_factor
|
||||
width = width or self.unet.config.sample_size * self.vae_scale_factor
|
||||
|
||||
# Check inputs. Raise error if not correct
|
||||
self.check_inputs(prompt, height, width, callback_steps)
|
||||
|
||||
# Define call parameters
|
||||
batch_size = 1 if isinstance(prompt, str) else len(prompt)
|
||||
device = self._execution_device
|
||||
# here `guidance_scale` is defined analog to the guidance weight `w` of equation (2)
|
||||
# of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1`
|
||||
# corresponds to doing no classifier free guidance.
|
||||
do_classifier_free_guidance = guidance_scale > 1.0
|
||||
|
||||
# Encode input prompt
|
||||
prompt_embeds = self._encode_prompt(
|
||||
prompt, device, num_videos_per_prompt, do_classifier_free_guidance, negative_prompt
|
||||
)
|
||||
|
||||
# Prepare timesteps
|
||||
self.scheduler.set_timesteps(num_inference_steps, device=device)
|
||||
timesteps = self.scheduler.timesteps
|
||||
|
||||
# Prepare latent variables
|
||||
num_channels_latents = self.unet.config.in_channels
|
||||
latents = self.prepare_latents(
|
||||
batch_size * num_videos_per_prompt,
|
||||
num_channels_latents,
|
||||
height,
|
||||
width,
|
||||
prompt_embeds.dtype,
|
||||
device,
|
||||
generator,
|
||||
latents,
|
||||
)
|
||||
# Prepare extra step kwargs.
|
||||
extra_step_kwargs = self.prepare_extra_step_kwargs(generator, eta)
|
||||
num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order
|
||||
|
||||
prv_features = None #record cache feature ****
|
||||
latents_list = [latents]
|
||||
|
||||
if cache_interval == 1:
|
||||
interval_seq = list(range(num_inference_steps))
|
||||
else:
|
||||
if uniform:
|
||||
interval_seq = list(range(0, num_inference_steps, cache_interval))
|
||||
else:
|
||||
num_slow_step = num_inference_steps//cache_interval
|
||||
if num_inference_steps%cache_interval != 0:
|
||||
num_slow_step += 1
|
||||
|
||||
interval_seq, pow = sample_from_quad_center(num_inference_steps, num_slow_step, center=center, pow=pow)#[0, 3, 6, 9, 12, 16, 22, 28, 35, 43,]
|
||||
#interval_seq, pow = sample_from_quad(num_inference_steps, num_inference_steps//cache_interval, pow=pow)#[0, 3, 6, 9, 12, 16, 22, 28, 35, 43,]
|
||||
|
||||
interval_seq = sorted(interval_seq)
|
||||
|
||||
# Perform the first backward process up to time T_1
|
||||
x_1_t1 = self.backward_loop(
|
||||
timesteps=timesteps[: -t1 - 1],
|
||||
prompt_embeds=prompt_embeds,
|
||||
latents=latents,
|
||||
guidance_scale=guidance_scale,
|
||||
callback=callback,
|
||||
callback_steps=callback_steps,
|
||||
extra_step_kwargs=extra_step_kwargs,
|
||||
num_warmup_steps=num_warmup_steps,
|
||||
prv_features=prv_features,
|
||||
interval_seq=interval_seq,
|
||||
cache_interval=cache_interval,
|
||||
cache_block_id=cache_block_id,
|
||||
cache_layer_id=cache_layer_id,
|
||||
)
|
||||
scheduler_copy = copy.deepcopy(self.scheduler)
|
||||
|
||||
# Perform the second backward process up to time T_0
|
||||
x_1_t0 = self.backward_loop(
|
||||
timesteps=timesteps[-t1 - 1 : -t0 - 1],
|
||||
prompt_embeds=prompt_embeds,
|
||||
latents=x_1_t1,
|
||||
guidance_scale=guidance_scale,
|
||||
callback=callback,
|
||||
callback_steps=callback_steps,
|
||||
extra_step_kwargs=extra_step_kwargs,
|
||||
num_warmup_steps=0,
|
||||
prv_features=prv_features,
|
||||
interval_seq=interval_seq,
|
||||
cache_interval=cache_interval,
|
||||
cache_block_id=cache_block_id,
|
||||
cache_layer_id=cache_layer_id,
|
||||
)
|
||||
|
||||
# Propagate first frame latents at time T_0 to remaining frames
|
||||
x_2k_t0 = x_1_t0.repeat(video_length - 1, 1, 1, 1)
|
||||
|
||||
# Add motion in latents at time T_0
|
||||
x_2k_t0 = create_motion_field_and_warp_latents(
|
||||
motion_field_strength_x=motion_field_strength_x,
|
||||
motion_field_strength_y=motion_field_strength_y,
|
||||
latents=x_2k_t0,
|
||||
frame_ids=frame_ids[1:],
|
||||
)
|
||||
|
||||
# Perform forward process up to time T_1
|
||||
x_2k_t1 = self.forward_loop(
|
||||
x_t0=x_2k_t0,
|
||||
t0=timesteps[-t0 - 1].item(),
|
||||
t1=timesteps[-t1 - 1].item(),
|
||||
generator=generator,
|
||||
)
|
||||
|
||||
# Perform backward process from time T_1 to 0
|
||||
x_1k_t1 = torch.cat([x_1_t1, x_2k_t1])
|
||||
b, l, d = prompt_embeds.size()
|
||||
prompt_embeds = prompt_embeds[:, None].repeat(1, video_length, 1, 1).reshape(b * video_length, l, d)
|
||||
|
||||
self.scheduler = scheduler_copy
|
||||
x_1k_0 = self.backward_loop(
|
||||
timesteps=timesteps[-t1 - 1 :],
|
||||
prompt_embeds=prompt_embeds,
|
||||
latents=x_1k_t1,
|
||||
guidance_scale=guidance_scale,
|
||||
callback=callback,
|
||||
callback_steps=callback_steps,
|
||||
extra_step_kwargs=extra_step_kwargs,
|
||||
num_warmup_steps=0,
|
||||
prv_features=prv_features,
|
||||
interval_seq=interval_seq,
|
||||
cache_interval=cache_interval,
|
||||
cache_block_id=cache_block_id,
|
||||
cache_layer_id=cache_layer_id,
|
||||
)
|
||||
latents = x_1k_0
|
||||
|
||||
# manually for max memory savings
|
||||
if hasattr(self, "final_offload_hook") and self.final_offload_hook is not None:
|
||||
self.unet.to("cpu")
|
||||
torch.cuda.empty_cache()
|
||||
|
||||
if output_type == "latent":
|
||||
image = latents
|
||||
has_nsfw_concept = None
|
||||
else:
|
||||
image = self.decode_latents(latents)
|
||||
# Run safety checker
|
||||
image, has_nsfw_concept = self.run_safety_checker(image, device, prompt_embeds.dtype)
|
||||
|
||||
# Offload all models
|
||||
self.maybe_free_model_hooks()
|
||||
|
||||
if not return_dict:
|
||||
return (image, has_nsfw_concept)
|
||||
|
||||
return TextToVideoPipelineOutput(images=image, nsfw_content_detected=has_nsfw_concept)
|
||||
1839
ixformer_sdk/contrib/DeepCache/sd/pipeline_utils.py
Normal file
1839
ixformer_sdk/contrib/DeepCache/sd/pipeline_utils.py
Normal file
File diff suppressed because it is too large
Load Diff
3296
ixformer_sdk/contrib/DeepCache/sd/unet_2d_blocks.py
Normal file
3296
ixformer_sdk/contrib/DeepCache/sd/unet_2d_blocks.py
Normal file
File diff suppressed because it is too large
Load Diff
1257
ixformer_sdk/contrib/DeepCache/sd/unet_2d_condition.py
Normal file
1257
ixformer_sdk/contrib/DeepCache/sd/unet_2d_condition.py
Normal file
File diff suppressed because it is too large
Load Diff
0
ixformer_sdk/contrib/DeepCache/sdxl/__init__.py
Normal file
0
ixformer_sdk/contrib/DeepCache/sdxl/__init__.py
Normal file
1100
ixformer_sdk/contrib/DeepCache/sdxl/pipeline_stable_diffusion_xl.py
Normal file
1100
ixformer_sdk/contrib/DeepCache/sdxl/pipeline_stable_diffusion_xl.py
Normal file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1839
ixformer_sdk/contrib/DeepCache/sdxl/pipeline_utils.py
Normal file
1839
ixformer_sdk/contrib/DeepCache/sdxl/pipeline_utils.py
Normal file
File diff suppressed because it is too large
Load Diff
3339
ixformer_sdk/contrib/DeepCache/sdxl/unet_2d_blocks.py
Normal file
3339
ixformer_sdk/contrib/DeepCache/sdxl/unet_2d_blocks.py
Normal file
File diff suppressed because it is too large
Load Diff
1259
ixformer_sdk/contrib/DeepCache/sdxl/unet_2d_condition.py
Normal file
1259
ixformer_sdk/contrib/DeepCache/sdxl/unet_2d_condition.py
Normal file
File diff suppressed because it is too large
Load Diff
0
ixformer_sdk/contrib/DeepCache/svd/__init__.py
Normal file
0
ixformer_sdk/contrib/DeepCache/svd/__init__.py
Normal file
@@ -0,0 +1,659 @@
|
||||
# Copyright 2023 The HuggingFace 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 inspect
|
||||
from dataclasses import dataclass
|
||||
from typing import Callable, Dict, List, Optional, Union
|
||||
|
||||
import numpy as np
|
||||
import PIL.Image
|
||||
import torch
|
||||
from transformers import CLIPImageProcessor, CLIPVisionModelWithProjection
|
||||
|
||||
from diffusers.image_processor import VaeImageProcessor
|
||||
from diffusers.models import AutoencoderKLTemporalDecoder, UNetSpatioTemporalConditionModel
|
||||
from diffusers.schedulers import EulerDiscreteScheduler
|
||||
from diffusers.utils import BaseOutput, logging
|
||||
from diffusers.utils.torch_utils import randn_tensor
|
||||
from .pipeline_utils import DiffusionPipeline
|
||||
|
||||
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
|
||||
|
||||
|
||||
def _append_dims(x, target_dims):
|
||||
"""Appends dimensions to the end of a tensor until it has target_dims dimensions."""
|
||||
dims_to_append = target_dims - x.ndim
|
||||
if dims_to_append < 0:
|
||||
raise ValueError(f"input has {x.ndim} dims but target_dims is {target_dims}, which is less")
|
||||
return x[(...,) + (None,) * dims_to_append]
|
||||
|
||||
|
||||
def tensor2vid(video: torch.Tensor, processor, output_type="np"):
|
||||
# Based on:
|
||||
# https://github.com/modelscope/modelscope/blob/1509fdb973e5871f37148a4b5e5964cafd43e64d/modelscope/pipelines/multi_modal/text_to_video_synthesis_pipeline.py#L78
|
||||
|
||||
batch_size, channels, num_frames, height, width = video.shape
|
||||
outputs = []
|
||||
for batch_idx in range(batch_size):
|
||||
batch_vid = video[batch_idx].permute(1, 0, 2, 3)
|
||||
batch_output = processor.postprocess(batch_vid, output_type)
|
||||
|
||||
outputs.append(batch_output)
|
||||
|
||||
return outputs
|
||||
|
||||
|
||||
@dataclass
|
||||
class StableVideoDiffusionPipelineOutput(BaseOutput):
|
||||
r"""
|
||||
Output class for zero-shot text-to-video pipeline.
|
||||
|
||||
Args:
|
||||
frames (`[List[PIL.Image.Image]`, `np.ndarray`]):
|
||||
List of denoised PIL images of length `batch_size` or NumPy array of shape `(batch_size, height, width,
|
||||
num_channels)`.
|
||||
"""
|
||||
|
||||
frames: Union[List[PIL.Image.Image], np.ndarray]
|
||||
|
||||
|
||||
class StableVideoDiffusionPipeline(DiffusionPipeline):
|
||||
r"""
|
||||
Pipeline to generate video from an input image using Stable Video Diffusion.
|
||||
|
||||
This model inherits from [`DiffusionPipeline`]. Check the superclass documentation for the generic methods
|
||||
implemented for all pipelines (downloading, saving, running on a particular device, etc.).
|
||||
|
||||
Args:
|
||||
vae ([`AutoencoderKL`]):
|
||||
Variational Auto-Encoder (VAE) model to encode and decode images to and from latent representations.
|
||||
image_encoder ([`~transformers.CLIPVisionModelWithProjection`]):
|
||||
Frozen CLIP image-encoder ([laion/CLIP-ViT-H-14-laion2B-s32B-b79K](https://huggingface.co/laion/CLIP-ViT-H-14-laion2B-s32B-b79K)).
|
||||
unet ([`UNetSpatioTemporalConditionModel`]):cache_interval=5, cache_branch=0,
|
||||
A `UNetSpatioTemporalConditionModel` to denoise the encoded image latents.
|
||||
scheduler ([`EulerDiscreteScheduler`]):
|
||||
A scheduler to be used in combination with `unet` to denoise the encoded image latents.
|
||||
feature_extractor ([`~transformers.CLIPImageProcessor`]):
|
||||
A `CLIPImageProcessor` to extract features from generated images.
|
||||
"""
|
||||
|
||||
model_cpu_offload_seq = "image_encoder->unet->vae"
|
||||
_callback_tensor_inputs = ["latents"]
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vae: AutoencoderKLTemporalDecoder,
|
||||
image_encoder: CLIPVisionModelWithProjection,
|
||||
unet: UNetSpatioTemporalConditionModel,
|
||||
scheduler: EulerDiscreteScheduler,
|
||||
feature_extractor: CLIPImageProcessor,
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
self.register_modules(
|
||||
vae=vae,
|
||||
image_encoder=image_encoder,
|
||||
unet=unet,
|
||||
scheduler=scheduler,
|
||||
feature_extractor=feature_extractor,
|
||||
)
|
||||
self.vae_scale_factor = 2 ** (len(self.vae.config.block_out_channels) - 1)
|
||||
self.image_processor = VaeImageProcessor(vae_scale_factor=self.vae_scale_factor)
|
||||
|
||||
def _encode_image(self, image, device, num_videos_per_prompt, do_classifier_free_guidance):
|
||||
dtype = next(self.image_encoder.parameters()).dtype
|
||||
|
||||
if not isinstance(image, torch.Tensor):
|
||||
image = self.image_processor.pil_to_numpy(image)
|
||||
image = self.image_processor.numpy_to_pt(image)
|
||||
|
||||
# We normalize the image before resizing to match with the original implementation.
|
||||
# Then we unnormalize it after resizing.
|
||||
image = image * 2.0 - 1.0
|
||||
image = _resize_with_antialiasing(image, (224, 224))
|
||||
image = (image + 1.0) / 2.0
|
||||
|
||||
# Normalize the image with for CLIP input
|
||||
image = self.feature_extractor(
|
||||
images=image,
|
||||
do_normalize=True,
|
||||
do_center_crop=False,
|
||||
do_resize=False,
|
||||
do_rescale=False,
|
||||
return_tensors="pt",
|
||||
).pixel_values
|
||||
|
||||
image = image.to(device=device, dtype=dtype)
|
||||
image_embeddings = self.image_encoder(image).image_embeds
|
||||
image_embeddings = image_embeddings.unsqueeze(1)
|
||||
|
||||
# duplicate image embeddings for each generation per prompt, using mps friendly method
|
||||
bs_embed, seq_len, _ = image_embeddings.shape
|
||||
image_embeddings = image_embeddings.repeat(1, num_videos_per_prompt, 1)
|
||||
image_embeddings = image_embeddings.view(bs_embed * num_videos_per_prompt, seq_len, -1)
|
||||
|
||||
if do_classifier_free_guidance:
|
||||
negative_image_embeddings = torch.zeros_like(image_embeddings)
|
||||
|
||||
# For classifier free guidance, we need to do two forward passes.
|
||||
# Here we concatenate the unconditional and text embeddings into a single batch
|
||||
# to avoid doing two forward passes
|
||||
image_embeddings = torch.cat([negative_image_embeddings, image_embeddings])
|
||||
|
||||
return image_embeddings
|
||||
|
||||
def _encode_vae_image(
|
||||
self,
|
||||
image: torch.Tensor,
|
||||
device,
|
||||
num_videos_per_prompt,
|
||||
do_classifier_free_guidance,
|
||||
):
|
||||
image = image.to(device=device)
|
||||
image_latents = self.vae.encode(image).latent_dist.mode()
|
||||
|
||||
if do_classifier_free_guidance:
|
||||
negative_image_latents = torch.zeros_like(image_latents)
|
||||
|
||||
# For classifier free guidance, we need to do two forward passes.
|
||||
# Here we concatenate the unconditional and text embeddings into a single batch
|
||||
# to avoid doing two forward passes
|
||||
image_latents = torch.cat([negative_image_latents, image_latents])
|
||||
|
||||
# duplicate image_latents for each generation per prompt, using mps friendly method
|
||||
image_latents = image_latents.repeat(num_videos_per_prompt, 1, 1, 1)
|
||||
|
||||
return image_latents
|
||||
|
||||
def _get_add_time_ids(
|
||||
self,
|
||||
fps,
|
||||
motion_bucket_id,
|
||||
noise_aug_strength,
|
||||
dtype,
|
||||
batch_size,
|
||||
num_videos_per_prompt,
|
||||
do_classifier_free_guidance,
|
||||
):
|
||||
add_time_ids = [fps, motion_bucket_id, noise_aug_strength]
|
||||
|
||||
passed_add_embed_dim = self.unet.config.addition_time_embed_dim * len(add_time_ids)
|
||||
expected_add_embed_dim = self.unet.add_embedding.linear_1.in_features
|
||||
|
||||
if expected_add_embed_dim != passed_add_embed_dim:
|
||||
raise ValueError(
|
||||
f"Model expects an added time embedding vector of length {expected_add_embed_dim}, but a vector of {passed_add_embed_dim} was created. The model has an incorrect config. Please check `unet.config.time_embedding_type` and `text_encoder_2.config.projection_dim`."
|
||||
)
|
||||
|
||||
add_time_ids = torch.tensor([add_time_ids], dtype=dtype)
|
||||
add_time_ids = add_time_ids.repeat(batch_size * num_videos_per_prompt, 1)
|
||||
|
||||
if do_classifier_free_guidance:
|
||||
add_time_ids = torch.cat([add_time_ids, add_time_ids])
|
||||
|
||||
return add_time_ids
|
||||
|
||||
def decode_latents(self, latents, num_frames, decode_chunk_size=14):
|
||||
# [batch, frames, channels, height, width] -> [batch*frames, channels, height, width]
|
||||
latents = latents.flatten(0, 1)
|
||||
|
||||
latents = 1 / self.vae.config.scaling_factor * latents
|
||||
|
||||
accepts_num_frames = "num_frames" in set(inspect.signature(self.vae.forward).parameters.keys())
|
||||
|
||||
# decode decode_chunk_size frames at a time to avoid OOM
|
||||
frames = []
|
||||
for i in range(0, latents.shape[0], decode_chunk_size):
|
||||
num_frames_in = latents[i : i + decode_chunk_size].shape[0]
|
||||
decode_kwargs = {}
|
||||
if accepts_num_frames:
|
||||
# we only pass num_frames_in if it's expected
|
||||
decode_kwargs["num_frames"] = num_frames_in
|
||||
|
||||
frame = self.vae.decode(latents[i : i + decode_chunk_size], **decode_kwargs).sample
|
||||
frames.append(frame)
|
||||
frames = torch.cat(frames, dim=0)
|
||||
|
||||
# [batch*frames, channels, height, width] -> [batch, channels, frames, height, width]
|
||||
frames = frames.reshape(-1, num_frames, *frames.shape[1:]).permute(0, 2, 1, 3, 4)
|
||||
|
||||
# we always cast to float32 as this does not cause significant overhead and is compatible with bfloat16
|
||||
frames = frames.float()
|
||||
return frames
|
||||
|
||||
def check_inputs(self, image, height, width):
|
||||
if (
|
||||
not isinstance(image, torch.Tensor)
|
||||
and not isinstance(image, PIL.Image.Image)
|
||||
and not isinstance(image, list)
|
||||
):
|
||||
raise ValueError(
|
||||
"`image` has to be of type `torch.FloatTensor` or `PIL.Image.Image` or `List[PIL.Image.Image]` but is"
|
||||
f" {type(image)}"
|
||||
)
|
||||
|
||||
if height % 8 != 0 or width % 8 != 0:
|
||||
raise ValueError(f"`height` and `width` have to be divisible by 8 but are {height} and {width}.")
|
||||
|
||||
def prepare_latents(
|
||||
self,
|
||||
batch_size,
|
||||
num_frames,
|
||||
num_channels_latents,
|
||||
height,
|
||||
width,
|
||||
dtype,
|
||||
device,
|
||||
generator,
|
||||
latents=None,
|
||||
):
|
||||
shape = (
|
||||
batch_size,
|
||||
num_frames,
|
||||
num_channels_latents // 2,
|
||||
height // self.vae_scale_factor,
|
||||
width // self.vae_scale_factor,
|
||||
)
|
||||
if isinstance(generator, list) and len(generator) != batch_size:
|
||||
raise ValueError(
|
||||
f"You have passed a list of generators of length {len(generator)}, but requested an effective batch"
|
||||
f" size of {batch_size}. Make sure the batch size matches the length of the generators."
|
||||
)
|
||||
|
||||
if latents is None:
|
||||
latents = randn_tensor(shape, generator=generator, device=device, dtype=dtype)
|
||||
else:
|
||||
latents = latents.to(device)
|
||||
|
||||
# scale the initial noise by the standard deviation required by the scheduler
|
||||
latents = latents * self.scheduler.init_noise_sigma
|
||||
return latents
|
||||
|
||||
@property
|
||||
def guidance_scale(self):
|
||||
return self._guidance_scale
|
||||
|
||||
# here `guidance_scale` is defined analog to the guidance weight `w` of equation (2)
|
||||
# of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1`
|
||||
# corresponds to doing no classifier free guidance.
|
||||
@property
|
||||
def do_classifier_free_guidance(self):
|
||||
return self._guidance_scale > 1 and self.unet.config.time_cond_proj_dim is None
|
||||
|
||||
@property
|
||||
def num_timesteps(self):
|
||||
return self._num_timesteps
|
||||
|
||||
@torch.no_grad()
|
||||
def __call__(
|
||||
self,
|
||||
image: Union[PIL.Image.Image, List[PIL.Image.Image], torch.FloatTensor],
|
||||
height: int = 576,
|
||||
width: int = 1024,
|
||||
num_frames: Optional[int] = None,
|
||||
num_inference_steps: int = 25,
|
||||
min_guidance_scale: float = 1.0,
|
||||
max_guidance_scale: float = 3.0,
|
||||
fps: int = 7,
|
||||
motion_bucket_id: int = 127,
|
||||
noise_aug_strength: int = 0.02,
|
||||
decode_chunk_size: Optional[int] = None,
|
||||
num_videos_per_prompt: Optional[int] = 1,
|
||||
generator: Optional[Union[torch.Generator, List[torch.Generator]]] = None,
|
||||
latents: Optional[torch.FloatTensor] = None,
|
||||
output_type: Optional[str] = "pil",
|
||||
callback_on_step_end: Optional[Callable[[int, int, Dict], None]] = None,
|
||||
callback_on_step_end_tensor_inputs: List[str] = ["latents"],
|
||||
cache_interval: Optional[int] = 1,
|
||||
cache_branch: Optional[int] = None,
|
||||
return_dict: bool = True,
|
||||
):
|
||||
r"""
|
||||
The call function to the pipeline for generation.
|
||||
|
||||
Args:
|
||||
image (`PIL.Image.Image` or `List[PIL.Image.Image]` or `torch.FloatTensor`):
|
||||
Image or images to guide image generation. If you provide a tensor, it needs to be compatible with
|
||||
[`CLIPImageProcessor`](https://huggingface.co/lambdalabs/sd-image-variations-diffusers/blob/main/feature_extractor/preprocessor_config.json).
|
||||
height (`int`, *optional*, defaults to `self.unet.config.sample_size * self.vae_scale_factor`):
|
||||
The height in pixels of the generated image.
|
||||
width (`int`, *optional*, defaults to `self.unet.config.sample_size * self.vae_scale_factor`):
|
||||
The width in pixels of the generated image.
|
||||
num_frames (`int`, *optional*):
|
||||
The number of video frames to generate. Defaults to 14 for `stable-video-diffusion-img2vid` and to 25 for `stable-video-diffusion-img2vid-xt`
|
||||
num_inference_steps (`int`, *optional*, defaults to 25):
|
||||
The number of denoising steps. More denoising steps usually lead to a higher quality image at the
|
||||
expense of slower inference. This parameter is modulated by `strength`.
|
||||
min_guidance_scale (`float`, *optional*, defaults to 1.0):
|
||||
The minimum guidance scale. Used for the classifier free guidance with first frame.
|
||||
max_guidance_scale (`float`, *optional*, defaults to 3.0):
|
||||
The maximum guidance scale. Used for the classifier free guidance with last frame.
|
||||
fps (`int`, *optional*, defaults to 7):
|
||||
Frames per second. The rate at which the generated images shall be exported to a video after generation.
|
||||
Note that Stable Diffusion Video's UNet was micro-conditioned on fps-1 during training.
|
||||
motion_bucket_id (`int`, *optional*, defaults to 127):
|
||||
The motion bucket ID. Used as conditioning for the generation. The higher the number the more motion will be in the video.
|
||||
noise_aug_strength (`int`, *optional*, defaults to 0.02):
|
||||
The amount of noise added to the init image, the higher it is the less the video will look like the init image. Increase it for more motion.
|
||||
decode_chunk_size (`int`, *optional*):
|
||||
The number of frames to decode at a time. The higher the chunk size, the higher the temporal consistency
|
||||
between frames, but also the higher the memory consumption. By default, the decoder will decode all frames at once
|
||||
for maximal quality. Reduce `decode_chunk_size` to reduce memory usage.
|
||||
num_videos_per_prompt (`int`, *optional*, defaults to 1):
|
||||
The number of images to generate per prompt.
|
||||
generator (`torch.Generator` or `List[torch.Generator]`, *optional*):
|
||||
A [`torch.Generator`](https://pytorch.org/docs/stable/generated/torch.Generator.html) to make
|
||||
generation deterministic.
|
||||
latents (`torch.FloatTensor`, *optional*):
|
||||
Pre-generated noisy latents sampled from a Gaussian distribution, to be used as inputs for image
|
||||
generation. Can be used to tweak the same generation with different prompts. If not provided, a latents
|
||||
tensor is generated by sampling using the supplied random `generator`.
|
||||
output_type (`str`, *optional*, defaults to `"pil"`):
|
||||
The output format of the generated image. Choose between `PIL.Image` or `np.array`.
|
||||
callback_on_step_end (`Callable`, *optional*):
|
||||
A function that calls at the end of each denoising steps during the inference. The function is called
|
||||
with the following arguments: `callback_on_step_end(self: DiffusionPipeline, step: int, timestep: int,
|
||||
callback_kwargs: Dict)`. `callback_kwargs` will include a list of all tensors as specified by
|
||||
`callback_on_step_end_tensor_inputs`.
|
||||
callback_on_step_end_tensor_inputs (`List`, *optional*):
|
||||
The list of tensor inputs for the `callback_on_step_end` function. The tensors specified in the list
|
||||
will be passed as `callback_kwargs` argument. You will only be able to include variables listed in the
|
||||
`._callback_tensor_inputs` attribute of your pipeline class.
|
||||
return_dict (`bool`, *optional*, defaults to `True`):
|
||||
Whether or not to return a [`~pipelines.stable_diffusion.StableDiffusionPipelineOutput`] instead of a
|
||||
plain tuple.
|
||||
|
||||
Returns:
|
||||
[`~pipelines.stable_diffusion.StableVideoDiffusionPipelineOutput`] or `tuple`:
|
||||
If `return_dict` is `True`, [`~pipelines.stable_diffusion.StableVideoDiffusionPipelineOutput`] is returned,
|
||||
otherwise a `tuple` is returned where the first element is a list of list with the generated frames.
|
||||
|
||||
Examples:
|
||||
|
||||
```py
|
||||
from diffusers import StableVideoDiffusionPipeline
|
||||
from diffusers.utils import load_image, export_to_video
|
||||
|
||||
pipe = StableVideoDiffusionPipeline.from_pretrained("stabilityai/stable-video-diffusion-img2vid-xt", torch_dtype=torch.float16, variant="fp16")
|
||||
pipe.to("cuda")
|
||||
|
||||
image = load_image("https://lh3.googleusercontent.com/y-iFOHfLTwkuQSUegpwDdgKmOjRSTvPxat63dQLB25xkTs4lhIbRUFeNBWZzYf370g=s1200")
|
||||
image = image.resize((1024, 576))
|
||||
|
||||
frames = pipe(image, num_frames=25, decode_chunk_size=8).frames[0]
|
||||
export_to_video(frames, "generated.mp4", fps=7)
|
||||
```
|
||||
"""
|
||||
# 0. Default height and width to unet
|
||||
height = height or self.unet.config.sample_size * self.vae_scale_factor
|
||||
width = width or self.unet.config.sample_size * self.vae_scale_factor
|
||||
|
||||
num_frames = num_frames if num_frames is not None else self.unet.config.num_frames
|
||||
decode_chunk_size = decode_chunk_size if decode_chunk_size is not None else num_frames
|
||||
|
||||
# 1. Check inputs. Raise error if not correct
|
||||
self.check_inputs(image, height, width)
|
||||
|
||||
# 2. Define call parameters
|
||||
if isinstance(image, PIL.Image.Image):
|
||||
batch_size = 1
|
||||
elif isinstance(image, list):
|
||||
batch_size = len(image)
|
||||
else:
|
||||
batch_size = image.shape[0]
|
||||
device = self._execution_device
|
||||
# here `guidance_scale` is defined analog to the guidance weight `w` of equation (2)
|
||||
# of the Imagen paper: https://arxiv.org/pdf/2205.11487.pdf . `guidance_scale = 1`
|
||||
# corresponds to doing no classifier free guidance.
|
||||
do_classifier_free_guidance = max_guidance_scale > 1.0
|
||||
|
||||
# 3. Encode input image
|
||||
image_embeddings = self._encode_image(image, device, num_videos_per_prompt, do_classifier_free_guidance)
|
||||
|
||||
# NOTE: Stable Diffusion Video was conditioned on fps - 1, which
|
||||
# is why it is reduced here.
|
||||
# See: https://github.com/Stability-AI/generative-models/blob/ed0997173f98eaf8f4edf7ba5fe8f15c6b877fd3/scripts/sampling/simple_video_sample.py#L188
|
||||
fps = fps - 1
|
||||
|
||||
# 4. Encode input image using VAE
|
||||
image = self.image_processor.preprocess(image, height=height, width=width)
|
||||
noise = randn_tensor(image.shape, generator=generator, device=image.device, dtype=image.dtype)
|
||||
image = image + noise_aug_strength * noise
|
||||
|
||||
needs_upcasting = self.vae.dtype == torch.float16 and self.vae.config.force_upcast
|
||||
if needs_upcasting:
|
||||
self.vae.to(dtype=torch.float32)
|
||||
|
||||
image_latents = self._encode_vae_image(image, device, num_videos_per_prompt, do_classifier_free_guidance)
|
||||
image_latents = image_latents.to(image_embeddings.dtype)
|
||||
|
||||
# cast back to fp16 if needed
|
||||
if needs_upcasting:
|
||||
self.vae.to(dtype=torch.float16)
|
||||
|
||||
# Repeat the image latents for each frame so we can concatenate them with the noise
|
||||
# image_latents [batch, channels, height, width] ->[batch, num_frames, channels, height, width]
|
||||
image_latents = image_latents.unsqueeze(1).repeat(1, num_frames, 1, 1, 1)
|
||||
|
||||
# 5. Get Added Time IDs
|
||||
added_time_ids = self._get_add_time_ids(
|
||||
fps,
|
||||
motion_bucket_id,
|
||||
noise_aug_strength,
|
||||
image_embeddings.dtype,
|
||||
batch_size,
|
||||
num_videos_per_prompt,
|
||||
do_classifier_free_guidance,
|
||||
)
|
||||
added_time_ids = added_time_ids.to(device)
|
||||
|
||||
# 4. Prepare timesteps
|
||||
self.scheduler.set_timesteps(num_inference_steps, device=device)
|
||||
timesteps = self.scheduler.timesteps
|
||||
|
||||
# 5. Prepare latent variables
|
||||
num_channels_latents = self.unet.config.in_channels
|
||||
latents = self.prepare_latents(
|
||||
batch_size * num_videos_per_prompt,
|
||||
num_frames,
|
||||
num_channels_latents,
|
||||
height,
|
||||
width,
|
||||
image_embeddings.dtype,
|
||||
device,
|
||||
generator,
|
||||
latents,
|
||||
)
|
||||
|
||||
# 7. Prepare guidance scale
|
||||
guidance_scale = torch.linspace(min_guidance_scale, max_guidance_scale, num_frames).unsqueeze(0)
|
||||
guidance_scale = guidance_scale.to(device, latents.dtype)
|
||||
guidance_scale = guidance_scale.repeat(batch_size * num_videos_per_prompt, 1)
|
||||
guidance_scale = _append_dims(guidance_scale, latents.ndim)
|
||||
|
||||
self._guidance_scale = guidance_scale
|
||||
|
||||
cache_features = None
|
||||
interval_seq = list(range(0, num_inference_steps, cache_interval))
|
||||
interval_seq = sorted(interval_seq)
|
||||
|
||||
# 8. Denoising loop
|
||||
num_warmup_steps = len(timesteps) - num_inference_steps * self.scheduler.order
|
||||
self._num_timesteps = len(timesteps)
|
||||
with self.progress_bar(total=num_inference_steps) as progress_bar:
|
||||
for i, t in enumerate(timesteps):
|
||||
# expand the latents if we are doing classifier free guidance
|
||||
latent_model_input = torch.cat([latents] * 2) if do_classifier_free_guidance else latents
|
||||
latent_model_input = self.scheduler.scale_model_input(latent_model_input, t)
|
||||
|
||||
# Concatenate image_latents over channels dimention
|
||||
latent_model_input = torch.cat([latent_model_input, image_latents], dim=2)
|
||||
|
||||
if i in interval_seq:
|
||||
cache_features = None
|
||||
|
||||
# predict the noise residual
|
||||
noise_pred, cache_features = self.unet(
|
||||
latent_model_input,
|
||||
t,
|
||||
encoder_hidden_states=image_embeddings,
|
||||
added_time_ids=added_time_ids,
|
||||
cache_features=cache_features,
|
||||
cache_branch=cache_branch,
|
||||
return_dict=False,
|
||||
)
|
||||
|
||||
# perform guidance
|
||||
if do_classifier_free_guidance:
|
||||
noise_pred_uncond, noise_pred_cond = noise_pred.chunk(2)
|
||||
noise_pred = noise_pred_uncond + self.guidance_scale * (noise_pred_cond - noise_pred_uncond)
|
||||
|
||||
# compute the previous noisy sample x_t -> x_t-1
|
||||
latents = self.scheduler.step(noise_pred, t, latents).prev_sample
|
||||
|
||||
if callback_on_step_end is not None:
|
||||
callback_kwargs = {}
|
||||
for k in callback_on_step_end_tensor_inputs:
|
||||
callback_kwargs[k] = locals()[k]
|
||||
callback_outputs = callback_on_step_end(self, i, t, callback_kwargs)
|
||||
|
||||
latents = callback_outputs.pop("latents", latents)
|
||||
|
||||
if i == len(timesteps) - 1 or ((i + 1) > num_warmup_steps and (i + 1) % self.scheduler.order == 0):
|
||||
progress_bar.update()
|
||||
|
||||
if not output_type == "latent":
|
||||
# cast back to fp16 if needed
|
||||
if needs_upcasting:
|
||||
self.vae.to(dtype=torch.float16)
|
||||
frames = self.decode_latents(latents, num_frames, decode_chunk_size)
|
||||
frames = tensor2vid(frames, self.image_processor, output_type=output_type)
|
||||
else:
|
||||
frames = latents
|
||||
|
||||
self.maybe_free_model_hooks()
|
||||
|
||||
if not return_dict:
|
||||
return frames
|
||||
|
||||
return StableVideoDiffusionPipelineOutput(frames=frames)
|
||||
|
||||
|
||||
# resizing utils
|
||||
# TODO: clean up later
|
||||
def _resize_with_antialiasing(input, size, interpolation="bicubic", align_corners=True):
|
||||
h, w = input.shape[-2:]
|
||||
factors = (h / size[0], w / size[1])
|
||||
|
||||
# First, we have to determine sigma
|
||||
# Taken from skimage: https://github.com/scikit-image/scikit-image/blob/v0.19.2/skimage/transform/_warps.py#L171
|
||||
sigmas = (
|
||||
max((factors[0] - 1.0) / 2.0, 0.001),
|
||||
max((factors[1] - 1.0) / 2.0, 0.001),
|
||||
)
|
||||
|
||||
# Now kernel size. Good results are for 3 sigma, but that is kind of slow. Pillow uses 1 sigma
|
||||
# https://github.com/python-pillow/Pillow/blob/master/src/libImaging/Resample.c#L206
|
||||
# But they do it in the 2 passes, which gives better results. Let's try 2 sigmas for now
|
||||
ks = int(max(2.0 * 2 * sigmas[0], 3)), int(max(2.0 * 2 * sigmas[1], 3))
|
||||
|
||||
# Make sure it is odd
|
||||
if (ks[0] % 2) == 0:
|
||||
ks = ks[0] + 1, ks[1]
|
||||
|
||||
if (ks[1] % 2) == 0:
|
||||
ks = ks[0], ks[1] + 1
|
||||
|
||||
input = _gaussian_blur2d(input, ks, sigmas)
|
||||
|
||||
output = torch.nn.functional.interpolate(input, size=size, mode=interpolation, align_corners=align_corners)
|
||||
return output
|
||||
|
||||
|
||||
def _compute_padding(kernel_size):
|
||||
"""Compute padding tuple."""
|
||||
# 4 or 6 ints: (padding_left, padding_right,padding_top,padding_bottom)
|
||||
# https://pytorch.org/docs/stable/nn.html#torch.nn.functional.pad
|
||||
if len(kernel_size) < 2:
|
||||
raise AssertionError(kernel_size)
|
||||
computed = [k - 1 for k in kernel_size]
|
||||
|
||||
# for even kernels we need to do asymmetric padding :(
|
||||
out_padding = 2 * len(kernel_size) * [0]
|
||||
|
||||
for i in range(len(kernel_size)):
|
||||
computed_tmp = computed[-(i + 1)]
|
||||
|
||||
pad_front = computed_tmp // 2
|
||||
pad_rear = computed_tmp - pad_front
|
||||
|
||||
out_padding[2 * i + 0] = pad_front
|
||||
out_padding[2 * i + 1] = pad_rear
|
||||
|
||||
return out_padding
|
||||
|
||||
|
||||
def _filter2d(input, kernel):
|
||||
# prepare kernel
|
||||
b, c, h, w = input.shape
|
||||
tmp_kernel = kernel[:, None, ...].to(device=input.device, dtype=input.dtype)
|
||||
|
||||
tmp_kernel = tmp_kernel.expand(-1, c, -1, -1)
|
||||
|
||||
height, width = tmp_kernel.shape[-2:]
|
||||
|
||||
padding_shape: list[int] = _compute_padding([height, width])
|
||||
input = torch.nn.functional.pad(input, padding_shape, mode="reflect")
|
||||
|
||||
# kernel and input tensor reshape to align element-wise or batch-wise params
|
||||
tmp_kernel = tmp_kernel.reshape(-1, 1, height, width)
|
||||
input = input.view(-1, tmp_kernel.size(0), input.size(-2), input.size(-1))
|
||||
|
||||
# convolve the tensor with the kernel.
|
||||
output = torch.nn.functional.conv2d(input, tmp_kernel, groups=tmp_kernel.size(0), padding=0, stride=1)
|
||||
|
||||
out = output.view(b, c, h, w)
|
||||
return out
|
||||
|
||||
|
||||
def _gaussian(window_size: int, sigma):
|
||||
if isinstance(sigma, float):
|
||||
sigma = torch.tensor([[sigma]])
|
||||
|
||||
batch_size = sigma.shape[0]
|
||||
|
||||
x = (torch.arange(window_size, device=sigma.device, dtype=sigma.dtype) - window_size // 2).expand(batch_size, -1)
|
||||
|
||||
if window_size % 2 == 0:
|
||||
x = x + 0.5
|
||||
|
||||
gauss = torch.exp(-x.pow(2.0) / (2 * sigma.pow(2.0)))
|
||||
|
||||
return gauss / gauss.sum(-1, keepdim=True)
|
||||
|
||||
|
||||
def _gaussian_blur2d(input, kernel_size, sigma):
|
||||
if isinstance(sigma, tuple):
|
||||
sigma = torch.tensor([sigma], dtype=input.dtype)
|
||||
else:
|
||||
sigma = sigma.to(dtype=input.dtype)
|
||||
|
||||
ky, kx = int(kernel_size[0]), int(kernel_size[1])
|
||||
bs = sigma.shape[0]
|
||||
kernel_x = _gaussian(kx, sigma[:, 1].view(bs, 1))
|
||||
kernel_y = _gaussian(ky, sigma[:, 0].view(bs, 1))
|
||||
out_x = _filter2d(input, kernel_x[..., None, :])
|
||||
out = _filter2d(out_x, kernel_y[..., None])
|
||||
|
||||
return out
|
||||
2108
ixformer_sdk/contrib/DeepCache/svd/pipeline_utils.py
Normal file
2108
ixformer_sdk/contrib/DeepCache/svd/pipeline_utils.py
Normal file
File diff suppressed because it is too large
Load Diff
2412
ixformer_sdk/contrib/DeepCache/svd/unet_3d_blocks.py
Normal file
2412
ixformer_sdk/contrib/DeepCache/svd/unet_3d_blocks.py
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,566 @@
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, Optional, Tuple, Union
|
||||
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
|
||||
from diffusers.configuration_utils import ConfigMixin, register_to_config
|
||||
from diffusers.loaders import UNet2DConditionLoadersMixin
|
||||
from diffusers.utils import BaseOutput, logging
|
||||
from diffusers.models.attention_processor import CROSS_ATTENTION_PROCESSORS, AttentionProcessor, AttnProcessor
|
||||
from diffusers.models.embeddings import TimestepEmbedding, Timesteps
|
||||
from diffusers.models.modeling_utils import ModelMixin
|
||||
|
||||
from .unet_3d_blocks import UNetMidBlockSpatioTemporal, get_down_block, get_up_block
|
||||
|
||||
|
||||
logger = logging.get_logger(__name__) # pylint: disable=invalid-name
|
||||
|
||||
|
||||
@dataclass
|
||||
class UNetSpatioTemporalConditionOutput(BaseOutput):
|
||||
"""
|
||||
The output of [`UNetSpatioTemporalConditionModel`].
|
||||
|
||||
Args:
|
||||
sample (`torch.FloatTensor` of shape `(batch_size, num_frames, num_channels, height, width)`):
|
||||
The hidden states output conditioned on `encoder_hidden_states` input. Output of last layer of model.
|
||||
"""
|
||||
|
||||
sample: torch.FloatTensor = None
|
||||
|
||||
|
||||
class UNetSpatioTemporalConditionModel(ModelMixin, ConfigMixin, UNet2DConditionLoadersMixin):
|
||||
r"""
|
||||
A conditional Spatio-Temporal UNet model that takes a noisy video frames, conditional state, and a timestep and returns a sample
|
||||
shaped output.
|
||||
|
||||
This model inherits from [`ModelMixin`]. Check the superclass documentation for it's generic methods implemented
|
||||
for all models (such as downloading or saving).
|
||||
|
||||
Parameters:
|
||||
sample_size (`int` or `Tuple[int, int]`, *optional*, defaults to `None`):
|
||||
Height and width of input/output sample.
|
||||
in_channels (`int`, *optional*, defaults to 8): Number of channels in the input sample.
|
||||
out_channels (`int`, *optional*, defaults to 4): Number of channels in the output.
|
||||
down_block_types (`Tuple[str]`, *optional*, defaults to `("CrossAttnDownBlockSpatioTemporal", "CrossAttnDownBlockSpatioTemporal", "CrossAttnDownBlockSpatioTemporal", "DownBlockSpatioTemporal")`):
|
||||
The tuple of downsample blocks to use.
|
||||
up_block_types (`Tuple[str]`, *optional*, defaults to `("UpBlockSpatioTemporal", "CrossAttnUpBlockSpatioTemporal", "CrossAttnUpBlockSpatioTemporal", "CrossAttnUpBlockSpatioTemporal")`):
|
||||
The tuple of upsample blocks to use.
|
||||
block_out_channels (`Tuple[int]`, *optional*, defaults to `(320, 640, 1280, 1280)`):
|
||||
The tuple of output channels for each block.
|
||||
addition_time_embed_dim: (`int`, defaults to 256):
|
||||
Dimension to to encode the additional time ids.
|
||||
projection_class_embeddings_input_dim (`int`, defaults to 768):
|
||||
The dimension of the projection of encoded `added_time_ids`.
|
||||
layers_per_block (`int`, *optional*, defaults to 2): The number of layers per block.
|
||||
cross_attention_dim (`int` or `Tuple[int]`, *optional*, defaults to 1280):
|
||||
The dimension of the cross attention features.
|
||||
transformer_layers_per_block (`int`, `Tuple[int]`, or `Tuple[Tuple]` , *optional*, defaults to 1):
|
||||
The number of transformer blocks of type [`~models.attention.BasicTransformerBlock`]. Only relevant for
|
||||
[`~models.unet_3d_blocks.CrossAttnDownBlockSpatioTemporal`], [`~models.unet_3d_blocks.CrossAttnUpBlockSpatioTemporal`],
|
||||
[`~models.unet_3d_blocks.UNetMidBlockSpatioTemporal`].
|
||||
num_attention_heads (`int`, `Tuple[int]`, defaults to `(5, 10, 10, 20)`):
|
||||
The number of attention heads.
|
||||
dropout (`float`, *optional*, defaults to 0.0): The dropout probability to use.
|
||||
"""
|
||||
|
||||
_supports_gradient_checkpointing = True
|
||||
|
||||
@register_to_config
|
||||
def __init__(
|
||||
self,
|
||||
sample_size: Optional[int] = None,
|
||||
in_channels: int = 8,
|
||||
out_channels: int = 4,
|
||||
down_block_types: Tuple[str] = (
|
||||
"CrossAttnDownBlockSpatioTemporal",
|
||||
"CrossAttnDownBlockSpatioTemporal",
|
||||
"CrossAttnDownBlockSpatioTemporal",
|
||||
"DownBlockSpatioTemporal",
|
||||
),
|
||||
up_block_types: Tuple[str] = (
|
||||
"UpBlockSpatioTemporal",
|
||||
"CrossAttnUpBlockSpatioTemporal",
|
||||
"CrossAttnUpBlockSpatioTemporal",
|
||||
"CrossAttnUpBlockSpatioTemporal",
|
||||
),
|
||||
block_out_channels: Tuple[int] = (320, 640, 1280, 1280),
|
||||
addition_time_embed_dim: int = 256,
|
||||
projection_class_embeddings_input_dim: int = 768,
|
||||
layers_per_block: Union[int, Tuple[int]] = 2,
|
||||
cross_attention_dim: Union[int, Tuple[int]] = 1024,
|
||||
transformer_layers_per_block: Union[int, Tuple[int], Tuple[Tuple]] = 1,
|
||||
num_attention_heads: Union[int, Tuple[int]] = (5, 10, 10, 20),
|
||||
num_frames: int = 25,
|
||||
):
|
||||
super().__init__()
|
||||
|
||||
self.sample_size = sample_size
|
||||
|
||||
# Check inputs
|
||||
if len(down_block_types) != len(up_block_types):
|
||||
raise ValueError(
|
||||
f"Must provide the same number of `down_block_types` as `up_block_types`. `down_block_types`: {down_block_types}. `up_block_types`: {up_block_types}."
|
||||
)
|
||||
|
||||
if len(block_out_channels) != len(down_block_types):
|
||||
raise ValueError(
|
||||
f"Must provide the same number of `block_out_channels` as `down_block_types`. `block_out_channels`: {block_out_channels}. `down_block_types`: {down_block_types}."
|
||||
)
|
||||
|
||||
if not isinstance(num_attention_heads, int) and len(num_attention_heads) != len(down_block_types):
|
||||
raise ValueError(
|
||||
f"Must provide the same number of `num_attention_heads` as `down_block_types`. `num_attention_heads`: {num_attention_heads}. `down_block_types`: {down_block_types}."
|
||||
)
|
||||
|
||||
if isinstance(cross_attention_dim, list) and len(cross_attention_dim) != len(down_block_types):
|
||||
raise ValueError(
|
||||
f"Must provide the same number of `cross_attention_dim` as `down_block_types`. `cross_attention_dim`: {cross_attention_dim}. `down_block_types`: {down_block_types}."
|
||||
)
|
||||
|
||||
if not isinstance(layers_per_block, int) and len(layers_per_block) != len(down_block_types):
|
||||
raise ValueError(
|
||||
f"Must provide the same number of `layers_per_block` as `down_block_types`. `layers_per_block`: {layers_per_block}. `down_block_types`: {down_block_types}."
|
||||
)
|
||||
|
||||
# input
|
||||
self.conv_in = nn.Conv2d(
|
||||
in_channels,
|
||||
block_out_channels[0],
|
||||
kernel_size=3,
|
||||
padding=1,
|
||||
)
|
||||
|
||||
# time
|
||||
time_embed_dim = block_out_channels[0] * 4
|
||||
|
||||
self.time_proj = Timesteps(block_out_channels[0], True, downscale_freq_shift=0)
|
||||
timestep_input_dim = block_out_channels[0]
|
||||
|
||||
self.time_embedding = TimestepEmbedding(timestep_input_dim, time_embed_dim)
|
||||
|
||||
self.add_time_proj = Timesteps(addition_time_embed_dim, True, downscale_freq_shift=0)
|
||||
self.add_embedding = TimestepEmbedding(projection_class_embeddings_input_dim, time_embed_dim)
|
||||
|
||||
self.down_blocks = nn.ModuleList([])
|
||||
self.up_blocks = nn.ModuleList([])
|
||||
|
||||
if isinstance(num_attention_heads, int):
|
||||
num_attention_heads = (num_attention_heads,) * len(down_block_types)
|
||||
|
||||
if isinstance(cross_attention_dim, int):
|
||||
cross_attention_dim = (cross_attention_dim,) * len(down_block_types)
|
||||
|
||||
if isinstance(layers_per_block, int):
|
||||
layers_per_block = [layers_per_block] * len(down_block_types)
|
||||
|
||||
if isinstance(transformer_layers_per_block, int):
|
||||
transformer_layers_per_block = [transformer_layers_per_block] * len(down_block_types)
|
||||
|
||||
blocks_time_embed_dim = time_embed_dim
|
||||
|
||||
# down
|
||||
output_channel = block_out_channels[0]
|
||||
for i, down_block_type in enumerate(down_block_types):
|
||||
input_channel = output_channel
|
||||
output_channel = block_out_channels[i]
|
||||
is_final_block = i == len(block_out_channels) - 1
|
||||
|
||||
down_block = get_down_block(
|
||||
down_block_type,
|
||||
num_layers=layers_per_block[i],
|
||||
transformer_layers_per_block=transformer_layers_per_block[i],
|
||||
in_channels=input_channel,
|
||||
out_channels=output_channel,
|
||||
temb_channels=blocks_time_embed_dim,
|
||||
add_downsample=not is_final_block,
|
||||
resnet_eps=1e-5,
|
||||
cross_attention_dim=cross_attention_dim[i],
|
||||
num_attention_heads=num_attention_heads[i],
|
||||
resnet_act_fn="silu",
|
||||
)
|
||||
self.down_blocks.append(down_block)
|
||||
|
||||
# mid
|
||||
self.mid_block = UNetMidBlockSpatioTemporal(
|
||||
block_out_channels[-1],
|
||||
temb_channels=blocks_time_embed_dim,
|
||||
transformer_layers_per_block=transformer_layers_per_block[-1],
|
||||
cross_attention_dim=cross_attention_dim[-1],
|
||||
num_attention_heads=num_attention_heads[-1],
|
||||
)
|
||||
|
||||
# count how many layers upsample the images
|
||||
self.num_upsamplers = 0
|
||||
|
||||
# up
|
||||
reversed_block_out_channels = list(reversed(block_out_channels))
|
||||
reversed_num_attention_heads = list(reversed(num_attention_heads))
|
||||
reversed_layers_per_block = list(reversed(layers_per_block))
|
||||
reversed_cross_attention_dim = list(reversed(cross_attention_dim))
|
||||
reversed_transformer_layers_per_block = list(reversed(transformer_layers_per_block))
|
||||
|
||||
output_channel = reversed_block_out_channels[0]
|
||||
for i, up_block_type in enumerate(up_block_types):
|
||||
is_final_block = i == len(block_out_channels) - 1
|
||||
|
||||
prev_output_channel = output_channel
|
||||
output_channel = reversed_block_out_channels[i]
|
||||
input_channel = reversed_block_out_channels[min(i + 1, len(block_out_channels) - 1)]
|
||||
|
||||
# add upsample block for all BUT final layer
|
||||
if not is_final_block:
|
||||
add_upsample = True
|
||||
self.num_upsamplers += 1
|
||||
else:
|
||||
add_upsample = False
|
||||
|
||||
up_block = get_up_block(
|
||||
up_block_type,
|
||||
num_layers=reversed_layers_per_block[i] + 1,
|
||||
transformer_layers_per_block=reversed_transformer_layers_per_block[i],
|
||||
in_channels=input_channel,
|
||||
out_channels=output_channel,
|
||||
prev_output_channel=prev_output_channel,
|
||||
temb_channels=blocks_time_embed_dim,
|
||||
add_upsample=add_upsample,
|
||||
resnet_eps=1e-5,
|
||||
resolution_idx=i,
|
||||
cross_attention_dim=reversed_cross_attention_dim[i],
|
||||
num_attention_heads=reversed_num_attention_heads[i],
|
||||
resnet_act_fn="silu",
|
||||
)
|
||||
self.up_blocks.append(up_block)
|
||||
prev_output_channel = output_channel
|
||||
|
||||
# out
|
||||
self.conv_norm_out = nn.GroupNorm(num_channels=block_out_channels[0], num_groups=32, eps=1e-5)
|
||||
self.conv_act = nn.SiLU()
|
||||
|
||||
self.conv_out = nn.Conv2d(
|
||||
block_out_channels[0],
|
||||
out_channels,
|
||||
kernel_size=3,
|
||||
padding=1,
|
||||
)
|
||||
|
||||
@property
|
||||
def attn_processors(self) -> Dict[str, AttentionProcessor]:
|
||||
r"""
|
||||
Returns:
|
||||
`dict` of attention processors: A dictionary containing all attention processors used in the model with
|
||||
indexed by its weight name.
|
||||
"""
|
||||
# set recursively
|
||||
processors = {}
|
||||
|
||||
def fn_recursive_add_processors(
|
||||
name: str,
|
||||
module: torch.nn.Module,
|
||||
processors: Dict[str, AttentionProcessor],
|
||||
):
|
||||
if hasattr(module, "get_processor"):
|
||||
processors[f"{name}.processor"] = module.get_processor(return_deprecated_lora=True)
|
||||
|
||||
for sub_name, child in module.named_children():
|
||||
fn_recursive_add_processors(f"{name}.{sub_name}", child, processors)
|
||||
|
||||
return processors
|
||||
|
||||
for name, module in self.named_children():
|
||||
fn_recursive_add_processors(name, module, processors)
|
||||
|
||||
return processors
|
||||
|
||||
def set_attn_processor(self, processor: Union[AttentionProcessor, Dict[str, AttentionProcessor]]):
|
||||
r"""
|
||||
Sets the attention processor to use to compute attention.
|
||||
|
||||
Parameters:
|
||||
processor (`dict` of `AttentionProcessor` or only `AttentionProcessor`):
|
||||
The instantiated processor class or a dictionary of processor classes that will be set as the processor
|
||||
for **all** `Attention` layers.
|
||||
|
||||
If `processor` is a dict, the key needs to define the path to the corresponding cross attention
|
||||
processor. This is strongly recommended when setting trainable attention processors.
|
||||
|
||||
"""
|
||||
count = len(self.attn_processors.keys())
|
||||
|
||||
if isinstance(processor, dict) and len(processor) != count:
|
||||
raise ValueError(
|
||||
f"A dict of processors was passed, but the number of processors {len(processor)} does not match the"
|
||||
f" number of attention layers: {count}. Please make sure to pass {count} processor classes."
|
||||
)
|
||||
|
||||
def fn_recursive_attn_processor(name: str, module: torch.nn.Module, processor):
|
||||
if hasattr(module, "set_processor"):
|
||||
if not isinstance(processor, dict):
|
||||
module.set_processor(processor)
|
||||
else:
|
||||
module.set_processor(processor.pop(f"{name}.processor"))
|
||||
|
||||
for sub_name, child in module.named_children():
|
||||
fn_recursive_attn_processor(f"{name}.{sub_name}", child, processor)
|
||||
|
||||
for name, module in self.named_children():
|
||||
fn_recursive_attn_processor(name, module, processor)
|
||||
|
||||
def set_default_attn_processor(self):
|
||||
"""
|
||||
Disables custom attention processors and sets the default attention implementation.
|
||||
"""
|
||||
if all(proc.__class__ in CROSS_ATTENTION_PROCESSORS for proc in self.attn_processors.values()):
|
||||
processor = AttnProcessor()
|
||||
else:
|
||||
raise ValueError(
|
||||
f"Cannot call `set_default_attn_processor` when attention processors are of type {next(iter(self.attn_processors.values()))}"
|
||||
)
|
||||
|
||||
self.set_attn_processor(processor)
|
||||
|
||||
def _set_gradient_checkpointing(self, module, value=False):
|
||||
if hasattr(module, "gradient_checkpointing"):
|
||||
module.gradient_checkpointing = value
|
||||
|
||||
# Copied from diffusers.models.unet_3d_condition.UNet3DConditionModel.enable_forward_chunking
|
||||
def enable_forward_chunking(self, chunk_size: Optional[int] = None, dim: int = 0) -> None:
|
||||
"""
|
||||
Sets the attention processor to use [feed forward
|
||||
chunking](https://huggingface.co/blog/reformer#2-chunked-feed-forward-layers).
|
||||
|
||||
Parameters:
|
||||
chunk_size (`int`, *optional*):
|
||||
The chunk size of the feed-forward layers. If not specified, will run feed-forward layer individually
|
||||
over each tensor of dim=`dim`.
|
||||
dim (`int`, *optional*, defaults to `0`):
|
||||
The dimension over which the feed-forward computation should be chunked. Choose between dim=0 (batch)
|
||||
or dim=1 (sequence length).
|
||||
"""
|
||||
if dim not in [0, 1]:
|
||||
raise ValueError(f"Make sure to set `dim` to either 0 or 1, not {dim}")
|
||||
|
||||
# By default chunk size is 1
|
||||
chunk_size = chunk_size or 1
|
||||
|
||||
def fn_recursive_feed_forward(module: torch.nn.Module, chunk_size: int, dim: int):
|
||||
if hasattr(module, "set_chunk_feed_forward"):
|
||||
module.set_chunk_feed_forward(chunk_size=chunk_size, dim=dim)
|
||||
|
||||
for child in module.children():
|
||||
fn_recursive_feed_forward(child, chunk_size, dim)
|
||||
|
||||
for module in self.children():
|
||||
fn_recursive_feed_forward(module, chunk_size, dim)
|
||||
|
||||
def forward(
|
||||
self,
|
||||
sample: torch.FloatTensor,
|
||||
timestep: Union[torch.Tensor, float, int],
|
||||
encoder_hidden_states: torch.Tensor,
|
||||
added_time_ids: torch.Tensor,
|
||||
cache_features: Optional[torch.Tensor] = None,
|
||||
cache_branch: Optional[int] = None,
|
||||
return_dict: bool = True,
|
||||
) -> Union[UNetSpatioTemporalConditionOutput, Tuple]:
|
||||
r"""
|
||||
The [`UNetSpatioTemporalConditionModel`] forward method.
|
||||
|
||||
Args:
|
||||
sample (`torch.FloatTensor`):
|
||||
The noisy input tensor with the following shape `(batch, num_frames, channel, height, width)`.
|
||||
timestep (`torch.FloatTensor` or `float` or `int`): The number of timesteps to denoise an input.
|
||||
encoder_hidden_states (`torch.FloatTensor`):
|
||||
The encoder hidden states with shape `(batch, sequence_length, cross_attention_dim)`.
|
||||
added_time_ids: (`torch.FloatTensor`):
|
||||
The additional time ids with shape `(batch, num_additional_ids)`. These are encoded with sinusoidal
|
||||
embeddings and added to the time embeddings.
|
||||
return_dict (`bool`, *optional*, defaults to `True`):
|
||||
Whether or not to return a [`~models.unet_slatio_temporal.UNetSpatioTemporalConditionOutput`] instead of a plain
|
||||
tuple.
|
||||
Returns:
|
||||
[`~models.unet_slatio_temporal.UNetSpatioTemporalConditionOutput`] or `tuple`:
|
||||
If `return_dict` is True, an [`~models.unet_slatio_temporal.UNetSpatioTemporalConditionOutput`] is returned, otherwise
|
||||
a `tuple` is returned where the first element is the sample tensor.
|
||||
"""
|
||||
# 1. time
|
||||
timesteps = timestep
|
||||
if not torch.is_tensor(timesteps):
|
||||
# TODO: this requires sync between CPU and GPU. So try to pass timesteps as tensors if you can
|
||||
# This would be a good case for the `match` statement (Python 3.10+)
|
||||
is_mps = sample.device.type == "mps"
|
||||
if isinstance(timestep, float):
|
||||
dtype = torch.float32 if is_mps else torch.float64
|
||||
else:
|
||||
dtype = torch.int32 if is_mps else torch.int64
|
||||
timesteps = torch.tensor([timesteps], dtype=dtype, device=sample.device)
|
||||
elif len(timesteps.shape) == 0:
|
||||
timesteps = timesteps[None].to(sample.device)
|
||||
|
||||
# broadcast to batch dimension in a way that's compatible with ONNX/Core ML
|
||||
batch_size, num_frames = sample.shape[:2]
|
||||
timesteps = timesteps.expand(batch_size)
|
||||
|
||||
t_emb = self.time_proj(timesteps)
|
||||
|
||||
# `Timesteps` does not contain any weights and will always return f32 tensors
|
||||
# but time_embedding might actually be running in fp16. so we need to cast here.
|
||||
# there might be better ways to encapsulate this.
|
||||
t_emb = t_emb.to(dtype=sample.dtype)
|
||||
|
||||
emb = self.time_embedding(t_emb)
|
||||
|
||||
time_embeds = self.add_time_proj(added_time_ids.flatten())
|
||||
time_embeds = time_embeds.reshape((batch_size, -1))
|
||||
time_embeds = time_embeds.to(emb.dtype)
|
||||
aug_emb = self.add_embedding(time_embeds)
|
||||
emb = emb + aug_emb
|
||||
|
||||
# Flatten the batch and frames dimensions
|
||||
# sample: [batch, frames, channels, height, width] -> [batch * frames, channels, height, width]
|
||||
sample = sample.flatten(0, 1)
|
||||
# Repeat the embeddings num_video_frames times
|
||||
# emb: [batch, channels] -> [batch * frames, channels]
|
||||
emb = emb.repeat_interleave(num_frames, dim=0)
|
||||
# encoder_hidden_states: [batch, 1, channels] -> [batch * frames, 1, channels]
|
||||
encoder_hidden_states = encoder_hidden_states.repeat_interleave(num_frames, dim=0)
|
||||
|
||||
# 2. pre-process
|
||||
sample = self.conv_in(sample)
|
||||
|
||||
image_only_indicator = torch.zeros(batch_size, num_frames, dtype=sample.dtype, device=sample.device)
|
||||
|
||||
# Branch: 4 down_blocks, each with 3 skip connections. Here we ignore the first skip branch, whose computations only has up_blocks but without down_blocks.
|
||||
if cache_branch is not None:
|
||||
each_module_num = len(self.down_blocks[0].resnets) + 1
|
||||
down_cache_block_idx = cache_branch // each_module_num
|
||||
down_cache_module_idx = cache_branch % each_module_num
|
||||
|
||||
up_cache_block_idx = len(self.up_blocks) - 1 - down_cache_block_idx
|
||||
up_cache_module_idx = 1 - down_cache_module_idx
|
||||
if down_cache_module_idx == each_module_num - 1:
|
||||
up_cache_block_idx -= 1
|
||||
up_cache_module_idx = 2
|
||||
|
||||
if cache_features is not None:
|
||||
# 3. down
|
||||
down_block_res_samples = (sample,)
|
||||
for block_id, downsample_block in enumerate(self.down_blocks):
|
||||
if hasattr(downsample_block, "has_cross_attention") and downsample_block.has_cross_attention:
|
||||
sample, res_samples = downsample_block(
|
||||
hidden_states=sample,
|
||||
temb=emb,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
image_only_indicator=image_only_indicator,
|
||||
exist_module_idx=down_cache_module_idx if down_cache_block_idx == block_id else None
|
||||
)
|
||||
else:
|
||||
sample, res_samples = downsample_block(
|
||||
hidden_states=sample,
|
||||
temb=emb,
|
||||
image_only_indicator=image_only_indicator,
|
||||
exist_module_idx=down_cache_module_idx if down_cache_block_idx == block_id else None
|
||||
)
|
||||
|
||||
down_block_res_samples += res_samples
|
||||
if down_cache_block_idx == block_id:
|
||||
break
|
||||
|
||||
# 4. no mid
|
||||
sample = cache_features
|
||||
|
||||
# 5. up
|
||||
for i, upsample_block in enumerate(self.up_blocks):
|
||||
if i < up_cache_block_idx:
|
||||
continue
|
||||
|
||||
if i == up_cache_block_idx:
|
||||
trunc_res_samples_len = len(upsample_block.resnets) - up_cache_module_idx
|
||||
else:
|
||||
trunc_res_samples_len = len(upsample_block.resnets)
|
||||
|
||||
res_samples = down_block_res_samples[-trunc_res_samples_len :]
|
||||
down_block_res_samples = down_block_res_samples[: -trunc_res_samples_len]
|
||||
|
||||
if hasattr(upsample_block, "has_cross_attention") and upsample_block.has_cross_attention:
|
||||
sample, _ = upsample_block(
|
||||
hidden_states=sample,
|
||||
temb=emb,
|
||||
res_hidden_states_tuple=res_samples,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
image_only_indicator=image_only_indicator,
|
||||
enter_module_idx=up_cache_module_idx if i == up_cache_block_idx else None
|
||||
)
|
||||
else:
|
||||
sample, _ = upsample_block(
|
||||
hidden_states=sample,
|
||||
temb=emb,
|
||||
res_hidden_states_tuple=res_samples,
|
||||
image_only_indicator=image_only_indicator,
|
||||
enter_module_idx=up_cache_module_idx if i == up_cache_block_idx else None
|
||||
)
|
||||
else:
|
||||
# 3. down
|
||||
down_block_res_samples = (sample,)
|
||||
for downsample_block in self.down_blocks:
|
||||
if hasattr(downsample_block, "has_cross_attention") and downsample_block.has_cross_attention:
|
||||
sample, res_samples = downsample_block(
|
||||
hidden_states=sample,
|
||||
temb=emb,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
image_only_indicator=image_only_indicator,
|
||||
)
|
||||
else:
|
||||
sample, res_samples = downsample_block(
|
||||
hidden_states=sample,
|
||||
temb=emb,
|
||||
image_only_indicator=image_only_indicator,
|
||||
)
|
||||
|
||||
down_block_res_samples += res_samples
|
||||
|
||||
# 4. mid
|
||||
sample = self.mid_block(
|
||||
hidden_states=sample,
|
||||
temb=emb,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
image_only_indicator=image_only_indicator,
|
||||
)
|
||||
|
||||
# 5. up
|
||||
for i, upsample_block in enumerate(self.up_blocks):
|
||||
res_samples = down_block_res_samples[-len(upsample_block.resnets) :]
|
||||
down_block_res_samples = down_block_res_samples[: -len(upsample_block.resnets)]
|
||||
|
||||
if hasattr(upsample_block, "has_cross_attention") and upsample_block.has_cross_attention:
|
||||
sample, current_record_f = upsample_block(
|
||||
hidden_states=sample,
|
||||
temb=emb,
|
||||
res_hidden_states_tuple=res_samples,
|
||||
encoder_hidden_states=encoder_hidden_states,
|
||||
image_only_indicator=image_only_indicator,
|
||||
)
|
||||
else:
|
||||
sample, current_record_f = upsample_block(
|
||||
hidden_states=sample,
|
||||
temb=emb,
|
||||
res_hidden_states_tuple=res_samples,
|
||||
image_only_indicator=image_only_indicator,
|
||||
)
|
||||
|
||||
if cache_branch is not None and i == up_cache_block_idx:
|
||||
cache_features = current_record_f[up_cache_module_idx]
|
||||
|
||||
# 6. post-process
|
||||
sample = self.conv_norm_out(sample)
|
||||
sample = self.conv_act(sample)
|
||||
sample = self.conv_out(sample)
|
||||
|
||||
# 7. Reshape back to original shape
|
||||
sample = sample.reshape(batch_size, num_frames, *sample.shape[1:])
|
||||
|
||||
if not return_dict:
|
||||
return (sample, cache_features)
|
||||
|
||||
return UNetSpatioTemporalConditionOutput(sample=sample)
|
||||
0
ixformer_sdk/contrib/__init__.py
Normal file
0
ixformer_sdk/contrib/__init__.py
Normal file
0
ixformer_sdk/contrib/comfy/__init__.py
Normal file
0
ixformer_sdk/contrib/comfy/__init__.py
Normal file
496
ixformer_sdk/contrib/comfy/unet_model_wrapper.py
Normal file
496
ixformer_sdk/contrib/comfy/unet_model_wrapper.py
Normal file
@@ -0,0 +1,496 @@
|
||||
import torch
|
||||
import torch.nn as nn
|
||||
import torch.nn.functional as F
|
||||
|
||||
import ixformer.functions as ixf_F
|
||||
|
||||
|
||||
def time_embed(t_emb, weight1, bias1, weight2, bias2):
|
||||
# unet time_emd
|
||||
# linear + silu + linear
|
||||
emb = ixf_F.act_bias_mm(
|
||||
t_emb, weight1, act_type="silu", bias=bias1, scale=1, trans_format="TN"
|
||||
)
|
||||
emb = ixf_F.act_bias_mm(
|
||||
emb, weight2, act_type="none", bias=bias2, scale=1, trans_format="TN"
|
||||
)
|
||||
return emb
|
||||
|
||||
|
||||
def ixf_layer_norm(input, normalized_shape, weight=None, bias=None, eps=1e-05):
|
||||
return ixf_F.layernorm(input, weight, bias, normalized_shape)
|
||||
|
||||
|
||||
def ixf_pt_scaled_dot_product_attention(
|
||||
query, key, value, attn_mask=None, dropout_p=0.0, is_causal=False
|
||||
):
|
||||
if (
|
||||
not query.is_contiguous()
|
||||
and query.transpose(1, 2).is_contiguous()
|
||||
and key.transpose(1, 2).is_contiguous()
|
||||
and value.transpose(1, 2).is_contiguous()
|
||||
and attn_mask is None
|
||||
):
|
||||
|
||||
batch_size, head_num, seq_len_q, head_dim = query.shape
|
||||
_, _, seq_len_k, _ = key.shape
|
||||
|
||||
query = query.transpose(1, 2).view(batch_size * seq_len_q, head_num, head_dim)
|
||||
key = key.transpose(1, 2).view(batch_size * seq_len_k, head_num, head_dim)
|
||||
value = value.transpose(1, 2).view(batch_size * seq_len_k, head_num, head_dim)
|
||||
|
||||
cu_seqlens_q = torch.arange(
|
||||
0,
|
||||
seq_len_q * (batch_size + 1),
|
||||
seq_len_q,
|
||||
dtype=torch.int32,
|
||||
device=query.device,
|
||||
)
|
||||
if seq_len_q == seq_len_k:
|
||||
cu_seqlens_k = cu_seqlens_q
|
||||
else:
|
||||
cu_seqlens_k = torch.arange(
|
||||
0,
|
||||
seq_len_k * (batch_size + 1),
|
||||
seq_len_k,
|
||||
dtype=torch.int32,
|
||||
device=query.device,
|
||||
)
|
||||
|
||||
res = ixf_F.flash_attn_varlen_func(
|
||||
query,
|
||||
key,
|
||||
value,
|
||||
cu_seqlens_q.int(),
|
||||
cu_seqlens_k.int(),
|
||||
seq_len_q,
|
||||
seq_len_k,
|
||||
)
|
||||
res = res.view(batch_size, seq_len_q, head_num, head_dim).transpose(1, 2)
|
||||
return res
|
||||
|
||||
if not query.is_contiguous():
|
||||
query = query.contiguous()
|
||||
if not key.is_contiguous():
|
||||
key = key.contiguous()
|
||||
if not value.is_contiguous():
|
||||
value = value.contiguous()
|
||||
return ixf_F.scaled_dot_product_attention(
|
||||
query, key, value, attn_mask=attn_mask, is_causal=is_causal
|
||||
)
|
||||
|
||||
|
||||
class UnetIxformerFunction:
|
||||
def __init__(self) -> None:
|
||||
self.ixf_linear = ixf_F.linear
|
||||
self.pt_linear = F.linear
|
||||
self.pt_layer_norm = F.layer_norm
|
||||
self.pt_scaled_dot_product_attention = F.scaled_dot_product_attention
|
||||
|
||||
def __enter__(self):
|
||||
F.linear = self.ixf_linear
|
||||
F.layer_norm = ixf_layer_norm
|
||||
F.scaled_dot_product_attention = ixf_pt_scaled_dot_product_attention
|
||||
return self
|
||||
|
||||
def __exit__(self, exc_type, exc_val, exc_tb):
|
||||
F.linear = self.pt_linear
|
||||
F.layer_norm = self.pt_layer_norm
|
||||
F.scaled_dot_product_attention = self.pt_scaled_dot_product_attention
|
||||
if exc_tb is not None:
|
||||
print(f"{exc_type} {exc_val}")
|
||||
return False
|
||||
return True
|
||||
|
||||
|
||||
def ForwardWrapper(fun):
|
||||
def wrap(*args, **kwargs):
|
||||
with UnetIxformerFunction() as w:
|
||||
return fun(*args, **kwargs)
|
||||
|
||||
return wrap
|
||||
|
||||
|
||||
class IxformerComfyWrapper(nn.Module):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.is_ixf_wrapper = True
|
||||
|
||||
|
||||
class Conv2dNhwcWrapper(IxformerComfyWrapper):
|
||||
def __init__(self, module):
|
||||
super().__init__()
|
||||
module.weight.data = module.weight.permute(0, 2, 3, 1).contiguous()
|
||||
module.bias.data = module.bias.float()
|
||||
self.weight = module.weight.data
|
||||
self.bias = module.bias.data
|
||||
self.stride = module.stride
|
||||
self.padding = module.padding
|
||||
self.dilation = module.dilation
|
||||
self.groups = module.groups
|
||||
|
||||
def forward(self, x):
|
||||
h2 = ixf_F.conv2d(
|
||||
x,
|
||||
self.weight,
|
||||
self.bias,
|
||||
self.stride,
|
||||
self.padding,
|
||||
self.dilation,
|
||||
self.groups,
|
||||
)
|
||||
return h2
|
||||
|
||||
|
||||
class ResBlockNhwcWrapper(IxformerComfyWrapper):
|
||||
def __init__(self, module) -> None:
|
||||
super().__init__()
|
||||
assert not module.updown
|
||||
assert not module.use_scale_shift_norm
|
||||
assert not module.skip_t_emb
|
||||
assert not module.exchange_temb_dims
|
||||
|
||||
if isinstance(module.skip_connection, nn.Identity):
|
||||
self.skip_connection = module.skip_connection
|
||||
elif get_class_name(module.skip_connection) == "Conv2d":
|
||||
self.skip_connection = Conv2dNhwcWrapper(module.skip_connection)
|
||||
else:
|
||||
raise NotImplementedError(
|
||||
f"ResBlockNhwcWrapper support Conv2d or nn.Identity, but got {module.skip_connection}"
|
||||
)
|
||||
|
||||
self.in_layers = module.in_layers
|
||||
self.out_layers = module.out_layers
|
||||
self.emb_layers = module.emb_layers
|
||||
self.in_layers_conv = Conv2dNhwcWrapper(module.in_layers[2])
|
||||
self.out_layers_conv = Conv2dNhwcWrapper(module.out_layers[3])
|
||||
|
||||
def forward(self, x, emb):
|
||||
# x: nhwc
|
||||
x1 = x
|
||||
# print(x1.shape)
|
||||
# fused group_norm silu
|
||||
h = ixf_F.group_norm(
|
||||
x1, # nchw->nhwc
|
||||
self.in_layers[0].num_groups,
|
||||
self.in_layers[0].weight,
|
||||
self.in_layers[0].bias,
|
||||
format=False,
|
||||
act_type=1,
|
||||
)
|
||||
h = self.in_layers_conv(h)
|
||||
|
||||
emb_out = self.emb_layers(emb)
|
||||
while len(emb_out.shape) < len(h.shape):
|
||||
emb_out = emb_out[..., None]
|
||||
|
||||
h = h + emb_out.permute(0, 2, 3, 1)
|
||||
# print(h.shape)
|
||||
h = ixf_F.group_norm(
|
||||
h,
|
||||
self.out_layers[0].num_groups,
|
||||
self.out_layers[0].weight,
|
||||
self.out_layers[0].bias,
|
||||
format=False,
|
||||
act_type=1,
|
||||
)
|
||||
|
||||
h = self.out_layers[2](h)
|
||||
h = self.out_layers_conv(h)
|
||||
# TODO: support other skip_connection
|
||||
return self.skip_connection(x) + h
|
||||
|
||||
|
||||
class DownsampleNhwcWrapper(IxformerComfyWrapper):
|
||||
def __init__(self, module) -> None:
|
||||
# TODO: support avg_pool_nd
|
||||
super().__init__()
|
||||
assert module.use_conv
|
||||
self.channels = module.channels
|
||||
self.op = Conv2dNhwcWrapper(module.op)
|
||||
|
||||
def forward(self, x):
|
||||
assert x.shape[-1] == self.channels
|
||||
return self.op(x)
|
||||
|
||||
|
||||
def ffn_forward(self, x):
|
||||
if get_class_name(self.net[0]) == "GEGLU":
|
||||
net = self.net[1:]
|
||||
geglu_net = self.net[0]
|
||||
x = geglu_net.proj(x)
|
||||
x = ixf_F.gelu_and_mul(x)
|
||||
return net(x)
|
||||
else:
|
||||
return self.net(x)
|
||||
|
||||
|
||||
# ComfyUI/comfy/ldm/modules/attention.py `class BasicTransformerBlock(nn.Module)`
|
||||
def transformer_block_forward(self, x, context=None, transformer_options={}):
|
||||
extra_options = {}
|
||||
block = transformer_options.get("block", None)
|
||||
block_index = transformer_options.get("block_index", 0)
|
||||
transformer_patches = {}
|
||||
transformer_patches_replace = {}
|
||||
|
||||
for k in transformer_options:
|
||||
if k == "patches":
|
||||
transformer_patches = transformer_options[k]
|
||||
elif k == "patches_replace":
|
||||
transformer_patches_replace = transformer_options[k]
|
||||
else:
|
||||
extra_options[k] = transformer_options[k]
|
||||
|
||||
extra_options["n_heads"] = self.n_heads
|
||||
extra_options["dim_head"] = self.d_head
|
||||
|
||||
if self.ff_in:
|
||||
x_skip = x
|
||||
x = self.ff_in(self.norm_in(x))
|
||||
if self.is_res:
|
||||
x += x_skip
|
||||
|
||||
n = self.norm1(x)
|
||||
if self.disable_self_attn:
|
||||
context_attn1 = context
|
||||
else:
|
||||
context_attn1 = None
|
||||
value_attn1 = None
|
||||
|
||||
if "attn1_patch" in transformer_patches:
|
||||
patch = transformer_patches["attn1_patch"]
|
||||
if context_attn1 is None:
|
||||
context_attn1 = n
|
||||
value_attn1 = context_attn1
|
||||
for p in patch:
|
||||
n, context_attn1, value_attn1 = p(
|
||||
n, context_attn1, value_attn1, extra_options
|
||||
)
|
||||
|
||||
if block is not None:
|
||||
transformer_block = (block[0], block[1], block_index)
|
||||
else:
|
||||
transformer_block = None
|
||||
|
||||
attn1_replace_patch = transformer_patches_replace.get("attn1", {})
|
||||
block_attn1 = transformer_block
|
||||
if block_attn1 not in attn1_replace_patch:
|
||||
block_attn1 = block
|
||||
|
||||
if block_attn1 in attn1_replace_patch:
|
||||
if context_attn1 is None:
|
||||
context_attn1 = n
|
||||
value_attn1 = n
|
||||
n = self.attn1.to_q(n)
|
||||
context_attn1 = self.attn1.to_k(context_attn1)
|
||||
value_attn1 = self.attn1.to_v(value_attn1)
|
||||
n = attn1_replace_patch[block_attn1](
|
||||
n, context_attn1, value_attn1, extra_options
|
||||
)
|
||||
n = self.attn1.to_out(n)
|
||||
else:
|
||||
n = self.attn1(n, context=context_attn1, value=value_attn1)
|
||||
|
||||
if "attn1_output_patch" in transformer_patches:
|
||||
patch = transformer_patches["attn1_output_patch"]
|
||||
for p in patch:
|
||||
n = p(n, extra_options)
|
||||
|
||||
x += n
|
||||
if "middle_patch" in transformer_patches:
|
||||
patch = transformer_patches["middle_patch"]
|
||||
for p in patch:
|
||||
x = p(x, extra_options)
|
||||
|
||||
if self.attn2 is not None:
|
||||
n = self.norm2(x)
|
||||
if self.switch_temporal_ca_to_sa:
|
||||
context_attn2 = n
|
||||
else:
|
||||
context_attn2 = context
|
||||
value_attn2 = None
|
||||
if "attn2_patch" in transformer_patches:
|
||||
patch = transformer_patches["attn2_patch"]
|
||||
value_attn2 = context_attn2
|
||||
for p in patch:
|
||||
n, context_attn2, value_attn2 = p(
|
||||
n, context_attn2, value_attn2, extra_options
|
||||
)
|
||||
|
||||
attn2_replace_patch = transformer_patches_replace.get("attn2", {})
|
||||
block_attn2 = transformer_block
|
||||
if block_attn2 not in attn2_replace_patch:
|
||||
block_attn2 = block
|
||||
|
||||
if block_attn2 in attn2_replace_patch:
|
||||
if value_attn2 is None:
|
||||
value_attn2 = context_attn2
|
||||
n = self.attn2.to_q(n)
|
||||
context_attn2 = self.attn2.to_k(context_attn2)
|
||||
value_attn2 = self.attn2.to_v(value_attn2)
|
||||
n = attn2_replace_patch[block_attn2](
|
||||
n, context_attn2, value_attn2, extra_options
|
||||
)
|
||||
n = self.attn2.to_out(n)
|
||||
else:
|
||||
n = self.attn2(n, context=context_attn2, value=value_attn2)
|
||||
|
||||
if "attn2_output_patch" in transformer_patches:
|
||||
patch = transformer_patches["attn2_output_patch"]
|
||||
for p in patch:
|
||||
n = p(n, extra_options)
|
||||
|
||||
# x += n
|
||||
# if self.is_res:
|
||||
# x_skip = x
|
||||
# x = self.ff(self.norm3(x))
|
||||
|
||||
x, x_skip = ixf_F.residual_layer_norm(
|
||||
n,
|
||||
self.norm3.normalized_shape,
|
||||
self.norm3.weight,
|
||||
self.norm3.bias,
|
||||
x,
|
||||
eps=self.norm3.eps,
|
||||
is_post_ln=False,
|
||||
)
|
||||
x = ffn_forward(self.ff, x)
|
||||
|
||||
# x = ffn_forward(self.ff, self.norm3(x))
|
||||
if self.is_res:
|
||||
x += x_skip
|
||||
|
||||
return x
|
||||
|
||||
|
||||
class SpatialTransformerNhwcWrapper(IxformerComfyWrapper):
|
||||
def __init__(self, module):
|
||||
super().__init__()
|
||||
self.use_linear = module.use_linear
|
||||
self.transformer_blocks = module.transformer_blocks
|
||||
self.norm = module.norm
|
||||
if not self.use_linear:
|
||||
self.proj_in = Conv2dNhwcWrapper(module.proj_in)
|
||||
self.proj_out = Conv2dNhwcWrapper(module.proj_out)
|
||||
else:
|
||||
self.proj_in = module.proj_in
|
||||
self.proj_out = module.proj_out
|
||||
|
||||
@ForwardWrapper
|
||||
def forward(self, x, context=None, transformer_options={}):
|
||||
# note: if no context is given, cross-attention defaults to self-attention
|
||||
if not isinstance(context, list):
|
||||
context = [context] * len(self.transformer_blocks)
|
||||
|
||||
b, h, w, c = x.shape
|
||||
x_in = x
|
||||
|
||||
# group_norm
|
||||
x = ixf_F.group_norm(
|
||||
x,
|
||||
self.norm.num_groups,
|
||||
self.norm.weight,
|
||||
self.norm.bias,
|
||||
format=False,
|
||||
)
|
||||
# conv2d
|
||||
if not self.use_linear:
|
||||
x = self.proj_in(x)
|
||||
# n,(hw),c
|
||||
x = x.view(x.shape[0], -1, x.shape[-1])
|
||||
if self.use_linear:
|
||||
x = self.proj_in(x)
|
||||
|
||||
for i, block in enumerate(self.transformer_blocks):
|
||||
transformer_options["block_index"] = i
|
||||
# x = block(x, context=context[i], transformer_options=transformer_options)
|
||||
x = transformer_block_forward(
|
||||
block, x, context=context[i], transformer_options=transformer_options
|
||||
)
|
||||
|
||||
if self.use_linear:
|
||||
x = self.proj_out(x)
|
||||
x = x.view(b, h, w, c)
|
||||
if not self.use_linear:
|
||||
x = self.proj_out(x)
|
||||
return x + x_in
|
||||
|
||||
|
||||
class UpsampleNhwcWrapper(IxformerComfyWrapper):
|
||||
def __init__(self, module) -> None:
|
||||
# TODO: support mhwc interpolate
|
||||
super().__init__()
|
||||
self.dims = module.dims
|
||||
self.use_conv = module.use_conv
|
||||
self.channels = module.channels
|
||||
if self.use_conv:
|
||||
self.conv = Conv2dNhwcWrapper(module.conv)
|
||||
|
||||
def forward(self, x, output_shape=None):
|
||||
# print("================== Upsample is running ==================")
|
||||
assert x.shape[-1] == self.channels
|
||||
assert len(x.shape) == 4
|
||||
|
||||
# nhwc -> nchw
|
||||
if output_shape is not None:
|
||||
assert len(output_shape) == 4
|
||||
output_shape = [
|
||||
output_shape[0],
|
||||
output_shape[3],
|
||||
output_shape[1],
|
||||
output_shape[2],
|
||||
]
|
||||
x = x.permute(0, 3, 1, 2).contiguous()
|
||||
if self.dims == 3:
|
||||
shape = [x.shape[2], x.shape[3] * 2, x.shape[4] * 2]
|
||||
if output_shape is not None:
|
||||
shape[1] = output_shape[3]
|
||||
shape[2] = output_shape[4]
|
||||
else:
|
||||
shape = [x.shape[2] * 2, x.shape[3] * 2]
|
||||
if output_shape is not None:
|
||||
shape[0] = output_shape[2]
|
||||
shape[1] = output_shape[3]
|
||||
# TODO: interpolate 支持 nhwc, 去掉前后转置
|
||||
x = F.interpolate(x, size=shape, mode="nearest")
|
||||
# nchw -> nhwc
|
||||
x = x.permute(0, 2, 3, 1).contiguous()
|
||||
if self.use_conv:
|
||||
x = self.conv(x)
|
||||
return x
|
||||
|
||||
|
||||
unet_wrappers = {
|
||||
"Conv2d": Conv2dNhwcWrapper,
|
||||
"ResBlock": ResBlockNhwcWrapper,
|
||||
"Downsample": DownsampleNhwcWrapper,
|
||||
"SpatialTransformer": SpatialTransformerNhwcWrapper,
|
||||
"Upsample": UpsampleNhwcWrapper,
|
||||
}
|
||||
|
||||
|
||||
def get_class_name(module):
|
||||
return module.__class__.__name__
|
||||
|
||||
|
||||
def module_wrapper(module):
|
||||
# 将原始的 module 封装为 nhwc 模式
|
||||
module_name = get_class_name(module)
|
||||
assert (
|
||||
module_name == "TimestepEmbedSequential"
|
||||
), f"ixformer unet_model_wrapper only support 'TimestepEmbedSequential' now, but got {module_name}"
|
||||
|
||||
num_sequential = len(module)
|
||||
for idx_seq in range(num_sequential):
|
||||
sub_module = module[idx_seq]
|
||||
sub_module_name = get_class_name(sub_module)
|
||||
# 判断模块是否已经封装
|
||||
if not getattr(sub_module, "is_ixf_wrapper", False):
|
||||
if sub_module_name in unet_wrappers:
|
||||
module[idx_seq].forward = unet_wrappers[sub_module_name](
|
||||
sub_module
|
||||
).forward
|
||||
module[idx_seq].is_ixf_wrapper = True
|
||||
else:
|
||||
raise NotImplementedError(f"{sub_module_name} not support")
|
||||
return module
|
||||
17
ixformer_sdk/contrib/flashinfer/__init__.py
Normal file
17
ixformer_sdk/contrib/flashinfer/__init__.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from .decode import BatchDecodeWithPagedKVCacheWrapper
|
||||
from .prefill import (
|
||||
BatchPrefillWithPagedKVCacheWrapper,
|
||||
BatchPrefillWithRaggedKVCacheWrapper,
|
||||
)
|
||||
|
||||
|
||||
def bmm_fp8():
|
||||
pass
|
||||
|
||||
|
||||
def SegmentGEMMWrapper():
|
||||
pass
|
||||
|
||||
|
||||
def bmm_fp8():
|
||||
pass
|
||||
29
ixformer_sdk/contrib/flashinfer/activation.py
Normal file
29
ixformer_sdk/contrib/flashinfer/activation.py
Normal file
@@ -0,0 +1,29 @@
|
||||
import ixformer.inference.functions as ops
|
||||
import torch
|
||||
|
||||
|
||||
def gelu_and_mul():
|
||||
pass
|
||||
|
||||
|
||||
def gelu_tanh_and_mul():
|
||||
pass
|
||||
|
||||
|
||||
def silu_and_mul(input: torch.Tensor, out: torch.Tensor = None) -> torch.Tensor:
|
||||
r"""Fused SiLU and Mul operation.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
input: torch.Tensor
|
||||
Input tensor, shape (..., 2 * hidden_size).
|
||||
|
||||
out: Optional[torch.Tensor]
|
||||
The the output tensor, if specified, the kernel will update this tensor inplace.
|
||||
|
||||
Returns
|
||||
-------
|
||||
output: torch.Tensor
|
||||
Output tensor, shape (..., hidden_size).
|
||||
"""
|
||||
return ops.silu_and_mul(input=input, output=out)
|
||||
2
ixformer_sdk/contrib/flashinfer/cascade.py
Normal file
2
ixformer_sdk/contrib/flashinfer/cascade.py
Normal file
@@ -0,0 +1,2 @@
|
||||
def merge_state():
|
||||
pass
|
||||
101
ixformer_sdk/contrib/flashinfer/decode.py
Normal file
101
ixformer_sdk/contrib/flashinfer/decode.py
Normal file
@@ -0,0 +1,101 @@
|
||||
import math
|
||||
from typing import Optional, Tuple, Union
|
||||
|
||||
import ixformer.inference.functions as ops
|
||||
import torch
|
||||
|
||||
|
||||
def _grouped_size_compiled_for_decode_kernels(
|
||||
num_qo_heads: int, num_kv_heads: int
|
||||
) -> bool:
|
||||
return (num_qo_heads // num_kv_heads) in [1, 2, 4, 8]
|
||||
|
||||
|
||||
class BatchDecodeWithPagedKVCacheWrapper:
|
||||
def __init__(
|
||||
self,
|
||||
float_workspace_buffer: torch.Tensor,
|
||||
kv_layout: str = "NHD",
|
||||
use_cuda_graph: bool = False,
|
||||
use_tensor_cores: bool = False,
|
||||
) -> None:
|
||||
pass
|
||||
|
||||
def plan(
|
||||
self,
|
||||
indptr: torch.Tensor,
|
||||
indices: torch.Tensor,
|
||||
last_page_len: torch.Tensor,
|
||||
num_qo_heads: int,
|
||||
num_kv_heads: int,
|
||||
head_dim: int,
|
||||
page_size: int,
|
||||
# pos_encoding_mode: str = "NONE",
|
||||
# window_left: int = -1,
|
||||
# logits_soft_cap: Optional[float] = None,
|
||||
data_type: Union[str, torch.dtype] = "float16",
|
||||
q_data_type: Optional[Union[str, torch.dtype]] = None,
|
||||
sm_scale: Optional[float] = None,
|
||||
# rope_scale: Optional[float] = None,
|
||||
# rope_theta: Optional[float] = None,
|
||||
max_seqlen_q: int = None,
|
||||
max_seqlen_k: int = None,
|
||||
) -> None:
|
||||
self.indptr = indptr
|
||||
self.indices = indices
|
||||
self.last_page_len = last_page_len
|
||||
self.num_qo_heads = num_qo_heads
|
||||
self.num_kv_heads = num_kv_heads
|
||||
self.head_dim = head_dim
|
||||
|
||||
assert page_size == 1
|
||||
|
||||
self.cu_seqlens_q = torch.ones_like(indptr)
|
||||
self.cu_seqlens_q[0] = 0
|
||||
self.cu_seqlens_q = torch.cumsum(self.cu_seqlens_q, dim=0).int()
|
||||
|
||||
self.cu_seqlens_k = indptr
|
||||
if sm_scale is None:
|
||||
sm_scale = 1.0 / math.sqrt(head_dim)
|
||||
|
||||
self.sm_scale = sm_scale
|
||||
self.max_seqlen_q = max_seqlen_q
|
||||
self.max_seqlen_k = max_seqlen_k
|
||||
|
||||
begin_forward = plan
|
||||
|
||||
def forward(
|
||||
self,
|
||||
q: torch.Tensor,
|
||||
paged_kv_cache: Union[torch.Tensor, Tuple[torch.Tensor, torch.Tensor]],
|
||||
pos_encoding_mode: str = "NONE",
|
||||
q_scale: Optional[float] = None,
|
||||
k_scale: Optional[float] = None,
|
||||
v_scale: Optional[float] = None,
|
||||
window_left: int = -1,
|
||||
logits_soft_cap: Optional[float] = None,
|
||||
sm_scale: Optional[float] = None,
|
||||
rope_scale: Optional[float] = None,
|
||||
rope_theta: Optional[float] = None,
|
||||
) -> torch.Tensor:
|
||||
k_cache, v_cache = paged_kv_cache
|
||||
|
||||
out = torch.empty_like(q)
|
||||
|
||||
ops.paged_attention_flashinfer(
|
||||
output=out,
|
||||
query=q,
|
||||
paged_kv_data=(k_cache.unsqueeze(1), v_cache.unsqueeze(1)),
|
||||
paged_kv_indptr=self.indptr,
|
||||
paged_kv_indices=self.indices,
|
||||
paged_kv_last_page_len=self.last_page_len,
|
||||
scale=self.sm_scale,
|
||||
max_seq_len=self.max_seqlen_k,
|
||||
kv_cache_format="NHD",
|
||||
)
|
||||
|
||||
return out
|
||||
|
||||
def end_forward(self) -> None:
|
||||
r"""Warning: this function is deprecated and has no effect."""
|
||||
pass
|
||||
61
ixformer_sdk/contrib/flashinfer/norm.py
Normal file
61
ixformer_sdk/contrib/flashinfer/norm.py
Normal file
@@ -0,0 +1,61 @@
|
||||
import ixformer.inference.functions as ops
|
||||
import torch
|
||||
|
||||
|
||||
def fused_add_rmsnorm(
|
||||
input: torch.Tensor, residual: torch.Tensor, weight: torch.Tensor, eps: float = 1e-6
|
||||
):
|
||||
r"""Fused add root mean square normalization.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
input: torch.Tensor
|
||||
Input tensor, shape (batch_size, hidden_size).
|
||||
residual: torch.Tensor
|
||||
Residual tensor, shape (batch_size, hidden_size).
|
||||
weight: torch.Tensor
|
||||
Weight tensor, shape (hidden_size,).
|
||||
eps: float
|
||||
Epsilon for numerical stability.
|
||||
"""
|
||||
return ops.residual_rms_norm(
|
||||
input=input,
|
||||
residual=residual,
|
||||
weight=weight,
|
||||
eps=eps,
|
||||
)
|
||||
|
||||
|
||||
def gemma_fused_add_rmsnorm():
|
||||
pass
|
||||
|
||||
|
||||
def gemma_rmsnorm():
|
||||
pass
|
||||
|
||||
|
||||
def rmsnorm(
|
||||
input: torch.Tensor, weight: torch.Tensor, eps: float = 1e-6
|
||||
) -> torch.Tensor:
|
||||
r"""Root mean square normalization.
|
||||
|
||||
Parameters
|
||||
----------
|
||||
input: torch.Tensor
|
||||
Input tensor, shape (batch_size, hidden_size).
|
||||
weight: torch.Tensor
|
||||
Weight tensor, shape (hidden_size,).
|
||||
eps: float
|
||||
Epsilon for numerical stability.
|
||||
|
||||
Returns
|
||||
-------
|
||||
output: torch.Tensor
|
||||
Normalized tensor, shape (batch_size, hidden_size).
|
||||
"""
|
||||
|
||||
return ops.rms_norm(
|
||||
input=input,
|
||||
weight=weight,
|
||||
eps=eps,
|
||||
)
|
||||
113
ixformer_sdk/contrib/flashinfer/prefill.py
Normal file
113
ixformer_sdk/contrib/flashinfer/prefill.py
Normal file
@@ -0,0 +1,113 @@
|
||||
import math
|
||||
from typing import Optional, Tuple, Union
|
||||
|
||||
import ixformer._C as ops
|
||||
import torch
|
||||
|
||||
|
||||
class BatchPrefillWithRaggedKVCacheWrapper:
|
||||
def __init__(
|
||||
self,
|
||||
float_workspace_buffer: torch.Tensor,
|
||||
kv_layout: str = "NHD",
|
||||
):
|
||||
pass
|
||||
|
||||
def plan(
|
||||
self,
|
||||
qo_indptr: torch.Tensor,
|
||||
kv_indptr: torch.Tensor,
|
||||
num_qo_heads: int,
|
||||
num_kv_heads: int,
|
||||
head_dim: int,
|
||||
max_seqlen_q: int,
|
||||
max_seqlen_k: int,
|
||||
# custom_mask: Optional[torch.Tensor] = None,
|
||||
# packed_custom_mask: Optional[torch.Tensor] = None,
|
||||
causal: bool = True,
|
||||
# pos_encoding_mode: str = "NONE",
|
||||
# allow_fp16_qk_reduction: bool = False,
|
||||
# window_left: int = -1,
|
||||
# logits_soft_cap: Optional[float] = None,
|
||||
sm_scale: Optional[float] = None,
|
||||
# rope_scale: Optional[float] = None,
|
||||
# rope_theta: Optional[float] = None,
|
||||
# q_data_type: str = "float16",
|
||||
) -> None:
|
||||
batch_size = len(qo_indptr) - 1
|
||||
if len(kv_indptr) != batch_size + 1:
|
||||
raise ValueError(
|
||||
"The kv_indptr length should be equal to qk_indptr length."
|
||||
)
|
||||
self._causal = causal
|
||||
self._sm_scale = sm_scale
|
||||
if sm_scale is None:
|
||||
sm_scale = 1.0 / math.sqrt(head_dim)
|
||||
|
||||
self.cu_seqlens_q = qo_indptr
|
||||
self.cu_seqlens_k = kv_indptr
|
||||
self.num_qo_heads = num_qo_heads
|
||||
self.num_kv_heads = num_kv_heads
|
||||
self.head_dim = head_dim
|
||||
self.max_seqlen_q = max_seqlen_q
|
||||
self.max_seqlen_k = max_seqlen_k
|
||||
|
||||
begin_forward = plan
|
||||
|
||||
def forward(
|
||||
self,
|
||||
q: torch.Tensor,
|
||||
k: torch.Tensor,
|
||||
v: torch.Tensor,
|
||||
causal: bool = True,
|
||||
# pos_encoding_mode: str = "NONE",
|
||||
# allow_fp16_qk_reduction: bool = False,
|
||||
# window_left: int = -1,
|
||||
logits_soft_cap: Optional[float] = None,
|
||||
sm_scale: Optional[float] = None,
|
||||
# rope_scale: Optional[float] = None,
|
||||
# rope_theta: Optional[float] = None,
|
||||
) -> torch.Tensor:
|
||||
r"""Warning: This function is deprecated, please use :meth:`run` instead."""
|
||||
|
||||
q = q.view(-1, self.num_qo_heads, self.head_dim)
|
||||
k = k.view(-1, self.num_kv_heads, self.head_dim)
|
||||
v = v.view(-1, self.num_kv_heads, self.head_dim)
|
||||
|
||||
out = torch.empty_like(q)
|
||||
|
||||
assert causal
|
||||
assert (
|
||||
logits_soft_cap is None or logits_soft_cap == 0
|
||||
), f"logits_soft_cap not supported, but got logits_soft_cap={logits_soft_cap}"
|
||||
|
||||
ops.infer.ixinfer_flash_attn_unpad(
|
||||
q,
|
||||
k,
|
||||
v,
|
||||
out,
|
||||
self.cu_seqlens_q,
|
||||
self.cu_seqlens_k,
|
||||
self.max_seqlen_q,
|
||||
self.max_seqlen_k,
|
||||
causal,
|
||||
False, # need_lse =False
|
||||
sm_scale,
|
||||
False,
|
||||
None,
|
||||
)
|
||||
return out
|
||||
|
||||
def end_forward(self) -> None:
|
||||
r"""Warning: this function is deprecated and has no effect."""
|
||||
pass
|
||||
|
||||
|
||||
class BatchPrefillWithPagedKVCacheWrapper:
|
||||
def __init__(
|
||||
self,
|
||||
float_workspace_buffer: torch.Tensor,
|
||||
kv_layout: str = "NHD",
|
||||
use_cuda_graph: bool = False,
|
||||
) -> None:
|
||||
pass
|
||||
14
ixformer_sdk/contrib/flashinfer/sampling.py
Normal file
14
ixformer_sdk/contrib/flashinfer/sampling.py
Normal file
@@ -0,0 +1,14 @@
|
||||
def min_p_sampling_from_probs():
|
||||
pass
|
||||
|
||||
|
||||
def top_k_renorm_prob():
|
||||
pass
|
||||
|
||||
|
||||
def top_k_top_p_sampling_from_probs():
|
||||
pass
|
||||
|
||||
|
||||
def top_p_renorm_prob():
|
||||
pass
|
||||
1
ixformer_sdk/contrib/tgi/__init__.py
Normal file
1
ixformer_sdk/contrib/tgi/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .fused_moe import fused_moe
|
||||
430
ixformer_sdk/contrib/tgi/fused_moe.py
Normal file
430
ixformer_sdk/contrib/tgi/fused_moe.py
Normal file
@@ -0,0 +1,430 @@
|
||||
import functools
|
||||
import json
|
||||
import os
|
||||
from typing import Any, Dict, Optional, Tuple
|
||||
from loguru import logger
|
||||
import torch
|
||||
import ixformer.inference.functions as ops
|
||||
|
||||
CHUNK_SIZE = int(os.getenv("VLLM_FUSED_MOE_CHUNK_SIZE", "65536"))
|
||||
|
||||
def fused_topk(
|
||||
hidden_states: torch.Tensor,
|
||||
gating_output: torch.Tensor,
|
||||
topk: int,
|
||||
renormalize: bool,
|
||||
):
|
||||
assert hidden_states.shape[0] == gating_output.shape[0], (
|
||||
"Number of tokens mismatch")
|
||||
|
||||
M, _ = hidden_states.shape
|
||||
|
||||
topk_weights = torch.empty(M,
|
||||
topk,
|
||||
dtype=torch.float32,
|
||||
device=hidden_states.device)
|
||||
topk_ids = torch.empty(M,
|
||||
topk,
|
||||
dtype=torch.int32,
|
||||
device=hidden_states.device)
|
||||
token_expert_indicies = torch.empty(M,
|
||||
topk,
|
||||
dtype=torch.int32,
|
||||
device=hidden_states.device)
|
||||
ops.vllm_moe_topk_softmax(
|
||||
topk_weights,
|
||||
topk_ids,
|
||||
token_expert_indicies,
|
||||
gating_output.float(), # TODO(woosuk): Optimize this.
|
||||
)
|
||||
del token_expert_indicies # Not used. Will be used in the future.
|
||||
|
||||
if renormalize:
|
||||
topk_weights = topk_weights / topk_weights.sum(dim=-1, keepdim=True)
|
||||
return topk_weights, topk_ids
|
||||
|
||||
# This is used by the Deepseek-V2 model
|
||||
def grouped_topk(hidden_states: torch.Tensor,
|
||||
gating_output: torch.Tensor,
|
||||
topk: int,
|
||||
renormalize: bool,
|
||||
num_expert_group: int = 0,
|
||||
topk_group: int = 0):
|
||||
|
||||
assert hidden_states.shape[0] == gating_output.shape[0], (
|
||||
"Number of tokens mismatch")
|
||||
|
||||
scores = torch.softmax(gating_output, dim=-1)
|
||||
num_token = scores.shape[0]
|
||||
group_scores = scores.view(num_token, num_expert_group,
|
||||
-1).max(dim=-1).values # [n, n_group]
|
||||
group_idx = torch.topk(group_scores, k=topk_group, dim=-1,
|
||||
sorted=False)[1] # [n, top_k_group]
|
||||
group_mask = torch.zeros_like(group_scores) # [n, n_group]
|
||||
group_mask.scatter_(1, group_idx, 1) # [n, n_group]
|
||||
score_mask = group_mask.unsqueeze(-1).expand(
|
||||
num_token, num_expert_group,
|
||||
scores.shape[-1] // num_expert_group).reshape(num_token, -1) # [n, e]
|
||||
tmp_scores = scores.masked_fill(~score_mask.bool(), 0.0) # [n, e]
|
||||
topk_weights, topk_ids = torch.topk(tmp_scores,
|
||||
k=topk,
|
||||
dim=-1,
|
||||
sorted=False)
|
||||
|
||||
if renormalize:
|
||||
topk_weights = topk_weights / topk_weights.sum(dim=-1, keepdim=True)
|
||||
return topk_weights, topk_ids
|
||||
|
||||
def get_config_file_name(E: int, N: int, dtype: Optional[str]) -> str:
|
||||
device_name = torch.cuda.get_device_name().replace(" ", "_")
|
||||
dtype_selector = "" if not dtype else f",dtype={dtype}"
|
||||
return f"E={E},N={N},device_name={device_name}{dtype_selector}.json"
|
||||
|
||||
@functools.lru_cache
|
||||
def get_moe_configs(E: int, N: int,
|
||||
dtype: Optional[str]) -> Optional[Dict[int, Any]]:
|
||||
"""
|
||||
Return optimized configurations for the fused MoE kernel.
|
||||
|
||||
The return value will be a dictionary that maps an irregular grid of
|
||||
batch sizes to configurations of the fused_moe kernel. To evaluate the
|
||||
kernel on a given batch size bs, the closest batch size in the grid should
|
||||
be picked and the associated configuration chosen to invoke the kernel.
|
||||
"""
|
||||
|
||||
# First look up if an optimized configuration is available in the configs
|
||||
# directory
|
||||
json_file_name = get_config_file_name(E, N, dtype)
|
||||
|
||||
config_file_path = os.path.join(
|
||||
os.path.dirname(os.path.realpath(__file__)), "configs", json_file_name)
|
||||
if os.path.exists(config_file_path):
|
||||
with open(config_file_path) as f:
|
||||
logger.info("Using configuration from %s for MoE layer.",
|
||||
config_file_path)
|
||||
# If a configuration has been found, return it
|
||||
return {int(key): val for key, val in json.load(f).items()}
|
||||
|
||||
# If no optimized configuration is available, we will use the default
|
||||
# configuration
|
||||
return None
|
||||
|
||||
def get_default_config(
|
||||
M: int,
|
||||
E: int,
|
||||
N: int,
|
||||
K: int,
|
||||
topk: int,
|
||||
dtype: Optional[str],
|
||||
) -> Dict[str, int]:
|
||||
config = {
|
||||
'BLOCK_SIZE_M': 64,
|
||||
'BLOCK_SIZE_N': 64,
|
||||
'BLOCK_SIZE_K': 32,
|
||||
'GROUP_SIZE_M': 8
|
||||
}
|
||||
if M <= E:
|
||||
config = {
|
||||
'BLOCK_SIZE_M': 16,
|
||||
'BLOCK_SIZE_N': 32,
|
||||
'BLOCK_SIZE_K': 64,
|
||||
'GROUP_SIZE_M': 1
|
||||
}
|
||||
numel = M * topk
|
||||
if numel <= 64:
|
||||
config['BLOCK_SIZE_M'] = 32
|
||||
elif numel <= 1024:
|
||||
config['BLOCK_SIZE_M'] = 64
|
||||
else:
|
||||
config['BLOCK_SIZE_M'] = 256
|
||||
return config
|
||||
|
||||
def try_get_optimal_moe_config(
|
||||
w1_shape: Tuple[int, ...],
|
||||
w2_shape: Tuple[int, ...],
|
||||
top_k: int,
|
||||
dtype: Optional[str],
|
||||
M: int,
|
||||
override_config: Optional[Dict[str, Any]] = None,
|
||||
):
|
||||
if override_config:
|
||||
config = override_config
|
||||
else:
|
||||
# First try to load optimal config from the file
|
||||
E, _, N = w2_shape
|
||||
configs = get_moe_configs(E, N, dtype)
|
||||
|
||||
if configs:
|
||||
# If an optimal configuration map has been found, look up the
|
||||
# optimal config
|
||||
config = configs[min(configs.keys(), key=lambda x: abs(x - M))]
|
||||
else:
|
||||
# Else use the default config
|
||||
config = get_default_config(M, E, N, w1_shape[2], top_k, dtype)
|
||||
return config
|
||||
|
||||
def moe_align_block_size(
|
||||
topk_ids: torch.Tensor, block_size: int,
|
||||
num_experts: int) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]:
|
||||
"""
|
||||
Aligns the token distribution across experts to be compatible with block
|
||||
size for matrix multiplication.
|
||||
|
||||
Parameters:
|
||||
- topk_ids: A tensor of shape [total_tokens, top_k] representing the
|
||||
top-k expert indices for each token.
|
||||
- block_size: The block size used in block matrix multiplication.
|
||||
- num_experts: The total number of experts.
|
||||
|
||||
Returns:
|
||||
- sorted_token_ids: A tensor containing the sorted token indices according
|
||||
to their allocated expert.
|
||||
- expert_ids: A tensor indicating the assigned expert index for each block.
|
||||
- num_tokens_post_padded: The total number of tokens after padding,
|
||||
ensuring divisibility by block_size.
|
||||
|
||||
This function pads the number of tokens that each expert needs to process
|
||||
so that it is divisible by block_size.
|
||||
Padding ensures that during block matrix multiplication, the dimensions
|
||||
align correctly.
|
||||
|
||||
Example:
|
||||
Given topk_ids = [[2, 3, 4], [1, 2, 4], [1, 3, 4], [1, 2, 3]],
|
||||
block_size = 4, and num_experts = 4:
|
||||
- We initially have 12 tokens (after repeating 'top_k' times) and 4 experts,
|
||||
with each expert needing to process 3 tokens.
|
||||
- As block_size is 4, we pad 1 token for each expert.
|
||||
- First, flatten topk_ids to [2, 3, 4, 1, 2, 4, 1, 3, 4, 1, 2, 3].
|
||||
- Then append padding tokens [12, 12, 12, 12] for each block.
|
||||
- After sorting by expert index, we obtain token_ids
|
||||
[3, 6, 9, 12, 0, 4, 10, 12, 1, 7, 11, 12, 2, 5, 8, 12].
|
||||
Tokens 12 are non-existent (padding) and are ignored in
|
||||
the subsequent matrix multiplication.
|
||||
- The padding ensures that the total number of tokens is now divisible
|
||||
by block_size for proper block matrix operations.
|
||||
"""
|
||||
max_num_tokens_padded = topk_ids.numel() + num_experts * (block_size - 1)
|
||||
sorted_ids = torch.empty((max_num_tokens_padded, ),
|
||||
dtype=torch.int32,
|
||||
device=topk_ids.device)
|
||||
sorted_ids.fill_(topk_ids.numel())
|
||||
# max_num_m_blocks = triton.cdiv(max_num_tokens_padded, block_size)
|
||||
max_num_m_blocks = topk_ids.numel() + num_experts
|
||||
expert_ids = torch.empty((max_num_m_blocks, ),
|
||||
dtype=torch.int32,
|
||||
device=topk_ids.device)
|
||||
num_tokens_post_pad = torch.empty((1),
|
||||
dtype=torch.int32,
|
||||
device=topk_ids.device)
|
||||
ops.vllm_moe_align_block_size(topk_ids, num_experts, block_size, sorted_ids,
|
||||
expert_ids, num_tokens_post_pad)
|
||||
return sorted_ids, expert_ids, num_tokens_post_pad
|
||||
|
||||
def invoke_fused_moe_kernel(A: torch.Tensor, B: torch.Tensor, C: torch.Tensor,
|
||||
A_scale: Optional[torch.Tensor],
|
||||
B_scale: Optional[torch.Tensor],
|
||||
topk_weights: torch.Tensor, topk_ids: torch.Tensor,
|
||||
sorted_token_ids: torch.Tensor,
|
||||
expert_ids: torch.Tensor,
|
||||
num_tokens_post_padded: torch.Tensor,
|
||||
mul_routed_weight: bool, top_k: int,
|
||||
config: Dict[str, Any], compute_type: torch.dtype,
|
||||
use_fp8: bool) -> None:
|
||||
ops.vllm_invoke_fused_moe_kernel(A, B, C, topk_weights, topk_ids,
|
||||
sorted_token_ids,expert_ids, num_tokens_post_padded,
|
||||
mul_routed_weight, top_k, config['BLOCK_SIZE_M'])
|
||||
|
||||
def fused_experts(hidden_states: torch.Tensor,
|
||||
w1: torch.Tensor,
|
||||
w2: torch.Tensor,
|
||||
topk_weights: torch.Tensor,
|
||||
topk_ids: torch.Tensor,
|
||||
inplace: bool = False,
|
||||
override_config: Optional[Dict[str, Any]] = None,
|
||||
use_fp8: bool = False,
|
||||
w1_scale: Optional[torch.Tensor] = None,
|
||||
w2_scale: Optional[torch.Tensor] = None,
|
||||
a1_scale: Optional[torch.Tensor] = None,
|
||||
a2_scale: Optional[torch.Tensor] = None):
|
||||
# Check constraints.
|
||||
assert hidden_states.shape[1] == w1.shape[2], "Hidden size mismatch"
|
||||
assert topk_weights.shape == topk_ids.shape, "topk shape mismatch"
|
||||
assert hidden_states.is_contiguous(), "Hidden_states must be contiguous"
|
||||
assert w1.is_contiguous(), "Expert weights1 must be contiguous"
|
||||
assert w2.is_contiguous(), "Expert weights2 must be contiguous"
|
||||
assert hidden_states.dtype in [
|
||||
torch.float32, torch.float16, torch.bfloat16
|
||||
]
|
||||
|
||||
num_tokens, _ = hidden_states.shape
|
||||
E, N, _ = w1.shape
|
||||
# We execute the fused_moe kernel in chunks to circumvent this issue:
|
||||
# https://github.com/vllm-project/vllm/issues/5938
|
||||
M = min(num_tokens, CHUNK_SIZE)
|
||||
|
||||
get_config_func = functools.partial(
|
||||
try_get_optimal_moe_config,
|
||||
w1.shape,
|
||||
w2.shape,
|
||||
topk_ids.shape[1],
|
||||
"float8" if use_fp8 else None,
|
||||
override_config=override_config,
|
||||
)
|
||||
|
||||
config = get_config_func(M)
|
||||
|
||||
intermediate_cache1 = torch.empty((M, topk_ids.shape[1], N),
|
||||
device=hidden_states.device,
|
||||
dtype=hidden_states.dtype)
|
||||
intermediate_cache2 = torch.empty((M * topk_ids.shape[1], N // 2),
|
||||
device=hidden_states.device,
|
||||
dtype=hidden_states.dtype)
|
||||
intermediate_cache3 = torch.empty((M, topk_ids.shape[1], w2.shape[1]),
|
||||
device=hidden_states.device,
|
||||
dtype=hidden_states.dtype)
|
||||
|
||||
compute_type = (torch.bfloat16
|
||||
if hidden_states.dtype == torch.bfloat16 else torch.float16)
|
||||
|
||||
if inplace:
|
||||
out_hidden_states = hidden_states
|
||||
else:
|
||||
out_hidden_states = torch.empty_like(hidden_states)
|
||||
|
||||
for chunk in range((num_tokens // CHUNK_SIZE) + 1):
|
||||
begin_chunk_idx, end_chunk_idx = (chunk * CHUNK_SIZE,
|
||||
min((chunk + 1) * CHUNK_SIZE,
|
||||
num_tokens))
|
||||
curr_hidden_states = hidden_states[begin_chunk_idx:end_chunk_idx]
|
||||
tokens_in_chunk, _ = curr_hidden_states.shape
|
||||
|
||||
if tokens_in_chunk == 0:
|
||||
break
|
||||
|
||||
if tokens_in_chunk < CHUNK_SIZE and chunk > 0:
|
||||
# Adjust the intermediate cache size and config for the last
|
||||
# chunk. Note that in most cases we only have one chunk
|
||||
# so the cache size and config are already set correctly and
|
||||
# do not need to be adjusted.
|
||||
intermediate_cache1 = intermediate_cache1[:tokens_in_chunk]
|
||||
intermediate_cache2 = intermediate_cache2[:tokens_in_chunk]
|
||||
intermediate_cache3 = intermediate_cache3[:tokens_in_chunk]
|
||||
config = get_config_func(tokens_in_chunk)
|
||||
|
||||
curr_topk_ids = topk_ids[begin_chunk_idx:end_chunk_idx]
|
||||
curr_topk_weights = topk_weights[begin_chunk_idx:end_chunk_idx]
|
||||
|
||||
sorted_token_ids, expert_ids, num_tokens_post_padded = (
|
||||
moe_align_block_size(curr_topk_ids, config['BLOCK_SIZE_M'], E))
|
||||
|
||||
invoke_fused_moe_kernel(curr_hidden_states,
|
||||
w1,
|
||||
intermediate_cache1,
|
||||
a1_scale,
|
||||
w1_scale,
|
||||
curr_topk_weights,
|
||||
curr_topk_ids,
|
||||
sorted_token_ids,
|
||||
expert_ids,
|
||||
num_tokens_post_padded,
|
||||
False,
|
||||
topk_ids.shape[1],
|
||||
config,
|
||||
compute_type=compute_type,
|
||||
use_fp8=use_fp8)
|
||||
|
||||
ops.silu_and_mul(intermediate_cache1.view(-1, N), intermediate_cache2)
|
||||
|
||||
invoke_fused_moe_kernel(intermediate_cache2,
|
||||
w2,
|
||||
intermediate_cache3,
|
||||
a2_scale,
|
||||
w2_scale,
|
||||
curr_topk_weights,
|
||||
curr_topk_ids,
|
||||
sorted_token_ids,
|
||||
expert_ids,
|
||||
num_tokens_post_padded,
|
||||
True,
|
||||
1,
|
||||
config,
|
||||
compute_type=compute_type,
|
||||
use_fp8=use_fp8)
|
||||
|
||||
torch.sum(intermediate_cache3.view(*intermediate_cache3.shape),
|
||||
dim=1,
|
||||
out=out_hidden_states[begin_chunk_idx:end_chunk_idx])
|
||||
return out_hidden_states
|
||||
|
||||
def fused_moe(
|
||||
hidden_states: torch.Tensor,
|
||||
w1: torch.Tensor,
|
||||
w2: torch.Tensor,
|
||||
gating_output: torch.Tensor,
|
||||
topk: int,
|
||||
renormalize: bool,
|
||||
inplace: bool = False,
|
||||
override_config: Optional[Dict[str, Any]] = None,
|
||||
use_grouped_topk: bool = False,
|
||||
num_expert_group: Optional[int] = None,
|
||||
topk_group: Optional[int] = None,
|
||||
use_fp8: bool = False,
|
||||
w1_scale: Optional[torch.Tensor] = None,
|
||||
w2_scale: Optional[torch.Tensor] = None,
|
||||
a1_scale: Optional[torch.Tensor] = None,
|
||||
a2_scale: Optional[torch.Tensor] = None,
|
||||
) -> torch.Tensor:
|
||||
"""
|
||||
This function computes a Mixture of Experts (MoE) layer using two sets of
|
||||
weights, w1 and w2, and top-k gating mechanism.
|
||||
|
||||
Parameters:
|
||||
- hidden_states (torch.Tensor): The input tensor to the MoE layer.
|
||||
- w1 (torch.Tensor): The first set of expert weights.
|
||||
- w2 (torch.Tensor): The second set of expert weights.
|
||||
- gating_output (torch.Tensor): The output of the gating operation
|
||||
(before softmax).
|
||||
- topk (int): The number of top-k experts to select.
|
||||
- renormalize (bool): If True, renormalize the top-k weights to sum to 1.
|
||||
- inplace (bool): If True, perform the operation in-place.
|
||||
Defaults to False.
|
||||
- override_config (Optional[Dict[str, Any]]): Optional override
|
||||
for the kernel configuration.
|
||||
- num_expert_group: Optional[int]: additional parameter for grouped_topk
|
||||
- topk_group: Optional[int]: additional parameter for grouped_topk
|
||||
- use_grouped_topk: If True, use grouped_topk instead of fused_topk
|
||||
note: Deepseekv2 model uses grouped_topk
|
||||
- use_fp8 (bool): If True, use fp8 arithmetic to compute the inner
|
||||
products for w1 and w2. Defaults to False.
|
||||
- w1_scale (Optional[torch.Tensor]): Optional scale to be used for
|
||||
w1.
|
||||
- w2_scale (Optional[torch.Tensor]): Optional scale to be used for
|
||||
w2.
|
||||
|
||||
Returns:
|
||||
- torch.Tensor: The output tensor after applying the MoE layer.
|
||||
"""
|
||||
# Check constraints.
|
||||
assert gating_output.shape[1] == w1.shape[0], "Number of experts mismatch"
|
||||
|
||||
if use_grouped_topk:
|
||||
assert num_expert_group is not None and topk_group is not None
|
||||
topk_weights, topk_ids = grouped_topk(hidden_states, gating_output,
|
||||
topk, renormalize,
|
||||
num_expert_group, topk_group)
|
||||
else:
|
||||
topk_weights, topk_ids = fused_topk(hidden_states, gating_output, topk,
|
||||
renormalize)
|
||||
|
||||
return fused_experts(hidden_states,
|
||||
w1,
|
||||
w2,
|
||||
topk_weights,
|
||||
topk_ids,
|
||||
inplace=inplace,
|
||||
override_config=override_config,
|
||||
use_fp8=use_fp8,
|
||||
w1_scale=w1_scale,
|
||||
w2_scale=w2_scale,
|
||||
a1_scale=a1_scale,
|
||||
a2_scale=a2_scale)
|
||||
2
ixformer_sdk/contrib/transformers/__init__.py
Normal file
2
ixformer_sdk/contrib/transformers/__init__.py
Normal file
@@ -0,0 +1,2 @@
|
||||
from .models.bert.modeling_bert import BertForQuestionAnswering
|
||||
from .models.t5.modeling_t5 import T5ForConditionalGeneration
|
||||
@@ -0,0 +1,150 @@
|
||||
# coding=utf-8
|
||||
# Copyright 2018 The Google AI Language Team Authors and The HuggingFace Inc. team.
|
||||
# Copyright (c) 2018, NVIDIA CORPORATION. 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.
|
||||
"""BERT model configuration"""
|
||||
|
||||
from collections import OrderedDict
|
||||
from typing import Mapping
|
||||
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
from transformers.onnx import OnnxConfig
|
||||
from transformers.utils import logging
|
||||
|
||||
logger = logging.get_logger(__name__)
|
||||
|
||||
|
||||
class BertConfig(PretrainedConfig):
|
||||
r"""
|
||||
This is the configuration class to store the configuration of a [`BertModel`] or a [`TFBertModel`]. It is used to
|
||||
instantiate a BERT model according to the specified arguments, defining the model architecture. Instantiating a
|
||||
configuration with the defaults will yield a similar configuration to that of the BERT
|
||||
[google-bert/bert-base-uncased](https://huggingface.co/google-bert/bert-base-uncased) architecture.
|
||||
|
||||
Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
|
||||
documentation from [`PretrainedConfig`] for more information.
|
||||
|
||||
|
||||
Args:
|
||||
vocab_size (`int`, *optional*, defaults to 30522):
|
||||
Vocabulary size of the BERT model. Defines the number of different tokens that can be represented by the
|
||||
`inputs_ids` passed when calling [`BertModel`] or [`TFBertModel`].
|
||||
hidden_size (`int`, *optional*, defaults to 768):
|
||||
Dimensionality of the encoder layers and the pooler layer.
|
||||
num_hidden_layers (`int`, *optional*, defaults to 12):
|
||||
Number of hidden layers in the Transformer encoder.
|
||||
num_attention_heads (`int`, *optional*, defaults to 12):
|
||||
Number of attention heads for each attention layer in the Transformer encoder.
|
||||
intermediate_size (`int`, *optional*, defaults to 3072):
|
||||
Dimensionality of the "intermediate" (often named feed-forward) layer in the Transformer encoder.
|
||||
hidden_act (`str` or `Callable`, *optional*, defaults to `"gelu"`):
|
||||
The non-linear activation function (function or string) in the encoder and pooler. If string, `"gelu"`,
|
||||
`"relu"`, `"silu"` and `"gelu_new"` are supported.
|
||||
hidden_dropout_prob (`float`, *optional*, defaults to 0.1):
|
||||
The dropout probability for all fully connected layers in the embeddings, encoder, and pooler.
|
||||
attention_probs_dropout_prob (`float`, *optional*, defaults to 0.1):
|
||||
The dropout ratio for the attention probabilities.
|
||||
max_position_embeddings (`int`, *optional*, defaults to 512):
|
||||
The maximum sequence length that this model might ever be used with. Typically set this to something large
|
||||
just in case (e.g., 512 or 1024 or 2048).
|
||||
type_vocab_size (`int`, *optional*, defaults to 2):
|
||||
The vocabulary size of the `token_type_ids` passed when calling [`BertModel`] or [`TFBertModel`].
|
||||
initializer_range (`float`, *optional*, defaults to 0.02):
|
||||
The standard deviation of the truncated_normal_initializer for initializing all weight matrices.
|
||||
layer_norm_eps (`float`, *optional*, defaults to 1e-12):
|
||||
The epsilon used by the layer normalization layers.
|
||||
position_embedding_type (`str`, *optional*, defaults to `"absolute"`):
|
||||
Type of position embedding. Choose one of `"absolute"`, `"relative_key"`, `"relative_key_query"`. For
|
||||
positional embeddings use `"absolute"`. For more information on `"relative_key"`, please refer to
|
||||
[Self-Attention with Relative Position Representations (Shaw et al.)](https://arxiv.org/abs/1803.02155).
|
||||
For more information on `"relative_key_query"`, please refer to *Method 4* in [Improve Transformer Models
|
||||
with Better Relative Position Embeddings (Huang et al.)](https://arxiv.org/abs/2009.13658).
|
||||
is_decoder (`bool`, *optional*, defaults to `False`):
|
||||
Whether the model is used as a decoder or not. If `False`, the model is used as an encoder.
|
||||
use_cache (`bool`, *optional*, defaults to `True`):
|
||||
Whether or not the model should return the last key/values attentions (not used by all models). Only
|
||||
relevant if `config.is_decoder=True`.
|
||||
classifier_dropout (`float`, *optional*):
|
||||
The dropout ratio for the classification head.
|
||||
|
||||
Examples:
|
||||
|
||||
```python
|
||||
>>> from transformers import BertConfig, BertModel
|
||||
|
||||
>>> # Initializing a BERT google-bert/bert-base-uncased style configuration
|
||||
>>> configuration = BertConfig()
|
||||
|
||||
>>> # Initializing a model (with random weights) from the google-bert/bert-base-uncased style configuration
|
||||
>>> model = BertModel(configuration)
|
||||
|
||||
>>> # Accessing the model configuration
|
||||
>>> configuration = model.config
|
||||
```"""
|
||||
|
||||
model_type = "bert"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vocab_size=30522,
|
||||
hidden_size=768,
|
||||
num_hidden_layers=12,
|
||||
num_attention_heads=12,
|
||||
intermediate_size=3072,
|
||||
hidden_act="gelu",
|
||||
hidden_dropout_prob=0.1,
|
||||
attention_probs_dropout_prob=0.1,
|
||||
max_position_embeddings=512,
|
||||
type_vocab_size=2,
|
||||
initializer_range=0.02,
|
||||
layer_norm_eps=1e-12,
|
||||
pad_token_id=0,
|
||||
position_embedding_type="absolute",
|
||||
use_cache=True,
|
||||
classifier_dropout=None,
|
||||
**kwargs,
|
||||
):
|
||||
super().__init__(pad_token_id=pad_token_id, **kwargs)
|
||||
|
||||
self.vocab_size = vocab_size
|
||||
self.hidden_size = hidden_size
|
||||
self.num_hidden_layers = num_hidden_layers
|
||||
self.num_attention_heads = num_attention_heads
|
||||
self.hidden_act = hidden_act
|
||||
self.intermediate_size = intermediate_size
|
||||
self.hidden_dropout_prob = hidden_dropout_prob
|
||||
self.attention_probs_dropout_prob = attention_probs_dropout_prob
|
||||
self.max_position_embeddings = max_position_embeddings
|
||||
self.type_vocab_size = type_vocab_size
|
||||
self.initializer_range = initializer_range
|
||||
self.layer_norm_eps = layer_norm_eps
|
||||
self.position_embedding_type = position_embedding_type
|
||||
self.use_cache = use_cache
|
||||
self.classifier_dropout = classifier_dropout
|
||||
|
||||
|
||||
class BertOnnxConfig(OnnxConfig):
|
||||
@property
|
||||
def inputs(self) -> Mapping[str, Mapping[int, str]]:
|
||||
if self.task == "multiple-choice":
|
||||
dynamic_axis = {0: "batch", 1: "choice", 2: "sequence"}
|
||||
else:
|
||||
dynamic_axis = {0: "batch", 1: "sequence"}
|
||||
return OrderedDict(
|
||||
[
|
||||
("input_ids", dynamic_axis),
|
||||
("attention_mask", dynamic_axis),
|
||||
("token_type_ids", dynamic_axis),
|
||||
]
|
||||
)
|
||||
2145
ixformer_sdk/contrib/transformers/models/bert/modeling_bert.py
Normal file
2145
ixformer_sdk/contrib/transformers/models/bert/modeling_bert.py
Normal file
File diff suppressed because it is too large
Load Diff
174
ixformer_sdk/contrib/transformers/models/t5/configuration_t5.py
Normal file
174
ixformer_sdk/contrib/transformers/models/t5/configuration_t5.py
Normal file
@@ -0,0 +1,174 @@
|
||||
# coding=utf-8
|
||||
# Copyright 2020, The T5 Authors and HuggingFace Inc.
|
||||
#
|
||||
# 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.
|
||||
""" T5 model configuration"""
|
||||
from typing import Mapping
|
||||
|
||||
from transformers.configuration_utils import PretrainedConfig
|
||||
from transformers.onnx import OnnxSeq2SeqConfigWithPast
|
||||
from transformers.utils import logging
|
||||
|
||||
logger = logging.get_logger(__name__)
|
||||
|
||||
T5_PRETRAINED_CONFIG_ARCHIVE_MAP = {
|
||||
"t5-small": "https://huggingface.co/t5-small/resolve/main/config.json",
|
||||
"t5-base": "https://huggingface.co/t5-base/resolve/main/config.json",
|
||||
"t5-large": "https://huggingface.co/t5-large/resolve/main/config.json",
|
||||
"t5-3b": "https://huggingface.co/t5-3b/resolve/main/config.json",
|
||||
"t5-11b": "https://huggingface.co/t5-11b/resolve/main/config.json",
|
||||
}
|
||||
|
||||
|
||||
class T5Config(PretrainedConfig):
|
||||
r"""
|
||||
This is the configuration class to store the configuration of a [`T5Model`] or a [`TFT5Model`]. It is used to
|
||||
instantiate a T5 model according to the specified arguments, defining the model architecture. Instantiating a
|
||||
configuration with the defaults will yield a similar configuration to that of the T5
|
||||
[t5-small](https://huggingface.co/t5-small) architecture.
|
||||
|
||||
Configuration objects inherit from [`PretrainedConfig`] and can be used to control the model outputs. Read the
|
||||
documentation from [`PretrainedConfig`] for more information.
|
||||
|
||||
Arguments:
|
||||
vocab_size (`int`, *optional*, defaults to 32128):
|
||||
Vocabulary size of the T5 model. Defines the number of different tokens that can be represented by the
|
||||
`inputs_ids` passed when calling [`T5Model`] or [`TFT5Model`].
|
||||
d_model (`int`, *optional*, defaults to 512):
|
||||
Size of the encoder layers and the pooler layer.
|
||||
d_kv (`int`, *optional*, defaults to 64):
|
||||
Size of the key, query, value projections per attention head. The `inner_dim` of the projection layer will
|
||||
be defined as `num_heads * d_kv`.
|
||||
d_ff (`int`, *optional*, defaults to 2048):
|
||||
Size of the intermediate feed forward layer in each `T5Block`.
|
||||
num_layers (`int`, *optional*, defaults to 6):
|
||||
Number of hidden layers in the Transformer encoder.
|
||||
num_decoder_layers (`int`, *optional*):
|
||||
Number of hidden layers in the Transformer decoder. Will use the same value as `num_layers` if not set.
|
||||
num_heads (`int`, *optional*, defaults to 8):
|
||||
Number of attention heads for each attention layer in the Transformer encoder.
|
||||
relative_attention_num_buckets (`int`, *optional*, defaults to 32):
|
||||
The number of buckets to use for each attention layer.
|
||||
relative_attention_max_distance (`int`, *optional*, defaults to 128):
|
||||
The maximum distance of the longer sequences for the bucket separation.
|
||||
dropout_rate (`float`, *optional*, defaults to 0.1):
|
||||
The ratio for all dropout layers.
|
||||
layer_norm_eps (`float`, *optional*, defaults to 1e-6):
|
||||
The epsilon used by the layer normalization layers.
|
||||
initializer_factor (`float`, *optional*, defaults to 1):
|
||||
A factor for initializing all weight matrices (should be kept to 1, used internally for initialization
|
||||
testing).
|
||||
feed_forward_proj (`string`, *optional*, defaults to `"relu"`):
|
||||
Type of feed forward layer to be used. Should be one of `"relu"` or `"gated-gelu"`. T5v1.1 uses the
|
||||
`"gated-gelu"` feed forward projection. Original T5 uses `"relu"`.
|
||||
use_cache (`bool`, *optional*, defaults to `True`):
|
||||
Whether or not the model should return the last key/values attentions (not used by all models).
|
||||
"""
|
||||
model_type = "t5"
|
||||
keys_to_ignore_at_inference = ["past_key_values"]
|
||||
attribute_map = {
|
||||
"hidden_size": "d_model",
|
||||
"num_attention_heads": "num_heads",
|
||||
"num_hidden_layers": "num_layers",
|
||||
}
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
vocab_size=32128,
|
||||
d_model=512,
|
||||
d_kv=64,
|
||||
d_ff=2048,
|
||||
num_layers=6,
|
||||
num_decoder_layers=None,
|
||||
num_heads=8,
|
||||
relative_attention_num_buckets=32,
|
||||
relative_attention_max_distance=128,
|
||||
dropout_rate=0.1,
|
||||
layer_norm_epsilon=1e-6,
|
||||
initializer_factor=1.0,
|
||||
feed_forward_proj="relu",
|
||||
is_encoder_decoder=True,
|
||||
use_cache=True,
|
||||
pad_token_id=0,
|
||||
eos_token_id=1,
|
||||
**kwargs,
|
||||
):
|
||||
self.vocab_size = vocab_size
|
||||
self.d_model = d_model
|
||||
self.d_kv = d_kv
|
||||
self.d_ff = d_ff
|
||||
self.num_layers = num_layers
|
||||
self.num_decoder_layers = (
|
||||
num_decoder_layers if num_decoder_layers is not None else self.num_layers
|
||||
) # default = symmetry
|
||||
self.num_heads = num_heads
|
||||
self.relative_attention_num_buckets = relative_attention_num_buckets
|
||||
self.relative_attention_max_distance = relative_attention_max_distance
|
||||
self.dropout_rate = dropout_rate
|
||||
self.layer_norm_epsilon = layer_norm_epsilon
|
||||
self.initializer_factor = initializer_factor
|
||||
self.feed_forward_proj = feed_forward_proj
|
||||
self.use_cache = use_cache
|
||||
|
||||
act_info = self.feed_forward_proj.split("-")
|
||||
self.dense_act_fn = act_info[-1]
|
||||
self.is_gated_act = act_info[0] == "gated"
|
||||
|
||||
if len(act_info) > 1 and act_info[0] != "gated" or len(act_info) > 2:
|
||||
raise ValueError(
|
||||
f"`feed_forward_proj`: {feed_forward_proj} is not a valid activation function of the dense layer."
|
||||
"Please make sure `feed_forward_proj` is of the format `gated-{ACT_FN}` or `{ACT_FN}`, e.g. "
|
||||
"'gated-gelu' or 'relu'"
|
||||
)
|
||||
|
||||
# for backwards compatibility
|
||||
if feed_forward_proj == "gated-gelu":
|
||||
self.dense_act_fn = "gelu_new"
|
||||
|
||||
super().__init__(
|
||||
pad_token_id=pad_token_id,
|
||||
eos_token_id=eos_token_id,
|
||||
is_encoder_decoder=is_encoder_decoder,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
|
||||
class T5OnnxConfig(OnnxSeq2SeqConfigWithPast):
|
||||
@property
|
||||
def inputs(self) -> Mapping[str, Mapping[int, str]]:
|
||||
common_inputs = {
|
||||
"input_ids": {0: "batch", 1: "encoder_sequence"},
|
||||
"attention_mask": {0: "batch", 1: "encoder_sequence"},
|
||||
}
|
||||
if self.use_past:
|
||||
common_inputs["attention_mask"][1] = "past_encoder_sequence + sequence"
|
||||
common_inputs["decoder_input_ids"] = {0: "batch"}
|
||||
common_inputs["decoder_attention_mask"] = {
|
||||
0: "batch",
|
||||
1: "past_decoder_sequence + sequence",
|
||||
}
|
||||
else:
|
||||
common_inputs["decoder_input_ids"] = {0: "batch", 1: "decoder_sequence"}
|
||||
common_inputs["decoder_attention_mask"] = {
|
||||
0: "batch",
|
||||
1: "decoder_sequence",
|
||||
}
|
||||
|
||||
if self.use_past:
|
||||
self.fill_with_past_key_values_(common_inputs, direction="inputs")
|
||||
|
||||
return common_inputs
|
||||
|
||||
@property
|
||||
def default_onnx_opset(self) -> int:
|
||||
return 13
|
||||
@@ -0,0 +1,315 @@
|
||||
import torch
|
||||
from transformers.activations import NewGELUActivation
|
||||
|
||||
import ixformer
|
||||
|
||||
|
||||
def self_attention_forward(
|
||||
self,
|
||||
hidden_states,
|
||||
attention_mask=None,
|
||||
position_bias=None,
|
||||
layer_head_mask=None,
|
||||
past_key_value=None,
|
||||
use_cache=False,
|
||||
output_attentions=False,
|
||||
):
|
||||
assert output_attentions is False
|
||||
assert layer_head_mask is None
|
||||
|
||||
normed_hidden_states = self.layer_norm(hidden_states)
|
||||
|
||||
if not hasattr(self, "qkv_weight"):
|
||||
self.qkv_weight = torch.cat(
|
||||
[
|
||||
self.SelfAttention.q.weight,
|
||||
self.SelfAttention.k.weight,
|
||||
self.SelfAttention.v.weight,
|
||||
],
|
||||
dim=0,
|
||||
)
|
||||
self.qkv_bias = None
|
||||
|
||||
del self.SelfAttention.q.weight
|
||||
del self.SelfAttention.k.weight
|
||||
del self.SelfAttention.v.weight
|
||||
|
||||
batch_size, seq_length = hidden_states.shape[:2]
|
||||
real_seq_length = seq_length
|
||||
if past_key_value is not None:
|
||||
if len(past_key_value) != 2:
|
||||
raise ValueError(
|
||||
f"past_key_value should have 2 past states: keys and values. Got { len(past_key_value)} past states"
|
||||
)
|
||||
real_seq_length += past_key_value[0].shape[2]
|
||||
key_length = real_seq_length
|
||||
|
||||
def unshape(states):
|
||||
"""reshape"""
|
||||
return (
|
||||
states.transpose(1, 2)
|
||||
.contiguous()
|
||||
.view(batch_size, -1, self.SelfAttention.inner_dim)
|
||||
)
|
||||
|
||||
qkv = ixformer.functions.linear(
|
||||
normed_hidden_states, self.qkv_weight, self.qkv_bias
|
||||
)
|
||||
|
||||
if past_key_value is not None:
|
||||
pask_key, past_value = past_key_value
|
||||
(
|
||||
query_states,
|
||||
key_states,
|
||||
value_states,
|
||||
) = ixformer.functions.t5_split_qkv_update_kv_cache(
|
||||
qkv,
|
||||
pask_key,
|
||||
past_value,
|
||||
self.SelfAttention.n_heads,
|
||||
self.SelfAttention.key_value_proj_dim,
|
||||
)
|
||||
else:
|
||||
query_states, key_states, value_states = ixformer.functions.t5_split_qkv(
|
||||
qkv, self.SelfAttention.n_heads, self.SelfAttention.key_value_proj_dim
|
||||
)
|
||||
|
||||
if position_bias is None:
|
||||
if not self.SelfAttention.has_relative_attention_bias:
|
||||
position_bias = torch.zeros(
|
||||
(1, self.SelfAttention.n_heads, real_seq_length, key_length),
|
||||
device=query_states.device,
|
||||
dtype=query_states.dtype,
|
||||
)
|
||||
else:
|
||||
position_bias = self.SelfAttention.compute_bias(
|
||||
real_seq_length, key_length, device=query_states.device
|
||||
)
|
||||
|
||||
# if key and values are already calculated
|
||||
# we want only the last query position bias
|
||||
if past_key_value is not None:
|
||||
position_bias = position_bias[:, :, -hidden_states.size(1) :, :]
|
||||
|
||||
if attention_mask is not None:
|
||||
# (batch_size, n_heads, seq_length, key_length)
|
||||
position_bias = position_bias + attention_mask
|
||||
|
||||
if self.SelfAttention.pruned_heads:
|
||||
mask = torch.ones(position_bias.shape[1])
|
||||
mask[list(self.pruned_heads)] = 0
|
||||
position_bias_masked = position_bias[:, mask.bool()]
|
||||
else:
|
||||
position_bias_masked = position_bias
|
||||
|
||||
attn_output = ixformer.functions.ixinfer_flash_attn_pad(
|
||||
query_states.contiguous(),
|
||||
key_states.contiguous(),
|
||||
value_states.contiguous(),
|
||||
mask=position_bias_masked.float().contiguous(),
|
||||
atten_scale=1,
|
||||
)
|
||||
attn_output = unshape(attn_output)
|
||||
|
||||
attn_output = self.SelfAttention.o(attn_output)
|
||||
|
||||
present_key_value_state = (
|
||||
(key_states, value_states)
|
||||
if (self.SelfAttention.is_decoder and use_cache)
|
||||
else None
|
||||
)
|
||||
outputs = (attn_output,) + (present_key_value_state,) + (position_bias,)
|
||||
|
||||
if output_attentions:
|
||||
outputs = outputs + (None,)
|
||||
hidden_states = attn_output + hidden_states
|
||||
outputs = (hidden_states,) + outputs[1:]
|
||||
|
||||
return outputs
|
||||
|
||||
|
||||
def cross_attention_forward(
|
||||
self,
|
||||
hidden_states,
|
||||
key_value_states,
|
||||
attention_mask=None,
|
||||
position_bias=None,
|
||||
layer_head_mask=None,
|
||||
past_key_value=None,
|
||||
use_cache=False,
|
||||
query_length=None,
|
||||
output_attentions=False,
|
||||
):
|
||||
|
||||
assert output_attentions is False
|
||||
assert layer_head_mask is None
|
||||
|
||||
def unshape(states):
|
||||
"""reshape"""
|
||||
return (
|
||||
states.transpose(1, 2)
|
||||
.contiguous()
|
||||
.view(batch_size, -1, self.EncDecAttention.inner_dim)
|
||||
)
|
||||
|
||||
normed_hidden_states = self.layer_norm(hidden_states)
|
||||
|
||||
# cross attn need key_value_states
|
||||
assert key_value_states is not None
|
||||
batch_size, seq_length = hidden_states.shape[:2]
|
||||
real_seq_length = seq_length
|
||||
|
||||
if past_key_value is not None:
|
||||
if len(past_key_value) != 2:
|
||||
raise ValueError(
|
||||
f"past_key_value should have 2 past states: keys and values. Got { len(past_key_value)} past states"
|
||||
)
|
||||
real_seq_length += (
|
||||
past_key_value[0].shape[2] if query_length is None else query_length
|
||||
)
|
||||
|
||||
key_length = (
|
||||
real_seq_length if key_value_states is None else key_value_states.shape[1]
|
||||
)
|
||||
head_num, head_dim = (
|
||||
self.EncDecAttention.n_heads,
|
||||
self.EncDecAttention.key_value_proj_dim,
|
||||
)
|
||||
|
||||
query_states = (
|
||||
self.EncDecAttention.q(normed_hidden_states)
|
||||
.view(batch_size, seq_length, head_num, head_dim)
|
||||
.transpose(1, 2)
|
||||
.contiguous()
|
||||
)
|
||||
|
||||
if past_key_value is not None:
|
||||
if past_key_value[0].shape[2] != key_value_states.shape[1]:
|
||||
# checking that the `sequence_length` of the `past_key_value` is the same as
|
||||
# the provided `key_value_states` to support prefix tuning
|
||||
# cross-attn
|
||||
# (batch_size, n_heads, seq_length, dim_per_head)
|
||||
key_states = (
|
||||
self.EncDecAttention.k(key_value_states)
|
||||
.view(batch_size, key_length, head_num, head_dim)
|
||||
.transpose(1, 2)
|
||||
)
|
||||
value_states = (
|
||||
self.EncDecAttention.v(key_value_states)
|
||||
.view(batch_size, key_length, head_num, head_dim)
|
||||
.transpose(1, 2)
|
||||
)
|
||||
else:
|
||||
# cross-attn
|
||||
key_states = past_key_value[0]
|
||||
value_states = past_key_value[1]
|
||||
else:
|
||||
key_states = (
|
||||
self.EncDecAttention.k(key_value_states)
|
||||
.view(batch_size, key_length, head_num, head_dim)
|
||||
.transpose(1, 2)
|
||||
)
|
||||
value_states = (
|
||||
self.EncDecAttention.v(key_value_states)
|
||||
.view(batch_size, key_length, head_num, head_dim)
|
||||
.transpose(1, 2)
|
||||
)
|
||||
|
||||
if not query_states.is_contiguous():
|
||||
query_states = query_states.contiguous()
|
||||
|
||||
# TODO: fix this bug
|
||||
if not value_states.is_contiguous():
|
||||
new_value_states = query_states.new_empty(value_states.shape)
|
||||
new_value_states.copy_(value_states)
|
||||
value_states = new_value_states
|
||||
if not key_states.is_contiguous():
|
||||
numel = torch.numel(key_states)
|
||||
new_key_states = query_states.new_empty([numel * 2])[:numel].view(
|
||||
*list(key_states.shape)
|
||||
)
|
||||
new_key_states.copy_(key_states)
|
||||
key_states = new_key_states
|
||||
|
||||
if position_bias is None:
|
||||
if not self.EncDecAttention.has_relative_attention_bias:
|
||||
position_bias = torch.zeros(
|
||||
(1, self.EncDecAttention.n_heads, real_seq_length, key_length),
|
||||
device=query_states.device,
|
||||
dtype=query_states.dtype,
|
||||
)
|
||||
else:
|
||||
position_bias = self.EncDecAttention.compute_bias(
|
||||
real_seq_length, key_length, device=query_states.device
|
||||
)
|
||||
|
||||
# if key and values are already calculated
|
||||
# we want only the last query position bias
|
||||
if past_key_value is not None:
|
||||
position_bias = position_bias[:, :, -hidden_states.size(1) :, :]
|
||||
|
||||
if attention_mask is not None:
|
||||
# (batch_size, n_heads, seq_length, key_length)
|
||||
position_bias = position_bias + attention_mask
|
||||
|
||||
if self.EncDecAttention.pruned_heads:
|
||||
mask = torch.ones(position_bias.shape[1])
|
||||
mask[list(self.pruned_heads)] = 0
|
||||
position_bias_masked = position_bias[:, mask.bool()]
|
||||
else:
|
||||
position_bias_masked = position_bias
|
||||
|
||||
attn_output = ixformer.functions.ixinfer_flash_attn_pad(
|
||||
query_states,
|
||||
key_states.contiguous(),
|
||||
value_states.contiguous(),
|
||||
mask=position_bias_masked.float().contiguous(),
|
||||
atten_scale=1,
|
||||
)
|
||||
attn_output = unshape(attn_output)
|
||||
|
||||
attn_output = self.EncDecAttention.o(attn_output)
|
||||
|
||||
present_key_value_state = (
|
||||
(key_states, value_states)
|
||||
if (self.EncDecAttention.is_decoder and use_cache)
|
||||
else None
|
||||
)
|
||||
outputs = (attn_output,) + (present_key_value_state,) + (position_bias,)
|
||||
|
||||
if output_attentions:
|
||||
outputs = outputs + (None,)
|
||||
hidden_states = attn_output + hidden_states
|
||||
outputs = (hidden_states,) + outputs[1:]
|
||||
|
||||
return outputs
|
||||
|
||||
|
||||
def dense_gated_act_dense_forward(self, hidden_states):
|
||||
if isinstance(self.act, NewGELUActivation):
|
||||
if not hasattr(self, "wi"):
|
||||
self.wi = torch.cat([self.wi_1.weight, self.wi_0.weight], dim=0)
|
||||
del self.wi_1
|
||||
del self.wi_0
|
||||
hidden_states = ixformer.functions.linear(hidden_states, self.wi, None)
|
||||
hidden_states = ixformer.functions.gelu_and_mul(hidden_states)
|
||||
hidden_states = ixformer.functions.linear(hidden_states, self.wo.weight, None)
|
||||
else:
|
||||
hidden_gelu = self.act(self.wi_0(hidden_states))
|
||||
hidden_linear = self.wi_1(hidden_states)
|
||||
hidden_states = hidden_gelu * hidden_linear
|
||||
|
||||
hidden_states = self.dropout(hidden_states)
|
||||
|
||||
# To make 8bit quantization work for google/flan-t5-xxl, self.wo is kept in float32.
|
||||
# See https://github.com/huggingface/transformers/issues/20287
|
||||
# we also make sure the weights are not in `int8` in case users will force `_keep_in_fp32_modules` to be `None``
|
||||
if (
|
||||
isinstance(self.wo.weight, torch.Tensor)
|
||||
and hidden_states.dtype != self.wo.weight.dtype
|
||||
and self.wo.weight.dtype != torch.int8
|
||||
):
|
||||
hidden_states = hidden_states.to(self.wo.weight.dtype)
|
||||
|
||||
hidden_states = self.wo(hidden_states)
|
||||
return hidden_states
|
||||
2644
ixformer_sdk/contrib/transformers/models/t5/modeling_t5.py
Normal file
2644
ixformer_sdk/contrib/transformers/models/t5/modeling_t5.py
Normal file
File diff suppressed because it is too large
Load Diff
0
ixformer_sdk/contrib/vllm/__init__.py
Normal file
0
ixformer_sdk/contrib/vllm/__init__.py
Normal file
30
ixformer_sdk/contrib/vllm/layers/__init__.py
Normal file
30
ixformer_sdk/contrib/vllm/layers/__init__.py
Normal file
@@ -0,0 +1,30 @@
|
||||
from .llama import forward_smoothquant
|
||||
from .mixtral import mixtral_decoder_layer_forward
|
||||
|
||||
SUPPORT_REPLACE_METHOD = {
|
||||
"llama": forward_smoothquant,
|
||||
}
|
||||
|
||||
SUPPORT_REPLACE_LAYER = {
|
||||
"llama": None,
|
||||
}
|
||||
|
||||
|
||||
def get_replace_forward(name: str):
|
||||
try:
|
||||
method = SUPPORT_REPLACE_METHOD[name]
|
||||
except:
|
||||
raise ValueError(
|
||||
f"Only support replace names: {SUPPORT_REPLACE_METHOD.keys()}, but got {name}"
|
||||
)
|
||||
return method
|
||||
|
||||
|
||||
def get_replace_layer(name: str):
|
||||
try:
|
||||
layer = SUPPORT_REPLACE_LAYER[name]
|
||||
except:
|
||||
raise ValueError(
|
||||
f"Only support replace names: {SUPPORT_REPLACE_LAYER.keys()}, but got {name}"
|
||||
)
|
||||
return layer
|
||||
100
ixformer_sdk/contrib/vllm/layers/llama.py
Normal file
100
ixformer_sdk/contrib/vllm/layers/llama.py
Normal file
@@ -0,0 +1,100 @@
|
||||
from typing import Any, Dict, Iterable, List, Optional, Tuple
|
||||
|
||||
import torch
|
||||
from transformers import LlamaConfig
|
||||
|
||||
from vllm.attention import AttentionMetadata
|
||||
from vllm.config import CacheConfig
|
||||
from vllm.distributed import tensor_model_parallel_all_reduce
|
||||
from vllm.model_executor.layers.quantization.base_config import QuantizationConfig
|
||||
from vllm.model_executor.models.llama import LlamaDecoderLayer as VllmLlamaDecoderLayer
|
||||
|
||||
import vllm._custom_ops as ops
|
||||
# from ..overlap_comm import DecoderLayerOverlapComm, get_overlap_linear_method
|
||||
|
||||
|
||||
# This method is needed for support smoothquant no overlap forward
|
||||
def forward_smoothquant(
|
||||
input_ids: Optional[torch.Tensor],
|
||||
positions: torch.Tensor,
|
||||
kv_caches: List[torch.Tensor],
|
||||
attn_metadata: AttentionMetadata,
|
||||
inputs_embeds: Optional[torch.Tensor] = None,
|
||||
self = None, # will be set by partial
|
||||
) -> torch.Tensor:
|
||||
dtype = self.dtype
|
||||
|
||||
def forward_smoothquant_mlp(self,x,scales):
|
||||
# gate_up_proj
|
||||
# Int8 Matrix multiply.
|
||||
bias = self.gate_up_proj.bias if not self.gate_up_proj.skip_bias_add else None
|
||||
gate_up = ops.w8a8(x, self.gate_up_proj.weight, scales, self.gate_up_proj.weight_scales, dtype)
|
||||
if bias:
|
||||
gate_up += bias
|
||||
|
||||
# act_fun
|
||||
x, scales = ops.silu_and_mul_smoothquant(gate_up, self.down_proj.smooth_scales)
|
||||
|
||||
# down_proj
|
||||
output_parallel = ops.w8a8(x, self.down_proj.weight, scales, self.down_proj.weight_scales, dtype)
|
||||
if self.down_proj.reduce_results and self.down_proj.tp_size > 1:
|
||||
output = tensor_model_parallel_all_reduce(output_parallel)
|
||||
else:
|
||||
output = output_parallel
|
||||
|
||||
if not self.down_proj.skip_bias_add:
|
||||
output = output + self.down_proj.bias if self.down_proj.bias is not None else output
|
||||
|
||||
return output
|
||||
|
||||
def forward_smoothquant_attn(
|
||||
self,
|
||||
positions: torch.Tensor,
|
||||
hidden_states: torch.Tensor,
|
||||
kv_cache: torch.Tensor,
|
||||
attn_metadata: AttentionMetadata,
|
||||
scales: torch.Tensor,
|
||||
) -> torch.Tensor:
|
||||
# qkv proj
|
||||
bias = self.qkv_proj.bias if not self.qkv_proj.skip_bias_add else None
|
||||
|
||||
qkv = ops.w8a8(hidden_states, self.qkv_proj.weight, scales, self.qkv_proj.weight_scales, dtype)
|
||||
if bias:
|
||||
qkv += bias
|
||||
|
||||
q, k, v = qkv.split([self.q_size, self.kv_size, self.kv_size], dim=-1)
|
||||
q, k = self.rotary_emb(positions, q, k)
|
||||
attn_output = self.attn(q, k, v, kv_cache, attn_metadata)
|
||||
output, _ = self.o_proj(attn_output) # TODO
|
||||
return output
|
||||
|
||||
if inputs_embeds is not None:
|
||||
hidden_states = inputs_embeds
|
||||
else:
|
||||
hidden_states = self.get_input_embeddings(input_ids)
|
||||
residual = None
|
||||
for i in range(len(self.layers)):
|
||||
layer = self.layers[i]
|
||||
if residual is None:
|
||||
residual = hidden_states
|
||||
hidden_states, scales = ops.rms_norm_smoothquant(hidden_states,layer.input_layernorm.weight,layer.input_layernorm.variance_epsilon, layer.self_attn.qkv_proj.smooth_scales)
|
||||
else:
|
||||
hidden_states, residual, scales = ops.fused_add_rms_norm_smoothquant(hidden_states, residual, layer.input_layernorm.weight, layer.input_layernorm.variance_epsilon, layer.self_attn.qkv_proj.smooth_scales)
|
||||
|
||||
hidden_states = forward_smoothquant_attn(
|
||||
layer.self_attn,
|
||||
positions=positions,
|
||||
hidden_states=hidden_states,
|
||||
kv_cache=kv_caches[i],
|
||||
attn_metadata=attn_metadata,
|
||||
scales=scales,
|
||||
)
|
||||
|
||||
# Fully Connected
|
||||
hidden_states, residual, scales = ops.fused_add_rms_norm_smoothquant(hidden_states, residual, layer.post_attention_layernorm.weight, layer.post_attention_layernorm.variance_epsilon, layer.mlp.gate_up_proj.smooth_scales)
|
||||
|
||||
hidden_states = forward_smoothquant_mlp(layer.mlp, hidden_states, scales)
|
||||
|
||||
hidden_states, _ = self.norm(hidden_states, residual)
|
||||
return hidden_states
|
||||
|
||||
331
ixformer_sdk/contrib/vllm/layers/mixtral.py
Normal file
331
ixformer_sdk/contrib/vllm/layers/mixtral.py
Normal file
@@ -0,0 +1,331 @@
|
||||
import functools
|
||||
from typing import Dict, Optional, Tuple
|
||||
|
||||
import ixformer.inference.functions as ixf
|
||||
import torch
|
||||
|
||||
|
||||
def mixtral_decoder_layer_forward(
|
||||
self,
|
||||
positions: torch.Tensor,
|
||||
hidden_states: torch.Tensor,
|
||||
kv_cache: torch.Tensor,
|
||||
attn_metadata,
|
||||
residual: Optional[torch.Tensor],
|
||||
) -> torch.Tensor:
|
||||
if self.use_int_w8a8:
|
||||
return w8a8_forward(
|
||||
self, positions, hidden_states, kv_cache, attn_metadata, residual
|
||||
)
|
||||
else:
|
||||
return original_forward(
|
||||
self, positions, hidden_states, kv_cache, attn_metadata, residual
|
||||
)
|
||||
|
||||
|
||||
def original_forward(
|
||||
self,
|
||||
positions: torch.Tensor,
|
||||
hidden_states: torch.Tensor,
|
||||
kv_cache: torch.Tensor,
|
||||
attn_metadata,
|
||||
residual: Optional[torch.Tensor],
|
||||
) -> torch.Tensor:
|
||||
# Self Attention
|
||||
if residual is None:
|
||||
residual = hidden_states
|
||||
hidden_states = self.input_layernorm(hidden_states)
|
||||
else:
|
||||
hidden_states, residual = self.input_layernorm(hidden_states, residual)
|
||||
hidden_states = self.self_attn(
|
||||
positions=positions,
|
||||
hidden_states=hidden_states,
|
||||
kv_cache=kv_cache,
|
||||
attn_metadata=attn_metadata,
|
||||
)
|
||||
|
||||
# Fully Connected
|
||||
hidden_states, residual = self.post_attention_layernorm(hidden_states, residual)
|
||||
hidden_states = self.block_sparse_moe(hidden_states)
|
||||
return hidden_states, residual
|
||||
|
||||
|
||||
def dynamic_scaled_int8_quant(x):
|
||||
m, k = x.shape
|
||||
i8_x = x.new_empty([m, k], dtype=torch.int8, device="cuda")
|
||||
i8_scales = torch.empty([m], dtype=torch.float32, device="cuda")
|
||||
ixf.dynamic_scaled_int8_quant(i8_x, x, i8_scales)
|
||||
return i8_x, i8_scales
|
||||
|
||||
|
||||
def dynamic_w8a8(x, i8_weight, weight_scale):
|
||||
i8_x, i8_scale = dynamic_scaled_int8_quant(x)
|
||||
m, k = x.shape
|
||||
k, n = i8_weight.shape
|
||||
output = x.new_empty([m, n], dtype=x.dtype, device="cuda")
|
||||
ixf.w8a8(
|
||||
i8_x,
|
||||
i8_weight.transpose(0, 1),
|
||||
i8_scale,
|
||||
weight_scale,
|
||||
output=output,
|
||||
out_dtype=x.dtype,
|
||||
)
|
||||
return output
|
||||
|
||||
|
||||
def fused_rms_norm_quant_linear(
|
||||
self,
|
||||
hidden_states,
|
||||
ln_weight,
|
||||
eps,
|
||||
linear_weight,
|
||||
linear_weight_scale,
|
||||
residual=None,
|
||||
):
|
||||
# lower rouge
|
||||
# if residual is None:
|
||||
# residual = hidden_states
|
||||
# i8_hidden_states, _, i8_scales = ixf.residual_rms_norm_dynamic_int8(
|
||||
# input=hidden_states,
|
||||
# weight=ln_weight,
|
||||
# residual=None,
|
||||
# eps=eps,
|
||||
# )
|
||||
# else:
|
||||
# i8_hidden_states, residual, i8_scales = ixf.residual_rms_norm_dynamic_int8(
|
||||
# input=hidden_states,
|
||||
# weight=ln_weight,
|
||||
# residual=residual,
|
||||
# eps=eps,
|
||||
# )
|
||||
|
||||
if residual is None:
|
||||
residual = hidden_states
|
||||
hidden_states = self.input_layernorm(hidden_states)
|
||||
else:
|
||||
hidden_states, residual = self.input_layernorm(hidden_states, residual)
|
||||
i8_hidden_states, i8_scales = dynamic_scaled_int8_quant(hidden_states)
|
||||
|
||||
qkv = hidden_states.new_empty(hidden_states.shape[0], linear_weight.shape[1])
|
||||
ixf.w8a8(
|
||||
i8_hidden_states,
|
||||
linear_weight.transpose(0, 1),
|
||||
i8_scales,
|
||||
linear_weight_scale,
|
||||
output=qkv,
|
||||
out_dtype=hidden_states.dtype,
|
||||
)
|
||||
return qkv, residual
|
||||
|
||||
|
||||
def attention(qkv, positions, kv_cache, attn_metadata, self_attn):
|
||||
q, k, v = qkv.split(
|
||||
[self_attn.q_size, self_attn.kv_size, self_attn.kv_size], dim=-1
|
||||
)
|
||||
q, k = self_attn.rotary_emb(positions, q, k)
|
||||
attn_output = self_attn.attn(q, k, v, kv_cache, attn_metadata)
|
||||
return attn_output
|
||||
|
||||
|
||||
def fused_rms_norm_attention(
|
||||
self,
|
||||
hidden_states,
|
||||
ln_weight,
|
||||
eps,
|
||||
positions,
|
||||
kv_cache,
|
||||
attn_metadata,
|
||||
self_attn,
|
||||
residual=None,
|
||||
):
|
||||
hidden_states, residual = fused_rms_norm_quant_linear(
|
||||
self,
|
||||
hidden_states,
|
||||
ln_weight,
|
||||
eps,
|
||||
self_attn.qkv_proj.weight,
|
||||
self_attn.qkv_proj.weight_scale,
|
||||
residual,
|
||||
)
|
||||
hidden_states = attention(
|
||||
hidden_states, positions, kv_cache, attn_metadata, self_attn
|
||||
)
|
||||
|
||||
hidden_states = dynamic_w8a8(
|
||||
hidden_states, self_attn.o_proj.weight, self_attn.o_proj.weight_scale
|
||||
)
|
||||
# hidden_states,_ = self_attn.o_proj(hidden_states) # quant+linear+allreduce
|
||||
return hidden_states, residual
|
||||
|
||||
|
||||
def w8a8_forward(
|
||||
self,
|
||||
positions: torch.Tensor,
|
||||
hidden_states: torch.Tensor,
|
||||
kv_cache: torch.Tensor,
|
||||
attn_metadata,
|
||||
residual: Optional[torch.Tensor],
|
||||
) -> torch.Tensor:
|
||||
|
||||
# qkv,_ = self.self_attn.qkv_proj(hidden_states)
|
||||
hidden_states, residual = fused_rms_norm_attention(
|
||||
self,
|
||||
hidden_states,
|
||||
self.input_layernorm.weight,
|
||||
self.input_layernorm.variance_epsilon,
|
||||
positions,
|
||||
kv_cache,
|
||||
attn_metadata,
|
||||
self.self_attn,
|
||||
residual,
|
||||
)
|
||||
|
||||
# allreduce
|
||||
tp_size = self.block_sparse_moe.experts.tp_size
|
||||
if tp_size > 1:
|
||||
from vllm.distributed import tensor_model_parallel_all_reduce
|
||||
|
||||
hidden_states = tensor_model_parallel_all_reduce(hidden_states)
|
||||
|
||||
# rms norm
|
||||
hidden_states, residual = self.post_attention_layernorm(hidden_states, residual)
|
||||
# moe
|
||||
hidden_states = fused_moe(
|
||||
hidden_states,
|
||||
self.block_sparse_moe.gate.weight,
|
||||
top_k=self.block_sparse_moe.experts.top_k,
|
||||
w1=self.block_sparse_moe.experts.w13_weight,
|
||||
w2=self.block_sparse_moe.experts.w2_weight,
|
||||
w1_scale=self.block_sparse_moe.experts.w13_weight_scale,
|
||||
w2_scale=self.block_sparse_moe.experts.w2_weight_scale,
|
||||
)
|
||||
|
||||
# allreduce
|
||||
if tp_size > 1:
|
||||
from vllm.distributed import tensor_model_parallel_all_reduce
|
||||
|
||||
hidden_states = tensor_model_parallel_all_reduce(hidden_states)
|
||||
|
||||
return hidden_states, residual
|
||||
|
||||
|
||||
def fused_experts(hidden_states, router_logits, top_k, w1, w2, w1_scale, w2_scale):
|
||||
|
||||
"""
|
||||
Args:
|
||||
hidden_states: (num_tokens, k) dtype
|
||||
router_logits: (num_tokens, num_experts) torch.float32
|
||||
top_k int
|
||||
w1: (num_experts, 2n, k) torch.int8
|
||||
w2: (num_experts, k, n) torch.int8
|
||||
w1_scale: (num_experts, 2n) torch.float32
|
||||
w2_scale: (num_experts, k) torch.float32
|
||||
Returns
|
||||
final_hidden_states: (num_tokens, k) dtype
|
||||
"""
|
||||
|
||||
# topk_weight: (num_tokens, top_k) torch.float32
|
||||
# topk_ids: (num_tokens, top_k) torch.int32
|
||||
topk_weight, topk_ids = ixf.moe_topk_softmax(
|
||||
gating_output=router_logits,
|
||||
topk=top_k,
|
||||
renormalize=True,
|
||||
)
|
||||
|
||||
dtype = hidden_states.dtype
|
||||
num_tokens, num_experts = router_logits.shape
|
||||
expand_tokens = num_tokens * top_k
|
||||
|
||||
(
|
||||
src_to_dst,
|
||||
sorted_token_ids,
|
||||
expert_sizes_gpu,
|
||||
expert_sizes_cpu,
|
||||
) = ixf.moe_compute_token_index(
|
||||
topk_ids=topk_ids,
|
||||
num_experts=num_experts,
|
||||
)
|
||||
expert_sizes_cpu = expert_sizes_gpu.cpu()
|
||||
|
||||
# expand + reorder + quant
|
||||
# i8_hidden_states: (expand_tokens, k) torch.int8
|
||||
i8_hidden_states, a_scale = ixf.moe_expand_input_dynamic_scaled_int8(
|
||||
hidden_states=hidden_states,
|
||||
dst_to_src=sorted_token_ids,
|
||||
dst_tokens=expand_tokens,
|
||||
topk=top_k,
|
||||
src_to_dst=src_to_dst,
|
||||
topk_ids=None, # use smooth quant
|
||||
smooth_scales=None, # use smooth quant
|
||||
)
|
||||
|
||||
# w8a8 group gemm 1
|
||||
# pt_output_1: (expand_tokens, 2n) dtype
|
||||
pt_output_1 = ixf.moe_w8a8_group_gemm(
|
||||
input=i8_hidden_states,
|
||||
weight=w1,
|
||||
i_scales=a_scale,
|
||||
w_scales=w1_scale,
|
||||
output_dtype=dtype,
|
||||
tokens_per_experts=expert_sizes_cpu,
|
||||
dst_to_src=None,
|
||||
format="TN",
|
||||
)
|
||||
|
||||
# act + quant
|
||||
# pt_output_2: (expand_tokens, n) torch.int8
|
||||
pt_output_2, a2_scale = ixf.activation_dynamic_scaled_int8(
|
||||
input=pt_output_1,
|
||||
bias=None, # add gemm bias
|
||||
smooth_scales=None, # use smooth quant
|
||||
dst_to_src=sorted_token_ids,
|
||||
topk_ids=None, # add gemm bias or use smooth quant
|
||||
act_type="swiglu",
|
||||
)
|
||||
|
||||
# w8a8 group gemm 2 + reorder
|
||||
# pt_output_3: (expand_tokens, k) dtype
|
||||
pt_output_3 = ixf.moe_w8a8_group_gemm(
|
||||
input=pt_output_2,
|
||||
weight=w2,
|
||||
i_scales=a2_scale,
|
||||
w_scales=w2_scale,
|
||||
output_dtype=dtype,
|
||||
tokens_per_experts=expert_sizes_cpu,
|
||||
dst_to_src=sorted_token_ids,
|
||||
format="TN",
|
||||
)
|
||||
|
||||
# mul + reduce_sum
|
||||
# final_hidden_states: (num_tokens, k)
|
||||
final_hidden_states = ixf.moe_output_reduce_sum(
|
||||
input=pt_output_3.view(num_tokens, top_k, -1),
|
||||
topk_weight=topk_weight,
|
||||
)
|
||||
|
||||
return final_hidden_states
|
||||
|
||||
|
||||
def fused_moe(hidden_states, gate_weight, top_k, w1, w2, w1_scale, w2_scale):
|
||||
orig_shape = hidden_states.shape
|
||||
hidden_size = hidden_states.shape[-1]
|
||||
|
||||
hidden_states = hidden_states.view(-1, hidden_size)
|
||||
|
||||
# router_logits: (num_tokens, n_experts)
|
||||
# gate_weight: fp16
|
||||
router_logits = ixf.linear(hidden_states, gate_weight)
|
||||
router_logits = router_logits.to(torch.float32)
|
||||
|
||||
final_hidden_states = fused_experts(
|
||||
hidden_states,
|
||||
router_logits,
|
||||
top_k,
|
||||
w1,
|
||||
w2,
|
||||
w1_scale,
|
||||
w2_scale,
|
||||
)
|
||||
|
||||
return final_hidden_states.view(orig_shape)
|
||||
14
ixformer_sdk/contrib/vllm/quantize/__init__.py
Normal file
14
ixformer_sdk/contrib/vllm/quantize/__init__.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from .smoothquant import smoothquant_prepare_quantize,smoothquant_export_quantized_weights
|
||||
from .w8a16 import w8a16_prepare_quantize,w8a16_export_quantized_weights
|
||||
|
||||
SUPPORT_METHOD = {
|
||||
"smoothquant": [smoothquant_prepare_quantize,smoothquant_export_quantized_weights],
|
||||
"w8a16": [w8a16_prepare_quantize,w8a16_export_quantized_weights],
|
||||
}
|
||||
|
||||
def get_quantize_method(method_name:str):
|
||||
try:
|
||||
method = SUPPORT_METHOD[method_name]
|
||||
except:
|
||||
raise ValueError(f"Only support quantization methods: {SUPPORT_METHOD.keys()}, but got {method_name}")
|
||||
return method
|
||||
407
ixformer_sdk/contrib/vllm/quantize/smoothquant.py
Normal file
407
ixformer_sdk/contrib/vllm/quantize/smoothquant.py
Normal file
@@ -0,0 +1,407 @@
|
||||
import os
|
||||
|
||||
import torch
|
||||
|
||||
|
||||
def smoothquant_prepare_quantize(self, quant_params={}):
|
||||
model = self.model_runner.model
|
||||
|
||||
def update_act_scales(act_scales, x):
|
||||
# 动态统计每次输入的最大值
|
||||
hidden_dim = x.shape[-1]
|
||||
x = x.view(-1, hidden_dim).abs().detach()
|
||||
# [k]
|
||||
comming_max = torch.max(x, dim=0, keepdim=True)[0].float()
|
||||
|
||||
if act_scales is None:
|
||||
act_scales = comming_max
|
||||
else:
|
||||
act_scales = torch.max(act_scales, comming_max)
|
||||
return act_scales
|
||||
|
||||
from functools import partial
|
||||
|
||||
from vllm.model_executor.layers.linear import (
|
||||
ColumnParallelLinear,
|
||||
MergedColumnParallelLinear,
|
||||
QKVParallelLinear,
|
||||
RowParallelLinear,
|
||||
)
|
||||
|
||||
def new_forward(input_, m, raw_forward):
|
||||
if not hasattr(m, "act_scales"):
|
||||
m.act_scales = None
|
||||
m.act_scales = update_act_scales(m.act_scales, input_)
|
||||
return raw_forward(input_)
|
||||
|
||||
for name, m in model.named_modules():
|
||||
if (
|
||||
isinstance(m, QKVParallelLinear)
|
||||
or isinstance(m, RowParallelLinear)
|
||||
or isinstance(m, MergedColumnParallelLinear)
|
||||
or isinstance(m, ColumnParallelLinear)
|
||||
):
|
||||
m.forward = partial(new_forward, m=m, raw_forward=m.forward)
|
||||
|
||||
def smoothquant_export_quantized_weights(self, save_path, quant_params={}):
|
||||
gb_per_file = quant_params.get("filesize_limit", None)
|
||||
smooth_alpha = quant_params.get("smooth_alpha", 0.5)
|
||||
dynamic_quant_type = quant_params.get("dynamic_quant_type", "gpu")
|
||||
assert dynamic_quant_type in ["gpu","cpu","kernel"]
|
||||
if self.rank == 0:
|
||||
print(f"set smooth_alpha={smooth_alpha}")
|
||||
print(f"use quantize weight type: {dynamic_quant_type}")
|
||||
|
||||
import ixformer._C as ops
|
||||
def per_token_quant_8bit(weight):
|
||||
# weight: [m,k]
|
||||
dtype = weight.dtype
|
||||
i8_weight = weight
|
||||
scale = i8_weight.abs().max(dim=-1, keepdim=True)[0] / 127
|
||||
i8_weight = i8_weight / scale.to(dtype)
|
||||
i8_weight = torch.clamp(torch.round(i8_weight), -128, 127).to(torch.int8)
|
||||
return i8_weight, scale.float()
|
||||
|
||||
def smooth_quant_weight_gpu_cpu(weight, act_scale, alpha=0.5, device="cpu"):
|
||||
device = torch.device("cpu") if device == "cpu" else weight.device
|
||||
ori_dtype = weight.dtype
|
||||
# [1, k]
|
||||
act_scale = act_scale.float().to(device).view(1, -1)
|
||||
weight = weight.to(device)
|
||||
# [1, k]
|
||||
weight_scale = weight.abs().max(dim=0, keepdim=True)[0].float()
|
||||
if alpha == -1:
|
||||
smooth_scales = torch.ones_like(act_scale)
|
||||
else:
|
||||
smooth_scales = act_scale.pow(alpha) / weight_scale.pow(1 - alpha).clamp(
|
||||
min=1e-5
|
||||
)
|
||||
weight = weight * smooth_scales.to(ori_dtype)
|
||||
i8_weight, weight_scales = per_token_quant_8bit(weight)
|
||||
# 为了可以使用 input * smooth_scales
|
||||
if alpha == -1:
|
||||
smooth_scales = torch.ones_like(act_scale)
|
||||
else:
|
||||
smooth_scales = weight_scale.pow(1 - alpha) / act_scale.pow(alpha).clamp(
|
||||
min=1e-5
|
||||
)
|
||||
return i8_weight, weight_scales, smooth_scales.to(ori_dtype)
|
||||
|
||||
def smooth_quant_weight_kernel(weight, act_scale, alpha=0.5):
|
||||
output = torch.zeros_like(weight,dtype=torch.int8)
|
||||
weight_scales = torch.zeros(weight.shape[:-1],dtype=torch.float, device=weight.device)
|
||||
weight_max = torch.zeros(weight.shape[-1],dtype=torch.float, device=weight.device)
|
||||
smooth_scales = torch.zeros(weight.shape[-1],dtype=weight.dtype, device=weight.device)
|
||||
ops.infer.weight_quant_smoothquant(
|
||||
weight, act_scale, alpha, output, weight_scales, smooth_scales, weight_max
|
||||
)
|
||||
return output, weight_scales.view(-1,1), smooth_scales.view(1,-1)
|
||||
|
||||
def smooth_quant_weight(weight, act_scale, alpha=0.5):
|
||||
if dynamic_quant_type == "kernel":
|
||||
return smooth_quant_weight_kernel(weight,act_scale,alpha)
|
||||
else:
|
||||
return smooth_quant_weight_gpu_cpu(weight,act_scale,alpha,dynamic_quant_type)
|
||||
|
||||
model = self.model_runner.model
|
||||
|
||||
from vllm.distributed import (
|
||||
tensor_model_parallel_all_gather,
|
||||
tensor_model_parallel_all_reduce,
|
||||
get_tensor_model_parallel_world_size
|
||||
)
|
||||
from vllm.model_executor.layers.linear import (
|
||||
ColumnParallelLinear,
|
||||
MergedColumnParallelLinear,
|
||||
QKVParallelLinear,
|
||||
RowParallelLinear,
|
||||
)
|
||||
from vllm.model_executor.layers.vocab_parallel_embedding import (
|
||||
ParallelLMHead,
|
||||
VocabParallelEmbedding,
|
||||
)
|
||||
from vllm.model_executor.models.falcon import FalconForCausalLM
|
||||
|
||||
for name, m in model.named_modules():
|
||||
if isinstance(m, VocabParallelEmbedding):
|
||||
# weight shape: [vocab_size // tp, embedding_dim]
|
||||
weight_tensor = tensor_model_parallel_all_gather(m.weight, dim=0)
|
||||
weight_tensor = weight_tensor[:m.org_vocab_size,:].contiguous()
|
||||
if self.is_driver_worker:
|
||||
m.weight = torch.nn.Parameter(weight_tensor.cpu(), requires_grad=False)
|
||||
print(f"merged: {name}, shape={m.weight.shape}")
|
||||
|
||||
elif isinstance(m, ParallelLMHead):
|
||||
# weight shape: [vocab_size // tp, embedding_dim]
|
||||
# bias shape: [vocab_size // tp]
|
||||
if m.bias is not None:
|
||||
bias = tensor_model_parallel_all_gather(m.bias, dim=0)
|
||||
bias = bias[:m.org_vocab_size].contiguous()
|
||||
if self.is_driver_worker:
|
||||
m.bias = torch.nn.Parameter(bias.cpu(), requires_grad=False)
|
||||
|
||||
weight_tensor = tensor_model_parallel_all_gather(m.weight, dim=0)
|
||||
weight_tensor = weight_tensor[:m.org_vocab_size,:].contiguous()
|
||||
if self.is_driver_worker:
|
||||
m.weight = torch.nn.Parameter(weight_tensor.cpu(), requires_grad=False)
|
||||
print(f"merged: {name}, shape={m.weight.shape}")
|
||||
|
||||
elif isinstance(m, QKVParallelLinear):
|
||||
# weight shape: [total_num_head * head_size // tp + 2 * total_num_head * head_size // tp, hidden_size]
|
||||
# bias shape: [total_num_head * head_size // tp + 2 * total_num_head * head_size // tp]
|
||||
if self.parallel_config.world_size > 1:
|
||||
total_q_hidden_size = m.total_num_heads * m.head_size
|
||||
partial_q_hidden_size = m.num_heads * m.head_size
|
||||
total_kv_hidden_size = m.total_num_kv_heads * m.head_size
|
||||
partial_kv_hidden_size = m.num_kv_heads * m.head_size
|
||||
|
||||
if m.bias is not None:
|
||||
# TODO do not support padding..
|
||||
bias_tenosr = m.bias.new_zeros(total_q_hidden_size + total_kv_hidden_size * 2, m.hidden_size)
|
||||
q_bias = bias_tenosr[:total_q_hidden_size][self.rank * partial_q_hidden_size : (self.rank + 1) * partial_q_hidden_size]
|
||||
k_bias = bias_tenosr[total_q_hidden_size:total_q_hidden_size+total_kv_hidden_size]\
|
||||
[self.rank * partial_kv_hidden_size : (self.rank + 1) * partial_kv_hidden_size]
|
||||
v_bias = bias_tenosr[total_q_hidden_size+total_kv_hidden_size:]\
|
||||
[self.rank * partial_kv_hidden_size : (self.rank + 1) * partial_kv_hidden_size]
|
||||
|
||||
q_bias[:] = m.bias[:partial_q_hidden_size]
|
||||
k_bias[:] = m.bias[partial_q_hidden_size:partial_q_hidden_size+partial_kv_hidden_size]
|
||||
v_bias[:] = m.bias[partial_q_hidden_size+partial_kv_hidden_size:]
|
||||
|
||||
bias_tensor = tensor_model_parallel_all_reduce(bias_tenosr)
|
||||
if self.is_driver_worker:
|
||||
m.bias = torch.nn.Parameter(bias_tensor.cpu(), requires_grad=False)
|
||||
|
||||
q_tensor = m.weight.new_zeros(m.total_num_heads * m.head_size, m.weight.shape[1])
|
||||
k_tensor = m.weight.new_zeros(m.total_num_kv_heads * m.head_size, m.weight.shape[1])
|
||||
v_tensor = m.weight.new_zeros(m.total_num_kv_heads * m.head_size, m.weight.shape[1])
|
||||
|
||||
q_in_weight = m.weight[:-m.num_kv_heads * m.head_size * 2]
|
||||
k_in_weight = m.weight[-m.num_kv_heads * m.head_size * 2:-m.num_kv_heads * m.head_size]
|
||||
v_in_weight = m.weight[-m.num_kv_heads * m.head_size:]
|
||||
|
||||
if getattr(m,"start_idx",None) is not None:
|
||||
start_idx = getattr(m,"start_idx")
|
||||
weight_end_idx = m.num_heads * m.head_size if not getattr(m,"is_padding") else (m.num_heads - 1) * m.head_size
|
||||
end_idx = start_idx + weight_end_idx
|
||||
else:
|
||||
start_idx = self.rank * m.num_heads * m.head_size
|
||||
weight_end_idx = m.num_heads * m.head_size
|
||||
end_idx = start_idx + weight_end_idx
|
||||
assert q_tensor[start_idx:end_idx,:].shape == q_in_weight[:weight_end_idx, :].shape
|
||||
q_tensor[start_idx:end_idx,:] = q_in_weight[:weight_end_idx, :]
|
||||
|
||||
if m.num_kv_head_replicas > 1:
|
||||
if self.rank % m.num_kv_head_replicas == 0:
|
||||
rank = self.rank // m.num_kv_head_replicas
|
||||
k_tensor[rank * m.num_kv_heads * m.head_size:(rank+1) * m.num_kv_heads * m.head_size] = k_in_weight
|
||||
v_tensor[rank * m.num_kv_heads * m.head_size:(rank+1) * m.num_kv_heads * m.head_size] = v_in_weight
|
||||
else:
|
||||
k_tensor[self.rank * m.num_kv_heads * m.head_size:(self.rank+1) * m.num_kv_heads * m.head_size] = k_in_weight
|
||||
v_tensor[self.rank * m.num_kv_heads * m.head_size:(self.rank+1) * m.num_kv_heads * m.head_size] = v_in_weight
|
||||
|
||||
q_tensor = tensor_model_parallel_all_reduce(q_tensor)
|
||||
k_tensor = tensor_model_parallel_all_reduce(k_tensor)
|
||||
v_tensor = tensor_model_parallel_all_reduce(v_tensor)
|
||||
|
||||
if isinstance(model, FalconForCausalLM):
|
||||
num_query_heads_per_kv_head = (
|
||||
m.total_num_heads // m.total_num_kv_heads
|
||||
)
|
||||
q_tensor = q_tensor.view(
|
||||
m.total_num_kv_heads,
|
||||
num_query_heads_per_kv_head,
|
||||
m.head_size,
|
||||
-1,
|
||||
)
|
||||
k_tensor = k_tensor.view(m.total_num_kv_heads, 1, m.head_size, -1)
|
||||
v_tensor = v_tensor.view(m.total_num_kv_heads, 1, m.head_size, -1)
|
||||
weight_tensor = torch.cat(
|
||||
[q_tensor, k_tensor, v_tensor], dim=1
|
||||
).view(-1, m.hidden_size)
|
||||
else:
|
||||
weight_tensor = torch.cat([q_tensor, k_tensor, v_tensor])
|
||||
assert (
|
||||
weight_tensor.shape[0]
|
||||
== total_q_hidden_size + total_kv_hidden_size * 2
|
||||
)
|
||||
assert weight_tensor.shape[1] == m.hidden_size
|
||||
|
||||
else:
|
||||
weight_tensor = m.weight
|
||||
if m.bias is not None and self.is_driver_worker:
|
||||
m.bias = torch.nn.Parameter(m.bias.cpu(), requires_grad=False)
|
||||
|
||||
if self.is_driver_worker:
|
||||
i8_weight, weight_scales, smooth_scales = smooth_quant_weight(
|
||||
weight_tensor, m.act_scales, smooth_alpha
|
||||
)
|
||||
m.weight = torch.nn.Parameter(i8_weight.cpu(), requires_grad=False)
|
||||
m.weight_scales = torch.nn.Parameter(
|
||||
weight_scales.cpu(), requires_grad=False
|
||||
)
|
||||
m.smooth_scales = torch.nn.Parameter(
|
||||
smooth_scales.cpu(), requires_grad=False
|
||||
)
|
||||
print(f"Quantized: {name}")
|
||||
|
||||
elif isinstance(m, MergedColumnParallelLinear):
|
||||
if self.parallel_config.world_size > 1:
|
||||
# weight shape: [intermediate_size // tp * 2, hidden_size]
|
||||
# bias shape: [intermediate_size // tp * 2]
|
||||
output_sizes = m.output_sizes
|
||||
output_size = sum(output_sizes)
|
||||
partial_output_sizes = [
|
||||
i // self.parallel_config.world_size for i in output_sizes
|
||||
]
|
||||
|
||||
if m.bias is not None:
|
||||
index_start = 0
|
||||
partial_index_start = 0
|
||||
bias_tenosr = m.bias.new_zeros(output_size)
|
||||
for i in range(len(output_sizes)):
|
||||
index_out = index_start + output_sizes[i]
|
||||
sub_bias_tensor = bias_tenosr[index_start:index_out]
|
||||
partial_size = partial_output_sizes[i]
|
||||
sub_bias_tensor[self.rank * partial_size:(self.rank+1) * partial_size] = m.bias[partial_index_start:partial_index_start+partial_size]
|
||||
|
||||
index_start += output_sizes[i]
|
||||
partial_index_start += partial_size
|
||||
bias_tenosr = tensor_model_parallel_all_reduce(bias_tenosr)
|
||||
if self.is_driver_worker:
|
||||
m.bias = torch.nn.Parameter(bias_tenosr, requires_grad=False)
|
||||
|
||||
weight_tensor = m.weight.new_zeros(output_size, m.input_size)
|
||||
|
||||
idx_out_start = 0
|
||||
idx_partial_satrt = 0
|
||||
for i in range(len(output_sizes)):
|
||||
idx_out_end = idx_out_start + output_sizes[i]
|
||||
sub_weight_tensor = weight_tensor[idx_out_start:idx_out_end]
|
||||
partial_size = partial_output_sizes[i]
|
||||
sub_weight_tensor[
|
||||
self.rank * partial_size : (self.rank + 1) * partial_size
|
||||
] = m.weight[idx_partial_satrt : idx_partial_satrt + partial_size]
|
||||
|
||||
idx_out_start += output_sizes[i]
|
||||
idx_partial_satrt += partial_size
|
||||
weight_tensor = tensor_model_parallel_all_reduce(weight_tensor)
|
||||
else:
|
||||
weight_tensor = m.weight
|
||||
if m.bias is not None and self.is_driver_worker:
|
||||
m.bias = torch.nn.Parameter(m.bias.cpu(), requires_grad=False)
|
||||
|
||||
if self.is_driver_worker:
|
||||
i8_weight, weight_scales, smooth_scales = smooth_quant_weight(
|
||||
weight_tensor, m.act_scales, smooth_alpha
|
||||
)
|
||||
m.weight = torch.nn.Parameter(i8_weight.cpu(), requires_grad=False)
|
||||
m.weight_scales = torch.nn.Parameter(
|
||||
weight_scales.cpu(), requires_grad=False
|
||||
)
|
||||
m.smooth_scales = torch.nn.Parameter(
|
||||
smooth_scales.cpu(), requires_grad=False
|
||||
)
|
||||
print(f"Quantized: {name}")
|
||||
|
||||
elif isinstance(m, ColumnParallelLinear):
|
||||
# weight shape: [some_dim // tp, hidden_size] // for this Linear, some_dim mostly is hidden_size * 4
|
||||
# bias shape: [some_dim // tp]
|
||||
if m.bias is not None:
|
||||
bias_tenosr = tensor_model_parallel_all_gather(m.bias, dim=0)
|
||||
if self.is_driver_worker:
|
||||
m.bias = torch.nn.Parameter(bias_tenosr.cpu(), requires_grad=False)
|
||||
|
||||
weight_tensor = tensor_model_parallel_all_gather(m.weight, dim=0)
|
||||
|
||||
if self.is_driver_worker:
|
||||
i8_weight, weight_scales, smooth_scales = smooth_quant_weight(
|
||||
weight_tensor, m.act_scales, smooth_alpha
|
||||
)
|
||||
m.weight = torch.nn.Parameter(i8_weight.cpu(), requires_grad=False)
|
||||
m.weight_scales = torch.nn.Parameter(
|
||||
weight_scales.cpu(), requires_grad=False
|
||||
)
|
||||
m.smooth_scales = torch.nn.Parameter(
|
||||
smooth_scales.cpu(), requires_grad=False
|
||||
)
|
||||
print(f"Quantized: {name}")
|
||||
|
||||
elif isinstance(m, RowParallelLinear):
|
||||
# weight shape: [hidden_size, some_dim // tp] // for this Linear, some_dim mostly is hidden_size * 4 or intermediate_size
|
||||
# bias shape: [hidden_size]
|
||||
if m.bias is not None:
|
||||
bias_tensor = tensor_model_parallel_all_gather(m.bias, dim=-1)
|
||||
if self.is_driver_worker:
|
||||
m.bias = torch.nn.Parameter(m.bias.cpu(), requires_grad=False)
|
||||
|
||||
if getattr(m,"start_idx", None) is not None:
|
||||
start_idx = getattr(m,"start_idx")
|
||||
end_idx = start_idx + (m.input_size_per_partition if not getattr(m,"is_padding") else (m.input_size_per_partition - m.padding_size))
|
||||
weight_end_idx = m.input_size_per_partition if not getattr(m,"is_padding") else (m.input_size_per_partition - m.padding_size)
|
||||
else:
|
||||
start_idx = m.input_size_per_partition * self.rank
|
||||
end_idx = start_idx + m.input_size_per_partition
|
||||
weight_end_idx = m.input_size_per_partition
|
||||
|
||||
act_scales = m.act_scales.new_zeros(m.input_size)
|
||||
assert act_scales[start_idx:end_idx].shape == m.act_scales.view(-1)[:weight_end_idx].shape
|
||||
act_scales[start_idx:end_idx] = m.act_scales.view(-1)[:weight_end_idx]
|
||||
act_scales = tensor_model_parallel_all_reduce(act_scales)
|
||||
m.act_scales = act_scales
|
||||
|
||||
weight_tensor = m.weight.new_zeros(m.weight.shape[0],m.input_size)
|
||||
assert weight_tensor[:,start_idx:end_idx].shape == m.weight[:,:weight_end_idx].shape
|
||||
weight_tensor[:,start_idx:end_idx] = m.weight[:,:weight_end_idx]
|
||||
weight_tensor = tensor_model_parallel_all_reduce(weight_tensor)
|
||||
|
||||
if self.is_driver_worker:
|
||||
i8_weight, weight_scales, smooth_scales = smooth_quant_weight(
|
||||
weight_tensor, m.act_scales, smooth_alpha
|
||||
)
|
||||
smooth_scales = smooth_scales.view(1,-1)
|
||||
m.weight = torch.nn.Parameter(i8_weight.cpu(), requires_grad=False)
|
||||
m.weight_scales = torch.nn.Parameter(
|
||||
weight_scales.cpu(), requires_grad=False
|
||||
)
|
||||
m.smooth_scales = torch.nn.Parameter(
|
||||
smooth_scales.cpu(), requires_grad=False
|
||||
)
|
||||
print(f"Quantized: {name}")
|
||||
else:
|
||||
pass
|
||||
|
||||
torch.cuda.empty_cache()
|
||||
|
||||
# save weights
|
||||
if self.is_driver_worker:
|
||||
from safetensors.torch import save_file
|
||||
|
||||
tensors = {}
|
||||
saved = False
|
||||
count = 0
|
||||
size_in_bytes = 0
|
||||
|
||||
tensors = {}
|
||||
for name, weight in model.named_parameters():
|
||||
if "act_scales" in name:
|
||||
continue
|
||||
# skip lm_head_weight if needed..
|
||||
if "lm_head" in name and model.config.tie_word_embeddings:
|
||||
continue
|
||||
tensors[name] = weight
|
||||
|
||||
saved = False
|
||||
if gb_per_file is not None and size_in_bytes >= gb_per_file * 1024 * 1024 * 1024:
|
||||
weight_path = os.path.join(save_path, "model_{}.safetensors".format(str(count).zfill(6)))
|
||||
save_file(tensors, weight_path)
|
||||
print(f"The quantified weights were successfully saved in {weight_path}.")
|
||||
tensors.clear()
|
||||
saved = True
|
||||
count += 1
|
||||
size_in_bytes = 0
|
||||
|
||||
if not saved:
|
||||
weight_path = os.path.join(save_path, "model_{}.safetensors".format(str(count).zfill(6)))
|
||||
save_file(tensors, weight_path)
|
||||
print(f"The quantified weights were successfully saved in {weight_path}.")
|
||||
233
ixformer_sdk/contrib/vllm/quantize/w8a16.py
Normal file
233
ixformer_sdk/contrib/vllm/quantize/w8a16.py
Normal file
@@ -0,0 +1,233 @@
|
||||
import os
|
||||
|
||||
import torch
|
||||
|
||||
def w8a16_prepare_quantize(self, quant_params={}):
|
||||
# We need do nothing in here
|
||||
pass
|
||||
|
||||
|
||||
def w8a16_export_quantized_weights(self, save_path, quant_params={}):
|
||||
gb_per_file = quant_params.get("filesize_limit", None)
|
||||
int8_min = -127
|
||||
|
||||
def w8a16_quantization(weight):
|
||||
# all weights should be [output,input], otherwise, we may get an wrong weight and scale...
|
||||
scale = torch.abs(weight).max(dim=-1)[0] / 127.0
|
||||
int8_weight = torch.clamp(weight / scale.view(-1,1),min=int8_min,max=127).to(torch.int8).contiguous()
|
||||
scale = scale.view(1,-1).contiguous()
|
||||
return int8_weight, scale
|
||||
|
||||
|
||||
model = self.model_runner.model
|
||||
|
||||
from vllm.distributed.communication_op import (
|
||||
tensor_model_parallel_all_gather,
|
||||
tensor_model_parallel_all_reduce,
|
||||
)
|
||||
from vllm.model_executor.layers.linear import (
|
||||
ColumnParallelLinear,
|
||||
MergedColumnParallelLinear,
|
||||
QKVParallelLinear,
|
||||
RowParallelLinear,
|
||||
)
|
||||
from vllm.model_executor.layers.vocab_parallel_embedding import (
|
||||
ParallelLMHead,
|
||||
VocabParallelEmbedding,
|
||||
)
|
||||
|
||||
for name, m in model.named_modules():
|
||||
if isinstance(m, VocabParallelEmbedding):
|
||||
# weight shape: [vocab_size // tp, embedding_dim]
|
||||
weight_tensor = tensor_model_parallel_all_gather(m.weight, dim=0)
|
||||
weight_tensor = weight_tensor[:m.org_vocab_size,:].contiguous()
|
||||
if self.is_driver_worker:
|
||||
m.weight = torch.nn.Parameter(weight_tensor.cpu(), requires_grad=False)
|
||||
print(f"merged: {name}, shape={m.weight.shape}")
|
||||
|
||||
elif isinstance(m, ParallelLMHead):
|
||||
# weight shape: [vocab_size // tp, embedding_dim]
|
||||
# bias shape: [vocab_size // tp]
|
||||
if m.bias is not None:
|
||||
bias = tensor_model_parallel_all_gather(m.bias, dim=0)
|
||||
bias = bias[:m.org_vocab_size].contiguous()
|
||||
if self.is_driver_worker:
|
||||
m.bias = torch.nn.Parameter(bias.cpu(), requires_grad=False)
|
||||
|
||||
weight_tensor = tensor_model_parallel_all_gather(m.weight, dim=0)
|
||||
weight_tensor = weight_tensor[:m.org_vocab_size,:].contiguous()
|
||||
if self.is_driver_worker:
|
||||
m.weight = torch.nn.Parameter(weight_tensor.cpu(), requires_grad=False)
|
||||
print(f"merged: {name}, shape={m.weight.shape}")
|
||||
|
||||
elif isinstance(m, QKVParallelLinear):
|
||||
# weight shape: [total_num_head * head_size // tp + 2 * total_num_head * head_size // tp, hidden_size]
|
||||
# bias shape: [total_num_head * head_size // tp + 2 * total_num_head * head_size // tp]
|
||||
if self.parallel_config.world_size > 1:
|
||||
total_q_hidden_size = m.total_num_heads * m.head_size
|
||||
partial_q_hidden_size = m.num_heads * m.head_size
|
||||
total_kv_hidden_size = m.total_num_kv_heads * m.head_size
|
||||
partial_kv_hidden_size = m.num_kv_heads * m.head_size
|
||||
|
||||
if m.bias is not None:
|
||||
bias_tenosr = m.bias.new_zeros(total_q_hidden_size + total_kv_hidden_size * 2, m.hidden_size)
|
||||
q_bias = bias_tenosr[:total_q_hidden_size][self.rank * partial_q_hidden_size : (self.rank + 1) * partial_q_hidden_size]
|
||||
k_bias = bias_tenosr[total_q_hidden_size:total_q_hidden_size+total_kv_hidden_size]\
|
||||
[self.rank * partial_kv_hidden_size : (self.rank + 1) * partial_kv_hidden_size]
|
||||
v_bias = bias_tenosr[total_q_hidden_size+total_kv_hidden_size:]\
|
||||
[self.rank * partial_kv_hidden_size : (self.rank + 1) * partial_kv_hidden_size]
|
||||
|
||||
q_bias[:] = m.bias[:partial_q_hidden_size]
|
||||
k_bias[:] = m.bias[partial_q_hidden_size:partial_q_hidden_size+partial_kv_hidden_size]
|
||||
v_bias[:] = m.bias[partial_q_hidden_size+partial_kv_hidden_size:]
|
||||
|
||||
bias_tensor = tensor_model_parallel_all_reduce(bias_tenosr)
|
||||
if self.is_driver_worker:
|
||||
m.bias = torch.nn.Parameter(bias_tensor.cpu(), requires_grad=False)
|
||||
|
||||
weight_tensor = m.weight.new_zeros(
|
||||
total_q_hidden_size + total_kv_hidden_size * 2, m.hidden_size
|
||||
)
|
||||
|
||||
q_tensor = weight_tensor[:total_q_hidden_size, :]
|
||||
q_tensor = q_tensor[self.rank * partial_q_hidden_size : (self.rank + 1) * partial_q_hidden_size]
|
||||
|
||||
k_tensor = weight_tensor[total_q_hidden_size : total_q_hidden_size + total_kv_hidden_size]
|
||||
k_tensor = k_tensor[self.rank * partial_kv_hidden_size : (self.rank + 1) * partial_kv_hidden_size]
|
||||
|
||||
v_tensor = weight_tensor[total_q_hidden_size + total_kv_hidden_size :]
|
||||
v_tensor = v_tensor[self.rank * partial_kv_hidden_size : (self.rank + 1) * partial_kv_hidden_size]
|
||||
|
||||
q_tensor[:, :] = m.weight[: partial_q_hidden_size, :]
|
||||
k_tensor[:, :] = m.weight[partial_q_hidden_size : partial_q_hidden_size + partial_kv_hidden_size, :]
|
||||
v_tensor[:, :] = m.weight[partial_q_hidden_size + partial_kv_hidden_size : , :]
|
||||
|
||||
weight_tensor = tensor_model_parallel_all_reduce(weight_tensor)
|
||||
else:
|
||||
weight_tensor = m.weight
|
||||
if m.bias is not None and self.is_driver_worker:
|
||||
m.bias = torch.nn.Parameter(m.bias.cpu(), requires_grad=False)
|
||||
|
||||
int8_weight, weight_scales = w8a16_quantization(weight_tensor)
|
||||
|
||||
if self.is_driver_worker:
|
||||
m.weight = torch.nn.Parameter(int8_weight.cpu(), requires_grad=False)
|
||||
m.scales = torch.nn.Parameter(weight_scales.cpu(), requires_grad=False)
|
||||
print(f"Quantized: {name}")
|
||||
|
||||
elif isinstance(m, MergedColumnParallelLinear):
|
||||
# weight shape: [intermediate_size // tp * 2, hidden_size]
|
||||
# bias shape: [intermediate_size // tp * 2]
|
||||
if self.parallel_config.world_size > 1:
|
||||
output_sizes = m.output_sizes
|
||||
output_size = sum(output_sizes)
|
||||
partial_output_sizes = [
|
||||
i // self.parallel_config.world_size for i in output_sizes
|
||||
]
|
||||
|
||||
if m.bias is not None:
|
||||
index_start = 0
|
||||
partial_index_start = 0
|
||||
bias_tenosr = m.bias.new_zeros(output_size)
|
||||
for i in range(len(output_sizes)):
|
||||
index_out = index_start + output_sizes[i]
|
||||
sub_bias_tensor = bias_tenosr[index_start:index_out]
|
||||
partial_size = partial_output_sizes[i]
|
||||
sub_bias_tensor[self.rank * partial_size:(self.rank+1) * partial_size] = m.bias[partial_index_start:partial_index_start+partial_size]
|
||||
|
||||
index_start += output_sizes[i]
|
||||
partial_index_start += partial_size
|
||||
bias_tenosr = tensor_model_parallel_all_reduce(bias_tenosr)
|
||||
if self.is_driver_worker:
|
||||
m.bias = torch.nn.Parameter(bias_tenosr, requires_grad=False)
|
||||
|
||||
weight_tensor = m.weight.new_zeros(output_size, m.input_size)
|
||||
|
||||
index_start = 0
|
||||
partial_index_start = 0
|
||||
for i in range(len(output_sizes)):
|
||||
index_out = index_start + output_sizes[i]
|
||||
sub_weight_tensor = weight_tensor[index_start:index_out]
|
||||
partial_size = partial_output_sizes[i]
|
||||
sub_weight_tensor[self.rank * partial_size : (self.rank + 1) * partial_size] = m.weight[partial_index_start : partial_index_start + partial_size]
|
||||
|
||||
index_start += output_sizes[i]
|
||||
partial_index_start += partial_size
|
||||
weight_tensor = tensor_model_parallel_all_reduce(weight_tensor)
|
||||
else:
|
||||
weight_tensor = m.weight
|
||||
if m.bias is not None and self.is_driver_worker:
|
||||
m.bias = torch.nn.Parameter(m.bias.cpu(), requires_grad=False)
|
||||
|
||||
int8_weight, weight_scales = w8a16_quantization(weight_tensor)
|
||||
|
||||
if self.is_driver_worker:
|
||||
m.weight = torch.nn.Parameter(int8_weight.cpu(), requires_grad=False)
|
||||
m.scales = torch.nn.Parameter(weight_scales.cpu(), requires_grad=False)
|
||||
print(f"Quantized: {name}")
|
||||
|
||||
elif isinstance(m, ColumnParallelLinear):
|
||||
# weight shape: [some_dim // tp, hidden_size] // for this Linear, some_dim mostly is hidden_size * 4
|
||||
# bias shape: [some_dim // tp]
|
||||
if m.bias is not None:
|
||||
bias_tenosr = tensor_model_parallel_all_gather(m.bias, dim=0)
|
||||
if self.is_driver_worker:
|
||||
m.bias = torch.nn.Parameter(bias_tenosr.cpu(), requires_grad=False)
|
||||
|
||||
weight_tensor = tensor_model_parallel_all_gather(m.weight, dim=0)
|
||||
|
||||
int8_weight, weight_scales = w8a16_quantization(weight_tensor)
|
||||
|
||||
if self.is_driver_worker:
|
||||
m.weight = torch.nn.Parameter(int8_weight.cpu(), requires_grad=False)
|
||||
m.scales = torch.nn.Parameter(weight_scales.cpu(), requires_grad=False)
|
||||
print(f"Quantized: {name}")
|
||||
|
||||
elif isinstance(m, RowParallelLinear):
|
||||
# weight shape: [hidden_size, some_dim // tp] // for this Linear, some_dim mostly is hidden_size * 4 or intermediate_size
|
||||
# bias shape: [hidden_size]
|
||||
if m.bias is not None:
|
||||
bias_tensor = tensor_model_parallel_all_gather(m.bias, dim=-1)
|
||||
if self.is_driver_worker:
|
||||
m.bias = torch.nn.Parameter(m.bias.cpu(), requires_grad=False)
|
||||
|
||||
weight_tensor = tensor_model_parallel_all_gather(m.weight, dim=-1)
|
||||
int8_weight, weight_scales = w8a16_quantization(weight_tensor)
|
||||
|
||||
if self.is_driver_worker:
|
||||
m.weight = torch.nn.Parameter(int8_weight.cpu(), requires_grad=False)
|
||||
m.scales = torch.nn.Parameter(weight_scales.cpu(), requires_grad=False)
|
||||
print(f"Quantized: {name}")
|
||||
else:
|
||||
pass
|
||||
|
||||
torch.cuda.empty_cache()
|
||||
|
||||
# save weights
|
||||
if self.is_driver_worker:
|
||||
from safetensors.torch import save_file
|
||||
|
||||
tensors = {}
|
||||
saved = False
|
||||
count = 0
|
||||
size_in_bytes = 0
|
||||
|
||||
for name, weight in model.named_parameters():
|
||||
if "lm_head" in name and model.config.tie_word_embeddings:
|
||||
continue
|
||||
size_in_bytes += weight.numel() * weight.element_size()
|
||||
tensors[name] = weight
|
||||
saved = False
|
||||
if gb_per_file is not None and size_in_bytes >= gb_per_file * 1024 * 1024 * 1024:
|
||||
weight_path = os.path.join(save_path, "model_{}.safetensors".format(str(count).zfill(6)))
|
||||
save_file(tensors, weight_path)
|
||||
print(f"The quantified weights were successfully saved in {weight_path}.")
|
||||
tensors.clear()
|
||||
saved = True
|
||||
count += 1
|
||||
size_in_bytes = 0
|
||||
|
||||
if not saved:
|
||||
weight_path = os.path.join(save_path, "model_{}.safetensors".format(str(count).zfill(6)))
|
||||
save_file(tensors, weight_path)
|
||||
print(f"The quantified weights were successfully saved in {weight_path}.")
|
||||
3
ixformer_sdk/contrib/vllm_flash_attn/__init__.py
Normal file
3
ixformer_sdk/contrib/vllm_flash_attn/__init__.py
Normal file
@@ -0,0 +1,3 @@
|
||||
__version__ = "2.6.1"
|
||||
|
||||
from .flash_attn_interface import *
|
||||
1018
ixformer_sdk/contrib/vllm_flash_attn/flash_attn_interface.py
Normal file
1018
ixformer_sdk/contrib/vllm_flash_attn/flash_attn_interface.py
Normal file
File diff suppressed because it is too large
Load Diff
0
ixformer_sdk/core/__init__.py
Normal file
0
ixformer_sdk/core/__init__.py
Normal file
184
ixformer_sdk/core/config.py
Normal file
184
ixformer_sdk/core/config.py
Normal file
@@ -0,0 +1,184 @@
|
||||
import os
|
||||
from typing import Callable, Optional
|
||||
|
||||
# =========================================================
|
||||
# Utils
|
||||
# =========================================================
|
||||
|
||||
|
||||
def number_type(scalar_type):
|
||||
def wrap(val: Optional[str]):
|
||||
if val is None:
|
||||
return None
|
||||
|
||||
return scalar_type(val)
|
||||
|
||||
return wrap
|
||||
|
||||
|
||||
def bool_type(val: Optional[str]):
|
||||
if val is None:
|
||||
return False
|
||||
|
||||
if isinstance(val, str):
|
||||
return val.lower() in ["1", "t", "true"]
|
||||
|
||||
if isinstance(val, int):
|
||||
return val != 0
|
||||
|
||||
raise RuntimeError(f"Invalid bool type, got {type(val), val}")
|
||||
|
||||
|
||||
def list_type(scalar_type=str):
|
||||
def wrap(val: Optional[str]):
|
||||
if val is None:
|
||||
return []
|
||||
|
||||
if not isinstance(val, str):
|
||||
raise RuntimeError(
|
||||
f"list_type: Got invalid type, expect str, but got {val}."
|
||||
)
|
||||
|
||||
return [scalar_type(v) for v in val.split(",")]
|
||||
|
||||
return wrap
|
||||
|
||||
|
||||
def Field(
|
||||
name: str,
|
||||
static: bool = True,
|
||||
type: Callable = str,
|
||||
choices: Optional[list] = None,
|
||||
help: Optional[str] = None,
|
||||
**kwargs,
|
||||
):
|
||||
"""
|
||||
Define environment variable field
|
||||
|
||||
Example:
|
||||
Static mode:
|
||||
# define
|
||||
ENABLE_XX = Field("ENABLE_XX", type=bool, help="ENABLE_XX")
|
||||
|
||||
# use
|
||||
config.ENABLE_XX
|
||||
|
||||
Dynamic mode:
|
||||
# Please use lowercase naming to differentiate it with static mode.
|
||||
|
||||
# define
|
||||
enable_cc = Field("ENABLE_CC", type=bool, static=False, help="enable_cc")
|
||||
|
||||
# use
|
||||
config.enable_cc()
|
||||
|
||||
Set default value:
|
||||
# define
|
||||
ENABLE_TT = Field("ENABLE_TT", type=bool, default=False, help="ENABLE_TT")
|
||||
|
||||
# use
|
||||
config.ENABLE_TT
|
||||
|
||||
Use list:
|
||||
# define
|
||||
CUDA_VISIBLE_DEVICES = Field("CUDA_VISIBLE_DEVICES", type=list_type(int), help="CUDA_VISIBLE_DEVICES")
|
||||
|
||||
# use
|
||||
# the CUDA_VISIBLE_DEVICES is parsed to list, and it's value is int type.
|
||||
for device_id in CUDA_VISIBLE_DEVICES:
|
||||
...
|
||||
|
||||
"""
|
||||
|
||||
if type == bool:
|
||||
type = bool_type
|
||||
|
||||
elif type in [list, tuple]:
|
||||
type = list_type(scalar_type=str)
|
||||
|
||||
elif type in [int, float]:
|
||||
type = number_type(type)
|
||||
|
||||
if static:
|
||||
env_val = type(os.environ.get(name, **kwargs))
|
||||
if choices is not None and env_val is not None and env_val not in choices:
|
||||
raise RuntimeError(
|
||||
f"Got invalid value, expect {choices}, but got {env_val}."
|
||||
)
|
||||
return env_val
|
||||
|
||||
def _get():
|
||||
env_val = type(os.environ.get(name, **kwargs))
|
||||
if choices is not None and env_val is not None and env_val not in choices:
|
||||
raise RuntimeError(
|
||||
f"Got invalid value, expect {choices}, but got {env_val}."
|
||||
)
|
||||
return env_val
|
||||
|
||||
return _get
|
||||
|
||||
|
||||
# =========================================================
|
||||
# Functions Config
|
||||
# =========================================================
|
||||
|
||||
IXFORMER_GEMV_THRESHOLD = Field(
|
||||
"IXFORMER_GEMV_THRESHOLD",
|
||||
type=int,
|
||||
default=1,
|
||||
help="Set the threshold for using gemv.",
|
||||
)
|
||||
|
||||
|
||||
# =========================================================
|
||||
# Distributed Config
|
||||
# =========================================================
|
||||
|
||||
IXFORMER_COMM_SHM_SIZE = Field(
|
||||
"IXFORMER_COMM_SHM_SIZE",
|
||||
type=int,
|
||||
default=None,
|
||||
help="set shared memory size of ipc comm.",
|
||||
)
|
||||
|
||||
IXFORMER_ENABLE_OVERLAP_COMM = Field(
|
||||
"IXFORMER_ENABLE_OVERLAP_COMM",
|
||||
type=bool,
|
||||
default=False,
|
||||
help="enable overlap communcation and compute.",
|
||||
)
|
||||
|
||||
IXFORMER_OVERLAP_GEMM_METHOD = Field(
|
||||
"IXFORMER_OVERLAP_GEMM_METHOD",
|
||||
type=int,
|
||||
default=None,
|
||||
choices=[0, 1],
|
||||
help="set gemm backend, 0: ixinfer, 1: cublas.",
|
||||
)
|
||||
|
||||
IXFORMER_OVERLAP_CHUNKS = Field(
|
||||
"IXFORMER_OVERLAP_CHUNKS", type=int, default=2, help="set split chunks."
|
||||
)
|
||||
|
||||
IXFORMER_OVERLAP_SPLIT_RATIO = Field(
|
||||
"IXFORMER_OVERLAP_SPLIT_RATIO",
|
||||
type=float,
|
||||
default=None,
|
||||
help="set split chunks ratio.",
|
||||
)
|
||||
|
||||
IXFORMER_PAGED_ATTENTION_ALGO = Field(
|
||||
"IXFORMER_PAGED_ATTENTION_ALGO",
|
||||
type=str,
|
||||
default="ixinfer",
|
||||
choices=["ixinfer", "ixformer"],
|
||||
help="set paged attention algo.",
|
||||
)
|
||||
|
||||
IXFORMER_UNPAD_ATTENTION_ALGO = Field(
|
||||
"IXFORMER_UNPAD_ATTENTION_ALGO",
|
||||
type=str,
|
||||
default="ixinfer",
|
||||
choices=["ixinfer", "ixinfer-ex"],
|
||||
help="set enpad attention algo.",
|
||||
)
|
||||
20
ixformer_sdk/core/dispatcher.py
Normal file
20
ixformer_sdk/core/dispatcher.py
Normal file
@@ -0,0 +1,20 @@
|
||||
class Dispatcher(object):
|
||||
"""
|
||||
create object by dispatcher to reuse object.
|
||||
"""
|
||||
|
||||
_dispatcher = dict()
|
||||
|
||||
@classmethod
|
||||
def dispatcher(cls, *args, **kwargs):
|
||||
key = cls.dispatcher_key(*args, **kwargs)
|
||||
obj = cls._dispatcher.get(key, None)
|
||||
if obj is None:
|
||||
obj = cls(*args, **kwargs)
|
||||
cls._dispatcher[key] = obj
|
||||
|
||||
return obj
|
||||
|
||||
@classmethod
|
||||
def dispatcher_key(cls, *args, **kwargs):
|
||||
raise NotImplementedError()
|
||||
54
ixformer_sdk/core/multi_level_cache.py
Normal file
54
ixformer_sdk/core/multi_level_cache.py
Normal file
@@ -0,0 +1,54 @@
|
||||
class MultiLevelCache(object):
|
||||
def __init__(self):
|
||||
self._l1_key = None
|
||||
self._l1_value = None
|
||||
|
||||
self._l2_size = 3
|
||||
self._l2 = [(None, None) for _ in range(self._l2_size)]
|
||||
self._l2_ptr = 0
|
||||
|
||||
self._l3 = dict()
|
||||
|
||||
def set(self, key, value):
|
||||
self._l1_key = key
|
||||
self._l1_value = value
|
||||
|
||||
self._l2[self._l2_ptr] = (key, value)
|
||||
self._l2_ptr = (self._l2_ptr + 1) % 3 # l2_size: 3
|
||||
|
||||
self._l3[key] = value
|
||||
|
||||
def get(self, key, *args):
|
||||
if key == self._l1_key:
|
||||
return self._l1_value
|
||||
|
||||
l2 = self._l2
|
||||
if key == l2[0][0]:
|
||||
return l2[0][1]
|
||||
|
||||
if key == l2[1][0]:
|
||||
return l2[1][1]
|
||||
|
||||
if key == l2[2][0]:
|
||||
return l2[2][1]
|
||||
|
||||
return self._l3.get(key, *args)
|
||||
|
||||
def containe(self, key):
|
||||
return key in self._l3
|
||||
|
||||
def __getitem__(self, item):
|
||||
return self.get(item)
|
||||
|
||||
def __setitem__(self, key, value):
|
||||
self.set(key, value)
|
||||
|
||||
def __contains__(self, item):
|
||||
if item == self._l1_key:
|
||||
return True
|
||||
|
||||
l2 = self._l2
|
||||
if item == l2[0][0] or item == l2[1][0] or item == l2[2][0]:
|
||||
return True
|
||||
|
||||
return item in self._l3
|
||||
237
ixformer_sdk/core/operator_autotuning.py
Normal file
237
ixformer_sdk/core/operator_autotuning.py
Normal file
@@ -0,0 +1,237 @@
|
||||
import abc
|
||||
import bisect
|
||||
import functools
|
||||
import itertools
|
||||
import random
|
||||
from typing import Any, Callable, Dict, Iterable, List, Optional, Tuple, Union
|
||||
|
||||
import torch
|
||||
import torch.distributed as dist
|
||||
|
||||
import ixformer.distributed as ixfd
|
||||
from ixformer.utils.benchmark.cuda_benchmark import Functor, cuda_benchmark
|
||||
|
||||
|
||||
def sync_ranks_metric(value, group=None):
|
||||
if not isinstance(value, (torch.Tensor, int, float)):
|
||||
raise RuntimeError(
|
||||
f"Invalid metric value, expect `Tensor`, `int`, or `float` type, but got {value}."
|
||||
)
|
||||
|
||||
if torch.is_tensor(value):
|
||||
value = value.to("cuda")
|
||||
else:
|
||||
value = torch.tensor([value], dtype=torch.float, device="cuda")
|
||||
|
||||
dist.broadcast(value, src=0, group=group)
|
||||
return value.cpu().item()
|
||||
|
||||
|
||||
class AutotuningFinder(object):
|
||||
def freeze(self):
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def get(self, key) -> Callable:
|
||||
pass
|
||||
|
||||
@abc.abstractmethod
|
||||
def set(self, *args, **kwargs):
|
||||
pass
|
||||
|
||||
|
||||
class BasedKeyFinder(AutotuningFinder):
|
||||
def __init__(self):
|
||||
self._key_to_value: Dict[Any, Callable] = dict()
|
||||
|
||||
def get(self, key, **kwargs) -> Callable:
|
||||
if "default" in kwargs:
|
||||
return self._key_to_value.get(key, kwargs["default"])
|
||||
return self._key_to_value[key]
|
||||
|
||||
def set(self, key, value):
|
||||
self._key_to_value[key] = value
|
||||
|
||||
def containe(self, key):
|
||||
return key in self._key_to_value
|
||||
|
||||
|
||||
class TreeNode:
|
||||
def __init__(self):
|
||||
self.nodes: List[Union[Any, TreeNode]] = list()
|
||||
self.key_to_nodes: Dict[Any, TreeNode] = dict()
|
||||
|
||||
def add(self, key, value):
|
||||
if isinstance(key, (tuple, list)):
|
||||
if len(key) == 1:
|
||||
self.insert_value(key[0], value)
|
||||
else:
|
||||
self.recurse_add_node(key, value)
|
||||
else:
|
||||
self.insert_value(key, value)
|
||||
|
||||
def insert_value(self, key, value):
|
||||
self.nodes.append((key, value))
|
||||
self.key_to_nodes[key] = value
|
||||
|
||||
def recurse_add_node(self, key, value):
|
||||
if key[0] in self.key_to_nodes:
|
||||
node = self.key_to_nodes[key[0]]
|
||||
else:
|
||||
node = TreeNode()
|
||||
self.key_to_nodes[key[0]] = node
|
||||
self.insert_value(key[0], node)
|
||||
|
||||
node.add(key[1:], value)
|
||||
|
||||
def sort(self):
|
||||
self.nodes.sort(key=lambda x: x[0])
|
||||
for _, node in self.nodes:
|
||||
if isinstance(node, TreeNode):
|
||||
node.sort()
|
||||
|
||||
def find(self, key):
|
||||
is_list_key = isinstance(key, (tuple, list))
|
||||
if not is_list_key:
|
||||
key = (key,)
|
||||
|
||||
num_querys = len(key)
|
||||
node = self
|
||||
for key_idx in range(num_querys):
|
||||
query_key = key[key_idx]
|
||||
idx = bisect.bisect_left(node.nodes, (query_key,)) - 1
|
||||
if idx <= 0:
|
||||
node = node.nodes[0][1]
|
||||
elif idx >= len(node.nodes):
|
||||
node = node.nodes[-1][1]
|
||||
else:
|
||||
node = node.nodes[idx][1]
|
||||
|
||||
return node
|
||||
|
||||
def show(self, indent=0):
|
||||
for k, node in self.nodes:
|
||||
print(" " * indent, end="")
|
||||
if isinstance(node, TreeNode):
|
||||
print(f"key: {k}")
|
||||
node.show(indent=indent + 4)
|
||||
else:
|
||||
print(f"key: {k}, node: {node}")
|
||||
|
||||
|
||||
class BasedRangeFinder(AutotuningFinder):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
|
||||
self.tree = TreeNode()
|
||||
self._found_cache: Dict[Any, Callable] = dict()
|
||||
|
||||
def freeze(self):
|
||||
self.tree.sort()
|
||||
|
||||
def get(self, key) -> Callable:
|
||||
value = self._found_cache.get(key, None)
|
||||
if value is not None:
|
||||
return value
|
||||
|
||||
value = self.tree.find(key)
|
||||
self._found_cache[key] = value
|
||||
return value
|
||||
|
||||
def set(self, key, value):
|
||||
self.tree.add(key, value)
|
||||
|
||||
|
||||
class OperatorAutotuning(object):
|
||||
def __init__(self, num_repeated=5, num_warmup=3, dist_barrier=False):
|
||||
self.num_repeated = num_repeated
|
||||
self.num_warmup = num_warmup
|
||||
self.dist_barrier = dist_barrier
|
||||
|
||||
@abc.abstractmethod
|
||||
def operators(self):
|
||||
raise NotImplementedError()
|
||||
|
||||
def __call__(self, *args, **kwargs):
|
||||
return self.exec_best_operator(args, kwargs)
|
||||
|
||||
@abc.abstractmethod
|
||||
def exec_best_operator(self, args, kwargs):
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def autotuning(self, *args, **kwargs):
|
||||
raise NotImplementedError()
|
||||
|
||||
def perf_best_operator(self, *args, **kwargs) -> Callable:
|
||||
best_operator = None
|
||||
best_operator_time = float("inf")
|
||||
|
||||
for idx, operator in enumerate(self.operators()):
|
||||
op_time = self.perf_operator_time(operator, *args, **kwargs)
|
||||
if op_time < best_operator_time:
|
||||
best_operator = operator
|
||||
best_operator_time = op_time
|
||||
|
||||
# print(operator, op_time)
|
||||
|
||||
return best_operator
|
||||
|
||||
def perf_operator_time(self, op: Callable, *args, **kwargs) -> float:
|
||||
fn = Functor(op, *args, **kwargs)
|
||||
time = cuda_benchmark(fn, self.num_repeated, self.num_warmup, self.dist_barrier)
|
||||
return time.gpu
|
||||
|
||||
|
||||
class OperatorRuntimeAutotuning(OperatorAutotuning):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.operator_finder = BasedKeyFinder()
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_operator_key(self, *args, **kwargs):
|
||||
raise NotImplementedError()
|
||||
|
||||
def exec_best_operator(self, args, kwargs):
|
||||
key = self.get_operator_key(*args, **kwargs)
|
||||
operator = self.operator_finder.get(key, default=None)
|
||||
if operator is None:
|
||||
operator = self.perf_best_operator(*args, **kwargs)
|
||||
self.operator_finder.set(key, operator)
|
||||
|
||||
return operator(*args, **kwargs)
|
||||
|
||||
|
||||
class OperatorPreBaseRangeAutotuning(OperatorAutotuning):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.operator_finder = BasedRangeFinder()
|
||||
self._finished_autotuning = False
|
||||
|
||||
@abc.abstractmethod
|
||||
def get_operator_key(self, *args, **kwargs):
|
||||
raise NotImplementedError()
|
||||
|
||||
@abc.abstractmethod
|
||||
def generate_operator_inputs(self) -> Iterable[Tuple[Tuple, Dict]]:
|
||||
raise NotImplementedError()
|
||||
|
||||
def exec_best_operator(self, args, kwargs):
|
||||
if not self._finished_autotuning:
|
||||
self.autotuning()
|
||||
|
||||
best_op = self.operator_finder.get(self.get_operator_key(*args, **kwargs))
|
||||
return best_op(*args, **kwargs)
|
||||
|
||||
def autotuning(self):
|
||||
for op_args, op_kwargs in self.generate_operator_inputs():
|
||||
best_op = self.perf_best_operator(*op_args, **op_kwargs)
|
||||
self.operator_finder.set(
|
||||
self.get_operator_key(*op_args, **op_kwargs), best_op
|
||||
)
|
||||
|
||||
self.operator_finder.freeze()
|
||||
self._finished_autotuning = True
|
||||
# self.operator_finder.tree.show()
|
||||
40
ixformer_sdk/csrc/FindIXFORMER.cmake
Normal file
40
ixformer_sdk/csrc/FindIXFORMER.cmake
Normal file
@@ -0,0 +1,40 @@
|
||||
# use python to find ixformer libs and include
|
||||
if (CMAKE_VERSION VERSION_LESS 3.18)
|
||||
set(DEV_MODULE Development)
|
||||
else()
|
||||
set(DEV_MODULE Development.Module)
|
||||
endif()
|
||||
|
||||
find_package(Python COMPONENTS Interpreter ${DEV_MODULE} REQUIRED)
|
||||
|
||||
# find ixformer
|
||||
set(IXFORMER_FOUND FALSE)
|
||||
|
||||
if("${Python_FOUND}" STREQUAL "TRUE")
|
||||
execute_process(
|
||||
COMMAND ${Python_EXECUTABLE} -c "import os, ixformer; print(os.path.dirname(ixformer.__file__))"
|
||||
OUTPUT_VARIABLE IXFORMER_PYDIR
|
||||
ERROR_VARIABLE PYTHON_ERROR
|
||||
RESULT_VARIABLE PYTHON_RESULT
|
||||
OUTPUT_STRIP_TRAILING_WHITESPACE
|
||||
ERROR_STRIP_TRAILING_WHITESPACE
|
||||
)
|
||||
if ("${IXFORMER_PYDIR}" STREQUAL "")
|
||||
message("-- Not found ixFormer")
|
||||
else ()
|
||||
message("-- Found ixFormer: ${IXFORMER_PYDIR}")
|
||||
set(IXFORMER_FOUND TRUE)
|
||||
endif ()
|
||||
endif()
|
||||
|
||||
set(IXFORMER_COMM_LIBS "ixformer_comm")
|
||||
set(IXFORMER_KERNEL_LIBS "ixformer_kernels")
|
||||
set(IXFORMER_LIBS "${IXFORMER_COMM_LIBS} ${IXFORMER_KERNEL_LIBS}")
|
||||
set(IXFORMER_INCLUDE "")
|
||||
set(IXFORMER_DIR "")
|
||||
|
||||
if("${IXFORMER_FOUND}" STREQUAL "TRUE")
|
||||
set(IXFORMER_INCLUDE "${IXFORMER_PYDIR}/csrc/include")
|
||||
set(IXFORMER_DIR "${IXFORMER_PYDIR}")
|
||||
message("-- ixFormer LIBS: ${IXFORMER_LIBS}, INCLUDE: ${IXFORMER_INCLUDE}")
|
||||
endif ()
|
||||
357
ixformer_sdk/csrc/include/ixformer/comm/ccl.h
Normal file
357
ixformer_sdk/csrc/include/ixformer/comm/ccl.h
Normal file
@@ -0,0 +1,357 @@
|
||||
#pragma once
|
||||
|
||||
#include "core/op_algo.h"
|
||||
#include "nccl.h"
|
||||
|
||||
namespace ixformer::comm {
|
||||
|
||||
const uint8_t MAX_TENSOR_NDIM = 8;
|
||||
constexpr size_t DEFAULT_SHM_SIZE = 16 * 1024 * 1024 * sizeof(float);
|
||||
|
||||
struct Comm;
|
||||
typedef Comm *Comm_t;
|
||||
|
||||
|
||||
struct TensorDesc {
|
||||
void *data_ptr;
|
||||
ncclDataType_t dtype;
|
||||
uint64_t numel;
|
||||
uint8_t ndim;
|
||||
int64_t shape[MAX_TENSOR_NDIM];
|
||||
int64_t stride[MAX_TENSOR_NDIM];
|
||||
bool contiguous;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @brief Generate unique communicator id.
|
||||
*
|
||||
* Generates an Id to be used in ncclCommInitRank. ncclGetUniqueId should be
|
||||
* called once and the Id should be distributed to all ranks in the
|
||||
* communicator before calling ncclCommInitRank.
|
||||
*
|
||||
* @param commId: the unique id of communicator, it is created in main rank, and broadcast other rank.
|
||||
*/
|
||||
void getUniqueId(ncclUniqueId *commId);
|
||||
|
||||
/**
|
||||
* @brief Serialize commId to string.
|
||||
* @param commId: the unique id of communicator.
|
||||
* @return: serialized string.
|
||||
*/
|
||||
std::string serializeUniqueId(const ncclUniqueId &commId);
|
||||
|
||||
/**
|
||||
* @brief Deserialize the string of commId.
|
||||
* @param commIdStr: serialized string by serializeUniqueId.
|
||||
* @param commId: output commId
|
||||
*/
|
||||
void deserializeUniqueId(const std::string &commIdStr, ncclUniqueId *commId);
|
||||
|
||||
/**
|
||||
* @brief Creates a new communicator (multi process version).
|
||||
*
|
||||
* Rank must be between 0 and nranks-1 and unique within a communicator clique.
|
||||
* Each rank is associated to a CUDA device, which has to be set before calling ncclCommInitRank.
|
||||
*
|
||||
* It is important to ensure that the current process's CUDA device is set by cudaSetDevice before calling this function,
|
||||
* otherwise, an exception will be thrown.
|
||||
*
|
||||
* @param comm: Communicator
|
||||
* @param nranks: the number of ranks.
|
||||
* @param commId: the unique id of communicator.
|
||||
* @param rank: the rank of current process
|
||||
* @param shm_size: Unlike NCCL, IxFormer communication relies on CUDA IPC for communication by shared memory.
|
||||
* If the shm_size is not provided, it will use the default value: DEFAULT_SHM_SIZE.
|
||||
* @throw CommError: Throw CommError when an error is encountered.
|
||||
*/
|
||||
void initRank(Comm_t *comm, int nranks, ncclUniqueId commId, int rank, size_t shm_size = DEFAULT_SHM_SIZE);
|
||||
|
||||
/**
|
||||
* @brief Finalize a communicator.
|
||||
*
|
||||
* ncclCommFinalize flushes all issued communications,
|
||||
* and marks communicator state as ncclInProgress. The state will change to ncclSuccess
|
||||
* when the communicator is globally quiescent and related resources are freed; then,
|
||||
* calling ncclCommDestroy can locally free the rest of the resources (e.g. communicator
|
||||
* itself) without blocking.
|
||||
*
|
||||
* @param comm: Communicator
|
||||
* @throw CommError: Throw CommError when an error is encountered.
|
||||
*/
|
||||
void destroy(Comm_t comm);
|
||||
|
||||
void delete_comm_resuouces(Comm_t comm);
|
||||
|
||||
/**
|
||||
* @brief Whether is initiated.
|
||||
* @param comm: Communicator
|
||||
*/
|
||||
bool isInitiated(Comm_t comm);
|
||||
|
||||
/**
|
||||
* @brief Gets ncclComm_t.
|
||||
* @param comm: Communicator
|
||||
*/
|
||||
ncclComm_t getNcclComm(Comm_t comm);
|
||||
|
||||
/**
|
||||
* @brief Gets the number of ranks in the communicator clique
|
||||
* @param comm: Communicator
|
||||
*/
|
||||
int getWorldSize(Comm_t comm);
|
||||
|
||||
/**
|
||||
* @brief Gets the number of nodes in the communicator clique
|
||||
* @param comm: Communicator
|
||||
*/
|
||||
int getNumNodes(Comm_t comm);
|
||||
|
||||
/**
|
||||
* @brief Returns the user-ordered "rank" associated with the communicator.
|
||||
* @param comm: Communicator
|
||||
*/
|
||||
int getRank(Comm_t comm);
|
||||
|
||||
/**
|
||||
* @brief Returns the cuda device number associated with the communicator.
|
||||
* @param comm: Communicator
|
||||
*/
|
||||
int getDevice(Comm_t comm);
|
||||
|
||||
/**
|
||||
* @brief Gets shared memory size in the communicator clique
|
||||
* @param comm: Communicator
|
||||
*/
|
||||
uint64_t getIpcShmSize(Comm_t comm);
|
||||
|
||||
|
||||
// ============================================================================
|
||||
// Collective communication operations
|
||||
//
|
||||
// Collective communication operations must be called separately for each
|
||||
// communicator in a communicator clique.
|
||||
//
|
||||
// They return when operations have been enqueued on the CUDA stream.
|
||||
//
|
||||
// Since they may perform inter-CPU synchronization, each call has to be done
|
||||
// from a different thread or process, or need to use Group Semantics (see
|
||||
// below).
|
||||
// ============================================================================
|
||||
|
||||
/**
|
||||
* @brief Barrier the member of the communicator
|
||||
* @param comm: Communicator
|
||||
* @param stream: CUDA Stream
|
||||
*/
|
||||
void barrier(Comm_t comm, cudaStream_t stream);
|
||||
|
||||
/**
|
||||
* @brief All-Gather
|
||||
*
|
||||
* Each device gathers sendcount values from other GPUs into senddata,
|
||||
* receiving data from rank i at offset i*sendcount.
|
||||
* Assumes recvcount is equal to nranks*sendcount, which means that recvdata
|
||||
* should have a size of at least nranks*sendcount elements.
|
||||
*
|
||||
* In-place operations will happen if senddata == recvdata + rank * sendcount.
|
||||
*
|
||||
* @param comm: Communicator
|
||||
* @param senddata: send data
|
||||
* @param recvdata: recv data
|
||||
* @param sendcount: the number of send elements, it is not nbytes.
|
||||
* @param dtype: data type
|
||||
* @param stream: CUDA Stream
|
||||
* @param algo: Algorithm
|
||||
* @throw CommError: Throw CommError when an error is encountered.
|
||||
*/
|
||||
void allGather(Comm_t comm, const void *senddata, void *recvdata, size_t sendcount, ncclDataType_t dtype,
|
||||
cudaStream_t stream, AllGatherAlgo algo = AllGatherAlgo::kNone);
|
||||
|
||||
/**
|
||||
* @brief All-Reduce
|
||||
*
|
||||
* Reduces data arrays of length count in senddata using op operation, and
|
||||
* leaves identical copies of result on each recvdata.
|
||||
*
|
||||
* In-place operation will happen if senddata == recvdata.
|
||||
*
|
||||
* @param comm: Communicator
|
||||
* @param senddata: send data
|
||||
* @param recvdata: recv data
|
||||
* @param count: the number of elements, it is not nbytes.
|
||||
* @param dtype: data type
|
||||
* @param op:Reduce type: ncclSum, ncclProd, ncclMin, ncclMax, ncclAvg
|
||||
* @param stream: CUDA Stream
|
||||
* @param algo: Algorithm
|
||||
* @throw CommError: Throw CommError when an error is encountered.
|
||||
*/
|
||||
void allReduce(Comm_t comm, const void *senddata, void *recvdata, size_t count, ncclDataType_t dtype,
|
||||
ncclRedOp_t op, cudaStream_t stream, AllReduceAlgo algo = AllReduceAlgo::kNone);
|
||||
|
||||
/**
|
||||
* @brief Whether is supported non-contiguous tensors
|
||||
* @param comm: Communicator
|
||||
* @param dtype: data type
|
||||
* @param shape: tensor shape
|
||||
* @param ndim: the ndim of tensor
|
||||
* @param numel: the number of tensor
|
||||
* @param op: Reduce type: ncclSum, ncclProd, ncclMin, ncclMax, ncclAvg
|
||||
* @return: supported
|
||||
*/
|
||||
bool allReduceStrideSupported(Comm_t comm, ncclDataType_t dtype, const int64_t *shape, int ndim, uint64_t numel, ncclRedOp_t op);
|
||||
|
||||
/**
|
||||
* @brief All-Reduce for non-contiguous tensors
|
||||
* @param comm: Communicator
|
||||
* @param senddata: send tensor
|
||||
* @param recvdata: recv tensor
|
||||
* @param stream: CUDA Stream
|
||||
*/
|
||||
void allReduceStride(Comm_t comm, const TensorDesc &senddata, TensorDesc &recvdata, cudaStream_t stream);
|
||||
|
||||
/**
|
||||
* @brief Send data from senddata to rank peer.
|
||||
*
|
||||
* Rank peer needs to call ncclRecv with the same datatype and the same count from this
|
||||
* rank. This operation is blocking for the GPU. If multiple ncclSend and ncclRecv operations
|
||||
* need to progress concurrently to complete.
|
||||
*
|
||||
* @param comm: Communicator
|
||||
* @param senddata: send data
|
||||
* @param count: the number of send elements, it is not nbytes.
|
||||
* @param dtype: data type
|
||||
* @param peer: the destination rank
|
||||
* @param stream: CUDA Stream
|
||||
* @param algo: Algorithm
|
||||
* @throw CommError: Throw CommError when an error is encountered.
|
||||
*/
|
||||
void send(Comm_t comm, const void *senddata, size_t count, ncclDataType_t dtype, int peer, cudaStream_t stream,
|
||||
SendAlgo algo = SendAlgo::kNone);
|
||||
|
||||
/**
|
||||
* @brief Receive data from rank peer into recvdata.
|
||||
*
|
||||
* Rank peer needs to call ncclSend with the same datatype and the same count to this
|
||||
* rank. This operation is blocking for the GPU. If multiple ncclSend and ncclRecv operations
|
||||
* need to progress concurrently to complete.
|
||||
*
|
||||
* @param comm: Communicator
|
||||
* @param recvdata: recv data
|
||||
* @param count: the number of recv elements, it is not nbytes.
|
||||
* @param dtype: data type
|
||||
* @param peer: source rank
|
||||
* @param stream: CUDA Stream
|
||||
* @param algo: Algorithm
|
||||
* @throw CommError: Throw CommError when an error is encountered.
|
||||
*/
|
||||
void recv(Comm_t comm, void *recvdata, size_t count, ncclDataType_t dtype, int peer, cudaStream_t stream,
|
||||
RecvAlgo algo = RecvAlgo::kNone);
|
||||
|
||||
/**
|
||||
* @brief Reduces data arrays of length count in senddata into recvdata using op operation.
|
||||
*
|
||||
* Recvdata may be NULL on all calls except for root device.
|
||||
* root is the rank (not the CUDA device) where data will reside after the
|
||||
* operation is complete.
|
||||
*
|
||||
* In-place operation will happen if senddata == recvdata.
|
||||
*
|
||||
* @param comm: Communicator
|
||||
* @param senddata: send data
|
||||
* @param recvdata: recv data
|
||||
* @param count: the number of elements, it is not nbytes.
|
||||
* @param dtype: data type
|
||||
* @param op: Reduce type: ncclSum, ncclProd, ncclMin, ncclMax, ncclAvg
|
||||
* @param root: root rank
|
||||
* @param stream: CUDA Stream
|
||||
* @param algo: Algorithm
|
||||
* @throw CommError: Throw CommError when an error is encountered.
|
||||
*/
|
||||
void reduce(Comm_t comm, const void *senddata, void *recvdata, size_t count, ncclDataType_t dtype, ncclRedOp_t op,
|
||||
int root, cudaStream_t stream, ReduceAlgo algo = ReduceAlgo::kNone);
|
||||
|
||||
/**
|
||||
* @brief Broadcast
|
||||
*
|
||||
* Copies count values from root to all other devices.
|
||||
* root is the rank (not the CUDA device) where data resides before the
|
||||
* operation is started.
|
||||
*
|
||||
* In-place operation will happen if senddata == recvdata.
|
||||
*
|
||||
* @param comm: Communicator
|
||||
* @param senddata: send data
|
||||
* @param recvdata: recv data
|
||||
* @param count: the number of elements, it is not nbytes.
|
||||
* @param dtype: data type
|
||||
* @param root: root rank
|
||||
* @param stream: CUDA Stream
|
||||
* @param algo: Algorithm
|
||||
* @throw CommError: Throw CommError when an error is encountered.
|
||||
*/
|
||||
void broadcast(Comm_t comm, const void *senddata, void *recvdata, size_t count, ncclDataType_t dtype, int root,
|
||||
cudaStream_t stream, BroadcastAlgo algo = BroadcastAlgo::kNone);
|
||||
|
||||
/**
|
||||
*
|
||||
* @brief Reduce-Scatter
|
||||
*
|
||||
* Reduces data in senddata using op operation and leaves reduced result
|
||||
* scattered over the devices so that recvdata on rank i will contain the i-th
|
||||
* block of the result.
|
||||
* Assumes sendcount is equal to nranks*recvcount, which means that senddata
|
||||
* should have a size of at least nranks*recvcount elements.
|
||||
*
|
||||
* In-place operations will happen if recvdata == senddata + rank * recvcount.
|
||||
*
|
||||
* @param comm: Communicator
|
||||
* @param senddata: send data
|
||||
* @param recvdata: recv data
|
||||
* @param recvcount: the number of recv elements, it is not nbytes.
|
||||
* @param dtype: data type
|
||||
* @param op:Reduce type: ncclSum, ncclProd, ncclMin, ncclMax, ncclAvg
|
||||
* @param stream: CUDA Stream
|
||||
* @param algo: Algorithm
|
||||
* @throw CommError: Throw CommError when an error is encountered.
|
||||
*/
|
||||
void reduceScatter(Comm_t comm, const void *senddata, void *recvdata,
|
||||
size_t recvcount, ncclDataType_t dtype, ncclRedOp_t op, cudaStream_t stream,
|
||||
ReduceScatterAlgo algo = ReduceScatterAlgo::kNone);
|
||||
|
||||
/**
|
||||
* @brief Send data from src_rank to dst_rank on src_rank process, recv data on dst_rank.
|
||||
*
|
||||
* @param comm: Communicator
|
||||
* @param data: send data to dst rank if current rank is src_rank, recv data if current rank is dst_rank.
|
||||
* @param count: the number of send/recv elements, it is not nbytes.
|
||||
* @param dtype: data type
|
||||
* @param src_rank: src rank
|
||||
* @param dst_rank: dst rank
|
||||
* @param stream: CUDA Stream
|
||||
* @param algo: Algorithm
|
||||
* @throw CommError: Throw CommError when an error is encountered.
|
||||
*/
|
||||
void p2p(Comm_t comm, void *data, size_t count, ncclDataType_t dtype, int src_rank, int dst_rank, cudaStream_t stream,
|
||||
SendAlgo algo = SendAlgo::kNone);
|
||||
|
||||
/**
|
||||
* @brief The all ranks of communicator send senddata to dst_rank.
|
||||
*
|
||||
* @param comm: Communicator
|
||||
* @param senddata: send data
|
||||
* @param recvdatas: recv datas,it is two-dim array, shape: [WorldSize, RecvDataPointer],
|
||||
* the first dim is host pointer,the second dim is GPU pointer,
|
||||
* it can be nullptr when current rank is not dst rank.
|
||||
* @param sendcount: the number of send elements, it is not nbytes.
|
||||
* @param dst_rank: dst rank
|
||||
* @param dtype: data type
|
||||
* @param stream: CUDA Stream
|
||||
* @param algo: Algorithm
|
||||
* @throw CommError: Throw CommError when an error is encountered.
|
||||
*/
|
||||
void gather(Comm_t comm, const void *senddata, void **recvdatas, size_t sendcount, int dst_rank, ncclDataType_t dtype,
|
||||
cudaStream_t stream, GatherAlgo algo = GatherAlgo::kNone);
|
||||
|
||||
|
||||
}// namespace ixformer::comm
|
||||
21
ixformer_sdk/csrc/include/ixformer/comm/core/error.h
Normal file
21
ixformer_sdk/csrc/include/ixformer/comm/core/error.h
Normal file
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdexcept>
|
||||
#include "status.h"
|
||||
|
||||
namespace ixformer::comm {
|
||||
|
||||
class CommError : public std::runtime_error {
|
||||
public:
|
||||
template<class ERROR_STR>
|
||||
CommError(CommStatus error, const ERROR_STR str) : error_{error}, std::runtime_error(str) {}
|
||||
|
||||
CommStatus status() {
|
||||
return error_;
|
||||
}
|
||||
|
||||
private:
|
||||
CommStatus error_;
|
||||
};
|
||||
|
||||
}// namespace ixformer::comm
|
||||
80
ixformer_sdk/csrc/include/ixformer/comm/core/op_algo.h
Normal file
80
ixformer_sdk/csrc/include/ixformer/comm/core/op_algo.h
Normal file
@@ -0,0 +1,80 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ixformer::comm {
|
||||
|
||||
enum class AllGatherAlgo {
|
||||
kNone,
|
||||
kAuto,
|
||||
kNCCL,
|
||||
kNumAlgo
|
||||
};
|
||||
|
||||
enum class AllReduceAlgo {
|
||||
kNone, // None
|
||||
kAuto, // 自动选择算法
|
||||
kAllGatherSum, // 针对小数据量的算法
|
||||
kBroadcastSum, // 针对小数据量的算法
|
||||
kRing, // Ring AllReduce
|
||||
kQuant, // 对通讯算法进行量化,默认使用 kQuantL1
|
||||
kQuantL1, // 对通讯算法进行量化,优先使用量化算法以及最大保留精度,在部分 Size 性能不佳时,退化为 Auto 算法
|
||||
kQuantL2, // 对通讯算法进行量化,优先使用量化算法以及最大化速度,在部分 Size 性能不佳时,退化为 Auto 算法
|
||||
kQuantL1AllSize,// 对所有的 Size 都使用量化算法
|
||||
kQuantL2AllSize,// 对所有的 Size 都使用量化算法
|
||||
kNCCL, // 使用 NCCL
|
||||
kStride, // 输入或输出的 Tensor 不是连续的
|
||||
kNumAlgo
|
||||
};
|
||||
|
||||
|
||||
enum class BroadcastAlgo {
|
||||
kNone,
|
||||
kAuto,
|
||||
kNCCL,
|
||||
kNumAlgo
|
||||
};
|
||||
|
||||
enum class GatherAlgo {
|
||||
kNone,
|
||||
kAuto,
|
||||
kNCCL,
|
||||
kNumAlgo
|
||||
};
|
||||
|
||||
enum class SendAlgo {
|
||||
kNone,
|
||||
kAuto,
|
||||
kNCCL,
|
||||
kNumAlgo
|
||||
};
|
||||
|
||||
typedef SendAlgo RecvAlgo;
|
||||
|
||||
enum class ReduceAlgo {
|
||||
kNone,
|
||||
kAuto,
|
||||
kNCCL,
|
||||
kNumAlgo
|
||||
};
|
||||
|
||||
enum class ReduceScatterAlgo {
|
||||
kNone,
|
||||
kAuto,
|
||||
kNCCL,
|
||||
kNumAlgo
|
||||
};
|
||||
|
||||
|
||||
std::string to_string(AllGatherAlgo algo);
|
||||
std::string to_string(AllReduceAlgo algo);
|
||||
std::string to_string(BroadcastAlgo algo);
|
||||
std::string to_string(GatherAlgo algo);
|
||||
std::string to_string(SendAlgo algo);
|
||||
std::string to_string(ReduceAlgo algo);
|
||||
std::string to_string(ReduceScatterAlgo algo);
|
||||
|
||||
template<typename Algo>
|
||||
Algo get_algo_from_str(const std::string &name);
|
||||
|
||||
}// namespace ixformer::comm
|
||||
22
ixformer_sdk/csrc/include/ixformer/comm/core/status.h
Normal file
22
ixformer_sdk/csrc/include/ixformer/comm/core/status.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
#include "comm/core/common.h"
|
||||
|
||||
|
||||
namespace ixformer::comm {
|
||||
|
||||
enum CommStatus {
|
||||
commSuccess,
|
||||
commFail,
|
||||
commCudaError,
|
||||
commNcclError,
|
||||
commInvalidArgument,
|
||||
commUnsupported,
|
||||
commInternalError,
|
||||
commInvalidComm// maybe comm is nullptr
|
||||
};
|
||||
|
||||
|
||||
std::string to_string(CommStatus status);
|
||||
|
||||
|
||||
}// namespace ixformer::comm
|
||||
22
ixformer_sdk/csrc/include/ixformer/kernels/error.h
Normal file
22
ixformer_sdk/csrc/include/ixformer/kernels/error.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
#include "status.h"
|
||||
|
||||
namespace ixformer::kernels {
|
||||
|
||||
class KernelError : public std::runtime_error {
|
||||
public:
|
||||
template<class ERROR_STR>
|
||||
KernelError(KernelStatus error, const ERROR_STR str) : error_{error}, std::runtime_error(str) {}
|
||||
|
||||
KernelStatus status() {
|
||||
return error_;
|
||||
}
|
||||
|
||||
private:
|
||||
KernelStatus error_;
|
||||
};
|
||||
|
||||
}// namespace ixformer::kernels
|
||||
2520
ixformer_sdk/csrc/include/ixformer/kernels/kernels.h
Normal file
2520
ixformer_sdk/csrc/include/ixformer/kernels/kernels.h
Normal file
File diff suppressed because it is too large
Load Diff
20
ixformer_sdk/csrc/include/ixformer/kernels/status.h
Normal file
20
ixformer_sdk/csrc/include/ixformer/kernels/status.h
Normal file
@@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ixformer::kernels {
|
||||
|
||||
enum KernelStatus {
|
||||
kernelSuccess,
|
||||
kernelFail,
|
||||
kernelCudaError,
|
||||
kernelInvalidArgument,
|
||||
kernelCuinferError,
|
||||
kernelUnsupported,
|
||||
};
|
||||
|
||||
|
||||
std::string to_string(KernelStatus status);
|
||||
|
||||
|
||||
}// namespace ixformer::kernels
|
||||
92
ixformer_sdk/csrc/include/ixformer/kernels/tensor.h
Normal file
92
ixformer_sdk/csrc/include/ixformer/kernels/tensor.h
Normal file
@@ -0,0 +1,92 @@
|
||||
#pragma once
|
||||
|
||||
#include <string>
|
||||
|
||||
namespace ixformer::kernels {
|
||||
|
||||
const uint8_t MAX_TENSOR_NDIM = 8;
|
||||
|
||||
// align with at::ScalarType
|
||||
enum DType {
|
||||
Byte = 0,
|
||||
Char = 1,
|
||||
Short = 2,
|
||||
Int = 3,
|
||||
Long = 4,
|
||||
Half = 5,
|
||||
Float = 6,
|
||||
Double = 7,
|
||||
ComplexHalf = 8,
|
||||
ComplexFloat = 9,
|
||||
ComplexDoubl = 10,
|
||||
Bool = 11,
|
||||
QInt8 = 12,
|
||||
QUInt8 = 13,
|
||||
QInt32 = 14,
|
||||
BFloat16 = 15,
|
||||
QUInt4x2 = 16,
|
||||
QUInt2x4 = 17,
|
||||
Bits1x8 = 18,
|
||||
Bits2x4 = 19,
|
||||
Bits4x2 = 20,
|
||||
Bits8 = 21,
|
||||
Bits16 = 22,
|
||||
Float8_e5m2 = 23,
|
||||
Float8_e4m3fn = 24,
|
||||
Undefined = 25,
|
||||
NumOptions = 26
|
||||
};
|
||||
|
||||
struct TensorDesc {
|
||||
|
||||
public:
|
||||
// delete default constructor
|
||||
TensorDesc() = delete;
|
||||
// All information must be (should be) prepared when constructing a TensorDesc object.
|
||||
TensorDesc(DType scalar_type, void *data_ptr, int64_t numel, int64_t dim, const int64_t *size, const int64_t *stride, bool is_contiguous, bool is_cuda)
|
||||
: dtype(scalar_type), ptr(data_ptr), nnumel(numel), ndim(dim), sizes(size), strides(stride), contiguous(is_contiguous), cuda(is_cuda) {}
|
||||
|
||||
inline DType scalar_type() const {
|
||||
return dtype;
|
||||
}
|
||||
|
||||
inline void *data_ptr() const {
|
||||
return ptr;
|
||||
}
|
||||
|
||||
inline int64_t numel() const {
|
||||
return nnumel;
|
||||
}
|
||||
|
||||
inline int64_t dim() const {
|
||||
return ndim;
|
||||
}
|
||||
|
||||
inline int64_t size(int64_t dim) const {
|
||||
return dim < 0 ? sizes[ndim - dim] : sizes[dim];
|
||||
}
|
||||
|
||||
inline int64_t stride(int64_t dim) const {
|
||||
return dim < 0 ? strides[ndim - dim] : strides[dim];
|
||||
}
|
||||
|
||||
inline bool is_contiguous() const {
|
||||
return contiguous;
|
||||
}
|
||||
|
||||
inline bool is_cuda() const {
|
||||
return cuda;
|
||||
}
|
||||
|
||||
private:
|
||||
void *ptr{nullptr};
|
||||
DType dtype;
|
||||
int64_t nnumel{0};
|
||||
int64_t ndim{0};
|
||||
const int64_t *sizes{nullptr};
|
||||
const int64_t *strides{nullptr};
|
||||
bool contiguous{false};
|
||||
bool cuda{false};
|
||||
};
|
||||
|
||||
}// namespace ixformer::kernels
|
||||
1
ixformer_sdk/distributed/__init__.py
Normal file
1
ixformer_sdk/distributed/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from ._distributed import *
|
||||
481
ixformer_sdk/distributed/_distributed.py
Normal file
481
ixformer_sdk/distributed/_distributed.py
Normal file
@@ -0,0 +1,481 @@
|
||||
import warnings
|
||||
from collections import defaultdict
|
||||
from typing import List, Optional, Tuple
|
||||
|
||||
import torch
|
||||
import torch.distributed as dist
|
||||
import torch.distributed.distributed_c10d as c10d
|
||||
from ixformer._C import _distributed as cdist
|
||||
from ixformer._C._distributed import comm
|
||||
from ixformer._C._distributed.comm import (
|
||||
AllGatherAlgo,
|
||||
AllReduceAlgo,
|
||||
BroadcastAlgo,
|
||||
ReduceAlgo,
|
||||
ReduceOp,
|
||||
ReduceScatterAlgo,
|
||||
SendAlgo,
|
||||
)
|
||||
from ixformer.core.multi_level_cache import MultiLevelCache
|
||||
from torch import Tensor
|
||||
from torch.distributed import ProcessGroup
|
||||
|
||||
from ixformer.core import config
|
||||
|
||||
IxformerCommType = int
|
||||
RecvAlgo = SendAlgo
|
||||
|
||||
_GROUP_TO_IXFC_COMM_CACHE = MultiLevelCache()
|
||||
_IXFC_COMM_TO_GROUP_CACHE = MultiLevelCache()
|
||||
|
||||
|
||||
def get_store(group: dist.ProcessGroup = None) -> dist.Store:
|
||||
if group is None:
|
||||
group = c10d._get_default_group()
|
||||
|
||||
return c10d._pg_map[group][1]
|
||||
|
||||
|
||||
class StoreWrapper(cdist.comm.C10dStoreWrapper):
|
||||
_GROUP_COUNT = defaultdict(dict)
|
||||
|
||||
def __init__(self, group: ProcessGroup):
|
||||
super().__init__()
|
||||
self.store = get_store()
|
||||
|
||||
ranks = dist.get_process_group_ranks(group)
|
||||
|
||||
group_key = "_".join([str(r) for r in ranks])
|
||||
if group not in self._GROUP_COUNT[group_key]:
|
||||
self._GROUP_COUNT[group_key][group] = len(self._GROUP_COUNT[group_key])
|
||||
group_count = self._GROUP_COUNT[group_key][group]
|
||||
|
||||
self.prefix = f"gid_{group_count}_" + group_key
|
||||
|
||||
def _gen_unique_key(self, key):
|
||||
return f"{self.prefix}_{key}"
|
||||
|
||||
def set(self, key: str, value: str):
|
||||
key = self._gen_unique_key(key)
|
||||
self.store.set(key, value)
|
||||
|
||||
def get(self, key: str) -> str:
|
||||
key = self._gen_unique_key(key)
|
||||
self.store.wait([key])
|
||||
return self.store.get(key).decode("utf8")
|
||||
|
||||
|
||||
def init_comm_with_store(group=None, shmsize: int = None):
|
||||
if group is None:
|
||||
group = c10d._get_default_group()
|
||||
|
||||
world_size = dist.get_world_size(group=group)
|
||||
rank = dist.get_group_rank(group=group, global_rank=dist.get_rank())
|
||||
|
||||
if shmsize is None:
|
||||
shmsize = config.IXFORMER_COMM_SHM_SIZE
|
||||
|
||||
store_wrapper = StoreWrapper(group=group)
|
||||
ixfc_comm = cdist.comm.init_communicator_by_store(
|
||||
store=store_wrapper, world_size=world_size, rank=rank, max_shm_mem_size=shmsize
|
||||
)
|
||||
|
||||
_GROUP_TO_IXFC_COMM_CACHE.set(group, ixfc_comm)
|
||||
_IXFC_COMM_TO_GROUP_CACHE.set(ixfc_comm, group)
|
||||
return ixfc_comm
|
||||
|
||||
|
||||
_sub_store = None
|
||||
|
||||
|
||||
def create_nccl_unique_id(addr: str, port: str, world_size: int, rank: int):
|
||||
global _sub_store
|
||||
_sub_store = dist.TCPStore(
|
||||
host_name=addr, port=int(port), world_size=world_size, is_master=rank == 0
|
||||
)
|
||||
store_key = "ncclUniqueId"
|
||||
if rank == 0:
|
||||
commid = cdist.comm.create_nccl_unique_id()
|
||||
_sub_store.set(store_key, commid)
|
||||
else:
|
||||
_sub_store.wait([store_key])
|
||||
commid = _sub_store.get(store_key).decode("utf8")
|
||||
|
||||
return commid
|
||||
|
||||
|
||||
def init_comm_with_eth(
|
||||
addr: str, port: str, world_size: int, rank: int, shmsize: int = None
|
||||
):
|
||||
commid = create_nccl_unique_id(addr, port, world_size=world_size, rank=rank)
|
||||
return cdist.comm.init_communicator_by_nccl_id(commid, world_size, rank, shmsize)
|
||||
|
||||
|
||||
def _check_group(group: Optional[ProcessGroup] = None):
|
||||
if group is None:
|
||||
group = c10d._get_default_group()
|
||||
|
||||
if isinstance(group, ProcessGroup):
|
||||
ixfc_comm = _GROUP_TO_IXFC_COMM_CACHE.get(group, None)
|
||||
if ixfc_comm is None:
|
||||
return init_comm_with_store(group)
|
||||
return ixfc_comm
|
||||
|
||||
return group
|
||||
|
||||
|
||||
def get_comm_group_stream(group: Optional[ProcessGroup] = None):
|
||||
group = _check_group(group)
|
||||
return comm.get_comm_group_stream(group)
|
||||
|
||||
|
||||
def set_comm_group_stream(stream: int, group: Optional[ProcessGroup] = None):
|
||||
group = _check_group(group)
|
||||
return comm.set_comm_group_stream(group, stream)
|
||||
|
||||
|
||||
def get_group_rank(group: Optional[ProcessGroup], global_rank) -> int:
|
||||
"""将 global rank 映射到 group 中的相对 rank"""
|
||||
if isinstance(group, IxformerCommType):
|
||||
_pg = _IXFC_COMM_TO_GROUP_CACHE.get(group, None)
|
||||
if _pg is None:
|
||||
return global_rank
|
||||
else:
|
||||
group = _IXFC_COMM_TO_GROUP_CACHE.get(group)
|
||||
|
||||
if group is None:
|
||||
group = c10d._get_default_group()
|
||||
|
||||
return dist.get_group_rank(group, global_rank)
|
||||
|
||||
|
||||
def get_global_rank(group: Optional[ProcessGroup], group_rank: int) -> int:
|
||||
"""将一个 group rank 映射到 global rank"""
|
||||
if group is None:
|
||||
group = c10d._get_default_group()
|
||||
return c10d.get_global_rank(group, group_rank)
|
||||
|
||||
|
||||
def get_process_group_ranks(group: Optional[ProcessGroup] = None) -> List[int]:
|
||||
"""获取 Group 的 global ranks"""
|
||||
if group is None:
|
||||
group = c10d._get_default_group()
|
||||
return c10d.get_process_group_ranks(group)
|
||||
|
||||
|
||||
def new_group(ranks: List[int] = None, shmsize=None, *args, **kwargs):
|
||||
"""通过 global ranks 去创建一个通讯组"""
|
||||
group = c10d.new_group(ranks, *args, **kwargs)
|
||||
|
||||
if ranks is None:
|
||||
ranks = dist.get_process_group_ranks(group)
|
||||
|
||||
if get_rank() in ranks:
|
||||
init_comm_with_store(group=group, shmsize=shmsize)
|
||||
return group
|
||||
|
||||
|
||||
def new_subgroups_by_enumeration(
|
||||
ranks_per_subgroup_list, shmsize=None, *args, **kwargs
|
||||
) -> Tuple[ProcessGroup, List[ProcessGroup]]:
|
||||
"""
|
||||
通过一组 global ranks 去创建通讯组
|
||||
|
||||
:param ranks_per_subgroup_list: global ranks
|
||||
:return: 返回当前 rank 所在的通讯组 和 新的 subgroups
|
||||
"""
|
||||
self_group, other_group = c10d.new_subgroups_by_enumeration(
|
||||
ranks_per_subgroup_list, *args, **kwargs
|
||||
)
|
||||
init_comm_with_store(self_group, shmsize=shmsize)
|
||||
return self_group, other_group
|
||||
|
||||
|
||||
def destroy_process_group(group: Optional[ProcessGroup] = None):
|
||||
"""销毁 Group"""
|
||||
if group is None:
|
||||
group = c10d._get_default_group()
|
||||
ixfc_comm = _GROUP_TO_IXFC_COMM_CACHE.get(group, None)
|
||||
|
||||
if ixfc_comm is None:
|
||||
dist.destroy_process_group(group)
|
||||
else:
|
||||
comm.destroy(ixfc_comm)
|
||||
dist.destroy_process_group(group)
|
||||
|
||||
|
||||
def get_rank(group: Optional[ProcessGroup] = None) -> int:
|
||||
"""获取当前进程的 Rank,如果 group 是 null,那么返回的是 Global Rank, 否则返回的相对的 Rank,即在当前组中的 rank"""
|
||||
return c10d.get_rank(group)
|
||||
|
||||
|
||||
def get_world_size(group: Optional[ProcessGroup] = None) -> int:
|
||||
"""获取 Group 中的成员大小"""
|
||||
return c10d.get_world_size(group)
|
||||
|
||||
|
||||
def barrier(group: Optional[ProcessGroup] = None, use_comm_stream: bool = False):
|
||||
"""同步 Group 中的 rank"""
|
||||
group = _check_group(group)
|
||||
comm.barrier(group, use_comm_stream)
|
||||
|
||||
|
||||
def isend(
|
||||
tensor: Tensor,
|
||||
dst: int,
|
||||
group: Optional[ProcessGroup] = None,
|
||||
use_comm_stream: bool = False,
|
||||
):
|
||||
dst = get_group_rank(group, dst)
|
||||
group = _check_group(group)
|
||||
return comm.send(group, tensor, dst, use_comm_stream, SendAlgo.kNone)
|
||||
|
||||
|
||||
def send(*args, **kwargs):
|
||||
warnings.warn("not support sync mode, as async to call.")
|
||||
return isend(*args, **kwargs)
|
||||
|
||||
|
||||
def irecv(
|
||||
tensor: torch.Tensor,
|
||||
src: int,
|
||||
group: Optional[ProcessGroup] = None,
|
||||
use_comm_stream: bool = False,
|
||||
):
|
||||
src = get_group_rank(group, src)
|
||||
group = _check_group(group)
|
||||
return comm.recv(group, tensor, src, use_comm_stream, SendAlgo.kNone)
|
||||
|
||||
|
||||
def recv(*args, **kwargs):
|
||||
warnings.warn("not support sync mode, as async to call.")
|
||||
return irecv(*args, **kwargs)
|
||||
|
||||
|
||||
def point_to_point(
|
||||
tensor: Tensor,
|
||||
src: int,
|
||||
dst: int,
|
||||
group: Optional[ProcessGroup] = None,
|
||||
use_comm_stream: bool = False,
|
||||
):
|
||||
"""在 src rank 发送 tensor,在 dst_rank 上接收数据到 tensor 中"""
|
||||
src = get_group_rank(group, src)
|
||||
dst = get_group_rank(group, dst)
|
||||
group = _check_group(group)
|
||||
return comm.p2p(group, tensor, src, dst, use_comm_stream)
|
||||
|
||||
|
||||
def reduce(
|
||||
tensor,
|
||||
root: int,
|
||||
op=ReduceOp.SUM,
|
||||
group: Optional[ProcessGroup] = None,
|
||||
async_op=False,
|
||||
out: Tensor = None,
|
||||
use_comm_stream: bool = False,
|
||||
):
|
||||
"""
|
||||
Example:
|
||||
ixf_tensor = torch.tensor([1], device="cuda")
|
||||
ixfd.reduce(ixf_tensor, 1, async_op=True)
|
||||
print("rank {rank}:", ixf_tensor)
|
||||
|
||||
# output
|
||||
rank 0: tensor([1], device='cuda:0')
|
||||
rank 1: tensor([4], device='cuda:1')
|
||||
rank 2: tensor([1], device='cuda:2')
|
||||
rank 3: tensor([1], device='cuda:3')
|
||||
"""
|
||||
|
||||
if not async_op:
|
||||
raise RuntimeError("Not support sync operation now.")
|
||||
|
||||
if out is None:
|
||||
out = tensor
|
||||
|
||||
root = get_group_rank(group, root)
|
||||
group = _check_group(group)
|
||||
return comm.reduce(group, tensor, out, op, root, use_comm_stream, ReduceAlgo.kNone)
|
||||
|
||||
|
||||
def broadcast(
|
||||
tensor: Tensor,
|
||||
src: int,
|
||||
group: Optional[ProcessGroup] = None,
|
||||
async_op=False,
|
||||
out: Tensor = None,
|
||||
use_comm_stream: bool = False,
|
||||
):
|
||||
"""
|
||||
Example:
|
||||
ixf_tensor = torch.tensor([rank], device="cuda")
|
||||
ixfd.broadcast(ixf_tensor, 1, async_op=True)
|
||||
print("rank {rank}: ", ixf_tensor)
|
||||
|
||||
# output
|
||||
rank 0: tensor([1], device='cuda:0')
|
||||
rank 1: tensor([1], device='cuda:1')
|
||||
rank 2: tensor([1], device='cuda:2')
|
||||
rank 3: tensor([1], device='cuda:3')
|
||||
"""
|
||||
if not async_op:
|
||||
raise RuntimeError("Not support sync operation now.")
|
||||
|
||||
if out is None:
|
||||
out = tensor
|
||||
|
||||
src = get_group_rank(group, src)
|
||||
group = _check_group(group)
|
||||
return comm.broadcast(group, tensor, out, src, use_comm_stream, BroadcastAlgo.kNone)
|
||||
|
||||
|
||||
def reduce_scatter_tensor(
|
||||
output: Tensor,
|
||||
input: Tensor,
|
||||
op=ReduceOp.SUM,
|
||||
group: Optional[ProcessGroup] = None,
|
||||
async_op=False,
|
||||
use_comm_stream: bool = False,
|
||||
):
|
||||
"""
|
||||
Example:
|
||||
ixf_tensor_out = torch.zeros(2, dtype=torch.int64, device="cuda")
|
||||
tensor_in = torch.arange(world_size * 2, dtype=torch.int64, device="cuda")
|
||||
# tensor_in: tensor([0, 1, 2, 3, 4, 5, 6, 7], device='cuda:0')
|
||||
|
||||
ixfd.reduce_scatter_tensor(ixf_tensor_out, tensor_in, async_op=True)
|
||||
print("rank {rank}:", ixf_tensor_out)
|
||||
|
||||
# output
|
||||
rank 0: tensor([0, 4], device='cuda:0')
|
||||
rank 1: tensor([ 8, 12], device='cuda:1')
|
||||
rank 2: tensor([16, 20], device='cuda:2')
|
||||
rank 3: tensor([24, 28], device='cuda:3')
|
||||
"""
|
||||
if not async_op:
|
||||
raise RuntimeError("Not support sync operation now.")
|
||||
|
||||
group = _check_group(group)
|
||||
return comm.reduce_scatter(
|
||||
group, input, output, op, use_comm_stream, ReduceScatterAlgo.kNone
|
||||
)
|
||||
|
||||
|
||||
def all_reduce(
|
||||
tensor: Tensor,
|
||||
op=ReduceOp.SUM,
|
||||
group: Optional[ProcessGroup] = None,
|
||||
async_op=False,
|
||||
out: Tensor = None,
|
||||
algo: AllReduceAlgo = AllReduceAlgo.kNone,
|
||||
use_comm_stream: bool = False,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
tensor: inpute tensor
|
||||
op: ReduceOp: SUM, MIN or MAX
|
||||
group: communicator group
|
||||
async_op: ixformer support async mode
|
||||
out: output tensor
|
||||
algo: AllReduce Algo: Auto, Quant, QuantL1, QuantL2, NCCL, Ring, AllGatherSum, BroadcastSum
|
||||
use_comm_stream: ixformer support set communication stream by ixformer.distributed.set_comm_group_stream,
|
||||
if true, submit the kernels of communication to communication stream,
|
||||
if false, use current stream by torch.cuda.current_stream
|
||||
Returns: out
|
||||
|
||||
Example:
|
||||
>>> # All tensors below are of torch.int64 type.
|
||||
>>> # We have 2 process groups, 2 ranks.
|
||||
>>> tensor = torch.arange(2, dtype=torch.int64) + 1 + 2 * rank
|
||||
>>> tensor
|
||||
tensor([1, 2]) # Rank 0
|
||||
tensor([3, 4]) # Rank 1
|
||||
>>> ixfd.all_reduce(tensor, op=ReduceOp.SUM, async_op=True)
|
||||
>>> tensor
|
||||
tensor([4, 6]) # Rank 0
|
||||
tensor([4, 6]) # Rank 1
|
||||
"""
|
||||
if not async_op:
|
||||
raise RuntimeError("Not support sync operation now.")
|
||||
|
||||
group = _check_group(group)
|
||||
|
||||
if out is None:
|
||||
out = tensor
|
||||
|
||||
comm.all_reduce(
|
||||
group,
|
||||
tensor,
|
||||
out,
|
||||
op,
|
||||
use_comm_stream=use_comm_stream,
|
||||
algo=algo,
|
||||
)
|
||||
|
||||
|
||||
def all_gather_into_tensor(
|
||||
output: Tensor,
|
||||
input: Tensor,
|
||||
group: Optional[ProcessGroup] = None,
|
||||
async_op=False,
|
||||
use_comm_stream: bool = False,
|
||||
):
|
||||
"""
|
||||
Example:
|
||||
tensor_in = torch.arange(2, dtype=torch.int64, device="cuda") + 1 + 2 * rank
|
||||
rank 0: tensor in: tensor([1, 2], device='cuda:0')
|
||||
rank 1: tensor in: tensor([3, 4], device='cuda:1')
|
||||
rank 2: tensor in: tensor([5, 6], device='cuda:2')
|
||||
rank 3: tensor in: tensor([7, 8], device='cuda:3')
|
||||
|
||||
ixf_tensor_out = torch.zeros(world_size * 2, dtype=torch.int64, device="cuda")
|
||||
ixfd.all_gather_into_tensor(ixf_tensor_out, tensor_in, async_op=True)
|
||||
print("rank {rank}:", ixf_tensor_out)
|
||||
|
||||
# output:
|
||||
rank 0: tensor([1, 2, 3, 4, 5, 6, 7, 8], device='cuda:0')
|
||||
rank 1: tensor([1, 2, 3, 4, 5, 6, 7, 8], device='cuda:1')
|
||||
rank 2: tensor([1, 2, 3, 4, 5, 6, 7, 8], device='cuda:2')
|
||||
rank 3: tensor([1, 2, 3, 4, 5, 6, 7, 8], device='cuda:3')
|
||||
"""
|
||||
if not async_op:
|
||||
raise RuntimeError("Not support sync operation now.")
|
||||
|
||||
group = _check_group(group)
|
||||
return comm.all_gather(
|
||||
group, input, output, use_comm_stream, algo=AllGatherAlgo.kNone
|
||||
)
|
||||
|
||||
|
||||
def gather(
|
||||
tensor,
|
||||
gather_list=None,
|
||||
dst=0,
|
||||
group: Optional[ProcessGroup] = None,
|
||||
async_op=False,
|
||||
use_comm_stream: bool = False,
|
||||
):
|
||||
"""
|
||||
Example:
|
||||
>>> # We have 2 process groups, 2 ranks.
|
||||
>>> tensor = torch.tensor(rank+1,dtype=torch.float32).cuda()
|
||||
>>> tensor
|
||||
tensor(1.) # Rank 0
|
||||
tensor(2.) # Rank 1
|
||||
>>> gather_list = [torch.zeros(1).cuda() for _ in range(rank)] if rank == dst else None
|
||||
>>> gather_list
|
||||
[tensor([0,]),tensor([1,])] # Rank 0
|
||||
None # Rank 1
|
||||
ixfd.gather(tensor,gather_list,0,async_op=True)
|
||||
>>> gather_list
|
||||
[tensor([1.]),tensor([2.])] # Rank 0
|
||||
None # Rank 1
|
||||
"""
|
||||
gather_list = gather_list if gather_list is not None else []
|
||||
if not async_op:
|
||||
raise RuntimeError("Not support sync operation now.")
|
||||
|
||||
dst = get_group_rank(group, dst)
|
||||
group = _check_group(group)
|
||||
return comm.gather(group, tensor, gather_list, dst, use_comm_stream)
|
||||
412
ixformer_sdk/distributed/overlap_comm.py
Normal file
412
ixformer_sdk/distributed/overlap_comm.py
Normal file
@@ -0,0 +1,412 @@
|
||||
import abc
|
||||
import enum
|
||||
import os
|
||||
from contextlib import contextmanager, nullcontext
|
||||
from typing import List, Optional
|
||||
|
||||
import torch.cuda
|
||||
from ixformer.core.dispatcher import Dispatcher
|
||||
|
||||
from ixformer.core import config
|
||||
|
||||
from . import _distributed as ixfd
|
||||
|
||||
|
||||
class SplitOverlapComm(Dispatcher):
|
||||
def __init__(self, num_chunks, num_compute_streams=None, comm_group=None):
|
||||
"""
|
||||
Args:
|
||||
num_chunks: the number of chunks
|
||||
num_compute_streams: the number of compute streams, default: 1
|
||||
comm_group: communicator group
|
||||
"""
|
||||
|
||||
self._num_chunks = num_chunks
|
||||
self._num_compute_streams = num_compute_streams or 1
|
||||
self._comm_group = comm_group
|
||||
|
||||
self._compute_streams: List[torch.cuda.Stream] = self.create_compute_streams()
|
||||
self._comm_stream: torch.cuda.Stream = torch.cuda.Stream(priority=-1)
|
||||
|
||||
self._start_compute_event: torch.cuda.Event = torch.cuda.Event()
|
||||
self._stop_compute_event: torch.cuda.Event = torch.cuda.Event()
|
||||
|
||||
self._start_comm_event: torch.cuda.Event = torch.cuda.Event()
|
||||
self._stop_comm_event: torch.cuda.Event = torch.cuda.Event()
|
||||
|
||||
# keep origin state
|
||||
self._main_stream: Optional[torch.cuda.Stream] = None
|
||||
self._origin_ixf_comm_stream = None
|
||||
self._ixformer_streams = dict()
|
||||
|
||||
@classmethod
|
||||
def dispatcher_key(
|
||||
cls, num_chunks, num_compute_streams=None, comm_group=None, *args, **kwargs
|
||||
):
|
||||
"""
|
||||
the key of SplitOverlapComm
|
||||
Args:
|
||||
num_chunks: the number of chunks
|
||||
num_compute_streams: the number of compute streams, default: 1
|
||||
comm_group: communicator group
|
||||
Returns: unique key
|
||||
"""
|
||||
# warn: keey same function parameters with init
|
||||
return (cls.__name__, num_chunks, num_compute_streams, comm_group)
|
||||
|
||||
@classmethod
|
||||
def enable(cls):
|
||||
return config.IXFORMER_ENABLE_OVERLAP_COMM
|
||||
|
||||
@property
|
||||
def num_chunks(self):
|
||||
return self._num_chunks
|
||||
|
||||
@property
|
||||
def num_compute_streams(self):
|
||||
return self._num_compute_streams
|
||||
|
||||
@property
|
||||
def comm_group(self):
|
||||
return self._comm_group
|
||||
|
||||
def create_compute_streams(self):
|
||||
streams = []
|
||||
for _ in range(self.num_compute_streams):
|
||||
streams.append(torch.cuda.Stream())
|
||||
return streams
|
||||
|
||||
def start_overlap(self):
|
||||
self._main_stream = torch.cuda.current_stream()
|
||||
|
||||
self._start_compute_event.record(torch.cuda.current_stream())
|
||||
for compute_stream in self._compute_streams:
|
||||
compute_stream.wait_event(self._start_compute_event)
|
||||
|
||||
self._origin_ixf_comm_stream = ixfd.get_comm_group_stream(self._comm_group)
|
||||
ixfd.set_comm_group_stream(self._comm_stream.cuda_stream, self._comm_group)
|
||||
|
||||
def stop_overlap(self):
|
||||
last_compute_stream_id = (
|
||||
self.num_chunks + self.num_compute_streams - 1
|
||||
) % self.num_compute_streams
|
||||
self._stop_compute_event.record(self._compute_streams[last_compute_stream_id])
|
||||
self._stop_comm_event.record(self._comm_stream)
|
||||
torch.cuda.current_stream().wait_event(self._stop_compute_event)
|
||||
torch.cuda.current_stream().wait_event(self._stop_comm_event)
|
||||
|
||||
ixfd.set_comm_group_stream(self._origin_ixf_comm_stream, self._comm_group)
|
||||
|
||||
def start_comm(self, chunk_idx):
|
||||
"""
|
||||
prepare communication stream and wait event.
|
||||
Args:
|
||||
chunk_idx: the index of chunk
|
||||
"""
|
||||
|
||||
self._start_comm_event.record(
|
||||
self._compute_streams[chunk_idx % self.num_compute_streams]
|
||||
)
|
||||
self._comm_stream.wait_event(self._start_comm_event)
|
||||
|
||||
@contextmanager
|
||||
def compute_stream_context(self, chunk_idx):
|
||||
"""
|
||||
open python context and switch to compute stream in torch context
|
||||
Args:
|
||||
chunk_idx: the index of chunk
|
||||
"""
|
||||
|
||||
stream = self._compute_streams[chunk_idx % self.num_compute_streams]
|
||||
|
||||
# print("before stream:", torch.cuda.current_stream())
|
||||
torch.cuda.set_stream(stream)
|
||||
|
||||
# print("after stream:", torch.cuda.current_stream(), ixformer.cuda.current_stream())
|
||||
yield stream
|
||||
|
||||
torch.cuda.set_stream(self._main_stream)
|
||||
|
||||
@contextmanager
|
||||
def stream_context(self, stream):
|
||||
# print("before stream:", torch.cuda.current_stream())
|
||||
torch.cuda.set_stream(stream)
|
||||
|
||||
# print("after stream:", torch.cuda.current_stream(), ixformer.cuda.current_stream())
|
||||
yield stream
|
||||
|
||||
torch.cuda.set_stream(self._main_stream)
|
||||
|
||||
def forward(self, *args, **kwargs):
|
||||
self.start_overlap()
|
||||
out = self.compute(*args, **kwargs)
|
||||
self.stop_overlap()
|
||||
return out
|
||||
|
||||
@abc.abstractmethod
|
||||
def compute(self, *args, **kwargs):
|
||||
"""
|
||||
it is abstract method to execute compute and communication.
|
||||
"""
|
||||
pass
|
||||
|
||||
|
||||
class GemmMethod(enum.IntEnum):
|
||||
kCUINFER = 0
|
||||
kCUBLAS = 1
|
||||
kLIMITED_GEMM = 2
|
||||
|
||||
|
||||
class GemmWithLimitedBlock:
|
||||
def __init__(self, limit_algo=0) -> None:
|
||||
self.limit_algo = limit_algo
|
||||
self.env_key = "PYTORCH_GEMM_BLOCK_LIMITATION"
|
||||
|
||||
def __enter__(self) -> None:
|
||||
os.environ[self.env_key] = str(self.limit_algo)
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback) -> None:
|
||||
del os.environ[self.env_key]
|
||||
|
||||
|
||||
class IxFormerLimitedGemmContext:
|
||||
def __init__(self) -> None:
|
||||
self.env_key = "IXFORMER_ENABLE_PERSISTENT_GEMM"
|
||||
|
||||
def __enter__(self) -> None:
|
||||
os.environ[self.env_key] = "1"
|
||||
|
||||
def __exit__(self, exc_type, exc_value, traceback) -> None:
|
||||
os.environ[self.env_key] = "0"
|
||||
|
||||
|
||||
class GemmAllReduceSplitOverlapComm(SplitOverlapComm):
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.gemm_method_env = config.IXFORMER_OVERLAP_GEMM_METHOD
|
||||
|
||||
if self.gemm_method_env is None:
|
||||
if ixfd.get_world_size(self.comm_group) == 2:
|
||||
self.gemm_method_env = 0
|
||||
else:
|
||||
self.gemm_method_env = 2
|
||||
|
||||
self.gemm_method = GemmMethod(int(self.gemm_method_env))
|
||||
self.limited_gemm_ctx = GemmWithLimitedBlock()
|
||||
self.ixf_limited_gemm_ctx = IxFormerLimitedGemmContext()
|
||||
self.split_ratio = config.IXFORMER_OVERLAP_SPLIT_RATIO
|
||||
|
||||
@classmethod
|
||||
def compute_row_parallel_dims(cls, input):
|
||||
batch = 1
|
||||
if input.ndim == 2:
|
||||
seqlen = input.shape[0]
|
||||
else:
|
||||
batch = input.shape[0]
|
||||
seqlen = input.shape[1]
|
||||
|
||||
parallel_dims = batch * seqlen
|
||||
return parallel_dims
|
||||
|
||||
def compute(self, input, weight, bias=None, out=None, *args, **kwargs):
|
||||
"""
|
||||
:param input: [Batch, SeqLen, Hidden]
|
||||
:param weight: [OutChannel, InChannel]
|
||||
:param bias: [OutChannel]
|
||||
"""
|
||||
|
||||
is_update_shape = input.ndim > 2
|
||||
batch = 1
|
||||
if input.ndim == 2:
|
||||
seqlen = input.shape[0]
|
||||
else:
|
||||
batch = input.shape[0]
|
||||
seqlen = input.shape[1]
|
||||
|
||||
parallel_dims = batch * seqlen
|
||||
|
||||
if is_update_shape:
|
||||
input = input.reshape(parallel_dims, -1)
|
||||
|
||||
if out is None:
|
||||
out_shape = [parallel_dims, weight.shape[0]]
|
||||
out_dtype = kwargs["out_dtype"] if "out_dtype" in kwargs else input.dtype
|
||||
out = torch.empty(out_shape, dtype=out_dtype, device=input.device)
|
||||
|
||||
if self.split_ratio is not None:
|
||||
round_multiples = 256 if parallel_dims >= 256 else parallel_dims
|
||||
first_chunk_size = (
|
||||
round((parallel_dims * float(self.split_ratio)) / round_multiples)
|
||||
* round_multiples
|
||||
)
|
||||
middle_chunk_size = (parallel_dims - first_chunk_size) // (
|
||||
self.num_chunks - 1
|
||||
)
|
||||
middle_chunk_size = (middle_chunk_size // round_multiples) * round_multiples
|
||||
last_chunk_size = (
|
||||
parallel_dims
|
||||
- first_chunk_size
|
||||
- middle_chunk_size * (self.num_chunks - 2)
|
||||
)
|
||||
|
||||
chunk_sizes = (
|
||||
[first_chunk_size]
|
||||
+ [middle_chunk_size] * (self.num_chunks - 2)
|
||||
+ [last_chunk_size]
|
||||
)
|
||||
input_chunks = torch.split_with_sizes(input, chunk_sizes, dim=0)
|
||||
out_chunks = torch.split_with_sizes(out, chunk_sizes, dim=0)
|
||||
|
||||
# print(first_chunk_size, middle_chunk_size, last_chunk_size, chunk_sizes)
|
||||
else:
|
||||
input_chunks = torch.chunk(input, self.num_chunks, dim=0)
|
||||
out_chunks = torch.chunk(out, self.num_chunks, dim=0)
|
||||
|
||||
for chunk_idx in range(len(input_chunks)):
|
||||
with self.compute_stream_context(chunk_idx):
|
||||
chunk_out = self.gemm_dispatcher(
|
||||
chunk_idx,
|
||||
input_chunks[chunk_idx],
|
||||
weight,
|
||||
out_chunks[chunk_idx],
|
||||
*args,
|
||||
**kwargs,
|
||||
)
|
||||
|
||||
self.start_comm(chunk_idx)
|
||||
|
||||
ixfd.all_reduce(
|
||||
chunk_out, async_op=True, group=self.comm_group, use_comm_stream=True
|
||||
)
|
||||
|
||||
if is_update_shape:
|
||||
out = out.reshape(batch, seqlen, -1)
|
||||
|
||||
if bias is not None:
|
||||
out = out + bias
|
||||
|
||||
return out
|
||||
|
||||
def gemm_dispatcher(
|
||||
self,
|
||||
chunk_idx,
|
||||
chunk_input,
|
||||
weight,
|
||||
chunk_out=None,
|
||||
user_gemm_method=None,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
if user_gemm_method is not None and callable(user_gemm_method):
|
||||
ctx = nullcontext() if chunk_idx == 0 else self.ixf_limited_gemm_ctx
|
||||
with ctx:
|
||||
return user_gemm_method(
|
||||
chunk_input, weight, out=chunk_out, *args, **kwargs
|
||||
)
|
||||
|
||||
if user_gemm_method is None:
|
||||
user_gemm_method = self.gemm_method
|
||||
|
||||
if user_gemm_method == GemmMethod.kCUINFER:
|
||||
import ixformer.functions as ixff
|
||||
|
||||
return ixff.linear(chunk_input, weight, output=chunk_out)
|
||||
elif user_gemm_method == GemmMethod.kCUBLAS:
|
||||
return torch.matmul(chunk_input, weight.T, out=chunk_out)
|
||||
elif user_gemm_method == GemmMethod.kLIMITED_GEMM:
|
||||
ctx = self.limited_gemm_ctx
|
||||
with ctx:
|
||||
return torch.matmul(chunk_input, weight.T, out=chunk_out)
|
||||
elif user_gemm_method == GemmMethod.kCUBLAS:
|
||||
return torch.matmul(chunk_input, weight.T, out=chunk_out)
|
||||
else:
|
||||
raise RuntimeError(f"Invalid gemm method, got {self.gemm_method}.")
|
||||
|
||||
@classmethod
|
||||
def native_forward(
|
||||
cls,
|
||||
input,
|
||||
weight,
|
||||
bias=None,
|
||||
out=None,
|
||||
group=None,
|
||||
user_gemm_method=None,
|
||||
*args,
|
||||
**kwargs,
|
||||
):
|
||||
if user_gemm_method is not None and callable(user_gemm_method):
|
||||
gemm_out = user_gemm_method(
|
||||
input, weight, bias=bias, out=out, *args, **kwargs
|
||||
)
|
||||
out = out if gemm_out is None else gemm_out
|
||||
else:
|
||||
import ixformer.functions as ixff
|
||||
|
||||
# warning: 下面的两种 gemm 可能存在精度不一致
|
||||
# out = torch.matmul(input, weight.T, out=out)
|
||||
out = ixff.linear(input=input, weight=weight, bias=bias, output=out)
|
||||
ixfd.all_reduce(out, async_op=True, group=group)
|
||||
return out
|
||||
|
||||
@classmethod
|
||||
def is_supported(cls, input, num_chunks, comm_group):
|
||||
if not cls.enable():
|
||||
return False
|
||||
|
||||
ndim = input.ndim
|
||||
shape = input.shape
|
||||
|
||||
if ndim == 1:
|
||||
m, k = 1, shape[0]
|
||||
elif ndim == 2:
|
||||
m, k = shape
|
||||
else:
|
||||
m, k = sum(shape[:-1]), shape[-1]
|
||||
|
||||
return m >= 512
|
||||
|
||||
|
||||
_DEFAULT_OVERLAP_GROUP = None
|
||||
_DEFAULT_OVERLAP_COMM_N2 = None
|
||||
_DEFAULT_OVERLAP_COMM_N4 = None
|
||||
_DEFAULT_OVERLAP_CHUNKS = config.IXFORMER_OVERLAP_CHUNKS
|
||||
|
||||
|
||||
def linear_allreduce_overlap(
|
||||
input, weight, bias=None, out=None, group=None, num_chunks=None, *args, **kwargs
|
||||
):
|
||||
num_chunks = num_chunks or _DEFAULT_OVERLAP_CHUNKS
|
||||
|
||||
# print("call overlap:", GemmAllReduceSplitOverlapComm.is_supported(input, num_chunks=num_chunks, comm_group=group), input.shape, weight.shape if torch.is_tensor(weight) else None, "WorldSize:", ixfd.get_group_world_size(group), ", NumChunks:", num_chunks)
|
||||
if not GemmAllReduceSplitOverlapComm.is_supported(
|
||||
input, num_chunks=num_chunks, comm_group=group
|
||||
):
|
||||
return GemmAllReduceSplitOverlapComm.native_forward(
|
||||
input, weight, bias=bias, out=out, group=group, *args, **kwargs
|
||||
)
|
||||
|
||||
global _DEFAULT_OVERLAP_GROUP
|
||||
global _DEFAULT_OVERLAP_COMM_N2
|
||||
global _DEFAULT_OVERLAP_COMM_N4
|
||||
|
||||
if _DEFAULT_OVERLAP_GROUP is None:
|
||||
_DEFAULT_OVERLAP_GROUP = group
|
||||
|
||||
if num_chunks == 2 and group == _DEFAULT_OVERLAP_GROUP:
|
||||
if _DEFAULT_OVERLAP_COMM_N2 is None:
|
||||
_DEFAULT_OVERLAP_COMM_N2 = GemmAllReduceSplitOverlapComm.dispatcher(
|
||||
num_chunks=num_chunks, comm_group=group
|
||||
)
|
||||
overlap_comm = _DEFAULT_OVERLAP_COMM_N2
|
||||
elif num_chunks == 4 and group == _DEFAULT_OVERLAP_GROUP:
|
||||
if _DEFAULT_OVERLAP_COMM_N4 is None:
|
||||
_DEFAULT_OVERLAP_COMM_N4 = GemmAllReduceSplitOverlapComm.dispatcher(
|
||||
num_chunks=num_chunks, comm_group=group
|
||||
)
|
||||
overlap_comm = _DEFAULT_OVERLAP_COMM_N4
|
||||
else:
|
||||
overlap_comm = GemmAllReduceSplitOverlapComm.dispatcher(
|
||||
num_chunks=num_chunks, comm_group=group
|
||||
)
|
||||
return overlap_comm.forward(input, weight, bias=bias, out=out, *args, **kwargs)
|
||||
1
ixformer_sdk/functions/__init__.py
Normal file
1
ixformer_sdk/functions/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from ..inference.functions import *
|
||||
0
ixformer_sdk/inference/__init__.py
Normal file
0
ixformer_sdk/inference/__init__.py
Normal file
1
ixformer_sdk/inference/distributed/__init__.py
Normal file
1
ixformer_sdk/inference/distributed/__init__.py
Normal file
@@ -0,0 +1 @@
|
||||
from .mpi_utils import *
|
||||
21
ixformer_sdk/inference/distributed/mpi_utils.py
Normal file
21
ixformer_sdk/inference/distributed/mpi_utils.py
Normal file
@@ -0,0 +1,21 @@
|
||||
import os
|
||||
|
||||
from mpi4py import MPI
|
||||
|
||||
|
||||
def get_world_size(comm=None):
|
||||
if comm is None:
|
||||
comm = MPI.COMM_WORLD
|
||||
|
||||
return comm.Get_size()
|
||||
|
||||
|
||||
def get_local_rank(comm=None):
|
||||
return int(os.environ["OMPI_COMM_WORLD_LOCAL_RANK"])
|
||||
|
||||
|
||||
def get_rank(comm=None):
|
||||
if comm is None:
|
||||
comm = MPI.COMM_WORLD
|
||||
|
||||
return comm.Get_rank()
|
||||
44
ixformer_sdk/inference/functions/__init__.py
Normal file
44
ixformer_sdk/inference/functions/__init__.py
Normal file
@@ -0,0 +1,44 @@
|
||||
from .act_and_mul import *
|
||||
from .act_bias_mm import *
|
||||
from .add import *
|
||||
from .bert import *
|
||||
from .bnb_dequant import *
|
||||
from .bnb_double_quant import *
|
||||
from .bnb_mm_dequant import *
|
||||
from .bnb_qgemm import *
|
||||
from .bnb_quant import *
|
||||
from .bnb_rowcol_absmax import *
|
||||
from .conv2d import *
|
||||
from .cross_entropy_loss import *
|
||||
from .flash_attn import *
|
||||
from .flash_attn_lib import *
|
||||
from .fused_rope import *
|
||||
from .gemv import *
|
||||
from .groupnorm import *
|
||||
from .i8w8o32 import *
|
||||
from .layernorm import *
|
||||
from .lightllm import *
|
||||
from .linalg import *
|
||||
from .linear import *
|
||||
from .lmdeploy import *
|
||||
from .marlin import *
|
||||
from .matmul import *
|
||||
from .mla_fused import *
|
||||
from .mm import *
|
||||
from .moe import *
|
||||
from .overlap_comm import *
|
||||
from .paged_attention import *
|
||||
from .quantized_linear import *
|
||||
from .residual_bias import *
|
||||
from .rms_norm import *
|
||||
from .scaled_dot_product_attention import *
|
||||
from .smoothquant import *
|
||||
from .softmax import *
|
||||
from .store_kv_cache import *
|
||||
from .t5 import *
|
||||
from .tgi import *
|
||||
from .vllm import *
|
||||
from .w8a8 import *
|
||||
from .w8a16 import *
|
||||
from .wi4a16 import *
|
||||
from .wui4a16 import *
|
||||
88
ixformer_sdk/inference/functions/act_and_mul.py
Normal file
88
ixformer_sdk/inference/functions/act_and_mul.py
Normal file
@@ -0,0 +1,88 @@
|
||||
from typing import List, Union
|
||||
|
||||
import ixformer._C as ops
|
||||
import torch
|
||||
import torch.nn.functional as NNF
|
||||
|
||||
__all__ = ["ref_silu_and_mul", "ref_gelu_and_mul", "ref_gelu_tanh_and_mul",
|
||||
"silu_and_mul", "gelu_and_mul", "gelu_tanh_and_mul"]
|
||||
|
||||
|
||||
def ref_silu_and_mul(input: "torch.Tensor") -> torch.Tensor:
|
||||
x1, x2 = input.chunk(chunks=2, dim=-1)
|
||||
res = NNF.silu(x1) * x2
|
||||
return res
|
||||
|
||||
|
||||
def ref_gelu_and_mul(input: "torch.Tensor", gate_first=True) -> torch.Tensor:
|
||||
x1, x2 = input.chunk(chunks=2, dim=-1)
|
||||
if gate_first:
|
||||
res = NNF.gelu(x1) * x2
|
||||
else:
|
||||
res = NNF.gelu(x2) * x1
|
||||
return res
|
||||
|
||||
|
||||
def ref_gelu_tanh_and_mul(input: "torch.Tensor") -> torch.Tensor:
|
||||
x1, x2 = input.chunk(chunks=2, dim=-1)
|
||||
res = NNF.gelu(x1) * x2
|
||||
return res
|
||||
|
||||
|
||||
def silu_and_mul(input: torch.Tensor, output: torch.Tensor = None):
|
||||
|
||||
"""
|
||||
Args:
|
||||
input: (..., 2*hidden_size) torch.float16, torch.bfloat16, torch.float32
|
||||
output: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
|
||||
|
||||
Returns:
|
||||
output: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
|
||||
"""
|
||||
if output is None:
|
||||
output_shape = list(input.shape)
|
||||
output_shape[-1] = output_shape[-1] // 2
|
||||
output = input.new_empty(output_shape)
|
||||
|
||||
ops.infer.silu_and_mul(input, output)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def gelu_and_mul(input: "torch.Tensor", output: torch.Tensor = None, gate_first=True):
|
||||
|
||||
"""
|
||||
Args:
|
||||
input: (..., 2*hidden_size) torch.float16, torch.bfloat16, torch.float32
|
||||
output: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
|
||||
gate_first: bool
|
||||
Returns:
|
||||
output: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
|
||||
"""
|
||||
if output is None:
|
||||
output_shape = list(input.shape)
|
||||
output_shape[-1] = output_shape[-1] // 2
|
||||
output = input.new_empty(output_shape)
|
||||
|
||||
ops.infer.gelu_and_mul(input, output, gate_first)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def gelu_tanh_and_mul(input: torch.Tensor, output: torch.Tensor = None):
|
||||
|
||||
"""
|
||||
Args:
|
||||
input: (..., 2*hidden_size) torch.float16, torch.bfloat16, torch.float32
|
||||
output: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
|
||||
Returns:
|
||||
output: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
|
||||
"""
|
||||
if output is None:
|
||||
output_shape = list(input.shape)
|
||||
output_shape[-1] = output_shape[-1] // 2
|
||||
output = input.new_empty(output_shape)
|
||||
|
||||
ops.infer.gelu_tanh_and_mul(input, output)
|
||||
|
||||
return output
|
||||
89
ixformer_sdk/inference/functions/act_bias_mm.py
Normal file
89
ixformer_sdk/inference/functions/act_bias_mm.py
Normal file
@@ -0,0 +1,89 @@
|
||||
import ixformer._C as ops
|
||||
import torch
|
||||
import torch.nn.functional as NNF
|
||||
|
||||
__all__ = ["act_bias_mm", "ref_act_bias_mm"]
|
||||
|
||||
|
||||
def ref_act_bias_mm(
|
||||
mat1: torch.Tensor,
|
||||
mat2: torch.Tensor,
|
||||
bias: torch.Tensor = None,
|
||||
scale: float = 1,
|
||||
act_type: str = "none",
|
||||
trans_format: str = "NN",
|
||||
):
|
||||
assert len(mat1.shape) >= 2
|
||||
assert len(mat2.shape) >= 2
|
||||
if trans_format == "NN":
|
||||
if bias is not None:
|
||||
output = torch.matmul(mat1, mat2) * scale + bias
|
||||
else:
|
||||
output = torch.matmul(mat1, mat2) * scale
|
||||
else:
|
||||
if bias is not None:
|
||||
output = torch.matmul(mat1, mat2.transpose(-1, -2)) * scale + bias
|
||||
else:
|
||||
output = torch.matmul(mat1, mat2.transpose(-1, -2)) * scale
|
||||
if act_type == "gelu":
|
||||
output = NNF.gelu(output)
|
||||
elif act_type == "relu":
|
||||
output = NNF.relu(output)
|
||||
elif act_type == "silu":
|
||||
output = NNF.silu(output)
|
||||
elif act_type == "none":
|
||||
output = output
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
return output
|
||||
|
||||
|
||||
def act_bias_mm(
|
||||
mat1: torch.Tensor,
|
||||
mat2: torch.Tensor,
|
||||
bias: torch.Tensor = None,
|
||||
output: torch.Tensor = None,
|
||||
scale: float = 1,
|
||||
act_type: str = "none",
|
||||
trans_format: str = "NN",
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
mat1: [m,k] or [batch_count,m,k] torch.float16
|
||||
mat2: [k,n] or [n,k] torch.float16
|
||||
当trans_format为"NN"时[k,n], 当trans_format为"TN"时[n,k]
|
||||
bias: [n] torch.float16
|
||||
output: [m,n] torch.float16
|
||||
scale: float
|
||||
act_type: silu/gelu/relu/None str
|
||||
如果act_type不为None,则bias也不可以为None
|
||||
trans_format: NN or TN str
|
||||
Returns:
|
||||
output: [m,n] torch.float16
|
||||
"""
|
||||
assert len(mat1.shape) >= 2
|
||||
assert len(mat2.shape) >= 2
|
||||
if output is None:
|
||||
output_shape = list(mat1.shape)
|
||||
m = mat1.shape[-2]
|
||||
if trans_format == "NN":
|
||||
n = mat2.shape[-1]
|
||||
else:
|
||||
n = mat2.shape[-2]
|
||||
output_shape[-2] = m
|
||||
output_shape[-1] = n
|
||||
output = mat1.new_empty(output_shape)
|
||||
|
||||
add_bias = False
|
||||
if bias is not None:
|
||||
add_bias = True
|
||||
|
||||
if add_bias:
|
||||
ops.infer.act_bias_mm(
|
||||
mat1, mat2, bias, output, add_bias, scale, act_type, trans_format
|
||||
)
|
||||
else:
|
||||
ops.infer.act_bias_mm(
|
||||
mat1, mat2, mat1, output, add_bias, scale, act_type, trans_format
|
||||
)
|
||||
return output
|
||||
46
ixformer_sdk/inference/functions/add.py
Normal file
46
ixformer_sdk/inference/functions/add.py
Normal file
@@ -0,0 +1,46 @@
|
||||
import ixformer._C as ops
|
||||
import torch
|
||||
|
||||
__all__ = [
|
||||
"ref_add",
|
||||
"add",
|
||||
]
|
||||
|
||||
|
||||
def ref_add(input: torch.Tensor, other: torch.Tensor, out: torch.Tensor = None):
|
||||
return torch.add(input, other, out=out)
|
||||
|
||||
|
||||
def add(input: torch.Tensor, other: torch.Tensor, out: torch.Tensor = None):
|
||||
"""
|
||||
out = input + other
|
||||
Support elementwise addition, but broadcasting is not supported yet.
|
||||
Note: The dtype of input and other needs to be the same.
|
||||
Args:
|
||||
input: (...) torch.float32, torch.float16, torch.bfloat16
|
||||
other: (...) same as input
|
||||
out: (...) same as input
|
||||
Returns:
|
||||
out: (...) same as input
|
||||
"""
|
||||
if input.dtype not in [torch.float16, torch.float32, torch.bfloat16]:
|
||||
return torch.add(input, other, out=out)
|
||||
if not input.is_contiguous() or not other.is_contiguous():
|
||||
return torch.add(input, other, out=out)
|
||||
if out is not None and not out.is_contiguous():
|
||||
return torch.add(input, other, out=out)
|
||||
|
||||
if input.dtype != other.dtype:
|
||||
return torch.add(input, other, out=out)
|
||||
if out is not None and out.dtype != input.dtype:
|
||||
return torch.add(input, other, out=out)
|
||||
|
||||
assert input.shape == other.shape, (f"broadcasting is not supported yet."
|
||||
"input is {input.shape}, other is {other.shape}")
|
||||
|
||||
if out is None:
|
||||
out = torch.empty_like(input)
|
||||
|
||||
ops.infer.add(input, other, out)
|
||||
|
||||
return out
|
||||
199
ixformer_sdk/inference/functions/bert.py
Normal file
199
ixformer_sdk/inference/functions/bert.py
Normal file
@@ -0,0 +1,199 @@
|
||||
import ixformer._C as ops
|
||||
import torch
|
||||
|
||||
__all__ = [
|
||||
"ref_bert_embedding",
|
||||
"bert_embedding",
|
||||
"ref_bert_add_norm",
|
||||
"bert_add_norm",
|
||||
"ref_bert_unpack_start_end_logits",
|
||||
"bert_unpack_start_end_logits",
|
||||
"ref_bert_linear_residual",
|
||||
"bert_linear_residual",
|
||||
]
|
||||
|
||||
|
||||
def ref_bert_embedding(
|
||||
token_weight: torch.Tensor,
|
||||
pos_weight: torch.Tensor,
|
||||
type_weight: torch.Tensor,
|
||||
ln_weight: torch.Tensor,
|
||||
ln_bias: torch.Tensor,
|
||||
token_ids: torch.Tensor,
|
||||
pos_ids: torch.Tensor,
|
||||
type_ids: torch.Tensor,
|
||||
epsilon: float = 1e-5,
|
||||
out: torch.Tensor = None,
|
||||
):
|
||||
assert out is None
|
||||
emd1 = torch.nn.functional.embedding(token_ids, token_weight)
|
||||
emd2 = torch.nn.functional.embedding(pos_ids, pos_weight)
|
||||
emd3 = torch.nn.functional.embedding(type_ids, type_weight)
|
||||
|
||||
out = emd1 + emd2 + emd3
|
||||
out = torch.nn.functional.layer_norm(out, [out.shape[-1]], ln_weight, ln_bias)
|
||||
return out
|
||||
|
||||
|
||||
def bert_embedding(
|
||||
token_weight: torch.Tensor,
|
||||
pos_weight: torch.Tensor,
|
||||
type_weight: torch.Tensor,
|
||||
ln_weight: torch.Tensor,
|
||||
ln_bias: torch.Tensor,
|
||||
token_ids: torch.Tensor,
|
||||
pos_ids: torch.Tensor,
|
||||
type_ids: torch.Tensor,
|
||||
epsilon: float = 1e-5,
|
||||
out: torch.Tensor = None,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
token_weight: (vocab_size, hidden_size) torch.float16, torch.bfloat16
|
||||
pos_weight: (pos_size, hidden_size) same as token_weight
|
||||
type_weight: (type_size, hidden_size) same as token_weight
|
||||
ln_weight: (hidden_size) same as token_weight
|
||||
ln_bias: (hidden_size) same as token_weight
|
||||
token_ids: (num_tokens) torch.int32, torch.int64
|
||||
pos_ids: (num_tokens) same as token_ids
|
||||
type_ids: (num_tokens) same as token_ids
|
||||
epsilon: float
|
||||
out: (num_tokens, hidden_size) same as token_weight
|
||||
Returns:
|
||||
out: (num_tokens, hidden_size) same as token_weight
|
||||
"""
|
||||
if out is None:
|
||||
out_shape = list(token_ids.shape)
|
||||
hidden_size = token_weight.shape[-1]
|
||||
out_shape.append(hidden_size)
|
||||
out = token_weight.new_empty(out_shape)
|
||||
|
||||
ops.infer.bert_embedding(
|
||||
token_weight,
|
||||
pos_weight,
|
||||
type_weight,
|
||||
ln_weight,
|
||||
ln_bias,
|
||||
token_ids,
|
||||
pos_ids,
|
||||
type_ids,
|
||||
out,
|
||||
epsilon,
|
||||
)
|
||||
|
||||
return out
|
||||
|
||||
|
||||
def ref_bert_add_norm(
|
||||
input: torch.Tensor,
|
||||
residual: torch.Tensor,
|
||||
ln_weight: torch.Tensor,
|
||||
ln_bias: torch.Tensor,
|
||||
epsilon: float = 1e-5,
|
||||
out: torch.Tensor = None,
|
||||
):
|
||||
assert out is None
|
||||
input = input + residual
|
||||
return torch.nn.functional.layer_norm(
|
||||
input, [input.shape[-1]], ln_weight, ln_bias, epsilon
|
||||
)
|
||||
|
||||
|
||||
def bert_add_norm(
|
||||
input: torch.Tensor,
|
||||
residual: torch.Tensor,
|
||||
ln_weight: torch.Tensor,
|
||||
ln_bias: torch.Tensor,
|
||||
epsilon: float = 1e-5,
|
||||
out: torch.Tensor = None,
|
||||
):
|
||||
"""
|
||||
out = input + residual
|
||||
out = add_norm(out, ln_weight, ln_bias, epsilon)
|
||||
Args:
|
||||
input: (num_tokens, hidden_size) torch.float16, torch.bfloat16
|
||||
residual: (num_tokens, hidden_size) same as input
|
||||
ln_weight: (hidden_size) same as input
|
||||
ln_bias: (hidden_size) same as input
|
||||
epsilon: float
|
||||
out: (num_tokens, hidden_size) same as input
|
||||
Returns:
|
||||
out: (num_tokens, hidden_size) same as input
|
||||
"""
|
||||
if out is None:
|
||||
out = torch.empty_like(input)
|
||||
ops.infer.bert_add_norm(input, residual, ln_weight, ln_bias, out, epsilon)
|
||||
return out
|
||||
|
||||
|
||||
def ref_bert_unpack_start_end_logits(
|
||||
logits: torch.Tensor,
|
||||
cu_seq_lens: torch.Tensor,
|
||||
max_seq_len: int,
|
||||
start_logits: torch.Tensor = None,
|
||||
end_logits: torch.Tensor = None,
|
||||
):
|
||||
batch_size = cu_seq_lens.shape[0] - 1
|
||||
if start_logits is None:
|
||||
start_logits = logits.new_empty([batch_size, max_seq_len])
|
||||
if end_logits is None:
|
||||
end_logits = logits.new_empty([batch_size, max_seq_len])
|
||||
cu_seq_len_cpu = cu_seq_lens.detach().cpu()
|
||||
for i in range(batch_size):
|
||||
start_idx = cu_seq_len_cpu[i]
|
||||
end_idx = cu_seq_len_cpu[i + 1]
|
||||
cur_len = end_idx - start_idx
|
||||
start_logits[i, :cur_len] = logits[start_idx:end_idx, 0]
|
||||
end_logits[i, :cur_len] = logits[start_idx:end_idx, 1]
|
||||
return start_logits, end_logits
|
||||
|
||||
|
||||
def bert_unpack_start_end_logits(
|
||||
logits: torch.Tensor,
|
||||
cu_seq_lens: torch.Tensor,
|
||||
max_seq_len: int,
|
||||
start_logits: torch.Tensor = None,
|
||||
end_logits: torch.Tensor = None,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
logits: (num_tokens, 2) torch.float16, torch.bfloat16
|
||||
cu_seq_lens: (batch_size+1) torch.int32, torch.int64
|
||||
max_seq_len: int
|
||||
start_logits: (batch_size, max_seq_len) same as logits
|
||||
end_logits: (batch_size, max_seq_len) same as logits
|
||||
Returns:
|
||||
start_logits: (batch_size, max_seq_len) same as logits
|
||||
end_logits: (batch_size, max_seq_len) same as logits
|
||||
"""
|
||||
batch_size = cu_seq_lens.shape[0] - 1
|
||||
if start_logits is None:
|
||||
start_logits = logits.new_empty([batch_size, max_seq_len])
|
||||
if end_logits is None:
|
||||
end_logits = logits.new_empty([batch_size, max_seq_len])
|
||||
ops.infer.bert_unpack_start_end_logits(
|
||||
logits, cu_seq_lens, start_logits, end_logits
|
||||
)
|
||||
return start_logits, end_logits
|
||||
|
||||
|
||||
def ref_bert_linear_residual(
|
||||
input: torch.Tensor, weight: torch.Tensor, bias: torch.Tensor, out: torch.Tensor
|
||||
):
|
||||
return torch.nn.functional.linear(input, weight, bias) + out
|
||||
|
||||
|
||||
def bert_linear_residual(
|
||||
input: torch.Tensor, weight: torch.Tensor, bias: torch.Tensor, out: torch.Tensor
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
input: (m, k) torch.float16, torch.bfloat16
|
||||
weight: (n, k) same as input
|
||||
bias: (n) same as input
|
||||
out: (m, n) same as input
|
||||
Returns:
|
||||
out: (m, n) same as input
|
||||
"""
|
||||
ops.infer.bert_linear_residual(input, weight, bias, out)
|
||||
return out
|
||||
55
ixformer_sdk/inference/functions/bnb_dequant.py
Normal file
55
ixformer_sdk/inference/functions/bnb_dequant.py
Normal file
@@ -0,0 +1,55 @@
|
||||
from typing import List, Union
|
||||
|
||||
import ixformer._C as ops
|
||||
import torch
|
||||
from torch.autograd.function import Function, FunctionCtx
|
||||
|
||||
__all__ = [
|
||||
"bnb_dequant",
|
||||
"ref_bnb_dequant",
|
||||
]
|
||||
|
||||
|
||||
def ref_bnb_dequant(
|
||||
qA: torch.Tensor,
|
||||
SA: torch.Tensor,
|
||||
training: bool = False,
|
||||
scale: float = 127.0,
|
||||
dequant_type: int = 0,
|
||||
):
|
||||
A = torch.empty(qA.shape, dtype = SA.dtype, device = SA.device)
|
||||
if dequant_type == 0:
|
||||
for i in range(qA.size(0)):
|
||||
A[i:] = qA[i:] * (SA[i].to(torch.float) / scale).to(SA.dtype)
|
||||
else:
|
||||
for i in range(qA.size(1)):
|
||||
A[:,i] = qA[:,i] * (SA[i].to(torch.float) / scale).to(SA.dtype)
|
||||
|
||||
return A
|
||||
|
||||
|
||||
def bnb_dequant(
|
||||
qA: torch.Tensor,
|
||||
SA: torch.Tensor,
|
||||
training: bool = False,
|
||||
scale: float = 127.0,
|
||||
dequant_type: int = 0,
|
||||
) -> torch.Tensor:
|
||||
|
||||
"""
|
||||
Args:
|
||||
qA: (row, col) torch.int8
|
||||
dequant input
|
||||
SA: (row) or (col) torch.half
|
||||
scale vector
|
||||
training: bool
|
||||
scale: float
|
||||
dequnt_type: int
|
||||
0 : every row shared a scale, SA shape : [row]
|
||||
1 : every col shared a scale, SA shape : [col]
|
||||
Returns:
|
||||
Tensor: (row, col) torch.half
|
||||
dequant output
|
||||
|
||||
"""
|
||||
return ops.infer.bnb_dequant(qA, SA, scale, dequant_type)
|
||||
175
ixformer_sdk/inference/functions/bnb_double_quant.py
Normal file
175
ixformer_sdk/inference/functions/bnb_double_quant.py
Normal file
@@ -0,0 +1,175 @@
|
||||
from typing import List, Union
|
||||
|
||||
import ixformer._C as ops
|
||||
from torch.autograd.function import Function, FunctionCtx
|
||||
|
||||
__all__ = ["bnb_double_quant"]
|
||||
|
||||
import ctypes as ct
|
||||
|
||||
import torch
|
||||
from torch import Tensor
|
||||
|
||||
|
||||
def get_ptr(A):
|
||||
if A is None:
|
||||
return None
|
||||
else:
|
||||
return ct.c_void_p(A.data.data_ptr())
|
||||
|
||||
|
||||
class COOSparseTensor:
|
||||
def __init__(self, rows, cols, nnz, rowidx, colidx, values):
|
||||
assert rowidx.dtype == torch.int
|
||||
assert colidx.dtype == torch.int
|
||||
assert values.dtype == torch.half
|
||||
assert values.numel() == nnz
|
||||
assert rowidx.numel() == nnz
|
||||
assert colidx.numel() == nnz
|
||||
|
||||
self.rows = rows
|
||||
self.cols = cols
|
||||
self.nnz = nnz
|
||||
self.rowidx = rowidx
|
||||
self.colidx = colidx
|
||||
self.values = values
|
||||
|
||||
|
||||
def coo_zeros(rows, cols, nnz, device, dtype=torch.half):
|
||||
rowidx = torch.full(size=(nnz,), fill_value=0, dtype=torch.int, device=device)
|
||||
|
||||
colidx = torch.full((nnz,), fill_value=0, dtype=torch.int, device=device)
|
||||
values = torch.full((nnz,), fill_value=0, dtype=dtype, device=device)
|
||||
return COOSparseTensor(rows, cols, nnz, rowidx, colidx, values)
|
||||
|
||||
|
||||
def get_colrow_absmax(
|
||||
A, row_stats=None, col_stats=None, nnz_block_ptr=None, threshold=0.0
|
||||
):
|
||||
cols = A.shape[-1]
|
||||
if len(A.shape) == 3:
|
||||
rows = A.shape[0] * A.shape[1]
|
||||
else:
|
||||
rows = A.shape[0]
|
||||
|
||||
col_tiles = (cols + 255) // 256
|
||||
tiled_rows = ((rows + 15) // 16) * 16
|
||||
if row_stats is None:
|
||||
row_stats = torch.full(
|
||||
size=(rows,), fill_value=-50000.0, dtype=torch.float, device=A.device
|
||||
)
|
||||
if col_stats is None:
|
||||
col_stats = torch.full(
|
||||
size=(cols,), fill_value=-50000.0, dtype=torch.float, device=A.device
|
||||
)
|
||||
|
||||
# if nnz_block_ptr is None and threshold > 0.0:
|
||||
nnz_block_ptr = torch.full(
|
||||
size=(tiled_rows * col_tiles + 1,),
|
||||
fill_value=0,
|
||||
dtype=torch.int,
|
||||
device=A.device,
|
||||
)
|
||||
|
||||
ops.infer.bnb_getColRowStats(
|
||||
A, row_stats, col_stats, nnz_block_ptr, threshold, rows, cols
|
||||
)
|
||||
|
||||
return row_stats, col_stats, nnz_block_ptr
|
||||
|
||||
|
||||
# A : quant input shape : [row, col] shape:torch.half
|
||||
def bnb_double_quant(
|
||||
A: torch.Tensor, training: bool = False, threshold: float = 0.0
|
||||
) -> torch.Tensor:
|
||||
|
||||
"""
|
||||
Args:
|
||||
A: (row, col) torch.float16
|
||||
quant input
|
||||
training: bool
|
||||
threshold: float
|
||||
abs of element exceeds threshold will be ignored
|
||||
Returns:
|
||||
out_row: (row, col) torch.int8
|
||||
out_col: (row, col) torch.int8
|
||||
row_stats (row) torch.float
|
||||
col_stats (col) torch.float
|
||||
coo_tensor
|
||||
"""
|
||||
|
||||
assert A.dtype == torch.half
|
||||
|
||||
cols = A.shape[-1]
|
||||
if len(A.shape) == 3:
|
||||
rows = A.shape[0] * A.shape[1]
|
||||
else:
|
||||
rows = A.shape[0]
|
||||
|
||||
row_stats, col_stats, nnz_row_ptr = get_colrow_absmax(A, threshold=threshold)
|
||||
|
||||
out_col = torch.full(size=A.shape, fill_value=0, dtype=torch.int8, device=A.device)
|
||||
out_row = torch.full(size=A.shape, fill_value=0, dtype=torch.int8, device=A.device)
|
||||
|
||||
coo_tensor = None
|
||||
if threshold > 0.0:
|
||||
nnz = nnz_row_ptr.cpu().numpy()[-1]
|
||||
if nnz > 0:
|
||||
coo_tensor = coo_zeros(A.shape[0], A.shape[1], nnz, A.device)
|
||||
|
||||
ops.infer.bnb_doubleRowColQuant(
|
||||
A,
|
||||
row_stats,
|
||||
col_stats,
|
||||
out_col,
|
||||
out_row,
|
||||
coo_tensor.rowidx,
|
||||
coo_tensor.colidx,
|
||||
coo_tensor.values,
|
||||
nnz_row_ptr,
|
||||
threshold,
|
||||
rows,
|
||||
cols,
|
||||
)
|
||||
val, idx = torch.sort(torch.Tensor(coo_tensor.rowidx.cpu().numpy()))
|
||||
coo_tensor.rowidx = val
|
||||
coo_tensor.colidx = torch.Tensor(coo_tensor.colidx.cpu().numpy())[idx].to(
|
||||
torch.int32
|
||||
)
|
||||
coo_tensor.values = torch.Tensor(coo_tensor.values.cpu().numpy())[idx].to(
|
||||
torch.half
|
||||
)
|
||||
# coo_tensor.colidx = coo_tensor.colidx[idx]
|
||||
# coo_tensor.values = coo_tensor.values[idx]
|
||||
else:
|
||||
ops.infer.bnb_doubleRowColQuant(
|
||||
A,
|
||||
row_stats,
|
||||
col_stats,
|
||||
out_col,
|
||||
out_row,
|
||||
out_row,
|
||||
out_row,
|
||||
out_row,
|
||||
out_row,
|
||||
0.0,
|
||||
rows,
|
||||
cols,
|
||||
)
|
||||
else:
|
||||
ops.infer.bnb_doubleRowColQuant(
|
||||
A,
|
||||
row_stats,
|
||||
col_stats,
|
||||
out_col,
|
||||
out_row,
|
||||
out_row,
|
||||
out_row,
|
||||
out_row,
|
||||
out_row,
|
||||
threshold,
|
||||
rows,
|
||||
cols,
|
||||
)
|
||||
|
||||
return out_row, out_col, row_stats, col_stats, coo_tensor
|
||||
73
ixformer_sdk/inference/functions/bnb_mm_dequant.py
Normal file
73
ixformer_sdk/inference/functions/bnb_mm_dequant.py
Normal file
@@ -0,0 +1,73 @@
|
||||
from typing import List, Union
|
||||
|
||||
import ixformer._C as ops
|
||||
import torch
|
||||
from torch.autograd.function import Function, FunctionCtx
|
||||
|
||||
__all__ = ["bnb_mm_dequant"]
|
||||
|
||||
|
||||
# A : quant input shape : [row, col] shape : torch.int
|
||||
def bnb_mm_dequant(
|
||||
A: torch.Tensor,
|
||||
quant_state: tuple,
|
||||
row_stats: torch.Tensor,
|
||||
col_stats: torch.Tensor,
|
||||
bias: torch.Tensor = None,
|
||||
add_bias: bool = False,
|
||||
training: bool = False,
|
||||
) -> torch.Tensor:
|
||||
|
||||
"""
|
||||
Args:
|
||||
A: (row, col) torch.int8
|
||||
quant_state: tuple
|
||||
row_stats: (row) torch.float
|
||||
col_stats: (col) torch.float
|
||||
bias: (col) torch.half
|
||||
add_bias: bool
|
||||
training: bool
|
||||
Returns:
|
||||
Tensor: (row, col) torch.half
|
||||
|
||||
"""
|
||||
|
||||
assert A.dtype == torch.int
|
||||
if bias is not None:
|
||||
add_bias = True
|
||||
print("bias.dtype:", bias.dtype)
|
||||
assert bias.dtype == torch.half
|
||||
else:
|
||||
bias = A
|
||||
out_shape = quant_state[0]
|
||||
if len(out_shape) == 3:
|
||||
out_shape = (out_shape[0] * out_shape[1], out_shape[2])
|
||||
out = torch.full(size=out_shape, fill_value=0, dtype=torch.half, device=A.device)
|
||||
new_row_stats = torch.full(
|
||||
size=(out_shape[0],), fill_value=0, dtype=torch.float, device=A.device
|
||||
)
|
||||
new_col_stats = torch.full(
|
||||
size=(out_shape[1],), fill_value=0, dtype=torch.float, device=A.device
|
||||
)
|
||||
|
||||
assert (
|
||||
new_row_stats.shape[0] == row_stats.shape[0]
|
||||
), f"{new_row_stats.shape} vs {row_stats.shape}"
|
||||
assert (
|
||||
new_col_stats.shape[0] == col_stats.shape[0]
|
||||
), f"{new_col_stats.shape} vs {col_stats.shape}"
|
||||
numRows = out_shape[0]
|
||||
numCols = out_shape[1]
|
||||
ops.infer.bnb_mm_dequant(
|
||||
A,
|
||||
row_stats,
|
||||
col_stats,
|
||||
out,
|
||||
new_row_stats,
|
||||
new_col_stats,
|
||||
numRows,
|
||||
numCols,
|
||||
add_bias,
|
||||
bias,
|
||||
)
|
||||
return out
|
||||
56
ixformer_sdk/inference/functions/bnb_qgemm.py
Normal file
56
ixformer_sdk/inference/functions/bnb_qgemm.py
Normal file
@@ -0,0 +1,56 @@
|
||||
from typing import List, Union
|
||||
|
||||
import ixformer._C as ops
|
||||
import torch
|
||||
|
||||
__all__ = ["bnb_qgemm", "ref_bnb_qgemm"]
|
||||
|
||||
|
||||
# qA : quant input shape : [bs, in_feature]
|
||||
# qW : quant weight shape : [out_feature, in_feature]
|
||||
# SA : scale vector of qA shape : [bs]
|
||||
# SW : scale vector of qW shape : [out_feature]
|
||||
|
||||
|
||||
def ref_bnb_qgemm(
|
||||
qA: torch.Tensor,
|
||||
qW: torch.Tensor,
|
||||
SA: torch.Tensor,
|
||||
SW: torch.Tensor,
|
||||
training: bool = False,
|
||||
scaleA: float = 127.0,
|
||||
scaleW: float = 127.0,
|
||||
):
|
||||
y = torch.nn.functional.linear(qA.to(torch.float), qW.to(torch.float))
|
||||
out = torch.empty(y.shape, dtype = SA.dtype, device = SA.device)
|
||||
for i in range(qA.size(0)):
|
||||
for j in range(qW.size(0)):
|
||||
out[i][j] = y[i][j] * (SA[i].to(torch.float) / scaleA) * (SW[j].to(torch.float) / scaleW)
|
||||
return out.to(SA.dtype)
|
||||
|
||||
|
||||
def bnb_qgemm(
|
||||
qA: torch.Tensor,
|
||||
qW: torch.Tensor,
|
||||
SA: torch.Tensor,
|
||||
SW: torch.Tensor,
|
||||
training: bool = False,
|
||||
scaleA: float = 127.0,
|
||||
scaleW: float = 127.0,
|
||||
) -> torch.Tensor:
|
||||
|
||||
"""
|
||||
Args:
|
||||
qA: (bs, in_feature) torch.int8
|
||||
qW: (out_feature, in_feature) torch.int8
|
||||
SA: (bs) torch.half
|
||||
scale vector of qA
|
||||
SA: (out_feature) torch.half
|
||||
scale vector of qW
|
||||
training: bool
|
||||
scaleA: float
|
||||
scaleW: float
|
||||
Returns:
|
||||
Tensor: (bs, out_feature) torch.half
|
||||
"""
|
||||
return ops.infer.bnb_qgemm(qA, qW, SA, SW, scaleA, scaleW)
|
||||
58
ixformer_sdk/inference/functions/bnb_quant.py
Normal file
58
ixformer_sdk/inference/functions/bnb_quant.py
Normal file
@@ -0,0 +1,58 @@
|
||||
from typing import List, Union
|
||||
|
||||
import ixformer._C as ops
|
||||
import torch
|
||||
|
||||
__all__ = ["bnb_quant", "ref_bnb_quant"]
|
||||
|
||||
|
||||
# A : input shape : [row, col]
|
||||
# SA : scale vector
|
||||
# quant_type
|
||||
# 0 : every row shared a scale, SA shape : [row]
|
||||
# 1 : every col shared a scale, SA shape : [col]
|
||||
def ref_bnb_quant(
|
||||
A: torch.Tensor,
|
||||
SA: torch.Tensor,
|
||||
training: bool = False,
|
||||
scale: float = 127.0,
|
||||
quant_type: int = 0,
|
||||
):
|
||||
qA = torch.empty(A.shape, device = SA.device)
|
||||
if quant_type == 0:
|
||||
for i in range(A.size(0)):
|
||||
qA[i:] = torch.round(A[i:] * (scale / SA[i].to(torch.float)))
|
||||
else:
|
||||
for i in range(A.size(1)):
|
||||
qA[:,i] = torch.round(A[:,i] * (scale / SA[i].to(torch.float)))
|
||||
|
||||
qA_clamped = torch.clamp(qA, min=-128, max=127)
|
||||
qA = qA_clamped.to(torch.int8)
|
||||
return qA
|
||||
|
||||
|
||||
def bnb_quant(
|
||||
A: torch.Tensor,
|
||||
SA: torch.Tensor,
|
||||
training: bool = False,
|
||||
scale: float = 127.0,
|
||||
quant_type: int = 0,
|
||||
) -> torch.Tensor:
|
||||
|
||||
"""
|
||||
Args:
|
||||
A: (row, col) torch.half
|
||||
quant input
|
||||
SA: (row) or (col) torch.half
|
||||
scale vector
|
||||
training: bool
|
||||
scale: float
|
||||
qunt_type: int
|
||||
0 : every row shared a scale, SA shape : [row]
|
||||
1 : every col shared a scale, SA shape : [col]
|
||||
Returns:
|
||||
Tensor: (row, col) torch.int8
|
||||
quant output
|
||||
|
||||
"""
|
||||
return ops.infer.bnb_quant(A, SA, scale, quant_type)
|
||||
52
ixformer_sdk/inference/functions/bnb_rowcol_absmax.py
Normal file
52
ixformer_sdk/inference/functions/bnb_rowcol_absmax.py
Normal file
@@ -0,0 +1,52 @@
|
||||
from typing import List, Union
|
||||
|
||||
import ixformer._C as ops
|
||||
import torch
|
||||
|
||||
__all__ = ["bnb_rowcol_absmax", "ref_bnb_rowcol_absmax"]
|
||||
|
||||
|
||||
# input : input shape : [row, col]
|
||||
# threshold : abs of element exceeds threshold will be ignored
|
||||
# type
|
||||
# 0 : row absmax
|
||||
def ref_bnb_rowcol_absmax(
|
||||
input: torch.Tensor,
|
||||
training: bool = False,
|
||||
threshold: float = 0.0,
|
||||
type: int = 0,
|
||||
):
|
||||
input = input.float()
|
||||
if threshold ==0.0:
|
||||
threshold = float('inf')
|
||||
mask = (torch.abs(input) < threshold)
|
||||
masked_input = mask * input
|
||||
masked_input = masked_input.half()
|
||||
if type == 0:
|
||||
out = torch.amax(torch.abs(masked_input), dim=1)
|
||||
|
||||
else:
|
||||
out = torch.amax(torch.abs(masked_input), dim=0)
|
||||
return out
|
||||
|
||||
|
||||
def bnb_rowcol_absmax(
|
||||
input: torch.Tensor,
|
||||
training: bool = False,
|
||||
threshold: float = 0.0,
|
||||
type: int = 0,
|
||||
) -> torch.Tensor:
|
||||
|
||||
"""
|
||||
Args:
|
||||
input: (row, col) torch.half
|
||||
目前col值必须满足col%2==0
|
||||
training: bool
|
||||
threshold: float
|
||||
abs of element exceeds threshold will be ignored
|
||||
type: int
|
||||
row absmax, 目前只支持type=0
|
||||
Returns:
|
||||
Tensor: (row) torch.half
|
||||
"""
|
||||
return ops.infer.bnb_rowcol_absmax(input, threshold, type)
|
||||
198
ixformer_sdk/inference/functions/conv2d.py
Normal file
198
ixformer_sdk/inference/functions/conv2d.py
Normal file
@@ -0,0 +1,198 @@
|
||||
from typing import Union
|
||||
|
||||
import ixformer._C as ops
|
||||
import torch
|
||||
import torch.nn.functional as NNF
|
||||
|
||||
__all__ = ["conv2d", "ref_conv2d", "ref_conv2d_nhwc", "conv2d_nhwc"]
|
||||
|
||||
|
||||
def is_channels_last(ten):
|
||||
return torch._prims_common.suggest_memory_format(ten) == torch.channels_last
|
||||
|
||||
|
||||
def _pair(x):
|
||||
if isinstance(x, (list, tuple)):
|
||||
return x
|
||||
return (x, x)
|
||||
|
||||
|
||||
def ref_conv2d(
|
||||
input: torch.Tensor,
|
||||
weight: torch.Tensor,
|
||||
bias: torch.Tensor = None,
|
||||
stride: Union[int, tuple] = 1,
|
||||
padding: Union[int, tuple] = 0,
|
||||
dilation: Union[int, tuple] = 1,
|
||||
groups: int = 1,
|
||||
):
|
||||
|
||||
output = NNF.conv2d(input, weight, bias, stride, padding, dilation, groups)
|
||||
return output
|
||||
|
||||
|
||||
# conv2d官方接口,如果weight是torch.channels_last,输出也是torch.channels_last;如果weight是nchw,那么输出也是nchw;特殊情况,如果输入是nchw,weight是torch.channels_last,输出也是torch.channels_last
|
||||
def conv2d(
|
||||
input: torch.Tensor,
|
||||
weight: torch.Tensor,
|
||||
bias: torch.Tensor = None,
|
||||
stride: Union[int, tuple] = 1,
|
||||
padding: Union[int, tuple] = 0,
|
||||
dilation: Union[int, tuple] = 1,
|
||||
groups: int = 1,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
input: (n,in_c,h,w) torch.float16
|
||||
weight: (out_c,in_c/groups,kH,kW) torch.float16
|
||||
bias: (out_c) torch.float16
|
||||
stride: int or tuple
|
||||
Stride of the convolution. Default: 1
|
||||
padding: int or tuple
|
||||
Padding added to all four sides of the input. Default: 0
|
||||
dilation: int or tuple
|
||||
Spacing between kernel elements. Default: 1
|
||||
groups: int
|
||||
Number of blocked connections from input channels to output channels. Default: 1
|
||||
Returns:
|
||||
Tensor: (n,out_c,h_out,w_out) torch.float16
|
||||
h_out = (h_in + 2 * pad_h - dilation_h * (kernel_h - 1) - 1) / stride_h + 1;
|
||||
w_out = (w_in + 2 * pad_w - dilation_w * (kernel_w - 1) - 1) / stride_w + 1;
|
||||
"""
|
||||
stride = _pair(stride)
|
||||
padding = _pair(padding)
|
||||
dilation = _pair(dilation)
|
||||
|
||||
channel_last = is_channels_last(weight)
|
||||
if not is_channels_last(input) and channel_last:
|
||||
input = input.to(memory_format=torch.channels_last)
|
||||
|
||||
# compute outshape
|
||||
n, in_c, h_in, w_in = input.shape
|
||||
out_c, _, kernel_h, kernel_w = weight.shape
|
||||
pad_h = padding[0]
|
||||
pad_w = padding[1]
|
||||
stride_h = stride[0]
|
||||
stride_w = stride[1]
|
||||
dilation_h = dilation[0]
|
||||
dilation_w = dilation[1]
|
||||
h_out = (h_in + 2 * pad_h - dilation_h * (kernel_h - 1) - 1) // stride_h + 1
|
||||
w_out = (w_in + 2 * pad_w - dilation_w * (kernel_w - 1) - 1) // stride_w + 1
|
||||
|
||||
if channel_last:
|
||||
output_shape = [n, out_c, h_out, w_out]
|
||||
output = torch.empty(
|
||||
output_shape,
|
||||
memory_format=torch.channels_last,
|
||||
dtype=input.dtype,
|
||||
device=input.device,
|
||||
)
|
||||
else:
|
||||
output_shape = [n, out_c, h_out, w_out]
|
||||
output = input.new_empty(output_shape)
|
||||
|
||||
if channel_last:
|
||||
input = input.permute(0, 2, 3, 1)
|
||||
weight = weight.permute(0, 2, 3, 1)
|
||||
output = output.permute(0, 2, 3, 1)
|
||||
|
||||
if bias is not None:
|
||||
bias = bias.float()
|
||||
ops.infer.conv2d(
|
||||
input, weight, bias, output, stride, padding, dilation, groups, channel_last
|
||||
)
|
||||
if channel_last:
|
||||
output = output.permute(0, 3, 1, 2)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def ref_conv2d_nhwc(
|
||||
input: torch.Tensor,
|
||||
weight: torch.Tensor,
|
||||
bias: torch.Tensor = None,
|
||||
stride: Union[int, tuple] = 1,
|
||||
padding: Union[int, tuple] = 0,
|
||||
dilation: Union[int, tuple] = 1,
|
||||
groups: int = 1,
|
||||
):
|
||||
|
||||
output = NNF.conv2d(
|
||||
input.permute(0, 3, 1, 2).contiguous(),
|
||||
weight.permute(0, 3, 1, 2).contiguous(),
|
||||
bias,
|
||||
stride,
|
||||
padding,
|
||||
dilation,
|
||||
groups,
|
||||
)
|
||||
return output.permute(0, 2, 3, 1).contiguous()
|
||||
|
||||
|
||||
# conv2d_nhwc,
|
||||
# conv2d官方接口解决两种情况:
|
||||
# 1、务必输入tensor内存上是nhwc,且tensor属于memory_format=torch.channels_last,
|
||||
# 2、或者输入tensor内存上是nchw,并且是contiguous;
|
||||
# conv2d官方接口不能解决,conv2d_nhwc则可处理这种情况的
|
||||
# 输入tensor内存上是nhwc的,但tensor没有用memory_format=torch.channels_last进行过处理,不会有memory_format=torch.channels_last的标签
|
||||
def conv2d_nhwc(
|
||||
input: torch.Tensor,
|
||||
weight: torch.Tensor,
|
||||
bias: torch.Tensor = None,
|
||||
stride: Union[int, tuple] = 1,
|
||||
padding: Union[int, tuple] = 0,
|
||||
dilation: Union[int, tuple] = 1,
|
||||
groups: int = 1,
|
||||
):
|
||||
|
||||
"""
|
||||
Args:
|
||||
input: (n,h,w,in_c) torch.float16
|
||||
weight: (out_c,kH,kW,in_c/groups) torch.float16
|
||||
bias: (out_c) torch.float16
|
||||
stride: int or tuple
|
||||
Stride of the convolution. Default: 1
|
||||
padding: int or tuple
|
||||
Padding added to all four sides of the input. Default: 0
|
||||
dilation: int or tuple
|
||||
Spacing between kernel elements. Default: 1
|
||||
groups: int
|
||||
Number of blocked connections from input channels to output channels. Default: 1
|
||||
Returns:
|
||||
Tensor: (n,h_out,w_out,out_c) torch.float16
|
||||
h_out = (h_in + 2 * pad_h - dilation_h * (kernel_h - 1) - 1) / stride_h + 1;
|
||||
w_out = (w_in + 2 * pad_w - dilation_w * (kernel_w - 1) - 1) / stride_w + 1;
|
||||
"""
|
||||
|
||||
stride = _pair(stride)
|
||||
padding = _pair(padding)
|
||||
dilation = _pair(dilation)
|
||||
|
||||
assert input.is_contiguous()
|
||||
assert weight.is_contiguous()
|
||||
|
||||
# compute outshape
|
||||
n, h_in, w_in, in_c = input.shape
|
||||
(
|
||||
out_c,
|
||||
kernel_h,
|
||||
kernel_w,
|
||||
_,
|
||||
) = weight.shape
|
||||
pad_h = padding[0]
|
||||
pad_w = padding[1]
|
||||
stride_h = stride[0]
|
||||
stride_w = stride[1]
|
||||
dilation_h = dilation[0]
|
||||
dilation_w = dilation[1]
|
||||
h_out = (h_in + 2 * pad_h - dilation_h * (kernel_h - 1) - 1) // stride_h + 1
|
||||
w_out = (w_in + 2 * pad_w - dilation_w * (kernel_w - 1) - 1) // stride_w + 1
|
||||
|
||||
output_shape = [n, h_out, w_out, out_c]
|
||||
output = torch.empty(output_shape, dtype=input.dtype, device=input.device)
|
||||
if bias is not None:
|
||||
bias = bias.float()
|
||||
ops.infer.conv2d(
|
||||
input, weight, bias, output, stride, padding, dilation, groups, True
|
||||
)
|
||||
return output
|
||||
204
ixformer_sdk/inference/functions/cross_entropy_loss.py
Normal file
204
ixformer_sdk/inference/functions/cross_entropy_loss.py
Normal file
@@ -0,0 +1,204 @@
|
||||
from typing import Union
|
||||
|
||||
import ixformer._C as ops
|
||||
import torch
|
||||
|
||||
__all__ = ["vocab_parallel_cross_entropy", "ref_vocab_parallel_cross_entropy"]
|
||||
|
||||
|
||||
def ref_vocab_parallel_cross_entropy(
|
||||
vocab_parallel_logits: torch.Tensor,
|
||||
target: torch.Tensor,
|
||||
label_smoothing: float = 0.0,
|
||||
world_size: int = 1,
|
||||
vocab_start_index: int = 0,
|
||||
vocab_end_index: int = 320000,
|
||||
group=None,
|
||||
):
|
||||
if world_size == 1:
|
||||
vocab_parallel_logits = vocab_parallel_logits.float()
|
||||
partition_vocab_size = vocab_parallel_logits.size()[-1]
|
||||
logits_max = torch.max(vocab_parallel_logits, dim=-1)[0]
|
||||
vocab_parallel_logits = vocab_parallel_logits - logits_max.unsqueeze(dim=-1)
|
||||
target_mask = (target < vocab_start_index) | (target >= vocab_end_index)
|
||||
masked_target = target.clone() - vocab_start_index
|
||||
masked_target[target_mask] = 0
|
||||
logits_2d = vocab_parallel_logits.view(-1, partition_vocab_size)
|
||||
masked_target_1d = masked_target.view(-1)
|
||||
arange_1d = torch.arange(
|
||||
start=0, end=logits_2d.size()[0], device=logits_2d.device
|
||||
)
|
||||
|
||||
predicted_logits_1d = logits_2d[arange_1d, masked_target_1d]
|
||||
predicted_logits_1d = predicted_logits_1d.clone().contiguous()
|
||||
|
||||
predicted_logits = predicted_logits_1d.view_as(target)
|
||||
predicted_logits[target_mask] = 0.0
|
||||
exp_logits = vocab_parallel_logits
|
||||
torch.exp(vocab_parallel_logits, out=exp_logits)
|
||||
sum_exp_logits = exp_logits.sum(dim=-1)
|
||||
loss = torch.log(sum_exp_logits) - predicted_logits
|
||||
exp_logits.div_(sum_exp_logits.unsqueeze(dim=-1))
|
||||
if label_smoothing > 0:
|
||||
"""
|
||||
We'd like to assign 1 / (K - 1) probability mass to every index that is not the ground truth.
|
||||
= (1 - alpha) * y_gt + alpha * mean(y_{i for i != gt})
|
||||
= (1 - alpha) * y_gt + (alpha / (K - 1)) * \sum_{i != gt} y_i
|
||||
= ((K - 1) * (1 - alpha) / (K - 1)) * y_gt + (alpha / (K - 1)) * \sum_{i != gt} y_i
|
||||
= (K * (1 - alpha) - 1) / (K - 1)) * y_gt + (alpha / (K - 1)) * \sum_{i} y_i
|
||||
= (1 - (alpha * K) / (K - 1)) * y_gt + ( (alpha * K) / (K - 1) ) * \sum_{i} y_i / K
|
||||
From: https://github.com/NVIDIA/NeMo/blob/main/nemo/collections/common/losses/smoothed_cross_entropy.py
|
||||
"""
|
||||
assert 1.0 > label_smoothing > 0.0
|
||||
smoothing = label_smoothing * partition_vocab_size / (partition_vocab_size - 1)
|
||||
|
||||
# Exp logits at this point are normalized probabilities. So we can just take the log to get log-probs.
|
||||
log_probs = torch.log(exp_logits)
|
||||
mean_log_probs = log_probs.mean(dim=-1)
|
||||
loss = (1.0 - smoothing) * loss - smoothing * mean_log_probs
|
||||
|
||||
else:
|
||||
# Maximum value along vocab dimension across all GPUs.
|
||||
logits_max = torch.max(vocab_parallel_logits, dim=-1)[0]
|
||||
torch.distributed.all_reduce(
|
||||
logits_max, op=torch.distributed.ReduceOp.MAX, group=group
|
||||
)
|
||||
# Subtract the maximum value.
|
||||
vocab_parallel_logits = vocab_parallel_logits - logits_max.unsqueeze(dim=-1)
|
||||
|
||||
# Get the partition's vocab indecies
|
||||
partition_vocab_size = vocab_parallel_logits.size()[-1]
|
||||
|
||||
# Create a mask of valid vocab ids (1 means it needs to be masked).
|
||||
target_mask = (target < vocab_start_index) | (target >= vocab_end_index)
|
||||
masked_target = target.clone() - vocab_start_index
|
||||
masked_target[target_mask] = 0
|
||||
|
||||
# Get predicted-logits = logits[target].
|
||||
# For Simplicity, we convert logits to a 2-D tensor with size
|
||||
# [*, partition-vocab-size] and target to a 1-D tensor of size [*].
|
||||
logits_2d = vocab_parallel_logits.view(-1, partition_vocab_size)
|
||||
masked_target_1d = masked_target.view(-1)
|
||||
arange_1d = torch.arange(
|
||||
start=0, end=logits_2d.size()[0], device=logits_2d.device
|
||||
)
|
||||
predicted_logits_1d = logits_2d[arange_1d, masked_target_1d]
|
||||
predicted_logits_1d = predicted_logits_1d.clone().contiguous()
|
||||
predicted_logits = predicted_logits_1d.view_as(target)
|
||||
predicted_logits[target_mask] = 0.0
|
||||
# All reduce is needed to get the chunks from other GPUs.
|
||||
torch.distributed.all_reduce(
|
||||
predicted_logits,
|
||||
op=torch.distributed.ReduceOp.SUM,
|
||||
group=group,
|
||||
)
|
||||
|
||||
# Sum of exponential of logits along vocab dimension across all GPUs.
|
||||
exp_logits = vocab_parallel_logits
|
||||
torch.exp(vocab_parallel_logits, out=exp_logits)
|
||||
sum_exp_logits = exp_logits.sum(dim=-1)
|
||||
torch.distributed.all_reduce(
|
||||
sum_exp_logits,
|
||||
op=torch.distributed.ReduceOp.SUM,
|
||||
group=group,
|
||||
)
|
||||
|
||||
# Loss = log(sum(exp(logits))) - predicted-logit.
|
||||
loss = torch.log(sum_exp_logits) - predicted_logits
|
||||
|
||||
# Normalize and optionally smooth logits
|
||||
exp_logits.div_(sum_exp_logits.unsqueeze(dim=-1))
|
||||
|
||||
vocab_size = exp_logits.size(-1)
|
||||
if label_smoothing > 0:
|
||||
"""
|
||||
We'd like to assign 1 / (K - 1) probability mass to every index that is not the ground truth.
|
||||
= (1 - alpha) * y_gt + alpha * mean(y_{i for i != gt})
|
||||
= (1 - alpha) * y_gt + (alpha / (K - 1)) * \sum_{i != gt} y_i
|
||||
= ((K - 1) * (1 - alpha) / (K - 1)) * y_gt + (alpha / (K - 1)) * \sum_{i != gt} y_i
|
||||
= (K * (1 - alpha) - 1) / (K - 1)) * y_gt + (alpha / (K - 1)) * \sum_{i} y_i
|
||||
= (1 - (alpha * K) / (K - 1)) * y_gt + ( (alpha * K) / (K - 1) ) * \sum_{i} y_i / K
|
||||
From: https://github.com/NVIDIA/NeMo/blob/main/nemo/collections/common/losses/smoothed_cross_entropy.py
|
||||
"""
|
||||
assert 1.0 > label_smoothing > 0.0
|
||||
smoothing = label_smoothing * vocab_size / (vocab_size - 1)
|
||||
|
||||
# Exp logits at this point are normalized probabilities. So we can just take the log to get log-probs.
|
||||
log_probs = torch.log(exp_logits)
|
||||
mean_log_probs = log_probs.mean(dim=-1)
|
||||
loss = (1.0 - smoothing) * loss - smoothing * mean_log_probs
|
||||
|
||||
return loss
|
||||
|
||||
|
||||
def vocab_parallel_cross_entropy(
|
||||
vocab_parallel_logits: torch.Tensor,
|
||||
target: torch.Tensor,
|
||||
label_smoothing: float = 0.0,
|
||||
world_size: int = 1,
|
||||
vocab_start_index: int = 0,
|
||||
vocab_end_index: int = 320000,
|
||||
group=None,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
vocab_parallel_logits: (seq_len,1,vocal_size) torch.float16, torch.bfloat16, torch.float
|
||||
target: (seq_len,1) torch.int64
|
||||
label_smoothing: float
|
||||
默认为0.0. 用于标签平滑
|
||||
world_size: int
|
||||
当world_size = 1时,目前只支持batch_size = 1 的情况
|
||||
vocab_start_index: int
|
||||
vocab_end_index: int
|
||||
group:
|
||||
TP 并行组
|
||||
Returns:
|
||||
loss: (seq_len,1) torch.float
|
||||
"""
|
||||
if world_size == 1:
|
||||
device = vocab_parallel_logits.device
|
||||
xnumel = vocab_parallel_logits.shape[0]
|
||||
rnumel = vocab_parallel_logits.shape[-1]
|
||||
exp_logits = torch.empty(
|
||||
(xnumel, 1, rnumel), device=device, dtype=torch.float32
|
||||
)
|
||||
masked_target_1d = torch.empty((xnumel,), device=device, dtype=torch.int32)
|
||||
loss = torch.empty((xnumel, 1), device=device, dtype=torch.float32)
|
||||
|
||||
ops.train.cross_entropy_loss_forward(
|
||||
vocab_parallel_logits, target.int(), exp_logits, masked_target_1d, loss
|
||||
)
|
||||
|
||||
vocab_size = exp_logits.size(-1)
|
||||
if label_smoothing > 0:
|
||||
"""
|
||||
We'd like to assign 1 / (K - 1) probability mass to every index that is not the ground truth.
|
||||
= (1 - alpha) * y_gt + alpha * mean(y_{i for i != gt})
|
||||
= (1 - alpha) * y_gt + (alpha / (K - 1)) * \sum_{i != gt} y_i
|
||||
= ((K - 1) * (1 - alpha) / (K - 1)) * y_gt + (alpha / (K - 1)) * \sum_{i != gt} y_i
|
||||
= (K * (1 - alpha) - 1) / (K - 1)) * y_gt + (alpha / (K - 1)) * \sum_{i} y_i
|
||||
= (1 - (alpha * K) / (K - 1)) * y_gt + ( (alpha * K) / (K - 1) ) * \sum_{i} y_i / K
|
||||
From: https://github.com/NVIDIA/NeMo/blob/main/nemo/collections/common/losses/smoothed_cross_entropy.py
|
||||
"""
|
||||
assert 1.0 > label_smoothing > 0.0
|
||||
smoothing = label_smoothing * vocab_size / (vocab_size - 1)
|
||||
|
||||
# Exp logits at this point are normalized probabilities. So we can just take the log to get log-probs.
|
||||
log_probs = torch.log(exp_logits)
|
||||
mean_log_probs = log_probs.mean(dim=-1)
|
||||
loss = (1.0 - smoothing) * loss - smoothing * mean_log_probs
|
||||
|
||||
# Store softmax, target-mask and masked-target for backward pass.
|
||||
|
||||
return loss
|
||||
else:
|
||||
loss = ref_vocab_parallel_cross_entropy(
|
||||
vocab_parallel_logits,
|
||||
target,
|
||||
label_smoothing,
|
||||
world_size,
|
||||
vocab_start_index,
|
||||
vocab_end_index,
|
||||
group,
|
||||
)
|
||||
return loss
|
||||
201
ixformer_sdk/inference/functions/flash_attn.py
Normal file
201
ixformer_sdk/inference/functions/flash_attn.py
Normal file
@@ -0,0 +1,201 @@
|
||||
import math
|
||||
from typing import List, Union
|
||||
|
||||
import ixformer._C as ops
|
||||
import torch
|
||||
from torch.autograd.function import Function, FunctionCtx
|
||||
|
||||
__all__ = [
|
||||
"ixinfer_flash_attn_unpad",
|
||||
"ixinfer_flash_attn_pad",
|
||||
"ref_ixinfer_flash_attn_pad",
|
||||
]
|
||||
|
||||
|
||||
def ixinfer_flash_attn_unpad(
|
||||
# total_q x num_heads x head_size, total_q := \sum_{i=0}^{b} s_i
|
||||
q: "torch.Tensor",
|
||||
# total_k x num_heads_k x head_size, total_k := \sum_{i=0}^{b} s_i
|
||||
k: "torch.Tensor",
|
||||
# total_k x num_heads_k x head_size, total_k := \sum_{i=0}^{b} s_i
|
||||
v: "torch.Tensor",
|
||||
# total_q x num_heads x head_size, total_k := \sum_{i=0}^{b} s_i
|
||||
cu_seqlens_q: "torch.Tensor", # b+1
|
||||
cu_seqlens_k: "torch.Tensor", # b+1
|
||||
max_seqlen_q: int,
|
||||
max_seqlen_k: int,
|
||||
is_causal: bool = False,
|
||||
atten_scale: float = None,
|
||||
sqrt_alibi: bool = False,
|
||||
# total_q x num_heads x head_size, total_k := \sum_{i=0}^{b} s_i
|
||||
alibi_slopes: "torch.Tensor" = None,
|
||||
out: "torch.Tenosr" = None,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
q: (total_q, nheads, headdim) torch.float16, torch.bfloat16
|
||||
where total_q = total number of query tokens in the batch.
|
||||
k: (total_k, nheads_k, headdim) torch.float16, torch.bfloat16
|
||||
where total_k = total number of key tokens in the batch.
|
||||
v: (total_k, nheads_k, headdim) torch.float16, torch.bfloat16
|
||||
cu_seqlens_q: (batch_size + 1) torch.int32
|
||||
The cumulative sequence lengths of the sequences in the batch, used to index into q.
|
||||
cu_seqlens_k: (batch_size + 1) torch.int32
|
||||
The cumulative sequence lengths of the sequences in the batch, used to index into kv.
|
||||
max_seqlen_q: int
|
||||
Maximum query sequence length in the batch.
|
||||
max_seqlen_k: int
|
||||
Maximum key sequence length in the batch.
|
||||
atten_scale: float
|
||||
The scaling of QK^T before applying softmax. Default to 1 / sqrt(headdim).
|
||||
is_causal: bool
|
||||
Whether to apply causal attention mask (e.g., for auto-regressive modeling).
|
||||
sqrt_alibi: bool
|
||||
Whether to apply abilimode
|
||||
out: (total, nheads, headdim) torch.float16, torch.bfloat16
|
||||
Returns:
|
||||
out: (total, nheads, headdim) torch.float16, torch.bfloat16
|
||||
if not q.size(-1) % 32 == 0: out shape is (total_q, nheads, q.size(-1) + (32 - q.size(-1) % 32))
|
||||
"""
|
||||
if atten_scale is None:
|
||||
atten_scale = 1.0 / (q.size(-1) ** 0.5)
|
||||
|
||||
# 判断是否pad
|
||||
cur_head = q.size(-1)
|
||||
cur_head32 = cur_head
|
||||
if not cur_head % 32 == 0:
|
||||
cur_head32 = cur_head + (32 - cur_head % 32)
|
||||
q_infer = torch.nn.functional.pad(q, [0, cur_head32 - cur_head, 0, 0], value=0)
|
||||
k_infer = torch.nn.functional.pad(k, [0, cur_head32 - cur_head, 0, 0], value=0)
|
||||
v_infer = torch.nn.functional.pad(v, [0, cur_head32 - cur_head, 0, 0], value=0)
|
||||
else:
|
||||
q_infer = q
|
||||
k_infer = k
|
||||
v_infer = v
|
||||
|
||||
if out is None:
|
||||
out = torch.empty_like(q_infer)
|
||||
# ixinfer 新接口版
|
||||
ops.infer.ixinfer_flash_attn_unpad(
|
||||
q_infer,
|
||||
k_infer,
|
||||
v_infer,
|
||||
out,
|
||||
cu_seqlens_q,
|
||||
cu_seqlens_k,
|
||||
max_seqlen_q,
|
||||
max_seqlen_k,
|
||||
is_causal,
|
||||
False, # need_lse =False
|
||||
atten_scale,
|
||||
sqrt_alibi,
|
||||
alibi_slopes,
|
||||
)
|
||||
if not cur_head % 32 == 0:
|
||||
out = out[:, :, :cur_head]
|
||||
return out
|
||||
|
||||
|
||||
def ref_ixinfer_flash_attn_pad(
|
||||
# [ batch num_heads seq_q head_size]
|
||||
q: torch.Tensor,
|
||||
# [ batch num_heads_k max_seq_kv head_size]
|
||||
k: torch.Tensor,
|
||||
# [ batch num_heads_k max_seq_kv head_size]
|
||||
v: torch.Tensor,
|
||||
# [ batch num_heads seq_q seq_kv] seq_kv<=max_seq_kv
|
||||
mask: torch.Tensor,
|
||||
# [ batch num_heads seq_q head_size]
|
||||
atten_scale: float = None,
|
||||
kv_seq_start: int = None,
|
||||
kv_seq_end: int = None,
|
||||
):
|
||||
head_dim = q.size(-1)
|
||||
k_effective = k[:, :, kv_seq_start:kv_seq_end, :]
|
||||
v_effective = v[:, :, kv_seq_start:kv_seq_end, :]
|
||||
# 2. q*kt softmax
|
||||
scores_qk = (
|
||||
torch.matmul(q.float(), k_effective.float().transpose(-2, -1)) * atten_scale
|
||||
)
|
||||
# softmax
|
||||
# print(scores_qk.shape,mask.shape)
|
||||
if mask is not None:
|
||||
if mask.dtype == torch.int32:
|
||||
scores_qk = scores_qk + mask * (-100000)
|
||||
elif mask.dtype == torch.float32:
|
||||
scores_qk = scores_qk + mask
|
||||
else:
|
||||
print(
|
||||
f"mask dtype is not surported {mask.dtype},now surport int32 and float32"
|
||||
)
|
||||
scores_qk = torch.nn.functional.softmax(scores_qk, dim=-1)
|
||||
# 3. x = qk_scores * v
|
||||
scores_v = torch.matmul(scores_qk, v_effective.float())
|
||||
return scores_v.half()
|
||||
|
||||
|
||||
def ixinfer_flash_attn_pad(
|
||||
# [ batch num_heads seq_q head_size]
|
||||
q: torch.Tensor,
|
||||
# [ batch num_heads_k max_seq_kv head_size]
|
||||
k: torch.Tensor,
|
||||
# [ batch num_heads_k max_seq_kv head_size]
|
||||
v: torch.Tensor,
|
||||
# [ batch num_heads seq_q seq_kv] seq_kv<=max_seq_kv
|
||||
mask: torch.Tensor,
|
||||
# [ batch num_heads seq_q head_size]
|
||||
atten_scale: float = None,
|
||||
kv_seq_start: int = None,
|
||||
kv_seq_end: int = None,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
q: (batch_size, num_head, seq_len_q, head_dim) torch.float16, torch.bfloat16
|
||||
k: (batch_size, num_head_kv, seq_len_kv, head_dim) torch.float16, torch.bfloat16
|
||||
v: (batch_size, num_head_kv, seq_len_kv, head_dim) torch.float16, torch.bfloat16
|
||||
mask: (batch_size, num_head, seq_len_q, kv_seq_start:kv_seq_end) torch.int32, torch.int64, torch.float32
|
||||
atten_scale: float
|
||||
The scaling of QK^T before applying softmax. Default to 1 / sqrt(headdim).
|
||||
kv_seq_start: int
|
||||
kv sequence start index used for computation in the batch
|
||||
kv_seq_end: int
|
||||
kv sequence end index used for computation in the batch.
|
||||
Returns:
|
||||
out: (batch_size, num_head, seq_len_q, head_dim) torch.float16, torch.bfloat16
|
||||
"""
|
||||
|
||||
# 判断是否pad
|
||||
cur_head = q.size(-1)
|
||||
cur_head32 = cur_head
|
||||
if not cur_head % 32 == 0:
|
||||
cur_head32 = cur_head + (32 - cur_head % 32)
|
||||
q_infer = torch.nn.functional.pad(q, [0, cur_head32 - cur_head, 0, 0], value=0)
|
||||
k_infer = torch.nn.functional.pad(k, [0, cur_head32 - cur_head, 0, 0], value=0)
|
||||
v_infer = torch.nn.functional.pad(v, [0, cur_head32 - cur_head, 0, 0], value=0)
|
||||
else:
|
||||
q_infer = q
|
||||
k_infer = k
|
||||
v_infer = v
|
||||
|
||||
if atten_scale is None:
|
||||
atten_scale = 1.0 / (q.size(-1) ** 0.5)
|
||||
if kv_seq_start is None or kv_seq_end is None:
|
||||
kv_seq_start = 0
|
||||
kv_seq_end = k.size(-2) # kv seq len
|
||||
elif kv_seq_start < 0 or kv_seq_end > k.size(-2) or kv_seq_start >= kv_seq_end:
|
||||
raise NotImplementedError(
|
||||
"must kv_seq_start<0 or kv_seq_end>k.size(-2) or kv_seq_start>=kv_seq_end!"
|
||||
)
|
||||
out_shape = list(q_infer.shape)
|
||||
out = torch.empty(out_shape, dtype=q.dtype, device=q.device)
|
||||
if mask is not None:
|
||||
ops.infer.ixinfer_flash_attn_pad_fwd(
|
||||
q_infer, k_infer, v_infer, mask, out, atten_scale, kv_seq_start, kv_seq_end
|
||||
)
|
||||
else:
|
||||
ops.infer.ixinfer_flash_attn_pad_fwd_nomask(
|
||||
q_infer, k_infer, v_infer, out, atten_scale, kv_seq_start, kv_seq_end
|
||||
)
|
||||
if not cur_head % 32 == 0:
|
||||
out = out[:, :, :, :cur_head]
|
||||
return out
|
||||
350
ixformer_sdk/inference/functions/flash_attn_lib.py
Normal file
350
ixformer_sdk/inference/functions/flash_attn_lib.py
Normal file
@@ -0,0 +1,350 @@
|
||||
import math
|
||||
from typing import List, Union
|
||||
|
||||
import ixformer._C as ops
|
||||
import torch
|
||||
|
||||
from .flash_attn import ixinfer_flash_attn_unpad
|
||||
|
||||
__all__ = [
|
||||
"flash_attn_varlen_func",
|
||||
"ref_flash_attn_varlen_func",
|
||||
"flash_attn_func",
|
||||
"ref_flash_attn_func",
|
||||
]
|
||||
|
||||
|
||||
def ref_flash_attn_varlen_func(
|
||||
q: torch.Tensor,
|
||||
k: torch.Tensor,
|
||||
v: torch.Tensor,
|
||||
cu_seqlens_q: torch.Tensor,
|
||||
cu_seqlens_k: torch.Tensor,
|
||||
max_seqlen_q: int,
|
||||
max_seqlen_k: int,
|
||||
dropout_p: float = 0.0,
|
||||
softmax_scale: float = None,
|
||||
causal: bool = False,
|
||||
return_attn_probs: bool = False,
|
||||
):
|
||||
if return_attn_probs:
|
||||
raise NotImplementedError("return_attn_probs not supported!")
|
||||
out = torch.zeros_like(q)
|
||||
unpad_causal_torch(
|
||||
q,
|
||||
k,
|
||||
v,
|
||||
out,
|
||||
cu_seqlens_q,
|
||||
cu_seqlens_k,
|
||||
max_seqlen_q,
|
||||
max_seqlen_k,
|
||||
torch.float16,
|
||||
softmax_scale,
|
||||
causal,
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
def unpad_causal_torch(
|
||||
q,
|
||||
k,
|
||||
v,
|
||||
output,
|
||||
cu_seqlens_q,
|
||||
cu_seqlens_k,
|
||||
max_seq_len_q,
|
||||
max_seq_len_kv,
|
||||
dtype,
|
||||
atten_scale,
|
||||
is_causal=True,
|
||||
):
|
||||
|
||||
head_num = q.size(1)
|
||||
head_num_kv = k.size(1)
|
||||
head_dim = q.size(2)
|
||||
|
||||
assert head_num % head_num_kv == 0
|
||||
if atten_scale == None:
|
||||
atten_scale = 1.0 / (q.size(-1) ** 0.5)
|
||||
# tokens,head_num,head_dim
|
||||
if head_num != head_num_kv:
|
||||
# k = k.repeat(1, head_num//head_num_kv, 1)#[0,1,2,0,1,2,0,1,2,0,1,2]
|
||||
# v = v.repeat(1, head_num//head_num_kv, 1)
|
||||
|
||||
k = repeat_kv(k, head_num // head_num_kv) # [0,0,0,0,1,1,1,1,2,2,2,2] GROUP
|
||||
v = repeat_kv(v, head_num // head_num_kv)
|
||||
|
||||
batch_size = cu_seqlens_q.size(0) - 1
|
||||
|
||||
for i in range(batch_size):
|
||||
q_start_index = cu_seqlens_q[i]
|
||||
q_end_index = cu_seqlens_q[i + 1]
|
||||
cur_q_len = q_end_index - q_start_index
|
||||
# 1*seq_len,head_num,head_dim
|
||||
cur_q = q[q_start_index:q_end_index]
|
||||
|
||||
k_start_index = cu_seqlens_k[i]
|
||||
k_end_index = cu_seqlens_k[i + 1]
|
||||
cur_k_len = k_end_index - k_start_index
|
||||
|
||||
cur_k = k[k_start_index:k_end_index]
|
||||
cur_v = v[k_start_index:k_end_index]
|
||||
|
||||
# mask = torch.tril(torch.ones([cur_q_len, cur_k_len], dtype=torch.bool)).cuda()
|
||||
# mask = mask.unsqueeze(0).unsqueeze(0)
|
||||
if is_causal:
|
||||
# Create attention mask.
|
||||
attn_mask = torch.triu(
|
||||
torch.ones(cur_q_len, cur_k_len, dtype=dtype), diagonal=1
|
||||
)
|
||||
attn_mask = attn_mask * torch.finfo(dtype).min
|
||||
attn_mask = attn_mask.to(dtype=dtype, device="cuda")
|
||||
else:
|
||||
attn_mask = None
|
||||
|
||||
ref_output = ref_masked_attention(
|
||||
cur_q,
|
||||
cur_k,
|
||||
cur_v,
|
||||
atten_scale,
|
||||
attn_mask=attn_mask,
|
||||
)
|
||||
output[q_start_index:q_end_index].copy_(ref_output)
|
||||
|
||||
|
||||
def ref_masked_attention(
|
||||
query: torch.Tensor,
|
||||
key: torch.Tensor,
|
||||
value: torch.Tensor,
|
||||
scale: float,
|
||||
attn_mask=None,
|
||||
) -> torch.Tensor:
|
||||
query = query * scale
|
||||
dtype = query.dtype
|
||||
device = query.device
|
||||
query = query.to(torch.float32).cpu()
|
||||
key = key.to(torch.float32).cpu()
|
||||
value = value.to(torch.float32).cpu()
|
||||
attn = torch.einsum("qhd,khd->hqk", query, key)
|
||||
if attn_mask is not None:
|
||||
attn_mask = attn_mask.cpu()
|
||||
attn = attn + attn_mask
|
||||
attn = torch.softmax(attn, dim=-1)
|
||||
out = torch.einsum("hqk,khd->qhd", attn, value)
|
||||
out = out.to(device).to(dtype)
|
||||
return out
|
||||
|
||||
|
||||
def flash_attn_varlen_func(
|
||||
q: torch.Tensor,
|
||||
k: torch.Tensor,
|
||||
v: torch.Tensor,
|
||||
cu_seqlens_q: torch.Tensor,
|
||||
cu_seqlens_k: torch.Tensor,
|
||||
max_seqlen_q: int,
|
||||
max_seqlen_k: int,
|
||||
dropout_p: float = 0.0,
|
||||
softmax_scale: float = None,
|
||||
causal: bool = False,
|
||||
return_attn_probs: bool = False,
|
||||
out: torch.Tensor = None,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
q: (total_q, nheads, headdim) torch.float16, torch.bfloat16
|
||||
where total_q = total number of query tokens in the batch.
|
||||
k: (total_k, nheads_k, headdim) torch.float16, torch.bfloat16
|
||||
where total_k = total number of key tokens in the batch.
|
||||
v: (total_k, nheads_k, headdim) torch.float16, torch.bfloat16
|
||||
cu_seqlens_q: (batch_size + 1) torch.int32
|
||||
The cumulative sequence lengths of the sequences in the batch, used to index into q.
|
||||
cu_seqlens_k: (batch_size + 1) torch.int32
|
||||
The cumulative sequence lengths of the sequences in the batch, used to index into kv.
|
||||
max_seqlen_q: int
|
||||
Maximum query sequence length in the batch.
|
||||
max_seqlen_k: int
|
||||
Maximum key sequence length in the batch.
|
||||
dropout_p: float
|
||||
Dropout probability. dropout_p should be set to 0.0 during evaluation
|
||||
softmax_scale: float
|
||||
The scaling of QK^T before applying softmax. Default to 1 / sqrt(headdim).
|
||||
causal: bool
|
||||
Whether to apply causal attention mask (e.g., for auto-regressive modeling).
|
||||
return_attn_probs: bool
|
||||
Whether to return the attention probabilities. This option is for testing only. The returned probabilities are not guaranteed to be correct (they might not have the right scaling).
|
||||
out: (total, nheads, headdim) torch.float16, torch.bfloat16
|
||||
Returns:
|
||||
out: (total, nheads, headdim) torch.float16, torch.bfloat16
|
||||
"""
|
||||
|
||||
|
||||
assert len(q.shape) == 3, "q.shape != [total_q, nheads, head_dim]"
|
||||
assert len(k.shape) == 3, "k.shape != [total_k, nheads_k, head_dim]"
|
||||
assert len(v.shape) == 3, "v.shape != [total_k, nheads_k, head_dim]"
|
||||
assert len(cu_seqlens_q.shape) == 1, "cu_seqlens_q.shape != [batch_size+1]"
|
||||
assert len(cu_seqlens_k.shape) == 1, "cu_seqlens_k.shape != [batch_size+1]"
|
||||
|
||||
if return_attn_probs:
|
||||
raise NotImplementedError("return_attn_probs not supported!")
|
||||
atten_scale = softmax_scale
|
||||
training = q.requires_grad
|
||||
nheads = q.size(1)
|
||||
nheads_k = k.size(1)
|
||||
if training:
|
||||
raise NotImplementedError("not support training!")
|
||||
else: # 推理支持group query attention
|
||||
assert nheads % nheads_k == 0
|
||||
return ixinfer_flash_attn_unpad(
|
||||
q,
|
||||
k,
|
||||
v,
|
||||
cu_seqlens_q,
|
||||
cu_seqlens_k,
|
||||
max_seqlen_q,
|
||||
max_seqlen_k,
|
||||
causal,
|
||||
atten_scale,
|
||||
out=out,
|
||||
)
|
||||
|
||||
|
||||
def repeat_kv(x: torch.Tensor, n_rep: int) -> torch.Tensor:
|
||||
"""torch.repeat_interleave(x, dim=2, repeats=n_rep)"""
|
||||
if len(x.shape) == 4:
|
||||
batch, seq_len, n_kv_heads, head_dim = x.shape
|
||||
elif len(x.shape) == 3:
|
||||
tokens, n_kv_heads, head_dim = x.shape
|
||||
if n_rep == 1:
|
||||
return x
|
||||
if len(x.shape) == 4:
|
||||
return (
|
||||
x[:, :, :, None, :]
|
||||
.expand(batch, seq_len, n_kv_heads, n_rep, head_dim)
|
||||
.reshape(batch, seq_len, n_kv_heads * n_rep, head_dim)
|
||||
)
|
||||
elif len(x.shape) == 3:
|
||||
return (
|
||||
x[:, :, None, :]
|
||||
.expand(tokens, n_kv_heads, n_rep, head_dim)
|
||||
.reshape(tokens, n_kv_heads * n_rep, head_dim)
|
||||
)
|
||||
|
||||
|
||||
def mha(q, k, v, atten_scale, is_causal):
|
||||
q = q.permute(0, 2, 1, 3).contiguous() # batch num_head seq_len head_dim
|
||||
k = k.permute(0, 2, 1, 3).contiguous()
|
||||
v = v.permute(0, 2, 1, 3).contiguous()
|
||||
|
||||
# 2. q*kt softmax
|
||||
scores_qk = torch.matmul(q.float(), k.float().transpose(-2, -1)) * atten_scale
|
||||
q_seq_len = q.size(2)
|
||||
kv_seq_len = k.size(2)
|
||||
if is_causal:
|
||||
# Create attention mask.
|
||||
attn_mask = torch.triu(
|
||||
torch.ones(q_seq_len, kv_seq_len, dtype=torch.int), diagonal=1
|
||||
)
|
||||
attn_mask = attn_mask.to(dtype=torch.int, device="cuda")
|
||||
else:
|
||||
attn_mask = None
|
||||
# softmax
|
||||
# print(scores_qk.shape,attn_mask.shape)
|
||||
if attn_mask is not None:
|
||||
# print(scores_qk.shape,attn_mask.shape)
|
||||
scores_qk = scores_qk + attn_mask * (-100000)
|
||||
scores_qk = torch.nn.functional.softmax(scores_qk, dim=-1)
|
||||
# 3. x = qk_scores * v
|
||||
scores_v = torch.matmul(scores_qk, v.float())
|
||||
scores_v = scores_v.half()
|
||||
return scores_v.permute(0, 2, 1, 3).contiguous()
|
||||
|
||||
|
||||
def ref_flash_attn_func(
|
||||
q: torch.Tensor,
|
||||
k: torch.Tensor,
|
||||
v: torch.Tensor,
|
||||
dropout_p: float = 0.0,
|
||||
softmax_scale: float = None,
|
||||
causal: bool = False,
|
||||
return_attn_probs: bool = False,
|
||||
):
|
||||
if return_attn_probs:
|
||||
raise NotImplementedError("return_attn_probs not supported!")
|
||||
head_num = q.size(2)
|
||||
head_num_kv = k.size(2)
|
||||
if head_num != head_num_kv:
|
||||
k = repeat_kv(k, head_num // head_num_kv) # [0,0,0,0,1,1,1,1,2,2,2,2] GROUP
|
||||
v = repeat_kv(v, head_num // head_num_kv)
|
||||
output_pt = mha(q, k, v, softmax_scale, causal)
|
||||
return output_pt
|
||||
|
||||
|
||||
def flash_attn_func(
|
||||
q: torch.Tensor,
|
||||
k: torch.Tensor,
|
||||
v: torch.Tensor,
|
||||
dropout_p: float = 0.0,
|
||||
softmax_scale: float = None,
|
||||
causal: bool = False,
|
||||
return_attn_probs: bool = False,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
q: (batch_size, seqlen, nheads, headdim) torch.float16, torch.bfloat16
|
||||
k: (batch_size, seqlen, nheads_k, headdim) torch.float16, torch.bfloat16
|
||||
v: (batch_size, seqlen, nheads_k, headdim) torch.float16, torch.bfloat16
|
||||
dropout_p: float
|
||||
Dropout probability. dropout_p should be set to 0.0 during evaluation
|
||||
softmax_scale: float
|
||||
The scaling of QK^T before applying softmax. Default to 1 / sqrt(headdim).
|
||||
causal: bool
|
||||
Whether to apply causal attention mask (e.g., for auto-regressive modeling).
|
||||
return_attn_probs: bool
|
||||
Whether to return the attention probabilities. This option is for testing only. The returned probabilities are not guaranteed to be correct (they might not have the right scaling).
|
||||
Returns:
|
||||
Tensor: (total, nheads, headdim) torch.float16, torch.bfloat16
|
||||
"""
|
||||
|
||||
if return_attn_probs:
|
||||
raise NotImplementedError("return_attn_probs not supported!")
|
||||
atten_scale = softmax_scale
|
||||
training = q.requires_grad
|
||||
|
||||
q_dim = q.dim()
|
||||
assert q_dim == 4
|
||||
|
||||
batch_size, max_seqlen_q, nheads, head_dim = q.shape
|
||||
_, max_seqlen_k, nheads_k, head_dim_k = k.shape
|
||||
assert head_dim == head_dim_k
|
||||
if training:
|
||||
raise NotImplementedError("not support training!")
|
||||
else: # 推理支持group query attention
|
||||
assert nheads % nheads_k == 0
|
||||
|
||||
q = q.view(batch_size * max_seqlen_q, nheads, head_dim)
|
||||
k = k.view(batch_size * max_seqlen_k, nheads_k, head_dim)
|
||||
v = v.view(batch_size * max_seqlen_k, nheads_k, head_dim)
|
||||
|
||||
cu_seqlens_q = torch.ones([batch_size + 1]) * max_seqlen_q
|
||||
cu_seqlens_q[0] = 0
|
||||
cu_seqlens_k = torch.ones([batch_size + 1]) * max_seqlen_k
|
||||
cu_seqlens_k[0] = 0
|
||||
cu_seqlens_q = cu_seqlens_q.cuda().int()
|
||||
cu_seqlens_k = cu_seqlens_k.cuda().int()
|
||||
cu_seqlens_q = torch.cumsum(cu_seqlens_q, dim=0).int()
|
||||
cu_seqlens_k = torch.cumsum(cu_seqlens_k, dim=0).int()
|
||||
output = ixinfer_flash_attn_unpad(
|
||||
q,
|
||||
k,
|
||||
v,
|
||||
cu_seqlens_q,
|
||||
cu_seqlens_k,
|
||||
max_seqlen_q,
|
||||
max_seqlen_k,
|
||||
causal,
|
||||
atten_scale,
|
||||
sqrt_alibi=False,
|
||||
alibi_slopes=None,
|
||||
)
|
||||
return output.view(batch_size, max_seqlen_q, nheads, head_dim)
|
||||
76
ixformer_sdk/inference/functions/fused_rope.py
Normal file
76
ixformer_sdk/inference/functions/fused_rope.py
Normal file
@@ -0,0 +1,76 @@
|
||||
from typing import List, Tuple, Union
|
||||
|
||||
import ixformer._C as ops
|
||||
import torch
|
||||
|
||||
# adding by xuelu.peng 20240417
|
||||
# from https://github.com/NVIDIA/apex/blob/master/apex/transformer/functional/fused_rope.py#L59
|
||||
__all__ = ["fused_apply_rotary_pos_emb", "ref_fused_apply_rotary_pos_emb"]
|
||||
|
||||
# Copied from Megatron-Core for testing.
|
||||
# https://github.com/NVIDIA/Megatron-LM/blob/5f2877d85cb26e47ce6dcdae4b80adf376abf4e8/megatron/core/models/common/embeddings/rotary_pos_embedding.py#L139
|
||||
def apply_rotary_pos_emb(t: torch.Tensor, freqs: torch.Tensor) -> torch.Tensor:
|
||||
"""Apply rotary positional embedding to input tensor T.
|
||||
|
||||
check https://kexue.fm/archives/8265 for detailed formulas
|
||||
|
||||
Arguments:
|
||||
t (Tensor): Input tensor T is of shape [seq_length, ... , dim]
|
||||
freqs (Tensor): Rotary Positional embedding tensor freq is of shape [seq_length, ..., dim]
|
||||
|
||||
Returns:
|
||||
Tensor: The input tensor after applying RoPE
|
||||
"""
|
||||
rot_dim = freqs.shape[-1]
|
||||
|
||||
# ideally t_pass is empty so rotary pos embedding is applied to all tensor t
|
||||
t, t_pass = t[..., :rot_dim], t[..., rot_dim:]
|
||||
|
||||
# first part is cosine component
|
||||
# second part is sine component, need to change signs with _rotate_half method
|
||||
cos_ = torch.cos(freqs).to(t.dtype)
|
||||
sin_ = torch.sin(freqs).to(t.dtype)
|
||||
|
||||
t = (t * cos_) + (_rotate_half(t) * sin_)
|
||||
return torch.cat((t, t_pass), dim=-1)
|
||||
|
||||
|
||||
def _rotate_half(x: torch.Tensor) -> torch.Tensor:
|
||||
"""Change sign so the last dimension becomes [-odd, +even]
|
||||
|
||||
Arguments:
|
||||
x (Tensor): Input tensor
|
||||
|
||||
Returns:
|
||||
Tensor: Tensor rotated half
|
||||
"""
|
||||
|
||||
x1, x2 = torch.chunk(x, 2, dim=-1)
|
||||
return torch.cat((-x2, x1), dim=-1)
|
||||
|
||||
|
||||
def ref_fused_apply_rotary_pos_emb(
|
||||
t: torch.Tensor, freqs: torch.Tensor, transpose_output_memory: bool = False
|
||||
):
|
||||
output_unfused = apply_rotary_pos_emb(t, freqs)
|
||||
return output_unfused
|
||||
|
||||
|
||||
def fused_apply_rotary_pos_emb(
|
||||
t: torch.Tensor,
|
||||
freqs: torch.Tensor,
|
||||
transpose_output_memory: bool = False,
|
||||
) -> torch.Tensor:
|
||||
|
||||
"""
|
||||
Args:
|
||||
t: (sequence length,batch size,head num,head_dim) torch.float16, torch.bfloat16, torch.float32
|
||||
freqs: (sequence length,1 ,1, head_dim) torch.float32
|
||||
transpose_output_memory: bool
|
||||
Default to False. Whether to transpose the 's' and 'b' dimension of the output's underlying memory format. This is very helpful when you want to get a contiguous tensor after calling `output.transpose(0, 1)`.
|
||||
|
||||
Returns:
|
||||
Tensor: (sequence length,batch size,head num,head_dim) torch.float16, torch.bfloat16, torch.float32
|
||||
"""
|
||||
output = ops.train.fused_rope_forward(t, freqs, transpose_output_memory)
|
||||
return output
|
||||
48
ixformer_sdk/inference/functions/gemv.py
Normal file
48
ixformer_sdk/inference/functions/gemv.py
Normal file
@@ -0,0 +1,48 @@
|
||||
import os
|
||||
from typing import Union
|
||||
|
||||
import ixformer._C as ops
|
||||
import torch
|
||||
|
||||
__all__ = ["gemv", "ref_gemv"]
|
||||
|
||||
|
||||
def ref_gemv(x: torch.Tensor, A: torch.Tensor, gemv_max_batch: int = 1):
|
||||
output = torch.nn.functional.linear(x, A)
|
||||
return output
|
||||
|
||||
|
||||
def gemv_conditions(input, weight, gemv_max_batch):
|
||||
# gemv 使用的条件 input:[m,k] weight:[n,k]
|
||||
# 1. m<=gemv_max_batch
|
||||
# 2. k%2==0 n%2==0
|
||||
# 3. bias is None
|
||||
input = input.view(-1, input.shape[-1])
|
||||
weight = weight.view(-1, weight.shape[-1])
|
||||
m = input.shape[0]
|
||||
k = input.shape[1]
|
||||
n = weight.shape[0]
|
||||
if m <= gemv_max_batch and k % 2 == 0 and n % 2 == 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def gemv(x: torch.Tensor, A: torch.Tensor, gemv_max_batch: int = 1):
|
||||
|
||||
"""
|
||||
Args:
|
||||
x: (..., k) torch.float16, torch.bfloat16
|
||||
A: (n,k) torch.float16, torch.bfloat16, torch.float32
|
||||
gemv_max_batch: int
|
||||
用于是否满足gemv使用条件的判断,目前只支持到1
|
||||
Returns:
|
||||
Tensor: (..., n) torch.float16, torch.bfloat16
|
||||
"""
|
||||
disable_infer_gemm_ex = os.getenv("DISABLE_INFER_GEMM_EX", "0")
|
||||
use_gemv = gemv_conditions(x, A, gemv_max_batch) and disable_infer_gemm_ex != "1"
|
||||
assert use_gemv == True
|
||||
output_shape = list(x.shape)
|
||||
output_shape[-1] = A.shape[0]
|
||||
output = x.new_empty(output_shape)
|
||||
output = ops.infer.linear_ex(x, A, None, output)
|
||||
return output
|
||||
120
ixformer_sdk/inference/functions/groupnorm.py
Normal file
120
ixformer_sdk/inference/functions/groupnorm.py
Normal file
@@ -0,0 +1,120 @@
|
||||
from typing import List, Tuple, Union
|
||||
|
||||
import ixformer._C as ops
|
||||
import torch
|
||||
from torch.autograd.function import Function, FunctionCtx
|
||||
|
||||
import ixformer
|
||||
|
||||
__all__ = [
|
||||
"group_norm",
|
||||
"ref_group_norm",
|
||||
"ref_fused_group_norm_silu",
|
||||
"fused_group_norm_silu",
|
||||
"ref_fused_group_norm_silu_nhwc",
|
||||
"fused_group_norm_silu_nhwc"
|
||||
]
|
||||
def is_channels_last(ten):
|
||||
return torch._prims_common.suggest_memory_format(ten) == torch.channels_last
|
||||
|
||||
def ref_group_norm(input, num_groups, weight, bias, eps):
|
||||
output = torch.nn.functional.group_norm(input, num_groups, weight, bias, eps)
|
||||
return output
|
||||
|
||||
#group_norm官方接口,如果input是nhwc(channel_last),输出则不是channel_last,而是nchw;如果input是nchw,那么输出也是nchw
|
||||
def group_norm(
|
||||
input: torch.Tensor,
|
||||
num_groups: int,
|
||||
weight: torch.Tensor,
|
||||
bias: torch.Tensor,
|
||||
eps: float = 1e-05,
|
||||
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
input: (n,c,h,w) or (n,c,h) or (n,h,w,c) torch.float16
|
||||
"contiguous_format":(n,c,h,w) or (n,c,h) "channels_last": (n,h,w,c)
|
||||
num_groups: int
|
||||
weight: (c) torch.float16
|
||||
bias: (c) torch.float16
|
||||
eps: float
|
||||
Returns:
|
||||
Tensor: (n,c,h,w) torch.float16
|
||||
"""
|
||||
|
||||
is_nhwc=is_channels_last(input)
|
||||
out = ops.infer.groupnorm(input, num_groups, weight, bias, eps, is_nhwc, 0)
|
||||
if is_nhwc:
|
||||
out=out.permute(0,3,1,2).contiguous()
|
||||
return out
|
||||
def ref_fused_group_norm_silu_nhwc(
|
||||
input: torch.Tensor,
|
||||
num_groups: int,
|
||||
weight: torch.Tensor,
|
||||
bias: torch.Tensor,
|
||||
eps: float = 1e-05,
|
||||
act_type:int = 0
|
||||
):
|
||||
output = torch.nn.functional.group_norm(input.permute(0,3,1,2).contiguous(), num_groups, weight, bias, eps)
|
||||
if act_type:
|
||||
output = output * torch.sigmoid(output)
|
||||
output = output.permute(0,2,3,1).contiguous()
|
||||
return output
|
||||
#为了减少permute/contiguous,新接口支持输入输出都是nhwc的,融合silu
|
||||
def fused_group_norm_silu_nhwc(
|
||||
input: torch.Tensor,
|
||||
num_groups: int,
|
||||
weight: torch.Tensor,
|
||||
bias: torch.Tensor,
|
||||
eps: float = 1e-05,
|
||||
act_type:int = 0
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
input: (n,h,w,c) torch.float16
|
||||
num_groups: int
|
||||
weight: (c) torch.float16
|
||||
bias: (c) torch.float16
|
||||
eps: float
|
||||
act_type: int
|
||||
0 or 1,if act_type=1, silu
|
||||
Returns:
|
||||
Tensor: (n,h,w,c) torch.float16
|
||||
|
||||
"""
|
||||
out = ops.infer.groupnorm(input, num_groups, weight, bias, eps, True, act_type)
|
||||
return out
|
||||
|
||||
def ref_fused_group_norm_silu(
|
||||
input: torch.Tensor,
|
||||
num_groups: int,
|
||||
weight: torch.Tensor,
|
||||
bias: torch.Tensor,
|
||||
eps: float = 1e-05,
|
||||
):
|
||||
output = torch.nn.functional.group_norm(input, num_groups, weight, bias, eps)
|
||||
output = output * torch.sigmoid(output)
|
||||
return output
|
||||
|
||||
|
||||
def fused_group_norm_silu(
|
||||
input: torch.Tensor,
|
||||
num_groups: int,
|
||||
weight: torch.Tensor,
|
||||
bias: torch.Tensor,
|
||||
eps: float = 1e-05,
|
||||
):
|
||||
|
||||
"""
|
||||
Args:
|
||||
input: (n,c,h,w) or (n,c,h) torch.float16
|
||||
num_groups: int
|
||||
weight: (c) torch.float16
|
||||
bias: (c) torch.float16
|
||||
eps: float
|
||||
Returns:
|
||||
output: (n,c,h,w) or (n,c,h) torch.float16
|
||||
"""
|
||||
|
||||
out = ops.infer.groupnorm(input, num_groups, weight, bias, eps, False, 1)
|
||||
return out
|
||||
32
ixformer_sdk/inference/functions/i8w8o32.py
Normal file
32
ixformer_sdk/inference/functions/i8w8o32.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import os
|
||||
from typing import Union
|
||||
|
||||
import ixformer._C as ops
|
||||
import torch
|
||||
|
||||
__all__ = ["i8w8o32", "ref_i8w8o32"]
|
||||
|
||||
|
||||
def ref_i8w8o32(input: torch.Tensor, weight: torch.Tensor):
|
||||
output = torch.nn.functional.linear(input.float(), weight.float()).int()
|
||||
return output
|
||||
|
||||
|
||||
def i8w8o32(input: torch.Tensor, weight: torch.Tensor):
|
||||
|
||||
"""
|
||||
Args:
|
||||
input: (bs, ic) torch.int8
|
||||
weight: (oc, ic) torch.int8
|
||||
Returns:
|
||||
Tensor: (bs, oc)) torch.int32
|
||||
"""
|
||||
if not torch.is_tensor(input):
|
||||
raise RuntimeError("Not impl.")
|
||||
output_shape = list(input.shape)
|
||||
output_shape[-1] = weight.size(0)
|
||||
output = torch.empty(output_shape, dtype=torch.int32, device=input.device)
|
||||
ic_dim = input.size(-1)
|
||||
input = input.view(-1, ic_dim)
|
||||
ops.infer.linear_i8w8o32(input.view(-1, ic_dim), weight, output)
|
||||
return output
|
||||
429
ixformer_sdk/inference/functions/layernorm.py
Normal file
429
ixformer_sdk/inference/functions/layernorm.py
Normal file
@@ -0,0 +1,429 @@
|
||||
from typing import List, Tuple, Union
|
||||
|
||||
import ixformer._C as ops
|
||||
import torch
|
||||
|
||||
__all__ = [
|
||||
"layer_norm",
|
||||
"ref_layer_norm",
|
||||
"residual_layer_norm",
|
||||
"ref_residual_layer_norm",
|
||||
"ref_residual_layer_norm_bias_alpha",
|
||||
"residual_layer_norm_bias_alpha",
|
||||
"ref_layer_norm_2sb_fused",
|
||||
"layer_norm_2sb_fused",
|
||||
]
|
||||
|
||||
|
||||
def ref_layer_norm(
|
||||
input: torch.Tensor,
|
||||
normalized_shape: List[int],
|
||||
weight: torch.Tensor,
|
||||
bias: torch.Tensor,
|
||||
eps: float = 1e-5,
|
||||
output: torch.Tensor = None,
|
||||
):
|
||||
|
||||
if weight is None or bias is None or weight.dim() > 1 or bias.dim() > 1:
|
||||
raise NotImplementedError(
|
||||
"layer_norm only support weight.dim() ==1 and bias.dim()==1!"
|
||||
)
|
||||
if normalized_shape == None:
|
||||
norm_size = weight.size(-1)
|
||||
normalized_shape = [norm_size]
|
||||
else:
|
||||
if (
|
||||
isinstance(normalized_shape, list) or isinstance(normalized_shape, tuple)
|
||||
) and len(normalized_shape) == 1:
|
||||
norm_size = normalized_shape[0]
|
||||
else:
|
||||
raise ValueError(
|
||||
f"layer_norm(): argument 'normalized_shape' (position 2) must be tuple of ints and length of tuple is equal to 1, not {type(normalized_shape)}"
|
||||
)
|
||||
if norm_size != weight.size(-1):
|
||||
raise ValueError(f"layer_norm(): argument 'norm_size' must == weight.size(-1)")
|
||||
|
||||
norm_out = torch.nn.functional.layer_norm(
|
||||
input, normalized_shape, weight, bias, eps=eps
|
||||
)
|
||||
if output is not None:
|
||||
assert output.shape == norm_out.shape
|
||||
output.copy_(norm_out)
|
||||
else:
|
||||
output = norm_out
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def layer_norm(
|
||||
input: torch.Tensor,
|
||||
normalized_shape: List[int],
|
||||
weight: torch.Tensor,
|
||||
bias: torch.Tensor,
|
||||
eps: float = 1e-5,
|
||||
output: torch.Tensor = None,
|
||||
):
|
||||
"""
|
||||
This function is deprecated, please use residual_layer_norm.
|
||||
等价实现:
|
||||
torch.nn.functional.layer_norm( input, normalized_shape, weight, bias, eps=0.000001)
|
||||
Args:
|
||||
input: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
|
||||
normalized_shape: list[int]
|
||||
weight: (hidden_size) torch.float16, torch.bfloat16, torch.float32
|
||||
bias: (hidden_size) torch.float16, torch.bfloat16, torch.float32
|
||||
eps: float32
|
||||
Returns:
|
||||
output: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
|
||||
"""
|
||||
if weight is None or bias is None or weight.dim() > 1 or bias.dim() > 1:
|
||||
raise NotImplementedError(
|
||||
"layer_norm only support weight.dim() ==1 and bias.dim()==1!"
|
||||
)
|
||||
if normalized_shape == None:
|
||||
norm_size = weight.size(-1)
|
||||
normalized_shape = [norm_size]
|
||||
else:
|
||||
if (
|
||||
isinstance(normalized_shape, list) or isinstance(normalized_shape, tuple)
|
||||
) and len(normalized_shape) == 1:
|
||||
norm_size = normalized_shape[0]
|
||||
else:
|
||||
raise ValueError(
|
||||
f"layer_norm(): argument 'normalized_shape' (position 2) must be tuple of ints and length of tuple is equal to 1, not {type(normalized_shape)}"
|
||||
)
|
||||
if norm_size != weight.size(-1):
|
||||
raise ValueError(f"layer_norm(): argument 'norm_size' must == weight.size(-1)")
|
||||
if output is None:
|
||||
output = torch.empty_like(input)
|
||||
ops.infer.layer_norm(input, weight, bias, None, output, eps)
|
||||
return output
|
||||
|
||||
|
||||
def ref_residual_layer_norm(
|
||||
input: torch.Tensor,
|
||||
weight: torch.Tensor,
|
||||
bias: torch.Tensor,
|
||||
residual: torch.Tensor = None,
|
||||
residual_bias: torch.Tensor = None,
|
||||
eps: float = 1e-5,
|
||||
output: torch.Tensor = None,
|
||||
residual_output: torch.Tensor = None,
|
||||
):
|
||||
normalized_shape = [weight.size(-1)]
|
||||
|
||||
if residual_bias is not None:
|
||||
input = input + residual_bias
|
||||
|
||||
if residual is not None:
|
||||
residual_output = torch.add(input, residual, out=residual_output)
|
||||
input = residual_output
|
||||
|
||||
norm_out = torch.nn.functional.layer_norm(
|
||||
input, normalized_shape, weight, bias, eps=eps
|
||||
)
|
||||
|
||||
if output is None:
|
||||
output = norm_out
|
||||
else:
|
||||
output.copy_(norm_out)
|
||||
|
||||
return output, residual_output
|
||||
|
||||
|
||||
def residual_layer_norm(
|
||||
input: torch.Tensor,
|
||||
weight: torch.Tensor,
|
||||
bias: torch.Tensor,
|
||||
residual: torch.Tensor = None,
|
||||
residual_bias: torch.Tensor = None,
|
||||
eps: float = 1e-5,
|
||||
output: torch.Tensor = None,
|
||||
residual_output: torch.Tensor = None,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
input: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
|
||||
weight: (hidden_size) torch.float16, torch.bfloat16, torch.float32
|
||||
bias: (hidden_size) torch.float16, torch.bfloat16, torch.float32
|
||||
residual: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
|
||||
residual_bias: (hidden_size) torch.float16, torch.bfloat16, torch.float32
|
||||
eps: float32
|
||||
Returns:
|
||||
output: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32 If set to None, an inplace operation will be performed on input.
|
||||
residual_output: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32 If set to None, an inplace operation will be performed on residual.
|
||||
"""
|
||||
if residual is None:
|
||||
if output is None:
|
||||
output = torch.empty(input.shape, device=input.device, dtype=input.dtype)
|
||||
ops.infer.layer_norm(input, weight, bias, residual_bias, output, eps)
|
||||
else:
|
||||
ops.infer.residual_layer_norm(
|
||||
input,
|
||||
residual,
|
||||
weight,
|
||||
bias,
|
||||
residual_bias,
|
||||
output,
|
||||
residual_output,
|
||||
1.0,
|
||||
eps,
|
||||
False,
|
||||
)
|
||||
residual_output = residual_output if residual_output is not None else residual
|
||||
output = output if output is not None else input
|
||||
|
||||
return output, residual_output
|
||||
|
||||
|
||||
def ref_residual_layer_norm_bias_alpha(
|
||||
input: torch.Tensor,
|
||||
normalized_shape: List[int],
|
||||
weight: torch.Tensor,
|
||||
bias: torch.Tensor,
|
||||
residual: torch.Tensor,
|
||||
residual_bias: torch.Tensor = None,
|
||||
alpha: float = 1.0,
|
||||
eps: float = 1e-5,
|
||||
is_post_ln=False,
|
||||
):
|
||||
if (
|
||||
weight is None
|
||||
or bias is None
|
||||
or residual is None
|
||||
or weight.dim() > 1
|
||||
or bias.dim() > 1
|
||||
):
|
||||
raise NotImplementedError(
|
||||
"residual_layer_norm only support weight.dim() ==1 and bias.dim()==1!"
|
||||
)
|
||||
if normalized_shape == None:
|
||||
norm_size = weight.size(-1)
|
||||
normalized_shape = [norm_size]
|
||||
else:
|
||||
if (
|
||||
isinstance(normalized_shape, list) or isinstance(normalized_shape, tuple)
|
||||
) and len(normalized_shape) == 1:
|
||||
norm_size = normalized_shape[0]
|
||||
else:
|
||||
raise ValueError(
|
||||
f"residual_layer_norm(): argument 'normalized_shape' (position 2) must be tuple of ints and length of tuple is equal to 1, not {type(normalized_shape)}"
|
||||
)
|
||||
|
||||
if norm_size != weight.size(-1):
|
||||
raise ValueError(
|
||||
f"residual_layer_norm(): argument 'norm_size' must == weight.size(-1)"
|
||||
)
|
||||
dtype = input.dtype
|
||||
if residual_bias is None:
|
||||
x = input.float() + residual.float() * alpha
|
||||
else:
|
||||
x = input.float() + residual.float() * alpha + residual_bias.float()
|
||||
|
||||
y = torch.nn.functional.layer_norm(
|
||||
x.to(dtype), normalized_shape, weight, bias, eps=eps
|
||||
)
|
||||
|
||||
if is_post_ln:
|
||||
return y, y
|
||||
else:
|
||||
return y, x.to(dtype)
|
||||
|
||||
|
||||
def residual_layer_norm_bias_alpha(
|
||||
input: torch.Tensor,
|
||||
normalized_shape: List[int],
|
||||
weight: torch.Tensor,
|
||||
bias: torch.Tensor,
|
||||
residual: torch.Tensor,
|
||||
residual_bias: torch.Tensor = None,
|
||||
alpha: float = 1.0,
|
||||
eps: float = 1e-5,
|
||||
is_post_ln=False,
|
||||
):
|
||||
"""
|
||||
等价实现:
|
||||
residual = input + residual.float() * alpha + residual_bias
|
||||
output = torch.nn.functional.layer_norm(
|
||||
residual, normalized_shape, weight, bias, eps=eps
|
||||
)
|
||||
residual = output if is_post_ln else residual
|
||||
|
||||
Args:
|
||||
input: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
|
||||
normalized_shape list[int]
|
||||
weight: (hidden_size) torch.float16, torch.bfloat16, torch.float32
|
||||
bias: (hidden_size) torch.float16, torch.bfloat16, torch.float32
|
||||
residual: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
|
||||
residual_bias: (hidden_size) torch.float16, torch.bfloat16, torch.float32
|
||||
alpha: float32
|
||||
eps: float32
|
||||
is_post_ln: bool
|
||||
Returns:
|
||||
input: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
|
||||
residual: (..., hidden_size) torch.float16, torch.bfloat16, torch.float32
|
||||
Inplace operation will be performed on residual and input.
|
||||
"""
|
||||
if (
|
||||
weight is None
|
||||
or bias is None
|
||||
or residual is None
|
||||
or weight.dim() > 1
|
||||
or bias.dim() > 1
|
||||
):
|
||||
raise NotImplementedError(
|
||||
"residual_layer_norm only support weight.dim() ==1 and bias.dim()==1!"
|
||||
)
|
||||
if normalized_shape == None:
|
||||
norm_size = weight.size(-1)
|
||||
normalized_shape = [norm_size]
|
||||
else:
|
||||
if (
|
||||
isinstance(normalized_shape, list) or isinstance(normalized_shape, tuple)
|
||||
) and len(normalized_shape) == 1:
|
||||
norm_size = normalized_shape[0]
|
||||
else:
|
||||
raise ValueError(
|
||||
f"residual_layer_norm(): argument 'normalized_shape' (position 2) must be tuple of ints and length of tuple is equal to 1, not {type(normalized_shape)}"
|
||||
)
|
||||
|
||||
if norm_size != weight.size(-1):
|
||||
raise ValueError(
|
||||
f"residual_layer_norm(): argument 'norm_size' must == weight.size(-1)"
|
||||
)
|
||||
|
||||
ops.infer.residual_layer_norm(
|
||||
input, residual, weight, bias, residual_bias, None, None, alpha, eps, is_post_ln
|
||||
)
|
||||
|
||||
return input, residual
|
||||
|
||||
|
||||
def ref_layer_norm_2sb_fused(
|
||||
input: torch.Tensor,
|
||||
normalized_shape: List[int],
|
||||
weight1: torch.Tensor,
|
||||
bias1: torch.Tensor,
|
||||
weight2: torch.Tensor,
|
||||
bias2: torch.Tensor,
|
||||
eps: float = 1e-5,
|
||||
):
|
||||
assert input.shape[-1] <= 16384
|
||||
if not (input.dtype == torch.float16 or input.dtype == torch.bfloat16):
|
||||
raise NotImplementedError(
|
||||
"layer_norm_2sb() only support data format of float16 or bfloat16 now!"
|
||||
)
|
||||
if (
|
||||
weight1 is None
|
||||
or bias1 is None
|
||||
or weight2 is None
|
||||
or bias2 is None
|
||||
or weight1.dim() > 1
|
||||
or bias1.dim() > 1
|
||||
or weight2.dim() > 1
|
||||
or bias2.dim() > 1
|
||||
):
|
||||
raise NotImplementedError(
|
||||
"layer_norm_2sb only support weight1.dim() ==1, bias1.dim()==1, weight2.dim() ==1 and bias2.dim()==1 !"
|
||||
)
|
||||
if normalized_shape == None:
|
||||
norm_size = weight1.size(-1)
|
||||
normalized_shape = [norm_size]
|
||||
else:
|
||||
if (
|
||||
isinstance(normalized_shape, list) or isinstance(normalized_shape, tuple)
|
||||
) and len(normalized_shape) == 1:
|
||||
norm_size = normalized_shape[0]
|
||||
else:
|
||||
raise ValueError(
|
||||
f"layer_norm_2sb(): argument 'normalized_shape' (position 2) must be tuple of ints and length of tuple is equal to 1, not {type(normalized_shape)}"
|
||||
)
|
||||
|
||||
if norm_size != weight1.size(-1) or norm_size != weight2.size(-1):
|
||||
raise ValueError(
|
||||
f"layer_norm_2sb(): argument 'norm_size' must == weight.size(-1)"
|
||||
)
|
||||
|
||||
output1 = torch.nn.functional.layer_norm(
|
||||
input, normalized_shape, weight1, bias1, eps=eps
|
||||
)
|
||||
|
||||
output2 = torch.nn.functional.layer_norm(
|
||||
input, normalized_shape, weight2, bias2, eps=eps
|
||||
)
|
||||
|
||||
return output1, output2
|
||||
|
||||
|
||||
def layer_norm_2sb_fused(
|
||||
input: torch.Tensor,
|
||||
normalized_shape: List[int],
|
||||
weight1: torch.Tensor,
|
||||
bias1: torch.Tensor,
|
||||
weight2: torch.Tensor,
|
||||
bias2: torch.Tensor,
|
||||
eps: float = 1e-5,
|
||||
):
|
||||
"""
|
||||
等价实现:
|
||||
output1 = torch.nn.functional.layer_norm(
|
||||
input, normalized_shape, weight1, bias1, eps=eps
|
||||
)
|
||||
output2 = torch.nn.functional.layer_norm(
|
||||
input, normalized_shape, weight2, bias2, eps=eps
|
||||
)
|
||||
|
||||
Args:
|
||||
input: (..., hidden_size) torch.float16, torch.bfloat16
|
||||
normalized_shape list[int]
|
||||
weight1: (hidden_size) torch.float16, torch.bfloat16
|
||||
bias1: (hidden_size) torch.float16, torch.bfloat16
|
||||
weight2: (hidden_size) torch.float16, torch.bfloat16
|
||||
bias2: (hidden_size) torch.float16, torch.bfloat16
|
||||
eps: float32
|
||||
Returns:
|
||||
output1: (..., hidden_size) torch.float16, torch.bfloat16
|
||||
output2: (..., hidden_size) torch.float16, torch.bfloat16
|
||||
"""
|
||||
assert input.shape[-1] <= 16384
|
||||
if not (input.dtype == torch.float16 or input.dtype == torch.bfloat16):
|
||||
raise NotImplementedError(
|
||||
"layer_norm_2sb() only support data format of float16 or bfloat16 now!"
|
||||
)
|
||||
if (
|
||||
weight1 is None
|
||||
or bias1 is None
|
||||
or weight2 is None
|
||||
or bias2 is None
|
||||
or weight1.dim() > 1
|
||||
or bias1.dim() > 1
|
||||
or weight2.dim() > 1
|
||||
or bias2.dim() > 1
|
||||
):
|
||||
raise NotImplementedError(
|
||||
"layer_norm_2sb only support weight1.dim() ==1, bias1.dim()==1, weight2.dim() ==1 and bias2.dim()==1 !"
|
||||
)
|
||||
if normalized_shape == None:
|
||||
norm_size = weight1.size(-1)
|
||||
normalized_shape = [norm_size]
|
||||
else:
|
||||
if (
|
||||
isinstance(normalized_shape, list) or isinstance(normalized_shape, tuple)
|
||||
) and len(normalized_shape) == 1:
|
||||
norm_size = normalized_shape[0]
|
||||
else:
|
||||
raise ValueError(
|
||||
f"layer_norm_2sb(): argument 'normalized_shape' (position 2) must be tuple of ints and length of tuple is equal to 1, not {type(normalized_shape)}"
|
||||
)
|
||||
|
||||
if norm_size != weight1.size(-1) or norm_size != weight2.size(-1):
|
||||
raise ValueError(
|
||||
f"layer_norm_2sb(): argument 'norm_size' must == weight.size(-1)"
|
||||
)
|
||||
output1 = torch.empty_like(input)
|
||||
output2 = torch.empty_like(input)
|
||||
ops.infer.layer_norm_2sb(
|
||||
input, weight1, bias1, weight2, bias2, eps, output1, output2
|
||||
)
|
||||
|
||||
return output1, output2
|
||||
277
ixformer_sdk/inference/functions/lightllm.py
Normal file
277
ixformer_sdk/inference/functions/lightllm.py
Normal file
@@ -0,0 +1,277 @@
|
||||
from typing import Union
|
||||
|
||||
import ixformer._C as ops
|
||||
import torch
|
||||
|
||||
__all__ = [
|
||||
"lightllm_tokenattention",
|
||||
"ref_lightllm_tokenattention",
|
||||
"lightllm_destindex_copy_kv",
|
||||
"ref_lightllm_destindex_copy_kv",
|
||||
"lightllm_apply_penalty",
|
||||
"ref_lightllm_apply_penalty",
|
||||
"lightllm_glm2_rope",
|
||||
"ref_lightllm_glm2_rope",
|
||||
]
|
||||
|
||||
|
||||
def ref_lightllm_glm2_rope(
|
||||
x: torch.Tensor, # tokens,head_num,head_dim
|
||||
cos: torch.Tensor, # tokens,rotdim
|
||||
sin: torch.Tensor,
|
||||
):
|
||||
num_tokens, _, rot_dim = list(cos.shape)
|
||||
head_num = x.shape[1]
|
||||
x12 = x[:, :, : rot_dim * 2]
|
||||
x3 = x[:, :, rot_dim * 2 :]
|
||||
x12 = x12.reshape(num_tokens, head_num, rot_dim, 2)
|
||||
x1 = x12[:, :, :, 0]
|
||||
x2 = x12[:, :, :, 1]
|
||||
|
||||
# out0 = q0 * cos - q1 * sin
|
||||
# out1 = q0 * sin + q1 * cos
|
||||
# q1, q2 是沿着 head_dim维度,交叉取值的
|
||||
|
||||
q1 = x1 * cos - x2 * sin
|
||||
q2 = x2 * cos + x1 * sin
|
||||
|
||||
q12 = torch.stack([q1, q2], dim=-1)
|
||||
q12 = q12.reshape(num_tokens, head_num, -1)
|
||||
x_pytorch = torch.cat([q12, x3], dim=-1)
|
||||
return x_pytorch
|
||||
|
||||
|
||||
def lightllm_glm2_rope(
|
||||
x: torch.Tensor, # tokens,head_num,head_dim
|
||||
cos: torch.Tensor, # tokens,rotdim
|
||||
sin: torch.Tensor,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
x: (num_tokens, head_num, head_dim) torch.half
|
||||
cos: (num_tokens,1,head_dim//2//2) torch.half
|
||||
sin: (num_tokens,1,head_dim//2//2) torch.half
|
||||
Returns:
|
||||
x: (num_tokens, head_num, head_dim) torch.half
|
||||
|
||||
"""
|
||||
if isinstance(x, torch.Tensor):
|
||||
ops.infer.lightllm_glm2_rope(x, cos, sin)
|
||||
return x
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def ref_lightllm_apply_penalty(
|
||||
Logits: torch.Tensor,
|
||||
presence_penalty: torch.Tensor,
|
||||
freqency_penalty: torch.Tensor,
|
||||
p_token_ids: torch.Tensor,
|
||||
p_token_counts: torch.Tensor,
|
||||
p_cumsum_seq_len: torch.Tensor,
|
||||
p_max_len_in_batch: int,
|
||||
):
|
||||
batch_size = Logits.size(0)
|
||||
output = Logits.clone()
|
||||
for cur_batch in range(batch_size):
|
||||
cur_freqency = freqency_penalty[cur_batch]
|
||||
cur_presence = presence_penalty[cur_batch]
|
||||
cur_batch_start_index = p_cumsum_seq_len[cur_batch]
|
||||
cur_batch_end_index = p_cumsum_seq_len[cur_batch + 1]
|
||||
for token_idx in range(cur_batch_start_index, cur_batch_end_index):
|
||||
batch_ids = p_token_ids[token_idx]
|
||||
batch_ids_count = p_token_counts[token_idx]
|
||||
cur_logits = output[cur_batch][batch_ids]
|
||||
|
||||
freq_logits = cur_logits - batch_ids_count * cur_freqency
|
||||
pre_logits = freq_logits - cur_presence
|
||||
# if token_idx==0:
|
||||
# print(f"batch_ids {batch_ids} cur_logits {cur_logits} pre_logits {pre_logits}")
|
||||
output[cur_batch][batch_ids] = pre_logits
|
||||
return output
|
||||
|
||||
|
||||
def lightllm_apply_penalty(
|
||||
Logits: torch.Tensor,
|
||||
presence_penalty: torch.Tensor,
|
||||
freqency_penalty: torch.Tensor,
|
||||
p_token_ids: torch.Tensor,
|
||||
p_token_counts: torch.Tensor,
|
||||
p_cumsum_seq_len: torch.Tensor,
|
||||
p_max_len_in_batch: int,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
logits: (batch_size, vocab_size) torch.float
|
||||
presence_penalty: (batch_size) torch.float
|
||||
freqency_penalty: (batch_size) torch.float
|
||||
p_token_ids: (num_tokens) torch.int
|
||||
p_token_counts: (num_tokens) torch.int
|
||||
p_cumsum_seq_len: (batch_size+1) torch.int
|
||||
p_max_len_in_batch: int
|
||||
在一个batch中seq的最大长度
|
||||
Returns:
|
||||
logits: (batch_size, vocab_size) torch.float
|
||||
"""
|
||||
if isinstance(Logits, torch.Tensor):
|
||||
ops.infer.lightllm_apply_penalty(
|
||||
Logits,
|
||||
presence_penalty,
|
||||
freqency_penalty,
|
||||
p_token_ids,
|
||||
p_token_counts,
|
||||
p_cumsum_seq_len,
|
||||
p_max_len_in_batch,
|
||||
)
|
||||
return Logits
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
|
||||
|
||||
def ref_lightllm_destindex_copy_kv(
|
||||
key_cache: torch.Tensor,
|
||||
mem_idx: torch.Tensor,
|
||||
output: torch.Tensor,
|
||||
):
|
||||
if key_cache.dim() != 3 or key_cache.size(-1) != 128:
|
||||
raise NotImplementedError(
|
||||
"lightllm_destindex_copy_kv only support key_cache.dim()==3 and head_size ==128 !"
|
||||
)
|
||||
output[mem_idx.long()] = key_cache
|
||||
return output
|
||||
|
||||
|
||||
def lightllm_destindex_copy_kv(
|
||||
key_cache: torch.Tensor,
|
||||
mem_idx: torch.Tensor,
|
||||
output: torch.Tensor,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
key_cache: (tokens, num_kv_heads, head_size) torch.half
|
||||
目前head_size 只支持128的情况
|
||||
mem_idx: (tokens) torch.int
|
||||
output: (max_tokens, num_kv_heads, head_size) torch.half
|
||||
Returns:
|
||||
output: (max_tokens, num_kv_heads, head_size) torch.half
|
||||
"""
|
||||
|
||||
if key_cache.dim() != 3 or key_cache.size(-1) != 128:
|
||||
raise NotImplementedError(
|
||||
"lightllm_destindex_copy_kv only support key_cache.dim()==3 and head_size ==128 !"
|
||||
)
|
||||
if isinstance(key_cache, torch.Tensor):
|
||||
ops.infer.lightllm_destindex_copy_kv(key_cache, mem_idx, output)
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
return output
|
||||
|
||||
|
||||
def ref_lightllm_tokenattention(
|
||||
query: torch.Tensor,
|
||||
key_cache: torch.Tensor,
|
||||
value_cache: torch.Tensor,
|
||||
reg_tokens: torch.Tensor,
|
||||
b_req_idx: torch.Tensor,
|
||||
b_seq_len: torch.Tensor,
|
||||
scale: float,
|
||||
max_context_len: int,
|
||||
):
|
||||
batch_size, tp_q_head_num_, head_dim_ = query.shape
|
||||
tp_k_head_num_ = key_cache.size(-2)
|
||||
sm_scale = scale
|
||||
curbatch_max_context_len = max_context_len
|
||||
tmp_k = torch.zeros(
|
||||
(batch_size, tp_q_head_num_, curbatch_max_context_len, head_dim_),
|
||||
dtype=query.dtype,
|
||||
device="cuda",
|
||||
)
|
||||
tmp_v = torch.zeros(
|
||||
(batch_size, tp_q_head_num_, curbatch_max_context_len, head_dim_),
|
||||
dtype=query.dtype,
|
||||
device="cuda",
|
||||
)
|
||||
mask = torch.ones([batch_size, 1, 1, curbatch_max_context_len])
|
||||
|
||||
kv_group_num = tp_q_head_num_ // tp_k_head_num_
|
||||
for cur_batch in range(batch_size):
|
||||
cur_batch_req_idx = b_req_idx[cur_batch]
|
||||
seq_len = b_seq_len[cur_batch]
|
||||
mask[cur_batch, :, :, :seq_len] = 0
|
||||
# print(f"cur_batch {cur_batch}")
|
||||
|
||||
for seq_idx in range(seq_len):
|
||||
k_loc = reg_tokens[cur_batch_req_idx][seq_idx]
|
||||
# print(k_loc)
|
||||
for cur_head in range(tp_q_head_num_):
|
||||
cur_kv_head = cur_head // kv_group_num
|
||||
tmp_k[cur_batch, cur_head, seq_idx, :] = key_cache[k_loc][cur_kv_head]
|
||||
tmp_v[cur_batch, cur_head, seq_idx, :] = value_cache[k_loc][cur_kv_head]
|
||||
mask = mask.cuda()
|
||||
# batch_size, self.tp_q_head_num_, 1, max_len_in_batch
|
||||
attn_score = (
|
||||
torch.matmul(
|
||||
query.view(batch_size, tp_q_head_num_, 1, head_dim_),
|
||||
tmp_k.transpose(-1, -2),
|
||||
)
|
||||
* sm_scale
|
||||
)
|
||||
attn_score = attn_score + mask * -1000
|
||||
attn_score = torch.softmax(attn_score, dim=-1)
|
||||
# batch_size, self.tp_q_head_num_, 1, head_dim
|
||||
py_out = torch.matmul(attn_score.to(query.dtype), tmp_v).view(
|
||||
batch_size, tp_q_head_num_, -1
|
||||
)
|
||||
return py_out
|
||||
|
||||
|
||||
def lightllm_tokenattention(
|
||||
query: torch.Tensor,
|
||||
key_cache: torch.Tensor,
|
||||
value_cache: torch.Tensor,
|
||||
reg_tokens: torch.Tensor,
|
||||
b_req_idx: torch.Tensor,
|
||||
b_seq_len: torch.Tensor,
|
||||
scale: float,
|
||||
max_context_len: int,
|
||||
partition: int,
|
||||
output: torch.Tensor,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
query: (batch_size,head_num,head_dim) torch.float16, torch.bfloat16
|
||||
key_cache: (max_num_tokens, head_num_kv, head_dim) torch.float16, torch.bfloat16
|
||||
value_cache: (max_num_tokens, head_num_kv, head_dim) torch.float16, torch.bfloat16
|
||||
reg_tokens: (max_request,max_tokens) torch.int32
|
||||
目前max_tokens只支持3080
|
||||
b_req_idx: (batch_size) torch.int32
|
||||
b_req_len: (batch_size) torch.int32
|
||||
scale: float
|
||||
The scaling of QK^T before applying softmax.
|
||||
max_context_len: int
|
||||
b_seq_len.max()
|
||||
partition: int
|
||||
Returns:
|
||||
output: (batch_size,head_num,head_dim) torch.float16, torch.bfloat16
|
||||
"""
|
||||
_,max_tokens=reg_tokens.shape
|
||||
if not max_tokens == 3080:
|
||||
raise NotImplementedError(
|
||||
"lightllm_tokenattention only support reg_tokens.size(-1)==3080"
|
||||
)
|
||||
if isinstance(query, torch.Tensor):
|
||||
ops.infer.lightllm_tokenattention(
|
||||
query,
|
||||
key_cache,
|
||||
value_cache,
|
||||
reg_tokens,
|
||||
b_req_idx,
|
||||
b_seq_len,
|
||||
scale,
|
||||
max_context_len,
|
||||
partition,
|
||||
output,
|
||||
)
|
||||
else:
|
||||
raise NotImplementedError()
|
||||
return output
|
||||
50
ixformer_sdk/inference/functions/linalg.py
Normal file
50
ixformer_sdk/inference/functions/linalg.py
Normal file
@@ -0,0 +1,50 @@
|
||||
from typing import Union
|
||||
|
||||
import ixformer._C as ops
|
||||
import torch
|
||||
|
||||
__all__ = ["solve", "ref_slove"]
|
||||
|
||||
|
||||
def ref_slove(
|
||||
A: torch.Tensor, B: torch.Tensor, *, left: bool = True, out: torch.Tensor = None
|
||||
):
|
||||
out = torch.linalg.solve(A, B, left=left)
|
||||
return out
|
||||
|
||||
|
||||
def solve(
|
||||
A: torch.Tensor, B: torch.Tensor, *, left: bool = True, out: torch.Tensor = None
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
A: (..., n, n) torch.float
|
||||
B: (..., n) or (..., n, k) or (n,...) or (n, k) or (n) torch.float
|
||||
left: bool
|
||||
whether to solve the system AX=B or XA=B. Default: True, 目前只支持left =True
|
||||
out: (..., n, k) torch.float
|
||||
Returns:
|
||||
out: (..., n, k) torch.float
|
||||
"""
|
||||
|
||||
n = A.shape[-1]
|
||||
batch_count = A.numel() // (n * n)
|
||||
if B.dim() == 1:
|
||||
k = 1
|
||||
elif B.dim() == 2:
|
||||
if A.dim() > 2 and B.shape == (batch_count, n):
|
||||
k = 1
|
||||
else:
|
||||
nid = 0 if left else 1
|
||||
k = B.shape[nid ^ 1]
|
||||
else:
|
||||
k = B.size(B.dim() - 1 if left else B.dim() - 2)
|
||||
|
||||
if n <= 64 and k <= 64 and left:
|
||||
return ops.infer.solve(A, B, left)
|
||||
else:
|
||||
device = A.device
|
||||
cpu_A = A.cpu()
|
||||
cpu_B = B.cpu()
|
||||
cpu_res = ref_slove(A=cpu_A, B=cpu_B, left=left)
|
||||
return cpu_res.to(device)
|
||||
122
ixformer_sdk/inference/functions/linear.py
Normal file
122
ixformer_sdk/inference/functions/linear.py
Normal file
@@ -0,0 +1,122 @@
|
||||
import os
|
||||
from typing import Union
|
||||
|
||||
import ixformer._C as ops
|
||||
import torch
|
||||
|
||||
__all__ = ["linear", "ref_linear", "mixed_type_linear", "ref_mixed_type_linear"]
|
||||
|
||||
|
||||
def ref_linear(
|
||||
input: torch.Tensor,
|
||||
weight: torch.Tensor,
|
||||
bias: torch.Tensor = None,
|
||||
act_type=-1,
|
||||
):
|
||||
output = torch.nn.functional.linear(input, weight, bias)
|
||||
if act_type == -1:
|
||||
act_fn = torch.nn.Identity()
|
||||
elif act_type == 3:
|
||||
act_fn = torch.nn.GELU()
|
||||
elif act_type == 4:
|
||||
act_fn = torch.nn.ReLU()
|
||||
elif act_type == 12:
|
||||
act_fn = torch.nn.SiLU()
|
||||
else:
|
||||
raise KeyError("act_type not supported")
|
||||
output = act_fn(output)
|
||||
return output
|
||||
|
||||
|
||||
def gemv_conditions(input, weight, bias, gemv_max_batch):
|
||||
# gemv 使用的条件 input:[m,k] weight:[n,k]
|
||||
# 1. m<=gemv_max_batch
|
||||
# 2. k%32==0 n%2==0
|
||||
# 3. bias is None
|
||||
input = input.view(-1, input.shape[-1])
|
||||
weight = weight.view(-1, weight.shape[-1])
|
||||
m = input.shape[0]
|
||||
k = input.shape[1]
|
||||
n = weight.shape[0]
|
||||
if bias is None and m <= gemv_max_batch and k % 32 == 0 and n % 2 == 0:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
def linear(
|
||||
input: torch.Tensor,
|
||||
weight: torch.Tensor,
|
||||
bias: torch.Tensor = None,
|
||||
output: torch.Tensor = None,
|
||||
persistent: bool = False,
|
||||
act_type : int = -1,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
input: (...,k) torch.float16, torch.bfloat16
|
||||
weight: (n, k) torch.float16, torch.bfloat16
|
||||
bias: (n) torch.float16, torch.bfloat16
|
||||
output: (...,n) torch.float16, torch.bfloat16
|
||||
persistent: bool
|
||||
是否限制 Gemm Kernel 的 Block 数量
|
||||
Returns:
|
||||
output: (...,n) torch.float16, torch.bfloat16
|
||||
"""
|
||||
if not input.is_contiguous():
|
||||
input = input.contiguous()
|
||||
if not weight.is_contiguous():
|
||||
weight = weight.contiguous()
|
||||
use_gemv = True
|
||||
gemv_max_batch = 1
|
||||
disable_infer_gemm_ex = os.getenv("DISABLE_INFER_GEMM_EX", "0")
|
||||
use_gemv = (
|
||||
use_gemv
|
||||
and gemv_conditions(input, weight, bias, gemv_max_batch)
|
||||
and disable_infer_gemm_ex != "1"
|
||||
)
|
||||
|
||||
if output is None:
|
||||
output_shape = list(input.shape)
|
||||
output_shape[-1] = weight.shape[0]
|
||||
output = input.new_empty(output_shape)
|
||||
|
||||
if not use_gemv:
|
||||
output = ops.infer.linear(input, weight, act_type, bias, output, persistent)
|
||||
else:
|
||||
output = ops.infer.linear_ex(input, weight, bias, output)
|
||||
return output
|
||||
|
||||
|
||||
def ref_mixed_type_linear(
|
||||
input: torch.Tensor,
|
||||
weight: torch.Tensor,
|
||||
bias: torch.Tensor = None,
|
||||
output: torch.Tensor = None,
|
||||
persistent=False, # TODO: support persistent
|
||||
):
|
||||
input = input.to(weight.dtype)
|
||||
if bias:
|
||||
bias = bias.to(weight.dtype)
|
||||
output = torch.nn.functional.linear(input, weight, bias)
|
||||
return output
|
||||
|
||||
|
||||
def mixed_type_linear(
|
||||
input: torch.Tensor,
|
||||
weight: torch.Tensor,
|
||||
bias: torch.Tensor = None,
|
||||
output: torch.Tensor = None,
|
||||
persistent=False, # TODO: support persistent
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
input: (...,k) torch.half, torch.bfloat16
|
||||
weight: (m, k) torch.float32
|
||||
bias: not supported
|
||||
output: (...,m) torch.float32
|
||||
persistent: bool
|
||||
Returns:
|
||||
output: (...,m) torch.float32
|
||||
"""
|
||||
output = ops.infer.mixed_type_linear(input, weight, bias, output)
|
||||
return output
|
||||
212
ixformer_sdk/inference/functions/lmdeploy.py
Normal file
212
ixformer_sdk/inference/functions/lmdeploy.py
Normal file
@@ -0,0 +1,212 @@
|
||||
import math
|
||||
from typing import Literal, Optional, Union
|
||||
|
||||
import ixformer._C as ops
|
||||
import ixformer._C._functions as CF
|
||||
import torch
|
||||
|
||||
from ixformer.core import config
|
||||
|
||||
from .linear import linear
|
||||
from .paged_attention import paged_attention as paged_attention_ixformer_impl
|
||||
|
||||
__all__ = [
|
||||
"ref_lmdeploy_paged_attention",
|
||||
"lmdeploy_paged_attention",
|
||||
]
|
||||
|
||||
weak_ref_tensor = ops.infer.weak_ref_tensor
|
||||
|
||||
|
||||
def ref_lmdeploy_paged_attention(
|
||||
output: torch.Tensor,
|
||||
query: torch.Tensor,
|
||||
key_cache: torch.Tensor,
|
||||
value_cache: torch.Tensor,
|
||||
num_kv_heads: torch.Tensor,
|
||||
scale: float,
|
||||
block_tables: torch.Tensor,
|
||||
context_lens: torch.Tensor,
|
||||
block_size: int,
|
||||
max_context_len: int,
|
||||
alibi_slopes: torch.Tensor = None,
|
||||
softcap: float = 0.0,
|
||||
window_left: int = -1,
|
||||
window_right: int = -1,
|
||||
use_sqrt_alibi: bool = False,
|
||||
quant_type: int = 0,
|
||||
is_bbhh: bool = False,
|
||||
):
|
||||
assert window_right in [-1, 0]
|
||||
|
||||
if is_bbhh:
|
||||
key_cache = key_cache.permute(0, 2, 1, 3).contiguous()
|
||||
value_cache = value_cache.permute(0, 2, 1, 3).contiguous()
|
||||
|
||||
def get_alibi_mask(num_heads, seqlen, device, dtype):
|
||||
x = torch.arange(0, seqlen, device=device, dtype=torch.float32).view(-1, 1)
|
||||
y = torch.tensor(seqlen - 1, device=device, dtype=torch.float32).view(1, -1)
|
||||
offsets = -(y - x).view(1, 1, seqlen)
|
||||
return offsets
|
||||
|
||||
def ref_masked_attention(
|
||||
query: torch.Tensor,
|
||||
key: torch.Tensor,
|
||||
value: torch.Tensor,
|
||||
scale: float,
|
||||
attn_mask: Optional[torch.Tensor] = None,
|
||||
) -> torch.Tensor:
|
||||
query = query * scale
|
||||
dtype = query.dtype
|
||||
device = query.device
|
||||
query = query.to(torch.float32)
|
||||
key = key.to(torch.float32)
|
||||
value = value.to(torch.float32)
|
||||
attn = torch.einsum("qhd,khd->hqk", query, key)
|
||||
if attn_mask is not None:
|
||||
attn_mask = attn_mask
|
||||
attn = attn + attn_mask
|
||||
attn = torch.softmax(attn, dim=-1)
|
||||
out = torch.einsum("hqk,khd->qhd", attn, value)
|
||||
out = out.to(device).to(dtype)
|
||||
return out
|
||||
|
||||
head_size = query.shape[-1]
|
||||
num_query_heads = query.shape[1]
|
||||
num_kv_heads = value_cache.shape[1]
|
||||
num_input_tokens = query.shape[0]
|
||||
|
||||
num_q_per_kv = num_query_heads // num_kv_heads
|
||||
slopes = (
|
||||
alibi_slopes.view(num_query_heads, 1, 1)
|
||||
if alibi_slopes is not None
|
||||
else alibi_slopes
|
||||
)
|
||||
|
||||
for i in range(num_input_tokens):
|
||||
q = query[i].unsqueeze(0)
|
||||
block_table = block_tables[i]
|
||||
context_len = int(context_lens[i])
|
||||
|
||||
keys = []
|
||||
values = []
|
||||
for j in range(context_len):
|
||||
block_number = int(block_table[j // block_size])
|
||||
block_offset = j % block_size
|
||||
|
||||
k = key_cache[block_number, :, block_offset, :]
|
||||
keys.append(k)
|
||||
|
||||
v = value_cache[block_number, :, block_offset, :]
|
||||
values.append(v)
|
||||
keys = torch.stack(keys, dim=0)
|
||||
values = torch.stack(values, dim=0)
|
||||
if num_q_per_kv > 1:
|
||||
keys = torch.repeat_interleave(keys, num_q_per_kv, dim=1)
|
||||
values = torch.repeat_interleave(values, num_q_per_kv, dim=1)
|
||||
if alibi_slopes is not None:
|
||||
offsets = get_alibi_mask(
|
||||
num_query_heads, context_len, output.device, output.dtype
|
||||
)
|
||||
mask = offsets * slopes
|
||||
mask = mask.to(output.dtype)
|
||||
if window_left != -1:
|
||||
index = torch.ones_like(mask, dtype=torch.int32, device=mask.device)
|
||||
index[:, :, (context_len - 1 - window_left) :] = 0
|
||||
index = index.bool()
|
||||
mask.masked_fill_(index, float("-inf"))
|
||||
else:
|
||||
if window_left != -1:
|
||||
mask = torch.zeros([1, 1, context_len], dtype=q.dtype, device=q.device)
|
||||
index = torch.ones_like(mask, dtype=torch.int32, device=mask.device)
|
||||
index[:, :, (context_len - 1 - window_left) :] = 0
|
||||
index = index.bool()
|
||||
mask.masked_fill_(index, float("-inf"))
|
||||
else:
|
||||
mask = None
|
||||
|
||||
out = ref_masked_attention(
|
||||
q,
|
||||
keys,
|
||||
values,
|
||||
scale,
|
||||
mask,
|
||||
)
|
||||
out = out.view(num_query_heads, head_size)
|
||||
if softcap != 0.0:
|
||||
out = softcap * torch.tanh(out / softcap)
|
||||
output[i].copy_(out, non_blocking=True)
|
||||
|
||||
return output
|
||||
|
||||
|
||||
def lmdeploy_paged_attention(
|
||||
output: torch.Tensor,
|
||||
query: torch.Tensor,
|
||||
key_cache: torch.Tensor,
|
||||
value_cache: torch.Tensor,
|
||||
num_kv_heads: torch.Tensor,
|
||||
scale: float,
|
||||
block_tables: torch.Tensor,
|
||||
context_lens: torch.Tensor,
|
||||
block_size: int,
|
||||
max_context_len: int,
|
||||
alibi_slopes: torch.Tensor = None,
|
||||
softcap: float = 0.0,
|
||||
causal: bool = True,
|
||||
window_left: int = -1,
|
||||
window_right: int = -1,
|
||||
use_cuda_graph: bool = False,
|
||||
use_sqrt_alibi: bool = False,
|
||||
quant_type: int = 0,
|
||||
is_bbhh: bool = False,
|
||||
):
|
||||
"""
|
||||
is_bbhh = False key_cache, value_cache: [num_blocks, block_size, num_kv_heads, head_size]
|
||||
is_bbhh = True key_cache, value_cache: [num_blocks, num_kv_heads, block_size, head_size]
|
||||
|
||||
is_bbhh = False
|
||||
Arguments:
|
||||
query: [torch.half, torch.bfloat16] [num_tokens, num_heads, head_size]
|
||||
key_cache: [torch.half, torch.bfloat16] [num_blocks, num_kv_heads, block_size, head_size]
|
||||
value_cache: [torch.half, torch.bfloat16] [num_blocks, num_kv_heads, block_size, head_size]
|
||||
num_kv_heads: int
|
||||
scale: float
|
||||
block_tables: [torch.int64] [num_tokens, max_num_blocks_per_seq]
|
||||
context_lens: [torch.int32] [num_tokens]
|
||||
block_size: int
|
||||
max_context_len: int
|
||||
alibi_slopes: [torch.float32] [num_heads]
|
||||
softcap: float
|
||||
causal: bool
|
||||
window_left: int
|
||||
window_right: int
|
||||
use_sqrt_alibi: bool: False
|
||||
Return:
|
||||
output: [torch.half, torch.bfloat16] [num_tokens, num_heads, head_size]
|
||||
"""
|
||||
|
||||
ops.infer.lmdeploy_paged_attention(
|
||||
output,
|
||||
query,
|
||||
key_cache,
|
||||
value_cache,
|
||||
num_kv_heads,
|
||||
scale,
|
||||
block_tables,
|
||||
context_lens,
|
||||
block_size,
|
||||
max_context_len,
|
||||
alibi_slopes,
|
||||
causal,
|
||||
window_left,
|
||||
window_right,
|
||||
softcap,
|
||||
use_cuda_graph,
|
||||
use_sqrt_alibi,
|
||||
is_bbhh,
|
||||
quant_type,
|
||||
)
|
||||
return output
|
||||
|
||||
# lmdeploy_paged_attention = lmdeploy_paged_attention_ixinfer
|
||||
234
ixformer_sdk/inference/functions/marlin.py
Normal file
234
ixformer_sdk/inference/functions/marlin.py
Normal file
@@ -0,0 +1,234 @@
|
||||
import ixformer._C as ops
|
||||
import torch
|
||||
|
||||
__all__ = [
|
||||
"marlin_w4a16",
|
||||
"marlin_w4_weight_repack",
|
||||
"marlin_w8a16",
|
||||
"marlin_w8_weight_repack",
|
||||
]
|
||||
|
||||
|
||||
def marlin_w4a16(
|
||||
inputs: torch.Tensor,
|
||||
weights: torch.Tensor,
|
||||
scales: torch.Tensor,
|
||||
zeros: torch.Tensor,
|
||||
bias: torch.Tensor = None, # TODO
|
||||
group_size: int = -1,
|
||||
format: str = "k16n32",
|
||||
batch_first: bool = True,
|
||||
outputs: torch.Tensor = None,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
inputs: (batch, m, k) if batch_first else (m, batch, k) torch.float16, torch.bfloat16
|
||||
weights: (batch, k/16, n/32, 64) torch.int32
|
||||
scales:
|
||||
(batch, k_groups, n) format:k16n32 torch.float16, torch.bfloat16
|
||||
(batch, n_groups, k) format:k16n32_grouped_n torch.float16, torch.bfloat16
|
||||
zeros:
|
||||
(batch, k_groups, n/8) format:k16n32 torch.int32
|
||||
(batch, n_groups, k/8) format:k16n32_grouped_n torch.int32
|
||||
group_size: int
|
||||
group size of quant
|
||||
format: str
|
||||
describe format of weight
|
||||
batch_first: bool
|
||||
describe format of input and output
|
||||
Returns:
|
||||
outputs: (batch, m, n) if batch_first else (m, batch, n) torch.float16, torch.bfloat16
|
||||
"""
|
||||
if outputs is None:
|
||||
batch, m = (
|
||||
(inputs.shape[0], inputs.shape[1])
|
||||
if batch_first
|
||||
else (inputs.shape[1], inputs.shape[0])
|
||||
)
|
||||
if format.startswith("k16n32"):
|
||||
n = weights.shape[2] * 32
|
||||
outputs = torch.empty(
|
||||
(batch, m, n) if batch_first else (m, batch, n),
|
||||
dtype=inputs.dtype,
|
||||
device=inputs.device,
|
||||
)
|
||||
|
||||
ops.infer.marlin_w4a16(
|
||||
outputs, inputs, weights, scales, zeros, bias, group_size, format, batch_first
|
||||
)
|
||||
return outputs
|
||||
|
||||
|
||||
def marlin_w4_weight_repack(
|
||||
weights: torch.Tensor,
|
||||
scales: torch.Tensor = None,
|
||||
zeros: torch.Tensor = None,
|
||||
weight_format: str = "gptq",
|
||||
reformat: str = "k16n32",
|
||||
pack_order: str = "default",
|
||||
repack_weight: torch.Tensor = None,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
weights:
|
||||
(batch, k, n/8) weight_format:awq torch.int32
|
||||
(batch, k/8, n) weight_format:gptq torch.int32
|
||||
scales:
|
||||
(batch, k_groups, n) format:k16n32 torch.float16, torch.bfloat16
|
||||
(batch, n_groups, k) format:k16n32_grouped_n torch.float16, torch.bfloat16
|
||||
zeros:
|
||||
(batch, k_groups, n/8) format:k16n32 torch.int32
|
||||
(batch, n_groups, k/8) format:k16n32_grouped_n torch.int32
|
||||
weight_format: str
|
||||
describe format of weight
|
||||
reformat: str
|
||||
describe format of repacked weight
|
||||
pack_order: str
|
||||
describe pack order on a pack unit
|
||||
Returns:
|
||||
repack_weight: (batch, k/16, n/32, 64) torch.int32
|
||||
"""
|
||||
assert weight_format in ["gptq", "gptq_grouped_n", "awq"]
|
||||
assert reformat in ["k16n32", "k16n32_grouped_n"]
|
||||
assert pack_order in ["default", "02461357", "01234567"]
|
||||
|
||||
if pack_order == "default":
|
||||
default_order = {
|
||||
"gptq": "01234567",
|
||||
"awq": "02461357",
|
||||
"gptq_grouped_n": "02461357",
|
||||
}
|
||||
pack_order = default_order[weight_format]
|
||||
|
||||
if weight_format.startswith("gptq"):
|
||||
batch, pack_k, n = weights.shape
|
||||
k = pack_k * 8
|
||||
elif weight_format == "awq":
|
||||
batch, k, pack_n = weights.shape
|
||||
n = pack_n * 8
|
||||
|
||||
repack_scales, repack_zeros = None, None
|
||||
if reformat.startswith("k16n32"):
|
||||
if repack_weight is None:
|
||||
repack_weight = torch.empty(
|
||||
(batch, k // 16, n // 32, 64),
|
||||
dtype=torch.int32,
|
||||
device=weights.device,
|
||||
)
|
||||
if scales is not None:
|
||||
repack_scales = torch.empty_like(scales)
|
||||
if zeros is not None:
|
||||
repack_zeros = torch.empty_like(zeros)
|
||||
|
||||
ops.infer.marlin_w4_weight_repack(
|
||||
weights,
|
||||
repack_weight,
|
||||
scales,
|
||||
repack_scales,
|
||||
zeros,
|
||||
repack_zeros,
|
||||
weight_format,
|
||||
reformat,
|
||||
pack_order,
|
||||
)
|
||||
|
||||
if repack_scales is not None and repack_zeros is not None:
|
||||
return repack_weight, repack_scales, repack_zeros
|
||||
else:
|
||||
return repack_weight
|
||||
|
||||
|
||||
def marlin_w8a16(
|
||||
inputs: torch.Tensor,
|
||||
weights: torch.Tensor,
|
||||
scales: torch.Tensor,
|
||||
bias: torch.Tensor = None, # TODO
|
||||
group_size: int = -1,
|
||||
format: str = "k16n16",
|
||||
batch_first: bool = True,
|
||||
outputs: torch.Tensor = None,
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
inputs: (batch, m, k) if batch_first else (m, batch, k) torch.float16, torch.bfloat16
|
||||
weights: (batch, k/16, n/16, 64) torch.int32
|
||||
scales:
|
||||
(batch, k_groups, n) format:k16n16 torch.float32
|
||||
(batch, n_groups, k) format:k16n16_grouped_n torch.float32
|
||||
group_size: int
|
||||
group size of quant
|
||||
format: str
|
||||
describe format of weight
|
||||
batch_first: bool
|
||||
describe format of input and output
|
||||
Returns:
|
||||
outputs: (batch, m, n) if batch_first else (m, batch, n) torch.float16, torch.bfloat16
|
||||
"""
|
||||
if outputs is None:
|
||||
batch, m = (
|
||||
(inputs.shape[0], inputs.shape[1])
|
||||
if batch_first
|
||||
else (inputs.shape[1], inputs.shape[0])
|
||||
)
|
||||
if format.startswith("k16n16"):
|
||||
n = weights.shape[2] * 16
|
||||
outputs = torch.empty(
|
||||
(batch, m, n) if batch_first else (m, batch, n),
|
||||
dtype=inputs.dtype,
|
||||
device=inputs.device,
|
||||
)
|
||||
|
||||
ops.infer.marlin_w8a16(
|
||||
outputs, inputs, weights, scales, bias, group_size, format, batch_first
|
||||
)
|
||||
return outputs
|
||||
|
||||
|
||||
def marlin_w8_weight_repack(
|
||||
weights: torch.Tensor,
|
||||
scales: torch.Tensor = None,
|
||||
weight_format: str = "int8",
|
||||
reformat: str = "k16n16",
|
||||
):
|
||||
"""
|
||||
Args:
|
||||
weights:
|
||||
(batch, k, n) weight_format:int8 torch.int8
|
||||
scales:
|
||||
(batch, k_groups, n) format:k16n16 torch.float32
|
||||
(batch, n_groups, k) format:k16n16_grouped_n torch.float32
|
||||
weight_format: str
|
||||
describe format of weight
|
||||
reformat: str
|
||||
describe format of repacked weight
|
||||
Returns:
|
||||
repack_weight:
|
||||
(batch, k/16, n/16, 64) torch.int32
|
||||
"""
|
||||
assert weight_format in ["int8"]
|
||||
assert reformat in ["k16n16", "k16n16_grouped_n"]
|
||||
|
||||
repack_scales = None
|
||||
if weight_format == "int8":
|
||||
batch, k, n = weights.shape
|
||||
repack_weight = torch.empty(
|
||||
(batch, k // 16, n // 16, 64),
|
||||
dtype=torch.int32,
|
||||
device=weights.device,
|
||||
)
|
||||
if scales is not None:
|
||||
repack_scales = torch.empty_like(scales)
|
||||
|
||||
ops.infer.marlin_w8_weight_repack(
|
||||
weights,
|
||||
repack_weight,
|
||||
scales,
|
||||
repack_scales,
|
||||
weight_format,
|
||||
reformat,
|
||||
)
|
||||
|
||||
if repack_scales is not None:
|
||||
return repack_weight, repack_scales
|
||||
else:
|
||||
return repack_weight
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user