Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[cuda] vectorized gamma and beta loading in vectorized_layer_norm #107287

Closed
wants to merge 11 commits into from

Conversation

valentinandrei
Copy link
Contributor

@valentinandrei valentinandrei commented Aug 16, 2023

Improves the performance of vectorized_layer_norm by vectorizing access to gamma and beta buffers. This uses 128 bit load instructions which improves memory bandwidth. The speedup is ~3% on average and there are no obvious regressions on any problem sizes.

Used the following code to test:

import torch
from torch.utils.benchmark import Compare, Timer  # @manual

l_inputs = [
    (32, 32),
    (64, 32),
    (256, 128),
    (512, 1024),
    (1024, 2048),
    (2048, 2048),
    (4096, 16384),
    (70000, 64),
    (131072, 512),
    (1000, 520),
    (4005, 4005),
    (10000, 1000),
    (1024, 10000),
    (8192, 4096),
    (10000, 10000),
    (3072, 10000),
    (6144, 10000),
    (1024, 20000),
    (1024, 20000),
    (512, 1536),
    (512, 6144),
    (512, 10240),
    (1000, 1000),
    (2000, 2000),
    (10240, 10240),
    (384, 128),
    (2048, 1024),
    (267, 513),
    (67, 123479),
    (1024, 123479),
    (2048, 66679),
    (200, 256),
    (1000, 256),
    (6000, 256),
    (6272, 256),
    (200, 512),
    (1000, 512),
    (6000, 512),
    (6272, 512),
    (200, 1024),
    (1000, 1024),
    (6000, 1024),
    (6272, 1024),
    (200, 2048),
    (1000, 2048),
    (6000, 2048),
    (6272, 2048),
    (200, 3072),
    (1000, 3072),
    (6000, 3072),
    (6272, 3072),
]


def run_model_on_device(fs, X, gO, device_string, numeric_type):
    ln = torch.nn.LayerNorm((fs,), device=device_string, dtype=numeric_type)
    ln.reset_parameters()
    X.grad = None
    ln.zero_grad(set_to_none=True)
    out = ln(X)
    out.backward(gO)
    return (ln.weight.grad, ln.bias.grad)


def run_correctness_test(eps_weight, eps_bias):
    dtype = torch.float
    for val in l_inputs:
        bs = val[0]
        fs = val[1]
        mean_adjustment = torch.randn(fs, device="cpu", dtype=torch.float)
        X = mean_adjustment * torch.randn(
            bs, fs, device="cpu", dtype=torch.float, requires_grad=True
        )

        X = X.detach().requires_grad_()
        gO = torch.rand_like(X)
        X_gpu = X.to("cuda")
        X_gpu = X_gpu.detach().requires_grad_()
        gO_gpu = gO.to("cuda")
        gO_gpu = gO_gpu.detach().requires_grad_()

        grad_cpu_ref = run_model_on_device(fs, X, gO, "cpu", dtype)
        grad_gpu = run_model_on_device(fs, X_gpu, gO_gpu, "cuda", dtype)
        weight_grad_gpu_target = grad_gpu[0].detach().to("cpu")
        bias_grad_gpu_target = grad_gpu[1].detach().to("cpu")

        weight_delta = torch.abs(grad_cpu_ref[0] - weight_grad_gpu_target)
        weight_mismatches = (weight_delta >= eps_weight).nonzero()
        weight_mismatch_pct = len(weight_mismatches) / len(weight_delta) * 100

        bias_delta = torch.abs(grad_cpu_ref[1] - bias_grad_gpu_target)
        bias_mismatches = (bias_delta >= eps_bias).nonzero()
        bias_mismatch_pct = len(bias_mismatches) / len(bias_delta) * 100

        print(
            "Size ({} x {}) mismatch percentage: weight {:3.2f} bias {:3.2f}".format(
                fs, bs, weight_mismatch_pct, bias_mismatch_pct
            )
        )


# Run the correctness tests
run_correctness_test(0.01, 0.01)

# Run the performance tests. We need to run this at global scope because otherwise
# the `ln` and `gO` objects are likely removed by the JIT compiler
results = []
for dtype in (torch.float, torch.half):
    for val in l_inputs:
        bs = val[0]
        fs = val[1]
        ln = torch.nn.LayerNorm((fs,), device="cuda", dtype=dtype)
        X = torch.randn(bs, fs, device="cuda", dtype=dtype, requires_grad=True)
        gO = torch.rand_like(X)
        stmtfwd = "ln(X)"
        stmtfwdbwd = (
            "X.grad=None; ln.zero_grad(set_to_none=True); out = ln(X); out.backward(gO)"
        )
        tfwd = Timer(
            stmt=stmtfwd,
            label="ln",
            sub_label=f"{bs:5}, {fs:5}",
            description=f"fwd, {dtype}",
            globals=globals(),
        )
        tfwdbwd = Timer(
            stmt=stmtfwdbwd,
            label="ln",
            sub_label=f"{bs:5}, {fs:5}",
            description=f"fwdbwd, {dtype}",
            globals=globals(),
        )
        for t in (tfwd, tfwdbwd):
            results.append(t.blocked_autorange())
    print(fs, end="\r")
