Compare commits
8 Commits
v0.6.0-rc0
...
parth/samp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
a5d638dfe7 | ||
|
|
4aeb67ef4c | ||
|
|
3ba91634c1 | ||
|
|
1b7433b71e | ||
|
|
a70820daa0 | ||
|
|
6b45b1d6b4 | ||
|
|
b3af953a55 | ||
|
|
ad4e0bf3be |
@@ -54,6 +54,10 @@ Here are some example models that can be downloaded:
|
|||||||
|
|
||||||
| Model | Parameters | Size | Download |
|
| Model | Parameters | Size | Download |
|
||||||
| ------------------ | ---------- | ----- | -------------------------------- |
|
| ------------------ | ---------- | ----- | -------------------------------- |
|
||||||
|
| Gemma 3 | 1B | 815MB | `ollama run gemma3:1b` |
|
||||||
|
| Gemma 3 | 4B | 3.3GB | `ollama run gemma3` |
|
||||||
|
| Gemma 3 | 12B | 8.1GB | `ollama run gemma3:12b` |
|
||||||
|
| Gemma 3 | 27B | 17GB | `ollama run gemma3:27b` |
|
||||||
| QwQ | 32B | 20GB | `ollama run qwq` |
|
| QwQ | 32B | 20GB | `ollama run qwq` |
|
||||||
| DeepSeek-R1 | 7B | 4.7GB | `ollama run deepseek-r1` |
|
| DeepSeek-R1 | 7B | 4.7GB | `ollama run deepseek-r1` |
|
||||||
| DeepSeek-R1 | 671B | 404GB | `ollama run deepseek-r1:671b` |
|
| DeepSeek-R1 | 671B | 404GB | `ollama run deepseek-r1:671b` |
|
||||||
@@ -66,9 +70,6 @@ Here are some example models that can be downloaded:
|
|||||||
| Llama 3.1 | 405B | 231GB | `ollama run llama3.1:405b` |
|
| Llama 3.1 | 405B | 231GB | `ollama run llama3.1:405b` |
|
||||||
| Phi 4 | 14B | 9.1GB | `ollama run phi4` |
|
| Phi 4 | 14B | 9.1GB | `ollama run phi4` |
|
||||||
| Phi 4 Mini | 3.8B | 2.5GB | `ollama run phi4-mini` |
|
| Phi 4 Mini | 3.8B | 2.5GB | `ollama run phi4-mini` |
|
||||||
| Gemma 2 | 2B | 1.6GB | `ollama run gemma2:2b` |
|
|
||||||
| Gemma 2 | 9B | 5.5GB | `ollama run gemma2` |
|
|
||||||
| Gemma 2 | 27B | 16GB | `ollama run gemma2:27b` |
|
|
||||||
| Mistral | 7B | 4.1GB | `ollama run mistral` |
|
| Mistral | 7B | 4.1GB | `ollama run mistral` |
|
||||||
| Moondream 2 | 1.4B | 829MB | `ollama run moondream` |
|
| Moondream 2 | 1.4B | 829MB | `ollama run moondream` |
|
||||||
| Neural Chat | 7B | 4.1GB | `ollama run neural-chat` |
|
| Neural Chat | 7B | 4.1GB | `ollama run neural-chat` |
|
||||||
|
|||||||
@@ -195,6 +195,10 @@ func generateInteractive(cmd *cobra.Command, opts runOptions) error {
|
|||||||
opts.Messages = []api.Message{}
|
opts.Messages = []api.Message{}
|
||||||
fmt.Printf("Loading model '%s'\n", opts.Model)
|
fmt.Printf("Loading model '%s'\n", opts.Model)
|
||||||
if err := loadOrUnloadModel(cmd, &opts); err != nil {
|
if err := loadOrUnloadModel(cmd, &opts); err != nil {
|
||||||
|
if strings.Contains(err.Error(), "not found") {
|
||||||
|
fmt.Printf("error: %v\n", err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
continue
|
continue
|
||||||
|
|||||||
@@ -15,7 +15,6 @@ type TextOptions struct {
|
|||||||
attnKeyLen, attnValLen int
|
attnKeyLen, attnValLen int
|
||||||
eps, ropeScale float32
|
eps, ropeScale float32
|
||||||
ropeLocalBase, ropeGlobalBase float32
|
ropeLocalBase, ropeGlobalBase float32
|
||||||
finalLogitSoftcap float32
|
|
||||||
largeModelScaling bool
|
largeModelScaling bool
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -57,16 +56,15 @@ func newTextModel(c ml.Config) *TextModel {
|
|||||||
),
|
),
|
||||||
Layers: make([]TextLayer, numBlocks),
|
Layers: make([]TextLayer, numBlocks),
|
||||||
TextOptions: &TextOptions{
|
TextOptions: &TextOptions{
|
||||||
hiddenSize: int(c.Uint("embedding_length")),
|
hiddenSize: int(c.Uint("embedding_length")),
|
||||||
numHeads: int(c.Uint("attention.head_count")),
|
numHeads: int(c.Uint("attention.head_count")),
|
||||||
numKVHeads: int(c.Uint("attention.head_count_kv")),
|
numKVHeads: int(c.Uint("attention.head_count_kv")),
|
||||||
attnKeyLen: int(c.Uint("attention.key_length", 256)),
|
attnKeyLen: int(c.Uint("attention.key_length", 256)),
|
||||||
attnValLen: int(c.Uint("attention.value_length", 256)),
|
attnValLen: int(c.Uint("attention.value_length", 256)),
|
||||||
eps: c.Float("attention.layer_norm_rms_epsilon", 1e-06),
|
eps: c.Float("attention.layer_norm_rms_epsilon", 1e-06),
|
||||||
ropeLocalBase: c.Float("rope.local.freq_base", 10000.0),
|
ropeLocalBase: c.Float("rope.local.freq_base", 10000.0),
|
||||||
ropeGlobalBase: c.Float("rope.global.freq_base", 1000000.0),
|
ropeGlobalBase: c.Float("rope.global.freq_base", 1000000.0),
|
||||||
ropeScale: c.Float("rope.freq_scale", 1.0),
|
ropeScale: c.Float("rope.freq_scale", 1.0),
|
||||||
finalLogitSoftcap: c.Float("final_logit_softcapping", 30.0),
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -245,10 +243,5 @@ func (m *TextModel) Forward(ctx ml.Context, inputs, positions, outputs ml.Tensor
|
|||||||
}
|
}
|
||||||
|
|
||||||
hiddenState = m.OutputNorm.Forward(ctx, hiddenState, m.eps)
|
hiddenState = m.OutputNorm.Forward(ctx, hiddenState, m.eps)
|
||||||
hiddenState = m.Output.Forward(ctx, hiddenState)
|
return m.Output.Forward(ctx, hiddenState)
|
||||||
|
|
||||||
// final logit softcap
|
|
||||||
hiddenState = hiddenState.Scale(ctx, 1.0/float64(m.TextOptions.finalLogitSoftcap))
|
|
||||||
hiddenState = hiddenState.Tanh(ctx)
|
|
||||||
return hiddenState.Scale(ctx, float64(m.TextOptions.finalLogitSoftcap))
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -116,19 +116,9 @@ func (i *Instance) Readline() (string, error) {
|
|||||||
|
|
||||||
switch r {
|
switch r {
|
||||||
case KeyUp:
|
case KeyUp:
|
||||||
if i.History.Pos > 0 {
|
i.historyPrev(buf, ¤tLineBuf)
|
||||||
if i.History.Pos == i.History.Size() {
|
|
||||||
currentLineBuf = []rune(buf.String())
|
|
||||||
}
|
|
||||||
buf.Replace([]rune(i.History.Prev()))
|
|
||||||
}
|
|
||||||
case KeyDown:
|
case KeyDown:
|
||||||
if i.History.Pos < i.History.Size() {
|
i.historyNext(buf, ¤tLineBuf)
|
||||||
buf.Replace([]rune(i.History.Next()))
|
|
||||||
if i.History.Pos == i.History.Size() {
|
|
||||||
buf.Replace(currentLineBuf)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
case KeyLeft:
|
case KeyLeft:
|
||||||
buf.MoveLeft()
|
buf.MoveLeft()
|
||||||
case KeyRight:
|
case KeyRight:
|
||||||
@@ -185,6 +175,10 @@ func (i *Instance) Readline() (string, error) {
|
|||||||
esc = true
|
esc = true
|
||||||
case CharInterrupt:
|
case CharInterrupt:
|
||||||
return "", ErrInterrupt
|
return "", ErrInterrupt
|
||||||
|
case CharPrev:
|
||||||
|
i.historyPrev(buf, ¤tLineBuf)
|
||||||
|
case CharNext:
|
||||||
|
i.historyNext(buf, ¤tLineBuf)
|
||||||
case CharLineStart:
|
case CharLineStart:
|
||||||
buf.MoveToStart()
|
buf.MoveToStart()
|
||||||
case CharLineEnd:
|
case CharLineEnd:
|
||||||
@@ -246,6 +240,24 @@ func (i *Instance) HistoryDisable() {
|
|||||||
i.History.Enabled = false
|
i.History.Enabled = false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (i *Instance) historyPrev(buf *Buffer, currentLineBuf *[]rune) {
|
||||||
|
if i.History.Pos > 0 {
|
||||||
|
if i.History.Pos == i.History.Size() {
|
||||||
|
*currentLineBuf = []rune(buf.String())
|
||||||
|
}
|
||||||
|
buf.Replace([]rune(i.History.Prev()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i *Instance) historyNext(buf *Buffer, currentLineBuf *[]rune) {
|
||||||
|
if i.History.Pos < i.History.Size() {
|
||||||
|
buf.Replace([]rune(i.History.Next()))
|
||||||
|
if i.History.Pos == i.History.Size() {
|
||||||
|
buf.Replace(*currentLineBuf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func NewTerminal() (*Terminal, error) {
|
func NewTerminal() (*Terminal, error) {
|
||||||
fd := os.Stdin.Fd()
|
fd := os.Stdin.Fd()
|
||||||
termios, err := SetRawMode(fd)
|
termios, err := SetRawMode(fd)
|
||||||
|
|||||||
@@ -1,11 +1,10 @@
|
|||||||
package sample
|
package sample
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
|
||||||
"math"
|
"math"
|
||||||
"math/rand/v2"
|
"math/rand"
|
||||||
"slices"
|
|
||||||
"sync"
|
"sync"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/ollama/ollama/llama"
|
"github.com/ollama/ollama/llama"
|
||||||
)
|
)
|
||||||
@@ -84,59 +83,56 @@ func (s *Sampler) sample(tokens []token) (token, error) {
|
|||||||
return greedy(tokens), nil
|
return greedy(tokens), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
if s.topK > 0 {
|
// topK also sorts the tokens in descending order of logits
|
||||||
tokens = topK(tokens, s.topK)
|
tokens = topK(tokens, s.topK)
|
||||||
} else {
|
|
||||||
sortLogits(tokens)
|
|
||||||
}
|
|
||||||
|
|
||||||
// token logit values are updated to probabilities
|
|
||||||
tokens = temperature(tokens, s.temperature)
|
|
||||||
|
|
||||||
tokens = topP(tokens, s.topP)
|
tokens = topP(tokens, s.topP)
|
||||||
tokens = minP(tokens, s.minP)
|
tokens = minP(tokens, s.minP)
|
||||||
|
|
||||||
// TODO: this should fall back to greedy sampling
|
// token logit values are updated to probabilities
|
||||||
// or topP, topK values etc should be such that
|
temperature(tokens, s.temperature)
|
||||||
// there are always tokens to sample from
|
softmax(tokens)
|
||||||
if len(tokens) == 0 {
|
return tokens[dist(tokens, s.rng.Int63())], nil
|
||||||
return token{}, errors.New("no tokens to sample from")
|
|
||||||
}
|
|
||||||
|
|
||||||
var r float32
|
// // TODO: this should fall back to greedy sampling
|
||||||
if s.rng != nil {
|
// // or topP, topK values etc should be such that
|
||||||
r = s.rng.Float32()
|
// // there are always tokens to sample from
|
||||||
} else {
|
// if len(tokens) == 0 {
|
||||||
r = rand.Float32()
|
// return token{}, errors.New("no tokens to sample from")
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Calculate cumulative sum of probabilities
|
// var r float32
|
||||||
var sum float32
|
// if s.rng != nil {
|
||||||
for i := range tokens {
|
// r = s.rng.Float32()
|
||||||
sum += tokens[i].value
|
// } else {
|
||||||
tokens[i].value = sum
|
// r = rand.Float32()
|
||||||
}
|
// }
|
||||||
r *= tokens[len(tokens)-1].value
|
|
||||||
|
|
||||||
idx, _ := slices.BinarySearchFunc(tokens, r, func(token token, target float32) int {
|
// // Calculate cumulative sum of probabilities
|
||||||
if token.value < target {
|
// var sum float32
|
||||||
return -1
|
// for i := range tokens {
|
||||||
}
|
// sum += tokens[i].value
|
||||||
return 1
|
// 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
|
// 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 {
|
func NewSampler(temperature float32, topK int, topP float32, minP float32, seed int, grammar *Grammar) Sampler {
|
||||||
var rng *rand.Rand
|
var rng *rand.Rand
|
||||||
if seed != -1 {
|
if seed != -1 {
|
||||||
// PCG requires two parameters: sequence and stream
|
rng = rand.New(rand.NewSource(int64(seed)))
|
||||||
// Use original seed for sequence
|
} else {
|
||||||
sequence := uint64(seed)
|
rng = rand.New(rand.NewSource(time.Now().UnixNano()))
|
||||||
// Use golden ratio hash to generate statistically independent seeds
|
|
||||||
rng = rand.New(rand.NewPCG(sequence, sequence^0x9E3779B9))
|
|
||||||
}
|
}
|
||||||
if temperature < 0.0 {
|
if temperature < 0.0 {
|
||||||
temperature = 0.0
|
temperature = 0.0
|
||||||
|
|||||||
1
sample/testdata/logits.bin
vendored
Normal file
1
sample/testdata/logits.bin
vendored
Normal file
File diff suppressed because one or more lines are too long
@@ -1,92 +1,67 @@
|
|||||||
package sample
|
package sample
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"container/heap"
|
||||||
"math"
|
"math"
|
||||||
|
"math/rand"
|
||||||
"slices"
|
"slices"
|
||||||
)
|
)
|
||||||
|
|
||||||
// temperature applies scaling and softmax to the logits
|
// tokenHeap implements heap.Interface and holds tokens as a min-heap to track k largest elements
|
||||||
func temperature(ts []token, temp float32) []token {
|
type tokenHeap []token
|
||||||
// Find max logit for numerical stability
|
|
||||||
maxLogit := float32(math.Inf(-1))
|
|
||||||
for _, t := range ts {
|
|
||||||
if t.value > maxLogit {
|
|
||||||
maxLogit = t.value
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Apply temperature and compute exp(x - max)
|
func (h tokenHeap) Len() int { return len(h) }
|
||||||
temp = max(temp, 1e-7)
|
func (h tokenHeap) Less(i, j int) bool { return h[i].value < h[j].value }
|
||||||
var sum float32
|
func (h tokenHeap) Swap(i, j int) { h[i], h[j] = h[j], h[i] }
|
||||||
for i, v := range ts {
|
|
||||||
ts[i].value = float32(math.Exp(float64((v.value - maxLogit) / temp)))
|
|
||||||
sum += ts[i].value
|
|
||||||
}
|
|
||||||
|
|
||||||
// Normalize
|
func (h *tokenHeap) Push(x any) {
|
||||||
for i := range ts {
|
*h = append(*h, x.(token))
|
||||||
ts[i].value /= sum
|
|
||||||
}
|
|
||||||
|
|
||||||
return ts
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// siftDown maintains a min-heap property by recursively moving larger elements down the heap.
|
func (h *tokenHeap) Pop() any {
|
||||||
//
|
old := *h
|
||||||
// The heap is represented as an array where for any node at index i:
|
n := len(old)
|
||||||
// - Left child is at index 2i + 1
|
x := old[n-1]
|
||||||
// - Right child is at index 2i + 2
|
*h = old[0 : n-1]
|
||||||
// - Parent is at index (i-1)/2
|
return x
|
||||||
//
|
|
||||||
// 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
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// topK limits the number of tokens considered to the k highest logits
|
// topK limits the number of tokens considered to the k highest logits
|
||||||
func topK(ts []token, k int) []token {
|
func topK(ts []token, k int) []token {
|
||||||
if k >= len(ts) {
|
if k >= len(ts) || k <= 0 {
|
||||||
|
slices.SortFunc(ts, func(a, b token) int {
|
||||||
|
switch {
|
||||||
|
case a.value < b.value:
|
||||||
|
return 1
|
||||||
|
case a.value > b.value:
|
||||||
|
return -1
|
||||||
|
default:
|
||||||
|
return 0
|
||||||
|
}
|
||||||
|
})
|
||||||
return 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++ {
|
for i := k; i < len(ts); i++ {
|
||||||
if ts[i].value > heap[0].value {
|
if ts[i].value > h[0].value {
|
||||||
heap[0] = ts[i]
|
heap.Pop(&h)
|
||||||
siftDown(heap, 0, k)
|
heap.Push(&h, ts[i])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
slices.Reverse(heap)
|
// Convert heap to sorted slice in descending order
|
||||||
|
result := make([]token, len(h))
|
||||||
|
for i := k - 1; i >= 0; i-- {
|
||||||
|
result[i] = heap.Pop(&h).(token)
|
||||||
|
}
|
||||||
|
|
||||||
ts = heap
|
return result
|
||||||
return ts
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// topP limits tokens to those with cumulative probability p
|
// topP limits tokens to those with cumulative probability p
|
||||||
@@ -135,61 +110,58 @@ func minP(ts []token, p float32) []token {
|
|||||||
return ts
|
return ts
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO(parthsareen): possibly replace with simpler implementation https://github.com/ollama/ollama/issues/9584
|
func temperature(ts []token, temp float32) {
|
||||||
// sortLogits sorts implementation to sort tokens by logits using counting sort
|
for i := range ts {
|
||||||
// counting sort is faster than built-in sort for this use case
|
ts[i].value /= temp
|
||||||
func sortLogits(tokens []token) {
|
}
|
||||||
if len(tokens) <= 1 {
|
}
|
||||||
|
|
||||||
|
func softmax(ts []token) {
|
||||||
|
if len(ts) == 0 {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Find max/min in a single pass
|
// Find max logit for numerical stability
|
||||||
minLogit, maxLogit := tokens[0].value, tokens[0].value
|
maxLogit := ts[0].value
|
||||||
for _, t := range tokens[1:] {
|
for _, t := range ts {
|
||||||
if t.value < minLogit {
|
if t.value > maxLogit {
|
||||||
minLogit = t.value
|
|
||||||
} else if t.value > maxLogit {
|
|
||||||
maxLogit = t.value
|
maxLogit = t.value
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate scaling to map to uint32 range
|
// Compute exp(logit - maxLogit) and sum them
|
||||||
logitRange := maxLogit - minLogit
|
var sumExp float32
|
||||||
if logitRange < 1e-6 {
|
for i, t := range ts {
|
||||||
return // All values effectively equal
|
expVal := float32(math.Exp(float64(t.value - maxLogit)))
|
||||||
|
ts[i].value = expVal
|
||||||
|
sumExp += expVal
|
||||||
}
|
}
|
||||||
|
|
||||||
// Count frequencies directly from tokens
|
// Normalize probabilities
|
||||||
const maxInt = (1 << 24) - 1 // Use 24 bits for good granularity
|
for i := range ts {
|
||||||
var counts [256]int // For first byte
|
ts[i].value /= sumExp
|
||||||
|
|
||||||
// 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]++
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
// Calculate offsets
|
|
||||||
var offset int
|
// applyDist selects a token based on probabilities and seed
|
||||||
for i := range counts {
|
func dist(ts []token, seed int64) int {
|
||||||
count := counts[i]
|
rng := rand.New(rand.NewSource(seed))
|
||||||
counts[i] = offset
|
|
||||||
offset += count
|
cdf := make([]float32, len(ts))
|
||||||
}
|
var cumSum float32
|
||||||
|
for i, t := range ts {
|
||||||
// Second pass: place elements in correct position
|
cumSum += t.value
|
||||||
output := make([]token, len(tokens))
|
cdf[i] = cumSum
|
||||||
// Track current positions
|
}
|
||||||
countsCopy := counts
|
|
||||||
|
r := rng.Float32() * cumSum
|
||||||
for i, t := range tokens {
|
|
||||||
score := min(uint32((t.value-minLogit)*float32(maxInt)/logitRange), maxInt)
|
// Select token based on CDF
|
||||||
|
for i, probSum := range cdf {
|
||||||
pos := countsCopy[score>>16]
|
if r < probSum {
|
||||||
countsCopy[score>>16]++
|
return i
|
||||||
output[len(tokens)-1-pos] = tokens[i]
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
copy(tokens, output)
|
return len(ts) - 1
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +1,44 @@
|
|||||||
package sample
|
package sample
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
"math"
|
"math"
|
||||||
"math/rand/v2"
|
"math/rand/v2"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
"testing"
|
"testing"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Helper to convert float64 slice to logit slice
|
// Helper to convert float32 slice to logit slice
|
||||||
func toTokens(values []float64) []token {
|
func toTokens(values []float32) []token {
|
||||||
tokens := make([]token, len(values))
|
tokens := make([]token, len(values))
|
||||||
for i, v := range values {
|
for i, v := range values {
|
||||||
tokens[i] = token{
|
tokens[i] = token{
|
||||||
id: int32(i),
|
id: int32(i),
|
||||||
value: float32(v),
|
value: v,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return tokens
|
return tokens
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper to compare logit slices
|
// 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()
|
t.Helper()
|
||||||
if len(want) != len(got) {
|
if len(want) != len(got) {
|
||||||
t.Errorf("%s: length mismatch: want %d, got %d", name, len(want), len(got))
|
t.Errorf("%s: length mismatch: want %d, got %d", name, len(want), len(got))
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
for i := range want {
|
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)
|
t.Errorf("%s: index %d: want %f, got %f", name, i, want[i], got[i].value)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTemperatureAndSoftmax(t *testing.T) {
|
func TestTemperatureAndSoftmax(t *testing.T) {
|
||||||
input := []float64{1, 4, -2, 0}
|
input := []float32{1, 4, -2, 0}
|
||||||
got := temperature(toTokens(input), 0.5)
|
got := temperature(toTokens(input), 0.5)
|
||||||
|
|
||||||
// Check probabilities sum to 1
|
// Check probabilities sum to 1
|
||||||
@@ -41,7 +46,7 @@ func TestTemperatureAndSoftmax(t *testing.T) {
|
|||||||
for _, token := range got {
|
for _, token := range got {
|
||||||
sum += token.value
|
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)
|
t.Errorf("probabilities don't sum to 1: got %f", sum)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -51,35 +56,54 @@ func TestTemperatureAndSoftmax(t *testing.T) {
|
|||||||
for _, token := range got {
|
for _, token := range got {
|
||||||
sum += token.value
|
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)
|
t.Errorf("probabilities don't sum to 1: got %f", sum)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTopK(t *testing.T) {
|
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
|
// Test k=5
|
||||||
got := topK(toTokens(input), 3)
|
got := topK(toTokens(input), 5)
|
||||||
if len(got) != 3 {
|
if len(got) != 5 {
|
||||||
t.Errorf("topK(3): wrong length: want 3, got %d", len(got))
|
t.Errorf("topK(5): wrong length: want 5, got %d", len(got))
|
||||||
}
|
}
|
||||||
// Should keep highest 3 values: 4, 2, 1
|
// Should keep highest 3 values in descending order
|
||||||
want := []float64{4, 2, 1}
|
want := []float32{0.27755088, 0.20409796, 0.15720603, 0.08582123, 0.045046154}
|
||||||
compareLogits(t, "topK(3)", want, got)
|
compareLogits(t, "topK(3)", want, got)
|
||||||
|
|
||||||
// Test k > len
|
got = topK(toTokens(input), 20)
|
||||||
got = topK(toTokens(input), 10)
|
if len(got) != len(input) {
|
||||||
compareLogits(t, "topK(10)", input, got)
|
t.Errorf("topK(20): wrong length: want %d, got %d", len(input), len(got))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test k=-1
|
||||||
|
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}
|
||||||
|
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}
|
||||||
|
got = topK(toTokens(input), -1)
|
||||||
|
if len(got) != len(input) {
|
||||||
|
t.Errorf("topK(-1): wrong length: want %d, got %d", len(input), len(got))
|
||||||
|
}
|
||||||
|
compareLogits(t, "topK(-1)", want, got)
|
||||||
|
|
||||||
|
// Test k=0
|
||||||
|
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}
|
||||||
|
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}
|
||||||
|
got = topK(toTokens(input), 0)
|
||||||
|
if len(got) != len(input) {
|
||||||
|
t.Errorf("topK(-1): wrong length: want %d, got %d", len(input), len(got))
|
||||||
|
}
|
||||||
|
compareLogits(t, "topK(-1)", want, got)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestTopP(t *testing.T) {
|
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)
|
tokens := toTokens(input)
|
||||||
|
|
||||||
// First apply temperature and softmax to get probabilities
|
// First apply temperature and softmax to get probabilities
|
||||||
tokens = temperature(tokens, 1)
|
tokens = temperature(tokens, 1)
|
||||||
sortLogits(tokens)
|
tokens = topK(tokens, 20)
|
||||||
|
|
||||||
// Then apply topP
|
// Then apply topP
|
||||||
got := topP(tokens, 0.95)
|
got := topP(tokens, 0.95)
|
||||||
@@ -92,7 +116,7 @@ func TestTopP(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestMinP(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)
|
tokens := toTokens(input)
|
||||||
|
|
||||||
// First apply temperature and softmax
|
// First apply temperature and softmax
|
||||||
@@ -108,10 +132,10 @@ func TestMinP(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestSortLogits(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)
|
tokens := toTokens(input)
|
||||||
|
|
||||||
sortLogits(tokens)
|
tokens = topK(tokens, 20)
|
||||||
|
|
||||||
for i := 1; i < len(tokens); i++ {
|
for i := 1; i < len(tokens); i++ {
|
||||||
if tokens[i].value > tokens[i-1].value {
|
if tokens[i].value > tokens[i-1].value {
|
||||||
@@ -120,10 +144,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)
|
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) {
|
func BenchmarkTransforms(b *testing.B) {
|
||||||
// Generate random logits
|
// Generate random logits
|
||||||
tokens := make([]token, 1<<16)
|
tokens := make([]token, 1<<16)
|
||||||
@@ -172,7 +288,7 @@ func BenchmarkTransforms(b *testing.B) {
|
|||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for b.Loop() {
|
for b.Loop() {
|
||||||
copy(tokensCopy, tokens)
|
copy(tokensCopy, tokens)
|
||||||
sortLogits(tokensCopy)
|
topK(tokensCopy, 200000)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user