Repository navigation
vulkan: ggml src1 precision support - #30656
Conversation
579799a to
5772158
Compare
|
I understand that ggml-metal is already doing this scaling+f16 hack, but it doesn't seem like a legitimate implementation of PREC_F32. There are obvious cases where it breaks down, surely somebody will eventually find a real-world case where it doesn't work. IMO ggml should either require a legitimate fp32-based implementation of PREC_F32 and accept that it will be slow, or disallow F32 as a src prec and move this amax+scale trick into the graph. CC @ggerganov for his thoughts. |
|
I had the same concern and discussed it with Georgi before, my understanding was that this is a performance-compromise to avoid fp16 overflow, which was the actual problem that this src prec flag was meant to solve. And the overflow is avoided by this path. But I agree, it's not "true" FP32 precision. |
|
Technically, I agree that the amax is a bit of a hack, so thanks for pushing on this - let's clean it up. For the Metal backend I have a few refactors in the pipe to enable proper BF16 support for I think what we have to do in general is:
The amax trick could be a stopgap until the above can be satisfied and if it make sense in terms of complexity/maintenance. But we should likely mark all such tricks with Does this sound OK? |
|
I'm not sure about the hardware support in Metal devices, but while bfloat16 is better and I certainly want to support that, for Vulkan we have a lot of devices that have good performant fp16 support, but not bfloat16, and falling back to fp32 will be very slow in comparison. Possibly some of this can be cushioned with some kind of bf16 emulation just for shmem storage + fp32 math shader, but I think this will be painful for older devices. Maybe that's fine as long as it just affects a limited amount of huge models, like Mistral 4. |
|
It also effects smaller models like mellum2.1 . It also effects cuda. #30704 |
Pre-M3 devices do not have hardware for BF16 - it is emulated and hence a bit slower than F16. |
I'm most worried about RDNA3, RADV does not expose bfloat16 support without an env var (because of numerical inconsistencies in the hardware), so falling back to fp32 means disabling tensor cores and all scalar fp16 dual issue instructions, so it's gonna be a lot slower. For older devices it would just be about losing the dual issue and possibly having to store twice as much data in shared memory. |
|
Yeah, I'm not 100% sure what is the right call here. The fundamental issue is that using F16 is by definition not enough for LLMs because all models are trained in BF16. So there is never a guarantee that F16 will not break for some specific activation values. We've only observed Mistral4 and Mellum, but in theory any model can exhibit such issues. So from correctness perspective, all compute has to be done in BF16. But on the other hand the performance loss on a lot of consumer hardware is significant - we can't ignore that too. The approach that we can take is to relax the API requirements a bit. I guess we can declare in the |
|
bfloat16 is also problematic for precision, since it has fewer bits for the mantissa, so I'm not even sure which path is closer to actual fp32. |
|
The graph builder could check for bf16 support in the backend and choose between using PREC_BF16 vs amax+PREC_F16. |
Support is not enough, and graph builder would need to make decisions about performance too (non trivial eg. mi100 has bf16 matrix instructions but they are half rate compared to fp16, amd hw in general dosent have packed valu bf16 instructions for its fp16 equivalents etc) |
|
@IMbackK I think Jeff's point is that the decision which path to take (fast and not-exact or slow and exact) should be lifted up to the user code. This way the ggml supports both options and it's up to the user application to figure out which option to use when. How exactly (via CLI args, some extra logic, etc.) not yet defined. Btw, we have the |
Right, and the user code can make the choice on a per-model basis, based on correctness and performance testing. bf16 is also half speed on Geforce (well, bf16 is only supported with fp32 accumulators, and fp32 accumulators are half speed, but presumably fp32 accumulators are required for these models so maybe no real slowdown?). Also I don't think token gen will really be penalized by using bf16. codex tells me the bf16 work is maybe 20% of the math in these models. So I don't think half speed for that fraction of the workload is catastrophic. Maybe for GPUs that need to fall back from tensor core to non-tensor core (rdna3) the situation would be worse. |
|
We'll lift the amax trick to the graph for the specified models as a stopgap. After that we'll work to add auto-detection of which backends have support for the necessary src1 precision (i.e. |
|
How do you intend to handle bf16-capable hardware that would prefer fp16 for performance reasons? |
Not sure yet - this will be part of the second step. For now I'd like to close the gap (e.g. #30733) and in the meantime we can figure out what to do for fast/slow BF16 hardware. |
|
It seems doing the amax trick at the graph level is not so simple - injecting an |
|
The tensor parallel issue aside, the prompt processing performance drops significantly due to the @jeffbolznv Let me know if you have any suggestions - here is the WIP (981f0a6) |
|
I tried it on Vulkan and the argmax was very slow, like many times slower. Maybe top_k would be faster? For vulkan at least we have a fast path for k=1. I had codex put this together and perf was much better, but I haven't validated that it's doing the right thing.
Is it really splitting the inner dimension of the src matrices? I'd have expected it to split dim1 of the result. |
|
When I dumped the perf logger stats for top_k I see this: Maybe the tensor is incorrectly being treated as flattened and that's why it's so slow? Or it could just mean codex gave me bad code to run. |
|
Yes, let's keep the non-scaling stuff. I'm preparing a PR to remove the amax scaling from the Metal backend too. |
a1024c4 to
f66f97e
Compare
|
Okay, I reduced it to only the Vulkan precision handling without scaling. |
jeffbolznv
left a comment
There was a problem hiding this comment.
My comments are just nitpicks, fine to merge if you want.
* vulkan: update accumulator precision selection * handle fp32 overflows through scaling * handle overflows in copy-to-fp16 activations * add missing cases * simplify code * add tests * add ggml prec get helpers, use in Vulkan backend * clean up comments * revert comment edit * trim tests * also handle fp32 src0, fix test scaling src0 values too * don't scale if disabled * remove fp16 scaling * add check to mul_mat * fix inconsistency * rename to acc_prec
* vulkan: update accumulator precision selection * handle fp32 overflows through scaling * handle overflows in copy-to-fp16 activations * add missing cases * simplify code * add tests * add ggml prec get helpers, use in Vulkan backend * clean up comments * revert comment edit * trim tests * also handle fp32 src0, fix test scaling src0 values too * don't scale if disabled * remove fp16 scaling * add check to mul_mat * fix inconsistency * rename to acc_prec
Overview
Handle src1 precision in the Vulkan backend MUL_MAT and MUL_MAT_ID implementation, by scaling the values into the fp16 range, similar to how it is handled by Metal (#29029).
I also added the GGML helper functions and switch cases discussed in #30105 (comment).
Supercedes #30105 and #30544
Requirements