utils.cpp 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268
  1. #include "utils.h"
  2. #include <ggml-backend.h>
  3. #include <fstream>
  4. #include <iostream>
  5. #include <cstring>
  6. #include <cmath>
  7. #include <algorithm>
  8. #include <filesystem>
  9. #include <sstream>
  10. namespace fs = std::filesystem;
  11. namespace utils {
  12. // NPY header parsing (simplified - assumes float32, C-order)
  13. std::pair<float*, std::vector<size_t>> load_npy(const std::string& filepath) {
  14. std::ifstream file(filepath, std::ios::binary);
  15. if (!file) {
  16. std::cerr << "Failed to open: " << filepath << std::endl;
  17. return {nullptr, {}};
  18. }
  19. // Read magic string
  20. char magic[6];
  21. file.read(magic, 6);
  22. if (std::string(magic, 6) != "\x93NUMPY") {
  23. std::cerr << "Invalid NPY file: " << filepath << std::endl;
  24. return {nullptr, {}};
  25. }
  26. // Read version
  27. uint8_t major, minor;
  28. file.read(reinterpret_cast<char*>(&major), 1);
  29. file.read(reinterpret_cast<char*>(&minor), 1);
  30. // Read header length
  31. uint16_t header_len;
  32. if (major == 1) {
  33. file.read(reinterpret_cast<char*>(&header_len), 2);
  34. } else {
  35. uint32_t header_len_32;
  36. file.read(reinterpret_cast<char*>(&header_len_32), 4);
  37. header_len = header_len_32;
  38. }
  39. // Read header
  40. std::string header(header_len, ' ');
  41. file.read(&header[0], header_len);
  42. // Parse shape from header
  43. std::vector<size_t> shape;
  44. size_t shape_start = header.find("'shape': (");
  45. if (shape_start == std::string::npos) {
  46. shape_start = header.find("\"shape\": (");
  47. }
  48. if (shape_start != std::string::npos) {
  49. size_t shape_end = header.find(')', shape_start);
  50. std::string shape_str = header.substr(shape_start + 10, shape_end - shape_start - 10);
  51. std::istringstream ss(shape_str);
  52. std::string token;
  53. while (std::getline(ss, token, ',')) {
  54. // Remove spaces
  55. token.erase(std::remove(token.begin(), token.end(), ' '), token.end());
  56. if (!token.empty()) {
  57. shape.push_back(std::stoull(token));
  58. }
  59. }
  60. }
  61. // Calculate total elements
  62. size_t nelements = 1;
  63. for (size_t dim : shape) {
  64. nelements *= dim;
  65. }
  66. // Read data
  67. float* data = new float[nelements];
  68. file.read(reinterpret_cast<char*>(data), nelements * sizeof(float));
  69. file.close();
  70. return {data, shape};
  71. }
  72. std::map<std::string, std::pair<float*, std::vector<size_t>>> load_all_weights(const std::string& debug_dir) {
  73. std::map<std::string, std::pair<float*, std::vector<size_t>>> weights;
  74. std::string weights_dir = debug_dir + "/weights";
  75. for (const auto& entry : fs::directory_iterator(weights_dir)) {
  76. if (entry.path().extension() == ".npy") {
  77. std::string name = entry.path().stem().string();
  78. auto [data, shape] = load_npy(entry.path().string());
  79. if (data) {
  80. weights[name] = {data, shape};
  81. // std::cout << "Loaded weight: " << name << " shape: [";
  82. //for (size_t i = 0; i < shape.size(); ++i) {
  83. // std::cout << shape[i];
  84. // if (i < shape.size() - 1) std::cout << ", ";
  85. //}
  86. // std::cout << "]" << std::endl;
  87. }
  88. }
  89. }
  90. return weights;
  91. }
  92. std::pair<float*, std::vector<size_t>> load_activation(const std::string& debug_dir, const std::string& name) {
  93. std::string filepath = debug_dir + "/activations/" + name + ".npy";
  94. return load_npy(filepath);
  95. }
  96. TensorComparison compare_tensors(
  97. const std::string& name,
  98. const float* expected,
  99. const std::vector<size_t>& expected_shape,
  100. const ggml_tensor* actual,
  101. float atol,
  102. float rtol
  103. ) {
  104. TensorComparison result;
  105. result.name = name;
  106. result.shape_expected = expected_shape;
  107. // Extract actual shape
  108. std::vector<size_t> actual_shape;
  109. for (int i = 0; i < GGML_MAX_DIMS; ++i) {
  110. if (actual->ne[i] > 1 || i == 0) {
  111. actual_shape.push_back(actual->ne[i]);
  112. }
  113. }
  114. std::reverse(actual_shape.begin(), actual_shape.end());
  115. result.shape_actual = actual_shape;
  116. // Robust Squeeze Comparison
  117. std::vector<size_t> expected_squeezed;
  118. for (size_t dim : expected_shape) {
  119. if (dim > 1) expected_squeezed.push_back(dim);
  120. }
  121. std::vector<size_t> actual_squeezed;
  122. for (size_t dim : actual_shape) {
  123. if (dim > 1) actual_squeezed.push_back(dim);
  124. }
  125. bool shape_match = false;
  126. if (expected_squeezed.size() == actual_squeezed.size()) {
  127. shape_match = true;
  128. for (size_t i = 0; i < expected_squeezed.size(); ++i) {
  129. if (expected_squeezed[i] != actual_squeezed[i]) {
  130. shape_match = false;
  131. break;
  132. }
  133. }
  134. }
  135. if (!shape_match) {
  136. result.match = false;
  137. result.max_abs_diff = -1;
  138. result.mean_abs_diff = -1;
  139. result.max_rel_diff = -1;
  140. return result;
  141. }
  142. // Compare values
  143. size_t nelements = shape_nelements(expected_shape);
  144. // Note: shape_nelements uses full shape, which is correct as total elements match.
  145. // Safe data access for Backend/CUDA (Copy to CPU first)
  146. // Note: This requires including ggml-backend.h and linking against it
  147. std::vector<float> actual_data_vec(nelements);
  148. ggml_backend_tensor_get(const_cast<ggml_tensor*>(actual), actual_data_vec.data(), 0, ggml_nbytes(actual));
  149. const float* actual_data = actual_data_vec.data();
  150. float max_abs = 0.0f;
  151. float sum_abs = 0.0f;
  152. float max_rel = 0.0f;
  153. for (size_t i = 0; i < nelements; ++i) {
  154. float diff = std::abs(expected[i] - actual_data[i]);
  155. max_abs = std::max(max_abs, diff);
  156. sum_abs += diff;
  157. float rel_diff = 0.0f;
  158. if (std::abs(expected[i]) > 1e-8) {
  159. rel_diff = diff / std::abs(expected[i]);
  160. }
  161. max_rel = std::max(max_rel, rel_diff);
  162. }
  163. result.max_abs_diff = max_abs;
  164. result.mean_abs_diff = sum_abs / nelements;
  165. result.max_rel_diff = max_rel;
  166. result.match = (max_abs <= atol) || (max_rel <= rtol);
  167. return result;
  168. }
  169. void print_comparison(const TensorComparison& cmp, bool verbose) {
  170. std::cout << "\n[Comparison] " << cmp.name << std::endl;
  171. // Print shapes
  172. std::cout << " Expected shape: [";
  173. for (size_t i = 0; i < cmp.shape_expected.size(); ++i) {
  174. std::cout << cmp.shape_expected[i];
  175. if (i < cmp.shape_expected.size() - 1) std::cout << ", ";
  176. }
  177. std::cout << "]" << std::endl;
  178. std::cout << " Actual shape: [";
  179. for (size_t i = 0; i < cmp.shape_actual.size(); ++i) {
  180. std::cout << cmp.shape_actual[i];
  181. if (i < cmp.shape_actual.size() - 1) std::cout << ", ";
  182. }
  183. std::cout << "]" << std::endl;
  184. // Print statistics
  185. if (cmp.max_abs_diff >= 0) {
  186. std::cout << " Max abs diff: " << cmp.max_abs_diff << std::endl;
  187. std::cout << " Mean abs diff: " << cmp.mean_abs_diff << std::endl;
  188. std::cout << " Max rel diff: " << cmp.max_rel_diff << std::endl;
  189. std::cout << " Status: " << (cmp.match ? "✓ MATCH" : "✗ MISMATCH") << std::endl;
  190. } else {
  191. std::cout << " Status: ✗ SHAPE MISMATCH" << std::endl;
  192. }
  193. }
  194. ggml_tensor* create_tensor_from_numpy(
  195. ggml_context* ctx,
  196. const float* data,
  197. const std::vector<size_t>& shape
  198. ) {
  199. // GGML uses reversed dimension order
  200. int64_t ne[GGML_MAX_DIMS] = {1, 1, 1, 1};
  201. for (size_t i = 0; i < shape.size() && i < GGML_MAX_DIMS; ++i) {
  202. ne[shape.size() - 1 - i] = shape[i];
  203. }
  204. ggml_tensor* tensor = ggml_new_tensor(ctx, GGML_TYPE_F32, shape.size(), ne);
  205. memcpy(tensor->data, data, shape_nelements(shape) * sizeof(float));
  206. return tensor;
  207. }
  208. size_t shape_nelements(const std::vector<size_t>& shape) {
  209. size_t n = 1;
  210. for (size_t dim : shape) {
  211. n *= dim;
  212. }
  213. return n;
  214. }
  215. void print_tensor_shape(const std::string& name, const ggml_tensor* tensor) {
  216. std::cout << name << " shape: [";
  217. for (int i = GGML_MAX_DIMS - 1; i >= 0; --i) {
  218. if (tensor->ne[i] > 1 || i == 0) {
  219. std::cout << tensor->ne[i];
  220. if (i > 0) std::cout << ", ";
  221. }
  222. }
  223. std::cout << "]" << std::endl;
  224. }
  225. void free_npy_data(float* data) {
  226. delete[] data;
  227. }
  228. } // namespace utils