mirror of
https://github.com/deepseek-ai/DeepGEMM.git
synced 2026-11-05 00:47:26 +00:00
[PR #68] [MERGED] Correctly flush L2 (+performance impact & upcoming optimization fork) #229
Labels
No labels
pull-request
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
deepseek-ai/DeepGEMM#229
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
📋 Pull Request Information
Original PR: https://github.com/deepseek-ai/DeepGEMM/pull/68
Author: @ademeure
Created: 3/15/2025
Status: ✅ Merged
Merged: 3/16/2025
Merged by: @soundOfDestiny
Base:
main← Head:flush_l2_pr📝 Commits (1)
6cbff57Correctly flush L2, as reconstructing the tensors on every iteration effectively put them in the L2, and gave the GPU enough idle time to avoid thermal throttling in a potentially unrealistic way.📊 Changes
2 files changed (+35 additions, -11 deletions)
View changed files
📝
deep_gemm/utils.py(+23 -2)📝
tests/test_core.py(+12 -9)📄 Description
Thank you for open sourcing this under a MIT license! I was working on optimized PTX matrix multiplications for H100 as you released this, and decided to continue working on it as a fork of your repo instead (not yet public). I've achieved very large performance gains (see below) and I plan to open source it very soon (+ maybe try to get a modded nanogpt speedrun record with it). I hope it can be useful to the wider community worldwide.
However as part of this work I have discovered that your benchmarking code does not properly flush the L2 cache. This comment is misleading "# Construct new tensors every time to avoid L2 cache acceleration"!
Constructing new tensors actually puts them in the L2 cache, as all DRAM writes on NVIDIA GPUs always go through the L2 cache first, including host-to-device cudaMemcpy. The "flush_l2" parameter in bench_kineto is disabled by default, but enabling it won't work because construction happens after the L2 flush. Another interesting side-effect is this gives the GPU enough time to "cool down" and you avoid thermal throttling completely (the GPU is idle some of the time and the kernels that do run are very low power compared to matmuls).
This might be representative of some use cases (e.g. previous kernel converts A & B to FP8 putting them in L2 + low enough power that the GPU cools down) and thermal throttling might be avoided with liquid cooling or if you reserve SMs for communication etc... But that's clearly not the intended effect of the code based on your comments and it's not standard benchmarking practice, so I think it makes sense to fix this.
I've also added options to maximise throttling (2000 runs) or minimise it (long sleep between tests) compared to the new default (huge 8GB memset to give the GPU time to cool down while staying at maximum clocks with DVFS, this gave me the best performance on GH200 interestingly enough - you could just replace it with a short flush and a sleep if you prefer, it's not a big difference).
Unfortunately it significantly reduces performance. Here's my GH200 96GiB baseline (you'd need to retest H800 yourself):
And with fixed L2 flush on same GH200 96GiB (faster tests might be because it's even better at avoiding thermal throttling):
Thankfully I have managed to more than recover that performance in my upcoming fork (which by now changes nearly all of fp8_gemm.cuh "Ship of Theseus" style).
Here's a small teaser (this is before I fixed unaligned block sizes so n=2048 instead of 2112, and I might lose a bit of the performance once I fix it properly, to be determined...) - looking forward to releasing this soon, it involves a lot of crazy tricks (different setup than numbers above so not 1:1 comparable):
🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.