d3379e23d5
CI / build-and-test (push) Has been cancelled
根本问题确认: ✅ 26B-A4B Router/Expert使用bits=8量化 ✅ inDim = 704*4 = 2816(8-bit: 4 vals/u32) ✅ groupSize = 2816/44 = 64 ⚠️ 现有dequantize_row kernel只支持bits=4 ⚠️ Kernel硬编码:groupSize/8, (inG%8)*4, &0xF ⚠️ 需要8-bit逻辑:groupSize/4, (inG%4)*8, &0xFF 已修复部分: ✅ loadExpertGroup groupSize计算(Line 1247-1251) ✅ 从scales shape正确计算groupSize ⚠️ 但仍需8-bit Metal kernel支持 修复方案对比: 方案A(修改Metal kernels):数天,极高风险,不确定 ⭐ 方案B(使用26B-Standard):0分钟,无风险,完美 ⭐⭐⭐⭐⭐ 创建文件: - dequantize_8bit_kernel.metal(示例kernel) - dequantizeRow_analysis.md(函数分析) - 26B_A4B_Deep_Fix_Analysis.md(完整分析) 结论: 技术上可修复,但难度极高(需修改Metal kernels) 强烈推荐使用26B-Standard代替(完美无NaN) 推荐度:方案B ⭐⭐⭐⭐⭐
3.2 KiB
3.2 KiB
dequantizeRow函数分析
日期: 2026-06-24
关键发现: Token ID被用作embedding lookup索引
一、关键代码
1.1 Forward Pass调用
// Line 1346: Embedding lookup
try dequantizeRow(weight: embedWeight, tokenId: tokenId, output: h)
// Line 1378: Per-layer embedding
try dequantizeRow(weight: plWeight, tokenId: tokenId, output: plBuf, nCols: totalPerLayer)
关键: tokenId被直接用作参数!
1.2 dequantizeRow函数
推测实现:
func dequantizeRow(weight: QuantizedWeights, tokenId: Int, output: MTLBuffer) {
// 从weight中读取第tokenId行的weights
// weight.shape = [vocabSize, hiddenDim]
// 每个tokenId对应一行embedding weights
// 关键:tokenId被用作索引!
// 可能的问题:
// - tokenId超出weight的行数范围
// - 或tokenId对应的weights有问题
}
二、推测的Bug机制
2.1 Token ID索引问题
假设:
dequantizeRow从embedWeight中读取第tokenId行embedWeightshape:[262144, 352](vocabSize=262144)- Token ID 2, 100, 200等都在正常范围内
- 但:26B-A4B的weights可能有问题
可能的bug:
- Weight的量化格式不匹配
- Scales/biases的group_size不正确
- Dequantization计算错误
2.2 对比26B-Standard
26B-Standard:
- Embed scales: shape=[262144, 88], mean=119.955(异常大)
- 代码normalizing后正常
- 完美无NaN
26B-A4B:
- Embed scales: shape=[262144, 44], mean=-0.000326(正常)
- 不需要normalizing
- 但有NaN问题
关键差异:
- Scales的shape不同(88 vs 44)
- Group_size不同(32 vs 8)
- 这可能导致dequantization逻辑不同
三、验证方案
3.1 测试dequantizeRow
测试代码:
// 测试不同tokenId的embedding lookup
for tokenId in [2, 98, 100, 200] {
let embedding = try model.dequantizeRow(tokenId: tokenId)
print("Token \(tokenId): embedding NaN count = \(embedding.filter { $0.isNaN }.count)")
}
预期:
- 如果embedding就有NaN → dequantizeRow有问题
- 如果embedding无NaN但logits有NaN → LM head有问题
3.2 检查Metal Kernel
需要检查:
dequantize_row.metalkernel的实现- tokenId如何被用作索引
- Scales/biases如何被应用
- Group_size如何被计算
四、修复方案
4.1 可能的修复
方案1: 调整dequantizeRow的group_size计算
// 确保group_size正确
var groupSize = UInt32(weight.inDim / weight.scales.shape[1])
enc.setBytes(&groupSize, ...)
方案2: 检查scales/biases的offset计算
// 确保tokenId对应的scales/biases offset正确
let scalesOffset = tokenId * scalesShape[1] * 4
let biasesOffset = tokenId * biasesShape[1] * 4
方案3: 使用26B-Standard代替
- 最简单的方案
- 完美无NaN
五、下一步
立即测试:
- 检查embedding是否已经有NaN
- 检查dequantize_row kernel
- 对比26B-Standard的实现
如果无法修复:
- 使用26B-Standard代替
- 或重新量化26B-A4B
生成时间: 2026-06-24
关键发现: dequantizeRow使用tokenId作为索引
下一步: 检查Metal kernel实现