Buckets:
| extern "C" { | |
| } | |
| namespace neuroflow { | |
| namespace { | |
| constexpr size_t OMP_MIN_ITER = 1024; | |
| using omp_idx_t = long long; | |
| using omp_idx_t = size_t; | |
| class ThreadPool { | |
| public: | |
| explicit ThreadPool(size_t n = std::thread::hardware_concurrency()) { | |
| n = std::max(n, size_t(1)); | |
| for (size_t i = 0; i < n; ++i) { | |
| workers_.emplace_back([this] { | |
| while (true) { | |
| std::function<void()> task; | |
| { | |
| std::unique_lock<std::mutex> lock(mutex_); | |
| cv_.wait(lock, [this] { return stop_ || !tasks_.empty(); }); | |
| if (stop_ && tasks_.empty()) return; | |
| task = std::move(tasks_.front()); | |
| tasks_.pop(); | |
| } | |
| task(); | |
| } | |
| }); | |
| } | |
| } | |
| ~ThreadPool() { | |
| { | |
| std::lock_guard<std::mutex> lock(mutex_); | |
| stop_ = true; | |
| } | |
| cv_.notify_all(); | |
| for (auto& w : workers_) { | |
| if (w.joinable()) w.join(); | |
| } | |
| } | |
| void submit(std::function<void()> task) { | |
| { | |
| std::lock_guard<std::mutex> lock(mutex_); | |
| tasks_.push(std::move(task)); | |
| } | |
| cv_.notify_one(); | |
| } | |
| void wait_all() { | |
| while (true) { | |
| std::lock_guard<std::mutex> lock(mutex_); | |
| if (tasks_.empty()) break; | |
| } | |
| std::this_thread::sleep_for(std::chrono::milliseconds(1)); | |
| } | |
| private: | |
| std::vector<std::thread> workers_; | |
| std::queue<std::function<void()>> tasks_; | |
| std::mutex mutex_; | |
| std::condition_variable cv_; | |
| bool stop_ = false; | |
| }; | |
| } | |
| void TensorOps::gemm(const Tensor& A, const Tensor& B, Tensor& C, | |
| bool transA, bool transB, | |
| float alpha, float beta) { | |
| size_t K_A = transA ? A.shape_[0] : A.shape_[1]; | |
| size_t K_B = transB ? B.shape_[1] : B.shape_[0]; | |
| if (K_A != K_B) { | |
| throw std::runtime_error("GEMM dimension mismatch: K dimensions don't match"); | |
| } | |
| if (A.dtype_ != QuantType::FP32 || B.dtype_ != QuantType::FP32) { | |
| quantized_gemm(A, B, C); | |
| return; | |
| } | |
| size_t M = transA ? A.shape_[1] : A.shape_[0]; | |
| size_t K = transA ? A.shape_[0] : A.shape_[1]; | |
| size_t N = transB ? B.shape_[0] : B.shape_[1]; | |
| if (CudaContext::instance().is_available() && A.is_on_gpu() && B.is_on_gpu()) { | |
| C.to_gpu(); // 确保输出张量在GPU上有内存分配 | |
| const float* d_a = A.as_gpu_fp32(); | |
| const float* d_b = B.as_gpu_fp32(); | |
| float* d_c = C.as_gpu_fp32(); | |
| int lda = static_cast<int>(transA ? M : K); | |
| int ldb = static_cast<int>(transB ? K : N); | |
| int ldc = static_cast<int>(N); | |
| CudaContext::instance().sgemm_rowmajor(transA, transB, | |
| static_cast<int>(M), static_cast<int>(N), static_cast<int>(K), | |
| alpha, d_a, lda, d_b, ldb, beta, d_c, ldc); | |
| C.gpu_dirty_ = true; | |
| return; | |
| } | |
| auto a = reinterpret_cast<const float*>(A.data_.get()); | |
| auto b = reinterpret_cast<const float*>(B.data_.get()); | |
| auto c = reinterpret_cast<float*>(C.data_.get()); | |
| CBLAS_TRANSPOSE ctransA = transA ? CblasTrans : CblasNoTrans; | |
| CBLAS_TRANSPOSE ctransB = transB ? CblasTrans : CblasNoTrans; | |
| CBLAS_ORDER order = CblasRowMajor; | |
| size_t lda_cblas = transA ? M : K; | |
| size_t ldb_cblas = transB ? K : N; | |
| cblas_sgemm(order, ctransA, ctransB, M, N, K, alpha, a, lda_cblas, b, ldb_cblas, beta, c, N); | |
| gemm_avx2(a, b, c, M, K, N, transA, transB, alpha, beta); | |
| gemm_neon(a, b, c, M, K, N, transA, transB, alpha, beta); | |
| gemm_scalar(a, b, c, M, K, N, transA, transB, alpha, beta); | |
| } | |
| void TensorOps::gemm_avx2(const float* a, const float* b, float* c, | |
| size_t M, size_t K, size_t N, | |
| bool transA, bool transB, | |
| float alpha, float beta) { | |
| if (beta != 0.0f) { | |
| for (size_t i = 0; i < M * N; ++i) c[i] *= beta; | |
| } else { | |
| memset(c, 0, M * N * sizeof(float)); | |
| } | |
| const size_t BLOCK = 8; | |
| for (size_t i = 0; i < M; ++i) { | |
| for (size_t j = 0; j < N; j += BLOCK) { | |
| __m256 sum = _mm256_setzero_ps(); | |
| for (size_t k = 0; k < K; ++k) { | |
| float av = transA ? a[k * M + i] : a[i * K + k]; | |
| __m256 bv; | |
| if (transB) { | |
| float bvals[BLOCK]; | |
| for (size_t jj = 0; jj < BLOCK && j + jj < N; ++jj) { | |
| bvals[jj] = b[(j + jj) * K + k]; | |
| } | |
| bv = _mm256_loadu_ps(bvals); | |
| } else { | |
| bv = _mm256_loadu_ps(&b[k * N + j]); | |
| } | |
| sum = _mm256_fmadd_ps(_mm256_set1_ps(av), bv, sum); | |
| } | |
| sum = _mm256_mul_ps(sum, _mm256_set1_ps(alpha)); | |
| if (j + BLOCK <= N) { | |
| __m256 cv = _mm256_loadu_ps(&c[i * N + j]); | |
| cv = _mm256_add_ps(cv, sum); | |
| _mm256_storeu_ps(&c[i * N + j], cv); | |
| } else { | |
| float tmp[BLOCK]; | |
| _mm256_storeu_ps(tmp, sum); | |
| for (size_t jj = 0; j + jj < N; ++jj) { | |
| c[i * N + j + jj] += tmp[jj]; | |
| } | |
| } | |
| } | |
| } | |
| } | |
| void TensorOps::gemm_neon(const float* a, const float* b, float* c, | |
| size_t M, size_t K, size_t N, | |
| bool transA, bool transB, | |
| float alpha, float beta) { | |
| if (beta != 0.0f) { | |
| for (size_t i = 0; i < M * N; ++i) c[i] *= beta; | |
| } else { | |
| memset(c, 0, M * N * sizeof(float)); | |
| } | |
| const size_t BLOCK = 4; | |
| for (size_t i = 0; i < M; ++i) { | |
| for (size_t j = 0; j < N; j += BLOCK) { | |
| float32x4_t sum = vdupq_n_f32(0.0f); | |
| for (size_t k = 0; k < K; ++k) { | |
| float av = transA ? a[k * M + i] : a[i * K + k]; | |
| float32x4_t bv; | |
| if (transB) { | |
| float bvals[BLOCK]; | |
| for (size_t jj = 0; jj < BLOCK && j + jj < N; ++jj) { | |
| bvals[jj] = b[(j + jj) * K + k]; | |
| } | |
| bv = vld1q_f32(bvals); | |
| } else { | |
| bv = vld1q_f32(&b[k * N + j]); | |
| } | |
| sum = vmlaq_n_f32(sum, bv, av); | |
| } | |
| sum = vmulq_n_f32(sum, alpha); | |
| float32x4_t cv = vld1q_f32(&c[i * N + j]); | |
| cv = vaddq_f32(cv, sum); | |
| vst1q_f32(&c[i * N + j], cv); | |
| } | |
| } | |
| } | |
| void TensorOps::gemm_scalar(const float* a, const float* b, float* c, | |
| size_t M, size_t K, size_t N, | |
| bool transA, bool transB, | |
| float alpha, float beta) { | |
| if (beta != 0.0f) { | |
| for (size_t i = 0; i < M * N; ++i) c[i] *= beta; | |
| } else { | |
| memset(c, 0, M * N * sizeof(float)); | |
| } | |
| for (size_t i = 0; i < M; ++i) { | |
| for (size_t j = 0; j < N; ++j) { | |
| float sum = 0.0f; | |
| for (size_t k = 0; k < K; ++k) { | |
| float av = transA ? a[k * M + i] : a[i * K + k]; | |
| float bv = transB ? b[j * K + k] : b[k * N + j]; | |
| sum += av * bv; | |
| } | |
| c[i * N + j] += alpha * sum; | |
| } | |
| } | |
| } | |
| void TensorOps::quantized_gemm(const Tensor& A, const Tensor& B, Tensor& C) { | |
| if (A.dtype_ == QuantType::INT8 && B.dtype_ == QuantType::FP32) { | |
| const int8_t* a = reinterpret_cast<const int8_t*>(A.data_.get()); | |
| const float* b = reinterpret_cast<const float*>(B.data_.get()); | |
| float* c = reinterpret_cast<float*>(C.data_.get()); | |
| size_t M = A.shape_[0]; | |
| size_t K = A.shape_[1]; | |
| size_t N = B.shape_[1]; | |
| memset(c, 0, M * N * sizeof(float)); | |
| for (size_t i = 0; i < M; ++i) { | |
| for (size_t j = 0; j < N; ++j) { | |
| int32_t sum = 0; | |
| for (size_t k = 0; k < K; ++k) { | |
| sum += a[i * K + k] * static_cast<int32_t>(b[k * N + j]); | |
| } | |
| c[i * N + j] = static_cast<float>(sum); | |
| } | |
| } | |
| } | |
| } | |
| void TensorOps::layer_norm(Tensor& x, Tensor& weight, Tensor& bias, float eps) { | |
| float* data = x.as_fp32(); | |
| float* w = weight.as_fp32(); | |
| float* b = bias.as_fp32(); | |
| size_t batch = x.shape_[0]; | |
| size_t dim = x.shape_[1]; | |
| for (size_t i = 0; i < batch; ++i) { | |
| float mean = 0.0f; | |
| float var = 0.0f; | |
| for (size_t j = 0; j < dim; ++j) { | |
| mean += data[i * dim + j]; | |
| } | |
| mean /= dim; | |
| for (size_t j = 0; j < dim; ++j) { | |
| float diff = data[i * dim + j] - mean; | |
| var += diff * diff; | |
| } | |
| var /= dim; | |
| float std = std::sqrt(var + eps); | |
| for (size_t j = 0; j < dim; ++j) { | |
| data[i * dim + j] = w[j] * (data[i * dim + j] - mean) / std + b[j]; | |
| } | |
| } | |
| } | |
| void TensorOps::gelu(Tensor& x) { | |
| float* data = x.as_fp32(); | |
| size_t n = x.numel(); | |
| const float SQRT_2_PI = 0.7978845608f; | |
| const float COEFF = 0.044715f; | |
| for (size_t i = 0; i < n; ++i) { | |
| float v = data[i]; | |
| float inner = SQRT_2_PI * (v + COEFF * v * v * v); | |
| data[i] = 0.5f * v * (1.0f + std::tanh(inner)); | |
| } | |
| } | |
| void TensorOps::softmax(Tensor& x, int axis) { | |
| float* data = x.as_fp32(); | |
| if (axis == -1) axis = static_cast<int>(x.shape_.size()) - 1; | |
| size_t outer = 1; | |
| for (int i = 0; i < axis; ++i) outer *= x.shape_[i]; | |
| size_t inner_dim = x.shape_[axis]; | |
| size_t inner = 1; | |
| for (size_t i = axis + 1; i < x.shape_.size(); ++i) inner *= x.shape_[i]; | |
| for (size_t o = 0; o < outer; ++o) { | |
| for (size_t i = 0; i < inner; ++i) { | |
| float* row = data + o * inner_dim * inner + i; | |
| float max_val = row[0]; | |
| for (size_t d = 1; d < inner_dim; ++d) { | |
| max_val = std::max(max_val, row[d * inner]); | |
| } | |
| float sum = 0.0f; | |
| for (size_t d = 0; d < inner_dim; ++d) { | |
| row[d * inner] = std::exp(row[d * inner] - max_val); | |
| sum += row[d * inner]; | |
| } | |
| for (size_t d = 0; d < inner_dim; ++d) { | |
| row[d * inner] /= sum; | |
| } | |
| } | |
| } | |
| } | |
| void TensorOps::dropout(Tensor& x, float rate, bool training) { | |
| if (!training || rate == 0.0f) return; | |
| float* data = x.as_fp32(); | |
| size_t n = x.numel(); | |
| float scale = 1.0f / (1.0f - rate); | |
| size_t addr_bits = reinterpret_cast<uintptr_t>(data); | |
| std::mt19937 rng(static_cast<unsigned>( | |
| std::hash<std::thread::id>{}(std::this_thread::get_id()) ^ addr_bits ^ n)); | |
| std::uniform_real_distribution<float> dist(0.0f, 1.0f); | |
| for (size_t i = 0; i < n; ++i) { | |
| if (dist(rng) < rate) { | |
| data[i] = 0.0f; | |
| } else { | |
| data[i] *= scale; | |
| } | |
| } | |
| } | |
| void TensorOps::quantize_int8(const Tensor& src, Tensor& dst, Tensor& scale) { | |
| if (src.dtype_ != QuantType::FP32) throw std::runtime_error("Source must be FP32"); | |
| const float* sdata = reinterpret_cast<const float*>(src.data_.get()); | |
| int8_t* ddata = reinterpret_cast<int8_t*>(dst.data_.get()); | |
| float* scales = reinterpret_cast<float*>(scale.data_.get()); | |
| size_t rows = src.shape_[0]; | |
| size_t cols = src.shape_[1]; | |
| for (size_t i = 0; i < rows; ++i) { | |
| float max_abs = 0.0f; | |
| for (size_t j = 0; j < cols; ++j) { | |
| max_abs = std::max(max_abs, std::abs(sdata[i * cols + j])); | |
| } | |
| scales[i] = max_abs / 127.0f; | |
| if (scales[i] < 1e-8f) scales[i] = 1e-8f; | |
| for (size_t j = 0; j < cols; ++j) { | |
| float v = sdata[i * cols + j] / scales[i]; | |
| ddata[i * cols + j] = static_cast<int8_t>(std::round(std::clamp(v, -128.0f, 127.0f))); | |
| } | |
| } | |
| } | |
| void TensorOps::dequantize_int8(const Tensor& src, Tensor& dst, const Tensor& scale) { | |
| const int8_t* sdata = reinterpret_cast<const int8_t*>(src.data_.get()); | |
| float* ddata = reinterpret_cast<float*>(dst.data_.get()); | |
| const float* scales = reinterpret_cast<const float*>(scale.data_.get()); | |
| size_t rows = src.shape_[0]; | |
| size_t cols = src.shape_[1]; | |
| for (size_t i = 0; i < rows; ++i) { | |
| for (size_t j = 0; j < cols; ++j) { | |
| ddata[i * cols + j] = static_cast<float>(sdata[i * cols + j]) * scales[i]; | |
| } | |
| } | |
| } | |
| void TensorOps::add(Tensor& a, const Tensor& b) { | |
| if (a.dtype_ != QuantType::FP32 || b.dtype_ != QuantType::FP32) | |
| throw std::runtime_error("add: only FP32 supported"); | |
| float* da = a.as_fp32(); | |
| const float* db = reinterpret_cast<const float*>(b.data_.get()); | |
| size_t n = a.numel(); | |
| for (size_t i = 0; i < n; ++i) da[i] += db[i]; | |
| } | |
| void TensorOps::mul(Tensor& a, float scalar) { | |
| float* data = a.as_fp32(); | |
| size_t n = a.numel(); | |
| for (size_t i = 0; i < n; ++i) data[i] *= scalar; | |
| } | |
| void TensorOps::parallel_gemm(const Tensor& A, const Tensor& B, Tensor& C, | |
| bool transA, bool transB, | |
| float alpha, float beta, | |
| size_t num_threads) { | |
| if (A.dtype_ != QuantType::FP32 || B.dtype_ != QuantType::FP32) { | |
| quantized_gemm(A, B, C); | |
| return; | |
| } | |
| size_t M = transA ? A.shape_[1] : A.shape_[0]; | |
| size_t K = transA ? A.shape_[0] : A.shape_[1]; | |
| size_t N = transB ? B.shape_[0] : B.shape_[1]; | |
| auto* a = reinterpret_cast<const float*>(A.data_.get()); | |
| auto* b = reinterpret_cast<const float*>(B.data_.get()); | |
| auto* c = reinterpret_cast<float*>(C.data_.get()); | |
| CBLAS_TRANSPOSE ctransA = transA ? CblasTrans : CblasNoTrans; | |
| CBLAS_TRANSPOSE ctransB = transB ? CblasTrans : CblasNoTrans; | |
| size_t lda = transA ? M : K; | |
| size_t ldb = transB ? K : N; | |
| cblas_sgemm(CblasRowMajor, ctransA, ctransB, M, N, K, alpha, a, lda, b, ldb, beta, c, N); | |
| if (beta != 0.0f) { | |
| for (size_t i = 0; i < M * N; ++i) c[i] *= beta; | |
| } else { | |
| memset(c, 0, M * N * sizeof(float)); | |
| } | |
| if (num_threads <= 1 || M < 8) { | |
| gemm_scalar(a, b, c, M, K, N, transA, transB, alpha, beta); | |
| return; | |
| } | |
| size_t use_threads = std::min(static_cast<size_t>(omp_get_max_threads()), num_threads); | |
| for (omp_idx_t i = 0; i < static_cast<omp_idx_t>(M); ++i) { | |
| for (size_t j = 0; j < N; ++j) { | |
| float sum = 0.0f; | |
| for (size_t k = 0; k < K; ++k) { | |
| float av = transA ? a[k * M + i] : a[i * K + k]; | |
| float bv = transB ? b[j * K + k] : b[k * N + j]; | |
| sum += av * bv; | |
| } | |
| c[i * N + j] += alpha * sum; | |
| } | |
| } | |
| ThreadPool pool; | |
| size_t rows_per_thread = (M + num_threads - 1) / num_threads; | |
| for (size_t t = 0; t < num_threads; ++t) { | |
| size_t row_start = t * rows_per_thread; | |
| size_t row_end = std::min(row_start + rows_per_thread, M); | |
| if (row_start >= M) break; | |
| pool.submit([=]() { | |
| for (size_t i = row_start; i < row_end; ++i) { | |
| for (size_t j = 0; j < N; ++j) { | |
| float sum = 0.0f; | |
| for (size_t k = 0; k < K; ++k) { | |
| float av = transA ? a[k * M + i] : a[i * K + k]; | |
| float bv = transB ? b[j * K + k] : b[k * N + j]; | |
| sum += av * bv; | |
| } | |
| c[i * N + j] += alpha * sum; | |
| } | |
| } | |
| }); | |
| } | |
| pool.wait_all(); | |
| } | |
| Tensor TensorOps::matmul(const Tensor& A, const Tensor& B) { | |
| if (A.shape_.size() != 2 || B.shape_.size() != 2) | |
| throw std::runtime_error("matmul requires 2D tensors"); | |
| if (A.shape_[1] != B.shape_[0]) | |
| throw std::runtime_error("matmul dimension mismatch"); | |
| Tensor C({A.shape_[0], B.shape_[1]}, A.dtype_); | |
| gemm(A, B, C, false, false); | |
| return C; | |
| } | |
| Tensor TensorOps::elementwise_add(const Tensor& A, const Tensor& B) { | |
| if (A.shape_ != B.shape_) throw std::runtime_error("shape mismatch in add"); | |
| Tensor C = A.clone(); | |
| add(C, B); | |
| return C; | |
| } | |
| Tensor TensorOps::elementwise_mul(const Tensor& A, const Tensor& B) { | |
| if (A.shape_ != B.shape_) throw std::runtime_error("shape mismatch in mul"); | |
| Tensor C(A.shape_, A.dtype_); | |
| const float* a = A.as_fp32(); | |
| const float* b = B.as_fp32(); | |
| float* c = C.as_fp32(); | |
| size_t n = A.numel(); | |
| for (omp_idx_t i = 0; i < static_cast<omp_idx_t>(n); ++i) c[i] = a[i] * b[i]; | |
| return C; | |
| } | |
| Tensor TensorOps::scalar_mul(const Tensor& A, float s) { | |
| Tensor C = A.clone(); | |
| mul(C, s); | |
| return C; | |
| } | |
| Tensor TensorOps::elementwise_sub(const Tensor& A, const Tensor& B) { | |
| if (A.shape_ != B.shape_) throw std::runtime_error("shape mismatch in sub"); | |
| Tensor C(A.shape_, A.dtype_); | |
| const float* a = A.as_fp32(); | |
| const float* b = B.as_fp32(); | |
| float* c = C.as_fp32(); | |
| size_t n = A.numel(); | |
| for (omp_idx_t i = 0; i < static_cast<omp_idx_t>(n); ++i) c[i] = a[i] - b[i]; | |
| return C; | |
| } | |
| Tensor TensorOps::transpose2d(const Tensor& A) { | |
| if (A.shape_.size() != 2) throw std::runtime_error("transpose2d requires 2D tensor"); | |
| size_t rows = A.shape_[0], cols = A.shape_[1]; | |
| Tensor C({cols, rows}, A.dtype_); | |
| const float* a = A.as_fp32(); | |
| float* c = C.as_fp32(); | |
| for (size_t i = 0; i < rows; ++i) | |
| for (size_t j = 0; j < cols; ++j) | |
| c[j * rows + i] = a[i * cols + j]; | |
| return C; | |
| } | |
| Tensor TensorOps::broadcast_add(const Tensor& A, const Tensor& B) { | |
| Tensor C = A.clone(); | |
| float* c = C.as_fp32(); | |
| const float* b = B.as_fp32(); | |
| if (B.shape_.size() == 1 && A.shape_.size() == 2 && B.shape_[0] == A.shape_[1]) { | |
| size_t batch = A.shape_[0], dim = A.shape_[1]; | |
| size_t n = batch * dim; | |
| for (omp_idx_t i = 0; i < static_cast<omp_idx_t>(batch); ++i) | |
| for (size_t j = 0; j < dim; ++j) | |
| c[i * dim + j] += b[j]; | |
| } else if (B.shape_ == A.shape_) { | |
| size_t n = A.numel(); | |
| for (omp_idx_t i = 0; i < static_cast<omp_idx_t>(n); ++i) c[i] += b[i]; | |
| } else { | |
| throw std::runtime_error("broadcast_add: incompatible shapes"); | |
| } | |
| return C; | |
| } | |
| void TensorOps::fill(Tensor& A, float value) { | |
| float* a = A.as_fp32(); | |
| size_t n = A.numel(); | |
| if (n >= 1024) { | |
| memset(a, 0, n * sizeof(float)); | |
| if (value != 0.0f) { | |
| for (omp_idx_t i = 0; i < static_cast<omp_idx_t>(n); ++i) a[i] = value; | |
| } | |
| } else { | |
| for (size_t i = 0; i < n; ++i) a[i] = value; | |
| } | |
| } | |
| void TensorOps::copy_data(Tensor& dst, const Tensor& src) { | |
| if (dst.numel() != src.numel()) throw std::runtime_error("copy_data: size mismatch"); | |
| memcpy(dst.as_fp32(), src.as_fp32(), dst.numel() * sizeof(float)); | |
| } | |
| float TensorOps::dot(const Tensor& A, const Tensor& B) { | |
| if (A.shape_ != B.shape_) throw std::runtime_error("dot: shape mismatch"); | |
| const float* a = A.as_fp32(); | |
| const float* b = B.as_fp32(); | |
| size_t n = A.numel(); | |
| float sum = 0.0f; | |
| for (omp_idx_t i = 0; i < static_cast<omp_idx_t>(n); ++i) sum += a[i] * b[i]; | |
| return sum; | |
| } | |
| float TensorOps::norm2(const Tensor& A) { | |
| return std::sqrt(dot(A, A)); | |
| } | |
| Tensor TensorOps::reduce_sum(const Tensor& A, int axis) { | |
| if (axis == -1) axis = static_cast<int>(A.shape_.size()) - 1; | |
| if (A.shape_.size() != 2 || axis != 1) | |
| throw std::runtime_error("reduce_sum: only 2D axis=1 supported"); | |
| size_t batch = A.shape_[0], dim = A.shape_[1]; | |
| Tensor C({batch, 1}, QuantType::FP32); | |
| const float* a = A.as_fp32(); | |
| float* c = C.as_fp32(); | |
| for (size_t i = 0; i < batch; ++i) { | |
| float sum = 0.0f; | |
| for (size_t j = 0; j < dim; ++j) sum += a[i * dim + j]; | |
| c[i] = sum; | |
| } | |
| return C; | |
| } | |
| Tensor TensorOps::slice(const Tensor& A, size_t dim, size_t start, size_t end) { | |
| if (dim >= A.shape_.size()) throw std::runtime_error("slice: dim out of range"); | |
| if (end > A.shape_[dim]) end = A.shape_[dim]; | |
| auto new_shape = A.shape_; | |
| new_shape[dim] = end - start; | |
| Tensor C(new_shape, A.dtype_); | |
| if (A.shape_.size() == 2 && dim == 1) { | |
| size_t rows = A.shape_[0]; | |
| const float* a = A.as_fp32(); | |
| float* c = C.as_fp32(); | |
| for (size_t i = 0; i < rows; ++i) | |
| memcpy(c + i * (end - start), a + i * A.shape_[1] + start, (end - start) * sizeof(float)); | |
| } else if (A.shape_.size() == 2 && dim == 0) { | |
| const float* a = A.as_fp32(); | |
| float* c = C.as_fp32(); | |
| memcpy(c, a + start * A.shape_[1], (end - start) * A.shape_[1] * sizeof(float)); | |
| } else { | |
| throw std::runtime_error("slice: only 2D supported"); | |
| } | |
| return C; | |
| } | |
| Tensor TensorOps::pad1d(const Tensor& A, size_t left, size_t right, float value) { | |
| if (A.shape_.size() != 2) throw std::runtime_error("pad1d requires 2D tensor"); | |
| size_t batch = A.shape_[0], dim = A.shape_[1]; | |
| size_t new_dim = left + dim + right; | |
| Tensor C({batch, new_dim}, QuantType::FP32); | |
| float* c = C.as_fp32(); | |
| const float* a = A.as_fp32(); | |
| for (size_t i = 0; i < batch; ++i) { | |
| for (size_t j = 0; j < left; ++j) c[i * new_dim + j] = value; | |
| memcpy(c + i * new_dim + left, a + i * dim, dim * sizeof(float)); | |
| for (size_t j = left + dim; j < new_dim; ++j) c[i * new_dim + j] = value; | |
| } | |
| return C; | |
| } | |
| void TensorOps::apply_inplace(Tensor& A, std::function<float(float)> fn) { | |
| float* a = A.as_fp32(); | |
| size_t n = A.numel(); | |
| for (omp_idx_t i = 0; i < static_cast<omp_idx_t>(n); ++i) a[i] = fn(a[i]); | |
| } | |
| Tensor TensorOps::apply(const Tensor& A, std::function<float(float)> fn) { | |
| Tensor C = A.clone(); | |
| apply_inplace(C, fn); | |
| return C; | |
| } | |
| Tensor TensorOps::relu(const Tensor& A) { | |
| Tensor C = A.clone(); | |
| float* c = C.as_fp32(); | |
| size_t n = A.numel(); | |
| for (omp_idx_t i = 0; i < static_cast<omp_idx_t>(n); ++i) c[i] = std::max(0.0f, c[i]); | |
| return C; | |
| } | |
| Tensor TensorOps::sigmoid(const Tensor& A) { | |
| Tensor C = A.clone(); | |
| float* c = C.as_fp32(); | |
| size_t n = A.numel(); | |
| for (omp_idx_t i = 0; i < static_cast<omp_idx_t>(n); ++i) c[i] = 1.0f / (1.0f + std::exp(-c[i])); | |
| return C; | |
| } | |
| Tensor TensorOps::tanh_act(const Tensor& A) { | |
| Tensor C = A.clone(); | |
| float* c = C.as_fp32(); | |
| size_t n = A.numel(); | |
| for (omp_idx_t i = 0; i < static_cast<omp_idx_t>(n); ++i) c[i] = std::tanh(c[i]); | |
| return C; | |
| } | |
| Tensor TensorOps::log(const Tensor& A) { | |
| Tensor C = A.clone(); | |
| float* c = C.as_fp32(); | |
| size_t n = A.numel(); | |
| for (omp_idx_t i = 0; i < static_cast<omp_idx_t>(n); ++i) c[i] = std::log(std::max(c[i], 1e-7f)); | |
| return C; | |
| } | |
| Tensor TensorOps::exp(const Tensor& A) { | |
| Tensor C = A.clone(); | |
| float* c = C.as_fp32(); | |
| size_t n = A.numel(); | |
| for (omp_idx_t i = 0; i < static_cast<omp_idx_t>(n); ++i) c[i] = std::exp(c[i]); | |
| return C; | |
| } | |
| void TensorOps::clip_inplace(Tensor& A, float min_val, float max_val) { | |
| float* a = A.as_fp32(); | |
| size_t n = A.numel(); | |
| for (omp_idx_t i = 0; i < static_cast<omp_idx_t>(n); ++i) | |
| a[i] = std::clamp(a[i], min_val, max_val); | |
| } | |
| Tensor TensorOps::concat(const std::vector<Tensor>& tensors, size_t axis) { | |
| if (tensors.empty()) throw std::runtime_error("Empty tensors"); | |
| auto new_shape = tensors[0].shape_; | |
| size_t total = 0; | |
| for (auto& t : tensors) { | |
| total += t.shape_[axis]; | |
| } | |
| new_shape[axis] = total; | |
| Tensor result(new_shape); | |
| float* out = result.as_fp32(); | |
| size_t offset = 0; | |
| size_t outer = 1; | |
| for (size_t i = 0; i < axis; ++i) outer *= tensors[0].shape_[i]; | |
| size_t inner = 1; | |
| for (size_t i = axis + 1; i < tensors[0].shape_.size(); ++i) inner *= tensors[0].shape_[i]; | |
| for (auto& t : tensors) { | |
| const float* in = reinterpret_cast<const float*>(t.data_.get()); | |
| size_t dim = t.shape_[axis]; | |
| for (size_t o = 0; o < outer; ++o) { | |
| memcpy(out + o * total * inner + offset * inner, | |
| in + o * dim * inner, | |
| dim * inner * sizeof(float)); | |
| } | |
| offset += dim; | |
| } | |
| return result; | |
| } | |
| } // namespace neuroflow |
Xet Storage Details
- Size:
- 27.1 kB
- Xet hash:
- d7f9b1bb46487304fdc89bd7fbe75f7f186a43614af34f03d6df76b27fb557a9
·
Xet efficiently stores files, intelligently splitting them into unique chunks and accelerating uploads and downloads. More info.