diff --git a/build_nvidia.cmd b/build_nvidia.cmd new file mode 100644 index 000000000..e71fdd5f4 --- /dev/null +++ b/build_nvidia.cmd @@ -0,0 +1,8 @@ +@echo off +set CUDA_PATH=D:\ANACONDA\envs\llaisys\Library +set CUDA_HOME=%CUDA_PATH% +set PATH=%CUDA_PATH%\bin;%CUDA_PATH%\lib;%PATH% +cd /d E:\githubwork\llaisys +rmdir /S /Q build 2>nul +xmake f --nv-gpu=y -c +xmake \ No newline at end of file diff --git a/python/llaisys/__init__.py b/python/llaisys/__init__.py index de8d99f48..70c63dc24 100644 --- a/python/llaisys/__init__.py +++ b/python/llaisys/__init__.py @@ -5,9 +5,8 @@ from .libllaisys import llaisysStream_t as Stream from .tensor import Tensor from .ops import Ops -from . import models from .models import * - +from .models.qwen2 import Qwen2 __all__ = [ "RuntimeAPI", "DeviceType", diff --git a/python/llaisys/libllaisys/__init__.py b/python/llaisys/libllaisys/__init__.py index f536fb527..69d121629 100644 --- a/python/llaisys/libllaisys/__init__.py +++ b/python/llaisys/libllaisys/__init__.py @@ -2,7 +2,6 @@ import sys import ctypes from pathlib import Path - from .runtime import load_runtime from .runtime import LlaisysRuntimeAPI from .llaisys_types import llaisysDeviceType_t, DeviceType diff --git a/python/llaisys/libllaisys/model.py b/python/llaisys/libllaisys/model.py new file mode 100644 index 000000000..34dd1b20e --- /dev/null +++ b/python/llaisys/libllaisys/model.py @@ -0,0 +1,76 @@ +import ctypes +from . import LIB_LLAISYS as _lib + +class LlaisysQwen2Meta(ctypes.Structure): + _fields_ = [ + ("dtype", ctypes.c_int), + ("nlayer", ctypes.c_size_t), + ("hs", ctypes.c_size_t), + ("nh", ctypes.c_size_t), + ("nkvh", ctypes.c_size_t), + ("dh", ctypes.c_size_t), + ("di", ctypes.c_size_t), + ("maxseq", ctypes.c_size_t), + ("voc", ctypes.c_size_t), + ("epsilon", ctypes.c_float), + ("theta", ctypes.c_float), + ("end_token", ctypes.c_int64), + ] + +class LlaisysQwen2Weights(ctypes.Structure): + _fields_ = [ + ("in_embed", ctypes.c_void_p), + ("out_embed", ctypes.c_void_p), + ("out_norm_w", ctypes.c_void_p), + ("attn_norm_w", ctypes.POINTER(ctypes.c_void_p)), + ("attn_q_w", ctypes.POINTER(ctypes.c_void_p)), + ("attn_q_b", ctypes.POINTER(ctypes.c_void_p)), + ("attn_k_w", ctypes.POINTER(ctypes.c_void_p)), + ("attn_k_b", ctypes.POINTER(ctypes.c_void_p)), + ("attn_v_w", ctypes.POINTER(ctypes.c_void_p)), + ("attn_v_b", ctypes.POINTER(ctypes.c_void_p)), + ("attn_o_w", ctypes.POINTER(ctypes.c_void_p)), + ("mlp_norm_w", ctypes.POINTER(ctypes.c_void_p)), + ("mlp_gate_w", ctypes.POINTER(ctypes.c_void_p)), + ("mlp_up_w", ctypes.POINTER(ctypes.c_void_p)), + ("mlp_down_w", ctypes.POINTER(ctypes.c_void_p)), + ] + +_lib.llaisysQwen2ModelCreate.argtypes = [ + ctypes.POINTER(LlaisysQwen2Meta), + ctypes.c_int, + ctypes.POINTER(ctypes.c_int), + ctypes.c_int, +] +_lib.llaisysQwen2ModelCreate.restype = ctypes.c_void_p + +_lib.llaisysQwen2ModelDestroy.argtypes = [ctypes.c_void_p] +_lib.llaisysQwen2ModelDestroy.restype = None + +_lib.llaisysQwen2ModelWeights.argtypes = [ctypes.c_void_p] +_lib.llaisysQwen2ModelWeights.restype = ctypes.POINTER(LlaisysQwen2Weights) + +_lib.llaisysQwen2ModelInfer.argtypes = [ + ctypes.c_void_p, + ctypes.POINTER(ctypes.c_int64), + ctypes.c_size_t, +] +_lib.llaisysQwen2ModelInfer.restype = ctypes.c_int64 + +def create_model(meta, device, device_ids): + return _lib.llaisysQwen2ModelCreate( + ctypes.byref(meta), + device, + (ctypes.c_int * len(device_ids))(*device_ids), + len(device_ids) + ) + +def destroy_model(handle): + _lib.llaisysQwen2ModelDestroy(handle) + +def get_weights(handle): + return _lib.llaisysQwen2ModelWeights(handle) + +def infer(handle, token_ids): + arr = (ctypes.c_int64 * len(token_ids))(*token_ids) + return _lib.llaisysQwen2ModelInfer(handle, arr, len(token_ids)) \ No newline at end of file diff --git a/python/llaisys/models/qwen2.py b/python/llaisys/models/qwen2.py index 0d07b0b21..135d7e352 100644 --- a/python/llaisys/models/qwen2.py +++ b/python/llaisys/models/qwen2.py @@ -1,33 +1,128 @@ from typing import Sequence -from ..libllaisys import LIB_LLAISYS -from ..libllaisys import DeviceType - from pathlib import Path +import json +import ctypes import safetensors +import safetensors.torch +import torch +import numpy as np +from llaisys.libllaisys import DeviceType, DataType +from llaisys.libllaisys.model import ( + create_model, + get_weights, + infer, + LlaisysQwen2Meta, + LlaisysQwen2Weights, +) +from llaisys.tensor import Tensor class Qwen2: - def __init__(self, model_path, device: DeviceType = DeviceType.CPU): - # TODO: Implement model constructor - model_path = Path(model_path) + self.device = DeviceType.CPU # Always use CPU for weights (GPU compute via CUDA kernels on-the-fly) + self.device_id = 0 + + with open(model_path / "config.json", "r") as f: + config = json.load(f) + self.config = config + + meta = LlaisysQwen2Meta() + meta.dtype = 0 # F32 + meta.nlayer = config["num_hidden_layers"] + meta.hs = config["hidden_size"] + meta.nh = config["num_attention_heads"] + meta.nkvh = config.get("num_key_value_heads", meta.nh) + meta.dh = meta.hs // meta.nh + meta.di = config["intermediate_size"] + meta.maxseq = config.get("max_position_embeddings", 4096) + meta.voc = config["vocab_size"] + meta.epsilon = config.get("rms_norm_eps", 1e-6) + meta.theta = config.get("rope_theta", 10000.0) + meta.end_token = config.get("eos_token_id", 151645) + + self.handle = create_model(meta, device.value, [self.device_id]) + self.weights_ptr = get_weights(self.handle) + self._load_weights(model_path) + + def _load_weights(self, model_path): + w = self.weights_ptr.contents + + def to_tensor(np_arr): + shape = list(np_arr.shape) + dtype_map = { + np.float32: DataType.F32, + np.float16: DataType.F16, + np.int64: DataType.I64, + } + dtype = dtype_map.get(np_arr.dtype.type, DataType.F32) + tensor = Tensor(shape, dtype, self.device, self.device_id) + tensor.load(np_arr.ctypes.data_as(ctypes.c_void_p)) + if not hasattr(self, '_tensor_refs'): + self._tensor_refs = [] + self._tensor_refs.append(tensor) + return tensor.lib_tensor() for file in sorted(model_path.glob("*.safetensors")): - data_ = safetensors.safe_open(file, framework="numpy", device="cpu") - for name_ in data_.keys(): - ## TODO: load the model weights - pass - - def generate( - self, - inputs: Sequence[int], - max_new_tokens: int = None, - top_k: int = 1, - top_p: float = 0.8, - temperature: float = 0.8, - ): - - # TODO: Implement generate function - - return [] + data = safetensors.torch.load_file(str(file)) + for name, arr in data.items(): + if arr.dtype == torch.bfloat16: + arr = arr.float() + arr = arr.cpu().numpy() + if name == "model.embed_tokens.weight": + w.in_embed = to_tensor(arr) + elif name == "model.norm.weight": + w.out_norm_w = to_tensor(arr) + elif name == "lm_head.weight": + w.out_embed = to_tensor(arr) + elif name.startswith("model.layers."): + parts = name.split('.') + layer_idx = int(parts[2]) + suffix = parts[3] + if suffix == "input_layernorm": + w.attn_norm_w[layer_idx] = to_tensor(arr) + elif suffix == "self_attn": + proj = parts[4] + if proj == "q_proj": + if name.endswith(".weight"): + w.attn_q_w[layer_idx] = to_tensor(arr) + elif name.endswith(".bias"): + w.attn_q_b[layer_idx] = to_tensor(arr) + elif proj == "k_proj": + if name.endswith(".weight"): + w.attn_k_w[layer_idx] = to_tensor(arr) + elif name.endswith(".bias"): + w.attn_k_b[layer_idx] = to_tensor(arr) + elif proj == "v_proj": + if name.endswith(".weight"): + w.attn_v_w[layer_idx] = to_tensor(arr) + elif name.endswith(".bias"): + w.attn_v_b[layer_idx] = to_tensor(arr) + elif proj == "o_proj": + if name.endswith(".weight"): + w.attn_o_w[layer_idx] = to_tensor(arr) + elif suffix == "post_attention_layernorm": + w.mlp_norm_w[layer_idx] = to_tensor(arr) + elif suffix == "mlp": + proj = parts[4] + if proj == "gate_proj": + w.mlp_gate_w[layer_idx] = to_tensor(arr) + elif proj == "up_proj": + w.mlp_up_w[layer_idx] = to_tensor(arr) + elif proj == "down_proj": + w.mlp_down_w[layer_idx] = to_tensor(arr) + + def generate(self, inputs: Sequence[int], max_new_tokens: int = 20, **kwargs): + print(f"generate called with {inputs}") + if not inputs: + return [] + input_ids = list(inputs) + generated = [] + for step in range(max_new_tokens): + next_token = infer(self.handle, input_ids) + if next_token == self.config.get("eos_token_id", -1): + break + generated.append(next_token) + input_ids.append(next_token) + print(f"generate returning {generated}") + return generated \ No newline at end of file diff --git a/python/llaisys/tensor.py b/python/llaisys/tensor.py index 1466d851e..03093ecaa 100644 --- a/python/llaisys/tensor.py +++ b/python/llaisys/tensor.py @@ -70,7 +70,12 @@ def debug(self): LIB_LLAISYS.tensorDebug(self._tensor) def __repr__(self): - return f"" + return ( + f"" + ) def load(self, data: c_void_p): LIB_LLAISYS.tensorLoad(self._tensor, data) diff --git a/src/device/nvidia/nvidia_runtime_api.cu b/src/device/nvidia/nvidia_runtime_api.cu index cab928261..fb23aeed0 100644 --- a/src/device/nvidia/nvidia_runtime_api.cu +++ b/src/device/nvidia/nvidia_runtime_api.cu @@ -1,56 +1,80 @@ #include "../runtime_api.hpp" +#include "nvidia_resource.cuh" +#include #include -#include namespace llaisys::device::nvidia { namespace runtime_api { int getDeviceCount() { - TO_BE_IMPLEMENTED(); + int count = 0; + cudaGetDeviceCount(&count); + return count; } -void setDevice(int) { - TO_BE_IMPLEMENTED(); +void setDevice(int device) { + cudaSetDevice(device); } void deviceSynchronize() { - TO_BE_IMPLEMENTED(); + cudaDeviceSynchronize(); } llaisysStream_t createStream() { - TO_BE_IMPLEMENTED(); + cudaStream_t stream = nullptr; + cudaStreamCreate(&stream); + return reinterpret_cast(stream); } void destroyStream(llaisysStream_t stream) { - TO_BE_IMPLEMENTED(); + cudaStreamDestroy(reinterpret_cast(stream)); } + void streamSynchronize(llaisysStream_t stream) { - TO_BE_IMPLEMENTED(); + cudaStreamSynchronize(reinterpret_cast(stream)); } void *mallocDevice(size_t size) { - TO_BE_IMPLEMENTED(); + void *ptr = nullptr; + cudaMalloc(&ptr, size); + return ptr; } void freeDevice(void *ptr) { - TO_BE_IMPLEMENTED(); + cudaFree(ptr); } void *mallocHost(size_t size) { - TO_BE_IMPLEMENTED(); + void *ptr = nullptr; + cudaMallocHost(&ptr, size); + return ptr; } void freeHost(void *ptr) { - TO_BE_IMPLEMENTED(); + cudaFreeHost(ptr); } void memcpySync(void *dst, const void *src, size_t size, llaisysMemcpyKind_t kind) { - TO_BE_IMPLEMENTED(); + cudaMemcpyKind cuda_kind = cudaMemcpyDefault; + switch (kind) { + case LLAISYS_MEMCPY_H2H: cuda_kind = cudaMemcpyHostToHost; break; + case LLAISYS_MEMCPY_H2D: cuda_kind = cudaMemcpyHostToDevice; break; + case LLAISYS_MEMCPY_D2H: cuda_kind = cudaMemcpyDeviceToHost; break; + case LLAISYS_MEMCPY_D2D: cuda_kind = cudaMemcpyDeviceToDevice; break; + } + cudaMemcpy(dst, src, size, cuda_kind); } -void memcpyAsync(void *dst, const void *src, size_t size, llaisysMemcpyKind_t kind) { - TO_BE_IMPLEMENTED(); +void memcpyAsync(void *dst, const void *src, size_t size, llaisysMemcpyKind_t kind, llaisysStream_t stream) { + cudaMemcpyKind cuda_kind = cudaMemcpyDefault; + switch (kind) { + case LLAISYS_MEMCPY_H2H: cuda_kind = cudaMemcpyHostToHost; break; + case LLAISYS_MEMCPY_H2D: cuda_kind = cudaMemcpyHostToDevice; break; + case LLAISYS_MEMCPY_D2H: cuda_kind = cudaMemcpyDeviceToHost; break; + case LLAISYS_MEMCPY_D2D: cuda_kind = cudaMemcpyDeviceToDevice; break; + } + cudaMemcpyAsync(dst, src, size, cuda_kind, reinterpret_cast(stream)); } static const LlaisysRuntimeAPI RUNTIME_API = { @@ -72,4 +96,4 @@ static const LlaisysRuntimeAPI RUNTIME_API = { const LlaisysRuntimeAPI *getRuntimeAPI() { return &runtime_api::RUNTIME_API; } -} // namespace llaisys::device::nvidia +} // namespace llaisys::device::nvidia \ No newline at end of file diff --git a/src/llaisys/models/qwen2.cpp b/src/llaisys/models/qwen2.cpp new file mode 100644 index 000000000..c7d610798 --- /dev/null +++ b/src/llaisys/models/qwen2.cpp @@ -0,0 +1,149 @@ +#include "llaisys/models/qwen2.h" +#include "../../llaisys/llaisys_tensor.hpp" +#include "../../models/qwen2/qwen2.hpp" +#include "../../tensor/tensor.hpp" +#include +#include +#include +#include + +using namespace llaisys; +using namespace llaisys::models; + +static std::unordered_map g_models; +static std::unordered_map g_weights; +static std::unordered_set g_weights_loaded_set; + +static inline tensor_t unwrap_handle(llaisysTensor_t h) { + if (!h) return nullptr; + return reinterpret_cast(h)->tensor; +} + +static void sync_weights_to_model(Qwen2Model *m, LlaisysQwen2Weights *w, size_t nlayer) { + if (g_weights_loaded_set.count(m)) return; + g_weights_loaded_set.insert(m); + m->_embed_tokens = unwrap_handle(w->in_embed); + m->_lm_head = unwrap_handle(w->out_embed); + m->_final_norm = unwrap_handle(w->out_norm_w); + + m->_layer_norms.resize(nlayer); + m->_layer_attn_q.resize(nlayer); + m->_layer_attn_q_b.resize(nlayer); + m->_layer_attn_k.resize(nlayer); + m->_layer_attn_k_b.resize(nlayer); + m->_layer_attn_v.resize(nlayer); + m->_layer_attn_v_b.resize(nlayer); + m->_layer_attn_o.resize(nlayer); + m->_layer_post_norm.resize(nlayer); + m->_layer_ffn_gate.resize(nlayer); + m->_layer_ffn_up.resize(nlayer); + m->_layer_ffn_down.resize(nlayer); + + for (size_t i = 0; i < nlayer; ++i) { + m->_layer_norms[i] = unwrap_handle(w->attn_norm_w[i]); + m->_layer_attn_q[i] = unwrap_handle(w->attn_q_w[i]); + m->_layer_attn_q_b[i] = unwrap_handle(w->attn_q_b[i]); + m->_layer_attn_k[i] = unwrap_handle(w->attn_k_w[i]); + m->_layer_attn_k_b[i] = unwrap_handle(w->attn_k_b[i]); + m->_layer_attn_v[i] = unwrap_handle(w->attn_v_w[i]); + m->_layer_attn_v_b[i] = unwrap_handle(w->attn_v_b[i]); + m->_layer_attn_o[i] = unwrap_handle(w->attn_o_w[i]); + m->_layer_post_norm[i] = unwrap_handle(w->mlp_norm_w[i]); + m->_layer_ffn_gate[i] = unwrap_handle(w->mlp_gate_w[i]); + m->_layer_ffn_up[i] = unwrap_handle(w->mlp_up_w[i]); + m->_layer_ffn_down[i] = unwrap_handle(w->mlp_down_w[i]); + } +} + +extern "C" { + +LlaisysQwen2Model *llaisysQwen2ModelCreate(const LlaisysQwen2Meta *meta, + llaisysDeviceType_t device, + int *device_ids, + int ndevice) { + Qwen2Config config; + config.hidden_size = meta->hs; + config.intermediate_size = meta->di; + config.num_attention_heads = meta->nh; + config.num_key_value_heads = meta->nkvh; + config.vocab_size = meta->voc; + config.max_position_embeddings = meta->maxseq; + config.num_hidden_layers = meta->nlayer; + config.rms_norm_eps = meta->epsilon; + config.rope_theta = meta->theta; + config.end_token_id = meta->end_token; + + auto *model = new Qwen2Model(config); + g_models[model] = model; + + auto *weights = new LlaisysQwen2Weights(); + std::memset(weights, 0, sizeof(LlaisysQwen2Weights)); + size_t nlayer = meta->nlayer; + weights->attn_norm_w = new llaisysTensor_t[nlayer]; + weights->attn_q_w = new llaisysTensor_t[nlayer]; + weights->attn_q_b = new llaisysTensor_t[nlayer]; + weights->attn_k_w = new llaisysTensor_t[nlayer]; + weights->attn_k_b = new llaisysTensor_t[nlayer]; + weights->attn_v_w = new llaisysTensor_t[nlayer]; + weights->attn_v_b = new llaisysTensor_t[nlayer]; + weights->attn_o_w = new llaisysTensor_t[nlayer]; + weights->mlp_norm_w = new llaisysTensor_t[nlayer]; + weights->mlp_gate_w = new llaisysTensor_t[nlayer]; + weights->mlp_up_w = new llaisysTensor_t[nlayer]; + weights->mlp_down_w = new llaisysTensor_t[nlayer]; + g_weights[model] = weights; + + return reinterpret_cast(model); +} + +void llaisysQwen2ModelDestroy(LlaisysQwen2Model *model) { + auto it = g_models.find(model); + if (it != g_models.end()) { + Qwen2Model *m = it->second; + delete m; + g_models.erase(it); + auto w_it = g_weights.find(m); + if (w_it != g_weights.end()) { + delete[] w_it->second->attn_norm_w; + delete[] w_it->second->attn_q_w; + delete[] w_it->second->attn_q_b; + delete[] w_it->second->attn_k_w; + delete[] w_it->second->attn_k_b; + delete[] w_it->second->attn_v_w; + delete[] w_it->second->attn_v_b; + delete[] w_it->second->attn_o_w; + delete[] w_it->second->mlp_norm_w; + delete[] w_it->second->mlp_gate_w; + delete[] w_it->second->mlp_up_w; + delete[] w_it->second->mlp_down_w; + delete w_it->second; + g_weights.erase(w_it); + } + } +} + +LlaisysQwen2Weights *llaisysQwen2ModelWeights(LlaisysQwen2Model *model) { + auto it = g_models.find(model); + if (it != g_models.end()) { + auto w_it = g_weights.find(it->second); + if (w_it != g_weights.end()) { + return w_it->second; + } + } + return nullptr; +} + +int64_t llaisysQwen2ModelInfer(LlaisysQwen2Model *model, int64_t *token_ids, size_t ntoken) { + auto it = g_models.find(model); + if (it != g_models.end()) { + Qwen2Model *m = it->second; + auto w_it = g_weights.find(m); + if (w_it != g_weights.end()) { + sync_weights_to_model(m, w_it->second, m->_config.num_hidden_layers); + } + return m->infer(token_ids, ntoken); + } + return -1; +} + +} // extern "C" \ No newline at end of file diff --git a/src/models/qwen2/qwen2.cpp b/src/models/qwen2/qwen2.cpp new file mode 100644 index 000000000..41101de84 --- /dev/null +++ b/src/models/qwen2/qwen2.cpp @@ -0,0 +1,84 @@ +#include "qwen2.hpp" +#include "../../ops/ops.hpp" +#include "../../utils.hpp" +#include +#include + +namespace llaisys::models { + +Qwen2Model::Qwen2Model(const Qwen2Config &c) : _config(c) { + _kv_cache_k.resize(c.num_hidden_layers); _kv_cache_v.resize(c.num_hidden_layers); +} +void Qwen2Model::load_weight(const std::string &n, tensor_t w) { _weights[n] = w; } +void Qwen2Model::load_weight_with_bias(const std::string &n, tensor_t w, tensor_t b) { + _weights[n] = w; if (b) _weights[n+".bias"] = b; +} +void Qwen2Model::reset_kv_cache() { for(auto&t:_kv_cache_k)t.reset(); for(auto&t:_kv_cache_v)t.reset(); } +tensor_t Qwen2Model::_rms_norm(const tensor_t &x, const tensor_t &w, float e) { + tensor_t o = Tensor::create(x->shape(), x->dtype(), x->deviceType(), x->deviceId()); + ops::rms_norm(o, x, w, e); return o; +} +tensor_t Qwen2Model::_attention(const tensor_t &x, const tensor_t &qw, const tensor_t &kw, const tensor_t &vw, + const tensor_t &ow, const tensor_t &qb, const tensor_t &kb, const tensor_t &vb, + size_t li) { + size_t S=x->shape()[0], D=_config.hidden_size/_config.num_attention_heads; + size_t Hq=_config.num_attention_heads, Hkv=_config.num_key_value_heads; + auto q2=Tensor::create({S,Hq*D},x->dtype(),x->deviceType(),x->deviceId()); + auto k2=Tensor::create({S,Hkv*D},x->dtype(),x->deviceType(),x->deviceId()); + auto v2=Tensor::create({S,Hkv*D},x->dtype(),x->deviceType(),x->deviceId()); + ops::linear(q2,x,qw,qb); ops::linear(k2,x,kw,kb); ops::linear(v2,x,vw,vb); + auto q=q2->view({S,Hq,D}); auto k=k2->view({S,Hkv,D}); auto v=v2->view({S,Hkv,D}); + std::vectorpos(S);for(size_t p=0;pdeviceType(),x->deviceId()); pid->load(pos.data()); + auto qr=Tensor::create(q->shape(),q->dtype(),q->deviceType(),q->deviceId()); + auto kr=Tensor::create(k->shape(),k->dtype(),k->deviceType(),k->deviceId()); + ops::rope(qr,q,pid,_config.rope_theta); ops::rope(kr,k,pid,_config.rope_theta); + auto a3=Tensor::create({S,Hq,D},x->dtype(),x->deviceType(),x->deviceId()); + ops::self_attention(a3,qr,kr,v,1.0f/std::sqrt((float)D)); + auto a2=a3->view({S,Hq*D}); + auto o=Tensor::create({S,_config.hidden_size},x->dtype(),x->deviceType(),x->deviceId()); + ops::linear(o,a2,ow,nullptr); + return o; +} + +tensor_t Qwen2Model::_swiglu_ffn(const tensor_t &x, const tensor_t &gw, const tensor_t &uw, const tensor_t &dw) { + size_t S=x->shape()[0]; + auto g=Tensor::create({S,_config.intermediate_size},x->dtype(),x->deviceType(),x->deviceId()); + auto u=Tensor::create({S,_config.intermediate_size},x->dtype(),x->deviceType(),x->deviceId()); + ops::linear(g,x,gw,nullptr); ops::linear(u,x,uw,nullptr); + auto sw=Tensor::create({S,_config.intermediate_size},x->dtype(),x->deviceType(),x->deviceId()); + ops::swiglu(sw,g,u); + auto o=Tensor::create({S,_config.hidden_size},x->dtype(),x->deviceType(),x->deviceId()); + ops::linear(o,sw,dw,nullptr); + return o; +} + +int64_t Qwen2Model::infer(const int64_t *tok, size_t n) { + if(n==0)return _config.end_token_id; + auto dev = LLAISYS_DEVICE_CPU; int dev_id = 0; + auto inp=Tensor::create({n},LLAISYS_DTYPE_I64,dev,dev_id); inp->load(tok); + auto hid=Tensor::create({n,_config.hidden_size},LLAISYS_DTYPE_F32,dev,dev_id); + ops::embedding(hid,inp,_embed_tokens); + for(size_t i=0;i<_config.num_hidden_layers;++i){ + auto nh=_rms_norm(hid,_layer_norms[i],_config.rms_norm_eps); + auto ao=_attention(nh,_layer_attn_q[i],_layer_attn_k[i],_layer_attn_v[i],_layer_attn_o[i], + _layer_attn_q_b[i],_layer_attn_k_b[i],_layer_attn_v_b[i],i); + auto a1=Tensor::create(hid->shape(),hid->dtype(),hid->deviceType(),hid->deviceId()); + ops::add(a1,hid,ao); hid=a1; + nh=_rms_norm(hid,_layer_post_norm[i],_config.rms_norm_eps); + auto fo=_swiglu_ffn(nh,_layer_ffn_gate[i],_layer_ffn_up[i],_layer_ffn_down[i]); + auto a2=Tensor::create(hid->shape(),hid->dtype(),hid->deviceType(),hid->deviceId()); + ops::add(a2,hid,fo); hid=a2; + } + hid=_rms_norm(hid,_final_norm,_config.rms_norm_eps); + auto log=Tensor::create({n,_config.vocab_size},hid->dtype(),hid->deviceType(),hid->deviceId()); + ops::linear(log,hid,_lm_head,nullptr); + auto last=log->slice(0,n-1,n); + auto flat=last->view({_config.vocab_size}); + auto mx=Tensor::create({1},LLAISYS_DTYPE_I64,dev,dev_id); + auto mv=Tensor::create({1},flat->dtype(),flat->deviceType(),flat->deviceId()); + ops::argmax(mx,mv,flat); + int64_t nt; std::memcpy(&nt,mx->data(),sizeof(int64_t)); + return nt; +} +} // namespace llaisys::models \ No newline at end of file diff --git a/src/models/qwen2/qwen2.hpp b/src/models/qwen2/qwen2.hpp new file mode 100644 index 000000000..de3c353ac --- /dev/null +++ b/src/models/qwen2/qwen2.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include "../../tensor/tensor.hpp" +#include +#include +#include + +namespace llaisys::models { + +struct Qwen2Config { + size_t hidden_size; + size_t intermediate_size; + size_t num_attention_heads; + size_t num_key_value_heads; + size_t vocab_size; + size_t max_position_embeddings; + size_t num_hidden_layers; + float rms_norm_eps; + float rope_theta; + int64_t end_token_id; +}; + +class Qwen2Model { +public: + explicit Qwen2Model(const Qwen2Config &config); + ~Qwen2Model() = default; + + void load_weight(const std::string &name, tensor_t weight); + void load_weight_with_bias(const std::string &name, tensor_t weight, tensor_t bias); + + void reset_kv_cache(); + int64_t infer(const int64_t *token_ids, size_t ntoken); + + Qwen2Config _config; + std::unordered_map _weights; + + tensor_t _embed_tokens; + std::vector _layer_norms; + std::vector _layer_attn_q; + std::vector _layer_attn_q_b; + std::vector _layer_attn_k; + std::vector _layer_attn_k_b; + std::vector _layer_attn_v; + std::vector _layer_attn_v_b; + std::vector _layer_attn_o; + std::vector _layer_post_norm; + std::vector _layer_ffn_gate; + std::vector _layer_ffn_up; + std::vector _layer_ffn_down; + tensor_t _final_norm; + tensor_t _lm_head; + + std::vector _kv_cache_k; + std::vector _kv_cache_v; + +private: + tensor_t _rms_norm(const tensor_t &x, const tensor_t &weight, float eps); + tensor_t _attention(const tensor_t &x, + const tensor_t &q_w, const tensor_t &k_w, const tensor_t &v_w, + const tensor_t &o_w, + const tensor_t &q_b, const tensor_t &k_b, const tensor_t &v_b, + size_t layer_idx); + tensor_t _swiglu_ffn(const tensor_t &x, + const tensor_t &gate_w, const tensor_t &up_w, const tensor_t &down_w); +}; + +} // namespace llaisys::models \ No newline at end of file diff --git a/src/ops/add/nvidia/add_cuda.cu b/src/ops/add/nvidia/add_cuda.cu new file mode 100644 index 000000000..404fe6410 --- /dev/null +++ b/src/ops/add/nvidia/add_cuda.cu @@ -0,0 +1,17 @@ +#include "add_cuda.cuh" +#include "../../../utils.hpp" +#include +namespace llaisys::ops::nvidia { +__global__ void add_kernel_f32(const float *a, const float *b, float *c, size_t n) { + size_t idx = blockIdx.x * blockDim.x + threadIdx.x; + if (idx < n) c[idx] = a[idx] + b[idx]; +} +void add(const std::byte *a, const std::byte *b, std::byte *c, + llaisysDataType_t dtype, size_t numel) { + if (dtype == LLAISYS_DTYPE_F32) { + int block = 256, grid = (int)((numel + block - 1) / block); + add_kernel_f32<<>>((const float*)a, (const float*)b, (float*)c, numel); + cudaDeviceSynchronize(); + } +} +} \ No newline at end of file diff --git a/src/ops/add/nvidia/add_cuda.cuh b/src/ops/add/nvidia/add_cuda.cuh new file mode 100644 index 000000000..caf6e955b --- /dev/null +++ b/src/ops/add/nvidia/add_cuda.cuh @@ -0,0 +1,10 @@ +#pragma once +#include "../../../utils.hpp" +#include + +namespace llaisys::ops::nvidia { + +void add(const std::byte *a_data, const std::byte *b_data, std::byte *c_data, + llaisysDataType_t dtype, size_t numel); + +} // namespace llaisys::ops::nvidia \ No newline at end of file diff --git a/src/ops/add/op.cpp b/src/ops/add/op.cpp index a057330d7..c457e71de 100644 --- a/src/ops/add/op.cpp +++ b/src/ops/add/op.cpp @@ -4,6 +4,9 @@ #include "../../utils.hpp" #include "cpu/add_cpu.hpp" +#ifdef ENABLE_NVIDIA_API +#include "nvidia/add_cuda.cuh" +#endif namespace llaisys::ops { void add(tensor_t c, tensor_t a, tensor_t b) { @@ -25,8 +28,7 @@ void add(tensor_t c, tensor_t a, tensor_t b) { return cpu::add(c->data(), a->data(), b->data(), c->dtype(), c->numel()); #ifdef ENABLE_NVIDIA_API case LLAISYS_DEVICE_NVIDIA: - TO_BE_IMPLEMENTED(); - return; + return nvidia::add(c->data(), a->data(), b->data(), c->dtype(), c->numel()); #endif default: EXCEPTION_UNSUPPORTED_DEVICE; diff --git a/src/ops/argmax/cpu/argmax_cpu.hpp b/src/ops/argmax/cpu/argmax_cpu.hpp new file mode 100644 index 000000000..6aa7d3218 --- /dev/null +++ b/src/ops/argmax/cpu/argmax_cpu.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include "../../../utils.hpp" +#include + +namespace llaisys::ops::cpu { + +// 模板函数:处理 float / fp16_t / bf16_t +template +void argmax_impl(const std::byte *vals_data, + std::byte *max_idx_data, + std::byte *max_val_data, + size_t numel) { + // 空张量防御 + if (numel == 0) { + *reinterpret_cast(max_idx_data) = 0; + return; + } + + const T *vals = reinterpret_cast(vals_data); + T *max_val = reinterpret_cast(max_val_data); + int64_t *max_idx = reinterpret_cast(max_idx_data); + + // 初始化:取第一个元素 + size_t best_idx = 0; + float best_val = utils::cast(vals[0]); + + // 遍历比较(统一转 float 比较,避免半精度直接比较) + for (size_t i = 1; i < numel; ++i) { + float current = utils::cast(vals[i]); + if (current > best_val) { + best_val = current; + best_idx = i; + } + } + + // 写回结果 + *max_idx = static_cast(best_idx); + max_val[0] = utils::cast(best_val); +} + +// CPU 入口:根据 dtype 分发 +inline void argmax(const std::byte *vals_data, + std::byte *max_idx_data, + std::byte *max_val_data, + llaisysDataType_t dtype, + size_t numel) { + switch (dtype) { + case LLAISYS_DTYPE_F32: + return argmax_impl(vals_data, max_idx_data, max_val_data, numel); + case LLAISYS_DTYPE_F16: + return argmax_impl(vals_data, max_idx_data, max_val_data, numel); + case LLAISYS_DTYPE_BF16: + return argmax_impl(vals_data, max_idx_data, max_val_data, numel); + default: + EXCEPTION_UNSUPPORTED_DATATYPE(dtype); + } +} + +} // namespace llaisys::ops::cpu \ No newline at end of file diff --git a/src/ops/argmax/nvidia/argmax_cuda.cu b/src/ops/argmax/nvidia/argmax_cuda.cu new file mode 100644 index 000000000..83ef16ce1 --- /dev/null +++ b/src/ops/argmax/nvidia/argmax_cuda.cu @@ -0,0 +1,22 @@ +#include "argmax_cuda.cuh" +#include +namespace llaisys::ops::nvidia { +__global__ void argmax_f32_kernel(const float *vals, int64_t *idx, float *val, size_t N) { + __shared__ float s_max[256]; + __shared__ int64_t s_idx[256]; + int tid = threadIdx.x, i = tid; + float best = -1e30f; int64_t best_i = 0; + for (; i < N; i += blockDim.x) { if (vals[i] > best) { best = vals[i]; best_i = i; } } + s_max[tid] = best; s_idx[tid] = best_i; __syncthreads(); + for (int s = 128; s > 0; s >>= 1) { + if (tid < s && s_max[tid + s] > s_max[tid]) { s_max[tid] = s_max[tid + s]; s_idx[tid] = s_idx[tid + s]; } + __syncthreads(); + } + if (tid == 0) { *idx = s_idx[0]; *val = s_max[0]; } +} +void argmax(const std::byte *vals, std::byte *max_idx, std::byte *max_val, + llaisysDataType_t dtype, size_t N) { + if (dtype == LLAISYS_DTYPE_F32) + argmax_f32_kernel<<<1, 256>>>((const float*)vals, (int64_t*)max_idx, (float*)max_val, N); +} +} \ No newline at end of file diff --git a/src/ops/argmax/nvidia/argmax_cuda.cuh b/src/ops/argmax/nvidia/argmax_cuda.cuh new file mode 100644 index 000000000..012c65d3e --- /dev/null +++ b/src/ops/argmax/nvidia/argmax_cuda.cuh @@ -0,0 +1,7 @@ +#pragma once +#include "../../../utils.hpp" +#include +namespace llaisys::ops::nvidia { +void argmax(const std::byte *vals, std::byte *max_idx, std::byte *max_val, + llaisysDataType_t dtype, size_t N); +} \ No newline at end of file diff --git a/src/ops/argmax/op.cpp b/src/ops/argmax/op.cpp index 6dc37d426..2cdfbf7dd 100644 --- a/src/ops/argmax/op.cpp +++ b/src/ops/argmax/op.cpp @@ -1,7 +1,58 @@ #include "op.hpp" +#include "cpu/argmax_cpu.hpp" +#ifdef ENABLE_NVIDIA_API +#include "nvidia/argmax_cuda.cuh" +#endif + +#include "../../core/llaisys_core.hpp" +#include "../../utils.hpp" namespace llaisys::ops { + void argmax(tensor_t max_idx, tensor_t max_val, tensor_t vals) { - TO_BE_IMPLEMENTED(); + // ============================================================ + // 1. 参数检查 + // ============================================================ + CHECK_SAME_DEVICE(max_idx, max_val, vals); + + ASSERT(max_idx->ndim() == 1 && max_idx->numel() == 1, + "argmax: max_idx must be shape [1]"); + ASSERT(max_val->ndim() == 1 && max_val->numel() == 1, + "argmax: max_val must be shape [1]"); + ASSERT(vals->ndim() == 1, + "argmax: vals must be 1D tensor"); + + ASSERT(max_idx->dtype() == LLAISYS_DTYPE_I64, + "argmax: max_idx dtype must be int64"); + ASSERT(max_idx->isContiguous() && max_val->isContiguous() && vals->isContiguous(), + "argmax: all tensors must be contiguous"); + + llaisysDataType_t dtype = vals->dtype(); + ASSERT(dtype == LLAISYS_DTYPE_F32 || dtype == LLAISYS_DTYPE_F16 || dtype == LLAISYS_DTYPE_BF16, + "argmax: vals dtype must be F32, F16, or BF16"); + ASSERT(max_val->dtype() == dtype, + "argmax: max_val dtype must match vals dtype"); + + // ============================================================ + // 2. 设备分发(当前阶段只有 CPU) + // ============================================================ + if (vals->deviceType() == LLAISYS_DEVICE_CPU) { + // 调用 cpu 命名空间里的函数 + return cpu::argmax( + vals->data(), + max_idx->data(), + max_val->data(), + dtype, + vals->numel()); + } + + // 如果是在 GPU 上,作业 #4 再实现 +#ifdef ENABLE_NVIDIA_API + if (vals->deviceType() == LLAISYS_DEVICE_NVIDIA) { + return nvidia::argmax(vals->data(), max_idx->data(), max_val->data(), dtype, vals->numel()); + } +#endif + EXCEPTION_UNSUPPORTED_DEVICE; } -} // namespace llaisys::ops + +} // namespace llaisys::ops \ No newline at end of file diff --git a/src/ops/embedding/cpu/embedding_cpu.hpp b/src/ops/embedding/cpu/embedding_cpu.hpp new file mode 100644 index 000000000..0848d2c54 --- /dev/null +++ b/src/ops/embedding/cpu/embedding_cpu.hpp @@ -0,0 +1,58 @@ +#pragma once + +#include "../../../utils.hpp" +#include +#include // std::memcpy + +namespace llaisys::ops::cpu { + +template +void embedding_impl(const int64_t *index_data, + const T *weight_data, + T *out_data, + size_t num_indices, + size_t hidden_size) { + // 每行占用的字节数 + size_t row_bytes = hidden_size * sizeof(T); + + for (size_t i = 0; i < num_indices; ++i) { + int64_t idx = index_data[i]; // 取出要拷贝的行号 + + // 源行指针:weight 的第 idx 行 + const T *src_row = weight_data + idx * hidden_size; + // 目标行指针:out 的第 i 行 + T *dst_row = out_data + i * hidden_size; + + // 直接用 memcpy 把一整行搬过去! + std::memcpy(dst_row, src_row, row_bytes); + } +} + +inline void embedding(const int64_t *index_data, + const std::byte *weight_data, + std::byte *out_data, + llaisysDataType_t dtype, + size_t num_indices, + size_t hidden_size) { + switch (dtype) { + case LLAISYS_DTYPE_F32: + return embedding_impl(index_data, + reinterpret_cast(weight_data), + reinterpret_cast(out_data), + num_indices, hidden_size); + case LLAISYS_DTYPE_F16: + return embedding_impl(index_data, + reinterpret_cast(weight_data), + reinterpret_cast(out_data), + num_indices, hidden_size); + case LLAISYS_DTYPE_BF16: + return embedding_impl(index_data, + reinterpret_cast(weight_data), + reinterpret_cast(out_data), + num_indices, hidden_size); + default: + EXCEPTION_UNSUPPORTED_DATATYPE(dtype); + } +} + +} // namespace llaisys::ops::cpu \ No newline at end of file diff --git a/src/ops/embedding/nvidia/embedding_cuda.cu b/src/ops/embedding/nvidia/embedding_cuda.cu new file mode 100644 index 000000000..49709fa44 --- /dev/null +++ b/src/ops/embedding/nvidia/embedding_cuda.cu @@ -0,0 +1,18 @@ +#include "embedding_cuda.cuh" +#include +namespace llaisys::ops::nvidia { +__global__ void embedding_f32_kernel(const int64_t *idx, const float *weight, float *out, int H) { + int i = blockIdx.x; + int64_t token = idx[i]; + const float *src = weight + token * H; + float *dst = out + i * (size_t)H; + for (int j = threadIdx.x; j < H; j += blockDim.x) dst[j] = src[j]; +} +void embedding(const int64_t *idx, const std::byte *weight, std::byte *out, + llaisysDataType_t dtype, size_t N, size_t H) { + if (dtype == LLAISYS_DTYPE_F32) { + embedding_f32_kernel<<<(int)N, 256>>>((const int64_t*)idx, (const float*)weight, (float*)out, (int)H); + cudaDeviceSynchronize(); + } +} +} \ No newline at end of file diff --git a/src/ops/embedding/nvidia/embedding_cuda.cuh b/src/ops/embedding/nvidia/embedding_cuda.cuh new file mode 100644 index 000000000..020f8c931 --- /dev/null +++ b/src/ops/embedding/nvidia/embedding_cuda.cuh @@ -0,0 +1,7 @@ +#pragma once +#include "../../../utils.hpp" +#include +namespace llaisys::ops::nvidia { +void embedding(const int64_t *idx, const std::byte *weight, std::byte *out, + llaisysDataType_t dtype, size_t N, size_t H); +} \ No newline at end of file diff --git a/src/ops/embedding/op.cpp b/src/ops/embedding/op.cpp index 84b9a5d06..1df973970 100644 --- a/src/ops/embedding/op.cpp +++ b/src/ops/embedding/op.cpp @@ -1,7 +1,66 @@ #include "op.hpp" +#include "cpu/embedding_cpu.hpp" +#ifdef ENABLE_NVIDIA_API +#include "nvidia/embedding_cuda.cuh" +#endif + +#include "../../core/llaisys_core.hpp" +#include "../../utils.hpp" namespace llaisys::ops { + void embedding(tensor_t out, tensor_t index, tensor_t weight) { - TO_BE_IMPLEMENTED(); + // ============================================================ + // 1. 参数检查 + // ============================================================ + CHECK_SAME_DEVICE(out, index, weight); + + // index 必须是 1D 的 int64 + ASSERT(index->ndim() == 1, "embedding: index must be 1D"); + ASSERT(index->dtype() == LLAISYS_DTYPE_I64, "embedding: index must be int64"); + + // weight 必须是 2D + ASSERT(weight->ndim() == 2, "embedding: weight must be 2D"); + + // out 必须是 2D,且形状匹配 + ASSERT(out->ndim() == 2, "embedding: out must be 2D"); + ASSERT(out->shape()[0] == index->numel(), "embedding: out.shape[0] must equal index.numel()"); + ASSERT(out->shape()[1] == weight->shape()[1], "embedding: hidden size must match weight"); + + // out 和 weight 的 dtype 必须一致 + ASSERT(out->dtype() == weight->dtype(), "embedding: out and weight must have same dtype"); + + // 所有张量必须连续(简化处理) + ASSERT(out->isContiguous() && index->isContiguous() && weight->isContiguous(), + "embedding: all tensors must be contiguous"); + + llaisysDataType_t dtype = out->dtype(); + ASSERT(dtype == LLAISYS_DTYPE_F32 || dtype == LLAISYS_DTYPE_F16 || dtype == LLAISYS_DTYPE_BF16, + "embedding: dtype must be F32, F16, or BF16"); + + // ============================================================ + // 2. 设备分发 + // ============================================================ + if (out->deviceType() == LLAISYS_DEVICE_CPU) { + return cpu::embedding( + reinterpret_cast(index->data()), // index 是 int64 + weight->data(), // 输入权重(字节指针) + out->data(), // 输出(字节指针) + dtype, + index->numel(), // 要取多少行 + weight->shape()[1] // 隐藏层维度 + ); + } + +#ifdef ENABLE_NVIDIA_API + if (out->deviceType() == LLAISYS_DEVICE_NVIDIA) { + return nvidia::embedding( + reinterpret_cast(index->data()), + weight->data(), out->data(), dtype, + index->numel(), weight->shape()[1]); + } +#endif + EXCEPTION_UNSUPPORTED_DEVICE; } -} // namespace llaisys::ops + +} // namespace llaisys::ops \ No newline at end of file diff --git a/src/ops/linear/cpu/linear_cpu.hpp b/src/ops/linear/cpu/linear_cpu.hpp new file mode 100644 index 000000000..d47c5095c --- /dev/null +++ b/src/ops/linear/cpu/linear_cpu.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include "../../../utils.hpp" +#include +#include + +namespace llaisys::ops::cpu { + +template +void linear_impl(const T *in_data, + const T *weight_data, + const T *bias_data, // 可能为 nullptr + T *out_data, + size_t batch, // M + size_t in_features, // K + size_t out_features) { // N + for (size_t i = 0; i < batch; ++i) { + for (size_t j = 0; j < out_features; ++j) { + // 计算 Y[i][j] = sum_{k} X[i][k] * W[j][k] + // 注意:W 没有预先转置,所以这里按行读取 W,而不是按列 + float sum = 0.0f; + for (size_t k = 0; k < in_features; ++k) { + float x = utils::cast(in_data[i * in_features + k]); + float w = utils::cast(weight_data[j * in_features + k]); + sum += x * w; + } + // 加上偏置(如果存在) + if (bias_data != nullptr) { + float b = utils::cast(bias_data[j]); + sum += b; + } + out_data[i * out_features + j] = utils::cast(sum); + } + } +} + +inline void linear(const std::byte *in_data, + const std::byte *weight_data, + const std::byte *bias_data, // 可能为 nullptr + std::byte *out_data, + llaisysDataType_t dtype, + size_t batch, + size_t in_features, + size_t out_features) { + switch (dtype) { + case LLAISYS_DTYPE_F32: + return linear_impl( + reinterpret_cast(in_data), + reinterpret_cast(weight_data), + reinterpret_cast(bias_data), + reinterpret_cast(out_data), + batch, in_features, out_features); + case LLAISYS_DTYPE_F16: + return linear_impl( + reinterpret_cast(in_data), + reinterpret_cast(weight_data), + reinterpret_cast(bias_data), + reinterpret_cast(out_data), + batch, in_features, out_features); + case LLAISYS_DTYPE_BF16: + return linear_impl( + reinterpret_cast(in_data), + reinterpret_cast(weight_data), + reinterpret_cast(bias_data), + reinterpret_cast(out_data), + batch, in_features, out_features); + default: + EXCEPTION_UNSUPPORTED_DATATYPE(dtype); + } +} + +} // namespace llaisys::ops::cpu \ No newline at end of file diff --git a/src/ops/linear/nvidia/linear_cuda.cu b/src/ops/linear/nvidia/linear_cuda.cu new file mode 100644 index 000000000..67fe1969c --- /dev/null +++ b/src/ops/linear/nvidia/linear_cuda.cu @@ -0,0 +1,23 @@ +#include "linear_cuda.cuh" +#include +namespace llaisys::ops::nvidia { +__global__ void linear_f32_kernel(const float *in, const float *w, const float *bias, float *out, + int B, int K, int N) { + int row = blockIdx.x * blockDim.x + threadIdx.x; + int col = blockIdx.y * blockDim.y + threadIdx.y; + if (row < B && col < N) { + float sum = bias ? bias[col] : 0.0f; + for (int k = 0; k < K; ++k) sum += in[row * K + k] * w[col * K + k]; + out[row * N + col] = sum; + } +} +void linear(const std::byte *in, const std::byte *weight, const std::byte *bias, std::byte *out, + llaisysDataType_t dtype, size_t B, size_t K, size_t N) { + if (dtype == LLAISYS_DTYPE_F32) { + dim3 block(16, 16); + dim3 grid((int)((B + 15) / 16), (int)((N + 15) / 16)); + linear_f32_kernel<<>>((const float*)in, (const float*)weight, (const float*)bias, (float*)out, (int)B, (int)K, (int)N); + cudaDeviceSynchronize(); + } +} +} \ No newline at end of file diff --git a/src/ops/linear/nvidia/linear_cuda.cuh b/src/ops/linear/nvidia/linear_cuda.cuh new file mode 100644 index 000000000..3291086ca --- /dev/null +++ b/src/ops/linear/nvidia/linear_cuda.cuh @@ -0,0 +1,7 @@ +#pragma once +#include "../../../utils.hpp" +#include +namespace llaisys::ops::nvidia { +void linear(const std::byte *in, const std::byte *weight, const std::byte *bias, std::byte *out, + llaisysDataType_t dtype, size_t batch, size_t in_feat, size_t out_feat); +} \ No newline at end of file diff --git a/src/ops/linear/op.cpp b/src/ops/linear/op.cpp index 97d1f8655..6a7038633 100644 --- a/src/ops/linear/op.cpp +++ b/src/ops/linear/op.cpp @@ -1,7 +1,78 @@ #include "op.hpp" +#include "cpu/linear_cpu.hpp" +#ifdef ENABLE_NVIDIA_API +#include "nvidia/linear_cuda.cuh" +#endif + +#include "../../core/llaisys_core.hpp" +#include "../../utils.hpp" namespace llaisys::ops { + void linear(tensor_t out, tensor_t in, tensor_t weight, tensor_t bias) { - TO_BE_IMPLEMENTED(); + // ============================================================ + // 1. 参数检查 + // ============================================================ + CHECK_SAME_DEVICE(out, in, weight); + if (bias != nullptr) { + CHECK_SAME_DEVICE(out, bias); + } + + // 检查维度 + ASSERT(in->ndim() == 2, "linear: in must be 2D"); + ASSERT(weight->ndim() == 2, "linear: weight must be 2D"); + ASSERT(out->ndim() == 2, "linear: out must be 2D"); + + size_t batch = in->shape()[0]; + size_t in_features = in->shape()[1]; + size_t out_features = weight->shape()[0]; + + ASSERT(weight->shape()[1] == in_features, "linear: weight shape[1] must equal in_features"); + ASSERT(out->shape()[0] == batch, "linear: out.shape[0] must equal batch"); + ASSERT(out->shape()[1] == out_features, "linear: out.shape[1] must equal out_features"); + + if (bias != nullptr) { + ASSERT(bias->ndim() == 1, "linear: bias must be 1D"); + ASSERT(bias->shape()[0] == out_features, "linear: bias size must equal out_features"); + ASSERT(out->dtype() == bias->dtype(), "linear: out and bias must have same dtype"); + } + + ASSERT(out->dtype() == in->dtype(), "linear: out and in must have same dtype"); + ASSERT(out->dtype() == weight->dtype(), "linear: out and weight must have same dtype"); + + ASSERT(out->isContiguous() && in->isContiguous() && weight->isContiguous(), + "linear: all tensors must be contiguous"); + if (bias != nullptr) { + ASSERT(bias->isContiguous(), "linear: bias must be contiguous"); + } + + llaisysDataType_t dtype = out->dtype(); + ASSERT(dtype == LLAISYS_DTYPE_F32 || dtype == LLAISYS_DTYPE_F16 || dtype == LLAISYS_DTYPE_BF16, + "linear: dtype must be F32, F16, or BF16"); + + // ============================================================ + // 2. 设备分发 + // ============================================================ + if (out->deviceType() == LLAISYS_DEVICE_CPU) { + return cpu::linear( + in->data(), + weight->data(), + bias != nullptr ? bias->data() : nullptr, + out->data(), + dtype, + batch, + in_features, + out_features); + } + +#ifdef ENABLE_NVIDIA_API + if (out->deviceType() == LLAISYS_DEVICE_NVIDIA) { + return nvidia::linear(in->data(), weight->data(), + bias != nullptr ? bias->data() : nullptr, + out->data(), dtype, batch, in_features, out_features); + } +#endif + EXCEPTION_UNSUPPORTED_DEVICE; } -} // namespace llaisys::ops + +} // namespace llaisys::ops \ No newline at end of file diff --git a/src/ops/ops.hpp b/src/ops/ops.hpp new file mode 100644 index 000000000..6ac0231aa --- /dev/null +++ b/src/ops/ops.hpp @@ -0,0 +1,10 @@ +#pragma once + +#include "add/op.hpp" +#include "argmax/op.hpp" +#include "embedding/op.hpp" +#include "linear/op.hpp" +#include "rms_norm/op.hpp" +#include "rope/op.hpp" +#include "self_attention/op.hpp" +#include "swiglu/op.hpp" \ No newline at end of file diff --git a/src/ops/rms_norm/cpu/rms_norm_cpu.hpp b/src/ops/rms_norm/cpu/rms_norm_cpu.hpp new file mode 100644 index 000000000..7bf599bbc --- /dev/null +++ b/src/ops/rms_norm/cpu/rms_norm_cpu.hpp @@ -0,0 +1,76 @@ +#pragma once + +#include "../../../utils.hpp" +#include +#include + +namespace llaisys::ops::cpu { + +template +void rms_norm_impl(const T *in_data, + const T *weight_data, + T *out_data, + size_t rows, + size_t cols, + float eps) { + // 每行的元素个数(列数) + size_t d = cols; + + for (size_t r = 0; r < rows; ++r) { + // 计算当前行的起始指针(跳过 r 行) + const T *in_row = in_data + r * d; + T *out_row = out_data + r * d; + + // 第一步:计算平方和 sum = Σ(x_i^2) + float sum = 0.0f; + for (size_t i = 0; i < d; ++i) { + float x = utils::cast(in_row[i]); + sum += x * x; + } + + // 第二步:计算均方根的分母 = sqrt(sum / d + eps) + float mean_sq = sum / static_cast(d); + float inv_denom = 1.0f / std::sqrt(mean_sq + eps); + + // 第三步:归一化并乘以 weight + for (size_t i = 0; i < d; ++i) { + float x = utils::cast(in_row[i]); + float w = utils::cast(weight_data[i]); + float result = (x * inv_denom) * w; + out_row[i] = utils::cast(result); + } + } +} + +inline void rms_norm(const std::byte *in_data, + const std::byte *weight_data, + std::byte *out_data, + llaisysDataType_t dtype, + size_t rows, + size_t cols, + float eps) { + switch (dtype) { + case LLAISYS_DTYPE_F32: + return rms_norm_impl( + reinterpret_cast(in_data), + reinterpret_cast(weight_data), + reinterpret_cast(out_data), + rows, cols, eps); + case LLAISYS_DTYPE_F16: + return rms_norm_impl( + reinterpret_cast(in_data), + reinterpret_cast(weight_data), + reinterpret_cast(out_data), + rows, cols, eps); + case LLAISYS_DTYPE_BF16: + return rms_norm_impl( + reinterpret_cast(in_data), + reinterpret_cast(weight_data), + reinterpret_cast(out_data), + rows, cols, eps); + default: + EXCEPTION_UNSUPPORTED_DATATYPE(dtype); + } +} + +} // namespace llaisys::ops::cpu diff --git a/src/ops/rms_norm/nvidia/rms_norm_cuda.cu b/src/ops/rms_norm/nvidia/rms_norm_cuda.cu new file mode 100644 index 000000000..02f1819da --- /dev/null +++ b/src/ops/rms_norm/nvidia/rms_norm_cuda.cu @@ -0,0 +1,25 @@ +#include "rms_norm_cuda.cuh" +#include +#include +namespace llaisys::ops::nvidia { +__global__ void rms_norm_f32_kernel(const float *in, const float *weight, float *out, + int rows, int cols, float eps) { + int r = blockIdx.x; if (r >= rows) return; + const float *in_row = in + r * cols; + float *out_row = out + r * cols; + float sum = 0.0f; + for (int i = threadIdx.x; i < cols; i += blockDim.x) sum += in_row[i] * in_row[i]; + for (int offset = 16; offset > 0; offset /= 2) + sum += __shfl_down_sync(0xffffffff, sum, offset); + float inv = 1.0f / sqrtf(sum / (float)cols + eps); + for (int i = threadIdx.x; i < cols; i += blockDim.x) + out_row[i] = in_row[i] * inv * weight[i]; +} +void rms_norm(const std::byte *in, const std::byte *weight, std::byte *out, + llaisysDataType_t dtype, size_t rows, size_t cols, float eps) { + if (dtype == LLAISYS_DTYPE_F32) { + rms_norm_f32_kernel<<<(int)rows, 256>>>((const float*)in, (const float*)weight, (float*)out, (int)rows, (int)cols, eps); + cudaDeviceSynchronize(); + } +} +} \ No newline at end of file diff --git a/src/ops/rms_norm/nvidia/rms_norm_cuda.cuh b/src/ops/rms_norm/nvidia/rms_norm_cuda.cuh new file mode 100644 index 000000000..ea2ed6cd7 --- /dev/null +++ b/src/ops/rms_norm/nvidia/rms_norm_cuda.cuh @@ -0,0 +1,10 @@ +#pragma once +#include "../../../utils.hpp" +#include + +namespace llaisys::ops::nvidia { + +void rms_norm(const std::byte *in_data, const std::byte *weight_data, std::byte *out_data, + llaisysDataType_t dtype, size_t rows, size_t cols, float eps); + +} \ No newline at end of file diff --git a/src/ops/rms_norm/op.cpp b/src/ops/rms_norm/op.cpp index 529553d9d..c98162a7a 100644 --- a/src/ops/rms_norm/op.cpp +++ b/src/ops/rms_norm/op.cpp @@ -1,7 +1,62 @@ #include "op.hpp" +#include "cpu/rms_norm_cpu.hpp" +#ifdef ENABLE_NVIDIA_API +#include "nvidia/rms_norm_cuda.cuh" +#endif + +#include "../../core/llaisys_core.hpp" +#include "../../utils.hpp" namespace llaisys::ops { + void rms_norm(tensor_t out, tensor_t in, tensor_t weight, float eps) { - TO_BE_IMPLEMENTED(); + // ============================================================ + // 1. 参数检查 + // ============================================================ + CHECK_SAME_DEVICE(out, in, weight); + + // out 和 in 必须是 2D,且形状相同 + ASSERT(out->ndim() == 2, "rms_norm: out must be 2D"); + ASSERT(in->ndim() == 2, "rms_norm: in must be 2D"); + ASSERT(out->shape()[0] == in->shape()[0], "rms_norm: out and in must have same rows"); + ASSERT(out->shape()[1] == in->shape()[1], "rms_norm: out and in must have same cols"); + + // weight 必须是 1D,且长度等于列数 + ASSERT(weight->ndim() == 1, "rms_norm: weight must be 1D"); + ASSERT(weight->shape()[0] == in->shape()[1], "rms_norm: weight size must match hidden size"); + + // out 和 in 的 dtype 必须一致 + ASSERT(out->dtype() == in->dtype(), "rms_norm: out and in must have same dtype"); + + // 所有张量必须连续 + ASSERT(out->isContiguous() && in->isContiguous() && weight->isContiguous(), + "rms_norm: all tensors must be contiguous"); + + llaisysDataType_t dtype = out->dtype(); + ASSERT(dtype == LLAISYS_DTYPE_F32 || dtype == LLAISYS_DTYPE_F16 || dtype == LLAISYS_DTYPE_BF16, + "rms_norm: dtype must be F32, F16, or BF16"); + + // ============================================================ + // 2. 设备分发 + // ============================================================ + if (out->deviceType() == LLAISYS_DEVICE_CPU) { + return cpu::rms_norm( + in->data(), + weight->data(), + out->data(), + dtype, + in->shape()[0], // 行数 + in->shape()[1], // 列数(hidden size) + eps); + } + +#ifdef ENABLE_NVIDIA_API + if (out->deviceType() == LLAISYS_DEVICE_NVIDIA) { + return nvidia::rms_norm(in->data(), weight->data(), out->data(), dtype, + in->shape()[0], in->shape()[1], eps); + } +#endif + EXCEPTION_UNSUPPORTED_DEVICE; } -} // namespace llaisys::ops + +} // namespace llaisys::ops \ No newline at end of file diff --git a/src/ops/rope/cpu/rope_cpu.hpp b/src/ops/rope/cpu/rope_cpu.hpp new file mode 100644 index 000000000..6afc31dac --- /dev/null +++ b/src/ops/rope/cpu/rope_cpu.hpp @@ -0,0 +1,75 @@ +#pragma once + +#include "../../../utils.hpp" +#include +#include + +namespace llaisys::ops::cpu { + +template +void rope_impl(const T *in_data, + const int64_t *pos_ids_data, + T *out_data, + size_t seqlen, + size_t nhead, + size_t d, + float theta) { + size_t half_d = d / 2; + // 预计算频率指数:theta^(-2j/d) + // 为了数值稳定,使用 double 计算角度 + for (size_t i = 0; i < seqlen; ++i) { + double pos = static_cast(pos_ids_data[i]); + for (size_t h = 0; h < nhead; ++h) { + size_t head_offset = (i * nhead + h) * d; + for (size_t j = 0; j < half_d; ++j) { + double exponent = (2.0 * j) / d; + double angle = pos / std::pow(theta, exponent); + double cos_val = std::cos(angle); + double sin_val = std::sin(angle); + + float a = utils::cast(in_data[head_offset + j]); + float b = utils::cast(in_data[head_offset + half_d + j]); + + float out_a = static_cast(a * cos_val - b * sin_val); + float out_b = static_cast(a * sin_val + b * cos_val); + + out_data[head_offset + j] = utils::cast(out_a); + out_data[head_offset + half_d + j] = utils::cast(out_b); + } + } + } +} + +inline void rope(const std::byte *in_data, + const std::byte *pos_ids_data, + std::byte *out_data, + llaisysDataType_t dtype, + size_t seqlen, + size_t nhead, + size_t d, + float theta) { + switch (dtype) { + case LLAISYS_DTYPE_F32: + return rope_impl( + reinterpret_cast(in_data), + reinterpret_cast(pos_ids_data), + reinterpret_cast(out_data), + seqlen, nhead, d, theta); + case LLAISYS_DTYPE_F16: + return rope_impl( + reinterpret_cast(in_data), + reinterpret_cast(pos_ids_data), + reinterpret_cast(out_data), + seqlen, nhead, d, theta); + case LLAISYS_DTYPE_BF16: + return rope_impl( + reinterpret_cast(in_data), + reinterpret_cast(pos_ids_data), + reinterpret_cast(out_data), + seqlen, nhead, d, theta); + default: + EXCEPTION_UNSUPPORTED_DATATYPE(dtype); + } +} + +} // namespace llaisys::ops::cpu \ No newline at end of file diff --git a/src/ops/rope/nvidia/rope_cuda.cu b/src/ops/rope/nvidia/rope_cuda.cu new file mode 100644 index 000000000..92df13e3b --- /dev/null +++ b/src/ops/rope/nvidia/rope_cuda.cu @@ -0,0 +1,33 @@ +#include "rope_cuda.cuh" +#include +#include +namespace llaisys::ops::nvidia { +__global__ void rope_f32(const float *in, const int64_t *pos, float *out, + int seqlen, int nhead, int d, float theta) { + int idx = blockIdx.x * blockDim.x + threadIdx.x; + int total = seqlen * nhead * (d / 2); + if (idx >= total) return; + int s = idx / (nhead * (d/2)); + int h = (idx / (d/2)) % nhead; + int j = idx % (d/2); + + int off = (s * nhead + h) * d; + double p = (double)pos[s]; + double angle = p / pow(theta, (2.0*j)/d); + float cos_a = cosf((float)angle), sin_a = sinf((float)angle); + + float a = in[off + j]; + float b = in[off + d/2 + j]; + out[off + j] = a * cos_a - b * sin_a; + out[off + d/2 + j] = a * sin_a + b * cos_a; +} +void rope(const std::byte *in, const std::byte *pos_ids, std::byte *out, + llaisysDataType_t dtype, size_t seqlen, size_t nhead, size_t d, float theta) { + if (dtype != LLAISYS_DTYPE_F32) return; + int total = (int)(seqlen * nhead * (d/2)); + int block = 256; + int grid = (total + block - 1) / block; + rope_f32<<>>((const float*)in, (const int64_t*)pos_ids, (float*)out, + (int)seqlen, (int)nhead, (int)d, theta); +} +} \ No newline at end of file diff --git a/src/ops/rope/nvidia/rope_cuda.cuh b/src/ops/rope/nvidia/rope_cuda.cuh new file mode 100644 index 000000000..552737be2 --- /dev/null +++ b/src/ops/rope/nvidia/rope_cuda.cuh @@ -0,0 +1,7 @@ +#pragma once +#include "../../../utils.hpp" +#include +namespace llaisys::ops::nvidia { +void rope(const std::byte *in, const std::byte *pos_ids, std::byte *out, + llaisysDataType_t dtype, size_t seqlen, size_t nhead, size_t d, float theta); +} \ No newline at end of file diff --git a/src/ops/rope/op.cpp b/src/ops/rope/op.cpp index d60dbe64e..e666156fd 100644 --- a/src/ops/rope/op.cpp +++ b/src/ops/rope/op.cpp @@ -1,7 +1,60 @@ #include "op.hpp" +#include "cpu/rope_cpu.hpp" +#ifdef ENABLE_NVIDIA_API +#include "nvidia/rope_cuda.cuh" +#endif + +#include "../../core/llaisys_core.hpp" +#include "../../utils.hpp" namespace llaisys::ops { + void rope(tensor_t out, tensor_t in, tensor_t pos_ids, float theta) { - TO_BE_IMPLEMENTED(); + // 1. 参数检查 + CHECK_SAME_DEVICE(out, in, pos_ids); + + ASSERT(in->ndim() == 3, "rope: in must be 3D [seqlen, nhead, d]"); + ASSERT(out->ndim() == 3, "rope: out must be 3D"); + ASSERT(pos_ids->ndim() == 1, "rope: pos_ids must be 1D"); + + ASSERT(in->shape()[0] == out->shape()[0], "rope: seqlen mismatch"); + ASSERT(in->shape()[1] == out->shape()[1], "rope: nhead mismatch"); + ASSERT(in->shape()[2] == out->shape()[2], "rope: d mismatch"); + ASSERT(in->shape()[0] == pos_ids->shape()[0], "rope: seqlen must match pos_ids size"); + + ASSERT(pos_ids->dtype() == LLAISYS_DTYPE_I64, "rope: pos_ids must be int64"); + ASSERT(in->dtype() == out->dtype(), "rope: in and out dtype must match"); + + // 检查 d 为偶数(RoPE 要求 d 是偶数) + ASSERT(in->shape()[2] % 2 == 0, "rope: d must be even"); + + ASSERT(in->isContiguous() && out->isContiguous() && pos_ids->isContiguous(), + "rope: all tensors must be contiguous"); + + llaisysDataType_t dtype = in->dtype(); + ASSERT(dtype == LLAISYS_DTYPE_F32 || dtype == LLAISYS_DTYPE_F16 || dtype == LLAISYS_DTYPE_BF16, + "rope: dtype must be F32, F16, or BF16"); + + // 2. 设备分发 + if (in->deviceType() == LLAISYS_DEVICE_CPU) { + return cpu::rope( + in->data(), + pos_ids->data(), + out->data(), + dtype, + in->shape()[0], // seqlen + in->shape()[1], // nhead + in->shape()[2], // d + theta); + } + +#ifdef ENABLE_NVIDIA_API + if (in->deviceType() == LLAISYS_DEVICE_NVIDIA) { + return nvidia::rope(in->data(), pos_ids->data(), out->data(), dtype, + in->shape()[0], in->shape()[1], in->shape()[2], theta); + } +#endif + EXCEPTION_UNSUPPORTED_DEVICE; } -} // namespace llaisys::ops + +} // namespace llaisys::ops \ No newline at end of file diff --git a/src/ops/self_attention/cpu/self_attention_cpu.hpp b/src/ops/self_attention/cpu/self_attention_cpu.hpp new file mode 100644 index 000000000..906b780e8 --- /dev/null +++ b/src/ops/self_attention/cpu/self_attention_cpu.hpp @@ -0,0 +1,135 @@ +#pragma once + +#include "../../../utils.hpp" +#include +#include +#include + +namespace llaisys::ops::cpu { + +// 辅助函数:对长度为 dim 的一维数组执行 softmax(原地修改) +template +void softmax_inplace(T *row, size_t dim) { + // 找最大值(数值稳定)- start from -inf to handle masked entries + float max_val = -std::numeric_limits::infinity(); + for (size_t i = 0; i < dim; ++i) { + float val = utils::cast(row[i]); + if (val > max_val) max_val = val; + } + + // 计算 exp 和总和 + float sum = 0.0f; + std::vector exp_vals(dim); + for (size_t i = 0; i < dim; ++i) { + float val = utils::cast(row[i]) - max_val; + float expv = std::exp(val); + exp_vals[i] = expv; + sum += expv; + } + + // 归一化并写回(scores are float type) + float inv_sum = 1.0f / sum; + for (size_t i = 0; i < dim; ++i) { + row[i] = exp_vals[i] * inv_sum; // scores array is float + } +} + +template +void self_attention_impl(const T *q_data, + const T *k_data, + const T *v_data, + T *out_data, + size_t qlen, + size_t kvlen, + size_t nhead, + size_t nkvhead, + size_t head_dim, + float scale) { + // 分配分数矩阵:qlen × kvlen + std::vector scores(qlen * kvlen); + + // 遍历每个查询头 + for (size_t h = 0; h < nhead; ++h) { + // 当前 Q 头对应的 KV 头索引(因为 nhead 是 nkvhead 的倍数,这里取模映射) + size_t kvh = h / (nhead / nkvhead); + + // 计算注意力分数:scores[i][j] = (Q[i,h] · K[j,kvh]) * scale + for (size_t i = 0; i < qlen; ++i) { + for (size_t j = 0; j < kvlen; ++j) { + float sum = 0.0f; + for (size_t d = 0; d < head_dim; ++d) { + float qv = utils::cast(q_data[(i * nhead + h) * head_dim + d]); + float kv = utils::cast(k_data[(j * nkvhead + kvh) * head_dim + d]); + sum += qv * kv; + } + scores[i * kvlen + j] = sum * scale; + } + } + + // 应用因果掩码(匹配 PyTorch 参考) + int diagonal = static_cast(kvlen) - static_cast(qlen); + for (size_t i = 0; i < qlen; ++i) { + for (size_t j = 0; j < kvlen; ++j) { + // 无效位置设为 -inf + if (static_cast(j) > static_cast(i) + diagonal) { + scores[i * kvlen + j] = -std::numeric_limits::infinity(); + } + } + // 对当前行做 softmax(原地修改) + softmax_inplace(&scores[i * kvlen], kvlen); + } + + // 计算输出:out[i,h,d] = sum_j scores[i,j] * v[j,kvh,d] + for (size_t i = 0; i < qlen; ++i) { + for (size_t d = 0; d < head_dim; ++d) { + float sum = 0.0f; + for (size_t j = 0; j < kvlen; ++j) { + float weight = scores[i * kvlen + j]; + float vv = utils::cast(v_data[(j * nkvhead + kvh) * head_dim + d]); + sum += weight * vv; + } + out_data[(i * nhead + h) * head_dim + d] = utils::cast(sum); + } + } + } +} + +inline void self_attention(const std::byte *q_data, + const std::byte *k_data, + const std::byte *v_data, + std::byte *out_data, + llaisysDataType_t dtype, + size_t qlen, + size_t kvlen, + size_t nhead, + size_t nkvhead, + size_t head_dim, + float scale) { + switch (dtype) { + case LLAISYS_DTYPE_F32: + return self_attention_impl( + reinterpret_cast(q_data), + reinterpret_cast(k_data), + reinterpret_cast(v_data), + reinterpret_cast(out_data), + qlen, kvlen, nhead, nkvhead, head_dim, scale); + case LLAISYS_DTYPE_F16: + return self_attention_impl( + reinterpret_cast(q_data), + reinterpret_cast(k_data), + reinterpret_cast(v_data), + reinterpret_cast(out_data), + qlen, kvlen, nhead, nkvhead, head_dim, scale); + case LLAISYS_DTYPE_BF16: + return self_attention_impl( + reinterpret_cast(q_data), + reinterpret_cast(k_data), + reinterpret_cast(v_data), + reinterpret_cast(out_data), + qlen, kvlen, nhead, nkvhead, head_dim, scale); + default: + EXCEPTION_UNSUPPORTED_DATATYPE(dtype); + } +} + +} // namespace llaisys::ops::cpu \ No newline at end of file diff --git a/src/ops/self_attention/nvidia/self_attention_cuda.cu b/src/ops/self_attention/nvidia/self_attention_cuda.cu new file mode 100644 index 000000000..d94c6450d --- /dev/null +++ b/src/ops/self_attention/nvidia/self_attention_cuda.cu @@ -0,0 +1,84 @@ +#include "self_attention_cuda.cuh" +#include +#include +#include + +namespace llaisys::ops::nvidia { + +__global__ void softmax_f32(float *scores, int dim, int stride) { + int i = blockIdx.x; + float *row = scores + i * stride; + float maxv = -INFINITY; + for (int j = 0; j < dim; ++j) { float v = row[j]; if (v > maxv) maxv = v; } + float sum = 0.0f; + for (int j = 0; j < dim; ++j) { row[j] = expf(row[j] - maxv); sum += row[j]; } + float inv = 1.0f / sum; + for (int j = 0; j < dim; ++j) row[j] *= inv; +} + +// Compute attention: attn = softmax(Q * K^T * scale) * V +// Q [qlen, nhead, head_dim] K/V [kvlen, nkvhead, head_dim] +__global__ void self_attention_f32_kernel( + const float *q, const float *k, const float *v, float *out, + int qlen, int kvlen, int nhead, int nkvhead, int head_dim, float scale) { + + int tid = blockIdx.x * blockDim.x + threadIdx.x; + int head_groups = nhead / nkvhead; + if (tid >= nhead) return; + + int h = tid; + int kvh = h / head_groups; + + // Allocate shared memory for scores (max 256 tokens) + extern __shared__ float s_scores[]; + float *scores = s_scores; + + for (int qi = 0; qi < qlen; ++qi) { + // Compute QK^T + for (int kj = 0; kj < kvlen; ++kj) { + float dot = 0.0f; + int q_off = (qi * nhead + h) * head_dim; + int k_off = (kj * nkvhead + kvh) * head_dim; + for (int d = 0; d < head_dim; ++d) + dot += q[q_off + d] * k[k_off + d]; + scores[kj] = dot * scale; + } + + // Causal mask + int diag = kvlen - qlen; + for (int kj = 0; kj < kvlen; ++kj) + if (kj > qi + diag) scores[kj] = -INFINITY; + + // Softmax + float maxv = scores[0]; + for (int j = 1; j < kvlen; ++j) if (scores[j] > maxv) maxv = scores[j]; + float sum = 0.0f; + for (int j = 0; j < kvlen; ++j) { scores[j] = expf(scores[j] - maxv); sum += scores[j]; } + float inv = 1.0f / sum; + for (int j = 0; j < kvlen; ++j) scores[j] *= inv; + + // Weighted sum + for (int d = 0; d < head_dim; ++d) { + float wsum = 0.0f; + int v_off_base = kvh * head_dim; + for (int kj = 0; kj < kvlen; ++kj) + wsum += scores[kj] * v[(kj * nkvhead + kvh) * head_dim + d]; + out[(qi * nhead + h) * head_dim + d] = wsum; + } + } +} + +void self_attention(const std::byte *q, const std::byte *k, const std::byte *v, + std::byte *out, llaisysDataType_t dtype, + size_t qlen, size_t kvlen, size_t nhead, size_t nkvhead, + size_t head_dim, float scale) { + if (dtype != LLAISYS_DTYPE_F32) return; + int block = 32; + int grid = (int)((nhead + block - 1) / block); + size_t shm_size = kvlen * sizeof(float); + self_attention_f32_kernel<<>>( + (const float*)q, (const float*)k, (const float*)v, (float*)out, + (int)qlen, (int)kvlen, (int)nhead, (int)nkvhead, (int)head_dim, scale); +} + +} // namespace llaisys::ops::nvidia \ No newline at end of file diff --git a/src/ops/self_attention/nvidia/self_attention_cuda.cuh b/src/ops/self_attention/nvidia/self_attention_cuda.cuh new file mode 100644 index 000000000..be1e00e14 --- /dev/null +++ b/src/ops/self_attention/nvidia/self_attention_cuda.cuh @@ -0,0 +1,10 @@ +#pragma once +#include "../../../utils.hpp" +#include + +namespace llaisys::ops::nvidia { +void self_attention(const std::byte *q, const std::byte *k, const std::byte *v, + std::byte *out, llaisysDataType_t dtype, + size_t qlen, size_t kvlen, size_t nhead, size_t nkvhead, + size_t head_dim, float scale); +} \ No newline at end of file diff --git a/src/ops/self_attention/op.cpp b/src/ops/self_attention/op.cpp index 43d620142..d629a3129 100644 --- a/src/ops/self_attention/op.cpp +++ b/src/ops/self_attention/op.cpp @@ -1,7 +1,69 @@ #include "op.hpp" +#include "cpu/self_attention_cpu.hpp" +#ifdef ENABLE_NVIDIA_API +#include "nvidia/self_attention_cuda.cuh" +#endif + +#include "../../core/llaisys_core.hpp" +#include "../../utils.hpp" namespace llaisys::ops { + void self_attention(tensor_t attn_val, tensor_t q, tensor_t k, tensor_t v, float scale) { - TO_BE_IMPLEMENTED(); + // 1. 参数检查 + CHECK_SAME_DEVICE(attn_val, q, k, v); + + ASSERT(q->ndim() == 3, "self_attention: q must be 3D"); + ASSERT(k->ndim() == 3, "self_attention: k must be 3D"); + ASSERT(v->ndim() == 3, "self_attention: v must be 3D"); + ASSERT(attn_val->ndim() == 3, "self_attention: attn_val must be 3D"); + + // 检查基本维度一致性 + ASSERT(q->shape()[1] == attn_val->shape()[1], "self_attention: q and attn_val nhead must match"); + ASSERT(q->shape()[2] == k->shape()[2] && k->shape()[2] == v->shape()[2], "self_attention: head_dim must match"); + ASSERT(attn_val->shape()[2] == q->shape()[2], "self_attention: attn_val head_dim must match q"); + + // 允许 nhead 是 nkvhead 的倍数 + ASSERT(q->shape()[1] % k->shape()[1] == 0, "self_attention: nhead must be multiple of nkvhead"); + ASSERT(k->shape()[1] == v->shape()[1], "self_attention: k and v nkvhead must match"); + + // dtype 一致性检查 + ASSERT(q->dtype() == k->dtype() && k->dtype() == v->dtype(), "self_attention: q, k, v dtype must match"); + ASSERT(q->dtype() == attn_val->dtype(), "self_attention: attn_val dtype must match"); + + // 连续性检查 + ASSERT(q->isContiguous() && k->isContiguous() && v->isContiguous() && attn_val->isContiguous(), + "self_attention: all tensors must be contiguous"); + + llaisysDataType_t dtype = q->dtype(); + ASSERT(dtype == LLAISYS_DTYPE_F32 || dtype == LLAISYS_DTYPE_F16 || dtype == LLAISYS_DTYPE_BF16, + "self_attention: dtype must be F32, F16, or BF16"); + + // 2. 设备分发 + if (q->deviceType() == LLAISYS_DEVICE_CPU) { + return cpu::self_attention( + q->data(), + k->data(), + v->data(), + attn_val->data(), + dtype, + q->shape()[0], // qlen + k->shape()[0], // kvlen + q->shape()[1], // nhead + k->shape()[1], // nkvhead + q->shape()[2], // head_dim + scale); + } + +#ifdef ENABLE_NVIDIA_API + if (q->deviceType() == LLAISYS_DEVICE_NVIDIA) { + return nvidia::self_attention( + q->data(), k->data(), v->data(), attn_val->data(), dtype, + q->shape()[0], k->shape()[0], q->shape()[1], k->shape()[1], + q->shape()[2], scale); + } +#endif + EXCEPTION_UNSUPPORTED_DEVICE; } -} // namespace llaisys::ops + +} // namespace llaisys::ops \ No newline at end of file diff --git a/src/ops/swiglu/cpu/swiglu_cpu.hpp b/src/ops/swiglu/cpu/swiglu_cpu.hpp new file mode 100644 index 000000000..6b6acfeb4 --- /dev/null +++ b/src/ops/swiglu/cpu/swiglu_cpu.hpp @@ -0,0 +1,50 @@ +#pragma once +#pragma once + +#include "../../../utils.hpp" +#include + +namespace llaisys::ops::cpu { + +template +void swiglu_impl(const std::byte *gate_data, + const std::byte *up_data, + std::byte *out_data, + size_t numel) +{ + const T *gate = reinterpret_cast(gate_data); + const T *up = reinterpret_cast(up_data); + T *out = reinterpret_cast(out_data); + + for (size_t i = 0; i < numel; ++i) { + float g = utils::cast(gate[i]); + float u = utils::cast(up[i]); + + // SwiGLU 公式: out = up * sigmoid(gate) * gate + // 等价于: out = up * SiLU(gate) where SiLU(x) = x * sigmoid(x) + // 标准实现: + float sigmoid_g = 1.0f / (1.0f + std::exp(-g)); + float result = u * g * sigmoid_g; + + out[i] = utils::cast(result); + } +} + +inline void swiglu(const std::byte *gate_data, + const std::byte *up_data, + std::byte *out_data, + llaisysDataType_t dtype, + size_t numel) { + switch (dtype) { + case LLAISYS_DTYPE_F32: + return swiglu_impl(gate_data, up_data, out_data, numel); + case LLAISYS_DTYPE_F16: + return swiglu_impl(gate_data, up_data, out_data, numel); + case LLAISYS_DTYPE_BF16: + return swiglu_impl(gate_data, up_data, out_data, numel); + default: + EXCEPTION_UNSUPPORTED_DATATYPE(dtype); + } +} + +} // namespace llaisys::ops::cpu \ No newline at end of file diff --git a/src/ops/swiglu/nvidia/swiglu_cuda.cu b/src/ops/swiglu/nvidia/swiglu_cuda.cu new file mode 100644 index 000000000..7b11d4c6f --- /dev/null +++ b/src/ops/swiglu/nvidia/swiglu_cuda.cu @@ -0,0 +1,17 @@ +#include "swiglu_cuda.cuh" +#include +namespace llaisys::ops::nvidia { +__global__ void swiglu_f32_kernel(const float *gate, const float *up, float *out, size_t N) { + size_t i = blockIdx.x * blockDim.x + threadIdx.x; + if (i < N) { + float g = gate[i], sig = 1.0f / (1.0f + expf(-g)); + out[i] = up[i] * g * sig; + } +} +void swiglu(const std::byte *gate, const std::byte *up, std::byte *out, llaisysDataType_t dtype, size_t N) { + if (dtype == LLAISYS_DTYPE_F32) { + swiglu_f32_kernel<<<(int)((N + 255) / 256), 256>>>((const float*)gate, (const float*)up, (float*)out, (int)N); + cudaDeviceSynchronize(); + } +} +} \ No newline at end of file diff --git a/src/ops/swiglu/nvidia/swiglu_cuda.cuh b/src/ops/swiglu/nvidia/swiglu_cuda.cuh new file mode 100644 index 000000000..e0c28d102 --- /dev/null +++ b/src/ops/swiglu/nvidia/swiglu_cuda.cuh @@ -0,0 +1,6 @@ +#pragma once +#include "../../../utils.hpp" +#include +namespace llaisys::ops::nvidia { +void swiglu(const std::byte *gate, const std::byte *up, std::byte *out, llaisysDataType_t dtype, size_t N); +} \ No newline at end of file diff --git a/src/ops/swiglu/op.cpp b/src/ops/swiglu/op.cpp index 47edbcc97..06e1b1125 100644 --- a/src/ops/swiglu/op.cpp +++ b/src/ops/swiglu/op.cpp @@ -1,7 +1,43 @@ #include "op.hpp" +#include "cpu/swiglu_cpu.hpp" +#ifdef ENABLE_NVIDIA_API +#include "nvidia/swiglu_cuda.cuh" +#endif + +#include "../../core/llaisys_core.hpp" +#include "../../utils.hpp" namespace llaisys::ops { + void swiglu(tensor_t out, tensor_t gate, tensor_t up) { - TO_BE_IMPLEMENTED(); + // 1. 参数检查 + CHECK_SAME_DEVICE(out, gate, up); + CHECK_SAME_SHAPE(out->shape(), gate->shape(), up->shape()); + CHECK_SAME_DTYPE(out->dtype(), gate->dtype(), up->dtype()); + + ASSERT(out->isContiguous() && gate->isContiguous() && up->isContiguous(), + "swiglu: all tensors must be contiguous."); + + llaisysDataType_t dtype = out->dtype(); + ASSERT(dtype == LLAISYS_DTYPE_F32 || dtype == LLAISYS_DTYPE_F16 || dtype == LLAISYS_DTYPE_BF16, + "swiglu: dtype must be F32, F16, or BF16."); + + // 2. 设备分发 + if (out->deviceType() == LLAISYS_DEVICE_CPU) { + return cpu::swiglu( + gate->data(), + up->data(), + out->data(), + dtype, + out->numel()); + } + +#ifdef ENABLE_NVIDIA_API + if (out->deviceType() == LLAISYS_DEVICE_NVIDIA) { + return nvidia::swiglu(gate->data(), up->data(), out->data(), dtype, out->numel()); + } +#endif + EXCEPTION_UNSUPPORTED_DEVICE; } -} // namespace llaisys::ops + +} // namespace llaisys::ops \ No newline at end of file diff --git a/src/tensor/tensor.cpp b/src/tensor/tensor.cpp index 2f594bb65..5a342dc93 100644 --- a/src/tensor/tensor.cpp +++ b/src/tensor/tensor.cpp @@ -1,11 +1,8 @@ #include "tensor.hpp" - #include "../utils.hpp" - #include #include #include - namespace llaisys { Tensor::Tensor(TensorMeta meta, core::storage_t storage, size_t offset) @@ -164,42 +161,263 @@ void Tensor::debug() const { } bool Tensor::isContiguous() const { - TO_BE_IMPLEMENTED(); + size_t ndim = _meta.shape.size(); + + // 2. 如果维度是 0(标量)或者 1(一维数组),内存一定是连续的 + // 因为一维数组在内存里就是一条线,不存在“跳跃”的可能。 + if (ndim <= 1) { + return true; + } + + // 3. 从最后一维开始往前推算“理论上的连续步长” + // 例如 shape = [2, 3, 4] 时,理论连续步长必须是 [12, 4, 1] + ptrdiff_t expected_stride = 1; // 最后一维的步长永远应该是 1 + + // 注意:这里要用 int 类型的 i,因为循环结束后 i 会变成 -1,避免死循环 + for (int i = static_cast(ndim) - 1; i >= 0; --i) { + // 检查:当前维度的实际步长,是否等于理论推算值? + if (_meta.strides[i] != expected_stride) { + return false; // 只要有一维对不上,就是不连续 + } + // 推算下一层(更外层)的步长:当前层步长 × 当前层的大小 + expected_stride *= static_cast(_meta.shape[i]); + } + + // 所有维度都检查通过,说明内存是连续的 return true; } tensor_t Tensor::permute(const std::vector &order) const { - TO_BE_IMPLEMENTED(); - return std::shared_ptr(new Tensor(_meta, _storage)); + // 1. 检查 order 长度是否匹配 + size_t ndim = this->ndim(); + if (order.size() != ndim) { + throw std::invalid_argument("permute: order size must match tensor ndim"); + } + + // 2. 检查 order 是否是一个合法的排列(包含 0 ~ ndim-1 各一次) + std::vector seen(ndim, false); + for (size_t i = 0; i < ndim; ++i) { + if (order[i] >= ndim) { + throw std::out_of_range("permute: order index out of range"); + } + if (seen[order[i]]) { + throw std::invalid_argument("permute: duplicate index in order"); + } + seen[order[i]] = true; + } + + // 3. 准备新的元数据容器 + std::vector new_shape(ndim); + std::vector new_strides(ndim); + + // 4. 按照 order 重新排列 shape 和 strides + for (size_t i = 0; i < ndim; ++i) { + new_shape[i] = _meta.shape[order[i]]; + new_strides[i] = _meta.strides[order[i]]; + } + + // 5. 组装新的元数据(dtype 不变) + TensorMeta new_meta{_meta.dtype, new_shape, new_strides}; + + // 6. 返回新张量:共享 storage,偏移量保持不变(因为是从头看) + return std::shared_ptr(new Tensor(new_meta, _storage, _offset)); } tensor_t Tensor::view(const std::vector &shape) const { - TO_BE_IMPLEMENTED(); - return std::shared_ptr(new Tensor(_meta, _storage)); + // 1. 检查:新形状的元素总数是否等于当前元素总数? + size_t new_numel = 1; + for (size_t s : shape) { + new_numel *= s; + } + if (new_numel != this->numel()) { + throw std::invalid_argument("view: new shape must have the same number of elements as current tensor"); + } + + // 2. 检查:当前张量是否连续?(这是 view 能正常工作的命脉) + if (!this->isContiguous()) { + throw std::runtime_error("view: tensor is not contiguous. Please call contiguous() first."); + } + + // 3. 计算新形状的连续步长(和 create 里的算法完全一样) + size_t ndim = shape.size(); + std::vector new_strides(ndim); + ptrdiff_t stride = 1; + // 从最后一维往前倒推 + for (int i = static_cast(ndim) - 1; i >= 0; --i) { + new_strides[i] = stride; + stride *= static_cast(shape[i]); + } + + // 4. 组装新的元数据 + TensorMeta new_meta{_meta.dtype, shape, new_strides}; + + // 5. 返回新张量(共享 storage,偏移量不变) + return std::shared_ptr(new Tensor(new_meta, _storage, _offset)); } tensor_t Tensor::slice(size_t dim, size_t start, size_t end) const { - TO_BE_IMPLEMENTED(); - return std::shared_ptr(new Tensor(_meta, _storage)); + // 1. 边界检查(防止你取越界) + if (dim >= this->ndim()) { + throw std::out_of_range("slice: dim out of range"); + } + if (start >= end || end > _meta.shape[dim]) { + throw std::out_of_range("slice: invalid start/end"); + } + + // 2. 复制一份当前的元数据(身份证复印件) + TensorMeta new_meta = _meta; + + // 3. 修改形状:该维度缩小了 + new_meta.shape[dim] = end - start; + + // 4. 步长(strides)保持不变!因为内存排列没变,跳一下还是跨那么远。 + // new_meta.strides 不需要动,直接继承原值。 + + // 5. 计算新的字节偏移量(最关键!) + // _offset 是字节数,strides[dim] 是元素数,所以要乘以单个元素大小。 + size_t byte_offset = _offset + start * _meta.strides[dim] * this->elementSize(); + + // 6. 构造新张量:使用新的元数据、共享原来的存储、加上新的偏移量 + return std::shared_ptr(new Tensor(new_meta, _storage, byte_offset)); } void Tensor::load(const void *src_) { - TO_BE_IMPLEMENTED(); + // 1. 空指针检查 + if (src_ == nullptr) { + throw std::invalid_argument("load: source pointer is null"); + } + + // 2. 计算总字节数 + size_t total_bytes = this->numel() * this->elementSize(); + + // 3. 判断目标设备 + if (this->deviceType() == LLAISYS_DEVICE_CPU) { + // 目标在 CPU:直接用 memcpy + std::memcpy(this->data(), src_, total_bytes); + } + else { + // 目标在 GPU:必须调用设备 API,方向是 H2D(Host to Device) + // 先确保切换到正确的设备上下文 + core::context().setDevice(this->deviceType(), this->deviceId()); + + // 调用底层拷贝函数(参考 debug 里的写法) + core::context().runtime().api()->memcpy_sync( + this->data(), // 目标(显存地址) + src_, // 源(主机内存地址) + total_bytes, + LLAISYS_MEMCPY_H2D // 方向:主机 -> 设备 + ); + } } tensor_t Tensor::contiguous() const { - TO_BE_IMPLEMENTED(); - return std::shared_ptr(new Tensor(_meta, _storage)); + // 1. 如果已经连续,直接返回自身(浅拷贝) + if (this->isContiguous()) { + return std::shared_ptr(new Tensor(_meta, _storage, _offset)); + } + + // 2. 创建一个同形状、同类型、同设备的新张量(自动分配连续内存) + // 注意:这里用 create 会重新算一遍步长,新张量肯定是连续的。 + tensor_t new_tensor = Tensor::create( + this->shape(), + this->dtype(), + this->deviceType(), + this->deviceId()); + + // 3. 准备拷贝数据 + size_t ndim = this->ndim(); + const auto &shape = this->shape(); + const auto &strides = this->strides(); + const std::byte *src_base = this->data(); // 原数据起始指针(已含 _offset) + std::byte *dst_base = new_tensor->data(); // 新数据起始指针(连续) + size_t elem_size = this->elementSize(); + size_t total = this->numel(); + + // 4. 创建“多维索引”迭代器(模拟 for 循环遍历所有逻辑坐标) + std::vector indices(ndim, 0); // 初始化为 [0,0,...] + + for (size_t i = 0; i < total; ++i) { + // 4.1 根据当前索引和步长,计算在原内存中的偏移(元素个数) + size_t src_elem_offset = 0; + for (size_t d = 0; d < ndim; ++d) { + src_elem_offset += indices[d] * strides[d]; + } + + // 4.2 拷贝一个元素(从跳跃的原地址 -> 连续的新地址) + std::memcpy( + dst_base + i * elem_size, // 新地址(连续递增) + src_base + src_elem_offset * elem_size, // 旧地址(跳着读) + elem_size); + + // 4.3 索引进位(类似数字时钟加1:从最后一维往前加) + for (int d = static_cast(ndim) - 1; d >= 0; --d) { + indices[d]++; + if (indices[d] < shape[d]) { + break; // 当前维没溢出,跳出循环 + } + // 如果溢出(比如到达 shape 边界),归零并往更高维进位 + indices[d] = 0; + } + } + + // 5. 返回这个全新的连续张量 + return new_tensor; } tensor_t Tensor::reshape(const std::vector &shape) const { - TO_BE_IMPLEMENTED(); - return std::shared_ptr(new Tensor(_meta, _storage)); + // 1. 检查元素总数是否匹配 + size_t new_numel = 1; + for (size_t s : shape) { + new_numel *= s; + } + if (new_numel != this->numel()) { + throw std::invalid_argument("reshape: new shape must have the same number of elements"); + } + + // 2. 核心逻辑:先确保连续,再调用 view + // 如果已经是连续的,contiguous() 会直接浅拷贝返回(不复制数据) + return this->contiguous()->view(shape); } tensor_t Tensor::to(llaisysDeviceType_t device_type, int device) const { - TO_BE_IMPLEMENTED(); - return std::shared_ptr(new Tensor(_meta, _storage)); -} + // 1. 如果目标设备和当前设备完全一致,直接返回自身(浅拷贝) + if (this->deviceType() == device_type && this->deviceId() == device) { + return std::shared_ptr(new Tensor(_meta, _storage, _offset)); + } + + // 2. 在目标设备上创建一个全新的空张量(形状、类型完全相同) + tensor_t new_tensor = Tensor::create( + this->shape(), + this->dtype(), + device_type, + device); + size_t total_bytes = this->numel() * this->elementSize(); + + // 3. 判断源设备和目标设备,决定拷贝方向 + if (this->deviceType() == LLAISYS_DEVICE_CPU && device_type == LLAISYS_DEVICE_CPU) { + // CPU -> CPU:直接用 memcpy + std::memcpy(new_tensor->data(), this->data(), total_bytes); + return new_tensor; + } else if (this->deviceType() == LLAISYS_DEVICE_CPU && device_type == LLAISYS_DEVICE_NVIDIA) { + // CPU -> NVIDIA GPU + core::context().setDevice(device_type, device); + core::context().runtime().api()->memcpy_sync( + new_tensor->data(), this->data(), total_bytes, LLAISYS_MEMCPY_H2D); + } else if (this->deviceType() == LLAISYS_DEVICE_NVIDIA && device_type == LLAISYS_DEVICE_CPU) { + // NVIDIA GPU -> CPU + core::context().setDevice(device_type, device); // 切换到目标设备(CPU) + core::context().runtime().api()->memcpy_sync( + new_tensor->data(), this->data(), total_bytes, LLAISYS_MEMCPY_D2H); + } else if (this->deviceType() == LLAISYS_DEVICE_NVIDIA && device_type == LLAISYS_DEVICE_NVIDIA) { + // NVIDIA GPU -> NVIDIA GPU(可能同卡或不同卡) + core::context().setDevice(device_type, device); + core::context().runtime().api()->memcpy_sync( + new_tensor->data(), this->data(), total_bytes, LLAISYS_MEMCPY_D2D); + } else { + EXCEPTION_UNSUPPORTED_DEVICE; + } + + return new_tensor; +} } // namespace llaisys diff --git a/src/tensor/tensor.hpp b/src/tensor/tensor.hpp index 35e340922..b8dae0b43 100644 --- a/src/tensor/tensor.hpp +++ b/src/tensor/tensor.hpp @@ -1,60 +1,230 @@ +// ============================================================ +// 第 1 行:#pragma once +// ============================================================ #pragma once +// 这是一个“头文件守卫”,作用是防止这个头文件被重复包含(比如被多个 .cpp 文件 include 导致编译报错)。 +// 相当于老式的 #ifndef ... #define ... #endif,写法更简洁。 + +// ============================================================ +// 第 2 行:#include "../core/llaisys_core.hpp" +// ============================================================ #include "../core/llaisys_core.hpp" +// 引入上一层 core 目录下的核心头文件。 +// 这里面大概率定义了基础类型,比如 llaisysDataType_t(数据类型枚举)、 +// llaisysDeviceType_t(设备类型枚举)、core::storage_t(存储类)等。 +// ============================================================ +// 第 3 行:#include +// ============================================================ #include +// 引入 C++ 标准库的 vector(动态数组)。 +// 后面用来存储张量的形状(shape)和步长(strides)。 + +// ============================================================ +// 第 4 行:namespace llaisys { +// ============================================================ namespace llaisys { +// 定义一个命名空间叫 llaisys(可能是 "LLM AI System" 的缩写)。 +// 作用是把所有相关的类、函数包在里面,防止和别的库(比如标准库)里的名字冲突。 + +// ============================================================ +// 第 5 - 6 行:class Tensor; 和 using 别名 +// ============================================================ class Tensor; +// 这行是“前向声明”(Forward Declaration)。 +// 意思就是告诉编译器:“Tensor 是一个类,我待会儿再详细定义它”。 +// 这里主要是为了第 7 行的 using 能提前用这个名字。 + using tensor_t = std::shared_ptr; +// 这里定义了一个“类型别名”。 +// std::shared_ptr 是 C++ 的“智能指针”,它会自动帮我们管理内存(不用手动 delete)。 +// 这行意思是:以后我们写 tensor_t,就等价于写 std::shared_ptr。 +// 以后创建张量都返回这个别名,非常安全。 +// ============================================================ +// 第 8 - 12 行:struct TensorMeta(张量元数据) +// ============================================================ struct TensorMeta { + // struct 和 class 很像,区别是 struct 里的成员默认是公开的(public)。 + // 这个结构体用来存放张量的“形状信息”,不存具体数据。 + llaisysDataType_t dtype; + // 数据类型,比如是 float32、int8 还是 bool。 + std::vector shape; + // 形状,比如 [2, 3, 4] 表示 2x3x4 的三维张量。 + // size_t 是无符号整数,用来表示大小。 + std::vector strides; + // 步长(Strides),表示在某个维度上,移动到下一个元素需要在内存中跳过多少个元素。 + // ptrdiff_t 是有符号整数,可以处理负数(虽然这里通常是正数)。 }; +// ============================================================ +// 第 13 - 17 行:class Tensor 的主体开始 +// ============================================================ class Tensor { + // 这里开始定义真正的张量类。 + + // ============================================================ + // 第 14 - 17 行:private(私有成员) + // ============================================================ private: + // private 表示下面的内容只能被这个类自己的函数访问,外部无法直接修改。 + TensorMeta _meta; + // 上面定义的那个元数据结构体,存着 dtype、shape、strides。 + core::storage_t _storage; + // 实际的“数据存储”对象。它是一个智能指针(看名字里的 _t 后缀)。 + // 它管理着真正存放字节数据的内存(可能在 CPU 内存条里,也可能在 GPU 显存里)。 + size_t _offset; + // 偏移量(按字节数算)。因为多个张量可以共享同一块存储(比如切片操作), + // 这个偏移量表示该张量的数据是从 _storage 的哪个位置开始的。 + // ============================================================ + // 第 18 行:私有构造函数(重点语法) + // ============================================================ Tensor(TensorMeta meta, core::storage_t storage, size_t offset = 0); + // 这是构造函数,注意它被放在了 private 里面! + // 这意味着:外部代码不能直接写 new Tensor(...) 或者 Tensor t(...) 来创建对象。 + // 为什么要这样做?因为创建张量很复杂(要判断 CPU 还是 GPU),所以强制用户使用下面的静态方法 create 来创建。 + // 参数最后的 "= 0" 表示 offset 有默认值 0,如果不传就默认为 0。 + // ============================================================ + // 第 19 行:public(公开成员) + // ============================================================ public: + // public 表示下面的内容谁都可以调用。 + + // ============================================================ + // 第 20 - 24 行:静态工厂方法 create + // ============================================================ static tensor_t create( const std::vector &shape, + // 传入形状,这里用了 const &(常量引用),避免拷贝整个 vector,效率更高。 + llaisysDataType_t dtype, + // 数据类型。 + llaisysDeviceType_t device_type = LLAISYS_DEVICE_CPU, + // 设备类型,默认是 CPU。 + int device = 0); + // 设备编号(比如有多张显卡时指定用哪张),默认是 0。 + // static 表示这个函数属于类本身,不需要对象就能调用。 + // 这就是唯一的“工厂方法”,内部会 new Tensor(...) 并返回智能指针。 + + // ============================================================ + // 第 25 行:析构函数 + // ============================================================ ~Tensor() = default; - // Info + // 这是析构函数(对象销毁时调用)。 + // "= default" 是 C++11 的新语法,意思是“请编译器自动生成默认的析构函数实现”。 + // 因为用了智能指针 _storage,它自己会释放内存,所以我们不需要手动写代码。 + + // ============================================================ + // 第 26 - 35 行:各种信息获取函数(Getter) + // ============================================================ + // -------------------- 数据指针 -------------------- std::byte *data(); + // 返回指向实际数据的指针(非 const 版本),允许修改数据。 + // std::byte 是 C++17 的字节类型,用来表示原始内存。 + const std::byte *data() const; + // 这是 data() 的“常量重载版本”。 + // 重点看后面的 "const":这个 const 表示该函数不会修改成员变量。 + // 如果外面有一个 const Tensor,就只能调用后面带 const 的 data(),拿到只读指针。 + + // -------------------- 维度信息 -------------------- size_t ndim() const; + // 返回维度数量(比如 shape 是 [2,3,4],则返回 3)。 + // 后面的 const 表示只是读取,不修改对象。 + const std::vector &shape() const; + // 返回形状的只读引用(const &)。这样不会拷贝整个 vector,效率高。 + // 外部只能看,不能改。 + const std::vector &strides() const; + // 返回步长的只读引用。 + + // -------------------- 类型和设备信息 -------------------- llaisysDataType_t dtype() const; + // 返回数据类型。 + llaisysDeviceType_t deviceType() const; + // 返回设备类型(CPU 还是 GPU)。 + int deviceId() const; + // 返回设备编号。 + + // -------------------- 数量信息 -------------------- size_t numel() const; + // 返回元素总数(把所有 shape 乘起来)。 + size_t elementSize() const; + // 返回单个元素占用的字节数(比如 float 占 4 字节)。 + // ============================================================ + // 第 37 - 38 行:信息打印 + // ============================================================ std::string info() const; + // 返回张量的描述信息(如 shape 和 dtype),以字符串形式。 + void debug() const; + // 在控制台打印张量的具体数值内容(调试用)。 + // ============================================================ + // 第 40 行:连续性判断 + // ============================================================ bool isContiguous() const; - // Meta Transform + // 判断内存是否连续(即元素在内存里的排列顺序是否和 shape 的行优先顺序一致)。 + // 很多操作(比如 view)要求张量是连续的。 + + // ============================================================ + // 第 42 - 45 行:元数据变换(轻量级,通常共享存储) + // ============================================================ + // Meta Transform(只改变元数据,不拷贝数据) + tensor_t permute(const std::vector &order) const; + // 重排维度(比如转置),返回新张量,但共享底层数据。 + tensor_t slice(size_t dim, size_t start, size_t end) const; + // 在指定维度上切片(取一部分),返回新张量,共享底层数据。 + tensor_t view(const std::vector &shape) const; + // 改变视图形状(前提是内存连续),数据不变。 + // ============================================================ + // 第 47 - 48 行:数据加载 + // ============================================================ // Load data from host memory void load(const void *src); + // 从主机(CPU)内存加载数据到当前张量。 + // const void * 表示指向任意类型的只读指针,可以接收任何类型的数组。 + + // ============================================================ + // 第 50 - 54 行:进阶操作(较难,会涉及数据拷贝) + // ============================================================ + // Challenging features(有挑战性的功能) - // Challenging features tensor_t contiguous() const; + // 如果当前不连续,就创建一个新的连续存储,把数据按行优先顺序拷贝进去; + // 如果已经连续,就返回自身。 + tensor_t reshape(const std::vector &shape) const; + // 相当于先确保连续,再调用 view。 + tensor_t to(llaisysDeviceType_t device_type, int device = -1) const; + // 把张量迁移到另一个设备(比如从 CPU 搬到 GPU,或从 GPU 0 搬到 GPU 1)。 + // device = -1 表示使用该设备类型下的默认设备。 + + // ============================================================ + // 第 55 行:命名空间结束 + // ============================================================ }; +// 类定义结束。 } // namespace llaisys +// 命名空间结束。 \ No newline at end of file diff --git a/test/ops/rope.py b/test/ops/rope.py index fe59dd11c..42d2688ab 100644 --- a/test/ops/rope.py +++ b/test/ops/rope.py @@ -59,6 +59,7 @@ def test_op_rope( ) + if __name__ == "__main__": import argparse @@ -71,7 +72,7 @@ def test_op_rope( ((512, 4, 4096), (512, 1024))] testDtypePrec = [ # type, atol, rtol - ("f32", 1e-4, 1e-4), + ("f32", 1e-3, 1e-3), ("f16", 1e-3, 1e-3), ("bf16", 1e-2, 1e-2), ] diff --git a/test/test_infer.py b/test/test_infer.py index 59d06b874..39196db7f 100644 --- a/test/test_infer.py +++ b/test/test_infer.py @@ -50,8 +50,11 @@ def hf_infer( top_p=top_p, temperature=temperature, ) - result = tokenizer.decode(outputs[0], skip_special_tokens=True) - return outputs[0].tolist(), result + full_tokens = outputs[0].tolist() + input_len = inputs[0].shape[0] + new_tokens = full_tokens[input_len:] # 只取新生成的 token + result = tokenizer.decode(full_tokens, skip_special_tokens=True) + return new_tokens, result def load_llaisys_model(model_path, device_name): @@ -146,4 +149,4 @@ def llaisys_infer( if args.test: assert llaisys_tokens == tokens - print("\033[92mTest passed!\033[0m\n") + print("\033[92mTest passed!\033[0m\n") \ No newline at end of file diff --git a/xmake.lua b/xmake.lua index 1f65f7a95..910d972a7 100644 --- a/xmake.lua +++ b/xmake.lua @@ -18,6 +18,16 @@ if has_config("nv-gpu") then includes("xmake/nvidia.lua") end +-- Helper function to conditionally add NVIDIA deps +function add_nvidia_deps_if_enabled(target_name) + if has_config("nv-gpu") then + target(target_name) + add_deps("llaisys-device-nvidia") + add_deps("llaisys-ops-nvidia") + target_end() + end +end + target("llaisys-utils") set_kind("static") @@ -37,6 +47,9 @@ target("llaisys-device") set_kind("static") add_deps("llaisys-utils") add_deps("llaisys-device-cpu") + if has_config("nv-gpu") then + add_deps("llaisys-device-nvidia") + end set_languages("cxx17") set_warnings("all", "error") @@ -83,6 +96,9 @@ target_end() target("llaisys-ops") set_kind("static") add_deps("llaisys-ops-cpu") + if has_config("nv-gpu") then + add_deps("llaisys-ops-nvidia") + end set_languages("cxx17") set_warnings("all", "error") @@ -105,12 +121,23 @@ target("llaisys") set_languages("cxx17") set_warnings("all", "error") + if is_plat("windows") then + set_runtimes("MD") + add_cxflags("/MD") + end add_files("src/llaisys/*.cc") + add_files("src/models/qwen2/*.cpp") + add_files("src/llaisys/models/*.cpp") + if has_config("nv-gpu") then + add_files("src/device/nvidia/*.cu") + add_files("src/ops/*/nvidia/*.cu") + add_cuflags("-arch=sm_75", "-allow-unsupported-compiler", "--compiler-options=/MD") + add_linkdirs("$(env CUDA_PATH)/lib/x64", "$(env CUDA_PATH)/lib") + add_links("cudart", "cudadevrt") + end set_installdir(".") - after_install(function (target) - -- copy shared library to python package print("Copying llaisys to python/llaisys/libllaisys/ ..") if is_plat("windows") then os.cp("bin/*.dll", "python/llaisys/libllaisys/") diff --git a/xmake/nvidia.lua b/xmake/nvidia.lua new file mode 100644 index 000000000..c20cdeaee --- /dev/null +++ b/xmake/nvidia.lua @@ -0,0 +1,26 @@ +-- CUDA files are compiled directly in the main llaisys target (shared library) +-- to ensure proper device linking. These stub targets exist only for dependency tracking. +target("llaisys-device-nvidia") + set_kind("static") + set_languages("cxx17") + set_warnings("all", "error") + if is_plat("windows") then + set_runtimes("MD") + add_cxflags("/MD") + end + add_linkdirs("$(env CUDA_PATH)/lib", "$(env CUDA_PATH)/lib/x64") + on_install(function (target) end) +target_end() + +target("llaisys-ops-nvidia") + set_kind("static") + add_deps("llaisys-tensor") + set_languages("cxx17") + set_warnings("all", "error") + if is_plat("windows") then + set_runtimes("MD") + add_cxflags("/MD") + end + add_linkdirs("$(env CUDA_PATH)/lib", "$(env CUDA_PATH)/lib/x64") + on_install(function (target) end) +target_end()