Skip to content

Integrating TiledMLP for a much smaller memory footprint #41826

Open

Description

@stas00

Similar to TiledFusedLogitsLoss #41306 it's of a great benefit to perform TiledMLP as well, which can save a lot of HBM memory and allow longer seqlen/larger batch size or even enable a bs=1 training which wasn't possible before.

This technology comes from the Arctic Long Sequence Training https://arxiv.org/abs/2506.13996 which allowed us to train in bf16 with 500K tokens on a single H100 GPU, 3.7M on a single node, and 15M on Llama-8B using just four nodes. You will want to refer to section 3.1.1 TiledMLP for the full details.

Tiled MLP processes hidden_states in small slices. If we extract a single LlamaMLP layer from Llama-8B and run a bf16 hidden_states tensor of shape [1, 256_000, 4096] through its forward-backward, without and with sequence dimension tiling, we get about 10x memory saved as can be seen from the memory profiler plot:

Image

There is the additional cost of recomputing forward path. So we trade time for memory.

Currently, we hack it in with monkey patching:

        module_path = f"transformers.models.{model_type}.modeling_{model_type}"
        model_cls_prefix, _ = get_causal_lm_model_cls_prefix(model_type)
        module = __import__(module_path, fromlist=[f"{model_cls_prefix}MLP"])
        mlp_cls = getattr(module, f"{model_cls_prefix}MLP")
        setattr(mlp_cls, "forward", tiled_mlp_forward_common)

You can see the full code here:

https://github.com/snowflakedb/ArcticTraining/blob/5959f72709ac40433e94d09d095c021e7466cf0d/arctic_training/model/tiled_compute.py

This could be a built-in feature and enabled with a simple flag to from_pretrained(enable_tiled_mlp=True, ...)

Implementation-wise - most LLMs archs use the exact same MLP module, so the common case is very easy. Some are slightly different and would require some special cases. But perhaps those use cases could be added on demand if users request those, making the initial integration very simple (i.e. start with supporting just the models that use the common MLP.

TiledMLP lives here https://github.com/deepspeedai/DeepSpeed/blob/3631712bd796593bf6e2e70d4f1c352937a44cf8/deepspeed/runtime/sequence_parallel/ulysses_sp.py#L807 but you can copy it if you don't want to make a dependency on deepspeed. It's a small independent self-contained autograd function.

cc: @Rocketknight1

Activity

  1. Rocketknight1 commented on Oct 24, 2025

    @Rocketknight1
    Member

    Hmm, the relevant people here are probably:

    @SunMarc @MekkCyber for trainer/deepspeed/accelerate
    @ArthurZucker @Cyrilvallez as core maintainers

  2. stas00 commented on Nov 20, 2025

    @stas00
    ContributorAuthor

    TiledMLP has just been integrated into Liger-Kernel, so it should be possible to just rely on it via a flag and just a soft-dependency on LK - or you could probably copy it - it's a very small class.

    linkedin/Liger-Kernel#935

  3. ArthurZucker commented on Nov 21, 2025

    @ArthurZucker
    Collaborator

    BTW sorry for the late late answer, this fits very well within our kernels approach @stas00 and is very welcome IMO! cc @MekkCyber !

  4. stas00 commented on Nov 21, 2025

    @stas00
    ContributorAuthor

    At the moment TiledMLP is pure pytorch, no kernels involved - even in the LK version - though they plan to change it down the road.

  5. Incheonkirin commented on Jun 17, 2026

    @Incheonkirin
    Contributor

    I'd like to take this on.

    Before I start implementing, I'd like to align on the integration shape:

    1. @stas00 noted that TiledMLP is currently pure PyTorch, using a custom autograd.Function that recomputes the MLP forward per shard. Given @ArthurZucker's note about the kernels approach, which direction would you prefer for v1: a pure-PyTorch path in Transformers core, a Liger soft-dependency exposed through use_liger_kernel / liger_kernel_config, or adapting it to the kernels framework?
    2. For the user surface, should this be Trainer-facing only, or model-level as well so non-Trainer training loops can enable it?
    3. Is supporting common SwiGLU MLPs first, with GEGLU and MoE as follow-ups, an acceptable v1 scope?

    For the first pass I'd plan on output/gradient parity tests against the regular MLP, gradcheck for the custom backward, gradient-checkpointing interaction tests, FSDP / torch.compile smoke tests where applicable, and a memory benchmark across sequence lengths.

  6. qgallouedec commented on Aug 14, 2026

    @qgallouedec
    Member

    @Incheonkirin sorry for the long silence. Much of this has since been answered by code. TiledMLP is being done through kernels rather than as a pure-PyTorch path in core:

    From those PRs the shape looks like: @use_kernel_forward_from_hub on the MLP class (so model-level, not Trainer-only), SwiGLU/GeGLU first. @vasqu can confirm the intended scope and whether anything is left for a contributor.

    Either way, probably best not to start a from-scratch implementation. Two things do look open:

    • DDP correctness. The tiled backward calls torch.autograd.backward once per shard, so each weight's AccumulateGrad fires N times per backward. On a 2-layer LlamaForCausalLM under 2-rank DDP that raises Expected to mark a variable ready only once; it doesn't reproduce on a single-MLP model, so it's easy to miss. Passing the weights as autograd.Function inputs gives one accumulation each and fixed it in a separate implementation (tested), but not unconditionally: under ZeRO-3 the params are 0-sized placeholders outside their leaf module's forward, and that path then fails with expected shape compatible with [0]. Branching on whether the params carry a ds_id worked there, though I haven't tried it against this kernel.
    • Double tiling. Per @vasqu, a tiled SwiGLU MLP and a tiled nn.Linear may clash and tile twice, to be confirmed.

    For these reasons, I'll keep this one open for now

  7. vasqu commented on Aug 24, 2026

    @vasqu
    Collaborator

    Yea I think the biggest blocker was the double kernelize on linear modules when we have supersets like Swiglu MLP, see vasqu#2

    But the idea is as shared by @qgallouedec: Use kernels and kernelize the nn linear instead of manual (monkey) patches. Re 1, I think it was fixed by Kashif iirc, but yea best to confirm things work as expected!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions