char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
b19cbe925
char/aliceai-t5-moe
1from __future__import annotations 2from abc import ABC ,ABCMeta ,abstractmethod 3 4import logging 5from typing import Any ,Callable 6 7import numpy as np 8from numpy .typing import DTypeLike 9 10 11logger = logging .getLogger (__name__ ) 12 13 14class LazyMeta (ABCMeta ): 15 16def __new__ (cls ,name :str ,bases :tuple [type , ...],namespace :dict [str ,Any ],** kwargs ): 17def __getattr__ (self ,name :str )-> Any : 18meta_attr = getattr (self ._meta ,name ) 19if callable (meta_attr ): 20return type (self )._wrap_fn ( 21 (lambda s ,* args ,** kwargs :getattr (s ,name )(* args ,** kwargs )), 22use_self = self , 23 ) 24elif isinstance (meta_attr ,self ._tensor_type ): 25# e.g. self.T with torch.Tensor should still be wrapped 26return type (self )._wrap_fn (lambda s :getattr (s ,name ))(self ) 27else : 28# no need to wrap non-tensor properties, 29# and they likely don't depend on the actual contents of the tensor 30return meta_attr 31 32namespace ["__getattr__" ]= __getattr__ 33 34# need to make a builder for the wrapped wrapper to copy the name, 35# or else it fails with very cryptic error messages, 36# because somehow the same string would end up in every closures 37def mk_wrap (op_name :str ,* ,meta_noop :bool = False ): 38# need to wrap the wrapper to get self 39def wrapped_special_op (self ,* args ,** kwargs ): 40return type (self )._wrap_fn ( 41getattr (type (self )._tensor_type ,op_name ), 42meta_noop = meta_noop , 43 )(self ,* args ,** kwargs ) 44return wrapped_special_op 45 46# special methods bypass __getattr__, so they need to be added manually 47# ref: https://docs.python.org/3/reference/datamodel.html#special-lookup 48# NOTE: doing this from a metaclass is very convenient 49# TODO: make this even more comprehensive 50for binary_op in ( 51"lt" ,"le" ,"eq" ,"ne" ,"ge" ,"gt" , 52"add" ,"and" ,"floordiv" ,"lshift" ,"mod" ,"mul" ,"matmul" , 53"or" ,"pow" ,"rshift" ,"sub" ,"truediv" ,"xor" , 54"iadd" ,"iand" ,"ifloordiv" ,"ilshift" ,"imod" ,"imul" ,"ior" ,"irshift" ,"isub" ,"ixor" , 55"radd" ,"rand" ,"rfloordiv" ,"rmul" ,"ror" ,"rpow" ,"rsub" ,"rtruediv" ,"rxor" , 56 ): 57attr_name = f"__ { binary_op } __" 58# evaluation on the meta tensor is needed in case there's broadcasting 59namespace [attr_name ]= mk_wrap (attr_name ,meta_noop = False ) 60 61for unary_op in ("not" ,"abs" ,"invert" ,"neg" ,"pos" ): 62attr_name = f"__ { unary_op } __" 63# the result of these operators usually has the same shape and dtype as the input, 64# so evaluation on the meta tensor can be skipped. 65namespace [attr_name ]= mk_wrap (attr_name ,meta_noop = True ) 66 67for special_op in ( 68"getitem" ,"setitem" ,"len" , 69 ): 70attr_name = f"__ { special_op } __" 71namespace [attr_name ]= mk_wrap (attr_name ,meta_noop = False ) 72 73return super ().__new__ (cls ,name ,bases ,namespace ,** kwargs ) 74 75 76# Tree of lazy tensors 77class LazyBase (ABC ,metaclass = LazyMeta ): 78_tensor_type :type 79_meta :Any 80_data :Any | None 81_args :tuple 82_kwargs :dict [str ,Any ] 83_func :Callable [[Any ],Any ]| None 84 85def __init__ (self ,* ,meta :Any ,data :Any | None = None ,args :tuple = (),kwargs :dict [str ,Any ]| None = None ,func :Callable [[Any ],Any ]| None = None ): 86super ().__init__ () 87self ._meta = meta 88self ._data = data 89self ._args = args 90self ._kwargs = kwargs if kwargs is not None else {} 91self ._func = func 92assert self ._func is not None or self ._data is not None 93 94def __init_subclass__ (cls )-> None : 95if "_tensor_type" not in cls .__dict__ : 96raise TypeError (f"property '_tensor_type' must be defined for { cls !r } " ) 97return super ().__init_subclass__ () 98 99@staticmethod 100def _recurse_apply (o :Any ,fn :Callable [[Any ],Any ])-> Any : 101# TODO: dict and set 102if isinstance (o , (list ,tuple )): 103L = [] 104for item in o : 105L .append (LazyBase ._recurse_apply (item ,fn )) 106if isinstance (o ,tuple ): 107L = tuple (L ) 108return L 109elif isinstance (o ,LazyBase ): 110return fn (o ) 111else : 112return o 113 114@classmethod 115def _wrap_fn (cls ,fn :Callable ,* ,use_self :LazyBase | None = None ,meta_noop :bool | DTypeLike | tuple [DTypeLike ,Callable [[tuple [int , ...]],tuple [int , ...]]]= False )-> Callable [[Any ],Any ]: 116def wrapped_fn (* args ,** kwargs ): 117if kwargs is None : 118kwargs = {} 119args = ((use_self ,)if use_self is not None else ())+ args 120 121meta_args = LazyBase ._recurse_apply (args ,lambda t :t ._meta ) 122# TODO: maybe handle tensors in kwargs too 123 124if isinstance (meta_noop ,bool )and not meta_noop : 125try : 126res = fn (* meta_args ,** kwargs ) 127except NotImplementedError : 128# running some operations on PyTorch's Meta tensors can cause this exception 129res = None 130else : 131# some operators don't need to actually run on the meta tensors 132assert len (args )> 0 133res = args [0 ] 134assert isinstance (res ,cls ) 135res = res ._meta 136# allow operations to override the dtype and shape 137if meta_noop is not True : 138if isinstance (meta_noop ,tuple ): 139dtype ,shape = meta_noop 140assert callable (shape ) 141res = cls .meta_with_dtype_and_shape (dtype ,shape (res .shape ))# ty: ignore[call-top-callable] 142else : 143res = cls .meta_with_dtype_and_shape (meta_noop ,res .shape ) 144 145if isinstance (res ,cls ._tensor_type ): 146return cls (meta = cls .eager_to_meta (res ),args = args ,kwargs = kwargs ,func = fn ) 147elif isinstance (res ,tuple )and all (isinstance (t ,cls ._tensor_type )for t in res ): 148# share the evaluation between lazy tuple elements 149shared_args :list = [args ,None ] 150 151def eager_tuple_element (a :list [Any ],i :int = 0 ,/ ,** kw )-> LazyBase : 152assert len (a )== 2 153if a [1 ]is None : 154a [1 ]= fn (* a [0 ],** kw ) 155return a [1 ][i ] 156return tuple (cls (meta = cls .eager_to_meta (res [i ]),args = (shared_args ,i ),kwargs = kwargs ,func = eager_tuple_element )for i in range (len (res ))) 157else : 158del res # not needed 159# non-tensor return likely relies on the contents of the args 160# (e.g. the result of torch.equal) 161eager_args = cls .to_eager (args ) 162return fn (* eager_args ,** kwargs ) 163return wrapped_fn 164 165@classmethod 166def to_eager (cls ,t :Any )-> Any : 167def simple_to_eager (_t :LazyBase )-> Any : 168if _t ._data is not None : 169return _t ._data 170 171# NOTE: there's a recursion limit in Python (usually 1000) 172 173assert _t ._func is not None 174_t ._args = cls ._recurse_apply (_t ._args ,simple_to_eager ) 175_t ._data = _t ._func (* _t ._args ,** _t ._kwargs ) 176# sanity check 177assert _t ._data is not None 178assert _t ._data .dtype == _t ._meta .dtype 179assert _t ._data .shape == _t ._meta .shape 180 181return _t ._data 182 183# recurse into lists and/or tuples, keeping their structure 184return cls ._recurse_apply (t ,simple_to_eager ) 185 186@classmethod 187def eager_to_meta (cls ,t :Any )-> Any : 188return cls .meta_with_dtype_and_shape (t .dtype ,t .shape ) 189 190# must be overridden, meta tensor init is backend-specific 191@classmethod 192@abstractmethod 193def meta_with_dtype_and_shape (cls ,dtype :Any ,shape :Any )-> Any :pass 194 195@classmethod 196def from_eager (cls ,t :Any )-> Any : 197if type (t )is cls : 198# already lazy 199return t 200elif isinstance (t ,cls ._tensor_type ): 201return cls (meta = cls .eager_to_meta (t ),data = t ) 202else : 203return TypeError (f" { type ( t )!r } is not compatible with { cls . _tensor_type !r } " ) 204 205 206class LazyNumpyTensor (LazyBase ): 207_tensor_type = np .ndarray 208 209shape :tuple [int , ...]# Makes the type checker happy in quants.py 210 211@classmethod 212def meta_with_dtype_and_shape (cls ,dtype :DTypeLike ,shape :tuple [int , ...])-> np .ndarray [Any ,Any ]: 213# The initial idea was to use np.nan as the fill value, 214# but non-float types like np.int16 can't use that. 215# So zero it is. 216cheat = np .zeros (1 ,dtype ) 217return np .lib .stride_tricks .as_strided (cheat ,shape , (0 for _ in shape )) 218 219def astype (self ,dtype ,* args ,** kwargs ): 220meta = type (self ).meta_with_dtype_and_shape (dtype ,self ._meta .shape ) 221full_args = (self ,dtype ,)+ args 222return type (self )(meta = meta ,args = full_args ,kwargs = kwargs ,func = (lambda a ,* args ,** kwargs :a .astype (* args ,** kwargs ))) 223 224def tofile (self ,* args ,** kwargs ): 225eager = LazyNumpyTensor .to_eager (self ) 226return eager .tofile (* args ,** kwargs ) 227 228# TODO: __array_function__ 229 230 231# Tensor written to file one row-chunk at a time 232class LazyChunkedTensor : 233 234def __init__ ( 235self ,chunks :list [Callable [[],np .ndarray ]],shape :tuple [int , ...],dtype :DTypeLike , 236qtype :Any = None ,byteswap :bool = False , 237 ): 238self ._chunks = chunks 239self ._qtype = qtype 240self ._byteswap = byteswap 241self .shape = tuple (shape ) 242self .dtype = np .dtype (dtype ) 243 244@property 245def nbytes (self )-> int : 246n = self .dtype .itemsize 247for d in self .shape : 248n *= d 249return n 250 251def numpy (self )-> LazyChunkedTensor : 252return self 253 254def __array__ (self ,* args ,** kwargs ): 255# numpy would otherwise make a 1-element object array of self, and write 8 bytes 256raise TypeError ("LazyChunkedTensor cannot become an ndarray, it is written in chunks" ) 257 258def quantize (self ,qtype :Any )-> LazyChunkedTensor : 259from .constants import GGMLQuantizationType 260from .quants import QuantError ,quant_shape_to_byte_shape 261 262if qtype == GGMLQuantizationType .F32 : 263shape ,dtype = self .shape ,np .dtype (np .float32 ) 264elif qtype == GGMLQuantizationType .F16 : 265shape ,dtype = self .shape ,np .dtype (np .float16 ) 266else : 267try : 268shape ,dtype = quant_shape_to_byte_shape (self .shape ,qtype ),np .dtype (np .uint8 ) 269except ValueError as e : 270# raised here and not per chunk, so callers can still fall back to F16 271raise QuantError (str (e ))from e 272return LazyChunkedTensor (self ._chunks ,shape ,dtype ,qtype ,self ._byteswap ) 273 274def byteswap (self ,inplace :bool = False )-> LazyChunkedTensor : 275if inplace : 276raise NotImplementedError ("a chunked tensor cannot be byteswapped in place" ) 277return LazyChunkedTensor (self ._chunks ,self .shape ,self .dtype ,self ._qtype ,not self ._byteswap ) 278 279def tofile (self ,* args ,** kwargs )-> None : 280from .quants import quantize 281 282written = 0 283for load_chunk in self ._chunks : 284chunk = load_chunk () 285if self ._qtype is not None : 286# exact only because chunks split on rows, and blocks never cross one 287chunk = quantize (chunk ,self ._qtype ) 288if self ._byteswap : 289chunk = chunk .byteswap (inplace = False ) 290chunk .tofile (* args ,** kwargs ) 291written += chunk .nbytes 292del chunk 293assert written == self .nbytes ,f"chunked tensor wrote { written } bytes, expected { self . nbytes } "