Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9622b928b4 | ||
|
|
7fa6ea0da7 | ||
|
|
310b235626 | ||
|
|
448fc4cd2a |
No files matched your search
+36
-37
@@ -1,11 +1,10 @@
|
||||
package sample
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math"
|
||||
"math/rand/v2"
|
||||
"slices"
|
||||
"math/rand"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/ollama/ollama/llama"
|
||||
)
|
||||
@@ -90,53 +89,53 @@ func (s *Sampler) sample(tokens []token) (token, error) {
|
||||
sortLogits(tokens)
|
||||
}
|
||||
|
||||
// token logit values are updated to probabilities
|
||||
tokens = temperature(tokens, s.temperature)
|
||||
|
||||
tokens = topP(tokens, s.topP)
|
||||
tokens = minP(tokens, s.minP)
|
||||
|
||||
// TODO: this should fall back to greedy sampling
|
||||
// or topP, topK values etc should be such that
|
||||
// there are always tokens to sample from
|
||||
if len(tokens) == 0 {
|
||||
return token{}, errors.New("no tokens to sample from")
|
||||
}
|
||||
// token logit values are updated to probabilities
|
||||
temperature(tokens, s.temperature)
|
||||
softmax(tokens)
|
||||
return tokens[dist(tokens, s.rng.Int63())], nil
|
||||
|
||||
var r float32
|
||||
if s.rng != nil {
|
||||
r = s.rng.Float32()
|
||||
} else {
|
||||
r = rand.Float32()
|
||||
}
|
||||
// // TODO: this should fall back to greedy sampling
|
||||
// // or topP, topK values etc should be such that
|
||||
// // there are always tokens to sample from
|
||||
// if len(tokens) == 0 {
|
||||
// return token{}, errors.New("no tokens to sample from")
|
||||
// }
|
||||
|
||||
// Calculate cumulative sum of probabilities
|
||||
var sum float32
|
||||
for i := range tokens {
|
||||
sum += tokens[i].value
|
||||
tokens[i].value = sum
|
||||
}
|
||||
r *= tokens[len(tokens)-1].value
|
||||
// var r float32
|
||||
// if s.rng != nil {
|
||||
// r = s.rng.Float32()
|
||||
// } else {
|
||||
// r = rand.Float32()
|
||||
// }
|
||||
|
||||
idx, _ := slices.BinarySearchFunc(tokens, r, func(token token, target float32) int {
|
||||
if token.value < target {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
})
|
||||
// // Calculate cumulative sum of probabilities
|
||||
// var sum float32
|
||||
// for i := range tokens {
|
||||
// sum += tokens[i].value
|
||||
// tokens[i].value = sum
|
||||
// }
|
||||
// r *= tokens[len(tokens)-1].value
|
||||
|
||||
return tokens[idx], nil
|
||||
// idx, _ := slices.BinarySearchFunc(tokens, r, func(token token, target float32) int {
|
||||
// if token.value < target {
|
||||
// return -1
|
||||
// }
|
||||
// return 1
|
||||
// })
|
||||
|
||||
// return tokens[idx], nil
|
||||
}
|
||||
|
||||
// TODO(parthsareen): update sampler interface to use json unmarshal https://github.com/ollama/ollama/issues/9278
|
||||
func NewSampler(temperature float32, topK int, topP float32, minP float32, seed int, grammar *Grammar) Sampler {
|
||||
var rng *rand.Rand
|
||||
if seed != -1 {
|
||||
// PCG requires two parameters: sequence and stream
|
||||
// Use original seed for sequence
|
||||
sequence := uint64(seed)
|
||||
// Use golden ratio hash to generate statistically independent seeds
|
||||
rng = rand.New(rand.NewPCG(sequence, sequence^0x9E3779B9))
|
||||
rng = rand.New(rand.NewSource(int64(seed)))
|
||||
} else {
|
||||
rng = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||
}
|
||||
if temperature < 0.0 {
|
||||
temperature = 0.0
|
||||
|
||||
Vendored
+1
@@ -0,0 +1 @@
|
||||
[-0.14049109816551208, 0.06803577393293381, 0.08947011083364487, -0.15352056920528412, 0.16544274985790253, -0.22324655950069427, -0.07907228916883469, -0.11153314262628555, 0.16745619475841522, -0.08845115453004837, -0.05397915467619896, 0.09702621400356293, 0.1040145754814148, 0.034065235406160355, -0.023494448512792587, 0.023317236453294754, 0.06201161816716194, -0.14594611525535583, 0.05035833641886711, -0.06913720071315765, -0.0850675031542778, 0.0007670316845178604, -0.08676620572805405, 0.020724542438983917, 0.18044163286685944, 0.04924239218235016, -0.15723371505737305, 0.03611345216631889, -0.17997989058494568, -0.068993479013443, 0.05774486064910889, 0.08498939871788025, 0.12943890690803528, -0.006152433808892965, 0.14178094267845154, 0.1473844349384308, -0.012766982428729534, 0.045800212770700455, 0.19246874749660492, -0.09747152775526047, 0.006072793155908585, 0.017143171280622482, -0.02200348488986492, -0.027592593804001808, -0.04569224640727043, 0.026975959539413452, 0.0077785165049135685, -0.17419104278087616, 0.2779255509376526, -0.030980607494711876, 0.11484438180923462, -0.20746532082557678, -0.06505528837442398, -0.019896341487765312, 0.02105427347123623, 0.20272167026996613, 0.04019889235496521, -0.017126401886343956, -0.0002831551246345043, -0.07687067240476608, 0.012454139068722725, -0.15010742843151093, -0.07807016372680664, 0.01188774686306715, 0.13062332570552826, -0.08224914968013763, -0.08921745419502258, 0.013551718555390835, -0.02187133952975273, 0.005148813594132662, 0.008781243115663528, 0.10651258379220963, 0.015292760916054249, 0.03104853443801403, 0.017170460894703865, 0.09132644534111023, 0.06599127501249313, 0.11407657712697983, 0.2534945011138916, -0.1733740270137787, 0.0020254752598702908, 0.05714576318860054, -0.19750545918941498, -0.10138755291700363, -0.15401461720466614, -0.011985579505562782, 0.04546714201569557, -0.0640132948756218, -0.030880948528647423, 0.11062195152044296, 0.14438383281230927, -0.07269343733787537, 0.08082295954227448, 0.028231162577867508, -0.0299157053232193, 0.015319091267883778, -0.07127209007740021, -0.004352295771241188, -0.08830961585044861, -0.15393312275409698, -0.05910560116171837, -0.044640619307756424, -0.04869438707828522, -0.1213475689291954, -0.0413384735584259, 0.11684834957122803, 0.02147417515516281, -0.19815197587013245, -0.027197321876883507, 0.09237757325172424, 0.016114329919219017, -0.07321144640445709, 0.013616911135613918, 0.1633937656879425, 0.2265119105577469, 0.04887519031763077, -0.09309308975934982, -0.10166775435209274, 0.034362174570560455, -0.016704145818948746, 0.26136764883995056, 0.024831606075167656, 0.019148696213960648, -0.10227427631616592, 0.05994879826903343, -0.028703754767775536, -0.08792375773191452, -0.09701353311538696, 0.006381520070135593, 0.09135088324546814, -0.03421201556921005, 0.027725202962756157, 0.10950426012277603, 0.08471143990755081, -0.06850480288267136, -0.2098373919725418, 0.024568362161517143, 0.016832124441862106, -0.09213020652532578, 0.07319895178079605, -0.2254962921142578, 0.09601815789937973, -0.14963679015636444, 0.13309000432491302, -0.06646179407835007, 0.007633576635271311, -0.006754335947334766, -0.12666666507720947, -0.06185764819383621, -0.03350026533007622, -0.07640213519334793, 0.11266022175550461, 0.05684903264045715, -0.021437257528305054, -0.057359494268894196, 0.022800210863351822, 0.042317431420087814, 0.010130883194506168, 0.12166910618543625, -0.0677243322134018, -0.05597330257296562, 0.10634074360132217, -0.14240652322769165, -0.07514529675245285, 0.11910972744226456, -0.0635921061038971, -0.12317600101232529, 0.07619466632604599, -0.05430442839860916, 0.01882130652666092, -0.013355006463825703, 0.13947084546089172, 0.13103191554546356, 0.21173261106014252, 0.012381404638290405, 0.10963138192892075, 0.20157527923583984, 0.04969905689358711, -0.07988657802343369, -0.12305821478366852, 0.030198488384485245, -0.12815065681934357, -0.1493828296661377, 0.16609710454940796, 0.03147311136126518, -0.05435416102409363, -0.06691042333841324, 0.08390865474939346, 0.1344001740217209, -0.10086281597614288, -0.07271518558263779, -0.05838662385940552, -0.10467743873596191, 0.047540608793497086, -0.06734462082386017, -0.13437806069850922, -0.05936319753527641, 0.10448285192251205, -0.11318164318799973, 0.018219152465462685, -0.1846952736377716, -0.07407224178314209, 0.07009526342153549, 0.11060779541730881, 0.0008995900861918926, 0.031116077676415443, 0.005915861111134291, -0.022779200226068497, -0.059181224554777145, 0.07091255486011505, 0.07433564215898514, 0.056968193501234055, 0.11839757114648819, 0.15867207944393158, -0.15376228094100952, -0.21046996116638184, -0.0966387465596199, -0.0876750648021698, -0.21115104854106903, -0.07812130451202393, 0.013138437643647194, 0.037374746054410934, -0.001360200229100883, -0.01634298823773861, -0.04794382303953171, -0.22696244716644287, -0.09551295638084412, 0.09177322685718536, -0.07562369853258133, 0.018677910789847374, 0.098722Line truncated
|
||||
+144
-106
@@ -1,92 +1,58 @@
|
||||
package sample
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"math"
|
||||
"math/rand"
|
||||
"slices"
|
||||
)
|
||||
|
||||
// temperature applies scaling and softmax to the logits
|
||||
func temperature(ts []token, temp float32) []token {
|
||||
// Find max logit for numerical stability
|
||||
maxLogit := float32(math.Inf(-1))
|
||||
for _, t := range ts {
|
||||
if t.value > maxLogit {
|
||||
maxLogit = t.value
|
||||
}
|
||||
}
|
||||
// tokenHeap implements heap.Interface and holds tokens as a min-heap to track k largest elements
|
||||
type tokenHeap []token
|
||||
|
||||
// Apply temperature and compute exp(x - max)
|
||||
temp = max(temp, 1e-7)
|
||||
var sum float32
|
||||
for i, v := range ts {
|
||||
ts[i].value = float32(math.Exp(float64((v.value - maxLogit) / temp)))
|
||||
sum += ts[i].value
|
||||
}
|
||||
func (h tokenHeap) Len() int { return len(h) }
|
||||
func (h tokenHeap) Less(i, j int) bool { return h[i].value < h[j].value } // Use < for min-heap to track largest elements
|
||||
func (h tokenHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
|
||||
|
||||
// Normalize
|
||||
for i := range ts {
|
||||
ts[i].value /= sum
|
||||
}
|
||||
|
||||
return ts
|
||||
func (h *tokenHeap) Push(x any) {
|
||||
*h = append(*h, x.(token))
|
||||
}
|
||||
|
||||
// siftDown maintains a min-heap property by recursively moving larger elements down the heap.
|
||||
//
|
||||
// The heap is represented as an array where for any node at index i:
|
||||
// - Left child is at index 2i + 1
|
||||
// - Right child is at index 2i + 2
|
||||
// - Parent is at index (i-1)/2
|
||||
//
|
||||
// The function compares a node with its children and:
|
||||
// 1. Finds the smallest value between the node and its children
|
||||
// 2. If the node is not the smallest, swaps it with its smallest child
|
||||
// 3. Continues this process down the affected path until the min-heap property is restored
|
||||
func siftDown(data []token, start, end int) {
|
||||
root := start
|
||||
for {
|
||||
child := 2*root + 1
|
||||
if child >= end {
|
||||
break
|
||||
}
|
||||
// Find smaller child (we want min heap)
|
||||
if child+1 < end && data[child+1].value < data[child].value {
|
||||
child++
|
||||
}
|
||||
// Exit if root is already smaller than children
|
||||
if data[root].value <= data[child].value {
|
||||
break
|
||||
}
|
||||
// Swap with smaller child and continue
|
||||
data[root], data[child] = data[child], data[root]
|
||||
root = child
|
||||
}
|
||||
func (h *tokenHeap) Pop() any {
|
||||
old := *h
|
||||
n := len(old)
|
||||
x := old[n-1]
|
||||
*h = old[0 : n-1]
|
||||
return x
|
||||
}
|
||||
|
||||
// topK limits the number of tokens considered to the k highest logits
|
||||
func topK(ts []token, k int) []token {
|
||||
if k >= len(ts) {
|
||||
sortLogits(ts)
|
||||
return ts
|
||||
}
|
||||
// Heapify + siftDown - O(nlog(k))
|
||||
// Build min-heap of first k elements
|
||||
heap := ts[:k]
|
||||
for i := k/2 - 1; i >= 0; i-- {
|
||||
siftDown(heap, i, k)
|
||||
}
|
||||
|
||||
// Process remaining elements - if larger than heap root, replace root
|
||||
// Initialize min-heap with first k elements
|
||||
h := make(tokenHeap, k)
|
||||
copy(h, ts[:k])
|
||||
heap.Init(&h)
|
||||
|
||||
// Process remaining elements
|
||||
for i := k; i < len(ts); i++ {
|
||||
if ts[i].value > heap[0].value {
|
||||
heap[0] = ts[i]
|
||||
siftDown(heap, 0, k)
|
||||
if ts[i].value > h[0].value {
|
||||
heap.Pop(&h)
|
||||
heap.Push(&h, ts[i])
|
||||
}
|
||||
}
|
||||
|
||||
slices.Reverse(heap)
|
||||
// Convert heap to sorted slice in descending order
|
||||
result := make([]token, k)
|
||||
for i := k - 1; i >= 0; i-- {
|
||||
result[i] = heap.Pop(&h).(token)
|
||||
}
|
||||
|
||||
ts = heap
|
||||
return ts
|
||||
return result
|
||||
}
|
||||
|
||||
// topP limits tokens to those with cumulative probability p
|
||||
@@ -135,61 +101,133 @@ func minP(ts []token, p float32) []token {
|
||||
return ts
|
||||
}
|
||||
|
||||
// TODO(parthsareen): possibly replace with simpler implementation https://github.com/ollama/ollama/issues/9584
|
||||
// sortLogits sorts implementation to sort tokens by logits using counting sort
|
||||
// counting sort is faster than built-in sort for this use case
|
||||
func sortLogits(tokens []token) {
|
||||
if len(tokens) <= 1 {
|
||||
// partialSortLogits uses quickselect to efficiently find and sort the top n tokens
|
||||
func partialSortLogits(ts []token, n int) []token {
|
||||
if n >= len(ts) {
|
||||
n = len(ts)
|
||||
}
|
||||
|
||||
left, right := 0, len(ts)-1
|
||||
target := n - 1
|
||||
|
||||
// Quickselect algorithm to partition array around pivot
|
||||
for left < right {
|
||||
// Choose middle element as pivot and move it to the end
|
||||
pivot := left + (right-left)/2
|
||||
ts[pivot], ts[right] = ts[right], ts[pivot]
|
||||
|
||||
// storeIndex tracks where to put next element greater than pivot
|
||||
storeIndex := left
|
||||
pivotValue := ts[right].value
|
||||
|
||||
// Partition array into elements >= pivot and < pivot
|
||||
// Elements >= pivot go to the left side
|
||||
for i := left; i < right; i++ {
|
||||
if ts[i].value >= pivotValue {
|
||||
ts[storeIndex], ts[i] = ts[i], ts[storeIndex]
|
||||
storeIndex++
|
||||
}
|
||||
}
|
||||
|
||||
// Move pivot to its final position
|
||||
ts[right], ts[storeIndex] = ts[storeIndex], ts[right]
|
||||
|
||||
// If pivot is at target position, we're done
|
||||
// Otherwise recursively partition the half containing target
|
||||
if storeIndex == target {
|
||||
break
|
||||
} else if storeIndex < target {
|
||||
left = storeIndex + 1 // Target is in right half
|
||||
} else {
|
||||
right = storeIndex - 1 // Target is in left half
|
||||
}
|
||||
}
|
||||
|
||||
// Sort just the top n elements in descending order
|
||||
slices.SortFunc(ts[:n], func(a, b token) int {
|
||||
if a.value > b.value {
|
||||
return -1
|
||||
}
|
||||
if a.value < b.value {
|
||||
return 1
|
||||
}
|
||||
return 0
|
||||
})
|
||||
|
||||
return ts[:n]
|
||||
}
|
||||
|
||||
// sortLogits uses partialSortLogits to efficiently sort tokens
|
||||
// It sorts approximately sqrt(len(tokens)) elements which balances
|
||||
// between having enough tokens for sampling while avoiding full sort
|
||||
func sortLogits(ts []token) {
|
||||
// Use sqrt of token length as a heuristic for partial sort size
|
||||
// This provides a good balance between performance and having enough tokens
|
||||
n := int(math.Sqrt(float64(len(ts)))) + 1
|
||||
|
||||
// Ensure we have at least 100 tokens and at most 1000
|
||||
switch {
|
||||
case n < 100:
|
||||
n = 100
|
||||
case n > 1000:
|
||||
n = 1000
|
||||
}
|
||||
|
||||
partialSortLogits(ts, n)
|
||||
}
|
||||
|
||||
func temperature(ts []token, temp float32) {
|
||||
for i := range ts {
|
||||
ts[i].value /= temp
|
||||
}
|
||||
}
|
||||
|
||||
func softmax(ts []token) {
|
||||
if len(ts) == 0 {
|
||||
return
|
||||
}
|
||||
|
||||
// Find max/min in a single pass
|
||||
minLogit, maxLogit := tokens[0].value, tokens[0].value
|
||||
for _, t := range tokens[1:] {
|
||||
if t.value < minLogit {
|
||||
minLogit = t.value
|
||||
} else if t.value > maxLogit {
|
||||
// Find max logit for numerical stability
|
||||
maxLogit := ts[0].value
|
||||
for _, t := range ts {
|
||||
if t.value > maxLogit {
|
||||
maxLogit = t.value
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate scaling to map to uint32 range
|
||||
logitRange := maxLogit - minLogit
|
||||
if logitRange < 1e-6 {
|
||||
return // All values effectively equal
|
||||
// Compute exp(logit - maxLogit) and sum them
|
||||
var sumExp float32
|
||||
for i, t := range ts {
|
||||
expVal := float32(math.Exp(float64(t.value - maxLogit)))
|
||||
ts[i].value = expVal
|
||||
sumExp += expVal
|
||||
}
|
||||
|
||||
// Count frequencies directly from tokens
|
||||
const maxInt = (1 << 24) - 1 // Use 24 bits for good granularity
|
||||
var counts [256]int // For first byte
|
||||
|
||||
// First pass: count frequencies
|
||||
for _, t := range tokens {
|
||||
// Map to [0, maxInt] range
|
||||
score := min(uint32((t.value-minLogit)*float32(maxInt)/logitRange), maxInt)
|
||||
counts[score>>16]++
|
||||
// Normalize probabilities
|
||||
for i := range ts {
|
||||
ts[i].value /= sumExp
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate offsets
|
||||
var offset int
|
||||
for i := range counts {
|
||||
count := counts[i]
|
||||
counts[i] = offset
|
||||
offset += count
|
||||
// applyDist selects a token based on probabilities and seed
|
||||
func dist(ts []token, seed int64) int {
|
||||
rng := rand.New(rand.NewSource(seed))
|
||||
|
||||
cdf := make([]float32, len(ts))
|
||||
var cumSum float32
|
||||
for i, t := range ts {
|
||||
cumSum += t.value
|
||||
cdf[i] = cumSum
|
||||
}
|
||||
|
||||
// Second pass: place elements in correct position
|
||||
output := make([]token, len(tokens))
|
||||
// Track current positions
|
||||
countsCopy := counts
|
||||
|
||||
for i, t := range tokens {
|
||||
score := min(uint32((t.value-minLogit)*float32(maxInt)/logitRange), maxInt)
|
||||
r := rng.Float32() * cumSum
|
||||
|
||||
pos := countsCopy[score>>16]
|
||||
countsCopy[score>>16]++
|
||||
output[len(tokens)-1-pos] = tokens[i]
|
||||
// Select token based on CDF
|
||||
for i, probSum := range cdf {
|
||||
if r < probSum {
|
||||
return i
|
||||
}
|
||||
}
|
||||
|
||||
copy(tokens, output)
|
||||
return len(ts) - 1
|
||||
}
|
||||
+119
-21
@@ -1,39 +1,44 @@
|
||||
package sample
|
||||
|
||||
import (
|
||||
"encoding/binary"
|
||||
"errors"
|
||||
"math"
|
||||
"math/rand/v2"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Helper to convert float64 slice to logit slice
|
||||
func toTokens(values []float64) []token {
|
||||
// Helper to convert float32 slice to logit slice
|
||||
func toTokens(values []float32) []token {
|
||||
tokens := make([]token, len(values))
|
||||
for i, v := range values {
|
||||
tokens[i] = token{
|
||||
id: int32(i),
|
||||
value: float32(v),
|
||||
value: v,
|
||||
}
|
||||
}
|
||||
return tokens
|
||||
}
|
||||
|
||||
// Helper to compare logit slices
|
||||
func compareLogits(t *testing.T, name string, want []float64, got []token) {
|
||||
func compareLogits(t *testing.T, name string, want []float32, got []token) {
|
||||
t.Helper()
|
||||
if len(want) != len(got) {
|
||||
t.Errorf("%s: length mismatch: want %d, got %d", name, len(want), len(got))
|
||||
return
|
||||
}
|
||||
for i := range want {
|
||||
if math.Abs(float64(got[i].value)-want[i]) > 1e-6 {
|
||||
if math.Abs(float64(got[i].value-want[i])) > 1e-6 {
|
||||
t.Errorf("%s: index %d: want %f, got %f", name, i, want[i], got[i].value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestTemperatureAndSoftmax(t *testing.T) {
|
||||
input := []float64{1, 4, -2, 0}
|
||||
input := []float32{1, 4, -2, 0}
|
||||
got := temperature(toTokens(input), 0.5)
|
||||
|
||||
// Check probabilities sum to 1
|
||||
@@ -41,7 +46,7 @@ func TestTemperatureAndSoftmax(t *testing.T) {
|
||||
for _, token := range got {
|
||||
sum += token.value
|
||||
}
|
||||
if math.Abs(float64(sum)-1.0) > 1e-6 {
|
||||
if math.Abs(float64(sum-1.0)) > 1e-6 {
|
||||
t.Errorf("probabilities don't sum to 1: got %f", sum)
|
||||
}
|
||||
|
||||
@@ -51,30 +56,31 @@ func TestTemperatureAndSoftmax(t *testing.T) {
|
||||
for _, token := range got {
|
||||
sum += token.value
|
||||
}
|
||||
if math.Abs(float64(sum)-1.0) > 1e-6 {
|
||||
if math.Abs(float64(sum-1.0)) > 1e-6 {
|
||||
t.Errorf("probabilities don't sum to 1: got %f", sum)
|
||||
}
|
||||
}
|
||||
|
||||
func TestTopK(t *testing.T) {
|
||||
input := []float64{-3, -2, -1, 0, 1, 2, 4}
|
||||
input := []float32{0.026986899, 0.043722924, 0.036774673, 0.27755088, 0.0046718004, 0.08582123, 0.20409796, 0.00412893, 0.15720603, 0.045046154, 0.0030491839, 0.01681367}
|
||||
|
||||
// Test k=3
|
||||
got := topK(toTokens(input), 3)
|
||||
if len(got) != 3 {
|
||||
t.Errorf("topK(3): wrong length: want 3, got %d", len(got))
|
||||
got := topK(toTokens(input), 5)
|
||||
if len(got) != 5 {
|
||||
t.Errorf("topK(5): wrong length: want 5, got %d", len(got))
|
||||
}
|
||||
// Should keep highest 3 values: 4, 2, 1
|
||||
want := []float64{4, 2, 1}
|
||||
// Should keep highest 3 values in descending order
|
||||
want := []float32{0.27755088, 0.20409796, 0.15720603, 0.08582123, 0.045046154}
|
||||
compareLogits(t, "topK(3)", want, got)
|
||||
|
||||
// Test k > len
|
||||
got = topK(toTokens(input), 10)
|
||||
compareLogits(t, "topK(10)", input, got)
|
||||
got = topK(toTokens(input), 20)
|
||||
if len(got) != len(input) {
|
||||
t.Errorf("topK(20): wrong length: want %d, got %d", len(input), len(got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestTopP(t *testing.T) {
|
||||
input := []float64{-3, -2, -1, 0, 1, 2, 4}
|
||||
input := []float32{-3, -2, -1, 0, 1, 2, 4}
|
||||
tokens := toTokens(input)
|
||||
|
||||
// First apply temperature and softmax to get probabilities
|
||||
@@ -92,7 +98,7 @@ func TestTopP(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestMinP(t *testing.T) {
|
||||
input := []float64{-3, -2, -1, 0, 1, 2, 4, 3}
|
||||
input := []float32{-3, -2, -1, 0, 1, 2, 4, 3}
|
||||
tokens := toTokens(input)
|
||||
|
||||
// First apply temperature and softmax
|
||||
@@ -108,7 +114,7 @@ func TestMinP(t *testing.T) {
|
||||
}
|
||||
|
||||
func TestSortLogits(t *testing.T) {
|
||||
input := []float64{3, 1, 4, 2, -1, 0, -2}
|
||||
input := []float32{0.026986899, 0.043722924, 0.036774673, 0.27755088, 0.0046718004, 0.08582123, 0.20409796, 0.00412893, 0.15720603, 0.045046154, 0.0030491839, 0.01681367}
|
||||
tokens := toTokens(input)
|
||||
|
||||
sortLogits(tokens)
|
||||
@@ -120,10 +126,102 @@ func TestSortLogits(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
want := []float64{4, 3, 2, 1, 0, -1, -2}
|
||||
want := []float32{0.27755088, 0.20409796, 0.15720603, 0.08582123, 0.045046154, 0.043722924, 0.036774673, 0.026986899, 0.01681367, 0.0046718004, 0.00412893, 0.0030491839}
|
||||
compareLogits(t, "sortLogits", want, tokens)
|
||||
}
|
||||
|
||||
// TestSortLogitsWithRealData tests sorting behavior using real model logit distributions
|
||||
func TestSortLogitsWithRealData(t *testing.T) {
|
||||
// This will be populated from testdata/logits.bin
|
||||
// Format: 32-bit float array in binary format
|
||||
logits, err := loadTestLogits(t)
|
||||
if err != nil {
|
||||
t.Skipf("Skipping real logit test: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
tokens := toTokens(logits)
|
||||
sortLogits(tokens)
|
||||
|
||||
// Calculate n for verification
|
||||
n := int(math.Sqrt(float64(len(tokens)))) + 1
|
||||
if n > 1000 {
|
||||
n = 1000
|
||||
} else if n < 100 {
|
||||
n = 100
|
||||
}
|
||||
|
||||
t.Logf("Testing with %d tokens, partial sorting top %d", len(tokens), n)
|
||||
|
||||
// Only verify the top n elements are sorted (which is what we guarantee)
|
||||
// This is much faster than checking the entire array
|
||||
topN := tokens[:n]
|
||||
for i := 1; i < len(topN); i++ {
|
||||
if topN[i].value > topN[i-1].value {
|
||||
t.Fatalf("top %d tokens not properly sorted at index %d: %.15f > %.15f",
|
||||
n, i, topN[i].value, topN[i-1].value)
|
||||
}
|
||||
}
|
||||
|
||||
// Verify we didn't lose any high value tokens by checking that
|
||||
// all tokens after position n are <= the nth token
|
||||
// Do this in chunks to avoid timeouts on large arrays
|
||||
nthValue := tokens[n-1].value
|
||||
const chunkSize = 1000
|
||||
|
||||
for start := n; start < len(tokens); start += chunkSize {
|
||||
end := min(start+chunkSize, len(tokens))
|
||||
for i := start; i < end; i++ {
|
||||
if tokens[i].value > nthValue {
|
||||
t.Fatalf("found higher value token after position %d: tokens[%d].value = %.15f > %.15f",
|
||||
n, i, tokens[i].value, nthValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// loadTestLogits loads logit test data from testdata/logits.bin
|
||||
func loadTestLogits(t *testing.T) ([]float32, error) {
|
||||
t.Helper()
|
||||
|
||||
_, currFile, _, ok := runtime.Caller(0)
|
||||
if !ok {
|
||||
return nil, errors.New("could not determine test file path")
|
||||
}
|
||||
testDataPath := filepath.Join(filepath.Dir(currFile), "testdata", "logits.bin")
|
||||
|
||||
file, err := os.Open(testDataPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
stat, err := file.Stat()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
numFloats := stat.Size() / 4 // each float32 is 4 bytes
|
||||
if numFloats*4 != stat.Size() {
|
||||
return nil, errors.New("logits.bin has invalid size: not a multiple of 4 bytes")
|
||||
}
|
||||
|
||||
logits := make([]float32, numFloats)
|
||||
for i := range logits {
|
||||
var val uint32
|
||||
if err := binary.Read(file, binary.LittleEndian, &val); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
logits[i] = math.Float32frombits(val)
|
||||
}
|
||||
|
||||
if len(logits) == 0 {
|
||||
return nil, errors.New("logits.bin is empty")
|
||||
}
|
||||
|
||||
return logits, nil
|
||||
}
|
||||
|
||||
func BenchmarkTransforms(b *testing.B) {
|
||||
// Generate random logits
|
||||
tokens := make([]token, 1<<16)
|
||||
|
||||
Reference in new issue
Block a user