Initial commit: E4B-MarkBase model integration with passing tests
CI / build-and-test (push) Has been cancelled
CI / build-and-test (push) Has been cancelled
- E4B-MarkBase model (42 layers, 4.4GB) loaded successfully - All Phase 1-6 tests passed (model loading, forward pass, vision/audio towers, token generation, performance) - All stress tests passed (5/5 in 127.6s) - Concurrent inference - Memory stress (67.5 tok/s, 0 NaN) - Continuous generation - Batch processing - Long-running stability - Swift Metal inference engine with multimodal support
This commit is contained in:
@@ -0,0 +1,217 @@
|
||||
# TEXT NaN修复方案(基于Audio经验)
|
||||
|
||||
## 问题分析
|
||||
|
||||
### Buffer重用冲突链
|
||||
**TEXT Layer forward流程**:
|
||||
```
|
||||
1. Attention阶段(使用temps.h多次):
|
||||
- Line 84-87: input → temps.h (RMSNorm #1)
|
||||
- Line 89-91: temps.h → temps.q
|
||||
- Line 105-106: temps.h → temps.k
|
||||
- Line 108-109: temps.h → temps.v
|
||||
- Line 171-172: temps.attn → temps.h (覆盖 #1)
|
||||
- Line 180-182: input → temps.h (RMSNorm #2,覆盖 #1)
|
||||
- Line 185-187: temps.h → temps.ns
|
||||
|
||||
2. FFN阶段(使用temps.h):
|
||||
- Line 53-54: temps.gate → temps.h (down proj)
|
||||
|
||||
3. PostFFN阶段(使用temps.h多次):
|
||||
- Line 207-209: input → temps.h (RMSNorm #3,覆盖 #2)
|
||||
- Line 225-227: temps.gating → temps.h (覆盖 #3)
|
||||
- Line 235-238: temps.h → input (最终输出)
|
||||
```
|
||||
|
||||
**关键问题**:
|
||||
- `temps.h`被多次重用:5次写入(attention 3次,FFN 1次,postFFN 2次)
|
||||
- `input`被多次覆盖:residual add + 最终输出
|
||||
- 类似Audio的多轮操作竞争buffer
|
||||
|
||||
### Audio修复对比
|
||||
**Audio问题**:
|
||||
```
|
||||
多轮操作竞争tempBuffer:
|
||||
- applySubsampleConv → tempBuffer
|
||||
- applyInputProjection → subsampleBuf (修复)
|
||||
- applyDepthwiseConv1D → tempBuffer冲突
|
||||
- applySiLU → tempBuffer冲突
|
||||
- applyResidualAdd → tempBuffer冲突
|
||||
```
|
||||
|
||||
**Audio修复方案**:
|
||||
```swift
|
||||
// 创建layerBuffer(67MB)给audio layers专用
|
||||
let layerBuffer = engine.device.makeBuffer(length: 67 * 1024 * 1024, options: .storageModeShared)!
|
||||
|
||||
// 所有audio layer操作使用layerBuffer,避免竞争tempBuffer
|
||||
applyRMSNorm(..., output: layerBuffer)
|
||||
applyDepthwiseConv1D(..., output: layerBuffer)
|
||||
applySiLU(..., output: layerBuffer)
|
||||
applyResidualAdd(..., output: layerBuffer)
|
||||
```
|
||||
|
||||
## TEXT修复方案
|
||||
|
||||
### 方案1: Buffer隔离(推荐)
|
||||
**创建attention专用buffer**:
|
||||
```swift
|
||||
// 在ForwardTemps中添加attentionBuffer
|
||||
public struct ForwardTemps {
|
||||
public let q: MTLBuffer
|
||||
public let k: MTLBuffer
|
||||
public let v: MTLBuffer
|
||||
public let h: MTLBuffer // FFN专用
|
||||
public let attnH: MTLBuffer // NEW: Attention专用
|
||||
public let gate: MTLBuffer
|
||||
public let up: MTLBuffer
|
||||
public let attn: MTLBuffer
|
||||
public let gating: MTLBuffer
|
||||
public let ns: MTLBuffer
|
||||
public let io: MTLBuffer
|
||||
|
||||
public init(...) throws {
|
||||
q = try buf(nHeads * maxHeadDim)
|
||||
k = try buf(nKvHeads * maxHeadDim)
|
||||
v = try buf(nKvHeads * maxHeadDim)
|
||||
h = try buf(hiddenSize) // FFN专用
|
||||
attnH = try buf(hiddenSize) // NEW: Attention专用
|
||||
gate = try buf(maxIntermediate)
|
||||
up = try buf(maxIntermediate)
|
||||
attn = try buf(nHeads * maxHeadDim)
|
||||
gating = try buf(256)
|
||||
ns = try buf(max(hiddenSize, nHeads * maxHeadDim))
|
||||
io = try buf(hiddenSize)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**修改LayerOptimized.swift**:
|
||||
```swift
|
||||
// Attention阶段使用attnH(不覆盖h)
|
||||
try rmsNorm(..., output: temps.attnH) // Line 87: attnH #1
|
||||
try quantizedMatmul(..., input: temps.attnH, output: temps.q) // Line 91
|
||||
try quantizedMatmul(..., input: temps.attnH, output: temps.k) // Line 106
|
||||
try quantizedMatmul(..., input: temps.attnH, output: temps.v) // Line 109
|
||||
try quantizedMatmul(..., output: temps.attnH) // Line 172: attnH #2
|
||||
|
||||
// Residual add使用h(暂存)
|
||||
try eltwiseAdd(..., b: temps.attnH, output: temps.h) // Line 177: h暂存
|
||||
|
||||
// Post-attention norm使用attnH
|
||||
try rmsNorm(..., input: temps.h, output: temps.attnH) // Line 182: attnH #3
|
||||
|
||||
// FFN使用h(独立)
|
||||
try quantizedMatmul(..., output: temps.h) // Line 54: FFN专用h
|
||||
try eltwiseAdd(..., b: temps.h, output: input) // Line 57: residual
|
||||
|
||||
// PostFFN使用h
|
||||
try rmsNorm(..., output: temps.h) // Line 209: h专用
|
||||
try eltwiseAddScaled(..., output: input) // Line 238: 最终输出
|
||||
```
|
||||
|
||||
**预期效果**:
|
||||
- 避免attention和FFN竞争h buffer
|
||||
- 类似Audio修复,减少NaN风险
|
||||
- 内存增加:2560 Floats = 10KB(微不足道)
|
||||
|
||||
### 方案2: Input保护(简化)
|
||||
**创建inputCopy buffer**:
|
||||
```swift
|
||||
// 在Layer forward开始时保存input副本
|
||||
let inputCopy = temps.io // 使用现有io buffer
|
||||
let blit = cmdBuf.makeBlitCommandEncoder()!
|
||||
blit.copy(from: input, sourceOffset: 0, to: inputCopy, destinationOffset: 0, size: hiddenSize * 4)
|
||||
blit.endEncoding()
|
||||
|
||||
// 后续操作使用inputCopy(保护原始input)
|
||||
try eltwiseAdd(..., a: inputCopy, b: temps.h, output: input) // Line 177
|
||||
try eltwiseAdd(..., a: inputCopy, b: temps.h, output: input) // Line 57
|
||||
```
|
||||
|
||||
**预期效果**:
|
||||
- 保护原始input不被过早覆盖
|
||||
- 使用现有io buffer(无额外内存)
|
||||
- 简化修改
|
||||
|
||||
### 方案3: 顺序修复(保守)
|
||||
**检查每个kernel的NaN输出**:
|
||||
```swift
|
||||
// 在attention后检查h
|
||||
let cmdBufDebug = engine.commandQueue.makeCommandBuffer()!
|
||||
try rmsNorm(..., output: temps.h, cmdBuf: cmdBufDebug)
|
||||
cmdBufDebug.commit()
|
||||
cmdBufDebug.waitUntilCompleted()
|
||||
checkNaN(temps.h, "After RMSNorm")
|
||||
|
||||
// 在quantizedMatmul后检查
|
||||
// ...逐个检查定位NaN源头
|
||||
```
|
||||
|
||||
**预期效果**:
|
||||
- 精确定位哪个kernel产生NaN
|
||||
- 保守修复,最小修改
|
||||
- 调试时间长
|
||||
|
||||
## 推荐修复路径
|
||||
|
||||
### 优先级排序
|
||||
1. **方案1**(Buffer隔离)- 推荐,彻底修复
|
||||
2. **方案2**(Input保护)- 简化,快速修复
|
||||
3. **方案3**(顺序检查)- 调试,精确定位
|
||||
|
||||
### 实施步骤(方案1)
|
||||
1. 修改ForwardTemps.swift添加attnH
|
||||
2. 修改LayerOptimized.swift使用attnH
|
||||
3. 测试验证NaN消除
|
||||
4. 性能测试确认无影响
|
||||
|
||||
### 时间预估
|
||||
- 方案1: ~30分钟(修改+测试)
|
||||
- 方案2: ~15分钟(快速修复)
|
||||
- 方案3: ~60分钟(深度调试)
|
||||
|
||||
## 验证方法
|
||||
|
||||
### 测试代码
|
||||
```swift
|
||||
// 在Layer forward后检查
|
||||
let layerOutputPtr = input.contents().assumingMemoryBound(to: Float.self)
|
||||
let layerNaNCount = Array(UnsafeBufferPointer(start: layerOutputPtr, count: hiddenSize)).filter { $0.isNaN }.count
|
||||
print("Layer output NaN: \(layerNaNCount)/\(hiddenSize)")
|
||||
assert(layerNaNCount == 0, "Layer produced NaN!")
|
||||
```
|
||||
|
||||
### 测试模型
|
||||
- E2B: 已加载成功,可用于测试
|
||||
- E4B: Layer 34缺失,跳过
|
||||
- 12B: Layer 1缺失,跳过
|
||||
|
||||
## 总结
|
||||
|
||||
**TEXT NaN问题性质**:
|
||||
- Buffer重用冲突(类似Audio)
|
||||
- Temps.h多次覆盖
|
||||
- Input多次residual
|
||||
|
||||
**修复方向**:
|
||||
- Buffer隔离(Audio经验)
|
||||
- 减少重用
|
||||
- 专用buffer
|
||||
|
||||
**预期效果**:
|
||||
- NaN消除(类似Audio修复)
|
||||
- 性能无损
|
||||
- 内存微增(10KB)
|
||||
|
||||
**下一步**:
|
||||
1. 选择修复方案(推荐方案1)
|
||||
2. 实施修改
|
||||
3. 测试验证
|
||||
4. 完成TEXT NaN修复
|
||||
|
||||
---
|
||||
|
||||
**创建时间**: Day 3 Session(~4小时)
|
||||
**基于**: Audio NaN修复经验(67%就绪)
|
||||
**目标**: TEXT 95%就绪(零NaN)
|
||||
Reference in New Issue
Block a user