c = Compare(results)
c.print()

@pytorch-bot pytorch-bot bot added the release notes: cuda release notes category label Aug 16, 2023
@pytorch-bot
Copy link

pytorch-bot bot commented Aug 16, 2023

🔗 Helpful Links

🧪 See artifacts and rendered test results at hud.pytorch.org/pr/107287

Note: Links to docs will display an error until the docs builds have been completed.

✅ No Failures

As of commit 19d91ad with merge base 8298720 (image):
💚 Looks good so far! There are no failures yet. 💚

This comment was automatically generated by Dr. CI and updates every 15 minutes.

@valentinandrei
Copy link
Contributor Author

cc: @malfet

@malfet
Copy link
Contributor

malfet commented Aug 16, 2023

@pytorchbot merge

@pytorch-bot pytorch-bot bot added the ciflow/trunk Trigger trunk jobs on your pull request label Aug 16, 2023
@pytorchmergebot
Copy link
Collaborator

Merge started

Your change will be merged once all checks pass (ETA 0-4 Hours).

Learn more about merging in the wiki.

Questions? Feedback? Please reach out to the PyTorch DevX Team

Advanced Debugging
Check the merge workflow status
here

@pytorchmergebot
Copy link
Collaborator

Merge failed

Reason: 1 jobs have failed, first few of them are: trunk / linux-focal-rocm5.6-py3.8 / test (default, 3, 3, linux.rocm.gpu)

Details for Dev Infra team Raised by workflow job

@vadimkantorov
Copy link
Contributor

vadimkantorov commented Aug 16, 2023

Also found this blog post on optimizing LayerNorm perf: https://oneflow2020.medium.com/how-to-implement-an-efficient-layernorm-cuda-kernel-oneflow-performance-optimization-731e91a285b8 (from the end of 2021)

Not sure if the results still stand, but it showed that FusedLayerNorm from apex was still faster that PyTorch, and that their impl is even faster than apex (so this oneflow was 2x faster than pytorch). If this is the case, worth borrowing their impl?

And yep, they are also using vectorized loads for CUDA. Their code is licensed Apache2 and is available at https://github.com/Oneflow-Inc/oneflow/blob/2d24fe08be1b1bedcc22fb409c5d688924ce89fc/oneflow/user/kernels/layer_norm_gpu_kernel.cu

Related:

@valentinandrei
Copy link
Contributor Author

Also found this blog post on optimizing LayerNorm perf: https://oneflow2020.medium.com/how-to-implement-an-efficient-layernorm-cuda-kernel-oneflow-performance-optimization-731e91a285b8 (from the end of 2021)

Not sure if the results still stand, but it showed that FusedLayerNorm from apex was still faster that PyTorch, and that their impl is even faster than apex (so this oneflow was 2x faster than pytorch). If this is the case, worth borrowing their impl?

And yep, they are also using vectorized loads for CUDA. Their code is licensed Apache2 and is available at https://github.com/Oneflow-Inc/oneflow/blob/2d24fe08be1b1bedcc22fb409c5d688924ce89fc/oneflow/user/kernels/layer_norm_gpu_kernel.cu

Related:

Thanks for the pointer. I'll take a look to see if it's applicable and follow-up.

@valentinandrei
Copy link
Contributor Author

@pytorchbot rebase -b main

@pytorchmergebot
Copy link
Collaborator

@pytorchbot started a rebase job onto refs/remotes/origin/main. Check the current status here

@pytorchmergebot
Copy link
Collaborator

Successfully rebased main onto refs/remotes/origin/main, please pull locally before adding more changes (for example, via git checkout main && git pull --rebase)

@valentinandrei
Copy link
Contributor Author

@pytorchbot merge

@pytorchmergebot
Copy link
Collaborator

Merge started

Your change will be merged once all checks pass (ETA 0-4 Hours).

Learn more about merging in the wiki.

Questions? Feedback? Please reach out to the PyTorch DevX Team

Advanced Debugging
Check the merge workflow status
here

@pytorchmergebot
Copy link
Collaborator

Merge failed

Reason: 2 jobs have failed, first few of them are: trunk / linux-focal-rocm5.6-py3.8 / test (default, 2, 3, linux.rocm.gpu), trunk / linux-focal-rocm5.6-py3.8 / test (default, 3, 3, linux.rocm.gpu)

Details for Dev Infra team Raised by workflow job

@valentinandrei
Copy link
Contributor Author

@pytorchbot merge

@pytorchmergebot
Copy link
Collaborator

Merge started

Your change will be merged once all checks pass (ETA 0-4 Hours).

Learn more about merging in the wiki.

Questions? Feedback? Please reach out to the PyTorch DevX Team

Advanced Debugging
Check the merge workflow status
here

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
ciflow/trunk Trigger trunk jobs on your pull request Merged release notes: cuda release notes category
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants