ac75faa0cc
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
6.4 KiB
6.4 KiB
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修复方案:
// 创建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:
// 在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:
// 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:
// 在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输出:
// 在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(Buffer隔离)- 推荐,彻底修复
- 方案2(Input保护)- 简化,快速修复
- 方案3(顺序检查)- 调试,精确定位
实施步骤(方案1)
- 修改ForwardTemps.swift添加attnH
- 修改LayerOptimized.swift使用attnH
- 测试验证NaN消除
- 性能测试确认无影响
时间预估
- 方案1: ~30分钟(修改+测试)
- 方案2: ~15分钟(快速修复)
- 方案3: ~60分钟(深度调试)
验证方法
测试代码
// 在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)
- 实施修改
- 测试验证
- 完成TEXT NaN修复
创建时间: Day 3 Session(~4小时) 基于: Audio NaN修复经验(67%就绪) 目标: TEXT 95%就绪(零NaN)