harness_cpu_test.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184
  1. package tests
  2. import (
  3. "math"
  4. "math/rand"
  5. "testing"
  6. "unsafe"
  7. "makarna/pkg/backend/cpu"
  8. "makarna/pkg/backend/cpu/nn"
  9. "makarna/pkg/quant"
  10. "makarna/pkg/tensor"
  11. )
  12. func maxAbsDiff(a, b []float32) float32 {
  13. max := float32(0)
  14. for i := range a {
  15. d := a[i] - b[i]
  16. if d < 0 {
  17. d = -d
  18. }
  19. if d > max {
  20. max = d
  21. }
  22. }
  23. return max
  24. }
  25. func mse(a, b []float32) float32 {
  26. var s float32
  27. for i := range a {
  28. d := a[i] - b[i]
  29. s += d * d
  30. }
  31. return s / float32(len(a))
  32. }
  33. func makeTestBlock(seed int64, scale float32) []float32 {
  34. r := rand.New(rand.NewSource(seed))
  35. out := make([]float32, 256)
  36. for i := range out {
  37. out[i] = (r.Float32()*2 - 1) * scale
  38. }
  39. return out
  40. }
  41. func TestHarness_CPU_QuantDequantRoundTrip(t *testing.T) {
  42. cases := []struct {
  43. name string
  44. seed int64
  45. scale float32
  46. mseMax float32
  47. maxAbsMax float32
  48. }{
  49. {name: "small", seed: 1, scale: 0.01, mseMax: 1e-4, maxAbsMax: 0.1},
  50. {name: "medium", seed: 2, scale: 1.0, mseMax: 5.0, maxAbsMax: 2.0},
  51. {name: "large", seed: 3, scale: 100.0, mseMax: 5e4, maxAbsMax: 200.0},
  52. }
  53. for _, tc := range cases {
  54. t.Run(tc.name, func(t *testing.T) {
  55. inp := makeTestBlock(tc.seed, tc.scale)
  56. q2 := quant.QuantizeQ2K(inp)
  57. q3 := quant.QuantizeQ3K(inp)
  58. q4 := quant.QuantizeQ4K(inp)
  59. q6 := quant.QuantizeQ6K(inp)
  60. q8 := quant.QuantizeQ8K(inp)
  61. if len(q2) != 84 {
  62. t.Fatalf("q2k size=%d", len(q2))
  63. }
  64. if len(q3) != 110 {
  65. t.Fatalf("q3k size=%d", len(q3))
  66. }
  67. if len(q4) != 144 {
  68. t.Fatalf("q4k size=%d", len(q4))
  69. }
  70. if len(q6) != 210 {
  71. t.Fatalf("q6k size=%d", len(q6))
  72. }
  73. if len(q8) != 292 {
  74. t.Fatalf("q8k size=%d", len(q8))
  75. }
  76. out2 := make([]float32, 256)
  77. out3 := make([]float32, 256)
  78. out4 := make([]float32, 256)
  79. out6 := make([]float32, 256)
  80. out8 := make([]float32, 256)
  81. tensor.DequantizeQ2_K((*tensor.BlockQ2_K)(unsafe.Pointer(&q2[0])), out2)
  82. tensor.DequantizeQ3_K((*tensor.BlockQ3_K)(unsafe.Pointer(&q3[0])), out3)
  83. tensor.DequantizeQ4_K((*tensor.BlockQ4_K)(unsafe.Pointer(&q4[0])), out4)
  84. tensor.DequantizeQ6_K((*tensor.BlockQ6_K)(unsafe.Pointer(&q6[0])), out6)
  85. tensor.DequantizeQ8_K((*tensor.BlockQ8_K)(unsafe.Pointer(&q8[0])), out8)
  86. outs := []struct {
  87. name string
  88. out []float32
  89. }{
  90. {name: "q2k", out: out2},
  91. {name: "q3k", out: out3},
  92. {name: "q4k", out: out4},
  93. {name: "q6k", out: out6},
  94. {name: "q8k", out: out8},
  95. }
  96. for _, o := range outs {
  97. for i, v := range o.out {
  98. if math.IsNaN(float64(v)) || math.IsInf(float64(v), 0) {
  99. t.Fatalf("%s invalid at %d: %f", o.name, i, v)
  100. }
  101. }
  102. m := mse(inp, o.out)
  103. mx := maxAbsDiff(inp, o.out)
  104. if m > tc.mseMax {
  105. t.Fatalf("%s mse=%f > %f", o.name, m, tc.mseMax)
  106. }
  107. if mx > tc.maxAbsMax {
  108. t.Fatalf("%s maxAbs=%f > %f", o.name, mx, tc.maxAbsMax)
  109. }
  110. }
  111. })
  112. }
  113. }
  114. func TestHarness_CPU_NNOpsSanity(t *testing.T) {
  115. dim := 32
  116. seq := 4
  117. r := rand.New(rand.NewSource(123))
  118. xData := make([]float32, seq*dim)
  119. wData := make([]float32, dim)
  120. for i := range xData {
  121. xData[i] = r.Float32()*2 - 1
  122. }
  123. for i := range wData {
  124. wData[i] = r.Float32()*2 - 1
  125. }
  126. x := cpu.NewTensor(tensor.Shape{seq, dim}, append([]float32(nil), xData...))
  127. w := cpu.NewTensor(tensor.Shape{dim}, append([]float32(nil), wData...))
  128. if err := nn.RMSNorm(x, w, 1e-5); err != nil {
  129. t.Fatalf("rmsnorm: %v", err)
  130. }
  131. for i, v := range x.DataFloat32() {
  132. if math.IsNaN(float64(v)) || math.IsInf(float64(v), 0) {
  133. t.Fatalf("rmsnorm invalid at %d: %f", i, v)
  134. }
  135. }
  136. headDim := 16
  137. numHeads := dim / headDim
  138. pos := make([]int, seq)
  139. for i := range pos {
  140. pos[i] = i
  141. }
  142. if err := nn.RoPE(x, pos, headDim, 10000); err != nil {
  143. t.Fatalf("rope: %v", err)
  144. }
  145. for i, v := range x.DataFloat32() {
  146. if math.IsNaN(float64(v)) || math.IsInf(float64(v), 0) {
  147. t.Fatalf("rope invalid at %d: %f", i, v)
  148. }
  149. }
  150. _ = numHeads
  151. row := cpu.NewTensor(tensor.Shape{dim}, append([]float32(nil), xData[:dim]...))
  152. if err := nn.Softmax(row); err != nil {
  153. t.Fatalf("softmax: %v", err)
  154. }
  155. var sum float32
  156. for i, v := range row.DataFloat32() {
  157. if v <= 0 || math.IsNaN(float64(v)) || math.IsInf(float64(v), 0) {
  158. t.Fatalf("softmax invalid at %d: %f", i, v)
  159. }
  160. sum += v
  161. }
  162. if d := float32(math.Abs(float64(sum - 1))); d > 1e-4 {
  163. t.Fatalf("softmax sum=%f", sum)
  164. }
  165. }