Category: note to self

References and aide memoires

  • Grouped GEMMs and MoE

    One of the challenges discussed in the Deepseek v3 paper is the availability of grouped GEMM kernels, which are used to hide the performance impact of many small kernel launches on GPUs. Deepseek uses many small experts (256!) rather than a few larger ones, which exacerbates this problem.

    Mixture of Experts models introduce multiple experts in the feed-forward portion of each transformer layer. Rather than having a single shared set of experts, each layer has its own. Each batch of tokens first passes through the standard attention block, followed by a lightweight linear layer with a softmax function1. This determines, for each token, which experts it should be sent to. Tokens designated for each expert are gathered and sent to the appropriate device via an all-to-all operation, as experts are typically distributed across different devices.

    Once the tokens are on the device with the right expert(s) we need to execute the matrix multiplies for each expert for its set of tokens. The obvious solution is just to loop through and launch each GEMM, but because these are small (small number of tokens, and smaller expert matrices) the kernel launch ends up being a lot of the performance. A grouped GEMM allows you to do this process on-device, taking in a list of tokens and experts and executing all the GEMMS with a single kernel launch.

    This varies from batch GEMMs as the inputs can vary – different experts might receive different numbers of tokens.

    There are example implementations available, including a tutorial on TritonLang that walks through a simple grouped GEMM kernel, as well as an example in Cutlass .

    1. In switch MoEs at least, but there are similar gating networks elsewhere. ↩︎
  • DeepSeek R1 & GRPO

    DeepSeek dropped another quality release with their R1 series. The models are an exploration of how to improve improving reasoning capabilities in LLMs. They’ve released a crop of models, which, despite some quirks in its licensing, are extremely helpful. The paper continues the trend of being clear and open.

    Core Approaches for Developing Reasoning Capabilities

    DeepSeek’s approach to reasoning in LLMs is built on three distinct approaches

    1. Reinforcement Learning (RL) on a Base Model (DeepSeek-R1-Zero)

    DeepSeek-R1-Zero applies RL directly to the base model without relying on any supervised data.

    • Training Approach:
      • Leveraged Group Relative Policy Optimization (GRPO), earlier work by the same folks, a simplified version of PPO that avoids the need for a large critic model by basing optimization on group-level scores.
      • Effectively it generates multiple completions per prompt, scoring them using a combination of reward models and reward functions:
        • Accuracy rewards: Validating correctness for problems like coding or math with deterministic solutions.
        • Format rewards: Encouraging structured reasoning traces using tags.
      • Rewards were averaged across completions, with constraints applied using KL divergence to maintain proximity to the base model.
    • Emergent Behaviors:
      • Spontaneous reflection and backtracking during training.
      • Majority voting across completions further boosted the model’s reasoning accuracy.
      • Over time, the model naturally extended its “thinking” process, solving increasingly complex tasks.
      • I saw a bit of skepticism about the “Aha” moments (the model demonstrating backtracking), with suggestions that perhaps they had some O1 data in the training mix. GitHub – Jiayi-Pan/TinyZero came out today that reproduced the recipe from R1-Zero and saw the same behavior, based on Qwen-2.5 3B. While that doesn’t totally rule it out, I think it does likely indicate that this method is a pretty sound way of eliciting reasoning.

    2. Supervised Fine-Tuning (SFT) with Chain of Thought (CoT) + RL

    Building upon the groundwork of R1-Zero, DeepSeek-R1 is a supervised finetune on DeepSeek V3 with reasoning trace/cha of thought data.

    • Data Preparation:
      • Generated ~600k CoT examples by iteratively fine-tuning on outputs from converging models (e.g. run it, get some good traces, add that to training set, rinse and repeat)
      • Supplemented the dataset with ~200k non-CoT examples to ensure the model learns to use CoT selectively and appropriately (they used data from the preexisting Deepseek v3 fine tuning set)
    • Training Process:
      • Fine tune on the 800k
      • Applied RL exactly as with R1-Zero to refine reasoning capabilities, using diverse prompts (including some non-Chain-of-Thought ones) and classic preference alignment techniques (e.g. don’t chain of thought “hello”) to optimize response quality and coherence.
    • Outcome:
      • This model seems both good at reasoning, and generally pretty strong. Its not everything everywhere, but this feels like a plausible recipe towards general purpose models, though they note it is a bit worse at tool use etc. than base V3.

    3. Distillation to Small Dense Models

    In a really interesting extension, DeepSeek distilled the reasoning knowledge of R1 into compact versions based on models like Qwen and Llama.

    • Process:
      • Fine-tuned smaller models with the 800k training examples used before, but didn’t do any fine tuning.
      • The distilled models exhibited strong reasoning capabilities, outperforming earlier open-source baselines.
    • Observations:
      • Distillation did not involve direct logic comparison, likely due to the challenges posed by token set differences.
      • RL stages were not applied to distilled models, but researchers suggested that doing so could enhance their performance further.

    Challenges and Unsuccessful Attempts

    I really like they called out some things that didn’t work:

    • Process Reward Model (PRM):
      • Attempted to reward reasoning steps individually rather than focusing on end results.
      • Faced challenges like reward hacking and inconsistencies in defining intermediate reasoning steps.
    • Monte Carlo Tree Search (MCTS):
      • Explored breaking problems into smaller parts and systematically searching for solutions.
      • Encountered exponential search space complexity, local optima issues, and difficulties in training reliable value models to guide search steps.

    Additional Observations

    • Few-shot Prompting:
      • Observed that few-shot prompting degraded R1’s performance, a behavior also noted by folks working with OpenAI’s o1 series.
    • Software Engineering challenges:
      • Slow evaluations limited the application of large-scale RL to software engineering tasks.
      • They highlighted the need for methods like rejection sampling (have “not chosen” examples) or asynchronous evaluations to address these inefficiencies.

    It is extremely cool to see that pure RL can push a model to improve reasoning capabilities. The approach of developing traces, fine tuning, and mixing in capabilities feels like a very practical approach as well. I am really looking forward to people exploring further!

  • TIL: You don’t need PIL for decoding images with TorchVision

    pytorch.org/vision/main/generated/torchvision.io.decode_image

    The always busy Nicolas Hug was sharing this at work, and I hadn’t realized just how comprehensive the image decoding support had become in TorchVision. Over the last year TorchVision has added a lot of image decoding capabilities and got a better entry API. It should generally be faster than PIL now (with the exception of animated GIFS).

    Rather than decoding with PIL:

    from PIL import Image
    # Load the image
    image_path = "chungus.png"
    image = Image.open(image_path)

    You can use the built in decoders like this:

    from torchvision.io import read_file, decode_image
    # Load the image as a tensor
    image_path = "chungus.png"
    image_data = read_file(image_path)
    image_tensor = decode_image(image_data)

    TorchVision’s transforms support PIL transparently, so you might be using it when not intending! Relatedly, you’ll want to use the v2 transforms if you happen to be using the older versions.

    In general this complements the release of TorchCodec which has been improving decoding for video – you now have a really good range of options for decoding media in a PyTorch native way!

  • Functionalization in PyTorch

    Functionalization in PyTorch: Everything You Wanted To Know – compiler – PyTorch Developer Mailing List

    Over a year old, but a very in depth breakdown from Brian Hirsh of how AOTAutograd functionlizes – e.g. removes mutations from – various graphs, what that enables, and what kind of edge cases exist. Inductor as a backend can handle mutation, but many other situations (including export!) can’t. It got bumped up because of a question on exactly that!

    torch.export uses functionalization. In particular, when you export for inference, you’ll get out a functionalized ATen graph!

  • Idle Speculation on GPU Capacity Management

    Training large models today is tightly coupled to specific hardware. This makes moving workloads across systems or abstracting the hardware almost impossible without losing efficiency, and hence why you don’t tend to see a lot of uptake of the kind of cloud-like abstractions we see elsewhere.

    1. Gang semantics: Large models rely on precise scheduling for memory, networking, and compute to achieve high utilization. These tend to be model and hardware specific, and are hard to abstract.
    2. Compute efficiency: Compute ops like GEMMS should be less exposed to model quirks and are already more abstractable, but a lot of custom work is done on number format support and shape optimizations for specific models.

    My entirely unfounded prediction is that this stuff is getting easier, and we will see more standardization over the next few years.

    • Slow Down in Number Formats: Research like “Scaling Laws for Precision” (https://arxiv.org/pdf/2411.04330) shows a tradeoff between precision and parameter count. There’s a lot of folk knowledge in getting formats like FP8 stable, and it’s not totally clear how much FP4/MXFP4 and their ilk will add: my guess would be less, and they will be used in more targeted (and perhaps predictable) ways. Either way, I expect things to get less choppy and more predictable on the compute side, eventually.
    • Parameter Stabilization: Model size growth may well plateau, either for fundamental reasons (e.g. say we have enough model capacity in the 2-4tn params range for all the data) or to become more aligned with practical cluster sizes for scale-out networking (e.g., 72 GPUs with Blackwell). Whether this is for a model or a set of experts in an MoE I don’t know – and it feels like there is room for some variants of MoE routing architectures if we find that pattern particularly successful.
    • Shift To Test-Time: As training stabilizes, focus will shift to test-time compute — the pain points there being handling longer sequences and optimizing KV caches, which feel like more general/repeatable problems. I see this in part as moving a chunk of the pool of “large job expertise” from pretraining focused to inference focused, which then opens the door to the benefit of the tools/standards to help scale on the pretrain side.

  • TIL: weights-only model loading will be the default in PyTorch 2.6

    I had missed this, but weights-only is going to be the default for torch.load in Pytorch 2.6:

    https://dev-discuss.pytorch.org/t/bc-breaking-change-torch-load-is-being-flipped-to-use-weights-only-true-by-default-in-the-nightlies-after-137602/2573

    This is one of those small-sounding changes which requires quite a lot of follow-through to actually land. The default torch.load supports pickled Python code, so allows for arbitrary code execution: very helpful in a lot of cases (hence the many places that need special consideration!), but, particularly these days when many users may be trying models of fairly unknown provenance, a source of ongoing security concerns. Making that behavior an explicit opt-in is a great win for the wider community. HuggingFace have done some good work in this area too with their safetensors project, and having the core safe-by-default is a very welcome addition!

  • TIL: torchdbg

    https://github.com/ezyang/torchdbg

    Step by step debugging through a PyTorch program and see the underlying operators and shapes. Helpful for getting a view of the graph and shapes – just annotate the code with with torchdbg.LoggingMode(): and add TORCH_TRACE=./log to dump the logging file. Comes with a handy viewer.

  • Thunderkittens /GPUs go brr

    Just got round to reading the intro post to the (now improved) thunderkittens kernel DSL.

    https://hazyresearch.stanford.edu/blog/2024-05-12-tk

    Many good nuggets on kernel writing in general and the hopper in particular.

    But to us a “register” is a 16×16 tile of data. We think AI wants this — after all this time, it’s still just matrix multiplies, reductions, and reshapes. And we think the hardware wants this, too — small matrix multiplies are just begging for hardware support beyond just the systolic mma.

    In fact, more broadly we believe we should really reorient our ideas of AI around what maps well onto the hardware. How big should a recurrent state be? As big can fit onto an SM. How dense should the compute be? No less so than what the hardware demands. 

  • TLParse

    https://github.com/ezyang/tlparse – or pip install tlparse

    Ed Yang’s Torch Logs Parser gets used a lot within Meta, where it has a bunch of extra integrations to make it even more helpful. Its still useful everywhere else too when working with torch.compile, particularly if approaching a more complex model that generates a lot of log output or trying to get a feel for performance issues.

    Basic usage is just:

    TORCH_TRACE=/tmp/my_traced_log python module.py
    tlparse /tmp/my_traced_log/filename.log -o tl_out/ --overwrite

    The result breaks down the log into a number of easier to consumer sections for different times the analysis restarted, graph breaks etc., and gives you chromium perf trace files for looking at performance.

    A screenshot of the build products from tlparse
  • TIL: TunableOp in PyTorch

    I wasn’t aware of this particular autotuning lever! There is a breakdown of TunableOp on the AMD blog from back in July:

    https://rocm.blogs.amd.com/artificial-intelligence/pytorch-tunableop/README.html

    Instead of using the default GEMMs, TunableOp will search for the best GEMMs for your specific environment. It does so by first querying the underlying BLAS library for a list of all solutions for a given GEMM, benchmarking each of them, and then selecting the fastest. TunableOp then writes the solutions to disk, which can then be used on subsequent runs. 

    Though the infrastructure is generic, this is effectively an AMD-specific tuning tool right now, as mentioned in the original docs.

    Currently only a TunableGemm for ROCm is implemented. Note that CUDA builds of PyTorch will function correctly when using TunableOp but the only solution available to CUDA builds is the ‘Default’ implementation i.e. the original cuBLAS default, now called through TunableOp.

  • Chinese Tech Term Glossary

    Very interesting list, via Jeff Ding at ChinAI.

    from Chinese primary sources on technology and security, with expert translations and annotations by CSET’s translation team. It’s created and maintained by Ben Murphy with support from the Emerging Technology Observatory.

    https://docs.google.com/spreadsheets/d/15MS8Qp9U-KOaoQF0R_e7lVxF3UBMyZFpC6-cohhnsD0/htmlview#gid=0

  • TIL: When does PyTorch upgrade Python versions

    The policy is in the RELEASE documentation:

    PyTorch supports all minor versions of CPython that are not EOL: https://devguide.python.org/versions/

    This is a little more consistent than how it was handled in the past, with annual upgrades and deprecations to match the cpython release schedule.

  • Unsupported: dynamic shape operator: aten.nonzero.default with boolean masks in torch.compile

    The error message you get actually tells you the fix, but I found it non-intuitive to what I was doing enough I was hesitant to actually just try the config:

    torch._dynamo.config.capture_dynamic_output_shape_ops = True

    The general issue is capturing shapes on scalars isn’t turned on by default due to various issues, but for your case it may actually work. It is also interesting to see where TorchVision hit this, and worked around with torch.where instead.

  • TIL: ROCM is actually open source

    I think I did know this at some point, but I was reminded today that unlike the (sometime) black box that is CUDA https://github.com/ROCm/ROCm is actually available on Github, which is operationally much nicer!

    I also recall learning that NCCL, which is open, https://github.com/NVIDIA/nccl is in part because some of it was funded by the Lawrence Berkeley National Laboratory!

  • Notes to Self: Torch Compile References

    Whenever I am trying to do something fun (read: poorly considered) with torch.compile I find myself googling for the same handful of references, most of which are in the PyTorch Drive folder, so putting them here for my own reference.

    If you are doing regular compile usage you shouldn’t need these, but they are helpful when debugging more complex shape issues or investigating performance issues or graph breaks.