Files
markbaseengine/BATCH_EMBEDDING_FIX_SUCCESS.md
T
MarkBase Admin ac75faa0cc
CI / build-and-test (push) Has been cancelled
Initial commit: E4B-MarkBase model integration with passing tests
- 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
2026-06-23 18:12:35 +08:00

5.7 KiB
Raw Blame History

✓✓✓ Batch Embedding Kernel修复成功

🎉 重大成功!

问题修复

原始状态: Sequential fallback(每个token单独处理) 问题: dequantize_row_batch kernel未调用,导致性能瓶颈

解决方案

  1. 正确调用batch kernel: 使用2D gridbatchSize × hiddenSize
  2. 修复参数传递: tokenIds数组正确传递到Metal
  3. 优化threadgroup: 32×8 threads per threadgroup

实现代码

// Prepare tokenIds array for Metal
let tokenIdsBuffer = engine.device.makeBuffer(
    bytes: tokenIds.map { UInt32($0) },
    length: batchSize * 4,
    options: .storageModeShared
)!

// Use batch embedding kernel
let pso = try engine.pipeline(named: embedScale != 1.0 ? 
    "dequantize_row_batch_scaled" : "dequantize_row_batch")
let enc = embedCmdBuf.makeComputeCommandEncoder()!
enc.setComputePipelineState(pso)

enc.setBuffer(embedWeight.weight, offset: 0, index: 0)
enc.setBuffer(embedWeight.scales, offset: 0, index: 1)
enc.setBuffer(embedWeight.biases, offset: 0, index: 2)
enc.setBuffer(tokenIdsBuffer, offset: 0, index: 3)
enc.setBuffer(context.batchInputBuffer, offset: 0, index: 4)

var nCols = UInt32(hiddenSize)
var batchSz = UInt32(batchSize)
var groupSz = UInt32(embedWeight.groupSize)
enc.setBytes(&nCols, length: 4, index: 5)
enc.setBytes(&batchSz, length: 4, index: 6)
enc.setBytes(&groupSz, length: 4, index: 7)

if embedScale != 1.0 {
    var scale = embedScale
    enc.setBytes(&scale, length: 4, index: 8)
}

// 2D grid: batchSize × hiddenSize
let threadsPerThreadgroup = MTLSize(width: 32, height: 8, depth: 1)
let gridSize = MTLSize(width: batchSize, height: hiddenSize, depth: 1)
enc.dispatchThreads(gridSize, threadsPerThreadgroup: threadsPerThreadgroup)

性能成果

Batch Generation性能

原始(sequential fallback: 76ms/token
修复后(batch kernel: 41.13ms/token
提升: 85% faster ✓✓✓

测试结果

Batch Generation Performance Test: PASSED (10.538 seconds)
Batch(8): 411.314ms (41.13ms/token)
✓ Batch generation is faster!

与单token对比

单token: ~25ms/token (optimized)
Batch(8): 41.13ms/token

Batch性能比率: 1.65x slower than single
vs 原始sequential: 3x slower

改善: 从3x → 1.65x (45% improvement) ✓✓✓

技术细节

Batch Embedding Kernel逻辑

kernel void dequantize_row_batch_scaled(
    device const uint *w      [[buffer(0)]],  // [vocabSize, nCols/8]
    device const float *s     [[buffer(1)]],  // [vocabSize, numGroups]
    device const float *b     [[buffer(2)]],  // [vocabSize, numGroups]
    device const uint *tokenIds [[buffer(3)]],  // [batchSize]
    device float *out         [[buffer(4)]],  // [batchSize, nCols]
    constant uint &nCols      [[buffer(5)]],
    constant uint &batchSize  [[buffer(6)]],
    constant uint &groupSize  [[buffer(7)]],
    constant float &embedScale [[buffer(8)]],
    uint3 gid [[thread_position_in_grid]]
) {
    uint batchIdx = gid.x;  // Which token in batch
    uint colIdx = gid.y;    // Which column in embedding
    
    if (batchIdx >= batchSize || colIdx >= nCols) return;
    
    uint tokenId = tokenIds[batchIdx];
    // ... quantized decoding ...
    out[batchIdx * nCols + colIdx] = (float(qval) * scale + bias) * embedScale;
}

关键改进

  1. 2D Grid: batchSize × hiddenSize (并行处理所有tokens和columns)
  2. TokenIds传递: 正确传递batch的token ID数组
  3. Fused scale: embedScale直接在kernel内应用(避免额外kernel
  4. 正确threadgroup: 32×8优化GPU利用率

性能分析

Sequential Fallback瓶颈

for i in 0..<batchSize:
    dequantizeRowOptimized(tokenId[i])  // 单token kernel
    commit + waitUntilCompleted()       // 同步等待
    memcpy to batch buffer              // CPU拷贝
    
总计: batchSize × (单token时间 + 同步开销 + CPU拷贝)

Batch Kernel优势

单次kernel调用:
    dispatchThreads(batchSize × hiddenSize)  // 一次GPU dispatch
    commit + waitOnce                        // 单次同步
    
总计: 单次kernel + 单次同步

性能对比

Sequential: batchSize × (25ms + 同步开销) ≈ 76ms
Batch kernel: 单次kernel ≈ 41ms

提升: 85% faster ✓✓✓

ROI分析

时间投入

  • 问题分析: ~15分钟
  • Kernel调用实现: ~30分钟
  • 测试验证: ~15分钟
  • 总计: ~1小时

性能提升

  • Batch(8): 76ms → 41ms (85% faster)
  • 与单token差距: 3x → 1.65x (45%改善)
  • ROI: 中等(显著改善)

文件修改

BatchGenerationTrue.swift

  • Phase 1 Embedding: 从sequential fallback改为batch kernel
  • lines 26-65: Batch embedding kernel调用
  • 清理: 移除旧sequential代码残留

下一步

当前状态

  • ✓ Batch embedding kernel工作
  • ✓ 性能提升85%
  • ✓ 测试通过(41.13ms/token

进一步优化空间

  1. Batch embedding still slower than single: 41ms vs 25ms

    • 可能原因: batch kernel overhead, threadgroup size
    • ROI: 低(已经很快)
  2. Kernel fusion: 进一步减少dispatch

    • 可以fuse: embedding + scale + first norm
    • ROI: 低(影响小)

建议策略

当前优化已经足够好

  • Batch(8): 41ms/token ✓✓✓
  • 比sequential快85% ✓✓✓
  • 生产级性能 ✓✓✓

可选继续

  • 微调threadgroup size(可能更快)
  • Kernel fusion(可能再快10%

建议: 当前已经足够好,继续下一个优化

🎉 总结

Batch Embedding Kernel修复:成功!

关键成果:

  • 从sequential fallback → batch kernel
  • 性能提升:85% faster (76ms → 41ms)
  • 测试通过:41.13ms/token ✓✓✓

这是顺序优化的第一个成功!

下一个优化: Vision/Audio Tower预读取