Files
2026-07-13 12:37:18 +08:00

296 lines
9.4 KiB
Python

# -*- coding:utf-8 -*-
# Author: hankcs
# Date: 2020-05-09 15:52
import os
import random
import time
from typing import List, Union, Dict, Tuple
import numpy as np
import torch
from pynvml import nvmlDeviceGetHandleByIndex, nvmlDeviceGetMemoryInfo, nvmlInit, nvmlShutdown, nvmlDeviceGetCount
from torch import nn
from torch.nn.utils.rnn import pad_sequence
from hanlp.utils.io_util import get_resource, replace_ext, TimingFileIterator
from hanlp.utils.log_util import logger, flash
from hanlp_common.constant import HANLP_VERBOSE
from hanlp_common.io import load_pickle, save_pickle
def gpus_available() -> Dict[int, float]:
if not torch.cuda.is_available():
return dict()
try:
nvmlInit()
gpus = {}
visible_devices = os.environ.get('CUDA_VISIBLE_DEVICES', None)
if visible_devices is None:
visible_devices = list(range(nvmlDeviceGetCount()))
else:
visible_devices = {int(x.strip()) for x in visible_devices.split(',')}
for i, real_id in enumerate(visible_devices):
h = nvmlDeviceGetHandleByIndex(real_id)
info = nvmlDeviceGetMemoryInfo(h)
total = info.total
free = info.free
ratio = free / total
gpus[i] = ratio
# print(f'total : {info.total}')
# print(f'free : {info.free}')
# print(f'used : {info.used}')
# t = torch.cuda.get_device_properties(0).total_memory
# c = torch.cuda.memory_cached(0)
# a = torch.cuda.memory_allocated(0)
# print(t, c, a)
nvmlShutdown()
return dict(sorted(gpus.items(), key=lambda x: x[1], reverse=True))
except Exception as e:
logger.debug(f'Failed to get gpu info due to {e}')
return dict((i, 1.0) for i in range(torch.cuda.device_count()))
def cuda_devices(query=None) -> List[int]:
"""Decide which GPUs to use
Args:
query: (Default value = None)
Returns:
"""
if isinstance(query, list):
if len(query) == 0:
return [-1]
return query
if query is None:
query = gpus_available()
if not query:
return []
size, idx = max((v, k) for k, v in query.items())
# When multiple GPUs have the same size, randomly pick one to avoid conflicting
gpus_with_same_size = [k for k, v in query.items() if v == size]
query = random.choice(gpus_with_same_size)
if isinstance(query, float):
gpus = gpus_available()
if not query:
return []
query = [k for k, v in gpus.items() if v > query]
elif isinstance(query, int):
query = [query]
return query
def pad_lists(sequences: List[List], dtype=torch.long, padding_value=0):
return pad_sequence([torch.tensor(x, dtype=dtype) for x in sequences], True, padding_value)
def set_seed(seed=233, dont_care_speed=False):
"""Copied from https://github.com/huggingface/transformers/blob/7b75aa9fa55bee577e2c7403301ed31103125a35/src/transformers/trainer.py#L76
Args:
seed: (Default value = 233)
dont_care_speed: True may have a negative single-run performance impact, but ensures deterministic
Returns:
"""
random.seed(seed)
np.random.seed(seed)
torch.manual_seed(seed)
# ^^ safe to call this function even if cuda is not available
torch.cuda.manual_seed_all(seed)
if dont_care_speed:
torch.backends.cudnn.deterministic = True
torch.backends.cudnn.benchmark = False
def batched_index_select(input, index, dim=1):
"""
Args:
input: B x * x ... x *
index: B x M
dim: (Default value = 1)
Returns:
"""
views = [input.shape[0]] + [1 if i != dim else -1 for i in range(1, len(input.shape))]
expanse = list(input.shape)
expanse[0] = -1
expanse[dim] = -1
index = index.view(views).expand(expanse)
return torch.gather(input, dim, index)
def truncated_normal_(tensor, mean=0, std=1):
size = tensor.shape
tmp = tensor.new_empty(size + (4,)).normal_()
valid = (tmp < 2) & (tmp > -2)
ind = valid.max(-1, keepdim=True)[1]
tensor.data.copy_(tmp.gather(-1, ind).squeeze(-1))
tensor.data.mul_(std).add_(mean)
return tensor
def dtype_of(e: Union[int, bool, float]):
if isinstance(e, bool):
return torch.bool
if isinstance(e, int):
return torch.long
if isinstance(e, float):
return torch.float
raise ValueError(f'Unsupported type of {repr(e)}')
def mean_model(model: torch.nn.Module):
return float(torch.mean(torch.stack([torch.sum(p) for p in model.parameters() if p.requires_grad])))
def main():
start = time.time()
print(gpus_available())
print(time.time() - start)
# print(gpus_available())
# print(cuda_devices())
# print(cuda_devices(0.1))
if __name__ == '__main__':
main()
def clip_grad_norm(model: nn.Module, grad_norm, transformer: nn.Module = None, transformer_grad_norm=None):
if transformer_grad_norm is None:
if grad_norm is not None:
nn.utils.clip_grad_norm_(filter(lambda p: p.requires_grad, model.parameters()), grad_norm)
else:
is_transformer = []
non_transformer = []
transformer = set(transformer.parameters())
for p in model.parameters():
if not p.requires_grad:
continue
if p in transformer:
is_transformer.append(p)
else:
non_transformer.append(p)
nn.utils.clip_grad_norm_(non_transformer, grad_norm)
nn.utils.clip_grad_norm_(is_transformer, transformer_grad_norm)
def load_word2vec(path, delimiter=' ', cache=True) -> Tuple[Dict[str, np.ndarray], int]:
realpath = get_resource(path)
binpath = replace_ext(realpath, '.pkl')
if cache:
try:
flash('Loading word2vec from cache [blink][yellow]...[/yellow][/blink]')
word2vec, dim = load_pickle(binpath)
flash('')
return word2vec, dim
except IOError:
pass
dim = None
word2vec = dict()
f = TimingFileIterator(realpath)
for idx, line in enumerate(f):
f.log('Loading word2vec from text file [blink][yellow]...[/yellow][/blink]')
line = line.rstrip().split(delimiter)
if len(line) > 2:
if dim is None:
dim = len(line)
else:
if len(line) != dim:
logger.warning('{}#{} length mismatches with {}'.format(path, idx + 1, dim))
continue
word, vec = line[0], line[1:]
word2vec[word] = np.array(vec, dtype=np.float32)
dim -= 1
if cache:
flash('Caching word2vec [blink][yellow]...[/yellow][/blink]')
save_pickle((word2vec, dim), binpath)
flash('')
return word2vec, dim
def load_word2vec_as_vocab_tensor(path, delimiter=' ', cache=True) -> Tuple[Dict[str, int], torch.Tensor]:
realpath = get_resource(path)
vocab_path = replace_ext(realpath, '.vocab')
matrix_path = replace_ext(realpath, '.pt')
if cache:
try:
if HANLP_VERBOSE:
flash('Loading vocab and matrix from cache [blink][yellow]...[/yellow][/blink]')
vocab = load_pickle(vocab_path)
matrix = torch.load(matrix_path, map_location='cpu')
if HANLP_VERBOSE:
flash('')
return vocab, matrix
except IOError:
pass
word2vec, dim = load_word2vec(path, delimiter, cache)
vocab = dict((k, i) for i, k in enumerate(word2vec.keys()))
matrix = torch.Tensor(np.stack(list(word2vec.values())))
if cache:
flash('Caching vocab and matrix [blink][yellow]...[/yellow][/blink]')
save_pickle(vocab, vocab_path)
torch.save(matrix, matrix_path)
flash('')
return vocab, matrix
def save_word2vec(word2vec: dict, filepath, delimiter=' '):
with open(filepath, 'w', encoding='utf-8') as out:
for w, v in word2vec.items():
out.write(f'{w}{delimiter}')
out.write(f'{delimiter.join(str(x) for x in v)}\n')
def lengths_to_mask(seq_len, max_len=None):
r"""
.. code-block::
>>> seq_len = torch.arange(2, 16)
>>> mask = lengths_to_mask(seq_len)
>>> print(mask.size())
torch.Size([14, 15])
>>> seq_len = np.arange(2, 16)
>>> mask = lengths_to_mask(seq_len)
>>> print(mask.shape)
(14, 15)
>>> seq_len = torch.arange(2, 16)
>>> mask = lengths_to_mask(seq_len, max_len=100)
>>>print(mask.size())
torch.Size([14, 100])
:param torch.LongTensor seq_len: (B,)
:param int max_len: max sequence length。
:return: torch.Tensor (B, max_len)
"""
assert seq_len.dim() == 1, f"seq_len can only have one dimension, got {seq_len.dim() == 1}."
batch_size = seq_len.size(0)
max_len = int(max_len) if max_len else seq_len.max().long()
broad_cast_seq_len = torch.arange(max_len).expand(batch_size, -1).to(seq_len)
mask = broad_cast_seq_len.lt(seq_len.unsqueeze(1))
return mask
def activation_from_name(name: str):
return getattr(torch.nn, name)
def filter_state_dict_safely(model_state: dict, load_state: dict):
safe_state = dict()
for k, v in load_state.items():
model_v = model_state.get(k, None)
if model_v is not None and model_v.shape == v.shape:
safe_state[k] = v
return safe_state