【问题标题】:Metal compute values isnt the same as the CPU valuesMetal 计算值与 CPU 值不同
【发布时间】:2021-12-25 16:24:59
【问题描述】:

我正在尝试实现 3DPoints 向量的长度,当我将 GPU 检索到的值与 CPU 进行比较时,它们并不完全相同,通常存在大量差异。 我最初使用了 packed_float3,它存在更多差异,所以我开始使用 float3 并进行了一些改进,但仍有一些差异我想修复。

这些值差别不大,平均而言它们相差 -0.00000000048358334004,但是当我运行诸如对两个数组求和和相减之类的操作时,差异不会发生,我希望它会发生相同的情况。

这是代码的一部分

main.m

- (void) lenght_function:(NSArray*) array {
    _buffer[0] = [_mDevice newBufferWithLength:_sp_size_alloc options:MTLResourceStorageModeShared];
    _buffer[1] = [_mDevice newBufferWithLength:_sp_size_alloc options:MTLResourceStorageModeShared];
    float3 *datapt = [_buffer[0] contents];

    for (unsigned long index = 0 ; index< _sp_lenght ; index++) {
        datapt[index].x = (float)[array[index] getX];
        datapt[index].y = (float)[array[index] getY];
        datapt[index].z = (float)[array[index] getZ];


    }
    commandBuffer = [_mCommandQueue commandBuffer];
    assert(commandBuffer != nil);
    
    id<MTLComputeCommandEncoder> computeEncoder = [commandBuffer computeCommandEncoder];
    assert(computeEncoder != nil);

    [computeEncoder setComputePipelineState:_mLenghtFunctionPSO];
    
    [computeEncoder setBuffer:_buffer[0] offset:0 atIndex:0];
    [computeEncoder setBuffer:_buffer[1] offset:0 atIndex:1];
    //[array1 makeData];
    
    MTLSize gridSize = MTLSizeMake(_sp_lenght, 1, 1);
   
    NSUInteger threadGroupSize = _mLenghtFunctionPSO.maxTotalThreadsPerThreadgroup;
    if(threadGroupSize > _sp_lenght){
        threadGroupSize = _sp_lenght;
    }
    
    MTLSize threadgroupsize = MTLSizeMake(threadGroupSize, 1, 1);
    
    [computeEncoder dispatchThreads:gridSize threadsPerThreadgroup:threadgroupsize];
    [computeEncoder endEncoding];
    [commandBuffer commit];
    [commandBuffer waitUntilCompleted];
    float3 *arr1 = _buffer[0].contents;
    float* result = _buffer[1].contents;
    unsigned long counter = 0;
    for (unsigned long index = 0; index < _sp_lenght; index++)
    {
        if (result[index] != sqrtf(arr1[index].x*arr1[index].x + arr1[index].y*arr1[index].y + arr1[index].z*arr1[index].z)){
            counter++;;
        }
    }
    NSLog(@"ERROR counter %lu\n",counter);

}

kernel.metal

kernel void lenght(const device float3 *arr1,
                         device float *result,
                         uint index[[thread_position_in_grid]]){
    
    result[index] = precise::sqrt(precise::pow(arr1[index].x,2) + precise::pow(arr1[index].y,2) + precise::pow(arr1[index].z,2));

}

【问题讨论】:

  • 嗨。查看您获得的值和您期望的值会很有帮助。他们减了多少?
  • 感谢您的回复,我也是 StackOverflow 的新手,我没有详细说明问题,所以我更新了描述以帮助更好地理解我遇到的问题。
  • 尝试为 Metal 着色器编译禁用 fastmath 并再次比较。

标签: objective-c metal


【解决方案1】:

32 位精度只有大约 7 个小数位,而您显示的差异大约是 9-10 个小数位。因此,您所展示的内容实际上比人们期望的 32 位浮点精度要好一些。听起来您想要 64 位双精度,但这不是内置的 Metal 数据类型。

如果您将这些值乘以 100 或 1000 以将小数位向上移动,然后在您的值相加后除以该数字,这可能会有所帮助。

另一种可能性是首先标准化你的值,所以它们都在 0 到 1 的范围内。然后你甚至可以使用半精度。

【讨论】:

  • 感谢您的回答,但我不明白的是为什么像添加和子这样的操作没有任何区别,而这个“功能”却有。
  • GPU 和 CPU 是非常不同的设备。对于任何具有 32 位浮点数的东西,你不能指望超过 7 或 8 位小数,而且 GPU 的精度比 CPU 略低一点也不奇怪。我认为 GPU 基本上可以在 FP16 上运行,所以它可能在内部用两个 FP16 值表示值。
  • 我认为这个前提是错误的。 GPU 并非“不那么准确”。 IEEE-754 是一个标准,浮点运算被精确定义,相同的浮点运算将在基本上任何处理浮点数的现代设备上产生相同的结果。可能发生的情况是编译器可以引入行为不同的 fastmath 优化。
  • 参考基于物理的渲染书中关于浮点的一章或这篇文章:ciechanow.ski/exposing-floating-point
  • 谢谢你的清理,我使用金属的精确模块来获得最精确的数据但我仍然无法实现它
猜你喜欢
  • 1970-01-01
  • 2017-10-30
  • 2017-09-14
  • 1970-01-01
  • 2019-05-06
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多