-
Notifications
You must be signed in to change notification settings - Fork 419
Expand file tree
/
Copy pathKVCache.swift
More file actions
113 lines (92 loc) · 3.4 KB
/
Copy pathKVCache.swift
File metadata and controls
113 lines (92 loc) · 3.4 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
// Copyright © 2024 Apple Inc.
import Foundation
import MLX
/// Interface for Key/Value cache for LLMs.
///
/// See ``LanguageModel/newCache(parameters:)``
public protocol KVCache: Evaluatable {
/// get the current offset
var offset: Int { get }
func update(keys: MLXArray, values: MLXArray) -> (MLXArray, MLXArray)
func isTrimmable() -> Bool
func trim(n: Int) -> Int
}
func createAdditiveCausalMask(n: Int, offset: Int) -> MLXArray {
let rinds = MLXArray(Int32(0) ..< Int32(offset + n))
let linds = offset != 0 ? MLXArray(Int32(offset) ..< Int32(offset + n)) : rinds
let mask = linds[0..., .newAxis] .< rinds[.newAxis]
return mask * Float32(-1e9)
}
/// create an attention mask using the parameters from the KVCache.
///
/// See also ``MultiHeadAttention/createAdditiveCausalMask(_:dtype:)`` -- same idea
/// but doesn't honor the cache offset.
public func createAttentionMask(h: MLXArray, cache: [KVCache]?) -> MLXArray? {
let t = h.dim(1)
if t > 1 {
var offset = 0
if let c = cache?.first {
offset = c.offset
}
return createAdditiveCausalMask(n: t, offset: offset)
.asType(h.dtype)
}
return nil
}
/// See https://github.com/ml-explore/mlx-examples/blob/main/llms/mlx_lm/models/base.py#L11
public class KVCacheSimple: KVCache, Evaluatable {
var keys: MLXArray?
var values: MLXArray?
public var offset = 0
var step = 256
public init() {}
public func innerState() -> [MLXArray] {
[self.keys, self.values].compactMap { $0 }
}
public func update(keys: MLXArray, values: MLXArray) -> (MLXArray, MLXArray) {
let previous = self.offset
let reset =
if let currentKeys = self.keys, (previous + keys.dim(2)) > currentKeys.dim(2) {
true
} else {
self.keys == nil
}
if reset {
let B = keys.dim(0)
let kvHeads = keys.dim(1)
let kHeadDim = keys.dim(3)
let vHeadDim = values.dim(3)
let nSteps = (step + keys.dim(2) - 1) / step
let kShape = [B, kvHeads, nSteps * step, kHeadDim]
let vShape = [B, kvHeads, nSteps * step, vHeadDim]
let newK = MLXArray.zeros(kShape, dtype: keys.dtype)
let newV = MLXArray.zeros(vShape, dtype: values.dtype)
if var currentKeys = self.keys, var currentValues = self.values {
if previous % step != 0 {
currentKeys = currentKeys[.ellipsis, ..<previous, 0...]
currentValues = currentValues[.ellipsis, ..<previous, 0...]
}
self.keys = concatenated([currentKeys, newK], axis: 2)
self.values = concatenated([currentValues, newV], axis: 2)
} else {
self.keys = newK
self.values = newV
}
}
self.offset += keys.dim(2)
self.keys?[.ellipsis, previous ..< self.offset, 0...] = keys
self.values?[.ellipsis, previous ..< self.offset, 0...] = values
return (
self.keys![.ellipsis, ..<self.offset, 0...],
self.values![.ellipsis, ..<self.offset, 0...]
)
}
public func isTrimmable() -> Bool {
return true
}
public func trim(n: Int) -> Int {
let toTrim = min(self.offset, n)
self.offset -= toTrim
return toTrim
}
}